[
  {
    "path": ".clang-format",
    "content": "---\nLanguage:        Cpp\n# BasedOnStyle:  LLVM\nAccessModifierOffset: -2\nAlignAfterOpenBracket: Align\nAlignConsecutiveAssignments: false\nAlignConsecutiveDeclarations: false\nAlignEscapedNewlines: Right\nAlignOperands:   true\nAlignTrailingComments: true\nAllowAllParametersOfDeclarationOnNextLine: true\nAllowShortBlocksOnASingleLine: false\nAllowShortCaseLabelsOnASingleLine: false\nAllowShortFunctionsOnASingleLine: All\nAllowShortIfStatementsOnASingleLine: false\nAllowShortLoopsOnASingleLine: false\n# AlwaysBreakAfterDefinitionReturnType: None # option is deprecated\nAlwaysBreakAfterReturnType: AllDefinitions\nAlwaysBreakBeforeMultilineStrings: false\nAlwaysBreakTemplateDeclarations: MultiLine\nBinPackArguments: false\nBinPackParameters: true\nBraceWrapping:\n  AfterCaseLabel: true\n  AfterClass:      true\n  AfterControlStatement: true\n  AfterEnum:       true\n  AfterFunction:   true\n  AfterNamespace:  true\n  AfterObjCDeclaration: true\n  AfterStruct:     true\n  AfterUnion:      true\n  AfterExternBlock: true\n  BeforeCatch:     true\n  BeforeElse:      true\n  IndentBraces:    false\n  SplitEmptyFunction: true\n  SplitEmptyRecord: true\n  SplitEmptyNamespace: true\nBreakBeforeBinaryOperators: None\nBreakBeforeBraces: Custom\nBreakBeforeInheritanceComma: false # N/A C++\nBreakInheritanceList: BeforeColon\nBreakBeforeTernaryOperators: false\nBreakConstructorInitializersBeforeComma: false\nBreakConstructorInitializers: BeforeColon\nBreakAfterJavaFieldAnnotations: false # N/A Java\nBreakStringLiterals: true\nColumnLimit:     100\nCommentPragmas:  '^ TS Pragma:' #For future proofing\nCompactNamespaces: false # N/A c++\nConstructorInitializerAllOnOneLineOrOnePerLine: false # N/A C++\nConstructorInitializerIndentWidth: 40 # N/A C++\nContinuationIndentWidth: 4\nCpp11BracedListStyle: false # see catalog.c array struct assigns for an example\nDerivePointerAlignment: false # always use Right\nDisableFormat:   false # haha\n# ExperimentalAutoDetectBinPacking: false #the docs say not to have this in config file\nFixNamespaceComments: true # N/A C++\nForEachMacros:\n  - foreach\n  - forboth\n  - for_each_cell\n  - for_both_cell\n  - forthree\nIncludeBlocks:   Preserve # separate include blocks will not be merged\nIncludeCategories: # we want to ensure c.h and postgres.h appear first\n  - Regex:            '^<(string|unistd)\\.h>'\n    Priority:        -2\n  - Regex:            '^<postgres\\.h>'\n    Priority:        -1\n  - Regex:            '^(<|\")compat/compat\\.h'\n    Priority:        -2\n  - Regex:            '^\"compat/compat-msvc-enter\\.h'\n    Priority:        -1 \n  - Regex:            '^\"compat/compat-msvc-exit\\.h'\n    Priority:         2000000000\n  - Regex:            '.*'\n    Priority:         1\nIncludeIsMainRegex: '' # filename_<suffix> will be seen as the primary include\nIndentCaseLabels: true\nIndentPPDirectives: None # do not indent preprocessor directives after the '#'\nIndentWidth:     4\nIndentWrappedFunctionNames: false # we do not indent the function name in the declaration\nJavaScriptQuotes: Double # N/A js\nJavaScriptWrapImports: true # N/A js\nKeepEmptyLinesAtTheStartOfBlocks: false\nMacroBlockBegin: '' # regex of macros that behave like '{'\nMacroBlockEnd:   '' # regex of macros that behave like '}'\nMaxEmptyLinesToKeep: 1\nNamespaceIndentation: None # N/A c++\nObjCBinPackProtocolList: Auto # N/A objC\nObjCBlockIndentWidth: 2 # N/A objC\nObjCSpaceAfterProperty: false # N/A objC\nObjCSpaceBeforeProtocolList: true # N/A objC\nPenaltyBreakAssignment: 2\nPenaltyBreakBeforeFirstCallParameter: 10000\nPenaltyBreakComment: 300\nPenaltyBreakFirstLessLess: 120\nPenaltyBreakString: 1000\nPenaltyBreakTemplateDeclaration: 10\nPenaltyExcessCharacter: 1000000\nPenaltyReturnTypeOnItsOwnLine: 60\nPointerAlignment: Right # as in char *foo;\nReflowComments:  true # break up long comments into multiple lines\nSortIncludes: CaseInsensitive # sort includes\nSortUsingDeclarations: false # N/A c++\nSpaceAfterCStyleCast: true\nSpaceAfterTemplateKeyword: false # N/A c++\nSpaceBeforeAssignmentOperators: true\nSpaceBeforeCpp11BracedList: false\nSpaceBeforeCtorInitializerColon: true # N/A c++\nSpaceBeforeInheritanceColon: false # N/A c++\nSpaceBeforeParens: ControlStatements\nSpaceBeforeRangeBasedForLoopColon: true # N/A C++\nSpaceInEmptyParentheses: false\nSpacesBeforeTrailingComments: 1\nSpacesInAngles:  false # N/A c++\nSpacesInContainerLiterals: true # N/A c++\nSpacesInCStyleCastParentheses: false\nSpacesInParentheses: false\nSpacesInSquareBrackets: false\nStandard:        Cpp11\nTabWidth:        4\nUseTab:          Always\n...\n"
  },
  {
    "path": ".codecov.yml",
    "content": "# If it says that commit YAML is invalid again,\n# validate it with:\n# curl --data-binary @.codecov.yml https://codecov.io/validate\n# More docs: https://docs.codecov.com/docs/codecov-yaml#repository-yaml\nignore:\n  - \"test/src\"\ncoverage:\n  status:\n    project:\n      default:\n        flags:\n          - pr\n"
  },
  {
    "path": ".dir-locals.el",
    "content": ";; see also src/tools/editors/emacs.samples in the PostgreSQL source\n;; tree for more complete settings\n\n((c-mode . ((c-basic-offset . 4)\n            (c-file-style . \"bsd\")\n            (fill-column . 78)\n            (indent-tabs-mode . t)\n            (tab-width . 4)))\n (diff-mode .  ((tab-width . 4)))\n (dsssl-mode . ((indent-tabs-mode . nil)))\n (nxml-mode . ((indent-tabs-mode . nil)))\n (perl-mode . ((perl-indent-level . 4)\n               (perl-continued-statement-offset . 4)\n               (perl-continued-brace-offset . 4)\n               (perl-brace-offset . 0)\n               (perl-brace-imaginary-offset . 0)\n               (perl-label-offset . -2)\n               (indent-tabs-mode . t)\n               (tab-width . 4)))\n (sgml-mode . ((fill-column . 78)\n               (indent-tabs-mode . nil)))\n (sql-mode . ((sql-product . postgres)))\n )\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\nend_of_line = lf\n\n[*.{c,h}]\nindent_style = tab\nindent_size = 4\n\n[*.out]\ntrim_trailing_whitespace = false\ninsert_final_newline = false\n"
  },
  {
    "path": ".git-blame-ignore-revs",
    "content": "# This file contains a list of commits that are not likely what you\n# are looking for in a blame, such as mass reformatting or renaming.\n# You can set this file as a default ignore file for blame by running\n# the following command.\n#\n# $ git config blame.ignoreRevsFile .git-blame-ignore-revs\n\n# formatting with pgindent\n32c45b75b27e3f690236a9d1c8d13a025316ad2f\n# Fix formatting to comply with pgindent\n2ec065b53823e50dd1ac1d7cf925ae5f90e293ea\n# Fix formatting issues\n3e42150e3b0402ae865d9a827d8d178568a0d27e\n# Run clang-format on code\n34edba16a9385a4b0353e8e07a19dba98d7e3fb9\n# Run pg_format on SQL files\n3f5872ec61650519e2c5e6fe1dfb60d07696cac7\n# Fix various misspellings\n68aec9593c0f37dddbaa4f2e2b34a9ba3f5b11d9\n# Switch to clang-format-14\n7758f5959c8ed64499ab0e6bb66c30464b11dd81\n# Remove trailing whitespaces from test code\na4356f342f1732857a1d8057f71219b50f1919b2\n# Cosmetic changes to create.c\n230f368f4e5d146ce5f919cc5999b236997befaf\n\n# Adding python and yaml linters\n9133319081aef92705f1405087822fc281d215d4\n44cd71a602ba96029001de6e97a1b44488730080\nf75a51def79796ff7fef58ec950c859fe4e71618\n21a3f8206c0de98932867096637c7d1e3d04d925\n\n# Clang-tidy\nf862212c8ca19b1af56c7608a68f22b7dd0c985e\n05ba1cf22f0dc9232069b566dd23c3edb2cbaee4\necf34132c69e1709cd393eab43b5fcbfd7c201db\ne75274ee7c6eef1dafc9b4f4d9f71e8e88f76813\na3ef0384655d57200e83ad7b13c91a31177b97c1\n\n"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "The Timescale Code of Conduct can be found at <https://www.timescale.com/code-of-conduct>.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "---\nname: Bug report\ndescription: Is something not working? Help us fix it!\ntitle: \"[Bug]: <Title>\"\nlabels: [\"bug\", \"triage\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for taking the time to fill out this bug report!\n\n  - type: dropdown\n    id: type\n    attributes:\n      label: What type of bug is this?\n      multiple: true\n      options:\n        - Configuration\n        - Crash\n        - Data corruption\n        - Incorrect result\n        - Locking issue\n        - Performance issue\n        - Unexpected error\n        - Other\n    validations:\n      required: true\n\n  - type: dropdown\n    id: subsystem\n    attributes:\n      label: What subsystems and features are affected?\n      description: You can pick multiple subsystems and features.\n      multiple: true\n      options:\n        - Adaptive chunking\n        - Background worker\n        - Backup\n        - Build system\n        - Command processing\n        - Compression\n        - Configuration\n        - Continuous aggregate\n        - Data ingestion\n        - Gapfill\n        - Packaging\n        - Partitioning\n        - Platform/OS\n        - Policy\n        - Query executor\n        - Query planner\n        - Replication\n        - Restore\n        - SkipScan\n        - Telemetry\n        - User-Defined Action (UDA)\n        - Other\n    validations:\n      required: true\n        \n  - type: textarea\n    id: what-happened\n    attributes:\n      label: What happened?\n      description: |\n        Tell us what happened and also what you would have expected to\n        happen instead.\n      placeholder: \"Describe the bug\"\n    validations:\n      required: true\n      \n  - type: input\n    id: timescaledb-version\n    attributes:\n      label: TimescaleDB version affected\n      description: Let us know what version of TimescaleDB you are running.\n      placeholder: \"2.5.0\"\n    validations:\n      required: true\n      \n  - type: input\n    id: postgresql-version\n    attributes:\n      label: PostgreSQL version used\n      description: Let us know what version of PostgreSQL you are running.\n      placeholder: \"17.4\"\n    validations:\n      required: true\n      \n  - type: input\n    id: os\n    attributes:\n      label: What operating system did you use?\n      description: |\n        Please provide OS, version, and architecture. For example:\n        Windows 10 x64, Ubuntu 21.04 x64, Mac OS X 10.5 ARM, Rasperry\n        Pi i386, etc.\n      placeholder: \"Ubuntu 21.04 x64\"\n    validations:\n      required: true\n      \n  - type: dropdown\n    id: installation\n    attributes:\n      label: What installation method did you use?\n      multiple: true\n      options:\n        - Deb/Apt\n        - Docker\n        - Homebrew\n        - RPM\n        - Source\n        - Other\n        - Not applicable\n    validations:\n      required: true\n      \n  - type: dropdown\n    id: platform\n    attributes:\n      label: What platform did you run on?\n      multiple: true\n      options:\n        - Amazon Web Services (AWS)\n        - Google Cloud Platform (GCP)\n        - Managed Service for TimescaleDB (MST/Aiven)\n        - Microsoft Azure Cloud\n        - On prem/Self-hosted\n        - Timescale Cloud\n        - Other\n        - Not applicable\n    validations:\n      required: true\n      \n  - type: textarea\n    id: logs\n    attributes:\n      label: Relevant log output and stack trace\n      description: |\n        Please copy and paste any relevant log output or a stack\n        trace. This will be automatically formatted into code, so no\n        need for backticks.\n      render: bash\n      \n  - type: textarea\n    id: reproduce\n    attributes:\n      label: How can we reproduce the bug?\n      description: |\n        Please try to provide step-by-step instructions how to\n        reproduce the issue. If possible, provide scripts that we can\n        run to trigger the bug.\n      render: bash\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: true\ncontact_links:\n  - name: Support Request\n    url: https://www.timescale.com/support\n    about: Support request or question relating to TimescaleDB\n  - name: Timescale Community Slack\n    url: https://slack.timescale.com/\n    about: Get free help from the TimescaleDB community\n  - name: Timescale Community Forum\n    url: https://forum.timescale.com/\n    about: Get free help from the TimescaleDB community\n  - name: Documentation request\n    url: https://github.com/timescale/docs/issues/new/choose\n    about: Request a change to our documentation\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/enhancement.yml",
    "content": "---\nname: Enhancement\ndescription: Suggest an enhancement to existing functionality\ntitle: \"[Enhancement]: <Title>\"\nlabels: [ \"enhancement\" ]\nbody:\n  - type: dropdown\n    id: type\n    attributes:\n      label: What type of enhancement is this?\n      multiple: true\n      options:\n        - API improvement\n        - Configuration\n        - Performance\n        - Refactor\n        - Tech debt reduction\n        - User experience\n        - Other\n    validations:\n      required: true\n\n  - type: dropdown\n    id: subsystem\n    attributes:\n      label: What subsystems and features will be improved?\n      description: You can pick multiple subsystems and features.\n      multiple: true\n      options:\n        - Adaptive chunking\n        - Background worker\n        - Build system\n        - Command processing\n        - Compression\n        - Configuration\n        - Continuous aggregate\n        - Data ingestion\n        - Gapfill\n        - Packaging\n        - Partitioning\n        - Platform/OS\n        - Policy\n        - Query executor\n        - Query planner\n        - Replication\n        - SkipScan\n        - Telemetry\n        - User-Defined Action (UDA)\n        - Other\n    validations:\n      required: true\n\n  - type: textarea\n    id: what\n    attributes:\n      label: What does the enhancement do?\n      description: |\n        Give a high-level overview of how you suggest to improve an\n        existing feature or functionality.\n    validations:\n     required: true\n\n  - type: textarea\n    id: implementation\n    attributes:\n      label: Implementation challenges\n      description: |\n        Share any ideas of how to implement the enhancement.\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature.yml",
    "content": "---\nname: Feature request\ndescription: Suggest a new feature for TimescaleDB\ntitle: \"[Feature]: <Feature name>\"\nlabels: [ \"feature-request\" ]\nbody:\n  - type: markdown\n    id: info\n    attributes:\n      value: |\n        Only use this template to suggest a new feature that doesn't already exist in TimescaleDB.\n        For enhancements to existing features, use the \"Enhancement\" issue template. For bugs,\n        use the bug report template.\n\n  - type: textarea\n    id: what\n    attributes:\n      label: What problem does the new feature solve?\n      description: |\n        Describe the problem and why it is important to solve. Did you consider alternative\n        solutions, perhaps outside the database? Why is it better to add the feature to\n        TimescaleDB?\n    validations:\n      required: true\n\n  - type: textarea\n    id: how\n    attributes:\n      label: What does the feature do?\n      description: |\n        Give a high-level overview of what the feature does and how it would work.\n    validations:\n     required: true\n\n  - type: textarea\n    id: implementation\n    attributes:\n      label: Implementation challenges\n      description: |\n        If you have ideas of how to implement the feature, and any particularly\n        challenging issues to overcome, then provide them here.\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE/pull_request_template.md",
    "content": "- [ ] Add [CHANGELOG](https://github.com/timescale/timescaledb/blob/main/CHANGELOG.md) updates\n- [ ] Needs [documentation](https://github.com/timescale/docs) updates\n\nFixes #<issue number>.\n\n---\nCut everything below the above line.\n\n## Guidelines for Pull requests\n\nChecklist for a pull request:\n\n- Try to follow [the seven rules of a great Git commit\nmessage](https://chris.beams.io/posts/git-commit/).\n- Ideally, the PR should be merged as a single commit.\n- Rebase on latest main code.\n- Reference the issue(s) resolved with Fixes #<issue number>, or\n  Closes #<issue number>.\n- Make sure the PR has appropriate CHANGELOG updates, including thanks\n  to people that reported issues.\n- Include appropriate documentation changes.\n- Two approvals are necessary to merge.\n\n### The seven rules of a great Git commit message\n\n1. Separate subject from body with a blank line\n2. Limit the subject line to 50 characters\n3. Capitalize the subject line\n4. Do not end the subject line with a period\n5. Use the imperative mood in the subject line\n6. Wrap the body at 72 characters\n7. Use the body to explain what and why vs. how\n\n### Single commit PR\n\nThe simplify reviewing and to protect against accidental merges of\nwork-in-progress commits, the CI system enforces that a PR is merged\nas a single commit. However, sometimes a multi-commit PR is warranted\nif each commit is a distinct change that makes sense to submit in the\nsame PR. The single-commit enforcement can be disabled by adding the\nfollowing trailer to the PR description (note that the trailer should\nnot be in the commit message):\n\nDisable-check: commit-count\n\nGenerally, though, small single-commit PRs are preferred over large\nmulti-commit PRs so that PRs are easier to review. It is also good to\navoid multiple commits that have unrelated changes or a lot of\nwork-in-progress changes that are not appropriate for a well-formatted\ncommit log.\n\n### Documentation\n\nIf the code change adds a new feature, limitation, or change in\nbehavior, the PR might need to include documentation or a separate\nfollow-up PR to the [documentation repository](https://github.com/timescale/docs).\n"
  },
  {
    "path": ".github/ci_settings.py",
    "content": "#!/usr/bin/env python\n\n#  This file and its contents are licensed under the Apache License 2.0.\n#  Please see the included NOTICE for copyright information and\n#  LICENSE-APACHE for a copy of the license.\n\n# Common settings for our CI jobs\n#\n# EARLIEST is the minimum postgres version required when building from source\n# LATEST is the maximum postgres version that is supported\n# ABI_MIN is the minimum postgres version required when the extension was build against LATEST\n#\n\nPG15_EARLIEST = \"15.10\"\nPG15_LATEST = \"15.17\"\nPG15_ABI_MIN = \"15.10\"\n\nPG16_EARLIEST = \"16.6\"\nPG16_LATEST = \"16.13\"\nPG16_ABI_MIN = \"16.6\"\n\nPG17_EARLIEST = \"17.2\"\nPG17_LATEST = \"17.9\"\nPG17_ABI_MIN = \"17.2\"\n\nPG18_EARLIEST = \"18.0\"\nPG18_LATEST = \"18.3\"\nPG18_ABI_MIN = \"18.0\"\n\nPG_LATEST = [PG15_LATEST, PG16_LATEST, PG17_LATEST, PG18_LATEST]\nPG_LATEST_ONLY = [PG17_LATEST]\n"
  },
  {
    "path": ".github/codespell-ignore-words",
    "content": "brin\nclos\ninh\ninout\nisnt\nlarg\nrelaction\ntextin\n"
  },
  {
    "path": ".github/filters.yaml",
    "content": "shell:\n  - '**.sh'\n  - .github/workflows/shellcheck.yaml\nsql:\n  - 'sql/**'\n  - '.github/**'\nsrc:\n  - 'CMakeLists.txt'\n  - 'cmake/**'\n  - 'coverage/**'\n  - 'scripts/**'\n  - 'sql/**'\n  - 'src/**'\n  - 'test/**'\n  - 'tsl/**'\n  - '.github/**'\n\nwindows-workflow:\n  - '.github/workflows/windows-build-and-test.yaml'\n"
  },
  {
    "path": ".github/gh_config_reader.py",
    "content": "#!/usr/bin/env python\n\n#  This file and its contents are licensed under the Apache License 2.0.\n#  Please see the included NOTICE for copyright information and\n#  LICENSE-APACHE for a copy of the license.\n\n# We hash the .github directory to understand whether our Postgres build cache\n# can still be used, and the __pycache__ files interfere with that, so don't\n# create them.\nimport sys\n\nsys.dont_write_bytecode = True\n\nimport ci_settings\nimport json\nimport os\n\n# generate commands to set github action variables\nfor key in dir(ci_settings):\n    if not key.startswith(\"__\"):\n        value = getattr(ci_settings, key)\n        with open(os.environ[\"GITHUB_OUTPUT\"], \"a\", encoding=\"utf-8\") as output:\n            print(str.format(\"{0}={1}\", key, json.dumps(value)), file=output)\n"
  },
  {
    "path": ".github/gh_matrix_builder.py",
    "content": "#!/usr/bin/env python\n\n#  This file and its contents are licensed under the Apache License 2.0.\n#  Please see the included NOTICE for copyright information and\n#  LICENSE-APACHE for a copy of the license.\n\n# Python script to dynamically generate matrix for github action\n\n# Since we want to run additional test configurations when triggered\n# by a push to prerelease_test or by cron but github actions don't\n# allow a dynamic matrix via yaml configuration, we generate the matrix\n# with this python script. While we could always have the full matrix\n# and have if checks on every step that would make the actual checks\n# harder to browse because the workflow will have lots of entries and\n# only by navigating into the individual jobs would it be visible\n# if a job was actually run.\n\n# We hash the .github directory to understand whether our Postgres build cache\n# can still be used, and the __pycache__ files interfere with that, so don't\n# create them.\nimport sys\n\nsys.dont_write_bytecode = True\n\nimport json\nimport os\nimport random\nimport subprocess\nfrom ci_settings import (\n    PG15_EARLIEST,\n    PG15_LATEST,\n    PG16_EARLIEST,\n    PG16_LATEST,\n    PG17_EARLIEST,\n    PG17_LATEST,\n    PG18_EARLIEST,\n    PG18_LATEST,\n    PG_LATEST,\n)\n\n# github event type which is either push, pull_request or schedule\nevent_type = sys.argv[1]\npull_request = event_type == \"pull_request\"\n\nm = {\n    \"include\": [],\n}\n\n# Ignored tests that are known to be flaky or have known issues.\ndefault_ignored_tests = {\n    \"bgw_db_scheduler\",\n    \"bgw_db_scheduler_fixed\",\n    \"bgw_job_stat_history\",\n    \"bgw_launcher\",\n    \"telemetry\",\n    \"memoize\",\n    \"net\",\n}\n\n# Some tests are ignored on PG earlier than 17 due to broken MergeAppend cost model there.\nignored_before_pg17 = default_ignored_tests | {\"merge_append_partially_compressed\"}\n\n# Some tests are ignored on PG earlier than 16 due to changes in default relation\n# size estimates.\nignored_before_pg16 = default_ignored_tests | {\"columnar_scan_cost\"}\n\n# Tests that we do not run as part of a Flake tests\nflaky_exclude_tests = {\n    # Not executed as a flake test since it easily exhausts available\n    # background worker slots.\n    \"bgw_launcher\",\n    # Not executed as a flake test since it takes a very long time and\n    # easily interferes with other tests.\n    \"bgw_scheduler_restart\",\n}\n\n\n# helper functions to generate matrix entries\n# the release and apache config inherit from the\n# debug config to reduce repetition\ndef build_debug_config(overrides):\n    # llvm version and clang versions must match otherwise\n    # there will be build errors this is true even when compiling\n    # with gcc as clang is used to compile the llvm parts.\n    #\n    # Strictly speaking, WARNINGS_AS_ERRORS=ON is not needed here, but\n    # we add it as a precaution. Intention is to have at least one\n    # release and one debug build with WARNINGS_AS_ERRORS=ON so that we\n    # capture warnings generated due to changes in the code base or the\n    # compiler.\n    base_config = dict(\n        {\n            \"build_type\": \"Debug\",\n            \"cc\": \"gcc\",\n            \"clang\": \"clang\",\n            \"coverage\": False,\n            \"cxx\": \"g++\",\n            \"extra_packages\": \"clang llvm llvm-dev\",\n            \"ignored_tests\": default_ignored_tests,\n            \"name\": \"Debug\",\n            \"os\": \"ubuntu-22.04\",\n            \"pg_extra_args\": \"--enable-debug --enable-cassert --with-llvm LLVM_CONFIG=llvm-config\",\n            \"pg_extensions\": \"postgres_fdw test_decoding\",\n            \"installcheck\": True,\n            \"pginstallcheck\": True,\n            \"tsdb_build_args\": \"-DWARNINGS_AS_ERRORS=ON -DREQUIRE_ALL_TESTS=ON\",\n        }\n    )\n    base_config.update(overrides)\n    return base_config\n\n\n# We build this release configuration with WARNINGS_AS_ERRORS=ON to\n# make sure that we can build with -Werrors even for release\n# builds. This will capture some cases where warnings are generated\n# for release builds but not for debug builds.\ndef build_release_config(overrides):\n    release_config = dict(\n        {\n            \"name\": \"Release\",\n            \"build_type\": \"RelWithDebInfo\",\n            \"tsdb_build_args\": \"-DWARNINGS_AS_ERRORS=ON -DREQUIRE_ALL_TESTS=ON\",\n            \"coverage\": False,\n        }\n    )\n    release_config.update(overrides)\n    return build_debug_config(release_config)\n\n\ndef build_without_telemetry(overrides):\n    config = dict(\n        {\n            \"name\": \"ReleaseWithoutTelemetry\",\n            \"coverage\": False,\n        }\n    )\n    config.update(overrides)\n    config = build_release_config(config)\n    config[\"tsdb_build_args\"] += \" -DUSE_TELEMETRY=OFF\"\n    return config\n\n\ndef build_apache_config(overrides):\n    apache_config = dict(\n        {\n            \"name\": \"ApacheOnly\",\n            \"build_type\": \"RelWithDebInfo\",\n            \"tsdb_build_args\": \"-DWARNINGS_AS_ERRORS=ON -DREQUIRE_ALL_TESTS=ON -DAPACHE_ONLY=1\",\n            \"coverage\": False,\n        }\n    )\n    apache_config.update(overrides)\n    return build_debug_config(apache_config)\n\n\ndef macos_config(overrides):\n    macos_ignored_tests = {\n        \"bgw_launcher\",\n        \"pg_dump\",\n        \"compression_bgw\",\n        \"compressed_collation\",\n    }\n    openssl_path = \"/usr/local/opt/openssl@3\"\n    base_config = dict(\n        {\n            \"cc\": \"clang\",\n            \"clang\": \"clang\",\n            \"coverage\": False,\n            \"cxx\": \"clang++\",\n            \"extra_packages\": \"\",\n            \"ignored_tests\": default_ignored_tests.union(macos_ignored_tests),\n            \"os\": \"macos-15-intel\",\n            \"pg_extra_args\": (\n                \" --enable-debug\"\n                f\" --with-libraries={openssl_path}/lib\"\n                f\" --with-includes={openssl_path}/include\"\n                \" --without-icu\"\n            ),\n            \"pg_extensions\": \"postgres_fdw test_decoding\",\n            \"pginstallcheck\": True,\n            \"tsdb_build_args\": (\n                \" -DASSERTIONS=ON\"\n                \" -DREQUIRE_ALL_TESTS=ON\"\n                f\" -DOPENSSL_ROOT_DIR={openssl_path}\"\n            ),\n        }\n    )\n    base_config.update(overrides)\n    return base_config\n\n\n# always test debug build on latest of all supported pg versions\nm[\"include\"].append(\n    build_debug_config({\"pg\": PG15_LATEST, \"ignored_tests\": ignored_before_pg16})\n)\n\nm[\"include\"].append(\n    build_debug_config({\"pg\": PG16_LATEST, \"ignored_tests\": ignored_before_pg17})\n)\n\nm[\"include\"].append(build_debug_config({\"pg\": PG17_LATEST}))\n\nm[\"include\"].append(build_debug_config({\"pg\": PG18_LATEST, \"coverage\": True}))\n\n# Also test on ARM. The custom arm64 runner is only available in the\n# timescale/timescaledb repository.\n# See the available runners here:\n# https://github.com/timescale/timescaledb/actions/runners\nif os.environ.get(\"GITHUB_REPOSITORY\") == \"timescale/timescaledb\":\n    m[\"include\"].append(\n        build_debug_config(\n            {\n                \"pg\": PG18_LATEST,\n                \"os\": \"timescaledb-runner-arm64\",\n                # We need to enable ARM crypto extensions to build the vectorized grouping\n                # code. The actual architecture for our ARM CI runner is reported as:\n                # -imultiarch aarch64-linux-gnu - -mlittle-endian -mabi=lp64 -march=armv8.2-a+crypto+fp16+rcpc+dotprod\n                \"pg_extra_args\": \"--enable-debug --enable-cassert --without-llvm CFLAGS=-march=armv8.2-a+crypto\",\n            }\n        )\n    )\n\n# test timescaledb with release config on latest postgres release in MacOS\n# we only run compilation tests in pull requests.\nm[\"include\"].append(\n    build_release_config(\n        macos_config(\n            {\n                \"pg\": PG18_LATEST,\n                \"installcheck\": not pull_request,\n                \"pginstallcheck\": not pull_request,\n            }\n        )\n    )\n)\n\n# Test latest postgres release without telemetry. Also run clang-tidy on it\n# because it's the fastest one.\nm[\"include\"].append(\n    build_without_telemetry(\n        {\n            \"pg\": PG18_LATEST,\n            \"cc\": \"clang\",\n            \"cxx\": \"clang++\",\n            \"tsdb_build_args\": \"-DLINTER=ON -DWARNINGS_AS_ERRORS=ON\",\n        }\n    )\n)\n\n# if this is not a pull request e.g. a scheduled run or a push\n# to a specific branch like prerelease_test we add additional\n# entries to the matrix\nif not pull_request:\n    # add debug test for first supported PG15 version\n    m[\"include\"].append(\n        build_debug_config(\n            {\n                \"pg\": PG15_EARLIEST,\n                \"ignored_tests\": ignored_before_pg16 | {\"insert_single\"},\n            }\n        )\n    )\n\n    # add debug test for first supported PG16 version\n    m[\"include\"].append(\n        build_debug_config(\n            {\n                \"pg\": PG16_EARLIEST,\n                \"ignored_tests\": ignored_before_pg17 | {\"insert_single\"},\n            }\n        )\n    )\n\n    # add debug test for first supported PG17 version\n    if PG17_EARLIEST != PG17_LATEST:\n        m[\"include\"].append(\n            build_debug_config(\n                {\n                    \"pg\": PG17_EARLIEST,\n                    \"ignored_tests\": {\"insert_single\"},\n                }\n            )\n        )\n\n    # add debug test for first supported PG18 version\n    if PG18_EARLIEST != PG18_LATEST:\n        m[\"include\"].append(build_debug_config({\"pg\": PG18_EARLIEST}))\n\n    # add debug tests for timescaledb on latest postgres release in MacOS\n    m[\"include\"].append(\n        build_debug_config(\n            macos_config({\"pg\": PG15_LATEST, \"ignored_tests\": ignored_before_pg16})\n        )\n    )\n\n    m[\"include\"].append(\n        build_debug_config(\n            macos_config({\"pg\": PG16_LATEST, \"ignored_tests\": ignored_before_pg17})\n        )\n    )\n\n    m[\"include\"].append(build_debug_config(macos_config({\"pg\": PG17_LATEST})))\n\n    m[\"include\"].append(build_debug_config(macos_config({\"pg\": PG18_LATEST})))\n\n    # add release test for latest pg releases\n    m[\"include\"].append(\n        build_release_config({\"pg\": PG15_LATEST, \"ignored_tests\": ignored_before_pg16})\n    )\n    m[\"include\"].append(\n        build_release_config({\"pg\": PG16_LATEST, \"ignored_tests\": ignored_before_pg17})\n    )\n    m[\"include\"].append(build_release_config({\"pg\": PG17_LATEST}))\n\n    m[\"include\"].append(build_release_config({\"pg\": PG18_LATEST}))\n\n    # add apache only test for latest pg versions\n    for PG_LATEST_VER in PG_LATEST:\n        m[\"include\"].append(build_apache_config({\"pg\": PG_LATEST_VER}))\n\n    # to discover issues with upcoming releases we run CI against\n    # the stable branches of supported PG releases\n    m[\"include\"].append(\n        build_debug_config(\n            {\n                \"pg\": 15,\n                \"ignored_tests\": ignored_before_pg16\n                | {\n                    \"bgw_custom\",\n                    \"bgw_scheduler_restart\",\n                    \"bgw_job_stat_history_errors_permissions\",\n                    \"bgw_job_stat_history_errors\",\n                    \"bgw_job_stat_history\",\n                    \"bgw_db_scheduler_fixed\",\n                    \"bgw_reorder_drop_chunks\",\n                    \"scheduler_fixed\",\n                    \"compress_bgw_reorder_drop_chunks\",\n                },\n                \"snapshot\": \"snapshot\",\n            }\n        )\n    )\n    m[\"include\"].append(\n        build_debug_config(\n            {\n                \"pg\": 16,\n                \"ignored_tests\": ignored_before_pg17,\n                \"snapshot\": \"snapshot\",\n            }\n        )\n    )\n    m[\"include\"].append(\n        build_debug_config(\n            {\n                \"pg\": 17,\n                \"snapshot\": \"snapshot\",\n            }\n        )\n    )\n    m[\"include\"].append(\n        build_debug_config(\n            {\n                \"pg\": 18,\n                \"snapshot\": \"snapshot\",\n            }\n        )\n    )\nelif len(sys.argv) > 2:\n    # Check if we need to check for the flaky tests. Determine which test files\n    # have been changed in the PR. The sql files might include other files that\n    # change independently, and might be .in templates, so it's easier to look\n    # at the output files. They are also the same for the isolation tests.\n    p = subprocess.Popen(\n        f\"git diff --name-only {sys.argv[2]} -- '**expected/*.out'\",\n        stdout=subprocess.PIPE,\n        shell=True,\n    )\n    output, err = p.communicate()\n    p_status = p.wait()\n    if p_status != 0:\n        print(\n            f'git diff failed: code {p_status}, output \"{output}\", stderr \"{err}\"',\n            file=sys.stderr,\n        )\n        sys.exit(1)\n    tests = set()\n    test_count = 1\n    for f in output.decode().split(\"\\n\"):\n        print(f)\n        if not f:\n            continue\n        test_count += 1\n        if test_count > 10:\n            print(\n                f\"too many ({test_count}) changed tests, won't run the flaky check\",\n                file=sys.stderr,\n            )\n            print(\"full list:\", file=sys.stderr)\n            print(output, file=sys.stderr)\n            tests = set()\n            break\n        basename = os.path.basename(f)\n        split = basename.split(\".\")\n        name = split[0]\n        ext = split[-1]\n        if ext == \"out\":\n            # Account for the version number.\n            tests.add(name)\n        else:\n            # Should've been filtered out above.\n            print(\n                f\"unknown extension '{ext}' for test output file '{f}'\", file=sys.stderr\n            )\n            sys.exit(1)\n\n    if tests:\n        to_run = [t for t in list(tests) if t not in flaky_exclude_tests] * 20\n        random.shuffle(to_run)\n        installcheck_args = f'TESTS=\"{\" \".join(to_run)}\"'\n        m[\"include\"].append(\n            build_debug_config(\n                {\n                    \"coverage\": False,\n                    \"installcheck_args\": installcheck_args,\n                    \"name\": \"Flaky Check Debug\",\n                    \"pg\": PG18_LATEST,\n                    \"pginstallcheck\": False,\n                }\n            )\n        )\n\n# generate command to set github action variable\nwith open(os.environ[\"GITHUB_OUTPUT\"], \"a\", encoding=\"utf-8\") as output:\n    print(str.format(\"matrix={0}\", json.dumps(m, default=list)), file=output)\n"
  },
  {
    "path": ".github/workflows/abi.yaml",
    "content": "# Test minimum and maximum ABI compatible postgres version\n#\n# Build timescaledb against specific postgres version and then run our\n# tests with that library loaded in a different postgres version.\n# This is to detect changes in required minimum/maximum postgres versions\n# for our built packages.\n# This test is expected to fail when upstream does ABI incompatible changes\n# in a new minor postgresql version.\nname: ABI Test\n\"on\":\n  schedule:\n    # run daily 20:00 on main branch\n    - cron: '0 20 * * *'\n  push:\n    branches:\n      - prerelease_test\n      - trigger/abi\n  pull_request:\n    paths: .github/workflows/abi.yaml\n  workflow_dispatch:\njobs:\n  config:\n    runs-on: ubuntu-latest\n    outputs:\n      pg15_abi_min: ${{ steps.config.outputs.pg15_abi_min }}\n      pg16_abi_min: ${{ steps.config.outputs.pg16_abi_min }}\n      pg17_abi_min: ${{ steps.config.outputs.pg17_abi_min }}\n      pg18_abi_min: ${{ steps.config.outputs.pg18_abi_min }}\n      pg15_latest: ${{ steps.config.outputs.pg15_latest }}\n      pg16_latest: ${{ steps.config.outputs.pg16_latest }}\n      pg17_latest: ${{ steps.config.outputs.pg17_latest }}\n      pg18_latest: ${{ steps.config.outputs.pg18_latest }}\n\n    steps:\n    - name: Checkout source code\n      uses: actions/checkout@v4\n    - name: Read configuration\n      id: config\n      run: python .github/gh_config_reader.py\n\n  abi_test:\n    name: ABI Test ${{ matrix.dir }} PG${{ matrix.pg }}\n    runs-on: ubuntu-latest\n    needs: config\n    strategy:\n      fail-fast: false\n      matrix:\n        pg: [ 15, 16, 17, 18 ]\n        ignores:\n          - 'net telemetry'\n        include:\n          - pg: 15\n            builder: ${{ fromJson(needs.config.outputs.pg15_abi_min) }}\n            tester: ${{ fromJson(needs.config.outputs.pg15_latest) }}\n          - pg: 16\n            builder: ${{ fromJson(needs.config.outputs.pg16_abi_min) }}\n            tester: ${{ fromJson(needs.config.outputs.pg16_latest) }}\n          - pg: 17\n            builder: ${{ fromJson(needs.config.outputs.pg17_abi_min) }}\n            tester: ${{ fromJson(needs.config.outputs.pg17_latest) }}\n          - pg: 18\n            builder: ${{ fromJson(needs.config.outputs.pg18_abi_min) }}\n            tester: ${{ fromJson(needs.config.outputs.pg18_latest) }}\n\n    steps:\n\n    - name: Checkout TimescaleDB\n      uses: actions/checkout@v4\n\n    - name: Build extension with ${{ matrix.builder }}\n      run: |\n        BUILDER_IMAGE=\"postgres:${{matrix.builder}}-alpine\"\n\n        docker pull ${BUILDER_IMAGE}\n        docker buildx imagetools inspect ${BUILDER_IMAGE}\n        docker run -i --rm -v $(pwd):/mnt -e EXTRA_PKGS=\"${EXTRA_PKGS}\" ${BUILDER_IMAGE} bash <<\"EOF\"\n          apk add cmake gcc make build-base git ${EXTRA_PKGS}\n          # We run the same extension on different docker images, old versions\n          # have OpenSSL 1.1 and the new versions have OpenSSL 3, so we try to\n          # pin the 1.1. Note that depending on PG version, both images might\n          # have 1.1 or 3, so we first try to install the versioned 1.1 package,\n          # and if it's not present, it means the unversioned package is 1.1, so\n          # we install it.\n          apk add openssl1.1-compat-dev || apk add openssl-dev\n          # Postgres is compiled with ICU, so the pg_locale.h depends on the ICU\n          # headers and we have to install them\n          apk add icu-dev\n          git config --global --add safe.directory /mnt\n          cd /mnt\n          BUILD_DIR=build_abi BUILD_FORCE_REMOVE=true ./bootstrap\n          make -C build_abi -j $(getconf _NPROCESSORS_ONLN) install\n          mkdir -p build_abi/install_ext build_abi/install_lib\n          cp `pg_config --sharedir`/extension/timescaledb*.{control,sql} build_abi/install_ext\n          cp `pg_config --pkglibdir`/timescaledb*.so build_abi/install_lib\n        EOF\n\n    - name: Run tests on server ${{ matrix.tester }}\n      run: |\n        TEST_IMAGE=\"postgres:${{ matrix.tester }}-alpine\"\n\n        docker pull ${TEST_IMAGE}\n        docker buildx imagetools inspect ${TEST_IMAGE}\n        docker run -i --rm -v $(pwd):/mnt -e EXTRA_PKGS=\"${EXTRA_PKGS}\" ${TEST_IMAGE} bash <<\"EOF\"\n          apk add cmake gcc make build-base sudo coreutils ${EXTRA_PKGS}\n          apk add openssl1.1-compat-dev || apk add openssl-dev\n          cd /mnt\n          cp build_abi/install_ext/* `pg_config --sharedir`/extension/\n          cp build_abi/install_lib/* `pg_config --pkglibdir`\n          chown -R postgres /mnt\n          set -o pipefail\n          [ -f /usr/bin/gmake ] || ln -s /usr/bin/make /usr/bin/gmake\n          sudo -u postgres make -j $(getconf _NPROCESSORS_ONLN) -C build_abi \\\n            -k regresscheck regresscheck-t regresscheck-shared \\\n            IGNORES=\"${{ matrix.ignores }}\" | tee installcheck.log\n        EOF\n\n    - name: Show regression diffs\n      if: always()\n      id: collectlogs\n      run: |\n        sudo chmod a+rw .\n        sudo find build_abi -name regression.diffs -exec cat {} + > regression.log\n        sudo find build_abi -name postmaster.log -exec cat {} + > postmaster.log\n        if [[ -s regression.log ]]; then echo \"regression_diff=true\" >>$GITHUB_OUTPUT; fi\n        grep -e 'FAILED' -e 'failed (ignored)' -e 'not ok' installcheck.log || true\n        cat regression.log\n\n    - name: Save regression diffs\n      if: always() && steps.collectlogs.outputs.regression_diff == 'true'\n      uses: actions/upload-artifact@v4\n      with:\n        name: Regression diff ABI Breakage ${{ matrix.dir }} PG${{ matrix.pg }}\n        path: regression.log\n\n    - name: Save postmaster.log\n      if: always()\n      uses: actions/upload-artifact@v4\n      with:\n        name: PostgreSQL log ABI Breakage ${{ matrix.dir }} PG${{ matrix.pg }}\n        path: postmaster.log\n\n"
  },
  {
    "path": ".github/workflows/apt-installcheck.yaml",
    "content": "# Test running make installcheck on our APT packages.\nname: \"Packaging tests: Installcheck for APT\"\n\"on\":\n  schedule:\n    # run daily 0:00 on main branch\n    - cron: '0 0 * * *'\n  pull_request:\n    paths: .github/workflows/apt-installcheck.yaml\n  push:\n    tags:\n    - '*'\n    branches:\n    - release_test\n    - trigger/package_test\n  workflow_dispatch:\njobs:\n  apt_tests:\n    name: APT ${{ matrix.runner }} ${{ matrix.image }} PG${{ matrix.pg }} ${{ matrix.license }}\n    container:\n      image: ${{ matrix.image }}\n      env:\n        DEBIAN_FRONTEND: noninteractive\n    strategy:\n      fail-fast: false\n      matrix:\n        runner:\n          - ubuntu-latest\n          - timescaledb-runner-arm64\n        image:\n          - ubuntu:22.04\n        pg:\n          - 17\n        license:\n          - \"TSL\"\n        include:\n          - runner: ubuntu-latest\n            arch: AMD64\n          - runner: timescaledb-runner-arm64\n            arch: ARM\n          - image: ubuntu:22.04\n            image_friendly: Ubuntu 22.04\n\n    runs-on: ${{ matrix.runner }}\n\n    steps:\n    - name: Add repositories\n      run: |\n        apt-get update\n        apt-get install -y wget lsb-release gnupg sudo postgresql-common git cmake jq\n        yes | /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh\n        image_type=$(lsb_release -i -s | tr '[:upper:]' '[:lower:]')\n        echo \"deb https://packagecloud.io/timescale/timescaledb/${image_type}/ $(lsb_release -c -s) main\" \\\n          > /etc/apt/sources.list.d/timescaledb.list\n        wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | gpg --dearmor -o /etc/apt/trusted.gpg.d/timescale_timescaledb.gpg\n\n    - name: Install timescaledb\n      run: |\n        apt-get update\n        apt-get install -y --no-install-recommends \\\n          timescaledb-2${{ matrix.pkg_suffix }}-postgresql-${{ matrix.pg }} timescaledb-tools\n        timescaledb-tune --quiet --yes\n\n    - uses: actions/checkout@v4\n\n    - name: Get version of latest release\n      id: versions\n      run: |\n        version=$(wget -q https://api.github.com/repos/timescale/timescaledb/releases/latest -O - | jq -r .tag_name)\n        echo \"version=${version}\"\n        echo \"version=${version}\" >>$GITHUB_OUTPUT\n\n        grep PRETTY_NAME /etc/os-release >> $GITHUB_OUTPUT\n        echo \"arch=$(uname -m)\" >> $GITHUB_OUTPUT\n\n    - name: Test Installation\n      id: installation\n      run: |\n        set -xeu\n        pg_ctlcluster ${{ matrix.pg }} main start\n        sudo -u postgres psql -X -c \"CREATE EXTENSION timescaledb\" \\\n          -c \"SELECT extname,extversion,version() FROM pg_extension WHERE extname='timescaledb'\"\n        installed_version=$(sudo -u postgres psql -X -t \\\n          -c \"SELECT extversion FROM pg_extension WHERE extname='timescaledb';\" | sed -e 's! !!g')\n        if [ \"${{ steps.versions.outputs.version }}\" != \"$installed_version\" ];then\n          false\n        fi\n\n        sudo -u postgres psql -qtAX -c \"\n          select format(E'commit_hash=%s\\ncommit_tag=%s', commit_hash, commit_tag)\n          from _timescaledb_functions.get_git_commit();\n        \" | tee -a $GITHUB_OUTPUT\n\n    - name: Checkout the sources for version ${{ steps.installation.outputs.commit_tag }}\n      uses: actions/checkout@v4\n      with:\n        ref: ${{ steps.installation.outputs.commit_hash }}\n\n    - name: Run make installcheck\n      id: installcheck\n      shell: bash\n      run: |\n        chown -R postgres:postgres .\n        chmod g+s .\n        sudo -u postgres git log -1 .\n        apt install -y postgresql-server-dev-${{ matrix.pg }}\n        sudo -u postgres cmake -B build -S . -DCMAKE_BUILD_TYPE=RelWithDebInfo \\\n          -DTEST_PG_LOG_DIRECTORY=\"$(readlink -f .)\"\n        set -o pipefail\n        sudo -u postgres LANG=C.UTF-8 make -k -C build installcheck | tee installcheck.log\n\n    - name: Collect the logs\n      if: always()\n      run: |\n        find . -name regression.diffs -exec cat {} + > regression.log\n        find . -mindepth 2 -name initdb.log -exec cat {} + > initdb.log\n\n    - name: Show regression diffs\n      if: always()\n      run: |\n        grep -e 'FAILED' -e 'failed (ignored)' -e 'not ok' installcheck.log || true\n        cat regression.log\n\n    - name: Save the logs\n      if: always()\n      uses: actions/upload-artifact@v4\n      with:\n        name: Installcheck logs ${{ matrix.runner }} ${{ matrix.image_friendly }} PG ${{ matrix.pg }}\n        path: |\n            postmaster.*\n            initdb.log\n            regression.log\n            installcheck.log\n\n"
  },
  {
    "path": ".github/workflows/apt-packages.yaml",
    "content": "# Test installing our ubuntu and debian packages for the latest version.\nname: \"Packaging tests: APT\"\n\"on\":\n  schedule:\n    # run daily 0:00 on main branch\n    - cron: '0 0 * * *'\n  pull_request:\n    paths: .github/workflows/apt-packages.yaml\n  push:\n    tags:\n    - '*'\n    branches:\n    - release_test\n    - trigger/package_test\n  workflow_dispatch:\njobs:\n  apt_tests:\n    name: APT ${{ matrix.arch }} ${{ matrix.image }} PG${{ matrix.pg }} ${{ matrix.license }}\n    runs-on: ${{ matrix.runner }}\n    container:\n      image: ${{ matrix.image }}\n      env:\n        DEBIAN_FRONTEND: noninteractive\n    strategy:\n      fail-fast: false\n      matrix:\n        arch: [ \"x86\", \"ARM\" ]\n        image: [ \"debian:11-slim\", \"debian:12-slim\", \"debian:13-slim\", \"ubuntu:22.04\", \"ubuntu:24.04\" ]\n        pg: [ 15, 16, 17, 18 ]\n        license: [ \"TSL\", \"Apache\"]\n        include:\n          - license: Apache\n            pkg_suffix: \"-oss\"\n          - arch: \"x86\"\n            runner: \"ubuntu-latest\"\n          - arch: \"ARM\"\n            runner: \"cloud-image-runner-ubuntu-24-arm64\"\n        exclude:\n          - image: \"debian:11-slim\"\n            pg: 18\n\n\n    steps:\n    - name: Add repositories\n      run: |\n        apt-get update\n        apt-get install -y wget lsb-release gnupg apt-transport-https sudo postgresql-common jq\n        yes | /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh\n        image_type=$(lsb_release -i -s | tr '[:upper:]' '[:lower:]')\n        echo \"deb https://packagecloud.io/timescale/timescaledb/${image_type}/ $(lsb_release -c -s) main\" \\\n          > /etc/apt/sources.list.d/timescaledb.list\n        if [ \"${image_type}\" = \"ubuntu\" ]; then\n          wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | apt-key add -\n        else\n          wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | gpg --dearmor -o /etc/apt/trusted.gpg.d/timescale_timescaledb.gpg\n        fi\n\n\n    - name: Install timescaledb\n      run: |\n        apt-get update\n        apt-get install -y --no-install-recommends \\\n          timescaledb-2${{ matrix.pkg_suffix }}-postgresql-${{ matrix.pg }} timescaledb-tools\n        timescaledb-tune --quiet --yes\n\n    - name: List available versions\n      run: |\n        apt-cache show timescaledb-2${{ matrix.pkg_suffix }}-postgresql-${{ matrix.pg }} \\\n          | grep -e Version: -e Depends: | tr '\\n' ' ' | sed -e 's! Version: !\\n!g' -e 's!Version: !!' -e 's!$!\\n!'\n\n    - name: Show files in package\n      run: |\n        dpkg -L timescaledb-2${{ matrix.pkg_suffix }}-postgresql-${{ matrix.pg }}\n\n    - uses: actions/checkout@v4\n\n    - name: Get version of latest release\n      id: versions\n      run: |\n        version=$(wget -q https://api.github.com/repos/timescale/timescaledb/releases/latest -O - | jq -r .tag_name)\n        echo \"version=${version}\"\n        echo \"version=${version}\" >>$GITHUB_OUTPUT\n\n    - name: Test Installation\n      run: |\n        pg_ctlcluster ${{ matrix.pg }} main start\n        sudo -u postgres psql -X -c \"CREATE EXTENSION timescaledb\" \\\n          -c \"SELECT extname,extversion,version() FROM pg_extension WHERE extname='timescaledb'\"\n        installed_version=$(sudo -u postgres psql -X -t \\\n          -c \"SELECT extversion FROM pg_extension WHERE extname='timescaledb';\" | sed -e 's! !!g')\n        if [ \"${{ steps.versions.outputs.version }}\" != \"$installed_version\" ];then\n          false\n        fi\n\n    - name: Test Downgrade\n      run: |\n        # Since this runs nightly on main we have to get the previous version\n        # from the last released version and not current branch.\n        prev_version=$(wget --quiet -O - \\\n        https://raw.githubusercontent.com/timescale/timescaledb/${{ steps.versions.outputs.version }}/version.config \\\n          | grep previous_version | sed -e 's!previous_version = !!')\n        sudo -u postgres psql -X -c \"ALTER EXTENSION timescaledb UPDATE TO '${prev_version}'\" \\\n          -c \"SELECT extname,extversion,version() FROM pg_extension WHERE extname='timescaledb'\"\n        installed_version=$(sudo -u postgres psql -X -t \\\n          -c \"SELECT extversion FROM pg_extension WHERE extname='timescaledb';\" | sed -e 's! !!g')\n        if [ \"$prev_version\" != \"$installed_version\" ];then\n          false\n        fi\n\n    - name: Install toolkit\n      run: |\n        apt-get install -y --no-install-recommends \\\n          timescaledb-toolkit-postgresql-${{ matrix.pg }}\n\n    - name: List available toolkit versions\n      run: |\n        apt-cache show timescaledb-toolkit-postgresql-${{ matrix.pg }} | grep -e Version: -e Depends: | tr '\\n' ' ' | sed -e 's! Version: !\\n!g' -e 's!Version: !!' -e 's!$!\\n!'\n\n    - name: PostgreSQL log\n      if: always()\n      run: |\n        cat /var/log/postgresql/postgresql-${{ matrix.pg }}-main.log\n\n"
  },
  {
    "path": ".github/workflows/backport-trigger.yaml",
    "content": "# A helper workflow to trigger the run of the backport workflow on the main\n# branch, when a release branch or the main branch were changed.\nname: Trigger the Backport Workflow\n\"on\":\n  push:\n    branches:\n      - main\n      - ?.*.x\n  pull_request:\n    paths: .github/workflows/backport-trigger.yaml\n\njobs:\n  backport_trigger:\n    runs-on: timescaledb-runner-arm64\n    steps:\n      - name: Checkout TimescaleDB\n        uses: actions/checkout@v4\n\n      - name: Trigger the Backport Workflow\n        env:\n          GH_TOKEN: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n        run: |\n          gh workflow run backport.yaml --ref main\n\n"
  },
  {
    "path": ".github/workflows/backport.yaml",
    "content": "name: Backport Bug Fixes\non:\n  schedule:\n    # Run weekdays 12:00 on main branch, so that it doesn't wreak havoc on\n    # weekends. Good to have regular runs so that we can react to changes in\n    # issue tags, or retry some spurious network errors, or whatever.\n    - cron: '0 12 * * 1-5'\n  workflow_dispatch:\n  push:\n    # This is also triggered from backport-trigger.yaml when the release branches\n    # are updated.\n    branches:\n      # You can run and debug new versions of the backport script by pushing it\n      # to this branch. workflow_dispatch can only be run through github cli for\n      # branches that are not main, so it's inconvenient.\n      - trigger/backport\n\n# The workflow needs the permission to push branches\npermissions:\n  contents: write\n  pull-requests: write\n  issues: write\n  actions: write\n  statuses: write\n\njobs:\n  backport:\n    name: Backport Bug Fixes\n    runs-on: timescaledb-runner-arm64\n\n    steps:\n      - name: Install Python Dependencies\n        run: |\n          pip install PyGithub requests\n\n      - name: Checkout TimescaleDB\n        uses: actions/checkout@v4\n        with:\n          token: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n\n      - name: Run the Backport Script\n        env:\n          ORG_AUTOMATION_TOKEN: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n        run: |\n          git remote --verbose\n\n          scripts/backport.py 2>&1\n\n          git remote --verbose\n"
  },
  {
    "path": ".github/workflows/catalog-updates-check.yaml",
    "content": "name: Check for unsafe catalog updates\n\"on\":\n  pull_request:\n  push:\n    branches:\n      - main\n      - ?.*.x\njobs:\n  check_catalog_correctly_updated:\n    name: Check updates to latest-dev and reverse-dev are properly handled by PR\n    runs-on: timescaledb-runner-arm64\n    steps:\n      - name: Checkout source\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - name: Install pglast\n        run: |\n          python -m pip install pglast\n\n      - name: Check sql file contents\n        run: |\n          find sql -name '*.sql' -not -path 'sql/updates/*' -not -path 'sql/compat.sql' | xargs -IFILE python scripts/check_updates.py FILE\n\n      - name: Check latest-dev contents\n        run: |\n          python scripts/check_updates.py --latest \"sql/updates/latest-dev.sql\"\n\n      - name: Check for idempotency in SQL scripts\n        if: always()\n        run: |\n          python scripts/check_sql_script.py sql/*.sql\n\n      # To allow fixing previous mistakes we run the check against reverse-dev but don't\n      # fail it on errors.\n      - name: Check reverse-dev contents\n        if: always()\n        run: |\n          python scripts/check_updates.py \"sql/updates/reverse-dev.sql\" || true\n\n"
  },
  {
    "path": ".github/workflows/changelog-check.yaml",
    "content": "name: Check for changelog entry file\n\"on\":\n  pull_request:\n    types: [opened, synchronize, reopened, edited]\n    branches:\n      - main\njobs:\n  # Check if the PR creates a separate file with changelog entry in the\n  # \".unreleased\"  folder\n  #\n  # This check can be disabled by adding the following line in the PR text\n  #\n  # Disable-check: force-changelog-file\n  #\n  # The file having the changelog entry is expected to have lines in the\n  # following format\n  #\n  # Fixes: #NNNN <bug description> (mandatory in case of bugfixes)\n  # Thanks: @name <thank you note> (optional)\n  # Implements: #NNNN <feature description> (mandatory in case of new features)\n  check_changelog_file:\n    name: Check for file with CHANGELOG entry\n    runs-on: timescaledb-runner-arm64\n    steps:\n      - name: Setup Python\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.13'\n\n      - name: Install Python Dependencies\n        run: |\n          pip install PyGithub\n\n      - name: Checkout source\n        uses: actions/checkout@v4\n        with:\n          ref: ${{ github.event.pull_request.head.sha }}\n          fetch-depth: 0\n\n      - name: Check if the pull request adds file in \".unreleased\" folder\n        shell: bash --norc --noprofile {0}\n        env:\n          BODY: ${{ github.event.pull_request.body }}\n          GH_TOKEN: ${{ github.token }}\n          PR_NUMBER: ${{ github.event.pull_request.number }}\n        run: |\n          set -euo pipefail\n\n          folder=\".unreleased\"\n\n          # Get the list of modified files as a bash array. Exclude the\n          # development-related files because they don't require a changelog.\n          mapfile -t modified_files < <(gh pr view $PR_NUMBER --json files --jq '\n            [.files.[].path | select(\n                    (startswith(\".github\") or startswith(\"test\")\n                        or startswith(\"tsl/test\") or startswith(\"scripts\"))\n                | not)] | .[]')\n          echo \"Modified files: ${modified_files[@]}\"\n\n          # Get the changelog files as a bash array\n          mapfile -t changelog_files < <(gh pr view $PR_NUMBER --json files --jq \"\n            [.files.[].path | select(startswith(\\\"${folder}\\\"))] | .[]\")\n          echo \"Changelog files: ${changelog_files[@]}\"\n\n          if echo \"$BODY\" | egrep -qsi \"Disable-check:[[:space:]]*force-changelog-file\"\n          then\n            # skip changelog checks if forced\n            :\n          elif (( ${#modified_files[@]} > 0 && ${#changelog_files[@]} == 0 ))\n          then\n            # if no changelog files found, and the PR does not have the force disable check option\n            echo \"PR does not add a change log file in .unreleased/ folder\"\n            echo \"Check .unreleased/template.rfc822 for the format of the change log file.\"\n            echo\n            echo \"To disable changelog updated check, add this trailer to pull request message:\"\n            echo\n            echo \"Disable-check: force-changelog-file\"\n            echo\n            echo \"Trailers follow RFC2822 conventions, so no whitespace\"\n            echo \"before field name and the check is case-insensitive for\"\n            echo \"both the field name and the field body.\"\n            exit 1\n          else\n            # check the format of the files in .unreleased folder\n            for file in \"${changelog_files[@]}\"\n            do\n              if ! scripts/check_changelog_format.py \"${file}\"\n              then\n                echo \"Invalid CHANGELOG entries in ${file}.\"\n                exit 1\n              fi\n            done\n          fi\n"
  },
  {
    "path": ".github/workflows/claude-code-review.yaml",
    "content": "name: Claude Code Review\n\non:\n  workflow_dispatch: # Manual trigger only, use @claude mentions for on-demand reviews\n\njobs:\n  claude-review:\n\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      pull-requests: read\n      issues: read\n      id-token: write\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 1\n\n      - name: Run Claude Code Review\n        id: claude-review\n        uses: anthropics/claude-code-action@v1\n        with:\n          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}\n          prompt: |\n            Please review this pull request and provide feedback on:\n            - Code quality and best practices\n            - Potential bugs or issues\n            - Performance considerations\n            - Security concerns\n            - Test coverage\n\n            Be constructive and helpful in your feedback.\n\n            Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR.\n\n          # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md\n          # or https://docs.anthropic.com/en/docs/claude-code/sdk#command-line for available options\n          claude_args: '--allowed-tools \"Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)\"'\n"
  },
  {
    "path": ".github/workflows/coccinelle.yaml",
    "content": "# Check our codebase for defective programming patterns\nname: Coccinelle\n\"on\":\n  pull_request:\n  push:\n    branches:\n      - main\n      - ?.*.x\njobs:\n  coccinelle:\n    name: Coccinelle\n    # coccinelle version in ubuntu-latest (20.04) is too old so we run\n    # this in jammy (22.04)\n    runs-on: ubuntu-22.04\n\n    steps:\n    - name: Install Dependencies\n      run: |\n        sudo apt-get update\n        sudo apt-get -y install coccinelle\n\n    - name: Checkout TimescaleDB\n      uses: actions/checkout@v4\n\n    - name: Run coccinelle\n      run: |\n        ./scripts/coccinelle.sh\n\n    - name: Save coccinelle.diff\n      if: always()\n      uses: actions/upload-artifact@v4\n      with:\n        name: coccinelle.diff\n        path: coccinelle.diff\n\n"
  },
  {
    "path": ".github/workflows/code_style.yaml",
    "content": "name: Code style\n\"on\":\n  push:\n    branches:\n      - main\n      - ?.*.x\n  pull_request:\n\njobs:\n  cmake_checks:\n    name: Check CMake files\n    runs-on: ubuntu-22.04\n    steps:\n      - name: Install prerequisites\n        run: pip install cmakelang\n      - name: Checkout source\n        uses: actions/checkout@v4\n      - name: Run format on CMake files\n        run: |\n          git reset --hard\n          find  -name CMakeLists.txt  -exec cmake-format -i {} +\n          find src test tsl -name '*.cmake' -exec cmake-format -i {} +\n          git diff --exit-code\n\n  perl_checks:\n    name: Check Perl code in tree\n    runs-on: ubuntu-22.04\n    steps:\n      - name: Install prerequisites\n        run: sudo apt install perltidy\n      - name: Checkout source\n        uses: actions/checkout@v4\n      - name: Check trailing whitespace\n        if: always()\n        run: |\n          find . -name '*.p[lm]' -exec perl -pi -e 's/[ \\t]+$//' {} +\n          git diff --exit-code\n      - name: Format Perl files, if needed\n        if: always()\n        run: |\n          git reset --hard\n          find . -name '*.p[lm]' -exec perltidy -b -bext=/ {} +\n          git diff --exit-code\n\n  yaml_checks:\n    name: Check YAML code in tree\n    runs-on: ubuntu-latest\n    steps:\n      - name: Install prerequisites\n        run: |\n          pip install yamllint\n      - name: Checkout source\n        uses: actions/checkout@v4\n      - name: Run yamllint\n        run: |\n          find . -type f \\( -name \"*.yaml\" -or -name \"*.yml\" \\) -print -exec yamllint {} \\+\n\n  spelling_checks:\n    name: Check spelling\n    runs-on: ubuntu-latest\n    steps:\n      - name: Install prerequisites\n        run: |\n          pip install codespell\n      - name: Checkout source\n        uses: actions/checkout@v4\n      - name: Run codespell\n        run: |\n          find . -type f \\( -name \"*.c\" -or -name \"*.h\" -or -name \"*.yaml\" -or -name \"*.yml\" -or -name \"*.sh\" -or -name \"*.cmake\" -or -name \"*.py\" -or -name \"*.pl\" -or -name \"CMakeLists.txt\" \\) \\\n            -exec codespell -I .github/codespell-ignore-words {} \\+\n\n  cc_checks:\n    name: Check code formatting\n    runs-on: ubuntu-22.04\n    strategy:\n      fail-fast: false\n    env:\n      LLVM_VER: 17\n    steps:\n    - name: Install dependencies\n      run: |\n          gpg --batch --keyserver hkp://keyserver.ubuntu.com --recv-keys 15CF4D18AF4F7421\n          gpg --batch --export --export-options export-minimal --armor 15CF4D18AF4F7421 | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc > /dev/null\n\n          . /etc/os-release\n          echo \"deb https://apt.llvm.org/${VERSION_CODENAME}/ llvm-toolchain-${VERSION_CODENAME}-${LLVM_VER} main\" | sudo tee /etc/apt/sources.list.d/llvm.list >/dev/null\n          sudo apt-get update\n\n          sudo apt-get install clang-format-${LLVM_VER}\n    - name: Checkout source\n      uses: actions/checkout@v4\n    - name: Check trailing whitespace\n      if: always()\n      run: |\n        git reset --hard\n        find . -type f -regex '.*\\.\\(c\\|h\\|sql\\|sql\\.in\\)$' -exec perl -pi -e 's/[ \\t]+$//' {} +\n        git diff --exit-code\n\n    - name: Check code formatting\n      if: always()\n      run: |\n        sudo update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-${LLVM_VER} 100\n        sudo update-alternatives --set clang-format /usr/bin/clang-format-${LLVM_VER}\n        git reset --hard\n        ./scripts/clang_format_all.sh\n        git diff --exit-code\n\n    - name: FIXME annotations left in code (use TODO for long-term notes)\n      if: always()\n      run: |\n        ! grep fixme -niR ./*\n\n  python_checks:\n    name: Check Python code in tree\n    runs-on: ubuntu-latest\n    steps:\n      - name: Install prerequisites\n        run: |\n          pip install --upgrade pip\n          pip install black prospector pylint dodgy \\\n            mccabe pycodestyle pyflakes \\\n            psutil pygithub pglast testgres more_itertools\n          # pinning snowballstemmer to version 2.2.0 due to:\n          # https://github.com/prospector-dev/prospector/issues/763\n          pip install --force-reinstall --no-deps snowballstemmer==2.2.0\n          pip list\n          pip list --user\n\n      - name: Checkout source\n        uses: actions/checkout@v4\n\n      - name: Run prospector\n        run: |\n          git reset --hard\n          find . -type f -name \"*.py\" -print -exec prospector {} + -exec black {} +\n          git diff --exit-code\n\n  misc_checks:\n    name: Check license, update scripts, git hooks, missing gitignore entries and unnecessary template tests\n    runs-on: ubuntu-24.04\n    strategy:\n      fail-fast: false\n    steps:\n    - name: Checkout source\n      if: always()\n      uses: actions/checkout@v4\n    - name: Check license\n      if: always()\n      run: ./scripts/check_license_all.sh\n    - name: Check git commit hooks\n      if: always()\n      run: |\n        ./scripts/githooks/commit_msg_tests.py\n    - name: Check for unreferenced test files\n      if: always()\n      run: ./scripts/check_unreferenced_files.sh\n    - name: Check for missing gitignore entries for template test files\n      if: always()\n      run: |\n        ./bootstrap\n        ./scripts/check_missing_gitignore_for_template_tests.sh\n    - name: Check for unnecessary template test files\n      if: always()\n      run: ./scripts/check_unnecessary_template_tests.sh\n    - name: Check for orphaned output test files\n      if: always()\n      run: ./scripts/check_orphaned_test_output_files.sh\n\n"
  },
  {
    "path": ".github/workflows/coverity.yaml",
    "content": "name: Coverity\n\"on\":\n  schedule:\n    # run at 22:00 on every saturday\n    - cron: '0 22 * * TUE,SAT'\n  push:\n    branches:\n      - coverity_scan\n      - trigger/coverity\n  pull_request:\n    paths: .github/workflows/coverity.yaml\n  workflow_dispatch:\njobs:\n\n  coverity:\n    name: Coverity ${{ matrix.pg }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        # run only on the 3 latest PG versions as we have rate limit on coverity\n        pg: [16, 17, 18]\n        os: [ubuntu-22.04]\n    steps:\n    - name: Install Dependencies\n      run: |\n        sudo apt-get update\n        sudo apt-get install gnupg systemd-coredump gdb postgresql-common\n        yes | sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh\n        sudo apt-get update\n        sudo apt-get install postgresql-${{ matrix.pg }} postgresql-server-dev-${{ matrix.pg }}\n\n    - name: Checkout TimescaleDB\n      uses: actions/checkout@v4\n\n    - name: Coverity tools\n      run: |\n        wget https://scan.coverity.com/download/linux64 \\\n          --post-data \"token=${{ secrets.COVERITY_TOKEN }}&project=timescale%2Ftimescaledb\" \\\n          -O coverity_tool.tgz -q\n        tar xf coverity_tool.tgz\n        mv cov-analysis-linux64-* coverity\n\n    - name: Build TimescaleDB\n      run: |\n        PATH=\"$GITHUB_WORKSPACE/coverity/bin:/usr/lib/postgresql/${{ matrix.pg }}/bin:$PATH\"\n        ./bootstrap -DCMAKE_BUILD_TYPE=Release\n        cov-build --dir cov-int make -C build\n\n    - name: Upload report\n      env:\n        FORM_EMAIL: --form email=ci@timescale.com\n        FORM_FILE: --form file=@timescaledb.tgz\n        FORM_DESC: --form description=\"CI\"\n        FORM_TOKEN: --form token=\"${{ secrets.COVERITY_TOKEN }}\"\n        COVERITY_URL: https://scan.coverity.com/builds?project=timescale%2Ftimescaledb\n      run: |\n        tar czf timescaledb.tgz cov-int\n        curl $FORM_TOKEN $FORM_EMAIL $FORM_DESC $FORM_FILE \\\n          --form version=\"$(grep '^version' version.config | cut -b11-)-${{ matrix.pg }}\" $COVERITY_URL\n"
  },
  {
    "path": ".github/workflows/docker-images.yaml",
    "content": "# Test our docker images are built with the most recent version\n# The main purpose of this test is to check the image is working\n# and the latest tag points to an image with the most recent\n# release.\nname: \"Packaging tests: Docker images\"\n\"on\":\n  schedule:\n    # run daily 0:00 on main branch\n    - cron: '0 0 * * *'\n  pull_request:\n    paths: .github/workflows/docker-images.yaml\n  push:\n    tags:\n    - '*'\n    branches:\n    - release_test\n    - trigger/package_test\n  workflow_dispatch:\njobs:\n  docker_tests:\n    name: ${{ matrix.image }}\n    runs-on: ubuntu-latest\n    services:\n      ts:\n        image: timescale/${{ matrix.image }}\n        ports:\n          - 5432:5432\n        env:\n          POSTGRES_HOST_AUTH_METHOD: trust\n          POSTGRESQL_PASSWORD: ci\n\n    env:\n      PGHOST: localhost\n      PGUSER: postgres\n      PGPASSWORD: ci\n\n    strategy:\n      fail-fast: false\n      matrix:\n        image: [\n            \"timescaledb:latest-pg15\",\n            \"timescaledb:latest-pg16\",\n            \"timescaledb:latest-pg17\",\n            \"timescaledb:latest-pg18\",\n            \"timescaledb-ha:pg15\",\n            \"timescaledb-ha:pg16\",\n            \"timescaledb-ha:pg17\",\n            \"timescaledb-ha:pg18\",\n          ]\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Get version of latest release\n      id: versions\n      env:\n        GH_TOKEN: ${{ github.token }}\n      run: |\n        version=$(gh release list --json tagName,isLatest --jq '.[] | select(.isLatest) | .tagName')\n        echo \"version=${version}\" >>$GITHUB_OUTPUT\n\n    - name: Wait for services to start\n      run: |\n        sleep 10\n        pg_isready -t 30\n\n    - name: Check version\n      run: |\n        psql -c \"SELECT extname,extversion,version() FROM pg_extension WHERE extname='timescaledb'\"\n        installed_version=$(psql -X -t \\\n          -c \"SELECT extversion FROM pg_extension WHERE extname='timescaledb';\" | sed -e 's! !!g')\n        if [ \"${{ steps.versions.outputs.version }}\" != \"$installed_version\" ];then\n          false\n        fi\n\n    - name: Create hypertable\n      run: |\n        psql -c \"$(cat <<SQL\n          CREATE TABLE metrics(time timestamptz, device text, metric text, value float);\n          SELECT create_hypertable('metrics','time');\n        SQL\n        )\"\n\n"
  },
  {
    "path": ".github/workflows/extras-diagnostic.yaml",
    "content": "# Test diagnostic script from timescaledb-extras works\nname: \"timescaledb-extras diagnostic\"\n\"on\":\n  schedule:\n    # run daily 0:00 on main branch\n    - cron: '0 0 * * *'\n  pull_request:\n    paths: .github/workflows/extras-diagnostic.yaml\n  push:\n    branches:\n    - trigger/package_test\n  workflow_dispatch:\njobs:\n  diagnostic_test:\n    name: timescaledb-extras PG${{ matrix.pg }} nightly\n    runs-on: ubuntu-latest\n    services:\n      ts:\n        image: timescaledev/timescaledb:nightly-pg${{ matrix.pg }}\n        ports:\n          - 5432:5432\n        env:\n          POSTGRES_HOST_AUTH_METHOD: trust\n          POSTGRESQL_PASSWORD: ci\n\n    env:\n      PGHOST: localhost\n      PGUSER: postgres\n      PGPASSWORD: ci\n\n    strategy:\n      fail-fast: false\n      matrix:\n        pg: [15,16,17,18]\n\n    steps:\n\n    - uses: actions/checkout@v4\n      with:\n        repository: 'timescale/timescaledb-extras'\n\n    - name: Wait for services to start\n      run: |\n        sleep 10\n        pg_isready -t 30\n\n    - name: Prepare database\n      run: |\n        psql -v ON_ERROR_STOP=1 -c \"$(cat <<SQL\n          SELECT version();\n          SELECT extname, extversion from pg_extension;\n          CREATE TABLE t(time timestamptz) WITH (tsdb.hypertable);\n          INSERT INTO t SELECT '2025-01-01';\n          INSERT INTO t SELECT '2025-02-01';\n          SELECT compress_chunk(show_chunks('t'));\n        SQL\n        )\"\n\n    - name: Run diagnostic script\n      run: |\n        psql -v ON_ERROR_STOP=1 < diagnostic.sql\n\n"
  },
  {
    "path": ".github/workflows/homebrew.yaml",
    "content": "# Test installation of our homebrew tap for latest version\nname: \"Packaging tests: Homebrew\"\n\"on\":\n  schedule:\n    # run daily 20:00 on main branch\n    - cron: '0 20 * * *'\n  pull_request:\n    paths: .github/workflows/homebrew.yaml\n  push:\n    tags:\n    - '*'\n    branches:\n    - release_test\n    - trigger/package_test\n    - trigger/homebrew_test\n  workflow_dispatch:\n\njobs:\n  homebrew:\n    name: Homebrew\n    runs-on: macos-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        license: [ \"TSL\", \"Apache\"]\n        include:\n          - license: Apache\n            install_options: \"--with-oss-only\"\n\n    steps:\n\n    - name: Setup\n      run: |\n        brew install postgresql@17\n        echo \"/opt/homebrew/opt/postgresql@17/bin\" >> $GITHUB_PATH\n        brew tap timescale/tap\n        brew info timescaledb\n\n    - name: Install timescaledb\n      run: |\n        brew uninstall cmake\n        brew install timescaledb ${{ matrix.install_options }}\n        timescaledb-tune --quiet --yes\n        timescaledb_move.sh\n        brew services start postgresql@17\n\n    # checkout code to get version information\n    - uses: actions/checkout@v4\n\n    - name: Test Installation\n      env:\n        GH_TOKEN: ${{ github.token }}\n      run: |\n        psql -X -c \"CREATE EXTENSION timescaledb;\" postgres\n        psql -X -c \"SELECT extname,extversion,version() FROM pg_extension WHERE extname='timescaledb';\" postgres\n        version=$(gh release list --json tagName,isLatest --jq '.[] | select(.isLatest) | .tagName')\n        installed_version=$(psql -X -t \\\n            -c \"SELECT extversion FROM pg_extension WHERE extname='timescaledb';\" \\\n            postgres | sed -e 's! !!g')\n        if [ \"$version\" != \"$installed_version\" ];then\n          echo \"Installed version \\\"${installed_version}\\\" does not match expected version \\\"${version}\\\".\"\n          false\n        fi\n\n"
  },
  {
    "path": ".github/workflows/issue-handling.yaml",
    "content": "name: Process issue workflows\n\n\"on\":\n  issues:\n    types: [opened, closed, labeled]\n  issue_comment:\n    types: [created, edited]\n\njobs:\n  add-to-project:\n    name: Add issue to projects\n    # issue_comment is triggered when commenting on both issues and\n    # pull requests. To avoid adding pull requests to the bug board,\n    # filter out pull requests\n    if: ${{ !github.event.issue.pull_request }}\n    runs-on: timescaledb-runner-arm64\n    steps:\n      - name: Add to bugs board\n        uses: actions/add-to-project@v0.5.0\n        with:\n          project-url: https://github.com/orgs/timescale/projects/55\n          github-token: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n          labeled: bug, needs-triage, flaky-test\n          label-operator: OR\n      - name: Add to CAggs board\n        uses: actions/add-to-project@v0.5.0\n        with:\n          project-url: https://github.com/orgs/timescale/projects/128\n          github-token: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n          labeled: continuous_aggregate\n\n  notify-sec:\n    name: Notify security channel\n    runs-on: timescaledb-runner-arm64\n    if: >-\n      github.event_name == 'issues' && github.event.action == 'opened' && (\n          contains(github.event.issue.labels.*.name, 'segfault') ||\n          contains(github.event.issue.labels.*.name, 'security')\n      )\n      ||\n      github.event_name == 'issues' && github.event.action == 'labeled' && (\n          github.event.label.name == 'segfault' ||\n          github.event.label.name == 'security'\n      )\n    env:\n      SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}\n    steps:\n      - name: Post to Security Channel\n        uses: slackapi/slack-github-action@v1.25.0\n        with:\n          channel-id: '${{ secrets.SLACK_CHANNEL_SECURITY }}'\n          payload: |\n            {\n              \"text\": \"Issue #${{github.event.issue.number}} (${{github.event.issue.title}})> needs attention\",\n              \"blocks\": [\n                {\n                  \"type\": \"section\",\n                  \"text\": {\n                    \"type\": \"mrkdwn\",\n                    \"text\": \"Issue <${{github.event.issue.html_url}}|#${{github.event.issue.number}} ${{github.event.issue.title}}> needs attention\"\n                  }\n                }\n              ]\n            }\n\n  close-issue:\n    name: Issue is closed\n    runs-on: timescaledb-runner-arm64\n    if: github.event_name == 'issues' && github.event.action == 'closed' && contains(github.event.issues.issue.labels.*.name, 'bug')\n    steps:\n      - uses: leonsteinhaeuser/project-beta-automations@v2.0.0\n        with:\n          gh_token: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n          organization: timescale\n          project_id: 55\n          resource_node_id: ${{ github.event.issue.node_id }}\n          status_value: 'Done'\n      - name: Remove waiting-for-author label\n        uses: andymckay/labeler@3a4296e9dcdf9576b0456050db78cfd34853f260\n        with:\n          remove-labels: 'waiting-for-author, no-activity'\n          repo-token: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n\n  waiting-for-author:\n    name: Waiting for Author\n    runs-on: timescaledb-runner-arm64\n    if: github.event_name == 'issues' && github.event.action == 'labeled'\n      && github.event.label.name == 'waiting-for-author'\n    steps:\n      - uses: leonsteinhaeuser/project-beta-automations@v2.0.0\n        with:\n          gh_token: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n          organization: timescale\n          project_id: 55\n          resource_node_id: ${{ github.event.issue.node_id }}\n          status_value: 'Waiting for Author'\n\n  waiting-for-engineering:\n    name: Waiting for Engineering\n    runs-on: timescaledb-runner-arm64\n    if: github.event_name == 'issue_comment' && !github.event.issue.pull_request\n    steps:\n      - name: Install dependencies\n        run: |\n          sudo apt-get update\n          sudo apt-get install jq\n      - name: Get board column of issue\n        id: extract_board_column\n        continue-on-error: true\n        run: |\n          # The following GraphQL query requests all issues from a project. It uses the repository\n          # to locate the issue and get the reference to the project. Then, a filter is applied\n          # (number: $project) to get the reference to the desired project (i.e., the bug board).\n          # Now, all issues from this project are requested. The reason for fetching all issues is\n          # because the current implementation of the GitHub GraphQL API for projects does not\n          # support server-side filters for issues and we can not restrict the query to our issue.\n          # Therefore, we fetch all issues and apply a filter on the client side in the next step.\n          gh api graphql --paginate -F issue=$ISSUE -F project=$PROJECT -F owner=$OWNER -F repo=$REPO -f query='\n            query board_column($issue: Int!, $project: Int!, $owner: String!, $repo: String!, $endCursor: String) {\n              repository(owner: $owner, name: $repo) {\n                issue(number: $issue) {\n                  projectV2(number: $project) {\n                    items(first: 100, after: $endCursor) {\n                      nodes {\n                        fieldValueByName(name: \"Status\") {\n                          ... on ProjectV2ItemFieldSingleSelectValue {\n                            name\n                          }\n                        }\n                        content {\n                          ... on Issue {\n                           id\n                           title\n                           number\n                           repository {\n                             name\n                             owner {\n                               login\n                              }\n                            }\n                          }\n                        }\n                      }\n                      pageInfo {\n                         hasNextPage\n                         endCursor\n                      }\n                    }\n                  }\n                }\n              }\n            }\n            ' > api_result\n            # Get board column for issue\n            board_column=$(jq -r \".data.repository.issue.projectV2.items.nodes[] |\n                select (.content.number == $ISSUE and .content.repository.name == \\\"$REPO\\\" and .content.repository.owner.login == \\\"$OWNER\\\") |\n                .fieldValueByName.name\" api_result)\n            echo \"Issue is in column: $board_column\"\n            echo \"issue_board_column=$board_column\" >> \"$GITHUB_OUTPUT\"\n        env:\n          OWNER: timescale\n          REPO: ${{ github.event.repository.name }}\n          PROJECT: 55\n          ISSUE: ${{ github.event.issue.number }}\n          GITHUB_TOKEN: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n\n      - name: Check if organization member\n        uses: tspascoal/get-user-teams-membership@v2\n        id: checkUserMember\n        with:\n         username: ${{ github.actor }}\n         organization: timescale\n         team: 'database-eng'\n         GITHUB_TOKEN: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n      - name: Remove waiting-for-author label\n        if: >-\n          steps.checkUserMember.outputs.isTeamMember == 'false' &&\n          steps.extract_board_column.outputs.issue_board_column == 'Waiting for Author'\n        uses: andymckay/labeler@3a4296e9dcdf9576b0456050db78cfd34853f260\n        with:\n          remove-labels: 'waiting-for-author, no-activity'\n          repo-token: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n      - name: Move to waiting for engineering column\n        if: ${{ steps.checkUserMember.outputs.isTeamMember == 'false'\n          && steps.extract_board_column.outputs.issue_board_column == 'Waiting for Author' }}\n        uses: leonsteinhaeuser/project-beta-automations@v2.0.0\n        with:\n          gh_token: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n          organization: timescale\n          project_id: 55\n          resource_node_id: ${{ github.event.issue.node_id }}\n          status_value: 'Waiting for Engineering'\n"
  },
  {
    "path": ".github/workflows/label-handling.yaml",
    "content": "#\n# Collection of actions to run when a label is added\n# to an issue or pull request\n#\nname: Label Handling\n\non:\n  pull_request:\n    types:\n      - labeled\n  issues:\n    types: \n      - labeled\n\njobs:\n  #\n  # Ping the Database team if the label was applied on a PR\n  #\n  pr-upgrade-requires-restart:\n    name: \"PR: Ping database team if 'upgrade-requires-restart' label is set\"\n    runs-on: ubuntu-latest\n    steps:\n      - name: \"PR: upgrade-requires-restart\"\n        if: github.event.label.name == 'upgrade-requires-restart'\n        uses: actions/github-script@v7\n        with:\n          script: |\n            github.rest.issues.createComment({\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              issue_number: context.payload.pull_request.number,\n              body: \"📣 the `upgrade-requires-restart` label was added, pinging @timescale/database-eng\"\n            })\n\n  #\n  # If a bug issue is opened, try to parse the affected TimescaleDB\n  # and Postgres version used, the add the labels to the issue. If \n  # the labels don't exist, created them. The format must be semver\n  # d.d.d+, any other format gets rejected and no label gets created.\n  # TimescaleDB labels are prefixed with a `v`, e.g. `v2.23.0``\n  # Postgres labels are prefixed with `postgres`, e.g. `postgres-17.4`\n  #\n  issue-add-version-labels-for-bugs:\n    name: \"Issue: Add TimescaleDB and Postgres version labels\"\n    runs-on: ubuntu-latest\n\n    # Only run if the issue has the \"bug\" label\n    if: contains(github.event.issue.labels.*.name, 'bug')\n    \n    permissions:\n      issues: write\n      \n    steps:\n      - name: Extract versions from issue body\n        id: extract-versions\n        uses: actions/github-script@v7\n        with:\n          script: |\n            const issueBody = context.payload.issue.body || '';\n            const issueNumber = context.issue.number;\n            \n            const results = {\n              issueNumber: issueNumber,\n              timescaledb: { found: false },\n              postgres: { found: false }\n            };\n            \n            // Extract TimescaleDB version (with line break: \"TimescaleDB version affected\\nX.Y.Z\")\n            const timescaleRegex = /TimescaleDB version affected\\s*[\\r\\n]+\\s*(\\d+\\.\\d+\\.\\d+)/i;\n            const timescaleMatch = issueBody.match(timescaleRegex);\n            \n            if (timescaleMatch) {\n              const version = timescaleMatch[1];\n              const formatRegex = /^\\d+\\.\\d+\\.\\d+$/;\n              \n              if (formatRegex.test(version)) {\n                console.log(`Found TimescaleDB version: ${version}`);\n                results.timescaledb = {\n                  found: true,\n                  version: version,\n                  label: `v${version}`\n                };\n              }\n            }\n            \n            // Extract PostgreSQL version (X.Y or X.Y.Z format, create X.Y label)\n            const postgresRegex = /PostgreSQL version used:\\s*[\\r\\n]+\\s*(\\d+\\.\\d+(?:\\.\\d+)?)/i;\n            const postgresMatch = issueBody.match(postgresRegex);\n            \n            if (postgresMatch) {\n              const fullVersion = postgresMatch[1];\n              // Extract just X.Y from X.Y or X.Y.Z\n              const majorMinor = fullVersion.match(/^(\\d+\\.\\d+)/);\n              \n              if (majorMinor) {\n                const version = majorMinor[1];\n                console.log(`Found PostgreSQL version: ${fullVersion}, using ${version} for label`);\n                results.postgres = {\n                  found: true,\n                  version: version,\n                  fullVersion: fullVersion,\n                  label: `postgres-${version}`\n                };\n              }\n            }\n            \n            if (!results.timescaledb.found) {\n              console.log('No valid TimescaleDB version found in issue body');\n            }\n            \n            if (!results.postgres.found) {\n              console.log('No valid PostgreSQL version found in issue body');\n            }\n            \n            return results;\n          result-encoding: json\n          \n      - name: Create and add TimescaleDB version label\n        if: fromJson(steps.extract-versions.outputs.result).timescaledb.found == true\n        uses: actions/github-script@v7\n        env:\n          RESULTS: ${{ steps.extract-versions.outputs.result }}\n        with:\n          script: |\n            const results = JSON.parse(process.env.RESULTS);\n            const labelName = results.timescaledb.label;\n            const owner = context.repo.owner;\n            const repo = context.repo.repo;\n            const issueNumber = results.issueNumber;\n            \n            // Check if label exists, create if it doesn't\n            try {\n              await github.rest.issues.getLabel({\n                owner,\n                repo,\n                name: labelName\n              });\n              console.log(`Label ${labelName} already exists`);\n            } catch (error) {\n              if (error.status === 404) {\n                console.log(`Label ${labelName} does not exist, creating it`);\n                await github.rest.issues.createLabel({\n                  owner,\n                  repo,\n                  name: labelName,\n                  color: '0366d6', // Blue color\n                  description: `TimescaleDB version ${results.timescaledb.version}`\n                });\n                console.log(`Created label ${labelName}`);\n              } else {\n                throw error;\n              }\n            }\n            \n            // Add label to issue\n            await github.rest.issues.addLabels({\n              owner,\n              repo,\n              issue_number: issueNumber,\n              labels: [labelName]\n            });\n            \n            console.log(`Added label ${labelName} to issue #${issueNumber}`);\n            \n      - name: Create and add PostgreSQL version label\n        if: fromJson(steps.extract-versions.outputs.result).postgres.found == true\n        uses: actions/github-script@v7\n        env:\n          RESULTS: ${{ steps.extract-versions.outputs.result }}\n        with:\n          script: |\n            const results = JSON.parse(process.env.RESULTS);\n            const labelName = results.postgres.label;\n            const owner = context.repo.owner;\n            const repo = context.repo.repo;\n            const issueNumber = results.issueNumber;\n            \n            // Check if label exists, create if it doesn't\n            try {\n              await github.rest.issues.getLabel({\n                owner,\n                repo,\n                name: labelName\n              });\n              console.log(`Label ${labelName} already exists`);\n            } catch (error) {\n              if (error.status === 404) {\n                console.log(`Label ${labelName} does not exist, creating it`);\n                await github.rest.issues.createLabel({\n                  owner,\n                  repo,\n                  name: labelName,\n                  color: '336791', // PostgreSQL blue color\n                  description: `PostgreSQL version ${results.postgres.version}`\n                });\n                console.log(`Created label ${labelName}`);\n              } else {\n                throw error;\n              }\n            }\n            \n            // Add label to issue\n            await github.rest.issues.addLabels({\n              owner,\n              repo,\n              issue_number: issueNumber,\n              labels: [labelName]\n            });\n            \n            console.log(`Added label ${labelName} to issue #${issueNumber}`);\n"
  },
  {
    "path": ".github/workflows/label-released-prs.yaml",
    "content": "# Apply \"released\" labels to the PRs that got into a particular release.\nname: Label Released PRs\n\non:\n  release:\n    types: [published]\n  push:\n    branches: [trigger/label-released-prs]\n\njobs:\n  label-release:\n    name: Label Released PRs\n    runs-on: timescaledb-runner-arm64\n    env:\n      GH_TOKEN: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Install Python Dependencies\n        run: |\n          pip install PyGithub requests\n\n      - name: Get latest release tag\n        run: |\n          echo \"LATEST_TAG=$(gh release view --json tagName --jq '.tagName')\" >> $GITHUB_ENV\n\n      - name: Run label-released script\n        run: |\n          scripts/label-released.py --release \"$LATEST_TAG\"\n"
  },
  {
    "path": ".github/workflows/libfuzzer.yaml",
    "content": "name: Libfuzzer\n\"on\":\n  schedule:\n    # run daily 1:00 on main branch\n    - cron: '0 1 * * *'\n  push:\n    branches:\n      - trigger/libfuzzer\n  pull_request:\n    paths:\n      - .github/workflows/libfuzzer.yaml\n      - 'tsl/test/fuzzing/compression/**'\n  workflow_dispatch:\n\njobs:\n  build:\n    runs-on: ubuntu-22.04\n    name: Build PostgreSQL and TimescaleDB\n    env:\n      PG_SRC_DIR: pgbuild\n      PG_INSTALL_DIR: postgresql\n\n    steps:\n    - name: Install Linux Dependencies\n      run: |\n        # Don't add ddebs here because the ddebs mirror is always 503 Service Unavailable.\n        # If needed, install them before opening the core dump.\n        sudo apt-get update\n        sudo apt-get install 7zip clang lld llvm flex bison libipc-run-perl \\\n          libtest-most-perl tree jq\n\n    - name: Checkout TimescaleDB\n      uses: actions/checkout@v4\n\n    - name: Read configuration\n      id: config\n      run: python -B .github/gh_config_reader.py\n\n    # We are going to rebuild Postgres daily, so that it doesn't suddenly break\n    # ages after the original problem.\n    - name: Get date for build caching\n      id: get-date\n      run: |\n        echo \"date=$(date +\"%d\")\" >> $GITHUB_OUTPUT\n\n    # we cache the build directory instead of the install directory here\n    # because extension installation will write files to install directory\n    # leading to a tainted cache\n    - name: Cache PostgreSQL\n      id: cache-postgresql\n      uses: actions/cache@v4\n      with:\n        path: ~/${{ env.PG_SRC_DIR }}\n        key: \"postgresql-libfuzzer-${{ steps.get-date.outputs.date }}-${{ hashFiles('.github/**') }}\"\n\n    - name: Build PostgreSQL\n      if: steps.cache-postgresql.outputs.cache-hit != 'true'\n      run: |\n       wget -q -O postgresql.tar.bz2 \\\n         https://ftp.postgresql.org/pub/source/v${{ steps.config.outputs.PG15_LATEST }}/postgresql-${{ steps.config.outputs.PG15_LATEST }}.tar.bz2\n        mkdir -p ~/$PG_SRC_DIR\n        tar --extract --file postgresql.tar.bz2 --directory ~/$PG_SRC_DIR --strip-components 1\n        cd ~/$PG_SRC_DIR\n        CC=clang ./configure --prefix=$HOME/$PG_INSTALL_DIR --with-openssl \\\n          --without-readline --without-zlib --without-libxml --enable-cassert \\\n          --enable-debug CC=clang \\\n          CFLAGS=\"-fuse-ld=lld -ggdb3 -O2 -fno-omit-frame-pointer\"\n        make -j$(getconf _NPROCESSORS_ONLN)\n\n    - name: Install PostgreSQL\n      run: |\n        make -C ~/$PG_SRC_DIR install\n        make -C ~/$PG_SRC_DIR/contrib/postgres_fdw install\n\n    - name: Upload config.log\n      if: always()\n      uses: actions/upload-artifact@v4\n      with:\n        name: config.log for PostgreSQL\n        path: ~/${{ env.PG_SRC_DIR }}/config.log\n\n    - name: Build TimescaleDB\n      run: |\n        set -e\n\n        export LIBFUZZER_PATH=$(dirname \"$(find $(llvm-config --libdir) -name libclang_rt.fuzzer_no_main-x86_64.a | head -1)\")\n\n        # Some pointers for the next time we have linking/undefined symbol problems:\n        # http://web.archive.org/web/20200926071757/https://github.com/google/sanitizers/issues/111\n        # http://web.archive.org/web/20231101091231/https://github.com/cms-sw/cmssw/issues/40680\n\n        cmake -B build -S . -DASSERTIONS=ON -DLINTER=OFF -DCMAKE_VERBOSE_MAKEFILE=1 \\\n            -DWARNINGS_AS_ERRORS=1 -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_COMPILER=clang \\\n            -DCMAKE_C_FLAGS=\"-fsanitize=fuzzer-no-link -lstdc++ -L$LIBFUZZER_PATH -l:libclang_rt.fuzzer_no_main-x86_64.a -static-libsan\" \\\n            -DCOMPRESSION_FUZZING=1 -DPG_PATH=$HOME/$PG_INSTALL_DIR\n\n        make -C build -j$(getconf _NPROCESSORS_ONLN) install\n\n    # Incredibly, the upload-artifact action can't preserve executable permissions:\n    # https://github.com/actions/upload-artifact/issues/38\n    # It's also extremely slow.\n    - name: Compress the installation directory\n      run: 7z a install.7z $HOME/$PG_INSTALL_DIR\n\n    - name: Save the installation directory\n      uses: actions/upload-artifact@v4\n      with:\n        name: fuzzing-install-dir\n        path: install.7z\n        if-no-files-found: error\n        retention-days: 1\n\n  fuzz:\n    needs: build\n    strategy:\n      fail-fast: false\n      matrix:\n        case: [\n            { algo: gorilla   , pgtype: float8, bulk: false, runs:   500000000 },\n            { algo: deltadelta, pgtype: int8  , bulk: false, runs:   500000000 },\n            { algo: gorilla   , pgtype: float8, bulk: true , runs:  1000000000 },\n            { algo: deltadelta, pgtype: int8  , bulk: true , runs:  1000000000 },\n            # array has a peculiar recv function that recompresses all input, so\n            # fuzzing it is much slower. The dictionary recv also uses it.\n            { algo: array     , pgtype: text  , bulk: false, runs:    10000000 },\n            { algo: array     , pgtype: text  , bulk: true , runs:    10000000 },\n            { algo: dictionary, pgtype: text  , bulk: false, runs:   100000000 },\n            { algo: dictionary, pgtype: text  , bulk: true , runs:   100000000 },\n            { algo: bool,       pgtype: bool  , bulk: false, runs:   500000000 },\n            { algo: bool,       pgtype: bool  , bulk: true , runs:  1000000000 },\n            # uuid tests are slower because it is more work to compare\n            { algo: dictionary, pgtype: uuid  , bulk: false, runs:   200000000 },\n            { algo: dictionary, pgtype: uuid  , bulk: true , runs:   500000000 },\n            { algo: uuid,       pgtype: uuid  , bulk: false, runs:   200000000 },\n            { algo: uuid,       pgtype: uuid  , bulk: true , runs:   400000000 },\n            ]\n\n    name: Fuzz decompression ${{ matrix.case.algo }} ${{ matrix.case.pgtype }} ${{ matrix.case.bulk && 'bulk' || 'rowbyrow' }}\n    runs-on: ubuntu-22.04\n    env:\n      PG_SRC_DIR: pgbuild\n      PG_INSTALL_DIR: postgresql\n      JOB_NAME: Fuzz decompression ${{ matrix.case.algo }} ${{ matrix.case.pgtype }} ${{ matrix.case.bulk && 'bulk' || 'rowbyrow' }}\n\n    steps:\n    - name: Install Linux dependencies\n      run: |\n        sudo apt update\n        sudo apt install 7zip systemd-coredump gdb\n\n    - name: Checkout TimescaleDB\n      uses: actions/checkout@v4\n\n    - name: Download the installation directory\n      uses: actions/download-artifact@v4\n      with:\n        name: fuzzing-install-dir\n\n    - name: Unpack the installation directory\n      run: 7z x -o$HOME install.7z\n\n    - name: initdb\n      run: |\n        # Have to do this before initializing the corpus, or initdb will complain.\n        set -xeu\n\n        export PGDATA=db\n        export PGPORT=5432\n        export PGDATABASE=postgres\n        export PATH=$HOME/$PG_INSTALL_DIR/bin:$PATH\n        initdb\n        echo \"shared_preload_libraries = 'timescaledb'\" >> $PGDATA/postgresql.conf\n\n    - name: Set configuration\n      id: config\n      run: |\n        set -x\n        echo \"cache_prefix=${{ format('libfuzzer-corpus-2-{0}-{1}', matrix.case.algo, matrix.case.pgtype) }}\" >> $GITHUB_OUTPUT\n        echo \"name=${{ matrix.case.algo }} ${{ matrix.case.pgtype }} ${{ matrix.case.bulk && 'bulk' || 'rowbyrow' }}\" >> $GITHUB_OUTPUT\n\n    - name: Restore the cached fuzzing corpus (bulk)\n      id: restore-corpus-cache-bulk\n      uses: actions/cache/restore@v4\n      with:\n        path: db/corpus-bulk\n        key: \"${{ steps.config.outputs.cache_prefix }}-bulk\"\n\n    # We save the row-by-row corpus separately from the bulk corpus, so that\n    # they don't overwrite each other. Now we are going to combine them.\n    - name: Restore the cached fuzzing corpus (rowbyrow)\n      id: restore-corpus-cache-rowbyrow\n      uses: actions/cache/restore@v4\n      with:\n        path: db/corpus-rowbyrow\n        key: \"${{ steps.config.outputs.cache_prefix }}-rowbyrow\"\n\n    - name: Initialize the fuzzing corpus\n      run: |\n        # Combine the cached corpus from rowbyrow and bulk fuzzing, and from repository.\n        mkdir -p db/corpus{,-rowbyrow,-bulk}\n        find \"tsl/test/fuzzing/compression/${{ matrix.case.algo }}-${{ matrix.case.pgtype }}\" -type f -exec cp -n -t db/corpus {} +\n        find \"db/corpus-rowbyrow\" -type f -exec cp -n -t db/corpus {} +\n        find \"db/corpus-bulk\" -type f -exec cp -n -t db/corpus {} +\n        ls db/corpus | wc -l\n\n    - name: Run libfuzzer for compression\n      run: |\n        set -xeu\n\n        export PGDATA=db\n        export PGPORT=5432\n        export PGDATABASE=postgres\n        export PATH=$HOME/$PG_INSTALL_DIR/bin:$PATH\n        pg_ctl -o \"-clogging_collector=true\" -o \"-clog_destination=jsonlog,stderr\" \\\n            -o \"-clog_directory=$(readlink -f .)\" -o \"-clog_filename=postmaster.log\" \\\n            -o \"-clog_error_verbosity=verbose\" start\n\n        psql -c \"create extension timescaledb;\"\n\n        # Create the fuzzing functions\n        export MODULE_NAME=$(basename $(find $HOME/$PG_INSTALL_DIR -name \"timescaledb-tsl-*.so\"))\n        psql -a -c \"create or replace function fuzz(algo cstring, pgtype regtype,\n                bulk bool, runs int)\n            returns int as '\"$MODULE_NAME\"', 'ts_fuzz_compression' language c;\n\n            create or replace function ts_read_compressed_data_directory(algo cstring,\n                pgtype regtype, path cstring, bulk bool)\n            returns table(path text, bytes int, rows int, sqlstate text, location text)\n            as '\"$MODULE_NAME\"', 'ts_read_compressed_data_directory' language c;\n\n            \"\n\n        # Start more fuzzing processes in the background. We won't even monitor\n        # their progress, because the server will panic if they find an error.\n        for x in {2..$(getconf _NPROCESSORS_ONLN)}\n        do\n          psql -v ON_ERROR_STOP=1 -c \"select fuzz('${{ matrix.case.algo }}',\n            '${{ matrix.case.pgtype }}', '${{ matrix.case.bulk }}', ${{ matrix.case.runs }});\" &\n        done\n\n        # Start the one fuzzing process that we will monitor, in foreground.\n        # The LLVM fuzzing driver calls exit(), so we expect to lose the connection.\n        ret=0\n        psql -v ON_ERROR_STOP=1 -c \"select fuzz('${{ matrix.case.algo }}',\n            '${{ matrix.case.pgtype }}', '${{ matrix.case.bulk }}', ${{ matrix.case.runs }});\" || ret=$?\n        if ! [ $ret -eq 2 ]\n        then\n            >&2 echo \"Unexpected psql exit code $ret\"\n            exit 1\n        fi\n\n        ls db/corpus | wc -l\n\n        fn=\"ts_read_compressed_data_directory('${{ matrix.case.algo }}',\n                '${{ matrix.case.pgtype }}', 'corpus', '${{ matrix.case.bulk }}')\"\n\n        # Show the statistics about fuzzing corpus\n        psql -c \"select count(*), location, min(sqlstate), min(path)\n            from $fn\n            group by location order by count(*) desc\n        \"\n\n        # Save interesting cases because the caches are not available for download from UI\n        mkdir -p interesting\n        psql -qtAX -c \"select distinct on (location) 'db/' || path from $fn\n            order by location, bytes, path\n        \" | xargs cp -t interesting\n\n        # Check that we don't have any internal errors\n        errors=$(psql -qtAX --set=ON_ERROR_STOP=1 -c \"select count(*)\n            from $fn\n            where sqlstate = 'XX000'\")\n        echo \"Internal program errors: $errors\"\n        [ $errors -eq 0 ] || exit 1\n\n        # Shouldn't have any WARNINGS in the log.\n        ! grep -F \"] WARNING: \" postmaster.log\n\n        # Check that the server is still alive.\n        psql -c \"select 1\"\n\n    - name: Collect the logs\n      if: always()\n      id: collectlogs\n      run: |\n        # wait in case there are in-progress coredumps\n        sleep 10\n        if coredumpctl -q list >/dev/null; then echo \"coredumps=true\" >>$GITHUB_OUTPUT; fi\n        # print OOM killer information\n        sudo journalctl --system -q --facility=kern --grep \"Killed process\" || true\n\n    - name: Save PostgreSQL log\n      if: always()\n      uses: actions/upload-artifact@v4\n      with:\n        name: PostgreSQL log for ${{ steps.config.outputs.name }}\n        path: postgres.*\n\n    - name: Save fuzzer-generated crash cases\n      if: always()\n      uses: actions/upload-artifact@v4\n      with:\n        name: Crash cases for ${{ steps.config.outputs.name }}\n        path: db/crash-*\n        if-no-files-found: ignore\n\n    - name: Save interesting cases\n      if: always()\n      uses: actions/upload-artifact@v4\n      with:\n        name: Interesting cases for ${{ steps.config.outputs.name }}\n        path: interesting/\n\n    # We use separate restore/save actions, because the default action won't\n    # save the updated folder after the cache hit. We also want to save the\n    # cache after fuzzing errors, and the default action doesn't save after\n    # errors.\n    # We can't overwrite the existing cache, so we add a unique suffix. The\n    # cache is matched by key prefix, not exact key, and picks the newest\n    # matching item, so this works.\n    # The caches for rowbyrow and bulk fuzzing are saved separately, otherwise\n    # the slower job would always overwrite the cache from the faster one. We\n    # want to combine corpuses from bulk and rowbyrow fuzzing for better\n    # coverage.\n    # Note that the cache action cannot be restored on a path different from the\n    # one it was saved from. To make our lives more interesting, it is not\n    # directly documented anywhere, but we can deduce it from path influencing\n    # the version.\n    - name: Change corpus path to please the 'actions/cache' GitHub Action\n      if: always()\n      run: |\n        rm -rf db/corpus-{bulk,rowbyrow} ||:\n        mv -fT db/corpus{,-${{ matrix.case.bulk && 'bulk' || 'rowbyrow' }}}\n\n    - name: Save fuzzer corpus\n      if: always()\n      uses: actions/cache/save@v4\n      with:\n        path: db/corpus-${{ matrix.case.bulk && 'bulk' || 'rowbyrow' }}\n        key: \"${{ format('{0}-{1}-{2}-{3}',\n            steps.config.outputs.cache_prefix,\n            matrix.case.bulk && 'bulk' || 'rowbyrow',\n            github.run_id, github.run_attempt) }}\"\n\n    - name: Stack trace\n      if: always() && steps.collectlogs.outputs.coredumps == 'true'\n      run: |\n        sudo coredumpctl gdb <<<\"\n          set verbose on\n          set trace-commands on\n          show debug-file-directory\n          printf \"'\"'\"query = '%s'\\n\\n\"'\"'\", debug_query_string\n          frame function ExceptionalCondition\n          printf \"'\"'\"condition = '%s'\\n\"'\"'\", conditionName\n          up 1\n          l\n          info args\n          info locals\n          bt full\n        \" 2>&1 | tee stacktrace.log\n        ./scripts/bundle_coredumps.sh\n        exit 1 # Fail the job if we have core dumps.\n\n    - name: Upload core dumps\n      if: always() && steps.collectlogs.outputs.coredumps == 'true'\n      uses: actions/upload-artifact@v4\n      with:\n        name: Coredumps for ${{ steps.config.outputs.name }}\n        path: coredumps\n\n    - name: Upload test results to the database\n      if: always()\n      env:\n        CI_STATS_DB: ${{ secrets.CI_STATS_DB }}\n        GITHUB_EVENT_NAME: ${{ github.event_name }}\n        GITHUB_REF_NAME: ${{ github.ref_name }}\n        GITHUB_REPOSITORY: ${{ github.repository }}\n        GITHUB_RUN_ATTEMPT: ${{ github.run_attempt }}\n        GITHUB_RUN_ID: ${{ github.run_id }}\n        GITHUB_RUN_NUMBER: ${{ github.run_number }}\n        JOB_STATUS: ${{ job.status }}\n      run: |\n        if [[ \"${{ github.event_name }}\" == \"pull_request\" ]] ;\n        then\n            GITHUB_PR_NUMBER=\"${{ github.event.number }}\"\n        else\n            GITHUB_PR_NUMBER=0\n        fi\n        export GITHUB_PR_NUMBER\n        scripts/upload_ci_stats.sh\n"
  },
  {
    "path": ".github/workflows/linux-32bit-build-and-test.yaml",
    "content": "name: Regression Linux i386\n\"on\":\n  schedule:\n    # run daily 0:00 on main branch\n    # Since we use the date as a part of the cache key to ensure no\n    # stale cache entries hiding build failures we need to make sure\n    # we have a cache entry present before workflows that depend on cache\n    # are run.\n    - cron: '0 0 * * *'\n  push:\n    branches:\n      - main\n      - ?.*.x\n      - prerelease_test\n      - trigger/regression\n  pull_request:\njobs:\n  config:\n    runs-on: ubuntu-latest\n    outputs:\n      pg_latest: ${{ github.event_name == 'pull_request' && steps.setter.outputs.PG_LATEST_ONLY || steps.setter.outputs.PG_LATEST }}\n    steps:\n    - name: Checkout source code\n      uses: actions/checkout@v4\n    - name: Read configuration\n      id: setter\n      run: python .github/gh_config_reader.py\n\n  check_paths:\n    runs-on: ubuntu-latest\n    outputs:\n      run_ci: >-\n        ${{ github.event_name == 'push'\n          || (steps.filter.outputs.src == 'true' && !contains(github.event.pull_request.labels.*.name, 'skip-ci'))\n         }}\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dorny/paths-filter@v3\n        id: filter\n        with:\n          filters: .github/filters.yaml\n\n  regress:\n    if: needs.check_paths.outputs.run_ci == 'true'\n    name: PG${{ matrix.pg }} ${{ matrix.build_type }} linux-i386\n    runs-on: ubuntu-latest\n    needs: [config, check_paths]\n    container:\n      image: i386/debian:bookworm-slim\n      options: --privileged --ulimit core=-1:-1 --ulimit fsize=-1:-1\n      env:\n        PG_SRC_DIR: pgbuild\n        PG_INSTALL_DIR: postgresql\n        CLANG: clang-14\n        CC: clang-14\n        CXX: clang++-14\n        DEBIAN_FRONTEND: noninteractive\n        # vectorized_aggregation has different output on i386 because int8 is by\n        # reference and currently it cannot be used for vectorized hash grouping.\n        # vector_agg_text and vector_agg_groupagg use the UMASH hashing library\n        # that we can't compile on i386.\n        # For the same reason of different hashing, we ignore the results of\n        # several bloom filter tests as well.\n        IGNORES: >-\n          append-*\n          bgw_db_scheduler*\n          columnar_scan_cost\n          compress_bloom_sparse_compat\n          compress_bloom_sparse_debug\n          compress_composite_bloom_debug\n          compress_qualpushdown_saop\n          compress_sort_transform\n          compression_allocation\n          merge_append_partially_compressed\n          net\n          pg_dump\n          telemetry\n          transparent_decompress_chunk-*\n          transparent_decompression-*\n          uncompressed_size\n          vector_agg_byte\n          vector_agg_expr\n          vector_agg_filter\n          vector_agg_groupagg\n          vector_agg_grouping\n          vector_agg_planning-*\n          vector_agg_text\n          vector_agg_uuid\n          vectorized_aggregation\n          ${{ matrix.pg < 16 && 'columnar_scan_cost' || '' }}\n        SKIPS: chunk_adaptive histogram_test-*\n        EXTENSIONS: \"postgres_fdw test_decoding\"\n    strategy:\n      matrix:\n        pg: ${{ fromJson(needs.config.outputs.pg_latest) }}\n        build_type: [ Debug ]\n      fail-fast: false\n\n    steps:\n\n    # /__e/node16/bin/node (used by actions/checkout@v4) needs 64-bit libraries\n    - name: Install 64-bit libraries for GitHub actions\n      run: |\n        apt-get update\n        apt-get install -y lib64atomic1 lib64gcc-s1 lib64stdc++6 libc6-amd64 jq\n\n    - name: Install build dependencies\n      run: |\n        PG_MAJOR=$(echo \"${{ matrix.pg }}\" | sed -e 's![.].*!!')\n        echo '/tmp/core.%h.%e.%t' > /proc/sys/kernel/core_pattern\n        apt-get install -y gcc make cmake libssl-dev libipc-run-perl \\\n          libtest-most-perl sudo gdb git wget gawk lbzip2 flex bison lcov base-files \\\n          locales clang-14 llvm-14 llvm-14-dev llvm-14-tools postgresql-client pkgconf \\\n          icu-devtools\n\n    - name: Checkout TimescaleDB\n      uses: actions/checkout@v4\n\n    # We are going to rebuild Postgres daily, so that it doesn't suddenly break\n    # ages after the original problem.\n    - name: Get date for build caching\n      id: get-date\n      run: |\n        echo \"date=$(date +\"%d\")\" >> $GITHUB_OUTPUT\n\n    # we cache the build directory instead of the install directory here\n    # because extension installation will write files to install directory\n    # leading to a tainted cache\n    - name: Cache PostgreSQL ${{ matrix.pg }} ${{ matrix.build_type }}\n      id: cache-postgresql\n      uses: actions/cache@v4\n      with:\n        path: ~/${{ env.PG_SRC_DIR }}\n        key: \"linux-32-bit-postgresql-${{ matrix.pg }}-${{ matrix.cc }}\\\n          -${{ steps.get-date.outputs.date }}-${{ hashFiles('.github/**') }}\"\n\n    - name: Build PostgreSQL ${{ matrix.pg }}\n      if: steps.cache-postgresql.outputs.cache-hit != 'true'\n      run: |\n        wget -q -O postgresql.tar.bz2 \\\n          https://ftp.postgresql.org/pub/source/v${{ matrix.pg }}/postgresql-${{ matrix.pg }}.tar.bz2\n        mkdir -p ~/$PG_SRC_DIR\n        tar --extract --file postgresql.tar.bz2 --directory ~/$PG_SRC_DIR --strip-components 1\n        cd ~/$PG_SRC_DIR\n        # When building on i386 with the clang compiler, Postgres requires -msse2 to be used\n        ./configure --prefix=$HOME/$PG_INSTALL_DIR --with-openssl \\\n          --without-readline --without-zlib --without-libxml --enable-cassert \\\n          --enable-debug --with-llvm --without-icu LLVM_CONFIG=llvm-config-14 CFLAGS=\"-msse2\"\n        make -j $(getconf _NPROCESSORS_ONLN)\n        for ext in ${EXTENSIONS}; do\n          make -j $(getconf _NPROCESSORS_ONLN) -C contrib/${ext}\n        done\n\n    - name: Install PostgreSQL ${{ matrix.pg }}\n      run: |\n        useradd postgres\n        cd ~/$PG_SRC_DIR\n        make install\n        for ext in ${EXTENSIONS}; do\n          make -C contrib/${ext} install\n        done\n        chown -R postgres:postgres $HOME/$PG_INSTALL_DIR\n        sed -i 's/^# *\\(en_US.UTF-8\\)/\\1/' /etc/locale.gen\n        locale-gen\n\n    - name: Upload config.log\n      if: always() && steps.cache-postgresql.outputs.cache-hit != 'true'\n      uses: actions/upload-artifact@v4\n      with:\n        name: config.log linux-i386 PG${{ matrix.pg }}\n        path: ~/${{ env.PG_SRC_DIR }}/config.log\n\n    - name: Build TimescaleDB\n      run: |\n        # The owner of the checkout directory and the files do not match. Add the directory to\n        # Git's \"safe.directory\" setting. Otherwise git would complain about\n        # 'detected dubious ownership in repository'\n        git config --global --add safe.directory $(pwd)\n        ./bootstrap -DCMAKE_BUILD_TYPE=\"${{ matrix.build_type }}\" \\\n          -DPG_SOURCE_DIR=~/$PG_SRC_DIR -DPG_PATH=~/$PG_INSTALL_DIR \\\n          -DREQUIRE_ALL_TESTS=ON \\\n          -DTEST_PG_LOG_DIRECTORY=\"$(readlink -f .)\"\n        make -j $(getconf _NPROCESSORS_ONLN) -C build\n        make -C build install\n        chown -R postgres:postgres .\n\n    - name: Upload CMake Logs\n      if: always()\n      uses: actions/upload-artifact@v4\n      with:\n        name: CMake Logs linux-i386 PG${{ matrix.pg }}\n        path: |\n            build/CMakeCache.txt\n            build/CMakeFiles/CMakeConfigureLog.yaml\n            build/CMakeFiles/CMakeError.log\n            build/CMakeFiles/CMakeOutput.log\n            build/compile_commands.json\n\n    - name: make installcheck\n      id: installcheck\n      shell: bash\n      run: |\n        set -o pipefail\n        export LANG=C.UTF-8\n        #\n        # Even disabling parallel plans the Sort Method on 32bits\n        # is always 'still in progress' for PG17 and PG18. So for now added\n        # those tests to SKIPS.\n        #\n        PG_MAJOR=$(echo \"${{ matrix.pg }}\" | sed -e 's![.].*!!')\n        if [ ${PG_MAJOR} -eq 17 ]; then\n          SKIPS=\"${SKIPS} constraint_exclusion_prepared-17 ordered_append*\"\n        fi\n        if [ ${PG_MAJOR} -eq 18 ]; then\n          SKIPS=\"${SKIPS} constraint_exclusion_prepared-18 ordered_append*\"\n        fi\n        # PostgreSQL cannot be run as root. So, switch to postgres user.\n        runuser -u postgres -- \\\n          make -k -C build installcheck IGNORES=\"${IGNORES}\" \\\n            SKIPS=\"${SKIPS}\" PSQL=\"${HOME}/${PG_INSTALL_DIR}/bin/psql\" \\\n            | tee installcheck.log\n\n    - name: Show regression diffs\n      if: always()\n      id: collectlogs\n      shell: bash\n      run: |\n        find . -name regression.diffs -exec cat {} + > regression.log\n\n        if [[ -s regression.log ]]; then echo \"regression_diff=true\" >>$GITHUB_OUTPUT; fi\n        grep -e 'FAILED' -e 'failed (ignored)' -e 'not ok' installcheck.log || true\n        cat regression.log\n\n    - name: Save regression diffs\n      if: always() && steps.collectlogs.outputs.regression_diff == 'true'\n      uses: actions/upload-artifact@v4\n      with:\n        name: Regression diff linux-i386 PG${{ matrix.pg }}\n        path: |\n          regression.log\n          installcheck.log\n\n    - name: Save PostgreSQL log\n      if: always()\n      uses: actions/upload-artifact@v4\n      with:\n        name: PostgreSQL log linux-i386 PG${{ matrix.pg }}\n        path: postmaster.*\n\n    - name: Stack trace Linux\n      if: always()\n      shell: bash\n      run: |\n        # wait in case there are in-progress coredumps\n        sleep 10\n        if compgen -G \"/tmp/core*\" > /dev/null; then\n          PG_MAJOR=$(echo \"${{ matrix.pg }}\" | sed -e 's![.].*!!')\n          for file in /tmp/core*\n          do\n            gdb \"${HOME}/${PG_INSTALL_DIR}/bin/postgres\" -c $file <<<\"\n          set verbose on\n          set trace-commands on\n          show debug-file-directory\n          printf \"'\"'\"query = '%s'\\n\\n\"'\"'\", debug_query_string\n          bt full\n\n          # We try to find ExceptionalCondition frame to print the failed condition\n          # for searching in logs.\n          frame function ExceptionalCondition\n          printf \"'\"'\"condition = '%s'\\n\"'\"'\", conditionName\n\n          # Hopefully now we should be around the failed assertion, print where\n          # we are.\n          up 1\n          list\n          info args\n          info locals\n            \" 2>&1 | tee -a stacktrace.log\n          done\n          echo \"coredumps=true\" >>$GITHUB_OUTPUT\n          exit 1\n        fi\n\n\n    - name: Coredumps\n      if: always() && steps.coredumps.outputs.coredumps == 'true'\n      uses: actions/upload-artifact@v4\n      with:\n        name: Coredumps linux-i386 PG${{ matrix.pg }}\n        path: coredumps\n\n    - name: Save stacktraces\n      if: always() && steps.coredumps.outputs.coredumps == 'true'\n      uses: actions/upload-artifact@v4\n      with:\n        name: Stacktraces linux-i386 PG${{ matrix.pg }}\n        path: stacktrace.log\n\n    - name: Save TAP test logs\n      if: always()\n      uses: actions/upload-artifact@v4\n      with:\n        name: TAP test logs ${{ matrix.os }} ${{ matrix.name }} ${{ matrix.pg }}\n        path: |\n          build/test/tmp_check/log\n          build/tsl/test/tmp_check/log\n\n    - name: Upload test results to the database\n      if: always()\n      shell: bash\n      env:\n        # GitHub Actions allow you neither to use the env context for the job name,\n        # nor to access the job name from the step context, so we have to\n        # duplicate it to work around this nonsense.\n        JOB_NAME: PG${{ matrix.pg }} ${{ matrix.build_type }} linux-i386\n        CI_STATS_DB: ${{ secrets.CI_STATS_DB }}\n        GITHUB_EVENT_NAME: ${{ github.event_name }}\n        GITHUB_REF_NAME: ${{ github.ref_name }}\n        GITHUB_REPOSITORY: ${{ github.repository }}\n        GITHUB_RUN_ATTEMPT: ${{ github.run_attempt }}\n        GITHUB_RUN_ID: ${{ github.run_id }}\n        GITHUB_RUN_NUMBER: ${{ github.run_number }}\n        JOB_STATUS: ${{ job.status }}\n      run: |\n        if [[ \"${{ github.event_name }}\" == \"pull_request\" ]] ;\n        then\n            GITHUB_PR_NUMBER=\"${{ github.event.number }}\"\n        else\n            GITHUB_PR_NUMBER=0\n        fi\n        export GITHUB_PR_NUMBER\n        scripts/upload_ci_stats.sh\n\n  report_status:\n    name: Regression Linux i386 summary\n    needs: [check_paths, regress]\n    if: always()\n    runs-on: ubuntu-latest\n    steps:\n      - run: |\n          # If the detector failed, or the matrix failed, exit with error\n          if [[ \"${{ needs.regress.result }}\" == \"failure\" ]]; then\n            exit 1\n          fi\n          echo \"All checks passed or were safely skipped.\"\n\n"
  },
  {
    "path": ".github/workflows/linux-build-and-test.yaml",
    "content": "name: Regression\n\"on\":\n  schedule:\n    # run daily 0:00 on main branch\n    # Since we use the date as a part of the cache key to ensure no\n    # stale cache entries hiding build failures we need to make sure\n    # we have a cache entry present before workflows that depend on cache\n    # are run.\n    - cron: '0 0 * * *'\n  push:\n    branches:\n      - main\n      - ?.*.x\n      - prerelease_test\n      - trigger/regression\n  pull_request:\njobs:\n  matrixbuilder:\n    runs-on: ubuntu-latest\n    outputs:\n      matrix: ${{ steps.set-matrix.outputs.matrix }}\n    steps:\n    - name: Checkout source code\n      uses: actions/checkout@v4\n\n    - name: Build matrix\n      id: set-matrix\n      run: |\n        if [[ \"${{ github.event_name }}\" == \"pull_request\" ]] ;\n        then\n          git fetch origin ${{ github.base_ref }}:base\n          .github/gh_matrix_builder.py ${{ github.event_name }} base\n        else\n          .github/gh_matrix_builder.py ${{ github.event_name }}\n        fi\n\n  check_paths:\n    runs-on: ubuntu-latest\n    outputs:\n      run_ci: >-\n        ${{ github.event_name == 'push'\n          || (steps.filter.outputs.src == 'true' && !contains(github.event.pull_request.labels.*.name, 'skip-ci'))\n         }}\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dorny/paths-filter@v3\n        id: filter\n        with:\n          filters: .github/filters.yaml\n\n  regress:\n    if: needs.check_paths.outputs.run_ci == 'true'\n    # Change the JOB_NAME variable below when changing the name.\n    name: PG${{ matrix.pg }}${{ matrix.snapshot }} ${{ matrix.name }} ${{ matrix.os }}\n    needs: [matrixbuilder, check_paths]\n    runs-on: ${{ matrix.os }}\n\n    env:\n      PG_SRC_DIR: pgbuild\n      PG_INSTALL_DIR: postgresql\n      CLANG: ${{ matrix.clang }}\n      CC: ${{ matrix.cc }}\n      CXX: ${{ matrix.cxx }}\n\n      # For some reason, on PG <= 15 with faketime the client backends get the\n      # modified time, and the background workers the unmodified time, so it\n      # doesn't work.\n      FAKETIME: |\n        ${{ (contains(matrix.os, 'ubuntu')\n               && !startsWith(matrix.pg, '14.')\n               && !startsWith(matrix.pg, '15.')\n            ) && 'faketime -f +379d' || '' }}\n\n    strategy:\n      matrix: ${{ fromJson(needs.matrixbuilder.outputs.matrix) }}\n      fail-fast: false\n\n    steps:\n    - name: Install Linux Dependencies\n      if: runner.os == 'Linux'\n      run: |\n        # Don't add ddebs here because the ddebs mirror is always 503 Service Unavailable.\n        # If needed, install them before opening the core dump.\n        sudo apt-get update\n        sudo apt-get install flex bison lcov systemd-coredump gdb libipc-run-perl \\\n          libtest-most-perl pkgconf icu-devtools faketime jq ${{ matrix.extra_packages }}\n\n    - name: Install macOS Dependencies\n      if: runner.os == 'macOS'\n      run: |\n        # Disable the automatic dependency upgrade executed by `brew install`\n        # https://docs.brew.sh/Manpage#install-options-formulacask-\n        HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install gawk\n        brew --prefix openssl@3\n        # Install perl modules after last Homebew call, since Homebrew can change the perl version\n        sudo perl -MCPAN -e \"CPAN::Shell->notest('install', 'IPC::Run')\"\n        sudo perl -MCPAN -e \"CPAN::Shell->notest('install', 'Test::Most')\"\n\n    - name: Setup macOS coredump directory\n      if: runner.os == 'macOS'\n      run: sudo chmod 777 /cores\n\n    - name: Checkout TimescaleDB\n      uses: actions/checkout@v4\n\n    # We are going to rebuild Postgres daily, so that it doesn't suddenly break\n    # ages after the original problem.\n    - name: Get date for build caching\n      id: get-date\n      run: |\n        echo \"date=$(date +\"%d\")\" >> $GITHUB_OUTPUT\n\n    # on macOS the path used is depending on the runner version leading to cache failure\n    # when the runner version changes so we extract runner version from path and add it\n    # as cache suffix\n    - name: Cache suffix\n      if: runner.os == 'macOS'\n      run: echo \"CACHE_SUFFIX=-${ImageVersion}\" >> $GITHUB_ENV\n\n    # we cache the build directory instead of the install directory here\n    # because extension installation will write files to install directory\n    # leading to a tainted cache\n    - name: Cache PostgreSQL ${{ matrix.pg }} ${{ matrix.build_type }}\n      id: cache-postgresql\n      if: matrix.snapshot != 'snapshot' && runner.os == 'Linux'\n      uses: actions/cache@v4\n      with:\n        path: ~/${{ env.PG_SRC_DIR }}\n        key: \"${{ matrix.os }}-postgresql-${{ matrix.pg }}-${{ matrix.cc }}-${{ steps.get-date.outputs.date }}-${{ hashFiles('.github/**') }}${{ env.CACHE_SUFFIX }}\"\n\n    - name: Build PostgreSQL ${{ matrix.pg }}${{ matrix.snapshot }}\n      if: steps.cache-postgresql.outputs.cache-hit != 'true'\n      run: |\n        if [ \"${{ matrix.snapshot }}\" = \"snapshot\" ]; then\n          wget -q -O postgresql.tar.bz2 \\\n            https://ftp.postgresql.org/pub/snapshot/${{ matrix.pg }}/postgresql-${{ matrix.pg }}-snapshot.tar.bz2\n        else\n          wget -q -O postgresql.tar.bz2 \\\n            https://ftp.postgresql.org/pub/source/v${{ matrix.pg }}/postgresql-${{ matrix.pg }}.tar.bz2\n        fi\n        mkdir -p ~/$PG_SRC_DIR\n        tar --extract --file postgresql.tar.bz2 --directory ~/$PG_SRC_DIR --strip-components 1\n        cd ~/$PG_SRC_DIR\n        ./configure --prefix=$HOME/$PG_INSTALL_DIR --with-openssl \\\n          --without-readline --without-zlib --without-libxml ${{ matrix.pg_extra_args }}\n        make -j $(getconf _NPROCESSORS_ONLN)\n        for ext in ${{ matrix.pg_extensions }}; do\n          make -j $(getconf _NPROCESSORS_ONLN) -C contrib/${ext}\n        done\n\n    - name: Install PostgreSQL ${{ matrix.pg }}\n      run: |\n        cd ~/$PG_SRC_DIR\n        make install\n        for ext in ${{ matrix.pg_extensions }}; do\n          make -C contrib/${ext} install\n        done\n        echo \"$HOME/$PG_INSTALL_DIR/bin\" >> \"${GITHUB_PATH}\"\n\n    - name: Upload config.log\n      if: always() && steps.cache-postgresql.outputs.cache-hit != 'true'\n      uses: actions/upload-artifact@v4\n      with:\n        name: config.log for PostgreSQL ${{ matrix.os }} ${{ matrix.name }} ${{ matrix.pg }}\n        path: ~/${{ env.PG_SRC_DIR }}/config.log\n\n    - name: Test telemetry without OpenSSL\n      if: github.event_name != 'pull_request' && runner.os == 'Linux' && matrix.build_type == 'Debug'\n      run: |\n        BUILD_DIR=nossl ./bootstrap -DCMAKE_BUILD_TYPE=Debug \\\n          -DPG_SOURCE_DIR=$HOME/$PG_SRC_DIR -DPG_PATH=$HOME/$PG_INSTALL_DIR \\\n          ${{ matrix.tsdb_build_args }} -DCODECOVERAGE=${{ matrix.coverage }} -DUSE_OPENSSL=OFF \\\n          -DTEST_PG_LOG_DIRECTORY=\"$(readlink -f .)\"\n        make -j $(getconf _NPROCESSORS_ONLN) -C nossl\n        make -C nossl install\n        make -C nossl regresscheck TESTS=telemetry\n\n    - name: Build TimescaleDB\n      run: |\n        # Show the actual architecture this CI runner has\n        \"$CC\" -march=native -E -v - </dev/null 2>&1 | grep cc1\n\n        ./bootstrap -DCMAKE_BUILD_TYPE=\"${{ matrix.build_type }}\" \\\n          -DPG_SOURCE_DIR=$HOME/$PG_SRC_DIR -DPG_PATH=$HOME/$PG_INSTALL_DIR \\\n          ${{ matrix.tsdb_build_args }} -DCODECOVERAGE=${{ matrix.coverage }} \\\n          -DTEST_PG_LOG_DIRECTORY=\"$(readlink -f .)\"\n        make -j $(getconf _NPROCESSORS_ONLN) -C build\n        make -C build install\n\n    - name: Upload CMake Logs\n      if: always()\n      uses: actions/upload-artifact@v4\n      with:\n        name: CMake Logs ${{ matrix.os }} ${{ matrix.name }} ${{ matrix.pg }}\n        path: |\n            build/CMakeCache.txt\n            build/CMakeFiles/CMakeConfigureLog.yaml\n            build/CMakeFiles/CMakeError.log\n            build/CMakeFiles/CMakeOutput.log\n            build/compile_commands.json\n\n    - name: Check exported symbols\n      run: ./build/scripts/export_prefix_check.sh\n\n    - name: make installcheck\n      if: matrix.installcheck\n      run: |\n        set -o pipefail\n        make -k -C build installcheck IGNORES=\"${{ join(matrix.ignored_tests, ' ') }}\" \\\n            SKIPS=\"${{ join(matrix.skipped_tests, ' ') }}\" ${{ matrix.installcheck_args }} \\\n            | tee installcheck.log\n\n    - name: pginstallcheck\n      if: matrix.pginstallcheck\n      run: make -C build pginstallcheck\n\n    - name: coverage\n      if: matrix.coverage\n      run: make -j $(getconf _NPROCESSORS_ONLN) -k -C build coverage\n\n    - name: Send coverage report to Codecov.io app\n      if: matrix.coverage\n      uses: codecov/codecov-action@v5\n      with:\n        token: ${{ secrets.CODECOV_TOKEN }}\n        files: ./build/coverage/timescaledb-coverage.info\n\n    - name: Save LCOV coverage report\n      if: matrix.coverage\n      uses: actions/upload-artifact@v4\n      with:\n        name: LCOV coverage report ${{ matrix.os }} ${{ matrix.name }} ${{ matrix.pg }}\n        path: ./build/coverage/lcov-report\n\n    - name: Show regression diffs\n      if: always()\n      id: collectlogs\n      run: |\n        find . -name regression.diffs -exec cat {} + > regression.log\n\n        if [[ \"${{ runner.os }}\" == \"Linux\" ]] ; then\n          # wait in case there are in-progress coredumps\n          sleep 10\n          if coredumpctl -q list >/dev/null; then echo \"coredumps=true\" >>$GITHUB_OUTPUT; fi\n          # print OOM killer information\n          sudo journalctl --system -q --facility=kern --grep \"Killed process\" || true\n        elif [[ \"${{ runner.os }}\" == \"macOS\" ]] ; then\n           if [ $(find /cores -type f | wc -l) -gt 0 ]; then echo \"coredumps=true\" >>$GITHUB_OUTPUT; fi\n        fi\n        if [[ -s regression.log ]]; then echo \"regression_diff=true\" >> $GITHUB_OUTPUT; fi\n        grep -e 'FAILED' -e 'failed (ignored)' -e 'not ok' installcheck.log || true\n        cat regression.log\n\n    - name: Save regression diffs\n      if: always() && steps.collectlogs.outputs.regression_diff == 'true'\n      uses: actions/upload-artifact@v4\n      with:\n        name: Regression diff ${{ matrix.os }} ${{ matrix.name }} ${{ matrix.pg }}\n        path: |\n          regression.log\n          installcheck.log\n\n    - name: Save PostgreSQL log\n      if: always()\n      uses: actions/upload-artifact@v4\n      with:\n        name: PostgreSQL log ${{ matrix.os }} ${{ matrix.name }} ${{ matrix.pg }}\n        path: postmaster.*\n\n    - name: Stack trace Linux\n      if: always() && steps.collectlogs.outputs.coredumps == 'true' && runner.os == 'Linux'\n      run: |\n        sudo coredumpctl debug --debugger=gdb --debugger-arguments='' <<<\"\n          set verbose on\n          set trace-commands on\n          show debug-file-directory\n          printf \"'\"'\"query = '%s'\\n\\n\"'\"'\", debug_query_string\n          bt full\n\n          # We try to find ExceptionalCondition frame to print the failed condition\n          # for searching in logs.\n          frame function ExceptionalCondition\n          printf \"'\"'\"condition = '%s'\\n\"'\"'\", conditionName\n\n          # Hopefully now we should be around the failed assertion, print where\n          # we are.\n          up 1\n          list\n          info args\n          info locals\n        \" 2>&1 | tee -a stacktrace.log\n        ./scripts/bundle_coredumps.sh\n\n    - name: Stack trace macOS\n      if: always() && steps.collectlogs.outputs.coredumps == 'true' && runner.os == 'macOS'\n      run: |\n        ~/$PG_SRC_DIR/src/tools/ci/cores_backtrace.sh macos /cores\n\n    - name: Coredumps\n      if: always() && steps.collectlogs.outputs.coredumps == 'true'\n      uses: actions/upload-artifact@v4\n      with:\n        name: Coredumps ${{ matrix.os }} ${{ matrix.name }} ${{ matrix.pg }}\n        path: coredumps\n\n    - name: Save stacktraces\n      if: always() && steps.collectlogs.outputs.coredumps == 'true'\n      uses: actions/upload-artifact@v4\n      with:\n        name: Stacktraces ${{ matrix.os }} ${{ matrix.name }} ${{ matrix.pg }}\n        path: stacktrace.log\n\n    - name: Save TAP test logs\n      if: always()\n      uses: actions/upload-artifact@v4\n      with:\n        name: TAP test logs ${{ matrix.os }} ${{ matrix.name }} ${{ matrix.pg }}\n        path: |\n          build/test/tmp_check/log\n          build/tsl/test/tmp_check/log\n\n    - name: Check that no new internal program errors are introduced\n      # We collect the same messages when uploading to the CI checks database,\n      # but it runs on different conditions (not on forks and not on flaky check),\n      # and requires different data (job date), so we do it separately here.\n      # The jq --exit-code option is broken with select() on jq-1.6 which we see\n      # on some machines, so we use grep instead (see https://github.com/jqlang/jq/issues/1139).\n      if: always() && contains(matrix.name, 'Flaky')\n      run: |\n        ! jq 'select(\n                (.state_code == \"XX000\" and .error_severity != \"LOG\" and (.message | test(\"TestFailure\") | not))\n                or (.message | test(\"resource was not closed\"))\n            ) | [.message, .func_name,  .statement] | @tsv\n        ' -r postmaster.json | grep .\n\n    - name: Upload test results to the database\n      # Don't upload the results of the flaky check, because the db schema only\n      # supports running one test once per job.\n      if: always() && matrix.installcheck && (! contains(matrix.name, 'Flaky'))\n      env:\n        # GitHub Actions allow you neither to use the env context for the job name,\n        # nor to access the job name from the step context, so we have to\n        # duplicate it to work around this nonsense.\n        JOB_NAME: PG${{ matrix.pg }}${{ matrix.snapshot }} ${{ matrix.name }} ${{ matrix.os }}\n        CI_STATS_DB: ${{ secrets.CI_STATS_DB }}\n        GITHUB_EVENT_NAME: ${{ github.event_name }}\n        GITHUB_REF_NAME: ${{ github.ref_name }}\n        GITHUB_REPOSITORY: ${{ github.repository }}\n        GITHUB_RUN_ATTEMPT: ${{ github.run_attempt }}\n        GITHUB_RUN_ID: ${{ github.run_id }}\n        GITHUB_RUN_NUMBER: ${{ github.run_number }}\n        JOB_STATUS: ${{ job.status }}\n      run: |\n        if [[ \"${{ github.event_name }}\" == \"pull_request\" ]] ;\n        then\n            GITHUB_PR_NUMBER=\"${{ github.event.number }}\"\n        else\n            GITHUB_PR_NUMBER=0\n        fi\n        export GITHUB_PR_NUMBER\n        scripts/upload_ci_stats.sh\n\n  report_status:\n    name: Regression summary\n    needs: [check_paths, regress]\n    if: always()\n    runs-on: ubuntu-latest\n    steps:\n      - run: |\n          # If the detector failed, or the matrix failed, exit with error\n          if [[ \"${{ needs.regress.result }}\" == \"failure\" ]]; then\n            exit 1\n          fi\n          echo \"All checks passed or were safely skipped.\"\n\n"
  },
  {
    "path": ".github/workflows/loader_check.yml",
    "content": "name: Check for loader changes\n\"on\":\n  pull_request:\n    types: [opened, synchronize, reopened, labeled, unlabeled, edited]\n\njobs:\n  check_loader_change:\n    name: Check for loader changes\n    # Ignore loader changes if acknowledged already\n    if: ${{ !contains(github.event.pull_request.labels.*.name, 'upgrade-requires-restart') }}\n    runs-on: timescaledb-runner-arm64\n    steps:\n      - name: Checkout source\n        uses: actions/checkout@v4\n\n      - name: Check if the pull request changes the loader\n        shell: bash --norc --noprofile {0}\n        env:\n          BODY: ${{ github.event.pull_request.body }}\n          GH_TOKEN: ${{ github.token }}\n          PR_NUMBER: ${{ github.event.pull_request.number }}\n        run: |\n          echo \"$BODY\" | egrep -qsi '^disable-check:.*\\<loader-change\\>'\n          if [[ $? -ne 0 ]]; then\n            # Get the list of modified files in this pull request\n            files=$(gh pr view $PR_NUMBER --json files --jq '.files.[].path')\n\n            # Check for loader changes\n            if echo \"${files}\" | grep -Eq \"^src/loader/.+$\"; then\n              echo \"Warning: This PR changes the loader. Therefore, upgrading to the next TimescaleDB\"\n              echo \"version requires a restart of PostgreSQL. Make sure to bump the loader version if\"\n              echo \"necessary and coordinate the release with the cloud team before merging.\"\n              echo\n              echo \"After the release is coordinated, add the 'upgrade-requires-restart' label\"\n              echo \"to the PR to acknowledge this warning.\"\n              echo\n              echo \"To disable this check, add this trailer to pull request message:\"\n              echo\n              echo \"Disable-check: loader-change\"\n              echo\n              exit 1\n            fi\n          fi\n\n  check_loader_version_bump:\n    name: Check if loader versions are incremented for required restart\n    # If the label is present, validate the loader version is bumped\n    if: ${{ contains(github.event.pull_request.labels.*.name, 'upgrade-requires-restart') }}\n    runs-on: timescaledb-runner-arm64\n    steps:\n      - name: Checkout source\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Checkout ${{ github.event.pull_request.base.ref }} branch\n        run: git checkout ${{ github.event.pull_request.base.ref }}\n\n      - name: Extract versions from ${{ github.event.pull_request.base.ref }} branch\n        id: upstream-versions\n        run: |\n          # Extract version from bgw_interface.c\n          BGW_VERSION_MAIN=$(grep -oP 'const int32 ts_bgw_loader_api_version = \\K\\d+' src/loader/bgw_interface.c || echo \"0\")\n          echo \"bgw_version_main=$BGW_VERSION_MAIN\" >> $GITHUB_OUTPUT\n          \n          # Extract version from launcher_interface.c\n          LAUNCHER_VERSION_MAIN=$(grep -oP '#define MIN_LOADER_API_VERSION \\K\\d+' src/bgw/launcher_interface.c || echo \"0\")\n          echo \"launcher_version_main=$LAUNCHER_VERSION_MAIN\" >> $GITHUB_OUTPUT\n\n          echo \"${{ github.event.pull_request.base.ref }} branch:\"\n          echo \"src/loader/bgw_interface.c:const int32 ts_bgw_loader_api_version = $BGW_VERSION_MAIN\"\n          echo \"src/bgw/launcher_interface.c:#define MIN_LOADER_API_VERSION $LAUNCHER_VERSION_MAIN\"\n\n      - name: Checkout PR branch\n        run: git checkout ${{ github.event.pull_request.head.sha }}\n\n      - name: Extract versions from PR branch\n        id: pr-versions\n        run: |\n          # Extract version from bgw_interface.c\n          BGW_VERSION_PR=$(grep -oP 'const int32 ts_bgw_loader_api_version = \\K\\d+' src/loader/bgw_interface.c || echo \"0\")\n          echo \"bgw_version_pr=$BGW_VERSION_PR\" >> $GITHUB_OUTPUT\n          \n          # Extract version from launcher_interface.c\n          LAUNCHER_VERSION_PR=$(grep -oP '#define MIN_LOADER_API_VERSION \\K\\d+' src/bgw/launcher_interface.c || echo \"0\")\n          echo \"launcher_version_pr=$LAUNCHER_VERSION_PR\" >> $GITHUB_OUTPUT\n          \n          echo \"PR:\"\n          echo \"src/loader/bgw_interface.c:const int32 ts_bgw_loader_api_version = $BGW_VERSION_PR\"\n          echo \"src/bgw/launcher_interface.c:#define MIN_LOADER_API_VERSION $LAUNCHER_VERSION_PR\"\n\n      - name: Validate version increments\n        run: |\n          BGW_MAIN=${{ steps.upstream-versions.outputs.bgw_version_main }}\n          BGW_PR=${{ steps.pr-versions.outputs.bgw_version_pr }}\n          LAUNCHER_MAIN=${{ steps.upstream-versions.outputs.launcher_version_main }}\n          LAUNCHER_PR=${{ steps.pr-versions.outputs.launcher_version_pr }}\n          \n          echo \"Validating version increments...\"\n          echo \"bgw_interface.c: $BGW_MAIN -> $BGW_PR (expected: $((BGW_MAIN + 1)))\"\n          echo \"launcher_interface.c: $LAUNCHER_MAIN -> $LAUNCHER_PR (expected: $((LAUNCHER_MAIN + 1)))\"\n          \n          VALIDATION_FAILED=false\n          \n          # Check bgw_interface version\n          if [ \"$BGW_PR\" -ne \"$((BGW_MAIN + 1))\" ]; then\n            echo \"❌ ERROR: bgw_interface.c version should be incremented by 1, expected: $((BGW_MAIN + 1)), Found: $BGW_PR\"\n            VALIDATION_FAILED=true\n          else\n            echo \"✅ bgw_interface.c version correctly incremented\"\n          fi\n          \n          # Check launcher_interface.c version\n          if [ \"$LAUNCHER_PR\" -ne \"$((LAUNCHER_MAIN + 1))\" ]; then\n            echo \"❌ ERROR: launcher_interface.c version should be incremented by 1, expected: $((LAUNCHER_MAIN + 1)), Found: $LAUNCHER_PR\"\n            VALIDATION_FAILED=true\n          else\n            echo \"✅ launcher_interface.c version correctly incremented\"\n          fi\n          \n          if [ \"$VALIDATION_FAILED\" = true ]; then\n            echo \"Version validation failed. Please ensure: Both version numbers are incremented by exactly 1 from ${{ github.event.pull_request.base.ref }} branch\"\n            exit 1\n          fi\n\n      - name: Add comment to PR (if validation fails)\n        if: failure()\n        uses: actions/github-script@v7\n        with:\n          script: |\n            const bgwMain = '${{ steps.upstream-versions.outputs.bgw_version_main }}';\n            const bgwPr = '${{ steps.pr-versions.outputs.bgw_version_pr }}';\n            const launcherMain = '${{ steps.upstream-versions.outputs.launcher_version_main }}';\n            const launcherPr = '${{ steps.pr-versions.outputs.launcher_version_pr }}';\n            \n            const comment = `## ❌ Loader Version Validation Failed\n            \n            The version numbers in your PR do not meet the requirements, to indicate a acknowledged loader change.\n            \n            | File | ${{ github.event.pull_request.base.ref }} | PR | Expected |\n            |------|-------------|-----------|----------|\n            | \\`src/loader/bgw_interface.c:const int32 ts_bgw_loader_api_version\\` | ${bgwMain} | ${bgwPr} | ${parseInt(bgwMain) + 1} |\n            | \\`src/bgw/launcher_interface.c:#define MIN_LOADER_API_VERSION\\` | ${launcherMain} | ${launcherPr} | ${parseInt(launcherMain) + 1} |\n            \n            **Requirements:** Both version numbers must be incremented by 1 from the ${{ github.event.pull_request.base.ref }} branch. Please update the version numbers and push your changes.`;\n            \n            github.rest.issues.createComment({\n              issue_number: context.issue.number,\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              body: comment\n            });\n"
  },
  {
    "path": ".github/workflows/memory-tests.yaml",
    "content": "name: Memory tests\n\"on\":\n  push:\n    branches:\n      - main\n      - ?.*.x\n      - memory_test\n      - trigger/memory_test\n  pull_request:\n    paths: .github/workflows/memory-tests.yaml\njobs:\n  memory_leak:\n    name: Memory leak on insert PG${{ matrix.pg }}\n    runs-on: ubuntu-22.04\n    strategy:\n      matrix:\n        pg: [15, 16, 17, 18]\n      fail-fast: false\n\n    steps:\n    - name: Install Dependencies\n      run: |\n        sudo apt-get update\n        sudo apt-get install gnupg systemd-coredump gdb postgresql-common python3-psutil\n        yes | sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh\n        sudo apt-get update\n        sudo apt-get install postgresql-${{ matrix.pg }} postgresql-server-dev-${{ matrix.pg }}\n\n    - name: Checkout TimescaleDB\n      uses: actions/checkout@v4\n\n    - name: Build TimescaleDB\n      run: |\n        PATH=\"/usr/lib/postgresql/${{ matrix.pg }}/bin:$PATH\"\n        ./bootstrap -DCMAKE_BUILD_TYPE=Release\n        make -C build\n        sudo make -C build install\n\n    - name: Setup database\n      run: |\n        sudo pg_createcluster ${{matrix.pg}} main\n        sudo tee -a /etc/postgresql/${{ matrix.pg }}/main/postgresql.conf <<-CONF\n          shared_preload_libraries = 'timescaledb'\n          max_worker_processes = 0\n          log_destination = syslog\n          max_wal_size = 8GB\n          max_wal_senders = 0\n          wal_level = minimal\n          checkpoint_timeout = 20min\n          log_checkpoints = on\n          bgwriter_lru_maxpages = 0\n          track_counts = off\n          fsync = off\n          port = 5432\n        CONF\n        sudo grep port /etc/postgresql/${{ matrix.pg }}/main/postgresql.conf\n        sudo systemctl start postgresql@${{ matrix.pg }}-main.service\n        sudo -u postgres psql -X -c \"CREATE USER runner SUPERUSER LOGIN;\"\n\n    - name: Run insert memory test\n      run: |\n        sudo -u postgres python ./scripts/test_memory_spikes.py & sleep 5 \\\n          && psql -d postgres -v ECHO=all -X -f scripts/out_of_order_random_direct.sql\n\n    - name: Run generic memory test\n      run: |\n        sudo -u postgres python ./scripts/test_memory_spikes.py & sleep 5 \\\n          && psql -d postgres -v ECHO=all -X -f scripts/memory_leaks.sql\n\n    - name: Postgres log\n      if: always()\n      run: |\n        sudo journalctl -u postgresql@${{ matrix.pg }}-main.service\n\n"
  },
  {
    "path": ".github/workflows/nightly_cloud_smoke_test.yaml",
    "content": "name: Nightly - Update smoke test on release branch\n\n# QA: run the upgrade smoke tests with the last 10 releases \n# against the the next version.\n# Requires upstream GitHub action to deploy the last commit\n# from the release branch to the QA service in Cloud\n\n\"on\":\n  #schedule:\n  #  - cron: '0 0 * * *'\n  workflow_dispatch:\n  pull_request:\n    paths:\n      - .github/workflows/nightly_cloud_smoke_test.yaml\n      - scripts/test_update_smoke.sh\n      - test/sql/updates/*.sql\n\njobs:\n  get-next-version:\n    runs-on: ubuntu-latest\n    outputs:\n      next_version: ${{ steps.fetch.outputs.next_version }}\n    steps:\n    - name: Get next version\n      # Get newest release branch with most recent commits\n      id: fetch\n      run: |\n        RELEASE_BRANCH=$(git ls-remote --heads https://github.com/timescale/timescaledb.git | \\\n          grep -Eo 'refs/heads/[2-9]+\\.[0-9]+\\.x' | \\\n          sed 's|refs/heads/||' | \\\n          sort -V | \\\n          tail -n1)\n        echo \"current release branch: ${RELEASE_BRANCH}\"\n        curl --fail -o version.config \"https://raw.githubusercontent.com/timescale/timescaledb/refs/heads/${RELEASE_BRANCH}/version.config\"\n        NEXT_VERSION=$(head -1 version.config | cut -d ' ' -f 3 | cut -d '-' -f 1)\n        echo \"next version: ${NEXT_VERSION} ..\"\n        echo \"next_version=${NEXT_VERSION}\" >> \"$GITHUB_OUTPUT\"\n\n  generate-matrix:\n    runs-on: ubuntu-latest\n    outputs:\n      matrix: ${{ steps.set-matrix.outputs.matrix }}\n    steps:\n      - name: Fetch TimescaleDB releases\n        id: fetch-releases\n        run: |\n          releases=$(curl -s \\\n            -H \"Accept: application/vnd.github.v3+json\" \\\n            \"https://api.github.com/repos/timescale/timescaledb/releases?per_page=10\" \\\n            | jq -r '.[].tag_name')\n          \n          # Convert to JSON array format for matrix\n          matrix_json=$(echo \"$releases\" | jq -R -s -c 'split(\"\\n\") | map(select(length > 0))')\n          echo \"releases=$matrix_json\" >> $GITHUB_OUTPUT\n          \n          # Also output for debugging\n          echo \"Found releases:\"\n          echo \"$releases\"\n\n      - name: Set matrix output\n        id: set-matrix\n        run: |\n          matrix='{\"version\": ${{ steps.fetch-releases.outputs.releases }}}'\n          echo \"matrix=$matrix\" >> $GITHUB_OUTPUT\n          echo $matrix\n\n  test-version:\n    needs: [generate-matrix, get-next-version]\n    runs-on: ubuntu-latest\n    strategy:\n      # run sequentially\n      matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}\n      max-parallel: 1\n      fail-fast: false\n    steps:\n      - name: Checkout TimescaleDB\n        uses: actions/checkout@v4\n\n      - name: Install Dependencies\n        # we want the right version of Postgres for handling any dump file\n        run: |\n          sudo apt-get update\n          sudo apt-get install gnupg postgresql-common \n          yes | sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh\n          sudo apt-get update\n          sudo apt-get install postgresql-17 \n    \n      - name: \"Run update smoke test with v${{ matrix.version }} to ${{needs.get-next-version.outputs.next_version}}\"\n        # Now run the test.  Currently the cloud instance is always up.\n        # only run the test if the versions are not equal\n        run: |\n          PATH=\"/usr/lib/postgresql/17/bin:$PATH\"\n          ./scripts/test_update_smoke.sh \\\n              ${{ matrix.version }} \\\n              ${{needs.get-next-version.outputs.next_version}} \\\n              \"${{ secrets.DB_TEAM_QA_SERVICE_CONNECTION_STRING }}\"\n      \n      - name: Show logs\n        if: always()\n        run: |\n          ls -l /tmp/smoketest*/*\n          cat /tmp/smoketest*/*\n        \n      - name: Upload Artifacts\n        # Save the logs, so if there is a failure we'll have a better\n        # chance to understand what went wrong.\n        if: failure()\n        uses: actions/upload-artifact@v4\n        with:\n          name: Cloud Update test smoke\n          path: /tmp/smoketest*/*\n"
  },
  {
    "path": ".github/workflows/pg_ladybug.yaml",
    "content": "name: pg_ladybug\n\"on\":\n  pull_request:\n  push:\n    branches:\n      - main\n      - ?.*.x\njobs:\n  pg_ladybug:\n    runs-on: ubuntu-latest\n    env:\n      CC: clang-19\n      CXX: clang++-19\n      LLVM_CONFIG: llvm-config-19\n\n    steps:\n\n    - name: Install dependencies\n      run: |\n        sudo apt-get update\n        sudo apt-get purge llvm-16 llvm-17 llvm-18 clang-16 clang-17 clang-18\n        sudo apt-get install llvm-19 llvm-19-dev clang-19 libclang-19-dev clang-tidy-19 libcurl4-openssl-dev\n        sudo ln -sf /usr/bin/clang-tidy-19 /usr/bin/clang-tidy\n\n    - name: Checkout timescaledb\n      uses: actions/checkout@v4\n\n    - name: Checkout pg_ladybug\n      uses: actions/checkout@v4\n      with:\n        repository: 'timescale/pg_ladybug'\n        path: 'pg_ladybug'\n        ref: '0.1.0'\n\n    - name: build pg_ladybug\n      run:  |\n        cd pg_ladybug\n        cmake -S . -B build -DLLVM_ROOT=/usr/lib/llvm-19\n        make -C build -j $(getconf _NPROCESSORS_ONLN)\n        sudo make -C build install\n\n    - name: Verify pg_ladybug\n      run: |\n        clang-tidy --load /usr/local/lib/libPostgresCheck.so --checks='-*,postgres-*' --list-checks | grep postgres\n\n    - name: Configure timescaledb\n      run: |\n        # installing postgres headers pulls in llvm-17 which confuses pg_ladybug build process so we install this here instead of at beginning\n        sudo apt-get install postgresql-server-dev-16\n        ./bootstrap -DCMAKE_BUILD_TYPE=Debug -DLINTER=ON -DCLANG_TIDY_EXTRA_OPTS=\",-*,postgres-*;--load=/usr/local/lib/libPostgresCheck.so\"\n\n    - name: Build timescaledb\n      run: |\n        make -C build -j $(getconf _NPROCESSORS_ONLN)\n\n"
  },
  {
    "path": ".github/workflows/pg_upgrade-test.yaml",
    "content": "name: pg_upgrade test\n\"on\":\n  push:\n    branches:\n      - main\n      - ?.*.x\n      - prerelease_test\n  pull_request:\n\njobs:\n  check_paths:\n    runs-on: ubuntu-latest\n    outputs:\n      run_ci: >-\n        ${{ github.event_name == 'push'\n          || (steps.filter.outputs.sql == 'true' && !contains(github.event.pull_request.labels.*.name, 'skip-ci'))\n         }}\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dorny/paths-filter@v3\n        id: filter\n        with:\n          filters: .github/filters.yaml\n\n  pg_upgrade_test:\n    needs: check_paths\n    if: needs.check_paths.outputs.run_ci == 'true'\n    name: pg_upgrade test from PG${{ matrix.pg_version_old }} to PG${{ matrix.pg_version_new }}\n    runs-on: 'ubuntu-latest'\n    strategy:\n      matrix:\n        include:\n          - pg_version_old: 15 # 15 to 16\n            pg_version_new: 16\n          - pg_version_old: 15 # 15 to 17\n            pg_version_new: 17\n          - pg_version_old: 15 # 15 to 18\n            pg_version_new: 18\n          - pg_version_old: 16 # 16 to 17\n            pg_version_new: 17\n          - pg_version_old: 16 # 16 to 18\n            pg_version_new: 18\n          - pg_version_old: 17 # 17 to 18\n            pg_version_new: 18\n      fail-fast: false\n    env:\n      OUTPUT_DIR: ${{ github.workspace }}/pg_upgrade_test\n\n    steps:\n      - name: Install Linux Dependencies\n        run: |\n          sudo apt-get update\n          sudo apt-get install pip postgresql-common\n\n      - name: Install testgres\n        run: |\n          pip install testgres\n\n      - name: Install PostgreSQL ${{ matrix.pg_version_old}} and ${{ matrix.pg_version_new }}\n        run: |\n          yes | sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh\n          echo \"deb https://packagecloud.io/timescale/timescaledb/ubuntu/ $(lsb_release -c -s) main\" | sudo tee /etc/apt/sources.list.d/timescaledb.list\n          wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | sudo apt-key add -\n          sudo apt-get update\n          sudo apt-get install -y \\\n            postgresql-${{ matrix.pg_version_old }} postgresql-server-dev-${{ matrix.pg_version_old }} \\\n            postgresql-${{ matrix.pg_version_new }} postgresql-server-dev-${{ matrix.pg_version_new }}\n          sudo apt-get install -y --no-install-recommends \\\n            timescaledb-2-postgresql-${{ matrix.pg_version_old }} \\\n            timescaledb-2-postgresql-${{ matrix.pg_version_new }}\n\n      - name: Checkout TimescaleDB\n        uses: actions/checkout@v4\n\n      - name: Build and install TimescaleDB on PostgreSQL ${{ matrix.pg_version_old}}\n        env:\n          BUILD_DIR: pg${{ matrix.pg_version_old }}\n        run: |\n          PATH=\"/usr/lib/postgresql/${{ matrix.pg_version_old }}/bin:$PATH\"\n          ./bootstrap -DCMAKE_BUILD_TYPE=Release -DWARNINGS_AS_ERRORS=OFF -DASSERTIONS=ON -DLINTER=OFF -DGENERATE_DOWNGRADE_SCRIPT=OFF -DREGRESS_CHECKS=OFF -DTAP_CHECKS=OFF\n          make -j -C pg${{ matrix.pg_version_old }}\n          sudo make -j -C pg${{ matrix.pg_version_old }} install\n\n      - name: Build and install TimescaleDB on PostgreSQL ${{ matrix.pg_version_new}}\n        env:\n          BUILD_DIR: pg${{ matrix.pg_version_new }}\n        run: |\n          PATH=\"/usr/lib/postgresql/${{ matrix.pg_version_new }}/bin:$PATH\"\n          ./bootstrap -DCMAKE_BUILD_TYPE=Release -DWARNINGS_AS_ERRORS=OFF -DASSERTIONS=ON -DLINTER=OFF -DGENERATE_DOWNGRADE_SCRIPT=OFF -DREGRESS_CHECKS=OFF -DTAP_CHECKS=OFF\n          make -j -C pg${{ matrix.pg_version_new }}\n          sudo make -j -C pg${{ matrix.pg_version_new }} install\n\n      - name: Run pg_upgrade test\n        env:\n          PGVERSIONOLD: ${{ matrix.pg_version_old }}\n          PGVERSIONNEW: ${{ matrix.pg_version_new }}\n          DIFFFILE: ${{ env.OUTPUT_DIR }}/upgrade_check.diff\n        run: |\n          scripts/test_pg_upgrade.py\n          diff -u \\\n            \"${OUTPUT_DIR}/post.pg${PGVERSIONOLD}.log\" \\\n            \"${OUTPUT_DIR}/post.pg${PGVERSIONNEW}.log\" | \\\n            tee \"${DIFFFILE}\"\n          if [[ -s \"${DIFFFILE}\" ]]; then\n            echo \"pg_upgrade test for ${PGVERSIONOLD} -> ${PGVERSIONNEW} failed\"\n            exit 1\n          fi\n\n      - name: Show pg_upgrade diffs\n        if: always()\n        env:\n          DIFFFILE: ${{ env.OUTPUT_DIR }}/upgrade_check.diff\n          DIROLD: pg${{ matrix.pg_version_old }}\n          DIRNEW: pg${{ matrix.pg_version_new }}\n        run: |\n          cd ${OUTPUT_DIR}\n          cat ${DIFFFILE}\n          tar czf /tmp/pg_upgrade_artifacts.tgz \\\n            ${DIFFFILE} \\\n            ${OUTPUT_DIR}/*.log \\\n            ${OUTPUT_DIR}/${DIROLD}/logs/* \\\n            ${OUTPUT_DIR}/${DIRNEW}/logs/* \\\n            ${OUTPUT_DIR}/${DIRNEW}/data/pg_upgrade_output.d/*\n\n      - name: Upload pg_upgrade logs\n        if: always()\n        uses: actions/upload-artifact@v4\n        with:\n          name: pg_upgrade logs from ${{ matrix.pg_version_old }} to ${{ matrix.pg_version_new }}\n          path: /tmp/pg_upgrade_artifacts.tgz\n\n  report_status:\n    name: pg_upgrade summary\n    needs: [check_paths, pg_upgrade_test]\n    if: always()\n    runs-on: ubuntu-latest\n    steps:\n      - run: |\n          # If the detector failed, or the matrix failed, exit with error\n          if [[ \"${{ needs.pg_upgrade_test.result }}\" == \"failure\" ]]; then\n            exit 1\n          fi\n          echo \"All checks passed or were safely skipped.\"\n\n"
  },
  {
    "path": ".github/workflows/pgspot.yaml",
    "content": "# Test our extension sql scripts are following security best practices\nname: pgspot\n\"on\":\n  pull_request:\n  push:\n    branches:\n      - main\n      - ?.*.x\n\njobs:\n  check_paths:\n    runs-on: ubuntu-latest\n    outputs:\n      run_ci: >-\n        ${{ github.event_name == 'push'\n          || (steps.filter.outputs.sql == 'true' && !contains(github.event.pull_request.labels.*.name, 'skip-ci'))\n         }}\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dorny/paths-filter@v3\n        id: filter\n        with:\n          filters: .github/filters.yaml\n\n  pgspot:\n    needs: check_paths\n    if: needs.check_paths.outputs.run_ci == 'true'\n    runs-on: ubuntu-latest\n    env:\n      # policy_compression and policy_compression_execute do not have explicit search_path\n      # because postgres does not allow it for procedures doing transaction control\n      PGSPOT_OPTS: --ignore PS002\n        --proc-without-search-path '_timescaledb_internal.policy_compression(job_id integer,config jsonb)'\n        --proc-without-search-path '_timescaledb_functions.policy_compression(job_id integer,config jsonb)'\n        --proc-without-search-path '_timescaledb_functions.policy_compression_execute(job_id integer,htid integer,lag anyelement,maxchunks integer,verbose_log boolean,recompress_enabled boolean,reindex_enabled boolean,use_creation_time boolean)'\n\n    steps:\n\n    - name: Setup python 3.13\n      uses: actions/setup-python@v5\n      with:\n        python-version: '3.13'\n\n    - name: Checkout timescaledb\n      uses: actions/checkout@v4\n\n    - name: Install pgspot\n      run: |\n        python -m pip install pgspot==0.9.2\n\n    - name: Build timescaledb sqlfiles\n      run: |\n        previous_version=$(grep '^previous_version = ' version.config | sed -e 's!^[^=]\\+ = !!')\n        git fetch --tags\n        ./bootstrap -DGENERATE_DOWNGRADE_SCRIPT=ON\n        git checkout ${previous_version}\n        make -C build sqlfile sqlupdatescripts\n        git checkout ${GITHUB_SHA}\n        make -C build sqlfile sqlupdatescripts\n        ls -la build/sql/timescaledb--*.sql\n\n    - name: Run pgspot\n      run: |\n        version=$(grep '^version = ' version.config | sed -e 's!^[^=]\\+ = !!')\n        previous_version=$(grep '^previous_version = ' version.config | sed -e 's!^[^=]\\+ = !!')\n\n        # Show files used\n        ls -la build/sql/timescaledb--${version}.sql build/sql/timescaledb--${previous_version}--${version}.sql \\\n          build/sql/timescaledb--${version}--${previous_version}.sql\n\n        # The next pgspot execution tests the installation script by itself\n        pgspot ${{ env.PGSPOT_OPTS }} build/sql/timescaledb--${version}.sql\n        # The next pgspot execution tests the update script to the latest version\n        # we prepend the installation script here so pgspot can correctly keep track of created objects\n        pgspot ${{ env.PGSPOT_OPTS }} -a build/sql/timescaledb--${previous_version}.sql \\\n          build/sql/timescaledb--${previous_version}--${version}.sql\n        # The next pgspot execution tests the downgrade script to the previous version\n        # we prepend the installation script here so pgspot can correctly keep track of created objects\n        pgspot ${{ env.PGSPOT_OPTS }} -a build/sql/timescaledb--${version}.sql \\\n          build/sql/timescaledb--${version}--${previous_version}.sql\n"
  },
  {
    "path": ".github/workflows/pr-approvals.yaml",
    "content": "# All PRs except trivial ones should require 2 approvals, since this a global setting\n# we cannot make this decision from github configuration alone. So we set the required\n# approved in github to 1 and make this check required which will enforce 2 approvals\n# unless overwritten or only CI files are touched.\nname: PR Approval Check\n\"on\":\n  pull_request:\n    types: [opened, synchronize, reopened, edited, auto_merge_enabled, auto_merge_disabled]\n    branches: [main]\n  pull_request_review:\n\njobs:\n  check_approvals:\n    name: Check for sufficient approvals\n    runs-on: timescaledb-runner-arm64\n    steps:\n      - name: Checkout source\n        uses: actions/checkout@v4\n\n      - name: Check for sufficient approvals\n        shell: bash --norc --noprofile {0}\n        env:\n          GH_TOKEN: ${{ github.token }}\n          PR_NUMBER: ${{ github.event.pull_request.number }}\n        run: |\n          set -eu\n          echo \"PR number is $PR_NUMBER\"\n          BODY=$(gh pr view $PR_NUMBER -q .body --json body)\n          if ! echo \"$BODY\" | egrep -qsi '^disable-check:.*\\<approval-count\\>'\n          then\n            # Get the list of modified files in this pull request\n            echo \"Modified files: \"\n            gh pr view $PR_NUMBER --json files\n            # Get the number of modified files, but exclude those that\n            # are workflow files. These require only a single\n            # reviewer.\n            files=$(gh pr view $PR_NUMBER --json files --jq '[.files.[].path | select(startswith(\".github\") | not)] | length')\n\n            # Get the number of approvals in this pull request\n            echo \"Reviews: \"\n            gh pr view $PR_NUMBER --json reviews\n            approvals=$(\n                gh pr view $PR_NUMBER --json reviews --jq '\n                    [\n                        .reviews.[]\n                        | select(.authorAssociation == \"MEMBER\" and .state == \"APPROVED\")\n                    ] | length\n                  '\n            )\n            echo \"approvals: $approvals, files: $files\"\n            if [[ $approvals -lt 2 && $files -gt 0 ]] ; then\n              echo \"This pull request requires 2 approvals before merging.\"\n              echo\n              echo \"For trivial changes, you may disable this check by adding this trailer to the pull request message:\"\n              echo\n              echo \"Disable-check: approval-count\"\n              echo\n              exit 1\n            fi\n          fi\n\n"
  },
  {
    "path": ".github/workflows/pr-handling.yaml",
    "content": "name: Assign PR to author and reviewers\n\n# This workflow runs on the pull_request_target event:\n# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target.\n#\n# So the secrets are accessible even if the PR was opened from an external\n# repository. This is done because the GitHub API key needs to be accessed\n# to modify the PRs.\n#\n# NOTE: Only API calls should be made in the actions defined here. The\n#       committed code should _NOT_ be touched in any case.\n\n\"on\":\n  pull_request_target:\n    types: [ opened, reopened, ready_for_review ]\n\njobs:\n\n  assign-pr:\n    name: Assign PR to author\n    runs-on: ubuntu-latest\n    steps:\n      - uses: toshimaru/auto-author-assign@v2.1.0\n\n  ask-review:\n    name: Run pull-review\n    runs-on: ubuntu-latest\n    if: ${{ !github.event.pull_request.draft && github.event.pull_request.base.ref == 'main' }}\n    steps:\n      - uses: actions/checkout@v4\n      - name: Run pull-review with docker\n        env:\n            GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n            OWNER: ${{ github.repository_owner }}\n            REPO: ${{ github.event.repository.name }}\n            PULL_REQUEST_NUMBER : ${{ github.event.pull_request.number }}\n        shell: bash\n        run: |\n          PR_URL=\"https://github.com/${OWNER}/${REPO}/pull/${PULL_REQUEST_NUMBER}\"\n          docker run ghcr.io/imsky/pull-review \"$PR_URL\" --github-token \"${GITHUB_TOKEN}\"\n"
  },
  {
    "path": ".github/workflows/pr-validation.yaml",
    "content": "name: Pull Request Validation\n\"on\":\n  pull_request:\n    types: [opened, synchronize, reopened, edited, auto_merge_enabled, auto_merge_disabled]\n    branches: [main]\njobs:\n  # Count the number of commits in a pull request. This can be\n  # disabled by adding a trailer line of the following form to the\n  # pull request message:\n  #\n  #    Disable-Check: commit-count\n  #\n  # The check is case-insensitive and ignores other contents on the\n  # line as well, so it is possible to add several different checks if\n  # that is necessary.\n  #\n  # It is assumed that the trailer is following RFC2822 conventions,\n  # but this is currently not enforced.\n  count_commits:\n    name: Enforce single commit pull request\n    runs-on: timescaledb-runner-arm64\n    steps:\n    - name: Checkout source\n      uses: actions/checkout@v4\n      with:\n        ref: ${{ github.event.pull_request.head.sha }}\n        fetch-depth: 0\n    - name: Dump GitHub context (for debugging)\n      env:\n        GITHUB_CONTEXT: ${{ toJSON(github) }}\n      run: |\n        echo \"GITHUB_CONTEXT: $GITHUB_CONTEXT\"\n    - name: Check number of commits\n      shell: bash --norc --noprofile {0}\n      env:\n        BODY: ${{ github.event.pull_request.body }}\n      run: |\n        echo \"$BODY\" | egrep -qsi '^disable-check:.*\\<commit-count\\>'\n        if [[ $? -ne 0 ]] && [[ \"${{ github.event.pull_request.auto_merge.merge_method }}\" != \"squash\" ]]\n        then\n          base=${{ github.event.pull_request.base.sha }}\n          head=${{ github.event.pull_request.head.sha }}\n          count=`git rev-list --no-merges --count $base..$head`\n          if [[ \"$count\" -ne 1 ]]; then\n            echo \"Found $count commits in pull request (there should be only one):\"\n            git log --format=format:'- %h %s' $base..$head\n            echo\n            echo \"To disable commit count, add this trailer to pull request message:\"\n            echo\n            echo \"Disable-check: commit-count\"\n            echo\n            echo \"Trailers follow RFC2822 conventions, so no whitespace\"\n            echo \"before field name and the check is case-insensitive for\"\n            echo \"both the field name and the field body.\"\n            exit 1\n          fi\n        fi\n"
  },
  {
    "path": ".github/workflows/prerelease-sanity.yaml",
    "content": "name: Prerelease Sanity\n\"on\":\n  push:\n    branches:\n      - prerelease_test\n  pull_request:\n    branches: \"?.*.x\"\n    paths:\n      - version.config\n      - .github/workflows/prerelease-sanity.yaml\n  workflow_dispatch:\n\njobs:\n  check_release_commit:\n    name: Check Release Commit\n    runs-on: timescaledb-runner-arm64\n\n    steps:\n    - name: Checkout TimescaleDB\n      uses: actions/checkout@v4\n      # GitHub creates an empty merge commit even for fast-fordward merges, which\n      # makes it needlessly difficult to inspect the actual commit title. Since\n      # we require the PRs to release branches to be up to date before merging,\n      # we can just work with the PR head here.\n      with:\n        ref: ${{ github.event.pull_request.head.sha }}\n        fetch-depth: 2\n\n    # The combined changelog must reference all changes, and the respective\n    # change files in the .unreleased folder must be deleted.\n    - name: No .unreleased files are left behind\n      run: |\n        ! compgen -G .unreleased/*\n\n    # The messages of the release commit and tag must start with Release <version>.\n    # If this is the release tag, it must point to the release commit\n    # and not something else.\n    - name: The release commit message references the respective version\n      run: |\n        required_title=$(sed -n \"s/^version = /Release /p\" version.config)\n        echo $required_title\n\n        tag_title=$(git log --oneline -1 --pretty=format:%s)\n        echo $tag_title\n        grep \"$required_title\" <<<\"$tag_title\"\n\n        # Our reference might be a tag, so check the pointed-to commit as well,\n        # using the ^0 git path specification to find it.\n        commit_title=$(git log --oneline -1 --pretty=format:%s @^0)\n        echo $commit_title\n        grep \"$required_title\" <<<\"$commit_title\"\n\n    # The release commit must modify the version.config\n    - name: The release commit modifies the version.config\n      run: |\n        git log --oneline -1 @^0\n        git log --oneline -1 @^0~\n        ! git diff --exit-code @^0~ @^0 -- version.config\n"
  },
  {
    "path": ".github/workflows/release_build_packages.yml",
    "content": "name: \"Packaging: Build distributions\"\n\n\"on\":\n  release:\n    types: [published]\n\njobs:\n  update:\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Initialize build summary\n      run: |\n        echo \"# 📦 Distribution Package Build Summary\" >> $GITHUB_STEP_SUMMARY\n        echo \"\" >> $GITHUB_STEP_SUMMARY\n        echo \"**Version:** \\`${{ github.event.release.tag_name }}\\`\" >> $GITHUB_STEP_SUMMARY\n        echo \"**URL:** [${{ github.event.release.name }}](${{ github.event.release.html_url }})\" >> $GITHUB_STEP_SUMMARY\n        echo \"**Triggered at:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')\" >> $GITHUB_STEP_SUMMARY\n        echo \"\" >> $GITHUB_STEP_SUMMARY\n        echo \"## 🚀 Build Status\" >> $GITHUB_STEP_SUMMARY\n        echo \"| Package | Status | Workflow |\" >> $GITHUB_STEP_SUMMARY\n        echo \"| --- | --- | --- |\" >> $GITHUB_STEP_SUMMARY\n        echo \"\" >> $GITHUB_STEP_SUMMARY\n\n        echo \"HAS_FAILURES=false\" >> $GITHUB_ENV\n\n    - name: Build Docker Image\n      env:\n        GH_TOKEN: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n      run: |\n        echo \"🐳 Triggering Docker image build...\" | tee -a build.log\n        if gh workflow run docker-image.yml -R timescale/timescaledb-docker -f version=${{ github.event.release.tag_name }} -f tag_latest=true -f registry=prod; then\n          echo \"| 🐳 Docker Image | ✅ Triggered | [timescaledb-docker](https://github.com/timescale/timescaledb-docker/actions/workflows/docker-image.yml) |\" >> $GITHUB_STEP_SUMMARY\n        else\n          echo \"| 🐳 Docker Image | ❌ Failed | [timescaledb-docker](https://github.com/timescale/timescaledb-docker/actions/workflows/docker-image.yml) |\" >> $GITHUB_STEP_SUMMARY\n          echo \"HAS_FAILURES=true\" >> $GITHUB_ENV\n        fi\n\n    - name: Build APT Packages\n      env:\n        GH_TOKEN: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n      run: |\n        echo \"🐧 Triggering APT package build...\" | tee -a build.log\n        if gh workflow run timescaledb-apt.yml -R timescale/release-build-scripts -f version=${{ github.event.release.tag_name }} -f upload-artifacts=true; then\n          echo \"| 🐧 APT Packages | ✅ Triggered | [summary](https://github.com/timescale/release-build-scripts/actions/workflows/timescaledb-apt.yml) |\" >> $GITHUB_STEP_SUMMARY\n        else\n          echo \"| 🐧 APT Packages | ❌ Failed | [summary](https://github.com/timescale/release-build-scripts/actions/workflows/timescaledb-apt.yml) |\" >> $GITHUB_STEP_SUMMARY\n          echo \"HAS_FAILURES=true\" >> $GITHUB_ENV\n        fi\n\n    - name: Build RPM Packages\n      env:\n        GH_TOKEN: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n      run: |\n        echo \"⚙ Triggering RPM package build...\" | tee -a build.log\n        if gh workflow run timescaledb-rpm.yml -R timescale/release-build-scripts -f version=${{ github.event.release.tag_name }} -f upload-artifacts=true; then\n          echo \"| ⚙ RPM Packages | ✅ Triggered | [summary](https://github.com/timescale/release-build-scripts/actions/workflows/timescaledb-rpm.yml) |\" >> $GITHUB_STEP_SUMMARY\n        else\n          echo \"| ⚙ RPM Packages | ❌ Failed | [summary](https://github.com/timescale/release-build-scripts/actions/workflows/timescaledb-rpm.yml) |\" >> $GITHUB_STEP_SUMMARY\n          echo \"HAS_FAILURES=true\" >> $GITHUB_ENV\n        fi\n\n    - name: Build Homebrew Packages\n      env:\n        GH_TOKEN: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n      run: |\n        echo \"🍺 Triggering Homebrew package build...\" | tee -a build.log\n        if gh workflow run timescaledb-homebrew.yml -R timescale/release-build-scripts -f version=${{ github.event.release.tag_name }} -f upload-artifacts=true; then\n          echo \"| 🍺 Homebrew Packages | ✅ Triggered | [summary](https://github.com/timescale/release-build-scripts/actions/workflows/timescaledb-homebrew.yml) |\" >> $GITHUB_STEP_SUMMARY\n        else\n          echo \"| 🍺 Homebrew Packages | ❌ Failed | [summary](https://github.com/timescale/release-build-scripts/actions/workflows/timescaledb-homebrew.yml) |\" >> $GITHUB_STEP_SUMMARY\n          echo \"HAS_FAILURES=true\" >> $GITHUB_ENV\n        fi\n\n    - name: Build Windows Packages\n      env:\n        GH_TOKEN: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n      run: |\n        echo \"🪟 Triggering Windows package build...\" | tee -a build.log\n        if gh workflow run timescaledb-windows.yml -R timescale/release-build-scripts -f version=${{ github.event.release.tag_name }} -f upload-artifacts=true; then\n          echo \"| 🪟 Windows Packages | ✅ Triggered | [summary](https://github.com/timescale/release-build-scripts/actions/workflows/timescaledb-windows.yml) |\" >> $GITHUB_STEP_SUMMARY\n        else\n          echo \"| 🪟 Windows Packages | ❌ Failed | [summary](https://github.com/timescale/release-build-scripts/actions/workflows/timescaledb-windows.yml) |\" >> $GITHUB_STEP_SUMMARY\n          echo \"HAS_FAILURES=true\" >> $GITHUB_ENV\n        fi\n\n    - name: Generate final summary\n      if: always()\n      run: |\n        echo \"---\" >> $GITHUB_STEP_SUMMARY\n        echo \"## 📋 Next Steps\" >> $GITHUB_STEP_SUMMARY\n        echo \"\" >> $GITHUB_STEP_SUMMARY\n        echo \"1. Monitor the triggered workflows in their respective repositories\" >> $GITHUB_STEP_SUMMARY\n        echo \"2. Check build artifacts once workflows complete\" >> $GITHUB_STEP_SUMMARY\n        echo \"3. Verify package availability in distribution channels\" >> $GITHUB_STEP_SUMMARY\n\n    - name: Validate workflow dispatch status\n      if: ${{ env.HAS_FAILURES == 'true' }}\n      run: |\n        echo \"---\" >> $GITHUB_STEP_SUMMARY\n        echo \"❌ one or more workflows could not get dispatched\" >> $GITHUB_STEP_SUMMARY\n        exit 1\n"
  },
  {
    "path": ".github/workflows/release_feature_freeze_ceremony.yaml",
    "content": "name: Release - Feature Freeze Ceremony\n\n#\n# Feature Freeze\n# \n# prereqs: \n# 0. needs approval from another person to run the action (environment)\n# 1. ensure the correct branch is used to execute the action\n#   a. if the run was triggered from the `main` branch, the NEXT_VERSION must be a new minor (end with a .0)\n#   b. if NEXT_VERSION is a patch release, then the branch the action was triggered on must be a release branch: `^[0-9]+\\.[0-9]+\\.x$`\n# 2. ensure the next version does not exist as tag yet\n# \n# Tasks\n# 0. bump minor -dev version in version.config on main, if and only if it's a .0 release\n# 1. creates the release branch X.Y.x, if and if the branch does not exist yet\n# 2. generates CHANGELOG and opens PR for it\n# 3. generates the release artefacts for the next versions\n# 4. bumps the version in version.config to minor + 1 if and only if a .0 release is done\n#\n\non:\n  workflow_dispatch:\n\n# The workflow needs the permission to push branches\npermissions:\n  contents: write\n  pull-requests: write\n\njobs:\n  validate-conditions:\n    name: Validate Run Conditions\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      \n      - name: Check Release Conditions\n        run: |\n          NEXT_VERSION=$(head -1 version.config | cut -d ' ' -f 3 | cut -d '-' -f 1)\n          RELEASE_BRANCH=\"${NEXT_VERSION/%.[0-9]/.x}\"\n          \n          echo \"Checking run conditions for release: $NEXT_VERSION\" >> $GITHUB_STEP_SUMMARY\n          echo \"Current branch: ${{ github.ref_name }}\" >> $GITHUB_STEP_SUMMARY\n          echo \"Expected release branch: $RELEASE_BRANCH\" >> $GITHUB_STEP_SUMMARY\n          echo \"\" >> $GITHUB_STEP_SUMMARY\n          \n          # Condition: version tag can not exist\n          if git tag -l | grep -q \"^$NEXT_VERSION$\"; then\n            echo \"❌ \\`$NEXT_VERSION\\` tag already exists\" >> $GITHUB_STEP_SUMMARY\n            exit 1\n          fi\n\n          # Condition: minor releases (.0) must be triggered from the main branch\n          if [[ \"$NEXT_VERSION\" =~ \\.0$ ]] && [[ \"${{ github.ref_name }}\" != \"main\" ]]; then\n            echo \"❌ for minor releases, \\`$NEXT_VERSION\\` the \\`main\\` branch is required, but got \\`${{ github.ref_name }}\\`\" >> $GITHUB_STEP_SUMMARY\n            exit 1\n          fi\n\n          # Condition: patch releases must be triggered from the release branch\n          if [[ ! \"$NEXT_VERSION\" =~ \\.0$ ]] && [[ \"${{ github.ref_name }}\" != \"$RELEASE_BRANCH\" ]]; then\n            echo \"❌ the patch releases, \\`$NEXT_VERSION\\` the action must be started from the release branch \\`$RELEASE_BRANCH\\`, but got \\`${{ github.ref_name }}\\`\" >> $GITHUB_STEP_SUMMARY\n            exit 1\n          fi\n\n          echo \"✅ All release conditions satisfied for \\`$NEXT_RELEASE\\`\" >> $GITHUB_STEP_SUMMARY\n\n  release-feature-freeze-ceremony:\n    name: Release - Feature Freeze\n    runs-on: ubuntu-latest\n    needs: validate-conditions\n    environment:\n      name: Release Ceremonies\n\n    steps:\n      - name: Checkout TimescaleDB\n        uses: actions/checkout@v4\n\n      # git user config timescale-automation\n      - run: |\n          git config user.name \"timescale-automation\"\n          git config user.email \"123763385+github-actions[bot]@users.noreply.github.com\"\n\n      - name: Install Dependencies\n        run: |\n          sudo apt-get update\n          sudo apt-get install pip\n          pip install PyGithub requests\n\n      # Read the next version and build the next versions\n      - name: Set version configuration\n        run: |\n          NEXT_VERSION=$(head -1 version.config | cut -d ' ' -f 3 | cut -d '-' -f 1)\n          CURRENT_VERSION=$(tail -1 version.config | cut -d ' ' -f 3 | cut -d '-' -f 1)\n          RELEASE_BRANCH=\"${NEXT_VERSION/%.[0-9]/.x}\"\n\n          echo \"RELEASE_BRANCH=${RELEASE_BRANCH}\" >> $GITHUB_ENV\n          echo \"CURRENT_VERSION=${CURRENT_VERSION}\" >> $GITHUB_ENV\n          echo \"NEXT_VERSION=${NEXT_VERSION}\" >> $GITHUB_ENV\n\n          echo \"# 🚀 Release v${NEXT_VERSION}\" >> $GITHUB_STEP_SUMMARY\n          echo \"Previous version: \\`$CURRENT_VERSION\\`\" >> $GITHUB_STEP_SUMMARY\n          echo \"Release branch: \\`$RELEASE_BRANCH\\`\" >> $GITHUB_STEP_SUMMARY\n          echo \"Workflow branch: \\`${{ github.ref_name }}\\`\" >> $GITHUB_STEP_SUMMARY\n          echo \"\" >> $GITHUB_STEP_SUMMARY\n\n      # -------------------------------------------------------\n      #\n      # Only running for new minor releases\n      #\n      # -------------------------------------------------------\n      \n      # Bump minor in main for minor releases\n      - uses: actions/checkout@v4\n        with:\n          ref: main\n\n      - name: Bump minor version on main if next release is a new minor\n        if: endsWith(env.NEXT_VERSION, '.0')\n        env:\n          GITHUB_TOKEN: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n        run: |\n          NEXT_MAIN_VERSION=$(head -1 version.config | cut -d ' ' -f 3 | cut -d '-' -f 1)\n          NEXT_MAIN_VERSION=$(echo \"$NEXT_MAIN_VERSION\" | awk -F. '{printf \"%d.%d.0-dev\", $1, $2 + 1}')\n          sed -i \"s/^version = .*/version = $NEXT_MAIN_VERSION/\" version.config\n\n          BRANCH=\"release/bump-main-to-$NEXT_MAIN_VERSION\"\n          git checkout -b $BRANCH\n          git add version.config\n          git commit -m \"Increase minor version $NEXT_MAIN_VERSION\"\n          git push origin $BRANCH\n\n          PR_URL=$(gh pr create \\\n            --title \"Increase minor version $NEXT_MAIN_VERSION\" \\\n            --body \"Bump to next minor for the upcoming ${{ env.CURRENT_VERSION }} release\" \\\n            --base main \\\n            --head $BRANCH \\\n            --label \"release\")\n          echo \"* ✅ minor version increase on main: $PR_URL\" >> $GITHUB_STEP_SUMMARY\n      \n      # Create `2.XX.x` release branch of `main`\n      # Release branch is a dependency for the next steps\n      # only execute this step on .0 releases\n      - name: Create Release Branch\n        env:\n          GITHUB_TOKEN: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n        run: |\n          if git ls-remote --heads origin \"${{ env.RELEASE_BRANCH }}\" | grep -q \"${{ env.RELEASE_BRANCH }}\"; then\n            echo \"* ✓ the \\`${{ env.RELEASE_BRANCH }}\\` release branch exists\" >> $GITHUB_STEP_SUMMARY\n          else\n            gh api repos/${{ github.repository }}/git/refs \\\n              --method POST \\\n              --field ref=\"refs/heads/${{ env.RELEASE_BRANCH }}\" \\\n              --field sha=\"${{ github.sha }}\"\n            echo \"* ✅ the \\`${{ env.RELEASE_BRANCH }}\\` release branch created\" >> $GITHUB_STEP_SUMMARY\n          fi\n\n      # -------------------------------------------------------\n      #\n      # Runs for every release\n      #\n      # -------------------------------------------------------\n\n      # Generate the CHANGELOG and submit it in a separate PR\n      - uses: actions/checkout@v4\n        with:\n          ref: ${{ env.RELEASE_BRANCH }}\n\n      - name: Generate and create Pull Request for CHANGELOG\n        env:\n          GITHUB_TOKEN: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n        run: |\n          ./scripts/changelog/generate.sh \n          BODY=$(head -n 19 CHANGELOG.md | tail -n 20)\n\n          BRANCH=\"release/${{ env.NEXT_VERSION }}-changelog\"\n          git checkout -b $BRANCH\n          git add .unreleased/* CHANGELOG.md\n          git commit -m \"CHANGELOG ${{ env.NEXT_VERSION }}\"\n          git push origin $BRANCH\n\n          PR_URL=$(gh pr create \\\n            --title \"Release ${{ env.NEXT_VERSION }}\" \\\n            --body \"$BODY\" \\\n            --base ${{ env.RELEASE_BRANCH }} \\\n            --head $BRANCH \\\n            --label \"release,changelog\")\n          echo \"* ✅ CHANGELOG: $PR_URL\" >> $GITHUB_STEP_SUMMARY\n\n      # Rework version.config and up & down grade scripts\n      - uses: actions/checkout@v4\n        with:\n          ref: ${{ env.RELEASE_BRANCH }}\n      - name: Generate release artefacts\n        env:\n          GITHUB_TOKEN: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n        run: |\n          ./scripts/release/build_release_artefacts.sh ${{ env.CURRENT_VERSION }} ${{ env.NEXT_VERSION }}\n\n          BRANCH=\"release/${{ env.CURRENT_VERSION }}-${{ env.NEXT_VERSION }}\"\n          git checkout -b $BRANCH\n          git add version.config sql/updates/*.sql sql/CMakeLists.txt\n          git commit -m \"release artefacts from ${{ env.CURRENT_VERSION }} to ${{ env.NEXT_VERSION }}\"\n          git push origin $BRANCH\n\n          BODY=\"- CHANGELOG\\n- set previous_version in version.config\\n- adjust CMakeList.txt with up & down grade files\"\n\n          PR_URL=$(gh pr create \\\n            --title \"Release artefacts ${{ env.NEXT_VERSION }}\" \\\n            --body \"${BODY//\\\\n/$\\n}\" \\\n            --base ${{ env.RELEASE_BRANCH }} \\\n            --head $BRANCH \\\n            --label \"release\")\n          echo \"* ✅ Release artefacts: $PR_URL\" >> $GITHUB_STEP_SUMMARY\n"
  },
  {
    "path": ".github/workflows/release_post_release_ceremony.yaml",
    "content": "name: Release - Post Release Ceremony\n\n#\n# Post Release Workflow\n#\n# 0. trigger the generate GUCs workflow in the docs repo\n# 1. generate the artefacts to forward port to main and open PR for it\n# 2. bump the next patch number + 1 on the release branch and open PR for it\n# \n\n\"on\":\n  release:\n    types: [published]\n\n# The workflow needs the permission to a PR\npermissions:\n  contents: write\n  pull-requests: write\n\njobs:\n  docs:\n    name: Post Release Ceremonies\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Generate list of GUCs for docs\n        env:\n          GITHUB_TOKEN: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n        run: |\n          if gh workflow run tsdb-refresh-gucs-list.yaml -R timescale/docs -f tag=${{ github.event.release.tag_name }}; then\n            echo \"✅ Triggered generation of GUC list in docs\" >> $GITHUB_STEP_SUMMARY\n          else\n            echo \"❌ docs workflow to generate the GUCs failed\" >> $GITHUB_STEP_SUMMARY\n          fi\n\n      - uses: actions/checkout@v4\n        with:\n          ref: ${{ github.event.release.tag_name }}\n      \n      # git user config timescale-automation\n      - run: |\n          git config user.name \"timescale-automation\"\n          git config user.email \"123763385+github-actions[bot]@users.noreply.github.com\"\n\n      - name: Forward port changes to main\n        env:\n          GITHUB_TOKEN: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n        run: |\n          ./scripts/release/build_post_release_artefacts.sh ${{ github.event.release.tag_name }}\n\n          BRANCH=\"release/${{ github.event.release.tag_name }}-main\"\n          git checkout -b $BRANCH\n          git add version.config sql/updates/*.sql sql/CMakeLists.txt .unreleased/* CHANGELOG.md\n          git commit -m \"forward port release artefacts of ${{ github.event.release.tag_name }} to main\"\n          git push origin $BRANCH\n\n          printf '%b- CHANGELOG\\n- set previous_version in version.config\\n- adjust CMakeList.txt with up & down grade files\\n- removed .unreleased files' > $BODY\n\n          PR_URL=$(gh pr create \\\n            --title \"Forwardport ${{ github.event.release.tag_name }}\" \\\n            --body \"$BODY\" \\\n            --base main \\\n            --head $BRANCH \\\n            --label \"release\")\n          echo \"✅ Forward ported release artefacts: $PR_URL\" >> $GITHUB_STEP_SUMMARY\n\n      - name: Get next versions\n        run: |\n          TAGGED_VERSION=$(head -1 version.config | cut -d ' ' -f 3 | cut -d '-' -f 1)\n          if [[ \"$TAGGED_VERSION\" != \"$${{ github.event.release.tag_name }}\" ]]; then\n            echo \"The tag: ${{ github.event.release.tag_name }} and the release version ${TAGGED_VERSION} do not match.\" >&2\n            echo \"❌ tagged version ${{ github.event.release.tag_name }} is not matching the current version $TAGGED_VERSION\" >> $GITHUB_STEP_SUMMARY\n            exit 1\n          fi\n\n          RELEASE_BRANCH=\"${TAGGED_VERSION/%.d/.x}\"\n          NEXT_VERSION=$(echo \"$TAGGED_VERSION\" | awk -F. '{printf \"%d.%d.%d\", $1, $2, $3+1}')\n\n          echo \"TAGGED_VERSION=${TAGGED_VERSION}\" >> $GITHUB_ENV\n          echo \"RELEASE_BRANCH=${RELEASE_BRANCH}\" >> $GITHUB_ENV\n          echo \"NEXT_VERSION=${NEXT_VERSION}\" >> $GITHUB_ENV\n\n      - uses: actions/checkout@v4\n        with:\n          ref: ${{ env.RELEASE_BRANCH }}\n\n      - name: Bump to next version on release branch\n        env:\n          GITHUB_TOKEN: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n        run: |\n          echo \"Tagged version: ${{ env.TAGGED_VERSION }}\"\n          echo \"Next version: ${{ env.NEXT_VERSION }}\" \n\n          # adjust version.config file\n          sed -i \\\n            -e \"s/^version = .*/version = ${{ env.NEXT_VERSION }}/\" \\\n            -e \"s/^previous_version = .*/previous_version = ${{ env.TAGGED_VERSION }}/\" \\\n            version.config\n\n          echo \"✅ setting next patch version ${{ env.TAGGED_VERSION }} in version.config\" >> $GITHUB_STEP_SUMMARY\n          cat version.config >> $GITHUB_STEP_SUMMARY\n\n          BRANCH=\"release/bump-${{ github.event.release.tag_name }}-to-${{ env.NEXT_VERSION }}\"\n          git checkout -b $BRANCH\n          git add version.config\n          git commit -m \"Bump to next patch version ${{ env.NEXT_VERSION }}\"\n          git push origin $BRANCH\n\n          PR_URL=$(gh pr create \\\n            --title \"Forwardport ${{ github.event.release.tag_name }}\" \\\n            --body \"Change to the next patch version ${{ env.NEXT_VERSION }} after the release of ${{ github.event.release.tag_name }}.\" \\\n            --base $RELEASE_BRANCH \\\n            --head $BRANCH \\\n            --label \"release\")\n          echo \"✅ Bump to next patch version: $PR_URL\" >> $GITHUB_STEP_SUMMARY\n"
  },
  {
    "path": ".github/workflows/rpm-packages.yaml",
    "content": "# Test rpm package installation for latest version\nname: \"Packaging tests: RPM\"\n\"on\":\n  schedule:\n    # run daily 0:00 on main branch\n    - cron: '0 0 * * *'\n  pull_request:\n    paths: .github/workflows/rpm-packages.yaml\n  push:\n    tags:\n    - '*'\n    branches:\n    - release_test\n    - trigger/package_test\n  workflow_dispatch:\n\njobs:\n  rpm_tests:\n    name: RPM ${{ matrix.image }} PG${{ matrix.pg }} ${{ matrix.license }}\n    runs-on: ubuntu-latest\n    container:\n      image: ${{ matrix.image }}\n    strategy:\n      fail-fast: false\n      matrix:\n        image: [ \"rockylinux:8\", \"rockylinux:9\" ]\n        pg: [ 15, 16, 17, 18 ]\n        license: [ \"TSL\", \"Apache\"]\n        include:\n          - license: Apache\n            pkg_suffix: \"-oss\"\n        exclude:\n          - image: \"rockylinux:8\"\n            pg: 18\n\n    steps:\n    - name: Add postgres repositories\n      run: \"yum install -y \\\"https://download.postgresql.org/pub/repos/yum/reporpms/\\\n        EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm\\\"\"\n    - name: Add other repositories\n      run: |\n        tee /etc/yum.repos.d/timescale_timescaledb.repo <<EOL\n        [timescale_timescaledb]\n        name=timescale_timescaledb\n        baseurl=https://packagecloud.io/timescale/timescaledb/el/$(rpm -E %{rhel})/\\$basearch\n        repo_gpgcheck=1\n        gpgcheck=0\n        enabled=1\n        gpgkey=https://packagecloud.io/timescale/timescaledb/gpgkey\n        sslverify=1\n        sslcacert=/etc/pki/tls/certs/ca-bundle.crt\n        metadata_expire=300\n        EOL\n\n    - name: Install timescaledb\n      run: |\n        yum update -y\n        if [[ \"$(rpm -E %{rhel})\" -eq \"8\" ]]; then dnf -qy module disable postgresql; fi\n        yum install -y timescaledb-2${{ matrix.pkg_suffix }}-postgresql-${{ matrix.pg }} sudo wget jq\n        sudo -u postgres /usr/pgsql-${{ matrix.pg }}/bin/initdb -D /var/lib/pgsql/${{ matrix.pg }}/data\n        timescaledb-tune --quiet --yes --pg-config /usr/pgsql-${{ matrix.pg }}/bin/pg_config\n\n    - name: List available versions\n      run: |\n        yum --showduplicates list timescaledb-2${{ matrix.pkg_suffix }}-postgresql-${{ matrix.pg }}\n\n    - name: Show files in package\n      run: |\n        rpm -ql timescaledb-2${{ matrix.pkg_suffix }}-postgresql-${{ matrix.pg }}\n\n    - uses: actions/checkout@v3\n\n    - name: Get version of latest release\n      id: versions\n      run: |\n        version=$(wget -q https://api.github.com/repos/timescale/timescaledb/releases/latest -O - | jq -r .tag_name)\n        echo \"version=${version}\"\n        echo \"version=${version}\" >>$GITHUB_OUTPUT\n\n    - name: Test Installation\n      run: |\n        sudo -u postgres /usr/pgsql-${{ matrix.pg }}/bin/pg_ctl -D /var/lib/pgsql/${{ matrix.pg }}/data start\n        while ! /usr/pgsql-${{ matrix.pg }}/bin/pg_isready; do sleep 1; done\n        sudo -u postgres psql -X -c \"CREATE EXTENSION timescaledb\" \\\n          -c \"SELECT extname,extversion,version() FROM pg_extension WHERE extname='timescaledb';\"\n        installed_version=$(sudo -u postgres psql -X -t \\\n          -c \"SELECT extversion FROM pg_extension WHERE extname='timescaledb';\" | sed -e 's! !!g')\n        if [ \"${{ steps.versions.outputs.version }}\" != \"$installed_version\" ];then\n          false\n        fi\n\n    - name: Test Downgrade\n      if: matrix.pg != '18' # pg18 only has 1 version so downgrade is not possible\n      run: |\n        # Since this runs nightly on main we have to get the previous version\n        # from the last released version and not current branch.\n        prev_version=$(wget --quiet -O - \\\n          https://raw.githubusercontent.com/timescale/timescaledb/${{ steps.versions.outputs.version }}/version.config \\\n          | grep previous_version | sed -e 's!previous_version = !!')\n        yum downgrade -y timescaledb-2${{ matrix.pkg_suffix }}-postgresql-${{ matrix.pg }}-${prev_version}\n        sudo -u postgres psql -X -c \"ALTER EXTENSION timescaledb UPDATE TO '${prev_version}'\" \\\n          -c \"SELECT extname,extversion,version() FROM pg_extension WHERE extname='timescaledb';\"\n        installed_version=$(sudo -u postgres psql -X -t \\\n          -c \"SELECT extversion FROM pg_extension WHERE extname='timescaledb';\" | sed -e 's! !!g')\n        if [ \"$prev_version\" != \"$installed_version\" ];then\n          false\n        fi\n\n    - name: Install toolkit\n      if: matrix.pg != 17 && matrix.image != 'rockylinux:9' # timescaledb-toolkit currently not available for PG17 and Rocky9\n      run: |\n        yum install -y timescaledb-toolkit-postgresql-${{ matrix.pg }}\n\n    - name: List available toolkit versions\n      if: matrix.pg != 17 && matrix.image != 'rockylinux:9' # timescaledb-toolkit currently not available for PG17 and Rocky9\n      run: |\n        yum --showduplicates list timescaledb-toolkit-postgresql-${{ matrix.pg }}\n\n\n"
  },
  {
    "path": ".github/workflows/sanitizer-build-and-test.yaml",
    "content": "# Run regression tests under memory sanitizer\nname: Sanitizer test\n\"on\":\n  schedule:\n    # run daily 0:00 on main branch\n    - cron: '0 0 * * *'\n  push:\n    branches:\n      - main\n      - ?.*.x\n      - trigger/sanitizer\n  pull_request:\n    paths: .github/workflows/sanitizer-build-and-test.yaml\n  workflow_dispatch:\n\nenv:\n  name: \"Sanitizer\"\n  PG_SRC_DIR: \"pgbuild\"\n  PG_INSTALL_DIR: \"postgresql\"\n  extra_packages: \"clang-15 llvm-15 llvm-15-dev llvm-15-tools\"\n  llvm_config: \"llvm-config-15\"\n  CLANG: \"clang-15\"\n  CC: \"clang-15\"\n  CXX: \"clang-15\"\n  # gcc CFLAGS, disable inlining for function name pattern matching to work for suppressions\n  # CFLAGS: \"-g -fsanitize=address,undefined -fno-omit-frame-pointer -O1 -fno-inline\"\n  # CXXFLAGS: \"-g -fsanitize=address,undefined -fno-omit-frame-pointer -O1 -fno-inline\"\n  # clang CFLAGS\n  CFLAGS: \"-g -fsanitize=address,undefined -fno-omit-frame-pointer -Og -fno-inline-functions\"\n  CXXFLAGS: \"-g -fsanitize=address,undefined -fno-omit-frame-pointer -Og -fno-inline-functions\"\n\n  # We do not link libasan dynamically to avoid problems with libdl and our libraries.\n  # clang does this by default, but we need to explicitly state that for gcc.\n  # static gcc LDFLAGS\n  # LDFLAGS: \"-fsanitize=address,undefined -static-libasan -static-liblsan -static-libubsan\"\n  # static sanitizer clang LDFLAGS or dynamic sanitizer gcc LDFLAGS\n  LDFLAGS: \"-fsanitize=address,undefined\"\n  ASAN_OPTIONS: suppressions=${{ github.workspace }}/scripts/suppressions/suppr_asan.txt\n    detect_odr_violation=0 log_path=${{ github.workspace }}/sanitizer_logs/sanitizer\n    log_exe_name=true print_suppressions=false exitcode=27\n    detect_leaks=0 abort_on_error=1\n\n  LSAN_OPTIONS: suppressions=${{ github.workspace }}/scripts/suppressions/suppr_leak.txt\n    print_suppressions=0 log_path=${{ github.workspace }}/sanitizer_logs/sanitizer\n    log_exe_name=true print_suppressions=false exitcode=27\n\n  UBSAN_OPTIONS: suppressions=${{ github.workspace }}/scripts/suppressions/suppr_ub.txt\n    print_stacktrace=1 halt_on_error=1 log_path=${{ github.workspace }}/sanitizer_logs/sanitizer\n    log_exe_name=true print_suppressions=false exitcode=27\n\n  IGNORES: >-\n    bgw_db_scheduler\n    bgw_db_scheduler_fixed\n    merge_append_partially_compressed\n    net\n    telemetry\n\n  EXTENSIONS: \"postgres_fdw test_decoding\"\njobs:\n  config:\n    runs-on: ubuntu-latest\n    outputs:\n      pg_latest: ${{ steps.setter.outputs.PG_LATEST }}\n    steps:\n    - name: Checkout source code\n      uses: actions/checkout@v4\n    - name: Read configuration\n      id: setter\n      run: python .github/gh_config_reader.py\n\n  sanitizer:\n    # Change the JOB_NAME variable below when changing the name.\n    # Don't use the env variable here because the env context is not accessible.\n    name: PG${{ matrix.pg }} Sanitizer ${{ matrix.os }}\n    runs-on: ${{ matrix.os }}\n    needs: config\n    strategy:\n      fail-fast: false\n      matrix:\n        # \"os\" has to be in the matrix due to a bug in \"env\": https://github.community/t/how-to-use-env-context/16975\n        os: [\"ubuntu-22.04\"]\n        pg: ${{ fromJson(needs.config.outputs.pg_latest) }}\n    steps:\n    - name: Install Linux Dependencies\n      run: |\n        sudo apt-get update\n        sudo apt-get install flex bison lcov systemd-coredump gdb libipc-run-perl \\\n          libtest-most-perl jq ${{ env.extra_packages }}\n\n    - name: Checkout TimescaleDB\n      uses: actions/checkout@v4\n\n    # We are going to rebuild Postgres daily, so that it doesn't suddenly break\n    # ages after the original problem.\n    - name: Get date for build caching\n      id: get-date\n      run: |\n        echo \"date=$(date +\"%d\")\" >> $GITHUB_OUTPUT\n\n    # Create a directory for sanitizer logs. This directory is referenced by\n    # ASAN_OPTIONS, LSAN_OPTIONS, and UBSAN_OPTIONS\n    - name: Create sanitizer log directory\n      run: |\n        mkdir ${{ github.workspace }}/sanitizer_logs\n\n    # we cache the build directory instead of the install directory here\n    # because extension installation will write files to install directory\n    # leading to a tainted cache\n    - name: Cache PostgreSQL ${{ matrix.pg }}\n      id: cache-postgresql\n      uses: actions/cache@v4\n      with:\n        path: ~/${{ env.PG_SRC_DIR }}\n        key: \"${{ matrix.os }}-${{ env.name }}-postgresql-${{ matrix.pg }}-${{ env.CC }}\\\n          -${{ steps.get-date.outputs.date }}-${{ hashFiles('.github/**') }}\"\n\n    - name: Build PostgreSQL ${{ matrix.pg }} if not in cache\n      if: steps.cache-postgresql.outputs.cache-hit != 'true'\n      run: |\n        wget -q -O postgresql.tar.bz2 \\\n          https://ftp.postgresql.org/pub/source/v${{ matrix.pg }}/postgresql-${{ matrix.pg }}.tar.bz2\n        mkdir -p ~/$PG_SRC_DIR\n        tar --extract --file postgresql.tar.bz2 --directory ~/$PG_SRC_DIR --strip-components 1\n        # Add instrumentation to the Postgres memory contexts. For more details, see\n        # https://github.com/timescale/eng-database/wiki/Using-Address-Sanitizer#adding-more-instrumentation\n        PG_MAJOR=$(echo \"${{ matrix.pg }}\" | sed -e 's![.].*!!')\n        if [ ${PG_MAJOR} -lt 18 ]; then\n          patch -F5 -p1 -d ~/$PG_SRC_DIR < test/postgres-asan-instrumentation.patch\n        else\n          patch -F5 -p1 -d ~/$PG_SRC_DIR < test/postgres-asan-instrumentation-PG18GE.patch\n        fi\n        cd ~/$PG_SRC_DIR\n        ./configure --prefix=$HOME/$PG_INSTALL_DIR --enable-debug --enable-cassert \\\n          --with-openssl --without-readline --without-zlib --without-libxml\n        make -j$(getconf _NPROCESSORS_ONLN)\n        for ext in ${EXTENSIONS}; do\n          make -j$(getconf _NPROCESSORS_ONLN) -C contrib/${ext}\n        done\n\n    - name: Upload config.log\n      if: always() && steps.cache-postgresql.outputs.cache-hit != 'true'\n      uses: actions/upload-artifact@v4\n      with:\n        name: config.log for PostgreSQL ${{ matrix.os }} ${{ env.name }} ${{ matrix.pg }}\n        path: ~/${{ env.PG_SRC_DIR }}/config.log\n\n    - name: Install PostgreSQL ${{ matrix.pg }}\n      run: |\n        cd ~/$PG_SRC_DIR\n        make install\n        for ext in ${EXTENSIONS}; do\n          make -C contrib/${ext} install\n        done\n        ~/$PG_INSTALL_DIR/bin/pg_config --version\n\n    - name: Build TimescaleDB\n      run: |\n        ./bootstrap -DCMAKE_BUILD_TYPE=Debug -DPG_SOURCE_DIR=~/$PG_SRC_DIR \\\n          -DPG_PATH=~/$PG_INSTALL_DIR -DCODECOVERAGE=OFF -DREQUIRE_ALL_TESTS=ON \\\n          -DTEST_GROUP_SIZE=5 -DTEST_PG_LOG_DIRECTORY=\"$(readlink -f .)\" -DTEST_TIMEOUT=240\n        make -j$(getconf _NPROCESSORS_ONLN) -C build\n        make -C build install\n\n    - name: make installcheck\n      run: |\n        set -o pipefail\n        # IGNORE some test since they fail under ASAN.\n        make -k -C build installcheck IGNORES=\"${IGNORES}\" \\\n          PSQL=\"${HOME}/${PG_INSTALL_DIR}/bin/psql\" | tee installcheck.log\n\n    - name: Show regression diffs\n      if: always()\n      id: collectlogs\n      run: |\n        find . -name regression.diffs -exec cat {} + > regression.log\n        if [[ \"${{ runner.os }}\" == \"Linux\" ]] ; then\n          # wait in case there are in-progress coredumps\n          sleep 10\n          if coredumpctl -q list >/dev/null; then echo \"coredumps=true\" >>$GITHUB_OUTPUT; fi\n          # print OOM killer information\n          sudo journalctl --system -q --facility=kern --grep \"Killed process\" || true\n        fi\n        if [[ -s regression.log ]]; then echo \"regression_diff=true\" >>$GITHUB_OUTPUT; fi\n        grep -e 'FAILED' -e 'failed (ignored)' -e 'not ok' installcheck.log || true\n        cat regression.log\n\n    - name: Save regression diffs\n      if: always() && steps.collectlogs.outputs.regression_diff == 'true'\n      uses: actions/upload-artifact@v4\n      with:\n        name: Regression diff ${{ matrix.os }} ${{ env.name }} ${{ matrix.pg }}\n        path: |\n          regression.log\n          installcheck.log\n\n    - name: Save PostgreSQL log\n      if: always()\n      uses: actions/upload-artifact@v4\n      with:\n        name: PostgreSQL log ${{ matrix.os }} ${{ env.name }} ${{ matrix.pg }}\n        path: postmaster.*\n\n    - name: Stack trace\n      if: always() && steps.collectlogs.outputs.coredumps == 'true'\n      run: |\n        sudo coredumpctl gdb <<<\"\n          set verbose on\n          set trace-commands on\n          show debug-file-directory\n          printf \"'\"'\"query = '%s'\\n\\n\"'\"'\", debug_query_string\n          frame function ExceptionalCondition\n          printf \"'\"'\"condition = '%s'\\n\"'\"'\", conditionName\n          up 1\n          l\n          info args\n          info locals\n          bt full\n        \" 2>&1 | tee stacktrace.log\n        ./scripts/bundle_coredumps.sh\n\n    - name: Show sanitizer logs\n      if: always()\n      run: |\n        tail -vn +1 ${{ github.workspace }}/sanitizer_logs/sanitizer* || :\n\n    - name: Coredumps\n      if: always() && steps.collectlogs.outputs.coredumps == 'true'\n      uses: actions/upload-artifact@v4\n      with:\n        name: Coredumps ${{ matrix.os }} ${{ env.name }} ${{ matrix.pg }}\n        path: coredumps\n\n    - name: Upload sanitizer logs\n      if: always()\n      uses: actions/upload-artifact@v4\n      with:\n        name: sanitizer logs ${{ matrix.os }} ${{ env.name }} ${{ matrix.pg }}\n        # The log_path sanitizer option means \"Write logs to 'log_path.pid'\".\n        # https://github.com/google/sanitizers/wiki/SanitizerCommonFlags\n        path: ${{ github.workspace }}/sanitizer_logs/*\n\n    - name: Upload test results to the database\n      if: always()\n      env:\n        # GitHub Actions allow you neither to use the env context for the job name,\n        # nor to access the job name from the step context, so we have to\n        # duplicate it to work around this nonsense.\n        JOB_NAME: PG${{ matrix.pg }} ${{ env.name }} ${{ matrix.os }}\n        CI_STATS_DB: ${{ secrets.CI_STATS_DB }}\n        GITHUB_EVENT_NAME: ${{ github.event_name }}\n        GITHUB_REF_NAME: ${{ github.ref_name }}\n        GITHUB_REPOSITORY: ${{ github.repository }}\n        GITHUB_RUN_ATTEMPT: ${{ github.run_attempt }}\n        GITHUB_RUN_ID: ${{ github.run_id }}\n        GITHUB_RUN_NUMBER: ${{ github.run_number }}\n        JOB_STATUS: ${{ job.status }}\n      run: |\n        if [[ \"${{ github.event_name }}\" == \"pull_request\" ]] ;\n        then\n            GITHUB_PR_NUMBER=\"${{ github.event.number }}\"\n        else\n            GITHUB_PR_NUMBER=0\n        fi\n        export GITHUB_PR_NUMBER\n        scripts/upload_ci_stats.sh\n"
  },
  {
    "path": ".github/workflows/shellcheck.yaml",
    "content": "# Test our shell scripts for bugs\nname: Shellcheck\n\"on\":\n  pull_request:\n  push:\n    branches:\n      - main\n      - ?.*.x\njobs:\n  check_paths:\n    runs-on: ubuntu-latest\n    outputs:\n      run_ci: >-\n        ${{ github.event_name == 'push'\n          || (steps.filter.outputs.shell == 'true' && !contains(github.event.pull_request.labels.*.name, 'skip-ci'))\n         }}\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dorny/paths-filter@v3\n        id: filter\n        with:\n          filters: .github/filters.yaml\n\n  shellcheck:\n    name: Shellcheck\n    needs: [check_paths]\n    if: needs.check_paths.outputs.run_ci == 'true'\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Install Dependencies\n      run: |\n        sudo apt-get update\n        sudo apt-get install shellcheck\n\n    - name: Checkout TimescaleDB\n      uses: actions/checkout@v4\n      with:\n        fetch-depth: 0\n\n    - name: Run shellcheck\n      run: scripts/shellcheck-ci.sh\n\n  report_status:\n    name: Shellcheck summary\n    needs: [check_paths, shellcheck]\n    if: always()\n    runs-on: ubuntu-latest\n    steps:\n      - run: |\n          # If the detector failed, or the matrix failed, exit with error\n          if [[ \"${{ needs.shellcheck.result }}\" == \"failure\" ]]; then\n            exit 1\n          fi\n          echo \"All checks passed or were safely skipped.\"\n\n"
  },
  {
    "path": ".github/workflows/snapshot-abi.yaml",
    "content": "# Test ABI versions against snapshot\n#\n#\n\nname: ABI Test Against Snapshot\n\"on\":\n  schedule:\n    # run daily 20:00 on main branch\n    - cron: '0 20 * * *'\n  push:\n    branches:\n      - ?.*.x\n      - trigger/snapshot-abi\n  pull_request:\n    paths: .github/workflows/snapshot-abi.yaml\n  workflow_dispatch:\n\njobs:\n  config:\n    runs-on: ubuntu-latest\n    outputs:\n      pg15_abi_min: ${{ steps.config.outputs.pg15_abi_min }}\n      pg16_abi_min: ${{ steps.config.outputs.pg16_abi_min }}\n      pg17_abi_min: ${{ steps.config.outputs.pg17_abi_min }}\n      pg18_abi_min: ${{ steps.config.outputs.pg18_abi_min }}\n\n    steps:\n    - name: Checkout source code\n      uses: actions/checkout@v4\n    - name: Read configuration\n      id: config\n      run: python .github/gh_config_reader.py\n\n  abi_snapshot_test:\n    name: ABI Snapshot Test PG${{ matrix.pg }}\n    runs-on: ubuntu-latest\n    needs: config\n\n    env:\n      PG_SRC_DIR: pgbuild\n      PG_INSTALL_DIR: postgresql\n      PG_EXTENSIONS: postgres_fdw test_decoding\n\n    strategy:\n      fail-fast: false\n      matrix:\n        pg: [ 15, 16, 17, 18 ]\n        ignores:\n          - 'net telemetry bgw_launcher'\n        include:\n          - pg: 15\n            abi_min: ${{ fromJson(needs.config.outputs.pg15_abi_min) }}\n          - pg: 16\n            abi_min: ${{ fromJson(needs.config.outputs.pg16_abi_min) }}\n          - pg: 17\n            abi_min: ${{ fromJson(needs.config.outputs.pg17_abi_min) }}\n          - pg: 18\n            abi_min: ${{ fromJson(needs.config.outputs.pg18_abi_min) }}\n\n    steps:\n\n    - name: Install Linux Dependencies\n      run: |\n        # Don't add ddebs here because the ddebs mirror is always 503 Service Unavailable.\n        # If needed, install them before opening the core dump.\n        sudo apt-get update\n        sudo apt-get install flex bison lcov systemd-coredump gdb libipc-run-perl \\\n          libtest-most-perl pkgconf icu-devtools clang-14 llvm-14 llvm-14-dev llvm-14-tools cmake\n\n    - name: Checkout TimescaleDB\n      uses: actions/checkout@v4\n\n    - name: Download Postgres ${{ matrix.pg }}\n      run: |\n        wget -q -O postgresql.tar.bz2 \\\n          https://ftp.postgresql.org/pub/source/v${{ matrix.abi_min }}/postgresql-${{ matrix.abi_min }}.tar.bz2\n        mkdir -p ~/$PG_SRC_DIR\n        tar --extract --file postgresql.tar.bz2 --directory ~/$PG_SRC_DIR --strip-components 1\n        cd ~/$PG_SRC_DIR\n        ./configure --prefix=$HOME/$PG_INSTALL_DIR --with-openssl \\\n          --without-readline --without-zlib --without-libxml --without-llvm\n        make -j $(getconf _NPROCESSORS_ONLN)\n        for ext in $PG_EXTENSIONS; do\n          make -j $(getconf _NPROCESSORS_ONLN) -C contrib/${ext}\n        done\n\n    - name: Install postgresql ${{ matrix.pg }}\n      run: |\n        cd ~/$PG_SRC_DIR\n        make install\n        for ext in $PG_EXTENSIONS; do\n          make -C contrib/${ext} install\n        done\n        echo \"$HOME/$PG_INSTALL_DIR/bin\" >> \"${GITHUB_PATH}\"\n\n    - name: Build TimescaleDB\n      run: |\n        ./bootstrap -DCMAKE_BUILD_TYPE=Debug \\\n          -DPG_SOURCE_DIR=~/$PG_SRC_DIR -DPG_PATH=~/$PG_INSTALL_DIR \\\n          -DWARNINGS_AS_ERRORS=OFF -DREQUIRE_ALL_TESTS=ON \\\n          -DTEST_PG_LOG_DIRECTORY=\"$(readlink -f .)\"\n        make -j $(getconf _NPROCESSORS_ONLN) -C build\n        make -C build install\n        mkdir -p build/install_ext build/install_lib\n        cp `pg_config --sharedir`/extension/timescaledb*.{control,sql} build/install_ext\n        cp `pg_config --pkglibdir`/timescaledb*.so build/install_lib\n\n    - name: Download Postgres ${{ matrix.pg }}-snapshot\n      run: |\n        wget -q -O postgresql.tar.bz2 \\\n          https://ftp.postgresql.org/pub/snapshot/${{ matrix.pg }}/postgresql-${{ matrix.pg }}-snapshot.tar.bz2\n        mkdir -p ~/$PG_SRC_DIR-snapshot\n        tar --extract --file postgresql.tar.bz2 --directory ~/$PG_SRC_DIR-snapshot --strip-components 1\n        cd ~/$PG_SRC_DIR-snapshot\n        ./configure --prefix=$HOME/$PG_INSTALL_DIR-snapshot --with-openssl \\\n          --without-readline --without-zlib --without-libxml --without-llvm\n        make -j $(getconf _NPROCESSORS_ONLN)\n        for ext in $PG_EXTENSIONS; do\n          make -j $(getconf _NPROCESSORS_ONLN) -C contrib/${ext}\n        done\n\n    - name: Install PostgreSQL ${{ matrix.pg }}-snapshot\n      run: |\n        cd ~/$PG_SRC_DIR-snapshot\n        make install\n        for ext in $PG_EXTENSIONS; do\n          make -C contrib/${ext} install\n        done\n        echo \"$HOME/$PG_INSTALL_DIR-snapshot/bin\" >> \"${GITHUB_PATH}\"\n\n    - name: Copy extension files to postgresql ${{ matrix.pg }}-snapshot\n      run: |\n        BUILD_DIR=build_snapshot ./bootstrap -DCMAKE_BUILD_TYPE=Debug \\\n          -DPG_SOURCE_DIR=~/$PG_SRC_DIR-snapshot -DPG_PATH=~/$PG_INSTALL_DIR-snapshot \\\n          -DWARNINGS_AS_ERRORS=ON -DREQUIRE_ALL_TESTS=ON \\\n          -DTEST_PG_LOG_DIRECTORY=\"$(readlink -f .)\"\n        cp build/install_ext/* `pg_config --sharedir`/extension/\n        cp build/install_lib/* `pg_config --pkglibdir`\n\n    - name: make regresscheck\n      id: regresscheck\n      run: |\n          set -o pipefail\n          make -k -C build_snapshot installcheck IGNORES=\"${{ matrix.ignores }}\" | tee installcheck.log\n\n    - name: Show regression diffs\n      if: always()\n      id: collectlogs\n      run: |\n        find . -name regression.diffs -exec cat {} + > regression.log\n\n        if [[ \"${{ runner.os }}\" == \"Linux\" ]] ; then\n          # wait in case there are in-progress coredumps\n          sleep 10\n          if coredumpctl -q list >/dev/null; then echo \"coredumps=true\" >>$GITHUB_OUTPUT; fi\n          # print OOM killer information\n          sudo journalctl --system -q --facility=kern --grep \"Killed process\" || true\n        elif [[ \"${{ runner.os }}\" == \"macOS\" ]] ; then\n           if [ $(find /cores -type f | wc -l) -gt 0 ]; then echo \"coredumps=true\" >>$GITHUB_OUTPUT; fi\n        fi\n        if [[ -s regression.log ]]; then echo \"regression_diff=true\" >>$GITHUB_OUTPUT; fi\n        grep -e 'FAILED' -e 'failed (ignored)' -e 'not ok' installcheck.log || true\n        cat regression.log\n\n    - name: Save regression diffs\n      if: always() && steps.collectlogs.outputs.regression_diff == 'true'\n      uses: actions/upload-artifact@v4\n      with:\n        name: Regression diff Snapshot ABI Breakage PG${{ matrix.pg }}\n        path: regression.log\n\n    - name: Save postmaster.log\n      if: always()\n      uses: actions/upload-artifact@v4\n      with:\n        name: PostgreSQL log Snapshot ABI Breakage PG${{ matrix.pg }}\n        path: postmaster.log\n"
  },
  {
    "path": ".github/workflows/sqlsmith.yaml",
    "content": "name: SQLsmith\n\"on\":\n  schedule:\n    # run daily 2:00 on main branch\n    - cron: '0 2 * * *'\n  workflow_dispatch:\n  push:\n    branches:\n      - sqlsmith\n      - main\n      - ?.*.x\n  pull_request:\n    paths: .github/workflows/sqlsmith.yaml\n\njobs:\n  sqlsmith:\n    # Change the JOB_NAME variable below when changing the name.\n    # Don't use the env variable here because the env context is not accessible.\n    name: SQLsmith PG${{ matrix.pg }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: [\"ubuntu-22.04\"]\n        pg: [ \"15\", \"16\", \"17\", \"18\" ]\n        build_type: [\"Debug\"]\n      fail-fast: false\n    env:\n      PG_SRC_DIR: pgbuild\n      PG_INSTALL_DIR: postgresql\n      JOB_NAME: SQLsmith PG${{ matrix.pg }}\n\n    steps:\n    - name: Install Dependencies\n      run: |\n        sudo apt-get update\n        sudo apt-get install gnupg systemd-coredump gdb postgresql-common \\\n            build-essential autoconf autoconf-archive \\\n            libboost-regex-dev libsqlite3-dev jq\n\n        yes | sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh\n        sudo apt-get purge postgresql*\n        sudo apt-get install libpqxx-dev postgresql-${{ matrix.pg }} postgresql-server-dev-${{ matrix.pg }}\n\n        # Make sure the system postgres service is not running.\n        sudo systemctl mask postgresql\n        sudo systemctl stop postgresql\n        { psql -c \"select 1\" && exit 1 ; } || :\n\n    - name: Checkout TimescaleDB\n      uses: actions/checkout@v4\n\n    - name: Build TimescaleDB\n      run: |\n        ./bootstrap -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DWARNINGS_AS_ERRORS=OFF\n        make -j$(getconf _NPROCESSORS_ONLN) -C build\n        sudo make -C build install\n\n    - name: Checkout sqlsmith\n      uses: actions/checkout@v4\n      with:\n        repository: 'timescale/sqlsmith'\n        path: 'sqlsmith'\n        ref: 'timescaledb'\n\n    - name: Build SQLsmith\n      run: |\n        cd sqlsmith\n        autoreconf -i\n        ./configure\n        make -j$(getconf _NPROCESSORS_ONLN)\n\n    - name: Setup test environment\n      run: |\n        mkdir ~/pgdata\n        /usr/lib/postgresql/${{ matrix.pg }}/bin/pg_ctl initdb -D ~/pgdata\n        /usr/lib/postgresql/${{ matrix.pg }}/bin/pg_ctl -D ~/pgdata start \\\n            -o \"-cshared_preload_libraries=timescaledb\" -o \"-cmax_connections=200\" \\\n            -o \"-cmax_prepared_transactions=100\" -o \"-cunix_socket_directories=/tmp\" \\\n            -o \"-clog_statement=all\" -o \"-clogging_collector=true\" \\\n            -o \"-clog_destination=jsonlog,stderr\" -o \"-clog_directory=$(readlink -f .)\" \\\n            -o \"-clog_error_verbosity=verbose\" -o \"-clog_filename=postmaster.log\"\n        psql -h /tmp postgres -c 'CREATE DATABASE smith;'\n        psql -h /tmp smith -c 'CREATE EXTENSION timescaledb;'\n        psql -h /tmp smith -c '\\i ${{ github.workspace }}/tsl/test/shared/sql/include/shared_setup.sql'\n        psql -h /tmp smith -c '\\i ${{ github.workspace }}/tsl/test/shared/sql/include/cagg_compression_setup.sql'\n\n    # we run these in a loop to reinitialize the random number generator\n    # 10000 queries seems to take roughly 4 minutes in CI\n    # so total runtime should be around 200 minutes in nightly run and 40 minutes otherwise\n    - name: Run SQLsmith\n      run: |\n        set -xeu\n        set -o pipefail\n        if [ \"${{ github.event_name }}\" == \"schedule\" ]; then\n          LOOPS=20\n        else\n          LOOPS=10\n        fi\n        cd sqlsmith\n        for _ in $(seq 1 $LOOPS)\n        do\n            ./sqlsmith --seed=$((16#$(openssl rand -hex 3))) --exclude-catalog \\\n                --target=\"host=/tmp dbname=smith\" --max-queries=10000 \\\n            2>&1 | tee -a sqlsmith.log\n\n            psql \"host=/tmp dbname=smith\" -c \"select 1\"\n        done\n\n    - name: Check for coredumps\n      if: always()\n      id: collectlogs\n      run: |\n        # wait for in progress coredumps\n        sleep 10\n        if coredumpctl list; then\n          echo \"coredumps=true\" >>$GITHUB_OUTPUT\n          false\n        fi\n\n    - name: Stack trace\n      if: always() && steps.collectlogs.outputs.coredumps == 'true'\n      run: |\n        sudo coredumpctl gdb <<<\"\n          set verbose on\n          set trace-commands on\n          show debug-file-directory\n          printf \"'\"'\"query = '%s'\\n\\n\"'\"'\", (char *) debug_query_string\n          frame function ExceptionalCondition\n          printf \"'\"'\"condition = '%s'\\n\"'\"'\", (char *) conditionName\n          up 1\n          l\n          info args\n          info locals\n          bt full\n        \" 2>&1 | tee stacktrace.log\n        ./scripts/bundle_coredumps.sh\n        false\n\n    - name: Upload Coredumps\n      if: always() && steps.collectlogs.outputs.coredumps == 'true'\n      uses: actions/upload-artifact@v4\n      with:\n        name: Coredumps sqlsmith ${{ matrix.os }} PG${{ matrix.pg }}\n        path: coredumps\n\n    - name: Save PostgreSQL log\n      if: always() && steps.collectlogs.outputs.coredumps == 'true'\n      uses: actions/upload-artifact@v4\n      with:\n        name: PostgreSQL log for PG${{ matrix.pg }}\n        path: postgres.*\n\n    - name: Upload test results to the database\n      if: always()\n      env:\n        CI_STATS_DB: ${{ secrets.CI_STATS_DB }}\n        GITHUB_EVENT_NAME: ${{ github.event_name }}\n        GITHUB_REF_NAME: ${{ github.ref_name }}\n        GITHUB_REPOSITORY: ${{ github.repository }}\n        GITHUB_RUN_ATTEMPT: ${{ github.run_attempt }}\n        GITHUB_RUN_ID: ${{ github.run_id }}\n        GITHUB_RUN_NUMBER: ${{ github.run_number }}\n        JOB_STATUS: ${{ job.status }}\n      run: |\n        if [[ \"${{ github.event_name }}\" == \"pull_request\" ]] ;\n        then\n            GITHUB_PR_NUMBER=\"${{ github.event.number }}\"\n        else\n            GITHUB_PR_NUMBER=0\n        fi\n        export GITHUB_PR_NUMBER\n        scripts/upload_ci_stats.sh\n"
  },
  {
    "path": ".github/workflows/stalebot.yaml",
    "content": "name: 'Close stale issues and PRs'\n\"on\":\n  schedule:\n    - cron: '30 1 * * *'\n  workflow_dispatch:\n  pull_request:\n    paths: .github/workflows/stalebot.yaml\n\njobs:\n  stale:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/stale@v10\n        with:\n          stale-issue-message: \"\n            Dear Author,\n            \\n\\n\n            This issue has been automatically marked as stale due to lack of\n            activity. With only the issue description that is currently\n            provided, we do not have enough information to take action. If you\n            have or find the answers we would need, please reach out. Otherwise,\n            this issue will be closed in 30 days.\n            \\n\\n\n            Thank you!\n            \"\n\n          close-issue-message: \"\n            Dear Author,\n            \\n\\n\n            We are closing this issue due to lack of activity. Feel free to add\n            a comment to this issue if you can provide more information and we\n            will re-open it.\n            \\n\\n\n            Thank you!\n            \"\n\n          stale-pr-message: \"\n            This pull request has been automatically marked as stale due to lack of\n            activity. This pull request will be closed in 30 days.\n            \"\n\n          close-pr-message: \"\n            We are closing this pull request due to lack of activity.\n            \"\n\n          days-before-stale: 30\n          days-before-close: 30\n\n          stale-issue-label: 'no-activity'\n          stale-pr-label: 'no-activity'\n\n          close-issue-label: 'closed-by-bot'\n          close-pr-label: 'closed-by-bot'\n\n          # Process only issues that contain the label 'waiting-for-author'\n          only-issue-labels: 'waiting-for-author'\n\n          # Exclude issues with the 'in-progress' label\n          exempt-issue-labels: 'in-progress'\n          exempt-pr-labels: 'in-progress'\n\n          operations-per-run: 100\n\n"
  },
  {
    "path": ".github/workflows/tests-fail-on-old-code.yaml",
    "content": "# This workflow verifies that new tests fail when run against old code.\n# It helps ensure tests are actually testing the new functionality.\nname: Tests should fail on old code\n\n\"on\":\n  pull_request:\n\njobs:\n  verify-tests-fail:\n    name: Verify tests fail on old code\n    runs-on: timescaledb-runner-arm64\n\n    env:\n      PG_SRC_DIR: pgbuild\n      PG_INSTALL_DIR: postgresql\n      CODE_PATHS: src/ tsl/src/ test/src tsl/test/src\n\n    steps:\n    - name: Checkout TimescaleDB\n      uses: actions/checkout@v4\n      with:\n        fetch-depth: 0\n\n    - name: Set PostgreSQL version\n      id: pg\n      run: |\n        MAJOR=17\n        echo \"major=$MAJOR\" >> $GITHUB_OUTPUT\n\n        # Choose the latest version from config that matches our major version.\n        # This weird quote handling in sed is to remove the quotes around the\n        # value that the config reader produces, i.e. version=\"17.7\".\n        GITHUB_OUTPUT=/dev/stdout .github/gh_config_reader.py \\\n          | sed -n \"s/PG${MAJOR}_LATEST=\\\"\\([^\\\"]*\\)\\\"/version=\\1/p\" \\\n          >> $GITHUB_OUTPUT\n\n    - name: Check for code and test changes\n      id: check\n      run: |\n        git fetch origin ${{ github.base_ref }}:base\n\n        echo \"Looking at PR event ref $(git rev-parse @) vs base $(git rev-parse base)\"\n\n        # Code changes: anything under src/ directories\n        readarray -t CODE_CHANGES < <(git diff --name-only base HEAD -- $CODE_PATHS)\n\n        if ! ((${#CODE_CHANGES[@]}))\n        then\n          echo \"No code changes found, skipping\"\n          echo \"should_run=false\" >> $GITHUB_OUTPUT\n          exit 0\n        fi\n\n        # Test changes: changed .out files under test/expected directories.\n        # For versioned test outputs, we should only check the version that\n        # matches the Postgers version we build.\n        readarray -t CHANGED_TESTS < <( \\\n            git diff --name-only base HEAD -- test/expected test/isolation/expected \\\n                tsl/test/expected tsl/test/isolation/expected tsl/test/shared/expected \\\n            | sed -n 's!^.*expected/\\([^/ -]\\+\\(-${{ steps.pg.outputs.major }}\\)\\?\\)\\.out$!\\1!gp')\n\n        if ! ((${#CHANGED_TESTS[@]}))\n        then\n          echo \"No test output changes found, skipping\"\n          echo \"should_run=false\" >> $GITHUB_OUTPUT\n          exit 0\n        fi\n\n        echo \"Changed tests: ${CHANGED_TESTS[@]}\"\n        echo \"should_run=true\" >> $GITHUB_OUTPUT\n        echo \"changed_tests=${CHANGED_TESTS[@]}\" >> $GITHUB_OUTPUT\n\n    - name: Install Linux Dependencies\n      if: steps.check.outputs.should_run == 'true'\n      run: |\n        sudo apt-get update\n        sudo apt-get install flex bison cmake\n\n    # We are going to rebuild Postgres daily, so that it doesn't suddenly break\n    # ages after the original problem.\n    - name: Get date for build caching\n      if: steps.check.outputs.should_run == 'true'\n      id: get-date\n      run: |\n        echo \"date=$(date +\"%d\")\" >> $GITHUB_OUTPUT\n\n    # we cache the build directory instead of the install directory here\n    # because extension installation will write files to install directory\n    # leading to a tainted cache\n    - name: Cache PostgreSQL ${{ steps.pg.outputs.version }}\n      if: steps.check.outputs.should_run == 'true'\n      id: cache-postgresql\n      uses: actions/cache@v4\n      with:\n        path: ~/${{ env.PG_SRC_DIR }}\n        key: \"pg-${{ steps.pg.outputs.version }}-${{ runner.os }}-${{ runner.arch }}-${{ steps.get-date.outputs.date }}-${{ hashFiles('.github/**') }}\"\n\n    - name: Build PostgreSQL ${{ steps.pg.outputs.version }}\n      if: steps.check.outputs.should_run == 'true' && steps.cache-postgresql.outputs.cache-hit != 'true'\n      run: |\n        wget -q -O postgresql.tar.bz2 \\\n          https://ftp.postgresql.org/pub/source/v${{ steps.pg.outputs.version }}/postgresql-${{ steps.pg.outputs.version }}.tar.bz2\n        mkdir -p ~/$PG_SRC_DIR\n        tar --extract --file postgresql.tar.bz2 --directory ~/$PG_SRC_DIR --strip-components 1\n        cd ~/$PG_SRC_DIR\n        ./configure --prefix=$HOME/$PG_INSTALL_DIR --with-openssl \\\n          --without-readline --without-zlib --without-libxml\n        make -j $(getconf _NPROCESSORS_ONLN)\n\n    - name: Install PostgreSQL ${{ steps.pg.outputs.version }}\n      if: steps.check.outputs.should_run == 'true'\n      run: |\n        cd ~/$PG_SRC_DIR\n        make install\n        echo \"$HOME/$PG_INSTALL_DIR/bin\" >> $GITHUB_PATH\n\n    - name: Revert code changes\n      if: steps.check.outputs.should_run == 'true'\n      run: |\n        git checkout base -- $CODE_PATHS\n        git status\n\n    - name: Build TimescaleDB\n      if: steps.check.outputs.should_run == 'true'\n      run: |\n        cmake -B build -S . \\\n          -DCMAKE_BUILD_TYPE=Debug \\\n          -DPG_SOURCE_DIR=$HOME/$PG_SRC_DIR \\\n          -DPG_PATH=$HOME/$PG_INSTALL_DIR\n        make -j $(getconf _NPROCESSORS_ONLN) -C build\n        make -C build install\n\n    - name: make installcheck\n      if: steps.check.outputs.should_run == 'true'\n      run: |\n        TESTS=\"${{ steps.check.outputs.changed_tests }}\"\n        echo \"Running tests: $TESTS\"\n\n        # Run all changed tests together, allow failure\n        make -C build -k installcheck TESTS=\"$TESTS\" 2>&1 | tee installcheck.log || true\n\n    - name: Show regression diffs\n      if: always() && steps.check.outputs.should_run == 'true'\n      id: collectlogs\n      run: |\n        find . -name regression.diffs -exec cat {} + > regression.log\n        if [[ -s regression.log ]]; then echo \"regression_diff=true\" >> $GITHUB_OUTPUT; fi\n        grep -e 'FAILED' -e 'failed (ignored)' -e 'not ok' installcheck.log || true\n        cat regression.log\n\n    - name: Save regression diffs\n      if: always() && steps.collectlogs.outputs.regression_diff == 'true'\n      uses: actions/upload-artifact@v4\n      with:\n        name: Regression diff PG${{ steps.pg.outputs.version }}\n        path: |\n          regression.log\n          installcheck.log\n\n    - name: Save PostgreSQL log\n      if: always() && steps.check.outputs.should_run == 'true'\n      uses: actions/upload-artifact@v4\n      with:\n        name: PostgreSQL log ${{ steps.pg.outputs.version }}\n        path: postmaster.*\n\n    - name: Verify tests failed on old code\n      if: steps.check.outputs.should_run == 'true'\n      run: |\n        TESTS=\"${{ steps.check.outputs.changed_tests }}\"\n\n        # Here we switch on results because there can be variant expected files\n        # using a _NNN suffix (pg_regress feature).\n        readarray -t FAILED_TESTS < <( \\\n          sed -n 's!^\\+\\+\\+.*results/\\([^/ -]\\+\\(-${{ steps.pg.outputs.major }}\\)\\?\\)\\.out[[:space:]].*$!\\1!gp' regression.log)\n        echo \"Tests failing with the old code: ${FAILED_TESTS[@]}\"\n\n        # Check which changed tests did NOT fail\n        PASSED_TESTS=\"\"\n        for TEST in $TESTS; do\n          if ! echo \"${FAILED_TESTS[@]}\" | grep -qw \"$TEST\"; then\n            PASSED_TESTS=\"$PASSED_TESTS $TEST\"\n          fi\n        done\n\n        if [[ -n \"$PASSED_TESTS\" ]]; then\n          echo \"WARNING: The following changed tests PASSED when they should have FAILED:\"\n          echo \" $PASSED_TESTS\"\n          echo \"\"\n          echo \"This suggests these tests may not be testing the new code changes.\"\n          echo \"Please verify that your tests actually exercise the new functionality.\"\n          exit 1\n        fi\n\n        echo \"All changed tests failed as expected.\"\n"
  },
  {
    "path": ".github/workflows/trigger-package-tests.yaml",
    "content": "# Trigger the Pre-Release tests\n#\n# Params:\n#   ref: branch, tag or SHA to checkout, defaults to `main`\n# \nname: Trigger Packaging tests\non:\n  workflow_dispatch:\n    inputs:\n      ref:\n        description: \"branch, tag or SHA to checkout\"\n        required: true\n        default: \"main\"\n\njobs:\n  trigger_tests:\n    runs-on: ubuntu-latest\n    steps:\n\n    - name: Checkout TimescaleDB\n      uses: actions/checkout@v4\n      with: \n        ref: ${{ github.event.inputs.ref }}\n        token: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n    \n    - name: Push to package_test branch\n      env:\n        GH_TOKEN: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n      run: |\n        git push origin HEAD:trigger/package_test --force\n"
  },
  {
    "path": ".github/workflows/trigger-prerelease-tests.yaml",
    "content": "# Trigger the Pre-Release tests\n#\n# Params:\n#   ref: branch, tag or SHA to checkout, defaults to `main`\n# \nname: Trigger Pre-Release tests\non:\n  workflow_dispatch:\n    inputs:\n      ref:\n        description: \"branch, tag or SHA to checkout\"\n        required: true\n        default: \"main\"\n\njobs:\n  trigger_tests:\n    runs-on: ubuntu-latest\n    steps:\n\n    - name: Checkout TimescaleDB\n      uses: actions/checkout@v4\n      with: \n        ref: ${{ github.event.inputs.ref }}\n        token: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n    \n    - name: Push to pre-release branch\n      env:\n        GH_TOKEN: ${{ secrets.ORG_AUTOMATION_TOKEN }}\n      run: |\n        git push origin HEAD:prerelease_test --force\n"
  },
  {
    "path": ".github/workflows/update-test.yaml",
    "content": "name: Update and Downgrade\n\"on\":\n  push:\n    branches:\n      - main\n      - ?.*.x\n      - prerelease_test\n  pull_request:\n  workflow_dispatch:\n\njobs:\n  check_paths:\n    runs-on: ubuntu-latest\n    outputs:\n      run_ci: >-\n        ${{ github.event_name == 'push'\n          || (steps.filter.outputs.sql == 'true' && !contains(github.event.pull_request.labels.*.name, 'skip-ci'))\n         }}\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dorny/paths-filter@v3\n        id: filter\n        with:\n          filters: .github/filters.yaml\n\n  update_test:\n    needs: check_paths\n    if: needs.check_paths.outputs.run_ci == 'true'\n    name: Update test PG${{ matrix.pg }}\n    runs-on: 'ubuntu-latest'\n    strategy:\n      matrix:\n        pg: [15, 16, 17, 18]\n      fail-fast: false\n    env:\n      PG_VERSION: ${{ matrix.pg }}\n      POSTGRES_HOST_AUTH_METHOD: trust\n    steps:\n    - name: Checkout TimescaleDB\n      uses: actions/checkout@v4\n\n    - name: Install Dependencies\n      run: |\n        sudo apt-get update\n        sudo apt-get install gnupg systemd-coredump gdb postgresql-common libkrb5-dev\n        yes | sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh\n        echo \"deb https://packagecloud.io/timescale/timescaledb/ubuntu/ $(lsb_release -c -s) main\" | sudo tee /etc/apt/sources.list.d/timescaledb.list\n        wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | sudo apt-key add -\n        sudo apt-get update\n        sudo apt-get install postgresql-${{ matrix.pg }} postgresql-server-dev-${{ matrix.pg }}\n        sudo apt-get install -y --no-install-recommends timescaledb-2-postgresql-${{ matrix.pg }}\n        git fetch --tags\n\n    - name: Update tests PG${{ matrix.pg }}\n      run: |\n        PATH=\"/usr/lib/postgresql/${{ matrix.pg }}/bin:$PATH\"\n        ./scripts/test_updates.sh\n\n    - name: Downgrade tests PG${{ matrix.pg }}\n      if: always()\n      run: |\n        PATH=\"/usr/lib/postgresql/${{ matrix.pg }}/bin:$PATH\"\n        ./scripts/test_downgrade.sh\n\n    - name: Update diff\n      if: failure()\n      run: |\n        find update_test -name \"*.diff\" | xargs -IFILE sh -c \"echo '\\nFILE\\n';cat FILE\"\n\n    - name: Postgres Errors\n      if: failure()\n      run: |\n        find update_test -name postgres.log -exec grep ERROR {} \\;\n\n    - name: Check for coredumps\n      if: failure()\n      id: collectlogs\n      run: |\n        # wait for in progress coredumps\n        sleep 10\n        if coredumpctl list; then\n          echo \"coredumps=true\" >>$GITHUB_OUTPUT\n          false\n        fi\n\n    - name: Stack trace\n      if: always() && steps.collectlogs.outputs.coredumps == 'true'\n      run: |\n        sudo coredumpctl gdb <<<\"\n          set verbose on\n          set trace-commands on\n          show debug-file-directory\n          printf \"'\"'\"query = '%s'\\n\\n\"'\"'\", (char *) debug_query_string\n          frame function ExceptionalCondition\n          printf \"'\"'\"condition = '%s'\\n\"'\"'\", (char *) conditionName\n          up 1\n          l\n          info args\n          info locals\n          bt full\n        \" 2>&1 | tee stacktrace.log\n        ./scripts/bundle_coredumps.sh\n        false\n\n    - name: Upload Coredumps\n      if: always() && steps.collectlogs.outputs.coredumps == 'true'\n      uses: actions/upload-artifact@v4\n      with:\n        name: Coredumps update-test ${{ matrix.os }} PG${{ matrix.pg }}\n        path: coredumps\n\n    - name: Upload Artifacts\n      if: always()\n      uses: actions/upload-artifact@v4\n      with:\n        name: Update test PG${{ matrix.pg }}\n        path: update_test\n\n  report_status:\n    name: Update and Downgrade summary\n    needs: [check_paths, update_test]\n    if: always()\n    runs-on: ubuntu-latest\n    steps:\n      - run: |\n          # If the detector failed, or the matrix failed, exit with error\n          if [[ \"${{ needs.update_test.result }}\" == \"failure\" ]]; then\n            exit 1\n          fi\n          echo \"All checks passed or were safely skipped.\"\n\n"
  },
  {
    "path": ".github/workflows/windows-build-and-test.yaml",
    "content": "# Test building the extension on Windows\nname: Regression Windows\n\"on\":\n  schedule:\n    # run daily 0:00 on main branch\n    - cron: '0 0 * * *'\n  push:\n    branches:\n      - main\n      - ?.*.x\n      - prerelease_test\n      - trigger/windows_tests\n      - trigger/regression\n  pull_request:\n  workflow_dispatch:\njobs:\n  config:\n    runs-on: ubuntu-latest\n    outputs:\n      build_type: ${{ steps.build_type.outputs.build_type }}\n      pg15_latest: ${{ steps.config.outputs.pg15_latest }}\n      pg16_latest: ${{ steps.config.outputs.pg16_latest }}\n      pg17_latest: ${{ steps.config.outputs.pg17_latest }}\n      pg18_latest: ${{ steps.config.outputs.pg18_latest }}\n\n    steps:\n    - name: Checkout source code\n      uses: actions/checkout@v4\n    - name: Read configuration\n      id: config\n      run: python .github/gh_config_reader.py\n    - name: Set build_type\n      id: build_type\n      run: |\n        if [[ \"${{ github.event_name }}\" == \"pull_request\" ]]; then\n          echo \"build_type=['Debug']\" >>$GITHUB_OUTPUT\n        else\n          echo \"build_type=['Debug','Release']\" >>$GITHUB_OUTPUT\n        fi\n\n  check_paths:\n    runs-on: ubuntu-latest\n    outputs:\n      run_ci: >-\n        ${{ github.event_name == 'push'\n          || (steps.filter.outputs.src == 'true' && !contains(github.event.pull_request.labels.*.name, 'skip-ci'))\n         }}\n      run_installcheck: ${{ github.event_name == 'push' || steps.filters.outputs.windows-workflow == 'true' }}\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dorny/paths-filter@v3\n        id: filter\n        with:\n          filters: .github/filters.yaml\n\n  build:\n    # Change the JOB_NAME variable below when changing the name.\n    name: PG${{ matrix.versions.pg }} ${{ matrix.build_type }} windows\n    runs-on: windows-2025\n    needs: [config, check_paths]\n    if: needs.check_paths.outputs.run_ci == 'true'\n    strategy:\n      fail-fast: false\n      matrix:\n        versions:\n          - { pg: 15, pg_version: \"${{ needs.config.outputs.pg15_latest }}\" }\n          - { pg: 16, pg_version: \"${{ needs.config.outputs.pg16_latest }}\" }\n          - { pg: 17, pg_version: \"${{ needs.config.outputs.pg17_latest }}\" }\n          - { pg: 18, pg_version: \"${{ needs.config.outputs.pg18_latest }}\" }\n        build_type: ${{ fromJson(needs.config.outputs.build_type) }}\n        pg_config: [\"-cfsync=off -cstatement_timeout=60s\"]\n        event_name: [\"${{ github.event_name }}\"]\n        exclude:\n          - versions: { pg: 15, pg_version: \"${{ needs.config.outputs.pg15_latest }}\" }\n            event_name: \"pull_request\"\n          - versions: { pg: 16, pg_version: \"${{ needs.config.outputs.pg16_latest }}\" }\n            event_name: \"pull_request\"\n    env:\n      # PostgreSQL configuration\n      PGPORT: 55432\n      PGDATA: pgdata\n      TABLESPACE1: D:\\tablespace1\\\n      TABLESPACE2: D:\\tablespace2\\\n      IGNORES: >-\n        bgw_launcher\n        chunk_adaptive\n        compress_bloom_sparse_compat\n        compress_bloom_sparse_debug\n        compress_composite_bloom_debug\n        compress_qualpushdown_saop\n        compress_sort_transform\n        compression_algos\n        compression_uuid\n        direct_compress_copy\n        merge_append_partially_compressed\n        metadata\n        telemetry\n        vector_agg_expr\n        vector_agg_byte\n        vector_agg_filter\n        vector_agg_groupagg\n        vector_agg_grouping\n        vector_agg_planning-*\n        vector_agg_text\n        vector_agg_uuid\n        vectorized_aggregation\n        ${{ matrix.versions.pg < 16 && 'columnar_scan_cost' || '' }}\n      SKIPS: >-\n        create_hypertable\n        create_table_with\n        bgw_db_scheduler\n        bgw_db_scheduler_fixed\n    steps:\n\n    - name: Remove Existing PostgreSQL\n      shell: powershell\n      run: |\n        # Search for any installed application package containing 'PostgreSQL' in the name.\n        $PostgresPackages = Get-Package -Name \"*PostgreSQL*\" -Provider Programs -ErrorAction SilentlyContinue\n        if ($PostgresPackages) {\n            Write-Host \"Found $($PostgresPackages.Count) PostgreSQL package(s) to uninstall.\"\n            # Use Uninstall-Package with -Force for a silent, non-interactive removal.\n            $PostgresPackages | Uninstall-Package -Force\n            # Manually remove common installation paths and data folders\n            # to ensure no conflicts with the new installation.\n            Remove-Item \"C:\\Program Files\\PostgreSQL\" -Recurse -Force -ErrorAction SilentlyContinue\n            Remove-Item \"C:\\PostgreSQL\" -Recurse -Force -ErrorAction SilentlyContinue\n        } else {\n            Write-Host \"No registered PostgreSQL installation found. Skipping uninstall step.\"\n        }\n\n    - name: Setup WSL\n      uses: Vampire/setup-wsl@v3.1.4\n      with:\n        distribution: Debian\n\n    - name: Configure apt retries and timeout in WSL\n      shell: wsl-bash {0}\n      run: |\n        echo 'Acquire::Retries \"10\";' >> /etc/apt/apt.conf.d/99retries\n        echo 'Acquire::http::Timeout \"240\";' >> /etc/apt/apt.conf.d/99retries\n        echo 'Acquire::https::Timeout \"240\";' >> /etc/apt/apt.conf.d/99retries\n\n    - name: Install additional packages in WSL\n      shell: wsl-bash {0}\n      run: |\n        # Workaround for https://github.com/Vampire/setup-wsl/issues/76\n        sed -i 's#ftp.debian.org/debian bullseye-backports#archive.debian.org/debian bullseye-backports#' /etc/apt/sources.list\n        apt-get update\n        apt-get install --yes --no-install-recommends cmake gawk gcc git gnupg jq make postgresql-client postgresql-common tree\n\n    - name: Configure git\n      # Since we want to reuse the checkout in the WSL environment\n      # we have to prevent git from changing the line ending in the\n      # shell scripts as that would break them.\n      run: |\n        git config --global core.autocrlf false\n        git config --global core.eol lf\n\n    - name: Checkout TimescaleDB source\n      uses: actions/checkout@v4\n\n    # We are going to rebuild Postgres daily, so that it doesn't suddenly break\n    # ages after the original problem.\n    - name: Get date for build caching\n      id: get-date\n      env:\n        WSLENV: GITHUB_OUTPUT/p\n      shell: wsl-bash {0}\n      run: |\n        echo \"date=$(date +\"%d\")\" >> $GITHUB_OUTPUT\n\n      # Use a cache for the PostgreSQL installation to speed things up\n      # and avoid unnecessary package downloads. Since we only save\n      # the directory containing the binaries, the runs with a cache\n      # hit won't have PostgreSQL installed with a running service\n      # since the installer never runs. We therefore install with\n      # --extract-only and launch our own test instance, which is\n      # probably better anyway since it gives us more control.\n    - name: Cache PostgreSQL installation\n      uses: actions/cache@v4\n      id: cache-postgresql\n      with:\n        path: C:\\Progra~1\\PostgreSQL\\${{ matrix.versions.pg }}\n        key: \"${{ runner.os }}-build-pg${{ matrix.pkg_version }}\\\n          -${{ steps.get-date.outputs.date }}-${{ hashFiles('.github/**') }}\"\n\n    - name: Install PostgreSQL ${{ matrix.versions.pg }} (using ${{ matrix.versions.pg_version }})\n      if: github.event_name != 'schedule' && steps.cache-postgresql.outputs.cache-hit != 'true'\n      run: |\n        $version = ${{ matrix.versions.pg_version }}\n        $version = $version.Replace('\"', '') + \"-1\"\n        winget install --id PostgreSQL.PostgreSQL.${{ matrix.versions.pg }} --version $version --force --accept-source-agreements --accept-package-agreements --disable-interactivity\n\n    # This is for nightly builds. Here we pick the latest version of the package.\n    - name: Install PostgreSQL ${{ matrix.versions.pg }}\n      if: github.event_name == 'schedule' && steps.cache-postgresql.outputs.cache-hit != 'true'\n      run: |\n        winget install --id PostgreSQL.PostgreSQL.${{ matrix.versions.pg }} --force --accept-source-agreements --accept-package-agreements --disable-interactivity\n\n    - name: Configure TimescaleDB\n      run: cmake -B build_win -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} `\n        -DPG_CONFIG=C:\\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/pg_config `\n        -DASSERTIONS=ON `\n        -DTEST_PG_LOG_DIRECTORY=\"log\"\n\n    - name: Build TimescaleDB\n      run: cmake --build build_win --config ${{ matrix.build_type }}\n\n    - name: Install TimescaleDB\n      run: cmake --install build_win --config ${{ matrix.build_type }}\n\n    - name: Enable crash dumps for postgres.exe (WER LocalDumps)\n      if: needs.check_paths.outputs.run_installcheck == 'true'\n      shell: powershell\n      run: |\n        $DumpDir = Join-Path $env:GITHUB_WORKSPACE \"crashdumps\"\n        New-Item -ItemType Directory -Force -Path $DumpDir | Out-Null\n\n        $Key = \"HKLM:\\SOFTWARE\\Microsoft\\Windows\\Windows Error Reporting\\LocalDumps\\postgres.exe\"\n        New-Item -Path $Key -Force | Out-Null\n        New-ItemProperty -Path $Key -Name DumpFolder -Value $DumpDir -PropertyType ExpandString -Force | Out-Null\n        New-ItemProperty -Path $Key -Name DumpCount -Value 10 -PropertyType DWord -Force | Out-Null\n        New-ItemProperty -Path $Key -Name DumpType -Value 2 -PropertyType DWord -Force | Out-Null\n\n        Write-Host \"Configured WER LocalDumps for postgres.exe => $DumpDir\"\n\n    - name: Setup postgres cluster\n      if: needs.check_paths.outputs.run_installcheck == 'true'\n      run: |\n        C:\\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/initdb -U postgres -A trust --locale=en_US --encoding=UTF8\n        mkdir -p ${{ env.TABLESPACE1 }}\\_default\n        mkdir -p ${{ env.TABLESPACE2 }}\\_default\n        icacls ${{ env.TABLESPACE1 }} /grant runneradmin:F /T\n        icacls ${{ env.TABLESPACE2 }} /grant runneradmin:F /T\n        copy build_win/test/postgresql.conf ${{ env.PGDATA }}\n        icacls . /grant runneradmin:F /T\n        C:\\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/pg_ctl start -o \"${{ matrix.pg_config }}\" --log=postmaster.log\n        C:\\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/pg_isready -U postgres -d postgres --timeout=60\n        C:\\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/psql -U postgres -d postgres -c 'CREATE USER root SUPERUSER LOGIN;'\n        echo \"PG version:\"\n        C:\\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/psql -U postgres -d postgres -c 'SELECT version();'\n        echo \"Log configuration:\"\n        C:\\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/psql -U postgres -d postgres -c 'SHOW logging_collector;'\n        C:\\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/psql -U postgres -d postgres -c 'SHOW log_filename;'\n        C:\\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/psql -U postgres -d postgres -c 'SHOW log_directory;'\n        C:\\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/psql -U postgres -d postgres -c 'SELECT pg_current_logfile();'\n        echo \"Data directory:\"\n        C:\\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/psql -U postgres -d postgres -c 'SHOW data_directory;'\n\n    - name: Install postgres for test runner\n      if: needs.check_paths.outputs.run_installcheck == 'true'\n      shell: wsl-bash {0}\n      run: |\n        yes | /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh || true\n        echo 'Acquire::Retries \"10\";' >> /etc/apt/apt.conf.d/99retries\n        echo 'Acquire::http::Timeout \"240\";' >> /etc/apt/apt.conf.d/99retries\n        echo 'Acquire::https::Timeout \"240\";' >> /etc/apt/apt.conf.d/99retries\n        apt-get install -y --force-yes postgresql-server-dev-${{ matrix.versions.pg }}\n\n    - name: Run tests\n      if: needs.check_paths.outputs.run_installcheck == 'true'\n      shell: wsl-bash {0}\n      env:\n        WSLENV: \"IGNORES:SKIPS\"\n      run: |\n        export TEST_TABLESPACE1_PREFIX='${{ env.TABLESPACE1 }}'\n        export TEST_TABLESPACE2_PREFIX='${{ env.TABLESPACE2 }}'\n        cmake -B build_wsl -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DTEST_PGPORT_LOCAL=${{ env.PGPORT }}\n\n        make -C build_wsl isolationchecklocal | tee -a installcheck.log\n        make -C build_wsl regresschecklocal IGNORES=\"${IGNORES}\" SKIPS=\"${SKIPS}\" | tee -a installcheck.log\n\n    - name: Setup postgres cluster for TSL tests\n      if: needs.check_paths.outputs.run_installcheck == 'true'\n      run: |\n        C:\\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/pg_ctl stop\n        timeout 10\n        Remove-Item -Recurse ${{ env.PGDATA }}\n        C:\\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/initdb -U postgres -A trust --locale=en_US --encoding=UTF8\n        copy build_win/tsl/test/postgresql.conf ${{ env.PGDATA }}\n        C:\\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/pg_ctl start -o \"${{ matrix.pg_config }}\" --log=\"postmaster.log\"\n        C:\\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/pg_isready -U postgres -d postgres --timeout=30\n        C:\\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/psql -U postgres -d postgres -c 'CREATE USER root SUPERUSER LOGIN;'\n\n    - name: Run TSL tests\n      if: needs.check_paths.outputs.run_installcheck == 'true'\n      shell: wsl-bash {0}\n      env:\n        WSLENV: \"IGNORES:SKIPS\"\n      run: |\n        export TEST_TABLESPACE1_PREFIX='${{ env.TABLESPACE1 }}'\n        export TEST_TABLESPACE2_PREFIX='${{ env.TABLESPACE2 }}'\n\n        make -C build_wsl isolationchecklocal-t | tee -a installcheck.log\n        make -C build_wsl -k regresschecklocal-t IGNORES=\"${IGNORES}\" SKIPS=\"${SKIPS}\" | tee -a installcheck.log\n\n    - name: Show regression diffs\n      id: collectlogs\n      if: always()\n      env:\n        WSLENV: GITHUB_OUTPUT/p\n      shell: wsl-bash {0}\n      run: |\n        find . -name regression.diffs -exec cat {} + > regression.log\n        if [[ -s regression.log ]]; then echo \"regression_diff=true\" >>$GITHUB_OUTPUT; fi\n        grep -e 'FAILED' -e 'failed (ignored)' -e 'not ok' installcheck.log || true\n        cat regression.log\n\n    - name: Extract stack traces from crash dumps\n      if: always()\n      shell: powershell\n      run: |\n        $DumpDir = Join-Path $env:GITHUB_WORKSPACE \"crashdumps\"\n        if (!(Test-Path $DumpDir)) { Write-Host \"No crashdumps directory found.\"; exit 0 }\n\n        $Dumps = Get-ChildItem -Path $DumpDir -Filter \"*.dmp\" -Recurse -ErrorAction SilentlyContinue\n        if (!$Dumps) { Write-Host \"No .dmp files found in $DumpDir.\"; exit 0 }\n\n        $SymbolCache = Join-Path $DumpDir \"symbols\"\n        New-Item -ItemType Directory -Force -Path $SymbolCache | Out-Null\n\n        $Out = Join-Path $DumpDir \"stacktraces.txt\"\n        foreach ($Dump in $Dumps) {\n          Add-Content -Path $Out -Value (\"==== \" + $Dump.FullName + \" ====\")\n          & cdb.exe -z $Dump.FullName -c \".symfix $SymbolCache; .reload; !analyze -v; kv; q\" | Out-File -FilePath $Out -Append -Encoding utf8\n        }\n\n    - name: Upload crash dumps and stack traces\n      if: always()\n      uses: actions/upload-artifact@v4\n      with:\n        name: Crash dumps PG${{ matrix.versions.pg }} ${{ matrix.build_type }} windows\n        path: |\n          crashdumps\\**\n        if-no-files-found: ignore\n\n    - name: Save regression diffs\n      if: always() && steps.collectlogs.outputs.regression_diff == 'true'\n      uses: actions/upload-artifact@v4\n      with:\n        name: Regression ${{ matrix.versions.pg }} diff ${{ matrix.os }} ${{ matrix.build_type }} Build\n        path: |\n          regression.log\n          installcheck.log\n\n    - name: Save PostgreSQL log\n      if: always()\n      uses: actions/upload-artifact@v4\n      with:\n        name: PostgreSQL ${{ matrix.versions.pg }} log ${{ matrix.os }} ${{ matrix.build_type }} Build\n        path: ${{ env.PGDATA }}\\log\\postmaster.log\n\n    - name: Upload CMake Logs\n      if: always()\n      uses: actions/upload-artifact@v4\n      with:\n        name: CMake Logs ${{ matrix.versions.pg }} ${{ matrix.os }} ${{ matrix.build_type }}\n        path: |\n            build_win/CMakeCache.txt\n            build_win/CMakeFiles/CMakeConfigureLog.yaml\n            build_win/CMakeFiles/CMakeError.log\n            build_win/CMakeFiles/CMakeOutput.log\n            build_win/compile_commands.json\n            build_wsl/CMakeCache.txt\n            build_wsl/CMakeFiles/CMakeConfigureLog.yaml\n            build_wsl/CMakeFiles/CMakeError.log\n            build_wsl/CMakeFiles/CMakeOutput.log\n            build_wsl/compile_commands.json\n\n    - name: Upload test results to the database\n      if: always()\n      shell: wsl-bash {0}\n      env:\n        # Update when adding new variables.\n        WSLENV: \"JOB_NAME:CI_STATS_DB:GITHUB_EVENT_NAME:GITHUB_REF_NAME\\\n          :GITHUB_REPOSITORY:GITHUB_RUN_ATTEMPT:GITHUB_RUN_ID:GITHUB_RUN_NUMBER:JOB_STATUS\"\n\n        # GitHub Actions allow you neither to use the env context for the job name,\n        # nor to access the job name from the step context, so we have to\n        # duplicate it to work around this nonsense.\n        JOB_NAME: PG${{ matrix.versions.pg }} ${{ matrix.build_type }} ${{ matrix.os }}\n        CI_STATS_DB: ${{ secrets.CI_STATS_DB }}\n        GITHUB_EVENT_NAME: ${{ github.event_name }}\n        GITHUB_REF_NAME: ${{ github.ref_name }}\n        GITHUB_REPOSITORY: ${{ github.repository }}\n        GITHUB_RUN_ATTEMPT: ${{ github.run_attempt }}\n        GITHUB_RUN_ID: ${{ github.run_id }}\n        GITHUB_RUN_NUMBER: ${{ github.run_number }}\n        JOB_STATUS: ${{ job.status }}\n      run: |\n        if [[ \"${{ github.event_name }}\" == \"pull_request\" ]] ;\n        then\n            GITHUB_PR_NUMBER=\"${{ github.event.number }}\"\n        else\n            GITHUB_PR_NUMBER=0\n        fi\n        export GITHUB_PR_NUMBER\n        scripts/upload_ci_stats.sh\n\n  report_status:\n    name: Regression Windows summary\n    needs: [check_paths, build]\n    if: always()\n    runs-on: ubuntu-latest\n    steps:\n      - run: |\n          # If the detector failed, or the matrix failed, exit with error\n          if [[ \"${{ needs.build.result }}\" == \"failure\" ]]; then\n            exit 1\n          fi\n          echo \"All checks passed or were safely skipped.\"\n"
  },
  {
    "path": ".github/workflows/windows-packages.yaml",
    "content": "# Test installation of windows package for latest version\nname: \"Packaging tests: Windows\"\n\"on\":\n  schedule:\n    # run daily 0:00 on main branch\n    - cron: '0 0 * * *'\n  pull_request:\n    paths: .github/workflows/windows-packages.yaml\n  push:\n    tags:\n    - '*'\n    branches:\n    - release_test\n    - trigger/windows_packages\n  workflow_dispatch:\n\njobs:\n\n  build:\n    name: Windows package PG${{ matrix.pg }}\n    runs-on: windows-2025\n    strategy:\n      fail-fast: false\n      matrix:\n        pg: [ \"15\", \"16\", \"17\", \"18\" ]\n    env:\n      # PostgreSQL configuration\n      PGPORT: 6543\n      PGDATA: pgdata\n    steps:\n\n    - name: Checkout TimescaleDB source\n      uses: actions/checkout@v4\n\n    - name: Get version\n      id: version\n      env:\n        GH_TOKEN: ${{ github.token }}\n      run: |\n        $version=gh release list --json tagName,isLatest --jq '.[] | select(.isLatest) | .tagName'\n        echo \"Determined version: \"\n        echo \"version=$version\"\n        echo \"version=$version\" >>$env:GITHUB_OUTPUT\n\n    - name: Install PostgreSQL ${{ matrix.pg }}\n      run: |\n        choco feature disable --name=usePackageExitCodes\n        choco feature disable --name=showDownloadProgress\n        choco uninstall postgresql --yes\n        winget install --id PostgreSQL.PostgreSQL.${{ matrix.pg }} --accept-source-agreements --accept-package-agreements --disable-interactivity\n        choco install wget\n\n    - name: Download TimescaleDB\n      run: \"wget --quiet -O timescaledb.zip 'https://github.com/timescale/timescaledb/releases/download/\\\n        ${{ steps.version.outputs.version }}/timescaledb-postgresql-${{ matrix.pg}}-windows-amd64.zip'\"\n\n    - name: Install TimescaleDB\n      run: |\n        tar -xf timescaledb.zip\n        cd timescaledb\n        ./setup.exe -yes-tune -pgconfig C:\\Progra~1\\PostgreSQL\\${{ matrix.pg }}\\bin\\pg_config\n\n    - name: Create DB\n      run: |\n        C:\\Progra~1\\PostgreSQL\\${{ matrix.pg }}\\bin\\initdb -U postgres -A trust\n        C:\\Progra~1\\PostgreSQL\\${{ matrix.pg }}\\bin\\pg_ctl start -o \"-cshared_preload_libraries=timescaledb\"\n\n    - name: Test creating extension\n      run: |\n        C:\\Progra~1\\PostgreSQL\\${{ matrix.pg }}\\bin\\psql -U postgres -d postgres -X `\n          -c \"CREATE EXTENSION timescaledb\" `\n          -c \"SELECT extname,extversion,version() FROM pg_extension WHERE extname='timescaledb'\"\n        $installed_version = C:\\Progra~1\\PostgreSQL\\${{ matrix.pg }}\\bin\\psql -U postgres `\n          -d postgres -qtAX -c \"SELECT extversion FROM pg_extension WHERE extname='timescaledb'\"\n        $installed_version = $installed_version.Trim()\n        echo \"Installed version is '${installed_version}'\"\n        if (\"${installed_version}\" -ne \"${{ steps.version.outputs.version }}\")\n        {\n          false\n        }\n"
  },
  {
    "path": ".gitignore",
    "content": "\\#*#\n.#*\n*~\n**/CMakeFiles/\n**/CMakeCache.txt\n/sql/tests/unit/testoutputs.tmp\n/sql/timescaledb--*.sql\n/sql/pre_install/*.gen\n/sql/updates/*.gen\n/data/\n/src/*.o\n/src/*.so\n/src/*.d\n/.vscode/\n/timescaledb.so\n*.bak\n*.backup\ntypedef.list\n/test/testcluster\n/test/log\n/test/temp_schedule\n/build*\n/update_test\n**/GPATH\n**/GTAGS\n**/GRTAGS\n**/GSYMS\ntags\n/.vs\n/compile_commands.json\n/.DS_Store\n/.clangd\n/.cache\n\n/CMakeSettings.json\n/out/*\n/debug/\n/cmake-build-debug/\n/.idea/\ncoccinelle.diff\n.gdb_history\n"
  },
  {
    "path": ".perltidyrc",
    "content": "--add-whitespace\n--delete-old-whitespace\n--entab-leading-whitespace=4\n--keep-old-blank-lines=2\n--maximum-line-length=78\n--nooutdent-long-comments\n--nooutdent-long-quotes\n--nospace-for-semicolon\n--opening-brace-on-new-line\n--output-line-ending=unix\n--paren-tightness=2\n--paren-vertical-tightness=2\n--paren-vertical-tightness-closing=2\n--noblanks-before-comments\n"
  },
  {
    "path": ".pull-review",
    "content": "---\n# pull-review config version (required)\nversion: 1\n\n# use review requests instead of assignees to assign reviewers to pull requests\nuse_review_requests: true\n\n# maximum number of files to evaluate per pull request (set to 0 for no limit)\nmax_files: 0 # evaluate all files of the PR\n\n# minimum number of reviewers to assign and notify for a pull request\nmin_reviewers: 2\n\n# maximum number of reviewers to assign and notify for a pull request\nmax_reviewers: 2\n\n# maximum number of files per reviewer (set to 0 for no limit)\nmax_files_per_reviewer: 0\n\n# maximum number of lines changed per reviewer (set to 0 for no limit)\nmax_lines_per_reviewer: 0\n\n# if at least a minimum number of reviewers is not found, assign a minimum number of reviewers randomly\nassign_min_reviewers_randomly: true\n\n# if the pull request changes code with fewer than a minimum number of authors, add extra reviewers if possible (set to 0 to disable)\nmin_authors_of_changed_files: 0\n\n# minimum percent of lines authored by a reviewer to require an extra reviewer (set to 0 to disable)\nmin_percent_authorship_for_extra_reviewer: 0\n\n# minimum number of lines that must be changed to add an extra reviewer (set to 0 to disable)\nmin_lines_changed_for_extra_reviewer: 0\n\n# require a user to be listed in the reviewers section in order to be assigned to a pull request\nrequire_notification: true\n\n# list of users and their app-specific usernames\nreviewers:\n  Poroma-Banerjee: {}\n  akuzm: {}\n  antekresic: {}\n  dbeck: {}\n  kpan2034: {}\n  melihmutlu: {}\n  natalya-aksman: {}\n  philkra: {}\n  pnthao: {}\n  svenklemm: {}\n\n# list of users who will never be notified\nreview_blacklist:\n  - timescale-automation\n  - philkra\n\n# ignore changes to the test output files.\n# 1. because they usually will have equivalent .sql files\n# 2. because people that modify a test, don't necessarily change the same c-functionality at all\n# so tests should be less important than C files\nfile_blacklist:\n  - tsl/test/expected/*.out\n  - test/expected/*.out\n\nlabel_blacklist:\n  - is-auto-backport\n"
  },
  {
    "path": ".unreleased/chunk-param",
    "content": "Implements: #9368 Enable runtime chunk exclusion on inner side of nested loop join\n"
  },
  {
    "path": ".unreleased/columnar-function",
    "content": "Implements: #9117 Support functions like `time_bucket` in the columnar aggregation and grouping pipeline.\n"
  },
  {
    "path": ".unreleased/constant-gapfill",
    "content": "Fixes: #7629 Forbid non-constant timezone parameter in time_bucket_gapfill\n"
  },
  {
    "path": ".unreleased/direct-loss",
    "content": "Fixes: #9381 Data loss with direct compress with client-ordered data in an INSERT SELECT from a compressed hypertable.\n"
  },
  {
    "path": ".unreleased/in-any-chunk-exclusion",
    "content": "Implements: #9398 Fix chunk exclusion for IN/ANY on open (time) dimensions\n"
  },
  {
    "path": ".unreleased/parameterized-merge",
    "content": "Fixes: #9356 Potential crash when using a hypertable with partial compression or space partitioning in a nested loop join\n"
  },
  {
    "path": ".unreleased/pr_8983",
    "content": "Implements: #8983 Add GUC for default chunk time interval\n"
  },
  {
    "path": ".unreleased/pr_9142",
    "content": "Implements: #9142 Remove column `dropped` from _timescaledb_catalog.chunk\n"
  },
  {
    "path": ".unreleased/pr_9238",
    "content": "Implements: #9238 Support non-partial aggregates with vectorized aggregation\n"
  },
  {
    "path": ".unreleased/pr_9253",
    "content": "Implements: #9253 Support VectorAgg in subqueries and CTEs\n"
  },
  {
    "path": ".unreleased/pr_9266",
    "content": "Implements: #9266 Add support for HAVING to vectorized aggregation\n"
  },
  {
    "path": ".unreleased/pr_9267",
    "content": "Implements: #9267 Enable ColumnarIndexScan custom scan\n"
  },
  {
    "path": ".unreleased/pr_9312",
    "content": "Implements: #9312 Remove advisory locks from bgw jobs and add graceful cancellation\nThanks: @leppaott for reporting a deadlock when deleting jobs\n"
  },
  {
    "path": ".unreleased/pr_9334",
    "content": "Implements: #9334 Fix out-of-range timestamp error in WHERE clauses\n"
  },
  {
    "path": ".unreleased/pr_9372",
    "content": "Implements: #9372 Push down composite bloom filter checks to SELECT execution.\n"
  },
  {
    "path": ".unreleased/pr_9374",
    "content": "Implements: #9374 Use bloom filters to eliminate decompression of unrelated compressed batches during UPSERTs.\n"
  },
  {
    "path": ".unreleased/pr_9376",
    "content": "Fixes: #9376 Allow CREATE EXTENSION after drop in the same session\nThanks: @janpio for reporting an issue with CREATE EXTENSION after dropping and recreating schema\n"
  },
  {
    "path": ".unreleased/pr_9378",
    "content": "Fixes: #9378 Fix FK constraint failure when inserting into hypertable with referencing FK\nThanks: @bronzinni for reporting an issue with foreign keys on hypertables\n"
  },
  {
    "path": ".unreleased/pr_9382",
    "content": "Implements: #9382 Fix chunk creation failure after replica identity invalidation\n"
  },
  {
    "path": ".unreleased/pr_9399",
    "content": "Implements: #9399 Use bloom filters to reduce decompression during UPDATE/DELETE commands.\n"
  },
  {
    "path": ".unreleased/pr_9413",
    "content": "Fixes: #9413 Fix incorrect decompress markers on full batch delete\n"
  },
  {
    "path": ".unreleased/pr_9414",
    "content": "Fixes: #9414 Fix NULL compression handling in estimate_uncompressed_size\n"
  },
  {
    "path": ".unreleased/pr_9417",
    "content": "Fixes: #9417 Fix segfault in bloom1_contains\n"
  },
  {
    "path": ".unreleased/saop-pushdown",
    "content": "Implements: #9192 Push down scalar array operations into the columnar metadata scan by transforming them into an OR/AND clause.\nSetting: `enable_columnar_scan_filter_pushdown`: enables pushing the filters on columnar scan down to the compressed scan level. On by default.\n"
  },
  {
    "path": ".unreleased/text-minmax",
    "content": "Implements: #9104 Support min(text), max(text) for C collation in columnar aggregation pipeline\n"
  },
  {
    "path": ".unreleased/wrong-partition",
    "content": "Fixes: #9344 Wrong result or crash on cross-type comparison of partitioning column\n"
  },
  {
    "path": ".yamllint.yaml",
    "content": "rules:\n  document-start: disable\n  line-length:\n    max: 250\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# TimescaleDB Changelog\n\n**Please note: When updating your database, you should connect using\nThis page lists all the latest features and updates to TimescaleDB. When \nyou use psql to update your database, use the -X flag and prevent any .psqlrc \ncommands from accidentally triggering the load of a previous DB version.**\n\n## 2.25.2 (2026-03-03)\n\nThis release contains performance improvements and bug fixes since the 2.25.1 release and a fix for a security vulnerability ([#9331](https://github.com/timescale/timescaledb/pull/9331)). You can check the [security advisory](https://github.com/timescale/timescaledb/security/advisories/GHSA-vgp2-jj5c-828m) for more information on the vulnerability and the platforms that are affected. We recommend that you upgrade as soon as possible.\n\n**Bugfixes**\n* [#9276](https://github.com/timescale/timescaledb/pull/9276) Fix `NULL` and `DEFAULT` handling in uniqueness check on compressed chunks\n* [#9277](https://github.com/timescale/timescaledb/pull/9277) Fix SSL-related build errors\n* [#9279](https://github.com/timescale/timescaledb/pull/9279) Fix `EXPLAIN VERBOSE` corrupting targetlist of cached ModifyHypertable plans\n* [#9281](https://github.com/timescale/timescaledb/pull/9281) Fix real-time continuous aggregates on UUID hypertables\n* [#9283](https://github.com/timescale/timescaledb/pull/9283) Fix plan-time error when using `enum` in `orderby` compression setting\n* [#9290](https://github.com/timescale/timescaledb/pull/9290) Propagate `ALTER <object> OWNER TO` to policy jobs\n* [#9292](https://github.com/timescale/timescaledb/pull/9292) Fix continuous aggregate column rename\n* [#9293](https://github.com/timescale/timescaledb/pull/9293) Fix `time_bucket_gapfill` inside `LATERAL` subqueries\n* [#9294](https://github.com/timescale/timescaledb/pull/9294) Fix `DELETE`and `UPDATE` with `WHERE EXISTS` on hypertables\n* [#9303](https://github.com/timescale/timescaledb/pull/9303) Fix segfault in continuous aggregate creation on Postgres 18\n* [#9308](https://github.com/timescale/timescaledb/pull/9308) Fix continuous aggregate offset/origin not applied in watermark and refresh window calculations\n* [#9314](https://github.com/timescale/timescaledb/pull/9314) Fix generated columns always `NULL` in compressed chunks\n* [#9321](https://github.com/timescale/timescaledb/pull/9321) Fix segfault when using OLD/NEW refs in `RETURNING` clause on Postgres 18\n* [#9324](https://github.com/timescale/timescaledb/pull/9324) Potential violation of a foreign key constraint referencing a hypertable caused by concurrent `DELETE` of the key record\n* [#9327](https://github.com/timescale/timescaledb/pull/9327) Fix handling of generated columns with `NOT NULL` domain type\n* [#9331](https://github.com/timescale/timescaledb/pull/9331) Ensure `search_path` is set before anything else in SQL scripts\n* [#9339](https://github.com/timescale/timescaledb/pull/9339) Fix segmentwise recompression clearing unordered flag\n\n**Thanks**\n* @CaptainCuddleCube for reporting an issue with `time_bucket_gapfill` and `LATERAL` subqueries\n* @JacobBrejnbjerg for reporting an issue with generated columns in compressed chunks\n* @Kusumoto for reporting an issue with continuous aggregates on hypertables with UUID columns\n* @arfathyahiya for reporting an issue with renaming columns in continuous aggregates\n* @desertmark for reporting an issue with `DELETE`/`UPDATE` and subqueries\n* @flaviofernandes004 for reporting an issue with `RETURNING` clause and references to OLD/NEW\n* @tureba for fixing SSL-related build errors\n\n## 2.25.1 (2026-02-17)\n\nThis release contains performance improvements and bug fixes since the 2.25.0 release. We recommend that you upgrade at the next available opportunity.\n\n**Bugfixes**\n* [#9215](https://github.com/timescale/timescaledb/pull/9215) Add missing handling for em_parent to sort_transform\n* [#9223](https://github.com/timescale/timescaledb/pull/9223) Clean up orphaned entries in continuous aggregate invalidaton logs\n* [#9226](https://github.com/timescale/timescaledb/pull/9226) Fix invalidation and batching issues for variable bucket continuous aggregates.\n* [#9256](https://github.com/timescale/timescaledb/pull/9256) Error \"record type has no extended hash function\" on some queries using a sparse bloom filter index on a column of composite type.\n* [#9257](https://github.com/timescale/timescaledb/pull/9257) Handle type coercion for metadata column equivalence members\n\n**Thanks**\n* @emapple for reporting a crash in a query with nested joins and subqueries\n\n## 2.25.0 (2026-01-29)\n\nThis release contains performance improvements and bug fixes since the 2.24.0 release. We recommend that you upgrade at the next available opportunity.\n\n**Highlighted features in TimescaleDB v2.25.0**\nThis release features multiple improvements for continuous aggregates on the columnstore: \n* Faster refreshes: You can now utilize direct compress during materialized view refreshes, resulting in higher throughput and reduced I/O usage.\n* Efficiency: The enablement of delete optimizations significantly lowers system resource requirements.\n* Smaller transactions: Adjusted defaults for `buckets_per_batch` to 10 reduces transaction sizes, requiring less WAL holding time.\n* Faster queries: Smarter defaults for `segmentby` and `orderby` yield improved query performance and better compression ratio on the columnstore.\n\n**Sunsetting announcements**\n* This release removes the WAL-based invalidation of continuous aggregates. This feature was introduced in [2.22.0](https://github.com/timescale/timescaledb/releases/tag/2.22.0) as tech preview to use logical decoding for building the invalidation logs. The feature was designed for high ingest workloads, reducing the write amplification. With the upcoming stream of improvements to continuous aggregates, this feature was deprioritized and removed.\n* The old continuous aggregate format, deprecated in version [2.10.0](https://github.com/timescale/timescaledb/releases/tag/2.10.0), has been fully removed from TimescaleDB in this release. Users still on the old format should read the [migration documentation](https://www.tigerdata.com/docs/use-timescale/latest/continuous-aggregates/migrate) to migrate to the new format. Users of Tiger Cloud have already been automatically migrated.\n\n**Features**\n* [#8777](https://github.com/timescale/timescaledb/pull/8777) Enable direct compress on continuous aggregate refresh using new GUC `timescaledb.enable_direct_compress_on_cagg_refresh`\n* [#9031](https://github.com/timescale/timescaledb/pull/9031) Change default `buckets_per_batch` on continuous aggregate refresh policy to `10`\n* [#9032](https://github.com/timescale/timescaledb/pull/9032) Add in-memory recompression for unordered chunks\n* [#9017](https://github.com/timescale/timescaledb/pull/9017) Move `bgw_job` table into schema `_timescaledb_catalog`\n* [#9033](https://github.com/timescale/timescaledb/pull/9033) Add `rebuild_columnstore` procedure\n* [#9038](https://github.com/timescale/timescaledb/pull/9038) Change default configuration for compressed continuous aggregates\n* [#9042](https://github.com/timescale/timescaledb/pull/9042) Enable batch sorted merge on unordered compressed chunks\n* [#9046](https://github.com/timescale/timescaledb/pull/9046) Allow non timescaledb namespace `SET` option for continuous aggregates\n* [#9059](https://github.com/timescale/timescaledb/pull/9059) Allow configuring `work_mem` for background worker jobs\n* [#9074](https://github.com/timescale/timescaledb/pull/9074) Add function to estimate uncompressed size of compressed chunk\n* [#9085](https://github.com/timescale/timescaledb/pull/9085) Don't register timescaledb-tune specific GUCs\n* [#9088](https://github.com/timescale/timescaledb/pull/9088) Add `ColumnarIndexScan` custom node\n* [#9090](https://github.com/timescale/timescaledb/pull/9090) Support direct batch delete on hypertables with continuous aggregates\n* [#9094](https://github.com/timescale/timescaledb/pull/9094) Enable the columnar pipeline for grouping without aggregation to speed up the queries of the form `select column from table group by column`.\n* [#9103](https://github.com/timescale/timescaledb/pull/9103) Support `FIRST` and `LAST` in `ColumnarIndexScan`\n* [#9108](https://github.com/timescale/timescaledb/pull/9108) Support multiple aggregates in `ColumnarIndexScan`\n* [#9111](https://github.com/timescale/timescaledb/pull/9111) Allow recompression with orderby/index changes\n* [#9113](https://github.com/timescale/timescaledb/pull/9113) Use `enable_columnarscan` to control columnarscan\n* [#9127](https://github.com/timescale/timescaledb/pull/9127) Remove primary dimension constraints from fully covered chunks\n* [#8710](https://github.com/timescale/timescaledb/pull/8710) Add SQL function to fetch continuous aggregate grouping columns\n* [#9133](https://github.com/timescale/timescaledb/pull/9133) Allow pushing down sort into columnar unordered chunks when it is possible\n* [#8229](https://github.com/timescale/timescaledb/pull/8229) Removed `time_bucket_ng` function\n* [#8859](https://github.com/timescale/timescaledb/pull/8859) Remove support for partial continuous aggregate format\n* [#9022](https://github.com/timescale/timescaledb/pull/9022) Remove WAL based invalidation\n* [#9016](https://github.com/timescale/timescaledb/pull/9016) Remove `_timescaledb_debug` schema\n* [#9030](https://github.com/timescale/timescaledb/pull/9030) Add new chunks to hypertable publication\n\n**Bug fixes**\n* [#8706](https://github.com/timescale/timescaledb/pull/8706) Fix planning performance regression on Postgres 16 and later on some join queries.\n* [#8986](https://github.com/timescale/timescaledb/pull/8986) Add pathkey replacement for `ColumnarScanPath`\n* [#8989](https://github.com/timescale/timescaledb/pull/8989) Ensure no XID is assigned during chunk query\n* [#8990](https://github.com/timescale/timescaledb/pull/8990) Fix `EquivalenceClass` index update for `RelOptInfo`\n* [#9007](https://github.com/timescale/timescaledb/pull/9007) Add validation for compression index key limits\n* [#9024](https://github.com/timescale/timescaledb/pull/9024) Recompress some chunks on `VACUUM FULL`\n* [#9045](https://github.com/timescale/timescaledb/pull/9045) Fix missing UUID check in compression policy\n* [#9056](https://github.com/timescale/timescaledb/pull/9056) Fix split chunk `relfrozenxid`\n* [#9058](https://github.com/timescale/timescaledb/pull/9058) Fix missing chunk column stats bug\n* [#9061](https://github.com/timescale/timescaledb/pull/9061) Fix update race with background worker jobs\n* [#9069](https://github.com/timescale/timescaledb/pull/9069) Fix applying multikey sort for columnstore when one numeric key is pinned to a Const of different type\n* [#9102](https://github.com/timescale/timescaledb/pull/9102) Support retention policies on UUIDv7-partitioned hypertables\n* [#9120](https://github.com/timescale/timescaledb/pull/9120) Fix for pre Postgres 17, where a `DELETE` from a partially compressed chunk may miss records if `BitmapHeapScan` is being used\n* [#9121](https://github.com/timescale/timescaledb/pull/9121) Allow any immutable constant expressions as default values for compressed columns\n* [#9121](https://github.com/timescale/timescaledb/pull/9121) Fix a potential \"unexpected column type 'bool'\" error for compressed bool columns with missing value\n* [#9144](https://github.com/timescale/timescaledb/pull/9144) Fix handling implicit constraints in `ALTER TABLE`\n* [#9155](https://github.com/timescale/timescaledb/pull/9155) Fix column generation during compressed chunk insert\n* [#9129](https://github.com/timescale/timescaledb/pull/9129) Fix `time_bucket` with timezone during DST\n* [#9177](https://github.com/timescale/timescaledb/pull/9177) Add alias for `bgw_job`\n* [#9176](https://github.com/timescale/timescaledb/pull/9176) Handle `NULL` values in continuous aggregate invalidation more gracefully\n* [#9175](https://github.com/timescale/timescaledb/pull/9175) Do not remove dimension constraints for OSM chunks\n\n**GUCs**\n* `enable_columnarindexscan`: Enable returning results directly from compression metadata without decompression. This feature is experimental, and in development towards a GA release. Not for production environments. Default: `false` \n* `enable_direct_compress_on_cagg_refresh`: Enable experimental support for direct compression during Continuous Aggregate refresh. Default: `false`\n* `enable_qual_filtering`: Filter qualifiers on chunks when complete chunk would be included by filter. Default: `true`\n\n**Thanks**\n* @t-aistleitner for reporting the planning performance regression on PG16 and later on some join queries.\n* @vahnrr for reporting a crash when adding columns and constraints to a hypertable at the same time\n* @cracksalad and @eyadmba for reporting a bug with timezone handling in `time_bucket`\n\n## 2.24.0 (2025-12-03)\n\nThis release contains performance improvements and bug fixes since the 2.23.1 release. We recommend that you upgrade at the next available opportunity.\n\n**Highlighted features in TimescaleDB v2.24.0**\n* **Direct Compress just got smarter and faster**: it now works seamlessly with hypertables generating continuous aggregates. Invalidation ranges are computed directly in-memory based on the ingested batches and written efficiently at transaction commit. This change reduces the IO footprint drastically by removing the write amplification of the invalidation logs.\n* **Continuous aggregates now speak UUIDv7**: hypertables partitioned by UUIDv7 are fully supported through an enhanced `time_bucket` that accepts UUIDv7 values and returns precise, timezone-aware timestamps — unlocking powerful time-series analytics on modern UUID-driven table schemas.\n* **Lightning-fast recompression**: the new `recompress := true` option on the `convert_to_columnstore` API enables pure in-memory recompression, delivering a **4–5× speed boost** over the previous disk-based process.\n\n**ARM support for bloom filters**\nThe [sparse bloom filter indexes](https://www.tigerdata.com/blog/blocked-bloom-filters-speeding-up-point-lookups-in-tiger-postgres-native-columnstore) will stop working after upgrade to 2.24. If you are affected by this problem, the warning \"bloom filter sparse indexes require action to re-enable\" will appear in the Postgres log during upgrade.\n\nIn versions before 2.24, the hashing scheme of the bloom filter sparse indexes used to depend on the build options of the TimescaleDB executables. These options are set by the package publishers and might differ between different package sources or even versions. After upgrading to a version with different options, the queries that use the bloom filter lookups could erroneously stop returning the rows that should in fact match the query conditions. The 2.24 release fixes this by using distinct column names for each hashing scheme.\n\nThe bloom filter sparse indexes will be disabled on the compressed chunks created before upgrading to 2.24. To re-enable them, you have to decompress and then compress the affected chunks.\n\nIf you were running the official APT package on AMD64 architecture, the hashing scheme did not change, and it is safe to use the existing bloom filter sparse indexes. To enable this, set the GUC `timescaledb.read_legacy_bloom1_v1 = on` in the server configuration.\n\nThe chunks compressed after upgrade to 2.24 will use the new index format, and the bloom filter sparse indexes will continue working as usual for these chunks without any intervention.\n\nFor more details, refer to the pull request [#8761](https://github.com/timescale/timescaledb/pull/8761).\n\n**Deprecations**\n* The next release of TimescaleDB will remove the deprecated partial continuous aggregates format. The new format was introduced in [`2.7.0`](https://github.com/timescale/timescaledb/releases/tag/2.7.0) and provides significant improvements in terms of performance and storage efficiency. Please use [`cagg_migrate(<CONTINUOUS_AGGREGATE_NAME>)`](https://www.tigerdata.com/docs/use-timescale/latest/continuous-aggregates/migrate) to migrate to the new format. Tiger Cloud users are migrated automatically.\n* In future releases the deprecated view `timescaledb_information.compression_settings` will be removed. Please use [`timescaledb_information.hypertable_columnstore_settings`](https://www.tigerdata.com/docs/api/latest/hypercore/hypertable_columnstore_settings) as a replacement.\n* The experimental view [`timescaledb_experimental.policies`](https://www.tigerdata.com/docs/api/latest/informational-views/policies) and the adjacent experimental functions [`add_policies`](https://www.tigerdata.com/docs/api/latest/continuous-aggregates/add_policies), [`alter_policies`](https://www.tigerdata.com/docs/api/latest/continuous-aggregates/alter_policies), [`show_policies`](https://www.tigerdata.com/docs/api/latest/continuous-aggregates/show_policies), [`remove_policies`](https://www.tigerdata.com/docs/api/latest/continuous-aggregates/remove_policies), and [`remove_all_policies`](https://www.tigerdata.com/docs/api/latest/continuous-aggregates/remove_all_policies) to manage continuous aggregates will be removed in an upcoming release. For replacements, please use the [Jobs API](https://www.tigerdata.com/docs/api/latest/jobs-automation).\n\n**Backward-Incompatible Changes**\n* [#8761](https://github.com/timescale/timescaledb/pull/8761) Fix matching rows in queries using the bloom filter sparse indexes potentially not returned after extension upgrade. The version of the bloom filter sparse indexes is changed. The existing indexes will stop working and will require action to re-enable. See the section above for details.\n\n**Features**\n* [#8465](https://github.com/timescale/timescaledb/pull/8465) Speed up the filters like `x = any(array[...])` using bloom filter sparse indexes.\n* [#8569](https://github.com/timescale/timescaledb/pull/8569) In-memory recompression\n* [#8754](https://github.com/timescale/timescaledb/pull/8754) Add concurrent mode for merging chunks\n* [#8786](https://github.com/timescale/timescaledb/pull/8786) Display chunks view range as timestamps for UUIDv7\n* [#8819](https://github.com/timescale/timescaledb/pull/8819) Refactor chunk compression logic\n* [#8840](https://github.com/timescale/timescaledb/pull/8840) Allow `ALTER COLUMN TYPE` when compression is enabled but no compressed chunks exist\n* [#8908](https://github.com/timescale/timescaledb/pull/8908) Add time bucketing support for UUIDv7\n* [#8909](https://github.com/timescale/timescaledb/pull/8909) Support direct compress on hypertables with continuous aggregates\n* [#8939](https://github.com/timescale/timescaledb/pull/8939) Support continuous aggregates on UUIDv7-partitioned hypertables\n* [#8959](https://github.com/timescale/timescaledb/pull/8959) Cap continuous aggregate invalidation interval range at chunk boundary\n* [#8975](https://github.com/timescale/timescaledb/pull/8975) Exclude date/time columns from default segmentby\n* [#8993](https://github.com/timescale/timescaledb/pull/8993) Add GUC for in-memory recompression\n\n**Bugfixes**\n* [#8839](https://github.com/timescale/timescaledb/pull/8839) Improve `_timescaledb_functions.cagg_watermark` error handling\n* [#8853](https://github.com/timescale/timescaledb/pull/8853) Change log level of continuous aggregate refresh messages to `DEBUG1`\n* [#8933](https://github.com/timescale/timescaledb/pull/8933) Potential crash or seemingly random errors when querying the compressed chunks created on releases before 2.15 and using the minmax sparse indexes.\n* [#8942](https://github.com/timescale/timescaledb/pull/8942) Fix lateral join handling for compressed chunks\n* [#8958](https://github.com/timescale/timescaledb/pull/8958) Fix `if_not_exists` behaviour when adding refresh policy\n* [#8969](https://github.com/timescale/timescaledb/pull/8969) Gracefully handle missing job stat in background worker\n* [#8988](https://github.com/timescale/timescaledb/pull/8988) Don't ignore additional filters on same column when building scankeys\n\n**GUCs**\n* `direct_compress_copy_tuple_sort_limit`: Number of tuples that can be sorted at once in a `COPY` operation.\n* `direct_compress_insert_tuple_sort_limit`: Number of tuples that can be sorted at once in an `INSERT` operation.\n* `read_legacy_bloom1_v1`: Enable reading the legacy `bloom1` version 1 sparse indexes for `SELECT` queries.\n* `enable_in_memory_recompression`: Enable in-memory recompression functionality.\n\n**Thanks**\n* @bezpechno for implementing `ALTER COLUMN TYPE` for hypertable with columnstore when no compressed chunks exist\n\n## 2.23.1 (2025-11-13)\n\nThis release contains performance improvements and bug fixes since the 2.23.0 release. We recommend that you upgrade at the next available opportunity.\n\n**Bugfixes**\n* [#8873](https://github.com/timescale/timescaledb/pull/8873) Don't error on failure to update job stats\n* [#8875](https://github.com/timescale/timescaledb/pull/8875) Fix decoding of UUID v7 timestamp microseconds\n* [#8879](https://github.com/timescale/timescaledb/pull/8879) Fix blocker for multiple hierarchical continuous aggregate policies\n* [#8882](https://github.com/timescale/timescaledb/pull/8882) Fix crash in policy creation\n\n**Thanks**\n* @alexanderlaw for reporting a crash when creating a policy\n* @leppaott for reporting an issue with hierarchical continuous aggregates\n\n## 2.23.0 (2025-10-29)\n\nThis release contains performance improvements and bug fixes since the 2.22.1 release. We recommend that you upgrade at the next available opportunity.\n\n**Highlighted features in TimescaleDB v2.23.0**\n* This release introduces full PostgreSQL 18 support for all existing features. TimescaleDB v2.23 is available for PostgreSQL 15, 16, 17, and 18.\n* UUIDv7 compression is now enabled by default on the columnstore. This feature was shipped in [v2.22.0](https://github.com/timescale/timescaledb/releases/tag/2.22.0). It saves you at least 30% of storage and delivers ~2× faster query performance with UUIDv7 columns in the filter conditions.\n* Added the ability to set hypertables to unlogged, addressing an open community request [#836](https://github.com/timescale/timescaledb/issues/836). This allows the tradeoff between durability and performance, with the latter being favourable for larger imports.\n* By allowing [set-returning functions](https://www.postgresql.org/docs/current/functions-srf.html) in continuous aggregates, this releases addresses a long standing blocker, raised by the community [#1717](https://github.com/timescale/timescaledb/issues/1717).\n\n**PostgreSQL 15 deprecation announcement**\n\nWe will continue supporting PostgreSQL 15 until June 2026. Closer to that time, we will announce the specific TimescaleDB version in which PostgreSQL 15 support will not be included going forward.\n\n**Features**\n* [#8373](https://github.com/timescale/timescaledb/pull/8373) More precise estimates of row numbers for columnar storage based on Postgres statistics.\n* [#8581](https://github.com/timescale/timescaledb/pull/8581) Allow mixing Postgres and TimescaleDB options in `ALTER TABLE SET`.\n* [#8582](https://github.com/timescale/timescaledb/pull/8582) Make `partition_column` in `CREATE TABLE WITH` optional.\n* [#8588](https://github.com/timescale/timescaledb/pull/8588) Automatically create a columnstore policy when a hypertable with columnstore enabled is created via `CREATE TABLE WITH` statement.\n* [#8606](https://github.com/timescale/timescaledb/pull/8606) Add job history config parameters for maximum successes and failures to keep for each job.\n* [#8632](https://github.com/timescale/timescaledb/pull/8632) Remove `ChunkDispatch` custom node.\n* [#8637](https://github.com/timescale/timescaledb/pull/8637) Add `INSERT` support for direct compress.\n* [#8661](https://github.com/timescale/timescaledb/pull/8661) Allow `ALTER TABLE ONLY` to change `reloptions` to apply setting changes only to future chunks.\n* [#8703](https://github.com/timescale/timescaledb/pull/8703) Allow set-returning functions in continuous aggregates.\n* [#8734](https://github.com/timescale/timescaledb/pull/8734) Support direct compress when inserting into a chunk.\n* [#8741](https://github.com/timescale/timescaledb/pull/8741) Add support for unlogged hypertables.\n* [#8769](https://github.com/timescale/timescaledb/pull/8769) Remove continuous aggregate invalidation trigger.\n* [#8798](https://github.com/timescale/timescaledb/pull/8798) Enable UUIDv7 compression by default.\n* [#8804](https://github.com/timescale/timescaledb/pull/8804) Remove `insert_blocker` trigger.\n\n**Bugfixes**\n* [#8561](https://github.com/timescale/timescaledb/pull/8561) Show warning when direct compress is skipped due to triggers or unique constraints.\n* [#8567](https://github.com/timescale/timescaledb/pull/8567) Do not require a job to have executed to show status.\n* [#8654](https://github.com/timescale/timescaledb/pull/8654) Fix `approximate_row_count` for compressed chunks.\n* [#8704](https://github.com/timescale/timescaledb/pull/8704) Fix direct `DELETE` on compressed chunk.\n* [#8728](https://github.com/timescale/timescaledb/pull/8728) Don't block dropping hypertables with other objects.\n* [#8735](https://github.com/timescale/timescaledb/pull/8735) Fix `ColumnarScan` for `UNION` queries.\n* [#8739](https://github.com/timescale/timescaledb/pull/8739) Fix cached utility statements.\n* [#8742](https://github.com/timescale/timescaledb/pull/8742) Potential internal program error when grouping by `bool` columns of a compressed hypertable.\n* [#8743](https://github.com/timescale/timescaledb/pull/8743) Modify schedule interval for job history pruning.\n* [#8746](https://github.com/timescale/timescaledb/pull/8746) Support show/drop chunks with UUIDv7 partitioning.\n* [#8753](https://github.com/timescale/timescaledb/pull/8753) Allow sorts over decompressed index scans for `ChunkAppend`.\n* [#8758](https://github.com/timescale/timescaledb/pull/8758) Improve error message on catalog version mismatch.\n* [#8774](https://github.com/timescale/timescaledb/pull/8774) Add GUC for WAL based invalidation of continuous aggregates.\n* [#8782](https://github.com/timescale/timescaledb/pull/8782) Stops sparse index from allowing multiple options.\n* [#8799](https://github.com/timescale/timescaledb/pull/8799) Set `next_start` for `WITH` clause compression policy.\n* [#8807](https://github.com/timescale/timescaledb/pull/8807) Only warn but not fail the compression if bloom filter indexes are configured but disabled with a GUC.\n\n**GUCs**\n* `cagg_processing_wal_batch_size`: Batch size when processing WAL entries.\n* `enable_cagg_wal_based_invalidation`: Enable experimental invalidations for continuous aggregates using WAL.\n* `enable_direct_compress_insert`: Enable direct compression during `INSERT`.\n* `enable_direct_compress_insert_client_sorted`: Enable direct compress `INSERT` with presorted data.\n* `enable_direct_compress_insert_sort_batches`: Enable batch sorting during direct compress `INSERT`.\n\n**Thanks**\n* @brandonpurcell-dev For highlighting issues with `show_chunks()` and UUIDv7 partitioning\n* @moodgorning for reporting an issue with the `timescaledb_information.job_stats` view\n* @ruideyllot for reporting set-returning functions not working in continuous aggregates\n* @t-aistleitner for reporting an issue with utility statements in plpgsql functions\n\n## 2.22.1 (2025-09-30)\n\nThis release contains performance improvements and bug fixes since the [2.22.0](https://github.com/timescale/timescaledb/releases/tag/2.20.0) release. We recommend that you upgrade at the next available opportunity.\n\nThis release blocks the ability to leverage **concurrent refresh policies** in **hierarchical continuous aggregates**, as potential deadlocks can occur. \n[Concurrent refresh policies](https://docs.tigerdata.com/use-timescale/latest/continuous-aggregates/refresh-policies/#add-concurrent-refresh-policies) were introduced in [2.21.0](https://github.com/timescale/timescaledb/releases/tag/2.20.0) and allow users to define multiple time ranges, to refresh, e.g. data from the last hour in policy and the last day in a second policy.\nIf you are using this feature with **hierarchical** continuous aggregates, please [remove the existing policies](https://docs.tigerdata.com/api/latest/jobs-automation/delete_job/#samples) and [create a new policy](https://docs.tigerdata.com/use-timescale/latest/continuous-aggregates/refresh-policies/#change-the-refresh-policy) for the full range you want to refresh, of the continuous aggregate as follows:\n```\n--- Find the job ID's of the concurrent refresh policies\nSELECT * FROM timescaledb_information.jobs WHERE proc_name = 'policy_refresh_continuous_aggregate';\n--- Remove the job\nSELECT delete_job(\"<job_id_of_concurrent_policy>\");\n--- Create new policy for hierarchical continuous aggregate\nSELECT add_continuous_aggregate_policy('<name_of_materialized_view>',\n  start_offset => INTERVAL '1 month',\n  end_offset => INTERVAL '1 day',\n  schedule_interval => INTERVAL '1 hour');\n```\n\n**Bugfixes**\n* [#7766](https://github.com/timescale/timescaledb/pull/7766) Load the OSM extension in the retention background worker to drop tiered chunks\n* [#8550](https://github.com/timescale/timescaledb/pull/8550) Error in gapfill with expressions over aggregates, groupby columns, and out-of-order columns\n* [#8593](https://github.com/timescale/timescaledb/pull/8593) Error on change of invalidation method for continuous aggregate\n* [#8599](https://github.com/timescale/timescaledb/pull/8599) Fix the attnum mismatch bug in chunk constraint checks\n* [#8607](https://github.com/timescale/timescaledb/pull/8607) Fix the interrupted continous aggregate refresh materialization phase that leaves behind pending materialization ranges\n* [#8638](https://github.com/timescale/timescaledb/pull/8638) `ALTER TABLE RESET` for `orderby` settings\n* [#8644](https://github.com/timescale/timescaledb/pull/8644) Fix the migration script for sparse index configuration\n* [#8657](https://github.com/timescale/timescaledb/pull/8657) Fix `CREATE TABLE WITH` when using UUIDv7 partitioning\n* [#8659](https://github.com/timescale/timescaledb/pull/8659) `ALTER TABLE` commands to foreign data wrapper chunks not propogated. \n* [#8693](https://github.com/timescale/timescaledb/pull/8693) Compressed index not chosen for `varchar` typed `segmentby` columns\n* [#8707](https://github.com/timescale/timescaledb/pull/8707) Block concurrent refresh policies for hierarchical continous aggregate due to potential deadlocks\n\n**Thanks**\n* @MKrkkl for reporting a bug in gapfill queries with expressions over aggregates and groupby columns\n* @brandonpurcell-dev for creating a test case that showed a bug in `CREATE TABLE WITH` when using UUIDv7 partitioning\n* @snyrkill for reporting a bug when interrupting a continous aggregate refresh\n\n## 2.22.0 (2025-09-02)\n\nThis release contains performance improvements and bug fixes since the 2.21.3 release. We recommend that you upgrade at the next available opportunity.\n\n**Highlighted features in TimescaleDB v2.22.0**\n* Sparse indexes on compressed hypertables can now be explicitly configured via `ALTER TABLE` rather than relying only on internal heuristics. Users can define indexes on multiple columns to improve query performance for their specific workloads.\n* [Tech Preview] Continuous aggregates now support the `timescaledb.invalidate_using` option, enabling invalidations to be collected either via triggers on the hypertable or directly from WAL using logical decoding. Aggregates inherit the hypertable’s method if none is specified.  \n* UUIDv7 compression and vectorization are now supported. The compression algorithm leverages the timestamp portion for delta-delta compression while storing the random portion separately. The vectorized equality/inequality filters with bulk decompression deliver ~2× faster query performance. The feature is disabled by default (`timescaledb.enable_uuid_compression`) to simplify the downgrading experience, and will be enabled out of the box in the next minor release.\n* Hypertables can now be partitioned by UUIDv7 columns, leveraging their embedded timestamps for time-based chunking. We’ve also added utility functions to simplify working with UUIDv7, such as generating values or extracting timestamps - e.g., `uuid_timestamp()` returns a PostgreSQL timestamp from a UUIDv7.\n* SkipScan now supports multi-column indexes in not-null mode, improving performance for distinct and ordered queries across multiple keys.  \n\n**Removal of the hypercore table access method**\nWe made the decision to deprecate the hypercore table access method (TAM) with the 2.21.0 release. Hypercore TAM was an experiment and it did not show the performance improvements we hoped for. It is removed with this release. Upgrades to 2.22.0 and higher are blocked if TAM is still in use. Since TAM’s inception in [2.18.0](https://github.com/timescale/timescaledb/releases/tag/2.18.0), we learned that btrees were not the right architecture. Recent advancements in the columnstore, such as more performant backfilling, SkipScan, adding check constraints, and faster point queries, put the [columnstore](https://www.timescale.com/blog/hypercore-a-hybrid-row-storage-engine-for-real-time-analytics) close to or on par with TAM without needing to store an additional index. We apologize for the inconvenience this action potentially causes and are here to assist you during the migration process.\n\nMigration path\n\n```\ndo $$\ndeclare\n   relid regclass;\nbegin\n   for relid in\n       select cl.oid from pg_class cl\n       join pg_am am on (am.oid = cl.relam)\n       where am.amname = 'hypercore'\n   loop\n       raise notice 'converting % to heap', relid::regclass;\n       execute format('alter table %s set access method heap', relid);\n   end loop;\nend\n$$;\n```\n\n**Features**\n* [#8247](https://github.com/timescale/timescaledb/pull/8247) Add configurable alter settings for sparse indexes\n* [#8306](https://github.com/timescale/timescaledb/pull/8306) Add option for invalidation collection using WAL for continuous aggregates\n* [#8340](https://github.com/timescale/timescaledb/pull/8340) Improve selectivity estimates for sparse minmax indexes, so that an index scan on a table in the columnstore is chosen more often when it's beneficial.\n* [#8360](https://github.com/timescale/timescaledb/pull/8360) Continuous aggregate multi-hypertable invalidation processing\n* [#8364](https://github.com/timescale/timescaledb/pull/8364) Remove hypercore table access method\n* [#8371](https://github.com/timescale/timescaledb/pull/8371) Show available timescaledb `ALTER` options when encountering unsupported options\n* [#8376](https://github.com/timescale/timescaledb/pull/8376) Change `DecompressChunk` custom node name to `ColumnarScan`\n* [#8385](https://github.com/timescale/timescaledb/pull/8385) UUID v7 functions for testing pre PG18\n* [#8393](https://github.com/timescale/timescaledb/pull/8393) Add specialized compression for UUIDs. Best suited for UUID v7, but still works with other UUID versions. This is experimental at the moment and backward compatibility is not guaranteed.\n* [#8398](https://github.com/timescale/timescaledb/pull/8398) Set default compression settings at compress time\n* [#8401](https://github.com/timescale/timescaledb/pull/8401) Support `ALTER TABLE RESET` for compression settings\n* [#8414](https://github.com/timescale/timescaledb/pull/8414) Vectorised filtering of UUID Eq and Ne filters, plus bulk decompression of UUIDs\n* [#8424](https://github.com/timescale/timescaledb/pull/8424) Block downgrade when orderby setting is `NULL`\n* [#8454](https://github.com/timescale/timescaledb/pull/8454) Remove internal unused index helper functions\n* [#8494](https://github.com/timescale/timescaledb/pull/8494) Improve job stat history retention policy\n* [#8496](https://github.com/timescale/timescaledb/pull/8496) Fix dropping chunks with foreign keys\n* [#8505](https://github.com/timescale/timescaledb/pull/8505) Add support for partitioning on UUIDv7\n* [#8513](https://github.com/timescale/timescaledb/pull/8513) Support multikey SkipScan when all keys are guaranteed to be non-null\n* [#8514](https://github.com/timescale/timescaledb/pull/8514) Concurrent continuous aggregates improvements\n* [#8528](https://github.com/timescale/timescaledb/pull/8528) Add the `_timescaledb_functions.chunk_status_text` helper function\n* [#8529](https://github.com/timescale/timescaledb/pull/8529) Optimize direct compress status handling\n\n**Bugfixes**\n* [#8422](https://github.com/timescale/timescaledb/pull/8422) Don't require `columnstore=false` when using the TimescaleDB Apache 2 Edition\n* [#8493](https://github.com/timescale/timescaledb/pull/8493) Change log level of `not null` constraint message\n* [#8500](https://github.com/timescale/timescaledb/pull/8500) Fix uniqueness check with generated columns and hypercore\n* [#8545](https://github.com/timescale/timescaledb/pull/8545) Fix error in LOCF/Interpolate with out-of-order and repeated columns\n* [#8558](https://github.com/timescale/timescaledb/pull/8558) Error out on bad args when processing invalidation\n* [#8559](https://github.com/timescale/timescaledb/pull/8559) Fix `timestamp out of range` using `end_offset=NULL` on CAgg refresh policy\n\n**GUCs**\n* `enable_multikey_skipscan`: Enable SkipScan for multiple distinct keys, default: on\n* `enable_uuid_compression`: Enable UUID compression functionality, default: off\n* `cagg_processing_wal_batch_size`: Batch size when processing WAL entries, default: 10000\n* `cagg_processing_low_work_mem`: Low working memory limit for continuous aggregate invalidation processing, default: 38.4MB\n* `cagg_processing_high_work_mem`: High working memory limit for continuous aggregate invalidation processing, default: 51.2MB\n\n**Thanks**\n* @CodeTherapist for reporting an issue where foreign key checks did not work after several insert statements\n* @moodgorning for reporting a bug in queries with LOCF/Interpolate using out-of-order columns\n* @nofalx for reporting an error when using `end_offset=NULL` on CAgg refresh policy\n* @pierreforstmann for fixing a bug that happened when dropping chunks with foreign keys\n* @Zaczero for reporting a bug with CREATE TABLE WITH when using the TimescaleDB Apache 2 Edition\n\n## 2.21.4 (2025-09-25)\n\nThis release contains performance improvements and bug fixes since the 2.21.3 release. We recommend that you upgrade at the next available opportunity.\n\n**Bugfixes**\n* [#8667](https://github.com/timescale/timescaledb/pull/8667) Fix wrong selectivity estimates uncovered by the recent Postgres minor releases 15.14, 16.10, 17.6.\n\n## 2.21.3 (2025-08-12)\n\nThis release contains performance improvements and bug fixes since the 2.21.2 release. We recommend that you upgrade at the next available opportunity.\n\n**Bugfixes**\n* [#8471](https://github.com/timescale/timescaledb/pull/8471) Fix MERGE behaviour with updated values\n\n## 2.21.2 (2025-08-05)\n\nThis release contains performance improvements and bug fixes since the 2.21.1 release. We recommend that you upgrade at the next available opportunity.\n\n**Bugfixes**\n* [#8418](https://github.com/timescale/timescaledb/pull/8418) Fix duplicate constraints in JOIN queries\n* [#8426](https://github.com/timescale/timescaledb/pull/8426) Fix chunk skipping min/max calculation\n* [#8434](https://github.com/timescale/timescaledb/pull/8434) Allow `show_chunks` to process more than 65535 chunks\n\n## 2.21.1 (2025-07-22)\n\nThis release contains one bug fix since the 2.21.0 release. We recommend that you upgrade at the next available opportunity.\n\n**Bugfixes**\n* [#8336](https://github.com/timescale/timescaledb/pull/8336) Fix generic plans for foreign key checks and prepared statements\n\n**Thanks**\n* @CodeTherapist for reporting the issue with foreign key checks not working after several `INSERT` statements\n\n## 2.21.0 (2025-07-08)\n\nThis release contains performance improvements and bug fixes since the 2.20.3 release. We recommend that you upgrade at the next available opportunity.\n\n**Highlighted features in TimescaleDB v2.21.0**\n* The attach & detach chunks feature allows manually adding or removing chunks from a hypertable with uncompressed chunks, similar to PostgreSQL’s partition management.\n* Continued improvement of backfilling into the columnstore, achieving up to 2.5x speedup for constrained tables, by introducing caching logic that boosts throughput for writes to compressed chunks, bringing `INSERT` performance close to that of uncompressed chunks.\n* Optimized `DELETE` operations on the columstore through batch-level deletions of non-segmentby keys in the filter condition, greatly improving performance to up to 42x faster in some cases, as well as reducing bloat, and lowering resource usage.\n* The heavy lock taken in Continuous Aggregate refresh was relaxed, enabling concurrent refreshes for non-overlapping ranges and eliminating the need for complex customer workarounds.\n* [tech preview] Direct Compress is an innovative TimescaleDB feature that improves high-volume data ingestion by compressing data in memory and writing it directly to disk, reducing I/O overhead, eliminating dependency on background compression jobs, and significantly boosting insert performance.\n\n**Sunsetting of the hypercore access method**\nWe made the decision to deprecate hypercore access method (TAM) with the 2.21.0 release. It was an experiment, which did not show the signals we hoped for and will be sunsetted in TimescaleDB 2.22.0, scheduled for September 2025. Upgrading to 2.22.0 and higher will be blocked if TAM is still in use. Since TAM’s inception in [2.18.0](https://github.com/timescale/timescaledb/releases/tag/2.18.0), we learned that btrees were not the right architecture. The recent advancements in the columnstore—such as more performant backfilling, SkipScan, adding check constraints, and faster point queries—put the [columnstore](https://www.timescale.com/blog/hypercore-a-hybrid-row-storage-engine-for-real-time-analytics) close to or on par with TAM without the storage from the additional index. We apologize for the inconvenience this action potentially causes and are here to assist you during the migration process.\n\nMigration path\n\n```\ndo $$\ndeclare   \n   relid regclass;\nbegin\n   for relid in\n       select cl.oid from pg_class cl\n       join pg_am am on (am.oid = cl.relam)\n       where am.amname = 'hypercore'\n   loop\n       raise notice 'converting % to heap', relid::regclass;\n       execute format('alter table %s set access method heap', relid);\n   end loop;\nend\n$$;\n```\n\n**Features**\n* [#8081](https://github.com/timescale/timescaledb/pull/8081) Use JSON error code for job configuration parsing\n* [#8100](https://github.com/timescale/timescaledb/pull/8100) Support splitting compressed chunks\n* [#8131](https://github.com/timescale/timescaledb/pull/8131) Add policy to process hypertable invalidations\n* [#8141](https://github.com/timescale/timescaledb/pull/8141) Add function to process hypertable invalidations\n* [#8165](https://github.com/timescale/timescaledb/pull/8165) Reindex recompressed chunks in compression policy\n* [#8178](https://github.com/timescale/timescaledb/pull/8178) Add columnstore option to `CREATE TABLE WITH`\n* [#8179](https://github.com/timescale/timescaledb/pull/8179) Implement direct `DELETE` on non-segmentby columns\n* [#8182](https://github.com/timescale/timescaledb/pull/8182) Cache information for repeated upserts into the same compressed chunk\n* [#8187](https://github.com/timescale/timescaledb/pull/8187) Allow concurrent Continuous Aggregate refreshes\n* [#8191](https://github.com/timescale/timescaledb/pull/8191) Add option to not process hypertable invalidations\n* [#8196](https://github.com/timescale/timescaledb/pull/8196) Show deprecation warning for TAM\n* [#8208](https://github.com/timescale/timescaledb/pull/8208) Use `NULL` compression for bool batches with all null values like the other compression algorithms\n* [#8223](https://github.com/timescale/timescaledb/pull/8223) Support for attach/detach chunk \n* [#8265](https://github.com/timescale/timescaledb/pull/8265) Set incremental Continous Aggregate refresh policy on by default\n* [#8274](https://github.com/timescale/timescaledb/pull/8274) Allow creating concurrent continuous aggregate refresh policies\n* [#8314](https://github.com/timescale/timescaledb/pull/8314) Add support for timescaledb_lake in loader\n* [#8209](https://github.com/timescale/timescaledb/pull/8209) Add experimental support for Direct Compress of `COPY`\n* [#8341](https://github.com/timescale/timescaledb/pull/8341) Allow quick migration from hypercore TAM to (columnstore) heap\n\n**Bugfixes**\n* [#8153](https://github.com/timescale/timescaledb/pull/8153) Restoring a database having NULL compressed data\n* [#8164](https://github.com/timescale/timescaledb/pull/8164) Check columns when creating new chunk from table\n* [#8294](https://github.com/timescale/timescaledb/pull/8294) The \"vectorized predicate called for a null value\" error for WHERE conditions like `x = any(null::int[])`.\n* [#8307](https://github.com/timescale/timescaledb/pull/8307) Fix missing catalog entries for bool and null compression in fresh installations\n* [#8323](https://github.com/timescale/timescaledb/pull/8323) Fix DML issue with expression indexes and BHS\n\n**GUCs**\n* `enable_direct_compress_copy`: Enable experimental support for direct compression during `COPY`, default: off\n* `enable_direct_compress_copy_sort_batches`: Enable batch sorting during direct compress `COPY`, default: on\n* `enable_direct_compress_copy_client_sorted`: Correct handling of data sorting by the user is required for this option, default: off\n\n## 2.20.3 (2025-06-11)\n\nThis release contains bug fixes since the 2.20.2 release. We recommend that you upgrade at the next available opportunity.\n\n**Bugfixes**\n* [#8107](https://github.com/timescale/timescaledb/pull/8107) Adjust SkipScan cost for quals needing full scan of indexed data.\n* [#8211](https://github.com/timescale/timescaledb/pull/8211) Fixed dump and restore when chunk skipping is enabled.\n* [#8216](https://github.com/timescale/timescaledb/pull/8216) Fix for dropped quals bug in SkipScan.\n* [#8230](https://github.com/timescale/timescaledb/pull/8230) Fix for inserting into compressed data when vectorised check is not available.\n* [#8236](https://github.com/timescale/timescaledb/pull/8236) Fixed the snapshot handling in background workers.\n\n**Thanks**\n* @ikaakkola for reporting that SkipScan is slow when non-index quals do not match any tuples.\n\n## 2.20.2 (2025-06-02)\n\nThis release contains bug fixes since the 2.20.1 release. We recommend that you upgrade at the next available opportunity.\n\n**Bugfixes**\n* [#8200](https://github.com/timescale/timescaledb/pull/8202) Fix `NULL` compression handling for vectorized constraint checking\n\n## 2.20.1 (2025-05-27)\n\nThis release contains performance improvements and bug fixes since the 2.20.0 release. We recommend that you upgrade at the next available opportunity.\n\n**Features**\n* [#8145](https://github.com/timescale/timescaledb/pull/8145) Log only if compression ratio warnings are enabled\n\n**Bugfixes**\n* [#7292](https://github.com/timescale/timescaledb/pull/7292) Intermittent \"could not find pathkey item to sort\" error when grouping or ordering by a time_bucket of an equality join variable.\n* [#8126](https://github.com/timescale/timescaledb/pull/8126) Allow setting bgw_log_level to FATAL and ERROR\n* [#8151](https://github.com/timescale/timescaledb/pull/8151) Treat null equal to null for merged CAgg refresh\n* [#8153](https://github.com/timescale/timescaledb/pull/8153) Restoring a database having NULL compressed data\n* [#8162](https://github.com/timescale/timescaledb/pull/8162) Fix setting compress_chunk_interval on continuous aggregates\n* [#8163](https://github.com/timescale/timescaledb/pull/8163) Fix gapfill crash with locf NULL values treated as missing\n* [#8171](https://github.com/timescale/timescaledb/pull/8171) Disable decompression limit during continuous aggregate refresh\n\n**Thanks**\n* @bobozaur, @kvc0, @ChadMoran, @PaddyKe for reporting the pathkey error.\n* @jlordiales for reporting an issue with setting compress_chunk_interval for continuous aggregates\n* @svanharmelen, @cmdjulian, @etaMS20 for reporting time_bucket_gapfill with locf crash when NULL values are treated as missing \n\n## 2.20.0 (2025-05-15)\n\nThis release contains performance improvements and bug fixes since the 2.19.3 release. We recommend that you upgrade at the next available opportunity.\n\n**Highlighted features in TimescaleDB v2.20.0**\n* The columnstore now leverages *bloom filters* to deliver up to 6x faster point queries on columns with high cardinality values, such as UUIDs.\n* Major *improvements to the columnstores' backfill process* enable `UPSERTS` with strict constraints to execute up to 10x faster.\n* *SkipScan is now supported in the columnstore*, including for DISTINCT queries. This enhancement leads to dramatic query performance improvements of 2000x to 2500x, especially for selective queries.\n* SIMD vectorization for the bool data type is now enabled by default. This change results in a 30–45% increase in performance for analytical queries with bool clauses on the columnstore.\n* *Continuous aggregates*  now include experimental support for *window functions and non-immutable functions*, extending the analytics use cases they can solve.\n* Several quality-of-life improvements have been introduced: job names for continuous aggregates are now more descriptive, you can assign custom names to them, and it is now possible to add unique constraints along with `ADD COLUMN` operations in the columnstore.\n* Improved management and optimization of chunks with the ability to split large uncompressed chunks at a specified point in time using the `split_chunk` function. This new function complements the existing `merge_chunk` function that can be used to merge two small chunks into one larger chunk. \n* Enhancements to the default behavior of the columnstore now provide better *automatic assessments* of `segment by` and `order by` columns, reducing the need for manual configuration and simplifying initial setup.\n\n**PostgreSQL 14 support removal announcement**\n\nFollowing the deprecation announcement for PostgreSQL 14 in TimescaleDB v2.19.0, PostgreSQL 14 is no longer supported in TimescaleDB v2.20.0. The currently supported PostgreSQL major versions are 15, 16, and 17.\n\n**Features**\n* [#7638](https://github.com/timescale/timescaledb/pull/7638) Bloom filter sparse indexes for compressed columns. Can be disabled with the GUC `timescaledb.enable_sparse_index_bloom`\n* [#7756](https://github.com/timescale/timescaledb/pull/7756) Add warning for poor compression ratio\n* [#7762](https://github.com/timescale/timescaledb/pull/7762) Speed up the queries that use minmax sparse indexes on compressed tables by changing the index TOAST storage type to `MAIN`. This applies to newly compressed chunks\n* [#7785](https://github.com/timescale/timescaledb/pull/7785) Do `DELETE` instead of `TRUNCATE` when locks aren't acquired\n* [#7852](https://github.com/timescale/timescaledb/pull/7852) Allow creating foreign key constraints on compressed tables\n* [#7854](https://github.com/timescale/timescaledb/pull/7854) Remove support for PG14\n* [#7864](https://github.com/timescale/timescaledb/pull/7854) Allow adding CHECK constraints to compressed chunks\n* [#7868](https://github.com/timescale/timescaledb/pull/7868) Allow adding columns with `CHECK` constraints to compressed chunks\n* [#7874](https://github.com/timescale/timescaledb/pull/7874) Support for SkipScan for distinct aggregates over the same column\n* [#7877](https://github.com/timescale/timescaledb/pull/7877) Remove blocker for unique constraints with `ADD COLUMN`\n* [#7878](https://github.com/timescale/timescaledb/pull/7878) Don't block non-immutable functions in continuous aggregates\n* [#7880](https://github.com/timescale/timescaledb/pull/7880) Add experimental support for window functions in continuous aggregates\n* [#7899](https://github.com/timescale/timescaledb/pull/7899) Vectorized decompression and filtering for boolean columns\n* [#7915](https://github.com/timescale/timescaledb/pull/7915) New option `refresh_newest_first` to continuous aggregate refresh policy API\n* [#7917](https://github.com/timescale/timescaledb/pull/7917) Remove `_timescaledb_functions.create_chunk_table` function\n* [#7929](https://github.com/timescale/timescaledb/pull/7929) Add `CREATE TABLE ... WITH` API for creating hypertables\n* [#7946](https://github.com/timescale/timescaledb/pull/7946) Add support for splitting a chunk\n* [#7958](https://github.com/timescale/timescaledb/pull/7958) Allow custom names for jobs\n* [#7972](https://github.com/timescale/timescaledb/pull/7972) Add vectorized filtering for constraint checking while backfilling into compressed chunks\n* [#7976](https://github.com/timescale/timescaledb/pull/7976) Include continuous aggregate name in jobs informational view\n* [#7977](https://github.com/timescale/timescaledb/pull/7977) Replace references to compression with columnstore\n* [#7981](https://github.com/timescale/timescaledb/pull/7981) Add columnstore as alias for `enable_columnstore `in `ALTER TABLE`\n* [#7983](https://github.com/timescale/timescaledb/pull/7983) Support for SkipScan over compressed data\n* [#7991](https://github.com/timescale/timescaledb/pull/7991) Improves default `segmentby` options\n* [#7992](https://github.com/timescale/timescaledb/pull/7992) Add API into hypertable invalidation log\n* [#8000](https://github.com/timescale/timescaledb/pull/8000) Add primary dimension info to information schema\n* [#8005](https://github.com/timescale/timescaledb/pull/8005) Support `ALTER TABLE SET (timescaledb.chunk_time_interval='1 day')`\n* [#8012](https://github.com/timescale/timescaledb/pull/8012) Add event triggers support on chunk creation\n* [#8014](https://github.com/timescale/timescaledb/pull/8014) Enable bool compression by default by setting `timescaledb.enable_bool_compression=true`. Note: for downgrading to `2.18` or earlier version, use [this downgrade script](https://github.com/timescale/timescaledb-extras/blob/master/utils/2.19.0-downgrade_new_compression_algorithms.sql)\n* [#8018](https://github.com/timescale/timescaledb/pull/8018) Add spin-lock during recompression on unique constraints\n* [#8026](https://github.com/timescale/timescaledb/pull/8026) Allow `WHERE` conditions that use nonvolatile functions to be pushed down to the compressed scan level. For example, conditions like `time > now()`, where `time` is a columnstore `orderby` column, will evaluate `now()` and use the sparse index on `time` to filter out the entire compressed batches that cannot contain matching rows.\n* [#8027](https://github.com/timescale/timescaledb/pull/8027) Add materialization invalidations API\n* [#8047](https://github.com/timescale/timescaledb/pull/8027) Support SkipScan for `SELECT DISTINCT` with multiple distincts when all but one distinct is pinned\n* [#8115](https://github.com/timescale/timescaledb/pull/8115) Add batch size limiting during compression\n\n**Bugfixes**\n* [#7862](https://github.com/timescale/timescaledb/pull/7862) Release cache pin when checking for `NOT NULL`\n* [#7909](https://github.com/timescale/timescaledb/pull/7909) Update compression stats when merging chunks\n* [#7928](https://github.com/timescale/timescaledb/pull/7928) Don't create a hypertable for implicitly published tables\n* [#7982](https://github.com/timescale/timescaledb/pull/7982) Fix crash in batch sort merge over eligible expressions\n* [#8008](https://github.com/timescale/timescaledb/pull/8008) Fix compression policy error message that shows number of successes\n* [#8031](https://github.com/timescale/timescaledb/pull/8031) Fix reporting of deleted tuples for direct batch delete\n* [#8033](https://github.com/timescale/timescaledb/pull/8033) Skip default `segmentby` if `orderby` is explicitly set\n* [#8061](https://github.com/timescale/timescaledb/pull/8061) Ensure settings for a compressed relation are found\n* [#7515](https://github.com/timescale/timescaledb/pull/7515) Add missing lock to Constraint-aware append\n* [#8067](https://github.com/timescale/timescaledb/pull/8067) Make sure hypercore TAM parent is vacuumed\n* [#8074](https://github.com/timescale/timescaledb/pull/8074) Fix memory leak in row compressor flush\n* [#8099](https://github.com/timescale/timescaledb/pull/8099) Block chunk merging on multi-dimensional hypertables\n* [#8106](https://github.com/timescale/timescaledb/pull/8106) Fix segfault when adding unique compression indexes to compressed chunks\n* [#8127](https://github.com/timescale/timescaledb/pull/8127) Read bit-packed version of booleans\n\n**GUCs**\n* `timescaledb.enable_sparse_index_bloom`: Enable creation of the bloom1 sparse index on compressed chunks; Default: `ON`\n* `timescaledb.compress_truncate_behaviour`: Defines how truncate behaves at the end of compression; Default: `truncate_only`\n* `timescaledb.enable_compression_ratio_warnings`: Enable warnings for poor compression ratio; Default: `ON`\n* `timescaledb.enable_event_triggers`: Enable event triggers for chunks creation; Default: `OFF`\n* `timescaledb.enable_cagg_window_functions`: Enable window functions in continuous aggregates; Default: `OFF`\n\n**Thanks**\n* @arajkumar for reporting that implicitly published tables were still able to create hypertables\n* @thotokraa for reporting an issue with unique expression indexes on compressed chunks\n\n## 2.19.3 (2025-04-15)\n\nThis release contains bug fixes since the 2.19.2 release. We recommend that you upgrade at the next available opportunity.\n\n**Bug fixes**\n* [#7893](https://github.com/timescale/timescaledb/pull/7893) Don't capture hard errors in with-clause parser\n* [#7903](https://github.com/timescale/timescaledb/pull/7903) Don't capture hard errors for old cagg format\n* [#7912](https://github.com/timescale/timescaledb/pull/7912) Don't capture errors estimating time max spread\n* [#7910](https://github.com/timescale/timescaledb/pull/7910) Fix not using SkipScan over one chunk\n* [#7913](https://github.com/timescale/timescaledb/pull/7913) Allow TAM chunk creation as non-owner\n* [#7935](https://github.com/timescale/timescaledb/pull/7935) Fix TAM segfault on DELETE using segmentby column\n* [#7954](https://github.com/timescale/timescaledb/pull/7954) Allow scheduler restarts to be disabled\n* [#7964](https://github.com/timescale/timescaledb/pull/7964) Crash when grouping by multiple columns of a compressed table, one of which is a UUID segmentby column.\n\n## 2.19.2 (2025-04-07)\n\nThis release contains bug fixes since the 2.19.1 release. We recommend that you upgrade at the next available opportunity.\n\n**Features**\n* [#7923](https://github.com/timescale/timescaledb/pull/7923) Add a GUC to set a compression batch size limit\n\n**Bugfixes**\n* [#7911](https://github.com/timescale/timescaledb/pull/7911) Don't create a Hypertable for published tables\n* [#7902](https://github.com/timescale/timescaledb/pull/7902) Fix crash in VectorAgg plan code\n\n**GUCs**\n* `compression_batch_size_limit`: set batch size limit, default: `1000`\n\n**Thanks**\n* @soedirgo for reporting that published tables don't get dropped when they have a hypertable*\n\n## 2.19.1 (2025-04-01)\n\nThis release contains bug fixes since the 2.19.0 release. We recommend that you upgrade at the next available opportunity.\n\n**Bugfixes**\n* [#7816](https://github.com/timescale/timescaledb/pull/7816) Fix `ORDER BY` for direct queries on partial chunks\n* [#7834](https://github.com/timescale/timescaledb/pull/7834) Avoid unnecessary scheduler restarts\n* [#7837](https://github.com/timescale/timescaledb/pull/7837) Ignore frozen chunks in compression policy\n* [#7850](https://github.com/timescale/timescaledb/pull/7850) Add `is_current_xact_tuple` to Arrow TTS\n* [#7890](https://github.com/timescale/timescaledb/pull/7890) Flush error state before doing anything else\n\n**Thanks**\n* @bjornuppeke for reporting a problem with `INSERT INTO ... ON CONFLICT DO NOTHING` on compressed chunks\n* @kav23alex for reporting a segmentation fault on `ALTER TABLE` with `DEFAULT`\n\n## 2.19.0 (2025-03-18)\n\nThis release contains performance improvements and bug fixes since the 2.18.2 release. We recommend that you upgrade at the next available opportunity.\n\n* Improved concurrency of `INSERT`, `UPDATE`, and `DELETE` operations on the columnstore by no longer blocking DML statements during the recompression of a chunk.\n* Improved system performance during continuous aggregate refreshes by breaking them into smaller batches. This reduces systems pressure and minimizes the risk of spilling to disk.\n* Faster and more up-to-date results for queries against continuous aggregates by materializing the most recent data first, as opposed to old data first in prior versions.\n* Faster analytical queries with SIMD vectorization of aggregations over text columns and `GROUP BY` over multiple columns.\n* Enable optimizing the chunk size for better query performance in the columnstore by merging them with `merge_chunk`.\n\n**Deprecation warning**\n\nThis is the last minor release supporting PostgreSQL 14. Starting with the next minor version of TimescaleDB, only Postgres 15, 16, and 17 will be supported.\n\n**Downgrading of 2.19.0**\n\nThis release introduces custom bool compression. If you enable this feature with `enable_bool_compression` and need to revert it, use [this script](https://github.com/timescale/timescaledb-extras/blob/master/utils/2.19.0-downgrade_new_compression_algorithms.sql) to convert the columns back to their previous state. TimescaleDB versions prior to 2.19.0 do not support this new type.\n\n**Features**\n* [#7586](https://github.com/timescale/timescaledb/pull/7586) Vectorized aggregation with grouping by a single text column\n* [#7632](https://github.com/timescale/timescaledb/pull/7632) Optimize recompression for chunks without segmentby\n* [#7655](https://github.com/timescale/timescaledb/pull/7655) Support vectorized aggregation on hypercore TAM\n* [#7669](https://github.com/timescale/timescaledb/pull/7669) Add support for merging compressed chunks\n* [#7701](https://github.com/timescale/timescaledb/pull/7701) Implement a custom compression algorithm for bool columns. It is in early access and can undergo backwards-incompatible changes. For testing, enable it using `timescaledb.enable_bool_compression = on`\n* [#7707](https://github.com/timescale/timescaledb/pull/7707) Support `ALTER COLUMN SET NOT NULL` on compressed chunks\n* [#7765](https://github.com/timescale/timescaledb/pull/7765) Allow `tsdb` as alias for `timescaledb` in `WITH` and `SET` clauses\n* [#7786](https://github.com/timescale/timescaledb/pull/7786) Show a warning for inefficient `compress_chunk_time_interval` configuration\n* [#7788](https://github.com/timescale/timescaledb/pull/7788) Add a callback to `mem_guard` for background workers\n* [#7789](https://github.com/timescale/timescaledb/pull/7789) Do not recompress segmentwise when default order by is empty\n* [#7790](https://github.com/timescale/timescaledb/pull/7790) Add a configurable incremental CAgg refresh policy\n\n**Bugfixes**\n* [#7665](https://github.com/timescale/timescaledb/pull/7665) Block merging of frozen chunks\n* [#7673](https://github.com/timescale/timescaledb/pull/7673) Don't abort additional `INSERT`s when hitting the first conflict\n* [#7714](https://github.com/timescale/timescaledb/pull/7714) Fix the wrong result when compressed `NULL` values were confused with default values. This happened for a very particular order of events.\n* [#7747](https://github.com/timescale/timescaledb/pull/7747) Block TAM rewrites with incompatible GUC setting\n* [#7748](https://github.com/timescale/timescaledb/pull/7748) Crash in the segmentwise recompression\n* [#7764](https://github.com/timescale/timescaledb/pull/7764) Fix compression settings handling in hypercore TAM\n* [#7768](https://github.com/timescale/timescaledb/pull/7768) Remove costing index scan of hypertable parent\n* [#7799](https://github.com/timescale/timescaledb/pull/7799) Handle the `DEFAULT` table access name in `ALTER TABLE`\n\n**GUCs**\n* `enable_bool_compression`: enable the BOOL compression algorithm, default: `OFF`\n* `enable_exclusive_locking_recompression`: enable exclusive locking during recompression (legacy mode), default: `OFF`\n\n**Thanks**\n* @bjornuppeke for reporting a problem with `INSERT INTO ... ON CONFLICT DO NOTHING` on compressed chunks\n* @kav23alex for reporting a segmentation fault on `ALTER TABLE with DEFAULT`\n\n## 2.18.2 (2025-02-19)\n\nThis release contains performance improvements and bug fixes since\nthe 2.18.1 release. We recommend that you upgrade at the next\navailable opportunity.\n\n**Bugfixes**\n* [#7686](https://github.com/timescale/timescaledb/pull/7686) Potential wrong aggregation result when using vectorized aggregation with hash grouping in reverse order\n* [#7694](https://github.com/timescale/timescaledb/pull/7694) Fix ExplainHook breaking call chain\n* [#7695](https://github.com/timescale/timescaledb/pull/7695) Block dropping internal compressed chunks with `drop_chunk()`\n* [#7711](https://github.com/timescale/timescaledb/pull/7711) License error when using hypercore handler\n* [#7712](https://github.com/timescale/timescaledb/pull/7712) Respect other extensions' ExecutorStart hooks\n\n**Thanks**\n* @davidmehren and @jflambert for reporting an issue with extension hooks\n* @jflambert for reporting a bug with license errors shown in autovacuum\n\n## 2.18.1 (2025-02-10)\n\nThis release contains performance improvements and bug fixes since\nthe 2.18.0 release. We recommend that you upgrade at the next\navailable opportunity.\n\n**Features**\n* [#7656](https://github.com/timescale/timescaledb/pull/7656) Remove limitation of compression policy for continuous aggregates\n\n**Bugfixes**\n* [#7600](https://github.com/timescale/timescaledb/pull/7600) Fix lock order when dropping index\n* [#7637](https://github.com/timescale/timescaledb/pull/7637) Allow EXPLAIN in read-only mode\n* [#7645](https://github.com/timescale/timescaledb/pull/7645) Fix DELETE on compressed chunk with non-btree operators\n* [#7649](https://github.com/timescale/timescaledb/pull/7649) Allow non-btree operator pushdown in UPDATE/DELETE queries on compressed chunks\n* [#7653](https://github.com/timescale/timescaledb/pull/7653) Push down orderby scankeys to Hypercore TAM\n* [#7665](https://github.com/timescale/timescaledb/pull/7665) Block merging of frozen chunks\n* [#7673](https://github.com/timescale/timescaledb/pull/7673) Don't abort additional INSERTs when hitting first conflict\n\n**GUCs**\n* `enable_hypercore_scankey_pushdown`: Push down qualifiers as scankeys when using Hypercore TAM introduced with [#7653](https://github.com/timescale/timescaledb/pull/7653)\n\n**Thanks**\n* @bjornuppeke for reporting a problem with INSERT INTO ... ON CONFLICT DO NOTHING on compressed chunks\n* @ikalafat for reporting a problem with EXPLAIN in read-only mode\n* Timescale community members Jacob and pantonis for reporting issues with slow queries.\n\n## 2.18.0 (2025-01-23)\n\nThis release introduces the ability to add secondary indexes to the columnstore, improves group by and filtering performance through columnstore vectorization, and contains the highly upvoted community request of transition table support. We recommend that you upgrade at the next available opportunity.\n\n**Highlighted features in TimescaleDB v2.18.0**\n\n* The ability to add secondary indexes to the columnstore through the new hypercore table access method.\n* Significant performance improvements through vectorization (`SIMD`) for aggregations using a group by with one column and/or using a filter clause when querying the columnstore.\n* Hypertables support triggers for transition tables, which is one of the most upvoted community feature requests.\n* Updated methods to manage Timescale's hybrid row-columnar store (hypercore) that highlight the usage of the columnstore which includes both an optimized columnar format as well as compression.\n\n**Dropping support for Bitnami images**\n\nAfter the recent change in Bitnami’s [LTS support policy](https://github.com/bitnami/containers/issues/75671), we are no longer building Bitnami images for TimescaleDB. We recommend using the [official TimescaleDB Docker image](https://hub.docker.com/r/timescale/timescaledb-ha)\n\n**Deprecation Notice**\n\nWe are deprecating the following parameters, functions, procedures and views. They will be removed with the next major release of TimescaleDB. Please find the replacements in the table below:\n\n| Deprecated | Replacement | Type |\n| --- | --- | --- |\n| decompress_chunk | convert_to_rowstore | Procedure |\n| compress_chunk | convert_to_columnstore | Procedure |\n| add_compression_policy | add_columnstore_policy | Function |\n| remove_compression_policy | remove_columnstore_policy | Function |\n| hypertable_compression_stats | hypertable_columnstore_stats | Function |\n| chunk_compression_stats | chunk_columnstore_stats | Function |\n| hypertable_compression_settings | hypertable_columnstore_settings | View |\n| chunk_compression_settings | chunk_columnstore_settings | View |\n| compression_settings | columnstore_settings | View |\n| timescaledb.compress | timescaledb.enable_columnstore | Parameter |\n| timescaledb.compress_segmentby | timescaledb.segmentby | Parameter |\n| timescaledb.compress_orderby  | timescaledb.orderby | Parameter |\n\n**Features**\n* #7341: Vectorized aggregation with grouping by one fixed-size by-value compressed column (such as arithmetic types).\n* #7104: Hypercore table access method.\n* #6901: Add hypertable support for transition tables.\n* #7482: Optimize recompression of partially compressed chunks.\n* #7458: Support vectorized aggregation with aggregate `filter` clauses that are also vectorizable.\n* #7433: Add support for merging chunks.\n* #7271: Push down `order by` in real-time continuous aggregate queries.\n* #7455: Support `drop not null` on compressed hypertables.\n* #7295: Support `alter table set access method` on hypertable.\n* #7411: Change parameter name to enable hypercore table access method.\n* #7436: Add index creation on `order by` columns.\n* #7443: Add hypercore function and view aliases.\n* #7521: Add optional `force` argument to `refresh_continuous_aggregate`.\n* #7528: Transform sorting on `time_bucket` to sorting on time for compressed chunks in some cases.\n* #7565: Add hint when hypertable creation fails.\n* #7390: Disable custom `hashagg` planner code.\n* #7587: Add `include_tiered_data` parameter to `add_continuous_aggregate_policy` API.\n* #7486: Prevent building against PostgreSQL versions with broken ABI.\n* #7412: Add [GUC](https://www.postgresql.org/docs/current/acronyms.html#:~:text=GUC) for the `hypercore_use_access_method` default.\n* #7413: Add GUC for segmentwise recompression.\n\n**Bugfixes**\n* #7378: Remove obsolete job referencing `policy_job_error_retention`.\n* #7409: Update `bgw_job` table when altering procedure.\n* #7410: Fix the `aggregated compressed column not found` error on aggregation query.\n* #7426: Fix `datetime` parsing error in chunk constraint creation.\n* #7432: Verify that the heap tuple is valid before using.\n* #7434: Fix the segfault when internally setting the replica identity for a given chunk.\n* #7488: Emit error for transition table trigger on chunks.\n* #7514: Fix the error: `invalid child of chunk append`.\n* #7517: Fix the performance regression on the `cagg_migrate` procedure.\n* #7527: Restart scheduler on error.\n* #7557: Fix null handling for in-memory tuple filtering.\n* #7566: Improve transaction check in CAGG refresh.\n* #7584: Fix NaN-handling for vectorized aggregation.\n* #7598: Match the Postgres NaN comparison behavior in WHERE clause over compressed tables.\n\n**Thanks**\n* @bharrisau for reporting the segfault when creating chunks.\n* @jakehedlund for reporting the incompatible NaN behavior in WHERE clause over compressed tables.\n* @k-rus for suggesting that we add a hint when hypertable creation fails.\n* @pgloader for reporting the issue in an internal background job.\n* @staticlibs for sending the pull request that improves the transaction check in CAGG refresh.\n* @uasiddiqi for reporting the `aggregated compressed column not found` error.\n\n## 2.17.2 (2024-11-06)\n\nThis release contains bug fixes since the 2.17.1 release. We recommend that you\nupgrade at the next available opportunity.\n\n**Bugfixes**\n* #7384 Fix \"negative bitmapset member not allowed\" and performance degradation\non queries to compressed tables with ORDER BY clause matching the order of the\ncompressed data\n* #7388 Use-after-free in vectorized grouping by segmentby columns\n\n**Thanks**\n* @dx034 for reporting an issue with negative bitmapset members due to large OIDs\n\n## 2.17.1 (2024-10-21)\n\nThis release contains performance improvements and bug fixes since\nthe 2.17.0 release. We recommend that you upgrade at the next\navailable opportunity.\n\n**Features**\n* #7360 Add chunk skipping GUC\n\n**Bugfixes**\n* #7335 Change log level used in compression\n* #7342 Fix collation for in-memory tuple filtering\n\n**Thanks**\n* @gmilamjr for reporting an issue with the log level of compression messages\n* @hackbnw for reporting an issue with collation during tuple filtering\n\n## 2.17.0 (2024-10-08)\n\nThis release adds support for PostgreSQL 17, significantly improves the performance of continuous aggregate refreshes,\nand contains performance improvements for analytical queries and delete operations over compressed hypertables.\nWe recommend that you upgrade at the next available opportunity.\n\n**Highlighted features in TimescaleDB v2.17.0**\n\n* Full PostgreSQL 17 support for all existing features. TimescaleDB v2.17 is available for PostgreSQL 14, 15, 16, and 17.\n\n* Significant performance improvements for continuous aggregate policies: continuous aggregate refresh is now using\n`merge` instead of deleting old materialized data and re-inserting.\n\n  This update can decrease dramatically the amount of data that must be written on the continuous aggregate in the\n  presence of a small number of changes, reduce the `i/o` cost of refreshing a continuous aggregate, and generate fewer\n  Write-Ahead Logs (`WAL`).\n  Overall, continuous aggregate policies will be more lightweight, use less system resources, and complete faster.\n\n* Increased performance for real-time analytical queries over compressed hypertables:\n  we are excited to introduce additional Single Instruction, Multiple Data (`SIMD`) vectorization optimization to our\n  engine by supporting vectorized execution for queries that group by using the `segment_by` column(s) and\n  aggregate using the basic aggregate functions (`sum`, `count`, `avg`, `min`, `max`).\n\n  Stay tuned for more to come in follow-up releases! Support for grouping on additional columns, filtered aggregation,\n  vectorized expressions, and `time_bucket` is coming soon.\n\n* Improved performance of deletes on compressed hypertables when a large amount of data is affected.\n\n  This improvement speeds up operations that delete whole segments by skipping the decompression step.\n  It is enabled for all deletes that filter by the `segment_by` column(s).\n\n**PostgreSQL 14 deprecation announcement**\n\n  We will continue supporting PostgreSQL 14 until April 2025. Closer to that time, we will announce the specific\n  version of TimescaleDB in which PostgreSQL 14 support will not be included going forward.\n\n**Features**\n* #6882: Allow delete of full segments on compressed chunks without decompression.\n* #7033: Use `merge` statement on continuous aggregates refresh.\n* #7126: Add functions to show the compression information.\n* #7147: Vectorize partial aggregation for `sum(int4)` with grouping on `segment by` columns.\n* #7204: Track additional extensions in telemetry.\n* #7207: Refactor the `decompress_batches_scan` functions for easier maintenance.\n* #7209: Add a function to drop the `osm` chunk.\n* #7275: Add support for the `returning` clause for `merge`.\n* #7200: Vectorize common aggregate functions like `min`, `max`, `sum`, `avg`, `stddev`, `variance` for compressed columns\n  of arithmetic types, when there is grouping on `segment by` columns or no grouping.\n\n**Bug fixes**\n* #7187: Fix the string literal length for the `compressed_data_info` function.\n* #7191: Fix creating default indexes on chunks when migrating the data.\n* #7195: Fix the `segment by` and `order by` checks when dropping a column from a compressed hypertable.\n* #7201: Use the generic extension description when building `apt` and `rpm` loader packages.\n* #7227: Add an index to the `compression_chunk_size` catalog table.\n* #7229: Fix the foreign key constraints where the index and the constraint column order are different.\n* #7230: Do not propagate the foreign key constraints to the `osm` chunk.\n* #7234: Release the cache after accessing the cache entry.\n* #7258: Force English in the `pg_config` command executed by `cmake` to avoid the unexpected building errors.\n* #7270: Fix the memory leak in compressed DML batch filtering.\n* #7286: Fix the index column check while searching for the index.\n* #7290: Add check for null offset for continuous aggregates built on top of continuous aggregates.\n* #7301: Make foreign key behavior for hypertables consistent.\n* #7318: Fix chunk skipping range filtering.\n* #7320: Set the license specific extension comment in the install script.\n\n**Thanks**\n* @MiguelTubio for reporting and fixing the Windows build error.\n* @posuch for reporting the misleading extension description in the generic loader packages.\n* @snyrkill for discovering and reporting the issue with continuous aggregates built on top of continuous aggregates.\n\n## 2.16.1 (2024-08-06)\n\nThis release contains bug fixes since the 2.16.0 release. We recommend\nthat you upgrade at the next available opportunity.\n\n**Bugfixes**\n* #7182 Fix untier_chunk for hypertables with foreign keys\n\n## 2.16.0 (2024-07-31)\n\nThis release contains significant performance improvements when working with compressed data, extended join\nsupport in continuous aggregates, and the ability to define foreign keys from regular tables towards hypertables.\nWe recommend that you upgrade at the next available opportunity.\n\nIn TimescaleDB v2.16.0 we:\n\n* Introduce multiple performance focused optimizations for data manipulation operations (DML) over compressed chunks.\n\n  Improved upsert performance by more than 100x in some cases and more than 1000x in some update/delete scenarios.\n\n* Add the ability to define chunk skipping indexes on non-partitioning columns of compressed hypertables\n\n  TimescaleDB v2.16.0 extends chunk exclusion to use those skipping (sparse) indexes when queries filter on the relevant columns,\n  and prune chunks that do not include any relevant data for calculating the query response.\n\n* Offer new options for use cases that require foreign keys defined.\n\n  You can now add foreign keys from regular tables towards hypertables. We have also removed\n  some really annoying locks in the reverse direction that blocked access to referenced tables\n  while compression was running.\n\n* Extend Continuous Aggregates to support more types of analytical queries.\n\n  More types of joins are supported, additional equality operators on join clauses, and\n  support for joins between multiple regular tables.\n\n**Highlighted features in this release**\n\n* Improved query performance through chunk exclusion on compressed hypertables.\n\n  You can now define chunk skipping indexes on compressed chunks for any column with one of the following\n  integer data types: `smallint`, `int`, `bigint`, `serial`, `bigserial`, `date`, `timestamp`, `timestamptz`.\n\n  After you call `enable_chunk_skipping` on a column, TimescaleDB tracks the min and max values for\n  that column. TimescaleDB uses that information to exclude chunks for queries that filter on that\n  column, and would not find any data in those chunks.\n\n* Improved upsert performance on compressed hypertables.\n\n  By using index scans to verify constraints during inserts on compressed chunks, TimescaleDB speeds\n  up some ON CONFLICT clauses by more than 100x.\n\n* Improved performance of updates, deletes, and inserts on compressed hypertables.\n\n  By filtering data while accessing the compressed data and before decompressing, TimescaleDB has\n  improved performance for updates and deletes on all types of compressed chunks, as well as inserts\n  into compressed chunks with unique constraints.\n\n  By signaling constraint violations without decompressing, or decompressing only when matching\n  records are found in the case of updates, deletes and upserts, TimescaleDB v2.16.0 speeds\n  up those operations more than 1000x in some update/delete scenarios, and 10x for upserts.\n\n* You can add foreign keys from regular tables to hypertables, with support for all types of cascading options.\n  This is useful for hypertables that partition using sequential IDs, and need to reference those IDs from other tables.\n\n* Lower locking requirements during compression for hypertables with foreign keys\n\n  Advanced foreign key handling removes the need for locking referenced tables when new chunks are compressed.\n  DML is no longer blocked on referenced tables while compression runs on a hypertable.\n\n* Improved support for queries on Continuous Aggregates\n\n  `INNER/LEFT` and `LATERAL` joins are now supported. Plus, you can now join with multiple regular tables,\n  and you can have more than one equality operator on join clauses.\n\n**PostgreSQL 13 support removal announcement**\n\nFollowing the deprecation announcement for PostgreSQL 13 in TimescaleDB v2.13,\nPostgreSQL 13 is no longer supported in TimescaleDB v2.16.\n\nThe Currently supported PostgreSQL major versions are 14, 15 and 16.\n\n**Features**\n* #6880: Add support for the array operators used for compressed DML batch filtering.\n* #6895: Improve the compressed DML expression pushdown.\n* #6897: Add support for replica identity on compressed hypertables.\n* #6918: Remove support for PG13.\n* #6920: Rework compression activity wal markers.\n* #6989: Add support for foreign keys when converting plain tables to hypertables.\n* #7020: Add support for the chunk column statistics tracking.\n* #7048: Add an index scan for INSERT DML decompression.\n* #7075: Reduce decompression on the compressed INSERT.\n* #7101: Reduce decompressions for the compressed UPDATE/DELETE.\n* #7108 Reduce decompressions for INSERTs with UNIQUE constraints\n* #7116 Use DELETE instead of TRUNCATE after compression\n* #7134 Refactor foreign key handling for compressed hypertables\n* #7161 Fix `mergejoin input data is out of order`\n\n**Bugfixes**\n* #6987 Fix REASSIGN OWNED BY for background jobs\n* #7018: Fix `search_path` quoting in the compression defaults function.\n* #7046: Prevent locking for compressed tuples.\n* #7055: Fix the `scankey` for `segment by` columns, where the type `constant` is different to `variable`.\n* #7064: Fix the bug in the default `order by` calculation in compression.\n* #7069: Fix the index column name usage.\n* #7074: Fix the bug in the default `segment by` calculation in compression.\n\n**Thanks**\n* @jledentu For reporting a problem with mergejoin input order\n\n\n## 2.15.3 (2024-07-02)\n\nThis release contains bug fixes since the 2.15.2 release.\nBest practice is to upgrade at the next available opportunity.\n\n**Migrating from self-hosted TimescaleDB v2.14.x and earlier**\n\nAfter you run `ALTER EXTENSION`, you must run [this SQL script](https://github.com/timescale/timescaledb-extras/blob/master/utils/2.15.X-fix_hypertable_foreign_keys.sql). For more details, see the following pull request [#6797](https://github.com/timescale/timescaledb/pull/6797).\n\nIf you are migrating from TimescaleDB v2.15.0, v2.15.1 or v2.15.2, no changes are required.\n\n**Bugfixes**\n* #7035: Fix the error when acquiring a tuple lock on the OSM chunks on the replica.\n* #7061: Fix the handling of multiple unique indexes in a compressed INSERT.\n* #7080: Fix the `corresponding equivalence member not found` error.\n* #7088: Fix the leaks in the DML functions.\n* #7091: Fix ORDER BY/GROUP BY expression not found in targetlist on PG16\n\n**Thanks**\n* @Kazmirchuk for reporting the issue about leaks with the functions in DML.\n\n## 2.15.2 (2024-06-07)\n\nThis release contains bug fixes since the 2.15.1 release. Best\npractice is to upgrade at the next available opportunity.\n\n**Migrating from self-hosted TimescaleDB v2.14.x and earlier**\n\nAfter you run `ALTER EXTENSION`, you must run [this SQL script](https://github.com/timescale/timescaledb-extras/blob/master/utils/2.15.X-fix_hypertable_foreign_keys.sql). For more details, see the following pull request [#6797](https://github.com/timescale/timescaledb/pull/6797).\n\nIf you are migrating from TimescaleDB v2.15.0 or v2.15.1, no changes are required.\n\n**Bugfixes**\n* #6975: Fix sort pushdown for partially compressed chunks.\n* #6976: Fix removal of metadata function and update script.\n* #6978: Fix segfault in `compress_chunk` with a primary space partition.\n* #6993: Disallow hash partitioning on primary column.\n\n**Thanks**\n* @gugu for reporting the issue with catalog corruption due to update.\n* @srieding for reporting an issue with partially compressed chunks and ordering on joined columns.\n\n## 2.15.1 (2024-05-28)\n\nThis release contains bug fixes since the 2.15.0 release.\nBest practice is to upgrade at the next available opportunity.\n\n**Migrating from self-hosted TimescaleDB v2.14.x and earlier**\n\nAfter you run `ALTER EXTENSION`, you must run [this SQL script](https://github.com/timescale/timescaledb-extras/blob/master/utils/2.15.X-fix_hypertable_foreign_keys.sql). For more details, see the following pull request [#6797](https://github.com/timescale/timescaledb/pull/6797).\n\nIf you are migrating from TimescaleDB v2.15.0, no changes are required.\n\n**Bugfixes**\n* #6540: Segmentation fault when you backfill data using COPY into a compressed chunk.\n* #6858: `BEFORE UPDATE` trigger not working correctly.\n* #6908: Fix `time_bucket_gapfill()` with timezone behaviour around daylight savings time (DST) switches.\n* #6911: Fix dropped chunk metadata removal in the update script.\n* #6940: Fix `pg_upgrade` failure by removing `regprocedure` from the catalog table.\n* #6957: Fix then `segfault` in UNION queries that contain ordering on compressed chunks.\n\n**Thanks**\n* @DiAifU, @kiddhombre and @intermittentnrg for reporting the issues with gapfill and daylight saving time.\n* @edgarzamora for reporting the issue with update triggers.\n* @hongquan for reporting the issue with the update script.\n* @iliastsa and @SystemParadox for reporting the issue with COPY into a compressed chunk.\n\n## 2.15.0 (2024-05-08)\n\nThis release contains performance improvements and bug fixes since\nthe 2.14.2 release. Best practice is to upgrade at the next\navailable opportunity.\n\nIn addition, it includes these noteworthy features:\n* Support `time_bucket` with `origin` and/or `offset` on Continuous Aggregate\n* Compression improvements:\n  - Improve expression pushdown\n  - Add minmax sparse indexes when compressing columns with btree indexes\n  - Improve compression setting defaults\n  - Vectorize filters in WHERE clause that contain text equality operators and LIKE expressions\n\n**Deprecation warning**\n* Starting on this release will not be possible to create Continuous Aggregate using `time_bucket_ng` anymore and it will be completely removed on the upcoming releases.\n* Recommend users to [migrate their old Continuous Aggregate format to the new one](https://docs.timescale.com/use-timescale/latest/continuous-aggregates/migrate/) because it support will be completely removed in next releases prevent them to migrate.\n* This is the last release supporting PostgreSQL 13.\n\n**Migrating from self-hosted TimescaleDB v2.14.x and earlier**\n\nAfter you run `ALTER EXTENSION`, you must run [this SQL script](https://github.com/timescale/timescaledb-extras/blob/master/utils/2.15.X-fix_hypertable_foreign_keys.sql). For more details, see the following pull request [#6797](https://github.com/timescale/timescaledb/pull/6797).\n\n**Features**\n* #6382: Support for `time_bucket` with `origin` and `offset` in CAggs.\n* #6696: Improve defaults for compression `segment_by` and `order_by`.\n* #6705: Add sparse minmax indexes for compressed columns that have uncompressed btree indexes.\n* #6754: Allow `DROP CONSTRAINT` on compressed hypertables.\n* #6767: Add metadata table `_timestaledb_internal.bgw_job_stat_history` for tracking job execution history.\n* #6798: Prevent usage of deprecated `time_bucket_ng` in CAgg definition.\n* #6810: Add telemetry for access methods.\n* #6811: Remove no longer relevant `timescaledb.allow_install_without_preload` GUC.\n* #6837: Add migration path for CAggs using `time_bucket_ng`.\n* #6865: Update the watermark when truncating a CAgg.\n\n**Bugfixes**\n* #6617: Fix error in show_chunks.\n* #6621: Remove metadata when dropping chunks.\n* #6677: Fix snapshot usage in CAgg invalidation scanner.\n* #6698: Define meaning of 0 retries for jobs as no retries.\n* #6717: Fix handling of compressed tables with primary or unique index in COPY path.\n* #6726: Fix constify cagg_watermark using window function when querying a CAgg.\n* #6729: Fix NULL start value handling in CAgg refresh.\n* #6732: Fix CAgg migration with custom timezone / date format settings.\n* #6752: Remove custom autovacuum setting from compressed chunks.\n* #6770: Fix plantime chunk exclusion for OSM chunk.\n* #6789: Fix deletes with subqueries and compression.\n* #6796: Fix a crash involving a view on a hypertable.\n* #6797: Fix foreign key constraint handling on compressed hypertables.\n* #6816: Fix handling of chunks with no contraints.\n* #6820: Fix a crash when the ts_hypertable_insert_blocker was called directly.\n* #6849: Use non-orderby compressed metadata in compressed DML.\n* #6867: Clean up compression settings when deleting compressed cagg.\n* #6869: Fix compressed DML with constraints of form value OP column.\n* #6870: Fix bool expression pushdown for queries on compressed chunks.\n\n**Thanks**\n* @brasic for reporting a crash when the ts_hypertable_insert_blocker was called directly.\n* @bvanelli for reporting an issue with the jobs retry count.\n* @djzurawsk for reporting error when dropping chunks.\n* @Dzuzepppe for reporting an issue with DELETEs using subquery on compressed chunk working incorrectly.\n* @hongquan for reporting a `timestamp out of range` error during CAgg migrations.\n* @kevcenteno for reporting an issue with the show_chunks API showing incorrect output when `created_before/created_after` was used with time-partitioned columns.\n* @mahipv for starting working on the job history PR.\n* @rovo89 for reporting constify cagg_watermark not working using window function when querying a CAgg.\n\n## 2.14.2 (2024-02-20)\n\nThis release contains bug fixes since the 2.14.1 release.\nWe recommend that you upgrade at the next available opportunity.\n\n**Bugfixes**\n* #6655 Fix segfault in cagg_validate_query\n* #6660 Fix refresh on empty CAgg with variable bucket\n* #6670 Don't try to compress osm chunks\n\n**Thanks**\n* @kav23alex for reporting a segfault in cagg_validate_query\n\n## 2.14.1 (2024-02-14)\n\nThis release contains bug fixes since the 2.14.0 release.\nWe recommend that you upgrade at the next available opportunity.\n\n**Features**\n* #6630 Add views for per chunk compression settings\n\n**Bugfixes**\n* #6636 Fixes extension update of compressed hypertables with dropped columns\n* #6637 Reset sequence numbers on non-rollup compression\n* #6639 Disable default indexscan for compression\n* #6651 Fix DecompressChunk path generation with per chunk settings\n\n**Thanks**\n* @anajavi for reporting an issue with extension update of compressed hypertables\n\n## 2.14.0 (2024-02-08)\n\nThis release contains performance improvements and bug fixes since\nthe 2.13.1 release. We recommend that you upgrade at the next\navailable opportunity.\n\nIn addition, it includes these noteworthy features:\n\n* Ability to change compression settings on existing compressed hypertables at any time.\nNew compression settings take effect on any new chunks that are compressed after the change.\n* Reduced locking requirements during chunk recompression\n* Limiting tuple decompression during DML operations to avoid decompressing a lot of tuples and causing storage issues (100k limit, configurable)\n* Helper functions for determining compression settings\n* Plan-time chunk exclusion for real-time Continuous Aggregate by constifying the cagg_watermark function call, leading to faster queries using real-time continuous aggregates\n\n**For this release only**, you will need to restart the database before running `ALTER EXTENSION`\n\n**Multi-node support removal announcement**\nFollowing the deprecation announcement for Multi-node in TimescaleDB 2.13,\nMulti-node is no longer supported starting with TimescaleDB 2.14.\n\nTimescaleDB 2.13 is the last version that includes multi-node support. Learn more about it [here](docs/MultiNodeDeprecation.md).\n\nIf you want to migrate from multi-node TimescaleDB to single-node TimescaleDB, read the\n[migration documentation](https://docs.timescale.com/migrate/latest/multi-node-to-timescale-service/).\n\n**Deprecation notice: recompress_chunk procedure**\nTimescaleDB 2.14 is the last version that will include the recompress_chunk procedure. Its\nfunctionality will be replaced by the compress_chunk function, which, starting on TimescaleDB 2.14,\nworks on both uncompressed and partially compressed chunks.\nThe compress_chunk function should be used going forward to fully compress all types of chunks or even recompress\nold fully compressed chunks using new compression settings (through the newly introduced recompress optional parameter).\n\n**Features**\n* #6325 Add plan-time chunk exclusion for real-time CAggs\n* #6360 Remove support for creating Continuous Aggregates with old format\n* #6386 Add functions for determining compression defaults\n* #6410 Remove multinode public API\n* #6440 Allow SQLValueFunction pushdown into compressed scan\n* #6463 Support approximate hypertable size\n* #6513 Make compression settings per chunk\n* #6529 Remove reindex_relation from recompression\n* #6531 Fix if_not_exists behavior for CAgg policy with NULL offsets\n* #6545 Remove restrictions for changing compression settings\n* #6566 Limit tuple decompression during DML operations\n* #6579 Change compress_chunk and decompress_chunk to idempotent version by default\n* #6608 Add LWLock for OSM usage in loader\n* #6609 Deprecate recompress_chunk\n* #6609 Add optional recompress argument to compress_chunk\n\n**Bugfixes**\n* #6541 Inefficient join plans on compressed hypertables.\n* #6491 Enable now() plantime constification with BETWEEN\n* #6494 Fix create_hypertable referenced by fk succeeds\n* #6498 Suboptimal query plans when using time_bucket with query parameters\n* #6507 time_bucket_gapfill with timezones doesn't handle daylight savings\n* #6509 Make extension state available through function\n* #6512 Log extension state changes\n* #6522 Disallow triggers on CAggs\n* #6523 Reduce locking level on compressed chunk index during segmentwise recompression\n* #6531 Fix if_not_exists behavior for CAgg policy with NULL offsets\n* #6571 Fix pathtarget adjustment for MergeAppend paths in aggregation pushdown code\n* #6575 Fix compressed chunk not found during upserts\n* #6592 Fix recompression policy ignoring partially compressed chunks\n* #6610 Ensure qsort comparison function is transitive\n\n**Thanks**\n* @coney21 and @GStechschulte for reporting the problem with inefficient join plans on compressed hypertables.\n* @HollowMan6 for reporting triggers not working on materialized views of\nCAggs\n* @jbx1 for reporting suboptimal query plans when using time_bucket with query parameters\n* @JerkoNikolic for reporting the issue with gapfill and DST\n* @pdipesh02 for working on removing the old Continuous Aggregate format\n* @raymalt and @martinhale for reporting very slow query plans on realtime CAggs queries\n\n## 2.13.1 (2024-01-09)\n\nThis release contains bug fixes since the 2.13.0 release.\nWe recommend that you upgrade at the next available opportunity.\n\n**Bugfixes**\n* #6365 Use numrows_pre_compression in approximate row count\n* #6377 Use processed group clauses in PG16\n* #6384 Change bgw_log_level to use PGC_SUSET\n* #6393 Disable vectorized sum for expressions.\n* #6405 Read CAgg watermark from materialized data\n* #6408 Fix groupby pathkeys for gapfill in PG16\n* #6428 Fix index matching during DML decompression\n* #6439 Fix compressed chunk permission handling on PG16\n* #6443 Fix lost concurrent CAgg updates\n* #6454 Fix unique expression indexes on compressed chunks\n* #6465 Fix use of freed path in decompression sort logic\n\n**Thanks**\n* @MA-MacDonald for reporting an issue with gapfill in PG16\n* @aarondglover for reporting an issue with unique expression indexes on compressed chunks\n* @adriangb for reporting an issue with security barrier views on pg16\n\n## 2.13.0 (2023-11-28)\n\nThis release contains performance improvements, an improved hypertable DDL API\nand bug fixes since the 2.12.2 release. We recommend that you upgrade at the next\navailable opportunity.\n\nIn addition, it includes these noteworthy features:\n\n* Full PostgreSQL 16 support for all existing features\n* Vectorized aggregation execution for sum()\n* Track chunk creation time used in retention/compression policies\n\n**Deprecation notice: Multi-node support**\nTimescaleDB 2.13 is the last version that will include multi-node support. Multi-node\nsupport in 2.13 is available for PostgreSQL 13, 14 and 15. Learn more about it\n[here](docs/MultiNodeDeprecation.md).\n\nIf you want to migrate from multi-node TimescaleDB to single-node TimescaleDB read the\n[migration documentation](https://docs.timescale.com/migrate/latest/multi-node-to-timescale-service/).\n\n**PostgreSQL 13 deprecation announcement**\nWe will continue supporting PostgreSQL 13 until April 2024. Sooner to that time, we will announce the specific version of TimescaleDB in which PostgreSQL 13 support will not be included going forward.\n\n**Starting from TimescaleDB 2.13.0**\n* No Amazon Machine Images (AMI) are published. If you previously used AMI, please\nuse another [installation method](https://docs.timescale.com/self-hosted/latest/install/)\n* Continuous Aggregates are materialized only (non-realtime) by default\n\n**Features**\n* #5575 Add chunk-wise sorted paths for compressed chunks\n* #5761 Simplify hypertable DDL API\n* #5890 Reduce WAL activity by freezing compressed tuples immediately\n* #6050 Vectorized aggregation execution for sum()\n* #6062 Add metadata for chunk creation time\n* #6077 Make Continous Aggregates materialized only (non-realtime) by default\n* #6177 Change show_chunks/drop_chunks using chunk creation time\n* #6178 Show batches/tuples decompressed during DML operations in EXPLAIN output\n* #6185 Keep track of catalog version\n* #6227 Use creation time in retention/compression policy\n* #6307 Add SQL function cagg_validate_query\n\n**Bugfixes**\n* #6188 Add GUC for setting background worker log level\n* #6222 Allow enabling compression on hypertable with unique expression index\n* #6240 Check if worker registration succeeded\n* #6254 Fix exception detail passing in compression_policy_execute\n* #6264 Fix missing bms_del_member result assignment\n* #6275 Fix negative bitmapset member not allowed in compression\n* #6280 Potential data loss when compressing a table with a partial index that matches compression order.\n* #6289 Add support for startup chunk exclusion with aggs\n* #6290 Repair relacl on upgrade\n* #6297 Fix segfault when creating a cagg using a NULL width in time bucket function\n* #6305 Make timescaledb_functions.makeaclitem strict\n* #6332 Fix typmod and collation for segmentby columns\n* #6339 Fix tablespace with constraints\n* #6343 Enable segmentwise recompression in compression policy\n\n**Thanks**\n* @fetchezar for reporting an issue with compression policy error messages\n* @jflambert for reporting the background worker log level issue\n* @torazem for reporting an issue with compression and large oids\n* @fetchezar for reporting an issue in the compression policy\n* @lyp-bobi for reporting an issue with tablespace with constraints\n* @pdipesh02 for contributing to the implementation of the metadata for chunk creation time,\n             the generalized hypertable API, and show_chunks/drop_chunks using chunk creation time\n* @lkshminarayanan for all his work on PG16 support\n\n## 2.12.2 (2023-10-19)\n\nThis release contains bug fixes since the 2.12.1 release.\nWe recommend that you upgrade at the next available opportunity.\n\n**Bugfixes**\n* #6155 Align gapfill bucket generation with time_bucket\n* #6181 Ensure fixed_schedule field is populated\n* #6210 Fix EXPLAIN ANALYZE for compressed DML\n\n## 2.12.1 (2023-10-12)\n\nThis release contains bug fixes since the 2.12.0 release.\nWe recommend that you upgrade at the next available opportunity.\n\n**Bugfixes**\n* #6113 Fix planner distributed table count\n* #6117 Avoid decompressing batches using an empty slot\n* #6123 Fix concurrency errors in OSM API\n* #6142 do not throw an error when deprecation GUC cannot be read\n\n**Thanks**\n* @symbx for reporting a crash when selecting from empty hypertables\n\n## 2.12.0 (2023-09-27)\n\nThis release contains performance improvements for compressed hypertables\nand continuous aggregates and bug fixes since the 2.11.2 release.\nWe recommend that you upgrade at the next available opportunity.\n\nThis release moves all internal functions from the _timescaleb_internal\nschema into the _timescaledb_functions schema. This separates code from\ninternal data objects and improves security by allowing more restrictive\npermissions for the code schema. If you are calling any of those internal\nfunctions you should adjust your code as soon as possible. This version\nalso includes a compatibility layer that allows calling them in the old\nlocation but that layer will be removed in 2.14.0.\n\n**PostgreSQL 12 support removal announcement**\nFollowing the deprecation announcement for PostgreSQL 12 in TimescaleDB 2.10,\nPostgreSQL 12 is not supported starting with TimescaleDB 2.12.\nCurrently supported PostgreSQL major versions are 13, 14 and 15.\nPostgreSQL 16 support will be added with a following TimescaleDB release.\n\n**Features**\n* #5137 Insert into index during chunk compression\n* #5150 MERGE support on hypertables\n* #5515 Make hypertables support replica identity\n* #5586 Index scan support during UPDATE/DELETE on compressed hypertables\n* #5596 Support for partial aggregations at chunk level\n* #5599 Enable ChunkAppend for partially compressed chunks\n* #5655 Improve the number of parallel workers for decompression\n* #5758 Enable altering job schedule type through `alter_job`\n* #5805 Make logrepl markers for (partial) decompressions\n* #5809 Relax invalidation threshold table-level lock to row-level when refreshing a Continuous Aggregate\n* #5839 Support CAgg names in chunk_detailed_size\n* #5852 Make set_chunk_time_interval CAggs aware\n* #5868 Allow ALTER TABLE ... REPLICA IDENTITY (FULL|INDEX) on materialized hypertables (continuous aggregates)\n* #5875 Add job exit status and runtime to log\n* #5909 CREATE INDEX ONLY ON hypertable creates index on chunks\n\n**Bugfixes**\n* #5860 Fix interval calculation for hierarchical CAggs\n* #5894 Check unique indexes when enabling compression\n* #5951 _timescaledb_internal.create_compressed_chunk doesn't account for existing uncompressed rows\n* #5988 Move functions to _timescaledb_functions schema\n* #5788 Chunk_create must add an existing table or fail\n* #5872 Fix duplicates on partially compressed chunk reads\n* #5918 Fix crash in COPY from program returning error\n* #5990 Place data in first/last function in correct mctx\n* #5991 Call eq_func correctly in time_bucket_gapfill\n* #6015 Correct row count in EXPLAIN ANALYZE INSERT .. ON CONFLICT output\n* #6035 Fix server crash on UPDATE of compressed chunk\n* #6044 Fix server crash when using duplicate segmentby column\n* #6045 Fix segfault in set_integer_now_func\n* #6053 Fix approximate_row_count for CAggs\n* #6081 Improve compressed DML datatype handling\n* #6084 Propagate parameter changes to decompress child nodes\n* #6102 Schedule compression policy more often\n\n**Thanks**\n* @ajcanterbury for reporting a problem with lateral joins on compressed chunks\n* @alexanderlaw for reporting multiple server crashes\n* @lukaskirner for reporting a bug with monthly continuous aggregates\n* @mrksngl for reporting a bug with unusual user names\n* @willsbit for reporting a crash in time_bucket_gapfill\n\n## 2.11.2 (2023-08-09)\n\nThis release contains bug fixes since the 2.11.1 release.\nWe recommend that you upgrade at the next available opportunity.\n\n**Features**\n* #5923 Feature flags for TimescaleDB features\n**Bugfixes**\n* #5680 Fix DISTINCT query with JOIN on multiple segmentby columns\n* #5774 Fixed two bugs in decompression sorted merge code\n* #5786 Ensure pg_config --cppflags are passed\n* #5906 Fix quoting owners in sql scripts.\n* #5912 Fix crash in 1-step integer policy creation\n**Thanks**\n* @mrksngl for submitting a PR to fix extension upgrade scripts\n* @ericdevries for reporting an issue with DISTINCT queries using segmentby columns of compressed hypertable\n\n## 2.11.1 (2023-06-29)\n\nThis release contains bug fixes since the 2.11.0 release.\nWe recommend that you upgrade at the next available opportunity.\n\n**Features**\n* #5679 Update the loader to add support for the OSM extension (used for data tiering on [Timescale](https://www.timescale.com/))\n\n**Bugfixes**\n* #5705 Scheduler accidentally getting killed when calling `delete_job`\n* #5742 Fix Result node handling with ConstraintAwareAppend on compressed chunks\n* #5750 Ensure tlist is present in decompress chunk plan\n* #5754 Fixed handling of NULL values in bookend_sfunc\n* #5798 Fixed batch look ahead in compressed sorted merge\n* #5804 Mark cagg_watermark function as PARALLEL RESTRICTED\n* #5807 Copy job config JSONB structure into current MemoryContext\n* #5824 Improve continuous aggregate query chunk exclusion\n\n**Thanks**\n* @JamieD9 for reporting an issue with a wrong result ordering\n* @xvaara for reporting an issue with Result node handling in ConstraintAwareAppend\n\n\n## 2.11.0 (2023-05-12)\n\nThis release contains new features and bug fixes since the 2.10.3 release.\nWe deem it moderate priority for upgrading.\n\nThis release includes these noteworthy features:\n* Support for DML operations on compressed chunks:\n  * UPDATE/DELETE support\n  * Support for unique constraints on compressed chunks\n  * Support for `ON CONFLICT DO UPDATE`\n  * Support for `ON CONFLICT DO NOTHING`\n* Join support for Hierarchical Continuous Aggregates\n* Performance improvements for real-time Hierarchical Continuous Aggregates\n\n**Features**\n* #5212 Allow pushdown of reference table joins\n* #5261 Improve Realtime Continuous Aggregate performance\n* #5252 Improve unique constraint support on compressed hypertables\n* #5339 Support UPDATE/DELETE on compressed hypertables\n* #5344 Enable JOINS for Hierarchical Continuous Aggregates\n* #5361 Add parallel support for partialize_agg()\n* #5417 Refactor and optimize distributed COPY\n* #5454 Add support for ON CONFLICT DO UPDATE for compressed hypertables\n* #5547 Skip Ordered Append when only 1 child node is present\n* #5510 Propagate vacuum/analyze to compressed chunks\n* #5584 Reduce decompression during constraint checking\n* #5530 Optimize compressed chunk resorting\n* #5639 Support sending telemetry event reports\n\n**Bugfixes**\n* #5396 Fix SEGMENTBY columns predicates to be pushed down\n* #5427 Handle user-defined FDW options properly\n* #5442 Decompression may have lost DEFAULT values\n* #5459 Fix issue creating dimensional constraints\n* #5570 Improve interpolate error message on datatype mismatch\n* #5573 Fix unique constraint on compressed tables\n* #5615 Add permission checks to run_job()\n* #5614 Enable run_job() for telemetry job\n* #5578 Fix on-insert decompression after schema changes\n* #5613 Quote username identifier appropriately\n* #5525 Fix tablespace for compressed hypertable and corresponding toast\n* #5642 Fix ALTER TABLE SET with normal tables\n* #5666 Reduce memory usage for distributed analyze\n* #5668 Fix subtransaction resource owner\n\n**Thanks**\n* @kovetskiy and @DZDomi for reporting peformance regression in Realtime Continuous Aggregates\n* @ollz272 for reporting an issue with interpolate error messages\n\n\n## 2.10.3 (2023-04-26)\n\n**Bugfixes**\n* #5583 Fix parameterization in DecompressChunk path generation\n* #5602 Fix broken CAgg with JOIN repair function\n\n\n## 2.10.2 (2023-04-20)\n\n**Bugfixes**\n* #5410 Fix file trailer handling in the COPY fetcher\n* #5446 Add checks for malloc failure in libpq calls\n* #5233 Out of on_proc_exit slots on guc license change\n* #5428 Use consistent snapshots when scanning metadata\n* #5499 Do not segfault on large histogram() parameters\n* #5470 Ensure superuser perms during copy/move chunk\n* #5500 Fix when no FROM clause in continuous aggregate definition\n* #5433 Fix join rte in CAggs with joins\n* #5556 Fix duplicated entries on timescaledb_experimental.policies view\n* #5462 Fix segfault after column drop on compressed table\n* #5543 Copy scheduled_jobs list before sorting it\n* #5497 Allow named time_bucket arguments in Cagg definition\n* #5544 Fix refresh from beginning of Continuous Aggregate with variable time bucket\n* #5558 Use regrole for job owner\n* #5542 Enable indexscan on uncompressed part of partially compressed chunks\n\n**Thanks**\n* @nikolaps for reporting an issue with the COPY fetcher\n* @S-imo-n for reporting the issue on Background Worker Scheduler crash\n* @geezhu for reporting issue on segfault in historgram()\n* @mwahlhuetter for reporting the issue with joins in CAggs\n* @mwahlhuetter for reporting issue with duplicated entries on timescaledb_experimental.policies view\n* @H25E for reporting error refreshing from beginning of a Continuous Aggregate with variable time bucket\n\n\n## 2.10.1 (2023-03-07)\n\nThis release contains bug fixes since the 2.10.0 release.\nWe recommend that you upgrade at the next available opportunity.\n\n**Bugfixes**\n* #5159 Support Continuous Aggregates names in hypertable_(detailed_)size\n* #5226 Fix concurrent locking with chunk_data_node table\n* #5317 Fix some incorrect memory handling\n* #5336 Use NameData and namestrcpy for names\n* #5343 Set PortalContext when starting job\n* #5360 Fix uninitialized bucket_info variable\n* #5362 Make copy fetcher more async\n* #5364 Fix num_chunks inconsistency in hypertables view\n* #5367 Fix column name handling in old-style continuous aggregates\n* #5378 Fix multinode DML HA performance regression\n* #5384 Fix Hierarchical Continuous Aggregates chunk_interval_size\n\n**Thanks**\n* @justinozavala for reporting an issue with PL/Python procedures in the background worker\n* @Medvecrab for discovering an issue with copying NameData when forming heap tuples.\n* @pushpeepkmonroe for discovering an issue in upgrading old-style continuous aggregates with renamed columns\n\n\n## 2.10.0 (2023-02-21)\n\nThis release contains new features and bug fixes since the 2.9.3 release.\nWe deem it moderate priority for upgrading.\n\nThis release includes these noteworthy features:\n* Joins in continuous aggregates\n* Re-architecture of how compression works: ~2x improvement on INSERT rate into compressed chunks.\n* Full PostgreSQL 15 support for all existing features. Support for the newly introduced MERGE command on hypertables will be introduced on a follow-up release.\n\n**PostgreSQL 12 deprecation announcement**\nWe will continue supporting PostgreSQL 12 until July 2023. Sooner to that time, we will announce the specific version of TimescaleDB in which PostgreSQL 12 support will not be included going forward.\n\n**Old format of Continuous Aggregates deprecation announcement**\nTimescaleDB 2.7 introduced a new format for continuous aggregates that improves performance.\nAll instances with Continuous Aggregates using the old format should [migrate to the new format](https://docs.timescale.com/api/latest/continuous-aggregates/cagg_migrate/) by July 2023,\nwhen support for the old format will be removed.\nSooner to that time, we will announce the specific version of TimescaleDB in which support for this feature will not be included going forward.\n\n**Features**\n* #4874 Allow joins in continuous aggregates\n* #4926 Refactor INSERT into compressed chunks\n* #5241 Allow RETURNING clause when inserting into compressed chunks\n* #5245 Manage life-cycle of connections via memory contexts\n* #5246 Make connection establishment interruptible\n* #5253 Make data node command execution interruptible\n* #5262 Extend enabling compression on a continuous aggregrate with 'compress_segmentby' and 'compress_orderby' parameters\n\n**Bugfixes**\n* #5214 Fix use of prepared statement in async module\n* #5218 Add role-level security to job error log\n* #5239 Fix next_start calculation for fixed schedules\n* #5290 Fix enabling compression on continuous aggregates with columns requiring quotation\n\n**Thanks**\n* @henriquegelio for reporting the issue on fixed schedules\n\n\n## 2.9.3 (2023-02-03)\n\nThis release contains bug fixes since the 2.9.2 release and a fix for a security vulnerability (#5259).\nYou can check the security advisory(https://github.com/timescale/timescaledb/security/advisories/GHSA-44jh-j22r-33wq)\nfor more information on the vulnerability and the platforms that are affected.\n\nThis release is high priority for upgrade. We strongly recommend that you upgrade as soon as possible.\n\n**Bugfixes**\n* #4804 Skip bucketing when start or end of refresh job is null\n* #5108 Fix column ordering in compressed table index not following the order of a multi-column segment by definition\n* #5187 Don't enable clang-tidy by default\n* #5255 Fix year not being considered as a multiple of day/month in hierarchical continuous aggregates\n* #5259 Lock down search_path in SPI calls\n\n**Thanks**\n* @ssmoss for reporting issues on continuous aggregates\n* @jaskij for reporting the compliation issue that occurred with clang\n\n\n## 2.9.2 (2023-01-26)\n\nThis release contains bug fixes since the 2.9.1 release.\nWe recommend that you upgrade at the next available opportunity.\n\n**Bugfixes**\n* #5114 Fix issue with deleting data node and dropping the database on multi-node\n* #5133 Fix creating a CAgg on a CAgg where the time column is in a different order of the original hypertable\n* #5152 Fix adding column with NULL constraint to compressed hypertable\n* #5170 Fix CAgg on CAgg variable bucket size validation\n* #5180 Fix default data node availability status on multi-node\n* #5181 Fix ChunkAppend and ConstraintAwareAppend with TidRangeScan child subplan\n* #5193 Fix repartition behavior when attaching data node on multi-node\n\n**Thanks**\n* @salquier-appvizer for reporting error on CAgg on CAgg using different column order on the original hypertable\n* @ikkala for reporting error when adding column with NULL constraint to compressed hypertable\n* @ssmoss, @adbnexxtlab and @ivanzamanov for reporting error on CAgg on CAgg variable bucket size validation\n* @ronnyas for reporting a bug \"Invalid child of chunk\" on specific ctid filtering\n\n## 2.9.1 (2022-12-23)\n\nThis release contains bug fixes since the 2.9.0 release.\nThis release is high priority for upgrade. We strongly recommend that you\nupgrade as soon as possible.\n\n**Bugfixes**\n* #5072 Fix CAgg on CAgg bucket size validation\n* #5101 Fix enabling compression on caggs with renamed columns\n* #5106 Fix building against PG15 on Windows\n* #5117 Fix postgres server restart on background worker exit\n* #5121 Fix privileges for job_errors in update script\n\n## 2.9.0 (2022-12-15)\n\nThis release adds major new features since the 2.8.1 release.\nWe deem it moderate priority for upgrading.\n\nThis release includes these noteworthy features:\n* Hierarchical Continuous Aggregates (aka Continuous Aggregate on top of another Continuous Aggregate)\n* Improve `time_bucket_gapfill` function to allow specifying the timezone to bucket\n* Introduce fixed schedules for background jobs and the ability to check job errors.\n* Use `alter_data_node()` to change the data node configuration. This function introduces the option to configure the availability of the data node.\n\nThis release also includes several bug fixes.\n\n**Features**\n* #4476 Batch rows on access node for distributed COPY\n* #4567 Exponentially backoff when out of background workers\n* #4650 Show warnings when not following best practices\n* #4664 Introduce fixed schedules for background jobs\n* #4668 Hierarchical Continuous Aggregates\n* #4670 Add timezone support to time_bucket_gapfill\n* #4678 Add interface for troubleshooting job failures\n* #4718 Add ability to merge chunks while compressing\n* #4786 Extend the now() optimization to also apply to CURRENT_TIMESTAMP\n* #4820 Support parameterized data node scans in joins\n* #4830 Add function to change configuration of a data nodes\n* #4966 Handle DML activity when datanode is not available\n* #4971 Add function to drop stale chunks on a datanode\n\n**Bugfixes**\n* #4663 Don't error when compression metadata is missing\n* #4673 Fix now() constification for VIEWs\n* #4681 Fix compression_chunk_size primary key\n* #4696 Report warning when enabling compression on hypertable\n* #4745 Fix FK constraint violation error while insert into hypertable which references partitioned table\n* #4756 Improve compression job IO performance\n* #4770 Continue compressing other chunks after an error\n* #4794 Fix degraded performance seen on timescaledb_internal.hypertable_local_size() function\n* #4807 Fix segmentation fault during INSERT into compressed hypertable\n* #4822 Fix missing segmentby compression option in CAGGs\n* #4823 Fix a crash that could occur when using nested user-defined functions with hypertables\n* #4840 Fix performance regressions in the copy code\n* #4860 Block multi-statement DDL command in one query\n* #4898 Fix cagg migration failure when trying to resume\n* #4904 Remove BitmapScan support in DecompressChunk\n* #4906 Fix a performance regression in the query planner by speeding up frozen chunk state checks\n* #4910 Fix a typo in process_compressed_data_out\n* #4918 Cagg migration orphans cagg policy\n* #4941 Restrict usage of the old format (pre 2.7) of continuous aggregates in PostgreSQL 15.\n* #4955 Fix cagg migration for hypertables using timestamp without timezone\n* #4968 Check for interrupts in gapfill main loop\n* #4988 Fix cagg migration crash when refreshing the newly created cagg\n* #5054 Fix segfault after second ANALYZE\n* #5086 Reset baserel cache on invalid hypertable cache\n\n**Thanks**\n* @byazici for reporting a problem with segmentby on compressed caggs\n* @jflambert for reporting a crash with nested user-defined functions.\n* @jvanns for reporting hypertable FK reference to vanilla PostgreSQL partitioned table doesn't seem to work\n* @kou for fixing a typo in process_compressed_data_out\n* @kyrias for reporting a crash when ANALYZE is executed on extended query protocol mode with extension loaded.\n* @tobiasdirksen for requesting Continuous aggregate on top of another continuous aggregate\n* @xima for reporting a bug in Cagg migration\n* @xvaara for helping reproduce a bug with bitmap scans in transparent decompression\n\n## 2.8.1 (2022-10-06)\n\nThis release is a patch release. We recommend that you upgrade at the\nnext available opportunity.\n\n**Bugfixes**\n* #4454 Keep locks after reading job status\n* #4658 Fix error when querying a compressed hypertable with compress_segmentby on an enum column\n* #4671 Fix a possible error while flushing the COPY data\n* #4675 Fix bad TupleTableSlot drop\n* #4676 Fix a deadlock when decompressing chunks and performing SELECTs\n* #4685 Fix chunk exclusion for space partitions in SELECT FOR UPDATE queries\n* #4694 Change parameter names of cagg_migrate procedure\n* #4698 Do not use row-by-row fetcher for parameterized plans\n* #4711 Remove support for procedures as custom checks\n* #4712 Fix assertion failure in constify_now\n* #4713 Fix Continuous Aggregate migration policies\n* #4720 Fix chunk exclusion for prepared statements and dst changes\n* #4726 Fix gapfill function signature\n* #4737 Fix join on time column of compressed chunk\n* #4738 Fix error when waiting for remote COPY to finish\n* #4739 Fix continuous aggregate migrate check constraint\n* #4760 Fix segfault when INNER JOINing hypertables\n* #4767 Fix permission issues on index creation for CAggs\n\n**Thanks**\n* @boxhock and @cocowalla for reporting a segfault when JOINing hypertables\n* @carobme for reporting constraint error during continuous aggregate migration\n* @choisnetm, @dustinsorensen, @jayadevanm and @joeyberkovitz for reporting a problem with JOINs on compressed hypertables\n* @daniel-k for reporting a background worker crash\n* @justinpryzby for reporting an error when compressing very wide tables\n* @maxtwardowski for reporting problems with chunk exclusion and space partitions\n* @yuezhihan for reporting GROUP BY error when having compress_segmentby on an enum column\n\n## 2.8.0 (2022-08-30)\n\nThis release adds major new features since the 2.7.2 release.\nWe deem it moderate priority for upgrading.\n\nThis release includes these noteworthy features:\n\n* time_bucket now supports bucketing by month, year and timezone\n* Improve performance of bulk SELECT and COPY for distributed hypertables\n* 1 step CAgg policy management\n* Migrate Continuous Aggregates to the new format\n\n**Features**\n* #4188 Use COPY protocol in row-by-row fetcher\n* #4307 Mark partialize_agg as parallel safe\n* #4380 Enable chunk exclusion for space dimensions in UPDATE/DELETE\n* #4384 Add schedule_interval to policies\n* #4390 Faster lookup of chunks by point\n* #4393 Support intervals with day component when constifying now()\n* #4397 Support intervals with month component when constifying now()\n* #4405 Support ON CONFLICT ON CONSTRAINT for hypertables\n* #4412 Add telemetry about replication\n* #4415 Drop remote data when detaching data node\n* #4416 Handle TRUNCATE TABLE on chunks\n* #4425 Add parameter check_config to alter_job\n* #4430 Create index on Continuous Aggregates\n* #4439 Allow ORDER BY on continuous aggregates\n* #4443 Add stateful partition mappings\n* #4484 Use non-blocking data node connections for COPY\n* #4495 Support add_dimension() with existing data\n* #4502 Add chunks to baserel cache on chunk exclusion\n* #4545 Add hypertable distributed argument and defaults\n* #4552 Migrate Continuous Aggregates to the new format\n* #4556 Add runtime exclusion for hypertables\n* #4561 Change get_git_commit to return full commit hash\n* #4563 1 step CAgg policy management\n* #4641 Allow bucketing by month, year, century in time_bucket and time_bucket_gapfill\n* #4642 Add timezone support to time_bucket\n\n**Bugfixes**\n* #4359 Create composite index on segmentby columns\n* #4374 Remove constified now() constraints from plan\n* #4416 Handle TRUNCATE TABLE on chunks\n* #4478 Synchronize chunk cache sizes\n* #4486 Adding boolean column with default value doesn't work on compressed table\n* #4512 Fix unaligned pointer access\n* #4519 Throw better error message on incompatible row fetcher settings\n* #4549 Fix dump_meta_data for windows\n* #4553 Fix timescaledb_post_restore GUC handling\n* #4573 Load TSL library on compressed_data_out call\n* #4575 Fix use of `get_partition_hash` and `get_partition_for_key` inside an IMMUTABLE function\n* #4577 Fix segfaults in compression code with corrupt data\n* #4580 Handle default privileges on CAggs properly\n* #4582 Fix assertion in GRANT .. ON ALL TABLES IN SCHEMA\n* #4583 Fix partitioning functions\n* #4589 Fix rename for distributed hypertable\n* #4601 Reset compression sequence when group resets\n* #4611 Fix a potential OOM when loading large data sets into a hypertable\n* #4624 Fix heap buffer overflow\n* #4627 Fix telemetry initialization\n* #4631 Ensure TSL library is loaded on database upgrades\n* #4646 Fix time_bucket_ng origin handling\n* #4647 Fix the error \"SubPlan found with no parent plan\" that occurred if using joins in RETURNING clause.\n\n**Thanks**\n* @AlmiS for reporting error on `get_partition_hash` executed inside an IMMUTABLE function\n* @Creatation for reporting an issue with renaming hypertables\n* @janko for reporting an issue when adding bool column with default value to compressed hypertable\n* @jayadevanm for reporting error of TRUNCATE TABLE on compressed chunk\n* @michaelkitson for reporting permission errors using default privileges on Continuous Aggregates\n* @mwahlhuetter for reporting error in joins in RETURNING clause\n* @ninjaltd and @mrksngl for reporting a potential OOM when loading large data sets into a hypertable\n* @PBudmark for reporting an issue with dump_meta_data.sql on Windows\n* @ssmoss for reporting an issue with time_bucket_ng origin handling\n\n## 2.7.2 (2022-07-26)\n\nThis release is a patch release. We recommend that you upgrade at the\nnext available opportunity.\nAmong other things this release fixes several memory leaks, handling\nof TOASTed values in GapFill and parameter handling in prepared statements.\n\n**Bugfixes**\n* #4517 Fix prepared statement param handling in ChunkAppend\n* #4522 Fix ANALYZE on dist hypertable for a set of nodes\n* #4526 Fix gapfill group comparison for TOASTed values\n* #4527 Handle stats properly for range types\n* #4532 Fix memory leak in function telemetry\n* #4534 Use explicit memory context with hash_create\n* #4538 Fix chunk creation on hypertables with non-default statistics\n\n**Thanks**\n* @3a6u9ka, @bgemmill, @hongquan, @stl-leonid-kalmaev and @victor-sudakov for reporting a memory leak\n* @hleung2021 and @laocaixw  for reporting an issue with parameter handling in prepared statements\n\n## 2.7.1 (2022-07-07)\n\nThis release is a patch release. We recommend that you upgrade at the\nnext available opportunity.\n\n**Bugfixes**\n* #4494 Handle timescaledb versions aptly in multinode\n* #4493 Segfault when executing IMMUTABLE functions\n* #4482 Fix race conditions during chunk (de)compression\n* #4367 Improved buffer management in the copy operator\n* #4375 Don't ask for orderby column if default already set\n* #4400 Use our implementation of `find_em_expr_for_rel` for PG15+\n* #4408 Fix crash during insert into distributed hypertable\n* #4411 Add `shmem_request_hook`\n* #4437 Fix segfault in subscription_exec\n* #4442 Fix perms in copy/move chunk\n* #4450 Retain hypertable ownership on `attach_data_node`\n* #4451 Repair numeric partial state on the fly\n* #4463 Fix empty bytea handlng with distributed tables\n* #4469 Better superuser handling for `move_chunk`\n\n**Features**\n* #4244 Function telemetry\n* #4287 Add internal api for foreign table chunk\n* #4470 Block drop chunk if chunk is in frozen state\n* #4464 Add internal api to associate a hypertable with custom jobs\n\n**Thanks**\n* @xin-hedera Finding bug in empty bytea values for distributed tables\n* @jflambert for reporting a bug with IMMUTABLE functions\n* @nikugogoi for reporting a bug with CTEs and upserts on distributed hypertables\n\n## 2.7.0 (2022-05-24)\n\nThis release adds major new features since the 2.6.1 release.\nWe deem it moderate priority for upgrading.\n\nThis release includes these noteworthy features:\n\n* Optimize continuous aggregate query performance and storage\n* The following query clauses and functions can now be used in a continuous\n  aggregate: FILTER, DISTINCT, ORDER BY as well as [Ordered-Set Aggregate](https://www.postgresql.org/docs/current/functions-aggregate.html#FUNCTIONS-ORDEREDSET-TABLE)\n  and [Hypothetical-Set Aggregate](https://www.postgresql.org/docs/current/functions-aggregate.html#FUNCTIONS-HYPOTHETICAL-TABLE)\n* Optimize now() query planning time\n* Improve COPY insert performance\n* Improve performance of UPDATE/DELETE on PG14 by excluding chunks\n\nThis release also includes several bug fixes.\n\nIf you are upgrading from a previous version and were using compression\nwith a non-default collation on a segmentby-column you should recompress\nthose hypertables.\n\n**Features**\n* #4045 Custom origin's support in CAGGs\n* #4120 Add logging for retention policy\n* #4158 Allow ANALYZE command on a data node directly\n* #4169 Add support for chunk exclusion on DELETE to PG14\n* #4209 Add support for chunk exclusion on UPDATE to PG14\n* #4269 Continuous Aggregates finals form\n* #4301 Add support for bulk inserts in COPY operator\n* #4311 Support non-superuser move chunk operations\n* #4330 Add GUC \"bgw_launcher_poll_time\"\n* #4340 Enable now() usage in plan-time chunk exclusion\n\n**Bugfixes**\n* #3899 Fix segfault in Continuous Aggregates\n* #4225 Fix TRUNCATE error as non-owner on hypertable\n* #4236 Fix potential wrong order of results for compressed hypertable with a non-default collation\n* #4249 Fix option \"timescaledb.create_group_indexes\"\n* #4251 Fix INSERT into compressed chunks with dropped columns\n* #4255 Fix option \"timescaledb.create_group_indexes\"\n* #4259 Fix logic bug in extension update script\n* #4269 Fix bad Continuous Aggregate view definition reported in #4233\n* #4289 Support moving compressed chunks between data nodes\n* #4300 Fix refresh window cap for cagg refresh policy\n* #4315 Fix memory leak in scheduler\n* #4323 Remove printouts from signal handlers\n* #4342 Fix move chunk cleanup logic\n* #4349 Fix crashes in functions using AlterTableInternal\n* #4358 Fix crash and other issues in telemetry reporter\n\n**Thanks**\n* @abrownsword for reporting a bug in the telemetry reporter and testing the fix\n* @jsoref for fixing various misspellings in code, comments and documentation\n* @yalon for reporting an error with ALTER TABLE RENAME on distributed hypertables\n* @zhuizhuhaomeng for reporting and fixing a memory leak in our scheduler\n\n## 2.6.1 (2022-04-11)\nThis release is patch release. We recommend that you upgrade at the next available opportunity.\n\n**Bugfixes**\n* #4121 Fix RENAME TO/SET SCHEMA on distributed hypertable\n* #4122 Fix segfault on INSERT into distributed hypertable\n* #4142 Ignore invalid relid when deleting hypertable\n* #4159 Fix ADD COLUMN IF NOT EXISTS error on compressed hypertable\n* #4161 Fix memory handling during scans\n* #4176 Fix remote EXPLAIN with parameterized queries\n* #4181 Fix spelling errors and omissions\n* #4186 Fix owner change for distributed hypertable\n* #4192 Abort sessions after extension reload\n* #4193 Fix relcache callback handling causing crashes\n* #4199 Remove signal-unsafe calls from signal handlers\n* #4219 Do not modify aggregation state in finalize\n\n**Thanks**\n* @abrownsword for reporting a crash in the telemetry reporter\n* @amalek215 for reporting a segmentation fault when running VACUUM FULL pg_class\n* @daydayup863 for reporting issue with remote explain\n* @krvajal for reporting an error with ADD COLUMN IF NOT EXISTS on compressed hypertables\n\n## 2.6.0 (2022-02-16)\nThis release is medium priority for upgrade. We recommend that you upgrade at the next available opportunity.\n\nThis release adds major new features since the 2.5.2 release, including:\n\n* Compression in continuous aggregates\n* Experimental support for timezones in continuous aggregates\n* Experimental support for monthly buckets in continuous aggregates\n\nThe release also includes several bug fixes. Telemetry reports now include new and more detailed statistics on regular tables and views, compression, distributed hypertables, and continuous aggregates, which will help us improve TimescaleDB.\n\n**Features**\n* #3768 Allow ALTER TABLE ADD COLUMN with DEFAULT on compressed hypertable\n* #3769 Allow ALTER TABLE DROP COLUMN on compressed hypertable\n* #3943 Optimize first/last\n* #3945 Add support for ALTER SCHEMA on multi-node\n* #3949 Add support for DROP SCHEMA on multi-node\n\n**Bugfixes**\n* #3808 Properly handle `max_retries` option\n* #3863 Fix remote transaction heal logic\n* #3869 Fix ALTER SET/DROP NULL constraint on distributed hypertable\n* #3944 Fix segfault in add_compression_policy\n* #3961 Fix crash in EXPLAIN VERBOSE on distributed hypertable\n* #4015 Eliminate float rounding instabilities in interpolate\n* #4019 Update ts_extension_oid in transitioning state\n* #4073 Fix buffer overflow in partition scheme\n* #4180 ALTER TABLE OWNER TO does not work for distributed hypertable\n\n**Improvements**\n* Query planning performance is improved for hypertables with a large number of chunks.\n\n**Thanks**\n* @fvannee for reporting a first/last memory leak\n* @mmouterde for reporting an issue with floats and interpolate\n\n## 2.5.2 (2022-02-09)\n\nThis release contains bug fixes since the 2.5.1 release.\nThis release is high priority for upgrade. We strongly recommend that you\nupgrade as soon as possible.\n\n**Bugfixes**\n* #3900 Improve custom scan node registration\n* #3911 Fix role type deparsing for GRANT command\n* #3918 Fix DataNodeScan plans with one-time filter\n* #3921 Fix segfault on insert into internal compressed table\n* #3938 Fix subtract_integer_from_now on 32-bit platforms and improve error handling\n* #3939 Fix projection handling in time_bucket_gapfill\n* #3948 Avoid double PGclear() in data fetchers\n* #3979 Fix deparsing of index predicates\n* #4020 Fix ALTER TABLE EventTrigger initialization\n* #4024 Fix premature cache release call\n* #4037 Fix status for dropped chunks that have catalog entries\n* #4069 Fix riinfo NULL handling in ANY construct\n* #4071 Fix extension installation privilege escalation (CVE-2022-24128)\n\n**Thanks**\n* @carlocperez for reporting crash with NULL handling in ANY construct\n* @erikhh for reporting an issue with time_bucket_gapfill\n* @kancsuki for reporting drop column and partial index creation not working\n* @mmouterde for reporting an issue with floats and interpolate\n* Pedro Gallegos for reporting a possible privilege escalation during extension installation\n\n## 2.5.1 (2021-12-02)\n\nThis release contains bug fixes since the 2.5.0 release.\nWe deem it medium priority to upgrade.\n\n**Bugfixes**\n* #3706 Test enabling dist compression within a procedure\n* #3734 Rework distributed DDL processing logic\n* #3737 Fix flaky pg_dump\n* #3739 Fix compression policy on tables using INTEGER\n* #3766 Fix segfault in ts_hist_sfunc\n* #3779 Support GRANT/REVOKE on distributed database\n* #3789 Fix time_bucket comparison transformation\n* #3797 Fix DISTINCT ON queries for distributed hyperatbles\n* #3799 Fix error printout on correct security label\n* #3801 Fail size utility functions when data nodes do not respond\n* #3809 Fix NULL pointer evaluation in fill_result_error()\n* #3811 Fix INSERT..SELECT involving dist hypertables\n* #3819 Fix reading garbage value from TSConnectionError\n* #3824 Remove pointers from CAGG lists for 64-bit archs\n* #3846 Eliminate deadlock in recompress chunk policy\n* #3881 Fix SkipScan crash due to pruned unique path\n* #3884 Fix create_distributed_restore_point memory issue\n\n**Thanks**\n* @cbisnett for reporting and fixing a typo in an error message\n* @CaptainCuddleCube for reporting bug on compression policy procedure on tables using INTEGER on time dimension\n* @phemmer for reporting bugs on multi-node\n\n## 2.5.0 (2021-10-28)\n\nThis release adds major new features since the 2.4.2 release.\nWe deem it moderate priority for upgrading.\n\nThis release includes these noteworthy features:\n\n* Continuous Aggregates for Distributed Hypertables\n* Support for PostgreSQL 14\n* Experimental: Support for timezones in `time_bucket_ng()`, including\nthe `origin` argument\n\nThis release also includes several bug fixes.\n\n**Features**\n* #3034 Add support for PostgreSQL 14\n* #3435 Add continuous aggregates for distributed hypertables\n* #3505 Add support for timezones in `time_bucket_ng()`\n\n**Bugfixes**\n* #3580 Fix memory context bug executing TRUNCATE\n* #3592 Allow alter column type on distributed hypertable\n* #3598 Improve evaluation of stable functions such as now() on access node\n* #3618 Fix execution of refresh_caggs from user actions\n* #3625 Add shared dependencies when creating chunk\n* #3626 Fix memory context bug executing TRUNCATE\n* #3627 Schema qualify UDTs in multi-node\n* #3638 Allow owner change of a data node\n* #3654 Fix index attnum mapping in reorder_chunk\n* #3661 Fix SkipScan path generation with constant DISTINCT column\n* #3667 Fix compress_policy for multi txn handling\n* #3673 Fix distributed hypertable DROP within a procedure\n* #3701 Allow anyone to use size utilities on distributed hypertables\n* #3708 Fix crash in get_aggsplit\n* #3709 Fix ordered append pathkey check\n* #3712 Fix GRANT/REVOKE ALL IN SCHEMA handling\n* #3717 Support transparent decompression on individual chunks\n* #3724 Fix inserts into compressed chunks on hypertables with caggs\n* #3727 Fix DirectFunctionCall crash in distributed_exec\n* #3728 Fix SkipScan with varchar column\n* #3733 Fix ANALYZE crash with custom statistics for custom types\n* #3747 Always reset expr context in DecompressChunk\n\n**Thanks**\n* @binakot and @sebvett for reporting an issue with DISTINCT queries\n* @hardikm10, @DavidPavlicek and @pafiti for reporting bugs on TRUNCATE\n* @mjf for reporting an issue with ordered append and JOINs\n* @phemmer for reporting the issues on multinode with aggregate queries and evaluation of now()\n* @abolognino for reporting an issue with INSERTs into compressed hypertables that have cagg\n* @tanglebones for reporting the ANALYZE crash with custom types on multinode\n* @amadeubarbosa and @felipenogueirajack for reporting crash using JSONB column in compressed chunks\n\n## 2.4.2 (2021-09-21)\n\nThis release contains bug fixes since the 2.4.1 release.\nWe deem it high priority to upgrade.\n\n**Bugfixes**\n* #3437 Rename on all continuous aggregate objects\n* #3469 Use signal-safe functions in signal handler\n* #3520 Modify compression job processing logic\n* #3527 Fix time_bucket_ng behaviour with origin argument\n* #3532 Fix bootstrap with regresschecks disabled\n* #3574 Fix failure on job execution by background worker\n* #3590 Call cleanup functions on backend exit\n\n**Thanks**\n* @jankatins for reporting a crash with background workers\n* @LutzWeischerFujitsu for reporting an issue with bootstrap\n\n## 2.4.1 (2021-08-19)\n\nThis release contains bug fixes since the 2.4.0 release.  We deem it\nhigh priority to upgrade.\n\nThe release fixes continous aggregate refresh for postgres 12.8 and\n13.4, a crash with ALTER TABLE commands and a crash with continuous\naggregates with HAVING clause.\n\n**Bugfixes**\n* #3430 Fix havingqual processing for continuous aggregates\n* #3468 Disable tests by default if tools are not found\n* #3462 Fix crash while tracking alter table commands\n* #3489 Fix continuous agg bgw job failure for PG 12.8 and 13.4\n* #3494 Improve error message when adding data nodes\n\n**Thanks**\n* @brianbenns for reporting a segfault with continuous aggregates\n* @usego for reporting an issue with continuous aggregate refresh on PG 13.4\n\n## 2.4.0 (2021-07-29)\n\nThis release adds new experimental features since the 2.3.1 release.\n\nThe experimental features in this release are:\n* APIs for chunk manipulation across data nodes in a distributed\nhypertable setup. This includes the ability to add a data node and move\nchunks to the new data node for cluster rebalancing.\n* The `time_bucket_ng` function, a newer version of `time_bucket`. This\nfunction supports years, months, days, hours, minutes, and seconds.\n\nWe’re committed to developing these experiments, giving the community\n a chance to provide early feedback and influence the direction of\nTimescaleDB’s development. We’ll travel faster with your input!\n\nPlease create your feedback as a GitHub issue (using the\nexperimental-schema label), describe what you found, and tell us the\nsteps or share the code snip to recreate it.\n\nThis release also includes several bug fixes.\n\nPostgreSQL 11 deprecation announcement\nTimescale is working hard on our next exciting features. To make that\npossible, we require functionality that is available in Postgres 12 and\nabove. Postgres 11 is not supported with TimescaleDB 2.4.\n\n**Experimental Features**\n* #3293 Add timescaledb_experimental schema\n* #3302 Add block_new_chunks and allow_new_chunks API to experimental\nschema. Add chunk based refresh_continuous_aggregate.\n* #3211 Introduce experimental time_bucket_ng function\n* #3366 Allow use of experimental time_bucket_ng function in continuous aggregates\n* #3408 Support for seconds, minutes and hours in time_bucket_ng\n* #3446 Implement cleanup for chunk copy/move.\n\n**Bugfixes**\n* #3401 Fix segfault for RelOptInfo without fdw_private\n* #3411 Verify compressed chunk validity for compressed path\n* #3416 Fix targetlist names for continuous aggregate views\n* #3434 Remove extension check from relcache invalidation callback\n* #3440 Fix remote_tx_heal_data_node to work with only current database\n\n**Thanks**\n* @fvannee for reporting an issue with hypertable expansion in functions\n* @amalek215 for reporting an issue with cache invalidation during pg_class vacuum full\n* @hardikm10 for reporting an issue with inserting into compressed chunks\n* @dberardo-com and @iancmcc for reporting an issue with extension updates after renaming columns of continuous aggregates.\n\n## 2.3.1 (2021-07-05)\n\nThis maintenance release contains bugfixes since the 2.3.0 release. We\ndeem it moderate priority for upgrading. The release introduces the\npossibility of generating downgrade scripts, improves the trigger\nhandling for distributed hypertables, adds some more randomness to\nchunk assignment to avoid thundering herd issues in chunk assignment,\nand fixes some issues in update handling as well as some other bugs.\n\n**Bugfixes**\n* #3279 Add some more randomness to chunk assignment\n* #3288 Fix failed update with parallel workers\n* #3300 Improve trigger handling on distributed hypertables\n* #3304 Remove paths that reference parent relids for compressed chunks\n* #3305 Fix pull_varnos miscomputation of relids set\n* #3310 Generate downgrade script\n* #3314 Fix heap buffer overflow in hypertable expansion\n* #3317 Fix heap buffer overflow in remote connection cache.\n* #3327 Make aggregates in caggs fully qualified\n* #3336 Fix pg_init_privs objsubid handling\n* #3345 Fix SkipScan distinct column identification\n* #3355 Fix heap buffer overflow when renaming compressed hypertable columns.\n* #3367 Improve DecompressChunk qual pushdown\n* #3377 Fix bad use of repalloc\n\n**Thanks**\n* @db-adrian for reporting an issue when accessing cagg view through postgres_fdw\n* @fncaldas and @pgwhalen for reporting an issue accessing caggs when public is not in search_path\n* @fvannee, @mglonnro and @ebreijo for reporting an issue with the upgrade script\n* @fvannee for reporting a performance regression with SkipScan\n\n## 2.3.0 (2021-05-25)\n\nThis release adds major new features since the 2.2.1 release.\nWe deem it moderate priority for upgrading.\n\nThis release adds support for inserting data into compressed chunks\nand improves performance when inserting data into distributed hypertables.\nDistributed hypertables now also support triggers and compression policies.\n\nThe bug fixes in this release address issues related to the handling\nof privileges on compressed hypertables, locking, and triggers with transition tables.\n\n**Features**\n* #3116 Add distributed hypertable compression policies\n* #3162 Use COPY when executing distributed INSERTs\n* #3199 Add GENERATED column support on distributed hypertables\n* #3210 Add trigger support on distributed hypertables\n* #3230 Support for inserts into compressed chunks\n\n**Bugfixes**\n* #3213 Propagate grants to compressed hypertables\n* #3229 Use correct lock mode when updating chunk\n* #3243 Fix assertion failure in decompress_chunk_plan_create\n* #3250 Fix constraint triggers on hypertables\n* #3251 Fix segmentation fault due to incorrect call to chunk_scan_internal\n* #3252 Fix blocking triggers with transition tables\n\n**Thanks**\n* @yyjdelete for reporting a crash with decompress_chunk and identifying the bug in the code\n* @fabriziomello for documenting the prerequisites when compiling against PostgreSQL 13\n\n## 2.2.1 (2021-05-05)\n\nThis maintenance release contains bugfixes since the 2.2.0 release. We\ndeem it high priority for upgrading.\n\nThis release extends Skip Scan to multinode by enabling the pushdown\nof `DISTINCT` to data nodes. It also fixes a number of bugs in the\nimplementation of Skip Scan, in distributed hypertables, in creation\nof indexes, in compression, and in policies.\n\n**Features**\n* #3113 Pushdown \"SELECT DISTINCT\" in multi-node to allow use of Skip\n  Scan\n\n**Bugfixes**\n* #3101 Use commit date in `get_git_commit()`\n* #3102 Fix `REINDEX TABLE` for distributed hypertables\n* #3104 Fix use after free in `add_reorder_policy`\n* #3106 Fix use after free in `chunk_api_get_chunk_stats`\n* #3109 Copy recreated object permissions on update\n* #3111 Fix `CMAKE_BUILD_TYPE` check\n* #3112 Use `%u` to format Oid instead of `%d`\n* #3118 Fix use after free in cache\n* #3123 Fix crash while using `REINDEX TABLE CONCURRENTLY`\n* #3135 Fix SkipScan path generation in `DISTINCT` queries with expressions\n* #3146 Fix SkipScan for IndexPaths without pathkeys\n* #3147 Skip ChunkAppend if AppendPath has no children\n* #3148 Make `SELECT DISTINCT` handle non-var targetlists\n* #3151 Fix `fdw_relinfo_get` assertion failure on `DELETE`\n* #3155 Inherit `CFLAGS` from PostgreSQL\n* #3169 Fix incorrect type cast in compression policy\n* #3183 Fix segfault in calculate_chunk_interval\n* #3185 Fix wrong datatype for integer based retention policy\n\n**Thanks**\n* @Dead2, @dv8472 and @einsibjarni for reporting an issue with multinode queries and views\n* @aelg for reporting an issue with policies on integer-based hypertables\n* @hperez75 for reporting an issue with Skip Scan\n* @nathanloisel for reporting an issue with compression on hypertables with integer-based timestamps\n* @xin-hedera for fixing an issue with compression on hypertables with integer-based timestamps\n\n## 2.2.0 (2021-04-13)\n\nThis release adds major new features since the 2.1.1 release.\nWe deem it moderate priority for upgrading.\n\nThis release adds the Skip Scan optimization, which significantly\nimproves the performance of queries with DISTINCT ON. This\noptimization is not yet available for queries on distributed\nhypertables.\n\nThis release also adds a function to create a distributed\nrestore point, which allows performing a consistent restore of a\nmulti-node cluster from a backup.\n\nThe bug fixes in this release address issues with size and stats\nfunctions, high memory usage in distributed inserts, slow distributed\nORDER BY queries, indexes involving INCLUDE, and single chunk query\nplanning.\n\n**PostgreSQL 11 deprecation announcement**\n\nTimescale is working hard on our next exciting features. To make that\npossible, we require functionality that is unfortunately absent on\nPostgreSQL 11. For this reason, we will continue supporting PostgreSQL\n11 until mid-June 2021. Sooner to that time, we will announce the\nspecific version of TimescaleDB in which PostgreSQL 11 support will\nnot be included going forward.\n\n**Major Features**\n* #2843 Add distributed restore point functionality\n* #3000 SkipScan to speed up SELECT DISTINCT\n\n**Bugfixes**\n* #2989 Refactor and harden size and stats functions\n* #3058 Reduce memory usage for distributed inserts\n* #3067 Fix extremely slow multi-node order by queries\n* #3082 Fix chunk index column name mapping\n* #3083 Keep Append pathkeys in ChunkAppend\n\n**Thanks**\n* @BowenGG for reporting an issue with indexes with INCLUDE\n* @fvannee for reporting an issue with ChunkAppend pathkeys\n* @pedrokost and @RobAtticus for reporting an issue with size\n  functions on empty hypertables\n* @phemmer and @ryanbooz for reporting issues with slow\n  multi-node order by queries\n* @stephane-moreau for reporting an issue with high memory usage during\n  single-transaction inserts on a distributed hypertable.\n\n## 2.1.1 (2021-03-29)\n\nThis maintenance release contains bugfixes since the 2.1.0 release. We\ndeem it high priority for upgrading.\n\nThe bug fixes in this release address issues with CREATE INDEX and\nUPSERT for hypertables, custom jobs, and gapfill queries.\n\nThis release marks TimescaleDB as a trusted extension in PG13, so that\nsuperuser privileges are not required anymore to install the extension.\n\n**Minor features**\n* #2998 Mark timescaledb as trusted extension\n\n**Bugfixes**\n* #2948 Fix off by 4 error in histogram deserialize\n* #2974 Fix index creation for hypertables with dropped columns\n* #2990 Fix segfault in job_config_check for cagg\n* #2987 Fix crash due to txns in emit_log_hook_callback\n* #3042 Commit end transaction for CREATE INDEX\n* #3053 Fix gapfill/hashagg planner interaction\n* #3059 Fix UPSERT on hypertables with columns with defaults\n\n**Thanks**\n* @eloyekunle and @kitwestneat for reporting an issue with UPSERT\n* @jocrau for reporting an issue with index creation\n* @kev009 for fixing a compilation issue\n* @majozv and @pehlert for reporting an issue with time_bucket_gapfill\n\n## 2.1.0 (2021-02-22)\n\nThis release adds major new features since the 2.0.2 release.\nWe deem it moderate priority for upgrading.\n\nThis release adds the long-awaited support for PostgreSQL 13 to TimescaleDB.\nThe minimum required PostgreSQL 13 version is 13.2 due to a security vulnerability\naffecting TimescaleDB functionality present in earlier versions of PostgreSQL 13.\n\nThis release also relaxes some restrictions for compressed hypertables;\nnamely, TimescaleDB now supports adding columns to compressed hypertables\nand renaming columns of compressed hypertables.\n\n**Major Features**\n* #2779 Add support for PostgreSQL 13\n\n**Minor features**\n* #2736 Support adding columns to hypertables with compression enabled\n* #2909 Support renaming columns of hypertables with compression enabled\n\n## 2.0.2 (2021-02-19)\n\nThis maintenance release contains bugfixes since the 2.0.1 release. We\ndeem it high priority for upgrading.\n\nThe bug fixes in this release address issues with joins, the status of\nbackground jobs, and disabling compression. It also includes\nenhancements to continuous aggregates, including improved validation\nof policies and optimizations for faster refreshes when there are a\nlot of invalidations.\n\n**Minor features**\n* #2926 Optimize cagg refresh for small invalidations\n\n**Bugfixes**\n* #2850 Set status for backend in background jobs\n* #2883 Fix join qual propagation for nested joins\n* #2884 Add GUC to control join qual propagation\n* #2885 Fix compressed chunk check when disabling compression\n* #2908 Fix changing column type of clustered hypertables\n* #2942 Validate continuous aggregate policy\n\n**Thanks**\n* @zeeshanshabbir93 for reporting an issue with joins\n* @Antiarchitect for reporting the issue with slow refreshes of\n  continuous aggregates.\n* @diego-hermida for reporting the issue about being unable to disable\n  compression\n* @mtin for reporting the issue about wrong job status\n\n## 1.7.5 (2021-02-12)\n\nThis maintenance release contains bugfixes since the 1.7.4 release.\nMost of these fixes were backported from the 2.0.0 and 2.0.1 releases.\nWe deem it high priority for upgrading for users on TimescaleDB 1.7.4\nor previous versions.\n\nIn particular the fixes contained in this maintenance release address\nissues in continuous aggregates, compression, JOINs with hypertables,\nand when upgrading from previous versions.\n\n**Bugfixes**\n* #2502 Replace check function when updating\n* #2558 Repair dimension slice table on update\n* #2619 Fix segfault in decompress_chunk for chunks with dropped\n  columns\n* #2664 Fix support for complex aggregate expression\n* #2800 Lock dimension slices when creating new chunk\n* #2860 Fix projection in ChunkAppend nodes\n* #2865 Apply volatile function quals at decompresschunk\n* #2851 Fix nested loop joins that involve compressed chunks\n* #2868 Fix corruption in gapfill plan\n* #2883 Fix join qual propagation for nested joins\n* #2885 Fix compressed chunk check when disabling compression\n* #2920 Fix repair in update scripts\n\n**Thanks**\n* @akamensky for reporting several issues including segfaults after\n  version update\n* @alex88 for reporting an issue with joined hypertables\n* @dhodyn for reporting an issue when joining compressed chunks\n* @diego-hermida for reporting an issue with disabling compression\n* @Netskeh for reporting bug on time_bucket problem in continuous\n  aggregates\n* @WarriorOfWire for reporting the bug with gapfill queries not being\n  able to find pathkey item to sort\n* @zeeshanshabbir93 for reporting an issue with joins\n\n## 2.0.1 (2021-01-28)\n\nThis maintenance release contains bugfixes since the 2.0.0 release.\nWe deem it high priority for upgrading.\n\nIn particular the fixes contained in this maintenance release address\nissues in continuous aggregates, compression, JOINs with hypertables\nand when upgrading from previous versions.\n\n**Bugfixes**\n* #2772 Always validate existing database and extension\n* #2780 Fix config enum entries for remote data fetcher\n* #2806 Add check for dropped chunk on update\n* #2828 Improve cagg watermark caching\n* #2838 Fix catalog repair in update script\n* #2842 Do not mark job as started when setting next_start field\n* #2845 Fix continuous aggregate privileges during upgrade\n* #2851 Fix nested loop joins that involve compressed chunks\n* #2860 Fix projection in ChunkAppend nodes\n* #2861 Remove compression stat update from update script\n* #2865 Apply volatile function quals at decompresschunk node\n* #2866 Avoid partitionwise planning of partialize_agg\n* #2868 Fix corruption in gapfill plan\n* #2874 Fix partitionwise agg crash due to uninitialized memory\n\n**Thanks**\n* @alex88 for reporting an issue with joined hypertables\n* @brian-from-quantrocket for reporting an issue with extension update and dropped chunks\n* @dhodyn for reporting an issue when joining compressed chunks\n* @markatosi for reporting a segfault with partitionwise aggregates enabled\n* @PhilippJust for reporting an issue with add_job and initial_start\n* @sgorsh for reporting an issue when using pgAdmin on windows\n* @WarriorOfWire for reporting the bug with gapfill queries not being\n  able to find pathkey item to sort\n\n## 2.0.0 (2020-12-18)\n\nWith this release, we are officially moving TimescaleDB 2.0 to GA,\nconcluding several release candidates.\n\nTimescaleDB 2.0 adds the much-anticipated support for distributed\nhypertables (multi-node TimescaleDB), as well as new features and\nenhancements to core functionality to give users better clarity and\nmore control and flexibility over their data.\n\nMulti-node architecture:  In particular, with TimescaleDB 2.0, users\ncan now create distributed hypertables across multiple instances of\nTimescaleDB, configured so that one instance serves as an access node\nand multiple others as data nodes. All queries for a distributed\nhypertable are issued to the access node, but inserted data and queries\nare pushed down across data nodes for greater scale and performance.\n\nMulti-node TimescaleDB can be self managed or, for easier operation,\nlaunched within Timescale's fully-managed cloud services.\n\nThis release also adds:\n\n* Support for user-defined actions, allowing users to define,\n  customize, and schedule automated tasks, which can be run by the\n  built-in jobs scheduling framework now exposed to users.\n* Significant changes to continuous aggregates, which now separate the\n  view creation from the policy.  Users can now refresh individual\n  regions of the continuous aggregate materialized view, or schedule\n  automated refreshing via  policy.\n* Redesigned informational views, including new (and more general)\n  views for information about hypertable's dimensions and chunks,\n  policies and user-defined actions, as well as support for multi-node\n  TimescaleDB.\n* Moving all formerly enterprise features into our Community Edition,\n  and updating Timescale License, which now provides additional (more\n  permissive) rights to users and developers.\n\nSome of the changes above (e.g., continuous aggregates, updated\ninformational views) do introduce breaking changes to APIs and are not\nbackwards compatible. While the update scripts in TimescaleDB 2.0 will\nupgrade databases running TimescaleDB 1.x automatically, some of these\nAPI and feature changes may require changes to clients and/or upstream\nscripts that rely on the previous APIs.  Before upgrading, we recommend\nreviewing upgrade documentation at docs.timescale.com for more details.\n\n**Major Features**\n\nTimescaleDB 2.0 moves the following major features to GA:\n* #1923 Add support for distributed hypertables\n* #2006 Add support for user-defined actions\n* #2125 #2221 Improve Continuous Aggregate API\n* #2084 #2089 #2098 #2417 Redesign informational views\n* #2435 Move enterprise features to community\n* #2437 Update Timescale License\n\n**Previous Release Candidates**\n\n* #2702 Release Candidate 4 (December 2, 2020)\n* #2637 Release Candidate 3 (November 12, 2020)\n* #2554 Release Candidate 2 (October 20, 2020)\n* #2478 Release Candidate 1 (October 1, 2020)\n\n**Minor Features**\n\nSince the last release candidate 4, there are several minor\nimprovements:\n* #2746 Optimize locking for create chunk API\n* #2705 Block tableoid access on distributed hypertable\n* #2730 Do not allow unique index on compressed hypertables\n* #2764 Bootstrap data nodes with versioned extension\n\n**Bugfixes**\n\nSince the last release candidate 4, there are several bugfixes:\n* #2719 Support disabling compression on distributed hypertables\n* #2742 Fix compression status in chunks view for distributed chunks\n* #2751 Fix crash and cancel when adding data node\n* #2763 Fix check constraint on hypertable metadata table\n\n**Thanks**\n\nThanks to all contributors for the TimescaleDB 2.0 release:\n* @airton-neto for reporting a bug in executing some queries with UNION\n* @nshah14285 for reporting an issue with propagating privileges\n* @kalman5 for reporting an issue with renaming constraints\n* @LbaNeXte for reporting a bug in decompression for queries with\n  subqueries\n* @semtexzv for reporting an issue with continuous aggregates on\n  int-based hypertables\n* @mr-ns for reporting an issue with privileges for creating chunks\n* @cloud-rocket for reporting an issue with setting an owner on\n  continuous aggregate\n* @jocrau for reporting a bug during creating an index with transaction\n  per chunk\n* @fvannee for reporting an issue with custom time types\n* @ArtificialPB for reporting a bug in executing queries with\n  conditional ordering on compressed hypertable\n* @dutchgecko for reporting an issue with continuous aggregate datatype\n  handling\n* @lambdaq for suggesting to improve error message in continuous\n  aggregate creation\n* @francesco11112 for reporting memory issue on COPY\n* @Netskeh for reporting bug on time_bucket problem in continuous\n  aggregates\n* @mr-ns for reporting the issue with CTEs on distributed hypertables\n* @akamensky for reporting an issue with recursive cache invalidation\n* @ryanbooz for reporting slow queries with real-time aggregation on\n  continuous aggregates\n* @cevian for reporting an issue with disabling compression on\n  distributed hypertables\n\n## 2.0.0-rc4 (2020-12-02)\n\nThis release candidate contains bugfixes since the previous release\ncandidate, as well as additional minor features. It improves\nvalidation of configuration changes for background jobs, adds support\nfor gapfill on distributed tables, contains improvements to the memory\nhandling for large COPY, and contains improvements to compression for\ndistributed hypertables.\n\n**Minor Features**\n* #2689 Check configuration in alter_job and add_job\n* #2696 Support gapfill on distributed hypertable\n* #2468 Show more information in get_git_commit\n* #2678 Include user actions into job stats view\n* #2664 Fix support for complex aggregate expression\n* #2672 Add hypertable to continuous aggregates view\n* #2662 Save compression metadata settings on access node\n* #2707 Introduce additional db for data node bootstrapping\n\n**Bugfixes**\n* #2688 Fix crash for concurrent drop and compress chunk\n* #2666 Fix timeout handling in async library\n* #2683 Fix crash in add_job when given NULL interval\n* #2698 Improve memory handling for remote COPY\n* #2555 Set metadata for chunks compressed before 2.0\n\n**Thanks**\n* @francesco11112 for reporting memory issue on COPY\n* @Netskeh for reporting bug on time_bucket problem in continuous\n  aggregates\n\n## 2.0.0-rc3 (2020-11-12)\n\nThis release candidate contains bugfixes since the previous release\ncandidate, as well as additional minor features including support for\n\"user-mapping\" authentication between access/data nodes and an\nexperimental API for refreshing continuous aggregates on individual\nchunks.\n\n**Minor Features**\n* #2627 Add optional user mappings support\n* #2635 Add API to refresh continuous aggregate on chunk\n\n**Bugfixes**\n* #2560 Fix SCHEMA DROP CASCADE with continuous aggregates\n* #2593 Set explicitly all lock parameters in alter_job\n* #2604 Fix chunk creation on hypertables with foreign key constraints\n* #2610 Support analyze of internal compression table\n* #2612 Optimize internal cagg_watermark function\n* #2613 Refresh correct partial during refresh on drop\n* #2617 Fix validation of available extensions on data node\n* #2619 Fix segfault in decompress_chunk for chunks with dropped columns\n* #2620 Fix DROP CASCADE for continuous aggregate\n* #2625 Fix subquery errors when using AsyncAppend\n* #2626 Fix incorrect total_table_pages setting for compressed scan\n* #2628 Stop recursion in cache invalidation\n\n**Thanks**\n* @mr-ns for reporting the issue with CTEs on distributed hypertables\n* @akamensky for reporting an issue with recursive cache invalidation\n* @ryanbooz for reporting slow queries with real-time aggregation on\n  continuous aggregates\n\n## 2.0.0-rc2 (2020-10-21)\n\nThis release candidate contains bugfixes since the previous release candidate.\n\n**Minor Features**\n* #2520 Support non-transactional distibuted_exec\n\n**Bugfixes**\n* #2307 Overflow handling for refresh policy with integer time\n* #2503 Remove error for correct bootstrap of data node\n* #2507 Fix validation logic when adding a new data node\n* #2510 Fix outer join qual propagation\n* #2514 Lock dimension slices when creating new chunk\n* #2515 Add if_attached argument to detach_data_node()\n* #2517 Fix member access within misaligned address in chunk_update_colstats\n* #2525 Fix index creation on hypertables with dropped columns\n* #2543 Pass correct status to lock_job\n* #2544 Assume custom time type range is same as bigint\n* #2563 Fix DecompressChunk path generation\n* #2564 Improve continuous aggregate datatype handling\n* #2568 Change use of ssl_dir GUC\n* #2571 Make errors and messages conform to style guide\n* #2577 Exclude compressed chunks from ANALYZE/VACUUM\n\n## 2.0.0-rc1 (2020-10-06)\n\nThis release adds major new features and bugfixes since the 1.7.4 release.\nWe deem it moderate priority for upgrading.\n\nThis release adds the long-awaited support for distributed hypertables to\nTimescaleDB. With 2.0, users can create distributed hypertables across\nmultiple instances of TimescaleDB, configured so that one instance serves\nas an access node and multiple others as data nodes. All queries for a\ndistributed hypertable are issued to the access node, but inserted data\nand queries are pushed down across data nodes for greater scale and\nperformance.\n\nThis release also adds support for user-defined actions allowing users to\ndefine actions that are run by the TimescaleDB automation framework.\n\nIn addition to these major new features, the 2.0 branch introduces _breaking_ changes\nto APIs and existing features, such as continuous aggregates. These changes are not\nbackwards compatible and might require changes to clients and/or scripts that rely on\nthe previous APIs. Please review our updated documentation and do proper testing to\nensure compatibility with your existing applications.\n\nThe noticeable breaking changes in APIs are:\n- Redefined functions for policies\n- A continuous aggregate is now created with `CREATE MATERIALIZED VIEW`\n  instead of `CREATE VIEW` and automated refreshing requires adding a policy\n  via `add_continuous_aggregate_policy`\n- Redesign of informational views, including new (and more general) views for\n  information about policies and user-defined actions\n\nThis release candidate is upgradable, so if you are on a previous release (e.g., 1.7.4)\nyou can upgrade to the release candidate and later expect to be able to upgrade to the\nfinal 2.0 release. However, please carefully consider your compatibility requirements\n_before_ upgrading.\n\n**Major Features**\n* #1923 Add support for distributed hypertables\n* #2006 Add support for user-defined actions\n* #2435 Move enterprise features to community\n* #2437 Update Timescale License\n\n**Minor Features**\n* #2011 Constify TIMESTAMPTZ OP INTERVAL in constraints\n* #2105 Support moving compressed chunks\n\n**Bugfixes**\n* #1843 Improve handling of \"dropped\" chunks\n* #1886 Change ChunkAppend leader to use worker subplan\n* #2116 Propagate privileges from hypertables to chunks\n* #2263 Fix timestamp overflow in time_bucket optimization\n* #2270 Fix handling of non-reference counted TupleDescs in gapfill\n* #2325 Fix rename constraint/rename index\n* #2370 Fix detection of hypertables in subqueries\n* #2376 Fix caggs width expression handling on int based hypertables\n* #2416 Check insert privileges to create chunk\n* #2428 Allow owner change of continuous aggregate\n* #2436 Propagate grants in continuous aggregates\n\n## 2.0.0-beta6 (2020-09-14)\n\n**For beta releases**, upgrading from an earlier version of the\nextension (including previous beta releases) is not supported.\n\nThis beta release includes breaking changes to APIs. The most\nnotable changes since the beta-5 release are the following, which will\nbe reflected in forthcoming documentation for the 2.0 release.\n\n* Existing information views were reorganized. Retrieving information\nabout sizes and statistics was moved to functions. New views were added\nto expose information, which was previously available only internally.\n* New ability to create custom jobs was added.\n* Continuous aggregate API was redesigned. Its policy creation is separated\nfrom the view creation.\n* compress_chunk_policy and drop_chunk_policy were renamed to compression_policy and\nretention_policy.\n\n## 1.7.4 (2020-09-07)\n\nThis maintenance release contains bugfixes since the 1.7.3 release. We deem it\nhigh priority for upgrading if TimescaleDB is deployed with replicas (synchronous\nor asynchronous).\n\nIn particular the fixes contained in this maintenance release address an issue with\nrunning queries on compressed hypertables on standby nodes.\n\n**Bugfixes**\n* #2340 Remove tuple lock on select path\n\n## 1.7.3 (2020-07-27)\n\nThis maintenance release contains bugfixes since the 1.7.2 release. We deem it high\npriority for upgrading.\n\nIn particular the fixes contained in this maintenance release address issues in compression,\ndrop_chunks and the background worker scheduler.\n\n**Bugfixes**\n* #2059 Improve infering start and stop arguments from gapfill query\n* #2067 Support moving compressed chunks\n* #2068 Apply SET TABLESPACE for compressed chunks\n* #2090 Fix index creation with IF NOT EXISTS for existing indexes\n* #2092 Fix delete on tables involving hypertables with compression\n* #2164 Fix telemetry installed_time format\n* #2184 Fix background worker scheduler memory consumption\n* #2222 Fix `negative bitmapset member not allowed` in decompression\n* #2255 Propagate privileges from hypertables to chunks\n* #2256 Fix segfault in chunk_append with space partitioning\n* #2259 Fix recursion in cache processing\n* #2261 Lock dimension slice tuple when scanning\n\n**Thanks**\n* @akamensky for reporting an issue with drop_chunks and ChunkAppend with space partitioning\n* @dewetburger430 for reporting an issue with setting tablespace for compressed chunks\n* @fvannee for reporting an issue with cache invalidation\n* @nexces for reporting an issue with ChunkAppend on space-partitioned hypertables\n* @PichetGoulu for reporting an issue with index creation and IF NOT EXISTS\n* @prathamesh-sonpatki for contributing a typo fix\n* @sezaru for reporting an issue with background worker scheduler memory consumption\n\n## 1.7.2 (2020-07-07)\n\nThis maintenance release contains bugfixes since the 1.7.1 release. We deem it medium\npriority for upgrading.\n\nIn particular the fixes contained in this maintenance release address bugs in continuous\naggregates, drop_chunks and compression.\n\n**Features**\n* #1877 Add support for fast pruning of inlined functions\n\n**Bugfixes**\n* #1908 Fix drop_chunks with unique constraints when cascade_to_materializations is false\n* #1915 Check for database in extension_current_state\n* #1918 Unify chunk index creation\n* #1932 Change compression locking order\n* #1938 Fix gapfill locf treat_null_as_missing\n* #1982 Check for disabled telemetry earlier\n* #1984 Fix compression bit array left shift count\n* #1997 Add checks for read-only transactions\n* #2002 Reset restoring gucs rather than explicitly setting 'off'\n* #2028 Fix locking in drop_chunks\n* #2031 Enable compression for tables with compound foreign key\n* #2039 Fix segfault in create_trigger_handler\n* #2043 Fix segfault in cagg_update_view_definition\n* #2046 Use index tablespace during chunk creation\n* #2047 Better handling of chunk insert state destruction\n* #2049 Fix handling of PlaceHolderVar in DecompressChunk\n* #2051 Fix tuple concurrently deleted error with multiple continuous aggregates\n\n**Thanks**\n* @akamensky for reporting an issue with telemetry and an issue with drop_chunks\n* @darko408 for reporting an issue with decompression\n* @dmitri191 for reporting an issue with failing background workers\n* @eduardotsj for reporting an issue with indexes not inheriting tablespace settings\n* @fourseventy for reporting an issue with multiple continuous aggregrates\n* @fvannee for contributing optimizations for pruning inlined functions\n* @jflambert for reporting an issue with failing telemetry jobs\n* @nbouscal for reporting an issue with compression jobs locking referenced tables\n* @nicolai6120 for reporting an issue with locf and treat_null_as_missing\n* @nomanor for reporting an issue with expression index with table references\n* @olernov for contributing a fix for compressing tables with compound foreign keys\n* @werjo for reporting an issue with drop_chunks and unique constraints\n\n## 2.0.0-beta5 (2020-06-08)\n\nThis release adds new functionality on distributed hypertables,\nincluding (but not limited to) basic LIMIT pushdown, manual chunk\ncompression, table access methods storage options,  SERIAL columns,\nand altering of the replication factor.\nThis release only supports PG11 and PG12. Thus, PG9.6 and PG10\nare no longer supported.\n\nNote that the 2.0 major release will introduce breaking changes\nto user functions and APIs. In particular, this beta removes the\ncascade parameter from drop_chunks and changes the names of\ncertain GUC parameters. Expect additional breaking changes to be\nintroduced up until the 2.0 release.\n\n**For beta releases**, upgrading from an earlier version of the\nextension (including previous beta releases) is not supported.\n\n**Features**\n\n* #1877 Add support for fast pruning of inlined functions\n* #1922 Cleanup GUC names\n* #1923 Add repartition option on detach/delete_data_node\n* #1923 Allow ALTER TABLE SET on distributed hypertable\n* #1923 Allow SERIAL columns for distributed hypertables\n* #1923 Basic LIMIT push down support\n* #1923 Implement altering replication factor\n* #1923 Support compression on distributed hypertables\n* #1923 Support storage options for distributed hypertables\n* #1941 Change default prefix for distributed tables\n* #1943 Support table access methods for distributed hypertables\n* #1952 Remove cascade option from drop_chunks\n* #1955 Remove support for PG9.6 and PG10\n\n**Bugfixes**\n* #1915 Check for database in extension_current_state\n* #1918 Unify chunk index creation\n* #1923 Fix insert batch size calculation for prepared statements\n* #1923 Fix port conversion issue in add_data_node\n* #1932 Change compression locking order\n* #1938 Fix gapfill locf treat_null_as_missing\n\n**Thanks**\n* @dmitri191 for reporting an issue with failing background workers\n* @fvannee for optimizing pruning of inlined functions\n* @nbouscal for reporting an issue with compression jobs locking referenced tables\n* @nicolai6120 for reporting an issue with locf and treat_null_as_missing\n* @nomanor for reporting an issue with expression index with table references\n\n## 1.7.1 (2020-05-18)\n\nThis maintenance release contains bugfixes since the 1.7.0 release. We deem it medium\npriority for upgrading and high priority for users with multiple continuous aggregates.\n\nIn particular the fixes contained in this maintenance release address bugs in continuous\naggregates with real-time aggregation and PostgreSQL 12 support.\n\n**Bugfixes**\n* #1834 Define strerror() for Windows\n* #1846 Fix segfault on COPY to hypertable\n* #1850 Fix scheduler failure due to bad next_start_time for jobs\n* #1851 Fix hypertable expansion for UNION ALL\n* #1854 Fix reorder policy job to skip compressed chunks\n* #1861 Fix qual pushdown for compressed hypertables where quals have casts\n* #1864 Fix issue with subplan selection in parallel ChunkAppend\n* #1868 Add support for WHERE, HAVING clauses with real time aggregates\n* #1869 Fix real time aggregate support for multiple continuous aggregates\n* #1871 Don't rely on timescaledb.restoring for upgrade\n* #1875 Fix hypertable detection in subqueries\n* #1884 Fix crash on SELECT WHERE NOT with empty table\n\n**Thanks**\n* @airton-neto for reporting an issue with queries over UNIONs of hypertables\n* @dhodyn for reporting an issue with UNION ALL queries\n* @frostwind for reporting an issue with casts in where clauses on compressed hypertables\n* @fvannee for reporting an issue with hypertable detection in inlined SQL functions and an issue with COPY\n* @hgiasac for reporting missing where clause with real time aggregates\n* @louisth for reporting an issue with real-time aggregation and multiple continuous aggregates\n* @michael-sayapin for reporting an issue with INSERTs and WHERE NOT EXISTS\n* @olernov for reporting and fixing an issue with compressed chunks in the reorder policy\n* @pehlert for reporting an issue with pg_upgrade\n\n## 1.7.0 (2020-04-16)\n\nThis release adds major new features and bugfixes since the 1.6.1 release.\nWe deem it moderate priority for upgrading.\n\nThis release adds the long-awaited support for PostgreSQL 12 to TimescaleDB.\n\nThis release also adds a new default behavior when querying continuous\naggregates that we call real-time aggregation. A query on a continuous\naggregate will now combine materialized data with recent data that has\nyet to be materialized.\n\nNote that only newly created continuous aggregates will have this\nreal-time query behavior, although it can be enabled on existing\ncontinuous aggregates with a configuration setting as follows:\n\nALTER VIEW continuous_view_name SET (timescaledb.materialized_only=false);\n\nThis release also moves several data management lifecycle features\nto the Community version of TimescaleDB (from Enterprise), including\ndata reordering and data retention policies.\n\n**Major Features**\n* #1456 Add support for PostgreSQL 12\n* #1685 Add support for real-time aggregation on continuous aggregates\n\n**Bugfixes**\n* #1665 Add ignore_invalidation_older_than to timescaledb_information.continuous_aggregates view\n* #1750 Handle undefined ignore_invalidation_older_than\n* #1757 Restrict watermark to max for continuous aggregates\n* #1769 Add rescan function to CompressChunkDml CustomScan node\n* #1785 Fix last_run_success value in continuous_aggregate_stats view\n* #1801 Include parallel leader in plan execution\n* #1808 Fix ts_hypertable_get_all for compressed tables\n* #1828 Ignore dropped chunks in compressed_chunk_stats\n\n**Licensing changes**\n* Reorder and policies around reorder and drop chunks are now\n  accessible to community users, not just enterprise\n* Gapfill functionality no longer warns about expired license\n\n**Thanks**\n\n* @t0k4rt for reporting an issue with parallel chunk append plans\n* @alxndrdude for reporting an issue when trying to insert into compressed chunks\n* @Olernov for reporting and fixing an issue with show_chunks and drop_chunks for compressed hypertables\n* @mjb512 for reporting an issue with INSERTs in CTEs in cached plans\n* @dmarsh19 for reporting and fixing an issue with dropped chunks in compressed_chunk_stats\n\n## 1.6.1 (2020-03-18)\n\nThis maintenance release contains bugfixes since the 1.6.0 release. We deem it medium\npriority for upgrading.\n\nIn particular the fixes contained in this maintenance release address bugs in continuous\naggregates, time_bucket_gapfill, partial index handling and drop_chunks.\n\n**For this release only**, you will need to restart the database after upgrade before\nrestoring a backup.\n\n**Minor Features**\n* #1666 Support drop_chunks API for continuous aggregates\n* #1711 Change log level for continuous aggregate materialization messages\n\n**Bugfixes**\n* #1630 Print notice for COPY TO on hypertable\n* #1648 Drop chunks from materialized hypertable\n* #1668 Cannot add dimension if hypertable has empty chunks\n* #1673 Fix crash when interrupting create_hypertable\n* #1674 Fix time_bucket_gapfill's interaction with GROUP BY\n* #1686 Fix order by queries on compressed hypertables that have char segment by column\n* #1687 Fix issue with disabling compression when foreign keys are present\n* #1688 Handle many BGW jobs better\n* #1698 Add logic to ignore dropped chunks in hypertable_relation_size\n* #1704 Fix bad plan for continuous aggregate materialization\n* #1709 Prevent starting background workers with NOLOGIN\n* #1713 Fix miscellaneous background worker issues\n* #1715 Fix issue with overly aggressive chunk exclusion in outer joins\n* #1719 Fix restoring/scheduler entrypoint to avoid BGW death\n* #1720 Add scheduler cache invalidations\n* #1727 Fix compressing INTERVAL columns\n* #1728 Handle Sort nodes in ConstraintAwareAppend\n* #1730 Fix partial index handling on hypertables\n* #1739 Use release OpenSSL DLLs for debug builds on Windows\n* #1740 Fix invalidation entries from multiple caggs on same hypertable\n* #1743 Fix continuous aggregate materialization timezone handling\n* #1748 Fix remove_drop_chunks_policy for continuous aggregates\n* #1756 Fix handling of dropped chunks in compression background worker\n\n**Thanks**\n* @RJPhillips01 for reporting an issue with drop chunks.\n* @b4eEx for reporting an issue with disabling compression.\n* @darko408 for reporting an issue with order by on compressed hypertables\n* @mrechte for reporting an issue with compressing INTERVAL columns\n* @tstaehli for reporting an issue with ConstraintAwareAppend\n* @chadshowalter for reporting an issue with partial index on hypertables\n* @geoffreybennett for reporting an issue with create_hypertable when interrupting operations\n* @alxndrdude for reporting an issue with background workers during restore\n* @zcavaliero for reporting and fixing an issue with dropped columns in hypertable_relation_size\n* @ismailakpolat for reporting an issue with cagg materialization on hypertables with TIMESTAMP column\n\n## 1.6.0 (2020-01-14)\n\nThis release adds major new features and bugfixes since the 1.5.1 release.\nWe deem it moderate priority for upgrading.\n\nThe major new feature in this release allows users to keep the aggregated\ndata in a continuous aggregate while dropping the raw data with drop_chunks.\nThis allows users to save storage by keeping only the aggregates.\n\nThe semantics of the refresh_lag parameter for continuous aggregates has\nbeen changed to be relative to the current timestamp instead of the maximum\nvalue in the table. This change requires that an integer_now func be set on\nhypertables with integer-based time columns to use continuous aggregates on\nthis table.\n\nWe added a timescaledb.ignore_invalidation_older_than parameter for continuous\naggregates. This parameter accept a time-interval (e.g. 1 month). If set,\nit limits the amount of time for which to process invalidation. Thus, if\ntimescaledb.ignore_invalidation_older_than = '1 month', then any modifications\nfor data older than 1 month from the current timestamp at modification time may\nnot cause continuous aggregate to be updated. This limits the amount of work\nthat a backfill can trigger. By default, all invalidations are processed.\n\n**Major Features**\n* #1589 Allow drop_chunks while keeping continuous aggregates\n\n**Minor Features**\n* #1568 Add ignore_invalidation_older_than option to continuous aggs\n* #1575 Reorder group-by clause for continuous aggregates\n* #1592 Improve continuous agg user messages\n\n**Bugfixes**\n* #1565 Fix partial select query for continuous aggregate\n* #1591 Fix locf treat_null_as_missing option\n* #1594 Fix error in compression constraint check\n* #1603 Add join info to compressed chunk\n* #1606 Fix constify params during runtime exclusion\n* #1607 Delete compression policy when drop hypertable\n* #1608 Add jobs to timescaledb_information.policy_stats\n* #1609 Fix bug with parent table in decompression\n* #1624 Fix drop_chunks for ApacheOnly\n* #1632 Check for NULL before dereferencing variable\n\n**Thanks**\n* @optijon for reporting an issue with locf treat_null_as_missing option\n* @acarrera42 for reporting an issue with constify params during runtime exclusion\n* @ChristopherZellermann for reporting an issue with the compression constraint check\n* @SimonDelamare for reporting an issue with joining hypertables with compression\n\n## 2.0.0-beta4 (2019-12-19)\n\n**For beta releases**, upgrading from an earlier version of the\nextension (including previous beta releases) is not supported.\n\nThis release includes user experience improvements for managing data\nnodes, more efficient statistics collection for distributed\nhypertables, and miscellaneous fixes and improvements.\n\n## 1.5.1 (2019-11-12)\n\nThis maintenance release contains bugfixes since the 1.5.0 release. We deem it low\npriority for upgrading.\n\nIn particular the fixes contained in this maintenance release address potential\nsegfaults and no other security vulnerabilities. The bugfixes are related to bloom\nindexes and updates from previous versions.\n\n**Bugfixes**\n* #1523 Fix bad SQL updates from previous updates\n* #1526 Fix hypertable model\n* #1530 Set active snapshots in multi-xact index create\n\n**Thanks**\n* @84660320 for reporting an issue with bloom indexes\n* @gumshoes @perhamm @jermudgeon @gmisagm for reporting the issue with updates\n\n## 2.0.0-beta3 (2019-11-05)\n\n**For beta releases**, upgrading from an earlier version of the\nextension (including previous beta releases) is not supported.\n\nThis release improves performance for queries executed on distributed\nhypertables, fixes minor issues and blocks a number of SQL API\nfunctions, which are not supported on distributed hypertables. It also\nadds information about distributed databases in the telemetry.\n\n## 2.0.0-beta2 (2019-10-22)\n\nThis release introduces *distributed hypertables*, a major new\nfeature that allows hypertables to scale out across multiple nodes for\nincreased performance and fault tolerance. Please review the\ndocumentation to learn how to configure and use distributed\nhypertables and what current limitations are.\n\n## 1.5.0 (2019-10-31)\n\nThis release adds major new features and bugfixes since the 1.4.2 release.\nWe deem it moderate priority for upgrading.\n\nThis release adds compression as a major new feature.\nMultiple type-specific compression options are available in this release\n(including DeltaDelta with run-length-encoding for integers and\ntimestamps; Gorilla compression for floats; dictionary-based compression\nfor any data type, but specifically for low-cardinality datasets;\nand other LZ-based techniques). Individual columns can be compressed with\ntype-specific compression algorithms as Postgres' native row-based format\nare rolled up into columnar-like arrays on a per chunk basis.\nThe query planner then handles transparent decompression for compressed\nchunks at execution time.\n\nThis release also adds support for basic data tiering by supporting\nthe migration of chunks between tablespaces, as well as support for\nparallel query coordination to the ChunkAppend node.\nPreviously ChunkAppend would rely on parallel coordination in the\nunderlying scans for parallel plans.\n\nMore information can be found on [our blog](https://blog.timescale.com/blog/building-columnar-compression-in-a-row-oriented-database)\nor in this [tutorial](https://docs.timescale.com/latest/tutorials/compression-tutorial)\n\n**For this release only**, you will need to restart the database before running\n`ALTER EXTENSION`\n\n**Major Features**\n* #1393 Moving chunks between different tablespaces\n* #1433 Make ChunkAppend parallel aware\n* #1434 Introducing native compression, multiple compression algorithms, and hybrid row/columnar projections\n\n**Minor Features**\n* #1471 Allow setting reloptions on chunks\n* #1479 Add next_start option to alter_job_schedule\n* #1481 Add last_successful_finish to bgw_job_stats\n\n**Bugfixes**\n* #1444 Prevent LIMIT pushdown in JOINs\n* #1447 Fix runtime exclusion memory leak\n* #1464 Fix ordered append with expressions in ORDER BY clause with space partitioning\n* #1476 Fix logic for BGW rescheduling\n* #1477 Fix gapfill treat_null_as_missing\n* #1493 Prevent recursion in invalidation processing\n* #1498 Fix overflow in gapfill's interpolate\n* #1499 Fix error for exported_uuid in pg_restore\n* #1503 Fix bug with histogram function in parallel\n\n**Thanks**\n* @dhyun-obsec for reporting an issue with pg_restore\n* @rhaymo for reporting an issue with interpolate\n* @optijon for reporting an issue with locf treat_null_as_missing\n* @fvannee for reporting an issue with runtime exclusion\n* @Lectem for reporting an issue with histograms\n* @rogerdwan for reporting an issue with BGW rescheduling\n* @od0 for reporting an issue with alter_job_schedule\n\n## 1.4.2 (2019-09-11)\n\nThis maintenance release contains bugfixes since the 1.4.1 release. We deem it medium\npriority for upgrading.\n\nIn particular the fixes contained in this maintenance release address 2 potential\nsegfaults and no other security vulnerabilities. The bugfixes are related to\nbackground workers, OUTER JOINs, ordered append on space partitioned hypertables\nand expression indexes.\n\n**Bugfixes**\n* #1327 Fix chunk exclusion with ordered append\n* #1390 Fix deletes of background workers while a job is running\n* #1392 Fix cagg_agg_validate expression handling (segfault)\n* #1408 Fix ChunkAppend space partitioning support for ordered append\n* #1420 Fix OUTER JOIN qual propagation\n* #1422 Fix background worker error handling (segfault)\n* #1424 Fix ChunkAppend LIMIT pushdown\n* #1429 Fix expression index creation\n\n**Thanks**\n* @shahidhk for reporting an issue with OUTER JOINs\n* @cossbow and @xxGL1TCHxx for reporting reporting issues with ChunkAppend and space partitioning\n* @est for reporting an issue with CASE expressions in continuous aggregates\n* @devlucasc for reporting the issue with deleting a background worker while a job is running\n* @ryan-shaw for reporting an issue with expression indexes on hypertables with dropped columns\n\n## 1.4.1 (2019-08-01)\n\nThis maintenance release contains bugfixes since the 1.4.0 release. We deem it medium\npriority for upgrading.\n\nIn particular the fixes contained in this maintenance release address 2 potential\nsegfaults and no other security vulnerabilities. The bugfixes are related to queries\nwith prepared statements, PL/pgSQL functions and interoperability with other extensions.\nMore details below.\n\n**Bugfixes**\n* #1362 Fix ConstraintAwareAppend subquery exclusion\n* #1363 Mark drop_chunks as VOLATILE and not PARALLEL SAFE\n* #1369 Fix ChunkAppend with prepared statements\n* #1373 Only allow PARAM_EXTERN as time_bucket_gapfill arguments\n* #1380 Handle Result nodes gracefully in ChunkAppend\n\n**Thanks**\n* @overhacked for reporting an issue with drop_chunks and parallel queries\n* @fvannee for reporting an issue with ConstraintAwareAppend and subqueries\n* @rrb3942 for reporting a segfault with ChunkAppend and prepared statements\n* @mchesser for reporting a segfault with time_bucket_gapfill and subqueries\n* @lolizeppelin for reporting and helping debug an issue with ChunkAppend and Result nodes\n\n## 1.4.0 (2019-07-18)\n\nThis release contains major new functionality for continuous aggregates\nand adds performance improvements for analytical queries.\n\nIn version 1.3.0 we added support for continuous aggregates which\nwas initially limited to one continuous aggregate per hypertable.\nWith this release, we remove this restriction and allow multiple\ncontinuous aggregates per hypertable.\n\nThis release adds a new custom node ChunkAppend that can perform\nexecution time constraint exclusion and is also used for ordered\nappend. Ordered append no longer requires a LIMIT clause and now\nsupports space partitioning and ordering by time_bucket.\n\n**Major features**\n* #1270 Use ChunkAppend to replace Append nodes\n* #1257 Support for multiple continuous aggregates\n\n**Minor features**\n* #1181 Remove LIMIT clause restriction from ordered append\n* #1273 Propagate quals to joined hypertables\n* #1317 Support time bucket functions in Ordered Append\n* #1331 Add warning message for REFRESH MATERIALIZED VIEW\n* #1332 Add job statistics columns to timescaledb_information.continuous_aggregate_stats view\n* #1326 Add architecture and bit size to telemetry\n\n**Bugfixes**\n* #1288 Do not remove Result nodes with one-time filter\n* #1300 Fix telemetry report return value\n* #1339 Fix continuous agg catalog table insert failure\n* #1344 Update continuous agg bgw job start time\n\n**Thanks**\n* @ik9999 for reporting a bug with continuous aggregates and negative refresh lag\n\n## 1.3.2 (2019-06-24)\n\nThis maintenance release contains bug and security fixes since the 1.3.1 release. We deem it moderate-to-high priority for upgrading.\n\nThis release fixes some security vulnerabilities, specifically related to being able to elevate role-based permissions by database users that already have access to the database.  We strongly recommend that users who rely on role-based permissions upgrade to this release as soon as possible.\n\n**Security Fixes**\n* #1311 Fix role-based permission checking logic\n\n**Bugfixes**\n* #1315 Fix potentially lost invalidations in continuous aggs\n* #1303 Fix handling of types with custom time partitioning\n* #1299 Arm32: Fix Datum to int cast issue\n* #1297 Arm32: Fix crashes due to long handling\n* #1019 Add ARM32 tests on travis\n\n**Thanks**\n* @hedayat for reporting the error with handling of types with custom time partitioning\n\n## 1.3.1 (2019-06-10)\n\nThis maintenance release contains bugfixes since the 1.3.0 release.\nWe deem it low-to-moderate priority for upgrading.\n\nIn particular, the fixes contained in this maintenance release do not address any\nsecurity vulnerabilities, while the only one affecting system stability is related\nto TimescaleDB running on PostgreSQL 11.  More details below.\n\n**Bugfixes**\n* #1220 Fix detecting JOINs for continuous aggs\n* #1221 Fix segfault in VACUUM on PG11\n* #1228 ARM32 Fix: Pass int64 using Int64GetDatum when a Datum is required\n* #1232 Allow Param as time_bucket_gapfill arguments\n* #1236 Stop preventing REFRESH in transaction blocks\n* #1283 Fix constraint exclusion for OUTER JOIN\n\n**Thanks**\n* @od0 for reporting an error with continuous aggs and JOINs\n* @rickbatka for reporting an error when using time_bucket_gapfill in functions\n* @OneMoreSec for reporting the bug with VACUUM\n* @dvdrozdov @od0 @t0k4rt for reporting the issue with REFRESH in transaction blocks\n* @mhagander and @devrimgunduz for suggesting adding a CMAKE flag to control the default telemetry level\n\n## 1.3.0 (2019-05-06)\n\nThis release contains major new functionality that we call continuous aggregates.\n\nAggregate queries which touch large swathes of time-series data can take a long\ntime to compute because the system needs to scan large amounts of data on every\nquery execution. Our continuous aggregates continuously calculate the\nresults of a query in the background and materialize the results. Queries to the\ncontinuous aggregate view are then significantly faster as they do not need to\ntouch the raw data in the hypertable, instead using the pre-computed aggregates\nin the view.\n\nContinuous aggregates are somewhat similar to PostgreSQL materialized\nviews, but unlike a materialized view, continuous\naggregates do not need to be refreshed manually; the view will be refreshed\nautomatically in the background as new data is added, or old data is\nmodified. Additionally, it does not need to re-calculate all of the data on\nevery refresh. Only new and/or invalidated data will be calculated.  Since this\nre-aggregation is automatic, it doesn’t add any maintenance burden to your\ndatabase.\n\nOur continuous aggregate approach supports high-ingest rates by avoiding the\nhigh-write amplification associated with trigger-based approaches. Instead,\nwe use invalidation techniques to track what data has changed, and then correct\nthe materialized aggregate the next time that the automated process executes.\n\nMore information can be found on [our docs overview](http://docs.timescale.com/using-timescaledb/continuous-aggregates)\nor in this [tutorial](http://docs.timescale.com/tutorials/continuous-aggs-tutorial).\n\n**Major Features**\n* #1184 Add continuous aggregate functionality\n\n**Minor Features**\n* #1005 Enable creating indexes with one transaction per chunk\n* #1007 Remove hypertable parent from query plans\n* #1038 Infer time_bucket_gapfill arguments from WHERE clause\n* #1062 Make constraint aware append parallel safe\n* #1067 Add treat_null_as_missing option to locf\n* #1112 Add support for window functions to gapfill\n* #1130 Add support for cross datatype chunk exclusion for time types\n* #1134 Add support for partitionwise aggregation\n* #1153 Add time_bucket support to chunk exclusion\n* #1170 Add functions for turning restoring on/off and setting license key\n* #1177 Add transformed time_bucket comparison to quals\n* #1182 Enable optimizing SELECTs within INSERTs\n* #1201 Add telemetry for policies: drop_chunk & reorder\n\n**Bugfixes**\n* #1010 Add session locks to CLUSTER\n* #1115 Fix ordered append optimization for join queries\n* #1123 Fix gapfill with prepared statements\n* #1125 Fix column handling for columns derived from GROUP BY columns\n* #1132 Adjust ordered append path cost\n* #1155 Limit initial max_open_chunks_per_insert to PG_INT16_MAX\n* #1167 Fix postgres.conf ApacheOnly license\n* #1183 Handle NULL in a check constraint name\n* #1195 Fix cascade in scheduled drop chunks\n* #1196 Fix UPSERT with prepared statements\n\n**Thanks**\n* @spickman for reporting a segfault with ordered append and JOINs\n* @comicfans for reporting a performance regression with ordered append\n* @Specter-Y for reporting a segfault with UPSERT and prepared statements\n* @erthalion submitting a bugfix for a segfault with validating check constraints\n\n## 1.2.2 (2019-03-14)\n\nThis release contains bugfixes.\n\n**Bugfixes**\n* #1097 Adjust ordered append plan cost\n* #1079 Stop background worker on ALTER DATABASE SET TABLESPACE and CREATE DATABASE WITH TEMPLATE\n* #1088 Fix ON CONFLICT when using prepared statements and functions\n* #1089 Fix compatibility with extensions that define planner_hook\n* #1057 Fix chunk exclusion constraint type inference\n* #1060 Fix sort_transform optimization\n\n**Thanks**\n* @esatterwhite for reporting a bug when using timescaledb with zombodb\n* @eeeebbbbrrrr for fixing compatibility with extensions that also define planner_hook\n* @naquad for reporting a segfault when using ON conflict in stored procedures\n* @aaronkaplan for reporting an issue with ALTER DATABASE SET TABLESPACE\n* @quetz for reporting an issue with CREATE DATABASE WITH TEMPLATE\n* @nbouscal for reporting an issue with ordered append resulting in bad plans\n\n## 1.2.1 (2019-02-11)\n\nThis release contains bugfixes.\n\n**Notable commits**\n* [2f6b58a] Fix tlist on hypertable inserts inside CTEs\n* [7973b4a] Stop background worker on rename database\n* [32cc645] Fix loading the tsl license in parallel workers\n\n**Thanks**\n\n* @jamessewell for reporting and helping debug a segfault in last() [034a0b0]\n* @piscopoc for reporting a segfault in time_bucket_gapfill [e6c68f8]\n\n## 1.2.0 (2019-01-29)\n\n**This is our first release to include Timescale-Licensed features, in addition to new Apache-2 capabilities.**\n\nWe are excited to be introducing new time-series analytical functions, advanced data lifecycle management capabilities, and improved performance.\n- **Time-series analytical functions**: Users can now use our `time_bucket_gapfill` function, to write complex gapfilling, last object carried forward, and interpolation queries.\n- **Advanced data lifecycle management**: We are introducing scheduled policies, which use our background worker framework to manage time-series data. In this release, we support scheduled `drop_chunks` and `reorder`.\n- **Improved performance**: We added support for ordered appends, which optimize a large range of queries - particularly those that are ordered by time and contain a LIMIT clause. Please note that ordered appends do not support ordering by `time_bucket` at this time.\n- **Postgres 11 Support**: We added beta support for PG11 in 1.1.0. We're happy to announce that our PG11 support is now out of beta, and fully supported.\n\nThis release adds code under a new license, LICENSE_TIMESCALE. This code can be found in `tsl`.\n\n**For this release only**, you will need to restart the database before running\n`ALTER EXTENSION`\n\n**Notable commits**\n\n* [a531733] switch cis state when we switch chunks\n* [5c6b619] Make a copy of the ri_onConflict object in PG11\n* [61e524e] Make slot for upserts be update for every chunk switch\n* [8a7c127] Fix for ExecSlotDescriptor during upserts\n* [fa61613] Change time_bucket_gapfill argument names\n* [01be394] Fix bgw_launcher restart when failing during launcher setup\n* [7b3929e] Add ordered append optimization\n* [a69f84c] Fix signal processing in background workers\n* [47b5b7d] Log which chunks are dropped by background workers\n* [4e1e15f] Add reorder command\n* [2e4bb5d] Recluster and drop chunks scheduling code\n* [ef43e52] Add alter_policy_schedule API function\n* [5ba740e] Add gapfill query support\n* [be7c74c] Add logic for automatic DB maintenance functions\n* [4ff6ac7] Initial Timescale-Licensed-Module and License-Key Implementation\n* [fc42539] Add new top-level licensing information\n* [31e9c5b] Fix time column handling in get_create_command\n* [1b8ceca] Avoid loading twice in parallel workers and load only from $libdir\n* [76d7875] Don't throw errors when extension is loaded but not installed yet\n* [eecd845] Add Timescale License (TSL)\n* [4b42b30] Free ChunkInsertStates when the es_per_tuple_exprcontext is freed\n\n**Thanks**\n\n* @fordred for reporting our docker-run.sh script was out of date\n* @JpWebster for reporting a deadlock between reads an drop_chunks\n* @chickenburgers for reporting an issue with our CMake\n* Dimtrj and Asbjørn D., on slack, for creating a reproducible testcase for an UPSERT bug\n* @skebanga for reporting a loader bug\n\n\n## 1.1.1 (2018-12-20)\n\nThis release contains bugfixes.\n\n**High-level changes**\n* Fix issue when upgrading with pg_upgrade\n* Fix a segfault that sometimes appeared in long COPYs\n* Other bug and stability fixes\n\n**Notable commits**\n\n* [f99b540] Avoid loading twice in parallel workers and load only from $libdir\n* [e310f7d] Don't throw errors when extension is loaded but not installed yet\n* [8498416] Free ChunkInsertStates when the es_per_tuple_exprcontext is freed\n* [937eefe] Set C standard to C11\n\n**Thanks**\n\n* @costigator for reporting the pg_upgrade bug\n* @FireAndIce68 for reporting the parallel workers issue\n* @damirda for reporting the copy bug\n\n## 1.1.0 (2018-12-13)\n\nOur 1.1 release introduces beta support for PG 11, as well as several performance optimizations aimed at improving chunk exclusion for read queries. We are also packaging our new timescale-tune tool (currently in beta) with our Debian and Linux releases. If you encounter any issues with our beta features, please file a Github issue.\n\n**Potential breaking changes**\n- In addition to optimizing first() / last() to utilize indices for non-group-by queries, we adjusted its sorting behavior to match that of PostgreSQL’s max() and min() functions. Previously, if the column being sorted had NULL values, a NULL would be returned. First() and last() now instead ignore NULL values.\n\n**Notable Commits**\n\n* [71f3a0c] Fix Datum conversion issues\n* [5aa1eda] Refactor compatibility functions and code to support PG11\n* [e4a4f8e] Add support for functions on open (time) dimensions\n* [ed5067c] Fix interval_from_now_to_internal timestamptz handling\n* [019971c] Optimize FIRST/LAST aggregate functions\n* [83014ee] Implement drop_chunks in C\n* [9a34028] Implement show_chunks in C and have drop_chunks use it\n* [d461959] Add view to show hypertable information\n* [35dee48] Remove version-checking from client-side\n* [5b6a5f4] Change size utility and job functions to STRICT\n* [7e55d91] Add checks for NULL arguments to DDL functions\n* [c1db608] Fix upsert TLE translation when mapping variable numbers\n* [55a378e] Check extension exists for DROP OWNED and DROP EXTENSION\n* [0c8c085] Exclude unneeded chunks for IN/ANY/ALL operators\n* [f27c0a3] Move int time_bucket functions with offset to C\n\n**Thanks**\n* @did-g for some memory improvements\n\n## 1.0.1 (2018-12-05)\n\nThis commit contains bugfixes and optimizations for 1.0.0\n\n**Notable commits**\n\n* [6553aa4] Make a number of size utility functions to `STRICT`\n* [bb1d748] Add checks for NULL arguments to `set_adaptive_chunking`, `set_number_partitions`, `set_chunk_time_interval`, `add_dimension`, and `create_hypertable`\n* [a534ed4] Fix upsert TLE translation when mapping variable numbers\n* [aecd55b] Check extension exists for DROP OWNED and DROP EXTENSION\n\n## 1.0.0 (2018-10-30)\n\n**This is our 1.0 release!**\n\nFor notable commits between 0.12.0/0.12.1 and this final 1.0 release, please see previous entries for the release candidates (rc1, rc2, and rc3).\n\n**Thanks**\nTo all the external contributors who helped us debug the release candidates, as well as anyone who has contributed bug reports, PRs, or feedback on Slack, GitHub, and other channels. All input has been valuable and helped us create the product we have today!\n\n**Potential breaking changes**\n* To better align with the ISO standard so that time bucketing starts each week by default on a Monday (rather than Saturday), the `time_bucket` epoch/origin has been changed from January 1, 2000 to January 3, 2000.  The function now includes an `origin` parameter that can be used to adjust this.\n* Error codes are now prefixed with `TS` instead of the prior `IO` prefix. If you were checking for these error codes by name, please update your code.\n\n\n## 1.0.0-rc3 (2018-10-18)\n\nThis release is our third 1.0 release candidate. We expect to only merge bug fixes between now and our final 1.0 release. This is a big milestone for us and signifies our maturity and enterprise readiness.\n\n**PLEASE NOTE** that release candidate (rc) builds will only be made available via GitHub and Docker, and _not_ on other release channels. Please help us test these release candidates out if you can!\n\n**Potential breaking change**: Starting with rc2, we updated our error codes to be prefixed with `TS` instead of the old `IO` prefix. If you were checking for these error codes by name, please update your checks.\n\n**Notable commits**\n* [f7ba13d] Handle and test tablespace changes to and from the default tablespace\n* [9ccda0d] Start stopped workers on restart message\n* [3e3bb0c] Add bool created to create_hypertable and add_dimension return value\n* [53ff656] Add state machine and polling to launcher\n* [d9b2dfe] Change return value of add_dimension to TABLE\n* [19299cf] Make all time_bucket function STRICT\n* [297d885] Add a version of time_bucket that takes an origin\n* [e74be30] Move time_bucket epoch to a Monday\n* [46564c1] Handle ALTER SCHEMA RENAME properly\n* [a83e283] Change return value of create_hypertable to TABLE\n* [aea7c7e] Add GRANTs to update script for pg_dump to work\n* [119963a] Replace hardcoded bash path in shell scripts\n\n**Thanks**\n* @jesperpedersen for several PRs that help improve documentation and some rough edges\n* @did-g for improvements to our build process\n* @skebanga for reporting an issue with ALTER SCHEMA RENAME\n* @p-alik for suggesting a way to improve our bash scripts' portability\n* @mx781 and @HeikoOnnebrink for reporting an issues with permission GRANTs and ownership when using pg_dump\n\n\n## 1.0.0-rc2 (2018-09-27)\n\nThis release is our second 1.0 release candidate. We expect to only merge bug fixes between now and our final 1.0 release. This is a big milestone for us and signifies our maturity and enterprise readiness.\n\n**PLEASE NOTE** that release candidate (rc) builds will only be made available via GitHub and Docker, and _not_ on other release channels. Please help us test these release candidates out if you can!\n\n**Potential breaking change**: We updated our error codes to be prefixed with `TS` instead of the old `IO` prefix. If you were checking for these error codes by name, please update your checks.\n\n**Notable commits**\n* [b43574f] Switch 'IO' error prefix to 'TS'\n* [9747885] Prefix public C functions with ts_\n* [39510c3] Block unknown alter table commands on  hypertables\n* [2408a83] Add support for ALTER TABLE SET TABLESPACE on hypertables\n* [41d9846] Enclose macro replacement list and arguments in parentheses\n* [cc59d51] Replace macro LEAST_TIMESTAMP by a static function\n* [281f363] Modify telemetry BGW to run every hour the first 12 hours\n* [a09b3ec] Add pg_isolation_regress support to the timescale build system\n* [2c267ba] Handle SIGTERM/SIGINT asynchronously\n* [5377e2d] Fix use-after-free bug for connections in the telemetry BGW\n* [248f662] Fix pg_dump for unprivileged users\n* [193fa4a] Stop background workers when extension is DROP OWNED\n* [625e3fa] Fix negative value handling in int time_bucket\n* [a33224b] Add loader API version function\n* [18b8068] Remove unnecessary index on dimension metadata table\n* [d09405d] Fix adaptive chunking when hypertables have multiple dimensions\n* [a81dc18] Block signals when writing to the log table in tests\n* [d5a6392] Fix adaptive chunking so it chooses correct index\n* [3489cca] Fix sigterm handling in background jobs\n* [2369ae9] Remove !WIN32 for sys/time.h and sys/socket.h, pg provides fills\n* [ebbb4ae] Also add sys/time.h for NetBSD. Fixes #700\n* [1a9ae17] Fix build on FreeBSD wrt sockets\n* [8225cd2] Remove (redefined) macro PG_VERSION and replace with PACKAGE_VERSION\n* [2a07cf9] Release SpinLock even when we're about to Error due to over-decrementing\n* [b2a15b8] Make sure DB schedulers are not decremented if they were never incremented\n* [6731c86] Add support for pre-release version checks\n\n**Thanks**\n* @did-g for an improvement to our macros to make compiliers happy\n* @mx781 and @HeikoOnnebrink for reporting issues with working with pg_dump fully\n* @znbang and @timolson for reporting a bug that was causing telemetry to fail\n* @alanhamlett for reporting an issue with spinlocks when handling SIGTERMs\n* @oldgreen for reporting an issue with building on NetBSD\n* @kev009 for fixing build issues on FreeBSD and NetBSD\n* All the others who have helped us test and used these RCs!\n\n\n## 0.12.1 (2018-09-19)\n\n**High-level changes**\n\n* Fixes for a few issues related to the new scheduler and background worker framework.\n* Fixed bug in adaptive chunking where the incorrect index could be used for determining the current interval.\n* Improved testing, code cleanup, and other housekeeping.\n\n**Notable commits**\n* [0f6f7fc] Fix adaptive chunking so it chooses correct index\n* [3ed79ed] Fix sigterm handling in background jobs\n* [bea098f] Remove !WIN32 for sys/time.h and sys/socket.h, pg provides fills\n* [9f62a1a] Also add sys/time.h for NetBSD. Fixes #700\n* [95a982f] Fix build on FreeBSD wrt sockets\n* [fcb4a79] Remove (redefined) macro PG_VERSION and replace with PACKAGE_VERSION\n* [2634897] Release SpinLock even when we're about to Error due to over-decrementing\n* [1f30dbb] Make sure DB schedulers are not decremented if they were never incremented\n* [f518cd0] Add support for pre-release version checks\n* [acebaea] Don't start schedulers for template databases.\n* [f221a12] Fix use-after-free bug in telemetry test\n* [0dc5bbb] Use pg_config bindir directory for pg executables\n\n**Thanks**\n* @did-g for reporting a use-after-free bug in a test and for improving the robustness of another test\n* @kev009 for fixing build issues on FreeBSD and NetBSD\n\n\n## 1.0.0-rc1 (2018-09-12)\n\nThis release is our 1.0 release candidate. We expect to only merge bug fixes between now and our final 1.0 release. This is a big milestone for us and signifies our maturity and enterprise readiness.\n\n**PLEASE NOTE** that release candidate (rc) builds will only be made available via GitHub and Docker, and _not_ on other release channels. Please help us test these release candidates out if you can!\n\n\n**Notable commits**\n* [acebaea] Don't start schedulers for template databases.\n* [f221a12] Fix use-after-free bug in telemetry test\n* [2092b2a] Fix unused variable warning in Release build\n* [0dc5bbb] Use pg_config bindir directory for pg executables\n\n**Thanks**\n* @did-g for reporting a use-after-free bug in a test and for improving the robustness of another test\n\n\n## 0.12.0 (2018-09-10)\n\n**High-level changes**\n\n*Scheduler framework:* This release introduces a background job framework and scheduler. Each database running within a PostgreSQL instance has a scheduler that schedules recurring jobs from a new jobs table while maintaining statistics that inform the scheduler's policy. Future releases will leverage this scheduler framework for more automated management of data retention, archiving, analytics, and the like.\n\n*Telemetry:* Using this new scheduler framework, TimescaleDB databases now send anonymized usage information to a telemetry server via HTTPS, as well as perform version checking to notify users if a newer version is available. For transparency, a new `get_telemetry_report` function details the exact JSON that is sent, and users may also opt out of this telemetry and version check.\n\n*Continued hardening:* This release addresses several issues around more robust backup and recovery, handling large numbers of chunks, and additional test coverage.\n\n**Notable commits**\n\n* [efab2aa] Fix net lib functionality on Windows and improve error handling\n* [71589c4] Fix issues when OpenSSL is not available\n* [a43cd04] Call the main telemetry function inside BGW executor\n* [faf481b] Add telemetry functionality\n* [45a2b76] Add new Connection and HTTP libraries\n* [b6fe657] Fix max_background_workers guc, errors on EXEC_BACKEND and formatting\n* [5d8c7cc] Add a scheduler for background jobs\n* [55a7141] Implement a cluster-wide launcher for background workers\n* [5bc705f] Update bootstrap to check for cmake and exit if not found\n* [98e56dd] Improve show_indexes test func to be more platform agnostic\n* [b928caa] Note how to recreate templated files\n* [8571e41] Use AttrNumberGetAttrOffset instead of Anum_name - 1 for array indexing\n* [d1710ef] Improve regression test script to cleanup more thoroughly\n* [fc3677f] Reduce number of open chunks per insert\n* [027b7b2] Hide extension symbols by default on Unix platforms\n* [6a3abe5] Fix SubspaceStore to ensure max_open_chunks_per_insert is obeyed\n\n**Thanks**\n\n@EvanCarroll for updates to the bootstrap script to check for cmake\n\n\n## 0.11.0 (2018-08-08)\n\n**High-level changes**\n\n* **Adaptive chunking**: This feature, currently in beta, allows the database to automatically adapt a chunk's time interval, so that users do not need to manually set (and possibly manually change) this interval size. In this release, users can specify either a target chunk data size (in terms of MB or GB), and the chunk's time intervals will be automatically adapted. Alternatively, users can ask the database to just estimate a target size itself based on the platform's available memory and other parameters, and the system will adapt accordingly. This type of automation can simplify initial database test and operations. This feature is default off. Note: The default time interval for non-adaptive chunking has also been changed from 1 month to 1 week.\n* **Continued hardening**: This release addresses a number of less frequently used schema modifications, functions, or constraints. Unsupported functions are safely blocked, while we have added support for a number of new types of table alterations. This release also adds additional test coverage.\n* Support for additional types of time columns, if they are binary compatible (thanks @fvannee!).\n\n**Notable commits**\n\n* [9ba2e81] Fix segfault with custom partition types\n* [7e9bf25] Change default chunk size to one week\n* [506fa18] Add tests for custom types\n* [1d9ade7] add support for other types as timescale column\n* [570f2f8] Validate parameters when creating partition info\n* [148f2da] Use shared_buffers as the available cache memory\n* [e0a15c1] Add additional comments to explain algorithm\n* [d81dccb] Set the default chunk_time_interval to 1 day with adaptive chunking enabled\n* [2e7b32c] Add WARNING when doing min-max heap scan for adaptive chunking\n* [6b452a8] Update adaptive chunk algorithm to handle very small chunks.\n* [9c9cdca] Add support for adaptive chunk sizing\n* [7f8d17d] Handle DEFERRED and VALID options for constraints\n* [0c5c21b] Block using rules with hypertables\n* [37142e9] Block INSERTs on a hypertable's root table\n* [4daf087] Fix some ALTER TABLE corner case bugs on hypertables\n* [122f5f1] Block replica identity usage with hypertables\n* [8bf552e] Block unlogged tables from being used as hypertables\n* [a8c637e] Create aggregate functions only once to avoid dependency issues\n* [a97f2af] Add support for custom hypertable dimension types\n* [dfe026c] Refactor create_hypertable rel access.\n* [ed379c3] Validate existing indexes before adding a new dimension\n* [1f2d276] Fix and improve show_indexes test support function\n* [77b0035] Enforce IMMUTABLE partitioning functions\n* [cbc5e60] Block NO INHERIT constraints on hypertables\n* [e362e9c] Block mixing hypertables with postgres inheritance\n* [011f12b] Add support for CLUSTER ON and SET WITHOUT CLUSTER\n* [e947c6b] Improve handling of column settings\n* [fc4957b] Update statistics on parent table when doing ANALYZE\n* [82942bf] Enable backwards compatibility for loader for 0.9.0 and 0.9.1\n\n**Thanks**\n\n* @Ngalstyan4 and @hjsuh18, our interns, for all of the PRs this summer\n* @fvannee for a PR adding support for binary compatible custom types as a time column\n* @fmacelw for reporting a bug where first() and last() hold reference across extension update\n* @soccerdroid for reporting a corner case bug in ALTER TABLE\n\n\n## 0.10.1 (2018-07-12)\n\n**High-level changes**\n* Improved memory management for long-lived connections.\n* Fixed handling of dropping triggers that would lead to orphaned references in pg_depend.\n* Fixed pruning in CustomScan when the subplan is not a Scan type that caused a crash with LATERALs.\n* Corrected size reporting that was not accurately counting TOAST size\n* Updated error messages that more closely conform to PG style.\n* Corrected handling of table and schema name changes to chunks; TimescaleDB metadata catalogs are now properly updated\n\n**Notable commits**\n* [8b58500] Fix bug where dropping triggers caused dangling references in pg_depend, disallow disabling triggers on hypertables\n* [745b8ab] Fixing CustomScan pruning whenever the subplan is NOT of a Scan type.\n* [67a8a41] Make chunk identifiers formatting safe using format\n* [41af6ff] Fix misreported toast_size in chunk_relation_size funcs\n* [4f2f1a6] Update the error messages to conform with the style guide; Fix tests\n* [3c28f65] Release cache pin memory\n* [abe76fc] Add support for changing chunk schema and name\n\n**Thanks**\n* @mfuterko for updating our error messages to conform with PG error message style\n* @fvannee for reporting a crash when using certain LATERAL joins with aggregates\n* @linba708 for reporting a memory leak with long lived connections\n* @phlsmk for reporting an issue where dropping triggers prevented drop_chunks from working due to orphaned dependencies\n\n\n## 0.10.0 (2018-06-27)\n\n**High-level changes**\n* Planning time improvement (**up to 15x**) when a hypertable has many chunks by only expanding (and taking locks on) chunks that will actually be used in a query, rather than on all chunks (as was the default PostgreSQL behavior).\n* Smarter use of HashAggregate by teaching the planner to better estimate the number of output rows when using time-based grouping.\n* New convenience function for getting the approximate number of rows in a hypertable (`hypertable_approximate_row_count`).\n* Fixed support for installing extension into non-`public` schemas\n* Other bug fixes and refactorings.\n\n**Notable commits**\n* [12bc117] Fix static analyzer warning when checking for index attributes\n* [7d9f49b] Fix missing NULL check when creating default indexes\n* [2e1f3b9] Improve memory allocation during cache lookups\n* [ca6e5ef] Fix upserts on altered tables.\n* [2de6b02] Add optimization to use HashAggregate more often\n* [4b4211f] Fix some external functions when setting a custom schema\n* [b7257fc] Optimize planning times when hypertables have many chunks\n* [c660fcd] Add hypertable_approximate_row_count convenience function\n* [9ce1576] Fix a compilation issue on pre 9.6.3 versions\n\n**Thanks**\n* @viragkothari for suggesting the addition of `hypertable_approximate_row_count` and @fvannee for providing the initial SQL used to build that function\n* 'mintekhab' from Slack for reporting a segfault when using upserts on an altered table\n* @mmouterde for reporting an issue where the extension implicitly expected to be installed in the `public` schema\n* @mfuterko for bringing some potential bugs to our attention via static analysis\n\n## 0.9.2 (2018-05-04)\n\n**High-level changes**\n* Fixed handling of `DISCARD ALL` command when parallel workers are involved, which sometimes caused the extension to complain it was not preloaded\n* User permission bug fix where users locating TRIGGER permissions in a database could not insert data into a hypertable\n* Fixes for some issues with 32-bit architectures\n\n**Notable commits**\n* [256b394] Fix parsing of GrantRoleStmt\n* [b78953b] Fix datum conversion typo\n* [c7283ef] Fix bug with extension loader when DISCARD ALL is executed\n* [fe20e48] Fix chunk creation with user that lacks TRIGGER permission\n\n**Thanks**\n* @gumshoes, @manigandham, @wallies, & @cjrh for reporting a problem where sometimes the extension would appear to not be preloaded when it actually was\n* @thaxy for reporting a permissions issue when user creating a hypertable lacks TRIGGER permission\n* @bertmelis for reporting some bugs with 32-bit architectures\n\n## 0.9.1 (2018-03-26)\n\n**High-level changes**\n* **For this release only**, you will need to restart the database before\nrunning `ALTER EXTENSION`\n* Several edge cases regarding CTEs addressed\n* Updated preloader with better error messaging and fixed edge case\n* ABI compatibility with latest PostgreSQL to help catch any breaking\nchanges\n\n**Notable commits**\n* [40ce037] Fix crash on explain analyze with insert cte\n* [8378beb] Enable hypertable inserts within CTEs\n* [bdfda75] Fix double-loading of extension\n* [01ea77e] Fix EXPLAIN output for ConstraintAwareAppend inside CTE\n* [fc05637] Add no preload error to versioned library.\n* [38f8e0c] Add ABI compatibility tests\n* [744ca09] Fix Cache Pinning for Subtxns\n* [39010db] Move more drops into event trigger system\n* [fc36699] Do not fail add_dimension() on non-empty table with 'if_not_exists'\n\n**Thanks**\n* @The-Alchemist for pointing out broken links in the README\n* @chaintng for pointing out a broken link in the docs\n* @jgranstrom for reporting a edge case crash with UPSERTs in CTEs\n* @saosebastiao for reporting the lack of an error message when the library is not preloaded and trying to delete/modify a hypertable\n* @jbylund for reporting a cache invalidation issue with the preloader\n\n## 0.9.0 (2018-03-05)\n\n**High-level changes**\n* Support for multiple extension versions on different databases in the\nsame PostgreSQL instance. This allows different databases to be updated\nindependently and provides for smoother updates between versions. No\nmore spurious errors in the log as the extension is being\nupdated, and new versions no longer require a restart of the database.\n* Streamlined update process for smaller binary/package sizes\n* Significant refactoring to simplify and improve codebase, including\nimprovements to error handling, security/permissions, and more\n* Corrections to edge-case scenarios involving dropping schemas,\nhypertables, dimensions, and more\n* Correctness improvements through propagating reloptions from main\ntable to chunk tables and blocking `ONLY` commands that try to alter\nhypertables (i.e., changes should be applied to chunks as well)\n* Addition of a `migrate_data` option to `create_hypertable` to allow\nnon-empty tables to be turned into hypertables without separate\ncreation & insertion steps. Note, this option may take a while if the\noriginal table has lots of data\n* Support for `ALTER TABLE RENAME CONSTRAINT`\n* Support for adjusting the number of partitions for a space dimension\n* Improvements to tablespace handling\n\n**Notable commits**\n* [4672719] Fix error in handling of RESET ALL\n* [9399308] Refactor/simplify update scripts and build process\n* [0e79df4] Fix handling of custom SQL-based partitioning functions\n* [f13969e] Fix possible memory safety issue and squash valgrind error.\n* [ef74491] Migrate table data when creating a hypertable\n* [2696582] Move index and constraints drop handling to event trigger\n* [d6baccb] Improve tablespace handling, including blocks for DROP and REVOKE\n* [b9a6f89] Handle DROP SCHEMA for hypertable and chunk schemas\n* [b534a5a] Add test case for adding metadata entries automatically\n* [6adce4c] Handle TRUNCATE without upcall and handle ONLY modifier\n* [71b1124] Delete orphaned dimension slices\n* [fa19a54] Handle deletes on metadata objects via native catalog API\n* [6e011d1] Refactor hypertable-related API functions\n* [5afd39a] Fix locking for serializing chunk creation\n* [6dd2c46] Add check for null in ca_append_rescan to prevent segfault\n* [71962b8] Refactor dimension-related API functions\n* [cc254a9] Fix CREATE EXTENSION IF NOT EXISTS and error messages\n* [d135256] Spread chunk indexes across tablespaces like chunks\n* [e85721a] Block ONLY hypertable on all ALTER TABLE commands.\n* [78d36b5] Handle subtxn for cache pinning\n* [26ef77f] Add subtxn abort logic to process_utility.c\n* [25f3284] Handle cache invalidation during subtxn rollback\n* [264956f] Block DROP NOT NULL on time-partitioned columns.\n* [ad7d361] Better accounting for number of items stored in a subspace\n* [12f92ea] Improve speed of out-of-order inserts\n* [87f055d] Add support for ALTER TABLE RENAME CONSTRAINT.\n* [da8cc79] Add support for multiple extension version in one pg instance\n* [68faddc] Make chunks inherit reloptions set on the hypertable\n* [4df8f28] Add proper permissions handling for associated (chunk) schemas\n* [21efcce] Refactor chunk table creation and unify constraint handling\n\n**Thanks**\n* @Anthares for a request to pass reloptions like fill factor to child chunks\n* @oldgreen for reporting an issue with subtransaction handling\n* @fvannee for a PR that fixed a bug with `ca_append_rescan`\n* @maksm90 for reporting an superfluous index being created in an internal catalog table\n* @Rashid987 for reporting an issue where deleting a chunk, then changing the time interval would not apply the change when a replacement chunk is created\n* RaedA from Slack for reporting compilation issues on Windows between\n0.8.0 and this release\n* @haohello for a request to adjust the number of partitions for a given dimension\n* @LonghronShen and @devereaux for reporting an issue (and submitting a PR) for handling version identification when there is more to the version than just major and minor numbers\n* @carlospeon for reporting an issue with dropping hypertables\n* @gumshoes, @simpod, @jbylund, and @ryan-shaw for testing a pre-release version to verify our new update path works as expected\n* @gumshoes for reporting an issue with `RESET ALL`\n\n## 0.8.0 (2017-12-19)\n\n**High-level changes**\n* TimescaleDB now builds and runs on Windows! Now in addition to using\nDocker, users can choose to build the extension from source and install\non 64-bit Windows\n* Update functions `add_dimension` and `set_chunk_time_interval` to take `INTERVAL` types\n* Improved tablespace management including detaching tablespaces from hypertables and looking up tablespaces associated with a hypertable\n* Reduced memory usage for `INSERT`s with out-of-order data\n* Fixes inserts on 32-bit architectures, in particular ARM\n* Other correctness improvements including preventing attachment of\nPG10 partitions to hypertables, improved handling of space dimensions\nwith one partition, and correctly working with `pg_upgrade`\n* Test and build improvements making those both more robust and easier\nto do\n\n**Notable commits**\n* [26971d2] Make `tablespace_show` function return Name instead of CString\n* [2fe447b] Make TimescaleDB work with pg_upgrade\n* [90c7a6f] Fix logic for one space partition\n* [6cfdd79] Prevent native partitioning attachment of hypertables\n* [438d79d] Fix trigger relcache handling for COPY\n* [cc1ad95] Reduce memory usage for out-of-order inserts\n* [a0f62c5] Improve bootstrap script's robustness\n* [00a096f] Modify tests to make more platform agnostic\n* [0e76b5f] Do not add tablespaces to hypertable objects\n* [176b75e] Add command to show tablespaces attached to a hypertable\n* [6e92383] Add function to detach tablespaces from hypertables\n* [e593876] Refactor tablespace handling\n* [c4a46ac] Add hypertable cache lookup on ID/pkey\n* [f38a578] Fix handling of long constraint names\n* [20c9b28] Unconditionally add pg_config --includedir to src build\n* [12dff61] Fixes insert for 32bit architecture\n* [e44e47e] Update add_dimension to take INTERVAL times\n* [0763e62] Update set_chunk_time_interval to take INTERVAL times\n* [87c4b4f] Fix test generator to work for PG 10.1\n* [51854ac] Fix error message to reflect that drop_chunks can take a DATE interval\n* [66396fb] Add build support for Windows\n* [e1a0e81] Refactor and fix cache invalidation\n\n**Thanks**\n* @oldgreen for reporting an issue where `COPY` was warning of relcache reference leaks\n* @campeterson for pointing out some documentation typos\n* @jwdeitch for the PR to prevent attaching PG10 partitions to hypertables\n* @vjpr and @sztanpet for reporting bugs and suggesting improvements to the bootstrap script\n\n## 0.7.1 (2017-11-29)\n\n**High-level changes**\n* Fix to the migration script for those coming from 0.6.1 (or earlier)\n* Fix edge case in `drop_chunks` when hypertable uses `TIMESTAMP` type\n* Query planning improvements & fixes\n* Permission fixes and support `SET ROLE` functionality\n\n**Notable commits**\n* [717299f] Change time handling in drop_chunks for TIMESTAMP times\n* [d8ec285] Do not append-optimize plans with result relations (DELETE/UPDATE)\n* [30b72ec] Handle empty append plans in ConstraintAwareAppend\n* [b35509b] Permission fixes and allow SET ROLE\n\n**Thanks**\n* @shaneodonnell for reporting a bug with empty append plans in ConstraintAwareAppend\n* @ryan-shaw for reporting a bug with query plans involving result relations and reporting an issue with our 0.6.1 to 0.7.0 migration script\n\n\n## 0.7.0 (2017-11-21)\n\n**Please note: This update may take a long time (minutes, even hours) to\ncomplete, depending on the size of your database**\n\n**High-level changes**\n* **Initial PostgreSQL 10 support**. TimescaleDB now should work on both PostgreSQL 9.6 and 10. As this is our first release supporting PG10, we look forward to community feedback and testing. _Some release channels, like Ubuntu & RPM-based distros will remain on 9.6 for now_\n* Support for `CLUSTER` on hypertables to recursively apply to chunks\n* Improve constraint handling of edge cases for `DATE` and `TIMESTAMP`\n* Fix `range_start` and `range_end` to properly handle the full 32-bit int space\n* Allow users to specify their desired partitioning function\n* Enforce `NOT NULL` constraint on time columns\n* Add testing infrastructure to use Coverity and test PostgreSQL regression tests in TimescaleDB\n* Switch to the CMake build system for better cross-platform support\n* Several other bug fixes, cleanups, and improvements\n\n**Notable commits**\n* [13e1cb5] Add reindex function\n* [6594018] Handle when create_hypertable is invoked on partitioned table\n* [818bdbc] Add coverity testing\n* [5d0cbc1] Recurse CLUSTER command to chunks\n* [9c7191e] Change TIMESTAMP partitioning to be completely tz-independent\n* [741b256] Mark IMMUTABLE functions as PARALLEL SAFE\n* [2ffb30d] Make aggregate serialize and deserialize functions STRICT\n* [c552410] Add build target to run the standard PostgreSQL regression tests\n* [291050b] Change DATE partitioning to be completely tz-independent\n* [ca0968a] Make all partitioning functions take anyelement argument\n* [a4e1e32] Change range_start and range_end semantics\n* [2dfbc82] Fix off-by-one error on range-end\n* [500563f] Add support for PostgreSQL 10\n* [201a948] Check that time dimensions are set as NOT NULL.\n* [4532650] Allow setting partitioning function\n* [4a0a0d8] Fix column type change on plain tables\n* [cf009cc] Avoid string conversion in hash partitioning\n* [8151098] Improve update testing by adding a rerun test\n* [c420c11] Create a catalog entry for constraint-backed indexes\n* [ec746d1] Add ability to run regression test locally\n* [44f9fec] Add analyze to parallel test for stability\n* [9e0422a] Fix bug with pointer assignment after realloc\n* [114fa8d] Refactor functions used to recurse DDL commands to chunks\n* [b1ec4fa] Refactor build system to use CMake\n\n**Thanks**\n* @jgraichen for reporting an issue with `drop_chunks` not accepting `BIGINT`\n* @nathansgreen for reporting an edge case with constraints for `TIMESTAMP`\n* @jonmd for reporting a similar edge case for `DATE`\n* @jwdeitch for a PR to cover an error case in PG10\n\n\n## 0.6.1 (2017-11-07)\n\n**High-level changes**\n\n* Fix several memory bugs that caused segfaults\n* Fix bug when creating expression indexes\n* Plug a memory leak with constraint expressions\n* Several other bug fixes and stability improvements\n\n**Notable commits**\n* [2799075] Fix EXPLAIN for ConstraintAware and MergeAppend\n* [8084594] Use per-chunk memory context for cached chunks\n* [a13d9de] Do not convert tuples on insert unless needed\n* [da09f24] Limit growth of range table during chunk inserts\n* [85dee79] Fix issue with creating expression indexes\n* [844ff7f] Fix memory leak due to constraint expressions.\n* [e90d3ee] Consider precvious CIS state in copy FROM file to rel\n* [56d632f] Fix bug with pointer assignment after realloc\n* [f97d624] Make event trigger creation idempotent\n\n**Thanks**\n* @jwdeitch for submitting a patch to correct behavior in the COPY operation\n* @jgraichen for reporting a bug with expression indexes\n* @zixet for reporting a memory leak\n* @djk447 for reporting a bug in EXPLAIN with ConstraintAware and MergeAppend\n\n## 0.6.0 (2017-10-12)\n\n**High-level changes**\n\n* Fix bugs where hypertable-specific handlers were affecting normal Postgres tables.\n* Make it so that all TimescaleDB commands can run as a normal user rather than a superuser.\n* Updates to the code to make the extension compileable on Windows; future changes will add steps to properly build.\n* Move `time_bucket` functions out of `public` schema (put in schema where extension is).\n* Several other bugs fixes.\n\n**Notable commits**\n* [1d73fb8] Fix bug with extension starting too early.\n* [fd390ec] Fix chunk index attribute mismatch and locking issue\n* [430ed8a] Fix bug with collected commands in index statement.\n* [614c2b7] Fix permissions bugs and run tests as normal user\n* [ce12104] Fix \"ON CONFLICT ON CONSTRAINT\" on plain PostgreSQL tables\n* [4c451e0] Fix rename and reindex bugs when objects are not relations\n* [c3ebc67] Fix permission problems with dropping hypertables and chunks\n* [040e815] Remove truncate and hypertable metadata triggers\n* [5c26328] Fix INSERT on hypertables using sub-selects with aggregates\n* [b57e2bf] Prepare C code for compiling on Windows\n* [a2bad2b] Fix constraint validation on regular tables\n* [fb5717f] Remove explicit schema for time_bucket\n* [04d01ce] Split DDL processing into start and end hooks\n\n**Thanks**\n* @oldgreen for reporting `time_bucket` being incorrectly put in the `public` schema and pointing out permission problems\n* @qlandman for reporting a bug with INSERT using sub-selects with aggregates\n* @min-mwei for reporting a deadlock issue during INSERTs\n* @ryan-shaw for reporting a bug where the extension sometimes used `pg_cache` too soon\n\n## 0.5.0 (2017-09-20)\n\n**High-level changes**\n* Improved support for primary-key, foreign-key, unique, and exclusion constraints.\n* New histogram function added for getting the frequency of a column's values.\n* Add support for using `DATE` as partition column.\n* `chunk_time_interval` now supports `INTERVAL` data types\n* Block several unsupported and/or dangerous operations on hypertables and chunks, including dropping or otherwise altering a chunk directly.\n* Several bug fixes throughout the code.\n\n**Notable commits**\n* [afcb0b1] Fix NULL handling in first/last functions.\n* [d53c705] Add script to dump meta data that can be useful for debugging.\n* [aa904fa] Block adding constraints without a constraint name\n* [a13039f] Fix dump and restore for tables with triggers and constraints\n* [8cf8d3c] Improve the size utils functions.\n* [2767548] Block adding constraints using an existing index\n* [5cee104] Allow chunk_time_interval to be specified as an INTERVAL type\n* [6232f98] Add histogram function.\n* [2380033] Block ALTER TABLE and handle DROP TABLE on chunks\n* [72d6681] Move security checks for ALTER TABLE ALTER COLUMN to C\n* [19d3d89] Handle changing the type of dimension columns correctly.\n* [17c4ba9] Handle ALTER TABLE rename column\n* [66932cf] Forbid relocating extension after install.\n* [d2561cc] Add ability to partition by a date type\n* [48e0a61] Remove triggers from chunk and chunk_constraint\n* [4dcbe61] Add support for hypertable constraints\n\n**Thanks**\n* @raycheung for reporting a segfault in `first`/`last`\n* @meotimdihia, @noyez, and @andrew-blake for reporting issues with `UNQIUE` and other types of constraints\n\n\n## 0.4.2 (2017-09-06)\n\n**High-level changes**\n* Provide scripts for backing up and restoring single hypertables\n\n**Notable commits**\n* [683c078] Add backup/restore scripts for single hypertables\n\n## 0.4.1 (2017-09-04)\n\n**High-level changes**\n* Bug fix for a segmentation fault in the planner\n* Shortcut when constraint-aware append excludes all chunks\n* Fix edge case with negative timestamps when points fell right on the boundary\n* Fix behavior of `time_bucket` for `DATE` types by not converting to `TIMESTAMPTZ`\n* Make the output of `chunk_relation_size` consistent\n\n**Notable commits**\n* [50c8c4c] Fix possible segfault in planner\n* [e49e45c] Fix failure when constraint-aware append excludes all chunks\n* [c3b6fb9] Fix bug with negative dimension values\n* [3c69e4f] Fix semantics of time_bucket on DATE input\n* [0137c92] Fix output order of chunk dimensions and ranges in chunk_relation_size.\n* [645b530] Convert inserted tuples to the chunk's rowtype\n\n**Thanks**\n* @yadid for reporting a segfault (fixed in 50c8c4c)\n* @ryan-shaw for reporting tuples not being correctly converted to a chunk's rowtype (fixed in 645b530)\n* @yuezhihan for reporting GROUP BY error when setting compress_segmentby with an enum column\n\n## 0.4.0 (2017-08-21)\n\n**High-level changes**\n* Exclude chunks when constraints can be constifyed even if they are\nconsidered mutable like `NOW()`.\n* Support for negative values in the dimension range which allows for pre-1970 dates.\n* Improve handling of default chunk times for integral date times by forcing it to be explicit rather than guessing the units of the time.\n* Improve memory usage for long-running `COPY` operations (previously it would grow unbounded).\n* `VACUUM` and `REINDEX` on hypertables now recurse down to chunks.\n\n**Notable commits**\n* [139fe34] Implement constraint-aware appends to exclude chunks at execution time\n* [2a51cf0] Add support for negative values in dimension range\n* [f2d5c3f] Error if add_dimension given both partition number and interval length\n* [f3df02d] Improve handling of non-TIMESTAMP/TZ timestamps\n* [6a5a7eb] Reduce memory usage on long-running COPY operations\n* [953346c] Make VACUUM and REINDEX recurse to chunks\n* [55bfdf7] Release all cache pins when a transaction ends\n\n## 0.3.0 (2017-07-31)\n\n**High-level changes**\n* \"Upserts\" are now supported via normal `ON CONFLICT DO UPDATE`/`ON CONFLICT DO NOTHING` syntax. However, `ON CONFLICT ON CONSTRAINT` is not yet supported.\n* Improved support for user-defined triggers on hypertables. Now handles both INSERT BEFORE and INSERT AFTER triggers, and triggers can be named arbitrarily (before, a \\_0\\_ prefix was required to ensure correct execution priority).\n* `TRUNCATE` on a hypertable now deletes empty chunks.\n\n**Notable commits**\n* [23f9d3c] Add support for upserts (`ON CONFLICT DO UPDATE`)\n* [1f3dcd8] Make `INSERT`s use a custom plan instead of triggers\n* [f23bf58] Remove empty chunks on `TRUNCATE` hypertable.\n\n## 0.2.0 (2017-07-12)\n\n**High-level changes**\n* Users can now define their own triggers on hypertables (except for `INSERT AFTER`)\n* Hypertables can now be renamed or moved to a different schema\n* Utility functions added so you can examine the size hypertables, chunks, and indices\n\n**Notable commits**\n* [83c75fd] Add support for triggers on hypertables for all triggers except `INSERT AFTER`\n* [e0eeeb9] Add hypertable, chunk, and indexes size utils functions.\n* [4d2a65d] Add infrastructure to build update script files.\n* [a5725d9] Add support to rename and change schema on hypertable.\n* [142f58c] Cleanup planner and process utility hooks\n\n## 0.1.0 (2017-06-28)\n\n**IMPORTANT NOTE**\n\nStarting with this release, TimescaleDB will now\nsupport upgrading between extension versions using the typical\n`ALTER EXTENSION` command, unless otherwise noted in future release notes. This\nimportant step should make it easier to test TimescaleDB and be able\nto get the latest benefits from new versions of TimescaleDB. If you\nwere previously using a version with the `-beta` tag, you will need\nto `DROP` any databases currently using TimescaleDB and re-create them\nin order to upgrade to this new version. To backup and migrate data,\nuse `pg_dump` to save the table schemas and `COPY` to write hypertable\ndata to CSV for re-importing after upgrading is complete. We describe\na similar process on [our docs](http://docs.timescale.com/getting-started/setup/migrate-from-postgresql#different-db).\n\n**High-level changes**\n* More refactoring to stabilize and cleanup the code base for supporting upgrades (see above note)\n* Correct handling of ownership and permission propagation for hypertables\n* Multiple bug fixes\n\n**Notable commits**\n* [696cc4c] Provide API for adding hypertable dimensions\n* [97681c2] Fixes permission handling\n* [aca7f32] Fix extension drop handling\n* [9b8a447] Limit the SubspaceStore size; Add documentation.\n* [14ac892] Fix possible segfault\n* [0f4169c] Fix check constraint on dimension table\n* [71c5e78] Fix and refactor tablespace support\n* [5452dc5] Fix partiton functions; bug fixes (including memory)\n* [e75cd7e] Finer grained memory management\n* [3c460f0] Fix partitioning, memory, and tests\n* [fe51d8d] Add native scan for the chunk table\n* [fc68baa] Separate out subspace_store and add it to the hypertable object as well\n* [c8124b8] Use hypercube instead of dimension slice list\n* [f5d7786] Change the semantics of range_end to be exclusive\n* [700c9c8] Refactor insert path in C.\n* [0584c47] Created chunk_get_or_create in sql with an SPI connector in C\n* [7b8de0c] Refactor catalog for new schema and add native data types\n* [d3bdcba] Start refactoring to support any number of partitioning dimensions\n\n## 0.0.12-beta (2017-06-21)\n\n**High-level changes**\n* A major cleanup and refactoring was done to remove legacy code and\ncurrently unused code paths. This change is **backwards incompatible**\nand will require a database to be re-initialized and data re-imported.\nThis refactoring will allow us to provide upgrade paths starting with\nthe next release.\n* `COPY` and `INSERT` commands now return the correct number of rows\n* Default indexes no longer duplicate existing indexes\n* Cleanup of the Docker image and build process\n* Chunks are now time-aligned across partitions\n\n**Notable commits**\n* [3192c8a] Remove Dockerfile and docker.mk\n* [2a01ebc] Ensure that chunks are aligned.\n* [73622bf] Fix default index creation duplication of indexes\n* [c8872fe] Fix command-tag for COPY and INSERT\n* [bfe58b6] Refactor towards supporting version upgrades\n* [db01c84] Make time-bucket function parallel safe\n* [18db11c] Fix timestamp test\n* [97bbb59] Make constraint exclusion work with non-text partition keys\n* [f2b42eb] Fix problems with partitioning logic for padded fields\n* [997029a] if_not_exist flag to create_hypertable now works on hypertables with data as well\n* [347a8bd] Reference the correct column when scanning partition epochs\n* [88a9849] Fix bug with timescaledb.allow_install_without_preload GUC not working\n\n## 0.0.11-beta (2017-05-24)\n\n**High-level changes**\n* New `first(value, time)` and `last(value, time)` aggregates\n* Remove `setup_timescaledb()` function to streamline setup\n* Allow for use cases where restarting the server is not feasible by force loading the library\n* Disable time series optimizations on non-hypertables\n* Add some default indexes for hypertables if they do not exist\n* Add \"if not exists\" flag for `create_hypertable`\n* Several bug fixes and cleanups\n\n**Notable commits**\n* [8ccc8cc] Add if_not_exists flag to create_hypertable()\n* [2bc60c7] Fix time interval field name in hypertable cache entry\n* [4638688] Improve GUC handling\n* [cedcafc] Remove setup_timescaledb() and fix pg_dump/pg_restore.\n* [34ad9a0] Add error when timescaledb library is not preloaded.\n* [fc4ddd6] Fix bug with dropping chunks on tables with indexes\n* [32215ff] Add default indexes for hypertables\n* [b2900f9] Disable query optimization on regular tables (non-hypertables)\n* [f227db4] Fixes command tag return for COPY on hypertables.\n* [eb32081] Fix Invalid database ID error\n* [662be94] Add the first(value, time),last(value, time) aggregates\n* [384a8fb] Add regression tests for deleted unit tests\n* [31ee92a] Remove unit tests and sql/setup\n* [13d3acb] Fix bug with alter table add/drop column if exists\n* [f960c24] Fix bug with querying a row as a composite type\n\n## 0.0.10-beta (2017-05-04)\n\n**High-level changes**\n* New `time_bucket` functions for doing roll-ups on varied intervals\n* Change default partition function (thanks @robin900)\n* Variety of bug fixes\n\n**Notable commits**\n* [1c4868d] Add documentation for chunk_time_interval argument\n* [55fd2f2] Fixes command tag return for `INSERT`s on hypertables.\n* [c3f930f] Add `time_bucket` functions\n* [b128ac2] Fix bug with `INSERT INTO...SELECT`\n* [e20edf8] Add better error checking for index creation.\n* [72f754a] use PostgreSQL's own `hash_any` function as default partfunc (thanks @robin900)\n* [39f4c0f] Remove sample data instructions and point to docs site\n* [9015314] Revised the `get_general_index_definition` function to handle cases where indexes have definitions other than just `CREATE INDEX` (thanks @bricklen)\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.15)\n\nlist(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)\n\ninclude(CheckCCompilerFlag)\ninclude(CheckSymbolExists)\ninclude(GitCommands)\ninclude(GenerateScripts)\ninclude(CMakeDependentOption)\n\noption(APACHE_ONLY \"only compile apache code\" off)\n# This requires all tests to run. This defaults to OFF but can be enabled to\n# ensure that no tests are skipped because of missing tools.\noption(REQUIRE_ALL_TESTS \"Require all tests to run.\" OFF)\noption(USE_OPENSSL \"Enable use of OpenSSL if available\" ON)\noption(SEND_TELEMETRY_DEFAULT \"The default value for whether to send telemetry\"\n       ON)\noption(\n  USE_TELEMETRY\n  \"Include telemetry functionality in the build. Disabling will exclude all telemetry code from the build.\"\n  ON)\n\noption(REGRESS_CHECKS \"PostgreSQL regress checks through installcheck\" ON)\noption(\n  ENABLE_OPTIMIZER_DEBUG\n  \"Enable OPTIMIZER_DEBUG when building. Requires Postgres server to be built with OPTIMIZER_DEBUG.\"\n  OFF)\noption(ENABLE_DEBUG_UTILS \"Enable debug utilities for the extension.\" ON)\n\n# Option to enable assertions. Note that if we include headers from a PostgreSQL\n# build that has assertions enabled, we might inherit that setting without\n# explicitly enabling assertions via the ASSERTIONS option defined here. Thus,\n# this option is mostly useful to enable assertions when the PostgreSQL we\n# compile against has it disabled.\noption(ASSERTIONS \"Compile with assertion checks (default OFF)\" OFF)\n\n# Function to call pg_config and extract values.\nfunction(GET_PG_CONFIG var)\n  set(_temp)\n\n  # Only call pg_config if the variable didn't already have a value.\n  if(NOT ${var})\n    execute_process(\n      COMMAND ${CMAKE_COMMAND} -E env LC_MESSAGES=C ${PG_CONFIG} ${ARGN}\n      OUTPUT_VARIABLE _temp\n      OUTPUT_STRIP_TRAILING_WHITESPACE)\n  endif()\n\n  # On Windows, fields that are not recorded will be given the value \"not\n  # recorded\", so we translate this into <var>-NOTFOUND to make it undefined.\n  #\n  # It will then also show as, e.g., \"PG_LDFLAGS-NOTFOUND\" in any string\n  # interpolation, making it obvious that it is an undefined CMake variable.\n  if(\"${_temp}\" STREQUAL \"not recorded\")\n    set(_temp ${var}-NOTFOUND)\n  endif()\n\n  set(${var}\n      ${_temp}\n      PARENT_SCOPE)\nendfunction()\n\n# Search paths for Postgres binaries\nif(WIN32)\n  find_path(\n    PG_PATH postgres.exe\n    PATHS \"C:/PostgreSQL\" \"C:/Program Files/PostgreSQL\"\n    PATH_SUFFIXES bin 15/bin 16/bin\n    DOC \"The path to a PostgreSQL installation\")\nelseif(UNIX)\n  find_path(\n    PG_PATH postgres\n    PATHS $ENV{HOME} /opt/local/pgsql /usr/local/pgsql /usr/lib/postgresql\n    PATH_SUFFIXES bin 15/bin 16/bin\n    DOC \"The path to a PostgreSQL installation\")\nendif()\n\nfind_program(\n  PG_CONFIG pg_config\n  HINTS ${PG_PATH}\n  PATH_SUFFIXES bin\n  DOC \"The path to the pg_config of the PostgreSQL version to compile against\")\n\nif(NOT PG_CONFIG)\n  message(FATAL_ERROR \"Unable to find 'pg_config'\")\nendif()\n\nconfigure_file(\"version.config\" \"version.config\" COPYONLY)\nfile(READ version.config VERSION_CONFIG)\n\nif(VERSION_CONFIG\n   MATCHES\n   \"(^|.*[^a-z])version[\\t ]*=[\\t ]*([0-9]+\\\\.[0-9]+\\\\.*[0-9]*)(-([a-z]+[0-9]*|dev))?.*\"\n)\n  set(VERSION ${CMAKE_MATCH_2})\n  set(VERSION_MOD ${CMAKE_MATCH_4}) # This is used in config.h\n  if(CMAKE_MATCH_3)\n    set(PROJECT_VERSION_MOD ${CMAKE_MATCH_2}${CMAKE_MATCH_3})\n  else()\n    set(PROJECT_VERSION_MOD ${CMAKE_MATCH_2})\n  endif()\nendif()\n\nif(VERSION_CONFIG MATCHES\n   \".*previous_version[\\t ]*=[\\t ]*([0-9]+\\\\.[0-9]+\\\\.[0-9]+(-[a-z]+[0-9]*)?).*\"\n)\n  set(PREVIOUS_VERSION ${CMAKE_MATCH_1})\nelse()\n  message(\n    FATAL_ERROR \"Could not determine previous version from version.config\")\nendif()\n\n# a hack to avoid change of SQL extschema variable\nset(extschema \"@extschema@\")\n\n# If not explicitly specified, try to use the same compiler as Postgres to avoid\n# mismatches and simplify configuration.\nif((NOT DEFINED CMAKE_C_COMPILER) AND (NOT DEFINED ENV{CC}))\n  get_pg_config(PG_CC --cc)\n\n  # The first word might be ccache or similar, so handle this.\n  string(REPLACE \" \" \";\" PG_CC_LIST ${PG_CC})\n  list(POP_FRONT PG_CC_LIST PG_CC_FIRST)\n  if(PG_CC_LIST)\n    # Got multi-word PG CC, treat the first word as ccache.\n    find_program(PG_CCACHE_FOUND ${PG_CC_FIRST})\n    if((NOT DEFINED CMAKE_C_COMPILER_LAUNCHER) AND PG_CCACHE_FOUND)\n      message(\n        STATUS\n          \"Using ${PG_CCACHE_FOUND} as C compiler launcher based on pg_config CC\"\n      )\n      set(CMAKE_C_COMPILER_LAUNCHER ${PG_CCACHE_FOUND})\n    endif()\n    set(PG_CC ${PG_CC_LIST})\n  else()\n    # Single-word CC.\n    set(PG_CC ${PG_CC_FIRST})\n  endif()\n\n  find_program(PG_CC_FOUND ${PG_CC})\n  if(PG_CC_FOUND)\n    message(STATUS \"Using ${PG_CC_FOUND} as CC based on pg_config CC\")\n    set(CMAKE_C_COMPILER ${PG_CC_FOUND})\n  endif()\nendif()\n\n# Set project name, version, and language. Language needs to be set for compiler\n# checks\nproject(\n  timescaledb\n  VERSION ${VERSION}\n  LANGUAGES C)\n\nif(NOT CMAKE_BUILD_TYPE)\n  # Default to Release builds\n  set(CMAKE_BUILD_TYPE\n      Release\n      CACHE\n        STRING\n        \"Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel\"\n        FORCE)\nendif()\n\nset(SUPPORTED_BUILD_TYPES Debug Release RelWithDebInfo MinSizeRel)\nif(NOT CMAKE_BUILD_TYPE IN_LIST SUPPORTED_BUILD_TYPES)\n  message(\n    FATAL_ERROR \"Bad CMAKE_BUILD_TYPE. Expected one of ${SUPPORTED_BUILD_TYPES}\"\n  )\nendif()\n\nmessage(\n  STATUS\n    \"TimescaleDB version ${PROJECT_VERSION_MOD}. Can be updated from version ${PREVIOUS_VERSION}\"\n)\nmessage(STATUS \"Build type is ${CMAKE_BUILD_TYPE}\")\n\nset(PROJECT_INSTALL_METHOD\n    source\n    CACHE STRING \"Specify what install platform this binary\nis built for\")\nmessage(STATUS \"Install method is '${PROJECT_INSTALL_METHOD}'\")\n\n# Build compilation database by default\nset(CMAKE_EXPORT_COMPILE_COMMANDS ON)\n\n# Code coverage is optional and OFF by default\noption(CODECOVERAGE \"Enable code coverage for the build\" OFF)\noption(EXPERIMENTAL \"Skip postgres version compatibility check\" OFF)\n\n# Generate downgrade script\noption(GENERATE_DOWNGRADE_SCRIPT\n       \"Generate downgrade script. Defaults to not generate a downgrade script.\"\n       OFF)\n\nif(CMAKE_BUILD_TYPE MATCHES Debug)\n  # CMAKE_BUILD_TYPE is set at CMake configuration type. But usage of\n  # CMAKE_C_FLAGS_DEBUG is determined at build time by running cmake --build .\n  # --config Debug (at least on Windows). Therefore, we only set these flags if\n  # the configuration-time CMAKE_BUILD_TYPE is set to Debug. Then Debug enabled\n  # builds will only happen on Windows if both the configuration- and build-time\n  # settings are Debug.\n  set(CMAKE_C_FLAGS_DEBUG \"${CMAKE_C_FLAGS_DEBUG} -DDEBUG=1 -DTS_DEBUG=1\")\nendif(CMAKE_BUILD_TYPE MATCHES Debug)\n\nset(SUPPORTED_COMPILERS \"GNU\" \"Clang\" \"AppleClang\" \"MSVC\")\n\n# Check for a supported compiler\nif(NOT CMAKE_C_COMPILER_ID IN_LIST SUPPORTED_COMPILERS)\n  message(\n    FATAL_ERROR\n      \"Unsupported compiler ${CMAKE_C_COMPILER_ID}. Supported compilers are: ${SUPPORTED_COMPILERS}\"\n  )\nendif()\n\n# Option to treat warnings as errors when compiling (default on for debug\n# builds, off for all other build types)\nif(CMAKE_BUILD_TYPE STREQUAL Debug)\n  option(WARNINGS_AS_ERRORS \"Make compiler warnings into errors (default ON)\"\n         ON)\nelse()\n  option(WARNINGS_AS_ERRORS \"Make compiler warnings into errors (default ON)\"\n         OFF)\nendif()\n\nif(WARNINGS_AS_ERRORS)\n  if(CMAKE_C_COMPILER_ID MATCHES \"GNU|Clang|AppleClang\")\n    add_compile_options(-Werror)\n  elseif(CMAKE_C_COMPILER_ID MATCHES \"MSVC\")\n    add_compile_options(/WX)\n  endif()\nendif(WARNINGS_AS_ERRORS)\n\nif(CMAKE_C_COMPILER_ID MATCHES \"GNU|AppleClang|Clang\")\n  # These two flags generate too many errors currently, but we probably want\n  # these optimizations enabled.\n  #\n  # -fdelete-null-pointer-checks -Wnull-dereference\n\n  # This flag avoid some subtle bugs related to standard conversions, but\n  # currently does not compile because we are using too many implicit\n  # conversions that potentially lose precision.\n  #\n  # -Wconversions\n\n  # These flags are supported on all compilers.\n  add_compile_options(\n    -Wempty-body\n    -Wvla\n    -Wall\n    -Wextra\n    # The SQL function arguments macro PG_FUNCTION_ARGS often inroduces unused\n    # arguments.\n    -Wno-unused-parameter\n    -Wundef\n    -Wmissing-prototypes\n    -Wpointer-arith\n    -Werror=vla\n    -Wendif-labels\n    -fno-strict-aliasing\n    -fno-omit-frame-pointer)\n\n  # These flags are just supported on some of the compilers, so we check them\n  # before adding them.\n  check_c_compiler_flag(-Wno-unused-command-line-argument\n                        CC_SUPPORTS_NO_UNUSED_CLI_ARG)\n  if(CC_SUPPORTS_NO_UNUSED_CLI_ARG)\n    add_compile_options(-Wno-unused-command-line-argument)\n  endif()\n\n  check_c_compiler_flag(-Wno-format-truncation CC_SUPPORTS_NO_FORMAT_TRUNCATION)\n  if(CC_SUPPORTS_NO_FORMAT_TRUNCATION)\n    add_compile_options(-Wno-format-truncation)\n  else()\n    message(STATUS \"Compiler does not support -Wno-format-truncation\")\n  endif()\n\n  check_c_compiler_flag(-Wstringop-truncation CC_STRINGOP_TRUNCATION)\n  if(CC_STRINGOP_TRUNCATION)\n    add_compile_options(-Wno-stringop-truncation)\n  else()\n    message(STATUS \"Compiler does not support -Wno-stringop-truncation\")\n  endif()\n\n  check_c_compiler_flag(-Wimplicit-fallthrough CC_SUPPORTS_IMPLICIT_FALLTHROUGH)\n  if(CC_SUPPORTS_IMPLICIT_FALLTHROUGH)\n    add_compile_options(-Wimplicit-fallthrough)\n  else()\n    message(STATUS \"Compiler does not support -Wimplicit-fallthrough\")\n  endif()\n\n  check_c_compiler_flag(-Wnewline-eof CC_SUPPORTS_NEWLINE_EOF)\n  if(CC_SUPPORTS_NEWLINE_EOF)\n    add_compile_options(-Wnewline-eof)\n  endif()\n\n  # strict overflow check produces false positives on gcc < 8\n  if(CMAKE_COMPILER_IS_GNUCC AND CMAKE_C_COMPILER_VERSION VERSION_LESS 8)\n    add_compile_options(-Wno-strict-overflow)\n  endif()\n\n  # -Wclobbered produces false positives on gcc < 9\n  if(CMAKE_COMPILER_IS_GNUCC AND CMAKE_C_COMPILER_VERSION VERSION_LESS 9)\n    add_compile_options(-Wno-clobbered)\n  endif()\n\n  if(CMAKE_COMPILER_IS_GNUCC)\n    add_compile_options(\n      # Seems to be broken in GCC 11 with designated initializers.\n      -Wno-missing-field-initializers)\n  endif()\n\n  # On UNIX, the compiler needs to support -fvisibility=hidden to hide symbols\n  # by default\n  check_c_compiler_flag(-fvisibility=hidden CC_SUPPORTS_VISIBILITY_HIDDEN)\n\n  if(NOT CC_SUPPORTS_VISIBILITY_HIDDEN)\n    message(\n      FATAL_ERROR\n        \"The compiler ${CMAKE_C_COMPILER_ID} does not support -fvisibility=hidden\"\n    )\n  endif(NOT CC_SUPPORTS_VISIBILITY_HIDDEN)\nendif()\n\n# On Windows, default to only include Release builds so MSBuild.exe 'just works'\nif(WIN32 AND NOT CMAKE_CONFIGURATION_TYPES)\n  set(CMAKE_CONFIGURATION_TYPES\n      Release\n      CACHE\n        STRING\n        \"Semicolon separated list of supported configuration types, only supports Debug, Release, MinSizeRel, and RelWithDebInfo, anything else will be ignored.\"\n        FORCE)\nendif()\n\nmessage(STATUS \"Using compiler ${CMAKE_C_COMPILER_ID}\")\n\nif(ENABLE_OPTIMIZER_DEBUG)\n  message(\n    STATUS\n      \"Enabling OPTIMIZER_DEBUG. Make sure that ${PG_SOURCE_DIR} is installed and built with OPTIMIZER_DEBUG.\"\n  )\n  add_definitions(-DOPTIMIZER_DEBUG)\nendif()\n\nfind_package(Git)\n\n# Check PostgreSQL version\nexecute_process(\n  COMMAND ${PG_CONFIG} --version\n  OUTPUT_VARIABLE PG_VERSION_STRING\n  OUTPUT_STRIP_TRAILING_WHITESPACE)\n\nif(NOT ${PG_VERSION_STRING} MATCHES\n   \"^PostgreSQL[ ]+([0-9]+)(\\\\.([0-9]+)|beta|devel|rc[0-9]+)\")\n  message(FATAL_ERROR \"Could not parse PostgreSQL version ${PG_VERSION_STRING}\")\nendif()\n\nset(PG_VERSION_MAJOR ${CMAKE_MATCH_1})\nif(${CMAKE_MATCH_COUNT} GREATER \"2\")\n  set(PG_VERSION_MINOR ${CMAKE_MATCH_3})\nelse()\n  set(PG_VERSION_MINOR 0)\nendif()\nset(PG_VERSION \"${PG_VERSION_MAJOR}.${PG_VERSION_MINOR}\")\n\nmessage(\n  STATUS\n    \"Compiling against PostgreSQL version ${PG_VERSION} using pg_config '${PG_CONFIG}'\"\n)\n\n# Ensure that PostgreSQL version is supported and consistent with src/compat.h\n# version check\nif((${PG_VERSION_MAJOR} LESS \"15\")\n   OR (${PG_VERSION_MAJOR} GREATER \"18\")\n   AND NOT (${EXPERIMENTAL}))\n  message(FATAL_ERROR \"TimescaleDB only supports PostgreSQL 15, 16, 17 and 18\")\nendif()\n\n# Get PostgreSQL configuration from pg_config\nget_pg_config(PG_INCLUDEDIR --includedir)\nget_pg_config(PG_INCLUDEDIR_SERVER --includedir-server)\nget_pg_config(PG_LIBDIR --libdir)\nget_pg_config(PG_PKGLIBDIR --pkglibdir)\nget_pg_config(PG_SHAREDIR --sharedir)\nget_pg_config(PG_BINDIR --bindir)\nget_pg_config(PG_CFLAGS --cflags)\nget_pg_config(PG_CFLAGS_SL --cflags_sl)\nget_pg_config(PG_CPPFLAGS --cppflags)\nget_pg_config(PG_LDFLAGS --ldflags)\nget_pg_config(PG_LIBS --libs)\n\nseparate_arguments(PG_CFLAGS)\nforeach(option ${PG_CFLAGS})\n  if(NOT ${option} MATCHES ^-W)\n    set(filtered \"${filtered} ${option}\")\n  endif()\nendforeach()\nset(PG_CFLAGS \"${filtered} ${PG_CFLAGS_SL}\")\n\nfind_path(\n  PG_SOURCE_DIR src/include/pg_config.h.in\n  HINTS $ENV{HOME} $ENV{HOME}/projects $ENV{HOME}/Projects\n        $ENV{HOME}/development $ENV{HOME}/Development $ENV{HOME}/workspace\n  PATH_SUFFIXES postgres postgresql pgsql\n  DOC \"The path to the PostgreSQL source tree\")\n\noption(PG_SOURCE_INCLUDES \"Add PG source to include directories\" OFF)\n\nif(PG_SOURCE_DIR)\n  message(STATUS \"Found PostgreSQL source in ${PG_SOURCE_DIR}\")\n  if(PG_SOURCE_INCLUDES)\n    # Add the PostgreSQL source dir include directories to the build system\n    # includes BEFORE the installed PG include files. This will allow the LSP\n    # (e.g., clangd) to navigate to the PostgreSQL source instead of the install\n    # path directory that only has the headers.\n    include_directories(BEFORE SYSTEM ${PG_SOURCE_DIR}/src/include)\n  endif(PG_SOURCE_INCLUDES)\nendif(PG_SOURCE_DIR)\n\nset(EXT_CONTROL_FILE ${PROJECT_NAME}.control)\nconfigure_file(${EXT_CONTROL_FILE}.in ${EXT_CONTROL_FILE})\n\ninstall(FILES ${CMAKE_CURRENT_BINARY_DIR}/${EXT_CONTROL_FILE}\n        DESTINATION \"${PG_SHAREDIR}/extension\")\n\nfind_program(\n  CLANG_FORMAT\n  NAMES clang-format-17 clang-format\n  PATHS /usr/bin /usr/local/bin /usr/local/opt/ /usr/local/opt/llvm/bin /opt/bin\n  DOC \"The path to clang-format\")\n\nif(CLANG_FORMAT)\n  execute_process(\n    COMMAND ${CLANG_FORMAT} --version\n    OUTPUT_VARIABLE CLANG_FORMAT_VERSION_OUTPUT\n    OUTPUT_STRIP_TRAILING_WHITESPACE)\n\n  if(NOT ${CLANG_FORMAT_VERSION_OUTPUT} MATCHES\n     \"version[ ]+([0-9]+)\\\\.([0-9]+)(\\\\.([0-9]+))*\")\n    message(\n      FATAL_ERROR\n        \"Could not parse clang-format version ${CLANG_FORMAT_VERSION_OUTPUT}\")\n  endif()\n\n  if((${CMAKE_MATCH_1} LESS \"17\"))\n    message(WARNING \"clang-format version 17 or greater required\")\n    set(CLANG_FORMAT False)\n  endif()\nendif()\n\nif(CLANG_FORMAT)\n  message(STATUS \"Using local clang-format\")\n  add_custom_target(\n    clang-format COMMAND ${CMAKE_COMMAND} -E env CLANG_FORMAT=${CLANG_FORMAT}\n                         ${PROJECT_SOURCE_DIR}/scripts/clang_format_all.sh)\nendif()\n\nfind_program(\n  CMAKE_FORMAT\n  NAMES cmake-format\n  PATHS /usr/bin /usr/local/bin /usr/local/opt/ /usr/local/opt/llvm/bin /opt/bin\n  DOC \"The path to cmake-format\")\n\nif(CMAKE_FORMAT)\n  add_custom_target(\n    cmake-format COMMAND ${CMAKE_COMMAND} -E env CMAKE_FORMAT=${CMAKE_FORMAT}\n                         ${PROJECT_SOURCE_DIR}/scripts/cmake_format_all.sh)\nendif()\n\nfind_program(\n  PERLTIDY\n  NAMES perltidy\n  PATHS /bin /usr/bin /usr/local/bin /usr/local/opt/ /opt/bin\n  DOC \"The path to perltidy\")\n\nif(PERLTIDY)\n  message(STATUS \"Using perltidy ${PERLTIDY}\")\n  add_custom_target(\n    perltidy\n    COMMAND\n      ${CMAKE_COMMAND} -E env PERLTIDY=${PERLTIDY}\n      PERLTIDY_CONFIG=\"${PROJECT_SOURCE_DIR}/.perltidyrc\"\n      ${PROJECT_SOURCE_DIR}/scripts/perltidy_format_all.sh)\nendif()\n\nif(TARGET clang-format\n   OR TARGET cmake-format\n   OR TARGET perltidy)\n  add_custom_target(format)\n  if(TARGET clang-format)\n    add_dependencies(format clang-format)\n  endif()\n  if(TARGET cmake-format)\n    add_dependencies(format cmake-format)\n  endif()\n  if(TARGET perltidy)\n    add_dependencies(format perltidy)\n  endif()\nendif()\n\nif(REGRESS_CHECKS)\n  find_program(PG_REGRESS pg_regress\n               HINTS \"${PG_BINDIR}\" \"${PG_PKGLIBDIR}/pgxs/src/test/regress/\")\n\n  if(NOT PG_REGRESS)\n    message(STATUS \"Regress checks disabled: program 'pg_regress' not found\")\n  endif()\n\n  find_program(\n    PG_ISOLATION_REGRESS\n    NAMES pg_isolation_regress\n    HINTS ${PG_BINDIR} ${PG_PKGLIBDIR}/pgxs/src/test/isolation\n          ${PG_SOURCE_DIR}/src/test/isolation ${BINDIR})\n\n  if(NOT PG_ISOLATION_REGRESS)\n    message(\n      STATUS\n        \"Isolation regress checks disabled: 'pg_isolation_regress' not found\")\n  endif()\nelse()\n  message(STATUS \"Regress checks and isolation checks disabled\")\nendif()\n\n# Linter support via clang-tidy. Enabled when using clang as compiler\noption(LINTER \"Enable linter support using clang-tidy\" OFF)\nset(CLANG_TIDY_EXTRA_OPTS\n    \"\"\n    CACHE STRING \"Additional options for clang-tidy\")\n\nif(LINTER)\n  find_program(\n    CLANG_TIDY clang-tidy\n    PATHS /usr/bin /usr/local/bin /usr/local/opt/ /usr/local/opt/llvm/bin\n          /opt/bin\n    DOC \"The path to the clang-tidy linter\" REQUIRED)\n\n  execute_process(COMMAND ${CLANG_TIDY} --version\n                  OUTPUT_VARIABLE CLANG_TIDY_VERSION)\n  message(STATUS \"Using clang-tidy ${CLANG_TIDY}, ${CLANG_TIDY_VERSION}\")\n  string(\n    CONCAT\n      CMAKE_C_CLANG_TIDY\n      \"${CLANG_TIDY}\"\n      \";--checks=clang-diagnostic-*,clang-analyzer-*\"\n      \",-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling\"\n      \",-clang-analyzer-deadcode.DeadStores\"\n      \",bugprone-*\"\n      \",-bugprone-branch-clone\"\n      \",-bugprone-easily-swappable-parameters\"\n      \",-bugprone-implicit-widening-of-multiplication-result\"\n      \",-bugprone-narrowing-conversions\"\n      \",-bugprone-reserved-identifier\"\n      \",-bugprone-suspicious-include\"\n      \",readability-*\"\n      \",-readability-avoid-const-params-in-decls\"\n      \",-readability-braces-around-statements\"\n      \",-readability-else-after-return\"\n      \",-readability-function-cognitive-complexity\"\n      \",-readability-function-size\"\n      \",-readability-identifier-length\"\n      \",-readability-isolate-declaration\"\n      \",-readability-magic-numbers\"\n      \",-readability-math-missing-parentheses\"\n      \",-readability-non-const-parameter\"\n      \",-readability-redundant-casting\"\n      \"${CLANG_TIDY_EXTRA_OPTS}\")\n  if(WARNINGS_AS_ERRORS)\n    set(CMAKE_C_CLANG_TIDY \"${CMAKE_C_CLANG_TIDY};--warnings-as-errors=*\")\n  else()\n    set(CMAKE_C_CLANG_TIDY \"${CMAKE_C_CLANG_TIDY};--quiet\")\n  endif(WARNINGS_AS_ERRORS)\nendif(LINTER)\n\nif(NOT EXISTS ${PG_INCLUDEDIR}/pg_config.h)\n  message(\n    FATAL_ERROR\n      \"Could not find pg_config.h in ${PG_INCLUDEDIR}. \"\n      \"Make sure PG_PATH points to a valid PostgreSQL installation that includes development headers.\"\n  )\nendif()\n\nfile(READ ${PG_INCLUDEDIR}/pg_config.h PG_CONFIG_H)\nstring(REGEX MATCH \"#define USE_ASSERT_CHECKING 1\" PG_USE_ASSERT_CHECKING\n             ${PG_CONFIG_H})\n\nif(PG_USE_ASSERT_CHECKING AND NOT ASSERTIONS)\n  message(\n    STATUS\n      \"Assertion checks are OFF although enabled in PostgreSQL build (pg_config.h). \"\n      \"The PostgreSQL setting for assertions will take precedence.\")\nelseif(ASSERTIONS)\n  message(STATUS \"Assertion checks are ON\")\n  add_compile_definitions(USE_ASSERT_CHECKING=1)\nelseif(CMAKE_BUILD_TYPE MATCHES Debug)\n  message(\n    \"Assertion checks are OFF in Debug build. Set -DASSERTIONS=ON to enable assertions.\"\n  )\nelse()\n  message(STATUS \"Assertion checks are OFF\")\nendif()\n\n# Check if PostgreSQL has OpenSSL enabled by inspecting pg_config.h. Right now,\n# a Postgres header will redefine an OpenSSL function if Postgres is not\n# installed --with-openssl, so in order for TimescaleDB to compile correctly\n# with OpenSSL, Postgres must also have OpenSSL enabled.\ncheck_symbol_exists(USE_OPENSSL ${PG_INCLUDEDIR}/pg_config.h PG_USE_OPENSSL)\n\nif(USE_OPENSSL AND (NOT PG_USE_OPENSSL))\n  message(\n    FATAL_ERROR\n      \"PostgreSQL was built without OpenSSL support, which TimescaleDB needs for full compatibility. Please rebuild PostgreSQL using `--with-openssl` or if you want to continue without OpenSSL, re-run bootstrap with `-DUSE_OPENSSL=0`\"\n  )\nendif(USE_OPENSSL AND (NOT PG_USE_OPENSSL))\n\n# While we dont link directly against OpenSSL on non-Windows, doing this on\n# Windows causes linker errors. So on Windows we link directly against the\n# OpenSSL libraries.\nif(USE_OPENSSL AND MSVC)\n  # Try to find a local OpenSSL installation\n  find_package(OpenSSL)\n\n  if(NOT OPENSSL_FOUND)\n    message(\n      FATAL_ERROR\n        \"TimescaleDB requires OpenSSL but it wasn't found. If you want to continue without OpenSSL, re-run bootstrap with `-DUSE_OPENSSL=0`\"\n    )\n  endif(NOT OPENSSL_FOUND)\n\n  if(${OPENSSL_VERSION} VERSION_LESS \"1.0\")\n    message(FATAL_ERROR \"TimescaleDB requires OpenSSL version 1.0 or greater\")\n  endif()\n\n  set(_libraries)\n  foreach(_path ${OPENSSL_LIBRARIES})\n    if(EXISTS \"${_path}\")\n      list(APPEND _libraries ${_path})\n    else()\n      # check if a release version of the libraries are available\n      if(CMAKE_BUILD_TYPE STREQUAL \"Debug\" AND MSVC)\n        get_filename_component(_dir ${_path} DIRECTORY)\n        get_filename_component(_name ${_path} NAME_WE)\n        string(REGEX REPLACE \"[Dd]$\" \"\" _fixed ${_name})\n        get_filename_component(_ext ${_path} EXT)\n        set(_new_path \"${_dir}/${_fixed}${_ext}\")\n        if(EXISTS \"${_new_path}\")\n          list(APPEND _libraries ${_new_path})\n        endif()\n      endif()\n    endif()\n  endforeach()\n  set(OPENSSL_LIBRARIES ${_libraries})\n\n  foreach(_path ${OPENSSL_LIBRARIES})\n    message(STATUS \"OpenSSL libraries: ${_path}\")\n  endforeach()\n  message(STATUS \"Using OpenSSL version ${OPENSSL_VERSION}\")\nendif(USE_OPENSSL AND MSVC)\n\nif(CODECOVERAGE)\n  message(STATUS \"Code coverage is enabled.\")\n  # Note that --coverage is synonym for the necessary compiler and linker flags\n  # for the given compiler.  For example, with GCC, --coverage translates to\n  # -fprofile-arcs -ftest-coverage when compiling and -lgcov when linking\n  add_compile_options(--coverage -O0)\n  add_link_options(--coverage)\n  # When a source file is recompiled, the compiler regenerates the .gcno file\n  # but any .gcda file from a previous test run remains with a stale checksum.\n  # lcov then silently produces incorrect coverage data.  Delete the .gcda\n  # alongside the .o to prevent this.  The $$ escaping produces a literal $ for\n  # the shell in both Ninja and Make generators.\n  set(CMAKE_C_COMPILE_OBJECT\n      \"${CMAKE_C_COMPILE_OBJECT} && o=<OBJECT> && rm -f \\$\\${o%.o}.gcda\")\nendif(CODECOVERAGE)\n\n# TAP test support\noption(TAP_CHECKS \"Enable TAP test support\" ON)\n\nif(TAP_CHECKS)\n  find_package(Perl 5.8)\n\n  if(PERL_FOUND)\n    get_filename_component(PERL_BIN_PATH ${PERL_EXECUTABLE} DIRECTORY)\n\n    find_program(\n      PROVE prove\n      HINTS ${PERL_BIN_PATH}\n      PATHS \"/usr/bin\")\n\n    if(NOT PROVE)\n      message(STATUS \"Not running TAP tests: 'prove' binary not found.\")\n      set(TAP_CHECKS OFF)\n    endif()\n\n    # Check for the IPC::Run module\n    execute_process(\n      COMMAND ${PERL_EXECUTABLE} -MIPC::Run -e \"\"\n      ERROR_QUIET\n      RESULT_VARIABLE PERL_MODULE_STATUS)\n\n    if(PERL_MODULE_STATUS)\n      message(STATUS \"Not running TAP tests: IPC::Run Perl module not found.\")\n      set(TAP_CHECKS OFF)\n    endif()\n  else()\n    message(STATUS \"Not running TAP tests: Perl not found.\")\n    set(TAP_CHECKS OFF)\n  endif()\nendif()\n\nif(UNIX)\n  add_subdirectory(scripts)\nendif(UNIX)\n\nadd_subdirectory(sql)\nadd_subdirectory(test)\nadd_subdirectory(src)\n\nif(NOT APACHE_ONLY)\n  add_subdirectory(tsl)\nendif()\n\nadd_custom_target(licensecheck\n                  COMMAND ${PROJECT_SOURCE_DIR}/scripts/check_license_all.sh)\n\n# This needs to be the last subdirectory so that other targets are already\n# defined\nif(CODECOVERAGE)\n  add_subdirectory(coverage)\nendif()\n\nif(IS_DIRECTORY ${PROJECT_SOURCE_DIR}/.git)\n  configure_file(${PROJECT_SOURCE_DIR}/scripts/githooks/commit_msg.py\n                 ${PROJECT_SOURCE_DIR}/.git/hooks/commit-msg COPYONLY)\nendif()\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to TimescaleDB\n\nWe appreciate any help the community can provide to make TimescaleDB better!  \n\nYou can help in different ways:\n\n* Open an [issue](https://github.com/timescale/timescaledb/issues) with a\n  bug report, build issue, feature request, suggestion, etc.\n\n* Fork this repository and submit a pull request\n\nFor any particular improvement you want to make, it can be beneficial to\nbegin discussion on the GitHub issues page. This is the best place to\ndiscuss your proposed improvement (and its implementation) with the core\ndevelopment team.\n\nBefore we accept any code contributions, Timescale contributors need to\nsign the [Contributor License Agreement](https://cla-assistant.io/timescale/timescaledb) (CLA). By signing a CLA, we can\nensure that the community is free and confident in its ability to use your\ncontributions.\n\n## Getting and building TimescaleDB\n\nPlease follow our README for [instructions on installing from source](https://github.com/timescale/timescaledb/blob/main/docs/BuildSource.md).\n\n## Style guide\n\nBefore submitting any contributions, please ensure that it adheres to\nour [Style Guide](https://github.com/timescale/timescaledb/blob/main/docs/StyleGuide.md).\n\n## Code review workflow\n\n* Sign the [Contributor License Agreement](https://cla-assistant.io/timescale/timescaledb) (CLA) if you're a new contributor.\n\n* Develop on your local branch:\n\n    * Fork the repository and create a local feature branch to do work on,\n      ideally on one thing at a time.  Don't mix bug fixes with unrelated\n      feature enhancements or stylistical changes.\n\n    * Hack away. Add tests for non-trivial changes.\n\n    * Run the [test suite](#testing) and make sure everything passes.\n\n    * When committing, be sure to write good commit messages according to [these\n      seven rules](https://chris.beams.io/posts/git-commit/#seven-rules). Doing \n      `git commit` prints a message if any of the rules is violated. \n      Stylistically,\n      we use commit message titles in the imperative tense, e.g., `Add\n      merge-append query optimization for time aggregate`.  In the case of\n      non-trivial changes, include a longer description in the commit message\n      body explaining and detailing the changes.  That is, a commit message\n      should have a short title, followed by a empty line, and then\n      followed by the longer description.\n\n    * When committing, link which GitHub issue of [this \n      repository](https://github.com/timescale/timescaledb/issues) is fixed or \n      closed by the commit with a [linking keyword recognised by \n      GitHub](https://docs.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword). \n      For example, if the commit fixes bug 123, add a line at the end of the \n      commit message with  `Fixes #123`, if the commit implements feature \n      request 321, add a line at the end of the commit message `Closes #321`.\n      This will be recognized by GitHub. It will close the corresponding issue \n      and place a hyperlink under the number.\n\n* Push your changes to an upstream branch:\n\n    * Make sure that each commit in the pull request will represent a\n      logical change to the code, will compile, and will pass tests.\n\n    * Make sure that the pull request message contains all important \n      information from the commit messages including which issues are\n      fixed and closed. If a pull request contains one commit only, then\n      repeating the commit message is preferred, which is done automatically\n      by GitHub when it creates the pull request.\n\n    * Rebase your local feature branch against main (`git fetch origin`,\n      then `git rebase origin/main`) to make sure you're\n      submitting your changes on top of the newest version of our code.\n\n    * When finalizing your PR (i.e., it has been approved for merging),\n      aim for the fewest number of commits that\n      make sense. That is, squash any \"fix up\" commits into the commit they\n      fix rather than keep them separate. Each commit should represent a\n      clean, logical change and include a descriptive commit message.\n\n    * Push your commit to your upstream feature branch: `git push -u <yourfork> my-feature-branch`\n\n* Create and manage pull request:\n\n    * [Create a pull request using GitHub](https://help.github.com/articles/creating-a-pull-request).\n      If you know a core developer well suited to reviewing your pull\n      request, either mention them (preferably by GitHub name) in the PR's\n      body or [assign them as a reviewer](https://help.github.com/articles/assigning-issues-and-pull-requests-to-other-github-users/).\n\n    * If you get a test failure in the CI, check them under [Github Actions](https://github.com/timescale/timescaledb/actions)\n\n    * Address feedback by amending your commit(s). If your change contains\n      multiple commits, address each piece of feedback by amending that\n      commit to which the particular feedback is aimed.\n\n    * The PR is marked as accepted when the reviewer thinks it's ready to be\n      merged.  Most new contributors aren't allowed to merge themselves; in\n      that case, we'll do it for you.\n\n## Testing\n\nEvery non-trivial change to the code base should be accompanied by a\nrelevant addition to or modification of the test suite.\n\nPlease check that the full test suite (including your test additions\nor changes) passes successfully on your local machine **before you\nopen a pull request**.\n\nIf you are running locally:\n```bash\n# Use Debug build mode for full battery of tests\n./bootstrap -DCMAKE_BUILD_TYPE=Debug\ncd build && make\nmake installcheck\n```\n\nAll submitted pull requests are also automatically\nrun against our test suite via [Github Actions](https://github.com/timescale/timescaledb/actions)\n(that link shows the latest build status of the repository).\n\n## Reviewing and accepting your contribution\n\nWe appreciate everyone who is investing time in contributing to TimescaleDB and regret that we sometimes have to reject contributions even when they might appear to add value. \nIf the contribution is accepted, we will merge the changes, acknowledge your contribution in the release, and take care of the backporting to the relevant branches, if necessary.\nBefore you start, please discuss your change in a GitHub issue before spending much time on its implementation. We sometimes have to reject contributions that duplicate other efforts, take the wrong approach to solving a problem, or solve a problem which does not need solving. An up-front discussion often saves your time.\n\nA contribution is expected to address one specific change. Pull requests are expected to be small, if a PR is deemed too large or touches too many disparate parts of the system, you will be required to break it down into a series of smaller, digestible PRs before reviewing continues. Avoid adding unnecessary stylistic changes.\n\nTimescaleDB is complex system, requiring a deep understanding of Postgres internals, the system architecture and the potential secondary effects of changes.\nWhile we highly value community input, the core development team's primary responsibility is maintaining the health of the project. \nWe reserve the right to respectfully close the PR. This is not a reflection of your skills as an engineer, but rather a necessity of resource allocation to keep the project stable and performant.\n\nWe reserved the right to reject contributions, if the time required on reviews would outweigh the benefits of a change by preventing us from working on other beneficial changes instead.\n\nWe sometimes reject contributions due to the low quality of the submission since low-quality submissions tend to take unreasonable effort to review properly. \nQuality is rather subjective so it is hard to describe exactly how to avoid this, but there are some basic steps you can take to reduce the chances of rejection:\n\n* Unit Tests: Every new function or modified logic path must have accompanying unit tests.\n* Integration Tests: Features that touch the storage engine, query planner, or sub system must include integration tests.\n* Edge Cases: You are expected to proactively test for edge cases, concurrency hazards, and out-of-memory (OOM) scenarios.\n* No regressions: Your code must pass all existing CI/CD pipelines, including e.g. fuzzing, without degrading current metrics.\n* Style Guide: Your code must be formatted according to the projects [style guide](#style-guide).\n\nWe welcome the use of AI coding assistants (Copilot, Gemini, etc.) to enhance your productivity. However, AI-generated contributions adhere to the exact same rigorous standards as human-written code. You are fully responsible for the accuracy, safety, and performance of any AI-generated code you submit.\nIf a PR appears to be low quality AI outputs without reflecting a proficient understanding of the change, we will close the PR immediately.\n\nWe expect you to follow up on review comments, but recognise that everyone has many priorities for their time and may not be able to respond for several days. We will understand if you find yourself without the time to complete your contribution, but please let us know that you have stopped working on it and we can conclude how to handle the contribution.\nAfter two weeks of inactivity, we will reject the contribution, unless we complete it. \n\nIf your contribution is rejected, we will close the pull request with a comment explaining why.\n\n## License headers\n\nWe require license headers on all C and SQL files, unless explicitly instructed otherwise.\n\nAll C and SQL files in the [tsl](https://github.com/timescale/timescaledb/tree/main/tsl) directory require the following short license header of the [Timescale License Agreement](https://github.com/timescale/timescaledb/blob/main/tsl/LICENSE-TIMESCALE):\n\n```\n/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n```\n\nAll C and SQL files in the [src](https://github.com/timescale/timescaledb/tree/main/src) directory require the following short license header of the [Apache License](https://github.com/timescale/timescaledb/blob/main/LICENSE-APACHE):\n\n```\n/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n```\n"
  },
  {
    "path": "LICENSE",
    "content": "Source code in this repository is variously licensed under the Apache License\nVersion 2.0, an Apache compatible license, or the Timescale License.\n\nAll source code should have information at the beginning of its respective file\nwhich specifies its licensing information.\n\n* Outside of the \"tsl\" directory, source code in a given file is licensed\n  under the Apache License Version 2.0, unless otherwise noted (e.g., an\n  Apache-compatible license).\n\n* Within the \"tsl\" folder, source code in a given file is licensed under the\n  Timescale License, unless otherwise noted.\n\nWhen built, separate shared object files are generated for the Apache-licensed\nsource code and the Timescale-licensed source code. The shared object binaries\nthat contain `-tsl` in their name are licensed under the Timescale License.\n"
  },
  {
    "path": "LICENSE-APACHE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "NOTICE",
    "content": "TimescaleDB (TM)\n\nCopyright (c) 2017-2026  Timescale, Inc. All Rights Reserved.\nCopyright (c) 2016-2017  440 Labs, Inc. dba Timescale. All Rights Reserved.\n\nSource code in this repository is variously licensed under the Apache License\nVersion 2.0, an Apache-compatible license, or the Timescale License. Please see\nLICENSE for more information.\n\n* For a copy of the Apache License Version 2.0, please see LICENSE-APACHE\n  as included in this repository's top-level directory.\n\n* For a copy of the Timescale License, please see LICENSE-TIMESCALE\n  as included in this repository's \"tsl\" directory.\n\n* For a copy of all other Apache-compatible licenses and notices,\n  please see below.\n\n\n========================================================================\nNOTICES\n========================================================================\n\nCertain files in this code base have been modified and/or copied,\neither partially or wholely, from source code from the PostgreSQL\ndatabase management system, which is licensed under the open-source\nPostgreSQL License with the following copyright information.\n\nThe PostgreSQL License\n========================================================================\n\nPostgreSQL Database Management System\n(formerly known as Postgres, then as Postgres95)\n\nPortions Copyright (c) 1996-2026, The PostgreSQL Global Development Group\nPortions Copyright (c) 1994, The Regents of the University of California\n\nPermission to use, copy, modify, and distribute this software and its\ndocumentation for any purpose, without fee, and without a written\nagreement is hereby granted, provided that the above copyright notice\nand this paragraph and the following two paragraphs appear in all\ncopies.\n\nIN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY\nFOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,\nINCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND\nITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN\nADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nTHE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,\nINCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE\nPROVIDED HEREUNDER IS ON AN \"AS IS\" BASIS, AND THE UNIVERSITY OF\nCALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,\nUPDATES, ENHANCEMENTS, OR MODIFICATIONS.\n\n========================================================================\n"
  },
  {
    "path": "README.md",
    "content": "<div align=center>\n<picture align=center>\n    <source  srcset=\"https://assets.timescale.com/timescale-web/brand/show/horizontal-black.svg\">\n    <img alt=\"Tiger Data logo\" >\n</picture>\n</div>\n\n<div align=center>\n\n<h3>TimescaleDB is a PostgreSQL extension for high-performance real-time analytics on time-series and event data</h3>\n\n[![Docs](https://img.shields.io/badge/Read_the_docs-black?style=for-the-badge&logo=readthedocs&logoColor=white)](https://docs.tigerdata.com/)\n[![SLACK](https://img.shields.io/badge/Ask_the_community-black?style=for-the-badge&logo=slack&logoColor=white)](https://timescaledb.slack.com/archives/C4GT3N90X)\n[![Try TimescaleDB for free](https://img.shields.io/badge/Try_Tiger_Cloud_for_free-black?style=for-the-badge&logo=timescale&logoColor=white)](https://console.cloud.timescale.com/signup)\n\n</div>\n\n## Quick Start with TimescaleDB\n\nGet started with TimescaleDB in under 10 minutes. This guide will help you run TimescaleDB locally, create your first hypertable with columnstore enabled, write data to the columnstore, and see instant analytical query performance.\n\n### What You'll Learn\n\n- How to run TimescaleDB with a one-line install or Docker command\n- How to create a hypertable with columnstore enabled\n- How to insert data directly to the columnstore \n- How to execute analytical queries\n\n### Prerequisites\n\n- Docker installed on your machine\n- 8GB RAM recommended\n- `psql` client (included with PostgreSQL) or any PostgreSQL client like [pgAdmin](https://www.pgadmin.org/download/)\n\n### Step 1: Start TimescaleDB\n\nYou have two options to start TimescaleDB:\n\n#### Option 1: One-line install (Recommended)\n\nThe easiest way to get started:\n\n> **Important:** This script is intended for local development and testing only. Do **not** use it for production deployments. For production-ready installation options, see the [TimescaleDB installation guide](https://docs.timescale.com/self-hosted/latest/install/).\n\n**Linux/Mac:**\n\n```sh\ncurl -sL https://tsdb.co/start-local | sh\n```\n\nThis command:\n- Downloads and starts TimescaleDB (if not already downloaded)\n- Exposes PostgreSQL on port **6543** (a non-standard port to avoid conflicts with other PostgreSQL instances on port 5432)\n- Automatically tunes settings for your environment using timescaledb-tune\n- Sets up a persistent data volume\n\n#### Option 2: Manual Docker command also used for Windows\n\nAlternatively, you can run TimescaleDB directly with Docker:\n\n```bash\ndocker run -d --name timescaledb \\\n    -p 6543:5432 \\\n    -e POSTGRES_PASSWORD=password \\\n    timescale/timescaledb-ha:pg18\n```\n\n**Note:** We use port **6543** (mapped to container port 5432) to avoid conflicts if you have other PostgreSQL instances running on the standard port 5432.\n\nWait about 1-2 minutes for TimescaleDB to download & initialize.\n\n### Step 2: Connect to TimescaleDB\n\nConnect using `psql`:\n\n```bash\npsql -h localhost -p 6543 -U postgres\n# When prompted, enter password: password\n```\n\nYou should see the PostgreSQL prompt. Verify TimescaleDB is installed:\n\n```sql\nSELECT extname, extversion FROM pg_extension WHERE extname = 'timescaledb';\n```\n\nExpected output:\n```\n   extname   | extversion\n-------------+------------\n timescaledb | 2.x.x\n```\n\n**Prefer a GUI?** If you'd rather use a graphical tool instead of the command line, you can download [pgAdmin](https://www.pgadmin.org/download/) and connect to TimescaleDB using the same connection details (host: `localhost`, port: `6543`, user: `postgres`, password: `password`).\n\n### Step 3: Create Your First Hypertable\n\nLet's create a hypertable for IoT sensor data with columnstore enabled:\n\n```sql\n-- Create a hypertable with automatic columnstore\nCREATE TABLE sensor_data (\n    time TIMESTAMPTZ NOT NULL,\n    sensor_id TEXT NOT NULL,\n    temperature DOUBLE PRECISION,\n    humidity DOUBLE PRECISION,\n    pressure DOUBLE PRECISION\n) WITH (\n    tsdb.hypertable\n);\n-- create index\nCREATE INDEX idx_sensor_id_time ON sensor_data(sensor_id, time DESC);\n```\n\n`tsdb.hypertable` - Converts this into a TimescaleDB hypertable\n\nSee more:\n\n- [About hypertables](https://docs.tigerdata.com/use-timescale/latest/hypertables/)\n- [API reference](https://docs.tigerdata.com/api/latest/hypertable/)\n- [About columnstore](https://docs.tigerdata.com/use-timescale/latest/compression/about-compression/)\n- [Enable columnstore manually](https://docs.tigerdata.com/use-timescale/latest/compression/manual-compression/)\n- [API reference](https://docs.tigerdata.com/api/latest/compression/)\n\n### Step 4: Insert Sample Data\n\nLet's add some sample sensor readings:\n\n\n```sql\n-- Enable timing to see time to execute queries\n\\timing on\n\n-- Insert sample data for multiple sensors\n-- SET timescaledb.enable_direct_compress_insert = on to insert data directly to the columnstore (columnnar format for performance)\nSET timescaledb.enable_direct_compress_insert = on;\nINSERT INTO sensor_data (time, sensor_id, temperature, humidity, pressure)\nSELECT\n    time,\n    'sensor_' || ((random() * 9)::int + 1),\n    20 + (random() * 15),\n    40 + (random() * 30),\n    1000 + (random() * 50)\nFROM generate_series(\n    NOW() - INTERVAL '90 days',\n    NOW(),\n    INTERVAL '1 seconds'\n) AS time;\n\n-- Once data is inserted into the columnstore we optimize the order and structure \n-- this compacts and orders the data in the chunks for optimal query performance and compression\nDO $$\nDECLARE ch TEXT;\nBEGIN\n    FOR ch IN SELECT show_chunks('sensor_data') LOOP\n        CALL convert_to_columnstore(ch, recompress := true);\n    END LOOP;\nEND $$;\n```\n\nThis generates ~7,776,001 readings across 10 sensors over the past 90 days.\n\nVerify the data was inserted:\n\n```sql\nSELECT COUNT(*) FROM sensor_data;\n```\n\n### Step 5: Run Your First Analytical Queries\n\nNow let's run some analytical queries that showcase TimescaleDB's performance:\n\n```sql\n-- Enable query timing to see performance\n\\timing on\n\n-- Query 1: Average readings per sensor over the last 7 days\nSELECT\n    sensor_id,\n    COUNT(*) as readings,\n    ROUND(AVG(temperature)::numeric, 2) as avg_temp,\n    ROUND(AVG(humidity)::numeric, 2) as avg_humidity,\n    ROUND(AVG(pressure)::numeric, 2) as avg_pressure\nFROM sensor_data\nWHERE time > NOW() - INTERVAL '7 days'\nGROUP BY sensor_id\nORDER BY sensor_id;\n\n-- Query 2: Hourly averages using time_bucket \n-- Time buckets enable you to aggregate data in hypertables by time interval and calculate summary values.\nSELECT\n    time_bucket('1 hour', time) AS hour,\n    sensor_id,\n    ROUND(AVG(temperature)::numeric, 2) as avg_temp,\n    ROUND(AVG(humidity)::numeric, 2) as avg_humidity\nFROM sensor_data\nWHERE time > NOW() - INTERVAL '24 hours'\nGROUP BY hour, sensor_id\nORDER BY hour DESC, sensor_id\nLIMIT 20;\n\n-- Query 3: Daily statistics across all sensors\nSELECT\n    time_bucket('1 day', time) AS day,\n    COUNT(*) as total_readings,\n    ROUND(AVG(temperature)::numeric, 2) as avg_temp,\n    ROUND(MIN(temperature)::numeric, 2) as min_temp,\n    ROUND(MAX(temperature)::numeric, 2) as max_temp\nFROM sensor_data\nGROUP BY day\nORDER BY day DESC\nLIMIT 10;\n\n-- Query 4: Latest reading for each sensor\n-- Highlights the value of Skipscan executing in under 100ms without skipscan it takes over 5sec\nSELECT DISTINCT ON (sensor_id)\n    sensor_id,\n    time,\n    ROUND(temperature::numeric, 2) as temperature,\n    ROUND(humidity::numeric, 2) as humidity,\n    ROUND(pressure::numeric, 2) as pressure\nFROM sensor_data\nORDER BY sensor_id, time DESC;\n```\n\nNotice how fast these analytical queries run, even with aggregations across millions of rows. This is the power of TimescaleDB's columnstore.\n\n### What's Happening Behind the Scenes?\n\nTimescaleDB automatically:\n- **Partitions your data** into time-based chunks for efficient querying\n- **Write directly to columnstore** using columnar storage (90%+ compression typical) and faster vectorized queries\n- **Optimizes queries** by only scanning relevant time ranges and columns\n- **Enables time_bucket()** - a powerful function for time-series aggregation\n\nSee more:\n\n- [Query data](https://docs.tigerdata.com/use-timescale/latest/query-data/)\n- [Write data](https://docs.tigerdata.com/use-timescale/latest/write-data/)\n- [About time buckets](https://docs.tigerdata.com/use-timescale/latest/time-buckets/about-time-buckets/)\n- [API reference](https://docs.tigerdata.com/api/latest/hyperfunctions/time_bucket/)\n- [All TimescaleDB features](https://docs.tigerdata.com/use-timescale/latest/)\n\n### Next Steps\n\nNow that you've got the basics, explore more:\n\n### Create Continuous Aggregates\n\nContinuous aggregates make real-time analytics run faster on very large datasets. They continuously and incrementally refresh a query in the background, so that when you run such query, only the data that has changed needs to be computed, not the entire dataset. This is what makes them different from regular PostgreSQL [materialized views](https://www.postgresql.org/docs/current/rules-materializedviews.html), which cannot be incrementally materialized and have to be rebuilt from scratch every time you want to refresh them.\n\nLet's create a continuous aggregate for hourly sensor statistics:\n\n#### Step 1: Create the Continuous Aggregate\n\n```sql\nCREATE MATERIALIZED VIEW sensor_data_hourly\nWITH (timescaledb.continuous) AS\nSELECT\n    time_bucket('1 hour', time) AS hour,\n    sensor_id,\n    AVG(temperature) AS avg_temp,\n    AVG(humidity) AS avg_humidity,\n    AVG(pressure) AS avg_pressure,\n    MIN(temperature) AS min_temp,\n    MAX(temperature) AS max_temp,\n    COUNT(*) AS reading_count\nFROM sensor_data\nGROUP BY hour, sensor_id;\n```\n\nThis creates a materialized view that pre-aggregates your sensor data into hourly buckets. The view is automatically populated with existing data.\n\n#### Step 2: Add a Refresh Policy\n\nTo keep the continuous aggregate up-to-date as new data arrives, add a refresh policy:\n\n```sql\nSELECT add_continuous_aggregate_policy(\n    'sensor_data_hourly',\n    start_offset => INTERVAL '3 hours',\n    end_offset => INTERVAL '1 hour',\n    schedule_interval => INTERVAL '1 hour'\n);\n```\n\nThis policy:\n- Refreshes the continuous aggregate every hour\n- Processes data from 3 hours ago up to 1 hour ago (leaving the most recent hour for real-time queries)\n- Only processes new or changed data incrementally\n\n#### Step 3: Query the Continuous Aggregate\n\nNow you can query the pre-aggregated data for much faster results:\n\n```sql\n-- Get hourly averages for the last 24 hours\nSELECT\n    hour,\n    sensor_id,\n    ROUND(avg_temp::numeric, 2) AS avg_temp,\n    ROUND(avg_humidity::numeric, 2) AS avg_humidity,\n    reading_count\nFROM sensor_data_hourly\nWHERE hour > NOW() - INTERVAL '24 hours'\nORDER BY hour DESC, sensor_id\nLIMIT 50;\n```\n\n#### Benefits of Continuous Aggregates\n\n- **Faster queries**: Pre-aggregated data means queries run in milliseconds instead of seconds\n- **Incremental refresh**: Only new/changed data is processed, not the entire dataset\n- **Automatic updates**: The refresh policy keeps your aggregates current without manual intervention\n- **Real-time option**: You can enable real-time aggregation to combine materialized and raw data\n\n#### Try It Yourself\n\nCompare the performance difference:\n\n```sql\n-- Query the raw hypertable (slower on large datasets)\n\\timing on\nSELECT\n    time_bucket('1 hour', time) AS hour,\n    AVG(temperature) AS avg_temp\nFROM sensor_data\nWHERE time > NOW() - INTERVAL '60 days'\nGROUP BY hour\nORDER BY hour DESC\nLIMIT 24;\n\n-- Query the continuous aggregate (much faster)\nSELECT\n    hour,\n    avg_temp\nFROM sensor_data_hourly\nWHERE hour > NOW() - INTERVAL '60 days'\nORDER BY hour DESC\nLIMIT 24;\n```\n\nNotice how the continuous aggregate query is significantly faster, especially as your dataset grows!\n\nSee more:\n\n- [About continuous aggregates](https://docs.tigerdata.com/use-timescale/latest/continuous-aggregates/)\n- [API reference](https://docs.tigerdata.com/api/latest/continuous-aggregates/create_materialized_view/)\n- [TimescaleDB Documentation](https://docs.timescale.com)\n- [Time-series Best Practices](https://docs.timescale.com/use-timescale/latest/schema-management/)\n- [Continuous Aggregates](https://docs.timescale.com/use-timescale/latest/continuous-aggregates/)\n\n## Examples\n\nLearn TimescaleDB with complete, standalone examples using real-world datasets. Each example includes sample data and analytical queries.\n\n- **[NYC Taxi Data](docs/getting-started/nyc-taxi/)** - Transportation and location-based analytics\n- **[Financial Market Data](docs/getting-started/financial-ticks/)** - Trading and market data analysis\n- **[Application Events](docs/getting-started/events-uuidv7/)** - Event logging with UUIDv7\n\nOr try some of our workshops\n- **[AI Workshop: EV Charging Station Analysis](https://github.com/timescale/TigerData-Workshops/tree/main/AI-Workshop)** - Integrate PostgreSQL with AI capabilities for managing and analyzing EV charging station data\n- **[Time-Series Workshop: Financial Data Analysis](https://github.com/timescale/TigerData-Workshops/tree/main/TimeSeries-Workshop-Finance/)** - Work with cryptocurrency tick data, create candlestick charts\n\n## Want TimescaleDB hosted and managed for you? Try Tiger Cloud\n\n[Tiger Cloud](https://docs.tigerdata.com/getting-started/latest/) is the modern PostgreSQL data platform for all your applications. It enhances PostgreSQL to handle time series, events, real-time analytics, and vector search—all in a single database alongside transactional workloads. You get one system that handles live data ingestion, late and out-of-order updates, and low latency queries, with the performance, reliability, and scalability your app needs. Ideal for IoT, crypto, finance, SaaS, and a myriad other domains, Tiger Cloud allows you to build data-heavy, mission-critical apps while retaining the familiarity and reliability of PostgreSQL. See [our whitepaper](https://docs.tigerdata.com/about/latest/whitepaper/) for a deep dive into Tiger Cloud's architecture and how it meets the needs of even the most demanding applications.\n\nA Tiger Cloud service is a single optimized 100% PostgreSQL database instance that you use as is, or extend with capabilities specific to your business needs. The available capabilities are:\n\n- **Time-series and analytics**: PostgreSQL with TimescaleDB. The PostgreSQL you know and love, supercharged with functionality for storing and querying time-series data at scale for real-time analytics and other use cases. Get faster time-based queries with hypertables, continuous aggregates, and columnar storage. Save on storage with native compression, data retention policies, and bottomless data tiering to Amazon S3.\n- **AI and vector**: PostgreSQL with vector extensions. Use PostgreSQL as a vector database with purpose built extensions for building AI applications from start to scale. Get fast and accurate similarity search with the pgvector and pgvectorscale extensions. Create vector embeddings and perform LLM reasoning on your data with the pgai extension.\n- **PostgreSQL**: the trusted industry-standard RDBMS. Ideal for applications requiring strong data consistency, complex relationships, and advanced querying capabilities. Get ACID compliance, extensive SQL support, JSON handling, and extensibility through custom functions, data types, and extensions.\nAll services include all the cloud tooling you'd expect for production use: [automatic backups](https://docs.tigerdata.com/use-timescale/latest/backup-restore/backup-restore-cloud/), [high availability](https://docs.tigerdata.com/use-timescale/latest/ha-replicas/), [read replicas](https://docs.tigerdata.com/use-timescale/latest/ha-replicas/read-scaling/), [data forking](https://docs.tigerdata.com/use-timescale/latest/services/service-management/#fork-a-service), [connection pooling](https://docs.tigerdata.com/use-timescale/latest/services/connection-pooling/), [tiered storage](https://docs.tigerdata.com/use-timescale/latest/data-tiering/), [usage-based storage](https://docs.tigerdata.com/about/latest/pricing-and-account-management/), and much more.\n\n## Check build status\n\n|Linux/macOS|Linux i386|Windows|Coverity|Code Coverage|OpenSSF|\n|:---:|:---:|:---:|:---:|:---:|:---:|\n|[![Build Status Linux/macOS](https://github.com/timescale/timescaledb/actions/workflows/linux-build-and-test.yaml/badge.svg?branch=main&event=schedule)](https://github.com/timescale/timescaledb/actions/workflows/linux-build-and-test.yaml?query=workflow%3ARegression+branch%3Amain+event%3Aschedule)|[![Build Status Linux i386](https://github.com/timescale/timescaledb/actions/workflows/linux-32bit-build-and-test.yaml/badge.svg?branch=main&event=schedule)](https://github.com/timescale/timescaledb/actions/workflows/linux-32bit-build-and-test.yaml?query=workflow%3ARegression+branch%3Amain+event%3Aschedule)|[![Windows build status](https://github.com/timescale/timescaledb/actions/workflows/windows-build-and-test.yaml/badge.svg?branch=main&event=schedule)](https://github.com/timescale/timescaledb/actions/workflows/windows-build-and-test.yaml?query=workflow%3ARegression+branch%3Amain+event%3Aschedule)|[![Coverity Scan Build Status](https://scan.coverity.com/projects/timescale-timescaledb/badge.svg)](https://scan.coverity.com/projects/timescale-timescaledb)|[![Code Coverage](https://codecov.io/gh/timescale/timescaledb/branch/main/graphs/badge.svg?branch=main)](https://codecov.io/gh/timescale/timescaledb)|[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/8012/badge)](https://www.bestpractices.dev/projects/8012)|\n\n## Get involved\n\nWe welcome contributions to TimescaleDB! See [Contributing](https://github.com/timescale/timescaledb/blob/main/CONTRIBUTING.md) and [Code style guide](https://github.com/timescale/timescaledb/blob/main/docs/StyleGuide.md) for details.\n\n## Learn about Tiger Data\n\nTiger Data is the fastest PostgreSQL for transactional, analytical and agentic workloads. To learn more about the company and its products, visit [tigerdata.com](https://www.tigerdata.com).\n\n## Troubleshooting\n\n#### Docker container won't start\n\n```bash\n# Check if container is running\ndocker ps -a\n\n# View container logs (use the appropriate container name)\n# For one-line install:\ndocker logs timescaledb-ha-pg18-quickstart\n# For manual Docker command:\ndocker logs timescaledb\n\n# Stop and remove existing container\n# For one-line install:\ndocker stop timescaledb-ha-pg18-quickstart && docker rm timescaledb-ha-pg18-quickstart\n# For manual Docker command:\ndocker stop timescaledb && docker rm timescaledb\n\n# Start fresh\n# Option 1: Use the one-line install\ncurl -sL https://tsdb.co/start-local | sh\n# Option 2: Use manual Docker command\ndocker run -d --name timescaledb -p 6543:5432 -e POSTGRES_PASSWORD=password timescale/timescaledb-ha:pg18\n```\n\n#### Can't connect with psql\n\n- Verify Docker container is running: `docker ps`\n- Check port 6543 isn't already in use: `lsof -i :6543`\n- Try using explicit host: `psql -h 127.0.0.1 -p 6543 -U postgres`\n\n#### TimescaleDB extension not found\n\nThe `timescale/timescaledb-ha:pg18` image has TimescaleDB pre-installed and pre-loaded. If you see errors, ensure you're using the correct image.\n\n## Clean Up\n\nWhen you're done experimenting:\n\n#### If you used the one-line install:\n\n```bash\n# Stop the container\ndocker stop timescaledb-ha-pg18-quickstart\n\n# Remove the container\ndocker rm timescaledb-ha-pg18-quickstart\n\n# Remove the persistent data volume\ndocker volume rm timescaledb_data\n\n# (Optional) Remove the Docker image\ndocker rmi timescale/timescaledb-ha:pg18\n```\n\n#### If you used the manual Docker command:\n\n```bash\n# Stop the container\ndocker stop timescaledb\n\n# Remove the container\ndocker rm timescaledb\n\n# (Optional) Remove the Docker image\ndocker rmi timescale/timescaledb-ha:pg18\n```\n\n**Note:** If you created a named volume with the manual Docker command, you can remove it with `docker volume rm <volume_name>`.\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\nWe aim to keep TimescaleDB safe for everyone. \nPublicly disclosing security bugs in a public forum can put everyone in the Timescale community at risk,\nhowever. Therefore, we ask that people follow the below instructions to report security vulnerability.\nThe entire Timescale community thanks you!\n\n## Supported Versions\n\nThe supported version is always the latest major release available in our repository.\nWe also release regular minor versions with fixes and corrections alongside some new features as well as patchfix releases, that you should keep upgrading to.\nVulnerability fixes are made available as part of these patchfix releases and you can read our list of [Security Advisories](https://github.com/timescale/timescaledb/security/advisories?state=published).\n \nYou can also take a look at our [Support Policy](https://www.timescale.com/legal/support-policy).\n\n\n## Reporting a Vulnerability\n\nIf you find a vulnerability in our software, please email the Timescale Security Team at security@timescale.com.\n\nPlease note that the e-mail address should only be used for reporting undisclosed security vulnerabilities in Timescale products and services. \nRegular bug reports should be submitted as GitHub issues, while other _questions_ around security,\ncompliance, or functionality can be made either through our support (for customers) or\ncommunity channels (e.g., [Timescale Slack](https://slack.timescale.com/), [Forums](https://www.timescale.com/forums), etc.)\n"
  },
  {
    "path": "bootstrap",
    "content": "#!/usr/bin/env bash\n\n# This bootstrap scripts set up the build environment for TimescaleDB\n# Any flags will be passed on to CMake, e.g.,\n# ./bootstrap -DCMAKE_BUILD_TYPE=\"Debug\"\n\n## Check to make cmake is installed\nif ! command -v cmake >/dev/null 2>&1; then\n\techo \"cmake is required to build TimescaleDB. Please install via your system's preferred method.\"\n\texit 1\nfi\n\nBUILD_DIR=${BUILD_DIR:-./build}\nBUILD_FORCE_REMOVE=${BUILD_FORCE_REMOVE:-false}\nSRC_DIR=$(dirname $0)\nif [[ ! ${SRC_DIR} == /* ]]; then\n    SRC_DIR=$(pwd)/${SRC_DIR}\nfi\n\nif [ ${BUILD_FORCE_REMOVE} == \"true\" ]; then\n    rm -fr ${BUILD_DIR}\nelif [ -d ${BUILD_DIR} ]; then\n    echo \"Build system already initialized in ${BUILD_DIR}\"\n\n    read -r -n 1 -p \"Do you want to remove it (this is IMMEDIATE and PERMANENT), y/n? \" choice\n    echo \"\"\n    if [ $choice == \"y\" ]; then\n        rm -fr ${BUILD_DIR}\n    else\n        exit\n    fi\nfi\n\nset -e\nset -u\n\nmkdir -p ${BUILD_DIR} && \\\n    cd ${BUILD_DIR} && \\\n    cmake ${SRC_DIR} \"$@\"\n\necho \"TimescaleDB build system initialized in ${BUILD_DIR}. To compile, do:\"\necho -e \"\\033[1mcd ${BUILD_DIR} && make\\033[0m\"\n"
  },
  {
    "path": "bootstrap.bat",
    "content": "@echo off\n:: This bootstrap scripts set up the build environment for TimescaleDB\n:: Any flags will be passed on to CMake, e.g.,\n:: ./bootstrap.bat -DCMAKE_BUILD_TYPE=\"Debug\"\n\n\n:: Get source directory to build from\nset ORIG=%0\nfor %%F in (%ORIG%) do set SRC_DIR=%%~dpF\n\nSET BUILD_DIR=./build\n\nIF EXIST \"%BUILD_DIR%\" (\n\tsetlocal EnableDelayedExpansion\n\tECHO Build system already initialized in %BUILD_DIR%\n\tSET /P resp=\"Do you want to remove it (this is IMMEDIATE and PERMANENT), y/n? \"\n\tIF \"!resp!\" == \"y\" (\n\t\trd /s /q \"%BUILD_DIR%\"\n\t) ELSE (\n\t\tECHO Exiting\n\t\tEXIT\n\t)\n)\n\nmkdir \"%BUILD_DIR%\"\ncd \"%BUILD_DIR%\"\ncmake %SRC_DIR% -A x64 %*\n\nECHO ---\nECHO TimescaleDB build system initialized in %BUILD_DIR%.\nECHO To compile, do:\nECHO     cmake --build %BUILD_DIR% --config Release\n"
  },
  {
    "path": "cmake/GenerateScripts.cmake",
    "content": "# Functions to generate downgrade scripts\n\n# generate_downgrade_script(<options>)\n#\n# Create a downgrade script from a source version to a target version. The\n# ScriptFiles.cmake manifest is read from the target version's git tag to\n# determine which files make up the prolog and epilog.\n#\n# SOURCE_VERSION <version>\n#   Version to generate downgrade script from.\n#\n# TARGET_VERSION <version>\n#   Version to generate downgrade script to.\n#\n# OUTPUT_DIRECTORY <dir>\n#   Output directory for script file. Defaults to CMAKE_CURRENT_BINARY_DIR.\n#\n# INPUT_DIRECTORY <dir>\n#   Input directory for downgrade files. Defaults to CMAKE_CURRENT_SOURCE_DIR.\n#\n# FILES <file> ...\n#   Downgrade-specific files to include between the prolog and epilog.\n#\n# The output script concatenates:\n# 1. Prolog from the target version (header.sql, pre-version-change.sql)\n# 2. Downgrade files from the source version\n# 3. Epilog from the target version (function defs, source files, post-update)\n#\n# All files undergo @VARIABLE@ template substitution with MODULE_PATHNAME set\n# to the target version's shared library path.\nfunction(generate_downgrade_script)\n  set(options)\n  set(oneValueArgs SOURCE_VERSION TARGET_VERSION OUTPUT_DIRECTORY\n                   INPUT_DIRECTORY FILES)\n  cmake_parse_arguments(_downgrade \"${options}\" \"${oneValueArgs}\"\n                        \"${multiValueArgs}\" ${ARGN})\n\n  if(NOT _downgrade_OUTPUT_DIRECTORY)\n    set(_downgrade_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})\n  endif()\n\n  if(NOT _downgrade_INPUT_DIRECTORY)\n    set(_downgrade_INPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})\n  endif()\n\n  foreach(_downgrade_file ${_downgrade_FILES})\n    if(NOT EXISTS ${_downgrade_INPUT_DIRECTORY}/${_downgrade_file})\n      message(FATAL_ERROR \"No downgrade file ${_downgrade_file} found!\")\n    endif()\n  endforeach()\n\n  # Fetch manifest with list of files for the prolog and epilog from the target\n  # version.\n  git_versioned_get(VERSION ${_downgrade_TARGET_VERSION} FILES\n                    ${CMAKE_SOURCE_DIR}/cmake/ScriptFiles.cmake)\n\n  # This will include the variables in this scope, but not in the parent scope\n  # so we can read them locally without affecting the parent scope.\n  include(\n    ${CMAKE_BINARY_DIR}/v${_downgrade_TARGET_VERSION}/cmake/ScriptFiles.cmake)\n\n  set(_downgrade_PRE_FILES \"header.sql;${PRE_DOWNGRADE_FILES}\")\n  set(_downgrade_POST_FILES \"${PRE_INSTALL_FUNCTION_FILES};${SOURCE_FILES}\" ${SET_POST_UPDATE_STAGE}\n                            ${POST_UPDATE_FILES} ${UNSET_UPDATE_STAGE})\n\n  # Fetch epilog from target version.\n  git_versioned_get(\n    VERSION\n    ${_downgrade_TARGET_VERSION}\n    FILES\n    ${_downgrade_POST_FILES}\n    RESULT_FILES\n    _epilog_files\n    IGNORE_ERRORS)\n\n  # Assemble the full file list: prolog + downgrade files + epilog\n  set(_files)\n  foreach(_downgrade_file ${_downgrade_PRE_FILES})\n    get_filename_component(_downgrade_filename ${_downgrade_file} NAME)\n    configure_file(${_downgrade_file} ${_downgrade_INPUT_DIRECTORY}/${_downgrade_filename} COPYONLY)\n    list(APPEND _files ${_downgrade_INPUT_DIRECTORY}/${_downgrade_filename})\n  endforeach()\n  foreach(_downgrade_file ${_downgrade_FILES})\n    list(APPEND _files ${_downgrade_INPUT_DIRECTORY}/${_downgrade_file})\n  endforeach()\n  list(APPEND _files ${_epilog_files})\n\n  # Set template variables for the target version\n  set(MODULE_PATHNAME \"$libdir/timescaledb-${_downgrade_TARGET_VERSION}\")\n  set(LOADER_PATHNAME \"$libdir/timescaledb\")\n  set(PROJECT_VERSION_MOD ${PREVIOUS_VERSION})\n\n  # Process all template files and concatenate into the output script\n  set(_script timescaledb--${_downgrade_SOURCE_VERSION}--${_downgrade_TARGET_VERSION}.sql)\n  set(_output ${_downgrade_OUTPUT_DIRECTORY}/${_script})\n  message(STATUS \"Generating script ${_script}\")\n\n  file(WRITE ${_output} \"\")\n  foreach(_file ${_files})\n    configure_file(${_file} ${_file}.gen @ONLY)\n    file(READ ${_file}.gen _contents)\n    file(APPEND ${_output} \"${_contents}\")\n  endforeach()\n\n  install(FILES ${_output} DESTINATION \"${PG_SHAREDIR}/extension\")\nendfunction()\n"
  },
  {
    "path": "cmake/GenerateTestSchedule.cmake",
    "content": "# generate_test_schedul(<output file> ...)\n#\n# A test schedule is generated for the files in TEST_FILES. The test schedule\n# groups the tests into groups of size GROUP_SIZE, with the exceptions of any\n# tests mentioned in the list SOLO, which will be in their own test group.\n#\n# TEST_FILES <file> ...\n#\n# Test files to generate a test schedule for.\n#\n# SOLO <test> ...\n#\n# Names of tests that should be run as solo tests. Note that these are test\n# names, not file names.\n#\n# GROUP_SIZE\n#\n# Size of each group in the test schedule.\nfunction(generate_test_schedule OUTPUT_FILE)\n  set(options)\n  set(oneValueArgs GROUP_SIZE)\n  set(multiValueArgs TEST_FILES SOLO)\n  cmake_parse_arguments(_generate \"${options}\" \"${oneValueArgs}\"\n                        \"${multiValueArgs}\" ${ARGN})\n\n  # Depending on the configuration we might end up\n  # with an empty schedule here. On older cmake versions < 3.14\n  # trying to sort an empty list will produce an error, so\n  # we check for empty list here.\n  if(_generate_TEST_FILES)\n    list(SORT _generate_TEST_FILES)\n  endif()\n  file(REMOVE ${OUTPUT_FILE})\n\n  # We put the solo tests in the test file first. Note that we do not generate\n  # groups for solo tests that are not in the list of test files.\n  foreach(_solo ${_generate_SOLO})\n    if(\"${_solo}.sql\" IN_LIST _generate_TEST_FILES)\n      file(APPEND ${OUTPUT_FILE} \"test: ${_solo}\\n\")\n    endif()\n  endforeach()\n\n  # Generate groups of tests\n  set(_members 0)\n  foreach(_file ${_generate_TEST_FILES})\n    string(REGEX REPLACE \"(.+)\\.sql\" \"\\\\1\" _test ${_file})\n    if(NOT (_test IN_LIST _generate_SOLO))\n      if(_members EQUAL 0)\n        file(APPEND ${OUTPUT_FILE} \"test: \")\n      endif()\n      file(APPEND ${OUTPUT_FILE} \"${_test} \")\n      if(_members LESS _generate_GROUP_SIZE)\n        math(EXPR _members \"${_members} + 1\")\n      else()\n        set(_members 0)\n        file(APPEND ${OUTPUT_FILE} \"\\n\")\n      endif()\n    endif()\n  endforeach()\n  file(APPEND ${OUTPUT_FILE} \"\\n\")\nendfunction()\n"
  },
  {
    "path": "cmake/GitCommands.cmake",
    "content": "# Git helper functions and macros\n\n# git_get_versioned(VERSION <version> FILES <path> ...)\n#\n# Get files by name relative to the current source directory but read it from\n# branch VERSION. Note that the version referenced has to exists as a branch,\n# otherwise you get an error.\n#\n# VERSION <version>\n#\n# Version to read files from. Any object name is accepted as given in\n# gitrevision(7), but typically you should use a tag name.\n#\n# FILES <path> ...\n#\n# Paths to extract. These are relative or absolute paths to files to extract. If\n# relative, the path names are resolved relative the current source directory.\n#\n# RESULT_FILES <var>\n#\n# Variable for the list of the full paths of the retrieved files.\n#\n# IGNORE_ERRORS\n#\n# Ignore errors when fetching file from previous version. This is currently used\n# to ignore missing files, but we should probably be more selective in the\n# following versions.\nfunction(git_versioned_get)\n  set(options IGNORE_ERRORS)\n  set(oneValueArgs VERSION RESULT_FILES)\n  set(multiValueArgs FILES)\n  cmake_parse_arguments(_git_get \"${options}\" \"${oneValueArgs}\"\n                        \"${multiValueArgs}\" ${ARGN})\n\n  execute_process(\n    COMMAND git show-ref --verify --quiet refs/tags/${_git_get_VERSION}\n    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}\n    RESULT_VARIABLE _version_missing)\n  if(_version_missing)\n    message(\n      FATAL_ERROR \"Version ${_git_get_VERSION} does not exist in repository.\")\n  endif()\n\n  set(_result_files)\n  foreach(_file ${_git_get_FILES})\n    # Build full path, if the path is not absolute.\n    if(IS_ABSOLUTE ${_file})\n      set(_path \"${_file}\")\n    else()\n      set(_path \"${CMAKE_CURRENT_SOURCE_DIR}/${_file}\")\n    endif()\n\n    # Remove root source directory to get path relative to source root. This is\n    # necessary for git-show to work correctly and then use that to generate a\n    # full path for the version.  (Yeah, we could use ./ before, but this is\n    # less clear in the log what file is actually fetched.)\n    string(REPLACE ${CMAKE_SOURCE_DIR}/ \"\" _relpath ${_path})\n    set(_fullpath \"${CMAKE_BINARY_DIR}/v${_git_get_VERSION}/${_relpath}\")\n    get_filename_component(_dirname ${_fullpath} DIRECTORY)\n    file(MAKE_DIRECTORY ${_dirname})\n\n    # We place the output file next to the final file to avoid cross-device\n    # renames but give it a different name since it is created even if the\n    # command fails.\n    execute_process(\n      COMMAND ${GIT_EXECUTABLE} show ${_git_get_VERSION}:${_relpath}\n      WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}\n      OUTPUT_FILE \"${_fullpath}.part\"\n      RESULT_VARIABLE _error\n      ERROR_VARIABLE _message)\n\n    if(_error)\n      if(_git_get_IGNORE_ERRORS)\n        string(STRIP \"${_message}\" _stripped_message)\n        message(STATUS \"${_stripped_message} (ignored).\")\n      else()\n        message(FATAL_ERROR \"${_message}\")\n      endif()\n    else()\n      file(RENAME \"${_fullpath}.part\" \"${_fullpath}\")\n      list(APPEND _result_files \"${_fullpath}\")\n    endif()\n  endforeach()\n  set(${_git_get_RESULT_FILES}\n      ${_result_files}\n      PARENT_SCOPE)\nendfunction()\n"
  },
  {
    "path": "cmake/ScriptFiles.cmake",
    "content": "# File defining all variables used to generate script files.\n#\n# This is needed for the downgrade script since files can be added and removed\n# and it is necessary to get a list of all files available for a specific\n# version.\n#\n# We only care about files that are part of generating the prolog or epilog for\n# the update scripts, to the actual versioned files are not necessary to put\n# here.\n\n# Source files that define the schemas and tables for our metadata\nset(PRE_INSTALL_SOURCE_FILES\n    header.sql # Must be first since it sets up the search path\n    pre_install/schemas.sql\n    pre_install/types.pre.sql\n    pre_install/types.functions.sql\n    pre_install/types.post.sql # Must be before tables.sql\n    pre_install/tables.sql\n    pre_install/cache.sql\n    pre_install/insert_data.sql)\n\n# Source files that define functions and need to be rerun in update\nset(PRE_INSTALL_FUNCTION_FILES\n    pre_install/types.functions.sql\n    )\n\n# The rest of the source files defining mostly functions\nset(SOURCE_FILES\n    hypertable.sql\n    chunk.sql\n    util_time.sql\n    util_internal_table_ddl.sql\n    chunk_constraint.sql\n    partitioning.sql\n    ddl_api.sql\n    ddl_triggers.sql\n    bookend.sql\n    time_bucket.sql\n    version.sql\n    size_utils.sql\n    histogram.sql\n    bgw_scheduler.sql\n    metadata.sql\n    uuidv7.sql\n    views.sql\n    views_experimental.sql\n    gapfill.sql\n    compression.sql\n    maintenance_utils.sql\n    restoring.sql\n    job_api.sql\n    policy_api.sql\n    policy_internal.sql\n    cagg_utils.sql\n    job_stat_history_log_retention.sql\n    osm_api.sql\n    compression_defaults.sql\n    sparse_index.sql)\n\nif(ENABLE_DEBUG_UTILS AND CMAKE_BUILD_TYPE MATCHES Debug)\n  list(APPEND SOURCE_FILES debug_build_utils.sql)\nendif()\n\nif(ENABLE_DEBUG_UTILS)\n  list(APPEND SOURCE_FILES debug_utils.sql)\nendif()\n\nif(USE_TELEMETRY)\n  list(APPEND SOURCE_FILES with_telemetry.sql)\nelse()\n  list(APPEND SOURCE_FILES without_telemetry.sql)\nendif()\n\n# Compatibility layer for timescaledb 2.12 for internal functions that got moved into _timescaledb_functions\nlist(APPEND SOURCE_FILES\n  compat.sql)\n\n# These files need to be last in the scripts.\nlist(APPEND SOURCE_FILES\n  bgw_startup.sql)\n\nif(APACHE_ONLY)\n  list(APPEND SOURCE_FILES comment_apache.sql)\nelse()\n  list(APPEND SOURCE_FILES comment_tsl.sql)\nendif()\n\n# These files should be prepended to update scripts so that they are executed\n# before anything else during updates\nset(PRE_UPDATE_FILES updates/pre-version-change.sql updates/pre-update.sql)\nset(PRE_DOWNGRADE_FILES updates/pre-version-change.sql)\n\n# The POST_UPDATE_FILES should be executed as the last part of the update\n# script. sets state for executing POST_UPDATE_FILES during ALTER EXTENSION\nset(SET_POST_UPDATE_STAGE updates/set_post_update_stage.sql)\nset(UNSET_UPDATE_STAGE updates/unset_update_stage.sql)\nset(POST_UPDATE_FILES updates/post-update.sql)\n"
  },
  {
    "path": "coccinelle/README.md",
    "content": "This directory contains scripts to check the codebase for defective\nprogramming patterns, eg use after free or not freeing resources.\n\nCoccinelle is a static code analysis program. It uses a semantic patch\nlanguage which resembles unified diff output. The semantic patches may\ninline python or ocaml code for more advanced use cases.\n\nA coccinelle patch file consists of multiple blocks.\nExample block header:\n```\n@ name @\nExpression var1;\nExpression var2;\n@@\n```\nThe block header may contain variable definitions. It may also contain\nrequired matches or non-matches in previous blocks.\nExamples for blocks with required previous matches:\n```\n@ b2 depends on name @\n@@\n@ b3 depends on name && !b2 @\n@@\n```\nVariables inside a block can also reference matches from previous blocks.\n```\n@ b2 depends on name @\nExpression name.var1;\n@@\n```\nvar1 inside this block will be the match from the `name` block.\n\n- https://github.com/coccinelle/coccinelle\n- https://coccinelle.gitlabpages.inria.fr/website/\n- https://coccinelle.gitlabpages.inria.fr/website/docs/main_grammar.html\n"
  },
  {
    "path": "coccinelle/attrnumbergetattroffset.cocci",
    "content": "//\n// find missing `AttrNumberGetAttrOffset` usage\n//\n// Postgres has `AttrNumberGetAttrOffset()` macro to proper access Datum array members\n// (see https://github.com/postgres/postgres/blob/master/src/include/access/attnum.h).\n//\n// For example:\n//    `datum[attrno - 1]` should be `datum[AttrNumberGetAttrOffset(attrno)]`\n//\n\n@@\ntypedef Datum;\nexpression attrno;\nDatum [] datum;\n@@\n\n- datum[attrno - 1]\n+ /* use AttrNumberGetAttrOffset() for accessing Datum array members */\n+ datum[AttrNumberGetAttrOffset(attrno)]\n\n@@\ntypedef bool;\nexpression attrno;\nbool [] isnull;\n@@\n\n- isnull[attrno - 1]\n+ /* use AttrNumberGetAttrOffset() for accessing bool array members */\n+ isnull[AttrNumberGetAttrOffset(attrno)]\n\n"
  },
  {
    "path": "coccinelle/bms_result.cocci",
    "content": "// Some bitmap set operations recycle one of the input parameters or return\n// a reference to a new bitmap set. The following set of rules checks that the\n// returned reference is not discarded.\n \n@rule_1@\nexpression E1, E2;\n@@\n\n+ /* Result of bms_add_member has to be used */\n+ E1 = bms_add_member(E1, E2);\n- bms_add_member(E1, E2);\n\n@rule_2@\nexpression E1, E2;\n@@\n\n+ /* Result of bms_del_member has to be used */\n+ E1 = bms_del_member(E1, E2);\n- bms_del_member(E1, E2);\n\n@rule_3@\nexpression E1, E2;\n@@\n\n+ /* Result of bms_add_members has to be used */\n+ E1 = bms_add_members(E1, E2);\n- bms_add_members(E1, E2);\n\n@rule_4@\nexpression E1, E2, E3;\n@@\n\n+ /* Result of bms_add_range has to be used */\n+ E1 = bms_add_range(E1, E2, E3);\n- bms_add_range(E1, E2, E3);\n\n@rule_5@\nexpression E1, E2;\n@@\n\n+ /* Result of bms_int_members has to be used */\n+ E1 = bms_int_members(E1, E2);\n- bms_int_members(E1, E2);\n\n@rule_6@\nexpression E1, E2;\n@@\n\n+ /* Result of bms_del_members has to be used */\n+ E1 = bms_del_members(E1, E2);\n- bms_del_members(E1, E2);\n\n@rule_7@\nexpression E1, E2;\n@@\n\n+ /* Result of bms_join has to be used */\n+ E1 = bms_join(E1, E2);\n- bms_join(E1, E2);\n\n@rule_8@\nexpression E1, E2;\n@@\n\n+ /* Result of bms_union has to be used */\n+ E1 = bms_union(E1, E2);\n- bms_union(E1, E2);\n\n@rule_9@\nexpression E1, E2;\n@@\n\n+ /* Result of bms_intersect has to be used */\n+ E1 = bms_intersect(E1, E2);\n- bms_intersect(E1, E2);\n\n@rule_10@\nexpression E1, E2;\n@@\n\n+ /* Result of bms_difference has to be used */\n+ E1 = bms_difference(E1, E2);\n- bms_difference(E1, E2);\n\n"
  },
  {
    "path": "coccinelle/hash_create.cocci",
    "content": "// find hash_create calls without HASH_CONTEXT flag\n//\n// hash_create calls without HASH_CONTEXT flag will create the hash table in\n// TopMemoryContext which can introduce memory leaks if not intended. We want\n// to be explicit about the memory context our hash tables live in so we enforce\n// usage of the flag.\n@ hash_create @\nposition p;\n@@\n\nhash_create@p(...)\n\n@safelist@\nexpression arg1, arg2, arg3;\nexpression w1, w2;\nposition hash_create.p;\n@@\n(\nhash_create@p(arg1,arg2,arg3, w1 | HASH_CONTEXT | w2)\n|\nhash_create@p(arg1,arg2,arg3, w1 | HASH_CONTEXT)\n|\nhash_create@p(arg1,arg2,arg3, HASH_CONTEXT | w2 )\n)\n@safelist2@\nexpression res;\nexpression arg1, arg2, arg3;\nexpression flags;\nposition hash_create.p;\n@@\nAssert(flags & HASH_CONTEXT);\nres = hash_create@p(arg1,arg2,arg3, flags);\n@ depends on !safelist && !safelist2 @\nposition hash_create.p;\n@@\n\n+ /* hash_create without HASH_CONTEXT flag */\n  hash_create@p(...)\n\n"
  },
  {
    "path": "coccinelle/heap_freetuple.cocci",
    "content": "// find heap_form_tuple with missing heap_freetuple calls\n@ heap_form_tuple @\nidentifier tuple;\nposition p;\n@@\n\ntuple@p = heap_form_tuple(...);\n\n@safelist@\nexpression tuple;\nposition heap_form_tuple.p;\n@@\n\ntuple@p = heap_form_tuple(...);\n...\n(\nreturn tuple;\n|\nheap_freetuple(tuple)\n|\nHeapTupleGetDatum(tuple)\n)\n@depends on !safelist@\nexpression tuple;\nposition heap_form_tuple.p;\n@@\n\n+ /* heap_form_tuple with missing heap_freetuple call */\n  tuple@p = heap_form_tuple(...);\n"
  },
  {
    "path": "coccinelle/hypertable_cache.cocci",
    "content": "// find hypertable uses after cache release\n@ cache_get @\nexpression cache, ht, htid, relid, rv, schema, table, flags;\nposition p;\n@@\n(\nht = ts_hypertable_cache_get_entry(cache,...);\n|\nht = ts_hypertable_cache_get_cache_and_entry(relid, flags, &cache);\n|\nht = ts_hypertable_cache_get_entry_rv(cache, rv);\n|\nht = ts_hypertable_cache_get_entry_with_table(cache, relid, schema, table, flags);\n|\nht = ts_hypertable_cache_get_entry_by_id(cache, htid);\n)\n...\nts_cache_release(cache)@p;\n...\nht\n@ safelist1 depends on cache_get @\nexpression cache_get.cache;\nposition cache_get.p;\n@@\nts_cache_release(cache)@p;\n(\nPG_RETURN_DATUM\n|\naclcheck_error(...)\n|\nereport(ERROR,...)\n)\n//\n// if variable gets reassigned it's not use after free\n//\n@ safelist2 depends on cache_get && !safelist1 @\nexpression cache_get.cache;\nexpression cache_get.ht;\nposition cache_get.p;\n@@\nts_cache_release(cache)@p;\n...\n(\nht = ts_hypertable_cache_get_entry(...);\n|\nht = ts_hypertable_cache_get_cache_and_entry(...);\n)\n// print context of matched use after free\n@ match depends on cache_get && !safelist1 && !safelist2 @\nexpression cache_get.cache;\nexpression cache_get.ht;\nposition cache_get.p;\nposition m;\n@@\n* ts_cache_release(cache)@p;\n...\n* ht@m\n"
  },
  {
    "path": "coccinelle/hypertable_cache2.cocci",
    "content": "// find use after free bugs due to premature cache releases\n// this will find bugs of the following form:\n// ht = ts_hypertable_cache_get_entry(cache)\n// dim = hyperspace_get_open_dimension(ht->space, ...)\n// ts_cache_release(cache)\n// usage of dim after cache release\n@ cache_get @\nexpression cache, htid, relid, rv, schema, table, flags;\nidentifier dim, ht;\nposition p;\n@@\n(\nht = ts_hypertable_cache_get_entry(cache,...)\n|\nht = ts_hypertable_cache_get_cache_and_entry(relid, flags, &cache)\n|\nht = ts_hypertable_cache_get_entry_rv(cache, rv)\n|\nht = ts_hypertable_cache_get_entry_with_table(cache, relid, schema, table, flags)\n|\nht = ts_hypertable_cache_get_entry_by_id(cache, htid)\n)\n...\n(\ndim = hyperspace_get_open_dimension(ht->space, ...)\n|\ndim = ts_hyperspace_get_dimension(ht->space, ...)\n|\ndim = ts_hyperspace_get_dimension_by_name(ht->space, ...)\n|\ndim = ts_hyperspace_get_mutable_dimension_by_name(ht->space, ...)\n|\ndim = ts_hyperspace_get_dimension_by_id(ht->space, ...)\n)\n...\nts_cache_release(cache)@p;\n...\ndim\n@ m1 depends on cache_get @\nexpression cache_get.cache;\nposition cache_get.p;\n@@\nts_cache_release(cache)@p;\nPG_RETURN_DATUM\n@ m2 depends on cache_get && !m1 @\nexpression cache_get.cache;\nidentifier cache_get.dim;\nposition cache_get.p;\n@@\n- ts_cache_release(cache)@p;\n...\n+ /* dim used after calling ts_cache_release */\ndim\n"
  },
  {
    "path": "coccinelle/mcxt.cocci",
    "content": "// find MemoryContextSwitchTo missing a context switch back\n@ MemoryContextSwitch @\nlocal idexpression oldctx;\nposition p;\n@@\noldctx@p = MemoryContextSwitchTo(...);\n\n@safelist@\nlocal idexpression MemoryContextSwitch.oldctx;\nposition MemoryContextSwitch.p;\n@@\noldctx@p = MemoryContextSwitchTo(...);\n...\nMemoryContextSwitchTo(oldctx)\n\n@depends on !safelist@\nexpression oldctx;\nposition MemoryContextSwitch.p;\n@@\n+ /* MemoryContextSwitch missing context switch back */\n  oldctx@p = MemoryContextSwitchTo(...);\n"
  },
  {
    "path": "coccinelle/namedata.cocci",
    "content": "// NameData is a fixed-size type of 64 bytes. Using strlcpy to copy data into a\n// NameData struct can cause problems because any data that follows the initial\n// null-terminated string will also be part of the data.\n\n@rule_var_decl_struct@\nsymbol NAMEDATALEN;\nidentifier I1, I2;\n@@\nstruct I1\n{\n  ...\n- char I2[NAMEDATALEN];\n+ /* You are declaring a char of length NAMEDATALEN, please consider using NameData instead. */\n+ NameData I2;\n  ...\n}\n\n@rule_namedata_strlcpy@\nidentifier I1;\nexpression E1;\nsymbol NAMEDATALEN;\n@@\n- strlcpy(I1, E1, NAMEDATALEN);\n+ /* You are using strlcpy with NAMEDATALEN, please consider using NameData and namestrcpy instead. */\n+ namestrcpy(I1, E1);\n\n@rule_namedata_memcpy@\nexpression E1, E2;\nsymbol NAMEDATALEN;\n@@\n- memcpy(E1, E2, NAMEDATALEN);\n+ /* You are using memcpy with NAMEDATALEN, please consider using NameData and namestrcpy instead. */\n+ namestrcpy(E1, E2);\n\n@@\ntypedef NameData;\nNameData E;\n@@\n- E.data\n+ /* Use NameStr rather than accessing data member directly */\n+ NameStr(E)\n\n@@\nNameData *E;\n@@\n- E->data\n+ /* Use NameStr rather than accessing data member directly */\n+ NameStr(*E)\n\n@@\ntypedef Name;\nName E;\n@@\n- E->data\n+ /* Use NameStr rather than accessing data member directly */\n+ NameStr(*E)\n"
  },
  {
    "path": "coccinelle/oidisvalid.cocci",
    "content": "//\n// find comparisons against `InvalidOid`\n//\n// Postgres has `OidIsValid()` macro to check if a given Oid is valid or not\n// (see https://github.com/postgres/postgres/blob/master/src/include/c.h).\n//\n// This script look for comparisons to `InvalidOid` and recommend the usage of\n// `OidIsValid()` instead.\n//\n// For example:\n//    `oid != InvalidOid` should be `OidIsValid(oid)`\n//    `oid == InvalidOid` should be `!OidIsValid(oid)`\n//\n@@\nsymbol InvalidOid;\nexpression oid;\n@@\n\n- (oid != InvalidOid)\n+ /* use OidIsValid() instead of comparing against InvalidOid */\n+ OidIsValid(oid)\n\n@@\nsymbol InvalidOid;\nexpression oid;\n@@\n\n- (InvalidOid != oid)\n+ /* use OidIsValid() instead of comparing against InvalidOid */\n+ OidIsValid(oid)\n\n@@\nsymbol InvalidOid;\nexpression oid;\n@@\n\n- (oid == InvalidOid)\n+ /* use OidIsValid() instead of comparing against InvalidOid */\n+ !OidIsValid(oid)\n\n@@\nsymbol InvalidOid;\nexpression oid;\n@@\n\n- (InvalidOid == oid)\n+ /* use OidIsValid() instead of comparing against InvalidOid */\n+ !OidIsValid(oid)\n\n"
  },
  {
    "path": "coverage/CMakeLists.txt",
    "content": "# CMake targets for generating code coverage reports\n#\n# Note that the targets in here are not needed to enable code coverage on\n# builds. To have builds generate code coverage metadata, one only has to enable\n# the --coverage option for the compiler and this is done in the top-level\n# CMakeLists.txt so that the option covers all build targets in the project.\n#\n# Given that all dependencies (lcov, gcov, genhtml) are installed, and CMake is\n# initialized with -DCODECOVERAGE=ON, it should be possible to generate a code\n# coverage report by running:\n#\n# $ make coverage\n#\n# The report is generated in REPORT_DIR and can be viewed in a web browser.\n\n# If we use clang, prefer the LLVM gcov. The \"normal\" gcov segfaults with the\n# coverage info generated by clang, and the LLVM gcov with GCC-generated\n# coverage gives weird errors like GCOV_TAG_COUNTER_ARCS mismatch.\nset(GCOV_NAMES \"gcov\")\nif(CMAKE_C_COMPILER_ID MATCHES \"Clang|AppleClang\")\n  string(REGEX MATCH \"^[0-9]+\" CMAKE_C_COMPILER_VERSION_MAJOR\n               ${CMAKE_C_COMPILER_VERSION})\n  list(PREPEND GCOV_NAMES \"llvm-cov-${CMAKE_C_COMPILER_VERSION_MAJOR}\")\nendif()\n\nfind_program(GCOV NAMES ${GCOV_NAMES})\n\n# Find lcov for html output\nfind_program(LCOV lcov)\n\nif(NOT GCOV)\n  message(STATUS \"Install gcov to generate code coverage reports\")\nelseif(NOT LCOV)\n  message(STATUS \"Install lcov to generate code coverage reports\")\nelse()\n  execute_process(\n    COMMAND ${LCOV} --version\n    OUTPUT_VARIABLE LCOV_VERSION_OUTPUT\n    RESULT_VARIABLE LCOV_VERSION_RESULT\n    OUTPUT_STRIP_TRAILING_WHITESPACE)\n  if(LCOV_VERSION_RESULT EQUAL 0)\n    # You might need to parse LCOV_VERSION_OUTPUT to extract just the version\n    # number For example, if the output is \"lcov: LCOV version 1.15\", you'd\n    # extract \"1.15\"\n    string(REGEX MATCH \"LCOV version ([0-9]+\\\\.[0-9]+)\" LCOV_VERSION_MATCH\n                 \"${LCOV_VERSION_OUTPUT}\")\n    if(LCOV_VERSION_MATCH)\n      set(LCOV_VERSION ${CMAKE_MATCH_1})\n      message(STATUS \"Using lcov ${LCOV}: ${LCOV_VERSION}\")\n    else()\n      message(\n        FATAL_ERROR\n          \"Could not parse LCOV version from output: ${LCOV_VERSION_OUTPUT}\")\n    endif()\n  else()\n    message(\n      FATAL_ERROR\n        \"Failed to get LCOV version. Error code: ${LCOV_VERSION_RESULT}\")\n  endif()\n\n  message(STATUS \"Using gcov ${GCOV}:\")\n  execute_process(COMMAND ${GCOV} --version)\n\n  # Final tracefile for code coverage\n  set(OUTPUT_FILE \"timescaledb-coverage.info\")\n\n  # Common options for lcov capture commands. We disable branch coverage for\n  # Assert and Ensure, because these are never supposed to be hit and always\n  # have partial coverage. The ereport macro also has some internal conditions\n  # that lead to partial coverage.\n  set(LCOV_CAPTURE_OPTS\n      --rc\n      lcov_branch_coverage=1\n      --rc\n      \"lcov_excl_br_line=Assert\\\\(|Ensure\\\\(|ereport\\\\(\"\n      --no-external\n      --base-directory\n      ${CMAKE_SOURCE_DIR}\n      --directory\n      ${CMAKE_BINARY_DIR}\n      --gcov-tool\n      ${CMAKE_CURRENT_BINARY_DIR}/gcov)\n\n  if(${LCOV_VERSION} VERSION_GREATER_EQUAL \"2.0\")\n    list(APPEND LCOV_CAPTURE_OPTS --ignore-errors=mismatch,mismatch)\n  endif()\n\n  # Directory where to generate the HTML report\n  set(REPORT_DIR \"lcov-report\")\n\n  # We can't directly use llvm-cov as --gcov-tool, because it has to be called\n  # like \"llvm-cov gcov <gcov args>\" for that. Thankfully, if its $0 is gcov, it\n  # will understand that we want it to operate like gcov. Just create a symlink.\n  add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/gcov\n                     COMMAND ln -s ${GCOV} ${CMAKE_CURRENT_BINARY_DIR}/gcov)\n\n  # The baseline run needs to run before tests to learn what zero coverage looks\n  # like\n  add_custom_command(\n    OUTPUT ${OUTPUT_FILE}.base\n    COMMENT \"Generating code coverage base file\"\n    COMMAND ${LCOV} --capture --initial ${LCOV_CAPTURE_OPTS} --output-file\n            ${OUTPUT_FILE}.base\n    DEPENDS timescaledb-tsl timescaledb timescaledb-loader\n            ${CMAKE_CURRENT_BINARY_DIR}/gcov\n    VERBATIM)\n\n  add_custom_target(coverage_base DEPENDS ${OUTPUT_FILE}.base)\n\n  # Ensure baseline file is generated before tests\n  add_dependencies(installcheck coverage_base)\n\n  # The test run needs to run after tests to analyze the test coverage\n  add_custom_command(\n    OUTPUT ${OUTPUT_FILE}.test\n    COMMENT \"Generating code coverage test file\"\n    COMMAND ${LCOV} --capture ${LCOV_CAPTURE_OPTS} --output-file\n            ${OUTPUT_FILE}.test\n    DEPENDS ${OUTPUT_FILE}.base coverage_base\n    VERBATIM)\n\n  # Make sure coverage_test runs after tests (installcheck) finish\n  add_custom_target(coverage_test DEPENDS ${OUTPUT_FILE}.test)\n  add_dependencies(installcheck-post-hook coverage_test)\n\n  # Generate the final coverage file by combining the pre and post test\n  # tracefiles\n  add_custom_command(\n    OUTPUT ${OUTPUT_FILE}\n    COMMENT \"Generating final code coverage file\"\n    COMMAND ${LCOV} ${LCOV_CAPTURE_OPTS} --add-tracefile ${OUTPUT_FILE}.base\n            --add-tracefile ${OUTPUT_FILE}.test --output-file ${OUTPUT_FILE}\n    DEPENDS ${OUTPUT_FILE}.test coverage_test\n    VERBATIM)\n\n  add_custom_target(coverage_final DEPENDS ${OUTPUT_FILE})\n  add_dependencies(coverage_final coverage_test)\n\n  # Look for genhtml to produce HTML report. This tool is part of the lcov\n  # suite, so should be installed if lcov is installed. Thus, this is an\n  # over-cautious check just in case some distributions break these tools up in\n  # separate packages.\n  find_program(GENHTML genhtml)\n\n  if(GENHTML)\n    message(\n      STATUS\n        \"Generate a code coverage report using the 'coverage' target after tests have run.\"\n    )\n\n    add_custom_command(\n      OUTPUT ${REPORT_DIR}/index.html\n      COMMENT\n        \"Generating HTML code coverage report in ${CMAKE_CURRENT_BINARY_DIR}/${REPORT_DIR}\"\n      COMMAND\n        ${GENHTML} --prefix ${CMAKE_SOURCE_DIR} --branch-coverage\n        --ignore-errors source --legend --title \"TimescaleDB\" --output-directory\n        ${REPORT_DIR} ${OUTPUT_FILE}\n      DEPENDS ${OUTPUT_FILE})\n    add_custom_target(coverage DEPENDS ${REPORT_DIR}/index.html)\n    add_dependencies(coverage coverage_final)\n\n    add_custom_command(\n      COMMAND\n        echo\n        \"Open file://${CMAKE_CURRENT_BINARY_DIR}/${REPORT_DIR}/index.html in a browser to view the report\"\n        TARGET coverage POST_BUILD\n      COMMENT)\n  else()\n    message(STATUS \"Install genhtml to generate code coverage reports\")\n  endif(GENHTML)\nendif()\n"
  },
  {
    "path": "coverage/README.md",
    "content": "# Code coverage for TimescaleDB\n\nCode coverage can be enabled for TimescaleDB builds by setting the\noption `-DCODECOVEAGE=ON` when running CMake (it is off by\ndefault). This enables the necessary compiler option (`--coverage`) to\ngenerate code coverage statistics and should be enough for CI build\nreports using, e.g., `codecov.io`. In addition, local code coverage\nreports can be generated with the `lcov` tool, when this tool is\ninstalled on the build system.\n\n\n## Generating local code coverage data files\n\nA code coverage report is generated in three steps using `lcov`:\n\n1. A pre test baseline run to learn what zero coverage looks like (the\n   `coverage_base` target).\n2. A post test run to learn the test coverage (the `coverage_test`\n   target).\n3. A final run to combine the pre test and post test output files into\n   a final data file (the `coverage_final` target).\n\nEach of these steps can be run manually using the mentioned targets,\nbut should happen automatically as part of regular build and test\nsteps. Optionally, this process can be extended with a filtering step\nto ignore certain paths that shouldn't be included in the final\nreport.\n\n## Producing a HTML-based code coverage report\n\nOnce the complete test suite has run (`installcheck` target), it is\npossible to produce a HTML-based code coverage report that can be\nviewed in a web browser. This is automated by the `coverage` target.\nThus, the complete steps to produce a code coverage report are:\n\n1. `cmake --build`\n2. `cmake --build --target installcheck`\n3. `cmake --build --target coverage`\n"
  },
  {
    "path": "docs/BuildSource.md",
    "content": "### Building from source\n\n#### Building from source (Unix-based systems)\n\nIf you are building from source for **non-development purposes**\n(i.e., you want to run TimescaleDB, not submit a patch), you should\n**always use a release-tagged commit and not build from `main`**.\nSee the Releases tab for the latest release.\n\n**Prerequisites**:\n\n- A standard PostgreSQL 15, 16, or 17 installation with development\nenvironment (header files) (e.g., `postgresql-server-dev-17` package\nfor Linux, Postgres.app for MacOS)\n- C compiler (e.g., gcc or clang)\n- [CMake](https://cmake.org/) version 3.15 or greater\n\n```bash\ngit clone git@github.com:timescale/timescaledb.git\ncd timescaledb\n# Find the latest release and checkout, e.g. for 2.15.0:\ngit checkout 2.15.0\n# Bootstrap the build system\n./bootstrap\n# To build the extension\ncd build && make\n# To install\nmake install\n```\n\nNote, if you have multiple versions of PostgreSQL installed you can specify the path to `pg_config`\nthat should be used by using `./bootstrap -DPG_CONFIG=/path/to/pg_config`.\n\nPlease see our [additional configuration instructions](https://docs.timescale.com/self-hosted/latest/install/).\n\n#### Building from source (Windows)\n\nIf you are building from source for **non-development purposes**\n(i.e., you want to run TimescaleDB, not submit a patch), you should\n**always use a release-tagged commit and not build from `main`**.\nSee the Releases tab for the latest release.\n\n**Prerequisites**:\n\n- A standard [PostgreSQL 15, 16, or 17 64-bit installation](https://www.enterprisedb.com/downloads/postgres-postgresql-downloads#windows)\n- OpenSSL for Windows\n- Microsoft Visual Studio 2017 with CMake and Git components\n- OR Visual Studio 2015/2016 with [CMake](https://cmake.org/) version 3.15 or greater and Git\n- Make sure all relevant binaries are in your PATH: `pg_config` and `cmake`\n\nIf using Visual Studio 2017 with the CMake and Git components, you\nshould be able to simply clone the repo and open the folder in\nVisual Studio which will take care of the rest.\n\nIf you are using an earlier version of Visual Studio, then it can\nbe built in the following way:\n```bash\ngit clone git@github.com:timescale/timescaledb.git\ncd timescaledb\n\n# Find the latest release and checkout, e.g. for 2.15.0:\ngit checkout 2.15.0\n# Bootstrap the build system\nbootstrap.bat\n# To build the extension from command line\ncmake --build ./build --config Release\n# To install\ncmake --build ./build --config Release --target install\n\n# Alternatively, build in Visual Studio via its built-in support for\n# CMake or by opening the generated build/timescaledb.sln solution file.\n```\n"
  },
  {
    "path": "docs/MultiNodeDeprecation.md",
    "content": "## Multi-node Deprecation\r\n\r\nMulti-node support has been deprecated.\r\nTimescaleDB 2.13 is the last version that will include multi-node support. Multi-node support in 2.13 is available for PostgreSQL 13, 14 and 15.\r\nThis decision was not made lightly, and we want to provide a clear understanding of the reasoning behind this change and the path forward.\r\n\r\n### Why we are ending multi-node support\r\n\r\nWe began to work on multi-node support in 2018 and released the first version in 2020 to address the growing\r\ndemand for higher scalability in TimescaleDB deployments.\r\nThe distributed architecture of multi-node allowed for horizontal scalability of writes and reads to go beyond what a single node could provide. \r\n\r\nWhile we added many improvements since the initial launch, the architecture of multi-node came with a number of\r\ninherent limitations and challenges that have limited its adoption.\r\nRegrettably, only about 1% of current TimescaleDB deployments utilize multi-node, and the complexity involved in\r\nmaintaining this feature has become a significant obstacle.\r\nIt’s not an isolated feature that can be kept in the product with very little effort since adding new features required\r\nextra development and testing to ensure they also worked for multi-node.\r\n\r\nAs we've evolved our single-node product and expanded our cloud offering to serve thousands of customers,\r\nwe've identified more efficient solutions to meet the scalability needs of our users.\r\n\r\nFirst, we’ve been able and will continue to make big improvements in the write and read performance of single-node. \r\nWe’ve scaled a single-node deployment to process 2 million inserts per second and have seen performance improvements of 10x for common queries.\r\nYou can read a summary of the latest query performance improvements [here](https://www.timescale.com/blog/8-performance-improvements-in-recent-timescaledb-releases-for-faster-query-analytics/).\r\n\r\nAnd second, we are leveraging cloud technologies that have become very mature to provide higher scalability in a more accessible way.\r\nFor example, our cloud offering uses object storage to deliver virtually [infinite storage capacity at a very low cost](https://www.timescale.com/blog/scaling-postgresql-for-cheap-introducing-tiered-storage-in-timescale/).\r\n\r\nFor those reasons, we’ve decided to focus our efforts on improving single-node and leveraging cloud technologies to solve for high scalability and as a result we’ve ended support for multi-node.\r\n\r\n### What this means for you\r\n\r\nWe understand that this change may raise questions, and we are committed to supporting you through the transition.\r\n\r\nFor current TimescaleDB multi-node users, please refer to our [migration documentation](https://docs.timescale.com/migrate/latest/multi-node-to-timescale-service/)\r\nfor a step-by-step guide to transition to a single-node configuration.\r\n\r\nAlternatively, you can continue to use multi-node up to version 2.13. However, please be aware that future versions will no longer include this functionality.\r\n\r\nIf you have any questions or feedback, you can share them in the #multi-node channel in our [community Slack](https://slack.timescale.com/).\r\n\r\n"
  },
  {
    "path": "docs/StyleGuide.md",
    "content": "# TimescaleDB code style guide\n\nSource code should follow the\n[PostgreSQL coding conventions](https://www.postgresql.org/docs/current/static/source.html). This\nincludes\n[source formatting](https://www.postgresql.org/docs/current/static/source-format.html)\nwith 4 column tab spacing and layout rules according to the BSD style.\n\n## SQL and PL/pgSQL style\n\nThere is no official SQL or PL/pgSQL style guide for PostgreSQL that\nwe are aware of, apart from the general spacing and layout rules\nabove. For now, try to follow the style of the surrounding code when\nmaking modifications. We might develop more stringent guidelines in\nthe future.\n\n## Error messages\n\nError messages in TimescaleDB should obey the PostgreSQL\n[error message style guide](https://www.postgresql.org/docs/current/static/error-style-guide.html).\n\n## C style\n\nWhile the PostgreSQL project mandates that\n[C code adheres to the C89 standard](https://www.postgresql.org/docs/current/static/source-conventions.html)\nwith some exceptions, we've chosen to allow many C99 features for the\nclarity and convenience they provide. PostgreSQL sticks with C89\nmostly for compatibility with many older compilers and platforms, such\nas Visual Studio on Windows. Visual Studio should support most of C99\nnowadays, however. We might revisit this decision in the future if it\nturns out to be a problem for important platforms.\n\nUnfortunately, PostgreSQL does not have a consistent style for naming\nof functions, variables and types. This\n[mailing-list thread](https://www.postgresql.org/message-id/1221125165.5637.12.camel%40abbas-laptop)\nelaborates on the situation. Instead, we've tried our best to develop\na consistent style that roughly follows the style of the PostgreSQL\nsource code.\n\n### Declarations before code\n\nC99 supports having code before a declaration, similar to C++, and we\nalso support this in the code. For instance, the following code\nfollows the guidelines:\n\n```C\nvoid some_function()\n{\n    prepare();\n\tint result = fetch();\n\t...\n}\n```\n\n### Function and variable names\n\nFor clarity and consistency, we've chosen to go with lowercase\nunder-score separated names for functions and variables. For instance,\nthis piece of code is correct:\n\n```C\nstatic void\nmy_function_name(int my_parameter)\n{\n    int my_variable;\n    ...\n}\n```\n\nwhile this one is wrong:\n\n```C\nstatic void\nMyFunctionName(int myParameter)\n{\n    int myVariable;\n    ...\n}\n```\n\n### Type declarations\n\nNew composite/aggregate types should be typedef'd and use\nUpperCamelCase naming. For instance, the following is correct:\n\n```C\ntypedef struct MyType\n{\n    ...\n} MyType;\n```\n\nwhile the following is wrong:\n\n```C\ntypedef struct my_type\n{\n    ...\n} my_type;\n```\n\n### Modular code and namespacing\n\nWhen possible, code should be grouped into logical modules. Such modules\ntypically resemble classes in object-oriented programming (OOP)\nlanguages and should use namespaced function and variable names that\nhave the module name as prefix. TimescaleDB's [Cache](../src/cache.c)\nimplementation is a good example of such a module where one would use\n\n```C\nvoid\ncache_initialize(Cache *c)\n{\n    ...\n}\n```\n\nrather than\n\n```C\nvoid\ninitialize_cache(Cache *c)\n{\n\n}\n```\n\n### Object-orientated programming style\n\nEven though C is not an object-oriented programming language, it is\nfairly straight-forward to write C code with an OOP flavor. While we\ndo not mandate that C code has an OOP flavor, we recommend it when it\nmakes sense (e.g., to achieve modularity and code reuse).\n\nFor example, TimescaleDB's [cache.c](../src/cache.c) module can be\nseen as a _base class_ with multiple derived _subclasses_, such as\n[hypertable_cache.c](../src/hypertable_cache.c) and\n[chunk_cache.c](../src/chunk_cache.c). Here's another example of\nsubclassing using shapes:\n\n```C\ntypedef struct Shape\n{\n    int color;\n    void (*draw)(Shape *, Canvas *);\n} Shape;\n\nvoid\nshape_draw(Shape *shape)\n{\n    /* open canvas for drawing */\n    Canvas *c = canvas_open();\n\n    /* other common shape code */\n    ...\n\n    shape->draw(shape, c);\n\n    canvas_close(c);\n}\n\ntypedef struct Circle\n{\n    Shape shape;\n    float diameter;\n} Circle;\n\nCircle blue_circle = {\n    .shape = {\n        .color = BLUE,\n        .draw = circle_draw,\n    },\n    .diameter = 10.1,\n};\n\nvoid\ncircle_draw(Shape *shape, Canvas *canvas)\n{\n    Circle *circle = (Circle *) shape;\n\n    /* draw circle */\n    ...\n}\n\n```\n\nThere are a couple of noteworthy take-aways from this\nexample.\n\n* Non-static \"member\" methods should take a pointer to the\nobject as first argument and be namespaced as described above.\n* Derived modules can expand the original type using struct embedding,\nin which case the \"superclass\" should be the first member of the\n\"subclass\", so that one can easily upcast a `Circle` to a `Shape` or,\nvice-versa, downcast as in `circle_draw()` above.\n* C++-style virtual functions can be implemented with functions\npointers. Good use cases for such functions are module-specific\ninitialization and cleanup, or function overriding in a subclass.\n\n### Other noteworthy recommendations\n\n* Prefer static allocation and/or stack-allocated variables over\n  heap/dynamic allocation when possible. Fortunately, PostgreSQL has a\n  nice `MemoryContext` implementation that helps with heap allocation\n  when needed.\n* Try to minimize the number of included headers in source files,\n  especially when header files include other headers. Avoid circular\n  header dependencies by predeclaring types (or use struct pointers).\n\n\nFor a general guide to writing C functions in PostgreSQL, you can\nreview the section on\n[C-language functions](https://www.postgresql.org/docs/current/static/xfunc-c.html)\nin the PostgreSQL documentation.\n\n## Tools and editors\n\nWe require running C code through clang-format before submitting a PR.\nThis will ensure your code is properly formatted according to our style\n(which is similar to the PostgreSQL style but implement in clang-format).\nYou can run clang-format on all of the TimescaleDB code using `make format`\nif you have clang-format (version >= 7) or docker installed.\n\n\nThe following\n[Wiki post](https://wiki.postgresql.org/wiki/Developer_FAQ#What.27s_the_formatting_style_used_in_PostgreSQL_source_code.3F)\ncontains links to style configuration files for various editors.\n"
  },
  {
    "path": "docs/getting-started/README.md",
    "content": "# TimescaleDB Examples\n\nThis directory contains complete, standalone examples to help you get started with TimescaleDB using real-world use cases. Each example includes sample data and analytical queries to showcase TimescaleDB's capabilities.\n\n## Available Examples\n\n### [NYC Taxi Data](nyc-taxi/)\nAnalyze New York City taxi trip data to understand transportation patterns. Great for learning about location-based analytics and high-volume time-series data.\n\n**What you'll learn:**\n- Handling high-cardinality data (locations, cab types)\n- Time-series aggregations with `time_bucket()`\n- Segmentation strategies for optimal compression\n- Revenue and usage pattern analysis\n\n**Use cases:** Transportation analytics, ride-sharing platforms, logistics optimization, urban planning\n\n---\n\n### [Financial Market Data](financial-ticks/)\nWork with financial tick and candlestick data for market analysis. Ideal for understanding high-frequency time-series data and multi-timeframe aggregations.\n\n**What you'll learn:**\n- OHLCV (Open, High, Low, Close, Volume) data modeling\n- Creating candlestick aggregations at multiple intervals\n- Continuous aggregates for different timeframes (1min, 5min, 1hour)\n- Real-time market analysis queries\n\n**Use cases:** Trading platforms, market data analysis, portfolio analytics, algorithmic trading\n\n---\n\n### [Application Events with UUIDv7](events-uuidv7/)\nTrack and analyze application events using modern UUIDv7 identifiers. Perfect for understanding event-driven analytics and user behavior tracking.\n\n**What you'll learn:**\n- Using UUIDv7 for time-ordered unique identifiers\n- Efficient time-range queries with `to_uuidv7_boundary()`\n- Session tracking and user analytics\n- Event funnel and conversion analysis\n\n**Use cases:** Application monitoring, user behavior analytics, audit logging, event-driven architectures\n\n---\n\n## Workshops\n\n### [AI Workshop: EV Charging Station Analysis](https://github.com/timescale/TigerData-Workshops/tree/main/AI-Workshop)\nIntegrate PostgreSQL with AI capabilities for managing and analyzing EV charging station data. This workshop demonstrates how to combine time-series data with vector search and AI features.\n\n**What you'll learn:**\n- Integrating TimescaleDB with AI and vector extensions\n- Managing EV charging station time-series data\n- Vector search and similarity queries\n- AI-powered analytics on time-series data\n\n**Use cases:** EV infrastructure management, smart grid analytics, energy optimization, predictive maintenance\n\n---\n\n### [Time-Series Workshop: Financial Data Analysis](https://github.com/timescale/TigerData-Workshops/tree/main/TimeSeries-Workshop-Finance/)\nWork with cryptocurrency tick data and create candlestick charts. Learn advanced time-series analysis techniques for financial markets.\n\n**What you'll learn:**\n- Working with high-frequency cryptocurrency tick data\n- Creating candlestick aggregations and visualizations\n- Advanced time-series analysis patterns\n- Multi-timeframe financial analytics\n\n**Use cases:** Cryptocurrency trading, market analysis, financial data visualization, algorithmic trading\n\n---\n\n## How to Use These Examples\n\nEach example is completely standalone and self-contained. You can use any example as your starting point:\n\n1. **Choose an example** that matches your use case or interests\n2. **Navigate to the example directory** and read the README\n3. **Follow the step-by-step guide** - each example includes:\n   - Complete schema definition (SQL)\n   - Sample data (CSV) included in the repository\n   - Data loading instructions (both direct to columnstore and standard approaches)\n   - Sample analytical queries (SQL)\n   - Detailed explanations of what's happening\n\n4. **Run the queries** and see TimescaleDB's columnstore performance in action\n5. **Adapt to your needs** - use these patterns for your own data\n\n## Quick Start Path\n\n**New to TimescaleDB?** We recommend this path:\n\n1. Start with the [Quick Start Guide](../../README.md#quick-start-with-timescaledb) (10 minutes)\n2. Try the [NYC Taxi](nyc-taxi/) example (20 minutes)\n3. Explore other examples based on your use case\n4. Ready for your data? See [Your Own Data Guide](../your-own-data/)\n\n## Common Patterns Across All Examples\n\nAll examples demonstrate these TimescaleDB features:\n\n- **Hypertables** - Automatic time-based partitioning with `tsdb.hypertable`\n- **Columnstore** - Hybrid row-columnar storage with `tsdb.enable_columnstore=true`\n- **Direct to Columnstore** - Instant analytical performance with `enable_direct_compress_copy`\n- **time_bucket()** - Powerful time-series aggregation function\n- **Compression** - Automatic 90%+ data compression\n- **Optimal indexing** - Best practices for time-series indexes\n\n## Prerequisites\n\nAll examples require:\n- Docker (for running TimescaleDB)\n- A PostgreSQL client (`psql` recommended)\n- 10-30 minutes of your time\n\nEach example works with the same Docker setup. You have two options:\n\n**Option 1: One-line install (Recommended)**\n\n```sh\ncurl -sL https://tsdb.co/start-local | sh\n```\n\nThis command:\n- Downloads and starts TimescaleDB (if not already downloaded)\n- Exposes PostgreSQL on port **6543** (a non-standard port to avoid conflicts with other PostgreSQL instances on port 5432)\n- Automatically tunes settings for your environment using timescaledb-tune\n- Sets up a persistent data volume\n\n**Option 2: Manual Docker command**\n\n```bash\ndocker run -d --name timescaledb \\\n    -p 6543:5432 \\\n    -e POSTGRES_PASSWORD=password \\\n    timescale/timescaledb-ha:pg18\n```\n\n**Note:** We use port **6543** (mapped to container port 5432) to avoid conflicts if you have other PostgreSQL instances running on the standard port 5432.\n\n## Example Selection Guide\n\n**Choose your example based on your use case:**\n\n| Your Use Case | Recommended Example |\n|--------------|---------------------|\n| Location-based services, GPS tracking | [NYC Taxi](nyc-taxi/) |\n| Financial trading, market data | [Financial Ticks](financial-ticks/) |\n| Application logs, user events | [Events with UUIDv7](events-uuidv7/) |\n| Cryptocurrency, volatile markets | [Time-Series Workshop: Financial Data Analysis](https://github.com/timescale/TigerData-Workshops/tree/main/TimeSeries-Workshop-Finance/) |\n| AI/ML with time-series data | [AI Workshop: EV Charging Station Analysis](https://github.com/timescale/TigerData-Workshops/tree/main/AI-Workshop) |\n| General time-series analytics | Start with [NYC Taxi](nyc-taxi/) |\n\n## What Makes These Examples Different?\n\n- **Completely standalone** - No dependencies between examples\n- **Sample data included** - CSV files in the repo, ready to load\n- **Production-ready patterns** - Real-world schema designs and query patterns\n- **Instant performance** - Direct to columnstore examples for immediate results\n- **Copy-paste friendly** - All code works as-is in `psql`\n- **Explained thoroughly** - Comments and documentation explain the \"why\"\n\n## Next Steps\n\nAfter trying these examples:\n\n1. **Bring your own data** - See [Your Own Data Guide](../your-own-data/)\n2. **Learn advanced features** - Explore [TimescaleDB Documentation](https://docs.timescale.com)\n3. **Production deployment** - Check out [Timescale Cloud](https://www.timescale.com/cloud) for managed hosting\n4. **Join the community** - Get help in [Timescale Community Forums](https://www.timescale.com/forum)\n\n## Contributing\n\nFound an issue or want to improve an example? Contributions are welcome! Please open an issue or pull request on our [GitHub repository](https://github.com/timescale/timescaledb).\n\n---\n\n**Ready to start?** Pick an example above and dive in!\n"
  },
  {
    "path": "docs/getting-started/events-uuidv7/README.md",
    "content": "# Application Events with UUIDv7 Example\n\nGet started with TimescaleDB using application event data leveraging UUIDv7 identifiers. This example demonstrates how to handle event logging and analytics using time-embedded UUIDs for partitioning—**no separate timestamp column needed!**\n\n## What You'll Learn\n\n- Using UUIDv7 for time-ordered unique identifiers\n- Efficient time-range queries with `to_uuidv7_boundary()`\n- Session tracking and user analytics\n- Event funnel and conversion analysis\n\n## Prerequisites\n\n- Docker installed\n- `psql` PostgreSQL client\n- 15-20 minutes\n\n## Quick Start\n\n### Step 1: Start TimescaleDB\n\nYou have two options to start TimescaleDB:\n\n#### Option 1: One-line install (Recommended)\n\nThe easiest way to get started:\n\n> **Important:** This script is intended for local development and testing only. Do **not** use it for production deployments. For production-ready installation options, see the [TimescaleDB installation guide](https://docs.timescale.com/self-hosted/latest/install/).\n\n**Linux/Mac:**\n\n```sh\ncurl -sL https://tsdb.co/start-local | sh\n```\n\nThis command:\n- Downloads and starts TimescaleDB (if not already downloaded)\n- Exposes PostgreSQL on port **6543** (a non-standard port to avoid conflicts with other PostgreSQL instances on port 5432)\n- Automatically tunes settings for your environment using timescaledb-tune\n- Sets up a persistent data volume\n\n#### Option 2: Manual Docker command also used for Windows\n\nAlternatively, you can run TimescaleDB directly with Docker:\n\n```bash\ndocker run -d --name timescaledb \\\n    -p 6543:5432 \\\n    -e POSTGRES_PASSWORD=password \\\n    timescale/timescaledb-ha:pg18\n```\n\n**Note:** We use port **6543** (mapped to container port 5432) to avoid conflicts if you have other PostgreSQL instances running on the standard port 5432.\n\nWait about 1-2 minutes for TimescaleDB to download & initialize.\n\n### Step 2: Connect to TimescaleDB\n\nConnect using `psql`:\n\n```bash\npsql -h localhost -p 6543 -U postgres\n# When prompted, enter password: password\n```\n\nYou should see the PostgreSQL prompt. Verify TimescaleDB is installed:\n\n```sql\nSELECT extname, extversion FROM pg_extension WHERE extname = 'timescaledb';\n```\n\nExpected output:\n```\n   extname   | extversion\n-------------+------------\n timescaledb | 2.x.x\n```\n\n**Prefer a GUI?** If you'd rather use a graphical tool instead of the command line, you can download [pgAdmin](https://www.pgadmin.org/download/) and connect to TimescaleDB using the same connection details (host: `localhost`, port: `6543`, user: `postgres`, password: `password`).\n\n### Step 3: Create the Schema\n\nCreate the optimized hypertable by running this SQL in your `psql` session:\n\n```sql\n-- Create the app_events table with UUIDv7 partitioning\n-- Note: No separate timestamp column needed - the timestamp is embedded in the UUIDv7!\n-- Note: No PRIMARY KEY to allow direct compress during COPY\nCREATE TABLE IF NOT EXISTS app_events (\n    event_id UUID NOT NULL,\n    user_id UUID NOT NULL,\n    session_id UUID NOT NULL,\n    event_type TEXT NOT NULL,\n    event_name TEXT,\n    device_type TEXT,\n    country_code TEXT,\n    category TEXT,\n    page_path TEXT,\n    referrer TEXT,\n    viewport_width INTEGER,\n    element_id TEXT,\n    position_x INTEGER,\n    position_y INTEGER,\n    product_id TEXT,\n    quantity INTEGER,\n    revenue_cents INTEGER\n) WITH (\n    tsdb.hypertable,\n    tsdb.partition_column = 'event_id',\n    tsdb.segmentby = 'user_id'\n);\n\n-- Create index on event_id for lookups (not unique to allow direct compress)\nCREATE INDEX idx_app_events_event_id ON app_events(event_id);\n```\n\nThis creates an `app_events` table with:\n- UUIDv7 partitioning on `event_id` (time is embedded in the UUID)\n- Segmentation by `user_id` for optimal compression\n- No separate timestamp column needed!\n\n### Step 4: Load Sample Data\n\nFirst, download and decompress the sample data:\n\n```bash\n# Download the sample data\nwget https://assets.timescale.com/timescaledb-datasets/events_uuid.csv.gz\n\n# Decompress the CSV file\ngunzip events_uuid.csv.gz\n\n# This will create events_uuid.csv ready for loading\n```\n\nWe provide two approaches for loading data. Choose based on your needs:\n\n#### Option A: Direct to Columnstore (Recommended - Instant Performance)\n\nThis approach writes data directly to the columnstore, bypassing the rowstore entirely. You get instant analytical performance.\n\n**From command line:**\n\n```bash\npsql -h localhost -p 6543 -U postgres \\\n  -v ON_ERROR_STOP=1 \\\n  -c \"SET timescaledb.enable_direct_compress_copy = on;\n      COPY app_events FROM STDIN WITH (FORMAT csv, HEADER true);\" \\\n  < events_uuid.csv\n```\n\nThis command reads the CSV file from your local filesystem and pipes it to PostgreSQL, which loads it directly into the columnstore.\n\n**Verify data loaded:**\n\n```sql\nSELECT COUNT(*) FROM app_events;\n```\n\n#### Option B: Standard COPY (Fallback)\n\nThis approach loads data into the rowstore first. Data will be converted to the columnstore by a background policy (12-24 hours) for faster querying.\n\n**From command line:**\n\n```bash\npsql -h localhost -p 6543 -U postgres \\\n  -v ON_ERROR_STOP=1 \\\n  -c \"COPY app_events FROM STDIN WITH (FORMAT csv, HEADER true);\" \\\n  < events_uuid.csv\n```\n\n**Verify data loaded:**\n\n```sql\nSELECT COUNT(*) FROM app_events;\n```\n\n**Manually convert to columnstore (Optional):**\n\nIf you loaded data using standard copy a background process will convert your rowstore data to the columnstore in 12-24 hours, you can manually convert it immediately to get the best query performance:\n\n```sql\nDO $$\nDECLARE ch TEXT;\nBEGIN\n    FOR ch IN SELECT show_chunks('app_events') LOOP\n        CALL convert_to_columnstore(ch);\n    END LOOP;\nEND $$;\n```\n\n### Step 5: Run Sample Queries\n\nNow let's explore the data with some analytical queries. Run these in your `psql` session:\n\n**Query 1: Efficient Time Range Query (Chunk Pruning)**\n```sql\n-- ✅ CORRECT: Uses boundary function for chunk exclusion\n\\timing on\nSELECT COUNT(*), event_type\nFROM app_events\nWHERE event_id >= to_uuidv7_boundary(now() - interval '7 days')\nGROUP BY event_type;\n```\n\n**Why this is fast:** The `to_uuidv7_boundary()` function creates a UUID boundary value that TimescaleDB can use to exclude entire chunks without scanning them.\n\n**Query 2: Inefficient Query (Anti-Pattern)**\n```sql\n-- ❌ WRONG: Scans ALL chunks, extracts timestamp from every row\nSELECT COUNT(*), event_type\nFROM app_events\nWHERE uuid_timestamp(event_id) >= now() - interval '7 days'\nGROUP BY event_type;\n```\n\n**Why this is slow:** The `uuid_timestamp()` function must be evaluated for every row, preventing chunk exclusion.\n\n**Query 3: SkipScan on Single Column (Distinct Users)**\n```sql\n-- Demonstrates SkipScan optimization: uses compression index to skip repeated values\n\\timing on\nSELECT DISTINCT ON (user_id)\n    user_id,\n    event_type,\n    uuid_timestamp(event_id) as event_time\nFROM app_events\nWHERE event_id >= to_uuidv7_boundary(now() - interval '30 days')\nORDER BY user_id, event_id DESC\nLIMIT 50;\n```\n\n**Why this uses SkipScan:** Since `user_id` is the segmentby column, TimescaleDB automatically creates a compression index on it. SkipScan can jump directly to the next unique `user_id` value instead of scanning all rows. The WHERE clause ensures chunk exclusion (only scans chunks with recent data), making it even faster.\n\n**Verify SkipScan is used:** Check the query plan with `EXPLAIN` - you should see `Custom Scan (SkipScan)` on the compressed chunks instead of a sequential scan.\n\n**Query 4: Funnel Analysis**\n```sql\nWITH funnel AS (\n    SELECT\n        user_id,\n        session_id,\n        MAX(CASE WHEN event_type = 'page_view' THEN 1 ELSE 0 END) as viewed,\n        MAX(CASE WHEN event_type = 'add_to_cart' THEN 1 ELSE 0 END) as added_to_cart,\n        MAX(CASE WHEN event_type = 'purchase' THEN 1 ELSE 0 END) as purchased\n    FROM app_events\n    WHERE event_id >= to_uuidv7_boundary(now() - interval '30 days')\n    GROUP BY user_id, session_id\n)\nSELECT\n    SUM(viewed) as sessions_with_view,\n    SUM(added_to_cart) as sessions_with_cart,\n    SUM(purchased) as sessions_with_purchase,\n    ROUND(100.0 * SUM(added_to_cart) / NULLIF(SUM(viewed), 0), 2) as view_to_cart_pct,\n    ROUND(100.0 * SUM(purchased) / NULLIF(SUM(added_to_cart), 0), 2) as cart_to_purchase_pct\nFROM funnel;\n```\n\n**Query 5: Revenue by Country (Last 30 Days)**\n```sql\nSELECT\n    country_code,\n    COUNT(*) as purchase_count,\n    SUM(revenue_cents) / 100.0 as total_revenue,\n    ROUND(AVG(revenue_cents) / 100.0, 2) as avg_order_value\nFROM app_events\nWHERE event_id >= to_uuidv7_boundary(now() - interval '30 days')\n  AND event_type = 'purchase'\nGROUP BY country_code\nORDER BY total_revenue DESC\nLIMIT 10;\n```\n\n## What's Happening Behind the Scenes?\n\n### UUIDv7 Partitioning\n\nWhen you create a table with `tsdb.partition_column = 'event_id'` where `event_id` is a UUIDv7:\n- TimescaleDB automatically partitions your data by the time-embedded UUID\n- **No separate timestamp column needed** - the timestamp is embedded in the UUID itself\n- Chunk exclusion works with `to_uuidv7_boundary()` for efficient time-range queries\n- Vectorized UUID compression provides 30% storage savings and 2x query performance\n\n### Columnstore Compression\n\nWith `tsdb.enable_columnstore=true`:\n- Data is stored in a hybrid row-columnar format\n- Analytical queries only scan the columns they need (massive speedup)\n- Typical compression ratios: 90%+ for time-series data\n- Compression happens transparently - no changes to your queries\n\n### Direct to Columnstore\n\nWhen you use `SET timescaledb.enable_direct_compress_copy = on`:\n- Data loads directly into compressed columnstore format\n- Bypasses the rowstore entirely\n- Instant analytical performance - no waiting for background compression\n\n### Segmentation\n\nThe `tsdb.segmentby='user_id'` setting:\n- Groups data by user within each chunk\n- Improves compression ratios (similar data together)\n- Speeds up queries that filter by user_id\n- Better for user-based analytics\n\n### UUIDv7 Functions\n\nTimescaleDB provides comprehensive UUIDv7 functionality across **all supported PostgreSQL versions** (including PostgreSQL 15, 16, and 17), while PostgreSQL only provides UUIDv7 support in PostgreSQL 18. \n\nFor complete documentation on all available UUIDv7 functions, see the [UUIDv7 Functions API Reference](https://www.tigerdata.com/docs/api/latest/uuid-functions). \n\n## Continuous Aggregates (Advanced)\n\nFor real-time dashboards, you can create continuous aggregates that automatically update:\n\n```sql\n-- Create a continuous aggregate for hourly event statistics\nCREATE MATERIALIZED VIEW app_events_hourly\nWITH (timescaledb.continuous) AS\nSELECT\n    time_bucket('1 hour', uuid_timestamp(event_id)) AS hour,\n    event_type,\n    COUNT(*) as event_count,\n    COUNT(DISTINCT user_id) as unique_users,\n    SUM(revenue_cents) / 100.0 as total_revenue\nFROM app_events\nGROUP BY hour, event_type;\n\n-- Add a refresh policy to keep it updated\nSELECT add_continuous_aggregate_policy('app_events_hourly',\n    start_offset => INTERVAL '2 hours',\n    end_offset => INTERVAL '1 hour',\n    schedule_interval => INTERVAL '1 hour');\n```\n\nNow you can query `app_events_hourly` for instant results on pre-aggregated data.\n\n## Troubleshooting\n\n### Data didn't load\n- Check the CSV file path is correct\n- Ensure the CSV header matches the schema columns\n- Try loading a few rows first to test: `LIMIT 10` in your data file\n\n### Direct to columnstore not working\n- Verify TimescaleDB version 2.24+: `SELECT extversion FROM pg_extension WHERE extname = 'timescaledb';`\n- Ensure you ran `SET timescaledb.enable_direct_compress_copy = on;` in the same session\n- Check for error messages in the output\n\n### Queries seem slow\n- Verify you're using `to_uuidv7_boundary()` for time-range queries (not `uuid_timestamp()`)\n- Check if data is compressed: `SELECT * FROM timescaledb_information.chunks WHERE hypertable_name = 'app_events';`\n- Ensure chunk exclusion is working: Use `EXPLAIN ANALYZE` to see chunk pruning\n\n### UUIDv7 functions not found\n- Verify TimescaleDB version supports UUIDv7: `SELECT extversion FROM pg_extension WHERE extname = 'timescaledb';`\n- UUIDv7 support requires TimescaleDB 2.24+ with the uuidv7 extension enabled\n\n## Use Cases\n\nThis Application Events example demonstrates patterns applicable to:\n\n- **SaaS analytics** - User behavior tracking, feature usage, conversion funnels\n- **E-commerce** - Shopping cart analysis, purchase patterns, product recommendations\n- **Application monitoring** - Error tracking, performance metrics, user sessions\n- **Audit logging** - Security events, compliance tracking, change history\n- **Event-driven architectures** - Microservices event sourcing, message queues\n- **A/B testing** - Experiment tracking, variant analysis, statistical significance\n\n## Files in This Example\n\n- [`README.md`](README.md) - This file\n- Sample data files (to be added)\n\n## Clean Up\n\nWhen you're done experimenting:\n\n#### If you used the one-line install:\n\n```bash\n# Stop the container\ndocker stop timescaledb-ha-pg18-quickstart\n\n# Remove the container\ndocker rm timescaledb-ha-pg18-quickstart\n\n# Remove the persistent data volume\ndocker volume rm timescaledb_data\n\n# (Optional) Remove the Docker image\ndocker rmi timescale/timescaledb-ha:pg18\n```\n\n#### If you used the manual Docker command:\n\n```bash\n# Stop the container\ndocker stop timescaledb\n\n# Remove the container\ndocker rm timescaledb\n\n# (Optional) Remove the Docker image\ndocker rmi timescale/timescaledb-ha:pg18\n```\n\n**Note:** If you created a named volume with the manual Docker command, you can remove it with `docker volume rm <volume_name>`.\n\n---\n\n**Questions?** Check out [TimescaleDB Documentation](https://docs.timescale.com) or the [TimescaleDB Community Forums](https://www.timescale.com/forum).\n"
  },
  {
    "path": "docs/getting-started/financial-ticks/README.md",
    "content": "# Financial Market Data Example\n\nThis example will demonstrate financial tick and candlestick data analysis with TimescaleDB.\nThe datasets corresponds to the stocks listed in the [S&P 500 index](https://en.wikipedia.org/wiki/List_of_S%26P_500_companies), with fictive prices and movements.\nThe tick cadence is per second, over three business days, containing approx 35 million records in total and 503 tickers.\n\n## Use Cases\n\nTrading platforms, market data analysis, portfolio analytics, algorithmic trading\n\n## What You'll Learn\n\n- OHLCV (Open, High, Low, Close, Volume) data modeling\n- Candlestick aggregations at multiple intervals\n- Continuous aggregates for different timeframes (1min, 5min, 1hour)\n- Real-time market analysis queries\n\n## Dataset Preview\n\n* timestamp\n* ticker\n* price\n* price delta\n* change percentage\n* volume\n\n```csv\n2025-11-12 14:30:00+00:00,NVDA,38.25,0.25,0.65359,5095712\n2025-11-12 14:30:00+00:00,AAPL,152.03,0.03,0.01973,6466554\n2025-11-12 14:30:00+00:00,MSFT,129.23,0.23,0.17798,4417848\n2025-11-12 14:30:00+00:00,GOOG,174.93,-0.07,-0.04002,19602229\n2025-11-12 14:30:00+00:00,GOOGL,71.21,0.21,0.2949,3149482\n2025-11-12 14:30:00+00:00,AMZN,95.95,-0.05,-0.05211,12150474\n2025-11-12 14:30:00+00:00,AVGO,196.15,0.15,0.07647,6166047\n2025-11-12 14:30:00+00:00,META,133.22,0.22,0.16514,12230004\n2025-11-12 14:30:00+00:00,TSLA,82.0,0.0,0.0,10298937\n2025-11-12 14:30:00+00:00,BRK.B,56.84,-0.16,-0.28149,10980047\n```\n\n## Prerequisites\n\n- Docker installed\n- `psql` PostgreSQL client\n- 15-20 minutes\n\n## Quick Start\n\nFirst download the dataset:\n\n```sh\ncurl -L https://assets.timescale.com/timescaledb-datasets/sp500_stock_prices_3d_1s.tar.gz | tar -xzf - \n```\n\n### Step 1: Start TimescaleDB\n\nYou have two options to start TimescaleDB:\n\n#### Option 1: One-line install (Recommended)\n\nThe easiest way to get started:\n\n> **Important:** This script is intended for local development and testing only. Do **not** use it for production deployments. For production-ready installation options, see the [TimescaleDB installation guide](https://docs.timescale.com/self-hosted/latest/install/).\n\n**Linux/Mac:**\n\n```sh\ncurl -sL https://tsdb.co/start-local | sh\n```\n\nThis command:\n- Downloads and starts TimescaleDB (if not already downloaded)\n- Exposes PostgreSQL on port **6543** (a non-standard port to avoid conflicts with other PostgreSQL instances on port 5432)\n- Automatically tunes settings for your environment using timescaledb-tune\n- Sets up a persistent data volume\n\n#### Option 2: Manual Docker command also used for Windows\n\nAlternatively, you can run TimescaleDB directly with Docker:\n\n```bash\ndocker run -d --name timescaledb \\\n    -p 6543:5432 \\\n    -e POSTGRES_PASSWORD=password \\\n    timescale/timescaledb-ha:pg18\n```\n\n**Note:** We use port **6543** (mapped to container port 5432) to avoid conflicts if you have other PostgreSQL instances running on the standard port 5432.\n\nWait about 1-2 minutes for TimescaleDB to download & initialize.\n\n### Step 2: Connect to TimescaleDB\n\nConnect using `psql`:\n\n```sh\npsql \"postgres://postgres:password@localhost:6543/postgres\"\n```\n\nYou should see the PostgreSQL prompt. Verify TimescaleDB is installed:\n\n```sql\nSELECT extname, extversion FROM pg_extension WHERE extname = 'timescaledb';\n```\n\nExpected output:\n```\n   extname   | extversion\n-------------+------------\n timescaledb | 2.x.x\n```\n\n**Prefer a GUI?** If you'd rather use a graphical tool instead of the command line, you can download [pgAdmin](https://www.pgadmin.org/download/) and connect to TimescaleDB using the same connection details (host: `localhost`, port: `6543`, user: `postgres`, password: `password`).\n\n### Step 3: Create the Schema\n\nCreate the optimized hypertable by running this SQL in your `psql` session:\n\n```sql\n-- Create the stock_prices table with the column ts as partitioning\n-- Note: for optimized query performance grouping on ticker, we select this column to segment by\nCREATE TABLE stock_prices (\n  ts                TIMESTAMPTZ         NOT NULL,\n  ticker            TEXT                NOT NULL,\n  price             DOUBLE PRECISION    NOT NULL,\n  change_delta      DOUBLE PRECISION    NOT NULL,\n  change_percentage DOUBLE PRECISION    NOT NULL,\n  volume            BIGINT NOT NULL CHECK (volume >= 0)\n)\nWITH (\n  timescaledb.hypertable,\n  timescaledb.segmentby='ticker'\n);\n```\n\nThis creates a `stock_prices` table with:\n- partitioned by timestamp on column `ts`\n- Segmentation by `ticker` for optimal compression and query performance\n\n### Step 4: Load Sample Data into TimescaleDB\n\nThis approach writes data directly to the columnstore, bypassing the rowstore entirely. You get instant analytical performance.\n\n**From psql:**\n\n```sql\n-- Enable direct to columnstore for this session\nSET timescaledb.enable_direct_compress_copy = on;\n\n-- Load data directly into columnstore (if you have a CSV file)\n\\COPY stock_prices FROM 'sp500_stock_prices_3d_1s.csv' WITH (FORMAT csv, HEADER true);\n\n-- Verify data loaded\nSELECT COUNT(*) FROM stock_prices;\n```\n\n### Step 5: Run Sample Queries\n\nNow let's explore the data with some analytical queries. Run these in your `psql` session:\n\n```sql\n-- Activate time measuring\n\\timing on\n```\n\n**Query 1: OHLCV per hour of AAPL**\n\nAggregating raw 1 second tick data into 15-minute \"candlesticks\" (Open, High, Low, Close, Volume) of the ticker `AAPL`.\n\n```sql\nSELECT\n    time_bucket('1 hour', ts) AS hour_bucket,\n    ticker,\n    FIRST(price, ts) AS open_price,\n    MAX(price) AS high_price,\n    MIN(price) AS low_price,\n    LAST(price, ts) AS close_price,\n    AVG(price) AS avg_price,\n    SUM(volume) AS sum_volume\nFROM\n    stock_prices\nWHERE \n    ticker = 'AAPL'\nGROUP BY\n    hour_bucket,\n    ticker\nORDER BY\n    hour_bucket DESC;\n```\n\n**Query 2: Trend Analysis: Simple Moving Average (SMA) of MSFT**\n\nCalculating a \"smoothing\" line to see trends over noise for the ticker `MSFT` over 4 hours.\n\n```sql\nWITH candles AS (\n  SELECT\n    time_bucket('1 hour', ts) AS bucket,\n    ticker,\n    LAST(price, ts) AS close_price\n  FROM stock_prices\n  WHERE ticker = 'MSFT'\n  GROUP BY bucket, ticker\n)\nSELECT\n  bucket,\n  ticker,\n  close_price,\n  AVG(close_price) OVER (\n    PARTITION BY ticker \n    ORDER BY bucket \n    ROWS BETWEEN 3 PRECEDING AND CURRENT ROW\n  ) AS sma_4hours\nFROM candles\nORDER BY bucket DESC;\n```\n\n**Query 3: Hour-over-Hour Return**\n\nComparing the current price to the price exactly one hour ago to calculate percentage growth.\n\n```sql\nWITH hourly_close AS (\n  SELECT\n    time_bucket('1 hour', ts) AS bucket,\n    ticker,\n    LAST(price, ts) AS closing_price\n  FROM stock_prices\n  GROUP BY bucket, ticker\n)\nSELECT\n  bucket,\n  ticker,\n  closing_price,\n  LAG(closing_price, 1) OVER (PARTITION BY ticker ORDER BY bucket) AS prev_close,\n  ((closing_price - LAG(closing_price, 1) OVER (PARTITION BY ticker ORDER BY bucket)) \n   / LAG(closing_price, 1) OVER (PARTITION BY ticker ORDER BY bucket)) * 100 AS hourly_return_pct\nFROM hourly_close;\n```\n\n**Query 4: Price volatility**\n\n```sql\nSELECT\n  ticker,\n  AVG(price) AS avg_price,\n  STDDEV(price) AS price_volatility,\n  MAX(price) - MIN(price) AS price_spread\nFROM stock_prices\nWHERE ts > NOW() - INTERVAL '7 days'\nGROUP BY ticker\nHAVING count(*) > 10\nORDER BY price_volatility DESC;\n```\n\n## What's Happening Behind the Scenes?\n\n### Columnstore Compression\n\nWith `tsdb.enable_columnstore=true`:\n- Data is stored in a hybrid row-columnar format\n- Analytical queries only scan the columns they need (massive speedup)\n- Typical compression ratios: 90%+ for time-series data\n- Compression happens transparently - no changes to your queries\n\n### Direct to Columnstore\n\nWhen you use `SET timescaledb.enable_direct_compress_copy = on`:\n- Data loads directly into compressed columnstore format\n- Bypasses the rowstore entirely\n- Instant analytical performance - no waiting for background compression\n\n### Segmentation\n\nThe `tsdb.segmentby='ticker'` setting:\n- Groups data by user within each chunk\n- Improves compression ratios (similar data together)\n- Speeds up queries that filter by ticker\n- Better for ticker-based analytics\n\n## Continuous Aggregates (Advanced)\n\nFor real-time dashboards, you can create continuous aggregates that automatically update:\n\n```sql\n-- Create a continuous aggregate for hourly candlesticks\nCREATE MATERIALIZED VIEW candlesticks_hourly\nWITH (timescaledb.continuous) AS\nSELECT\n    time_bucket('1 hour', ts) AS hour,\n    ticker,\n    FIRST(price, ts) AS open_price,\n    MAX(price) AS high_price,\n    MIN(price) AS low_price,\n    LAST(price, ts) AS close_price,\n    AVG(price) AS avg_price,\n    SUM(volume) AS sum_volume\nFROM stock_prices\nGROUP BY hour, ticker\nORDER BY hour DESC, ticker ASC;\n\n-- Add a refresh policy to keep it updated\nSELECT add_continuous_aggregate_policy('candlesticks_hourly',\n    start_offset => INTERVAL '2 hours',\n    end_offset => INTERVAL '1 hour',\n    schedule_interval => INTERVAL '1 hour');\n```\n\nNow you can query `candlesticks_hourly` for instant results on pre-aggregated data.\n\n```sql\nSELECT * from candlesticks_hourly WHERE ticker = 'NFLX';\n```\n\n---\n\n**Questions?** Check out [TimescaleDB Documentation](https://docs.timescale.com) or the [TimescaleDB Community Forums](https://www.timescale.com/forum).\n"
  },
  {
    "path": "docs/getting-started/nyc-taxi/README.md",
    "content": "# NYC Taxi Data Example\n\nGet started with TimescaleDB using New York City taxi trip data. This example demonstrates how to handle high-volume transportation data with location-based analytics and time-series aggregations.\n\n## What You'll Learn\n\n- How to model high-volume transportation data with lat/lon coordinates\n- Time-series aggregations with `time_bucket()`\n- Optimal segmentation strategies for compression\n- Revenue and usage pattern analysis\n- Loading data with direct to columnstore for instant performance\n\n## Prerequisites\n\n- Docker installed\n- `psql` PostgreSQL client\n- 15-20 minutes\n\n## Quick Start\n\n### Step 1: Start TimescaleDB\n\nYou have two options to start TimescaleDB:\n\n#### Option 1: One-line install (Recommended)\n\nThe easiest way to get started:\n\n> **Important:** This script is intended for local development and testing only. Do **not** use it for production deployments. For production-ready installation options, see the [TimescaleDB installation guide](https://docs.timescale.com/self-hosted/latest/install/).\n\n**Linux/Mac:**\n\n```sh\ncurl -sL https://tsdb.co/start-local | sh\n```\n\nThis command:\n- Downloads and starts TimescaleDB (if not already downloaded)\n- Exposes PostgreSQL on port **6543** (a non-standard port to avoid conflicts with other PostgreSQL instances on port 5432)\n- Automatically tunes settings for your environment using timescaledb-tune\n- Sets up a persistent data volume\n\n#### Option 2: Manual Docker command also used for Windows\n\nAlternatively, you can run TimescaleDB directly with Docker:\n\n```bash\ndocker run -d --name timescaledb \\\n    -p 6543:5432 \\\n    -e POSTGRES_PASSWORD=password \\\n    timescale/timescaledb-ha:pg18\n```\n\n**Note:** We use port **6543** (mapped to container port 5432) to avoid conflicts if you have other PostgreSQL instances running on the standard port 5432.\n\nWait about 1-2 minutes for TimescaleDB to download & initialize.\n\n### Step 2: Connect to TimescaleDB\n\nConnect using `psql`:\n\n```bash\npsql -h localhost -p 6543 -U postgres\n# When prompted, enter password: password\n```\n\nYou should see the PostgreSQL prompt. Verify TimescaleDB is installed:\n\n```sql\nSELECT extname, extversion FROM pg_extension WHERE extname = 'timescaledb';\n```\n\nExpected output:\n```\n   extname   | extversion\n-------------+------------\n timescaledb | 2.x.x\n```\n\n**Prefer a GUI?** If you'd rather use a graphical tool instead of the command line, you can download [pgAdmin](https://www.pgadmin.org/download/) and connect to TimescaleDB using the same connection details (host: `localhost`, port: `6543`, user: `postgres`, password: `password`).\n\n### Step 3: Create the Schema\n\nCreate the optimized hypertable by running this SQL in your `psql` session:\n\n```sql\n-- Create the hypertable with optimal settings for NYC Taxi data\n-- This automatically enables columnstore for fast analytical queries\nCREATE TABLE trips (\n    vendor_id TEXT,\n    pickup_boroname VARCHAR,\n    pickup_datetime TIMESTAMP WITHOUT TIME ZONE NOT NULL,\n    dropoff_datetime TIMESTAMP WITHOUT TIME ZONE NOT NULL,\n    passenger_count NUMERIC,\n    trip_distance NUMERIC,\n    pickup_longitude NUMERIC,\n    pickup_latitude NUMERIC,\n    rate_code INTEGER,\n    dropoff_longitude NUMERIC,\n    dropoff_latitude NUMERIC,\n    payment_type VARCHAR,\n    fare_amount NUMERIC,\n    extra NUMERIC,\n    mta_tax NUMERIC,\n    tip_amount NUMERIC,\n    tolls_amount NUMERIC,\n    improvement_surcharge NUMERIC,\n    total_amount NUMERIC\n) WITH (\n    tsdb.hypertable,\n    tsdb.partition_column='pickup_datetime',\n    tsdb.enable_columnstore=true,\n    tsdb.segmentby='pickup_boroname',\n    tsdb.orderby='pickup_datetime DESC'\n);\n\n-- Create indexes \nCREATE INDEX idx_trips_pickup_time ON trips (pickup_datetime DESC);\nCREATE INDEX idx_trips_borough_time ON trips (pickup_boroname, pickup_datetime DESC);\n```\n\nThis creates a `trips` table with:\n- Automatic time-based partitioning on `pickup_datetime`\n- Columnstore enabled for fast analytical queries\n- Segmentation by `pickup_boroname` for optimal compression (6 boroughs)\n- Full trip details including fares, distances, and coordinates\n\n### Step 4: Load Sample Data\n\nFirst, download and decompress the sample data:\n\n```bash\n# Download the sample data\nwget https://assets.timescale.com/timescaledb-datasets/nyc_taxi_sample_nov_dec_2015.csv.gz\n\n# Decompress the CSV file\ngunzip nyc_taxi_sample_nov_dec_2015.csv.gz\n\n# This will create nyc_taxi_sample_nov_dec_2015.csv ready for loading\n```\n\nWe provide two approaches for loading data. Choose based on your needs:\n\n#### Option A: Direct to Columnstore (Recommended - Instant Performance)\n\nThis approach writes data directly to the columnstore, bypassing the rowstore entirely. You get instant analytical performance.\n\n**From command line:**\n\n```bash\npsql -h localhost -p 6543 -U postgres \\\n  -v ON_ERROR_STOP=1 \\\n  -c \"SET timescaledb.enable_direct_compress_copy = on;\n      COPY trips FROM STDIN WITH (FORMAT csv, HEADER true);\" \\\n  < nyc_taxi_sample_nov_dec_2015.csv\n```\n\nThis command reads the CSV file from your local filesystem and pipes it to PostgreSQL, which loads it directly into the columnstore.\n\n**Verify data loaded:**\n\n```sql\nSELECT COUNT(*) FROM trips;\n```\n\n#### Option B: Standard COPY (Fallback)\n\nThis approach loads data into the rowstore first. Data will be converted to the columnstore by a background policy (12-24 hours) for faster querying.\n\n**From command line:**\n\n```bash\npsql -h localhost -p 6543 -U postgres \\\n  -v ON_ERROR_STOP=1 \\\n  -c \"COPY trips FROM STDIN WITH (FORMAT csv, HEADER true);\" \\\n  < nyc_taxi_sample_nov_dec_2015.csv\n```\n\n**Verify data loaded:**\n\n```sql\nSELECT COUNT(*) FROM trips;\n```\n\n**Manually convert to columnstore (Optional):**\n\nIf you loaded data using standard copy a background process will convert your rowstore data to the columnstore in 12-24 hours, you can manually convert it immediately to get the best query performance:\n\n```sql\nDO $$\nDECLARE ch TEXT;\nBEGIN\n    FOR ch IN SELECT show_chunks('trips') LOOP\n        CALL convert_to_columnstore(ch);\n    END LOOP;\nEND $$;\n```\n\n### Step 5: Run Sample Queries\n\nNow let's explore the data with some analytical queries. Run these in your `psql` session:\n\n**Query 1: Overall statistics**\n```sql\n\\timing on\n\nSELECT\n    COUNT(*) as total_trips,\n    ROUND(SUM(fare_amount)::numeric, 2) as total_revenue,\n    ROUND(AVG(fare_amount)::numeric, 2) as avg_fare,\n    ROUND(AVG(trip_distance)::numeric, 2) as avg_distance\nFROM trips;\n```\n\n**Query 2: Breakdown by vendor**\n```sql\nSELECT\n    vendor_id,\n    COUNT(*) as trips,\n    ROUND(AVG(fare_amount)::numeric, 2) as avg_fare,\n    ROUND(AVG(tip_amount)::numeric, 2) as avg_tip,\n    ROUND(AVG(passenger_count)::numeric, 2) as avg_passengers\nFROM trips\nGROUP BY vendor_id\nORDER BY trips DESC;\n```\n\n**Query 3: Hourly patterns using time_bucket**\n```sql\nSELECT\n    time_bucket('1 hour', pickup_datetime) AS hour,\n    COUNT(*) as trips,\n    ROUND(AVG(fare_amount)::numeric, 2) as avg_fare,\n    ROUND(SUM(tip_amount)::numeric, 2) as total_tips\nFROM trips\nGROUP BY hour\nORDER BY hour DESC\nLIMIT 24;\n```\n\n**Query 4: Payment type analysis**\n```sql\nSELECT\n    payment_type,\n    COUNT(*) as trip_count,\n    ROUND(SUM(fare_amount)::numeric, 2) as total_revenue,\n    ROUND(AVG(trip_distance)::numeric, 2) as avg_distance,\n    ROUND(AVG(tip_amount)::numeric, 2) as avg_tip\nFROM trips\nGROUP BY payment_type\nORDER BY total_revenue DESC;\n```\n\n**Query 5: Daily statistics by borough**\n```sql\nSELECT\n    time_bucket('1 day', pickup_datetime) AS day,\n    pickup_boroname,\n    COUNT(*) as trips,\n    ROUND(AVG(fare_amount)::numeric, 2) as avg_fare,\n    ROUND(MAX(fare_amount)::numeric, 2) as max_fare\nFROM trips\nGROUP BY day, pickup_boroname\nORDER BY day DESC, pickup_boroname\nLIMIT 20;\n```\n\n**Query 6: Trips by distance category**\n```sql\nSELECT\n    CASE\n        WHEN trip_distance < 1 THEN 'Short (< 1 mile)'\n        WHEN trip_distance < 5 THEN 'Medium (1-5 miles)'\n        WHEN trip_distance < 10 THEN 'Long (5-10 miles)'\n        ELSE 'Very Long (> 10 miles)'\n    END as distance_category,\n    COUNT(*) as trips,\n    ROUND(AVG(fare_amount)::numeric, 2) as avg_fare,\n    ROUND(AVG(tip_amount)::numeric, 2) as avg_tip\nFROM trips\nGROUP BY distance_category\nORDER BY trips DESC;\n```\n\n## What's Happening Behind the Scenes?\n\n### Hypertables\nWhen you create a table with `tsdb.hypertable`, TimescaleDB automatically:\n- Partitions your data into time-based chunks (default: 7 days per chunk)\n- Manages chunk lifecycle automatically\n- Optimizes queries to scan only relevant chunks\n\n### Columnstore Compression\nWith `tsdb.enable_columnstore=true`:\n- Data is stored in a hybrid row-columnar format\n- Analytical queries only scan the columns they need (massive speedup)\n- Typical compression ratios: 90%+ for time-series data\n- Compression happens transparently - no changes to your queries\n\n### Direct to Columnstore\nWhen you use `SET timescaledb.enable_direct_compress_copy = on`:\n- Data loads directly into compressed columnstore format\n- Bypasses the rowstore entirely\n- Instant analytical performance - no waiting for background compression\n- Perfect for bulk data loads and migrations\n\n### Segmentation\nThe `tsdb.segmentby='pickup_boroname'` setting:\n- Groups data by pickup borough within each chunk (6 unique values: Manhattan, Brooklyn, Queens, Bronx, Staten Island, EWR)\n- Improves compression ratios (similar data together)\n- Speeds up queries that filter by pickup_boroname\n- Better cardinality than vendor_id (6 values vs 2) for optimal compression\n- Automatically optimized without manual tuning\n\n### time_bucket() Function\nTimescaleDB's `time_bucket()` is like PostgreSQL's `date_trunc()` but more powerful:\n- Works with any interval: `5 minutes`, `1 hour`, `1 day`, etc.\n- Optimized for time-series queries\n- Integrates seamlessly with continuous aggregates\n- Essential for time-series analytics\n\n## Sample Queries Explained\n\nSee [`nyc-taxi-queries.sql`](nyc-taxi-queries.sql) for the complete set of queries. Each query demonstrates:\n\n1. **Total trips and revenue** - Simple aggregations across all data\n2. **Breakdown by vendor** - Segmentation analysis by taxi vendor\n3. **Hourly patterns** - Using `time_bucket()` for time-based aggregation\n4. **Payment type analysis** - Analyzing payment methods\n5. **Daily statistics** - Multi-dimensional aggregation (time + borough)\n6. **Distance categories** - CASE statement with aggregations\n\n## Schema Design Choices\n\n### Why these settings?\n\n**partition_column='pickup_datetime'**\n- Time is the natural partition key for time-series data\n- Enables automatic chunk pruning for time-range queries\n- Default chunk interval (7 days) works well for taxi data\n\n**segmentby='pickup_boroname'**\n- Optimal cardinality with 6 borough values (Manhattan, Brooklyn, Queens, Bronx, Staten Island, EWR)\n- Frequently used in WHERE clauses and GROUP BY for location-based analytics\n- Improves compression by grouping geographically similar trips\n- Better than vendor_id (only 2 values) for compression efficiency\n\n**orderby='pickup_datetime DESC'**\n- Most queries want recent data first\n- Optimizes for \"latest trips\" queries\n- Improves query performance for time-range scans\n\n## Continuous Aggregates (Advanced)\n\nFor real-time dashboards, you can create continuous aggregates that automatically update:\n\n```sql\n-- Create a continuous aggregate for hourly statistics by borough\nCREATE MATERIALIZED VIEW trips_hourly\nWITH (timescaledb.continuous) AS\nSELECT\n    time_bucket('1 hour', pickup_datetime) AS hour,\n    pickup_boroname,\n    COUNT(*) as trip_count,\n    AVG(fare_amount) as avg_fare,\n    SUM(fare_amount) as total_revenue,\n    AVG(trip_distance) as avg_distance\nFROM trips\nGROUP BY hour, pickup_boroname;\n\n-- Add a refresh policy to keep it updated\nSELECT add_continuous_aggregate_policy('trips_hourly',\n    start_offset => INTERVAL '2 hours',\n    end_offset => INTERVAL '1 hour',\n    schedule_interval => INTERVAL '1 hour');\n```\n\nNow you can query `trips_hourly` for instant results on pre-aggregated data.\n\n\n## Troubleshooting\n\n### Data didn't load\n- Check the CSV file path is correct\n- Ensure the CSV header matches the schema columns\n- Try loading a few rows first to test: `LIMIT 10` in your data file\n\n### Direct to columnstore not working\n- Verify TimescaleDB version 2.24+: `SELECT extversion FROM pg_extension WHERE extname = 'timescaledb';`\n- Ensure you ran `SET timescaledb.enable_direct_compress_copy = on;` in the same session\n- Check for error messages in the output\n\n### Queries seem slow\n- Verify columnstore is enabled: `SELECT * FROM timescaledb_information.hypertables WHERE hypertable_name = 'trips';`\n- Check if data is compressed: `SELECT * FROM timescaledb_information.chunks WHERE hypertable_name = 'trips';`\n- Ensure you're querying with time ranges (enables chunk exclusion)\n\n### Out of memory during load\n- Reduce batch size in COPY command\n- Increase Docker memory allocation\n- Consider loading data in smaller time-range batches\n\n## Use Cases\n\nThis NYC Taxi example demonstrates patterns applicable to:\n\n- **Ride-sharing platforms** - Track trips, drivers, pricing\n- **Fleet management** - Vehicle tracking, route optimization\n- **Delivery services** - Order tracking, delivery times, driver analytics\n- **Public transportation** - Route analysis, passenger counts, schedule optimization\n- **Urban planning** - Traffic patterns, popular routes, demand forecasting\n- **Logistics** - Shipment tracking, route efficiency, cost analysis\n\n## Clean Up\n\nWhen you're done experimenting:\n\n#### If you used the one-line install:\n\n```bash\n# Stop the container\ndocker stop timescaledb-ha-pg18-quickstart\n\n# Remove the container\ndocker rm timescaledb-ha-pg18-quickstart\n\n# Remove the persistent data volume\ndocker volume rm timescaledb_data\n\n# (Optional) Remove the Docker image\ndocker rmi timescale/timescaledb-ha:pg18\n```\n\n#### If you used the manual Docker command:\n\n```bash\n# Stop the container\ndocker stop timescaledb\n\n# Remove the container\ndocker rm timescaledb\n\n# (Optional) Remove the Docker image\ndocker rmi timescale/timescaledb-ha:pg18\n```\n\n**Note:** If you created a named volume with the manual Docker command, you can remove it with `docker volume rm <volume_name>`.\n\n## Contributing\n\nFound an issue or want to improve this example? Contributions welcome! Open an issue or PR on [GitHub](https://github.com/timescale/timescaledb).\n\n---\n\n**Questions?** Check out [Timescale Community Forums](https://www.timescale.com/forum) or [TimescaleDB Documentation](https://docs.timescale.com).\n"
  },
  {
    "path": "docs/getting-started/nyc-taxi/nyc-taxi-queries.sql",
    "content": "-- Sample analytical queries for NYC Taxi dataset\n-- These showcase TimescaleDB's columnstore performance\n\n\\echo '=== Sample Queries for NYC Taxi Data ==='\n\\echo ''\n\n-- Query 1: Total trips and revenue\n\\echo 'Query 1: Overall statistics'\nSELECT\n    COUNT(*) as total_trips,\n    SUM(fare_amount) as total_revenue,\n    AVG(fare_amount) as avg_fare,\n    AVG(trip_distance) as avg_distance\nFROM trips;\n\n-- Query 2: Trips by vendor\n\\echo ''\n\\echo 'Query 2: Breakdown by vendor'\nSELECT\n    vendor_id,\n    COUNT(*) as trips,\n    AVG(fare_amount) as avg_fare,\n    AVG(tip_amount) as avg_tip,\n    AVG(passenger_count) as avg_passengers\nFROM trips\nGROUP BY vendor_id\nORDER BY trips DESC;\n\n-- Query 3: Hourly patterns using time_bucket\n\\echo ''\n\\echo 'Query 3: Hourly trip patterns (using time_bucket)'\nSELECT\n    time_bucket('1 hour', pickup_datetime) AS hour,\n    COUNT(*) as trips,\n    AVG(fare_amount) as avg_fare,\n    SUM(tip_amount) as total_tips\nFROM trips\nGROUP BY hour\nORDER BY hour DESC\nLIMIT 24;\n\n-- Query 4: Payment type analysis\n\\echo ''\n\\echo 'Query 4: Payment type analysis'\nSELECT\n    payment_type,\n    COUNT(*) as trip_count,\n    SUM(fare_amount) as total_revenue,\n    AVG(trip_distance) as avg_distance,\n    AVG(tip_amount) as avg_tip\nFROM trips\nGROUP BY payment_type\nORDER BY total_revenue DESC;\n\n-- Query 5: Daily aggregation with time_bucket\n\\echo ''\n\\echo 'Query 5: Daily statistics by borough'\nSELECT\n    time_bucket('1 day', pickup_datetime) AS day,\n    pickup_boroname,\n    COUNT(*) as trips,\n    AVG(fare_amount) as avg_fare,\n    MAX(fare_amount) as max_fare\nFROM trips\nGROUP BY day, pickup_boroname\nORDER BY day DESC, pickup_boroname\nLIMIT 20;\n\n-- Query 6: Distance-based analysis\n\\echo ''\n\\echo 'Query 6: Trips by distance category'\nSELECT\n    CASE\n        WHEN trip_distance < 1 THEN 'Short (< 1 mile)'\n        WHEN trip_distance < 5 THEN 'Medium (1-5 miles)'\n        WHEN trip_distance < 10 THEN 'Long (5-10 miles)'\n        ELSE 'Very Long (> 10 miles)'\n    END as distance_category,\n    COUNT(*) as trips,\n    AVG(fare_amount) as avg_fare,\n    AVG(tip_amount) as avg_tip\nFROM trips\nGROUP BY distance_category\nORDER BY trips DESC;\n\n\\echo ''\n\\echo '=== Query examples complete! ==='\n\\echo 'Notice how fast these analytical queries run on compressed columnar data.'\n"
  },
  {
    "path": "docs/getting-started/nyc-taxi/nyc-taxi-sample.csv",
    "content": "vendor_id,pickup_boroname,pickup_datetime,dropoff_datetime,passenger_count,trip_distance,pickup_longitude,pickup_latitude,rate_code,dropoff_longitude,dropoff_latitude,payment_type,fare_amount,extra,mta_tax,tip_amount,tolls_amount,improvement_surcharge,total_amount\n# Placeholder: This file will contain ~1000 rows of NYC taxi trip data\n# Sample row format: CMT,Manhattan,2024-01-15 08:30:00,2024-01-15 08:45:00,1,2.5,-73.9812,40.7685,1,-73.9580,40.7784,1,12.50,0.50,0.50,2.50,0.00,0.30,16.30\n"
  },
  {
    "path": "docs/getting-started/nyc-taxi/nyc-taxi-schema.sql",
    "content": "-- TimescaleDB NYC Taxi Example Schema\n--\n-- This schema demonstrates optimal design for high-volume transportation data.\n-- NYC Taxi data includes timestamps, locations, fares, and trip details.\n\n-- Enable timing to show query performance\n\\timing on\n\n-- Create the hypertable with optimal settings for NYC Taxi data\n-- This automatically enables columnstore for fast analytical queries\nCREATE TABLE trips (\n    vendor_id TEXT,\n    pickup_boroname VARCHAR,\n    pickup_datetime TIMESTAMP WITHOUT TIME ZONE NOT NULL,\n    dropoff_datetime TIMESTAMP WITHOUT TIME ZONE NOT NULL,\n    passenger_count NUMERIC,\n    trip_distance NUMERIC,\n    pickup_longitude NUMERIC,\n    pickup_latitude NUMERIC,\n    rate_code INTEGER,\n    dropoff_longitude NUMERIC,\n    dropoff_latitude NUMERIC,\n    payment_type VARCHAR,\n    fare_amount NUMERIC,\n    extra NUMERIC,\n    mta_tax NUMERIC,\n    tip_amount NUMERIC,\n    tolls_amount NUMERIC,\n    improvement_surcharge NUMERIC,\n    total_amount NUMERIC\n) WITH (\n    tsdb.hypertable,\n    tsdb.partition_column='pickup_datetime',\n    tsdb.enable_columnstore=true,\n    tsdb.segmentby='pickup_boroname',\n    tsdb.orderby='pickup_datetime DESC'\n);\n\n-- Create indexes for common query patterns on rowstore data\n-- Note: These indexes primarily help with uncompressed rowstore data.\n-- Columnstore queries use internal structures (min/max stats) for pruning.\nCREATE INDEX idx_trips_pickup_time ON trips (pickup_datetime DESC);\nCREATE INDEX idx_trips_borough_time ON trips (pickup_boroname, pickup_datetime DESC);\n\n-- Add helpful table comments\nCOMMENT ON TABLE trips IS 'NYC Taxi trip data with automatic partitioning and columnstore compression';\nCOMMENT ON COLUMN trips.vendor_id IS 'Taxi vendor ID';\nCOMMENT ON COLUMN trips.pickup_boroname IS 'Pickup borough name (Manhattan, Brooklyn, Queens, Bronx, Staten Island, EWR)';\nCOMMENT ON COLUMN trips.pickup_datetime IS 'Timestamp when the trip started';\nCOMMENT ON COLUMN trips.dropoff_datetime IS 'Timestamp when the trip ended';\nCOMMENT ON COLUMN trips.passenger_count IS 'Number of passengers';\nCOMMENT ON COLUMN trips.trip_distance IS 'Trip distance in miles';\nCOMMENT ON COLUMN trips.pickup_longitude IS 'Pickup location longitude';\nCOMMENT ON COLUMN trips.pickup_latitude IS 'Pickup location latitude';\nCOMMENT ON COLUMN trips.rate_code IS 'Rate code for the trip';\nCOMMENT ON COLUMN trips.dropoff_longitude IS 'Dropoff location longitude';\nCOMMENT ON COLUMN trips.dropoff_latitude IS 'Dropoff location latitude';\nCOMMENT ON COLUMN trips.payment_type IS 'Payment type (e.g., Credit card, Cash, No charge, Dispute, Unknown, Voided trip)';\nCOMMENT ON COLUMN trips.fare_amount IS 'Base fare amount';\nCOMMENT ON COLUMN trips.extra IS 'Extra charges';\nCOMMENT ON COLUMN trips.mta_tax IS 'MTA tax';\nCOMMENT ON COLUMN trips.tip_amount IS 'Tip amount';\nCOMMENT ON COLUMN trips.tolls_amount IS 'Tolls amount';\nCOMMENT ON COLUMN trips.improvement_surcharge IS 'Improvement surcharge';\nCOMMENT ON COLUMN trips.total_amount IS 'Total trip amount';\n\n\\echo ''\n\\echo '=== NYC Taxi hypertable created successfully! ==='\n\\echo ''\n\\echo 'Table: trips'\n\\echo 'Features enabled:'\n\\echo '  - Automatic time-based partitioning'\n\\echo '  - Columnstore compression for fast analytics'\n\\echo '  - Optimized segmentation by pickup_boroname (6 boroughs)'\n\\echo ''\n\\echo 'Next: Load sample data using nyc-taxi-sample.csv'\n\\echo ''\n"
  },
  {
    "path": "scripts/CMakeLists.txt",
    "content": "find_program(\n  NM\n  NAMES nm\n  PATHS /usr/bin /usr/local/bin /opt/local/bin)\nif(NM)\n  message(STATUS \"Using nm ${NM}\")\nelse()\n  message(STATUS \"Install nm to be able to run export checks\")\nendif(NM)\n\nconfigure_file(export_prefix_check.sh.in export_prefix_check.sh @ONLY)\n"
  },
  {
    "path": "scripts/backport.py",
    "content": "#!/usr/bin/env python3\n\nimport os\nimport random\nimport re\nimport string\nimport subprocess\nimport sys\n\nfrom github import Github  # This is PyGithub.\nimport requests\n\n# Limit our history search and fetch depth to this value, not to get stuck in\n# case of a bug.\nHISTORY_DEPTH = 1000\n\n\ndef run_query(query):\n    \"\"\"A simple function to use requests.post to make the GraphQL API call.\"\"\"\n\n    token = os.environ.get(\"ORG_AUTOMATION_TOKEN\")\n\n    request = requests.post(\n        \"https://api.github.com/graphql\",\n        json={\"query\": query},\n        headers={\"Authorization\": f\"Bearer {token}\"} if token else None,\n        timeout=20,\n    )\n    response = request.json()\n\n    # Have to work around the unique GraphQL convention of returning 200 for errors.\n    if request.status_code != 200 or \"errors\" in response:\n        raise ValueError(\n            f\"Query failed to run by returning code of {request.status_code}.\"\n            f\"\\nQuery: '{query}'\"\n            f\"\\nResponse: '{request.json()}'\"\n        )\n\n    return response\n\n\ndef get_referenced_issue(pr_number):\n    \"\"\"Get the number of issue fixed by the given pull request.\n    Returns None if no issue is fixed, or more than one issue\"\"\"\n\n    # We only need the first issue here. We also request only the first 30 labels,\n    # because GitHub requires some small restriction there that is counted\n    # towards the GraphQL API usage quota.\n    ref_result = run_query(string.Template(\"\"\"\n        query {\n            repository(owner: \"timescale\", name: \"timescaledb\") {\n              pullRequest(number: $pr_number) {\n                closingIssuesReferences(first: 1) {\n                  nodes {\n                      number, title,\n                      labels (first: 30) { nodes { name } }\n                  }\n                }\n              }\n            }\n          }\n          \"\"\").substitute({\"pr_number\": pr_number}))\n\n    # The above returns:\n    # {'data': {'repository': {'pullRequest': {'closingIssuesReferences': {'nodes': [{'number': 6819,\n    #    'title': '[Bug]: Segfault when `ts_insert_blocker` function is called',\n    #    'labels': {'nodes': [{'name': 'bug'}]}}]}}}}}\n    #\n    # We can have {'nodes': [None]} in case it references an inaccessible repository,\n    # just ignore it.\n\n    ref_nodes = ref_result[\"data\"][\"repository\"][\"pullRequest\"][\n        \"closingIssuesReferences\"\n    ][\"nodes\"]\n\n    if not ref_nodes or len(ref_nodes) != 1 or not ref_nodes[0]:\n        return None, None, None\n\n    number = ref_nodes[0][\"number\"]\n    title = ref_nodes[0][\"title\"]\n    labels = {x[\"name\"] for x in ref_nodes[0][\"labels\"][\"nodes\"]}\n\n    return number, title, labels\n\n\ndef set_auto_merge(pr_number):\n    \"\"\"Enable auto-merge for the given PR\"\"\"\n\n    # We first have to find out the PR id, which is some base64 string, different\n    # from its number.\n    query = string.Template(\"\"\"query {\n          repository(owner: \"$owner\", name: \"$name\") {\n            pullRequest(number: $pr_number) {\n              id\n            }\n          }\n        }\"\"\").substitute(\n        pr_number=pr_number, owner=source_repo.owner.login, name=source_repo.name\n    )\n    result = run_query(query)\n    pr_id = result[\"data\"][\"repository\"][\"pullRequest\"][\"id\"]\n\n    query = string.Template(\"\"\"mutation {\n            enablePullRequestAutoMerge(\n                input: {\n                    pullRequestId: \"$pr_id\",\n                    mergeMethod: REBASE\n                }\n            ) {\n                clientMutationId\n            }\n        }\"\"\").substitute(pr_id=pr_id)\n    run_query(query)\n\n\ndef git_output(command):\n    \"\"\"Get output from the git command, checking for the successful exit code\"\"\"\n    return subprocess.check_output(f\"git {command}\", shell=True, text=True)\n\n\ndef git_check(command):\n    \"\"\"Run a git command, checking for the successful exit code\"\"\"\n    subprocess.run(f\"git {command}\", shell=True, check=True)\n\n\ndef git_returncode(command):\n    \"\"\"Run a git command, returning the exit code\"\"\"\n    return subprocess.run(f\"git {command}\", shell=True, check=False).returncode\n\n\n# The token has to have the \"access public repositories\" permission, or else creating a PR returns 404.\ngithub = Github(os.environ.get(\"ORG_AUTOMATION_TOKEN\"))\n\nsource_remote = \"origin\"\nsource_repo_name = os.environ.get(\"GITHUB_REPOSITORY\")  # This is set in GitHub Actions.\nif not source_repo_name:\n    source_repo_name = \"timescale/timescaledb\"\n\nprint(\n    f\"Will look at '{source_repo_name}' (git remote '{source_remote}') for bug fixes.\"\n)\n\nsource_repo = github.get_repo(source_repo_name)\n\n# Fetch the main branch. Apparently the local repo can be shallow in some cases\n# in Github Actions, so specify the depth. --unshallow will complain on normal\n# repositories, this is why we don't use it here.\ngit_check(\n    f\"fetch --quiet --depth={HISTORY_DEPTH} {source_remote} main:refs/remotes/{source_remote}/main\"\n)\n\n# Find out what is the branch corresponding to the previous version compared to\n# main. We will backport to that branch.\nversion_config = dict(\n    [\n        re.match(r\"^(.+)\\s+=\\s+(.+)$\", line).group(1, 2)\n        for line in git_output(f\"show {source_remote}/main:version.config\").splitlines()\n        if line\n    ]\n)\n\nversion = version_config[\"version\"].split(\"-\")[0]  # Split off the 'dev' suffix.\nversion_parts = version.split(\".\")  # Split the three version numbers.\nversion_parts[1] = str(int(version_parts[1]) - 1)\nversion_parts[2] = \"x\"\nbackport_target = \".\".join(version_parts)\nbackported_label = f\"backported-{backport_target}\"\n\nprint(f\"Will backport to {backport_target}.\")\n\n\n# Fetch the target branch. Apparently the local repo can be shallow in some cases\n# in Github Actions, so specify the depth. --unshallow will complain on normal\n# repositories, this is why we don't use it here.\ngit_check(\n    f\"fetch --quiet --depth={HISTORY_DEPTH} {source_remote} {backport_target}:refs/remotes/{source_remote}/{backport_target}\"\n)\n\n# Find out which commits are unique to main and target branch. Also build sets of\n# the titles of these commits. We will compare the titles to check whether a\n# commit was backported.\nmain_commits = [\n    line.split(\"\\t\")\n    for line in git_output(\n        f'log -{HISTORY_DEPTH} --abbrev=12 --pretty=\"format:%h\\t%s\" {source_remote}/{backport_target}..{source_remote}/main'\n    ).splitlines()\n    if line\n]\n\nprint(f\"Have {len(main_commits)} new commits in the main branch.\")\n\nbranch_commits = [\n    line.split(\"\\t\")\n    for line in git_output(\n        f'log -{HISTORY_DEPTH} --abbrev=12 --pretty=\"format:%h\\t%s\" {source_remote}/main..{source_remote}/{backport_target}'\n    ).splitlines()\n    if line\n]\nbranch_commit_titles = {x[1] for x in branch_commits}\n\n\n# We will do backports per-PR, because one PR, though not often, might contain\n# many commits. So as the first step, go through the commits unique to main, find\n# out which of them have to be backported, and remember the corresponding PRs.\n# We also have to remember which commits to backport. The list from PR itself is\n# not what we need, these are the original commits from the PR branch, and we\n# need the resulting commits in master.\nclass PRInfo:\n    \"\"\"Information about the PR to be backported.\"\"\"\n\n    def __init__(self, pygithub_pr_, issue_number_):\n        self.pygithub_pr = pygithub_pr_\n        self.pygithub_commits = []\n        self.issue_number = issue_number_\n\n\ndef should_backport_by_labels(number, title, labels):\n    \"\"\"Should we backport the given PR/issue, judging by the labels?\n    Note that this works in ternary logic:\n    True means we must,\n    False means we must not (tags to disable backport take precedence),\n    and None means weak no (no tags to either request or disable backport)\"\"\"\n    stopper_labels = labels.intersection(\n        [\"disable-auto-backport\", \"auto-backport-not-done\"]\n    )\n    if stopper_labels:\n        print(\n            f\"#{number} '{title}' is labeled as '{list(stopper_labels)[0]}' which prevents automated backporting.\"\n        )\n        return False\n\n    force_labels = labels.intersection([\"bug\", \"force-auto-backport\"])\n    if force_labels:\n        print(\n            f\"#{number} '{title}' is labeled as '{list(force_labels)[0]}' which requests automated backporting.\"\n        )\n        return True\n\n    return None\n\n\n# Go through the commits unique to main, and build a dict(pr number -> PRInfo)\n# of PRs that we will consider for backporting.\nprs_to_backport = {}\nfor commit_sha, commit_title in main_commits:\n    print()\n\n    pygithub_commit = source_repo.get_commit(sha=commit_sha)\n\n    pulls = pygithub_commit.get_pulls()\n    if not pulls or pulls.totalCount == 0:\n        print(f\"{commit_sha[:9]} '{commit_title}' does not belong to a PR.\")\n        continue\n\n    if pulls.totalCount > 1:\n        # What would that mean? Just play it safe and skip it.\n        print(\n            f\"{commit_sha[:9]} '{commit_title}' references multiple PRs: {', '.join([pull.number for pull in pulls])}\"\n        )\n        continue\n\n    pull = pulls[0]\n\n    # If a commit with the same title is already in the branch, mark the PR with\n    # a corresponding tag. This makes it easier to check what was backported\n    # when looking at the release milestone. Note that we do this before other\n    # checks -- maybe it was backported manually regardless of the usual\n    # conditions.\n    if commit_title in branch_commit_titles:\n        print(f\"{commit_sha[:9]} '{commit_title}' is already in the branch.\")\n\n        if backported_label not in {label.name for label in pull.labels}:\n            pull.add_to_labels(backported_label)\n\n        continue\n\n    # Next, we're going to look at the labels of both the PR and the linked\n    # issue, if any, to understand whether we should backport the fix. We have\n    # labels to request backport like \"bug\", and labels to prevent backport\n    # like \"disable-auto-backport\", on both issue and the PR. We're going to use\n    # the ternary False/None/True logic to combine them properly.\n    issue_number, issue_title, issue_labels = get_referenced_issue(pull.number)\n    if not issue_number:\n        should_backport_issue_ternary = None\n        print(\n            f\"{commit_sha[:9]} belongs to the PR #{pull.number} '{pull.title}' that does not close an issue.\"\n        )\n    else:\n        issue = source_repo.get_issue(number=issue_number)\n        should_backport_issue_ternary = should_backport_by_labels(\n            issue_number, issue_title, issue_labels\n        )\n        print(\n            f\"{commit_sha[:9]} belongs to the PR #{pull.number} '{pull.title}' \"\n            f\"that references the issue #{issue.number} '{issue.title}'.\"\n        )\n    pull_labels = {label.name for label in pull.labels}\n    should_backport_pr_ternary = should_backport_by_labels(\n        pull.number, pull.title, pull_labels\n    )\n\n    # We backport if either the PR or the issue labels request the backport, and\n    # none of them prevent it. I'm writing it with `is True` because I don't\n    # remember python rules for ternary logic with None (do you?).\n    if (\n        should_backport_pr_ternary is True or should_backport_issue_ternary is True\n    ) and (\n        should_backport_pr_ternary is not False\n        and should_backport_issue_ternary is not False\n    ):\n        print(f\"{commit_sha[:9]} '{commit_title}' will be considered for backporting.\")\n    else:\n        continue\n\n    # Remember the PR and the corresponding resulting commit in main.\n    if pull.number not in prs_to_backport:\n        prs_to_backport[pull.number] = PRInfo(pull, issue_number)\n\n    # We're traversing the history backwards, and want to have the list of\n    # commits in forward order.\n    prs_to_backport[pull.number].pygithub_commits.insert(0, pygithub_commit)\n\n\ndef branch_has_open_pr(repo, branch):\n    \"\"\"Check whether the given branch has an open PR.\"\"\"\n\n    # There's no way to search by branch name + fork name, but in the case the\n    # branch name is probably unique. We'll bail out if we find more than one PR.\n    template = \"\"\"query {\n      repository(name: \"$repo_name\", owner: \"$repo_owner\") {\n        pullRequests(headRefName: \"$branch\", first: 2) {\n          nodes { closed }}}}\"\"\"\n\n    params = {\n        \"branch\": branch,\n        \"repo_name\": repo.name,\n        \"repo_owner\": repo.owner.login,\n    }\n\n    query = string.Template(template).substitute(params)\n\n    result = run_query(query)\n\n    # This returns:\n    # {'data': {'repository': {'pullRequests': {'nodes': [{'closed': True}]}}}}\n\n    prs = result[\"data\"][\"repository\"][\"pullRequests\"][\"nodes\"]\n\n    if not prs or len(prs) != 1 or not prs[0]:\n        return None\n\n    return not prs[0][\"closed\"]\n\n\ndef report_backport_not_done(original_pr, reason, details=None):\n    \"\"\"If something prevents us from backporting the PR automatically,\n    report it in a comment to original PR, and add a label preventing\n    further attempts.\"\"\"\n    print(\n        f\"Will not backport the PR #{original_pr.number} '{original_pr.title}': {reason}\"\n    )\n\n    github_comment = f\"Automated backport to {backport_target} not done: {reason}.\"\n\n    if details:\n        github_comment += f\"\\n\\n{details}\"\n\n    # Link to the job if we're running in the Github Action environment.\n    if \"GITHUB_REPOSITORY\" in os.environ:\n        github_comment += (\n            \"\\n\\n\"\n            f\"[Job log](https://github.com/{os.environ.get('GITHUB_REPOSITORY')}\"\n            f\"/actions/runs/{os.environ.get('GITHUB_RUN_ID')}\"\n            f\"/attempts/{os.environ.get('GITHUB_RUN_ATTEMPT')})\"\n        )\n\n    original_pr.create_issue_comment(github_comment)\n    original_pr.add_to_labels(\"auto-backport-not-done\")\n\n\n# Set git name and email corresponding to the token user.\ntoken_user = github.get_user()\nos.environ[\"GIT_COMMITTER_NAME\"] = token_user.name\nos.environ[\"GIT_AUTHOR_NAME\"] = token_user.name\n\n# This is an email that is used by Github when you opt to hide your real email\n# address. It is required so that the commits are recognized by Github as made\n# by the user. That is, if you use a wrong e-mail, there won't be a clickable\n# profile picture next to the commit in the Github interface.\nos.environ[\"GIT_COMMITTER_EMAIL\"] = (\n    f\"{token_user.id}+{token_user.login}@users.noreply.github.com\"\n)\nos.environ[\"GIT_AUTHOR_EMAIL\"] = os.environ[\"GIT_COMMITTER_EMAIL\"]\n\nprint(\n    f\"Will commit as {os.environ['GIT_COMMITTER_NAME']} <{os.environ['GIT_COMMITTER_EMAIL']}>\"\n)\n\n# Fetch all branches from the repository, because we use the presence\n# of the backport branches to determine that a backport exists. It's not convenient\n# to query for branch existence through the PyGithub API.\ngit_check(f\"fetch {source_remote}\")\n\n# Now, go over the list of PRs that we have collected, and try to backport\n# each of them. Do it in randomized order to avoid getting stuck on a single\n# error.\nprint(f\"Have {len(prs_to_backport)} PRs to backport.\")\nfor index, pr_info in enumerate(\n    random.sample(list(prs_to_backport.values()), len(prs_to_backport))\n):\n    print()\n\n    # Don't want to have an endless loop that modifies the repository in an\n    # unattended script. The already backported/conflicted PRs shouldn't even\n    # get into this list, so the low number is OK, it will still make progress.\n    if index > 5:\n        print(f\"{index} PRs processed, stopping as a precaution.\")\n        sys.exit(0)\n\n    original_pr = pr_info.pygithub_pr\n    backport_branch = f\"backport/{backport_target}/{original_pr.number}\"\n\n    # If there is already a backport branch for this PR, this probably means\n    # that we already created the backport PR. Update it, because the PR might\n    # not auto-merge when the branch is not up to date with the target branch,\n    # depending on the branch protection settings. We want to update to the\n    # recent target automatically to minimize the amount of manual work.\n    if (\n        git_returncode(f\"rev-parse {source_remote}/{backport_branch} > /dev/null 2>&1\")\n        == 0\n    ):\n        print(\n            f'Backport branch {backport_branch} for PR #{original_pr.number}: \"{original_pr.title}\" already exists.'\n        )\n\n        if not branch_has_open_pr(source_repo, backport_branch):\n            # The PR can be closed manually when the backport is not needed, or\n            # can not exist when there was some error. We are only interested in\n            # the most certain case when there is an open backport PR.\n            continue\n\n        print(f\"Updating the branch {backport_branch} because it has an open PR.\")\n        git_check(\"reset --hard\")\n        git_check(\"clean -xfd\")\n        git_check(\n            f\"checkout --quiet --force --detach {source_remote}/{backport_branch} > /dev/null\"\n        )\n        # Use merge and no force-push, so that the simultaneous changes made by\n        # other users are not accidentally overwritten.\n        git_check(f\"merge --quiet --no-edit {source_remote}/{backport_target}\")\n        git_check(f\"push {source_remote} @:{backport_branch}\")\n        continue\n\n    # Try to cherry-pick the commits.\n    git_check(\"reset --hard\")\n    git_check(\"clean -xfd\")\n    git_check(\n        f\"checkout --quiet --force --detach {source_remote}/{backport_target} > /dev/null\"\n    )\n\n    commit_shas = [commit.sha for commit in pr_info.pygithub_commits]\n    if git_returncode(f\"cherry-pick --quiet -m 1 -x {' '.join(commit_shas)}\") != 0:\n        details = f\"### Git status\\n\\n```\\n{git_output('status')}\\n```\"\n        git_check(\"cherry-pick --abort\")\n        report_backport_not_done(original_pr, \"cherry-pick failed\", details)\n        continue\n\n    changed_files = {file.filename for file in original_pr.get_files()}\n\n    # Push the backport branch.\n    git_check(f\"push {source_remote} @:refs/heads/{backport_branch}\")\n\n    # Prepare description for the backport PR.\n    backport_description = (\n        f\"This is an automated backport of #{original_pr.number}: {original_pr.title}.\"\n    )\n\n    if pr_info.issue_number:\n        backport_description += f\"\\nThe original issue is #{pr_info.issue_number}.\"\n\n    # Do not merge the PR automatically if it changes some particularly\n    # conflict-prone files that are better to review manually. Also mention this\n    # in the description.\n    stopper_files = changed_files.intersection(\n        [\"sql/updates/latest-dev.sql\", \"sql/updates/reverse-dev.sql\"]\n    )\n    if stopper_files:\n        backport_description += (\n            \"\\n\"\n            f\"This PR will not be merged automatically, because it modifies '{list(stopper_files)[0]}' \"\n            \"which is conflict-prone. Please review these changes manually.\"\n        )\n    else:\n        backport_description += (\n            \"\\n\"\n            \"This PR will be merged automatically after all the relevant CI checks pass.\"\n        )\n\n    backport_description += (\n        \" If this fix should not be backported, or will be backported manually, \"\n        \"just close this PR. You can use the backport branch to add your \"\n        \"changes, it won't be modified automatically anymore.\"\n        \"\\n\"\n        \"\\n\"\n        \"For more details, please see the [documentation]\"\n        \"(https://github.com/timescale/eng-database/wiki/Releasing-TimescaleDB#automated-cherry-picking-of-bug-fixes)\"\n    )\n\n    # Add original PR description. Comment out the Github issue reference\n    # keywords like 'Fixes #1234', to avoid having multiple PRs saying they fix\n    # a given issue. The backport PR is going to reference the fixed issue as\n    # \"Original issue #xxxx\".\n    original_description = re.sub(\n        r\"((fix|clos|resolv)[esd]+)(\\s+#[0-9]+)\",\n        r\"`\\1`\\3\",\n        original_pr.body or \"\",  # Match \"\" if pr_body is None\n        flags=re.IGNORECASE,\n    )\n    backport_description += (\n        \"\\n\"\n        \"\\n\"\n        \"## Original description\"\n        \"\\n\"\n        f\"### {original_pr.title}\"\n        \"\\n\"\n        f\"{original_description}\"\n    )\n\n    # Create the backport PR.\n    backport_pr = source_repo.create_pull(\n        title=f\"Backport to {backport_target}: #{original_pr.number}: {original_pr.title}\",\n        body=backport_description,\n        # We're creating PR from the token user's fork.\n        head=backport_branch,\n        base=backport_target,\n    )\n    backport_pr.add_to_labels(\"is-auto-backport\")\n    backport_pr.add_to_assignees(original_pr.user.login)\n    if not stopper_files:\n        set_auto_merge(backport_pr.number)\n\n    print(\n        f\"Created backport PR #{backport_pr.number} for #{original_pr.number}: {original_pr.title}\"\n    )\n"
  },
  {
    "path": "scripts/bundle_coredumps.sh",
    "content": "#!/bin/bash\n\nTARGET=coredumps\nCOREDUMP_DIR=/var/lib/systemd/coredump\n\nset -e\n\nmkdir -p \"$TARGET\"\n\n# get information from gdb\ninfo=$(echo \"info sharedlibrary\" | coredumpctl gdb)\n\nexecutable=$(echo \"$info\" | grep Executable | sed -e 's!^[^/]\\+!!')\n\ncp \"$executable\" \"$TARGET\"\ncp ${COREDUMP_DIR}/* \"$TARGET\"\n\n# copy libraries extracted from gdb info\necho \"$info\" | grep '^0x' | sed -e 's!^[^/]\\+!!' | xargs -ILIB cp \"LIB\" \"$TARGET\"\n\n"
  },
  {
    "path": "scripts/c_license_header-apache.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n"
  },
  {
    "path": "scripts/c_license_header-timescale.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n"
  },
  {
    "path": "scripts/changelog/generate.sh",
    "content": "#!/usr/bin/env bash\nset -eu\n\n#\n# This script build a CHANGELOG.md for a new release\n#\n\necho_changelog() {\n    echo \"${1}\"\n    # skip the template and release notes files\n    grep -i \"${2}\" .unreleased/* | \\\n        cut -d: -f3- | sort | uniq | sed -e 's/^[[:space:]]*//' -e 's/^/* /' -e 's!#\\([0-9][0-9]*\\)![#\\1](https://github.com/timescale/timescaledb/pull/\\1)!g'\n    echo\n}\n\n# Build a delta of the GUCs between two releases\n#\n# Param: previous release\n# Param: release branch\n#\necho_gucs() {\n    echo \"**GUCs**\"\n    git diff ${1}..${2} src/guc.c | grep EXTOPTION | grep -o '\"[^\"]*\"' | sed 's/^/* /' | sort | sed 's/\"/`/g'\n}\n\nget_version_config_var() {\n    grep \"${1}\" version.config | awk '{print $3}' | sed 's/-dev//'\n}\n\nRELEASE_NEXT=$(get_version_config_var '^version')\nRELEASE_PREVIOUS=$(get_version_config_var '^previous_version')\nRELEASE_BRANCH=\"${RELEASE_NEXT/%.[0-9]/.x}\"\n\necho \"Building CHANGELOG\"\n{\n    echo \"## ${RELEASE_NEXT} ($(date +\"%Y-%m-%d\"))\"\n    echo \"\"\n    echo \"This release contains performance improvements and bug fixes since the ${RELEASE_PREVIOUS} release. We recommend that you upgrade at the next available opportunity.\"\n    echo \"\"\n    echo \"**Highlighted features in TimescaleDB v${RELEASE_NEXT}**\"\n    echo \"* \"\n    echo \"\"\n    echo_changelog '**Backward-Incompatible Changes**' '^Backward-Incompatible Change:'\n    echo_changelog '**Features**' '^Implements:'\n    echo_changelog '**Bugfixes**' '^Fixes:'\n    echo_changelog '**New Settings**' '^Setting:'\n    echo_gucs $RELEASE_PREVIOUS $RELEASE_BRANCH\n    echo \"\" \n    echo_changelog '**Thanks**' '^Thanks:' \n} > CHANGELOG_next.md\n\nRELEASE_NOTE_START=$(grep -n $RELEASE_PREVIOUS CHANGELOG.md | cut -d ':' -f 1 | head -1)\nCHANGELOG_HEADER_LINES=$((RELEASE_NOTE_START - 1))\n\nmv CHANGELOG.md CHANGELOG.md.tmp\nhead -n $CHANGELOG_HEADER_LINES CHANGELOG.md.tmp > CHANGELOG.md\ncat CHANGELOG_next.md >> CHANGELOG.md\nCHANGELOG_LENGTH=$(wc -l < CHANGELOG.md.tmp)\nCHANGELOG_ENTRIES=$((CHANGELOG_LENGTH-CHANGELOG_HEADER_LINES))\ntail -n \"$CHANGELOG_ENTRIES\" CHANGELOG.md.tmp >> CHANGELOG.md\nrm -f CHANGELOG.md.tmp CHANGELOG_next.md\n\n# Remove the CHANGELOG generating\n# Fresh start for next version\necho \"Deleting all .unreleased files\"\nrm -f .unreleased/*\n\necho \"done.\"\n"
  },
  {
    "path": "scripts/changelog/template.rfc822",
    "content": "Backward-Incompatible Change: #NNNNN <one line description of the feature>\n\nImplements: #NNNNN <one line description of the feature>\n\nFixes: #NNNNN <one line description of the Issue>\n\nSetting: <one-line description of the new or changed setting (GUC)>\n\nThanks: @name <Thank you note>\n"
  },
  {
    "path": "scripts/check_changelog_format.py",
    "content": "#!/usr/bin/env python3\n\nimport sys\nimport re\nimport os\n\nimport github  # this is PyGithub.\n\nimport requests\nimport string\n\n\ndef run_query(query):\n    \"\"\"A simple function to use requests.post to make the GraphQL API call.\"\"\"\n\n    request = requests.post(\n        \"https://api.github.com/graphql\",\n        json={\"query\": query},\n        headers={\"Authorization\": f'Bearer {os.environ.get(\"GH_TOKEN\")}'},\n        timeout=20,\n    )\n    response = request.json()\n\n    # Have to work around the unique GraphQL convention of returning 200 for errors.\n    if request.status_code != 200 or \"errors\" in response:\n        raise ValueError(\n            f\"Query failed to run by returning code of {request.status_code}.\"\n            f\"\\nQuery: '{query}'\"\n            f\"\\nResponse: '{request.json()}'\"\n        )\n\n    return response\n\n\ndef get_referenced_issues(pr_number):\n    \"\"\"Get the numbers of issue fixed by the given pull request.\"\"\"\n\n    ref_result = run_query(string.Template(\"\"\"\n        query {\n            repository(owner: \"timescale\", name: \"timescaledb\") {\n              pullRequest(number: $pr_number) {\n                closingIssuesReferences(first: 100) {\n                  edges {\n                    node {\n                      number\n                    }\n                  }\n                }\n              }\n            }\n          }\n          \"\"\").substitute({\"pr_number\": pr_number}))\n\n    # The above returns {'data': {'repository': {'pullRequest': {'closingIssuesReferences': {'edges': [{'node': {'number': 4944}}]}}}}}\n\n    ref_edges = ref_result[\"data\"][\"repository\"][\"pullRequest\"][\n        \"closingIssuesReferences\"\n    ][\"edges\"]\n\n    if not ref_edges:\n        return []\n\n    return [edge[\"node\"][\"number\"] for edge in ref_edges if edge]\n\n\n# Check if a line matches any of the specified patterns\ndef is_valid_line(line):\n    patterns = [\n        r\"^Fixes:\\s*.*$\",\n        r\"^Implements:\\s*.*$\",\n        r\"^Thanks:\\s*.*$\",\n        r\"^Backward-Incompatible Change:\\s*.*$\",\n        r\"^Setting:\\s*.*$\",\n    ]\n    for pattern in patterns:\n        if re.match(pattern, line):\n            return True\n    return False\n\n\ndef main():\n    github_token = os.environ.get(\"GH_TOKEN\")\n\n    if not github_token:\n        print(\"Please populate the GH_TOKEN environment variable.\")\n        sys.exit(1)\n\n    github_obj = github.Github(github_token)\n    repo = github_obj.get_repo(\"timescale/timescaledb\")\n    # Get the file name from the command line argument\n    if len(sys.argv) != 2:\n        print(\"Please provide a file name as a command-line argument.\")\n        sys.exit(1)\n\n    file_name = sys.argv[1]\n\n    # Check if the file exists\n    if not os.path.exists(file_name):\n        print(f\"{file_name} does not exist\")\n        sys.exit(1)\n\n    this_pr_number = int(os.environ[\"PR_NUMBER\"])\n    pr_issues = set(get_referenced_issues(this_pr_number))\n\n    # Read the file and check non-empty lines\n    changelog_issues = set()\n    with open(file_name, \"r\", encoding=\"utf-8\") as file:\n        for line in file:\n            line = line.strip()\n            if not is_valid_line(line):\n                print(f'Invalid entry in change log: \"{line}\"')\n                sys.exit(1)\n\n            # The referenced issue number should be valid.\n            for issue_number in re.findall(\"#([0-9]+)\", line):\n                issue_number = int(issue_number)\n                try:\n                    issue = repo.get_issue(number=issue_number)\n                except github.UnknownObjectException:\n                    print(\n                        f\"The changelog entry references an invalid issue #{issue_number}:\\n{line}\"\n                    )\n                    sys.exit(1)\n\n                as_pr = None\n                try:\n                    as_pr = issue.as_pull_request()\n                except github.UnknownObjectException:\n                    # Not a pull request\n                    pass\n\n                # Accept references to PR itself.\n                if as_pr:\n                    if issue_number != this_pr_number:\n                        print(\n                            f\"The changelog for PR #{this_pr_number} references another PR #{issue_number}\"\n                        )\n                        sys.exit(1)\n                    changelog_issues = pr_issues\n                else:\n                    changelog_issues.add(issue_number)\n\n    if changelog_issues != pr_issues:\n        print(\n            \"Instead of \"\n            + (f\"the issues {pr_issues}\" if pr_issues else \"no issues\")\n            + f\" linked to the PR #{this_pr_number}, the changelog references \"\n            + (f\"the issues {changelog_issues}\" if changelog_issues else \"no issues\")\n        )\n        sys.exit(1)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "scripts/check_file_license.sh",
    "content": "#!/bin/bash\n\nget_c_license() {\n    awk 'BEGIN {ORS=\"\"}{if($1 == \"*/\") {print; exit;}} {print}' $1\n}\n\nget_sql_license() {\n    awk 'BEGIN {ORS=\"\"}{if($1 == \"\") {print; exit;}} {print}' $1\n}\n\ncheck_file() {\n    FLAG=${1}\n    FILE=${2}\n    SCRIPTPATH=\"$( cd \"$(dirname \"${0}\")\" || exit ; pwd -P )\"\n    TIMESCALE_LOCATION=$(dirname ${SCRIPTPATH})\n\n    LICENSE_FILE=\n    LICENSE_STRING=\n    FIRST_COMMENT=\n\n    case ${FLAG} in\n        ('-c')\n            LICENSE_FILE=\"${SCRIPTPATH}/c_license_header-apache.h\"\n            LICENSE_STRING=$(get_c_license ${LICENSE_FILE})\n            FIRST_COMMENT=$(get_c_license ${FILE})\n            ;;\n        ('-e')\n            LICENSE_FILE=\"${SCRIPTPATH}/c_license_header-timescale.h\"\n            LICENSE_STRING=$(get_c_license ${LICENSE_FILE})\n            FIRST_COMMENT=$(get_c_license ${FILE})\n            ;;\n        ('-i')\n            LICENSE_FILE=\"${SCRIPTPATH}/license_apache.spec\"\n            LICENSE_STRING=$(get_sql_license ${LICENSE_FILE})\n            FIRST_COMMENT=$(get_sql_license ${FILE})\n            ;;\n        ('-j')\n            LICENSE_FILE=\"${SCRIPTPATH}/license_tsl.spec\"\n            LICENSE_STRING=$(get_sql_license ${LICENSE_FILE})\n            FIRST_COMMENT=$(get_sql_license ${FILE})\n            ;;\n        ('-s')\n            LICENSE_FILE=\"${SCRIPTPATH}/sql_license_apache.sql\"\n            LICENSE_STRING=$(get_sql_license ${LICENSE_FILE})\n            FIRST_COMMENT=$(get_sql_license ${FILE})\n            ;;\n        ('-t')\n            LICENSE_FILE=\"${SCRIPTPATH}/sql_license_tsl.sql\"\n            LICENSE_STRING=$(get_sql_license ${LICENSE_FILE})\n            FIRST_COMMENT=$(get_sql_license ${FILE})\n            ;;\n        ('-p')\n            LICENSE_FILE=\"${SCRIPTPATH}/license_tsl.spec\"\n            LICENSE_STRING=$(get_sql_license ${LICENSE_FILE})\n            FIRST_COMMENT=$(get_sql_license ${FILE})\n            ;;\n        (\"--\")\n            return 0;\n            ;;\n        (*)\n            echo \"Unknown flag\" ${1}\n            return 1;\n    esac\n\n    if [[ \"${FIRST_COMMENT}\" != \"${LICENSE_STRING}\" ]]; then\n        echo ${FILE#\"$TIMESCALE_LOCATION/\"} \"lacks a license header. Add\";\n        echo\n        cat ${LICENSE_FILE}\n        echo\n        echo \"to the top of the file\";\n        return 1;\n    fi\n}\n\nargs=$(getopt \"c:e:i:j:p:s:t:\" \"$@\"); errcode=$?; set -- $args\n\nif [[ ${errcode} != 0 ]]; then\n        echo 'Usage: check_file_license ((-c|-e|-i|-j|-s|-t) <filename> ...)'\n        return 2\nfi\n\n\nERRORCODE=0\n\nwhile [[ ${1} ]]; do\n    if [[ ${1} == \"--\" ]]; then\n        break;\n    fi\n    check_file ${1} ${2}\n    FILE_ERR=${?}\n    ERRORCODE=$((FILE_ERR | ERRORCODE));\n    shift; shift;\ndone\n\nexit ${ERRORCODE};\n"
  },
  {
    "path": "scripts/check_license.sh",
    "content": "#! /bin/bash\n\nSCRIPT_DIR=$(dirname ${0})\nSRC_DIR=$(dirname ${SCRIPT_DIR})\n\n# we skip license checks for:\n#   - the update script fragments, because the generated update scripts will\n#     contain the license at top, and we don't want to repeat it in the middle\n#   - test/sql/dump which contains auto-generated code\n#   - src/chunk_adatptive since it's still in BETA\n\ncheck_file() {\n    SUFFIX0=\n    SUFFIX1=\n\n    if [[ ${1} == '-c' || ${1} == '-e' ]]; then\n        SUFFIX0='*.c'\n        SUFFIX1='*.h'\n    elif [[ ${1} == '-i' || ${1} == '-j' ]]; then\n        SUFFIX0='*.spec'\n        SUFFIX1='*.spec.in'\n    elif [[ ${1} == '-p' ]]; then\n        SUFFIX0='*.pl'\n        SUFFIX1='*.pm'\n    else\n        SUFFIX0='*.sql'\n        SUFFIX1='*.sql.in'\n    fi\n\n    find $2 -type f \\( -name \"${SUFFIX0}\" -or -name \"${SUFFIX1}\" \\) -and -not -path \"${SRC_DIR}/sql/updates/*.sql\" -and -not -path \"${SRC_DIR}/test/sql/dump/*.sql\" -and -not -path \"${SRC_DIR}/src/chunk_adaptive.*\" -print0 | xargs -0 -n1 \"$(dirname ${0})/check_file_license.sh\" ${1}\n}\n\nargs=$(getopt \"c:e:i:j:p:s:t:\" \"$@\"); set -- $args\n\nERRORCODE=0\n\nwhile [[ ${1} ]]; do\n    if [[ ${1} == \"--\" ]]; then\n        break;\n    fi\n    check_file ${1} ${2}\n    FILE_ERR=${?}\n    ERRORCODE=$((FILE_ERR | ERRORCODE));\n    shift; shift;\ndone\n\nexit ${ERRORCODE};\n"
  },
  {
    "path": "scripts/check_license_all.sh",
    "content": "#!/bin/bash\nSCRIPT_DIR=$(dirname $0)\nBASE_DIR=$(dirname ${SCRIPT_DIR})\n\nSRC_DIR=$BASE_DIR ${SCRIPT_DIR}/check_license.sh -c ${BASE_DIR}/src -s ${BASE_DIR}/sql -c ${BASE_DIR}/test -s ${BASE_DIR}/test -i ${BASE_DIR}/test\nexit_apache=$?\nSRC_DIR=$BASE_DIR ${SCRIPT_DIR}/check_license.sh -e ${BASE_DIR}/tsl/src -t ${BASE_DIR}/tsl/test -e ${BASE_DIR}/tsl/test -j ${BASE_DIR}/tsl/test -p ${BASE_DIR}/tsl/test -p ${BASE_DIR}/test/perl\nexit_tsl=$?\n\nif [ ${exit_apache} -ne 0 ] || [ ${exit_tsl} -ne 0 ]; then\n  exit 1\nfi\n\n"
  },
  {
    "path": "scripts/check_missing_gitignore_for_template_tests.sh",
    "content": "#!/bin/bash\n\nERROR=0\n\nfor FILE in $(git ls-files | grep '\\.sql\\.in')\ndo\n  DIRNAME=$(dirname \"${FILE}\")\n  FILENAME=$(basename \"${FILE}\" .sql.in)\n  GITIGNORE=${DIRNAME}/.gitignore\n  if [ -f \"${GITIGNORE}\" ]; then\n    if git ls-files --others --exclude-standard $DIRNAME | grep --silent \"${FILENAME}-\"; then\n      echo \"Missing entry in ${GITIGNORE} for template file ${FILE}\"\n      ERROR=1\n    fi\n  fi\ndone\n\nexit ${ERROR}\n"
  },
  {
    "path": "scripts/check_orphaned_test_output_files.sh",
    "content": "#!/bin/bash\n\nERROR=0\n\nfor FILE in $(git ls-files test/expected/*-[0-9][0-9].out tsl/test/expected/*-[0-9][0-9].out)\ndo\n  DIRNAME=$(dirname \"${FILE}\" | sed 's/expected/sql/g')\n  TESTOUTPUTNAME=$(basename \"${FILE}\" .out | sed 's/-[0-9][0-9]//g') \n\n  if [ ! -f \"${DIRNAME}/${TESTOUTPUTNAME}.sql.in\" ]; then\n      echo \"ERROR: template SQL test does not found: \\\"${DIRNAME}/${TESTOUTPUTNAME}.sql.in\\\"\"\n      echo \"HINT: Please remove the output test file \\\"${FILE}\\\"\"\n      ERROR=1\n  fi\ndone\n\nexit ${ERROR}\n"
  },
  {
    "path": "scripts/check_sql_script.py",
    "content": "#!/usr/bin/env python\n\n# Check SQL script components for problematic patterns. This script is\n# intended to be run on the scripts that are added to every update script,\n# but not the compiled update script or the pre_install scripts.\n#\n# This script will find patterns that are not idempotent and therefore\n# should be moved to the pre_install part.\n\nfrom pglast import parse_sql\nfrom pglast.visitors import Visitor, Skip, Continue\nfrom pglast.stream import RawStream\nimport sys\nimport re\nimport argparse\n\nparser = argparse.ArgumentParser()\nparser.add_argument(\"filename\", type=argparse.FileType(\"r\"), nargs=\"+\")\nargs = parser.parse_args()\n\n\nclass SQLVisitor(Visitor):\n    def __init__(self, file):\n        self.errors = 0\n        self.file = file\n        super().__init__()\n\n    def error(self, node, hint):\n        self.errors += 1\n        print(\n            f\"Invalid statement found in sql script({self.file}):\\n\",\n            RawStream()(node),\n        )\n        print(hint, \"\\n\")\n\n    def visit_RawStmt(self, _ancestors, _node):\n        # Statements are nested in RawStmt so we need to let the visitor descend\n        return Continue\n\n    def visit(self, _ancestors, node):\n        self.error(node, \"Consider moving the statement into a pre_install script\")\n\n        # We are only interested in checking top-level statements\n        return Skip\n\n    def visit_CommentStmt(self, _ancestors, _node):\n        return Skip\n\n    def visit_GrantStmt(self, _ancestors, _node):\n        return Skip\n\n    def visit_SelectStmt(self, _ancestors, _node):\n        return Skip\n\n    def visit_InsertStmt(self, _ancestors, _node):\n        return Skip\n\n    def visit_DeleteStmt(self, _ancestors, _node):\n        return Skip\n\n    def visit_DoStmt(self, _ancestors, _node):\n        return Skip\n\n    def visit_CreateEventTrigStmt(self, _ancestors, _node):\n        return Skip\n\n    def visit_VariableSetStmt(self, _ancestors, node):\n        if not node.is_local:\n            self.error(node, \"Consider using SET LOCAL instead of SET\")\n\n        return Skip\n\n    def visit_CreateTrigStmt(self, _ancestors, node):\n        if not node.replace:\n            self.error(node, \"Consider using CREATE OR REPLACE TRIGGER\")\n\n        return Skip\n\n    def visit_DefineStmt(self, _ancestors, node):\n        if not node.replace:\n            self.error(node, \"Consider using CREATE OR REPLACE\")\n\n        return Skip\n\n    def visit_DropStmt(self, _ancestors, node):\n        if not node.missing_ok:\n            self.error(node, \"Consider using DROP IF EXISTS\")\n\n        return Skip\n\n    def visit_ViewStmt(self, _ancestors, node):\n        if not node.replace:\n            self.error(node, \"Consider using CREATE OR REPLACE VIEW\")\n\n        return Skip\n\n    def visit_CreateFunctionStmt(self, _ancestors, node):\n        if not node.replace:\n            fn_str = (\"FUNCTION\", \"PROCEDURE\")[node.is_procedure is True]\n            self.error(node, f\"Consider using CREATE OR REPLACE {fn_str}\")\n\n        return Skip\n\n\n# copied from pgspot\ndef visit_sql(sql, file):\n    # @extschema@ is placeholder in extension scripts for\n    # the schema the extension gets installed in\n    sql = sql.replace(\"@extschema@\", \"extschema\")\n    sql = sql.replace(\"@extowner@\", \"extowner\")\n    sql = sql.replace(\"@database_owner@\", \"database_owner\")\n    # postgres contrib modules are protected by psql meta commands to\n    # prevent running extension files in psql.\n    # The SQL parser will error on those since they are not valid\n    # SQL, so we comment out all psql meta commands before parsing.\n    sql = re.sub(r\"^\\\\\", \"-- \\\\\\\\\", sql, flags=re.MULTILINE)\n\n    visitor = SQLVisitor(file)\n    for stmt in parse_sql(sql):\n        visitor(stmt)\n    return visitor.errors\n\n\ndef main(args):\n    errors = 0\n    error_files = []\n    for file in args.filename:\n        sql = file.read()\n        result = visit_sql(sql, file.name)\n        if result > 0:\n            errors += result\n            error_files.append(file.name)\n\n    if errors > 0:\n        numbering = \"errors\" if errors > 1 else \"error\"\n        print(\n            f\"{errors} {numbering} detected in {len(error_files)} files({', '.join(error_files)})\"\n        )\n        sys.exit(1)\n    sys.exit(0)\n\n\nif __name__ == \"__main__\":\n    main(args)\n    sys.exit(0)\n"
  },
  {
    "path": "scripts/check_unnecessary_template_tests.sh",
    "content": "#!/bin/bash\n\nERROR=0\n\nfor FILE in $(git ls-files test/sql/*.sql.in tsl/test/sql/*.sql.in)\ndo\n  DIRNAME=$(dirname \"${FILE}\" | sed 's/sql/expected/g')\n  TESTNAME=$(basename \"${FILE}\" .sql.in)\n\n  if diff --from-file ${DIRNAME}/${TESTNAME}-*.out > /dev/null 2>&1; then\n      echo \"ERROR: all template output test files are equal: \\\"${DIRNAME}/${TESTNAME}-*.out\\\"\"\n      echo \"HINT: Please turn template test file \\\"${FILE}\\\" into a regular test file\"\n      ERROR=1\n  fi\ndone\n\nexit ${ERROR}\n"
  },
  {
    "path": "scripts/check_unreferenced_files.sh",
    "content": "#!/bin/bash\n\nSCRIPT_DIR=$(dirname ${0})\nBASE_DIR=$(pwd)/$SCRIPT_DIR/..\n\nunreferenced=0\n\nfunction get_filenames {\n  echo ./*.$2 ./*.$2.in | xargs git ls-files | xargs -IFILE basename FILE\n}\n\nfunction check_directory {\n  cd $BASE_DIR/$1 || exit\n  test_files=$(get_filenames $1 $2)\n\n  for file in $test_files; do\n    output=$(grep --files-without-match $file CMakeLists.txt)\n\n    # return value from grep --files-without-match seems to differ\n    # between grep versions so we use output instead of return value\n    if [ \"$output\" != \"\" ]; then\n      echo -e \"\\nUnreferenced file in $1: $file\\n\"\n      unreferenced=1\n    fi\n  done\n}\n\ncheck_directory test/sql sql\ncheck_directory tsl/test/sql sql\ncheck_directory tsl/test/shared/sql sql\n\ncheck_directory test/isolation/specs spec\ncheck_directory tsl/test/isolation/specs spec\n\nexit $unreferenced\n"
  },
  {
    "path": "scripts/check_updates.py",
    "content": "#!/usr/bin/env python\n\n# Check SQL update script for undesirable patterns. This script is\n# intended to be run on the compiled update script or subsets of\n# the update script (e.g. latest-dev.sql and reverse-dev.sql)\n\nfrom pglast import parse_sql\nfrom pglast.ast import ColumnDef\nfrom pglast.visitors import Visitor\nfrom pglast import enums\nimport sys\nimport re\nimport argparse\n\nparser = argparse.ArgumentParser()\nparser.add_argument(\"filename\")\nparser.add_argument(\"--latest\", action=\"store_true\", help=\"process latest-dev.sql\")\nargs = parser.parse_args()\n\n\nclass SQLVisitor(Visitor):\n    def __init__(self):\n        self.errors = 0\n        self.catalog_schemata = [\n            \"_timescaledb_catalog\",\n            \"_timescaledb_config\",\n            \"_timescaledb_internal\",\n        ]\n        super().__init__()\n\n    def error(self, msg, hint=None):\n        self.errors += 1\n        print(msg)\n        if hint:\n            print(hint)\n        print()\n\n    # ALTER TABLE _timescaledb_catalog.<tablename> ADD/DROP COLUMN\n    def visit_AlterTableStmt(self, ancestors, node):  # pylint: disable=unused-argument\n        if (\n            \"schemaname\" in node.relation\n            and node.relation.schemaname in self.catalog_schemata\n        ):\n            schema = node.relation.schemaname\n            table = node.relation.relname\n            for cmd in node.cmds:\n                if cmd.subtype in (\n                    enums.AlterTableType.AT_AddColumn,\n                    enums.AlterTableType.AT_DropColumn,\n                ):\n                    if cmd.subtype == enums.AlterTableType.AT_AddColumn:\n                        subcmd = \"ADD\"\n                        column = cmd.def_.colname\n                    else:\n                        subcmd = \"DROP\"\n                        column = cmd.name\n\n                    self.error(\n                        f\"Attempting to {subcmd} COLUMN {column} to catalog table {schema}.{table}\",\n                        \"Tables need to be rebuilt in update script to ensure consistent attribute numbers\",\n                    )\n\n    # ALTER TABLE _timescaledb_catalog.<tablename> RENAME TO\n    def visit_RenameStmt(self, ancestors, node):  # pylint: disable=unused-argument\n        if (\n            node.renameType == enums.ObjectType.OBJECT_TABLE\n            and node.relation.schemaname in self.catalog_schemata\n        ):\n            self.error(\n                f\"Attempting to RENAME catalog table {node.relation.schemaname}.{node.relation.relname}\",\n                \"Catalog tables should be rebuilt in update scripts to ensure consistent naming for dependent objects\",\n            )\n\n    # CREATE TEMP | TEMPORARY TABLE ..\n    # CREATE TABLE IF NOT EXISTS ..\n    def visit_CreateStmt(self, ancestors, node):  # pylint: disable=unused-argument\n        if node.relation.relpersistence == \"t\":\n            schema = (\n                node.relation.schemaname + \".\"\n                if node.relation.schemaname is not None\n                else \"\"\n            )\n            self.error(\n                f\"Attempting to CREATE TEMPORARY TABLE {schema}{node.relation.relname}\"\n                \"Creating temporary tables is blocked in pg_extwlist context\"\n            )\n        if node.if_not_exists:\n            self.error(\n                f\"Attempting to CREATE TABLE IF NOT EXISTS {node.relation.relname}\"\n            )\n\n        # We have to be careful with the column types we use in our catalog to only allow types\n        # that are safe to use in catalog tables and not cause problems during extension upgrade,\n        # pg_upgrade or dump/restore.\n        if node.tableElts is not None:\n            for coldef in node.tableElts:\n                if isinstance(coldef, ColumnDef):\n                    if coldef.typeName.arrayBounds is not None:\n                        if coldef.typeName.names[-1].sval not in [\n                            \"bool\",\n                            \"text\",\n                        ]:\n                            self.error(\n                                f\"Attempting to CREATE TABLE {node.relation.relname} with blocked array type {coldef.typeName.names[-1].sval}\"\n                            )\n                    else:\n                        if coldef.typeName.names[-1].sval not in [\n                            \"bool\",\n                            \"int2\",\n                            \"int4\",\n                            \"int8\",\n                            \"interval\",\n                            \"jsonb\",\n                            \"name\",\n                            \"regclass\",\n                            \"regtype\",\n                            \"regrole\",\n                            \"serial\",\n                            \"text\",\n                            \"timestamptz\",\n                        ]:\n                            self.error(\n                                f\"Attempting to CREATE TABLE {node.relation.relname} with blocked type {coldef.typeName.names[-1].sval}\"\n                            )\n\n    # CREATE SCHEMA IF NOT EXISTS ..\n    def visit_CreateSchemaStmt(\n        self, ancestors, node\n    ):  # pylint: disable=unused-argument\n        if node.if_not_exists:\n            self.error(f\"Attempting to CREATE SCHEMA IF NOT EXISTS {node.schemaname}\")\n\n    # CREATE MATERIALIZED VIEW IF NOT EXISTS ..\n    def visit_CreateTableAsStmt(\n        self, ancestors, node\n    ):  # pylint: disable=unused-argument\n        if node.if_not_exists:\n            self.error(\n                f\"Attempting to CREATE MATERIALIZED VIEW IF NOT EXISTS {node.into.rel.relname}\"\n            )\n\n    # CREATE FOREIGN TABLE IF NOT EXISTS ..\n    def visit_CreateForeignTableStmt(\n        self, ancestors, node\n    ):  # pylint: disable=unused-argument\n        if node.base.if_not_exists:\n            self.error(\n                f\"Attempting to CREATE FOREIGN TABLE IF NOT EXISTS {node.base.relation.relname}\"\n            )\n\n    # CREATE FUNCTION / PROCEDURE _timescaledb_internal...\n    def visit_CreateFunctionStmt(\n        self, ancestors, node\n    ):  # pylint: disable=unused-argument\n        if args.latest:\n            # C functions should only appear in actual function definition but not\n            # in latest-dev.sql as that would introduce a dependency on the library.\n            # In that case, we want to use a dedicated placeholder function.\n            lang = [elem for elem in node.options if elem.defname == \"language\"]\n            code = [elem for elem in node.options if elem.defname == \"as\"][0].arg\n            if (\n                lang\n                and lang[0].arg.sval == \"c\"\n                and code[-1].sval != \"ts_update_placeholder\"\n                and node.returnType.names[0].sval\n                not in [\"table_am_handler\", \"index_am_handler\"]\n            ):\n                functype = \"procedure\" if node.is_procedure else \"function\"\n                self.error(\n                    f\"Attempting to create {functype} {node.funcname[-1].sval} with language 'c'\",\n                    \"latest-dev should link C functions to ts_update_placeholder\",\n                )\n\n        if len(node.funcname) == 2 and node.funcname[0].sval == \"_timescaledb_internal\":\n            functype = \"procedure\" if node.is_procedure else \"function\"\n            self.error(\n                f\"Attempting to create {functype} {node.funcname[1].sval} in the internal schema\",\n                \"_timescaledb_functions should be used as schema for internal functions\",\n            )\n\n\n# copied from pgspot\ndef visit_sql(sql):\n    # @extschema@ is placeholder in extension scripts for\n    # the schema the extension gets installed in\n    sql = sql.replace(\"@extschema@\", \"extschema\")\n    sql = sql.replace(\"@extowner@\", \"extowner\")\n    sql = sql.replace(\"@database_owner@\", \"database_owner\")\n    # postgres contrib modules are protected by psql meta commands to\n    # prevent running extension files in psql.\n    # The SQL parser will error on those since they are not valid\n    # SQL, so we comment out all psql meta commands before parsing.\n    sql = re.sub(r\"^\\\\\", \"-- \\\\\\\\\", sql, flags=re.MULTILINE)\n\n    visitor = SQLVisitor()\n    # try:\n    for stmt in parse_sql(sql):\n        visitor(stmt)\n    return visitor.errors\n\n\ndef main(args):\n    file = args.filename\n\n    with open(file, \"r\", encoding=\"utf-8\") as f:\n        sql = f.read()\n        errors = visit_sql(sql)\n        if errors > 0:\n            numbering = \"errors\" if errors > 1 else \"error\"\n            print(f\"{errors} {numbering} detected in file {file}\")\n            sys.exit(1)\n    sys.exit(0)\n\n\nif __name__ == \"__main__\":\n    main(args)\n    sys.exit(0)\n"
  },
  {
    "path": "scripts/clang_format_all.sh",
    "content": "#!/bin/bash\n\n# we need to convert script dir to an absolute path\nSCRIPT_DIR=$(cd \"$(dirname $0)\" || exit; pwd)\nBASE_DIR=$(dirname $SCRIPT_DIR)\n\nfind ${BASE_DIR} \\( -path \"${BASE_DIR}/src/*\" -or -path \"${BASE_DIR}/test/*\" -or -path \"${BASE_DIR}/tsl/*\" \\) \\\n    -and -not \\( -path \"*/.*\" -or -path \"*CMake*\" -or -path \"${BASE_DIR}/src/import/*\" -or -path \"${BASE_DIR}/tsl/src/import/*\" \\) \\\n    -and \\( -name '*.c' -or -name '*.h' \\) -print0 | xargs -0 ${SCRIPT_DIR}/clang_format_wrapper.sh -style=file -i\n"
  },
  {
    "path": "scripts/clang_format_wrapper.sh",
    "content": "#!/bin/bash\n\n# clang-format misunderstand sql function written in C because they have the signature\n#   Datum my_func(PG_FUNCTION_ARGS)\n# and clang-format does not interpret PG_FUNCTION_ARGS as a type, name pair.\n# This script replaces PG_FUNCTION_ARGS with \"PG_FUNCTION_ARGS fake_var_for_clang\" to\n# make it look like a proper function for clang and then converts it back after clang runs.\n\nSCRIPT_DIR=$(cd \"$(dirname $0)\"; pwd)\nBASE_DIR=$(dirname $SCRIPT_DIR)\nTEMP_DIR=\"/tmp/timescaledb_format\"\n\nOPTIONS=(\"${@}\")\n\nif [ -e ${TEMP_DIR} ]\nthen\n    echo \"error: ${TEMP_DIR} already exists\"\n    echo \"       delete ${TEMP_DIR} to format\"\n    exit 1\nfi\n\n# shellcheck disable=SC2317\ncleanup() {\n    echo \"cleaning\"\n    rm -rf ${TEMP_DIR}\n}\n\ntrap cleanup EXIT SIGINT SIGTERM\n\nif ! mkdir ${TEMP_DIR}\nthen\n    echo \"error: could not create temporary directory ${TEMP_DIR}\"\n    exit 1\nfi\n\n#from this point on, if we get a failure we end up in an invalid state, so exit\nset -e\n\nCLANG_FORMAT_FLAGS=\"\"\nFILE_NAMES=\"\"\n\nfor opt in \"${OPTIONS[@]}\"\ndo\n    if [[ \"${opt:0:1}\" != \"-\" ]]\n    then\n        file_path=${opt#\"$BASE_DIR/\"}\n        FILE_NAMES=\"${FILE_NAMES} $file_path\"\n    else\n        CLANG_FORMAT_FLAGS=\"${CLANG_FORMAT_FLAGS} $opt\"\n    fi\ndone\n\necho \"copying to ${TEMP_DIR}\"\n\nfor name in ${FILE_NAMES}\ndo\n    # sed -i have different semantics on mac and linux, don't use\n    mkdir -p \"$(dirname \"${TEMP_DIR}/${name}\")\"\n    sed -e 's/(PG_FUNCTION_ARGS)/(PG_FUNCTION_ARGS fake_var_for_clang)/' ${BASE_DIR}/${name} > ${TEMP_DIR}/${name}\ndone\n\ncp ${BASE_DIR}/.clang-format ${TEMP_DIR}/.clang-format\n\nCURR_DIR=${PWD}\n\necho \"formatting\"\n\ncd ${TEMP_DIR}\n\n${CLANG_FORMAT:-clang-format} --version\n${CLANG_FORMAT:-clang-format} -Wno-error=unknown ${CLANG_FORMAT_FLAGS} ${FILE_NAMES}\n\ncd ${CURR_DIR}\n\necho \"copying back\"\n\nfor name in ${FILE_NAMES}\ndo\n    if sed -e 's/PG_FUNCTION_ARGS fake_var_for_clang/PG_FUNCTION_ARGS/' ${TEMP_DIR}/${name} > ${TEMP_DIR}/replace_file; then\n\tif ! cmp -s ${TEMP_DIR}/replace_file ${BASE_DIR}/${name}; then\n\t    echo \"Updating ${BASE_DIR}/${name}\"\n            mv ${TEMP_DIR}/replace_file ${BASE_DIR}/${name}\n\tfi\n    fi\ndone\n\nexit 0\n"
  },
  {
    "path": "scripts/cmake_format_all.sh",
    "content": "#!/bin/bash\n\nSCRIPTDIR=$(cd \"$(dirname $0)\" || exit; pwd)\nBASEDIR=$(dirname $SCRIPTDIR)\n\nfind $BASEDIR -name CMakeLists.txt  -exec cmake-format -i {} +\nfind $BASEDIR/src $BASEDIR/test $BASEDIR/tsl -name '*.cmake' -exec cmake-format -i {} +\n"
  },
  {
    "path": "scripts/coccinelle.sh",
    "content": "#!/bin/bash\n\nset -o pipefail\n\nSCRIPT_DIR=$(dirname \"${0}\")\nFAILED=false\ntrue > coccinelle.diff\n\nfor f in \"${SCRIPT_DIR}\"/../coccinelle/*.cocci; do\n  spatch --very-quiet --include-headers --sp-file \"$f\" --dir \"${SCRIPT_DIR}\"/.. | tee -a coccinelle.diff\n  rc=$?\n  if [ $rc -ne 0 ]; then\n    FAILED=true\n  fi\ndone\n\nif [ $FAILED = true ] || [ -s coccinelle.diff ] ; then exit 1; fi\n\n"
  },
  {
    "path": "scripts/delete_released_change_logs.sh",
    "content": "#!/bin/bash\n\n# Check if both old_version and new_version have been provided\nif [[ -z \"$1\" || -z \"$2\" ]]; then\n  echo \"Usage: $0 <old_version> <new_version>\"\n  exit 1\nfi\n\n# Assign the values to variables for easier use later\nOLD_VERSION=\"$1\"\nNEW_VERSION=\"$2\"\n\n# Run git diff and remove the listed files\ngit diff --name-only \"$OLD_VERSION\" \"$NEW_VERSION\" .unreleased | grep -v '.unreleased/template.rfc822' | xargs rm\n"
  },
  {
    "path": "scripts/docker-build.sh",
    "content": "#!/usr/bin/env bash\n#\n# This script builds a development TimescaleDB image from the\n# currently checked out source on the host.\n#\nSCRIPT_DIR=$(dirname $0)\nBASE_DIR=${PWD}/${SCRIPT_DIR}/..\nPG_VERSION=${PG_VERSION:-17.4}\nPG_IMAGE_TAG=${PG_IMAGE_TAG:-${PG_VERSION}-alpine}\nBUILD_CONTAINER_NAME=${BUILD_CONTAINER_NAME:-pgbuild}\nBUILD_IMAGE_NAME=${BUILD_IMAGE_NAME:-$USER/pgbuild}\nIMAGE_NAME=${IMAGE_NAME:-$USER/timescaledb}\nGIT_ID=$(git -C ${BASE_DIR} describe --dirty --always | sed -e \"s|/|_|g\")\nTAG_NAME=${TAG_NAME:-$GIT_ID}\nBUILD_TYPE=${BUILD_TYPE:-Release}\nUSE_OPENSSL=${USE_OPENSSL:-true}\nPUSH_PG_IMAGE=${PUSH_PG_IMAGE:-false}\nGENERATE_DOWNGRADE_SCRIPT=${GENERATE_DOWNGRADE_SCRIPT:-OFF}\n\n# Full image identifiers\nPG_IMAGE=${BUILD_IMAGE_NAME}:${PG_IMAGE_TAG}\nTS_IMAGE=${IMAGE_NAME}:${TAG_NAME}\n\ntrap remove_build_container EXIT\n\nimage_exists() {\n    [[ $(docker image ls -f \"reference=$1\" -q) ]]\n}\n\npostgres_build_image_exists() {\n    image_exists ${PG_IMAGE}\n}\n\ntimescaledb_image_exists() {\n    image_exists ${TS_IMAGE}\n}\n\nremove_build_container() {\n    local container=${1:-${BUILD_CONTAINER_NAME}}\n    docker rm -vf \"$(docker ps -a -q -f name=${container} 2>/dev/null)\" 2>/dev/null\n}\n\nfetch_postgres_build_image() {\n    local image=${1:-${PG_IMAGE}}\n    docker pull ${image}\n}\n\ncreate_postgres_build_image() {\n    local image=${1:-${PG_IMAGE}}\n\n    echo \"Creating new PostgreSQL build image ${image}\"\n    # Run a Postgres container\n    docker run -d --name ${BUILD_CONTAINER_NAME} --env POSTGRES_HOST_AUTH_METHOD=trust -v ${BASE_DIR}:/src postgres:${PG_IMAGE_TAG}\n\n    # Install build dependencies\n    docker exec -u root ${BUILD_CONTAINER_NAME} /bin/bash -c \"apk add --no-cache --virtual .build-deps postgresql-dev gdb coreutils dpkg-dev gcc git libc-dev make cmake util-linux-dev diffutils libssl3 && mkdir -p /build/debug\"\n    docker commit -a $USER -m \"TimescaleDB build base image version $PG_IMAGE_TAG\" ${BUILD_CONTAINER_NAME} ${image}\n    remove_build_container ${BUILD_CONTAINER_NAME}\n\n    if ${PUSH_PG_IMAGE}; then\n        docker push ${image}\n    fi\n}\n\nrun_postgres_build_image() {\n    local image=${1:-${PG_IMAGE}}\n    echo \"Starting image ${image}\"\n    docker run -d --name ${BUILD_CONTAINER_NAME} -v ${BASE_DIR}:/src ${image}\n}\n\nbuild_timescaledb()\n{\n    echo \"Building TimescaleDB image \\\"${TS_IMAGE}\\\" with USE_OPENSSL=${USE_OPENSSL} BUILD_TYPE=${BUILD_TYPE}\"\n\n    run_postgres_build_image ${PG_IMAGE}\n\n    # Build and install the extension with debug symbols and assertions\n    tar -c -C ${BASE_DIR} {cmake,src,sql,test,scripts,tsl,version.config,CMakeLists.txt,timescaledb.control.in} | docker cp - ${BUILD_CONTAINER_NAME}:/build/\n    if ! docker exec -u root ${BUILD_CONTAINER_NAME} /bin/bash -c \" \\\n        cd /build/debug \\\n        && git config --global --add safe.directory /src \\\n        && cmake -DGENERATE_DOWNGRADE_SCRIPT=${GENERATE_DOWNGRADE_SCRIPT} -DUSE_OPENSSL=${USE_OPENSSL} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} /src \\\n        && make -j $(getconf _NPROCESSORS_ONLN) && make install \\\n        && echo \\\"shared_preload_libraries = 'timescaledb'\\\" >> /usr/local/share/postgresql/postgresql.conf.sample \\\n        && echo \\\"timescaledb.telemetry_level=off\\\" >> /usr/local/share/postgresql/postgresql.conf.sample \\\n        && cd / && rm -rf /build\"\n    then\n      echo \"Building timescaledb failed\"\n      return 1\n    fi\n    docker commit -a $USER -m \"TimescaleDB development image\" ${BUILD_CONTAINER_NAME} ${TS_IMAGE}\n}\n\nmessage_and_exit() {\n    echo\n    echo \"Run 'docker run -d --name some-timescaledb -p 5432:5432 ${TS_IMAGE}' to launch\"\n    exit\n}\n\nremove_build_container\n\nif timescaledb_image_exists; then\n    echo \"Image \\\"${TS_IMAGE}\\\" already exists.\"\n    message_and_exit\nfi\n\n\nif ! postgres_build_image_exists; then\n    if ! fetch_postgres_build_image \"$@\"; then\n        create_postgres_build_image \"$@\" || exit 1\n    fi\nfi\n\nbuild_timescaledb || exit 1\n\nmessage_and_exit\n"
  },
  {
    "path": "scripts/docker-run-tests.sh",
    "content": "#!/usr/bin/env bash\n#\n# This script runs the TimescaleDB tests through a standard PostgreSQL\n# container, first installing the extension via a mounted host volume.\n#\nSCRIPT_DIR=$(dirname $0)\nBASE_DIR=${PWD}/${SCRIPT_DIR}/..\nPG_IMAGE_TAG=${PG_IMAGE_TAG:-12.0-alpine}\nCONTAINER_NAME=${CONTAINER_NAME:-pgtest}\n\ncase $1 in\n    clean)\n        docker rm -f \"$(docker ps -a -q -f name=${CONTAINER_NAME} 2>/dev/null)\" 2>/dev/null\n        ;;\nesac\n\nif [ \"$(docker ps -q -f name=${CONTAINER_NAME} 2>/dev/null | wc -l)\" == \"0\" ]; then\n    echo \"Creating container ${CONTAINER_NAME}\"\n    docker rm ${CONTAINER_NAME} 2>/dev/null\n    # Run a Postgres container\n    docker run -u postgres -d --name ${CONTAINER_NAME} -v ${BASE_DIR}:/src postgres:${PG_IMAGE_TAG}\n    # Install build dependencies\n    docker exec -u root -it ${CONTAINER_NAME} /bin/bash -c \"apk add --no-cache --virtual .build-deps coreutils dpkg-dev gcc libc-dev make util-linux-dev diffutils cmake bison flex && mkdir -p /build/debug\"\nfi\n\n# Copy the source files to build directory\ndocker exec -u root -it ${CONTAINER_NAME} /bin/bash -c \"cp -a /src/{src,sql,scripts,test,tsl,CMakeLists.txt,timescaledb.control.in,version.config} /build/ &&cd /build/debug/ && CFLAGS=-Werror cmake .. -DCMAKE_BUILD_TYPE=Debug && make -C /build/debug install && chown -R postgres /build/debug\"\n\n# Run tests\nif ! docker exec -u postgres -it ${CONTAINER_NAME} /bin/bash -c \"make -C /build/debug installcheck TEST_INSTANCE_OPTS='--temp-instance=/tmp/pgdata --temp-config=/build/test/postgresql.conf'\"\nthen\n    docker exec -it ${CONTAINER_NAME} cat /build/debug/test/regression.diffs\nfi\n"
  },
  {
    "path": "scripts/dump_meta_data.sql",
    "content": "--\n-- This file is licensed under the Apache License, see LICENSE-APACHE\n-- at the top level directory of the TimescaleDB distribution.\n\n-- This script will dump relevant meta data from internal TimescaleDB tables\n-- that can help our engineers trouble shoot.\n--\n-- usage:\n-- psql [your connect flags] -d your_timescale_db < dump_meta_data.sql > dumpfile.txt\n\n\\echo 'TimescaleDB meta data dump'\n\\echo '<exclude_from_test>'\n\\echo 'Date, git commit, and extension version can change without it being an error.'\n\\echo 'Adding this tag allows us to run regression tests on this script file.'\nselect now();\n\\echo 'Postgres version'\nselect version();\n\n\\echo 'Build tag'\n\\set ON_ERROR_STOP 0\nSELECT * FROM _timescaledb_internal.get_git_commit();\n\\set ON_ERROR_STOP 1\n\\dx\n\n\\echo '</exclude_from_test>'\n\n\\echo 'List of tables'\nSELECT n.nspname as \"Schema\",\n  c.relname as \"Name\",\n  CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 't' THEN 'TOAST table' WHEN 'f' THEN 'foreign table' WHEN 'p' THEN 'partitioned table' WHEN 'I' THEN 'partitioned index' END as \"Type\",\n  pg_catalog.pg_get_userbyid(c.relowner) as \"Owner\"\nFROM pg_catalog.pg_class c\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n     LEFT JOIN pg_catalog.pg_am am ON am.oid = c.relam\nWHERE c.relkind IN ('r','p','')\n      AND n.nspname <> 'pg_catalog'\n      AND n.nspname !~ '^pg_toast'\n      AND n.nspname <> 'information_schema'\n  AND pg_catalog.pg_table_is_visible(c.oid)\nORDER BY 1,2;\n\n\\echo 'List of hypertables'\nSELECT * FROM _timescaledb_catalog.hypertable;\n\n\\echo 'List of chunk indexes'\nSELECT ch.id AS chunk_id, ci.indexrelid::regclass::text AS index_name, h.id AS hypertable_id\nFROM _timescaledb_catalog.hypertable h\nJOIN _timescaledb_catalog.chunk ch ON ch.hypertable_id = h.id\nJOIN pg_index ci ON ci.indrelid = format('%I.%I', ch.schema_name, ch.table_name)::regclass\nORDER BY h.id, ch.id, ci.indrelid::regclass::text;\n\n\\echo 'Size of hypertables'\nSELECT hypertable,\n       table_bytes,\n       index_bytes,\n       toast_bytes,\n       total_bytes\n       FROM (\n       SELECT *, total_bytes-index_bytes-COALESCE(toast_bytes,0) AS table_bytes FROM (\n              SELECT\n              pgc.oid::regclass::text as hypertable,\n              sum(pg_total_relation_size('\"' || c.schema_name || '\".\"' || c.table_name || '\"'))::bigint as total_bytes,\n              sum(pg_indexes_size('\"' || c.schema_name || '\".\"' || c.table_name || '\"'))::bigint AS index_bytes,\n              sum(pg_total_relation_size(reltoastrelid))::bigint AS toast_bytes\n              FROM\n              _timescaledb_catalog.hypertable h,\n              _timescaledb_catalog.chunk c,\n              pg_class pgc,\n              pg_namespace pns\n              WHERE c.hypertable_id = h.id\n              AND pgc.relname = h.table_name\n              AND pns.oid = pgc.relnamespace\n              AND pns.nspname = h.schema_name\n              AND relkind = 'r'\n              GROUP BY pgc.oid\n              ) sub1\n       ) sub2;\n\n\\echo 'Chunk sizes:'\nSELECT chunk_id,\nchunk_table,\npartitioning_columns,\npartitioning_column_types,\npartitioning_hash_functions,\nranges,\ntable_bytes,\nindex_bytes,\ntoast_bytes,\ntotal_bytes\nFROM (\nSELECT *,\n      total_bytes-index_bytes-COALESCE(toast_bytes,0) AS table_bytes\n      FROM (\n       SELECT c.id as chunk_id,\n       '\"' || c.schema_name || '\".\"' || c.table_name || '\"' as chunk_table,\n       pg_total_relation_size('\"' || c.schema_name || '\".\"' || c.table_name || '\"') AS total_bytes,\n       pg_indexes_size('\"' || c.schema_name || '\".\"' || c.table_name || '\"') AS index_bytes,\n       pg_total_relation_size(reltoastrelid) AS toast_bytes,\n       array_agg(d.column_name ORDER BY d.interval_length, d.column_name ASC) as partitioning_columns,\n       array_agg(d.column_type ORDER BY d.interval_length, d.column_name ASC) as partitioning_column_types,\n       array_agg(d.partitioning_func_schema || '.' || d.partitioning_func ORDER BY d.interval_length, d.column_name ASC) as partitioning_hash_functions,\n       array_agg('[' || _timescaledb_functions.range_value_to_pretty(range_start, column_type) ||\n                 ',' ||\n                 _timescaledb_functions.range_value_to_pretty(range_end, column_type) || ')' ORDER BY d.interval_length, d.column_name ASC) as ranges\n       FROM\n       _timescaledb_catalog.hypertable h,\n       _timescaledb_catalog.chunk c,\n       _timescaledb_catalog.chunk_constraint cc,\n       _timescaledb_catalog.dimension d,\n       _timescaledb_catalog.dimension_slice ds,\n       pg_class pgc,\n       pg_namespace pns\n       WHERE pgc.relname = h.table_name\n             AND pns.oid = pgc.relnamespace\n             AND pns.nspname = h.schema_name\n             AND relkind = 'r'\n             AND c.hypertable_id = h.id\n             AND c.id = cc.chunk_id\n             AND cc.dimension_slice_id = ds.id\n             AND ds.dimension_id = d.id\n       GROUP BY c.id, pgc.reltoastrelid, pgc.oid ORDER BY c.id\n       ) sub1\n) sub2;\n\n\\echo 'Hypertable index sizes'\nSET search_path TO pg_catalog, pg_temp;\nSELECT\n  i.indrelid::regclass AS hypertable,\n  i.indexrelid::regclass AS index_name,\n  public.hypertable_index_size(i.indexrelid::regclass) AS index_bytes\nFROM _timescaledb_catalog.hypertable h\nJOIN pg_index i ON i.indrelid = format('%I.%I',h.schema_name,h.table_name)::regclass\nORDER BY i.indrelid::regclass::text, i.indexrelid::regclass::text;\nRESET search_path;\n\n"
  },
  {
    "path": "scripts/export_prefix_check.sh.in",
    "content": "#!/bin/bash\n\nif [ ! -x \"@NM@\" ];\nthen\n  echo \"Cannot check export format because nm is not installed\" >&2\n  exit 1\nfi\n\nDEFINED_ONLY=\"--defined-only\"\nEXTERN_ONLY=\"--extern-only\"\n\nif nm --help | grep -q ' \\-defined\\-only'; then\n\t# The flag can be either --defined-only or -defined-only\n\t# depending on nm-llvm version. If there is \" -defined-only\"\n\t# string in the --help output, we assume this is the right\n\t# way to specify the flag.\n    DEFINED_ONLY=\"-defined-only\"\n    EXTERN_ONLY=\"-extern-only\"\nfi\n\n# this script outputs all symbols not starting with an allowed prefix\n# exported symbols are allowed to start with\n#    ts_      for regular timescaledb functions\n#    pg_finfo for metadata defined by PG_FUNCTION_INFO_V1\n# we also whitelist a couple of special symbols\n#    _.*_init                     module startup functions\n#    _.*_fini                     module shutdown functions\n#    Pg_magic_func                used by postgres to check extension compatibility\n#    timescaledb_hello            used to test that our name collision resistance works\n#    loader_hello                 used to test that our name collision resistance works\n#    test_symbol_conflict         used to test that our name collision resistance works\n#    tsl_license_update_check     used to check for valid license\n#    __sanitizer.* __sancov.*     llvm sanitizer\n#    _ZN11__sanitizer.*           llvm sanitizer\n#    __asan.* __odr_asan.*        address sanitizer\n#    __ubsan.* _ZN7__ubsan.*      undefined behaviour sanitizer\n#    internal_sigreturn           gcc\n#    OnPrint                      gcc\n#    backtrace_uncompress_zdebug  libbacktrace\n#    reset_fn_list                used by llvm code coverage infrastructure\n# all of these symbols start with an additional leading '_' on macos\nexports=$(find @CMAKE_BINARY_DIR@ -not -path '*/\\.*' -name '*.so' -print0 \\\n    | xargs -0 @NM@ ${DEFINED_ONLY} ${EXTERN_ONLY} \\\n    | sed -e 's:^/.*$::' -e '/^$/d' -e 's/^[a-f0-9]* [A-Za-z] //' \\\n    | grep -v \\\n        -e '^_\\?__gcov_' \\\n        -e '^__bss_start$' \\\n        -e '^_edata$' \\\n        -e '^_end$' \\\n        -e '^_fini$' \\\n        -e '^_init$' \\\n        -e '^_\\?flush_fn_list$' \\\n        -e '^_\\?writeout_fn_list$' \\\n        -e '^_\\?lprofDirMode$' \\\n        -e '^_\\?mangle_path$' \\\n        -e '^_\\?ts_' \\\n        -e '^_\\?tsl_license_update_check$' \\\n        -e '^_\\?pg_finfo' \\\n        -e '^_\\?_.*_init$' \\\n        -e '^_\\?_.*_fini$' \\\n        -e '^_\\?Pg_magic_func$' \\\n        -e '^_\\?timescaledb_' \\\n        -e '^_\\?loader_hello$' \\\n        -e '^_\\?test_symbol_conflict$' \\\n        -e '^_\\?__sanitizer' \\\n        -e '^_\\?__sancov' \\\n        -e '^_\\?__asan' \\\n        -e '^_\\?__odr_asan' \\\n        -e '^_\\?__ubsan' \\\n        -e '^_\\?_ZN7__ubsan' \\\n        -e '^_\\?_ZN11__sanitizer' \\\n        -e '^_\\?internal_sigreturn$' \\\n        -e '^_\\?OnPrint$' \\\n        -e '^_\\?backtrace_uncompress_zdebug$' \\\n        -e '^reset_fn_list$' \\\n)\n\nnum_exports=$(echo \"${exports}\" | grep -v '^$' -c)\necho \"${exports}\"\n\nif [ ${num_exports} -gt 0 ];\nthen\n  exit 1\nelse\n  exit 0\nfi\n"
  },
  {
    "path": "scripts/githooks/.gitignore",
    "content": "/__pycache__/\n/*.pyc\n/*~"
  },
  {
    "path": "scripts/githooks/commit_msg.py",
    "content": "#!/usr/bin/env python3\n\n# Check a Git commit message according to the seven rules of a good commit message:\n# https://chris.beams.io/posts/git-commit/\nimport sys\n\n\nclass GitCommitMessage:\n    \"Represents a parsed Git commit message\"\n\n    rules = [\n        \"Separate subject from body with a blank line\",\n        \"Limit the subject line to 50 characters\",\n        \"Capitalize the subject line\",\n        \"Do not end the subject line with a period\",\n        \"Use the imperative mood in the subject line\",\n        \"Wrap the body at 72 characters\",\n        \"Use the body to explain what and why vs. how\",\n    ]\n\n    valid_rules = [False, False, False, False, False, False, False]\n\n    def __init__(self, filename=None):\n        lines = []\n\n        if filename is not None:\n            with open(filename, \"r\", encoding=\"utf-8\") as f:\n                for line in f:\n                    if line.startswith(\n                        \"# ------------------------ >8 ------------------------\"\n                    ):\n                        break\n                    if not line.startswith(\"#\"):\n                        lines.append(line)\n\n        self.parse_lines(lines)\n\n    def parse_lines(self, lines):\n        self.body_lines = []\n        self.subject = []\n\n        if not lines or len(lines) == 0:\n            return self\n\n        self.subject = lines[0]\n        self.subject_words = self.subject.split()\n        self.has_subject_body_separator = False\n\n        if len(lines) > 1:\n            self.has_subject_body_separator = len(lines[1].strip()) == 0\n\n            if self.has_subject_body_separator:\n                self.body_lines = lines[2:]\n            else:\n                self.body_lines = lines[1:]\n\n        return self\n\n    def check_subject_body_separtor(self):\n        \"Rule 1: Separate subject from body with a blank line\"\n\n        if len(self.body_lines) > 0:\n            return self.has_subject_body_separator\n        return True\n\n    def check_subject_limit(self):\n        \"Rule 2: Limit the subject line to 50 characters\"\n        return len(self.subject.rstrip(\"\\n\")) <= 50\n\n    def check_subject_capitalized(self):\n        \"Rule 3: Capitalize the subject line\"\n        return len(self.subject) > 0 and self.subject[0].isupper()\n\n    def check_subject_no_period(self):\n        \"Rule 4: Do not end the subject line with a period\"\n        return not self.subject.endswith(\".\")\n\n    common_first_words = [\n        \"Add\",\n        \"Adjust\",\n        \"Support\",\n        \"Change\",\n        \"Remove\",\n        \"Fix\",\n        \"Print\",\n        \"Track\",\n        \"Refactor\",\n        \"Combine\",\n        \"Release\",\n        \"Set\",\n        \"Stop\",\n        \"Make\",\n        \"Mark\",\n        \"Enable\",\n        \"Check\",\n        \"Exclude\",\n        \"Format\",\n        \"Correct\",\n    ]\n\n    def check_subject_imperative(self):\n        \"\"\"Rule 5: Use the imperative mood in the subject line.\n\n        We can only check for common mistakes here, like using\n        the -ing form of a verb or non-imperative version of\n        common verbs\n        \"\"\"\n\n        firstword = self.subject_words[0]\n\n        if firstword.endswith(\"ing\"):\n            return False\n\n        for word in self.common_first_words:\n            if firstword.startswith(word) and firstword != word:\n                return False\n\n        return True\n\n    def check_body_limit(self):\n        \"Rule 6: Wrap the body at 72 characters\"\n\n        if len(self.body_lines) == 0:\n            return True\n\n        for line in self.body_lines:\n            if len(line.rstrip(\"\\n\")) > 72:\n                return False\n\n        return True\n\n    def check_body_uses_why(self):\n        \"Rule 7: Use the body to explain what and why vs. how\"\n        # Not enforceable\n        return True\n\n    rule_funcs = [\n        check_subject_body_separtor,\n        check_subject_limit,\n        check_subject_capitalized,\n        check_subject_no_period,\n        check_subject_imperative,\n        check_body_limit,\n        check_body_uses_why,\n    ]\n\n    def check_the_seven_rules(self):\n        \"validates the commit message against the seven rules\"\n\n        num_violations = 0\n\n        for i, func in enumerate(self.rule_funcs):\n            res = func(self)\n            self.valid_rules[i] = res\n\n            if not res:\n                num_violations += 1\n\n        if num_violations > 0:\n            print()\n            print(\"**** WARNING ****\")\n            print()\n            print(\n                \"The commit message does not seem to comply with the project's guidelines.\"\n            )\n            print('Please try to follow the \"Seven rules of a great commit message\":')\n            print(\"https://chris.beams.io/posts/git-commit/\")\n            print()\n            print(\"The following rules are violated:\\n\")\n\n            for i in range(len(self.rule_funcs)):\n                if not self.valid_rules[i]:\n                    print(f'\\t* Rule {i+1}: \"{self.rules[i]}\"')\n\n        # Extra sanity checks beyond the seven rules\n        if len(self.body_lines) == 0:\n            print()\n            print(\"NOTE: the commit message has no body.\")\n            print(\"It is recommended to add a body with a description of your\")\n            print(\n                \"changes, even if they are small. Explain what and why instead of how:\"\n            )\n            print(\"https://chris.beams.io/posts/git-commit/#why-not-how\")\n\n        if len(self.subject_words) < 3:\n            print()\n            print(\"Warning: the subject line has less than three words.\")\n            print(\"Consider using a more explanatory subject line.\")\n\n        if num_violations > 0:\n            print()\n            print(\"Run 'git commit --amend' to change the commit message\")\n\n        print()\n\n        return num_violations\n\n\ndef main():\n    if len(sys.argv) != 2:\n        print(\"Unexpected number of arguments\")\n        sys.exit(1)\n\n    msg = GitCommitMessage(sys.argv[1])\n    return msg.check_the_seven_rules()\n\n\nif __name__ == \"__main__\":\n    main()\n    # Always exit with success. We could also fail the commit if with\n    # a non-zero exit code, but that might be a bit excessive and we'd\n    # have to save the failed commit message to a file so that it can\n    # be recovered.\n    sys.exit(0)\n"
  },
  {
    "path": "scripts/githooks/commit_msg_tests.py",
    "content": "#!/usr/bin/env python3\n\nimport unittest\nfrom commit_msg import GitCommitMessage\n\n\nclass TestCommitMsg(unittest.TestCase):\n    def testNoInput(self):\n        m = GitCommitMessage().parse_lines([])\n        self.assertEqual(len(m.body_lines), 0)\n        m = GitCommitMessage().parse_lines(None)\n        self.assertEqual(len(m.body_lines), 0)\n\n    def testParsing(self):\n        m = GitCommitMessage().parse_lines(\n            [\"This is a subject line\", \"   \", \"body line 1\", \"body line 2\"]\n        )\n        self.assertEqual(m.subject, \"This is a subject line\")\n        self.assertTrue(m.has_subject_body_separator)\n        self.assertEqual(m.body_lines[0], \"body line 1\")\n        self.assertEqual(m.body_lines[1], \"body line 2\")\n\n    def testNonImperative(self):\n        m = GitCommitMessage().parse_lines([\"Adds new file\"])\n        self.assertFalse(m.check_subject_imperative())\n\n        m.parse_lines([\"Adding new file\"])\n        self.assertFalse(m.check_subject_imperative())\n\n        # Default to accept\n        m.parse_lines([\"Foo bar\"])\n        self.assertTrue(m.check_subject_imperative())\n\n    def testSubjectBodySeparator(self):\n        m = GitCommitMessage().parse_lines([\"This is a subject line\"])\n        self.assertTrue(m.check_subject_body_separtor())\n        m = GitCommitMessage().parse_lines([\"This is a subject line\", \"body\"])\n        self.assertFalse(m.check_subject_body_separtor())\n        m = GitCommitMessage().parse_lines([\"This is a subject line\", \"\", \"body\"])\n        self.assertTrue(m.check_subject_body_separtor())\n        m = GitCommitMessage().parse_lines([\"This is a subject line\", \"   \", \"body\"])\n        self.assertTrue(m.check_subject_body_separtor())\n\n    def testSubjectLimit(self):\n        m = GitCommitMessage().parse_lines(\n            [\"This subject line is exactly 48 characters long\"]\n        )\n        self.assertTrue(m.check_subject_limit())\n        m = GitCommitMessage().parse_lines(\n            [\"This is a very long subject line that will obviously exceed the limit\"]\n        )\n        self.assertFalse(m.check_subject_limit())\n        m = GitCommitMessage().parse_lines(\n            [\"This 50-character subject line ends with an LF EOL\\n\"]\n        )\n        self.assertTrue(m.check_subject_limit())\n\n    def testSubjectCapitalized(self):\n        m = GitCommitMessage().parse_lines([\"This subject line is capitalized\"])\n        self.assertTrue(m.check_subject_capitalized())\n        m = GitCommitMessage().parse_lines([\"this subject line is not capitalized\"])\n        self.assertFalse(m.check_subject_capitalized())\n\n    def testSubjectNoPeriod(self):\n        m = GitCommitMessage().parse_lines([\"This subject line ends with a period.\"])\n        self.assertFalse(m.check_subject_no_period())\n        m = GitCommitMessage().parse_lines(\n            [\"This subject line does not end with a period\"]\n        )\n        self.assertTrue(m.check_subject_no_period())\n\n    def testBodyLimit(self):\n        m = GitCommitMessage().parse_lines(\n            [\"This is a subject line\", \" \", \"A short body line\"]\n        )\n        self.assertTrue(m.check_body_limit())\n        m = GitCommitMessage().parse_lines(\n            [\n                \"This is a subject line\",\n                \" \",\n                \"A very long body line which certainly exceeds the 72 char recommended limit\",\n            ]\n        )\n        self.assertFalse(m.check_body_limit())\n        m = GitCommitMessage().parse_lines(\n            [\n                \"This is a subject line\",\n                \" \",\n                \"A body line with exactly 72 characters, followed by an EOL (Unix-style).\\n\",\n            ]\n        )\n        self.assertTrue(m.check_body_limit())\n\n    def testCheckAllRules(self):\n        m = GitCommitMessage().parse_lines(\n            [\"This is a subject line\", \"\", \"A short body line\"]\n        )\n        self.assertEqual(0, m.check_the_seven_rules())\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "scripts/label-released.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nLook at commits between the given release and the previous one, and label all\nPRs that made these commits with the \"released-...\" label.\n\"\"\"\n\nimport os\nimport sys\nimport argparse\nimport re\nimport subprocess\nimport requests\nimport github  # This is PyGithub.\nimport more_itertools\n\nOWNER = \"timescale\"\nREPO = \"timescaledb\"\nTOKEN = os.environ.get(\"GH_TOKEN\")\nif not TOKEN:\n    print(\"Specify the GitHub token in GH_TOKEN environment variable.\", file=sys.stderr)\n    sys.exit(1)\n\n\ndef git_check(cmd: str):\n    subprocess.run(f\"git {cmd}\", shell=True, check=True)\n\n\ndef git_output(cmd: str) -> str:\n    return subprocess.check_output(f\"git {cmd}\", shell=True, text=True).strip()\n\n\ndef run_query(query, params):\n    \"\"\"A simple function to use requests.post to make the GraphQL API call.\"\"\"\n\n    request = requests.post(\n        \"https://api.github.com/graphql\",\n        json={\"query\": query, \"variables\": params},\n        headers={\"Authorization\": f\"Bearer {TOKEN}\"} if TOKEN else None,\n        timeout=20,\n    )\n    response = request.json()\n\n    # Have to work around the unique GraphQL convention of returning 200 for errors.\n    if request.status_code != 200 or \"errors\" in response:\n        raise ValueError(\n            f\"Query failed to run by returning code of {request.status_code}.\"\n            f\"\\nQuery: '{query}'\"\n            f\"\\nResponse: '{request.json()}'\"\n        )\n\n    return response[\"data\"]\n\n\nparser = argparse.ArgumentParser()\nparser.add_argument(\"--release\", required=True, help=\"Tag to process, e.g. 2.20.0\")\nparser.add_argument(\"--dry-run\", action=\"store_true\")\nargs = parser.parse_args()\ntarget_tag = args.release\ndry_run = args.dry_run\n\n# Create the label if needed\ngh = github.Github(TOKEN)\nrepo = gh.get_repo(f\"{OWNER}/{REPO}\")\nlabel_name = f\"released-{target_tag}\"\ntry:\n    pygithub_label = repo.get_label(label_name)\n    label_id = pygithub_label.raw_data[\"node_id\"]\nexcept github.UnknownObjectException:\n    if not args.dry_run:\n        pygithub_label = repo.create_label(\n            label_name, \"d3d3d3\", f\"Released in {target_tag}\"\n        )\n        label_id = pygithub_label.raw_data[\"node_id\"]\n    else:\n        label_id = \"<dry run>\"\nprint(f\"Label will be: {label_name}\")\n\n# Make sure the branches are present in the repository\ngit_check(\"fetch --depth=1000 origin main:refs/remotes/origin/main\")\ngit_check(f\"fetch --depth=1000 origin tag {target_tag}\")\n\n# Read the previous release from version.config.\ntry:\n    cfg = git_output(f\"show {target_tag}:version.config\")\nexcept subprocess.CalledProcessError:\n    sys.exit(f\"Error: cannot read version.config at '{target_tag}'\")\n\nm = re.search(r\"^(?:previous_version|update_from_version)\\s*=\\s*(\\S+)\", cfg, re.M)\nif not m:\n    sys.exit(\"Error: version.config missing previous_version/update_from_version\")\nprev_tag = m.group(1)\ngit_check(f\"fetch --depth=1000 origin tag {prev_tag}\")\n\nprint(f\"Comparing tags: {prev_tag} → {target_tag}\")\nif dry_run:\n    print(\"[dry-run] no changes will be made\")\n\n\ndef fetch_commits_with_prs(starting_commit, cutoff_date):\n    \"\"\"\n    Fetch the commits starting from the given one until the cutoff\n    date, with the associated PRs, using paginated GraphQL request.\n    \"\"\"\n    GQL_COMMITS = \"\"\"\n    query CommitsWithPRs(\n      $owner: String!, $repo: String!,\n      $expr: String!, $since: GitTimestamp!,\n      $first: Int!, $after: String\n    ) {\n      repository(owner:$owner,name:$repo) {\n        object(expression:$expr) {\n          ... on Commit {\n            history(first:$first, since:$since, after:$after) {\n              pageInfo { hasNextPage endCursor }\n              nodes {\n                oid\n                messageHeadline\n                # We only handle the commits with one PR, so fetch at most two.\n                associatedPullRequests(first:2) {\n                  nodes { id number title baseRefName }\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n    \"\"\"\n\n    nodes = []\n    after = None\n    while True:\n        params = {\n            \"owner\": OWNER,\n            \"repo\": REPO,\n            \"expr\": starting_commit,\n            \"since\": cutoff_date,\n            \"first\": 100,\n            \"after\": after,\n        }\n        hist = run_query(GQL_COMMITS, params)[\"repository\"][\"object\"][\"history\"]\n\n        nodes.extend(hist[\"nodes\"])\n        page_info = hist[\"pageInfo\"]\n        if not page_info[\"hasNextPage\"]:\n            break\n        after = page_info[\"endCursor\"]\n        print(\"Fetching next page...\")\n\n    print(f\"Fetched {len(nodes)} commits with PR info starting from {starting_commit}\")\n    return nodes\n\n\ntarget_release = git_output(f\"rev-parse {target_tag}^0\")\nprint(f\"Target release commit {git_output(f'log -1 --oneline {target_release}')}\")\nprev_release = git_output(f\"rev-parse {prev_tag}^0\")\nprint(f\"Prev release commit {git_output(f'log -1 --oneline {prev_release}')}\")\nprev_release_fork_sha = git_output(f\"merge-base {target_release} {prev_release}\")\nprint(\n    f\"Prev release fork point {git_output(f'log -1 --oneline {prev_release_fork_sha}')}\"\n)\nprev_release_fork_date = git_output(f\"show -s --format=%cI {prev_release_fork_sha}\")\nprint(f\"Prev release fork date {prev_release_fork_date}\")\nmain_fork_sha = git_output(f\"merge-base {target_release} origin/main\")\nprint(f\"Main fork point {git_output(f'log -1 --oneline {main_fork_sha}')}\")\nmain_fork_date = git_output(f\"show -s --format=%cI {main_fork_sha}\")\nprint(f\"Main fork date {main_fork_date}\")\n\n\n# This is the relationship between the various refs we've built above:\n# For a patch release:\n# (current release branch)  -(backports)---prev_release_fork_sha---target_release--->\n#                          /  ^ ^ ^\n# (main) >----main_fork_sha---------------->\n#\n# For a minor release:\n# (prev release branch)        prev_release-->\n#                             /\n# (current release branch)   /                 -(backports)-target_release-->\n#                           /                 /  ^ ^ ^\n# (main) >-prev_release_fork_sha---main_fork_sha--------------->\n#\n# We can't use the commit SHAs for the commit lookups due to API limitations, so\n# we use the dates instead.\n# Now, perform the lookups for release branch commits and the potentially\n# backported main commits with the respective PRs.\n\nbranch_commit_nodes = fetch_commits_with_prs(target_release, prev_release_fork_date)\n\nmain_commit_nodes = fetch_commits_with_prs(\"main\", main_fork_date)\n\n# We're going to match the commits by title to account for backports.\nmain_commit_title_to_pr = {\n    node[\"messageHeadline\"]: prs[0]\n    for node in main_commit_nodes\n    if (prs := node[\"associatedPullRequests\"][\"nodes\"]) and len(prs) == 1\n}\n\nprint(f\"On main ({len(main_commit_title_to_pr)} PRs):\")\nprint(\n    \"\\n\".join(\n        [\n            f'#{pr[\"number\"]}: {pr[\"title\"]} <- {title}'\n            for title, pr in main_commit_title_to_pr.items()\n        ]\n    )\n)\nprint()\n\nbranch_commit_title_to_pr = {\n    node[\"messageHeadline\"]: prs[0]\n    for node in branch_commit_nodes\n    if (prs := node[\"associatedPullRequests\"][\"nodes\"]) and len(prs) == 1\n}\n\nprint(f\"On branch ({len(branch_commit_title_to_pr)} PRs):\")\nprint(\n    \"\\n\".join(\n        [\n            f'#{pr[\"number\"]}: {pr[\"title\"]} <- {title}'\n            for title, pr in branch_commit_title_to_pr.items()\n        ]\n    )\n)\nprint()\n\n# The commits with same titles in main and release branch, but with different PRs,\n# are backported commits. We have to label the PR to main in that case, and not\n# the backport PR.\nbackported_titles = {\n    title: pr\n    for title, pr in main_commit_title_to_pr.items()\n    if title in branch_commit_title_to_pr\n    and pr[\"number\"] != branch_commit_title_to_pr[title][\"number\"]\n}\n\nprint(f\"Backported ({len(backported_titles)} PRs):\")\nprint(\n    \"\\n\".join([f'#{pr[\"number\"]}: {pr[\"title\"]}' for pr in backported_titles.values()])\n)\nprint()\n\nbranch_commit_title_to_pr.update(backported_titles)\nprint(f\"To label as {label_name} ({len(branch_commit_title_to_pr)} PRs):\")\nprint(\n    \"\\n\".join(\n        [f'#{pr[\"number\"]}: {pr[\"title\"]}' for pr in branch_commit_title_to_pr.values()]\n    )\n)\nprint()\n\n# Label the PRs in bulk using GraphQL.\nids = list({pr[\"id\"] for pr in branch_commit_title_to_pr.values()})\nfor chunk in more_itertools.chunked(ids, 10):\n    parts = [\n        f'p{j}: addLabelsToLabelable(input: {{labelableId:\"{nid}\",labelIds:[\"{label_id}\"]}}) {{ clientMutationId }}'\n        for j, nid in enumerate(chunk)\n    ]\n    gql = \"mutation BulkLabel {\\n\" + \"\\n\".join(parts) + \"\\n}\"\n    if dry_run:\n        print(f\"\\nDry-run for {len(chunk)} PRs:\\n{gql}\")\n    else:\n        run_query(gql, {})\n        print(f\"Labeled {len(chunk)} PRs.\")\n"
  },
  {
    "path": "scripts/license_apache.spec",
    "content": "# This file and its contents are licensed under the Apache License 2.0.\n# Please see the included NOTICE for copyright information and\n# LICENSE-APACHE for a copy of the license.\n"
  },
  {
    "path": "scripts/license_tsl.spec",
    "content": "# This file and its contents are licensed under the Timescale License.\n# Please see the included NOTICE for copyright information and\n# LICENSE-TIMESCALE for a copy of the license.\n"
  },
  {
    "path": "scripts/memory_leaks.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nDROP DATABASE IF EXISTS test;\nCREATE DATABASE test;\n\\c test\n\\timing\n\nDROP EXTENSION IF EXISTS timescaledb CASCADE;\nCREATE EXTENSION timescaledb;\n\nSELECT pg_backend_pid();\nSELECT pg_sleep(5);\n\nCREATE TABLE metrics(time timestamptz, device text, value float);\nSELECT create_hypertable('metrics', 'time');\n\nINSERT INTO metrics SELECT '2020-01-01'::timestamptz + format('%ss',time)::interval, format('Device %s',time%10), random() FROM generate_series(1,1000000) g(time);\n\n\n\\o /dev/null\n\\set ECHO none\n\\timing off\n\\echo Starting 1 million SELECT queries\nSELECT format($$SELECT * FROM metrics WHERE device = 'Device %s' ORDER BY time DESC LIMIT 1;$$,time % 10) FROM generate_series(1,1000000) g(time) \\gexec\n\n\\echo Starting 10k UPDATE queries\nSELECT format($$UPDATE metrics SET value = %s WHERE time < '2020-01-02' AND device = 'Device %s';$$,time,time % 10) FROM generate_series(1,10000) g(time) \\gexec\n\n\\echo Starting 50k INSERTs with batches of 1000\nSELECT format($$INSERT INTO metrics SELECT '2021-01-01'::timestamptz + '%ss'::interval + format('%%s', time)::interval, format('Device %%s',time %% 10), random() FROM generate_series(1,1000) g(time);$$,time) FROM generate_series(1,50000) g(time) \\gexec\n\n"
  },
  {
    "path": "scripts/out_of_order_random_direct.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nDROP DATABASE IF EXISTS test;\nCREATE DATABASE test;\n\\c test\n\\timing\n\nDROP EXTENSION IF EXISTS timescaledb CASCADE;\nCREATE EXTENSION timescaledb;\n\nSELECT pg_backend_pid();\n\nCREATE TABLE test_hyper (i bigint, j double precision, k bigint, ts timestamp);\nSELECT create_hypertable('test_hyper', 'i', chunk_time_interval=>10);\n\nINSERT INTO test_hyper SELECT random()*100, random()*100, x*10, _timescaledb_functions.to_timestamp(x)  FROM generate_series(1,100000000) AS x;\n"
  },
  {
    "path": "scripts/perltidy_format_all.sh",
    "content": "#!/bin/bash\n\nSCRIPTDIR=$(cd \"$(dirname $0)\" || exit; pwd)\nBASEDIR=$(dirname $SCRIPTDIR)\n\nPERLTIDY=${PERLTIDY:-perltidy}\nPERLTIDY_CONFIG=${PERLTIDY_CONFIG:-$BASEDIR/.perltidyrc}\n\nif [ ! -f \"$PERLTIDY_CONFIG\" ]; then\n   echo \"perltidy config '$PERLTIDY_CONFIG' not found\"\n   exit 1\nfi\n\nfind \"$BASEDIR\"/test -name '*.p[lm]' -exec \"$PERLTIDY\" --profile=\"$PERLTIDY_CONFIG\" -b -bext=/ {} +\nfind \"$BASEDIR\"/tsl/test -name '*.p[lm]' -exec \"$PERLTIDY\" --profile=\"$PERLTIDY_CONFIG\" -b -bext=/ {} +\n\n"
  },
  {
    "path": "scripts/release/build_post_release_artefacts.sh",
    "content": "#!/bin/bash\n\n#\n# Build the necessary artefacts for the forwardporting the\n# the release to `main` (Minor & Patch)\n# - up & down files for the next version\n# - latest.sql & reverse.sql\n# - sets new version in version.config\n# - adjusts the CMakeLists.txt\n#\n# Param: <published_version>\n#\n\nset -eu\n\nif [ \"$#\" -ne 1 ]; then\n    echo \"${0} <published_version>\"\n    exit 2\nfi\n\nPUBLISHED_VERSION=$1\nPREVIOUS_VERSION=$(tail -1 version.config | cut -d ' ' -f 3 | cut -d '-' -f 1)\n\necho \"fetch the up & downgrade files\"\nURL_UPGRADE=\"https://raw.githubusercontent.com/timescale/timescaledb/refs/tags/${PUBLISHED_VERSION}/sql/updates/${PREVIOUS_VERSION}--${PUBLISHED_VERSION}.sql\"\nURL_DOWNGRADE=\"https://raw.githubusercontent.com/timescale/timescaledb/refs/tags/${PUBLISHED_VERSION}/sql/updates/${PUBLISHED_VERSION}--${PREVIOUS_VERSION}.sql\"\necho $URL_UPGRADE\ncurl --fail -o sql/updates/${PREVIOUS_VERSION}--${PUBLISHED_VERSION}.sql $URL_UPGRADE\ncurl --fail -o sql/updates/${PUBLISHED_VERSION}--${PREVIOUS_VERSION}.sql $URL_DOWNGRADE\n\n# find last up & down grade files\nLAST_UPDATE_FILE=$(find sql/updates/*--${PREVIOUS_VERSION}.sql | head -1 | cut -d '/' -f 3)\nLAST_DOWNGRADE_FILE=$(find sql/updates/${PREVIOUS_VERSION}--*.sql | head -1 | cut -d '/' -f 3)\n\necho \"register upgrade sql file\"\ngawk -i inplace '/'${LAST_UPDATE_FILE}')/ { print; print \"    updates/'${PREVIOUS_VERSION}'--'${PUBLISHED_VERSION}'.sql)\"; next }1' ./sql/CMakeLists.txt\nsed -i.bak \"s/${LAST_UPDATE_FILE})/${LAST_UPDATE_FILE}/g\" ./sql/CMakeLists.txt\n\necho \"register downgrade sql file\"\ngawk -i inplace '/'${LAST_DOWNGRADE_FILE}')/ { print; print \"    '${PUBLISHED_VERSION}'--'${PREVIOUS_VERSION}'.sql)\"; next }1' ./sql/CMakeLists.txt\nsed -i.bak \"s/${LAST_DOWNGRADE_FILE})/${LAST_DOWNGRADE_FILE}/g\" ./sql/CMakeLists.txt\n\n# CHANGELOG\necho \"fetching CHANGELOG\"\nURL_CHANGELOG=\"https://raw.githubusercontent.com/timescale/timescaledb/refs/tags/${PUBLISHED_VERSION}/CHANGELOG.md\"\ncurl --silent --fail -o CHANGELOG.md $URL_CHANGELOG\n\n# .unreleased files\necho \"remove .unreleased files\"\n#git diff ${PUBLISHED_VERSION}..main .unreleased/* | xargs rm -v\n\n# Set new previous version\necho \"Set new previous version version.config to ${PUBLISHED_VERSION}\"\nsed -i '' -e \"s/^previous_version = .*/previous_version = $PUBLISHED_VERSION/\" version.config\ntail -n 1 version.config\n"
  },
  {
    "path": "scripts/release/build_release_artefacts.sh",
    "content": "#!/bin/bash\n\n#\n# Build the necessary artefacts for the next release (Minor & Patch)\n# - up & down files for the next version\n# - latest.sql & reverse.sql\n# - sets new version in version.config\n# - adjusts the CMakeLists.txt\n#\n# Param: <current_version>\n# Param: <nextt_version>\n#\n\nset -eu\n\nif [ \"$#\" -ne 2 ]; then\n    echo \"${0} <current_version> <next_version>\"\n    exit 2\nfi\n\nCURRENT_VERSION=$1\nNEXT_VERSION=$2\n\nUPDATE_FILE=\"$CURRENT_VERSION--$NEXT_VERSION.sql\"\nDOWNGRADE_FILE=\"$NEXT_VERSION--$CURRENT_VERSION.sql\"\nLAST_UPDATE_FILE=$(find sql/updates/*--${CURRENT_VERSION}.sql | head -1 | cut -d '/' -f 3)\nLAST_DOWNGRADE_FILE=$(find sql/updates/${CURRENT_VERSION}--*.sql | head -1 | cut -d '/' -f 3)\n\n# prepare next up & down files\necho \"generate up and downgrade files\"\ncp ./sql/updates/latest-dev.sql ./sql/updates/$UPDATE_FILE\ncp ./sql/updates/reverse-dev.sql ./sql/updates/$DOWNGRADE_FILE\n\necho \"truncate dev up & downgrade paths\"\ntruncate -s 0 ./sql/updates/latest-dev.sql\ntruncate -s 0 ./sql/updates/reverse-dev.sql\n\n# CMakeLists\necho \"register upgrade sql file\"\ngawk -i inplace '/'${LAST_UPDATE_FILE}')/ { print; print \"    updates/'${UPDATE_FILE}')\"; next }1' ./sql/CMakeLists.txt\nsed -i.bak \"s/${LAST_UPDATE_FILE})/${LAST_UPDATE_FILE}/g\" ./sql/CMakeLists.txt\n\necho \"register downgrade sql file\"\ngawk -i inplace '/'${LAST_DOWNGRADE_FILE}')/ { print; print \"    '${DOWNGRADE_FILE}')\"; next }1' ./sql/CMakeLists.txt\nsed -i.bak \"s/${LAST_DOWNGRADE_FILE})/${LAST_DOWNGRADE_FILE}/g\" ./sql/CMakeLists.txt\n\necho \"register reverse path\"\nsed -i.bak \"s/FILE reverse-dev.sql)/FILE ${DOWNGRADE_FILE})/g\" ./sql/CMakeLists.txt\n\n# Set only next minor release version in version.config \n# and create this as a separate PR on `main`\necho \"Set next minor release version.config\"\nsed -i.bak \"s/${NEXT_VERSION}-dev/${NEXT_VERSION}/g\" version.config\nrm version.config.bak\nhead -n1 version.config\n"
  },
  {
    "path": "scripts/release/create_minor_release_branch.sh",
    "content": "#!/bin/bash\nset -eu\n\n# Folder, where we have cloned repositories' sources\nSOURCES_DIR=\"timescaledb\"\n\nFORK_DIR=\"timescaledb\"\n\necho \"---- Deriving the release related versions from main ----\"\n\ncd ~/\"$SOURCES_DIR\"/\"$FORK_DIR\"\ngit fetch --all\n\nNEW_PATCH_VERSION=\"0\"\n\nNEW_VERSION=$(head -1 version.config | cut -d ' ' -f 3 | cut -d '-' -f 1)\n\nRELEASE_BRANCH=\"${NEW_VERSION/%.$NEW_PATCH_VERSION/.x}\"\n\necho \"RELEASE_BRANCH is $RELEASE_BRANCH\"\necho \"NEW_VERSION is $NEW_VERSION\"\n\necho \"---- Creating the version branch from main ----\"\n\ngit fetch --all\ngit checkout -b \"$RELEASE_BRANCH\" origin/main\ngit push origin \"$RELEASE_BRANCH\":\"$RELEASE_BRANCH\"\n\n"
  },
  {
    "path": "scripts/release/ready_fork_for_commit.sh",
    "content": "#!/bin/bash\nset -eu\n\n\necho \"---- Setting git user parameters as per current global git configuration ----\"\n\n# GITHUB_USERNAMES\nGH_EMAIL=$(git config user.email)\nGH_FULL_USERNAME=$(git config user.name)\nGH_USERNAME=$(gh auth status | grep 'Logged in to' |cut -d ' ' -f 9)\n\necho \"GH_EMAIL is $GH_EMAIL\"\necho \"GH_FULL_USERNAME is $GH_FULL_USERNAME\"\necho \"GH_USERNAME is $GH_USERNAME\"\n\n# Folder, where we have cloned repositories' sources\nSOURCES_DIR=\"sources\"\n\n# Derived Variables\nFORK_DIR=\"$GH_USERNAME-timescaledb\"\n\necho \"---- Updating fork with upstream for user $GH_USERNAME ----\"\n\ngh repo sync \"$GH_USERNAME/timescaledb\" -b main\n\n\necho \"---- Cloning the fork to $FORK_DIR ----\"\n\ncd\ncd \"$SOURCES_DIR\"\nrm -rf \"$FORK_DIR\"\ngit clone git@github.com:\"$GH_USERNAME\"/timescaledb.git \"$FORK_DIR\"\ncd \"$FORK_DIR\"\ngit branch\ngit pull && git diff HEAD\ngit log -n 2\n\necho \"---- Configuring the fork for commit ----\"\n\ngit config user.name \"$GH_FULL_USERNAME\"\ngit config user.email \"$GH_EMAIL\"\ngit remote add upstream git@github.com:timescale/timescaledb.git\ngit config -l\ngit remote -v\n\n\necho \"---- Updating tags from upstream on the fork ----\"\n\ngit fetch --tags upstream\ngit push --tags origin main\n# Check the needed branch name here - could it be 2.14.x ?\n# git push -f --tags origin main\n\n"
  },
  {
    "path": "scripts/run_sql.sh",
    "content": "#!/usr/bin/env bash\n\n# To avoid pw writing, add localhost:5432:*:postgres:test to ~/.pgpass\nset -u\nset -e\n\nPWD=$(pwd)\nDIR=$(dirname $0)\n\nexport PGUSER=${PGUSER:-postgres}\nexport PGHOST=${PGHOST:-localhost}\nexport PGDATABASE=${PGDATABASE:-timescaledb}\n\npsql -v ON_ERROR_STOP=1 -q -X -f $DIR/sql/$1\n"
  },
  {
    "path": "scripts/shellcheck-ci.sh",
    "content": "#!/bin/bash\nset -e\n\nfind ./* .github -type f -executable \\\n  -exec bash -c '[ \"$(file --brief --mime-type \"$1\")\" == \"text/x-shellscript\" ]' sh {} \\; \\\n  -not -exec shellcheck -x --exclude=SC2086 {} \\; \\\n  -print \\\n  | grep . \\\n  && exit 1 \\\n  || exit 0\n"
  },
  {
    "path": "scripts/sql_license_apache.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n"
  },
  {
    "path": "scripts/sql_license_tsl.sql",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n"
  },
  {
    "path": "scripts/start-local-docker.ps1",
    "content": "#requires -Version 5.1\n$ErrorActionPreference = \"Stop\"\n\n# --- Configuration ---\n$IMAGE_NAME     = \"timescale/timescaledb-ha:pg18\"\n$CONTAINER_NAME = \"timescaledb-ha-pg18-quickstart\"\n$DB_PASSWORD    = \"password\"\n$DB_PORT        = 6543\n$VOLUME_NAME    = \"timescaledb_data\"\n\n# Catch-all so users see the error and return to command prompt\ntrap {\n  Write-Host \"\" \n  Write-Host (\"[ERROR] \" + $_.Exception.Message) -ForegroundColor Red\n  return  # Return to command prompt instead of exiting\n}\n\nfunction Fail($msg) {\n  throw $msg  # IMPORTANT: do NOT use exit here; it kills the host instantly when invoked via iwr|iex\n}\nfunction Info($msg)    { Write-Host \"[INFO] $msg\" -ForegroundColor Cyan }\nfunction Success($msg) { Write-Host \"[SUCCESS] $msg\" -ForegroundColor Green }\n\nfunction Test-Command($name) {\n  return [bool](Get-Command $name -ErrorAction SilentlyContinue)\n}\n\nClear-Host\nWrite-Host \"\"\nWrite-Host \"  _____ _                              __      ____  ____  \" -ForegroundColor Yellow\nWrite-Host \" |_   _(_)_ __ ___   ___  ___  ___ __ _| | ___|  _ \\| __ ) \" -ForegroundColor Yellow\nWrite-Host \"   | | | | '_ \\` _ \\ / _ \\/ __|/ __/ _\\` | |/ _ \\ | | |  _ \\ \" -ForegroundColor Yellow\nWrite-Host \"   | | | | | | | | |  __/\\__ \\ (_| (_| | |  __/ |_| | |_) |\" -ForegroundColor Yellow\nWrite-Host \"   |_| |_|_| |_| |_|\\___||___/\\___\\__,_|_|\\___|____/|____/ \" -ForegroundColor Yellow\nWrite-Host \"\"\n\nWrite-Host \"1. System Check\" -ForegroundColor White\n\nif (-not (Test-Command docker)) {\n  Fail \"Docker is not found. Install Docker Desktop first.`nDownload Docker: https://www.docker.com/get-started/\"\n}\nSuccess \"Docker found\"\n\ntry {\n  docker info *> $null\n} catch {\n  Fail \"Docker is installed but not running. Start Docker Desktop and try again.`nDownload Docker: https://www.docker.com/get-started/\"\n}\nSuccess \"Docker is running\"\n\n# Cleanup old container\n$existing = (docker ps -aq -f \"name=^/$CONTAINER_NAME$\").Trim()\nif ($existing) {\n  Info \"Found existing container. Removing it ..\"\n  docker rm -f $CONTAINER_NAME *> $null\n  Success \"Removed existing container\"\n}\n\nWrite-Host \"\"\nWrite-Host \"2. Deployment\" -ForegroundColor White\n\nInfo \"Pulling image ($IMAGE_NAME) ..\"\ndocker pull $IMAGE_NAME | Out-Host\nSuccess \"Image pulled ($IMAGE_NAME)\"\n\nInfo \"Starting TimescaleDB on port $DB_PORT ..\"\ndocker run -d `\n  --name $CONTAINER_NAME `\n  -p \"$DB_PORT`:5432\" `\n  -e \"POSTGRES_PASSWORD=$DB_PASSWORD\" `\n  -v \"$VOLUME_NAME`:/home/postgres/pgdata/data\" `\n  $IMAGE_NAME *> $null\nSuccess \"Container started ($CONTAINER_NAME)\"\n\n# Wait for readiness\nInfo \"Waiting for database to accept connections ..\"\n$retries = 30\n$ready = $false\nfor ($i=0; $i -lt $retries; $i++) {\n  try {\n    docker exec $CONTAINER_NAME pg_isready -U postgres *> $null\n    if ($LASTEXITCODE -eq 0) { $ready = $true; break }\n  } catch {}\n  Start-Sleep -Seconds 1\n}\n\nif (-not $ready) {\n  Fail \"Database failed to start in time. Check logs with: docker logs $CONTAINER_NAME\"\n}\nSuccess \"Database is ready!\"\n\n# Get versions without host psql: use psql inside container\n$tsdbVersion = (docker exec $CONTAINER_NAME psql -U postgres -t -A -c \"select extversion from pg_extension where extname='timescaledb';\" 2>$null).Trim()\n$pgVersion   = (docker exec $CONTAINER_NAME psql -U postgres -t -A -c \"select split_part(version(),' ',2);\" 2>$null).Trim()\n\nWrite-Host \"\"\nWrite-Host \"╔══════════════════════════════════════════════════════════╗\" -ForegroundColor Green\nWrite-Host \"║             🚀 SETUP COMPLETED SUCCESSFULLY              ║\" -ForegroundColor Green\nWrite-Host \"╚══════════════════════════════════════════════════════════╝\" -ForegroundColor Green\nWrite-Host \"\"\n\nWrite-Host (\"   Postgres:    {0}\" -f ($(if ($pgVersion) { $pgVersion } else { \"(unknown)\" })))\nWrite-Host (\"   TimescaleDB: {0}\" -f ($(if ($tsdbVersion) { $tsdbVersion } else { \"(unknown)\" })))\nWrite-Host \"\"\nWrite-Host \"   Container:   $CONTAINER_NAME\"\nWrite-Host \"   Port:        $DB_PORT\"\nWrite-Host \"   User:        postgres\"\nWrite-Host \"   Password:    $DB_PASSWORD\"\nWrite-Host \"\"\nWrite-Host \"   Connect:\"\nWrite-Host \"     psql \\\"postgres://postgres:$DB_PASSWORD@localhost:$DB_PORT/postgres\\\"\"\nWrite-Host \"\"\nWrite-Host \"   Getting Started:\"\nWrite-Host \"    * Quick start:    https://tsdb.co/quick-start\"\nWrite-Host \"    * NYC taxis:      https://tsdb.co/quick-start-nyc-taxis\"\nWrite-Host \"    * S&P 500 stocks: https://tsdb.co/quick-start-sp500-stocks\"\nWrite-Host \"    * Sensor devices: https://tsdb.co/quick-start-sensors\"\nWrite-Host \"\"\n\n# Success: optionally pause if launched in a transient window\n# Pause-OnExit 0\n"
  },
  {
    "path": "scripts/start-local-docker.sh",
    "content": "#!/bin/sh\nset -e\n\n#\n# Getting Started script with Docker\n# \n# URL: https://tsdb.co/start-local \n#\n\n# --- Configuration ---\nIMAGE_NAME=\"timescale/timescaledb-ha:pg18\" \nCONTAINER_NAME=\"timescaledb-ha-pg18-quickstart\"\nDB_PASSWORD=\"password\"\nDB_PORT=\"6543\"\n\n# Colors\nRESET=\"\\033[0m\"\nBOLD=\"\\033[1m\"\nCOLOR_GREEN=\"\\033[32m\"\nCOLOR_BLUE=\"\\033[34m\"\nCOLOR_RED=\"\\033[31m\"\nCOLOR_CYAN=\"\\033[36m\"\nCOLOR_YELLOW=\"\\033[33m\"\n\n# --- Formatting Helpers ---\n# Hide cursor for cleaner UI, restore on exit\ncleanup() {\n    tput cnorm # restore cursor\n}\ntrap cleanup EXIT INT TERM\n\nsuccess() { printf \"%b✔%b %s\\n\" \"${COLOR_GREEN}\" \"${RESET}\" \"$1\"; }\nerror() { printf \"%b✖%b %s\\n\" \"${COLOR_RED}\" \"${RESET}\" \"$1\"; exit 1; }\n\n# --- Spinner Function ---\n# Usage: Run command in background, then call: spinner $! \"Loading text...\"\nspinner() {\n    local pid=$1\n    local text=$2\n    local delay=0.1\n    local spinstr='|/-\\'\n    \n    # Hide cursor\n    tput civis\n\n    printf \"%s  \" \"$text\"\n\n    while kill -0 \"$pid\" 2>/dev/null; do\n        # Loop through spin string characters\n        # Note: POSIX sh handles string slicing differently, so we use a simpler approach\n        printf \"\\b%s\" \"|\"\n        sleep $delay\n        printf \"\\b%s\" \"/\"\n        sleep $delay\n        printf \"\\b%s\" \"-\"\n        sleep $delay\n        printf \"\\b%s\" \"\\\\\"\n        sleep $delay\n    done\n    \n    # Clear spinner character\n    printf \"\\b \" \n    # Restore cursor\n    tput cnorm\n}\n\nheader() {\n    clear\n    echo \"\"\n    printf \"%b  _____ _                              __      ____  ____  %b\\n\" \"${COLOR_YELLOW}\" \"${RESET}\"\n    printf \"%b |_   _(_)_ __ ___   ___  ___  ___ __ _| | ___|  _ \\| __ ) %b\\n\" \"${COLOR_YELLOW}\" \"${RESET}\"\n    printf \"%b   | | | | '_ \\` _ \\ / _ \\/ __|/ __/ _\\` | |/ _ \\ | | |  _ \\ %b\\n\" \"${COLOR_YELLOW}\" \"${RESET}\"\n    printf \"%b   | | | | | | | | |  __/\\__ \\ (_| (_| | |  __/ |_| | |_) |%b\\n\" \"${COLOR_YELLOW}\" \"${RESET}\"\n    printf \"%b   |_| |_|_| |_| |_|\\___||___/\\___\\__,_|_|\\___|____/|____/ %b\\n\" \"${COLOR_YELLOW}\" \"${RESET}\"\n    echo \"\"\n}\n\nget_timescaledb_version() {\n    psql \"postgres://postgres:$DB_PASSWORD@localhost:$DB_PORT/postgres\" \\\n        -c \"select extversion from pg_extension where extname = 'timescaledb';\" \\\n        | awk 'NR==3 {print $1}'\n}\n\nget_postgres_version() {\n    psql \"postgres://postgres:$DB_PASSWORD@localhost:$DB_PORT/postgres\" \\\n        -t \\\n        -c \"select version();\" \\\n        | awk '{print $2}'\n}\n\n# --------------------------------------------------------------- #\n\nheader\n\n# --- 1. Check for Docker ---\nprintf \"%b1. System Check%b\\n\" \"${BOLD}\" \"${RESET}\"\n\nif ! command -v docker > /dev/null 2>&1; then\n    error \"Docker is not found. Please install Docker Desktop (Windows/Mac) or Docker Engine (Linux) first.\"\nelse\n    success \"Docker found\"\nfi\n\nif ! docker info > /dev/null 2>&1; then\n    error \"Docker is installed but not running. Please start Docker and try again.\"\nelse\n    success \"Docker is running\"\nfi\n\n# Cleanup Old Containers\nif [ \"$(docker ps -aq -f name=^/${CONTAINER_NAME}$)\" ]; then\n    success \"Found existing container. Removing it ..\"\n    docker rm -f $CONTAINER_NAME > /dev/null\nfi\n\n# --- 2. Pull and Run ---\necho \"\"\nprintf \"%b2. Deployment%b\\n\" \"${BOLD}\" \"${RESET}\"\n\n# Start pull in background\nprintf \"%b::%b \" \"${COLOR_CYAN}\" \"${RESET}\"\ndocker pull -q $IMAGE_NAME > /dev/null 2>&1 &\nPID=$!\n\n# Run spinner attached to that PID\nspinner $PID \"Pulling image ($IMAGE_NAME) ..\"\n\n# Wait for PID to capture exit code\nwait $PID\nEXIT_CODE=$?\n\nif [ $EXIT_CODE -eq 0 ]; then\n    printf \"\\r%b::%b Image pulled ($IMAGE_NAME)                 \\n\" \"${COLOR_GREEN}\" \"${RESET}\"\n    printf \"%b✔%b Success\\n\" \"${COLOR_GREEN}\" \"${RESET}\"\nelse\n    printf \"\\r%b✖%b Failed to pull image.\\n\" \"${COLOR_RED}\" \"${RESET}\"\n    exit 1\nfi\n\nsuccess \"Starting TimescaleDB on port $DB_PORT\\n\"\n\ndocker run -d \\\n    --name \"$CONTAINER_NAME\" \\\n    -p ${DB_PORT}:5432 \\\n    -e POSTGRES_PASSWORD=\"$DB_PASSWORD\" \\\n    -v ${CONTAINER_NAME}_data:/home/postgres/pgdata/data \\\n    \"$IMAGE_NAME\" > /dev/null\n\n# --- 4. Wait for Healthcheck ---\nprintf \"%b::%b Waiting for database to accept connections ..  \" \"${COLOR_CYAN}\" \"${RESET}\"\n\nRETRIES=30\nSUCCESS=false\n\n# We use a custom loop here to animate the spinner while sleeping\ntput civis # Hide cursor\nwhile [ $RETRIES -gt 0 ]; do\n    # Check DB\n    if docker exec $CONTAINER_NAME pg_isready -U postgres > /dev/null 2>&1; then\n        SUCCESS=true\n        break\n    fi\n    \n    # Animate spinner for 1 second (4 * 0.25s)\n    for i in 1 2 3 4; do\n        printf \"\\b|\" ; sleep 0.1\n        printf \"\\b/\" ; sleep 0.1\n        printf \"\\b-\" ; sleep 0.1\n        printf \"\\b\\\\\" ; sleep 0.1\n    done\n    \n    RETRIES=$((RETRIES-1))\ndone\ntput cnorm # Restore cursor\n\nif [ $RETRIES -eq 0 ]; then\n    printf \"\\n\"\n    error \"Database failed to start in time. Check logs with: docker logs $CONTAINER_NAME\"\nfi\n\n# --- 5. Success ---\nif [ \"$SUCCESS\" = true ]; then\n    printf \"\\r%b::%b Database is ready!                              \\n\" \"${COLOR_GREEN}\" \"${RESET}\"\n    echo \"\"\n\n    TSDB_VERSION=$(get_timescaledb_version)\n    POSTGRES_VERSION=$(get_postgres_version)\n\n    printf \"${COLOR_GREEN}╔══════════════════════════════════════════════════════════╗${RESET}\"\n    echo \"\"\n    printf \"${COLOR_GREEN}║             🚀 SETUP COMPLETED SUCCESSFULLY              ║${RESET}\"\n    echo \"\"\n    printf \"${COLOR_GREEN}╚══════════════════════════════════════════════════════════╝${RESET}\"\n    echo \"\"\n    printf \"   %bPostgres:%b    $POSTGRES_VERSION\\n\" \"${BOLD}\" \"${RESET}\"\n    printf \"   %bTimescaleDB:%b $TSDB_VERSION\\n\" \"${BOLD}\" \"${RESET}\"\n    echo \"\"\n    printf \"   %bContainer:%b   $CONTAINER_NAME\\n\" \"${BOLD}\" \"${RESET}\"\n    printf \"   %bPort:%b        $DB_PORT\\n\" \"${BOLD}\" \"${RESET}\"\n    printf \"   %bUser:%b        postgres\\n\" \"${BOLD}\" \"${RESET}\"\n    printf \"   %bPassword:%b    $DB_PASSWORD\\n\" \"${BOLD}\" \"${RESET}\"\n    echo \"\"\n    printf \"%b   Connect:%b\\n\" \"${BOLD}\" \"${RESET}\"\n    echo \"     psql \\\"postgres://postgres:$DB_PASSWORD@localhost:$DB_PORT/postgres\\\"\"\n    echo \"\"\n    printf \"%b   Getting Started:%b\\n\" \"${BOLD}\" \"${RESET}\"\n    echo \"    * Examples:       https://tsdb.co/quick-start-examples\"\n    echo \"    * NYC taxis:      https://tsdb.co/quick-start-nyc-taxis\"\n    echo \"    * S&P 500 stocks: https://tsdb.co/quick-start-sp500-stocks\"\n    echo \"    * Events:         https://tsdb.co/quick-start-events-uuid\"\n    echo \"\"\nelse\n    printf \"\\r%b✖%b Database timed out.\\n\" \"${COLOR_RED}\" \"${RESET}\"\n    echo \"   Check logs with: docker logs $CONTAINER_NAME\"\n    exit 1\nfi\n"
  },
  {
    "path": "scripts/suppressions/README.md",
    "content": "# Suppressions for Clang Sanitizers #\n\nThis folder contains [supression files](https://clang.llvm.org/docs/SanitizerSpecialCaseList.html) for\nrunning timescale using Clang's [AddressSanitizer](https://clang.llvm.org/docs/AddressSanitizer.html)\nand [UndefinedBehaviorSanitizer](https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html), which\nwe use as part of timescale's regission suite. There are a few places OSs have UB and where postgres\nhas benign memory leaks, in order to run these sanitizers, we suppress these warning.\n\nFor ease of use, we provide a script in [/scripts/test_sanitizers.sh] to run our regression tests with\nsanitizers enabled.\n"
  },
  {
    "path": "scripts/suppressions/suppr_asan.txt",
    "content": "#suppress getaddrinfo due to internal error on macos\ninterceptor_via_fun:getaddrinfo\n"
  },
  {
    "path": "scripts/suppressions/suppr_leak.txt",
    "content": "leak:save_ps_display_args\nleak:psql/startup.c\n#don't care about the frontend leaks\nleak:fe_memutils.c\nleak:fe-connect.c\nleak:fe-exec.c\nleak:initdb.c\n#annoingly have to supress strdup because it leaks in PostmasterMain -D option\nleak:strdup\nleak:ProcessConfigFileInternal\nleak:internal_load_library\n\nleak:pg_timezone_abbrev_initialize\nleak:check_timezone_abbreviations\nleak:check_session_authorization\nleak:InitializeGUCOptions\nleak:CheckMyDatabase\n\n\n#pg_dump\nleak:getSchemaData\nleak:dumpDumpableObject\n\n#should live as long as the process\nleak:ShmemInitHash\n\n#test only functions\nleak:deserialize_test_parameters\nleak:ts_params_get\nleak:test_job_dispatcher\n\n#openssl\nleak:CRYPTO_zalloc\n"
  },
  {
    "path": "scripts/suppressions/suppr_ub.txt",
    "content": "alignment:pg_comp_crc32c_sse42\nalignment:array_cmp\nalignment:array_iter_setup\nalignment:array_out\nalignment:array_unnest\nalignment:array_eq\nalignment:AllocSetCheck\nnonnull-attribute:TransactionIdSetPageStatus\nnonnull-attribute:SerializeTransactionState\nnonnull-attribute:initscan\nnonnull-attribute:SetTransactionSnapshot\nnonnull-attribute:shm_mq_receive\n#care, gcc cannot parse the path, only the filename\nnonnull-attribute:*/src/fe_utils/print.c\nnonnull-attribute:print_aligned_text\n#PG13.3-copyfuncs.c:2374:2: runtime error: null pointer passed as argument 2, which is declared to never be null\nnonnull-attribute:_copyAppendRelInfo\n#PG13.3-copyfuncs.c:1190:2: runtime error: null pointer passed as argument 2, which is declared to never be null\nnonnull-attribute:_copyLimit\nnonnull-attribute:*/src/backend/nodes/copyfuncs.c\n#division by 0, looks like a real postgres ug\nfloat-divide-by-zero:_bt_vacuum_needs_cleanup\n\n# It complains about casting -nan to int in histogram_test.sql. It is in the\n# postgres code and doesn't lead to bugs, because it is caught by a check later.\nfloat-cast-overflow:width_bucket_float8\n"
  },
  {
    "path": "scripts/test_downgrade.sh",
    "content": "#!/bin/bash\n\nset -e\n\nSCRIPT_DIR=$(dirname $0)\nPG_MAJOR_VERSION=$(pg_config --version | awk '{print $2}' | awk -F. '{print $1}')\n\nPG_EXTENSION_DIR=$(pg_config --sharedir)/extension\nif [ \"${CI:-false}\" == true ]; then\n  GIT_REF=${GIT_REF:-$(git rev-parse HEAD)}\nelse\n  GIT_REF=$(git branch --show-current)\nfi\n\n\nBUILD_DIR=\"build_update_pg${PG_MAJOR_VERSION}\"\n\nCURRENT_VERSION=$(grep '^version ' version.config | awk '{ print $3 }')\nPREV_VERSION=$(grep '^previous_version ' version.config | awk '{ print $3 }')\n\nif [ ! -d \"${BUILD_DIR}\" ]; then\n  echo \"Initializing build directory\"\n\tBUILD_DIR=\"${BUILD_DIR}\" ./bootstrap -DCMAKE_BUILD_TYPE=Release -DWARNINGS_AS_ERRORS=OFF -DASSERTIONS=ON -DLINTER=ON -DGENERATE_DOWNGRADE_SCRIPT=ON -DREGRESS_CHECKS=OFF -DTAP_CHECKS=OFF\nfi\n\nif [ ! -f \"${PG_EXTENSION_DIR}/timescaledb--${PREV_VERSION}.sql\" ]; then\n  echo \"Building ${PREV_VERSION}\"\n  git checkout ${PREV_VERSION}\n  make -C \"${BUILD_DIR}\" -j \"$(getconf _NPROCESSORS_ONLN)\" > /dev/null\n  sudo make -C \"${BUILD_DIR}\" install > /dev/null\n  git checkout ${GIT_REF}\nfi\n\n# We want to use the latest loader for all the tests so we build it last\nmake -C \"${BUILD_DIR}\" -j \"$(getconf _NPROCESSORS_ONLN)\"\nsudo make -C \"${BUILD_DIR}\" install > /dev/null\n\nset +e\n\nFROM_VERSION=${CURRENT_VERSION} TO_VERSION=${PREV_VERSION} TEST_REPAIR=false \"${SCRIPT_DIR}/test_update_from_version.sh\"\nreturn_code=$?\nif [ $return_code -ne 0 ]; then\n  echo -e \"\\nFailed downgrade from ${CURRENT_VERSION} to ${PREV_VERSION}\\n\"\n  exit 1\nfi\n\n"
  },
  {
    "path": "scripts/test_license.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n"
  },
  {
    "path": "scripts/test_memory_spikes.py",
    "content": "#!/usr/bin/env python\n\n#  This file and its contents are licensed under the Apache License 2.0.\n#  Please see the included NOTICE for copyright information and\n#  LICENSE-APACHE for a copy of the license.\n\n# Python script to check if there are memory spikes when running queries\nimport psutil\nimport time\nimport sys\nfrom datetime import datetime\n\nDEFAULT_MEMCAP = 300  # in MB\nTHRESHOLD_RATIO = 1.5  # ratio above which considered memory spike\nWAIT_TO_STABILIZE = 15  # wait in seconds before considering memory stable\nCHECK_INTERVAL = 15\nDEBUG = False\n\n\n# finds processes with name as argument\ndef find_procs_by_name(name):\n    # Return a list of processes matching 'name'\n    ls = []\n    for p in psutil.process_iter():\n        if p.name() == name:\n            ls.append(p)\n    return ls\n\n\n# return human readable form of number of bytes n\ndef bytes2human(n):\n    # http://code.activestate.com/recipes/578019\n    # >>> bytes2human(10000)\n    # '9.8K'\n    # >>> bytes2human(100001221)\n    # '95.4M'\n    symbols = (\"K\", \"M\", \"G\", \"T\", \"P\", \"E\", \"Z\", \"Y\")\n    prefix = {}\n    for i, s in enumerate(symbols):\n        prefix[s] = 1 << (i + 1) * 10\n    for s in reversed(symbols):\n        if n >= prefix[s]:\n            value = float(n) / prefix[s]\n            return f\"{value:.1f}{s}\"\n    return f\"{n}B\"\n\n\n# prints pid of processes\ndef process_details(process):\n    return f\"{process.pid} {''.join(process.cmdline()).strip()}\"\n\n\ndef process_stats():\n    processes = find_procs_by_name(\"postgres\")\n    for p in processes:\n        print(p, p.num_ctx_switches(), p.cpu_times(), p.memory_info(), flush=True)\n\n\n# return process id of new postgres process created when running SQL file\ndef find_new_process():\n    # get postgres processes that are running before insertion starts\n    base_process = find_procs_by_name(\"postgres\")\n    print(\"Processes running before inserts run: \")\n    for p in base_process:\n        print(process_details(p))\n\n    process_count = len(base_process)\n\n    print(\n        f\"Waiting {WAIT_TO_STABILIZE} seconds for process running inserts to start\",\n        flush=True,\n    )\n    time.sleep(\n        WAIT_TO_STABILIZE\n    )  # wait WAIT_TO_STABILIZE seconds to get process that runs the inserts\n\n    # continuously check for creation of new postgres process\n    timeout = time.time() + 60\n    while True:\n        # prevent infinite loop\n        if time.time() > timeout:\n            print(\n                \"Timed out on finding new process, should force quit SQL inserts\",\n                flush=True,\n            )\n            sys.exit(4)\n\n        process = find_procs_by_name(\"postgres\")\n        cnt = len(process)\n        print(\"process count \", cnt)\n        if cnt > process_count:\n            process = find_procs_by_name(\"postgres\")\n            difference_set = set(process) - set(base_process)\n            new_process = None\n            # We assume that the backend is the first 'new' process to start, so it will have\n            # the lower PID\n            for p in difference_set:\n                print(f\"found process: {process_details(p)}\")\n                if new_process is None or p.pid < new_process.pid:\n                    new_process = p\n            print(f\"new_process: {process_details(new_process)}\")\n            return new_process.pid\n        time.sleep(1)\n\n\ndef main():\n    # get process created from running insert test sql file\n    pid = find_new_process()\n    p = psutil.Process(pid)\n    print('*** Check this pid is the same as \"pg_backend_pid\" from SQL command ***')\n    print(\"New process backend process:\", pid)\n\n    print(\n        f\"Waiting {WAIT_TO_STABILIZE} seconds for memory consumption to stabilize\",\n        flush=True,\n    )\n    time.sleep(WAIT_TO_STABILIZE)\n\n    # Calculate average memory consumption from 5 values over 15 seconds\n    total = 0\n    for _ in range(0, 5):\n        total += p.memory_info().rss\n        time.sleep(3)\n    avg = total / 5\n    print(\"Average memory consumption: \", bytes2human(avg), flush=True)\n\n    cap = int(sys.argv[1] if len(sys.argv) > 1 else DEFAULT_MEMCAP) * 1024 * 1024\n    upper_threshold = min(cap, avg * THRESHOLD_RATIO)\n\n    # check if memory consumption goes above threshold until process terminates every 30 seconds\n    timeout = time.time() + 45 * 60\n    while True:\n        # insert finished\n        if not psutil.pid_exists(pid):\n            break\n        # prevent infinite loop\n        if time.time() > timeout:\n            print(\"Timed out on running inserts (over 45 mins)\")\n            print(\"Killing postgres process\")\n            p.kill()\n            sys.exit(4)\n\n        rss = p.memory_info().rss\n        stamp = datetime.now().strftime(\"%H:%M:%S\")\n        print(f\"{stamp} Memory used by process {p.pid}: {bytes2human(rss)}\", flush=True)\n        if DEBUG:\n            process_stats()\n\n        # exit with error if memory above threshold\n        if rss > upper_threshold:\n            print(\"Memory consumption exceeded upper threshold\")\n            print(\"Killing postgres process\", flush=True)\n            p.kill()\n            sys.exit(4)\n        time.sleep(CHECK_INTERVAL)\n\n    print(\"No memory leaks detected\", flush=True)\n    sys.exit(0)  # success\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "scripts/test_pg_upgrade.py",
    "content": "#!/usr/bin/env python3\n\nimport os\nimport sys\n\nfrom shutil import rmtree\nfrom testgres import get_new_node, PostgresNode\n\n\n# Accessor functions\ndef set_default_conf(node: PostgresNode, unix_socket_dir):\n    node.default_conf(fsync=True, allow_streaming=False, allow_logical=False)\n    node.append_conf(unix_socket_directories=unix_socket_dir)\n    node.append_conf(timezone=\"GMT\")\n    node.append_conf(client_min_messages=\"warning\")\n    node.append_conf(max_prepared_transactions=\"100\")\n    node.append_conf(max_worker_processes=\"0\")\n    node.append_conf(shared_preload_libraries=\"timescaledb\")\n    node.append_conf(\"timescaledb.telemetry_level=off\")\n\n\ndef write_bytes_to_file(filename, nbytes):\n    with open(filename, \"wb\") as f:\n        f.write(nbytes)\n        f.close()\n\n\ndef getenv_or_error(envvar):\n    return os.getenv(envvar) or sys.exit(f\"Environment variable {envvar} not defined\")\n\n\ndef getenv_or_default(envvar, default):\n    return os.getenv(envvar) or default\n\n\ndef initialize_node(working_dir, prefix, port, bin_dir, base_dir):\n    node = get_new_node(prefix=prefix, port=port, bin_dir=bin_dir, base_dir=base_dir)\n    node.init(initdb_params=[\"--data-checksums\"])\n    set_default_conf(node, working_dir)\n    return node\n\n\n# Globals\npg_version_old = getenv_or_error(\"PGVERSIONOLD\")\npg_version_new = getenv_or_error(\"PGVERSIONNEW\")\n\npg_node_old = f\"pg{pg_version_old}\"\npg_node_new = f\"pg{pg_version_new}\"\n\npg_port_old = int(getenv_or_default(\"PGPORTOLD\", \"54321\"))\npg_port_new = int(getenv_or_default(\"PGPORTNEW\", \"54322\"))\n\ntest_version = getenv_or_default(\"TEST_VERSION\", \"v8\")\n\npg_bin_old = getenv_or_default(\"PGBINOLD\", f\"/usr/lib/postgresql/{pg_version_old}/bin\")\npg_bin_new = getenv_or_default(\"PGBINNEW\", f\"/usr/lib/postgresql/{pg_version_new}/bin\")\n\nworking_dir = getenv_or_default(\n    \"OUTPUT_DIR\",\n    f\"/tmp/pg_upgrade_output/{pg_version_old}_to_{pg_version_new}\",\n)\n\npg_data_old = getenv_or_default(\"PGDATAOLD\", f\"{working_dir}/{pg_node_old}\")\npg_data_new = getenv_or_default(\"PGDATAOLD\", f\"{working_dir}/{pg_node_new}\")\n\npg_database_test = getenv_or_default(\"PGDATABASE\", \"pg_upgrade_test\")\n\nif os.path.exists(working_dir):\n    rmtree(working_dir)\nos.makedirs(working_dir)\n\n# Real testing code\nprint(f\"Initializing nodes {pg_node_old} and {pg_node_new}\")\nnode_old = initialize_node(\n    working_dir, pg_node_old, pg_port_old, pg_bin_old, pg_data_old\n)\nnode_old.start()\n\nnode_new = initialize_node(\n    working_dir, pg_node_new, pg_port_new, pg_bin_new, pg_data_new\n)\n\nprint(f\"Creating {pg_database_test} database on node {pg_node_old}\")\nnode_old.safe_psql(filename=\"test/sql/updates/setup.roles.sql\")\nnode_old.safe_psql(query=f\"CREATE DATABASE {pg_database_test};\")\nnode_old.safe_psql(dbname=pg_database_test, query=\"CREATE EXTENSION timescaledb;\")\nnode_old.safe_psql(dbname=pg_database_test, filename=\"test/sql/updates/pre.testing.sql\")\nnode_old.safe_psql(\n    dbname=pg_database_test, filename=f\"test/sql/updates/setup.{test_version}.sql\"\n)\nnode_old.safe_psql(\n    dbname=pg_database_test, filename=\"test/sql/updates/setup.pg_upgrade.sql\"\n)\nnode_old.safe_psql(dbname=pg_database_test, query=\"CHECKPOINT\")\nnode_old.safe_psql(dbname=pg_database_test, filename=\"test/sql/updates/setup.check.sql\")\n\n# Run over the old node to check the output\ncode, old_out, old_err = node_old.psql(\n    dbname=pg_database_test, filename=\"test/sql/updates/post.pg_upgrade.sql\"\n)\n\n# Save output to log\nwrite_bytes_to_file(f\"{working_dir}/post.{pg_node_old}.log\", old_out)\nnode_old.stop()\n\nprint(f\"Upgrading node {pg_node_old} to {pg_node_new}\")\nres = node_new.upgrade_from(old_node=node_old, options=[\"--retain\"])\nnode_new.start()\n\ncode, new_out, new_err = node_new.psql(\n    dbname=pg_database_test,\n    filename=\"test/sql/updates/post.pg_upgrade.sql\",\n)\n\n# Save output to log\nwrite_bytes_to_file(f\"{working_dir}/post.pg{pg_version_new}.log\", new_out)\nnode_new.stop()\n\nprint(f\"Finish upgrading node {pg_node_old} to {pg_node_new}\")\n"
  },
  {
    "path": "scripts/test_regressions.sh",
    "content": "#!/bin/bash\nset -eu\n\n#\n# Run regression check\n#\n# Param: <next_version>\n# Param: <connection_string>\n#\n\nif [ \"$#\" -ne 2 ]; then\n    echo \"${0} <next_version> <connection_string>\"\n    exit 2\nfi\n\n#NEXT_VERSION=$1\n#CONNECTION_STRING=$2\n\n#psql \"$CONNECTION_STRING\" -c \"ALTER EXTENSION timescaledb UPDATE TO '${NEXT_VERSION}'\"\n\n#export TEST_PGPORT_REMOTE=\"$PORT\"\n#export TEST_PGHOST=\"$HOSTNAME\"\n#export TEST_PGUSER=\"$SERVICE_USER\"\n#export TEST_DBNAME=\"$DBNAME\"\n\n#export PGUSER=\"$SERVICE_USER\"\n#export PGPASSWORD=\"$PASSWORD\"\n#export PGPORT=\"$PORT\"\n#export USER=\"tsdbadmin\" #$SERVICE_USER\"\n\n#Run the test\n#make regresscheck-shared\n\n#exit 2\n\n# timescaledb Version/Tag related parameters, more changeable:\n#NEW_DEV_VERSION=\"2.17.0-dev\"\n#COUNT=\"0\"\n\n# PG Tag related parameters , usually less changeable:\n#PG_MAJOR_VERSION=16\n#PG_MINOR_VERSION=4\n\n\n#create required users on the service.\n\n#This is blocked , for now.\n#psql \"$SERVICE_URL\" -c \"CREATE USER default_perm_user\"\n#psql \"$SERVICE_URL\" -c \"GRANT default_perm_user TO tsdbadmin\"\n#psql \"$SERVICE_URL\" -c \"CREATE USER default_perm_user_2\"\n#psql \"$SERVICE_URL\" -c \"GRANT default_perm_user_2 TO tsdbadmin\"\n\n\n#Modify some GUCs, as needed on the service.\n\n#psql tsdb -c \"ALTER database tsdb set timescaledb_experimental.enable_distributed_ddl to on;\"\n#psql tsdb -c \"ALTER database tsdb set timezone to 'US/Pacific';\"\n#psql tsdb -c \"ALTER database tsdb set timescaledb.telemetry_level to off;\" \n#psql tsdb -c \"ALTER database tsdb set extra_float_digits to 0;\"\n#psql tsdb -c \"ALTER database tsdb set random_page_cost to 1.0;\"\n#psql tsdb -c \"ALTER database tsdb set datestyle to 'Postgres, MDY';\"\n#psql tsdb -c \"ALTER system set autovacuum to false;\"\n#psql tsdb -c \"ALTER system set wal_level to 'logical';\"\n#psql tsdb -c \"SELECT _timescaledb_functions.stop_background_workers();\"\n#psql tsdb -c \"ALTER database tsdb set max_parallel_workers to 8;\"\n#psql tsdb -c \"ALTER database tsdb set max_parallel_workers_per_gather to 2;\"\n# This does not switch off Query Identifier , right now. Need to check further.\n#psql tsdb -c \"SET compute_query_id = off;\"\n\n\n\n#sleeps are to allow the server to restart after these GUC parameters' change.\n#sc service parameters -E \"$ENVIRONMENT\" -R \"$REGION\" -S \"$SERVICE_ID\" set -p max_connections -n 200\n#sleep 120\n#sc service parameters -E \"$ENVIRONMENT\" -R \"$REGION\" -S \"$SERVICE_ID\" set -p max_worker_processes -n 24\n#sleep 120\n\n\n#Set the parameters\n\n#export TEST_PGPORT_REMOTE=\"$PORT\"\n#export TEST_PGHOST=\"$HOSTNAME\"\n#export TEST_PGUSER=\"$SERVICE_USER\"\n#export TEST_DBNAME=\"$DBNAME\"\n\n#export PGUSER=\"$SERVICE_USER\"\n#export PGPASSWORD=\"$PASSWORD\"\n#export PGPORT=\"$PORT\"\n#export USER=\"$SERVICE_USER\"\n\n#Run the test\n#make regresscheck-shared\n"
  },
  {
    "path": "scripts/test_sanitizers.sh",
    "content": "#!/bin/bash\n\nset -e\nset -o pipefail\n\nDO_CLEANUP=true\nSCRIPT_DIR=${SCRIPT_DIR:-$(dirname \"$0\")}\nEXCLUDE_PATTERN=${EXCLUDE_PATTERN:-'^$'} # tests matching regex pattern will be excluded\nINCLUDE_PATTERN=${INCLUDE_PATTERN:-'.*'} # tests matching regex pattern will be included\nTEST_MAX=${TEST_MAX:-$((2**16))}\nTEST_MIN=${TEST_MIN:-$((-1))}\nUSE_REMOTE=${USE_REMOTE:-false}\nREMOTE_TAG=${REMOTE_TAG:-'latest'}\nPUSH_IMAGE=${PUSH_IMAGE:-false}\nREMOTE_ORG=${REMOTE_ORG:-'timescaledev'}\nREMOTE_NAME=${REMOTE_NAME:-'postgres-dev-clang'}\nTIMESCALE_DIR=${TIMESCALE_DIR:-${PWD}/${SCRIPT_DIR}/..}\n\nwhile getopts \"d\" opt;\ndo\n    case $opt in\n        d)\n            DO_CLEANUP=false\n            echo \"!!Debug mode: Containers and temporary directory will be left on disk\"\n            echo\n            ;;\n    \t*)\n    \t\techo \"Unknown flag '$opt'\"\n    \t\texit 1\n    \t\t;;\n    esac\ndone\n\nshift $((OPTIND-1))\n\nif [ \"$DO_CLEANUP\" == \"true\" ] ; then\n    trap cleanup EXIT\nfi\n\ncleanup() {\n    # Save status here so that we can return the status of the last\n    # command in the script and not the last command of the cleanup\n    # function\n    status=\"$?\"\n    set +e # do not exit immediately on failure in cleanup handler\n\n    if [[ $status -eq 0 ]]; then\n        echo \"All tests passed\"\n        docker rm -vf timescaledb-san 2>/dev/null\n    else\n        # docker logs timescaledb-san\n        # only print respective postmaster.log when regression.diffs exists\n        docker_exec timescaledb-san \"if [ -f /tsdb_build/timescaledb/build/test/regression.diffs ]; then cat /tsdb_build/timescaledb/build/test/regression.diffs /tsdb_build/timescaledb/build/test/log/postmaster.log; fi\"\n        docker_exec timescaledb-san \"if [ -f /tsdb_build/timescaledb/build/tsl/test/regression.diffs ]; then cat /tsdb_build/timescaledb/build/tsl/test/regression.diffs /tsdb_build/timescaledb/build/tsl/test/log/postmaster.log; fi\"\n    fi\n\n    echo \"Exit status is $status\"\n    exit $status\n}\n\ndocker_exec() {\n    # Echo to stderr\n    >&2 echo -e \"\\033[1m$1\\033[0m: $2\"\n    docker exec \"$1\" /bin/bash -c \"$2\"\n}\n\ndocker rm -f timescaledb-san 2>/dev/null || true\n\ndocker run -d --privileged --name timescaledb-san --env POSTGRES_HOST_AUTH_METHOD=trust -v \"${TIMESCALE_DIR}\":/timescaledb \"${REMOTE_ORG}/${REMOTE_NAME}\":\"${REMOTE_TAG}\"\n\n# Run these commands as root to copy the source into the\n# container. Make sure that all files in the copy is owned by user\n# 'postgres', which we use to run tests below.\ndocker exec -i timescaledb-san /bin/bash -Oe <<EOF\nmkdir /tsdb_build\nchown postgres /tsdb_build\ncp -R /timescaledb tsdb_build\nchown -R postgres:postgres /tsdb_build\nEOF\n\n# Build TimescaleDB as 'postgres' user\ndocker exec -i -u postgres -w /tsdb_build/timescaledb timescaledb-san /bin/bash -Oe <<EOF\nexport CFLAGS=\"-fsanitize=address,undefined -fno-omit-frame-pointer -O2\"\nexport PG_SOURCE_DIR=\"/usr/src/postgresql/\"\nexport BUILD_FORCE_REMOVE=true\n./bootstrap -DCMAKE_BUILD_TYPE='Debug' -DTEST_GROUP_SIZE=1\ncd build\nmake\nEOF\n\n# Install TimescaleDB as root\ndocker exec -i -w /tsdb_build/timescaledb/build timescaledb-san /bin/bash <<EOF\nmake install\nEOF\n\necho \"Testing\"\n\n# Echo to stderr\n>&2 echo -e \"\\033[1m$1\\033[0m: $2\"\n\n# Run tests as 'postgres' user.\n#\n# IGNORE some test since they fail under ASAN.\ndocker exec -i -u postgres -w /tsdb_build/timescaledb/build timescaledb-san /bin/bash <<EOF\nmake -k regresscheck regresscheck-t IGNORES='bgw_db_scheduler bgw_db_scheduler_fixed bgw_launcher cluster-11 continuous_aggs_ddl-11'\nEOF\n"
  },
  {
    "path": "scripts/test_update_from_version.sh",
    "content": "#!/usr/bin/env bash\n\n# During the update test the following databases will be created:\n# - baseline: fresh installation of $TO_VERSION\n# - updated: install $FROM_VERSION, update to $TO_VERSION\n# - restored: restore from updated dump\n# - repair: install $FROM_VERSION, update to $TO_VERSION and run integrity tests\n\nset -xeu\n\nFROM_VERSION=${FROM_VERSION:-$(grep '^previous_version ' version.config | awk '{ print $3 }')}\nTO_VERSION=${TO_VERSION:-$(grep '^version ' version.config | awk '{ print $3 }')}\n\nTEST_REPAIR=${TEST_REPAIR:-false}\nTEST_VERSION=${TEST_VERSION:-v10}\n\nOUTPUT_DIR=${OUTPUT_DIR:-update_test/${FROM_VERSION}_to_${TO_VERSION}}\nPGDATA=\"${OUTPUT_DIR}/data\"\n# Get an unused port to allow\tfor parallel execution\nPGHOST=localhost\nPGPORT=${PGPORT:-$(python -c 'import socket; s=socket.socket(); s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) ; s.bind((\"\", 0)); print(s.getsockname()[1]); s.close()')}\nPGDATABASE=postgres\n\nexport PGHOST PGPORT PGDATABASE PGDATA\n\nrun_sql() {\n  local db=${2:-$PGDATABASE}\n  psql -X -q --echo-queries -d \"${db}\" -v ON_ERROR_STOP=1 -v VERBOSITY=verbose -v WITH_ROLES=true -v WITH_SUPERUSER=true -v WITH_CHUNK=true -c \"${1}\"\n}\n\nrun_sql_file() {\n  local db=${2:-$PGDATABASE}\n  psql -X -q --echo-queries -d \"${db}\" -v ON_ERROR_STOP=1 -v VERBOSITY=verbose -v WITH_ROLES=true -v WITH_SUPERUSER=true -v WITH_CHUNK=true -f \"${1}\"\n}\n\ncheck_version() {\n  psql -X -c \"DO \\$\\$BEGIN PERFORM from pg_available_extension_versions WHERE name='timescaledb' AND version='$1'; IF NOT FOUND THEN RAISE 'Version $1 not available'; END IF; END\\$\\$;\" > /dev/null\n}\n\ntrap cleanup EXIT\n\ncleanup() {\n    # Save status here so that we can return the status of the last\n    # command in the script and not the last command of the cleanup\n    # function\n    local status=\"$?\"\n    set +e # do not exit immediately on failure in cleanup handler\n    pg_ctl stop > /dev/null\n    rm -rf \"${PGDATA}\"\n    exit ${status}\n}\n\nmkdir -p \"${OUTPUT_DIR}\"\nrm -rf \"${OUTPUT_DIR}/data\"\nmkdir -p \"${OUTPUT_DIR}/data\"\n\nUNIX_SOCKET_DIR=$(readlink -f \"${OUTPUT_DIR}\")\n\ninitdb > \"${OUTPUT_DIR}/initdb.log\" 2>&1\n\n# Don't try to wrap the settings, pg_ctl can't handle newlines there.\npg_ctl -l \"${OUTPUT_DIR}/postgres.log\" start -o \"-c unix_socket_directories=${UNIX_SOCKET_DIR} -c timezone=GMT -c client_min_messages=warning -c port=${PGPORT} -c max_prepared_transactions=100 -c shared_preload_libraries=timescaledb -c timescaledb.telemetry_level=off -c timescaledb.enable_compression_ratio_warnings=off -c max_worker_processes=0 -c log_statement=all\"\n\npg_isready -t 30 > /dev/null\n\necho -e \"\\nUpdate test version ${TEST_VERSION} for ${FROM_VERSION} -> ${TO_VERSION}\\n\"\n\n# caller should ensure that the versions are available\ncheck_version \"${FROM_VERSION}\"\ncheck_version \"${TO_VERSION}\"\n\nrun_sql_file test/sql/updates/setup.roles.sql > /dev/null\n\necho \"Creating baseline database\"\n{\n  run_sql \"CREATE DATABASE baseline;\"\n  PGDATABASE=baseline\n  run_sql \"CREATE EXTENSION timescaledb VERSION \\\"${TO_VERSION}\\\";\"\n  run_sql_file test/sql/updates/pre.testing.sql\n  run_sql_file test/sql/updates/setup.${TEST_VERSION}.sql\n  run_sql \"CHECKPOINT;\"\n  run_sql_file test/sql/updates/setup.check.sql\n} > \"${OUTPUT_DIR}/baseline.log\" 2>&1\n\necho \"Creating updated database\"\n{\n  run_sql \"CREATE DATABASE updated;\" > \"${OUTPUT_DIR}/updated.log\"\n  PGDATABASE=updated\n  run_sql \"CREATE EXTENSION timescaledb VERSION \\\"${FROM_VERSION}\\\";\"\n  run_sql_file test/sql/updates/pre.testing.sql\n  run_sql_file test/sql/updates/setup.${TEST_VERSION}.sql\n  run_sql \"CHECKPOINT;\" >> \"${OUTPUT_DIR}/updated.log\"\n  run_sql \"ALTER EXTENSION timescaledb UPDATE TO \\\"${TO_VERSION}\\\";\"\n  run_sql_file test/sql/updates/setup.check.sql\n} > \"${OUTPUT_DIR}/updated.log\" 2>&1\n\necho \"Creating restored database\"\n{\n  run_sql \"CREATE DATABASE restored;\"\n  PGDATABASE=restored\n  run_sql \"CREATE EXTENSION timescaledb VERSION \\\"${TO_VERSION}\\\";\"\n  run_sql \"ALTER DATABASE restored SET timescaledb.restoring='on';\"\n  pg_dump -Fc -d updated > \"${OUTPUT_DIR}/updated.dump\"\n  pg_restore -d restored \"${OUTPUT_DIR}/updated.dump\"\n  run_sql \"ALTER DATABASE restored RESET timescaledb.restoring;\"\n} > \"${OUTPUT_DIR}/restored.log\" 2>&1\n\nrun_sql_file test/sql/updates/post.${TEST_VERSION}.sql baseline > \"${OUTPUT_DIR}/post.baseline.log\"\nrun_sql_file test/sql/updates/post.${TEST_VERSION}.sql updated > \"${OUTPUT_DIR}/post.updated.log\"\nrun_sql_file test/sql/updates/post.${TEST_VERSION}.sql restored > \"${OUTPUT_DIR}/post.restored.log\"\n\nsed -i -E -e 's!\"*_ts_meta_v2_bl[0-9A-Za-z]+_([_0-9A-Za-z]+)\"*!regress-test-bloom_\\1!g' \\\n    \"${OUTPUT_DIR}\"/post.*.log\n\nif [ \"${TEST_REPAIR}\" = \"true\" ]; then\n  echo \"Creating repair database\"\n  {\n    run_sql \"CREATE DATABASE repair;\"\n    PGDATABASE=repair\n    run_sql \"CREATE EXTENSION timescaledb VERSION \\\"${FROM_VERSION}\\\";\"\n    run_sql_file test/sql/updates/setup.repair.sql baseline\n    run_sql \"ALTER EXTENSION timescaledb UPDATE TO \\\"${TO_VERSION}\\\";\"\n    run_sql_file test/sql/updates/post.repair.sql baseline\n    run_sql_file test/sql/updates/post.integrity_test.sql baseline\n  } > \"${OUTPUT_DIR}/repair.log\" 2>&1\nfi\n\ndiff -u \"${OUTPUT_DIR}/post.baseline.log\" \"${OUTPUT_DIR}/post.updated.log\" | tee \"${OUTPUT_DIR}/baseline_vs_updated.diff\"\nif [ ! -s \"${OUTPUT_DIR}/baseline_vs_updated.diff\" ]; then\n  rm \"${OUTPUT_DIR}/baseline_vs_updated.diff\"\nfi\ndiff -u \"${OUTPUT_DIR}/post.baseline.log\" \"${OUTPUT_DIR}/post.restored.log\" | tee \"${OUTPUT_DIR}/baseline_vs_restored.diff\"\nif [ ! -s \"${OUTPUT_DIR}/baseline_vs_restored.diff\" ]; then\n  rm \"${OUTPUT_DIR}/baseline_vs_restored.diff\"\nfi\n\nif [ -f \"${OUTPUT_DIR}/baseline_vs_updated.diff\" ] || [ -f \"${OUTPUT_DIR}/baseline_vs_restored.diff\" ]; then\n  echo \"Update test for ${FROM_VERSION} -> ${TO_VERSION} failed\"\n  exit 1\nfi\n"
  },
  {
    "path": "scripts/test_update_smoke.sh",
    "content": "#!/bin/bash\n# shellcheck disable=SC2129,SC2230\n# SC2129: Consider using { cmd1; cmd2; } >> file instead of individual redirects.\n# SC2230: which is non-standard. Use builtin 'command -v' instead.\n\n# Run smoke tests to test that updating between versions work.\n#\n# Usage:\n#      bash test_update_smoke.sh postgres://...\n#\n# This is based on our update tests but doing some tweaks to ensure we\n# can run it on Forge (or any other PostgreSQL server that only permit\n# a single user and single database).\n#\n# In particular, we cannot create new roles and we cannot create new\n# databases.\n#\n# Info on the parameters:\n# - current_version is the version to update from\n#\n# - next_version is the version to update to \n#\n# - connection_string is the URL to use for the connection\n#\n# - dbname if not defined use \"tsdb\" as database name\n#\n\nif [ \"$#\" -ne 3 ]; then\n    echo \"${0} <current_version> <next_version> <connection_string>\"\n    exit 2\nfi\n\nCURRENT_VERSION=$1\nNEXT_VERSION=$2\nCONNECTION_STRING=$3\n\necho \"testing upgrade path from v${CURRENT_VERSION} to ${NEXT_VERSION} ..\"\n\nSCRIPT_DIR=$(dirname $0)\nBASE_DIR=${PWD}/${SCRIPT_DIR}/..\nSCRATCHDIR=$(mktemp -d -t \"smoketest-${CURRENT_VERSION}-${NEXT_VERSION}-XXXX\")\nLOGFILE=\"$SCRATCHDIR/update-test.log\"\nDUMPFILE=\"$SCRATCHDIR/smoke.dump\"\nUPGRADE_OUT=\"$SCRATCHDIR/upgrade.out\"\nCLEAN_OUT=\"$SCRATCHDIR/clean.out\"\nRESTORE_OUT=\"$SCRATCHDIR/restore.out\"\nTEST_VERSION=${TEST_VERSION:-v7}\n\n# We do not have superuser privileges when running smoke tests.\nWITH_SUPERUSER=false\nWITH_ROLES=false\n\nshift $((OPTIND-1))\n\necho \"**** pg_dump at   \" \"$(which pg_dump)\"\necho \"**** pg_restore at\" \"$(which pg_restore)\"\n\n# Extra options to pass to psql\nPGOPTS=\"-v TEST_VERSION=${TEST_VERSION} -v WITH_SUPERUSER=${WITH_SUPERUSER} -v WITH_ROLES=${WITH_ROLES} -v WITH_CHUNK=false\"\nPSQL=\"psql -a -qX $PGOPTS\"\n\n# If we are providing a URI for the connection, we parse it here and\n# set the PG??? variables since that is the only reliable way to\n# provide connection information to psql, pg_dump, and pg_restore.\n#\n# To work with Forge, we need to only set PGPASSWORD when the password\n# is available and leave it unset otherwise. If the user has either\n# set PGPASSWORD or has the password in a .pgpass file, it will be\n# picked up and used for the connection.\n# shellcheck disable=SC2207 # Prefer mapfile or read -a to split command output (or quote to avoid splitting).\nparts=($(echo $CONNECTION_STRING | perl -mURI::Split=uri_split -ne '@F = uri_split($_); print join(\" \", split(qr/[:@]/, $F[1]), substr($F[2], 1))'))\nexport PGUSER=${parts[0]}\nif [[ ${#parts[@]} -eq 5 ]]; then\n    # Cloud has 5 fields\n    export PGPASSWORD=${parts[1]}\n    export PGHOST=${parts[2]}\n    export PGPORT=${parts[3]}\n    export PGDATABASE=${parts[4]}\nelif [[ ${#parts[@]} -eq 4 ]]; then\n    # Forge has 4 fields\n    export PGHOST=${parts[1]}\n    export PGPORT=${parts[2]}\n    export PGDATABASE=${parts[3]}\nelse\n    echo \"Malformed URL '$CONNECTION_STRING'\" 1>&2\n    exit 2\nfi\n\nerr_trap() {\n    exit 3\n}\n\nexit_trap() {\n    exit_code=$?\n    if [ \"$exit_code\" != \"0\" ]; then\n        echo \"!!!! FAILED !!!!\"\n    else\n        echo \"**** passed ****\"\n    fi\n    echo \"**** logs can be found in $SCRATCHDIR\"\n}\n\nset -e\nset -o pipefail\ntrap exit_trap EXIT\ntrap err_trap ERR\n\nmissing_versions() {\n        $PSQL -v ECHO=none -t <<-EOF\n\tSELECT * FROM (VALUES ('$1'), ('$2')) AS foo\n\tEXCEPT\n\tSELECT version FROM pg_available_extension_versions\n\tWHERE name = 'timescaledb' AND version IN ('$1', '$2');\n\tEOF\n}\n\necho \"**** Scratch directory: ${SCRATCHDIR}\"\necho \"**** Update files in directory ${BASE_DIR}/test/sql/updates\"\ncd ${BASE_DIR}/test/sql/updates\n\n$PSQL -c '\\conninfo'\n$PSQL -c \"ALTER DATABASE ${PGDATABASE} SET timescaledb.enable_compression_ratio_warnings = 'off'\";\n\n# shellcheck disable=SC2207 # Prefer mapfile or read -a to split command output (or quote to avoid splitting).\nmissing=($(missing_versions $CURRENT_VERSION $NEXT_VERSION))\nif [[ ${#missing[@]} -gt 0 ]]; then\n    echo \"ERROR: Missing version(s) ${missing[*]} of 'timescaledb'\"\n    echo \"Available versions: \" \"$($PSQL -tc \"SELECT version FROM pg_available_extension_versions WHERE name = 'timescaledb'\")\"\n    exit 1\nfi\n\n# For the comments below, we assume the upgrade is from 1.7.5 to 2.0.2\n# (this is just an example, the real value is given by variables\n# above).\n\n# Create a 1.7.5 version Upgrade\necho \"---- Connecting to ${FORGE_CONNINFO} and running setup ----\"\n$PSQL -f cleanup.${TEST_VERSION}.sql >>$LOGFILE 2>&1\n$PSQL -c \"DROP EXTENSION IF EXISTS timescaledb CASCADE\" >>$LOGFILE 2>&1\n$PSQL -f pre.cleanup.sql >>$LOGFILE 2>&1\n$PSQL -c \"CREATE EXTENSION timescaledb VERSION '${CURRENT_VERSION}'\" >>$LOGFILE 2>&1\n$PSQL -c \"\\dx\"\n\n# Run setup on Upgrade\n$PSQL -f pre.smoke.sql >>$LOGFILE 2>&1\n$PSQL -f setup.${TEST_VERSION}.sql >>$LOGFILE 2>&1\n# Run update on Upgrade. You now have a 2.0.2 version in Upgrade.\n$PSQL -c \"ALTER EXTENSION timescaledb UPDATE TO '${NEXT_VERSION}'\" >>$LOGFILE 2>&1\n\necho -n \"Dumping the contents of Upgrade...\"\npg_dump -Fc -f $DUMPFILE >>$LOGFILE 2>&1\necho \"done\"\n\n# Run the post scripts on Upgrade to get UpgradeOut to compare\n# with.  We can now discard Upgrade database.\necho -n \"Collecting post-update status...\"\n$PSQL -f post.${TEST_VERSION}.sql >$UPGRADE_OUT\necho \"done\"\n\n$PSQL -f cleanup.${TEST_VERSION}.sql >>$LOGFILE 2>&1\n\necho \"---- Create a ${NEXT_VERSION} version Clean ----\"\n$PSQL -c \"DROP EXTENSION IF EXISTS timescaledb CASCADE\" >>$LOGFILE 2>&1\n$PSQL -f pre.cleanup.sql >>$LOGFILE 2>&1\n$PSQL -c \"CREATE EXTENSION timescaledb VERSION '${NEXT_VERSION}'\" >>$LOGFILE 2>&1\n$PSQL -c \"\\dx\"\n\necho \"---- Run the setup scripts on Clean, with post-update actions ----\"\n$PSQL -f pre.smoke.sql >>$LOGFILE 2>&1\n$PSQL -f setup.${TEST_VERSION}.sql >>$LOGFILE 2>&1\n\necho \"---- Run the post scripts on Clean to get output CleanOut ----\"\n$PSQL -f post.${TEST_VERSION}.sql >$CLEAN_OUT\n\n$PSQL -f cleanup.${TEST_VERSION}.sql >>$LOGFILE 2>&1\n\necho \"---- Create a ${NEXT_VERSION} version Restore ----\"\n$PSQL -c \"DROP EXTENSION IF EXISTS timescaledb CASCADE\" >>$LOGFILE 2>&1\n$PSQL -f pre.cleanup.sql >>$LOGFILE 2>&1\n$PSQL -c \"CREATE EXTENSION timescaledb VERSION '${NEXT_VERSION}'\" >>$LOGFILE 2>&1\n$PSQL -c \"\\dx\"\n\necho \"---- Restore the UpgradeDump into Restore ----\"\necho -n \"Restoring dump...\"\n$PSQL -c \"SELECT timescaledb_pre_restore()\" >>$LOGFILE 2>&1\npg_restore -d $PGDATABASE $DUMPFILE >>$LOGFILE 2>&1 || true\n$PSQL -c \"SELECT timescaledb_post_restore()\" >>$LOGFILE 2>&1\necho \"done\"\n\necho \"---- Run the post scripts on Restore to get a RestoreOut ----\"\n$PSQL -f post.${TEST_VERSION}.sql >$RESTORE_OUT\n\necho \"---- Compare UpgradeOut with CleanOut and make sure they are identical ----\"\ndiff -u $UPGRADE_OUT $CLEAN_OUT | tee $SCRATCHDIR/upgrade-clean.diff\n\necho \"---- Compare RestoreOut with CleanOut and make sure they are identical ----\"\ndiff -u $RESTORE_OUT $CLEAN_OUT | tee $SCRATCHDIR/restore-clean.diff\n\n$PSQL -f cleanup.${TEST_VERSION}.sql >>$LOGFILE 2>&1\n"
  },
  {
    "path": "scripts/test_updates.sh",
    "content": "#!/bin/bash\n\nset -eu\n\nSCRIPT_DIR=$(readlink -f \"$(dirname $0)\")\nPG_MAJOR_VERSION=$(pg_config --version | awk '{print $2}' | awk -F. '{print $1}')\n\nPG_EXTENSION_DIR=$(pg_config --sharedir)/extension\nif [ \"${CI:-false}\" == true ]; then\n  GIT_REF=${GIT_REF:-$(git rev-parse HEAD)}\nelse\n  GIT_REF=$(git branch --show-current)\nfi\n\nBUILD_DIR=\"build_update_pg${PG_MAJOR_VERSION}\"\n\nVERSIONS=\"\"\nFAILED_VERSIONS=\"\"\n\nALL_VERSIONS=$(git tag --sort=taggerdate | grep -P '^[2]\\.[0-9]+\\.[0-9]+$')\nMAX_VERSION=$(grep '^previous_version ' version.config | awk '{ print $3 }')\n\n# major version is always 2 atm\nmax_minor_version=$(echo \"${MAX_VERSION}\" | awk -F. '{print $2}')\nmax_patch_version=$(echo \"${MAX_VERSION}\" | awk -F. '{print $3}')\n\n# Filter versions depending on the current postgres version\n# Minimum version for valid update paths are as follows:\n# PG 14 v8 2.5+\n# PG 15 v8 2.9+\n# PG 16 v8 2.13+\n# PG 17 v8 2.17+\nfor version in ${ALL_VERSIONS}; do\n  minor_version=$(echo \"${version}\" | awk -F. '{print $2}')\n  patch_version=$(echo \"${version}\" | awk -F. '{print $3}')\n\n  # skip versions that are newer than the max version\n  # We might have a tag for a newer version defined already but the post release\n  # adjustment have not been merged yet. So we want to skip those versions.\n  if [ \"${minor_version}\" -gt \"${max_minor_version}\" ]; then\n    continue\n  elif [ \"${minor_version}\" -eq \"${max_minor_version}\" ] && [ \"${patch_version}\" -gt \"${max_patch_version}\" ]; then\n    continue\n  fi\n\n  if [ \"${minor_version}\" -le 8 ]; then\n    # not part of any valid update path\n    continue\n  elif [ \"${minor_version}\" -le 12 ]; then\n    if [ \"${PG_MAJOR_VERSION}\" -le 15 ]; then\n        VERSIONS=\"${VERSIONS} ${version}\"\n    fi\n  elif [ \"${minor_version}\" -le 16 ]; then\n    if [ \"${PG_MAJOR_VERSION}\" -le 16 ]; then\n        VERSIONS=\"${VERSIONS} ${version}\"\n    fi\n  elif [ \"${minor_version}\" -le 22 ]; then\n    if [ \"${PG_MAJOR_VERSION}\" -le 17 ]; then\n        VERSIONS=\"${VERSIONS} ${version}\"\n    fi\n  else\n    VERSIONS=\"${VERSIONS} ${version}\"\n  fi\ndone\n\nFAIL_COUNT=0\n\nif [ ! -d \"${BUILD_DIR}\" ]; then\n  echo \"Initializing build directory\"\n  BUILD_DIR=\"${BUILD_DIR}\" ./bootstrap -DCMAKE_BUILD_TYPE=Release -DWARNINGS_AS_ERRORS=OFF -DASSERTIONS=ON -DLINTER=OFF -DGENERATE_DOWNGRADE_SCRIPT=ON -DREGRESS_CHECKS=OFF -DTAP_CHECKS=OFF\nfi\n\nfor version in ${VERSIONS}; do\n  if [ ! -f \"${PG_EXTENSION_DIR}/timescaledb--${version}.sql\" ]; then\n    echo \"Building ${version}\"\n    git checkout ${version}\n    make -C \"${BUILD_DIR}\" -j \"$(getconf _NPROCESSORS_ONLN)\" > /dev/null\n    sudo make -C \"${BUILD_DIR}\" install > /dev/null\n    git checkout ${GIT_REF}\n  fi\ndone\n\n# We want to use the latest loader for all the tests so we build it last\ngit checkout ${GIT_REF}\nmake -C \"${BUILD_DIR}\" -j \"$(getconf _NPROCESSORS_ONLN)\"\nsudo make -C \"${BUILD_DIR}\" install\n\nset +e\n\nif [ -n \"${VERSIONS}\" ]; then\n  for version in ${VERSIONS}; do\n    ts_minor_version=$(echo \"${version}\" | awk -F. '{print $2}')\n\n    if [ \"${ts_minor_version}\" -ge 10 ]; then\n      TEST_REPAIR=true\n    else\n      TEST_REPAIR=false\n    fi\n\n\n    if [ \"${ts_minor_version}\" -ge 20 ]; then\n        TEST_VERSION=v10\n    elif [ \"${ts_minor_version}\" -ge 16 ]; then\n        TEST_VERSION=v9\n    else\n        TEST_VERSION=v8\n    fi\n\n    export TEST_VERSION TEST_REPAIR\n\n    FROM_VERSION=${version} \"${SCRIPT_DIR}/test_update_from_version.sh\"\n    return_code=$?\n    if [ $return_code -ne 0 ]; then\n      FAIL_COUNT=$((FAIL_COUNT + 1))\n      FAILED_VERSIONS=\"${FAILED_VERSIONS} ${version}\"\n    fi\n  done\nfi\n\necho -e \"\\nUpdate test finished for ${VERSIONS}\\n\"\n\nif [ $FAIL_COUNT -gt 0 ]; then\n  echo -e \"Failed versions: ${FAILED_VERSIONS}\\n\"\n  echo -e \"Postgres errors:\\n\"\n  find update_test -name postgres.log -exec grep ERROR {} \\;\nelse\n  echo -e \"All tests succeeded.\\n\"\nfi\n\nexit $FAIL_COUNT\n"
  },
  {
    "path": "scripts/ts_dump.sh",
    "content": "#!/usr/bin/env bash\n\n#  This file and its contents are licensed under the Apache License 2.0.\n#  Please see the included NOTICE for copyright information and\n#  LICENSE-APACHE for a copy of the license.\n\n# This script is used for backing up a single hypertable into an easy-to-restore\n# tarball. The tarball contains two files: (1) a .sql file for recreating the\n# hypertable and its indices and (2) a .csv file containing the data as CSV.\n#\n# Because pg_dump/pg_restore dump all of TimescaleDB's internal tables when\n# used, this script is useful if you want a backup that can be restored\n# regardless of TimescaleDB version running, or as part of a process where you\n# do not want to always backup all your hypertables at once.\n\n\nif [[ -z \"$1\" || -z \"$2\" ]]; then\n    echo \"Usage: $0 hypertable output_name [pg_dump CONNECTION OPTIONS]\"\n    echo \"    hypertable  - Hypertable to backup\"\n    echo \"    output_name - Output files will be stored in an archive named [output_name].tar.gz\"\n    echo \"    \"\n    echo \"Any connection options for pg_dump/psql (e.g. -d database, -U username) should be listed at the end\"\n    exit 1\nfi\n\nHYPERTABLE=$1\nPREFIX=$2\n\nshift 2\nset -e\necho \"Backing up schema as $PREFIX-schema.sql...\"\npg_dump \"$@\" --schema-only -t $HYPERTABLE -f $PREFIX-schema.sql\necho >> $PREFIX-schema.sql \"--\n-- Restore to hypertable\n--\"\npsql \"$@\" -qAtX -c \"SELECT _timescaledb_functions.get_create_command('$HYPERTABLE');\" >> $PREFIX-schema.sql\n\necho \"Backing up data as $PREFIX-data.csv...\"\npsql \"$@\" -c \"\\COPY (SELECT * FROM $HYPERTABLE) TO $PREFIX-data.csv DELIMITER ',' CSV\"\n\necho \"Archiving and removing previous files...\"\ntar -czvf $PREFIX.tar.gz $PREFIX-data.csv $PREFIX-schema.sql\nrm $PREFIX-data.csv\nrm $PREFIX-schema.sql\n"
  },
  {
    "path": "scripts/ts_restore.sh",
    "content": "#!/usr/bin/env bash\n\n#  This file and its contents are licensed under the Apache License 2.0.\n#  Please see the included NOTICE for copyright information and\n#  LICENSE-APACHE for a copy of the license.\n\n# This script is used for restoring a hypertable from a tarball made with\n# ts_dump.sh. It unarchives the tarball, producing a schema file and data file,\n# which are then restore separately.\n\n\nif [[ -z \"$1\" || -z \"$2\" ]]; then\n    echo \"Usage: $0 hypertable tarfile_name\"\n    echo \"    hypertable   - Hypertable to restore\"\n    echo \"    tarfile_name - Name of the tarball created by ts_dump.sh to restore\"\n    echo \"    \"\n    echo \"Any connection options for pg_restore/psql (e.g. -d database, -U username) should be listed at the end\"\n    exit 1\n\nfi\n\nHYPERTABLE=$1\nTARFILE=$2\nPREFIX=\"${TARFILE/.tar.gz/}\"\nshift 2\necho \"Unarchiving tarball...\"\ntar xvzf $TARFILE\n\necho \"Restoring hypertable's schema...\"\nif ! psql -q -v \"ON_ERROR_STOP=1\" \"$@\" < $PREFIX-schema.sql\nthen\n    echo \"Restoring schema failed, exiting.\"\n    rm $PREFIX-data.csv\n    rm $PREFIX-schema.sql\n    exit $?\nfi\n\necho \"Restoring hypertable's data...\"\nif ! psql \"$@\" -v \"ON_ERROR_STOP=1\" -c \"\\COPY $HYPERTABLE FROM $PREFIX-data.csv DELIMITER ',' CSV\"\nthen\n    echo \"Restoring data failed, exiting.\"\nfi\n\nrm $PREFIX-data.csv\nrm $PREFIX-schema.sql\n"
  },
  {
    "path": "scripts/upload_ci_stats.sh",
    "content": "#!/usr/bin/env bash\nset -xue\nset -o pipefail\n\nif [ -z \"${CI_STATS_DB:-}\" ]\nthen\n    # The secret with the stats db connection string is not accessible in forks.\n    echo \"The statistics database connection string is not specified\"\n    exit 0\nfi\n\nPSQL=${PSQL:-psql}\nPSQL=(\"${PSQL}\" \"${CI_STATS_DB}\" -qtAX \"--set=ON_ERROR_STOP=1\")\n\n# The tables we are going to use. This schema is here just as a reminder, you'll\n# have to create them manually. After you manually change the actual DB schema,\n# don't forget to append the needed migration code below.\n: \"\ncreate extension if not exists timescaledb;\n\ncreate table job(\n    job_date timestamptz, -- Serves as a unique id.\n    commit_sha text,\n    job_name text,\n    repository text,\n    ref_name text,\n    event_name text,\n    pr_number int,\n    job_status text,\n    url text,\n    run_attempt int,\n    run_id bigint,\n    run_number int,\n    pg_version text generated always as (\n        substring(job_name from 'PG([0-9]+(\\.[0-9]+)*)')\n    ) stored\n);\n\ncreate unique index on job(job_date);\n\nselect create_hypertable('job', 'job_date');\n\ncreate table test(\n    job_date timestamptz,\n    test_name text,\n    test_status text,\n    test_duration float\n);\n\ncreate unique index on test(job_date, test_name);\n\nselect create_hypertable('test', 'job_date');\n\ncreate table log(\n    job_date timestamptz,\n    test_name text,\n    log_contents text\n);\n\ncreate unique index on log(job_date, test_name);\n\nselect create_hypertable('log', 'job_date');\n\ncreate table ipe(\n    job_date timestamptz,\n    error text,\n    location text,\n    statement text\n);\n\nselect create_hypertable('ipe', 'job_date');\n\"\n\n# Create the job record.\nCOMMIT_SHA=$(git -C \"$(dirname \"${BASH_SOURCE[0]}\")\" rev-parse @)\nexport COMMIT_SHA\n\nJOB_NAME=\"${JOB_NAME:-test-job}\"\nexport JOB_NAME\n\nJOB_DATE=$(\"${PSQL[@]}\" -c \"\ninsert into job(\n    job_date, commit_sha, job_name,\n    repository, ref_name, event_name,\n    pr_number, job_status,\n    url,\n    run_attempt, run_id, run_number\n) values (\n    now(), '$COMMIT_SHA', '$JOB_NAME',\n    '$GITHUB_REPOSITORY', '$GITHUB_REF_NAME', '$GITHUB_EVENT_NAME',\n    '$GITHUB_PR_NUMBER', '$JOB_STATUS',\n    'https://github.com/timescale/timescaledb/actions/runs/$GITHUB_RUN_ID/attempts/$GITHUB_RUN_ATTEMPT',\n    '$GITHUB_RUN_ATTEMPT', '$GITHUB_RUN_ID', '$GITHUB_RUN_NUMBER')\nreturning job_date;\n\")\nexport JOB_DATE\n\n# Parse the installcheck.log to find the individual test results. Note that this\n# file might not exist for failed checks or non-regression checks like SQLSmith.\n# We still want to save the other logs.\nif [ -f 'installcheck.log' ]\nthen\n    gawk -v OFS='\\t' '\n    match($0, /^(test|    ) ([^ ]+)[ ]+\\.\\.\\.[ ]+([^ ]+) (|\\(.*\\))[ ]+([0-9]+) ms$/, a) {\n        print ENVIRON[\"JOB_DATE\"], a[2], tolower(a[3] (a[4] ? (\" \" a[4]) : \"\")), a[5];\n    }\n    match($0, /^([^0-9]+) [0-9]+ +[-+] ([^ ]+) +([0-9]+) ms/, a) {\n        print ENVIRON[\"JOB_DATE\"], a[2], a[1], a[3];\n    }\n    ' installcheck.log > tests.tsv\n\n    # Save the test results into the database.\n    \"${PSQL[@]}\" -c \"\\copy test from tests.tsv\"\n\n    # Split the regression.diffs into per-test files.\n    gawk '\n        match($0, /^(diff|\\+\\+\\+|\\-\\-\\-) .*\\/(.*)[.]out/, a) {\n            file = a[2] \".diff\";\n            next;\n        }\n\n        { if (file) print $0 > file; }\n    ' regression.log\nfi\n\n# Snip the long sequences of \"+\" or \"-\" changes in the diffs.\nfor x in *.diff;\ndo\n    if ! [ -f \"$x\" ] ; then continue ; fi\n    gawk -v max_context_lines=10 -v min_context_lines=2 '\n        /^-/ { new_sign = \"-\" }\n        /^+/ { new_sign = \"+\" }\n        /^[^+-]/ { new_sign = \" \" }\n\n        {\n            if (old_sign != new_sign) {\n                to_print = lines_buffered > max_context_lines ? min_context_lines : lines_buffered;\n\n                if (lines_buffered > to_print)\n                    print \"<\" lines_buffered - to_print \" lines skipped>\";\n\n                for (i = 0; i < to_print; i++) {\n                    print buf[(NR + i - to_print) % max_context_lines]\n                }\n\n                printf(\"c %04d: %s\\n\", NR, $0);\n                old_sign = new_sign;\n                lines_printed = 0;\n                lines_buffered = 0;\n            } else {\n                if (lines_printed >= min_context_lines) {\n                    lines_buffered++;\n                    buf[NR % max_context_lines] = sprintf(\"b %04d: %s\", NR, $0)\n                } else {\n                    lines_printed++;\n                    printf(\"p %04d: %s\\n\", NR, $0);\n                }\n            }\n        }\n\n        END {\n            to_print = lines_buffered > max_context_lines ? min_context_lines : lines_buffered;\n\n            if (lines_buffered > to_print)\n                print \"<\" lines_buffered - to_print \" lines skipped>\";\n\n            for (i = 0; i < to_print; i++) {\n                print buf[(NR + 1 + i - to_print) % max_context_lines]\n            }\n        }' \"$x\" > \"$x.tmp\"\n    mv \"$x.tmp\" \"$x\"\ndone\n\n# Save a snippet of logs where a backend was terminated by signal.\ngrep -C40 \"was terminated by signal\" postmaster.log > postgres-failure.log ||:\n\n# Find internal program errors and resource owner leak warnings in the sever log.\n# We do the same thing in Flaky Check and error out if we find any, not to\n# introduce these errors for the new tests.\njq 'select(\n        (.state_code == \"XX000\" and .error_severity != \"LOG\")\n        or (.message | test(\"resource was not closed\"))\n    ) | [env.JOB_DATE, .message, .func_name,  .statement] | @tsv\n' -r postmaster.json > ipe.tsv ||:\n\"${PSQL[@]}\" -c \"\\copy ipe from ipe.tsv\"\n\n# Upload the logs.\n# Note that the sanitizer setting log_path means \"write logs to 'log_path.pid'\".\nfor x in sanitizer* sanitizer/* {sqlsmith/sqlsmith,sanitizer,stacktrace,postgres-failure}.log *.diff\ndo\n    if ! [ -e \"$x\" ]; then continue ; fi\n    \"${PSQL[@]}\" <<<\"\n        \\set contents \\`cat $x\\`\n        insert into log values ('$JOB_DATE', '$(basename \"$x\" .diff)', :'contents');\n    \"\ndone\n"
  },
  {
    "path": "sql/CMakeLists.txt",
    "content": "set(INSTALL_FILE ${PROJECT_NAME}--${PROJECT_VERSION_MOD}.sql)\n\ninclude(ScriptFiles)\n\n# These files represent the modifications that happen in each version, excluding\n# new objects or updates to functions. We use them to build a path (update\n# script) from every historical version to the current version. Note that not\n# all of these files may exist on disk, in case they would have no contents.\n# There still needs to be an entry here to build an update script for that\n# version. Thus, for every new release, an entry should be added here.\nset(MOD_FILES\n    updates/2.9.0--2.9.1.sql\n    updates/2.9.1--2.9.2.sql\n    updates/2.9.2--2.9.3.sql\n    updates/2.9.3--2.10.0.sql\n    updates/2.10.0--2.10.1.sql\n    updates/2.10.1--2.10.2.sql\n    updates/2.10.2--2.10.3.sql\n    updates/2.10.3--2.11.0.sql\n    updates/2.11.0--2.11.1.sql\n    updates/2.11.1--2.11.2.sql\n    updates/2.11.2--2.12.0.sql\n    updates/2.12.0--2.12.1.sql\n    updates/2.12.1--2.12.2.sql\n    updates/2.12.2--2.13.0.sql\n    updates/2.13.0--2.13.1.sql\n    updates/2.13.1--2.14.0.sql\n    updates/2.14.0--2.14.1.sql\n    updates/2.14.1--2.14.2.sql\n    updates/2.14.2--2.15.0.sql\n    updates/2.15.0--2.15.1.sql\n    updates/2.15.1--2.15.2.sql\n    updates/2.15.2--2.15.3.sql\n    updates/2.15.3--2.16.0.sql\n    updates/2.16.0--2.16.1.sql\n    updates/2.16.1--2.17.0.sql\n    updates/2.17.0--2.17.1.sql\n    updates/2.17.1--2.17.2.sql\n    updates/2.17.2--2.18.0.sql\n    updates/2.18.0--2.18.1.sql\n    updates/2.18.1--2.18.2.sql\n    updates/2.18.2--2.19.0.sql\n    updates/2.19.0--2.19.1.sql\n    updates/2.19.1--2.19.2.sql\n    updates/2.19.2--2.19.3.sql\n    updates/2.19.3--2.20.0.sql\n    updates/2.20.0--2.20.1.sql\n    updates/2.20.1--2.20.2.sql\n    updates/2.20.2--2.20.3.sql\n    updates/2.20.3--2.21.0.sql\n    updates/2.21.0--2.21.1.sql\n    updates/2.21.1--2.21.2.sql\n    updates/2.21.2--2.21.3.sql\n    updates/2.21.3--2.21.4.sql\n    updates/2.21.4--2.22.0.sql\n    updates/2.22.0--2.22.1.sql\n    updates/2.22.1--2.23.0.sql\n    updates/2.23.0--2.23.1.sql\n    updates/2.23.1--2.24.0.sql\n    updates/2.24.0--2.25.0.sql\n    updates/2.25.0--2.25.1.sql\n    updates/2.25.1--2.25.2.sql)\n\n# The downgrade file to generate a downgrade script for the current version, as\n# specified in version.config\nset(CURRENT_REV_FILE reverse-dev.sql)\n\nset(MODULE_PATHNAME \"$libdir/timescaledb-${PROJECT_VERSION_MOD}\")\nset(LOADER_PATHNAME \"$libdir/timescaledb\")\n\nset(TS_MODULE_PATHNAME\n    ${MODULE_PATHNAME}\n    PARENT_SCOPE)\n\nset(TSL_MODULE_PATHNAME\n    \"$libdir/timescaledb-tsl-${PROJECT_VERSION_MOD}\"\n    PARENT_SCOPE)\n\nif(NOT GENERATE_DOWNGRADE_SCRIPT)\n  message(\n    STATUS \"Not generating downgrade script: downgrade generation disabled.\")\nelseif(NOT GIT_FOUND)\n  message(STATUS \"Not generating downgrade script: Git not available.\")\nelse()\n  generate_downgrade_script(\n    SOURCE_VERSION\n    ${PROJECT_VERSION_MOD}\n    TARGET_VERSION\n    ${PREVIOUS_VERSION}\n    INPUT_DIRECTORY\n    ${CMAKE_CURRENT_SOURCE_DIR}/updates\n    FILES\n    ${CURRENT_REV_FILE})\nendif()\n\n# Function to replace @VARIABLE@ in source files, producing output files in the\n# build dir. When SUFFIX is provided, it is appended to each output filename\n# (used for version-specific copies like version_check.sql).\nfunction(version_files SRC_FILE_LIST OUTPUT_FILE_LIST)\n  cmake_parse_arguments(_vf \"\" \"SUFFIX\" \"\" ${ARGN})\n  set(result \"\")\n  foreach(unversioned_file ${SRC_FILE_LIST})\n    set(versioned_file ${unversioned_file}${_vf_SUFFIX})\n    list(APPEND result ${CMAKE_CURRENT_BINARY_DIR}/${versioned_file})\n    if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${unversioned_file})\n      configure_file(${unversioned_file} ${versioned_file} @ONLY)\n    endif()\n  endforeach(unversioned_file)\n  set(${OUTPUT_FILE_LIST}\n      \"${result}\"\n      PARENT_SCOPE)\nendfunction()\n\n# Create versioned files (replacing MODULE_PATHNAME) in the build directory of\n# all our source files\nversion_files(\"${PRE_UPDATE_FILES}\" PRE_UPDATE_FILES_VERSIONED)\nversion_files(\"${POST_UPDATE_FILES}\" POST_UPDATE_FILES_VERSIONED)\nversion_files(\"${PRE_INSTALL_SOURCE_FILES}\" PRE_INSTALL_SOURCE_FILES_VERSIONED)\nversion_files(\"${PRE_INSTALL_FUNCTION_FILES}\"\n              PRE_INSTALL_FUNCTION_FILES_VERSIONED)\nversion_files(\"${SOURCE_FILES}\" SOURCE_FILES_VERSIONED)\nversion_files(\"${MOD_FILES}\" MOD_FILES_VERSIONED)\nversion_files(\"updates/latest-dev.sql\" LASTEST_MOD_VERSIONED)\nversion_files(\"notice.sql\" NOTICE_FILE)\n\n# Function to concatenate all files in SRC_FILE_LIST into file OUTPUT_FILE. When\n# STRIP_REPLACE is set, strips `OR REPLACE` from the output to prevent privilege\n# escalation attacks in CREATE scripts.\nfunction(cat_files SRC_FILE_LIST OUTPUT_FILE)\n  if(WIN32)\n    set(\"SRC_ARG\" \"-DSRC_FILE_LIST=${SRC_FILE_LIST}\")\n  else()\n    set(\"SRC_ARG\" \"'-DSRC_FILE_LIST=${SRC_FILE_LIST}'\")\n  endif()\n  set(_extra_args)\n  if(STRIP_REPLACE)\n    list(APPEND _extra_args \"-DSTRIP_REPLACE=ON\")\n  endif()\n  add_custom_command(\n    OUTPUT ${OUTPUT_FILE}\n    DEPENDS ${SRC_FILE_LIST}\n    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}\n    COMMAND ${CMAKE_COMMAND} \"${SRC_ARG}\" \"-DOUTPUT_FILE=${OUTPUT_FILE}\"\n            ${_extra_args} -P cat.cmake\n    COMMENT \"Generating ${OUTPUT_FILE}\")\nendfunction()\n\n# Generate the extension file used with CREATE EXTENSION\nset(STRIP_REPLACE ON)\ncat_files(\n  \"${PRE_INSTALL_SOURCE_FILES_VERSIONED};${SOURCE_FILES_VERSIONED};${NOTICE_FILE}\"\n  ${CMAKE_CURRENT_BINARY_DIR}/${INSTALL_FILE})\nset(STRIP_REPLACE OFF)\nadd_custom_target(sqlfile ALL\n                  DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${INSTALL_FILE})\n\n# Generate the update files used with ALTER EXTENSION <name> UPDATE\nset(MOD_FILE_REGEX\n    \"([0-9]+\\\\.[0-9]+\\\\.*[0-9]+[-a-z0-9]*)--([0-9]+\\\\.[0-9]+\\\\.*[0-9]+[-a-z0-9]*).sql\"\n)\n\n# We'd like to process the updates in reverse (descending) order\nif(EXISTS\n   \"${CMAKE_CURRENT_SOURCE_DIR}/updates/${PREVIOUS_VERSION}--${PROJECT_VERSION_MOD}.sql\"\n)\n  set(MOD_FILES_LIST ${MOD_FILES_VERSIONED})\nelse()\n  set(MOD_FILES_LIST\n      \"${MOD_FILES_VERSIONED};updates/${PREVIOUS_VERSION}--${PROJECT_VERSION_MOD}.sql\"\n  )\nendif()\n\nlist(REVERSE MOD_FILES_LIST)\n\n# Variable that will hold the list of update scripts from every previous version\n# to the current version\nset(UPDATE_SCRIPTS \"\")\n\n# A list of current modfiles. We append to this list for every previous version\n# that moves us further away from the current version, thus making the update\n# path longer as we move back in history\nset(CURR_MOD_FILES \"${LASTEST_MOD_VERSIONED}\")\n\n# Generate the post-update file once (same for all update scripts)\nset(POST_FILES_PROCESSED ${POST_UPDATE_FILES_VERSIONED}.processed)\ncat_files(\n  \"${SET_POST_UPDATE_STAGE};${POST_UPDATE_FILES_VERSIONED};${UNSET_UPDATE_STAGE}\"\n  ${POST_FILES_PROCESSED})\n\n# Now loop through the modfiles and generate the update files\nforeach(transition_mod_file ${MOD_FILES_LIST})\n\n  if(NOT (${transition_mod_file} MATCHES ${MOD_FILE_REGEX}))\n    message(FATAL_ERROR \"Cannot parse update file name ${transition_mod_file}\")\n  endif()\n\n  set(START_VERSION ${CMAKE_MATCH_1})\n  set(END_VERSION ${CMAKE_MATCH_2})\n  set(PRE_FILES ${PRE_UPDATE_FILES_VERSIONED})\n  # Check for version-specific update code with fixes\n  if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/updates/${START_VERSION}.sql)\n    version_files(\"updates/${START_VERSION}.sql\" ORIGIN_MOD_FILE)\n    list(APPEND PRE_FILES ${ORIGIN_MOD_FILE})\n  endif()\n\n  version_files(\"updates/version_check.sql\" VERSION_CHECK_VERSIONED SUFFIX\n                -${START_VERSION})\n  list(PREPEND PRE_FILES \"header.sql;${VERSION_CHECK_VERSIONED}\")\n\n  # There might not have been any changes in the modfile, in which case the\n  # modfile need not be present\n  if(EXISTS ${transition_mod_file})\n    # Prepend the modfile as we are moving through the versions in descending\n    # order\n    list(INSERT CURR_MOD_FILES 0 ${transition_mod_file})\n  endif()\n\n  set(UPDATE_SCRIPT\n      ${CMAKE_CURRENT_BINARY_DIR}/timescaledb--${START_VERSION}--${PROJECT_VERSION_MOD}.sql\n  )\n  list(APPEND UPDATE_SCRIPTS ${UPDATE_SCRIPT})\n  if(CURR_MOD_FILES)\n    cat_files(\n      \"${PRE_FILES};${CURR_MOD_FILES};${PRE_INSTALL_FUNCTION_FILES_VERSIONED};${SOURCE_FILES_VERSIONED};${POST_FILES_PROCESSED}\"\n      ${UPDATE_SCRIPT})\n  else()\n    cat_files(\n      \"${PRE_FILES};${PRE_INSTALL_FUNCTION_FILES_VERSIONED};${SOURCE_FILES_VERSIONED};${POST_FILES_PROCESSED}\"\n      ${UPDATE_SCRIPT})\n  endif()\n\nendforeach(transition_mod_file)\n\nadd_custom_target(sqlupdatescripts ALL DEPENDS ${UPDATE_SCRIPTS})\n\n# Install target for the extension file and update scripts\ninstall(FILES ${CMAKE_CURRENT_BINARY_DIR}/${INSTALL_FILE} ${UPDATE_SCRIPTS}\n        DESTINATION \"${PG_SHAREDIR}/extension\")\n"
  },
  {
    "path": "sql/bgw_scheduler.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.restart_background_workers()\nRETURNS BOOL\nAS '@LOADER_PATHNAME@', 'ts_bgw_db_workers_restart'\nLANGUAGE C VOLATILE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.stop_background_workers()\nRETURNS BOOL\nAS '@LOADER_PATHNAME@', 'ts_bgw_db_workers_stop'\nLANGUAGE C VOLATILE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.start_background_workers()\nRETURNS BOOL\nAS '@LOADER_PATHNAME@', 'ts_bgw_db_workers_start'\nLANGUAGE C VOLATILE;\n"
  },
  {
    "path": "sql/bgw_startup.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nSELECT _timescaledb_functions.restart_background_workers();\n"
  },
  {
    "path": "sql/bookend.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.first_sfunc(internal, anyelement, \"any\")\nRETURNS internal\nAS '@MODULE_PATHNAME@', 'ts_first_sfunc'\nLANGUAGE C IMMUTABLE PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.first_combinefunc(internal, internal)\nRETURNS internal\nAS '@MODULE_PATHNAME@', 'ts_first_combinefunc'\nLANGUAGE C IMMUTABLE PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.last_sfunc(internal, anyelement, \"any\")\nRETURNS internal\nAS '@MODULE_PATHNAME@', 'ts_last_sfunc'\nLANGUAGE C IMMUTABLE PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.last_combinefunc(internal, internal)\nRETURNS internal\nAS '@MODULE_PATHNAME@', 'ts_last_combinefunc'\nLANGUAGE C IMMUTABLE PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.bookend_finalfunc(internal, anyelement, \"any\")\nRETURNS anyelement\nAS '@MODULE_PATHNAME@', 'ts_bookend_finalfunc'\nLANGUAGE C IMMUTABLE PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.bookend_serializefunc(internal)\nRETURNS bytea\nAS '@MODULE_PATHNAME@', 'ts_bookend_serializefunc'\nLANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.bookend_deserializefunc(bytea, internal)\nRETURNS internal\nAS '@MODULE_PATHNAME@', 'ts_bookend_deserializefunc'\nLANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\n\n-- We started using CREATE OR REPLACE AGGREGATE for aggregate creation once the syntax was fully supported\n-- as it is easier to support idempotent changes this way. This will allow for changes to functions supporting\n-- the aggregate, and, for instance, the definition and inclusion of inverse functions for window function\n-- support. However, it should still be noted that changes to the data structures used for the internal\n-- state of the aggregate must be backwards compatible and the old format must be accepted by any new functions\n-- in order for them to continue working with Continuous Aggregates, where old states may have been materialized.\n\n--This aggregate returns the \"first\" value of the first argument when ordered by the second argument.\n--Ex. first(temp, time) returns the temp value for the row with the lowest time\nCREATE OR REPLACE AGGREGATE @extschema@.first(anyelement, \"any\") (\n    SFUNC = _timescaledb_functions.first_sfunc,\n    STYPE = internal,\n    COMBINEFUNC = _timescaledb_functions.first_combinefunc,\n    SERIALFUNC = _timescaledb_functions.bookend_serializefunc,\n    DESERIALFUNC = _timescaledb_functions.bookend_deserializefunc,\n    PARALLEL = SAFE,\n    FINALFUNC = _timescaledb_functions.bookend_finalfunc,\n    FINALFUNC_EXTRA\n);\n\n--This aggregate returns the \"last\" value of the first argument when ordered by the second argument.\n--Ex. last(temp, time) returns the temp value for the row with highest time\nCREATE OR REPLACE AGGREGATE @extschema@.last(anyelement, \"any\") (\n    SFUNC = _timescaledb_functions.last_sfunc,\n    STYPE = internal,\n    COMBINEFUNC = _timescaledb_functions.last_combinefunc,\n    SERIALFUNC = _timescaledb_functions.bookend_serializefunc,\n    DESERIALFUNC = _timescaledb_functions.bookend_deserializefunc,\n    PARALLEL = SAFE,\n    FINALFUNC = _timescaledb_functions.bookend_finalfunc,\n    FINALFUNC_EXTRA\n);\n"
  },
  {
    "path": "sql/cagg_utils.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.cagg_validate_query(\n    query TEXT,\n    OUT is_valid BOOLEAN,\n    OUT error_level TEXT,\n    OUT error_code TEXT,\n    OUT error_message TEXT,\n    OUT error_detail TEXT,\n    OUT error_hint TEXT\n) RETURNS RECORD AS '@MODULE_PATHNAME@', 'ts_continuous_agg_validate_query' LANGUAGE C STRICT VOLATILE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.cagg_get_bucket_function_info(\n    mat_hypertable_id INTEGER,\n    -- The bucket function\n    OUT bucket_func REGPROCEDURE,\n    -- `bucket_width` argument of the function, e.g. \"1 month\"\n    OUT bucket_width TEXT,\n    -- optional `origin` argument of the function provided by the user\n    OUT bucket_origin TEXT,\n    -- optional `offset` argument of the function provided by the user\n    OUT bucket_offset TEXT,\n    -- optional `timezone` argument of the function provided by the user\n    OUT bucket_timezone TEXT,\n    -- fixed or variable sized bucket\n    OUT bucket_fixed_width BOOLEAN\n) RETURNS RECORD AS '@MODULE_PATHNAME@', 'ts_continuous_agg_get_bucket_function_info' LANGUAGE C STRICT VOLATILE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.cagg_get_grouping_columns(\n    cagg REGCLASS )\n    RETURNS TEXT[] AS '@MODULE_PATHNAME@', 'ts_continuous_agg_get_grouping_columns'\nLANGUAGE C STRICT VOLATILE;\n"
  },
  {
    "path": "sql/cat.cmake",
    "content": "\nIF(POLICY CMP0012)\n  CMAKE_POLICY(SET CMP0012 NEW)\nENDIF()\n\nif (NOT DEFINED STRIP_REPLACE)\n  set(STRIP_REPLACE OFF)\nendif()\n\nfunction(append_file IN_FILE OUT_FILE STRIP_REPLACE)\n  file(READ ${IN_FILE} CONTENTS)\n  if (${STRIP_REPLACE})\n    string(REPLACE \" OR REPLACE \" \" \" CONTENTS \"${CONTENTS}\")\n  endif()\n  file(APPEND ${OUT_FILE} \"${CONTENTS}\")\nendfunction()\n\n# Function to concatenate all files in SRC_FILE_LIST into file OUTPUT_FILE\nfunction(cat SRC_FILE_LIST OUTPUT_FILE STRIP_REPLACE)\n  file(WRITE ${OUTPUT_FILE} \"\")\n  foreach(SRC_FILE ${SRC_FILE_LIST})\n    append_file(${SRC_FILE} ${OUTPUT_FILE} ${STRIP_REPLACE})\n  endforeach()\nendfunction()\n\ncat(\"${SRC_FILE_LIST}\" \"${OUTPUT_FILE}\" \"${STRIP_REPLACE}\")\n"
  },
  {
    "path": "sql/chunk.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Built-in function for calculating the next chunk interval when\n-- using adaptive chunking. The function can be replaced by a\n-- user-defined function with the same signature.\n--\n-- The parameters passed to the function are as follows:\n--\n-- dimension_id: the ID of the dimension to calculate the interval for\n-- dimension_coord: the coordinate / point on the dimensional axis\n-- where the tuple that triggered this chunk creation falls.\n-- chunk_target_size: the target size in bytes that the chunk should have.\n--\n-- The function should return the new interval in dimension-specific\n-- time (ususally microseconds).\nCREATE OR REPLACE FUNCTION _timescaledb_functions.calculate_chunk_interval(\n        dimension_id INTEGER,\n        dimension_coord BIGINT,\n        chunk_target_size BIGINT\n) RETURNS BIGINT AS '@MODULE_PATHNAME@', 'ts_calculate_chunk_interval' LANGUAGE C;\n\n-- Get the status of the chunk\nCREATE OR REPLACE FUNCTION _timescaledb_functions.chunk_status(REGCLASS) RETURNS INT\nAS '@MODULE_PATHNAME@', 'ts_chunk_status' LANGUAGE C;\n\n-- Get the status of the chunk as text array\nCREATE OR REPLACE FUNCTION _timescaledb_functions.chunk_status_text(chunk_status int) RETURNS TEXT[]\nAS '@MODULE_PATHNAME@', 'ts_chunk_status_text' LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.chunk_status_text(chunk regclass) RETURNS TEXT[]\nAS $$ SELECT _timescaledb_functions.chunk_status_text(_timescaledb_functions.chunk_status($1)); $$ LANGUAGE SQL STRICT IMMUTABLE PARALLEL SAFE SET search_path TO pg_catalog, pg_temp;;\n\n--given a chunk's relid, return the id. Error out if not a chunk relid.\nCREATE OR REPLACE FUNCTION _timescaledb_functions.chunk_id_from_relid(relid OID) RETURNS INTEGER\nAS '@MODULE_PATHNAME@', 'ts_chunk_id_from_relid' LANGUAGE C STABLE STRICT PARALLEL SAFE;\n\n-- Show the definition of a chunk.\nCREATE OR REPLACE FUNCTION _timescaledb_functions.show_chunk(chunk REGCLASS)\nRETURNS TABLE(chunk_id INTEGER, hypertable_id INTEGER, schema_name NAME, table_name NAME, relkind \"char\", slices JSONB)\nAS '@MODULE_PATHNAME@', 'ts_chunk_show' LANGUAGE C VOLATILE;\n\n-- Create a chunk with the given dimensional constraints (slices) as\n-- given in the JSONB. If chunk_table is a valid relation, it will be\n-- attached to the hypertable and used as the data table for the new\n-- chunk. Note that schema_name and table_name need not be the same as\n-- the existing schema and name for chunk_table. The provided chunk\n-- table will be renamed and/or moved as necessary.\nCREATE OR REPLACE FUNCTION _timescaledb_functions.create_chunk(\n       hypertable REGCLASS,\n       slices JSONB,\n       schema_name NAME = NULL,\n       table_name NAME = NULL,\n\t   chunk_table REGCLASS = NULL)\nRETURNS TABLE(chunk_id INTEGER, hypertable_id INTEGER, schema_name NAME, table_name NAME, relkind \"char\", slices JSONB, created BOOLEAN)\nAS '@MODULE_PATHNAME@', 'ts_chunk_create' LANGUAGE C VOLATILE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.freeze_chunk(\n   chunk REGCLASS)\nRETURNS BOOL AS '@MODULE_PATHNAME@', 'ts_chunk_freeze_chunk' LANGUAGE C VOLATILE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.unfreeze_chunk(\n   chunk REGCLASS)\nRETURNS BOOL AS '@MODULE_PATHNAME@', 'ts_chunk_unfreeze_chunk' LANGUAGE C VOLATILE;\n\n--wrapper for ts_chunk_drop\n--drops the chunk table and its entry in the chunk catalog\nCREATE OR REPLACE FUNCTION _timescaledb_functions.drop_chunk(\n   chunk REGCLASS)\nRETURNS BOOL AS '@MODULE_PATHNAME@', 'ts_chunk_drop_single_chunk' LANGUAGE C VOLATILE;\n\n-- internal API used by OSM extension to attach a table as a chunk of the hypertable\nCREATE OR REPLACE FUNCTION _timescaledb_functions.attach_osm_table_chunk(\n   hypertable REGCLASS,\n   chunk REGCLASS)\nRETURNS BOOL AS '@MODULE_PATHNAME@', 'ts_chunk_attach_osm_table_chunk' LANGUAGE C VOLATILE;\n\n-- internal API used by OSM extension to drop an OSM chunk table from the hypertable\nCREATE OR REPLACE FUNCTION _timescaledb_functions.drop_osm_chunk(hypertable REGCLASS)\nRETURNS BOOL AS '@MODULE_PATHNAME@', 'ts_chunk_drop_osm_chunk' LANGUAGE C VOLATILE;\n\nCREATE OR REPLACE PROCEDURE @extschema@.detach_chunk(chunk REGCLASS)\nLANGUAGE C AS '@MODULE_PATHNAME@', 'ts_detach_chunk';\n\nCREATE OR REPLACE PROCEDURE @extschema@.attach_chunk(hypertable REGCLASS,\n   chunk REGCLASS,\n   slices JSONB)\nLANGUAGE C AS '@MODULE_PATHNAME@', 'ts_attach_chunk';\n"
  },
  {
    "path": "sql/chunk_constraint.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- create constraint on newly created chunk based on hypertable constraint\nCREATE OR REPLACE FUNCTION _timescaledb_functions.chunk_constraint_add_table_constraint(\n    chunk_constraint_row  _timescaledb_catalog.chunk_constraint\n)\n    RETURNS VOID LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n    chunk_row _timescaledb_catalog.chunk;\n    hypertable_row _timescaledb_catalog.hypertable;\n    constraint_oid OID;\n    constraint_type CHAR;\n    check_sql TEXT;\n    def TEXT;\n    indx_tablespace NAME;\n    tablespace_def TEXT;\nBEGIN\n    SELECT * INTO STRICT chunk_row FROM _timescaledb_catalog.chunk c WHERE c.id = chunk_constraint_row.chunk_id;\n    SELECT * INTO STRICT hypertable_row FROM _timescaledb_catalog.hypertable h WHERE h.id = chunk_row.hypertable_id;\n\n    IF chunk_constraint_row.dimension_slice_id IS NOT NULL THEN\n\t    RAISE 'cannot create dimension constraint %', chunk_constraint_row;\n    ELSIF chunk_constraint_row.hypertable_constraint_name IS NOT NULL THEN\n\n        SELECT oid, contype INTO STRICT constraint_oid, constraint_type FROM pg_constraint\n        WHERE conname=chunk_constraint_row.hypertable_constraint_name AND\n              conrelid = format('%I.%I', hypertable_row.schema_name, hypertable_row.table_name)::regclass::oid;\n\n        IF constraint_type IN ('p','u') THEN\n          -- since primary keys and unique constraints are backed by an index\n          -- they might have an index tablespace assigned\n          -- the tablspace is not part of the constraint definition so\n          -- we have to append it explicitly to preserve it\n          SELECT T.spcname INTO indx_tablespace\n          FROM pg_constraint C, pg_class I, pg_tablespace T\n          WHERE C.oid = constraint_oid AND C.contype IN ('p', 'u') AND I.oid = C.conindid AND I.reltablespace = T.oid;\n\n          def := pg_get_constraintdef(constraint_oid);\n\n        ELSIF constraint_type = 't' THEN\n          -- constraint triggers are copied separately with normal triggers\n          def := NULL;\n        ELSE\n          def := pg_get_constraintdef(constraint_oid);\n        END IF;\n\n    ELSE\n        RAISE 'unknown constraint type';\n    END IF;\n\n    IF def IS NOT NULL THEN\n        -- to allow for custom types with operators outside of pg_catalog\n        -- we set search_path to @extschema@\n        SET LOCAL search_path TO @extschema@, pg_temp;\n        EXECUTE pg_catalog.format(\n            $$ ALTER TABLE %I.%I ADD CONSTRAINT %I %s $$,\n            chunk_row.schema_name, chunk_row.table_name, chunk_constraint_row.constraint_name, def\n        );\n\n        -- if constraint (primary or unique) needs a tablespace then add it\n        -- via a separate ALTER INDEX SET TABLESPACE command. We cannot append it\n        -- to the \"def\" string above since it leads to a SYNTAX error when\n        -- \"DEFERRABLE\" or \"INITIALLY DEFERRED\" are used in the constraint\n        IF indx_tablespace IS NOT NULL THEN\n            EXECUTE pg_catalog.format(\n                $$ ALTER INDEX %I.%I SET TABLESPACE %I $$,\n                chunk_row.schema_name, chunk_constraint_row.constraint_name, indx_tablespace\n            );\n        END IF;\n\n    END IF;\nEND\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\n-- Clone fk constraint from a hypertable to a compressed chunk\nCREATE OR REPLACE FUNCTION _timescaledb_functions.constraint_clone(\n    constraint_oid OID,\n    target_oid REGCLASS\n)\n    RETURNS VOID LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n    constraint_name NAME;\n    def TEXT;\nBEGIN\n    def := pg_get_constraintdef(constraint_oid);\n    SELECT conname INTO STRICT constraint_name FROM pg_constraint WHERE oid = constraint_oid;\n\n    IF def IS NULL THEN\n        RAISE 'constraint not found';\n    END IF;\n\n    -- to allow for custom types with operators outside of pg_catalog\n    -- we set search_path to @extschema@\n    SET LOCAL search_path TO @extschema@, pg_temp;\n    EXECUTE pg_catalog.format($$ ALTER TABLE %s ADD CONSTRAINT %I %s $$, target_oid::pg_catalog.text, constraint_name, def);\n\nEND\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n"
  },
  {
    "path": "sql/comment_apache.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCOMMENT ON EXTENSION timescaledb IS 'Enables scalable inserts and complex queries for time-series data (Apache 2 Edition)';\n"
  },
  {
    "path": "sql/comment_tsl.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCOMMENT ON EXTENSION timescaledb IS 'Enables scalable inserts and complex queries for time-series data (Community Edition)';\n"
  },
  {
    "path": "sql/compat.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- TimescaleDB 2.12 moved all functions present in _timescaledb_internal into\n-- _timescaledb_functions. This file contains a compatibility layer to allow\n-- for more flexibility when migrating for any users calling these internal\n-- functions.\n-- This compatibility layer will be removed in a future versions.\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.alter_job_set_hypertable_id(job_id integer, hypertable regclass) RETURNS integer LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.alter_job_set_hypertable_id(integer,regclass) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.alter_job_set_hypertable_id($1,$2);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.attach_osm_table_chunk(hypertable regclass, chunk regclass) RETURNS boolean LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.attach_osm_table_chunk(regclass,regclass) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.attach_osm_table_chunk($1,$2);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.cagg_watermark(hypertable_id integer) RETURNS bigint LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.cagg_watermark(integer) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.cagg_watermark($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.cagg_watermark_materialized(hypertable_id integer) RETURNS bigint LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.cagg_watermark_materialized(integer) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.cagg_watermark_materialized($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.calculate_chunk_interval(dimension_id integer,dimension_coord bigint,chunk_target_size bigint) RETURNS bigint LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.calculate_chunk_interval(integer,bigint,bigint) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.calculate_chunk_interval($1,$2,$3);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.chunk_constraint_add_table_constraint(chunk_constraint_row _timescaledb_catalog.chunk_constraint) RETURNS void LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.chunk_constraint_add_table_constraint(_timescaledb_catalog.chunk_constraint) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  PERFORM _timescaledb_functions.chunk_constraint_add_table_constraint($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.chunk_id_from_relid(relid oid) RETURNS integer LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.chunk_id_from_relid(oid) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.chunk_id_from_relid($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.chunk_status(regclass) RETURNS integer LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.chunk_status(regclass) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.chunk_status($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.chunks_local_size(schema_name_in name,table_name_in name) RETURNS TABLE (chunk_id integer, chunk_schema NAME, chunk_name  NAME, table_bytes bigint, index_bytes bigint, toast_bytes bigint, total_bytes bigint) LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.chunks_local_size(name,name) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN QUERY SELECT * FROM _timescaledb_functions.chunks_local_size($1,$2);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.compressed_chunk_local_stats(schema_name_in name,table_name_in name) RETURNS TABLE (chunk_schema name, chunk_name name, compression_status text, before_compression_table_bytes bigint, before_compression_index_bytes bigint, before_compression_toast_bytes bigint, before_compression_total_bytes bigint, after_compression_table_bytes bigint, after_compression_index_bytes bigint, after_compression_toast_bytes bigint, after_compression_total_bytes bigint) LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.compressed_chunk_local_stats(name,name) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN QUERY SELECT * FROM _timescaledb_functions.compressed_chunk_local_stats($1,$2);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.compressed_chunk_remote_stats(schema_name_in name,table_name_in name) RETURNS TABLE ( chunk_schema name, chunk_name name, compression_status text, before_compression_table_bytes bigint, before_compression_index_bytes bigint, before_compression_toast_bytes bigint, before_compression_total_bytes bigint, after_compression_table_bytes bigint, after_compression_index_bytes bigint, after_compression_toast_bytes bigint, after_compression_total_bytes bigint, node_name name) LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.compressed_chunk_remote_stats(name,name) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN QUERY SELECT * FROM _timescaledb_functions.compressed_chunk_remote_stats($1,$2);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n-- we have to prefix slices, schema_name and table_name parameter with _ here to not clash with output names otherwise plpgsql will complain\nCREATE OR REPLACE FUNCTION _timescaledb_internal.create_chunk(hypertable regclass,_slices jsonb,_schema_name name=NULL,_table_name name=NULL,chunk_table regclass=NULL) RETURNS TABLE(chunk_id INTEGER, hypertable_id INTEGER, schema_name NAME, table_name NAME, relkind \"char\", slices JSONB, created BOOLEAN) LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.create_chunk(regclass,jsonb,name,name,regclass) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN QUERY SELECT * FROM _timescaledb_functions.create_chunk($1,$2,$3,$4,$5);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.create_compressed_chunk(chunk regclass,chunk_table regclass,uncompressed_heap_size bigint,uncompressed_toast_size bigint,uncompressed_index_size bigint,compressed_heap_size bigint,compressed_toast_size bigint,compressed_index_size bigint,numrows_pre_compression bigint,numrows_post_compression bigint) RETURNS regclass LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.create_compressed_chunk(regclass,regclass,bigint,bigint,bigint,bigint,bigint,bigint,bigint,bigint) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.create_compressed_chunk($1,$2,$3,$4,$5,$6,$7,$8,$9,$10);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.drop_chunk(chunk regclass) RETURNS boolean LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.drop_chunk(regclass) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.drop_chunk($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.freeze_chunk(chunk regclass) RETURNS boolean LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.freeze_chunk(regclass) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.freeze_chunk($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.generate_uuid() RETURNS uuid LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.generate_uuid() is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.generate_uuid();\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.get_approx_row_count(relation regclass) RETURNS bigint LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.get_approx_row_count(regclass) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.get_approx_row_count($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.get_compressed_chunk_index_for_recompression(uncompressed_chunk regclass) RETURNS regclass LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.get_compressed_chunk_index_for_recompression(regclass) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.get_compressed_chunk_index_for_recompression($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.get_create_command(table_name name) RETURNS text LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.get_create_command(name) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.get_create_command($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.get_git_commit() RETURNS TABLE(commit_tag TEXT, commit_hash TEXT, commit_time TIMESTAMPTZ) LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.get_git_commit() is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN QUERY SELECT * FROM _timescaledb_functions.get_git_commit();\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.get_os_info() RETURNS TABLE(sysname TEXT, version TEXT, release TEXT, version_pretty TEXT) LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.get_os_info() is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN QUERY SELECT * FROM _timescaledb_functions.get_os_info();\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.get_partition_for_key(val anyelement) RETURNS integer LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.get_partition_for_key(anyelement) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.get_partition_for_key($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.get_partition_hash(val anyelement) RETURNS integer LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.get_partition_hash(anyelement) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.get_partition_hash($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.hypertable_local_size(schema_name_in name,table_name_in name) RETURNS TABLE ( table_bytes BIGINT, index_bytes BIGINT, toast_bytes BIGINT, total_bytes BIGINT) LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.hypertable_local_size(name,name) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN QUERY SELECT * FROM _timescaledb_functions.hypertable_local_size($1,$2);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.interval_to_usec(chunk_interval interval) RETURNS bigint LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.interval_to_usec(interval) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.interval_to_usec($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.policy_compression_check(config jsonb) RETURNS void LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.policy_compression_check(jsonb) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  PERFORM _timescaledb_functions.policy_compression_check($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.policy_job_stat_history_retention(job_id integer,config jsonb) RETURNS integer LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.policy_job_stat_history_retention(integer,jsonb) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.policy_job_stat_history_retention($1,$2);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.policy_job_stat_history_retention_check(config jsonb) RETURNS void LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.policy_job_stat_history_retention_check(jsonb) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  PERFORM _timescaledb_functions.policy_job_stat_history_retention_check($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.policy_refresh_continuous_aggregate_check(config jsonb) RETURNS void LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.policy_refresh_continuous_aggregate_check(jsonb) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  PERFORM _timescaledb_functions.policy_refresh_continuous_aggregate_check($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.policy_reorder_check(config jsonb) RETURNS void LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.policy_reorder_check(jsonb) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  PERFORM _timescaledb_functions.policy_reorder_check($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.policy_retention_check(config jsonb) RETURNS void LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.policy_retention_check(jsonb) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  PERFORM _timescaledb_functions.policy_retention_check($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.process_ddl_event() RETURNS event_trigger LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.process_ddl_event() is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.process_ddl_event();\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.range_value_to_pretty(time_value bigint,column_type regtype) RETURNS text LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.range_value_to_pretty(bigint,regtype) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.range_value_to_pretty($1,$2);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.recompress_chunk_segmentwise(uncompressed_chunk regclass,if_compressed boolean=false) RETURNS regclass LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.recompress_chunk_segmentwise(regclass,boolean) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.recompress_chunk_segmentwise($1,$2);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.relation_size(relation regclass) RETURNS TABLE (total_size BIGINT, heap_size BIGINT, index_size BIGINT, toast_size BIGINT) LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.relation_size(regclass) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN QUERY SELECT * FROM _timescaledb_functions.relation_size($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.restart_background_workers() RETURNS boolean LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.restart_background_workers() is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.restart_background_workers();\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.show_chunk(chunk regclass) RETURNS TABLE(chunk_id INTEGER, hypertable_id INTEGER, schema_name NAME, table_name NAME, relkind \"char\", slices JSONB) LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.show_chunk(regclass) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN QUERY SELECT * FROM _timescaledb_functions.show_chunk($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.start_background_workers() RETURNS boolean LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.start_background_workers() is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.start_background_workers();\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.stop_background_workers() RETURNS boolean LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.stop_background_workers() is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.stop_background_workers();\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.subtract_integer_from_now(hypertable_relid regclass,lag bigint) RETURNS bigint LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.subtract_integer_from_now(regclass,bigint) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.subtract_integer_from_now($1,$2);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.time_to_internal(time_val anyelement) RETURNS bigint LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.time_to_internal(anyelement) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.time_to_internal($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.to_date(unixtime_us bigint) RETURNS date LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.to_date(bigint) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.to_date($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.to_interval(unixtime_us bigint) RETURNS interval LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.to_interval(bigint) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.to_interval($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.to_timestamp(unixtime_us bigint) RETURNS timestamp with time zone LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.to_timestamp(bigint) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.to_timestamp($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.to_timestamp_without_timezone(unixtime_us bigint) RETURNS timestamp without time zone LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.to_timestamp_without_timezone(bigint) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.to_timestamp_without_timezone($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.to_unix_microseconds(ts timestamp with time zone) RETURNS bigint LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.to_unix_microseconds(timestamp with time zone) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.to_unix_microseconds($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.tsl_loaded() RETURNS boolean LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.tsl_loaded() is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.tsl_loaded();\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.unfreeze_chunk(chunk regclass) RETURNS boolean LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'function _timescaledb_internal.unfreeze_chunk(regclass) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  RETURN _timescaledb_functions.unfreeze_chunk($1);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE PROCEDURE _timescaledb_internal.policy_compression(job_id integer,config jsonb) LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'procedure _timescaledb_internal.policy_compression(integer,jsonb) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  CALL _timescaledb_functions.policy_compression($1,$2);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE PROCEDURE _timescaledb_internal.policy_recompression(job_id integer,config jsonb) LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'procedure _timescaledb_internal.policy_recompression(integer,jsonb) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  CALL _timescaledb_functions.policy_recompression($1,$2);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE PROCEDURE _timescaledb_internal.policy_refresh_continuous_aggregate(job_id integer,config jsonb) LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'procedure _timescaledb_internal.policy_refresh_continuous_aggregate(integer,jsonb) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  CALL _timescaledb_functions.policy_refresh_continuous_aggregate($1,$2);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE PROCEDURE _timescaledb_internal.policy_reorder(job_id integer,config jsonb) LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'procedure _timescaledb_internal.policy_reorder(integer,jsonb) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  CALL _timescaledb_functions.policy_reorder($1,$2);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\nCREATE OR REPLACE PROCEDURE _timescaledb_internal.policy_retention(job_id integer,config jsonb) LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'procedure _timescaledb_internal.policy_retention(integer,jsonb) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';\n  END IF;\n  CALL _timescaledb_functions.policy_retention($1,$2);\nEND$$\nSET search_path TO pg_catalog,pg_temp;\n\n\n"
  },
  {
    "path": "sql/compression.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.compressed_data_to_array(_timescaledb_internal.compressed_data, ANYELEMENT)\n   RETURNS ANYARRAY\n   AS '@MODULE_PATHNAME@', 'ts_compressed_data_to_array'\n   LANGUAGE C IMMUTABLE PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.compressed_data_column_size(_timescaledb_internal.compressed_data, ANYELEMENT)\n   RETURNS INTEGER\n   AS '@MODULE_PATHNAME@', 'ts_compressed_data_column_size'\n   LANGUAGE C IMMUTABLE PARALLEL SAFE;\n\n"
  },
  {
    "path": "sql/compression_defaults.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\n-- This function return a jsonb with the following keys:\n-- - columns: an array of column names that shold be used for segment by\n-- - confidence: a number between 0 and 10 (most confident) indicating how sure we are.\n-- - message: a message that should be displayed to the user to evaluate the result.\nCREATE OR REPLACE FUNCTION _timescaledb_functions.get_segmentby_defaults(\n    relation regclass\n)\n    RETURNS JSONB LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n    _table_name NAME;\n    _schema_name NAME;\n    _hypertable_row _timescaledb_catalog.hypertable;\n    _segmentby NAME;\n    _cnt int;\nBEGIN\n    SELECT n.nspname, c.relname INTO STRICT _schema_name, _table_name\n    FROM pg_class c\n    INNER JOIN pg_namespace n ON (n.oid = c.relnamespace)\n    WHERE c.oid = relation;\n\n    SELECT * INTO STRICT _hypertable_row FROM _timescaledb_catalog.hypertable h WHERE h.table_name = _table_name AND h.schema_name = _schema_name;\n\n    --STEP 1 if column stats exist use unique indexes.\n    --Pick the column that comes first in any such indexes\n    --Select the column such that tuples are segmented evenly across distinct values.\n    --Note: this will only pick a column that is NOT unique in a multi-column unique index.\n    with index_attr as (\n      SELECT\n        a.attnum, min(a.pos) as pos\n      FROM (\n        SELECT indkey, indnkeyatts\n        FROM pg_catalog.pg_index\n        WHERE indisunique AND indrelid = relation\n      ) i\n      INNER JOIN LATERAL (\n        SELECT * FROM unnest(i.indkey) WITH ORDINALITY\n      ) a(attnum, pos) ON TRUE\n      WHERE a.pos <= i.indnkeyatts\n      GROUP BY a.attnum\n    ),\n    stats_with_stddev as (\n      SELECT\n        a.attname,\n        i.pos,\n        ROUND(stddev_pop(freqs)::numeric, 5) as freq_stddev\n      FROM index_attr i\n      INNER JOIN pg_attribute a ON a.attnum = i.attnum AND a.attrelid = relation\n      INNER JOIN pg_type t ON t.oid = a.atttypid\n      INNER JOIN pg_stats s ON s.attname = a.attname\n                            AND s.schemaname = _schema_name\n                            AND s.tablename = _table_name\n                            AND s.inherited = true\n      LEFT JOIN LATERAL unnest(s.most_common_freqs) as freqs ON TRUE\n      WHERE a.attname NOT IN (\n        SELECT column_name\n        FROM _timescaledb_catalog.dimension d\n        WHERE d.hypertable_id = _hypertable_row.id\n      )\n      AND s.n_distinct > 1\n      -- exclude date/time type category\n      AND t.typcategory NOT IN ('D')\n      GROUP BY a.attname, i.pos\n    )\n    SELECT attname\n    INTO _segmentby\n    FROM stats_with_stddev\n    ORDER BY pos ASC, freq_stddev ASC NULLS LAST\n    LIMIT 1;\n\n    IF FOUND THEN\n        return json_build_object('columns', json_build_array(_segmentby), 'confidence', 10);\n    END IF;\n\n\n    --STEP 2 if column stats exist and no unique indexes use non-unique indexes.\n    --Pick the column that comes first in any such indexes\n    --Select the column such that tuples are segmented evenly across distinct values.\n    with index_attr as (\n        SELECT\n        a.attnum, min(a.pos) as pos\n        FROM\n            (select indkey, indnkeyatts from pg_catalog.pg_index where NOT indisunique and indrelid = relation) i\n        INNER JOIN LATERAL\n            (select * from unnest(i.indkey) with ordinality) a(attnum, pos) ON (TRUE)\n        WHERE a.pos <= i.indnkeyatts\n        GROUP BY 1\n    ),\n    stats_with_stddev as (\n      SELECT\n        a.attname,\n        i.pos,\n        ROUND(stddev_pop(freqs)::numeric, 5) as freq_stddev\n      FROM index_attr i\n      INNER JOIN pg_attribute a ON a.attnum = i.attnum AND a.attrelid = relation\n      INNER JOIN pg_type t ON t.oid = a.atttypid\n      INNER JOIN pg_stats s ON s.attname = a.attname\n                            AND s.schemaname = _schema_name\n                            AND s.tablename = _table_name\n                            AND s.inherited = true\n      LEFT JOIN LATERAL unnest(s.most_common_freqs) as freqs ON TRUE\n      WHERE a.attname NOT IN (\n        SELECT column_name\n        FROM _timescaledb_catalog.dimension d\n        WHERE d.hypertable_id = _hypertable_row.id\n      )\n      AND s.n_distinct > 1\n      AND t.typcategory NOT IN ('D')\n      GROUP BY a.attname, i.pos\n    )\n    SELECT attname\n    INTO _segmentby\n    FROM stats_with_stddev\n    ORDER BY pos ASC, freq_stddev ASC NULLS LAST\n    LIMIT 1;\n\n    IF FOUND THEN\n        return json_build_object('columns', json_build_array(_segmentby), 'confidence', 8);\n    END IF;\n\n    --STEP 3 if column stats exist but there are no indexes\n    --Select the column such that tuples are segmented evenly across distinct values.\n    with stats_with_stddev as (\n      SELECT\n        a.attname,\n        ROUND(stddev_pop(freqs)::numeric, 5) as freq_stddev\n      FROM pg_attribute a\n      INNER JOIN pg_type t ON t.oid = a.atttypid\n      INNER JOIN pg_stats s ON s.attname = a.attname\n                            AND s.schemaname = _schema_name\n                            AND s.tablename = _table_name\n                            AND s.inherited = true\n      LEFT JOIN LATERAL unnest(s.most_common_freqs) as freqs ON TRUE\n      WHERE a.attrelid = relation\n        AND a.attname NOT IN (\n          SELECT column_name\n          FROM _timescaledb_catalog.dimension d\n          WHERE d.hypertable_id = _hypertable_row.id\n        )\n      AND s.n_distinct > 1\n      AND t.typcategory NOT IN ('D')\n      GROUP BY a.attname\n    )\n    SELECT attname\n    INTO _segmentby\n    FROM stats_with_stddev\n    ORDER BY freq_stddev ASC NULLS LAST\n    LIMIT 1;\n\n    IF FOUND THEN\n        return json_build_object('columns', json_build_array(_segmentby), 'confidence', 7);\n    END IF;\n\n    --STEP 4 if column stats do not exist use non-unique indexes. Pick the column that comes first in any such indexes. Ties are broken arbitrarily.\n    with index_attr as (\n        SELECT\n        a.attnum, min(a.pos) as pos\n        FROM\n            (select indkey, indnkeyatts from pg_catalog.pg_index where NOT indisunique and indrelid = relation) i\n        INNER JOIN LATERAL\n            (select * from unnest(i.indkey) with ordinality) a(attnum, pos) ON (TRUE)\n        WHERE a.pos <= i.indnkeyatts\n        GROUP BY 1\n    )\n    SELECT\n      a.attname INTO _segmentby\n    FROM\n      index_attr i\n    INNER JOIN\n      pg_attribute a on (a.attnum = i.attnum AND a.attrelid = relation)\n    INNER JOIN\n      pg_type t ON t.oid = a.atttypid\n    LEFT JOIN\n      pg_catalog.pg_attrdef ad ON (ad.adrelid = relation AND ad.adnum = a.attnum)\n    LEFT JOIN pg_stats s ON s.attname = a.attname\n                          AND s.schemaname = _schema_name\n                          AND s.tablename = _table_name\n                          AND s.inherited = true\n    WHERE\n      a.attname NOT IN (SELECT column_name FROM _timescaledb_catalog.dimension d WHERE d.hypertable_id = _hypertable_row.id)\n      AND s.n_distinct is null\n      AND a.attidentity = '' AND (ad.adbin IS NULL OR pg_get_expr(adbin, adrelid) not like 'nextval%')\n      AND t.typcategory NOT IN ('D')\n    ORDER BY i.pos\n    LIMIT 1;\n\n    IF FOUND THEN\n        return json_build_object(\n            'columns', json_build_array(_segmentby),\n            'confidence', 5,\n            'message',  'Please make sure '|| _segmentby||' is not a unique column and appropriate for a segment by');\n    END IF;\n\n    --STEP 5 if column stats do not exist and no non-unique indexes, use unique indexes. Pick the column that comes first in any such indexes. Ties are broken arbitrarily.\n    with index_attr as (\n        SELECT\n        a.attnum, min(a.pos) as pos\n        FROM\n            (select indkey, indnkeyatts from pg_catalog.pg_index where indisunique and indrelid = relation) i\n        INNER JOIN LATERAL\n            (select * from unnest(i.indkey) with ordinality) a(attnum, pos) ON (TRUE)\n        WHERE a.pos <= i.indnkeyatts\n        GROUP BY 1\n    )\n    SELECT\n      a.attname INTO _segmentby\n    FROM\n      index_attr i\n    INNER JOIN\n      pg_attribute a on (a.attnum = i.attnum AND a.attrelid = relation)\n    INNER JOIN\n      pg_type t ON t.oid = a.atttypid\n    LEFT JOIN\n      pg_catalog.pg_attrdef ad ON (ad.adrelid = relation AND ad.adnum = a.attnum)\n    LEFT JOIN pg_stats s ON s.attname = a.attname\n                          AND s.schemaname = _schema_name\n                          AND s.tablename = _table_name\n                          AND s.inherited = true\n    WHERE\n      a.attname NOT IN (SELECT column_name FROM _timescaledb_catalog.dimension d WHERE d.hypertable_id = _hypertable_row.id)\n      AND s.n_distinct is null\n      AND a.attidentity = '' AND (ad.adbin IS NULL OR pg_get_expr(adbin, adrelid) not like 'nextval%')\n      AND t.typcategory NOT IN ('D')\n    ORDER BY i.pos\n    LIMIT 1;\n\n    IF FOUND THEN\n            return json_build_object(\n            'columns', json_build_array(_segmentby),\n            'confidence', 5,\n            'message',  'Please make sure '|| _segmentby||' is not a unique column and appropriate for a segment by');\n    END IF;\n\n\n    --are there any indexed columns that are not dimemsions and are not serial/identity?\n    with index_attr as (\n        SELECT\n        a.attnum, min(a.pos) as pos\n        FROM\n            (select indkey, indnkeyatts from pg_catalog.pg_index where indisunique and indrelid = relation) i\n        INNER JOIN LATERAL\n            (select * from unnest(i.indkey) with ordinality) a(attnum, pos) ON (TRUE)\n        WHERE a.pos <= i.indnkeyatts\n        GROUP BY 1\n    )\n    SELECT\n      count(*) INTO STRICT _cnt\n    FROM\n      index_attr i\n    INNER JOIN\n      pg_attribute a on (a.attnum = i.attnum AND a.attrelid = relation)\n    INNER JOIN\n      pg_type t ON t.oid = a.atttypid\n    LEFT JOIN\n      pg_catalog.pg_attrdef ad ON (ad.adrelid = relation AND ad.adnum = a.attnum)\n    WHERE\n      a.attname NOT IN (SELECT column_name FROM _timescaledb_catalog.dimension d WHERE d.hypertable_id = _hypertable_row.id)\n      AND a.attidentity = '' AND (ad.adbin IS NULL OR pg_get_expr(adbin, adrelid) not like 'nextval%')\n      AND t.typcategory NOT IN ('D');\n\n    IF _cnt > 0 THEN\n        --there are many potential candidates. We do not have enough information to choose one.\n        return json_build_object(\n            'columns', json_build_array(),\n            'confidence', 0,\n            'message',  'Several columns are potential segment by candidates and we do not have enough information to choose one. Please use the segment_by option to explicitly specify the segment_by column');\n    ELSE\n        --there are no potential candidates. There is a good chance no segment by is the correct choice.\n        return json_build_object(\n            'columns', json_build_array(),\n            'confidence', 5,\n            'message',  'You do not have any indexes on columns that can be used for segment_by and thus we are not using segment_by for converting to columnstore. Please make sure you are not missing any indexes');\n    END IF;\nEND\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\n-- This function return a jsonb with the following keys:\n-- - clauses: an array of column names and sort order key words that shold be used for order by.\n-- - confidence: a number between 0 and 10 (most confident) indicating how sure we are.\n-- - message: a message that should be shown to the user to evaluate the result.\nCREATE OR REPLACE FUNCTION _timescaledb_functions.get_orderby_defaults(\n    relation regclass, segment_by_cols text[]\n)\n    RETURNS JSONB LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n    _table_name NAME;\n    _schema_name NAME;\n    _hypertable_row _timescaledb_catalog.hypertable;\n    _orderby_names NAME[];\n    _dimension_names NAME[];\n    _first_index_attrs NAME[];\n    _orderby_clauses text[];\n    _confidence int;\nBEGIN\n    SELECT n.nspname, c.relname INTO STRICT _schema_name, _table_name\n    FROM pg_class c\n    INNER JOIN pg_namespace n ON (n.oid = c.relnamespace)\n    WHERE c.oid = relation;\n\n    SELECT * INTO STRICT _hypertable_row FROM _timescaledb_catalog.hypertable h WHERE h.table_name = _table_name AND h.schema_name = _schema_name;\n\n    --start with the unique index columns minus the segment by columns\n    with index_attr as (\n        SELECT\n        a.attnum, min(a.pos) as pos\n        FROM\n             --is there a better way to pick the right unique index if there are multiple?\n            (select indkey, indnkeyatts from pg_catalog.pg_index where indisunique and indrelid = relation limit 1) i\n        INNER JOIN LATERAL\n            (select * from unnest(i.indkey) with ordinality) a(attnum, pos) ON (TRUE)\n        WHERE a.pos <= i.indnkeyatts\n        GROUP BY 1\n    )\n    SELECT\n      array_agg(a.attname ORDER BY i.pos) INTO _orderby_names\n    FROM\n      index_attr i\n    INNER JOIN\n      pg_attribute a on (a.attnum = i.attnum AND a.attrelid = relation)\n    WHERE\n      NOT(a.attname::text = ANY (segment_by_cols));\n\n    if _orderby_names is null then\n        _orderby_names := array[]::name[];\n        _confidence := 5;\n    else\n        _confidence := 8;\n    end if;\n\n    --add dimension colomns to the end. A dimension column like time should probably always be part of the order by.\n    SELECT\n      array_agg(d.column_name) INTO _dimension_names\n    FROM _timescaledb_catalog.dimension d\n    WHERE\n      d.hypertable_id = _hypertable_row.id\n      AND NOT(d.column_name::text = ANY (_orderby_names))\n      AND NOT(d.column_name::text = ANY (segment_by_cols));\n    _orderby_names := _orderby_names || _dimension_names;\n\n    --add the first attribute of any index\n    with index_attr as (\n        SELECT\n        a.attnum, min(a.pos) as pos\n        FROM\n            (select indkey, indnkeyatts from pg_catalog.pg_index where indrelid = relation) i\n        INNER JOIN LATERAL\n            (select * from unnest(i.indkey) with ordinality) a(attnum, pos) ON (TRUE)\n        WHERE a.pos = 1\n        GROUP BY 1\n    )\n    SELECT\n      array_agg(a.attname ORDER BY i.pos) INTO _first_index_attrs\n    FROM\n      index_attr i\n    INNER JOIN\n      pg_attribute a on (a.attnum = i.attnum AND a.attrelid = relation)\n    WHERE\n          NOT(a.attname::text = ANY (_orderby_names))\n      AND NOT(a.attname::text = ANY (segment_by_cols));\n\n    _orderby_names := _orderby_names || _first_index_attrs;\n\n    --add DESC to any dimensions\n    SELECT\n      coalesce(array_agg(\n      CASE WHEN d.column_name IS NULL THEN\n        format('%I', a.colname)\n      ELSE\n        format('%I DESC', a.colname)\n      END ORDER BY pos), array[]::text[]) INTO STRICT _orderby_clauses\n    FROM unnest(_orderby_names) WITH ORDINALITY as a(colname, pos)\n    LEFT JOIN _timescaledb_catalog.dimension d ON (d.column_name = a.colname AND d.hypertable_id = _hypertable_row.id);\n\n\n    return json_build_object('clauses', _orderby_clauses, 'confidence', _confidence);\nEND\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n"
  },
  {
    "path": "sql/ddl_api.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- This file defines DDL functions for adding and manipulating hypertables.\n\n-- Converts a regular postgres table to a hypertable.\n--\n-- relation - The OID of the table to be converted\n-- time_column_name - Name of the column that contains time for a given record\n-- partitioning_column - Name of the column to partition data by\n-- number_partitions - (Optional) Number of partitions for data\n-- associated_schema_name - (Optional) Schema for internal hypertable tables\n-- associated_table_prefix - (Optional) Prefix for internal hypertable table names\n-- chunk_time_interval - (Optional) Initial time interval for a chunk\n-- create_default_indexes - (Optional) Whether or not to create the default indexes\n-- if_not_exists - (Optional) Do not fail if table is already a hypertable\n-- partitioning_func - (Optional) The partitioning function to use for spatial partitioning\n-- migrate_data - (Optional) Set to true to migrate any existing data in the table to chunks\n-- chunk_target_size - (Optional) The target size for chunks (e.g., '1000MB', 'estimate', or 'off')\n-- chunk_sizing_func - (Optional) A function to calculate the chunk time interval for new chunks\n-- time_partitioning_func - (Optional) The partitioning function to use for \"time\" partitioning\nCREATE OR REPLACE FUNCTION @extschema@.create_hypertable(\n    relation                REGCLASS,\n    time_column_name        NAME,\n    partitioning_column     NAME = NULL,\n    number_partitions       INTEGER = NULL,\n    associated_schema_name  NAME = NULL,\n    associated_table_prefix NAME = NULL,\n    chunk_time_interval     ANYELEMENT = NULL::bigint,\n    create_default_indexes  BOOLEAN = TRUE,\n    if_not_exists           BOOLEAN = FALSE,\n    partitioning_func       REGPROC = NULL,\n    migrate_data            BOOLEAN = FALSE,\n    chunk_target_size       TEXT = NULL,\n    chunk_sizing_func       REGPROC = '_timescaledb_functions.calculate_chunk_interval'::regproc,\n    time_partitioning_func  REGPROC = NULL\n) RETURNS TABLE(hypertable_id INT, schema_name NAME, table_name NAME, created BOOL) AS '@MODULE_PATHNAME@', 'ts_hypertable_create' LANGUAGE C VOLATILE;\n\n-- A generalized hypertable creation API that can be used to convert a PostgreSQL table\n-- with TIME/SERIAL/BIGSERIAL columns to a hypertable.\n--\n-- relation - The OID of the table to be converted\n-- dimension - The dimension to use for partitioning\n-- create_default_indexes (Optional) Whether or not to create the default indexes\n-- if_not_exists (Optional) Do not fail if table is already a hypertable\n-- migrate_data (Optional) Set to true to migrate any existing data in the table to chunks\nCREATE OR REPLACE FUNCTION @extschema@.create_hypertable(\n    relation                REGCLASS,\n    dimension               _timescaledb_internal.dimension_info,\n    create_default_indexes  BOOLEAN = TRUE,\n    if_not_exists           BOOLEAN = FALSE,\n    migrate_data            BOOLEAN = FALSE\n) RETURNS TABLE(hypertable_id INT, created BOOL) AS '@MODULE_PATHNAME@', 'ts_hypertable_create_general' LANGUAGE C VOLATILE;\n\n\n-- Set adaptive chunking. To disable, set chunk_target_size => 'off'.\nCREATE OR REPLACE FUNCTION @extschema@.set_adaptive_chunking(\n    hypertable                     REGCLASS,\n    chunk_target_size              TEXT,\n    INOUT chunk_sizing_func        REGPROC = '_timescaledb_functions.calculate_chunk_interval'::regproc,\n    OUT chunk_target_size          BIGINT\n) RETURNS RECORD AS '@MODULE_PATHNAME@', 'ts_chunk_adaptive_set' LANGUAGE C VOLATILE;\n\n-- Update chunk_time_interval for a hypertable [DEPRECATED].\n--\n-- hypertable - The OID of the table corresponding to a hypertable whose time\n--     interval should be updated\n-- chunk_time_interval - The new time interval. For hypertables with integral\n--     time columns, this must be an integral type. For hypertables with a\n--     TIMESTAMP/TIMESTAMPTZ/DATE type, it can be integral which is treated as\n--     microseconds, or an INTERVAL type.\nCREATE OR REPLACE FUNCTION @extschema@.set_chunk_time_interval(\n    hypertable              REGCLASS,\n    chunk_time_interval     ANYELEMENT,\n    dimension_name          NAME = NULL\n) RETURNS VOID AS '@MODULE_PATHNAME@', 'ts_dimension_set_interval' LANGUAGE C VOLATILE;\n\n-- Update partition_interval for a hypertable.\n--\n-- hypertable - The OID of the table corresponding to a hypertable whose\n--     partition interval should be updated\n-- partition_interval - The new interval. For hypertables with integral/serial/bigserial\n--     time columns, this must be an integral type. For hypertables with a\n--     TIMESTAMP/TIMESTAMPTZ/DATE type, it can be integral which is treated as\n--     microseconds, or an INTERVAL type.\nCREATE OR REPLACE FUNCTION @extschema@.set_partitioning_interval(\n    hypertable              REGCLASS,\n    partition_interval      ANYELEMENT,\n    dimension_name          NAME = NULL\n) RETURNS VOID AS '@MODULE_PATHNAME@', 'ts_dimension_set_interval' LANGUAGE C VOLATILE;\n\nCREATE OR REPLACE FUNCTION @extschema@.set_number_partitions(\n    hypertable              REGCLASS,\n    number_partitions       INTEGER,\n    dimension_name          NAME = NULL\n) RETURNS VOID AS '@MODULE_PATHNAME@', 'ts_dimension_set_num_slices' LANGUAGE C VOLATILE;\n\n-- Drop chunks older than the given timestamp for the specific\n-- hypertable or continuous aggregate.\nCREATE OR REPLACE FUNCTION @extschema@.drop_chunks(\n    relation               REGCLASS,\n    older_than             \"any\" = NULL,\n    newer_than             \"any\" = NULL,\n    verbose                BOOLEAN = FALSE,\n    created_before         \"any\" = NULL,\n    created_after          \"any\" = NULL\n) RETURNS SETOF TEXT AS '@MODULE_PATHNAME@', 'ts_chunk_drop_chunks'\nLANGUAGE C VOLATILE PARALLEL UNSAFE;\n\n-- show chunks older than or newer than a specific time.\n-- `relation` must be a valid hypertable or continuous aggregate.\nCREATE OR REPLACE FUNCTION @extschema@.show_chunks(\n    relation               REGCLASS,\n    older_than             \"any\" = NULL,\n    newer_than             \"any\" = NULL,\n    created_before         \"any\" = NULL,\n    created_after          \"any\" = NULL\n) RETURNS SETOF REGCLASS AS '@MODULE_PATHNAME@', 'ts_chunk_show_chunks'\nLANGUAGE C STABLE PARALLEL SAFE;\n\n-- Add a dimension (of partitioning) to a hypertable [DEPRECATED]\n--\n-- hypertable - OID of the table to add a dimension to\n-- column_name - NAME of the column to use in partitioning for this dimension\n-- number_partitions - Number of partitions, for non-time dimensions\n-- chunk_time_interval - Size of intervals for time dimensions (can be integral or INTERVAL)\n-- partitioning_func - Function used to partition the column\n-- if_not_exists - If set, and the dimension already exists, generate a notice instead of an error\nCREATE OR REPLACE FUNCTION @extschema@.add_dimension(\n    hypertable              REGCLASS,\n    column_name             NAME,\n    number_partitions       INTEGER = NULL,\n    chunk_time_interval     ANYELEMENT = NULL::BIGINT,\n    partitioning_func       REGPROC = NULL,\n    if_not_exists           BOOLEAN = FALSE\n) RETURNS TABLE(dimension_id INT, schema_name NAME, table_name NAME, column_name NAME, created BOOL)\nAS '@MODULE_PATHNAME@', 'ts_dimension_add' LANGUAGE C VOLATILE;\n\n-- Add a dimension (of partitioning) to a hypertable.\n--\n-- hypertable - OID of the table to add a dimension to\n-- dimension - Dimension to add\n-- if_not_exists - If set, and the dimension already exists, generate a notice instead of an error\nCREATE OR REPLACE FUNCTION @extschema@.add_dimension(\n    hypertable              REGCLASS,\n    dimension               _timescaledb_internal.dimension_info,\n    if_not_exists           BOOLEAN = FALSE\n) RETURNS TABLE(dimension_id INT, created BOOL)\nAS '@MODULE_PATHNAME@', 'ts_dimension_add_general' LANGUAGE C VOLATILE;\n\n-- Enable tracking of statistics on a column of a hypertable.\n--\n-- hypertable - OID of the table to which the column belongs to\n-- column_name - The column to track statistics for\n-- if_not_exists - If set, and the entry already exists, generate a notice instead of an error\n-- Returns the \"id\" of the entry created. The \"enabled\" field\n-- is set to true if entry is created or exists already.\nCREATE OR REPLACE FUNCTION @extschema@.enable_chunk_skipping(\n    hypertable              REGCLASS,\n    column_name             NAME,\n    if_not_exists           BOOLEAN = FALSE\n) RETURNS TABLE(column_stats_id INT, enabled BOOL)\nAS '@MODULE_PATHNAME@', 'ts_chunk_column_stats_enable' LANGUAGE C VOLATILE;\n\n-- Disable tracking of statistics on a column of a hypertable.\n--\n-- hypertable - OID of the table to remove from\n-- column_name - NAME of the column on which the stats are tracked\n-- if_not_exists - If set, and the entry does not exist,\n-- generate a notice instead of an error. The \"disabled\" field\n-- is set to true if entry is deleted successfully.\nCREATE OR REPLACE FUNCTION @extschema@.disable_chunk_skipping(\n    hypertable              REGCLASS,\n    column_name             NAME,\n    if_not_exists           BOOLEAN = FALSE\n) RETURNS TABLE(hypertable_id INT, column_name NAME, disabled BOOL)\nAS '@MODULE_PATHNAME@', 'ts_chunk_column_stats_disable' LANGUAGE C VOLATILE;\n\nCREATE OR REPLACE FUNCTION @extschema@.by_hash(column_name NAME, number_partitions INTEGER,\n                                               partition_func regproc = NULL)\n    RETURNS _timescaledb_internal.dimension_info LANGUAGE C\n    AS '@MODULE_PATHNAME@', 'ts_hash_dimension';\n\nCREATE OR REPLACE FUNCTION @extschema@.by_range(column_name NAME,\n                                                partition_interval ANYELEMENT = NULL::bigint,\n                                                partition_func regproc = NULL)\n    RETURNS _timescaledb_internal.dimension_info LANGUAGE C\n    AS '@MODULE_PATHNAME@', 'ts_range_dimension';\n\nCREATE OR REPLACE FUNCTION @extschema@.attach_tablespace(\n    tablespace NAME,\n    hypertable REGCLASS,\n    if_not_attached BOOLEAN = false\n) RETURNS VOID\nAS '@MODULE_PATHNAME@', 'ts_tablespace_attach' LANGUAGE C VOLATILE;\n\nCREATE OR REPLACE FUNCTION @extschema@.detach_tablespace(\n    tablespace NAME,\n    hypertable REGCLASS = NULL,\n    if_attached BOOLEAN = false\n) RETURNS INTEGER\nAS '@MODULE_PATHNAME@', 'ts_tablespace_detach' LANGUAGE C VOLATILE;\n\nCREATE OR REPLACE FUNCTION @extschema@.detach_tablespaces(hypertable REGCLASS) RETURNS INTEGER\nAS '@MODULE_PATHNAME@', 'ts_tablespace_detach_all_from_hypertable' LANGUAGE C VOLATILE;\n\nCREATE OR REPLACE FUNCTION @extschema@.show_tablespaces(hypertable REGCLASS) RETURNS SETOF NAME\nAS '@MODULE_PATHNAME@', 'ts_tablespace_show' LANGUAGE C VOLATILE STRICT;\n\n-- Refresh a continuous aggregate across the given window.\nCREATE OR REPLACE PROCEDURE @extschema@.refresh_continuous_aggregate(\n    continuous_aggregate     REGCLASS,\n    window_start             \"any\",\n    window_end               \"any\",\n    force                    BOOLEAN = FALSE,\n    options                  JSONB = NULL\n) LANGUAGE C AS '@MODULE_PATHNAME@', 'ts_continuous_agg_refresh';\n\n"
  },
  {
    "path": "sql/ddl_triggers.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nDROP EVENT TRIGGER IF EXISTS timescaledb_ddl_command_end;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.process_ddl_event() RETURNS event_trigger\nAS '@MODULE_PATHNAME@', 'ts_timescaledb_process_ddl_event' LANGUAGE C;\n\n--EVENT TRIGGER MUST exclude the ALTER EXTENSION tag.\nCREATE EVENT TRIGGER timescaledb_ddl_command_end ON ddl_command_end\nWHEN TAG IN ('ALTER TABLE','CREATE TRIGGER','CREATE TABLE','CREATE INDEX','ALTER INDEX', 'DROP TABLE', 'DROP INDEX', 'DROP SCHEMA')\nEXECUTE FUNCTION _timescaledb_functions.process_ddl_event();\n\nDROP EVENT TRIGGER IF EXISTS timescaledb_ddl_sql_drop;\nCREATE EVENT TRIGGER timescaledb_ddl_sql_drop ON sql_drop\nEXECUTE FUNCTION _timescaledb_functions.process_ddl_event();\n"
  },
  {
    "path": "sql/debug_build_utils.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- This file contains utility functions that are only used in debug\n-- builds for debugging and testing.\n\nCREATE OR REPLACE FUNCTION debug_waitpoint_enable(TEXT) RETURNS VOID LANGUAGE C VOLATILE STRICT\nAS '@MODULE_PATHNAME@', 'ts_debug_point_enable';\n\nCREATE OR REPLACE FUNCTION debug_waitpoint_release(TEXT) RETURNS VOID LANGUAGE C VOLATILE STRICT\nAS '@MODULE_PATHNAME@', 'ts_debug_point_release';\n\nCREATE OR REPLACE FUNCTION debug_waitpoint_id(TEXT) RETURNS BIGINT LANGUAGE C VOLATILE STRICT\nAS '@MODULE_PATHNAME@', 'ts_debug_point_id';\n\nCREATE OR REPLACE FUNCTION ts_now_mock()\nRETURNS TIMESTAMPTZ AS '@MODULE_PATHNAME@', 'ts_now_mock' LANGUAGE C STABLE STRICT PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION clear_hypertable_cache() RETURNS VOID\nAS '@MODULE_PATHNAME@', 'ts_hypertable_cache_clear' LANGUAGE C;\n"
  },
  {
    "path": "sql/debug_utils.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- This file contains utility functions and views that are used for\n-- debugging in release builds. These are all placed in the schema\n-- _timescaledb_debug.\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.extension_state() RETURNS TEXT\nAS '@MODULE_PATHNAME@', 'ts_extension_get_state' LANGUAGE C;\n\n"
  },
  {
    "path": "sql/gapfill.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket_gapfill(bucket_width SMALLINT, ts SMALLINT, start SMALLINT=NULL, finish SMALLINT=NULL) RETURNS SMALLINT\n\tAS '@MODULE_PATHNAME@', 'ts_gapfill_int16_bucket' LANGUAGE C VOLATILE PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket_gapfill(bucket_width INT, ts INT, start INT=NULL, finish INT=NULL) RETURNS INT\n\tAS '@MODULE_PATHNAME@', 'ts_gapfill_int32_bucket' LANGUAGE C VOLATILE PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket_gapfill(bucket_width BIGINT, ts BIGINT, start BIGINT=NULL, finish BIGINT=NULL) RETURNS BIGINT\n\tAS '@MODULE_PATHNAME@', 'ts_gapfill_int64_bucket' LANGUAGE C VOLATILE PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket_gapfill(bucket_width INTERVAL, ts DATE, start DATE=NULL, finish DATE=NULL) RETURNS DATE\n\tAS '@MODULE_PATHNAME@', 'ts_gapfill_date_bucket' LANGUAGE C VOLATILE PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket_gapfill(bucket_width INTERVAL, ts TIMESTAMP, start TIMESTAMP=NULL, finish TIMESTAMP=NULL) RETURNS TIMESTAMP\n\tAS '@MODULE_PATHNAME@', 'ts_gapfill_timestamp_bucket' LANGUAGE C VOLATILE PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket_gapfill(bucket_width INTERVAL, ts TIMESTAMPTZ, start TIMESTAMPTZ=NULL, finish TIMESTAMPTZ=NULL) RETURNS TIMESTAMPTZ\n\tAS '@MODULE_PATHNAME@', 'ts_gapfill_timestamptz_bucket' LANGUAGE C VOLATILE PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket_gapfill(bucket_width INTERVAL, ts TIMESTAMPTZ, timezone TEXT, start TIMESTAMPTZ=NULL, finish TIMESTAMPTZ=NULL) RETURNS TIMESTAMPTZ\n\tAS '@MODULE_PATHNAME@', 'ts_gapfill_timestamptz_timezone_bucket' LANGUAGE C VOLATILE PARALLEL SAFE;\n\n-- locf function\nCREATE OR REPLACE FUNCTION @extschema@.locf(value ANYELEMENT, prev ANYELEMENT=NULL, treat_null_as_missing BOOL=false) RETURNS ANYELEMENT\n\tAS '@MODULE_PATHNAME@', 'ts_gapfill_marker' LANGUAGE C VOLATILE PARALLEL SAFE;\n\n-- interpolate functions\nCREATE OR REPLACE FUNCTION @extschema@.interpolate(value SMALLINT,prev RECORD=NULL,next RECORD=NULL) RETURNS SMALLINT\n\tAS '@MODULE_PATHNAME@', 'ts_gapfill_marker' LANGUAGE C VOLATILE PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION @extschema@.interpolate(value INT,prev RECORD=NULL,next RECORD=NULL) RETURNS INT\n\tAS '@MODULE_PATHNAME@', 'ts_gapfill_marker' LANGUAGE C VOLATILE PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION @extschema@.interpolate(value BIGINT,prev RECORD=NULL,next RECORD=NULL) RETURNS BIGINT\n\tAS '@MODULE_PATHNAME@', 'ts_gapfill_marker' LANGUAGE C VOLATILE PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION @extschema@.interpolate(value REAL,prev RECORD=NULL,next RECORD=NULL) RETURNS REAL\n\tAS '@MODULE_PATHNAME@', 'ts_gapfill_marker' LANGUAGE C VOLATILE PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION @extschema@.interpolate(value FLOAT,prev RECORD=NULL,next RECORD=NULL) RETURNS FLOAT\n\tAS '@MODULE_PATHNAME@', 'ts_gapfill_marker' LANGUAGE C VOLATILE PARALLEL SAFE;\n\n"
  },
  {
    "path": "sql/header.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- This file is always prepended to all installation and upgrade/downgrade scripts.\nSET LOCAL search_path TO pg_catalog, pg_temp;\n"
  },
  {
    "path": "sql/histogram.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.hist_sfunc (state INTERNAL, val DOUBLE PRECISION, MIN DOUBLE PRECISION, MAX DOUBLE PRECISION, nbuckets INTEGER)\nRETURNS INTERNAL\nAS '@MODULE_PATHNAME@', 'ts_hist_sfunc'\nLANGUAGE C IMMUTABLE PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.hist_combinefunc(state1 INTERNAL, state2 INTERNAL)\nRETURNS INTERNAL\nAS '@MODULE_PATHNAME@', 'ts_hist_combinefunc'\nLANGUAGE C IMMUTABLE PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.hist_serializefunc(INTERNAL)\nRETURNS bytea\nAS '@MODULE_PATHNAME@', 'ts_hist_serializefunc'\nLANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.hist_deserializefunc(bytea, INTERNAL)\nRETURNS INTERNAL\nAS '@MODULE_PATHNAME@', 'ts_hist_deserializefunc'\nLANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.hist_finalfunc(state INTERNAL, val DOUBLE PRECISION, MIN DOUBLE PRECISION, MAX DOUBLE PRECISION, nbuckets INTEGER)\nRETURNS INTEGER[]\nAS '@MODULE_PATHNAME@', 'ts_hist_finalfunc'\nLANGUAGE C IMMUTABLE PARALLEL SAFE;\n\n-- We started using CREATE OR REPLACE AGGREGATE for aggregate creation once the syntax was fully supported\n-- as it is easier to support idempotent changes this way. This will allow for changes to functions supporting\n-- the aggregate, and, for instance, the definition and inclusion of inverse functions for window function\n-- support. However, it should still be noted that changes to the data structures used for the internal\n-- state of the aggregate must be backwards compatible and the old format must be accepted by any new functions\n-- in order for them to continue working with Continuous Aggregates, where old states may have been materialized.\n\n-- This aggregate partitions the dataset into a specified number of buckets (nbuckets) ranging\n-- from the inputted min to max values.\nCREATE OR REPLACE AGGREGATE @extschema@.histogram (DOUBLE PRECISION, DOUBLE PRECISION, DOUBLE PRECISION, INTEGER) (\n    SFUNC = _timescaledb_functions.hist_sfunc,\n    STYPE = INTERNAL,\n    COMBINEFUNC = _timescaledb_functions.hist_combinefunc,\n    SERIALFUNC = _timescaledb_functions.hist_serializefunc,\n    DESERIALFUNC = _timescaledb_functions.hist_deserializefunc,\n    PARALLEL = SAFE,\n    FINALFUNC = _timescaledb_functions.hist_finalfunc,\n    FINALFUNC_EXTRA\n);\n"
  },
  {
    "path": "sql/hypertable.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE OR REPLACE FUNCTION @extschema@.set_integer_now_func(hypertable REGCLASS, integer_now_func REGPROC, replace_if_exists BOOL = false) RETURNS VOID\nAS '@MODULE_PATHNAME@', 'ts_hypertable_set_integer_now_func'\nLANGUAGE C VOLATILE STRICT;\n\n"
  },
  {
    "path": "sql/job_api.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE OR REPLACE FUNCTION @extschema@.add_job(\n  proc REGPROC,\n  schedule_interval INTERVAL,\n  config JSONB DEFAULT NULL,\n  initial_start TIMESTAMPTZ DEFAULT NULL,\n  scheduled BOOL DEFAULT true,\n  check_config REGPROC DEFAULT NULL,\n  fixed_schedule BOOL DEFAULT TRUE,\n  timezone TEXT DEFAULT NULL,\n  job_name TEXT DEFAULT NULL\n) RETURNS INTEGER AS '@MODULE_PATHNAME@', 'ts_job_add' LANGUAGE C VOLATILE;\n\nCREATE OR REPLACE FUNCTION @extschema@.delete_job(job_id INTEGER) RETURNS VOID AS '@MODULE_PATHNAME@', 'ts_job_delete' LANGUAGE C VOLATILE STRICT;\nCREATE OR REPLACE PROCEDURE @extschema@.run_job(job_id INTEGER) AS '@MODULE_PATHNAME@', 'ts_job_run' LANGUAGE C;\n\n-- Returns the updated job schedule values\nCREATE OR REPLACE FUNCTION @extschema@.alter_job(\n    job_id INTEGER,\n    schedule_interval INTERVAL = NULL,\n    max_runtime INTERVAL = NULL,\n    max_retries INTEGER = NULL,\n    retry_period INTERVAL = NULL,\n    scheduled BOOL = NULL,\n    config JSONB = NULL,\n    next_start TIMESTAMPTZ = NULL,\n    if_exists BOOL = FALSE,\n    check_config REGPROC = NULL,\n    fixed_schedule BOOL = NULL,\n    initial_start TIMESTAMPTZ = NULL,\n    timezone TEXT DEFAULT NULL,\n    job_name TEXT DEFAULT NULL\n)\nRETURNS TABLE (job_id INTEGER, schedule_interval INTERVAL, max_runtime INTERVAL, max_retries INTEGER, retry_period INTERVAL, scheduled BOOL, config JSONB,\nnext_start TIMESTAMPTZ, check_config TEXT, fixed_schedule BOOL, initial_start TIMESTAMPTZ, timezone TEXT, application_name name)\nAS '@MODULE_PATHNAME@', 'ts_job_alter'\nLANGUAGE C VOLATILE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.alter_job_set_hypertable_id(\n    job_id INTEGER,\n    hypertable REGCLASS )\nRETURNS INTEGER AS '@MODULE_PATHNAME@', 'ts_job_alter_set_hypertable_id'\nLANGUAGE C VOLATILE;\n"
  },
  {
    "path": "sql/job_stat_history_log_retention.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- A retention policy is set up for the table _timescaledb_internal.job_errors (Error Log Retention Policy [2])\n-- By default, it will run once a month and and drop rows older than a month.\n\n-- We use binary search on the id column to figure out which rows should be retained from the job history table\n-- Doing it this way allows us to use the index on the `id` column, which (empirically) we found is faster than querying on the `execution_finish` column directly without an index\n-- This works because `execution_finish` is always ordered.\n-- We can consider alternative approaches to simplify this in the future.\nCREATE OR REPLACE FUNCTION _timescaledb_functions.job_history_bsearch(search_point TIMESTAMPTZ) RETURNS BIGINT\nAS\n$$\nDECLARE\n  id_lower BIGINT;\n  id_upper BIGINT;\n  id_middle BIGINT DEFAULT 0;\n  target_tz TIMESTAMPTZ;\nBEGIN\n\n  SELECT COALESCE(min(id), 0), COALESCE(max(id), 0)\n  INTO id_lower, id_upper\n  FROM _timescaledb_internal.bgw_job_stat_history;\n\n  IF id_lower = 0 AND id_upper = 0 THEN\n    RETURN NULL;\n  END IF;\n\n  -- We want the first entry in the table where execution_finish is >= search_point\n  WHILE id_lower < id_upper LOOP\n    id_middle := id_lower + (id_upper - id_lower) / 2;\n\n    SELECT execution_finish\n    INTO target_tz\n    FROM _timescaledb_internal.bgw_job_stat_history\n    WHERE id = id_middle;\n\n    -- If the id_middle is not found, shift to a previous id that's still in the search space\n    IF NOT FOUND THEN\n      SELECT execution_finish, id\n      INTO target_tz, id_middle\n      FROM _timescaledb_internal.bgw_job_stat_history\n      WHERE id <= id_middle AND id >= id_lower\n      ORDER BY id LIMIT 1;\n\n      IF NOT FOUND THEN\n        id_middle := id_lower;\n      END IF;\n\n    END IF;\n\n    IF target_tz >= search_point THEN\n      id_upper := id_middle;\n    ELSE\n      id_lower := id_middle + 1;\n    END IF;\n  END LOOP;\n\n  -- Handle the case where no ids need to be deleted and return NULL instead\n  SELECT execution_finish\n  INTO target_tz\n  FROM _timescaledb_internal.bgw_job_stat_history\n  WHERE id = id_lower;\n\n  IF target_tz < search_point THEN\n    RETURN NULL;\n  END IF;\n\n  RETURN id_lower;\nEND\n$$\nLANGUAGE plpgsql SET search_path TO pg_catalog, pg_temp;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.policy_job_stat_history_retention(job_id integer, config JSONB) RETURNS VOID\nLANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n    search_point TIMESTAMPTZ;\n    id_found BIGINT;\nBEGIN\n  PERFORM set_config('lock_timeout', coalesce(config->>'lock_timeout', '5s'), true /* is local */);\n\n  -- We need to prevent concurrent changes on this table when running this retention job\n  -- We take an AccessExclusiveLock at the start since we TRUNCATE later\n  LOCK TABLE _timescaledb_internal.bgw_job_stat_history IN ACCESS EXCLUSIVE MODE;\n\n  search_point := now() - (config->>'drop_after')::interval;\n\n  id_found := _timescaledb_functions.job_history_bsearch(search_point);\n\n  IF id_found IS NULL THEN\n    RETURN;\n  END IF;\n\n  -- Build a table that contains only rows younger than the max age\n  -- and satisfy the constraints on number of successfull and failures\n  -- for each job. Since the table is ordered, we can use the \"id\"\n  -- column to find out what records to remove.\n  CREATE TEMP TABLE __tmp_bgw_job_stat_history ON COMMIT DROP AS\n  WITH\n    enumerated AS (\n      SELECT *,\n             row_number() OVER (\n                 PARTITION BY j.job_id, j.succeeded\n                 ORDER BY j.execution_finish DESC\n             ) AS row_number\n        FROM _timescaledb_internal.bgw_job_stat_history j\n       WHERE id >= id_found)\n  SELECT id, e.job_id, pid, execution_start, execution_finish, succeeded, data\n    FROM enumerated e\n   WHERE succeeded AND row_number <= (config->>'max_successes_per_job')::int\n      OR NOT succeeded AND row_number <= (config->>'max_failures_per_job')::int\n  ORDER BY id;\n\n  TRUNCATE _timescaledb_internal.bgw_job_stat_history;\n\n  INSERT INTO _timescaledb_internal.bgw_job_stat_history\n  SELECT * FROM __tmp_bgw_job_stat_history;\n\nEND\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.policy_job_stat_history_retention_check(config JSONB) RETURNS VOID\nLANGUAGE PLPGSQL AS\n$BODY$\nBEGIN\n    IF config IS NULL THEN\n        RAISE EXCEPTION 'config cannot be NULL, and must contain drop_after';\n    END IF;\n\n    IF NOT (config ? 'drop_after') THEN\n        RAISE EXCEPTION 'drop_after interval not provided';\n    END IF;\n\n    IF NOT (config ? 'max_successes_per_job') THEN\n        RAISE EXCEPTION 'max_successes_per_job not provided';\n    END IF;\n\n    IF NOT (config ? 'max_failures_per_job') THEN\n        RAISE EXCEPTION 'max_failures_per_job not provided';\n    END IF;\n\n    IF (config->>'max_successes_per_job')::integer < 10 THEN\n        RAISE EXCEPTION 'max_successes_per_job has to be at least 10';\n    END IF;\n\n    IF (config->>'max_failures_per_job')::integer < 10 THEN\n        RAISE EXCEPTION 'max_failures_per_job has to be at least 10';\n    END IF;\nEND\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\nINSERT INTO _timescaledb_catalog.bgw_job (\n    id,\n    application_name,\n    schedule_interval,\n    max_runtime,\n    max_retries,\n    retry_period,\n    proc_schema,\n    proc_name,\n    owner,\n    scheduled,\n    config,\n    check_schema,\n    check_name,\n    fixed_schedule,\n    initial_start\n)\nVALUES\n(\n    3,\n    'Job History Log Retention Policy [3]',\n    INTERVAL '6 hours',\n    INTERVAL '1 hour',\n    -1,\n    INTERVAL '1h',\n    '_timescaledb_functions',\n    'policy_job_stat_history_retention',\n    pg_catalog.quote_ident(current_role)::regrole,\n    true,\n    '{\"drop_after\":\"1 month\",\"max_successes_per_job\":1000,\"max_failures_per_job\":1000}',\n    '_timescaledb_functions',\n    'policy_job_stat_history_retention_check',\n    true,\n    '2000-01-01 00:00:00+00'::timestamptz\n) ON CONFLICT (id) DO NOTHING;\n"
  },
  {
    "path": "sql/maintenance_utils.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- chunk - the OID of the chunk to be CLUSTERed\n-- index - the OID of the index to be CLUSTERed on, or NULL to use the index\n--         last used\nCREATE OR REPLACE FUNCTION @extschema@.reorder_chunk(\n    chunk REGCLASS,\n    index REGCLASS=NULL,\n    verbose BOOLEAN=FALSE\n) RETURNS VOID AS '@MODULE_PATHNAME@', 'ts_reorder_chunk' LANGUAGE C VOLATILE;\n\nCREATE OR REPLACE FUNCTION @extschema@.move_chunk(\n    chunk REGCLASS,\n    destination_tablespace Name,\n    index_destination_tablespace Name=NULL,\n    reorder_index REGCLASS=NULL,\n    verbose BOOLEAN=FALSE\n) RETURNS VOID AS '@MODULE_PATHNAME@', 'ts_move_chunk' LANGUAGE C VOLATILE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.create_compressed_chunk(\n    chunk REGCLASS,\n    chunk_table REGCLASS,\n    uncompressed_heap_size BIGINT,\n    uncompressed_toast_size BIGINT,\n    uncompressed_index_size BIGINT,\n    compressed_heap_size BIGINT,\n    compressed_toast_size BIGINT,\n    compressed_index_size BIGINT,\n    numrows_pre_compression BIGINT,\n    numrows_post_compression BIGINT\n) RETURNS REGCLASS AS '@MODULE_PATHNAME@', 'ts_create_compressed_chunk' LANGUAGE C STRICT VOLATILE;\n\nCREATE OR REPLACE FUNCTION @extschema@.compress_chunk(\n    uncompressed_chunk REGCLASS,\n    if_not_compressed BOOLEAN = true,\n    recompress BOOLEAN = false\n) RETURNS REGCLASS AS '@MODULE_PATHNAME@', 'ts_compress_chunk' LANGUAGE C VOLATILE;\n\n-- Alias for compress_chunk above.\nCREATE OR REPLACE PROCEDURE @extschema@.convert_to_columnstore(\n    chunk REGCLASS,\n    if_not_columnstore BOOLEAN = true,\n    recompress BOOLEAN = false\n) AS '@MODULE_PATHNAME@', 'ts_compress_chunk' LANGUAGE C;\n\nCREATE OR REPLACE FUNCTION @extschema@.decompress_chunk(\n    uncompressed_chunk REGCLASS,\n    if_compressed BOOLEAN = true\n) RETURNS REGCLASS AS '@MODULE_PATHNAME@', 'ts_decompress_chunk' LANGUAGE C STRICT VOLATILE;\n\nCREATE OR REPLACE PROCEDURE @extschema@.convert_to_rowstore(\n    chunk REGCLASS,\n    if_columnstore BOOLEAN = true\n) AS '@MODULE_PATHNAME@', 'ts_decompress_chunk' LANGUAGE C;\n\nCREATE OR REPLACE PROCEDURE _timescaledb_functions.rebuild_columnstore(\n    chunk REGCLASS\n) AS '@MODULE_PATHNAME@', 'ts_rebuild_columnstore' LANGUAGE C;\n\nCREATE OR REPLACE PROCEDURE _timescaledb_functions.chunk_rewrite_cleanup()\nLANGUAGE C AS '@MODULE_PATHNAME@', 'ts_chunk_rewrite_cleanup';\n\nCREATE OR REPLACE PROCEDURE @extschema@.merge_chunks(\n   chunk1 REGCLASS, chunk2 REGCLASS, concurrently BOOLEAN = false\n) LANGUAGE C AS '@MODULE_PATHNAME@', 'ts_merge_two_chunks';\n\nCREATE OR REPLACE PROCEDURE @extschema@.merge_chunks(\n    chunks REGCLASS[]\n) LANGUAGE C AS '@MODULE_PATHNAME@', 'ts_merge_chunks';\n\nCREATE OR REPLACE PROCEDURE @extschema@.merge_chunks_concurrently(\n    chunks REGCLASS[]\n) LANGUAGE C AS '@MODULE_PATHNAME@', 'ts_merge_chunks_concurrently';\n\nCREATE OR REPLACE PROCEDURE @extschema@.split_chunk(\n    chunk REGCLASS,\n    split_at \"any\" = NULL\n) LANGUAGE C AS '@MODULE_PATHNAME@', 'ts_split_chunk';\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.recompress_chunk_segmentwise(\n    uncompressed_chunk REGCLASS,\n    if_compressed BOOLEAN = true\n) RETURNS REGCLASS AS '@MODULE_PATHNAME@', 'ts_recompress_chunk_segmentwise' LANGUAGE C STRICT VOLATILE;\n\n-- find the index on the compressed chunk that can be used to recompress efficiently\n-- this index must contain all the segmentby columns and the meta_sequence_number column last\nCREATE OR REPLACE FUNCTION _timescaledb_functions.get_compressed_chunk_index_for_recompression(\n    uncompressed_chunk REGCLASS\n) RETURNS REGCLASS AS '@MODULE_PATHNAME@', 'ts_get_compressed_chunk_index_for_recompression' LANGUAGE C STRICT VOLATILE;\n-- Recompress a chunk\n--\n-- Will give an error if the chunk was not already compressed. In this\n-- case, the user should use compress_chunk instead. Note that this\n-- function cannot be executed in an explicit transaction since it\n-- contains transaction control commands.\n--\n-- Parameters:\n--   chunk: Chunk to recompress.\n--   if_not_compressed: Print notice instead of error if chunk is already compressed.\n\nCREATE OR REPLACE PROCEDURE @extschema@.recompress_chunk(chunk REGCLASS, if_not_compressed BOOLEAN = true) LANGUAGE PLPGSQL AS $$\nBEGIN\n  IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN\n    RAISE WARNING 'procedure @extschema@.recompress_chunk(regclass,boolean) is deprecated and the functionality is now included in @extschema@.compress_chunk. this compatibility function will be removed in a future version.';\n  END IF;\n  PERFORM @extschema@.compress_chunk(chunk, if_not_compressed);\nEND$$ SET search_path TO pg_catalog,pg_temp;\n\n-- A version of makeaclitem that accepts a comma-separated list of\n-- privileges rather than just a single privilege. This is copied from\n-- PG16, but since we need to support earlier versions, we provide it\n-- with the extension.\n--\n-- This is intended for internal usage and interface might change.\nCREATE OR REPLACE FUNCTION _timescaledb_functions.makeaclitem(regrole, regrole, text, bool)\nRETURNS AclItem AS '@MODULE_PATHNAME@', 'ts_makeaclitem'\nLANGUAGE C STABLE PARALLEL SAFE STRICT;\n\n-- Repair relation ACL by removing roles that do not exist in pg_authid.\nCREATE OR REPLACE PROCEDURE _timescaledb_functions.repair_relation_acls()\nLANGUAGE SQL AS $$\n  WITH\n    badrels AS (\n\tSELECT oid::regclass\n\t  FROM (SELECT oid, (aclexplode(relacl)).* FROM pg_class) AS rels\n\t WHERE rels.grantee != 0\n\t   AND rels.grantee NOT IN (SELECT oid FROM pg_authid)\n    ),\n    pickacls AS (\n      SELECT oid::regclass,\n\t     _timescaledb_functions.makeaclitem(\n\t         b.grantee,\n\t\t b.grantor,\n\t\t string_agg(b.privilege_type, ','),\n\t\t b.is_grantable\n\t     ) AS acl\n\tFROM (SELECT oid, (aclexplode(relacl)).* AS a FROM pg_class) AS b\n       WHERE b.grantee IN (SELECT oid FROM pg_authid)\n       GROUP BY oid, b.grantee, b.grantor, b.is_grantable\n    ),\n    cleanacls AS (\n      SELECT oid, array_agg(acl) AS acl FROM pickacls GROUP BY oid\n    )\n  UPDATE pg_class c\n     SET relacl = (SELECT acl FROM cleanacls n WHERE c.oid = n.oid)\n   WHERE oid IN (SELECT oid FROM badrels)\n$$ SET search_path TO pg_catalog, pg_temp;\n\n-- Remove chunk metadata when marked as dropped\nCREATE OR REPLACE FUNCTION _timescaledb_functions.remove_dropped_chunk_metadata(_hypertable_id INTEGER)\nRETURNS INTEGER LANGUAGE plpgsql AS $$\nDECLARE\n  _chunk_id INTEGER;\n  _removed INTEGER := 0;\nBEGIN\n  FOR _chunk_id IN\n    SELECT id FROM _timescaledb_catalog.chunk\n    WHERE hypertable_id = _hypertable_id\n    AND NOT EXISTS (\n        SELECT FROM information_schema.tables\n        WHERE tables.table_schema = chunk.schema_name\n        AND tables.table_name = chunk.table_name\n    )\n  LOOP\n    _removed := _removed + 1;\n    RAISE INFO 'Removing metadata of chunk % from hypertable %', _chunk_id, _hypertable_id;\n\n    WITH _dimension_slice_remove AS (\n        DELETE FROM _timescaledb_catalog.dimension_slice\n        USING _timescaledb_catalog.chunk_constraint\n        WHERE dimension_slice.id = chunk_constraint.dimension_slice_id\n        AND chunk_constraint.chunk_id = _chunk_id\n        AND NOT EXISTS (\n            SELECT FROM _timescaledb_catalog.chunk_constraint cc\n            WHERE cc.chunk_id <> _chunk_id\n            AND cc.dimension_slice_id = dimension_slice.id\n        )\n        RETURNING _timescaledb_catalog.dimension_slice.id\n    )\n    DELETE FROM _timescaledb_catalog.chunk_constraint\n    USING _dimension_slice_remove\n    WHERE chunk_constraint.dimension_slice_id = _dimension_slice_remove.id;\n\n    DELETE FROM _timescaledb_catalog.chunk_constraint\n    WHERE chunk_constraint.chunk_id = _chunk_id;\n\n    DELETE FROM _timescaledb_internal.bgw_policy_chunk_stats\n    WHERE bgw_policy_chunk_stats.chunk_id = _chunk_id;\n\n    DELETE FROM _timescaledb_catalog.compression_chunk_size\n    WHERE compression_chunk_size.chunk_id = _chunk_id\n    OR compression_chunk_size.compressed_chunk_id = _chunk_id;\n\n    DELETE FROM _timescaledb_catalog.chunk\n    WHERE chunk.id = _chunk_id\n    OR chunk.compressed_chunk_id = _chunk_id;\n  END LOOP;\n\n  RETURN _removed;\nEND;\n$$ SET search_path TO pg_catalog, pg_temp;\n"
  },
  {
    "path": "sql/metadata.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.generate_uuid() RETURNS UUID\nAS '@MODULE_PATHNAME@', 'ts_uuid_generate' LANGUAGE C VOLATILE STRICT;\n\n-- Trigger to change INSERT into UPDATE if key already exists.\n--\n-- During extension installation we create 3 entries in the metadata table which are\n-- included in dumps. To allow loading logical dumps we need this trigger to turn INSERTs\n-- into UPDATEs if the key already exists.\nCREATE OR REPLACE FUNCTION _timescaledb_functions.metadata_insert_trigger() RETURNS TRIGGER LANGUAGE PLPGSQL\nAS $$\nBEGIN\n  IF EXISTS (SELECT FROM _timescaledb_catalog.metadata WHERE key = NEW.key) THEN\n    UPDATE _timescaledb_catalog.metadata SET value = NEW.value WHERE key = NEW.key;\n    RETURN NULL;\n  END IF;\n  RETURN NEW;\nEND\n$$ SET search_path TO pg_catalog, pg_temp;\n\nCREATE OR REPLACE TRIGGER metadata_insert_trigger BEFORE INSERT ON _timescaledb_catalog.metadata FOR EACH ROW EXECUTE PROCEDURE _timescaledb_functions.metadata_insert_trigger();\n\n-- Insert uuid and install_timestamp on database creation since the trigger\n-- will turn these into UPDATEs on conflicts we can't use ON CONFLICT DO NOTHING.\nDO $$\nBEGIN\n  IF (NOT EXISTS (SELECT FROM _timescaledb_catalog.metadata WHERE key = 'uuid')) THEN\n    INSERT INTO _timescaledb_catalog.metadata SELECT 'uuid', _timescaledb_functions.generate_uuid(), TRUE;\n  END IF;\n  IF (NOT EXISTS (SELECT FROM _timescaledb_catalog.metadata WHERE key = 'install_timestamp')) THEN\n    INSERT INTO _timescaledb_catalog.metadata SELECT 'install_timestamp', now(), TRUE;\n  END IF;\nEND\n$$;\n\n-- Install catalog version on database installation and upgrade.\n-- This allows us to detect catalog mismatches in dump/restore cycle.\nINSERT INTO _timescaledb_catalog.metadata (key, value, include_in_telemetry) SELECT 'timescaledb_version', '@PROJECT_VERSION_MOD@', FALSE;\n\n"
  },
  {
    "path": "sql/notice.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nDO language plpgsql $$\nDECLARE\n  telemetry_string TEXT;\n  telemetry_level text;\nBEGIN\n  telemetry_level := current_setting('timescaledb.telemetry_level', true);\n  CASE telemetry_level\n  WHEN 'off' THEN\n    telemetry_string = E'Note: Please enable telemetry to help us improve our product by running: ALTER DATABASE \"' || current_database() || E'\" SET timescaledb.telemetry_level = ''basic'';';\n  WHEN 'basic' THEN\n    telemetry_string = E'Note: TimescaleDB collects anonymous reports to better understand and assist our users.\\nFor more information and how to disable, please see our docs https://docs.timescale.com/timescaledb/latest/how-to-guides/configuration/telemetry.';\n  ELSE\n    telemetry_string = E'';\n  END CASE;\n\n  RAISE NOTICE E'%\\n%\\n',\n    E'\\nWELCOME TO\\n' ||\n    E' _____ _                               _     ____________  \\n' ||\n    E'|_   _(_)                             | |    |  _  \\\\ ___ \\\\ \\n' ||\n    E'  | |  _ _ __ ___   ___  ___  ___ __ _| | ___| | | | |_/ / \\n' ||\n    '  | | | |  _ ` _ \\ / _ \\/ __|/ __/ _` | |/ _ \\ | | | ___ \\ ' || E'\\n' ||\n    '  | | | | | | | | |  __/\\__ \\ (_| (_| | |  __/ |/ /| |_/ /' || E'\\n' ||\n    '  |_| |_|_| |_| |_|\\___||___/\\___\\__,_|_|\\___|___/ \\____/' || E'\\n' ||\n    E'               Running version ' || '@PROJECT_VERSION_MOD@' || E'\\n' ||\n\n    E'For more information on TimescaleDB, please visit the following links:\\n\\n'\n    ||\n    E' 1. Getting started: https://docs.timescale.com/timescaledb/latest/getting-started\\n' ||\n    E' 2. API reference documentation: https://docs.timescale.com/api/latest\\n',\n    telemetry_string;\nEND;\n$$;\n"
  },
  {
    "path": "sql/osm_api.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- This function updates the dimension slice range stored in the catalog with the min and max\n-- values that the OSM chunk contains. Since there is only one OSM chunk per hypertable with\n-- only a time dimension, the hypertable is used to determine the corresponding slice\nCREATE OR REPLACE FUNCTION _timescaledb_functions.hypertable_osm_range_update(\n    hypertable REGCLASS,\n    range_start ANYELEMENT = NULL::bigint,\n    range_end ANYELEMENT = NULL,\n    empty BOOL = false\n) RETURNS BOOL AS '@MODULE_PATHNAME@',\n'ts_hypertable_osm_range_update' LANGUAGE C VOLATILE;\n"
  },
  {
    "path": "sql/partitioning.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Deprecated partition hash function\nCREATE OR REPLACE FUNCTION _timescaledb_functions.get_partition_for_key(val anyelement)\n    RETURNS int\n    AS '@MODULE_PATHNAME@', 'ts_get_partition_for_key' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.get_partition_hash(val anyelement)\n    RETURNS int\n    AS '@MODULE_PATHNAME@', 'ts_get_partition_hash' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n"
  },
  {
    "path": "sql/policy_api.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Add a retention policy to a hypertable or continuous aggregate.\n-- The retention_window (typically an INTERVAL) determines the\n-- window beyond which data is dropped at the time\n-- of execution of the policy (e.g., '1 week'). Note that the retention\n-- window will always align with chunk boundaries, thus the window\n-- might be larger than the given one, but never smaller. In other\n-- words, some data beyond the retention window\n-- might be kept, but data within the window will never be deleted.\nCREATE OR REPLACE FUNCTION @extschema@.add_retention_policy(\n       relation REGCLASS,\n       drop_after \"any\" = NULL,\n       if_not_exists BOOL = false,\n       schedule_interval INTERVAL = NULL,\n       initial_start TIMESTAMPTZ = NULL,\n       timezone TEXT = NULL,\n       drop_created_before INTERVAL = NULL\n)\nRETURNS INTEGER AS '@MODULE_PATHNAME@', 'ts_policy_retention_add'\nLANGUAGE C VOLATILE;\n\nCREATE OR REPLACE FUNCTION @extschema@.remove_retention_policy(\n    relation REGCLASS,\n    if_exists BOOL = false\n) RETURNS VOID\nAS '@MODULE_PATHNAME@', 'ts_policy_retention_remove'\nLANGUAGE C VOLATILE STRICT;\n\n/* reorder policy */\nCREATE OR REPLACE FUNCTION @extschema@.add_reorder_policy(\n    hypertable REGCLASS,\n    index_name NAME,\n    if_not_exists BOOL = false,\n    initial_start timestamptz = NULL,\n    timezone TEXT = NULL\n) RETURNS INTEGER\nAS '@MODULE_PATHNAME@', 'ts_policy_reorder_add'\nLANGUAGE C VOLATILE;\n\nCREATE OR REPLACE FUNCTION @extschema@.remove_reorder_policy(hypertable REGCLASS, if_exists BOOL = false) RETURNS VOID\nAS '@MODULE_PATHNAME@', 'ts_policy_reorder_remove'\nLANGUAGE C VOLATILE STRICT;\n\n/* compression policy */\nCREATE OR REPLACE FUNCTION @extschema@.add_compression_policy(\n    hypertable REGCLASS,\n    compress_after \"any\" = NULL,\n    if_not_exists BOOL = false,\n    schedule_interval INTERVAL = NULL,\n    initial_start TIMESTAMPTZ = NULL,\n    timezone TEXT = NULL,\n    compress_created_before INTERVAL = NULL\n)\nRETURNS INTEGER\nAS '@MODULE_PATHNAME@', 'ts_policy_compression_add'\nLANGUAGE C VOLATILE; -- not strict because we need to set different default values for schedule_interval\n\nCREATE OR REPLACE PROCEDURE @extschema@.add_columnstore_policy(\n    hypertable REGCLASS,\n    after \"any\" = NULL,\n    if_not_exists BOOL = false,\n    schedule_interval INTERVAL = NULL,\n    initial_start TIMESTAMPTZ = NULL,\n    timezone TEXT = NULL,\n    created_before INTERVAL = NULL\n) LANGUAGE C AS '@MODULE_PATHNAME@', 'ts_policy_compression_add';\n\nCREATE OR REPLACE FUNCTION @extschema@.remove_compression_policy(hypertable REGCLASS, if_exists BOOL = false) RETURNS BOOL\nAS '@MODULE_PATHNAME@', 'ts_policy_compression_remove'\nLANGUAGE C VOLATILE STRICT;\n\nCREATE OR REPLACE PROCEDURE @extschema@.remove_columnstore_policy(\n       hypertable REGCLASS,\n       if_exists BOOL = false\n) LANGUAGE C AS '@MODULE_PATHNAME@', 'ts_policy_compression_remove';\n\n/* continuous aggregates policy */\nCREATE OR REPLACE FUNCTION @extschema@.add_continuous_aggregate_policy(\n    continuous_aggregate REGCLASS,\n    start_offset \"any\",\n    end_offset \"any\",\n    schedule_interval INTERVAL,\n    if_not_exists BOOL = false,\n    initial_start TIMESTAMPTZ = NULL,\n    timezone TEXT = NULL,\n    include_tiered_data BOOL = NULL,\n    buckets_per_batch INTEGER = NULL,\n    max_batches_per_execution INTEGER = NULL,\n    refresh_newest_first BOOL = NULL\n)\nRETURNS INTEGER\nAS '@MODULE_PATHNAME@', 'ts_policy_refresh_cagg_add'\nLANGUAGE C VOLATILE;\n\nCREATE OR REPLACE FUNCTION @extschema@.remove_continuous_aggregate_policy(\n    continuous_aggregate REGCLASS,\n    if_not_exists BOOL = false, -- deprecating this argument, if_exists overrides it\n    if_exists BOOL = NULL) -- when NULL get the value from if_not_exists\n\nRETURNS VOID\nAS '@MODULE_PATHNAME@', 'ts_policy_refresh_cagg_remove'\nLANGUAGE C VOLATILE;\n\nCREATE OR REPLACE PROCEDURE @extschema@.add_process_hypertable_invalidations_policy(\n    hypertable REGCLASS,\n    schedule_interval INTERVAL,\n    if_not_exists BOOL = false,\n    initial_start TIMESTAMPTZ = NULL,\n    timezone TEXT = NULL\n) LANGUAGE C AS '@MODULE_PATHNAME@', 'ts_policy_process_hyper_inval_add';\n\nCREATE OR REPLACE PROCEDURE @extschema@.remove_process_hypertable_invalidations_policy(\n       hypertable REGCLASS,\n       if_exists BOOL = false\n) LANGUAGE C AS '@MODULE_PATHNAME@', 'ts_policy_process_hyper_inval_remove';\n\n/* 1 step policies */\n\n/* Add policies */\n/* Unsupported drop_created_before/compress_created_before in add/alter for caggs */\nCREATE OR REPLACE FUNCTION timescaledb_experimental.add_policies(\n    relation REGCLASS,\n    if_not_exists BOOL = false,\n    refresh_start_offset \"any\" = NULL,\n    refresh_end_offset \"any\" = NULL,\n    compress_after \"any\" = NULL,\n    drop_after \"any\" = NULL\n) RETURNS BOOL AS '@MODULE_PATHNAME@', 'ts_policies_add' LANGUAGE C VOLATILE;\n\n/* Remove policies */\nCREATE OR REPLACE FUNCTION timescaledb_experimental.remove_policies(\n    relation REGCLASS,\n    if_exists BOOL = false,\n    VARIADIC policy_names TEXT[] = NULL)\nRETURNS BOOL\nAS '@MODULE_PATHNAME@', 'ts_policies_remove'\nLANGUAGE C VOLATILE;\n\n/* Remove all policies */\nCREATE OR REPLACE FUNCTION timescaledb_experimental.remove_all_policies(\n    relation REGCLASS,\n    if_exists BOOL = false)\nRETURNS BOOL\nAS '@MODULE_PATHNAME@', 'ts_policies_remove_all'\nLANGUAGE C VOLATILE;\n\n/* Alter policies */\nCREATE OR REPLACE FUNCTION timescaledb_experimental.alter_policies(\n    relation REGCLASS,\n    if_exists BOOL = false,\n    refresh_start_offset \"any\" = NULL,\n    refresh_end_offset \"any\" = NULL,\n    compress_after \"any\" = NULL,\n    drop_after \"any\" = NULL)\nRETURNS BOOL\nAS '@MODULE_PATHNAME@', 'ts_policies_alter'\nLANGUAGE C VOLATILE;\n\n/* Show policies info */\nCREATE OR REPLACE FUNCTION timescaledb_experimental.show_policies(\n    relation REGCLASS)\nRETURNS SETOF JSONB\nAS '@MODULE_PATHNAME@', 'ts_policies_show'\nLANGUAGE C  VOLATILE;\n"
  },
  {
    "path": "sql/policy_internal.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE OR REPLACE PROCEDURE _timescaledb_functions.policy_retention(job_id INTEGER, config JSONB)\nAS '@MODULE_PATHNAME@', 'ts_policy_retention_proc'\nLANGUAGE C;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.policy_retention_check(config JSONB)\nRETURNS void AS '@MODULE_PATHNAME@', 'ts_policy_retention_check'\nLANGUAGE C;\n\nCREATE OR REPLACE PROCEDURE _timescaledb_functions.policy_reorder(job_id INTEGER, config JSONB)\nAS '@MODULE_PATHNAME@', 'ts_policy_reorder_proc'\nLANGUAGE C;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.policy_reorder_check(config JSONB)\nRETURNS void AS '@MODULE_PATHNAME@', 'ts_policy_reorder_check'\nLANGUAGE C;\n\nCREATE OR REPLACE PROCEDURE _timescaledb_functions.policy_recompression(job_id INTEGER, config JSONB)\nAS '@MODULE_PATHNAME@', 'ts_policy_recompression_proc'\nLANGUAGE C;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.policy_compression_check(config JSONB)\nRETURNS void AS '@MODULE_PATHNAME@', 'ts_policy_compression_check'\nLANGUAGE C;\n\nCREATE OR REPLACE PROCEDURE _timescaledb_functions.policy_refresh_continuous_aggregate(job_id INTEGER, config JSONB)\nAS '@MODULE_PATHNAME@', 'ts_policy_refresh_cagg_proc'\nLANGUAGE C;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.policy_refresh_continuous_aggregate_check(config JSONB)\nRETURNS void AS '@MODULE_PATHNAME@', 'ts_policy_refresh_cagg_check'\nLANGUAGE C;\n\nCREATE OR REPLACE PROCEDURE _timescaledb_functions.policy_process_hypertable_invalidations(job_id INTEGER, config JSONB)\nAS '@MODULE_PATHNAME@', 'ts_policy_process_hyper_inval_proc'\nLANGUAGE C;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.policy_process_hypertable_invalidations_check(config JSONB)\nRETURNS void AS '@MODULE_PATHNAME@', 'ts_policy_process_hyper_inval_check'\nLANGUAGE C;\n\nCREATE OR REPLACE PROCEDURE\n_timescaledb_functions.policy_compression_execute(\n  job_id              INTEGER,\n  htid                INTEGER,\n  lag                 ANYELEMENT,\n  maxchunks           INTEGER,\n  verbose_log         BOOLEAN,\n  recompress_enabled  BOOLEAN,\n  reindex_enabled     BOOLEAN,\n  use_creation_time   BOOLEAN\n)\nAS $$\nDECLARE\n  htoid       REGCLASS;\n  chunk_rec   RECORD;\n  idx_rec     RECORD;\n  numchunks_compressed   INTEGER := 0;\n  _message     text;\n  _detail      text;\n  _sqlstate    text;\n  -- fully compressed chunk status\n  status_fully_compressed int := 1;\n  -- chunk status bits:\n  bit_compressed int := 1;\n  bit_compressed_unordered int := 2;\n  bit_frozen int := 4;\n  bit_compressed_partial int := 8;\n  creation_lag INTERVAL := NULL;\n  chunks_failure INTEGER := 0;\nBEGIN\n\n  -- procedures with SET clause cannot execute transaction\n  -- control so we adjust search_path in procedure body\n  SET LOCAL search_path TO pg_catalog, pg_temp;\n\n  SELECT format('%I.%I', schema_name, table_name) INTO htoid\n  FROM _timescaledb_catalog.hypertable\n  WHERE id = htid;\n\n  -- for the integer cases, we have to compute the lag w.r.t\n  -- the integer_now function and then pass on to show_chunks\n  IF pg_typeof(lag) IN ('BIGINT'::regtype, 'INTEGER'::regtype, 'SMALLINT'::regtype) THEN\n    -- cannot have use_creation_time set with this\n    IF use_creation_time IS TRUE THEN\n        RAISE EXCEPTION 'job % cannot use creation time with integer_now function', job_id;\n    END IF;\n    lag := _timescaledb_functions.subtract_integer_from_now(htoid, lag::BIGINT);\n  END IF;\n\n  -- if use_creation_time has been specified then the lag needs to be used with the\n  -- \"compress_created_before\" argument. Otherwise the usual \"older_than\" argument\n  -- is good enough\n  IF use_creation_time IS TRUE THEN\n    creation_lag := lag;\n    lag := NULL;\n  END IF;\n\n  FOR chunk_rec IN\n    SELECT\n      show.oid, ch.schema_name, ch.table_name, ch.status\n    FROM\n      @extschema@.show_chunks(htoid, older_than => lag, created_before => creation_lag) AS show(oid)\n      INNER JOIN pg_class pgc ON pgc.oid = show.oid\n      INNER JOIN pg_namespace pgns ON pgc.relnamespace = pgns.oid\n      INNER JOIN _timescaledb_catalog.chunk ch ON ch.table_name = pgc.relname AND ch.schema_name = pgns.nspname AND ch.hypertable_id = htid\n    WHERE NOT ch.osm_chunk\n    -- Checking for chunks which are not fully compressed and not frozen\n    AND ch.status != status_fully_compressed\n    AND ch.status & bit_frozen = 0\n  LOOP\n    BEGIN\n      IF chunk_rec.status = bit_compressed OR recompress_enabled IS TRUE THEN\n        PERFORM @extschema@.compress_chunk(chunk_rec.oid);\n        numchunks_compressed := numchunks_compressed + 1;\n      END IF;\n    EXCEPTION WHEN OTHERS THEN\n      GET STACKED DIAGNOSTICS\n          _message = MESSAGE_TEXT,\n          _detail = PG_EXCEPTION_DETAIL,\n          _sqlstate = RETURNED_SQLSTATE;\n      RAISE WARNING 'converting chunk \"%\" to columnstore failed when recompress columnstore policy is executed', chunk_rec.oid::regclass::text\n          USING DETAIL = format('Message: (%s), Detail: (%s).', _message, _detail),\n                ERRCODE = _sqlstate;\n      chunks_failure := chunks_failure + 1;\n    END;\n    COMMIT;\n\n    -- went through recompression successfully now reindex indexes\n    IF (chunk_rec.status & bit_compressed_partial = bit_compressed_partial) AND (reindex_enabled IS TRUE) THEN\n      FOR idx_rec IN\n        SELECT idx.schemaname, idx.indexname\n        FROM pg_indexes idx\n        JOIN _timescaledb_catalog.chunk ch ON ch.schema_name = idx.schemaname AND ch.table_name = idx.tablename\n        WHERE idx.schemaname = chunk_rec.schema_name\n          AND idx.tablename = chunk_rec.table_name\n          AND ch.status = status_fully_compressed\n      LOOP\n        BEGIN\n          EXECUTE format('REINDEX INDEX %I.%I;', idx_rec.schemaname, idx_rec.indexname);\n        EXCEPTION WHEN OTHERS THEN\n          GET STACKED DIAGNOSTICS\n              _message = MESSAGE_TEXT,\n              _detail = PG_EXCEPTION_DETAIL,\n              _sqlstate = RETURNED_SQLSTATE;\n          RAISE WARNING 'reindexing index \"%.%\" for chunk \"%\" to columnstore failed when columnstore policy is executed', idx_rec.schemaname, idx_rec.indexname, chunk_rec.oid::regclass::text\n              USING DETAIL = format('Message: (%s), Detail: (%s).', _message, _detail),\n                    ERRCODE = _sqlstate;\n          chunks_failure := chunks_failure + 1;\n        END;\n        COMMIT;\n      END LOOP;\n    END IF;\n\n    -- SET LOCAL is only active until end of transaction.\n    -- While we could use SET at the start of the function we do not\n    -- want to bleed out search_path to caller, so we do SET LOCAL\n    -- again after COMMIT\n    SET LOCAL search_path TO pg_catalog, pg_temp;\n    IF verbose_log THEN\n       RAISE LOG 'job % completed processing chunk %.%', job_id, chunk_rec.schema_name, chunk_rec.table_name;\n    END IF;\n    IF maxchunks > 0 AND numchunks_compressed >= maxchunks THEN\n         EXIT;\n    END IF;\n  END LOOP;\n\n  IF chunks_failure > 0 THEN\n    RAISE EXCEPTION 'columnstore policy failure'\n      USING\n        DETAIL = format('Failed to convert %L chunks to columnstore. Successfully converted %L chunks.', chunks_failure, numchunks_compressed),\n        ERRCODE = 'data_exception';\n  END IF;\nEND;\n$$ LANGUAGE PLPGSQL;\n\nCREATE OR REPLACE PROCEDURE\n_timescaledb_functions.policy_compression(job_id INTEGER, config JSONB)\nAS $$\nDECLARE\n  dimtype             REGTYPE;\n  dimtypeinput        REGPROC;\n  compress_after      TEXT;\n  compress_created_before TEXT;\n  lag_value           TEXT;\n  lag_bigint_value    BIGINT;\n  htid                INTEGER;\n  htoid               REGCLASS;\n  chunk_rec           RECORD;\n  verbose_log         BOOL;\n  maxchunks           INTEGER := 0;\n  numchunks           INTEGER := 1;\n  recompress_enabled  BOOL;\n  reindex_enabled     BOOL;\n  use_creation_time   BOOL := FALSE;\nBEGIN\n\n  -- procedures with SET clause cannot execute transaction\n  -- control so we adjust search_path in procedure body\n  SET LOCAL search_path TO pg_catalog, pg_temp;\n\n  IF config IS NULL THEN\n    RAISE EXCEPTION 'job % has null config', job_id;\n  END IF;\n\n  htid := jsonb_object_field_text(config, 'hypertable_id')::INTEGER;\n  IF htid is NULL THEN\n    RAISE EXCEPTION 'job % config must have hypertable_id', job_id;\n  END IF;\n\n  verbose_log         := COALESCE(jsonb_object_field_text(config, 'verbose_log')::BOOLEAN, FALSE);\n  maxchunks           := COALESCE(jsonb_object_field_text(config, 'maxchunks_to_compress')::INTEGER, 0);\n  recompress_enabled  := COALESCE(jsonb_object_field_text(config, 'recompress')::BOOLEAN, TRUE);\n  reindex_enabled     := COALESCE(jsonb_object_field_text(config, 'reindex')::BOOLEAN, TRUE);\n\n  -- find primary dimension type --\n  SELECT dim.column_type INTO dimtype\n  FROM  _timescaledb_catalog.hypertable ht\n        JOIN _timescaledb_catalog.dimension dim ON ht.id = dim.hypertable_id\n  WHERE ht.id = htid\n  ORDER BY dim.id\n  LIMIT 1;\n\n  compress_after      := jsonb_object_field_text(config, 'compress_after');\n  IF compress_after IS NULL THEN\n    compress_created_before := jsonb_object_field_text(config, 'compress_created_before');\n    IF compress_created_before IS NULL THEN\n        RAISE EXCEPTION 'job % config must have compress_after or compress_created_before', job_id;\n    END IF;\n    lag_value := compress_created_before;\n    use_creation_time := true;\n    dimtype := 'INTERVAL' ::regtype;\n  ELSE\n    lag_value := compress_after;\n  END IF;\n\n  -- execute the properly type casts for the lag value\n  CASE dimtype\n    WHEN 'TIMESTAMP'::regtype, 'TIMESTAMPTZ'::regtype, 'DATE'::regtype, 'INTERVAL' ::regtype, 'UUID'::regtype THEN\n      CALL _timescaledb_functions.policy_compression_execute(job_id, htid, lag_value::INTERVAL, maxchunks, verbose_log, recompress_enabled, reindex_enabled, use_creation_time);\n    WHEN 'BIGINT'::regtype THEN\n      CALL _timescaledb_functions.policy_compression_execute(job_id, htid, lag_value::BIGINT, maxchunks, verbose_log, recompress_enabled, reindex_enabled, use_creation_time);\n    WHEN 'INTEGER'::regtype THEN\n      CALL _timescaledb_functions.policy_compression_execute(job_id, htid, lag_value::INTEGER, maxchunks, verbose_log, recompress_enabled, reindex_enabled, use_creation_time);\n    WHEN 'SMALLINT'::regtype THEN\n      CALL _timescaledb_functions.policy_compression_execute(job_id, htid, lag_value::SMALLINT, maxchunks, verbose_log, recompress_enabled, reindex_enabled, use_creation_time);\n  END CASE;\n  COMMIT;\nEND;\n$$ LANGUAGE PLPGSQL;\n"
  },
  {
    "path": "sql/pre_install/cache.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- This file contains infrastructure for cache invalidation of TimescaleDB\n-- metadata caches kept in C. Please look at cache_invalidate.c for a\n-- description of how this works.\nCREATE TABLE _timescaledb_cache.cache_inval_hypertable();\n\n-- For notifying the scheduler of changes to the bgw_job table.\nCREATE TABLE _timescaledb_cache.cache_inval_bgw_job();\n\n-- This is pretty subtle. We create this dummy cache_inval_extension table\n-- solely for the purpose of getting a relcache invalidation event when it is\n-- deleted on DROP extension. It has no related triggers. When the table is\n-- invalidated, all backends will be notified and will know that they must\n-- invalidate all cached information, including catalog table and index OIDs,\n-- etc.\nCREATE TABLE _timescaledb_cache.cache_inval_extension();\n\nGRANT SELECT ON ALL TABLES IN SCHEMA _timescaledb_cache TO PUBLIC;\n\n"
  },
  {
    "path": "sql/pre_install/insert_data.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n--insert data for compression_algorithm --\ninsert into _timescaledb_catalog.compression_algorithm( id, version, name, description) values\n( 0, 1, 'COMPRESSION_ALGORITHM_NONE', 'no compression'),\n( 1, 1, 'COMPRESSION_ALGORITHM_ARRAY', 'array'),\n( 2, 1, 'COMPRESSION_ALGORITHM_DICTIONARY', 'dictionary'),\n( 3, 1, 'COMPRESSION_ALGORITHM_GORILLA', 'gorilla'),\n( 4, 1, 'COMPRESSION_ALGORITHM_DELTADELTA', 'deltadelta'),\n( 5, 1, 'COMPRESSION_ALGORITHM_BOOL', 'bool'),\n( 6, 1, 'COMPRESSION_ALGORITHM_NULL', 'null'),\n( 7, 1, 'COMPRESSION_ALGORITHM_UUID', 'uuid');\n\n"
  },
  {
    "path": "sql/pre_install/schemas.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nSET LOCAL search_path TO pg_catalog, pg_temp;\n\nCREATE SCHEMA _timescaledb_catalog;\nCREATE SCHEMA _timescaledb_config;\nCREATE SCHEMA _timescaledb_functions;\nCREATE SCHEMA _timescaledb_internal;\nCREATE SCHEMA _timescaledb_cache;\nCREATE SCHEMA timescaledb_experimental;\nCREATE SCHEMA timescaledb_information;\n\nGRANT USAGE ON SCHEMA\n      _timescaledb_cache,\n      _timescaledb_catalog,\n      _timescaledb_config,\n      _timescaledb_functions,\n      _timescaledb_internal,\n      timescaledb_experimental,\n      timescaledb_information\nTO PUBLIC;\n\n"
  },
  {
    "path": "sql/pre_install/tables.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n--NOTICE: UPGRADE-SCRIPT-NEEDED contents in this file are not auto-upgraded.\n-- This file contains table definitions for various abstractions and data\n-- structures for representing hypertables and lower-level concepts.\n-- Hypertable\n-- ==========\n--\n-- The hypertable is an abstraction that represents a table that is\n-- partitioned in N dimensions, where each dimension maps to a column\n-- in the table. A dimension can either be 'open' or 'closed', which\n-- reflects the scheme that divides the dimension's keyspace into\n-- \"slices\".\n--\n-- Conceptually, a partition -- called a \"chunk\", is a hypercube in\n-- the N-dimensional space. A chunk stores a subset of the\n-- hypertable's tuples on disk in its own distinct table. The slices\n-- that span the chunk's hypercube each correspond to a constraint on\n-- the chunk's table, enabling constraint exclusion during queries on\n-- the hypertable's data.\n--\n--\n-- Open dimensions\n------------------\n-- An open dimension does on-demand slicing, creating a new slice\n-- based on a configurable interval whenever a tuple falls outside the\n-- existing slices. Open dimensions fit well with columns that are\n-- incrementally increasing, such as time-based ones.\n--\n-- Closed dimensions\n--------------------\n-- A closed dimension completely divides its keyspace into a\n-- configurable number of slices. The number of slices can be\n-- reconfigured, but the new partitioning only affects newly created\n-- chunks.\n-- The unique constraint is table_name +schema_name. The ordering is\n-- important as we want index access when we filter by table_name\nCREATE SEQUENCE _timescaledb_catalog.hypertable_id_seq MINVALUE 1;\n\nCREATE TABLE _timescaledb_catalog.hypertable (\n  id INTEGER NOT NULL DEFAULT nextval('_timescaledb_catalog.hypertable_id_seq'),\n  schema_name name NOT NULL,\n  table_name name NOT NULL,\n  associated_schema_name name NOT NULL,\n  associated_table_prefix name NOT NULL,\n  num_dimensions smallint NOT NULL,\n  chunk_sizing_func_schema name NOT NULL,\n  chunk_sizing_func_name name NOT NULL,\n  chunk_target_size bigint NOT NULL, -- size in bytes\n  compression_state smallint NOT NULL DEFAULT 0,\n  compressed_hypertable_id integer,\n  status int NOT NULL DEFAULT 0,\n  -- table constraints\n  CONSTRAINT hypertable_pkey PRIMARY KEY (id),\n  CONSTRAINT hypertable_associated_schema_name_associated_table_prefix_key UNIQUE (associated_schema_name, associated_table_prefix),\n  CONSTRAINT hypertable_table_name_schema_name_key UNIQUE (table_name, schema_name),\n  CONSTRAINT hypertable_schema_name_check CHECK (schema_name != '_timescaledb_catalog'),\n  -- internal compressed hypertables have compression state = 2\n  CONSTRAINT hypertable_dim_compress_check CHECK (num_dimensions > 0 OR compression_state = 2),\n  CONSTRAINT hypertable_chunk_target_size_check CHECK (chunk_target_size >= 0),\n  CONSTRAINT hypertable_compress_check CHECK ( (compression_state = 0 OR compression_state = 1 )  OR (compression_state = 2 AND compressed_hypertable_id IS NULL)),\n  CONSTRAINT hypertable_compressed_hypertable_id_fkey FOREIGN KEY (compressed_hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id)\n);\nALTER SEQUENCE _timescaledb_catalog.hypertable_id_seq OWNED BY _timescaledb_catalog.hypertable.id;\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.hypertable_id_seq', '');\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.hypertable', 'WHERE id >= 1');\n\n-- The tablespace table maps tablespaces to hypertables.\n-- This allows spreading a hypertable's chunks across multiple disks.\nCREATE TABLE _timescaledb_catalog.tablespace (\n  id serial NOT NULL,\n  hypertable_id int NOT NULL,\n  tablespace_name name NOT NULL,\n  -- table constraints\n  CONSTRAINT tablespace_pkey PRIMARY KEY (id),\n  CONSTRAINT tablespace_hypertable_id_tablespace_name_key UNIQUE (hypertable_id, tablespace_name),\n  CONSTRAINT tablespace_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE\n);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.tablespace', '');\n\n-- A dimension represents an axis along which data is partitioned.\nCREATE TABLE _timescaledb_catalog.dimension (\n  id serial NOT NULL ,\n  hypertable_id integer NOT NULL,\n  column_name name NOT NULL,\n  column_type REGTYPE NOT NULL,\n  aligned boolean NOT NULL,\n  -- closed dimensions\n  num_slices smallint NULL,\n  partitioning_func_schema name NULL,\n  partitioning_func name NULL,\n  -- open dimensions (e.g., time)\n  interval_length bigint NULL,\n  -- compress interval is used by rollup procedure during compression\n  -- in order to merge multiple chunks into a single one\n  compress_interval_length bigint NULL,\n  integer_now_func_schema name NULL,\n  integer_now_func name NULL,\n  -- table constraints\n  CONSTRAINT dimension_pkey PRIMARY KEY (id),\n  CONSTRAINT dimension_hypertable_id_column_name_key UNIQUE (hypertable_id, column_name),\n  CONSTRAINT dimension_check CHECK ((partitioning_func_schema IS NULL AND partitioning_func IS NULL) OR (partitioning_func_schema IS NOT NULL AND partitioning_func IS NOT NULL)),\n  CONSTRAINT dimension_check1 CHECK ((num_slices IS NULL AND interval_length IS NOT NULL) OR (num_slices IS NOT NULL AND interval_length IS NULL)),\n  CONSTRAINT dimension_check2 CHECK ((integer_now_func_schema IS NULL AND integer_now_func IS NULL) OR (integer_now_func_schema IS NOT NULL AND integer_now_func IS NOT NULL)),\n  CONSTRAINT dimension_interval_length_check CHECK (interval_length IS NULL OR interval_length > 0),\n  CONSTRAINT dimension_compress_interval_length_check CHECK (compress_interval_length IS NULL OR compress_interval_length > 0),\n  CONSTRAINT dimension_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE\n);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.dimension', '');\n\nSELECT pg_catalog.pg_extension_config_dump(pg_get_serial_sequence('_timescaledb_catalog.dimension', 'id'), '');\n\n-- A dimension slice defines a keyspace range along a dimension\n-- axis. A chunk references a slice in each of its dimensions, forming\n-- a hypercube.\nCREATE TABLE _timescaledb_catalog.dimension_slice (\n  id serial NOT NULL,\n  dimension_id integer NOT NULL,\n  range_start bigint NOT NULL,\n  range_end bigint NOT NULL,\n  -- table constraints\n  CONSTRAINT dimension_slice_pkey PRIMARY KEY (id),\n  CONSTRAINT dimension_slice_dimension_id_range_start_range_end_key UNIQUE (dimension_id, range_start, range_end),\n  CONSTRAINT dimension_slice_check CHECK (range_start <= range_end),\n  CONSTRAINT dimension_slice_dimension_id_fkey FOREIGN KEY (dimension_id) REFERENCES _timescaledb_catalog.dimension (id) ON DELETE CASCADE\n);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.dimension_slice', '');\n\nSELECT pg_catalog.pg_extension_config_dump(pg_get_serial_sequence('_timescaledb_catalog.dimension_slice', 'id'), '');\n\n-- A chunk is a partition (hypercube) in an N-dimensional\n-- hyperspace. Each chunk is associated with N constraints that define\n-- the chunk's hypercube. Tuples that fall within the chunk's\n-- hypercube are stored in the chunk's data table, as given by\n-- 'schema_name' and 'table_name'.\nCREATE SEQUENCE _timescaledb_catalog.chunk_id_seq MINVALUE 1;\n\nCREATE TABLE _timescaledb_catalog.chunk (\n  id integer NOT NULL DEFAULT nextval('_timescaledb_catalog.chunk_id_seq'),\n  hypertable_id int NOT NULL,\n  schema_name name NOT NULL,\n  table_name name NOT NULL,\n  compressed_chunk_id integer ,\n  status integer NOT NULL DEFAULT 0,\n  osm_chunk boolean NOT NULL DEFAULT FALSE,\n  creation_time timestamptz NOT NULL,\n  -- table constraints\n  CONSTRAINT chunk_pkey PRIMARY KEY (id),\n  CONSTRAINT chunk_schema_name_table_name_key UNIQUE (schema_name, table_name),\n  CONSTRAINT chunk_compressed_chunk_id_fkey FOREIGN KEY (compressed_chunk_id) REFERENCES _timescaledb_catalog.chunk (id),\n  CONSTRAINT chunk_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id)\n);\nALTER SEQUENCE _timescaledb_catalog.chunk_id_seq OWNED BY _timescaledb_catalog.chunk.id;\n\nCREATE INDEX chunk_hypertable_id_idx ON _timescaledb_catalog.chunk (hypertable_id);\nCREATE INDEX chunk_compressed_chunk_id_idx ON _timescaledb_catalog.chunk (compressed_chunk_id);\n--we could use a partial index (where osm_chunk is true). However, the catalog code\n--does not work with partial/functional indexes. So we instead have a full index here.\n--Another option would be to use the status field to identify a OSM chunk. However bit\n--operations only work on varbit datatype and not integer datatype.\nCREATE INDEX chunk_osm_chunk_idx ON _timescaledb_catalog.chunk (osm_chunk, hypertable_id);\nCREATE INDEX chunk_hypertable_id_creation_time_idx ON _timescaledb_catalog.chunk(hypertable_id, creation_time);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.chunk', '');\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.chunk_id_seq', '');\n\n-- A chunk constraint maps a dimension slice to a chunk. Each\n-- constraint associated with a chunk will also be a table constraint\n-- on the chunk's data table.\nCREATE TABLE _timescaledb_catalog.chunk_constraint (\n  chunk_id integer NOT NULL,\n  dimension_slice_id integer NULL,\n  constraint_name name NOT NULL,\n  hypertable_constraint_name name NULL,\n  -- table constraints\n  CONSTRAINT chunk_constraint_chunk_id_constraint_name_key UNIQUE (chunk_id, constraint_name),\n  CONSTRAINT chunk_constraint_chunk_id_fkey FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk (id),\n  CONSTRAINT chunk_constraint_dimension_slice_id_fkey FOREIGN KEY (dimension_slice_id) REFERENCES _timescaledb_catalog.dimension_slice (id)\n);\n\nCREATE INDEX chunk_constraint_dimension_slice_id_idx ON _timescaledb_catalog.chunk_constraint (dimension_slice_id);\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.chunk_constraint', '');\n\nCREATE SEQUENCE _timescaledb_catalog.chunk_constraint_name;\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.chunk_constraint_name', '');\n\n-- Track statistics for columns of chunks from a hypertable.\n-- Currently, we track the min/max range for a given column across chunks.\n-- More statistics (like bloom filters) can be added in the future.\n--\n-- A \"special\" entry for a column with invalid chunk_id, PG_INT64_MAX,\n-- PG_INT64_MIN indicates that min/max ranges could be computed for this column\n-- for chunks.\n--\n-- The ranges can overlap across chunks. The values could be out-of-date if\n-- modifications/changes occur in the corresponding chunk and such entries\n-- should be marked as \"invalid\" to ensure that the chunk is in\n-- appropriate state to be able to use these values. Thus these entries\n-- are different from dimension_slice which is used for tracking partitioning\n-- column ranges which have different characteristics.\n--\n-- Currently this catalog supports datatypes like INT, SERIAL, BIGSERIAL,\n-- DATE, TIMESTAMP etc. by storing the ranges in bigint columns. In the\n-- future, we could support additional datatypes (which support btree style\n-- >, <, = comparators) by storing their textual representation.\n--\nCREATE TABLE _timescaledb_catalog.chunk_column_stats (\n  id serial NOT NULL,\n  hypertable_id integer NOT NULL,\n  chunk_id integer NULL,\n  column_name name NOT NULL,\n  range_start bigint NOT NULL,\n  range_end bigint NOT NULL,\n  valid boolean NOT NULL,\n  -- table constraints\n  CONSTRAINT chunk_column_stats_pkey PRIMARY KEY (id),\n  CONSTRAINT chunk_column_stats_ht_id_chunk_id_colname_key UNIQUE (hypertable_id, chunk_id, column_name),\n  CONSTRAINT chunk_column_stats_range_check CHECK (range_start <= range_end),\n  CONSTRAINT chunk_column_stats_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id),\n  CONSTRAINT chunk_column_stats_chunk_id_fkey FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk (id)\n);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.chunk_column_stats', '');\n\nSELECT pg_catalog.pg_extension_config_dump(pg_get_serial_sequence('_timescaledb_catalog.chunk_column_stats', 'id'), '');\n\n-- Default jobs are given the id space [1,1000). User-installed jobs and any jobs created inside tests\n-- are given the id space [1000, INT_MAX). That way, we do not pg_dump jobs that are always default-installed\n-- inside other .sql scripts. This avoids insertion conflicts during pg_restore.\nCREATE SEQUENCE _timescaledb_catalog.bgw_job_id_seq\nMINVALUE 1000;\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.bgw_job_id_seq', '');\n\n  -- We put columns that can be null or have variable length\n  -- last. This allow us to read the important fields above in the\n  -- scheduler without materializing these fields below, which the\n  -- scheduler does not neeed.\nCREATE TABLE _timescaledb_catalog.bgw_job (\n  id integer NOT NULL DEFAULT nextval('_timescaledb_catalog.bgw_job_id_seq'),\n  application_name name NOT NULL,\n  schedule_interval interval NOT NULL,\n  max_runtime interval NOT NULL,\n  max_retries integer NOT NULL,\n  retry_period interval NOT NULL,\n  proc_schema name NOT NULL,\n  proc_name name NOT NULL,\n  owner regrole NOT NULL DEFAULT pg_catalog.quote_ident(current_role)::regrole,\n  scheduled bool NOT NULL DEFAULT TRUE,\n  fixed_schedule bool not null default true,\n  initial_start timestamptz,\n  hypertable_id integer,\n  config jsonb,\n  check_schema name,\n  check_name name,\n  timezone text,\n  -- table constraints\n  CONSTRAINT bgw_job_pkey PRIMARY KEY (id),\n  CONSTRAINT bgw_job_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE\n);\n\nALTER SEQUENCE _timescaledb_catalog.bgw_job_id_seq OWNED BY _timescaledb_catalog.bgw_job.id;\n\nCREATE INDEX bgw_job_proc_hypertable_id_idx ON _timescaledb_catalog.bgw_job (proc_schema, proc_name, hypertable_id);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.bgw_job', 'WHERE id >= 1000');\n\nCREATE TABLE _timescaledb_internal.bgw_job_stat (\n  job_id integer NOT NULL,\n  last_start timestamptz NOT NULL DEFAULT NOW(),\n  last_finish timestamptz NOT NULL,\n  next_start timestamptz NOT NULL,\n  last_successful_finish timestamptz NOT NULL,\n  last_run_success bool NOT NULL,\n  total_runs bigint NOT NULL,\n  total_duration interval NOT NULL,\n  total_duration_failures interval NOT NULL,\n  total_successes bigint NOT NULL,\n  total_failures bigint NOT NULL,\n  total_crashes bigint NOT NULL,\n  consecutive_failures int NOT NULL,\n  consecutive_crashes int NOT NULL,\n  flags int NOT NULL DEFAULT 0,\n  -- table constraints\n  CONSTRAINT bgw_job_stat_pkey PRIMARY KEY (job_id),\n  CONSTRAINT bgw_job_stat_job_id_fkey FOREIGN KEY (job_id) REFERENCES _timescaledb_catalog.bgw_job (id) ON DELETE CASCADE\n);\n\nCREATE SEQUENCE _timescaledb_internal.bgw_job_stat_history_id_seq MINVALUE 1;\n\nCREATE TABLE _timescaledb_internal.bgw_job_stat_history (\n  id BIGINT NOT NULL DEFAULT nextval('_timescaledb_internal.bgw_job_stat_history_id_seq'),\n  job_id INTEGER NOT NULL,\n  pid INTEGER,\n  execution_start TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n  execution_finish TIMESTAMPTZ,\n  succeeded boolean,\n  data jsonb,\n  -- table constraints\n  CONSTRAINT bgw_job_stat_history_pkey PRIMARY KEY (id)\n);\n\nALTER SEQUENCE _timescaledb_internal.bgw_job_stat_history_id_seq OWNED BY _timescaledb_internal.bgw_job_stat_history.id;\n\nCREATE INDEX bgw_job_stat_history_execution_start_idx\n    ON _timescaledb_internal.bgw_job_stat_history (execution_start);\nCREATE INDEX bgw_job_stat_history_job_id_execution_start_idx\n    ON _timescaledb_internal.bgw_job_stat_history(job_id, execution_start DESC);\n\n--The job_stat table is not dumped by pg_dump on purpose because\n--the statistics probably aren't very meaningful across instances.\n-- Now we define a special stats table for each job/chunk pair. This will be used by the scheduler\n-- to determine whether to run a specific job on a specific chunk.\nCREATE TABLE _timescaledb_internal.bgw_policy_chunk_stats (\n  job_id integer NOT NULL,\n  chunk_id integer NOT NULL,\n  num_times_job_run integer,\n  last_time_job_run timestamptz,\n  -- table constraints\n  CONSTRAINT bgw_policy_chunk_stats_job_id_chunk_id_key UNIQUE (job_id, chunk_id),\n  CONSTRAINT bgw_policy_chunk_stats_chunk_id_fkey FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk (id) ON DELETE CASCADE,\n  CONSTRAINT bgw_policy_chunk_stats_job_id_fkey FOREIGN KEY (job_id) REFERENCES _timescaledb_catalog.bgw_job (id) ON DELETE CASCADE\n);\n\nCREATE TABLE _timescaledb_catalog.metadata (\n  key NAME NOT NULL,\n  value text NOT NULL,\n  include_in_telemetry boolean NOT NULL,\n  -- table constraints\n  CONSTRAINT metadata_pkey PRIMARY KEY (key)\n);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.metadata', $$ WHERE key <> 'uuid' $$);\n\n-- Log with events that will be sent out with the telemetry. The log\n-- will be flushed after it has been sent out. We do not save it to\n-- backups since it should not contain important data.\nCREATE TABLE _timescaledb_catalog.telemetry_event (\n       created timestamptz NOT NULL DEFAULT current_timestamp,\n       tag name NOT NULL,\n       body jsonb NOT NULL\n);\n\nCREATE TABLE _timescaledb_catalog.continuous_agg (\n  mat_hypertable_id integer NOT NULL,\n  raw_hypertable_id integer NOT NULL,\n  parent_mat_hypertable_id integer,\n  user_view_schema name NOT NULL,\n  user_view_name name NOT NULL,\n  partial_view_schema name NOT NULL,\n  partial_view_name name NOT NULL,\n  direct_view_schema name NOT NULL,\n  direct_view_name name NOT NULL,\n  materialized_only bool NOT NULL DEFAULT FALSE,\n  -- table constraints\n  CONSTRAINT continuous_agg_pkey PRIMARY KEY (mat_hypertable_id),\n  CONSTRAINT continuous_agg_partial_view_schema_partial_view_name_key UNIQUE (partial_view_schema, partial_view_name),\n  CONSTRAINT continuous_agg_user_view_schema_user_view_name_key UNIQUE (user_view_schema, user_view_name),\n  CONSTRAINT continuous_agg_mat_hypertable_id_fkey FOREIGN KEY (mat_hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE,\n  CONSTRAINT continuous_agg_raw_hypertable_id_fkey FOREIGN KEY (raw_hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE,\n  CONSTRAINT continuous_agg_parent_mat_hypertable_id_fkey FOREIGN KEY (parent_mat_hypertable_id)\n    REFERENCES _timescaledb_catalog.continuous_agg (mat_hypertable_id) ON DELETE CASCADE\n);\n\nCREATE INDEX continuous_agg_raw_hypertable_id_idx ON _timescaledb_catalog.continuous_agg (raw_hypertable_id);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.continuous_agg', '');\n\n-- See the comments for ContinuousAggsBucketFunction structure.\nCREATE TABLE _timescaledb_catalog.continuous_aggs_bucket_function (\n  mat_hypertable_id integer NOT NULL,\n  -- The bucket function\n  bucket_func text NOT NULL,\n  -- `bucket_width` argument of the function, e.g. \"1 month\"\n  bucket_width text NOT NULL,\n  -- optional `origin` argument of the function provided by the user\n  bucket_origin text,\n  -- optional `offset` argument of the function provided by the user\n  bucket_offset text,\n  -- optional `timezone` argument of the function provided by the user\n  bucket_timezone text,\n  -- fixed or variable sized bucket\n  bucket_fixed_width bool NOT NULL,\n  -- table constraints\n  CONSTRAINT continuous_aggs_bucket_function_pkey PRIMARY KEY (mat_hypertable_id),\n  CONSTRAINT continuous_aggs_bucket_function_mat_hypertable_id_fkey FOREIGN KEY (mat_hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE,\n  CONSTRAINT continuous_aggs_bucket_function_func_check CHECK (pg_catalog.to_regprocedure(bucket_func) IS DISTINCT FROM 0)\n);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.continuous_aggs_bucket_function', '');\n\nCREATE TABLE _timescaledb_catalog.continuous_aggs_invalidation_threshold (\n  hypertable_id integer NOT NULL,\n  watermark bigint NOT NULL,\n  -- table constraints\n  CONSTRAINT continuous_aggs_invalidation_threshold_pkey PRIMARY KEY (hypertable_id),\n  CONSTRAINT continuous_aggs_invalidation_threshold_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE\n);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.continuous_aggs_invalidation_threshold', '');\n\nCREATE TABLE _timescaledb_catalog.continuous_aggs_watermark (\n  mat_hypertable_id integer NOT NULL,\n  watermark bigint NOT NULL,\n  -- table constraints\n  CONSTRAINT continuous_aggs_watermark_pkey PRIMARY KEY (mat_hypertable_id),\n  CONSTRAINT continuous_aggs_watermark_mat_hypertable_id_fkey FOREIGN KEY (mat_hypertable_id) REFERENCES _timescaledb_catalog.continuous_agg (mat_hypertable_id) ON DELETE CASCADE\n);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.continuous_aggs_watermark', '');\n\n\n\n-- this does not have an FK on the materialization table since INSERTs to this\n-- table are performance critical\nCREATE TABLE _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log (\n  hypertable_id integer NOT NULL,\n  lowest_modified_value bigint NOT NULL,\n  greatest_modified_value bigint NOT NULL\n);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.continuous_aggs_hypertable_invalidation_log', '');\n\nCREATE INDEX continuous_aggs_hypertable_invalidation_log_idx ON _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log (hypertable_id, lowest_modified_value ASC);\n\n-- per cagg copy of invalidation log\nCREATE TABLE _timescaledb_catalog.continuous_aggs_materialization_invalidation_log (\n  materialization_id integer,\n  lowest_modified_value bigint NOT NULL,\n  greatest_modified_value bigint NOT NULL,\n  -- table constraints\n  CONSTRAINT continuous_aggs_materialization_invalid_materialization_id_fkey FOREIGN KEY (materialization_id) REFERENCES _timescaledb_catalog.continuous_agg (mat_hypertable_id) ON DELETE CASCADE\n);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.continuous_aggs_materialization_invalidation_log', '');\n\nCREATE INDEX continuous_aggs_materialization_invalidation_log_idx ON _timescaledb_catalog.continuous_aggs_materialization_invalidation_log (materialization_id, lowest_modified_value ASC);\n\n-- cagg materialization ranges\nCREATE TABLE _timescaledb_catalog.continuous_aggs_materialization_ranges (\n  materialization_id integer,\n  lowest_modified_value bigint NOT NULL,\n  greatest_modified_value bigint NOT NULL,\n  -- table constraints\n  CONSTRAINT continuous_aggs_materialization_ranges_materialization_id_fkey FOREIGN KEY (materialization_id) REFERENCES _timescaledb_catalog.continuous_agg (mat_hypertable_id) ON DELETE CASCADE\n);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.continuous_aggs_materialization_ranges', '');\n\nCREATE INDEX continuous_aggs_materialization_ranges_idx ON _timescaledb_catalog.continuous_aggs_materialization_ranges (materialization_id, lowest_modified_value ASC);\n\n/* the source of this data is the enum from the source code that lists\n *  the algorithms. This table is NOT dumped.\n */\nCREATE TABLE _timescaledb_catalog.compression_algorithm (\n  id smallint NOT NULL,\n  version smallint NOT NULL,\n  name name NOT NULL,\n  description text,\n  -- table constraints\n  CONSTRAINT compression_algorithm_pkey PRIMARY KEY (id)\n);\n\nCREATE TABLE _timescaledb_catalog.compression_settings (\n  relid regclass NOT NULL,\n  compress_relid regclass NULL,\n  segmentby text[],\n  orderby text[],\n  orderby_desc bool[],\n  orderby_nullsfirst bool[],\n  index jsonb,\n  CONSTRAINT compression_settings_pkey PRIMARY KEY (relid),\n  CONSTRAINT compression_settings_check_segmentby CHECK (array_ndims(segmentby) = 1),\n  CONSTRAINT compression_settings_check_orderby_null CHECK ((orderby IS NULL AND orderby_desc IS NULL AND orderby_nullsfirst IS NULL) OR (orderby IS NOT NULL AND orderby_desc IS NOT NULL AND orderby_nullsfirst IS NOT NULL)),\n  CONSTRAINT compression_settings_check_orderby_cardinality CHECK (array_ndims(orderby) = 1 AND array_ndims(orderby_desc) = 1 AND array_ndims(orderby_nullsfirst) = 1 AND cardinality(orderby) = cardinality(orderby_desc) AND cardinality(orderby) = cardinality(orderby_nullsfirst))\n);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.compression_settings', '');\nCREATE INDEX compression_settings_compress_relid_idx ON _timescaledb_catalog.compression_settings (compress_relid);\n\nCREATE TABLE _timescaledb_catalog.compression_chunk_size (\n  chunk_id integer NOT NULL,\n  compressed_chunk_id integer NOT NULL,\n  uncompressed_heap_size bigint NOT NULL,\n  uncompressed_toast_size bigint NOT NULL,\n  uncompressed_index_size bigint NOT NULL,\n  compressed_heap_size bigint NOT NULL,\n  compressed_toast_size bigint NOT NULL,\n  compressed_index_size bigint NOT NULL,\n  numrows_pre_compression bigint,\n  numrows_post_compression bigint,\n  numrows_frozen_immediately bigint,\n  -- table constraints\n  CONSTRAINT compression_chunk_size_pkey PRIMARY KEY (chunk_id),\n  CONSTRAINT compression_chunk_size_chunk_id_fkey FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk (id) ON DELETE CASCADE,\n  CONSTRAINT compression_chunk_size_compressed_chunk_id_fkey FOREIGN KEY (compressed_chunk_id) REFERENCES _timescaledb_catalog.chunk (id) ON DELETE CASCADE\n);\n\n-- Create index on the compressed_chunk_id to speed up maintainance\n-- operations during upgrades. This is mostly relevant for very large\n-- number of chunks.\nCREATE INDEX compression_chunk_size_idx ON _timescaledb_catalog.compression_chunk_size (compressed_chunk_id);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.compression_chunk_size', '');\n\nCREATE TABLE _timescaledb_catalog.chunk_rewrite (\n  chunk_relid REGCLASS NOT NULL,\n  new_relid REGCLASS NOT NULL,\n  CONSTRAINT chunk_rewrite_key UNIQUE (chunk_relid)\n);\n\n-- Set table permissions\n-- We need to grant SELECT to PUBLIC for all tables even those not\n-- marked as being dumped because pg_dump will try to access all\n-- tables initially to detect inheritance chains and then decide\n-- which objects actually need to be dumped.\nGRANT SELECT ON ALL TABLES IN SCHEMA _timescaledb_catalog TO PUBLIC;\n\nGRANT SELECT ON ALL TABLES IN SCHEMA _timescaledb_internal TO PUBLIC;\n\nGRANT SELECT ON ALL SEQUENCES IN SCHEMA _timescaledb_catalog TO PUBLIC;\n\nGRANT SELECT ON ALL SEQUENCES IN SCHEMA _timescaledb_internal TO PUBLIC;\n\n-- We want to restrict access to the bgw_job_stat_history to only work through\n-- the job_errors and job_history views.\nREVOKE ALL ON _timescaledb_internal.bgw_job_stat_history FROM PUBLIC;\n"
  },
  {
    "path": "sql/pre_install/types.functions.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\n-- Functions have to be run in 2 places:\n-- 1) In pre-install between types.pre.sql and types.post.sql to set up the types.\n-- 2) On every update to make sure the function points to the correct versioned.so\n\n\n-- PostgreSQL composite types do not support constraint checks. That is why any table having a ts_interval column must use the following\n-- function for constraint validation.\n-- This function needs to be defined before executing pre_install/tables.sql because it is used as\n-- validation constraint for columns of type ts_interval.\n\n--the textual input/output is simply base64 encoding of the binary representation\nCREATE OR REPLACE FUNCTION _timescaledb_functions.compressed_data_in(CSTRING)\n   RETURNS _timescaledb_internal.compressed_data\n   AS '@MODULE_PATHNAME@', 'ts_compressed_data_in'\n   LANGUAGE C IMMUTABLE STRICT;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.compressed_data_out(_timescaledb_internal.compressed_data)\n   RETURNS CSTRING\n   AS '@MODULE_PATHNAME@', 'ts_compressed_data_out'\n   LANGUAGE C IMMUTABLE STRICT;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.compressed_data_send(_timescaledb_internal.compressed_data)\n   RETURNS BYTEA\n   AS '@MODULE_PATHNAME@', 'ts_compressed_data_send'\n   LANGUAGE C IMMUTABLE STRICT;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.compressed_data_recv(internal)\n   RETURNS _timescaledb_internal.compressed_data\n   AS '@MODULE_PATHNAME@', 'ts_compressed_data_recv'\n   LANGUAGE C IMMUTABLE STRICT;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.compressed_data_info(_timescaledb_internal.compressed_data)\n    RETURNS TABLE (algorithm name, has_nulls bool)\n    LANGUAGE C STRICT IMMUTABLE\n    AS '@MODULE_PATHNAME@', 'ts_compressed_data_info';\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.compressed_data_has_nulls(_timescaledb_internal.compressed_data)\n    RETURNS BOOL\n    LANGUAGE C STRICT IMMUTABLE\n    AS '@MODULE_PATHNAME@', 'ts_compressed_data_has_nulls';\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.dimension_info_in(cstring)\n    RETURNS _timescaledb_internal.dimension_info\n    LANGUAGE C STRICT IMMUTABLE\n    AS '@MODULE_PATHNAME@', 'ts_dimension_info_in';\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.dimension_info_out(_timescaledb_internal.dimension_info)\n    RETURNS cstring\n    LANGUAGE C STRICT IMMUTABLE\n    AS '@MODULE_PATHNAME@', 'ts_dimension_info_out';\n\n\n-- Type for bloom filters used by the sparse indexes on compressed hypertables.\nCREATE OR REPLACE FUNCTION _timescaledb_functions.bloom1in(cstring) RETURNS _timescaledb_internal.bloom1 AS 'byteain' LANGUAGE INTERNAL STRICT IMMUTABLE PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION _timescaledb_functions.bloom1out(_timescaledb_internal.bloom1) RETURNS cstring AS 'byteaout' LANGUAGE INTERNAL STRICT IMMUTABLE PARALLEL SAFE;\n\n"
  },
  {
    "path": "sql/pre_install/types.post.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n--\n-- The general compressed_data type;\n--\nCREATE TYPE _timescaledb_internal.compressed_data (\n  INTERNALLENGTH = VARIABLE,\n  STORAGE = EXTERNAL,\n  ALIGNMENT = double, --needed for alignment in ARRAY type compression\n  INPUT = _timescaledb_functions.compressed_data_in,\n  OUTPUT = _timescaledb_functions.compressed_data_out,\n  RECEIVE = _timescaledb_functions.compressed_data_recv,\n  SEND = _timescaledb_functions.compressed_data_send\n);\n\n-- Dimension type used in create_hypertable, add_dimension, etc. It is\n-- deliberately an opaque type.\nCREATE TYPE _timescaledb_internal.dimension_info (\n    INPUT = _timescaledb_functions.dimension_info_in,\n    OUTPUT = _timescaledb_functions.dimension_info_out,\n    INTERNALLENGTH = VARIABLE\n);\n\n\n-- Type for bloom filters used by the sparse indexes on compressed hypertables.\nCREATE TYPE _timescaledb_internal.bloom1 (\n    INPUT = _timescaledb_functions.bloom1in,\n    OUTPUT = _timescaledb_functions.bloom1out,\n    LIKE = bytea\n);\n\n"
  },
  {
    "path": "sql/pre_install/types.pre.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n--\n-- The general compressed_data type;\n--\nCREATE TYPE _timescaledb_internal.compressed_data;\n\n--placeholder to allow creation of functions below\n\nCREATE TYPE _timescaledb_internal.dimension_info;\n\n\n-- Type for bloom filters used by the sparse indexes on compressed hypertables.\nCREATE TYPE _timescaledb_internal.bloom1;\n"
  },
  {
    "path": "sql/restoring.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE OR REPLACE FUNCTION @extschema@.timescaledb_pre_restore() RETURNS BOOL AS\n$BODY$\nDECLARE\n    db text;\nBEGIN\n    SELECT current_database() INTO db;\n    EXECUTE format($$ALTER DATABASE %I SET timescaledb.restoring ='on'$$, db);\n    SET SESSION timescaledb.restoring = 'on';\n    PERFORM _timescaledb_functions.stop_background_workers();\n    RETURN true;\nEND\n$BODY$\nLANGUAGE PLPGSQL SET search_path TO pg_catalog, pg_temp;\n\n\nCREATE OR REPLACE FUNCTION @extschema@.timescaledb_post_restore() RETURNS BOOL AS\n$BODY$\nDECLARE\n    db text;\n    catalog_version text;\nBEGIN\n    SELECT m.value INTO catalog_version FROM pg_extension x\n    JOIN _timescaledb_catalog.metadata m ON m.key='timescaledb_version'\n    WHERE x.extname='timescaledb' AND x.extversion <> m.value;\n\n    -- check that a loaded dump is compatible with the currently running code\n    IF FOUND THEN\n        RAISE EXCEPTION 'catalog version mismatch, expected \"%\" seen \"%\"', '@PROJECT_VERSION_MOD@', catalog_version;\n    END IF;\n\n    SELECT current_database() INTO db;\n    EXECUTE format($$ALTER DATABASE %I RESET timescaledb.restoring $$, db);\n    -- we cannot use reset here because the reset_val might not be off\n    SET timescaledb.restoring TO off;\n    PERFORM _timescaledb_functions.restart_background_workers();\n\n    RETURN true;\nEND\n$BODY$\nLANGUAGE PLPGSQL SET search_path TO pg_catalog, pg_temp;\n"
  },
  {
    "path": "sql/size_utils.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- This file contains utility functions to get the relation size\n-- of hypertables, chunks, and indexes on hypertables.\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.index_matches(index1 regclass, index2 regclass) RETURNS BOOLEAN\nAS '@MODULE_PATHNAME@', 'ts_index_matches' LANGUAGE C STRICT IMMUTABLE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.relation_size(relation REGCLASS)\nRETURNS TABLE (total_size BIGINT, heap_size BIGINT, index_size BIGINT, toast_size BIGINT)\nAS '@MODULE_PATHNAME@', 'ts_relation_size' LANGUAGE C VOLATILE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.relation_approximate_size(relation REGCLASS)\nRETURNS TABLE (total_size BIGINT, heap_size BIGINT, index_size BIGINT, toast_size BIGINT)\nAS '@MODULE_PATHNAME@', 'ts_relation_approximate_size' LANGUAGE C STRICT VOLATILE;\n\nCREATE OR REPLACE VIEW _timescaledb_internal.hypertable_chunk_local_size AS\nSELECT\n    h.schema_name AS hypertable_schema,\n    h.table_name AS hypertable_name,\n    h.id AS hypertable_id,\n    c.id AS chunk_id,\n    c.schema_name AS chunk_schema,\n    c.table_name AS chunk_name,\n    COALESCE((relsize).total_size, 0) AS total_bytes,\n    COALESCE((relsize).heap_size, 0) AS heap_bytes,\n    COALESCE((relsize).index_size, 0) AS index_bytes,\n    COALESCE((relsize).toast_size, 0) AS toast_bytes,\n    COALESCE((relcompsize).total_size, 0) AS compressed_total_size,\n    COALESCE((relcompsize).heap_size, 0) AS compressed_heap_size,\n    COALESCE((relcompsize).index_size, 0) AS compressed_index_size,\n    COALESCE((relcompsize).toast_size, 0) AS compressed_toast_size\nFROM\n    _timescaledb_catalog.hypertable h\n    JOIN _timescaledb_catalog.chunk c ON h.id = c.hypertable_id\n    JOIN pg_class cl ON cl.relname = c.table_name AND cl.relkind = 'r'\n    JOIN pg_namespace n ON n.oid = cl.relnamespace\n    AND n.nspname = c.schema_name\n    JOIN LATERAL _timescaledb_functions.relation_size(cl.oid) AS relsize ON TRUE\n    LEFT JOIN _timescaledb_catalog.compression_settings cs ON cs.relid = cl.oid\n    LEFT JOIN LATERAL _timescaledb_functions.relation_size(cs.compress_relid) AS relcompsize ON TRUE;\n\nGRANT SELECT ON  _timescaledb_internal.hypertable_chunk_local_size TO PUBLIC;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.hypertable_local_size(\n\tschema_name_in name,\n\ttable_name_in name)\nRETURNS TABLE (\n\ttable_bytes BIGINT,\n\tindex_bytes BIGINT,\n\ttoast_bytes BIGINT,\n\ttotal_bytes BIGINT)\nLANGUAGE SQL VOLATILE STRICT AS\n$BODY$\n    /* get the main hypertable id and sizes */\n    WITH _hypertable_sizes AS (\n        SELECT\n            id,\n            COALESCE((relsize).total_size, 0) AS total_bytes,\n            COALESCE((relsize).heap_size, 0) AS heap_bytes,\n            COALESCE((relsize).index_size, 0) AS index_bytes,\n            COALESCE((relsize).toast_size, 0) AS toast_bytes,\n            0::BIGINT AS compressed_total_size,\n            0::BIGINT AS compressed_index_size,\n            0::BIGINT AS compressed_toast_size,\n            0::BIGINT AS compressed_heap_size\n        FROM\n            _timescaledb_catalog.hypertable ht\n            JOIN pg_class c ON relname = ht.table_name AND c.relkind = 'r'\n            JOIN pg_namespace n ON n.oid = c.relnamespace\n            AND n.nspname = ht.schema_name\n            JOIN LATERAL _timescaledb_functions.relation_size(c.oid) AS relsize ON TRUE\n        WHERE\n            schema_name = schema_name_in\n            AND table_name = table_name_in\n    ),\n    /* calculate the size of the hypertable chunks */\n    _chunk_sizes AS (\n        SELECT\n            chunk_id,\n            COALESCE(ch.total_bytes, 0) AS total_bytes,\n            COALESCE(ch.heap_bytes, 0) AS heap_bytes,\n            COALESCE(ch.index_bytes, 0) AS index_bytes,\n            COALESCE(ch.toast_bytes, 0) AS toast_bytes,\n            COALESCE(ch.compressed_total_size, 0) AS compressed_total_size,\n            COALESCE(ch.compressed_index_size, 0) AS compressed_index_size,\n            COALESCE(ch.compressed_toast_size, 0) AS compressed_toast_size,\n            COALESCE(ch.compressed_heap_size, 0) AS compressed_heap_size\n        FROM\n            _timescaledb_internal.hypertable_chunk_local_size ch\n            JOIN _hypertable_sizes ht ON ht.id = ch.hypertable_id\n        WHERE hypertable_schema = schema_name_in\n          AND hypertable_name = table_name_in\n    )\n    /* calculate the SUM of the hypertable and chunk sizes */\n\tSELECT\n\t\t(SUM(heap_bytes)  + SUM(compressed_heap_size))::BIGINT AS heap_bytes,\n\t\t(SUM(index_bytes) + SUM(compressed_index_size))::BIGINT AS index_bytes,\n\t\t(SUM(toast_bytes) + SUM(compressed_toast_size))::BIGINT AS toast_bytes,\n\t\t(SUM(total_bytes) + SUM(compressed_total_size))::BIGINT AS total_bytes\n\tFROM\n\t\t(SELECT * FROM _hypertable_sizes\n         UNION ALL\n         SELECT * FROM _chunk_sizes) AS sizes;\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\n-- Get relation size of hypertable\n-- like pg_relation_size(hypertable)\n--\n-- hypertable - hypertable to get size of\n--\n-- Returns:\n-- table_bytes        - Disk space used by hypertable (like pg_relation_size(hypertable))\n-- index_bytes        - Disk space used by indexes\n-- toast_bytes        - Disk space of toast tables\n-- total_bytes        - Total disk space used by the specified table, including all indexes and TOAST data\n\nCREATE OR REPLACE FUNCTION @extschema@.hypertable_detailed_size(\n    hypertable              REGCLASS)\nRETURNS TABLE (table_bytes BIGINT,\n               index_bytes BIGINT,\n               toast_bytes BIGINT,\n               total_bytes BIGINT,\n               node_name   NAME)\nLANGUAGE PLPGSQL VOLATILE STRICT AS\n$BODY$\nDECLARE\n        table_name       NAME = NULL;\n        schema_name      NAME = NULL;\nBEGIN\n        SELECT relname, nspname\n        INTO table_name, schema_name\n        FROM pg_class c\n        INNER JOIN pg_namespace n ON (n.OID = c.relnamespace)\n        INNER JOIN _timescaledb_catalog.hypertable ht ON (ht.schema_name = n.nspname AND ht.table_name = c.relname)\n        WHERE c.OID = hypertable;\n\n        IF table_name IS NULL THEN\n                SELECT h.schema_name, h.table_name\n                INTO schema_name, table_name\n                FROM pg_class c\n                INNER JOIN pg_namespace n ON (n.OID = c.relnamespace)\n                INNER JOIN _timescaledb_catalog.continuous_agg a ON (a.user_view_schema = n.nspname AND a.user_view_name = c.relname)\n                INNER JOIN _timescaledb_catalog.hypertable h ON h.id = a.mat_hypertable_id\n                WHERE c.OID = hypertable;\n\n\t        IF table_name IS NULL THEN\n                        RETURN;\n                END IF;\n        END IF;\n\n\t\t\tRETURN QUERY\n\t\t\tSELECT *, NULL::name\n\t\t\tFROM _timescaledb_functions.hypertable_local_size(schema_name, table_name);\nEND;\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\n--- returns total-bytes for a hypertable (includes table + index)\nCREATE OR REPLACE FUNCTION @extschema@.hypertable_size(\n    hypertable              REGCLASS)\nRETURNS BIGINT\nLANGUAGE SQL VOLATILE STRICT AS\n$BODY$\n   SELECT total_bytes::bigint FROM @extschema@.hypertable_detailed_size(hypertable);\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\n-- Get approximate relation size of hypertable\n--\n-- hypertable - hypertable to get approximate size of\n--\n-- Returns:\n-- table_bytes        - Approximate disk space used by hypertable\n-- index_bytes        - Approximate disk space used by indexes\n-- toast_bytes        - Approximate disk space of toast tables\n-- total_bytes        - Total approximate disk space used by the specified table, including all indexes and TOAST data\nCREATE OR REPLACE FUNCTION @extschema@.hypertable_approximate_detailed_size(relation REGCLASS)\nRETURNS TABLE (table_bytes BIGINT, index_bytes BIGINT, toast_bytes BIGINT, total_bytes BIGINT)\nAS '@MODULE_PATHNAME@', 'ts_hypertable_approximate_size' LANGUAGE C VOLATILE;\n\n--- returns approximate total-bytes for a hypertable (includes table + index)\nCREATE OR REPLACE FUNCTION @extschema@.hypertable_approximate_size(\n    hypertable              REGCLASS)\nRETURNS BIGINT\nLANGUAGE SQL VOLATILE STRICT AS\n$BODY$\n   SELECT sum(total_bytes)::bigint\n   FROM @extschema@.hypertable_approximate_detailed_size(hypertable);\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.chunks_local_size(\n    schema_name_in name,\n    table_name_in name)\nRETURNS TABLE (\n    chunk_id    integer,\n    chunk_schema NAME,\n    chunk_name  NAME,\n    table_bytes bigint,\n    index_bytes bigint,\n    toast_bytes bigint,\n    total_bytes bigint)\nLANGUAGE SQL VOLATILE STRICT AS\n$BODY$\n   SELECT\n      ch.chunk_id,\n      ch.chunk_schema,\n      ch.chunk_name,\n      (ch.total_bytes - COALESCE( ch.index_bytes , 0 ) - COALESCE( ch.toast_bytes, 0 ) + COALESCE( ch.compressed_heap_size , 0 ))::bigint  as heap_bytes,\n      (COALESCE( ch.index_bytes, 0 ) + COALESCE( ch.compressed_index_size , 0) )::bigint as index_bytes,\n      (COALESCE( ch.toast_bytes, 0 ) + COALESCE( ch.compressed_toast_size, 0 ))::bigint as toast_bytes,\n      (ch.total_bytes + COALESCE( ch.compressed_total_size, 0 ))::bigint as total_bytes\n   FROM\n\t  _timescaledb_internal.hypertable_chunk_local_size ch\n   WHERE\n      ch.hypertable_schema = schema_name_in\n      AND ch.hypertable_name = table_name_in;\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\n-- Get relation size of the chunks of an hypertable\n-- hypertable - hypertable to get size of\n--\n-- Returns:\n-- chunk_schema                  - schema name for chunk\n-- chunk_name                    - chunk table name\n-- table_bytes                   - Disk space used by chunk table\n-- index_bytes                   - Disk space used by indexes\n-- toast_bytes                   - Disk space of toast tables\n-- total_bytes                   - Disk space used in total\n-- node_name                     - node on which chunk lives if this is\n--                              a distributed hypertable.\nCREATE OR REPLACE FUNCTION @extschema@.chunks_detailed_size(\n    hypertable              REGCLASS\n)\nRETURNS TABLE (\n               chunk_schema NAME,\n               chunk_name NAME,\n               table_bytes BIGINT,\n               index_bytes BIGINT,\n               toast_bytes BIGINT,\n               total_bytes BIGINT,\n               node_name   NAME)\nLANGUAGE PLPGSQL VOLATILE STRICT AS\n$BODY$\nDECLARE\n        table_name       NAME;\n        schema_name      NAME;\nBEGIN\n        SELECT relname, nspname\n        INTO table_name, schema_name\n        FROM pg_class c\n        INNER JOIN pg_namespace n ON (n.OID = c.relnamespace)\n        INNER JOIN _timescaledb_catalog.hypertable ht ON (ht.schema_name = n.nspname AND ht.table_name = c.relname)\n        WHERE c.OID = hypertable;\n\n        IF table_name IS NULL THEN\n            SELECT h.schema_name, h.table_name\n            INTO schema_name, table_name\n            FROM pg_class c\n            INNER JOIN pg_namespace n ON (n.OID = c.relnamespace)\n            INNER JOIN _timescaledb_catalog.continuous_agg a ON (a.user_view_schema = n.nspname AND a.user_view_name = c.relname)\n            INNER JOIN _timescaledb_catalog.hypertable h ON h.id = a.mat_hypertable_id\n            WHERE c.OID = hypertable;\n\n            IF table_name IS NULL THEN\n                RETURN;\n            END IF;\n\t\tEND IF;\n\n    RETURN QUERY SELECT chl.chunk_schema, chl.chunk_name, chl.table_bytes, chl.index_bytes,\n                        chl.toast_bytes, chl.total_bytes, NULL::NAME\n            FROM _timescaledb_functions.chunks_local_size(schema_name, table_name) chl;\nEND;\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n---------- end of detailed size functions ------\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.range_value_to_pretty(\n    time_value      BIGINT,\n    column_type     REGTYPE\n)\n    RETURNS TEXT LANGUAGE PLPGSQL STABLE AS\n$BODY$\nDECLARE\nBEGIN\n    IF NOT (time_value > (-9223372036854775808)::bigint AND\n\t   \t    time_value < 9223372036854775807::bigint) THEN\n        RETURN '';\n    END IF;\n    IF time_value IS NULL THEN\n        RETURN format('%L', NULL);\n    END IF;\n    CASE column_type\n      WHEN 'BIGINT'::regtype, 'INTEGER'::regtype, 'SMALLINT'::regtype THEN\n        RETURN format('%L', time_value); -- scale determined by user.\n      WHEN 'TIMESTAMP'::regtype, 'TIMESTAMPTZ'::regtype THEN\n        -- assume time_value is in microsec\n        RETURN format('%1$L', _timescaledb_functions.to_timestamp(time_value)); -- microseconds\n      WHEN 'DATE'::regtype THEN\n        RETURN format('%L', timezone('UTC',_timescaledb_functions.to_timestamp(time_value))::date);\n      ELSE\n        RETURN time_value;\n    END CASE;\nEND\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\n-- Convenience function to return approximate row count\n--\n-- relation - table or hypertable to get approximate row count for\n--\n-- Returns:\n-- Estimated number of rows according to catalog tables\nCREATE OR REPLACE FUNCTION @extschema@.approximate_row_count(relation REGCLASS)\nRETURNS BIGINT\nLANGUAGE PLPGSQL VOLATILE STRICT AS\n$BODY$\nDECLARE\n    v_mat_ht REGCLASS = NULL;\n    v_name NAME = NULL;\n    v_schema NAME = NULL;\n    v_hypertable_id INTEGER;\nBEGIN\n    -- Check if input relation is continuous aggregate view then\n    -- get the corresponding materialized hypertable and schema name\n    SELECT format('%I.%I', ht.schema_name, ht.table_name)::regclass INTO v_mat_ht\n      FROM pg_class c\n      JOIN pg_namespace n ON (n.OID = c.relnamespace)\n      JOIN _timescaledb_catalog.continuous_agg a ON (a.user_view_schema = n.nspname AND a.user_view_name = c.relname)\n      JOIN _timescaledb_catalog.hypertable ht ON (a.mat_hypertable_id = ht.id)\n      WHERE c.OID = relation;\n\n    IF FOUND THEN\n        relation = v_mat_ht;\n    END IF;\n\n    SELECT nspname, relname FROM pg_class c\n    INNER JOIN pg_namespace n ON (n.OID = c.relnamespace)\n    INTO v_schema, v_name\n    WHERE c.OID = relation;\n\n    -- for hypertables return the sum of the row counts of all chunks\n    SELECT id FROM _timescaledb_catalog.hypertable INTO v_hypertable_id WHERE table_name = v_name AND schema_name = v_schema;\n    IF FOUND THEN\n        RETURN (SELECT coalesce(sum(_timescaledb_functions.get_approx_row_count(format('%I.%I',schema_name,table_name))),0)\n          FROM _timescaledb_catalog.chunk\n          WHERE hypertable_id = v_hypertable_id);\n    END IF;\n\n\t\tIF EXISTS (SELECT FROM pg_inherits WHERE inhparent = relation) THEN\n\t\tRETURN (\n        SELECT _timescaledb_functions.get_approx_row_count(relation) + COALESCE(SUM(@extschema@.approximate_row_count(i.inhrelid)),0) FROM pg_inherits i\n        WHERE i.inhparent = relation\n     );\n    END IF;\n\n    -- Check for input relation is Plain RELATION\n    RETURN _timescaledb_functions.get_approx_row_count(relation);\nEND;\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.estimate_compressed_batch_size(relation REGCLASS)\nRETURNS FLOAT8\nAS '@MODULE_PATHNAME@', 'ts_estimate_compressed_batch_size' LANGUAGE C STRICT STABLE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.get_approx_row_count(relation REGCLASS)\nRETURNS BIGINT\nLANGUAGE PLPGSQL VOLATILE STRICT AS\n$BODY$\nDECLARE\n  v_schema NAME;\n  v_name NAME;\n  v_chunk_id INTEGER;\n  v_oid OID;\n  row_count BIGINT = 0;\nBEGIN\n  SELECT nspname, relname INTO v_schema, v_name FROM pg_class c JOIN pg_namespace n ON (n.OID = c.relnamespace) WHERE c.OID = relation;\n\n  -- we only need to check if the relation has a compressed chunk if it is a chunk\n  SELECT compress_relid FROM _timescaledb_catalog.compression_settings INTO v_oid WHERE relid = relation;\n\n  IF v_oid IS NOT NULL THEN\n    row_count := (SELECT CASE WHEN reltuples IS NULL THEN 0 WHEN reltuples < 0 THEN 0 ELSE reltuples * _timescaledb_functions.estimate_compressed_batch_size(oid) END FROM pg_class WHERE oid = v_oid);\n  END IF;\n\n  row_count := COALESCE((SELECT row_count + CASE WHEN reltuples < 0 OR relkind = 'p' THEN 0 ELSE reltuples END FROM pg_class WHERE oid = relation), 0);\n\n  RETURN row_count;\nEND\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\n-------- stats related to compression ------\nCREATE OR REPLACE VIEW _timescaledb_internal.compressed_chunk_stats AS\nSELECT\n    srcht.schema_name AS hypertable_schema,\n    srcht.table_name AS hypertable_name,\n    srcch.schema_name AS chunk_schema,\n    srcch.table_name AS chunk_name,\n    CASE WHEN srcch.status & 1 = 1 THEN\n        'Compressed'::text\n    ELSE\n        'Uncompressed'::text\n    END AS compression_status,\n    map.uncompressed_heap_size,\n    map.uncompressed_index_size,\n    map.uncompressed_toast_size,\n    map.uncompressed_heap_size + map.uncompressed_toast_size + map.uncompressed_index_size AS uncompressed_total_size,\n    map.compressed_heap_size,\n    map.compressed_index_size,\n    map.compressed_toast_size,\n    map.compressed_heap_size + map.compressed_toast_size + map.compressed_index_size AS compressed_total_size\nFROM\n    _timescaledb_catalog.hypertable AS srcht\n    JOIN _timescaledb_catalog.chunk AS srcch ON srcht.id = srcch.hypertable_id\n        AND srcht.compressed_hypertable_id IS NOT NULL\n    LEFT JOIN _timescaledb_catalog.compression_chunk_size map ON srcch.id = map.chunk_id;\n\nGRANT SELECT ON _timescaledb_internal.compressed_chunk_stats TO PUBLIC;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.compressed_chunk_local_stats(schema_name_in name, table_name_in name)\n    RETURNS TABLE (\n        chunk_schema name,\n        chunk_name name,\n        compression_status text,\n        before_compression_table_bytes bigint,\n        before_compression_index_bytes bigint,\n        before_compression_toast_bytes bigint,\n        before_compression_total_bytes bigint,\n        after_compression_table_bytes bigint,\n        after_compression_index_bytes bigint,\n        after_compression_toast_bytes bigint,\n        after_compression_total_bytes bigint)\n    LANGUAGE SQL\n    STABLE STRICT\n    AS\n$BODY$\n    SELECT\n        ch.chunk_schema,\n        ch.chunk_name,\n        ch.compression_status,\n        ch.uncompressed_heap_size,\n        ch.uncompressed_index_size,\n        ch.uncompressed_toast_size,\n        ch.uncompressed_total_size,\n        ch.compressed_heap_size,\n        ch.compressed_index_size,\n        ch.compressed_toast_size,\n        ch.compressed_total_size\n    FROM\n        _timescaledb_internal.compressed_chunk_stats ch\n    WHERE\n        ch.hypertable_schema = schema_name_in\n        AND ch.hypertable_name = table_name_in;\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\n-- Get per chunk compression statistics for a hypertable that has\n-- compression enabled\nCREATE OR REPLACE FUNCTION @extschema@.chunk_compression_stats (hypertable REGCLASS)\n    RETURNS TABLE (\n        chunk_schema name,\n        chunk_name name,\n        compression_status text,\n        before_compression_table_bytes bigint,\n        before_compression_index_bytes bigint,\n        before_compression_toast_bytes bigint,\n        before_compression_total_bytes bigint,\n        after_compression_table_bytes bigint,\n        after_compression_index_bytes bigint,\n        after_compression_toast_bytes bigint,\n        after_compression_total_bytes bigint,\n        node_name name)\n    LANGUAGE PLPGSQL\n    STABLE STRICT\n    AS $BODY$\nDECLARE\n    table_name name;\n    schema_name name;\nBEGIN\n    SELECT\n      relname, nspname\n    INTO\n\t    table_name, schema_name\n    FROM\n        pg_class c\n        INNER JOIN pg_namespace n ON (n.OID = c.relnamespace)\n        INNER JOIN _timescaledb_catalog.hypertable ht ON (ht.schema_name = n.nspname\n                AND ht.table_name = c.relname)\n    WHERE\n        c.OID = hypertable;\n\n    IF table_name IS NULL THEN\n\t    RETURN;\n\tEND IF;\n\n  RETURN QUERY\n  SELECT\n      *,\n      NULL::name\n  FROM\n      _timescaledb_functions.compressed_chunk_local_stats(schema_name, table_name);\nEND;\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\nCREATE OR REPLACE FUNCTION @extschema@.chunk_columnstore_stats (hypertable REGCLASS)\n    RETURNS TABLE (\n        chunk_schema name,\n        chunk_name name,\n        compression_status text,\n        before_compression_table_bytes bigint,\n        before_compression_index_bytes bigint,\n        before_compression_toast_bytes bigint,\n        before_compression_total_bytes bigint,\n        after_compression_table_bytes bigint,\n        after_compression_index_bytes bigint,\n        after_compression_toast_bytes bigint,\n        after_compression_total_bytes bigint,\n        node_name name)\n    LANGUAGE SQL\n    STABLE STRICT\n    AS 'SELECT * FROM @extschema@.chunk_compression_stats($1)'\n    SET search_path TO pg_catalog, pg_temp;\n\n-- Get compression statistics for a hypertable that has\n-- compression enabled\nCREATE OR REPLACE FUNCTION @extschema@.hypertable_compression_stats (hypertable REGCLASS)\n    RETURNS TABLE (\n        total_chunks bigint,\n        number_compressed_chunks bigint,\n        before_compression_table_bytes bigint,\n        before_compression_index_bytes bigint,\n        before_compression_toast_bytes bigint,\n        before_compression_total_bytes bigint,\n        after_compression_table_bytes bigint,\n        after_compression_index_bytes bigint,\n        after_compression_toast_bytes bigint,\n        after_compression_total_bytes bigint,\n        node_name name)\n    LANGUAGE SQL\n    STABLE STRICT\n    AS\n$BODY$\n\tSELECT\n        count(*)::bigint AS total_chunks,\n        (count(*) FILTER (WHERE ch.compression_status = 'Compressed'))::bigint AS number_compressed_chunks,\n        sum(ch.before_compression_table_bytes)::bigint AS before_compression_table_bytes,\n        sum(ch.before_compression_index_bytes)::bigint AS before_compression_index_bytes,\n        sum(ch.before_compression_toast_bytes)::bigint AS before_compression_toast_bytes,\n        sum(ch.before_compression_total_bytes)::bigint AS before_compression_total_bytes,\n        sum(ch.after_compression_table_bytes)::bigint AS after_compression_table_bytes,\n        sum(ch.after_compression_index_bytes)::bigint AS after_compression_index_bytes,\n        sum(ch.after_compression_toast_bytes)::bigint AS after_compression_toast_bytes,\n        sum(ch.after_compression_total_bytes)::bigint AS after_compression_total_bytes,\n        ch.node_name\n    FROM\n\t    @extschema@.chunk_compression_stats(hypertable) ch\n    GROUP BY\n        ch.node_name;\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\nCREATE OR REPLACE FUNCTION @extschema@.hypertable_columnstore_stats (hypertable REGCLASS)\n    RETURNS TABLE (\n        total_chunks bigint,\n        number_compressed_chunks bigint,\n        before_compression_table_bytes bigint,\n        before_compression_index_bytes bigint,\n        before_compression_toast_bytes bigint,\n        before_compression_total_bytes bigint,\n        after_compression_table_bytes bigint,\n        after_compression_index_bytes bigint,\n        after_compression_toast_bytes bigint,\n        after_compression_total_bytes bigint,\n        node_name name)\n    LANGUAGE SQL\n    STABLE STRICT\n    AS 'SELECT * FROM @extschema@.hypertable_compression_stats($1)'\n    SET search_path TO pg_catalog, pg_temp;\n\n-------------Get index size for hypertables -------\n\nCREATE OR REPLACE FUNCTION @extschema@.hypertable_index_size(\n    index_name              REGCLASS\n)\nRETURNS BIGINT\nLANGUAGE SQL VOLATILE STRICT AS\n$BODY$\n  SELECT\n  \tpg_relation_size(ht_i.indexrelid) + COALESCE(sum(pg_relation_size(ch_i.indexrelid)), 0)\n  FROM pg_index ht_i\n  LEFT JOIN pg_inherits ch on ch.inhparent = ht_i.indrelid\n  LEFT JOIN pg_index ch_i on ch_i.indrelid = ch.inhrelid and _timescaledb_functions.index_matches(ht_i.indexrelid, ch_i.indexrelid)\n  WHERE ht_i.indexrelid = index_name\n  GROUP BY ht_i.indexrelid;\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\n-------------End index size for hypertables -------\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.estimate_uncompressed_size(IN regclass, OUT tuples bigint, OUT relation_size bigint, OUT index_size bigint, OUT total_size bigint)\nAS $$\nDECLARE\n  v_compressed_chunk regclass;\n  v_uncompressed_chunk regclass;\n  v_index regclass;\n  v_fixed_column_size integer;\n  v_num_varlen_columns integer;\n  v_tuple_header integer;\n  v_tuple_data integer;\n  v_index_header integer;\n  v_index_size bigint;\n  v_columns integer;\n  v_varlen_query text:= '';\n  v_multiplier decimal:=1.15; -- multiplier to account for page header, fill factor and alignment padding\n  v_index_multiplier decimal:=1.25; -- multiplier to account for page header, fill factor and alignment padding\nBEGIN\n\n  v_compressed_chunk := $1;\n\n  SELECT relid INTO v_uncompressed_chunk FROM _timescaledb_catalog.compression_settings WHERE compress_relid = v_compressed_chunk;\n  IF NOT FOUND THEN\n    RETURN;\n  END IF;\n\n  SELECT\n    count(*), sum(attlen) FILTER(WHERE attlen > 0), count(*) FILTER(WHERE attlen = -1)\n  FROM pg_attribute\n    INTO v_columns, v_fixed_column_size, v_num_varlen_columns\n  WHERE attrelid = v_uncompressed_chunk AND attnum > 0 AND NOT attisdropped;\n\n  -- header size = MAXALIGN(Header + NullBitmap) + MAXALIGN(Data)\n  v_tuple_header := 23; -- Heap tuple header\n  v_tuple_header := v_tuple_header + ((v_columns + 7) / 8); -- Null bitmap size\n  v_tuple_header := v_tuple_header + 7 & ~7; -- align to 8 bytes\n\n  v_tuple_data := v_fixed_column_size; -- Fixed-length column sizes\n  v_tuple_data := v_tuple_data + 7 & ~7; -- align to 8 bytes\n\n  IF v_num_varlen_columns > 0 THEN\n\t  SELECT ' + (' || string_agg(format('sum(_timescaledb_functions.compressed_data_column_size(%I,NULL::%s))', attname, pg_catalog.format_type(atttypid, atttypmod)), ' + ') || ')' FROM pg_attribute INTO v_varlen_query WHERE attrelid = v_uncompressed_chunk AND attnum > 0 AND NOT attisdropped AND attlen = -1;\n  END IF;\n\n  EXECUTE format('SELECT sum(_ts_meta_count) FROM %s', v_compressed_chunk) INTO tuples;\n  -- we can optimize the following query if all columns are fixed size\n  EXECUTE format('SELECT ((%s * (%s + %s)) %s) * %s FROM %s', tuples, v_tuple_header, v_tuple_data, v_varlen_query, v_multiplier, v_compressed_chunk) INTO relation_size;\n\n  index_size := 0;\n  FOR v_index, v_varlen_query, v_columns IN\n    SELECT\n      i.indexrelid::regclass,\n      (SELECT ' + (' || string_agg(format('sum(_timescaledb_functions.compressed_data_column_size(%I,NULL::%s))', attname, pg_catalog.format_type(atttypid, atttypmod)), ' + ' ORDER BY attnum) || ')' FROM pg_attribute att WHERE att.attrelid=i.indrelid AND attnum =ANY(i.indkey)),\n      array_length(i.indkey,1) FROM pg_index i\n    WHERE i.indrelid = v_uncompressed_chunk\n  LOOP\n    v_index_header := 8; -- Index tuple header\n\n    -- v_compressed_chunk is a regclass, which will be properly escaped when cast to `text`\n    EXECUTE format('SELECT ((%s * %s) %s) * %s FROM %s', tuples, v_index_header, v_varlen_query, v_index_multiplier, v_compressed_chunk) INTO v_index_size;\n    index_size := index_size + v_index_size;\n  END LOOP;\n\n  total_size := relation_size + index_size;\nEND\n$$ LANGUAGE plpgsql SET search_path TO pg_catalog, pg_temp;\n\n"
  },
  {
    "path": "sql/sparse_index.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.bloom1_contains(_timescaledb_internal.bloom1, anyelement)\nRETURNS bool\nAS '@MODULE_PATHNAME@', 'ts_bloom1_contains'\nLANGUAGE C IMMUTABLE PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.bloom1_contains_any(_timescaledb_internal.bloom1, anyarray)\nRETURNS bool\nAS '@MODULE_PATHNAME@', 'ts_bloom1_contains_any'\nLANGUAGE C IMMUTABLE PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.jsonb_get_matching_index_entry(\n    config jsonb,\n    attr_name text,\n    target_type text\n) RETURNS jsonb AS $$\nDECLARE\n    elem jsonb;\n    attr_count int := 0;\nBEGIN\n  -- Return NULL if any input is NULL\n  IF config IS NULL OR attr_name IS NULL OR target_type IS NULL THEN\n    RETURN NULL;\n  END IF;\n\n  FOR elem IN SELECT * FROM jsonb_array_elements(config)\n  LOOP\n    IF elem->>'column' =  attr_name THEN\n      attr_count := attr_count + 1;\n\n      IF elem->>'type' = target_type THEN\n        IF attr_count > 2 THEN\n          RAISE EXCEPTION 'Found % sparse index entries for attribute \"%\"', attr_count, attr_name;\n        END IF;\n        RETURN elem;\n      END IF;\n    END IF;\n  END LOOP;\n\n  IF attr_count > 2 THEN\n    RAISE EXCEPTION 'Found % sparse index entries for attribute \"%\"', attr_count, attr_name;\n  END IF;\n\n  RETURN NULL;\nEND;\n$$ LANGUAGE PLPGSQL\nSET search_path TO pg_catalog, pg_temp;\n"
  },
  {
    "path": "sql/time_bucket.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- time_bucket returns the left edge of the bucket where ts falls into.\n-- Buckets span an interval of time equal to the bucket_width and are aligned with the epoch.\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket(bucket_width INTERVAL, ts TIMESTAMP) RETURNS TIMESTAMP\n\tAS '@MODULE_PATHNAME@', 'ts_timestamp_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;\n\n-- bucketing of timestamptz happens at UTC time\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket(bucket_width INTERVAL, ts TIMESTAMPTZ) RETURNS TIMESTAMPTZ\n\tAS '@MODULE_PATHNAME@', 'ts_timestamptz_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;\n\n--bucketing on date should not do any timezone conversion\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket(bucket_width INTERVAL, ts DATE) RETURNS DATE\n\tAS '@MODULE_PATHNAME@', 'ts_date_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;\n\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket(bucket_width INTERVAL, ts UUID) RETURNS TIMESTAMPTZ\n\tAS '@MODULE_PATHNAME@', 'ts_uuid_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;\n\n--bucketing with origin\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket(bucket_width INTERVAL, ts TIMESTAMP, origin TIMESTAMP) RETURNS TIMESTAMP\n\tAS '@MODULE_PATHNAME@', 'ts_timestamp_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket(bucket_width INTERVAL, ts TIMESTAMPTZ, origin TIMESTAMPTZ) RETURNS TIMESTAMPTZ\n\tAS '@MODULE_PATHNAME@', 'ts_timestamptz_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket(bucket_width INTERVAL, ts DATE, origin DATE) RETURNS DATE\n\tAS '@MODULE_PATHNAME@', 'ts_date_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket(bucket_width INTERVAL, ts UUID, origin TIMESTAMPTZ) RETURNS TIMESTAMPTZ\n  AS '@MODULE_PATHNAME@', 'ts_uuid_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;\n\n--bucketing with offset\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket(bucket_width INTERVAL, ts TIMESTAMP, \"offset\" INTERVAL) RETURNS TIMESTAMP\n\tAS '@MODULE_PATHNAME@', 'ts_timestamp_offset_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket(bucket_width INTERVAL, ts TIMESTAMPTZ, \"offset\" INTERVAL) RETURNS TIMESTAMPTZ\n\tAS '@MODULE_PATHNAME@', 'ts_timestamptz_offset_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket(bucket_width INTERVAL, ts DATE, \"offset\" INTERVAL) RETURNS DATE\n\tAS '@MODULE_PATHNAME@', 'ts_date_offset_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket(bucket_width INTERVAL, ts UUID, \"offset\" INTERVAL) RETURNS TIMESTAMPTZ\n\tAS '@MODULE_PATHNAME@', 'ts_uuid_offset_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;\n\n-- bucketing with timezone\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket(bucket_width INTERVAL, ts TIMESTAMPTZ, timezone TEXT, origin TIMESTAMPTZ DEFAULT NULL, \"offset\" INTERVAL DEFAULT NULL) RETURNS TIMESTAMPTZ\n\tAS '@MODULE_PATHNAME@', 'ts_timestamptz_timezone_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket(bucket_width INTERVAL, ts UUID, timezone TEXT, origin TIMESTAMPTZ DEFAULT NULL, \"offset\" INTERVAL DEFAULT NULL) RETURNS TIMESTAMPTZ\n\tAS '@MODULE_PATHNAME@', 'ts_uuid_timezone_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE;\n\n-- bucketing of int\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket(bucket_width SMALLINT, ts SMALLINT) RETURNS SMALLINT\n\tAS '@MODULE_PATHNAME@', 'ts_int16_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket(bucket_width INT, ts INT) RETURNS INT\n\tAS '@MODULE_PATHNAME@', 'ts_int32_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket(bucket_width BIGINT, ts BIGINT) RETURNS BIGINT\n\tAS '@MODULE_PATHNAME@', 'ts_int64_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;\n\n-- bucketing of int with offset\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket(bucket_width SMALLINT, ts SMALLINT, \"offset\" SMALLINT) RETURNS SMALLINT\n\tAS '@MODULE_PATHNAME@', 'ts_int16_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket(bucket_width INT, ts INT, \"offset\" INT) RETURNS INT\n\tAS '@MODULE_PATHNAME@', 'ts_int32_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;\nCREATE OR REPLACE FUNCTION @extschema@.time_bucket(bucket_width BIGINT, ts BIGINT, \"offset\" BIGINT) RETURNS BIGINT\n\tAS '@MODULE_PATHNAME@', 'ts_int64_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;\n\n-- This will align a range to a bucket size. It is similar to\n-- time_bucket(), but takes a range and produces a range that starts\n-- and ends at bucket boundaries.\nCREATE OR REPLACE FUNCTION _timescaledb_functions.align_to_bucket(width interval, rng anyrange)\nRETURNS anyrange AS\n$body$\nBEGIN\n  RETURN _timescaledb_functions.make_range_from_internal_time(\n         rng,\n         @extschema@.time_bucket(width, lower(rng)),\n         @extschema@.time_bucket(width, upper(rng) - '1 microsecond'::interval) + width\n  );\nEND\n$body$\nLANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE\nSET search_path TO pg_catalog, pg_temp;\n\n"
  },
  {
    "path": "sql/updates/2.10.0--2.9.3.sql",
    "content": "GRANT ALL ON _timescaledb_internal.job_errors TO PUBLIC;\n\nALTER EXTENSION timescaledb DROP VIEW timescaledb_information.job_errors;\n\nDROP VIEW timescaledb_information.job_errors;\n"
  },
  {
    "path": "sql/updates/2.10.1--2.10.0.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.10.1--2.10.2.sql",
    "content": "DROP FUNCTION _timescaledb_internal.ping_data_node(NAME);\n\n-- We only create stub here to not introduce shared library dependencies in update chains\n-- the proper function definition will be created at end of update script when all functions\n-- are recreated.\nCREATE OR REPLACE FUNCTION _timescaledb_internal.ping_data_node(node_name NAME, timeout INTERVAL = NULL) RETURNS BOOLEAN\nAS $$SELECT false;$$ LANGUAGE SQL VOLATILE;\n\n-- drop dependent views\nDROP VIEW IF EXISTS timescaledb_information.job_errors;\nDROP VIEW IF EXISTS timescaledb_information.job_stats;\nDROP VIEW IF EXISTS timescaledb_information.jobs;\nDROP VIEW IF EXISTS timescaledb_experimental.policies;\n\nALTER TABLE _timescaledb_config.bgw_job\n    ALTER COLUMN owner DROP DEFAULT,\n    ALTER COLUMN owner TYPE regrole USING pg_catalog.quote_ident(owner)::regrole,\n    ALTER COLUMN owner SET DEFAULT pg_catalog.quote_ident(current_role)::regrole;\nCREATE TABLE _timescaledb_config.bgw_job_tmp AS SELECT * FROM _timescaledb_config.bgw_job;\n\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_config.bgw_job;\nALTER EXTENSION timescaledb DROP SEQUENCE _timescaledb_config.bgw_job_id_seq;\nALTER TABLE _timescaledb_internal.bgw_job_stat\n      DROP CONSTRAINT IF EXISTS bgw_job_stat_job_id_fkey;\nALTER TABLE _timescaledb_internal.bgw_policy_chunk_stats\n      DROP CONSTRAINT IF EXISTS bgw_policy_chunk_stats_job_id_fkey;\nCREATE TABLE _timescaledb_internal.tmp_bgw_job_seq_value AS\n       SELECT last_value, is_called FROM _timescaledb_config.bgw_job_id_seq;\nDROP TABLE _timescaledb_config.bgw_job;\n\nCREATE SEQUENCE _timescaledb_config.bgw_job_id_seq MINVALUE 1000;\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_config.bgw_job_id_seq', '');\nSELECT pg_catalog.setval('_timescaledb_config.bgw_job_id_seq', last_value, is_called)\n  FROM _timescaledb_internal.tmp_bgw_job_seq_value;\nDROP TABLE _timescaledb_internal.tmp_bgw_job_seq_value;\n\nCREATE TABLE _timescaledb_config.bgw_job (\n  id integer NOT NULL DEFAULT nextval('_timescaledb_config.bgw_job_id_seq'),\n  application_name name NOT NULL,\n  schedule_interval interval NOT NULL,\n  max_runtime interval NOT NULL,\n  max_retries integer NOT NULL,\n  retry_period interval NOT NULL,\n  proc_schema name NOT NULL,\n  proc_name name NOT NULL,\n  owner regrole NOT NULL DEFAULT pg_catalog.quote_ident(current_role)::regrole,\n  scheduled bool NOT NULL DEFAULT TRUE,\n  fixed_schedule bool not null default true,\n  initial_start timestamptz,\n  hypertable_id integer,\n  config jsonb,\n  check_schema name,\n  check_name name,\n  timezone text,\n  CONSTRAINT bgw_job_pkey PRIMARY KEY (id),\n  CONSTRAINT bgw_job_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE\n);\n\nALTER SEQUENCE _timescaledb_config.bgw_job_id_seq OWNED BY _timescaledb_config.bgw_job.id;\nCREATE INDEX bgw_job_proc_hypertable_id_idx\n       ON _timescaledb_config.bgw_job(proc_schema,proc_name,hypertable_id);\nINSERT INTO _timescaledb_config.bgw_job\n       SELECT * FROM _timescaledb_config.bgw_job_tmp ORDER BY id;\nDROP TABLE _timescaledb_config.bgw_job_tmp;\nALTER TABLE _timescaledb_internal.bgw_job_stat\n      ADD CONSTRAINT bgw_job_stat_job_id_fkey\n      \t  FOREIGN KEY(job_id)\n\t  REFERENCES _timescaledb_config.bgw_job(id)\n\t  ON DELETE CASCADE;\nALTER TABLE _timescaledb_internal.bgw_policy_chunk_stats\n      ADD CONSTRAINT bgw_policy_chunk_stats_job_id_fkey\n      \t  FOREIGN KEY(job_id)\n\t  REFERENCES _timescaledb_config.bgw_job(id)\n\t  ON DELETE CASCADE;\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_config.bgw_job', 'WHERE id >= 1000');\nGRANT SELECT ON _timescaledb_config.bgw_job TO PUBLIC;\nGRANT SELECT ON _timescaledb_config.bgw_job_id_seq TO PUBLIC;\n"
  },
  {
    "path": "sql/updates/2.10.2--2.10.1.sql",
    "content": "DROP FUNCTION _timescaledb_internal.ping_data_node(NAME, INTERVAL);\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.ping_data_node(node_name NAME) RETURNS BOOLEAN\nAS '@MODULE_PATHNAME@', 'ts_data_node_ping' LANGUAGE C VOLATILE;\n\n-- drop dependent views\nDROP VIEW IF EXISTS timescaledb_information.job_errors;\nDROP VIEW IF EXISTS timescaledb_information.job_stats;\nDROP VIEW IF EXISTS timescaledb_information.jobs;\nDROP VIEW IF EXISTS timescaledb_experimental.policies;\n\nALTER TABLE _timescaledb_config.bgw_job\n    ALTER COLUMN owner DROP DEFAULT,\n    ALTER COLUMN owner TYPE name USING owner::name,\n    ALTER COLUMN owner SET DEFAULT current_role;\nCREATE TABLE _timescaledb_config.bgw_job_tmp AS SELECT * FROM _timescaledb_config.bgw_job;\n\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_config.bgw_job;\nALTER EXTENSION timescaledb DROP SEQUENCE _timescaledb_config.bgw_job_id_seq;\nALTER TABLE _timescaledb_internal.bgw_job_stat\n      DROP CONSTRAINT IF EXISTS bgw_job_stat_job_id_fkey;\nALTER TABLE _timescaledb_internal.bgw_policy_chunk_stats\n      DROP CONSTRAINT IF EXISTS bgw_policy_chunk_stats_job_id_fkey;\nCREATE TABLE _timescaledb_internal.tmp_bgw_job_seq_value AS\n       SELECT last_value, is_called FROM _timescaledb_config.bgw_job_id_seq;\nDROP TABLE _timescaledb_config.bgw_job;\n\nCREATE SEQUENCE _timescaledb_config.bgw_job_id_seq MINVALUE 1000;\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_config.bgw_job_id_seq', '');\nSELECT pg_catalog.setval('_timescaledb_config.bgw_job_id_seq', last_value, is_called)\n  FROM _timescaledb_internal.tmp_bgw_job_seq_value;\nDROP TABLE _timescaledb_internal.tmp_bgw_job_seq_value;\n\nCREATE TABLE _timescaledb_config.bgw_job (\n  id integer NOT NULL DEFAULT nextval('_timescaledb_config.bgw_job_id_seq'),\n  application_name name NOT NULL,\n  schedule_interval interval NOT NULL,\n  max_runtime interval NOT NULL,\n  max_retries integer NOT NULL,\n  retry_period interval NOT NULL,\n  proc_schema name NOT NULL,\n  proc_name name NOT NULL,\n  owner name NOT NULL DEFAULT current_role,\n  scheduled bool NOT NULL DEFAULT TRUE,\n  fixed_schedule bool not null default true,\n  initial_start timestamptz,\n  hypertable_id integer,\n  config jsonb,\n  check_schema name,\n  check_name name,\n  timezone text,\n  CONSTRAINT bgw_job_pkey PRIMARY KEY (id),\n  CONSTRAINT bgw_job_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE\n);\n\nALTER SEQUENCE _timescaledb_config.bgw_job_id_seq OWNED BY _timescaledb_config.bgw_job.id;\nCREATE INDEX bgw_job_proc_hypertable_id_idx\n       ON _timescaledb_config.bgw_job(proc_schema,proc_name,hypertable_id);\nINSERT INTO _timescaledb_config.bgw_job\n       SELECT * FROM _timescaledb_config.bgw_job_tmp ORDER BY id;\nDROP TABLE _timescaledb_config.bgw_job_tmp;\nALTER TABLE _timescaledb_internal.bgw_job_stat\n      ADD CONSTRAINT bgw_job_stat_job_id_fkey\n      \t  FOREIGN KEY(job_id)\n\t  REFERENCES _timescaledb_config.bgw_job(id)\n\t  ON DELETE CASCADE;\nALTER TABLE _timescaledb_internal.bgw_policy_chunk_stats\n      ADD CONSTRAINT bgw_policy_chunk_stats_job_id_fkey\n      \t  FOREIGN KEY(job_id)\n\t  REFERENCES _timescaledb_config.bgw_job(id)\n\t  ON DELETE CASCADE;\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_config.bgw_job', 'WHERE id >= 1000');\nGRANT SELECT ON _timescaledb_config.bgw_job TO PUBLIC;\nGRANT SELECT ON _timescaledb_config.bgw_job_id_seq TO PUBLIC;\n"
  },
  {
    "path": "sql/updates/2.10.2--2.10.3.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.10.3--2.10.2.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.10.3--2.11.0.sql",
    "content": "CREATE TABLE _timescaledb_catalog.continuous_aggs_watermark (\n  mat_hypertable_id integer NOT NULL,\n  watermark bigint NOT NULL,\n  -- table constraints\n  CONSTRAINT continuous_aggs_watermark_pkey PRIMARY KEY (mat_hypertable_id),\n  CONSTRAINT continuous_aggs_watermark_mat_hypertable_id_fkey FOREIGN KEY (mat_hypertable_id) REFERENCES _timescaledb_catalog.continuous_agg (mat_hypertable_id) ON DELETE CASCADE\n);\n\nGRANT SELECT ON _timescaledb_catalog.continuous_aggs_watermark TO PUBLIC;\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.continuous_aggs_watermark', '');\n\nCREATE FUNCTION _timescaledb_internal.cagg_watermark_materialized(hypertable_id INTEGER)\nRETURNS INT8 AS '@MODULE_PATHNAME@', 'ts_continuous_agg_watermark_materialized' LANGUAGE C STABLE STRICT PARALLEL SAFE;\nCREATE FUNCTION _timescaledb_internal.recompress_chunk_segmentwise(REGCLASS, BOOLEAN) RETURNS REGCLASS\nAS '@MODULE_PATHNAME@', 'ts_recompress_chunk_segmentwise' LANGUAGE C STRICT VOLATILE;\nCREATE FUNCTION _timescaledb_internal.get_compressed_chunk_index_for_recompression(REGCLASS) RETURNS REGCLASS\nAS '@MODULE_PATHNAME@', 'ts_get_compressed_chunk_index_for_recompression' LANGUAGE C STRICT VOLATILE;\n\nDROP FUNCTION _timescaledb_internal.dimension_is_finite;\nDROP FUNCTION _timescaledb_internal.dimension_slice_get_constraint_sql;\n\nCREATE SCHEMA _timescaledb_functions;\nGRANT USAGE ON SCHEMA _timescaledb_functions TO PUBLIC;\n\n-- migrate histogram support functions into _timescaledb_functions schema\nALTER FUNCTION _timescaledb_internal.hist_sfunc (state INTERNAL, val DOUBLE PRECISION, MIN DOUBLE PRECISION, MAX DOUBLE PRECISION, nbuckets INTEGER) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.hist_combinefunc(state1 INTERNAL, state2 INTERNAL) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.hist_serializefunc(INTERNAL) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.hist_deserializefunc(bytea, INTERNAL) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.hist_finalfunc(state INTERNAL, val DOUBLE PRECISION, MIN DOUBLE PRECISION, MAX DOUBLE PRECISION, nbuckets INTEGER) SET SCHEMA _timescaledb_functions;\n\n-- migrate first/last support functions into _timescaledb_functions schema\nALTER FUNCTION _timescaledb_internal.first_sfunc(internal, anyelement, \"any\") SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.first_combinefunc(internal, internal) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.last_sfunc(internal, anyelement, \"any\") SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.last_combinefunc(internal, internal) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.bookend_finalfunc(internal, anyelement, \"any\") SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.bookend_serializefunc(internal) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.bookend_deserializefunc(bytea, internal) SET SCHEMA _timescaledb_functions;\n\nDROP FUNCTION IF EXISTS _timescaledb_internal.is_main_table(regclass);\nDROP FUNCTION IF EXISTS _timescaledb_internal.is_main_table(name, name);\nDROP FUNCTION IF EXISTS _timescaledb_internal.hypertable_from_main_table(regclass);\nDROP FUNCTION IF EXISTS _timescaledb_internal.main_table_from_hypertable(integer);\nDROP FUNCTION IF EXISTS _timescaledb_internal.time_literal_sql(bigint, regtype);\n\nALTER FUNCTION _timescaledb_internal.compressed_data_in(CSTRING) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.compressed_data_out(_timescaledb_internal.compressed_data) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.compressed_data_send(_timescaledb_internal.compressed_data) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.compressed_data_recv(internal) SET SCHEMA _timescaledb_functions;\n\nALTER FUNCTION _timescaledb_internal.rxid_in(cstring) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.rxid_out(@extschema@.rxid) SET SCHEMA _timescaledb_functions;\n\nALTER TABLE _timescaledb_config.bgw_job\n    ALTER COLUMN owner SET DEFAULT pg_catalog.quote_ident(current_role)::regrole;\n\nALTER TABLE _timescaledb_catalog.continuous_agg_migrate_plan\n  ADD COLUMN user_view_definition TEXT,\n  DROP CONSTRAINT continuous_agg_migrate_plan_mat_hypertable_id_fkey;\n\n-- Log with events that will be sent out with the telemetry. The log\n-- will be flushed after it has been sent out. We do not save it to\n-- backups since it should not contain important data.\nCREATE TABLE _timescaledb_catalog.telemetry_event (\n       created timestamptz NOT NULL DEFAULT current_timestamp,\n       tag name NOT NULL,\n       body jsonb NOT NULL\n);\n\nGRANT SELECT ON _timescaledb_catalog.telemetry_event TO PUBLIC;\n"
  },
  {
    "path": "sql/updates/2.11.0--2.10.3.sql",
    "content": "DROP FUNCTION IF EXISTS _timescaledb_internal.get_approx_row_count(REGCLASS);\n\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.continuous_aggs_watermark;\n\nDROP TABLE IF EXISTS _timescaledb_catalog.continuous_aggs_watermark;\n\nDROP FUNCTION IF EXISTS _timescaledb_internal.cagg_watermark_materialized(hypertable_id INTEGER);\nDROP FUNCTION _timescaledb_internal.recompress_chunk_segmentwise(REGCLASS, BOOLEAN);\nDROP FUNCTION _timescaledb_internal.get_compressed_chunk_index_for_recompression(REGCLASS);\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.dimension_is_finite(\n    val      BIGINT\n)\n    RETURNS BOOLEAN LANGUAGE SQL IMMUTABLE PARALLEL SAFE AS\n$BODY$\n    --end values of bigint reserved for infinite\n    SELECT val > (-9223372036854775808)::bigint AND val < 9223372036854775807::bigint\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.dimension_slice_get_constraint_sql(\n    dimension_slice_id  INTEGER\n)\n    RETURNS TEXT LANGUAGE PLPGSQL VOLATILE AS\n$BODY$\nDECLARE\n    dimension_slice_row _timescaledb_catalog.dimension_slice;\n    dimension_row _timescaledb_catalog.dimension;\n    dimension_def TEXT;\n    dimtype REGTYPE;\n    parts TEXT[];\nBEGIN\n    SELECT * INTO STRICT dimension_slice_row\n    FROM _timescaledb_catalog.dimension_slice\n    WHERE id = dimension_slice_id;\n\n    SELECT * INTO STRICT dimension_row\n    FROM _timescaledb_catalog.dimension\n    WHERE id = dimension_slice_row.dimension_id;\n\n    IF dimension_row.partitioning_func_schema IS NOT NULL AND\n       dimension_row.partitioning_func IS NOT NULL THEN\n        SELECT prorettype INTO STRICT dimtype\n        FROM pg_catalog.pg_proc pro\n        WHERE pro.oid = format('%I.%I', dimension_row.partitioning_func_schema, dimension_row.partitioning_func)::regproc::oid;\n\n        dimension_def := format('%1$I.%2$I(%3$I)',\n             dimension_row.partitioning_func_schema,\n             dimension_row.partitioning_func,\n             dimension_row.column_name);\n    ELSE\n        dimension_def := format('%1$I', dimension_row.column_name);\n        dimtype := dimension_row.column_type;\n    END IF;\n\n    IF dimension_row.num_slices IS NOT NULL THEN\n\n        IF  _timescaledb_internal.dimension_is_finite(dimension_slice_row.range_start) THEN\n            parts = parts || format(' %1$s >= %2$L ', dimension_def, dimension_slice_row.range_start);\n        END IF;\n\n        IF _timescaledb_internal.dimension_is_finite(dimension_slice_row.range_end) THEN\n            parts = parts || format(' %1$s < %2$L ', dimension_def, dimension_slice_row.range_end);\n        END IF;\n\n        IF array_length(parts, 1) = 0 THEN\n            RETURN NULL;\n        END IF;\n        return array_to_string(parts, 'AND');\n    ELSE\n        -- only works with time for now\n        IF _timescaledb_internal.time_literal_sql(dimension_slice_row.range_start, dimtype) =\n           _timescaledb_internal.time_literal_sql(dimension_slice_row.range_end, dimtype) THEN\n            RAISE 'time-based constraints have the same start and end values for column \"%\": %',\n                    dimension_row.column_name,\n                    _timescaledb_internal.time_literal_sql(dimension_slice_row.range_end, dimtype);\n        END IF;\n\n        parts = ARRAY[]::text[];\n\n        IF _timescaledb_internal.dimension_is_finite(dimension_slice_row.range_start) THEN\n            parts = parts || format(' %1$s >= %2$s ',\n            dimension_def,\n            _timescaledb_internal.time_literal_sql(dimension_slice_row.range_start, dimtype));\n        END IF;\n\n        IF _timescaledb_internal.dimension_is_finite(dimension_slice_row.range_end) THEN\n            parts = parts || format(' %1$s < %2$s ',\n            dimension_def,\n            _timescaledb_internal.time_literal_sql(dimension_slice_row.range_end, dimtype));\n        END IF;\n\n        return array_to_string(parts, 'AND');\n    END IF;\nEND\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\nALTER FUNCTION _timescaledb_functions.hist_sfunc (state INTERNAL, val DOUBLE PRECISION, MIN DOUBLE PRECISION, MAX DOUBLE PRECISION, nbuckets INTEGER) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.hist_combinefunc(state1 INTERNAL, state2 INTERNAL) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.hist_serializefunc(INTERNAL) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.hist_deserializefunc(bytea, INTERNAL) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.hist_finalfunc(state INTERNAL, val DOUBLE PRECISION, MIN DOUBLE PRECISION, MAX DOUBLE PRECISION, nbuckets INTEGER) SET SCHEMA _timescaledb_internal;\n\nALTER FUNCTION _timescaledb_functions.first_sfunc(internal, anyelement, \"any\") SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.first_combinefunc(internal, internal) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.last_sfunc(internal, anyelement, \"any\") SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.last_combinefunc(internal, internal) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.bookend_finalfunc(internal, anyelement, \"any\") SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.bookend_serializefunc(internal) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.bookend_deserializefunc(bytea, internal) SET SCHEMA _timescaledb_internal;\n\nALTER FUNCTION _timescaledb_functions.compressed_data_in(CSTRING) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.compressed_data_out(_timescaledb_internal.compressed_data) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.compressed_data_send(_timescaledb_internal.compressed_data) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.compressed_data_recv(internal) SET SCHEMA _timescaledb_internal;\n\nALTER FUNCTION _timescaledb_functions.rxid_in(cstring) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.rxid_out(@extschema@.rxid) SET SCHEMA _timescaledb_internal;\n\nDROP SCHEMA _timescaledb_functions;\n\nCREATE FUNCTION _timescaledb_internal.is_main_table(\n    table_oid regclass\n)\n    RETURNS bool LANGUAGE SQL STABLE AS\n$BODY$\n    SELECT EXISTS(SELECT 1 FROM _timescaledb_catalog.hypertable WHERE table_name = relname AND schema_name = nspname)\n    FROM pg_class c\n    INNER JOIN pg_namespace n ON (n.OID = c.relnamespace)\n    WHERE c.OID = table_oid;\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\n-- Check if given table is a hypertable's main table\nCREATE FUNCTION _timescaledb_internal.is_main_table(\n    schema_name NAME,\n    table_name  NAME\n)\n    RETURNS BOOLEAN LANGUAGE SQL STABLE AS\n$BODY$\n     SELECT EXISTS(\n         SELECT 1 FROM _timescaledb_catalog.hypertable h\n         WHERE h.schema_name = is_main_table.schema_name AND\n               h.table_name = is_main_table.table_name\n     );\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\n-- Get a hypertable given its main table OID\nCREATE FUNCTION _timescaledb_internal.hypertable_from_main_table(\n    table_oid regclass\n)\n    RETURNS _timescaledb_catalog.hypertable LANGUAGE SQL STABLE AS\n$BODY$\n    SELECT h.*\n    FROM pg_class c\n    INNER JOIN pg_namespace n ON (n.OID = c.relnamespace)\n    INNER JOIN _timescaledb_catalog.hypertable h ON (h.table_name = c.relname AND h.schema_name = n.nspname)\n    WHERE c.OID = table_oid;\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\nCREATE FUNCTION _timescaledb_internal.main_table_from_hypertable(\n    hypertable_id int\n)\n    RETURNS regclass LANGUAGE SQL STABLE AS\n$BODY$\n    SELECT format('%I.%I',h.schema_name, h.table_name)::regclass\n    FROM _timescaledb_catalog.hypertable h\n    WHERE id = hypertable_id;\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\n-- Gets the sql code for representing the literal for the given time value (in the internal representation) as the column_type.\nCREATE FUNCTION _timescaledb_internal.time_literal_sql(\n    time_value      BIGINT,\n    column_type     REGTYPE\n)\n    RETURNS text LANGUAGE PLPGSQL STABLE AS\n$BODY$\nDECLARE\n    ret text;\nBEGIN\n    IF time_value IS NULL THEN\n        RETURN format('%L', NULL);\n    END IF;\n    CASE column_type\n      WHEN 'BIGINT'::regtype, 'INTEGER'::regtype, 'SMALLINT'::regtype THEN\n        RETURN format('%L', time_value); -- scale determined by user.\n      WHEN 'TIMESTAMP'::regtype THEN\n        --the time_value for timestamps w/o tz does not depend on local timezones. So perform at UTC.\n        RETURN format('TIMESTAMP %1$L', timezone('UTC',_timescaledb_internal.to_timestamp(time_value))); -- microseconds\n      WHEN 'TIMESTAMPTZ'::regtype THEN\n        -- assume time_value is in microsec\n        RETURN format('TIMESTAMPTZ %1$L', _timescaledb_internal.to_timestamp(time_value)); -- microseconds\n      WHEN 'DATE'::regtype THEN\n        RETURN format('%L', timezone('UTC',_timescaledb_internal.to_timestamp(time_value))::date);\n      ELSE\n         EXECUTE 'SELECT format(''%L'', $1::' || column_type::text || ')' into ret using time_value;\n         RETURN ret;\n    END CASE;\nEND\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\nALTER TABLE _timescaledb_config.bgw_job\n    ALTER COLUMN owner SET DEFAULT pg_catalog.quote_ident(current_role)::regrole;\n\n-- Rebuild the _timescaledb_catalog.continuous_agg_migrate_plan_step definition\nALTER TABLE _timescaledb_catalog.continuous_agg_migrate_plan_step\n    DROP CONSTRAINT continuous_agg_migrate_plan_step_mat_hypertable_id_fkey;\n\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.continuous_agg_migrate_plan;\nCREATE TABLE _timescaledb_catalog._tmp_continuous_agg_migrate_plan AS SELECT mat_hypertable_id, start_ts, end_ts FROM _timescaledb_catalog.continuous_agg_migrate_plan;\nDROP TABLE _timescaledb_catalog.continuous_agg_migrate_plan;\n\nCREATE TABLE _timescaledb_catalog.continuous_agg_migrate_plan (\n  mat_hypertable_id integer NOT NULL,\n  start_ts TIMESTAMPTZ NOT NULL DEFAULT pg_catalog.now(),\n  end_ts TIMESTAMPTZ,\n  -- table constraints\n  CONSTRAINT continuous_agg_migrate_plan_pkey PRIMARY KEY (mat_hypertable_id),\n  CONSTRAINT continuous_agg_migrate_plan_mat_hypertable_id_fkey FOREIGN KEY (mat_hypertable_id) REFERENCES _timescaledb_catalog.continuous_agg (mat_hypertable_id)\n);\n\nINSERT INTO _timescaledb_catalog.continuous_agg_migrate_plan SELECT * FROM _timescaledb_catalog._tmp_continuous_agg_migrate_plan;\nDROP TABLE _timescaledb_catalog._tmp_continuous_agg_migrate_plan;\n\nALTER TABLE _timescaledb_catalog.continuous_agg_migrate_plan_step\n    ADD CONSTRAINT continuous_agg_migrate_plan_step_mat_hypertable_id_fkey FOREIGN KEY (mat_hypertable_id) REFERENCES _timescaledb_catalog.continuous_agg_migrate_plan (mat_hypertable_id) ON DELETE CASCADE;\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.continuous_agg_migrate_plan', '');\n\nGRANT SELECT ON TABLE _timescaledb_catalog.continuous_agg_migrate_plan TO PUBLIC;\n\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.telemetry_event;\n\nDROP TABLE IF EXISTS _timescaledb_catalog.telemetry_event;\n"
  },
  {
    "path": "sql/updates/2.11.0--2.11.1.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.11.1--2.11.0.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.11.1--2.11.2.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.11.2--2.11.1.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.11.2--2.12.0.sql",
    "content": "DROP FUNCTION IF EXISTS @extschema@.alter_job(\n    INTEGER,\n    INTERVAL,\n    INTERVAL,\n    INTEGER,\n    INTERVAL,\n    BOOL,\n    JSONB,\n    TIMESTAMPTZ,\n    BOOL,\n    REGPROC\n);\n\nCREATE FUNCTION @extschema@.alter_job(\n    job_id INTEGER,\n    schedule_interval INTERVAL = NULL,\n    max_runtime INTERVAL = NULL,\n    max_retries INTEGER = NULL,\n    retry_period INTERVAL = NULL,\n    scheduled BOOL = NULL,\n    config JSONB = NULL,\n    next_start TIMESTAMPTZ = NULL,\n    if_exists BOOL = FALSE,\n    check_config REGPROC = NULL,\n    fixed_schedule BOOL = NULL,\n    initial_start TIMESTAMPTZ = NULL,\n    timezone TEXT DEFAULT NULL\n)\nRETURNS TABLE (job_id INTEGER, schedule_interval INTERVAL, max_runtime INTERVAL, max_retries INTEGER, retry_period INTERVAL, scheduled BOOL, config JSONB,\nnext_start TIMESTAMPTZ, check_config TEXT, fixed_schedule BOOL, initial_start TIMESTAMPTZ, timezone TEXT)\nAS '@MODULE_PATHNAME@', 'ts_job_alter'\nLANGUAGE C VOLATILE;\n\n-- when upgrading from old versions on PG13 this function might not be present\n-- since there is no ALTER FUNCTION IF EXISTS we have to work around it with a DO block\nDO $$\nDECLARE\n  foid regprocedure;\n  funcs text[] = '{\n    drop_dist_ht_invalidation_trigger,\n    subtract_integer_from_now,\n    get_approx_row_count,\n    chunk_status,\n    create_chunk,create_chunk_table,\n    freeze_chunk,unfreeze_chunk,drop_chunk,\n    attach_osm_table_chunk\n  }';\nBEGIN\n  FOR foid IN\n    SELECT oid FROM pg_proc WHERE proname = ANY(funcs) AND pronamespace = '_timescaledb_internal'::regnamespace\n  LOOP\n    EXECUTE format('ALTER FUNCTION %s SET SCHEMA _timescaledb_functions', foid);\n  END LOOP;\nEND;\n$$;\n\nDROP FUNCTION IF EXISTS _timescaledb_internal.get_time_type(integer);\n\nALTER FUNCTION _timescaledb_internal.insert_blocker() SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.continuous_agg_invalidation_trigger() SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.get_create_command(name) SET SCHEMA _timescaledb_functions;\n\nALTER FUNCTION _timescaledb_internal.to_unix_microseconds(timestamptz) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.to_timestamp(bigint) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.to_timestamp_without_timezone(bigint) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.to_date(bigint) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.to_interval(bigint) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.interval_to_usec(interval) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.time_to_internal(anyelement) SET SCHEMA _timescaledb_functions;\n\nALTER FUNCTION _timescaledb_internal.set_dist_id(uuid) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.set_peer_dist_id(uuid) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.validate_as_data_node() SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.show_connection_cache() SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.ping_data_node(name, interval) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.remote_txn_heal_data_node(oid) SET SCHEMA _timescaledb_functions;\n\nALTER FUNCTION _timescaledb_internal.relation_size(regclass) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.data_node_hypertable_info(name, name, name) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.data_node_chunk_info(name, name, name) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.hypertable_local_size(name, name) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.hypertable_remote_size(name, name) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.chunks_local_size(name, name) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.chunks_remote_size(name, name) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.range_value_to_pretty(bigint, regtype) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.data_node_compressed_chunk_stats(name, name, name) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.compressed_chunk_local_stats(name, name) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.compressed_chunk_remote_stats(name, name) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.indexes_local_size(name, name) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.data_node_index_size(name, name, name) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.indexes_remote_size(name, name, name) SET SCHEMA _timescaledb_functions;\n\nALTER FUNCTION _timescaledb_internal.generate_uuid() SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.get_git_commit() SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.get_os_info() SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.tsl_loaded() SET SCHEMA _timescaledb_functions;\n\nALTER FUNCTION _timescaledb_internal.calculate_chunk_interval(int, bigint, bigint) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.chunks_in(record, integer[]) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.chunk_id_from_relid(oid) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.show_chunk(regclass) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.get_chunk_relstats(regclass) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.get_chunk_colstats(regclass) SET SCHEMA _timescaledb_functions;\n\nUPDATE _timescaledb_catalog.hypertable SET chunk_sizing_func_schema = '_timescaledb_functions' WHERE chunk_sizing_func_schema = '_timescaledb_internal' AND chunk_sizing_func_name = 'calculate_chunk_interval';\n\nDO $$\nDECLARE\n  foid regprocedure;\n  kind text;\n  funcs text[] = '{\n    policy_compression_check,policy_compression_execute,policy_compression,\n    policy_job_error_retention_check,policy_job_error_retention,\n    policy_recompression,\n    policy_refresh_continuous_aggregate_check,policy_refresh_continuous_aggregate,\n    policy_reorder_check,policy_reorder,policy_retention_check,policy_retention,\n\n    cagg_watermark, cagg_watermark_materialized,\n    cagg_migrate_plan_exists, cagg_migrate_pre_validation, cagg_migrate_create_plan, cagg_migrate_execute_create_new_cagg,\n    cagg_migrate_execute_disable_policies, cagg_migrate_execute_enable_policies, cagg_migrate_execute_copy_policies,\n    cagg_migrate_execute_refresh_new_cagg, cagg_migrate_execute_copy_data, cagg_migrate_execute_override_cagg,\n    cagg_migrate_execute_drop_old_cagg, cagg_migrate_execute_plan,\n\n    finalize_agg,\n\n    hypertable_invalidation_log_delete, invalidation_cagg_log_add_entry, invalidation_hyper_log_add_entry,\n    invalidation_process_cagg_log, invalidation_process_hypertable_log, materialization_invalidation_log_delete,\n\n    alter_job_set_hypertable_id,\n\n    set_chunk_default_data_node,\n\n    create_compressed_chunk, get_compressed_chunk_index_for_recompression, recompress_chunk_segmentwise,\n    chunk_drop_replica, chunk_index_clone, chunk_index_replace, create_chunk_replica_table, drop_stale_chunks,\n\t\tchunk_constraint_add_table_constraint, hypertable_constraint_add_table_fk_constraint,\n    health, wait_subscription_sync\n  }';\nBEGIN\n  FOR foid, kind IN\n    SELECT oid,\n    CASE\n      WHEN prokind = 'f' THEN 'FUNCTION'\n      WHEN prokind = 'a' THEN 'AGGREGATE'\n      ELSE 'PROCEDURE'\n    END\n    FROM pg_proc WHERE proname = ANY(funcs) AND pronamespace = '_timescaledb_internal'::regnamespace\n  LOOP\n    EXECUTE format('ALTER %s %s SET SCHEMA _timescaledb_functions', kind, foid);\n  END LOOP;\nEND;\n$$;\n\nUPDATE _timescaledb_config.bgw_job SET proc_schema = '_timescaledb_functions' WHERE proc_schema = '_timescaledb_internal';\nUPDATE _timescaledb_config.bgw_job SET check_schema = '_timescaledb_functions' WHERE check_schema = '_timescaledb_internal';\n\nALTER FUNCTION _timescaledb_internal.start_background_workers() SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.stop_background_workers() SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.restart_background_workers() SET SCHEMA _timescaledb_functions;\n\nALTER FUNCTION _timescaledb_internal.process_ddl_event() SET SCHEMA _timescaledb_functions;\n\nALTER FUNCTION _timescaledb_internal.get_partition_for_key(val anyelement) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.get_partition_hash(val anyelement) SET SCHEMA _timescaledb_functions;\n\nUPDATE _timescaledb_catalog.dimension SET partitioning_func_schema = '_timescaledb_functions' WHERE partitioning_func_schema = '_timescaledb_internal' AND partitioning_func IN ('get_partition_for_key','get_partition_hash');\n\nALTER FUNCTION _timescaledb_internal.finalize_agg_ffunc(internal,text,name,name,name[],bytea,anyelement) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.finalize_agg_sfunc(internal,text,name,name,name[],bytea,anyelement) SET SCHEMA _timescaledb_functions;\nALTER FUNCTION _timescaledb_internal.partialize_agg(anyelement) SET SCHEMA _timescaledb_functions;\n\n-- Fix osm chunk ranges\nUPDATE _timescaledb_catalog.dimension_slice ds\n  SET range_start = 9223372036854775806\nFROM _timescaledb_catalog.chunk_constraint cc\nINNER JOIN _timescaledb_catalog.chunk c ON c.id = cc.chunk_id AND c.osm_chunk\nWHERE cc.dimension_slice_id = ds.id AND ds.range_start <> 9223372036854775806;\n\n-- OSM support - table must be rebuilt to ensure consistent attribute numbers\n-- we cannot just ALTER TABLE .. ADD COLUMN\nALTER TABLE _timescaledb_config.bgw_job\n    DROP CONSTRAINT bgw_job_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.chunk\n    DROP CONSTRAINT chunk_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.chunk_index\n    DROP CONSTRAINT chunk_index_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.continuous_agg\n    DROP CONSTRAINT continuous_agg_mat_hypertable_id_fkey,\n    DROP CONSTRAINT continuous_agg_raw_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.continuous_aggs_bucket_function\n    DROP CONSTRAINT continuous_aggs_bucket_function_mat_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.continuous_aggs_invalidation_threshold\n    DROP CONSTRAINT continuous_aggs_invalidation_threshold_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.dimension\n    DROP CONSTRAINT dimension_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.hypertable\n    DROP CONSTRAINT hypertable_compressed_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.hypertable_compression\n    DROP CONSTRAINT hypertable_compression_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.hypertable_data_node\n    DROP CONSTRAINT hypertable_data_node_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.tablespace\n    DROP CONSTRAINT tablespace_hypertable_id_fkey;\n\nDROP VIEW IF EXISTS timescaledb_information.hypertables;\nDROP VIEW IF EXISTS timescaledb_information.job_stats;\nDROP VIEW IF EXISTS timescaledb_information.jobs;\nDROP VIEW IF EXISTS timescaledb_information.continuous_aggregates;\nDROP VIEW IF EXISTS timescaledb_information.chunks;\nDROP VIEW IF EXISTS timescaledb_information.dimensions;\nDROP VIEW IF EXISTS timescaledb_information.compression_settings;\nDROP VIEW IF EXISTS _timescaledb_internal.hypertable_chunk_local_size;\nDROP VIEW IF EXISTS _timescaledb_internal.compressed_chunk_stats;\nDROP VIEW IF EXISTS timescaledb_experimental.chunk_replication_status;\nDROP VIEW IF EXISTS timescaledb_experimental.policies;\n\n-- recreate table\nCREATE TABLE _timescaledb_catalog.hypertable_tmp AS SELECT * FROM _timescaledb_catalog.hypertable;\nCREATE TABLE _timescaledb_catalog.tmp_hypertable_seq_value AS SELECT last_value, is_called FROM _timescaledb_catalog.hypertable_id_seq;\n\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.hypertable;\nALTER EXTENSION timescaledb DROP SEQUENCE _timescaledb_catalog.hypertable_id_seq;\n\nSET timescaledb.restoring = on; -- must disable the hooks otherwise we can't do anything without the table _timescaledb_catalog.hypertable\n\nDROP TABLE _timescaledb_catalog.hypertable;\n\nCREATE SEQUENCE _timescaledb_catalog.hypertable_id_seq MINVALUE 1;\nSELECT setval('_timescaledb_catalog.hypertable_id_seq', last_value, is_called) FROM _timescaledb_catalog.tmp_hypertable_seq_value;\nDROP TABLE _timescaledb_catalog.tmp_hypertable_seq_value;\n\nCREATE TABLE _timescaledb_catalog.hypertable (\n    id INTEGER PRIMARY KEY NOT NULL DEFAULT nextval('_timescaledb_catalog.hypertable_id_seq'),\n    schema_name name NOT NULL,\n    table_name name NOT NULL,\n    associated_schema_name name NOT NULL,\n    associated_table_prefix name NOT NULL,\n    num_dimensions smallint NOT NULL,\n    chunk_sizing_func_schema name NOT NULL,\n    chunk_sizing_func_name name NOT NULL,\n    chunk_target_size bigint NOT NULL, -- size in bytes\n    compression_state smallint NOT NULL DEFAULT 0,\n    compressed_hypertable_id integer,\n    replication_factor smallint NULL,\n    status integer NOT NULL DEFAULT 0\n);\n\nSET timescaledb.restoring = off;\n\nINSERT INTO _timescaledb_catalog.hypertable (\n    id,\n    schema_name,\n    table_name,\n    associated_schema_name,\n    associated_table_prefix,\n    num_dimensions,\n    chunk_sizing_func_schema,\n    chunk_sizing_func_name,\n    chunk_target_size,\n    compression_state,\n    compressed_hypertable_id,\n    replication_factor\n)\nSELECT\n    id,\n    schema_name,\n    table_name,\n    associated_schema_name,\n    associated_table_prefix,\n    num_dimensions,\n    chunk_sizing_func_schema,\n    chunk_sizing_func_name,\n    chunk_target_size,\n    compression_state,\n    compressed_hypertable_id,\n    replication_factor\nFROM\n    _timescaledb_catalog.hypertable_tmp\nORDER BY id;\n\nUPDATE _timescaledb_catalog.hypertable h\nSET status = 3\nWHERE EXISTS (\n  SELECT FROM _timescaledb_catalog.chunk c WHERE c.osm_chunk AND c.hypertable_id = h.id\n);\n\nALTER SEQUENCE _timescaledb_catalog.hypertable_id_seq OWNED BY _timescaledb_catalog.hypertable.id;\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.hypertable', 'WHERE id >= 1');\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.hypertable_id_seq', '');\n\nGRANT SELECT ON _timescaledb_catalog.hypertable TO PUBLIC;\nGRANT SELECT ON _timescaledb_catalog.hypertable_id_seq TO PUBLIC;\n\nDROP TABLE _timescaledb_catalog.hypertable_tmp;\n-- now add any constraints\nALTER TABLE _timescaledb_catalog.hypertable\n    ADD CONSTRAINT hypertable_associated_schema_name_associated_table_prefix_key UNIQUE (associated_schema_name, associated_table_prefix),\n    ADD CONSTRAINT hypertable_table_name_schema_name_key UNIQUE (table_name, schema_name),\n    ADD CONSTRAINT hypertable_schema_name_check CHECK (schema_name != '_timescaledb_catalog'),\n    ADD CONSTRAINT hypertable_dim_compress_check CHECK (num_dimensions > 0 OR compression_state = 2),\n    ADD CONSTRAINT hypertable_chunk_target_size_check CHECK (chunk_target_size >= 0),\n    ADD CONSTRAINT hypertable_compress_check CHECK ( (compression_state = 0 OR compression_state = 1 )  OR (compression_state = 2 AND compressed_hypertable_id IS NULL)),\n    ADD CONSTRAINT hypertable_replication_factor_check CHECK (replication_factor > 0 OR replication_factor = -1),\n    ADD CONSTRAINT hypertable_compressed_hypertable_id_fkey FOREIGN KEY (compressed_hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id);\n\nGRANT SELECT ON TABLE _timescaledb_catalog.hypertable TO PUBLIC;\n\n-- 3. reestablish constraints on other tables\nALTER TABLE _timescaledb_config.bgw_job\n    ADD CONSTRAINT bgw_job_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.chunk\n    ADD CONSTRAINT chunk_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id);\nALTER TABLE _timescaledb_catalog.chunk_index\n    ADD CONSTRAINT chunk_index_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.continuous_agg\n    ADD CONSTRAINT continuous_agg_mat_hypertable_id_fkey FOREIGN KEY (mat_hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE,\n    ADD CONSTRAINT continuous_agg_raw_hypertable_id_fkey FOREIGN KEY (raw_hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.continuous_aggs_bucket_function\n    ADD CONSTRAINT continuous_aggs_bucket_function_mat_hypertable_id_fkey FOREIGN KEY (mat_hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.continuous_aggs_invalidation_threshold\n    ADD CONSTRAINT continuous_aggs_invalidation_threshold_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.dimension\n    ADD CONSTRAINT dimension_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.hypertable_compression\n    ADD CONSTRAINT hypertable_compression_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.hypertable_data_node\n    ADD CONSTRAINT hypertable_data_node_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id);\nALTER TABLE _timescaledb_catalog.tablespace\n    ADD CONSTRAINT tablespace_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\n"
  },
  {
    "path": "sql/updates/2.12.0--2.11.2.sql",
    "content": "\n-- remove compatibility wrapper functions\n-- this needs to happen before we move the actual functions back into _timescaledb_internal\nDROP FUNCTION _timescaledb_internal.alter_job_set_hypertable_id(integer,regclass);\nDROP FUNCTION _timescaledb_internal.attach_osm_table_chunk(regclass,regclass);\nDROP FUNCTION _timescaledb_internal.cagg_migrate_plan_exists(integer);\nDROP FUNCTION _timescaledb_internal.cagg_migrate_pre_validation(text,text,text);\nDROP FUNCTION _timescaledb_internal.cagg_watermark(integer);\nDROP FUNCTION _timescaledb_internal.cagg_watermark_materialized(integer);\nDROP FUNCTION _timescaledb_internal.calculate_chunk_interval(integer,bigint,bigint);\nDROP FUNCTION _timescaledb_internal.chunk_constraint_add_table_constraint(_timescaledb_catalog.chunk_constraint);\nDROP FUNCTION _timescaledb_internal.chunk_drop_replica(regclass,name);\nDROP FUNCTION _timescaledb_internal.chunk_id_from_relid(oid);\nDROP FUNCTION _timescaledb_internal.chunk_index_clone(oid);\nDROP FUNCTION _timescaledb_internal.chunk_index_replace(oid,oid);\nDROP FUNCTION _timescaledb_internal.chunk_status(regclass);\nDROP FUNCTION _timescaledb_internal.chunks_in(record,integer[]);\nDROP FUNCTION _timescaledb_internal.chunks_local_size(name,name);\nDROP FUNCTION _timescaledb_internal.chunks_remote_size(name,name);\nDROP FUNCTION _timescaledb_internal.compressed_chunk_local_stats(name,name);\nDROP FUNCTION _timescaledb_internal.compressed_chunk_remote_stats(name,name);\nDROP FUNCTION _timescaledb_internal.continuous_agg_invalidation_trigger();\nDROP FUNCTION _timescaledb_internal.create_chunk(regclass,jsonb,name,name,regclass);\nDROP FUNCTION _timescaledb_internal.create_chunk_replica_table(regclass,name);\nDROP FUNCTION _timescaledb_internal.create_chunk_table(regclass,jsonb,name,name);\nDROP FUNCTION _timescaledb_internal.create_compressed_chunk(regclass,regclass,bigint,bigint,bigint,bigint,bigint,bigint,bigint,bigint);\nDROP FUNCTION _timescaledb_internal.data_node_chunk_info(name,name,name);\nDROP FUNCTION _timescaledb_internal.data_node_compressed_chunk_stats(name,name,name);\nDROP FUNCTION _timescaledb_internal.data_node_hypertable_info(name,name,name);\nDROP FUNCTION _timescaledb_internal.data_node_index_size(name,name,name);\nDROP FUNCTION _timescaledb_internal.drop_chunk(regclass);\nDROP FUNCTION _timescaledb_internal.drop_dist_ht_invalidation_trigger(integer);\nDROP FUNCTION _timescaledb_internal.drop_stale_chunks(name,integer[]);\nDROP AGGREGATE _timescaledb_internal.finalize_agg(agg_name TEXT, inner_agg_collation_schema NAME, inner_agg_collation_name NAME, inner_agg_input_types NAME[][], inner_agg_serialized_state BYTEA, return_type_dummy_val anyelement);\nDROP FUNCTION _timescaledb_internal.finalize_agg_ffunc(internal, text, name, name, name[][], bytea, anyelement);\nDROP FUNCTION _timescaledb_internal.finalize_agg_sfunc(internal, text, name, name, name[][], bytea, anyelement);\nDROP FUNCTION _timescaledb_internal.freeze_chunk(regclass);\nDROP FUNCTION _timescaledb_internal.generate_uuid();\nDROP FUNCTION _timescaledb_internal.get_approx_row_count(regclass);\nDROP FUNCTION _timescaledb_internal.get_chunk_colstats(regclass);\nDROP FUNCTION _timescaledb_internal.get_chunk_relstats(regclass);\nDROP FUNCTION _timescaledb_internal.get_compressed_chunk_index_for_recompression(regclass);\nDROP FUNCTION _timescaledb_internal.get_create_command(name);\nDROP FUNCTION _timescaledb_internal.get_git_commit();\nDROP FUNCTION _timescaledb_internal.get_os_info();\nDROP FUNCTION _timescaledb_internal.get_partition_for_key(anyelement);\nDROP FUNCTION _timescaledb_internal.get_partition_hash(anyelement);\nDROP FUNCTION _timescaledb_internal.health();\nDROP FUNCTION _timescaledb_internal.hypertable_constraint_add_table_fk_constraint(name,name,name,integer);\nDROP FUNCTION _timescaledb_internal.hypertable_invalidation_log_delete(integer);\nDROP FUNCTION _timescaledb_internal.hypertable_local_size(name,name);\nDROP FUNCTION _timescaledb_internal.hypertable_remote_size(name,name);\nDROP FUNCTION _timescaledb_internal.indexes_local_size(name,name);\nDROP FUNCTION _timescaledb_internal.indexes_remote_size(name,name,name);\nDROP FUNCTION _timescaledb_internal.insert_blocker();\nDROP FUNCTION _timescaledb_internal.interval_to_usec(interval);\nDROP FUNCTION _timescaledb_internal.invalidation_cagg_log_add_entry(integer,bigint,bigint);\nDROP FUNCTION _timescaledb_internal.invalidation_hyper_log_add_entry(integer,bigint,bigint);\nDROP FUNCTION _timescaledb_internal.invalidation_process_cagg_log(integer,integer,regtype,bigint,bigint,integer[],bigint[],bigint[]);\nDROP FUNCTION _timescaledb_internal.invalidation_process_cagg_log(integer,integer,regtype,bigint,bigint,integer[],bigint[],bigint[],text[]);\nDROP FUNCTION _timescaledb_internal.invalidation_process_hypertable_log(integer,integer,regtype,integer[],bigint[],bigint[]);\nDROP FUNCTION _timescaledb_internal.invalidation_process_hypertable_log(integer,integer,regtype,integer[],bigint[],bigint[],text[]);\nDROP FUNCTION _timescaledb_internal.materialization_invalidation_log_delete(integer);\nDROP FUNCTION _timescaledb_internal.partialize_agg(anyelement);\nDROP FUNCTION _timescaledb_internal.ping_data_node(name,interval);\nDROP FUNCTION _timescaledb_internal.policy_compression_check(jsonb);\nDROP FUNCTION _timescaledb_internal.policy_job_error_retention(integer,jsonb);\nDROP FUNCTION _timescaledb_internal.policy_job_error_retention_check(jsonb);\nDROP FUNCTION _timescaledb_internal.policy_refresh_continuous_aggregate_check(jsonb);\nDROP FUNCTION _timescaledb_internal.policy_reorder_check(jsonb);\nDROP FUNCTION _timescaledb_internal.policy_retention_check(jsonb);\nDROP FUNCTION _timescaledb_internal.process_ddl_event();\nDROP FUNCTION _timescaledb_internal.range_value_to_pretty(bigint,regtype);\nDROP FUNCTION _timescaledb_internal.recompress_chunk_segmentwise(regclass,boolean);\nDROP FUNCTION _timescaledb_internal.relation_size(regclass);\nDROP FUNCTION _timescaledb_internal.remote_txn_heal_data_node(oid);\nDROP FUNCTION _timescaledb_internal.set_chunk_default_data_node(regclass,name);\nDROP FUNCTION _timescaledb_internal.set_dist_id(uuid);\nDROP FUNCTION _timescaledb_internal.set_peer_dist_id(uuid);\nDROP FUNCTION _timescaledb_internal.show_chunk(regclass);\nDROP FUNCTION _timescaledb_internal.show_connection_cache();\nDROP FUNCTION _timescaledb_internal.start_background_workers();\nDROP FUNCTION _timescaledb_internal.stop_background_workers();\nDROP FUNCTION _timescaledb_internal.subtract_integer_from_now(regclass,bigint);\nDROP FUNCTION _timescaledb_internal.time_to_internal(anyelement);\nDROP FUNCTION _timescaledb_internal.to_date(bigint);\nDROP FUNCTION _timescaledb_internal.to_interval(bigint);\nDROP FUNCTION _timescaledb_internal.to_timestamp(bigint);\nDROP FUNCTION _timescaledb_internal.to_timestamp_without_timezone(bigint);\nDROP FUNCTION _timescaledb_internal.to_unix_microseconds(timestamp with time zone);\nDROP FUNCTION _timescaledb_internal.tsl_loaded();\nDROP FUNCTION _timescaledb_internal.unfreeze_chunk(regclass);\nDROP FUNCTION _timescaledb_internal.validate_as_data_node();\nDROP PROCEDURE _timescaledb_internal.cagg_migrate_create_plan(_timescaledb_catalog.continuous_agg,text,boolean,boolean);\nDROP PROCEDURE _timescaledb_internal.cagg_migrate_execute_copy_data(_timescaledb_catalog.continuous_agg,_timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE _timescaledb_internal.cagg_migrate_execute_copy_policies(_timescaledb_catalog.continuous_agg,_timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE _timescaledb_internal.cagg_migrate_execute_create_new_cagg(_timescaledb_catalog.continuous_agg,_timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE _timescaledb_internal.cagg_migrate_execute_disable_policies(_timescaledb_catalog.continuous_agg,_timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE _timescaledb_internal.cagg_migrate_execute_drop_old_cagg(_timescaledb_catalog.continuous_agg,_timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE _timescaledb_internal.cagg_migrate_execute_enable_policies(_timescaledb_catalog.continuous_agg,_timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE _timescaledb_internal.cagg_migrate_execute_override_cagg(_timescaledb_catalog.continuous_agg,_timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE _timescaledb_internal.cagg_migrate_execute_plan(_timescaledb_catalog.continuous_agg);\nDROP PROCEDURE _timescaledb_internal.cagg_migrate_execute_refresh_new_cagg(_timescaledb_catalog.continuous_agg,_timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE _timescaledb_internal.policy_compression(integer,jsonb);\nDROP PROCEDURE _timescaledb_internal.policy_compression_execute(integer,integer,anyelement,integer,boolean,boolean);\nDROP PROCEDURE _timescaledb_internal.policy_recompression(integer,jsonb);\nDROP PROCEDURE _timescaledb_internal.policy_refresh_continuous_aggregate(integer,jsonb);\nDROP PROCEDURE _timescaledb_internal.policy_reorder(integer,jsonb);\nDROP PROCEDURE _timescaledb_internal.policy_retention(integer,jsonb);\nDROP PROCEDURE _timescaledb_internal.wait_subscription_sync(name,name,integer,numeric);\n\nDROP FUNCTION IF EXISTS @extschema@.alter_job(\n    INTEGER,\n    INTERVAL,\n    INTERVAL,\n    INTEGER,\n    INTERVAL,\n    BOOL,\n    JSONB,\n    TIMESTAMPTZ,\n    BOOL,\n    REGPROC,\n    BOOL,\n    TIMESTAMPTZ,\n    TEXT\n);\n\nCREATE FUNCTION @extschema@.alter_job(\n    job_id INTEGER,\n    schedule_interval INTERVAL = NULL,\n    max_runtime INTERVAL = NULL,\n    max_retries INTEGER = NULL,\n    retry_period INTERVAL = NULL,\n    scheduled BOOL = NULL,\n    config JSONB = NULL,\n    next_start TIMESTAMPTZ = NULL,\n    if_exists BOOL = FALSE,\n    check_config REGPROC = NULL\n)\nRETURNS TABLE (job_id INTEGER, schedule_interval INTERVAL, max_runtime INTERVAL, max_retries INTEGER, retry_period INTERVAL, scheduled BOOL, config JSONB,\nnext_start TIMESTAMPTZ, check_config TEXT)\nAS '@MODULE_PATHNAME@', 'ts_job_alter'\nLANGUAGE C VOLATILE;\n\nALTER FUNCTION _timescaledb_functions.insert_blocker() SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.continuous_agg_invalidation_trigger() SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.drop_dist_ht_invalidation_trigger(integer) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.get_create_command(name) SET SCHEMA _timescaledb_internal;\n\nALTER FUNCTION _timescaledb_functions.to_unix_microseconds(timestamptz) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.to_timestamp(bigint) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.to_timestamp_without_timezone(bigint) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.to_date(bigint) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.to_interval(bigint) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.interval_to_usec(interval) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.time_to_internal(anyelement) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.subtract_integer_from_now(regclass, bigint) SET SCHEMA _timescaledb_internal;\n\nALTER FUNCTION _timescaledb_functions.set_dist_id(uuid) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.set_peer_dist_id(uuid) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.validate_as_data_node() SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.show_connection_cache() SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.ping_data_node(name, interval) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.remote_txn_heal_data_node(oid) SET SCHEMA _timescaledb_internal;\n\nALTER FUNCTION _timescaledb_functions.relation_size(regclass) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.data_node_hypertable_info(name, name, name) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.data_node_chunk_info(name, name, name) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.hypertable_local_size(name, name) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.hypertable_remote_size(name, name) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.chunks_local_size(name, name) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.chunks_remote_size(name, name) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.range_value_to_pretty(bigint, regtype) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.get_approx_row_count(regclass) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.data_node_compressed_chunk_stats(name, name, name) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.compressed_chunk_local_stats(name, name) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.compressed_chunk_remote_stats(name, name) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.indexes_local_size(name, name) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.data_node_index_size(name, name, name) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.indexes_remote_size(name, name, name) SET SCHEMA _timescaledb_internal;\n\nALTER FUNCTION _timescaledb_functions.generate_uuid() SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.get_git_commit() SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.get_os_info() SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.tsl_loaded() SET SCHEMA _timescaledb_internal;\n\nALTER FUNCTION _timescaledb_functions.calculate_chunk_interval(int, bigint, bigint) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.chunk_status(regclass) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.chunks_in(record, integer[]) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.chunk_id_from_relid(oid) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.show_chunk(regclass) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.create_chunk(regclass, jsonb, name, name, regclass) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.set_chunk_default_data_node(regclass, name) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.get_chunk_relstats(regclass) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.get_chunk_colstats(regclass) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.create_chunk_table(regclass, jsonb, name, name) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.freeze_chunk(regclass) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.unfreeze_chunk(regclass) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.drop_chunk(regclass) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.attach_osm_table_chunk(regclass, regclass) SET SCHEMA _timescaledb_internal;\n\nUPDATE _timescaledb_catalog.hypertable SET chunk_sizing_func_schema = '_timescaledb_internal' WHERE chunk_sizing_func_schema = '_timescaledb_functions' AND chunk_sizing_func_name = 'calculate_chunk_interval';\n\nALTER FUNCTION _timescaledb_functions.policy_compression_check(jsonb) SET SCHEMA _timescaledb_internal;\nALTER PROCEDURE _timescaledb_functions.policy_compression_execute(integer,integer,anyelement,integer,boolean,boolean) SET SCHEMA _timescaledb_internal;\nALTER PROCEDURE _timescaledb_functions.policy_compression(integer,jsonb) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.policy_job_error_retention_check(jsonb) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.policy_job_error_retention(integer,jsonb) SET SCHEMA _timescaledb_internal;\nALTER PROCEDURE _timescaledb_functions.policy_recompression(integer,jsonb) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.policy_refresh_continuous_aggregate_check(jsonb) SET SCHEMA _timescaledb_internal;\nALTER PROCEDURE _timescaledb_functions.policy_refresh_continuous_aggregate(integer,jsonb) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.policy_reorder_check(jsonb) SET SCHEMA _timescaledb_internal;\nALTER PROCEDURE _timescaledb_functions.policy_reorder(integer,jsonb) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.policy_retention_check(jsonb) SET SCHEMA _timescaledb_internal;\nALTER PROCEDURE _timescaledb_functions.policy_retention(integer,jsonb) SET SCHEMA _timescaledb_internal;\n\nUPDATE _timescaledb_config.bgw_job SET proc_schema = '_timescaledb_internal' WHERE proc_schema = '_timescaledb_functions';\nUPDATE _timescaledb_config.bgw_job SET check_schema = '_timescaledb_internal' WHERE check_schema = '_timescaledb_functions';\n\nALTER FUNCTION _timescaledb_functions.cagg_migrate_plan_exists(INTEGER) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.cagg_migrate_pre_validation(TEXT, TEXT, TEXT) SET SCHEMA _timescaledb_internal;\nALTER PROCEDURE _timescaledb_functions.cagg_migrate_create_plan(_timescaledb_catalog.continuous_agg, TEXT, BOOLEAN, BOOLEAN) SET SCHEMA _timescaledb_internal;\nALTER PROCEDURE _timescaledb_functions.cagg_migrate_execute_create_new_cagg(_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step) SET SCHEMA _timescaledb_internal;\nALTER PROCEDURE _timescaledb_functions.cagg_migrate_execute_disable_policies(_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step) SET SCHEMA _timescaledb_internal;\nALTER PROCEDURE _timescaledb_functions.cagg_migrate_execute_enable_policies(_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step) SET SCHEMA _timescaledb_internal;\nALTER PROCEDURE _timescaledb_functions.cagg_migrate_execute_copy_policies(_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step) SET SCHEMA _timescaledb_internal;\nALTER PROCEDURE _timescaledb_functions.cagg_migrate_execute_refresh_new_cagg(_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step) SET SCHEMA _timescaledb_internal;\nALTER PROCEDURE _timescaledb_functions.cagg_migrate_execute_copy_data(_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step) SET SCHEMA _timescaledb_internal;\nALTER PROCEDURE _timescaledb_functions.cagg_migrate_execute_override_cagg(_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step) SET SCHEMA _timescaledb_internal;\nALTER PROCEDURE _timescaledb_functions.cagg_migrate_execute_drop_old_cagg(_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step) SET SCHEMA _timescaledb_internal;\nALTER PROCEDURE _timescaledb_functions.cagg_migrate_execute_plan(_timescaledb_catalog.continuous_agg) SET SCHEMA _timescaledb_internal;\n\n-- pre-update of previous version will have created an additional copy of restart_background_workers\n-- since restart_background_workers was handled differently from other functions in previous versions\nDROP FUNCTION _timescaledb_internal.restart_background_workers();\nALTER FUNCTION _timescaledb_functions.start_background_workers() SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.stop_background_workers() SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.restart_background_workers() SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.alter_job_set_hypertable_id(integer,regclass) SET SCHEMA _timescaledb_internal;\n\nALTER FUNCTION _timescaledb_functions.cagg_watermark(integer) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.cagg_watermark_materialized(integer) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.hypertable_invalidation_log_delete(integer) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.invalidation_cagg_log_add_entry(integer,bigint,bigint) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.invalidation_hyper_log_add_entry(integer,bigint,bigint) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.invalidation_process_cagg_log(integer,integer,regtype,bigint,bigint,integer[],bigint[],bigint[]) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.invalidation_process_cagg_log(integer,integer,regtype,bigint,bigint,integer[],bigint[],bigint[],text[]) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.invalidation_process_hypertable_log(integer,integer,regtype,integer[],bigint[],bigint[]) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.invalidation_process_hypertable_log(integer,integer,regtype,integer[],bigint[],bigint[],text[]) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.materialization_invalidation_log_delete(integer) SET SCHEMA _timescaledb_internal;\n\nALTER FUNCTION _timescaledb_functions.chunk_constraint_add_table_constraint(_timescaledb_catalog.chunk_constraint) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.chunk_drop_replica(regclass,name) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.chunk_index_clone(oid) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.chunk_index_replace(oid,oid) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.create_chunk_replica_table(regclass,name) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.create_compressed_chunk(regclass,regclass,bigint,bigint,bigint,bigint,bigint,bigint,bigint,bigint) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.drop_stale_chunks(name,integer[]) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.get_compressed_chunk_index_for_recompression(regclass) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.health() SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.hypertable_constraint_add_table_fk_constraint(name,name,name,integer) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.process_ddl_event() SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.recompress_chunk_segmentwise(regclass,boolean) SET SCHEMA _timescaledb_internal;\nALTER PROCEDURE _timescaledb_functions.wait_subscription_sync(name,name,integer,numeric) SET SCHEMA _timescaledb_internal;\n\nALTER FUNCTION _timescaledb_functions.get_partition_for_key(val anyelement) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.get_partition_hash(val anyelement) SET SCHEMA _timescaledb_internal;\n\nUPDATE _timescaledb_catalog.dimension SET partitioning_func_schema = '_timescaledb_internal' WHERE partitioning_func_schema = '_timescaledb_functions' AND partitioning_func IN ('get_partition_for_key','get_partition_hash');\n\nALTER FUNCTION _timescaledb_functions.finalize_agg_ffunc(internal,text,name,name,name[],bytea,anyelement) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.finalize_agg_sfunc(internal,text,name,name,name[],bytea,anyelement) SET SCHEMA _timescaledb_internal;\nALTER FUNCTION _timescaledb_functions.partialize_agg(anyelement) SET SCHEMA _timescaledb_internal;\nALTER AGGREGATE _timescaledb_functions.finalize_agg(text,name,name,name[][],bytea,anyelement) SET SCHEMA _timescaledb_internal;\n\nDROP FUNCTION _timescaledb_functions.hypertable_osm_range_update(regclass, anyelement, anyelement, boolean);\n\n-- recreate the _timescaledb_catalog.hypertable table as new field was added\n-- 1. drop CONSTRAINTS from other tables referencing the existing one\nALTER TABLE _timescaledb_config.bgw_job\n    DROP CONSTRAINT bgw_job_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.chunk\n    DROP CONSTRAINT chunk_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.chunk_index\n    DROP CONSTRAINT chunk_index_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.continuous_agg\n    DROP CONSTRAINT continuous_agg_mat_hypertable_id_fkey,\n    DROP CONSTRAINT continuous_agg_raw_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.continuous_aggs_bucket_function\n    DROP CONSTRAINT continuous_aggs_bucket_function_mat_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.continuous_aggs_invalidation_threshold\n    DROP CONSTRAINT continuous_aggs_invalidation_threshold_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.dimension\n    DROP CONSTRAINT dimension_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.hypertable\n    DROP CONSTRAINT hypertable_compressed_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.hypertable_compression\n    DROP CONSTRAINT hypertable_compression_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.hypertable_data_node\n    DROP CONSTRAINT hypertable_data_node_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.tablespace\n    DROP CONSTRAINT tablespace_hypertable_id_fkey;\n\n-- drop dependent views\nALTER EXTENSION timescaledb DROP VIEW timescaledb_information.hypertables;\nALTER EXTENSION timescaledb DROP VIEW timescaledb_information.job_stats;\nALTER EXTENSION timescaledb DROP VIEW timescaledb_information.jobs;\nALTER EXTENSION timescaledb DROP VIEW timescaledb_information.continuous_aggregates;\nALTER EXTENSION timescaledb DROP VIEW timescaledb_information.chunks;\nALTER EXTENSION timescaledb DROP VIEW timescaledb_information.dimensions;\nALTER EXTENSION timescaledb DROP VIEW timescaledb_information.compression_settings;\nALTER EXTENSION timescaledb DROP VIEW  _timescaledb_internal.hypertable_chunk_local_size;\nALTER EXTENSION timescaledb DROP VIEW _timescaledb_internal.compressed_chunk_stats;\nALTER EXTENSION timescaledb DROP VIEW timescaledb_experimental.chunk_replication_status;\nALTER EXTENSION timescaledb DROP VIEW timescaledb_experimental.policies;\n\nDROP VIEW timescaledb_information.hypertables;\nDROP VIEW timescaledb_information.job_stats;\nDROP VIEW timescaledb_information.jobs;\nDROP VIEW timescaledb_information.continuous_aggregates;\nDROP VIEW timescaledb_information.chunks;\nDROP VIEW timescaledb_information.dimensions;\nDROP VIEW timescaledb_information.compression_settings;\nDROP VIEW _timescaledb_internal.hypertable_chunk_local_size;\nDROP VIEW _timescaledb_internal.compressed_chunk_stats;\nDROP VIEW timescaledb_experimental.chunk_replication_status;\nDROP VIEW timescaledb_experimental.policies;\n\n-- recreate table\nCREATE TABLE _timescaledb_catalog.hypertable_tmp AS SELECT * FROM _timescaledb_catalog.hypertable;\nCREATE TABLE _timescaledb_catalog.tmp_hypertable_seq_value AS SELECT last_value, is_called FROM _timescaledb_catalog.hypertable_id_seq;\n\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.hypertable;\nALTER EXTENSION timescaledb DROP SEQUENCE _timescaledb_catalog.hypertable_id_seq;\n\nSET timescaledb.restoring = on; -- must disable the hooks otherwise we can't do anything without the table _timescaledb_catalog.hypertable\n\nDROP TABLE _timescaledb_catalog.hypertable;\n\nCREATE SEQUENCE _timescaledb_catalog.hypertable_id_seq MINVALUE 1;\nSELECT setval('_timescaledb_catalog.hypertable_id_seq', last_value, is_called) FROM _timescaledb_catalog.tmp_hypertable_seq_value;\nDROP TABLE _timescaledb_catalog.tmp_hypertable_seq_value;\n\nCREATE TABLE _timescaledb_catalog.hypertable (\n    id INTEGER PRIMARY KEY NOT NULL DEFAULT nextval('_timescaledb_catalog.hypertable_id_seq'),\n    schema_name name NOT NULL,\n    table_name name NOT NULL,\n    associated_schema_name name NOT NULL,\n    associated_table_prefix name NOT NULL,\n    num_dimensions smallint NOT NULL,\n    chunk_sizing_func_schema name NOT NULL,\n    chunk_sizing_func_name name NOT NULL,\n    chunk_target_size bigint NOT NULL, -- size in bytes\n    compression_state smallint NOT NULL DEFAULT 0,\n    compressed_hypertable_id integer,\n    replication_factor smallint NULL\n);\n\nSET timescaledb.restoring = off;\n\nINSERT INTO _timescaledb_catalog.hypertable (\n    id,\n    schema_name,\n    table_name,\n    associated_schema_name,\n    associated_table_prefix,\n    num_dimensions,\n    chunk_sizing_func_schema,\n    chunk_sizing_func_name,\n    chunk_target_size,\n    compression_state,\n    compressed_hypertable_id,\n    replication_factor\n)\nSELECT\n    id,\n    schema_name,\n    table_name,\n    associated_schema_name,\n    associated_table_prefix,\n    num_dimensions,\n    chunk_sizing_func_schema,\n    chunk_sizing_func_name,\n    chunk_target_size,\n    compression_state,\n    compressed_hypertable_id,\n    replication_factor\nFROM\n    _timescaledb_catalog.hypertable_tmp\nORDER BY id;\n\nALTER SEQUENCE _timescaledb_catalog.hypertable_id_seq OWNED BY _timescaledb_catalog.hypertable.id;\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.hypertable', 'WHERE id >= 1');\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.hypertable_id_seq', '');\n\nGRANT SELECT ON _timescaledb_catalog.hypertable TO PUBLIC;\nGRANT SELECT ON _timescaledb_catalog.hypertable_id_seq TO PUBLIC;\n\nDROP TABLE _timescaledb_catalog.hypertable_tmp;\n-- now add any constraints\nALTER TABLE _timescaledb_catalog.hypertable\n    -- ADD CONSTRAINT hypertable_pkey PRIMARY KEY (id),\n    ADD CONSTRAINT hypertable_associated_schema_name_associated_table_prefix_key UNIQUE (associated_schema_name, associated_table_prefix),\n    ADD CONSTRAINT hypertable_table_name_schema_name_key UNIQUE (table_name, schema_name),\n    ADD CONSTRAINT hypertable_schema_name_check CHECK (schema_name != '_timescaledb_catalog'),\n    -- internal compressed hypertables have compression state = 2\n    ADD CONSTRAINT hypertable_dim_compress_check CHECK (num_dimensions > 0 OR compression_state = 2),\n    ADD CONSTRAINT hypertable_chunk_target_size_check CHECK (chunk_target_size >= 0),\n    ADD CONSTRAINT hypertable_compress_check CHECK ( (compression_state = 0 OR compression_state = 1 )  OR (compression_state = 2 AND compressed_hypertable_id IS NULL)),\n    -- replication_factor NULL: regular hypertable\n    -- replication_factor > 0: distributed hypertable on access node\n    -- replication_factor -1: distributed hypertable on data node, which is part of a larger table\n    ADD CONSTRAINT hypertable_replication_factor_check CHECK (replication_factor > 0 OR replication_factor = -1),\n    ADD CONSTRAINT hypertable_compressed_hypertable_id_fkey FOREIGN KEY (compressed_hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id);\n\nGRANT SELECT ON TABLE _timescaledb_catalog.hypertable TO PUBLIC;\n\n-- 3. reestablish constraints on other tables\nALTER TABLE _timescaledb_config.bgw_job\n    ADD CONSTRAINT bgw_job_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.chunk\n    ADD CONSTRAINT chunk_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id);\nALTER TABLE _timescaledb_catalog.chunk_index\n    ADD CONSTRAINT chunk_index_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.continuous_agg\n    ADD CONSTRAINT continuous_agg_mat_hypertable_id_fkey FOREIGN KEY (mat_hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE,\n    ADD CONSTRAINT continuous_agg_raw_hypertable_id_fkey FOREIGN KEY (raw_hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.continuous_aggs_bucket_function\n    ADD CONSTRAINT continuous_aggs_bucket_function_mat_hypertable_id_fkey FOREIGN KEY (mat_hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.continuous_aggs_invalidation_threshold\n    ADD CONSTRAINT continuous_aggs_invalidation_threshold_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.dimension\n    ADD CONSTRAINT dimension_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.hypertable_compression\n    ADD CONSTRAINT hypertable_compression_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.hypertable_data_node\n    ADD CONSTRAINT hypertable_data_node_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id);\nALTER TABLE _timescaledb_catalog.tablespace\n    ADD CONSTRAINT tablespace_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\n"
  },
  {
    "path": "sql/updates/2.12.0-2.12.1.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.12.1--2.12.0.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.12.1--2.12.2.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.12.2--2.12.1.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.12.2--2.13.0.sql",
    "content": "CREATE TYPE _timescaledb_internal.dimension_info;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.dimension_info_in(cstring)\n    RETURNS _timescaledb_internal.dimension_info\n    LANGUAGE C STRICT IMMUTABLE\n    AS '@MODULE_PATHNAME@', 'ts_dimension_info_in';\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.dimension_info_out(_timescaledb_internal.dimension_info)\n    RETURNS cstring\n    LANGUAGE C STRICT IMMUTABLE\n    AS '@MODULE_PATHNAME@', 'ts_dimension_info_out';\n\nCREATE TYPE _timescaledb_internal.dimension_info (\n    INPUT = _timescaledb_functions.dimension_info_in,\n    OUTPUT = _timescaledb_functions.dimension_info_out,\n    INTERNALLENGTH = VARIABLE\n);\n\nCREATE FUNCTION @extschema@.create_hypertable(\n    relation                REGCLASS,\n    dimension               _timescaledb_internal.dimension_info,\n    create_default_indexes  BOOLEAN = TRUE,\n    if_not_exists           BOOLEAN = FALSE,\n    migrate_data            BOOLEAN = FALSE\n) RETURNS TABLE(hypertable_id INT, created BOOL) AS '@MODULE_PATHNAME@', 'ts_hypertable_create_general' LANGUAGE C VOLATILE;\n\nCREATE FUNCTION @extschema@.add_dimension(\n    hypertable              REGCLASS,\n    dimension               _timescaledb_internal.dimension_info,\n    if_not_exists           BOOLEAN = FALSE\n) RETURNS TABLE(dimension_id INT, created BOOL)\nAS '@MODULE_PATHNAME@', 'ts_dimension_add_general' LANGUAGE C VOLATILE;\n\nCREATE FUNCTION @extschema@.set_partitioning_interval(\n    hypertable              REGCLASS,\n    partition_interval      ANYELEMENT,\n    dimension_name          NAME = NULL\n) RETURNS VOID AS '@MODULE_PATHNAME@', 'ts_dimension_set_interval' LANGUAGE C VOLATILE;\n\nCREATE FUNCTION @extschema@.by_hash(column_name NAME, number_partitions INTEGER,\n                                    partition_func regproc = NULL)\n    RETURNS _timescaledb_internal.dimension_info LANGUAGE C\n    AS '@MODULE_PATHNAME@', 'ts_hash_dimension';\n\nCREATE FUNCTION @extschema@.by_range(column_name NAME,\n                                     partition_interval ANYELEMENT = NULL::bigint,\n                                     partition_func regproc = NULL)\n    RETURNS _timescaledb_internal.dimension_info LANGUAGE C\n    AS '@MODULE_PATHNAME@', 'ts_range_dimension';\n\n--\n-- Rebuild the catalog table `_timescaledb_catalog.chunk` to\n-- add new column `creation_time`\n--\nCREATE TABLE _timescaledb_internal.chunk_tmp\nAS SELECT * from _timescaledb_catalog.chunk;\n\nCREATE TABLE _timescaledb_internal.tmp_chunk_seq_value AS\nSELECT last_value, is_called FROM _timescaledb_catalog.chunk_id_seq;\n\n--drop foreign keys on chunk table\nALTER TABLE _timescaledb_catalog.chunk_constraint DROP CONSTRAINT\nchunk_constraint_chunk_id_fkey;\nALTER TABLE _timescaledb_catalog.chunk_index DROP CONSTRAINT\nchunk_index_chunk_id_fkey;\nALTER TABLE _timescaledb_catalog.chunk_data_node DROP CONSTRAINT\nchunk_data_node_chunk_id_fkey;\nALTER TABLE _timescaledb_internal.bgw_policy_chunk_stats DROP CONSTRAINT\nbgw_policy_chunk_stats_chunk_id_fkey;\nALTER TABLE _timescaledb_catalog.compression_chunk_size DROP CONSTRAINT\ncompression_chunk_size_chunk_id_fkey;\nALTER TABLE _timescaledb_catalog.compression_chunk_size DROP CONSTRAINT\ncompression_chunk_size_compressed_chunk_id_fkey;\nALTER TABLE _timescaledb_catalog.chunk_copy_operation DROP CONSTRAINT\nchunk_copy_operation_chunk_id_fkey;\n\n--drop dependent views\nDROP VIEW IF EXISTS timescaledb_information.hypertables;\nDROP VIEW IF EXISTS timescaledb_information.chunks;\nDROP VIEW IF EXISTS _timescaledb_internal.hypertable_chunk_local_size;\nDROP VIEW IF EXISTS _timescaledb_internal.compressed_chunk_stats;\nDROP VIEW IF EXISTS timescaledb_experimental.chunk_replication_status;\n\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.chunk;\nALTER EXTENSION timescaledb DROP SEQUENCE _timescaledb_catalog.chunk_id_seq;\nDROP TABLE _timescaledb_catalog.chunk;\n\nCREATE SEQUENCE _timescaledb_catalog.chunk_id_seq MINVALUE 1;\n\n-- now create table without self referential foreign key\nCREATE TABLE _timescaledb_catalog.chunk (\n  id integer NOT NULL DEFAULT nextval('_timescaledb_catalog.chunk_id_seq'),\n  hypertable_id int NOT NULL,\n  schema_name name NOT NULL,\n  table_name name NOT NULL,\n  compressed_chunk_id integer ,\n  dropped boolean NOT NULL DEFAULT FALSE,\n  status integer NOT NULL DEFAULT 0,\n  osm_chunk boolean NOT NULL DEFAULT FALSE,\n  creation_time timestamptz,\n  -- table constraints\n  CONSTRAINT chunk_pkey PRIMARY KEY (id),\n  CONSTRAINT chunk_schema_name_table_name_key UNIQUE (schema_name, table_name)\n);\n\nINSERT INTO _timescaledb_catalog.chunk\n( id, hypertable_id, schema_name, table_name,\n  compressed_chunk_id, dropped, status, osm_chunk)\nSELECT id, hypertable_id, schema_name, table_name,\n  compressed_chunk_id, dropped, status, osm_chunk\nFROM _timescaledb_internal.chunk_tmp;\n\n-- update creation_time for chunks\nUPDATE\n    _timescaledb_catalog.chunk c\nSET\n    creation_time = (pg_catalog.pg_stat_file(pg_catalog.pg_relation_filepath(r.oid))).modification\nFROM\n    pg_class r, pg_namespace n\nWHERE\n    r.relnamespace = n.oid\n    AND r.relname = c.table_name\n    AND n.nspname = c.schema_name\n    AND r.relkind = 'r'\n    AND c.dropped IS FALSE;\n\n-- Make sure that there are no record with empty creation time\nUPDATE _timescaledb_catalog.chunk SET creation_time = now() WHERE creation_time IS NULL;\n\n--add indexes to the chunk table\nCREATE INDEX chunk_hypertable_id_idx ON _timescaledb_catalog.chunk (hypertable_id);\nCREATE INDEX chunk_compressed_chunk_id_idx ON _timescaledb_catalog.chunk (compressed_chunk_id);\nCREATE INDEX chunk_osm_chunk_idx ON _timescaledb_catalog.chunk (osm_chunk, hypertable_id);\nCREATE INDEX chunk_hypertable_id_creation_time_idx ON _timescaledb_catalog.chunk(hypertable_id, creation_time);\n\nALTER SEQUENCE _timescaledb_catalog.chunk_id_seq OWNED BY _timescaledb_catalog.chunk.id;\nSELECT setval('_timescaledb_catalog.chunk_id_seq', last_value, is_called) FROM _timescaledb_internal.tmp_chunk_seq_value;\n\n-- add self referential foreign key\nALTER TABLE _timescaledb_catalog.chunk ADD CONSTRAINT chunk_compressed_chunk_id_fkey FOREIGN KEY ( compressed_chunk_id )\n REFERENCES _timescaledb_catalog.chunk( id );\n\n--add foreign key constraint\nALTER TABLE _timescaledb_catalog.chunk\n      ADD CONSTRAINT chunk_hypertable_id_fkey\n      FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.chunk', '');\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.chunk_id_seq', '');\n\n-- Add non-null constraint\nALTER TABLE _timescaledb_catalog.chunk\n  ALTER COLUMN creation_time SET NOT NULL;\n\n--add the foreign key constraints\nALTER TABLE _timescaledb_catalog.chunk_constraint ADD CONSTRAINT\nchunk_constraint_chunk_id_fkey FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk(id);\nALTER TABLE _timescaledb_catalog.chunk_index ADD CONSTRAINT\nchunk_index_chunk_id_fkey FOREIGN KEY (chunk_id)\nREFERENCES _timescaledb_catalog.chunk(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.chunk_data_node ADD CONSTRAINT\nchunk_data_node_chunk_id_fkey FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk(id);\nALTER TABLE _timescaledb_internal.bgw_policy_chunk_stats ADD CONSTRAINT\nbgw_policy_chunk_stats_chunk_id_fkey FOREIGN KEY (chunk_id)\nREFERENCES _timescaledb_catalog.chunk(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.compression_chunk_size ADD CONSTRAINT\ncompression_chunk_size_chunk_id_fkey FOREIGN KEY (chunk_id)\nREFERENCES _timescaledb_catalog.chunk(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.compression_chunk_size ADD CONSTRAINT\ncompression_chunk_size_compressed_chunk_id_fkey FOREIGN KEY (compressed_chunk_id)\nREFERENCES _timescaledb_catalog.chunk(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.chunk_copy_operation ADD CONSTRAINT\nchunk_copy_operation_chunk_id_fkey FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk (id) ON DELETE CASCADE;\n\n--cleanup\nDROP TABLE _timescaledb_internal.chunk_tmp;\nDROP TABLE _timescaledb_internal.tmp_chunk_seq_value;\n\nGRANT SELECT ON _timescaledb_catalog.chunk_id_seq TO PUBLIC;\nGRANT SELECT ON _timescaledb_catalog.chunk TO PUBLIC;\n-- end recreate _timescaledb_catalog.chunk table --\n\n--\n-- Rebuild the catalog table `_timescaledb_catalog.compression_chunk_size` to\n-- add new column `numrows_frozen_immediately`\n--\nCREATE TABLE _timescaledb_internal.compression_chunk_size_tmp\n    AS SELECT * from _timescaledb_catalog.compression_chunk_size;\n\n-- Drop depended views\n-- We assume that '_timescaledb_internal.compressed_chunk_stats' was already dropped in this update\n-- (see above)\n\n-- Drop table\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.compression_chunk_size;\nDROP TABLE _timescaledb_catalog.compression_chunk_size;\n\nCREATE TABLE _timescaledb_catalog.compression_chunk_size (\n  chunk_id integer NOT NULL,\n  compressed_chunk_id integer NOT NULL,\n  uncompressed_heap_size bigint NOT NULL,\n  uncompressed_toast_size bigint NOT NULL,\n  uncompressed_index_size bigint NOT NULL,\n  compressed_heap_size bigint NOT NULL,\n  compressed_toast_size bigint NOT NULL,\n  compressed_index_size bigint NOT NULL,\n  numrows_pre_compression bigint,\n  numrows_post_compression bigint,\n  numrows_frozen_immediately bigint,\n  -- table constraints\n  CONSTRAINT compression_chunk_size_pkey PRIMARY KEY (chunk_id),\n  CONSTRAINT compression_chunk_size_chunk_id_fkey FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk (id) ON DELETE CASCADE,\n  CONSTRAINT compression_chunk_size_compressed_chunk_id_fkey FOREIGN KEY (compressed_chunk_id) REFERENCES _timescaledb_catalog.chunk (id) ON DELETE CASCADE\n);\n\nINSERT INTO _timescaledb_catalog.compression_chunk_size\n(chunk_id, compressed_chunk_id, uncompressed_heap_size, uncompressed_toast_size,\n  uncompressed_index_size, compressed_heap_size, compressed_toast_size,\n  compressed_index_size, numrows_pre_compression, numrows_post_compression, numrows_frozen_immediately)\nSELECT chunk_id, compressed_chunk_id, uncompressed_heap_size, uncompressed_toast_size,\n  uncompressed_index_size, compressed_heap_size, compressed_toast_size,\n  compressed_index_size, numrows_pre_compression, numrows_post_compression, 0\nFROM _timescaledb_internal.compression_chunk_size_tmp;\n\nDROP TABLE _timescaledb_internal.compression_chunk_size_tmp;\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.compression_chunk_size', '');\n\nGRANT SELECT ON _timescaledb_catalog.compression_chunk_size TO PUBLIC;\n\n-- End modify `_timescaledb_catalog.compression_chunk_size`\n\nDROP FUNCTION @extschema@.drop_chunks(REGCLASS, \"any\", \"any\", BOOL);\nCREATE FUNCTION @extschema@.drop_chunks(\n     relation               REGCLASS,\n     older_than             \"any\" = NULL,\n     newer_than             \"any\" = NULL,\n     verbose                BOOLEAN = FALSE,\n     created_before         \"any\" = NULL,\n     created_after          \"any\" = NULL\n ) RETURNS SETOF TEXT AS '@MODULE_PATHNAME@', 'ts_chunk_drop_chunks'\n LANGUAGE C VOLATILE PARALLEL UNSAFE;\n\nDROP FUNCTION @extschema@.show_chunks(REGCLASS, \"any\", \"any\");\nCREATE FUNCTION @extschema@.show_chunks(\n     relation               REGCLASS,\n     older_than             \"any\" = NULL,\n     newer_than             \"any\" = NULL,\n     created_before         \"any\" = NULL,\n     created_after          \"any\" = NULL\n ) RETURNS SETOF REGCLASS AS '@MODULE_PATHNAME@', 'ts_chunk_show_chunks'\n LANGUAGE C STABLE PARALLEL SAFE;\n\nDROP FUNCTION @extschema@.add_retention_policy(REGCLASS, \"any\", BOOL, INTERVAL, TIMESTAMPTZ, TEXT);\nCREATE FUNCTION @extschema@.add_retention_policy(\n       relation REGCLASS,\n       drop_after \"any\" = NULL,\n       if_not_exists BOOL = false,\n       schedule_interval INTERVAL = NULL,\n       initial_start TIMESTAMPTZ = NULL,\n       timezone TEXT = NULL,\n       drop_created_before INTERVAL = NULL\n)\nRETURNS INTEGER AS '@MODULE_PATHNAME@', 'ts_policy_retention_add'\nLANGUAGE C VOLATILE;\n\nDROP FUNCTION @extschema@.add_compression_policy(REGCLASS, \"any\", BOOL, INTERVAL, TIMESTAMPTZ, TEXT);\nCREATE FUNCTION @extschema@.add_compression_policy(\n    hypertable REGCLASS,\n    compress_after \"any\" = NULL,\n    if_not_exists BOOL = false,\n    schedule_interval INTERVAL = NULL,\n    initial_start TIMESTAMPTZ = NULL,\n    timezone TEXT = NULL,\n    compress_created_before INTERVAL = NULL\n)\nRETURNS INTEGER\nAS '@MODULE_PATHNAME@', 'ts_policy_compression_add'\nLANGUAGE C VOLATILE;\n\nDROP PROCEDURE IF EXISTS _timescaledb_functions.policy_compression_execute(INTEGER, INTEGER, ANYELEMENT, INTEGER, BOOLEAN, BOOLEAN);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.policy_compression_execute(INTEGER, INTEGER, ANYELEMENT, INTEGER, BOOLEAN, BOOLEAN);\nCREATE PROCEDURE\n_timescaledb_functions.policy_compression_execute(\n  job_id              INTEGER,\n  htid                INTEGER,\n  lag                 ANYELEMENT,\n  maxchunks           INTEGER,\n  verbose_log         BOOLEAN,\n  recompress_enabled  BOOLEAN,\n  use_creation_time   BOOLEAN)\nAS $$\nDECLARE\n  htoid       REGCLASS;\n  chunk_rec   RECORD;\n  numchunks   INTEGER := 1;\n  _message     text;\n  _detail      text;\n  -- chunk status bits:\n  bit_compressed int := 1;\n  bit_compressed_unordered int := 2;\n  bit_frozen int := 4;\n  bit_compressed_partial int := 8;\n  creation_lag INTERVAL := NULL;\nBEGIN\n\n  -- procedures with SET clause cannot execute transaction\n  -- control so we adjust search_path in procedure body\n  SET LOCAL search_path TO pg_catalog, pg_temp;\n\n  SELECT format('%I.%I', schema_name, table_name) INTO htoid\n  FROM _timescaledb_catalog.hypertable\n  WHERE id = htid;\n\n  -- for the integer cases, we have to compute the lag w.r.t\n  -- the integer_now function and then pass on to show_chunks\n  IF pg_typeof(lag) IN ('BIGINT'::regtype, 'INTEGER'::regtype, 'SMALLINT'::regtype) THEN\n    -- cannot have use_creation_time set with this\n    IF use_creation_time IS TRUE THEN\n        RAISE EXCEPTION 'job % cannot use creation time with integer_now function', job_id;\n    END IF;\n    lag := _timescaledb_functions.subtract_integer_from_now(htoid, lag::BIGINT);\n  END IF;\n\n  -- if use_creation_time has been specified then the lag needs to be used with the\n  -- \"compress_created_before\" argument. Otherwise the usual \"older_than\" argument\n  -- is good enough\n  IF use_creation_time IS TRUE THEN\n    creation_lag := lag;\n    lag := NULL;\n  END IF;\n\n  FOR chunk_rec IN\n    SELECT\n      show.oid, ch.schema_name, ch.table_name, ch.status\n    FROM\n      @extschema@.show_chunks(htoid, older_than => lag, created_before => creation_lag) AS show(oid)\n      INNER JOIN pg_class pgc ON pgc.oid = show.oid\n      INNER JOIN pg_namespace pgns ON pgc.relnamespace = pgns.oid\n      INNER JOIN _timescaledb_catalog.chunk ch ON ch.table_name = pgc.relname AND ch.schema_name = pgns.nspname AND ch.hypertable_id = htid\n    WHERE\n      ch.dropped IS FALSE\n      AND (\n        ch.status = 0 OR\n        (\n          ch.status & bit_compressed > 0 AND (\n            ch.status & bit_compressed_unordered > 0 OR\n            ch.status & bit_compressed_partial > 0\n          )\n        )\n      )\n  LOOP\n    IF chunk_rec.status = 0 THEN\n      BEGIN\n        PERFORM @extschema@.compress_chunk( chunk_rec.oid );\n      EXCEPTION WHEN OTHERS THEN\n        GET STACKED DIAGNOSTICS\n            _message = MESSAGE_TEXT,\n            _detail = PG_EXCEPTION_DETAIL;\n        RAISE WARNING 'compressing chunk \"%\" failed when compression policy is executed', chunk_rec.oid::regclass::text\n            USING DETAIL = format('Message: (%s), Detail: (%s).', _message, _detail),\n                  ERRCODE = sqlstate;\n      END;\n    ELSIF\n      (\n        chunk_rec.status & bit_compressed > 0 AND (\n          chunk_rec.status & bit_compressed_unordered > 0 OR\n          chunk_rec.status & bit_compressed_partial > 0\n        )\n      ) AND recompress_enabled IS TRUE THEN\n      BEGIN\n        PERFORM @extschema@.decompress_chunk(chunk_rec.oid, if_compressed => true);\n      EXCEPTION WHEN OTHERS THEN\n        RAISE WARNING 'decompressing chunk \"%\" failed when compression policy is executed', chunk_rec.oid::regclass::text\n            USING DETAIL = format('Message: (%s), Detail: (%s).', _message, _detail),\n                  ERRCODE = sqlstate;\n      END;\n      -- SET LOCAL is only active until end of transaction.\n      -- While we could use SET at the start of the function we do not\n      -- want to bleed out search_path to caller, so we do SET LOCAL\n      -- again after COMMIT\n      BEGIN\n        PERFORM @extschema@.compress_chunk(chunk_rec.oid);\n      EXCEPTION WHEN OTHERS THEN\n        RAISE WARNING 'compressing chunk \"%\" failed when compression policy is executed', chunk_rec.oid::regclass::text\n            USING DETAIL = format('Message: (%s), Detail: (%s).', _message, _detail),\n                  ERRCODE = sqlstate;\n      END;\n    END IF;\n    COMMIT;\n    -- SET LOCAL is only active until end of transaction.\n    -- While we could use SET at the start of the function we do not\n    -- want to bleed out search_path to caller, so we do SET LOCAL\n    -- again after COMMIT\n    SET LOCAL search_path TO pg_catalog, pg_temp;\n    IF verbose_log THEN\n       RAISE LOG 'job % completed processing chunk %.%', job_id, chunk_rec.schema_name, chunk_rec.table_name;\n    END IF;\n    numchunks := numchunks + 1;\n    IF maxchunks > 0 AND numchunks >= maxchunks THEN\n         EXIT;\n    END IF;\n  END LOOP;\nEND;\n$$ LANGUAGE PLPGSQL;\n\n-- fix atttypmod and attcollation for segmentby columns\nDO $$\nDECLARE\n  htc_id INTEGER;\n  htc REGCLASS;\n  _attname NAME;\n  _atttypmod INTEGER;\n  _attcollation OID;\nBEGIN\n  -- find any segmentby columns where typmod and collation in\n  -- the compressed hypertable does not match the uncompressed\n  -- hypertable values\n  FOR htc_id, htc, _attname, _atttypmod, _attcollation IN\n    SELECT cat.htc_id, cat.htc, pga.attname, ht_mod, ht_coll\n    FROM pg_attribute pga\n    INNER JOIN\n    (\n      SELECT\n        htc.id AS htc_id,\n        format('%I.%I',htc.schema_name,htc.table_name) AS htc,\n        att_ht.atttypmod AS ht_mod,\n        att_ht.attcollation AS ht_coll,\n        c.attname\n      FROM _timescaledb_catalog.hypertable_compression c\n      INNER JOIN _timescaledb_catalog.hypertable ht ON ht.id=c.hypertable_id\n      INNER JOIN pg_attribute att_ht ON att_ht.attname = c.attname AND att_ht.attrelid = format('%I.%I',ht.schema_name,ht.table_name)::regclass\n      INNER JOIN _timescaledb_catalog.hypertable htc ON htc.id=ht.compressed_hypertable_id\n      WHERE c.segmentby_column_index > 0\n    ) cat ON cat.htc::regclass = pga.attrelid AND cat.attname = pga.attname\n    WHERE pga.atttypmod <> ht_mod OR pga.attcollation <> ht_coll\n  LOOP\n    -- fix typmod and collation for the compressed hypertable and all compressed chunks\n    UPDATE pg_attribute SET atttypmod = _atttypmod, attcollation = _attcollation WHERE attname = _attname AND attrelid IN (\n      SELECT format('%I.%I',schema_name,table_name)::regclass from _timescaledb_catalog.chunk WHERE hypertable_id = htc_id AND NOT dropped UNION ALL SELECT htc\n    );\n  END LOOP;\nEND\n$$;\n"
  },
  {
    "path": "sql/updates/2.13.0--2.12.2.sql",
    "content": "-- API changes related to hypertable generalization\nDROP FUNCTION IF EXISTS @extschema@.add_dimension(regclass,dimension_info,boolean);\nDROP FUNCTION IF EXISTS @extschema@.create_hypertable(regclass,dimension_info,boolean,boolean,boolean);\nDROP FUNCTION IF EXISTS @extschema@.set_partitioning_interval(regclass,anyelement,name);\nDROP FUNCTION IF EXISTS @extschema@.by_hash(name,integer,regproc);\nDROP FUNCTION IF EXISTS @extschema@.by_range(name,anyelement,regproc);\n\nDROP TYPE IF EXISTS _timescaledb_internal.dimension_info CASCADE;\n\n--\n-- Rebuild the catalog table `_timescaledb_catalog.chunk`\n--\n-- We need to recreate the catalog from scratch because when we drop a column\n-- Postgres marks `pg_attribute.attisdropped=TRUE` instead of removing it from\n-- the `pg_catalog.pg_attribute` table.\n--\n-- If we downgrade and upgrade the extension without rebuilding the catalog table it\n-- will mess up `pg_attribute.attnum` and we will end up with issues when trying\n-- to update data in those catalog tables.\n\n-- Recreate _timescaledb_catalog.chunk table --\nCREATE TABLE _timescaledb_internal.chunk_tmp\nAS SELECT * from _timescaledb_catalog.chunk;\n\nCREATE TABLE _timescaledb_internal.tmp_chunk_seq_value AS\nSELECT last_value, is_called FROM _timescaledb_catalog.chunk_id_seq;\n\n--drop foreign keys on chunk table\nALTER TABLE _timescaledb_catalog.chunk_constraint DROP CONSTRAINT\nchunk_constraint_chunk_id_fkey;\nALTER TABLE _timescaledb_catalog.chunk_index DROP CONSTRAINT\nchunk_index_chunk_id_fkey;\nALTER TABLE _timescaledb_catalog.chunk_data_node DROP CONSTRAINT\nchunk_data_node_chunk_id_fkey;\nALTER TABLE _timescaledb_internal.bgw_policy_chunk_stats DROP CONSTRAINT\nbgw_policy_chunk_stats_chunk_id_fkey;\nALTER TABLE _timescaledb_catalog.compression_chunk_size DROP CONSTRAINT\ncompression_chunk_size_chunk_id_fkey;\nALTER TABLE _timescaledb_catalog.compression_chunk_size DROP CONSTRAINT\ncompression_chunk_size_compressed_chunk_id_fkey;\nALTER TABLE _timescaledb_catalog.chunk_copy_operation DROP CONSTRAINT\nchunk_copy_operation_chunk_id_fkey;\n\n--drop dependent views\nDROP VIEW IF EXISTS timescaledb_information.hypertables;\nDROP VIEW IF EXISTS timescaledb_information.chunks;\nDROP VIEW IF EXISTS _timescaledb_internal.hypertable_chunk_local_size;\nDROP VIEW IF EXISTS _timescaledb_internal.compressed_chunk_stats;\nDROP VIEW IF EXISTS timescaledb_experimental.chunk_replication_status;\n\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.chunk;\nALTER EXTENSION timescaledb DROP SEQUENCE _timescaledb_catalog.chunk_id_seq;\nDROP TABLE _timescaledb_catalog.chunk;\n\nCREATE SEQUENCE _timescaledb_catalog.chunk_id_seq MINVALUE 1;\n\n-- now create table without self referential foreign key\nCREATE TABLE _timescaledb_catalog.chunk (\n  id integer NOT NULL DEFAULT nextval('_timescaledb_catalog.chunk_id_seq'),\n  hypertable_id int NOT NULL,\n  schema_name name NOT NULL,\n  table_name name NOT NULL,\n  compressed_chunk_id integer ,\n  dropped boolean NOT NULL DEFAULT FALSE,\n  status integer NOT NULL DEFAULT 0,\n  osm_chunk boolean NOT NULL DEFAULT FALSE,\n  -- table constraints\n  CONSTRAINT chunk_pkey PRIMARY KEY (id),\n  CONSTRAINT chunk_schema_name_table_name_key UNIQUE (schema_name, table_name)\n);\n\nINSERT INTO _timescaledb_catalog.chunk\n( id, hypertable_id, schema_name, table_name,\n  compressed_chunk_id, dropped, status, osm_chunk)\nSELECT id, hypertable_id, schema_name, table_name,\n  compressed_chunk_id, dropped, status, osm_chunk\nFROM _timescaledb_internal.chunk_tmp;\n\n--add indexes to the chunk table\nCREATE INDEX chunk_hypertable_id_idx ON _timescaledb_catalog.chunk (hypertable_id);\nCREATE INDEX chunk_compressed_chunk_id_idx ON _timescaledb_catalog.chunk (compressed_chunk_id);\nCREATE INDEX chunk_osm_chunk_idx ON _timescaledb_catalog.chunk (osm_chunk, hypertable_id);\n\nALTER SEQUENCE _timescaledb_catalog.chunk_id_seq OWNED BY _timescaledb_catalog.chunk.id;\nSELECT setval('_timescaledb_catalog.chunk_id_seq', last_value, is_called) FROM _timescaledb_internal.tmp_chunk_seq_value;\n\n-- add self referential foreign key\nALTER TABLE _timescaledb_catalog.chunk ADD CONSTRAINT chunk_compressed_chunk_id_fkey FOREIGN KEY ( compressed_chunk_id )\n REFERENCES _timescaledb_catalog.chunk( id );\n\n--add foreign key constraint\nALTER TABLE _timescaledb_catalog.chunk\n      ADD CONSTRAINT chunk_hypertable_id_fkey\n      FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.chunk', '');\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.chunk_id_seq', '');\n\n--add the foreign key constraints\nALTER TABLE _timescaledb_catalog.chunk_constraint ADD CONSTRAINT\nchunk_constraint_chunk_id_fkey FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk(id);\nALTER TABLE _timescaledb_catalog.chunk_index ADD CONSTRAINT\nchunk_index_chunk_id_fkey FOREIGN KEY (chunk_id)\nREFERENCES _timescaledb_catalog.chunk(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.chunk_data_node ADD CONSTRAINT\nchunk_data_node_chunk_id_fkey FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk(id);\nALTER TABLE _timescaledb_internal.bgw_policy_chunk_stats ADD CONSTRAINT\nbgw_policy_chunk_stats_chunk_id_fkey FOREIGN KEY (chunk_id)\nREFERENCES _timescaledb_catalog.chunk(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.compression_chunk_size ADD CONSTRAINT\ncompression_chunk_size_chunk_id_fkey FOREIGN KEY (chunk_id)\nREFERENCES _timescaledb_catalog.chunk(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.compression_chunk_size ADD CONSTRAINT\ncompression_chunk_size_compressed_chunk_id_fkey FOREIGN KEY (compressed_chunk_id)\nREFERENCES _timescaledb_catalog.chunk(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.chunk_copy_operation ADD CONSTRAINT\nchunk_copy_operation_chunk_id_fkey FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk (id) ON DELETE CASCADE;\n\n--cleanup\nDROP TABLE _timescaledb_internal.chunk_tmp;\nDROP TABLE _timescaledb_internal.tmp_chunk_seq_value;\n\nGRANT SELECT ON _timescaledb_catalog.chunk_id_seq TO PUBLIC;\nGRANT SELECT ON _timescaledb_catalog.chunk TO PUBLIC;\n\n-- end recreate _timescaledb_catalog.chunk table --\n\n\n--\n-- Rebuild the catalog table `_timescaledb_catalog.compression_chunk_size` to\n-- remove column `numrows_frozen_immediately`\n--\nCREATE TABLE _timescaledb_internal.compression_chunk_size_tmp\n    AS SELECT * from _timescaledb_catalog.compression_chunk_size;\n\n-- Drop depended views\n-- We assume that '_timescaledb_internal.compressed_chunk_stats' was already dropped in this update\n-- (see above)\n\n-- Drop table\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.compression_chunk_size;\nDROP TABLE _timescaledb_catalog.compression_chunk_size;\n\nCREATE TABLE _timescaledb_catalog.compression_chunk_size (\n  chunk_id integer NOT NULL,\n  compressed_chunk_id integer NOT NULL,\n  uncompressed_heap_size bigint NOT NULL,\n  uncompressed_toast_size bigint NOT NULL,\n  uncompressed_index_size bigint NOT NULL,\n  compressed_heap_size bigint NOT NULL,\n  compressed_toast_size bigint NOT NULL,\n  compressed_index_size bigint NOT NULL,\n  numrows_pre_compression bigint,\n  numrows_post_compression bigint,\n  -- table constraints\n  CONSTRAINT compression_chunk_size_pkey PRIMARY KEY (chunk_id),\n  CONSTRAINT compression_chunk_size_chunk_id_fkey FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk (id) ON DELETE CASCADE,\n  CONSTRAINT compression_chunk_size_compressed_chunk_id_fkey FOREIGN KEY (compressed_chunk_id) REFERENCES _timescaledb_catalog.chunk (id) ON DELETE CASCADE\n);\n\nINSERT INTO _timescaledb_catalog.compression_chunk_size\n(chunk_id, compressed_chunk_id, uncompressed_heap_size, uncompressed_toast_size,\n  uncompressed_index_size, compressed_heap_size, compressed_toast_size,\n  compressed_index_size, numrows_pre_compression, numrows_post_compression)\nSELECT chunk_id, compressed_chunk_id, uncompressed_heap_size, uncompressed_toast_size,\n  uncompressed_index_size, compressed_heap_size, compressed_toast_size,\n  compressed_index_size, numrows_pre_compression, numrows_post_compression\nFROM _timescaledb_internal.compression_chunk_size_tmp;\n\nDROP TABLE _timescaledb_internal.compression_chunk_size_tmp;\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.compression_chunk_size', '');\n\nGRANT SELECT ON _timescaledb_catalog.compression_chunk_size TO PUBLIC;\n\n-- End modify `_timescaledb_catalog.compression_chunk_size`\n\nDROP FUNCTION @extschema@.drop_chunks(REGCLASS, \"any\", \"any\", BOOL, \"any\", \"any\");\nCREATE FUNCTION @extschema@.drop_chunks(\n     relation               REGCLASS,\n     older_than             \"any\" = NULL,\n     newer_than             \"any\" = NULL,\n     verbose                BOOLEAN = FALSE\n ) RETURNS SETOF TEXT AS '@MODULE_PATHNAME@', 'ts_chunk_drop_chunks'\n LANGUAGE C VOLATILE PARALLEL UNSAFE;\n\nDROP FUNCTION @extschema@.show_chunks(REGCLASS, \"any\", \"any\", \"any\", \"any\");\nCREATE FUNCTION @extschema@.show_chunks(\n     relation               REGCLASS,\n     older_than             \"any\" = NULL,\n     newer_than             \"any\" = NULL\n ) RETURNS SETOF REGCLASS AS '@MODULE_PATHNAME@', 'ts_chunk_show_chunks'\n LANGUAGE C STABLE PARALLEL SAFE;\n\nDROP PROCEDURE IF EXISTS _timescaledb_functions.repair_relation_acls();\nDROP FUNCTION IF EXISTS _timescaledb_functions.makeaclitem(regrole, regrole, text, bool);\n\nDROP FUNCTION IF EXISTS _timescaledb_functions.cagg_validate_query(TEXT);\n\nDROP FUNCTION @extschema@.add_retention_policy(REGCLASS, \"any\", BOOL, INTERVAL, TIMESTAMPTZ, TEXT, INTERVAL);\nCREATE FUNCTION @extschema@.add_retention_policy(\n       relation REGCLASS,\n       drop_after \"any\",\n       if_not_exists BOOL = false,\n       schedule_interval INTERVAL = NULL,\n       initial_start TIMESTAMPTZ = NULL,\n       timezone TEXT = NULL\n)\nRETURNS INTEGER AS '@MODULE_PATHNAME@', 'ts_policy_retention_add'\nLANGUAGE C VOLATILE;\n\nDROP FUNCTION @extschema@.add_compression_policy(REGCLASS, \"any\", BOOL, INTERVAL, TIMESTAMPTZ, TEXT, INTERVAL);\nCREATE FUNCTION @extschema@.add_compression_policy(\n    hypertable REGCLASS,\n    compress_after \"any\",\n    if_not_exists BOOL = false,\n    schedule_interval INTERVAL = NULL,\n    initial_start TIMESTAMPTZ = NULL,\n    timezone TEXT = NULL\n)\nRETURNS INTEGER\nAS '@MODULE_PATHNAME@', 'ts_policy_compression_add'\nLANGUAGE C VOLATILE;\n\nDROP PROCEDURE IF EXISTS _timescaledb_functions.policy_compression_execute(INTEGER, INTEGER, ANYELEMENT, INTEGER, BOOLEAN, BOOLEAN, BOOLEAN);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.policy_compression_execute(INTEGER, INTEGER, ANYELEMENT, INTEGER, BOOLEAN, BOOLEAN, BOOLEAN);\nCREATE PROCEDURE\n_timescaledb_functions.policy_compression_execute(\n  job_id              INTEGER,\n  htid                INTEGER,\n  lag                 ANYELEMENT,\n  maxchunks           INTEGER,\n  verbose_log         BOOLEAN,\n  recompress_enabled  BOOLEAN)\nAS $$\nDECLARE\n  htoid       REGCLASS;\n  chunk_rec   RECORD;\n  numchunks   INTEGER := 1;\n  _message     text;\n  _detail      text;\n  -- chunk status bits:\n  bit_compressed int := 1;\n  bit_compressed_unordered int := 2;\n  bit_frozen int := 4;\n  bit_compressed_partial int := 8;\nBEGIN\n\n  -- procedures with SET clause cannot execute transaction\n  -- control so we adjust search_path in procedure body\n  SET LOCAL search_path TO pg_catalog, pg_temp;\n\n  SELECT format('%I.%I', schema_name, table_name) INTO htoid\n  FROM _timescaledb_catalog.hypertable\n  WHERE id = htid;\n\n  -- for the integer cases, we have to compute the lag w.r.t\n  -- the integer_now function and then pass on to show_chunks\n  IF pg_typeof(lag) IN ('BIGINT'::regtype, 'INTEGER'::regtype, 'SMALLINT'::regtype) THEN\n    lag := _timescaledb_functions.subtract_integer_from_now(htoid, lag::BIGINT);\n  END IF;\n\n  FOR chunk_rec IN\n    SELECT\n      show.oid, ch.schema_name, ch.table_name, ch.status\n    FROM\n      @extschema@.show_chunks(htoid, older_than => lag) AS show(oid)\n      INNER JOIN pg_class pgc ON pgc.oid = show.oid\n      INNER JOIN pg_namespace pgns ON pgc.relnamespace = pgns.oid\n      INNER JOIN _timescaledb_catalog.chunk ch ON ch.table_name = pgc.relname AND ch.schema_name = pgns.nspname AND ch.hypertable_id = htid\n    WHERE\n      ch.dropped IS FALSE\n      AND (\n        ch.status = 0 OR\n        (\n          ch.status & bit_compressed > 0 AND (\n            ch.status & bit_compressed_unordered > 0 OR\n            ch.status & bit_compressed_partial > 0\n          )\n        )\n      )\n  LOOP\n    IF chunk_rec.status = 0 THEN\n      BEGIN\n        PERFORM @extschema@.compress_chunk( chunk_rec.oid );\n      EXCEPTION WHEN OTHERS THEN\n        GET STACKED DIAGNOSTICS\n            _message = MESSAGE_TEXT,\n            _detail = PG_EXCEPTION_DETAIL;\n        RAISE WARNING 'compressing chunk \"%\" failed when compression policy is executed', chunk_rec.oid::regclass::text\n            USING DETAIL = format('Message: (%s), Detail: (%s).', _message, _detail),\n                  ERRCODE = sqlstate;\n      END;\n    ELSIF\n      (\n        chunk_rec.status & bit_compressed > 0 AND (\n          chunk_rec.status & bit_compressed_unordered > 0 OR\n          chunk_rec.status & bit_compressed_partial > 0\n        )\n      ) AND recompress_enabled IS TRUE THEN\n      BEGIN\n        PERFORM @extschema@.decompress_chunk(chunk_rec.oid, if_compressed => true);\n      EXCEPTION WHEN OTHERS THEN\n        RAISE WARNING 'decompressing chunk \"%\" failed when compression policy is executed', chunk_rec.oid::regclass::text\n            USING DETAIL = format('Message: (%s), Detail: (%s).', _message, _detail),\n                  ERRCODE = sqlstate;\n      END;\n      -- SET LOCAL is only active until end of transaction.\n      -- While we could use SET at the start of the function we do not\n      -- want to bleed out search_path to caller, so we do SET LOCAL\n      -- again after COMMIT\n      BEGIN\n        PERFORM @extschema@.compress_chunk(chunk_rec.oid);\n      EXCEPTION WHEN OTHERS THEN\n        RAISE WARNING 'compressing chunk \"%\" failed when compression policy is executed', chunk_rec.oid::regclass::text\n            USING DETAIL = format('Message: (%s), Detail: (%s).', _message, _detail),\n                  ERRCODE = sqlstate;\n      END;\n    END IF;\n    COMMIT;\n    -- SET LOCAL is only active until end of transaction.\n    -- While we could use SET at the start of the function we do not\n    -- want to bleed out search_path to caller, so we do SET LOCAL\n    -- again after COMMIT\n    SET LOCAL search_path TO pg_catalog, pg_temp;\n    IF verbose_log THEN\n       RAISE LOG 'job % completed processing chunk %.%', job_id, chunk_rec.schema_name, chunk_rec.table_name;\n    END IF;\n    numchunks := numchunks + 1;\n    IF maxchunks > 0 AND numchunks >= maxchunks THEN\n         EXIT;\n    END IF;\n  END LOOP;\nEND;\n$$ LANGUAGE PLPGSQL;\n\nDROP FUNCTION _timescaledb_functions.chunk_constraint_add_table_constraint(\n    chunk_constraint_row  _timescaledb_catalog.chunk_constraint\n);\n\nCREATE FUNCTION _timescaledb_functions.chunk_constraint_add_table_constraint(\n    chunk_constraint_row  _timescaledb_catalog.chunk_constraint\n)\n    RETURNS VOID LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n    chunk_row _timescaledb_catalog.chunk;\n    hypertable_row _timescaledb_catalog.hypertable;\n    constraint_oid OID;\n    constraint_type CHAR;\n    check_sql TEXT;\n    def TEXT;\n    indx_tablespace NAME;\n    tablespace_def TEXT;\nBEGIN\n    SELECT * INTO STRICT chunk_row FROM _timescaledb_catalog.chunk c WHERE c.id = chunk_constraint_row.chunk_id;\n    SELECT * INTO STRICT hypertable_row FROM _timescaledb_catalog.hypertable h WHERE h.id = chunk_row.hypertable_id;\n\n    IF chunk_constraint_row.dimension_slice_id IS NOT NULL THEN\n\t    RAISE 'cannot create dimension constraint %', chunk_constraint_row;\n    ELSIF chunk_constraint_row.hypertable_constraint_name IS NOT NULL THEN\n\n        SELECT oid, contype INTO STRICT constraint_oid, constraint_type FROM pg_constraint\n        WHERE conname=chunk_constraint_row.hypertable_constraint_name AND\n              conrelid = format('%I.%I', hypertable_row.schema_name, hypertable_row.table_name)::regclass::oid;\n\n        IF constraint_type IN ('p','u') THEN\n          -- since primary keys and unique constraints are backed by an index\n          -- they might have an index tablespace assigned\n          -- the tablspace is not part of the constraint definition so\n          -- we have to append it explicitly to preserve it\n          SELECT T.spcname INTO indx_tablespace\n          FROM pg_constraint C, pg_class I, pg_tablespace T\n          WHERE C.oid = constraint_oid AND C.contype IN ('p', 'u') AND I.oid = C.conindid AND I.reltablespace = T.oid;\n\n          def := pg_get_constraintdef(constraint_oid);\n\n          IF indx_tablespace IS NOT NULL THEN\n            def := format('%s USING INDEX TABLESPACE %I', def, indx_tablespace);\n          END IF;\n\n        ELSIF constraint_type = 't' THEN\n          -- constraint triggers are copied separately with normal triggers\n          def := NULL;\n        ELSE\n          def := pg_get_constraintdef(constraint_oid);\n        END IF;\n\n    ELSE\n        RAISE 'unknown constraint type';\n    END IF;\n\n    IF def IS NOT NULL THEN\n        -- to allow for custom types with operators outside of pg_catalog\n        -- we set search_path to @extschema@\n        SET LOCAL search_path TO @extschema@, pg_temp;\n        EXECUTE pg_catalog.format(\n            $$ ALTER TABLE %I.%I ADD CONSTRAINT %I %s $$,\n            chunk_row.schema_name, chunk_row.table_name, chunk_constraint_row.constraint_name, def\n        );\n\n    END IF;\nEND\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n"
  },
  {
    "path": "sql/updates/2.13.0--2.13.1.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.13.1--2.13.0.sql",
    "content": "-- Manually drop the following functions / procedures since 'OR REPLACE' is missing in 2.13.0\nDROP PROCEDURE IF EXISTS _timescaledb_functions.repair_relation_acls();\nDROP FUNCTION IF EXISTS _timescaledb_functions.makeaclitem(regrole, regrole, text, bool);\n"
  },
  {
    "path": "sql/updates/2.13.1--2.14.0.sql",
    "content": "\n-- ERROR if trying to update the extension while multinode is present\nDO $$\nDECLARE\n  data_nodes TEXT;\n  dist_hypertables TEXT;\nBEGIN\n  SELECT string_agg(format('%I.%I', schema_name, table_name), ', ')\n  INTO dist_hypertables\n  FROM _timescaledb_catalog.hypertable\n  WHERE replication_factor > 0;\n\n  IF dist_hypertables IS NOT NULL THEN\n    RAISE USING\n      ERRCODE = 'feature_not_supported',\n      MESSAGE = 'cannot upgrade because multi-node has been removed in 2.14.0',\n      DETAIL = 'The following distributed hypertables should be migrated to regular: '||dist_hypertables;\n  END IF;\n\n  SELECT string_agg(format('%I', srv.srvname), ', ')\n  INTO data_nodes\n  FROM pg_foreign_server srv\n  JOIN pg_foreign_data_wrapper fdw ON srv.srvfdw = fdw.oid AND fdw.fdwname = 'timescaledb_fdw';\n\n  IF data_nodes IS NOT NULL THEN\n    RAISE USING\n      ERRCODE = 'feature_not_supported',\n      MESSAGE = 'cannot upgrade because multi-node has been removed in 2.14.0',\n      DETAIL = 'The following data nodes should be removed: '||data_nodes;\n  END IF;\n\n  IF EXISTS(SELECT FROM _timescaledb_catalog.metadata WHERE key = 'dist_uuid') THEN\n    RAISE USING\n      ERRCODE = 'feature_not_supported',\n      MESSAGE = 'cannot upgrade because multi-node has been removed in 2.14.0',\n      DETAIL = 'This node appears to be part of a multi-node installation';\n  END IF;\nEND $$;\n\nDROP FUNCTION IF EXISTS _timescaledb_functions.ping_data_node;\nDROP FUNCTION IF EXISTS _timescaledb_internal.ping_data_node;\nDROP FUNCTION IF EXISTS _timescaledb_functions.remote_txn_heal_data_node;\nDROP FUNCTION IF EXISTS _timescaledb_internal.remote_txn_heal_data_node;\n\nDROP FUNCTION IF EXISTS _timescaledb_functions.set_dist_id;\nDROP FUNCTION IF EXISTS _timescaledb_internal.set_dist_id;\nDROP FUNCTION IF EXISTS _timescaledb_functions.set_peer_dist_id;\nDROP FUNCTION IF EXISTS _timescaledb_internal.set_peer_dist_id;\nDROP FUNCTION IF EXISTS _timescaledb_functions.validate_as_data_node;\nDROP FUNCTION IF EXISTS _timescaledb_internal.validate_as_data_node;\nDROP FUNCTION IF EXISTS _timescaledb_functions.show_connection_cache;\nDROP FUNCTION IF EXISTS _timescaledb_internal.show_connection_cache;\n\nDROP FUNCTION IF EXISTS @extschema@.create_hypertable(relation REGCLASS, time_column_name NAME, partitioning_column NAME, number_partitions INTEGER, associated_schema_name NAME, associated_table_prefix NAME, chunk_time_interval ANYELEMENT, create_default_indexes BOOLEAN, if_not_exists BOOLEAN, partitioning_func REGPROC, migrate_data BOOLEAN, chunk_target_size TEXT, chunk_sizing_func REGPROC, time_partitioning_func REGPROC, replication_factor INTEGER, data_nodes NAME[], distributed BOOLEAN);\n\nCREATE FUNCTION @extschema@.create_hypertable(\n    relation                REGCLASS,\n    time_column_name        NAME,\n    partitioning_column     NAME = NULL,\n    number_partitions       INTEGER = NULL,\n    associated_schema_name  NAME = NULL,\n    associated_table_prefix NAME = NULL,\n    chunk_time_interval     ANYELEMENT = NULL::bigint,\n    create_default_indexes  BOOLEAN = TRUE,\n    if_not_exists           BOOLEAN = FALSE,\n    partitioning_func       REGPROC = NULL,\n    migrate_data            BOOLEAN = FALSE,\n    chunk_target_size       TEXT = NULL,\n    chunk_sizing_func       REGPROC = '_timescaledb_functions.calculate_chunk_interval'::regproc,\n    time_partitioning_func  REGPROC = NULL\n) RETURNS TABLE(hypertable_id INT, schema_name NAME, table_name NAME, created BOOL) AS '@MODULE_PATHNAME@', 'ts_hypertable_create' LANGUAGE C VOLATILE;\n\nDROP FUNCTION IF EXISTS @extschema@.create_distributed_hypertable;\n\nDROP FUNCTION IF EXISTS @extschema@.add_data_node;\nDROP FUNCTION IF EXISTS @extschema@.delete_data_node;\nDROP FUNCTION IF EXISTS @extschema@.attach_data_node;\nDROP FUNCTION IF EXISTS @extschema@.detach_data_node;\nDROP FUNCTION IF EXISTS @extschema@.alter_data_node;\n\nDROP PROCEDURE IF EXISTS @extschema@.distributed_exec;\nDROP FUNCTION IF EXISTS @extschema@.create_distributed_restore_point;\nDROP FUNCTION IF EXISTS @extschema@.set_replication_factor;\n\nCREATE TABLE _timescaledb_catalog.compression_settings (\n  relid regclass NOT NULL,\n  segmentby text[],\n  orderby text[],\n  orderby_desc bool[],\n  orderby_nullsfirst bool[],\n  CONSTRAINT compression_settings_pkey PRIMARY KEY (relid),\n  CONSTRAINT compression_settings_check_segmentby CHECK (array_ndims(segmentby) = 1),\n  CONSTRAINT compression_settings_check_orderby_null CHECK ( (orderby IS NULL AND orderby_desc IS NULL AND orderby_nullsfirst IS NULL) OR (orderby IS NOT NULL AND orderby_desc IS NOT NULL AND orderby_nullsfirst IS NOT NULL) ),\n  CONSTRAINT compression_settings_check_orderby_cardinality CHECK (array_ndims(orderby) = 1 AND array_ndims(orderby_desc) = 1 AND array_ndims(orderby_nullsfirst) = 1 AND cardinality(orderby) = cardinality(orderby_desc) AND cardinality(orderby) = cardinality(orderby_nullsfirst))\n);\n\nINSERT INTO _timescaledb_catalog.compression_settings(relid, segmentby, orderby, orderby_desc, orderby_nullsfirst)\n  SELECT\n    format('%I.%I', ht.schema_name, ht.table_name)::regclass,\n    array_agg(attname ORDER BY segmentby_column_index) FILTER(WHERE segmentby_column_index >= 1) AS compress_segmentby,\n    array_agg(attname ORDER BY orderby_column_index) FILTER(WHERE orderby_column_index >= 1) AS compress_orderby,\n    array_agg(NOT orderby_asc ORDER BY orderby_column_index) FILTER(WHERE orderby_column_index >= 1) AS compress_orderby_desc,\n    array_agg(orderby_nullsfirst ORDER BY orderby_column_index) FILTER(WHERE orderby_column_index >= 1) AS compress_orderby_nullsfirst\n  FROM _timescaledb_catalog.hypertable_compression hc\n    INNER JOIN _timescaledb_catalog.hypertable ht ON ht.id = hc.hypertable_id\n  GROUP BY hypertable_id, ht.schema_name, ht.table_name;\n\nINSERT INTO _timescaledb_catalog.compression_settings\nSELECT format('%I.%I',ch.schema_name,ch.table_name)::regclass,s.segmentby,s.orderby,s.orderby_desc,s.orderby_nullsfirst\nFROM _timescaledb_catalog.hypertable ht1\nINNER JOIN _timescaledb_catalog.hypertable ht2 ON ht2.id = ht1.compressed_hypertable_id\nINNER JOIN _timescaledb_catalog.compression_settings s ON s.relid = format('%I.%I',ht1.schema_name,ht1.table_name)::regclass\nINNER JOIN _timescaledb_catalog.chunk ch ON ch.hypertable_id = ht2.id ON CONFLICT DO NOTHING;\n\nGRANT SELECT ON _timescaledb_catalog.compression_settings TO PUBLIC;\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.compression_settings', '');\n\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.hypertable_compression;\nDROP VIEW IF EXISTS timescaledb_information.compression_settings;\nDROP TABLE _timescaledb_catalog.hypertable_compression;\n\nDROP FOREIGN DATA WRAPPER IF EXISTS timescaledb_fdw;\nDROP FUNCTION IF EXISTS @extschema@.timescaledb_fdw_handler();\nDROP FUNCTION IF EXISTS @extschema@.timescaledb_fdw_validator(text[], oid);\n\n\nDROP FUNCTION IF EXISTS _timescaledb_functions.create_chunk_replica_table;\nDROP FUNCTION IF EXISTS _timescaledb_functions.chunk_drop_replica;\nDROP PROCEDURE IF EXISTS _timescaledb_functions.wait_subscription_sync;\nDROP FUNCTION IF EXISTS _timescaledb_functions.health;\nDROP FUNCTION IF EXISTS _timescaledb_functions.drop_stale_chunks;\n\nDROP FUNCTION IF EXISTS _timescaledb_internal.create_chunk_replica_table;\nDROP FUNCTION IF EXISTS _timescaledb_internal.chunk_drop_replica;\nDROP PROCEDURE IF EXISTS _timescaledb_internal.wait_subscription_sync;\nDROP FUNCTION IF EXISTS _timescaledb_internal.health;\nDROP FUNCTION IF EXISTS _timescaledb_internal.drop_stale_chunks;\n\nALTER TABLE _timescaledb_catalog.remote_txn DROP CONSTRAINT remote_txn_remote_transaction_id_check;\n\nDROP TYPE IF EXISTS @extschema@.rxid CASCADE;\nDROP FUNCTION IF EXISTS _timescaledb_functions.rxid_in;\nDROP FUNCTION IF EXISTS _timescaledb_functions.rxid_out;\n\nDROP FUNCTION IF EXISTS _timescaledb_functions.data_node_hypertable_info;\nDROP FUNCTION IF EXISTS _timescaledb_functions.data_node_chunk_info;\nDROP FUNCTION IF EXISTS _timescaledb_functions.data_node_compressed_chunk_stats;\nDROP FUNCTION IF EXISTS _timescaledb_functions.data_node_index_size;\nDROP FUNCTION IF EXISTS _timescaledb_internal.data_node_hypertable_info;\nDROP FUNCTION IF EXISTS _timescaledb_internal.data_node_chunk_info;\nDROP FUNCTION IF EXISTS _timescaledb_internal.data_node_compressed_chunk_stats;\nDROP FUNCTION IF EXISTS _timescaledb_internal.data_node_index_size;\n\nDROP FUNCTION IF EXISTS timescaledb_experimental.block_new_chunks;\nDROP FUNCTION IF EXISTS timescaledb_experimental.allow_new_chunks;\nDROP FUNCTION IF EXISTS timescaledb_experimental.subscription_exec;\nDROP PROCEDURE IF EXISTS timescaledb_experimental.move_chunk;\nDROP PROCEDURE IF EXISTS timescaledb_experimental.copy_chunk;\nDROP PROCEDURE IF EXISTS timescaledb_experimental.cleanup_copy_chunk_operation;\n\nDROP FUNCTION IF EXISTS _timescaledb_functions.set_chunk_default_data_node;\nDROP FUNCTION IF EXISTS _timescaledb_internal.set_chunk_default_data_node;\n\nDROP FUNCTION IF EXISTS _timescaledb_functions.drop_dist_ht_invalidation_trigger;\nDROP FUNCTION IF EXISTS _timescaledb_internal.drop_dist_ht_invalidation_trigger;\n\n-- remove multinode catalog tables\nDROP VIEW IF EXISTS timescaledb_information.chunks;\nDROP VIEW IF EXISTS timescaledb_information.data_nodes;\nDROP VIEW IF EXISTS timescaledb_information.hypertables;\nDROP VIEW IF EXISTS timescaledb_experimental.chunk_replication_status;\n\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.remote_txn;\nDROP TABLE _timescaledb_catalog.remote_txn;\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.hypertable_data_node;\nDROP TABLE _timescaledb_catalog.hypertable_data_node;\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.chunk_data_node;\nDROP TABLE _timescaledb_catalog.chunk_data_node;\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.chunk_copy_operation;\nDROP TABLE _timescaledb_catalog.chunk_copy_operation;\nALTER EXTENSION timescaledb DROP SEQUENCE _timescaledb_catalog.chunk_copy_operation_id_seq;\nDROP SEQUENCE _timescaledb_catalog.chunk_copy_operation_id_seq;\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.dimension_partition;\nDROP TABLE _timescaledb_catalog.dimension_partition;\n\nDROP FUNCTION IF EXISTS _timescaledb_functions.hypertable_remote_size;\nDROP FUNCTION IF EXISTS _timescaledb_internal.hypertable_remote_size;\nDROP FUNCTION IF EXISTS _timescaledb_functions.chunks_remote_size;\nDROP FUNCTION IF EXISTS _timescaledb_internal.chunks_remote_size;\nDROP FUNCTION IF EXISTS _timescaledb_functions.indexes_remote_size;\nDROP FUNCTION IF EXISTS _timescaledb_internal.indexes_remote_size;\nDROP FUNCTION IF EXISTS _timescaledb_functions.compressed_chunk_remote_stats;\nDROP FUNCTION IF EXISTS _timescaledb_internal.compressed_chunk_remote_stats;\n\n-- rebuild _timescaledb_catalog.hypertable\nALTER TABLE _timescaledb_config.bgw_job\n    DROP CONSTRAINT bgw_job_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.chunk\n    DROP CONSTRAINT chunk_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.chunk_index\n    DROP CONSTRAINT chunk_index_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.continuous_agg\n    DROP CONSTRAINT continuous_agg_mat_hypertable_id_fkey,\n    DROP CONSTRAINT continuous_agg_raw_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.continuous_aggs_bucket_function\n    DROP CONSTRAINT continuous_aggs_bucket_function_mat_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.continuous_aggs_invalidation_threshold\n    DROP CONSTRAINT continuous_aggs_invalidation_threshold_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.dimension\n    DROP CONSTRAINT dimension_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.hypertable\n    DROP CONSTRAINT hypertable_compressed_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.tablespace\n    DROP CONSTRAINT tablespace_hypertable_id_fkey;\n\nDROP VIEW IF EXISTS timescaledb_information.hypertables;\nDROP VIEW IF EXISTS timescaledb_information.job_stats;\nDROP VIEW IF EXISTS timescaledb_information.jobs;\nDROP VIEW IF EXISTS timescaledb_information.continuous_aggregates;\nDROP VIEW IF EXISTS timescaledb_information.chunks;\nDROP VIEW IF EXISTS timescaledb_information.dimensions;\nDROP VIEW IF EXISTS timescaledb_information.compression_settings;\nDROP VIEW IF EXISTS _timescaledb_internal.hypertable_chunk_local_size;\nDROP VIEW IF EXISTS _timescaledb_internal.compressed_chunk_stats;\nDROP VIEW IF EXISTS timescaledb_experimental.chunk_replication_status;\nDROP VIEW IF EXISTS timescaledb_experimental.policies;\n\n-- recreate table\nCREATE TABLE _timescaledb_catalog.hypertable_tmp AS SELECT * FROM _timescaledb_catalog.hypertable;\nCREATE TABLE _timescaledb_catalog.tmp_hypertable_seq_value AS SELECT last_value, is_called FROM _timescaledb_catalog.hypertable_id_seq;\n\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.hypertable;\nALTER EXTENSION timescaledb DROP SEQUENCE _timescaledb_catalog.hypertable_id_seq;\n\nSET timescaledb.restoring = on; -- must disable the hooks otherwise we can't do anything without the table _timescaledb_catalog.hypertable\n\nDROP TABLE _timescaledb_catalog.hypertable;\n\nCREATE SEQUENCE _timescaledb_catalog.hypertable_id_seq MINVALUE 1;\nSELECT setval('_timescaledb_catalog.hypertable_id_seq', last_value, is_called) FROM _timescaledb_catalog.tmp_hypertable_seq_value;\nDROP TABLE _timescaledb_catalog.tmp_hypertable_seq_value;\n\nCREATE TABLE _timescaledb_catalog.hypertable (\n    id INTEGER PRIMARY KEY NOT NULL DEFAULT nextval('_timescaledb_catalog.hypertable_id_seq'),\n    schema_name name NOT NULL,\n    table_name name NOT NULL,\n    associated_schema_name name NOT NULL,\n    associated_table_prefix name NOT NULL,\n    num_dimensions smallint NOT NULL,\n    chunk_sizing_func_schema name NOT NULL,\n    chunk_sizing_func_name name NOT NULL,\n    chunk_target_size bigint NOT NULL, -- size in bytes\n    compression_state smallint NOT NULL DEFAULT 0,\n    compressed_hypertable_id integer,\n    status integer NOT NULL DEFAULT 0\n);\n\nSET timescaledb.restoring = off;\n\nINSERT INTO _timescaledb_catalog.hypertable (\n    id,\n    schema_name,\n    table_name,\n    associated_schema_name,\n    associated_table_prefix,\n    num_dimensions,\n    chunk_sizing_func_schema,\n    chunk_sizing_func_name,\n    chunk_target_size,\n    compression_state,\n    compressed_hypertable_id\n)\nSELECT\n    id,\n    schema_name,\n    table_name,\n    associated_schema_name,\n    associated_table_prefix,\n    num_dimensions,\n    chunk_sizing_func_schema,\n    chunk_sizing_func_name,\n    chunk_target_size,\n    compression_state,\n    compressed_hypertable_id\nFROM\n    _timescaledb_catalog.hypertable_tmp\nORDER BY id;\n\nUPDATE _timescaledb_catalog.hypertable h\nSET status = 3\nWHERE EXISTS (\n  SELECT FROM _timescaledb_catalog.chunk c WHERE c.osm_chunk AND c.hypertable_id = h.id\n);\n\nALTER SEQUENCE _timescaledb_catalog.hypertable_id_seq OWNED BY _timescaledb_catalog.hypertable.id;\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.hypertable_id_seq', '');\n\nGRANT SELECT ON _timescaledb_catalog.hypertable TO PUBLIC;\nGRANT SELECT ON _timescaledb_catalog.hypertable_id_seq TO PUBLIC;\n\nDROP TABLE _timescaledb_catalog.hypertable_tmp;\n-- now add any constraints\nALTER TABLE _timescaledb_catalog.hypertable\n    ADD CONSTRAINT hypertable_associated_schema_name_associated_table_prefix_key UNIQUE (associated_schema_name, associated_table_prefix),\n    ADD CONSTRAINT hypertable_table_name_schema_name_key UNIQUE (table_name, schema_name),\n    ADD CONSTRAINT hypertable_schema_name_check CHECK (schema_name != '_timescaledb_catalog'),\n    ADD CONSTRAINT hypertable_dim_compress_check CHECK (num_dimensions > 0 OR compression_state = 2),\n    ADD CONSTRAINT hypertable_chunk_target_size_check CHECK (chunk_target_size >= 0),\n    ADD CONSTRAINT hypertable_compress_check CHECK ( (compression_state = 0 OR compression_state = 1 )  OR (compression_state = 2 AND compressed_hypertable_id IS NULL)),\n    ADD CONSTRAINT hypertable_compressed_hypertable_id_fkey FOREIGN KEY (compressed_hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id);\n\nGRANT SELECT ON TABLE _timescaledb_catalog.hypertable TO PUBLIC;\n\n-- 3. reestablish constraints on other tables\nALTER TABLE _timescaledb_config.bgw_job\n    ADD CONSTRAINT bgw_job_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.chunk\n    ADD CONSTRAINT chunk_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id);\nALTER TABLE _timescaledb_catalog.chunk_index\n    ADD CONSTRAINT chunk_index_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.continuous_agg\n    ADD CONSTRAINT continuous_agg_mat_hypertable_id_fkey FOREIGN KEY (mat_hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE,\n    ADD CONSTRAINT continuous_agg_raw_hypertable_id_fkey FOREIGN KEY (raw_hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.continuous_aggs_bucket_function\n    ADD CONSTRAINT continuous_aggs_bucket_function_mat_hypertable_id_fkey FOREIGN KEY (mat_hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.continuous_aggs_invalidation_threshold\n    ADD CONSTRAINT continuous_aggs_invalidation_threshold_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.dimension\n    ADD CONSTRAINT dimension_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.tablespace\n    ADD CONSTRAINT tablespace_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\n\nCREATE SCHEMA _timescaledb_debug;\n\n-- Migrate existing compressed hypertables to new internal format\nDO $$\nDECLARE\n  chunk regclass;\n  hypertable regclass;\n  ht_id integer;\n  index regclass;\n  column_name name;\n  cmd text;\nBEGIN\n  SET timescaledb.restoring TO ON;\n\n  -- Detach compressed chunks from their parent hypertables\n  FOR chunk, hypertable, ht_id IN\n    SELECT\n      format('%I.%I',ch.schema_name,ch.table_name)::regclass chunk,\n      format('%I.%I',ht.schema_name,ht.table_name)::regclass hypertable,\n      ht.id\n    FROM _timescaledb_catalog.chunk ch\n    INNER JOIN _timescaledb_catalog.hypertable ht_uncomp\n      ON ch.hypertable_id = ht_uncomp.compressed_hypertable_id\n    INNER JOIN _timescaledb_catalog.hypertable ht\n      ON ht.id = ht_uncomp.compressed_hypertable_id\n  LOOP\n\n    cmd := format('ALTER TABLE %s NO INHERIT %s', chunk, hypertable);\n    EXECUTE cmd;\n    -- remove references to indexes from the compressed hypertable\n    DELETE FROM _timescaledb_catalog.chunk_index WHERE hypertable_id = ht_id;\n\n  END LOOP;\n\n\n  FOR hypertable IN\n    SELECT\n      format('%I.%I',ht.schema_name,ht.table_name)::regclass hypertable\n    FROM _timescaledb_catalog.hypertable ht_uncomp\n    INNER JOIN _timescaledb_catalog.hypertable ht\n      ON ht.id = ht_uncomp.compressed_hypertable_id\n  LOOP\n\n    -- remove indexes from the compressed hypertable (but not chunks)\n    FOR index IN\n      SELECT indexrelid::regclass FROM pg_index WHERE indrelid = hypertable\n    LOOP\n      cmd := format('DROP INDEX %s', index);\n      EXECUTE cmd;\n    END LOOP;\n\n    -- remove columns from the compressed hypertable (but not chunks)\n    FOR column_name IN\n      SELECT attname FROM pg_attribute WHERE attrelid = hypertable AND attnum > 0 AND NOT attisdropped\n    LOOP\n      cmd := format('ALTER TABLE %s DROP COLUMN %I', hypertable, column_name);\n      EXECUTE cmd;\n    END LOOP;\n\n  END LOOP;\n\n  SET timescaledb.restoring TO OFF;\nEND $$;\n\nDROP FUNCTION IF EXISTS _timescaledb_internal.hypertable_constraint_add_table_fk_constraint;\nDROP FUNCTION IF EXISTS _timescaledb_functions.hypertable_constraint_add_table_fk_constraint;\n\n-- only define stub here, actual code will be filled in at end of update script\nCREATE FUNCTION _timescaledb_functions.constraint_clone(constraint_oid OID,target_oid REGCLASS) RETURNS VOID LANGUAGE PLPGSQL AS $$BEGIN END$$ SET search_path TO pg_catalog, pg_temp;\n\nDROP FUNCTION IF EXISTS _timescaledb_functions.chunks_in;\nDROP FUNCTION IF EXISTS _timescaledb_internal.chunks_in;\n\nCREATE FUNCTION _timescaledb_functions.metadata_insert_trigger() RETURNS TRIGGER LANGUAGE PLPGSQL\nAS $$\nBEGIN\n  IF EXISTS (SELECT FROM _timescaledb_catalog.metadata WHERE key = NEW.key) THEN\n    UPDATE _timescaledb_catalog.metadata SET value = NEW.value WHERE key = NEW.key;\n    RETURN NULL;\n  END IF;\n  RETURN NEW;\nEND\n$$ SET search_path TO pg_catalog, pg_temp;\n\nCREATE TRIGGER metadata_insert_trigger BEFORE INSERT ON _timescaledb_catalog.metadata FOR EACH ROW EXECUTE PROCEDURE _timescaledb_functions.metadata_insert_trigger();\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.metadata', $$ WHERE key <> 'uuid' $$);\n\n-- Remove unwanted entries from extconfig and extcondition in pg_extension\n-- We use ALTER EXTENSION DROP TABLE to remove these entries.\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_cache.cache_inval_hypertable;\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_cache.cache_inval_extension;\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_cache.cache_inval_bgw_job;\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_internal.job_errors;\n\n-- Associate the above tables back to keep the dependencies safe\nALTER EXTENSION timescaledb ADD TABLE _timescaledb_cache.cache_inval_hypertable;\nALTER EXTENSION timescaledb ADD TABLE _timescaledb_cache.cache_inval_extension;\nALTER EXTENSION timescaledb ADD TABLE _timescaledb_cache.cache_inval_bgw_job;\nALTER EXTENSION timescaledb ADD TABLE _timescaledb_internal.job_errors;\n\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.hypertable;\nALTER EXTENSION timescaledb ADD TABLE _timescaledb_catalog.hypertable;\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.hypertable', 'WHERE id >= 1');\n\nCREATE FUNCTION _timescaledb_functions.relation_approximate_size(relation REGCLASS)\nRETURNS TABLE (total_size BIGINT, heap_size BIGINT, index_size BIGINT, toast_size BIGINT)\nAS '@MODULE_PATHNAME@', 'ts_relation_approximate_size' LANGUAGE C STRICT VOLATILE;\n\nCREATE FUNCTION @extschema@.hypertable_approximate_detailed_size(relation REGCLASS)\nRETURNS TABLE (table_bytes BIGINT, index_bytes BIGINT, toast_bytes BIGINT, total_bytes BIGINT)\nAS '@MODULE_PATHNAME@', 'ts_hypertable_approximate_size' LANGUAGE C VOLATILE;\n\n--- returns approximate total-bytes for a hypertable (includes table + index)\nCREATE FUNCTION @extschema@.hypertable_approximate_size(\n    hypertable              REGCLASS)\nRETURNS BIGINT\nLANGUAGE SQL VOLATILE STRICT AS\n$BODY$\n   SELECT sum(total_bytes)::bigint\n   FROM @extschema@.hypertable_approximate_detailed_size(hypertable);\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\nDROP FUNCTION IF EXISTS @extschema@.compress_chunk;\nCREATE FUNCTION @extschema@.compress_chunk(uncompressed_chunk REGCLASS, if_not_compressed BOOLEAN = true, recompress BOOLEAN = false) RETURNS REGCLASS AS '' LANGUAGE SQL SET search_path TO pg_catalog, pg_temp;\n\n"
  },
  {
    "path": "sql/updates/2.14.0--2.13.1.sql",
    "content": "-- check whether we can safely downgrade compression setup\nDO $$\nDECLARE\n  hypertable regclass;\n  ht_uncomp regclass;\n  chunk_relids oid[];\n  ht_id integer;\nBEGIN\n\n  FOR hypertable, ht_uncomp, ht_id IN\n    SELECT\n      format('%I.%I',ht.schema_name,ht.table_name)::regclass,\n      format('%I.%I',ht_uncomp.schema_name,ht_uncomp.table_name)::regclass,\n      ht.id\n    FROM _timescaledb_catalog.hypertable ht_uncomp\n    INNER JOIN _timescaledb_catalog.hypertable ht\n      ON ht.id = ht_uncomp.compressed_hypertable_id\n  LOOP\n\n    -- hypertables need to at least have 1 compressed chunk so we can restore the columns\n    IF NOT EXISTS(SELECT FROM _timescaledb_catalog.chunk WHERE hypertable_id = ht_id) THEN\n      RAISE USING\n        ERRCODE = 'feature_not_supported',\n        MESSAGE = 'Cannot downgrade compressed hypertables with no compressed chunks. Disable compression on the affected hypertable before downgrading.',\n        DETAIL = 'The following hypertable is affected: '|| ht_uncomp::text;\n    END IF;\n\n    chunk_relids := array(SELECT format('%I.%I',schema_name,table_name)::regclass FROM _timescaledb_catalog.chunk WHERE hypertable_id = ht_id);\n\n    -- any hypertable with distinct compression settings cannot be downgraded\n    IF EXISTS (\n\t\t\tSELECT FROM (\n        SELECT DISTINCT segmentby, orderby, orderby_desc, orderby_nullsfirst\n        FROM _timescaledb_catalog.compression_settings\n        WHERE relid = hypertable OR relid = ANY(chunk_relids)\n      ) dist_settings HAVING count(*) > 1\n\t\t) THEN\n      RAISE USING\n        ERRCODE = 'feature_not_supported',\n        MESSAGE = 'Cannot downgrade hypertables with distinct compression settings. Decompress the affected hypertable before downgrading.',\n        DETAIL = 'The following hypertable is affected: '|| ht_uncomp::text;\n    END IF;\n\n  END LOOP;\nEND\n$$;\n\nCREATE FUNCTION _timescaledb_functions.tmp_resolve_indkeys(oid,int2[]) RETURNS text[] LANGUAGE SQL AS $$\n\tSELECT array_agg(attname)\n\tFROM (\n\t\tSELECT attname\n\t\tFROM (SELECT unnest($2) attnum) indkeys\n\t\tJOIN LATERAL (\n\t\t\tSELECT attname FROM pg_attribute att WHERE att.attnum=indkeys.attnum AND att.attrelid=$1\n\t\t) r ON true\n  ) resolve;\n$$ SET search_path TO pg_catalog, pg_temp;\n\nDO $$\nDECLARE\n  chunk regclass;\n  hypertable regclass;\n  ht_id integer;\n  chunk_id integer;\n  _index regclass;\n  ht_index regclass;\n  chunk_index regclass;\n  index_name name;\n  chunk_index_name name;\n  _indkey text[];\n  column_name name;\n  column_type regtype;\n  cmd text;\nBEGIN\n  SET timescaledb.restoring TO ON;\n\n  FOR hypertable, ht_id IN\n    SELECT\n      format('%I.%I',ht.schema_name,ht.table_name)::regclass,\n      ht.id\n    FROM _timescaledb_catalog.hypertable ht_uncomp\n    INNER JOIN _timescaledb_catalog.hypertable ht\n      ON ht.id = ht_uncomp.compressed_hypertable_id\n  LOOP\n\n    -- get first chunk which we use as template for restoring columns and indexes\n    SELECT format('%I.%I',schema_name,table_name)::regclass INTO STRICT chunk FROM _timescaledb_catalog.chunk WHERE hypertable_id = ht_id ORDER by id LIMIT 1;\n\n    -- restore columns from the compressed hypertable\n    FOR column_name, column_type IN\n      SELECT attname, atttypid::regtype FROM pg_attribute WHERE attrelid = chunk AND attnum > 0 AND NOT attisdropped\n    LOOP\n      cmd := format('ALTER TABLE %s ADD COLUMN %I %s', hypertable, column_name, column_type);\n      EXECUTE cmd;\n    END LOOP;\n\n    -- restore indexes on the compressed hypertable\n    FOR _index, _indkey IN\n      SELECT indexrelid::regclass, _timescaledb_functions.tmp_resolve_indkeys(indrelid, indkey) FROM pg_index WHERE indrelid = chunk\n    LOOP\n      SELECT relname INTO STRICT index_name FROM pg_class WHERE oid = _index;\n      cmd := pg_get_indexdef(_index);\n      cmd := replace(cmd, format(' INDEX %s ON ', index_name), ' INDEX ON ');\n      cmd := replace(cmd, chunk::text, hypertable::text);\n      EXECUTE cmd;\n\n      -- get indexrelid of index we just created on hypertable\n      SELECT indexrelid INTO STRICT ht_index FROM pg_index WHERE indrelid = hypertable AND _timescaledb_functions.tmp_resolve_indkeys(hypertable, indkey) = _indkey;\n      SELECT relname INTO STRICT index_name FROM pg_class WHERE oid = ht_index;\n\n      -- restore indexes in our catalog\n      FOR chunk, chunk_id IN\n        SELECT format('%I.%I',schema_name,table_name)::regclass, id FROM _timescaledb_catalog.chunk WHERE hypertable_id = ht_id\n      LOOP\n        SELECT indexrelid INTO STRICT chunk_index FROM pg_index WHERE indrelid = chunk AND _timescaledb_functions.tmp_resolve_indkeys(chunk, indkey) = _indkey;\n        SELECT relname INTO STRICT chunk_index_name FROM pg_class WHERE oid = chunk_index;\n        INSERT INTO _timescaledb_catalog.chunk_index (chunk_id, index_name, hypertable_id, hypertable_index_name)\n          VALUES (chunk_id, chunk_index_name, ht_id, index_name);\n      END LOOP;\n\n    END LOOP;\n\n    -- restore inheritance\n    cmd := format('ALTER TABLE %s INHERIT %s', chunk, hypertable);\n    EXECUTE cmd;\n\n  END LOOP;\n\n  SET timescaledb.restoring TO OFF;\nEND $$;\n\nDROP FUNCTION _timescaledb_functions.tmp_resolve_indkeys;\n\nCREATE FUNCTION _timescaledb_functions.ping_data_node(node_name NAME, timeout INTERVAL = NULL) RETURNS BOOLEAN\nAS '@MODULE_PATHNAME@', 'ts_data_node_ping' LANGUAGE C VOLATILE;\n\nCREATE FUNCTION _timescaledb_functions.remote_txn_heal_data_node(foreign_server_oid oid)\nRETURNS INT\nAS '@MODULE_PATHNAME@', 'ts_remote_txn_heal_data_node'\nLANGUAGE C STRICT;\n\nCREATE FUNCTION _timescaledb_functions.set_dist_id(dist_id UUID) RETURNS BOOL\nAS '@MODULE_PATHNAME@', 'ts_dist_set_id' LANGUAGE C VOLATILE STRICT;\n\nCREATE FUNCTION _timescaledb_functions.set_peer_dist_id(dist_id UUID) RETURNS BOOL\nAS '@MODULE_PATHNAME@', 'ts_dist_set_peer_id' LANGUAGE C VOLATILE STRICT;\n\n-- Function to validate that a node has local settings to function as\n-- a data node. Throws error if validation fails.\nCREATE FUNCTION _timescaledb_functions.validate_as_data_node() RETURNS void\nAS '@MODULE_PATHNAME@', 'ts_dist_validate_as_data_node' LANGUAGE C VOLATILE STRICT;\n\nCREATE FUNCTION _timescaledb_functions.show_connection_cache()\nRETURNS TABLE (\n    node_name           name,\n    user_name           name,\n    host                text,\n    port                int,\n    database            name,\n    backend_pid         int,\n    connection_status   text,\n    transaction_status  text,\n    transaction_depth   int,\n    processing          boolean,\n    invalidated         boolean)\nAS '@MODULE_PATHNAME@', 'ts_remote_connection_cache_show' LANGUAGE C VOLATILE STRICT;\n\nDROP FUNCTION IF EXISTS @extschema@.create_hypertable(relation REGCLASS, time_column_name NAME, partitioning_column NAME, number_partitions INTEGER, associated_schema_name NAME, associated_table_prefix NAME, chunk_time_interval ANYELEMENT, create_default_indexes BOOLEAN, if_not_exists BOOLEAN, partitioning_func REGPROC, migrate_data BOOLEAN, chunk_target_size TEXT, chunk_sizing_func REGPROC, time_partitioning_func REGPROC);\n\nCREATE FUNCTION @extschema@.create_hypertable(\n    relation                REGCLASS,\n    time_column_name        NAME,\n    partitioning_column     NAME = NULL,\n    number_partitions       INTEGER = NULL,\n    associated_schema_name  NAME = NULL,\n    associated_table_prefix NAME = NULL,\n    chunk_time_interval     ANYELEMENT = NULL::bigint,\n    create_default_indexes  BOOLEAN = TRUE,\n    if_not_exists           BOOLEAN = FALSE,\n    partitioning_func       REGPROC = NULL,\n    migrate_data            BOOLEAN = FALSE,\n    chunk_target_size       TEXT = NULL,\n    chunk_sizing_func       REGPROC = '_timescaledb_functions.calculate_chunk_interval'::regproc,\n    time_partitioning_func  REGPROC = NULL,\n    replication_factor      INTEGER = NULL,\n    data_nodes              NAME[] = NULL,\n    distributed             BOOLEAN = NULL\n) RETURNS TABLE(hypertable_id INT, schema_name NAME, table_name NAME, created BOOL) AS '@MODULE_PATHNAME@', 'ts_hypertable_create' LANGUAGE C VOLATILE;\n\nCREATE FUNCTION @extschema@.create_distributed_hypertable(\n    relation                REGCLASS,\n    time_column_name        NAME,\n    partitioning_column     NAME = NULL,\n    number_partitions       INTEGER = NULL,\n    associated_schema_name  NAME = NULL,\n    associated_table_prefix NAME = NULL,\n    chunk_time_interval     ANYELEMENT = NULL::bigint,\n    create_default_indexes  BOOLEAN = TRUE,\n    if_not_exists           BOOLEAN = FALSE,\n    partitioning_func       REGPROC = NULL,\n    migrate_data            BOOLEAN = FALSE,\n    chunk_target_size       TEXT = NULL,\n    chunk_sizing_func       REGPROC = '_timescaledb_functions.calculate_chunk_interval'::regproc,\n    time_partitioning_func  REGPROC = NULL,\n    replication_factor      INTEGER = NULL,\n    data_nodes              NAME[] = NULL\n) RETURNS TABLE(hypertable_id INT, schema_name NAME, table_name NAME, created BOOL) AS '@MODULE_PATHNAME@', 'ts_hypertable_distributed_create' LANGUAGE C VOLATILE;\n\nCREATE FUNCTION @extschema@.add_data_node(\n    node_name              NAME,\n    host                   TEXT,\n    database               NAME = NULL,\n    port                   INTEGER = NULL,\n    if_not_exists          BOOLEAN = FALSE,\n    bootstrap              BOOLEAN = TRUE,\n    password               TEXT = NULL\n) RETURNS TABLE(node_name NAME, host TEXT, port INTEGER, database NAME,\n                node_created BOOL, database_created BOOL, extension_created BOOL)\nAS '@MODULE_PATHNAME@', 'ts_data_node_add' LANGUAGE C VOLATILE;\n\nCREATE FUNCTION @extschema@.delete_data_node(\n    node_name              NAME,\n    if_exists              BOOLEAN = FALSE,\n    force                  BOOLEAN = FALSE,\n    repartition            BOOLEAN = TRUE,\n    drop_database          BOOLEAN = FALSE\n) RETURNS BOOLEAN AS '@MODULE_PATHNAME@', 'ts_data_node_delete' LANGUAGE C VOLATILE;\n\nCREATE FUNCTION @extschema@.attach_data_node(\n    node_name              NAME,\n    hypertable             REGCLASS,\n    if_not_attached        BOOLEAN = FALSE,\n    repartition            BOOLEAN = TRUE\n) RETURNS TABLE(hypertable_id INTEGER, node_hypertable_id INTEGER, node_name NAME)\nAS '@MODULE_PATHNAME@', 'ts_data_node_attach' LANGUAGE C VOLATILE;\n\nCREATE FUNCTION @extschema@.detach_data_node(\n    node_name              NAME,\n    hypertable             REGCLASS = NULL,\n    if_attached            BOOLEAN = FALSE,\n    force                  BOOLEAN = FALSE,\n    repartition            BOOLEAN = TRUE,\n    drop_remote_data       BOOLEAN = FALSE\n) RETURNS INTEGER\nAS '@MODULE_PATHNAME@', 'ts_data_node_detach' LANGUAGE C VOLATILE;\n\nCREATE FUNCTION @extschema@.alter_data_node(\n    node_name              NAME,\n    host                   TEXT = NULL,\n    database               NAME = NULL,\n    port                   INTEGER = NULL,\n    available              BOOLEAN = NULL\n) RETURNS TABLE(node_name NAME, host TEXT, port INTEGER, database NAME, available BOOLEAN)\n\nAS '@MODULE_PATHNAME@', 'ts_data_node_alter' LANGUAGE C VOLATILE;\nCREATE PROCEDURE @extschema@.distributed_exec(\n       query TEXT,\n       node_list name[] = NULL,\n       transactional BOOLEAN = TRUE)\nAS '@MODULE_PATHNAME@', 'ts_distributed_exec' LANGUAGE C;\n\nCREATE FUNCTION @extschema@.create_distributed_restore_point(\n    name                   TEXT\n) RETURNS TABLE(node_name NAME, node_type TEXT, restore_point pg_lsn)\nAS '@MODULE_PATHNAME@', 'ts_create_distributed_restore_point' LANGUAGE C VOLATILE STRICT;\n\nCREATE FUNCTION @extschema@.set_replication_factor(\n    hypertable              REGCLASS,\n    replication_factor      INTEGER\n) RETURNS VOID\nAS '@MODULE_PATHNAME@', 'ts_hypertable_distributed_set_replication_factor' LANGUAGE C VOLATILE;\n\nCREATE TABLE _timescaledb_catalog.hypertable_compression (\n  hypertable_id integer NOT NULL,\n  attname name NOT NULL,\n  compression_algorithm_id smallint,\n  segmentby_column_index smallint,\n  orderby_column_index smallint,\n  orderby_asc boolean,\n  orderby_nullsfirst boolean,\n  -- table constraints\n  CONSTRAINT hypertable_compression_pkey PRIMARY KEY (hypertable_id, attname),\n  CONSTRAINT hypertable_compression_hypertable_id_orderby_column_index_key UNIQUE (hypertable_id, orderby_column_index),\n  CONSTRAINT hypertable_compression_hypertable_id_segmentby_column_index_key UNIQUE (hypertable_id, segmentby_column_index),\n  CONSTRAINT hypertable_compression_compression_algorithm_id_fkey FOREIGN KEY (compression_algorithm_id) REFERENCES _timescaledb_catalog.compression_algorithm (id)\n);\n\nINSERT INTO _timescaledb_catalog.hypertable_compression(\n\thypertable_id,\n\tattname,\n\tcompression_algorithm_id,\n\tsegmentby_column_index,\n\torderby_column_index,\n\torderby_asc,\n\torderby_nullsfirst\n) SELECT\n  ht.id,\n  att.attname,\n  CASE\n    WHEN att.attname = ANY(cs.segmentby) THEN 0\n\t\tWHEN att.atttypid IN ('numeric'::regtype) THEN 1\n\t\tWHEN att.atttypid IN ('float4'::regtype,'float8'::regtype) THEN 3\n\t\tWHEN att.atttypid IN ('int2'::regtype,'int4'::regtype,'int8'::regtype,'date'::regtype,'timestamp'::regtype,'timestamptz'::regtype) THEN 4\n\t\tWHEN EXISTS(SELECT FROM pg_operator op WHERE op.oprname = '=' AND op.oprkind = 'b' AND op.oprcanhash = true AND op.oprleft = att.atttypid AND op.oprright = att.atttypid) THEN 2\n    ELSE 1\n  END AS compression_algorithm_id,\n  CASE WHEN att.attname = ANY(cs.segmentby) THEN array_position(cs.segmentby, att.attname::text) ELSE NULL END AS segmentby_column_index,\n  CASE WHEN att.attname = ANY(cs.orderby) THEN array_position(cs.orderby, att.attname::text) ELSE NULL END AS orderby_column_index,\n  CASE WHEN att.attname = ANY(cs.orderby) THEN NOT cs.orderby_desc[array_position(cs.orderby, att.attname::text)] ELSE false END AS orderby_asc,\n  CASE WHEN att.attname = ANY(cs.orderby) THEN cs.orderby_nullsfirst[array_position(cs.orderby, att.attname::text)] ELSE false END AS orderby_nullsfirst\nFROM _timescaledb_catalog.hypertable ht\nINNER JOIN _timescaledb_catalog.compression_settings cs ON cs.relid = format('%I.%I',ht.schema_name,ht.table_name)::regclass\nLEFT JOIN pg_attribute att ON att.attrelid = format('%I.%I',ht.schema_name,ht.table_name)::regclass AND attnum > 0\nWHERE compressed_hypertable_id IS NOT NULL;\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.hypertable_compression', '');\nGRANT SELECT ON _timescaledb_catalog.hypertable_compression TO PUBLIC;\n\nALTER EXTENSION timescaledb DROP VIEW timescaledb_information.compression_settings;\nDROP VIEW timescaledb_information.compression_settings;\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.compression_settings;\nDROP TABLE _timescaledb_catalog.compression_settings;\n\nCREATE FUNCTION @extschema@.timescaledb_fdw_handler() RETURNS fdw_handler AS '@MODULE_PATHNAME@', 'ts_timescaledb_fdw_handler' LANGUAGE C STRICT;\nCREATE FUNCTION @extschema@.timescaledb_fdw_validator(text[], oid) RETURNS void AS '@MODULE_PATHNAME@', 'ts_timescaledb_fdw_validator' LANGUAGE C STRICT;\n\nCREATE FOREIGN DATA WRAPPER timescaledb_fdw HANDLER @extschema@.timescaledb_fdw_handler VALIDATOR @extschema@.timescaledb_fdw_validator;\n\nCREATE FUNCTION _timescaledb_functions.create_chunk_replica_table(\n    chunk REGCLASS,\n    data_node_name NAME\n) RETURNS VOID AS '@MODULE_PATHNAME@', 'ts_chunk_create_replica_table' LANGUAGE C VOLATILE;\n\nCREATE FUNCTION  _timescaledb_functions.chunk_drop_replica(\n    chunk                   REGCLASS,\n    node_name               NAME\n) RETURNS VOID\nAS '@MODULE_PATHNAME@', 'ts_chunk_drop_replica' LANGUAGE C VOLATILE;\n\nCREATE PROCEDURE _timescaledb_functions.wait_subscription_sync(\n    schema_name    NAME,\n    table_name     NAME,\n    retry_count    INT DEFAULT 18000,\n    retry_delay_ms NUMERIC DEFAULT 0.200\n)\nLANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n    in_sync BOOLEAN;\nBEGIN\n    FOR i in 1 .. retry_count\n    LOOP\n        SELECT pgs.srsubstate = 'r'\n        INTO in_sync\n        FROM pg_subscription_rel pgs\n        JOIN pg_class pgc ON relname = table_name\n        JOIN pg_namespace n ON (n.OID = pgc.relnamespace)\n        WHERE pgs.srrelid = pgc.oid AND schema_name = n.nspname;\n\n        if (in_sync IS NULL OR NOT in_sync) THEN\n          PERFORM pg_sleep(retry_delay_ms);\n        ELSE\n          RETURN;\n        END IF;\n    END LOOP;\n    RAISE 'subscription sync wait timedout';\nEND\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\nCREATE FUNCTION _timescaledb_functions.health() RETURNS\nTABLE (node_name NAME, healthy BOOL, in_recovery BOOL, error TEXT)\nAS '@MODULE_PATHNAME@', 'ts_health_check' LANGUAGE C VOLATILE;\n\nCREATE FUNCTION _timescaledb_functions.drop_stale_chunks(\n    node_name NAME,\n    chunks integer[] = NULL\n) RETURNS VOID\nAS '@MODULE_PATHNAME@', 'ts_chunks_drop_stale' LANGUAGE C VOLATILE;\n\nCREATE FUNCTION _timescaledb_functions.rxid_in(cstring) RETURNS @extschema@.rxid\n    AS '@MODULE_PATHNAME@', 'ts_remote_txn_id_in' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\nCREATE FUNCTION _timescaledb_functions.rxid_out(@extschema@.rxid) RETURNS cstring\n    AS '@MODULE_PATHNAME@', 'ts_remote_txn_id_out' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\nCREATE TYPE @extschema@.rxid (\n  internallength = 16,\n  input = _timescaledb_functions.rxid_in,\n  output = _timescaledb_functions.rxid_out\n);\n\nCREATE FUNCTION _timescaledb_functions.data_node_hypertable_info(\n    node_name              NAME,\n    schema_name_in name,\n    table_name_in name\n)\nRETURNS TABLE (\n    table_bytes     bigint,\n    index_bytes     bigint,\n    toast_bytes     bigint,\n    total_bytes     bigint)\nAS '@MODULE_PATHNAME@', 'ts_dist_remote_hypertable_info' LANGUAGE C VOLATILE STRICT;\n\nCREATE FUNCTION _timescaledb_functions.data_node_chunk_info(\n    node_name              NAME,\n    schema_name_in name,\n    table_name_in name\n)\nRETURNS TABLE (\n    chunk_id        integer,\n    chunk_schema    name,\n    chunk_name      name,\n    table_bytes     bigint,\n    index_bytes     bigint,\n    toast_bytes     bigint,\n    total_bytes     bigint)\nAS '@MODULE_PATHNAME@', 'ts_dist_remote_chunk_info' LANGUAGE C VOLATILE STRICT;\n\nCREATE FUNCTION _timescaledb_functions.data_node_compressed_chunk_stats(node_name name, schema_name_in name, table_name_in name)\n    RETURNS TABLE (\n        chunk_schema name,\n        chunk_name name,\n        compression_status text,\n        before_compression_table_bytes bigint,\n        before_compression_index_bytes bigint,\n        before_compression_toast_bytes bigint,\n        before_compression_total_bytes bigint,\n        after_compression_table_bytes bigint,\n        after_compression_index_bytes bigint,\n        after_compression_toast_bytes bigint,\n        after_compression_total_bytes bigint\n    )\nAS '@MODULE_PATHNAME@' , 'ts_dist_remote_compressed_chunk_info' LANGUAGE C VOLATILE STRICT;\n\nCREATE FUNCTION _timescaledb_functions.data_node_index_size(node_name name, schema_name_in name, index_name_in name)\nRETURNS TABLE ( hypertable_id INTEGER, total_bytes BIGINT)\nAS '@MODULE_PATHNAME@' , 'ts_dist_remote_hypertable_index_info' LANGUAGE C VOLATILE STRICT;\n\nCREATE FUNCTION timescaledb_experimental.block_new_chunks(data_node_name NAME, hypertable REGCLASS = NULL, force BOOLEAN = FALSE) RETURNS INTEGER\nAS '@MODULE_PATHNAME@', 'ts_data_node_block_new_chunks' LANGUAGE C VOLATILE;\n\nCREATE FUNCTION timescaledb_experimental.allow_new_chunks(data_node_name NAME, hypertable REGCLASS = NULL) RETURNS INTEGER\nAS '@MODULE_PATHNAME@', 'ts_data_node_allow_new_chunks' LANGUAGE C VOLATILE;\n\nCREATE PROCEDURE timescaledb_experimental.move_chunk(\n    chunk REGCLASS,\n    source_node NAME = NULL,\n    destination_node NAME = NULL,\n    operation_id NAME = NULL)\nAS '@MODULE_PATHNAME@', 'ts_move_chunk_proc' LANGUAGE C;\n\nCREATE PROCEDURE timescaledb_experimental.copy_chunk(\n    chunk REGCLASS,\n    source_node NAME = NULL,\n    destination_node NAME = NULL,\n    operation_id NAME = NULL)\nAS '@MODULE_PATHNAME@', 'ts_copy_chunk_proc' LANGUAGE C;\n\nCREATE FUNCTION timescaledb_experimental.subscription_exec(\n    subscription_command TEXT\n) RETURNS VOID AS '@MODULE_PATHNAME@', 'ts_subscription_exec' LANGUAGE C VOLATILE;\n\nCREATE PROCEDURE timescaledb_experimental.cleanup_copy_chunk_operation(\n    operation_id NAME)\nAS '@MODULE_PATHNAME@', 'ts_copy_chunk_cleanup_proc' LANGUAGE C;\n\nCREATE FUNCTION _timescaledb_functions.set_chunk_default_data_node(chunk REGCLASS, node_name NAME) RETURNS BOOLEAN\nAS '@MODULE_PATHNAME@', 'ts_chunk_set_default_data_node' LANGUAGE C VOLATILE;\n\nCREATE FUNCTION _timescaledb_functions.drop_dist_ht_invalidation_trigger(\n    raw_hypertable_id INTEGER\n) RETURNS VOID AS '@MODULE_PATHNAME@', 'ts_drop_dist_ht_invalidation_trigger' LANGUAGE C STRICT VOLATILE;\n\n-- restore multinode catalog tables\nCREATE TABLE _timescaledb_catalog.remote_txn (\n  data_node_name name, --this is really only to allow us to cleanup stuff on a per-node basis.\n  remote_transaction_id text NOT NULL,\n  -- table constraints\n  CONSTRAINT remote_txn_pkey PRIMARY KEY (remote_transaction_id)\n);\n\nALTER TABLE _timescaledb_catalog.remote_txn ADD CONSTRAINT remote_txn_remote_transaction_id_check CHECK (remote_transaction_id::@extschema@.rxid IS NOT NULL);\n\nCREATE INDEX remote_txn_data_node_name_idx ON _timescaledb_catalog.remote_txn (data_node_name);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.remote_txn', '');\nGRANT SELECT ON TABLE _timescaledb_catalog.remote_txn TO PUBLIC;\n\nCREATE TABLE _timescaledb_catalog.hypertable_data_node (\n  hypertable_id integer NOT NULL,\n  node_hypertable_id integer NULL,\n  node_name name NOT NULL,\n  block_chunks boolean NOT NULL,\n  -- table constraints\n  CONSTRAINT hypertable_data_node_hypertable_id_node_name_key UNIQUE (hypertable_id, node_name),\n  CONSTRAINT hypertable_data_node_node_hypertable_id_node_name_key UNIQUE (node_hypertable_id, node_name),\n  CONSTRAINT hypertable_data_node_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id)\n);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.hypertable_data_node', '');\nGRANT SELECT ON TABLE _timescaledb_catalog.hypertable_data_node TO PUBLIC;\n\nCREATE TABLE _timescaledb_catalog.chunk_data_node (\n  chunk_id integer NOT NULL,\n  node_chunk_id integer NOT NULL,\n  node_name name NOT NULL,\n  -- table constraints\n  CONSTRAINT chunk_data_node_chunk_id_node_name_key UNIQUE (chunk_id, node_name),\n  CONSTRAINT chunk_data_node_node_chunk_id_node_name_key UNIQUE (node_chunk_id, node_name),\n  CONSTRAINT chunk_data_node_chunk_id_fkey FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk (id)\n);\n\nCREATE INDEX chunk_data_node_node_name_idx ON _timescaledb_catalog.chunk_data_node (node_name);\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.chunk_data_node', '');\nGRANT SELECT ON TABLE _timescaledb_catalog.chunk_data_node TO PUBLIC;\n\nCREATE SEQUENCE _timescaledb_catalog.chunk_copy_operation_id_seq MINVALUE 1;\nGRANT SELECT ON SEQUENCE _timescaledb_catalog.chunk_copy_operation_id_seq TO PUBLIC;\n\nCREATE TABLE _timescaledb_catalog.chunk_copy_operation (\n  operation_id name NOT NULL, -- the publisher/subscriber identifier used\n  backend_pid integer NOT NULL, -- the pid of the backend running this activity\n  completed_stage name NOT NULL, -- the completed stage/step\n  time_start timestamptz NOT NULL DEFAULT NOW(), -- start time of the activity\n  chunk_id integer NOT NULL,\n  compress_chunk_name name NOT NULL,\n  source_node_name name NOT NULL,\n  dest_node_name name NOT NULL,\n  delete_on_source_node bool NOT NULL, -- is a move or copy activity\n  -- table constraints\n  CONSTRAINT chunk_copy_operation_pkey PRIMARY KEY (operation_id),\n  CONSTRAINT chunk_copy_operation_chunk_id_fkey FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk (id) ON DELETE CASCADE\n);\n\nGRANT SELECT ON TABLE _timescaledb_catalog.chunk_copy_operation TO PUBLIC;\n\nCREATE TABLE _timescaledb_catalog.dimension_partition (\n  dimension_id integer NOT NULL REFERENCES _timescaledb_catalog.dimension (id) ON DELETE CASCADE,\n  range_start bigint NOT NULL,\n  data_nodes name[] NULL,\n  UNIQUE (dimension_id, range_start)\n);\n\nGRANT SELECT ON TABLE _timescaledb_catalog.dimension_partition TO PUBLIC;\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.dimension_partition', '');\nCREATE FUNCTION _timescaledb_functions.hypertable_remote_size(\n    schema_name_in name,\n    table_name_in name)\nRETURNS TABLE (\n    table_bytes bigint,\n    index_bytes bigint,\n    toast_bytes bigint,\n    total_bytes bigint,\n    node_name   NAME)\nLANGUAGE SQL VOLATILE STRICT AS\n$BODY$\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\nCREATE FUNCTION _timescaledb_functions.chunks_remote_size(\n    schema_name_in name,\n    table_name_in name)\nRETURNS TABLE (\n    chunk_id    integer,\n    chunk_schema NAME,\n    chunk_name  NAME,\n    table_bytes bigint,\n    index_bytes bigint,\n    toast_bytes bigint,\n    total_bytes bigint,\n    node_name NAME)\nLANGUAGE SQL VOLATILE STRICT AS\n$BODY$\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\nCREATE FUNCTION _timescaledb_functions.indexes_remote_size(\n    schema_name_in             NAME,\n    table_name_in              NAME,\n    index_name_in              NAME\n)\nRETURNS BIGINT\nLANGUAGE SQL VOLATILE STRICT AS\n$BODY$\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\nCREATE FUNCTION _timescaledb_functions.compressed_chunk_remote_stats(schema_name_in name, table_name_in name)\n    RETURNS TABLE (\n        chunk_schema name,\n        chunk_name name,\n        compression_status text,\n        before_compression_table_bytes bigint,\n        before_compression_index_bytes bigint,\n        before_compression_toast_bytes bigint,\n        before_compression_total_bytes bigint,\n        after_compression_table_bytes bigint,\n        after_compression_index_bytes bigint,\n        after_compression_toast_bytes bigint,\n        after_compression_total_bytes bigint,\n        node_name name)\n    LANGUAGE SQL\n    STABLE STRICT\n    AS\n$BODY$\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\n-- recreate the _timescaledb_catalog.hypertable table as new field was added\n-- 1. drop CONSTRAINTS from other tables referencing the existing one\nALTER TABLE _timescaledb_config.bgw_job\n    DROP CONSTRAINT bgw_job_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.chunk\n    DROP CONSTRAINT chunk_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.chunk_index\n    DROP CONSTRAINT chunk_index_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.continuous_agg\n    DROP CONSTRAINT continuous_agg_mat_hypertable_id_fkey,\n    DROP CONSTRAINT continuous_agg_raw_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.continuous_aggs_bucket_function\n    DROP CONSTRAINT continuous_aggs_bucket_function_mat_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.continuous_aggs_invalidation_threshold\n    DROP CONSTRAINT continuous_aggs_invalidation_threshold_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.dimension\n    DROP CONSTRAINT dimension_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.hypertable\n    DROP CONSTRAINT hypertable_compressed_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.hypertable_data_node\n    DROP CONSTRAINT hypertable_data_node_hypertable_id_fkey;\nALTER TABLE _timescaledb_catalog.tablespace\n    DROP CONSTRAINT tablespace_hypertable_id_fkey;\n\n-- drop dependent views\nALTER EXTENSION timescaledb DROP VIEW timescaledb_information.hypertables;\nALTER EXTENSION timescaledb DROP VIEW timescaledb_information.job_stats;\nALTER EXTENSION timescaledb DROP VIEW timescaledb_information.jobs;\nALTER EXTENSION timescaledb DROP VIEW timescaledb_information.continuous_aggregates;\nALTER EXTENSION timescaledb DROP VIEW timescaledb_information.chunks;\nALTER EXTENSION timescaledb DROP VIEW timescaledb_information.dimensions;\nALTER EXTENSION timescaledb DROP VIEW  _timescaledb_internal.hypertable_chunk_local_size;\nALTER EXTENSION timescaledb DROP VIEW _timescaledb_internal.compressed_chunk_stats;\nALTER EXTENSION timescaledb DROP VIEW timescaledb_experimental.policies;\n\nDROP VIEW timescaledb_information.hypertables;\nDROP VIEW timescaledb_information.job_stats;\nDROP VIEW timescaledb_information.jobs;\nDROP VIEW timescaledb_information.continuous_aggregates;\nDROP VIEW timescaledb_information.chunks;\nDROP VIEW timescaledb_information.dimensions;\nDROP VIEW _timescaledb_internal.hypertable_chunk_local_size;\nDROP VIEW _timescaledb_internal.compressed_chunk_stats;\nDROP VIEW timescaledb_experimental.policies;\n\n-- recreate table\nCREATE TABLE _timescaledb_catalog.hypertable_tmp AS SELECT * FROM _timescaledb_catalog.hypertable;\nCREATE TABLE _timescaledb_catalog.tmp_hypertable_seq_value AS SELECT last_value, is_called FROM _timescaledb_catalog.hypertable_id_seq;\n\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.hypertable;\nALTER EXTENSION timescaledb DROP SEQUENCE _timescaledb_catalog.hypertable_id_seq;\n\nSET timescaledb.restoring = on; -- must disable the hooks otherwise we can't do anything without the table _timescaledb_catalog.hypertable\n\nDROP TABLE _timescaledb_catalog.hypertable;\n\nCREATE SEQUENCE _timescaledb_catalog.hypertable_id_seq MINVALUE 1;\nSELECT setval('_timescaledb_catalog.hypertable_id_seq', last_value, is_called) FROM _timescaledb_catalog.tmp_hypertable_seq_value;\nDROP TABLE _timescaledb_catalog.tmp_hypertable_seq_value;\n\nCREATE TABLE _timescaledb_catalog.hypertable (\n    id INTEGER PRIMARY KEY NOT NULL DEFAULT nextval('_timescaledb_catalog.hypertable_id_seq'),\n    schema_name name NOT NULL,\n    table_name name NOT NULL,\n    associated_schema_name name NOT NULL,\n    associated_table_prefix name NOT NULL,\n    num_dimensions smallint NOT NULL,\n    chunk_sizing_func_schema name NOT NULL,\n    chunk_sizing_func_name name NOT NULL,\n    chunk_target_size bigint NOT NULL, -- size in bytes\n    compression_state smallint NOT NULL DEFAULT 0,\n    compressed_hypertable_id integer,\n    replication_factor smallint NULL,\n    status int NOT NULL DEFAULT 0\n);\n\nSET timescaledb.restoring = off;\n\nINSERT INTO _timescaledb_catalog.hypertable (\n    id,\n    schema_name,\n    table_name,\n    associated_schema_name,\n    associated_table_prefix,\n    num_dimensions,\n    chunk_sizing_func_schema,\n    chunk_sizing_func_name,\n    chunk_target_size,\n    compression_state,\n    compressed_hypertable_id\n)\nSELECT\n    id,\n    schema_name,\n    table_name,\n    associated_schema_name,\n    associated_table_prefix,\n    num_dimensions,\n    chunk_sizing_func_schema,\n    chunk_sizing_func_name,\n    chunk_target_size,\n    compression_state,\n    compressed_hypertable_id\nFROM\n    _timescaledb_catalog.hypertable_tmp\nORDER BY id;\n\nALTER SEQUENCE _timescaledb_catalog.hypertable_id_seq OWNED BY _timescaledb_catalog.hypertable.id;\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.hypertable_id_seq', '');\n\nGRANT SELECT ON _timescaledb_catalog.hypertable TO PUBLIC;\nGRANT SELECT ON _timescaledb_catalog.hypertable_id_seq TO PUBLIC;\n\nDROP TABLE _timescaledb_catalog.hypertable_tmp;\n-- now add any constraints\nALTER TABLE _timescaledb_catalog.hypertable\n    -- ADD CONSTRAINT hypertable_pkey PRIMARY KEY (id),\n    ADD CONSTRAINT hypertable_associated_schema_name_associated_table_prefix_key UNIQUE (associated_schema_name, associated_table_prefix),\n    ADD CONSTRAINT hypertable_table_name_schema_name_key UNIQUE (table_name, schema_name),\n    ADD CONSTRAINT hypertable_schema_name_check CHECK (schema_name != '_timescaledb_catalog'),\n    -- internal compressed hypertables have compression state = 2\n    ADD CONSTRAINT hypertable_dim_compress_check CHECK (num_dimensions > 0 OR compression_state = 2),\n    ADD CONSTRAINT hypertable_chunk_target_size_check CHECK (chunk_target_size >= 0),\n    ADD CONSTRAINT hypertable_compress_check CHECK ( (compression_state = 0 OR compression_state = 1 )  OR (compression_state = 2 AND compressed_hypertable_id IS NULL)),\n    ADD CONSTRAINT hypertable_replication_factor_check CHECK (replication_factor > 0 OR replication_factor = -1),\n    ADD CONSTRAINT hypertable_compressed_hypertable_id_fkey FOREIGN KEY (compressed_hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id);\n\nGRANT SELECT ON TABLE _timescaledb_catalog.hypertable TO PUBLIC;\n\n-- 3. reestablish constraints on other tables\nALTER TABLE _timescaledb_config.bgw_job\n    ADD CONSTRAINT bgw_job_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.chunk\n    ADD CONSTRAINT chunk_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id);\nALTER TABLE _timescaledb_catalog.chunk_index\n    ADD CONSTRAINT chunk_index_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.continuous_agg\n    ADD CONSTRAINT continuous_agg_mat_hypertable_id_fkey FOREIGN KEY (mat_hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE,\n    ADD CONSTRAINT continuous_agg_raw_hypertable_id_fkey FOREIGN KEY (raw_hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.continuous_aggs_bucket_function\n    ADD CONSTRAINT continuous_aggs_bucket_function_mat_hypertable_id_fkey FOREIGN KEY (mat_hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.continuous_aggs_invalidation_threshold\n    ADD CONSTRAINT continuous_aggs_invalidation_threshold_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.dimension\n    ADD CONSTRAINT dimension_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.hypertable_compression\n    ADD CONSTRAINT hypertable_compression_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.hypertable_data_node\n    ADD CONSTRAINT hypertable_data_node_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id);\nALTER TABLE _timescaledb_catalog.tablespace\n    ADD CONSTRAINT tablespace_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE;\n\nDROP FUNCTION IF EXISTS _timescaledb_debug.extension_state;\nDROP SCHEMA IF EXISTS _timescaledb_debug;\n\nDROP FUNCTION IF EXISTS _timescaledb_internal.hypertable_constraint_add_table_fk_constraint;\n\nDROP FUNCTION _timescaledb_functions.constraint_clone;\n\nCREATE FUNCTION _timescaledb_functions.hypertable_constraint_add_table_fk_constraint(user_ht_constraint_name name,user_ht_schema_name name,user_ht_table_name name,compress_ht_id   integer) RETURNS void LANGUAGE PLPGSQL AS $$BEGIN END$$ SET search_path TO pg_catalog,pg_temp;\n\nCREATE FUNCTION _timescaledb_functions.chunks_in(record RECORD, chunks INTEGER[]) RETURNS BOOL\nAS 'BEGIN END' LANGUAGE PLPGSQL SET search_path TO pg_catalog,pg_temp;\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.metadata', $$\n  WHERE KEY = 'exported_uuid' $$);\n\nDROP TRIGGER metadata_insert_trigger ON _timescaledb_catalog.metadata;\nDROP FUNCTION _timescaledb_functions.metadata_insert_trigger();\n\nDROP FUNCTION IF EXISTS _timescaledb_functions.get_orderby_defaults(regclass,text[]);\nDROP FUNCTION IF EXISTS _timescaledb_functions.get_segmentby_defaults(regclass);\n\n--- re-include in the pg_dump config\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_cache.cache_inval_hypertable', '');\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_cache.cache_inval_extension', '');\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_cache.cache_inval_bgw_job', '');\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_internal.job_errors', '');\n\n-- Remove unwanted entry from extconfig and extcondition in pg_extension\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.hypertable;\n-- Associate the above table back to keep the dependencies safe\nALTER EXTENSION timescaledb ADD TABLE _timescaledb_catalog.hypertable;\n-- include this now in the config\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.hypertable', '');\nDROP FUNCTION IF EXISTS _timescaledb_functions.relation_approximate_size(relation REGCLASS);\nDROP FUNCTION IF EXISTS @extschema@.hypertable_approximate_detailed_size(relation REGCLASS);\nDROP FUNCTION IF EXISTS @extschema@.hypertable_approximate_size(hypertable REGCLASS);\n\nDROP FUNCTION IF EXISTS @extschema@.compress_chunk;\nCREATE FUNCTION @extschema@.compress_chunk(uncompressed_chunk REGCLASS, if_not_compressed BOOLEAN = true) RETURNS REGCLASS AS '' LANGUAGE SQL SET search_path TO pg_catalog,pg_temp;\n\n"
  },
  {
    "path": "sql/updates/2.14.0--2.14.1.sql",
    "content": "\nCREATE VIEW timescaledb_information.hypertable_compression_settings AS\n\tSELECT\n\t\tformat('%I.%I',ht.schema_name,ht.table_name)::regclass AS hypertable,\n\t\tarray_to_string(segmentby,',') AS segmentby,\n\t\tun.orderby,\n    d.compress_interval_length\n  FROM _timescaledb_catalog.hypertable ht\n  JOIN LATERAL (\n    SELECT\n      CASE WHEN d.column_type = ANY(ARRAY['timestamp','timestamptz','date']::regtype[]) THEN\n        _timescaledb_functions.to_interval(d.compress_interval_length)::text\n      ELSE\n        d.compress_interval_length::text\n      END AS compress_interval_length\n    FROM _timescaledb_catalog.dimension d WHERE d.hypertable_id = ht.id ORDER BY id LIMIT 1\n  ) d ON true\n  LEFT JOIN _timescaledb_catalog.compression_settings s ON format('%I.%I',ht.schema_name,ht.table_name)::regclass = s.relid\n\tLEFT JOIN LATERAL (\n\t\tSELECT\n\t\t\tstring_agg(\n\t\t\t\tformat('%I%s%s',orderby,\n\t\t\t\t\tCASE WHEN \"desc\" THEN ' DESC' ELSE '' END,\n\t\t\t\t\tCASE WHEN nullsfirst AND NOT \"desc\" THEN ' NULLS FIRST' WHEN NOT nullsfirst AND \"desc\" THEN ' NULLS LAST' ELSE '' END\n\t\t\t\t)\n\t\t\t,',') AS orderby\n\t\tFROM unnest(s.orderby, s.orderby_desc, s.orderby_nullsfirst) un(orderby, \"desc\", nullsfirst)\n\t) un ON true;\n\nCREATE VIEW timescaledb_information.chunk_compression_settings AS\n\tSELECT\n\t\tformat('%I.%I',ht.schema_name,ht.table_name)::regclass AS hypertable,\n\t\tformat('%I.%I',ch.schema_name,ch.table_name)::regclass AS chunk,\n\t\tarray_to_string(segmentby,',') AS segmentby,\n\t\tun.orderby\n\tFROM _timescaledb_catalog.hypertable ht\n\tINNER JOIN _timescaledb_catalog.chunk ch ON ch.hypertable_id = ht.id\n  INNER JOIN _timescaledb_catalog.chunk ch2 ON ch2.id = ch.compressed_chunk_id\n  LEFT JOIN _timescaledb_catalog.compression_settings s ON format('%I.%I',ch2.schema_name,ch2.table_name)::regclass = s.relid\n\tLEFT JOIN LATERAL (\n\t\tSELECT\n\t\t\tstring_agg(\n\t\t\t\tformat('%I%s%s',orderby,\n\t\t\t\t\tCASE WHEN \"desc\" THEN ' DESC' ELSE '' END,\n\t\t\t\t\tCASE WHEN nullsfirst AND NOT \"desc\" THEN ' NULLS FIRST' WHEN NOT nullsfirst AND \"desc\" THEN ' NULLS LAST' ELSE '' END\n\t\t\t),',') AS orderby\n\t\tFROM unnest(s.orderby, s.orderby_desc, s.orderby_nullsfirst) un(orderby, \"desc\", nullsfirst)\n\t) un ON true;\n\nINSERT INTO _timescaledb_catalog.compression_settings\nSELECT\n\tformat('%I.%I',ch.schema_name,ch.table_name)::regclass,s.segmentby,s.orderby,s.orderby_desc,s.orderby_nullsfirst\nFROM _timescaledb_catalog.hypertable ht1\nINNER JOIN _timescaledb_catalog.hypertable ht2 ON ht2.id = ht1.compressed_hypertable_id\nINNER JOIN _timescaledb_catalog.compression_settings s ON s.relid = format('%I.%I',ht1.schema_name,ht1.table_name)::regclass\nINNER JOIN _timescaledb_catalog.chunk ch ON ch.hypertable_id = ht2.id ON CONFLICT DO NOTHING;\n\n"
  },
  {
    "path": "sql/updates/2.14.1--2.14.0.sql",
    "content": "\nDROP VIEW IF EXISTS timescaledb_information.hypertable_compression_settings;\nDROP VIEW IF EXISTS timescaledb_information.chunk_compression_settings;\n\n"
  },
  {
    "path": "sql/updates/2.14.1--2.14.2.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.14.2--2.14.1.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.14.2--2.15.0.sql",
    "content": "-- Remove multi-node CAGG support\nDROP FUNCTION IF EXISTS _timescaledb_internal.invalidation_cagg_log_add_entry(integer,bigint,bigint);\nDROP FUNCTION IF EXISTS _timescaledb_internal.invalidation_hyper_log_add_entry(integer,bigint,bigint);\nDROP FUNCTION IF EXISTS _timescaledb_internal.materialization_invalidation_log_delete(integer);\nDROP FUNCTION IF EXISTS _timescaledb_internal.invalidation_process_cagg_log(integer,integer,regtype,bigint,bigint,integer[],bigint[],bigint[]);\nDROP FUNCTION IF EXISTS _timescaledb_internal.invalidation_process_cagg_log(integer,integer,regtype,bigint,bigint,integer[],bigint[],bigint[],text[]);\nDROP FUNCTION IF EXISTS _timescaledb_internal.invalidation_process_hypertable_log(integer,integer,regtype,integer[],bigint[],bigint[]);\nDROP FUNCTION IF EXISTS _timescaledb_internal.invalidation_process_hypertable_log(integer,integer,regtype,integer[],bigint[],bigint[],text[]);\nDROP FUNCTION IF EXISTS _timescaledb_internal.hypertable_invalidation_log_delete(integer);\n\nDROP FUNCTION IF EXISTS _timescaledb_functions.invalidation_cagg_log_add_entry(integer,bigint,bigint);\nDROP FUNCTION IF EXISTS _timescaledb_functions.invalidation_hyper_log_add_entry(integer,bigint,bigint);\nDROP FUNCTION IF EXISTS _timescaledb_functions.materialization_invalidation_log_delete(integer);\nDROP FUNCTION IF EXISTS _timescaledb_functions.invalidation_process_cagg_log(integer,integer,regtype,bigint,bigint,integer[],bigint[],bigint[]);\nDROP FUNCTION IF EXISTS _timescaledb_functions.invalidation_process_cagg_log(integer,integer,regtype,bigint,bigint,integer[],bigint[],bigint[],text[]);\nDROP FUNCTION IF EXISTS _timescaledb_functions.invalidation_process_hypertable_log(integer,integer,regtype,integer[],bigint[],bigint[]);\nDROP FUNCTION IF EXISTS _timescaledb_functions.invalidation_process_hypertable_log(integer,integer,regtype,integer[],bigint[],bigint[],text[]);\nDROP FUNCTION IF EXISTS _timescaledb_functions.hypertable_invalidation_log_delete(integer);\n\n-- Remove chunk metadata when marked as dropped\nCREATE FUNCTION _timescaledb_functions.remove_dropped_chunk_metadata(_hypertable_id INTEGER)\nRETURNS INTEGER LANGUAGE plpgsql AS $$\nDECLARE\n  _chunk_id INTEGER;\n  _removed INTEGER := 0;\nBEGIN\n  FOR _chunk_id IN\n    SELECT id FROM _timescaledb_catalog.chunk\n    WHERE hypertable_id = _hypertable_id\n    AND dropped IS TRUE\n    AND NOT EXISTS (\n        SELECT FROM information_schema.tables\n        WHERE tables.table_schema = chunk.schema_name\n        AND tables.table_name = chunk.table_name\n    )\n    AND NOT EXISTS (\n        SELECT FROM _timescaledb_catalog.hypertable\n        JOIN _timescaledb_catalog.continuous_agg ON continuous_agg.raw_hypertable_id = hypertable.id\n        WHERE hypertable.id = chunk.hypertable_id\n        -- for the old caggs format we need to keep chunk metadata for dropped chunks\n        AND continuous_agg.finalized IS FALSE\n    )\n  LOOP\n    _removed := _removed + 1;\n    RAISE INFO 'Removing metadata of chunk % from hypertable %', _chunk_id, _hypertable_id;\n\n    WITH _dimension_slice_remove AS (\n        DELETE FROM _timescaledb_catalog.dimension_slice\n        USING _timescaledb_catalog.chunk_constraint\n        WHERE dimension_slice.id = chunk_constraint.dimension_slice_id\n        AND chunk_constraint.chunk_id = _chunk_id\n        AND NOT EXISTS (\n            SELECT FROM _timescaledb_catalog.chunk_constraint cc\n            WHERE cc.chunk_id <> _chunk_id\n            AND cc.dimension_slice_id = dimension_slice.id\n        )\n        RETURNING _timescaledb_catalog.dimension_slice.id\n    )\n    DELETE FROM _timescaledb_catalog.chunk_constraint\n    USING _dimension_slice_remove\n    WHERE chunk_constraint.dimension_slice_id = _dimension_slice_remove.id;\n\n    DELETE FROM _timescaledb_catalog.chunk_constraint\n    WHERE chunk_constraint.chunk_id = _chunk_id;\n\n    DELETE FROM _timescaledb_internal.bgw_policy_chunk_stats\n    WHERE bgw_policy_chunk_stats.chunk_id = _chunk_id;\n\n    DELETE FROM _timescaledb_catalog.chunk_index\n    WHERE chunk_index.chunk_id = _chunk_id;\n\n    DELETE FROM _timescaledb_catalog.compression_chunk_size\n    WHERE compression_chunk_size.chunk_id = _chunk_id\n    OR compression_chunk_size.compressed_chunk_id = _chunk_id;\n\n    DELETE FROM _timescaledb_catalog.chunk\n    WHERE chunk.id = _chunk_id\n    OR chunk.compressed_chunk_id = _chunk_id;\n  END LOOP;\n\n  RETURN _removed;\nEND;\n$$ SET search_path TO pg_catalog, pg_temp;\n\nSELECT _timescaledb_functions.remove_dropped_chunk_metadata(id) AS chunks_metadata_removed\nFROM _timescaledb_catalog.hypertable;\n\n--\n-- Rebuild the catalog table `_timescaledb_catalog.continuous_aggs_bucket_function`\n--\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.cagg_get_bucket_function(\n    mat_hypertable_id INTEGER\n) RETURNS regprocedure AS '@MODULE_PATHNAME@', 'ts_continuous_agg_get_bucket_function' LANGUAGE C STRICT VOLATILE;\n\n-- Since we need now the regclass of the used bucket function, we have to recover it\n-- by parsing the view query by calling 'cagg_get_bucket_function'.\nCREATE TABLE _timescaledb_catalog._tmp_continuous_aggs_bucket_function AS\n    SELECT\n      mat_hypertable_id,\n      _timescaledb_functions.cagg_get_bucket_function(mat_hypertable_id),\n      bucket_width,\n      origin,\n      NULL::text AS bucket_offset,\n      timezone,\n      false AS bucket_fixed_width\n    FROM\n      _timescaledb_catalog.continuous_aggs_bucket_function\n    ORDER BY\n         mat_hypertable_id;\n\nALTER EXTENSION timescaledb\n    DROP TABLE _timescaledb_catalog.continuous_aggs_bucket_function;\n\nDROP TABLE _timescaledb_catalog.continuous_aggs_bucket_function;\n\nCREATE TABLE _timescaledb_catalog.continuous_aggs_bucket_function (\n  mat_hypertable_id integer NOT NULL,\n  -- The bucket function\n  bucket_func regprocedure NOT NULL,\n  -- `bucket_width` argument of the function, e.g. \"1 month\"\n  bucket_width text NOT NULL,\n  -- optional `origin` argument of the function provided by the user\n  bucket_origin text,\n  -- optional `offset` argument of the function provided by the user\n  bucket_offset text,\n  -- optional `timezone` argument of the function provided by the user\n  bucket_timezone text,\n  -- fixed or variable sized bucket\n  bucket_fixed_width bool NOT NULL,\n  -- table constraints\n  CONSTRAINT continuous_aggs_bucket_function_pkey PRIMARY KEY (mat_hypertable_id),\n  CONSTRAINT continuous_aggs_bucket_function_mat_hypertable_id_fkey FOREIGN KEY (mat_hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE\n);\n\nINSERT INTO _timescaledb_catalog.continuous_aggs_bucket_function\n  SELECT * FROM _timescaledb_catalog._tmp_continuous_aggs_bucket_function;\n\nDROP TABLE _timescaledb_catalog._tmp_continuous_aggs_bucket_function;\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.continuous_aggs_bucket_function', '');\n\nGRANT SELECT ON TABLE _timescaledb_catalog.continuous_aggs_bucket_function TO PUBLIC;\n\nANALYZE _timescaledb_catalog.continuous_aggs_bucket_function;\n\nALTER EXTENSION timescaledb DROP FUNCTION _timescaledb_functions.cagg_get_bucket_function(INTEGER);\nDROP FUNCTION IF EXISTS _timescaledb_functions.cagg_get_bucket_function(INTEGER);\n\n--\n-- End rebuild the catalog table `_timescaledb_catalog.continuous_aggs_bucket_function`\n--\n\n-- Convert _timescaledb_catalog.continuous_aggs_bucket_function.bucket_origin to TimestampTZ\nUPDATE _timescaledb_catalog.continuous_aggs_bucket_function\n   SET bucket_origin = bucket_origin::timestamp::timestamptz::text\n   WHERE length(bucket_origin) > 1;\n\n-- Historically, we have used empty strings for undefined bucket_origin and timezone\n-- attributes. This is now replaced by proper NULL values. We use TRIM() to ensure we handle empty string well.\nUPDATE _timescaledb_catalog.continuous_aggs_bucket_function SET bucket_origin = NULL WHERE TRIM(bucket_origin) = '';\nUPDATE _timescaledb_catalog.continuous_aggs_bucket_function SET bucket_timezone = NULL WHERE TRIM(bucket_timezone) = '';\n\n-- So far, there were no difference between 0 and -1 retries. Since now on, 0 means no retries. Updating the retry\n-- count of existing jobs to -1 to keep the current semantics.\nUPDATE _timescaledb_config.bgw_job SET max_retries = -1 WHERE max_retries = 0;\n\nDROP FUNCTION IF EXISTS _timescaledb_functions.get_chunk_relstats;\nDROP FUNCTION IF EXISTS _timescaledb_functions.get_chunk_colstats;\nDROP FUNCTION IF EXISTS _timescaledb_internal.get_chunk_relstats;\nDROP FUNCTION IF EXISTS _timescaledb_internal.get_chunk_colstats;\n\n-- In older TSDB versions, we disabled autovacuum for compressed chunks\n-- to keep the statistics. However, this restriction was removed in\n-- #5118 but no migration was performed to remove the custom\n-- autovacuum setting for existing chunks.\nDO $$\nDECLARE\n  chunk regclass;\nBEGIN\n  FOR chunk IN\n    SELECT pg_catalog.format('%I.%I', schema_name, table_name)::regclass\n      FROM _timescaledb_catalog.chunk c\n      JOIN pg_catalog.pg_class AS pc ON (pc.oid=format('%I.%I', schema_name, table_name)::regclass)\n      CROSS JOIN unnest(reloptions) AS u(option)\n      WHERE\n        dropped = false\n        AND osm_chunk = false\n        AND option LIKE 'autovacuum_enabled%'\n  LOOP\n    EXECUTE pg_catalog.format('ALTER TABLE %s RESET (autovacuum_enabled);', chunk::text);\n  END LOOP;\nEND\n$$;\n\n--\n-- Rebuild the catalog table `_timescaledb_catalog.continuous_agg`\n--\n\n-- (1) Create missing entries in _timescaledb_catalog.continuous_aggs_bucket_function\nCREATE OR REPLACE FUNCTION _timescaledb_functions.cagg_get_bucket_function(\n    mat_hypertable_id INTEGER\n) RETURNS regprocedure AS '@MODULE_PATHNAME@', 'ts_continuous_agg_get_bucket_function' LANGUAGE C STRICT VOLATILE;\n\n-- Make sure function points to the new version of TSDB\nCREATE OR REPLACE FUNCTION _timescaledb_functions.to_interval(unixtime_us BIGINT) RETURNS INTERVAL\n    AS '@MODULE_PATHNAME@', 'ts_pg_unix_microseconds_to_interval' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\n-- We need to create entries in continuous_aggs_bucket_function for all CAggs that were treated so far\n-- as fixed indicated by a bucket_width != -1\nINSERT INTO _timescaledb_catalog.continuous_aggs_bucket_function\n  SELECT\n  mat_hypertable_id,\n  _timescaledb_functions.cagg_get_bucket_function(mat_hypertable_id),\n  -- Intervals needs to be converted into the proper interval format\n  -- Function name could be prefixed with 'public.'. Therefore LIKE instead of starts_with is used\n  CASE WHEN _timescaledb_functions.cagg_get_bucket_function(mat_hypertable_id)::text LIKE '%time_bucket(interval,%' THEN\n    _timescaledb_functions.to_interval(bucket_width)::text\n  ELSE\n    bucket_width::text\n  END,\n  NULL, -- bucket_origin\n  NULL, -- bucket_offset\n  NULL, -- bucket_timezone\n  true  -- bucket_fixed_width\n  FROM _timescaledb_catalog.continuous_agg WHERE bucket_width != -1;\n\nALTER EXTENSION timescaledb DROP FUNCTION _timescaledb_functions.cagg_get_bucket_function(INTEGER);\nDROP FUNCTION IF EXISTS _timescaledb_functions.cagg_get_bucket_function(INTEGER);\n\n-- (2) Rebuild catalog table\nDROP VIEW IF EXISTS timescaledb_experimental.policies;\nDROP VIEW IF EXISTS timescaledb_information.hypertables;\nDROP VIEW IF EXISTS timescaledb_information.continuous_aggregates;\n\nDROP PROCEDURE IF EXISTS @extschema@.cagg_migrate (REGCLASS, BOOLEAN, BOOLEAN);\nDROP FUNCTION IF EXISTS _timescaledb_internal.cagg_migrate_pre_validation (TEXT, TEXT, TEXT);\nDROP FUNCTION IF EXISTS _timescaledb_functions.cagg_migrate_pre_validation (TEXT, TEXT, TEXT);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_create_plan (_timescaledb_catalog.continuous_agg, TEXT, BOOLEAN, BOOLEAN);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_create_plan (_timescaledb_catalog.continuous_agg, TEXT, BOOLEAN, BOOLEAN);\n\nDROP FUNCTION IF EXISTS _timescaledb_functions.cagg_migrate_plan_exists (INTEGER);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_plan (_timescaledb_catalog.continuous_agg);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_plan (_timescaledb_catalog.continuous_agg);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_create_new_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_create_new_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_disable_policies (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_disable_policies (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_enable_policies (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_enable_policies (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_copy_policies (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_copy_policies (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_refresh_new_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_refresh_new_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_copy_data (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_copy_data (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_override_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_override_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_drop_old_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_drop_old_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\n\nALTER TABLE _timescaledb_catalog.continuous_aggs_materialization_invalidation_log\n    DROP CONSTRAINT continuous_aggs_materialization_invalid_materialization_id_fkey;\n\nALTER TABLE _timescaledb_catalog.continuous_aggs_watermark\n    DROP CONSTRAINT continuous_aggs_watermark_mat_hypertable_id_fkey;\n\nALTER EXTENSION timescaledb\n    DROP TABLE _timescaledb_catalog.continuous_agg;\n\nCREATE TABLE _timescaledb_catalog._tmp_continuous_agg AS\n    SELECT\n        mat_hypertable_id,\n        raw_hypertable_id,\n        parent_mat_hypertable_id,\n        user_view_schema,\n        user_view_name,\n        partial_view_schema,\n        partial_view_name,\n        direct_view_schema,\n        direct_view_name,\n        materialized_only,\n        finalized\n    FROM\n        _timescaledb_catalog.continuous_agg\n    ORDER BY\n        mat_hypertable_id;\n\nDROP TABLE _timescaledb_catalog.continuous_agg;\n\nCREATE TABLE _timescaledb_catalog.continuous_agg (\n    mat_hypertable_id integer NOT NULL,\n    raw_hypertable_id integer NOT NULL,\n    parent_mat_hypertable_id integer,\n    user_view_schema name NOT NULL,\n    user_view_name name NOT NULL,\n    partial_view_schema name NOT NULL,\n    partial_view_name name NOT NULL,\n    direct_view_schema name NOT NULL,\n    direct_view_name name NOT NULL,\n    materialized_only bool NOT NULL DEFAULT FALSE,\n    finalized bool NOT NULL DEFAULT TRUE,\n    -- table constraints\n    CONSTRAINT continuous_agg_pkey PRIMARY KEY (mat_hypertable_id),\n    CONSTRAINT continuous_agg_partial_view_schema_partial_view_name_key UNIQUE (partial_view_schema, partial_view_name),\n    CONSTRAINT continuous_agg_user_view_schema_user_view_name_key UNIQUE (user_view_schema, user_view_name),\n    CONSTRAINT continuous_agg_mat_hypertable_id_fkey\n        FOREIGN KEY (mat_hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE,\n    CONSTRAINT continuous_agg_raw_hypertable_id_fkey\n        FOREIGN KEY (raw_hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE,\n    CONSTRAINT continuous_agg_parent_mat_hypertable_id_fkey\n        FOREIGN KEY (parent_mat_hypertable_id)\n        REFERENCES _timescaledb_catalog.continuous_agg (mat_hypertable_id) ON DELETE CASCADE\n);\n\nINSERT INTO _timescaledb_catalog.continuous_agg\nSELECT * FROM _timescaledb_catalog._tmp_continuous_agg;\nDROP TABLE _timescaledb_catalog._tmp_continuous_agg;\n\nCREATE INDEX continuous_agg_raw_hypertable_id_idx ON _timescaledb_catalog.continuous_agg (raw_hypertable_id);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.continuous_agg', '');\n\nGRANT SELECT ON TABLE _timescaledb_catalog.continuous_agg TO PUBLIC;\n\nALTER TABLE _timescaledb_catalog.continuous_aggs_materialization_invalidation_log\n    ADD CONSTRAINT continuous_aggs_materialization_invalid_materialization_id_fkey\n        FOREIGN KEY (materialization_id)\n        REFERENCES _timescaledb_catalog.continuous_agg(mat_hypertable_id) ON DELETE CASCADE;\n\nALTER TABLE _timescaledb_catalog.continuous_aggs_watermark\n    ADD CONSTRAINT continuous_aggs_watermark_mat_hypertable_id_fkey\n        FOREIGN KEY (mat_hypertable_id)\n        REFERENCES _timescaledb_catalog.continuous_agg (mat_hypertable_id) ON DELETE CASCADE;\n\nANALYZE _timescaledb_catalog.continuous_agg;\n\n--\n-- END Rebuild the catalog table `_timescaledb_catalog.continuous_agg`\n--\n\n--\n-- START bgw_job_stat_history\n--\nDROP VIEW IF EXISTS timescaledb_information.job_errors;\n\nCREATE SEQUENCE _timescaledb_internal.bgw_job_stat_history_id_seq MINVALUE 1;\n\nCREATE TABLE _timescaledb_internal.bgw_job_stat_history (\n  id INTEGER NOT NULL DEFAULT nextval('_timescaledb_internal.bgw_job_stat_history_id_seq'),\n  job_id INTEGER NOT NULL,\n  pid INTEGER,\n  execution_start TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n  execution_finish TIMESTAMPTZ,\n  succeeded boolean NOT NULL DEFAULT FALSE,\n  data jsonb,\n  -- table constraints\n  CONSTRAINT bgw_job_stat_history_pkey PRIMARY KEY (id)\n);\n\nALTER SEQUENCE _timescaledb_internal.bgw_job_stat_history_id_seq OWNED BY _timescaledb_internal.bgw_job_stat_history.id;\n\nCREATE INDEX bgw_job_stat_history_job_id_idx ON _timescaledb_internal.bgw_job_stat_history (job_id);\n\nREVOKE ALL ON _timescaledb_internal.bgw_job_stat_history FROM PUBLIC;\n\nINSERT INTO _timescaledb_internal.bgw_job_stat_history (job_id, pid, execution_start, execution_finish, data)\nSELECT\n  job_errors.job_id,\n  job_errors.pid,\n  job_errors.start_time,\n  job_errors.finish_time,\n  jsonb_build_object('job', to_jsonb(bgw_job.*)) || jsonb_build_object('error_data', job_errors.error_data)\nFROM\n  _timescaledb_internal.job_errors\n  LEFT JOIN _timescaledb_config.bgw_job ON bgw_job.id = job_errors.job_id\nORDER BY\n  job_errors.job_id, job_errors.start_time;\n\nALTER EXTENSION timescaledb\n    DROP TABLE _timescaledb_internal.job_errors;\n\nDROP TABLE _timescaledb_internal.job_errors;\n\nUPDATE _timescaledb_config.bgw_job SET scheduled = false WHERE id = 2;\nINSERT INTO _timescaledb_config.bgw_job (\n    id,\n    application_name,\n    schedule_interval,\n    max_runtime,\n    max_retries,\n    retry_period,\n    proc_schema,\n    proc_name,\n    owner,\n    scheduled,\n    config,\n    check_schema,\n    check_name,\n    fixed_schedule,\n    initial_start\n)\nVALUES\n(\n    3,\n    'Job History Log Retention Policy [3]',\n    INTERVAL '1 month',\n    INTERVAL '1 hour',\n    -1,\n    INTERVAL '1h',\n    '_timescaledb_functions',\n    'policy_job_stat_history_retention',\n    pg_catalog.quote_ident(current_role)::regrole,\n    true,\n    '{\"drop_after\":\"1 month\"}',\n    '_timescaledb_functions',\n    'policy_job_stat_history_retention_check',\n    true,\n    '2000-01-01 00:00:00+00'::timestamptz\n) ON CONFLICT (id) DO NOTHING;\n\nDROP FUNCTION IF EXISTS _timescaledb_internal.policy_job_error_retention(job_id integer,config jsonb);\nDROP FUNCTION IF EXISTS _timescaledb_internal.policy_job_error_retention_check(config jsonb);\nDROP FUNCTION IF EXISTS _timescaledb_functions.policy_job_error_retention(job_id integer,config jsonb);\nDROP FUNCTION IF EXISTS _timescaledb_functions.policy_job_error_retention_check(config jsonb);\n\n--\n-- END bgw_job_stat_history\n--\n\n-- Migrate existing CAggs using time_bucket_ng to time_bucket\nDO $$\nDECLARE\n  cagg_name regclass;\n  caggs text;\nBEGIN\n    SELECT string_agg(pg_catalog.format('%I.%I', user_view_schema, user_view_name), ', ')\n      INTO caggs\n      FROM _timescaledb_catalog.continuous_agg cagg\n      JOIN _timescaledb_catalog.continuous_aggs_bucket_function AS bf ON (cagg.mat_hypertable_id = bf.mat_hypertable_id)\n      WHERE\n         bf.bucket_func::text LIKE '%time_bucket_ng%';\n\n    IF caggs IS NOT NULL THEN\n      RAISE\n        WARNING 'continuous aggregates with time_bucket_ng found, please use _timescaledb_functions.cagg_migrate_to_time_bucket to migrate caggs manually after extension update'\n        USING\n          DETAIL = format('Continuous Aggregates: %s', caggs);\n    END IF;\nEND\n$$;\n"
  },
  {
    "path": "sql/updates/2.15.0--2.14.2.sql",
    "content": "DROP FUNCTION IF EXISTS _timescaledb_functions.remove_dropped_chunk_metadata(INTEGER);\n\n\n--\n-- Rebuild the catalog table `_timescaledb_catalog.continuous_agg`\n--\nDROP VIEW IF EXISTS timescaledb_experimental.policies;\nDROP VIEW IF EXISTS timescaledb_information.hypertables;\nDROP VIEW IF EXISTS timescaledb_information.continuous_aggregates;\n\nDROP PROCEDURE IF EXISTS @extschema@.cagg_migrate (REGCLASS, BOOLEAN, BOOLEAN);\nDROP FUNCTION IF EXISTS _timescaledb_internal.cagg_migrate_pre_validation (TEXT, TEXT, TEXT);\nDROP FUNCTION IF EXISTS _timescaledb_functions.cagg_migrate_pre_validation (TEXT, TEXT, TEXT);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_create_plan (_timescaledb_catalog.continuous_agg, TEXT, BOOLEAN, BOOLEAN);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_create_plan (_timescaledb_catalog.continuous_agg, TEXT, BOOLEAN, BOOLEAN);\n\nDROP FUNCTION IF EXISTS _timescaledb_functions.cagg_migrate_plan_exists (INTEGER);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_plan (_timescaledb_catalog.continuous_agg);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_plan (_timescaledb_catalog.continuous_agg);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_create_new_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_create_new_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_disable_policies (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_disable_policies (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_enable_policies (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_enable_policies (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_copy_policies (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_copy_policies (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_refresh_new_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_refresh_new_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_copy_data (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_copy_data (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_override_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_override_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_drop_old_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_drop_old_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\n\nALTER TABLE _timescaledb_catalog.continuous_aggs_materialization_invalidation_log\n    DROP CONSTRAINT continuous_aggs_materialization_invalid_materialization_id_fkey;\n\nALTER TABLE _timescaledb_catalog.continuous_aggs_watermark\n    DROP CONSTRAINT continuous_aggs_watermark_mat_hypertable_id_fkey;\n\nALTER EXTENSION timescaledb\n    DROP TABLE _timescaledb_catalog.continuous_agg;\n\nCREATE TABLE _timescaledb_catalog._tmp_continuous_agg AS\n    SELECT\n        mat_hypertable_id,\n        raw_hypertable_id,\n        parent_mat_hypertable_id,\n        user_view_schema,\n        user_view_name,\n        partial_view_schema,\n        partial_view_name,\n        -1::bigint as bucket_width, -- -1 means variable width. Will be modified soon if not variable.\n        direct_view_schema,\n        direct_view_name,\n        materialized_only,\n        finalized\n    FROM\n        _timescaledb_catalog.continuous_agg\n    ORDER BY\n        mat_hypertable_id;\n\n-- Migrate CAggs with fixed bucket on interval back\nWITH fixed_buckets AS (\n    SELECT * FROM _timescaledb_catalog.continuous_aggs_bucket_function WHERE bucket_fixed_width = true AND bucket_func::text LIKE '%time_bucket(interval%'\n)\nUPDATE _timescaledb_catalog._tmp_continuous_agg cagg\n    SET bucket_width = _timescaledb_functions.interval_to_usec(fb.bucket_width::interval)\n    FROM fixed_buckets fb\n    WHERE cagg.mat_hypertable_id = fb.mat_hypertable_id;\n\n-- Migrate CAggs with fixed bucket on integer back\nWITH fixed_buckets AS (\n    SELECT * FROM _timescaledb_catalog.continuous_aggs_bucket_function WHERE bucket_fixed_width = true AND bucket_func::text NOT LIKE '%time_bucket(interval%'\n)\nUPDATE _timescaledb_catalog._tmp_continuous_agg cagg\n    SET bucket_width = fb.bucket_width::bigint\n    FROM fixed_buckets fb\n    WHERE cagg.mat_hypertable_id = fb.mat_hypertable_id;\n\nDELETE FROM _timescaledb_catalog.continuous_aggs_bucket_function WHERE bucket_fixed_width = true;\n\nDROP TABLE _timescaledb_catalog.continuous_agg;\n\nCREATE TABLE _timescaledb_catalog.continuous_agg (\n    mat_hypertable_id integer NOT NULL,\n    raw_hypertable_id integer NOT NULL,\n    parent_mat_hypertable_id integer,\n    user_view_schema name NOT NULL,\n    user_view_name name NOT NULL,\n    partial_view_schema name NOT NULL,\n    partial_view_name name NOT NULL,\n    bucket_width bigint NOT NULL,\n    direct_view_schema name NOT NULL,\n    direct_view_name name NOT NULL,\n    materialized_only bool NOT NULL DEFAULT FALSE,\n    finalized bool NOT NULL DEFAULT TRUE,\n    -- table constraints\n    CONSTRAINT continuous_agg_pkey PRIMARY KEY (mat_hypertable_id),\n    CONSTRAINT continuous_agg_partial_view_schema_partial_view_name_key UNIQUE (partial_view_schema, partial_view_name),\n    CONSTRAINT continuous_agg_user_view_schema_user_view_name_key UNIQUE (user_view_schema, user_view_name),\n    CONSTRAINT continuous_agg_mat_hypertable_id_fkey\n        FOREIGN KEY (mat_hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE,\n    CONSTRAINT continuous_agg_raw_hypertable_id_fkey\n        FOREIGN KEY (raw_hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE,\n    CONSTRAINT continuous_agg_parent_mat_hypertable_id_fkey\n        FOREIGN KEY (parent_mat_hypertable_id)\n        REFERENCES _timescaledb_catalog.continuous_agg (mat_hypertable_id) ON DELETE CASCADE\n);\n\nINSERT INTO _timescaledb_catalog.continuous_agg\nSELECT * FROM _timescaledb_catalog._tmp_continuous_agg;\nDROP TABLE _timescaledb_catalog._tmp_continuous_agg;\n\nCREATE INDEX continuous_agg_raw_hypertable_id_idx ON _timescaledb_catalog.continuous_agg (raw_hypertable_id);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.continuous_agg', '');\n\nGRANT SELECT ON TABLE _timescaledb_catalog.continuous_agg TO PUBLIC;\n\nALTER TABLE _timescaledb_catalog.continuous_aggs_materialization_invalidation_log\n    ADD CONSTRAINT continuous_aggs_materialization_invalid_materialization_id_fkey\n        FOREIGN KEY (materialization_id)\n        REFERENCES _timescaledb_catalog.continuous_agg(mat_hypertable_id) ON DELETE CASCADE;\n\nALTER TABLE _timescaledb_catalog.continuous_aggs_watermark\n    ADD CONSTRAINT continuous_aggs_watermark_mat_hypertable_id_fkey\n        FOREIGN KEY (mat_hypertable_id)\n        REFERENCES _timescaledb_catalog.continuous_agg (mat_hypertable_id) ON DELETE CASCADE;\n\nANALYZE _timescaledb_catalog.continuous_agg;\n\n--\n-- END Rebuild the catalog table `_timescaledb_catalog.continuous_agg`\n--\n\n--\n-- Rebuild the catalog table `_timescaledb_catalog.continuous_aggs_bucket_function`\n--\nUPDATE _timescaledb_catalog.continuous_aggs_bucket_function SET bucket_origin = '' WHERE bucket_origin IS NULL;\nUPDATE _timescaledb_catalog.continuous_aggs_bucket_function SET bucket_timezone = '' WHERE bucket_timezone IS NULL;\n\nCREATE TABLE _timescaledb_catalog._tmp_continuous_aggs_bucket_function AS\n    SELECT\n      mat_hypertable_id,\n      CASE WHEN bucket_func::text like 'timescaledb_experimental%' THEN true ELSE false END,\n      split_part(bucket_func::regproc::text, '.', 2),\n      bucket_width,\n      bucket_origin,\n      bucket_timezone\n    FROM\n      _timescaledb_catalog.continuous_aggs_bucket_function\n    ORDER BY\n         mat_hypertable_id;\n\nALTER EXTENSION timescaledb\n    DROP TABLE _timescaledb_catalog.continuous_aggs_bucket_function;\n\nDROP TABLE _timescaledb_catalog.continuous_aggs_bucket_function;\n\nCREATE TABLE _timescaledb_catalog.continuous_aggs_bucket_function (\n  mat_hypertable_id integer NOT NULL,\n  -- The schema of the function. Equals TRUE for \"timescaledb_experimental\", FALSE otherwise.\n  experimental bool NOT NULL,\n  -- Name of the bucketing function, e.g. \"time_bucket\" or \"time_bucket_ng\"\n  name text NOT NULL,\n  -- `bucket_width` argument of the function, e.g. \"1 month\"\n  bucket_width text NOT NULL,\n  -- `origin` argument of the function provided by the user\n  origin text NOT NULL,\n  -- `timezone` argument of the function provided by the user\n  timezone text NOT NULL,\n  -- table constraints\n  CONSTRAINT continuous_aggs_bucket_function_pkey PRIMARY KEY (mat_hypertable_id),\n  CONSTRAINT continuous_aggs_bucket_function_mat_hypertable_id_fkey FOREIGN KEY (mat_hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE\n);\n\nINSERT INTO _timescaledb_catalog.continuous_aggs_bucket_function\n  SELECT * FROM _timescaledb_catalog._tmp_continuous_aggs_bucket_function;\n\nDROP TABLE _timescaledb_catalog._tmp_continuous_aggs_bucket_function;\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.continuous_aggs_bucket_function', '');\n\nGRANT SELECT ON TABLE _timescaledb_catalog.continuous_aggs_bucket_function TO PUBLIC;\n\nANALYZE _timescaledb_catalog.continuous_aggs_bucket_function;\n\n--\n-- End rebuild the catalog table `_timescaledb_catalog.continuous_aggs_bucket_function`\n--\n\n-- Convert _timescaledb_catalog.continuous_aggs_bucket_function.origin back to Timestamp\nUPDATE _timescaledb_catalog.continuous_aggs_bucket_function\n   SET origin = origin::timestamptz::timestamp::text\n   WHERE length(origin) > 1;\n\n-- only create stub\nCREATE FUNCTION _timescaledb_functions.get_chunk_relstats(relid REGCLASS)\nRETURNS TABLE(chunk_id INTEGER, hypertable_id INTEGER, num_pages INTEGER, num_tuples REAL, num_allvisible INTEGER)\nAS $$BEGIN END$$ LANGUAGE plpgsql SET search_path = pg_catalog, pg_temp;\n\nCREATE FUNCTION _timescaledb_functions.get_chunk_colstats(relid REGCLASS)\nRETURNS TABLE(chunk_id INTEGER, hypertable_id INTEGER, att_num INTEGER, nullfrac REAL, width INTEGER, distinctval REAL, slotkind INTEGER[], slotopstrings CSTRING[], slotcollations OID[], slot1numbers FLOAT4[], slot2numbers FLOAT4[], slot3numbers FLOAT4[], slot4numbers FLOAT4[], slot5numbers FLOAT4[], slotvaluetypetrings CSTRING[], slot1values CSTRING[], slot2values CSTRING[], slot3values CSTRING[], slot4values CSTRING[], slot5values CSTRING[])\nAS $$BEGIN END$$ LANGUAGE plpgsql SET search_path = pg_catalog, pg_temp;\n\n--\n-- START bgw_job_stat_history\n--\nDROP VIEW IF EXISTS timescaledb_information.job_errors;\n\nALTER EXTENSION timescaledb\n  DROP VIEW timescaledb_information.job_history;\n\nDROP VIEW IF EXISTS timescaledb_information.job_history;\n\nCREATE TABLE _timescaledb_internal.job_errors (\n  job_id integer not null,\n  pid integer,\n  start_time timestamptz,\n  finish_time timestamptz,\n  error_data jsonb\n);\n\nINSERT INTO _timescaledb_internal.job_errors (job_id, pid, start_time, finish_time, error_data)\nSELECT\n  job_id,\n  pid,\n  execution_start,\n  execution_finish,\n  data->'error_data'\nFROM\n  _timescaledb_internal.bgw_job_stat_history\nWHERE\n  succeeded IS FALSE\nORDER BY\n  job_id, execution_start;\n\nALTER EXTENSION timescaledb\n    DROP TABLE _timescaledb_internal.bgw_job_stat_history;\n\nDROP TABLE IF EXISTS _timescaledb_internal.bgw_job_stat_history;\n\nREVOKE ALL ON _timescaledb_internal.job_errors FROM PUBLIC;\n\nDROP FUNCTION IF EXISTS _timescaledb_internal.policy_job_stat_history_retention(job_id integer,config jsonb);\nDROP FUNCTION IF EXISTS _timescaledb_internal.policy_job_stat_history_retention_check(config jsonb);\nDROP FUNCTION IF EXISTS _timescaledb_functions.policy_job_stat_history_retention(job_id integer,config jsonb);\nDROP FUNCTION IF EXISTS _timescaledb_functions.policy_job_stat_history_retention_check(config jsonb);\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.policy_job_error_retention(job_id integer, config JSONB) RETURNS integer\nLANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n    drop_after INTERVAL;\n    numrows INTEGER;\nBEGIN\n    SELECT  config->>'drop_after' INTO STRICT drop_after;\n    WITH deleted AS\n        (DELETE\n        FROM _timescaledb_internal.job_errors\n        WHERE finish_time < (now() - drop_after) RETURNING *)\n        SELECT count(*)\n        FROM deleted INTO numrows;\n    RETURN numrows;\nEND;\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.policy_job_error_retention_check(config JSONB) RETURNS VOID\nLANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n  drop_after interval;\nBEGIN\n    IF config IS NULL THEN\n        RAISE EXCEPTION 'config cannot be NULL, and must contain drop_after';\n    END IF;\n    SELECT config->>'drop_after' INTO STRICT drop_after;\n    IF drop_after IS NULL THEN\n        RAISE EXCEPTION 'drop_after interval not provided';\n    END IF ;\nEND;\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\nUPDATE _timescaledb_config.bgw_job SET scheduled = true WHERE id = 2;\nDELETE FROM _timescaledb_config.bgw_job WHERE id = 3;\n\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_to_time_bucket(cagg REGCLASS);\n"
  },
  {
    "path": "sql/updates/2.15.0--2.15.1.sql",
    "content": "CREATE TABLE _timescaledb_catalog._tmp_continuous_aggs_bucket_function AS\n    SELECT\n      mat_hypertable_id,\n      bucket_func::text AS bucket_func,\n      bucket_width,\n      bucket_origin,\n      bucket_offset,\n      bucket_timezone,\n      bucket_fixed_width\n    FROM\n      _timescaledb_catalog.continuous_aggs_bucket_function\n    ORDER BY\n         mat_hypertable_id;\n\nALTER EXTENSION timescaledb\n    DROP TABLE _timescaledb_catalog.continuous_aggs_bucket_function;\n\nDROP TABLE _timescaledb_catalog.continuous_aggs_bucket_function;\n\nCREATE TABLE _timescaledb_catalog.continuous_aggs_bucket_function (\n  mat_hypertable_id integer NOT NULL,\n  -- The bucket function\n  bucket_func text NOT NULL,\n  -- `bucket_width` argument of the function, e.g. \"1 month\"\n  bucket_width text NOT NULL,\n  -- optional `origin` argument of the function provided by the user\n  bucket_origin text,\n  -- optional `offset` argument of the function provided by the user\n  bucket_offset text,\n  -- optional `timezone` argument of the function provided by the user\n  bucket_timezone text,\n  -- fixed or variable sized bucket\n  bucket_fixed_width bool NOT NULL,\n  -- table constraints\n  CONSTRAINT continuous_aggs_bucket_function_pkey PRIMARY KEY (mat_hypertable_id),\n  CONSTRAINT continuous_aggs_bucket_function_mat_hypertable_id_fkey FOREIGN KEY (mat_hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE,\n  CONSTRAINT continuous_aggs_bucket_function_func_check CHECK (pg_catalog.to_regprocedure(bucket_func) IS DISTINCT FROM 0)\n);\n\nINSERT INTO _timescaledb_catalog.continuous_aggs_bucket_function\n  SELECT * FROM _timescaledb_catalog._tmp_continuous_aggs_bucket_function;\n\nDROP TABLE _timescaledb_catalog._tmp_continuous_aggs_bucket_function;\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.continuous_aggs_bucket_function', '');\n\nGRANT SELECT ON TABLE _timescaledb_catalog.continuous_aggs_bucket_function TO PUBLIC;\n\nANALYZE _timescaledb_catalog.continuous_aggs_bucket_function;\n"
  },
  {
    "path": "sql/updates/2.15.1--2.15.0.sql",
    "content": "CREATE TABLE _timescaledb_catalog._tmp_continuous_aggs_bucket_function AS\n    SELECT\n      mat_hypertable_id,\n      pg_catalog.to_regprocedure(bucket_func) AS bucket_func,\n      bucket_width,\n      bucket_origin,\n      bucket_offset,\n      bucket_timezone,\n      bucket_fixed_width\n    FROM\n      _timescaledb_catalog.continuous_aggs_bucket_function\n    ORDER BY\n         mat_hypertable_id;\n\nALTER EXTENSION timescaledb\n    DROP TABLE _timescaledb_catalog.continuous_aggs_bucket_function;\n\nDROP TABLE _timescaledb_catalog.continuous_aggs_bucket_function;\n\nCREATE TABLE _timescaledb_catalog.continuous_aggs_bucket_function (\n  mat_hypertable_id integer NOT NULL,\n  -- The bucket function\n  bucket_func regprocedure NOT NULL,\n  -- `bucket_width` argument of the function, e.g. \"1 month\"\n  bucket_width text NOT NULL,\n  -- optional `origin` argument of the function provided by the user\n  bucket_origin text,\n  -- optional `offset` argument of the function provided by the user\n  bucket_offset text,\n  -- optional `timezone` argument of the function provided by the user\n  bucket_timezone text,\n  -- fixed or variable sized bucket\n  bucket_fixed_width bool NOT NULL,\n  -- table constraints\n  CONSTRAINT continuous_aggs_bucket_function_pkey PRIMARY KEY (mat_hypertable_id),\n  CONSTRAINT continuous_aggs_bucket_function_mat_hypertable_id_fkey FOREIGN KEY (mat_hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE\n);\n\nINSERT INTO _timescaledb_catalog.continuous_aggs_bucket_function\n  SELECT * FROM _timescaledb_catalog._tmp_continuous_aggs_bucket_function;\n\nDROP TABLE _timescaledb_catalog._tmp_continuous_aggs_bucket_function;\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.continuous_aggs_bucket_function', '');\n\nGRANT SELECT ON TABLE _timescaledb_catalog.continuous_aggs_bucket_function TO PUBLIC;\n\nANALYZE _timescaledb_catalog.continuous_aggs_bucket_function;\n"
  },
  {
    "path": "sql/updates/2.15.1--2.15.2.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.15.2--2.15.1.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.15.2--2.15.3.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.15.3--2.15.2.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.15.3--2.16.0.sql",
    "content": "-- Enable tracking of statistics on a column of a hypertable.\n--\n-- hypertable - OID of the table to which the column belongs to\n-- column_name - The column to track statistics for\n-- if_not_exists - If set, and the entry already exists, generate a notice instead of an error\nCREATE FUNCTION @extschema@.enable_chunk_skipping(\n    hypertable              REGCLASS,\n    column_name             NAME,\n    if_not_exists           BOOLEAN = FALSE\n) RETURNS TABLE(column_stats_id INT, enabled BOOL)\nAS 'SELECT NULL,NULL' LANGUAGE SQL VOLATILE SET search_path = pg_catalog, pg_temp;\n\n-- Disable tracking of statistics on a column of a hypertable.\n--\n-- hypertable - OID of the table to remove from\n-- column_name - NAME of the column on which the stats are tracked\n-- if_not_exists - If set, and the entry does not exist,\n-- generate a notice instead of an error\nCREATE FUNCTION @extschema@.disable_chunk_skipping(\n    hypertable              REGCLASS,\n    column_name             NAME,\n    if_not_exists           BOOLEAN = FALSE\n) RETURNS TABLE(hypertable_id INT, column_name NAME, disabled BOOL)\nAS 'SELECT NULL,NULL,NULL' LANGUAGE SQL VOLATILE SET search_path = pg_catalog, pg_temp;\n\n-- Track statistics for columns of chunks from a hypertable.\n-- Currently, we track the min/max range for a given column across chunks.\n-- More statistics (like bloom filters) will be added in the future.\n--\n-- A \"special\" entry for a column with invalid chunk_id, PG_INT64_MAX,\n-- PG_INT64_MIN indicates that min/max ranges could be computed for this column\n-- for chunks.\n--\n-- The ranges can overlap across chunks. The values could be out-of-date if\n-- modifications/changes occur in the corresponding chunk and such entries\n-- should be marked as \"invalid\" to ensure that the chunk is in\n-- appropriate state to be able to use these values. Thus these entries\n-- are different from dimension_slice which is used for tracking partitioning\n-- column ranges which have different characteristics.\n--\n-- Currently this catalog supports datatypes like INT, SERIAL, BIGSERIAL,\n-- DATE, TIMESTAMP etc. by storing the ranges in bigint columns. In the\n-- future, we could support additional datatypes (which support btree style\n-- >, <, = comparators) by storing their textual representation.\n--\nCREATE TABLE _timescaledb_catalog.chunk_column_stats (\n  id serial NOT NULL,\n  hypertable_id integer NOT NULL,\n  chunk_id integer NOT NULL,\n  column_name name NOT NULL,\n  range_start bigint NOT NULL,\n  range_end bigint NOT NULL,\n  valid boolean NOT NULL,\n  -- table constraints\n  CONSTRAINT chunk_column_stats_pkey PRIMARY KEY (id),\n  CONSTRAINT chunk_column_stats_ht_id_chunk_id_colname_key UNIQUE (hypertable_id, chunk_id, column_name),\n  CONSTRAINT chunk_column_stats_range_check CHECK (range_start <= range_end),\n  CONSTRAINT chunk_column_stats_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id),\n  CONSTRAINT chunk_column_stats_chunk_id_fkey FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk (id)\n);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.chunk_column_stats', '');\n\nSELECT pg_catalog.pg_extension_config_dump(pg_get_serial_sequence('_timescaledb_catalog.chunk_column_stats', 'id'), '');\n\nGRANT SELECT ON _timescaledb_catalog.chunk_column_stats TO PUBLIC;\nGRANT SELECT ON _timescaledb_catalog.chunk_column_stats_id_seq TO PUBLIC;\n\n-- Remove foreign key constraints from compressed chunks\nDO $$\nDECLARE\n  conrelid regclass;\n  conname name;\nBEGIN\n  FOR conrelid, conname IN\n  SELECT\n    con.conrelid::regclass,\n    con.conname\n  FROM _timescaledb_catalog.chunk ch\n  JOIN pg_constraint con ON con.conrelid = format('%I.%I',schema_name,table_name)::regclass AND con.contype='f'\n  WHERE NOT ch.dropped AND EXISTS(SELECT FROM _timescaledb_catalog.chunk ch2 WHERE NOT ch2.dropped AND ch2.compressed_chunk_id=ch.id)\n  LOOP\n    EXECUTE format('ALTER TABLE %s DROP CONSTRAINT %I', conrelid, conname);\n  END LOOP;\nEND $$;\n\n"
  },
  {
    "path": "sql/updates/2.16.0--2.15.3.sql",
    "content": "DROP FUNCTION IF EXISTS _timescaledb_functions.cagg_get_bucket_function_info(INTEGER);\n-- remove chunk column statistics related objects\nDROP FUNCTION IF EXISTS @extschema@.enable_chunk_skipping(REGCLASS, NAME, BOOLEAN);\nDROP FUNCTION IF EXISTS @extschema@.disable_chunk_skipping(REGCLASS, NAME, BOOLEAN);\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.chunk_column_stats;\nALTER EXTENSION timescaledb DROP SEQUENCE _timescaledb_catalog.chunk_column_stats_id_seq;\nDROP TABLE IF EXISTS _timescaledb_catalog.chunk_column_stats;\n\n-- Add foreign key constraints back to compressed chunks\nDO $$\nDECLARE\n  chunkrelid regclass;\n  conname name;\n  conoid oid;\nBEGIN\n  FOR chunkrelid, conname, conoid IN\n  SELECT format('%I.%I',ch.schema_name,ch.table_name)::regclass, con.conname, con.oid\n  FROM _timescaledb_catalog.hypertable ht\n  JOIN pg_constraint con ON con.contype = 'f' AND con.conrelid=format('%I.%I',ht.schema_name,ht.table_name)::regclass\n  JOIN _timescaledb_catalog.chunk ch on ch.hypertable_id=ht.compressed_hypertable_id and not ch.dropped\n  LOOP\n    EXECUTE format('ALTER TABLE %s ADD CONSTRAINT %I %s', chunkrelid, conname, pg_get_constraintdef(conoid));\n  END LOOP;\nEND $$;\n"
  },
  {
    "path": "sql/updates/2.16.0--2.16.1.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.16.1--2.16.0.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.16.1--2.17.0.sql",
    "content": "CREATE FUNCTION _timescaledb_functions.compressed_data_info(_timescaledb_internal.compressed_data)\n    RETURNS TABLE (algorithm name, has_nulls bool)\n    AS '@MODULE_PATHNAME@', 'ts_update_placeholder'\n    LANGUAGE C STRICT IMMUTABLE SET search_path = pg_catalog, pg_temp;\n\nCREATE INDEX compression_chunk_size_idx ON _timescaledb_catalog.compression_chunk_size (compressed_chunk_id);\n\nCREATE FUNCTION _timescaledb_functions.drop_osm_chunk(hypertable REGCLASS)\n\tRETURNS BOOL\n\tAS '@MODULE_PATHNAME@', 'ts_update_placeholder'\n\tLANGUAGE C VOLATILE;\n"
  },
  {
    "path": "sql/updates/2.17.0--2.16.1.sql",
    "content": "-- check whether we can safely downgrade the existing compression setup\nCREATE OR REPLACE FUNCTION _timescaledb_functions.add_sequence_number_metadata_column(\n\tcomp_ch_schema_name text,\n\tcomp_ch_table_name text\n)\n\tRETURNS BOOL LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n\tchunk_schema_name text;\n\tchunk_table_name text;\n\tindex_name text;\n\tsegmentby_columns text;\nBEGIN\n\tSELECT ch.schema_name, ch.table_name INTO STRICT chunk_schema_name, chunk_table_name\n\tFROM _timescaledb_catalog.chunk ch\n\tINNER JOIN  _timescaledb_catalog.chunk comp_ch\n\tON ch.compressed_chunk_id = comp_ch.id\n\tWHERE comp_ch.schema_name = comp_ch_schema_name\n\tAND comp_ch.table_name = comp_ch_table_name;\n\n    IF NOT FOUND THEN\n\t\tRAISE USING\n\t\t\tERRCODE = 'feature_not_supported',\n\t\t\tMESSAGE = 'Cannot migrate compressed chunk to version 2.16.1, chunk not found';\n    END IF;\n\n\t-- Add sequence number column to compressed chunk\n\tEXECUTE format('ALTER TABLE %s.%s ADD COLUMN _ts_meta_sequence_num INT DEFAULT NULL', comp_ch_schema_name, comp_ch_table_name);\n\n\t-- Remove all indexes from compressed chunk\n\tFOR index_name IN\n\t\tSELECT format('%s.%s', i.schemaname, i.indexname)\n\t\tFROM pg_indexes i\n\t\tWHERE i.schemaname = comp_ch_schema_name\n\t\tAND i.tablename = comp_ch_table_name\n\tLOOP\n\t\tEXECUTE format('DROP INDEX %s;', index_name);\n\tEND LOOP;\n\n\t-- Fetch the segmentby columns from compression settings\n\tSELECT string_agg(cs.segmentby_column, ',') INTO segmentby_columns\n\tFROM (\n\t\tSELECT unnest(segmentby)\n\t\tFROM _timescaledb_catalog.compression_settings\n\t\tWHERE relid = format('%s.%s', comp_ch_schema_name, comp_ch_table_name)::regclass::oid\n\t\tAND segmentby IS NOT NULL\n\t) AS cs(segmentby_column);\n\n\t-- Create compressed chunk index based on sequence num metadata column\n\t-- If there is no segmentby columns, we can skip creating the index\n    IF FOUND AND segmentby_columns IS NOT NULL THEN\n\t\tEXECUTE format('CREATE INDEX ON %s.%s (%s, _ts_meta_sequence_num);', comp_ch_schema_name, comp_ch_table_name, segmentby_columns);\n    END IF;\n\n\t-- Mark compressed chunk as unordered\n\t-- Marking the chunk status bit (2) makes it unordered\n\t-- and disables some optimizations. In order to re-enable\n\t-- them, you need to recompress these chunks.\n\tUPDATE _timescaledb_catalog.chunk\n\tSET status = status | 2 -- set unordered bit\n\tWHERE schema_name = chunk_schema_name\n\tAND table_name = chunk_table_name;\n\n\tRETURN true;\nEND\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\nDO $$\nDECLARE\n\tchunk_count int;\n\tchunk_record record;\nBEGIN\n    -- if we find chunks which don't have sequence number metadata column in\n\t-- compressed chunk, we need to stop downgrade and have the user run\n\t-- a migration script to re-add the missing columns\n\tSELECT count(*) INTO STRICT chunk_count\n\tFROM _timescaledb_catalog.chunk ch\n\tINNER JOIN _timescaledb_catalog.chunk uncomp_ch\n\tON uncomp_ch.compressed_chunk_id = ch.id\n\tWHERE not exists (\n\t\tSELECT\n\t\tFROM pg_attribute att\n\t\tWHERE attrelid=format('%I.%I',ch.schema_name,ch.table_name)::regclass\n\t\tAND attname='_ts_meta_sequence_num')\n\tAND NOT uncomp_ch.dropped;\n\n\t-- Doing the migration if we find 10 or less chunks that need to be migrated\n\tIF chunk_count > 10 THEN\n      RAISE USING\n        ERRCODE = 'feature_not_supported',\n        MESSAGE = 'Cannot downgrade compressed hypertables with chunks that do not contain sequence numbers. Run timescaledb--2.17-2.16.1.sql migration script before downgrading.',\n        DETAIL = 'Number of chunks that need to be migrated: '|| chunk_count::text;\n\tELSIF chunk_count > 0 THEN\n\t\tFOR chunk_record IN\n\t\tSELECT comp_ch.*\n\t\tFROM _timescaledb_catalog.chunk ch\n\t\tINNER JOIN _timescaledb_catalog.chunk comp_ch\n\t\tON ch.compressed_chunk_id = comp_ch.id\n\t\tWHERE not exists (\n\t\t\tSELECT\n\t\t\tFROM pg_attribute att\n\t\t\tWHERE attrelid=format('%I.%I',comp_ch.schema_name,comp_ch.table_name)::regclass\n\t\t\tAND attname='_ts_meta_sequence_num')\n\t\t\tAND NOT ch.dropped\n\t\tLOOP\n\t\t\tPERFORM  _timescaledb_functions.add_sequence_number_metadata_column(chunk_record.schema_name, chunk_record.table_name);\n\t\t\tRAISE LOG 'Migrated compressed chunk %s.%s to version 2.16.1', chunk_record.schema_name, chunk_record.table_name;\n\t\tEND LOOP;\n\n\t\tRAISE LOG 'Migration successful!';\n\tEND IF;\nEND\n$$;\n\nDROP FUNCTION _timescaledb_functions.add_sequence_number_metadata_column(text, text);\n\nDROP FUNCTION _timescaledb_functions.compressed_data_info(_timescaledb_internal.compressed_data);\nDROP INDEX _timescaledb_catalog.compression_chunk_size_idx;\nDROP FUNCTION IF EXISTS _timescaledb_functions.drop_osm_chunk(REGCLASS);\n"
  },
  {
    "path": "sql/updates/2.17.0--2.17.1.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.17.1--2.17.0.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.17.1--2.17.2.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.17.2--2.17.1.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.17.2--2.18.0.sql",
    "content": "-- remove obsolete job\nDELETE FROM _timescaledb_config.bgw_job WHERE id = 2;\n\n-- Hypercore updates\nCREATE FUNCTION _timescaledb_debug.is_compressed_tid(tid) RETURNS BOOL\nAS '@MODULE_PATHNAME@', 'ts_update_placeholder' LANGUAGE C STRICT;\n\nDROP FUNCTION IF EXISTS @extschema@.compress_chunk(uncompressed_chunk REGCLASS,\tif_not_compressed BOOLEAN, recompress BOOLEAN);\n\nCREATE FUNCTION @extschema@.compress_chunk(\n    uncompressed_chunk REGCLASS,\n    if_not_compressed BOOLEAN = true,\n    recompress BOOLEAN = false,\n    hypercore_use_access_method BOOL = NULL\n) RETURNS REGCLASS AS '@MODULE_PATHNAME@', 'ts_update_placeholder' LANGUAGE C VOLATILE;\n\nDROP FUNCTION IF EXISTS @extschema@.add_compression_policy(hypertable REGCLASS, compress_after \"any\", if_not_exists BOOL, schedule_interval INTERVAL, initial_start TIMESTAMPTZ, timezone TEXT, compress_created_before INTERVAL);\n\nCREATE FUNCTION @extschema@.add_compression_policy(\n    hypertable REGCLASS,\n    compress_after \"any\" = NULL,\n    if_not_exists BOOL = false,\n    schedule_interval INTERVAL = NULL,\n    initial_start TIMESTAMPTZ = NULL,\n    timezone TEXT = NULL,\n    compress_created_before INTERVAL = NULL,\n    hypercore_use_access_method BOOL = NULL\n)\nRETURNS INTEGER\nAS '@MODULE_PATHNAME@', 'ts_update_placeholder'\nLANGUAGE C VOLATILE;\n\nDROP FUNCTION IF EXISTS timescaledb_experimental.add_policies(relation REGCLASS, if_not_exists BOOL, refresh_start_offset \"any\", refresh_end_offset \"any\", compress_after \"any\", drop_after \"any\");\n\nCREATE FUNCTION timescaledb_experimental.add_policies(\n    relation REGCLASS,\n    if_not_exists BOOL = false,\n    refresh_start_offset \"any\" = NULL,\n    refresh_end_offset \"any\" = NULL,\n    compress_after \"any\" = NULL,\n    drop_after \"any\" = NULL,\n    hypercore_use_access_method BOOL = NULL)\nRETURNS BOOL\nAS '@MODULE_PATHNAME@', 'ts_update_placeholder'\nLANGUAGE C VOLATILE;\n\nDROP PROCEDURE IF EXISTS _timescaledb_functions.policy_compression_execute(job_id INTEGER, htid INTEGER, lag ANYELEMENT, maxchunks INTEGER, verbose_log BOOLEAN, recompress_enabled  BOOLEAN, use_creation_time BOOLEAN);\n\nDROP PROCEDURE IF EXISTS _timescaledb_functions.policy_compression(job_id INTEGER, config JSONB);\n\nCREATE PROCEDURE @extschema@.convert_to_columnstore(\n    chunk REGCLASS,\n    if_not_columnstore BOOLEAN = true,\n    recompress BOOLEAN = false,\n    hypercore_use_access_method BOOL = NULL)\nAS '@MODULE_PATHNAME@', 'ts_update_placeholder'\nLANGUAGE C;\n\nCREATE PROCEDURE @extschema@.convert_to_rowstore(\n    chunk REGCLASS,\n    if_columnstore BOOLEAN = true)\nAS '@MODULE_PATHNAME@', 'ts_update_placeholder'\nLANGUAGE C;\n\nCREATE PROCEDURE @extschema@.add_columnstore_policy(\n    hypertable REGCLASS,\n    after \"any\" = NULL,\n    if_not_exists BOOL = false,\n    schedule_interval INTERVAL = NULL,\n    initial_start TIMESTAMPTZ = NULL,\n    timezone TEXT = NULL,\n    created_before INTERVAL = NULL,\n    hypercore_use_access_method BOOL = NULL\n) LANGUAGE C AS '@MODULE_PATHNAME@', 'ts_update_placeholder';\n\nCREATE PROCEDURE @extschema@.remove_columnstore_policy(\n       hypertable REGCLASS,\n       if_exists BOOL = false\n) LANGUAGE C AS '@MODULE_PATHNAME@', 'ts_update_placeholder';\n\nCREATE FUNCTION @extschema@.chunk_columnstore_stats (hypertable REGCLASS)\n    RETURNS TABLE (\n        chunk_schema name,\n        chunk_name name,\n        compression_status text,\n        before_compression_table_bytes bigint,\n        before_compression_index_bytes bigint,\n        before_compression_toast_bytes bigint,\n        before_compression_total_bytes bigint,\n        after_compression_table_bytes bigint,\n        after_compression_index_bytes bigint,\n        after_compression_toast_bytes bigint,\n        after_compression_total_bytes bigint,\n        node_name name)\n    LANGUAGE SQL\n    STABLE STRICT\n    AS 'SELECT * FROM @extschema@.chunk_compression_stats($1)'\n    SET search_path TO pg_catalog, pg_temp;\n\nCREATE FUNCTION @extschema@.hypertable_columnstore_stats (hypertable REGCLASS)\n    RETURNS TABLE (\n        total_chunks bigint,\n        number_compressed_chunks bigint,\n        before_compression_table_bytes bigint,\n        before_compression_index_bytes bigint,\n        before_compression_toast_bytes bigint,\n        before_compression_total_bytes bigint,\n        after_compression_table_bytes bigint,\n        after_compression_index_bytes bigint,\n        after_compression_toast_bytes bigint,\n        after_compression_total_bytes bigint,\n        node_name name)\n    LANGUAGE SQL\n    STABLE STRICT\n    AS 'SELECT * FROM @extschema@.hypertable_compression_stats($1)'\n    SET search_path TO pg_catalog, pg_temp;\n\n-- Recreate `refresh_continuous_aggregate` procedure to add `force` argument\nDROP PROCEDURE IF EXISTS @extschema@.refresh_continuous_aggregate (continuous_aggregate REGCLASS, window_start \"any\", window_end \"any\");\n\nCREATE PROCEDURE @extschema@.refresh_continuous_aggregate(\n    continuous_aggregate     REGCLASS,\n    window_start             \"any\",\n    window_end               \"any\",\n    force                    BOOLEAN = FALSE\n) LANGUAGE C AS '@MODULE_PATHNAME@', 'ts_update_placeholder';\n\n-- Add `include_tiered_data` argument to `add_continuous_aggregate_policy`\nDROP FUNCTION @extschema@.add_continuous_aggregate_policy(\n    continuous_aggregate REGCLASS, start_offset \"any\",\n    end_offset \"any\", schedule_interval INTERVAL,\n    if_not_exists BOOL,\n    initial_start TIMESTAMPTZ,\n    timezone TEXT\n);\nCREATE FUNCTION @extschema@.add_continuous_aggregate_policy(\n    continuous_aggregate REGCLASS, start_offset \"any\",\n    end_offset \"any\", schedule_interval INTERVAL,\n    if_not_exists BOOL = false,\n    initial_start TIMESTAMPTZ = NULL,\n    timezone TEXT = NULL,\n\tinclude_tiered_data BOOL = NULL\n)\nRETURNS INTEGER\nAS '@MODULE_PATHNAME@', 'ts_update_placeholder'\nLANGUAGE C VOLATILE;\n\n-- Merge chunks\nCREATE PROCEDURE @extschema@.merge_chunks(\n    chunk1 REGCLASS, chunk2 REGCLASS\n) LANGUAGE C AS '@MODULE_PATHNAME@', 'ts_update_placeholder';\n\nCREATE PROCEDURE @extschema@.merge_chunks(\n    chunks REGCLASS[]\n) LANGUAGE C AS '@MODULE_PATHNAME@', 'ts_update_placeholder';\n\nCREATE FUNCTION ts_hypercore_handler(internal) RETURNS table_am_handler\nAS 'heap_tableam_handler' LANGUAGE internal;\n\nCREATE FUNCTION ts_hypercore_proxy_handler(internal) RETURNS index_am_handler\nAS 'bthandler' LANGUAGE internal;\n\nCREATE ACCESS METHOD hypercore TYPE TABLE HANDLER ts_hypercore_handler;\nCOMMENT ON ACCESS METHOD hypercore IS 'Storage engine using hybrid row/columnar compression';\n\nCREATE ACCESS METHOD hypercore_proxy TYPE INDEX HANDLER ts_hypercore_proxy_handler;\nCOMMENT ON ACCESS METHOD hypercore_proxy IS 'Hypercore proxy index access method';\n\nCREATE OPERATOR CLASS int4_ops\nDEFAULT FOR TYPE int4 USING hypercore_proxy AS\n       OPERATOR 1 = (int4, int4),\n       FUNCTION 1 hashint4(int4);\n"
  },
  {
    "path": "sql/updates/2.18.0--2.17.2.sql",
    "content": "-- Hypercore AM\nDROP ACCESS METHOD IF EXISTS hypercore_proxy;\nDROP FUNCTION IF EXISTS ts_hypercore_proxy_handler;\nDROP ACCESS METHOD IF EXISTS hypercore;\nDROP FUNCTION IF EXISTS ts_hypercore_handler;\nDROP FUNCTION IF EXISTS _timescaledb_debug.is_compressed_tid;\n\nDROP FUNCTION IF EXISTS @extschema@.compress_chunk(uncompressed_chunk REGCLASS,\tif_not_compressed BOOLEAN, recompress BOOLEAN, hypercore_use_access_method BOOL);\n\nCREATE FUNCTION @extschema@.compress_chunk(\n    uncompressed_chunk REGCLASS,\n    if_not_compressed BOOLEAN = true,\n    recompress BOOLEAN = false\n) RETURNS REGCLASS AS '@MODULE_PATHNAME@', 'ts_compress_chunk' LANGUAGE C STRICT VOLATILE;\n\nDROP FUNCTION IF EXISTS @extschema@.add_compression_policy(hypertable REGCLASS, compress_after \"any\", if_not_exists BOOL, schedule_interval INTERVAL, initial_start TIMESTAMPTZ, timezone TEXT, compress_created_before INTERVAL, hypercore_use_access_method BOOL);\n\nCREATE FUNCTION @extschema@.add_compression_policy(\n    hypertable REGCLASS,\n    compress_after \"any\" = NULL,\n    if_not_exists BOOL = false,\n    schedule_interval INTERVAL = NULL,\n    initial_start TIMESTAMPTZ = NULL,\n    timezone TEXT = NULL,\n    compress_created_before INTERVAL = NULL\n)\nRETURNS INTEGER\nAS '@MODULE_PATHNAME@', 'ts_policy_compression_add'\nLANGUAGE C VOLATILE;\n\nDROP FUNCTION IF EXISTS timescaledb_experimental.add_policies(relation REGCLASS, if_not_exists BOOL, refresh_start_offset \"any\", refresh_end_offset \"any\", compress_after \"any\", drop_after \"any\", hypercore_use_access_method BOOL);\n\nCREATE FUNCTION timescaledb_experimental.add_policies(\n    relation REGCLASS,\n    if_not_exists BOOL = false,\n    refresh_start_offset \"any\" = NULL,\n    refresh_end_offset \"any\" = NULL,\n    compress_after \"any\" = NULL,\n    drop_after \"any\" = NULL)\nRETURNS BOOL\nAS '@MODULE_PATHNAME@', 'ts_policies_add'\nLANGUAGE C VOLATILE;\n\nDROP PROCEDURE IF EXISTS _timescaledb_functions.policy_compression_execute(job_id INTEGER, htid INTEGER, lag ANYELEMENT, maxchunks INTEGER, verbose_log BOOLEAN, recompress_enabled  BOOLEAN, use_creation_time BOOLEAN, useam BOOLEAN);\n\nDROP PROCEDURE IF EXISTS _timescaledb_functions.policy_compression(job_id INTEGER, config JSONB);\nDROP PROCEDURE IF EXISTS @extschema@.convert_to_columnstore(REGCLASS, BOOLEAN, BOOLEAN, BOOLEAN);\nDROP PROCEDURE IF EXISTS @extschema@.convert_to_rowstore(REGCLASS, BOOLEAN);\nDROP PROCEDURE IF EXISTS @extschema@.add_columnstore_policy(REGCLASS, \"any\", BOOL, INTERVAL, TIMESTAMPTZ, TEXT, INTERVAL, BOOL);\nDROP PROCEDURE IF EXISTS @extschema@.remove_columnstore_policy(REGCLASS, BOOL);\nDROP FUNCTION IF EXISTS @extschema@.hypertable_columnstore_stats(REGCLASS);\nDROP FUNCTION IF EXISTS @extschema@.chunk_columnstore_stats(REGCLASS);\n\nALTER EXTENSION timescaledb DROP VIEW timescaledb_information.hypertable_columnstore_settings;\nALTER EXTENSION timescaledb DROP VIEW timescaledb_information.chunk_columnstore_settings;\n\nDROP VIEW timescaledb_information.hypertable_columnstore_settings;\nDROP VIEW timescaledb_information.chunk_columnstore_settings;\n\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_update_watermark(INTEGER);\n\n-- Recreate `refresh_continuous_aggregate` procedure to remove the `force` argument\nDROP PROCEDURE IF EXISTS @extschema@.refresh_continuous_aggregate (continuous_aggregate REGCLASS, window_start \"any\", window_end \"any\", force BOOLEAN);\n\nCREATE PROCEDURE @extschema@.refresh_continuous_aggregate(\n    continuous_aggregate     REGCLASS,\n    window_start             \"any\",\n    window_end               \"any\"\n) LANGUAGE C AS '@MODULE_PATHNAME@', 'ts_continuous_agg_refresh';\n\n-- Remove `include_tiered_data` argument from `add_continuous_aggregate_policy`\nDROP FUNCTION @extschema@.add_continuous_aggregate_policy(\n    continuous_aggregate REGCLASS, start_offset \"any\",\n    end_offset \"any\", schedule_interval INTERVAL,\n    if_not_exists BOOL,\n    initial_start TIMESTAMPTZ,\n    timezone TEXT,\n    include_tiered_data BOOL\n);\nCREATE FUNCTION @extschema@.add_continuous_aggregate_policy(\n    continuous_aggregate REGCLASS, start_offset \"any\",\n    end_offset \"any\", schedule_interval INTERVAL,\n    if_not_exists BOOL = false,\n    initial_start TIMESTAMPTZ = NULL,\n    timezone TEXT = NULL\n)\nRETURNS INTEGER\nAS '@MODULE_PATHNAME@', 'ts_policy_refresh_cagg_add'\nLANGUAGE C VOLATILE;\n\n-- Merge chunks\nDROP PROCEDURE IF EXISTS @extschema@.merge_chunks(chunk1 REGCLASS, chunk2 REGCLASS);\nDROP PROCEDURE IF EXISTS @extschema@.merge_chunks(chunks REGCLASS[]);\n"
  },
  {
    "path": "sql/updates/2.18.0--2.18.1.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.18.1--2.18.0.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.18.1--2.18.2.sql",
    "content": "ALTER TABLE _timescaledb_internal.bgw_job_stat_history\n    ALTER COLUMN succeeded DROP NOT NULL,\n    ALTER COLUMN succeeded DROP DEFAULT;\n"
  },
  {
    "path": "sql/updates/2.18.2--2.18.1.sql",
    "content": "UPDATE _timescaledb_internal.bgw_job_stat_history SET succeeded = FALSE WHERE succeeded IS NULL;\n\nALTER TABLE _timescaledb_internal.bgw_job_stat_history\n    ALTER COLUMN succeeded SET NOT NULL,\n    ALTER COLUMN succeeded SET DEFAULT FALSE;\n"
  },
  {
    "path": "sql/updates/2.18.2--2.19.0.sql",
    "content": "CREATE FUNCTION _timescaledb_functions.compressed_data_has_nulls(_timescaledb_internal.compressed_data)\n    RETURNS BOOL\n    LANGUAGE C STRICT IMMUTABLE\n    AS '@MODULE_PATHNAME@', 'ts_update_placeholder';\n\nINSERT INTO _timescaledb_catalog.compression_algorithm( id, version, name, description) values\n( 5, 1, 'COMPRESSION_ALGORITHM_BOOL', 'bool'),\n( 6, 1, 'COMPRESSION_ALGORITHM_NULL', 'null')\n;\n\n-------------------------------\n-- Update compression settings\n-------------------------------\nCREATE TABLE _timescaledb_catalog.tempsettings (LIKE _timescaledb_catalog.compression_settings);\nINSERT INTO _timescaledb_catalog.tempsettings SELECT * FROM _timescaledb_catalog.compression_settings;\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.compression_settings;\nDROP TABLE _timescaledb_catalog.compression_settings CASCADE;\n\nCREATE TABLE _timescaledb_catalog.compression_settings (\n  relid regclass NOT NULL,\n  compress_relid regclass NULL,\n  segmentby text[],\n  orderby text[],\n  orderby_desc bool[],\n  orderby_nullsfirst bool[],\n  CONSTRAINT compression_settings_pkey PRIMARY KEY (relid),\n  CONSTRAINT compression_settings_check_segmentby CHECK (array_ndims(segmentby) = 1),\n  CONSTRAINT compression_settings_check_orderby_null CHECK ((orderby IS NULL AND orderby_desc IS NULL AND orderby_nullsfirst IS NULL) OR (orderby IS NOT NULL AND orderby_desc IS NOT NULL AND orderby_nullsfirst IS NOT NULL)),\n  CONSTRAINT compression_settings_check_orderby_cardinality CHECK (array_ndims(orderby) = 1 AND array_ndims(orderby_desc) = 1 AND array_ndims(orderby_nullsfirst) = 1 AND cardinality(orderby) = cardinality(orderby_desc) AND cardinality(orderby) = cardinality(orderby_nullsfirst))\n);\n\n-- Insert updated settings\nINSERT INTO _timescaledb_catalog.compression_settings\nSELECT\n    CASE\n        WHEN h.schema_name IS NOT NULL THEN\n            cs.relid\n        ELSE\n            format('%I.%I', ch.schema_name, ch.table_name)::regclass\n    END AS relid,\n    CASE\n        WHEN h.schema_name IS NOT NULL THEN\n            NULL\n        ELSE\n            cs.relid\n    END AS compress_relid,\n    cs.segmentby,\n    cs.orderby,\n    cs.orderby_desc,\n    cs.orderby_nullsfirst\nFROM\n    _timescaledb_catalog.tempsettings cs\nINNER JOIN\n    pg_class c ON (cs.relid = c.oid)\nINNER JOIN\n    pg_namespace ns ON (ns.oid = c.relnamespace)\nLEFT JOIN\n    _timescaledb_catalog.hypertable h ON (h.schema_name = ns.nspname AND h.table_name = c.relname)\nLEFT JOIN\n    _timescaledb_catalog.chunk cch ON (cch.schema_name = ns.nspname AND cch.table_name = c.relname)\nLEFT JOIN\n    _timescaledb_catalog.chunk ch ON (cch.id = ch.compressed_chunk_id);\n\n-- Add index on secondary compressed relid key\nCREATE INDEX compression_settings_compress_relid_idx ON _timescaledb_catalog.compression_settings (compress_relid);\n\nDROP TABLE _timescaledb_catalog.tempsettings CASCADE;\nGRANT SELECT ON _timescaledb_catalog.compression_settings TO PUBLIC;\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.compression_settings', '');\n\n\n-- New add_continuous_aggregate_policy API for incremental refresh policy\nDROP FUNCTION @extschema@.add_continuous_aggregate_policy(\n    continuous_aggregate REGCLASS,\n    start_offset \"any\",\n    end_offset \"any\",\n    schedule_interval INTERVAL,\n    if_not_exists BOOL,\n    initial_start TIMESTAMPTZ,\n    timezone TEXT,\n    include_tiered_data BOOL\n);\n\nCREATE FUNCTION @extschema@.add_continuous_aggregate_policy(\n    continuous_aggregate REGCLASS,\n    start_offset \"any\",\n    end_offset \"any\",\n    schedule_interval INTERVAL,\n    if_not_exists BOOL = false,\n    initial_start TIMESTAMPTZ = NULL,\n    timezone TEXT = NULL,\n\tinclude_tiered_data BOOL = NULL,\n    buckets_per_batch INTEGER = NULL,\n    max_batches_per_execution INTEGER = NULL\n)\nRETURNS INTEGER\nAS '@MODULE_PATHNAME@', 'ts_update_placeholder'\nLANGUAGE C VOLATILE;"
  },
  {
    "path": "sql/updates/2.19.0--2.18.2.sql",
    "content": "DROP FUNCTION IF EXISTS _timescaledb_functions.compressed_data_has_nulls(_timescaledb_internal.compressed_data);\n\nDELETE FROM _timescaledb_catalog.compression_algorithm WHERE id = 5 AND version = 1 AND name = 'COMPRESSION_ALGORITHM_BOOL';\nDELETE FROM _timescaledb_catalog.compression_algorithm WHERE id = 6 AND version = 1 AND name = 'COMPRESSION_ALGORITHM_NULL';\n\n-- Update compression settings\nCREATE TABLE _timescaledb_catalog.tempsettings (LIKE _timescaledb_catalog.compression_settings);\nINSERT INTO _timescaledb_catalog.tempsettings SELECT * FROM _timescaledb_catalog.compression_settings;\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.compression_settings;\nDROP TABLE _timescaledb_catalog.compression_settings CASCADE;\n\nCREATE TABLE _timescaledb_catalog.compression_settings (\n  relid regclass NOT NULL,\n  segmentby text[],\n  orderby text[],\n  orderby_desc bool[],\n  orderby_nullsfirst bool[],\n  CONSTRAINT compression_settings_pkey PRIMARY KEY (relid),\n  CONSTRAINT compression_settings_check_segmentby CHECK (array_ndims(segmentby) = 1),\n  CONSTRAINT compression_settings_check_orderby_null CHECK ((orderby IS NULL AND orderby_desc IS NULL AND orderby_nullsfirst IS NULL) OR (orderby IS NOT NULL AND orderby_desc IS NOT NULL AND orderby_nullsfirst IS NOT NULL)),\n  CONSTRAINT compression_settings_check_orderby_cardinality CHECK (array_ndims(orderby) = 1 AND array_ndims(orderby_desc) = 1 AND array_ndims(orderby_nullsfirst) = 1 AND cardinality(orderby) = cardinality(orderby_desc) AND cardinality(orderby) = cardinality(orderby_nullsfirst))\n);\n\n-- Revert information in compression settings\nINSERT INTO _timescaledb_catalog.compression_settings\nSELECT\n    CASE\n        WHEN cs.compress_relid IS NULL THEN\n            cs.relid\n        ELSE\n            cs.compress_relid\n    END as relid,\n    cs.segmentby,\n    cs.orderby,\n    cs.orderby_desc,\n    cs.orderby_nullsfirst\nFROM\n    _timescaledb_catalog.tempsettings cs;\n\nDROP TABLE _timescaledb_catalog.tempsettings CASCADE;\nGRANT SELECT ON _timescaledb_catalog.compression_settings TO PUBLIC;\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.compression_settings', '');\n\n-- Revert add_continuous_aggregate_policy API for incremental refresh policy\nDROP FUNCTION @extschema@.add_continuous_aggregate_policy(\n    continuous_aggregate REGCLASS,\n    start_offset \"any\",\n    end_offset \"any\",\n    schedule_interval INTERVAL,\n    if_not_exists BOOL,\n    initial_start TIMESTAMPTZ,\n    timezone TEXT,\n\tinclude_tiered_data BOOL,\n    buckets_per_batch INTEGER,\n    max_batches_per_execution INTEGER\n);\n\nCREATE FUNCTION @extschema@.add_continuous_aggregate_policy(\n    continuous_aggregate REGCLASS,\n    start_offset \"any\",\n    end_offset \"any\",\n    schedule_interval INTERVAL,\n    if_not_exists BOOL = false,\n    initial_start TIMESTAMPTZ = NULL,\n    timezone TEXT = NULL,\n\tinclude_tiered_data BOOL = NULL\n)\nRETURNS INTEGER\nAS '@MODULE_PATHNAME@', 'ts_update_placeholder'\nLANGUAGE C VOLATILE;"
  },
  {
    "path": "sql/updates/2.19.0--2.19.1.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.19.1--2.19.0.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.19.1--2.19.2.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.19.2--2.19.1.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.19.2--2.19.3.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.19.3--2.19.2.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.19.3--2.20.0.sql",
    "content": "-- Type for bloom filters used by the sparse indexes on compressed hypertables.\nCREATE TYPE _timescaledb_internal.bloom1;\n\nCREATE FUNCTION _timescaledb_functions.bloom1in(cstring) RETURNS _timescaledb_internal.bloom1 AS 'byteain' LANGUAGE INTERNAL STRICT IMMUTABLE PARALLEL SAFE;\n\nCREATE FUNCTION _timescaledb_functions.bloom1out(_timescaledb_internal.bloom1) RETURNS cstring AS 'byteaout' LANGUAGE INTERNAL STRICT IMMUTABLE PARALLEL SAFE;\n\nCREATE TYPE _timescaledb_internal.bloom1 (\n    INPUT = _timescaledb_functions.bloom1in,\n    OUTPUT = _timescaledb_functions.bloom1out,\n    LIKE = bytea\n);\n\nCREATE FUNCTION _timescaledb_functions.bloom1_contains(_timescaledb_internal.bloom1, anyelement)\nRETURNS bool\nAS '@MODULE_PATHNAME@', 'ts_update_placeholder'\nLANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\n\n\nDROP FUNCTION IF EXISTS _timescaledb_internal.create_chunk_table;\nDROP FUNCTION IF EXISTS _timescaledb_functions.create_chunk_table;\n\n\n-- New option `refresh_newest_first` for incremental cagg refresh policy\nDROP FUNCTION @extschema@.add_continuous_aggregate_policy(\n    continuous_aggregate REGCLASS,\n    start_offset \"any\",\n    end_offset \"any\",\n    schedule_interval INTERVAL,\n    if_not_exists BOOL,\n    initial_start TIMESTAMPTZ,\n    timezone TEXT,\n    include_tiered_data BOOL,\n    buckets_per_batch INTEGER,\n    max_batches_per_execution INTEGER\n);\n\nCREATE FUNCTION @extschema@.add_continuous_aggregate_policy(\n    continuous_aggregate REGCLASS,\n    start_offset \"any\",\n    end_offset \"any\",\n    schedule_interval INTERVAL,\n    if_not_exists BOOL = false,\n    initial_start TIMESTAMPTZ = NULL,\n    timezone TEXT = NULL,\n    include_tiered_data BOOL = NULL,\n    buckets_per_batch INTEGER = NULL,\n    max_batches_per_execution INTEGER = NULL,\n    refresh_newest_first BOOL = NULL\n)\nRETURNS INTEGER\nAS '@MODULE_PATHNAME@', 'ts_update_placeholder'\nLANGUAGE C VOLATILE;\n\nUPDATE _timescaledb_catalog.hypertable SET chunk_sizing_func_schema = '_timescaledb_functions' WHERE chunk_sizing_func_schema = '_timescaledb_internal' AND chunk_sizing_func_name = 'calculate_chunk_interval';\n\nDROP VIEW IF EXISTS timescaledb_information.hypertables;\n\n-- Rename Columnstore Policy jobs to Compression Policy\nUPDATE _timescaledb_config.bgw_job SET application_name = replace(application_name, 'Compression Policy', 'Columnstore Policy') WHERE application_name LIKE '%Compression Policy%';\n\n-- Split chunk\nCREATE PROCEDURE @extschema@.split_chunk(\n    chunk REGCLASS,\n    split_at \"any\" = NULL\n) LANGUAGE C AS '@MODULE_PATHNAME@', 'ts_update_placeholder';\n\nCREATE FUNCTION _timescaledb_functions.align_to_bucket(width INTERVAL, rng ANYRANGE)\nRETURNS ANYRANGE AS\n$body$\nBEGIN\n  RETURN _timescaledb_functions.make_range_from_internal_time(\n         rng,\n         @extschema@.time_bucket(width, lower(rng)),\n         @extschema@.time_bucket(width, upper(rng) - '1 microsecond'::interval) + width\n  );\nEND\n$body$\nLANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE\nSET search_path TO pg_catalog, pg_temp;\n\nCREATE FUNCTION _timescaledb_functions.make_multirange_from_internal_time(\n    base TSTZRANGE, low_usec BIGINT, high_usec BIGINT\n) RETURNS TSTZMULTIRANGE AS\n$body$\n  select multirange(tstzrange(_timescaledb_functions.to_timestamp(low_usec),\n\t\t\t      _timescaledb_functions.to_timestamp(high_usec)));\n$body$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE\nSET search_path TO pg_catalog, pg_temp;\n\nCREATE FUNCTION _timescaledb_functions.make_multirange_from_internal_time(\n    base TSRANGE, low_usec BIGINT, high_usec BIGINT\n) RETURNS TSMULTIRANGE AS\n$body$\n  select multirange(tsrange(_timescaledb_functions.to_timestamp_without_timezone(low_usec),\n\t\t\t    _timescaledb_functions.to_timestamp_without_timezone(high_usec)));\n$body$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE\nSET search_path TO pg_catalog, pg_temp;\n\nCREATE FUNCTION _timescaledb_functions.make_range_from_internal_time(\n    base ANYRANGE, low_usec ANYELEMENT, high_usec ANYELEMENT\n) RETURNS anyrange\nAS '@MODULE_PATHNAME@', 'ts_update_placeholder'\nLANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.get_internal_time_min(REGTYPE) RETURNS BIGINT\nAS '@MODULE_PATHNAME@', 'ts_update_placeholder' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.get_internal_time_max(REGTYPE) RETURNS BIGINT\nAS '@MODULE_PATHNAME@', 'ts_update_placeholder' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\nDROP FUNCTION IF EXISTS @extschema@.add_job(\n  proc REGPROC,\n  schedule_interval INTERVAL,\n  config JSONB,\n  initial_start TIMESTAMPTZ,\n  scheduled BOOL,\n  check_config REGPROC,\n  fixed_schedule BOOL,\n  timezone TEXT\n);\n\nCREATE FUNCTION @extschema@.add_job(\n  proc REGPROC,\n  schedule_interval INTERVAL,\n  config JSONB DEFAULT NULL,\n  initial_start TIMESTAMPTZ DEFAULT NULL,\n  scheduled BOOL DEFAULT true,\n  check_config REGPROC DEFAULT NULL,\n  fixed_schedule BOOL DEFAULT TRUE,\n  timezone TEXT DEFAULT NULL,\n  job_name TEXT DEFAULT NULL\n)\nRETURNS INTEGER\nAS '@MODULE_PATHNAME@', 'ts_update_placeholder'\nLANGUAGE C VOLATILE;\n\nDROP FUNCTION IF EXISTS @extschema@.alter_job(\n    job_id INTEGER,\n    schedule_interval INTERVAL,\n    max_runtime INTERVAL,\n    max_retries INTEGER,\n    retry_period INTERVAL,\n    scheduled BOOL,\n    config JSONB,\n    next_start TIMESTAMPTZ,\n    if_exists BOOL,\n    check_config REGPROC,\n    fixed_schedule BOOL,\n    initial_start TIMESTAMPTZ,\n    timezone TEXT\n);\n\nCREATE FUNCTION @extschema@.alter_job(\n    job_id INTEGER,\n    schedule_interval INTERVAL = NULL,\n    max_runtime INTERVAL = NULL,\n    max_retries INTEGER = NULL,\n    retry_period INTERVAL = NULL,\n    scheduled BOOL = NULL,\n    config JSONB = NULL,\n    next_start TIMESTAMPTZ = NULL,\n    if_exists BOOL = FALSE,\n    check_config REGPROC = NULL,\n    fixed_schedule BOOL = NULL,\n    initial_start TIMESTAMPTZ = NULL,\n    timezone TEXT DEFAULT NULL,\n    job_name TEXT DEFAULT NULL\n)\nRETURNS TABLE (job_id INTEGER, schedule_interval INTERVAL, max_runtime INTERVAL, max_retries INTEGER, retry_period INTERVAL, scheduled BOOL, config JSONB,\nnext_start TIMESTAMPTZ, check_config TEXT, fixed_schedule BOOL, initial_start TIMESTAMPTZ, timezone TEXT, application_name name)\nAS '@MODULE_PATHNAME@', 'ts_update_placeholder'\nLANGUAGE C VOLATILE;"
  },
  {
    "path": "sql/updates/2.20.0--2.19.3.sql",
    "content": "-- Drop the type used by the bloom sparse indexes on compressed hypertables.\nDROP TYPE _timescaledb_internal.bloom1 CASCADE;\n\n\nCREATE FUNCTION _timescaledb_internal.create_chunk_table(hypertable REGCLASS, slices JSONB, schema_name NAME, table_name NAME) RETURNS BOOL AS '@MODULE_PATHNAME@', 'ts_update_placeholder' LANGUAGE C VOLATILE;\nCREATE FUNCTION _timescaledb_functions.create_chunk_table(hypertable REGCLASS, slices JSONB, schema_name NAME, table_name NAME) RETURNS BOOL AS '@MODULE_PATHNAME@', 'ts_update_placeholder' LANGUAGE C VOLATILE;\nDROP FUNCTION _timescaledb_functions.get_hypertable_id(REGCLASS, REGTYPE);\nDROP FUNCTION _timescaledb_functions.get_hypertable_invalidations(REGCLASS, TIMESTAMPTZ, INTERVAL[]);\nDROP FUNCTION _timescaledb_functions.get_hypertable_invalidations(REGCLASS, TIMESTAMP, INTERVAL[]);\nDROP PROCEDURE _timescaledb_functions.accept_hypertable_invalidations(REGCLASS, TEXT);\n\n-- Revert new option `refresh_newest_first` from incremental cagg refresh policy\nDROP FUNCTION @extschema@.add_continuous_aggregate_policy(\n    continuous_aggregate REGCLASS,\n    start_offset \"any\",\n    end_offset \"any\",\n    schedule_interval INTERVAL,\n    if_not_exists BOOL,\n    initial_start TIMESTAMPTZ,\n    timezone TEXT,\n    include_tiered_data BOOL,\n    buckets_per_batch INTEGER,\n    max_batches_per_execution INTEGER,\n    refresh_newest_first BOOL\n);\n\nCREATE FUNCTION @extschema@.add_continuous_aggregate_policy(\n    continuous_aggregate REGCLASS,\n    start_offset \"any\",\n    end_offset \"any\",\n    schedule_interval INTERVAL,\n    if_not_exists BOOL = false,\n    initial_start TIMESTAMPTZ = NULL,\n    timezone TEXT = NULL,\n    include_tiered_data BOOL = NULL,\n    buckets_per_batch INTEGER = NULL,\n    max_batches_per_execution INTEGER = NULL\n)\nRETURNS INTEGER\nAS '@MODULE_PATHNAME@', 'ts_update_placeholder'\nLANGUAGE C VOLATILE;\n\nDROP VIEW IF EXISTS timescaledb_information.hypertables;\n\n-- Rename Columnstore Policy jobs to Compression Policy\nUPDATE _timescaledb_config.bgw_job SET application_name = replace(application_name, 'Columnstore Policy', 'Compression Policy') WHERE application_name LIKE '%Columnstore Policy%';\n\nCREATE OR REPLACE PROCEDURE\n_timescaledb_functions.policy_compression_execute(\n  job_id              INTEGER,\n  htid                INTEGER,\n  lag                 ANYELEMENT,\n  maxchunks           INTEGER,\n  verbose_log         BOOLEAN,\n  recompress_enabled  BOOLEAN,\n  use_creation_time   BOOLEAN,\n  useam               BOOLEAN = NULL)\nAS $$\nDECLARE\n  htoid       REGCLASS;\n  chunk_rec   RECORD;\n  numchunks   INTEGER := 1;\n  _message     text;\n  _detail      text;\n  _sqlstate    text;\n  -- fully compressed chunk status\n  status_fully_compressed int := 1;\n  -- chunk status bits:\n  bit_compressed int := 1;\n  bit_compressed_unordered int := 2;\n  bit_frozen int := 4;\n  bit_compressed_partial int := 8;\n  creation_lag INTERVAL := NULL;\n  chunks_failure INTEGER := 0;\nBEGIN\n\n  -- procedures with SET clause cannot execute transaction\n  -- control so we adjust search_path in procedure body\n  SET LOCAL search_path TO pg_catalog, pg_temp;\n\n  SELECT format('%I.%I', schema_name, table_name) INTO htoid\n  FROM _timescaledb_catalog.hypertable\n  WHERE id = htid;\n\n  -- for the integer cases, we have to compute the lag w.r.t\n  -- the integer_now function and then pass on to show_chunks\n  IF pg_typeof(lag) IN ('BIGINT'::regtype, 'INTEGER'::regtype, 'SMALLINT'::regtype) THEN\n    -- cannot have use_creation_time set with this\n    IF use_creation_time IS TRUE THEN\n        RAISE EXCEPTION 'job % cannot use creation time with integer_now function', job_id;\n    END IF;\n    lag := _timescaledb_functions.subtract_integer_from_now(htoid, lag::BIGINT);\n  END IF;\n\n  -- if use_creation_time has been specified then the lag needs to be used with the\n  -- \"compress_created_before\" argument. Otherwise the usual \"older_than\" argument\n  -- is good enough\n  IF use_creation_time IS TRUE THEN\n    creation_lag := lag;\n    lag := NULL;\n  END IF;\n\n  FOR chunk_rec IN\n    SELECT\n      show.oid, ch.schema_name, ch.table_name, ch.status\n    FROM\n      @extschema@.show_chunks(htoid, older_than => lag, created_before => creation_lag) AS show(oid)\n      INNER JOIN pg_class pgc ON pgc.oid = show.oid\n      INNER JOIN pg_namespace pgns ON pgc.relnamespace = pgns.oid\n      INNER JOIN _timescaledb_catalog.chunk ch ON ch.table_name = pgc.relname AND ch.schema_name = pgns.nspname AND ch.hypertable_id = htid\n    WHERE NOT ch.dropped\n    AND NOT ch.osm_chunk\n    -- Checking for chunks which are not fully compressed and not frozen\n    AND ch.status != status_fully_compressed\n    AND ch.status & bit_frozen = 0\n  LOOP\n    BEGIN\n      IF chunk_rec.status = bit_compressed OR recompress_enabled IS TRUE THEN\n        PERFORM @extschema@.compress_chunk(chunk_rec.oid, hypercore_use_access_method => useam);\n        numchunks := numchunks + 1;\n      END IF;\n    EXCEPTION WHEN OTHERS THEN\n      GET STACKED DIAGNOSTICS\n          _message = MESSAGE_TEXT,\n          _detail = PG_EXCEPTION_DETAIL,\n          _sqlstate = RETURNED_SQLSTATE;\n      RAISE WARNING 'compressing chunk \"%\" failed when compression policy is executed', chunk_rec.oid::regclass::text\n          USING DETAIL = format('Message: (%s), Detail: (%s).', _message, _detail),\n                ERRCODE = _sqlstate;\n      chunks_failure := chunks_failure + 1;\n    END;\n    COMMIT;\n    -- SET LOCAL is only active until end of transaction.\n    -- While we could use SET at the start of the function we do not\n    -- want to bleed out search_path to caller, so we do SET LOCAL\n    -- again after COMMIT\n    SET LOCAL search_path TO pg_catalog, pg_temp;\n    IF verbose_log THEN\n       RAISE LOG 'job % completed processing chunk %.%', job_id, chunk_rec.schema_name, chunk_rec.table_name;\n    END IF;\n    IF maxchunks > 0 AND numchunks >= maxchunks THEN\n         EXIT;\n    END IF;\n  END LOOP;\n\n  IF chunks_failure > 0 THEN\n    RAISE EXCEPTION 'compression policy failure'\n      USING DETAIL = format('Failed to compress %L chunks. Successfully compressed %L chunks.', chunks_failure, numchunks - chunks_failure);\n  END IF;\nEND;\n$$ LANGUAGE PLPGSQL;\n\nCREATE OR REPLACE PROCEDURE\n_timescaledb_functions.policy_compression(job_id INTEGER, config JSONB)\nAS $$\nDECLARE\n  dimtype             REGTYPE;\n  dimtypeinput        REGPROC;\n  compress_after      TEXT;\n  compress_created_before TEXT;\n  lag_value           TEXT;\n  lag_bigint_value    BIGINT;\n  htid                INTEGER;\n  htoid               REGCLASS;\n  chunk_rec           RECORD;\n  verbose_log         BOOL;\n  maxchunks           INTEGER := 0;\n  numchunks           INTEGER := 1;\n  recompress_enabled  BOOL;\n  use_creation_time   BOOL := FALSE;\n  hypercore_use_access_method   BOOL;\nBEGIN\n\n  -- procedures with SET clause cannot execute transaction\n  -- control so we adjust search_path in procedure body\n  SET LOCAL search_path TO pg_catalog, pg_temp;\n\n  IF config IS NULL THEN\n    RAISE EXCEPTION 'job % has null config', job_id;\n  END IF;\n\n  htid := jsonb_object_field_text(config, 'hypertable_id')::INTEGER;\n  IF htid is NULL THEN\n    RAISE EXCEPTION 'job % config must have hypertable_id', job_id;\n  END IF;\n\n  verbose_log         := COALESCE(jsonb_object_field_text(config, 'verbose_log')::BOOLEAN, FALSE);\n  maxchunks           := COALESCE(jsonb_object_field_text(config, 'maxchunks_to_compress')::INTEGER, 0);\n  recompress_enabled  := COALESCE(jsonb_object_field_text(config, 'recompress')::BOOLEAN, TRUE);\n\n  -- find primary dimension type --\n  SELECT dim.column_type INTO dimtype\n  FROM  _timescaledb_catalog.hypertable ht\n        JOIN _timescaledb_catalog.dimension dim ON ht.id = dim.hypertable_id\n  WHERE ht.id = htid\n  ORDER BY dim.id\n  LIMIT 1;\n\n  compress_after      := jsonb_object_field_text(config, 'compress_after');\n  IF compress_after IS NULL THEN\n    compress_created_before := jsonb_object_field_text(config, 'compress_created_before');\n    IF compress_created_before IS NULL THEN\n        RAISE EXCEPTION 'job % config must have compress_after or compress_created_before', job_id;\n    END IF;\n    lag_value := compress_created_before;\n    use_creation_time := true;\n    dimtype := 'INTERVAL' ::regtype;\n  ELSE\n    lag_value := compress_after;\n  END IF;\n\n  hypercore_use_access_method := jsonb_object_field_text(config, 'hypercore_use_access_method')::bool;\n\n  -- execute the properly type casts for the lag value\n  CASE dimtype\n    WHEN 'TIMESTAMP'::regtype, 'TIMESTAMPTZ'::regtype, 'DATE'::regtype, 'INTERVAL' ::regtype  THEN\n      CALL _timescaledb_functions.policy_compression_execute(\n        job_id, htid, lag_value::INTERVAL,\n        maxchunks, verbose_log, recompress_enabled, use_creation_time, hypercore_use_access_method\n      );\n    WHEN 'BIGINT'::regtype THEN\n      CALL _timescaledb_functions.policy_compression_execute(\n        job_id, htid, lag_value::BIGINT,\n        maxchunks, verbose_log, recompress_enabled, use_creation_time, hypercore_use_access_method\n      );\n    WHEN 'INTEGER'::regtype THEN\n      CALL _timescaledb_functions.policy_compression_execute(\n        job_id, htid, lag_value::INTEGER,\n        maxchunks, verbose_log, recompress_enabled, use_creation_time, hypercore_use_access_method\n      );\n    WHEN 'SMALLINT'::regtype THEN\n      CALL _timescaledb_functions.policy_compression_execute(\n        job_id, htid, lag_value::SMALLINT,\n        maxchunks, verbose_log, recompress_enabled, use_creation_time, hypercore_use_access_method\n      );\n  END CASE;\nEND;\n$$ LANGUAGE PLPGSQL;\n\n-- Split chunk\nDROP PROCEDURE IF EXISTS @extschema@.split_chunk(chunk REGCLASS, split_at \"any\");\nDROP FUNCTION _timescaledb_functions.align_to_bucket(INTERVAL, ANYRANGE);\nDROP FUNCTION _timescaledb_functions.make_multirange_from_internal_time(TSTZRANGE, BIGINT, BIGINT);\nDROP FUNCTION _timescaledb_functions.make_multirange_from_internal_time(TSRANGE, BIGINT, BIGINT);\nDROP FUNCTION _timescaledb_functions.make_range_from_internal_time(ANYRANGE, ANYELEMENT, ANYELEMENT);\nDROP FUNCTION _timescaledb_functions.get_internal_time_min(REGTYPE);\nDROP FUNCTION _timescaledb_functions.get_internal_time_max(REGTYPE);\nDROP PROCEDURE _timescaledb_functions.add_materialization_invalidations(REGCLASS,TSRANGE);\nDROP PROCEDURE _timescaledb_functions.add_materialization_invalidations(REGCLASS,TSTZRANGE);\nDROP FUNCTION _timescaledb_functions.get_raw_materialization_ranges(REGTYPE);\nDROP FUNCTION _timescaledb_functions.get_materialization_invalidations(REGCLASS, TSTZRANGE);\nDROP FUNCTION _timescaledb_functions.get_materialization_invalidations(REGCLASS, TSRANGE);\nDROP FUNCTION _timescaledb_functions.get_materialization_info(REGCLASS);\n\nDROP FUNCTION IF EXISTS @extschema@.add_job(\n  proc REGPROC,\n  schedule_interval INTERVAL,\n  config JSONB,\n  initial_start TIMESTAMPTZ,\n  scheduled BOOL,\n  check_config REGPROC,\n  fixed_schedule BOOL,\n  timezone TEXT,\n  job_name TEXT\n);\n\nCREATE FUNCTION @extschema@.add_job(\n  proc REGPROC,\n  schedule_interval INTERVAL,\n  config JSONB DEFAULT NULL,\n  initial_start TIMESTAMPTZ DEFAULT NULL,\n  scheduled BOOL DEFAULT true,\n  check_config REGPROC DEFAULT NULL,\n  fixed_schedule BOOL DEFAULT TRUE,\n  timezone TEXT DEFAULT NULL\n)\nRETURNS INTEGER\nAS '@MODULE_PATHNAME@', 'ts_update_placeholder'\nLANGUAGE C VOLATILE;\n\nDROP FUNCTION IF EXISTS @extschema@.alter_job(\n    job_id INTEGER,\n    schedule_interval INTERVAL,\n    max_runtime INTERVAL,\n    max_retries INTEGER,\n    retry_period INTERVAL,\n    scheduled BOOL,\n    config JSONB,\n    next_start TIMESTAMPTZ,\n    if_exists BOOL,\n    check_config REGPROC,\n    fixed_schedule BOOL,\n    initial_start TIMESTAMPTZ,\n    timezone TEXT,\n    job_name TEXT\n);\n\nCREATE FUNCTION @extschema@.alter_job(\n    job_id INTEGER,\n    schedule_interval INTERVAL = NULL,\n    max_runtime INTERVAL = NULL,\n    max_retries INTEGER = NULL,\n    retry_period INTERVAL = NULL,\n    scheduled BOOL = NULL,\n    config JSONB = NULL,\n    next_start TIMESTAMPTZ = NULL,\n    if_exists BOOL = FALSE,\n    check_config REGPROC = NULL,\n    fixed_schedule BOOL = NULL,\n    initial_start TIMESTAMPTZ = NULL,\n    timezone TEXT DEFAULT NULL\n)\nRETURNS TABLE (job_id INTEGER, schedule_interval INTERVAL, max_runtime INTERVAL, max_retries INTEGER, retry_period INTERVAL, scheduled BOOL, config JSONB,\nnext_start TIMESTAMPTZ, check_config TEXT, fixed_schedule BOOL, initial_start TIMESTAMPTZ, timezone TEXT)\nAS '@MODULE_PATHNAME@', 'ts_update_placeholder'\nLANGUAGE C VOLATILE;"
  },
  {
    "path": "sql/updates/2.20.0--2.20.1.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.20.1--2.20.0.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.20.1--2.20.2.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.20.2--2.20.1.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.20.2--2.20.3.sql",
    "content": "-- Make chunk_id use NULL to mark special entries instead of 0\n-- (Invalid chunk) since that doesn't work with the FK constraint on\n-- chunk_id.\nALTER TABLE _timescaledb_catalog.chunk_column_stats ALTER COLUMN chunk_id DROP NOT NULL;\nUPDATE _timescaledb_catalog.chunk_column_stats SET chunk_id = NULL WHERE chunk_id = 0;\n"
  },
  {
    "path": "sql/updates/2.20.3--2.20.2.sql",
    "content": "-- Add back the chunk_column_stats NOT NULL constraint. But first\n-- delete all entries with with NULL since they will no longer be\n-- allowed. Note that reverting chunk_id back to 0 because it will\n-- violate the FK constraint. Even if we would revert, the downgrade\n-- tests for \"restore\" would fail due to violating entries. Removing\n-- the entries effectively means that collecting column stats for\n-- those columns will be disabled. It can be enabled again after\n-- downgrade. We emit a warning if anything was disabled.\nDO $$\nDECLARE\n    num_null_chunk_ids int;\nBEGIN\n\n    SELECT count(*) INTO num_null_chunk_ids\n    FROM _timescaledb_catalog.chunk_column_stats WHERE chunk_id IS NULL;\n\n    IF num_null_chunk_ids > 0 THEN\n       RAISE WARNING 'chunk skipping has been disabled for all hypertables'\n              USING HINT = 'Use enable_chunk_skipping() to re-enable chunk skipping';\n    END IF;\nEND\n$$;\n\nDELETE FROM _timescaledb_catalog.chunk_column_stats WHERE chunk_id IS NULL;\nALTER TABLE _timescaledb_catalog.chunk_column_stats ALTER COLUMN chunk_id SET NOT NULL;\n"
  },
  {
    "path": "sql/updates/2.20.3--2.21.0.sql",
    "content": "CREATE PROCEDURE _timescaledb_functions.process_hypertable_invalidations(\n    hypertable REGCLASS\n) LANGUAGE C AS '@MODULE_PATHNAME@', 'ts_update_placeholder';\n\nCREATE PROCEDURE @extschema@.add_process_hypertable_invalidations_policy(\n    hypertable REGCLASS,\n    schedule_interval INTERVAL,\n    if_not_exists BOOL = false,\n    initial_start TIMESTAMPTZ = NULL,\n    timezone TEXT = NULL\n) LANGUAGE C AS '@MODULE_PATHNAME@', 'ts_update_placeholder';\n\nCREATE PROCEDURE @extschema@.remove_process_hypertable_invalidations_policy(\n       hypertable REGCLASS,\n       if_exists BOOL = false\n) LANGUAGE C AS '@MODULE_PATHNAME@', 'ts_update_placeholder';\n\nDROP PROCEDURE IF EXISTS _timescaledb_functions.policy_compression(job_id INTEGER, config JSONB);\n\nDROP PROCEDURE IF EXISTS _timescaledb_internal.policy_compression_execute(\nINTEGER, INTEGER, ANYELEMENT, INTEGER, BOOLEAN, BOOLEAN, BOOLEAN\n);\n\nDROP PROCEDURE IF EXISTS _timescaledb_functions.policy_compression_execute(\n  INTEGER, INTEGER, ANYELEMENT, INTEGER, BOOLEAN, BOOLEAN, BOOLEAN, BOOLEAN\n);\n\nCREATE PROCEDURE _timescaledb_functions.policy_compression_execute(\n  job_id              INTEGER,\n  htid                INTEGER,\n  lag                 ANYELEMENT,\n  maxchunks           INTEGER,\n  verbose_log         BOOLEAN,\n  recompress_enabled  BOOLEAN,\n  reindex_enabled     BOOLEAN,\n  use_creation_time   BOOLEAN,\n  useam               BOOLEAN = NULL)\nAS $$\nBEGIN\n  -- empty body\nEND;\n$$ LANGUAGE PLPGSQL;\n\nDROP PROCEDURE @extschema@.refresh_continuous_aggregate(\n    continuous_aggregate REGCLASS,\n    window_start \"any\",\n    window_end \"any\",\n    force BOOLEAN\n);\n\nCREATE PROCEDURE @extschema@.refresh_continuous_aggregate(\n    continuous_aggregate     REGCLASS,\n    window_start             \"any\",\n    window_end               \"any\",\n    force                    BOOLEAN = FALSE,\n    options                  JSONB = NULL\n) LANGUAGE C AS '@MODULE_PATHNAME@', 'ts_update_placeholder';\n\n-- since we forgot to add the compression algorithms in the previous release to the preinstall script\n-- we add them here with an ON CONFLICT DO NOTHING clause\nINSERT INTO _timescaledb_catalog.compression_algorithm( id, version, name, description) values\n( 5, 1, 'COMPRESSION_ALGORITHM_BOOL', 'bool'),\n( 6, 1, 'COMPRESSION_ALGORITHM_NULL', 'null') ON CONFLICT (id) DO NOTHING\n;\n\nCREATE PROCEDURE @extschema@.detach_chunk(\n  chunk REGCLASS\n) LANGUAGE C AS '@MODULE_PATHNAME@', 'ts_update_placeholder';\n\nCREATE PROCEDURE @extschema@.attach_chunk(\n  hypertable REGCLASS,\n  chunk REGCLASS,\n  slices JSONB\n) LANGUAGE C AS '@MODULE_PATHNAME@', 'ts_update_placeholder';\n"
  },
  {
    "path": "sql/updates/2.21.0--2.20.3.sql",
    "content": "DROP PROCEDURE _timescaledb_functions.process_hypertable_invalidations(REGCLASS);\nDROP PROCEDURE @extschema@.add_process_hypertable_invalidations_policy(REGCLASS, INTERVAL, BOOL, TIMESTAMPTZ, TEXT);\nDROP PROCEDURE @extschema@.remove_process_hypertable_invalidations_policy(REGCLASS, BOOL);\nDROP PROCEDURE _timescaledb_functions.policy_process_hypertable_invalidations(INTEGER, JSONB);\nDROP FUNCTION _timescaledb_functions.policy_process_hypertable_invalidations_check(JSONB);\n\nDROP PROCEDURE IF EXISTS _timescaledb_functions.policy_compression(job_id INTEGER, config JSONB);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.policy_compression_execute(\n  INTEGER, INTEGER, ANYELEMENT, INTEGER, BOOLEAN, BOOLEAN, BOOLEAN, BOOLEAN, BOOLEAN\n);\n\nCREATE PROCEDURE _timescaledb_functions.policy_compression_execute(\n  job_id              INTEGER,\n  htid                INTEGER,\n  lag                 ANYELEMENT,\n  maxchunks           INTEGER,\n  verbose_log         BOOLEAN,\n  recompress_enabled  BOOLEAN,\n  use_creation_time   BOOLEAN,\n  useam               BOOLEAN = NULL)\nAS $$\nBEGIN\n  -- empty body\nEND;\n$$ LANGUAGE PLPGSQL;\n\nDROP PROCEDURE @extschema@.refresh_continuous_aggregate(\n    continuous_aggregate REGCLASS,\n    window_start \"any\",\n    window_end \"any\",\n    force BOOLEAN,\n    options JSONB\n);\n\nCREATE PROCEDURE @extschema@.refresh_continuous_aggregate(\n    continuous_aggregate     REGCLASS,\n    window_start             \"any\",\n    window_end               \"any\",\n    force                    BOOLEAN = FALSE\n) LANGUAGE C AS '@MODULE_PATHNAME@', 'ts_update_placeholder';\n\nDROP PROCEDURE IF EXISTS @extschema@.detach_chunk(REGCLASS);\nDROP PROCEDURE IF EXISTS @extschema@.attach_chunk(REGCLASS, REGCLASS, JSONB);\n"
  },
  {
    "path": "sql/updates/2.21.0--2.21.1.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.21.1--2.21.0.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.21.1--2.21.2.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.21.2--2.21.1.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.21.2--2.21.3.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.21.3--2.21.2.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.21.3--2.21.4.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.21.4--2.22.0.sql",
    "content": "\n-- block upgrade if hypercore access method is still in use\nDO $$\nBEGIN\n  IF EXISTS(SELECT from pg_class c join pg_am am ON c.relam=am.oid AND am.amname='hypercore') THEN\n    RAISE EXCEPTION 'TimescaleDB does no longer support the hypercore table access method. Convert all tables to heap access method before upgrading.';\n  END IF;\nEND\n$$;\n\nDROP OPERATOR CLASS IF EXISTS int4_ops USING hypercore_proxy;\nDROP ACCESS METHOD IF EXISTS hypercore_proxy;\nDROP ACCESS METHOD IF EXISTS hypercore;\n\nDROP FUNCTION IF EXISTS ts_hypercore_proxy_handler;\nDROP FUNCTION IF EXISTS ts_hypercore_handler;\nDROP FUNCTION IF EXISTS _timescaledb_debug.is_compressed_tid;\n\nDROP PROCEDURE IF EXISTS _timescaledb_functions.policy_compression_execute;\nDROP FUNCTION IF EXISTS @extschema@.add_compression_policy;\nDROP PROCEDURE IF EXISTS @extschema@.add_columnstore_policy;\nDROP FUNCTION IF EXISTS timescaledb_experimental.add_policies;\nDROP FUNCTION IF EXISTS @extschema@.compress_chunk;\nDROP PROCEDURE IF EXISTS @extschema@.convert_to_columnstore;\n\nCREATE FUNCTION @extschema@.compress_chunk(\n  uncompressed_chunk REGCLASS,\n  if_not_compressed BOOLEAN = true,\n  recompress BOOLEAN = false\n) RETURNS REGCLASS AS '@MODULE_PATHNAME@', 'ts_update_placeholder' LANGUAGE C VOLATILE;\n\nCREATE PROCEDURE @extschema@.convert_to_columnstore(\n    chunk REGCLASS,\n    if_not_columnstore BOOLEAN = true,\n    recompress BOOLEAN = false\n) AS '@MODULE_PATHNAME@', 'ts_update_placeholder' LANGUAGE C;\n\nCREATE FUNCTION @extschema@.add_compression_policy(\n    hypertable REGCLASS,\n    compress_after \"any\" = NULL,\n    if_not_exists BOOL = false,\n    schedule_interval INTERVAL = NULL,\n    initial_start TIMESTAMPTZ = NULL,\n    timezone TEXT = NULL,\n    compress_created_before INTERVAL = NULL\n)\nRETURNS INTEGER\nAS '@MODULE_PATHNAME@', 'ts_update_placeholder'\nLANGUAGE C VOLATILE;\n\nCREATE PROCEDURE @extschema@.add_columnstore_policy(\n    hypertable REGCLASS,\n    after \"any\" = NULL,\n    if_not_exists BOOL = false,\n    schedule_interval INTERVAL = NULL,\n    initial_start TIMESTAMPTZ = NULL,\n    timezone TEXT = NULL,\n    created_before INTERVAL = NULL\n) LANGUAGE C AS '@MODULE_PATHNAME@', 'ts_update_placeholder';\n\nCREATE FUNCTION timescaledb_experimental.add_policies(\n    relation REGCLASS,\n    if_not_exists BOOL = false,\n    refresh_start_offset \"any\" = NULL,\n    refresh_end_offset \"any\" = NULL,\n    compress_after \"any\" = NULL,\n    drop_after \"any\" = NULL\n) RETURNS BOOL AS '@MODULE_PATHNAME@', 'ts_update_placeholder' LANGUAGE C VOLATILE;\n\nCREATE PROCEDURE _timescaledb_functions.policy_compression_execute(\n  job_id              INTEGER,\n  htid                INTEGER,\n  lag                 ANYELEMENT,\n  maxchunks           INTEGER,\n  verbose_log         BOOLEAN,\n  recompress_enabled  BOOLEAN,\n  reindex_enabled     BOOLEAN,\n  use_creation_time   BOOLEAN\n)\nAS $$ BEGIN END $$ LANGUAGE PLPGSQL;\n\nINSERT INTO _timescaledb_catalog.compression_algorithm( id, version, name, description) values\n( 7, 1, 'COMPRESSION_ALGORITHM_UUID', 'uuid');\n\nDROP FUNCTION IF EXISTS _timescaledb_internal.chunk_index_clone(oid);\nDROP FUNCTION IF EXISTS _timescaledb_functions.chunk_index_clone(oid);\nDROP FUNCTION IF EXISTS _timescaledb_internal.chunk_index_replace(oid,oid);\nDROP FUNCTION IF EXISTS _timescaledb_functions.chunk_index_replace(oid,oid);\n\n-- Update compression settings\nCREATE TABLE _timescaledb_catalog.tempsettings (LIKE _timescaledb_catalog.compression_settings);\nINSERT INTO _timescaledb_catalog.tempsettings SELECT * FROM _timescaledb_catalog.compression_settings;\nDROP VIEW IF EXISTS timescaledb_information.hypertable_columnstore_settings;\nDROP VIEW IF EXISTS timescaledb_information.chunk_columnstore_settings;\nDROP VIEW IF EXISTS timescaledb_information.hypertable_compression_settings;\nDROP VIEW IF EXISTS timescaledb_information.chunk_compression_settings;\nDROP VIEW IF EXISTS timescaledb_information.compression_settings;\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.compression_settings;\nDROP TABLE _timescaledb_catalog.compression_settings;\n\nCREATE TABLE _timescaledb_catalog.compression_settings (\n  relid regclass NOT NULL,\n  compress_relid regclass NULL,\n  segmentby text[],\n  orderby text[],\n  orderby_desc bool[],\n  orderby_nullsfirst bool[],\n  index jsonb,\n  CONSTRAINT compression_settings_pkey PRIMARY KEY (relid),\n  CONSTRAINT compression_settings_check_segmentby CHECK (array_ndims(segmentby) = 1),\n  CONSTRAINT compression_settings_check_orderby_null CHECK ((orderby IS NULL AND orderby_desc IS NULL AND orderby_nullsfirst IS NULL) OR (orderby IS NOT NULL AND orderby_desc IS NOT NULL AND orderby_nullsfirst IS NOT NULL)),\n  CONSTRAINT compression_settings_check_orderby_cardinality CHECK (array_ndims(orderby) = 1 AND array_ndims(orderby_desc) = 1 AND array_ndims(orderby_nullsfirst) = 1 AND cardinality(orderby) = cardinality(orderby_desc) AND cardinality(orderby) = cardinality(orderby_nullsfirst))\n);\n\n-- Insert updated settings\nINSERT INTO _timescaledb_catalog.compression_settings\nSELECT\n    cs.relid,\n    cs.compress_relid,\n    cs.segmentby,\n    cs.orderby,\n    cs.orderby_desc,\n    cs.orderby_nullsfirst\nFROM\n    _timescaledb_catalog.tempsettings cs;\n\n-- Add index on secondary compressed relid key\nCREATE INDEX compression_settings_compress_relid_idx ON _timescaledb_catalog.compression_settings (compress_relid);\n\nDROP TABLE _timescaledb_catalog.tempsettings;\nGRANT SELECT ON _timescaledb_catalog.compression_settings TO PUBLIC;\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.compression_settings', '');\n\nCREATE FUNCTION _timescaledb_functions.jsonb_get_matching_index_entry(\n    config      jsonb,\n    attr_name   text,\n    target_type text\n)\nRETURNS jsonb\nAS $$\nBEGIN\n  --empty body\n  RETURN NULL;\nEND;\n$$ LANGUAGE PLPGSQL\nSET search_path TO pg_catalog, pg_temp;\n\n--migration script\nCREATE FUNCTION process_single_table_index_migrate(\n    IN target_schema TEXT,\n    IN target_table TEXT\n)\nRETURNS jsonb\nAS $$\nDECLARE\n    col_name TEXT;\n    json_entries jsonb := '[]';\n    type_part TEXT;\n    sparse_type TEXT;\n    col_part TEXT;\nBEGIN\n    -- Step 1: Loop over relevant column names\n    FOR col_name IN\n        SELECT column_name\n        FROM information_schema.columns\n        WHERE table_name = target_table\n          AND table_schema = target_schema\n          AND column_name LIKE '_ts_meta_v2_%'\n    LOOP\n        -- Step 2: Parse column name: _ts_meta_v2_<type>_<col>\n        col_name := regexp_replace(col_name, '^_ts_meta_v2_', '');\n\n        type_part := split_part(col_name, '_', 1);\n        col_part := substring(col_name FROM length(type_part) + 2);  -- skip type and underscore\n\n        IF type_part = 'bloom1' THEN\n            sparse_type := 'bloom';\n        ELSIF type_part = 'min' THEN\n            sparse_type := 'minmax';\n        ELSE\n            CONTINUE;\n        END IF;\n\n        json_entries := json_entries || jsonb_build_object('type', sparse_type, 'source', 'default', 'column', col_part);\n    END LOOP;\n\n    RETURN json_entries;\nEND;\n$$ LANGUAGE PLPGSQL\nSET search_path TO pg_catalog, pg_temp;\n\nDO $$\nDECLARE\n    rec RECORD;\n    schema_name TEXT;\n    table_name TEXT;\n    json_entries JSONB;\n    rows_updated INT;\nBEGIN\n    FOR rec IN\n        SELECT relid, compress_relid\n        FROM _timescaledb_catalog.compression_settings\n        WHERE index IS NULL\n    LOOP\n        IF rec.compress_relid IS NULL THEN\n            CONTINUE;\n        ELSE\n            -- Get schema and table name from compress_relid\n            SELECT n.nspname, c.relname\n            INTO schema_name, table_name\n            FROM pg_class c\n            JOIN pg_namespace n ON c.relnamespace = n.oid\n            WHERE c.oid = rec.compress_relid;\n        END IF;\n\n        -- Call procedure for that table\n        json_entries := process_single_table_index_migrate(schema_name, table_name);\n\n        IF jsonb_array_length(json_entries) > 0 THEN\n            UPDATE _timescaledb_catalog.compression_settings\n            SET index = json_entries\n            WHERE relid = rec.relid AND index IS NULL;\n        END IF;\n    END LOOP;\nEND $$;\n\nDROP FUNCTION process_single_table_index_migrate(text, text);\n\n-- add orderby minmax columns to sparse index settings\nUPDATE _timescaledb_catalog.compression_settings cs\nSET index = COALESCE(index, '[]'::jsonb) ||\n            (\n            SELECT jsonb_agg(jsonb_build_object(\n                                'type', 'minmax',\n                                'source', 'orderby',\n                                'column', elem))\n            FROM unnest(cs.orderby) AS elem\n            )\nWHERE cs.orderby IS NOT NULL\nAND cardinality(cs.orderby) > 0\nAND compress_relid IS NOT NULL;\n\nDROP FUNCTION IF EXISTS _timescaledb_internal.indexes_local_size;\nDROP FUNCTION IF EXISTS _timescaledb_functions.indexes_local_size;\n\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.chunk_index;\nDROP TABLE IF EXISTS _timescaledb_catalog.chunk_index;\n\n-- cagg materialization ranges\nCREATE TABLE _timescaledb_catalog.continuous_aggs_materialization_ranges (\n  materialization_id integer,\n  lowest_modified_value bigint NOT NULL,\n  greatest_modified_value bigint NOT NULL,\n  -- table constraints\n  CONSTRAINT continuous_aggs_materialization_ranges_materialization_id_fkey FOREIGN KEY (materialization_id) REFERENCES _timescaledb_catalog.continuous_agg (mat_hypertable_id) ON DELETE CASCADE\n);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.continuous_aggs_materialization_ranges', '');\n\nCREATE INDEX continuous_aggs_materialization_ranges_idx ON _timescaledb_catalog.continuous_aggs_materialization_ranges (materialization_id, lowest_modified_value ASC);\n\nGRANT SELECT ON TABLE _timescaledb_catalog.continuous_aggs_materialization_ranges TO PUBLIC;\n\n"
  },
  {
    "path": "sql/updates/2.22.0--2.21.3.sql",
    "content": "ALTER EXTENSION timescaledb DROP VIEW timescaledb_information.continuous_aggregates;\n\nDROP VIEW timescaledb_information.continuous_aggregates;\n\nDROP PROCEDURE _timescaledb_functions.process_hypertable_invalidations(REGCLASS[]);\nDROP PROCEDURE _timescaledb_functions.process_hypertable_invalidations(NAME);\nDROP FUNCTION _timescaledb_functions.cagg_parse_invalidation_record(BYTEA);\nDROP FUNCTION _timescaledb_functions.has_invalidation_trigger(regclass);\nDROP FUNCTION _timescaledb_functions.invalidation_plugin_name();\n\nCREATE FUNCTION ts_hypercore_handler(internal) RETURNS table_am_handler\nAS '@MODULE_PATHNAME@', 'ts_hypercore_handler' LANGUAGE C;\n\nCREATE FUNCTION ts_hypercore_proxy_handler(internal) RETURNS index_am_handler\nAS '@MODULE_PATHNAME@', 'ts_hypercore_proxy_handler' LANGUAGE C;\n\nCREATE ACCESS METHOD hypercore TYPE TABLE HANDLER ts_hypercore_handler;\nCOMMENT ON ACCESS METHOD hypercore IS 'Storage engine using hybrid row/columnar compression';\n\nCREATE ACCESS METHOD hypercore_proxy TYPE INDEX HANDLER ts_hypercore_proxy_handler;\nCOMMENT ON ACCESS METHOD hypercore_proxy IS 'Hypercore proxy index access method';\n\nCREATE OPERATOR CLASS int4_ops\nDEFAULT FOR TYPE int4 USING hypercore_proxy AS\n       OPERATOR 1 = (int4, int4),\n       FUNCTION 1 hashint4(int4);\n\nCREATE FUNCTION _timescaledb_debug.is_compressed_tid(tid) RETURNS BOOL\nAS '@MODULE_PATHNAME@', 'ts_update_placeholder' LANGUAGE C STRICT;\n\nDROP PROCEDURE IF EXISTS _timescaledb_functions.policy_compression_execute;\nDROP FUNCTION IF EXISTS @extschema@.add_compression_policy;\nDROP PROCEDURE IF EXISTS @extschema@.add_columnstore_policy;\nDROP FUNCTION IF EXISTS timescaledb_experimental.add_policies;\nDROP FUNCTION IF EXISTS @extschema@.compress_chunk;\nDROP PROCEDURE IF EXISTS @extschema@.convert_to_columnstore;\n\nDO $$\nDECLARE\n    remove_these text[];\nBEGIN\n    SELECT array_agg(format('%I.%I', user_view_schema, user_view_name))\n      INTO remove_these\n      FROM _timescaledb_catalog.continuous_agg\n      JOIN _timescaledb_catalog.hypertable ht\n        ON raw_hypertable_id = ht.id\n     WHERE 'ts_cagg_invalidation_trigger' NOT IN (\n         SELECT tgname FROM pg_trigger\n          WHERE tgrelid = format('%I.%I', ht.schema_name, ht.table_name)::regclass\n     );\n\n    IF array_length(remove_these, 1) > 0 THEN\n        RAISE EXCEPTION 'cannot downgrade because there are continuous aggregates using WAL-based invalidation collection'\n            USING\n                ERRCODE = 'object_not_in_prerequisite_state',\n                DETAIL = format('Please remove these CAggs before downgrade: %s.', array_to_string(remove_these, ','));\n    END IF;\nEND\n$$;\n\nCREATE FUNCTION @extschema@.compress_chunk(\n  uncompressed_chunk REGCLASS,\n  if_not_compressed BOOLEAN = true,\n  recompress BOOLEAN = false,\n  hypercore_use_access_method BOOL = NULL\n) RETURNS REGCLASS AS '@MODULE_PATHNAME@', 'ts_update_placeholder' LANGUAGE C VOLATILE;\n\nCREATE PROCEDURE @extschema@.convert_to_columnstore(\n  chunk REGCLASS,\n  if_not_columnstore BOOLEAN = true,\n  recompress BOOLEAN = false,\n  hypercore_use_access_method BOOL = NULL\n) AS '@MODULE_PATHNAME@', 'ts_update_placeholder' LANGUAGE C;\n\nCREATE FUNCTION @extschema@.add_compression_policy(\n    hypertable REGCLASS,\n    compress_after \"any\" = NULL,\n    if_not_exists BOOL = false,\n    schedule_interval INTERVAL = NULL,\n    initial_start TIMESTAMPTZ = NULL,\n    timezone TEXT = NULL,\n    compress_created_before INTERVAL = NULL,\n    hypercore_use_access_method BOOL = NULL\n)\nRETURNS INTEGER\nAS '@MODULE_PATHNAME@', 'ts_update_placeholder'\nLANGUAGE C VOLATILE;\n\nCREATE PROCEDURE @extschema@.add_columnstore_policy(\n    hypertable REGCLASS,\n    after \"any\" = NULL,\n    if_not_exists BOOL = false,\n    schedule_interval INTERVAL = NULL,\n    initial_start TIMESTAMPTZ = NULL,\n    timezone TEXT = NULL,\n    created_before INTERVAL = NULL,\n    hypercore_use_access_method BOOL = NULL\n) LANGUAGE C AS '@MODULE_PATHNAME@', 'ts_update_placeholder';\n\nCREATE OR REPLACE FUNCTION timescaledb_experimental.add_policies(\n    relation REGCLASS,\n    if_not_exists BOOL = false,\n    refresh_start_offset \"any\" = NULL,\n    refresh_end_offset \"any\" = NULL,\n    compress_after \"any\" = NULL,\n    drop_after \"any\" = NULL,\n    hypercore_use_access_method BOOL = NULL)\nRETURNS BOOL\nAS '@MODULE_PATHNAME@', 'ts_update_placeholder'\nLANGUAGE C VOLATILE;\n\nCREATE PROCEDURE\n_timescaledb_functions.policy_compression_execute(\n  job_id              INTEGER,\n  htid                INTEGER,\n  lag                 ANYELEMENT,\n  maxchunks           INTEGER,\n  verbose_log         BOOLEAN,\n  recompress_enabled  BOOLEAN,\n  reindex_enabled     BOOLEAN,\n  use_creation_time   BOOLEAN,\n  useam               BOOLEAN = NULL)\nAS $$ BEGIN END $$ LANGUAGE PLPGSQL;\n\nDROP FUNCTION IF EXISTS _timescaledb_functions.generate_uuid_v7;\nDROP FUNCTION IF EXISTS _timescaledb_functions.uuid_v7_from_timestamptz;\nDROP FUNCTION IF EXISTS _timescaledb_functions.uuid_v7_from_timestamptz_zeroed;\nDROP FUNCTION IF EXISTS _timescaledb_functions.timestamptz_from_uuid_v7;\nDROP FUNCTION IF EXISTS _timescaledb_functions.timestamptz_from_uuid_v7_with_microseconds;\nDROP FUNCTION IF EXISTS _timescaledb_functions.uuid_version;\n\nDELETE FROM _timescaledb_catalog.compression_algorithm WHERE id = 7 AND version = 1 AND name = 'COMPRESSION_ALGORITHM_UUID';\n\n-- downgrade compression settings\nCREATE TABLE _timescaledb_catalog.tempsettings (LIKE _timescaledb_catalog.compression_settings);\nINSERT INTO _timescaledb_catalog.tempsettings SELECT * FROM _timescaledb_catalog.compression_settings;\nDROP VIEW timescaledb_information.hypertable_columnstore_settings;\nDROP VIEW timescaledb_information.chunk_columnstore_settings;\nDROP VIEW timescaledb_information.hypertable_compression_settings;\nDROP VIEW timescaledb_information.chunk_compression_settings;\nDROP VIEW timescaledb_information.compression_settings;\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.compression_settings;\nDROP TABLE _timescaledb_catalog.compression_settings;\n\nCREATE TABLE _timescaledb_catalog.compression_settings (\n  relid regclass NOT NULL,\n  compress_relid regclass NULL,\n  segmentby text[],\n  orderby text[],\n  orderby_desc bool[],\n  orderby_nullsfirst bool[],\n  CONSTRAINT compression_settings_pkey PRIMARY KEY (relid),\n  CONSTRAINT compression_settings_check_segmentby CHECK (array_ndims(segmentby) = 1),\n  CONSTRAINT compression_settings_check_orderby_null CHECK ((orderby IS NULL AND orderby_desc IS NULL AND orderby_nullsfirst IS NULL) OR (orderby IS NOT NULL AND orderby_desc IS NOT NULL AND orderby_nullsfirst IS NOT NULL)),\n  CONSTRAINT compression_settings_check_orderby_cardinality CHECK (array_ndims(orderby) = 1 AND array_ndims(orderby_desc) = 1 AND array_ndims(orderby_nullsfirst) = 1 AND cardinality(orderby) = cardinality(orderby_desc) AND cardinality(orderby) = cardinality(orderby_nullsfirst))\n);\n\n-- Revert information in compression settings\nINSERT INTO _timescaledb_catalog.compression_settings\nSELECT\n    cs.relid,\n    cs.compress_relid,\n    cs.segmentby,\n    cs.orderby,\n    cs.orderby_desc,\n    cs.orderby_nullsfirst\nFROM\n    _timescaledb_catalog.tempsettings cs;\n\nDROP TABLE _timescaledb_catalog.tempsettings;\n\nCREATE INDEX compression_settings_compress_relid_idx ON _timescaledb_catalog.compression_settings (compress_relid);\n\nGRANT SELECT ON _timescaledb_catalog.compression_settings TO PUBLIC;\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.compression_settings', '');\n\nDROP FUNCTION IF EXISTS _timescaledb_functions.jsonb_get_matching_index_entry(jsonb, text, text);\n\n-- block downgrade if a table has NULL orderby setting (not allowed in 2.21)\nDO $$\nBEGIN\n  IF EXISTS (\n        SELECT 1\n        FROM _timescaledb_catalog.compression_settings\n        WHERE orderby IS NULL\n        ) THEN\n    RAISE EXCEPTION 'TimescaleDB 2.21 can not have NULL columnstore orderby settings. Use ALTER TABLE to configure them before downgrading.';\n  END IF;\nEND\n$$;\n\n-- remove empty segmentby\nUPDATE _timescaledb_catalog.compression_settings\nSET segmentby = NULL\nWHERE segmentby = '{}';\n\nDROP FUNCTION IF EXISTS _timescaledb_functions.index_matches;\n\nCREATE TABLE _timescaledb_catalog.chunk_index (\n  chunk_id integer NOT NULL,\n  index_name name NOT NULL,\n  hypertable_id integer NOT NULL,\n  hypertable_index_name name NOT NULL,\n  -- table constraints\n  CONSTRAINT chunk_index_chunk_id_index_name_key UNIQUE (chunk_id, index_name),\n  CONSTRAINT chunk_index_chunk_id_fkey FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk (id) ON DELETE CASCADE,\n  CONSTRAINT chunk_index_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE\n);\n\nCREATE INDEX chunk_index_hypertable_id_hypertable_index_name_idx ON _timescaledb_catalog.chunk_index (hypertable_id, hypertable_index_name);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.chunk_index', '');\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.temp_index_keycolumns(oid)\nRETURNS text[] AS $$\n  SELECT array_agg(att.attname ORDER BY array_position(idx.indkey, att.attnum)) AS index_columns\n  FROM pg_index AS idx\n  JOIN pg_attribute AS att ON att.attrelid = idx.indrelid\n  WHERE idx.indexrelid = $1 AND att.attnum = ANY(idx.indkey);\n$$ LANGUAGE SQL IMMUTABLE SET search_path TO pg_catalog, pg_temp;\n\nINSERT INTO _timescaledb_catalog.chunk_index (chunk_id, index_name, hypertable_id, hypertable_index_name)\nSELECT\n  ch.id,\n  ch_ci.relname,\n  h.id,\n  ht_ci.relname\nFROM _timescaledb_catalog.hypertable h\nJOIN pg_index ht_i ON ht_i.indrelid = format('%I.%I',h.schema_name,h.table_name)::regclass\nJOIN pg_class ht_ci ON ht_ci.oid=ht_i.indexrelid\nJOIN _timescaledb_catalog.chunk ch ON ch.hypertable_id=h.id AND NOT ch.dropped\nJOIN pg_index ch_i ON\n  ch_i.indrelid=format('%I.%I',ch.schema_name,ch.table_name)::regclass AND\n  ht_i.indnatts = ch_i.indnatts AND\n  ht_i.indnkeyatts = ch_i.indnkeyatts AND\n  ht_i.indisunique = ch_i.indisunique AND\n  ht_i.indnullsnotdistinct = ch_i.indnullsnotdistinct AND\n  ht_i.indisprimary = ch_i.indisprimary AND\n  ht_i.indisexclusion = ch_i.indisexclusion AND\n  ht_i.indimmediate = ch_i.indimmediate AND\n  ht_i.indcollation=ch_i.indcollation AND\n  ht_i.indclass=ch_i.indclass AND\n  ht_i.indoption = ch_i.indoption AND\n  ht_i.indexprs IS NOT DISTINCT FROM ch_i.indexprs AND\n  ht_i.indpred IS NOT DISTINCT FROM ch_i.indpred AND\n  _timescaledb_functions.temp_index_keycolumns(ht_i.indexrelid) = _timescaledb_functions.temp_index_keycolumns(ch_i.indexrelid)\nJOIN pg_class ch_ci ON ch_ci.oid=ch_i.indexrelid;\n\nDROP FUNCTION IF EXISTS _timescaledb_functions.temp_index_keycolumns(oid);\n\nGRANT SELECT ON TABLE _timescaledb_catalog.chunk_index TO PUBLIC;\n\nDROP FUNCTION IF EXISTS _timescaledb_functions.chunk_status_text(regclass);\nDROP FUNCTION IF EXISTS _timescaledb_functions.chunk_status_text(int);\n\nDO\n$$\nDECLARE\n  caggs_to_refresh TEXT;\nBEGIN\n  IF EXISTS (SELECT FROM _timescaledb_catalog.continuous_aggs_materialization_ranges LIMIT 1) THEN\n    SELECT string_agg(format('%I.%I', user_view_schema, user_view_name), ', ' ORDER BY user_view_schema, user_view_name)\n    INTO caggs_to_refresh\n    FROM _timescaledb_catalog.continuous_aggs_materialization_ranges\n    JOIN _timescaledb_catalog.continuous_agg ON materialization_id = mat_hypertable_id;\n\n    RAISE EXCEPTION 'cannot downgrade because there are pending CAgg refreshes'\n      USING\n        ERRCODE = 'object_not_in_prerequisite_state',\n        DETAIL = format('Please refresh the CAggs before downgrade: %s.', caggs_to_refresh);\n  END IF;\nEND;\n$$;\n\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.continuous_aggs_materialization_ranges;\n\nDROP TABLE IF EXISTS _timescaledb_catalog.continuous_aggs_materialization_ranges;\n\nDROP FUNCTION _timescaledb_functions.job_history_bsearch(TIMESTAMPTZ);\n\nDROP FUNCTION IF EXISTS @extschema@.generate_uuidv7();\nDROP FUNCTION IF EXISTS @extschema@.to_uuidv7(timestamptz);\nDROP FUNCTION IF EXISTS @extschema@.to_uuidv7_boundary(timestamptz);\nDROP FUNCTION IF EXISTS @extschema@.uuid_timestamp(uuid);\nDROP FUNCTION IF EXISTS @extschema@.uuid_timestamp_micros(uuid);\nDROP FUNCTION IF EXISTS @extschema@.uuid_version(uuid);\n\n"
  },
  {
    "path": "sql/updates/2.22.0--2.22.1.sql",
    "content": "-- Fix wrong migration by removing all sparse index configurations\n-- which only contain auto sparse indexing definitions on hypertable\nDO $$\nDECLARE\n    rec RECORD;\n    num_config INT;\nBEGIN\n\tIF NOT EXISTS (\n\t\tSELECT column_name\n\t\tFROM information_schema.columns\n\t\tWHERE table_schema = '_timescaledb_catalog'\n\t\tAND table_name = 'compression_settings'\n\t\tAND column_name = 'index') THEN\n\t\tRETURN;\n\tEND IF;\n\n    FOR rec IN\n        SELECT relid, compress_relid, index\n        FROM _timescaledb_catalog.compression_settings\n\t\tWHERE compress_relid IS NULL\n    LOOP\n\t\tnum_config:=0;\n\n\t\tSELECT count(*)\n\t\tINTO num_config\n\t\tFROM jsonb_array_elements(rec.index) AS idx\n\t\tWHERE idx::jsonb @> '{\"storage\":\"config\"}'::jsonb;\n\n\t\tIF num_config = 0 THEN\n            UPDATE _timescaledb_catalog.compression_settings\n            SET index = NULL\n            WHERE relid = rec.relid;\n\t\tEND IF;\n    END LOOP;\nEND $$;\n"
  },
  {
    "path": "sql/updates/2.22.1--2.22.0.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.22.1--2.23.0.sql",
    "content": "DO $$\nBEGIN\n    UPDATE _timescaledb_config.bgw_job\n      SET config = config || '{\"max_successes_per_job\": 1000, \"max_failures_per_job\": 1000}',\n          schedule_interval = '6 hours'\n    WHERE id = 3; -- system job retention\n\n    RAISE WARNING 'job history configuration modified'\n    USING DETAIL = 'The job history will only keep the last 1000 successes and failures and run once each day.';\nEND\n$$;\n\nDROP VIEW IF EXISTS timescaledb_information.job_stats;\nDROP VIEW IF EXISTS timescaledb_information.continuous_aggregates;\n\n-- remove cagg trigger from all hypertables and chunks\nDO $$\nDECLARE\n  rel regclass;\nBEGIN\n  FOR rel IN SELECT format('%I.%I', schema_name, table_name)::regclass\n    FROM _timescaledb_catalog.hypertable ht\n  LOOP\n    EXECUTE format('DROP TRIGGER IF EXISTS ts_cagg_invalidation_trigger ON %s;', rel);\n  END LOOP;\n  FOR rel IN SELECT format('%I.%I', schema_name, table_name)::regclass\n    FROM _timescaledb_catalog.chunk ch\n  LOOP\n    EXECUTE format('DROP TRIGGER IF EXISTS ts_cagg_invalidation_trigger ON %s;', rel);\n  END LOOP;\nEND\n$$;\n\nDROP FUNCTION IF EXISTS _timescaledb_internal.continuous_agg_invalidation_trigger();\nDROP FUNCTION IF EXISTS _timescaledb_functions.continuous_agg_invalidation_trigger();\nDROP FUNCTION IF EXISTS _timescaledb_functions.has_invalidation_trigger(regclass);\n\n-- remove ts_insert_blocker trigger from all hypertables\nDO $$\nDECLARE\n  rel regclass;\nBEGIN\n  FOR rel IN SELECT format('%I.%I', schema_name, table_name)::regclass\n    FROM _timescaledb_catalog.hypertable ht\n  LOOP\n    EXECUTE format('DROP TRIGGER IF EXISTS ts_insert_blocker ON %s;', rel);\n  END LOOP;\nEND\n$$;\n\nDROP FUNCTION IF EXISTS _timescaledb_internal.insert_blocker();\nDROP FUNCTION IF EXISTS _timescaledb_functions.insert_blocker();"
  },
  {
    "path": "sql/updates/2.23.0--2.22.1.sql",
    "content": "DO $$\nBEGIN\n    UPDATE _timescaledb_config.bgw_job\n      SET config = config - 'max_successes_per_job' - 'max_failures_per_job',\n          schedule_interval = '1 month'\n    WHERE id = 3; -- system job retention\n\n    RAISE WARNING 'job history configuration modified'\n    USING DETAIL = 'The job history will keep full history for each job and run once each month.';\nEND\n$$;\n\nDROP VIEW IF EXISTS timescaledb_information.job_stats;\nDROP VIEW IF EXISTS timescaledb_information.continuous_aggregates;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.continuous_agg_invalidation_trigger() RETURNS TRIGGER AS '@MODULE_PATHNAME@', 'ts_update_placeholder' LANGUAGE C;\n\n-- add cagg trigger to hypertables with caggs and their chunks\nDO $$\nDECLARE\n  v_hypertable regclass;\n  v_chunk regclass;\n  v_hypertable_id integer;\nBEGIN\n  FOR v_hypertable_id, v_hypertable IN SELECT ht.id, format('%I.%I', schema_name, table_name)::regclass\n  FROM _timescaledb_catalog.hypertable ht WHERE compression_state <> 2\n    AND EXISTS (SELECT FROM _timescaledb_catalog.continuous_agg agg WHERE agg.raw_hypertable_id = ht.id)\n  LOOP\n    EXECUTE format('CREATE TRIGGER ts_cagg_invalidation_trigger AFTER INSERT OR UPDATE OR DELETE ON %s FOR EACH ROW EXECUTE FUNCTION _timescaledb_functions.continuous_agg_invalidation_trigger(''%s'');', v_hypertable, v_hypertable_id);\n    FOR v_chunk IN SELECT format('%I.%I', schema_name, table_name)::regclass\n      FROM _timescaledb_catalog.chunk ch WHERE ch.hypertable_id = v_hypertable_id\n    LOOP\n      EXECUTE format('CREATE TRIGGER ts_cagg_invalidation_trigger AFTER INSERT OR UPDATE OR DELETE ON %s FOR EACH ROW EXECUTE FUNCTION _timescaledb_functions.continuous_agg_invalidation_trigger(''%s'');', v_chunk, v_hypertable_id);\n    END LOOP;\n  END LOOP;\nEND\n$$;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.insert_blocker() RETURNS TRIGGER AS '@MODULE_PATHNAME@', 'ts_update_placeholder' LANGUAGE C;\n\n-- add ts_insert_blocker trigger to hypertables\nDO $$\nDECLARE\n  v_hypertable regclass;\nBEGIN\n  FOR v_hypertable IN SELECT format('%I.%I', schema_name, table_name)::regclass\n  FROM _timescaledb_catalog.hypertable ht\n  LOOP\n    EXECUTE format('CREATE TRIGGER ts_insert_blocker BEFORE INSERT ON %s FOR EACH ROW EXECUTE FUNCTION _timescaledb_functions.insert_blocker();', v_hypertable);\n  END LOOP;\nEND\n$$;"
  },
  {
    "path": "sql/updates/2.23.0--2.23.1.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.23.1--2.23.0.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.23.1--2.24.0.sql",
    "content": "DROP FUNCTION IF EXISTS _timescaledb_functions.policy_job_stat_history_retention;\nDROP VIEW IF EXISTS timescaledb_information.chunks;\n\n-- Add support for concurrent merge_chunks()\nCREATE TABLE _timescaledb_catalog.chunk_rewrite (\n  chunk_relid REGCLASS NOT NULL,\n  new_relid REGCLASS NOT NULL,\n  CONSTRAINT chunk_rewrite_key UNIQUE (chunk_relid)\n);\n\nGRANT SELECT ON _timescaledb_catalog.chunk_rewrite TO PUBLIC;\nDROP PROCEDURE IF EXISTS @extschema@.merge_chunks(REGCLASS, REGCLASS);\n\n\n-- Check whether the database has the sparse bloom filter indexes on compressed\n-- chunks, which will require manual action to re-enable.\nDO $$\nDECLARE\n    num_chunks_with_bloom int;\nBEGIN\n    SELECT count(*) INTO num_chunks_with_bloom\n    FROM pg_attribute WHERE attname LIKE '_ts_meta_v2_bloom1_%';\n\n    IF num_chunks_with_bloom > 0 THEN\n       RAISE WARNING 'bloom filter sparse indexes require action to re-enable'\n              USING HINT = 'See the changelog for details.';\n    END IF;\nEND\n$$;\n"
  },
  {
    "path": "sql/updates/2.24.0--2.23.1.sql",
    "content": "DROP FUNCTION _timescaledb_functions.bloom1_contains_any(_timescaledb_internal.bloom1, anyarray);\n\nDROP FUNCTION IF EXISTS _timescaledb_functions.policy_job_stat_history_retention;\nDROP VIEW IF EXISTS timescaledb_information.chunks;\n\n-- Revert support for concurrent merge chunks()\nDROP PROCEDURE IF EXISTS _timescaledb_functions.chunk_rewrite_cleanup();\nDROP PROCEDURE IF EXISTS @extschema@.merge_chunks_concurrently(REGCLASS[]);\nDROP PROCEDURE IF EXISTS @extschema@.merge_chunks(REGCLASS, REGCLASS, BOOLEAN);\nDROP TABLE IF EXISTS _timescaledb_catalog.chunk_rewrite;\n\n-- Remove UUID time_bucket functions\nDROP FUNCTION IF EXISTS @extschema@.time_bucket(INTERVAL, UUID);\nDROP FUNCTION IF EXISTS @extschema@.time_bucket(INTERVAL, UUID, TIMESTAMPTZ);\nDROP FUNCTION IF EXISTS @extschema@.time_bucket(INTERVAL, UUID, INTERVAL);\nDROP FUNCTION IF EXISTS @extschema@.time_bucket(INTERVAL, UUID, TEXT, TIMESTAMPTZ, INTERVAL);"
  },
  {
    "path": "sql/updates/2.24.0--2.25.0.sql",
    "content": "DROP VIEW IF EXISTS timescaledb_information.dimensions;\n-- Block update if CAggs in old format are found\nDO\n$$\nDECLARE\n  caggs text;\nBEGIN\n  SELECT string_agg(format('%I.%I', user_view_schema, user_view_name), ', ')\n  INTO caggs\n  FROM _timescaledb_catalog.continuous_agg\n  WHERE finalized IS FALSE\n  GROUP BY user_view_schema, user_view_name\n  ORDER BY user_view_schema, user_view_name;\n\n  IF caggs IS NOT NULL THEN\n    RAISE\n      EXCEPTION 'continuous aggregates with old format found, update blocked'\n      USING\n        DETAIL = format('Continuous Aggregates: %s', caggs),\n        HINT = 'You should use `cagg_migrate` procedure to migrate to the new format.';\n  END IF;\nEND\n$$;\n\n-- Block update if CAggs using time_bucket_ng are found\nDO\n$$\nDECLARE\n  caggs text;\nBEGIN\n  SELECT string_agg(pg_catalog.format('%I.%I', user_view_schema, user_view_name), ', ')\n  INTO caggs\n  FROM _timescaledb_catalog.continuous_agg cagg\n  JOIN _timescaledb_catalog.continuous_aggs_bucket_function AS bf ON (cagg.mat_hypertable_id = bf.mat_hypertable_id)\n  WHERE bf.bucket_func::text LIKE '%time_bucket_ng%';\n\n  IF caggs IS NOT NULL THEN\n    RAISE\n      EXCEPTION 'continuous aggregates using time_bucket_ng found, update blocked'\n      USING\n        DETAIL = format('Continuous Aggregates: %s', caggs),\n        HINT = 'time_bucket_ng has been removed. Please migrate the continuous aggregates using `cagg_migrate` before updating';\n  END IF;\nEND\n$$;\n\n--\n-- Rebuild the catalog table `_timescaledb_catalog.continuous_agg` to remove `finalized` column\n--\n\n-- (1) Remove cagg migration functions and procedures from public and internal schemas\nDROP PROCEDURE IF EXISTS @extschema@.cagg_migrate (REGCLASS, BOOLEAN, BOOLEAN);\nDROP FUNCTION IF EXISTS _timescaledb_internal.cagg_migrate_pre_validation (TEXT, TEXT, TEXT);\nDROP FUNCTION IF EXISTS _timescaledb_functions.cagg_migrate_pre_validation (TEXT, TEXT, TEXT);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_create_plan (_timescaledb_catalog.continuous_agg, TEXT, BOOLEAN, BOOLEAN);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_create_plan (_timescaledb_catalog.continuous_agg, TEXT, BOOLEAN, BOOLEAN);\n\nDROP FUNCTION IF EXISTS _timescaledb_internal.cagg_migrate_plan_exists (INTEGER);\nDROP FUNCTION IF EXISTS _timescaledb_functions.cagg_migrate_plan_exists (INTEGER);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_plan (_timescaledb_catalog.continuous_agg);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_plan (_timescaledb_catalog.continuous_agg);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_create_new_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_create_new_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_disable_policies (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_disable_policies (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_enable_policies (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_enable_policies (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_copy_policies (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_copy_policies (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_refresh_new_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_refresh_new_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_update_watermark(integer);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_copy_data (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_copy_data (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_override_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_override_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_drop_old_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_execute_drop_old_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\n\n-- (2) Rebuild catalog table\nDROP VIEW IF EXISTS timescaledb_experimental.policies;\nDROP VIEW IF EXISTS timescaledb_information.hypertables;\nDROP VIEW IF EXISTS timescaledb_information.continuous_aggregates;\nDROP VIEW IF EXISTS timescaledb_information.jobs;\n\nALTER TABLE _timescaledb_catalog.continuous_aggs_materialization_ranges\n    DROP CONSTRAINT continuous_aggs_materialization_ranges_materialization_id_fkey;\n\nALTER TABLE _timescaledb_catalog.continuous_aggs_materialization_invalidation_log\n    DROP CONSTRAINT continuous_aggs_materialization_invalid_materialization_id_fkey;\n\nALTER TABLE _timescaledb_catalog.continuous_aggs_watermark\n    DROP CONSTRAINT continuous_aggs_watermark_mat_hypertable_id_fkey;\n\nALTER EXTENSION timescaledb\n    DROP TABLE _timescaledb_catalog.continuous_agg;\n\nCREATE TABLE _timescaledb_catalog._tmp_continuous_agg AS\n    SELECT\n        mat_hypertable_id,\n        raw_hypertable_id,\n        parent_mat_hypertable_id,\n        user_view_schema,\n        user_view_name,\n        partial_view_schema,\n        partial_view_name,\n        direct_view_schema,\n        direct_view_name,\n        materialized_only\n    FROM\n        _timescaledb_catalog.continuous_agg\n    ORDER BY\n        mat_hypertable_id;\n\nDROP TABLE _timescaledb_catalog.continuous_agg;\n\nCREATE TABLE _timescaledb_catalog.continuous_agg (\n    mat_hypertable_id integer NOT NULL,\n    raw_hypertable_id integer NOT NULL,\n    parent_mat_hypertable_id integer,\n    user_view_schema name NOT NULL,\n    user_view_name name NOT NULL,\n    partial_view_schema name NOT NULL,\n    partial_view_name name NOT NULL,\n    direct_view_schema name NOT NULL,\n    direct_view_name name NOT NULL,\n    materialized_only bool NOT NULL DEFAULT FALSE,\n    -- table constraints\n    CONSTRAINT continuous_agg_pkey PRIMARY KEY (mat_hypertable_id),\n    CONSTRAINT continuous_agg_partial_view_schema_partial_view_name_key UNIQUE (partial_view_schema, partial_view_name),\n    CONSTRAINT continuous_agg_user_view_schema_user_view_name_key UNIQUE (user_view_schema, user_view_name),\n    CONSTRAINT continuous_agg_mat_hypertable_id_fkey\n        FOREIGN KEY (mat_hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE,\n    CONSTRAINT continuous_agg_raw_hypertable_id_fkey\n        FOREIGN KEY (raw_hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE,\n    CONSTRAINT continuous_agg_parent_mat_hypertable_id_fkey\n        FOREIGN KEY (parent_mat_hypertable_id)\n        REFERENCES _timescaledb_catalog.continuous_agg (mat_hypertable_id) ON DELETE CASCADE\n);\n\nINSERT INTO _timescaledb_catalog.continuous_agg\nSELECT * FROM _timescaledb_catalog._tmp_continuous_agg;\nDROP TABLE _timescaledb_catalog._tmp_continuous_agg;\n\nCREATE INDEX continuous_agg_raw_hypertable_id_idx ON _timescaledb_catalog.continuous_agg (raw_hypertable_id);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.continuous_agg', '');\n\nGRANT SELECT ON TABLE _timescaledb_catalog.continuous_agg TO PUBLIC;\n\n-- clean up orphaned entries in related tables\nDELETE FROM _timescaledb_catalog.continuous_aggs_materialization_ranges range WHERE NOT EXISTS (\n    SELECT FROM _timescaledb_catalog.continuous_agg ca WHERE ca.mat_hypertable_id = range.materialization_id\n);\nDELETE FROM _timescaledb_catalog.continuous_aggs_materialization_invalidation_log inval WHERE NOT EXISTS (\n    SELECT FROM _timescaledb_catalog.continuous_agg ca WHERE ca.mat_hypertable_id = inval.materialization_id\n);\nDELETE FROM _timescaledb_catalog.continuous_aggs_watermark wm WHERE NOT EXISTS (\n    SELECT FROM _timescaledb_catalog.continuous_agg ca WHERE ca.mat_hypertable_id = wm.mat_hypertable_id\n);\n\nALTER TABLE _timescaledb_catalog.continuous_aggs_materialization_ranges\n    ADD CONSTRAINT continuous_aggs_materialization_ranges_materialization_id_fkey\n        FOREIGN KEY (materialization_id)\n        REFERENCES _timescaledb_catalog.continuous_agg(mat_hypertable_id) ON DELETE CASCADE;\n\nALTER TABLE _timescaledb_catalog.continuous_aggs_materialization_invalidation_log\n    ADD CONSTRAINT continuous_aggs_materialization_invalid_materialization_id_fkey\n        FOREIGN KEY (materialization_id)\n        REFERENCES _timescaledb_catalog.continuous_agg(mat_hypertable_id) ON DELETE CASCADE;\n\nALTER TABLE _timescaledb_catalog.continuous_aggs_watermark\n    ADD CONSTRAINT continuous_aggs_watermark_mat_hypertable_id_fkey\n        FOREIGN KEY (mat_hypertable_id)\n        REFERENCES _timescaledb_catalog.continuous_agg (mat_hypertable_id) ON DELETE CASCADE;\n\nANALYZE _timescaledb_catalog.continuous_agg;\n\n--\n-- END Rebuild the catalog table `_timescaledb_catalog.continuous_agg`\n--\n\nDROP FUNCTION IF EXISTS _timescaledb_debug.extension_state();\nDROP SCHEMA IF EXISTS _timescaledb_debug;\n\nALTER TABLE _timescaledb_config.bgw_job SET SCHEMA _timescaledb_catalog;\n\n-- Remove legacy partialize/finalize aggregate functions. It should be\n-- conditional because on 2.12.0 we moved from internal to functions schema\nDO\n$$\nDECLARE\n    foid regprocedure;\n    fkind text;\n    fargs text;\n    funcs text[] = '{finalize_agg, finalize_agg_sfunc, finalize_agg_ffunc, partialize_agg}';\nBEGIN\n    FOR foid, fkind, fargs IN\n        SELECT\n            p.oid,\n            CASE\n                WHEN p.prokind = 'f' THEN 'FUNCTION'\n                WHEN p.prokind = 'a' THEN 'AGGREGATE'\n                ELSE 'PROCEDURE'\n            END,\n            pg_catalog.pg_get_function_arguments(p.oid)\n        FROM\n            pg_catalog.pg_proc AS p\n        WHERE\n            p.proname = ANY(funcs)\n            AND p.pronamespace IN ('_timescaledb_internal'::regnamespace, '_timescaledb_functions'::regnamespace)\n        ORDER BY\n            p.proname\n    LOOP\n        EXECUTE format('ALTER EXTENSION timescaledb DROP %s %s (%s);', fkind, foid::regproc, fargs);\n        EXECUTE format('DROP %s %s (%s);', fkind, foid::regproc, fargs);\n    END LOOP;\nEND;\n$$\nLANGUAGE plpgsql;\n\nDROP FUNCTION IF EXISTS _timescaledb_functions.cagg_parse_invalidation_record(bytea);\nDROP FUNCTION IF EXISTS _timescaledb_functions.get_hypertable_id(regclass, regtype);\nDROP FUNCTION IF EXISTS _timescaledb_functions.get_hypertable_invalidations(regclass,timestamp without time zone,interval[]);\nDROP FUNCTION IF EXISTS _timescaledb_functions.get_hypertable_invalidations(regclass,timestamp with time zone,interval[]);\nDROP FUNCTION IF EXISTS _timescaledb_functions.get_materialization_info(regclass);\nDROP FUNCTION IF EXISTS _timescaledb_functions.get_materialization_invalidations(regclass,tsrange);\nDROP FUNCTION IF EXISTS _timescaledb_functions.get_materialization_invalidations(regclass,tstzrange);\nDROP FUNCTION IF EXISTS _timescaledb_functions.get_raw_materialization_ranges(regtype);\nDROP FUNCTION IF EXISTS _timescaledb_functions.invalidation_plugin_name();\nDROP PROCEDURE IF EXISTS _timescaledb_functions.accept_hypertable_invalidations(regclass,text);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.add_materialization_invalidations(regclass,tsrange);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.add_materialization_invalidations(regclass,tstzrange);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.process_hypertable_invalidations(name);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.process_hypertable_invalidations(regclass);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.process_hypertable_invalidations(regclass[]);\n\nDROP PROCEDURE IF EXISTS _timescaledb_functions.cagg_migrate_to_time_bucket(regclass);\n\nDROP FUNCTION timescaledb_experimental.time_bucket_ng(bucket_width INTERVAL, ts DATE);\n\nDROP FUNCTION timescaledb_experimental.time_bucket_ng(bucket_width INTERVAL, ts DATE, origin DATE);\n\nDROP FUNCTION timescaledb_experimental.time_bucket_ng(bucket_width INTERVAL, ts TIMESTAMP);\n\nDROP FUNCTION timescaledb_experimental.time_bucket_ng(bucket_width INTERVAL, ts TIMESTAMP, origin TIMESTAMP);\n\nDROP FUNCTION timescaledb_experimental.time_bucket_ng(bucket_width INTERVAL, ts TIMESTAMPTZ, timezone TEXT);\n\nDROP FUNCTION timescaledb_experimental.time_bucket_ng(bucket_width INTERVAL, ts TIMESTAMPTZ, origin TIMESTAMPTZ, timezone TEXT);\n\nDROP FUNCTION timescaledb_experimental.time_bucket_ng(bucket_width INTERVAL, ts TIMESTAMPTZ);\n\nDROP FUNCTION timescaledb_experimental.time_bucket_ng(bucket_width INTERVAL, ts TIMESTAMPTZ, origin TIMESTAMPTZ);\n"
  },
  {
    "path": "sql/updates/2.25.0--2.24.0.sql",
    "content": "DROP VIEW IF EXISTS timescaledb_information.dimensions;\n--\n-- Rebuild the catalog table `_timescaledb_catalog.continuous_agg` to add `finalized` column\n--\n\nDROP VIEW IF EXISTS timescaledb_experimental.policies;\nDROP VIEW IF EXISTS timescaledb_information.hypertables;\nDROP VIEW IF EXISTS timescaledb_information.continuous_aggregates;\nDROP VIEW IF EXISTS timescaledb_information.jobs;\n\nALTER TABLE _timescaledb_catalog.continuous_aggs_materialization_ranges\n    DROP CONSTRAINT continuous_aggs_materialization_ranges_materialization_id_fkey;\n\nALTER TABLE _timescaledb_catalog.continuous_aggs_materialization_invalidation_log\n    DROP CONSTRAINT continuous_aggs_materialization_invalid_materialization_id_fkey;\n\nALTER TABLE _timescaledb_catalog.continuous_aggs_watermark\n    DROP CONSTRAINT continuous_aggs_watermark_mat_hypertable_id_fkey;\n\nALTER EXTENSION timescaledb\n    DROP TABLE _timescaledb_catalog.continuous_agg;\n\nCREATE TABLE _timescaledb_catalog._tmp_continuous_agg AS\n    SELECT\n        mat_hypertable_id,\n        raw_hypertable_id,\n        parent_mat_hypertable_id,\n        user_view_schema,\n        user_view_name,\n        partial_view_schema,\n        partial_view_name,\n        direct_view_schema,\n        direct_view_name,\n        materialized_only,\n        true AS finalized\n    FROM\n        _timescaledb_catalog.continuous_agg\n    ORDER BY\n        mat_hypertable_id;\n\nDROP TABLE _timescaledb_catalog.continuous_agg;\n\nCREATE TABLE _timescaledb_catalog.continuous_agg (\n    mat_hypertable_id integer NOT NULL,\n    raw_hypertable_id integer NOT NULL,\n    parent_mat_hypertable_id integer,\n    user_view_schema name NOT NULL,\n    user_view_name name NOT NULL,\n    partial_view_schema name NOT NULL,\n    partial_view_name name NOT NULL,\n    direct_view_schema name NOT NULL,\n    direct_view_name name NOT NULL,\n    materialized_only bool NOT NULL DEFAULT FALSE,\n    finalized bool NOT NULL DEFAULT TRUE,\n    -- table constraints\n    CONSTRAINT continuous_agg_pkey PRIMARY KEY (mat_hypertable_id),\n    CONSTRAINT continuous_agg_partial_view_schema_partial_view_name_key UNIQUE (partial_view_schema, partial_view_name),\n    CONSTRAINT continuous_agg_user_view_schema_user_view_name_key UNIQUE (user_view_schema, user_view_name),\n    CONSTRAINT continuous_agg_mat_hypertable_id_fkey\n        FOREIGN KEY (mat_hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE,\n    CONSTRAINT continuous_agg_raw_hypertable_id_fkey\n        FOREIGN KEY (raw_hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE,\n    CONSTRAINT continuous_agg_parent_mat_hypertable_id_fkey\n        FOREIGN KEY (parent_mat_hypertable_id)\n        REFERENCES _timescaledb_catalog.continuous_agg (mat_hypertable_id) ON DELETE CASCADE\n);\n\nINSERT INTO _timescaledb_catalog.continuous_agg\nSELECT * FROM _timescaledb_catalog._tmp_continuous_agg;\nDROP TABLE _timescaledb_catalog._tmp_continuous_agg;\n\nCREATE INDEX continuous_agg_raw_hypertable_id_idx ON _timescaledb_catalog.continuous_agg (raw_hypertable_id);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.continuous_agg', '');\n\nGRANT SELECT ON TABLE _timescaledb_catalog.continuous_agg TO PUBLIC;\n\nALTER TABLE _timescaledb_catalog.continuous_aggs_materialization_ranges\n    ADD CONSTRAINT continuous_aggs_materialization_ranges_materialization_id_fkey\n        FOREIGN KEY (materialization_id)\n        REFERENCES _timescaledb_catalog.continuous_agg(mat_hypertable_id) ON DELETE CASCADE;\n\nALTER TABLE _timescaledb_catalog.continuous_aggs_materialization_invalidation_log\n    ADD CONSTRAINT continuous_aggs_materialization_invalid_materialization_id_fkey\n        FOREIGN KEY (materialization_id)\n        REFERENCES _timescaledb_catalog.continuous_agg(mat_hypertable_id) ON DELETE CASCADE;\n\nALTER TABLE _timescaledb_catalog.continuous_aggs_watermark\n    ADD CONSTRAINT continuous_aggs_watermark_mat_hypertable_id_fkey\n        FOREIGN KEY (mat_hypertable_id)\n        REFERENCES _timescaledb_catalog.continuous_agg (mat_hypertable_id) ON DELETE CASCADE;\n\nANALYZE _timescaledb_catalog.continuous_agg;\n\n--\n-- END Rebuild the catalog table `_timescaledb_catalog.continuous_agg`\n--\n\nDROP FUNCTION IF EXISTS _timescaledb_functions.extension_state();\nCREATE SCHEMA _timescaledb_debug;\nGRANT USAGE ON SCHEMA _timescaledb_debug TO PUBLIC;\n\nDROP VIEW IF EXISTS _timescaledb_config.bgw_job;\nALTER TABLE _timescaledb_catalog.bgw_job SET SCHEMA _timescaledb_config;\n\nDROP FUNCTION IF EXISTS _timescaledb_functions.estimate_compressed_batch_size(REGCLASS);\nDROP PROCEDURE IF EXISTS _timescaledb_functions.rebuild_columnstore(REGCLASS);\nDROP FUNCTION IF EXISTS _timescaledb_functions.cagg_get_grouping_columns;\n\nDROP FUNCTION IF EXISTS _timescaledb_functions.compressed_data_to_array(_timescaledb_internal.compressed_data, ANYELEMENT);\nDROP FUNCTION IF EXISTS _timescaledb_functions.compressed_data_column_size(_timescaledb_internal.compressed_data, ANYELEMENT);\n\nDROP FUNCTION IF EXISTS _timescaledb_functions.estimate_uncompressed_size;\n\n"
  },
  {
    "path": "sql/updates/2.25.0--2.25.1.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.25.1--2.25.0.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.25.1--2.25.2.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.25.2--2.25.1.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.9.0--2.8.1.sql",
    "content": "-- gapfill with timezone support\nDROP FUNCTION @extschema@.time_bucket_gapfill(INTERVAL,TIMESTAMPTZ,TEXT,TIMESTAMPTZ,TIMESTAMPTZ);\n\nALTER TABLE _timescaledb_catalog.compression_chunk_size DROP CONSTRAINT compression_chunk_size_pkey;\nALTER TABLE _timescaledb_catalog.compression_chunk_size ADD CONSTRAINT compression_chunk_size_pkey PRIMARY KEY(chunk_id,compressed_chunk_id);\n\nDROP FUNCTION _timescaledb_internal.policy_job_error_retention(integer, JSONB);\nDROP FUNCTION _timescaledb_internal.policy_job_error_retention_check(JSONB);\nDELETE FROM _timescaledb_config.bgw_job WHERE id = 2;\n\nALTER EXTENSION timescaledb DROP VIEW timescaledb_information.job_errors;\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_internal.job_errors;\n\nDROP VIEW timescaledb_information.job_errors;\nDROP TABLE _timescaledb_internal.job_errors;\n\n-- drop dependent views\nDROP VIEW IF EXISTS timescaledb_information.job_stats;\nDROP VIEW IF EXISTS timescaledb_information.jobs;\n\nCREATE TABLE _timescaledb_internal._tmp_bgw_job_stat AS SELECT * FROM _timescaledb_internal.bgw_job_stat;\nDROP TABLE _timescaledb_internal.bgw_job_stat;\n\nCREATE TABLE _timescaledb_internal.bgw_job_stat (\n  job_id integer NOT NULL,\n  last_start timestamptz NOT NULL DEFAULT NOW(),\n  last_finish timestamptz NOT NULL,\n  next_start timestamptz NOT NULL,\n  last_successful_finish timestamptz NOT NULL,\n  last_run_success bool NOT NULL,\n  total_runs bigint NOT NULL,\n  total_duration interval NOT NULL,\n  total_successes bigint NOT NULL,\n  total_failures bigint NOT NULL,\n  total_crashes bigint NOT NULL,\n  consecutive_failures int NOT NULL,\n  consecutive_crashes int NOT NULL,\n  -- table constraints\n  CONSTRAINT bgw_job_stat_pkey PRIMARY KEY (job_id),\n  CONSTRAINT bgw_job_stat_job_id_fkey FOREIGN KEY (job_id) REFERENCES _timescaledb_config.bgw_job (id) ON DELETE CASCADE\n);\n\nINSERT INTO _timescaledb_internal.bgw_job_stat SELECT\n  job_id, last_start, last_finish, next_start, last_successful_finish, last_run_success, total_runs, total_duration, total_successes, total_failures, total_crashes, consecutive_failures, consecutive_crashes\nFROM _timescaledb_internal._tmp_bgw_job_stat;\nDROP TABLE _timescaledb_internal._tmp_bgw_job_stat;\n\nGRANT SELECT ON TABLE _timescaledb_internal.bgw_job_stat TO PUBLIC;\n\nDROP VIEW _timescaledb_internal.hypertable_chunk_local_size;\nDROP FUNCTION _timescaledb_internal.hypertable_local_size(name, name);\n\nCREATE FUNCTION _timescaledb_internal.hypertable_local_size(\n\tschema_name_in name,\n\ttable_name_in name)\nRETURNS TABLE (\n\ttable_bytes BIGINT,\n\tindex_bytes BIGINT,\n\ttoast_bytes BIGINT,\n\ttotal_bytes BIGINT)\nLANGUAGE SQL VOLATILE STRICT AS\n$BODY$\n    /* get the main hypertable id and sizes */\n    WITH _hypertable AS (\n        SELECT\n            id,\n            _timescaledb_internal.relation_size(format('%I.%I', schema_name, table_name)::regclass) AS relsize\n        FROM\n            _timescaledb_catalog.hypertable\n        WHERE\n            schema_name = schema_name_in\n            AND table_name = table_name_in\n    ),\n    /* project the size of the parent hypertable */\n    _hypertable_sizes AS (\n        SELECT\n            id,\n            COALESCE((relsize).total_size, 0) AS total_bytes,\n            COALESCE((relsize).heap_size, 0) AS heap_bytes,\n            COALESCE((relsize).index_size, 0) AS index_bytes,\n            COALESCE((relsize).toast_size, 0) AS toast_bytes,\n            0::BIGINT AS compressed_total_size,\n            0::BIGINT AS compressed_index_size,\n            0::BIGINT AS compressed_toast_size,\n            0::BIGINT AS compressed_heap_size\n        FROM\n            _hypertable\n    ),\n    /* calculate the size of the hypertable chunks */\n    _chunk_sizes AS (\n        SELECT\n            chunk_id,\n            COALESCE(ch.total_bytes, 0) AS total_bytes,\n            COALESCE(ch.heap_bytes, 0) AS heap_bytes,\n            COALESCE(ch.index_bytes, 0) AS index_bytes,\n            COALESCE(ch.toast_bytes, 0) AS toast_bytes,\n            COALESCE(ch.compressed_total_size, 0) AS compressed_total_size,\n            COALESCE(ch.compressed_index_size, 0) AS compressed_index_size,\n            COALESCE(ch.compressed_toast_size, 0) AS compressed_toast_size,\n            COALESCE(ch.compressed_heap_size, 0) AS compressed_heap_size\n        FROM\n            _timescaledb_internal.hypertable_chunk_local_size ch\n            JOIN _hypertable_sizes ht ON ht.id = ch.hypertable_id\n    )\n    /* calculate the SUM of the hypertable and chunk sizes */\n\tSELECT\n\t\t(SUM(heap_bytes)  + SUM(compressed_heap_size))::BIGINT AS heap_bytes,\n\t\t(SUM(index_bytes) + SUM(compressed_index_size))::BIGINT AS index_bytes,\n\t\t(SUM(toast_bytes) + SUM(compressed_toast_size))::BIGINT AS toast_bytes,\n\t\t(SUM(total_bytes) + SUM(compressed_total_size))::BIGINT AS total_bytes\n\tFROM\n\t\t(SELECT * FROM _hypertable_sizes\n         UNION ALL\n         SELECT * FROM _chunk_sizes) AS sizes;\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\n\nDROP VIEW IF EXISTS timescaledb_information.job_stats;\nDROP VIEW IF EXISTS timescaledb_information.jobs;\nDROP VIEW IF EXISTS timescaledb_experimental.policies;\n-- fixed schedule\nDROP FUNCTION IF EXISTS  @extschema@.add_retention_policy(REGCLASS, \"any\", BOOL, INTERVAL, TIMESTAMPTZ, BOOL);\n\nDROP FUNCTION IF EXISTS  @extschema@.add_compression_policy(REGCLASS, \"any\", BOOL, INTERVAL);\n\n-- fixed schedule changes\n-- drop and recreate functions with modified signatures, modified views, modified tables\nDROP FUNCTION IF EXISTS @extschema@.add_job(REGPROC, INTERVAL, JSONB, TIMESTAMPTZ, BOOL, REGPROC, BOOL, TEXT);\nDROP FUNCTION IF EXISTS @extschema@.add_continuous_aggregate_policy(REGCLASS, \"any\", \"any\", INTERVAL, BOOL, TIMESTAMPTZ, TEXT);\nDROP FUNCTION IF EXISTS @extschema@.add_compression_policy(REGCLASS, \"any\", BOOL, INTERVAL, TIMESTAMPTZ, TEXT);\nDROP FUNCTION IF EXISTS @extschema@.add_retention_policy(REGCLASS, \"any\", BOOL, INTERVAL, TIMESTAMPTZ, TEXT);\nDROP FUNCTION IF EXISTS @extschema@.add_reorder_policy(REGCLASS, NAME, BOOL, TIMESTAMPTZ, TEXT);\n-- recreate functions with the previous signature\nCREATE FUNCTION @extschema@.add_job(\n  proc REGPROC,\n  schedule_interval INTERVAL,\n  config JSONB DEFAULT NULL,\n  initial_start TIMESTAMPTZ DEFAULT NULL,\n  scheduled BOOL DEFAULT true,\n  check_config REGPROC DEFAULT NULL\n) RETURNS INTEGER AS '@MODULE_PATHNAME@', 'ts_job_add' LANGUAGE C VOLATILE;\n\nCREATE FUNCTION @extschema@.add_compression_policy(hypertable REGCLASS, compress_after \"any\", if_not_exists BOOL = false, schedule_interval INTERVAL = NULL)\nRETURNS INTEGER AS '@MODULE_PATHNAME@', 'ts_policy_compression_add' LANGUAGE C VOLATILE STRICT;\n\nCREATE FUNCTION @extschema@.add_retention_policy(\n       relation REGCLASS,\n       drop_after \"any\",\n       if_not_exists BOOL = false,\n       schedule_interval INTERVAL = NULL\n)\nRETURNS INTEGER AS '@MODULE_PATHNAME@', 'ts_policy_retention_add'\nLANGUAGE C VOLATILE STRICT;\n\nCREATE FUNCTION @extschema@.add_continuous_aggregate_policy(continuous_aggregate REGCLASS, start_offset \"any\", end_offset \"any\", schedule_interval INTERVAL, if_not_exists BOOL = false)\nRETURNS INTEGER\nAS '@MODULE_PATHNAME@', 'ts_policy_refresh_cagg_add'\nLANGUAGE C VOLATILE;\n\nCREATE FUNCTION @extschema@.add_reorder_policy(\n    hypertable REGCLASS,\n    index_name NAME,\n    if_not_exists BOOL = false\n) RETURNS INTEGER\nAS '@MODULE_PATHNAME@', 'ts_policy_reorder_add'\nLANGUAGE C VOLATILE STRICT;\n\nDROP VIEW IF EXISTS timescaledb_information.jobs;\nDROP VIEW IF EXISTS timescaledb_information.job_stats;\n\n-- now need to rebuild the table\nALTER TABLE _timescaledb_internal.bgw_job_stat\n      DROP CONSTRAINT bgw_job_stat_job_id_fkey;\nALTER TABLE _timescaledb_internal.bgw_policy_chunk_stats\n      DROP CONSTRAINT bgw_policy_chunk_stats_chunk_id_fkey,\n      DROP CONSTRAINT bgw_policy_chunk_stats_job_id_fkey;\n\nCREATE TABLE _timescaledb_config.bgw_job_tmp AS SELECT * FROM _timescaledb_config.bgw_job;\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_config.bgw_job;\nALTER EXTENSION timescaledb DROP SEQUENCE _timescaledb_config.bgw_job_id_seq;\n\nCREATE TABLE _timescaledb_internal.tmp_bgw_job_seq_value AS SELECT last_value, is_called FROM _timescaledb_config.bgw_job_id_seq;\nDROP TABLE _timescaledb_config.bgw_job;\n\nCREATE SEQUENCE _timescaledb_config.bgw_job_id_seq MINVALUE 1000;\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_config.bgw_job_id_seq', '');\nSELECT setval('_timescaledb_config.bgw_job_id_seq', last_value, is_called) FROM _timescaledb_internal.tmp_bgw_job_seq_value;\nDROP TABLE _timescaledb_internal.tmp_bgw_job_seq_value;\n\nCREATE TABLE _timescaledb_config.bgw_job (\n  id integer PRIMARY KEY DEFAULT nextval('_timescaledb_config.bgw_job_id_seq'),\n  application_name name NOT NULL,\n  schedule_interval interval NOT NULL,\n  max_runtime interval NOT NULL,\n  max_retries integer NOT NULL,\n  retry_period interval NOT NULL,\n  proc_schema name NOT NULL,\n  proc_name name NOT NULL,\n  owner name NOT NULL DEFAULT CURRENT_ROLE,\n  scheduled bool NOT NULL DEFAULT TRUE,\n  hypertable_id integer REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE,\n  config jsonb,\n  check_schema NAME,\n  check_name NAME\n);\n\nINSERT INTO _timescaledb_config.bgw_job(id, application_name, schedule_interval, max_runtime, max_retries, retry_period, proc_schema, proc_name, owner, scheduled, hypertable_id, config)\nSELECT id, application_name, schedule_interval, max_runtime, max_retries, retry_period, proc_schema, proc_name, owner, scheduled, hypertable_id, config FROM _timescaledb_config.bgw_job_tmp ORDER BY id;\n\nALTER SEQUENCE _timescaledb_config.bgw_job_id_seq OWNED BY _timescaledb_config.bgw_job.id;\nCREATE INDEX bgw_job_proc_hypertable_id_idx ON _timescaledb_config.bgw_job(proc_schema,proc_name,hypertable_id);\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_config.bgw_job', 'WHERE id >= 1000');\nGRANT SELECT ON _timescaledb_config.bgw_job TO PUBLIC;\nGRANT SELECT ON _timescaledb_config.bgw_job_id_seq TO PUBLIC;\n\nDROP TABLE _timescaledb_config.bgw_job_tmp;\nALTER TABLE _timescaledb_internal.bgw_job_stat ADD CONSTRAINT bgw_job_stat_job_id_fkey FOREIGN KEY(job_id) REFERENCES _timescaledb_config.bgw_job(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_internal.bgw_policy_chunk_stats\n      ADD CONSTRAINT bgw_policy_chunk_stats_chunk_id_fkey\n          FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk(id)\n          ON DELETE CASCADE,\n      ADD CONSTRAINT bgw_policy_chunk_stats_job_id_fkey\n          FOREIGN KEY(job_id) REFERENCES _timescaledb_config.bgw_job(id)\n          ON DELETE CASCADE;\n\nDROP FUNCTION _timescaledb_internal.health;\n\n-- Recreate _timescaledb_catalog.dimension table without the compress_interval_length column --\nCREATE TABLE _timescaledb_internal.dimension_tmp\nAS SELECT * from _timescaledb_catalog.dimension;\n\nCREATE TABLE _timescaledb_internal.tmp_dimension_seq_value AS\nSELECT last_value, is_called FROM _timescaledb_catalog.dimension_id_seq;\n\n--drop foreign keys on dimension table\nALTER TABLE _timescaledb_catalog.dimension_partition DROP CONSTRAINT\ndimension_partition_dimension_id_fkey;\nALTER TABLE _timescaledb_catalog.dimension_slice DROP CONSTRAINT\ndimension_slice_dimension_id_fkey;\n\n--drop dependent views\nDROP VIEW IF EXISTS timescaledb_information.chunks;\nDROP VIEW IF EXISTS timescaledb_information.dimensions;\n\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.dimension;\nALTER EXTENSION timescaledb DROP SEQUENCE _timescaledb_catalog.dimension_id_seq;\nDROP TABLE _timescaledb_catalog.dimension;\n\nCREATE TABLE _timescaledb_catalog.dimension (\n  id serial NOT NULL ,\n  hypertable_id integer NOT NULL,\n  column_name name NOT NULL,\n  column_type REGTYPE NOT NULL,\n  aligned boolean NOT NULL,\n  -- closed dimensions\n  num_slices smallint NULL,\n  partitioning_func_schema name NULL,\n  partitioning_func name NULL,\n  -- open dimensions (e.g., time)\n  interval_length bigint NULL,\n  integer_now_func_schema name NULL,\n  integer_now_func name NULL,\n  -- table constraints\n  CONSTRAINT dimension_pkey PRIMARY KEY (id),\n  CONSTRAINT dimension_hypertable_id_column_name_key UNIQUE (hypertable_id, column_name),\n  CONSTRAINT dimension_check CHECK ((partitioning_func_schema IS NULL AND partitioning_func IS NULL) OR (partitioning_func_schema IS NOT NULL AND partitioning_func IS NOT NULL)),\n  CONSTRAINT dimension_check1 CHECK ((num_slices IS NULL AND interval_length IS NOT NULL) OR (num_slices IS NOT NULL AND interval_length IS NULL)),\n  CONSTRAINT dimension_check2 CHECK ((integer_now_func_schema IS NULL AND integer_now_func IS NULL) OR (integer_now_func_schema IS NOT NULL AND integer_now_func IS NOT NULL)),\n  CONSTRAINT dimension_interval_length_check CHECK (interval_length IS NULL OR interval_length > 0),\n  CONSTRAINT dimension_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE\n);\n\nINSERT INTO _timescaledb_catalog.dimension\n( id, hypertable_id, column_name, column_type,\n  aligned, num_slices, partitioning_func_schema,\n  partitioning_func, interval_length,\n  integer_now_func_schema, integer_now_func)\nSELECT id, hypertable_id, column_name, column_type,\n  aligned, num_slices, partitioning_func_schema,\n  partitioning_func, interval_length,\n  integer_now_func_schema, integer_now_func\nFROM _timescaledb_internal.dimension_tmp;\n\nALTER SEQUENCE _timescaledb_catalog.dimension_id_seq OWNED BY _timescaledb_catalog.dimension.id;\nSELECT setval('_timescaledb_catalog.dimension_id_seq', last_value, is_called) FROM _timescaledb_internal.tmp_dimension_seq_value;\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.dimension', '');\nSELECT pg_catalog.pg_extension_config_dump(pg_get_serial_sequence('_timescaledb_catalog.dimension', 'id'), '');\n\n--add the foreign key constraints\nALTER TABLE _timescaledb_catalog.dimension_partition ADD CONSTRAINT\ndimension_partition_dimension_id_fkey FOREIGN KEY (dimension_id)\nREFERENCES _timescaledb_catalog.dimension(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.dimension_slice ADD CONSTRAINT\ndimension_slice_dimension_id_fkey FOREIGN KEY (dimension_id)\nREFERENCES _timescaledb_catalog.dimension(id) ON DELETE CASCADE;\n\n--cleanup\nDROP TABLE _timescaledb_internal.dimension_tmp;\nDROP TABLE _timescaledb_internal.tmp_dimension_seq_value;\n\nGRANT SELECT ON _timescaledb_catalog.dimension_id_seq TO PUBLIC;\nGRANT SELECT ON _timescaledb_catalog.dimension TO PUBLIC;\n\n-- end recreate _timescaledb_catalog.dimension table --\n\n-- changes related to alter_data_node()\nDROP INDEX _timescaledb_catalog.chunk_data_node_node_name_idx;\nDROP FUNCTION @extschema@.alter_data_node;\n\n--\n-- Prevent downgrading if there are hierarchical continuous aggregates\n--\nDO\n$$\nDECLARE\n    caggs_hierarchical TEXT;\n    caggs_count INTEGER;\nBEGIN\n    SELECT\n        string_agg(format('%I.%I', user_view_schema, user_view_name), ', '),\n        count(*)\n    INTO\n        caggs_hierarchical,\n        caggs_count\n    FROM\n        _timescaledb_catalog.continuous_agg\n    WHERE\n        parent_mat_hypertable_id IS NOT NULL;\n\n    IF caggs_count > 0 THEN\n        RAISE EXCEPTION 'Downgrade is not possible because there are % hierarchical continuous aggregates: %', caggs_count, caggs_nested\n            USING HINT = 'Remove the corresponding continuous aggregates manually before downgrading';\n    END IF;\nEND;\n$$\nLANGUAGE 'plpgsql';\n\n--\n-- Rebuild the catalog table `_timescaledb_catalog.continuous_agg`\n--\nDROP VIEW IF EXISTS timescaledb_information.hypertables;\nDROP VIEW IF EXISTS timescaledb_information.continuous_aggregates;\nDROP PROCEDURE IF EXISTS @extschema@.cagg_migrate (REGCLASS, BOOLEAN, BOOLEAN);\nDROP FUNCTION IF EXISTS _timescaledb_internal.cagg_migrate_pre_validation (TEXT, TEXT, TEXT);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_create_plan (_timescaledb_catalog.continuous_agg, TEXT, BOOLEAN, BOOLEAN);\nDROP FUNCTION IF EXISTS _timescaledb_internal.cagg_migrate_plan_exists (INTEGER);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_plan (_timescaledb_catalog.continuous_agg);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_create_new_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_disable_policies (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_enable_policies (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_copy_policies (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_refresh_new_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_copy_data (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_override_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\nDROP PROCEDURE IF EXISTS _timescaledb_internal.cagg_migrate_execute_drop_old_cagg (_timescaledb_catalog.continuous_agg, _timescaledb_catalog.continuous_agg_migrate_plan_step);\n\nALTER EXTENSION timescaledb\n    DROP TABLE _timescaledb_catalog.continuous_agg;\n\nALTER TABLE _timescaledb_catalog.continuous_aggs_materialization_invalidation_log\n    DROP CONSTRAINT continuous_aggs_materialization_invalid_materialization_id_fkey;\n\nALTER TABLE _timescaledb_catalog.continuous_agg_migrate_plan\n    DROP CONSTRAINT continuous_agg_migrate_plan_mat_hypertable_id_fkey;\n\nCREATE TABLE _timescaledb_catalog._tmp_continuous_agg AS\n    SELECT\n        mat_hypertable_id,\n        raw_hypertable_id,\n        user_view_schema,\n        user_view_name,\n        partial_view_schema,\n        partial_view_name,\n        bucket_width,\n        direct_view_schema,\n        direct_view_name,\n        materialized_only,\n        finalized\n    FROM\n        _timescaledb_catalog.continuous_agg\n    ORDER BY\n        mat_hypertable_id;\n\nDROP TABLE _timescaledb_catalog.continuous_agg;\n\nCREATE TABLE _timescaledb_catalog.continuous_agg (\n    mat_hypertable_id integer NOT NULL,\n    raw_hypertable_id integer NOT NULL,\n    user_view_schema name NOT NULL,\n    user_view_name name NOT NULL,\n    partial_view_schema name NOT NULL,\n    partial_view_name name NOT NULL,\n    bucket_width bigint NOT NULL,\n    direct_view_schema name NOT NULL,\n    direct_view_name name NOT NULL,\n    materialized_only bool NOT NULL DEFAULT FALSE,\n    finalized bool NOT NULL DEFAULT TRUE,\n    -- table constraints\n    CONSTRAINT continuous_agg_pkey PRIMARY KEY (mat_hypertable_id),\n    CONSTRAINT continuous_agg_partial_view_schema_partial_view_name_key UNIQUE (partial_view_schema, partial_view_name),\n    CONSTRAINT continuous_agg_user_view_schema_user_view_name_key UNIQUE (user_view_schema, user_view_name),\n    CONSTRAINT continuous_agg_mat_hypertable_id_fkey\n        FOREIGN KEY (mat_hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE,\n    CONSTRAINT continuous_agg_raw_hypertable_id_fkey\n        FOREIGN KEY (raw_hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE\n);\n\nINSERT INTO _timescaledb_catalog.continuous_agg\nSELECT * FROM _timescaledb_catalog._tmp_continuous_agg;\nDROP TABLE _timescaledb_catalog._tmp_continuous_agg;\n\nCREATE INDEX continuous_agg_raw_hypertable_id_idx ON _timescaledb_catalog.continuous_agg (raw_hypertable_id);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.continuous_agg', '');\n\nGRANT SELECT ON TABLE _timescaledb_catalog.continuous_agg TO PUBLIC;\n\nALTER TABLE _timescaledb_catalog.continuous_aggs_materialization_invalidation_log\n    ADD CONSTRAINT continuous_aggs_materialization_invalid_materialization_id_fkey\n        FOREIGN KEY (materialization_id)\n        REFERENCES _timescaledb_catalog.continuous_agg(mat_hypertable_id) ON DELETE CASCADE;\n\nALTER TABLE _timescaledb_catalog.continuous_agg_migrate_plan\n    ADD CONSTRAINT continuous_agg_migrate_plan_mat_hypertable_id_fkey\n        FOREIGN KEY (mat_hypertable_id)\n        REFERENCES _timescaledb_catalog.continuous_agg (mat_hypertable_id);\n\nANALYZE _timescaledb_catalog.continuous_agg;\n\n-- changes related to drop_stale_chunks()\nDROP FUNCTION _timescaledb_internal.drop_stale_chunks;\n"
  },
  {
    "path": "sql/updates/2.9.0--2.9.1.sql",
    "content": "GRANT SELECT ON _timescaledb_internal.job_errors to PUBLIC;\n"
  },
  {
    "path": "sql/updates/2.9.1--2.9.0.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.9.1--2.9.2.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.9.2--2.9.1.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.9.2--2.9.3.sql",
    "content": ""
  },
  {
    "path": "sql/updates/2.9.3--2.10.0.sql",
    "content": "CREATE OR REPLACE VIEW timescaledb_information.job_errors\nWITH (security_barrier = true) AS\nSELECT\n    job_id,\n    error_data ->> 'proc_schema' as proc_schema,\n    error_data ->> 'proc_name' as proc_name,\n    pid,\n    start_time,\n    finish_time,\n    error_data ->> 'sqlerrcode' AS sqlerrcode,\n    CASE WHEN error_data ->>'message' IS NOT NULL THEN\n      CASE WHEN error_data ->>'detail' IS NOT NULL THEN\n        CASE WHEN error_data ->>'hint' IS NOT NULL THEN concat(error_data ->>'message', '. ', error_data ->>'detail', '. ', error_data->>'hint')\n        ELSE concat(error_data ->>'message', ' ', error_data ->>'detail')\n        END\n      ELSE\n        CASE WHEN error_data ->>'hint' IS NOT NULL THEN concat(error_data ->>'message', '. ', error_data->>'hint')\n        ELSE error_data ->>'message'\n        END\n      END\n    ELSE\n      'job crash detected, see server logs'\n    END\n    AS err_message\nFROM\n    _timescaledb_internal.job_errors\nLEFT JOIN\n    _timescaledb_config.bgw_job ON (bgw_job.id = job_errors.job_id)\nWHERE\n    pg_catalog.pg_has_role(current_user,\n\t\t\t   (SELECT pg_catalog.pg_get_userbyid(datdba)\n\t\t\t      FROM pg_catalog.pg_database\n\t\t\t     WHERE datname = current_database()),\n\t\t\t   'MEMBER') IS TRUE\n    OR pg_catalog.pg_has_role(current_user, owner, 'MEMBER') IS TRUE;\n\nREVOKE ALL ON _timescaledb_internal.job_errors FROM PUBLIC;\n"
  },
  {
    "path": "sql/updates/2.9.3--2.9.2.sql",
    "content": ""
  },
  {
    "path": "sql/updates/README.md",
    "content": "## General principles for statements in update/downgrade scripts\n\n1. The `search_path` for these scripts will be locked down to\n  `pg_catalog, pg_temp`. Locking down `search_path` happens in\n  `pre-update.sql`. Therefore all object references need to be fully\n  qualified unless they reference objects from `pg_catalog`.\n  Use `@extschema@` to refer to the target schema of the installation\n  (resolves to `public` by default).\n2. Creating objects must not use IF NOT EXISTS as this will\n  introduce privilege escalation vulnerabilities.\n3. All functions should have explicit `search_path`. Setting explicit\n  `search_path` will prevent SQL function inlining for functions and\n  transaction control for procedures so for some functions/procedures\n  it is acceptable to not have explicit `search_path`. Special care\n  needs to be taken with those functions/procedures by either setting\n  `search_path` in function body or having only fully qualified object\n  references including operators.\n4. When generating the install scripts `CREATE OR REPLACE` will be\n  changed to `CREATE` to prevent users from precreating extension\n  objects. Since we need `CREATE OR REPLACE` for update scripts and\n  we don't want to maintain two versions of the sql files containing\n  the function definitions we use `CREATE OR REPLACE` in those.\n5. Any object added in a new version needs to have an equivalent\n  `CREATE` statement in the update script without `OR REPLACE` to\n  prevent precreation of the object.\n6. The creation of new metadata tables need to be part of modfiles,\n   similar to `ALTER`s of such tables. Otherwise, later modfiles\n   cannot rely on those tables being present.\n\n## Extension updates\n\nThis directory contains \"modfiles\" (SQL scripts) with modifications\nthat are applied when updating from one version of the extension to\nanother.\n\nThe actual update scripts are compiled from modfiles by concatenating\nthem with the current source code (which should come at the end of the\nresulting update script). Update scripts can \"jump\" several versions\nby using multiple modfiles in order. There are two types of modfiles:\n\n* Transition modfiles named `<from>-<to>.sql`, where `from` and `to`\n  indicate the (adjacent) versions transitioning between. Transition\n  modfiles are concatenated to form the lineage from an origin version\n  to any later version.\n* Origin modfiles named <version>.sql, which are included only in\n  update scripts that origin at the particular version given in the\n  name. So, for instance, `0.7.0.sql` is only included in the script\n  moving from `0.7.0` to the current version, but not in, e.g., the\n  update script for `0.4.0` to the current version. These files\n  typically contain fixes for bugs that are specific to the origin\n  version, but are no longer present in the transition modfiles.\n\nNotes on post_update.sql\n   We use a special config var (timescaledb.update_script_stage )\nto notify that dependencies have been setup and now timescaledb\nspecific queries can be enabled. This is useful if we want to,\nfor example, modify objects that need timescaledb specific syntax as\npart of the extension update).\nThe scripts in post_update.sql are executed as part of the `ALTER\nEXTENSION` stmt.\n\nNote that modfiles that contain no changes need not exist as a\nfile. Transition modfiles must, however, be listed in the\n`CMakeLists.txt` file in the parent directory for an update script to\nbe built for that version.\n\n## Extension downgrades\n\nYou can enable the generation of a downgrade file by setting\n`GENERATE_DOWNGRADE_SCRIPT` to `ON`, for example:\n\n```\n./bootstrap -DGENERATE_DOWNGRADE_SCRIPT=ON\n```\n\nTo support downgrades to previous versions of the extension, it is\nnecessary to execute CMake from a Git repository since the generation\nof a downgrade script requires access to the previous version files\nthat are used to generate an update script. In addition, we only\ngenerate a downgrade script to the immediate preceeding version and\nnot to any other preceeding versions.\n\nThe source and target versions are found in be found in the file\n`version.config` file in the root of the source tree, where `version`\nis the source version and `previous_version` is the target\nversion. Note that we have a separate field for the downgrade.\n\nA downgrade file consists of:\n- A prolog that is retrieved from the target version.\n- A version-specific piece of code that exists on the source version.\n- An epilog that is retrieved from the target version.\n\nThe prolog consists of the files mentioned in the `PRE_UPDATE_FILES`\nvariable in the target version of `cmake/ScriptFiles.cmake`.\n\nThe version-specific code is found in the source version of the file\n`sql/updates/reverse-dev.sql`.\n\nThe epilog consists of the files in variables `SOURCE_FILES`,\n`SET_POST_UPDATE_STAGE`, `POST_UPDATE_FILES`, and `UNSET_UPDATE_STAGE`\nin that order.\n\nFor downgrades to work correctly, some rules need to be followed:\n\n1. If you add new objects in `sql/updates/latest-dev.sql`, you need to\n   remove them in the version-specific downgrade file. The\n   `sql/updates/pre-update.sql` in the target version do not know\n   about objects created in the source version, so they need to be\n   dropped explicitly.\n2. Since `sql/updates/pre-update.sql` can be executed on a later\n   version of the extension, it might be that some objects have been\n   removed and do not exist. Hence `DROP` calls need to use `IF NOT\n   EXISTS`.\n\nNote that, in contrast to update scripts, downgrade scripts are not\nbuilt by composing several downgrade scripts into a more extensive\ndowngrade script. The downgrade scripts are intended to be use only in\nspecial cases and are not intended to be use to move up and down\nbetween versions at will, which is why we only generate a downgrade\nscript to the immediately preceeding version.\n\n### When releasing a new version\n\nWhen releasing a new version, please rename the file `reverse-dev.sql`\nto `<version>--<previous_version>.sql` and add that name to\n`REV_FILES` variable in the `sql/CMakeLists.txt`. This will allow\ngeneration of downgrade scripts for any version in that list, but it\nis currently not added.\n"
  },
  {
    "path": "sql/updates/latest-dev.sql",
    "content": "\n--\n-- Rebuild the catalog table `_timescaledb_catalog.chunk` to drop column `dropped`\n--\n\nCREATE TABLE _timescaledb_internal.tmp_chunk AS SELECT * from _timescaledb_catalog.chunk WHERE NOT dropped;\nCREATE TABLE _timescaledb_internal.tmp_chunk_seq_value AS SELECT last_value, is_called FROM _timescaledb_catalog.chunk_id_seq;\n\n--drop foreign keys on chunk table\nALTER TABLE _timescaledb_catalog.chunk_constraint DROP CONSTRAINT chunk_constraint_chunk_id_fkey;\nALTER TABLE _timescaledb_catalog.chunk_column_stats DROP CONSTRAINT chunk_column_stats_chunk_id_fkey;\nALTER TABLE _timescaledb_internal.bgw_policy_chunk_stats DROP CONSTRAINT bgw_policy_chunk_stats_chunk_id_fkey;\nALTER TABLE _timescaledb_catalog.compression_chunk_size DROP CONSTRAINT compression_chunk_size_chunk_id_fkey;\nALTER TABLE _timescaledb_catalog.compression_chunk_size DROP CONSTRAINT compression_chunk_size_compressed_chunk_id_fkey;\n\n--drop dependent views\nDROP VIEW IF EXISTS timescaledb_information.hypertables;\nDROP VIEW IF EXISTS timescaledb_information.chunks;\nDROP VIEW IF EXISTS _timescaledb_internal.hypertable_chunk_local_size;\nDROP VIEW IF EXISTS _timescaledb_internal.compressed_chunk_stats;\nDROP VIEW IF EXISTS timescaledb_information.chunk_columnstore_settings;\nDROP VIEW IF EXISTS timescaledb_information.chunk_compression_settings;\n\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.chunk;\nALTER EXTENSION timescaledb DROP SEQUENCE _timescaledb_catalog.chunk_id_seq;\n\nDROP TABLE _timescaledb_catalog.chunk;\n\nCREATE SEQUENCE _timescaledb_catalog.chunk_id_seq MINVALUE 1;\n\n-- now create table without self referential foreign key\nCREATE TABLE _timescaledb_catalog.chunk (\n  id integer NOT NULL DEFAULT nextval('_timescaledb_catalog.chunk_id_seq'),\n  hypertable_id int NOT NULL,\n  schema_name name NOT NULL,\n  table_name name NOT NULL,\n  compressed_chunk_id integer ,\n  status integer NOT NULL DEFAULT 0,\n  osm_chunk boolean NOT NULL DEFAULT FALSE,\n  creation_time timestamptz NOT NULL,\n  -- table constraints\n  CONSTRAINT chunk_pkey PRIMARY KEY (id),\n  CONSTRAINT chunk_schema_name_table_name_key UNIQUE (schema_name, table_name)\n);\n\nINSERT INTO _timescaledb_catalog.chunk( id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk, creation_time)\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk, creation_time\nFROM _timescaledb_internal.tmp_chunk;\n\n--add indexes to the chunk table\nCREATE INDEX chunk_hypertable_id_idx ON _timescaledb_catalog.chunk (hypertable_id);\nCREATE INDEX chunk_compressed_chunk_id_idx ON _timescaledb_catalog.chunk (compressed_chunk_id);\nCREATE INDEX chunk_osm_chunk_idx ON _timescaledb_catalog.chunk (osm_chunk, hypertable_id);\nCREATE INDEX chunk_hypertable_id_creation_time_idx ON _timescaledb_catalog.chunk(hypertable_id, creation_time);\n\nALTER SEQUENCE _timescaledb_catalog.chunk_id_seq OWNED BY _timescaledb_catalog.chunk.id;\nSELECT setval('_timescaledb_catalog.chunk_id_seq', last_value, is_called) FROM _timescaledb_internal.tmp_chunk_seq_value;\n\n-- add self referential foreign key\nALTER TABLE _timescaledb_catalog.chunk ADD CONSTRAINT chunk_compressed_chunk_id_fkey FOREIGN KEY ( compressed_chunk_id ) REFERENCES _timescaledb_catalog.chunk( id );\n\n--add foreign key constraint\nALTER TABLE _timescaledb_catalog.chunk ADD CONSTRAINT chunk_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.chunk', '');\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.chunk_id_seq', '');\n\n--add the foreign key constraints\nALTER TABLE _timescaledb_catalog.chunk_constraint ADD CONSTRAINT chunk_constraint_chunk_id_fkey FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk(id);\nALTER TABLE _timescaledb_catalog.chunk_column_stats ADD CONSTRAINT chunk_column_stats_chunk_id_fkey FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk (id);\nALTER TABLE _timescaledb_internal.bgw_policy_chunk_stats ADD CONSTRAINT bgw_policy_chunk_stats_chunk_id_fkey FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.compression_chunk_size ADD CONSTRAINT compression_chunk_size_chunk_id_fkey FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.compression_chunk_size ADD CONSTRAINT compression_chunk_size_compressed_chunk_id_fkey FOREIGN KEY (compressed_chunk_id) REFERENCES _timescaledb_catalog.chunk(id) ON DELETE CASCADE;\n\n--cleanup\nDROP TABLE _timescaledb_internal.tmp_chunk;\nDROP TABLE _timescaledb_internal.tmp_chunk_seq_value;\n\nGRANT SELECT ON _timescaledb_catalog.chunk_id_seq TO PUBLIC;\nGRANT SELECT ON _timescaledb_catalog.chunk TO PUBLIC;\n-- end rebuild _timescaledb_catalog.chunk table --\n\n-- drop the catalog tables for continuous aggregate migration plans\n\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.continuous_agg_migrate_plan;\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.continuous_agg_migrate_plan_step;\nALTER EXTENSION timescaledb DROP SEQUENCE _timescaledb_catalog.continuous_agg_migrate_plan_step_step_id_seq;\nDROP TABLE _timescaledb_catalog.continuous_agg_migrate_plan_step;\nDROP TABLE _timescaledb_catalog.continuous_agg_migrate_plan;\n\n--\n-- Add this index to speed up queries for recent job history\n-- This statement is idempotent to allow the index to have been precreated.\n--\nCREATE INDEX IF NOT EXISTS bgw_job_stat_history_execution_start_idx\n    ON _timescaledb_internal.bgw_job_stat_history(execution_start);\nCREATE INDEX IF NOT EXISTS bgw_job_stat_history_job_id_execution_start_idx\n    ON _timescaledb_internal.bgw_job_stat_history(job_id, execution_start DESC);\n\nDROP INDEX IF EXISTS _timescaledb_internal.bgw_job_stat_history_job_id_idx;\n\n"
  },
  {
    "path": "sql/updates/post-update.sql",
    "content": "-- For objects that are newly created, we need to set the initprivs to\n-- the initprivs for some table that was created in the installation\n-- of the TimescaleDB extension and not as part of any update.\n--\n-- We chose the \"chunk\" catalog table for this since that is created\n-- in the first version of TimescaleDB and should have the correct\n-- initprivs, but we could use any other table that existed in the\n-- first installation.\nINSERT INTO _timescaledb_internal.saved_privs\n     SELECT nspname, relname, relacl,\n       (SELECT tmpini FROM _timescaledb_internal.saved_privs\n        WHERE tmpnsp = '_timescaledb_catalog' AND tmpname = 'chunk')\n       FROM pg_class JOIN pg_namespace ns ON ns.oid = relnamespace\n         LEFT JOIN _timescaledb_internal.saved_privs ON tmpnsp = nspname AND tmpname = relname\n      WHERE relkind IN ('r', 'v') AND nspname IN ('_timescaledb_catalog', '_timescaledb_config')\n        OR nspname = '_timescaledb_internal'\n        AND relname IN ('hypertable_chunk_local_size', 'compressed_chunk_stats',\n                        'bgw_job_stat', 'bgw_policy_chunk_stats', 'job_errors')\nON CONFLICT DO NOTHING;\n\n-- The above is good enough for tables and views. However sequences need to\n-- use the \"chunk_id_seq\" catalog sequence as a template\nINSERT INTO _timescaledb_internal.saved_privs\n     SELECT nspname, relname, relacl,\n        (SELECT tmpini FROM _timescaledb_internal.saved_privs\n\t     WHERE tmpnsp = '_timescaledb_catalog' AND tmpname = 'chunk_id_seq')\n        FROM pg_class JOIN pg_namespace ns ON ns.oid = relnamespace\n\t\t    LEFT JOIN _timescaledb_internal.saved_privs ON tmpnsp = nspname AND tmpname = relname\n      WHERE relkind IN ('S') AND nspname IN ('_timescaledb_catalog', '_timescaledb_config')\n        OR nspname = '_timescaledb_internal'\n        AND relname IN ('hypertable_chunk_local_size', 'compressed_chunk_stats',\n                        'bgw_job_stat', 'bgw_policy_chunk_stats')\nON CONFLICT DO NOTHING;\n\n-- We can now copy back saved initprivs.\nWITH to_update AS (\n     SELECT objoid, tmpini\n     FROM pg_class cl JOIN pg_namespace ns ON ns.oid = relnamespace\n        JOIN pg_init_privs ip ON ip.objoid = cl.oid AND ip.objsubid = 0\n        JOIN _timescaledb_internal.saved_privs ON tmpnsp = nspname AND tmpname = relname)\nUPDATE pg_init_privs\n   SET initprivs = tmpini\n  FROM to_update\n WHERE to_update.objoid = pg_init_privs.objoid\n   AND classoid = 'pg_class'::regclass\n   AND objsubid = 0;\n\n-- Can only restore permissions on views after they have been rebuilt,\n-- so we restore for all types of objects here.\nWITH to_update AS (\n     SELECT cl.oid, tmpacl\n     FROM pg_class cl JOIN pg_namespace ns ON ns.oid = relnamespace\n                      JOIN _timescaledb_internal.saved_privs ON tmpnsp = nspname AND tmpname = relname)\nUPDATE pg_class cl SET relacl = tmpacl\n  FROM to_update WHERE cl.oid = to_update.oid;\n\nDROP TABLE _timescaledb_internal.saved_privs;\n\n-- Create watermark record when required\nDO\n$$\nDECLARE\n  ts_version TEXT;\nBEGIN\n    SELECT extversion INTO ts_version FROM pg_extension WHERE extname = 'timescaledb';\n    IF ts_version >= '2.11.0' THEN\n      INSERT INTO _timescaledb_catalog.continuous_aggs_watermark (mat_hypertable_id, watermark)\n      SELECT a.mat_hypertable_id, _timescaledb_functions.cagg_watermark_materialized(a.mat_hypertable_id)\n      FROM _timescaledb_catalog.continuous_agg a\n      LEFT JOIN _timescaledb_catalog.continuous_aggs_watermark b ON b.mat_hypertable_id = a.mat_hypertable_id\n      WHERE b.mat_hypertable_id IS NULL\n      ORDER BY 1;\n    END IF;\nEND;\n$$;\n\n-- Repair relations that have relacl entries for users that do not\n-- exist in pg_authid\nCALL _timescaledb_functions.repair_relation_acls();\n\n-- Cleanup orphaned compression settings\nWITH orphaned_settings AS (\n     SELECT cs.relid, cl.relname\n     FROM _timescaledb_catalog.compression_settings cs\n     LEFT JOIN pg_class cl ON (cs.relid = cl.oid)\n     WHERE cl.relname IS NULL\n)\nDELETE FROM _timescaledb_catalog.compression_settings AS cs\nUSING orphaned_settings AS os WHERE cs.relid = os.relid;\n"
  },
  {
    "path": "sql/updates/pre-update.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- This file is always prepended to all upgrade scripts.\n\n"
  },
  {
    "path": "sql/updates/pre-version-change.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- This file is always prepended to all upgrade and downgrade scripts.\n-- This file must avoid referencing extension objects directly as that\n-- would limit the things we can alter in extension update/downgrade\n-- itself.\nSET LOCAL search_path TO pg_catalog, pg_temp;\n\n-- Disable parallel execution for the duration of the update process.\n-- This avoids version mismatch errors that would have beeen triggered by the\n-- parallel workers in ts_extension_check_version().\nSET LOCAL max_parallel_workers = 0;\n\n-- Triggers should be disabled during upgrades to avoid having them\n-- invoke functions that might load an old version of the shared\n-- library before those functions have been updated.\nDROP EVENT TRIGGER IF EXISTS timescaledb_ddl_command_end;\nDROP EVENT TRIGGER IF EXISTS timescaledb_ddl_sql_drop;\n\n-- Since we want to call the new version of restart_background_workers we\n-- create a function that points to that version. The proper restart_background_workers\n-- may either be in _timescaledb_internal or in _timescaledb_functions\n-- depending on the version we are upgrading from and we can't make\n-- the move in this location as the new schema might not have been set up.\nDO $$\nBEGIN\n  IF EXISTS (SELECT FROM pg_namespace WHERE nspname='_timescaledb_functions') THEN\n    CREATE FUNCTION _timescaledb_functions._tmp_restart_background_workers() RETURNS BOOL\n    AS '@LOADER_PATHNAME@', 'ts_bgw_db_workers_restart' LANGUAGE C VOLATILE;\n    PERFORM _timescaledb_functions._tmp_restart_background_workers();\n    DROP FUNCTION _timescaledb_functions._tmp_restart_background_workers();\n  ELSE\n    -- timescaledb < 2.11 does not have _timescaledb_functions schema\n    CREATE FUNCTION _timescaledb_internal._tmp_restart_background_workers() RETURNS BOOL\n    AS '@LOADER_PATHNAME@', 'ts_bgw_db_workers_restart' LANGUAGE C VOLATILE;\n    PERFORM _timescaledb_internal._tmp_restart_background_workers();\n    DROP FUNCTION _timescaledb_internal._tmp_restart_background_workers();\n  END IF;\nEND\n$$;\n\n-- Table for ACL and initprivs of tables.\nCREATE TABLE _timescaledb_internal.saved_privs(\n       tmpnsp name,\n       tmpname name,\n       tmpacl aclitem[],\n       tmpini aclitem[],\n       UNIQUE (tmpnsp, tmpname));\n\n-- We save away both the ACL and the initprivs for all tables and\n-- views in the extension (but not for chunks and internal objects) so\n-- that we can restore them to the proper state after the update.\nINSERT INTO _timescaledb_internal.saved_privs\nSELECT nspname, relname, relacl, initprivs\n  FROM pg_class cl JOIN pg_namespace ns ON ns.oid = relnamespace\n                   JOIN pg_init_privs ip ON ip.objoid = cl.oid AND ip.objsubid = 0 AND ip.classoid = 'pg_class'::regclass\nWHERE\n  nspname IN ('_timescaledb_catalog', '_timescaledb_config')\n  OR (\n    relname IN ('hypertable_chunk_local_size', 'compressed_chunk_stats', 'bgw_job_stat', 'bgw_policy_chunk_stats')\n    AND nspname = '_timescaledb_internal'\n  )\n;\n\n"
  },
  {
    "path": "sql/updates/reverse-dev.sql",
    "content": "--\n-- Rebuild the catalog table `_timescaledb_catalog.chunk` to add column `dropped`\n--\n\nCREATE TABLE _timescaledb_internal.tmp_chunk AS SELECT * from _timescaledb_catalog.chunk;\nCREATE TABLE _timescaledb_internal.tmp_chunk_seq_value AS SELECT last_value, is_called FROM _timescaledb_catalog.chunk_id_seq;\n\n--drop foreign keys on chunk table\nALTER TABLE _timescaledb_catalog.chunk_constraint DROP CONSTRAINT chunk_constraint_chunk_id_fkey;\nALTER TABLE _timescaledb_catalog.chunk_column_stats DROP CONSTRAINT chunk_column_stats_chunk_id_fkey;\nALTER TABLE _timescaledb_internal.bgw_policy_chunk_stats DROP CONSTRAINT bgw_policy_chunk_stats_chunk_id_fkey;\nALTER TABLE _timescaledb_catalog.compression_chunk_size DROP CONSTRAINT compression_chunk_size_chunk_id_fkey;\nALTER TABLE _timescaledb_catalog.compression_chunk_size DROP CONSTRAINT compression_chunk_size_compressed_chunk_id_fkey;\n\n--drop dependent views\nDROP VIEW IF EXISTS timescaledb_information.hypertables;\nDROP VIEW IF EXISTS timescaledb_information.chunks;\nDROP VIEW IF EXISTS _timescaledb_internal.hypertable_chunk_local_size;\nDROP VIEW IF EXISTS _timescaledb_internal.compressed_chunk_stats;\nDROP VIEW IF EXISTS timescaledb_information.chunk_columnstore_settings;\nDROP VIEW IF EXISTS timescaledb_information.chunk_compression_settings;\n\nALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.chunk;\nALTER EXTENSION timescaledb DROP SEQUENCE _timescaledb_catalog.chunk_id_seq;\n\nDROP TABLE _timescaledb_catalog.chunk;\n\nCREATE SEQUENCE _timescaledb_catalog.chunk_id_seq MINVALUE 1;\n\n-- now create table without self referential foreign key\nCREATE TABLE _timescaledb_catalog.chunk (\n  id integer NOT NULL DEFAULT nextval('_timescaledb_catalog.chunk_id_seq'),\n  hypertable_id int NOT NULL,\n  schema_name name NOT NULL,\n  table_name name NOT NULL,\n  compressed_chunk_id integer ,\n  dropped boolean NOT NULL DEFAULT FALSE,\n  status integer NOT NULL DEFAULT 0,\n  osm_chunk boolean NOT NULL DEFAULT FALSE,\n  creation_time timestamptz NOT NULL,\n  -- table constraints\n  CONSTRAINT chunk_pkey PRIMARY KEY (id),\n  CONSTRAINT chunk_schema_name_table_name_key UNIQUE (schema_name, table_name)\n);\n\nINSERT INTO _timescaledb_catalog.chunk( id, hypertable_id, schema_name, table_name, compressed_chunk_id, dropped, status, osm_chunk, creation_time)\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, false, status, osm_chunk, creation_time\nFROM _timescaledb_internal.tmp_chunk;\n\n--add indexes to the chunk table\nCREATE INDEX chunk_hypertable_id_idx ON _timescaledb_catalog.chunk (hypertable_id);\nCREATE INDEX chunk_compressed_chunk_id_idx ON _timescaledb_catalog.chunk (compressed_chunk_id);\nCREATE INDEX chunk_osm_chunk_idx ON _timescaledb_catalog.chunk (osm_chunk, hypertable_id);\nCREATE INDEX chunk_hypertable_id_creation_time_idx ON _timescaledb_catalog.chunk(hypertable_id, creation_time);\n\nALTER SEQUENCE _timescaledb_catalog.chunk_id_seq OWNED BY _timescaledb_catalog.chunk.id;\nSELECT setval('_timescaledb_catalog.chunk_id_seq', last_value, is_called) FROM _timescaledb_internal.tmp_chunk_seq_value;\n\n-- add self referential foreign key\nALTER TABLE _timescaledb_catalog.chunk ADD CONSTRAINT chunk_compressed_chunk_id_fkey FOREIGN KEY ( compressed_chunk_id ) REFERENCES _timescaledb_catalog.chunk( id );\n\n--add foreign key constraint\nALTER TABLE _timescaledb_catalog.chunk ADD CONSTRAINT chunk_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.chunk', '');\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.chunk_id_seq', '');\n\n--add the foreign key constraints\nALTER TABLE _timescaledb_catalog.chunk_constraint ADD CONSTRAINT chunk_constraint_chunk_id_fkey FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk(id);\nALTER TABLE _timescaledb_catalog.chunk_column_stats ADD CONSTRAINT chunk_column_stats_chunk_id_fkey FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk (id);\nALTER TABLE _timescaledb_internal.bgw_policy_chunk_stats ADD CONSTRAINT bgw_policy_chunk_stats_chunk_id_fkey FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.compression_chunk_size ADD CONSTRAINT compression_chunk_size_chunk_id_fkey FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk(id) ON DELETE CASCADE;\nALTER TABLE _timescaledb_catalog.compression_chunk_size ADD CONSTRAINT compression_chunk_size_compressed_chunk_id_fkey FOREIGN KEY (compressed_chunk_id) REFERENCES _timescaledb_catalog.chunk(id) ON DELETE CASCADE;\n\n--cleanup\nDROP TABLE _timescaledb_internal.tmp_chunk;\nDROP TABLE _timescaledb_internal.tmp_chunk_seq_value;\n\nGRANT SELECT ON _timescaledb_catalog.chunk_id_seq TO PUBLIC;\nGRANT SELECT ON _timescaledb_catalog.chunk TO PUBLIC;\n-- end recreate _timescaledb_catalog.chunk table --\n\n-- Rebuild the catalog tables for continuous aggregate migration plans\n\nCREATE TABLE _timescaledb_catalog.continuous_agg_migrate_plan (\n  mat_hypertable_id integer NOT NULL,\n  start_ts TIMESTAMPTZ NOT NULL DEFAULT pg_catalog.now(),\n  end_ts TIMESTAMPTZ,\n  user_view_definition TEXT,\n  -- table constraints\n  CONSTRAINT continuous_agg_migrate_plan_pkey PRIMARY KEY (mat_hypertable_id)\n);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.continuous_agg_migrate_plan', '');\n\nCREATE TABLE _timescaledb_catalog.continuous_agg_migrate_plan_step (\n  mat_hypertable_id integer NOT NULL,\n  step_id serial NOT NULL,\n  status TEXT NOT NULL DEFAULT 'NOT STARTED', -- NOT STARTED, STARTED, FINISHED, CANCELED\n  start_ts TIMESTAMPTZ,\n  end_ts TIMESTAMPTZ,\n  type TEXT NOT NULL,\n  config JSONB,\n  -- table constraints\n  CONSTRAINT continuous_agg_migrate_plan_step_pkey PRIMARY KEY (mat_hypertable_id, step_id),\n  CONSTRAINT continuous_agg_migrate_plan_step_mat_hypertable_id_fkey FOREIGN KEY (mat_hypertable_id) REFERENCES _timescaledb_catalog.continuous_agg_migrate_plan (mat_hypertable_id) ON DELETE CASCADE,\n  CONSTRAINT continuous_agg_migrate_plan_step_check CHECK (start_ts <= end_ts),\n  CONSTRAINT continuous_agg_migrate_plan_step_check2 CHECK (type IN ('CREATE NEW CAGG', 'DISABLE POLICIES', 'COPY POLICIES', 'ENABLE POLICIES', 'SAVE WATERMARK', 'REFRESH NEW CAGG', 'COPY DATA', 'OVERRIDE CAGG', 'DROP OLD CAGG'))\n);\n\nSELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.continuous_agg_migrate_plan_step', '');\n\nSELECT pg_catalog.pg_extension_config_dump(pg_get_serial_sequence('_timescaledb_catalog.continuous_agg_migrate_plan_step', 'step_id'), '');\n\nGRANT SELECT ON ALL TABLES IN SCHEMA _timescaledb_catalog TO PUBLIC;\nGRANT SELECT ON ALL SEQUENCES IN SCHEMA _timescaledb_catalog TO PUBLIC;\n\nCREATE INDEX bgw_job_stat_history_job_id_idx ON _timescaledb_internal.bgw_job_stat_history (job_id);\nDROP INDEX _timescaledb_internal.bgw_job_stat_history_execution_start_idx;\nDROP INDEX _timescaledb_internal.bgw_job_stat_history_job_id_execution_start_idx;\n\n"
  },
  {
    "path": "sql/updates/set_post_update_stage.sql",
    "content": "set timescaledb.update_script_stage = 'post';\n"
  },
  {
    "path": "sql/updates/unset_update_stage.sql",
    "content": "set timescaledb.update_script_stage = '';\n"
  },
  {
    "path": "sql/updates/version_check.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nDO $$\nDECLARE\n  catalog_version TEXT;\nBEGIN\n  SELECT value INTO catalog_version FROM _timescaledb_catalog.metadata WHERE key='timescaledb_version' AND value <> '@START_VERSION@';\n  IF FOUND THEN\n    RAISE EXCEPTION 'catalog version mismatch'\n      USING\n        DETAIL = format('current extension version is \"%s\" but catalog version is \"%s\"', '@START_VERSION@', catalog_version),\n        HINT = 'Make sure the TimescaleDB version used to dump the database is the same as the one used to restore it.';\n  END IF;\nEND$$;\n\n"
  },
  {
    "path": "sql/util_internal_table_ddl.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- This file contains functions associated with creating new\n-- hypertables.\n\n-- Outputs the create_hypertable command to recreate the given hypertable.\n--\n-- This is currently used internally for our single hypertable backup tool\n-- so that it knows how to restore the hypertable without user intervention.\n--\n-- It only works for hypertables with up to 2 dimensions.\nCREATE OR REPLACE FUNCTION _timescaledb_functions.get_create_command(\n    table_name NAME\n)\n    RETURNS TEXT LANGUAGE PLPGSQL VOLATILE AS\n$BODY$\nDECLARE\n    h_id             INTEGER;\n    schema_name      NAME;\n    time_column      NAME;\n    time_interval    BIGINT;\n    space_column     NAME;\n    space_partitions INTEGER;\n    dimension_cnt    INTEGER;\n    dimension_row    record;\n    ret              TEXT;\nBEGIN\n    SELECT h.id, h.schema_name\n    FROM _timescaledb_catalog.hypertable AS h\n    WHERE h.table_name = get_create_command.table_name\n    INTO h_id, schema_name;\n\n    IF h_id IS NULL THEN\n        RAISE EXCEPTION 'hypertable \"%\" not found', table_name\n        USING ERRCODE = 'TS101';\n    END IF;\n\n    SELECT COUNT(*)\n    FROM _timescaledb_catalog.dimension d\n    WHERE d.hypertable_id = h_id\n    INTO STRICT dimension_cnt;\n\n    IF dimension_cnt > 2 THEN\n        RAISE EXCEPTION 'get_create_command only supports hypertables with up to 2 dimensions'\n        USING ERRCODE = 'TS101';\n    END IF;\n\n    FOR dimension_row IN\n        SELECT *\n        FROM _timescaledb_catalog.dimension d\n        WHERE d.hypertable_id = h_id\n        LOOP\n        IF dimension_row.interval_length IS NOT NULL THEN\n            time_column := dimension_row.column_name;\n            time_interval := dimension_row.interval_length;\n        ELSIF dimension_row.num_slices IS NOT NULL THEN\n            space_column := dimension_row.column_name;\n            space_partitions := dimension_row.num_slices;\n        END IF;\n    END LOOP;\n\n    ret := format($$SELECT create_hypertable('%I.%I', '%s'$$, schema_name, table_name, time_column);\n    IF space_column IS NOT NULL THEN\n        ret := ret || format($$, '%I', %s$$, space_column, space_partitions);\n    END IF;\n    ret := ret || format($$, chunk_time_interval => %s, create_default_indexes=>FALSE);$$, time_interval);\n\n    RETURN ret;\nEND\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n"
  },
  {
    "path": "sql/util_time.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- This file contains utilities for time conversion.\n\n-- Return the minimum for the type. For time types, it will be the\n-- Unix timestamp in microseconds.\nCREATE OR REPLACE FUNCTION _timescaledb_functions.get_internal_time_min(REGTYPE) RETURNS BIGINT\nAS '@MODULE_PATHNAME@', 'ts_get_internal_time_min' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\n-- Return the minimum for the type. For time types, it will be the\n-- Unix timestamp in microseconds.\nCREATE OR REPLACE FUNCTION _timescaledb_functions.get_internal_time_max(REGTYPE) RETURNS BIGINT\nAS '@MODULE_PATHNAME@', 'ts_get_internal_time_max' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.to_unix_microseconds(ts TIMESTAMPTZ) RETURNS BIGINT\n    AS '@MODULE_PATHNAME@', 'ts_pg_timestamp_to_unix_microseconds' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.to_timestamp(unixtime_us BIGINT) RETURNS TIMESTAMPTZ\n    AS '@MODULE_PATHNAME@', 'ts_pg_unix_microseconds_to_timestamp' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.to_timestamp_without_timezone(unixtime_us BIGINT)\n  RETURNS TIMESTAMP\n  AS '@MODULE_PATHNAME@', 'ts_pg_unix_microseconds_to_timestamp'\n  LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.to_date(unixtime_us BIGINT)\n  RETURNS DATE\n  AS '@MODULE_PATHNAME@', 'ts_pg_unix_microseconds_to_date'\n  LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.to_interval(unixtime_us BIGINT) RETURNS INTERVAL\n    AS '@MODULE_PATHNAME@', 'ts_pg_unix_microseconds_to_interval' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\n-- Time can be represented in a hypertable as an int* (bigint/integer/smallint) or as a timestamp type (\n-- with or without timezones). In metatables and other internal systems all time values are stored as bigint.\n-- Converting from int* columns to internal representation is a cast to bigint.\n-- Converting from timestamps to internal representation is conversion to epoch (in microseconds).\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.interval_to_usec(\n       chunk_interval INTERVAL\n)\nRETURNS BIGINT LANGUAGE SQL IMMUTABLE PARALLEL SAFE AS\n$BODY$\n    SELECT (int_sec * 1000000)::bigint from extract(epoch from chunk_interval) as int_sec;\n$BODY$ SET search_path TO pg_catalog, pg_temp;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.time_to_internal(time_val ANYELEMENT)\nRETURNS BIGINT AS '@MODULE_PATHNAME@', 'ts_time_to_internal' LANGUAGE C VOLATILE STRICT;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.cagg_watermark(hypertable_id INTEGER)\nRETURNS INT8 AS '@MODULE_PATHNAME@', 'ts_continuous_agg_watermark' LANGUAGE C STABLE STRICT PARALLEL RESTRICTED;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.cagg_watermark_materialized(hypertable_id INTEGER)\nRETURNS INT8 AS '@MODULE_PATHNAME@', 'ts_continuous_agg_watermark_materialized' LANGUAGE C STABLE STRICT PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.subtract_integer_from_now( hypertable_relid REGCLASS, lag INT8 )\nRETURNS INT8 AS '@MODULE_PATHNAME@', 'ts_subtract_integer_from_now' LANGUAGE C STABLE STRICT;\n\n-- Convert integer UNIX timestamps in microsecond to a timestamp range.\nCREATE OR REPLACE FUNCTION _timescaledb_functions.make_multirange_from_internal_time(\n    base tstzrange, low_usec bigint, high_usec bigint\n) RETURNS TSTZMULTIRANGE AS\n$body$\n  select multirange(tstzrange(_timescaledb_functions.to_timestamp(low_usec),\n\t\t\t      _timescaledb_functions.to_timestamp(high_usec)));\n$body$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE\nSET search_path TO pg_catalog, pg_temp;\n\n-- Convert integer UNIX timestamps in microsecond to a timestamp range.\nCREATE OR REPLACE FUNCTION _timescaledb_functions.make_multirange_from_internal_time(\n    base TSRANGE, low_usec bigint, high_usec bigint\n) RETURNS TSMULTIRANGE AS\n$body$\n  select multirange(tsrange(_timescaledb_functions.to_timestamp_without_timezone(low_usec),\n\t\t\t    _timescaledb_functions.to_timestamp_without_timezone(high_usec)));\n$body$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE\nSET search_path TO pg_catalog, pg_temp;\n\n-- Helper function to construct a range given an existing type from\n-- UNIX timestamps in microsecond precision.\nCREATE OR REPLACE FUNCTION _timescaledb_functions.make_range_from_internal_time(\n    base anyrange, low_usec anyelement, high_usec anyelement\n) RETURNS anyrange\nAS '@MODULE_PATHNAME@', 'ts_make_range_from_internal_time'\nLANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n"
  },
  {
    "path": "sql/uuidv7.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE OR REPLACE FUNCTION @extschema@.generate_uuidv7() RETURNS UUID\nAS '@MODULE_PATHNAME@', 'ts_uuid_generate_v7' LANGUAGE C VOLATILE STRICT PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION @extschema@.to_uuidv7(\n  ts TIMESTAMPTZ\n) RETURNS UUID\nAS '@MODULE_PATHNAME@', 'ts_uuid_v7_from_timestamptz' LANGUAGE C VOLATILE STRICT PARALLEL SAFE;\n\n--\n-- Produce a boundary UUIDv7 from a timestamp, with all otherwise\n-- random bits in the resulting UUID set to zero. Useful for\n-- time-range queries directly on a UUID column.\n--\nCREATE OR REPLACE FUNCTION @extschema@.to_uuidv7_boundary(\n  ts TIMESTAMPTZ\n) RETURNS UUID\nAS '@MODULE_PATHNAME@', 'ts_uuid_v7_from_timestamptz_boundary' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\n--\n-- Get the v7 UUID timestamp with millisecond precision.\n--\nCREATE OR REPLACE FUNCTION @extschema@.uuid_timestamp(\n  uuid UUID\n) RETURNS TIMESTAMPTZ\nAS '@MODULE_PATHNAME@', 'ts_timestamptz_from_uuid_v7' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\n--\n-- Get the v7 UUID timestamp with microsecond precision using the\n-- (optional) rand_a bits.\n--\nCREATE OR REPLACE FUNCTION @extschema@.uuid_timestamp_micros(\n  uuid UUID\n) RETURNS TIMESTAMPTZ\nAS '@MODULE_PATHNAME@', 'ts_timestamptz_from_uuid_v7_with_microseconds' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION @extschema@.uuid_version(\n  uuid UUID\n) RETURNS INTEGER\nAS '@MODULE_PATHNAME@', 'ts_uuid_version' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n"
  },
  {
    "path": "sql/version.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.get_git_commit()\n    RETURNS TABLE(commit_tag TEXT, commit_hash TEXT, commit_time TIMESTAMPTZ)\n    AS '@MODULE_PATHNAME@', 'ts_get_git_commit' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.get_os_info()\n    RETURNS TABLE(sysname TEXT, version TEXT, release TEXT, version_pretty TEXT)\n    AS '@MODULE_PATHNAME@', 'ts_get_os_info' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_functions.tsl_loaded() RETURNS BOOLEAN\nAS '@MODULE_PATHNAME@', 'ts_tsl_loaded' LANGUAGE C;\n"
  },
  {
    "path": "sql/views.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Convenience view to list all hypertables\nCREATE OR REPLACE VIEW timescaledb_information.hypertables AS\nWITH\n  hypertable_info AS (\n    SELECT hypertable_id, schema_name, table_name,\n           num_dimensions, compression_state, column_name,\n           column_type, interval_length,\n           (compression_state = 1) AS compression_enabled,\n           row_number() OVER (PARTITION BY hypertable_id ORDER BY di.id) AS dimension_num\n      FROM _timescaledb_catalog.hypertable ht\n      JOIN _timescaledb_catalog.dimension di ON ht.id = di.hypertable_id\n  )\nSELECT\n  ht.schema_name AS hypertable_schema,\n  ht.table_name AS hypertable_name,\n  t.tableowner AS owner,\n  ht.num_dimensions,\n  (\n    SELECT count(1)\n    FROM _timescaledb_catalog.chunk ch\n    WHERE ch.hypertable_id = ht.hypertable_id\n      AND ch.osm_chunk IS FALSE\n  ) AS num_chunks,\n  ht.compression_enabled,\n  srchtbs.tablespace_list AS tablespaces,\n  ht.column_name AS primary_dimension,\n  ht.column_type AS primary_dimension_type\nFROM hypertable_info ht\nJOIN pg_tables t ON ht.table_name = t.tablename AND ht.schema_name = t.schemaname\nLEFT JOIN _timescaledb_catalog.continuous_agg ca ON ca.mat_hypertable_id = ht.hypertable_id\nLEFT JOIN (\n    SELECT hypertable_id,\n      array_agg(tablespace_name ORDER BY id) AS tablespace_list\n    FROM _timescaledb_catalog.tablespace\n    GROUP BY hypertable_id) srchtbs ON ht.hypertable_id = srchtbs.hypertable_id\nWHERE ht.compression_state != 2 --> no internal compression tables\n  AND ca.mat_hypertable_id IS NULL\n  AND ht.interval_length IS NOT NULL\n  AND ht.dimension_num = 1;\n\n-- Get status of existing jobs.\n--\n-- Note that we will always list all jobs that are available in the\n-- database, but some fields might be null if, for example, the job\n-- has not yet executed, or there is no hypertable associated with the\n-- job.\nCREATE OR REPLACE VIEW timescaledb_information.job_stats AS\nSELECT ht.schema_name AS hypertable_schema,\n  ht.table_name AS hypertable_name,\n  j.id AS job_id,\n  js.last_start AS last_run_started_at,\n  js.last_successful_finish AS last_successful_finish,\n  CASE WHEN js.last_finish < '4714-11-24 00:00:00+00 BC' THEN\n    NULL\n  WHEN js.last_finish IS NOT NULL THEN\n    CASE WHEN js.last_run_success = 't' THEN\n      'Success'\n    WHEN js.last_run_success = 'f' THEN\n      'Failed'\n    END\n  END AS last_run_status,\n  CASE WHEN pgs.state = 'active' THEN\n    'Running'\n  WHEN j.scheduled = FALSE THEN\n    'Paused'\n  ELSE\n    'Scheduled'\n  END AS job_status,\n  CASE WHEN js.last_finish > js.last_start THEN\n  (js.last_finish - js.last_start)\n  END AS last_run_duration,\n  CASE WHEN j.scheduled THEN\n    js.next_start\n  END AS next_start,\n  js.total_runs,\n  js.total_successes,\n  js.total_failures\nFROM _timescaledb_catalog.bgw_job j\n  LEFT JOIN _timescaledb_internal.bgw_job_stat js ON j.id = js.job_id\n  LEFT JOIN _timescaledb_catalog.hypertable ht ON j.hypertable_id = ht.id\n  LEFT JOIN pg_stat_activity pgs ON pgs.datname = current_database()\n    AND pgs.application_name = j.application_name\n  ORDER BY ht.schema_name,\n    ht.table_name;\n\n-- view for background worker jobs\nCREATE OR REPLACE VIEW timescaledb_information.jobs AS\nSELECT j.id AS job_id,\n  j.application_name,\n  j.schedule_interval,\n  j.max_runtime,\n  j.max_retries,\n  j.retry_period,\n  j.proc_schema,\n  j.proc_name,\n  j.owner,\n  j.scheduled,\n  j.fixed_schedule,\n  j.config,\n  js.next_start,\n  j.initial_start,\n  COALESCE(ca.user_view_schema, ht.schema_name) AS hypertable_schema,\n  COALESCE(ca.user_view_name, ht.table_name) AS hypertable_name,\n  j.check_schema,\n  j.check_name\nFROM _timescaledb_catalog.bgw_job j\n  LEFT JOIN _timescaledb_catalog.hypertable ht ON ht.id = j.hypertable_id\n  LEFT JOIN _timescaledb_internal.bgw_job_stat js ON js.job_id = j.id\n  LEFT JOIN _timescaledb_catalog.continuous_agg ca ON ca.mat_hypertable_id = j.hypertable_id;\n\n-- views for continuous aggregate queries ---\nCREATE OR REPLACE VIEW timescaledb_information.continuous_aggregates AS\nSELECT ht.schema_name AS hypertable_schema,\n  ht.table_name AS hypertable_name,\n  cagg.user_view_schema AS view_schema,\n  cagg.user_view_name AS view_name,\n  viewinfo.viewowner AS view_owner,\n  cagg.materialized_only,\n  CASE WHEN mat_ht.compressed_hypertable_id IS NOT NULL\n       THEN TRUE\n       ELSE FALSE\n  END AS compression_enabled,\n  mat_ht.schema_name AS materialization_hypertable_schema,\n  mat_ht.table_name AS materialization_hypertable_name,\n  directview.viewdefinition AS view_definition\nFROM _timescaledb_catalog.continuous_agg cagg,\n  _timescaledb_catalog.hypertable ht,\n  LATERAL (\n    SELECT C.oid,\n      pg_get_userbyid(C.relowner) AS viewowner\n    FROM pg_class C\n      LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)\n    WHERE C.relkind = 'v'\n      AND C.relname = cagg.user_view_name\n      AND N.nspname = cagg.user_view_schema) viewinfo,\n  LATERAL (\n    SELECT pg_get_viewdef(C.oid) AS viewdefinition\n    FROM pg_class C\n    LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)\n  WHERE C.relkind = 'v'\n    AND C.relname = cagg.direct_view_name\n    AND N.nspname = cagg.direct_view_schema) directview,\n  LATERAL (\n    SELECT schema_name, table_name, compressed_hypertable_id\n    FROM _timescaledb_catalog.hypertable\n    WHERE cagg.mat_hypertable_id = id) mat_ht\nWHERE cagg.raw_hypertable_id = ht.id;\n\n-- chunks metadata view, shows information about the primary dimension column\n-- query plans with CTEs are not always optimized by PG. So use in-line\n-- tables.\n\nCREATE OR REPLACE VIEW timescaledb_information.chunks AS\nSELECT hypertable_schema,\n  hypertable_name,\n  schema_name AS chunk_schema,\n  chunk_name,\n  primary_dimension,\n  primary_dimension_type,\n  range_start,\n  range_end,\n  integer_range_start AS range_start_integer,\n  integer_range_end AS range_end_integer,\n  is_compressed,\n  chunk_table_space AS chunk_tablespace,\n  creation_time AS chunk_creation_time\nFROM (\n  SELECT ht.schema_name AS hypertable_schema,\n    ht.table_name AS hypertable_name,\n    srcch.schema_name AS schema_name,\n    srcch.table_name AS chunk_name,\n    dim.column_name AS primary_dimension,\n    dim.column_type AS primary_dimension_type,\n    row_number() OVER (PARTITION BY chcons.chunk_id ORDER BY dim.id) AS chunk_dimension_num,\n    CASE WHEN dim.column_type = ANY(ARRAY['timestamp','timestamptz','date', 'uuid']::regtype[]) THEN\n      _timescaledb_functions.to_timestamp(dimsl.range_start)\n    ELSE\n      NULL\n    END AS range_start,\n    CASE WHEN dim.column_type = ANY(ARRAY['timestamp','timestamptz','date', 'uuid']::regtype[]) THEN\n      _timescaledb_functions.to_timestamp(dimsl.range_end)\n    ELSE\n      NULL\n    END AS range_end,\n    CASE WHEN dim.column_type = ANY(ARRAY['timestamp','timestamptz','date', 'uuid']::regtype[]) THEN\n      NULL\n    ELSE\n      dimsl.range_start\n    END AS integer_range_start,\n    CASE WHEN dim.column_type = ANY(ARRAY['timestamp','timestamptz','date', 'uuid']::regtype[]) THEN\n      NULL\n    ELSE\n      dimsl.range_end\n    END AS integer_range_end,\n    CASE WHEN (srcch.status & 1 = 1) THEN\n        TRUE\n    ELSE FALSE\n    END AS is_compressed,\n    pgtab.spcname AS chunk_table_space,\n\tsrcch.creation_time AS creation_time\n  FROM _timescaledb_catalog.chunk srcch\n    INNER JOIN _timescaledb_catalog.hypertable ht ON ht.id = srcch.hypertable_id\n    INNER JOIN _timescaledb_catalog.chunk_constraint chcons ON srcch.id = chcons.chunk_id\n    INNER JOIN _timescaledb_catalog.dimension dim ON srcch.hypertable_id = dim.hypertable_id\n    INNER JOIN _timescaledb_catalog.dimension_slice dimsl ON dim.id = dimsl.dimension_id\n      AND chcons.dimension_slice_id = dimsl.id\n    INNER JOIN (\n      SELECT relname,\n        reltablespace,\n        nspname AS schema_name\n      FROM pg_class,\n        pg_namespace\n      WHERE pg_class.relnamespace = pg_namespace.oid) cl ON srcch.table_name = cl.relname\n      AND srcch.schema_name = cl.schema_name\n    LEFT OUTER JOIN pg_tablespace pgtab ON pgtab.oid = reltablespace\n  WHERE srcch.osm_chunk IS FALSE\n    AND ht.compression_state != 2 ) finalq\nWHERE chunk_dimension_num = 1;\n\n-- hypertable's dimension information\n-- CTEs aren't used in the query as PG does not always optimize them\n-- as expected.\n\nCREATE OR REPLACE VIEW timescaledb_information.dimensions AS\nSELECT ht.schema_name AS hypertable_schema,\n  ht.table_name AS hypertable_name,\n  rank() OVER (PARTITION BY hypertable_id ORDER BY dim.id) AS dimension_number,\n  dim.column_name,\n  dim.column_type,\n  CASE WHEN dim.interval_length IS NULL THEN\n    'Space'\n  ELSE\n    'Time'\n  END AS dimension_type,\n  CASE WHEN dim.interval_length IS NOT NULL THEN\n    CASE WHEN dim.column_type = ANY(ARRAY['timestamp','timestamptz','date', 'uuid']::regtype[]) THEN\n      _timescaledb_functions.to_interval(dim.interval_length)\n    ELSE\n      NULL\n    END\n  END AS time_interval,\n  CASE WHEN dim.interval_length IS NOT NULL THEN\n    CASE WHEN dim.column_type = ANY(ARRAY['timestamp','timestamptz','date', 'uuid']::regtype[]) THEN\n      NULL\n    ELSE\n      dim.interval_length\n    END\n  END AS integer_interval,\n  dim.integer_now_func,\n  dim.num_slices AS num_partitions\nFROM _timescaledb_catalog.hypertable ht,\n  _timescaledb_catalog.dimension dim\nWHERE dim.hypertable_id = ht.id;\n\n---compression parameters information ---\nCREATE OR REPLACE VIEW timescaledb_information.compression_settings AS\nSELECT\n\tschema_name AS hypertable_schema,\n  table_name AS hypertable_name,\n  (unnest(cs.segmentby))::name COLLATE \"C\" AS attname,\n  generate_series(1,array_length(cs.segmentby,1))::smallint AS segmentby_column_index,\n  NULL::smallint AS orderby_column_index,\n  NULL::bool AS orderby_asc,\n  NULL::bool AS orderby_nullsfirst\nFROM _timescaledb_catalog.hypertable ht\nINNER JOIN _timescaledb_catalog.compression_settings cs ON cs.relid = format('%I.%I',ht.schema_name,ht.table_name)::regclass AND cs.segmentby IS NOT NULL\nWHERE compressed_hypertable_id IS NOT NULL\nUNION ALL\nSELECT\n\tschema_name AS hypertable_schema,\n  table_name AS hypertable_name,\n  (unnest(cs.orderby))::name COLLATE \"C\" AS attname,\n  NULL::smallint AS segmentby_column_index,\n  generate_series(1,array_length(cs.orderby,1))::smallint AS orderby_column_index,\n  unnest(array_replace(array_replace(array_replace(cs.orderby_desc,false,NULL),true,false),NULL,true)) AS orderby_asc,\n  unnest(cs.orderby_nullsfirst) AS orderby_nullsfirst\nFROM _timescaledb_catalog.hypertable ht\nINNER JOIN _timescaledb_catalog.compression_settings cs ON cs.relid = format('%I.%I',ht.schema_name,ht.table_name)::regclass AND cs.orderby IS NOT NULL\nWHERE compressed_hypertable_id IS NOT NULL\nORDER BY hypertable_name,\n  segmentby_column_index,\n  orderby_column_index;\n\n-- Job errors view that adds a security barrier on the bgw_job_stat_history\n-- table in _timescaledb_internal. The view only allows users to view\n-- log entries belonging to jobs that are owned by any of the users\n-- role. A special case is added so that the superuser or the database\n-- owner can see all job log entries, even those that do not have an\n-- associated job.\n--\n-- Note that we have to use a sub-select here since pg_database_owner\n-- does not exist before PostgreSQL 14.\nCREATE OR REPLACE VIEW timescaledb_information.job_errors\nWITH (security_barrier = true) AS\nSELECT\n    h.job_id,\n    h.data->'job'->>'proc_schema' as proc_schema,\n    h.data->'job'->>'proc_name' as proc_name,\n    h.pid,\n    h.execution_start AS start_time,\n    h.execution_finish AS finish_time,\n    h.data->'error_data'->>'sqlerrcode' AS sqlerrcode,\n    CASE\n      WHEN h.succeeded IS NULL AND h.execution_finish IS NULL AND h.pid IS NULL THEN\n        'job crash detected, see server logs'\n      WHEN h.data->'error_data'->>'message' IS NOT NULL THEN\n        CASE WHEN h.data->'error_data'->>'detail' IS NOT NULL THEN\n          CASE WHEN h.data->'error_data'->>'hint' IS NOT NULL THEN concat(h.data->'error_data'->>'message', '. ', h.data->'error_data'->>'detail', '. ', h.data->'error_data'->>'hint')\n          ELSE concat(h.data->'error_data'->>'message', ' ', h.data->'error_data'->>'detail')\n          END\n        ELSE\n          CASE WHEN h.data->'error_data'->>'hint' IS NOT NULL THEN concat(h.data->'error_data'->>'message', '. ', h.data->'error_data'->>'hint')\n          ELSE h.data->'error_data'->>'message'\n          END\n        END\n    END AS err_message\nFROM\n    _timescaledb_internal.bgw_job_stat_history h\nLEFT JOIN\n    _timescaledb_catalog.bgw_job j ON (j.id = h.job_id)\nWHERE\n    h.succeeded IS FALSE\n    OR h.succeeded IS NULL\n    AND (pg_catalog.pg_has_role(current_user,\n\t\t\t   (SELECT pg_catalog.pg_get_userbyid(datdba)\n\t\t\t      FROM pg_catalog.pg_database\n\t\t\t     WHERE datname = current_database()),\n\t\t\t   'MEMBER') IS TRUE\n    OR pg_catalog.pg_has_role(current_user, owner, 'MEMBER') IS TRUE);\n\nCREATE OR REPLACE VIEW timescaledb_information.job_history\nWITH (security_barrier = true) AS\nSELECT\n    h.id,\n    h.job_id,\n    h.succeeded,\n    coalesce(h.data->'job'->>'proc_schema', j.proc_schema) as proc_schema,\n    coalesce(h.data->'job'->>'proc_name', j.proc_name) as proc_name,\n    h.pid,\n    h.execution_start AS start_time,\n    h.execution_finish AS finish_time,\n    h.data->'job'->'config' AS config,\n    h.data->'error_data'->>'sqlerrcode' AS sqlerrcode,\n    CASE\n      WHEN h.succeeded IS NULL AND h.execution_finish IS NULL AND h.pid IS NULL THEN\n        'job crash detected, see server logs'\n      WHEN h.succeeded IS FALSE AND h.data->'error_data'->>'message' IS NOT NULL THEN\n        CASE WHEN h.data->'error_data'->>'detail' IS NOT NULL THEN\n          CASE WHEN h.data->'error_data'->>'hint' IS NOT NULL THEN concat(h.data->'error_data'->>'message', '. ', h.data->'error_data'->>'detail', '. ', h.data->'error_data'->>'hint')\n          ELSE concat(h.data->'error_data'->>'message', ' ', h.data->'error_data'->>'detail')\n          END\n        ELSE\n          CASE WHEN h.data->'error_data'->>'hint' IS NOT NULL THEN concat(h.data->'error_data'->>'message', '. ', h.data->'error_data'->>'hint')\n          ELSE h.data->'error_data'->>'message'\n          END\n        END\n    END AS err_message\nFROM\n    _timescaledb_internal.bgw_job_stat_history h\nLEFT JOIN\n    _timescaledb_catalog.bgw_job j ON (j.id = h.job_id)\nWHERE (pg_catalog.pg_has_role(current_user,\n\t\t\t   (SELECT pg_catalog.pg_get_userbyid(datdba)\n\t\t\t      FROM pg_catalog.pg_database\n\t\t\t     WHERE datname = current_database()),\n\t\t\t   'MEMBER') IS TRUE\n    OR pg_catalog.pg_has_role(current_user, owner, 'MEMBER') IS TRUE);\n\nCREATE OR REPLACE VIEW timescaledb_information.hypertable_compression_settings AS\n\tSELECT\n\t\tformat('%I.%I',ht.schema_name,ht.table_name)::regclass AS hypertable,\n\t\tarray_to_string(segmentby,',') AS segmentby,\n\t\tun.orderby,\n    d.compress_interval_length,\n    s.index AS index\n  FROM _timescaledb_catalog.hypertable ht\n  JOIN LATERAL (\n    SELECT\n      CASE WHEN d.column_type = ANY(ARRAY['timestamp','timestamptz','date']::regtype[]) THEN\n        _timescaledb_functions.to_interval(d.compress_interval_length)::text\n      ELSE\n        d.compress_interval_length::text\n      END AS compress_interval_length\n    FROM _timescaledb_catalog.dimension d WHERE d.hypertable_id = ht.id ORDER BY id LIMIT 1\n  ) d ON true\n  LEFT JOIN _timescaledb_catalog.compression_settings s ON format('%I.%I',ht.schema_name,ht.table_name)::regclass = s.relid\n\tLEFT JOIN LATERAL (\n\t\tSELECT\n\t\t\tstring_agg(\n\t\t\t\tformat('%I%s%s',orderby,\n\t\t\t\t\tCASE WHEN \"desc\" THEN ' DESC' ELSE '' END,\n\t\t\t\t\tCASE WHEN nullsfirst AND NOT \"desc\" THEN ' NULLS FIRST' WHEN NOT nullsfirst AND \"desc\" THEN ' NULLS LAST' ELSE '' END\n\t\t\t\t)\n\t\t\t,',') AS orderby\n\t\tFROM unnest(s.orderby, s.orderby_desc, s.orderby_nullsfirst) un(orderby, \"desc\", nullsfirst)\n\t) un ON true;\n\nCREATE OR REPLACE VIEW timescaledb_information.chunk_compression_settings AS\n\tSELECT\n\t\tformat('%I.%I',ht.schema_name,ht.table_name)::regclass AS hypertable,\n\t\tformat('%I.%I',ch.schema_name,ch.table_name)::regclass AS chunk,\n\t\tarray_to_string(segmentby,',') AS segmentby,\n\t\tun.orderby,\n    s.index AS index\n\tFROM _timescaledb_catalog.hypertable ht\n    INNER JOIN _timescaledb_catalog.chunk ch ON ch.hypertable_id = ht.id\n    INNER JOIN _timescaledb_catalog.compression_settings s ON (format('%I.%I',ch.schema_name,ch.table_name)::regclass = s.relid)\n\tLEFT JOIN LATERAL (\n\t\tSELECT\n\t\t\tstring_agg(\n\t\t\t\tformat('%I%s%s',orderby,\n\t\t\t\t\tCASE WHEN \"desc\" THEN ' DESC' ELSE '' END,\n\t\t\t\t\tCASE WHEN nullsfirst AND NOT \"desc\" THEN ' NULLS FIRST' WHEN NOT nullsfirst AND \"desc\" THEN ' NULLS LAST' ELSE '' END\n\t\t\t\t)\n\t\t\t,',') AS orderby\n\t\tFROM unnest(s.orderby, s.orderby_desc, s.orderby_nullsfirst) un(orderby, \"desc\", nullsfirst)\n\t) un ON true;\n\n\nCREATE OR REPLACE VIEW timescaledb_information.hypertable_columnstore_settings\nAS SELECT * FROM timescaledb_information.hypertable_compression_settings;\n\nCREATE OR REPLACE VIEW timescaledb_information.chunk_columnstore_settings AS\nSELECT * FROM timescaledb_information.chunk_compression_settings;\n\n--temporary alias for bgw_job\nCREATE OR REPLACE VIEW _timescaledb_config.bgw_job AS\nSELECT * from _timescaledb_catalog.bgw_job;\n\nGRANT SELECT ON ALL TABLES IN SCHEMA _timescaledb_config TO PUBLIC;\nGRANT SELECT ON ALL TABLES IN SCHEMA timescaledb_information TO PUBLIC;\n"
  },
  {
    "path": "sql/views_experimental.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE OR REPLACE VIEW timescaledb_experimental.policies AS\nSELECT ca.user_view_name AS relation_name,\n  ca.user_view_schema AS relation_schema,\n  j.schedule_interval,\n  j.proc_schema,\n  j.proc_name,\n  j.config,\n  ht.schema_name AS hypertable_schema,\n  ht.table_name AS hypertable_name\nFROM _timescaledb_catalog.bgw_job j\n  JOIN _timescaledb_catalog.continuous_agg ca ON ca.mat_hypertable_id = j.hypertable_id\n  JOIN _timescaledb_catalog.hypertable ht ON ht.id = ca.mat_hypertable_id;\n\nGRANT SELECT ON ALL TABLES IN SCHEMA timescaledb_experimental TO PUBLIC;\n"
  },
  {
    "path": "sql/with_telemetry.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE OR REPLACE FUNCTION @extschema@.get_telemetry_report()\n       RETURNS jsonb AS '@MODULE_PATHNAME@', 'ts_telemetry_get_report_jsonb'\n       LANGUAGE C STABLE PARALLEL SAFE;\n\nINSERT INTO _timescaledb_catalog.bgw_job (id, application_name, schedule_interval, max_runtime, max_retries, retry_period, proc_schema, proc_name, owner, scheduled, fixed_schedule) VALUES\n(1, 'Telemetry Reporter [1]', INTERVAL '24h', INTERVAL '100s', -1, INTERVAL '1h', '_timescaledb_functions', 'policy_telemetry', pg_catalog.quote_ident(current_role)::regrole, true, false)\nON CONFLICT (id) DO NOTHING;\n"
  },
  {
    "path": "sql/without_telemetry.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- We drop the telemetry function here since we have a non-telemetry\n-- build and the function might exist.\nDROP FUNCTION IF EXISTS @extschema@.get_telemetry_report;\n\n-- We delete the telemetry job since this is a non-telemetry build.\nDELETE FROM _timescaledb_catalog.bgw_job WHERE id = 1;\n\n\n"
  },
  {
    "path": "src/CMakeLists.txt",
    "content": "set(SOURCES\n    uuid.c\n    agg_bookend.c\n    bmslist_utils.c\n    func_cache.c\n    cache.c\n    cache_invalidate.c\n    chunk.c\n    chunk_adaptive.c\n    chunk_constraint.c\n    chunk_index.c\n    chunk_insert_state.c\n    chunk_scan.c\n    chunk_tuple_routing.c\n    constraint.c\n    cross_module_fn.c\n    copy.c\n    dimension.c\n    dimension_slice.c\n    dimension_vector.c\n    estimate.c\n    event_trigger.c\n    extension.c\n    extension_constants.c\n    expression_utils.c\n    foreign_key.c\n    gapfill.c\n    guc.c\n    histogram.c\n    hypercube.c\n    hypertable.c\n    hypertable_cache.c\n    hypertable_restrict_info.c\n    indexing.c\n    init.c\n    jsonb_utils.c\n    license_guc.c\n    osm_callbacks.c\n    partitioning.c\n    partition_chunk.c\n    process_utility.c\n    scanner.c\n    scan_iterator.c\n    sort_transform.c\n    subspace_store.c\n    timezones.c\n    time_bucket.c\n    time_utils.c\n    custom_type_cache.c\n    trigger.c\n    utils.c\n    version.c\n    tss_callbacks.c)\n\n# Add test source code in Debug builds\nif(CMAKE_BUILD_TYPE MATCHES Debug)\n  set(TS_DEBUG 1)\n  set(DEBUG 1)\n  list(APPEND SOURCES debug_point.c)\nendif(CMAKE_BUILD_TYPE MATCHES Debug)\n\ninclude(build-defs.cmake)\n\nset(GITREV_TMP ${CMAKE_CURRENT_BINARY_DIR}/tmp_gitcommit.h)\nset(GITREV_FILE ${CMAKE_CURRENT_BINARY_DIR}/gitcommit.h)\n\n# The commands for generating gitcommit.h need to be executed on every make run\n# and not on cmake run to detect branch switches, commit changes or local\n# modifications. That's why we add the commands in a custom target and run them\n# on every make run. We do the generation part in a temporary file and only\n# overwrite the actual file when the content is different to not trigger\n# unnecessary recompilations.\n\nadd_custom_target(\n  gitcheck\n  COMMAND\n    ${CMAKE_COMMAND} \"-DGIT_FOUND=${GIT_FOUND}\"\n    \"-DSOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}\"\n    \"-DGIT_EXECUTABLE=${GIT_EXECUTABLE}\"\n    \"-DINPUT_FILE=${CMAKE_CURRENT_SOURCE_DIR}/gitcommit.h.in\"\n    \"-DOUTPUT_FILE=${GITREV_TMP}\" -P ${CMAKE_CURRENT_SOURCE_DIR}/gitcommit.cmake\n  COMMAND ${CMAKE_COMMAND} -E copy_if_different ${GITREV_TMP} ${GITREV_FILE})\n\nif(CMAKE_BUILD_TYPE MATCHES Debug)\n  add_library(${PROJECT_NAME} MODULE ${SOURCES} ${GITCOMMIT_H}\n                                     $<TARGET_OBJECTS:${TESTS_LIB_NAME}>)\nelse()\n  add_library(${PROJECT_NAME} MODULE ${SOURCES} ${GITCOMMIT_H})\nendif()\n\nif(USE_TELEMETRY)\n  if(SEND_TELEMETRY_DEFAULT)\n    set(TELEMETRY_DEFAULT TELEMETRY_BASIC)\n  else()\n    set(TELEMETRY_DEFAULT TELEMETRY_OFF)\n  endif()\nendif()\n\nset_target_properties(\n  ${PROJECT_NAME} PROPERTIES OUTPUT_NAME ${PROJECT_NAME}-${PROJECT_VERSION_MOD}\n                             PREFIX \"\")\n\ninstall(TARGETS ${PROJECT_NAME} DESTINATION ${PG_PKGLIBDIR})\n\nif(USE_OPENSSL)\n  set(TS_USE_OPENSSL ${USE_OPENSSL})\n  target_include_directories(${PROJECT_NAME} SYSTEM\n                             PUBLIC ${OPENSSL_INCLUDE_DIR})\n  if(MSVC)\n    target_link_libraries(${PROJECT_NAME} ${OPENSSL_LIBRARIES})\n  endif(MSVC)\nendif(USE_OPENSSL)\n\nconfigure_file(config.h.in config.h)\nadd_dependencies(${PROJECT_NAME} gitcheck)\ninclude_directories(${CMAKE_CURRENT_SOURCE_DIR})\nadd_subdirectory(bgw)\nadd_subdirectory(net)\nif(USE_TELEMETRY)\n  add_subdirectory(telemetry)\nendif()\nadd_subdirectory(loader)\nadd_subdirectory(bgw_policy)\nadd_subdirectory(compat)\nadd_subdirectory(ts_catalog)\nadd_subdirectory(nodes)\nadd_subdirectory(planner)\nadd_subdirectory(with_clause)\n\n# Don't run clang-tidy on the files we copied from Postgres. We don't want to\n# introduce changes there unless absolutely necessary. CMake can only access the\n# file properties if the target was added in the same directory, so just move it\n# all here.\nset(IMPORTED_SOURCES import/allpaths.c import/heapswap.c import/list.c\n                     import/planner.c import/ts_explain.c)\nset_source_files_properties(${IMPORTED_SOURCES} PROPERTIES SKIP_LINTING ON)\ntarget_sources(${PROJECT_NAME} PRIVATE ${IMPORTED_SOURCES})\n"
  },
  {
    "path": "src/README.md",
    "content": "# Basic TimescaleDB Features\n\n- [TimescaleDB Abstract Data Types](adts/README.md)\n- [TimescaleDB Scheduler](bgw/README.md)\n- [TimescaleDB Multi-version Loader](loader/README.md)\n\n\n"
  },
  {
    "path": "src/adts/README.md",
    "content": "# A Collection of ADTs\n\nThis directory contains a collection of Abstract Data Types.\nThese ADTS are containers that can store data of any other type.\nThese ADTs use macros as oppossed to void pointers for performance\nreasons, as well as for better type safety. This approach to\nADTs follows Postgres convention.\n\n## Vector\n\nA dynamic vector implementation that can store any type. It handles\ngrowing/shrinking the memory for you.\n\n## Bit Array\n\nA dynamic vector to store bits. The API allows appending and iterating\nan arbitrary amount of bits. It stores the bits in a vector of uint64\nand has methods to serialize/deserialize.\n"
  },
  {
    "path": "src/adts/bit_array.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include <lib/stringinfo.h>\n#include <libpq/pqformat.h>\n\n#include \"bit_array_impl.h\"\n#include <adts/uint64_vec.h>\n\n/*******************\n ***  Public API ***\n *******************/\n\n/* Array to hold blobs of bits of arbitrary sizes. The interface\n * expects you to read the same amount of bits, in the same order, as what\n * was written.\n */\ntypedef struct BitArray BitArray;\ntypedef struct BitArrayIterator BitArrayIterator;\n\n/* Main Interface */\nstatic void bit_array_init(BitArray *array, int expected_bits);\n\n/* Append num_bits to the array */\nstatic void bit_array_append(BitArray *array, uint8 num_bits, uint64 bits);\n\nstatic void bit_array_iterator_init(BitArrayIterator *iter, const BitArray *array);\n/* return next num_bits from the iterator; must have been written as num_bits */\npg_attribute_always_inline static uint64 bit_array_iter_next(BitArrayIterator *iter,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t uint8 num_bits);\nstatic void bit_array_iterator_init_rev(BitArrayIterator *iter, const BitArray *array);\n/* return last num_bits in forward-order (not reverse-order); must have been written as num_bits */\nstatic uint64 bit_array_iter_next_rev(BitArrayIterator *iter, uint8 num_bits);\n\n/* I/O */\nstatic inline void bit_array_send(StringInfo buffer, const BitArray *data);\nstatic inline BitArray bit_array_recv(const StringInfo buffer);\nstatic char *bytes_store_bit_array_and_advance(char *dest, size_t expected_size,\n\t\t\t\t\t\t\t\t\t\t\t   const BitArray *array, uint32 *num_buckets,\n\t\t\t\t\t\t\t\t\t\t\t   uint8 *bits_in_last_bucket);\nstatic size_t bit_array_output(const BitArray *array, uint64 *data, size_t max_n_bytes,\n\t\t\t\t\t\t\t   uint64 *num_bits_out);\nstatic void bit_array_wrap(BitArray *dst, uint64 *data, uint64 num_bits);\n\n/* Accessors / Info */\nstatic uint64 bit_array_num_bits(const BitArray *array);\nstatic uint32 bit_array_num_buckets(const BitArray *array);\nstatic uint64 *bit_array_buckets(const BitArray *array);\nstatic size_t bit_array_data_bytes_used(const BitArray *array);\n"
  },
  {
    "path": "src/adts/bit_array_impl.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include <lib/stringinfo.h>\n#include <libpq/pqformat.h>\n\n#include \"compat/compat.h\"\n#include \"adts/uint64_vec.h\"\n\n#define BITS_PER_BUCKET 64\n\ntypedef struct BitArray\n{\n\tuint64_vec buckets;\n\tuint8 bits_used_in_last_bucket;\n} BitArray;\n\ntypedef struct BitArrayIterator\n{\n\tconst BitArray *array;\n\tuint8 bits_used_in_current_bucket;\n\t/* note that current_bucket should be signed since it sometimes gets decremented and must be\n\t * able to hold UINT32_MAX */\n\tint64 current_bucket;\n} BitArrayIterator;\n\n/************************\n ***  Private Helpers ***\n ************************/\n\nstatic void bit_array_append_bucket(BitArray *array, uint8 bits_used, uint64 bucket);\nstatic uint64 bit_array_low_bits_mask(uint8 bits_used);\n\nstatic inline void bit_array_wrap_internal(BitArray *array, uint32 num_buckets,\n\t\t\t\t\t\t\t\t\t\t   uint8 bits_used_in_last_bucket, uint64 *buckets);\n\n/************************\n ***  Implementation ***\n ************************/\n\nstatic inline void\nbit_array_init(BitArray *array, int expected_bits)\n{\n\t*array = (BitArray){\n\t\t.bits_used_in_last_bucket = 0,\n\t};\n\tuint64_vec_init(&array->buckets, CurrentMemoryContext, expected_bits / 64);\n}\n\n/* This initializes the bit array by wrapping buckets. Note, that the bit array will\n * point to buckets instead of creating a new copy. */\nstatic inline void\nbit_array_wrap_internal(BitArray *array, uint32 num_buckets, uint8 bits_used_in_last_bucket,\n\t\t\t\t\t\tuint64 *buckets)\n{\n\t*array = (BitArray){\n\t\t.bits_used_in_last_bucket = bits_used_in_last_bucket,\n\t\t.buckets =\n\t\t\t(uint64_vec){\n\t\t\t\t.data = buckets,\n\t\t\t\t.num_elements = num_buckets,\n\t\t\t\t.max_elements = num_buckets,\n\t\t\t},\n\t};\n}\n\nstatic inline size_t\nbit_array_data_bytes_used(const BitArray *array)\n{\n\treturn array->buckets.num_elements * sizeof(*array->buckets.data);\n}\n\nstatic inline uint32\nbit_array_num_buckets(const BitArray *array)\n{\n\treturn array->buckets.num_elements;\n}\n\nstatic inline uint64\nbit_array_num_bits(const BitArray *array)\n{\n\treturn (BITS_PER_BUCKET * (array->buckets.num_elements - UINT64CONST(1))) +\n\t\t   array->bits_used_in_last_bucket;\n}\n\nstatic inline uint64 *\nbit_array_buckets(const BitArray *array)\n{\n\treturn array->buckets.data;\n}\n\nstatic inline BitArray\nbit_array_recv(const StringInfo buffer)\n{\n\tuint32 i;\n\tuint32 num_elements = pq_getmsgint32(buffer);\n\tuint8 bits_used_in_last_bucket = pq_getmsgbyte(buffer);\n\tBitArray array;\n\tCheckCompressedData(num_elements <= GLOBAL_MAX_ROWS_PER_COMPRESSION);\n\tCheckCompressedData(bits_used_in_last_bucket <= BITS_PER_BUCKET);\n\n\tarray = (BitArray){\n\t\t.bits_used_in_last_bucket = bits_used_in_last_bucket,\n\t\t.buckets = {\n\t\t\t.num_elements = num_elements,\n\t\t\t.max_elements = num_elements,\n\t\t\t.ctx = CurrentMemoryContext,\n\t\t\t.data = palloc(num_elements * sizeof(uint64)),\n\t\t},\n\t};\n\n\tfor (i = 0; i < num_elements; i++)\n\t\tarray.buckets.data[i] = pq_getmsgint64(buffer);\n\n\treturn array;\n}\n\nstatic inline void\nbit_array_send(StringInfo buffer, const BitArray *data)\n{\n\tpq_sendint32(buffer, data->buckets.num_elements);\n\tpq_sendbyte(buffer, data->bits_used_in_last_bucket);\n\tfor (uint32 i = 0; i < data->buckets.num_elements; i++)\n\t\tpq_sendint64(buffer, data->buckets.data[i]);\n}\n\nstatic inline size_t\nbit_array_output(const BitArray *array, uint64 *dst, size_t max_n_bytes, uint64 *num_bits_out)\n{\n\tsize_t size = bit_array_data_bytes_used(array);\n\n\tif (max_n_bytes < size)\n\t\telog(ERROR, \"not enough memory to serialize bit array\");\n\n\tif (num_bits_out != NULL)\n\t\t*num_bits_out = bit_array_num_bits(array);\n\n\tmemcpy(dst, array->buckets.data, size);\n\treturn size;\n}\n\nstatic inline char *\nbytes_store_bit_array_and_advance(char *dest, size_t expected_size, const BitArray *array,\n\t\t\t\t\t\t\t\t  uint32 *num_buckets_out, uint8 *bits_in_last_bucket_out)\n{\n\tsize_t size = bit_array_data_bytes_used(array);\n\n\tif (expected_size != size)\n\t\telog(ERROR, \"the size to serialize does not match the  bit array\");\n\n\t*num_buckets_out = bit_array_num_buckets(array);\n\t*bits_in_last_bucket_out = array->bits_used_in_last_bucket;\n\n\tif (size > 0)\n\t{\n\t\tAssert(array->buckets.data != NULL);\n\t\tmemcpy(dest, array->buckets.data, size);\n\t}\n\treturn dest + size;\n}\n\nstatic inline void\nbit_array_wrap(BitArray *dst, uint64 *data, uint64 num_bits)\n{\n\tuint32 num_buckets = num_bits / BITS_PER_BUCKET;\n\tuint8 bits_used_in_last_bucket = num_bits % BITS_PER_BUCKET;\n\tif (bits_used_in_last_bucket == 0)\n\t{\n\t\t/* last bucket uses all bits */\n\t\tif (num_buckets > 0)\n\t\t\tbits_used_in_last_bucket = BITS_PER_BUCKET;\n\t}\n\telse\n\t\tnum_buckets += 1;\n\tbit_array_wrap_internal(dst, num_buckets, bits_used_in_last_bucket, data);\n}\n\nstatic inline void\nbit_array_append(BitArray *array, uint8 num_bits, uint64 bits)\n{\n\t/* Fill bits from LSB to MSB */\n\tuint8 bits_remaining_in_last_bucket;\n\tuint8 num_bits_for_new_bucket;\n\tuint64 bits_for_new_bucket;\n\tAssert(num_bits <= 64);\n\tif (num_bits == 0)\n\t\treturn;\n\n\tif (array->buckets.num_elements == 0)\n\t\tbit_array_append_bucket(array, 0, 0);\n\n\tbits &= bit_array_low_bits_mask(num_bits);\n\n\tbits_remaining_in_last_bucket = 64 - array->bits_used_in_last_bucket;\n\tif (bits_remaining_in_last_bucket >= num_bits)\n\t{\n\t\tuint64 *bucket = uint64_vec_last(&array->buckets);\n\t\t/* mask out any unused high bits, probably unneeded */\n\t\t*bucket |= bits << array->bits_used_in_last_bucket;\n\t\tarray->bits_used_in_last_bucket += num_bits;\n\t\treturn;\n\t}\n\n\t/* When splitting an integer across buckets, the low-order bits go into the first bucket and\n\t * the high-order bits go into the second bucket  */\n\tnum_bits_for_new_bucket = num_bits - bits_remaining_in_last_bucket;\n\tif (bits_remaining_in_last_bucket > 0)\n\t{\n\t\tuint64 bits_for_current_bucket =\n\t\t\tbits & bit_array_low_bits_mask(bits_remaining_in_last_bucket);\n\t\tuint64 *current_bucket = uint64_vec_last(&array->buckets);\n\t\t*current_bucket |= bits_for_current_bucket << array->bits_used_in_last_bucket;\n\t\tbits >>= bits_remaining_in_last_bucket;\n\t}\n\n\t/* We zero out the high bits of the new bucket, to ensure that unused bits are always 0 */\n\tbits_for_new_bucket = bits & bit_array_low_bits_mask(num_bits_for_new_bucket);\n\tbit_array_append_bucket(array, num_bits_for_new_bucket, bits_for_new_bucket);\n}\n\nstatic inline void\nbit_array_iterator_init(BitArrayIterator *iter, const BitArray *array)\n{\n\t*iter = (BitArrayIterator){\n\t\t.array = array,\n\t};\n}\n\npg_attribute_always_inline static uint64\nbit_array_iter_next(BitArrayIterator *iter, uint8 num_bits)\n{\n\tuint8 bits_remaining_in_current_bucket;\n\tuint8 num_bits_from_next_bucket;\n\tuint64 value = 0;\n\tuint64 value_from_next_bucket;\n\tCheckCompressedData(num_bits <= 64);\n\tif (num_bits == 0)\n\t\treturn 0;\n\n\tCheckCompressedData(iter->current_bucket < iter->array->buckets.num_elements);\n\n\tbits_remaining_in_current_bucket = 64 - iter->bits_used_in_current_bucket;\n\tif (bits_remaining_in_current_bucket >= num_bits)\n\t{\n\t\tvalue = *uint64_vec_get(&iter->array->buckets, iter->current_bucket);\n\t\tvalue >>= iter->bits_used_in_current_bucket;\n\t\tvalue &= bit_array_low_bits_mask(num_bits);\n\t\titer->bits_used_in_current_bucket += num_bits;\n\n\t\treturn value;\n\t}\n\n\tnum_bits_from_next_bucket = num_bits - bits_remaining_in_current_bucket;\n\tif (bits_remaining_in_current_bucket > 0)\n\t{\n\t\t/* The first bucket has the low-order bits */\n\t\tvalue = *uint64_vec_get(&iter->array->buckets, iter->current_bucket);\n\t\tvalue >>= iter->bits_used_in_current_bucket;\n\t}\n\n\t/* The second bucket has the high-order bits */\n\tCheckCompressedData(iter->current_bucket + 1 < iter->array->buckets.num_elements);\n\tvalue_from_next_bucket = *uint64_vec_get(&iter->array->buckets, iter->current_bucket + 1) &\n\t\t\t\t\t\t\t bit_array_low_bits_mask(num_bits_from_next_bucket);\n\tvalue_from_next_bucket <<= bits_remaining_in_current_bucket;\n\tvalue |= value_from_next_bucket;\n\n\titer->current_bucket += 1;\n\titer->bits_used_in_current_bucket = num_bits_from_next_bucket;\n\n\treturn value;\n}\n\nstatic inline void\nbit_array_iterator_init_rev(BitArrayIterator *iter, const BitArray *array)\n{\n\t*iter = (BitArrayIterator){\n\t\t.array = array,\n\t\t.current_bucket = array->buckets.num_elements - 1,\n\t\t.bits_used_in_current_bucket = array->bits_used_in_last_bucket,\n\t};\n}\n\nstatic inline uint64\nbit_array_iter_next_rev(BitArrayIterator *iter, uint8 num_bits)\n{\n\tuint8 bits_remaining_in_current_bucket;\n\tuint8 num_bits_from_previous_bucket;\n\tuint64 value = 0;\n\tuint64 bits_from_previous;\n\tAssert(num_bits <= BITS_PER_BUCKET);\n\tif (num_bits == 0)\n\t\treturn 0;\n\n\tAssert(iter->current_bucket >= 0);\n\n\tbits_remaining_in_current_bucket = iter->bits_used_in_current_bucket;\n\tif (bits_remaining_in_current_bucket >= num_bits)\n\t{\n\t\tvalue = *uint64_vec_get(&iter->array->buckets, iter->current_bucket);\n\t\tvalue >>= (iter->bits_used_in_current_bucket - num_bits);\n\t\tvalue &= bit_array_low_bits_mask(num_bits);\n\t\titer->bits_used_in_current_bucket -= num_bits;\n\t\treturn value;\n\t}\n\n\tAssert(iter->current_bucket - 1 >= 0);\n\n\tnum_bits_from_previous_bucket = num_bits - bits_remaining_in_current_bucket;\n\n\tAssert(num_bits <= BITS_PER_BUCKET);\n\tif (bits_remaining_in_current_bucket > 0)\n\t{\n\t\t/* The current bucket has the high-order bits (it's the second bucket) */\n\t\tvalue |= *uint64_vec_get(&iter->array->buckets, iter->current_bucket) &\n\t\t\t\t bit_array_low_bits_mask(bits_remaining_in_current_bucket);\n\t\tvalue <<= num_bits_from_previous_bucket;\n\t}\n\n\t/* The previous bucket has the low-order bits (it's the first bucket) */\n\tbits_from_previous = *uint64_vec_get(&iter->array->buckets, iter->current_bucket - 1);\n\tbits_from_previous >>= BITS_PER_BUCKET - num_bits_from_previous_bucket;\n\tbits_from_previous &= bit_array_low_bits_mask(num_bits_from_previous_bucket);\n\tvalue |= bits_from_previous;\n\n\titer->current_bucket -= 1;\n\titer->bits_used_in_current_bucket = BITS_PER_BUCKET - num_bits_from_previous_bucket;\n\treturn value;\n}\n\n/************************\n ***  Private Helpers ***\n ************************/\nstatic void\nbit_array_append_bucket(BitArray *array, uint8 bits_used, uint64 bucket)\n{\n\tuint64_vec_append(&array->buckets, bucket);\n\tarray->bits_used_in_last_bucket = bits_used;\n}\n\nstatic uint64\nbit_array_low_bits_mask(uint8 bits_used)\n{\n\tAssert(bits_used > 0);\n\treturn ~0ULL >> (64 - bits_used);\n}\n"
  },
  {
    "path": "src/adts/char_vec.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#define VEC_PREFIX char\n#define VEC_ELEMENT_TYPE char\n#define VEC_DECLARE 1\n#define VEC_DEFINE 1\n#define VEC_SCOPE static inline\n#include <adts/vec.h>\n"
  },
  {
    "path": "src/adts/uint64_vec.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#define VEC_PREFIX uint64\n#define VEC_ELEMENT_TYPE uint64\n#define VEC_DECLARE 1\n#define VEC_DEFINE 1\n#define VEC_SCOPE static inline\n#include <adts/vec.h>\n"
  },
  {
    "path": "src/adts/vec.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n/* NOTE header guard deliberately omitted, as it is valid to include this header\n * multiple times in a single file\n */\n\n/*\n *\t  To generate a vector and associated functions for a use case several\n *\t  macros have to be #define'ed before this file is included.  Including\n *\t  the file #undef's all those, so a new vectors can be generated afterwards.\n *\t  The relevant parameters are:\n *\t  - VEC_PREFIX - prefix for all symbol names generated. A prefix of 'foo'\n *\t\t    will result in vector table type 'foo_vec' and functions like\n *\t\t    'foo_vec_append'/'foo_vec_at' and so forth. This is usually the\n *          name of the stored element type\n *\t  - VEC_ELEMENT_TYPE - type of the contained elements.\n *\t  - VEC_DECLARE - if defined function prototypes and type declarations are\n *\t\t    generated\n *\t  - VEC_DEFINE - if defined function definitions are generated\n *\t  - VEC_SCOPE - in which scope (e.g. extern, static inline) do function\n *\t\t    declarations reside\n */\n\n#define VEC_MAKE_PREFIX(a) CppConcat(a, _)\n#define VEC_MAKE_NAME(name) VEC_MAKE_NAME_(VEC_MAKE_PREFIX(VEC_PREFIX), name)\n#define VEC_MAKE_NAME_(a, b) CppConcat(a, b)\n\n/* name macros for: */\n\n/* type declarations */\n#define VEC_TYPE VEC_MAKE_NAME(vec)\n\n/* function declarations */\n#define VEC_INIT VEC_MAKE_NAME(vec_init)\n#define VEC_CREATE VEC_MAKE_NAME(vec_create)\n#define VEC_FREE_DATA VEC_MAKE_NAME(vec_free_data)\n#define VEC_CLEAR VEC_MAKE_NAME(vec_clear)\n#define VEC_AT VEC_MAKE_NAME(vec_at)\n#define VEC_GET VEC_MAKE_NAME(vec_get)\n#define VEC_LAST VEC_MAKE_NAME(vec_last)\n#define VEC_APPEND VEC_MAKE_NAME(vec_append)\n#define VEC_APPEND_ARRAY VEC_MAKE_NAME(vec_append_array)\n#define VEC_APPEND_ZEROS VEC_MAKE_NAME(vec_append_zeros)\n#define VEC_DELETE VEC_MAKE_NAME(vec_delete)\n#define VEC_DELETE_RANGE VEC_MAKE_NAME(vec_delete_range)\n#define VEC_RESERVE VEC_MAKE_NAME(vec_reserve)\n#define VEC_FREE VEC_MAKE_NAME(vec_free)\n\n/* generate forward declarations necessary to use the vector */\n#ifdef VEC_DECLARE\n\n/* type definitions */\ntypedef struct VEC_TYPE\n{\n\t/* size of the elements array */\n\tuint32 max_elements;\n\t/* number of elements currently used */\n\tuint32 num_elements;\n\n\t/* the actual data */\n\tVEC_ELEMENT_TYPE *data;\n\n\t/* memory context to use for allocations */\n\tMemoryContext ctx;\n} VEC_TYPE;\n\n/* externally visible function prototypes */\nVEC_SCOPE void VEC_INIT(VEC_TYPE *vec, MemoryContext ctx, uint32 nelements);\nVEC_SCOPE VEC_TYPE *VEC_CREATE(MemoryContext ctx, uint32 nelements);\nVEC_SCOPE void VEC_FREE_DATA(VEC_TYPE *vec);\nVEC_SCOPE void VEC_CLEAR(VEC_TYPE *vec);\nVEC_SCOPE VEC_ELEMENT_TYPE *VEC_AT(VEC_TYPE *vec, uint32 index);\nVEC_SCOPE const VEC_ELEMENT_TYPE *VEC_GET(const VEC_TYPE *vec, uint32 index);\nVEC_SCOPE VEC_ELEMENT_TYPE *VEC_LAST(VEC_TYPE *vec);\nVEC_SCOPE void VEC_APPEND(VEC_TYPE *vec, VEC_ELEMENT_TYPE element);\nVEC_SCOPE VEC_ELEMENT_TYPE *VEC_APPEND_ARRAY(VEC_TYPE *vec, VEC_ELEMENT_TYPE *elements,\n\t\t\t\t\t\t\t\t\t\t\t uint32 num_elements);\nVEC_SCOPE VEC_ELEMENT_TYPE *VEC_APPEND_ZEROS(VEC_TYPE *vec, uint32 num_elements);\nVEC_SCOPE void VEC_DELETE(VEC_TYPE *vec, uint32 index);\nVEC_SCOPE void VEC_DELETE_RANGE(VEC_TYPE *vec, uint32 start, uint32 len);\nVEC_SCOPE void VEC_RESERVE(VEC_TYPE *vec, uint32 additional);\nVEC_SCOPE void VEC_FREE(VEC_TYPE *vec);\n\n#endif /* VEC_DECLARE */\n\n/* generate implementation of the vector */\n#ifdef VEC_DEFINE\n\n#include <utils/memutils.h>\n\n/*\n * Allocate space so the vector can store least `additional` new elements.\n *\n * Usually this will automatically be called by appends/inserts, when\n * necessary. But resizing to the exact input size can be advantageous\n * performance-wise, when known at some point.\n */\nVEC_SCOPE void\nVEC_RESERVE(VEC_TYPE *vec, uint32 additional)\n{\n\tuint64 num_new_elements = additional;\n\tuint64 max_element_limit = MaxAllocSize / sizeof(VEC_ELEMENT_TYPE);\n\tuint64 num_elements;\n\tuint64 num_bytes;\n\n\t/* this doesn't handle integer overflow or >MaxAllocSize allocations */\n\tif (num_new_elements == 0 || vec->num_elements + num_new_elements <= vec->max_elements)\n\t\treturn;\n\n\tnum_elements = vec->num_elements + num_new_elements;\n\tif (num_new_elements < vec->num_elements)\n\t{\n\t\t/* Follow the usual doubling progression of allocation sizes. */\n\t\tnum_elements = vec->num_elements * 2;\n\t}\n\n\tif (num_elements >= max_element_limit)\n\t{\n\t\tif (vec->num_elements + num_new_elements >= max_element_limit)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),\n\t\t\t\t\t errmsg(\"vector allocation overflow when trying to allocate %ld bytes\",\n\t\t\t\t\t\t\t(long) ((vec->num_elements + num_new_elements) *\n\t\t\t\t\t\t\t\t\tsizeof(VEC_ELEMENT_TYPE)))));\n\n\t\t/* Clamp num_elements to max allocation size if they can fit*/\n\t\tnum_elements = max_element_limit;\n\t}\n\n\tAssert(num_elements > vec->num_elements);\n\tvec->max_elements = num_elements;\n\n\tnum_bytes = vec->max_elements * sizeof(VEC_ELEMENT_TYPE);\n\tif (vec->data == NULL)\n\t\tvec->data = MemoryContextAlloc(vec->ctx, num_bytes);\n\telse\n\t\tvec->data = repalloc(vec->data, num_bytes);\n}\n\n/*\n * Initialize a vector with enough space for `nelements`. Memory is allocated\n * from the passed-in context.\n */\nVEC_SCOPE void\nVEC_INIT(VEC_TYPE *vec, MemoryContext ctx, uint32 nelements)\n{\n\t*vec = (VEC_TYPE){\n\t\t.ctx = ctx,\n\t};\n\tif (nelements > 0)\n\t\tVEC_RESERVE(vec, nelements);\n}\n\n/*\n * Create a vector with enough space for `nelements`. Memory for the vector, and\n * its elements, is allocated from the passed-in context.\n */\nVEC_SCOPE VEC_TYPE *\nVEC_CREATE(MemoryContext ctx, uint32 nelements)\n{\n\tVEC_TYPE *vec = MemoryContextAlloc(ctx, sizeof(*vec));\n\tVEC_INIT(vec, ctx, nelements);\n\treturn vec;\n}\n\n/* free the underlying array */\nVEC_SCOPE void\nVEC_FREE_DATA(VEC_TYPE *vec)\n{\n\tif (vec->data != NULL)\n\t\tpfree(vec->data);\n\t/* zero out all the vec data except the memory context so it can be reused */\n\t*vec = (VEC_TYPE){\n\t\t.ctx = vec->ctx,\n\t};\n}\n\n/* free an allocated vector, and its data */\nVEC_SCOPE void\nVEC_FREE(VEC_TYPE *vec)\n{\n\tif (vec == NULL)\n\t\treturn;\n\tVEC_FREE_DATA(vec);\n\tpfree(vec);\n}\n\n/* clear a vector, but don't free the underlying array */\nVEC_SCOPE void\nVEC_CLEAR(VEC_TYPE *vec)\n{\n\tvec->num_elements = 0;\n}\n\nVEC_SCOPE VEC_ELEMENT_TYPE *\nVEC_AT(VEC_TYPE *vec, uint32 index)\n{\n\tAssert(index < vec->num_elements);\n\treturn &vec->data[index];\n}\n\nVEC_SCOPE const VEC_ELEMENT_TYPE *\nVEC_GET(const VEC_TYPE *vec, uint32 index)\n{\n\tAssert(index < vec->num_elements);\n\treturn &vec->data[index];\n}\n\n/* return a pointer to the last element in the vector */\nVEC_SCOPE VEC_ELEMENT_TYPE *\nVEC_LAST(VEC_TYPE *vec)\n{\n\tAssert(vec->num_elements > 0);\n\treturn VEC_AT(vec, vec->num_elements - 1);\n}\n\nVEC_SCOPE VEC_ELEMENT_TYPE *\nVEC_APPEND_ARRAY(VEC_TYPE *vec, VEC_ELEMENT_TYPE *elements, uint32 num_elements)\n{\n\tVEC_ELEMENT_TYPE *first_new_element;\n\tVEC_RESERVE(vec, num_elements);\n\tAssert(vec->num_elements < vec->max_elements);\n\tfirst_new_element = vec->data + vec->num_elements;\n\tmemcpy(first_new_element, elements, sizeof(*elements) * num_elements);\n\tvec->num_elements += num_elements;\n\treturn first_new_element;\n}\n\nVEC_SCOPE void\nVEC_APPEND(VEC_TYPE *vec, VEC_ELEMENT_TYPE element)\n{\n\tVEC_APPEND_ARRAY(vec, &element, 1);\n}\n\nVEC_SCOPE VEC_ELEMENT_TYPE *\nVEC_APPEND_ZEROS(VEC_TYPE *vec, uint32 num_elements)\n{\n\tVEC_ELEMENT_TYPE *first_new_element;\n\tVEC_RESERVE(vec, num_elements);\n\tAssert(vec->num_elements + num_elements <= vec->max_elements);\n\tfirst_new_element = vec->data + vec->num_elements;\n\tmemset(first_new_element, 0, sizeof(*first_new_element) * num_elements);\n\tvec->num_elements += num_elements;\n\treturn first_new_element;\n}\n\nVEC_SCOPE void\nVEC_DELETE_RANGE(VEC_TYPE *vec, uint32 start, uint32 len)\n{\n\tif (start > vec->num_elements)\n\t\telog(ERROR, \"trying to delete starting past the end of a vector\");\n\tif (start + (uint64) len > (uint64) vec->num_elements)\n\t\telog(ERROR, \"trying to delete past the end of a vector\");\n\n\tif (start + (uint64) len < (uint64) vec->num_elements)\n\t{\n\t\t/* backshift the elements after the deletion that still remain */\n\t\tuint64 backshit_elements = vec->num_elements - (start + len);\n\t\tuint64 backshit_bytes = backshit_elements * sizeof(*vec->data);\n\t\tmemmove(&vec->data[start], &vec->data[start + len], backshit_bytes);\n\t}\n\tvec->num_elements -= len;\n}\n\nVEC_SCOPE void\nVEC_DELETE(VEC_TYPE *vec, uint32 index)\n{\n\tVEC_DELETE_RANGE(vec, index, 1);\n}\n\n#endif\n\n/* undefine external parameters, so next vector can be defined */\n#undef VEC_PREFIX\n#undef VEC_ELEMENT_TYPE\n#undef VEC_SCOPE\n#undef VEC_DECLARE\n#undef VEC_DEFINE\n\n/* undefine locally declared macros */\n#undef VEC_MAKE_PREFIX\n#undef VEC_MAKE_NAME\n#undef VEC_MAKE_NAME\n\n/* types */\n#undef VEC_TYPE\n\n/* external function names */\n#undef VEC_INIT\n#undef VEC_CREATE\n#undef VEC_FREE_DATA\n#undef VEC_CLEAR\n#undef VEC_AT\n#undef VEC_GET\n#undef VEC_LAST\n#undef VEC_APPEND\n#undef VEC_APPEND_ARRAY\n#undef VEC_APPEND_ZEROS\n#undef VEC_DELETE\n#undef VEC_DELETE_RANGE\n#undef VEC_RESERVE\n#undef VEC_FREE\n"
  },
  {
    "path": "src/agg_bookend.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/htup_details.h>\n#include <catalog/namespace.h>\n#include <catalog/pg_type.h>\n#include <fmgr.h>\n#include <lib/stringinfo.h>\n#include <libpq/pqformat.h>\n#include <nodes/value.h>\n#include <utils/datum.h>\n#include <utils/lsyscache.h>\n#include <utils/syscache.h>\n\n#include \"export.h\"\n\n/* bookend aggregates first and last:\n *\t first(value, cmp) returns the value for the row with the smallest cmp element.\n *\t last(value, cmp) returns the value for the row with the biggest cmp element.\n *\n * Usage:\n *\t SELECT first(metric, time), last(metric, time) FROM metric GROUP BY hostname.\n */\n\nTS_FUNCTION_INFO_V1(ts_first_sfunc);\nTS_FUNCTION_INFO_V1(ts_first_combinefunc);\nTS_FUNCTION_INFO_V1(ts_last_sfunc);\nTS_FUNCTION_INFO_V1(ts_last_combinefunc);\nTS_FUNCTION_INFO_V1(ts_bookend_finalfunc);\nTS_FUNCTION_INFO_V1(ts_bookend_serializefunc);\nTS_FUNCTION_INFO_V1(ts_bookend_deserializefunc);\n\n/* A  PolyDatum represents a polymorphic datum */\ntypedef struct PolyDatum\n{\n\tbool is_null;\n\tDatum datum;\n} PolyDatum;\n\ntypedef struct TypeInfoCache\n{\n\tOid typoid;\n\tint16 typlen;\n\tbool typbyval;\n} TypeInfoCache;\n\n/* PolyDatumIOState is internal state used by  polydatum_serialize and\tpolydatum_deserialize  */\ntypedef struct PolyDatumIOState\n{\n\tTypeInfoCache type;\n\n\tFmgrInfo proc;\n\tOid typeioparam;\n} PolyDatumIOState;\n\nstatic PolyDatum\npolydatum_from_arg(int argno, FunctionCallInfo fcinfo)\n{\n\tPolyDatum value;\n\n\tvalue.is_null = PG_ARGISNULL(argno);\n\tif (!value.is_null)\n\t\tvalue.datum = PG_GETARG_DATUM(argno);\n\telse\n\t\tvalue.datum = PointerGetDatum(NULL);\n\treturn value;\n}\n\n/* Serialize type as namespace name string + type name string.\n *  Don't simple send Oid since this state may be needed across pg_dumps.\n */\nstatic void\npolydatum_serialize_type(StringInfo buf, Oid type_oid)\n{\n\tHeapTuple tup;\n\tForm_pg_type type_tuple;\n\tchar *namespace_name;\n\n\ttup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));\n\tif (!HeapTupleIsValid(tup))\n\t\telog(ERROR, \"cache lookup failed for type %u\", type_oid);\n\ttype_tuple = (Form_pg_type) GETSTRUCT(tup);\n\tnamespace_name = get_namespace_name(type_tuple->typnamespace);\n\n\t/* send qualified type name */\n\tpq_sendstring(buf, namespace_name);\n\tpq_sendstring(buf, NameStr(type_tuple->typname));\n\n\tReleaseSysCache(tup);\n}\n\n/* serializes the polydatum pd unto buf */\nstatic void\npolydatum_serialize(PolyDatum *pd, StringInfo buf, PolyDatumIOState *state, FunctionCallInfo fcinfo)\n{\n\tbytea *outputbytes;\n\n\tAssert(OidIsValid(state->type.typoid));\n\tpolydatum_serialize_type(buf, state->type.typoid);\n\n\tif (pd->is_null)\n\t{\n\t\t/* emit -1 data length to signify a NULL */\n\t\tpq_sendint32(buf, -1);\n\t\treturn;\n\t}\n\n\toutputbytes = SendFunctionCall(&state->proc, pd->datum);\n\tpq_sendint32(buf, VARSIZE(outputbytes) - VARHDRSZ);\n\tpq_sendbytes(buf, VARDATA(outputbytes), VARSIZE(outputbytes) - VARHDRSZ);\n}\n\nstatic Oid\npolydatum_deserialize_type(StringInfo buf)\n{\n\tconst char *schema_name = pq_getmsgstring(buf);\n\tconst char *type_name = pq_getmsgstring(buf);\n\tOid schema_oid = LookupExplicitNamespace(schema_name, false);\n\tOid type_oid = GetSysCacheOid2(TYPENAMENSP,\n\t\t\t\t\t\t\t\t   Anum_pg_type_oid,\n\t\t\t\t\t\t\t\t   PointerGetDatum(type_name),\n\t\t\t\t\t\t\t\t   ObjectIdGetDatum(schema_oid));\n\tif (!OidIsValid(type_oid))\n\t\telog(ERROR, \"cache lookup failed for type %s.%s\", schema_name, type_name);\n\n\treturn type_oid;\n}\n\n/*\n * Deserialize the PolyDatum where the binary representation is in buf.\n * If a not-null PolyDatum is passed in, fill in it's fields, otherwise palloc.\n *\n */\nstatic PolyDatum *\npolydatum_deserialize(MemoryContext mem_ctx, PolyDatum *result, StringInfo buf,\n\t\t\t\t\t  PolyDatumIOState *state, FunctionCallInfo fcinfo)\n{\n\tint itemlen;\n\tStringInfoData item_buf;\n\tStringInfo bufptr;\n\tchar csave;\n\n\tAssert(result != NULL);\n\n\tMemoryContext old_context = MemoryContextSwitchTo(mem_ctx);\n\n\tOid deserialized_type = polydatum_deserialize_type(buf);\n\n\t/* Following is copied/adapted from record_recv in core postgres */\n\n\t/* Get and check the item length */\n\titemlen = pq_getmsgint(buf, 4);\n\tif (itemlen < -1 || itemlen > (buf->len - buf->cursor))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),\n\t\t\t\t errmsg(\"insufficient data left in message %d %d\", itemlen, buf->len)));\n\n\tif (itemlen == -1)\n\t{\n\t\t/* -1 length means NULL */\n\t\tresult->is_null = true;\n\t\tbufptr = NULL;\n\t\tcsave = 0;\n\t}\n\telse\n\t{\n\t\t/*\n\t\t * Rather than copying data around, we just set up a phony StringInfo\n\t\t * pointing to the correct portion of the input buffer. We assume we\n\t\t * can scribble on the input buffer so as to maintain the convention\n\t\t * that StringInfos have a trailing null.\n\t\t */\n\t\titem_buf.data = &buf->data[buf->cursor];\n\t\titem_buf.maxlen = itemlen + 1;\n\t\titem_buf.len = itemlen;\n\t\titem_buf.cursor = 0;\n\n\t\tbuf->cursor += itemlen;\n\n\t\tcsave = buf->data[buf->cursor];\n\t\tbuf->data[buf->cursor] = '\\0';\n\n\t\tbufptr = &item_buf;\n\t\tresult->is_null = false;\n\t}\n\n\t/* Now call the column's receiveproc */\n\tif (state->type.typoid != deserialized_type)\n\t{\n\t\tAssert(!OidIsValid(state->type.typoid));\n\n\t\tOid func;\n\t\tgetTypeBinaryInputInfo(deserialized_type, &func, &state->typeioparam);\n\t\tfmgr_info_cxt(func, &state->proc, fcinfo->flinfo->fn_mcxt);\n\t\tstate->type.typoid = deserialized_type;\n\t\tget_typlenbyval(state->type.typoid, &state->type.typlen, &state->type.typbyval);\n\t}\n\n\tresult->datum = ReceiveFunctionCall(&state->proc, bufptr, state->typeioparam, -1);\n\n\tif (bufptr)\n\t{\n\t\t/* Trouble if it didn't eat the whole buffer */\n\t\tif (item_buf.cursor != itemlen)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),\n\t\t\t\t\t errmsg(\"improper binary format in polydata\")));\n\n\t\tbuf->data[buf->cursor] = csave;\n\t}\n\n\tMemoryContextSwitchTo(old_context);\n\n\treturn result;\n}\n\ntypedef struct TransCache\n{\n\tTypeInfoCache value_type_cache;\n\tTypeInfoCache cmp_type_cache;\n\tFmgrInfo cmp_proc;\n} TransCache;\n\n/* Internal state for bookend aggregates */\ntypedef struct InternalCmpAggStore\n{\n\tTransCache aggstate_type_cache;\n\tPolyDatum value;\n\tPolyDatum cmp; /* the comparison element. e.g. time */\n} InternalCmpAggStore;\n\ninline static InternalCmpAggStore *\ninit_store(MemoryContext aggcontext)\n{\n\tInternalCmpAggStore *state =\n\t\t(InternalCmpAggStore *) MemoryContextAllocZero(aggcontext, sizeof(InternalCmpAggStore));\n\tstate->value.is_null = true;\n\tstate->cmp.is_null = true;\n\n\treturn state;\n}\n\n/* State used to cache data for serialize/deserialize operations */\ntypedef struct InternalCmpAggStoreIOState\n{\n\tPolyDatumIOState value;\n\tPolyDatumIOState cmp; /* the comparison element. e.g. time */\n} InternalCmpAggStoreIOState;\n\ninline static void\ntypeinfocache_polydatumcopy(TypeInfoCache *tic, PolyDatum input, PolyDatum *output)\n{\n\tAssert(OidIsValid(tic->typoid));\n\n\tif (!tic->typbyval && !output->is_null)\n\t{\n\t\tpfree(DatumGetPointer(output->datum));\n\t}\n\n\t*output = input;\n\n\tif (!input.is_null)\n\t{\n\t\toutput->datum = datumCopy(input.datum, tic->typbyval, tic->typlen);\n\t\toutput->is_null = false;\n\t}\n\telse\n\t{\n\t\toutput->datum = PointerGetDatum(NULL);\n\t\toutput->is_null = true;\n\t}\n}\n\ninline static void\ncmpproc_init(FunctionCallInfo fcinfo, FmgrInfo *cmp_proc, Oid type_oid, char *opname)\n{\n\tOid cmp_op, cmp_regproc;\n\n\tif (!OidIsValid(type_oid))\n\t\telog(ERROR, \"could not determine the type of the comparison_element\");\n\n\tcmp_op = OpernameGetOprid(list_make1(makeString(opname)), type_oid, type_oid);\n\tif (!OidIsValid(cmp_op))\n\t\telog(ERROR, \"could not find a %s operator for type %d\", opname, type_oid);\n\tcmp_regproc = get_opcode(cmp_op);\n\tif (!OidIsValid(cmp_regproc))\n\t\telog(ERROR,\n\t\t\t \"could not find the procedure for the %s operator for type %d\",\n\t\t\t opname,\n\t\t\t type_oid);\n\tfmgr_info_cxt(cmp_regproc, cmp_proc, fcinfo->flinfo->fn_mcxt);\n}\n\ninline static bool\ncmpproc_cmp(FmgrInfo *cmp_proc, FunctionCallInfo fcinfo, PolyDatum left, PolyDatum right)\n{\n\treturn DatumGetBool(FunctionCall2Coll(cmp_proc, fcinfo->fncollation, left.datum, right.datum));\n}\n\n/*\n * bookend_sfunc - internal function called by ts_last_sfunc and ts_first_sfunc;\n */\nstatic inline Datum\nbookend_sfunc(MemoryContext aggcontext, InternalCmpAggStore *state, char *opname,\n\t\t\t  FunctionCallInfo fcinfo)\n{\n\tPolyDatum value = polydatum_from_arg(1, fcinfo);\n\tPolyDatum cmp = polydatum_from_arg(2, fcinfo);\n\n\tMemoryContext old_context;\n\n\told_context = MemoryContextSwitchTo(aggcontext);\n\n\tif (state == NULL)\n\t{\n\t\tstate = init_store(aggcontext);\n\t\tTransCache *cache = &state->aggstate_type_cache;\n\n\t\tTypeInfoCache *v = &cache->value_type_cache;\n\t\tv->typoid = get_fn_expr_argtype(fcinfo->flinfo, 1);\n\t\tget_typlenbyval(v->typoid, &v->typlen, &v->typbyval);\n\n\t\tTypeInfoCache *c = &cache->cmp_type_cache;\n\t\tc->typoid = get_fn_expr_argtype(fcinfo->flinfo, 2);\n\t\tget_typlenbyval(c->typoid, &c->typlen, &c->typbyval);\n\n\t\ttypeinfocache_polydatumcopy(&cache->value_type_cache, value, &state->value);\n\t\ttypeinfocache_polydatumcopy(&cache->cmp_type_cache, cmp, &state->cmp);\n\t}\n\telse if (!cmp.is_null)\n\t{\n\t\tTransCache *cache = &state->aggstate_type_cache;\n\n\t\tif (cache->cmp_proc.fn_addr == NULL)\n\t\t{\n\t\t\tcmpproc_init(fcinfo, &cache->cmp_proc, cache->cmp_type_cache.typoid, opname);\n\t\t}\n\n\t\t/* only do comparison if cmp is not NULL */\n\t\tif (state->cmp.is_null || cmpproc_cmp(&cache->cmp_proc, fcinfo, cmp, state->cmp))\n\t\t{\n\t\t\ttypeinfocache_polydatumcopy(&cache->value_type_cache, value, &state->value);\n\t\t\ttypeinfocache_polydatumcopy(&cache->cmp_type_cache, cmp, &state->cmp);\n\t\t}\n\t}\n\tMemoryContextSwitchTo(old_context);\n\n\tPG_RETURN_POINTER(state);\n}\n\n/* bookend_combinefunc - internal function called by ts_last_combinefunc and ts_first_combinefunc;\n * fmgr args are: (internal internal_state, internal2 internal_state)\n */\nstatic inline Datum\nbookend_combinefunc(MemoryContext aggcontext, InternalCmpAggStore *state1,\n\t\t\t\t\tInternalCmpAggStore *state2, char *opname, FunctionCallInfo fcinfo)\n{\n\tMemoryContext old_context;\n\n\tif (state2 == NULL)\n\t\tPG_RETURN_POINTER(state1);\n\n\t/*\n\t * manually copy all fields from state2 to state1, as per other combine\n\t * func like int8_avg_combine\n\t */\n\tif (state1 == NULL)\n\t{\n\t\told_context = MemoryContextSwitchTo(aggcontext);\n\n\t\tstate1 = init_store(aggcontext);\n\t\tAssert(OidIsValid(state2->aggstate_type_cache.value_type_cache.typoid));\n\t\tAssert(OidIsValid(state2->aggstate_type_cache.cmp_type_cache.typoid));\n\t\tTransCache *cache1 = &state1->aggstate_type_cache;\n\t\tTransCache *cache2 = &state2->aggstate_type_cache;\n\t\t/*\n\t\t * Initialize the type information from the right-hand state. Note that\n\t\t * we will have to re-lookup the comparison procedure on demand, because\n\t\t * the comparison procedure from the right-hand state might have been\n\t\t * allocated in a different memory context.\n\t\t */\n\t\tcache1->value_type_cache = cache2->value_type_cache;\n\t\tcache1->cmp_type_cache = cache2->cmp_type_cache;\n\n\t\ttypeinfocache_polydatumcopy(&cache1->value_type_cache, state2->value, &state1->value);\n\t\ttypeinfocache_polydatumcopy(&cache1->cmp_type_cache, state2->cmp, &state1->cmp);\n\n\t\tMemoryContextSwitchTo(old_context);\n\t\tPG_RETURN_POINTER(state1);\n\t}\n\n\tif (state1->cmp.is_null && state2->cmp.is_null)\n\t{\n\t\tPG_RETURN_POINTER(state1);\n\t}\n\telse if (state1->cmp.is_null != state2->cmp.is_null)\n\t{\n\t\tif (state1->cmp.is_null)\n\t\t\tPG_RETURN_POINTER(state2);\n\t\telse\n\t\t\tPG_RETURN_POINTER(state1);\n\t}\n\n\tTransCache *cache1 = &state1->aggstate_type_cache;\n\tif (cache1->cmp_proc.fn_addr == NULL)\n\t{\n\t\tcmpproc_init(fcinfo, &cache1->cmp_proc, cache1->cmp_type_cache.typoid, opname);\n\t}\n\tif (cmpproc_cmp(&cache1->cmp_proc, fcinfo, state2->cmp, state1->cmp))\n\t{\n\t\told_context = MemoryContextSwitchTo(aggcontext);\n\t\ttypeinfocache_polydatumcopy(&cache1->value_type_cache, state2->value, &state1->value);\n\t\ttypeinfocache_polydatumcopy(&cache1->cmp_type_cache, state2->cmp, &state1->cmp);\n\t\tMemoryContextSwitchTo(old_context);\n\t}\n\n\tPG_RETURN_POINTER(state1);\n}\n\n/* first(internal internal_state, anyelement value, \"any\" comparison_element) */\nDatum\nts_first_sfunc(PG_FUNCTION_ARGS)\n{\n\tInternalCmpAggStore *store =\n\t\tPG_ARGISNULL(0) ? NULL : (InternalCmpAggStore *) PG_GETARG_POINTER(0);\n\tMemoryContext aggcontext;\n\n\tif (!AggCheckCallContext(fcinfo, &aggcontext))\n\t{\n\t\t/* cannot be called directly because of internal-type argument */\n\t\telog(ERROR, \"first_sfun called in non-aggregate context\");\n\t}\n\n\treturn bookend_sfunc(aggcontext, store, \"<\", fcinfo);\n}\n\n/* last(internal internal_state, anyelement value, \"any\" comparison_element) */\nDatum\nts_last_sfunc(PG_FUNCTION_ARGS)\n{\n\tInternalCmpAggStore *store =\n\t\tPG_ARGISNULL(0) ? NULL : (InternalCmpAggStore *) PG_GETARG_POINTER(0);\n\tMemoryContext aggcontext;\n\n\tif (!AggCheckCallContext(fcinfo, &aggcontext))\n\t{\n\t\t/* cannot be called directly because of internal-type argument */\n\t\telog(ERROR, \"last_sfun called in non-aggregate context\");\n\t}\n\n\treturn bookend_sfunc(aggcontext, store, \">\", fcinfo);\n}\n\n/* first_combinerfunc(internal, internal) => internal */\nDatum\nts_first_combinefunc(PG_FUNCTION_ARGS)\n{\n\tMemoryContext aggcontext;\n\tInternalCmpAggStore *state1 =\n\t\tPG_ARGISNULL(0) ? NULL : (InternalCmpAggStore *) PG_GETARG_POINTER(0);\n\tInternalCmpAggStore *state2 =\n\t\tPG_ARGISNULL(1) ? NULL : (InternalCmpAggStore *) PG_GETARG_POINTER(1);\n\n\tif (!AggCheckCallContext(fcinfo, &aggcontext))\n\t{\n\t\t/* cannot be called directly because of internal-type argument */\n\t\telog(ERROR, \"ts_first_combinefunc called in non-aggregate context\");\n\t}\n\treturn bookend_combinefunc(aggcontext, state1, state2, \"<\", fcinfo);\n}\n\n/* last_combinerfunc(internal, internal) => internal */\nDatum\nts_last_combinefunc(PG_FUNCTION_ARGS)\n{\n\tMemoryContext aggcontext;\n\tInternalCmpAggStore *state1 =\n\t\tPG_ARGISNULL(0) ? NULL : (InternalCmpAggStore *) PG_GETARG_POINTER(0);\n\tInternalCmpAggStore *state2 =\n\t\tPG_ARGISNULL(1) ? NULL : (InternalCmpAggStore *) PG_GETARG_POINTER(1);\n\n\tif (!AggCheckCallContext(fcinfo, &aggcontext))\n\t{\n\t\t/* cannot be called directly because of internal-type argument */\n\t\telog(ERROR, \"ts_last_combinefunc called in non-aggregate context\");\n\t}\n\treturn bookend_combinefunc(aggcontext, state1, state2, \">\", fcinfo);\n}\n\n/* ts_bookend_serializefunc(internal) => bytea */\nDatum\nts_bookend_serializefunc(PG_FUNCTION_ARGS)\n{\n\tStringInfoData buf;\n\tInternalCmpAggStoreIOState *my_extra;\n\tInternalCmpAggStore *state;\n\n\tAssert(!PG_ARGISNULL(0));\n\tstate = (InternalCmpAggStore *) PG_GETARG_POINTER(0);\n\n\tmy_extra = (InternalCmpAggStoreIOState *) fcinfo->flinfo->fn_extra;\n\tif (my_extra == NULL)\n\t{\n\t\tfcinfo->flinfo->fn_extra =\n\t\t\tMemoryContextAllocZero(fcinfo->flinfo->fn_mcxt, sizeof(InternalCmpAggStoreIOState));\n\t\tmy_extra = (InternalCmpAggStoreIOState *) fcinfo->flinfo->fn_extra;\n\n\t\tOid func;\n\t\tbool is_varlena;\n\n\t\tmy_extra->value.type = state->aggstate_type_cache.value_type_cache;\n\t\tAssert(OidIsValid(my_extra->value.type.typoid));\n\n\t\tgetTypeBinaryOutputInfo(my_extra->value.type.typoid, &func, &is_varlena);\n\t\tfmgr_info_cxt(func, &my_extra->value.proc, fcinfo->flinfo->fn_mcxt);\n\n\t\tmy_extra->cmp.type = state->aggstate_type_cache.cmp_type_cache;\n\t\tAssert(OidIsValid(my_extra->cmp.type.typoid));\n\n\t\tgetTypeBinaryOutputInfo(my_extra->cmp.type.typoid, &func, &is_varlena);\n\t\tfmgr_info_cxt(func, &my_extra->cmp.proc, fcinfo->flinfo->fn_mcxt);\n\t}\n\tpq_begintypsend(&buf);\n\tpolydatum_serialize(&state->value, &buf, &my_extra->value, fcinfo);\n\tpolydatum_serialize(&state->cmp, &buf, &my_extra->cmp, fcinfo);\n\tPG_RETURN_BYTEA_P(pq_endtypsend(&buf));\n}\n\n/* ts_bookend_deserializefunc(bytea, internal) => internal */\nDatum\nts_bookend_deserializefunc(PG_FUNCTION_ARGS)\n{\n\tMemoryContext aggcontext;\n\tbytea *sstate;\n\tStringInfoData buf;\n\tInternalCmpAggStore *result;\n\tInternalCmpAggStoreIOState *my_extra;\n\n\tif (!AggCheckCallContext(fcinfo, &aggcontext))\n\t\telog(ERROR, \"aggregate function called in non-aggregate context\");\n\n\tsstate = PG_GETARG_BYTEA_P(0);\n\n\t/*\n\t * Copy the bytea into a StringInfo so that we can \"receive\" it using the\n\t * standard recv-function infrastructure.\n\t */\n\tinitStringInfo(&buf);\n\tappendBinaryStringInfo(&buf, VARDATA(sstate), VARSIZE(sstate) - VARHDRSZ);\n\n\tmy_extra = (InternalCmpAggStoreIOState *) fcinfo->flinfo->fn_extra;\n\tif (my_extra == NULL)\n\t{\n\t\tfcinfo->flinfo->fn_extra =\n\t\t\tMemoryContextAllocZero(fcinfo->flinfo->fn_mcxt, sizeof(InternalCmpAggStoreIOState));\n\t\tmy_extra = (InternalCmpAggStoreIOState *) fcinfo->flinfo->fn_extra;\n\t}\n\n\tresult = MemoryContextAllocZero(aggcontext, sizeof(InternalCmpAggStore));\n\tpolydatum_deserialize(aggcontext, &result->value, &buf, &my_extra->value, fcinfo);\n\tpolydatum_deserialize(aggcontext, &result->cmp, &buf, &my_extra->cmp, fcinfo);\n\n\tresult->aggstate_type_cache.value_type_cache = my_extra->value.type;\n\tresult->aggstate_type_cache.cmp_type_cache = my_extra->cmp.type;\n\n\tPG_RETURN_POINTER(result);\n}\n\n/* ts_bookend_finalfunc(internal, anyelement, \"any\") => anyelement */\nDatum\nts_bookend_finalfunc(PG_FUNCTION_ARGS)\n{\n\tInternalCmpAggStore *state;\n\n\tif (!AggCheckCallContext(fcinfo, NULL))\n\t{\n\t\t/* cannot be called directly because of internal-type argument */\n\t\telog(ERROR, \"ts_bookend_finalfunc called in non-aggregate context\");\n\t}\n\n\tstate = PG_ARGISNULL(0) ? NULL : (InternalCmpAggStore *) PG_GETARG_POINTER(0);\n\n\tif (state == NULL || state->value.is_null || state->cmp.is_null)\n\t\tPG_RETURN_NULL();\n\n\tPG_RETURN_DATUM(state->value.datum);\n}\n"
  },
  {
    "path": "src/annotations.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n/* Supported since clang 12 and GCC 7 */\n#if defined __has_attribute\n#if __has_attribute(fallthrough)\n#define TS_FALLTHROUGH __attribute__((fallthrough))\n#else\n#define TS_FALLTHROUGH /* FALLTHROUGH */\n#endif\n#else\n#define TS_FALLTHROUGH /* FALLTHROUGH */\n#endif\n\n#ifdef __has_attribute\n#if __has_attribute(used)\n#define TS_USED __attribute__((used))\n#else\n#define TS_USED\n#endif\n#else\n#define TS_USED\n#endif\n"
  },
  {
    "path": "src/bgw/CMakeLists.txt",
    "content": "set(SOURCES\n    ${CMAKE_CURRENT_SOURCE_DIR}/job.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/job_stat.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/job_stat_history.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/launcher_interface.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/scheduler.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/timer.c)\ntarget_sources(${PROJECT_NAME} PRIVATE ${SOURCES})\n"
  },
  {
    "path": "src/bgw/README.md",
    "content": "# Background worker jobs\n\nTimescaleDB needs to run multiple background jobs. This module\nimplements a simple scheduler so that jobs inserted into a jobs table\ncan be run on a schedule. Each database in an instance runs it's own\nscheduler because different databases may run different TimescaleDB\nextension versions which may require different scheduler logic.\n\n## Schedules\n\nThe scheduler allows you to set a `schedule_interval` for every job.\nThat defines the interval the scheduler will wait after a job finishes\nto start it again, if the job is successful. If the job fails, the\nscheduler uses `retry_period` in an exponential backoff to decide when\nto run the job again.\n\n## Design\n\nThe scheduler itself is a background job that continuously runs and waits\nfor a time when jobs need to be scheduled. It then launches jobs as new\nbackground workers that it controls through the background worker handle.\n\nAggregate statistics about a job are kept in the job stat catalog\ntable.  These statistics include the start and finish times of the\nlast run of the job as well as whether or not the job succeeded. The\n`next_start` is used to figure out when next to run a job after a\nscheduler is restarted.\n\nThe statistics table also tracks consecutive failures and crashes for\nthe job which are used for calculating the exponential backoff after a\ncrash or failure (which is used to set the `next_start` after the\ncrash/failure). Note also that there is a minimum time after the\ndatabase scheduler starts up and a crashed job is restarted. This is\nto allow the operator enough time to disable the job if needed.\n\nNote that the number of crashes is an overestimate of the actual\nnumber of crashes for a job. This is so that we are conservative and\nnever miss a crash and fail to use the appropriate backoff\nlogic. There is some complexity in ensuring that all crashes are\ncounted. A crash in Postgres causes *all* processes to quit\nimmediately therefore we cannot write anything to the database once\nany process has crashed. Thus, we must be able to deduce that a crash\noccurred from a commit that happened before any crash. We accomplish\nthis by committing a changes to the stats table before a job starts\nand undoing the change after it finishes. If a job crashed, it will be\nleft in an intermediate state from which we deduce that it could have\nbeen the crashing process.\n\n## Scheduler State Machine\n\nThe scheduler implements a state machine for each job.  Each job\nstarts in the `SCHEDULED` state. As soon as a job starts it enters the\n`STARTING` state. If the scheduler determines the job should be\nterminated (e.g. it has reached a timeout), it moves the job to a\nTERMINATING state. Once a background worker has for a job has stopped,\nthe job returns to the `SCHEDULED` state.  The states and associated\ntransitions are as follows.\n\n```ditaa\n      +---------+         +--------+\n+---> |SCHEDULED+-------> |DISABLED|\n|     +----+----+         +--------+\n|          |\n|          |\n|          v\n|      +---+----+\n+<-----+STARTING|\n|      +---+----+\n|          |\n|          |\n|          v\n|      +---+-------+\n+<-----+TERMINATING|\n       +-----------+\n```\n\n## Limitations\n\nThis first implementation has two limitations:\n\n- The list of jobs to be run is read from the database when the\n  scheduler is first started. We do not update this list if the jobs\n  table changes.\n- There is no prioritization for when to run jobs.\n\n"
  },
  {
    "path": "src/bgw/job.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n\n#include <unistd.h>\n#include <access/xact.h>\n#include <catalog/pg_authid.h>\n#include <executor/execdebug.h>\n#include <executor/instrument.h>\n#include <miscadmin.h>\n#include <nodes/makefuncs.h>\n#include <parser/parse_func.h>\n#include <parser/parser.h>\n#include <pgstat.h>\n#include <postmaster/bgworker.h>\n#include <storage/ipc.h>\n#include <storage/lock.h>\n#include <tcop/tcopprot.h>\n#include <utils/acl.h>\n#include <utils/backend_status.h>\n#include <utils/builtins.h>\n#include <utils/elog.h>\n#include <utils/jsonb.h>\n#include <utils/lsyscache.h>\n#include <utils/memutils.h>\n#include <utils/snapmgr.h>\n#include <utils/syscache.h>\n#include <utils/timestamp.h>\n\n#include \"compat/compat.h\"\n#include \"bgw/job_stat_history.h\"\n#include \"bgw/scheduler.h\"\n#include \"bgw_policy/chunk_stats.h\"\n#include \"bgw_policy/policy.h\"\n#include \"config.h\"\n#include \"cross_module_fn.h\"\n#include \"debug_assert.h\"\n#include \"debug_point.h\"\n#include \"extension.h\"\n#include \"job.h\"\n#include \"job_stat.h\"\n#include \"license_guc.h\"\n#include \"scan_iterator.h\"\n#include \"scanner.h\"\n#include \"tss_callbacks.h\"\n#include \"utils.h\"\n\n#ifdef USE_TELEMETRY\n#include \"telemetry/telemetry.h\"\n#endif\n\nstatic scheduler_test_hook_type scheduler_test_hook = NULL;\nstatic char *job_entrypoint_function_name = \"ts_bgw_job_entrypoint\";\n\n/*\n * Get the mem_guard callbacks.\n *\n * You might get a NULL pointer back if there are no mem_guard installed, so\n * check before using.\n */\nMGCallbacks *\nts_get_mem_guard_callbacks(void)\n{\n\tstatic MGCallbacks **mem_guard_callback_ptr = NULL;\n\n\tif (mem_guard_callback_ptr)\n\t\treturn *mem_guard_callback_ptr;\n\n\tmem_guard_callback_ptr = (MGCallbacks **) find_rendezvous_variable(MG_CALLBACKS_VAR_NAME);\n\n\treturn *mem_guard_callback_ptr;\n}\n\nBackgroundWorkerHandle *\nts_bgw_job_start(BgwJob *job, Oid user_oid)\n{\n\tBgwParams bgw_params = {\n\t\t.job_id = Int32GetDatum(job->fd.id),\n\t\t.job_history_id = job->job_history.id,\n\t\t.job_history_execution_start = job->job_history.execution_start,\n\t\t.user_oid = user_oid,\n\t};\n\n\tstrlcpy(bgw_params.bgw_main, job_entrypoint_function_name, sizeof(bgw_params.bgw_main));\n\n\treturn ts_bgw_start_worker(NameStr(job->fd.application_name), &bgw_params);\n}\n\nstatic void\njob_execute_function(FuncExpr *funcexpr)\n{\n\tbool isnull;\n\n\tEState *estate = CreateExecutorState();\n\tExprContext *econtext = CreateExprContext(estate);\n\n\tExprState *es = ExecPrepareExpr((Expr *) funcexpr, estate);\n\tExecEvalExpr(es, econtext, &isnull);\n\tFreeExprContext(econtext, true);\n\tFreeExecutorState(estate);\n}\n\n/**\n * Run configuration check validation function.\n *\n * This will run the configuration check validation function registered for\n * the job. If a new job is added, the job_id is going to be zero.\n */\nvoid\nts_bgw_job_run_config_check(Oid check, int32 job_id, Jsonb *config)\n{\n\t/* Nothing to check if there is no check function provided */\n\tif (!OidIsValid(check))\n\t\treturn;\n\n\t/* NULL config may be valid */\n\tConst *arg;\n\tif (config == NULL)\n\t\targ = makeNullConst(JSONBOID, -1, InvalidOid);\n\telse\n\t\targ = makeConst(JSONBOID, -1, InvalidOid, -1, JsonbPGetDatum(config), false, false);\n\n\tList *args = list_make1(arg);\n\tFuncExpr *funcexpr =\n\t\tmakeFuncExpr(check, VOIDOID, args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL);\n\n\tif (get_func_prokind(check) == PROKIND_FUNCTION)\n\t\tjob_execute_function(funcexpr);\n\telse\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"unsupported function type\"),\n\t\t\t\t errdetail(\"Only functions are allowed as custom configuration checks\"),\n\t\t\t\t errhint(\"Use a FUNCTION instead\")));\n}\n\n/* Run the check function on a configuration. It will generate errors if there\n * is anything wrong with the configuration, otherwise just return. If the\n * check function does not exist, no checking will be done.*/\nstatic void\njob_config_check(BgwJob *job, Jsonb *config)\n{\n\tOid proc;\n\tList *funcname;\n\n\t/* Both should either be empty or contain a schema and name */\n\tAssert((strlen(NameStr(job->fd.check_schema)) == 0) ==\n\t\t   (strlen(NameStr(job->fd.check_schema)) == 0));\n\n\t/* If there is no function, just return */\n\tif (strlen(NameStr(job->fd.check_name)) == 0)\n\t\treturn;\n\n\tfuncname = list_make2(makeString(NameStr(job->fd.check_schema)),\n\t\t\t\t\t\t  makeString(NameStr(job->fd.check_name)));\n\n\tOid argtypes[] = { JSONBOID };\n\t/* Only functions allowed as custom checks, as procedures can cause errors with COMMIT\n\t * statements */\n\tproc = LookupFuncName(funcname, 1, argtypes, true);\n\n\t/* a check function has been registered but it can't be found anymore\n\t because it was dropped or renamed. Allow alter_job to run if that's the case\n\t without validating the config but also print a warning */\n\tif (OidIsValid(proc))\n\t\tts_bgw_job_run_config_check(proc, job->fd.id, config);\n\telse\n\t\telog(WARNING,\n\t\t\t \"function %s.%s(config jsonb) not found, skipping config validation for \"\n\t\t\t \"job %d\",\n\t\t\t NameStr(job->fd.check_schema),\n\t\t\t NameStr(job->fd.check_name),\n\t\t\t job->fd.id);\n}\n\nstatic BgwJob *\nbgw_job_from_tupleinfo(TupleInfo *ti, size_t alloc_size)\n{\n\tBgwJob *job;\n\tbool should_free;\n\tHeapTuple tuple;\n\tMemoryContext old_ctx;\n\tDatum values[Natts_bgw_job] = { 0 };\n\tbool nulls[Natts_bgw_job] = { false };\n\n\t/*\n\t * allow for embedding with arbitrary alloc_size, which means we can't use\n\t * the STRUCT_FROM_TUPLE macro\n\t */\n\tAssert(alloc_size >= sizeof(BgwJob));\n\tjob = MemoryContextAllocZero(ti->mctx, alloc_size);\n\ttuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\n\told_ctx = MemoryContextSwitchTo(ti->mctx);\n\n\t/*\n\t * Using heap_deform_tuple instead of GETSTRUCT since the tuple can\n\t * contain NULL values. Some of these cannot really be null, but we check\n\t * anyway since it is cheap and will avoid problems in the future. Note\n\t * that the job structure is zeroed, so we only need to update the field\n\t * if it is non-NULL.\n\t */\n\theap_deform_tuple(tuple, ts_scanner_get_tupledesc(ti), values, nulls);\n\n\tif (!nulls[AttrNumberGetAttrOffset(Anum_bgw_job_id)])\n\t\tjob->fd.id = DatumGetInt32(values[AttrNumberGetAttrOffset(Anum_bgw_job_id)]);\n\tif (!nulls[AttrNumberGetAttrOffset(Anum_bgw_job_application_name)])\n\t\tnamestrcpy(&job->fd.application_name,\n\t\t\t\t   DatumGetCString(values[AttrNumberGetAttrOffset(Anum_bgw_job_application_name)]));\n\tif (!nulls[AttrNumberGetAttrOffset(Anum_bgw_job_schedule_interval)])\n\t\tjob->fd.schedule_interval =\n\t\t\t*DatumGetIntervalP(values[AttrNumberGetAttrOffset(Anum_bgw_job_schedule_interval)]);\n\n\tif (!nulls[AttrNumberGetAttrOffset(Anum_bgw_job_max_runtime)])\n\t\tjob->fd.max_runtime =\n\t\t\t*DatumGetIntervalP(values[AttrNumberGetAttrOffset(Anum_bgw_job_max_runtime)]);\n\n\tif (!nulls[AttrNumberGetAttrOffset(Anum_bgw_job_max_retries)])\n\t\tjob->fd.max_retries =\n\t\t\tDatumGetInt32(values[AttrNumberGetAttrOffset(Anum_bgw_job_max_retries)]);\n\n\tif (!nulls[AttrNumberGetAttrOffset(Anum_bgw_job_fixed_schedule)])\n\t\tjob->fd.fixed_schedule =\n\t\t\tDatumGetBool(values[AttrNumberGetAttrOffset(Anum_bgw_job_fixed_schedule)]);\n\n\tif (!nulls[AttrNumberGetAttrOffset(Anum_bgw_job_initial_start)])\n\t{\n\t\tjob->fd.initial_start =\n\t\t\tDatumGetTimestampTz(values[AttrNumberGetAttrOffset(Anum_bgw_job_initial_start)]);\n\t}\n\telse\n\t\tjob->fd.initial_start = DT_NOBEGIN;\n\n\tif (!nulls[AttrNumberGetAttrOffset(Anum_bgw_job_timezone)])\n\t\tjob->fd.timezone =\n\t\t\tDatumGetTextPCopy(values[AttrNumberGetAttrOffset(Anum_bgw_job_timezone)]);\n\n\tif (!nulls[AttrNumberGetAttrOffset(Anum_bgw_job_retry_period)])\n\t\tjob->fd.retry_period =\n\t\t\t*DatumGetIntervalP(values[AttrNumberGetAttrOffset(Anum_bgw_job_retry_period)]);\n\n\tif (!nulls[AttrNumberGetAttrOffset(Anum_bgw_job_proc_schema)])\n\t\tnamestrcpy(&job->fd.proc_schema,\n\t\t\t\t   DatumGetCString(values[AttrNumberGetAttrOffset(Anum_bgw_job_proc_schema)]));\n\tif (!nulls[AttrNumberGetAttrOffset(Anum_bgw_job_proc_name)])\n\t\tnamestrcpy(&job->fd.proc_name,\n\t\t\t\t   DatumGetCString(values[AttrNumberGetAttrOffset(Anum_bgw_job_proc_name)]));\n\tif (!nulls[AttrNumberGetAttrOffset(Anum_bgw_job_check_schema)])\n\t\tnamestrcpy(&job->fd.check_schema,\n\t\t\t\t   DatumGetCString(values[AttrNumberGetAttrOffset(Anum_bgw_job_check_schema)]));\n\tif (!nulls[AttrNumberGetAttrOffset(Anum_bgw_job_check_name)])\n\t\tnamestrcpy(&job->fd.check_name,\n\t\t\t\t   DatumGetCString(values[AttrNumberGetAttrOffset(Anum_bgw_job_check_name)]));\n\tif (!nulls[AttrNumberGetAttrOffset(Anum_bgw_job_owner)])\n\t\tjob->fd.owner = DatumGetObjectId(values[AttrNumberGetAttrOffset(Anum_bgw_job_owner)]);\n\n\tif (!nulls[AttrNumberGetAttrOffset(Anum_bgw_job_scheduled)])\n\t\tjob->fd.scheduled = DatumGetBool(values[AttrNumberGetAttrOffset(Anum_bgw_job_scheduled)]);\n\n\tif (!nulls[AttrNumberGetAttrOffset(Anum_bgw_job_hypertable_id)])\n\t\tjob->fd.hypertable_id =\n\t\t\tDatumGetInt32(values[AttrNumberGetAttrOffset(Anum_bgw_job_hypertable_id)]);\n\n\tif (!nulls[AttrNumberGetAttrOffset(Anum_bgw_job_config)])\n\t\tjob->fd.config = DatumGetJsonbPCopy(values[AttrNumberGetAttrOffset(Anum_bgw_job_config)]);\n\n\tMemoryContextSwitchTo(old_ctx);\n\n\tif (should_free)\n\t\theap_freetuple(tuple);\n\n\treturn job;\n}\n\ntypedef struct AccumData\n{\n\tList *list;\n\tsize_t alloc_size;\n} AccumData;\n\nstatic ScanTupleResult\nbgw_job_accum_tuple_found(TupleInfo *ti, void *data)\n{\n\tAccumData *list_data = data;\n\tBgwJob *job = bgw_job_from_tupleinfo(ti, list_data->alloc_size);\n\tMemoryContext orig = MemoryContextSwitchTo(ti->mctx);\n\n\tlist_data->list = lappend(list_data->list, job);\n\n\tMemoryContextSwitchTo(orig);\n\treturn SCAN_CONTINUE;\n}\n\nstatic ScanFilterResult\nbgw_job_filter_scheduled(const TupleInfo *ti, void *data)\n{\n\tbool isnull;\n\tDatum scheduled = slot_getattr(ti->slot, Anum_bgw_job_scheduled, &isnull);\n\tEnsure(!isnull, \"scheduled column was null\");\n\n\treturn DatumGetBool(scheduled);\n}\n\n/* This function is meant to be used by the scheduler only\n * it does not include the config field which saves us from\n * detoasting and makes memory management in the scheduler\n * simpler as otherwise the config field would have to be\n * freed separately when freeing jobs which would prevent\n * the use of list_free_deep.\n * The scheduler does not need the config field only the\n * individual jobs do.\n * The scheduler requires jobs to be sorted by id\n * which is guaranteed by the index scan on the primary key\n */\nList *\nts_bgw_job_get_scheduled(size_t alloc_size, MemoryContext mctx)\n{\n\tMemoryContext old_ctx;\n\tList *jobs = NIL;\n\tScanIterator iterator =\n\t\tts_scan_iterator_create_with_catalog_snapshot(BGW_JOB, AccessShareLock, mctx);\n\n\titerator.ctx.index = catalog_get_index(ts_catalog_get(), BGW_JOB, BGW_JOB_PKEY_IDX);\n\titerator.ctx.filter = bgw_job_filter_scheduled;\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tbool should_free, isnull, initial_start_isnull, timezone_isnull;\n\t\tDatum value, initial_start, timezone;\n\n\t\tBgwJob *job = MemoryContextAllocZero(mctx, alloc_size);\n\t\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\n\t\t/*\n\t\t * Note that the nullable columns might have variable width, so we\n\t\t * handle them below. We can only use memcpy for the non-nullable fixed\n\t\t * width starting part of the BgwJob struct.\n\t\t */\n\t\tmemcpy(job, GETSTRUCT(tuple), offsetof(FormData_bgw_job, initial_start));\n\n\t\tif (should_free)\n\t\t\theap_freetuple(tuple);\n\n#ifdef USE_TELEMETRY\n\t\t/* ignore telemetry jobs if telemetry is disabled */\n\t\tif (!ts_telemetry_on() && ts_is_telemetry_job(job))\n\t\t{\n\t\t\tpfree(job);\n\t\t\tcontinue;\n\t\t}\n#endif\n\t\t/* handle NULL columns */\n\t\tinitial_start = slot_getattr(ti->slot, Anum_bgw_job_initial_start, &initial_start_isnull);\n\t\tif (!initial_start_isnull)\n\t\t\tjob->fd.initial_start = DatumGetTimestampTz(initial_start);\n\t\telse\n\t\t\tjob->fd.initial_start = DT_NOBEGIN;\n\n\t\tvalue = slot_getattr(ti->slot, Anum_bgw_job_hypertable_id, &isnull);\n\t\tjob->fd.hypertable_id = isnull ? 0 : DatumGetInt32(value);\n\n\t\t/* We skip config, check_name, and check_schema since the scheduler\n\t\t * doesn't need these, it saves us from detoasting, and simplifies\n\t\t * freeing job lists in the scheduler as otherwise the config field\n\t\t * would have to be freed separately when freeing a job. */\n\t\tjob->fd.config = NULL;\n\n\t\told_ctx = MemoryContextSwitchTo(mctx);\n\n\t\ttimezone = slot_getattr(ti->slot, Anum_bgw_job_timezone, &timezone_isnull);\n\t\tif (!timezone_isnull)\n\t\t{\n\t\t\t/* We use DatumGetTextPCopy to move the detoasted value into our memory context */\n\t\t\tjob->fd.timezone = DatumGetTextPCopy(timezone);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tjob->fd.timezone = NULL;\n\t\t}\n\n\t\tjobs = lappend(jobs, job);\n\t\tMemoryContextSwitchTo(old_ctx);\n\t}\n\n\treturn jobs;\n}\n\nList *\nts_bgw_job_get_all(size_t alloc_size, MemoryContext mctx)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tAccumData list_data = {\n\t\t.list = NIL,\n\t\t.alloc_size = sizeof(BgwJob),\n\t};\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, BGW_JOB),\n\t\t.data = &list_data,\n\t\t.tuple_found = bgw_job_accum_tuple_found,\n\t\t.lockmode = AccessShareLock,\n\t\t.result_mctx = mctx,\n\t\t.scandirection = ForwardScanDirection,\n\t\t.use_catalog_snapshot = true,\n\t};\n\n\tts_scanner_scan(&scanctx);\n\treturn list_data.list;\n}\n\nstatic void\ninit_scan_by_proc_name(ScanKeyData *scankey, const char *proc_name)\n{\n\tScanKeyInit(scankey,\n\t\t\t\tAnum_bgw_job_proc_hypertable_id_idx_proc_name,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_NAMEEQ,\n\t\t\t\tCStringGetDatum(proc_name));\n}\n\nstatic void\ninit_scan_by_proc_schema(ScanKeyData *scankey, const char *proc_schema)\n{\n\tScanKeyInit(scankey,\n\t\t\t\tAnum_bgw_job_proc_hypertable_id_idx_proc_schema,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_NAMEEQ,\n\t\t\t\tCStringGetDatum(proc_schema));\n}\n\nstatic void\ninit_scan_by_hypertable_id(ScanKeyData *scankey, int32 hypertable_id)\n{\n\tScanKeyInit(scankey,\n\t\t\t\tAnum_bgw_job_proc_hypertable_id_idx_hypertable_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(hypertable_id));\n}\n\nList *\nts_bgw_job_find_by_proc_and_hypertable_id(const char *proc_name, const char *proc_schema,\n\t\t\t\t\t\t\t\t\t\t  int32 hypertable_id)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tScanKeyData scankey[3];\n\tAccumData list_data = {\n\t\t.list = NIL,\n\t\t.alloc_size = sizeof(BgwJob),\n\t};\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, BGW_JOB),\n\t\t.index = catalog_get_index(ts_catalog_get(), BGW_JOB, BGW_JOB_PROC_HYPERTABLE_ID_IDX),\n\t\t.data = &list_data,\n\t\t.scankey = scankey,\n\t\t.nkeys = sizeof(scankey) / sizeof(*scankey),\n\t\t.tuple_found = bgw_job_accum_tuple_found,\n\t\t.lockmode = AccessShareLock,\n\t\t.scandirection = ForwardScanDirection,\n\t\t.use_catalog_snapshot = true,\n\t};\n\n\tinit_scan_by_proc_schema(&scankey[0], proc_schema);\n\tinit_scan_by_proc_name(&scankey[1], proc_name);\n\tinit_scan_by_hypertable_id(&scankey[2], hypertable_id);\n\n\tts_scanner_scan(&scanctx);\n\treturn list_data.list;\n}\n\nList *\nts_bgw_job_find_by_hypertable_id(int32 hypertable_id)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tScanKeyData scankey[1];\n\tAccumData list_data = {\n\t\t.list = NIL,\n\t\t.alloc_size = sizeof(BgwJob),\n\t};\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, BGW_JOB),\n\t\t.index = catalog_get_index(ts_catalog_get(), BGW_JOB, BGW_JOB_PROC_HYPERTABLE_ID_IDX),\n\t\t.data = &list_data,\n\t\t.scankey = scankey,\n\t\t.nkeys = sizeof(scankey) / sizeof(*scankey),\n\t\t.tuple_found = bgw_job_accum_tuple_found,\n\t\t.lockmode = AccessShareLock,\n\t\t.scandirection = ForwardScanDirection,\n\t\t.use_catalog_snapshot = true,\n\t};\n\n\tinit_scan_by_hypertable_id(&scankey[0], hypertable_id);\n\n\tts_scanner_scan(&scanctx);\n\treturn list_data.list;\n}\n\nstatic void\ninit_scan_by_job_id(ScanIterator *iterator, int32 job_id)\n{\n\titerator->ctx.index = catalog_get_index(ts_catalog_get(), BGW_JOB, BGW_JOB_PKEY_IDX);\n\tts_scan_iterator_scan_key_init(iterator,\n\t\t\t\t\t\t\t\t   Anum_bgw_job_pkey_idx_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(job_id));\n}\n\nBgwJob *\nts_bgw_job_find(int32 bgw_job_id, MemoryContext mctx, bool fail_if_not_found)\n{\n\tScanIterator iterator =\n\t\tts_scan_iterator_create_with_catalog_snapshot(BGW_JOB, AccessShareLock, mctx);\n\tint num_found = 0;\n\tBgwJob *job = NULL;\n\n\tinit_scan_by_job_id(&iterator, bgw_job_id);\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tAssert(num_found == 0);\n\t\tjob = bgw_job_from_tupleinfo(ts_scan_iterator_tuple_info(&iterator), sizeof(BgwJob));\n\t\tnum_found++;\n\t\tDEBUG_WAITPOINT(\"bgw_job_find_during_scan\");\n\t}\n\n\tif (num_found == 0 && fail_if_not_found)\n\t\tereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg(\"job %d not found\", bgw_job_id)));\n\n\treturn job;\n}\n\nstatic ScanTupleResult\nbgw_job_tuple_delete(TupleInfo *ti, void *data)\n{\n\tCatalogSecurityContext sec_ctx;\n\tbool isnull_job_id;\n\tDatum datum = slot_getattr(ti->slot, Anum_bgw_job_id, &isnull_job_id);\n\tint32 job_id = DatumGetInt32(datum);\n\n\tEnsure(!isnull_job_id, \"job id was null\");\n\n\t/* Also delete the bgw_stat entry */\n\tts_bgw_job_stat_delete(job_id);\n\n\t/* Delete any stats in bgw_policy_chunk_stats related to this job */\n\tts_bgw_policy_chunk_stats_delete_row_only_by_job_id(job_id);\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_delete_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\tts_catalog_restore_user(&sec_ctx);\n\n\treturn SCAN_CONTINUE;\n}\n\nstatic bool\nbgw_job_delete_scan(ScanKeyData *scankey, int32 job_id)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tScannerCtx scanctx;\n\n\tscanctx = (ScannerCtx){\n\t\t.table = catalog_get_table_id(catalog, BGW_JOB),\n\t\t.index = catalog_get_index(catalog, BGW_JOB, BGW_JOB_PKEY_IDX),\n\t\t.nkeys = 1,\n\t\t.scankey = scankey,\n\t\t.data = NULL,\n\t\t.limit = 1,\n\t\t.tuple_found = bgw_job_tuple_delete,\n\t\t.lockmode = RowExclusiveLock,\n\t\t.scandirection = ForwardScanDirection,\n\t\t.result_mctx = CurrentMemoryContext,\n\t\t.use_catalog_snapshot = true,\n\t};\n\n\treturn ts_scanner_scan(&scanctx);\n}\n\n/*\n * Cancel the background worker running the given job, identified by its\n * application_name. This is best-effort: if the worker is not found or\n * already exited, we simply do nothing.\n */\nstatic void\ncancel_worker_for_job(const char *appname)\n{\n\tconst int num_backends = pgstat_fetch_stat_numbackends();\n\tfor (int i = 1; i <= num_backends; i++)\n\t{\n\t\tconst LocalPgBackendStatus *local_beentry = pgstat_get_local_beentry_by_index_compat(i);\n\t\tconst PgBackendStatus *beentry = &local_beentry->backendStatus;\n\t\tif (beentry->st_databaseid == MyDatabaseId && beentry->st_appname[0] != '\\0' &&\n\t\t\tstrcmp(beentry->st_appname, appname) == 0)\n\t\t{\n\t\t\tDirectFunctionCall1(pg_cancel_backend, Int32GetDatum(beentry->st_procpid));\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/*\n * Delete the job identified by `job_id`. If the job is currently running,\n * we send SIGINT to the worker for prompt cancellation.\n */\nbool\nts_bgw_job_delete_by_id(int32 job_id)\n{\n\tScanKeyData scankey[1];\n\tNameData appname = { .data = { 0 } };\n\tbool result;\n\n\t/* Look up the job's application_name before deleting */\n\tBgwJob *job = ts_bgw_job_find(job_id, CurrentMemoryContext, false);\n\tif (job != NULL)\n\t{\n\t\tnamestrcpy(&appname, NameStr(job->fd.application_name));\n\t\tpfree(job);\n\t}\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_bgw_job_pkey_idx_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(job_id));\n\n\tresult = bgw_job_delete_scan(scankey, job_id);\n\n\t/* Send SIGINT to the running worker for prompt cancellation */\n\tif (result && NameStr(appname)[0] != '\\0')\n\t\tcancel_worker_for_job(NameStr(appname));\n\n\treturn result;\n}\n\n/* This function only updates the fields modifiable with alter_job. */\nstatic ScanTupleResult\nbgw_job_tuple_update_by_id(TupleInfo *ti, void *const data)\n{\n\tBgwJob *updated_job = (BgwJob *) data;\n\tbool should_free;\n\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\tHeapTuple new_tuple;\n\n\tDatum values[Natts_bgw_job] = { 0 };\n\tbool isnull[Natts_bgw_job] = { 0 };\n\tbool doReplace[Natts_bgw_job] = { 0 };\n\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_application_name)] =\n\t\tNameGetDatum(&updated_job->fd.application_name);\n\tdoReplace[AttrNumberGetAttrOffset(Anum_bgw_job_application_name)] = true;\n\n\tDatum old_schedule_interval =\n\t\tslot_getattr(ti->slot, Anum_bgw_job_schedule_interval, &isnull[0]);\n\tAssert(!isnull[0]);\n\n\t/* when we update the schedule interval, modify the next start time as well*/\n\tif (!DatumGetBool(DirectFunctionCall2(interval_eq,\n\t\t\t\t\t\t\t\t\t\t  old_schedule_interval,\n\t\t\t\t\t\t\t\t\t\t  IntervalPGetDatum(&updated_job->fd.schedule_interval))))\n\t{\n\t\tBgwJobStat *stat = ts_bgw_job_stat_find(updated_job->fd.id);\n\n\t\tif (stat != NULL)\n\t\t{\n\t\t\tTimestampTz next_start = DatumGetTimestampTz(\n\t\t\t\tDirectFunctionCall2(timestamptz_pl_interval,\n\t\t\t\t\t\t\t\t\tTimestampTzGetDatum(stat->fd.last_finish),\n\t\t\t\t\t\t\t\t\tIntervalPGetDatum(&updated_job->fd.schedule_interval)));\n\t\t\t/* allow DT_NOBEGIN for next_start here through allow_unset=true in the case that\n\t\t\t * last_finish is DT_NOBEGIN,\n\t\t\t * This means the value is counted as unset which is what we want */\n\t\t\tts_bgw_job_stat_update_next_start(updated_job->fd.id, next_start, true);\n\t\t}\n\t\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_schedule_interval)] =\n\t\t\tIntervalPGetDatum(&updated_job->fd.schedule_interval);\n\t\tdoReplace[AttrNumberGetAttrOffset(Anum_bgw_job_schedule_interval)] = true;\n\t}\n\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_max_runtime)] =\n\t\tIntervalPGetDatum(&updated_job->fd.max_runtime);\n\tdoReplace[AttrNumberGetAttrOffset(Anum_bgw_job_max_runtime)] = true;\n\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_max_retries)] =\n\t\tInt32GetDatum(updated_job->fd.max_retries);\n\tdoReplace[AttrNumberGetAttrOffset(Anum_bgw_job_max_retries)] = true;\n\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_retry_period)] =\n\t\tIntervalPGetDatum(&updated_job->fd.retry_period);\n\tdoReplace[AttrNumberGetAttrOffset(Anum_bgw_job_retry_period)] = true;\n\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_scheduled)] =\n\t\tBoolGetDatum(updated_job->fd.scheduled);\n\tdoReplace[AttrNumberGetAttrOffset(Anum_bgw_job_scheduled)] = true;\n\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_fixed_schedule)] =\n\t\tBoolGetDatum(updated_job->fd.fixed_schedule);\n\tdoReplace[AttrNumberGetAttrOffset(Anum_bgw_job_fixed_schedule)] = true;\n\n\tdoReplace[AttrNumberGetAttrOffset(Anum_bgw_job_config)] = true;\n\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_check_schema)] =\n\t\tNameGetDatum(&updated_job->fd.check_schema);\n\tdoReplace[AttrNumberGetAttrOffset(Anum_bgw_job_check_schema)] = true;\n\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_check_name)] =\n\t\tNameGetDatum(&updated_job->fd.check_name);\n\tdoReplace[AttrNumberGetAttrOffset(Anum_bgw_job_check_name)] = true;\n\n\tif (strlen(NameStr(updated_job->fd.check_name)) == 0)\n\t{\n\t\tisnull[AttrNumberGetAttrOffset(Anum_bgw_job_check_name)] = true;\n\t\tisnull[AttrNumberGetAttrOffset(Anum_bgw_job_check_schema)] = true;\n\t}\n\n\tif (updated_job->fd.config)\n\t{\n\t\tjob_config_check(updated_job, updated_job->fd.config);\n\t\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_config)] =\n\t\t\tJsonbPGetDatum(updated_job->fd.config);\n\t}\n\telse\n\t\tisnull[AttrNumberGetAttrOffset(Anum_bgw_job_config)] = true;\n\n\tif (updated_job->fd.hypertable_id != INVALID_HYPERTABLE_ID)\n\t{\n\t\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_hypertable_id)] =\n\t\t\tInt32GetDatum(updated_job->fd.hypertable_id);\n\t\tdoReplace[AttrNumberGetAttrOffset(Anum_bgw_job_hypertable_id)] = true;\n\t}\n\telse\n\t\tisnull[AttrNumberGetAttrOffset(Anum_bgw_job_hypertable_id)] = true;\n\n\tif (TIMESTAMP_NOT_FINITE(updated_job->fd.initial_start))\n\t\tisnull[AttrNumberGetAttrOffset(Anum_bgw_job_initial_start)] = true;\n\telse\n\t\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_initial_start)] =\n\t\t\tTimestampTzGetDatum(updated_job->fd.initial_start);\n\tdoReplace[AttrNumberGetAttrOffset(Anum_bgw_job_initial_start)] = true;\n\n\tif (updated_job->fd.timezone)\n\t{\n\t\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_timezone)] =\n\t\t\tPointerGetDatum(updated_job->fd.timezone);\n\t}\n\telse\n\t\tisnull[AttrNumberGetAttrOffset(Anum_bgw_job_timezone)] = true;\n\tdoReplace[AttrNumberGetAttrOffset(Anum_bgw_job_timezone)] = true;\n\n\tnew_tuple = heap_modify_tuple(tuple, ts_scanner_get_tupledesc(ti), values, isnull, doReplace);\n\n\tts_catalog_update(ti->scanrel, new_tuple);\n\n\theap_freetuple(new_tuple);\n\tif (should_free)\n\t\theap_freetuple(tuple);\n\n\treturn SCAN_DONE;\n}\n\n/*\n * Overwrite job with specified job_id with the given fields\n *\n * This function only updates the fields modifiable with alter_job.\n */\nbool\nts_bgw_job_update_by_id(int32 job_id, BgwJob *job)\n{\n\tScanKeyData scankey[1];\n\tCatalog *catalog = ts_catalog_get();\n\tScanTupLock scantuplock = {\n\t\t.waitpolicy = LockWaitBlock,\n\t\t.lockmode = LockTupleExclusive,\n\t};\n\tScannerCtx scanctx = { .table = catalog_get_table_id(catalog, BGW_JOB),\n\t\t\t\t\t\t   .index = catalog_get_index(catalog, BGW_JOB, BGW_JOB_PKEY_IDX),\n\t\t\t\t\t\t   .nkeys = 1,\n\t\t\t\t\t\t   .scankey = scankey,\n\t\t\t\t\t\t   .data = job,\n\t\t\t\t\t\t   .limit = 1,\n\t\t\t\t\t\t   .tuple_found = bgw_job_tuple_update_by_id,\n\t\t\t\t\t\t   .lockmode = RowExclusiveLock,\n\t\t\t\t\t\t   .scandirection = ForwardScanDirection,\n\t\t\t\t\t\t   .result_mctx = CurrentMemoryContext,\n\t\t\t\t\t\t   .tuplock = &scantuplock,\n\t\t\t\t\t\t   .use_catalog_snapshot = true };\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_bgw_job_pkey_idx_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(job_id));\n\n\treturn ts_scanner_scan(&scanctx);\n}\n\nstatic void\nts_bgw_job_check_max_retries(BgwJob *job)\n{\n\tBgwJobStat *job_stat;\n\n\tjob_stat = ts_bgw_job_stat_find(job->fd.id);\n\n\t/* stop to execute failing jobs after reached the \"max_retries\" option */\n\tif (job->fd.max_retries >= 0 && job_stat->fd.consecutive_failures >= job->fd.max_retries)\n\t{\n\t\tereport(WARNING,\n\t\t\t\t(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),\n\t\t\t\t errmsg(\"job %d reached max_retries after %d consecutive failures\",\n\t\t\t\t\t\tjob->fd.id,\n\t\t\t\t\t\tjob_stat->fd.consecutive_failures),\n\t\t\t\t errdetail(\"Job %d unscheduled as max_retries reached %d, consecutive failures %d.\",\n\t\t\t\t\t\t   job->fd.id,\n\t\t\t\t\t\t   job->fd.max_retries,\n\t\t\t\t\t\t   job_stat->fd.consecutive_failures),\n\t\t\t\t errhint(\"Use alter_job(%d, scheduled => TRUE) SQL function to reschedule.\",\n\t\t\t\t\t\t job->fd.id)));\n\n\t\tif (job->fd.scheduled)\n\t\t{\n\t\t\tjob->fd.scheduled = false;\n\t\t\tts_bgw_job_update_by_id(job->fd.id, job);\n\t\t}\n\t}\n}\n\nvoid\nts_bgw_job_permission_check(BgwJob *job, const char *cmd)\n{\n\tif (!has_privs_of_role(GetUserId(), job->fd.owner))\n\t{\n\t\tconst char *owner_name = GetUserNameFromId(job->fd.owner, false);\n\t\tconst char *user_name = GetUserNameFromId(GetUserId(), false);\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),\n\t\t\t\t errmsg(\"insufficient permissions to %s job %d\", cmd, job->fd.id),\n\t\t\t\t errdetail(\"Job %d is owned by role \\\"%s\\\" but user \\\"%s\\\" does not belong to that \"\n\t\t\t\t\t\t   \"role.\",\n\t\t\t\t\t\t   job->fd.id,\n\t\t\t\t\t\t   owner_name,\n\t\t\t\t\t\t   user_name)));\n\t}\n}\n\nvoid\nts_bgw_job_validate_job_owner(Oid owner)\n{\n\tHeapTuple role_tup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(owner));\n\n\tif (!HeapTupleIsValid(role_tup))\n\t\telog(ERROR, \"cache lookup failed for role %u\", owner);\n\n\tForm_pg_authid rform = (Form_pg_authid) GETSTRUCT(role_tup);\n\n\tif (!rform->rolcanlogin)\n\t{\n\t\tReleaseSysCache(role_tup);\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),\n\t\t\t\t errmsg(\"permission denied to start background process as role \\\"%s\\\"\",\n\t\t\t\t\t\tNameStr(rform->rolname)),\n\t\t\t\t errhint(\"Hypertable owner must have LOGIN permission to run background tasks.\")));\n\t}\n\tReleaseSysCache(role_tup);\n}\n\n/*\n * Is the job the telemetry job?\n */\n#ifdef USE_TELEMETRY\nbool\nts_is_telemetry_job(BgwJob *job)\n{\n\treturn namestrcmp(&job->fd.proc_schema, FUNCTIONS_SCHEMA_NAME) == 0 &&\n\t\t   namestrcmp(&job->fd.proc_name, \"policy_telemetry\") == 0;\n}\n#endif\n\nJobResult\nts_bgw_job_execute(BgwJob *job)\n{\n#ifdef USE_TELEMETRY\n\t/* The telemetry job has a separate code path since we want to be able to\n\t * use telemetry even if the TSL code is not installed. */\n\tif (ts_is_telemetry_job(job))\n\t{\n\t\t/*\n\t\t * In the first 12 hours, we want telemetry to ping every\n\t\t * hour. After that initial period, we default to the\n\t\t * schedule_interval listed in the job table.\n\t\t */\n\t\tInterval one_hour = { .time = 1 * USECS_PER_HOUR };\n\t\treturn ts_bgw_job_run_and_set_next_start(job,\n\t\t\t\t\t\t\t\t\t\t\t\t ts_telemetry_main_wrapper,\n\t\t\t\t\t\t\t\t\t\t\t\t TELEMETRY_INITIAL_NUM_RUNS,\n\t\t\t\t\t\t\t\t\t\t\t\t &one_hour,\n\t\t\t\t\t\t\t\t\t\t\t\t /* atomic */ true,\n\t\t\t\t\t\t\t\t\t\t\t\t /* mark */ false);\n\t}\n#endif\n\n#ifdef TS_DEBUG\n\tif (scheduler_test_hook != NULL)\n\t\treturn scheduler_test_hook(job);\n#endif\n\n\treturn ts_cm_functions->job_execute(job);\n}\n\nbool\nts_bgw_job_has_timeout(BgwJob *job)\n{\n\tInterval zero_val = {\n\t\t.time = 0,\n\t};\n\n\treturn DatumGetBool(DirectFunctionCall2(interval_gt,\n\t\t\t\t\t\t\t\t\t\t\tIntervalPGetDatum(&job->fd.max_runtime),\n\t\t\t\t\t\t\t\t\t\t\tIntervalPGetDatum(&zero_val)));\n}\n\n/* Return the timestamp at which to kill the job due to a timeout */\nTimestampTz\nts_bgw_job_timeout_at(BgwJob *job, TimestampTz start_time)\n{\n\t/* timestamptz plus interval */\n\treturn DatumGetTimestampTz(DirectFunctionCall2(timestamptz_pl_interval,\n\t\t\t\t\t\t\t\t\t\t\t\t   TimestampTzGetDatum(start_time),\n\t\t\t\t\t\t\t\t\t\t\t\t   IntervalPGetDatum(&job->fd.max_runtime)));\n}\n\nTS_FUNCTION_INFO_V1(ts_bgw_job_entrypoint);\n\nstatic void\nzero_guc(const char *guc_name)\n{\n\tint config_change =\n\t\tset_config_option(guc_name, \"0\", PGC_SUSET, PGC_S_SESSION, GUC_ACTION_SET, true, 0, false);\n\n\tif (config_change == 0)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR), errmsg(\"guc \\\"%s\\\" does not exist\", guc_name)));\n\telse if (config_change < 0)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR), errmsg(\"could not set \\\"%s\\\" guc\", guc_name)));\n}\n\nOid\nts_bgw_job_get_funcid(BgwJob *job)\n{\n\tObjectWithArgs *object = makeNode(ObjectWithArgs);\n\tobject->objname = list_make2(makeString(NameStr(job->fd.proc_schema)),\n\t\t\t\t\t\t\t\t makeString(NameStr(job->fd.proc_name)));\n\tobject->objargs = list_make2(SystemTypeName(\"int4\"), SystemTypeName(\"jsonb\"));\n\n\t/* Return InvalidOid if don't found */\n\treturn LookupFuncWithArgs(OBJECT_ROUTINE, object, true);\n}\n\nconst char *\nts_bgw_job_function_call_string(BgwJob *job)\n{\n\tOid funcid = ts_bgw_job_get_funcid(job);\n\t/* If do not found the function or procedure then fallback to PROKIND_FUNCTION */\n\tchar prokind = OidIsValid(funcid) ? get_func_prokind(funcid) : PROKIND_FUNCTION;\n\tStringInfoData stmt;\n\tinitStringInfo(&stmt);\n\tchar *jsonb_str = \"NULL\";\n\n\tif (job->fd.config)\n\t\tjsonb_str = quote_literal_cstr(\n\t\t\tJsonbToCString(NULL, &job->fd.config->root, VARSIZE(job->fd.config)));\n\n\tswitch (prokind)\n\t{\n\t\tcase PROKIND_FUNCTION:\n\t\t\tappendStringInfo(&stmt,\n\t\t\t\t\t\t\t \"SELECT %s.%s('%d', %s)\",\n\t\t\t\t\t\t\t quote_identifier(NameStr(job->fd.proc_schema)),\n\t\t\t\t\t\t\t quote_identifier(NameStr(job->fd.proc_name)),\n\t\t\t\t\t\t\t job->fd.id,\n\t\t\t\t\t\t\t jsonb_str);\n\t\t\tbreak;\n\t\tcase PROKIND_PROCEDURE:\n\t\t\tappendStringInfo(&stmt,\n\t\t\t\t\t\t\t \"CALL %s.%s('%d', %s)\",\n\t\t\t\t\t\t\t quote_identifier(NameStr(job->fd.proc_schema)),\n\t\t\t\t\t\t\t quote_identifier(NameStr(job->fd.proc_name)),\n\t\t\t\t\t\t\t job->fd.id,\n\t\t\t\t\t\t\t jsonb_str);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_WRONG_OBJECT_TYPE),\n\t\t\t\t\t errmsg(\"unsupported function type: %c\", prokind)));\n\t\t\tbreak;\n\t}\n\n\treturn stmt.data;\n}\n\nextern Datum\nts_bgw_job_entrypoint(PG_FUNCTION_ARGS)\n{\n\tOid db_oid = DatumGetObjectId(MyBgworkerEntry->bgw_main_arg);\n\tBgwParams params;\n\tBgwJob *job;\n\tJobResult volatile res = JOB_FAILURE_IN_EXECUTION;\n\tinstr_time start;\n\tinstr_time duration;\n\n\tmemcpy(&params, MyBgworkerEntry->bgw_extra, sizeof(BgwParams));\n\tEnsure(OidIsValid(params.user_oid) && params.job_id != 0,\n\t\t   \"job id or user oid was zero - job_id: %d, user_oid: %d\",\n\t\t   params.job_id,\n\t\t   params.user_oid);\n\n\tBackgroundWorkerBlockSignals();\n\t/* Setup any signal handlers here */\n\n\t/*\n\t * do not use the default `bgworker_die` sigterm handler because it does\n\t * not respect critical sections\n\t */\n\tpqsignal(SIGTERM, die);\n\tBackgroundWorkerUnblockSignals();\n\n\t/*\n\t * Set up mem_guard before starting to allocate (any significant amounts\n\t * of) memory but after we have unblocked signals since we have no control\n\t * over how the callback behaves.\n\t */\n\tMGCallbacks *callbacks = ts_get_mem_guard_callbacks();\n\tif (callbacks && callbacks->version_num == MG_CALLBACKS_VERSION &&\n\t\tcallbacks->toggle_allocation_blocking && !callbacks->enabled)\n\t\tcallbacks->toggle_allocation_blocking(/*enable=*/true);\n\n\tBackgroundWorkerInitializeConnectionByOid(db_oid, params.user_oid, 0);\n\n\tlog_min_messages = ts_guc_bgw_log_level;\n\n\telog(DEBUG2, \"job %d started execution\", params.job_id);\n\n\tts_license_enable_module_loading();\n\n\tINSTR_TIME_SET_CURRENT(start);\n\n\tStartTransactionCommand();\n\tPushActiveSnapshot(GetTransactionSnapshot());\n\n\tjob = ts_bgw_job_find(params.job_id, TopMemoryContext, false);\n\tif (job == NULL)\n\t\t/* If the job is not found, we can't proceed */\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t errmsg(\"job %d not found when running the background worker\", params.job_id)));\n\n\t/* get parameters from bgworker */\n\tjob->job_history.id = params.job_history_id;\n\tjob->job_history.execution_start = params.job_history_execution_start;\n\tts_bgw_job_stat_history_update(JOB_STAT_HISTORY_UPDATE_PID, job, JOB_SUCCESS, NULL);\n\n\tPopActiveSnapshot();\n\tCommitTransactionCommand();\n\n\telog(DEBUG2, \"job %d (%s) found\", params.job_id, NameStr(job->fd.application_name));\n\n\tpgstat_report_appname(NameStr(job->fd.application_name));\n\tMemoryContext oldcontext = CurrentMemoryContext;\n\n\tbool job_failed = false;\n\tif (scheduler_test_hook == NULL)\n\t\tts_begin_tss_store_callback();\n\n\tPG_TRY();\n\t{\n\t\t/*\n\t\t * we do not necessarily have a valid parallel worker context in\n\t\t * background workers, so disable parallel execution by default\n\t\t */\n\t\tzero_guc(\"max_parallel_workers_per_gather\");\n\t\tzero_guc(\"max_parallel_workers\");\n\t\tzero_guc(\"max_parallel_maintenance_workers\");\n\n\t\tres = ts_bgw_job_execute(job);\n\n\t\t/* The job is responsible for committing or aborting it's own txns */\n\t\tif (IsTransactionState())\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_TRANSACTION_STATE),\n\t\t\t\t\t errmsg(\"TimescaleDB background job \\\"%s\\\" failed to end the transaction\",\n\t\t\t\t\t\t\tNameStr(job->fd.application_name))));\n\t}\n\tPG_CATCH();\n\t{\n\t\tErrorData *edata;\n\t\tNameData proc_schema = { .data = { 0 } }, proc_name = { .data = { 0 } };\n\n\t\tif (IsTransactionState())\n\t\t\t/* If there was an error, rollback what was done before the error */\n\t\t\tAbortCurrentTransaction();\n\t\tStartTransactionCommand();\n\t\tPushActiveSnapshot(GetTransactionSnapshot());\n\n\t\t/* Free the old job if it exists, it's no longer needed, and since it's\n\t\t * in the TopMemoryContext it won't be freed otherwise.\n\t\t */\n\n\t\tif (job != NULL)\n\t\t{\n\t\t\tpfree(job);\n\t\t}\n\n\t\t/* switch away from error context to not lose the data */\n\t\tMemoryContextSwitchTo(oldcontext);\n\t\tjob_failed = true;\n\t\tedata = CopyErrorData();\n\t\tFlushErrorState();\n\n\t\t/*\n\t\t * Note that the mark_start happens in the scheduler right before the\n\t\t * job is launched.\n\t\t */\n\t\tjob = ts_bgw_job_find(params.job_id, TopMemoryContext, false);\n\t\tif (job != NULL)\n\t\t{\n\t\t\tnamestrcpy(&proc_name, NameStr(job->fd.proc_name));\n\t\t\tnamestrcpy(&proc_schema, NameStr(job->fd.proc_schema));\n\n\t\t\tjob->job_history.id = params.job_history_id;\n\t\t\tjob->job_history.execution_start = params.job_history_execution_start;\n\n\t\t\tts_bgw_job_stat_mark_end(job,\n\t\t\t\t\t\t\t\t\t JOB_FAILURE_IN_EXECUTION,\n\t\t\t\t\t\t\t\t\t ts_errdata_to_jsonb(edata, &proc_schema, &proc_name));\n\t\t\tts_bgw_job_check_max_retries(job);\n\t\t\tpfree(job);\n\t\t}\n\n\t\t/*\n\t\t * the rethrow will log the error; but also log which job threw the\n\t\t * error\n\t\t */\n\t\telog(LOG, \"job %d threw an error\", params.job_id);\n\n\t\tPopActiveSnapshot();\n\t\tCommitTransactionCommand();\n\t\tReThrowError(edata);\n\t}\n\tPG_END_TRY();\n\n\tAssert(!IsTransactionState());\n\n\tStartTransactionCommand();\n\tPushActiveSnapshot(GetTransactionSnapshot());\n\n\t/*\n\t * Note that the mark_start happens in the scheduler right before the job\n\t * is launched\n\t */\n\tts_bgw_job_stat_mark_end(job, res, NULL);\n\n\tif (!job_failed && ts_is_tss_enabled() && scheduler_test_hook == NULL)\n\t{\n\t\tconst char *stmt = ts_bgw_job_function_call_string(job);\n\t\tts_end_tss_store_callback(stmt, -1, (int) strlen(stmt), 0, 0);\n\t}\n\n\tPopActiveSnapshot();\n\tCommitTransactionCommand();\n\n\tINSTR_TIME_SET_CURRENT(duration);\n\tINSTR_TIME_SUBTRACT(duration, start);\n\n\telog(DEBUG1,\n\t\t \"job %d (%s) exiting with %s: execution time %.2f ms\",\n\t\t params.job_id,\n\t\t NameStr(job->fd.application_name),\n\t\t (res == JOB_SUCCESS ? \"success\" : \"failure\"),\n\t\t INSTR_TIME_GET_MILLISEC(duration));\n\n\tif (job != NULL)\n\t{\n\t\tpfree(job);\n\t\tjob = NULL;\n\t}\n\n\tPG_RETURN_VOID();\n}\n\nvoid\nts_bgw_job_set_scheduler_test_hook(scheduler_test_hook_type hook)\n{\n\tscheduler_test_hook = hook;\n}\n\nvoid\nts_bgw_job_set_job_entrypoint_function_name(char *func_name)\n{\n\tjob_entrypoint_function_name = func_name;\n}\n\n/*\n * Run job and set next start.\n *\n * job: Job to run and update\n * func: Function to execute for the job\n * initial_runs: Limit on the number of runs to do\n * next_interval: Interval to use when computing next start\n * atomic: Should be executed as a single transaction.\n * mark: Mark the start and end of the function execution in job_stats\n */\nbool\nts_bgw_job_run_and_set_next_start(BgwJob *job, job_main_func func, int64 initial_runs,\n\t\t\t\t\t\t\t\t  Interval *next_interval, bool atomic, bool mark)\n{\n\tBgwJobStat *job_stat;\n\tbool result;\n\n\tif (atomic)\n\t\tStartTransactionCommand();\n\n\tif (mark)\n\t\tts_bgw_job_stat_mark_start(job);\n\n\tresult = func();\n\n\tif (mark)\n\t\tts_bgw_job_stat_mark_end(job, result ? JOB_SUCCESS : JOB_FAILURE_IN_EXECUTION, NULL);\n\n\t/* Now update next_start. */\n\tjob_stat = ts_bgw_job_stat_find(job->fd.id);\n\n\t/*\n\t * Note that setting next_start explicitly from this function will\n\t * override any backoff calculation due to failure.\n\t */\n\tEnsure(job_stat != NULL, \"job status for job %d not found\", job->fd.id);\n\tif (job_stat->fd.total_runs < initial_runs)\n\t{\n\t\tTimestampTz next_start =\n\t\t\tDatumGetTimestampTz(DirectFunctionCall2(timestamptz_pl_interval,\n\t\t\t\t\t\t\t\t\t\t\t\t\tTimestampTzGetDatum(job_stat->fd.last_start),\n\t\t\t\t\t\t\t\t\t\t\t\t\tIntervalPGetDatum(next_interval)));\n\n\t\tts_bgw_job_stat_set_next_start(job->fd.id, next_start);\n\t}\n\n\tif (atomic)\n\t\tCommitTransactionCommand();\n\n\treturn result;\n}\n\n/* Insert a new job in the bgw_job relation */\nint\nts_bgw_job_insert_relation(Name application_name, Interval *schedule_interval,\n\t\t\t\t\t\t   Interval *max_runtime, int32 max_retries, Interval *retry_period,\n\t\t\t\t\t\t   Name proc_schema, Name proc_name, Name check_schema, Name check_name,\n\t\t\t\t\t\t   Oid owner, bool scheduled, bool fixed_schedule, int32 hypertable_id,\n\t\t\t\t\t\t   Jsonb *config, TimestampTz initial_start, const char *timezone)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tRelation rel;\n\tTupleDesc desc;\n\tDatum values[Natts_bgw_job] = { 0 };\n\tCatalogSecurityContext sec_ctx;\n\tbool nulls[Natts_bgw_job] = { false };\n\tint32 job_id;\n\tchar app_name[NAMEDATALEN];\n\tint name_len;\n\n\trel = table_open(catalog_get_table_id(catalog, BGW_JOB), RowExclusiveLock);\n\tdesc = RelationGetDescr(rel);\n\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_schedule_interval)] =\n\t\tIntervalPGetDatum(schedule_interval);\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_max_runtime)] = IntervalPGetDatum(max_runtime);\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_max_retries)] = Int32GetDatum(max_retries);\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_retry_period)] = IntervalPGetDatum(retry_period);\n\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_proc_schema)] = NameGetDatum(proc_schema);\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_proc_name)] = NameGetDatum(proc_name);\n\n\tif (strlen(NameStr(*check_schema)) > 0)\n\t\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_check_schema)] = NameGetDatum(check_schema);\n\telse\n\t\tnulls[AttrNumberGetAttrOffset(Anum_bgw_job_check_schema)] = true;\n\n\tif (strlen(NameStr(*check_name)) > 0)\n\t\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_check_name)] = NameGetDatum(check_name);\n\telse\n\t\tnulls[AttrNumberGetAttrOffset(Anum_bgw_job_check_name)] = true;\n\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_owner)] = ObjectIdGetDatum(owner);\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_scheduled)] = BoolGetDatum(scheduled);\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_fixed_schedule)] = BoolGetDatum(fixed_schedule);\n\t/* initial_start must have a value if the schedule is fixed */\n\tAssert(!fixed_schedule || !TIMESTAMP_NOT_FINITE(initial_start));\n\n\tif (TIMESTAMP_NOT_FINITE(initial_start))\n\t{\n\t\tnulls[AttrNumberGetAttrOffset(Anum_bgw_job_initial_start)] = true;\n\t\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_initial_start)] =\n\t\t\tTimestampTzGetDatum(initial_start);\n\t}\n\telse\n\t{\n\t\tnulls[AttrNumberGetAttrOffset(Anum_bgw_job_initial_start)] = false;\n\t\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_initial_start)] =\n\t\t\tTimestampTzGetDatum(initial_start);\n\t}\n\n\tif (hypertable_id == INVALID_HYPERTABLE_ID)\n\t\tnulls[AttrNumberGetAttrOffset(Anum_bgw_job_hypertable_id)] = true;\n\telse\n\t\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_hypertable_id)] = Int32GetDatum(hypertable_id);\n\n\tif (config == NULL)\n\t\tnulls[AttrNumberGetAttrOffset(Anum_bgw_job_config)] = true;\n\telse\n\t\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_config)] = JsonbPGetDatum(config);\n\tif (timezone == NULL)\n\t\tnulls[AttrNumberGetAttrOffset(Anum_bgw_job_timezone)] = true;\n\telse\n\t\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_timezone)] = CStringGetTextDatum(timezone);\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\n\tjob_id = DatumGetInt32(ts_catalog_table_next_seq_id(catalog, BGW_JOB));\n\tname_len = snprintf(app_name, NAMEDATALEN, \"%s [%d]\", NameStr(*application_name), job_id);\n\n\tif (name_len >= NAMEDATALEN)\n\t\tereport(ERROR, (errcode(ERRCODE_NAME_TOO_LONG), errmsg(\"application name too long.\")));\n\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_id)] = Int32GetDatum(job_id);\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_application_name)] = CStringGetDatum(app_name);\n\n\tts_catalog_insert_values(rel, desc, values, nulls);\n\tts_catalog_restore_user(&sec_ctx);\n\n\t/* This is where we would add a call to recordDependencyOnOwner, but it\n\t * cannot support dependencies on anything but built-in classes since\n\t * getObjectClass() have a lot of hard-coded checks in place.\n\t *\n\t * Instead we have a check in process_utility.c that prevents dropping the\n\t * user if there is a dependent job. */\n\n\ttable_close(rel, NoLock);\n\treturn values[AttrNumberGetAttrOffset(Anum_bgw_job_id)];\n}\n\n/*\n * This function ensures the schedule interval is acceptable in the case\n * of fixed job schedules. Intervals that mix months with day and time\n * components are not acceptable since internally we use time_bucket and\n * cannot bucket by such an interval.\n */\nvoid\nts_bgw_job_validate_schedule_interval(Interval *schedule_interval)\n{\n\tbool has_month, has_day, has_time;\n\thas_month = schedule_interval->month;\n\thas_day = schedule_interval->day;\n\thas_time = schedule_interval->time;\n\n\tif (has_month && (has_day || has_time))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"month intervals cannot have day or time component\"),\n\t\t\t\t errdetail(\"Fixed schedule jobs do not support such schedule intervals.\"),\n\t\t\t\t errhint(\"Express the interval in terms of days or time instead.\")));\n}\n\nchar *\nts_bgw_job_validate_timezone(Datum timezone)\n{\n\tDirectFunctionCall2(timestamp_zone,\n\t\t\t\t\t\ttimezone,\n\t\t\t\t\t\tTimestampGetDatum(ts_timer_get_current_timestamp()));\n\treturn TextDatumGetCString(timezone);\n}\n"
  },
  {
    "path": "src/bgw/job.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <postmaster/bgworker.h>\n\n#include \"export.h\"\n#include \"ts_catalog/catalog.h\"\n\n#define TELEMETRY_INITIAL_NUM_RUNS 12\n#define SCHEDULER_APPNAME \"TimescaleDB Background Worker Scheduler\"\n\n/*\n * This is copied from mem_guard and have to be the same as the type in\n * mem_guard.\n *\n * These are intended as an interim solution and will be removed once we have\n * a stable plugin ABI for TimescaleDB.\n */\n\n#define MG_CALLBACKS_VERSION 1\n#define MG_CALLBACKS_VAR_NAME \"mg_callbacks\"\n\ntypedef void (*mg_toggle_allocation_blocking)(bool enable);\ntypedef size_t (*mg_get_allocated_memory)();\ntypedef size_t (*mg_get_total_allocated_memory)();\ntypedef bool (*mg_enabled)();\n\ntypedef struct MGCallbacks\n{\n\tint64 version_num;\n\tmg_toggle_allocation_blocking toggle_allocation_blocking;\n\tmg_get_allocated_memory get_allocated_memory;\n\tmg_get_total_allocated_memory get_total_allocated_memory;\n\tmg_enabled enabled;\n} MGCallbacks;\n\ntypedef struct BgwJobHistory\n{\n\tint64 id;\n\tTimestampTz execution_start;\n} BgwJobHistory;\n\ntypedef struct BgwJob\n{\n\tFormData_bgw_job fd;\n\tBgwJobHistory job_history;\n} BgwJob;\n\n/* Positive result numbers reserved for success */\ntypedef enum JobResult\n{\n\tJOB_FAILURE_TO_START = -1,\n\tJOB_FAILURE_IN_EXECUTION = 0,\n\tJOB_SUCCESS = 1,\n} JobResult;\n\ntypedef bool job_main_func(void);\ntypedef bool (*scheduler_test_hook_type)(BgwJob *job);\n\nextern BackgroundWorkerHandle *ts_bgw_job_start(BgwJob *job, Oid user_oid);\n\nextern List *ts_bgw_job_get_all(size_t alloc_size, MemoryContext mctx);\nextern List *ts_bgw_job_get_scheduled(size_t alloc_size, MemoryContext mctx);\n\nextern TSDLLEXPORT List *ts_bgw_job_find_by_hypertable_id(int32 hypertable_id);\nextern TSDLLEXPORT List *ts_bgw_job_find_by_proc_and_hypertable_id(const char *proc_name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   const char *proc_schema,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   int32 hypertable_id);\n\nTSDLLEXPORT BgwJob *ts_bgw_job_find(int job_id, MemoryContext mctx, bool fail_if_not_found);\n\nextern bool ts_bgw_job_has_timeout(BgwJob *job);\nextern TimestampTz ts_bgw_job_timeout_at(BgwJob *job, TimestampTz start_time);\n\nextern TSDLLEXPORT bool ts_bgw_job_delete_by_id(int32 job_id);\nextern TSDLLEXPORT bool ts_bgw_job_update_by_id(int32 job_id, BgwJob *job);\nextern TSDLLEXPORT int32 ts_bgw_job_insert_relation(\n\tName application_name, Interval *schedule_interval, Interval *max_runtime, int32 max_retries,\n\tInterval *retry_period, Name proc_schema, Name proc_name, Name check_schema, Name check_name,\n\tOid owner, bool scheduled, bool fixed_schedule, int32 hypertable_id, Jsonb *config,\n\tTimestampTz initial_start, const char *timezone);\nextern TSDLLEXPORT void ts_bgw_job_permission_check(BgwJob *job, const char *cmd);\n\nextern TSDLLEXPORT void ts_bgw_job_validate_job_owner(Oid owner);\n\nextern JobResult ts_bgw_job_execute(BgwJob *job);\nextern TSDLLEXPORT void ts_bgw_job_run_config_check(Oid check, int32 job_id, Jsonb *config);\n\nextern TSDLLEXPORT Datum ts_bgw_job_entrypoint(PG_FUNCTION_ARGS);\nextern void ts_bgw_job_set_scheduler_test_hook(scheduler_test_hook_type hook);\nextern void ts_bgw_job_set_job_entrypoint_function_name(char *func_name);\nextern TSDLLEXPORT bool ts_bgw_job_run_and_set_next_start(BgwJob *job, job_main_func func,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  int64 initial_runs,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  Interval *next_interval, bool atomic,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  bool mark);\nextern TSDLLEXPORT void ts_bgw_job_validate_schedule_interval(Interval *schedule_interval);\nextern TSDLLEXPORT char *ts_bgw_job_validate_timezone(Datum timezone);\n\nextern TSDLLEXPORT bool ts_is_telemetry_job(BgwJob *job);\nScanTupleResult ts_bgw_job_change_owner(TupleInfo *ti, void *data);\n\nextern TSDLLEXPORT Oid ts_bgw_job_get_funcid(BgwJob *job);\nextern TSDLLEXPORT const char *ts_bgw_job_function_call_string(BgwJob *job);\n\nextern TSDLLEXPORT MGCallbacks *ts_get_mem_guard_callbacks(void);\n"
  },
  {
    "path": "src/bgw/job_stat.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n\n#include <access/xact.h>\n#include <math.h>\n#include <stdlib.h>\n#include <utils/builtins.h>\n#include <utils/fmgrprotos.h>\n#include <utils/resowner.h>\n\n#include \"guc.h\"\n#include \"job.h\"\n#include \"job_stat.h\"\n#include \"job_stat_history.h\"\n#include \"jsonb_utils.h\"\n#include \"scanner.h\"\n#include \"time_bucket.h\"\n#include \"timer.h\"\n#include \"utils.h\"\n\n#define MAX_INTERVALS_BACKOFF 5\n#define MAX_FAILURES_MULTIPLIER 20\n#define MIN_WAIT_AFTER_CRASH_MS (5 * 60 * 1000)\n\nstatic bool\nbgw_job_stat_next_start_was_set(FormData_bgw_job_stat *fd)\n{\n\treturn fd->next_start != DT_NOBEGIN;\n}\n\nstatic ScanTupleResult\nbgw_job_stat_tuple_found(TupleInfo *ti, void *const data)\n{\n\tBgwJobStat **job_stat_pp = (BgwJobStat **) data;\n\n\t*job_stat_pp = STRUCT_FROM_SLOT(ti->slot, ti->mctx, BgwJobStat, FormData_bgw_job_stat);\n\n\t/*\n\t * Return SCAN_CONTINUE because we check for multiple tuples as an error\n\t * condition.\n\t */\n\treturn SCAN_CONTINUE;\n}\n\nstatic bool\nbgw_job_stat_scan_one(int indexid, ScanKeyData scankey[], int nkeys, tuple_found_func tuple_found,\n\t\t\t\t\t  tuple_filter_func tuple_filter, void *data, LOCKMODE lockmode)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, BGW_JOB_STAT),\n\t\t.index = catalog_get_index(catalog, BGW_JOB_STAT, indexid),\n\t\t.nkeys = nkeys,\n\t\t.scankey = scankey,\n\t\t.flags = SCANNER_F_KEEPLOCK,\n\t\t.tuple_found = tuple_found,\n\t\t.filter = tuple_filter,\n\t\t.data = data,\n\t\t.lockmode = lockmode,\n\t\t.scandirection = ForwardScanDirection,\n\t};\n\n\treturn ts_scanner_scan_one(&scanctx, false, \"bgw job stat\");\n}\n\nstatic inline bool\nbgw_job_stat_scan_job_id(int32 bgw_job_id, tuple_found_func tuple_found,\n\t\t\t\t\t\t tuple_filter_func tuple_filter, void *data, LOCKMODE lockmode)\n{\n\tScanKeyData scankey[1];\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_bgw_job_stat_pkey_idx_job_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(bgw_job_id));\n\treturn bgw_job_stat_scan_one(BGW_JOB_STAT_PKEY_IDX,\n\t\t\t\t\t\t\t\t scankey,\n\t\t\t\t\t\t\t\t 1,\n\t\t\t\t\t\t\t\t tuple_found,\n\t\t\t\t\t\t\t\t tuple_filter,\n\t\t\t\t\t\t\t\t data,\n\t\t\t\t\t\t\t\t lockmode);\n}\n\nTSDLLEXPORT BgwJobStat *\nts_bgw_job_stat_find(int32 bgw_job_id)\n{\n\tBgwJobStat *job_stat = NULL;\n\n\tbgw_job_stat_scan_job_id(bgw_job_id,\n\t\t\t\t\t\t\t bgw_job_stat_tuple_found,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t (void *) &job_stat,\n\t\t\t\t\t\t\t AccessShareLock);\n\n\treturn job_stat;\n}\n\nstatic ScanTupleResult\nbgw_job_stat_tuple_delete(TupleInfo *ti, void *const data)\n{\n\tts_catalog_delete_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\n\treturn SCAN_CONTINUE;\n}\n\nvoid\nts_bgw_job_stat_delete(int32 bgw_job_id)\n{\n\tbgw_job_stat_scan_job_id(bgw_job_id,\n\t\t\t\t\t\t\t bgw_job_stat_tuple_delete,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t ShareRowExclusiveLock);\n}\n\n/* Mark the start of a job. This should be done in a separate transaction by the scheduler\n *  before the bgw for a job is launched. This ensures that the job is counted as started\n * before /any/ job specific code is executed. A job that has been started but never ended\n * is assumed to have crashed. We use this conservative design since no process in the database\n * instance can write once a crash happened in any job. Therefore our only choice is to deduce\n * a crash from the lack of a write (the marked end write in this case).\n */\nstatic ScanTupleResult\nbgw_job_stat_tuple_mark_start(TupleInfo *ti, void *const data)\n{\n\tbool should_free;\n\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\tHeapTuple new_tuple = heap_copytuple(tuple);\n\tFormData_bgw_job_stat *fd = (FormData_bgw_job_stat *) GETSTRUCT(new_tuple);\n\n\tif (should_free)\n\t\theap_freetuple(tuple);\n\n\tfd->last_start = ts_timer_get_current_timestamp();\n\tfd->last_finish = DT_NOBEGIN;\n\tfd->next_start = DT_NOBEGIN;\n\n\tfd->total_runs++;\n\n\t/*\n\t * This is undone by any of the end marks. This is so that we count\n\t * crashes conservatively. Pretty much the crash is incremented in the\n\t * beginning and then decremented during `bgw_job_stat_tuple_mark_end`.\n\t * Thus, it only remains incremented if the job is never marked as having\n\t * ended. This happens when: 1) the job crashes 2) another process crashes\n\t * while the job is running 3) the scheduler gets a SIGTERM while the job\n\t * is running\n\t *\n\t * Unfortunately, 3 cannot be helped because when a scheduler gets a\n\t * SIGTERM it sends SIGTERMS to it's any running jobs as well. Since you\n\t * aren't supposed to write to the DB Once you get a sigterm, neither the\n\t * job nor the scheduler can mark the end of a job.\n\t */\n\tfd->last_run_success = false;\n\tfd->total_crashes++;\n\tfd->consecutive_crashes++;\n\tfd->flags = ts_clear_flags_32(fd->flags, LAST_CRASH_REPORTED);\n\tts_catalog_update(ti->scanrel, new_tuple);\n\theap_freetuple(new_tuple);\n\n\treturn SCAN_DONE;\n}\n\ntypedef struct\n{\n\tJobResult result;\n\tBgwJob *job;\n} JobResultCtx;\n\n/*\n * logic is the following\n * Ideally we would return\n * time_bucket(schedule_interval, finish_time, origin => initial_start, timezone).\n * That is what we return when the schedule interval does not have month components.\n * However, when there is a month component in the schedule interval,\n * then supplying the origin in time_bucket\n * does not work and the returned bucket is aligned on the start of the month.\n * In those cases, we only have month components. So we compute the difference in\n * months between the initial_start's timebucket and the finish time's bucket.\n */\nTimestampTz\nts_get_next_scheduled_execution_slot(BgwJob *job, TimestampTz finish_time)\n{\n\tAssert(job->fd.fixed_schedule == true);\n\tDatum schedint_datum = IntervalPGetDatum(&job->fd.schedule_interval);\n\tDatum timebucket_fini, timebucket_init, result;\n\n\tInterval one_month = {\n\t\t.day = 0,\n\t\t.time = 0,\n\t\t.month = 1,\n\t};\n\n\tif (job->fd.schedule_interval.month > 0)\n\t{\n\t\tif (job->fd.timezone == NULL)\n\t\t{\n\t\t\ttimebucket_init = DirectFunctionCall2(ts_timestamptz_bucket,\n\t\t\t\t\t\t\t\t\t\t\t\t  schedint_datum,\n\t\t\t\t\t\t\t\t\t\t\t\t  TimestampTzGetDatum(job->fd.initial_start));\n\t\t\ttimebucket_fini = DirectFunctionCall2(ts_timestamptz_bucket,\n\t\t\t\t\t\t\t\t\t\t\t\t  schedint_datum,\n\t\t\t\t\t\t\t\t\t\t\t\t  TimestampTzGetDatum(finish_time));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tchar *tz = text_to_cstring(job->fd.timezone);\n\t\t\ttimebucket_fini = DirectFunctionCall3(ts_timestamptz_timezone_bucket,\n\t\t\t\t\t\t\t\t\t\t\t\t  schedint_datum,\n\t\t\t\t\t\t\t\t\t\t\t\t  TimestampTzGetDatum(finish_time),\n\t\t\t\t\t\t\t\t\t\t\t\t  CStringGetTextDatum(tz));\n\n\t\t\ttimebucket_init = DirectFunctionCall3(ts_timestamptz_timezone_bucket,\n\t\t\t\t\t\t\t\t\t\t\t\t  schedint_datum,\n\t\t\t\t\t\t\t\t\t\t\t\t  TimestampTzGetDatum(job->fd.initial_start),\n\t\t\t\t\t\t\t\t\t\t\t\t  CStringGetTextDatum(tz));\n\t\t}\n\t\t/* always the next bucket */\n\t\ttimebucket_fini =\n\t\t\tDirectFunctionCall2(timestamptz_pl_interval, timebucket_fini, schedint_datum);\n\t\t/* get the number of months between them */\n\t\tDatum year_init =\n\t\t\tDirectFunctionCall2(timestamptz_part, CStringGetTextDatum(\"year\"), timebucket_init);\n\t\tDatum year_fini =\n\t\t\tDirectFunctionCall2(timestamptz_part, CStringGetTextDatum(\"year\"), timebucket_fini);\n\n\t\tDatum month_init =\n\t\t\tDirectFunctionCall2(timestamptz_part, CStringGetTextDatum(\"month\"), timebucket_init);\n\t\tDatum month_fini =\n\t\t\tDirectFunctionCall2(timestamptz_part, CStringGetTextDatum(\"month\"), timebucket_fini);\n\n\t\t/* convert everything to months */\n\t\tfloat8 month_diff = (DatumGetFloat8(year_fini) * 12) + DatumGetFloat8(month_fini) -\n\t\t\t\t\t\t\t((DatumGetFloat8(year_init) * 12) + DatumGetFloat8(month_init));\n\n\t\tDatum months_to_add = DirectFunctionCall2(interval_mul,\n\t\t\t\t\t\t\t\t\t\t\t\t  IntervalPGetDatum(&one_month),\n\t\t\t\t\t\t\t\t\t\t\t\t  Float8GetDatum(month_diff));\n\n\t\tresult = DirectFunctionCall2(timestamptz_pl_interval,\n\t\t\t\t\t\t\t\t\t TimestampTzGetDatum(job->fd.initial_start),\n\t\t\t\t\t\t\t\t\t months_to_add);\n\t}\n\telse\n\t{\n\t\tif (job->fd.timezone == NULL)\n\t\t{\n\t\t\t/* it is safe to use the origin in time_bucket calculation */\n\t\t\ttimebucket_fini = DirectFunctionCall3(ts_timestamptz_bucket,\n\t\t\t\t\t\t\t\t\t\t\t\t  schedint_datum,\n\t\t\t\t\t\t\t\t\t\t\t\t  TimestampTzGetDatum(finish_time),\n\t\t\t\t\t\t\t\t\t\t\t\t  TimestampTzGetDatum(job->fd.initial_start));\n\t\t\tresult = timebucket_fini;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tchar *tz = text_to_cstring(job->fd.timezone);\n\t\t\ttimebucket_fini = DirectFunctionCall4(ts_timestamptz_timezone_bucket,\n\t\t\t\t\t\t\t\t\t\t\t\t  schedint_datum,\n\t\t\t\t\t\t\t\t\t\t\t\t  TimestampTzGetDatum(finish_time),\n\t\t\t\t\t\t\t\t\t\t\t\t  CStringGetTextDatum(tz),\n\t\t\t\t\t\t\t\t\t\t\t\t  TimestampTzGetDatum(job->fd.initial_start));\n\t\t\tresult = timebucket_fini;\n\t\t}\n\t}\n\twhile (DatumGetTimestampTz(result) <= finish_time)\n\t{\n\t\tresult = DirectFunctionCall2(timestamptz_pl_interval, result, schedint_datum);\n\t}\n\treturn DatumGetTimestampTz(result);\n}\n\nstatic TimestampTz\ncalculate_next_start_on_success_fixed(TimestampTz finish_time, BgwJob *job)\n{\n\tTimestampTz next_slot;\n\n\tnext_slot = ts_get_next_scheduled_execution_slot(job, finish_time);\n\n\treturn next_slot;\n}\n\nstatic TimestampTz\ncalculate_next_start_on_success_drifting(TimestampTz last_finish, BgwJob *job)\n{\n\tTimestampTz ts;\n\tts = DatumGetTimestampTz(DirectFunctionCall2(timestamptz_pl_interval,\n\t\t\t\t\t\t\t\t\t\t\t\t TimestampTzGetDatum(last_finish),\n\t\t\t\t\t\t\t\t\t\t\t\t IntervalPGetDatum(&job->fd.schedule_interval)));\n\treturn ts;\n}\n\nstatic TimestampTz\ncalculate_next_start_on_success(TimestampTz finish_time, BgwJob *job)\n{\n\t/* next_start is the previously calculated next_start for this job */\n\tTimestampTz ts;\n\tTimestampTz last_finish = finish_time;\n\tif (!IS_VALID_TIMESTAMP(finish_time))\n\t{\n\t\tlast_finish = ts_timer_get_current_timestamp();\n\t}\n\n\t/* calculate next_start differently depending on drift/no drift */\n\tif (job->fd.fixed_schedule)\n\t\tts = calculate_next_start_on_success_fixed(last_finish, job);\n\telse\n\t\tts = calculate_next_start_on_success_drifting(last_finish, job);\n\n\treturn ts;\n}\n\nstatic float8\ncalculate_jitter_percent()\n{\n\t/* returns a number in the range [-0.125, 0.125] */\n\tuint8 percent = rand();\n\treturn ldexp((double) (16 - (int) (percent % 32)), -7);\n}\n\n/* For failures we have backoff based on consecutive failures\n * along with a ceiling at schedule_interval * MAX_INTERVALS_BACKOFF / 1 minute\n * for jobs failing at runtime / for jobs failing to launch.\n * We also limit the backoff in case of consecutive failures as we don't\n * want to pass in input that leads to out of range timestamps and don't want to\n * put off the next start time for the job indefinitely\n */\nstatic TimestampTz\ncalculate_next_start_on_failure(TimestampTz finish_time, int consecutive_failures, BgwJob *job,\n\t\t\t\t\t\t\t\tbool launch_failure)\n{\n\tfloat8 jitter = calculate_jitter_percent();\n\n\t/*\n\t * Have to be declared volatile because they are modified between\n\t * setjmp/longjmp calls.\n\t */\n\tvolatile TimestampTz res = 0;\n\tvolatile bool res_set = false;\n\tvolatile TimestampTz last_finish = finish_time;\n\n\t/* consecutive failures includes this failure */\n\tfloat8 multiplier = (consecutive_failures > MAX_FAILURES_MULTIPLIER ? MAX_FAILURES_MULTIPLIER :\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  consecutive_failures);\n\tAssert(consecutive_failures > 0 && multiplier < 63);\n\n\tMemoryContext oldctx = CurrentMemoryContext;\n\tResourceOwner oldowner = CurrentResourceOwner;\n\t/* 2^(consecutive_failures) - 1, at most 2^20 */\n\tint64 max_slots = (INT64CONST(1) << (int64) multiplier) - INT64CONST(1);\n\tint64 rand_backoff = rand() % (max_slots * USECS_PER_SEC);\n\n\tif (!IS_VALID_TIMESTAMP(finish_time))\n\t{\n\t\telog(LOG, \"%s: invalid finish time\", __func__);\n\t\tlast_finish = ts_timer_get_current_timestamp();\n\t}\n\n\tPG_TRY();\n\t{\n\t\tDatum ival;\n\t\t/* ival_max is the ceiling = MAX_INTERVALS_BACKOFF * schedule_interval */\n\t\tDatum ival_max;\n\t\t// max wait time to launch job is 1 minute\n\t\tInterval interval_max = { .time = 60000000 };\n\t\tInterval retry_ival = { .time = 2000000 };\n\t\tretry_ival.time += rand_backoff;\n\n\t\tBeginInternalSubTransaction(\"next start on failure\");\n\n\t\tif (launch_failure)\n\t\t{\n\t\t\t// random backoff seconds in [2, 2 + 2^f]\n\t\t\tival = IntervalPGetDatum(&retry_ival);\n\t\t\tival_max = IntervalPGetDatum(&interval_max);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* ival = retry_period * (consecutive_failures)  */\n\t\t\tival = DirectFunctionCall2(interval_mul,\n\t\t\t\t\t\t\t\t\t   IntervalPGetDatum(&job->fd.retry_period),\n\t\t\t\t\t\t\t\t\t   Float8GetDatum(multiplier));\n\t\t\t/* ival_max is the ceiling = MAX_INTERVALS_BACKOFF * schedule_interval */\n\t\t\tival_max = DirectFunctionCall2(interval_mul,\n\t\t\t\t\t\t\t\t\t\t   IntervalPGetDatum(&job->fd.schedule_interval),\n\t\t\t\t\t\t\t\t\t\t   Float8GetDatum(MAX_INTERVALS_BACKOFF));\n\t\t}\n\n\t\tif (DatumGetInt32(DirectFunctionCall2(interval_cmp, ival, ival_max)) > 0)\n\t\t\tival = ival_max;\n\n\t\t/* Add some random jitter to prevent stampeding-herds, interval will be within about +-13%\n\t\t */\n\t\tival = DirectFunctionCall2(interval_mul, ival, Float8GetDatum(1.0 + jitter));\n\n\t\tres = DatumGetTimestampTz(\n\t\t\tDirectFunctionCall2(timestamptz_pl_interval, TimestampTzGetDatum(last_finish), ival));\n\t\tres_set = true;\n\t\tReleaseCurrentSubTransaction();\n\t\tMemoryContextSwitchTo(oldctx);\n\t\tCurrentResourceOwner = oldowner;\n\t}\n\tPG_CATCH();\n\t{\n\t\tRollbackAndReleaseCurrentSubTransaction();\n\t\tMemoryContextSwitchTo(oldctx);\n\t\tCurrentResourceOwner = oldowner;\n\t\tErrorData *errdata = CopyErrorData();\n\t\tFlushErrorState();\n\t\tereport(LOG,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t errmsg(\"could not calculate next start on failure: resetting value\"),\n\t\t\t\t errdetail(\"Error: %s.\", errdata->message)));\n\t\tFreeErrorData(errdata);\n\t}\n\tPG_END_TRY();\n\tAssert(CurrentMemoryContext == oldctx);\n\tif (!res_set)\n\t{\n\t\tTimestampTz nowt;\n\t\t/* job->fd_retry_period is a valid non-null value */\n\t\tnowt = ts_timer_get_current_timestamp();\n\t\tres = DatumGetTimestampTz(DirectFunctionCall2(timestamptz_pl_interval,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  TimestampTzGetDatum(nowt),\n\t\t\t\t\t\t\t\t\t\t\t\t\t  IntervalPGetDatum(&job->fd.retry_period)));\n\t}\n\t/* for fixed_schedules, we make sure that if the calculated next_start time\n\t * surpasses the next scheduled slot, then next_start will be set to the value\n\t * of the next scheduled slot, so we don't get off track */\n\tif (job->fd.fixed_schedule)\n\t{\n\t\tTimestampTz next_slot = ts_get_next_scheduled_execution_slot(job, finish_time);\n\t\tif (res > next_slot)\n\t\t\tres = next_slot;\n\t}\n\treturn res;\n}\n\nstatic TimestampTz\ncalculate_next_start_on_failed_launch(int consecutive_failed_launches, BgwJob *job)\n{\n\tTimestampTz now = ts_timer_get_current_timestamp();\n\tTimestampTz failure_calc =\n\t\tcalculate_next_start_on_failure(now, consecutive_failed_launches, job, true);\n\n\treturn failure_calc;\n}\n\n/* For crashes, the logic is the similar as for failures except we also have\n *  a minimum wait after a crash that we wait, so that if an operator needs to disable the job,\n *  there will be enough time before another crash.\n */\nstatic TimestampTz\ncalculate_next_start_on_crash(int consecutive_crashes, BgwJob *job)\n{\n\tTimestampTz now = ts_timer_get_current_timestamp();\n\tTimestampTz failure_calc =\n\t\tcalculate_next_start_on_failure(now, consecutive_crashes, job, false);\n\tTimestampTz min_time = TimestampTzPlusMilliseconds(now, MIN_WAIT_AFTER_CRASH_MS);\n\n\tif (min_time > failure_calc)\n\t\treturn min_time;\n\treturn failure_calc;\n}\n\nstatic ScanTupleResult\nbgw_job_stat_tuple_mark_end(TupleInfo *ti, void *const data)\n{\n\tJobResultCtx *result_ctx = data;\n\tbool should_free;\n\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\tHeapTuple new_tuple = heap_copytuple(tuple);\n\tFormData_bgw_job_stat *fd = (FormData_bgw_job_stat *) GETSTRUCT(new_tuple);\n\tInterval *duration;\n\n\tif (should_free)\n\t\theap_freetuple(tuple);\n\n\tfd->last_finish = ts_timer_get_current_timestamp();\n\n\tduration = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,\n\t\t\t\t\t\t\t\t\t\t\t\t\t TimestampTzGetDatum(fd->last_finish),\n\t\t\t\t\t\t\t\t\t\t\t\t\t TimestampTzGetDatum(fd->last_start)));\n\n\t/* undo marking created by start marks */\n\tfd->last_run_success = result_ctx->result == JOB_SUCCESS ? true : false;\n\tfd->total_crashes--;\n\tfd->consecutive_crashes = 0;\n\tfd->flags = ts_clear_flags_32(fd->flags, LAST_CRASH_REPORTED);\n\n\tif (result_ctx->result == JOB_SUCCESS)\n\t{\n\t\tfd->total_success++;\n\t\tfd->consecutive_failures = 0;\n\t\tfd->last_successful_finish = fd->last_finish;\n\t\tfd->total_duration =\n\t\t\t*DatumGetIntervalP(DirectFunctionCall2(interval_pl,\n\t\t\t\t\t\t\t\t\t\t\t\t   IntervalPGetDatum(&fd->total_duration),\n\t\t\t\t\t\t\t\t\t\t\t\t   IntervalPGetDatum(duration)));\n\t\t/* Mark the next start at the end if the job itself hasn't */\n\t\tif (!bgw_job_stat_next_start_was_set(fd))\n\t\t\tfd->next_start = calculate_next_start_on_success(fd->last_finish, result_ctx->job);\n\t}\n\telse\n\t{\n\t\tfd->total_failures++;\n\t\tfd->consecutive_failures++;\n\t\tfd->total_duration_failures =\n\t\t\t*DatumGetIntervalP(DirectFunctionCall2(interval_pl,\n\t\t\t\t\t\t\t\t\t\t\t\t   IntervalPGetDatum(&fd->total_duration_failures),\n\t\t\t\t\t\t\t\t\t\t\t\t   IntervalPGetDatum(duration)));\n\n\t\t/*\n\t\t * Mark the next start at the end if the job itself hasn't (this may\n\t\t * have happened before failure) and the failure was not in starting.\n\t\t * If the failure was in starting, then next_start should have been\n\t\t * restored in `on_failure_to_start_job` and thus we don't change it here.\n\t\t * Even if it wasn't restored, then keep it as DT_NOBEGIN to mark it as highest priority.\n\t\t */\n\t\tif (!bgw_job_stat_next_start_was_set(fd) && result_ctx->result != JOB_FAILURE_TO_START)\n\t\t\tfd->next_start = calculate_next_start_on_failure(fd->last_finish,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t fd->consecutive_failures,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t result_ctx->job,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t false);\n\t}\n\n\tts_catalog_update(ti->scanrel, new_tuple);\n\theap_freetuple(new_tuple);\n\n\treturn SCAN_DONE;\n}\n\nstatic ScanTupleResult\nbgw_job_stat_tuple_mark_crash_reported(TupleInfo *ti, void *const data)\n{\n\tbool should_free;\n\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\tHeapTuple new_tuple = heap_copytuple(tuple);\n\tFormData_bgw_job_stat *fd = (FormData_bgw_job_stat *) GETSTRUCT(new_tuple);\n\n\tif (should_free)\n\t\theap_freetuple(tuple);\n\n\tfd->flags = ts_set_flags_32(fd->flags, LAST_CRASH_REPORTED);\n\n\tts_catalog_update(ti->scanrel, new_tuple);\n\theap_freetuple(new_tuple);\n\n\treturn SCAN_DONE;\n}\n\nstatic ScanTupleResult\nbgw_job_stat_tuple_set_next_start(TupleInfo *ti, void *const data)\n{\n\tTimestampTz *next_start = data;\n\tbool should_free;\n\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\tHeapTuple new_tuple = heap_copytuple(tuple);\n\tFormData_bgw_job_stat *fd = (FormData_bgw_job_stat *) GETSTRUCT(new_tuple);\n\n\tif (should_free)\n\t\theap_freetuple(tuple);\n\n\tfd->next_start = *next_start;\n\tts_catalog_update(ti->scanrel, new_tuple);\n\theap_freetuple(new_tuple);\n\n\treturn SCAN_DONE;\n}\n\nstatic bool\nbgw_job_stat_insert_relation(Relation rel, int32 bgw_job_id, bool mark_start,\n\t\t\t\t\t\t\t TimestampTz next_start)\n{\n\tTupleDesc desc = RelationGetDescr(rel);\n\tDatum values[Natts_bgw_job_stat];\n\tbool nulls[Natts_bgw_job_stat] = { false };\n\tCatalogSecurityContext sec_ctx;\n\tInterval zero_ival = {\n\t\t.time = 0,\n\t};\n\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_stat_job_id)] = Int32GetDatum(bgw_job_id);\n\tif (mark_start)\n\t\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_stat_last_start)] =\n\t\t\tTimestampGetDatum(ts_timer_get_current_timestamp());\n\telse\n\t\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_stat_last_start)] =\n\t\t\tTimestampGetDatum(DT_NOBEGIN);\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_stat_last_finish)] = TimestampGetDatum(DT_NOBEGIN);\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_stat_next_start)] = TimestampGetDatum(next_start);\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_stat_last_successful_finish)] =\n\t\tTimestampGetDatum(DT_NOBEGIN);\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_stat_total_runs)] =\n\t\tInt64GetDatum((mark_start ? 1 : 0));\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_stat_total_duration)] =\n\t\tIntervalPGetDatum(&zero_ival);\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_stat_total_duration_failures)] =\n\t\tIntervalPGetDatum(&zero_ival);\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_stat_total_success)] = Int64GetDatum(0);\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_stat_total_failures)] = Int64GetDatum(0);\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_stat_consecutive_failures)] = Int32GetDatum(0);\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_stat_flags)] =\n\t\tInt32GetDatum(JOB_STAT_FLAGS_DEFAULT);\n\n\tif (mark_start)\n\t{\n\t\t/* This is udone by any of the end marks */\n\t\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_stat_last_run_success)] = BoolGetDatum(false);\n\t\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_stat_total_crashes)] = Int64GetDatum(1);\n\t\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_stat_consecutive_crashes)] = Int32GetDatum(1);\n\t}\n\telse\n\t{\n\t\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_stat_last_run_success)] = BoolGetDatum(true);\n\t\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_stat_total_crashes)] = Int64GetDatum(0);\n\t\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_stat_consecutive_crashes)] = Int32GetDatum(0);\n\t}\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_insert_values(rel, desc, values, nulls);\n\tts_catalog_restore_user(&sec_ctx);\n\n\treturn true;\n}\n\nvoid\nts_bgw_job_stat_mark_start(BgwJob *job)\n{\n\t/* We grab a ShareRowExclusiveLock here because we need to ensure that no\n\t * job races and adds a job when we insert the relation as well since that\n\t * can trigger a failure when inserting a row for the job. We use the\n\t * RowExclusiveLock in the scan since we cannot use NoLock (relation_open\n\t * requires a lock that it not NoLock). */\n\tRelation rel =\n\t\ttable_open(catalog_get_table_id(ts_catalog_get(), BGW_JOB_STAT), ShareRowExclusiveLock);\n\tif (!bgw_job_stat_scan_job_id(job->fd.id,\n\t\t\t\t\t\t\t\t  bgw_job_stat_tuple_mark_start,\n\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t  RowExclusiveLock))\n\t\tbgw_job_stat_insert_relation(rel, job->fd.id, true, DT_NOBEGIN);\n\ttable_close(rel, NoLock);\n\n\t/* We need to capture the execution start because failures are always logged */\n\tjob->job_history.execution_start = ts_timer_get_current_timestamp();\n\tjob->job_history.id = INVALID_BGW_JOB_STAT_HISTORY_ID;\n\n\tts_bgw_job_stat_history_update(JOB_STAT_HISTORY_UPDATE_START, job, JOB_SUCCESS, NULL);\n\n\tpgstat_report_activity(STATE_IDLE, NULL);\n}\n\nvoid\nts_bgw_job_stat_mark_end(BgwJob *job, JobResult result, Jsonb *edata)\n{\n\tJobResultCtx res = {\n\t\t.job = job,\n\t\t.result = result,\n\t};\n\n\tif (!bgw_job_stat_scan_job_id(job->fd.id,\n\t\t\t\t\t\t\t\t  bgw_job_stat_tuple_mark_end,\n\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t  &res,\n\t\t\t\t\t\t\t\t  ShareRowExclusiveLock))\n\t{\n\t\tif (!ts_bgw_job_find(job->fd.id, CurrentMemoryContext, false))\n\t\t{\n\t\t\telog(WARNING,\n\t\t\t\t \"skipping job statistics update for job %d, job was deleted during execution\",\n\t\t\t\t job->fd.id);\n\t\t\treturn;\n\t\t}\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"unable to find job statistics for job %d\", job->fd.id)));\n\t}\n\n\tts_bgw_job_stat_history_update(JOB_STAT_HISTORY_UPDATE_END, job, result, edata);\n\n\tpgstat_report_activity(STATE_IDLE, NULL);\n}\n\nvoid\nts_bgw_job_stat_mark_crash_reported(BgwJob *job, JobResult result)\n{\n\tif (!bgw_job_stat_scan_job_id(job->fd.id,\n\t\t\t\t\t\t\t\t  bgw_job_stat_tuple_mark_crash_reported,\n\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t  RowExclusiveLock))\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"unable to find job statistics for job %d\", job->fd.id)));\n\t}\n\n\tts_bgw_job_stat_history_update(JOB_STAT_HISTORY_UPDATE_END, job, result, NULL);\n\n\tpgstat_report_activity(STATE_IDLE, NULL);\n}\n\nbool\nts_bgw_job_stat_end_was_marked(BgwJobStat *jobstat)\n{\n\treturn !TIMESTAMP_IS_NOBEGIN(jobstat->fd.last_finish);\n}\n\nTSDLLEXPORT void\nts_bgw_job_stat_set_next_start(int32 job_id, TimestampTz next_start)\n{\n\t/* Cannot use DT_NOBEGIN as that's the value used to indicate \"not set\" */\n\tif (next_start == DT_NOBEGIN)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"cannot set next start to -infinity\")));\n\t}\n\n\tif (!bgw_job_stat_scan_job_id(job_id,\n\t\t\t\t\t\t\t\t  bgw_job_stat_tuple_set_next_start,\n\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t  &next_start,\n\t\t\t\t\t\t\t\t  ShareRowExclusiveLock))\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"unable to find job statistics for job %d\", job_id)));\n\t}\n}\n\n/* update next_start if job stat exists */\nTSDLLEXPORT bool\nts_bgw_job_stat_update_next_start(int32 job_id, TimestampTz next_start, bool allow_unset)\n{\n\tbool found = false;\n\t/* Cannot use DT_NOBEGIN as that's the value used to indicate \"not set\" */\n\tif (!allow_unset && next_start == DT_NOBEGIN)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"cannot set next start to -infinity\")));\n\t}\n\n\tfound = bgw_job_stat_scan_job_id(job_id,\n\t\t\t\t\t\t\t\t\t bgw_job_stat_tuple_set_next_start,\n\t\t\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t\t\t &next_start,\n\t\t\t\t\t\t\t\t\t ShareRowExclusiveLock);\n\treturn found;\n}\n\nTSDLLEXPORT void\nts_bgw_job_stat_upsert_next_start(int32 bgw_job_id, TimestampTz next_start)\n{\n\t/* Cannot use DT_NOBEGIN as that's the value used to indicate \"not set\" */\n\tif (next_start == DT_NOBEGIN)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"cannot set next start to -infinity\")));\n\t}\n\n\t/* We grab a ShareRowExclusiveLock here because we need to ensure that no\n\t * job races and adds a job when we insert the relation as well since that\n\t * can trigger a failure when inserting a row for the job. We use the\n\t * RowExclusiveLock in the scan since we cannot use NoLock (relation_open\n\t * requires a lock that it not NoLock). */\n\tRelation rel =\n\t\ttable_open(catalog_get_table_id(ts_catalog_get(), BGW_JOB_STAT), ShareRowExclusiveLock);\n\tif (!bgw_job_stat_scan_job_id(bgw_job_id,\n\t\t\t\t\t\t\t\t  bgw_job_stat_tuple_set_next_start,\n\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t  &next_start,\n\t\t\t\t\t\t\t\t  RowExclusiveLock))\n\t\tbgw_job_stat_insert_relation(rel, bgw_job_id, false, next_start);\n\ttable_close(rel, NoLock);\n}\n\nbool\nts_bgw_job_stat_should_execute(BgwJobStat *jobstat, BgwJob *job)\n{\n\t/*\n\t * Stub to allow the system to disable jobs based on the number of crashes\n\t * or failures.\n\t */\n\treturn true;\n}\n\nTimestampTz\nts_bgw_job_stat_next_start(BgwJobStat *jobstat, BgwJob *job, int32 consecutive_failed_launches)\n{\n\t/* give the system some room to breathe, wait before trying to launch again */\n\tif (consecutive_failed_launches > 0)\n\t\treturn calculate_next_start_on_failed_launch(consecutive_failed_launches, job);\n\tif (jobstat == NULL)\n\t\t/* Never previously run - run right away */\n\t\treturn DT_NOBEGIN;\n\n\tif (jobstat->fd.consecutive_crashes > 0)\n\t{\n\t\t/* Update the errors table regarding the crash */\n\t\tif (!ts_flags_are_set_32(jobstat->fd.flags, LAST_CRASH_REPORTED))\n\t\t{\n\t\t\tts_bgw_job_stat_mark_crash_reported(job, JOB_FAILURE_IN_EXECUTION);\n\t\t}\n\n\t\treturn calculate_next_start_on_crash(jobstat->fd.consecutive_crashes, job);\n\t}\n\n\treturn jobstat->fd.next_start;\n}\n"
  },
  {
    "path": "src/bgw/job_stat.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include \"job.h\"\n#include \"ts_catalog/catalog.h\"\n\n#define JOB_STAT_FLAGS_DEFAULT 0\n#define LAST_CRASH_REPORTED 1\n\ntypedef struct BgwJobStat\n{\n\tFormData_bgw_job_stat fd;\n} BgwJobStat;\n\nextern TSDLLEXPORT BgwJobStat *ts_bgw_job_stat_find(int job_id);\nextern void ts_bgw_job_stat_delete(int job_id);\nextern TSDLLEXPORT void ts_bgw_job_stat_mark_start(BgwJob *job);\nextern void ts_bgw_job_stat_mark_end(BgwJob *job, JobResult result, Jsonb *edata);\nextern bool ts_bgw_job_stat_end_was_marked(BgwJobStat *jobstat);\n\nextern TSDLLEXPORT void ts_bgw_job_stat_set_next_start(int32 job_id, TimestampTz next_start);\nextern TSDLLEXPORT bool ts_bgw_job_stat_update_next_start(int32 job_id, TimestampTz next_start,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  bool allow_unset);\n\nextern TSDLLEXPORT void ts_bgw_job_stat_upsert_next_start(int32 bgw_job_id, TimestampTz next_start);\n\nextern bool ts_bgw_job_stat_should_execute(BgwJobStat *jobstat, BgwJob *job);\n\nextern TimestampTz ts_bgw_job_stat_next_start(BgwJobStat *jobstat, BgwJob *job,\n\t\t\t\t\t\t\t\t\t\t\t  int32 consecutive_failed_launches);\nextern TSDLLEXPORT void ts_bgw_job_stat_mark_crash_reported(BgwJob *job, JobResult result);\n\nextern TSDLLEXPORT TimestampTz ts_get_next_scheduled_execution_slot(BgwJob *job,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tTimestampTz finish_time);\n"
  },
  {
    "path": "src/bgw/job_stat_history.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n\n#include <access/xact.h>\n#include <utils/jsonb.h>\n\n#include \"compat/compat.h\"\n#include \"guc.h\"\n#include \"hypertable.h\"\n#include \"job_stat_history.h\"\n#include \"jsonb_utils.h\"\n#include \"timer.h\"\n#include \"utils.h\"\n\ntypedef struct BgwJobStatHistoryContext\n{\n\tJobResult result;\n\tBgwJobStatHistoryUpdateType update_type;\n\tBgwJob *job;\n\tJsonb *edata;\n} BgwJobStatHistoryContext;\n\nstatic Jsonb *\nbuild_job_info(BgwJob *job)\n{\n\tJsonbParseState *parse_state = NULL;\n\tpushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);\n\n\t/* all fields that is possible to change with `alter_job` API */\n\tts_jsonb_add_interval(parse_state, \"schedule_interval\", &job->fd.schedule_interval);\n\tts_jsonb_add_interval(parse_state, \"max_runtime\", &job->fd.max_runtime);\n\tts_jsonb_add_int32(parse_state, \"max_retries\", job->fd.max_retries);\n\tts_jsonb_add_interval(parse_state, \"retry_period\", &job->fd.retry_period);\n\tts_jsonb_add_str(parse_state, \"proc_schema\", NameStr(job->fd.proc_schema));\n\tts_jsonb_add_str(parse_state, \"proc_name\", NameStr(job->fd.proc_name));\n\tts_jsonb_add_str(parse_state, \"owner\", GetUserNameFromId(job->fd.owner, false));\n\tts_jsonb_add_bool(parse_state, \"scheduled\", job->fd.scheduled);\n\tts_jsonb_add_bool(parse_state, \"fixed_schedule\", job->fd.fixed_schedule);\n\n\tif (job->fd.initial_start)\n\t\tts_jsonb_add_interval(parse_state, \"initial_start\", &job->fd.retry_period);\n\n\tif (job->fd.hypertable_id != INVALID_HYPERTABLE_ID)\n\t\tts_jsonb_add_int32(parse_state, \"hypertable_id\", job->fd.hypertable_id);\n\n\tif (job->fd.config != NULL)\n\t{\n\t\t/* config information jsonb*/\n\t\tJsonbValue value = { 0 };\n\t\tJsonbToJsonbValue(job->fd.config, &value);\n\t\tts_jsonb_add_value(parse_state, \"config\", &value);\n\t}\n\n\tif (strlen(NameStr(job->fd.check_schema)) > 0)\n\t\tts_jsonb_add_str(parse_state, \"check_schema\", NameStr(job->fd.check_schema));\n\n\tif (strlen(NameStr(job->fd.check_name)) > 0)\n\t\tts_jsonb_add_str(parse_state, \"check_name\", NameStr(job->fd.check_name));\n\n\tif (job->fd.timezone != NULL)\n\t\tts_jsonb_add_str(parse_state, \"timezone\", text_to_cstring(job->fd.timezone));\n\n\treturn JsonbValueToJsonb(pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL));\n}\n\nstatic Jsonb *\nts_bgw_job_stat_history_build_data_info(BgwJobStatHistoryContext *context)\n{\n\tJsonbParseState *parse_state = NULL;\n\tJsonbValue value = { 0 };\n\tpushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);\n\n\tAssert(context != NULL && context->job != NULL);\n\n\t/* job information jsonb */\n\tJsonbToJsonbValue(build_job_info(context->job), &value);\n\tts_jsonb_add_value(parse_state, \"job\", &value);\n\n\tif (context->edata != NULL)\n\t{\n\t\t/* error information jsonb */\n\t\tJsonbToJsonbValue(context->edata, &value);\n\t\tts_jsonb_add_value(parse_state, \"error_data\", &value);\n\t}\n\n\treturn JsonbValueToJsonb(pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL));\n}\n\nstatic void\nbgw_job_stat_history_insert(BgwJobStatHistoryContext *context, bool track_only_errors)\n{\n\tAssert(context != NULL);\n\n\tRelation rel = table_open(catalog_get_table_id(ts_catalog_get(), BGW_JOB_STAT_HISTORY),\n\t\t\t\t\t\t\t  ShareRowExclusiveLock);\n\tTupleDesc desc = RelationGetDescr(rel);\n\tNullableDatum values[Natts_bgw_job_stat_history] = { { 0 } };\n\tCatalogSecurityContext sec_ctx;\n\n\tts_datum_set_int32(Anum_bgw_job_stat_history_job_id, values, context->job->fd.id, false);\n\tts_datum_set_timestamptz(Anum_bgw_job_stat_history_execution_start,\n\t\t\t\t\t\t\t values,\n\t\t\t\t\t\t\t context->job->job_history.execution_start,\n\t\t\t\t\t\t\t false);\n\tif (track_only_errors)\n\t{\n\t\t/* In case of logging only ERRORs */\n\t\tts_datum_set_int32(Anum_bgw_job_stat_history_pid, values, MyProcPid, false);\n\t\tts_datum_set_timestamptz(Anum_bgw_job_stat_history_execution_finish,\n\t\t\t\t\t\t\t\t values,\n\t\t\t\t\t\t\t\t ts_timer_get_current_timestamp(),\n\t\t\t\t\t\t\t\t false);\n\t\tts_datum_set_bool(Anum_bgw_job_stat_history_succeeded, values, false, false);\n\t}\n\telse\n\t{\n\t\t/* When tracking history first we INSERT the job without the FINISH execution timestamp,\n\t\t * PID and SUCCEED flag because it will be marked once the job finishes */\n\t\tts_datum_set_int32(Anum_bgw_job_stat_history_pid, values, 0, true);\n\t\tts_datum_set_timestamptz(Anum_bgw_job_stat_history_execution_finish, values, 0, true);\n\t\tts_datum_set_bool(Anum_bgw_job_stat_history_succeeded, values, false, true);\n\t}\n\n\tts_datum_set_jsonb(Anum_bgw_job_stat_history_data,\n\t\t\t\t\t   values,\n\t\t\t\t\t   ts_bgw_job_stat_history_build_data_info(context));\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\n\tif (context->job->job_history.id == INVALID_BGW_JOB_STAT_HISTORY_ID)\n\t{\n\t\t/* We need to get a new job id to mark the end later */\n\t\tcontext->job->job_history.id =\n\t\t\tts_catalog_table_next_seq_id(ts_catalog_get(), BGW_JOB_STAT_HISTORY);\n\t}\n\tts_datum_set_int64(Anum_bgw_job_stat_history_id, values, context->job->job_history.id, false);\n\n\tts_catalog_insert_datums(rel, desc, values);\n\tts_catalog_restore_user(&sec_ctx);\n\n\ttable_close(rel, NoLock);\n}\n\nstatic void\nbgw_job_stat_history_mark_start(BgwJobStatHistoryContext *context)\n{\n\t/* Don't mark the start in case of the GUC be disabled */\n\tif (!ts_guc_enable_job_execution_logging)\n\t\treturn;\n\n\tbgw_job_stat_history_insert(context, false);\n}\n\nstatic void\nbgw_job_stat_history_update_entry(int64 bgw_job_history_id, tuple_found_func tuple_found,\n\t\t\t\t\t\t\t\t  tuple_filter_func tuple_filter, void *data, LOCKMODE lockmode)\n{\n\tif (bgw_job_history_id == INVALID_BGW_JOB_STAT_HISTORY_ID)\n\t\treturn;\n\n\tScanKeyData scankey[1];\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_bgw_job_stat_history_pkey_idx_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT8EQ,\n\t\t\t\tInt64GetDatum(bgw_job_history_id));\n\n\tCatalog *catalog = ts_catalog_get();\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, BGW_JOB_STAT_HISTORY),\n\t\t.index = catalog_get_index(catalog, BGW_JOB_STAT_HISTORY, BGW_JOB_STAT_HISTORY_PKEY_IDX),\n\t\t.nkeys = 1,\n\t\t.scankey = scankey,\n\t\t.flags = SCANNER_F_KEEPLOCK,\n\t\t.tuple_found = tuple_found,\n\t\t.filter = tuple_filter,\n\t\t.data = data,\n\t\t.lockmode = lockmode,\n\t\t.scandirection = ForwardScanDirection,\n\t};\n\n\tint num_found = ts_scanner_scan(&scanctx);\n\n\t/* We do not want to raise an error in case there is something wrong with history entries */\n\tif (num_found == 0)\n\t\t/* This might happen due to job history retention deleting entries */\n\t\tereport(DEBUG1,\n\t\t\t\t(errmsg(\"could not find job stat history entry with id \" INT64_FORMAT,\n\t\t\t\t\t\tbgw_job_history_id)));\n\telse if (num_found > 1)\n\t\tereport(DEBUG1,\n\t\t\t\t(errmsg(\"found multiple job stat history entries with id \" INT64_FORMAT,\n\t\t\t\t\t\tbgw_job_history_id)));\n}\n\nstatic ScanTupleResult\nbgw_job_stat_history_tuple_update(TupleInfo *ti, void *const data)\n{\n\tbool should_free;\n\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\tBgwJobStatHistoryContext *context = (BgwJobStatHistoryContext *) data;\n\tJsonb *job_history_data = NULL;\n\n\tDatum values[Natts_bgw_job_stat_history] = { 0 };\n\tbool nulls[Natts_bgw_job_stat_history] = { 0 };\n\tbool doReplace[Natts_bgw_job_stat_history] = { 0 };\n\n\tswitch (context->update_type)\n\t{\n\t\tcase JOB_STAT_HISTORY_UPDATE_PID:\n\t\t{\n\t\t\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_stat_history_pid)] =\n\t\t\t\tInt32GetDatum(MyProcPid);\n\t\t\tdoReplace[AttrNumberGetAttrOffset(Anum_bgw_job_stat_history_pid)] = true;\n\t\t\tbreak;\n\t\t}\n\n\t\tcase JOB_STAT_HISTORY_UPDATE_END:\n\t\t{\n\t\t\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_stat_history_execution_finish)] =\n\t\t\t\tTimestampTzGetDatum(ts_timer_get_current_timestamp());\n\t\t\tdoReplace[AttrNumberGetAttrOffset(Anum_bgw_job_stat_history_execution_finish)] = true;\n\n\t\t\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_stat_history_succeeded)] =\n\t\t\t\tBoolGetDatum((context->result == JOB_SUCCESS));\n\t\t\tdoReplace[AttrNumberGetAttrOffset(Anum_bgw_job_stat_history_succeeded)] = true;\n\n\t\t\tjob_history_data = ts_bgw_job_stat_history_build_data_info(context);\n\n\t\t\tif (job_history_data != NULL)\n\t\t\t{\n\t\t\t\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_stat_history_data)] =\n\t\t\t\t\tJsonbPGetDatum(job_history_data);\n\t\t\t\tdoReplace[AttrNumberGetAttrOffset(Anum_bgw_job_stat_history_data)] = true;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\tcase JOB_STAT_HISTORY_UPDATE_START:\n\t\t\tpg_unreachable();\n\t\t\tbreak;\n\t}\n\n\tHeapTuple new_tuple =\n\t\theap_modify_tuple(tuple, ts_scanner_get_tupledesc(ti), values, nulls, doReplace);\n\n\tts_catalog_update(ti->scanrel, new_tuple);\n\n\theap_freetuple(new_tuple);\n\n\tif (should_free)\n\t\theap_freetuple(tuple);\n\n\treturn SCAN_DONE;\n}\n\nstatic void\nbgw_job_stat_history_update(BgwJobStatHistoryContext *context)\n{\n\t/* Don't execute in case of the GUC is false and the job succeeded, because failures are always\n\t * logged\n\t */\n\tif (!ts_guc_enable_job_execution_logging && context->result == JOB_SUCCESS)\n\t\treturn;\n\n\t/* Re-read the job information because it can change during the execution by using the\n\t * `alter_job` API inside the function/procedure (i.e. job config) */\n\tBgwJob *new_job = ts_bgw_job_find(context->job->fd.id, CurrentMemoryContext, true);\n\n\t/* Set the job history information */\n\tnew_job->job_history = context->job->job_history;\n\n\t/* Use the newly loaded job in the current context to use this information to register the\n\t * execution history */\n\tcontext->job = new_job;\n\n\t/* Failures are always logged so in case of the GUC is false and a failure happens then we need\n\t * to insert all the information in the job error history table */\n\tif (!ts_guc_enable_job_execution_logging && context->result != JOB_SUCCESS)\n\t{\n\t\tbgw_job_stat_history_insert(context, true);\n\t}\n\telse\n\t{\n\t\t/* Mark the end of the previous inserted start execution */\n\t\tbgw_job_stat_history_update_entry(new_job->job_history.id,\n\t\t\t\t\t\t\t\t\t\t  bgw_job_stat_history_tuple_update,\n\t\t\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t\t\t  context,\n\t\t\t\t\t\t\t\t\t\t  RowExclusiveLock);\n\t}\n}\n\nvoid\nts_bgw_job_stat_history_update(BgwJobStatHistoryUpdateType update_type, BgwJob *job,\n\t\t\t\t\t\t\t   JobResult result, Jsonb *edata)\n{\n\tBgwJobStatHistoryContext context = {\n\t\t.result = result,\n\t\t.update_type = update_type,\n\t\t.job = job,\n\t\t.edata = edata,\n\t};\n\n\tswitch (update_type)\n\t{\n\t\tcase JOB_STAT_HISTORY_UPDATE_START:\n\t\t\tbgw_job_stat_history_mark_start(&context);\n\t\t\tbreak;\n\t\tcase JOB_STAT_HISTORY_UPDATE_END:\n\t\tcase JOB_STAT_HISTORY_UPDATE_PID:\n\t\t\tbgw_job_stat_history_update(&context);\n\t\t\tbreak;\n\t}\n}\n"
  },
  {
    "path": "src/bgw/job_stat_history.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include \"job.h\"\n#include \"job_stat.h\"\n#include \"ts_catalog/catalog.h\"\n\n#define INVALID_BGW_JOB_STAT_HISTORY_ID 0\n\ntypedef enum BgwJobStatHistoryUpdateType\n{\n\tJOB_STAT_HISTORY_UPDATE_START,\n\tJOB_STAT_HISTORY_UPDATE_END,\n\tJOB_STAT_HISTORY_UPDATE_PID,\n} BgwJobStatHistoryUpdateType;\n\nextern void ts_bgw_job_stat_history_update(BgwJobStatHistoryUpdateType update_type, BgwJob *job,\n\t\t\t\t\t\t\t\t\t\t   JobResult result, Jsonb *edata);\n"
  },
  {
    "path": "src/bgw/launcher_interface.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n\n#include <fmgr.h>\n\n#include \"compat/compat.h\"\n#include \"extension.h\"\n#include \"launcher_interface.h\"\n\n#define MIN_LOADER_API_VERSION 4\n\nextern bool\nts_bgw_worker_reserve(void)\n{\n\tPGFunction reserve = load_external_function(EXTENSION_SO, \"ts_bgw_worker_reserve\", true, NULL);\n\n\treturn DatumGetBool(\n\t\tDirectFunctionCall1(reserve, BoolGetDatum(false))); /* no function call zero */\n}\n\nextern void\nts_bgw_worker_release(void)\n{\n\tPGFunction release = load_external_function(EXTENSION_SO, \"ts_bgw_worker_release\", true, NULL);\n\n\tDirectFunctionCall1(release, BoolGetDatum(false)); /* no function call zero */\n}\n\nextern int\nts_bgw_num_unreserved(void)\n{\n\tPGFunction unreserved =\n\t\tload_external_function(EXTENSION_SO, \"ts_bgw_num_unreserved\", true, NULL);\n\n\treturn DatumGetInt32(\n\t\tDirectFunctionCall1(unreserved, BoolGetDatum(false))); /* no function call zero */\n}\n\nextern int\nts_bgw_loader_api_version(void)\n{\n\tvoid **versionptr = find_rendezvous_variable(RENDEZVOUS_BGW_LOADER_API_VERSION);\n\n\tif (*versionptr == NULL)\n\t\treturn 0;\n\treturn *((int32 *) *versionptr);\n}\n\nextern void\nts_bgw_check_loader_api_version()\n{\n\tint version = ts_bgw_loader_api_version();\n\n\tif (version < MIN_LOADER_API_VERSION)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"loader version out-of-date\"),\n\t\t\t\t errhint(\"Please restart the database to upgrade the loader version.\")));\n}\n"
  },
  {
    "path": "src/bgw/launcher_interface.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\nextern bool ts_bgw_worker_reserve(void);\nextern void ts_bgw_worker_release(void);\nextern int ts_bgw_num_unreserved(void);\nextern int ts_bgw_loader_api_version(void);\nextern void ts_bgw_check_loader_api_version(void);\n"
  },
  {
    "path": "src/bgw/scheduler.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n/*\n * This is a scheduler that takes background jobs and schedules them appropriately\n *\n * Limitations: For now the jobs are only loaded when the scheduler starts and are not\n * updated if the jobs table changes\n *\n */\n#include <postgres.h>\n\n#include <access/xact.h>\n#include <miscadmin.h>\n#include <nodes/pg_list.h>\n#include <pgstat.h>\n#include <postmaster/bgworker.h>\n#include <storage/ipc.h>\n#include <storage/latch.h>\n#include <storage/lwlock.h>\n#include <storage/proc.h>\n#include <storage/shmem.h>\n#include <tcop/tcopprot.h>\n#include <utils/acl.h>\n#include <utils/inval.h>\n#include <utils/jsonb.h>\n#include <utils/memutils.h>\n#include <utils/snapmgr.h>\n#include <utils/timestamp.h>\n\n#include \"compat/compat.h\"\n#include \"extension.h\"\n#include \"guc.h\"\n#include \"job.h\"\n#include \"job_stat.h\"\n#include \"launcher_interface.h\"\n#include \"scheduler.h\"\n#include \"timer.h\"\n#include \"version.h\"\n#include \"worker.h\"\n\n#define START_RETRY_MS (1 * INT64CONST(1000)) /* 1 seconds */\n#define ONE_SECOND_IN_MICROSECONDS 1000000\n\nstatic TimestampTz\nleast_timestamp(TimestampTz left, TimestampTz right)\n{\n\treturn (left < right ? left : right);\n}\n\nTS_FUNCTION_INFO_V1(ts_bgw_scheduler_main);\n\n/*\n * Global so the invalidate cache message can set. Don't need to protect\n * access with a lock because it's accessed only by the scheduler process.\n */\nstatic bool jobs_list_needs_update;\n\n/* has to be global to shutdown jobs on exit */\nstatic List *scheduled_jobs = NIL;\n\nstatic MemoryContext scheduler_mctx;\nstatic MemoryContext scratch_mctx;\n\n/* See the README for a state transition diagram */\ntypedef enum JobState\n{\n\t/* terminal state for now. Later we may have path to JOB_STATE_SCHEDULED */\n\tJOB_STATE_DISABLED,\n\n\t/*\n\t * This is the initial state. next states: JOB_STATE_STARTED,\n\t * JOB_STATE_DISABLED. This job is not running and has been scheduled to\n\t * be started at a later time.\n\t */\n\tJOB_STATE_SCHEDULED,\n\n\t/*\n\t * next states: JOB_STATE_TERMINATING, JOB_STATE_SCHEDULED. This job has\n\t * been started by the scheduler and is either running or finished (and\n\t * the finish has not yet been detected by the scheduler).\n\t */\n\tJOB_STATE_STARTED,\n\n\t/*\n\t * next states: JOB_STATE_SCHEDULED. The scheduler has explicitly sent a\n\t * terminate to this job but has not yet detected that it has stopped.\n\t */\n\tJOB_STATE_TERMINATING\n} JobState;\n\ntypedef struct ScheduledBgwJob\n{\n\tBgwJob job;\n\tTimestampTz next_start;\n\tTimestampTz timeout_at;\n\tJobState state;\n\tBackgroundWorkerHandle *handle;\n\n\tbool reserved_worker;\n\n\t/*\n\t * We say \"may\" here since under normal circumstances the job itself will\n\t * perform the mark_end\n\t */\n\tbool may_need_mark_end;\n\tint32 consecutive_failed_launches;\n} ScheduledBgwJob;\n\nstatic void on_failure_to_start_job(ScheduledBgwJob *sjob);\n\nstatic volatile sig_atomic_t got_SIGHUP = false;\n\nBackgroundWorkerHandle *\nts_bgw_start_worker(const char *name, const BgwParams *bgw_params)\n{\n\tBackgroundWorker worker = {\n\t\t.bgw_flags = BGWORKER_SHMEM_ACCESS | BGWORKER_BACKEND_DATABASE_CONNECTION,\n\t\t.bgw_start_time = BgWorkerStart_RecoveryFinished,\n\t\t.bgw_restart_time = BGW_NEVER_RESTART,\n\t\t.bgw_notify_pid = MyProcPid,\n\t\t.bgw_main_arg = ObjectIdGetDatum(MyDatabaseId),\n\t};\n\tBackgroundWorkerHandle *handle = NULL;\n\n\tstrlcpy(worker.bgw_name, name, BGW_MAXLEN);\n\tstrlcpy(worker.bgw_library_name, ts_extension_get_so_name(), BGW_MAXLEN);\n\tstrlcpy(worker.bgw_function_name, bgw_params->bgw_main, BGW_MAXLEN);\n\n\tmemcpy(worker.bgw_extra, bgw_params, sizeof(*bgw_params));\n\n\t/* handle needs to be allocated in long-lived memory context */\n\tMemoryContextSwitchTo(scheduler_mctx);\n\tif (!RegisterDynamicBackgroundWorker(&worker, &handle))\n\t{\n\t\telog(NOTICE, \"unable to register background worker\");\n\t\thandle = NULL;\n\t}\n\tMemoryContextSwitchTo(scratch_mctx);\n\n\treturn handle;\n}\n\n#ifdef USE_ASSERT_CHECKING\nstatic void\nassert_that_worker_has_stopped(ScheduledBgwJob *sjob)\n{\n\tpid_t pid;\n\tBgwHandleStatus status;\n\n\tAssert(sjob->reserved_worker);\n\tstatus = GetBackgroundWorkerPid(sjob->handle, &pid);\n\tAssert(BGWH_STOPPED == status);\n}\n#endif\n\nstatic void\nmark_job_as_started(ScheduledBgwJob *sjob)\n{\n\tAssert(!sjob->may_need_mark_end);\n\tsjob->consecutive_failed_launches = 0;\n\tts_bgw_job_stat_mark_start(&sjob->job);\n\tsjob->may_need_mark_end = true;\n}\n\nstatic void\nmark_job_as_ended(ScheduledBgwJob *sjob, JobResult res, Jsonb *edata)\n{\n\tAssert(sjob->may_need_mark_end);\n\tts_bgw_job_stat_mark_end(&sjob->job, res, edata);\n\tsjob->may_need_mark_end = false;\n}\n\nstatic ErrorData *\nmakeJobErrorData(ScheduledBgwJob *sjob, JobResult res)\n{\n\tErrorData *edata = (ErrorData *) palloc0(sizeof(ErrorData));\n\tedata->elevel = ERROR;\n\tedata->sqlerrcode = ERRCODE_INTERNAL_ERROR;\n\tedata->hint = NULL;\n\n\tAssert(res != JOB_SUCCESS);\n\n\tswitch (res)\n\t{\n\t\tcase JOB_FAILURE_TO_START:\n\t\t\tedata->message = \"failed to start job\";\n\t\t\tedata->detail = psprintf(\"Job %d (\\\"%s\\\") failed to start\",\n\t\t\t\t\t\t\t\t\t sjob->job.fd.id,\n\t\t\t\t\t\t\t\t\t NameStr(sjob->job.fd.application_name));\n\t\t\tbreak;\n\t\tcase JOB_FAILURE_IN_EXECUTION:\n\t\t\tedata->message = \"failed to execute job\";\n\t\t\tedata->detail = psprintf(\"Job %d (\\\"%s\\\") failed to execute.\",\n\t\t\t\t\t\t\t\t\t sjob->job.fd.id,\n\t\t\t\t\t\t\t\t\t NameStr(sjob->job.fd.application_name));\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tpg_unreachable();\n\t\t\tbreak;\n\t}\n\n\treturn edata;\n}\n\nstatic void\nworker_state_cleanup(ScheduledBgwJob *sjob)\n{\n\t/*\n\t * This function needs to be safe wrt failures occurring at any point in\n\t * the job starting process.\n\t */\n\tif (sjob->handle != NULL)\n\t{\n#ifdef USE_ASSERT_CHECKING\n\t\t/* Sanity check: worker has stopped (if it was started) */\n\t\tassert_that_worker_has_stopped(sjob);\n#endif\n\t\tpfree(sjob->handle);\n\t\tsjob->handle = NULL;\n\t}\n\n\t/*\n\t * first cleanup reserved workers before accessing db. Want to minimize\n\t * the possibility of errors before worker is released\n\t */\n\tif (sjob->reserved_worker)\n\t{\n\t\tts_bgw_worker_release();\n\t\tsjob->reserved_worker = false;\n\t}\n\n\tif (sjob->may_need_mark_end)\n\t{\n\t\tBgwJobStat *job_stat;\n\n\t\tif (!ts_bgw_job_find(sjob->job.fd.id, CurrentMemoryContext, false))\n\t\t{\n\t\t\telog(WARNING,\n\t\t\t\t \"scheduler detected that job %d was deleted after job quit\",\n\t\t\t\t sjob->job.fd.id);\n\t\t\tts_bgw_job_cache_invalidate_callback();\n\t\t\tsjob->may_need_mark_end = false;\n\t\t\treturn;\n\t\t}\n\n\t\tjob_stat = ts_bgw_job_stat_find(sjob->job.fd.id);\n\n\t\tif (job_stat && !ts_bgw_job_stat_end_was_marked(job_stat))\n\t\t{\n\t\t\t/*\n\t\t\t * Usually the job process will mark the end, but if the job gets\n\t\t\t * a signal (cancel or terminate), it won't be able to so we\n\t\t\t * should.\n\t\t\t */\n\t\t\telog(LOG, \"job %d failed\", sjob->job.fd.id);\n\t\t\tErrorData *edata = makeJobErrorData(sjob, JOB_FAILURE_IN_EXECUTION);\n\t\t\tmark_job_as_ended(sjob,\n\t\t\t\t\t\t\t  JOB_FAILURE_IN_EXECUTION,\n\t\t\t\t\t\t\t  ts_errdata_to_jsonb(edata,\n\t\t\t\t\t\t\t\t\t\t\t\t  &sjob->job.fd.proc_schema,\n\t\t\t\t\t\t\t\t\t\t\t\t  &sjob->job.fd.proc_name));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tsjob->may_need_mark_end = false;\n\t\t}\n\t}\n}\n\n/* Set the state of the job.\n * This function is responsible for setting all of the variables in ScheduledBgwJob\n * except for the job itself.\n */\nstatic void\nscheduled_bgw_job_transition_state_to(ScheduledBgwJob *sjob, JobState new_state)\n{\n#ifdef USE_ASSERT_CHECKING\n\tJobState prev_state = sjob->state;\n#endif\n\n\tBgwJobStat *job_stat;\n\n\tswitch (new_state)\n\t{\n\t\tcase JOB_STATE_DISABLED:\n\t\t\tAssert(prev_state == JOB_STATE_STARTED || prev_state == JOB_STATE_TERMINATING);\n\t\t\tsjob->handle = NULL;\n\t\t\tbreak;\n\t\tcase JOB_STATE_SCHEDULED:\n\t\t\t/* prev_state can be any value, including itself */\n\n\t\t\tworker_state_cleanup(sjob);\n\n\t\t\tjob_stat = ts_bgw_job_stat_find(sjob->job.fd.id);\n\n\t\t\tAssert(!sjob->reserved_worker);\n\t\t\tsjob->next_start =\n\t\t\t\tts_bgw_job_stat_next_start(job_stat, &sjob->job, sjob->consecutive_failed_launches);\n\t\t\tbreak;\n\t\tcase JOB_STATE_STARTED:\n\t\t\tAssert(prev_state == JOB_STATE_SCHEDULED);\n\t\t\tAssert(sjob->handle == NULL);\n\t\t\tAssert(!sjob->reserved_worker);\n\n\t\t\tStartTransactionCommand();\n\t\t\tPushActiveSnapshot(GetTransactionSnapshot());\n\n\t\t\tif (!ts_bgw_job_find(sjob->job.fd.id, CurrentMemoryContext, false))\n\t\t\t{\n\t\t\t\telog(WARNING,\n\t\t\t\t\t \"scheduler detected that job %d was deleted when starting job\",\n\t\t\t\t\t sjob->job.fd.id);\n\t\t\t\tts_bgw_job_cache_invalidate_callback();\n\t\t\t\tCommitTransactionCommand();\n\t\t\t\tMemoryContextSwitchTo(scratch_mctx);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t/* If we are unable to reserve a worker go back to the scheduled state */\n\t\t\tsjob->reserved_worker = ts_bgw_worker_reserve();\n\t\t\tif (!sjob->reserved_worker)\n\t\t\t{\n\t\t\t\telog(WARNING,\n\t\t\t\t\t \"failed to launch job %d \\\"%s\\\": out of background workers\",\n\t\t\t\t\t sjob->job.fd.id,\n\t\t\t\t\t NameStr(sjob->job.fd.application_name));\n\t\t\t\tsjob->consecutive_failed_launches++;\n\t\t\t\tscheduled_bgw_job_transition_state_to(sjob, JOB_STATE_SCHEDULED);\n\t\t\t\tPopActiveSnapshot();\n\t\t\t\tCommitTransactionCommand();\n\t\t\t\tMemoryContextSwitchTo(scratch_mctx);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * start the job before you can encounter any errors so that they\n\t\t\t * are always registered\n\t\t\t */\n\t\t\tmark_job_as_started(sjob);\n\t\t\tif (ts_bgw_job_has_timeout(&sjob->job))\n\t\t\t\tsjob->timeout_at =\n\t\t\t\t\tts_bgw_job_timeout_at(&sjob->job, ts_timer_get_current_timestamp());\n\t\t\telse\n\t\t\t\tsjob->timeout_at = DT_NOEND;\n\n\t\t\tPopActiveSnapshot();\n\t\t\tCommitTransactionCommand();\n\t\t\tMemoryContextSwitchTo(scratch_mctx);\n\n\t\t\telog(DEBUG1,\n\t\t\t\t \"launching job %d \\\"%s\\\"\",\n\t\t\t\t sjob->job.fd.id,\n\t\t\t\t NameStr(sjob->job.fd.application_name));\n\n\t\t\tsjob->handle = ts_bgw_job_start(&sjob->job, sjob->job.fd.owner);\n\t\t\tif (sjob->handle == NULL)\n\t\t\t{\n\t\t\t\telog(WARNING,\n\t\t\t\t\t \"failed to launch job %d \\\"%s\\\": failed to start a background worker\",\n\t\t\t\t\t sjob->job.fd.id,\n\t\t\t\t\t NameStr(sjob->job.fd.application_name));\n\t\t\t\ton_failure_to_start_job(sjob);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tAssert(sjob->reserved_worker);\n\t\t\tbreak;\n\t\tcase JOB_STATE_TERMINATING:\n\t\t\tAssert(prev_state == JOB_STATE_STARTED);\n\t\t\tAssert(sjob->handle != NULL);\n\t\t\tAssert(sjob->reserved_worker);\n\t\t\tTerminateBackgroundWorker(sjob->handle);\n\t\t\tbreak;\n\t}\n\tsjob->state = new_state;\n}\n\nstatic void\non_failure_to_start_job(ScheduledBgwJob *sjob)\n{\n\tStartTransactionCommand();\n\tPushActiveSnapshot(GetTransactionSnapshot());\n\n\tif (!ts_bgw_job_find(sjob->job.fd.id, CurrentMemoryContext, false))\n\t{\n\t\telog(WARNING,\n\t\t\t \"scheduler detected that job %d was deleted while failing to start\",\n\t\t\t sjob->job.fd.id);\n\t\tts_bgw_job_cache_invalidate_callback();\n\t}\n\telse\n\t{\n\t\t/* restore the original next_start to maintain priority (it is unset during mark_start) */\n\t\tif (sjob->next_start != DT_NOBEGIN)\n\t\t\tts_bgw_job_stat_set_next_start(sjob->job.fd.id, sjob->next_start);\n\t\tErrorData *edata = makeJobErrorData(sjob, JOB_FAILURE_TO_START);\n\t\tmark_job_as_ended(sjob,\n\t\t\t\t\t\t  JOB_FAILURE_TO_START,\n\t\t\t\t\t\t  ts_errdata_to_jsonb(edata,\n\t\t\t\t\t\t\t\t\t\t\t  &sjob->job.fd.proc_schema,\n\t\t\t\t\t\t\t\t\t\t\t  &sjob->job.fd.proc_name));\n\t}\n\tscheduled_bgw_job_transition_state_to(sjob, JOB_STATE_SCHEDULED);\n\tPopActiveSnapshot();\n\tCommitTransactionCommand();\n\tMemoryContextSwitchTo(scratch_mctx);\n}\n\nstatic inline void\nbgw_scheduler_on_postmaster_death(void)\n{\n\t/*\n\t * Don't call exit hooks cause we want to bail out quickly. We don't care\n\t * about cleaning up shared memory in this case anyway since it's\n\t * potentially corrupt.\n\t */\n\ton_exit_reset();\n\tereport(FATAL,\n\t\t\t(errcode(ERRCODE_ADMIN_SHUTDOWN),\n\t\t\t errmsg(\"postmaster exited while TimescaleDB scheduler was working\")));\n}\n\n/*\n * This function starts a job.\n * To correctly count crashes we need to mark the start of a job in a separate\n * txn before we kick off the actual job. Thus this function cannot be run\n * from within a transaction.\n */\nstatic void\nscheduled_ts_bgw_job_start(ScheduledBgwJob *sjob,\n\t\t\t\t\t\t   register_background_worker_callback_type bgw_register)\n{\n\tpid_t pid;\n\tBgwHandleStatus status;\n\n\tscheduled_bgw_job_transition_state_to(sjob, JOB_STATE_STARTED);\n\n\tif (sjob->state != JOB_STATE_STARTED)\n\t\treturn;\n\n\tAssert(sjob->handle != NULL);\n\tif (bgw_register != NULL)\n\t\tbgw_register(sjob->handle, scheduler_mctx);\n\n\tstatus = WaitForBackgroundWorkerStartup(sjob->handle, &pid);\n\tswitch (status)\n\t{\n\t\tcase BGWH_POSTMASTER_DIED:\n\t\t\tbgw_scheduler_on_postmaster_death();\n\t\t\tbreak;\n\t\tcase BGWH_STARTED:\n\t\t\t/* all good */\n\t\t\tbreak;\n\t\tcase BGWH_STOPPED:\n\t\t\tStartTransactionCommand();\n\t\t\tscheduled_bgw_job_transition_state_to(sjob, JOB_STATE_SCHEDULED);\n\t\t\tCommitTransactionCommand();\n\t\t\tMemoryContextSwitchTo(scratch_mctx);\n\t\t\tbreak;\n\t\tcase BGWH_NOT_YET_STARTED:\n\t\t\t/* should not be possible */\n\t\t\telog(ERROR, \"unexpected bgworker state %d\", status);\n\t\t\tbreak;\n\t}\n}\n\nstatic void\nterminate_and_cleanup_job(ScheduledBgwJob *sjob)\n{\n\tif (sjob->handle != NULL)\n\t{\n\t\tpid_t pid;\n\t\tBgwHandleStatus status;\n\n\t\tstatus = GetBackgroundWorkerPid(sjob->handle, &pid);\n\t\tif (status == BGWH_STARTED)\n\t\t{\n\t\t\t/* Try graceful cancellation first (SIGINT via pg_cancel_backend) */\n\t\t\tDirectFunctionCall1(pg_cancel_backend, Int32GetDatum(pid));\n\n\t\t\t/* Poll for up to 3 seconds for the worker to exit */\n\t\t\tfor (int i = 0; i < 30; i++)\n\t\t\t{\n\t\t\t\tpg_usleep(100000); /* 100ms */\n\t\t\t\tstatus = GetBackgroundWorkerPid(sjob->handle, &pid);\n\t\t\t\tif (status == BGWH_STOPPED)\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (status != BGWH_STOPPED)\n\t\t\t{\n\t\t\t\telog(WARNING, \"job %d did not exit after SIGINT, sending SIGTERM\", sjob->job.fd.id);\n\t\t\t\tTerminateBackgroundWorker(sjob->handle);\n\t\t\t\tWaitForBackgroundWorkerShutdown(sjob->handle);\n\t\t\t}\n\t\t}\n\t\telse if (status != BGWH_STOPPED)\n\t\t{\n\t\t\tTerminateBackgroundWorker(sjob->handle);\n\t\t\tWaitForBackgroundWorkerShutdown(sjob->handle);\n\t\t}\n\t}\n\tsjob->may_need_mark_end = false;\n\tworker_state_cleanup(sjob);\n}\n\n/*\n *  Update the given job list with whatever is in the bgw_job table. For overlapping jobs,\n *  copy over any existing scheduler info from the given jobs list.\n *  Assume that both lists are ordered by job ID.\n *  Note that this function call will destroy cur_jobs_list and return a new list.\n */\nList *\nts_update_scheduled_jobs_list(List *cur_jobs_list, MemoryContext mctx)\n{\n\tList *new_jobs = ts_bgw_job_get_scheduled(sizeof(ScheduledBgwJob), mctx);\n\tListCell *new_ptr = list_head(new_jobs);\n\tListCell *cur_ptr = list_head(cur_jobs_list);\n\n\telog(DEBUG2, \"updating scheduled jobs list\");\n\n\twhile (cur_ptr != NULL && new_ptr != NULL)\n\t{\n\t\tScheduledBgwJob *new_sjob = lfirst(new_ptr);\n\t\tScheduledBgwJob *cur_sjob = lfirst(cur_ptr);\n\n\t\tif (cur_sjob->job.fd.id < new_sjob->job.fd.id)\n\t\t{\n\t\t\t/*\n\t\t\t * We don't need cur_sjob anymore. Make sure to clean up the job\n\t\t\t * state. Then keep advancing cur pointer until we catch up.\n\t\t\t */\n\t\t\tterminate_and_cleanup_job(cur_sjob);\n\n\t\t\tcur_ptr = lnext(cur_jobs_list, cur_ptr);\n\t\t\tcontinue;\n\t\t}\n\t\tif (cur_sjob->job.fd.id == new_sjob->job.fd.id)\n\t\t{\n\t\t\t/*\n\t\t\t * Then this job already exists. Copy over any state and advance\n\t\t\t * both pointers.\n\t\t\t */\n\t\t\tcur_sjob->job = new_sjob->job;\n\t\t\t*new_sjob = *cur_sjob;\n\n\t\t\t/* reload the scheduling information from the job_stats */\n\t\t\tif (cur_sjob->state == JOB_STATE_SCHEDULED)\n\t\t\t\tscheduled_bgw_job_transition_state_to(new_sjob, JOB_STATE_SCHEDULED);\n\n\t\t\tcur_ptr = lnext(cur_jobs_list, cur_ptr);\n\t\t\tnew_ptr = lnext(new_jobs, new_ptr);\n\t\t}\n\t\telse if (cur_sjob->job.fd.id > new_sjob->job.fd.id)\n\t\t{\n\t\t\tscheduled_bgw_job_transition_state_to(new_sjob, JOB_STATE_SCHEDULED);\n\t\t\telog(DEBUG1,\n\t\t\t\t \"sjob %d was new, its fixed_schedule is %d\",\n\t\t\t\t new_sjob->job.fd.id,\n\t\t\t\t new_sjob->job.fd.fixed_schedule);\n\n\t\t\t/* Advance the new_job list until we catch up to cur_list */\n\t\t\tnew_ptr = lnext(new_jobs, new_ptr);\n\t\t}\n\t}\n\n\t/* If there's more stuff in cur_list, clean it all up */\n\tif (cur_ptr != NULL)\n\t{\n\t\tListCell *ptr;\n\n\t\tfor_each_cell (ptr, cur_jobs_list, cur_ptr)\n\t\t\tterminate_and_cleanup_job(lfirst(ptr));\n\t}\n\n\tif (new_ptr != NULL)\n\t{\n\t\t/* Then there are more new jobs. Initialize all of them. */\n\t\tListCell *ptr;\n\n\t\tfor_each_cell (ptr, new_jobs, new_ptr)\n\t\t\tscheduled_bgw_job_transition_state_to(lfirst(ptr), JOB_STATE_SCHEDULED);\n\t}\n\n\t/* Free the old list */\n\tlist_free_deep(cur_jobs_list);\n\treturn new_jobs;\n}\n\n#ifdef TS_DEBUG\n\n/* Only used by test code */\nvoid\nts_populate_scheduled_job_tuple(ScheduledBgwJob *sjob, Datum *values)\n{\n\tif (sjob == NULL)\n\t\treturn;\n\n\tvalues[0] = Int32GetDatum(sjob->job.fd.id);\n\tvalues[1] = NameGetDatum(&sjob->job.fd.application_name);\n\tvalues[2] = IntervalPGetDatum(&sjob->job.fd.schedule_interval);\n\tvalues[3] = IntervalPGetDatum(&sjob->job.fd.max_runtime);\n\tvalues[4] = Int32GetDatum(sjob->job.fd.max_retries);\n\tvalues[5] = IntervalPGetDatum(&sjob->job.fd.retry_period);\n\tvalues[6] = TimestampTzGetDatum(sjob->next_start);\n\tvalues[7] = TimestampTzGetDatum(sjob->timeout_at);\n\tvalues[8] = BoolGetDatum(sjob->reserved_worker);\n\tvalues[9] = BoolGetDatum(sjob->may_need_mark_end);\n}\n#endif\n\nstatic int\ncmp_next_start(const ListCell *left_cell, const ListCell *right_cell)\n{\n\tScheduledBgwJob *left_sjob = lfirst(left_cell);\n\tScheduledBgwJob *right_sjob = lfirst(right_cell);\n\n\tif (left_sjob->next_start < right_sjob->next_start)\n\t\treturn -1;\n\n\tif (left_sjob->next_start > right_sjob->next_start)\n\t\treturn 1;\n\n\treturn 0;\n}\n\nstatic void\nstart_scheduled_jobs(register_background_worker_callback_type bgw_register)\n{\n\tList *ordered_scheduled_jobs;\n\tListCell *lc;\n\tAssert(CurrentMemoryContext == scratch_mctx);\n\n\t/* Order jobs by increasing next_start */\n\t/* list_sort does in-place sort - so make a copy and sort that */\n\tordered_scheduled_jobs = list_copy(scheduled_jobs);\n\tlist_sort(ordered_scheduled_jobs, cmp_next_start);\n\n\tforeach (lc, ordered_scheduled_jobs)\n\t{\n\t\tScheduledBgwJob *sjob = lfirst(lc);\n\n\t\tint64 job_start_diff = sjob->next_start - ts_timer_get_current_timestamp();\n\n\t\tif (sjob->state == JOB_STATE_SCHEDULED &&\n\t\t\t(job_start_diff <= 0 || sjob->next_start == DT_NOBEGIN))\n\t\t{\n\t\t\telog(DEBUG2, \"starting scheduled job %d\", sjob->job.fd.id);\n\t\t\tscheduled_ts_bgw_job_start(sjob, bgw_register);\n\t\t}\n\t\telse\n\t\t{\n\t\t\telog(DEBUG5,\n\t\t\t\t \"starting scheduled job %d in \" INT64_FORMAT \" seconds\",\n\t\t\t\t sjob->job.fd.id,\n\t\t\t\t job_start_diff / ONE_SECOND_IN_MICROSECONDS);\n\t\t}\n\t}\n\n\tlist_free(ordered_scheduled_jobs);\n}\n\n/* Returns the earliest time the scheduler should start a job that is waiting to be started */\nstatic TimestampTz\nearliest_wakeup_to_start_next_job()\n{\n\tListCell *lc;\n\tTimestampTz earliest = DT_NOEND;\n\tTimestampTz now = ts_timer_get_current_timestamp();\n\n\tforeach (lc, scheduled_jobs)\n\t{\n\t\tScheduledBgwJob *sjob = lfirst(lc);\n\n\t\tif (sjob->state == JOB_STATE_SCHEDULED)\n\t\t{\n\t\t\tTimestampTz start = sjob->next_start;\n\t\t\t/* if the start is less than now, this means we tried and failed to start it already, so\n\t\t\t * use the retry period */\n\t\t\tif (start < now)\n\t\t\t\tstart = TimestampTzPlusMilliseconds(now, START_RETRY_MS);\n\t\t\tearliest = least_timestamp(earliest, start);\n\t\t}\n\t}\n\treturn earliest;\n}\n\n/* Returns the earliest time the scheduler needs to kill a job according to its timeout  */\nstatic TimestampTz\nearliest_job_timeout()\n{\n\tListCell *lc;\n\tTimestampTz earliest = DT_NOEND;\n\n\tforeach (lc, scheduled_jobs)\n\t{\n\t\tScheduledBgwJob *sjob = lfirst(lc);\n\n\t\tif (sjob->state == JOB_STATE_STARTED)\n\t\t\tearliest = least_timestamp(earliest, sjob->timeout_at);\n\t}\n\treturn earliest;\n}\n\n/* Special exit function only used in shmem_exit_callback.\n * Do not call the normal cleanup function (worker_state_cleanup), because\n * 1) we do not wait for the BGW to terminate,\n * 2) we cannot access the database at this time, so we should not be\n *    trying to update the bgw_stat table.\n */\nstatic void\nterminate_all_jobs_and_release_workers()\n{\n\tListCell *lc;\n\n\tforeach (lc, scheduled_jobs)\n\t{\n\t\tScheduledBgwJob *sjob = lfirst(lc);\n\n\t\t/*\n\t\t * Clean up the background workers. Don't worry about state of the\n\t\t * sjobs, because this callback might have interrupted a state\n\t\t * transition.\n\t\t */\n\t\tif (sjob->handle != NULL)\n\t\t\tTerminateBackgroundWorker(sjob->handle);\n\n\t\tif (sjob->reserved_worker)\n\t\t{\n\t\t\tts_bgw_worker_release();\n\t\t\tsjob->reserved_worker = false;\n\t\t}\n\t}\n}\n\nstatic void\nwait_for_all_jobs_to_shutdown()\n{\n\tListCell *lc;\n\n\tforeach (lc, scheduled_jobs)\n\t{\n\t\tScheduledBgwJob *sjob = lfirst(lc);\n\n\t\tif (sjob->state == JOB_STATE_STARTED || sjob->state == JOB_STATE_TERMINATING)\n\t\t\tWaitForBackgroundWorkerShutdown(sjob->handle);\n\t}\n}\n\nstatic void\ncheck_for_stopped_and_timed_out_jobs()\n{\n\tListCell *lc;\n\n\tforeach (lc, scheduled_jobs)\n\t{\n\t\tBgwHandleStatus status;\n\t\tpid_t pid;\n\t\tScheduledBgwJob *sjob = lfirst(lc);\n\t\tTimestampTz now = ts_timer_get_current_timestamp();\n\n\t\tif (sjob->state != JOB_STATE_STARTED && sjob->state != JOB_STATE_TERMINATING)\n\t\t\tcontinue;\n\n\t\tstatus = GetBackgroundWorkerPid(sjob->handle, &pid);\n\n\t\tswitch (status)\n\t\t{\n\t\t\tcase BGWH_POSTMASTER_DIED:\n\t\t\t\tbgw_scheduler_on_postmaster_death();\n\t\t\t\tbreak;\n\t\t\tcase BGWH_NOT_YET_STARTED:\n\t\t\t\telog(ERROR, \"unexpected bgworker state %d\", status);\n\t\t\t\tbreak;\n\t\t\tcase BGWH_STARTED:\n\t\t\t\t/* still running */\n\t\t\t\tif (sjob->state == JOB_STATE_STARTED && now >= sjob->timeout_at)\n\t\t\t\t{\n\t\t\t\t\telog(WARNING,\n\t\t\t\t\t\t \"terminating background worker \\\"%s\\\" due to timeout\",\n\t\t\t\t\t\t NameStr(sjob->job.fd.application_name));\n\t\t\t\t\tscheduled_bgw_job_transition_state_to(sjob, JOB_STATE_TERMINATING);\n\t\t\t\t\tAssert(sjob->state != JOB_STATE_STARTED);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase BGWH_STOPPED:\n\t\t\t\tStartTransactionCommand();\n\t\t\t\tPushActiveSnapshot(GetTransactionSnapshot());\n\t\t\t\tscheduled_bgw_job_transition_state_to(sjob, JOB_STATE_SCHEDULED);\n\t\t\t\tPopActiveSnapshot();\n\t\t\t\tCommitTransactionCommand();\n\t\t\t\tMemoryContextSwitchTo(scratch_mctx);\n\t\t\t\tAssert(sjob->state != JOB_STATE_STARTED);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/* This is the guts of the scheduler which runs the main loop.\n * The parameter ttl_ms gives a maximum time to run the loop (after which\n * the loop will exit). This functionality is used to ease testing.\n * In production, ttl_ms should be < 0 to signal that the loop should\n * run forever (or until the process gets a signal).\n *\n * The scheduler uses 2 memory contexts for its operation: scheduler_mctx\n * for long-lived objects and scratch_mctx for short-lived objects.\n * After every iteration of the scheduling main loop scratch_mctx gets\n * reset. Special care needs to be taken in regards to memory contexts\n * since StartTransactionCommand creates and switches to a transaction\n * memory context which gets deleted on CommitTransactionCommand which\n * switches CurrentMemoryContext back to TopMemoryContext. So operations\n * wrapped in Start/CommitTransactionCommit will not happen in scratch_mctx\n * but will get freed on CommitTransactionCommand.\n */\nvoid\nts_bgw_scheduler_process(int32 run_for_interval_ms,\n\t\t\t\t\t\t register_background_worker_callback_type bgw_register)\n{\n\tTimestampTz start = ts_timer_get_current_timestamp();\n\tTimestampTz quit_time = DT_NOEND;\n\n\tlog_min_messages = ts_guc_bgw_log_level;\n\n\tpgstat_report_activity(STATE_RUNNING, NULL);\n\n\t/* If we are restoring or upgrading, don't schedule anything. Just\n\t * exit. */\n\tif (ts_guc_restoring || IsBinaryUpgrade)\n\t{\n\t\tereport(LOG,\n\t\t\t\terrmsg(\"scheduler for database %u exiting with exit status %d\",\n\t\t\t\t\t   MyDatabaseId,\n\t\t\t\t\t   ts_debug_bgw_scheduler_exit_status),\n\t\t\t\terrdetail(\"the database is restoring or upgrading\"));\n\t\tterminate_all_jobs_and_release_workers();\n\t\tgoto scheduler_exit;\n\t}\n\n\t/* txn to read the list of jobs from the DB */\n\tStartTransactionCommand();\n\tPushActiveSnapshot(GetTransactionSnapshot());\n\tscheduled_jobs = ts_update_scheduled_jobs_list(scheduled_jobs, scheduler_mctx);\n\tPopActiveSnapshot();\n\tCommitTransactionCommand();\n\tMemoryContextSwitchTo(scratch_mctx);\n\n\tjobs_list_needs_update = false;\n\n\tif (run_for_interval_ms > 0)\n\t\tquit_time = TimestampTzPlusMilliseconds(start, run_for_interval_ms);\n\n\telog(DEBUG1, \"database scheduler for database %u starting\", MyDatabaseId);\n\n\t/*\n\t * on SIGTERM the process will usually die from the CHECK_FOR_INTERRUPTS\n\t * in the die() called from the sigterm handler. Child reaping is then\n\t * handled in the before_shmem_exit,\n\t * bgw_scheduler_before_shmem_exit_callback.\n\t */\n\twhile (quit_time > ts_timer_get_current_timestamp() && !ProcDiePending && !ts_shutdown_bgw)\n\t{\n\t\tTimestampTz next_wakeup = quit_time;\n\t\tAssert(CurrentMemoryContext == scratch_mctx);\n\n\t\t/* start jobs, and then check when to next wake up */\n\t\telog(DEBUG5, \"scheduler wakeup in database %u\", MyDatabaseId);\n\t\tstart_scheduled_jobs(bgw_register);\n\t\tnext_wakeup = least_timestamp(next_wakeup, earliest_wakeup_to_start_next_job());\n\t\tnext_wakeup = least_timestamp(next_wakeup, earliest_job_timeout());\n\n\t\tpgstat_report_activity(STATE_IDLE, NULL);\n\t\tts_timer_wait(next_wakeup);\n\t\tpgstat_report_activity(STATE_RUNNING, NULL);\n\n\t\tCHECK_FOR_INTERRUPTS();\n\n\t\tif (got_SIGHUP)\n\t\t{\n\t\t\tgot_SIGHUP = false;\n\t\t\tProcessConfigFile(PGC_SIGHUP);\n\t\t\tlog_min_messages = ts_guc_bgw_log_level;\n\t\t}\n\n\t\t/*\n\t\t * Process any cache invalidation message that indicates we need to\n\t\t * update the jobs list\n\t\t */\n\t\tAcceptInvalidationMessages();\n\n\t\tif (jobs_list_needs_update)\n\t\t{\n\t\t\tStartTransactionCommand();\n\t\t\tAssert(CurrentMemoryContext == CurTransactionContext);\n\t\t\tscheduled_jobs = ts_update_scheduled_jobs_list(scheduled_jobs, scheduler_mctx);\n\t\t\tCommitTransactionCommand();\n\t\t\tMemoryContextSwitchTo(scratch_mctx);\n\t\t\tjobs_list_needs_update = false;\n\t\t}\n\n\t\tcheck_for_stopped_and_timed_out_jobs();\n\n\t\tMemoryContextReset(scratch_mctx);\n\t}\n\n\telog(DEBUG1,\n\t\t \"scheduler for database %u exiting with exit status %d\",\n\t\t MyDatabaseId,\n\t\t ts_debug_bgw_scheduler_exit_status);\n\n#ifdef TS_DEBUG\n\tif (ts_shutdown_bgw)\n\t\telog(WARNING, \"bgw scheduler stopped due to shutdown_bgw guc\");\n#endif\n\nscheduler_exit:\n\tCHECK_FOR_INTERRUPTS();\n\n\twait_for_all_jobs_to_shutdown();\n\tcheck_for_stopped_and_timed_out_jobs();\n\tscheduled_jobs = NIL;\n\tproc_exit(ts_debug_bgw_scheduler_exit_status);\n}\n\nstatic void\nbgw_scheduler_before_shmem_exit_callback(int code, Datum arg)\n{\n\tterminate_all_jobs_and_release_workers();\n}\n\nvoid\nts_bgw_scheduler_setup_callbacks()\n{\n\tbefore_shmem_exit(bgw_scheduler_before_shmem_exit_callback, PointerGetDatum(NULL));\n}\n\n/* some of the scheduler mock code calls functions from this file without going through\n * the main loop so we need a way to setup the memory contexts\n */\nvoid\nts_bgw_scheduler_setup_mctx()\n{\n\tscheduler_mctx = AllocSetContextCreate(TopMemoryContext, \"Scheduler\", ALLOCSET_DEFAULT_SIZES);\n\tscratch_mctx =\n\t\tAllocSetContextCreate(scheduler_mctx, \"SchedulerScratch\", ALLOCSET_DEFAULT_SIZES);\n\tMemoryContextSwitchTo(scratch_mctx);\n}\n\nstatic void\nhandle_sighup(SIGNAL_ARGS)\n{\n\t/* based on av_sighup_handler */\n\tint save_errno = errno;\n\n\tgot_SIGHUP = true;\n\tSetLatch(MyLatch);\n\n\terrno = save_errno;\n}\n\n/*\n * Register SIGTERM and SIGHUP handlers for bgw_scheduler.\n * This function _must_ be called with signals blocked, i.e., after calling\n * BackgroundWorkerBlockSignals\n */\nvoid\nts_bgw_scheduler_register_signal_handlers(void)\n{\n\t/*\n\t * do not use the default `bgworker_die` sigterm handler because it does\n\t * not respect critical sections\n\t */\n\tpqsignal(SIGTERM, die);\n\tpqsignal(SIGHUP, handle_sighup);\n\n\t/* Some SIGHUPS may already have been dropped, so we must load the file here */\n\tgot_SIGHUP = false;\n\tProcessConfigFile(PGC_SIGHUP);\n\tlog_min_messages = ts_guc_bgw_log_level;\n}\n\nDatum\nts_bgw_scheduler_main(PG_FUNCTION_ARGS)\n{\n\tBackgroundWorkerBlockSignals();\n\t/* Setup any signal handlers here */\n\tts_bgw_scheduler_register_signal_handlers();\n\tBackgroundWorkerUnblockSignals();\n\n\tts_bgw_scheduler_setup_callbacks();\n\n\tpgstat_report_appname(SCHEDULER_APPNAME);\n\n\tts_bgw_scheduler_setup_mctx();\n\n\tts_bgw_scheduler_process(-1, NULL);\n\n\tAssert(scheduled_jobs == NIL);\n\tMemoryContextSwitchTo(TopMemoryContext);\n\tMemoryContextDelete(scheduler_mctx);\n\n\tPG_RETURN_VOID();\n};\n\nvoid\nts_bgw_job_cache_invalidate_callback()\n{\n\tjobs_list_needs_update = true;\n}\n"
  },
  {
    "path": "src/bgw/scheduler.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include \"compat/compat.h\"\n#include <postgres.h>\n#include <fmgr.h>\n#include <postmaster/bgworker.h>\n\n#include \"timer.h\"\n#include \"worker.h\"\n\ntypedef struct ScheduledBgwJob ScheduledBgwJob;\n\n/* callback used in testing */\ntypedef void (*register_background_worker_callback_type)(BackgroundWorkerHandle *,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t MemoryContext scheduler_ctx);\n\n/* Exposed for testing */\nextern List *ts_update_scheduled_jobs_list(List *cur_jobs_list, MemoryContext mctx);\n#ifdef TS_DEBUG\nextern void ts_populate_scheduled_job_tuple(ScheduledBgwJob *sjob, Datum *values);\n#endif\n\nextern void ts_bgw_scheduler_process(int32 run_for_interval_ms,\n\t\t\t\t\t\t\t\t\t register_background_worker_callback_type bgw_register);\n\n/* exposed for access by mock */\nextern void ts_bgw_scheduler_setup_callbacks(void);\n\nextern void ts_bgw_job_cache_invalidate_callback(void);\nextern void ts_bgw_scheduler_register_signal_handlers(void);\nextern void ts_bgw_scheduler_setup_mctx(void);\n\nextern BackgroundWorkerHandle *ts_bgw_start_worker(const char *name, const BgwParams *bgw_params);\n"
  },
  {
    "path": "src/bgw/timer.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/xact.h>\n#include <miscadmin.h>\n#include <pgstat.h>\n#include <postmaster/bgworker.h>\n#include <storage/ipc.h>\n#include <storage/latch.h>\n#include <storage/lwlock.h>\n#include <storage/proc.h>\n#include <storage/shmem.h>\n#include <utils/jsonb.h>\n#include <utils/memutils.h>\n#include <utils/snapmgr.h>\n#include <utils/timestamp.h>\n\n#include \"compat/compat.h\"\n#include \"config.h\"\n#include \"timer.h\"\n\n#define MAX_TIMEOUT (5 * INT64CONST(1000))\n#define MILLISECS_PER_SEC INT64CONST(1000)\n#define USECS_PER_MILLISEC INT64CONST(1000)\n\nstatic inline void\non_postmaster_death(void)\n{\n\t/*\n\t * Don't call exit hooks cause we want to bail out quickly. We don't care\n\t * about cleaning up shared memory in this case anyway since it's\n\t * potentially corrupt.\n\t */\n\ton_exit_reset();\n\tereport(FATAL,\n\t\t\t(errcode(ERRCODE_ADMIN_SHUTDOWN),\n\t\t\t errmsg(\"postmaster exited while timescaledb scheduler was working\")));\n}\n\nstatic int64\nget_timeout_millisec(TimestampTz by_time)\n{\n\tlong timeout_sec = 0;\n\tint timeout_usec = 0;\n\n\tif (TIMESTAMP_IS_NOBEGIN(by_time))\n\t\treturn 0;\n\n\tif (TIMESTAMP_IS_NOEND(by_time))\n\t\treturn PG_INT64_MAX;\n\n\tTimestampDifference(GetCurrentTimestamp(), by_time, &timeout_sec, &timeout_usec);\n\n\tif (timeout_sec < 0 || timeout_usec < 0)\n\t\treturn 0;\n\n\treturn (int64) ((timeout_sec * MILLISECS_PER_SEC) +\n\t\t\t\t\t(((int64) timeout_usec) / USECS_PER_MILLISEC));\n}\n\nstatic bool\nwait_using_wait_latch(TimestampTz until)\n{\n\tint wl_rc;\n\n\tint64 timeout = get_timeout_millisec(until);\n\n\tAssert(timeout >= 0 && \"get_timeout_millisec underflow\");\n\n\tif (timeout > MAX_TIMEOUT)\n\t\ttimeout = MAX_TIMEOUT;\n\n\t/* Wait latch requires timeout to be <= INT_MAX */\n\tif (timeout > (int64) INT_MAX)\n\t\ttimeout = INT_MAX;\n\n\twl_rc = WaitLatch(MyLatch,\n\t\t\t\t\t  WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,\n\t\t\t\t\t  timeout,\n\t\t\t\t\t  PG_WAIT_EXTENSION);\n\tResetLatch(MyLatch);\n\tif (wl_rc & WL_POSTMASTER_DEATH)\n\t\ton_postmaster_death();\n\n\treturn true;\n}\n\nstatic const Timer standard_timer = {\n\t.get_current_timestamp = GetCurrentTimestamp,\n\t.wait = wait_using_wait_latch,\n};\n\nstatic const Timer *current_timer_implementation = &standard_timer;\n\nstatic inline const Timer *\ntimer_get()\n{\n\treturn current_timer_implementation;\n}\n\nbool\nts_timer_wait(TimestampTz until)\n{\n\treturn timer_get()->wait(until);\n}\n\nTimestampTz\nts_timer_get_current_timestamp()\n{\n\treturn timer_get()->get_current_timestamp();\n}\n\n#ifdef TS_DEBUG\nvoid\nts_timer_set(const Timer *timer)\n{\n\tcurrent_timer_implementation = timer;\n}\n\nconst Timer *\nts_get_standard_timer()\n{\n\treturn &standard_timer;\n}\n#endif\n"
  },
  {
    "path": "src/bgw/timer.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <utils/timestamp.h>\n\n#include \"config.h\"\n#include \"export.h\"\n\ntypedef struct Timer\n{\n\tTimestampTz (*get_current_timestamp)();\n\tbool (*wait)(TimestampTz until);\n\n} Timer;\n\nextern bool ts_timer_wait(TimestampTz until);\nextern TSDLLEXPORT TimestampTz ts_timer_get_current_timestamp(void);\n\n#ifdef TS_DEBUG\nextern void ts_timer_set(const Timer *timer);\nextern const Timer *ts_get_standard_timer(void);\n#endif\n"
  },
  {
    "path": "src/bgw/worker.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include <postmaster/bgworker.h>\n\n/**\n * Parameters to background workers.\n *\n * Do not add data here that cannot be simply copied to the background worker\n * using memcpy(3). If it is necessary to add fields that cannot simply be\n * copied, we need to start using the send and recv functions for the types.\n *\n * Only one of `job_id` and `ttl` is passed currently, with `job_id` being used\n * for normal jobs and `ttl` being used for tests.\n *\n * The `bgw_main` is the function to execute when starting the job and is\n * different depending on whether this is a test runner or the real runner.\n *\n * @see ts_bgw_db_scheduler_test_main\n * @see ts_bgw_job_entrypoint\n */\ntypedef struct BgwParams\n{\n\t/** User oid to run the job as. Used when initializing the database\n\t * connection. */\n\tOid user_oid;\n\n\t/** Job id to use for the worker when executing the job */\n\tint32 job_id;\n\n\t/** Job history information to use for the worker when recording the job execution */\n\tint64 job_history_id;\n\tTimestampTz job_history_execution_start;\n\n\t/** Time to live. Only used in tests. */\n\tint32 ttl;\n\n\t/** Name of function to call when starting the background worker. */\n\tchar bgw_main[BGW_MAXLEN];\n} BgwParams;\n\n/**\n * Compile-time check to ensure that the size of BgwParams fit into the bgw_extra field\n * of BackgroundWorker.\n */\nStaticAssertDecl(sizeof(BgwParams) <= sizeof(((BackgroundWorker *) 0)->bgw_extra),\n\t\t\t\t \"sizeof(BgwParams) exceeds sizeof(bgw_extra) field of BackgroundWorker\");\n"
  },
  {
    "path": "src/bgw_policy/CMakeLists.txt",
    "content": "set(SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/policy.c\n            ${CMAKE_CURRENT_SOURCE_DIR}/chunk_stats.c)\ntarget_sources(${PROJECT_NAME} PRIVATE ${SOURCES})\n"
  },
  {
    "path": "src/bgw_policy/chunk_stats.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <executor/tuptable.h>\n\n#include \"bgw/job.h\"\n#include \"chunk_stats.h\"\n#include \"policy.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"utils.h\"\n\n#include \"compat/compat.h\"\n\nstatic ScanTupleResult\nbgw_policy_chunk_stats_tuple_found(TupleInfo *ti, void *const data)\n{\n\tBgwPolicyChunkStats **chunk_stats = (BgwPolicyChunkStats **) data;\n\n\t*chunk_stats =\n\t\tSTRUCT_FROM_SLOT(ti->slot, ti->mctx, BgwPolicyChunkStats, FormData_bgw_policy_chunk_stats);\n\treturn SCAN_CONTINUE;\n}\n\n/* Cascades deletes via the job delete function */\nstatic ScanTupleResult\nbgw_policy_chunk_stats_delete_via_job_tuple_found(TupleInfo *ti, void *const data)\n{\n\tbool isnull;\n\tDatum job_id = slot_getattr(ti->slot, Anum_bgw_policy_chunk_stats_job_id, &isnull);\n\n\tAssert(!isnull);\n\t/* This call will actually delete the row for us */\n\tts_bgw_job_delete_by_id(DatumGetInt32(job_id));\n\n\treturn SCAN_CONTINUE;\n}\n\n/*\n * Delete all chunk_stat rows associated with this job_id.\n * To prevent infinite recursive calls from the job <-> policy tables, we do not cascade deletes in\n * this function. Instead, the caller must be responsible for making sure that the delete cascades\n * to the job corresponding to this policy.\n */\nvoid\nts_bgw_policy_chunk_stats_delete_row_only_by_job_id(int32 job_id)\n{\n\tScanKeyData scankey[1];\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_bgw_policy_chunk_stats_job_id_chunk_id_idx_job_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(job_id));\n\n\tts_catalog_scan_all(BGW_POLICY_CHUNK_STATS,\n\t\t\t\t\t\tBGW_POLICY_CHUNK_STATS_JOB_ID_CHUNK_ID_IDX,\n\t\t\t\t\t\tscankey,\n\t\t\t\t\t\t1,\n\t\t\t\t\t\tts_bgw_policy_delete_row_only_tuple_found,\n\t\t\t\t\t\tRowExclusiveLock,\n\t\t\t\t\t\tNULL);\n}\n\n/*\n * Delete all chunk_stat rows associated with this chunk_id.\n * Deletes are cascaded via ...delete_via_job_tuple_found.\n */\nvoid\nts_bgw_policy_chunk_stats_delete_by_chunk_id(int32 chunk_id)\n{\n\tScanKeyData scankey[1];\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_bgw_policy_chunk_stats_chunk_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(chunk_id));\n\n\tts_catalog_scan_all(BGW_POLICY_CHUNK_STATS,\n\t\t\t\t\t\tInvalidOid,\n\t\t\t\t\t\tscankey,\n\t\t\t\t\t\t1,\n\t\t\t\t\t\tbgw_policy_chunk_stats_delete_via_job_tuple_found,\n\t\t\t\t\t\tRowExclusiveLock,\n\t\t\t\t\t\tNULL);\n}\n\nstatic void\nts_bgw_policy_chunk_stats_insert_with_relation(Relation rel, BgwPolicyChunkStats *chunk_stats)\n{\n\tTupleDesc tupdesc;\n\tCatalogSecurityContext sec_ctx;\n\tDatum values[Natts_bgw_policy_chunk_stats];\n\tbool nulls[Natts_bgw_policy_chunk_stats] = { false };\n\n\ttupdesc = RelationGetDescr(rel);\n\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_policy_chunk_stats_job_id)] =\n\t\tInt32GetDatum(chunk_stats->fd.job_id);\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_policy_chunk_stats_chunk_id)] =\n\t\tInt32GetDatum(chunk_stats->fd.chunk_id);\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_policy_chunk_stats_num_times_job_run)] =\n\t\tInt32GetDatum(chunk_stats->fd.num_times_job_run);\n\tvalues[AttrNumberGetAttrOffset(Anum_bgw_policy_chunk_stats_last_time_job_run)] =\n\t\tTimestampTzGetDatum(chunk_stats->fd.last_time_job_run);\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_insert_values(rel, tupdesc, values, nulls);\n\tts_catalog_restore_user(&sec_ctx);\n}\n\nvoid\nts_bgw_policy_chunk_stats_insert(BgwPolicyChunkStats *chunk_stats)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tRelation rel =\n\t\ttable_open(catalog_get_table_id(catalog, BGW_POLICY_CHUNK_STATS), RowExclusiveLock);\n\n\tts_bgw_policy_chunk_stats_insert_with_relation(rel, chunk_stats);\n\ttable_close(rel, RowExclusiveLock);\n}\n\nBgwPolicyChunkStats *\nts_bgw_policy_chunk_stats_find(int32 job_id, int32 chunk_id)\n{\n\tScanKeyData scankeys[2];\n\tBgwPolicyChunkStats *stats = NULL;\n\n\tScanKeyInit(&scankeys[0],\n\t\t\t\tAnum_bgw_policy_chunk_stats_job_id_chunk_id_idx_job_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(job_id));\n\tScanKeyInit(&scankeys[1],\n\t\t\t\tAnum_bgw_policy_chunk_stats_job_id_chunk_id_idx_chunk_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(chunk_id));\n\n\tts_catalog_scan_one(BGW_POLICY_CHUNK_STATS,\n\t\t\t\t\t\tBGW_POLICY_CHUNK_STATS_JOB_ID_CHUNK_ID_IDX,\n\t\t\t\t\t\tscankeys,\n\t\t\t\t\t\t2,\n\t\t\t\t\t\tbgw_policy_chunk_stats_tuple_found,\n\t\t\t\t\t\tAccessShareLock,\n\t\t\t\t\t\tBGW_POLICY_CHUNK_STATS_TABLE_NAME,\n\t\t\t\t\t\t(void *) &stats);\n\treturn stats;\n}\n\nstatic ScanTupleResult\nbgw_policy_chunk_stats_update_tuple_found(TupleInfo *ti, void *const data)\n{\n\tTimestampTz *updated_last_time_job_run = data;\n\tbool should_free;\n\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\tHeapTuple new_tuple = heap_copytuple(tuple);\n\tBgwPolicyChunkStats *chunk_stats = (BgwPolicyChunkStats *) GETSTRUCT(new_tuple);\n\n\tif (should_free)\n\t\theap_freetuple(tuple);\n\n\tchunk_stats->fd.num_times_job_run++;\n\tchunk_stats->fd.last_time_job_run = *updated_last_time_job_run;\n\n\tts_catalog_update(ti->scanrel, new_tuple);\n\theap_freetuple(new_tuple);\n\n\treturn SCAN_CONTINUE;\n}\n\n/* This function also increments num_times_job_run by 1. */\nvoid\nts_bgw_policy_chunk_stats_record_job_run(int32 job_id, int32 chunk_id,\n\t\t\t\t\t\t\t\t\t\t TimestampTz last_time_job_run)\n{\n\tbool updated;\n\tScanKeyData scankeys[2];\n\n\tScanKeyInit(&scankeys[0],\n\t\t\t\tAnum_bgw_policy_chunk_stats_job_id_chunk_id_idx_job_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(job_id));\n\tScanKeyInit(&scankeys[1],\n\t\t\t\tAnum_bgw_policy_chunk_stats_job_id_chunk_id_idx_chunk_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(chunk_id));\n\n\tupdated = ts_catalog_scan_one(BGW_POLICY_CHUNK_STATS,\n\t\t\t\t\t\t\t\t  BGW_POLICY_CHUNK_STATS_JOB_ID_CHUNK_ID_IDX,\n\t\t\t\t\t\t\t\t  scankeys,\n\t\t\t\t\t\t\t\t  2,\n\t\t\t\t\t\t\t\t  bgw_policy_chunk_stats_update_tuple_found,\n\t\t\t\t\t\t\t\t  RowExclusiveLock,\n\t\t\t\t\t\t\t\t  BGW_POLICY_CHUNK_STATS_TABLE_NAME,\n\t\t\t\t\t\t\t\t  &last_time_job_run);\n\n\tif (!updated)\n\t{\n\t\tBgwPolicyChunkStats new_stat = {\n\t\t\t.fd = {\n\t\t\t\t.job_id = job_id,\n\t\t\t\t.chunk_id = chunk_id,\n\t\t\t\t.num_times_job_run = 1,\n\t\t\t\t.last_time_job_run = last_time_job_run,\n\t\t\t},\n\t\t};\n\n\t\tts_bgw_policy_chunk_stats_insert(&new_stat);\n\t}\n}\n"
  },
  {
    "path": "src/bgw_policy/chunk_stats.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include \"export.h\"\n#include \"ts_catalog/catalog.h\"\n\ntypedef struct BgwPolicyChunkStats\n{\n\tFormData_bgw_policy_chunk_stats fd;\n} BgwPolicyChunkStats;\n\nextern TSDLLEXPORT void ts_bgw_policy_chunk_stats_insert(BgwPolicyChunkStats *chunk_stats);\nextern BgwPolicyChunkStats *ts_bgw_policy_chunk_stats_find(int32 job_id, int32 chunk_id);\nextern void ts_bgw_policy_chunk_stats_delete_row_only_by_job_id(int32 job_id);\nextern void ts_bgw_policy_chunk_stats_delete_by_chunk_id(int32 chunk_id);\nextern TSDLLEXPORT void ts_bgw_policy_chunk_stats_record_job_run(int32 job_id, int32 chunk_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t TimestampTz last_time_job_run);\n"
  },
  {
    "path": "src/bgw_policy/policy.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <utils/builtins.h>\n\n#include \"bgw/job.h\"\n#include \"policy.h\"\n\nvoid\nts_bgw_policy_delete_by_hypertable_id(int32 hypertable_id)\n{\n\tList *jobs;\n\tListCell *lc;\n\n\tjobs = ts_bgw_job_find_by_hypertable_id(hypertable_id);\n\tforeach (lc, jobs)\n\t{\n\t\tBgwJob *job = lfirst(lc);\n\t\tts_bgw_job_delete_by_id(job->fd.id);\n\t}\n}\n\n/* This function does NOT cascade deletes to the bgw_job table. */\nScanTupleResult\nts_bgw_policy_delete_row_only_tuple_found(TupleInfo *ti, void *const data)\n{\n\tCatalogSecurityContext sec_ctx;\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_delete_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\tts_catalog_restore_user(&sec_ctx);\n\n\treturn SCAN_CONTINUE;\n}\n"
  },
  {
    "path": "src/bgw_policy/policy.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include \"config.h\"\n#include \"export.h\"\n#include \"scanner.h\"\n#include \"ts_catalog/catalog.h\"\n\nextern ScanTupleResult ts_bgw_policy_delete_row_only_tuple_found(TupleInfo *ti, void *const data);\n\nextern void ts_bgw_policy_delete_by_hypertable_id(int32 hypertable_id);\n"
  },
  {
    "path": "src/bmslist_utils.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n#include \"bmslist_utils.h\"\n\nTsBmsList\nts_bmslist_create(void)\n{\n\t/* Create a new empty list is NULL */\n\treturn NIL;\n}\n\nTsBmsList\nts_bmslist_add_member(TsBmsList bmslist, const int *items, int num_items)\n{\n\tAssert(items != NULL);\n\tAssert(num_items > 0);\n\n\tif (items == NULL || num_items == 0)\n\t\treturn bmslist;\n\n\t/* Create a new Bitmapset for the items */\n\tint first_item = items[0];\n\tBitmapset *set = bms_make_singleton(first_item);\n\tfor (int i = 1; i < num_items; i++)\n\t{\n\t\tint item = items[i];\n\t\tset = bms_add_member(set, item);\n\t}\n\n\t/* Add the new set to the list */\n\treturn lappend(bmslist, set);\n}\n\nTsBmsList\nts_bmslist_add_set(TsBmsList bmslist, Bitmapset *set)\n{\n\tAssert(set != NULL);\n\treturn lappend(bmslist, set);\n}\n\nbool\nts_bmslist_contains_items(TsBmsList bmslist, const int *items, int num_items)\n{\n\tbool result = false;\n\n\tAssert(items != NULL);\n\tAssert(num_items > 0);\n\n\tif (items == NULL || num_items == 0)\n\t\treturn false;\n\n\t/* Create a new Bitmapset for the items */\n\tint first_item = items[0];\n\tBitmapset *set = bms_make_singleton(first_item);\n\tfor (int i = 1; i < num_items; i++)\n\t{\n\t\tint item = items[i];\n\t\tset = bms_add_member(set, item);\n\t}\n\n\tresult = ts_bmslist_contains_set(bmslist, set);\n\n\tbms_free(set);\n\treturn result;\n}\n\nbool\nts_bmslist_contains_set(TsBmsList bmslist, Bitmapset *set)\n{\n\tListCell *lc;\n\tAssert(set != NULL);\n\n\tif (set == NULL)\n\t\treturn false;\n\n\tforeach (lc, bmslist)\n\t{\n\t\tBitmapset *item = (Bitmapset *) lfirst(lc);\n\t\tif (bms_equal(item, set))\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nvoid\nts_bmslist_free(TsBmsList bmslist)\n{\n\tlist_free_deep(bmslist);\n}\n"
  },
  {
    "path": "src/bmslist_utils.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n/*\n * Utility functions to simplify working with lists of Bitmapsets.\n * It builds on the Bitmapset and List implementations in Postgres.\n * It is merely a convenience layer on top of the Postgres functionality.\n */\n\n#include \"nodes/bitmapset.h\"\n#include \"nodes/pg_list.h\"\n#include \"postgres.h\"\n\n#include \"export.h\"\n\ntypedef List *TsBmsList;\n\n/* Just a wrapper around the List type */\nextern TSDLLEXPORT TsBmsList ts_bmslist_create(void);\n\n/* Convert the items to a Bitmapset and add it to the list. It doesn't verify if\n * the items are already in the list, so it may add duplicates. */\nextern TSDLLEXPORT TsBmsList ts_bmslist_add_member(TsBmsList bmslist, const int *items,\n\t\t\t\t\t\t\t\t\t\t\t\t   int num_items);\nextern TSDLLEXPORT TsBmsList ts_bmslist_add_set(TsBmsList bmslist, Bitmapset *set);\n\n/* Checks if the list contains the given items or set. */\nextern TSDLLEXPORT bool ts_bmslist_contains_items(TsBmsList bmslist, const int *items,\n\t\t\t\t\t\t\t\t\t\t\t\t  int num_items);\nextern TSDLLEXPORT bool ts_bmslist_contains_set(TsBmsList bmslist, Bitmapset *set);\n\n/* Frees the list and all the Bitmapsets it contains. */\nextern TSDLLEXPORT void ts_bmslist_free(TsBmsList bmslist);\n"
  },
  {
    "path": "src/build-defs.cmake",
    "content": "# Hide symbols by default in shared libraries\nif(NOT USE_DEFAULT_VISIBILITY)\n  set(CMAKE_C_VISIBILITY_PRESET \"hidden\")\nendif()\n\nif(UNIX)\n  set(CMAKE_C_STANDARD 11)\n  set(CMAKE_SHARED_LINKER_FLAGS \"${CMAKE_SHARED_LINKER_FLAGS} -L${PG_LIBDIR}\")\n  set(CMAKE_MODULE_LINKER_FLAGS \"${CMAKE_MODULE_LINKER_FLAGS} -L${PG_LIBDIR}\")\n  set(CMAKE_C_FLAGS \"${PG_CFLAGS} ${PG_CPPFLAGS} ${CMAKE_C_FLAGS}\")\n  set(CMAKE_C_FLAGS_DEBUG \"${CMAKE_C_FLAGS_DEBUG} -g\")\nendif()\n\nif(APPLE)\n  if((${PG_VERSION_MAJOR} GREATER_EQUAL \"16\"))\n    set(CMAKE_SHARED_MODULE_SUFFIX \".dylib\")\n  endif()\n  set(CMAKE_SHARED_LINKER_FLAGS\n      \"${CMAKE_SHARED_LINKER_FLAGS} -multiply_defined suppress\")\n  set(CMAKE_MODULE_LINKER_FLAGS\n      \"${CMAKE_MODULE_LINKER_FLAGS} -multiply_defined suppress -Wl,-undefined,dynamic_lookup -bundle_loader ${PG_BINDIR}/postgres\"\n  )\nelseif(WIN32)\n  set(CMAKE_SHARED_LINKER_FLAGS \"${CMAKE_SHARED_LINKER_FLAGS} /MANIFEST:NO\")\n  set(CMAKE_MODULE_LINKER_FLAGS \"${CMAKE_MODULE_LINKER_FLAGS} /MANIFEST:NO\")\nendif()\n\n# PG_LDFLAGS can have strange values if not found, so we just add the flags if\n# they are defined.\nif(PG_LDFLAGS)\n  set(CMAKE_SHARED_LINKER_FLAGS \"${CMAKE_SHARED_LINKER_FLAGS} ${PG_LDFLAGS}\")\n  set(CMAKE_MODULE_LINKER_FLAGS \"${CMAKE_MODULE_LINKER_FLAGS} ${PG_LDFLAGS}\")\nendif()\n\nif(APACHE_ONLY)\n  add_definitions(-DAPACHE_ONLY)\nendif()\n\ninclude_directories(${PROJECT_SOURCE_DIR}/src ${PROJECT_BINARY_DIR}/src)\ninclude_directories(SYSTEM ${PG_INCLUDEDIR_SERVER})\n\n# Only Windows and FreeBSD need the base include/ dir instead of\n# include/server/, and including both causes problems on Ubuntu where they\n# frequently get out of sync\nif(WIN32 OR (CMAKE_SYSTEM_NAME STREQUAL \"FreeBSD\"))\n  include_directories(SYSTEM ${PG_INCLUDEDIR})\nendif()\n\nif(WIN32)\n  set(CMAKE_MODULE_LINKER_FLAGS\n      \"${CMAKE_MODULE_LINKER_FLAGS} ${PG_LIBDIR}/postgres.lib ws2_32.lib Version.lib\"\n  )\n  set(CMAKE_C_FLAGS \"-D_CRT_SECURE_NO_WARNINGS\")\n  include_directories(SYSTEM ${PG_INCLUDEDIR_SERVER}/port/win32)\n\n  if(MSVC)\n    include_directories(SYSTEM ${PG_INCLUDEDIR_SERVER}/port/win32_msvc)\n  endif(MSVC)\nendif(WIN32)\n\n# Name of library with test-specific code\nset(TESTS_LIB_NAME ${PROJECT_NAME}-tests)\n"
  },
  {
    "path": "src/cache.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/xact.h>\n#include <storage/ipc.h>\n\n#include \"compat/compat.h\"\n#include \"cache.h\"\n\n/* List of pinned caches. A cache occurs once in this list for every pin\n * taken */\nstatic List *pinned_caches = NIL;\nstatic MemoryContext pinned_caches_mctx = NULL;\n\ntypedef struct CachePin\n{\n\tCache *cache;\n\tSubTransactionId subtxnid;\n} CachePin;\n\nstatic void\ncache_reset_pinned_caches(void)\n{\n\tif (NULL != pinned_caches_mctx)\n\t\tMemoryContextDelete(pinned_caches_mctx);\n\n\tpinned_caches_mctx =\n\t\tAllocSetContextCreate(CacheMemoryContext, \"Cache pins\", ALLOCSET_DEFAULT_SIZES);\n\n\tpinned_caches = NIL;\n}\n\nvoid\nts_cache_init(Cache *cache)\n{\n\tif (cache->htab != NULL)\n\t{\n\t\telog(ERROR, \"cache %s is already initialized\", cache->name);\n\t\treturn;\n\t}\n\n\t/*\n\t * The cache object should have been created in its own context so that\n\t * cache_destroy can just delete the context to free everything.\n\t */\n\tAssert(GetMemoryChunkContext(cache) == ts_cache_memory_ctx(cache));\n\n\t/*\n\t * We always want to be explicit about the memory context our hash table\n\t * ends up in to ensure it's not accidentally put in TopMemoryContext.\n\t */\n\tAssert(cache->flags & HASH_CONTEXT);\n\tcache->htab = hash_create(cache->name, cache->numelements, &cache->hctl, cache->flags);\n\tcache->refcount = 1;\n\tcache->handle_txn_callbacks = true;\n\tcache->release_on_commit = true;\n}\n\nstatic void\ncache_destroy(Cache **cache_ptr)\n{\n\tCache *cache = *cache_ptr;\n\tif (cache == NULL)\n\t\treturn;\n\tif (cache->refcount > 0)\n\t{\n\t\t/* will be destroyed later */\n\t\treturn;\n\t}\n\n\tif (cache->pre_destroy_hook != NULL)\n\t\tcache->pre_destroy_hook(cache);\n\n\thash_destroy(cache->htab);\n\tMemoryContextDelete(cache->hctl.hcxt);\n\t*cache_ptr = NULL;\n}\n\nvoid\nts_cache_invalidate(Cache **cache_ptr)\n{\n\tCache *cache = *cache_ptr;\n\tif (cache == NULL)\n\t\treturn;\n\tcache->refcount--;\n\tcache_destroy(cache_ptr);\n}\n\n/*\n * Pinning is needed if any items returned by the cache may need to survive\n * invalidation events (i.e. AcceptInvalidationMessages() may be called).\n *\n * Invalidation messages may be processed on any internal function that takes a\n * lock (e.g. table_open).\n *\n * Each call to cache_pin MUST BE paired with a call to cache_release.\n *\n */\nCache *\nts_cache_pin(Cache *cache)\n{\n\tMemoryContext old = MemoryContextSwitchTo(pinned_caches_mctx);\n\tCachePin *cp = palloc(sizeof(CachePin));\n\n\tcp->cache = cache;\n\tcp->subtxnid = GetCurrentSubTransactionId();\n\tif (cache->handle_txn_callbacks)\n\t\tpinned_caches = lappend(pinned_caches, cp);\n\tMemoryContextSwitchTo(old);\n\tcache->refcount++;\n\treturn cache;\n}\n\nstatic void\nremove_pin(Cache *cache, SubTransactionId subtxnid)\n{\n\tListCell *lc;\n\n\tforeach (lc, pinned_caches)\n\t{\n\t\tCachePin *cp = lfirst(lc);\n\n\t\tif (cp->cache == cache && cp->subtxnid == subtxnid)\n\t\t{\n\t\t\t// free cache memory and then remove the pin\n\t\t\tcache_destroy(&cp->cache);\n\t\t\tpinned_caches = list_delete_cell(pinned_caches, lc);\n\t\t\tpfree(cp);\n\t\t\treturn;\n\t\t}\n\t}\n\n\t/* should never reach here: there should always be a pin to remove */\n\tAssert(false);\n}\n\nstatic int\ncache_release_subtxn(Cache **cache_ptr, SubTransactionId subtxnid)\n{\n\tCache *cache = *cache_ptr;\n\tint refcount = cache->refcount - 1;\n\n\tAssert(cache->refcount > 0);\n\tcache->refcount--;\n\n\tif (cache->handle_txn_callbacks)\n\t{\n\t\tremove_pin(cache, subtxnid);\n\t}\n\telse\n\t{\n\t\tcache_destroy(cache_ptr);\n\t}\n\treturn refcount;\n}\n\nint\nts_cache_release(Cache **cache)\n{\n\treturn cache_release_subtxn(cache, GetCurrentSubTransactionId());\n}\n\nMemoryContext\nts_cache_memory_ctx(Cache *cache)\n{\n\treturn cache->hctl.hcxt;\n}\n\nvoid *\nts_cache_fetch(Cache *cache, CacheQuery *query)\n{\n\tHASHACTION action;\n\tbool found;\n\n\tif (cache->htab == NULL || cache->valid_result == NULL)\n\t\telog(ERROR, \"cache \\\"%s\\\" is not initialized\", cache->name);\n\n\tif (query->flags & CACHE_FLAG_NOCREATE)\n\t\taction = HASH_FIND;\n\telse if (cache->create_entry == NULL)\n\t\telog(ERROR, \"cache \\\"%s\\\" does not support creating new entries\", cache->name);\n\telse\n\t\taction = HASH_ENTER;\n\n\tquery->result = hash_search(cache->htab, cache->get_key(query), action, &found);\n\n\tif (found)\n\t{\n\t\tcache->stats.hits++;\n\n\t\tif (cache->update_entry != NULL)\n\t\t\tquery->result = cache->update_entry(cache, query);\n\t}\n\telse\n\t{\n\t\tcache->stats.misses++;\n\n\t\tif (action == HASH_ENTER)\n\t\t{\n\t\t\tcache->stats.numelements++;\n\t\t\tquery->result = cache->create_entry(cache, query);\n\t\t}\n\t}\n\n\tif (!(query->flags & CACHE_FLAG_MISSING_OK) && !cache->valid_result(query->result))\n\t{\n\t\tif (cache->missing_error != NULL)\n\t\t\tcache->missing_error(cache, query);\n\t\telse\n\t\t\telog(ERROR, \"failed to find entry in cache \\\"%s\\\"\", cache->name);\n\t}\n\n\treturn query->result;\n}\n\nstatic void\nrelease_all_pinned_caches()\n{\n\tListCell *lc;\n\n\t/*\n\t * release once for every occurrence of a cache in the pinned caches list.\n\t * On abort, release irrespective of cache->release_on_commit.\n\t */\n\tforeach (lc, pinned_caches)\n\t{\n\t\tCachePin *cp = lfirst(lc);\n\n\t\tcp->cache->refcount--;\n\t\tcache_destroy(&cp->cache);\n\t}\n\n\tcache_reset_pinned_caches();\n}\n\nstatic void\nrelease_subtxn_pinned_caches(SubTransactionId subtxnid, bool abort)\n{\n\tListCell *lc;\n\n\t/*\n\t * Need a copy because cache_release will modify pinned_caches.\n\t *\n\t * This needs to be allocated in pinned cache memory context.\n\t * Otherwise leaks ensue if CurTransactionContext (which is the\n\t * CurrentMemoryContext) gets used!\n\t */\n\tMemoryContext old = MemoryContextSwitchTo(pinned_caches_mctx);\n\tList *pinned_caches_copy = list_copy(pinned_caches);\n\tMemoryContextSwitchTo(old);\n\n\t/* Only release caches created in subtxn */\n\tforeach (lc, pinned_caches_copy)\n\t{\n\t\tCachePin *cp = lfirst(lc);\n\n\t\tif (cp->subtxnid == subtxnid && cp->cache)\n\t\t{\n\t\t\t/*\n\t\t\t * This assert makes sure that that we don't have a cache leak\n\t\t\t * when running with debugging\n\t\t\t */\n\t\t\tAssert(abort);\n\t\t\tcache_release_subtxn(&cp->cache, subtxnid);\n\t\t}\n\t}\n\n\tlist_free(pinned_caches_copy);\n}\n\n/*\n * Transaction end callback that cleans up any pinned caches. This is a\n * safeguard that protects against indefinitely pinned caches (memory leaks)\n * that may occur if a transaction ends (normally or abnormally) while a pin is\n * held. Without this, a ts_cache_pin() call always needs to be paired with a\n * ts_cache_release() call and wrapped in a PG_TRY() block to capture and handle\n * any exceptions that occur.\n *\n * Note that this checks that ts_cache_release() is always called by the end\n * of a non-aborted transaction unless cache->release_on_commit is set to true.\n * */\nstatic void\ncache_xact_end(XactEvent event, void *arg)\n{\n\tswitch (event)\n\t{\n\t\tcase XACT_EVENT_ABORT:\n\t\tcase XACT_EVENT_PARALLEL_ABORT:\n\t\t\trelease_all_pinned_caches();\n\t\t\tbreak;\n\t\tdefault:\n\t\t{\n\t\t\t/*\n\t\t\t * Make a copy of the list of pinned caches since\n\t\t\t * ts_cache_release() can manipulate the original list.\n\t\t\t */\n\t\t\tList *pinned_caches_copy = list_copy(pinned_caches);\n\t\t\tListCell *lc;\n\n\t\t\t/*\n\t\t\t * Only caches left should be marked as non-released\n\t\t\t */\n\t\t\tforeach (lc, pinned_caches_copy)\n\t\t\t{\n\t\t\t\tCachePin *cp = lfirst(lc);\n\n\t\t\t\t/*\n\t\t\t\t * This assert makes sure that that we don't have a cache\n\t\t\t\t * leak when running with debugging\n\t\t\t\t */\n\t\t\t\tAssert(!cp->cache->release_on_commit);\n\n\t\t\t\t/*\n\t\t\t\t * This may still happen in optimized environments where\n\t\t\t\t * Assert is turned off. In that case, release.\n\t\t\t\t */\n\t\t\t\tif (cp->cache->release_on_commit)\n\t\t\t\t\tts_cache_release(&cp->cache);\n\t\t\t}\n\n\t\t\tlist_free(pinned_caches_copy);\n\t\t}\n\t\tbreak;\n\t}\n}\n\nstatic void\ncache_subxact_abort(SubXactEvent event, SubTransactionId subtxn_id, SubTransactionId parentSubid,\n\t\t\t\t\tvoid *arg)\n{\n\t/*\n\t * Note that cache->release_on_commit is irrelevant here since can't have\n\t * cross-commit operations in subtxns\n\t */\n\t/*\n\t * In subtxns, caches should have already been released, unless an abort\n\t * happened. Be careful to only release caches that were created in the\n\t * same subtxn.\n\t */\n\n\tswitch (event)\n\t{\n\t\tcase SUBXACT_EVENT_START_SUB:\n\t\tcase SUBXACT_EVENT_PRE_COMMIT_SUB:\n\t\t\t/* do nothing */\n\t\t\tbreak;\n\t\tcase SUBXACT_EVENT_COMMIT_SUB:\n\t\t\trelease_subtxn_pinned_caches(subtxn_id, false);\n\t\t\tbreak;\n\t\tcase SUBXACT_EVENT_ABORT_SUB:\n\t\t\trelease_subtxn_pinned_caches(subtxn_id, true);\n\t\t\tbreak;\n\t}\n}\n\nvoid\n_cache_init(void)\n{\n\tcache_reset_pinned_caches();\n\tRegisterXactCallback(cache_xact_end, NULL);\n\tRegisterSubXactCallback(cache_subxact_abort, NULL);\n}\n\nvoid\n_cache_fini(void)\n{\n\trelease_all_pinned_caches();\n\tMemoryContextDelete(pinned_caches_mctx);\n\tpinned_caches_mctx = NULL;\n\tpinned_caches = NIL;\n\tUnregisterXactCallback(cache_xact_end, NULL);\n\tUnregisterSubXactCallback(cache_subxact_abort, NULL);\n}\n"
  },
  {
    "path": "src/cache.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <utils/hsearch.h>\n#include <utils/memutils.h>\n\n#include \"export.h\"\n\ntypedef enum CacheQueryFlags\n{\n\tCACHE_FLAG_NONE = 0,\n\tCACHE_FLAG_MISSING_OK = 1 << 0,\n\tCACHE_FLAG_NOCREATE = 1 << 1,\n} CacheQueryFlags;\n\n#define CACHE_FLAG_CHECK (CACHE_FLAG_MISSING_OK | CACHE_FLAG_NOCREATE)\n\ntypedef struct CacheQuery\n{\n\t/* CacheQueryFlags as defined above */\n\tconst unsigned int flags;\n\tvoid *result;\n\tvoid *data;\n} CacheQuery;\n\ntypedef struct CacheStats\n{\n\tlong numelements;\n\tuint64 hits;\n\tuint64 misses;\n} CacheStats;\n\ntypedef struct Cache\n{\n\tHASHCTL hctl;\n\tHTAB *htab;\n\tint refcount;\n\tconst char *name;\n\tlong numelements;\n\tint flags;\n\tCacheStats stats;\n\tvoid *(*get_key)(struct CacheQuery *);\n\tvoid *(*create_entry)(struct Cache *, CacheQuery *);\n\tvoid *(*update_entry)(struct Cache *, CacheQuery *);\n\tvoid (*missing_error)(const struct Cache *, const CacheQuery *);\n\tbool (*valid_result)(const void *);\n\tvoid (*remove_entry)(void *entry);\n\tvoid (*pre_destroy_hook)(struct Cache *);\n\n\tbool handle_txn_callbacks; /* Auto-release caches on (sub)txn\n\t\t\t\t\t\t\t\t* aborts and commits. Should be off\n\t\t\t\t\t\t\t\t* if cache used in txn callbacks */\n\tbool release_on_commit;\t   /* This should be false if doing\n\t\t\t\t\t\t\t\t* cross-commit operations like CLUSTER or\n\t\t\t\t\t\t\t\t* VACUUM */\n} Cache;\n\nextern TSDLLEXPORT void ts_cache_init(Cache *cache);\nextern TSDLLEXPORT void ts_cache_invalidate(Cache **cache_ptr);\nextern TSDLLEXPORT void *ts_cache_fetch(Cache *cache, CacheQuery *query);\nextern TSDLLEXPORT MemoryContext ts_cache_memory_ctx(Cache *cache);\nextern TSDLLEXPORT Cache *ts_cache_pin(Cache *cache);\nextern TSDLLEXPORT int ts_cache_release(Cache **cache_ptr);\n\nextern void _cache_init(void);\nextern void _cache_fini(void);\n"
  },
  {
    "path": "src/cache_invalidate.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/xact.h>\n#include <catalog/namespace.h>\n#include <miscadmin.h>\n#include <nodes/nodes.h>\n#include <utils/inval.h>\n#include <utils/lsyscache.h>\n#include <utils/syscache.h>\n\n#include \"compat/compat.h\"\n#include \"annotations.h\"\n#include \"extension.h\"\n#include \"hypertable_cache.h\"\n#include \"ts_catalog/catalog.h\"\n\n#include \"bgw/scheduler.h\"\n#include \"cache_invalidate.h\"\n#include \"cross_module_fn.h\"\n\n/*\n * Notes on the way cache invalidation works.\n *\n * Since our caches are stored in per-process (per-backend memory), we need a\n * way to signal all backends that they should invalidate their caches. For this\n * we use the PostgreSQL relcache mechanism that propagates relation cache\n * invalidation events to all backends. We register a callback with this\n * mechanism to receive events on all backends whenever a relation cache entry\n * is invalidated.\n *\n * To know which events should trigger invalidation of our caches, we use dummy\n * (empty) tables. We can trigger relcache invalidation events for these tables\n * to signal other backends. If the received table OID is a dummy table, we know\n * that this is an event that we care about.\n *\n * Caches for catalog tables should be invalidated on:\n *\n * 1. INSERT/UPDATE/DELETE on a catalog table\n * 2. Aborted transactions that taint the caches\n *\n * Generally, INSERTS do not warrant cache invalidation, unless it is an insert\n * of a subobject that belongs to an object that might already be in the cache\n * (e.g., a new dimension of a hypertable), or when replacing an existing entry\n * (e.g., when replacing a negative hypertable entry with a positive one). Note,\n * also, that INSERTS can taint the cache if the transaction that did the INSERT\n * fails. This is why we also need to invalidate caches on transaction failure.\n */\n\nvoid _cache_invalidate_init(void);\nvoid _cache_invalidate_fini(void);\n\nstatic inline void\ncache_invalidate_relcache_all(void)\n{\n\tts_hypertable_cache_invalidate_callback();\n\tts_bgw_job_cache_invalidate_callback();\n}\n\nstatic Oid hypertable_proxy_table_oid = InvalidOid;\nstatic Oid bgw_proxy_table_oid = InvalidOid;\n\nvoid\nts_cache_invalidate_set_proxy_tables(Oid hypertable_proxy_oid, Oid bgw_proxy_oid)\n{\n\thypertable_proxy_table_oid = hypertable_proxy_oid;\n\tbgw_proxy_table_oid = bgw_proxy_oid;\n}\n\n/*\n * This function is called when any relcache is invalidated.\n * Should route the invalidation to the correct cache.\n *\n * NOTE that the callback should not call any functions that could invoke the\n * relcache or syscache to query information during the invalidation. That\n * might lead to bad things happening.\n */\nstatic void\ncache_invalidate_relcache_callback(Datum arg, Oid relid)\n{\n\tif (!OidIsValid(relid))\n\t{\n\t\tcache_invalidate_relcache_all();\n\t}\n\telse if (ts_extension_is_proxy_table_relid(relid))\n\t{\n\t\tts_extension_invalidate();\n\t\tcache_invalidate_relcache_all();\n\t\tts_cache_invalidate_set_proxy_tables(InvalidOid, InvalidOid);\n\t}\n\telse if (relid == hypertable_proxy_table_oid)\n\t{\n\t\tts_hypertable_cache_invalidate_callback();\n\t}\n\telse if (relid == bgw_proxy_table_oid)\n\t{\n\t\tts_bgw_job_cache_invalidate_callback();\n\t}\n}\n\nTS_FUNCTION_INFO_V1(ts_timescaledb_invalidate_cache);\n\n/*\n * Force a cache invalidation for a catalog table.\n *\n * This function is used for debugging purposes and triggers a cache\n * invalidation.\n *\n * The first argument should be the catalog table that has changed, warranting a\n * cache invalidation.\n */\nDatum\nts_timescaledb_invalidate_cache(PG_FUNCTION_ARGS)\n{\n\tts_catalog_invalidate_cache(PG_GETARG_OID(0), CMD_UPDATE);\n\tPG_RETURN_VOID();\n}\n\nstatic void\ncache_invalidate_xact_end(XactEvent event, void *arg)\n{\n\tswitch (event)\n\t{\n\t\tcase XACT_EVENT_ABORT:\n\t\tcase XACT_EVENT_PARALLEL_ABORT:\n\n\t\t\t/*\n\t\t\t * Invalidate caches on aborted transactions to purge entries that\n\t\t\t * have been added during the transaction and are now no longer\n\t\t\t * valid. Note that we need not signal other backends of this\n\t\t\t * change since the transaction hasn't been committed and other\n\t\t\t * backends cannot have the invalid state.\n\t\t\t */\n\t\t\tcache_invalidate_relcache_all();\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n}\n\nstatic void\ncache_invalidate_subxact_end(SubXactEvent event, SubTransactionId mySubid,\n\t\t\t\t\t\t\t SubTransactionId parentSubid, void *arg)\n{\n\tswitch (event)\n\t{\n\t\tcase SUBXACT_EVENT_ABORT_SUB:\n\n\t\t\t/*\n\t\t\t * Invalidate caches on aborted sub transactions. See notes above\n\t\t\t * in cache_invalidate_xact_end.\n\t\t\t */\n\t\t\tcache_invalidate_relcache_all();\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n}\n\nvoid\n_cache_invalidate_init(void)\n{\n\tRegisterXactCallback(cache_invalidate_xact_end, NULL);\n\tRegisterSubXactCallback(cache_invalidate_subxact_end, NULL);\n\tCacheRegisterRelcacheCallback(cache_invalidate_relcache_callback, PointerGetDatum(NULL));\n}\n\nvoid\n_cache_invalidate_fini(void)\n{\n\tUnregisterXactCallback(cache_invalidate_xact_end, NULL);\n\tUnregisterSubXactCallback(cache_invalidate_subxact_end, NULL);\n\t/* No way to unregister relcache callback */\n}\n"
  },
  {
    "path": "src/cache_invalidate.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\nextern void ts_cache_invalidate_set_proxy_tables(Oid hypertable_proxy_oid, Oid bgw_proxy_oid);\n"
  },
  {
    "path": "src/chunk.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/htup.h>\n#include <access/htup_details.h>\n#include <access/reloptions.h>\n#include <access/table.h>\n#include <access/tableam.h>\n#include <access/tupdesc.h>\n#include <access/xact.h>\n#include <catalog/indexing.h>\n#include <catalog/namespace.h>\n#include <catalog/pg_class.h>\n#include <catalog/pg_constraint.h>\n#include <catalog/pg_inherits.h>\n#include <catalog/pg_opfamily.h>\n#include <catalog/pg_publication.h>\n#include <catalog/pg_publication_rel_d.h>\n#include <catalog/pg_trigger.h>\n#include <catalog/pg_type.h>\n#include <catalog/pg_type_d.h>\n#include <catalog/toasting.h>\n#include <commands/defrem.h>\n#include <commands/publicationcmds.h>\n#include <commands/tablecmds.h>\n#include <commands/trigger.h>\n#include <executor/executor.h>\n#include <fmgr.h>\n#include <foreign/fdwapi.h>\n#include <funcapi.h>\n#include <miscadmin.h>\n#include <nodes/execnodes.h>\n#include <nodes/lockoptions.h>\n#include <nodes/makefuncs.h>\n#include <nodes/value.h>\n#include <parser/parse_node.h>\n#include <storage/lmgr.h>\n#include <storage/lockdefs.h>\n#include <tcop/tcopprot.h>\n#include <utils/acl.h>\n#include <utils/array.h>\n#include <utils/builtins.h>\n#include <utils/datum.h>\n#include <utils/elog.h>\n#include <utils/hsearch.h>\n#include <utils/inval.h>\n#include <utils/lsyscache.h>\n#include <utils/palloc.h>\n#include <utils/syscache.h>\n#include <utils/timestamp.h>\n#include <utils/typcache.h>\n\n#include \"chunk.h\"\n\n#include \"compat/compat.h\"\n#include \"bgw_policy/chunk_stats.h\"\n#include \"cache.h\"\n#include \"chunk_index.h\"\n\n#include \"cross_module_fn.h\"\n#include \"debug_assert.h\"\n#include \"debug_point.h\"\n#include \"dimension.h\"\n#include \"dimension_slice.h\"\n#include \"dimension_vector.h\"\n#include \"errors.h\"\n#include \"foreign_key.h\"\n#include \"guc.h\"\n#include \"hypercube.h\"\n#include \"hypertable.h\"\n#include \"hypertable_cache.h\"\n#include \"osm_callbacks.h\"\n#include \"partition_chunk.h\"\n#include \"process_utility.h\"\n#include \"scan_iterator.h\"\n#include \"scanner.h\"\n#include \"time_utils.h\"\n#include \"trigger.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"ts_catalog/chunk_column_stats.h\"\n#include \"ts_catalog/chunk_rewrite.h\"\n#include \"ts_catalog/compression_chunk_size.h\"\n#include \"ts_catalog/compression_settings.h\"\n#include \"ts_catalog/continuous_agg.h\"\n#include \"ts_catalog/continuous_aggs_watermark.h\"\n#include \"utils.h\"\n\nTS_FUNCTION_INFO_V1(ts_chunk_show_chunks);\nTS_FUNCTION_INFO_V1(ts_chunk_drop_chunks);\nTS_FUNCTION_INFO_V1(ts_chunk_drop_single_chunk);\nTS_FUNCTION_INFO_V1(ts_chunk_attach_osm_table_chunk);\nTS_FUNCTION_INFO_V1(ts_chunk_drop_osm_chunk);\nTS_FUNCTION_INFO_V1(ts_chunk_id_from_relid);\nTS_FUNCTION_INFO_V1(ts_chunk_show);\nTS_FUNCTION_INFO_V1(ts_chunk_create);\nTS_FUNCTION_INFO_V1(ts_chunk_status);\n\nstatic bool ts_chunk_add_status(Chunk *chunk, int32 status);\n\nstatic const char *\nDatumGetNameString(Datum datum)\n{\n\tName name = DatumGetName(datum);\n\treturn pstrdup(NameStr(*name));\n}\n\n/* Used when processing scanned chunks */\ntypedef enum ChunkResult\n{\n\tCHUNK_DONE,\n\tCHUNK_IGNORED,\n\tCHUNK_PROCESSED\n} ChunkResult;\n\n/*\n * Context for scanning and building a chunk from a stub.\n *\n * If found, the chunk will be created and the chunk pointer member is set in\n * the result. Optionally, a caller can pre-allocate the chunk member's memory,\n * which is useful if one, e.g., wants to fill in an memory-aligned array of\n * chunks.\n *\n */\ntypedef struct ChunkStubScanCtx\n{\n\tChunkStub *stub;\n\tChunk *chunk;\n\tLOCKMODE chunk_lockmode;\n\tconst ScanTupLock *slice_lock;\n} ChunkStubScanCtx;\n\nstatic bool\nchunk_stub_is_valid(const ChunkStub *stub, int16 expected_slices)\n{\n\treturn stub && stub->id > 0 && stub->constraints && expected_slices == stub->cube->num_slices &&\n\t\t   stub->cube->num_slices == stub->constraints->num_dimension_constraints;\n}\n\ntypedef ChunkResult (*on_chunk_stub_func)(ChunkScanCtx *ctx, ChunkStub *stub);\nstatic void chunk_scan_ctx_init(ChunkScanCtx *ctx, const Hypertable *ht, const Point *point);\nstatic void chunk_scan_ctx_destroy(ChunkScanCtx *ctx);\nstatic void chunk_collision_scan(ChunkScanCtx *scanctx, const Hypercube *cube);\nstatic int chunk_scan_ctx_foreach_chunk_stub(ChunkScanCtx *ctx, on_chunk_stub_func on_chunk,\n\t\t\t\t\t\t\t\t\t\t\t uint64 limit);\nstatic Datum show_chunks_return_srf(FunctionCallInfo fcinfo);\nstatic int chunk_cmp(const void *ch1, const void *ch2);\nstatic void init_scan_by_qualified_table_name(ScanIterator *iterator, const char *schema_name,\n\t\t\t\t\t\t\t\t\t\t\t  const char *table_name);\nstatic Chunk *get_chunks_in_time_range(Hypertable *ht, int64 older_than, int64 newer_than,\n\t\t\t\t\t\t\t\t\t   MemoryContext mctx, uint64 *num_chunks_returned,\n\t\t\t\t\t\t\t\t\t   ScanTupLock *tuplock);\nstatic Chunk *get_chunks_in_creation_time_range(Hypertable *ht, int64 older_than, int64 newer_than,\n\t\t\t\t\t\t\t\t\t\t\t\tMemoryContext mctx, uint64 *num_chunks_returned,\n\t\t\t\t\t\t\t\t\t\t\t\tScanTupLock *tupLock);\n\nstatic HeapTuple\nchunk_formdata_make_tuple(const FormData_chunk *fd, TupleDesc desc)\n{\n\tDatum values[Natts_chunk];\n\tbool nulls[Natts_chunk] = { false };\n\n\tmemset(values, 0, sizeof(Datum) * Natts_chunk);\n\n\tvalues[AttrNumberGetAttrOffset(Anum_chunk_id)] = Int32GetDatum(fd->id);\n\tvalues[AttrNumberGetAttrOffset(Anum_chunk_hypertable_id)] = Int32GetDatum(fd->hypertable_id);\n\tvalues[AttrNumberGetAttrOffset(Anum_chunk_schema_name)] = NameGetDatum(&fd->schema_name);\n\tvalues[AttrNumberGetAttrOffset(Anum_chunk_table_name)] = NameGetDatum(&fd->table_name);\n\t/*when we insert a chunk the compressed chunk id is always NULL */\n\tif (fd->compressed_chunk_id == INVALID_CHUNK_ID)\n\t\tnulls[AttrNumberGetAttrOffset(Anum_chunk_compressed_chunk_id)] = true;\n\telse\n\t{\n\t\tvalues[AttrNumberGetAttrOffset(Anum_chunk_compressed_chunk_id)] =\n\t\t\tInt32GetDatum(fd->compressed_chunk_id);\n\t}\n\tvalues[AttrNumberGetAttrOffset(Anum_chunk_status)] = Int32GetDatum(fd->status);\n\tvalues[AttrNumberGetAttrOffset(Anum_chunk_osm_chunk)] = BoolGetDatum(fd->osm_chunk);\n\tvalues[AttrNumberGetAttrOffset(Anum_chunk_creation_time)] = Int64GetDatum(fd->creation_time);\n\n\treturn heap_form_tuple(desc, values, nulls);\n}\n\nvoid\nts_chunk_formdata_fill(FormData_chunk *fd, const TupleInfo *ti)\n{\n\tbool should_free;\n\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\tbool nulls[Natts_chunk];\n\tDatum values[Natts_chunk];\n\n\tmemset(fd, 0, sizeof(FormData_chunk));\n\theap_deform_tuple(tuple, ts_scanner_get_tupledesc(ti), values, nulls);\n\n\tAssert(!nulls[AttrNumberGetAttrOffset(Anum_chunk_id)]);\n\tAssert(!nulls[AttrNumberGetAttrOffset(Anum_chunk_hypertable_id)]);\n\tAssert(!nulls[AttrNumberGetAttrOffset(Anum_chunk_schema_name)]);\n\tAssert(!nulls[AttrNumberGetAttrOffset(Anum_chunk_table_name)]);\n\tAssert(!nulls[AttrNumberGetAttrOffset(Anum_chunk_status)]);\n\tAssert(!nulls[AttrNumberGetAttrOffset(Anum_chunk_osm_chunk)]);\n\tAssert(!nulls[AttrNumberGetAttrOffset(Anum_chunk_creation_time)]);\n\n\tfd->id = DatumGetInt32(values[AttrNumberGetAttrOffset(Anum_chunk_id)]);\n\tfd->hypertable_id = DatumGetInt32(values[AttrNumberGetAttrOffset(Anum_chunk_hypertable_id)]);\n\tnamestrcpy(&fd->schema_name,\n\t\t\t   DatumGetCString(values[AttrNumberGetAttrOffset(Anum_chunk_schema_name)]));\n\tnamestrcpy(&fd->table_name,\n\t\t\t   DatumGetCString(values[AttrNumberGetAttrOffset(Anum_chunk_table_name)]));\n\n\tif (nulls[AttrNumberGetAttrOffset(Anum_chunk_compressed_chunk_id)])\n\t\tfd->compressed_chunk_id = INVALID_CHUNK_ID;\n\telse\n\t\tfd->compressed_chunk_id =\n\t\t\tDatumGetInt32(values[AttrNumberGetAttrOffset(Anum_chunk_compressed_chunk_id)]);\n\n\tfd->status = DatumGetInt32(values[AttrNumberGetAttrOffset(Anum_chunk_status)]);\n\tfd->osm_chunk = DatumGetBool(values[AttrNumberGetAttrOffset(Anum_chunk_osm_chunk)]);\n\tfd->creation_time = DatumGetInt64(values[AttrNumberGetAttrOffset(Anum_chunk_creation_time)]);\n\n\tif (should_free)\n\t\theap_freetuple(tuple);\n}\n\nint64\nts_chunk_primary_dimension_start(const Chunk *chunk)\n{\n\treturn chunk->cube->slices[0]->fd.range_start;\n}\n\nint64\nts_chunk_primary_dimension_end(const Chunk *chunk)\n{\n\treturn chunk->cube->slices[0]->fd.range_end;\n}\n\nstatic void\nchunk_insert_relation(Relation rel, const Chunk *chunk)\n{\n\tHeapTuple new_tuple;\n\tCatalogSecurityContext sec_ctx;\n\n\tnew_tuple = chunk_formdata_make_tuple(&chunk->fd, RelationGetDescr(rel));\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_insert(rel, new_tuple);\n\tts_catalog_restore_user(&sec_ctx);\n\n\theap_freetuple(new_tuple);\n}\n\nvoid\nts_chunk_insert_lock(const Chunk *chunk, LOCKMODE lock)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tRelation rel;\n\n\trel = table_open(catalog_get_table_id(catalog, CHUNK), lock);\n\tchunk_insert_relation(rel, chunk);\n\ttable_close(rel, lock);\n}\n\ntypedef struct CollisionInfo\n{\n\tHypercube *cube;\n\tChunkStub *colliding_chunk;\n} CollisionInfo;\n\n/*-\n * Align a chunk's hypercube in 'aligned' dimensions.\n *\n * Alignment ensures that chunks line up in a particular dimension, i.e., their\n * ranges should either be identical or not overlap at all.\n *\n * Non-aligned:\n *\n * ' [---------]      <- existing slice\n * '      [---------] <- calculated (new) slice\n *\n * To align the slices above there are two cases depending on where the\n * insertion point happens:\n *\n * Case 1 (reuse slice):\n *\n * ' [---------]\n * '      [--x------]\n *\n * The insertion point x falls within the range of the existing slice. We should\n * reuse the existing slice rather than creating a new one.\n *\n * Case 2 (cut to align):\n *\n * ' [---------]\n * '      [-------x-]\n *\n * The insertion point falls outside the range of the existing slice and we need\n * to cut the new slice to line up.\n *\n * ' [---------]\n * '        cut [---]\n * '\n *\n * Note that slice reuse (case 1) happens already when calculating the tentative\n * hypercube for the chunk, and is thus already performed once reaching this\n * function. Thus, we deal only with case 2 here. Also note that a new slice\n * might overlap in complicated ways, requiring multiple cuts. For instance,\n * consider the following situation:\n *\n * ' [------]   [-] [---]\n * '      [---x-------]  <- calculated slice\n *\n * This should but cut-to-align as follows:\n *\n * ' [------]   [-] [---]\n * '         [x]\n *\n * After a chunk collision scan, this function is called for each chunk in the\n * chunk scan context. Chunks in the scan context may have only a partial set of\n * slices if they only overlap in some, but not all, dimensions (see\n * illustrations below). Still, partial chunks may still be of interest for\n * alignment in a particular dimension. Thus, if a chunk has an overlapping\n * slice in an aligned dimension, we cut to not overlap with that slice.\n */\nstatic ChunkResult\ndo_dimension_alignment(ChunkScanCtx *scanctx, ChunkStub *stub)\n{\n\tCollisionInfo *info = scanctx->data;\n\tHypercube *cube = info->cube;\n\tconst Hyperspace *space = scanctx->ht->space;\n\tChunkResult res = CHUNK_IGNORED;\n\tint i;\n\n\tfor (i = 0; i < space->num_dimensions; i++)\n\t{\n\t\tconst Dimension *dim = &space->dimensions[i];\n\t\tconst DimensionSlice *chunk_slice;\n\t\tDimensionSlice *cube_slice;\n\t\tint64 coord = scanctx->point->coordinates[i];\n\n\t\tif (!dim->fd.aligned)\n\t\t\tcontinue;\n\n\t\t/*\n\t\t * The stub might not have a slice for each dimension, so we cannot\n\t\t * use array indexing. Fetch slice by dimension ID instead.\n\t\t */\n\t\tchunk_slice = ts_hypercube_get_slice_by_dimension_id(stub->cube, dim->fd.id);\n\n\t\tif (NULL == chunk_slice)\n\t\t\tcontinue;\n\n\t\tcube_slice = cube->slices[i];\n\n\t\t/*\n\t\t * Only cut-to-align if the slices collide and are not identical\n\t\t * (i.e., if we are reusing an existing slice we should not cut it)\n\t\t */\n\t\tif (!ts_dimension_slices_equal(cube_slice, chunk_slice) &&\n\t\t\tts_dimension_slices_collide(cube_slice, chunk_slice))\n\t\t{\n\t\t\tts_dimension_slice_cut(cube_slice, chunk_slice, coord);\n\t\t\tres = CHUNK_PROCESSED;\n\t\t}\n\t}\n\n\treturn res;\n}\n\n/*\n * Calculate, and potentially set, a new chunk interval for an open dimension.\n */\nstatic bool\ncalculate_and_set_new_chunk_interval(const Hypertable *ht, const Point *p)\n{\n\tHyperspace *hs = ht->space;\n\tDimension *dim = NULL;\n\tDatum datum;\n\tint64 chunk_interval, coord;\n\tint i;\n\n\tif (!OidIsValid(ht->chunk_sizing_func) || ht->fd.chunk_target_size <= 0)\n\t\treturn false;\n\n\t/* Find first open dimension */\n\tfor (i = 0; i < hs->num_dimensions; i++)\n\t{\n\t\tdim = &hs->dimensions[i];\n\n\t\tif (IS_OPEN_DIMENSION(dim))\n\t\t\tbreak;\n\n\t\tdim = NULL;\n\t}\n\n\t/* Nothing to do if no open dimension */\n\tif (NULL == dim)\n\t{\n\t\telog(WARNING,\n\t\t\t \"adaptive chunking enabled on hypertable \\\"%s\\\" without an open (time) dimension\",\n\t\t\t get_rel_name(ht->main_table_relid));\n\n\t\treturn false;\n\t}\n\n\tcoord = p->coordinates[i];\n\tdatum = OidFunctionCall3(ht->chunk_sizing_func,\n\t\t\t\t\t\t\t Int32GetDatum(dim->fd.id),\n\t\t\t\t\t\t\t Int64GetDatum(coord),\n\t\t\t\t\t\t\t Int64GetDatum(ht->fd.chunk_target_size));\n\tchunk_interval = DatumGetInt64(datum);\n\n\t/* Check if the function didn't set and interval or nothing changed */\n\tif (chunk_interval <= 0 || chunk_interval == dim->fd.interval_length)\n\t\treturn false;\n\n\t/* Update the dimension */\n\tts_dimension_set_chunk_interval(dim, chunk_interval);\n\n\treturn true;\n}\n\n/*\n * Resolve chunk collisions.\n *\n * After a chunk collision scan, this function is called for each chunk in the\n * chunk scan context. We only care about chunks that have a full set of\n * slices/constraints that overlap with our tentative hypercube, i.e., they\n * fully collide. We resolve those collisions by cutting the hypercube.\n */\nstatic ChunkResult\ndo_collision_resolution(ChunkScanCtx *scanctx, ChunkStub *stub)\n{\n\tCollisionInfo *info = scanctx->data;\n\tHypercube *cube = info->cube;\n\tconst Hyperspace *space = scanctx->ht->space;\n\tChunkResult res = CHUNK_IGNORED;\n\tint i;\n\n\tif (stub->cube->num_slices != space->num_dimensions || !ts_hypercubes_collide(cube, stub->cube))\n\t\treturn CHUNK_IGNORED;\n\n\tfor (i = 0; i < space->num_dimensions; i++)\n\t{\n\t\tDimensionSlice *cube_slice = cube->slices[i];\n\t\tDimensionSlice *chunk_slice = stub->cube->slices[i];\n\t\tint64 coord = scanctx->point->coordinates[i];\n\n\t\t/*\n\t\t * Only cut if we aren't reusing an existing slice and there is a\n\t\t * collision\n\t\t */\n\t\tif (!ts_dimension_slices_equal(cube_slice, chunk_slice) &&\n\t\t\tts_dimension_slices_collide(cube_slice, chunk_slice))\n\t\t{\n\t\t\tts_dimension_slice_cut(cube_slice, chunk_slice, coord);\n\t\t\tres = CHUNK_PROCESSED;\n\n\t\t\t/*\n\t\t\t * Redo the collision check after each cut since cutting in one\n\t\t\t * dimension might have resolved the collision in another\n\t\t\t */\n\t\t\tif (!ts_hypercubes_collide(cube, stub->cube))\n\t\t\t\treturn res;\n\t\t}\n\t}\n\n\tAssert(!ts_hypercubes_collide(cube, stub->cube));\n\n\treturn res;\n}\n\nstatic ChunkResult\ncheck_for_collisions(ChunkScanCtx *scanctx, ChunkStub *stub)\n{\n\tCollisionInfo *info = scanctx->data;\n\tHypercube *cube = info->cube;\n\tconst Hyperspace *space = scanctx->ht->space;\n\n\t/* Check if this chunk collides with our hypercube */\n\tif (stub->cube->num_slices == space->num_dimensions && ts_hypercubes_collide(cube, stub->cube))\n\t{\n\t\tinfo->colliding_chunk = stub;\n\t\treturn CHUNK_DONE;\n\t}\n\n\treturn CHUNK_IGNORED;\n}\n\n/*\n * Check if a (tentative) chunk collides with existing chunks.\n *\n * Return the colliding chunk. Note that the chunk is a stub and not a full\n * chunk.\n */\nstatic ChunkStub *\nchunk_collides(const Hypertable *ht, const Hypercube *hc)\n{\n\tChunkScanCtx scanctx;\n\tCollisionInfo info = {\n\t\t.cube = (Hypercube *) hc,\n\t\t.colliding_chunk = NULL,\n\t};\n\n\tchunk_scan_ctx_init(&scanctx, ht, NULL);\n\n\t/* Scan for all chunks that collide with the hypercube of the new chunk */\n\tchunk_collision_scan(&scanctx, hc);\n\tscanctx.data = &info;\n\n\t/* Find chunks that collide */\n\tchunk_scan_ctx_foreach_chunk_stub(&scanctx, check_for_collisions, 0);\n\n\tchunk_scan_ctx_destroy(&scanctx);\n\n\treturn info.colliding_chunk;\n}\n\n/*-\n * Resolve collisions and perform alignment.\n *\n * Chunks collide only if their hypercubes overlap in all dimensions. For\n * instance, the 2D chunks below collide because they overlap in both the X and\n * Y dimensions:\n *\n * ' _____\n * ' |    |\n * ' | ___|__\n * ' |_|__|  |\n * '   |     |\n * '   |_____|\n *\n * While the following chunks do not collide, although they still overlap in the\n * X dimension:\n *\n * ' _____\n * ' |    |\n * ' |    |\n * ' |____|\n * '   ______\n * '   |     |\n * '   |    *|\n * '   |_____|\n *\n * For the collision case above we obviously want to cut our hypercube to no\n * longer collide with existing chunks. However, the second case might still be\n * of interest for alignment in case X is an 'aligned' dimension. If '*' is the\n * insertion point, then we still want to cut the hypercube to ensure that the\n * dimension remains aligned, like so:\n *\n * ' _____\n * ' |    |\n * ' |    |\n * ' |____|\n * '       ___\n * '       | |\n * '       |*|\n * '       |_|\n *\n *\n * We perform alignment first as that might actually resolve chunk\n * collisions. After alignment we check for any remaining collisions.\n */\nstatic void\nchunk_collision_resolve(const Hypertable *ht, Hypercube *cube, const Point *p)\n{\n\tChunkScanCtx scanctx;\n\tCollisionInfo info = {\n\t\t.cube = cube,\n\t\t.colliding_chunk = NULL,\n\t};\n\n\tchunk_scan_ctx_init(&scanctx, ht, p);\n\n\t/* Scan for all chunks that collide with the hypercube of the new chunk */\n\tchunk_collision_scan(&scanctx, cube);\n\tscanctx.data = &info;\n\n\t/* Cut the hypercube in any aligned dimensions */\n\tchunk_scan_ctx_foreach_chunk_stub(&scanctx, do_dimension_alignment, 0);\n\n\t/*\n\t * If there are any remaining collisions with chunks, then cut-to-fit to\n\t * resolve those collisions\n\t */\n\tchunk_scan_ctx_foreach_chunk_stub(&scanctx, do_collision_resolution, 0);\n\n\tchunk_scan_ctx_destroy(&scanctx);\n}\n\nstatic int\nchunk_add_constraints(const Chunk *chunk)\n{\n\tint num_added;\n\n\tnum_added = ts_chunk_constraints_add_dimension_constraints(chunk->constraints,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   chunk->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   chunk->cube);\n\tnum_added += ts_chunk_constraints_add_inheritable_constraints(chunk->constraints,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  chunk->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  chunk->relkind,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  chunk->hypertable_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  chunk->table_id);\n\n\treturn num_added;\n}\n\n/* applies the attributes and statistics target for columns on the hypertable\n   to columns on the chunk */\nstatic void\nset_attoptions(Relation ht_rel, Oid chunk_oid)\n{\n\tTupleDesc tupleDesc = RelationGetDescr(ht_rel);\n\tint natts = tupleDesc->natts;\n\tint attno;\n\tList *alter_cmds = NIL;\n\n\tfor (attno = 1; attno <= natts; attno++)\n\t{\n\t\tForm_pg_attribute attribute = TupleDescAttr(tupleDesc, attno - 1);\n\t\tchar *attributeName = NameStr(attribute->attname);\n\t\tHeapTuple tuple;\n\t\tDatum options;\n\t\tbool isnull;\n\n\t\t/* Ignore dropped */\n\t\tif (attribute->attisdropped)\n\t\t\tcontinue;\n\n\t\ttuple = SearchSysCacheAttName(RelationGetRelid(ht_rel), attributeName);\n\n\t\tAssert(tuple != NULL);\n\n\t\t/*\n\t\t * Pass down the attribute options (ALTER TABLE ALTER COLUMN SET\n\t\t * attribute_option)\n\t\t */\n\t\toptions = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions, &isnull);\n\n\t\tif (!isnull)\n\t\t{\n\t\t\tAlterTableCmd *cmd = makeNode(AlterTableCmd);\n\n\t\t\tcmd->subtype = AT_SetOptions;\n\t\t\tcmd->name = attributeName;\n\t\t\tcmd->def = (Node *) untransformRelOptions(options);\n\t\t\talter_cmds = lappend(alter_cmds, cmd);\n\t\t}\n\n\t\t/*\n\t\t * Pass down the attribute options (ALTER TABLE ALTER COLUMN SET\n\t\t * STATISTICS)\n\t\t */\n\t\toptions = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attstattarget, &isnull);\n\t\tif (!isnull)\n\t\t{\n\t\t\tint32 target = DatumGetInt32(options);\n\n\t\t\t/* Don't do anything if it's set to the default */\n\t\t\tif (target != -1)\n\t\t\t{\n\t\t\t\tAlterTableCmd *cmd = makeNode(AlterTableCmd);\n\n\t\t\t\tcmd->subtype = AT_SetStatistics;\n\t\t\t\tcmd->name = attributeName;\n\t\t\t\tcmd->def = (Node *) makeInteger(target);\n\t\t\t\talter_cmds = lappend(alter_cmds, cmd);\n\t\t\t}\n\t\t}\n\n\t\tReleaseSysCache(tuple);\n\t}\n\n\tif (alter_cmds != NIL)\n\t{\n\t\tAlterTableInternal(chunk_oid, alter_cmds, false);\n\t\tlist_free_deep(alter_cmds);\n\t}\n}\n\nstatic void\ncreate_toast_table(CreateStmt *stmt, Oid chunk_oid)\n{\n\t/* similar to tcop/utility.c */\n#if PG18_LT\n\tchar *validnsps[] = HEAP_RELOPT_NAMESPACES;\n#else\n\tconst char *const validnsps[] = HEAP_RELOPT_NAMESPACES;\n#endif\n\tDatum toast_options =\n\t\ttransformRelOptions(UnassignedDatum, stmt->options, \"toast\", validnsps, true, false);\n\n\t(void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true);\n\n\tNewRelationCreateToastTable(chunk_oid, toast_options);\n}\n\nstatic void\ncopy_hypertable_acl_to_relid(const Hypertable *ht, const Oid owner_id, const Oid relid)\n{\n\tts_copy_relation_acl(ht->main_table_relid, relid, owner_id);\n}\n\n/*\n * Create a chunk's table.\n *\n * A chunk inherits from the main hypertable and will have the same owner. Since\n * chunks can be created either in the TimescaleDB internal schema or in a\n * user-specified schema, some care has to be taken to use the right\n * permissions, depending on the case:\n *\n * 1. if the chunk is created in the internal schema, we create it as the\n * catalog/schema owner (i.e., anyone can create chunks there via inserting into\n * a hypertable, but can not do it via CREATE TABLE).\n *\n * 2. if the chunk is created in a user-specified \"associated schema\", then we\n * shouldn't use the catalog owner to create the table since that typically\n * implies super-user permissions. If we would allow that, anyone can specify\n * someone else's schema in create_hypertable() and create chunks in it without\n * having the proper permissions to do so. With this logic, the hypertable owner\n * must have permissions to create tables in the associated schema, or else\n * table creation will fail. If the schema doesn't yet exist, the table owner\n * instead needs the proper permissions on the database to create the schema.\n */\nOid\nts_chunk_create_table(const Chunk *chunk, const Hypertable *ht, const char *tablespacename)\n{\n\tRelation rel;\n\tObjectAddress address;\n\tint sec_ctx;\n\tchar *amname = NULL;\n\n\tamname = get_am_name(ts_get_rel_am(chunk->hypertable_relid));\n\n\t/*\n\t * CreateStmt node to create the chunk table\n\t */\n\tCreateStmt stmt = {\n\t\t.type = T_CreateStmt,\n\t\t.relation = makeRangeVar((char *) NameStr(chunk->fd.schema_name),\n\t\t\t\t\t\t\t\t (char *) NameStr(chunk->fd.table_name),\n\t\t\t\t\t\t\t\t 0),\n\t\t.tablespacename = tablespacename ? (char *) tablespacename : NULL,\n\t\t.options =\n\t\t\t(chunk->relkind == RELKIND_RELATION) ? ts_get_reloptions(ht->main_table_relid) : NIL,\n\t\t.accessMethod = amname,\n\t};\n\n\t/*\n\t * If partitioned hypertables are enabled, create the chunk as a standalone\n\t * table with the same columns as the hypertable to attach it as a partition\n\t * later. Otherwise, create it as an inherited table.\n\t */\n\tif (is_partitioning_allowed(ht->main_table_relid))\n\t{\n\t\tList *attlist = NIL;\n\t\tList *constraints = NIL;\n\t\tts_partition_chunk_prepare_attributes(ht->main_table_relid, &attlist, &constraints);\n\t\tstmt.tableElts = attlist;\n\t\tstmt.constraints = constraints;\n\t}\n\telse\n\t{\n\t\tstmt.inhRelations = list_make1(makeRangeVar((char *) NameStr(ht->fd.schema_name),\n\t\t\t\t\t\t\t\t\t\t\t\t\t(char *) NameStr(ht->fd.table_name),\n\t\t\t\t\t\t\t\t\t\t\t\t\t0));\n\t}\n\n\tOid uid, saved_uid;\n\n\tAssert(chunk->hypertable_relid == ht->main_table_relid);\n\n\trel = table_open(ht->main_table_relid, AccessShareLock);\n\n\t/* Inherit the persistence (LOGGED or UNLOGGED) from the parent hypertable */\n\tstmt.relation->relpersistence = rel->rd_rel->relpersistence;\n\n\t/*\n\t * If the chunk is created in the internal schema, become the catalog\n\t * owner, otherwise become the hypertable owner\n\t */\n\tif (namestrcmp((Name) &chunk->fd.schema_name, INTERNAL_SCHEMA_NAME) == 0)\n\t\tuid = ts_catalog_database_info_get()->owner_uid;\n\telse\n\t\tuid = rel->rd_rel->relowner;\n\n\tGetUserIdAndSecContext(&saved_uid, &sec_ctx);\n\n\tif (uid != saved_uid)\n\t\tSetUserIdAndSecContext(uid, sec_ctx | SECURITY_LOCAL_USERID_CHANGE);\n\n\t/* Prepare event trigger state and invoke ddl_command_start triggers */\n\tif (ts_guc_enable_event_triggers)\n\t{\n\t\tEventTriggerBeginCompleteQuery();\n\t\tEventTriggerDDLCommandStart((Node *) &stmt);\n\t}\n\n\taddress = DefineRelation(&stmt, chunk->relkind, rel->rd_rel->relowner, NULL, NULL);\n\n\t/* Invoke ddl_command_end triggers and clean up the event trigger state */\n\tif (ts_guc_enable_event_triggers)\n\t{\n\t\tEventTriggerCollectSimpleCommand(address, InvalidObjectAddress, (Node *) &stmt);\n\t\tEventTriggerDDLCommandEnd((Node *) &stmt);\n\t\tEventTriggerEndCompleteQuery();\n\t}\n\n\t/* Make the newly defined relation visible so that we can update the\n\t * ACL. */\n\tCommandCounterIncrement();\n\n\t/* Copy acl from hypertable to chunk relation record */\n\tcopy_hypertable_acl_to_relid(ht, rel->rd_rel->relowner, address.objectId);\n\n\tif (chunk->relkind == RELKIND_RELATION)\n\t{\n\t\t/*\n\t\t * need to create a toast table explicitly for some of the option\n\t\t * setting to work\n\t\t */\n\t\tcreate_toast_table(&stmt, address.objectId);\n\n\t\t/*\n\t\t * Some options require being table owner to set for example statistics\n\t\t * so we have to set them before restoring security context\n\t\t */\n\t\tset_attoptions(rel, address.objectId);\n\n\t\tif (uid != saved_uid)\n\t\t\tSetUserIdAndSecContext(saved_uid, sec_ctx);\n\t}\n\telse\n\t\telog(ERROR, \"invalid relkind \\\"%c\\\" when creating chunk\", chunk->relkind);\n\n\t/* Insert the table into the cache to attach it as partition later */\n\tif (is_partitioning_allowed(ht->main_table_relid))\n\t\tts_partition_cache_insert_chunk(ht, address.objectId);\n\n\ttable_close(rel, AccessShareLock);\n\n\treturn address.objectId;\n}\n\nstatic int32\nget_next_chunk_id()\n{\n\tint32 chunk_id;\n\tCatalogSecurityContext sec_ctx;\n\tconst Catalog *catalog = ts_catalog_get();\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tchunk_id = ts_catalog_table_next_seq_id(catalog, CHUNK);\n\tts_catalog_restore_user(&sec_ctx);\n\n\treturn chunk_id;\n}\n\n/*\n * Create a chunk object from the dimensional constraints in the given hypercube.\n *\n * The chunk object is then used to create the actual chunk table and update the\n * metadata separately.\n *\n * The table name for the chunk can be given explicitly, or generated if\n * table_name is NULL. If the table name is generated, it will use the given\n * prefix or, if NULL, use the hypertable's associated table prefix. Similarly,\n * if schema_name is NULL it will use the hypertable's associated schema for\n * the chunk.\n */\nstatic Chunk *\nchunk_create_object(const Hypertable *ht, Hypercube *cube, const char *schema_name,\n\t\t\t\t\tconst char *table_name, const char *prefix, int32 chunk_id)\n{\n\tconst Hyperspace *hs = ht->space;\n\tChunk *chunk;\n\tconst char relkind = RELKIND_RELATION;\n\n\tif (NULL == schema_name || schema_name[0] == '\\0')\n\t\tschema_name = NameStr(ht->fd.associated_schema_name);\n\n\t/* Create a new chunk based on the hypercube */\n\tchunk = ts_chunk_create_base(chunk_id, hs->num_dimensions, relkind);\n\n\tchunk->fd.hypertable_id = hs->hypertable_id;\n\tchunk->cube = cube;\n\tchunk->hypertable_relid = ht->main_table_relid;\n\tnamestrcpy(&chunk->fd.schema_name, schema_name);\n\n\tif (NULL == table_name || table_name[0] == '\\0')\n\t{\n\t\tint len;\n\n\t\tif (NULL == prefix)\n\t\t\tprefix = NameStr(ht->fd.associated_table_prefix);\n\n\t\tlen = snprintf(NameStr(chunk->fd.table_name),\n\t\t\t\t\t   NAMEDATALEN,\n\t\t\t\t\t   \"%s_%d_chunk\",\n\t\t\t\t\t   prefix,\n\t\t\t\t\t   chunk->fd.id);\n\n\t\tif (len >= NAMEDATALEN)\n\t\t\telog(ERROR, \"chunk table name too long\");\n\t}\n\telse\n\t\tnamestrcpy(&chunk->fd.table_name, table_name);\n\n\treturn chunk;\n}\n\nstatic void\nchunk_insert_into_metadata_after_lock(const Chunk *chunk)\n{\n\t/* Insert chunk */\n\tts_chunk_insert_lock(chunk, RowExclusiveLock);\n\n\t/* Add metadata for dimensional and inheritable constraints */\n\tts_chunk_constraints_insert_metadata(chunk->constraints);\n}\n\n/*\n * Ensure the replica identity setting of a chunk matches that of the root\n * table.\n */\nstatic void\nchunk_set_replica_identity(const Chunk *chunk)\n{\n\tRelation ht_rel = relation_open(chunk->hypertable_relid, AccessShareLock);\n\tRelation ch_rel = relation_open(chunk->table_id, AccessShareLock);\n\n\t/* Do nothing if REPLICA IDENTITY of hypertable and chunk are equal */\n\tif (ht_rel->rd_rel->relreplident == ch_rel->rd_rel->relreplident)\n\t{\n\t\ttable_close(ch_rel, NoLock);\n\t\ttable_close(ht_rel, NoLock);\n\t\treturn;\n\t}\n\n\tReplicaIdentityStmt stmt = {\n\t\t.type = T_ReplicaIdentityStmt,\n\t\t.identity_type = ht_rel->rd_rel->relreplident,\n\t};\n\tAlterTableCmd cmd = {\n\t\t.type = T_AlterTableCmd,\n\t\t.def = (Node *) &stmt,\n\t\t.subtype = AT_ReplicaIdentity,\n\t};\n\tCatalogSecurityContext sec_ctx;\n\n\tif (stmt.identity_type == REPLICA_IDENTITY_INDEX)\n\t{\n\t\t/* Use RelationGetReplicaIndex() instead of rd_replidindex\n\t\t * directly to ensure the index list is loaded after any\n\t\t * relcache invalidation. */\n\t\tOid ht_indexoid = RelationGetReplicaIndex(ht_rel);\n\t\tOid chunk_index_relid = InvalidOid;\n\n\t\tif (OidIsValid(ht_indexoid))\n\t\t\tchunk_index_relid = ts_chunk_index_get_by_hypertable_indexrelid(ch_rel, ht_indexoid);\n\n\t\tif (OidIsValid(chunk_index_relid))\n\t\t\tstmt.name = get_rel_name(chunk_index_relid);\n\t\telse\n\t\t\tstmt.identity_type = REPLICA_IDENTITY_NOTHING;\n\t}\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_alter_table_with_event_trigger(chunk->table_id, NULL, list_make1(&cmd), false);\n\tts_catalog_restore_user(&sec_ctx);\n\ttable_close(ch_rel, NoLock);\n\ttable_close(ht_rel, NoLock);\n}\n\nstatic void\nchunk_create_table_constraints(const Hypertable *ht, const Chunk *chunk)\n{\n\t/* Do not create any of these for partitioned hypertables */\n\tif (is_partitioning_allowed(ht->main_table_relid))\n\t\treturn;\n\n\t/* Create the chunk's constraints, triggers, and indexes */\n\tts_chunk_constraints_create(ht, chunk);\n\n\tif (chunk->relkind == RELKIND_RELATION && !IS_OSM_CHUNK(chunk))\n\t{\n\t\tts_trigger_create_all_on_chunk(chunk);\n\t\tts_chunk_index_create_all(chunk->fd.hypertable_id,\n\t\t\t\t\t\t\t\t  chunk->hypertable_relid,\n\t\t\t\t\t\t\t\t  chunk->fd.id,\n\t\t\t\t\t\t\t\t  chunk->table_id,\n\t\t\t\t\t\t\t\t  InvalidOid);\n\n\t\tchunk_set_replica_identity(chunk);\n\t}\n\n\t/* Copy FK constraints after indexes are created, since FK validation\n\t * requires the supporting unique index to exist on the chunk. */\n\tts_chunk_copy_referencing_fk(ht, chunk);\n}\n\nstatic Oid\nchunk_create_table(Chunk *chunk, const Hypertable *ht)\n{\n\t/* Create the actual table relation for the chunk */\n\tconst char *tablespace = ts_hypertable_select_tablespace_name(ht, chunk);\n\n\tchunk->table_id = ts_chunk_create_table(chunk, ht, tablespace);\n\n\tAssert(OidIsValid(chunk->table_id));\n\n\treturn chunk->table_id;\n}\n\n/*\n * Creates only a table for a chunk.\n * Either table name or chunk id needs to be provided.\n */\nstatic Chunk *\nchunk_create_only_table_after_lock(const Hypertable *ht, Hypercube *cube, const char *schema_name,\n\t\t\t\t\t\t\t\t   const char *table_name, const char *prefix, int32 chunk_id)\n{\n\tChunk *chunk;\n\n\tAssert(table_name != NULL || chunk_id != INVALID_CHUNK_ID);\n\n\tchunk = chunk_create_object(ht, cube, schema_name, table_name, prefix, chunk_id);\n\tAssert(chunk != NULL);\n\n\tchunk_create_table(chunk, ht);\n\n\treturn chunk;\n}\n\nstatic void\nget_hypertable_publication_filters(Oid puboid, const Chunk *chunk, List **columns,\n\t\t\t\t\t\t\t\t   Node **whereClause)\n{\n\tHeapTuple pubtuple;\n\tDatum datum;\n\tbool isnull;\n\n\t*columns = NIL;\n\t*whereClause = NULL;\n\n\t/* Get filters for hypertable, chunk should inherit them */\n\tpubtuple = SearchSysCache2(PUBLICATIONRELMAP,\n\t\t\t\t\t\t\t   ObjectIdGetDatum(chunk->hypertable_relid),\n\t\t\t\t\t\t\t   ObjectIdGetDatum(puboid));\n\n\tif (!HeapTupleIsValid(pubtuple))\n\t\treturn;\n\n\tdatum = SysCacheGetAttr(PUBLICATIONRELMAP, pubtuple, Anum_pg_publication_rel_prqual, &isnull);\n\tif (!isnull)\n\t{\n\t\tchar *prqual_str = TextDatumGetCString(datum);\n\t\t*whereClause = stringToNode(prqual_str);\n\t}\n\n\tdatum = SysCacheGetAttr(PUBLICATIONRELMAP, pubtuple, Anum_pg_publication_rel_prattrs, &isnull);\n\tif (!isnull)\n\t{\n\t\tArrayType *arr = DatumGetArrayTypeP(datum);\n\t\tint nelems = ARR_DIMS(arr)[0];\n\t\tint16 *attnums = (int16 *) ARR_DATA_PTR(arr);\n\n\t\tfor (int i = 0; i < nelems; i++)\n\t\t{\n\t\t\tchar *colname = get_attname(chunk->hypertable_relid, attnums[i], false);\n\t\t\t*columns = lappend(*columns, makeString(colname));\n\t\t}\n\t}\n\n\tReleaseSysCache(pubtuple);\n}\n\nstatic void\nchunk_add_to_publication(Oid puboid, const Chunk *chunk)\n{\n\tPublicationRelInfo pri = { 0 };\n\tRelation chunk_rel;\n\tList *columns = NIL;\n\tNode *whereClause = NULL;\n\n\tget_hypertable_publication_filters(puboid, chunk, &columns, &whereClause);\n\n\tchunk_rel = table_open(chunk->table_id, AccessShareLock);\n\n\tpri.relation = chunk_rel;\n\tpri.columns = columns;\n\tpri.whereClause = whereClause;\n\n\tpublication_add_relation(puboid, &pri, true);\n\n\ttable_close(chunk_rel, AccessShareLock);\n}\n\nstatic void\nchunk_add_to_publications(const Chunk *chunk)\n{\n\tList *puboids;\n\tListCell *lc;\n\n\tpuboids = GetRelationPublications(chunk->hypertable_relid);\n\tforeach (lc, puboids)\n\t{\n\t\tOid puboid = lfirst_oid(lc);\n\t\tchunk_add_to_publication(puboid, chunk);\n\t}\n}\n\nstatic Chunk *\nchunk_create_from_hypercube_after_lock(const Hypertable *ht, Hypercube *cube,\n\t\t\t\t\t\t\t\t\t   const char *schema_name, const char *table_name,\n\t\t\t\t\t\t\t\t\t   const char *prefix)\n{\n\tchunk_insert_check_hook_type osm_chunk_insert_hook = ts_get_osm_chunk_insert_hook();\n\n\tif (osm_chunk_insert_hook)\n\t{\n\t\t/* OSM only uses first dimension. */\n\t\tDimension *dim = &ht->space->dimensions[0];\n\t\t/* convert to PG timestamp from timescaledb internal format */\n\t\tint64 range_start =\n\t\t\tts_internal_to_time_int64(cube->slices[0]->fd.range_start, dim->fd.column_type);\n\t\tint64 range_end =\n\t\t\tts_internal_to_time_int64(cube->slices[0]->fd.range_end, dim->fd.column_type);\n\n\t\tint chunk_exists = osm_chunk_insert_hook(ht->main_table_relid, range_start, range_end);\n\n\t\tif (chunk_exists)\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"Cannot insert into tiered chunk range of %s.%s - attempt to create \"\n\t\t\t\t\t\t\t\"new chunk \"\n\t\t\t\t\t\t\t\"with range  [%s %s] failed\",\n\t\t\t\t\t\t\tNameStr(ht->fd.schema_name),\n\t\t\t\t\t\t\tNameStr(ht->fd.table_name),\n\t\t\t\t\t\t\tts_internal_to_time_string(cube->slices[0]->fd.range_start,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   dim->fd.column_type),\n\t\t\t\t\t\t\tts_internal_to_time_string(cube->slices[0]->fd.range_end,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   dim->fd.column_type)),\n\t\t\t\t\t errhint(\n\t\t\t\t\t\t \"Hypertable has tiered data with time range that overlaps the insert\")));\n\t\t}\n\t}\n\t/* Insert any new dimension slices into metadata */\n\tts_dimension_slice_insert_multi(cube->slices, cube->num_slices);\n\n\tChunk *chunk = chunk_create_only_table_after_lock(ht,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  cube,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  schema_name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  table_name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  prefix,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  get_next_chunk_id());\n\n\t/* Insert any new chunk column stats entries into the catalog */\n\tts_chunk_column_stats_insert(ht, chunk);\n\n\tchunk_add_constraints(chunk);\n\tchunk_insert_into_metadata_after_lock(chunk);\n\tchunk_create_table_constraints(ht, chunk);\n\n\t/* Add chunk to publications if hypertable is in any publications */\n\tif (ts_guc_enable_chunk_auto_publication)\n\t\tchunk_add_to_publications(chunk);\n\n\treturn chunk;\n}\n\n/*\n * Make a chunk table inherit a hypertable.\n *\n * Execution happens via high-level ALTER TABLE statement. This includes\n * numerous checks to ensure that the chunk table has all the prerequisites to\n * properly inherit the hypertable.\n */\nstatic void\nchunk_add_inheritance(Chunk *chunk, const Hypertable *ht)\n{\n\tAlterTableCmd altercmd = {\n\t\t.type = T_AlterTableCmd,\n\t\t.subtype = AT_AddInherit,\n\t\t.def = (Node *) makeRangeVar((char *) NameStr(ht->fd.schema_name),\n\t\t\t\t\t\t\t\t\t (char *) NameStr(ht->fd.table_name),\n\t\t\t\t\t\t\t\t\t 0),\n\t\t.missing_ok = false,\n\t};\n\tAlterTableStmt alterstmt = {\n\t\t.type = T_AlterTableStmt,\n\t\t.cmds = list_make1(&altercmd),\n\t\t.missing_ok = false,\n\t\t.objtype = OBJECT_TABLE,\n\t\t.relation = makeRangeVar((char *) NameStr(chunk->fd.schema_name),\n\t\t\t\t\t\t\t\t (char *) NameStr(chunk->fd.table_name),\n\t\t\t\t\t\t\t\t 0),\n\t};\n\tLOCKMODE lockmode = AlterTableGetLockLevel(alterstmt.cmds);\n\tAlterTableUtilityContext atcontext = {\n\t\t.relid = AlterTableLookupRelation(&alterstmt, lockmode),\n\t};\n\n\tAlterTable(&alterstmt, lockmode, &atcontext);\n}\n\nstatic Chunk *\nchunk_create_from_hypercube_and_table_after_lock(const Hypertable *ht, Hypercube *cube,\n\t\t\t\t\t\t\t\t\t\t\t\t Oid chunk_table_relid, const char *schema_name,\n\t\t\t\t\t\t\t\t\t\t\t\t const char *table_name, const char *prefix)\n{\n\tOid current_chunk_schemaid = get_rel_namespace(chunk_table_relid);\n\tOid new_chunk_schemaid = InvalidOid;\n\tChunk *chunk;\n\n\tAssert(OidIsValid(chunk_table_relid));\n\tAssert(OidIsValid(current_chunk_schemaid));\n\tAssert(OidIsValid(ht->main_table_relid));\n\n\tRelation ht_rel = table_open(ht->main_table_relid, AccessShareLock);\n\tRelation chunk_rel = table_open(chunk_table_relid, AccessShareLock);\n\tTupleDesc tupdesc = RelationGetDescr(chunk_rel);\n\n\tfor (int attno = 0; attno < tupdesc->natts; attno++)\n\t{\n\t\tForm_pg_attribute att = TupleDescAttr(tupdesc, attno);\n\t\tAttrNumber ht_attnum = InvalidAttrNumber;\n\n\t\t/* Ignore dropped */\n\t\tif (att->attisdropped)\n\t\t\tcontinue;\n\n\t\tht_attnum = get_attnum(ht->main_table_relid, NameStr(att->attname));\n\t\t/* Try to find the column in parent (matching on column name) */\n\t\tif (ht_attnum == InvalidAttrNumber)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_DATATYPE_MISMATCH),\n\t\t\t\t\t errmsg(\"table \\\"%s\\\" contains column \\\"%s\\\" not found in parent \\\"%s\\\"\",\n\t\t\t\t\t\t\tRelationGetRelationName(chunk_rel),\n\t\t\t\t\t\t\tNameStr(att->attname),\n\t\t\t\t\t\t\tRelationGetRelationName(ht_rel)),\n\t\t\t\t\t errdetail(\"The new chunk can contain only the columns present in parent.\")));\n\n\t\t/*\n\t\t * PG16 and later does not allow generated columns on child tables if the parent\n\t\t * column is not generated. This is a change from PG15 and earlier, where the\n\t\t * child column could be generated even if the parent was not.\n\t\t * We check if a generated column is also generated in the parent here to disallow\n\t\t * this behavior in PG15 too.\n\t\t *\n\t\t * The case when the parent column is generated and the child column is not is handled\n\t\t * by Postgres code, which will throw an error.\n\t\t *\n\t\t * This check can be removed once we drop support for PG15.\n\t\t */\n\t\tif (att->attgenerated && !get_attgenerated(ht->main_table_relid, ht_attnum))\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_DATATYPE_MISMATCH),\n\t\t\t\t\t errmsg(\"column \\\"%s\\\" in chunk table must not be a generated column\",\n\t\t\t\t\t\t\tNameStr(att->attname)),\n\t\t\t\t\t errdetail(\"Chunk column must be generated if and only if parent column is \"\n\t\t\t\t\t\t\t   \"also generated\")));\n\t\t}\n\n\t\t/* Check that the chunk column has the same expression as the hypertable column */\n\t\tif (att->attgenerated && get_attgenerated(ht->main_table_relid, ht_attnum))\n\t\t{\n\t\t\tchar *chunk_expr = ts_get_attr_expr(chunk_rel, attno + 1);\n\t\t\tchar *ht_expr = ts_get_attr_expr(ht_rel, ht_attnum);\n\n\t\t\tif (strcmp(chunk_expr, ht_expr) != 0)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_DATATYPE_MISMATCH),\n\t\t\t\t\t\t errmsg(\"chunk column \\\"%s\\\" must have the same expression as the \"\n\t\t\t\t\t\t\t\t\"hypertable column.\",\n\t\t\t\t\t\t\t\tNameStr(att->attname))));\n\t\t}\n\t}\n\ttable_close(ht_rel, NoLock);\n\n\t/* Insert any new dimension slices into metadata */\n\tts_dimension_slice_insert_multi(cube->slices, cube->num_slices);\n\tchunk = chunk_create_object(ht, cube, schema_name, table_name, prefix, get_next_chunk_id());\n\tchunk->table_id = chunk_table_relid;\n\tchunk->hypertable_relid = ht->main_table_relid;\n\n\tnew_chunk_schemaid = get_namespace_oid(NameStr(chunk->fd.schema_name), false);\n\n\tif (current_chunk_schemaid != new_chunk_schemaid)\n\t{\n\t\tObjectAddresses *objects;\n\n\t\tCheckSetNamespace(current_chunk_schemaid, new_chunk_schemaid);\n\t\tobjects = new_object_addresses();\n\t\tAlterTableNamespaceInternal(chunk_rel, current_chunk_schemaid, new_chunk_schemaid, objects);\n\t\tfree_object_addresses(objects);\n\t\t/* Make changes visible */\n\t\tCommandCounterIncrement();\n\t}\n\ttable_close(chunk_rel, NoLock);\n\n\tif (namestrcmp(&chunk->fd.table_name, get_rel_name(chunk_table_relid)) != 0)\n\t{\n\t\t/* Renaming will acquire and keep an AccessExclusivelock on the chunk\n\t\t * table */\n\t\tRenameRelationInternal(chunk_table_relid, NameStr(chunk->fd.table_name), true, false);\n\t\t/* Make changes visible */\n\t\tCommandCounterIncrement();\n\t}\n\n\t/* Note that we do not automatically add constrains and triggers to the\n\t * chunk table when the chunk is created from an existing table. However,\n\t * PostgreSQL currently validates that CHECK constraints exists, but no\n\t * validation is done for other objects, including triggers, UNIQUE,\n\t * PRIMARY KEY, and FOREIGN KEY constraints. We might want to either\n\t * enforce that these constraints exist prior to creating the chunk from a\n\t * table, or we ensure that they are automatically added when the chunk is\n\t * created. However, for the latter case, we risk duplicating constraints\n\t * and triggers if some of them already exist on the chunk table prior to\n\t * creating the chunk from it. */\n\tchunk_add_constraints(chunk);\n\tts_chunk_constraint_check_violated(chunk, ht->space);\n\tchunk_insert_into_metadata_after_lock(chunk);\n\tchunk_add_inheritance(chunk, ht);\n\tchunk_create_table_constraints(ht, chunk);\n\n\t/* Add chunk to publications if hypertable is in any publications */\n\tif (ts_guc_enable_chunk_auto_publication)\n\t\tchunk_add_to_publications(chunk);\n\n\treturn chunk;\n}\n\nstatic Chunk *\nchunk_create_from_point_after_lock(const Hypertable *ht, const Point *p, const char *schema_name,\n\t\t\t\t\t\t\t\t   const char *table_name, const char *prefix)\n{\n\tHyperspace *hs = ht->space;\n\tHypercube *cube;\n\tScanTupLock tuplock = {\n\t\t.lockmode = LockTupleKeyShare,\n\t\t.waitpolicy = LockWaitBlock,\n\t};\n\n\t/*\n\t * If the user has enabled adaptive chunking, call the function to\n\t * calculate and set the new chunk time interval.\n\t */\n\tcalculate_and_set_new_chunk_interval(ht, p);\n\n\t/* Calculate the hypercube for a new chunk that covers the tuple's point.\n\t *\n\t * We lock the tuple in KEY SHARE mode since we are concerned with\n\t * ensuring that it is not deleted (or the key value changed) while we are\n\t * adding chunk constraints (in `ts_chunk_constraints_insert_metadata`\n\t * called in `chunk_create_metadata_after_lock`). The range of a dimension\n\t * slice does not change, but we should use the weakest lock possible to\n\t * not unnecessarily block other operations. */\n\tcube = ts_hypercube_calculate_from_point(hs, p, &tuplock);\n\n\t/* Resolve collisions with other chunks by cutting the new hypercube */\n\tchunk_collision_resolve(ht, cube, p);\n\n\treturn chunk_create_from_hypercube_after_lock(ht, cube, schema_name, table_name, prefix);\n}\n\nChunk *\nts_chunk_find_or_create_without_cuts(const Hypertable *ht, Hypercube *hc, const char *schema_name,\n\t\t\t\t\t\t\t\t\t const char *table_name, Oid chunk_table_relid, bool *created)\n{\n\tChunkStub *stub;\n\tChunk *chunk = NULL;\n\n\tDEBUG_WAITPOINT(\"find_or_create_chunk_start\");\n\n\tstub = chunk_collides(ht, hc);\n\n\tif (NULL == stub)\n\t{\n\t\t/* Serialize chunk creation around the root hypertable */\n\t\tLockRelationOid(ht->main_table_relid, ShareUpdateExclusiveLock);\n\n\t\t/* Check again after lock */\n\t\tstub = chunk_collides(ht, hc);\n\n\t\tif (NULL == stub)\n\t\t{\n\t\t\tScanTupLock tuplock = {\n\t\t\t\t.lockmode = LockTupleKeyShare,\n\t\t\t\t.waitpolicy = LockWaitBlock,\n\t\t\t};\n\n\t\t\t/* Lock all slices that already exist to ensure they remain when we\n\t\t\t * commit since we won't create those slices ourselves. */\n\t\t\tts_hypercube_find_existing_slices(hc, &tuplock);\n\n\t\t\tif (OidIsValid(chunk_table_relid))\n\t\t\t\tchunk = chunk_create_from_hypercube_and_table_after_lock(ht,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t hc,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t chunk_table_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t schema_name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t table_name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t NULL);\n\t\t\telse\n\t\t\t\tchunk =\n\t\t\t\t\tchunk_create_from_hypercube_after_lock(ht, hc, schema_name, table_name, NULL);\n\n\t\t\tif (NULL != created)\n\t\t\t\t*created = true;\n\n\t\t\tASSERT_IS_VALID_CHUNK(chunk);\n\n\t\t\tDEBUG_WAITPOINT(\"find_or_create_chunk_created\");\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\t/* We didn't need the lock, so release it */\n\t\tUnlockRelationOid(ht->main_table_relid, ShareUpdateExclusiveLock);\n\t}\n\n\tAssert(NULL != stub);\n\n\t/* We can only use an existing chunk if it has identical dimensional\n\t * constraints. Otherwise, throw an error */\n\tif (OidIsValid(chunk_table_relid) || !ts_hypercube_equal(stub->cube, hc))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_TS_CHUNK_COLLISION),\n\t\t\t\t errmsg(\"chunk creation failed due to collision\")));\n\n\t/* chunk_collides only returned a stub, so we need to lookup the full\n\t * chunk. */\n\tchunk = ts_chunk_get_by_id(stub->id, true);\n\n\tif (NULL != created)\n\t\t*created = false;\n\n\tDEBUG_WAITPOINT(\"find_or_create_chunk_found\");\n\n\tASSERT_IS_VALID_CHUNK(chunk);\n\n\treturn chunk;\n}\n\n/*\n * Find the chunk containing the given point, locking all its dimension slices\n * for share. NULL if not found.\n */\nChunk *\nts_chunk_find_for_point(const Hypertable *ht, const Point *p, LOCKMODE lockmode)\n{\n\tScanTupLock slice_lock = {\n\t\t.lockmode = LockTupleKeyShare,\n\t\t.waitpolicy = LockWaitBlock,\n\t\t.lockflags = TUPLE_LOCK_FLAG_FIND_LAST_VERSION,\n\t};\n\n\tint32 chunk_id = ts_chunk_point_find_chunk_id(ht, p, NULL);\n\n\tif (chunk_id == INVALID_CHUNK_ID)\n\t\treturn NULL;\n\n\t/* The chunk might be dropped, so we don't fail if we haven't found it. */\n\treturn ts_chunk_get_by_id_with_slice_lock(chunk_id,\n\t\t\t\t\t\t\t\t\t\t\t  lockmode,\n\t\t\t\t\t\t\t\t\t\t\t  lockmode == NoLock ? NULL : &slice_lock,\n\t\t\t\t\t\t\t\t\t\t\t  /* fail_if_not_found = */ false);\n}\n\n/*\n * Create a chunk through insertion of a tuple at a given point.\n *\n * If some other process managed to create the chunk before us, the existing\n * chunk is locked with \"chunk_lockmode\".\n */\nChunk *\nts_chunk_create_for_point(const Hypertable *ht, const Point *p, const char *schema,\n\t\t\t\t\t\t  const char *prefix, LOCKMODE chunk_lockmode)\n{\n\t/*\n\t * Serialize chunk creation around a lock on the \"main table\" to avoid\n\t * multiple processes trying to create the same chunk. We use a\n\t * ShareUpdateExclusiveLock, which is the weakest lock possible that\n\t * conflicts with itself. The lock needs to be held until transaction end.\n\t */\n\tLockRelationOid(ht->main_table_relid, ShareUpdateExclusiveLock);\n\n\tDEBUG_WAITPOINT(\"chunk_create_for_point\");\n\n\t/*\n\t * Recheck if someone else created the chunk before we got the table\n\t * lock. The returned chunk will have all slices locked so that they\n\t * aren't removed.\n\t */\n\tint chunk_id = ts_chunk_point_find_chunk_id(ht, p, NULL);\n\tif (chunk_id != INVALID_CHUNK_ID)\n\t{\n\t\tScanTupLock slice_lock = {\n\t\t\t.lockmode = LockTupleKeyShare,\n\t\t\t.waitpolicy = LockWaitBlock,\n\t\t\t.lockflags = TUPLE_LOCK_FLAG_FIND_LAST_VERSION,\n\t\t};\n\n\t\t/* The chunk might be dropped, so we don't fail if we haven't found it. */\n\t\tChunk *chunk = ts_chunk_get_by_id_with_slice_lock(chunk_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  chunk_lockmode,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  &slice_lock,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  /* fail_if_not_found = */ false);\n\t\tif (chunk != NULL)\n\t\t{\n\t\t\t/*\n\t\t\t * Chunk was not created by us but by someone else, so we can\n\t\t\t * release the lock early.\n\t\t\t */\n\t\t\tUnlockRelationOid(ht->main_table_relid, ShareUpdateExclusiveLock);\n\t\t\treturn chunk;\n\t\t}\n\t}\n\n\t/* Create the chunk normally. */\n\tChunk *chunk = chunk_create_from_point_after_lock(ht, p, schema, NULL, prefix);\n\n\tASSERT_IS_VALID_CHUNK(chunk);\n\n\treturn chunk;\n}\n\nstatic void\nscan_add_chunk_context(ChunkScanCtx *ctx, int32 chunk_id, List *dimension_vecs, List **l_chunk_ids)\n{\n\tbool found = false;\n\tChunkScanEntry *entry = hash_search(ctx->htab, &chunk_id, HASH_ENTER, &found);\n\tif (!found)\n\t{\n\t\tentry->stub = NULL;\n\t\tentry->num_dimension_constraints = 0;\n\t}\n\n\tentry->num_dimension_constraints++;\n\n\t/*\n\t * A chunk is complete when we've found slices for all required dimensions,\n\t * i.e., a complete subspace.\n\t */\n\tif (entry->num_dimension_constraints == list_length(dimension_vecs))\n\t{\n\t\t*l_chunk_ids = lappend_int(*l_chunk_ids, entry->chunk_id);\n\t}\n}\n\n/*\n * Find the chunks that belong to the subspace identified by the given dimension\n * vectors. We might be restricting only some dimensions, so this subspace is\n * not a hypercube, but a hyperplane of some order.\n * Returns a list of matching chunk ids.\n */\nList *\nts_chunk_id_find_in_subspace(Hypertable *ht, List *dimension_vecs)\n{\n\tList *chunk_ids = NIL;\n\n\tChunkScanCtx ctx;\n\tchunk_scan_ctx_init(&ctx, ht, /* point = */ NULL);\n\n\tScanIterator iterator = ts_chunk_constraint_scan_iterator_create(CurrentMemoryContext);\n\n\tListCell *lc;\n\tforeach (lc, dimension_vecs)\n\t{\n\t\tconst DimensionVec *vec = lfirst(lc);\n\n\t\t/*\n\t\t * If it's an entry of type DIMENSION_TYPE_STATS then we need to get\n\t\t * the chunks using the _timescaledb_catalog.chunk_column_stats catalog.\n\t\t */\n\t\tAssert(vec->dri != NULL);\n\t\tif (vec->dri->dimension->type == DIMENSION_TYPE_STATS)\n\t\t{\n\t\t\tListCell *lc;\n\t\t\tList *range_chunk_ids;\n\n\t\t\tAssert(vec->num_slices == 0);\n\t\t\trange_chunk_ids = ts_chunk_column_stats_get_chunk_ids_by_scan(vec->dri);\n\n\t\t\t/* add these chunks to the context appropriately. */\n\t\t\tforeach (lc, range_chunk_ids)\n\t\t\t{\n\t\t\t\tint32 chunk_id = lfirst_int(lc);\n\n\t\t\t\tscan_add_chunk_context(&ctx, chunk_id, dimension_vecs, &chunk_ids);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\t/*\n\t\t * We shouldn't see a dimension with zero matching dimension slices.\n\t\t * That would mean that no chunks match at all, this should have been\n\t\t * handled earlier by gather_restriction_dimension_vectors().\n\t\t */\n\t\tAssert(vec->num_slices > 0);\n\t\tfor (int i = 0; i < vec->num_slices; i++)\n\t\t{\n\t\t\tconst DimensionSlice *slice = vec->slices[i];\n\n\t\t\tts_chunk_constraint_scan_iterator_set_slice_id(&iterator, slice->fd.id);\n\t\t\tts_scan_iterator_start_or_restart_scan(&iterator);\n\n\t\t\twhile (ts_scan_iterator_next(&iterator) != NULL)\n\t\t\t{\n\t\t\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\t\t\tbool PG_USED_FOR_ASSERTS_ONLY isnull = true;\n\t\t\t\tDatum datum = slot_getattr(ti->slot, Anum_chunk_constraint_chunk_id, &isnull);\n\t\t\t\tAssert(!isnull);\n\t\t\t\tint32 current_chunk_id = DatumGetInt32(datum);\n\t\t\t\tAssert(current_chunk_id != INVALID_CHUNK_ID);\n\n\t\t\t\t/*\n\t\t\t\t * We have only the dimension constraints here, because we're searching\n\t\t\t\t * by dimension slice id.\n\t\t\t\t */\n\t\t\t\tAssert(!slot_attisnull(ts_scan_iterator_slot(&iterator),\n\t\t\t\t\t\t\t\t\t   Anum_chunk_constraint_dimension_slice_id));\n\t\t\t\tscan_add_chunk_context(&ctx, current_chunk_id, dimension_vecs, &chunk_ids);\n\t\t\t}\n\t\t}\n\t}\n\n\tts_scan_iterator_close(&iterator);\n\n\tchunk_scan_ctx_destroy(&ctx);\n\n\treturn chunk_ids;\n}\n\nChunkStub *\nts_chunk_stub_create(int32 id, int16 num_constraints)\n{\n\tChunkStub *stub;\n\n\tstub = palloc0(sizeof(*stub));\n\tstub->id = id;\n\n\tif (num_constraints > 0)\n\t\tstub->constraints = ts_chunk_constraints_alloc(num_constraints, CurrentMemoryContext);\n\n\treturn stub;\n}\n\nChunk *\nts_chunk_create_base(int32 id, int16 num_constraints, const char relkind)\n{\n\tChunk *chunk;\n\n\tchunk = palloc0(sizeof(Chunk));\n\tchunk->fd.id = id;\n\tchunk->fd.compressed_chunk_id = INVALID_CHUNK_ID;\n\tchunk->relkind = relkind;\n\tchunk->fd.creation_time = GetCurrentTimestamp();\n\n\tif (num_constraints > 0)\n\t\tchunk->constraints = ts_chunk_constraints_alloc(num_constraints, CurrentMemoryContext);\n\n\treturn chunk;\n}\n\n/*\n * Build a chunk from a chunk tuple and a stub.\n *\n * The stub allows the chunk to be constructed more efficiently. But if the stub\n * is not \"valid\", dimension slices and constraints are fully\n * rescanned/recreated.\n */\nChunk *\nts_chunk_build_from_tuple_and_stub(Chunk **chunkptr, TupleInfo *ti, const ChunkStub *stub,\n\t\t\t\t\t\t\t\t   const ScanTupLock *slice_lock)\n{\n\tChunk *chunk = NULL;\n\tint num_constraints_hint = stub ? stub->constraints->num_constraints : 2;\n\n\tif (chunkptr == NULL)\n\t\tchunkptr = &chunk;\n\n\tif (*chunkptr == NULL)\n\t\t*chunkptr = MemoryContextAllocZero(ti->mctx, sizeof(Chunk));\n\n\tchunk = *chunkptr;\n\tts_chunk_formdata_fill(&chunk->fd, ti);\n\n\t/*\n\t * When searching for the chunk stub matching the dimensional point, we\n\t * only scanned for dimensional constraints. We now need to rescan the\n\t * constraints to also get the inherited constraints.\n\t */\n\tchunk->constraints =\n\t\tts_chunk_constraint_scan_by_chunk_id(chunk->fd.id, num_constraints_hint, ti->mctx);\n\n\t/* If a stub is provided then reuse its hypercube. Note that stubs that\n\t * are results of a point or range scan might be incomplete (in terms of\n\t * number of slices and constraints). Only a chunk stub that matches in\n\t * all dimensions will have a complete hypercube. Thus, we need to check\n\t * the validity of the stub before we can reuse it.\n\t */\n\tif (chunk_stub_is_valid(stub, chunk->constraints->num_dimension_constraints))\n\t{\n\t\tMemoryContext oldctx = MemoryContextSwitchTo(ti->mctx);\n\n\t\tchunk->cube = ts_hypercube_copy(stub->cube);\n\t\tMemoryContextSwitchTo(oldctx);\n\n\t\t/*\n\t\t * The hypercube slices were filled in during the scan. Now we need to\n\t\t * sort them in dimension order.\n\t\t */\n\t\tts_hypercube_slice_sort(chunk->cube);\n\t}\n\telse\n\t{\n\t\tScanIterator it = ts_dimension_slice_scan_iterator_create(slice_lock, ti->mctx);\n\t\tchunk->cube = ts_hypercube_from_constraints(chunk->constraints, &it);\n\t\tts_scan_iterator_close(&it);\n\t}\n\n\tchunk->hypertable_relid = ts_hypertable_id_to_relid(chunk->fd.hypertable_id, false);\n\tts_get_rel_info_by_name(NameStr(chunk->fd.schema_name),\n\t\t\t\t\t\t\tNameStr(chunk->fd.table_name),\n\t\t\t\t\t\t\t&chunk->table_id,\n\t\t\t\t\t\t\t&chunk->relkind);\n\n\tEnsure(chunk->relkind > 0,\n\t\t   \"relkind for chunk \\\"%s\\\".\\\"%s\\\" is invalid\",\n\t\t   NameStr(chunk->fd.schema_name),\n\t\t   NameStr(chunk->fd.table_name));\n\n\treturn chunk;\n}\n\nstatic ScanTupleResult\nchunk_tuple_found(TupleInfo *ti, void *arg)\n{\n\tChunkStubScanCtx *stubctx = arg;\n\n\t/*\n\t * The chunk table could also have been dropped concurrently. Try to\n\t * acquire the requested lock in order to guarantee that the chunk table\n\t * still exists.\n\t */\n\tif (stubctx->chunk_lockmode != NoLock)\n\t{\n\t\tDatum schema_name;\n\t\tDatum table_name;\n\t\tconst RangeVar *rv;\n\t\tbool isnull;\n\n\t\tschema_name = slot_getattr(ti->slot, Anum_chunk_schema_name, &isnull);\n\t\tAssert(!isnull);\n\t\ttable_name = slot_getattr(ti->slot, Anum_chunk_table_name, &isnull);\n\t\tAssert(!isnull);\n\n\t\trv = makeRangeVar(NameStr(*DatumGetName(schema_name)),\n\t\t\t\t\t\t  NameStr(*DatumGetName(table_name)),\n\t\t\t\t\t\t  -1);\n\t\tRelation rel = table_openrv_extended(rv, stubctx->chunk_lockmode, true);\n\n\t\tif (!rel)\n\t\t\treturn SCAN_DONE;\n\n\t\ttable_close(rel, NoLock);\n\t}\n\n\tts_chunk_build_from_tuple_and_stub(&stubctx->chunk, ti, stubctx->stub, stubctx->slice_lock);\n\n\treturn SCAN_DONE;\n}\n\n/* Create a chunk by scanning on chunk ID. A stub must be provided as input. */\nstatic Chunk *\nchunk_create_from_stub(ChunkStubScanCtx *stubctx)\n{\n\tScanKeyData scankey[1];\n\tCatalog *catalog = ts_catalog_get();\n\tint num_found;\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, CHUNK),\n\t\t.index = catalog_get_index(catalog, CHUNK, CHUNK_ID_INDEX),\n\t\t.nkeys = 1,\n\t\t.scankey = scankey,\n\t\t.data = stubctx,\n\t\t.tuple_found = chunk_tuple_found,\n\t\t.lockmode = AccessShareLock,\n\t\t.scandirection = ForwardScanDirection,\n\t};\n\n\t/*\n\t * Perform an index scan on chunk ID.\n\t */\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_chunk_idx_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(stubctx->stub->id));\n\n\tnum_found = ts_scanner_scan(&scanctx);\n\n\tAssert(num_found == 0 || num_found == 1);\n\n\tif (num_found != 1)\n\t\telog(ERROR, \"no chunk found with ID %d\", stubctx->stub->id);\n\n\tAssert(stubctx->chunk != NULL);\n\n\treturn stubctx->chunk;\n}\n\n/*\n * Initialize a chunk scan context.\n *\n * A chunk scan context is used to join chunk-related information from metadata\n * tables during scans.\n */\nstatic void\nchunk_scan_ctx_init(ChunkScanCtx *ctx, const Hypertable *ht, const Point *point)\n{\n\tstruct HASHCTL hctl = {\n\t\t.keysize = sizeof(int32),\n\t\t.entrysize = sizeof(ChunkScanEntry),\n\t\t.hcxt = CurrentMemoryContext,\n\t};\n\n\tmemset(ctx, 0, sizeof(*ctx));\n\tctx->htab = hash_create(\"chunk-scan-context\", 20, &hctl, HASH_ELEM | HASH_CONTEXT | HASH_BLOBS);\n\tctx->ht = ht;\n\tctx->point = point;\n\tctx->lockmode = NoLock;\n}\n\n/*\n * Destroy the chunk scan context.\n *\n * This will free the hash table in the context, but not the chunks within since\n * they are not allocated on the hash tables memory context.\n */\nstatic void\nchunk_scan_ctx_destroy(ChunkScanCtx *ctx)\n{\n\thash_destroy(ctx->htab);\n}\n\nstatic inline void\ndimension_slice_and_chunk_constraint_join(ChunkScanCtx *scanctx, const DimensionVec *vec)\n{\n\tint i;\n\n\tfor (i = 0; i < vec->num_slices; i++)\n\t{\n\t\t/*\n\t\t * For each dimension slice, find matching constraints. These will be\n\t\t * saved in the scan context\n\t\t */\n\t\tts_chunk_constraint_scan_by_dimension_slice(vec->slices[i], scanctx, CurrentMemoryContext);\n\t}\n}\n\n/*\n * Scan for chunks that collide with the given hypercube.\n *\n * Collisions are determined using axis-aligned bounding box collision detection\n * generalized to N dimensions. Slices are collected in the scan context's hash\n * table according to the chunk IDs they are associated with. A slice might\n * represent the dimensional bound of multiple chunks, and thus is added to all\n * the hash table slots of those chunks. At the end of the scan, those chunks\n * that have a full set of slices are the ones that actually collide with the\n * given hypercube.\n *\n * Chunks in the scan context that do not collide (do not have a full set of\n * slices), might still be important for ensuring alignment in those dimensions\n * that require alignment.\n */\nstatic void\nchunk_collision_scan(ChunkScanCtx *scanctx, const Hypercube *cube)\n{\n\tint i;\n\n\t/* Scan all dimensions for colliding slices */\n\tfor (i = 0; i < scanctx->ht->space->num_dimensions; i++)\n\t{\n\t\tDimensionVec *vec;\n\t\tDimensionSlice *slice = cube->slices[i];\n\n\t\tvec = dimension_slice_collision_scan(slice->fd.dimension_id,\n\t\t\t\t\t\t\t\t\t\t\t slice->fd.range_start,\n\t\t\t\t\t\t\t\t\t\t\t slice->fd.range_end);\n\n\t\t/* Add the slices to all the chunks they are associated with */\n\t\tdimension_slice_and_chunk_constraint_join(scanctx, vec);\n\t}\n}\n\n/*\n * Apply a function to each stub in the scan context's hash table. If the limit\n * is greater than zero only a limited number of chunks will be processed.\n *\n * The chunk handler function (on_chunk_func) should return CHUNK_PROCESSED if\n * the chunk should be considered processed and count towards the given\n * limit. CHUNK_IGNORE can be returned to have a chunk NOT count towards the\n * limit. CHUNK_DONE counts the chunk but aborts processing irrespective of\n * whether the limit is reached or not.\n *\n * Returns the number of processed chunks.\n */\nstatic int\nchunk_scan_ctx_foreach_chunk_stub(ChunkScanCtx *ctx, on_chunk_stub_func on_chunk, uint64 limit)\n{\n\tHASH_SEQ_STATUS status;\n\tChunkScanEntry *entry;\n\n\tctx->num_processed = 0;\n\thash_seq_init(&status, ctx->htab);\n\n\tfor (entry = hash_seq_search(&status); entry != NULL; entry = hash_seq_search(&status))\n\t{\n\t\tswitch (on_chunk(ctx, entry->stub))\n\t\t{\n\t\t\tcase CHUNK_DONE:\n\t\t\t\tctx->num_processed++;\n\t\t\t\thash_seq_term(&status);\n\t\t\t\treturn ctx->num_processed;\n\t\t\tcase CHUNK_PROCESSED:\n\t\t\t\tctx->num_processed++;\n\n\t\t\t\tif (limit > 0 && ctx->num_processed == limit)\n\t\t\t\t{\n\t\t\t\t\thash_seq_term(&status);\n\t\t\t\t\treturn ctx->num_processed;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase CHUNK_IGNORED:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn ctx->num_processed;\n}\n\ntypedef struct ChunkScanCtxAddChunkData\n{\n\tChunk *chunks;\n\tuint64 max_chunks;\n\tuint64 num_chunks;\n} ChunkScanCtxAddChunkData;\n\nstatic ChunkResult\nchunk_scan_context_add_chunk(ChunkScanCtx *scanctx, ChunkStub *stub)\n{\n\tChunkScanCtxAddChunkData *data = scanctx->data;\n\tChunkStubScanCtx stubctx = {\n\t\t.chunk = &data->chunks[data->num_chunks],\n\t\t.stub = stub,\n\t};\n\n\tAssert(data->num_chunks < data->max_chunks);\n\tchunk_create_from_stub(&stubctx);\n\n\tdata->num_chunks++;\n\n\treturn CHUNK_PROCESSED;\n}\n\nTM_Result\nts_chunk_lock_for_creating_compressed_chunk(int32 chunk_id, int32 *compressed_chunk_id)\n{\n\tScanIterator iterator;\n\tbool found = false;\n\tTM_Result lockresult;\n\tScanTupLock tuplock = {\n\t\t.lockmode = LockTupleExclusive,\n\t\t.waitpolicy = LockWaitBlock,\n\t\t.lockflags = TUPLE_LOCK_FLAG_FIND_LAST_VERSION,\n\t};\n\n\titerator = ts_scan_iterator_create(CHUNK, RowShareLock, CurrentMemoryContext);\n\tts_chunk_scan_iterator_set_chunk_id(&iterator, chunk_id);\n\titerator.ctx.tuplock = &tuplock;\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tlockresult = ti->lockresult;\n\n\t\tif (lockresult == TM_Ok && compressed_chunk_id)\n\t\t{\n\t\t\tbool isnull;\n\t\t\tDatum value = slot_getattr(ti->slot, Anum_chunk_compressed_chunk_id, &isnull);\n\t\t\t*compressed_chunk_id = isnull ? INVALID_CHUNK_ID : DatumGetInt32(value);\n\t\t}\n\t\tfound = true;\n\t}\n\n\tts_scan_iterator_close(&iterator);\n\n\tif (!found)\n\t\telog(ERROR, \"chunk with ID %d does not exist\", chunk_id);\n\n\treturn lockresult;\n}\n\n/*\n * Scan for the chunk that encloses the given point.\n *\n * In each dimension there can be one or more slices that match the point's\n * coordinate in that dimension. Slices are collected in the scan context's hash\n * table according to the chunk IDs they are associated with. A slice might\n * represent the dimensional bound of multiple chunks, and thus is added to all\n * the hash table slots of those chunks. At the end of the scan there will be at\n * most one chunk that has a complete set of slices, since a point cannot belong\n * to two chunks.\n *\n * This involves:\n *\n * 1) For each dimension:\n *\t  - Find all dimension slices that match the dimension\n * 2) For each dimension slice:\n *\t  - Find all chunk constraints matching the dimension slice\n * 3) For each matching chunk constraint\n *\t  - Insert a chunk stub into a hash table and add the constraint to the chunk\n *\t  - If chunk already exists in hash table, add the constraint to the chunk\n * 4) At the end of the scan, only one chunk in the hash table should have\n *\t  N number of constraints. This is the matching chunk.\n *\n * NOTE: this function allocates transient data, e.g., dimension slice,\n * constraints and chunks, that in the end are not part of the returned\n * chunk. Therefore, this scan should be executed on a transient memory\n * context. The returned chunk needs to be copied into another memory context in\n * case it needs to live beyond the lifetime of the other data.\n *\n * The slices can be locked by specifying an optional slice lock.\n */\nint32\nts_chunk_point_find_chunk_id(const Hypertable *ht, const Point *p, const ScanTupLock *slice_lock)\n{\n\tint32 matching_chunk_id = 0;\n\n\t/* The scan context will keep the state accumulated during the scan */\n\tChunkScanCtx ctx;\n\tchunk_scan_ctx_init(&ctx, ht, p);\n\n\t/* Scan all dimensions for slices enclosing the point */\n\tList *all_slices = NIL;\n\tfor (int dimension_index = 0; dimension_index < ctx.ht->space->num_dimensions;\n\t\t dimension_index++)\n\t{\n\t\tts_dimension_slice_scan_list(ctx.ht->space->dimensions[dimension_index].fd.id,\n\t\t\t\t\t\t\t\t\t p->coordinates[dimension_index],\n\t\t\t\t\t\t\t\t\t &all_slices,\n\t\t\t\t\t\t\t\t\t slice_lock);\n\t}\n\n\t/* Find constraints matching dimension slices. */\n\tScanIterator iterator = ts_chunk_constraint_scan_iterator_create(CurrentMemoryContext);\n\n\tListCell *lc;\n\tforeach (lc, all_slices)\n\t{\n\t\tDimensionSlice *slice = (DimensionSlice *) lfirst(lc);\n\n\t\tts_chunk_constraint_scan_iterator_set_slice_id(&iterator, slice->fd.id);\n\t\tts_scan_iterator_start_or_restart_scan(&iterator);\n\n\t\twhile (ts_scan_iterator_next(&iterator) != NULL)\n\t\t{\n\t\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\t\tbool PG_USED_FOR_ASSERTS_ONLY isnull = true;\n\t\t\tDatum datum = slot_getattr(ti->slot, Anum_chunk_constraint_chunk_id, &isnull);\n\t\t\tAssert(!isnull);\n\t\t\tint32 current_chunk_id = DatumGetInt32(datum);\n\t\t\tAssert(current_chunk_id != INVALID_CHUNK_ID);\n\n\t\t\tbool found = false;\n\t\t\tChunkScanEntry *entry = hash_search(ctx.htab, &current_chunk_id, HASH_ENTER, &found);\n\t\t\tif (!found)\n\t\t\t{\n\t\t\t\tentry->stub = NULL;\n\t\t\t\tentry->num_dimension_constraints = 0;\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * We have only the dimension constraints here, because we're searching\n\t\t\t * by dimension slice id.\n\t\t\t */\n\t\t\tAssert(!slot_attisnull(ts_scan_iterator_slot(&iterator),\n\t\t\t\t\t\t\t\t   Anum_chunk_constraint_dimension_slice_id));\n\t\t\tentry->num_dimension_constraints++;\n\n\t\t\t/*\n\t\t\t * A chunk is complete when we've found slices for all its dimensions,\n\t\t\t * i.e., a complete hypercube. Only one chunk matches a given hyperspace\n\t\t\t * point, so we can stop early.\n\t\t\t */\n\t\t\tif (entry->num_dimension_constraints == ctx.ht->space->num_dimensions)\n\t\t\t{\n\t\t\t\tmatching_chunk_id = entry->chunk_id;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (matching_chunk_id != INVALID_CHUNK_ID)\n\t\t{\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tts_scan_iterator_close(&iterator);\n\n\tchunk_scan_ctx_destroy(&ctx);\n\n\treturn matching_chunk_id;\n}\n\n/*\n * Find all the chunks in hyperspace that include elements (dimension slices)\n * calculated by given range constraints and return the corresponding\n * ChunkScanCxt. It is the caller's responsibility to destroy this context after\n * usage.\n */\nstatic void\nchunks_find_all_in_range_limit(const Hypertable *ht, const Dimension *time_dim,\n\t\t\t\t\t\t\t   StrategyNumber start_strategy, int64 start_value,\n\t\t\t\t\t\t\t   StrategyNumber end_strategy, int64 end_value, int limit,\n\t\t\t\t\t\t\t   uint64 *num_found, ScanTupLock *tuplock, ChunkScanCtx *ctx)\n{\n\tDimensionVec *slices;\n\n\tAssert(ht != NULL);\n\n\t/* must have been checked earlier that this is the case */\n\tAssert(time_dim != NULL);\n\n\tslices = ts_dimension_slice_scan_range_limit(time_dim->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t start_strategy,\n\t\t\t\t\t\t\t\t\t\t\t\t start_value,\n\t\t\t\t\t\t\t\t\t\t\t\t end_strategy,\n\t\t\t\t\t\t\t\t\t\t\t\t end_value,\n\t\t\t\t\t\t\t\t\t\t\t\t limit,\n\t\t\t\t\t\t\t\t\t\t\t\t tuplock);\n\n\t/* The scan context will keep the state accumulated during the scan */\n\tchunk_scan_ctx_init(ctx, ht, NULL);\n\n\t/* No abort when the first chunk is found */\n\tctx->early_abort = false;\n\n\t/* Scan for chunks that are in range */\n\tdimension_slice_and_chunk_constraint_join(ctx, slices);\n\n\t*num_found += hash_get_num_entries(ctx->htab);\n}\n\n/* show_chunks SQL function handler */\nDatum\nts_chunk_show_chunks(PG_FUNCTION_ARGS)\n{\n\t/*\n\t * show_chunks_return_srf is called even when it is not the first call but only\n\t * after doing some computation first\n\t */\n\tif (SRF_IS_FIRSTCALL())\n\t{\n\t\tFuncCallContext *funcctx;\n\t\tOid relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\t\tHypertable *ht;\n\t\tconst Dimension *time_dim;\n\t\tCache *hcache;\n\t\tint64 older_than = PG_INT64_MAX;\n\t\tint64 newer_than = PG_INT64_MIN;\n\t\tint64 created_before = PG_INT64_MAX;\n\t\tint64 created_after = PG_INT64_MIN;\n\t\tOid time_type;\n\t\tOid arg_type;\n\t\tbool older_newer = false;\n\t\tbool before_after = false;\n\n\t\thcache = ts_hypertable_cache_pin();\n\t\tht = ts_resolve_hypertable_from_table_or_cagg(hcache, relid, true);\n\t\tAssert(ht != NULL);\n\t\ttime_dim = hyperspace_get_open_dimension(ht->space, 0);\n\n\t\tif (!time_dim)\n\t\t\ttime_dim = hyperspace_get_closed_dimension(ht->space, 0);\n\n\t\tif (time_dim && IS_CLOSED_DIMENSION(time_dim) && (!PG_ARGISNULL(1) || !PG_ARGISNULL(2)))\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"cannot specify \\\"older_than\\\" or \\\"newer_than\\\" for \"\n\t\t\t\t\t\t\t\"\\\"closed\\\"-like partitioning types\"),\n\t\t\t\t\t errhint(\"Use \\\"created_before\\\" and/or \\\"created_after\\\" which rely on the \"\n\t\t\t\t\t\t\t \"chunk creation time values.\")));\n\n\t\tif (time_dim)\n\t\t\ttime_type = ts_dimension_get_partition_type(time_dim);\n\t\telse\n\t\t\ttime_type = InvalidOid;\n\n\t\t/*\n\t\t * Treat UUID (v7) as a timestamptz type. The expected input is an interval or absolute\n\t\t * timestamptz.\n\t\t */\n\t\tif (IS_UUID_TYPE(time_type))\n\t\t\ttime_type = TIMESTAMPTZOID;\n\n\t\t/* note that arg_types will be the same for all specified \"ANY\" elements for a given call */\n\t\targ_type = InvalidOid;\n\t\tif (!PG_ARGISNULL(1))\n\t\t{\n\t\t\targ_type = get_fn_expr_argtype(fcinfo->flinfo, 1);\n\t\t\tolder_than = ts_time_value_from_arg(PG_GETARG_DATUM(1), arg_type, time_type, true);\n\t\t\tolder_newer = true;\n\t\t}\n\n\t\tif (!PG_ARGISNULL(2))\n\t\t{\n\t\t\targ_type = get_fn_expr_argtype(fcinfo->flinfo, 2);\n\t\t\tnewer_than = ts_time_value_from_arg(PG_GETARG_DATUM(2), arg_type, time_type, true);\n\t\t\tolder_newer = true;\n\t\t}\n\n\t\t/*\n\t\t * We cannot have a mix of [older_than/newer_than] and [created_before/created_after].\n\t\t * So, check that first. Note that created_before/created_after have a type of\n\t\t * TIMESTAMPTZOID regardless of the partitioning type.\n\t\t */\n\n\t\tif (!PG_ARGISNULL(3))\n\t\t{\n\t\t\tif (older_newer)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t\t errmsg(\"cannot specify \\\"older_than\\\" or \\\"newer_than\\\" together with \"\n\t\t\t\t\t\t\t\t\"\\\"created_before\\\"\"\n\t\t\t\t\t\t\t\t\"or \\\"created_after\\\"\")));\n\n\t\t\targ_type = get_fn_expr_argtype(fcinfo->flinfo, 3);\n\t\t\t/* We use the existing function for various type/conversion checks */\n\t\t\tcreated_before =\n\t\t\t\tts_time_value_from_arg(PG_GETARG_DATUM(3), arg_type, TIMESTAMPTZOID, false);\n\t\t\t/* convert into int64 format for comparisons */\n\t\t\tcreated_before = ts_internal_to_time_int64(created_before, TIMESTAMPTZOID);\n\t\t\tbefore_after = true;\n\t\t}\n\n\t\tif (!PG_ARGISNULL(4))\n\t\t{\n\t\t\tif (older_newer)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t\t errmsg(\"cannot specify \\\"older_than\\\" or \\\"newer_than\\\" together with \"\n\t\t\t\t\t\t\t\t\"\\\"created_before\\\"\"\n\t\t\t\t\t\t\t\t\"or \\\"created_after\\\"\")));\n\n\t\t\targ_type = get_fn_expr_argtype(fcinfo->flinfo, 4);\n\t\t\t/* We use the existing function for various type/conversion checks */\n\t\t\tcreated_after =\n\t\t\t\tts_time_value_from_arg(PG_GETARG_DATUM(4), arg_type, TIMESTAMPTZOID, false);\n\t\t\t/* convert into int64 format for comparisons */\n\t\t\tcreated_after = ts_internal_to_time_int64(created_after, TIMESTAMPTZOID);\n\t\t\tbefore_after = true;\n\t\t}\n\n\t\t/* if both have not been specified then default to older_newer */\n\t\tif (!older_newer && !before_after)\n\t\t\tolder_newer = true;\n\n\t\tfuncctx = SRF_FIRSTCALL_INIT();\n\t\t/*\n\t\t * For INTEGER type dimensions, we support querying using intervals or any\n\t\t * timestamp or date input. For such INTEGER dimensions, we get the chunks\n\t\t * using their creation time values.\n\t\t */\n\t\tif (IS_INTEGER_TYPE(time_type) && (arg_type == INTERVALOID || IS_TIMESTAMP_TYPE(arg_type)))\n\t\t{\n\t\t\t/* check that we use proper inputs for such cases */\n\t\t\tif (older_newer)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t\t errmsg(\"cannot specify \\\"older_than\\\" and/or \\\"newer_than\\\" for \"\n\t\t\t\t\t\t\t\t\"\\\"integer\\\"-like partitioning types\"),\n\t\t\t\t\t\t errhint(\n\t\t\t\t\t\t\t \"Use \\\"created_before\\\" and/or \\\"created_after\\\" which rely on the \"\n\t\t\t\t\t\t\t \"chunk creation time values.\")));\n\t\t\tfuncctx->user_fctx = get_chunks_in_creation_time_range(ht,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   created_before,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   created_after,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   funcctx->multi_call_memory_ctx,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   &funcctx->max_calls,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   NULL);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* check that we use proper inputs for such cases */\n\t\t\tif (!older_newer)\n\t\t\t{\n\t\t\t\tfuncctx->user_fctx =\n\t\t\t\t\tget_chunks_in_creation_time_range(ht,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  created_before,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  created_after,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  funcctx->multi_call_memory_ctx,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  &funcctx->max_calls,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  NULL);\n\t\t\t}\n\t\t\telse\n\t\t\t\tfuncctx->user_fctx = get_chunks_in_time_range(ht,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  older_than,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  newer_than,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  funcctx->multi_call_memory_ctx,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  &funcctx->max_calls,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  NULL);\n\t\t}\n\t\tts_cache_release(&hcache);\n\t}\n\n\treturn show_chunks_return_srf(fcinfo);\n}\n\nstatic Chunk *\nget_chunks_in_time_range(Hypertable *ht, int64 older_than, int64 newer_than, MemoryContext mctx,\n\t\t\t\t\t\t uint64 *num_chunks_returned, ScanTupLock *tuplock)\n{\n\tMemoryContext oldcontext;\n\tChunkScanCtx chunk_scan_ctx;\n\tChunk *chunks;\n\tChunkScanCtxAddChunkData data;\n\tconst Dimension *time_dim;\n\tStrategyNumber start_strategy;\n\tStrategyNumber end_strategy;\n\tuint64 num_chunks = 0;\n\n\tif (older_than <= newer_than)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid time range\"),\n\t\t\t\t errhint(\"The start of the time range must be before the end.\")));\n\n\tif (TS_HYPERTABLE_IS_INTERNAL_COMPRESSION_TABLE(ht))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_TS_OPERATION_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"invalid operation on compressed hypertable\")));\n\n\tstart_strategy = (newer_than == PG_INT64_MIN) ? InvalidStrategy : BTGreaterEqualStrategyNumber;\n\tend_strategy = (older_than == PG_INT64_MAX) ? InvalidStrategy : BTLessStrategyNumber;\n\ttime_dim = hyperspace_get_open_dimension(ht->space, 0);\n\n\tif (time_dim == NULL)\n\t\ttime_dim = hyperspace_get_closed_dimension(ht->space, 0);\n\n\tEnsure(time_dim != NULL,\n\t\t   \"partitioning dimension not found for hypertable \\\"%s\\\".\\\"%s\\\"\",\n\t\t   NameStr(ht->fd.schema_name),\n\t\t   NameStr(ht->fd.table_name));\n\n\toldcontext = MemoryContextSwitchTo(mctx);\n\tchunks_find_all_in_range_limit(ht,\n\t\t\t\t\t\t\t\t   time_dim,\n\t\t\t\t\t\t\t\t   start_strategy,\n\t\t\t\t\t\t\t\t   newer_than,\n\t\t\t\t\t\t\t\t   end_strategy,\n\t\t\t\t\t\t\t\t   older_than,\n\t\t\t\t\t\t\t\t   -1,\n\t\t\t\t\t\t\t\t   &num_chunks,\n\t\t\t\t\t\t\t\t   tuplock,\n\t\t\t\t\t\t\t\t   &chunk_scan_ctx);\n\tMemoryContextSwitchTo(oldcontext);\n\n\tchunks = MemoryContextAllocZero(mctx, sizeof(Chunk) * num_chunks);\n\tdata = (ChunkScanCtxAddChunkData){\n\t\t.chunks = chunks,\n\t\t.max_chunks = num_chunks,\n\t\t.num_chunks = 0,\n\t};\n\n\t/* Get all the chunks from the context */\n\tchunk_scan_ctx.data = &data;\n\tchunk_scan_ctx_foreach_chunk_stub(&chunk_scan_ctx, chunk_scan_context_add_chunk, 0);\n\t/*\n\t * only affects ctx.htab Got all the chunk already so can now safely\n\t * destroy the context\n\t */\n\tchunk_scan_ctx_destroy(&chunk_scan_ctx);\n\n\t*num_chunks_returned = data.num_chunks;\n\tqsort(chunks, *num_chunks_returned, sizeof(Chunk), chunk_cmp);\n\n#ifdef USE_ASSERT_CHECKING\n\tdo\n\t{\n\t\tuint64 i = 0;\n\t\t/* Assert that we never return dropped chunks */\n\t\tfor (i = 0; i < *num_chunks_returned; i++)\n\t\t\tASSERT_IS_VALID_CHUNK(&chunks[i]);\n\t} while (false);\n#endif\n\n\treturn chunks;\n}\n\nChunk *\nts_chunk_copy(const Chunk *chunk)\n{\n\tChunk *copy;\n\n\tASSERT_IS_VALID_CHUNK(chunk);\n\tcopy = palloc(sizeof(Chunk));\n\tmemcpy(copy, chunk, sizeof(Chunk));\n\n\tif (NULL != chunk->constraints)\n\t\tcopy->constraints = ts_chunk_constraints_copy(chunk->constraints);\n\n\tif (NULL != chunk->cube)\n\t\tcopy->cube = ts_hypercube_copy(chunk->cube);\n\n\treturn copy;\n}\n\nstatic int\nchunk_scan_internal(int indexid, ScanKeyData scankey[], int nkeys, tuple_found_func tuple_found,\n\t\t\t\t\tvoid *data, int limit, ScanDirection scandir, LOCKMODE lockmode,\n\t\t\t\t\tMemoryContext mctx)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tScannerCtx ctx = {\n\t\t.table = catalog_get_table_id(catalog, CHUNK),\n\t\t.index = catalog_get_index(catalog, CHUNK, indexid),\n\t\t.nkeys = nkeys,\n\t\t.data = data,\n\t\t.scankey = scankey,\n\t\t.tuple_found = tuple_found,\n\t\t.limit = limit,\n\t\t.lockmode = lockmode,\n\t\t.scandirection = scandir,\n\t\t.result_mctx = mctx,\n\t};\n\n\treturn ts_scanner_scan(&ctx);\n}\n\n/*\n * Get a window of chunks that \"precedes\" the given dimensional point.\n *\n * For instance, if the dimension is \"time\", then given a point in time the\n * function returns the recent chunks that come before the chunk that includes\n * that point. The count parameter determines the number or slices the window\n * should include in the given dimension. Note, that with multi-dimensional\n * partitioning, there might be multiple chunks in each dimensional slice that\n * all precede the given point. For instance, the example below shows two\n * different situations that each go \"back\" two slices (count = 2) in the\n * x-dimension, but returns two vs. eight chunks due to different\n * partitioning.\n *\n * '_____________\n * '|   |   | * |\n * '|___|___|___|\n * '\n * '\n * '____ ________\n * '|   |   | * |\n * '|___|___|___|\n * '|   |   |   |\n * '|___|___|___|\n * '|   |   |   |\n * '|___|___|___|\n * '|   |   |   |\n * '|___|___|___|\n *\n * Note that the returned chunks will be allocated on the given memory\n * context, including the list itself. So, beware of not leaking the list if\n * the chunks are later cached somewhere else.\n */\nList *\nts_chunk_get_window(int32 dimension_id, int64 point, int count, MemoryContext mctx)\n{\n\tList *chunks = NIL;\n\tDimensionVec *dimvec;\n\tint i;\n\n\t/* Scan for \"count\" slices that precede the point in the given dimension */\n\tdimvec = ts_dimension_slice_scan_by_dimension_before_point(dimension_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   point,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   count,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   BackwardScanDirection,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   mctx);\n\n\t/*\n\t * For each slice, join with any constraints that reference the slice.\n\t * There might be multiple constraints for each slice in case of\n\t * multi-dimensional partitioning.\n\t */\n\tfor (i = 0; i < dimvec->num_slices; i++)\n\t{\n\t\tDimensionSlice *slice = dimvec->slices[i];\n\t\tChunkConstraints *ccs = ts_chunk_constraints_alloc(1, mctx);\n\t\tint j;\n\n\t\tts_chunk_constraint_scan_by_dimension_slice_id(slice->fd.id, ccs, mctx);\n\n\t\t/* For each constraint, find the corresponding chunk */\n\t\tfor (j = 0; j < ccs->num_constraints; j++)\n\t\t{\n\t\t\tChunkConstraint *cc = &ccs->constraints[j];\n\t\t\tChunk *chunk = ts_chunk_get_by_id(cc->fd.chunk_id, false);\n\t\t\tMemoryContext old;\n\t\t\tScanIterator it;\n\n\t\t\t/* Dropped chunks do not contain valid data and must not be returned */\n\t\t\tif (!chunk)\n\t\t\t\tcontinue;\n\t\t\tchunk->constraints = ts_chunk_constraint_scan_by_chunk_id(chunk->fd.id, 1, mctx);\n\n\t\t\tit = ts_dimension_slice_scan_iterator_create(NULL, mctx);\n\t\t\tchunk->cube = ts_hypercube_from_constraints(chunk->constraints, &it);\n\t\t\tts_scan_iterator_close(&it);\n\n\t\t\t/* Allocate the list on the same memory context as the chunks */\n\t\t\told = MemoryContextSwitchTo(mctx);\n\t\t\tchunks = lappend(chunks, chunk);\n\t\t\tMemoryContextSwitchTo(old);\n\t\t}\n\t}\n\n#ifdef USE_ASSERT_CHECKING\n\t/* Assert that we never return dropped chunks */\n\tdo\n\t{\n\t\tListCell *lc;\n\n\t\tforeach (lc, chunks)\n\t\t{\n\t\t\tChunk *chunk = lfirst(lc);\n\t\t\tASSERT_IS_VALID_CHUNK(chunk);\n\t\t}\n\t} while (false);\n#endif\n\n\treturn chunks;\n}\n\nstatic Chunk *\nchunk_scan_find(int indexid, ScanKeyData scankey[], int nkeys, MemoryContext mctx,\n\t\t\t\tLOCKMODE chunk_lockmode, const ScanTupLock *slice_lock, bool fail_if_not_found,\n\t\t\t\tconst DisplayKeyData displaykey[])\n{\n\tChunkStubScanCtx stubctx = {\n\t\t.slice_lock = slice_lock,\n\t\t.chunk_lockmode = chunk_lockmode,\n\t};\n\tChunk *chunk;\n\tint num_found;\n\n\tnum_found = chunk_scan_internal(indexid,\n\t\t\t\t\t\t\t\t\tscankey,\n\t\t\t\t\t\t\t\t\tnkeys,\n\t\t\t\t\t\t\t\t\tchunk_tuple_found,\n\t\t\t\t\t\t\t\t\t&stubctx,\n\t\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t\tForwardScanDirection,\n\t\t\t\t\t\t\t\t\tAccessShareLock,\n\t\t\t\t\t\t\t\t\tmctx);\n\n\tAssert(num_found == 0 || num_found == 1);\n\tchunk = stubctx.chunk;\n\n\tswitch (num_found)\n\t{\n\t\tcase 0:\n\t\t\tif (fail_if_not_found)\n\t\t\t{\n\t\t\t\tint i = 0;\n\t\t\t\tStringInfoData info;\n\t\t\t\tinitStringInfo(&info);\n\t\t\t\twhile (i < nkeys)\n\t\t\t\t{\n\t\t\t\t\tappendStringInfo(&info,\n\t\t\t\t\t\t\t\t\t \"%s: %s\",\n\t\t\t\t\t\t\t\t\t displaykey[i].name,\n\t\t\t\t\t\t\t\t\t displaykey[i].as_string(scankey[i].sk_argument));\n\t\t\t\t\tif (++i < nkeys)\n\t\t\t\t\t\tappendStringInfoString(&info, \", \");\n\t\t\t\t}\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t\t\t errmsg(\"chunk not found\"),\n\t\t\t\t\t\t errdetail(\"%s\", info.data)));\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 1:\n\t\t\tif (chunk)\n\t\t\t{\n\t\t\t\tASSERT_IS_VALID_CHUNK(chunk);\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\telog(ERROR, \"expected a single chunk, found %d\", num_found);\n\t}\n\n\treturn chunk;\n}\n\nChunk *\nts_chunk_get_by_name_with_memory_context(const char *schema_name, const char *table_name,\n\t\t\t\t\t\t\t\t\t\t LOCKMODE chunk_lockmode, const ScanTupLock *slice_lock,\n\t\t\t\t\t\t\t\t\t\t MemoryContext mctx, bool fail_if_not_found)\n{\n\tNameData schema, table;\n\tScanKeyData scankey[2];\n\tstatic const DisplayKeyData displaykey[2] = {\n\t\t[0] = { .name = \"schema_name\", .as_string = DatumGetNameString },\n\t\t[1] = { .name = \"table_name\", .as_string = DatumGetNameString },\n\t};\n\n\t/* Early check for rogue input */\n\tif (schema_name == NULL || table_name == NULL)\n\t{\n\t\tif (fail_if_not_found)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t\t errmsg(\"chunk not found\"),\n\t\t\t\t\t errdetail(\"schema_name: %s, table_name: %s\",\n\t\t\t\t\t\t\t   schema_name ? schema_name : \"<null>\",\n\t\t\t\t\t\t\t   table_name ? table_name : \"<null>\")));\n\t\telse\n\t\t\treturn NULL;\n\t}\n\n\tnamestrcpy(&schema, schema_name);\n\tnamestrcpy(&table, table_name);\n\n\t/*\n\t * Check that the table actually exists and get a lock, unless no lock\n\t * requested.\n\t */\n\tif (chunk_lockmode != NoLock)\n\t{\n\t\tRangeVar *rv = makeRangeVar(NameStr(schema), NameStr(table), -1);\n\t\tRelation rel = table_openrv_extended(rv, chunk_lockmode, true);\n\n\t\tif (!rel)\n\t\t{\n\t\t\tif (fail_if_not_found)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t\t\t errmsg(\"chunk not found\"),\n\t\t\t\t\t\t errdetail(\"schema_name: %s, table_name: %s\", schema_name, table_name)));\n\t\t\treturn NULL;\n\t\t}\n\n\t\ttable_close(rel, NoLock);\n\t}\n\n\t/*\n\t * Perform an index scan on chunk name.\n\t */\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_chunk_schema_name_idx_schema_name,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_NAMEEQ,\n\t\t\t\tNameGetDatum(&schema));\n\tScanKeyInit(&scankey[1],\n\t\t\t\tAnum_chunk_schema_name_idx_table_name,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_NAMEEQ,\n\t\t\t\tNameGetDatum(&table));\n\n\treturn chunk_scan_find(CHUNK_SCHEMA_NAME_INDEX,\n\t\t\t\t\t\t   scankey,\n\t\t\t\t\t\t   2,\n\t\t\t\t\t\t   mctx,\n\t\t\t\t\t\t   chunk_lockmode,\n\t\t\t\t\t\t   slice_lock,\n\t\t\t\t\t\t   fail_if_not_found,\n\t\t\t\t\t\t   displaykey);\n}\n\nChunk *\nts_chunk_get_by_relid_locked(Oid relid, LOCKMODE chunk_lockmode, const ScanTupLock *slice_lock,\n\t\t\t\t\t\t\t bool fail_if_not_found)\n{\n\tchar *schema;\n\tchar *table;\n\n\tif (!OidIsValid(relid))\n\t{\n\t\tif (fail_if_not_found)\n\t\t\tereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg(\"invalid Oid\")));\n\t\telse\n\t\t\treturn NULL;\n\t}\n\n\tschema = get_namespace_name(get_rel_namespace(relid));\n\ttable = get_rel_name(relid);\n\treturn chunk_get_by_name(schema, table, chunk_lockmode, slice_lock, fail_if_not_found);\n}\n\nChunk *\nts_chunk_get_by_relid(Oid relid, bool fail_if_not_found)\n{\n\tScanTupLock slice_lock = {\n\t\t.lockmode = LockTupleKeyShare,\n\t\t.waitpolicy = LockWaitBlock,\n\t\t.lockflags = TUPLE_LOCK_FLAG_FIND_LAST_VERSION,\n\t};\n\treturn ts_chunk_get_by_relid_locked(relid, NoLock, &slice_lock, fail_if_not_found);\n}\n\nvoid\nts_chunk_free(Chunk *chunk)\n{\n\tif (chunk->cube)\n\t{\n\t\tts_hypercube_free(chunk->cube);\n\t}\n\n\tif (chunk->constraints)\n\t{\n\t\tChunkConstraints *c = chunk->constraints;\n\t\tpfree(c->constraints);\n\t\tpfree(c);\n\t}\n\n\tpfree(chunk);\n}\n\nstatic const char *\nDatumGetInt32AsString(Datum datum)\n{\n\tchar *buf = (char *) palloc(12); /* sign, 10 digits, '\\0' */\n\tpg_ltoa(DatumGetInt32(datum), buf);\n\treturn buf;\n}\n\nChunk *\nts_chunk_get_by_id_with_slice_lock(int32 id, LOCKMODE chunk_lockmode, const ScanTupLock *slice_lock,\n\t\t\t\t\t\t\t\t   bool fail_if_not_found)\n{\n\tScanKeyData scankey[1];\n\tstatic const DisplayKeyData displaykey[1] = {\n\t\t[0] = { .name = \"id\", .as_string = DatumGetInt32AsString },\n\t};\n\n\t/*\n\t * Perform an index scan on chunk id.\n\t */\n\tScanKeyInit(&scankey[0], Anum_chunk_idx_id, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(id));\n\n\treturn chunk_scan_find(CHUNK_ID_INDEX,\n\t\t\t\t\t\t   scankey,\n\t\t\t\t\t\t   1,\n\t\t\t\t\t\t   CurrentMemoryContext,\n\t\t\t\t\t\t   chunk_lockmode,\n\t\t\t\t\t\t   slice_lock,\n\t\t\t\t\t\t   fail_if_not_found,\n\t\t\t\t\t\t   displaykey);\n}\n\nChunk *\nts_chunk_get_by_id(int32 id, bool fail_if_not_found)\n{\n\tScanTupLock slice_lock = {\n\t\t.lockmode = LockTupleKeyShare,\n\t\t.waitpolicy = LockWaitBlock,\n\t\t.lockflags = TUPLE_LOCK_FLAG_FIND_LAST_VERSION,\n\t};\n\n\treturn ts_chunk_get_by_id_with_slice_lock(id, NoLock, &slice_lock, fail_if_not_found);\n}\n\n/*\n * Simple scans provide lightweight ways to access chunk information without the\n * overhead of getting a full chunk (i.e., no extra metadata, like constraints,\n * are joined in). This function forms the basis of a number of lookup functions\n * that, e.g., translates a chunk relid to a chunk_id, or vice versa.\n */\nstatic bool\nchunk_simple_scan(ScanIterator *iterator, FormData_chunk *form, bool missing_ok,\n\t\t\t\t  const DisplayKeyData displaykey[])\n{\n\tint count = 0;\n\n\tts_scanner_foreach(iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(iterator);\n\t\tts_chunk_formdata_fill(form, ti);\n\t\tcount++;\n\t}\n\n\tAssert(count == 0 || count == 1);\n\n\tif (count == 0 && !missing_ok)\n\t{\n\t\tint i = 0;\n\t\tStringInfoData info;\n\t\tinitStringInfo(&info);\n\t\twhile (i < iterator->ctx.nkeys)\n\t\t{\n\t\t\tappendStringInfo(&info,\n\t\t\t\t\t\t\t \"%s: %s\",\n\t\t\t\t\t\t\t displaykey[i].name,\n\t\t\t\t\t\t\t displaykey[i].as_string(iterator->ctx.scankey[i].sk_argument));\n\t\t\tif (++i < iterator->ctx.nkeys)\n\t\t\t\tappendStringInfoString(&info, \", \");\n\t\t}\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t errmsg(\"chunk not found\"),\n\t\t\t\t errdetail(\"%s\", info.data)));\n\t}\n\n\treturn count == 1;\n}\n\nstatic bool\nchunk_simple_scan_by_name(const char *schema, const char *table, FormData_chunk *form,\n\t\t\t\t\t\t  bool missing_ok)\n{\n\tScanIterator iterator;\n\tstatic const DisplayKeyData displaykey[] = {\n\t\t[0] = { .name = \"schema_name\", .as_string = DatumGetNameString },\n\t\t[1] = { .name = \"table_name\", .as_string = DatumGetNameString },\n\t};\n\n\tif (schema == NULL || table == NULL)\n\t\treturn false;\n\n\titerator = ts_scan_iterator_create(CHUNK, AccessShareLock, CurrentMemoryContext);\n\tinit_scan_by_qualified_table_name(&iterator, schema, table);\n\n\treturn chunk_simple_scan(&iterator, form, missing_ok, displaykey);\n}\n\nbool\nts_chunk_simple_scan_by_reloid(Oid reloid, FormData_chunk *form, bool missing_ok)\n{\n\tbool found = false;\n\n\tif (OidIsValid(reloid))\n\t{\n\t\tconst char *table = get_rel_name(reloid);\n\n\t\tif (table != NULL)\n\t\t{\n\t\t\tOid nspid = get_rel_namespace(reloid);\n\t\t\tconst char *schema = get_namespace_name(nspid);\n\n\t\t\tfound = chunk_simple_scan_by_name(schema, table, form, missing_ok);\n\t\t}\n\t}\n\n\tif (!found && !missing_ok)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t errmsg(\"chunk with reloid %u not found\", reloid)));\n\n\treturn found;\n}\n\nstatic bool\nchunk_simple_scan_by_id(int32 chunk_id, FormData_chunk *form, bool missing_ok)\n{\n\tScanIterator iterator;\n\tstatic const DisplayKeyData displaykey[] = {\n\t\t[0] = { .name = \"id\", .as_string = DatumGetInt32AsString },\n\t};\n\n\titerator = ts_scan_iterator_create(CHUNK, AccessShareLock, CurrentMemoryContext);\n\tts_chunk_scan_iterator_set_chunk_id(&iterator, chunk_id);\n\n\treturn chunk_simple_scan(&iterator, form, missing_ok, displaykey);\n}\n\n/*\n * Lookup a Chunk ID from a chunk's relid.\n */\nDatum\nts_chunk_id_from_relid(PG_FUNCTION_ARGS)\n{\n\tstatic Oid last_relid = InvalidOid;\n\tstatic int32 last_id = 0;\n\tOid relid = PG_GETARG_OID(0);\n\tFormData_chunk form;\n\n\tif (last_relid == relid)\n\t\treturn last_id;\n\n\tts_chunk_simple_scan_by_reloid(relid, &form, false);\n\n\tlast_relid = relid;\n\tlast_id = form.id;\n\n\tPG_RETURN_INT32(last_id);\n}\n\nbool\nts_chunk_exists_relid(Oid relid)\n{\n\tFormData_chunk form;\n\n\treturn ts_chunk_simple_scan_by_reloid(relid, &form, true);\n}\n\n/*\n * Returns 0 if there is no chunk with such reloid.\n */\nint32\nts_chunk_get_hypertable_id_by_reloid(Oid reloid)\n{\n\tFormData_chunk form;\n\n\tif (ts_chunk_simple_scan_by_reloid(reloid, &form, /* missing_ok = */ true))\n\t{\n\t\treturn form.hypertable_id;\n\t}\n\n\treturn 0;\n}\n\nFormData_chunk\nts_chunk_get_formdata(int32 chunk_id)\n{\n\tFormData_chunk fd;\n\tchunk_simple_scan_by_id(chunk_id, &fd, /* missing_ok = */ false);\n\treturn fd;\n}\n\n/*\n * Get the relid of a chunk given its ID.\n */\nOid\nts_chunk_get_relid(int32 chunk_id, bool missing_ok)\n{\n\tFormData_chunk form = { 0 };\n\tOid relid = InvalidOid;\n\n\tif (chunk_simple_scan_by_id(chunk_id, &form, missing_ok))\n\t\trelid = ts_get_relation_relid(NameStr(form.schema_name), NameStr(form.table_name), true);\n\n\tif (!OidIsValid(relid) && !missing_ok)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_SCHEMA),\n\t\t\t\t errmsg(\"chunk with id %d not found\", chunk_id)));\n\n\treturn relid;\n}\n\n/*\n * Get the schema (namespace) of a chunk given its ID.\n *\n * This is a lightweight way to get the schema of a chunk without creating a\n * full Chunk object that joins in constraints, etc.\n */\nOid\nts_chunk_get_schema_id(int32 chunk_id, bool missing_ok)\n{\n\tFormData_chunk form = { 0 };\n\n\tif (!chunk_simple_scan_by_id(chunk_id, &form, missing_ok))\n\t\treturn InvalidOid;\n\n\treturn get_namespace_oid(NameStr(form.schema_name), missing_ok);\n}\n\nbool\nts_chunk_get_id(const char *schema, const char *table, int32 *chunk_id, bool missing_ok)\n{\n\tFormData_chunk form = { 0 };\n\n\tif (!chunk_simple_scan_by_name(schema, table, &form, missing_ok))\n\t\treturn false;\n\n\tif (NULL != chunk_id)\n\t\t*chunk_id = form.id;\n\n\treturn true;\n}\n\n/* Delete the chunk tuple.\n *\n * relid: Required when deleting via an event trigger hook, because at that\n * point the relation is gone and it is no longer possible to resolve the Oid\n * from the PG catalog.\n *\n */\nstatic void\nchunk_tuple_delete(TupleInfo *ti, Oid relid, DropBehavior behavior, bool detach)\n{\n\tFormData_chunk form;\n\tCatalogSecurityContext sec_ctx;\n\tint i;\n\n\tts_chunk_formdata_fill(&form, ti);\n\n\tChunkConstraints *ccs;\n\n\t/*\n\t * Do not drop any constraint if detaching\n\t * We will still need to delete dimension slices for the chunk\n\t */\n\tccs = ts_chunk_constraints_alloc(2, ti->mctx);\n\tts_chunk_constraint_delete_dimensional_constraints(form.id, ccs);\n\tts_chunk_constraint_delete_by_chunk_id(form.id, ccs, !detach);\n\n\t/* Check for dimension slices that are orphaned by the chunk deletion */\n\tfor (i = 0; i < ccs->num_constraints; i++)\n\t{\n\t\tChunkConstraint *cc = &ccs->constraints[i];\n\n\t\t/*\n\t\t * Delete the dimension slice if there are no remaining constraints\n\t\t * referencing it\n\t\t */\n\t\tif (is_dimension_constraint(cc))\n\t\t{\n\t\t\t/*\n\t\t\t * Dimension slices are shared between chunk constraints and\n\t\t\t * subsequently between chunks as well. Since different chunks\n\t\t\t * can reference the same dimension slice (through the chunk\n\t\t\t * constraint), we must lock the dimension slice in FOR UPDATE\n\t\t\t * mode *prior* to scanning the chunk constraints table. If we\n\t\t\t * do not do that, we can have the following scenario:\n\t\t\t *\n\t\t\t * - T1: Prepares to create a chunk that uses an existing dimension slice X\n\t\t\t * - T2: Deletes a chunk and dimension slice X because it is not\n\t\t\t *   references by a chunk constraint.\n\t\t\t * - T1: Adds a chunk constraint referencing dimension\n\t\t\t *   slice X (which is about to be deleted by T2).\n\t\t\t */\n\t\t\tScanTupLock tuplock = { .lockmode = LockTupleExclusive, .waitpolicy = LockWaitBlock };\n\t\t\tDimensionSlice *slice =\n\t\t\t\tts_dimension_slice_scan_by_id_and_lock(cc->fd.dimension_slice_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   &tuplock,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   CurrentMemoryContext,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   AccessShareLock);\n\t\t\t/* If the slice is not found in the scan above, the table is\n\t\t\t * broken so we do not delete the slice. We proceed\n\t\t\t * anyway since users need to be able to drop broken tables or\n\t\t\t * remove broken chunks. */\n\t\t\tif (!slice)\n\t\t\t{\n\t\t\t\tconst Hypertable *const ht = ts_hypertable_get_by_id(form.hypertable_id);\n\t\t\t\tereport(WARNING,\n\t\t\t\t\t\t(errmsg(\"unexpected state for chunk %s.%s, dropping anyway\",\n\t\t\t\t\t\t\t\tquote_identifier(NameStr(form.schema_name)),\n\t\t\t\t\t\t\t\tquote_identifier(NameStr(form.table_name))),\n\t\t\t\t\t\t errdetail(\"The integrity of hypertable %s.%s might be \"\n\t\t\t\t\t\t\t\t   \"compromised \"\n\t\t\t\t\t\t\t\t   \"since one of its chunks lacked a dimension slice.\",\n\t\t\t\t\t\t\t\t   quote_identifier(NameStr(ht->fd.schema_name)),\n\t\t\t\t\t\t\t\t   quote_identifier(NameStr(ht->fd.table_name)))));\n\t\t\t}\n\t\t\telse if (ts_chunk_constraint_scan_by_dimension_slice_id(slice->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tCurrentMemoryContext) == 0)\n\t\t\t\tts_dimension_slice_delete_by_id(cc->fd.dimension_slice_id, false);\n\t\t}\n\t}\n\n\t/*\n\t * Even tough we keep foreign key constraints on the chunk, we still\n\t * need to drop the referencing foreign keys since such keys are possibly\n\t * intended to reference the hypertable, not the chunk.\n\t */\n\tif (detach)\n\t\tts_chunk_drop_referencing_fk_by_chunk_id(form.id);\n\tts_compression_chunk_size_delete(form.id);\n\n\t/* Delete any row in bgw_policy_chunk-stats corresponding to this chunk */\n\tts_bgw_policy_chunk_stats_delete_by_chunk_id(form.id);\n\n\t/* Delete any rows in _timescaledb_catalog.chunk_column_stats corresponding to this chunk */\n\tts_chunk_column_stats_delete_by_chunk_id(form.id);\n\n\tif (!OidIsValid(relid))\n\t{\n\t\t/*\n\t\t * If the chunk is deleted as a result of deleting the Hypertable, and\n\t\t * it is cleaned up in the DROP eventtrigger hook, it might not be\n\t\t * possible to resolve the relid because the relation is already gone\n\t\t * in pg_catalog. But that's OK, because compression settings will be\n\t\t * cleaned up when processing the eventtrigger.\n\t\t */\n\t\trelid = ts_get_relation_relid(NameStr(form.schema_name), NameStr(form.table_name), true);\n\t}\n\n\t/*\n\t * Cleanup dependent catalogs.\n\t */\n\tif (OidIsValid(relid))\n\t{\n\t\tts_chunk_rewrite_delete(relid, false);\n\t}\n\n\tif (form.compressed_chunk_id != INVALID_CHUNK_ID)\n\t{\n\t\tChunk *compressed_chunk = ts_chunk_get_by_id(form.compressed_chunk_id, false);\n\n\t\tif (OidIsValid(relid))\n\t\t\tts_compression_settings_delete(relid);\n\n\t\t/* The chunk may have been deleted by a CASCADE */\n\t\tif (compressed_chunk != NULL)\n\t\t{\n\t\t\t/* Plain drop without preserving catalog row because this is the compressed\n\t\t\t * chunk */\n\t\t\tts_chunk_drop(compressed_chunk, behavior, DEBUG1);\n\t\t}\n\t}\n\telse if (OidIsValid(relid))\n\t{\n\t\t/* If there is no compressed chunk ID, this might be the actual\n\t\t * compressed chunk */\n\t\tts_compression_settings_delete_by_compress_relid(relid);\n\t}\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_delete_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\tts_catalog_restore_user(&sec_ctx);\n}\n\nstatic void\ninit_scan_by_qualified_table_name(ScanIterator *iterator, const char *schema_name,\n\t\t\t\t\t\t\t\t  const char *table_name)\n{\n\titerator->ctx.index = catalog_get_index(ts_catalog_get(), CHUNK, CHUNK_SCHEMA_NAME_INDEX);\n\tts_scan_iterator_scan_key_init(iterator,\n\t\t\t\t\t\t\t\t   Anum_chunk_schema_name_idx_schema_name,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_NAMEEQ,\n\t\t\t\t\t\t\t\t   CStringGetDatum(schema_name));\n\tts_scan_iterator_scan_key_init(iterator,\n\t\t\t\t\t\t\t\t   Anum_chunk_schema_name_idx_table_name,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_NAMEEQ,\n\t\t\t\t\t\t\t\t   CStringGetDatum(table_name));\n}\n\nstatic int\nchunk_delete(ScanIterator *iterator, Oid relid, DropBehavior behavior, bool detach)\n{\n\tint count = 0;\n\n\tts_scanner_foreach(iterator)\n\t{\n\t\tchunk_tuple_delete(ts_scan_iterator_tuple_info(iterator), relid, behavior, detach);\n\t\tcount++;\n\t}\n\n\treturn count;\n}\n\nstatic int\nts_chunk_delete_by_name_internal(const char *schema, const char *table, Oid relid,\n\t\t\t\t\t\t\t\t DropBehavior behavior)\n{\n\tScanIterator iterator = ts_scan_iterator_create(CHUNK, RowExclusiveLock, CurrentMemoryContext);\n\tint count;\n\n\tinit_scan_by_qualified_table_name(&iterator, schema, table);\n\tcount = chunk_delete(&iterator, relid, behavior, false);\n\n\t/* (schema,table) names and (hypertable_id) are unique so should only have\n\t * dropped one chunk or none (if not found) */\n\tAssert(count == 1 || count == 0);\n\n\treturn count;\n}\n\nint\nts_chunk_delete_by_name(const char *schema, const char *table, DropBehavior behavior)\n{\n\tOid relid = ts_get_relation_relid(schema, table, false);\n\treturn ts_chunk_delete_by_name_internal(schema, table, relid, behavior);\n}\n\nint\nts_chunk_delete_by_relid_and_relname(Oid relid, const char *schemaname, const char *tablename,\n\t\t\t\t\t\t\t\t\t DropBehavior behavior)\n{\n\tif (!OidIsValid(relid))\n\t\treturn 0;\n\n\treturn ts_chunk_delete_by_name_internal(schemaname, tablename, relid, behavior);\n}\n\nstatic void\ninit_scan_by_hypertable_id(ScanIterator *iterator, int32 hypertable_id)\n{\n\titerator->ctx.index = catalog_get_index(ts_catalog_get(), CHUNK, CHUNK_HYPERTABLE_ID_INDEX);\n\tts_scan_iterator_scan_key_init(iterator,\n\t\t\t\t\t\t\t\t   Anum_chunk_hypertable_id_idx_hypertable_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(hypertable_id));\n}\n\nint\nts_chunk_delete_by_hypertable_id(int32 hypertable_id)\n{\n\tScanIterator iterator = ts_scan_iterator_create(CHUNK, RowExclusiveLock, CurrentMemoryContext);\n\n\tinit_scan_by_hypertable_id(&iterator, hypertable_id);\n\n\treturn chunk_delete(&iterator, InvalidOid, DROP_RESTRICT, false);\n}\n\nbool\nts_chunk_exists_with_compression(int32 hypertable_id)\n{\n\tScanIterator iterator = ts_scan_iterator_create(CHUNK, AccessShareLock, CurrentMemoryContext);\n\tbool found = false;\n\n\tinit_scan_by_hypertable_id(&iterator, hypertable_id);\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tbool isnull_chunk_id =\n\t\t\tslot_attisnull(ts_scan_iterator_slot(&iterator), Anum_chunk_compressed_chunk_id);\n\n\t\tif (!isnull_chunk_id)\n\t\t{\n\t\t\tfound = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\tts_scan_iterator_close(&iterator);\n\treturn found;\n}\n\nstatic void\ninit_scan_by_compressed_chunk_id(ScanIterator *iterator, int32 compressed_chunk_id)\n{\n\titerator->ctx.index =\n\t\tcatalog_get_index(ts_catalog_get(), CHUNK, CHUNK_COMPRESSED_CHUNK_ID_INDEX);\n\tts_scan_iterator_scan_key_init(iterator,\n\t\t\t\t\t\t\t\t   Anum_chunk_compressed_chunk_id_idx_compressed_chunk_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(compressed_chunk_id));\n}\n\nChunk *\nts_chunk_get_compressed_chunk_parent(const Chunk *chunk)\n{\n\tScanIterator iterator = ts_scan_iterator_create(CHUNK, AccessShareLock, CurrentMemoryContext);\n\tOid parent_id = InvalidOid;\n\n\tinit_scan_by_compressed_chunk_id(&iterator, chunk->fd.id);\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tDatum datum;\n\t\tbool isnull;\n\n\t\tAssert(!OidIsValid(parent_id));\n\t\tdatum = slot_getattr(ti->slot, Anum_chunk_id, &isnull);\n\n\t\tif (!isnull)\n\t\t\tparent_id = DatumGetObjectId(datum);\n\t}\n\n\tif (OidIsValid(parent_id))\n\t\treturn ts_chunk_get_by_id(parent_id, true);\n\n\treturn NULL;\n}\n\nbool\nts_chunk_contains_compressed_data(const Chunk *chunk)\n{\n\tChunk *parent_chunk = ts_chunk_get_compressed_chunk_parent(chunk);\n\n\treturn parent_chunk != NULL;\n}\n\nList *\nts_chunk_get_chunk_ids_by_hypertable_id(int32 hypertable_id)\n{\n\tList *chunkids = NIL;\n\tScanIterator iterator = ts_scan_iterator_create(CHUNK, AccessShareLock, CurrentMemoryContext);\n\n\tinit_scan_by_hypertable_id(&iterator, hypertable_id);\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tbool isnull;\n\t\tDatum id = slot_getattr(ts_scan_iterator_slot(&iterator), Anum_chunk_id, &isnull);\n\t\tif (!isnull)\n\t\t\tchunkids = lappend_int(chunkids, DatumGetInt32(id));\n\t}\n\n\treturn chunkids;\n}\n\n/* Return list of chunks that belong to the given hypertable.\n *\n * The returned chunk objects will not have any constraints or dimension\n * information filled in.\n */\nList *\nts_chunk_get_by_hypertable_id(int32 hypertable_id)\n{\n\tList *chunks = NIL;\n\tOid hypertable_relid = ts_hypertable_id_to_relid(hypertable_id, false);\n\n\tScanIterator iterator = ts_scan_iterator_create(CHUNK, RowExclusiveLock, CurrentMemoryContext);\n\n\tinit_scan_by_hypertable_id(&iterator, hypertable_id);\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tChunk *chunk = palloc0(sizeof(Chunk));\n\t\tts_chunk_formdata_fill(&chunk->fd, ti);\n\n\t\tchunk->hypertable_relid = hypertable_relid;\n\n\t\tchunk->table_id = ts_get_relation_relid(NameStr(chunk->fd.schema_name),\n\t\t\t\t\t\t\t\t\t\t\t\tNameStr(chunk->fd.table_name),\n\t\t\t\t\t\t\t\t\t\t\t\tfalse);\n\n\t\tchunks = lappend(chunks, chunk);\n\t}\n\n\treturn chunks;\n}\n\nstatic ChunkResult\nchunk_recreate_constraint(ChunkScanCtx *ctx, ChunkStub *stub)\n{\n\tChunkStubScanCtx stubctx = {\n\t\t.stub = stub,\n\t};\n\tChunk *chunk = chunk_create_from_stub(&stubctx);\n\n\tts_chunk_constraints_recreate(ctx->ht, chunk);\n\n\treturn CHUNK_PROCESSED;\n}\n\nvoid\nts_chunk_recreate_all_constraints_for_dimension(Hypertable *ht, int32 dimension_id)\n{\n\tDimensionVec *slices;\n\tChunkScanCtx chunkctx;\n\tint i;\n\n\tslices = ts_dimension_slice_scan_by_dimension(dimension_id, 0);\n\n\tif (NULL == slices)\n\t\treturn;\n\n\tchunk_scan_ctx_init(&chunkctx, ht, NULL);\n\n\tfor (i = 0; i < slices->num_slices; i++)\n\t\tts_chunk_constraint_scan_by_dimension_slice(slices->slices[i],\n\t\t\t\t\t\t\t\t\t\t\t\t\t&chunkctx,\n\t\t\t\t\t\t\t\t\t\t\t\t\tCurrentMemoryContext);\n\n\tchunk_scan_ctx_foreach_chunk_stub(&chunkctx, chunk_recreate_constraint, 0);\n\tchunk_scan_ctx_destroy(&chunkctx);\n}\n\n/*\n * Chunk catalog updates are done in three steps.\n * This is achieved by following this sequence:\n *   1: call lock_chunk_tuple: this finds most recent version of tuple,\n *   locks it, fills TID and data\n *   2: make changes to the data\n *   3: call chunk_update_catalog_tuple with the TID and updated data\n *\n * This is equivalent to SELECT for UPDATE, followed by UPDATE\n *\n * All callers who want to update chunk tuples should respect this so that locks\n * are acquired correctly.\n *\n */\nstatic void\nchunk_update_catalog_tuple(ItemPointer tid, FormData_chunk *update)\n{\n\tHeapTuple new_tuple;\n\tCatalogSecurityContext sec_ctx;\n\tCatalog *catalog = ts_catalog_get();\n\tOid table = catalog_get_table_id(catalog, CHUNK);\n\tRelation chunk_rel = relation_open(table, RowExclusiveLock);\n\n\tnew_tuple = chunk_formdata_make_tuple(update, chunk_rel->rd_att);\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_update_tid(chunk_rel, tid, new_tuple);\n\tts_catalog_restore_user(&sec_ctx);\n\theap_freetuple(new_tuple);\n\trelation_close(chunk_rel, NoLock);\n}\n/*\n * This function locks the  timescaledb_catalog.chunk tuple (corresponding to chunk_id ) in\n * LockTupleExclusiveMode. It blocks till the lock is acquired. The tid and data (corresponding to\n * the locked tuple) are returned via tid and form arguments. Anyone updating/deleting a chunk entry\n * from the catalog table is expected to first call this function. Refer to\n * chunk_update_catalog_tuple() for more details.\n */\nstatic bool\nlock_chunk_tuple(int32 chunk_id, ItemPointer tid, FormData_chunk *form)\n{\n\tScanTupLock scantuplock = {\n\t\t.waitpolicy = LockWaitBlock,\n\t\t.lockmode = LockTupleExclusive,\n\t};\n\tScanIterator iterator = ts_scan_iterator_create(CHUNK, RowShareLock, CurrentMemoryContext);\n\titerator.ctx.index = catalog_get_index(ts_catalog_get(), CHUNK, CHUNK_ID_INDEX);\n\titerator.ctx.tuplock = &scantuplock;\n\t/* Keeping the lock since we presumably want to update the tuple */\n\titerator.ctx.flags = SCANNER_F_KEEPLOCK;\n\n\t/* see table_tuple_lock for details about flags that are set in TupleExclusive mode */\n\tscantuplock.lockflags = TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS;\n\tif (!IsolationUsesXactSnapshot())\n\t{\n\t\t/* in read committed mode, we follow all updates to this tuple */\n\t\tscantuplock.lockflags |= TUPLE_LOCK_FLAG_FIND_LAST_VERSION;\n\t}\n\n\tts_scan_iterator_scan_key_init(&iterator,\n\t\t\t\t\t\t\t\t   Anum_chunk_idx_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(chunk_id));\n\tbool success = false;\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\n\t\tif (ti->lockresult != TM_Ok)\n\t\t{\n\t\t\tif (IsolationUsesXactSnapshot())\n\t\t\t{\n\t\t\t\t/* For Repeatable Read and Serializable isolation level report error\n\t\t\t\t * if we cannot lock the tuple\n\t\t\t\t */\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),\n\t\t\t\t\t\t errmsg(\"could not serialize access due to concurrent update\")));\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t\t\t errmsg(\"unable to lock chunk catalog tuple, lock result is %d for chunk \"\n\t\t\t\t\t\t\t\t\"ID (%d)\",\n\t\t\t\t\t\t\t\tti->lockresult,\n\t\t\t\t\t\t\t\tchunk_id)));\n\t\t\t}\n\t\t}\n\n\t\tts_chunk_formdata_fill(form, ti);\n\t\tItemPointer result_tid = ts_scanner_get_tuple_tid(ti);\n\t\ttid->ip_blkid = result_tid->ip_blkid;\n\t\ttid->ip_posid = result_tid->ip_posid;\n\t\tsuccess = true;\n\t\tbreak;\n\t}\n\tts_scan_iterator_close(&iterator);\n\n\treturn success;\n}\n\nbool\nts_chunk_set_name(Chunk *chunk, const char *newname)\n{\n\tFormData_chunk form;\n\tItemPointerData tid;\n\tbool PG_USED_FOR_ASSERTS_ONLY found;\n\n\tfound = lock_chunk_tuple(chunk->fd.id, &tid, &form);\n\tAssert(found);\n\n\tnamestrcpy(&form.table_name, newname);\n\n\tchunk_update_catalog_tuple(&tid, &form);\n\treturn true;\n}\n\nbool\nts_chunk_set_schema(Chunk *chunk, const char *newschema)\n{\n\tFormData_chunk form;\n\tItemPointerData tid;\n\tbool PG_USED_FOR_ASSERTS_ONLY found;\n\n\tfound = lock_chunk_tuple(chunk->fd.id, &tid, &form);\n\tAssert(found);\n\n\tnamestrcpy(&form.schema_name, newschema);\n\n\tchunk_update_catalog_tuple(&tid, &form);\n\treturn true;\n}\n\nbool\nts_chunk_set_unordered(Chunk *chunk)\n{\n\tAssert(ts_chunk_is_compressed(chunk));\n\treturn ts_chunk_add_status(chunk, CHUNK_STATUS_COMPRESSED_UNORDERED);\n}\n\nbool\nts_chunk_set_partial(Chunk *chunk)\n{\n\tbool set_status;\n\n\tAssert(ts_chunk_is_compressed(chunk));\n\tset_status = ts_chunk_add_status(chunk, CHUNK_STATUS_COMPRESSED_PARTIAL);\n\n\tif (set_status)\n\t{\n\t\t/*\n\t\t * If the status was set then convert the corresponding\n\t\t * _timescaledb_catalog.chunk_column_stats entries \"INVALID\".\n\t\t */\n\t\tts_chunk_column_stats_set_invalid(chunk->fd.hypertable_id, chunk->fd.id);\n\n\t\t/* changed chunk status, so invalidate plans involving this chunk */\n\t\tCacheInvalidateRelcacheByRelid(chunk->table_id);\n\t}\n\treturn set_status;\n}\n\n/* No inserts, updates, and deletes are permitted on a frozen chunk.\n * Compression policies etc do not run on a frozen chunk.\n * Only valid operation is dropping the chunk\n */\nbool\nts_chunk_set_frozen(Chunk *chunk)\n{\n\treturn ts_chunk_add_status(chunk, CHUNK_STATUS_FROZEN);\n}\n\nbool\nts_chunk_unset_frozen(Chunk *chunk)\n{\n\treturn ts_chunk_clear_status(chunk, CHUNK_STATUS_FROZEN);\n}\n\nbool\nts_chunk_is_frozen(const Chunk *chunk)\n{\n\treturn ts_flags_are_set_32(chunk->fd.status, CHUNK_STATUS_FROZEN);\n}\n\n/* only caller used to be ts_chunk_unset_frozen. This code was in PG14 block as we run into\n * a \"defined but unset\" error in CI/CD builds for PG < 14. But now called from recompress as well\n */\nbool\nts_chunk_clear_status(Chunk *chunk, int32 status)\n{\n\t/* only frozen status can be cleared for a frozen chunk */\n\tif (status != CHUNK_STATUS_FROZEN && ts_flags_are_set_32(chunk->fd.status, CHUNK_STATUS_FROZEN))\n\t{\n\t\t/* chunk in frozen state cannot be modified */\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_TS_OPERATION_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot modify frozen chunk status\"),\n\t\t\t\t errdetail(\"chunk id = %d attempt to clear status %d , current status %x \",\n\t\t\t\t\t\t   chunk->fd.id,\n\t\t\t\t\t\t   status,\n\t\t\t\t\t\t   chunk->fd.status)));\n\t}\n\n\tFormData_chunk form;\n\tItemPointerData tid;\n\tbool PG_USED_FOR_ASSERTS_ONLY found;\n\n\tfound = lock_chunk_tuple(chunk->fd.id, &tid, &form);\n\tAssert(found);\n\n\t/* applying the flags after locking the metadata tuple */\n\tint32 old_status = form.status;\n\tform.status = ts_clear_flags_32(form.status, status);\n\tchunk->fd.status = form.status;\n\n\t/* Row-level locks are released at transaction end or during savepoint rollback */\n\tif (old_status != form.status)\n\t\tchunk_update_catalog_tuple(&tid, &form);\n\n\treturn true;\n}\n\nstatic bool\nts_chunk_add_status(Chunk *chunk, int32 status)\n{\n\tbool status_set = false;\n\tif (ts_flags_are_set_32(chunk->fd.status, CHUNK_STATUS_FROZEN))\n\t{\n\t\t/* chunk in frozen state cannot be modified */\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_TS_OPERATION_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot modify frozen chunk status\"),\n\t\t\t\t errdetail(\"chunk id = %d attempt to set status %d , current status %x \",\n\t\t\t\t\t\t   chunk->fd.id,\n\t\t\t\t\t\t   status,\n\t\t\t\t\t\t   chunk->fd.status)));\n\t}\n\tFormData_chunk form;\n\tItemPointerData tid;\n\tbool PG_USED_FOR_ASSERTS_ONLY found;\n\n\tfound = lock_chunk_tuple(chunk->fd.id, &tid, &form);\n\tAssert(found);\n\n\t/* Somebody could update the status before we are able to lock it so check again */\n\tif (ts_flags_are_set_32(form.status, CHUNK_STATUS_FROZEN))\n\t{\n\t\t/* chunk in frozen state cannot be modified */\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_TS_OPERATION_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot modify frozen chunk status\"),\n\t\t\t\t errdetail(\"chunk id = %d attempt to set status %d , current status %d \",\n\t\t\t\t\t\t   chunk->fd.id,\n\t\t\t\t\t\t   status,\n\t\t\t\t\t\t   form.status)));\n\t}\n\n\t/* applying the flags after locking the metadata tuple */\n\tint32 old_status = form.status;\n\tform.status = ts_set_flags_32(form.status, status);\n\tchunk->fd.status = form.status;\n\n\t/* Row-level locks are released at transaction end or during savepoint rollback */\n\tif (old_status != form.status)\n\t{\n\t\tchunk_update_catalog_tuple(&tid, &form);\n\t\tstatus_set = true;\n\t}\n\n\treturn status_set;\n}\n\n/*Assume permissions are already checked */\nbool\nts_chunk_set_compressed_chunk(Chunk *chunk, int32 compressed_chunk_id)\n{\n\tuint32 flags = CHUNK_STATUS_COMPRESSED;\n\tuint32 mstatus = ts_set_flags_32(chunk->fd.status, flags);\n\tif (ts_flags_are_set_32(chunk->fd.status, CHUNK_STATUS_FROZEN))\n\t{\n\t\t/* chunk in frozen state cannot be modified */\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_TS_OPERATION_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot modify frozen chunk status\"),\n\t\t\t\t errdetail(\"chunk id = %d attempt to set status %d , current status %d \",\n\t\t\t\t\t\t   chunk->fd.id,\n\t\t\t\t\t\t   mstatus,\n\t\t\t\t\t\t   chunk->fd.status)));\n\t}\n\n\tFormData_chunk form;\n\tItemPointerData tid;\n\tbool PG_USED_FOR_ASSERTS_ONLY found;\n\n\tfound = lock_chunk_tuple(chunk->fd.id, &tid, &form);\n\tAssert(found);\n\n\t/* Somebody could update the status before we are able to lock it so check again */\n\tif (ts_flags_are_set_32(form.status, CHUNK_STATUS_FROZEN))\n\t{\n\t\t/* chunk in frozen state cannot be modified */\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_TS_OPERATION_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot modify frozen chunk status\"),\n\t\t\t\t errdetail(\"chunk id = %d attempt to set status %d , current status %d \",\n\t\t\t\t\t\t   chunk->fd.id,\n\t\t\t\t\t\t   mstatus,\n\t\t\t\t\t\t   form.status)));\n\t}\n\n\t/* re-applying the flags after locking the metadata tuple */\n\tform.status = ts_set_flags_32(form.status, flags);\n\tform.compressed_chunk_id = compressed_chunk_id;\n\n\tchunk->fd.compressed_chunk_id = form.compressed_chunk_id;\n\tchunk->fd.status = form.status;\n\n\tchunk_update_catalog_tuple(&tid, &form);\n\treturn true;\n}\n\n/*Assume permissions are already checked */\nbool\nts_chunk_clear_compressed_chunk(Chunk *chunk)\n{\n\tuint32 flags = CHUNK_STATUS_COMPRESSED | CHUNK_STATUS_COMPRESSED_UNORDERED |\n\t\t\t\t   CHUNK_STATUS_COMPRESSED_PARTIAL;\n\tuint32 mstatus = ts_clear_flags_32(chunk->fd.status, flags);\n\tif (ts_flags_are_set_32(chunk->fd.status, CHUNK_STATUS_FROZEN))\n\t{\n\t\t/* chunk in frozen state cannot be modified */\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_TS_OPERATION_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot modify frozen chunk status\"),\n\t\t\t\t errdetail(\"chunk id = %d attempt to set status %d , current status %d \",\n\t\t\t\t\t\t   chunk->fd.id,\n\t\t\t\t\t\t   mstatus,\n\t\t\t\t\t\t   chunk->fd.status)));\n\t}\n\n\tFormData_chunk form;\n\tItemPointerData tid;\n\tbool PG_USED_FOR_ASSERTS_ONLY found;\n\n\tfound = lock_chunk_tuple(chunk->fd.id, &tid, &form);\n\tAssert(found);\n\n\t/* Somebody could update the status before we are able to lock it so check again */\n\tif (ts_flags_are_set_32(form.status, CHUNK_STATUS_FROZEN))\n\t{\n\t\t/* chunk in frozen state cannot be modified */\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_TS_OPERATION_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot modify frozen chunk status\"),\n\t\t\t\t errdetail(\"chunk id = %d attempt to set status %d , current status %d \",\n\t\t\t\t\t\t   chunk->fd.id,\n\t\t\t\t\t\t   mstatus,\n\t\t\t\t\t\t   form.status)));\n\t}\n\n\t/* re-applying the flags after locking the metadata tuple */\n\tform.status = ts_clear_flags_32(form.status, flags);\n\tform.compressed_chunk_id = INVALID_CHUNK_ID;\n\n\tchunk->fd.compressed_chunk_id = form.compressed_chunk_id;\n\tchunk->fd.status = form.status;\n\n\tchunk_update_catalog_tuple(&tid, &form);\n\treturn true;\n}\n\n/* Used as a tuple found function */\nstatic ScanTupleResult\nchunk_rename_schema_name(TupleInfo *ti, void *data)\n{\n\tFormData_chunk form;\n\tHeapTuple new_tuple;\n\tCatalogSecurityContext sec_ctx;\n\n\tts_chunk_formdata_fill(&form, ti);\n\t/* Rename schema name */\n\tnamestrcpy(&form.schema_name, (char *) data);\n\tnew_tuple = chunk_formdata_make_tuple(&form, ts_scanner_get_tupledesc(ti));\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_update_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti), new_tuple);\n\tts_catalog_restore_user(&sec_ctx);\n\theap_freetuple(new_tuple);\n\treturn SCAN_CONTINUE;\n}\n\n/* Go through the internal chunk table and rename all matching schemas */\nvoid\nts_chunks_rename_schema_name(char *old_schema, char *new_schema)\n{\n\tNameData old_schema_name;\n\tScanKeyData scankey[1];\n\tCatalog *catalog = ts_catalog_get();\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, CHUNK),\n\t\t.index = catalog_get_index(catalog, CHUNK, CHUNK_SCHEMA_NAME_INDEX),\n\t\t.nkeys = 1,\n\t\t.scankey = scankey,\n\t\t.tuple_found = chunk_rename_schema_name,\n\t\t.data = new_schema,\n\t\t.lockmode = RowExclusiveLock,\n\t\t.scandirection = ForwardScanDirection,\n\t};\n\n\tnamestrcpy(&old_schema_name, old_schema);\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_chunk_schema_name_idx_schema_name,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_NAMEEQ,\n\t\t\t\tNameGetDatum(&old_schema_name));\n\n\tts_scanner_scan(&scanctx);\n}\n\nstatic int\nchunk_cmp(const void *ch1, const void *ch2)\n{\n\tconst Chunk *v1 = ((const Chunk *) ch1);\n\tconst Chunk *v2 = ((const Chunk *) ch2);\n\n\tif (v1->fd.hypertable_id < v2->fd.hypertable_id)\n\t\treturn -1;\n\tif (v1->fd.hypertable_id > v2->fd.hypertable_id)\n\t\treturn 1;\n\tif (v1->table_id < v2->table_id)\n\t\treturn -1;\n\tif (v1->table_id > v2->table_id)\n\t\treturn 1;\n\treturn 0;\n}\n\n/*\n * This is a helper set returning function (SRF) that takes a set returning function context\n * and as argument and returns oids extracted from funcctx->user_fctx (which is Chunk*\n * array). Note that the caller needs to be registered as a set returning function for this\n * to work.\n */\nstatic Datum\nshow_chunks_return_srf(FunctionCallInfo fcinfo)\n{\n\tFuncCallContext *funcctx;\n\tuint64 call_cntr;\n\tTupleDesc tupdesc;\n\tChunk *result_set;\n\tChunk *curr_chunk;\n\n\t/* stuff done only on the first call of the function */\n\tif (SRF_IS_FIRSTCALL())\n\t{\n\t\t/* Build a tuple descriptor for our result type */\n\t\t/* not quite necessary */\n\t\tif (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_SCALAR)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"function returning record called in context \"\n\t\t\t\t\t\t\t\"that cannot accept type record\")));\n\t}\n\n\t/* stuff done on every call of the function */\n\tfuncctx = SRF_PERCALL_SETUP();\n\n\tcall_cntr = funcctx->call_cntr;\n\tresult_set = (Chunk *) funcctx->user_fctx;\n\n\t/*\n\t * skip if it's an OSM chunk. Ideally this check could be done deep down in\n\t * functions like \"chunk_scan_context_add_chunk\", \"chunk_tuple_dropped_filter\"\n\t * etc. but they are used by other APIs like drop_chunks, chunk_scan_find, etc\n\t * which need access to the OSM chunk. Trying to unify scan functions across\n\t * all such usages seems to be too much of an overhaul as compared to this.\n\t *\n\t * Check the index appropriately first.\n\t */\n\tif (call_cntr < funcctx->max_calls)\n\t{\n\t\tcurr_chunk = &result_set[call_cntr];\n\t\tif (IS_OSM_CHUNK(curr_chunk))\n\t\t{\n\t\t\tcall_cntr = ++funcctx->call_cntr;\n\t\t}\n\t}\n\n\t/* do when there is more left to send */\n\tif (call_cntr < funcctx->max_calls)\n\t\tSRF_RETURN_NEXT(funcctx, result_set[call_cntr].table_id);\n\telse /* do when there is no more left */\n\t\tSRF_RETURN_DONE(funcctx);\n}\n\nvoid\nts_chunk_drop(const Chunk *chunk, DropBehavior behavior, int32 log_level)\n{\n\tObjectAddress objaddr = {\n\t\t.classId = RelationRelationId,\n\t\t.objectId = chunk->table_id,\n\t};\n\n\tif (log_level >= 0)\n\t\telog(log_level,\n\t\t\t \"dropping chunk %s.%s\",\n\t\t\t NameStr(chunk->fd.schema_name),\n\t\t\t NameStr(chunk->fd.table_name));\n\n\t/* Remove the chunk from the chunk table */\n\tts_chunk_delete_by_relid_and_relname(chunk->table_id,\n\t\t\t\t\t\t\t\t\t\t NameStr(chunk->fd.schema_name),\n\t\t\t\t\t\t\t\t\t\t NameStr(chunk->fd.table_name),\n\t\t\t\t\t\t\t\t\t\t behavior);\n\n\t/* Drop the table */\n\tperformDeletion(&objaddr, behavior, 0);\n}\n\nstatic void\nlock_referenced_tables(Oid table_relid)\n{\n\tList *fk_relids = NIL;\n\tListCell *lf;\n\tList *cachedfkeys = NIL;\n\tRelation table_rel = table_open(table_relid, AccessShareLock);\n\n\t/* this list is from the relcache and can disappear with a cache flush, so\n\t * no further catalog access till we save the fk relids */\n\tcachedfkeys = RelationGetFKeyList(table_rel);\n\tforeach (lf, cachedfkeys)\n\t{\n\t\tForeignKeyCacheInfo *cachedfk = lfirst_node(ForeignKeyCacheInfo, lf);\n\n\t\t/* conrelid should always be that of the table we're considering */\n\t\tAssert(cachedfk->conrelid == RelationGetRelid(table_rel));\n\t\tfk_relids = lappend_oid(fk_relids, cachedfk->confrelid);\n\t}\n\ttable_close(table_rel, AccessShareLock);\n\tforeach (lf, fk_relids)\n\t\tLockRelationOid(lfirst_oid(lf), AccessExclusiveLock);\n}\n\nList *\nts_chunk_do_drop_chunks(Hypertable *ht, int64 older_than, int64 newer_than, int32 log_level,\n\t\t\t\t\t\tOid time_type, Oid arg_type, bool older_newer)\n\n{\n\tuint64 num_chunks = 0;\n\tChunk *chunks;\n\tconst char *schema_name, *table_name;\n\tconst int32 hypertable_id = ht->fd.id;\n\tbool has_continuous_aggs, is_materialization_hypertable;\n\tconst MemoryContext oldcontext = CurrentMemoryContext;\n\tScanTupLock tuplock = {\n\t\t.waitpolicy = LockWaitBlock,\n\t\t.lockmode = LockTupleExclusive,\n\t};\n\n\tts_hypertable_permissions_check(ht->main_table_relid, GetUserId());\n\n\t/* We have a FK between hypertable H and PAR. Hypertable H has number of\n\t * chunks C1, C2, etc. When we execute \"drop table C\", PG acquires locks\n\t * on C and PAR. If we have a query as \"select * from hypertable\", this\n\t * acquires a lock on C and PAR as well. But the order of the locks is not\n\t * the same and results in deadlocks. - github issue #865 We hope to\n\t * alleviate the problem by acquiring a lock on PAR before executing the\n\t * drop table stmt. This is not fool-proof as we could have multiple\n\t * fkrelids and the order of lock acquisition for these could differ as\n\t * well. Do not unlock - let the transaction semantics take care of it. */\n\tlock_referenced_tables(ht->main_table_relid);\n\n\tis_materialization_hypertable = false;\n\n\tswitch (ts_continuous_agg_hypertable_status(hypertable_id))\n\t{\n\t\tcase HypertableIsMaterialization:\n\t\t\thas_continuous_aggs = false;\n\t\t\tis_materialization_hypertable = true;\n\t\t\tbreak;\n\t\tcase HypertableIsMaterializationAndRaw:\n\t\t\thas_continuous_aggs = true;\n\t\t\tis_materialization_hypertable = true;\n\t\t\tbreak;\n\t\tcase HypertableIsRawTable:\n\t\t\thas_continuous_aggs = true;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\thas_continuous_aggs = false;\n\t\t\tbreak;\n\t}\n\n\tPG_TRY();\n\t{\n\t\t/*\n\t\t * For INTEGER type dimensions, we support querying using intervals or any\n\t\t * timestamp or date input. For such INTEGER dimensions, we get the chunks\n\t\t * using their creation time values.\n\t\t */\n\t\tif (IS_INTEGER_TYPE(time_type) && (arg_type == INTERVALOID || IS_TIMESTAMP_TYPE(arg_type)))\n\t\t{\n\t\t\tchunks = get_chunks_in_creation_time_range(ht,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   older_than,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   newer_than,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   CurrentMemoryContext,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   &num_chunks,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   &tuplock);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (!older_newer)\n\t\t\t\tchunks = get_chunks_in_creation_time_range(ht,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   older_than,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   newer_than,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   CurrentMemoryContext,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   &num_chunks,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   &tuplock);\n\t\t\telse\n\t\t\t\tchunks = get_chunks_in_time_range(ht,\n\t\t\t\t\t\t\t\t\t\t\t\t  older_than,\n\t\t\t\t\t\t\t\t\t\t\t\t  newer_than,\n\t\t\t\t\t\t\t\t\t\t\t\t  CurrentMemoryContext,\n\t\t\t\t\t\t\t\t\t\t\t\t  &num_chunks,\n\t\t\t\t\t\t\t\t\t\t\t\t  &tuplock);\n\t\t}\n\t}\n\tPG_CATCH();\n\t{\n\t\tErrorData *edata;\n\t\tMemoryContextSwitchTo(oldcontext);\n\t\tedata = CopyErrorData();\n\t\tFlushErrorState();\n\t\tif (edata->sqlerrcode == ERRCODE_LOCK_NOT_AVAILABLE)\n\t\t{\n\t\t\tedata->detail = edata->message;\n\t\t\tedata->message =\n\t\t\t\tpsprintf(\"some chunks could not be read since they are being concurrently updated\");\n\t\t}\n\t\tReThrowError(edata);\n\t}\n\tPG_END_TRY();\n\n\tDEBUG_WAITPOINT(\"drop_chunks_chunks_found\");\n\n\tint32 osm_chunk_id = ts_chunk_get_osm_chunk_id(ht->fd.id);\n\tif (has_continuous_aggs)\n\t{\n\t\t/* Exclusively lock all chunks, and invalidate the continuous\n\t\t * aggregates in the regions covered by the chunks. We do this in two\n\t\t * steps: first lock all the chunks and then invalidate the\n\t\t * regions. Since we are going to drop the chunks, there is no point\n\t\t * in allowing inserts into them.\n\t\t *\n\t\t * Locking prevents further modification of the dropped region during\n\t\t * this transaction, which allows moving the invalidation threshold\n\t\t * without having to worry about new invalidations while\n\t\t * refreshing. */\n\t\tfor (uint64 i = 0; i < num_chunks; i++)\n\t\t{\n\t\t\tLockRelationOid(chunks[i].table_id, ExclusiveLock);\n\n\t\t\tAssert(hyperspace_get_open_dimension(ht->space, 0)->fd.id ==\n\t\t\t\t   chunks[i].cube->slices[0]->fd.dimension_id);\n\t\t}\n\n\t\tDEBUG_WAITPOINT(\"drop_chunks_locked\");\n\n\t\t/* Invalidate the dropped region to indicate that it was modified.\n\t\t *\n\t\t * The invalidation will allow the refresh command on a continuous\n\t\t * aggregate to see that this region was dropped and and will\n\t\t * therefore be able to refresh accordingly.*/\n\t\tfor (uint64 i = 0; i < num_chunks; i++)\n\t\t{\n\t\t\tif (osm_chunk_id == chunks[i].fd.id)\n\t\t\t{\n\t\t\t\t// we do not rebuild continuous aggs if tiered data is dropped */\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tint64 start = ts_chunk_primary_dimension_start(&chunks[i]);\n\t\t\tint64 end = ts_chunk_primary_dimension_end(&chunks[i]);\n\n\t\t\tts_cm_functions->continuous_agg_invalidate_raw_ht(ht, start, end);\n\t\t}\n\t}\n\n\tList *dropped_chunk_names = NIL;\n\tfor (uint64 i = 0; i < num_chunks; i++)\n\t{\n\t\tchar *chunk_name;\n\n\t\tASSERT_IS_VALID_CHUNK(&chunks[i]);\n\n\t\t/* frozen chunks are skipped. Not dropped. */\n\t\tif (!ts_chunk_validate_chunk_status_for_operation(&chunks[i],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  CHUNK_DROP,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  false /*throw_error */) ||\n\t\t\tosm_chunk_id == chunks[i].fd.id)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* store chunk name for output */\n\t\tschema_name = quote_identifier(NameStr(chunks[i].fd.schema_name));\n\t\ttable_name = quote_identifier(NameStr(chunks[i].fd.table_name));\n\t\tchunk_name = psprintf(\"%s.%s\", schema_name, table_name);\n\t\tdropped_chunk_names = lappend(dropped_chunk_names, chunk_name);\n\n\t\tts_chunk_drop(chunks + i, DROP_RESTRICT, log_level);\n\t}\n\t// if we have tiered chunks cascade drop to tiering layer as well\n\tif (osm_chunk_id != INVALID_CHUNK_ID)\n\t{\n\t\tChunk *osm_chunk = ts_chunk_get_by_id(osm_chunk_id, true);\n\n\t\thypertable_drop_chunks_hook_type osm_drop_chunks_hook =\n\t\t\tts_get_osm_hypertable_drop_chunks_hook();\n\n\t\t/*\n\t\t * The OSM library may not be loaded at the moment if\n\t\t * `ts_chunk_do_drop_chunks` is called from the a background worker\n\t\t * (e.g. from a retention policy). We call `GetFdwRoutineByRelId` to\n\t\t * ensure the library is loaded.\n\t\t */\n\t\tif (!osm_drop_chunks_hook && GetFdwRoutineByRelId(osm_chunk->table_id))\n\t\t\tosm_drop_chunks_hook = ts_get_osm_hypertable_drop_chunks_hook();\n\n\t\tif (osm_drop_chunks_hook)\n\t\t{\n\t\t\tListCell *lc;\n\t\t\tDimension *dim = &ht->space->dimensions[0];\n\t\t\t/* convert to PG timestamp from timescaledb internal format */\n\t\t\tint64 range_start = ts_internal_to_time_int64(newer_than, dim->fd.column_type);\n\t\t\tint64 range_end = ts_internal_to_time_int64(older_than, dim->fd.column_type);\n\t\t\tList *osm_dropped_names = osm_drop_chunks_hook(osm_chunk->table_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   NameStr(ht->fd.schema_name),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   NameStr(ht->fd.table_name),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   range_start,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   range_end);\n\t\t\tforeach (lc, osm_dropped_names)\n\t\t\t{\n\t\t\t\tdropped_chunk_names = lappend(dropped_chunk_names, lfirst(lc));\n\t\t\t}\n\t\t}\n\t}\n\n\t/* When dropping chunks for a given CAgg then force set the watermark */\n\tif (is_materialization_hypertable)\n\t{\n\t\tbool isnull;\n\t\tint64 watermark = ts_hypertable_get_open_dim_max_value(ht, 0, &isnull);\n\t\tts_cagg_watermark_update(ht, watermark, isnull, true);\n\t}\n\n\tDEBUG_WAITPOINT(\"drop_chunks_end\");\n\n\treturn dropped_chunk_names;\n}\n\n/*\n * This is a helper set returning function (SRF) that takes a set returning function context\n * and as argument and returns cstrings extracted from funcctx->user_fctx (which is a List).\n * Note that the caller needs to be registered as a set returning function for this to work.\n */\nstatic Datum\nlist_return_srf(FunctionCallInfo fcinfo)\n{\n\tFuncCallContext *funcctx;\n\tuint64 call_cntr;\n\tTupleDesc tupdesc;\n\tList *result_set;\n\tDatum retval;\n\n\t/* stuff done only on the first call of the function */\n\tif (SRF_IS_FIRSTCALL())\n\t{\n\t\t/* Build a tuple descriptor for our result type */\n\t\t/* not quite necessary */\n\t\tif (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_SCALAR)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"function returning record called in context \"\n\t\t\t\t\t\t\t\"that cannot accept type record\")));\n\t}\n\n\t/* stuff done on every call of the function */\n\tfuncctx = SRF_PERCALL_SETUP();\n\n\tcall_cntr = funcctx->call_cntr;\n\tresult_set = castNode(List, funcctx->user_fctx);\n\n\t/* do when there is more left to send */\n\tif (call_cntr < funcctx->max_calls)\n\t{\n\t\t/* store return value and increment linked list */\n\t\tretval = CStringGetTextDatum(linitial(result_set));\n\t\tfuncctx->user_fctx = list_delete_first(result_set);\n\t\tSRF_RETURN_NEXT(funcctx, retval);\n\t}\n\telse /* do when there is no more left */\n\t\tSRF_RETURN_DONE(funcctx);\n}\n\nDatum\nts_chunk_drop_single_chunk(PG_FUNCTION_ARGS)\n{\n\tOid chunk_relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\tchar *chunk_table_name = get_rel_name(chunk_relid);\n\tchar *chunk_schema_name = get_namespace_name(get_rel_namespace(chunk_relid));\n\tScanTupLock tuplock = {\n\t\t.lockmode = LockTupleKeyShare,\n\t\t.waitpolicy = LockWaitBlock,\n\t\t.lockflags = TUPLE_LOCK_FLAG_FIND_LAST_VERSION,\n\t};\n\tconst Chunk *ch = ts_chunk_get_by_name_with_memory_context(chunk_schema_name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   chunk_table_name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   NoLock,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   &tuplock,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   CurrentMemoryContext,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   true);\n\tAssert(ch != NULL);\n\tts_chunk_validate_chunk_status_for_operation(ch, CHUNK_DROP, true /*throw_error */);\n\n\tif (ts_chunk_contains_compressed_data(ch))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"dropping compressed chunks not supported\"),\n\t\t\t\t errhint(\"Please drop the corresponding chunk on the uncompressed hypertable \"\n\t\t\t\t\t\t \"instead.\")));\n\n\t/* do not drop any chunk dependencies */\n\tts_chunk_drop(ch, DROP_RESTRICT, LOG);\n\tPG_RETURN_BOOL(true);\n}\n\nDatum\nts_chunk_drop_chunks(PG_FUNCTION_ARGS)\n{\n\tMemoryContext oldcontext;\n\tFuncCallContext *funcctx;\n\tHypertable *ht;\n\tList *dc_temp = NIL;\n\tList *dc_names = NIL;\n\tOid relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\n\t/*\n\t * Marked volatile to suppress the -Wclobbered warning. The warning is\n\t * actually incorrect because these values are not used after longjmp.\n\t */\n\tvolatile int64 older_than = PG_INT64_MAX;\n\tvolatile int64 newer_than = PG_INT64_MIN;\n\tvolatile int64 created_before = PG_INT64_MAX;\n\tvolatile int64 created_after = PG_INT64_MIN;\n\tvolatile bool older_newer = false;\n\tvolatile bool before_after = false;\n\n\tbool verbose;\n\tint elevel;\n\tCache *hcache;\n\tconst Dimension *time_dim;\n\tOid time_type;\n\tOid arg_type;\n\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\targ_type = InvalidOid;\n\n\t/*\n\t * When past the first call of the SRF, dropping has already been completed,\n\t * so we just return the next chunk in the list of dropped chunks.\n\t */\n\tif (!SRF_IS_FIRSTCALL())\n\t\treturn list_return_srf(fcinfo);\n\n\tif (PG_ARGISNULL(0))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid hypertable or continuous aggregate\"),\n\t\t\t\t errhint(\"Specify a hypertable or continuous aggregate.\")));\n\n\t/* Find either the hypertable or view, or error out if the relid is\n\t * neither.\n\t *\n\t * We should improve the printout since it can either be a proper relid\n\t * that does not refer to a hypertable or a continuous aggregate, or a\n\t * relid that does not refer to anything at all. */\n\thcache = ts_hypertable_cache_pin();\n\tht = ts_resolve_hypertable_from_table_or_cagg(hcache, relid, false);\n\tAssert(ht != NULL);\n\ttime_dim = hyperspace_get_open_dimension(ht->space, 0);\n\n\tif (!time_dim)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"hypertable has no open partitioning dimension\")));\n\n\ttime_type = ts_dimension_get_partition_type(time_dim);\n\n\t/*\n\t * Treat UUID (v7) as a timestamptz type. The expected input is an interval or absolute\n\t * timestamptz.\n\t */\n\tif (IS_UUID_TYPE(time_type))\n\t\ttime_type = TIMESTAMPTZOID;\n\n\t/* note that arg_types will be the same for all specified \"ANY\" elements for a given call */\n\tif (!PG_ARGISNULL(1))\n\t{\n\t\targ_type = get_fn_expr_argtype(fcinfo->flinfo, 1);\n\t\tolder_than = ts_time_value_from_arg(PG_GETARG_DATUM(1), arg_type, time_type, true);\n\t\tolder_newer = true;\n\t}\n\n\tif (!PG_ARGISNULL(2))\n\t{\n\t\targ_type = get_fn_expr_argtype(fcinfo->flinfo, 2);\n\t\tnewer_than = ts_time_value_from_arg(PG_GETARG_DATUM(2), arg_type, time_type, true);\n\t\tolder_newer = true;\n\t}\n\n\t/*\n\t * We cannot have a mix of [older_than/newer_than] and [created_before/created_after].\n\t * So, check that first. Note that created_before/created_after have a type of\n\t * TIMESTAMPTZOID regardless of the partitioning type.\n\t */\n\n\tif (!PG_ARGISNULL(4))\n\t{\n\t\tif (older_newer)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"cannot specify \\\"older_than\\\" or \\\"newer_than\\\" together with \"\n\t\t\t\t\t\t\t\"\\\"created_before\\\"\"\n\t\t\t\t\t\t\t\"or \\\"created_after\\\"\"),\n\t\t\t\t\t errhint(\"\\\"older_than\\\" and/or \\\"newer_than\\\" is recommended with \"\n\t\t\t\t\t\t\t \"\\\"time\\\"-like partitioning\"\n\t\t\t\t\t\t\t \" and  \\\"created_before\\\" and/or \\\"created_after\\\" is recommended \"\n\t\t\t\t\t\t\t \"with \\\"integer\\\"-like\"\n\t\t\t\t\t\t\t \" partitioning.\")));\n\n\t\targ_type = get_fn_expr_argtype(fcinfo->flinfo, 4);\n\t\t/* We use the existing function for various type/conversion checks */\n\t\tcreated_before =\n\t\t\tts_time_value_from_arg(PG_GETARG_DATUM(4), arg_type, TIMESTAMPTZOID, false);\n\t\t/* convert into int64 format for comparisons */\n\t\tcreated_before = ts_internal_to_time_int64(created_before, TIMESTAMPTZOID);\n\t\tbefore_after = true;\n\t\tolder_than = created_before;\n\t}\n\n\tif (!PG_ARGISNULL(5))\n\t{\n\t\tif (older_newer)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"cannot specify \\\"older_than\\\" or \\\"newer_than\\\" together with \"\n\t\t\t\t\t\t\t\"\\\"created_before\\\"\"\n\t\t\t\t\t\t\t\" or \\\"created_after\\\"\"),\n\t\t\t\t\t errhint(\"\\\"older_than\\\" and/or \\\"newer_than\\\" is recommended with \"\n\t\t\t\t\t\t\t \"\\\"time\\\"-like partitioning\"\n\t\t\t\t\t\t\t \" and  \\\"created_before\\\" and/or \\\"created_after\\\" is recommended \"\n\t\t\t\t\t\t\t \"with \\\"integer\\\"-like\"\n\t\t\t\t\t\t\t \" partitioning.\")));\n\t\targ_type = get_fn_expr_argtype(fcinfo->flinfo, 5);\n\t\t/* We use the existing function for various type/conversion checks */\n\t\tcreated_after = ts_time_value_from_arg(PG_GETARG_DATUM(5), arg_type, TIMESTAMPTZOID, false);\n\t\t/* convert into int64 format for comparisons */\n\t\tcreated_after = ts_internal_to_time_int64(created_after, TIMESTAMPTZOID);\n\t\tbefore_after = true;\n\t\tnewer_than = created_after;\n\t}\n\n\t/* if both have not been specified then error out */\n\tif (!older_newer && !before_after)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid time range for dropping chunks\"),\n\t\t\t\t errhint(\"At least one of older_than/newer_than or created_before/created_after\"\n\t\t\t\t\t\t \" must be provided.\")));\n\n\t/*\n\t * For INTEGER type dimensions, we support querying using intervals or any\n\t * timestamp or date input. For such INTEGER dimensions, we get the chunks\n\t * using their creation time values.\n\t */\n\tif (IS_INTEGER_TYPE(time_type) && (arg_type == INTERVALOID || IS_TIMESTAMP_TYPE(arg_type)))\n\t{\n\t\t/* check that we use proper inputs for such cases */\n\t\tif (older_newer)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"cannot specify \\\"older_than\\\" and/or \\\"newer_than\\\" for \"\n\t\t\t\t\t\t\t\"\\\"integer\\\"-like partitioning types\"),\n\t\t\t\t\t errhint(\"Use \\\"created_before\\\" and/or \\\"created_after\\\" which rely on the \"\n\t\t\t\t\t\t\t \"chunk creation time values.\")));\n\t}\n\n\tverbose = PG_ARGISNULL(3) ? false : PG_GETARG_BOOL(3);\n\televel = verbose ? INFO : DEBUG2;\n\n\t/* Initial multi function call setup */\n\tfuncctx = SRF_FIRSTCALL_INIT();\n\n\t/* Drop chunks and store their names for return */\n\toldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);\n\n\tPG_TRY();\n\t{\n\t\tdc_temp = ts_chunk_do_drop_chunks(ht,\n\t\t\t\t\t\t\t\t\t\t  older_than,\n\t\t\t\t\t\t\t\t\t\t  newer_than,\n\t\t\t\t\t\t\t\t\t\t  elevel,\n\t\t\t\t\t\t\t\t\t\t  time_type,\n\t\t\t\t\t\t\t\t\t\t  arg_type,\n\t\t\t\t\t\t\t\t\t\t  older_newer);\n\t}\n\tPG_CATCH();\n\t{\n\t\t/* An error is raised if there are dependent objects, but the original\n\t\t * message is not very helpful in suggesting that you should use\n\t\t * CASCADE (we don't support it), so we replace the hint with a more\n\t\t * accurate hint for our situation. */\n\t\tErrorData *edata;\n\n\t\tMemoryContextSwitchTo(oldcontext);\n\t\tedata = CopyErrorData();\n\t\tFlushErrorState();\n\n\t\tif (edata->sqlerrcode == ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST)\n\t\t\tedata->hint = pstrdup(\"Use DROP ... to drop the dependent objects.\");\n\n\t\tts_cache_release(&hcache);\n\t\tReThrowError(edata);\n\t}\n\tPG_END_TRY();\n\tts_cache_release(&hcache);\n\tdc_names = list_concat(dc_names, dc_temp);\n\n\tMemoryContextSwitchTo(oldcontext);\n\n\t/* store data for multi function call */\n\tfuncctx->max_calls = list_length(dc_names);\n\tfuncctx->user_fctx = dc_names;\n\n\treturn list_return_srf(fcinfo);\n}\n\n/* Return the compression status for the chunk\n */\nChunkCompressionStatus\nts_chunk_get_compression_status(int32 chunk_id)\n{\n\tChunkCompressionStatus st = CHUNK_COMPRESS_NONE;\n\tScanIterator iterator = ts_scan_iterator_create(CHUNK, AccessShareLock, CurrentMemoryContext);\n\titerator.ctx.index = catalog_get_index(ts_catalog_get(), CHUNK, CHUNK_ID_INDEX);\n\tts_scan_iterator_scan_key_init(&iterator,\n\t\t\t\t\t\t\t\t   Anum_chunk_idx_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(chunk_id));\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tbool status_isnull;\n\t\tDatum status;\n\n\t\tstatus = slot_getattr(ti->slot, Anum_chunk_status, &status_isnull);\n\t\tAssert(!status_isnull);\n\t\tbool status_is_compressed =\n\t\t\tts_flags_are_set_32(DatumGetInt32(status), CHUNK_STATUS_COMPRESSED);\n\t\tbool status_is_unordered =\n\t\t\tts_flags_are_set_32(DatumGetInt32(status), CHUNK_STATUS_COMPRESSED_UNORDERED);\n\t\tbool status_is_partial =\n\t\t\tts_flags_are_set_32(DatumGetInt32(status), CHUNK_STATUS_COMPRESSED_PARTIAL);\n\t\tif (status_is_compressed)\n\t\t{\n\t\t\tif (status_is_unordered || status_is_partial)\n\t\t\t\tst = CHUNK_COMPRESS_UNORDERED;\n\t\t\telse\n\t\t\t\tst = CHUNK_COMPRESS_ORDERED;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tAssert(!status_is_unordered);\n\t\t\tst = CHUNK_COMPRESS_NONE;\n\t\t}\n\t}\n\tts_scan_iterator_close(&iterator);\n\treturn st;\n}\n\n/* Note that only a compressed chunk can have unordered flag set */\nbool\nts_chunk_is_unordered(const Chunk *chunk)\n{\n\treturn ts_flags_are_set_32(chunk->fd.status, CHUNK_STATUS_COMPRESSED_UNORDERED);\n}\n\nbool\nts_chunk_is_compressed(const Chunk *chunk)\n{\n\treturn ts_flags_are_set_32(chunk->fd.status, CHUNK_STATUS_COMPRESSED);\n}\n\nbool\nts_chunk_needs_compression(const Chunk *chunk)\n{\n\treturn !ts_chunk_is_compressed(chunk);\n}\n\nbool\nts_chunk_needs_recompression(const Chunk *chunk)\n{\n\tAssert(ts_chunk_is_compressed(chunk));\n\treturn ts_chunk_is_partial(chunk) || ts_chunk_is_unordered(chunk);\n}\n\n/* Note that only a compressed chunk can have partial flag set */\nbool\nts_chunk_is_partial(const Chunk *chunk)\n{\n\treturn ts_flags_are_set_32(chunk->fd.status, CHUNK_STATUS_COMPRESSED_PARTIAL);\n}\n\nstatic const char *\nget_chunk_operation_str(ChunkOperation cmd)\n{\n\tswitch (cmd)\n\t{\n\t\tcase CHUNK_INSERT:\n\t\t\treturn \"Insert\";\n\t\tcase CHUNK_DELETE:\n\t\t\treturn \"Delete\";\n\t\tcase CHUNK_UPDATE:\n\t\t\treturn \"Update\";\n\t\tcase CHUNK_COMPRESS:\n\t\t\treturn \"compress_chunk\";\n\t\tcase CHUNK_DECOMPRESS:\n\t\t\treturn \"decompress_chunk\";\n\t\tcase CHUNK_DROP:\n\t\t\treturn \"drop_chunk\";\n\t\tdefault:\n\t\t\treturn \"Unsupported\";\n\t}\n}\n\nbool\nts_chunk_validate_chunk_status_for_operation(const Chunk *chunk, ChunkOperation cmd,\n\t\t\t\t\t\t\t\t\t\t\t bool throw_error)\n{\n\tOid chunk_relid = chunk->table_id;\n\tint32 chunk_status = chunk->fd.status;\n\n\t/*\n\t * Block everything but DELETE on OSM chunks.\n\t */\n\tif (chunk->fd.osm_chunk)\n\t{\n\t\tswitch (cmd)\n\t\t{\n\t\t\tcase CHUNK_DROP:\n\t\t\t\treturn true;\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tif (throw_error)\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t\t errmsg(\"%s not permitted on tiered chunk \\\"%s\\\" \",\n\t\t\t\t\t\t\t\t\tget_chunk_operation_str(cmd),\n\t\t\t\t\t\t\t\t\tget_rel_name(chunk_relid))));\n\t\t\t\treturn false;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/* Handle frozen chunks */\n\tif (ts_flags_are_set_32(chunk_status, CHUNK_STATUS_FROZEN))\n\t{\n\t\t/* Data modification is not permitted on a frozen chunk */\n\t\tswitch (cmd)\n\t\t{\n\t\t\tcase CHUNK_INSERT:\n\t\t\tcase CHUNK_DELETE:\n\t\t\tcase CHUNK_UPDATE:\n\t\t\tcase CHUNK_COMPRESS:\n\t\t\tcase CHUNK_DECOMPRESS:\n\t\t\tcase CHUNK_DROP:\n\t\t\t{\n\t\t\t\tif (throw_error)\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t\t errmsg(\"%s not permitted on frozen chunk \\\"%s\\\" \",\n\t\t\t\t\t\t\t\t\tget_chunk_operation_str(cmd),\n\t\t\t\t\t\t\t\t\tget_rel_name(chunk_relid))));\n\t\t\t\treturn false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tbreak; /*supported operations */\n\t\t}\n\t}\n\t/* Handle unfrozen chunks */\n\telse\n\t{\n\t\tswitch (cmd)\n\t\t{\n\t\t\t/* supported operations */\n\t\t\tcase CHUNK_INSERT:\n\t\t\tcase CHUNK_DELETE:\n\t\t\tcase CHUNK_UPDATE:\n\t\t\t\tbreak;\n\t\t\t/* Only uncompressed chunks can be compressed */\n\t\t\tcase CHUNK_COMPRESS:\n\t\t\t{\n\t\t\t\tif (ts_flags_are_set_32(chunk_status, CHUNK_STATUS_COMPRESSED))\n\t\t\t\t\tereport((throw_error ? ERROR : NOTICE),\n\t\t\t\t\t\t\t(errcode(ERRCODE_DUPLICATE_OBJECT),\n\t\t\t\t\t\t\t errmsg(\"chunk \\\"%s\\\" is already compressed\",\n\t\t\t\t\t\t\t\t\tget_rel_name(chunk_relid))));\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\t/* Only compressed chunks can be decompressed */\n\t\t\tcase CHUNK_DECOMPRESS:\n\t\t\t{\n\t\t\t\tif (!ts_flags_are_set_32(chunk_status, CHUNK_STATUS_COMPRESSED))\n\t\t\t\t\tereport((throw_error ? ERROR : NOTICE),\n\t\t\t\t\t\t\t(errcode(ERRCODE_DUPLICATE_OBJECT),\n\t\t\t\t\t\t\t errmsg(\"chunk \\\"%s\\\" is already decompressed\",\n\t\t\t\t\t\t\t\t\tget_rel_name(chunk_relid))));\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn true;\n}\n\nDatum\nts_chunk_show(PG_FUNCTION_ARGS)\n{\n\treturn ts_cm_functions->show_chunk(fcinfo);\n}\n\nDatum\nts_chunk_create(PG_FUNCTION_ARGS)\n{\n\treturn ts_cm_functions->create_chunk(fcinfo);\n}\n\n/**\n * Get the chunk status.\n *\n * Values returned are documented above and is a bitwise or of the\n * CHUNK_STATUS_XXX values.\n *\n * @see CHUNK_STATUS_DEFAULT\n * @see CHUNK_STATUS_COMPRESSED\n * @see CHUNK_STATUS_COMPRESSED_UNORDERED\n * @see CHUNK_STATUS_FROZEN\n * @see CHUNK_STATUS_COMPRESSED_PARTIAL\n */\nDatum\nts_chunk_status(PG_FUNCTION_ARGS)\n{\n\tOid chunk_relid = PG_GETARG_OID(0);\n\tChunk *chunk = ts_chunk_get_by_relid(chunk_relid, /* fail_if_not_found */ true);\n\tPG_RETURN_INT32(chunk->fd.status);\n}\n\nTS_FUNCTION_INFO_V1(ts_chunk_status_text);\n\nDatum\nts_chunk_status_text(PG_FUNCTION_ARGS)\n{\n\tint32 status = PG_GETARG_INT32(0);\n\n\tArrayBuildState *astate = initArrayResult(TEXTOID, CurrentMemoryContext, false);\n\n\tif (status & CHUNK_STATUS_COMPRESSED)\n\t\tastate = accumArrayResult(astate,\n\t\t\t\t\t\t\t\t  CStringGetTextDatum(\"COMPRESSED\"),\n\t\t\t\t\t\t\t\t  false,\n\t\t\t\t\t\t\t\t  TEXTOID,\n\t\t\t\t\t\t\t\t  CurrentMemoryContext);\n\n\tif (status & CHUNK_STATUS_COMPRESSED_UNORDERED)\n\t\tastate = accumArrayResult(astate,\n\t\t\t\t\t\t\t\t  CStringGetTextDatum(\"UNORDERED\"),\n\t\t\t\t\t\t\t\t  false,\n\t\t\t\t\t\t\t\t  TEXTOID,\n\t\t\t\t\t\t\t\t  CurrentMemoryContext);\n\n\tif (status & CHUNK_STATUS_FROZEN)\n\t\tastate = accumArrayResult(astate,\n\t\t\t\t\t\t\t\t  CStringGetTextDatum(\"FROZEN\"),\n\t\t\t\t\t\t\t\t  false,\n\t\t\t\t\t\t\t\t  TEXTOID,\n\t\t\t\t\t\t\t\t  CurrentMemoryContext);\n\n\tif (status & CHUNK_STATUS_COMPRESSED_PARTIAL)\n\t\tastate = accumArrayResult(astate,\n\t\t\t\t\t\t\t\t  CStringGetTextDatum(\"PARTIAL\"),\n\t\t\t\t\t\t\t\t  false,\n\t\t\t\t\t\t\t\t  TEXTOID,\n\t\t\t\t\t\t\t\t  CurrentMemoryContext);\n\n\tif (status < 0 || status > (CHUNK_STATUS_COMPRESSED | CHUNK_STATUS_COMPRESSED_UNORDERED |\n\t\t\t\t\t\t\t\tCHUNK_STATUS_FROZEN | CHUNK_STATUS_COMPRESSED_PARTIAL))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid chunk status %d\", status)));\n\n\tPG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext));\n}\n\n/*\n * Lock the chunk if the lockmode demands it.\n *\n * Also check that the chunk relation actually exists after the lock is\n * acquired. Return true if no locking is necessary or the chunk relation\n * exists and the lock was successfully acquired. Otherwise return false.\n */\nbool\nts_chunk_lock_if_exists(Oid chunk_oid, LOCKMODE chunk_lockmode)\n{\n\t/* No lock is requested, so assume relation exists */\n\tif (chunk_lockmode != NoLock)\n\t{\n\t\t/* Get the lock to synchronize against concurrent drop */\n\t\tLockRelationOid(chunk_oid, chunk_lockmode);\n\n\t\t/*\n\t\t * Now that we have the lock, double-check to see if the relation\n\t\t * really exists or not.  If not, assume it was dropped while we\n\t\t * waited to acquire lock, and ignore it.\n\t\t */\n\t\tif (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(chunk_oid)))\n\t\t{\n\t\t\t/* Release useless lock */\n\t\t\tUnlockRelationOid(chunk_oid, chunk_lockmode);\n\t\t\t/* And ignore this relation */\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\nScanIterator\nts_chunk_scan_iterator_create(MemoryContext result_mcxt)\n{\n\tScanIterator it = ts_scan_iterator_create(CHUNK, AccessShareLock, result_mcxt);\n\tit.ctx.flags |= SCANNER_F_NOEND_AND_NOCLOSE;\n\n\treturn it;\n}\n\nvoid\nts_chunk_scan_iterator_set_chunk_id(ScanIterator *it, int32 chunk_id)\n{\n\tit->ctx.index = catalog_get_index(ts_catalog_get(), CHUNK, CHUNK_ID_INDEX);\n\tts_scan_iterator_scan_key_reset(it);\n\tts_scan_iterator_scan_key_init(it,\n\t\t\t\t\t\t\t\t   Anum_chunk_idx_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(chunk_id));\n}\n\n/*\n * Create a hypercube for the OSM chunk\n * The initial range for the OSM chunk will be from INT64_MAX - 1 to INT64_MAX.\n * This range was chosen to minimize interference with tuple routing and\n * occupy a range outside of potential values as there must be no overlap\n * between the hypercube occupied by the osm chunk and actual chunks.\n */\nstatic Hypercube *\nfill_hypercube_for_osm_chunk(Hyperspace *hs)\n{\n\tHypercube *cube = ts_hypercube_alloc(hs->num_dimensions);\n\tAssert(hs->num_dimensions == 1); // does not work with partitioned range\n\tfor (int i = 0; i < hs->num_dimensions; i++)\n\t{\n\t\tconst Dimension *dim = &hs->dimensions[i];\n\t\tAssert(dim->type == DIMENSION_TYPE_OPEN);\n\t\tcube->slices[i] = ts_dimension_slice_create(dim->fd.id, PG_INT64_MAX - 1, PG_INT64_MAX);\n\t\tcube->num_slices++;\n\t}\n\tAssert(cube->num_slices == 1);\n\treturn cube;\n}\n\n/* adds foreign table as a chunk to the hypertable.\n * creates a dummy chunk constraint for the time dimension.\n * These constraints are recorded in the chunk-dimension slice metadata.\n * They are NOT added as CHECK constraints on the foreign table.\n *\n * Does not add any inheritable constraints or indexes that are already\n * defined on the hypertable.\n *\n * This is used to add an OSM table as a chunk.\n * Set the osm_chunk flag to true.\n */\nstatic void\nadd_foreign_table_as_chunk(Oid relid, Hypertable *parent_ht)\n{\n\tHyperspace *hs = parent_ht->space;\n\tCatalog *catalog = ts_catalog_get();\n\tCatalogSecurityContext sec_ctx;\n\tChunk *chunk;\n\tchar *relschema = get_namespace_name(get_rel_namespace(relid));\n\tchar *relname = get_rel_name(relid);\n\n\tOid ht_ownerid = ts_rel_get_owner(parent_ht->main_table_relid);\n\n\tif (!has_privs_of_role(GetUserId(), ht_ownerid))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),\n\t\t\t\t errmsg(\"must be owner of hypertable \\\"%s\\\"\",\n\t\t\t\t\t\tget_rel_name(parent_ht->main_table_relid))));\n\n\tAssert(get_rel_relkind(relid) == RELKIND_FOREIGN_TABLE);\n\tif (hs->num_dimensions > 1)\n\t\telog(ERROR,\n\t\t\t \"cannot attach a  foreign table to a hypertable that has more than 1 dimension\");\n\t/* Create a new chunk based on the hypercube */\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tchunk = ts_chunk_create_base(ts_catalog_table_next_seq_id(catalog, CHUNK),\n\t\t\t\t\t\t\t\t hs->num_dimensions,\n\t\t\t\t\t\t\t\t RELKIND_RELATION);\n\tts_catalog_restore_user(&sec_ctx);\n\n\t/* fill in the correct table_name for the chunk*/\n\tchunk->fd.hypertable_id = hs->hypertable_id;\n\tchunk->fd.osm_chunk = true; /* this is an OSM chunk */\n\tchunk->cube = fill_hypercube_for_osm_chunk(hs);\n\tchunk->hypertable_relid = parent_ht->main_table_relid;\n\tchunk->constraints = ts_chunk_constraints_alloc(1, CurrentMemoryContext);\n\n\tnamestrcpy(&chunk->fd.schema_name, relschema);\n\tnamestrcpy(&chunk->fd.table_name, relname);\n\n\t/* Insert chunk */\n\tts_chunk_insert_lock(chunk, RowExclusiveLock);\n\n\t/* insert dimension slices if they do not exist.\n\t */\n\tts_dimension_slice_insert_multi(chunk->cube->slices, chunk->cube->num_slices);\n\t/* check constraints are not automatically created for foreign tables.\n\t * See: ts_chunk_constraints_add_dimension_constraints.\n\t * Collect all the check constraints from the hypertable and add them to the\n\t * foreign table. Otherwise, cannot add as child of the hypertable (pg inheritance\n\t * code will error. Note that the name of the check constraint on the hypertable\n\t * and the foreign table chunk should match.\n\t */\n\tts_chunk_constraints_add_inheritable_check_constraints(chunk->constraints,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   chunk->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   chunk->relkind,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   chunk->hypertable_relid);\n\tchunk_create_table_constraints(parent_ht, chunk);\n\t/* Add dimension constraints for the chunk */\n\tts_chunk_constraints_add_dimension_constraints(chunk->constraints, chunk->fd.id, chunk->cube);\n\tts_chunk_constraints_insert_metadata(chunk->constraints);\n\tchunk_add_inheritance(chunk, parent_ht);\n\t/*\n\t * Update hypertable entry with tiering status information.\n\t * XXX: For compatibility reasons, we set the noncontiguous flag, but\n\t * this should be reverted as soon as the newer version of the OSM extension\n\t * is rolled out.\n\t * Noncontiguous flag should not be set since the chunk should be empty upon\n\t * creation, with an invalid range assigned, so ordered append should be allowed.\n\t * Once the data is moved into the OSM chunk, then our catalog should be\n\t * updated with proper API calls from the OSM extension.\n\t */\n\tparent_ht->fd.status =\n\t\tts_set_flags_32(parent_ht->fd.status,\n\t\t\t\t\t\tHYPERTABLE_STATUS_OSM | HYPERTABLE_STATUS_OSM_CHUNK_NONCONTIGUOUS);\n\tts_hypertable_update_status_osm(parent_ht);\n}\n\nvoid\nts_chunk_merge_on_dimension(const Hypertable *ht, Chunk *chunk, const Chunk *merge_chunk,\n\t\t\t\t\t\t\tint32 dimension_id)\n{\n\tconst DimensionSlice *slice, *merge_slice;\n\tint num_ccs = 0;\n\tbool dimension_slice_found = false;\n\n\tif (chunk->hypertable_relid != merge_chunk->hypertable_relid)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"cannot merge chunks from different hypertables\"),\n\t\t\t\t errhint(\"chunk 1: \\\"%s\\\", chunk 2: \\\"%s\\\"\",\n\t\t\t\t\t\t get_rel_name(chunk->table_id),\n\t\t\t\t\t\t get_rel_name(merge_chunk->table_id))));\n\n\tfor (int i = 0; i < chunk->cube->num_slices; i++)\n\t{\n\t\tif (chunk->cube->slices[i]->fd.dimension_id == dimension_id)\n\t\t{\n\t\t\tslice = chunk->cube->slices[i];\n\t\t\tmerge_slice = merge_chunk->cube->slices[i];\n\t\t\tdimension_slice_found = true;\n\t\t}\n\t\telse if (chunk->cube->slices[i]->fd.id != merge_chunk->cube->slices[i]->fd.id)\n\t\t{\n\t\t\t/* If the slices do not match (except on time dimension), we cannot merge the chunks. */\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t\t errmsg(\"cannot merge chunks with different partitioning schemas\"),\n\t\t\t\t\t errhint(\"chunk 1: \\\"%s\\\", chunk 2: \\\"%s\\\" have different slices on \"\n\t\t\t\t\t\t\t \"dimension ID %d\",\n\t\t\t\t\t\t\t get_rel_name(chunk->table_id),\n\t\t\t\t\t\t\t get_rel_name(merge_chunk->table_id),\n\t\t\t\t\t\t\t chunk->cube->slices[i]->fd.dimension_id)));\n\t\t}\n\t}\n\n\tif (!dimension_slice_found)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"cannot find slice for merging dimension\"),\n\t\t\t\t errhint(\"chunk 1: \\\"%s\\\", chunk 2: \\\"%s\\\", dimension ID %d\",\n\t\t\t\t\t\t get_rel_name(chunk->table_id),\n\t\t\t\t\t\t get_rel_name(merge_chunk->table_id),\n\t\t\t\t\t\t dimension_id)));\n\n\tif (slice->fd.range_end != merge_slice->fd.range_start)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"cannot merge non-adjacent chunks over supplied dimension\"),\n\t\t\t\t errhint(\"chunk 1: \\\"%s\\\", chunk 2: \\\"%s\\\", dimension ID %d\",\n\t\t\t\t\t\t get_rel_name(chunk->table_id),\n\t\t\t\t\t\t get_rel_name(merge_chunk->table_id),\n\t\t\t\t\t\t dimension_id)));\n\n\tnum_ccs =\n\t\tts_chunk_constraint_scan_by_dimension_slice_id(slice->fd.id, NULL, CurrentMemoryContext);\n\n\t/* There should always be an associated chunk constraint to a dimension slice.\n\t * This can only occur when the catalog metadata is corrupt.\n\t */\n\tif (num_ccs <= 0)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"missing chunk constraint for dimension slice\"),\n\t\t\t\t errhint(\"chunk: \\\"%s\\\", slice ID %d\",\n\t\t\t\t\t\t get_rel_name(chunk->table_id),\n\t\t\t\t\t\t slice->fd.id)));\n\n\tDimensionSlice *new_slice =\n\t\tts_dimension_slice_create(dimension_id, slice->fd.range_start, merge_slice->fd.range_end);\n\n\t/* Only if there is exactly one chunk constraint for the merged dimension slice\n\t * we can go ahead and delete it since we are dropping the chunk.\n\t */\n\tif (num_ccs == 1)\n\t\tts_dimension_slice_delete_by_id(slice->fd.id, false);\n\n\t/* Check for dimension slice already exists, if not create a new one. */\n\tScanTupLock tuplock = {\n\t\t.lockmode = LockTupleKeyShare,\n\t\t.waitpolicy = LockWaitBlock,\n\t};\n\tif (!ts_dimension_slice_scan_for_existing(new_slice, &tuplock))\n\t{\n\t\tts_dimension_slice_insert(new_slice);\n\t}\n\n\tts_chunk_constraint_update_slice_id(chunk->fd.id, slice->fd.id, new_slice->fd.id);\n\tChunkConstraints *ccs = ts_chunk_constraints_alloc(1, CurrentMemoryContext);\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(CHUNK_CONSTRAINT, AccessShareLock, CurrentMemoryContext);\n\n\tts_chunk_constraint_scan_iterator_set_slice_id(&iterator, new_slice->fd.id);\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tbool isnull;\n\t\tDatum d;\n\n\t\td = slot_getattr(ts_scan_iterator_slot(&iterator), Anum_chunk_constraint_chunk_id, &isnull);\n\n\t\tif (!isnull && DatumGetInt32(d) == chunk->fd.id)\n\t\t{\n\t\t\tnum_ccs++;\n\t\t\tts_chunk_constraints_add_from_tuple(ccs, ts_scan_iterator_tuple_info(&iterator));\n\t\t}\n\t}\n\n\tif (num_ccs <= 0)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"missing chunk constraint for merged dimension slice\"),\n\t\t\t\t errhint(\"chunk: \\\"%s\\\", slice ID %d\",\n\t\t\t\t\t\t get_rel_name(chunk->table_id),\n\t\t\t\t\t\t new_slice->fd.id)));\n\n\t/* Update the slice in the chunk's hypercube. Needed to make recreate constraints work. */\n\tfor (int i = 0; i < chunk->cube->num_slices; i++)\n\t{\n\t\tif (chunk->cube->slices[i]->fd.dimension_id == dimension_id)\n\t\t{\n\t\t\tchunk->cube->slices[i] = new_slice;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t/* Delete the old constraint */\n\tfor (int i = 0; i < chunk->constraints->num_constraints; i++)\n\t{\n\t\tconst ChunkConstraint *cc = &chunk->constraints->constraints[i];\n\n\t\tif (cc->fd.dimension_slice_id == slice->fd.id)\n\t\t{\n\t\t\tObjectAddress constrobj = {\n\t\t\t\t.classId = ConstraintRelationId,\n\t\t\t\t.objectId = get_relation_constraint_oid(chunk->table_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tNameStr(cc->fd.constraint_name),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfalse),\n\t\t\t};\n\n\t\t\tperformDeletion(&constrobj, DROP_RESTRICT, 0);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t/* We have to recreate the chunk constraints since we are changing\n\t * table constraints when updating the slice.\n\t */\n\tChunkConstraints *oldccs = chunk->constraints;\n\tchunk->constraints = ccs;\n\tts_process_utility_set_expect_chunk_modification(true);\n\tts_chunk_constraints_create(ht, chunk);\n\tts_chunk_copy_referencing_fk(ht, chunk);\n\tts_process_utility_set_expect_chunk_modification(false);\n\tchunk->constraints = oldccs;\n\n\tts_chunk_drop(merge_chunk, DROP_RESTRICT, 1);\n}\n\n/* Internal API used by OSM extension. OSM table is a foreign table that is\n * attached as a chunk of the hypertable. A chunk needs dimension constraints. We\n * add dummy constraints for the OSM chunk and then attach it to the hypertable.\n * OSM extension is responsible for maintaining any constraints on this table.\n */\nDatum\nts_chunk_attach_osm_table_chunk(PG_FUNCTION_ARGS)\n{\n\tOid hypertable_relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\tOid ftable_relid = PG_ARGISNULL(1) ? InvalidOid : PG_GETARG_OID(1);\n\tbool ret = false;\n\n\tCache *hcache;\n\tHypertable *ht =\n\t\tts_hypertable_cache_get_cache_and_entry(hypertable_relid, CACHE_FLAG_MISSING_OK, &hcache);\n\n\tif (!ht)\n\t{\n\t\tchar *name = get_rel_name(hypertable_relid);\n\n\t\tif (!name)\n\t\t\tereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg(\"invalid Oid\")));\n\t\telse\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_TS_HYPERTABLE_NOT_EXIST),\n\t\t\t\t\t errmsg(\"\\\"%s\\\" is not a hypertable\", name)));\n\t}\n\n\tif (get_rel_relkind(ftable_relid) == RELKIND_FOREIGN_TABLE)\n\t{\n\t\tadd_foreign_table_as_chunk(ftable_relid, ht);\n\t\tret = true;\n\t}\n\tts_cache_release(&hcache);\n\n\tPG_RETURN_BOOL(ret);\n}\n\nstatic ScanTupleResult\nchunk_tuple_osm_chunk_found(TupleInfo *ti, void *arg)\n{\n\tbool isnull;\n\tDatum osm_chunk = slot_getattr(ti->slot, Anum_chunk_osm_chunk, &isnull);\n\n\tAssert(!isnull);\n\tbool is_osm_chunk = DatumGetBool(osm_chunk);\n\n\tif (!is_osm_chunk)\n\t\treturn SCAN_CONTINUE;\n\n\tint *chunk_id = (int *) arg;\n\tDatum chunk_id_datum = slot_getattr(ti->slot, Anum_chunk_id, &isnull);\n\tAssert(!isnull);\n\t*chunk_id = DatumGetInt32(chunk_id_datum);\n\treturn SCAN_DONE;\n}\n\n/* get OSM chunk id associated with the hypertable */\nint\nts_chunk_get_osm_chunk_id(int hypertable_id)\n{\n\tint chunk_id = INVALID_CHUNK_ID;\n\tScanKeyData scankey[2];\n\tbool is_osm_chunk = true;\n\tCatalog *catalog = ts_catalog_get();\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, CHUNK),\n\t\t.index = catalog_get_index(catalog, CHUNK, CHUNK_OSM_CHUNK_INDEX),\n\t\t.nkeys = 2,\n\t\t.scankey = scankey,\n\t\t.data = &chunk_id,\n\t\t.tuple_found = chunk_tuple_osm_chunk_found,\n\t\t.lockmode = AccessShareLock,\n\t\t.scandirection = ForwardScanDirection,\n\t};\n\n\t/*\n\t * Perform an index scan on hypertable ID + osm_chunk\n\t */\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_chunk_osm_chunk_idx_osm_chunk,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_BOOLEQ,\n\t\t\t\tBoolGetDatum(is_osm_chunk));\n\tScanKeyInit(&scankey[1],\n\t\t\t\tAnum_chunk_osm_chunk_idx_hypertable_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(hypertable_id));\n\n\tint num_found = ts_scanner_scan(&scanctx);\n\n\tif (num_found > 1)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t errmsg(\"More than 1 OSM chunk found for hypertable (%d)\", hypertable_id)));\n\t}\n\n\treturn chunk_id;\n}\n\n/* Upon creation, OSM chunks are assigned an invalid range [INT64_MAX -1, infinity) */\nbool\nts_osm_chunk_range_is_invalid(int64 range_start, int64 range_end)\n{\n\treturn ((range_end == PG_INT64_MAX) && (range_start == range_end - 1));\n}\n\nint32\nts_chunk_get_osm_slice_id(int32 chunk_id, int32 time_dim_id)\n{\n\tChunk *chunk = ts_chunk_get_by_id(chunk_id, true);\n\tconst DimensionSlice *ds = ts_hypercube_get_slice_by_dimension_id(chunk->cube, time_dim_id);\n\tconst int slice_id = ds->fd.id;\n\treturn slice_id;\n}\n\n/*\n * Initialization and access method for ChunkVec. This needs to be extended to support additional\n * operations.\n */\nstatic ChunkVec *\nchunk_vec_expand(ChunkVec *chunks, uint32 new_capacity)\n{\n\tif (chunks != NULL && chunks->capacity >= new_capacity)\n\t\treturn chunks;\n\n\tif (chunks == NULL)\n\t\tchunks = palloc(CHUNK_VEC_SIZE(new_capacity));\n\telse\n\t\tchunks = repalloc(chunks, CHUNK_VEC_SIZE(new_capacity));\n\n\tchunks->capacity = new_capacity;\n\n\treturn chunks;\n}\n\nChunkVec *\nts_chunk_vec_create(int32 capacity)\n{\n\tChunkVec *chunks = chunk_vec_expand(NULL, capacity);\n\n\tchunks->num_chunks = 0;\n\treturn chunks;\n}\n\nChunkVec *\nts_chunk_vec_sort(ChunkVec **chunks)\n{\n\tChunkVec *vec = *chunks;\n\n\tif (vec->num_chunks > 1)\n\t\tqsort(&vec->chunks, vec->num_chunks, sizeof(Chunk), chunk_cmp);\n\n\treturn vec;\n}\n\nChunkVec *\nts_chunk_vec_add_from_tuple(ChunkVec **chunks, TupleInfo *ti)\n{\n\tChunk *chunkptr = NULL;\n\tChunkVec *vec;\n\tint num_constraints_hint;\n\tScanIterator it;\n\n\tvec = *chunks;\n\tnum_constraints_hint = 2;\n\n\tif (vec->num_chunks + 1 > vec->capacity)\n\t\t*chunks = vec = chunk_vec_expand(vec, vec->capacity + DEFAULT_CHUNK_VEC_SIZE);\n\n\tchunkptr = &vec->chunks[vec->num_chunks++];\n\tts_chunk_formdata_fill(&chunkptr->fd, ti);\n\n\t/*\n\t * Get the chunk constraints, hypercube slices and relation details.\n\t */\n\tchunkptr->constraints =\n\t\tts_chunk_constraint_scan_by_chunk_id(chunkptr->fd.id, num_constraints_hint, ti->mctx);\n\n\tit = ts_dimension_slice_scan_iterator_create(NULL, ti->mctx);\n\tchunkptr->cube = ts_hypercube_from_constraints(chunkptr->constraints, &it);\n\tts_scan_iterator_close(&it);\n\n\tchunkptr->table_id = ts_get_relation_relid(NameStr(chunkptr->fd.schema_name),\n\t\t\t\t\t\t\t\t\t\t\t   NameStr(chunkptr->fd.table_name),\n\t\t\t\t\t\t\t\t\t\t\t   true);\n\tchunkptr->hypertable_relid = ts_hypertable_id_to_relid(chunkptr->fd.hypertable_id, false);\n\tchunkptr->relkind = get_rel_relkind(chunkptr->table_id);\n\n\treturn vec;\n}\n\n/*\n * Prepare for an index scan for all the chunks matching the given hypertable_id and range criteria.\n */\nstatic int\nchunk_creation_time_set_range(ScanIterator *it, Oid hypertable_id, StrategyNumber start_strategy,\n\t\t\t\t\t\t\t  int64 start_value, StrategyNumber end_strategy, int64 end_value)\n{\n\tTypeCacheEntry *tce;\n\n\tit->ctx.index =\n\t\tcatalog_get_index(ts_catalog_get(), CHUNK, CHUNK_HYPERTABLE_ID_CREATION_TIME_INDEX);\n\tts_scan_iterator_scan_key_reset(it);\n\n\tts_scan_iterator_scan_key_init(it,\n\t\t\t\t\t\t\t\t   Anum_chunk_hypertable_id_creation_time_idx_hypertable_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(hypertable_id));\n\n\ttce = lookup_type_cache(TIMESTAMPTZOID, TYPECACHE_BTREE_OPFAMILY);\n\n\t/* If both are valid strategies then a proper scan within these limits will be performed */\n\tif (start_strategy != InvalidStrategy)\n\t{\n\t\tOid opno =\n\t\t\tget_opfamily_member(tce->btree_opf, TIMESTAMPTZOID, TIMESTAMPTZOID, start_strategy);\n\t\tOid proc = get_opcode(opno);\n\n\t\tAssert(OidIsValid(proc));\n\n\t\tts_scan_iterator_scan_key_init(it,\n\t\t\t\t\t\t\t\t\t   Anum_chunk_hypertable_id_creation_time_idx_creation_time,\n\t\t\t\t\t\t\t\t\t   start_strategy,\n\t\t\t\t\t\t\t\t\t   proc,\n\t\t\t\t\t\t\t\t\t   Int64GetDatum(start_value));\n\t}\n\tif (end_strategy != InvalidStrategy)\n\t{\n\t\tOid opno =\n\t\t\tget_opfamily_member(tce->btree_opf, TIMESTAMPTZOID, TIMESTAMPTZOID, end_strategy);\n\t\tOid proc = get_opcode(opno);\n\n\t\tAssert(OidIsValid(proc));\n\n\t\tts_scan_iterator_scan_key_init(it,\n\t\t\t\t\t\t\t\t\t   Anum_chunk_hypertable_id_creation_time_idx_creation_time,\n\t\t\t\t\t\t\t\t\t   end_strategy,\n\t\t\t\t\t\t\t\t\t   proc,\n\t\t\t\t\t\t\t\t\t   Int64GetDatum(end_value));\n\t}\n\n\treturn it->ctx.nkeys;\n}\n\n/*\n * Perform an index scan for given hypertable_id and chunk creation time range.\n *\n * Returns an array of chunks using ChunkVec the encloses all the chunks satisfying the range\n * criteria.\n */\nstatic Chunk *\nget_chunks_in_creation_time_range_limit(Hypertable *ht, StrategyNumber start_strategy,\n\t\t\t\t\t\t\t\t\t\tint64 start_value, StrategyNumber end_strategy,\n\t\t\t\t\t\t\t\t\t\tint64 end_value, int limit, uint64 *num_chunks,\n\t\t\t\t\t\t\t\t\t\tScanTupLock *tupLock)\n{\n\tScanIterator it;\n\tChunkVec *chunk_vec = NULL;\n\n\tit = ts_scan_iterator_create(CHUNK, AccessShareLock, CurrentMemoryContext);\n\tit.ctx.flags |= SCANNER_F_NOEND_AND_NOCLOSE;\n\tit.ctx.tuplock = tupLock;\n\n\tchunk_creation_time_set_range(&it,\n\t\t\t\t\t\t\t\t  ht->fd.id,\n\t\t\t\t\t\t\t\t  start_strategy,\n\t\t\t\t\t\t\t\t  start_value,\n\t\t\t\t\t\t\t\t  end_strategy,\n\t\t\t\t\t\t\t\t  end_value);\n\tit.ctx.limit = limit;\n\n#ifdef TS_DEBUG\n\t/* allow testing of the chunk vec expansion in debug builds */\n\tif (limit == -1)\n\t\tlimit = 1;\n#endif\n\n\tchunk_vec = ts_chunk_vec_create(limit > 0 ? limit : DEFAULT_CHUNK_VEC_SIZE);\n\n\tts_scanner_foreach(&it)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&it);\n\t\tts_chunk_vec_add_from_tuple(&chunk_vec, ti);\n\t}\n\n\tts_scan_iterator_close(&it);\n\n\tts_chunk_vec_sort(&chunk_vec);\n\n\t*num_chunks = chunk_vec->num_chunks;\n\n\treturn chunk_vec->chunks;\n}\n\nChunk *\nget_chunks_in_creation_time_range(Hypertable *ht, int64 older_than, int64 newer_than,\n\t\t\t\t\t\t\t\t  MemoryContext mctx, uint64 *num_chunks_returned,\n\t\t\t\t\t\t\t\t  ScanTupLock *tupLock)\n{\n\tMemoryContext oldcontext;\n\tChunk *chunks = NULL;\n\tStrategyNumber start_strategy;\n\tStrategyNumber end_strategy;\n\tuint64 num_chunks = 0;\n\n\tif (older_than <= newer_than)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid chunk creation time range\"),\n\t\t\t\t errhint(\"The start of the time range must be before the end.\")));\n\n\tstart_strategy = (newer_than == PG_INT64_MIN) ? InvalidStrategy : BTGreaterEqualStrategyNumber;\n\tend_strategy = (older_than == PG_INT64_MAX) ? InvalidStrategy : BTLessStrategyNumber;\n\n\toldcontext = MemoryContextSwitchTo(mctx);\n\tchunks = get_chunks_in_creation_time_range_limit(ht,\n\t\t\t\t\t\t\t\t\t\t\t\t\t start_strategy,\n\t\t\t\t\t\t\t\t\t\t\t\t\t newer_than,\n\t\t\t\t\t\t\t\t\t\t\t\t\t end_strategy,\n\t\t\t\t\t\t\t\t\t\t\t\t\t older_than,\n\t\t\t\t\t\t\t\t\t\t\t\t\t -1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t &num_chunks,\n\t\t\t\t\t\t\t\t\t\t\t\t\t tupLock);\n\tMemoryContextSwitchTo(oldcontext);\n\t*num_chunks_returned = num_chunks;\n\n\treturn chunks;\n}\n\nDatum\nts_chunk_drop_osm_chunk(PG_FUNCTION_ARGS)\n{\n\tOid hypertable_relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\tCache *hcache = ts_hypertable_cache_pin();\n\tHypertable *ht = ts_resolve_hypertable_from_table_or_cagg(hcache, hypertable_relid, true);\n\tint32 osm_chunk_id = ts_chunk_get_osm_chunk_id(ht->fd.id);\n\tChunk *osm_chunk = ts_chunk_get_by_id(osm_chunk_id, true);\n\n\tts_chunk_validate_chunk_status_for_operation(osm_chunk, CHUNK_DROP, true);\n\n\t/* do not drop any chunk dependencies */\n\tts_chunk_drop(osm_chunk, DROP_RESTRICT, LOG);\n\n\t/* reset hypertable OSM status */\n\tht->fd.status =\n\t\tts_clear_flags_32(ht->fd.status,\n\t\t\t\t\t\t  HYPERTABLE_STATUS_OSM | HYPERTABLE_STATUS_OSM_CHUNK_NONCONTIGUOUS);\n\tts_hypertable_update_status_osm(ht);\n\tts_cache_release(&hcache);\n\tPG_RETURN_BOOL(true);\n}\n\nTS_FUNCTION_INFO_V1(ts_merge_two_chunks);\n\nDatum\nts_merge_two_chunks(PG_FUNCTION_ARGS)\n{\n\tDatum chunks[2] = { PG_GETARG_DATUM(0), PG_GETARG_DATUM(1) };\n\tbool concurrently = PG_ARGISNULL(2) ? false : PG_GETARG_BOOL(2);\n\tArrayType *chunk_array =\n\t\tconstruct_array(chunks, 2, REGCLASSOID, sizeof(Oid), true, TYPALIGN_INT);\n\treturn DirectFunctionCall2(ts_cm_functions->merge_chunks,\n\t\t\t\t\t\t\t   PointerGetDatum(chunk_array),\n\t\t\t\t\t\t\t   BoolGetDatum(concurrently));\n}\n\nTS_FUNCTION_INFO_V1(ts_merge_chunks_concurrently);\n\nDatum\nts_merge_chunks_concurrently(PG_FUNCTION_ARGS)\n{\n\treturn DirectFunctionCall2(ts_cm_functions->merge_chunks,\n\t\t\t\t\t\t\t   PG_GETARG_DATUM(0),\n\t\t\t\t\t\t\t   BoolGetDatum(true));\n}\n\nvoid\nts_chunk_detach_by_relid(Oid relid)\n{\n\tScanIterator iterator = ts_scan_iterator_create(CHUNK, RowExclusiveLock, CurrentMemoryContext);\n\tchar *schema;\n\tchar *table;\n\tint PG_USED_FOR_ASSERTS_ONLY count;\n\n\tAssert(OidIsValid(relid));\n\n\tschema = get_namespace_name(get_rel_namespace(relid));\n\ttable = get_rel_name(relid);\n\n\tinit_scan_by_qualified_table_name(&iterator, schema, table);\n\tcount = chunk_delete(&iterator, relid, DROP_RESTRICT, true);\n\n\t/*\n\t * (schema,table) names and (hypertable_id) are unique so should only have\n\t * dropped one chunk\n\t */\n\tAssert(count == 1);\n}\n"
  },
  {
    "path": "src/chunk.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <access/htup.h>\n#include <access/tupdesc.h>\n#include <foreign/foreign.h>\n#include <utils/hsearch.h>\n\n#include \"chunk_constraint.h\"\n#include \"export.h\"\n#include \"hypertable.h\"\n#include \"ts_catalog/catalog.h\"\n\n#define INVALID_CHUNK_ID 0\n#define IS_OSM_CHUNK(chunk) ((chunk)->fd.osm_chunk == true)\n\n/* Should match definitions in ddl_api.sql */\n#define DROP_CHUNKS_FUNCNAME \"drop_chunks\"\n#define DROP_CHUNKS_NARGS 6\n#define COMPRESS_CHUNK_FUNCNAME \"compress_chunk\"\n#define COMPRESS_CHUNK_NARGS 2\n#define DECOMPRESS_CHUNK_FUNCNAME \"decompress_chunk\"\n#define RECOMPRESS_CHUNK_FUNCNAME \"recompress_chunk\"\n#define RECOMPRESS_CHUNK_NARGS 2\n\ntypedef enum ChunkCompressionStatus\n{\n\tCHUNK_COMPRESS_NONE = 0,\n\tCHUNK_COMPRESS_UNORDERED,\n\tCHUNK_COMPRESS_ORDERED,\n} ChunkCompressionStatus;\n\ntypedef enum ChunkOperation\n{\n\tCHUNK_DROP = 0,\n\tCHUNK_INSERT,\n\tCHUNK_UPDATE,\n\tCHUNK_DELETE,\n\tCHUNK_SELECT,\n\tCHUNK_COMPRESS,\n\tCHUNK_DECOMPRESS,\n} ChunkOperation;\n\ntypedef struct Hypercube Hypercube;\ntypedef struct Point Point;\ntypedef struct Hyperspace Hyperspace;\ntypedef struct Hypertable Hypertable;\n\n/*\n * A chunk represents a table that stores data, part of a partitioned\n * table.\n *\n * Conceptually, a chunk is a hypercube in an N-dimensional space. The\n * boundaries of the cube is represented by a collection of slices from the N\n * distinct dimensions.\n */\ntypedef struct Chunk\n{\n\tFormData_chunk fd;\n\tchar relkind;\n\tOid table_id;\n\tOid hypertable_relid;\n\n\t/*\n\t * The hypercube defines the chunks position in the N-dimensional space.\n\t * Each of the N slices in the cube corresponds to a constraint on the\n\t * chunk table.\n\t */\n\tHypercube *cube;\n\tChunkConstraints *constraints;\n\n} Chunk;\n\n/* This structure is used during the join of the chunk constraints to find\n * chunks that match all constraints. It is a stripped down version of the chunk\n * since we don't want to fill in all the fields until we find a match. */\ntypedef struct ChunkStub\n{\n\tint32 id;\n\tHypercube *cube;\n\tChunkConstraints *constraints;\n} ChunkStub;\n\n/*\n * ChunkScanCtx is used to scan for chunks in a hypertable's N-dimensional\n * hyperspace.\n *\n * For every matching constraint, a corresponding chunk will be created in the\n * context's hash table, keyed on the chunk ID.\n */\ntypedef struct ChunkScanCtx\n{\n\tHTAB *htab;\n\tchar relkind; /* Create chunks of this relkind */\n\tconst Hypertable *ht;\n\tconst Point *point;\n\tunsigned int num_complete_chunks;\n\tuint64 num_processed;\n\tbool early_abort;\n\tLOCKMODE lockmode;\n\n\tvoid *data;\n} ChunkScanCtx;\n\n/* Returns true if the stub has a full set of constraints, otherwise\n * false. Used to find a stub matching a point in an N-dimensional\n * hyperspace. */\nstatic inline bool\nchunk_stub_is_complete(const ChunkStub *stub, const Hyperspace *space)\n{\n\treturn space->num_dimensions == stub->constraints->num_dimension_constraints;\n}\n\n/* The hash table entry for the ChunkScanCtx */\ntypedef struct ChunkScanEntry\n{\n\tint32 chunk_id;\n\tChunkStub *stub;\n\n\t/* Used for fast chunk search where we don't want to build chunk stubs. */\n\tint32 num_dimension_constraints;\n} ChunkScanEntry;\n\n/*\n * Information to be able to display a scan key details for error messages.\n */\ntypedef struct DisplayKeyData\n{\n\tconst char *name;\n\tconst char *(*as_string)(Datum);\n} DisplayKeyData;\n\n/*\n * Chunk vector is collection of chunks for a given hypertable_id.\n */\ntypedef struct ChunkVec\n{\n\tuint32 capacity;\n\tuint32 num_chunks;\n\n\tChunk chunks[FLEXIBLE_ARRAY_MEMBER];\n} ChunkVec;\n\nextern ChunkVec *ts_chunk_vec_create(int32 capacity);\nextern ChunkVec *ts_chunk_vec_sort(ChunkVec **chunks);\nextern ChunkVec *ts_chunk_vec_add_from_tuple(ChunkVec **chunks, TupleInfo *ti);\n\n#define CHUNK_VEC_SIZE(num_chunks) (sizeof(ChunkVec) + (sizeof(Chunk) * num_chunks))\n#define DEFAULT_CHUNK_VEC_SIZE 10\n\nextern void ts_chunk_formdata_fill(FormData_chunk *fd, const TupleInfo *ti);\nextern int32 ts_chunk_point_find_chunk_id(const Hypertable *ht, const Point *p,\n\t\t\t\t\t\t\t\t\t\t  const ScanTupLock *slice_lock);\nextern Chunk *ts_chunk_find_for_point(const Hypertable *ht, const Point *p, LOCKMODE lockmode);\nextern Chunk *ts_chunk_create_for_point(const Hypertable *ht, const Point *p, const char *schema,\n\t\t\t\t\t\t\t\t\t\tconst char *prefix, LOCKMODE chunk_lockmode);\nList *ts_chunk_id_find_in_subspace(Hypertable *ht, List *dimension_vecs);\n\nextern TSDLLEXPORT Chunk *ts_chunk_create_base(int32 id, int16 num_constraints, const char relkind);\nextern TSDLLEXPORT ChunkStub *ts_chunk_stub_create(int32 id, int16 num_constraints);\nextern TSDLLEXPORT Chunk *ts_chunk_copy(const Chunk *chunk);\nextern TSDLLEXPORT Chunk *\nts_chunk_get_by_name_with_memory_context(const char *schema_name, const char *table_name,\n\t\t\t\t\t\t\t\t\t\t LOCKMODE chunk_lockmode, const ScanTupLock *slice_lock,\n\t\t\t\t\t\t\t\t\t\t MemoryContext mctx, bool fail_if_not_found);\nextern TSDLLEXPORT void ts_chunk_insert_lock(const Chunk *chunk, LOCKMODE lock);\n\nextern TSDLLEXPORT Oid ts_chunk_create_table(const Chunk *chunk, const Hypertable *ht,\n\t\t\t\t\t\t\t\t\t\t\t const char *tablespacename);\nextern TSDLLEXPORT Chunk *ts_chunk_get_by_id_with_slice_lock(int32 id, LOCKMODE chunk_lockmode,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t const ScanTupLock *slice_lock,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t bool fail_if_not_found);\n\nextern TSDLLEXPORT Chunk *ts_chunk_get_by_id(int32 id, bool fail_if_not_found);\nextern TSDLLEXPORT Chunk *ts_chunk_get_by_relid_locked(Oid relid, LOCKMODE lockmode,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   const ScanTupLock *slice_lock,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   bool fail_if_not_found);\nextern TSDLLEXPORT Chunk *ts_chunk_get_by_relid(Oid relid, bool fail_if_not_found);\nextern TSDLLEXPORT void ts_chunk_free(Chunk *chunk);\nextern bool ts_chunk_exists(const char *schema_name, const char *table_name);\nextern TSDLLEXPORT int32 ts_chunk_get_hypertable_id_by_reloid(Oid reloid);\nextern TSDLLEXPORT FormData_chunk ts_chunk_get_formdata(int32 chunk_id);\nextern TSDLLEXPORT bool ts_chunk_simple_scan_by_reloid(Oid reloid, FormData_chunk *form,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   bool missing_ok);\nextern TSDLLEXPORT Oid ts_chunk_get_relid(int32 chunk_id, bool missing_ok);\nextern Oid ts_chunk_get_schema_id(int32 chunk_id, bool missing_ok);\nextern TSDLLEXPORT bool ts_chunk_get_id(const char *schema, const char *table, int32 *chunk_id,\n\t\t\t\t\t\t\t\t\t\tbool missing_ok);\nextern bool ts_chunk_exists_relid(Oid relid);\nextern TSDLLEXPORT bool ts_chunk_exists_with_compression(int32 hypertable_id);\nextern void ts_chunk_recreate_all_constraints_for_dimension(Hypertable *ht, int32 dimension_id);\nextern int ts_chunk_delete_by_hypertable_id(int32 hypertable_id);\nextern TSDLLEXPORT int ts_chunk_delete_by_name(const char *schema, const char *table,\n\t\t\t\t\t\t\t\t\t\t\t   DropBehavior behavior);\nextern int ts_chunk_delete_by_relid_and_relname(Oid relid, const char *schemaname,\n\t\t\t\t\t\t\t\t\t\t\t\tconst char *tablename, DropBehavior behavior);\nextern bool ts_chunk_set_name(Chunk *chunk, const char *newname);\nextern bool ts_chunk_set_schema(Chunk *chunk, const char *newschema);\nextern TSDLLEXPORT List *ts_chunk_get_window(int32 dimension_id, int64 point, int count,\n\t\t\t\t\t\t\t\t\t\t\t MemoryContext mctx);\nextern void ts_chunks_rename_schema_name(char *old_schema, char *new_schema);\n\nextern TSDLLEXPORT bool ts_chunk_set_partial(Chunk *chunk);\nextern TSDLLEXPORT bool ts_chunk_set_unordered(Chunk *chunk);\nextern TSDLLEXPORT bool ts_chunk_set_frozen(Chunk *chunk);\nextern TSDLLEXPORT bool ts_chunk_unset_frozen(Chunk *chunk);\nextern TSDLLEXPORT bool ts_chunk_is_frozen(const Chunk *chunk);\nextern TSDLLEXPORT bool ts_chunk_set_compressed_chunk(Chunk *chunk, int32 compressed_chunk_id);\nextern TSDLLEXPORT bool ts_chunk_clear_compressed_chunk(Chunk *chunk);\nextern TSDLLEXPORT void ts_chunk_drop(const Chunk *chunk, DropBehavior behavior, int32 log_level);\nextern TSDLLEXPORT List *ts_chunk_do_drop_chunks(Hypertable *ht, int64 older_than, int64 newer_than,\n\t\t\t\t\t\t\t\t\t\t\t\t int32 log_level, Oid time_type, Oid arg_type,\n\t\t\t\t\t\t\t\t\t\t\t\t bool older_newer);\nextern TSDLLEXPORT Chunk *\nts_chunk_find_or_create_without_cuts(const Hypertable *ht, Hypercube *hc, const char *schema_name,\n\t\t\t\t\t\t\t\t\t const char *table_name, Oid chunk_table_relid, bool *created);\nextern TSDLLEXPORT Chunk *ts_chunk_get_compressed_chunk_parent(const Chunk *chunk);\nextern TSDLLEXPORT bool ts_chunk_is_unordered(const Chunk *chunk);\nextern TSDLLEXPORT bool ts_chunk_is_partial(const Chunk *chunk);\nextern TSDLLEXPORT bool ts_chunk_is_compressed(const Chunk *chunk);\nextern TSDLLEXPORT bool ts_chunk_needs_compression(const Chunk *chunk);\nextern TSDLLEXPORT bool ts_chunk_needs_recompression(const Chunk *chunk);\nextern TSDLLEXPORT bool ts_chunk_validate_chunk_status_for_operation(const Chunk *chunk,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t ChunkOperation cmd,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t bool throw_error);\n\nextern TSDLLEXPORT bool ts_chunk_contains_compressed_data(const Chunk *chunk);\nextern TSDLLEXPORT ChunkCompressionStatus ts_chunk_get_compression_status(int32 chunk_id);\nextern TSDLLEXPORT Datum ts_chunk_id_from_relid(PG_FUNCTION_ARGS);\nextern TSDLLEXPORT Datum ts_chunk_status_text(PG_FUNCTION_ARGS);\nextern TSDLLEXPORT List *ts_chunk_get_chunk_ids_by_hypertable_id(int32 hypertable_id);\nextern TSDLLEXPORT List *ts_chunk_get_by_hypertable_id(int32 hypertable_id);\n\nextern TSDLLEXPORT int64 ts_chunk_primary_dimension_start(const Chunk *chunk);\n\nextern TSDLLEXPORT int64 ts_chunk_primary_dimension_end(const Chunk *chunk);\nextern Chunk *ts_chunk_build_from_tuple_and_stub(Chunk **chunkptr, TupleInfo *ti,\n\t\t\t\t\t\t\t\t\t\t\t\t const ChunkStub *stub,\n\t\t\t\t\t\t\t\t\t\t\t\t const ScanTupLock *slice_lock);\n\nextern TM_Result ts_chunk_lock_for_creating_compressed_chunk(int32 chunk_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t int32 *compressed_chunk_id);\nextern ScanIterator ts_chunk_scan_iterator_create(MemoryContext result_mcxt);\nextern void ts_chunk_scan_iterator_set_chunk_id(ScanIterator *it, int32 chunk_id);\nextern bool ts_chunk_lock_if_exists(Oid chunk_oid, LOCKMODE chunk_lockmode);\nint ts_chunk_get_osm_chunk_id(int hypertable_id);\nextern TSDLLEXPORT void ts_chunk_merge_on_dimension(const Hypertable *ht, Chunk *chunk,\n\t\t\t\t\t\t\t\t\t\t\t\t\tconst Chunk *merge_chunk, int32 dimension_id);\nextern TSDLLEXPORT void ts_chunk_detach_by_relid(Oid relid);\n\n#define chunk_get_by_name(schema_name, table_name, chunk_lockmode, slice_lock, fail_if_not_found)  \\\n\tts_chunk_get_by_name_with_memory_context(schema_name,                                          \\\n\t\t\t\t\t\t\t\t\t\t\t table_name,                                           \\\n\t\t\t\t\t\t\t\t\t\t\t chunk_lockmode,                                       \\\n\t\t\t\t\t\t\t\t\t\t\t slice_lock,                                           \\\n\t\t\t\t\t\t\t\t\t\t\t CurrentMemoryContext,                                 \\\n\t\t\t\t\t\t\t\t\t\t\t fail_if_not_found)\n\n/*\n * Sanity checks for chunk.\n *\n * The individual checks are split into separate Asserts so it's\n * easier to tell from a stacktrace which one failed.\n */\n#define ASSERT_IS_VALID_CHUNK(chunk)                                                               \\\n\tdo                                                                                             \\\n\t{                                                                                              \\\n\t\tAssert(chunk);                                                                             \\\n\t\tAssert((chunk)->fd.id > 0);                                                                \\\n\t\tAssert((chunk)->fd.hypertable_id > 0);                                                     \\\n\t\tAssert(OidIsValid((chunk)->table_id));                                                     \\\n\t\tAssert(OidIsValid((chunk)->hypertable_relid));                                             \\\n\t\tAssert((chunk)->cube);                                                                     \\\n\t\tAssert((chunk)->relkind == RELKIND_RELATION || (chunk)->relkind == RELKIND_FOREIGN_TABLE); \\\n\t} while (0)\n\n/*\n * The chunk status field values are persisted in the database and must never be changed.\n * Those values are used as flags and must always be powers of 2 to allow bitwise operations.\n * When adding new status values we must make sure to add special handling for these values\n * to the downgrade script as previous versions will not know how to deal with those.\n */\n#define CHUNK_STATUS_DEFAULT 0\n/*\n * Setting a chunk status field as CHUNK_STATUS_COMPRESSED means that the corresponding\n * compressed_chunk_id field points to a chunk that holds the compressed data. Otherwise,\n * the corresponding compressed_chunk_id is NULL.\n */\n#define CHUNK_STATUS_COMPRESSED 1\n/*\n * When inserting into a compressed chunk the configured compress_orderby is not retained.\n * Any such chunks need an explicit Sort step to produce ordered output until the chunk\n * ordering has been restored by recompress_chunk. This flag can only exist on compressed\n * chunks.\n */\n#define CHUNK_STATUS_COMPRESSED_UNORDERED 2\n/*\n * A chunk is in frozen state (i.e no inserts/updates/deletes into this chunk are\n * permitted. Other chunk level operations like dropping chunk etc. are also blocked.\n *\n */\n#define CHUNK_STATUS_FROZEN 4\n/*\n * A chunk is in this state when it is compressed but also has uncompressed tuples\n * in the uncompressed chunk.\n */\n#define CHUNK_STATUS_COMPRESSED_PARTIAL 8\n\nextern TSDLLEXPORT bool ts_chunk_clear_status(Chunk *chunk, int32 status);\nextern bool ts_osm_chunk_range_is_invalid(int64 range_start, int64 range_end);\nextern int32 ts_chunk_get_osm_slice_id(int32 chunk_id, int32 time_dim_id);\n"
  },
  {
    "path": "src/chunk_adaptive.c",
    "content": "/*\n * The contents and feature provided by this file (and its associated header\n * file) -- adaptive chunking -- are currently in BETA.\n * Feedback, suggestions, and bugs should be reported\n * on the GitHub repository issues page, or in our public slack.\n */\n#include <postgres.h>\n#include <catalog/pg_proc.h>\n#include <catalog/pg_type.h>\n#include <funcapi.h>\n#include <math.h>\n#include <miscadmin.h>\n#include <parser/parse_func.h>\n#include <utils/acl.h>\n#include <utils/array.h>\n#include <utils/builtins.h>\n#include <utils/guc.h>\n#include <utils/lsyscache.h>\n#include <utils/snapmgr.h>\n#include <utils/syscache.h>\n#include <utils/typcache.h>\n\n#include \"compat/compat.h\"\n#include \"chunk.h\"\n#include \"chunk_adaptive.h\"\n#include \"errors.h\"\n#include \"hypercube.h\"\n#include \"hypertable_cache.h\"\n#include \"utils.h\"\n\n#define DEFAULT_CHUNK_SIZING_FN_NAME \"calculate_chunk_interval\"\n\n/* This can be set to a positive number (and non-zero) value from tests to\n * simulate memory cache size. This makes it possible to run tests\n * deterministically. */\nstatic int64 fixed_memory_cache_size = -1;\n\n/*\n * Takes a PostgreSQL text representation of data (e.g., 40MB) and converts it\n * into a int64 for calculations\n */\nstatic int64\nconvert_text_memory_amount_to_bytes(const char *memory_amount)\n{\n\tconst char *hintmsg;\n\tint nblocks;\n\tint64 bytes;\n\n\tif (NULL == memory_amount)\n\t\telog(ERROR, \"invalid memory amount\");\n\n\tif (!parse_int(memory_amount, &nblocks, GUC_UNIT_BLOCKS, &hintmsg))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid data amount\"),\n\t\t\t\t errhint(\"%s\", hintmsg)));\n\n\tbytes = nblocks;\n\tbytes *= BLCKSZ;\n\n\treturn bytes;\n}\n\n/*\n * Exposed for testing purposes to be able to simulate a different memory\n * cache size in tests.\n */\nTS_FUNCTION_INFO_V1(ts_set_memory_cache_size);\n\nDatum\nts_set_memory_cache_size(PG_FUNCTION_ARGS)\n{\n\tconst char *memory_amount = text_to_cstring(PG_GETARG_TEXT_P(0));\n\n\tfixed_memory_cache_size = convert_text_memory_amount_to_bytes(memory_amount);\n\n\tPG_RETURN_INT64(fixed_memory_cache_size);\n}\n\n/*\n * Get the amount of cache memory for chunks.\n * We use shared_buffers converted to bytes.\n */\nstatic int64\nget_memory_cache_size(void)\n{\n\tconst char *val;\n\tconst char *hintmsg;\n\tint shared_buffers;\n\tint64 memory_bytes;\n\n\tif (fixed_memory_cache_size > 0)\n\t\treturn fixed_memory_cache_size;\n\n\tval = GetConfigOption(\"shared_buffers\", false, false);\n\n\tif (NULL == val)\n\t\telog(ERROR, \"missing configuration for 'shared_buffers'\");\n\n\tif (!parse_int(val, &shared_buffers, GUC_UNIT_BLOCKS, &hintmsg))\n\t\telog(ERROR, \"could not parse 'shared_buffers' setting: %s\", hintmsg);\n\n\tmemory_bytes = shared_buffers;\n\n\t/* Value is in blocks, so convert to bytes */\n\tmemory_bytes *= BLCKSZ;\n\n\treturn memory_bytes;\n}\n\n/*\n * For chunk sizing, we don't want to set chunk size exactly the same as the\n * available cache memory, since chunk sizes won't be exact. We therefore give\n * some slack here.\n */\n#define DEFAULT_CACHE_MEMORY_SLACK 0.9\n\nextern inline int64\nts_chunk_calculate_initial_chunk_target_size(void)\n{\n\treturn (int64) ((double) get_memory_cache_size() * DEFAULT_CACHE_MEMORY_SLACK);\n}\n\ntypedef enum MinMaxResult\n{\n\tMINMAX_NO_INDEX,\n\tMINMAX_NO_TUPLES,\n\tMINMAX_FOUND,\n} MinMaxResult;\n\n/*\n * Use a heap scan to find the min and max of a given column of a chunk. This\n * could be a rather costly operation. Should figure out how to keep min-max\n * stats cached.\n */\nstatic MinMaxResult\nminmax_heapscan(Relation rel, Oid atttype, AttrNumber attnum, Datum minmax[2])\n{\n\tTupleTableSlot *slot = table_slot_create(rel, NULL);\n\tTableScanDesc scan;\n\tTypeCacheEntry *tce;\n\tbool nulls[2] = { true, true };\n\n\t/* Lookup the tuple comparison function from the type cache */\n\ttce = lookup_type_cache(atttype, TYPECACHE_CMP_PROC | TYPECACHE_CMP_PROC_FINFO);\n\n\tif (NULL == tce || !OidIsValid(tce->cmp_proc))\n\t\telog(ERROR, \"no comparison function for type %u\", atttype);\n\n\tPushActiveSnapshot(GetTransactionSnapshot());\n\tscan = table_beginscan(rel, GetActiveSnapshot(), 0, NULL);\n\n\twhile (table_scan_getnextslot(scan, ForwardScanDirection, slot))\n\t{\n\t\tbool isnull;\n\t\tDatum value = slot_getattr(slot, attnum, &isnull);\n\n\t\tif (isnull)\n\t\t\tcontinue;\n\n\t\t/* Check for new min */\n\t\tif (nulls[0] || DatumGetInt32(FunctionCall2(&tce->cmp_proc_finfo, value, minmax[0])) < 0)\n\t\t{\n\t\t\tnulls[0] = false;\n\t\t\tminmax[0] = value;\n\t\t}\n\n\t\t/* Check for new max */\n\t\tif (nulls[1] || DatumGetInt32(FunctionCall2(&tce->cmp_proc_finfo, value, minmax[1])) > 0)\n\t\t{\n\t\t\tnulls[1] = false;\n\t\t\tminmax[1] = value;\n\t\t}\n\t}\n\n\ttable_endscan(scan);\n\tExecDropSingleTupleTableSlot(slot);\n\tPopActiveSnapshot();\n\n\treturn (nulls[0] || nulls[1]) ? MINMAX_NO_TUPLES : MINMAX_FOUND;\n}\n\n/*\n * Use an index scan to find the min and max of a given column of a chunk.\n */\nstatic MinMaxResult\nminmax_indexscan(Relation rel, Relation idxrel, AttrNumber attnum, Datum minmax[2])\n{\n\tPushActiveSnapshot(GetTransactionSnapshot());\n\tIndexScanDesc scan = index_beginscan_compat(rel, idxrel, GetActiveSnapshot(), NULL, 0, 0);\n\tTupleTableSlot *slot = table_slot_create(rel, NULL);\n\tbool nulls[2] = { true, true };\n\tint i;\n\tScanDirection directions[2] = { ForwardScanDirection /* min */,\n\t\t\t\t\t\t\t\t\tBackwardScanDirection /* max */ };\n\tint16 option = idxrel->rd_indoption[0];\n\tbool index_orderby_asc = ((option & INDOPTION_DESC) == 0);\n\n\t/* default index ordering is ASC, check if that's not the case */\n\tif (!index_orderby_asc)\n\t{\n\t\tdirections[0] = BackwardScanDirection;\n\t\tdirections[1] = ForwardScanDirection;\n\t}\n\n\tfor (i = 0; i < 2; i++)\n\t{\n\t\tbool found_tuple;\n\t\tbool isnull;\n\n\t\tindex_rescan(scan, NULL, 0, NULL, 0);\n\t\tfound_tuple = index_getnext_slot(scan, directions[i], slot);\n\n\t\tif (!found_tuple)\n\t\t\tbreak;\n\n\t\tminmax[i] = slot_getattr(slot, attnum, &isnull);\n\t\tnulls[i] = isnull;\n\t}\n\n\tindex_endscan(scan);\n\tExecDropSingleTupleTableSlot(slot);\n\tPopActiveSnapshot();\n\n\tAssert((nulls[0] && nulls[1]) || (!nulls[0] && !nulls[1]));\n\n\treturn nulls[0] ? MINMAX_NO_TUPLES : MINMAX_FOUND;\n}\n\n/*\n * Do a scan for min and max using and index on the given column.\n */\nstatic MinMaxResult\nrelation_minmax_indexscan(Relation rel, Oid atttype, Name attname, AttrNumber attnum,\n\t\t\t\t\t\t  Datum minmax[2])\n{\n\tList *indexlist = RelationGetIndexList(rel);\n\tListCell *lc;\n\tMinMaxResult res = MINMAX_NO_INDEX;\n\n\tforeach (lc, indexlist)\n\t{\n\t\tRelation idxrel;\n\t\tForm_pg_attribute idxattr;\n\n\t\tidxrel = index_open(lfirst_oid(lc), AccessShareLock);\n\t\tidxattr = TupleDescAttr(idxrel->rd_att, 0);\n\n\t\tif (idxattr->atttypid == atttype && namestrcmp(&idxattr->attname, NameStr(*attname)) == 0)\n\t\t\tres = minmax_indexscan(rel, idxrel, attnum, minmax);\n\n\t\tindex_close(idxrel, AccessShareLock);\n\n\t\tif (res == MINMAX_FOUND)\n\t\t\tbreak;\n\t}\n\n\treturn res;\n}\n\n/*\n * Determines if a table has an appropriate index for finding the minimum and\n * maximum time value. This would be an index whose first column is the same as\n * the column used for time partitioning.\n */\nstatic bool\ntable_has_minmax_index(Oid relid, Oid atttype, Name attname, AttrNumber attnum)\n{\n\tDatum minmax[2];\n\tRelation rel = table_open(relid, AccessShareLock);\n\tMinMaxResult res = relation_minmax_indexscan(rel, atttype, attname, attnum, minmax);\n\n\ttable_close(rel, AccessShareLock);\n\n\treturn res != MINMAX_NO_INDEX;\n}\n\n/*\n * Get the min and max value for a given column of a chunk.\n *\n * Returns true iff min and max is found, otherwise false.\n */\nstatic bool\nchunk_get_minmax(Oid relid, Oid atttype, AttrNumber attnum, const char *call_context,\n\t\t\t\t Datum minmax[2])\n{\n\tRelation rel = table_open(relid, AccessShareLock);\n\tNameData attname;\n\tMinMaxResult res;\n\n\tnamestrcpy(&attname, get_attname(relid, attnum, false));\n\tres = relation_minmax_indexscan(rel, atttype, &attname, attnum, minmax);\n\n\tif (res == MINMAX_NO_INDEX)\n\t{\n\t\tereport(WARNING,\n\t\t\t\t(errmsg(\"no index on \\\"%s\\\" found for %s on chunk \\\"%s\\\"\",\n\t\t\t\t\t\tNameStr(attname),\n\t\t\t\t\t\tcall_context,\n\t\t\t\t\t\tget_rel_name(relid)),\n\t\t\t\t errdetail(\"%s works best with an index on the dimension.\", call_context)));\n\n\t\tres = minmax_heapscan(rel, atttype, attnum, minmax);\n\t}\n\n\ttable_close(rel, AccessShareLock);\n\n\treturn res == MINMAX_FOUND;\n}\n\n#define CHUNK_SIZING_FUNC_NARGS 3\n#define DEFAULT_CHUNK_WINDOW 3\n\n/* Tuples must have filled this fraction of the chunk interval to use it to\n * estimate a new chunk time interval */\n#define INTERVAL_FILLFACTOR_THRESH 0.5\n/* A chunk must fill this (extrapolated) fraction of the target size to use it\n * to estimate a new chunk time interval.  */\n#define SIZE_FILLFACTOR_THRESH 0.15\n\n/* The calculated chunk time interval must differ this much to actually change\n * the interval */\n#define INTERVAL_MIN_CHANGE_THRESH 0.15\n\n/* More than this number of intervals must be undersized in order to use the\n * undersized calculation path */\n#define NUM_UNDERSIZED_INTERVALS 1\n\n/* Threshold to boost to if there are only undersized intervals to make\n * predictions from. This should be slightly above the SIZE_FILLFACTOR_THRESH\n * so that the next chunks made with this are likely to meet that threshold\n * and be used in normal prediction mode */\n#define UNDERSIZED_FILLFACTOR_THRESH (SIZE_FILLFACTOR_THRESH * 1.1)\n\nTS_FUNCTION_INFO_V1(ts_calculate_chunk_interval);\n\n/*\n * Calculate a new interval for a chunk in a given dimension.\n *\n * This function implements the main algorithm for adaptive chunking. Given a\n * dimension, coordinate (point) on the dimensional axis (e.g., point in time),\n * and a chunk target size (in bytes), the function should return a new\n * interval that best fills the chunk to the target size.\n *\n * The intuition behind the current implementation is to look back at the recent\n * past chunks in the dimension and look at how close they are to the target\n * size (the fillfactor) and then use that information to calculate a new\n * interval. I.e., if the fillfactor of a past chunk was below 1.0 we increase\n * the interval, and if it was above 1.0 we decrease it. Thus, for each past\n * chunk, we calculate the interval that would have filled the chunk to the\n * target size. Then, to calculate the new chunk interval, we average the\n * intervals of the past chunks.\n *\n * Note, however, that there are a couple of caveats. First, we cannot look back\n * at the most recently created chunks, because there is no guarantee that data\n * was written exactly in order of the dimension we are looking at. Therefore,\n * we \"look back\" along the dimension axis instead of by, e.g., chunk\n * ID. Second, chunks can be filled unevenly. Below are three examples of how\n * chunks can be filled ('*' represents data):\n *\n *' |--------|\n *' | * * * *|  1. Evenly filled (ideal)\n *' |--------|\n *\n *' |--------|\n *' |    ****|  2. Partially filled\n *' |--------|\n *\n *' |--------|\n *' |  * * **|  3. Unevenly filled\n *' |--------|\n *\n * Chunk (1) above represents the ideal situation. The chunk is evenly filled\n * across the entire chunk interval. This indicates a steady stream of data at\n * an even rate. Given the size and interval of this chunk, it would be\n * straightforward to calculate a new interval to hit a given target size.\n *\n * Chunk (2) has the same amount of data as (1), but it is reasonable to believe\n * that the following chunk will be fully filled with about twice the amount of\n * data. It is common for the first chunk in a hypertable to look like\n * this. Thus, to be able to use the first chunk for prediction, we compensate\n * by finding the MIN and MAX dimension values of the data in the chunk and then\n * use max-min (difference) as the interval instead of the chunk's actual\n * interval (i.e., since we are more interested in data rate/density we pretend\n * that this is a smaller chunk in terms of the given dimension.)\n *\n * Chunk (3) is probably a common real world scenario. We don't do anything\n * special to handle this case.\n *\n * We use a number of thresholds to avoid changing intervals\n * unnecessarily. I.e., if we are close to the target interval, we avoid\n * changing the interval since there might be a natural variance in the\n * fillfactor across chunks. This is intended to avoid flip-flopping or unstable\n * behavior.\n *\n * Additionally, two other thresholds govern much of the algorithm's behavior.\n * First is the SIZE_FILLFACTOR_THRESH, which is the minimum percentage of\n * the extrapolated size a chunk should fill to be used in computing a new\n * target size. We want a minimum so as to not overreact to a chunk that is too\n * small to get an accurate extrapolation from. For example, a chunk that is\n * only a percentage point or two of the extrapolated size (or less!) may not\n * contain enough data to give a true sense of the data rate, i.e., if it was\n * made in a particularly bursty or slow period.\n *\n * However, in the event that an initial chunk size was set\n * way too small, the algorithm will never adjust because\n * _all_ the chunks fall below this threshold. Therefore we have another\n * threshold -- NUM_UNDERSIZED_INTERVALS -- that helps our algorithm make\n * progress to the correct estimate. If there are _no_ chunks that\n * meet SIZE_FILLFACTOR_THRESH, and at least NUM_UNDERSIZED_INTERVALS chunks\n * we are sufficiently full, we use those chunks to adjust the target chunk\n * size so that the next chunks created at least meet SIZE_FILLFACTOR_THRESH.\n * This will then allow the algorithm to work in the normal way to adjust\n * further if needed.\n */\nDatum\nts_calculate_chunk_interval(PG_FUNCTION_ARGS)\n{\n\tint32 dimension_id = PG_GETARG_INT32(0);\n\tint64 dimension_coord = PG_GETARG_INT64(1);\n\tint64 chunk_target_size_bytes = PG_GETARG_INT64(2);\n\tint64 chunk_interval = 0;\n\tint64 undersized_intervals = 0;\n\tint64 current_interval;\n\tint32 hypertable_id;\n\tHypertable *ht;\n\tconst Dimension *dim;\n\tList *chunks = NIL;\n\tListCell *lc;\n\tint num_intervals = 0;\n\tint num_undersized_intervals = 0;\n\tdouble interval_diff;\n\tdouble undersized_fillfactor = 0.0;\n\tAclResult acl_result;\n\n\tif (PG_NARGS() != CHUNK_SIZING_FUNC_NARGS)\n\t\telog(ERROR, \"invalid number of arguments\");\n\n\tif (chunk_target_size_bytes < 0)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"chunk_target_size must be positive\")));\n\n\telog(DEBUG1, \"[adaptive] chunk_target_size_bytes=\" UINT64_FORMAT, chunk_target_size_bytes);\n\n\thypertable_id = ts_dimension_get_hypertable_id(dimension_id);\n\n\tif (hypertable_id <= 0)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t errmsg(\"could not find a matching hypertable for dimension %u\", dimension_id)));\n\n\tht = ts_hypertable_get_by_id(hypertable_id);\n\n\tAssert(ht != NULL);\n\n\tacl_result = pg_class_aclcheck(ht->main_table_relid, GetUserId(), ACL_SELECT);\n\tif (acl_result != ACLCHECK_OK)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),\n\t\t\t\t errmsg(\"permission denied for table %s\", NameStr(ht->fd.table_name))));\n\n\tdim = ts_hyperspace_get_dimension_by_id(ht->space, dimension_id);\n\n\tAssert(dim != NULL);\n\n\tcurrent_interval = dim->fd.interval_length;\n\n\t/* Get a window of recent chunks */\n\tchunks = ts_chunk_get_window(dimension_id,\n\t\t\t\t\t\t\t\t dimension_coord,\n\t\t\t\t\t\t\t\t DEFAULT_CHUNK_WINDOW,\n\t\t\t\t\t\t\t\t CurrentMemoryContext);\n\n\tforeach (lc, chunks)\n\t{\n\t\tChunk *chunk = lfirst(lc);\n\t\tconst DimensionSlice *slice =\n\t\t\tts_hypercube_get_slice_by_dimension_id(chunk->cube, dimension_id);\n\t\tint64 chunk_size, slice_interval;\n\t\tDatum minmax[2];\n\t\tAttrNumber attno = ts_map_attno(ht->main_table_relid, chunk->table_id, dim->column_attno);\n\n\t\tAssert(NULL != slice);\n\n\t\tchunk_size = DatumGetInt64(\n\t\t\tDirectFunctionCall1(pg_total_relation_size, ObjectIdGetDatum(chunk->table_id)));\n\n\t\tslice_interval = slice->fd.range_end - slice->fd.range_start;\n\n\t\tif (chunk_get_minmax(chunk->table_id,\n\t\t\t\t\t\t\t dim->fd.column_type,\n\t\t\t\t\t\t\t attno,\n\t\t\t\t\t\t\t \"adaptive chunking\",\n\t\t\t\t\t\t\t minmax))\n\t\t{\n\t\t\tint64 min = ts_time_value_to_internal(minmax[0], dim->fd.column_type);\n\t\t\tint64 max = ts_time_value_to_internal(minmax[1], dim->fd.column_type);\n\t\t\tdouble interval_fillfactor, size_fillfactor;\n\t\t\tint64 extrapolated_chunk_size;\n\n\t\t\t/*\n\t\t\t * The fillfactor of the slice interval that the data actually\n\t\t\t * spans\n\t\t\t */\n\t\t\tinterval_fillfactor = ((double) max - min) / slice_interval;\n\n\t\t\t/*\n\t\t\t * Extrapolate the size the chunk would have if it spanned the\n\t\t\t * entire interval\n\t\t\t */\n\t\t\textrapolated_chunk_size = chunk_size / interval_fillfactor;\n\t\t\tsize_fillfactor = ((double) extrapolated_chunk_size) / chunk_target_size_bytes;\n\n\t\t\telog(DEBUG2,\n\t\t\t\t \"[adaptive] slice_interval=\" UINT64_FORMAT \" interval_fillfactor=%lf\"\n\t\t\t\t \" current_chunk_size=\" UINT64_FORMAT \" extrapolated_chunk_size=\" UINT64_FORMAT\n\t\t\t\t \" size_fillfactor=%lf\",\n\t\t\t\t slice_interval,\n\t\t\t\t interval_fillfactor,\n\t\t\t\t chunk_size,\n\t\t\t\t extrapolated_chunk_size,\n\t\t\t\t size_fillfactor);\n\n\t\t\t/*\n\t\t\t * If the chunk is sufficiently filled with data and its\n\t\t\t * extrapolated size is large enough to make a good estimate, use\n\t\t\t * it\n\t\t\t */\n\t\t\tif (interval_fillfactor > INTERVAL_FILLFACTOR_THRESH &&\n\t\t\t\tsize_fillfactor > SIZE_FILLFACTOR_THRESH)\n\t\t\t{\n\t\t\t\tchunk_interval += (slice_interval / size_fillfactor);\n\t\t\t\tnum_intervals++;\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * If the chunk is sufficiently filled with data but its\n\t\t\t * extrapolated size is too small, track it and maybe use it if it\n\t\t\t * is all we have\n\t\t\t */\n\t\t\telse if (interval_fillfactor > INTERVAL_FILLFACTOR_THRESH)\n\t\t\t{\n\t\t\t\telog(DEBUG2,\n\t\t\t\t\t \"[adaptive] chunk sufficiently full, \"\n\t\t\t\t\t \"but undersized. may use for prediction.\");\n\t\t\t\tundersized_intervals += slice_interval;\n\t\t\t\tundersized_fillfactor += size_fillfactor;\n\t\t\t\tnum_undersized_intervals++;\n\t\t\t}\n\t\t}\n\t}\n\n\telog(DEBUG1,\n\t\t \"[adaptive] current interval=\" UINT64_FORMAT\n\t\t \" num_intervals=%d num_undersized_intervals=%d\",\n\t\t current_interval,\n\t\t num_intervals,\n\t\t num_undersized_intervals);\n\n\t/*\n\t * No full sized intervals, but enough undersized intervals to adjust\n\t * higher. We only want to do this if there are no sufficiently sized\n\t * intervals to use for a normal adjustment. This keeps us from getting\n\t * stuck with a really small interval size.\n\t */\n\tif (num_intervals == 0 && num_undersized_intervals > NUM_UNDERSIZED_INTERVALS)\n\t{\n\t\tdouble avg_fillfactor = undersized_fillfactor / num_undersized_intervals;\n\t\tdouble incr_factor = UNDERSIZED_FILLFACTOR_THRESH / avg_fillfactor;\n\t\tint64 avg_interval = undersized_intervals / num_undersized_intervals;\n\n\t\telog(DEBUG1,\n\t\t\t \"[adaptive] no sufficiently large intervals found, but \"\n\t\t\t \"some undersized ones found. increase interval to probe for better\"\n\t\t\t \" threshold. factor=%lf\",\n\t\t\t incr_factor);\n\t\tchunk_interval = (int64) (avg_interval * incr_factor);\n\t}\n\t/* No data & insufficient amount of undersized chunks, keep old interval */\n\telse if (num_intervals == 0)\n\t{\n\t\telog(DEBUG1,\n\t\t\t \"[adaptive] no sufficiently large intervals found, \"\n\t\t\t \"nor enough undersized chunks to estimate. \"\n\t\t\t \"use previous size of \" UINT64_FORMAT,\n\t\t\t current_interval);\n\t\tPG_RETURN_INT64(current_interval);\n\t}\n\telse\n\t\tchunk_interval /= num_intervals;\n\n\t/*\n\t * If the interval hasn't really changed much from before, we keep the old\n\t * interval to ensure we do not have fluctuating behavior around the\n\t * target size.\n\t */\n\tinterval_diff = fabs(1.0 - ((double) chunk_interval / current_interval));\n\n\tif (interval_diff <= INTERVAL_MIN_CHANGE_THRESH)\n\t{\n\t\telog(DEBUG1,\n\t\t\t \"[adaptive] calculated chunk interval=\" UINT64_FORMAT\n\t\t\t \", but is below change threshold, keeping old interval\",\n\t\t\t chunk_interval);\n\t\tchunk_interval = current_interval;\n\t}\n\telse\n\t{\n\t\telog(LOG,\n\t\t\t \"[adaptive] calculated chunk interval=\" UINT64_FORMAT\n\t\t\t \" for hypertable %d, making change\",\n\t\t\t chunk_interval,\n\t\t\t hypertable_id);\n\t}\n\n\tPG_RETURN_INT64(chunk_interval);\n}\n\n/*\n * Validate that the provided function in the catalog can be used for\n * determining a new chunk size, i.e., has form (int,bigint,bigint) -> bigint.\n *\n * Parameter 'info' will be updated with the function's information\n */\nvoid\nts_chunk_sizing_func_validate(regproc func, ChunkSizingInfo *info)\n{\n\tHeapTuple tuple;\n\tForm_pg_proc form;\n\tOid *typearr;\n\n\tif (!OidIsValid(func))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_FUNCTION), (errmsg(\"invalid chunk sizing function\"))));\n\n\ttuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(func));\n\n\tif (!HeapTupleIsValid(tuple))\n\t\telog(ERROR, \"cache lookup failed for function %u\", func);\n\n\tform = (Form_pg_proc) GETSTRUCT(tuple);\n\ttypearr = form->proargtypes.values;\n\n\tif (form->pronargs != CHUNK_SIZING_FUNC_NARGS || typearr[0] != INT4OID ||\n\t\ttypearr[1] != INT8OID || typearr[2] != INT8OID || form->prorettype != INT8OID)\n\t{\n\t\tReleaseSysCache(tuple);\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),\n\t\t\t\t errmsg(\"invalid function signature\"),\n\t\t\t\t errhint(\"A chunk sizing function's signature should be (int, bigint, bigint) -> \"\n\t\t\t\t\t\t \"bigint\")));\n\t}\n\n\tif (NULL != info)\n\t{\n\t\tinfo->func = func;\n\t\tnamestrcpy(&info->func_schema, get_namespace_name(form->pronamespace));\n\t\tnamestrcpy(&info->func_name, NameStr(form->proname));\n\t}\n\n\tReleaseSysCache(tuple);\n}\n\n/*\n * Parse the target size text into an integer amount of bytes.\n *\n * 'off' / 'disable' - returns a target of 0\n * 'estimate' - returns a target based on number of bytes in shared memory\n * 'XXMB' / etc - converts from PostgreSQL pretty text into number of bytes\n */\nstatic int64\nchunk_target_size_in_bytes(const text *target_size_text)\n{\n\tconst char *target_size = text_to_cstring(target_size_text);\n\tint64 target_size_bytes = 0;\n\n\tif (pg_strcasecmp(target_size, \"off\") == 0 || pg_strcasecmp(target_size, \"disable\") == 0)\n\t\treturn 0;\n\n\tif (pg_strcasecmp(target_size, \"estimate\") == 0)\n\t\ttarget_size_bytes = ts_chunk_calculate_initial_chunk_target_size();\n\telse\n\t\ttarget_size_bytes = convert_text_memory_amount_to_bytes(target_size);\n\n\t/* Disable if target size is zero or less */\n\tif (target_size_bytes <= 0)\n\t\ttarget_size_bytes = 0;\n\n\treturn target_size_bytes;\n}\n\n#define MB (1024 * 1024)\n\nvoid\nts_chunk_adaptive_sizing_info_validate(ChunkSizingInfo *info)\n{\n\tAttrNumber attnum;\n\tNameData attname;\n\tOid atttype;\n\n\tif (!OidIsValid(info->table_relid))\n\t\tereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), errmsg(\"table does not exist\")));\n\n\tts_hypertable_permissions_check(info->table_relid, GetUserId());\n\n\tif (NULL == info->colname)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_TS_DIMENSION_NOT_EXIST),\n\t\t\t\t errmsg(\"no open dimension found for adaptive chunking\")));\n\n\tattnum = get_attnum(info->table_relid, info->colname);\n\tnamestrcpy(&attname, info->colname);\n\tatttype = get_atttype(info->table_relid, attnum);\n\n\tif (!OidIsValid(atttype))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_COLUMN),\n\t\t\t\t errmsg(\"column \\\"%s\\\" does not exist\", info->colname)));\n\n\tts_chunk_sizing_func_validate(info->func, info);\n\n\tif (NULL == info->target_size)\n\t\tinfo->target_size_bytes = 0;\n\telse\n\t\tinfo->target_size_bytes = chunk_target_size_in_bytes(info->target_size);\n\n\t/* Don't validate further if disabled */\n\tif (info->target_size_bytes <= 0 || !OidIsValid(info->func))\n\t\treturn;\n\n\t/* Warn of small target sizes */\n\tif (info->target_size_bytes > 0 && info->target_size_bytes < (10 * MB))\n\t\telog(WARNING, \"target chunk size for adaptive chunking is less than 10 MB\");\n\n\tif (info->check_for_index &&\n\t\t!table_has_minmax_index(info->table_relid, atttype, &attname, attnum))\n\t\tereport(WARNING,\n\t\t\t\t(errmsg(\"no index on \\\"%s\\\" found for adaptive chunking on hypertable \\\"%s\\\"\",\n\t\t\t\t\t\tinfo->colname,\n\t\t\t\t\t\tget_rel_name(info->table_relid)),\n\t\t\t\t errdetail(\"Adaptive chunking works best with an index on the dimension being \"\n\t\t\t\t\t\t   \"adapted.\")));\n}\n\nTS_FUNCTION_INFO_V1(ts_chunk_adaptive_set);\n\n/*\n * Change the settings for adaptive chunking.\n */\nDatum\nts_chunk_adaptive_set(PG_FUNCTION_ARGS)\n{\n\tChunkSizingInfo info = {\n\t\t.table_relid = PG_GETARG_OID(0),\n\t\t.target_size = PG_ARGISNULL(1) ? NULL : PG_GETARG_TEXT_P(1),\n\t\t.func = PG_ARGISNULL(2) ? InvalidOid : PG_GETARG_OID(2),\n\t\t.colname = NULL,\n\t\t.check_for_index = true,\n\t};\n\tHypertable *ht;\n\tconst Dimension *dim;\n\tCache *hcache;\n\tHeapTuple tuple;\n\tTupleDesc tupdesc;\n\tDatum values[2];\n\tbool nulls[2] = { false, false };\n\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\tif (PG_ARGISNULL(0))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid hypertable: cannot be NULL\")));\n\n\tif (!OidIsValid(info.table_relid))\n\t\tereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), errmsg(\"table does not exist\")));\n\n\tts_hypertable_permissions_check(info.table_relid, GetUserId());\n\n\tht = ts_hypertable_cache_get_cache_and_entry(info.table_relid, CACHE_FLAG_NONE, &hcache);\n\n\t/* Get the first open dimension that we will adapt on */\n\tdim = ts_hyperspace_get_dimension(ht->space, DIMENSION_TYPE_OPEN, 0);\n\n\tif (NULL == dim)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_TS_DIMENSION_NOT_EXIST),\n\t\t\t\t errmsg(\"no open dimension found for adaptive chunking\")));\n\n\tinfo.colname = NameStr(dim->fd.column_name);\n\n\tts_chunk_adaptive_sizing_info_validate(&info);\n\n\tif (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)\n\t\telog(ERROR, \"function returning record called in context that cannot accept type record\");\n\n\ttupdesc = BlessTupleDesc(tupdesc);\n\n\tif (OidIsValid(info.func))\n\t{\n\t\tht->chunk_sizing_func = info.func;\n\t\tvalues[0] = ObjectIdGetDatum(info.func);\n\t}\n\telse if (OidIsValid(ht->chunk_sizing_func))\n\t{\n\t\tts_chunk_sizing_func_validate(ht->chunk_sizing_func, &info);\n\t\tvalues[0] = ObjectIdGetDatum(ht->chunk_sizing_func);\n\t}\n\telse\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg(\"invalid chunk sizing function\")));\n\n\tvalues[1] = Int64GetDatum(info.target_size_bytes);\n\n\t/* Update the hypertable entry */\n\tht->fd.chunk_target_size = info.target_size_bytes;\n\tts_hypertable_update_chunk_sizing(ht);\n\tts_cache_release(&hcache);\n\n\ttuple = heap_form_tuple(tupdesc, values, nulls);\n\n\tPG_RETURN_DATUM(HeapTupleGetDatum(tuple));\n}\n\nstatic Oid\nget_default_chunk_sizing_fn_oid()\n{\n\tOid chunkfnargtypes[] = { INT4OID, INT8OID, INT8OID };\n\tList *funcname =\n\t\tlist_make2(makeString(FUNCTIONS_SCHEMA_NAME), makeString(DEFAULT_CHUNK_SIZING_FN_NAME));\n\tint nargs = sizeof(chunkfnargtypes) / sizeof(chunkfnargtypes[0]);\n\tOid chunkfnoid = LookupFuncName(funcname, nargs, chunkfnargtypes, false);\n\treturn chunkfnoid;\n}\n\nChunkSizingInfo *\nts_chunk_sizing_info_get_default_disabled(Oid table_relid)\n{\n\tChunkSizingInfo *chunk_sizing_info = palloc(sizeof(*chunk_sizing_info));\n\t*chunk_sizing_info = (ChunkSizingInfo){\n\t\t.table_relid = table_relid,\n\t\t.target_size = NULL,\n\t\t.func = get_default_chunk_sizing_fn_oid(),\n\t\t.colname = NULL,\n\t\t.check_for_index = false,\n\t};\n\treturn chunk_sizing_info;\n}\n"
  },
  {
    "path": "src/chunk_adaptive.h",
    "content": "/*\n * NOTE: adaptive chunking is still in BETA\n */\n#pragma once\n\n#include <postgres.h>\n\n#include \"export.h\"\n\ntypedef struct ChunkSizingInfo\n{\n\tOid table_relid;\n\t/* Set manually */\n\tOid func;\n\ttext *target_size;\n\tconst char *colname;  /* The column of the dimension we are adapting\n\t\t\t\t\t\t   * on */\n\tbool check_for_index; /* Set if we should check for an index on\n\t\t\t\t\t\t   * the dimension we are adapting on */\n\n\t/* Validated info */\n\tNameData func_name;\n\tNameData func_schema;\n\tint64 target_size_bytes;\n} ChunkSizingInfo;\n\nextern void ts_chunk_adaptive_sizing_info_validate(ChunkSizingInfo *info);\nextern void ts_chunk_sizing_func_validate(regproc func, ChunkSizingInfo *info);\nextern TSDLLEXPORT ChunkSizingInfo *ts_chunk_sizing_info_get_default_disabled(Oid table_relid);\n\nextern TSDLLEXPORT int64 ts_chunk_calculate_initial_chunk_target_size(void);\n"
  },
  {
    "path": "src/chunk_constraint.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/heapam.h>\n#include <access/xact.h>\n#include <catalog/dependency.h>\n#include <catalog/heap.h>\n#include <catalog/indexing.h>\n#include <catalog/objectaddress.h>\n#include <catalog/pg_constraint.h>\n#include <commands/tablecmds.h>\n#include <funcapi.h>\n#include <nodes/makefuncs.h>\n#include <storage/lockdefs.h>\n#include <utils/builtins.h>\n#include <utils/hsearch.h>\n#include <utils/lsyscache.h>\n#include <utils/palloc.h>\n#include <utils/rel.h>\n#include <utils/relcache.h>\n#include <utils/snapmgr.h>\n#include <utils/syscache.h>\n\n#include \"compat/compat.h\"\n#include \"chunk.h\"\n#include \"chunk_constraint.h\"\n#include \"chunk_index.h\"\n#include \"constraint.h\"\n#include \"debug_assert.h\"\n#include \"dimension_slice.h\"\n#include \"dimension_vector.h\"\n#include \"errors.h\"\n#include \"export.h\"\n#include \"foreign_key.h\"\n#include \"hypercube.h\"\n#include \"hypertable.h\"\n#include \"partitioning.h\"\n#include \"process_utility.h\"\n#include \"scan_iterator.h\"\n#include \"scanner.h\"\n\n#define DEFAULT_EXTRA_CONSTRAINTS_SIZE 4\n\n#define CHUNK_CONSTRAINTS_SIZE(num_constraints) (sizeof(ChunkConstraint) * (num_constraints))\n\nChunkConstraints *\nts_chunk_constraints_alloc(int size_hint, MemoryContext mctx)\n{\n\tChunkConstraints *ccs = MemoryContextAlloc(mctx, sizeof(ChunkConstraints));\n\n\tccs->mctx = mctx;\n\tccs->capacity = size_hint + DEFAULT_EXTRA_CONSTRAINTS_SIZE;\n\tccs->num_constraints = 0;\n\tccs->num_dimension_constraints = 0;\n\tccs->constraints = MemoryContextAllocZero(mctx, CHUNK_CONSTRAINTS_SIZE(ccs->capacity));\n\n\treturn ccs;\n}\n\nChunkConstraints *\nts_chunk_constraints_copy(ChunkConstraints *chunk_constraints)\n{\n\tChunkConstraints *copy = palloc(sizeof(ChunkConstraints));\n\n\tmemcpy(copy, chunk_constraints, sizeof(ChunkConstraints));\n\tcopy->constraints = palloc0(CHUNK_CONSTRAINTS_SIZE(chunk_constraints->capacity));\n\tmemcpy(copy->constraints,\n\t\t   chunk_constraints->constraints,\n\t\t   CHUNK_CONSTRAINTS_SIZE(chunk_constraints->num_constraints));\n\n\treturn copy;\n}\n\nstatic void\nchunk_constraints_expand(ChunkConstraints *ccs, int16 new_capacity)\n{\n\tMemoryContext old;\n\n\tif (new_capacity <= ccs->capacity)\n\t\treturn;\n\n\told = MemoryContextSwitchTo(ccs->mctx);\n\tccs->capacity = new_capacity;\n\tAssert(ccs->constraints); /* repalloc() does not work with NULL argument */\n\tccs->constraints = repalloc(ccs->constraints, CHUNK_CONSTRAINTS_SIZE(new_capacity));\n\tMemoryContextSwitchTo(old);\n}\n\nstatic void\nchunk_constraint_dimension_choose_name(Name dst, int32 dimension_slice_id)\n{\n\tsnprintf(NameStr(*dst), NAMEDATALEN, \"constraint_%d\", dimension_slice_id);\n}\n\nstatic void\nchunk_constraint_choose_name(Name dst, const char *hypertable_constraint_name, int32 chunk_id)\n{\n\tchar constrname[NAMEDATALEN];\n\tCatalogSecurityContext sec_ctx;\n\n\tAssert(hypertable_constraint_name != NULL);\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tsnprintf(constrname,\n\t\t\t NAMEDATALEN,\n\t\t\t \"%d_\" INT64_FORMAT \"_%s\",\n\t\t\t chunk_id,\n\t\t\t ts_catalog_table_next_seq_id(ts_catalog_get(), CHUNK_CONSTRAINT),\n\t\t\t hypertable_constraint_name);\n\tts_catalog_restore_user(&sec_ctx);\n\n\tnamestrcpy(dst, constrname);\n}\n\nChunkConstraint *\nts_chunk_constraints_add(ChunkConstraints *ccs, int32 chunk_id, int32 dimension_slice_id,\n\t\t\t\t\t\t const char *constraint_name, const char *hypertable_constraint_name)\n{\n\tChunkConstraint *cc;\n\n\tchunk_constraints_expand(ccs, ccs->num_constraints + 1);\n\tcc = &ccs->constraints[ccs->num_constraints++];\n\tcc->fd.chunk_id = chunk_id;\n\tcc->fd.dimension_slice_id = dimension_slice_id;\n\n\tif (NULL == constraint_name)\n\t{\n\t\tif (is_dimension_constraint(cc))\n\t\t{\n\t\t\tchunk_constraint_dimension_choose_name(&cc->fd.constraint_name,\n\t\t\t\t\t\t\t\t\t\t\t\t   cc->fd.dimension_slice_id);\n\t\t\tnamestrcpy(&cc->fd.hypertable_constraint_name, \"\");\n\t\t}\n\t\telse\n\t\t\tchunk_constraint_choose_name(&cc->fd.constraint_name,\n\t\t\t\t\t\t\t\t\t\t hypertable_constraint_name,\n\t\t\t\t\t\t\t\t\t\t cc->fd.chunk_id);\n\t}\n\telse\n\t\tnamestrcpy(&cc->fd.constraint_name, constraint_name);\n\n\tif (NULL != hypertable_constraint_name)\n\t\tnamestrcpy(&cc->fd.hypertable_constraint_name, hypertable_constraint_name);\n\n\tif (is_dimension_constraint(cc))\n\t\tccs->num_dimension_constraints++;\n\n\treturn cc;\n}\n\nstatic void\nchunk_constraint_fill_tuple_values(const ChunkConstraint *cc, Datum values[Natts_chunk_constraint],\n\t\t\t\t\t\t\t\t   bool nulls[Natts_chunk_constraint])\n{\n\tmemset(values, 0, sizeof(Datum) * Natts_chunk_constraint);\n\tvalues[AttrNumberGetAttrOffset(Anum_chunk_constraint_chunk_id)] =\n\t\tInt32GetDatum(cc->fd.chunk_id);\n\tvalues[AttrNumberGetAttrOffset(Anum_chunk_constraint_dimension_slice_id)] =\n\t\tInt32GetDatum(cc->fd.dimension_slice_id);\n\tvalues[AttrNumberGetAttrOffset(Anum_chunk_constraint_constraint_name)] =\n\t\tNameGetDatum(&cc->fd.constraint_name);\n\tvalues[AttrNumberGetAttrOffset(Anum_chunk_constraint_hypertable_constraint_name)] =\n\t\tNameGetDatum(&cc->fd.hypertable_constraint_name);\n\n\tif (is_dimension_constraint(cc))\n\t\tnulls[AttrNumberGetAttrOffset(Anum_chunk_constraint_hypertable_constraint_name)] = true;\n\telse\n\t\tnulls[AttrNumberGetAttrOffset(Anum_chunk_constraint_dimension_slice_id)] = true;\n}\n\nstatic void\nchunk_constraint_insert_relation(const Relation rel, const ChunkConstraint *cc)\n{\n\tTupleDesc desc = RelationGetDescr(rel);\n\tDatum values[Natts_chunk_constraint];\n\tbool nulls[Natts_chunk_constraint] = { false };\n\n\tchunk_constraint_fill_tuple_values(cc, values, nulls);\n\tts_catalog_insert_values(rel, desc, values, nulls);\n}\n\n/*\n * Insert multiple chunk constraints into the metadata catalog.\n */\nvoid\nts_chunk_constraints_insert_metadata(const ChunkConstraints *ccs)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tCatalogSecurityContext sec_ctx;\n\tRelation rel;\n\tint i;\n\n\trel = table_open(catalog_get_table_id(catalog, CHUNK_CONSTRAINT), RowExclusiveLock);\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\n\tfor (i = 0; i < ccs->num_constraints; i++)\n\t\tchunk_constraint_insert_relation(rel, &ccs->constraints[i]);\n\n\tts_catalog_restore_user(&sec_ctx);\n\ttable_close(rel, RowExclusiveLock);\n}\n\n/*\n * Insert a single chunk constraints into the metadata catalog.\n */\nvoid\nts_chunk_constraint_insert(ChunkConstraint *constraint)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tCatalogSecurityContext sec_ctx;\n\tRelation rel;\n\n\trel = table_open(catalog_get_table_id(catalog, CHUNK_CONSTRAINT), RowExclusiveLock);\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tchunk_constraint_insert_relation(rel, constraint);\n\tts_catalog_restore_user(&sec_ctx);\n\ttable_close(rel, RowExclusiveLock);\n}\n\nChunkConstraint *\nts_chunk_constraints_add_from_tuple(ChunkConstraints *ccs, const TupleInfo *ti)\n{\n\tbool nulls[Natts_chunk_constraint];\n\tDatum values[Natts_chunk_constraint];\n\tChunkConstraint *constraints;\n\tint32 dimension_slice_id;\n\tName constraint_name;\n\tName hypertable_constraint_name;\n\tbool should_free;\n\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\tMemoryContext oldcxt;\n\n\theap_deform_tuple(tuple, ts_scanner_get_tupledesc(ti), values, nulls);\n\n\toldcxt = MemoryContextSwitchTo(ccs->mctx);\n\n\tconstraint_name =\n\t\tDatumGetName(values[AttrNumberGetAttrOffset(Anum_chunk_constraint_constraint_name)]);\n\n\tif (nulls[AttrNumberGetAttrOffset(Anum_chunk_constraint_dimension_slice_id)])\n\t{\n\t\tdimension_slice_id = 0;\n\t\thypertable_constraint_name = DatumGetName(\n\t\t\tvalues[AttrNumberGetAttrOffset(Anum_chunk_constraint_hypertable_constraint_name)]);\n\t}\n\telse\n\t{\n\t\tdimension_slice_id = DatumGetInt32(\n\t\t\tvalues[AttrNumberGetAttrOffset(Anum_chunk_constraint_dimension_slice_id)]);\n\t\thypertable_constraint_name = DatumGetName(DirectFunctionCall1(namein, CStringGetDatum(\"\")));\n\t}\n\n\tconstraints = ts_chunk_constraints_add(ccs,\n\t\t\t\t\t\t\t\t\t\t   DatumGetInt32(values[AttrNumberGetAttrOffset(\n\t\t\t\t\t\t\t\t\t\t\t   Anum_chunk_constraint_chunk_id)]),\n\t\t\t\t\t\t\t\t\t\t   dimension_slice_id,\n\t\t\t\t\t\t\t\t\t\t   NameStr(*constraint_name),\n\t\t\t\t\t\t\t\t\t\t   NameStr(*hypertable_constraint_name));\n\n\tMemoryContextSwitchTo(oldcxt);\n\n\tif (should_free)\n\t\theap_freetuple(tuple);\n\n\treturn constraints;\n}\n\n/*\n * Create a dimensional CHECK constraint for a partitioning dimension.\n */\nConstraint *\nts_chunk_constraint_dimensional_create(const Dimension *dim, const DimensionSlice *slice,\n\t\t\t\t\t\t\t\t\t   const char *name)\n{\n\tConstraint *constr = NULL;\n\tNode *dimdef;\n\tColumnRef *colref;\n\tList *compexprs = NIL;\n\tOid type;\n\n\tif (slice->fd.range_start == PG_INT64_MIN && slice->fd.range_end == PG_INT64_MAX)\n\t\treturn NULL;\n\n\tcolref = makeNode(ColumnRef);\n\tcolref->fields = list_make1(makeString(pstrdup(NameStr(dim->fd.column_name))));\n\tcolref->location = -1;\n\n\t/* Convert the dimensional ranges to the appropriate text/string\n\t * representation for the time type. For dimensions with a\n\t * partitioning/time function, use the function's output type. */\n\tif (dim->partitioning != NULL)\n\t{\n\t\t/* Both open and closed dimensions can have a partitioning function */\n\t\tPartitioningInfo *partinfo = dim->partitioning;\n\t\tList *funcname = list_make2(makeString(NameStr(partinfo->partfunc.schema)),\n\t\t\t\t\t\t\t\t\tmakeString(NameStr(partinfo->partfunc.name)));\n\t\tdimdef = (Node *) makeFuncCall(funcname, list_make1(colref), COERCE_EXPLICIT_CALL, -1);\n\n\t\tif (IS_OPEN_DIMENSION(dim))\n\t\t{\n\t\t\t/* The dimension has a time function to compute the time value so\n\t\t\t * need to convert the range values to the time type returned by\n\t\t\t * the partitioning function. */\n\t\t\ttype = partinfo->partfunc.rettype;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* Closed dimension, just use the INT8 type */\n\t\t\ttype = INT8OID;\n\t\t}\n\t}\n\telse\n\t{\n\t\t/* Must be open dimension, since no partitioning function */\n\t\tAssert(IS_OPEN_DIMENSION(dim));\n\n\t\tdimdef = (Node *) colref;\n\t\ttype = dim->fd.column_type;\n\t}\n\n\t/*\n\t * We are forcing ISO datestyle here to prevent parsing errors with\n\t * certain timezone/datestyle combinations.\n\t */\n\tint current_datestyle = DateStyle;\n\tDateStyle = USE_ISO_DATES;\n\tchar *start_str = ts_internal_to_time_string(slice->fd.range_start, type);\n\tchar *end_str = ts_internal_to_time_string(slice->fd.range_end, type);\n\tDateStyle = current_datestyle;\n\n\t/* Elide range constraint for +INF or -INF */\n\tif (slice->fd.range_start != PG_INT64_MIN)\n\t{\n\t\tA_Const *start_const = makeNode(A_Const);\n\t\tmemcpy(&start_const->val, makeString(start_str), sizeof(start_const->val));\n\t\tstart_const->location = -1;\n\t\tA_Expr *ge_expr = makeSimpleA_Expr(AEXPR_OP, \">=\", dimdef, (Node *) start_const, -1);\n\t\tcompexprs = lappend(compexprs, ge_expr);\n\t}\n\n\tif (slice->fd.range_end != PG_INT64_MAX)\n\t{\n\t\tA_Const *end_const = makeNode(A_Const);\n\t\tmemcpy(&end_const->val, makeString(end_str), sizeof(end_const->val));\n\t\tend_const->location = -1;\n\t\tA_Expr *lt_expr = makeSimpleA_Expr(AEXPR_OP, \"<\", dimdef, (Node *) end_const, -1);\n\t\tcompexprs = lappend(compexprs, lt_expr);\n\t}\n\n\tconstr = makeNode(Constraint);\n\tconstr->contype = CONSTR_CHECK;\n\tconstr->conname = name ? pstrdup(name) : NULL;\n\tconstr->deferrable = false;\n\tconstr->skip_validation = true;\n\tconstr->initially_valid = true;\n#if PG18_GE\n\tconstr->is_enforced = true;\n#endif\n\n\tAssert(list_length(compexprs) >= 1);\n\n\tif (list_length(compexprs) == 2)\n\t\tconstr->raw_expr = (Node *) makeBoolExpr(AND_EXPR, compexprs, -1);\n\telse if (list_length(compexprs) == 1)\n\t\tconstr->raw_expr = linitial(compexprs);\n\n\treturn constr;\n}\n\n/*\n * Add a constraint to a chunk table.\n */\nstatic Oid\nchunk_constraint_create_on_table(const ChunkConstraint *cc, Oid chunk_oid)\n{\n\tHeapTuple tuple;\n\tDatum values[Natts_chunk_constraint];\n\tbool nulls[Natts_chunk_constraint] = { false };\n\tCatalogSecurityContext sec_ctx;\n\tRelation rel;\n\n\tchunk_constraint_fill_tuple_values(cc, values, nulls);\n\n\trel = RelationIdGetRelation(catalog_get_table_id(ts_catalog_get(), CHUNK_CONSTRAINT));\n\ttuple = heap_form_tuple(RelationGetDescr(rel), values, nulls);\n\tRelationClose(rel);\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tCatalogInternalCall1(DDL_ADD_CHUNK_CONSTRAINT, HeapTupleGetDatum(tuple));\n\tts_catalog_restore_user(&sec_ctx);\n\theap_freetuple(tuple);\n\n\treturn get_relation_constraint_oid(chunk_oid, NameStr(cc->fd.constraint_name), true);\n}\n\n/*\n * Create a non-dimensional constraint on a chunk table (foreign key, trigger\n * constraint, etc.), including adding relevant metadata to the catalog.\n */\nstatic Oid\ncreate_non_dimensional_constraint(const ChunkConstraint *cc, Oid chunk_oid, int32 chunk_id,\n\t\t\t\t\t\t\t\t  Oid hypertable_oid, int32 hypertable_id)\n{\n\tOid chunk_constraint_oid;\n\n\tAssert(!is_dimension_constraint(cc));\n\n\t/*\n\t * If we're creating constraints for a new chunk from an existing\n\t * table or attaching a chunk to an existing hypertable, we might\n\t * have constraints already created. If so, skip creating\n\t * such constraints, we only needed their metadata to be added.\n\t */\n\tif (ConstraintNameIsUsed(CONSTRAINT_RELATION, chunk_oid, NameStr(cc->fd.constraint_name)))\n\t{\n\t\tchunk_constraint_oid =\n\t\t\tget_relation_constraint_oid(chunk_oid, NameStr(cc->fd.constraint_name), true);\n\t}\n\telse\n\t{\n\t\tts_process_utility_set_expect_chunk_modification(true);\n\t\tchunk_constraint_oid = chunk_constraint_create_on_table(cc, chunk_oid);\n\t\tts_process_utility_set_expect_chunk_modification(false);\n\t}\n\n\treturn chunk_constraint_oid;\n}\n\nstatic const DimensionSlice *\nget_slice_with_id(const Hypercube *cube, int32 id)\n{\n\tint i;\n\n\tfor (i = 0; i < cube->num_slices; i++)\n\t{\n\t\tconst DimensionSlice *slice = cube->slices[i];\n\n\t\tif (slice->fd.id == id)\n\t\t\treturn slice;\n\t}\n\n\treturn NULL;\n}\n\n/*\n * Create a set of constraints on a chunk table.\n */\nvoid\nts_chunk_constraints_create(const Hypertable *ht, const Chunk *chunk)\n{\n\tconst ChunkConstraints *ccs = chunk->constraints;\n\tList *newconstrs = NIL;\n\tint i;\n\n\tfor (i = 0; i < ccs->num_constraints; i++)\n\t{\n\t\tconst ChunkConstraint *cc = &ccs->constraints[i];\n\n\t\tif (is_dimension_constraint(cc))\n\t\t{\n\t\t\tconst DimensionSlice *slice = get_slice_with_id(chunk->cube, cc->fd.dimension_slice_id);\n\t\t\tconst Dimension *dim;\n\t\t\tConstraint *constr;\n\n\t\t\tdim = ts_hyperspace_get_dimension_by_id(ht->space, slice->fd.dimension_id);\n\t\t\tAssert(dim);\n\n\t\t\tconstr =\n\t\t\t\tts_chunk_constraint_dimensional_create(dim, slice, NameStr(cc->fd.constraint_name));\n\n\t\t\t/* In some cases, a CHECK constraint is not needed. For instance,\n\t\t\t * if the range is -INF to +INF. */\n\t\t\tif (constr != NULL)\n\t\t\t\tnewconstrs = lappend(newconstrs, constr);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tcreate_non_dimensional_constraint(cc,\n\t\t\t\t\t\t\t\t\t\t\t  chunk->table_id,\n\t\t\t\t\t\t\t\t\t\t\t  chunk->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t  ht->main_table_relid,\n\t\t\t\t\t\t\t\t\t\t\t  ht->fd.id);\n\t\t}\n\t}\n\n\tif (newconstrs != NIL)\n\t{\n\t\tList PG_USED_FOR_ASSERTS_ONLY *cookedconstrs = NIL;\n\t\tRelation rel = table_open(chunk->table_id, AccessExclusiveLock);\n\t\tcookedconstrs = AddRelationNewConstraints(rel,\n\t\t\t\t\t\t\t\t\t\t\t\t  NIL /* List *newColDefaults */,\n\t\t\t\t\t\t\t\t\t\t\t\t  newconstrs,\n\t\t\t\t\t\t\t\t\t\t\t\t  false /* allow_merge */,\n\t\t\t\t\t\t\t\t\t\t\t\t  true /* is_local */,\n\t\t\t\t\t\t\t\t\t\t\t\t  false /* is_internal */,\n\t\t\t\t\t\t\t\t\t\t\t\t  NULL /* query string */);\n\t\ttable_close(rel, NoLock);\n\t\tAssert(list_length(cookedconstrs) == list_length(newconstrs));\n\t\tCommandCounterIncrement();\n\t}\n}\n\nScanIterator\nts_chunk_constraint_scan_iterator_create(MemoryContext result_mcxt)\n{\n\tScanIterator it = ts_scan_iterator_create(CHUNK_CONSTRAINT, AccessShareLock, result_mcxt);\n\tit.ctx.flags |= SCANNER_F_NOEND_AND_NOCLOSE;\n\n\treturn it;\n}\n\nvoid\nts_chunk_constraint_scan_iterator_set_slice_id(ScanIterator *it, int32 slice_id)\n{\n\tit->ctx.index = catalog_get_index(ts_catalog_get(),\n\t\t\t\t\t\t\t\t\t  CHUNK_CONSTRAINT,\n\t\t\t\t\t\t\t\t\t  CHUNK_CONSTRAINT_DIMENSION_SLICE_ID_IDX);\n\tts_scan_iterator_scan_key_reset(it);\n\tts_scan_iterator_scan_key_init(it,\n\t\t\t\t\t\t\t\t   Anum_chunk_constraint_dimension_slice_id_idx_dimension_slice_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(slice_id));\n}\n\nvoid\nts_chunk_constraint_scan_iterator_set_chunk_id(ScanIterator *it, int32 chunk_id)\n{\n\tit->ctx.index = catalog_get_index(ts_catalog_get(),\n\t\t\t\t\t\t\t\t\t  CHUNK_CONSTRAINT,\n\t\t\t\t\t\t\t\t\t  CHUNK_CONSTRAINT_CHUNK_ID_CONSTRAINT_NAME_IDX);\n\tts_scan_iterator_scan_key_reset(it);\n\tts_scan_iterator_scan_key_init(it,\n\t\t\t\t\t\t\t\t   Anum_chunk_constraint_chunk_id_constraint_name_idx_chunk_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(chunk_id));\n}\n\nstatic void\ninit_scan_by_chunk_id_constraint_name(ScanIterator *iterator, int32 chunk_id,\n\t\t\t\t\t\t\t\t\t  const char *constraint_name)\n{\n\titerator->ctx.index = catalog_get_index(ts_catalog_get(),\n\t\t\t\t\t\t\t\t\t\t\tCHUNK_CONSTRAINT,\n\t\t\t\t\t\t\t\t\t\t\tCHUNK_CONSTRAINT_CHUNK_ID_CONSTRAINT_NAME_IDX);\n\n\tts_scan_iterator_scan_key_reset(iterator);\n\tts_scan_iterator_scan_key_init(iterator,\n\t\t\t\t\t\t\t\t   Anum_chunk_constraint_chunk_id_constraint_name_idx_chunk_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(chunk_id));\n\n\tts_scan_iterator_scan_key_init(\n\t\titerator,\n\t\tAnum_chunk_constraint_chunk_id_constraint_name_idx_constraint_name,\n\t\tBTEqualStrategyNumber,\n\t\tF_NAMEEQ,\n\t\tCStringGetDatum(constraint_name));\n}\n\n/*\n * Scan all the chunk's constraints given its chunk ID.\n *\n * Returns a set of chunk constraints.\n */\nChunkConstraints *\nts_chunk_constraint_scan_by_chunk_id(int32 chunk_id, Size num_constraints_hint, MemoryContext mctx)\n{\n\tChunkConstraints *constraints = ts_chunk_constraints_alloc(num_constraints_hint, mctx);\n\tScanIterator iterator = ts_scan_iterator_create(CHUNK_CONSTRAINT, AccessShareLock, mctx);\n\tint num_found = 0;\n\n\tts_chunk_constraint_scan_iterator_set_chunk_id(&iterator, chunk_id);\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tnum_found++;\n\t\tts_chunk_constraints_add_from_tuple(constraints, ts_scan_iterator_tuple_info(&iterator));\n\t}\n\n\tif (num_found != constraints->num_constraints)\n\t\telog(ERROR, \"unexpected number of constraints found for chunk ID %d\", chunk_id);\n\n\treturn constraints;\n}\n\n/*\n * Scan for all chunk constraints that match the given slice ID. The chunk\n * constraints are saved in the chunk scan context.\n */\nint\nts_chunk_constraint_scan_by_dimension_slice(const DimensionSlice *slice, ChunkScanCtx *ctx,\n\t\t\t\t\t\t\t\t\t\t\tMemoryContext mctx)\n{\n\tScanIterator iterator = ts_scan_iterator_create(CHUNK_CONSTRAINT, AccessShareLock, mctx);\n\tint count = 0;\n\n\tts_chunk_constraint_scan_iterator_set_slice_id(&iterator, slice->fd.id);\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tconst Hyperspace *hs = ctx->ht->space;\n\t\tChunkStub *stub;\n\t\tChunkScanEntry *entry;\n\t\tbool found;\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tDatum datum = slot_getattr(ti->slot, Anum_chunk_constraint_chunk_id, &found);\n\t\tint32 chunk_id = DatumGetInt32(datum);\n\n\t\tif (slot_attisnull(ts_scan_iterator_slot(&iterator),\n\t\t\t\t\t\t   Anum_chunk_constraint_dimension_slice_id))\n\t\t\tcontinue;\n\n\t\tcount++;\n\n\t\tAssert(!slot_attisnull(ti->slot, Anum_chunk_constraint_dimension_slice_id));\n\n\t\tentry = hash_search(ctx->htab, &chunk_id, HASH_ENTER, &found);\n\n\t\tif (!found)\n\t\t{\n\t\t\tstub = ts_chunk_stub_create(chunk_id, hs->num_dimensions);\n\t\t\tstub->cube = ts_hypercube_alloc(hs->num_dimensions);\n\t\t\tentry->stub = stub;\n\t\t}\n\t\telse\n\t\t\tstub = entry->stub;\n\n\t\tts_chunk_constraints_add_from_tuple(stub->constraints, ti);\n\n\t\tts_hypercube_add_slice(stub->cube, slice);\n\n\t\t/* A stub is complete when we've added slices for all its dimensions,\n\t\t * i.e., a complete hypercube */\n\t\tif (chunk_stub_is_complete(stub, ctx->ht->space))\n\t\t{\n\t\t\tctx->num_complete_chunks++;\n\n\t\t\tif (ctx->early_abort)\n\t\t\t{\n\t\t\t\tts_scan_iterator_close(&iterator);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\treturn count;\n}\n\n/*\n * Similar to chunk_constraint_scan_by_dimension_slice, but stores only chunk_ids\n * in a list, which is easier to traverse and provides deterministic chunk selection.\n */\nint\nts_chunk_constraint_scan_by_dimension_slice_to_list(const DimensionSlice *slice, List **list,\n\t\t\t\t\t\t\t\t\t\t\t\t\tMemoryContext mctx)\n{\n\tScanIterator iterator = ts_scan_iterator_create(CHUNK_CONSTRAINT, AccessShareLock, mctx);\n\tint count = 0;\n\n\tts_chunk_constraint_scan_iterator_set_slice_id(&iterator, slice->fd.id);\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tbool is_null;\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tDatum chunk_id;\n\n\t\tif (slot_attisnull(ti->slot, Anum_chunk_constraint_dimension_slice_id))\n\t\t\tcontinue;\n\n\t\tcount++;\n\t\tchunk_id = slot_getattr(ti->slot, Anum_chunk_constraint_chunk_id, &is_null);\n\t\tAssert(!is_null);\n\t\t*list = lappend_int(*list, DatumGetInt32(chunk_id));\n\t}\n\treturn count;\n}\n\n/*\n * Scan for chunk constraints given a dimension slice ID.\n *\n * Optionally, collect all chunk constraints if ChunkConstraints is non-NULL.\n */\nint\nts_chunk_constraint_scan_by_dimension_slice_id(int32 dimension_slice_id, ChunkConstraints *ccs,\n\t\t\t\t\t\t\t\t\t\t\t   MemoryContext mctx)\n{\n\tScanIterator iterator = ts_scan_iterator_create(CHUNK_CONSTRAINT, AccessShareLock, mctx);\n\tint count = 0;\n\n\tts_chunk_constraint_scan_iterator_set_slice_id(&iterator, dimension_slice_id);\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tif (slot_attisnull(ts_scan_iterator_slot(&iterator),\n\t\t\t\t\t\t   Anum_chunk_constraint_dimension_slice_id))\n\t\t\tcontinue;\n\n\t\tcount++;\n\t\tif (ccs != NULL)\n\t\t\tts_chunk_constraints_add_from_tuple(ccs, ts_scan_iterator_tuple_info(&iterator));\n\t}\n\treturn count;\n}\n\nstatic bool\nchunk_constraint_need_on_chunk(Form_pg_constraint conform)\n{\n\tif (conform->contype == CONSTRAINT_CHECK\n#if PG18_GE\n\t\t/* Avoid NOT NULL constraints\n\t\t * https://github.com/postgres/postgres/commit/b0e96f31\n\t\t */\n\t\t|| conform->contype == CONSTRAINT_NOTNULL\n#endif\n\t)\n\t{\n\t\t/*\n\t\t * check and not null constraints handled by regular inheritance (from\n\t\t * docs): All check constraints and not-null constraints on a parent\n\t\t * table are automatically inherited by its children, unless\n\t\t * explicitly specified otherwise with NO INHERIT clauses. Other types\n\t\t * of constraints (unique, primary key, and foreign key constraints)\n\t\t * are not inherited.\"\n\t\t */\n\t\treturn false;\n\t}\n\t/*\n\t   Check if the foreign key constraint references a partition in a partitioned\n\t   table. In that case, we shouldn't include this constraint as we will end up\n\t   checking the foreign key constraint once for every partition, which obviously\n\t   leads to foreign key constraint violation. Instead, we only include constraints\n\t   referencing the parent table of the partitioned table.\n\t*/\n\tif (conform->contype == CONSTRAINT_FOREIGN && OidIsValid(conform->conparentid))\n\t\treturn false;\n\n\treturn true;\n}\n\nint\nts_chunk_constraints_add_dimension_constraints(ChunkConstraints *ccs, int32 chunk_id,\n\t\t\t\t\t\t\t\t\t\t\t   const Hypercube *cube)\n{\n\tint i;\n\n\tfor (i = 0; i < cube->num_slices; i++)\n\t\tts_chunk_constraints_add(ccs, chunk_id, cube->slices[i]->fd.id, NULL, NULL);\n\n\treturn cube->num_slices;\n}\n\ntypedef struct ConstraintContext\n{\n\tint num_added;\n\tchar chunk_relkind;\n\tChunkConstraints *ccs;\n\tint32 chunk_id;\n\tOid chunk_relid;\n} ConstraintContext;\n\nstatic ConstraintProcessStatus\nchunk_constraint_add(HeapTuple constraint_tuple, void *arg)\n{\n\tConstraintContext *cc = arg;\n\tForm_pg_constraint constraint = (Form_pg_constraint) GETSTRUCT(constraint_tuple);\n\n\tif (cc->chunk_relkind != RELKIND_FOREIGN_TABLE && chunk_constraint_need_on_chunk(constraint))\n\t{\n\t\t/* If the chunk already has an equivalent constraint, use the existing one. */\n\t\tRelation chunk = table_open(cc->chunk_relid, AccessShareLock);\n\t\tForm_pg_constraint matching_const = ts_constraint_find_matching(constraint_tuple, chunk);\n\t\ttable_close(chunk, NoLock);\n\n\t\tif (matching_const != NULL)\n\t\t\tts_chunk_constraints_add(cc->ccs,\n\t\t\t\t\t\t\t\t\t cc->chunk_id,\n\t\t\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t\t\t NameStr(matching_const->conname),\n\t\t\t\t\t\t\t\t\t NameStr(constraint->conname));\n\t\telse\n\t\t\tts_chunk_constraints_add(cc->ccs, cc->chunk_id, 0, NULL, NameStr(constraint->conname));\n\t\treturn CONSTR_PROCESSED;\n\t}\n\n\treturn CONSTR_IGNORED;\n}\n\nint\nts_chunk_constraints_add_inheritable_constraints(ChunkConstraints *ccs, int32 chunk_id,\n\t\t\t\t\t\t\t\t\t\t\t\t const char chunk_relkind, Oid hypertable_oid,\n\t\t\t\t\t\t\t\t\t\t\t\t Oid table_id)\n{\n\t/* This should never be called with NULL ccs.  */\n\tEnsure(ccs, \"ccs must not be NULL\");\n\n\tConstraintContext cc = {\n\t\t.chunk_relkind = chunk_relkind,\n\t\t.ccs = ccs,\n\t\t.chunk_id = chunk_id,\n\t\t.chunk_relid = table_id,\n\t};\n\n\treturn ts_constraint_process(hypertable_oid, chunk_constraint_add, &cc);\n}\n\n/* check constraints have the same name as the one on the hypertable */\nstatic ConstraintProcessStatus\nchunk_constraint_add_check(HeapTuple constraint_tuple, void *arg)\n{\n\tConstraintContext *cc = arg;\n\tForm_pg_constraint constraint = (Form_pg_constraint) GETSTRUCT(constraint_tuple);\n\n\tif (constraint->contype == CONSTRAINT_CHECK)\n\t{\n\t\tts_chunk_constraints_add(cc->ccs,\n\t\t\t\t\t\t\t\t cc->chunk_id,\n\t\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t\t NameStr(constraint->conname),\n\t\t\t\t\t\t\t\t NameStr(constraint->conname));\n\t\treturn CONSTR_PROCESSED;\n\t}\n\n\treturn CONSTR_IGNORED;\n}\n\n/* Adds only inheritable check constraints */\nint\nts_chunk_constraints_add_inheritable_check_constraints(ChunkConstraints *ccs, int32 chunk_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   const char chunk_relkind, Oid hypertable_oid)\n{\n\tConstraintContext cc = {\n\t\t.chunk_relkind = chunk_relkind,\n\t\t.ccs = ccs,\n\t\t.chunk_id = chunk_id,\n\t};\n\treturn ts_constraint_process(hypertable_oid, chunk_constraint_add_check, &cc);\n}\n\nvoid\nts_chunk_constraint_create_on_chunk(const Hypertable *ht, const Chunk *chunk, Oid constraint_oid)\n{\n\tHeapTuple tuple;\n\tForm_pg_constraint con;\n\n\ttuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constraint_oid));\n\n\tif (!HeapTupleIsValid(tuple))\n\t\telog(ERROR, \"cache lookup failed for constraint %u\", constraint_oid);\n\n\tcon = (Form_pg_constraint) GETSTRUCT(tuple);\n\n\tif (chunk->relkind != RELKIND_FOREIGN_TABLE && chunk_constraint_need_on_chunk(con))\n\t{\n\t\tChunkConstraint *cc = ts_chunk_constraints_add(chunk->constraints,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   chunk->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   0,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   NameStr(con->conname));\n\n\t\tts_chunk_constraint_insert(cc);\n\t\tcreate_non_dimensional_constraint(cc,\n\t\t\t\t\t\t\t\t\t\t  chunk->table_id,\n\t\t\t\t\t\t\t\t\t\t  chunk->fd.id,\n\t\t\t\t\t\t\t\t\t\t  ht->main_table_relid,\n\t\t\t\t\t\t\t\t\t\t  ht->fd.id);\n\t}\n\n\tReleaseSysCache(tuple);\n}\n\nstatic bool\nhypertable_constraint_matches_tuple(TupleInfo *ti, const char *hypertable_constraint_name)\n{\n\tbool isnull;\n\tDatum name = slot_getattr(ti->slot, Anum_chunk_constraint_hypertable_constraint_name, &isnull);\n\n\treturn !isnull && namestrcmp(DatumGetName(name), hypertable_constraint_name) == 0;\n}\n\nstatic void\nchunk_constraint_drop_constraint(TupleInfo *ti)\n{\n\tbool isnull;\n\tDatum constrname = slot_getattr(ti->slot, Anum_chunk_constraint_constraint_name, &isnull);\n\tint32 chunk_id = DatumGetInt32(slot_getattr(ti->slot, Anum_chunk_constraint_chunk_id, &isnull));\n\t/* Get the chunk relid. Note that, at this point, the chunk table can be\n\t * deleted already. */\n\tOid chunk_relid = ts_chunk_get_relid(chunk_id, true);\n\n\tif (OidIsValid(chunk_relid))\n\t{\n\t\tObjectAddress constrobj = {\n\t\t\t.classId = ConstraintRelationId,\n\t\t\t.objectId =\n\t\t\t\tget_relation_constraint_oid(chunk_relid, NameStr(*DatumGetName(constrname)), true),\n\t\t};\n\n\t\tif (OidIsValid(constrobj.objectId))\n\t\t\t/* must use DROP_CASCADE if regular table references a hypertable */\n\t\t\tperformDeletion(&constrobj, DROP_CASCADE, 0);\n\t}\n}\n\nint\nts_chunk_constraint_delete_by_hypertable_constraint_name(int32 chunk_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t const char *hypertable_constraint_name)\n{\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(CHUNK_CONSTRAINT, RowExclusiveLock, CurrentMemoryContext);\n\tint count = 0;\n\n\tts_chunk_constraint_scan_iterator_set_chunk_id(&iterator, chunk_id);\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tif (!hypertable_constraint_matches_tuple(ts_scan_iterator_tuple_info(&iterator),\n\t\t\t\t\t\t\t\t\t\t\t\t hypertable_constraint_name))\n\t\t\tcontinue;\n\n\t\tcount++;\n\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tts_catalog_delete_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\t\tchunk_constraint_drop_constraint(ti);\n\t}\n\treturn count;\n}\n\nint\nts_chunk_constraint_delete_by_constraint_name(int32 chunk_id, const char *constraint_name)\n{\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(CHUNK_CONSTRAINT, RowExclusiveLock, CurrentMemoryContext);\n\tint count = 0;\n\n\tinit_scan_by_chunk_id_constraint_name(&iterator, chunk_id, constraint_name);\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tcount++;\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tts_catalog_delete_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\t}\n\treturn count;\n}\n\n/*\n * Delete all constraints for a chunk. Optionally, collect the deleted constraints.\n */\nint\nts_chunk_constraint_delete_by_chunk_id(int32 chunk_id, ChunkConstraints *ccs, bool drop_constraint)\n{\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(CHUNK_CONSTRAINT, RowExclusiveLock, CurrentMemoryContext);\n\tint count = 0;\n\n\tts_chunk_constraint_scan_iterator_set_chunk_id(&iterator, chunk_id);\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tcount++;\n\n\t\tts_chunk_constraints_add_from_tuple(ccs, ts_scan_iterator_tuple_info(&iterator));\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tts_catalog_delete_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\n\t\tif (drop_constraint)\n\t\t\tchunk_constraint_drop_constraint(ts_scan_iterator_tuple_info(&iterator));\n\t}\n\treturn count;\n}\n\nint\nts_chunk_constraint_delete_by_dimension_slice_id(int32 dimension_slice_id)\n{\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(CHUNK_CONSTRAINT, RowExclusiveLock, CurrentMemoryContext);\n\tint count = 0;\n\n\tts_chunk_constraint_scan_iterator_set_slice_id(&iterator, dimension_slice_id);\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tcount++;\n\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tts_catalog_delete_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\t\tchunk_constraint_drop_constraint(ti);\n\t}\n\treturn count;\n}\n\nvoid\nts_chunk_constraints_recreate(const Hypertable *ht, const Chunk *chunk)\n{\n\tconst ChunkConstraints *ccs = chunk->constraints;\n\tint i;\n\n\tfor (i = 0; i < ccs->num_constraints; i++)\n\t{\n\t\tconst ChunkConstraint *cc = &ccs->constraints[i];\n\t\tObjectAddress constrobj = {\n\t\t\t.classId = ConstraintRelationId,\n\t\t\t.objectId = get_relation_constraint_oid(chunk->table_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\tNameStr(cc->fd.constraint_name),\n\t\t\t\t\t\t\t\t\t\t\t\t\tfalse),\n\t\t};\n\n\t\tperformDeletion(&constrobj, DROP_RESTRICT, 0);\n\t}\n\n\tts_chunk_constraints_create(ht, chunk);\n\tts_chunk_copy_referencing_fk(ht, chunk);\n}\n\nstatic void\nchunk_constraint_rename_on_chunk_table(int32 chunk_id, const char *old_name, const char *new_name)\n{\n\tOid chunk_relid = ts_chunk_get_relid(chunk_id, false);\n\tOid nspid = get_rel_namespace(chunk_relid);\n\tRenameStmt rename = {\n\t\t.renameType = OBJECT_TABCONSTRAINT,\n\t\t.relation = makeRangeVar(get_namespace_name(nspid), get_rel_name(chunk_relid), 0),\n\t\t.subname = pstrdup(old_name),\n\t\t.newname = pstrdup(new_name),\n\t};\n\n\tRenameConstraint(&rename);\n}\n\nstatic void\nchunk_constraint_rename_hypertable_from_tuple(TupleInfo *ti, const char *new_name)\n{\n\tbool nulls[Natts_chunk_constraint];\n\tDatum values[Natts_chunk_constraint];\n\tbool doReplace[Natts_chunk_constraint] = { false };\n\tHeapTuple tuple, new_tuple;\n\tTupleDesc tupdesc = ts_scanner_get_tupledesc(ti);\n\tNameData new_hypertable_constraint_name;\n\tNameData new_chunk_constraint_name;\n\tName old_chunk_constraint_name;\n\tint32 chunk_id;\n\tbool should_free;\n\n\ttuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\theap_deform_tuple(tuple, tupdesc, values, nulls);\n\n\tchunk_id = DatumGetInt32(values[AttrNumberGetAttrOffset(Anum_chunk_constraint_chunk_id)]);\n\tnamestrcpy(&new_hypertable_constraint_name, new_name);\n\tchunk_constraint_choose_name(&new_chunk_constraint_name, new_name, chunk_id);\n\n\tvalues[AttrNumberGetAttrOffset(Anum_chunk_constraint_hypertable_constraint_name)] =\n\t\tNameGetDatum(&new_hypertable_constraint_name);\n\tdoReplace[AttrNumberGetAttrOffset(Anum_chunk_constraint_hypertable_constraint_name)] = true;\n\told_chunk_constraint_name =\n\t\tDatumGetName(values[AttrNumberGetAttrOffset(Anum_chunk_constraint_constraint_name)]);\n\tvalues[AttrNumberGetAttrOffset(Anum_chunk_constraint_constraint_name)] =\n\t\tNameGetDatum(&new_chunk_constraint_name);\n\tdoReplace[AttrNumberGetAttrOffset(Anum_chunk_constraint_constraint_name)] = true;\n\n\tchunk_constraint_rename_on_chunk_table(chunk_id,\n\t\t\t\t\t\t\t\t\t\t   NameStr(*old_chunk_constraint_name),\n\t\t\t\t\t\t\t\t\t\t   NameStr(new_chunk_constraint_name));\n\n\tnew_tuple = heap_modify_tuple(tuple, tupdesc, values, nulls, doReplace);\n\tts_catalog_update(ti->scanrel, new_tuple);\n\theap_freetuple(new_tuple);\n\n\tif (should_free)\n\t\theap_freetuple(tuple);\n}\n\n/*\n * Adjust internal metadata after index/constraint rename\n */\nint\nts_chunk_constraint_adjust_meta(int32 chunk_id, const char *ht_name, const char *chunk_old_name,\n\t\t\t\t\t\t\t\tconst char *chunk_new_name)\n{\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(CHUNK_CONSTRAINT, RowExclusiveLock, CurrentMemoryContext);\n\tint count = 0;\n\n\tinit_scan_by_chunk_id_constraint_name(&iterator, chunk_id, chunk_old_name);\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tbool nulls[Natts_chunk_constraint];\n\t\tbool doReplace[Natts_chunk_constraint] = { false };\n\t\tDatum values[Natts_chunk_constraint];\n\t\tbool should_free;\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\t\tHeapTuple new_tuple;\n\n\t\theap_deform_tuple(tuple, ts_scanner_get_tupledesc(ti), values, nulls);\n\n\t\t/*\n\t\t * The constraint names are of Postgres type 'name' which is fixed-width\n\t\t * 64-byte type. The input strings might not have the necessary padding\n\t\t * after them.\n\t\t */\n\t\tNameData ht_constraint_namedata;\n\t\tnamestrcpy(&ht_constraint_namedata, ht_name);\n\t\tNameData new_namedata;\n\t\tnamestrcpy(&new_namedata, chunk_new_name);\n\n\t\tvalues[AttrNumberGetAttrOffset(Anum_chunk_constraint_hypertable_constraint_name)] =\n\t\t\tNameGetDatum(&ht_constraint_namedata);\n\t\tdoReplace[AttrNumberGetAttrOffset(Anum_chunk_constraint_hypertable_constraint_name)] = true;\n\t\tvalues[AttrNumberGetAttrOffset(Anum_chunk_constraint_constraint_name)] =\n\t\t\tNameGetDatum(&new_namedata);\n\t\tdoReplace[AttrNumberGetAttrOffset(Anum_chunk_constraint_constraint_name)] = true;\n\n\t\tnew_tuple =\n\t\t\theap_modify_tuple(tuple, ts_scanner_get_tupledesc(ti), values, nulls, doReplace);\n\n\t\tts_catalog_update(ti->scanrel, new_tuple);\n\t\theap_freetuple(new_tuple);\n\n\t\tif (should_free)\n\t\t\theap_freetuple(tuple);\n\n\t\tcount++;\n\t}\n\n\treturn count;\n}\n\nbool\nts_chunk_constraint_update_slice_id(int32 chunk_id, int32 old_slice_id, int32 new_slice_id)\n{\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(CHUNK_CONSTRAINT, RowExclusiveLock, CurrentMemoryContext);\n\n\tts_chunk_constraint_scan_iterator_set_slice_id(&iterator, old_slice_id);\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tbool replIsnull[Natts_chunk_constraint];\n\t\tbool repl[Natts_chunk_constraint] = { false };\n\t\tDatum values[Natts_chunk_constraint];\n\t\tbool should_free, isnull;\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tint32 current_chunk_id =\n\t\t\tDatumGetInt32(slot_getattr(ti->slot, Anum_chunk_constraint_chunk_id, &isnull));\n\n\t\tif (isnull || current_chunk_id != chunk_id)\n\t\t\tcontinue;\n\n\t\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\t\tHeapTuple new_tuple;\n\n\t\theap_deform_tuple(tuple, ts_scanner_get_tupledesc(ti), values, replIsnull);\n\n\t\tvalues[AttrNumberGetAttrOffset(Anum_chunk_constraint_dimension_slice_id)] =\n\t\t\tInt32GetDatum(new_slice_id);\n\t\trepl[AttrNumberGetAttrOffset(Anum_chunk_constraint_dimension_slice_id)] = true;\n\n\t\tnew_tuple =\n\t\t\theap_modify_tuple(tuple, ts_scanner_get_tupledesc(ti), values, replIsnull, repl);\n\n\t\tts_catalog_update(ti->scanrel, new_tuple);\n\t\theap_freetuple(new_tuple);\n\n\t\tif (should_free)\n\t\t\theap_freetuple(tuple);\n\n\t\tts_scan_iterator_close(&iterator);\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nint\nts_chunk_constraint_rename_hypertable_constraint(int32 chunk_id, const char *old_name,\n\t\t\t\t\t\t\t\t\t\t\t\t const char *new_name)\n{\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(CHUNK_CONSTRAINT, RowExclusiveLock, CurrentMemoryContext);\n\tint count = 0;\n\n\tts_chunk_constraint_scan_iterator_set_chunk_id(&iterator, chunk_id);\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tif (!hypertable_constraint_matches_tuple(ts_scan_iterator_tuple_info(&iterator), old_name))\n\t\t\tcontinue;\n\n\t\tcount++;\n\t\tchunk_constraint_rename_hypertable_from_tuple(ts_scan_iterator_tuple_info(&iterator),\n\t\t\t\t\t\t\t\t\t\t\t\t\t  new_name);\n\t}\n\treturn count;\n}\n\nchar *\nts_chunk_constraint_get_name_from_hypertable_constraint(Oid chunk_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst char *hypertable_constraint_name)\n{\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(CHUNK_CONSTRAINT, RowExclusiveLock, CurrentMemoryContext);\n\tDatum chunk_id = DirectFunctionCall1(ts_chunk_id_from_relid, ObjectIdGetDatum(chunk_relid));\n\n\tts_chunk_constraint_scan_iterator_set_chunk_id(&iterator, DatumGetInt32(chunk_id));\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tMemoryContext oldmctx;\n\t\tbool isnull;\n\t\tDatum datum;\n\t\tchar *name;\n\n\t\tif (!hypertable_constraint_matches_tuple(ti, hypertable_constraint_name))\n\t\t\tcontinue;\n\n\t\tdatum = slot_getattr(ti->slot, Anum_chunk_constraint_constraint_name, &isnull);\n\t\tAssert(!isnull);\n\n\t\toldmctx = MemoryContextSwitchTo(ti->mctx);\n\t\tname = pstrdup(NameStr(*DatumGetName(datum)));\n\t\tMemoryContextSwitchTo(oldmctx);\n\t\tts_scan_iterator_close(&iterator);\n\n\t\treturn name;\n\t}\n\treturn NULL;\n}\n\nint\nts_chunk_constraint_delete_dimensional_constraints(int32 chunk_id, ChunkConstraints *ccs)\n{\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(CHUNK_CONSTRAINT, RowExclusiveLock, CurrentMemoryContext);\n\tint count = 0;\n\n\tts_chunk_constraint_scan_iterator_set_chunk_id(&iterator, chunk_id);\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tbool isnull;\n\t\tint32 slice_id = DatumGetInt32(\n\t\t\tslot_getattr(ti->slot, Anum_chunk_constraint_dimension_slice_id, &isnull));\n\n\t\tif (isnull || slice_id == 0)\n\t\t\tcontinue;\n\n\t\tcount++;\n\n\t\tts_chunk_constraints_add_from_tuple(ccs, ti);\n\t\tts_catalog_delete_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\t\tchunk_constraint_drop_constraint(ti);\n\t}\n\treturn count;\n}\n\n/*\n * Drop a constraint using a pg_constraint heap tuple.\n */\nvoid\nts_chunk_constraint_drop_from_tuple(HeapTuple constraint_tuple)\n{\n\tFormData_pg_constraint *constr = (FormData_pg_constraint *) GETSTRUCT(constraint_tuple);\n\tObjectAddress constrobj = {\n\t\t.classId = ConstraintRelationId,\n\t\t.objectId = constr->oid,\n\t};\n\n\tif (OidIsValid(constr->conparentid))\n\t{\n\t\tdeleteDependencyRecordsForClass(constrobj.classId,\n\t\t\t\t\t\t\t\t\t\tconstrobj.objectId,\n\t\t\t\t\t\t\t\t\t\tConstraintRelationId,\n\t\t\t\t\t\t\t\t\t\tDEPENDENCY_INTERNAL);\n\t\tCommandCounterIncrement();\n\t}\n\n\tif (OidIsValid(constrobj.objectId))\n\t\tperformDeletion(&constrobj, DROP_RESTRICT, 0);\n}\n\nstatic void\ncheck_chunk_constraint_violated(Oid chunk_relid, const Dimension *dim, const DimensionSlice *slice)\n{\n\tRelation rel;\n\tTupleTableSlot *slot;\n\tTableScanDesc scandesc;\n\tbool isnull;\n\tint attno = get_attnum(chunk_relid, NameStr(dim->fd.column_name));\n\tEnsure(attno != InvalidAttrNumber, \"invalid attribute number\");\n\n\tPushActiveSnapshot(GetLatestSnapshot());\n\trel = table_open(chunk_relid, AccessShareLock);\n\tscandesc = table_beginscan(rel, GetActiveSnapshot(), 0, NULL);\n\tslot = table_slot_create(rel, NULL);\n\n\twhile (table_scan_getnextslot(scandesc, ForwardScanDirection, slot))\n\t{\n\t\tDatum datum;\n\t\tint64 value;\n\n\t\tdatum = slot_getattr(slot, attno, &isnull);\n\t\tAssert(!isnull);\n\n\t\tif (NULL != dim->partitioning)\n\t\t{\n\t\t\tOid collation = TupleDescAttr(slot->tts_tupleDescriptor, AttrNumberGetAttrOffset(attno))\n\t\t\t\t\t\t\t\t->attcollation;\n\t\t\tdatum = ts_partitioning_func_apply(dim->partitioning, collation, datum);\n\t\t}\n\n\t\tif (dim->type == DIMENSION_TYPE_OPEN)\n\t\t\tvalue = ts_time_value_to_internal(datum, ts_dimension_get_partition_type(dim));\n\t\telse if (dim->type == DIMENSION_TYPE_CLOSED)\n\t\t\tvalue = (int64) DatumGetInt32(datum);\n\t\telse\n\t\t\telog(ERROR, \"invalid dimension type when checking constraint\");\n\n\t\tif (value < slice->fd.range_start || value >= slice->fd.range_end)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_CHECK_VIOLATION),\n\t\t\t\t\t errmsg(\"dimension constraint for column \\\"%s\\\" violated by some row\",\n\t\t\t\t\t\t\tNameStr(dim->fd.column_name))));\n\t}\n\n\tExecDropSingleTupleTableSlot(slot);\n\ttable_endscan(scandesc);\n\ttable_close(rel, NoLock);\n\tPopActiveSnapshot();\n}\n\n/*\n * Check whether the chunk has any row that violates any of its dimensional constraints\n */\nvoid\nts_chunk_constraint_check_violated(const Chunk *chunk, const Hyperspace *hs)\n{\n\tconst ChunkConstraints *ccs = chunk->constraints;\n\n\tfor (int i = 0; i < ccs->num_constraints; i++)\n\t{\n\t\tconst ChunkConstraint *cc = &ccs->constraints[i];\n\n\t\tif (is_dimension_constraint(cc))\n\t\t{\n\t\t\tconst DimensionSlice *slice = get_slice_with_id(chunk->cube, cc->fd.dimension_slice_id);\n\t\t\tconst Dimension *dim;\n\n\t\t\tdim = ts_hyperspace_get_dimension_by_id(hs, slice->fd.dimension_id);\n\t\t\tAssert(dim);\n\n\t\t\t/* Check if the chunk has any row that violates the constraint */\n\t\t\tcheck_chunk_constraint_violated(chunk->table_id, dim, slice);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/chunk_constraint.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <nodes/pg_list.h>\n\n#include \"hypertable.h\"\n#include \"ts_catalog/catalog.h\"\n\ntypedef struct ChunkConstraint\n{\n\tFormData_chunk_constraint fd;\n} ChunkConstraint;\n\ntypedef struct ChunkConstraints\n{\n\tMemoryContext mctx;\n\tint16 capacity;\n\tint16 num_constraints;\n\tint16 num_dimension_constraints;\n\tChunkConstraint *constraints;\n} ChunkConstraints;\n\n#define chunk_constraints_get(cc, i) &((cc)->constraints[i])\n\n#define is_dimension_constraint(cc) ((cc)->fd.dimension_slice_id > 0)\n\ntypedef struct Chunk Chunk;\ntypedef struct DimensionSlice DimensionSlice;\ntypedef struct Hypercube Hypercube;\ntypedef struct ChunkScanCtx ChunkScanCtx;\n\nextern TSDLLEXPORT ChunkConstraints *ts_chunk_constraints_alloc(int size_hint, MemoryContext mctx);\nextern ChunkConstraints *\nts_chunk_constraint_scan_by_chunk_id(int32 chunk_id, Size num_constraints_hint, MemoryContext mctx);\nextern ChunkConstraints *ts_chunk_constraints_copy(ChunkConstraints *chunk_constraints);\nextern int ts_chunk_constraint_scan_by_dimension_slice(const DimensionSlice *slice,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   ChunkScanCtx *ctx, MemoryContext mctx);\nextern int ts_chunk_constraint_scan_by_dimension_slice_to_list(const DimensionSlice *slice,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   List **list, MemoryContext mctx);\nextern int TSDLLEXPORT ts_chunk_constraint_scan_by_dimension_slice_id(int32 dimension_slice_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  ChunkConstraints *ccs,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  MemoryContext mctx);\nextern ChunkConstraint *ts_chunk_constraints_add(ChunkConstraints *ccs, int32 chunk_id,\n\t\t\t\t\t\t\t\t\t\t\t\t int32 dimension_slice_id,\n\t\t\t\t\t\t\t\t\t\t\t\t const char *constraint_name,\n\t\t\t\t\t\t\t\t\t\t\t\t const char *hypertable_constraint_name);\nextern int ts_chunk_constraints_add_dimension_constraints(ChunkConstraints *ccs, int32 chunk_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  const Hypercube *cube);\nextern TSDLLEXPORT int ts_chunk_constraints_add_inheritable_constraints(ChunkConstraints *ccs,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tint32 chunk_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst char chunk_relkind,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tOid hypertable_oid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tOid table_id);\nextern TSDLLEXPORT int ts_chunk_constraints_add_inheritable_check_constraints(\n\tChunkConstraints *ccs, int32 chunk_id, const char chunk_relkind, Oid hypertable_oid);\nextern TSDLLEXPORT void ts_chunk_constraints_insert_metadata(const ChunkConstraints *ccs);\nextern TSDLLEXPORT Constraint *ts_chunk_constraint_dimensional_create(const Dimension *dim,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  const DimensionSlice *slice,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  const char *name);\nextern TSDLLEXPORT void ts_chunk_constraints_create(const Hypertable *ht, const Chunk *chunk);\nextern void ts_chunk_constraint_create_on_chunk(const Hypertable *ht, const Chunk *chunk,\n\t\t\t\t\t\t\t\t\t\t\t\tOid constraint_oid);\nextern int\nts_chunk_constraint_delete_by_hypertable_constraint_name(int32 chunk_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t const char *hypertable_constraint_name);\nextern int ts_chunk_constraint_delete_by_chunk_id(int32 chunk_id, ChunkConstraints *ccs,\n\t\t\t\t\t\t\t\t\t\t\t\t  bool drop_constraint);\nextern int ts_chunk_constraint_delete_by_dimension_slice_id(int32 dimension_slice_id);\nextern int ts_chunk_constraint_delete_by_constraint_name(int32 chunk_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t const char *constraint_name);\nextern void ts_chunk_constraints_recreate(const Hypertable *ht, const Chunk *chunk);\nextern int ts_chunk_constraint_rename_hypertable_constraint(int32 chunk_id, const char *old_name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst char *new_name);\nextern int ts_chunk_constraint_adjust_meta(int32 chunk_id, const char *ht_name,\n\t\t\t\t\t\t\t\t\t\t   const char *chunk_old_name, const char *chunk_new_name);\nextern TSDLLEXPORT bool ts_chunk_constraint_update_slice_id(int32 chunk_id, int32 old_slice_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tint32 new_slice_id);\n\nextern char *\nts_chunk_constraint_get_name_from_hypertable_constraint(Oid chunk_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst char *hypertable_constraint_name);\nextern void ts_chunk_constraint_insert(ChunkConstraint *constraint);\nextern ChunkConstraint *ts_chunk_constraints_add_from_tuple(ChunkConstraints *ccs,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst TupleInfo *ti);\n\nextern ScanIterator ts_chunk_constraint_scan_iterator_create(MemoryContext result_mcxt);\nextern void ts_chunk_constraint_scan_iterator_set_slice_id(ScanIterator *it, int32 slice_id);\nextern void ts_chunk_constraint_scan_iterator_set_chunk_id(ScanIterator *it, int32 chunk_id);\nextern int ts_chunk_constraint_delete_dimensional_constraints(int32 chunk_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  ChunkConstraints *ccs);\nextern TSDLLEXPORT void ts_chunk_constraint_drop_from_tuple(HeapTuple constraint_tuple);\nextern TSDLLEXPORT void ts_chunk_constraint_check_violated(const Chunk *chunk,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   const Hyperspace *hs);\n"
  },
  {
    "path": "src/chunk_index.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/htup_details.h>\n#include <access/xact.h>\n#include <catalog/dependency.h>\n#include <catalog/index.h>\n#include <catalog/indexing.h>\n#include <catalog/namespace.h>\n#include <catalog/objectaddress.h>\n#include <catalog/pg_constraint.h>\n#include <catalog/pg_depend.h>\n#include <catalog/pg_index.h>\n#include <commands/cluster.h>\n#include <commands/defrem.h>\n#include <commands/tablecmds.h>\n#include <commands/tablespace.h>\n#include <miscadmin.h>\n#include <nodes/parsenodes.h>\n#include <optimizer/optimizer.h>\n#include <utils/builtins.h>\n#include <utils/fmgroids.h>\n#include <utils/lsyscache.h>\n#include <utils/rel.h>\n#include <utils/syscache.h>\n\n#include \"chunk.h\"\n#include \"chunk_index.h\"\n#include \"hypertable.h\"\n#include \"hypertable_cache.h\"\n#include \"indexing.h\"\n#include \"scan_iterator.h\"\n#include \"scanner.h\"\n#include \"ts_catalog/catalog.h\"\n\nstatic Oid ts_chunk_index_create_post_adjustment(int32 hypertable_id, Relation template_indexrel,\n\t\t\t\t\t\t\t\t\t\t\t\t Relation chunkrel, IndexInfo *indexinfo,\n\t\t\t\t\t\t\t\t\t\t\t\t bool isconstraint, Oid index_tablespace);\n\nstatic List *\ncreate_index_colnames(Relation indexrel)\n{\n\tList *colnames = NIL;\n\tint i;\n\n\tfor (i = 0; i < indexrel->rd_att->natts; i++)\n\t{\n\t\tForm_pg_attribute idxattr = TupleDescAttr(indexrel->rd_att, i);\n\n\t\tcolnames = lappend(colnames, pstrdup(NameStr(idxattr->attname)));\n\t}\n\n\treturn colnames;\n}\n\n/*\n * Pick a name for a chunk index.\n *\n * The chunk's index name will the original index name prefixed with the chunk's\n * table name, modulo any conflict resolution we need to do.\n */\nstatic char *\nchunk_index_choose_name(const char *tabname, const char *main_index_name, Oid namespaceid)\n{\n\tchar buf[10];\n\tchar *label = NULL;\n\tchar *idxname;\n\tint n = 0;\n\n\tfor (;;)\n\t{\n\t\t/* makeObjectName will ensure the index name fits within a NAME type */\n\t\tidxname = makeObjectName(tabname, main_index_name, label);\n\n\t\tif (!OidIsValid(get_relname_relid(idxname, namespaceid)))\n\t\t\tbreak;\n\n\t\t/* found a conflict, so try a new name component */\n\t\tpfree(idxname);\n\t\tsnprintf(buf, sizeof(buf), \"%d\", ++n);\n\t\tlabel = buf;\n\t}\n\n\treturn idxname;\n}\n\nstatic void\nadjust_expr_attnos(Oid ht_relid, IndexInfo *ii, Relation chunkrel)\n{\n\tList *vars = NIL;\n\tListCell *lc;\n\n\t/* Get a list of references to all Vars in the expression */\n\tif (ii->ii_Expressions != NIL)\n\t\tvars = list_concat(vars, pull_var_clause((Node *) ii->ii_Expressions, 0));\n\n\t/* Get a list of references to all Vars in the predicate */\n\tif (ii->ii_Predicate != NIL)\n\t\tvars = list_concat(vars, pull_var_clause((Node *) ii->ii_Predicate, 0));\n\n\tforeach (lc, vars)\n\t{\n\t\tVar *var = lfirst_node(Var, lc);\n\n\t\tvar->varattno = ts_map_attno(ht_relid, chunkrel->rd_id, var->varattno);\n\t\tvar->varattnosyn = var->varattno;\n\t}\n}\n\n/*\n * Adjust column reference attribute numbers for regular indexes.\n */\nstatic void\nchunk_adjust_colref_attnos(IndexInfo *ii, Oid ht_relid, Relation chunkrel)\n{\n\tint i;\n\n\tfor (i = 0; i < ii->ii_NumIndexAttrs; i++)\n\t{\n\t\t/* zeroes indicate expressions */\n\t\tif (ii->ii_IndexAttrNumbers[i] == 0)\n\t\t\tcontinue;\n\t\t/* we must not use get_attname on the index here as the index column names\n\t\t * are independent of parent relation column names. Instead we need to look\n\t\t * up the attno of the referenced hypertable column and do the matching\n\t\t * with the hypertable column name */\n\t\tii->ii_IndexAttrNumbers[i] =\n\t\t\tts_map_attno(ht_relid, chunkrel->rd_id, ii->ii_IndexAttrNumbers[i]);\n\t}\n}\n\nvoid\nts_adjust_indexinfo_attnos(IndexInfo *indexinfo, Oid ht_relid, Relation chunkrel)\n{\n\t/*\n\t * Adjust a hypertable's index attribute numbers to match a chunk.\n\t *\n\t * A hypertable's IndexInfo for one of its indexes references the attributes\n\t * (columns) in the hypertable by number. These numbers might not be the same\n\t * for the corresponding attribute in one of its chunks. To be able to use an\n\t * IndexInfo from a hypertable's index to create a corresponding index on a\n\t * chunk, we need to adjust the attribute numbers to match the chunk.\n\t *\n\t * We need to handle 3 places:\n\t * - direct column references in ii_IndexAttrNumbers\n\t * - references in expressions in ii_Expressions\n\t * - references in expressions in ii_Predicate\n\t */\n\tchunk_adjust_colref_attnos(indexinfo, ht_relid, chunkrel);\n\n\tif (indexinfo->ii_Expressions || indexinfo->ii_Predicate)\n\t\tadjust_expr_attnos(ht_relid, indexinfo, chunkrel);\n}\n\n#define CHUNK_INDEX_TABLESPACE_OFFSET 1\n\n/*\n * Pick a chunk index's tablespace at an offset from the chunk's tablespace in\n * order to avoid colocating chunks and their indexes in the same tablespace.\n * This hopefully leads to more I/O parallelism.\n */\nstatic Oid\nchunk_index_select_tablespace(int32 hypertable_id, Relation chunkrel)\n{\n\tTablespace *tspc;\n\tOid tablespace_oid = InvalidOid;\n\n\ttspc = ts_hypertable_get_tablespace_at_offset_from(hypertable_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   chunkrel->rd_rel->reltablespace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   CHUNK_INDEX_TABLESPACE_OFFSET);\n\n\tif (NULL != tspc)\n\t\ttablespace_oid = tspc->tablespace_oid;\n\n\treturn tablespace_oid;\n}\n\nOid\nts_chunk_index_get_tablespace(int32 hypertable_id, Relation template_indexrel, Relation chunkrel)\n{\n\t/*\n\t * Determine the index's tablespace. Use the main index's tablespace, or,\n\t * if not set, select one at an offset from the chunk's tablespace.\n\t */\n\tif (OidIsValid(template_indexrel->rd_rel->reltablespace))\n\t\treturn template_indexrel->rd_rel->reltablespace;\n\telse\n\t\treturn chunk_index_select_tablespace(hypertable_id, chunkrel);\n}\n\n/*\n * Create a chunk index based on the configuration of the \"parent\" index.\n */\nstatic Oid\nchunk_relation_index_create(Relation htrel, Relation template_indexrel, Relation chunkrel,\n\t\t\t\t\t\t\tbool isconstraint, Oid index_tablespace)\n{\n\tIndexInfo *indexinfo = BuildIndexInfo(template_indexrel);\n\tint32 hypertable_id;\n\tbool skip_mapping = false;\n\n\t/*\n\t * If the supplied template index is not on the hypertable we must not do attnum\n\t * mapping based on the hypertable. Ideally we would check for the template being\n\t * on the chunk but we cannot do that since when we rebuild a chunk the new chunk\n\t * has a different id. But the template index should always be either on the\n\t * hypertable or on a relation with the same physical layout as chunkrel.\n\t */\n\tif (IndexGetRelation(template_indexrel->rd_id, false) != htrel->rd_id)\n\t\tskip_mapping = true;\n\n\t/*\n\t * Convert the IndexInfo's attnos to match the chunk instead of the\n\t * hypertable\n\t */\n\tif (!skip_mapping &&\n\t\tchunk_index_need_attnos_adjustment(RelationGetDescr(htrel), RelationGetDescr(chunkrel)))\n\t\tts_adjust_indexinfo_attnos(indexinfo, htrel->rd_id, chunkrel);\n\n\thypertable_id = ts_hypertable_relid_to_id(htrel->rd_id);\n\tAssert(hypertable_id != INVALID_HYPERTABLE_ID);\n\n\treturn ts_chunk_index_create_post_adjustment(hypertable_id,\n\t\t\t\t\t\t\t\t\t\t\t\t template_indexrel,\n\t\t\t\t\t\t\t\t\t\t\t\t chunkrel,\n\t\t\t\t\t\t\t\t\t\t\t\t indexinfo,\n\t\t\t\t\t\t\t\t\t\t\t\t isconstraint,\n\t\t\t\t\t\t\t\t\t\t\t\t index_tablespace);\n}\n\nstatic Oid\nts_chunk_index_create_post_adjustment(int32 hypertable_id, Relation template_indexrel,\n\t\t\t\t\t\t\t\t\t  Relation chunkrel, IndexInfo *indexinfo, bool isconstraint,\n\t\t\t\t\t\t\t\t\t  Oid index_tablespace)\n{\n\tOid chunk_indexrelid = InvalidOid;\n\tconst char *indexname;\n\tHeapTuple tuple;\n\tbool isnull;\n\tDatum reloptions;\n\tDatum indclass;\n\toidvector *indclassoid;\n\tList *colnames = create_index_colnames(template_indexrel);\n\tOid tablespace;\n\tbits16 flags = 0;\n\n\ttuple = SearchSysCache1(RELOID, ObjectIdGetDatum(RelationGetRelid(template_indexrel)));\n\n\tif (!HeapTupleIsValid(tuple))\n\t\telog(ERROR,\n\t\t\t \"cache lookup failed for index relation %u\",\n\t\t\t RelationGetRelid(template_indexrel));\n\n\treloptions = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions, &isnull);\n\n\tindclass = SysCacheGetAttr(INDEXRELID,\n\t\t\t\t\t\t\t   template_indexrel->rd_indextuple,\n\t\t\t\t\t\t\t   Anum_pg_index_indclass,\n\t\t\t\t\t\t\t   &isnull);\n\tAssert(!isnull);\n\tindclassoid = (oidvector *) DatumGetPointer(indclass);\n\n\tindexname = chunk_index_choose_name(get_rel_name(RelationGetRelid(chunkrel)),\n\t\t\t\t\t\t\t\t\t\tget_rel_name(RelationGetRelid(template_indexrel)),\n\t\t\t\t\t\t\t\t\t\tget_rel_namespace(RelationGetRelid(chunkrel)));\n\tif (OidIsValid(index_tablespace))\n\t\ttablespace = index_tablespace;\n\telse\n\t\ttablespace = ts_chunk_index_get_tablespace(hypertable_id, template_indexrel, chunkrel);\n\n\t/* assign flags for index creation and constraint creation */\n\tif (isconstraint)\n\t\tflags |= INDEX_CREATE_ADD_CONSTRAINT;\n\tif (template_indexrel->rd_index->indisprimary)\n\t\tflags |= INDEX_CREATE_IS_PRIMARY;\n\n\tchunk_indexrelid = index_create_compat(chunkrel,\n\t\t\t\t\t\t\t\t\t\t   indexname,\n\t\t\t\t\t\t\t\t\t\t   InvalidOid,\n\t\t\t\t\t\t\t\t\t\t   InvalidOid,\n\t\t\t\t\t\t\t\t\t\t   InvalidOid,\n\t\t\t\t\t\t\t\t\t\t   InvalidOid,\n\t\t\t\t\t\t\t\t\t\t   indexinfo,\n\t\t\t\t\t\t\t\t\t\t   colnames,\n\t\t\t\t\t\t\t\t\t\t   template_indexrel->rd_rel->relam,\n\t\t\t\t\t\t\t\t\t\t   tablespace,\n\t\t\t\t\t\t\t\t\t\t   template_indexrel->rd_indcollation,\n\t\t\t\t\t\t\t\t\t\t   indclassoid->values,\n\t\t\t\t\t\t\t\t\t\t   NULL, /* opclassOptions */\n\t\t\t\t\t\t\t\t\t\t   template_indexrel->rd_indoption,\n\t\t\t\t\t\t\t\t\t\t   NULL, /* stattargets */\n\t\t\t\t\t\t\t\t\t\t   reloptions,\n\t\t\t\t\t\t\t\t\t\t   flags,\n\t\t\t\t\t\t\t\t\t\t   0,\t  /* constr_flags constant and 0\n\t\t\t\t\t\t\t\t\t\t\t\t   * for now */\n\t\t\t\t\t\t\t\t\t\t   false, /* allow system table mods */\n\t\t\t\t\t\t\t\t\t\t   false, /* is internal */\n\t\t\t\t\t\t\t\t\t\t   NULL); /* constraintId */\n\n\tReleaseSysCache(tuple);\n\n\treturn chunk_indexrelid;\n}\n\nstatic Oid\nchunk_index_find_matching(Relation chunk_rel, Oid ht_indexoid)\n{\n\tList *indexlist = RelationGetIndexList(chunk_rel);\n\tListCell *lc;\n\tforeach (lc, indexlist)\n\t{\n\t\tOid indexoid = lfirst_oid(lc);\n\t\tif (ts_indexing_compare(indexoid, ht_indexoid))\n\t\t\treturn indexoid;\n\t}\n\tlist_free(indexlist);\n\treturn InvalidOid;\n}\n\n/*\n * Create a new chunk index as a child of a parent hypertable index.\n *\n * The chunk index is created based on the information from the parent index\n * relation. This function is typically called when a new chunk is created and\n * it should, for each hypertable index, have a corresponding index of its own.\n */\nstatic void\nchunk_index_create(Relation hypertable_rel, int32 hypertable_id, Relation hypertable_idxrel,\n\t\t\t\t   int32 chunk_id, Relation chunkrel, Oid constraint_oid, Oid index_tblspc)\n{\n\tOid chunk_indexrelid;\n\n\tif (OidIsValid(constraint_oid))\n\t{\n\t\t/*\n\t\t * If there is an associated constraint then that constraint created\n\t\t * both the index and the catalog entry for the index\n\t\t */\n\t\treturn;\n\t}\n\n\tchunk_indexrelid = chunk_index_find_matching(chunkrel, RelationGetRelid(hypertable_idxrel));\n\tif (!OidIsValid(chunk_indexrelid))\n\t{\n\t\tchunk_indexrelid = chunk_relation_index_create(hypertable_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   hypertable_idxrel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   chunkrel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   false,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   index_tblspc);\n\t}\n}\n\nvoid\nts_chunk_index_create_from_adjusted_index_info(int32 hypertable_id, Relation hypertable_idxrel,\n\t\t\t\t\t\t\t\t\t\t\t   int32 chunk_id, Relation chunkrel,\n\t\t\t\t\t\t\t\t\t\t\t   IndexInfo *indexinfo)\n{\n\tts_chunk_index_create_post_adjustment(hypertable_id,\n\t\t\t\t\t\t\t\t\t\t  hypertable_idxrel,\n\t\t\t\t\t\t\t\t\t\t  chunkrel,\n\t\t\t\t\t\t\t\t\t\t  indexinfo,\n\t\t\t\t\t\t\t\t\t\t  false,\n\t\t\t\t\t\t\t\t\t\t  false);\n}\n\n/*\n * Create all indexes on a chunk, given the indexes that exists on the chunk's\n * hypertable.\n */\nvoid\nts_chunk_index_create_all(int32 hypertable_id, Oid hypertable_relid, int32 chunk_id, Oid chunkrelid,\n\t\t\t\t\t\t  Oid index_tblspc)\n{\n\tRelation htrel;\n\tRelation chunkrel;\n\tList *indexlist;\n\tListCell *lc;\n\tconst char chunk_relkind = get_rel_relkind(chunkrelid);\n\n\t/* Foreign table chunks don't support indexes */\n\tif (chunk_relkind == RELKIND_FOREIGN_TABLE)\n\t\treturn;\n\n\tAssert(chunk_relkind == RELKIND_RELATION);\n\n\thtrel = table_open(hypertable_relid, AccessShareLock);\n\n\t/* Need ShareLock on the heap relation we are creating indexes on */\n\tchunkrel = table_open(chunkrelid, ShareLock);\n\n\t/*\n\t * We should only add those indexes that aren't created from constraints,\n\t * since those are added separately.\n\t *\n\t * Ideally, we should just be able to check the index relation's rd_index\n\t * struct for the flags indisunique, indisprimary, indisexclusion to\n\t * figure out if this is a constraint-supporting index. However,\n\t * indisunique is true both for plain unique indexes and those created\n\t * from constraints. Instead, we prune the main table's index list,\n\t * removing those indexes that are supporting a constraint.\n\t */\n\tindexlist = RelationGetIndexList(htrel);\n\n\tforeach (lc, indexlist)\n\t{\n\t\tOid hypertable_idxoid = lfirst_oid(lc);\n\t\tRelation hypertable_idxrel = index_open(hypertable_idxoid, AccessShareLock);\n\n\t\tchunk_index_create(htrel,\n\t\t\t\t\t\t   hypertable_id,\n\t\t\t\t\t\t   hypertable_idxrel,\n\t\t\t\t\t\t   chunk_id,\n\t\t\t\t\t\t   chunkrel,\n\t\t\t\t\t\t   get_index_constraint(hypertable_idxoid),\n\t\t\t\t\t\t   index_tblspc);\n\n\t\tindex_close(hypertable_idxrel, AccessShareLock);\n\t}\n\n\ttable_close(chunkrel, NoLock);\n\ttable_close(htrel, AccessShareLock);\n}\n\nList *\nts_chunk_index_get_mappings(Hypertable *ht, Oid hypertable_indexrelid)\n{\n\tList *mappings = NIL;\n\tList *chunks = ts_chunk_get_by_hypertable_id(ht->fd.id);\n\tListCell *lc;\n\n\tforeach (lc, chunks)\n\t{\n\t\tChunk *chunk = lfirst(lc);\n\t\tif (!OidIsValid(chunk->table_id))\n\t\t\tcontinue;\n\n\t\tRelation chunk_rel = table_open(chunk->table_id, AccessShareLock);\n\t\tOid chunk_indexrelid =\n\t\t\tts_chunk_index_get_by_hypertable_indexrelid(chunk_rel, hypertable_indexrelid);\n\t\ttable_close(chunk_rel, AccessShareLock);\n\t\tif (OidIsValid(chunk_indexrelid))\n\t\t{\n\t\t\tChunkIndexMapping *cim = palloc0(sizeof(ChunkIndexMapping));\n\t\t\tcim->chunkoid = chunk->table_id;\n\t\t\tcim->indexoid = chunk_indexrelid;\n\t\t\tcim->parent_indexoid = hypertable_indexrelid;\n\t\t\tcim->hypertableoid = ht->fd.id;\n\t\t\tmappings = lappend(mappings, cim);\n\t\t}\n\t}\n\treturn mappings;\n}\n\nTSDLLEXPORT Oid\nts_chunk_index_get_by_hypertable_indexrelid(Relation chunk_rel, Oid ht_indexoid)\n{\n\tList *indexlist = RelationGetIndexList(chunk_rel);\n\tListCell *lc;\n\tOid chunk_index_oid = InvalidOid;\n\n\tforeach (lc, indexlist)\n\t{\n\t\tOid indexoid = lfirst_oid(lc);\n\n\t\tif (ts_indexing_compare(indexoid, ht_indexoid))\n\t\t{\n\t\t\tchunk_index_oid = indexoid;\n\t\t\tbreak;\n\t\t}\n\t}\n\tlist_free(indexlist);\n\treturn chunk_index_oid;\n}\n\nvoid\nts_chunk_index_rename(Hypertable *ht, Oid hypertable_indexrelid, const char *ht_name)\n{\n\tListCell *lc;\n\tList *chunks = ts_chunk_get_by_hypertable_id(ht->fd.id);\n\tforeach (lc, chunks)\n\t{\n\t\tChunk *chunk = lfirst(lc);\n\t\tif (!OidIsValid(chunk->table_id))\n\t\t\tcontinue;\n\n\t\tRelation chunk_rel = table_open(chunk->table_id, AccessExclusiveLock);\n\t\tOid chunk_indexrelid =\n\t\t\tts_chunk_index_get_by_hypertable_indexrelid(chunk_rel, hypertable_indexrelid);\n\t\ttable_close(chunk_rel, NoLock);\n\n\t\t/* If there is no matching index on the chunk, skip it */\n\t\tif (OidIsValid(chunk_indexrelid))\n\t\t{\n\t\t\tOid chunk_schemaoid = get_namespace_oid(NameStr(chunk->fd.schema_name), false);\n\t\t\tconst char *chunk_old_name = get_rel_name(chunk_indexrelid);\n\t\t\tconst char *chunk_new_name =\n\t\t\t\tchunk_index_choose_name(NameStr(chunk->fd.table_name), ht_name, chunk_schemaoid);\n\n\t\t\t/*\n\t\t\t * Index might also have a constraint which we track separately in our catalog\n\t\t\t * and needs to be updated too\n\t\t\t */\n\t\t\tts_chunk_constraint_adjust_meta(chunk->fd.id, ht_name, chunk_old_name, chunk_new_name);\n\t\t\tRenameRelationInternal(chunk_indexrelid, chunk_new_name, false, true);\n\t\t}\n\t}\n}\n\nvoid\nts_chunk_index_set_tablespace(Hypertable *ht, Oid hypertable_indexrelid, char *tablespace)\n{\n\tList *chunks = ts_chunk_get_by_hypertable_id(ht->fd.id);\n\tListCell *lc;\n\n\tforeach (lc, chunks)\n\t{\n\t\tChunk *chunk = lfirst(lc);\n\t\tRelation chunk_rel = table_open(chunk->table_id, AccessExclusiveLock);\n\t\tOid chunk_indexrelid =\n\t\t\tts_chunk_index_get_by_hypertable_indexrelid(chunk_rel, hypertable_indexrelid);\n\t\ttable_close(chunk_rel, NoLock);\n\n\t\tif (OidIsValid(chunk_indexrelid))\n\t\t{\n\t\t\tAlterTableCmd *cmd = makeNode(AlterTableCmd);\n\t\t\tList *cmds = NIL;\n\n\t\t\tcmd->subtype = AT_SetTableSpace;\n\t\t\tcmd->name = tablespace;\n\t\t\tcmds = lappend(cmds, cmd);\n\n\t\t\tts_alter_table_with_event_trigger(chunk_indexrelid, NULL, cmds, false);\n\t\t}\n\t}\n}\n\nTSDLLEXPORT void\nts_chunk_index_mark_clustered(Oid chunkrelid, Oid indexrelid)\n{\n\tRelation rel = table_open(chunkrelid, AccessShareLock);\n\n\tmark_index_clustered(rel, indexrelid, true);\n\tCommandCounterIncrement();\n\ttable_close(rel, AccessShareLock);\n}\n\nstatic Oid\nchunk_index_duplicate_index(Relation hypertable_rel, Chunk *src_chunk, Oid chunk_index_oid,\n\t\t\t\t\t\t\tRelation dest_chunk_rel, Oid index_tablespace)\n{\n\tRelation chunk_index_rel = index_open(chunk_index_oid, AccessShareLock);\n\tOid constraint_oid;\n\tOid new_chunk_indexrelid;\n\n\tconstraint_oid = get_index_constraint(chunk_index_oid);\n\n\tnew_chunk_indexrelid = chunk_relation_index_create(hypertable_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   chunk_index_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   dest_chunk_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   OidIsValid(constraint_oid),\n\t\t\t\t\t\t\t\t\t\t\t\t\t   index_tablespace);\n\n\tindex_close(chunk_index_rel, NoLock);\n\treturn new_chunk_indexrelid;\n}\n\n/*\n * Create versions of every index over src_chunkrelid over chunkrelid.\n * Returns the relids of the new indexes created.\n * New indexes are in the same order as RelationGetIndexList.\n */\nTSDLLEXPORT List *\nts_chunk_index_duplicate(Oid src_chunkrelid, Oid dest_chunkrelid, List **src_index_oids,\n\t\t\t\t\t\t Oid index_tablespace)\n{\n\tRelation hypertable_rel = NULL;\n\tRelation src_chunk_rel;\n\tRelation dest_chunk_rel;\n\tList *index_oids;\n\tListCell *index_elem;\n\tList *new_index_oids = NIL;\n\tChunk *src_chunk;\n\n\tsrc_chunk_rel = table_open(src_chunkrelid, AccessShareLock);\n\tdest_chunk_rel = table_open(dest_chunkrelid, ShareLock);\n\n\tsrc_chunk = ts_chunk_get_by_relid(src_chunkrelid, true);\n\n\thypertable_rel = table_open(src_chunk->hypertable_relid, AccessShareLock);\n\n\tindex_oids = RelationGetIndexList(src_chunk_rel);\n\tforeach (index_elem, index_oids)\n\t{\n\t\tOid chunk_index_oid = lfirst_oid(index_elem);\n\t\tOid new_chunk_indexrelid = chunk_index_duplicate_index(hypertable_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   src_chunk,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   chunk_index_oid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   dest_chunk_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   index_tablespace);\n\n\t\tnew_index_oids = lappend_oid(new_index_oids, new_chunk_indexrelid);\n\t}\n\n\ttable_close(hypertable_rel, AccessShareLock);\n\n\ttable_close(dest_chunk_rel, NoLock);\n\ttable_close(src_chunk_rel, NoLock);\n\n\tif (src_index_oids != NULL)\n\t\t*src_index_oids = index_oids;\n\n\treturn new_index_oids;\n}\n\nvoid\nts_chunk_index_move_all(Oid chunk_relid, Oid index_tblspc)\n{\n\tRelation chunkrel;\n\tList *indexlist;\n\tListCell *lc;\n\tconst char chunk_relkind = get_rel_relkind(chunk_relid);\n\n\t/* execute ALTER INDEX .. SET TABLESPACE for each index on the chunk */\n\tAlterTableCmd cmd = { .type = T_AlterTableCmd,\n\t\t\t\t\t\t  .subtype = AT_SetTableSpace,\n\t\t\t\t\t\t  .name = get_tablespace_name(index_tblspc) };\n\n\t/* Foreign table chunks don't support indexes */\n\tif (chunk_relkind == RELKIND_FOREIGN_TABLE)\n\t\treturn;\n\n\tAssert(chunk_relkind == RELKIND_RELATION);\n\n\tchunkrel = table_open(chunk_relid, AccessShareLock);\n\n\tindexlist = RelationGetIndexList(chunkrel);\n\n\tforeach (lc, indexlist)\n\t{\n\t\tOid chunk_idxoid = lfirst_oid(lc);\n\t\tts_alter_table_with_event_trigger(chunk_idxoid, NULL, list_make1(&cmd), false);\n\t}\n\ttable_close(chunkrel, AccessShareLock);\n}\n"
  },
  {
    "path": "src/chunk_index.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <fmgr.h>\n#include <nodes/execnodes.h>\n#include <nodes/parsenodes.h>\n#include <utils/relcache.h>\n\n#include \"compat/compat.h\"\n#include \"export.h\"\n\ntypedef struct Chunk Chunk;\ntypedef struct Hypertable Hypertable;\n\ntypedef struct ChunkIndexMapping\n{\n\tOid chunkoid;\n\tOid parent_indexoid;\n\tOid indexoid;\n\tOid hypertableoid;\n} ChunkIndexMapping;\n\nextern void ts_chunk_index_create(Relation hypertable_rel, int32 hypertable_id,\n\t\t\t\t\t\t\t\t  Relation hypertable_idxrel, int32 chunk_id, Relation chunkrel);\n\nvoid ts_adjust_indexinfo_attnos(IndexInfo *indexinfo, Oid ht_relid, Relation chunkrel);\nextern void ts_chunk_index_create_from_adjusted_index_info(int32 hypertable_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   Relation hypertable_idxrel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   int32 chunk_id, Relation chunkrel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   IndexInfo *indexinfo);\nextern TSDLLEXPORT void ts_chunk_index_create_all(int32 hypertable_id, Oid hypertable_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t  int32 chunk_id, Oid chunkrelid, Oid index_tblspc);\nextern TSDLLEXPORT void ts_chunk_index_move_all(Oid chunk_relid, Oid index_tblspc);\nextern void ts_chunk_index_rename(Hypertable *ht, Oid hypertable_indexrelid, const char *ht_name);\nextern void ts_chunk_index_set_tablespace(Hypertable *ht, Oid hypertable_indexrelid,\n\t\t\t\t\t\t\t\t\t\t  char *tablespace);\nextern List *ts_chunk_index_get_mappings(Hypertable *ht, Oid hypertable_indexrelid);\nextern TSDLLEXPORT Oid ts_chunk_index_get_by_hypertable_indexrelid(Relation chunk_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   Oid ht_indexoid);\n\nextern TSDLLEXPORT void ts_chunk_index_mark_clustered(Oid chunkrelid, Oid indexrelid);\n\nextern TSDLLEXPORT List *ts_chunk_index_duplicate(Oid src_chunkrelid, Oid dest_chunkrelid,\n\t\t\t\t\t\t\t\t\t\t\t\t  List **src_index_oids, Oid index_tablespace);\n\nextern Oid ts_chunk_index_get_tablespace(int32 hypertable_id, Relation template_indexrel,\n\t\t\t\t\t\t\t\t\t\t Relation chunkrel);\n\nstatic inline bool\nchunk_index_columns_changed(int hypertable_natts, TupleDesc chunkdesc)\n{\n\t/*\n\t * We should be able to safely assume that the only reason the number of\n\t * attributes differ is because we have removed columns in the base table,\n\t * leaving junk attributes that aren't inherited by the chunk.\n\t */\n\treturn hypertable_natts != chunkdesc->natts;\n}\n\nstatic inline bool\nchunk_index_need_attnos_adjustment(TupleDesc htdesc, TupleDesc chunkdesc)\n{\n\treturn chunk_index_columns_changed(htdesc->natts, chunkdesc);\n}\n"
  },
  {
    "path": "src/chunk_insert_state.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/attnum.h>\n#include <access/xact.h>\n#include <catalog/pg_type.h>\n#include <executor/tuptable.h>\n#include <foreign/fdwapi.h>\n#include <miscadmin.h>\n#include <nodes/execnodes.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodes.h>\n#include <nodes/plannodes.h>\n#include <optimizer/optimizer.h>\n#include <parser/parsetree.h>\n#include <rewrite/rewriteManip.h>\n#include <utils/builtins.h>\n#include <utils/guc.h>\n#include <utils/lsyscache.h>\n#include <utils/memutils.h>\n#include <utils/rel.h>\n#include <utils/rls.h>\n\n#include \"compat/compat.h\"\n#include \"chunk_index.h\"\n#include \"chunk_insert_state.h\"\n#include \"chunk_tuple_routing.h\"\n#include \"debug_point.h\"\n#include \"errors.h\"\n#include \"indexing.h\"\n#include \"nodes/modify_hypertable.h\"\n#include \"ts_catalog/continuous_agg.h\"\n\n/* Just like ExecPrepareExpr except that it doesn't switch to the query memory context */\nstatic inline ExprState *\nprepare_constr_expr(Expr *node)\n{\n\tExprState *result;\n\n\tnode = expression_planner(node);\n\tresult = ExecInitExpr(node, NULL);\n\n\treturn result;\n}\n\n/*\n * Create the constraint exprs inside the current memory context. If this\n * is not done here, then ExecRelCheck will do it for you but put it into\n * the query memory context, which will cause a memory leak.\n *\n * See the comment in `ts_chunk_insert_state_destroy` for more information\n * on the implications of this.\n */\nstatic inline void\ncreate_chunk_rri_constraint_expr(ResultRelInfo *rri, Relation rel)\n{\n\tint ncheck, i;\n\tConstrCheck *check;\n\n\tAssert(rel->rd_att->constr != NULL && rri->ri_CheckConstraintExprs == NULL);\n\n\tncheck = rel->rd_att->constr->num_check;\n\tcheck = rel->rd_att->constr->check;\n\trri->ri_CheckConstraintExprs = (ExprState **) palloc(ncheck * sizeof(ExprState *));\n\n\tfor (i = 0; i < ncheck; i++)\n\t{\n\t\tExpr *checkconstr = stringToNode(check[i].ccbin);\n\n\t\trri->ri_CheckConstraintExprs[i] = prepare_constr_expr(checkconstr);\n\t}\n}\n\n/*\n * Create a new ResultRelInfo for a chunk.\n *\n * The ResultRelInfo holds the executor state (e.g., open relation, indexes, and\n * options) for the result relation where tuples will be stored.\n *\n * The Hypertable ResultRelInfo is used as a template for the chunk's new ResultRelInfo.\n */\nResultRelInfo *\ncreate_chunk_result_relation_info(ResultRelInfo *ht_rri, Relation rel, EState *estate)\n{\n\tResultRelInfo *rri;\n\trri = makeNode(ResultRelInfo);\n\n\tInitResultRelInfo(rri, rel, ht_rri->ri_RangeTableIndex, NULL, estate->es_instrument);\n\n\t/* Copy options from the main table's (hypertable's) result relation info */\n\trri->ri_WithCheckOptions = ht_rri->ri_WithCheckOptions;\n\trri->ri_WithCheckOptionExprs = ht_rri->ri_WithCheckOptionExprs;\n\trri->ri_projectReturning = ht_rri->ri_projectReturning;\n\n\trri->ri_FdwState = NULL;\n\trri->ri_usesFdwDirectModify = ht_rri->ri_usesFdwDirectModify;\n\n\tif (RelationGetForm(rel)->relkind == RELKIND_FOREIGN_TABLE)\n\t\trri->ri_FdwRoutine = GetFdwRoutineForRelation(rel, true);\n\n\tcreate_chunk_rri_constraint_expr(rri, rel);\n\n\treturn rri;\n}\n\nstatic ProjectionInfo *\nget_adjusted_projection_info_returning(ProjectionInfo *orig, List *returning_clauses,\n\t\t\t\t\t\t\t\t\t   TupleConversionMap *map, Index varno, Oid rowtype,\n\t\t\t\t\t\t\t\t\t   TupleDesc chunk_desc)\n{\n\tbool found_whole_row;\n\n\tAssert(returning_clauses != NIL);\n\n\t/* map hypertable attnos -> chunk attnos */\n\tif (map != NULL)\n\t\treturning_clauses = castNode(List,\n\t\t\t\t\t\t\t\t\t map_variable_attnos((Node *) returning_clauses,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t varno,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t map->attrMap,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t rowtype,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t &found_whole_row));\n\n\treturn ExecBuildProjectionInfo(returning_clauses,\n\t\t\t\t\t\t\t\t   orig->pi_exprContext,\n\t\t\t\t\t\t\t\t   orig->pi_state.resultslot,\n\t\t\t\t\t\t\t\t   orig->pi_state.parent,\n\t\t\t\t\t\t\t\t   chunk_desc);\n}\n\nstatic List *\ntranslate_clause(List *inclause, TupleConversionMap *chunk_map, Index varno, Relation hyper_rel,\n\t\t\t\t Relation chunk_rel)\n{\n\tList *clause = copyObject(inclause);\n\tbool found_whole_row;\n\n\t/* nothing to do here if the chunk_map is NULL */\n\tif (!chunk_map)\n\t\treturn list_copy(clause);\n\n\t/* map hypertable attnos -> chunk attnos for the \"excluded\" table */\n\tclause = castNode(List,\n\t\t\t\t\t  map_variable_attnos((Node *) clause,\n\t\t\t\t\t\t\t\t\t\t  INNER_VAR,\n\t\t\t\t\t\t\t\t\t\t  0,\n\t\t\t\t\t\t\t\t\t\t  chunk_map->attrMap,\n\t\t\t\t\t\t\t\t\t\t  RelationGetForm(chunk_rel)->reltype,\n\t\t\t\t\t\t\t\t\t\t  &found_whole_row));\n\n\t/* map hypertable attnos -> chunk attnos for the hypertable */\n\tclause = castNode(List,\n\t\t\t\t\t  map_variable_attnos((Node *) clause,\n\t\t\t\t\t\t\t\t\t\t  varno,\n\t\t\t\t\t\t\t\t\t\t  0,\n\t\t\t\t\t\t\t\t\t\t  chunk_map->attrMap,\n\t\t\t\t\t\t\t\t\t\t  RelationGetForm(chunk_rel)->reltype,\n\t\t\t\t\t\t\t\t\t\t  &found_whole_row));\n\n\treturn clause;\n}\n\n/*\n * adjust_chunk_colnos\n *\t\tAdjust the list of UPDATE target column numbers to account for\n *\t\tattribute differences between the parent and the partition.\n *\n * adapted from postgres adjust_partition_colnos\n */\nstatic List *\nadjust_chunk_colnos(List *colnos, ResultRelInfo *chunk_rri)\n{\n\tList *new_colnos = NIL;\n\tTupleConversionMap *map = ExecGetChildToRootMap(chunk_rri);\n\tAttrMap *attrMap;\n\tListCell *lc;\n\n\tAssert(map != NULL); /* else we shouldn't be here */\n\tattrMap = map->attrMap;\n\n\tforeach (lc, colnos)\n\t{\n\t\tAttrNumber parentattrno = lfirst_int(lc);\n\n\t\tif (parentattrno <= 0 || parentattrno > attrMap->maplen ||\n\t\t\tattrMap->attnums[parentattrno - 1] == 0)\n\t\t\telog(ERROR, \"unexpected attno %d in target column list\", parentattrno);\n\t\tnew_colnos = lappend_int(new_colnos, attrMap->attnums[parentattrno - 1]);\n\t}\n\n\treturn new_colnos;\n}\n\n/*\n * Setup ON CONFLICT state for a chunk.\n *\n * Mostly, this is about mapping attribute numbers from the hypertable root to\n * a chunk, accounting for differences in the tuple descriptors due to dropped\n * columns, etc.\n */\nstatic void\nsetup_on_conflict_state(ResultRelInfo *ht_rri, ModifyTableState *mtstate, ChunkInsertState *state,\n\t\t\t\t\t\tTupleConversionMap *chunk_map)\n{\n\tTupleConversionMap *map = state->hyper_to_chunk_map;\n\tResultRelInfo *chunk_rri = state->result_relation_info;\n\tRelation chunk_rel = state->result_relation_info->ri_RelationDesc;\n\tRelation hyper_rel = ht_rri->ri_RelationDesc;\n\tModifyTable *mt = castNode(ModifyTable, mtstate->ps.plan);\n\n\tOnConflictSetState *onconfl = makeNode(OnConflictSetState);\n\tmemcpy(onconfl, ht_rri->ri_onConflict, sizeof(OnConflictSetState));\n\tchunk_rri->ri_onConflict = onconfl;\n\n#if PG16_LT\n\tchunk_rri->ri_RootToPartitionMap = map;\n#else\n\tchunk_rri->ri_RootToChildMap = map;\n\tchunk_rri->ri_RootToChildMapValid = true;\n#endif\n\n\tAssert(mt->onConflictSet);\n\tAssert(ht_rri->ri_onConflict != NULL);\n\n\t/*\n\t * Need a separate existing slot for each partition, as the\n\t * partition could be of a different AM, even if the tuple\n\t * descriptors match.\n\t */\n\tonconfl->oc_Existing = table_slot_create(chunk_rri->ri_RelationDesc, NULL);\n\tstate->existing_slot = onconfl->oc_Existing;\n\n\t/*\n\t * If the chunk's tuple descriptor matches exactly the hypertable\n\t * (the common case), we can reuse most of the parent's ON\n\t * CONFLICT SET state, skipping a bunch of work.  Otherwise, we\n\t * need to create state specific to this partition.\n\t */\n\tif (!map)\n\t{\n\t\t/*\n\t\t * It's safe to reuse these from the hypertable, as we\n\t\t * only process one tuple at a time (therefore we won't\n\t\t * overwrite needed data in slots), and the results of\n\t\t * projections are independent of the underlying storage.\n\t\t * Projections and where clauses themselves don't store state\n\t\t * / are independent of the underlying storage.\n\t\t */\n\t\tonconfl->oc_ProjSlot = ht_rri->ri_onConflict->oc_ProjSlot;\n\t\tonconfl->oc_ProjInfo = ht_rri->ri_onConflict->oc_ProjInfo;\n\t\tonconfl->oc_WhereClause = ht_rri->ri_onConflict->oc_WhereClause;\n\t\tstate->conflproj_slot = onconfl->oc_ProjSlot;\n\t}\n\telse\n\t{\n\t\tList *onconflset;\n\t\tList *onconflcols;\n\n\t\t/*\n\t\t * Translate expressions in onConflictSet to account for\n\t\t * different attribute numbers.  For that, map partition\n\t\t * varattnos twice: first to catch the EXCLUDED\n\t\t * pseudo-relation (INNER_VAR), and second to handle the main\n\t\t * target relation (firstVarno).\n\t\t */\n\t\tonconflset = copyObject(mt->onConflictSet);\n\n\t\tAssert(map->outdesc == RelationGetDescr(chunk_rel));\n\n\t\tif (!chunk_map)\n\t\t\tchunk_map =\n\t\t\t\tconvert_tuples_by_name(RelationGetDescr(chunk_rel), RelationGetDescr(hyper_rel));\n\n\t\tonconflset = translate_clause(onconflset,\n\t\t\t\t\t\t\t\t\t  chunk_map,\n\t\t\t\t\t\t\t\t\t  ht_rri->ri_RangeTableIndex,\n\t\t\t\t\t\t\t\t\t  hyper_rel,\n\t\t\t\t\t\t\t\t\t  chunk_rel);\n\n\t\tchunk_rri->ri_ChildToRootMap = chunk_map;\n\t\tchunk_rri->ri_ChildToRootMapValid = true;\n\n\t\t/* Finally, adjust the target colnos to match the chunk. */\n\t\tif (chunk_map)\n\t\t\tonconflcols = adjust_chunk_colnos(mt->onConflictCols, chunk_rri);\n\t\telse\n\t\t\tonconflcols = mt->onConflictCols;\n\n\t\t/* create the tuple slot for the UPDATE SET projection */\n\t\tonconfl->oc_ProjSlot = table_slot_create(chunk_rel, NULL);\n\t\tstate->conflproj_slot = onconfl->oc_ProjSlot;\n\n\t\t/* build UPDATE SET projection state */\n\t\tonconfl->oc_ProjInfo = ExecBuildUpdateProjection(onconflset,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t onconflcols,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t RelationGetDescr(chunk_rel),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t mtstate->ps.ps_ExprContext,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t onconfl->oc_ProjSlot,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t &mtstate->ps);\n\n\t\tNode *onconflict_where = mt->onConflictWhere;\n\n\t\t/*\n\t\t * Map attribute numbers in the WHERE clause, if it exists.\n\t\t */\n\t\tif (onconflict_where && chunk_map)\n\t\t{\n\t\t\tList *clause = translate_clause(castNode(List, onconflict_where),\n\t\t\t\t\t\t\t\t\t\t\tchunk_map,\n\t\t\t\t\t\t\t\t\t\t\tht_rri->ri_RangeTableIndex,\n\t\t\t\t\t\t\t\t\t\t\thyper_rel,\n\t\t\t\t\t\t\t\t\t\t\tchunk_rel);\n\n\t\t\tchunk_rri->ri_onConflict->oc_WhereClause = ExecInitQual(clause, NULL);\n\t\t}\n\t}\n}\n\n/* Translate hypertable indexes to chunk indexes in the arbiter clause */\nstatic void\nset_arbiter_indexes(ChunkInsertState *state, List *ht_arbiter_indexes)\n{\n\tList *chunk_arbiter_indexes = NIL;\n\tListCell *lc;\n\n\tforeach (lc, ht_arbiter_indexes)\n\t{\n\t\tOid hypertable_index = lfirst_oid(lc);\n\t\tOid chunk_index_oid =\n\t\t\tts_chunk_index_get_by_hypertable_indexrelid(state->rel, hypertable_index);\n\t\tif (!OidIsValid(chunk_index_oid))\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"could not find arbiter index for hypertable index \\\"%s\\\" on chunk \"\n\t\t\t\t\t\t\t\"\\\"%s\\\"\",\n\t\t\t\t\t\t\tget_rel_name(hypertable_index),\n\t\t\t\t\t\t\tget_rel_name(RelationGetRelid(state->rel)))));\n\t\t}\n\n\t\tchunk_arbiter_indexes = lappend_oid(chunk_arbiter_indexes, chunk_index_oid);\n\t}\n\tstate->result_relation_info->ri_onConflictArbiterIndexes = chunk_arbiter_indexes;\n}\n\n/* Change the projections to work with chunks instead of hypertables */\nstatic void\nadjust_projections(ResultRelInfo *ht_rri, ModifyTableState *mtstate, ChunkInsertState *cis,\n\t\t\t\t   Oid rowtype)\n{\n\tResultRelInfo *chunk_rri = cis->result_relation_info;\n\tRelation hyper_rel = ht_rri->ri_RelationDesc;\n\tRelation chunk_rel = cis->rel;\n\tTupleConversionMap *chunk_map = NULL;\n\tOnConflictAction onConflictAction = ONCONFLICT_NONE;\n\tList *returningLists = NIL;\n\n\tif (mtstate)\n\t{\n\t\tModifyTable *mt = castNode(ModifyTable, mtstate->ps.plan);\n\t\tonConflictAction = mt->onConflictAction;\n\t\treturningLists = mt->returningLists;\n\t}\n\n\tif (returningLists)\n\t{\n\t\t/*\n\t\t * We need the opposite map from cis->hyper_to_chunk_map. The map needs\n\t\t * to have the hypertable_desc in the out spot for map_variable_attnos\n\t\t * to work correctly in mapping hypertable attnos->chunk attnos.\n\t\t */\n\t\tchunk_map =\n\t\t\tconvert_tuples_by_name(RelationGetDescr(chunk_rel), RelationGetDescr(hyper_rel));\n\n\t\tchunk_rri->ri_projectReturning =\n\t\t\tget_adjusted_projection_info_returning(chunk_rri->ri_projectReturning,\n\t\t\t\t\t\t\t\t\t\t\t\t   linitial(returningLists),\n\t\t\t\t\t\t\t\t\t\t\t\t   chunk_map,\n\t\t\t\t\t\t\t\t\t\t\t\t   ht_rri->ri_RangeTableIndex,\n\t\t\t\t\t\t\t\t\t\t\t\t   rowtype,\n\t\t\t\t\t\t\t\t\t\t\t\t   RelationGetDescr(chunk_rel));\n\t}\n\n\t/* Set the chunk's arbiter indexes for ON CONFLICT statements */\n\tif (onConflictAction != ONCONFLICT_NONE)\n\t{\n\t\tset_arbiter_indexes(cis, ht_rri->ri_onConflictArbiterIndexes);\n\n\t\tif (onConflictAction == ONCONFLICT_UPDATE)\n\t\t\tsetup_on_conflict_state(ht_rri, mtstate, cis, chunk_map);\n\t}\n}\n\n/*\n * Create new insert chunk state.\n *\n * This is essentially a ResultRelInfo for a chunk. Initialization of the\n * ResultRelInfo should be similar to ExecInitModifyTable().\n */\nextern ChunkInsertState *\nts_chunk_insert_state_create(Oid chunk_relid, const ChunkTupleRouting *ctr)\n{\n\tChunkInsertState *state;\n\tRelation rel, parent_rel;\n\tMemoryContext cis_context = AllocSetContextCreate(ctr->estate->es_query_cxt,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  \"chunk insert state memory context\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t  ALLOCSET_DEFAULT_SIZES);\n\tResultRelInfo *relinfo;\n\tconst Chunk *chunk;\n\n\tMemoryContext old_mcxt =\n\t\tMemoryContextSwitchTo(ctr->estate->es_per_tuple_exprcontext->ecxt_per_tuple_memory);\n\t/*\n\t * Since we insert data and won't modify metadata, a RowExclusiveLock\n\t * should be sufficient. This should conflict with any metadata-modifying\n\t * operations as they should take higher-level locks (ShareLock and\n\t * above).\n\t */\n\trel = table_open(chunk_relid, RowExclusiveLock);\n\n\t/*\n\t * A concurrent chunk operation (e.g., compression) might have changed the\n\t * chunk metadata before we got a lock, so re-read it.\n\t *\n\t * This works even in higher levels of isolation since catalog data is\n\t * always read from latest snapshot.\n\t */\n\tchunk = ts_chunk_get_by_relid(chunk_relid, true);\n\tAssert(chunk->relkind == RELKIND_RELATION);\n\tts_chunk_validate_chunk_status_for_operation(chunk, CHUNK_INSERT, true);\n\n\tMemoryContextSwitchTo(cis_context);\n\tif (ctr->single_chunk_insert)\n\t\trelinfo = ctr->root_rri;\n\telse\n\t\trelinfo = create_chunk_result_relation_info(ctr->root_rri, rel, ctr->estate);\n\n\tif (ctr->mht_state)\n\t\tCheckValidResultRelCompat(relinfo,\n\t\t\t\t\t\t\t\t  ctr->mht_state->mt->operation,\n\t\t\t\t\t\t\t\t  ctr->mht_state->mt->onConflictAction,\n\t\t\t\t\t\t\t\t  NIL);\n\n\tstate = palloc0(sizeof(ChunkInsertState));\n\tstate->counters = ctr->counters;\n\tif (ctr->mht_state)\n\t\tstate->onConflictAction = ctr->mht_state->mt->onConflictAction;\n\tstate->mctx = cis_context;\n\tstate->rel = rel;\n\tstate->result_relation_info = relinfo;\n\tstate->estate = ctr->estate;\n\tts_set_compression_status(state, chunk);\n\n\tif (relinfo->ri_RelationDesc->rd_rel->relhasindex && relinfo->ri_IndexRelationDescs == NULL)\n\t\tExecOpenIndices(relinfo, state->onConflictAction != ONCONFLICT_NONE);\n\n\tif (relinfo->ri_TrigDesc != NULL)\n\t{\n\t\tTriggerDesc *tg = relinfo->ri_TrigDesc;\n\n\t\t/* instead of triggers can only be created on VIEWs */\n\t\tAssert(!tg->trig_insert_instead_row);\n\n\t\t/*\n\t\t * A statement that targets a parent table in an inheritance or\n\t\t * partitioning hierarchy does not cause the statement-level triggers\n\t\t * of affected child tables to be fired; only the parent table's\n\t\t * statement-level triggers are fired. However, row-level triggers\n\t\t * of any affected child tables will be fired.\n\t\t * During chunk creation we only copy ROW trigger to chunks so\n\t\t * statement triggers should not exist on chunks.\n\t\t */\n\t\tif (tg->trig_insert_after_statement || tg->trig_insert_before_statement)\n\t\t\telog(ERROR, \"statement trigger on chunk table not supported\");\n\t}\n\n\tif (!ctr->single_chunk_insert)\n\t{\n\t\tparent_rel = table_open(ctr->hypertable->main_table_relid, AccessShareLock);\n\n\t\t/* Set tuple conversion map, if tuple needs conversion. */\n\t\tstate->hyper_to_chunk_map =\n\t\t\tconvert_tuples_by_name(RelationGetDescr(parent_rel), RelationGetDescr(rel));\n\n\t\tif (ctr->mht_state)\n\t\t\tadjust_projections(ctr->root_rri,\n\t\t\t\t\t\t\t   linitial_node(ModifyTableState,\n\t\t\t\t\t\t\t\t\t\t\t ctr->mht_state->cscan_state.custom_ps),\n\t\t\t\t\t\t\t   state,\n\t\t\t\t\t\t\t   RelationGetForm(rel)->reltype);\n\n\t\ttable_close(parent_rel, AccessShareLock);\n\t}\n\n\t/* Need a tuple table slot to store tuples going into this chunk. We don't\n\t * want this slot tied to the executor's tuple table, since that would tie\n\t * the slot's lifetime to the entire length of the execution and we want\n\t * to be able to dynamically create and destroy chunk insert\n\t * state. Otherwise, memory might blow up when there are many chunks being\n\t * inserted into. This also means that the slot needs to be destroyed with\n\t * the chunk insert state. */\n\tstate->slot = MakeSingleTupleTableSlot(RelationGetDescr(relinfo->ri_RelationDesc),\n\t\t\t\t\t\t\t\t\t\t   table_slot_callbacks(relinfo->ri_RelationDesc));\n\n\tstate->hypertable_relid = chunk->hypertable_relid;\n\tstate->chunk_id = chunk->fd.id;\n\n\tMemoryContextSwitchTo(old_mcxt);\n\n\treturn state;\n}\n\nvoid\nts_set_compression_status(ChunkInsertState *state, const Chunk *chunk)\n{\n\tstate->chunk_compressed = ts_chunk_is_compressed(chunk);\n\tif (state->chunk_compressed)\n\t{\n\t\tstate->chunk_partial = ts_chunk_is_partial(chunk);\n\t}\n}\n\nextern void\nts_chunk_insert_state_destroy(ChunkInsertState *state, bool single_chunk_insert)\n{\n\t/*\n\t * Check if we need to mark the chunk as partial.\n\t * We need to change chunk status to partial in the following cases:\n\t * - rowstore insert into compressed chunk\n\t * - columnstore insert into uncompressed chunk that is not a new chunk (flagged as\n\t * needs_partial in chunk_tuple_routing.c)\n\t */\n\tif (state->chunk_compressed && !state->chunk_partial &&\n\t\t(!state->columnstore_insert || state->needs_partial))\n\t{\n\t\tOid chunk_relid = RelationGetRelid(state->result_relation_info->ri_RelationDesc);\n\t\tChunk *chunk = ts_chunk_get_by_relid(chunk_relid, true);\n\t\tts_chunk_set_partial(chunk);\n\t}\n\n\ttable_close(state->rel, NoLock);\n\tif (state->slot)\n\t\tExecDropSingleTupleTableSlot(state->slot);\n\n\t/*\n\t * Clean up per-chunk tuple table slots created for ON CONFLICT handling.\n\t */\n\tif (NULL != state->existing_slot)\n\t\tExecDropSingleTupleTableSlot(state->existing_slot);\n\n\t/* The ON CONFLICT projection slot is only chunk specific in case the\n\t * tuple descriptor didn't match the hypertable */\n\tif (NULL != state->hyper_to_chunk_map && NULL != state->conflproj_slot)\n\t\tExecDropSingleTupleTableSlot(state->conflproj_slot);\n\n\tif (!single_chunk_insert)\n\t{\n\t\tExecCloseIndices(state->result_relation_info);\n\t\tMemoryContextDelete(state->mctx);\n\t}\n}\n"
  },
  {
    "path": "src/chunk_insert_state.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <access/tupconvert.h>\n#include <funcapi.h>\n\n#include \"bmslist_utils.h\"\n#include \"cache.h\"\n#include \"chunk.h\"\n\ntypedef struct ChunkTupleRouting ChunkTupleRouting;\ntypedef struct CompressionSettings CompressionSettings;\ntypedef struct tuple_filtering_constraints tuple_filtering_constraints;\ntypedef struct Bloom1Hasher Bloom1Hasher;\n\n/*\n * Bundle the ScanKey and the attribute numbers together\n * to be able to update the scankey by replacing the\n * `sk_argument` field with the value from the actual slot.\n */\ntypedef struct ScanKeyWithAttnos\n{\n\tint num_scankeys;\n\tScanKeyData *scankeys;\n\tAttrNumber *attnos;\n} ScanKeyWithAttnos;\n\n/*\n * Holds information to cache scan keys and other\n * information needed for repeated calls of\n * `decompress_batches_for_insert` on the same chunk.\n */\ntypedef struct CachedDecompressionState\n{\n\tbool has_primary_or_unique_index;\n\tCompressionSettings *compression_settings;\n\ttuple_filtering_constraints *constraints;\n\t/* Columns that needs to be checked manually because\n\t * heap scan doesn't support SK_SEARCHNULL:\n\t */\n\tBitmapset *columns_with_null_check;\n\tScanKeyWithAttnos heap_scankeys;\n\tScanKeyWithAttnos index_scankeys;\n\tScanKeyWithAttnos mem_scankeys;\n\tOid index_relid;\n\n\t/*\n\t * Bloom information for UPSERT bloom optimization.\n\t * This is the best bloom filter match for the chunk out\n\t * of the (potentially) multiple bloom filters for the\n\t * chunk, based on the number of columns in the bloom filter.\n\t */\n\tchar *bloom_column_name;\n\tBitmapset *bloom_insert_attnums;\n\tAttrNumber upsert_bloom_attnum;\n\tBloom1Hasher *bloom_hasher;\n\n\t/* Pre-computed bloom filter checks for UPDATE/DELETE (List of BloomFilterCheck) */\n\tList *bloom_filters;\n} CachedDecompressionState;\n\ntypedef struct SharedCounters\n{\n\t/* Number of batches deleted */\n\tint64 batches_deleted;\n\t/* Number of batches decompressed into the uncompressed table */\n\tint64 batches_decompressed;\n\t/* Number of tuples decompressed */\n\tint64 tuples_decompressed;\n\t/* Number of batches scanned */\n\tint64 batches_scanned;\n\t/* Number of batches checked by bloom */\n\tint64 batches_checked_by_bloom;\n\t/* Number of batches pruned by bloom */\n\tint64 batches_pruned_by_bloom;\n\t/* Number of batches without bloom */\n\tint64 batches_without_bloom;\n\t/* Number of batches bloom false positives */\n\tint64 batches_bloom_false_positives;\n\t/* Number of batches skipped by pre-decompression filters */\n\tint64 batches_filtered_compressed;\n\t/* Number of batches filtered after decompression */\n\tint64 batches_filtered_decompressed;\n} SharedCounters;\n\ntypedef struct ChunkInsertState\n{\n\tRelation rel;\n\tResultRelInfo *result_relation_info;\n\n\t/* When the tuple descriptors for the main hypertable (root) and a chunk\n\t * differs, it is necessary to convert tuples to chunk format before\n\t * insertion, ON CONFLICT, or RETURNING handling. The table AM (storage format)\n\t * can also differ between the hypertable root and each chunk (as well as\n\t * between each chunk, in theory).\n\t *\n\t * The ResultRelInfo keeps per-relation slots for these purposes. The slots\n\t * here simply points to the per-relation slots in the ResultRelInfo.\n\t */\n\n\t/* Pointer to slot for projected tuple in ON CONFLICT handling */\n\tTupleTableSlot *conflproj_slot;\n\t/* Pointer to slot for tuple replaced in ON CONFLICT DO UPDATE\n\t * handling. Note that this slot's tuple descriptor is always the same as\n\t * the chunk rel's. */\n\tTupleTableSlot *existing_slot;\n\n\t/* Slot for inserted/new tuples going into the chunk */\n\tTupleTableSlot *slot;\n\t/* Map for converting tuple from hypertable (root table) format to chunk format */\n\tTupleConversionMap *hyper_to_chunk_map;\n\tMemoryContext mctx;\n\tEState *estate;\n\tOid hypertable_relid;\n\tint32 chunk_id;\n\tOid user_id;\n\n\t/* for tracking compressed chunks */\n\tbool chunk_compressed;\n\tbool chunk_partial;\n\tbool columnstore_insert;\n\tbool needs_partial;\n\n\t/* To speedup repeated calls of `decompress_batches_for_insert` */\n\tCachedDecompressionState *cached_decompression_state;\n\n\tOnConflictAction onConflictAction;\n\t/* Should this INSERT be skipped due to ON CONFLICT DO NOTHING */\n\tbool skip_current_tuple;\n\tSharedCounters *counters;\n} ChunkInsertState;\n\nextern ChunkInsertState *ts_chunk_insert_state_create(Oid chunk_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  const ChunkTupleRouting *ctr);\nextern void ts_chunk_insert_state_destroy(ChunkInsertState *state, bool single_chunk_insert);\nResultRelInfo *create_chunk_result_relation_info(ResultRelInfo *ht_rri, Relation rel,\n\t\t\t\t\t\t\t\t\t\t\t\t EState *estate);\n\nvoid ts_set_compression_status(ChunkInsertState *state, const Chunk *chunk);\n"
  },
  {
    "path": "src/chunk_scan.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <catalog/namespace.h>\n#include <storage/lmgr.h>\n#include <utils/builtins.h>\n#include <utils/syscache.h>\n\n#include \"chunk.h\"\n#include \"chunk_constraint.h\"\n#include \"chunk_scan.h\"\n#include \"debug_point.h\"\n#include \"dimension_vector.h\"\n#include \"guc.h\"\n#include \"hypercube.h\"\n#include \"hypertable.h\"\n#include \"scan_iterator.h\"\n#include \"utils.h\"\n\n/*\n * Scan for chunks matching a query.\n *\n * Given a number of dimension slices that match a query (a vector of slices\n * is given for each dimension), find the chunks that reference one slice in\n * each of the given dimensions. The matching chunks are built across multiple\n * scans:\n *\n * 1. Dimensional chunk constraints\n * 2. Chunk metadata\n * 3. Additional chunk constraints\n * 4. Chunk data nodes\n *\n * For performance, try not to interleave scans of different metadata tables\n * in order to maintain data locality while scanning. Also, keep scanned\n * tables and indexes open until all the metadata is scanned for all chunks.\n */\nChunk **\nts_chunk_scan_by_chunk_ids(const Hyperspace *hs, const List *chunk_ids, unsigned int *num_chunks)\n{\n\tMemoryContext work_mcxt =\n\t\tAllocSetContextCreate(CurrentMemoryContext, \"chunk-scan-work\", ALLOCSET_DEFAULT_SIZES);\n\tChunk **locked_chunks = NULL;\n\tint locked_chunk_count = 0;\n\tListCell *lc;\n\n\tAssert(OidIsValid(hs->main_table_relid));\n\tMemoryContext orig_mcxt = MemoryContextSwitchTo(work_mcxt);\n\n\t/*\n\t * For each matching chunk, fill in the metadata from the \"chunk\" table.\n\t * Make sure to filter out \"dropped\" chunks.\n\t */\n\tScanIterator chunk_it = ts_chunk_scan_iterator_create(orig_mcxt);\n\tlocked_chunks =\n\t\t(Chunk **) MemoryContextAlloc(orig_mcxt, sizeof(Chunk *) * list_length(chunk_ids));\n\tforeach (lc, chunk_ids)\n\t{\n\t\tint chunk_id = lfirst_int(lc);\n\n\t\tAssert(CurrentMemoryContext == work_mcxt);\n\n\t\tts_chunk_scan_iterator_set_chunk_id(&chunk_it, chunk_id);\n\t\tts_scan_iterator_start_or_restart_scan(&chunk_it);\n\t\tTupleInfo *ti = ts_scan_iterator_next(&chunk_it);\n\t\tif (ti == NULL)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\t\tbool isnull;\n\n\t\t/* We found a chunk. First, try to lock it. */\n\t\tName schema_name = DatumGetName(slot_getattr(ti->slot, Anum_chunk_schema_name, &isnull));\n\t\tAssert(!isnull);\n\t\tName table_name = DatumGetName(slot_getattr(ti->slot, Anum_chunk_table_name, &isnull));\n\t\tAssert(!isnull);\n\n\t\tOid chunk_reloid = ts_get_relation_relid(NameStr(*schema_name),\n\t\t\t\t\t\t\t\t\t\t\t\t NameStr(*table_name),\n\t\t\t\t\t\t\t\t\t\t\t\t /* return_invalid = */ false);\n\t\tAssert(OidIsValid(chunk_reloid));\n\n\t\t/* Only one chunk should match */\n\t\tAssert(ts_scan_iterator_next(&chunk_it) == NULL);\n\n\t\tDEBUG_WAITPOINT(\"hypertable_expansion_before_lock_chunk\");\n\t\tif (!ts_chunk_lock_if_exists(chunk_reloid, AccessShareLock))\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\t/*\n\t\t * Now after we have locked the chunk, we have to reread its metadata.\n\t\t * It might have been modified concurrently by decompression, for\n\t\t * example.\n\t\t */\n\t\tts_chunk_scan_iterator_set_chunk_id(&chunk_it, chunk_id);\n\t\tts_scan_iterator_start_or_restart_scan(&chunk_it);\n\t\tti = ts_scan_iterator_next(&chunk_it);\n\t\tAssert(ti != NULL);\n\t\tChunk *chunk = MemoryContextAllocZero(orig_mcxt, sizeof(Chunk));\n\n\t\tts_chunk_formdata_fill(&chunk->fd, ti);\n\n\t\tchunk->constraints = NULL;\n\t\tchunk->cube = NULL;\n\t\tchunk->hypertable_relid = hs->main_table_relid;\n\t\tchunk->table_id = chunk_reloid;\n\n\t\tlocked_chunks[locked_chunk_count] = chunk;\n\t\tlocked_chunk_count++;\n\n\t\t/* Only one chunk should match */\n\t\tAssert(ts_scan_iterator_next(&chunk_it) == NULL);\n\t}\n\n\tts_scan_iterator_close(&chunk_it);\n\n\tAssert(locked_chunk_count == 0 || locked_chunks != NULL);\n\tAssert(locked_chunk_count <= list_length(chunk_ids));\n\tAssert(CurrentMemoryContext == work_mcxt);\n\n\tfor (int i = 0; i < locked_chunk_count; i++)\n\t{\n\t\tChunk *chunk = locked_chunks[i];\n\t\tchunk->relkind = get_rel_relkind(chunk->table_id);\n\t}\n\n\t/*\n\t * Fetch the chunk constraints.\n\t */\n\tScanIterator constr_it = ts_chunk_constraint_scan_iterator_create(orig_mcxt);\n\n\tfor (int i = 0; i < locked_chunk_count; i++)\n\t{\n\t\tChunk *chunk = locked_chunks[i];\n\t\tchunk->constraints = ts_chunk_constraints_alloc(/* size_hint = */ 0, orig_mcxt);\n\n\t\tts_chunk_constraint_scan_iterator_set_chunk_id(&constr_it, chunk->fd.id);\n\t\tts_scan_iterator_start_or_restart_scan(&constr_it);\n\n\t\twhile (ts_scan_iterator_next(&constr_it) != NULL)\n\t\t{\n\t\t\tTupleInfo *constr_ti = ts_scan_iterator_tuple_info(&constr_it);\n\t\t\tts_chunk_constraints_add_from_tuple(chunk->constraints, constr_ti);\n\t\t}\n\t}\n\tts_scan_iterator_close(&constr_it);\n\n\t/*\n\t * Build hypercubes for the chunks by finding and combining the dimension\n\t * slices that match the chunk constraints.\n\t */\n\tScanIterator slice_iterator = ts_dimension_slice_scan_iterator_create(NULL, orig_mcxt);\n\tfor (int chunk_index = 0; chunk_index < locked_chunk_count; chunk_index++)\n\t{\n\t\tChunk *chunk = locked_chunks[chunk_index];\n\t\tChunkConstraints *constraints = chunk->constraints;\n\t\tMemoryContextSwitchTo(orig_mcxt);\n\t\tHypercube *cube = ts_hypercube_alloc(constraints->num_dimension_constraints);\n\t\tMemoryContextSwitchTo(work_mcxt);\n\t\tfor (int constraint_index = 0; constraint_index < constraints->num_constraints;\n\t\t\t constraint_index++)\n\t\t{\n\t\t\tChunkConstraint *constraint = &constraints->constraints[constraint_index];\n\t\t\tif (!is_dimension_constraint(constraint))\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * Find the slice by id. Don't have to lock it because the chunk is\n\t\t\t * locked.\n\t\t\t */\n\t\t\tconst int slice_id = constraint->fd.dimension_slice_id;\n\t\t\tDimensionSlice *slice_ptr =\n\t\t\t\tts_dimension_slice_scan_iterator_get_by_id(&slice_iterator, slice_id);\n\t\t\tif (slice_ptr == NULL)\n\t\t\t{\n\t\t\t\telog(ERROR, \"dimension slice %d is not found\", slice_id);\n\t\t\t}\n\t\t\tMemoryContextSwitchTo(orig_mcxt);\n\t\t\tDimensionSlice *slice_copy = ts_dimension_slice_create(slice_ptr->fd.dimension_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   slice_ptr->fd.range_start,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   slice_ptr->fd.range_end);\n\t\t\tslice_copy->fd.id = slice_ptr->fd.id;\n\t\t\tMemoryContextSwitchTo(work_mcxt);\n\t\t\tAssert(cube->capacity > cube->num_slices);\n\t\t\tcube->slices[cube->num_slices++] = slice_copy;\n\t\t}\n\n\t\tif (cube->num_slices == 0)\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t\t errmsg(\"chunk %s has no dimension slices\", get_rel_name(chunk->table_id))));\n\t\t}\n\n\t\tts_hypercube_slice_sort(cube);\n\t\tchunk->cube = cube;\n\t}\n\tts_scan_iterator_close(&slice_iterator);\n\n\tAssert(CurrentMemoryContext == work_mcxt);\n\tMemoryContextSwitchTo(orig_mcxt);\n\tMemoryContextDelete(work_mcxt);\n\n#ifdef USE_ASSERT_CHECKING\n\t/* Assert that we always return valid chunks */\n\tfor (int i = 0; i < locked_chunk_count; i++)\n\t{\n\t\tASSERT_IS_VALID_CHUNK(locked_chunks[i]);\n\t}\n#endif\n\n\t*num_chunks = locked_chunk_count;\n\tAssert(*num_chunks == 0 || locked_chunks != NULL);\n\treturn locked_chunks;\n}\n"
  },
  {
    "path": "src/chunk_scan.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include \"hypertable.h\"\n\nextern Chunk **ts_chunk_scan_by_chunk_ids(const Hyperspace *hs, const List *chunk_ids,\n\t\t\t\t\t\t\t\t\t\t  unsigned int *num_chunks);\n"
  },
  {
    "path": "src/chunk_tuple_routing.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/tableam.h>\n#include <storage/lockdefs.h>\n#include <utils/rls.h>\n\n#include \"chunk_insert_state.h\"\n#include \"chunk_tuple_routing.h\"\n#include \"cross_module_fn.h\"\n#include \"debug_point.h\"\n#include \"guc.h\"\n#include \"hypercube.h\"\n#include \"subspace_store.h\"\n\nChunkTupleRouting *\nts_chunk_tuple_routing_create(EState *estate, Hypertable *ht, ResultRelInfo *rri)\n{\n\tChunkTupleRouting *ctr;\n\tAssert(ht);\n\n\t/*\n\t * Here we attempt to expend as little effort as possible in setting up\n\t * the ChunkTupleRouting. Each partition's ResultRelInfo is built on\n\t * demand, only when we actually need to route a tuple to that partition.\n\t * The reason for this is that a common case is for INSERT to insert a\n\t * single tuple into a partitioned table and this must be fast.\n\t */\n\tctr = (ChunkTupleRouting *) palloc0(sizeof(ChunkTupleRouting));\n\tctr->root_rri = rri;\n\tctr->root_rel = rri->ri_RelationDesc;\n\tctr->estate = estate;\n\tctr->counters = palloc0(sizeof(SharedCounters));\n\tctr->hypertable = ht;\n\t/*\n\t * If the relid of ResultRelInfo does not match the Hypertable this is an operation\n\t * directly on a chunk.\n\t */\n\tctr->single_chunk_insert = ht->main_table_relid != RelationGetRelid(rri->ri_RelationDesc);\n\n\tctr->subspace = ts_subspace_store_init(ctr->hypertable->space,\n\t\t\t\t\t\t\t\t\t\t   estate->es_query_cxt,\n\t\t\t\t\t\t\t\t\t\t   ts_guc_max_open_chunks_per_insert);\n\n\tctr->has_dropped_attrs = false;\n\n\treturn ctr;\n}\n\nvoid\nts_chunk_tuple_routing_destroy(ChunkTupleRouting *ctr)\n{\n\tts_subspace_store_free(ctr->subspace);\n\n\tpfree(ctr);\n}\n\nstatic void\ndestroy_chunk_insert_state_single_chunk(void *cis)\n{\n\tts_chunk_insert_state_destroy((ChunkInsertState *) cis, true);\n}\n\nstatic void\ndestroy_chunk_insert_state(void *cis)\n{\n\tts_chunk_insert_state_destroy((ChunkInsertState *) cis, false);\n}\n\nextern ChunkInsertState *\nts_chunk_tuple_routing_find_chunk(ChunkTupleRouting *ctr, Point *point)\n{\n\tChunk *chunk = NULL;\n\tChunkInsertState *cis = NULL;\n\tcis = ts_subspace_store_get(ctr->subspace, point);\n\n\t/*\n\t * The chunk search functions may leak memory, so switch to a temporary\n\t * memory context.\n\t */\n\tMemoryContext old_context = MemoryContextSwitchTo(GetPerTupleMemoryContext(ctr->estate));\n\n\tif (!cis)\n\t{\n\t\tbool chunk_created = false;\n\t\tbool needs_partial = false;\n\t\tconst LOCKMODE lockmode = RowExclusiveLock;\n\n\t\t/*\n\t\t * Normally, for every row of the chunk except the first one, we expect\n\t\t * the chunk to exist already. The \"create\" function would take a lock\n\t\t * on the hypertable to serialize the concurrent chunk creation. Here we\n\t\t * first use the \"find\" function to try to find the chunk without\n\t\t * locking the hypertable. This serves as a fast path for the usual case\n\t\t * where the chunk already exists.\n\t\t */\n\t\tDEBUG_WAITPOINT(\"chunk_insert_before_lock\");\n\t\tchunk = ts_hypertable_find_chunk_for_point(ctr->hypertable, point, lockmode);\n\n\t\t/*\n\t\t * When inserting directly into a chunk, we should always find the chunk and\n\t\t * the returned chunk should match the relid we are inserting into.\n\t\t */\n\t\tif (ctr->single_chunk_insert)\n\t\t{\n\t\t\tif (!chunk || chunk->table_id != RelationGetRelid(ctr->root_rri->ri_RelationDesc))\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_CHECK_VIOLATION),\n\t\t\t\t\t\t errmsg(\"new row for relation \\\"%s\\\" violates chunk constraint\",\n\t\t\t\t\t\t\t\tRelationGetRelationName(ctr->root_rri->ri_RelationDesc))));\n\t\t}\n\n\t\tif (chunk && ts_chunk_is_frozen(chunk))\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"cannot INSERT into frozen chunk \\\"%s\\\"\",\n\t\t\t\t\t\t\tget_rel_name(chunk->table_id))));\n\n\t\tif (chunk && IS_OSM_CHUNK(chunk))\n\t\t{\n\t\t\tconst Dimension *time_dim = hyperspace_get_open_dimension(ctr->hypertable->space, 0);\n\t\t\tAssert(time_dim != NULL);\n\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"Cannot insert into tiered chunk range of %s.%s - attempt to create \"\n\t\t\t\t\t\t\t\"new chunk \"\n\t\t\t\t\t\t\t\"with range  [%s %s] failed\",\n\t\t\t\t\t\t\tNameStr(ctr->hypertable->fd.schema_name),\n\t\t\t\t\t\t\tNameStr(ctr->hypertable->fd.table_name),\n\t\t\t\t\t\t\tts_internal_to_time_string(chunk->cube->slices[0]->fd.range_start,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   time_dim->fd.column_type),\n\t\t\t\t\t\t\tts_internal_to_time_string(chunk->cube->slices[0]->fd.range_end,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   time_dim->fd.column_type)),\n\t\t\t\t\t errhint(\n\t\t\t\t\t\t \"Hypertable has tiered data with time range that overlaps the insert\")));\n\t\t}\n\n\t\tif (!chunk)\n\t\t{\n\t\t\tchunk = ts_hypertable_create_chunk_for_point(ctr->hypertable, point, lockmode);\n\t\t\tchunk_created = true;\n\t\t}\n\n\t\tEnsure(chunk, \"no chunk found or created\");\n\n#ifdef USE_ASSERT_CHECKING\n\t\t/* Ensure we always hold a lock on the chunk table at this point */\n\t\tRelation chunk_rel = RelationIdGetRelation(chunk->table_id);\n\t\tAssert(CheckRelationLockedByMe(chunk_rel, lockmode, true));\n\t\tRelationClose(chunk_rel);\n#endif\n\t\tif (ctr->create_compressed_chunk && !chunk->fd.compressed_chunk_id)\n\t\t{\n\t\t\t/*\n\t\t\t * When creating a compressed chunk, the operation must be\n\t\t\t * synchronized with other operations. A RowExclusiveLock is\n\t\t\t * already held on the chunk table itself so it will conflict with\n\t\t\t * explicit compress calls like compress_chunk() or\n\t\t\t * convert_to_columnstore() that take at least\n\t\t\t * ExclusiveLock. However, it is also necessary to synchronize\n\t\t\t * with other concurrent inserts doing the same thing.\n\t\t\t *\n\t\t\t * We don't want to do a lock upgrade on the chunk table since\n\t\t\t * that increases the risk of deadlocks.\n\t\t\t *\n\t\t\t * Instead we synchronize around a tuple lock on the chunk\n\t\t\t * metadata row since this is the row getting updated with new\n\t\t\t * compression status.\n\t\t\t */\n\t\t\tTM_Result lockres;\n\n\t\t\tDEBUG_WAITPOINT(\"insert_create_compressed\");\n\n\t\t\tlockres = ts_chunk_lock_for_creating_compressed_chunk(chunk->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  &chunk->fd.compressed_chunk_id);\n\n\t\t\t/*\n\t\t\t * Since the locking function blocks and follows the update chain,\n\t\t\t * the only reasonable return value is TM_Ok. Everything else is\n\t\t\t * an error.\n\t\t\t */\n\t\t\tEnsure(lockres == TM_Ok,\n\t\t\t\t   \"could not lock chunk row for creating \"\n\t\t\t\t   \"compressed chunk. Lock result %d\",\n\t\t\t\t   lockres);\n\n\t\t\t/* recheck whether compressed chunk exists after acquiring the lock */\n\t\t\tif (!chunk->fd.compressed_chunk_id)\n\t\t\t{\n\t\t\t\tHypertable *compressed_ht =\n\t\t\t\t\tts_hypertable_get_by_id(ctr->hypertable->fd.compressed_hypertable_id);\n\t\t\t\tChunk *compressed_chunk =\n\t\t\t\t\tts_cm_functions->compression_chunk_create(compressed_ht, chunk);\n\t\t\t\tts_chunk_set_compressed_chunk(chunk, compressed_chunk->fd.id);\n\t\t\t\tchunk->fd.compressed_chunk_id = compressed_chunk->fd.id;\n\n\t\t\t\t/* mark chunk as partial unless completely new chunk */\n\t\t\t\tif (!chunk_created)\n\t\t\t\t\tneeds_partial = true;\n\t\t\t}\n\t\t}\n\n\t\tcis = ts_chunk_insert_state_create(chunk->table_id, ctr);\n\t\tcis->needs_partial = needs_partial;\n\t\tts_subspace_store_add(ctr->subspace,\n\t\t\t\t\t\t\t  chunk->cube,\n\t\t\t\t\t\t\t  cis,\n\t\t\t\t\t\t\t  ctr->single_chunk_insert ? destroy_chunk_insert_state_single_chunk :\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t destroy_chunk_insert_state);\n\t}\n\n\tMemoryContextSwitchTo(old_context);\n\n\tAssert(cis != NULL);\n\n\treturn cis;\n}\n\nextern void\nts_chunk_tuple_routing_decompress_for_insert(ChunkInsertState *cis, ResultRelInfo *root_rri,\n\t\t\t\t\t\t\t\t\t\t\t TupleTableSlot *slot, EState *estate,\n\t\t\t\t\t\t\t\t\t\t\t bool update_counter)\n{\n\tif (!cis->chunk_compressed || (cis->cached_decompression_state &&\n\t\t\t\t\t\t\t\t   !cis->cached_decompression_state->has_primary_or_unique_index))\n\t\treturn;\n\n\t/*\n\t * If this is an INSERT into a compressed chunk with UNIQUE or\n\t * PRIMARY KEY constraints we need to make sure any batches that could\n\t * potentially lead to a conflict are in the decompressed chunk so\n\t * postgres can do proper constraint checking.\n\t */\n\n\tts_cm_functions->init_decompress_state_for_insert(cis, slot);\n\n\t/* If we are dealing with generated stored columns, generate the values\n\t * so can use it for uniqueness checks.\n\t */\n\tRelation resultRelationDesc = cis->rel;\n\tif (resultRelationDesc->rd_att->constr &&\n\t\tresultRelationDesc->rd_att->constr->has_generated_stored)\n\t{\n\t\tslot->tts_tableOid = RelationGetRelid(resultRelationDesc);\n\t\tExecComputeStoredGenerated(root_rri, estate, slot, CMD_INSERT);\n\t}\n\tts_cm_functions->decompress_batches_for_insert(cis, slot);\n\n\t/* mark rows visible */\n\tif (update_counter)\n\t\testate->es_output_cid = GetCurrentCommandId(true);\n\n\tif (ts_guc_max_tuples_decompressed_per_dml > 0)\n\t{\n\t\tif (cis->counters->tuples_decompressed > ts_guc_max_tuples_decompressed_per_dml)\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),\n\t\t\t\t\t errmsg(\"tuple decompression limit exceeded by operation\"),\n\t\t\t\t\t errdetail(\"current limit: %d, tuples decompressed: %lld\",\n\t\t\t\t\t\t\t   ts_guc_max_tuples_decompressed_per_dml,\n\t\t\t\t\t\t\t   (long long int) cis->counters->tuples_decompressed),\n\t\t\t\t\t errhint(\"Consider increasing \"\n\t\t\t\t\t\t\t \"timescaledb.max_tuples_decompressed_per_dml_transaction or set \"\n\t\t\t\t\t\t\t \"to 0 (unlimited).\")));\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/chunk_tuple_routing.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <executor/nodeModifyTable.h>\n\n#include \"chunk_insert_state.h\"\n#include \"hypertable.h\"\n\ntypedef struct ModifyHypertableState ModifyHypertableState;\n\ntypedef struct ChunkTupleRouting\n{\n\tHypertable *hypertable;\n\t/*\n\t * When single_chunk_insert is true, root_rel and root_rri point to the\n\t * chunk being inserted into. Otherwise, they point to the hypertable.\n\t */\n\tRelation root_rel;\n\tResultRelInfo *root_rri;\n\tbool single_chunk_insert;\n\n\tSubspaceStore *subspace;\n\tEState *estate;\n\tbool create_compressed_chunk;\n\tbool has_dropped_attrs;\n\n\tModifyHypertableState *mht_state; /* state for the ModifyHypertable custom scan node */\n\tChunkInsertState *cis;\n\n\tSharedCounters *counters; /* shared counters for the current statement */\n} ChunkTupleRouting;\n\nChunkTupleRouting *ts_chunk_tuple_routing_create(EState *estate, Hypertable *ht,\n\t\t\t\t\t\t\t\t\t\t\t\t ResultRelInfo *rri);\nvoid ts_chunk_tuple_routing_destroy(ChunkTupleRouting *ctr);\nChunkInsertState *ts_chunk_tuple_routing_find_chunk(ChunkTupleRouting *ctr, Point *point);\nextern void ts_chunk_tuple_routing_decompress_for_insert(ChunkInsertState *cis,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t ResultRelInfo *root_rri,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t TupleTableSlot *slot, EState *estate,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t bool update_counter);\n"
  },
  {
    "path": "src/compat/CMakeLists.txt",
    "content": "set(SOURCES)\n\ntarget_sources(${PROJECT_NAME} PRIVATE ${SOURCES})\n"
  },
  {
    "path": "src/compat/compat-msvc-enter.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#pragma once\n\n#include <postgres.h>\n\n/*\n * Not all exported data symbols in PostgreSQL are marked with PGDLLIMPORT,\n * which causes errors during linking. This hack turns all extern symbols into\n * properly exported symbols so we can use them in our code. Only necessary\n * for files that use these incorrectly unlabeled data symbols (e.g. extension.c)\n *\n * NOTE: Applies to data symbols only, not functions\n */\n#ifdef _MSC_VER\n#undef PGDLLIMPORT\n#define PGDLLIMPORT\n#define extern extern _declspec(dllimport)\n\n#include <catalog/genbki.h>\n#undef DECLARE_TOAST\n#undef DECLARE_INDEX\n#undef DECLARE_UNIQUE_INDEX\n#undef DECLARE_UNIQUE_INDEX_PKEY\n#undef DECLARE_FOREIGN_KEY\n#undef DECLARE_FOREIGN_KEY_OPT\n#undef DECLARE_ARRAY_FOREIGN_KEY\n#undef DECLARE_ARRAY_FOREIGN_KEY_OPT\n\n#define DECLARE_TOAST(name, toastoid, indexoid)\n#define DECLARE_INDEX(name, oid, decl)\n#define DECLARE_UNIQUE_INDEX(name, oid, decl)\n#define DECLARE_UNIQUE_INDEX_PKEY(name, oid, decl)\n#define DECLARE_FOREIGN_KEY(cols, reftbl, refcols)\n#define DECLARE_FOREIGN_KEY_OPT(cols, reftbl, refcols)\n#define DECLARE_ARRAY_FOREIGN_KEY(cols, reftbl, refcols)\n#define DECLARE_ARRAY_FOREIGN_KEY_OPT(cols, reftbl, refcols)\n\n#endif /* _MSC_VER */\n"
  },
  {
    "path": "src/compat/compat-msvc-exit.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#pragma once\n\n/*\n * Included after all files that need compatibility are included, this undoes\n * the 'extern' macro so as not to break other headers (e.g. Windows headers).\n */\n#ifdef _MSC_VER\n#undef extern\n#undef PGDLLIMPORT\n#define PGDLLIMPORT __declspec(dllexport)\n#endif /* _MSC_VER */\n"
  },
  {
    "path": "src/compat/compat.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include <commands/cluster.h>\n#include <commands/defrem.h>\n#include <commands/explain.h>\n#include <commands/trigger.h>\n#include <commands/vacuum.h>\n#include <executor/executor.h>\n#include <executor/tuptable.h>\n#include <nodes/execnodes.h>\n#include <nodes/nodes.h>\n#include <optimizer/restrictinfo.h>\n#include <pgstat.h>\n#include <storage/lmgr.h>\n#include <utils/jsonb.h>\n#include <utils/lsyscache.h>\n#include <utils/rel.h>\n\n#include \"export.h\"\n\n#define PG_MAJOR_MIN 15\n\n/*\n * Prevent building against upstream versions that had ABI breaking change (15.9, 16.5, 17.1)\n * that was reverted in the following release.\n */\n\n#define is_supported_pg_version_15(version) ((version >= 150010) && (version < 160000))\n#define is_supported_pg_version_16(version) ((version >= 160006) && (version < 170000))\n#define is_supported_pg_version_17(version) ((version >= 170002) && (version < 180000))\n#define is_supported_pg_version_18(version) ((version >= 180000) && (version < 190000))\n\n/*\n * To compile with an unsupported version, use -DEXPERIMENTAL=ON with cmake.\n * (Useful when testing with unreleased versions)\n */\n#define is_supported_pg_version(version)                                                           \\\n\t(is_supported_pg_version_15(version) || is_supported_pg_version_16(version) ||                 \\\n\t is_supported_pg_version_17(version) || is_supported_pg_version_18(version))\n\n#define PG15 is_supported_pg_version_15(PG_VERSION_NUM)\n#define PG16 is_supported_pg_version_16(PG_VERSION_NUM)\n#define PG17 is_supported_pg_version_17(PG_VERSION_NUM)\n#define PG18 is_supported_pg_version_18(PG_VERSION_NUM)\n\n#define PG15_LT (PG_VERSION_NUM < 150000)\n#define PG15_GE (PG_VERSION_NUM >= 150000)\n#define PG16_LT (PG_VERSION_NUM < 160000)\n#define PG16_GE (PG_VERSION_NUM >= 160000)\n#define PG17_LT (PG_VERSION_NUM < 170000)\n#define PG17_GE (PG_VERSION_NUM >= 170000)\n#define PG18_LT (PG_VERSION_NUM < 180000)\n#define PG18_GE (PG_VERSION_NUM >= 180000)\n\n#if !(is_supported_pg_version(PG_VERSION_NUM))\n#error \"Unsupported PostgreSQL version\"\n#endif\n\n#if ((PG_VERSION_NUM >= 150009 && PG_VERSION_NUM < 160000) ||                                      \\\n\t (PG_VERSION_NUM >= 160005 && PG_VERSION_NUM < 170000) || (PG_VERSION_NUM >= 170001))\n/*\n * The above versions introduced a fix for potentially losing updates to\n * pg_class and pg_database due to inplace updates done to those catalog\n * tables by PostgreSQL. The fix requires taking a lock on the tuple via\n * SearchSysCacheLocked1(). For older PG versions, we just map the new\n * function to the unlocked version and the unlocking of the tuple is a noop.\n *\n * https://github.com/postgres/postgres/commit/3b7a689e1a805c4dac2f35ff14fd5c9fdbddf150\n *\n * Here's an excerpt from README.tuplock that explains the need for additional\n * tuple locks:\n *\n * If IsInplaceUpdateRelation() returns true for a table, the table is a\n * system catalog that receives systable_inplace_update_begin() calls.\n * Preparing a heap_update() of these tables follows additional locking rules,\n * to ensure we don't lose the effects of an inplace update. In particular,\n * consider a moment when a backend has fetched the old tuple to modify, not\n * yet having called heap_update(). Another backend's inplace update starting\n * then can't conclude until the heap_update() places its new tuple in a\n * buffer. We enforce that using locktags as follows. While DDL code is the\n * main audience, the executor follows these rules to make e.g. \"MERGE INTO\n * pg_class\" safer. Locking rules are per-catalog:\n *\n * pg_class heap_update() callers: before copying the tuple to modify, take a\n * lock on the tuple, a ShareUpdateExclusiveLock on the relation, or a\n * ShareRowExclusiveLock or stricter on the relation.\n */\n#define SYSCACHE_TUPLE_LOCK_NEEDED 1\n#define AssertSufficientPgClassUpdateLockHeld(relid)                                               \\\n\tAssert(CheckRelationOidLockedByMe(relid, ShareUpdateExclusiveLock, false) ||                   \\\n\t\t   CheckRelationOidLockedByMe(relid, ShareRowExclusiveLock, true));\n#define UnlockSysCacheTuple(rel, tid) UnlockTuple(rel, tid, InplaceUpdateTupleLock);\n#else\n#define SearchSysCacheLockedCopy1(rel, datum) SearchSysCacheCopy1(rel, datum)\n#define UnlockSysCacheTuple(rel, tid)\n#define AssertSufficientPgClassUpdateLockHeld(relid)\n#endif\n\n/*\n * The following are compatibility functions for different versions of\n * PostgreSQL. Each compatibility function (or group) has its own logic for\n * which versions get different behavior and is separated from others by a\n * comment with its name and any clarifying notes about supported behavior. Each\n * compatibility define is separated out by function so that it is easier to see\n * what changed about its behavior, and at what version, but closely related\n * functions that changed at the same time may be grouped together into a single\n * block. Compatibility functions are organized in alphabetical order.\n *\n * Wherever reasonable, we try to achieve forwards compatibility so that we can\n * take advantage of features added in newer PG versions. This avoids some\n * future tech debt, though may not always be possible.\n *\n * We append \"compat\" to the name of the function or define if we change the behavior\n * of something that existed in a previous version. If we are merely backpatching\n * behavior from a later version to an earlier version and not changing the\n * behavior of the new version we simply adopt the new version's name.\n */\n\n#if PG16_LT\n#define ExecInsertIndexTuplesCompat(rri,                                                           \\\n\t\t\t\t\t\t\t\t\tslot,                                                          \\\n\t\t\t\t\t\t\t\t\testate,                                                        \\\n\t\t\t\t\t\t\t\t\tupdate,                                                        \\\n\t\t\t\t\t\t\t\t\tnoDupErr,                                                      \\\n\t\t\t\t\t\t\t\t\tspecConflict,                                                  \\\n\t\t\t\t\t\t\t\t\tarbiterIndexes,                                                \\\n\t\t\t\t\t\t\t\t\tonlySummarizing)                                               \\\n\tExecInsertIndexTuples(rri, slot, estate, update, noDupErr, specConflict, arbiterIndexes)\n#else\n#define ExecInsertIndexTuplesCompat(rri,                                                           \\\n\t\t\t\t\t\t\t\t\tslot,                                                          \\\n\t\t\t\t\t\t\t\t\testate,                                                        \\\n\t\t\t\t\t\t\t\t\tupdate,                                                        \\\n\t\t\t\t\t\t\t\t\tnoDupErr,                                                      \\\n\t\t\t\t\t\t\t\t\tspecConflict,                                                  \\\n\t\t\t\t\t\t\t\t\tarbiterIndexes,                                                \\\n\t\t\t\t\t\t\t\t\tonlySummarizing)                                               \\\n\tExecInsertIndexTuples(rri,                                                                     \\\n\t\t\t\t\t\t  slot,                                                                    \\\n\t\t\t\t\t\t  estate,                                                                  \\\n\t\t\t\t\t\t  update,                                                                  \\\n\t\t\t\t\t\t  noDupErr,                                                                \\\n\t\t\t\t\t\t  specConflict,                                                            \\\n\t\t\t\t\t\t  arbiterIndexes,                                                          \\\n\t\t\t\t\t\t  onlySummarizing)\n#endif\n\n/*\n * PG16 removed outerjoin_delayed, nullable_relids arguments from make_restrictinfo\n * https://github.com/postgres/postgres/commit/b448f1c8d8\n *\n * PG16 adds three new parameter - has_clone, is_clone and incompatible_relids, as a\n * part of fixing the filtering of \"cloned\" outer-join quals\n * https://github.com/postgres/postgres/commit/991a3df227\n */\n\n#if PG16_LT\n#define make_restrictinfo_compat(root,                                                             \\\n\t\t\t\t\t\t\t\t clause,                                                           \\\n\t\t\t\t\t\t\t\t is_pushed_down,                                                   \\\n\t\t\t\t\t\t\t\t has_clone,                                                        \\\n\t\t\t\t\t\t\t\t is_clone,                                                         \\\n\t\t\t\t\t\t\t\t outerjoin_delayed,                                                \\\n\t\t\t\t\t\t\t\t pseudoconstant,                                                   \\\n\t\t\t\t\t\t\t\t security_level,                                                   \\\n\t\t\t\t\t\t\t\t required_relids,                                                  \\\n\t\t\t\t\t\t\t\t incompatible_relids,                                              \\\n\t\t\t\t\t\t\t\t outer_relids,                                                     \\\n\t\t\t\t\t\t\t\t nullable_relids)                                                  \\\n\tmake_restrictinfo(root,                                                                        \\\n\t\t\t\t\t  clause,                                                                      \\\n\t\t\t\t\t  is_pushed_down,                                                              \\\n\t\t\t\t\t  outerjoin_delayed,                                                           \\\n\t\t\t\t\t  pseudoconstant,                                                              \\\n\t\t\t\t\t  security_level,                                                              \\\n\t\t\t\t\t  required_relids,                                                             \\\n\t\t\t\t\t  outer_relids,                                                                \\\n\t\t\t\t\t  nullable_relids)\n#else\n#define make_restrictinfo_compat(root,                                                             \\\n\t\t\t\t\t\t\t\t clause,                                                           \\\n\t\t\t\t\t\t\t\t is_pushed_down,                                                   \\\n\t\t\t\t\t\t\t\t has_clone,                                                        \\\n\t\t\t\t\t\t\t\t is_clone,                                                         \\\n\t\t\t\t\t\t\t\t outerjoin_delayed,                                                \\\n\t\t\t\t\t\t\t\t pseudoconstant,                                                   \\\n\t\t\t\t\t\t\t\t security_level,                                                   \\\n\t\t\t\t\t\t\t\t required_relids,                                                  \\\n\t\t\t\t\t\t\t\t incompatible_relids,                                              \\\n\t\t\t\t\t\t\t\t outer_relids,                                                     \\\n\t\t\t\t\t\t\t\t nullable_relids)                                                  \\\n\tmake_restrictinfo(root,                                                                        \\\n\t\t\t\t\t  clause,                                                                      \\\n\t\t\t\t\t  is_pushed_down,                                                              \\\n\t\t\t\t\t  has_clone,                                                                   \\\n\t\t\t\t\t  is_clone,                                                                    \\\n\t\t\t\t\t  pseudoconstant,                                                              \\\n\t\t\t\t\t  security_level,                                                              \\\n\t\t\t\t\t  required_relids,                                                             \\\n\t\t\t\t\t  incompatible_relids,                                                         \\\n\t\t\t\t\t  outer_relids)\n#endif\n\n/* fmgr\n * In a9c35cf postgres changed how it calls SQL functions so that the number of\n * argument-slots allocated is chosen dynamically, instead of being fixed. This\n * change was ABI-breaking, so we cannot backport this optimization, however,\n * we do backport the interface, so that all our code will be compatible with\n * new versions.\n */\n\n/* convenience macro to allocate FunctionCallInfoData on the heap */\n#define HEAP_FCINFO(nargs) palloc(SizeForFunctionCallInfo(nargs))\n\n/* getting arguments has a different API, so these macros unify the versions */\n#define FC_ARG(fcinfo, n) ((fcinfo)->args[(n)].value)\n#define FC_NULL(fcinfo, n) ((fcinfo)->args[(n)].isnull)\n\n#define FC_FN_OID(fcinfo) ((fcinfo)->flinfo->fn_oid)\n\n/* convenience setters */\n#define FC_SET_ARG(fcinfo, n, val)                                                                 \\\n\tdo                                                                                             \\\n\t{                                                                                              \\\n\t\tshort _n = (n);                                                                            \\\n\t\tFunctionCallInfo _fcinfo = (fcinfo);                                                       \\\n\t\tFC_ARG(_fcinfo, _n) = (val);                                                               \\\n\t\tFC_NULL(_fcinfo, _n) = false;                                                              \\\n\t} while (0)\n\n#define FC_SET_NULL(fcinfo, n)                                                                     \\\n\tdo                                                                                             \\\n\t{                                                                                              \\\n\t\tshort _n = (n);                                                                            \\\n\t\tFunctionCallInfo _fcinfo = (fcinfo);                                                       \\\n\t\tFC_ARG(_fcinfo, _n) = 0;                                                                   \\\n\t\tFC_NULL(_fcinfo, _n) = true;                                                               \\\n\t} while (0)\n\n/* create this function for symmetry with pq_sendint32 */\n#define pq_getmsgint32(buf) pq_getmsgint(buf, 4)\n\n#define ts_tuptableslot_set_table_oid(slot, table_oid) (slot)->tts_tableOid = table_oid\n\nstatic inline ClusterParams *\nget_cluster_options(const ClusterStmt *stmt)\n{\n\tListCell *lc;\n\tClusterParams *params = palloc0(sizeof(ClusterParams));\n\tbool verbose = false;\n\n\t/* Parse option list */\n\tforeach (lc, stmt->params)\n\t{\n\t\tDefElem *opt = (DefElem *) lfirst(lc);\n\t\tif (strcmp(opt->defname, \"verbose\") == 0)\n\t\t\tverbose = defGetBoolean(opt);\n\t\telse\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_SYNTAX_ERROR),\n\t\t\t\t\t errmsg(\"unrecognized CLUSTER option \\\"%s\\\"\", opt->defname),\n\t\t\t\t\t parser_errposition(NULL, opt->location)));\n\t}\n\n\tparams->options = (verbose ? CLUOPT_VERBOSE : 0);\n\n\treturn params;\n}\n\n#include <catalog/index.h>\n\nstatic inline int\nget_reindex_options(ReindexStmt *stmt)\n{\n\tListCell *lc;\n\tbool concurrently = false;\n\tbool verbose = false;\n\n\t/* Parse option list */\n\tforeach (lc, stmt->params)\n\t{\n\t\tDefElem *opt = (DefElem *) lfirst(lc);\n\t\tif (strcmp(opt->defname, \"verbose\") == 0)\n\t\t\tverbose = defGetBoolean(opt);\n\t\telse if (strcmp(opt->defname, \"concurrently\") == 0)\n\t\t\tconcurrently = defGetBoolean(opt);\n\t\telse\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_SYNTAX_ERROR),\n\t\t\t\t\t errmsg(\"unrecognized REINDEX option \\\"%s\\\"\", opt->defname),\n\t\t\t\t\t parser_errposition(NULL, opt->location)));\n\t}\n\treturn (verbose ? REINDEXOPT_VERBOSE : 0) | (concurrently ? REINDEXOPT_CONCURRENTLY : 0);\n}\n\n/*\n * define some list macros for convenience\n */\n#define lfifth(l) lfirst(list_nth_cell(l, 4))\n#define lfifth_int(l) lfirst_int(list_nth_cell(l, 4))\n\n#if PG16_LT\n/*\n * PG15 consolidate VACUUM xid cutoff logic.\n *\n * https://github.com/postgres/postgres/commit/efa4a946\n *\n * PG16 introduced VacuumCutoffs so define here for previous PG versions.\n */\nstruct VacuumCutoffs\n{\n\tTransactionId relfrozenxid;\n\tMultiXactId relminmxid;\n\tTransactionId OldestXmin;\n\tMultiXactId OldestMxact;\n\tTransactionId FreezeLimit;\n\tMultiXactId MultiXactCutoff;\n};\n\nstatic inline bool\nvacuum_get_cutoffs(Relation rel, const VacuumParams *params, struct VacuumCutoffs *cutoffs)\n{\n\treturn vacuum_set_xid_limits(rel,\n\t\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t\t &cutoffs->OldestXmin,\n\t\t\t\t\t\t\t\t &cutoffs->OldestMxact,\n\t\t\t\t\t\t\t\t &cutoffs->FreezeLimit,\n\t\t\t\t\t\t\t\t &cutoffs->MultiXactCutoff);\n}\n#endif\n\n/*\n * PG16 adds TMResult argument to ExecBRUpdateTriggers\n * https://github.com/postgres/postgres/commit/7103ebb7\n * this was backported to PG15 in\n * https://github.com/postgres/postgres/commit/7d9a75713ab9\n */\n#if PG15\n#define ExecBRUpdateTriggers(estate,                                                               \\\n\t\t\t\t\t\t\t epqstate,                                                             \\\n\t\t\t\t\t\t\t resultRelInfo,                                                        \\\n\t\t\t\t\t\t\t tupleid,                                                              \\\n\t\t\t\t\t\t\t oldtuple,                                                             \\\n\t\t\t\t\t\t\t slot,                                                                 \\\n\t\t\t\t\t\t\t result,                                                               \\\n\t\t\t\t\t\t\t tmfdp)                                                                \\\n\tExecBRUpdateTriggersNew(estate, epqstate, resultRelInfo, tupleid, oldtuple, slot, result, tmfdp)\n#endif\n\n/*\n * PG16 adds TMResult argument to ExecBRDeleteTriggers\n * https://github.com/postgres/postgres/commit/9321c79c\n * this was backported to PG15 in\n * https://github.com/postgres/postgres/commit/7d9a75713ab9\n */\n#if PG15\n#define ExecBRDeleteTriggers(estate,                                                               \\\n\t\t\t\t\t\t\t epqstate,                                                             \\\n\t\t\t\t\t\t\t relinfo,                                                              \\\n\t\t\t\t\t\t\t tupleid,                                                              \\\n\t\t\t\t\t\t\t fdw_trigtuple,                                                        \\\n\t\t\t\t\t\t\t epqslot,                                                              \\\n\t\t\t\t\t\t\t tmresult,                                                             \\\n\t\t\t\t\t\t\t tmfd)                                                                 \\\n\tExecBRDeleteTriggersNew(estate,                                                                \\\n\t\t\t\t\t\t\tepqstate,                                                              \\\n\t\t\t\t\t\t\trelinfo,                                                               \\\n\t\t\t\t\t\t\ttupleid,                                                               \\\n\t\t\t\t\t\t\tfdw_trigtuple,                                                         \\\n\t\t\t\t\t\t\tepqslot,                                                               \\\n\t\t\t\t\t\t\ttmresult,                                                              \\\n\t\t\t\t\t\t\ttmfd)\n#endif\n\n#if PG16_GE\n#define pgstat_get_local_beentry_by_index_compat(idx) pgstat_get_local_beentry_by_index(idx)\n#else\n#define pgstat_get_local_beentry_by_index_compat(idx) pgstat_fetch_stat_local_beentry(idx)\n#endif\n\n/*\n * PG16 adds a new parameter to DefineIndex, total_parts, that takes\n * in the total number of direct and indirect partitions of the relation.\n *\n * https://github.com/postgres/postgres/commit/27f5c712\n */\n#if PG16_LT\n#define DefineIndexCompat(relationId,                                                              \\\n\t\t\t\t\t\t  stmt,                                                                    \\\n\t\t\t\t\t\t  indexRelationId,                                                         \\\n\t\t\t\t\t\t  parentIndexId,                                                           \\\n\t\t\t\t\t\t  parentConstraintId,                                                      \\\n\t\t\t\t\t\t  total_parts,                                                             \\\n\t\t\t\t\t\t  is_alter_table,                                                          \\\n\t\t\t\t\t\t  check_rights,                                                            \\\n\t\t\t\t\t\t  check_not_in_use,                                                        \\\n\t\t\t\t\t\t  skip_build,                                                              \\\n\t\t\t\t\t\t  quiet)                                                                   \\\n\tDefineIndex(relationId,                                                                        \\\n\t\t\t\tstmt,                                                                              \\\n\t\t\t\tindexRelationId,                                                                   \\\n\t\t\t\tparentIndexId,                                                                     \\\n\t\t\t\tparentConstraintId,                                                                \\\n\t\t\t\tis_alter_table,                                                                    \\\n\t\t\t\tcheck_rights,                                                                      \\\n\t\t\t\tcheck_not_in_use,                                                                  \\\n\t\t\t\tskip_build,                                                                        \\\n\t\t\t\tquiet)\n#else\n#define DefineIndexCompat(relationId,                                                              \\\n\t\t\t\t\t\t  stmt,                                                                    \\\n\t\t\t\t\t\t  indexRelationId,                                                         \\\n\t\t\t\t\t\t  parentIndexId,                                                           \\\n\t\t\t\t\t\t  parentConstraintId,                                                      \\\n\t\t\t\t\t\t  total_parts,                                                             \\\n\t\t\t\t\t\t  is_alter_table,                                                          \\\n\t\t\t\t\t\t  check_rights,                                                            \\\n\t\t\t\t\t\t  check_not_in_use,                                                        \\\n\t\t\t\t\t\t  skip_build,                                                              \\\n\t\t\t\t\t\t  quiet)                                                                   \\\n\tDefineIndex(relationId,                                                                        \\\n\t\t\t\tstmt,                                                                              \\\n\t\t\t\tindexRelationId,                                                                   \\\n\t\t\t\tparentIndexId,                                                                     \\\n\t\t\t\tparentConstraintId,                                                                \\\n\t\t\t\ttotal_parts,                                                                       \\\n\t\t\t\tis_alter_table,                                                                    \\\n\t\t\t\tcheck_rights,                                                                      \\\n\t\t\t\tcheck_not_in_use,                                                                  \\\n\t\t\t\tskip_build,                                                                        \\\n\t\t\t\tquiet)\n#endif\n\n#if PG16_LT\n#include <catalog/pg_database_d.h>\n#include <catalog/pg_foreign_server_d.h>\n#include <catalog/pg_namespace_d.h>\n#include <catalog/pg_proc_d.h>\n#include <catalog/pg_tablespace_d.h>\n#include <utils/acl.h>\n\n/*\n * PG16 replaces most aclcheck functions with a common object_aclcheck() function\n * https://github.com/postgres/postgres/commit/c727f511\n */\nstatic inline AclResult\nobject_aclcheck(Oid classid, Oid objectid, Oid roleid, AclMode mode)\n{\n\tswitch (classid)\n\t{\n\t\tcase DatabaseRelationId:\n\t\t\treturn pg_database_aclcheck(objectid, roleid, mode);\n\t\tcase ForeignServerRelationId:\n\t\t\treturn pg_foreign_server_aclcheck(objectid, roleid, mode);\n\t\tcase NamespaceRelationId:\n\t\t\treturn pg_namespace_aclcheck(objectid, roleid, mode);\n\t\tcase ProcedureRelationId:\n\t\t\treturn pg_proc_aclcheck(objectid, roleid, mode);\n\t\tcase TableSpaceRelationId:\n\t\t\treturn pg_tablespace_aclcheck(objectid, roleid, mode);\n\t\tdefault:\n\t\t\tAssert(false);\n\t}\n\treturn ACLCHECK_NOT_OWNER;\n}\n\n/*\n * PG16 replaces pg_foo_ownercheck() functions with a common object_ownercheck() function\n * https://github.com/postgres/postgres/commit/afbfc029\n */\nstatic inline bool\nobject_ownercheck(Oid classid, Oid objectid, Oid roleid)\n{\n\tswitch (classid)\n\t{\n\t\tcase RelationRelationId:\n\t\t\treturn pg_class_ownercheck(objectid, roleid);\n\t\tdefault:\n\t\t\tAssert(false);\n\t}\n\treturn false;\n}\n#endif\n\n#if PG17_LT\n/*\n * Backport of RestrictSearchPath() from PG17\n *\n * We skip the check for IsBootstrapProcessingMode() since it creates problems\n * on Windows builds and we don't need it for our use case.\n */\n#include <utils/guc.h>\nstatic inline void\nRestrictSearchPath(void)\n{\n\tset_config_option(\"search_path\",\n\t\t\t\t\t  \"pg_catalog, pg_temp\",\n\t\t\t\t\t  PGC_USERSET,\n\t\t\t\t\t  PGC_S_SESSION,\n\t\t\t\t\t  GUC_ACTION_SAVE,\n\t\t\t\t\t  true,\n\t\t\t\t\t  0,\n\t\t\t\t\t  false);\n}\n#endif\n\n#if PG17_LT\n\n/* This macro was renamed in PG17, see 414f6c0fb79a */\n#define WAIT_EVENT_MESSAGE_QUEUE_INTERNAL WAIT_EVENT_MQ_INTERNAL\n\n/* 'flush' argument was added in 173b56f1ef59 */\n#define LogLogicalMessageCompat(prefix, message, size, transactional, flush)                       \\\n\tLogLogicalMessage(prefix, message, size, transactional)\n\n/* 'stmt' argument was added in f21848de2013 */\n#define reindex_relation_compat(stmt, relid, flags, params) reindex_relation(relid, flags, params)\n\n/* 'vacuum_is_relation_owner' was renamed to 'vacuum_is_permitted_for_relation' in ecb0fd33720f */\n#define vacuum_is_permitted_for_relation_compat(relid, reltuple, options)                          \\\n\tvacuum_is_relation_owner(relid, reltuple, options)\n\n/*\n * 'BackendIdGetProc' was renamed to 'ProcNumberGetProc' in 024c52111757.\n * Also 'backendId' was renamed to 'procNumber'\n */\n#define VirtualTransactionGetProcCompat(vxid) BackendIdGetProc((vxid)->backendId)\n\n/*\n * 'opclassOptions' argument was added in 784162357130.\n * Previously indexInfo->ii_OpclassOptions was used instead.\n * On top of that 'stattargets' argument was added in 6a004f1be87d.\n */\n#define index_create_compat(heapRelation,                                                          \\\n\t\t\t\t\t\t\tindexRelationName,                                                     \\\n\t\t\t\t\t\t\tindexRelationId,                                                       \\\n\t\t\t\t\t\t\tparentIndexRelid,                                                      \\\n\t\t\t\t\t\t\tparentConstraintId,                                                    \\\n\t\t\t\t\t\t\trelFileNumber,                                                         \\\n\t\t\t\t\t\t\tindexInfo,                                                             \\\n\t\t\t\t\t\t\tindexColNames,                                                         \\\n\t\t\t\t\t\t\taccessMethodId,                                                        \\\n\t\t\t\t\t\t\ttableSpaceId,                                                          \\\n\t\t\t\t\t\t\tcollationIds,                                                          \\\n\t\t\t\t\t\t\topclassIds,                                                            \\\n\t\t\t\t\t\t\topclassOptions,                                                        \\\n\t\t\t\t\t\t\tcoloptions,                                                            \\\n\t\t\t\t\t\t\tstattargets,                                                           \\\n\t\t\t\t\t\t\treloptions,                                                            \\\n\t\t\t\t\t\t\tflags,                                                                 \\\n\t\t\t\t\t\t\tconstr_flags,                                                          \\\n\t\t\t\t\t\t\tallow_system_table_mods,                                               \\\n\t\t\t\t\t\t\tis_internal,                                                           \\\n\t\t\t\t\t\t\tconstraintId)                                                          \\\n\tindex_create(heapRelation,                                                                     \\\n\t\t\t\t indexRelationName,                                                                \\\n\t\t\t\t indexRelationId,                                                                  \\\n\t\t\t\t parentIndexRelid,                                                                 \\\n\t\t\t\t parentConstraintId,                                                               \\\n\t\t\t\t relFileNumber,                                                                    \\\n\t\t\t\t indexInfo,                                                                        \\\n\t\t\t\t indexColNames,                                                                    \\\n\t\t\t\t accessMethodId,                                                                   \\\n\t\t\t\t tableSpaceId,                                                                     \\\n\t\t\t\t collationIds,                                                                     \\\n\t\t\t\t opclassIds,                                                                       \\\n\t\t\t\t coloptions,                                                                       \\\n\t\t\t\t reloptions,                                                                       \\\n\t\t\t\t flags,                                                                            \\\n\t\t\t\t constr_flags,                                                                     \\\n\t\t\t\t allow_system_table_mods,                                                          \\\n\t\t\t\t is_internal,                                                                      \\\n\t\t\t\t constraintId)\n\n#else /* PG17_GE */\n\n#define LogLogicalMessageCompat(prefix, message, size, transactional, flush)                       \\\n\tLogLogicalMessage(prefix, message, size, transactional, flush)\n\n#define reindex_relation_compat(stmt, relid, flags, params)                                        \\\n\treindex_relation(stmt, relid, flags, params)\n\n#define vacuum_is_permitted_for_relation_compat(relid, reltuple, options)                          \\\n\tvacuum_is_permitted_for_relation(relid, reltuple, options)\n\n#define VirtualTransactionGetProcCompat(vxid) ProcNumberGetProc(vxid->procNumber)\n\n#define index_create_compat(heapRelation,                                                          \\\n\t\t\t\t\t\t\tindexRelationName,                                                     \\\n\t\t\t\t\t\t\tindexRelationId,                                                       \\\n\t\t\t\t\t\t\tparentIndexRelid,                                                      \\\n\t\t\t\t\t\t\tparentConstraintId,                                                    \\\n\t\t\t\t\t\t\trelFileNumber,                                                         \\\n\t\t\t\t\t\t\tindexInfo,                                                             \\\n\t\t\t\t\t\t\tindexColNames,                                                         \\\n\t\t\t\t\t\t\taccessMethodId,                                                        \\\n\t\t\t\t\t\t\ttableSpaceId,                                                          \\\n\t\t\t\t\t\t\tcollationIds,                                                          \\\n\t\t\t\t\t\t\topclassIds,                                                            \\\n\t\t\t\t\t\t\topclassOptions,                                                        \\\n\t\t\t\t\t\t\tcoloptions,                                                            \\\n\t\t\t\t\t\t\tstattargets,                                                           \\\n\t\t\t\t\t\t\treloptions,                                                            \\\n\t\t\t\t\t\t\tflags,                                                                 \\\n\t\t\t\t\t\t\tconstr_flags,                                                          \\\n\t\t\t\t\t\t\tallow_system_table_mods,                                               \\\n\t\t\t\t\t\t\tis_internal,                                                           \\\n\t\t\t\t\t\t\tconstraintId)                                                          \\\n\tindex_create(heapRelation,                                                                     \\\n\t\t\t\t indexRelationName,                                                                \\\n\t\t\t\t indexRelationId,                                                                  \\\n\t\t\t\t parentIndexRelid,                                                                 \\\n\t\t\t\t parentConstraintId,                                                               \\\n\t\t\t\t relFileNumber,                                                                    \\\n\t\t\t\t indexInfo,                                                                        \\\n\t\t\t\t indexColNames,                                                                    \\\n\t\t\t\t accessMethodId,                                                                   \\\n\t\t\t\t tableSpaceId,                                                                     \\\n\t\t\t\t collationIds,                                                                     \\\n\t\t\t\t opclassIds,                                                                       \\\n\t\t\t\t opclassOptions,                                                                   \\\n\t\t\t\t coloptions,                                                                       \\\n\t\t\t\t stattargets,                                                                      \\\n\t\t\t\t reloptions,                                                                       \\\n\t\t\t\t flags,                                                                            \\\n\t\t\t\t constr_flags,                                                                     \\\n\t\t\t\t allow_system_table_mods,                                                          \\\n\t\t\t\t is_internal,                                                                      \\\n\t\t\t\t constraintId)\n#endif\n\n#if PG17_LT\n/* 'mergeActions' argument was added in 5f2e179bd31e */\n#define CheckValidResultRelCompat(resultRelInfo, operation, onConflictAction, mergeActions)        \\\n\tCheckValidResultRel(resultRelInfo, operation)\n#elif PG18_LT\n#define CheckValidResultRelCompat(resultRelInfo, operation, onConflictAction, mergeActions)        \\\n\tCheckValidResultRel(resultRelInfo, operation, mergeActions)\n#else\n#define CheckValidResultRelCompat(resultRelInfo, operation, onConflictAction, mergeActions)        \\\n\tCheckValidResultRel(resultRelInfo, operation, onConflictAction, mergeActions)\n#endif\n\n#if PG17_LT\n/*\n * Overflow-aware comparison functions to be used in qsort. Introduced in PG\n * 17 and included here for older PG versions.\n */\nstatic inline int\npg_cmp_u32(uint32 a, uint32 b)\n{\n\treturn (a > b) - (a < b);\n}\n\n#endif\n\n#if PG16_LT\n/*\n * Similarly, wrappers around labs()/llabs() matching our int64.\n *\n * Introduced on PG16:\n * https://github.com/postgres/postgres/commit/357cfefb09115292cfb98d504199e6df8201c957\n */\n#ifdef HAVE_LONG_INT_64\n#define i64abs(i) labs(i)\n#else\n#define i64abs(i) llabs(i)\n#endif\n#endif\n\n/*\n * PG18 adds IndexScanInstrumentation parameter to index_beginscan\n * https://github.com/postgres/postgres/commit/0fbceae8\n */\n#if PG18_LT\n#define index_beginscan_compat(heapRelation,                                                       \\\n\t\t\t\t\t\t\t   indexRelation,                                                      \\\n\t\t\t\t\t\t\t   snapshot,                                                           \\\n\t\t\t\t\t\t\t   instrument,                                                         \\\n\t\t\t\t\t\t\t   nkeys,                                                              \\\n\t\t\t\t\t\t\t   norderbys)                                                          \\\n\tindex_beginscan(heapRelation, indexRelation, snapshot, nkeys, norderbys)\n#else\n#define index_beginscan_compat(heapRelation,                                                       \\\n\t\t\t\t\t\t\t   indexRelation,                                                      \\\n\t\t\t\t\t\t\t   snapshot,                                                           \\\n\t\t\t\t\t\t\t   instrument,                                                         \\\n\t\t\t\t\t\t\t   nkeys,                                                              \\\n\t\t\t\t\t\t\t   norderbys)                                                          \\\n\tindex_beginscan(heapRelation, indexRelation, snapshot, instrument, nkeys, norderbys)\n#endif\n\n#if PG16_LT\n#define make_range_compat(typcache, lower, upper, empty, escontext)                                \\\n\tmake_range(typcache, lower, upper, empty)\n#else\n#define make_range_compat(typcache, lower, upper, empty, escontext)                                \\\n\tmake_range(typcache, lower, upper, empty, escontext)\n#endif\n\n/* Copied from PG17. We can remove it once we deprecate older versions. */\n#if PG17_LT\nstatic inline void\ninitReadOnlyStringInfo(StringInfo str, char *data, int len)\n{\n\tstr->data = data;\n\tstr->len = len;\n\tstr->maxlen = 0; /* read-only */\n\tstr->cursor = 0;\n}\n#endif\n\n/*\n * PG18 renames ri_ConstraintExprs to ri_CheckConstraintExprs\n * Add macros so we can use the new naming for older versions.\n * https://github.com/postgres/postgres/commit/9a9ead11\n */\n#if PG18_LT\n#define ri_CheckConstraintExprs ri_ConstraintExprs\n#endif\n\n/*\n * PG18 renames ec_derives to ec_derives_list\n * Add macros so we can use the new naming for older versions.\n * https://github.com/postgres/postgres/commit/88f55bc9\n */\n#if PG18_LT\n#define ec_derives_list ec_derives\n#endif\n\n/* PG18 introduces new CompareType for ordering operations\n * Add macros so we can use the new naming for older versions.\n * https://github.com/postgres/postgres/commit/8123e91f\n */\n#if PG18_LT\n#define CompareType int16\n#define COMPARE_LT BTLessStrategyNumber\n#define COMPARE_GT BTGreaterStrategyNumber\n#define pk_cmptype pk_strategy\n#endif\n\n/* PG18 adds is_merge_delete param to ExecBR{Delete|Update}Triggers function.\n * This has been backported to 17.6 but with a new name (ExecBR{Delete|Update}TriggersNew)j\n * Add compat function to cover 3 versions (pre 17.6, 17.6 - 18, post 18)\n * https://github.com/postgres/postgres/commit/5022ff25\n */\n#if PG_VERSION_NUM < 170006\n#define ExecBRDeleteTriggersCompat(estate,                                                         \\\n\t\t\t\t\t\t\t\t   epqstate,                                                       \\\n\t\t\t\t\t\t\t\t   relinfo,                                                        \\\n\t\t\t\t\t\t\t\t   tupleid,                                                        \\\n\t\t\t\t\t\t\t\t   fdw_trigtuple,                                                  \\\n\t\t\t\t\t\t\t\t   epqslot,                                                        \\\n\t\t\t\t\t\t\t\t   tmresult,                                                       \\\n\t\t\t\t\t\t\t\t   tmfd,                                                           \\\n\t\t\t\t\t\t\t\t   is_merge_delete)                                                \\\n\tExecBRDeleteTriggers(estate, epqstate, relinfo, tupleid, fdw_trigtuple, epqslot, tmresult, tmfd)\n#define ExecBRUpdateTriggersCompat(estate,                                                         \\\n\t\t\t\t\t\t\t\t   epqstate,                                                       \\\n\t\t\t\t\t\t\t\t   relinfo,                                                        \\\n\t\t\t\t\t\t\t\t   tupleid,                                                        \\\n\t\t\t\t\t\t\t\t   fdw_trigtuple,                                                  \\\n\t\t\t\t\t\t\t\t   epqslot,                                                        \\\n\t\t\t\t\t\t\t\t   tmresult,                                                       \\\n\t\t\t\t\t\t\t\t   tmfd,                                                           \\\n\t\t\t\t\t\t\t\t   is_merge_delete)                                                \\\n\tExecBRUpdateTriggers(estate, epqstate, relinfo, tupleid, fdw_trigtuple, epqslot, tmresult, tmfd)\n#endif\n\n#if PG_VERSION_NUM >= 170006 && PG_VERSION_NUM < 180000\n#define ExecBRDeleteTriggersCompat(estate,                                                         \\\n\t\t\t\t\t\t\t\t   epqstate,                                                       \\\n\t\t\t\t\t\t\t\t   relinfo,                                                        \\\n\t\t\t\t\t\t\t\t   tupleid,                                                        \\\n\t\t\t\t\t\t\t\t   fdw_trigtuple,                                                  \\\n\t\t\t\t\t\t\t\t   epqslot,                                                        \\\n\t\t\t\t\t\t\t\t   tmresult,                                                       \\\n\t\t\t\t\t\t\t\t   tmfd,                                                           \\\n\t\t\t\t\t\t\t\t   is_merge_delete)                                                \\\n\tExecBRDeleteTriggersNew(estate,                                                                \\\n\t\t\t\t\t\t\tepqstate,                                                              \\\n\t\t\t\t\t\t\trelinfo,                                                               \\\n\t\t\t\t\t\t\ttupleid,                                                               \\\n\t\t\t\t\t\t\tfdw_trigtuple,                                                         \\\n\t\t\t\t\t\t\tepqslot,                                                               \\\n\t\t\t\t\t\t\ttmresult,                                                              \\\n\t\t\t\t\t\t\ttmfd,                                                                  \\\n\t\t\t\t\t\t\tis_merge_delete)\n#define ExecBRUpdateTriggersCompat(estate,                                                         \\\n\t\t\t\t\t\t\t\t   epqstate,                                                       \\\n\t\t\t\t\t\t\t\t   relinfo,                                                        \\\n\t\t\t\t\t\t\t\t   tupleid,                                                        \\\n\t\t\t\t\t\t\t\t   fdw_trigtuple,                                                  \\\n\t\t\t\t\t\t\t\t   epqslot,                                                        \\\n\t\t\t\t\t\t\t\t   tmresult,                                                       \\\n\t\t\t\t\t\t\t\t   tmfd,                                                           \\\n\t\t\t\t\t\t\t\t   is_merge_delete)                                                \\\n\tExecBRUpdateTriggersNew(estate,                                                                \\\n\t\t\t\t\t\t\tepqstate,                                                              \\\n\t\t\t\t\t\t\trelinfo,                                                               \\\n\t\t\t\t\t\t\ttupleid,                                                               \\\n\t\t\t\t\t\t\tfdw_trigtuple,                                                         \\\n\t\t\t\t\t\t\tepqslot,                                                               \\\n\t\t\t\t\t\t\ttmresult,                                                              \\\n\t\t\t\t\t\t\ttmfd,                                                                  \\\n\t\t\t\t\t\t\tis_merge_delete)\n#endif\n#if PG18_GE\n#define ExecBRDeleteTriggersCompat(estate,                                                         \\\n\t\t\t\t\t\t\t\t   epqstate,                                                       \\\n\t\t\t\t\t\t\t\t   relinfo,                                                        \\\n\t\t\t\t\t\t\t\t   tupleid,                                                        \\\n\t\t\t\t\t\t\t\t   fdw_trigtuple,                                                  \\\n\t\t\t\t\t\t\t\t   epqslot,                                                        \\\n\t\t\t\t\t\t\t\t   tmresult,                                                       \\\n\t\t\t\t\t\t\t\t   tmfd,                                                           \\\n\t\t\t\t\t\t\t\t   is_merge_delete)                                                \\\n\tExecBRDeleteTriggers(estate,                                                                   \\\n\t\t\t\t\t\t epqstate,                                                                 \\\n\t\t\t\t\t\t relinfo,                                                                  \\\n\t\t\t\t\t\t tupleid,                                                                  \\\n\t\t\t\t\t\t fdw_trigtuple,                                                            \\\n\t\t\t\t\t\t epqslot,                                                                  \\\n\t\t\t\t\t\t tmresult,                                                                 \\\n\t\t\t\t\t\t tmfd,                                                                     \\\n\t\t\t\t\t\t is_merge_delete)\n#define ExecBRUpdateTriggersCompat(estate,                                                         \\\n\t\t\t\t\t\t\t\t   epqstate,                                                       \\\n\t\t\t\t\t\t\t\t   relinfo,                                                        \\\n\t\t\t\t\t\t\t\t   tupleid,                                                        \\\n\t\t\t\t\t\t\t\t   fdw_trigtuple,                                                  \\\n\t\t\t\t\t\t\t\t   epqslot,                                                        \\\n\t\t\t\t\t\t\t\t   tmresult,                                                       \\\n\t\t\t\t\t\t\t\t   tmfd,                                                           \\\n\t\t\t\t\t\t\t\t   is_merge_delete)                                                \\\n\tExecBRUpdateTriggers(estate,                                                                   \\\n\t\t\t\t\t\t epqstate,                                                                 \\\n\t\t\t\t\t\t relinfo,                                                                  \\\n\t\t\t\t\t\t tupleid,                                                                  \\\n\t\t\t\t\t\t fdw_trigtuple,                                                            \\\n\t\t\t\t\t\t epqslot,                                                                  \\\n\t\t\t\t\t\t tmresult,                                                                 \\\n\t\t\t\t\t\t tmfd,                                                                     \\\n\t\t\t\t\t\t is_merge_delete)\n#endif\n\n/* PG16 removes create_new_ph parameter from add_vars_to_targetlist\n * https://github.com/postgres/postgres/commit/2489d76c4906 */\n#if PG16_LT\n#define add_vars_to_targetlist_compat(root, vars, where_needed)                                    \\\n\tadd_vars_to_targetlist(root, vars, where_needed, false)\n#else\n#define add_vars_to_targetlist_compat(root, vars, where_needed)                                    \\\n\tadd_vars_to_targetlist(root, vars, where_needed)\n#endif\n\n/* PG16 consolidates ItemPointer to datum functions so backported it to PG15\n * https://github.com/postgres/postgres/commit/bd944884e92a */\n#if PG16_LT\nstatic inline ItemPointer\nDatumGetItemPointer(Datum X)\n{\n\treturn (ItemPointer) DatumGetPointer(X);\n}\n\nstatic inline Datum\nItemPointerGetDatum(const ItemPointerData *X)\n{\n\treturn PointerGetDatum(X);\n}\n#endif\n"
  },
  {
    "path": "src/config.h.in",
    "content": "#ifndef TIMESCALEDB_CONFIG_H\n#define TIMESCALEDB_CONFIG_H\n\n#define TIMESCALEDB_VERSION \"@PROJECT_VERSION@\"\n#define TIMESCALEDB_VERSION_MOD \"@PROJECT_VERSION_MOD@\"\n#define TIMESCALEDB_MAJOR_VERSION \"@PROJECT_VERSION_MAJOR@\"\n#define TIMESCALEDB_MINOR_VERSION \"@PROJECT_VERSION_MINOR@\"\n#define TIMESCALEDB_PATCH_VERSION \"@PROJECT_VERSION_PATCH@\"\n#define TIMESCALEDB_MOD_VERSION \"@VERSION_MOD@\"\n#define BUILD_OS_NAME \"@CMAKE_SYSTEM_NAME@\"\n#define BUILD_OS_VERSION \"@CMAKE_SYSTEM_VERSION@\"\n#define BUILD_PROCESSOR \"@CMAKE_SYSTEM_PROCESSOR@\"\n#define BUILD_POINTER_BYTES @CMAKE_SIZEOF_VOID_P@\n\n/*\n * Value should be set in package release scripts. Otherwise\n * defaults to \"source\"\n */\n#define TIMESCALEDB_INSTALL_METHOD \"@PROJECT_INSTALL_METHOD@\"\n\n/* Platform */\n#ifndef WIN32\n#cmakedefine WIN32\n#endif\n#ifndef MSVC\n#cmakedefine MSVC\n#endif\n#ifndef UNIX\n#cmakedefine UNIX\n#endif\n#ifndef APPLE\n#cmakedefine APPLE\n#endif\n\n#ifndef DEBUG\n#cmakedefine DEBUG\n#endif\n\n#ifndef TS_DEBUG\n#cmakedefine TS_DEBUG\n#endif\n\n#ifndef USE_TELEMETRY\n#cmakedefine USE_TELEMETRY\n#endif\n\n#ifndef TELEMETRY_DEFAULT\n#cmakedefine TELEMETRY_DEFAULT @TELEMETRY_DEFAULT@\n#endif\n\n/* Avoid conflicts with USE_OPENSSL defined by PostgreSQL */\n#cmakedefine TS_USE_OPENSSL\n\n#endif /* TIMESCALEDB_CONFIG_H */\n"
  },
  {
    "path": "src/constraint.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/genam.h>\n#include <access/table.h>\n#include <catalog/index.h>\n#include <catalog/indexing.h>\n#include <catalog/pg_constraint.h>\n#include <utils/builtins.h>\n#include <utils/fmgroids.h>\n#include <utils/rel.h>\n\n#include \"constraint.h\"\n#include \"indexing.h\"\n\n/*\n * Process constraints that belong to a given relation.\n *\n * Returns the number of constraints processed.\n */\nint\nts_constraint_process(Oid relid, constraint_func process_func, void *ctx)\n{\n\tScanKeyData skey;\n\tRelation rel;\n\tSysScanDesc scan;\n\tHeapTuple htup;\n\tbool should_continue = true;\n\tint count = 0;\n\n\tScanKeyInit(&skey, Anum_pg_constraint_conrelid, BTEqualStrategyNumber, F_OIDEQ, relid);\n\n\trel = table_open(ConstraintRelationId, AccessShareLock);\n\tscan = systable_beginscan(rel, ConstraintRelidTypidNameIndexId, true, NULL, 1, &skey);\n\n\twhile (HeapTupleIsValid(htup = systable_getnext(scan)) && should_continue)\n\t{\n\t\tswitch (process_func(htup, ctx))\n\t\t{\n\t\t\tcase CONSTR_PROCESSED:\n\t\t\t\tcount++;\n\t\t\t\tbreak;\n\t\t\tcase CONSTR_PROCESSED_DONE:\n\t\t\t\tcount++;\n\t\t\t\tshould_continue = false;\n\t\t\t\tbreak;\n\t\t\tcase CONSTR_IGNORED:\n\t\t\t\tbreak;\n\t\t\tcase CONSTR_IGNORED_DONE:\n\t\t\t\tshould_continue = false;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tsystable_endscan(scan);\n\ttable_close(rel, AccessShareLock);\n\n\treturn count;\n}\n\n/*\n * Search for an existing constraint in the chunk that matches the given\n * hypertable constraint. This is used to avoid creating duplicate constraints\n * when creating chunks.\n *\n * Returns true if a matching constraint is found, false otherwise.\n */\nForm_pg_constraint\nts_constraint_find_matching(HeapTuple ht_tup, Relation chunk_rel)\n{\n\tScanKeyData skey;\n\tRelation constraint_rel;\n\tSysScanDesc scan;\n\tHeapTuple chunk_tup;\n\tForm_pg_constraint ht_con = (Form_pg_constraint) GETSTRUCT(ht_tup);\n\tForm_pg_constraint chunk_con;\n\tRelation ht_rel = RelationIdGetRelation(ht_con->conrelid);\n\tForm_pg_constraint result = NULL;\n\n\tconstraint_rel = table_open(ConstraintRelationId, RowExclusiveLock);\n\t/* Search for a constraint matching this one */\n\tScanKeyInit(&skey,\n\t\t\t\tAnum_pg_constraint_conrelid,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_OIDEQ,\n\t\t\t\tObjectIdGetDatum(RelationGetRelid(chunk_rel)));\n\tscan =\n\t\tsystable_beginscan(constraint_rel, ConstraintRelidTypidNameIndexId, true, NULL, 1, &skey);\n\n\twhile (HeapTupleIsValid(chunk_tup = systable_getnext(scan)))\n\t{\n\t\tchunk_con = (Form_pg_constraint) GETSTRUCT(chunk_tup);\n\n\t\t/* Check constraints are handled by CreateInheritance() */\n\t\tif (ht_con->contype != chunk_con->contype || ht_con->contype == CONSTRAINT_CHECK)\n\t\t\tcontinue;\n\n\t\t/*\n\t\t * Get the main name for chunk constraint and check whether it matches\n\t\t * the hypertable constraint name. If names match, then we compare indexes\n\t\t * to ensure that the constraints are equivalent. If the names do not\n\t\t * match, we assume that the constraints are not equivalent.\n\t\t */\n\t\tif (ht_con->contype == CONSTRAINT_PRIMARY || ht_con->contype == CONSTRAINT_UNIQUE ||\n\t\t\tht_con->contype == CONSTRAINT_EXCLUSION)\n\t\t{\n\t\t\tif (ts_indexing_compare(ht_con->conindid, chunk_con->conindid))\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * pfree'ing this form is up to the caller. It is not expected to\n\t\t\t\t * consume a lot of memory, as only one form is allocated per\n\t\t\t\t * constraint.\n\t\t\t\t */\n\t\t\t\tresult = (Form_pg_constraint) palloc(sizeof(FormData_pg_constraint));\n\t\t\t\tmemcpy(result, chunk_con, sizeof(FormData_pg_constraint));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\tsystable_endscan(scan);\n\n\ttable_close(constraint_rel, RowExclusiveLock);\n\tRelationClose(ht_rel);\n\n\treturn result;\n}\n"
  },
  {
    "path": "src/constraint.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <access/htup.h>\n\n#include \"export.h\"\n\n/*\n * Return status for constraint processing function.\n *\n * PROCESSED - count the constraint as processed\n * IGNORED - the constraint wasn't processed\n * DONE - stop processing constraints\n */\ntypedef enum ConstraintProcessStatus\n{\n\tCONSTR_PROCESSED,\n\tCONSTR_PROCESSED_DONE,\n\tCONSTR_IGNORED,\n\tCONSTR_IGNORED_DONE,\n} ConstraintProcessStatus;\n\ntypedef ConstraintProcessStatus (*constraint_func)(HeapTuple constraint_tuple, void *ctx);\nextern TSDLLEXPORT int ts_constraint_process(Oid relid, constraint_func process_func, void *ctx);\nextern TSDLLEXPORT Form_pg_constraint ts_constraint_find_matching(HeapTuple ht_tup,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  Relation chunk_rel);\n"
  },
  {
    "path": "src/copy.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n/*\n * This file contains source code that was copied and/or modified from\n * the PostgreSQL database, which is licensed under the open-source\n * PostgreSQL License. Please see the NOTICE at the top level\n * directory for a copy of the PostgreSQL License.\n *\n * The code copies data to a hypertable or migrates existing data from\n * a table to a hypertable when create_hypertable(..., migrate_data =>\n * 'true', ...) is called.\n *\n * Unfortunately, there aren't any good hooks in the regular COPY code to\n * insert our chunk dispatching. So, most of this code is a straight-up\n * copy of the regular PostgreSQL source code for the COPY command\n * (command/copy.c and command/copyfrom.c), albeit with minor modifications.\n */\n\n#include <postgres.h>\n\n#include <access/heapam.h>\n#include <access/hio.h>\n#include <access/sysattr.h>\n#include <access/xact.h>\n#include <catalog/pg_trigger_d.h>\n#include <commands/copy.h>\n#include <commands/copyfrom_internal.h>\n#include <commands/tablecmds.h>\n#include <commands/trigger.h>\n#include <executor/executor.h>\n#include <executor/nodeModifyTable.h>\n#include <miscadmin.h>\n#include <nodes/makefuncs.h>\n#include <optimizer/optimizer.h>\n#include <parser/parse_coerce.h>\n#include <parser/parse_collate.h>\n#include <parser/parse_expr.h>\n#include <parser/parse_relation.h>\n#include <storage/bufmgr.h>\n#include <storage/smgr.h>\n#include <utils/builtins.h>\n#include <utils/elog.h>\n#include <utils/guc.h>\n#include <utils/hsearch.h>\n#include <utils/lsyscache.h>\n#include <utils/rel.h>\n#include <utils/rls.h>\n\n#include \"compat/compat.h\"\n#include \"chunk_insert_state.h\"\n#include \"copy.h\"\n#include \"cross_module_fn.h\"\n#include \"dimension.h\"\n#include \"hypertable.h\"\n#include \"indexing.h\"\n#include \"subspace_store.h\"\n\n/*\n * Represents the insert method to be used during COPY FROM.\n */\ntypedef enum TSCopyInsertMethod\n{\n\tTS_CIM_SINGLE,\t\t\t  /* use table_tuple_insert or ExecForeignInsert */\n\tTS_CIM_MULTI_CONDITIONAL, /* use table_multi_insert or\n\t\t\t\t\t\t\t   * ExecForeignBatchInsert only if valid */\n\tTS_CIM_COMPRESSION,\t\t  /* use compression for the insert */\n} TSCopyInsertMethod;\n\n/*\n * No more than this many tuples per TSCopyMultiInsertBuffer\n *\n * Caution: Don't make this too big, as we could end up with this many\n * TSCopyMultiInsertBuffer items stored in TSCopyMultiInsertInfo's\n * multiInsertBuffers list.  Increasing this can cause quadratic growth in\n * memory requirements during copies into partitioned tables with a large\n * number of partitions.\n */\n#define MAX_BUFFERED_TUPLES 1000\n\n/*\n * Flush buffers if there are >= this many bytes, as counted by the input\n * size, of tuples stored.\n */\n#define MAX_BUFFERED_BYTES 65535\n\n/* Trim the list of buffers back down to this number after flushing */\n#define MAX_PARTITION_BUFFERS 32\n\n/* Stores multi-insert data related to a single relation in CopyFrom. */\ntypedef struct TSCopyMultiInsertBuffer\n{\n\tTSCopyInsertMethod method; /* The insert method to use */\n\n\t/*\n\t * Tuple description for inserted tuple slots. We use a copy of the result\n\t * relation tupdesc to disable reference counting for this tupdesc. It is\n\t * not needed and is wasting a lot of CPU in ResourceOwner.\n\t */\n\tTupleDesc tupdesc;\n\tTupleTableSlot *slots[MAX_BUFFERED_TUPLES]; /* Array to store tuples */\n\tPoint *point;\t\t\t\t\t\t\t\t/* The point in space of this buffer */\n\tBulkInsertState bistate;\t\t\t\t\t/* BulkInsertState for this buffer */\n\tint nused;\t\t\t\t\t\t\t\t\t/* number of 'slots' containing tuples */\n\tuint64 linenos[MAX_BUFFERED_TUPLES];\t\t/* Line # of tuple in copy\n\t\t\t\t\t\t\t\t\t\t\t\t * stream */\n\n\tbool can_skip_constraints; /* Whether we can skip constraint\n\t\t\t\t\t\t\t\t* checks for this relation */\n\n\tRowCompressor *compressor; /* compressor for the chunk */\n\tBulkWriter *bulk_writer;   /* BulkWriter for the compressed chunk */\n\n} TSCopyMultiInsertBuffer;\n\n/*\n * Stores one or many TSCopyMultiInsertBuffers and details about the size and\n * number of tuples which are stored in them.  This allows multiple buffers to\n * exist at once when COPYing into a partitioned table.\n *\n * The HTAB is used to store the relationship between a chunk and a\n * TSCopyMultiInsertBuffer beyond the lifetime of the ChunkInsertState.\n *\n * Chunks can be closed (e.g., due to timescaledb.max_open_chunks_per_insert).\n * When ts_chunk_dispatch_get_chunk_insert_state is called again for a closed\n * chunk, a new ChunkInsertState is returned.\n */\ntypedef struct TSCopyMultiInsertInfo\n{\n\tHTAB *multiInsertBuffers; /* Maps the chunk ids to the buffers (chunkid ->\n\t\t\t\t\t\t\t\t TSCopyMultiInsertBuffer) */\n\tint bufferedTuples;\t\t  /* number of tuples buffered over all buffers */\n\tint bufferedBytes;\t\t  /* number of bytes from all buffered tuples */\n\tCopyChunkState *ccstate;  /* Copy chunk state for this TSCopyMultiInsertInfo */\n\tEState *estate;\t\t\t  /* Executor state used for COPY */\n\tCommandId mycid;\t\t  /* Command Id used for COPY */\n\tint ti_options;\t\t\t  /* table insert options */\n\tHypertable *ht;\t\t\t  /* The hypertable for the inserts */\n\tbool has_continuous_aggregate;\n} TSCopyMultiInsertInfo;\n\n/*\n * The entry of the multiInsertBuffers HTAB.\n */\ntypedef struct MultiInsertBufferEntry\n{\n\tint32 key;\n\tTSCopyMultiInsertBuffer *buffer;\n} MultiInsertBufferEntry;\n\nstatic CopyChunkState *\ncopy_chunk_state_create(Hypertable *ht, Relation rel, CopyFromFunc from_func, CopyFromState cstate,\n\t\t\t\t\t\tTableScanDesc scandesc)\n{\n\tCopyChunkState *ccstate;\n\tEState *estate = CreateExecutorState();\n\n\tccstate = palloc(sizeof(CopyChunkState));\n\tccstate->rel = rel;\n\tccstate->estate = estate;\n\tccstate->cstate = cstate;\n\tccstate->scandesc = scandesc;\n\tccstate->next_copy_from = from_func;\n\tccstate->where_clause = NULL;\n\n\treturn ccstate;\n}\n\n/*\n * Determine whether we can skip constraints checks for this relation.\n * We will skip constraints checks if:\n * 1. The relation has CHECK constraints that match the number of dimensions\n * 2. The relation has no NOT NULL constraints on non-partitioning columns\n */\nstatic bool\ncan_skip_constraint_check(Hypertable *ht, TupleDesc tupledesc)\n{\n\t/*\n\t * When the number of constraints does not match the number of dimensions then there are\n\t * additional constraints that we need to check during COPY. Partitioning constraints would\n\t * have already been checked by tuple routing.\n\t */\n\tAssert(tupledesc->constr->num_check >= ht->space->num_dimensions);\n\tif (tupledesc->constr && tupledesc->constr->num_check != ht->space->num_dimensions)\n\t\treturn false;\n\n\tfor (int i = 0; i < tupledesc->natts; i++)\n\t{\n\t\tForm_pg_attribute att = TupleDescAttr(tupledesc, i);\n\n\t\tif (att->attisdropped)\n\t\t\tcontinue;\n\n\t\t/*\n\t\t * If we have NOT NULL constraints on non-partitioning columns, we cannot skip\n\t\t * constraints and have to check them.\n\t\t */\n\t\tif (att->attnotnull)\n\t\t{\n\t\t\tif (ts_is_partitioning_column_name(ht, att->attname))\n\t\t\t\tcontinue;\n\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/*\n * Allocate memory and initialize a new TSCopyMultiInsertBuffer for this\n * ResultRelInfo.\n */\nstatic TSCopyMultiInsertBuffer *\nTSCopyMultiInsertBufferInit(TSCopyMultiInsertInfo *miinfo, ChunkInsertState *cis, Point *point,\n\t\t\t\t\t\t\tTSCopyInsertMethod method)\n{\n\tTSCopyMultiInsertBuffer *buffer;\n\n\tbuffer = (TSCopyMultiInsertBuffer *) palloc0(sizeof(TSCopyMultiInsertBuffer));\n\tbuffer->method = method;\n\n\tbuffer->point = palloc(POINT_SIZE(point->num_coords));\n\tmemcpy(buffer->point, point, POINT_SIZE(point->num_coords));\n\n\tbuffer->can_skip_constraints = can_skip_constraint_check(miinfo->ht, cis->rel->rd_att);\n\n\t/*\n\t * Downgrade the insert method when triggers are present.\n\t */\n\tif (method != TS_CIM_SINGLE && cis->result_relation_info->ri_TrigDesc)\n\t{\n\t\t/* If there are BEFORE INSERT row triggers, we cannot use\n\t\t * multi-insert, as the tuples may be inserted in an out-of-order manner,\n\t\t * which might violate the semantics of the triggers.\n\t\t *\n\t\t * For compressed inserts we fall back to TS_CIM_SINGLE when any triggers are present.\n\t\t * This is a safety measure. We might actually safely allow some of these in the future.\n\t\t */\n\t\tif (method == TS_CIM_MULTI_CONDITIONAL &&\n\t\t\t(cis->result_relation_info->ri_TrigDesc->trig_insert_before_row ||\n\t\t\t cis->result_relation_info->ri_TrigDesc->trig_insert_instead_row))\n\t\t{\n\t\t\tbuffer->method = TS_CIM_SINGLE;\n\t\t}\n\t\telse if (method == TS_CIM_COMPRESSION)\n\t\t{\n\t\t\tbuffer->method = TS_CIM_SINGLE;\n\t\t}\n\t}\n\n\tswitch (buffer->method)\n\t{\n\t\tcase TS_CIM_SINGLE:\n\t\t\tbreak;\n\t\tcase TS_CIM_MULTI_CONDITIONAL:\n\t\t\tbuffer->bistate = GetBulkInsertState();\n\t\t\t/*\n\t\t\t * Make a non-refcounted copy of tupdesc to avoid spending CPU in\n\t\t\t * ResourceOwner when creating a big number of table slots. This happens\n\t\t\t * because each new slot pins its tuple descriptor using PinTupleDesc, and\n\t\t\t * for reference-counting tuples this involves adding a new reference to\n\t\t\t * ResourceOwner, which is not very efficient for a large number of\n\t\t\t * references.\n\t\t\t */\n\t\t\tbuffer->tupdesc = CreateTupleDescCopyConstr(cis->rel->rd_att);\n\t\t\tAssert(buffer->tupdesc->tdrefcount == -1);\n\t\t\tbreak;\n\t\tcase TS_CIM_COMPRESSION:\n\t\t{\n\t\t\tbool sort = ts_guc_enable_direct_compress_copy_sort_batches &&\n\t\t\t\t\t\t!ts_guc_enable_direct_compress_copy_client_sorted;\n\t\t\tbuffer->compressor =\n\t\t\t\tts_cm_functions->compressor_init(cis->rel,\n\t\t\t\t\t\t\t\t\t\t\t\t &buffer->bulk_writer,\n\t\t\t\t\t\t\t\t\t\t\t\t sort,\n\t\t\t\t\t\t\t\t\t\t\t\t ts_guc_direct_compress_copy_tuple_sort_limit);\n\n\t\t\tif (miinfo->has_continuous_aggregate)\n\t\t\t{\n\t\t\t\tts_cm_functions->compressor_set_invalidation(buffer->compressor,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t miinfo->ht,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t RelationGetRelid(cis->rel));\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * The sorting done in the compressor is only a local sort for the\n\t\t\t * currently ingested batch and will produce overlapping batches for\n\t\t\t * multiple independent insert streams. Therefore we still need to\n\t\t\t * mark the chunk as unordered until we adjust the rest of the code to\n\t\t\t * be able to deal with overlapping batches.\n\t\t\t */\n\t\t\tif (!ts_guc_enable_direct_compress_copy_client_sorted)\n\t\t\t{\n\t\t\t\tChunk *chunk = ts_chunk_get_by_id(cis->chunk_id, true);\n\t\t\t\tif (!ts_chunk_is_unordered(chunk))\n\t\t\t\t\tts_chunk_set_unordered(chunk);\n\t\t\t}\n\t\t\tcis->columnstore_insert = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn buffer;\n}\n\n/*\n * Get the existing TSCopyMultiInsertBuffer for the chunk or create a new one.\n */\nstatic inline TSCopyMultiInsertBuffer *\nTSCopyMultiInsertInfoGetOrSetupBuffer(TSCopyMultiInsertInfo *miinfo, ChunkInsertState *cis,\n\t\t\t\t\t\t\t\t\t  Point *point, TSCopyInsertMethod method)\n{\n\tbool found;\n\tint32 chunk_id;\n\n\tAssert(miinfo != NULL);\n\tAssert(cis != NULL);\n\tAssert(point != NULL);\n\n\tchunk_id = cis->chunk_id;\n\tMultiInsertBufferEntry *entry =\n\t\thash_search(miinfo->multiInsertBuffers, &chunk_id, HASH_ENTER, &found);\n\n\t/* No insert buffer for this chunk exists, create a new one */\n\tif (!found)\n\t{\n\t\tentry->buffer = TSCopyMultiInsertBufferInit(miinfo, cis, point, method);\n\t}\n\n\treturn entry->buffer;\n}\n\n/*\n * Create a new HTAB that maps from the chunk_id to the multi-insert buffers.\n */\nstatic HTAB *\nTSCopyCreateNewInsertBufferHashMap()\n{\n\tstruct HASHCTL hctl = {\n\t\t.keysize = sizeof(int32),\n\t\t.entrysize = sizeof(MultiInsertBufferEntry),\n\t\t.hcxt = CurrentMemoryContext,\n\t};\n\n\treturn hash_create(\"COPY insert buffer\", 20, &hctl, HASH_ELEM | HASH_CONTEXT | HASH_BLOBS);\n}\n\n/*\n * Initialize an already allocated TSCopyMultiInsertInfo.\n */\nstatic void\nTSCopyMultiInsertInfoInit(TSCopyMultiInsertInfo *miinfo, ResultRelInfo *rri,\n\t\t\t\t\t\t  CopyChunkState *ccstate, EState *estate, CommandId mycid, int ti_options,\n\t\t\t\t\t\t  Hypertable *ht)\n{\n\tmiinfo->multiInsertBuffers = TSCopyCreateNewInsertBufferHashMap();\n\tmiinfo->bufferedTuples = 0;\n\tmiinfo->bufferedBytes = 0;\n\tmiinfo->ccstate = ccstate;\n\tmiinfo->estate = estate;\n\tmiinfo->mycid = mycid;\n\tmiinfo->ti_options = ti_options;\n\tmiinfo->ht = ht;\n\tmiinfo->has_continuous_aggregate = ts_hypertable_has_continuous_aggregates(ht->fd.id);\n}\n\n/*\n * Returns true if the buffers are full.\n */\nstatic inline bool\nTSCopyMultiInsertInfoIsFull(TSCopyMultiInsertInfo *miinfo)\n{\n\tif (miinfo->bufferedTuples >= MAX_BUFFERED_TUPLES ||\n\t\tmiinfo->bufferedBytes >= MAX_BUFFERED_BYTES)\n\t\treturn true;\n\n\treturn false;\n}\n\n/*\n * Write the tuples stored in 'buffer' out to the table.\n */\nstatic inline int\nTSCopyMultiInsertBufferFlush(TSCopyMultiInsertInfo *miinfo, TSCopyMultiInsertBuffer *buffer)\n{\n\tMemoryContext oldcontext;\n\tint i;\n\n\tAssert(miinfo != NULL);\n\tAssert(buffer != NULL);\n\n\tEState *estate = miinfo->estate;\n\tCommandId mycid = miinfo->mycid;\n\tint ti_options = miinfo->ti_options;\n\tint nused = buffer->nused;\n\tTupleTableSlot **slots = buffer->slots;\n\n\tif (buffer->method == TS_CIM_COMPRESSION)\n\t{\n\t\tts_cm_functions->compressor_flush(buffer->compressor, buffer->bulk_writer);\n\t}\n\n\t/*\n\t * table_multi_insert and reinitialization of the chunk insert state may\n\t * leak memory, so switch to short-lived memory context before calling it.\n\t */\n\toldcontext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));\n\n\t/*\n\t * A chunk can be closed while buffering the tuples. Even when the chunk\n\t * insert state is moved to the copy memory context, the underlying\n\t * table is closed and pointers (e.g., result_relation_info point) to invalid\n\t * addresses. Re-reading the chunk insert state ensures that the table is\n\t * open and the pointers are valid.\n\t *\n\t * No callback on changed chunk is needed, the bulk insert state buffer is\n\t * freed in TSCopyMultiInsertBufferCleanup().\n\t */\n\tChunkInsertState *cis = ts_chunk_tuple_routing_find_chunk(miinfo->ccstate->ctr, buffer->point);\n\n\tResultRelInfo *resultRelInfo = cis->result_relation_info;\n\n\t/*\n\t * Add context information to the copy state, which is used to display\n\t * error messages with additional details.\n\t */\n\tuint64 save_cur_lineno = 0;\n\tbool line_buf_valid = false;\n\tCopyFromState cstate = miinfo->ccstate->cstate;\n\n\t/* cstate can be NULL in calls that are invoked from timescaledb_move_from_table_to_chunks. */\n\tif (cstate != NULL)\n\t{\n\t\tline_buf_valid = cstate->line_buf_valid;\n\t\tsave_cur_lineno = cstate->cur_lineno;\n\n\t\tcstate->line_buf_valid = false;\n\t}\n\n\ttable_multi_insert(resultRelInfo->ri_RelationDesc,\n\t\t\t\t\t   slots,\n\t\t\t\t\t   nused,\n\t\t\t\t\t   mycid,\n\t\t\t\t\t   ti_options,\n\t\t\t\t\t   buffer->bistate);\n\tMemoryContextSwitchTo(oldcontext);\n\n\tfor (i = 0; i < nused; i++)\n\t{\n\t\tif (cstate != NULL)\n\t\t\tcstate->cur_lineno = buffer->linenos[i];\n\t\t/*\n\t\t * If there are any indexes, update them for all the inserted tuples,\n\t\t * and run AFTER ROW INSERT triggers.\n\t\t */\n\t\tif (resultRelInfo->ri_NumIndices > 0)\n\t\t{\n\t\t\tList *recheckIndexes;\n\n\t\t\trecheckIndexes = ExecInsertIndexTuplesCompat(resultRelInfo,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t buffer->slots[i],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t estate,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t NIL,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t false);\n\n\t\t\tExecARInsertTriggers(estate,\n\t\t\t\t\t\t\t\t resultRelInfo,\n\t\t\t\t\t\t\t\t slots[i],\n\t\t\t\t\t\t\t\t recheckIndexes,\n\t\t\t\t\t\t\t\t NULL /* transition capture */);\n\t\t\tlist_free(recheckIndexes);\n\t\t}\n\n\t\t/*\n\t\t * There's no indexes, but see if we need to run AFTER ROW INSERT\n\t\t * triggers anyway.\n\t\t */\n\t\telse if (resultRelInfo->ri_TrigDesc != NULL &&\n\t\t\t\t (resultRelInfo->ri_TrigDesc->trig_insert_after_row ||\n\t\t\t\t  resultRelInfo->ri_TrigDesc->trig_insert_new_table))\n\t\t{\n\t\t\tExecARInsertTriggers(estate,\n\t\t\t\t\t\t\t\t resultRelInfo,\n\t\t\t\t\t\t\t\t slots[i],\n\t\t\t\t\t\t\t\t NIL,\n\t\t\t\t\t\t\t\t NULL /* transition capture */);\n\t\t}\n\n\t\tif (miinfo->has_continuous_aggregate)\n\t\t{\n\t\t\tbool should_free;\n\t\t\tHeapTuple tuple = ExecFetchSlotHeapTuple(slots[i], false, &should_free);\n\t\t\tts_cm_functions->continuous_agg_dml_invalidate(miinfo->ht->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   resultRelInfo->ri_RelationDesc,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   tuple,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   false);\n\t\t\tif (should_free)\n\t\t\t\theap_freetuple(tuple);\n\t\t}\n\n\t\tExecClearTuple(slots[i]);\n\t}\n\n\t/* Mark that all slots are free */\n\tbuffer->nused = 0;\n\n\t/* Chunk could be closed on a subsequent call of ts_chunk_dispatch_get_chunk_insert_state\n\t * (e.g., due to timescaledb.max_open_chunks_per_insert). So, ensure the bulk insert is\n\t * finished after the flush is complete.\n\t */\n\tResultRelInfo *result_relation_info = cis->result_relation_info;\n\tAssert(result_relation_info != NULL);\n\ttable_finish_bulk_insert(result_relation_info->ri_RelationDesc, miinfo->ti_options);\n\n\t/* Reset cur_lineno and line_buf_valid to what they were */\n\tif (cstate != NULL)\n\t{\n\t\tcstate->line_buf_valid = line_buf_valid;\n\t\tcstate->cur_lineno = save_cur_lineno;\n\t}\n\n\treturn cis->chunk_id;\n}\n\n/*\n * Drop used slots and free member for this buffer.\n *\n * The buffer must be flushed before cleanup.\n */\nstatic inline void\nTSCopyMultiInsertBufferCleanup(TSCopyMultiInsertInfo *miinfo, TSCopyMultiInsertBuffer *buffer)\n{\n\tint i;\n\n\t/* Ensure buffer was flushed */\n\tAssert(buffer->nused == 0);\n\n\tswitch (buffer->method)\n\t{\n\t\tcase TS_CIM_SINGLE:\n\t\t\tbreak;\n\t\tcase TS_CIM_MULTI_CONDITIONAL:\n\t\t\tFreeBulkInsertState(buffer->bistate);\n\n\t\t\t/* Since we only create slots on demand, just drop the non-null ones. */\n\t\t\tfor (i = 0; i < MAX_BUFFERED_TUPLES && buffer->slots[i] != NULL; i++)\n\t\t\t\tExecDropSingleTupleTableSlot(buffer->slots[i]);\n\n\t\t\tFreeTupleDesc(buffer->tupdesc);\n\t\t\tbreak;\n\t\tcase TS_CIM_COMPRESSION:\n\t\t\tts_cm_functions->compressor_free(buffer->compressor, buffer->bulk_writer);\n\t\t\tbreak;\n\t}\n\n\tpfree(buffer->point);\n\tpfree(buffer);\n}\n\n/* list_sort comparator to sort TSCopyMultiInsertBuffer by usage */\nstatic int\nTSCmpBuffersByUsage(const ListCell *a, const ListCell *b)\n{\n\tint b1 = ((const TSCopyMultiInsertBuffer *) lfirst(a))->nused;\n\tint b2 = ((const TSCopyMultiInsertBuffer *) lfirst(b))->nused;\n\n\tAssert(b1 >= 0);\n\tAssert(b2 >= 0);\n\n\tif (b1 > b2)\n\t{\n\t\treturn 1;\n\t}\n\n\tif (b1 == b2)\n\t{\n\t\treturn 0;\n\t}\n\n\treturn -1;\n}\n\n/*\n * Flush all buffers by writing the tuples to the chunks. In addition, trim down the\n * amount of multi-insert buffers to MAX_PARTITION_BUFFERS by deleting the least used\n * buffers (the buffers that store least tuples).\n */\nstatic inline void\nTSCopyMultiInsertInfoFlush(TSCopyMultiInsertInfo *miinfo, ChunkInsertState *cur_cis)\n{\n\tHASH_SEQ_STATUS status;\n\tMultiInsertBufferEntry *entry;\n\tint current_multi_insert_buffers;\n\tint buffers_to_delete;\n\tbool found;\n\tint32 flushed_chunk_id;\n\tList *buffer_list = NIL;\n\tListCell *lc;\n\n\tcurrent_multi_insert_buffers = hash_get_num_entries(miinfo->multiInsertBuffers);\n\tint current_chunk_id = cur_cis ? cur_cis->chunk_id : 0;\n\n\t/* Create a list of buffers that can be sorted by usage */\n\thash_seq_init(&status, miinfo->multiInsertBuffers);\n\tfor (entry = hash_seq_search(&status); entry != NULL; entry = hash_seq_search(&status))\n\t{\n\t\tbuffer_list = lappend(buffer_list, entry->buffer);\n\t}\n\n\tbuffers_to_delete = Max(current_multi_insert_buffers - MAX_PARTITION_BUFFERS, 0);\n\n\t/* Sorting is only needed if we want to remove the least used buffers */\n\tif (buffers_to_delete > 0)\n\t\tlist_sort(buffer_list, TSCmpBuffersByUsage);\n\n\t/* Flush buffers and delete them if needed */\n\tforeach (lc, buffer_list)\n\t{\n\t\tTSCopyMultiInsertBuffer *buffer = (TSCopyMultiInsertBuffer *) lfirst(lc);\n\t\tflushed_chunk_id = TSCopyMultiInsertBufferFlush(miinfo, buffer);\n\n\t\tif (buffers_to_delete > 0)\n\t\t{\n\t\t\t/*\n\t\t\t * Reduce active multi-insert buffers. However, the current used buffer\n\t\t\t * should not be deleted because it might reused for the next insert.\n\t\t\t */\n\t\t\tif (current_chunk_id == 0 || flushed_chunk_id != current_chunk_id)\n\t\t\t{\n\t\t\t\tTSCopyMultiInsertBufferCleanup(miinfo, buffer);\n\t\t\t\thash_search(miinfo->multiInsertBuffers, &flushed_chunk_id, HASH_REMOVE, &found);\n\t\t\t\tAssert(found);\n\t\t\t\tbuffers_to_delete--;\n\t\t\t}\n\t\t}\n\t}\n\n\tlist_free(buffer_list);\n\n\t/* All buffers have been flushed */\n\tmiinfo->bufferedTuples = 0;\n\tmiinfo->bufferedBytes = 0;\n}\n\n/*\n * All existing buffers are flushed and the multi-insert states\n * are freed. So, delete old hash map and create a new one for further\n * inserts.\n */\nstatic inline void\nTSCopyMultiInsertInfoFlushAndCleanup(TSCopyMultiInsertInfo *miinfo)\n{\n\tTSCopyMultiInsertInfoFlush(miinfo, NULL);\n\n\tHASH_SEQ_STATUS status;\n\tMultiInsertBufferEntry *entry;\n\n\thash_seq_init(&status, miinfo->multiInsertBuffers);\n\n\tfor (entry = hash_seq_search(&status); entry != NULL; entry = hash_seq_search(&status))\n\t{\n\t\tTSCopyMultiInsertBuffer *buffer = entry->buffer;\n\t\tTSCopyMultiInsertBufferCleanup(miinfo, buffer);\n\t}\n\n\thash_destroy(miinfo->multiInsertBuffers);\n}\n\n/*\n * Get the next TupleTableSlot that the next tuple should be stored in.\n *\n * Callers must ensure that the buffer is not full.\n *\n * Note: 'miinfo' is unused but has been included for consistency with the\n * other functions in this area.\n */\nstatic inline TupleTableSlot *\nTSCopyMultiInsertInfoNextFreeSlot(TSCopyMultiInsertInfo *miinfo,\n\t\t\t\t\t\t\t\t  ResultRelInfo *result_relation_info,\n\t\t\t\t\t\t\t\t  TSCopyMultiInsertBuffer *buffer)\n{\n\tint nused = buffer->nused;\n\n\tAssert(buffer != NULL);\n\tAssert(nused < MAX_BUFFERED_TUPLES);\n\n\tif (buffer->slots[nused] == NULL)\n\t{\n\t\tconst TupleTableSlotOps *tts_cb =\n\t\t\ttable_slot_callbacks(result_relation_info->ri_RelationDesc);\n\t\tbuffer->slots[nused] = MakeSingleTupleTableSlot(buffer->tupdesc, tts_cb);\n\t}\n\treturn buffer->slots[nused];\n}\n\n/*\n * Record the previously reserved TupleTableSlot that was reserved by\n * TSCopyMultiInsertInfoNextFreeSlot as being consumed.\n */\nstatic inline void\nTSCopyMultiInsertInfoStore(TSCopyMultiInsertInfo *miinfo, ResultRelInfo *rri,\n\t\t\t\t\t\t   TSCopyMultiInsertBuffer *buffer, TupleTableSlot *slot,\n\t\t\t\t\t\t   CopyFromState cstate)\n{\n\tAssert(buffer != NULL);\n\tAssert(slot == buffer->slots[buffer->nused]);\n\n\t/* Store the line number so we can properly report any errors later */\n\tuint64 lineno = 0;\n\n\t/* The structure CopyFromState is private in PG < 14. So we can not access\n\t * the members like the line number or the size of the tuple.\n\t */\n\tif (cstate != NULL)\n\t\tlineno = cstate->cur_lineno;\n\tbuffer->linenos[buffer->nused] = lineno;\n\n\t/* Record this slot as being used */\n\tbuffer->nused++;\n\n\t/* Update how many tuples are stored and their size */\n\tmiinfo->bufferedTuples++;\n\n\t/*\n\t * Note: There is no reliable way to determine the in-memory size of a virtual\n\t * tuple. So, we perform flushing in PG < 14 only based on the number of buffered\n\t * tuples and not based on the size.\n\t */\n\tif (cstate != NULL)\n\t{\n\t\tint tuplen = cstate->line_buf.len;\n\t\tmiinfo->bufferedBytes += tuplen;\n\t}\n}\n\nstatic void\ncopy_chunk_state_destroy(CopyChunkState *ccstate)\n{\n\tts_chunk_tuple_routing_destroy(ccstate->ctr);\n\tFreeExecutorState(ccstate->estate);\n}\n\nstatic bool\nnext_copy_from(CopyChunkState *ccstate, ExprContext *econtext, Datum *values, bool *nulls)\n{\n\tAssert(ccstate->cstate != NULL);\n\treturn NextCopyFrom(ccstate->cstate, econtext, values, nulls);\n}\n\n/*\n * Error context callback when copying from table to chunk.\n */\nstatic void\ncopy_table_to_chunk_error_callback(void *arg)\n{\n\tTableScanDesc scandesc = (TableScanDesc) arg;\n\terrcontext(\"copying from table %s\", RelationGetRelationName(scandesc->rs_rd));\n}\n\nstatic TSCopyInsertMethod\nchoose_copy_method(Hypertable *ht, CopyChunkState *ccstate, ResultRelInfo *resultRelInfo)\n{\n\t/*\n\t * Multi-insert buffers (TS_CIM_MULTI_CONDITIONAL) can only be used if no triggers are\n\t * defined on the target table. Otherwise, the tuples may be inserted in an out-of-order\n\t * manner, which might violate the semantics of the triggers. So, they are inserted\n\t * tuple-per-tuple (TS_CIM_SINGLE). However, the ts_block trigger on the hypertable can\n\t * be ignored.\n\t */\n\n\t/* Before INSERT Triggers */\n\tbool has_before_insert_row_trig =\n\t\t(resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_insert_before_row);\n\n\t/* Instead of INSERT Triggers */\n\tbool has_instead_insert_row_trig =\n\t\t(resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_insert_instead_row);\n\n\tbool has_after_insert_statement_trig =\n\t\t(resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_insert_new_table);\n\n\t/* Depending on the configured trigger, enable or disable the multi-insert buffers */\n\tif (has_after_insert_statement_trig || has_before_insert_row_trig ||\n\t\thas_instead_insert_row_trig)\n\t{\n\t\tereport(DEBUG1,\n\t\t\t\t(errmsg(\"Using normal unbuffered copy operation (TS_CIM_SINGLE) \"\n\t\t\t\t\t\t\"because triggers are defined on the destination table.\")));\n\t\tif (ts_guc_enable_direct_compress_copy)\n\t\t\tereport(WARNING,\n\t\t\t\t\t(errmsg(\"disabling direct compress copy due to presence of triggers on the \"\n\t\t\t\t\t\t\t\"destination table\")));\n\t\treturn TS_CIM_SINGLE;\n\t}\n\n\tif (TS_HYPERTABLE_HAS_COMPRESSION_ENABLED(ht) && ts_guc_enable_direct_compress_copy)\n\t{\n\t\tif (ts_indexing_relation_has_primary_or_unique_index(ccstate->rel))\n\t\t{\n\t\t\tereport(WARNING,\n\t\t\t\t\t(errmsg(\"disabling direct compress because the destination table has unique \"\n\t\t\t\t\t\t\t\"constraints\")));\n\t\t}\n\t\telse if (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->numtriggers > 1)\n\t\t{\n\t\t\tereport(WARNING,\n\t\t\t\t\t(errmsg(\n\t\t\t\t\t\t\"disabling direct compress because the destination table has triggers\")));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tccstate->ctr->create_compressed_chunk = true;\n\t\t\tereport(DEBUG1, (errmsg(\"Using compressed copy operation (TS_CIM_COMPRESSION).\")));\n\t\t\treturn TS_CIM_COMPRESSION;\n\t\t}\n\t}\n\n\tereport(DEBUG1,\n\t\t\t(errmsg(\"Using optimized multi-buffer copy operation (TS_CIM_MULTI_CONDITIONAL).\")));\n\treturn TS_CIM_MULTI_CONDITIONAL;\n}\n\n/*\n * Use COPY FROM to copy data from file to relation.\n */\nstatic uint64\ncopyfrom(CopyChunkState *ccstate, ParseState *pstate, Hypertable *ht, MemoryContext copycontext,\n\t\t void (*callback)(void *), void *arg)\n{\n\tResultRelInfo *resultRelInfo;\n\tResultRelInfo *saved_resultRelInfo = NULL;\n\tEState *estate = ccstate->estate; /* for ExecConstraints() */\n\tExprContext *econtext;\n\tTupleTableSlot *singleslot;\n\tMemoryContext oldcontext = CurrentMemoryContext;\n\tErrorContextCallback errcallback = {\n\t\t.callback = callback,\n\t\t.arg = arg,\n\t};\n\tCommandId mycid = GetCurrentCommandId(true);\n\tTSCopyInsertMethod insertMethod;\t\t\t   /* The insert method for the table */\n\tTSCopyMultiInsertInfo multiInsertInfo = { 0 }; /* pacify compiler */\n\tint ti_options = 0;\t\t\t\t\t\t\t   /* start with default options for insert */\n\tBulkInsertState bistate = NULL;\n\tuint64 processed = 0;\n\tExprState *qualexpr = NULL;\n\n\tAssert(pstate->p_rtable);\n\n\tif (ccstate->rel->rd_rel->relkind != RELKIND_RELATION)\n\t{\n\t\tif (ccstate->rel->rd_rel->relkind == RELKIND_VIEW)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_WRONG_OBJECT_TYPE),\n\t\t\t\t\t errmsg(\"cannot copy to view \\\"%s\\\"\", RelationGetRelationName(ccstate->rel))));\n\t\telse if (ccstate->rel->rd_rel->relkind == RELKIND_MATVIEW)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_WRONG_OBJECT_TYPE),\n\t\t\t\t\t errmsg(\"cannot copy to materialized view \\\"%s\\\"\",\n\t\t\t\t\t\t\tRelationGetRelationName(ccstate->rel))));\n\t\telse if (ccstate->rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_WRONG_OBJECT_TYPE),\n\t\t\t\t\t errmsg(\"cannot copy to foreign table \\\"%s\\\"\",\n\t\t\t\t\t\t\tRelationGetRelationName(ccstate->rel))));\n\t\telse if (ccstate->rel->rd_rel->relkind == RELKIND_SEQUENCE)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_WRONG_OBJECT_TYPE),\n\t\t\t\t\t errmsg(\"cannot copy to sequence \\\"%s\\\"\",\n\t\t\t\t\t\t\tRelationGetRelationName(ccstate->rel))));\n\t\telse\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_WRONG_OBJECT_TYPE),\n\t\t\t\t\t errmsg(\"cannot copy to non-table relation \\\"%s\\\"\",\n\t\t\t\t\t\t\tRelationGetRelationName(ccstate->rel))));\n\t}\n\n\t/*----------\n\t * Check to see if we can avoid writing WAL\n\t *\n\t * If archive logging/streaming is not enabled *and* either\n\t *\t- table was created in same transaction as this COPY\n\t *\t- data is being written to relfilenode created in this transaction\n\t * then we can skip writing WAL.  It's safe because if the transaction\n\t * doesn't commit, we'll discard the table (or the new relfilenode file).\n\t * If it does commit, we'll have done the heap_sync at the bottom of this\n\t * routine first.\n\t *\n\t * As mentioned in comments in utils/rel.h, the in-same-transaction test\n\t * is not always set correctly, since in rare cases rd_newRelfilenodeSubid\n\t * can be cleared before the end of the transaction. The exact case is\n\t * when a relation sets a new relfilenode twice in same transaction, yet\n\t * the second one fails in an aborted subtransaction, e.g.\n\t *\n\t * BEGIN;\n\t * TRUNCATE t;\n\t * SAVEPOINT save;\n\t * TRUNCATE t;\n\t * ROLLBACK TO save;\n\t * COPY ...\n\t *\n\t * Also, if the target file is new-in-transaction, we assume that checking\n\t * FSM for free space is a waste of time, even if we must use WAL because\n\t * of archiving.  This could possibly be wrong, but it's unlikely.\n\t *\n\t * The comments for heap_insert and RelationGetBufferForTuple specify that\n\t * skipping WAL logging is only safe if we ensure that our tuples do not\n\t * go into pages containing tuples from any other transactions --- but this\n\t * must be the case if we have a new table or new relfilenode, so we need\n\t * no additional work to enforce that.\n\t *----------\n\t */\n\t/* createSubid is creation check, newRelfilenodeSubid is truncation check */\n\tif (ccstate->rel->rd_createSubid != InvalidSubTransactionId ||\n#if PG16_LT\n\t\tccstate->rel->rd_newRelfilenodeSubid != InvalidSubTransactionId)\n#else\n\t\tccstate->rel->rd_newRelfilelocatorSubid != InvalidSubTransactionId)\n#endif\n\t{\n\t\tti_options |= HEAP_INSERT_SKIP_FSM;\n\t}\n\n\t/*\n\t * We need a ResultRelInfo so we can use the regular executor's\n\t * index-entry-making machinery.  (There used to be a huge amount of code\n\t * here that basically duplicated execUtils.c ...)\n\t *\n\t * WARNING. The dummy rangetable index is decremented by 1 (unchecked)\n\t * inside `ExecConstraints` so unless you want to have a overflow, keep it\n\t * above zero. See `rt_fetch` in parsetree.h.\n\t */\n\tresultRelInfo = makeNode(ResultRelInfo);\n\n#if PG16_LT\n\tExecInitRangeTable(estate, pstate->p_rtable);\n#elif PG18_LT\n\tAssert(pstate->p_rteperminfos != NULL);\n\tExecInitRangeTable(estate, pstate->p_rtable, pstate->p_rteperminfos);\n#else\n\t/*\n\t * PG18+ adds unpruned relids to ExecInitRangeTable\n\t * We initialize it with 1 similar to upstream behavior,\n\t * but since this is copy no pruning is expected to happen.\n\t */\n\tAssert(pstate->p_rteperminfos != NULL);\n\tExecInitRangeTable(estate, pstate->p_rtable, pstate->p_rteperminfos, bms_make_singleton(1));\n#endif\n\tExecInitResultRelation(estate, resultRelInfo, 1);\n\n\tCheckValidResultRelCompat(resultRelInfo, CMD_INSERT, ONCONFLICT_NONE, NIL);\n\n\tExecOpenIndices(resultRelInfo, false);\n\n\tccstate->ctr = ts_chunk_tuple_routing_create(estate, ht, resultRelInfo);\n\n\tsingleslot = table_slot_create(resultRelInfo->ri_RelationDesc, &estate->es_tupleTable);\n\n\t/* Prepare to catch AFTER triggers. */\n\tAfterTriggerBeginQuery();\n\n\t/*\n\t * If there are any triggers with transition tables on the named relation,\n\t * we need to be prepared to capture transition tuples. Note that\n\t * ccstate->cstate is null when we migrate from an existing table in a\n\t * call from create_hypertable(), so we do not need a transition capture\n\t * state in this case.\n\t */\n\tif (ccstate->cstate)\n\t\tccstate->cstate->transition_capture =\n\t\t\tMakeTransitionCaptureState(ccstate->rel->trigdesc,\n\t\t\t\t\t\t\t\t\t   RelationGetRelid(ccstate->rel),\n\t\t\t\t\t\t\t\t\t   CMD_INSERT);\n\n\tif (ccstate->where_clause)\n\t\tqualexpr = ExecInitQual(castNode(List, ccstate->where_clause), NULL);\n\n\t/*\n\t * Check BEFORE STATEMENT insertion triggers. It's debatable whether we\n\t * should do this for COPY, since it's not really an \"INSERT\" statement as\n\t * such. However, executing these triggers maintains consistency with the\n\t * EACH ROW triggers that we already fire on COPY.\n\t */\n\tExecBSInsertTriggers(estate, resultRelInfo);\n\n\tbistate = GetBulkInsertState();\n\tecontext = GetPerTupleExprContext(estate);\n\n\t/* Set up callback to identify error line number.\n\t *\n\t * It is not necessary to add an entry to the error context stack if we do\n\t * not have a CopyFromState or callback. In that case, we just use the existing\n\t * error already on the context stack. */\n\tif (ccstate->cstate && callback)\n\t{\n\t\terrcallback.previous = error_context_stack;\n\t\terror_context_stack = &errcallback;\n\t}\n\n\tinsertMethod = choose_copy_method(ht, ccstate, resultRelInfo);\n\n\tTSCopyMultiInsertInfoInit(&multiInsertInfo,\n\t\t\t\t\t\t\t  resultRelInfo,\n\t\t\t\t\t\t\t  ccstate,\n\t\t\t\t\t\t\t  estate,\n\t\t\t\t\t\t\t  mycid,\n\t\t\t\t\t\t\t  ti_options,\n\t\t\t\t\t\t\t  ht);\n\n\tTSCopyMultiInsertBuffer *buffer = NULL;\n\tint reset_count = 0;\t\t\t /* Reset the per-tuple exprcontext every 100 tuples */\n\tOid prev_chunk_oid = InvalidOid; /* Previous chunk OID to detect chunk changes */\n\tfor (;;)\n\t{\n\t\tTupleTableSlot *myslot = NULL;\n\t\tbool skip_tuple;\n\t\tPoint *point = NULL;\n\t\tChunkInsertState *cis = NULL;\n\n\t\tCHECK_FOR_INTERRUPTS();\n\n\t\t/*\n\t\t * Reset the per-tuple exprcontext. We do this after every tuple, to\n\t\t * clean-up after expression evaluations etc.\n\t\t */\n\t\tif (reset_count == 100)\n\t\t{\n\t\t\tResetPerTupleExprContext(estate);\n\t\t\treset_count = 0;\n\t\t}\n\t\telse\n\t\t\treset_count++;\n\n\t\tmyslot = singleslot;\n\t\tAssert(myslot != NULL);\n\n\t\t/* Switch into its memory context */\n\t\tMemoryContextSwitchTo(GetPerTupleMemoryContext(estate));\n\n\t\tExecClearTuple(myslot);\n\n\t\tif (!ccstate->next_copy_from(ccstate, econtext, myslot->tts_values, myslot->tts_isnull))\n\t\t\tbreak;\n\n\t\tExecStoreVirtualTuple(myslot);\n\n\t\t/* Calculate the tuple's point in the N-dimensional hyperspace */\n\t\tpoint = ts_hyperspace_calculate_point(ht->space, myslot);\n\n\t\t/* Find or create the insert state matching the point */\n\t\tcis = ts_chunk_tuple_routing_find_chunk(ccstate->ctr, point);\n\t\tif (OidIsValid(prev_chunk_oid) && prev_chunk_oid != cis->rel->rd_id)\n\t\t{\n\t\t\tReleaseBulkInsertStatePin(bistate);\n\t\t}\n\n\t\tprev_chunk_oid = cis->rel->rd_id;\n\n\t\tAssert(cis != NULL);\n\n\t\tts_chunk_tuple_routing_decompress_for_insert(cis,\n\t\t\t\t\t\t\t\t\t\t\t\t\t ccstate->ctr->root_rri,\n\t\t\t\t\t\t\t\t\t\t\t\t\t myslot,\n\t\t\t\t\t\t\t\t\t\t\t\t\t ccstate->ctr->estate,\n\t\t\t\t\t\t\t\t\t\t\t\t\t false);\n\n\t\t/* Triggers and stuff need to be invoked in query context. */\n\t\tMemoryContextSwitchTo(oldcontext);\n\n\t\tbuffer = TSCopyMultiInsertInfoGetOrSetupBuffer(&multiInsertInfo, cis, point, insertMethod);\n\n\t\t/*\n\t\t * If the insert method has changed, we need to flush the\n\t\t * multi-insert info to ensure that the tuples are\n\t\t * visible to the triggers.\n\t\t */\n\t\tif (insertMethod != buffer->method)\n\t\t\tTSCopyMultiInsertInfoFlush(&multiInsertInfo, cis);\n\n\t\t/* Convert the tuple to match the chunk's rowtype */\n\t\tif (buffer->method == TS_CIM_SINGLE)\n\t\t{\n\t\t\tif (NULL != cis->hyper_to_chunk_map)\n\t\t\t\tmyslot = execute_attr_map_slot(cis->hyper_to_chunk_map->attrMap, myslot, cis->slot);\n\t\t}\n\t\telse if (buffer->method == TS_CIM_COMPRESSION)\n\t\t{\n\t\t\tif (NULL != cis->hyper_to_chunk_map)\n\t\t\t\tmyslot = execute_attr_map_slot(cis->hyper_to_chunk_map->attrMap, myslot, cis->slot);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/*\n\t\t\t * Prepare to queue up tuple for later batch insert into\n\t\t\t * current chunk.\n\t\t\t */\n\t\t\tTupleTableSlot *batchslot;\n\n\t\t\tbatchslot = TSCopyMultiInsertInfoNextFreeSlot(&multiInsertInfo,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  cis->result_relation_info,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  buffer);\n\n\t\t\tif (NULL != cis->hyper_to_chunk_map)\n\t\t\t\tmyslot = execute_attr_map_slot(cis->hyper_to_chunk_map->attrMap, myslot, batchslot);\n\t\t\telse\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * This looks more expensive than it is (Believe me, I\n\t\t\t\t * optimized it away. Twice.). The input is in virtual\n\t\t\t\t * form, and we'll materialize the slot below - for most\n\t\t\t\t * slot types the copy performs the work materialization\n\t\t\t\t * would later require anyway.\n\t\t\t\t */\n\t\t\t\tExecCopySlot(batchslot, myslot);\n\t\t\t\tmyslot = batchslot;\n\t\t\t}\n\t\t}\n\n\t\tif (qualexpr != NULL)\n\t\t{\n\t\t\tecontext->ecxt_scantuple = myslot;\n\t\t\tif (!ExecQual(qualexpr, econtext))\n\t\t\t\tcontinue;\n\t\t}\n\n\t\t/*\n\t\t * Set the result relation in the executor state to the target chunk.\n\t\t * This makes sure that the tuple gets inserted into the correct\n\t\t * chunk.\n\t\t */\n\t\tsaved_resultRelInfo = resultRelInfo;\n\t\tresultRelInfo = cis->result_relation_info;\n\n\t\t/* Set the right relation for triggers */\n\t\tts_tuptableslot_set_table_oid(myslot, RelationGetRelid(resultRelInfo->ri_RelationDesc));\n\n\t\tskip_tuple = false;\n\n\t\t/* BEFORE ROW INSERT Triggers */\n\t\tif (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_insert_before_row)\n\t\t\tskip_tuple = !ExecBRInsertTriggers(estate, resultRelInfo, myslot);\n\n\t\tif (!skip_tuple)\n\t\t{\n\t\t\t/* Note that PostgreSQL's copy path would check INSTEAD OF\n\t\t\t * INSERT/UPDATE/DELETE triggers here, but such triggers can only\n\t\t\t * exist on views and chunks cannot be views.\n\t\t\t */\n\t\t\tList *recheckIndexes = NIL;\n\n\t\t\t/* Compute stored generated columns */\n\t\t\tif (resultRelInfo->ri_RelationDesc->rd_att->constr &&\n\t\t\t\tresultRelInfo->ri_RelationDesc->rd_att->constr->has_generated_stored)\n\t\t\t\tExecComputeStoredGenerated(resultRelInfo, estate, myslot, CMD_INSERT);\n\n\t\t\t/*\n\t\t\t * If the target is a plain table, check the constraints of\n\t\t\t * the tuple. Since we check the constraints during tuple routing\n\t\t\t * we only need to check if we have additional constraints beyond\n\t\t\t * partitioning constraints.\n\t\t\t */\n\t\t\tif (!buffer->can_skip_constraints)\n\t\t\t{\n\t\t\t\tAssert(resultRelInfo->ri_RangeTableIndex > 0 && estate->es_range_table);\n\t\t\t\tExecConstraints(resultRelInfo, myslot, estate);\n\t\t\t}\n\n\t\t\tif (buffer->method == TS_CIM_SINGLE)\n\t\t\t{\n\t\t\t\t/* OK, store the tuple and create index entries for it */\n\t\t\t\ttable_tuple_insert(resultRelInfo->ri_RelationDesc,\n\t\t\t\t\t\t\t\t   myslot,\n\t\t\t\t\t\t\t\t   mycid,\n\t\t\t\t\t\t\t\t   ti_options,\n\t\t\t\t\t\t\t\t   bistate);\n\n\t\t\t\tif (resultRelInfo->ri_NumIndices > 0)\n\t\t\t\t\trecheckIndexes = ExecInsertIndexTuplesCompat(resultRelInfo,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t myslot,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t estate,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t NIL,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t false);\n\t\t\t\t/* AFTER ROW INSERT Triggers. We do not need to do this if we\n\t\t\t\t * are migrating data from an existing table in a call from\n\t\t\t\t * create_hypertable(). */\n\t\t\t\tif (ccstate->cstate)\n\t\t\t\t\tExecARInsertTriggers(estate,\n\t\t\t\t\t\t\t\t\t\t resultRelInfo,\n\t\t\t\t\t\t\t\t\t\t myslot,\n\t\t\t\t\t\t\t\t\t\t recheckIndexes,\n\t\t\t\t\t\t\t\t\t\t ccstate->cstate->transition_capture);\n\t\t\t}\n\t\t\telse if (buffer->method == TS_CIM_COMPRESSION)\n\t\t\t{\n\t\t\t\tts_cm_functions->compressor_add_slot(buffer->compressor,\n\t\t\t\t\t\t\t\t\t\t\t\t\t buffer->bulk_writer,\n\t\t\t\t\t\t\t\t\t\t\t\t\t myslot);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * The slot previously might point into the per-tuple\n\t\t\t\t * context. For batching it needs to be longer lived.\n\t\t\t\t */\n\t\t\t\tExecMaterializeSlot(myslot);\n\n\t\t\t\t/* Add this tuple to the tuple buffer */\n\t\t\t\tTSCopyMultiInsertInfoStore(&multiInsertInfo,\n\t\t\t\t\t\t\t\t\t\t   resultRelInfo,\n\t\t\t\t\t\t\t\t\t\t   buffer,\n\t\t\t\t\t\t\t\t\t\t   myslot,\n\t\t\t\t\t\t\t\t\t\t   ccstate->cstate);\n\n\t\t\t\t/*\n\t\t\t\t * If enough inserts have queued up, then flush all\n\t\t\t\t * buffers out to their tables.\n\t\t\t\t */\n\t\t\t\tif (TSCopyMultiInsertInfoIsFull(&multiInsertInfo))\n\t\t\t\t{\n\t\t\t\t\tereport(DEBUG2,\n\t\t\t\t\t\t\t(errmsg(\"flush called with %d bytes and %d buffered tuples\",\n\t\t\t\t\t\t\t\t\tmultiInsertInfo.bufferedBytes,\n\t\t\t\t\t\t\t\t\tmultiInsertInfo.bufferedTuples)));\n\n\t\t\t\t\tTSCopyMultiInsertInfoFlush(&multiInsertInfo, cis);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlist_free(recheckIndexes);\n\n\t\t\t/*\n\t\t\t * We count only tuples not suppressed by a BEFORE INSERT trigger;\n\t\t\t * this is the same definition used by execMain.c for counting\n\t\t\t * tuples inserted by an INSERT command.\n\t\t\t */\n\t\t\tprocessed++;\n\t\t}\n\n\t\tresultRelInfo = saved_resultRelInfo;\n\t}\n\n\t/* Flush any remaining buffered tuples */\n\tif (insertMethod != TS_CIM_SINGLE)\n\t\tTSCopyMultiInsertInfoFlushAndCleanup(&multiInsertInfo);\n\n\t/* Done, clean up */\n\tif (ccstate->cstate && callback)\n\t\terror_context_stack = errcallback.previous;\n\n\tFreeBulkInsertState(bistate);\n\n\tMemoryContextSwitchTo(oldcontext);\n\n\t/*\n\t * Execute AFTER STATEMENT insertion triggers.\n\t *\n\t * We do not need to do this ccstate->cstate is NULL, which is the case\n\t * when migrating data from an existing table in a call from\n\t * create_hypertable().\n\t */\n\tif (ccstate->cstate)\n\t\tExecASInsertTriggers(estate, resultRelInfo, ccstate->cstate->transition_capture);\n\n\t/* Handle queued AFTER triggers */\n\tAfterTriggerEndQuery(estate);\n\n\tExecResetTupleTable(estate->es_tupleTable, false);\n\n\tExecCloseResultRelations(estate);\n\tExecCloseRangeTableRelations(estate);\n\n\t/*\n\t * If we skipped writing WAL, then we need to sync the heap (but not\n\t * indexes since those use WAL anyway)\n\t */\n\tif (!RelationNeedsWAL(ccstate->rel))\n\t\tsmgrimmedsync(RelationGetSmgr(ccstate->rel), MAIN_FORKNUM);\n\n\treturn processed;\n}\n\n/*\n * CopyGetAttnums - build an integer list of attnums to be copied\n *\n * The input attnamelist is either the user-specified column list,\n * or NIL if there was none (in which case we want all the non-dropped\n * columns).\n *\n * rel can be NULL ... it's only used for error reports.\n */\nstatic List *\ntimescaledb_CopyGetAttnums(TupleDesc tupDesc, Relation rel, List *attnamelist)\n{\n\tList *attnums = NIL;\n\n\tif (attnamelist == NIL)\n\t{\n\t\t/* Generate default column list */\n\t\tint attr_count = tupDesc->natts;\n\t\tint i;\n\n\t\tfor (i = 0; i < attr_count; i++)\n\t\t{\n\t\t\tForm_pg_attribute attr = TupleDescAttr(tupDesc, i);\n\n\t\t\tif (attr->attisdropped)\n\t\t\t\tcontinue;\n\t\t\tattnums = lappend_int(attnums, i + 1);\n\t\t}\n\t}\n\telse\n\t{\n\t\t/* Validate the user-supplied list and extract attnums */\n\t\tListCell *l;\n\n\t\tforeach (l, attnamelist)\n\t\t{\n\t\t\tchar *name = strVal(lfirst(l));\n\t\t\tint attnum;\n\t\t\tint i;\n\n\t\t\t/* Lookup column name */\n\t\t\tattnum = InvalidAttrNumber;\n\t\t\tfor (i = 0; i < tupDesc->natts; i++)\n\t\t\t{\n\t\t\t\tForm_pg_attribute attr = TupleDescAttr(tupDesc, i);\n\n\t\t\t\tif (attr->attisdropped)\n\t\t\t\t\tcontinue;\n\t\t\t\tif (namestrcmp(&(attr->attname), name) == 0)\n\t\t\t\t{\n\t\t\t\t\tattnum = attr->attnum;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (attnum == InvalidAttrNumber)\n\t\t\t{\n\t\t\t\tif (rel != NULL)\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_COLUMN),\n\t\t\t\t\t\t\t errmsg(\"column \\\"%s\\\" of relation \\\"%s\\\" does not exist\",\n\t\t\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\t\t\tRelationGetRelationName(rel))));\n\t\t\t\telse\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_COLUMN),\n\t\t\t\t\t\t\t errmsg(\"column \\\"%s\\\" does not exist\", name)));\n\t\t\t}\n\t\t\t/* Check for duplicates */\n\t\t\tif (list_member_int(attnums, attnum))\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_DUPLICATE_COLUMN),\n\t\t\t\t\t\t errmsg(\"column \\\"%s\\\" specified more than once\", name)));\n\t\t\tattnums = lappend_int(attnums, attnum);\n\t\t}\n\t}\n\n\treturn attnums;\n}\n\nstatic void\ncopy_constraints_and_check(ParseState *pstate, Relation rel, List *attnums)\n{\n\tListCell *cur;\n\tchar *xactReadOnly;\n\tParseNamespaceItem *nsitem =\n\t\taddRangeTableEntryForRelation(pstate, rel, RowExclusiveLock, NULL, false, false);\n\tRangeTblEntry *rte = nsitem->p_rte;\n\taddNSItemToQuery(pstate, nsitem, true, true, true);\n\n#if PG16_LT\n\trte->requiredPerms = ACL_INSERT;\n\n\tforeach (cur, attnums)\n\t{\n\t\tint attno = lfirst_int(cur) - FirstLowInvalidHeapAttributeNumber;\n\t\trte->insertedCols = bms_add_member(rte->insertedCols, attno);\n\t}\n\n\tExecCheckRTPerms(pstate->p_rtable, true);\n#else\n\tRTEPermissionInfo *perminfo = nsitem->p_perminfo;\n\tperminfo->requiredPerms = ACL_INSERT;\n\n\tforeach (cur, attnums)\n\t{\n\t\tint attno = lfirst_int(cur) - FirstLowInvalidHeapAttributeNumber;\n\t\tperminfo->insertedCols = bms_add_member(perminfo->insertedCols, attno);\n\t}\n\n\tExecCheckPermissions(pstate->p_rtable, list_make1(perminfo), true);\n#endif\n\n\t/*\n\t * Permission check for row security policies.\n\t *\n\t * check_enable_rls will ereport(ERROR) if the user has requested\n\t * something invalid and will otherwise indicate if we should enable RLS\n\t * (returns RLS_ENABLED) or not for this COPY statement.\n\t *\n\t * If the relation has a row security policy and we are to apply it then\n\t * perform a \"query\" copy and allow the normal query processing to handle\n\t * the policies.\n\t *\n\t * If RLS is not enabled for this, then just fall through to the normal\n\t * non-filtering relation handling.\n\t */\n\tif (check_enable_rls(rte->relid, InvalidOid, false) == RLS_ENABLED)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"COPY FROM not supported with row-level security\"),\n\t\t\t\t errhint(\"Use INSERT statements instead.\")));\n\t}\n\n\t/* check read-only transaction and parallel mode */\n\txactReadOnly = GetConfigOptionByName(\"transaction_read_only\", NULL, false);\n\n\tif (strncmp(xactReadOnly, \"on\", sizeof(\"on\")) == 0 && !rel->rd_islocaltemp)\n\t\tPreventCommandIfReadOnly(\"COPY FROM\");\n\tPreventCommandIfParallelMode(\"COPY FROM\");\n}\n\nvoid\ntimescaledb_DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed, Hypertable *ht)\n{\n\tCopyChunkState *ccstate;\n\tCopyFromState cstate;\n\tbool pipe = (stmt->filename == NULL);\n\tRelation rel;\n\tList *attnums = NIL;\n\tNode *where_clause = NULL;\n\tParseState *pstate;\n\tMemoryContext copycontext = NULL;\n\n\t/* Disallow COPY to/from file or program except to superusers. */\n\tif (!pipe && !superuser())\n\t{\n\t\tif (stmt->is_program)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),\n\t\t\t\t\t errmsg(\"must be superuser to COPY to or from an external program\"),\n\t\t\t\t\t errhint(\"Anyone can COPY to stdout or from stdin. \"\n\t\t\t\t\t\t\t \"psql's \\\\copy command also works for anyone.\")));\n\t\telse\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),\n\t\t\t\t\t errmsg(\"must be superuser to COPY to or from a file\"),\n\t\t\t\t\t errhint(\"Anyone can COPY to stdout or from stdin. \"\n\t\t\t\t\t\t\t \"psql's \\\\copy command also works for anyone.\")));\n\t}\n\n\tif (!stmt->is_from || NULL == stmt->relation)\n\t\telog(ERROR, \"timescale DoCopy should only be called for COPY FROM\");\n\n\tAssert(!stmt->query);\n\n\t/*\n\t * We never actually write to the main table, but we need RowExclusiveLock\n\t * to ensure no one else is. Because of the check above, we know that\n\t * `stmt->relation` is defined, so we are guaranteed to have a relation\n\t * available.\n\t */\n\trel = table_openrv(stmt->relation, RowExclusiveLock);\n\n\tattnums = timescaledb_CopyGetAttnums(RelationGetDescr(rel), rel, stmt->attlist);\n\n\tpstate = make_parsestate(NULL);\n\tpstate->p_sourcetext = queryString;\n\tcopy_constraints_and_check(pstate, rel, attnums);\n\n\tcstate = BeginCopyFrom(pstate,\n\t\t\t\t\t\t   rel,\n\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t   stmt->filename,\n\t\t\t\t\t\t   stmt->is_program,\n\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t   stmt->attlist,\n\t\t\t\t\t\t   stmt->options);\n\n\tif (stmt->whereClause)\n\t{\n\t\twhere_clause = transformExpr(pstate, stmt->whereClause, EXPR_KIND_COPY_WHERE);\n\n\t\twhere_clause = coerce_to_boolean(pstate, where_clause, \"WHERE\");\n\t\tassign_expr_collations(pstate, where_clause);\n\n\t\twhere_clause = eval_const_expressions(NULL, where_clause);\n\n\t\twhere_clause = (Node *) canonicalize_qual((Expr *) where_clause, false);\n\t\twhere_clause = (Node *) make_ands_implicit((Expr *) where_clause);\n\t}\n\n\tccstate = copy_chunk_state_create(ht, rel, next_copy_from, cstate, NULL);\n\tccstate->where_clause = where_clause;\n\tcopycontext = cstate->copycontext;\n\t*processed = copyfrom(ccstate, pstate, ht, copycontext, CopyFromErrorCallback, cstate);\n\n\tcopy_chunk_state_destroy(ccstate);\n\tEndCopyFrom(cstate);\n\tfree_parsestate(pstate);\n\ttable_close(rel, NoLock);\n}\n\nstatic bool\nnext_copy_from_table_to_chunks(CopyChunkState *ccstate, ExprContext *econtext, Datum *values,\n\t\t\t\t\t\t\t   bool *nulls)\n{\n\tTableScanDesc scandesc = ccstate->scandesc;\n\tHeapTuple tuple;\n\n\tAssert(scandesc != NULL);\n\ttuple = heap_getnext(scandesc, ForwardScanDirection);\n\n\tif (!HeapTupleIsValid(tuple))\n\t\treturn false;\n\n\theap_deform_tuple(tuple, RelationGetDescr(ccstate->rel), values, nulls);\n\n\treturn true;\n}\n\n/*\n * Move data from the given hypertable's main table to chunks.\n *\n * The data moving is essentially a COPY from the main table to the chunks\n * followed by a TRUNCATE on the main table.\n */\nvoid\ntimescaledb_move_from_table_to_chunks(Hypertable *ht, LOCKMODE lockmode)\n{\n\tRelation rel;\n\tCopyChunkState *ccstate;\n\tTableScanDesc scandesc;\n\tParseState *pstate = make_parsestate(NULL);\n\tSnapshot snapshot;\n\tList *attnums = NIL;\n\tMemoryContext copycontext;\n\n\tRangeVar rv = {\n\t\t.schemaname = NameStr(ht->fd.schema_name),\n\t\t.relname = NameStr(ht->fd.table_name),\n\t\t.inh = false, /* Don't recurse */\n\t};\n\n\tTruncateStmt stmt = {\n\t\t.type = T_TruncateStmt,\n\t\t.relations = list_make1(&rv),\n\t\t.behavior = DROP_RESTRICT,\n\t};\n\tint i;\n\n\trel = table_open(ht->main_table_relid, lockmode);\n\n\tfor (i = 0; i < rel->rd_att->natts; i++)\n\t{\n\t\tForm_pg_attribute attr = TupleDescAttr(rel->rd_att, i);\n\t\tattnums = lappend_int(attnums, attr->attnum);\n\t}\n\n\tcopycontext = AllocSetContextCreate(CurrentMemoryContext, \"COPY\", ALLOCSET_DEFAULT_SIZES);\n\n\tcopy_constraints_and_check(pstate, rel, attnums);\n\tsnapshot = RegisterSnapshot(GetLatestSnapshot());\n\tscandesc = table_beginscan(rel, snapshot, 0, NULL);\n\tccstate = copy_chunk_state_create(ht, rel, next_copy_from_table_to_chunks, NULL, scandesc);\n\tcopyfrom(ccstate, pstate, ht, copycontext, copy_table_to_chunk_error_callback, scandesc);\n\tcopy_chunk_state_destroy(ccstate);\n\ttable_endscan(scandesc);\n\tUnregisterSnapshot(snapshot);\n\ttable_close(rel, lockmode);\n\n\tif (MemoryContextIsValid(copycontext))\n\t\tMemoryContextDelete(copycontext);\n\n\tExecuteTruncate(&stmt);\n}\n"
  },
  {
    "path": "src/copy.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <access/relscan.h>\n#include <access/xact.h>\n#include <commands/copy.h>\n#include <executor/executor.h>\n#include <nodes/parsenodes.h>\n#include <storage/lockdefs.h>\n\n#include \"chunk_tuple_routing.h\"\n\ntypedef struct CopyChunkState CopyChunkState;\ntypedef struct Hypertable Hypertable;\n\ntypedef bool (*CopyFromFunc)(CopyChunkState *ccstate, ExprContext *econtext, Datum *values,\n\t\t\t\t\t\t\t bool *nulls);\n\ntypedef struct CopyChunkState\n{\n\tRelation rel;\n\tEState *estate;\n\tChunkTupleRouting *ctr;\n\tCopyFromFunc next_copy_from;\n\tCopyFromState cstate;\n\tTableScanDesc scandesc;\n\tNode *where_clause;\n} CopyChunkState;\n\nextern void timescaledb_DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed,\n\t\t\t\t\t\t\t   Hypertable *ht);\nextern void timescaledb_move_from_table_to_chunks(Hypertable *ht, LOCKMODE lockmode);\n"
  },
  {
    "path": "src/cross_module_fn.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/amapi.h>\n#include <fmgr.h>\n#include <utils/lsyscache.h>\n#include <utils/timestamp.h>\n\n#include \"bgw/job.h\"\n#include \"cross_module_fn.h\"\n#include \"export.h\"\n#include \"guc.h\"\n#include \"license_guc.h\"\n\n#define CROSSMODULE_WRAPPER(func)                                                                  \\\n\tTS_FUNCTION_INFO_V1(ts_##func);                                                                \\\n\tDatum ts_##func(PG_FUNCTION_ARGS)                                           \\\n\t{                                                                                              \\\n\t\tPG_RETURN_DATUM(ts_cm_functions->func(fcinfo));                                            \\\n\t}\n\n/* bgw policy functions */\nCROSSMODULE_WRAPPER(policy_compression_add);\nCROSSMODULE_WRAPPER(policy_compression_remove);\nCROSSMODULE_WRAPPER(policy_recompression_proc);\nCROSSMODULE_WRAPPER(policy_compression_check);\nCROSSMODULE_WRAPPER(policy_refresh_cagg_add);\nCROSSMODULE_WRAPPER(policy_refresh_cagg_proc);\nCROSSMODULE_WRAPPER(policy_refresh_cagg_check);\nCROSSMODULE_WRAPPER(policy_process_hyper_inval_remove);\nCROSSMODULE_WRAPPER(policy_process_hyper_inval_add);\nCROSSMODULE_WRAPPER(policy_process_hyper_inval_proc);\nCROSSMODULE_WRAPPER(policy_process_hyper_inval_check);\nCROSSMODULE_WRAPPER(policy_refresh_cagg_remove);\nCROSSMODULE_WRAPPER(policy_reorder_add);\nCROSSMODULE_WRAPPER(policy_reorder_proc);\nCROSSMODULE_WRAPPER(policy_reorder_check);\nCROSSMODULE_WRAPPER(policy_reorder_remove);\nCROSSMODULE_WRAPPER(policy_retention_add);\nCROSSMODULE_WRAPPER(policy_retention_proc);\nCROSSMODULE_WRAPPER(policy_retention_check);\nCROSSMODULE_WRAPPER(policy_retention_remove);\n\nCROSSMODULE_WRAPPER(job_add);\nCROSSMODULE_WRAPPER(job_delete);\nCROSSMODULE_WRAPPER(job_run);\nCROSSMODULE_WRAPPER(job_alter);\nCROSSMODULE_WRAPPER(job_alter_set_hypertable_id);\n\nCROSSMODULE_WRAPPER(reorder_chunk);\nCROSSMODULE_WRAPPER(move_chunk);\n\nCROSSMODULE_WRAPPER(policies_add);\nCROSSMODULE_WRAPPER(policies_remove);\nCROSSMODULE_WRAPPER(policies_remove_all);\nCROSSMODULE_WRAPPER(policies_alter);\nCROSSMODULE_WRAPPER(policies_show);\n\n/* compression functions */\nCROSSMODULE_WRAPPER(compressed_data_decompress_forward);\nCROSSMODULE_WRAPPER(compressed_data_decompress_reverse);\nCROSSMODULE_WRAPPER(compressed_data_column_size);\nCROSSMODULE_WRAPPER(compressed_data_to_array);\nCROSSMODULE_WRAPPER(compressed_data_send);\nCROSSMODULE_WRAPPER(compressed_data_recv);\nCROSSMODULE_WRAPPER(compressed_data_in);\nCROSSMODULE_WRAPPER(compressed_data_out);\nCROSSMODULE_WRAPPER(compressed_data_info);\nCROSSMODULE_WRAPPER(compressed_data_has_nulls);\nCROSSMODULE_WRAPPER(deltadelta_compressor_append);\nCROSSMODULE_WRAPPER(deltadelta_compressor_finish);\nCROSSMODULE_WRAPPER(gorilla_compressor_append);\nCROSSMODULE_WRAPPER(gorilla_compressor_finish);\nCROSSMODULE_WRAPPER(dictionary_compressor_append);\nCROSSMODULE_WRAPPER(dictionary_compressor_finish);\nCROSSMODULE_WRAPPER(array_compressor_append);\nCROSSMODULE_WRAPPER(array_compressor_finish);\nCROSSMODULE_WRAPPER(bool_compressor_append);\nCROSSMODULE_WRAPPER(bool_compressor_finish);\nCROSSMODULE_WRAPPER(uuid_compressor_append);\nCROSSMODULE_WRAPPER(uuid_compressor_finish);\nCROSSMODULE_WRAPPER(create_compressed_chunk);\nCROSSMODULE_WRAPPER(compress_chunk);\nCROSSMODULE_WRAPPER(decompress_chunk);\nCROSSMODULE_WRAPPER(rebuild_columnstore);\nCROSSMODULE_WRAPPER(bloom1_contains);\nCROSSMODULE_WRAPPER(bloom1_contains_any);\n\n/* continuous aggregate */\nCROSSMODULE_WRAPPER(continuous_agg_refresh);\nCROSSMODULE_WRAPPER(continuous_agg_validate_query);\nCROSSMODULE_WRAPPER(continuous_agg_get_bucket_function);\nCROSSMODULE_WRAPPER(continuous_agg_get_bucket_function_info);\nCROSSMODULE_WRAPPER(continuous_agg_get_grouping_columns);\n\nCROSSMODULE_WRAPPER(chunk_freeze_chunk);\nCROSSMODULE_WRAPPER(chunk_unfreeze_chunk);\n\nCROSSMODULE_WRAPPER(recompress_chunk_segmentwise);\nCROSSMODULE_WRAPPER(get_compressed_chunk_index_for_recompression);\nCROSSMODULE_WRAPPER(merge_chunks);\nCROSSMODULE_WRAPPER(split_chunk);\n\nCROSSMODULE_WRAPPER(detach_chunk);\nCROSSMODULE_WRAPPER(attach_chunk);\n\nCROSSMODULE_WRAPPER(estimate_compressed_batch_size);\n\n/*\n * casting a function pointer to a pointer of another type is undefined\n * behavior, so we need one of these for every function type we have\n */\n\nstatic void\nerror_no_default_fn_community(void)\n{\n\tereport(ERROR,\n\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t errmsg(\"functionality not supported under the current \\\"%s\\\" license. Learn more at \"\n\t\t\t\t\t\"https://tsdb.co/pdbir1r3\",\n\t\t\t\t\tts_guc_license),\n\t\t\t errhint(\"To access all features and the best time-series experience, try out \"\n\t\t\t\t\t \"Timescale Cloud.\")));\n}\n\nstatic bool\nerror_no_default_fn_bool_void_community(void)\n{\n\terror_no_default_fn_community();\n\tpg_unreachable();\n}\n\nstatic bool\njob_execute_default_fn(BgwJob *job)\n{\n\terror_no_default_fn_community();\n\tpg_unreachable();\n}\n\nstatic void\ntsl_postprocess_plan_stub(PlannedStmt *stmt)\n{\n}\n\nstatic bool\nprocess_compress_table_default(Hypertable *ht, WithClauseResult *with_clause_options)\n{\n\terror_no_default_fn_community();\n\tpg_unreachable();\n}\n\nstatic void\ncolumnstore_setup_default(Hypertable *ht, WithClauseResult *with_clause_options)\n{\n\terror_no_default_fn_community();\n\tpg_unreachable();\n}\n\nstatic Datum\nerror_no_default_fn_pg_community(PG_FUNCTION_ARGS)\n{\n\tereport(ERROR,\n\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t errmsg(\"function \\\"%s\\\" is not supported under the current \\\"%s\\\" license\",\n\t\t\t\t\tfcinfo->flinfo ? get_func_name(fcinfo->flinfo->fn_oid) : \"unknown\",\n\t\t\t\t\tts_guc_license),\n\t\t\t errhint(\"Upgrade your license to 'timescale' to use this free community feature.\")));\n\n\tpg_unreachable();\n}\n\nstatic void\nerror_no_default_fn_chunk_insert_state_community(ChunkInsertState *cis, TupleTableSlot *slot)\n{\n\terror_no_default_fn_community();\n\tpg_unreachable();\n}\n\n/*\n * TSL library is not loaded by the replication worker for some reason,\n * so a call to `compressed_data_in` and `compressed_data_out` functions would\n * produce a misleading error saying that your license is \"timescale\" and you\n * should upgrade to \"timescale\" license, even if you have already upgraded.\n *\n * As a workaround, we try to load the TSL module it in this function.\n * It will still error out in the \"apache\" version\n */\n\nstatic Datum\nprocess_compressed_data_in(PG_FUNCTION_ARGS)\n{\n\tts_license_enable_module_loading();\n\n\tif (ts_cm_functions->compressed_data_in != process_compressed_data_in)\n\t\treturn ts_cm_functions->compressed_data_in(fcinfo);\n\n\terror_no_default_fn_pg_community(fcinfo);\n\tpg_unreachable();\n}\n\nstatic Datum\nprocess_compressed_data_out(PG_FUNCTION_ARGS)\n{\n\tts_license_enable_module_loading();\n\n\tif (ts_cm_functions->compressed_data_out != process_compressed_data_out)\n\t\treturn ts_cm_functions->compressed_data_out(fcinfo);\n\n\terror_no_default_fn_pg_community(fcinfo);\n\tpg_unreachable();\n}\n\nstatic DDLResult\nprocess_cagg_viewstmt_default(Node *stmt, const char *query_string, void *pstmt,\n\t\t\t\t\t\t\t  WithClauseResult *with_clause_options)\n{\n\treturn error_no_default_fn_bool_void_community();\n}\n\nstatic void\ncontinuous_agg_update_options_default(ContinuousAgg *cagg, WithClauseResult *with_clause_options)\n{\n\terror_no_default_fn_community();\n\tpg_unreachable();\n}\n\nstatic void\ncontinuous_agg_invalidate_raw_ht_all_default(const Hypertable *raw_ht, int64 start, int64 end)\n{\n\terror_no_default_fn_community();\n\tpg_unreachable();\n}\n\nstatic void\ncontinuous_agg_invalidate_mat_ht_all_default(const Hypertable *raw_ht, const Hypertable *mat_ht,\n\t\t\t\t\t\t\t\t\t\t\t int64 start, int64 end)\n{\n\terror_no_default_fn_community();\n\tpg_unreachable();\n}\n\nstatic void\ncontinuous_agg_dml_invalidate_default(int32 hypertable_id, Relation chunk_rel,\n\t\t\t\t\t\t\t\t\t  HeapTuple chunk_tuple, HeapTuple chunk_newtuple, bool update)\n{\n\terror_no_default_fn_community();\n\tpg_unreachable();\n}\n\nTS_FUNCTION_INFO_V1(ts_tsl_loaded);\n\nPGDLLEXPORT Datum\nts_tsl_loaded(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_BOOL(ts_cm_functions != &ts_cm_functions_default);\n}\n\nstatic void\npreprocess_query_tsl_default_fn_community(Query *parse, int *cursor_opts)\n{\n\t/* No op in community licensed code */\n}\n\nstatic PGFunction\nbloom1_get_hash_function_default(Oid type, FmgrInfo **finfo)\n{\n\terror_no_default_fn_community();\n\tpg_unreachable();\n}\n\n/*\n * Define cross-module functions' default values:\n * If the submodule isn't activated, using one of the cm functions will throw an\n * exception.\n */\nTSDLLEXPORT CrossModuleFunctions ts_cm_functions_default = {\n\t.create_upper_paths_hook = NULL,\n\t.set_rel_pathlist_dml = NULL,\n\t.set_rel_pathlist_query = NULL,\n\t.sort_transform_replace_pathkeys = NULL,\n\t.process_altertable_cmd = NULL,\n\t.process_rename_cmd = NULL,\n\n\t/* gapfill */\n\t.gapfill_marker = error_no_default_fn_pg_community,\n\t.gapfill_int16_time_bucket = error_no_default_fn_pg_community,\n\t.gapfill_int32_time_bucket = error_no_default_fn_pg_community,\n\t.gapfill_int64_time_bucket = error_no_default_fn_pg_community,\n\t.gapfill_date_time_bucket = error_no_default_fn_pg_community,\n\t.gapfill_timestamp_time_bucket = error_no_default_fn_pg_community,\n\t.gapfill_timestamptz_time_bucket = error_no_default_fn_pg_community,\n\t.gapfill_timestamptz_timezone_time_bucket = error_no_default_fn_pg_community,\n\n\t/* bgw policies */\n\t.policy_compression_add = error_no_default_fn_pg_community,\n\t.policy_compression_remove = error_no_default_fn_pg_community,\n\t.policy_recompression_proc = error_no_default_fn_pg_community,\n\t.policy_compression_check = error_no_default_fn_pg_community,\n\t.policy_refresh_cagg_add = error_no_default_fn_pg_community,\n\t.policy_refresh_cagg_proc = error_no_default_fn_pg_community,\n\t.policy_refresh_cagg_check = error_no_default_fn_pg_community,\n\t.policy_refresh_cagg_remove = error_no_default_fn_pg_community,\n\t.policy_process_hyper_inval_add = error_no_default_fn_pg_community,\n\t.policy_process_hyper_inval_proc = error_no_default_fn_pg_community,\n\t.policy_process_hyper_inval_check = error_no_default_fn_pg_community,\n\t.policy_process_hyper_inval_remove = error_no_default_fn_pg_community,\n\t.policy_reorder_add = error_no_default_fn_pg_community,\n\t.policy_reorder_proc = error_no_default_fn_pg_community,\n\t.policy_reorder_check = error_no_default_fn_pg_community,\n\t.policy_reorder_remove = error_no_default_fn_pg_community,\n\t.policy_retention_add = error_no_default_fn_pg_community,\n\t.policy_retention_proc = error_no_default_fn_pg_community,\n\t.policy_retention_check = error_no_default_fn_pg_community,\n\t.policy_retention_remove = error_no_default_fn_pg_community,\n\n\t.job_add = error_no_default_fn_pg_community,\n\t.job_alter = error_no_default_fn_pg_community,\n\t.job_alter_set_hypertable_id = error_no_default_fn_pg_community,\n\t.job_delete = error_no_default_fn_pg_community,\n\t.job_run = error_no_default_fn_pg_community,\n\t.job_execute = job_execute_default_fn,\n\n\t.reorder_chunk = error_no_default_fn_pg_community,\n\t.move_chunk = error_no_default_fn_pg_community,\n\n\t.policies_add = error_no_default_fn_pg_community,\n\t.policies_remove = error_no_default_fn_pg_community,\n\t.policies_remove_all = error_no_default_fn_pg_community,\n\t.policies_alter = error_no_default_fn_pg_community,\n\t.policies_show = error_no_default_fn_pg_community,\n\n\t.tsl_postprocess_plan = tsl_postprocess_plan_stub,\n\n\t.process_cagg_viewstmt = process_cagg_viewstmt_default,\n\t.continuous_agg_refresh = error_no_default_fn_pg_community,\n\t.continuous_agg_invalidate_raw_ht = continuous_agg_invalidate_raw_ht_all_default,\n\t.continuous_agg_invalidate_mat_ht = continuous_agg_invalidate_mat_ht_all_default,\n\t.continuous_agg_dml_invalidate = continuous_agg_dml_invalidate_default,\n\t.continuous_agg_update_options = continuous_agg_update_options_default,\n\t.continuous_agg_validate_query = error_no_default_fn_pg_community,\n\t.continuous_agg_get_bucket_function = error_no_default_fn_pg_community,\n\t.continuous_agg_get_bucket_function_info = error_no_default_fn_pg_community,\n\t.continuous_agg_get_grouping_columns = error_no_default_fn_pg_community,\n\n\t/* compression */\n\t.compressed_data_send = error_no_default_fn_pg_community,\n\t.compressed_data_recv = error_no_default_fn_pg_community,\n\t.compressed_data_in = process_compressed_data_in,\n\t.compressed_data_out = process_compressed_data_out,\n\t.process_compress_table = process_compress_table_default,\n\t.create_compressed_chunk = error_no_default_fn_pg_community,\n\t.compress_chunk = error_no_default_fn_pg_community,\n\t.decompress_chunk = error_no_default_fn_pg_community,\n\t.rebuild_columnstore = error_no_default_fn_pg_community,\n\t.compressed_data_decompress_forward = error_no_default_fn_pg_community,\n\t.compressed_data_decompress_reverse = error_no_default_fn_pg_community,\n\t.compressed_data_column_size = error_no_default_fn_pg_community,\n\t.compressed_data_to_array = error_no_default_fn_pg_community,\n\t.deltadelta_compressor_append = error_no_default_fn_pg_community,\n\t.deltadelta_compressor_finish = error_no_default_fn_pg_community,\n\t.gorilla_compressor_append = error_no_default_fn_pg_community,\n\t.gorilla_compressor_finish = error_no_default_fn_pg_community,\n\t.dictionary_compressor_append = error_no_default_fn_pg_community,\n\t.dictionary_compressor_finish = error_no_default_fn_pg_community,\n\t.array_compressor_append = error_no_default_fn_pg_community,\n\t.array_compressor_finish = error_no_default_fn_pg_community,\n\t.bool_compressor_append = error_no_default_fn_pg_community,\n\t.bool_compressor_finish = error_no_default_fn_pg_community,\n\t.uuid_compressor_append = error_no_default_fn_pg_community,\n\t.uuid_compressor_finish = error_no_default_fn_pg_community,\n\t.bloom1_contains = error_no_default_fn_pg_community,\n\t.bloom1_contains_any = error_no_default_fn_pg_community,\n\t.bloom1_get_hash_function = bloom1_get_hash_function_default,\n\n\t.decompress_batches_for_insert = error_no_default_fn_chunk_insert_state_community,\n\t.init_decompress_state_for_insert = error_no_default_fn_chunk_insert_state_community,\n\n\t.columnstore_setup = columnstore_setup_default,\n\n\t.show_chunk = error_no_default_fn_pg_community,\n\t.create_chunk = error_no_default_fn_pg_community,\n\t.chunk_freeze_chunk = error_no_default_fn_pg_community,\n\t.chunk_unfreeze_chunk = error_no_default_fn_pg_community,\n\t.recompress_chunk_segmentwise = error_no_default_fn_pg_community,\n\t.get_compressed_chunk_index_for_recompression = error_no_default_fn_pg_community,\n\n\t.preprocess_query_tsl = preprocess_query_tsl_default_fn_community,\n\t.merge_chunks = error_no_default_fn_pg_community,\n\t.split_chunk = error_no_default_fn_pg_community,\n\n\t.detach_chunk = error_no_default_fn_pg_community,\n\t.attach_chunk = error_no_default_fn_pg_community,\n};\n\nTSDLLEXPORT CrossModuleFunctions *ts_cm_functions = &ts_cm_functions_default;\n"
  },
  {
    "path": "src/cross_module_fn.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <commands/event_trigger.h>\n#include <fmgr.h>\n#include <optimizer/planner.h>\n#include <utils/array.h>\n#include <utils/jsonb.h>\n#include <utils/timestamp.h>\n\n#include \"compat/compat.h\"\n#include \"bgw/job.h\"\n#include \"export.h\"\n#include \"planner/planner.h\"\n#include \"process_utility.h\"\n#include \"ts_catalog/continuous_agg.h\"\n#include \"with_clause/with_clause_parser.h\"\n\n/*\n * To define a cross-module function add it to this struct, add a default\n * version in to ts_cm_functions_default cross_module_fn.c, and the overridden\n * version to tsl_cm_functions tsl/src/init.c.\n * This will allow the function to be called from this codebase as\n *     ts_cm_functions-><function name>\n */\n\ntypedef struct JsonbParseState JsonbParseState;\ntypedef struct Hypertable Hypertable;\ntypedef struct Chunk Chunk;\ntypedef struct ChunkInsertState ChunkInsertState;\ntypedef struct CopyChunkState CopyChunkState;\ntypedef struct ModifyHypertableState ModifyHypertableState;\ntypedef struct RowCompressor RowCompressor;\ntypedef struct BulkWriter BulkWriter;\n\ntypedef struct CrossModuleFunctions\n{\n\tPGFunction policy_compression_add;\n\tPGFunction policy_compression_remove;\n\tPGFunction policy_recompression_proc;\n\tPGFunction policy_compression_check;\n\tPGFunction policy_refresh_cagg_add;\n\tPGFunction policy_refresh_cagg_proc;\n\tPGFunction policy_refresh_cagg_check;\n\tPGFunction policy_refresh_cagg_remove;\n\tPGFunction policy_process_hyper_inval_add;\n\tPGFunction policy_process_hyper_inval_proc;\n\tPGFunction policy_process_hyper_inval_check;\n\tPGFunction policy_process_hyper_inval_remove;\n\tPGFunction policy_reorder_add;\n\tPGFunction policy_reorder_proc;\n\tPGFunction policy_reorder_check;\n\tPGFunction policy_reorder_remove;\n\tPGFunction policy_retention_add;\n\tPGFunction policy_retention_proc;\n\tPGFunction policy_retention_check;\n\tPGFunction policy_retention_remove;\n\n\tPGFunction policies_add;\n\tPGFunction policies_remove;\n\tPGFunction policies_remove_all;\n\tPGFunction policies_alter;\n\tPGFunction policies_show;\n\n\tPGFunction job_add;\n\tPGFunction job_alter;\n\tPGFunction job_alter_set_hypertable_id;\n\tPGFunction job_delete;\n\tPGFunction job_run;\n\n\tbool (*job_execute)(BgwJob *job);\n\n\tvoid (*create_upper_paths_hook)(PlannerInfo *, UpperRelationKind, RelOptInfo *, RelOptInfo *,\n\t\t\t\t\t\t\t\t\tTsRelType input_reltype, Hypertable *ht, void *extra);\n\tvoid (*set_rel_pathlist_dml)(PlannerInfo *, RelOptInfo *, Index, RangeTblEntry *, Hypertable *);\n\tvoid (*set_rel_pathlist_query)(PlannerInfo *, RelOptInfo *, Index, RangeTblEntry *,\n\t\t\t\t\t\t\t\t   Hypertable *);\n\tvoid (*sort_transform_replace_pathkeys)(void *path, List *transformed_pathkeys,\n\t\t\t\t\t\t\t\t\t\t\tList *original_pathkeys);\n\n\t/* gapfill */\n\tPGFunction gapfill_marker;\n\tPGFunction gapfill_int16_time_bucket;\n\tPGFunction gapfill_int32_time_bucket;\n\tPGFunction gapfill_int64_time_bucket;\n\tPGFunction gapfill_date_time_bucket;\n\tPGFunction gapfill_timestamp_time_bucket;\n\tPGFunction gapfill_timestamptz_time_bucket;\n\tPGFunction gapfill_timestamptz_timezone_time_bucket;\n\n\tPGFunction reorder_chunk;\n\tPGFunction move_chunk;\n\n\t/* Vectorized queries */\n\tvoid (*tsl_postprocess_plan)(PlannedStmt *stmt);\n\n\t/* Continuous Aggregates */\n\tDDLResult (*process_cagg_viewstmt)(Node *stmt, const char *query_string, void *pstmt,\n\t\t\t\t\t\t\t\t\t   WithClauseResult *with_clause_options);\n\tPGFunction continuous_agg_refresh;\n\tvoid (*continuous_agg_invalidate_raw_ht)(const Hypertable *raw_ht, int64 start, int64 end);\n\tvoid (*continuous_agg_invalidate_mat_ht)(const Hypertable *raw_ht, const Hypertable *mat_ht,\n\t\t\t\t\t\t\t\t\t\t\t int64 start, int64 end);\n\tvoid (*continuous_agg_dml_invalidate)(int32 hypertable_id, Relation chunk_rel,\n\t\t\t\t\t\t\t\t\t\t  HeapTuple chunk_tuple, HeapTuple chunk_newtuple,\n\t\t\t\t\t\t\t\t\t\t  bool update);\n\tvoid (*continuous_agg_update_options)(ContinuousAgg *cagg,\n\t\t\t\t\t\t\t\t\t\t  WithClauseResult *with_clause_options);\n\tPGFunction continuous_agg_validate_query;\n\tPGFunction continuous_agg_get_bucket_function;\n\tPGFunction continuous_agg_get_bucket_function_info;\n\tPGFunction continuous_agg_get_grouping_columns;\n\n\tPGFunction compressed_data_send;\n\tPGFunction compressed_data_recv;\n\tPGFunction compressed_data_in;\n\tPGFunction compressed_data_out;\n\tPGFunction compressed_data_info;\n\tPGFunction compressed_data_has_nulls;\n\tbool (*process_compress_table)(Hypertable *ht, WithClauseResult *with_clause_options);\n\tvoid (*process_altertable_cmd)(Hypertable *ht, const AlterTableCmd *cmd);\n\tvoid (*process_rename_cmd)(Oid relid, Cache *hcache, const RenameStmt *stmt);\n\tPGFunction create_compressed_chunk;\n\tPGFunction compress_chunk;\n\tPGFunction decompress_chunk;\n\tPGFunction rebuild_columnstore;\n\tvoid (*decompress_batches_for_insert)(ChunkInsertState *state, TupleTableSlot *slot);\n\tvoid (*init_decompress_state_for_insert)(ChunkInsertState *state, TupleTableSlot *slot);\n\tbool (*decompress_target_segments)(ModifyHypertableState *ht_state);\n\n\tvoid (*columnstore_setup)(Hypertable *ht, WithClauseResult *with_clause_options);\n\tRowCompressor *(*compressor_init)(Relation in_rel, BulkWriter **bulk_writer, bool sort,\n\t\t\t\t\t\t\t\t\t  int tuple_sort_limit);\n\tvoid (*compressor_set_invalidation)(RowCompressor *compressor, Hypertable *ht, Oid chunk_relid);\n\tvoid (*compressor_add_slot)(RowCompressor *compressor, BulkWriter *bulk_writer,\n\t\t\t\t\t\t\t\tTupleTableSlot *slot);\n\tvoid (*compressor_flush)(RowCompressor *compressor, BulkWriter *bulk_writer);\n\tvoid (*compressor_free)(RowCompressor *compressor, BulkWriter *bulk_writer);\n\tChunk *(*compression_chunk_create)(Hypertable *ht, Chunk *src_chunk);\n\n\t/* The compression functions below are not installed in SQL as part of create extension;\n\t *  They are installed and tested during testing scripts. They are exposed in cross-module\n\t *  functions because they may be very useful for debugging customer problems if the sql\n\t *  stub is installed on the customer's machine.\n\t */\n\tPGFunction compressed_data_decompress_forward;\n\tPGFunction compressed_data_decompress_reverse;\n\tPGFunction compressed_data_column_size;\n\tPGFunction compressed_data_to_array;\n\tPGFunction deltadelta_compressor_append;\n\tPGFunction deltadelta_compressor_finish;\n\tPGFunction gorilla_compressor_append;\n\tPGFunction gorilla_compressor_finish;\n\tPGFunction dictionary_compressor_append;\n\tPGFunction dictionary_compressor_finish;\n\tPGFunction array_compressor_append;\n\tPGFunction array_compressor_finish;\n\tPGFunction bool_compressor_append;\n\tPGFunction bool_compressor_finish;\n\tPGFunction uuid_compressor_append;\n\tPGFunction uuid_compressor_finish;\n\tPGFunction bloom1_contains;\n\tPGFunction bloom1_contains_any;\n\tPGFunction (*bloom1_get_hash_function)(Oid type, FmgrInfo **finfo);\n\n\tPGFunction create_chunk;\n\tPGFunction show_chunk;\n\n\tPGFunction chunk_freeze_chunk;\n\tPGFunction chunk_unfreeze_chunk;\n\tPGFunction recompress_chunk_segmentwise;\n\tPGFunction get_compressed_chunk_index_for_recompression;\n\n\tvoid (*preprocess_query_tsl)(Query *parse, int *cursor_opts);\n\tPGFunction merge_chunks;\n\tPGFunction split_chunk;\n\n\tPGFunction detach_chunk;\n\tPGFunction attach_chunk;\n\n\tPGFunction estimate_compressed_batch_size;\n} CrossModuleFunctions;\n\nextern TSDLLEXPORT CrossModuleFunctions *ts_cm_functions;\nextern TSDLLEXPORT CrossModuleFunctions ts_cm_functions_default;\n"
  },
  {
    "path": "src/custom_type_cache.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <catalog/namespace.h>\n#include <catalog/pg_type.h>\n#include <utils/syscache.h>\n\n#include \"custom_type_cache.h\"\n#include \"extension_constants.h\"\n#include \"ts_catalog/catalog.h\"\n\n/* Information about functions that we put in the cache */\nstatic CustomTypeInfo typeinfo[_CUSTOM_TYPE_MAX_INDEX] = {\n\t[CUSTOM_TYPE_COMPRESSED_DATA] = {\n\t\t.schema_name = INTERNAL_SCHEMA_NAME,\n\t\t.type_name = \"compressed_data\",\n\t\t.type_oid = InvalidOid,\n\t},\n\t[CUSTOM_TYPE_BLOOM1] = {\n\t\t.schema_name = INTERNAL_SCHEMA_NAME,\n\t\t.type_name = \"bloom1\",\n\t\t.type_oid = InvalidOid,\n\t}\n};\n\nextern CustomTypeInfo *\nts_custom_type_cache_get(CustomType type)\n{\n\tCustomTypeInfo *tinfo;\n\n\tif (type >= _CUSTOM_TYPE_MAX_INDEX)\n\t\telog(ERROR, \"invalid timescaledb type %d\", type);\n\n\ttinfo = &typeinfo[type];\n\n\tif (!OidIsValid(tinfo->type_oid))\n\t{\n\t\tOid schema_oid = LookupExplicitNamespace(tinfo->schema_name, false);\n\t\tOid type_oid = GetSysCacheOid2(TYPENAMENSP,\n\t\t\t\t\t\t\t\t\t   Anum_pg_type_oid,\n\t\t\t\t\t\t\t\t\t   CStringGetDatum(tinfo->type_name),\n\t\t\t\t\t\t\t\t\t   ObjectIdGetDatum(schema_oid));\n\t\tif (!OidIsValid(type_oid))\n\t\t\telog(ERROR, \"unknown timescaledb type %s\", tinfo->type_name);\n\n\t\ttinfo->type_oid = type_oid;\n\t}\n\n\treturn tinfo;\n}\n"
  },
  {
    "path": "src/custom_type_cache.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include \"compat/compat.h\"\n#include <postgres.h>\n\ntypedef enum CustomType\n{\n\tCUSTOM_TYPE_COMPRESSED_DATA = 0,\n\tCUSTOM_TYPE_BLOOM1,\n\n\t_CUSTOM_TYPE_MAX_INDEX\n} CustomType;\n\ntypedef struct CustomTypeInfo\n{\n\tconst char *schema_name;\n\tconst char *type_name;\n\tOid type_oid;\n} CustomTypeInfo;\n\nextern TSDLLEXPORT CustomTypeInfo *ts_custom_type_cache_get(CustomType type);\n"
  },
  {
    "path": "src/debug_assert.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n/*\n * Macro that expands to an assert in debug builds and to an ereport in\n * release builds.\n *\n * This allow you to start the debugger if internal assumptions are violated\n * in debug builds, but a release build will just print an error and abort the\n * transaction but and not crash the server.\n *\n * The error code is automatically set to ERRCODE_INTERNAL_ERROR and the error\n * details contains the assertion that failed in text format.\n *\n * The macro should be used for checks that are not expected to occur in\n * normal execution, or which can occur in odd corner-cases for conditions out\n * of our control (e.g., unexpected changes to the metadata) so if you have a\n * test that trigger the error, this macro should not be used.\n */\n#define Ensure(COND, FMT, ...)                                                                     \\\n\tdo                                                                                             \\\n\t{                                                                                              \\\n\t\tif (unlikely(!(COND)))                                                                     \\\n\t\t{                                                                                          \\\n\t\t\tAssert(false);                                                                         \\\n\t\t\tereport(ERROR,                                                                         \\\n\t\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),                                              \\\n\t\t\t\t\t errdetail(\"Assertion '\" #COND \"' failed.\"),                                   \\\n\t\t\t\t\t errmsg(FMT, ##__VA_ARGS__)));                                                 \\\n\t\t}                                                                                          \\\n\t} while (0)\n"
  },
  {
    "path": "src/debug_point.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include \"debug_point.h\"\n\n#include <postgres.h>\n\n#include <fmgr.h>\n\n#include <access/hash.h>\n#include <access/xact.h>\n#include <miscadmin.h>\n#include <storage/ipc.h>\n#include <storage/lock.h>\n#include <utils/builtins.h>\n\n#include \"annotations.h\"\n#include \"export.h\"\n\nTS_FUNCTION_INFO_V1(ts_debug_point_enable);\nTS_FUNCTION_INFO_V1(ts_debug_point_release);\nTS_FUNCTION_INFO_V1(ts_debug_point_id);\n\n/*\n * Debug points only exist in debug code and are intended to allow\n * more controlled testing of the code.\n *\n * Debug points can be used as a wait point (1) or as a way to\n * introduce error injections (2).\n *\n * (1) When used as wait points, execution will halt until the debug points are\n * explicitly released.\n *\n * When waiting on a debug point, there is an attempt to take a shared lock on it.\n * If the debug point is enabled by locking using an exclusive\n * lock, this will block all waiters. Once the exclusive lock is released, all\n * waiters will be able to proceed.\n *\n * (2) is similar to (1), but, instead of waiting for the debug point to be\n * released, it will generate an error immediately.\n *\n */\n\n/* Tag for debug points.\n *\n * Each debug point is identified by a string that is hashed to a 8-byte\n * number and used with the normal advisory locks available in PostgreSQL.\n */\ntypedef struct DebugPoint\n{\n\tconst char *name;\n\tLOCKTAG tag;\n} DebugPoint;\n\nstatic uint64\ndebug_point_name_to_id(const char *name)\n{\n\treturn DatumGetUInt32(hash_any((const unsigned char *) name, strlen(name)));\n}\n\nstatic void\ndebug_point_init(DebugPoint *point, const char *name)\n{\n\t/* Use 64-bit hashing to get two independent 32-bit hashes */\n\tuint64 hash = debug_point_name_to_id(name);\n\n\tSET_LOCKTAG_ADVISORY(point->tag, MyDatabaseId, (uint32) (hash >> 32), (uint32) hash, 1);\n\tpoint->name = pstrdup(name);\n\tereport(DEBUG3,\n\t\t\t(errmsg(\"initializing debug point '%s' to use \" UINT64_FORMAT, point->name, hash)));\n}\n\nstatic void\ndebug_point_enable(const DebugPoint *point)\n{\n\tLockAcquireResult lock_acquire_result;\n\n\tereport(DEBUG1, (errmsg(\"enabling debug point \\\"%s\\\"\", point->name)));\n\n\tlock_acquire_result = LockAcquire(&point->tag, ExclusiveLock, true, true);\n\tswitch (lock_acquire_result)\n\t{\n\t\tcase LOCKACQUIRE_ALREADY_HELD:\n\t\tcase LOCKACQUIRE_ALREADY_CLEAR:\n\t\t\tLockRelease(&point->tag, ExclusiveLock, true);\n\t\t\tTS_FALLTHROUGH;\n\t\tcase LOCKACQUIRE_NOT_AVAIL:\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t\t errmsg(\"debug point \\\"%s\\\" already enabled\", point->name)));\n\t\t\tbreak;\n\t\tcase LOCKACQUIRE_OK:\n\t\t\tbreak;\n\t}\n}\n\nstatic void\ndebug_point_release(const DebugPoint *point)\n{\n\tereport(DEBUG1, (errmsg(\"releasing debug point \\\"%s\\\"\", point->name)));\n\n\tif (!LockRelease(&point->tag, ExclusiveLock, true))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"cannot release debug point \\\"%s\\\"\", point->name)));\n}\n\n/*\n * Enable a debug point to block when being reached.\n *\n * This function will always succeed since we will not lock the debug point if\n * it is already locked. A notice will be printed if the debug point is already\n * enabled.\n */\nDatum\nts_debug_point_enable(PG_FUNCTION_ARGS)\n{\n\ttext *name = PG_GETARG_TEXT_PP(0);\n\tDebugPoint point;\n\n\tif (PG_ARGISNULL(0))\n\t\tereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"no name provided\")));\n\n\tdebug_point_init(&point, text_to_cstring(name));\n\tdebug_point_enable(&point);\n\n\tPG_RETURN_VOID();\n}\n\n/*\n * Release a debug point allowing execution to proceed.\n */\nDatum\nts_debug_point_release(PG_FUNCTION_ARGS)\n{\n\ttext *name = PG_GETARG_TEXT_PP(0);\n\tDebugPoint point;\n\n\tif (PG_ARGISNULL(0))\n\t\tereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"no name provided\")));\n\n\tdebug_point_init(&point, text_to_cstring(name));\n\tdebug_point_release(&point);\n\n\tPG_RETURN_VOID();\n}\n\n/*\n * Get the debug point identifier from the name.\n */\nDatum\nts_debug_point_id(PG_FUNCTION_ARGS)\n{\n\ttext *name = PG_GETARG_TEXT_PP(0);\n\n\tif (PG_ARGISNULL(0))\n\t\tereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"no name provided\")));\n\n\tPG_RETURN_UINT64(debug_point_name_to_id(text_to_cstring(name)));\n}\n\n/*\n * Wait for the debug point to be released.\n *\n * This is handled by first trying to get a shared lock, which will not block\n * other sessions that try to grab the same lock but will block if an\n * exclusive lock is already taken, and then release the lock immediately\n * after.\n *\n * This function can decide to block while taking the shared lock or it can\n * have a retry loop to take the share lock. This retry loop option is useful\n * in cases where this function gets called from deep down inside a transaction\n * where interrupts are not being served currently.\n */\nvoid\nts_debug_point_wait(const char *name, bool blocking)\n{\n\tDebugPoint point;\n\tLockAcquireResult lock_acquire_result pg_attribute_unused();\n\tbool lock_release_result pg_attribute_unused();\n\n\t/* Ensure that we are in a transaction before trying for locks */\n\tif (!IsTransactionState())\n\t\treturn;\n\n\tdebug_point_init(&point, name);\n\n\tereport(DEBUG3, (errmsg(\"waiting on debug point '%s'\", point.name)));\n\n\tif (blocking)\n\t\tlock_acquire_result = LockAcquire(&point.tag, ShareLock, true, false);\n\telse\n\t{\n\t\t/*\n\t\t * Trying to wait indefinitely here could lead to hangs. The current\n\t\t * behavior is to retry for retry_count and return with a warning\n\t\t * if that's crossed.\n\t\t *\n\t\t * If required, in future, we could take an additional option to decide\n\t\t * if the caller wants to retry indefinitely or return with a warning.\n\t\t * But the current behavior based on the \"blocking\" argument is ok for\n\t\t * now.\n\t\t */\n\t\tunsigned int retry_count = 1000;\n\n\t\t/* try to acquire the lock without waiting. */\n\t\tdo\n\t\t{\n\t\t\t/* try to acquire the lock without waiting. */\n\t\t\tlock_acquire_result = LockAcquire(&point.tag, ShareLock, true, true);\n\n\t\t\tif (lock_acquire_result == LOCKACQUIRE_OK)\n\t\t\t\tbreak;\n\n\t\t\t/* don't dare to take a lock when the proc is exiting! */\n\t\t\tif (proc_exit_inprogress || ProcDiePending)\n\t\t\t\treturn;\n\n\t\t\tif (retry_count == 0)\n\t\t\t{\n\t\t\t\telog(WARNING, \"timeout while acquiring debug point lock\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tretry_count--;\n\n\t\t\t/* retry after some time */\n\t\t\tpg_usleep(100L);\n\n\t\t} while (lock_acquire_result == LOCKACQUIRE_NOT_AVAIL);\n\t}\n\tAssert(lock_acquire_result == LOCKACQUIRE_OK);\n\n\tlock_release_result = LockRelease(&point.tag, ShareLock, true);\n\tAssert(lock_release_result);\n\n\tereport(DEBUG3, (errmsg(\"proceeding after debug point '%s'\", point.name)));\n}\n\n/*\n * Produce an error in case if the debug point is enabled.\n *\n * The idea is to enable the debug point separately first which\n * acquires a ShareLock on this tag. With the debug point enabled, this function\n * when invoked will not get the exclusive lock and will be able to raise\n * the error as desired.\n */\nvoid\nts_debug_point_raise_error_if_enabled(const char *name)\n{\n\tDebugPoint point;\n\tLockAcquireResult lock_acquire_result;\n\n\tdebug_point_init(&point, name);\n\n\tlock_acquire_result = LockAcquire(&point.tag, ExclusiveLock, true, true);\n\tswitch (lock_acquire_result)\n\t{\n\t\tcase LOCKACQUIRE_OK:\n\t\tcase LOCKACQUIRE_ALREADY_HELD:\n\t\tcase LOCKACQUIRE_ALREADY_CLEAR:\n\t\t\t/* Release/decrement lock count */\n\t\t\tLockRelease(&point.tag, ExclusiveLock, true);\n\t\t\tif (lock_acquire_result == LOCKACQUIRE_OK)\n\t\t\t\treturn;\n\t\t\tbreak;\n\t\tcase LOCKACQUIRE_NOT_AVAIL:\n\t\t\tbreak;\n\t}\n\n\tereport(ERROR,\n\t\t\t(errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),\n\t\t\t errmsg(\"error injected at debug point '%s'\", point.name)));\n}\n"
  },
  {
    "path": "src/debug_point.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include \"export.h\"\n\nextern TSDLLEXPORT void ts_debug_point_wait(const char *name, bool blocking);\nextern TSDLLEXPORT void ts_debug_point_raise_error_if_enabled(const char *name);\n\n#ifdef TS_DEBUG\n\n#define DEBUG_WAITPOINT(NAME) ts_debug_point_wait((NAME), true)\n#define DEBUG_RETRY_WAITPOINT(NAME) ts_debug_point_wait((NAME), false)\n#define DEBUG_ERROR_INJECTION(NAME) ts_debug_point_raise_error_if_enabled((NAME))\n\n#else\n\n#define DEBUG_WAITPOINT(NAME)\n#define DEBUG_RETRY_WAITPOINT(NAME)\n#define DEBUG_ERROR_INJECTION(NAME)\n\n#endif\n"
  },
  {
    "path": "src/dimension.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/relscan.h>\n#include <catalog/namespace.h>\n#include <catalog/pg_type.h>\n#include <commands/tablecmds.h>\n#include <funcapi.h>\n#include <miscadmin.h>\n#include <nodes/makefuncs.h>\n#include <storage/lmgr.h>\n#include <utils/builtins.h>\n#include <utils/lsyscache.h>\n#include <utils/syscache.h>\n#include <utils/timestamp.h>\n\n#include \"compat/compat.h\"\n#include \"cross_module_fn.h\"\n#include \"debug_point.h\"\n#include \"dimension.h\"\n#include \"dimension_slice.h\"\n#include \"dimension_vector.h\"\n#include \"error_utils.h\"\n#include \"errors.h\"\n#include \"hypertable.h\"\n#include \"hypertable_cache.h\"\n#include \"indexing.h\"\n#include \"partitioning.h\"\n#include \"scanner.h\"\n#include \"time_utils.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"utils.h\"\n\n/* add_dimension record attribute numbers */\nenum Anum_add_dimension\n{\n\tAnum_add_dimension_id = 1,\n\tAnum_add_dimension_schema_name,\n\tAnum_add_dimension_table_name,\n\tAnum_add_dimension_column_name,\n\tAnum_add_dimension_created,\n\t_Anum_add_dimension_max,\n};\n\n#define Natts_add_dimension (_Anum_add_dimension_max - 1)\n\n/*\n * Generic add dimension attributes\n */\nenum Anum_generic_add_dimension\n{\n\tAnum_generic_add_dimension_id = 1,\n\tAnum_generic_add_dimension_created,\n\t_Anum_generic_add_dimension_max,\n};\n\n#define Natts_generic_add_dimension (_Anum_generic_add_dimension_max - 1)\n\nstatic int\ncmp_dimension_id(const void *left, const void *right)\n{\n\tconst Dimension *diml = (Dimension *) left;\n\tconst Dimension *dimr = (Dimension *) right;\n\n\tif (diml->fd.id < dimr->fd.id)\n\t\treturn -1;\n\n\tif (diml->fd.id > dimr->fd.id)\n\t\treturn 1;\n\n\treturn 0;\n}\n\nTS_FUNCTION_INFO_V1(ts_hash_dimension);\nTS_FUNCTION_INFO_V1(ts_range_dimension);\nPG_FUNCTION_INFO_V1(ts_dimension_info_in);\nPG_FUNCTION_INFO_V1(ts_dimension_info_out);\n\nconst Dimension *\nts_hyperspace_get_dimension_by_id(const Hyperspace *hs, int32 id)\n{\n\tDimension dim = {\n\t\t.fd.id = id,\n\t};\n\n\treturn bsearch(&dim, hs->dimensions, hs->num_dimensions, sizeof(Dimension), cmp_dimension_id);\n}\n\nDimension *\nts_hyperspace_get_mutable_dimension_by_name(Hyperspace *hs, DimensionType type, const char *name)\n{\n\tint i;\n\n\tfor (i = 0; i < hs->num_dimensions; i++)\n\t{\n\t\tDimension *dim = &hs->dimensions[i];\n\n\t\tif ((type == DIMENSION_TYPE_ANY || dim->type == type) &&\n\t\t\tnamestrcmp(&dim->fd.column_name, name) == 0)\n\t\t\treturn dim;\n\t}\n\n\treturn NULL;\n}\n\nconst Dimension *\nts_hyperspace_get_dimension_by_name(const Hyperspace *hs, DimensionType type, const char *name)\n{\n\treturn ts_hyperspace_get_mutable_dimension_by_name((Hyperspace *) hs, type, name);\n}\n\nDimension *\nts_hyperspace_get_mutable_dimension(Hyperspace *hs, DimensionType type, Index n)\n{\n\tint i;\n\n\tfor (i = 0; i < hs->num_dimensions; i++)\n\t{\n\t\tif (type == DIMENSION_TYPE_ANY || hs->dimensions[i].type == type)\n\t\t{\n\t\t\tif (n == 0)\n\t\t\t\treturn &hs->dimensions[i];\n\t\t\tn--;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\nconst Dimension *\nts_hyperspace_get_dimension(const Hyperspace *hs, DimensionType type, Index n)\n{\n\treturn ts_hyperspace_get_mutable_dimension((Hyperspace *) hs, type, n);\n}\n\nstatic int\nhyperspace_get_num_dimensions_by_type(Hyperspace *hs, DimensionType type)\n{\n\tint i;\n\tint n = 0;\n\n\tfor (i = 0; i < hs->num_dimensions; i++)\n\t{\n\t\tif (type == DIMENSION_TYPE_ANY || hs->dimensions[i].type == type)\n\t\t\tn++;\n\t}\n\n\treturn n;\n}\n\nstatic inline DimensionType\ndimension_type(TupleInfo *ti)\n{\n\tif (slot_attisnull(ti->slot, Anum_dimension_interval_length) &&\n\t\t!slot_attisnull(ti->slot, Anum_dimension_num_slices))\n\t\treturn DIMENSION_TYPE_CLOSED;\n\n\tif (!slot_attisnull(ti->slot, Anum_dimension_interval_length) &&\n\t\tslot_attisnull(ti->slot, Anum_dimension_num_slices))\n\t\treturn DIMENSION_TYPE_OPEN;\n\n\telog(ERROR, \"invalid partitioning dimension\");\n\t/* suppress compiler warning on MSVC */\n\treturn DIMENSION_TYPE_ANY;\n}\n\nstatic void\ndimension_fill_in_from_tuple(Dimension *d, TupleInfo *ti, Oid main_table_relid)\n{\n\tDatum values[Natts_dimension];\n\tbool isnull[Natts_dimension];\n\tbool should_free;\n\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\n\t/*\n\t * With need to use heap_deform_tuple() rather than GETSTRUCT(), since\n\t * optional values may be omitted from the tuple.\n\t */\n\theap_deform_tuple(tuple, ts_scanner_get_tupledesc(ti), values, isnull);\n\n\td->type = dimension_type(ti);\n\td->fd.id = DatumGetInt32(values[AttrNumberGetAttrOffset(Anum_dimension_id)]);\n\td->fd.hypertable_id =\n\t\tDatumGetInt32(values[AttrNumberGetAttrOffset(Anum_dimension_hypertable_id)]);\n\td->fd.aligned = DatumGetBool(values[AttrNumberGetAttrOffset(Anum_dimension_aligned)]);\n\td->fd.column_type =\n\t\tDatumGetObjectId(values[AttrNumberGetAttrOffset(Anum_dimension_column_type)]);\n\tnamestrcpy(&d->fd.column_name,\n\t\t\t   DatumGetCString(values[AttrNumberGetAttrOffset(Anum_dimension_column_name)]));\n\n\tif (!isnull[AttrNumberGetAttrOffset(Anum_dimension_partitioning_func_schema)] &&\n\t\t!isnull[AttrNumberGetAttrOffset(Anum_dimension_partitioning_func)])\n\t{\n\t\tMemoryContext old;\n\n\t\td->fd.num_slices =\n\t\t\tDatumGetInt16(values[AttrNumberGetAttrOffset(Anum_dimension_num_slices)]);\n\n\t\tnamestrcpy(&d->fd.partitioning_func_schema,\n\t\t\t\t   DatumGetCString(\n\t\t\t\t\t   values[AttrNumberGetAttrOffset(Anum_dimension_partitioning_func_schema)]));\n\t\tnamestrcpy(&d->fd.partitioning_func,\n\t\t\t\t   DatumGetCString(\n\t\t\t\t\t   values[AttrNumberGetAttrOffset(Anum_dimension_partitioning_func)]));\n\n\t\told = MemoryContextSwitchTo(ti->mctx);\n\t\td->partitioning = ts_partitioning_info_create(NameStr(d->fd.partitioning_func_schema),\n\t\t\t\t\t\t\t\t\t\t\t\t\t  NameStr(d->fd.partitioning_func),\n\t\t\t\t\t\t\t\t\t\t\t\t\t  NameStr(d->fd.column_name),\n\t\t\t\t\t\t\t\t\t\t\t\t\t  d->type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  main_table_relid);\n\n\t\tMemoryContextSwitchTo(old);\n\t}\n\n\tif (!isnull[AttrNumberGetAttrOffset(Anum_dimension_integer_now_func_schema)] &&\n\t\t!isnull[AttrNumberGetAttrOffset(Anum_dimension_integer_now_func)])\n\t{\n\t\tnamestrcpy(&d->fd.integer_now_func_schema,\n\t\t\t\t   DatumGetCString(\n\t\t\t\t\t   values[AttrNumberGetAttrOffset(Anum_dimension_integer_now_func_schema)]));\n\t\tnamestrcpy(&d->fd.integer_now_func,\n\t\t\t\t   DatumGetCString(\n\t\t\t\t\t   values[AttrNumberGetAttrOffset(Anum_dimension_integer_now_func)]));\n\t}\n\n\tif (IS_CLOSED_DIMENSION(d))\n\t\td->fd.num_slices =\n\t\t\tDatumGetInt16(values[AttrNumberGetAttrOffset(Anum_dimension_num_slices)]);\n\telse\n\t{\n\t\td->fd.interval_length =\n\t\t\tDatumGetInt64(values[AttrNumberGetAttrOffset(Anum_dimension_interval_length)]);\n\t\tif (!isnull[AttrNumberGetAttrOffset(Anum_dimension_compress_interval_length)])\n\t\t\td->fd.compress_interval_length = DatumGetInt64(\n\t\t\t\tvalues[AttrNumberGetAttrOffset(Anum_dimension_compress_interval_length)]);\n\t}\n\n\td->column_attno = get_attnum(main_table_relid, NameStr(d->fd.column_name));\n\td->main_table_relid = main_table_relid;\n\n\tif (should_free)\n\t\theap_freetuple(tuple);\n}\n\nstatic Datum\ncreate_range_datum(FunctionCallInfo fcinfo, DimensionSlice *slice)\n{\n\tTupleDesc tupdesc;\n\tDatum values[2];\n\tbool nulls[2] = { false };\n\tHeapTuple tuple;\n\n\tif (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)\n\t\telog(ERROR, \"function returning record called in context that cannot accept type record\");\n\n\ttupdesc = BlessTupleDesc(tupdesc);\n\n\tvalues[0] = Int64GetDatum(slice->fd.range_start);\n\tvalues[1] = Int64GetDatum(slice->fd.range_end);\n\ttuple = heap_form_tuple(tupdesc, values, nulls);\n\n\treturn HeapTupleGetDatum(tuple);\n}\n\nstatic DimensionSlice *\ncalculate_open_range_default(const Dimension *dim, int64 value)\n{\n\tint64 range_start, range_end;\n\tOid dimtype = ts_dimension_get_partition_type(dim);\n\n\tif (value < 0)\n\t{\n\t\tconst int64 dim_min = ts_time_get_min(dimtype);\n\n\t\trange_end = ((value + 1) / dim->fd.interval_length) * dim->fd.interval_length;\n\n\t\t/* prevent integer underflow */\n\t\tif (dim_min - range_end > -dim->fd.interval_length)\n\t\t\trange_start = DIMENSION_SLICE_MINVALUE;\n\t\telse\n\t\t\trange_start = range_end - dim->fd.interval_length;\n\t}\n\telse\n\t{\n\t\tconst int64 dim_end = ts_time_get_max(dimtype);\n\n\t\trange_start = (value / dim->fd.interval_length) * dim->fd.interval_length;\n\n\t\t/* prevent integer overflow */\n\t\tif (dim_end - range_start < dim->fd.interval_length)\n\t\t\trange_end = DIMENSION_SLICE_MAXVALUE;\n\t\telse\n\t\t\trange_end = range_start + dim->fd.interval_length;\n\t}\n\n\treturn ts_dimension_slice_create(dim->fd.id, range_start, range_end);\n}\n\nTS_FUNCTION_INFO_V1(ts_dimension_calculate_open_range_default);\n\n/*\n * Expose open dimension range calculation for testing purposes.\n */\nDatum\nts_dimension_calculate_open_range_default(PG_FUNCTION_ARGS)\n{\n\tint64 value = PG_GETARG_INT64(0);\n\tDimension dim = {\n\t\t.type = DIMENSION_TYPE_OPEN,\n\t\t.fd.id = 0,\n\t\t.fd.interval_length = PG_GETARG_INT64(1),\n\t\t.fd.column_type = TypenameGetTypid(PG_GETARG_CSTRING(2)),\n\t};\n\tDimensionSlice *slice = calculate_open_range_default(&dim, value);\n\n\tPG_RETURN_DATUM(create_range_datum(fcinfo, slice));\n}\n\nstatic int64\ncalculate_closed_range_interval(const Dimension *dim)\n{\n\tAssert(NULL != dim && IS_CLOSED_DIMENSION(dim));\n\n\treturn DIMENSION_SLICE_CLOSED_MAX / ((int64) dim->fd.num_slices);\n}\n\nstatic DimensionSlice *\ncalculate_closed_range_default(const Dimension *dim, int64 value)\n{\n\tint64 range_start, range_end;\n\n\t/* The interval that divides the dimension into N equal sized slices */\n\tint64 interval = calculate_closed_range_interval(dim);\n\tint64 last_start = interval * (dim->fd.num_slices - 1);\n\n\tif (value < 0)\n\t\telog(ERROR, \"invalid value \" INT64_FORMAT \" for closed dimension\", value);\n\n\tif (value >= last_start)\n\t{\n\t\t/* put overflow from integer-division errors in last range */\n\t\trange_start = last_start;\n\t\trange_end = DIMENSION_SLICE_MAXVALUE;\n\t}\n\telse\n\t{\n\t\trange_start = (value / interval) * interval;\n\t\trange_end = range_start + interval;\n\t}\n\n\tif (0 == range_start)\n\t{\n\t\trange_start = DIMENSION_SLICE_MINVALUE;\n\t}\n\n\treturn ts_dimension_slice_create(dim->fd.id, range_start, range_end);\n}\n\nTS_FUNCTION_INFO_V1(ts_dimension_calculate_closed_range_default);\n\n/*\n * Exposed closed dimension range calculation for testing purposes.\n */\nDatum\nts_dimension_calculate_closed_range_default(PG_FUNCTION_ARGS)\n{\n\tint64 value = PG_GETARG_INT64(0);\n\tDimension dim = {\n\t\t.type = DIMENSION_TYPE_CLOSED,\n\t\t.fd.id = 0,\n\t\t.fd.num_slices = PG_GETARG_INT16(1),\n\t};\n\tDimensionSlice *slice = calculate_closed_range_default(&dim, value);\n\n\tPG_RETURN_DATUM(create_range_datum(fcinfo, slice));\n}\n\nDimensionSlice *\nts_dimension_calculate_default_slice(const Dimension *dim, int64 value)\n{\n\tif (IS_OPEN_DIMENSION(dim))\n\t\treturn calculate_open_range_default(dim, value);\n\n\treturn calculate_closed_range_default(dim, value);\n}\n\n/*\n * Get the ordinal value of a slice in an open dimension.\n *\n * Note that, for an open dimension, we can only deal with already created\n * slices and cannot account for, e.g., gaps in the dimension where future\n * slices might be created and thus changing the ordinal value for a slice.\n *\n * For instance, the ordinal value of slice D below is 2 (zero indexed):\n *\n * ' | A | B | <gap> | D | E |\n *\n * but when slice C is later created the ordinal value of D will be 3:\n *\n * ' | A | B | C | D | E |\n */\nstatic int\nts_dimension_get_open_slice_ordinal(const Dimension *dim, const DimensionSlice *slice)\n{\n\tDimensionVec *vec;\n\tint i;\n\n\tAssert(NULL != dim && IS_OPEN_DIMENSION(dim));\n\tAssert(NULL != slice);\n\n\tvec = ts_dimension_get_slices(dim);\n\n\tAssert(NULL != vec);\n\n\t/* Find the index (ordinal) of the chunk's slice in the open dimension */\n\ti = ts_dimension_vec_find_slice_index(vec, slice->fd.id);\n\n\tif (i >= 0)\n\t\treturn i;\n\n\t/*\n\t * Returns the number of slices if the slice not found, i.e., i = -1.\n\t * Dimension slice might not exist if a chunk table is created without\n\t * modifying metadata.\n\t */\n\treturn vec->num_slices;\n}\n\n/*\n * Get the ordinal value of a slice in a closed dimension.\n *\n * For closed dimensions, we calculate the ordinal value of a slice based on\n * the assumption that the dimension is fully partitioned in equal size slices\n * as given by the current partitioning configuration. In reality, though,\n * slices are created lazily so a closed dimension might have less slices in\n * time interval than the configuration suggests. Further, during time\n * intervals where repartitioning happens, there might be an unexpected number\n * of slices due to a mix of slices from both the old and the new partitioning\n * configuration. As a result, the ordinal value of a given slice might not\n * actually match the partitioning settings at a given point in time. In this case, we will return\n * the ordinal of current slice most overlapping the given slice (or first fully overlapped slice).\n */\nstatic int\nts_dimension_get_closed_slice_ordinal(const Dimension *dim, const DimensionSlice *target_slice)\n{\n\tint64 current_slice_size;\n\tint64 target_slice_size;\n\tint candidate_slice_ordinal;\n\tint64 target_overlap_with_candidate_slice;\n\n\tAssert(NULL != dim && IS_CLOSED_DIMENSION(dim));\n\tAssert(NULL != target_slice);\n\tAssert(dim->fd.num_slices > 0);\n\n\t/* Slicing assumes partitioning functions use the range [0, INT32_MAX], though the first slice\n\t * uses INT64_MIN as its lower bound, and the last slice uses INT64_MAX as its upper bound. */\n\tif (target_slice->fd.range_start == DIMENSION_SLICE_MINVALUE)\n\t\treturn 0;\n\n\tif (target_slice->fd.range_end == DIMENSION_SLICE_MAXVALUE)\n\t\treturn dim->fd.num_slices - 1;\n\n\tAssert(target_slice->fd.range_start > 0);\n\tAssert(target_slice->fd.range_end < DIMENSION_SLICE_CLOSED_MAX);\n\n\t/* Given a target slice starting from some point p, determine a candidate slice in the current\n\t * partitioning configuration that contains p. If that slice contains over half of our target\n\t * slice, return it's ordinal. Otherwise return the ordinal for the next slice. */\n\tcurrent_slice_size = calculate_closed_range_interval(dim);\n\ttarget_slice_size = target_slice->fd.range_end - target_slice->fd.range_start;\n\tcandidate_slice_ordinal = target_slice->fd.range_start / current_slice_size;\n\ttarget_overlap_with_candidate_slice =\n\t\tcurrent_slice_size - (target_slice->fd.range_start % current_slice_size);\n\n\t/* Note that if the candidate slice wholly contains the target slice,\n\t * target_overlap_with_candidate_slice will actually be greater than target_slice_size.  This\n\t * doesn't affect the correctness of the following check. */\n\tif (target_overlap_with_candidate_slice >= target_slice_size / 2)\n\t\treturn candidate_slice_ordinal;\n\telse\n\t\treturn candidate_slice_ordinal + 1;\n}\n\n/*\n * Get the ordinal value of a slice in a dimension.\n *\n * This function returns the ordinal value of a slice (starting at 0) in the\n * dimension it belongs to. In other words, the \"earliest\" slice along the\n * dimensional axis gets the lowest ordinal value and the \"latest\" the largest.\n */\nint\nts_dimension_get_slice_ordinal(const Dimension *dim, const DimensionSlice *slice)\n{\n\tAssert(NULL != dim);\n\tAssert(NULL != slice);\n\tAssert(dim->fd.id == slice->fd.dimension_id);\n\n\tswitch (dim->type)\n\t{\n\t\tcase DIMENSION_TYPE_OPEN:\n\t\t\treturn ts_dimension_get_open_slice_ordinal(dim, slice);\n\t\tcase DIMENSION_TYPE_CLOSED:\n\t\t\treturn ts_dimension_get_closed_slice_ordinal(dim, slice);\n\t\tdefault:\n\t\t\tAssert(false);\n\t\t\tbreak;\n\t}\n\n\tpg_unreachable();\n\n\treturn -1;\n}\n\nstatic Hyperspace *\nhyperspace_create(int32 hypertable_id, Oid main_table_relid, uint16 num_dimensions,\n\t\t\t\t  MemoryContext mctx)\n{\n\tHyperspace *hs = MemoryContextAllocZero(mctx, HYPERSPACE_SIZE(num_dimensions));\n\n\ths->hypertable_id = hypertable_id;\n\ths->main_table_relid = main_table_relid;\n\ths->capacity = num_dimensions;\n\ths->num_dimensions = 0;\n\treturn hs;\n}\n\nstatic ScanTupleResult\ndimension_tuple_found(TupleInfo *ti, void *data)\n{\n\tHyperspace *hs = data;\n\tDimension *d = &hs->dimensions[hs->num_dimensions++];\n\n\tdimension_fill_in_from_tuple(d, ti, hs->main_table_relid);\n\n\treturn SCAN_CONTINUE;\n}\n\nstatic int\ndimension_scan_internal(ScanKeyData *scankey, int nkeys, tuple_found_func tuple_found, void *data,\n\t\t\t\t\t\tint limit, int dimension_index, LOCKMODE lockmode, MemoryContext mctx)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, DIMENSION),\n\t\t.index = catalog_get_index(catalog, DIMENSION, dimension_index),\n\t\t.nkeys = nkeys,\n\t\t.limit = limit,\n\t\t.scankey = scankey,\n\t\t.data = data,\n\t\t.tuple_found = tuple_found,\n\t\t.lockmode = lockmode,\n\t\t.scandirection = ForwardScanDirection,\n\t\t.result_mctx = mctx,\n\t};\n\n\treturn ts_scanner_scan(&scanctx);\n}\n\nHyperspace *\nts_dimension_scan(int32 hypertable_id, Oid main_table_relid, int16 num_dimensions,\n\t\t\t\t  MemoryContext mctx)\n{\n\tHyperspace *space = hyperspace_create(hypertable_id, main_table_relid, num_dimensions, mctx);\n\tScanKeyData scankey[1];\n\n\t/* Perform an index scan on hypertable_id. */\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_dimension_hypertable_id_column_name_idx_hypertable_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(hypertable_id));\n\n\tdimension_scan_internal(scankey,\n\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\tdimension_tuple_found,\n\t\t\t\t\t\t\tspace,\n\t\t\t\t\t\t\tnum_dimensions,\n\t\t\t\t\t\t\tDIMENSION_HYPERTABLE_ID_COLUMN_NAME_IDX,\n\t\t\t\t\t\t\tAccessShareLock,\n\t\t\t\t\t\t\tmctx);\n\n\t/* Sort dimensions in ascending order to allow binary search lookups */\n\tqsort(space->dimensions, space->num_dimensions, sizeof(Dimension), cmp_dimension_id);\n\n\treturn space;\n}\n\nstatic ScanTupleResult\ndimension_find_hypertable_id_tuple_found(TupleInfo *ti, void *data)\n{\n\tint32 *hypertable_id = data;\n\tbool isnull = false;\n\tDatum datum = slot_getattr(ti->slot, Anum_dimension_hypertable_id, &isnull);\n\n\tAssert(!isnull);\n\t*hypertable_id = DatumGetInt32(datum);\n\n\treturn SCAN_DONE;\n}\n\nint32\nts_dimension_get_hypertable_id(int32 dimension_id)\n{\n\tint32 hypertable_id;\n\tScanKeyData scankey[1];\n\tint ret;\n\n\t/* Perform an index scan dimension_id. */\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_dimension_id_idx_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(dimension_id));\n\n\tret = dimension_scan_internal(scankey,\n\t\t\t\t\t\t\t\t  1,\n\t\t\t\t\t\t\t\t  dimension_find_hypertable_id_tuple_found,\n\t\t\t\t\t\t\t\t  &hypertable_id,\n\t\t\t\t\t\t\t\t  1,\n\t\t\t\t\t\t\t\t  DIMENSION_ID_IDX,\n\t\t\t\t\t\t\t\t  AccessShareLock,\n\t\t\t\t\t\t\t\t  CurrentMemoryContext);\n\n\tif (ret == 1)\n\t\treturn hypertable_id;\n\n\treturn -1;\n}\n\nDimensionVec *\nts_dimension_get_slices(const Dimension *dim)\n{\n\treturn ts_dimension_slice_scan_by_dimension(dim->fd.id, 0);\n}\n\nstatic int\ndimension_scan_update(int32 dimension_id, tuple_found_func tuple_found, void *data,\n\t\t\t\t\t  LOCKMODE lockmode)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tScanKeyData scankey[1];\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, DIMENSION),\n\t\t.index = catalog_get_index(catalog, DIMENSION, DIMENSION_ID_IDX),\n\t\t.nkeys = 1,\n\t\t.limit = 1,\n\t\t.scankey = scankey,\n\t\t.data = data,\n\t\t.tuple_found = tuple_found,\n\t\t.lockmode = lockmode,\n\t\t.scandirection = ForwardScanDirection,\n\t};\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_dimension_id_idx_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(dimension_id));\n\n\treturn ts_scanner_scan(&scanctx);\n}\n\nstatic ScanTupleResult\ndimension_tuple_delete(TupleInfo *ti, void *data)\n{\n\tCatalogSecurityContext sec_ctx;\n\tbool isnull;\n\tDatum dimension_id = slot_getattr(ti->slot, Anum_dimension_id, &isnull);\n\tbool *delete_slices = data;\n\n\tAssert(!isnull);\n\n\t/* delete dimension slices */\n\tif (NULL != delete_slices && *delete_slices)\n\t\tts_dimension_slice_delete_by_dimension_id(DatumGetInt32(dimension_id), false);\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_delete_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\tts_catalog_restore_user(&sec_ctx);\n\n\treturn SCAN_CONTINUE;\n}\n\nint\nts_dimension_delete_by_hypertable_id(int32 hypertable_id, bool delete_slices)\n{\n\tScanKeyData scankey[1];\n\n\t/* Perform an index scan to delete based on hypertable_id */\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_dimension_hypertable_id_column_name_idx_hypertable_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(hypertable_id));\n\n\treturn dimension_scan_internal(scankey,\n\t\t\t\t\t\t\t\t   1,\n\t\t\t\t\t\t\t\t   dimension_tuple_delete,\n\t\t\t\t\t\t\t\t   &delete_slices,\n\t\t\t\t\t\t\t\t   0,\n\t\t\t\t\t\t\t\t   DIMENSION_HYPERTABLE_ID_COLUMN_NAME_IDX,\n\t\t\t\t\t\t\t\t   RowExclusiveLock,\n\t\t\t\t\t\t\t\t   CurrentMemoryContext);\n}\n\nstatic ScanTupleResult\ndimension_tuple_update(TupleInfo *ti, void *data)\n{\n\tDimension *dim = data;\n\tDatum values[Natts_dimension];\n\tbool nulls[Natts_dimension];\n\tCatalogSecurityContext sec_ctx;\n\tbool should_free;\n\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\tHeapTuple new_tuple;\n\n\theap_deform_tuple(tuple, ts_scanner_get_tupledesc(ti), values, nulls);\n\n\tAssert((dim->fd.num_slices <= 0 && dim->fd.interval_length > 0) ||\n\t\t   (dim->fd.num_slices > 0 && dim->fd.interval_length <= 0));\n\n\tvalues[AttrNumberGetAttrOffset(Anum_dimension_column_name)] =\n\t\tNameGetDatum(&dim->fd.column_name);\n\tvalues[AttrNumberGetAttrOffset(Anum_dimension_column_type)] =\n\t\tObjectIdGetDatum(dim->fd.column_type);\n\tvalues[AttrNumberGetAttrOffset(Anum_dimension_num_slices)] = Int16GetDatum(dim->fd.num_slices);\n\n\tif (!nulls[AttrNumberGetAttrOffset(Anum_dimension_partitioning_func)] &&\n\t\t!nulls[AttrNumberGetAttrOffset(Anum_dimension_partitioning_func_schema)])\n\t{\n\t\tvalues[AttrNumberGetAttrOffset(Anum_dimension_partitioning_func)] =\n\t\t\tNameGetDatum(&dim->fd.partitioning_func);\n\t\tvalues[AttrNumberGetAttrOffset(Anum_dimension_partitioning_func_schema)] =\n\t\t\tNameGetDatum(&dim->fd.partitioning_func_schema);\n\t}\n\n\tif (*NameStr(dim->fd.integer_now_func) != '\\0' &&\n\t\t*NameStr(dim->fd.integer_now_func_schema) != '\\0')\n\t{\n\t\tvalues[AttrNumberGetAttrOffset(Anum_dimension_integer_now_func)] =\n\t\t\tNameGetDatum(&dim->fd.integer_now_func);\n\t\tvalues[AttrNumberGetAttrOffset(Anum_dimension_integer_now_func_schema)] =\n\t\t\tNameGetDatum(&dim->fd.integer_now_func_schema);\n\t\tnulls[AttrNumberGetAttrOffset(Anum_dimension_integer_now_func)] = false;\n\t\tnulls[AttrNumberGetAttrOffset(Anum_dimension_integer_now_func_schema)] = false;\n\t}\n\n\tif (!nulls[AttrNumberGetAttrOffset(Anum_dimension_interval_length)])\n\t\tvalues[AttrNumberGetAttrOffset(Anum_dimension_interval_length)] =\n\t\t\tInt64GetDatum(dim->fd.interval_length);\n\n\tif (dim->fd.compress_interval_length > 0)\n\t{\n\t\tvalues[AttrNumberGetAttrOffset(Anum_dimension_compress_interval_length)] =\n\t\t\tInt64GetDatum(dim->fd.compress_interval_length);\n\t\tnulls[AttrNumberGetAttrOffset(Anum_dimension_compress_interval_length)] = false;\n\t}\n\telse\n\t{\n\t\tnulls[AttrNumberGetAttrOffset(Anum_dimension_compress_interval_length)] = true;\n\t}\n\n\tnew_tuple = heap_form_tuple(ts_scanner_get_tupledesc(ti), values, nulls);\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_update_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti), new_tuple);\n\tts_catalog_restore_user(&sec_ctx);\n\theap_freetuple(new_tuple);\n\n\tif (should_free)\n\t\theap_freetuple(tuple);\n\n\treturn SCAN_DONE;\n}\n\nstatic int32\ndimension_insert_relation(Relation rel, int32 hypertable_id, Name colname, Oid coltype,\n\t\t\t\t\t\t  int16 num_slices, regproc partitioning_func, int64 interval_length)\n{\n\tTupleDesc desc = RelationGetDescr(rel);\n\tDatum values[Natts_dimension];\n\tbool nulls[Natts_dimension] = { false };\n\tCatalogSecurityContext sec_ctx;\n\tint32 dimension_id;\n\n\tvalues[AttrNumberGetAttrOffset(Anum_dimension_hypertable_id)] = Int32GetDatum(hypertable_id);\n\tvalues[AttrNumberGetAttrOffset(Anum_dimension_column_name)] = NameGetDatum(colname);\n\tvalues[AttrNumberGetAttrOffset(Anum_dimension_column_type)] = ObjectIdGetDatum(coltype);\n\n\tif (OidIsValid(partitioning_func))\n\t{\n\t\tOid pronamespace = get_func_namespace(partitioning_func);\n\n\t\tvalues[AttrNumberGetAttrOffset(Anum_dimension_partitioning_func)] =\n\t\t\tDirectFunctionCall1(namein, CStringGetDatum(get_func_name(partitioning_func)));\n\t\tvalues[AttrNumberGetAttrOffset(Anum_dimension_partitioning_func_schema)] =\n\t\t\tDirectFunctionCall1(namein, CStringGetDatum(get_namespace_name(pronamespace)));\n\t}\n\telse\n\t{\n\t\tnulls[AttrNumberGetAttrOffset(Anum_dimension_partitioning_func)] = true;\n\t\tnulls[AttrNumberGetAttrOffset(Anum_dimension_partitioning_func_schema)] = true;\n\t}\n\n\tif (num_slices > 0)\n\t{\n\t\t/* Closed (hash) dimension */\n\t\tAssert(num_slices > 0 && interval_length <= 0);\n\t\tvalues[AttrNumberGetAttrOffset(Anum_dimension_num_slices)] = Int16GetDatum(num_slices);\n\t\tvalues[AttrNumberGetAttrOffset(Anum_dimension_aligned)] = BoolGetDatum(false);\n\t\tnulls[AttrNumberGetAttrOffset(Anum_dimension_interval_length)] = true;\n\t}\n\telse\n\t{\n\t\t/* Open (time) dimension */\n\t\tAssert(num_slices <= 0 && interval_length > 0);\n\t\tvalues[AttrNumberGetAttrOffset(Anum_dimension_interval_length)] =\n\t\t\tInt64GetDatum(interval_length);\n\t\tvalues[AttrNumberGetAttrOffset(Anum_dimension_aligned)] = BoolGetDatum(true);\n\t\tnulls[AttrNumberGetAttrOffset(Anum_dimension_num_slices)] = true;\n\t}\n\n\t/* no integer_now function by default */\n\tnulls[AttrNumberGetAttrOffset(Anum_dimension_integer_now_func_schema)] = true;\n\tnulls[AttrNumberGetAttrOffset(Anum_dimension_integer_now_func)] = true;\n\n\t/* no compress interval length by default */\n\tnulls[AttrNumberGetAttrOffset(Anum_dimension_compress_interval_length)] = true;\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tdimension_id = Int32GetDatum(ts_catalog_table_next_seq_id(ts_catalog_get(), DIMENSION));\n\tvalues[AttrNumberGetAttrOffset(Anum_dimension_id)] = dimension_id;\n\tts_catalog_insert_values(rel, desc, values, nulls);\n\tts_catalog_restore_user(&sec_ctx);\n\n\treturn dimension_id;\n}\n\nstatic int32\ndimension_insert(int32 hypertable_id, Name colname, Oid coltype, int16 num_slices,\n\t\t\t\t regproc partitioning_func, int64 interval_length)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tRelation rel;\n\tint32 dimension_id;\n\n\trel = table_open(catalog_get_table_id(catalog, DIMENSION), RowExclusiveLock);\n\tdimension_id = dimension_insert_relation(rel,\n\t\t\t\t\t\t\t\t\t\t\t hypertable_id,\n\t\t\t\t\t\t\t\t\t\t\t colname,\n\t\t\t\t\t\t\t\t\t\t\t coltype,\n\t\t\t\t\t\t\t\t\t\t\t num_slices,\n\t\t\t\t\t\t\t\t\t\t\t partitioning_func,\n\t\t\t\t\t\t\t\t\t\t\t interval_length);\n\ttable_close(rel, RowExclusiveLock);\n\treturn dimension_id;\n}\n\nint\nts_dimension_set_type(Dimension *dim, Oid newtype)\n{\n\tif (!IS_VALID_OPEN_DIM_TYPE(newtype))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_TABLE_DEFINITION),\n\t\t\t\t errmsg(\"cannot change data type of hypertable column \\\"%s\\\" from %s to %s\",\n\t\t\t\t\t\tNameStr(dim->fd.column_name),\n\t\t\t\t\t\tformat_type_be(dim->fd.column_type),\n\t\t\t\t\t\tformat_type_be(newtype)),\n\t\t\t\t errhint(\"Use an integer, timestamp, or date type.\")));\n\n\tdim->fd.column_type = newtype;\n\n\treturn dimension_scan_update(dim->fd.id, dimension_tuple_update, dim, RowExclusiveLock);\n}\n\nTSDLLEXPORT Oid\nts_dimension_get_partition_type(const Dimension *dim)\n{\n\tAssert(dim != NULL);\n\treturn dim->partitioning != NULL ? dim->partitioning->partfunc.rettype : dim->fd.column_type;\n}\n\nint\nts_dimension_set_name(Dimension *dim, const char *newname)\n{\n\tnamestrcpy(&dim->fd.column_name, newname);\n\n\treturn dimension_scan_update(dim->fd.id, dimension_tuple_update, dim, RowExclusiveLock);\n}\n\nint\nts_dimension_set_chunk_interval(Dimension *dim, int64 chunk_interval)\n{\n\tAssert(IS_OPEN_DIMENSION(dim));\n\n\tdim->fd.interval_length = chunk_interval;\n\n\treturn dimension_scan_update(dim->fd.id, dimension_tuple_update, dim, RowExclusiveLock);\n}\n\nint\nts_dimension_set_compress_interval(Dimension *dim, int64 compress_interval)\n{\n\tif (!IS_OPEN_DIMENSION(dim))\n\t\tereport(ERROR,\n\t\t\t\t(errmsg(\"trying to set compress interval on closed dimension\"),\n\t\t\t\t errhint(\"dimension ID %d\", dim->fd.id)));\n\n\tdim->fd.compress_interval_length = compress_interval;\n\n\treturn dimension_scan_update(dim->fd.id, dimension_tuple_update, dim, RowExclusiveLock);\n}\n\n/*\n * Apply any dimension-specific transformations on a value, i.e., apply\n * partitioning function. Optionally get the type of the resulting value via\n * the restype parameter.\n */\nDatum\nts_dimension_transform_value(const Dimension *dim, Oid collation, Datum value, Oid const_datum_type,\n\t\t\t\t\t\t\t Oid *restype)\n{\n\tif (NULL != dim->partitioning)\n\t\tvalue = ts_partitioning_func_apply(dim->partitioning, collation, value);\n\n\tif (NULL != restype)\n\t{\n\t\tif (NULL != dim->partitioning)\n\t\t\t*restype = dim->partitioning->partfunc.rettype;\n\t\telse if (OidIsValid(const_datum_type))\n\t\t\t*restype = const_datum_type;\n\t\telse\n\t\t\t*restype = dim->fd.column_type;\n\t}\n\n\treturn value;\n}\n\nPoint *\nts_point_create(int16 num_dimensions)\n{\n\tPoint *p = palloc0(POINT_SIZE(num_dimensions));\n\n\tp->cardinality = num_dimensions;\n\tp->num_coords = 0;\n\n\treturn p;\n}\n\nTSDLLEXPORT Point *\nts_hyperspace_calculate_point(const Hyperspace *hs, TupleTableSlot *slot)\n{\n\tPoint *p = ts_point_create(hs->num_dimensions);\n\tint i;\n\n\tfor (i = 0; i < hs->num_dimensions; i++)\n\t{\n\t\tconst Dimension *d = &hs->dimensions[i];\n\t\tDatum datum;\n\t\tbool isnull;\n\t\tOid dimtype;\n\n\t\tif (NULL != d->partitioning)\n\t\t\tdatum = ts_partitioning_func_apply_slot(d->partitioning, slot, &isnull);\n\t\telse\n\t\t\tdatum = slot_getattr(slot, d->column_attno, &isnull);\n\n\t\tswitch (d->type)\n\t\t{\n\t\t\tcase DIMENSION_TYPE_OPEN:\n\t\t\t\tdimtype = ts_dimension_get_partition_type(d);\n\n\t\t\t\tif (isnull)\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_NOT_NULL_VIOLATION),\n\t\t\t\t\t\t\t errmsg(\"NULL value in column \\\"%s\\\" violates not-null constraint\",\n\t\t\t\t\t\t\t\t\tNameStr(d->fd.column_name)),\n\t\t\t\t\t\t\t errhint(\"Columns used for time partitioning cannot be NULL.\")));\n\n\t\t\t\tp->coordinates[p->num_coords++] = ts_time_value_to_internal(datum, dimtype);\n\t\t\t\tbreak;\n\t\t\tcase DIMENSION_TYPE_CLOSED:\n\t\t\t\tp->coordinates[p->num_coords++] = (int64) DatumGetInt32(datum);\n\t\t\t\tbreak;\n\t\t\tcase DIMENSION_TYPE_STATS:\n\t\t\tcase DIMENSION_TYPE_ANY:\n\t\t\t\telog(ERROR, \"invalid dimension type when inserting tuple\");\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn p;\n}\n\n#define INT_TYPE_MAX(type)                                                                         \\\n\t(int64)(((type) == INT2OID) ? PG_INT16_MAX :                                                   \\\n\t\t\t\t\t\t\t\t  (((type) == INT4OID) ? PG_INT32_MAX : PG_INT64_MAX))\n\n#define IS_VALID_NUM_SLICES(num_slices) ((num_slices) >= 1 && (num_slices) <= PG_INT16_MAX)\n\nstatic int64\nget_validated_integer_interval(Oid dimtype, int64 value)\n{\n\tif (value < 1 || value > INT_TYPE_MAX(dimtype))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid interval: must be between 1 and \" INT64_FORMAT,\n\t\t\t\t\t\tINT_TYPE_MAX(dimtype))));\n\n\tif (IS_TIMESTAMP_TYPE(dimtype) && value < USECS_PER_SEC)\n\t\tereport(WARNING,\n\t\t\t\t(errcode(ERRCODE_AMBIGUOUS_PARAMETER),\n\t\t\t\t errmsg(\"unexpected interval: smaller than one second\"),\n\t\t\t\t errhint(\"The interval is specified in microseconds.\")));\n\n\treturn value;\n}\n\n/*\n * The default chunk interval to use by hypertables. This value is set via the\n * corresponding GUC in the assign hook, so do not assign a value here.\n */\nextern Interval *default_chunk_time_interval;\n\n/*\n * Get the default chunk interval based on dimension type.\n */\nstatic ChunkInterval\nget_default_interval(Oid dimtype, bool adaptive_chunking)\n{\n\tChunkInterval chunk_interval = {\n\t\t.type = InvalidOid,\n\t};\n\n\tswitch (dimtype)\n\t{\n\t\tcase INT2OID:\n\t\t\tchunk_interval.type = INT2OID;\n\t\t\tchunk_interval.integer_interval = DEFAULT_SMALLINT_INTERVAL;\n\t\t\tbreak;\n\t\tcase INT4OID:\n\t\t\tchunk_interval.type = INT4OID;\n\t\t\tchunk_interval.integer_interval = DEFAULT_INT_INTERVAL;\n\t\t\tbreak;\n\t\tcase INT8OID:\n\t\t\tchunk_interval.type = INT8OID;\n\t\t\tchunk_interval.integer_interval = DEFAULT_BIGINT_INTERVAL;\n\t\t\tbreak;\n\t\tcase TIMESTAMPOID:\n\t\tcase TIMESTAMPTZOID:\n\t\tcase DATEOID:\n\t\tcase UUIDOID:\n\t\t\tif (default_chunk_time_interval != NULL)\n\t\t\t{\n\t\t\t\tchunk_interval.type = INTERVALOID;\n\t\t\t\tchunk_interval.interval = *default_chunk_time_interval;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tchunk_interval.type = INT8OID;\n\n\t\t\t\tif (adaptive_chunking)\n\t\t\t\t\tchunk_interval.integer_interval = DEFAULT_CHUNK_TIME_INTERVAL_ADAPTIVE;\n\t\t\t\telse\n\t\t\t\t\tchunk_interval.integer_interval = DEFAULT_CHUNK_TIME_INTERVAL;\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"cannot get default interval for %s dimension\",\n\t\t\t\t\t\t\tformat_type_be(dimtype)),\n\t\t\t\t\t errhint(\"Use a valid dimension type.\")));\n\t}\n\n\treturn chunk_interval;\n}\n\nstatic int64\ndimension_interval_to_internal(const char *colname, Oid dimtype,\n\t\t\t\t\t\t\t   const ChunkInterval *chunk_interval, bool adaptive_chunking)\n{\n\tint64 interval;\n\n\tAssert(chunk_interval != NULL);\n\n\tif (!IS_VALID_OPEN_DIM_TYPE(dimtype))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_WRONG_OBJECT_TYPE),\n\t\t\t\t errmsg(\"invalid type for dimension \\\"%s\\\"\", colname),\n\t\t\t\t errhint(\"Use an integer, timestamp, or date type.\")));\n\n\tswitch (chunk_interval->type)\n\t{\n\t\tcase INT2OID:\n\t\t\tinterval = get_validated_integer_interval(dimtype, chunk_interval->integer_interval);\n\t\t\tbreak;\n\t\tcase INT4OID:\n\t\t\tinterval = get_validated_integer_interval(dimtype, chunk_interval->integer_interval);\n\t\t\tbreak;\n\t\tcase INT8OID:\n\t\t\tinterval = get_validated_integer_interval(dimtype, chunk_interval->integer_interval);\n\t\t\tbreak;\n\t\tcase INTERVALOID:\n\t\t\tif (!IS_TIMESTAMP_TYPE(dimtype) && !IS_UUID_TYPE(dimtype))\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t\t errmsg(\"invalid interval type for %s dimension\", format_type_be(dimtype)),\n\t\t\t\t\t\t errhint(\"Use an interval of type integer.\")));\n\n\t\t\tinterval = interval_to_usec(&chunk_interval->interval);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"invalid interval type for %s dimension\", format_type_be(dimtype)),\n\t\t\t\t\t IS_TIMESTAMP_TYPE(dimtype) ?\n\t\t\t\t\t\t errhint(\"Use an interval of type integer or interval.\") :\n\t\t\t\t\t\t errhint(\"Use an interval of type integer.\")));\n\t}\n\n\tif (dimtype == DATEOID && (interval <= 0 || interval % USECS_PER_DAY != 0))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid interval for %s dimension\", format_type_be(dimtype)),\n\t\t\t\t errhint(\"Use an interval that is a multiple of one day.\")));\n\n\treturn interval;\n}\n\nTS_FUNCTION_INFO_V1(ts_dimension_interval_to_internal_test);\n\n/*\n * Exposed for testing purposes.\n */\nDatum\nts_dimension_interval_to_internal_test(PG_FUNCTION_ARGS)\n{\n\tOid dimtype = PG_GETARG_OID(0);\n\tOid argtype = PG_ARGISNULL(1) ? InvalidOid : get_fn_expr_argtype(fcinfo->flinfo, 1);\n\tChunkInterval chunk_interval;\n\n\tif (!OidIsValid(argtype))\n\t\tchunk_interval = get_default_interval(dimtype, false);\n\telse\n\t\tchunk_interval_set(&chunk_interval, PG_GETARG_DATUM(1), argtype);\n\n\tPG_RETURN_INT64(dimension_interval_to_internal(\"testcol\", dimtype, &chunk_interval, false));\n}\n\nstatic void\ndimension_add_not_null_on_column(Oid table_relid, char *colname)\n{\n\tAlterTableCmd cmd = {\n\t\t.type = T_AlterTableCmd,\n\t\t.subtype = AT_SetNotNull,\n\t\t.name = colname,\n\t\t.missing_ok = false,\n\t};\n\n\tereport(DEBUG1,\n\t\t\t(errmsg(\"adding not-null constraint to column \\\"%s\\\"\", colname),\n\t\t\t errdetail(\"Dimensions cannot have NULL values.\")));\n\n\tts_alter_table_with_event_trigger(table_relid, (Node *) &cmd, list_make1(&cmd), false);\n}\n\nvoid\nts_dimension_update(const Hypertable *ht, const NameData *dimname, DimensionType dimtype,\n\t\t\t\t\tDatum *interval, Oid *intervaltype, int16 *num_slices, Oid *integer_now_func)\n{\n\tDimension *dim;\n\n\tif (NULL == ht)\n\t\tereport(ERROR, (errcode(ERRCODE_TS_HYPERTABLE_NOT_EXIST), errmsg(\"invalid hypertable\")));\n\n\tif (dimtype == DIMENSION_TYPE_ANY)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"invalid dimension type\")));\n\n\tif (NULL == dimname)\n\t{\n\t\tif (hyperspace_get_num_dimensions_by_type(ht->space, dimtype) > 1)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"hypertable \\\"%s\\\" has multiple %s dimensions\",\n\t\t\t\t\t\t\tget_rel_name(ht->main_table_relid),\n\t\t\t\t\t\t\tdimtype == DIMENSION_TYPE_OPEN ? \"time\" : \"space\"),\n\t\t\t\t\t errhint(\"An explicit dimension name must be specified.\")));\n\n\t\tdim = ts_hyperspace_get_mutable_dimension(ht->space, dimtype, 0);\n\t}\n\telse\n\t\tdim = ts_hyperspace_get_mutable_dimension_by_name(ht->space, dimtype, NameStr(*dimname));\n\n\tif (NULL == dim)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_TS_DIMENSION_NOT_EXIST),\n\t\t\t\t errmsg(\"hypertable \\\"%s\\\" does not have a matching dimension\",\n\t\t\t\t\t\tget_rel_name(ht->main_table_relid))));\n\n\tAssert(dim->type == dimtype);\n\n\tif (interval)\n\t{\n\t\tOid dimtype = ts_dimension_get_partition_type(dim);\n\t\tChunkInterval chunk_interval;\n\n\t\tchunk_interval_set(&chunk_interval, *interval, *intervaltype);\n\t\tdim->fd.interval_length =\n\t\t\tdimension_interval_to_internal(NameStr(dim->fd.column_name),\n\t\t\t\t\t\t\t\t\t\t   dimtype,\n\t\t\t\t\t\t\t\t\t\t   &chunk_interval,\n\t\t\t\t\t\t\t\t\t\t   hypertable_adaptive_chunking_enabled(ht));\n\t}\n\n\tif (num_slices)\n\t{\n\t\tAssert(IS_CLOSED_DIMENSION(dim));\n\t\tdim->fd.num_slices = *num_slices;\n\t}\n\n\tif (integer_now_func)\n\t{\n\t\tOid pronamespace = get_func_namespace(*integer_now_func);\n\t\tnamestrcpy(&dim->fd.integer_now_func_schema, get_namespace_name(pronamespace));\n\t\tnamestrcpy(&dim->fd.integer_now_func, get_func_name(*integer_now_func));\n\t}\n\n\tdimension_scan_update(dim->fd.id, dimension_tuple_update, dim, RowExclusiveLock);\n}\n\nTS_FUNCTION_INFO_V1(ts_dimension_set_num_slices);\n\nDatum\nts_dimension_set_num_slices(PG_FUNCTION_ARGS)\n{\n\tOid table_relid = PG_GETARG_OID(0);\n\tint32 num_slices_arg = PG_ARGISNULL(1) ? -1 : PG_GETARG_INT32(1);\n\tName colname = PG_ARGISNULL(2) ? NULL : PG_GETARG_NAME(2);\n\tCache *hcache = ts_hypertable_cache_pin();\n\tint16 num_slices;\n\tHypertable *ht;\n\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\tif (PG_ARGISNULL(0))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"hypertable cannot be NULL\")));\n\n\t/* Verify that we're dealing with a hypertable or fail */\n\tht = ts_hypertable_cache_get_entry(hcache, table_relid, CACHE_FLAG_NONE);\n\tts_hypertable_permissions_check(table_relid, GetUserId());\n\n\tif (PG_ARGISNULL(1) || !IS_VALID_NUM_SLICES(num_slices_arg))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid number of partitions: must be between 1 and %d\", PG_INT16_MAX)));\n\n\t/*\n\t * Our catalog stores num_slices as a smallint (int16). However, function\n\t * argument is an integer (int32) so that the user need not cast it to a\n\t * smallint. We therefore convert to int16 here after checking that\n\t * num_slices cannot be > INT16_MAX.\n\t */\n\tnum_slices = num_slices_arg & 0xffff;\n\tts_dimension_update(ht, colname, DIMENSION_TYPE_CLOSED, NULL, NULL, &num_slices, NULL);\n\tts_cache_release(&hcache);\n\n\tPG_RETURN_VOID();\n}\n\nTS_FUNCTION_INFO_V1(ts_dimension_set_interval);\n\n/*\n * Update chunk_time_interval for a hypertable.\n *\n * hypertable - The OID of the table corresponding to a hypertable whose time\n *     interval should be updated\n * chunk_time_interval - The new time interval. For hypertables with integral\n *     time columns, this must be an integral type. For hypertables with a\n *     TIMESTAMP/TIMESTAMPTZ/DATE type, it can be integral which is treated as\n *     microseconds, or an INTERVAL type.\n * dimension_name - The name of the dimension\n */\nDatum\nts_dimension_set_interval(PG_FUNCTION_ARGS)\n{\n\tOid table_relid = PG_GETARG_OID(0);\n\tDatum interval = PG_GETARG_DATUM(1);\n\tOid intervaltype = InvalidOid;\n\tName colname = PG_ARGISNULL(2) ? NULL : PG_GETARG_NAME(2);\n\tCache *hcache = ts_hypertable_cache_pin();\n\tHypertable *ht;\n\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\tif (PG_ARGISNULL(0))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"hypertable cannot be NULL\")));\n\n\tht = ts_resolve_hypertable_from_table_or_cagg(hcache, table_relid, true);\n\tts_hypertable_permissions_check(table_relid, GetUserId());\n\n\tif (PG_ARGISNULL(1))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid interval: an explicit interval must be specified\")));\n\n\tintervaltype = get_fn_expr_argtype(fcinfo->flinfo, 1);\n\tts_dimension_update(ht, colname, DIMENSION_TYPE_OPEN, &interval, &intervaltype, NULL, NULL);\n\tts_cache_release(&hcache);\n\n\tPG_RETURN_VOID();\n}\n\nDimensionInfo *\nts_dimension_info_create_open(Oid table_relid, Name column_name, Datum interval, Oid interval_type,\n\t\t\t\t\t\t\t  regproc partitioning_func)\n{\n\tDimensionInfo *info = palloc(sizeof(*info));\n\t*info = (DimensionInfo){\n\t\t.type = DIMENSION_TYPE_OPEN,\n\t\t.table_relid = table_relid,\n\t\t.partitioning_func = partitioning_func,\n\t};\n\n\tchunk_interval_set(&info->chunk_interval, interval, interval_type);\n\n\tnamestrcpy(&info->colname, NameStr(*column_name));\n\treturn info;\n}\n\nDimensionInfo *\nts_dimension_info_create_closed(Oid table_relid, Name column_name, int32 num_slices,\n\t\t\t\t\t\t\t\tregproc partitioning_func)\n{\n\tDimensionInfo *info = palloc(sizeof(*info));\n\t*info = (DimensionInfo){\n\t\t.type = DIMENSION_TYPE_CLOSED,\n\t\t.table_relid = table_relid,\n\t\t.num_slices = num_slices,\n\t\t.num_slices_is_set = (num_slices > 0),\n\t\t.partitioning_func = partitioning_func,\n\t};\n\tnamestrcpy(&info->colname, NameStr(*column_name));\n\treturn info;\n}\n\n/* Validate the configuration of an open (\"time\") dimension */\nstatic void\ndimension_info_validate_open(DimensionInfo *info)\n{\n\tOid dimtype = info->coltype;\n\n\tAssert(info->type == DIMENSION_TYPE_OPEN);\n\n\tif (OidIsValid(info->partitioning_func))\n\t{\n\t\tif (!ts_partitioning_func_is_valid(info->partitioning_func, info->type, info->coltype))\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),\n\t\t\t\t\t errmsg(\"invalid partitioning function\"),\n\t\t\t\t\t errhint(\"A valid partitioning function for open (time) dimensions must be \"\n\t\t\t\t\t\t\t \"IMMUTABLE, \"\n\t\t\t\t\t\t\t \"take the column type as input, and return an integer or \"\n\t\t\t\t\t\t\t \"timestamp type.\")));\n\n\t\tdimtype = get_func_rettype(info->partitioning_func);\n\t}\n\n\t/*\n\t * Validate the dimension type before trying to get the default interval.\n\t * This ensures we give a clear \"invalid type\" error rather than a confusing\n\t * \"cannot get default interval\" error for unsupported types.\n\t */\n\tif (!IS_VALID_OPEN_DIM_TYPE(dimtype))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_WRONG_OBJECT_TYPE),\n\t\t\t\t errmsg(\"invalid type for dimension \\\"%s\\\"\", NameStr(info->colname)),\n\t\t\t\t errhint(\"Use an integer, timestamp, or date type.\")));\n\n\tif (!OidIsValid(info->chunk_interval.type))\n\t\tinfo->chunk_interval = get_default_interval(dimtype, info->adaptive_chunking);\n\n\tinfo->interval = dimension_interval_to_internal(NameStr(info->colname),\n\t\t\t\t\t\t\t\t\t\t\t\t\tdimtype,\n\t\t\t\t\t\t\t\t\t\t\t\t\t&info->chunk_interval,\n\t\t\t\t\t\t\t\t\t\t\t\t\tinfo->adaptive_chunking);\n}\n\n/* Validate the configuration of a closed (\"space\") dimension */\nstatic void\ndimension_info_validate_closed(DimensionInfo *info)\n{\n\tAssert(info->type == DIMENSION_TYPE_CLOSED);\n\n\tif (!OidIsValid(info->partitioning_func))\n\t\tinfo->partitioning_func = ts_partitioning_func_get_closed_default();\n\telse if (!ts_partitioning_func_is_valid(info->partitioning_func, info->type, info->coltype))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),\n\t\t\t\t errmsg(\"invalid partitioning function\"),\n\t\t\t\t errhint(\"A valid partitioning function for closed (space) dimensions must be \"\n\t\t\t\t\t\t \"IMMUTABLE \"\n\t\t\t\t\t\t \"and have the signature (anyelement) -> integer.\")));\n\n\tif (!info->num_slices_is_set || !IS_VALID_NUM_SLICES(info->num_slices))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid number of partitions for dimension \\\"%s\\\"\",\n\t\t\t\t\t\tNameStr(info->colname)),\n\t\t\t\t errhint(\"A closed (space) dimension must specify between 1 and %d partitions.\",\n\t\t\t\t\t\t PG_INT16_MAX)));\n}\n\nvoid\nts_dimension_info_validate(DimensionInfo *info)\n{\n\tconst Dimension *dim;\n\tHeapTuple tuple;\n\tDatum datum;\n\tbool isnull = false;\n\tbool isgenerated;\n\n\tif (!DIMENSION_INFO_IS_SET(info))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"invalid dimension info\")));\n\n\tif (info->num_slices_is_set && OidIsValid(info->chunk_interval.type))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"cannot specify both the number of partitions and an interval\")));\n\n\t/* Check that the column exists and get its NOT NULL status */\n\ttuple = SearchSysCacheAttName(info->table_relid, NameStr(info->colname));\n\n\tif (!HeapTupleIsValid(tuple))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_COLUMN),\n\t\t\t\t errmsg(\"column \\\"%s\\\" does not exist\", NameStr(info->colname))));\n\n\tdatum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_atttypid, &isnull);\n\tAssert(!isnull);\n\n\tinfo->coltype = DatumGetObjectId(datum);\n\n\tdatum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attnotnull, &isnull);\n\tAssert(!isnull);\n\n\tinfo->set_not_null = !DatumGetBool(datum);\n\n\t/* check that the column is not generated */\n\tdatum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attgenerated, &isnull);\n\tAssert(!isnull);\n\tisgenerated = (DatumGetChar(datum) == ATTRIBUTE_GENERATED_STORED);\n\n\tif (isgenerated)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"invalid partitioning column\"),\n\t\t\t\t errhint(\"Generated columns cannot be used as partitioning dimensions.\")));\n\n\tReleaseSysCache(tuple);\n\n\tif (NULL != info->ht)\n\t{\n\t\t/* Check if the dimension already exists */\n\t\tdim = ts_hyperspace_get_dimension_by_name(info->ht->space,\n\t\t\t\t\t\t\t\t\t\t\t\t  DIMENSION_TYPE_ANY,\n\t\t\t\t\t\t\t\t\t\t\t\t  NameStr(info->colname));\n\n\t\tif (NULL != dim)\n\t\t{\n\t\t\tif (!info->if_not_exists)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_TS_DUPLICATE_DIMENSION),\n\t\t\t\t\t\t errmsg(\"column \\\"%s\\\" is already a dimension\", NameStr(info->colname))));\n\n\t\t\tinfo->dimension_id = dim->fd.id;\n\t\t\tinfo->skip = true;\n\n\t\t\tereport(NOTICE,\n\t\t\t\t\t(errmsg(\"column \\\"%s\\\" is already a dimension, skipping\",\n\t\t\t\t\t\t\tNameStr(info->colname))));\n\t\t\treturn;\n\t\t}\n\t}\n\n\tswitch (info->type)\n\t{\n\t\tcase DIMENSION_TYPE_CLOSED:\n\t\t\tdimension_info_validate_closed(info);\n\t\t\tbreak;\n\t\tcase DIMENSION_TYPE_OPEN:\n\t\t\tdimension_info_validate_open(info);\n\t\t\tbreak;\n\t\tcase DIMENSION_TYPE_STATS:\n\t\tcase DIMENSION_TYPE_ANY:\n\t\t\telog(ERROR, \"invalid dimension type in configuration\");\n\t\t\tbreak;\n\t}\n}\n\nint32\nts_dimension_add_from_info(DimensionInfo *info)\n{\n\tif (info->set_not_null && info->type == DIMENSION_TYPE_OPEN)\n\t\tdimension_add_not_null_on_column(info->table_relid, NameStr(info->colname));\n\n\tAssert(info->ht != NULL);\n\n\tinfo->dimension_id = dimension_insert(info->ht->fd.id,\n\t\t\t\t\t\t\t\t\t\t  &info->colname,\n\t\t\t\t\t\t\t\t\t\t  info->coltype,\n\t\t\t\t\t\t\t\t\t\t  info->num_slices,\n\t\t\t\t\t\t\t\t\t\t  info->partitioning_func,\n\t\t\t\t\t\t\t\t\t\t  info->interval);\n\n\treturn info->dimension_id;\n}\n\n/*\n * Create a datum to be returned by add_dimension DDL function\n */\nstatic Datum\ndimension_create_datum(FunctionCallInfo fcinfo, DimensionInfo *info, bool is_generic)\n{\n\tTupleDesc tupdesc;\n\tHeapTuple tuple;\n\n\tif (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"function returning record called in \"\n\t\t\t\t\t\t\"context that cannot accept type record\")));\n\n\ttupdesc = BlessTupleDesc(tupdesc);\n\n\tif (is_generic)\n\t{\n\t\tDatum values[Natts_generic_add_dimension];\n\t\tbool nulls[Natts_generic_add_dimension] = { false };\n\n\t\tAssert(tupdesc->natts == Natts_generic_add_dimension);\n\t\tvalues[AttrNumberGetAttrOffset(Anum_generic_add_dimension_id)] = info->dimension_id;\n\t\tvalues[AttrNumberGetAttrOffset(Anum_generic_add_dimension_created)] =\n\t\t\tBoolGetDatum(!info->skip);\n\t\ttuple = heap_form_tuple(tupdesc, values, nulls);\n\t}\n\telse\n\t{\n\t\tDatum values[Natts_add_dimension];\n\t\tbool nulls[Natts_add_dimension] = { false };\n\n\t\tAssert(tupdesc->natts == Natts_add_dimension);\n\t\tvalues[AttrNumberGetAttrOffset(Anum_add_dimension_id)] = info->dimension_id;\n\t\tvalues[AttrNumberGetAttrOffset(Anum_add_dimension_schema_name)] =\n\t\t\tNameGetDatum(&info->ht->fd.schema_name);\n\t\tvalues[AttrNumberGetAttrOffset(Anum_add_dimension_table_name)] =\n\t\t\tNameGetDatum(&info->ht->fd.table_name);\n\t\tvalues[AttrNumberGetAttrOffset(Anum_add_dimension_column_name)] =\n\t\t\tNameGetDatum(&info->colname);\n\t\tvalues[AttrNumberGetAttrOffset(Anum_add_dimension_created)] = BoolGetDatum(!info->skip);\n\t\ttuple = heap_form_tuple(tupdesc, values, nulls);\n\t}\n\n\treturn HeapTupleGetDatum(tuple);\n}\n\n/*\n * Add a new dimension to a hypertable.\n *\n * Arguments:\n * 0. Relation ID of table\n * 1. Column name\n * 2. Number of partitions / slices in close ('space') dimensions\n * 3. Interval for open ('time') dimensions\n * 4. Partitioning function\n * 5. IF NOT EXISTS option (bool)\n */\nstatic Datum\nts_dimension_add_internal(FunctionCallInfo fcinfo, DimensionInfo *info, bool is_generic)\n{\n\tCache *hcache;\n\tDatum retval = 0;\n\n\tAssert(DIMENSION_INFO_IS_SET(info));\n\n\tif (!DIMENSION_INFO_IS_VALID(info))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"must specify either the number of partitions or an interval\")));\n\n\tts_hypertable_permissions_check(info->table_relid, GetUserId());\n\n\t/*\n\t * The hypertable catalog table has a CHECK(num_dimensions > 0), which\n\t * means, that when this function is called from create_hypertable()\n\t * instead of directly, num_dimension is already set to one. We therefore\n\t * need to lock the hypertable tuple here so that we can set the correct\n\t * number of dimensions once we've added the new dimension.\n\t *\n\t * This lock is also used to serialize access from concurrent add_dimension()\n\t * call and a chunk creation.\n\t */\n\tLockRelationOid(info->table_relid, ShareUpdateExclusiveLock);\n\n\tDEBUG_WAITPOINT(\"add_dimension_ht_lock\");\n\n\tinfo->ht = ts_hypertable_cache_get_cache_and_entry(info->table_relid, CACHE_FLAG_NONE, &hcache);\n\n\tif (info->num_slices_is_set && OidIsValid(info->chunk_interval.type))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"cannot specify both the number of partitions and an interval\")));\n\n\tif (!info->num_slices_is_set && !OidIsValid(info->chunk_interval.type))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"cannot omit both the number of partitions and the interval\")));\n\n\tts_dimension_info_validate(info);\n\n\tif (!info->skip)\n\t{\n\t\tint32 dimension_id;\n\n\t\t/*\n\t\t * Note that space->num_dimensions reflects the actual number of\n\t\t * dimension rows and not the num_dimensions in the hypertable catalog\n\t\t * table.\n\t\t */\n\t\tts_hypertable_set_num_dimensions(info->ht, info->ht->space->num_dimensions + 1);\n\t\tdimension_id = ts_dimension_add_from_info(info);\n\n\t\t/* Verify that existing indexes are compatible with a hypertable */\n\n\t\t/*\n\t\t * Need to get a fresh copy of hypertable from the database as cache\n\t\t * does not reflect the changes in the previous 2 lines which add a\n\t\t * new dimension\n\t\t */\n\t\tinfo->ht = ts_hypertable_get_by_id(info->ht->fd.id);\n\t\tts_indexing_verify_indexes(info->ht);\n\n\t\t/*\n\t\t * If the hypertable has chunks, to make it compatible\n\t\t * we add artificial dimension slice which will cover -inf / inf\n\t\t * range.\n\t\t *\n\t\t * Newly created chunks will have a proper slice range according to\n\t\t * the created dimension and its partitioning.\n\t\t */\n\t\tif (ts_hypertable_has_chunks(info->table_relid, AccessShareLock))\n\t\t{\n\t\t\tListCell *lc;\n\t\t\tList *chunk_id_list = ts_chunk_get_chunk_ids_by_hypertable_id(info->ht->fd.id);\n\n\t\t\tDimensionSlice *slice;\n\t\t\tslice = ts_dimension_slice_create(dimension_id,\n\t\t\t\t\t\t\t\t\t\t\t  DIMENSION_SLICE_MINVALUE,\n\t\t\t\t\t\t\t\t\t\t\t  DIMENSION_SLICE_MAXVALUE);\n\t\t\tts_dimension_slice_insert_multi(&slice, 1);\n\n\t\t\tforeach (lc, chunk_id_list)\n\t\t\t{\n\t\t\t\tint32 chunk_id = lfirst_int(lc);\n\t\t\t\tChunk *chunk = ts_chunk_get_by_id(chunk_id, true);\n\t\t\t\tChunkConstraint *cc = ts_chunk_constraints_add(chunk->constraints,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   chunk->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   slice->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   NULL);\n\t\t\t\tts_chunk_constraint_insert(cc);\n\t\t\t}\n\t\t}\n\t}\n\n\tretval = dimension_create_datum(fcinfo, info, is_generic);\n\tts_cache_release(&hcache);\n\n\tPG_RETURN_DATUM(retval);\n}\n\nTS_FUNCTION_INFO_V1(ts_dimension_add);\nTS_FUNCTION_INFO_V1(ts_dimension_add_general);\n\nDatum\nts_dimension_add(PG_FUNCTION_ARGS)\n{\n\tOid interval_type = PG_ARGISNULL(3) ? InvalidOid : get_fn_expr_argtype(fcinfo->flinfo, 3);\n\tDimensionInfo info = {\n\t\t.type = PG_ARGISNULL(2) ? DIMENSION_TYPE_OPEN : DIMENSION_TYPE_CLOSED,\n\t\t.table_relid = PG_GETARG_OID(0),\n\t\t.num_slices = PG_ARGISNULL(2) ? DatumGetInt32(-1) : PG_GETARG_INT32(2),\n\t\t.num_slices_is_set = !PG_ARGISNULL(2),\n\t\t.chunk_interval.type = interval_type,\n\t\t.partitioning_func = PG_ARGISNULL(4) ? InvalidOid : PG_GETARG_OID(4),\n\t\t.if_not_exists = PG_ARGISNULL(5) ? false : PG_GETARG_BOOL(5),\n\t};\n\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\tif (!PG_ARGISNULL(1))\n\t\tnamestrcpy(&info.colname, NameStr(*PG_GETARG_NAME(1)));\n\n\tif (!PG_ARGISNULL(3))\n\t\tchunk_interval_set(&info.chunk_interval, PG_GETARG_DATUM(3), interval_type);\n\n\tif (PG_ARGISNULL(0))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"hypertable cannot be NULL\")));\n\n\treturn ts_dimension_add_internal(fcinfo, &info, false);\n}\n\nTSDLLEXPORT Datum\nts_dimension_info_in(PG_FUNCTION_ARGS)\n{\n\tereport(ERROR,\n\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t errmsg(\"cannot construct type \\\"dimension_info\\\" from string\"),\n\t\t\t errdetail(\"Type dimension_info cannot be constructed from string. You need to \"\n\t\t\t\t\t   \"use constructor function.\"),\n\t\t\t errhint(\"Use \\\"by_range\\\" or \\\"by_hash\\\" to construct dimension types.\")));\n\tPG_RETURN_VOID(); /* keep compiler quiet */\n}\n\n/*\n * Get the interval value as a Datum from a ChunkInterval.\n * On 32-bit platforms, int64 is pass-by-reference so we need Int64GetDatumFast.\n */\nstatic Datum\nchunk_interval_get_datum(const ChunkInterval *ci)\n{\n\tswitch (ci->type)\n\t{\n\t\tcase INT2OID:\n\t\t\treturn Int16GetDatum((int16) ci->integer_interval);\n\t\tcase INT4OID:\n\t\t\treturn Int32GetDatum((int32) ci->integer_interval);\n\t\tcase INT8OID:\n\t\t\t/* int64 is pass-by-ref on 32-bit, pass-by-val on 64-bit */\n\t\t\treturn Int64GetDatumFast(ci->integer_interval);\n\t\tcase INTERVALOID:\n\t\t\t/* Interval is always pass-by-ref */\n\t\t\treturn IntervalPGetDatum(&((ChunkInterval *) ci)->interval);\n\t\tdefault:\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"unsupported chunk interval type %d\", ci->type)));\n\t\t\treturn UnassignedDatum; /* keep compiler quiet */\n\t}\n}\n\nTSDLLEXPORT Datum\nts_dimension_info_out(PG_FUNCTION_ARGS)\n{\n\tDimensionInfo *info = (DimensionInfo *) PG_GETARG_POINTER(0);\n\tStringInfoData str;\n\tconst char *partfuncname =\n\t\tOidIsValid(info->partitioning_func) ? get_func_name(info->partitioning_func) : \"-\";\n\tinitStringInfo(&str);\n\tswitch (info->type)\n\t{\n\t\tcase DIMENSION_TYPE_CLOSED:\n\t\t\tappendStringInfo(&str,\n\t\t\t\t\t\t\t \"hash//%s//%d//%s\",\n\t\t\t\t\t\t\t NameStr(info->colname),\n\t\t\t\t\t\t\t info->num_slices,\n\t\t\t\t\t\t\t partfuncname);\n\t\t\tbreak;\n\n\t\tcase DIMENSION_TYPE_OPEN:\n\t\t{\n\t\t\tconst char *argvalstr = \"-\";\n\n\t\t\tif (OidIsValid(info->chunk_interval.type))\n\t\t\t{\n\t\t\t\targvalstr = ts_datum_to_string(chunk_interval_get_datum(&info->chunk_interval),\n\t\t\t\t\t\t\t\t\t\t\t   info->chunk_interval.type);\n\t\t\t}\n\n\t\t\tappendStringInfo(&str,\n\t\t\t\t\t\t\t \"range//%s//%s//%s\",\n\t\t\t\t\t\t\t NameStr(info->colname),\n\t\t\t\t\t\t\t argvalstr,\n\t\t\t\t\t\t\t partfuncname);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase DIMENSION_TYPE_STATS:\n\t\t\tappendStringInfo(&str, \"range\");\n\t\t\tbreak;\n\n\t\tcase DIMENSION_TYPE_ANY:\n\t\t\tappendStringInfo(&str, \"any\");\n\t\t\tbreak;\n\t}\n\tPG_RETURN_CSTRING(str.data);\n}\n\nstatic DimensionInfo *\nmake_dimension_info(Name colname, DimensionType dimtype)\n{\n\tsize_t size = sizeof(DimensionInfo);\n\tDimensionInfo *info = palloc0(size);\n\tSET_VARSIZE(info, size);\n\n\tinfo->type = dimtype;\n\tnamestrcpy(&info->colname, NameStr(*colname));\n\treturn info;\n}\n\n/*\n * DimensionInfo for a hash dimension.\n *\n * This structure is only partially filled in when constructed. The rest will\n * be filled in by ts_dimension_add_general.\n */\nDatum\nts_hash_dimension(PG_FUNCTION_ARGS)\n{\n\tEnsure(PG_NARGS() > 2, \"expected at most 3 arguments, invoked with %d arguments\", PG_NARGS());\n\tName column_name;\n\tGETARG_NOTNULL_NULLABLE(column_name, 0, \"column_name\", NAME);\n\tDimensionInfo *info = make_dimension_info(column_name, DIMENSION_TYPE_CLOSED);\n\tinfo->num_slices = PG_ARGISNULL(1) ? DatumGetInt32(-1) : PG_GETARG_INT32(1);\n\tinfo->num_slices_is_set = !PG_ARGISNULL(1);\n\tinfo->partitioning_func = PG_ARGISNULL(2) ? InvalidOid : PG_GETARG_OID(2);\n\tPG_RETURN_POINTER(info);\n}\n\n/*\n * DimensionInfo for a hash dimension.\n *\n * This structure is only partially filled in when constructed. The rest will\n * be filled in by ts_dimension_add_general.\n */\nDatum\nts_range_dimension(PG_FUNCTION_ARGS)\n{\n\tEnsure(PG_NARGS() > 2, \"expected at most 3 arguments, invoked with %d arguments\", PG_NARGS());\n\tName column_name;\n\tGETARG_NOTNULL_NULLABLE(column_name, 0, \"column_name\", NAME);\n\tDimensionInfo *info = make_dimension_info(column_name, DIMENSION_TYPE_OPEN);\n\tDatum interval_datum = PG_ARGISNULL(1) ? Int32GetDatum(-1) : PG_GETARG_DATUM(1);\n\tOid interval_type = PG_ARGISNULL(1) ? InvalidOid : get_fn_expr_argtype(fcinfo->flinfo, 1);\n\tinfo->partitioning_func = PG_ARGISNULL(2) ? InvalidOid : PG_GETARG_OID(2);\n\n\tchunk_interval_set(&info->chunk_interval, interval_datum, interval_type);\n\tPG_RETURN_POINTER(info);\n}\n\nDatum\nts_dimension_add_general(PG_FUNCTION_ARGS)\n{\n\tDimensionInfo *info = NULL;\n\tGETARG_NOTNULL_POINTER(info, 1, \"dimension\", DimensionInfo);\n\tinfo->table_relid = PG_GETARG_OID(0);\n\tif (PG_GETARG_BOOL(2))\n\t\tinfo->if_not_exists = true;\n\treturn ts_dimension_add_internal(fcinfo, info, true);\n}\n\n/* Used as a tuple found function */\nstatic ScanTupleResult\ndimension_rename_schema_name(TupleInfo *ti, void *data)\n{\n\t/* Dimension table may contain null valued columns that is why we do not use\n\t * FormData_dimension *dimension = (FormData_dimension *) GETSTRUCT(tuple);\n\t * pattern here\n\t */\n\tDatum values[Natts_dimension];\n\tbool nulls[Natts_dimension];\n\tbool doReplace[Natts_dimension] = { false };\n\tbool should_free;\n\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\tHeapTuple new_tuple;\n\t/* contains [old_name,new_name] in that order */\n\tchar **names = (char **) data;\n\tName schemaname;\n\n\theap_deform_tuple(tuple, ts_scanner_get_tupledesc(ti), values, nulls);\n\tAssert(!nulls[AttrNumberGetAttrOffset(Anum_dimension_partitioning_func_schema)] ||\n\t\t   !nulls[AttrNumberGetAttrOffset(Anum_dimension_integer_now_func_schema)]);\n\n\t/* Rename schema names */\n\tif (!nulls[AttrNumberGetAttrOffset(Anum_dimension_partitioning_func_schema)])\n\t{\n\t\tschemaname =\n\t\t\tDatumGetName(values[AttrNumberGetAttrOffset(Anum_dimension_partitioning_func_schema)]);\n\n\t\tif (namestrcmp(schemaname, names[0]) == 0)\n\t\t{\n\t\t\tnamestrcpy(schemaname, (const char *) names[1]);\n\t\t\tvalues[AttrNumberGetAttrOffset(Anum_dimension_partitioning_func_schema)] =\n\t\t\t\tNameGetDatum(schemaname);\n\t\t\tdoReplace[AttrNumberGetAttrOffset(Anum_dimension_partitioning_func_schema)] = true;\n\t\t}\n\t}\n\n\tif (!nulls[AttrNumberGetAttrOffset(Anum_dimension_integer_now_func_schema)])\n\t{\n\t\tschemaname =\n\t\t\tDatumGetName(values[AttrNumberGetAttrOffset(Anum_dimension_integer_now_func_schema)]);\n\t\tif (namestrcmp(schemaname, names[0]) == 0)\n\t\t{\n\t\t\tnamestrcpy(schemaname, (const char *) names[1]);\n\t\t\tvalues[AttrNumberGetAttrOffset(Anum_dimension_integer_now_func_schema)] =\n\t\t\t\tNameGetDatum(schemaname);\n\t\t\tdoReplace[AttrNumberGetAttrOffset(Anum_dimension_integer_now_func_schema)] = true;\n\t\t}\n\t}\n\n\tnew_tuple = heap_modify_tuple(tuple, ts_scanner_get_tupledesc(ti), values, nulls, doReplace);\n\tts_catalog_update(ti->scanrel, new_tuple);\n\theap_freetuple(new_tuple);\n\n\tif (should_free)\n\t\theap_freetuple(tuple);\n\n\treturn SCAN_CONTINUE;\n}\n\n/* Go through internal dimensions table and rename all relevant schema */\nvoid\nts_dimensions_rename_schema_name(const char *old_name, const char *new_name)\n{\n\tNameData old_schema_name;\n\tScanKeyData scankey[1];\n\tCatalog *catalog = ts_catalog_get();\n\tchar *names[2] = { (char *) old_name, (char *) new_name };\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, DIMENSION),\n\t\t.index = InvalidOid,\n\t\t.nkeys = 1,\n\t\t.scankey = scankey,\n\t\t.tuple_found = dimension_rename_schema_name,\n\t\t.data = (void *) names,\n\t\t.lockmode = RowExclusiveLock,\n\t\t.scandirection = ForwardScanDirection,\n\t};\n\n\tnamestrcpy(&old_schema_name, old_name);\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_dimension_partitioning_func_schema,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_NAMEEQ,\n\t\t\t\tNameGetDatum(&old_schema_name));\n\n\tts_scanner_scan(&scanctx);\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_dimension_integer_now_func_schema,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_NAMEEQ,\n\t\t\t\tNameGetDatum(&old_schema_name));\n\n\tts_scanner_scan(&scanctx);\n}\n"
  },
  {
    "path": "src/dimension.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <access/attnum.h>\n#include <access/htup_details.h>\n#include <catalog/pg_type.h>\n#include <executor/tuptable.h>\n\n#include \"export.h\"\n#include \"time_utils.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"utils.h\"\n\ntypedef struct PartitioningInfo PartitioningInfo;\ntypedef struct DimensionSlice DimensionSlice;\ntypedef struct DimensionVec DimensionVec;\n\n/*\n * The chunk interval of an open partitioning dimension.\n *\n * The type can either be INTERVALOID or an INT(2|4|8)OID.\n */\ntypedef struct ChunkInterval\n{\n\tOid type;\n\t/* Interval value storage */\n\tunion\n\t{\n\t\tint64 integer_interval; /* For INT8OID, INT4OID, INT2OID */\n\t\tInterval interval;\t\t/* For INTERVALOID */\n\t};\n} ChunkInterval;\n\nstatic inline void\nchunk_interval_set(ChunkInterval *chunk_interval, Datum interval, Oid type)\n{\n\tchunk_interval->type = type;\n\n\t/* Store interval value in appropriate union field */\n\tif (type == INT8OID)\n\t\tchunk_interval->integer_interval = DatumGetInt64(interval);\n\telse if (type == INTERVALOID)\n\t\tchunk_interval->interval = *DatumGetIntervalP(interval);\n\telse if (type == INT4OID)\n\t\tchunk_interval->integer_interval = DatumGetInt32(interval);\n\telse if (type == INT2OID)\n\t\tchunk_interval->integer_interval = DatumGetInt16(interval);\n}\n\ntypedef enum DimensionType\n{\n\tDIMENSION_TYPE_OPEN,\n\tDIMENSION_TYPE_CLOSED,\n\tDIMENSION_TYPE_STATS,\n\tDIMENSION_TYPE_ANY,\n} DimensionType;\n\ntypedef struct Dimension\n{\n\tFormData_dimension fd;\n\tDimensionType type;\n\tAttrNumber column_attno;\n\tOid main_table_relid;\n\tPartitioningInfo *partitioning;\n} Dimension;\n\n#define IS_OPEN_DIMENSION(d) ((d)->type == DIMENSION_TYPE_OPEN)\n#define IS_CLOSED_DIMENSION(d) ((d)->type == DIMENSION_TYPE_CLOSED)\n#define IS_VALID_OPEN_DIM_TYPE(type)                                                               \\\n\t(IS_INTEGER_TYPE(type) || IS_TIMESTAMP_TYPE(type) || IS_UUID_TYPE(type) ||                     \\\n\t ts_type_is_int8_binary_compatible(type))\n\n/*\n * A hyperspace defines how to partition in a N-dimensional space.\n */\ntypedef struct Hyperspace\n{\n\tint32 hypertable_id;\n\tOid main_table_relid;\n\tuint16 capacity;\n\tuint16 num_dimensions;\n\t/* Open dimensions should be stored before closed dimensions */\n\tDimension dimensions[FLEXIBLE_ARRAY_MEMBER];\n} Hyperspace;\n\n#define HYPERSPACE_SIZE(num_dimensions)                                                            \\\n\t(sizeof(Hyperspace) + (sizeof(Dimension) * (num_dimensions)))\n\n/*\n * A point in an N-dimensional hyperspace.\n */\ntypedef struct Point\n{\n\tint16 cardinality;\n\tuint8 num_coords;\n\t/* Open dimension coordinates are stored before the closed coordinates */\n\tint64 coordinates[FLEXIBLE_ARRAY_MEMBER];\n} Point;\n\n#define POINT_SIZE(cardinality) (sizeof(Point) + (sizeof(int64) * (cardinality)))\n\n#define DEFAULT_CHUNK_TIME_INTERVAL (USECS_PER_DAY * 7) /* 7 days w/o adaptive */\n#define DEFAULT_CHUNK_TIME_INTERVAL_ADAPTIVE                                                       \\\n\t(USECS_PER_DAY) /* 1 day with adaptive                                                         \\\n\t\t\t\t\t * chunking enabled */\n\n/* Default intervals for integer types */\n#define DEFAULT_SMALLINT_INTERVAL 10000\n#define DEFAULT_INT_INTERVAL 100000\n#define DEFAULT_BIGINT_INTERVAL 1000000\n\ntypedef struct Hypertable Hypertable;\n\n/*\n * Dimension information used to validate, create and update dimensions.\n *\n * This structure is used both partially filled in from the dimension info\n * constructors as well as when building dimension info for the storage into\n * the dimension table.\n *\n * @see ts_hash_dimension\n * @see ts_range_dimension\n */\ntypedef struct DimensionInfo\n{\n\t/* We declare the SQL type dimension_info with INTERNALLENGTH = VARIABLE.\n\t * So, PostgreSQL expects a proper length info field (varlena header).\n\t */\n\tint32 vl_len_;\n\n\tOid table_relid;\n\tint32 dimension_id;\n\tNameData colname;\n\tOid coltype;\n\tDimensionType type;\n\tChunkInterval chunk_interval;\n\tint64 interval;\n\tint32 num_slices;\n\tregproc partitioning_func;\n\tbool if_not_exists;\n\tbool skip;\n\tbool set_not_null;\n\tbool num_slices_is_set;\n\tbool adaptive_chunking; /* True if adaptive chunking is enabled */\n\tHypertable *ht;\n} DimensionInfo;\n\n#define DIMENSION_INFO_IS_SET(di) (di != NULL && OidIsValid((di)->table_relid))\n#define DIMENSION_INFO_IS_VALID(di)                                                                \\\n\t(info->num_slices_is_set || OidIsValid(info->chunk_interval.type))\n\nextern Hyperspace *ts_dimension_scan(int32 hypertable_id, Oid main_table_relid, int16 num_dimension,\n\t\t\t\t\t\t\t\t\t MemoryContext mctx);\nextern DimensionSlice *ts_dimension_calculate_default_slice(const Dimension *dim, int64 value);\nextern TSDLLEXPORT Point *ts_hyperspace_calculate_point(const Hyperspace *h, TupleTableSlot *slot);\nextern int ts_dimension_get_slice_ordinal(const Dimension *dim, const DimensionSlice *slice);\nextern TSDLLEXPORT const Dimension *ts_hyperspace_get_dimension_by_id(const Hyperspace *hs,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  int32 id);\nextern TSDLLEXPORT const Dimension *ts_hyperspace_get_dimension(const Hyperspace *hs,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tDimensionType type, Index n);\nextern TSDLLEXPORT Dimension *ts_hyperspace_get_mutable_dimension(Hyperspace *hs,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  DimensionType type, Index n);\nextern TSDLLEXPORT const Dimension *\nts_hyperspace_get_dimension_by_name(const Hyperspace *hs, DimensionType type, const char *name);\nextern TSDLLEXPORT Dimension *\nts_hyperspace_get_mutable_dimension_by_name(Hyperspace *hs, DimensionType type, const char *name);\nextern DimensionVec *ts_dimension_get_slices(const Dimension *dim);\nextern int32 ts_dimension_get_hypertable_id(int32 dimension_id);\nextern int ts_dimension_set_type(Dimension *dim, Oid newtype);\nextern TSDLLEXPORT Oid ts_dimension_get_partition_type(const Dimension *dim);\nextern int ts_dimension_set_name(Dimension *dim, const char *newname);\nextern TSDLLEXPORT int ts_dimension_set_chunk_interval(Dimension *dim, int64 chunk_interval);\nextern int ts_dimension_set_compress_interval(Dimension *dim, int64 compress_interval);\nextern Datum ts_dimension_transform_value(const Dimension *dim, Oid collation, Datum value,\n\t\t\t\t\t\t\t\t\t\t  Oid const_datum_type, Oid *restype);\nextern int ts_dimension_delete_by_hypertable_id(int32 hypertable_id, bool delete_slices);\n\nextern TSDLLEXPORT DimensionInfo *ts_dimension_info_create_open(Oid table_relid, Name column_name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tDatum interval, Oid interval_type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tregproc partitioning_func);\n\nextern TSDLLEXPORT DimensionInfo *ts_dimension_info_create_closed(Oid table_relid, Name column_name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  int32 num_slices,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  regproc partitioning_func);\n\nextern void ts_dimension_info_validate(DimensionInfo *info);\nextern int32 ts_dimension_add_from_info(DimensionInfo *info);\nextern void ts_dimensions_rename_schema_name(const char *old_name, const char *new_name);\nextern TSDLLEXPORT void ts_dimension_update(const Hypertable *ht, const NameData *dimname,\n\t\t\t\t\t\t\t\t\t\t\tDimensionType dimtype, Datum *interval,\n\t\t\t\t\t\t\t\t\t\t\tOid *intervaltype, int16 *num_slices,\n\t\t\t\t\t\t\t\t\t\t\tOid *integer_now_func);\nextern TSDLLEXPORT Point *ts_point_create(int16 num_dimensions);\nextern TSDLLEXPORT bool ts_is_equality_operator(Oid opno, Oid left, Oid right);\nextern TSDLLEXPORT Datum ts_dimension_info_in(PG_FUNCTION_ARGS);\nextern TSDLLEXPORT Datum ts_dimension_info_out(PG_FUNCTION_ARGS);\n\n#define hyperspace_get_open_dimension(space, i)                                                    \\\n\tts_hyperspace_get_dimension(space, DIMENSION_TYPE_OPEN, i)\n#define hyperspace_get_closed_dimension(space, i)                                                  \\\n\tts_hyperspace_get_dimension(space, DIMENSION_TYPE_CLOSED, i)\n"
  },
  {
    "path": "src/dimension_slice.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/heapam.h>\n#include <access/relscan.h>\n#include <access/xact.h>\n#include <catalog/indexing.h>\n#include <catalog/pg_opfamily.h>\n#include <catalog/pg_type.h>\n#include <funcapi.h>\n#include <utils/builtins.h>\n#include <utils/lsyscache.h>\n#include <utils/rel.h>\n\n#include \"bgw_policy/chunk_stats.h\"\n#include \"chunk.h\"\n#include \"chunk_constraint.h\"\n#include \"dimension.h\"\n#include \"dimension_slice.h\"\n#include \"dimension_vector.h\"\n#include \"hypertable.h\"\n#include \"scanner.h\"\n#include \"ts_catalog/catalog.h\"\n\n#include \"compat/compat.h\"\n\nstatic inline DimensionSlice *\ndimension_slice_alloc(void)\n{\n\treturn palloc0(sizeof(DimensionSlice));\n}\n\nstatic inline DimensionSlice *\ndimension_slice_from_form_data(const Form_dimension_slice fd)\n{\n\tDimensionSlice *slice = dimension_slice_alloc();\n\n\tmemcpy(&slice->fd, fd, sizeof(FormData_dimension_slice));\n\tslice->storage_free = NULL;\n\tslice->storage = NULL;\n\treturn slice;\n}\n\nstatic inline DimensionSlice *\ndimension_slice_from_slot(TupleTableSlot *slot)\n{\n\tbool should_free;\n\tHeapTuple tuple = ExecFetchSlotHeapTuple(slot, false, &should_free);\n\tDimensionSlice *slice;\n\n\tslice = dimension_slice_from_form_data((Form_dimension_slice) GETSTRUCT(tuple));\n\n\tif (should_free)\n\t\theap_freetuple(tuple);\n\n\treturn slice;\n}\n\nstatic HeapTuple\ndimension_slice_formdata_make_tuple(const FormData_dimension_slice *fd, TupleDesc desc)\n{\n\tDatum values[Natts_dimension_slice];\n\tbool nulls[Natts_dimension_slice] = { false };\n\n\tmemset(values, 0, sizeof(Datum) * Natts_dimension_slice);\n\n\tvalues[AttrNumberGetAttrOffset(Anum_dimension_slice_id)] = Int32GetDatum(fd->id);\n\tvalues[AttrNumberGetAttrOffset(Anum_dimension_slice_dimension_id)] =\n\t\tInt32GetDatum(fd->dimension_id);\n\tvalues[AttrNumberGetAttrOffset(Anum_dimension_slice_range_start)] =\n\t\tInt64GetDatum(fd->range_start);\n\tvalues[AttrNumberGetAttrOffset(Anum_dimension_slice_range_end)] = Int64GetDatum(fd->range_end);\n\n\treturn heap_form_tuple(desc, values, nulls);\n}\n\nstatic inline void\ndimension_slice_formdata_fill(FormData_dimension_slice *fd, const TupleInfo *ti)\n{\n\tbool nulls[Natts_dimension_slice];\n\tDatum values[Natts_dimension_slice];\n\tbool should_free;\n\tHeapTuple tuple;\n\n\ttuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\theap_deform_tuple(tuple, ts_scanner_get_tupledesc(ti), values, nulls);\n\n\tAssert(!nulls[AttrNumberGetAttrOffset(Anum_dimension_slice_id)]);\n\tAssert(!nulls[AttrNumberGetAttrOffset(Anum_dimension_slice_dimension_id)]);\n\tAssert(!nulls[AttrNumberGetAttrOffset(Anum_dimension_slice_range_start)]);\n\tAssert(!nulls[AttrNumberGetAttrOffset(Anum_dimension_slice_range_end)]);\n\n\tfd->id = DatumGetInt32(values[AttrNumberGetAttrOffset(Anum_dimension_slice_id)]);\n\tfd->dimension_id =\n\t\tDatumGetInt32(values[AttrNumberGetAttrOffset(Anum_dimension_slice_dimension_id)]);\n\tfd->range_start =\n\t\tDatumGetInt64(values[AttrNumberGetAttrOffset(Anum_dimension_slice_range_start)]);\n\tfd->range_end = DatumGetInt64(values[AttrNumberGetAttrOffset(Anum_dimension_slice_range_end)]);\n\n\tif (should_free)\n\t\theap_freetuple(tuple);\n}\n\nstatic bool\nlock_dimension_slice_tuple(int32 dimension_slice_id, ItemPointer tid,\n\t\t\t\t\t\t   FormData_dimension_slice *form)\n{\n\tbool success = false;\n\tScanTupLock scantuplock = {\n\t\t.waitpolicy = LockWaitBlock,\n\t\t.lockmode = LockTupleExclusive,\n\t};\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(DIMENSION_SLICE, RowShareLock, CurrentMemoryContext);\n\titerator.ctx.index =\n\t\tcatalog_get_index(ts_catalog_get(), DIMENSION_SLICE, DIMENSION_SLICE_ID_IDX);\n\titerator.ctx.tuplock = &scantuplock;\n\t/* Keeping the lock since we presumably want to update the tuple */\n\titerator.ctx.flags = SCANNER_F_KEEPLOCK;\n\n\t/* see table_tuple_lock for details about flags that are set in TupleExclusive mode */\n\tscantuplock.lockflags = TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS;\n\tif (!IsolationUsesXactSnapshot())\n\t{\n\t\t/* in read committed mode, we follow all updates to this tuple */\n\t\tscantuplock.lockflags |= TUPLE_LOCK_FLAG_FIND_LAST_VERSION;\n\t}\n\n\tts_scan_iterator_scan_key_init(&iterator,\n\t\t\t\t\t\t\t\t   Anum_dimension_slice_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(dimension_slice_id));\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tif (ti->lockresult != TM_Ok)\n\t\t{\n\t\t\tif (IsolationUsesXactSnapshot())\n\t\t\t{\n\t\t\t\t/* For Repeatable Read and Serializable isolation level report error\n\t\t\t\t * if we cannot lock the tuple\n\t\t\t\t */\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),\n\t\t\t\t\t\t errmsg(\"could not serialize access due to concurrent update\")));\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t\t\t errmsg(\"unable to lock hypertable catalog tuple, lock result is %d for \"\n\t\t\t\t\t\t\t\t\"hypertable \"\n\t\t\t\t\t\t\t\t\"ID (%d)\",\n\t\t\t\t\t\t\t\tti->lockresult,\n\t\t\t\t\t\t\t\tdimension_slice_id)));\n\t\t\t}\n\t\t}\n\t\tdimension_slice_formdata_fill(form, ti);\n\t\tItemPointer result_tid = ts_scanner_get_tuple_tid(ti);\n\t\ttid->ip_blkid = result_tid->ip_blkid;\n\t\ttid->ip_posid = result_tid->ip_posid;\n\t\tsuccess = true;\n\t\tbreak;\n\t}\n\tts_scan_iterator_close(&iterator);\n\treturn success;\n}\n\n/* update the tuple at this tid. The assumption is that we already hold a\n * tuple exclusive lock and no other transaction can modify this tuple\n * The sequence of operations for any update is:\n * lock the tuple using lock_hypertable_tuple.\n * then update the required fields\n * call dimension_slice_update_catalog_tuple to complete the update.\n * This ensures correct tuple locking and tuple updates in the presence of\n * concurrent transactions. Failure to follow this results in catalog corruption\n */\nstatic void\ndimension_slice_update_catalog_tuple(ItemPointer tid, FormData_dimension_slice *update)\n{\n\tHeapTuple new_tuple;\n\tCatalogSecurityContext sec_ctx;\n\tCatalog *catalog = ts_catalog_get();\n\tOid table = catalog_get_table_id(catalog, DIMENSION_SLICE);\n\tRelation dimension_slice_rel = relation_open(table, RowExclusiveLock);\n\n\tnew_tuple = dimension_slice_formdata_make_tuple(update, dimension_slice_rel->rd_att);\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_update_tid(dimension_slice_rel, tid, new_tuple);\n\tts_catalog_restore_user(&sec_ctx);\n\theap_freetuple(new_tuple);\n\trelation_close(dimension_slice_rel, NoLock);\n}\n\n/* delete the tuple at this tid. The assumption is that we already hold a\n * tuple exclusive lock and no other transaction can modify this tuple\n * The sequence of operations for any delete is:\n * lock the tuple using lock_hypertable_tuple.\n * call dimension_slice_delete_catalog_tuple to complete the delete.\n * This ensures correct tuple locking and tuple deletes in the presence of\n * concurrent transactions. Failure to follow this results in catalog corruption\n */\nstatic void\ndimension_slice_delete_catalog_tuple(ItemPointer tid)\n{\n\tCatalogSecurityContext sec_ctx;\n\tCatalog *catalog = ts_catalog_get();\n\tOid table = catalog_get_table_id(catalog, DIMENSION_SLICE);\n\tRelation dimension_slice_rel = relation_open(table, RowExclusiveLock);\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_delete_tid(dimension_slice_rel, tid);\n\tts_catalog_restore_user(&sec_ctx);\n\trelation_close(dimension_slice_rel, NoLock);\n}\n\nDimensionSlice *\nts_dimension_slice_create(int dimension_id, int64 range_start, int64 range_end)\n{\n\tDimensionSlice *slice = dimension_slice_alloc();\n\n\tslice->fd.dimension_id = dimension_id;\n\tslice->fd.range_start = range_start;\n\tslice->fd.range_end = range_end;\n\n\treturn slice;\n}\n\nint\nts_dimension_slice_cmp(const DimensionSlice *left, const DimensionSlice *right)\n{\n\tint res = DIMENSION_SLICE_RANGE_START_CMP(left, right);\n\n\tif (res == 0)\n\t\tres = DIMENSION_SLICE_RANGE_END_CMP(left, right);\n\n\treturn res;\n}\n\nint\nts_dimension_slice_cmp_coordinate(const DimensionSlice *slice, int64 coord)\n{\n\tcoord = REMAP_LAST_COORDINATE(coord);\n\tif (coord < slice->fd.range_start)\n\t\treturn -1;\n\n\tif (coord >= slice->fd.range_end)\n\t\treturn 1;\n\n\treturn 0;\n}\n\nstatic bool\ntuple_is_deleted(TupleInfo *ti)\n{\n#ifdef USE_ASSERT_CHECKING\n\tif (ti->lockresult == TM_Deleted)\n\t\tAssert(ItemPointerEquals(ts_scanner_get_tuple_tid(ti), &ti->lockfd.ctid));\n#endif\n\treturn ti->lockresult == TM_Deleted;\n}\n\nstatic void\nlock_result_ok_or_abort(TupleInfo *ti)\n{\n\tswitch (ti->lockresult)\n\t{\n\t\t/* Updating a tuple in the same transaction before taking a lock is OK\n\t\t * even though it is not expected in this case */\n\t\tcase TM_SelfModified:\n\t\tcase TM_Ok:\n\t\t\tbreak;\n\t\tcase TM_Deleted:\n\t\tcase TM_Updated:\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_LOCK_NOT_AVAILABLE),\n\t\t\t\t\t errmsg(\"chunk %s by other transaction\",\n\t\t\t\t\t\t\ttuple_is_deleted(ti) ? \"deleted\" : \"updated\"),\n\t\t\t\t\t errhint(\"Retry the operation again.\")));\n\t\t\tpg_unreachable();\n\t\t\tbreak;\n\n\t\tcase TM_BeingModified:\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_LOCK_NOT_AVAILABLE),\n\t\t\t\t\t errmsg(\"chunk updated by other transaction\"),\n\t\t\t\t\t errhint(\"Retry the operation again.\")));\n\t\t\tpg_unreachable();\n\t\t\tbreak;\n\t\tcase TM_Invisible:\n\t\t\telog(ERROR, \"attempt to lock invisible tuple\");\n\t\t\tpg_unreachable();\n\t\t\tbreak;\n\t\tcase TM_WouldBlock:\n\t\tdefault:\n\t\t\telog(ERROR, \"unexpected tuple lock status: %d\", ti->lockresult);\n\t\t\tpg_unreachable();\n\t\t\tbreak;\n\t}\n}\n\nstatic ScanTupleResult\ndimension_vec_tuple_found_list(TupleInfo *ti, void *data)\n{\n\tList **slices = (List **) data;\n\tDimensionSlice *slice;\n\tMemoryContext old;\n\n\tswitch (ti->lockresult)\n\t{\n\t\tcase TM_SelfModified:\n\t\tcase TM_Ok:\n\t\t\tbreak;\n\t\tcase TM_Deleted:\n\t\tcase TM_Updated:\n\t\t\t/* Treat as not found */\n\t\t\treturn SCAN_CONTINUE;\n\t\tdefault:\n\t\t\telog(ERROR, \"unexpected tuple lock status: %d\", ti->lockresult);\n\t\t\tpg_unreachable();\n\t\t\tbreak;\n\t}\n\n\told = MemoryContextSwitchTo(ti->mctx);\n\tslice = dimension_slice_from_slot(ti->slot);\n\tAssert(NULL != slice);\n\t*slices = lappend(*slices, slice);\n\tMemoryContextSwitchTo(old);\n\n\treturn SCAN_CONTINUE;\n}\n\nstatic ScanTupleResult\ndimension_vec_tuple_found(TupleInfo *ti, void *data)\n{\n\tDimensionVec **slices = (DimensionVec **) data;\n\tDimensionSlice *slice;\n\tMemoryContext old;\n\n\tswitch (ti->lockresult)\n\t{\n\t\tcase TM_SelfModified:\n\t\tcase TM_Ok:\n\t\t\tbreak;\n\t\tcase TM_Deleted:\n\t\tcase TM_Updated:\n\t\t\t/* Treat as not found */\n\t\t\treturn SCAN_CONTINUE;\n\t\tdefault:\n\t\t\telog(ERROR, \"unexpected tuple lock status: %d\", ti->lockresult);\n\t\t\tpg_unreachable();\n\t\t\tbreak;\n\t}\n\n\told = MemoryContextSwitchTo(ti->mctx);\n\tslice = dimension_slice_from_slot(ti->slot);\n\tAssert(NULL != slice);\n\t*slices = ts_dimension_vec_add_slice(slices, slice);\n\tMemoryContextSwitchTo(old);\n\n\treturn SCAN_CONTINUE;\n}\n\nstatic int\ndimension_slice_scan_limit_direction_internal(int indexid, ScanKeyData *scankey, int nkeys,\n\t\t\t\t\t\t\t\t\t\t\t  tuple_found_func on_tuple_found, void *scandata,\n\t\t\t\t\t\t\t\t\t\t\t  int limit, ScanDirection scandir, LOCKMODE lockmode,\n\t\t\t\t\t\t\t\t\t\t\t  const ScanTupLock *tuplock, MemoryContext mctx)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, DIMENSION_SLICE),\n\t\t.index = catalog_get_index(catalog, DIMENSION_SLICE, indexid),\n\t\t.nkeys = nkeys,\n\t\t.scankey = scankey,\n\t\t.data = scandata,\n\t\t.limit = limit,\n\t\t.tuplock = tuplock,\n\t\t.tuple_found = on_tuple_found,\n\t\t.lockmode = lockmode,\n\t\t.scandirection = scandir,\n\t\t.result_mctx = mctx,\n\t};\n\n\treturn ts_scanner_scan(&scanctx);\n}\n\nstatic int\ndimension_slice_scan_limit_internal(int indexid, ScanKeyData *scankey, int nkeys,\n\t\t\t\t\t\t\t\t\ttuple_found_func on_tuple_found, void *scandata, int limit,\n\t\t\t\t\t\t\t\t\tLOCKMODE lockmode, const ScanTupLock *tuplock,\n\t\t\t\t\t\t\t\t\tMemoryContext mctx)\n{\n\t/*\n\t * We have =, <=, > ops for index columns, so backwards scan direction is\n\t * more appropriate. Forward direction wouldn't be able to use the second\n\t * column to find a starting point for the scan. Unfortunately we can't do\n\t * anything about the third column, we'll be checking for it with a\n\t * sequential scan over index pages. Ideally we need some other index type\n\t * than btree for this.\n\t */\n\treturn dimension_slice_scan_limit_direction_internal(indexid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t scankey,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t nkeys,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t on_tuple_found,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t scandata,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t limit,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t BackwardScanDirection,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t lockmode,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t tuplock,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t mctx);\n}\n\n/*\n * Scan for slices that enclose the coordinate in the given dimension.\n *\n * Returns a dimension vector of slices that enclose the coordinate.\n */\nDimensionVec *\nts_dimension_slice_scan_limit(int32 dimension_id, int64 coordinate, int limit,\n\t\t\t\t\t\t\t  const ScanTupLock *slice_lock)\n{\n\tScanKeyData scankey[3];\n\tDimensionVec *slices = ts_dimension_vec_create(limit > 0 ? limit : DIMENSION_VEC_DEFAULT_SIZE);\n\n\tcoordinate = REMAP_LAST_COORDINATE(coordinate);\n\n\t/*\n\t * Perform an index scan for slices matching the dimension's ID and which\n\t * enclose the coordinate.\n\t */\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_dimension_slice_dimension_id_range_start_range_end_idx_dimension_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(dimension_id));\n\tScanKeyInit(&scankey[1],\n\t\t\t\tAnum_dimension_slice_dimension_id_range_start_range_end_idx_range_start,\n\t\t\t\tBTLessEqualStrategyNumber,\n\t\t\t\tF_INT8LE,\n\t\t\t\tInt64GetDatum(coordinate));\n\tScanKeyInit(&scankey[2],\n\t\t\t\tAnum_dimension_slice_dimension_id_range_start_range_end_idx_range_end,\n\t\t\t\tBTGreaterStrategyNumber,\n\t\t\t\tF_INT8GT,\n\t\t\t\tInt64GetDatum(coordinate));\n\n\tdimension_slice_scan_limit_internal(DIMENSION_SLICE_DIMENSION_ID_RANGE_START_RANGE_END_IDX,\n\t\t\t\t\t\t\t\t\t\tscankey,\n\t\t\t\t\t\t\t\t\t\t3,\n\t\t\t\t\t\t\t\t\t\tdimension_vec_tuple_found,\n\t\t\t\t\t\t\t\t\t\t(void *) &slices,\n\t\t\t\t\t\t\t\t\t\tlimit,\n\t\t\t\t\t\t\t\t\t\tAccessShareLock,\n\t\t\t\t\t\t\t\t\t\tslice_lock,\n\t\t\t\t\t\t\t\t\t\tCurrentMemoryContext);\n\n\treturn ts_dimension_vec_sort(&slices);\n}\n\nvoid\nts_dimension_slice_scan_list(int32 dimension_id, int64 coordinate, List **matching_dimension_slices,\n\t\t\t\t\t\t\t const ScanTupLock *slice_lock)\n{\n\tcoordinate = REMAP_LAST_COORDINATE(coordinate);\n\n\t/*\n\t * Perform an index scan for slices matching the dimension's ID and which\n\t * enclose the coordinate.\n\t */\n\tScanKeyData scankey[3];\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_dimension_slice_dimension_id_range_start_range_end_idx_dimension_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(dimension_id));\n\tScanKeyInit(&scankey[1],\n\t\t\t\tAnum_dimension_slice_dimension_id_range_start_range_end_idx_range_start,\n\t\t\t\tBTLessEqualStrategyNumber,\n\t\t\t\tF_INT8LE,\n\t\t\t\tInt64GetDatum(coordinate));\n\tScanKeyInit(&scankey[2],\n\t\t\t\tAnum_dimension_slice_dimension_id_range_start_range_end_idx_range_end,\n\t\t\t\tBTGreaterStrategyNumber,\n\t\t\t\tF_INT8GT,\n\t\t\t\tInt64GetDatum(coordinate));\n\n\tdimension_slice_scan_limit_internal(DIMENSION_SLICE_DIMENSION_ID_RANGE_START_RANGE_END_IDX,\n\t\t\t\t\t\t\t\t\t\tscankey,\n\t\t\t\t\t\t\t\t\t\t3,\n\t\t\t\t\t\t\t\t\t\tdimension_vec_tuple_found_list,\n\t\t\t\t\t\t\t\t\t\t(void *) matching_dimension_slices,\n\t\t\t\t\t\t\t\t\t\t/* limit = */ 0,\n\t\t\t\t\t\t\t\t\t\tAccessShareLock,\n\t\t\t\t\t\t\t\t\t\tslice_lock,\n\t\t\t\t\t\t\t\t\t\tCurrentMemoryContext);\n}\n\nint\nts_dimension_slice_scan_iterator_set_range(ScanIterator *it, int32 dimension_id,\n\t\t\t\t\t\t\t\t\t\t   StrategyNumber start_strategy, int64 start_value,\n\t\t\t\t\t\t\t\t\t\t   StrategyNumber end_strategy, int64 end_value)\n{\n\tCatalog *catalog = ts_catalog_get();\n\n\tit->ctx.index = catalog_get_index(catalog,\n\t\t\t\t\t\t\t\t\t  DIMENSION_SLICE,\n\t\t\t\t\t\t\t\t\t  DIMENSION_SLICE_DIMENSION_ID_RANGE_START_RANGE_END_IDX);\n\tts_scan_iterator_scan_key_reset(it);\n\tts_scan_iterator_scan_key_init(\n\t\tit,\n\t\tAnum_dimension_slice_dimension_id_range_start_range_end_idx_dimension_id,\n\t\tBTEqualStrategyNumber,\n\t\tF_INT4EQ,\n\t\tInt32GetDatum(dimension_id));\n\n\t/*\n\t * Perform an index scan for slices matching the dimension's ID and which\n\t * enclose the coordinate.\n\t */\n\tif (start_strategy != InvalidStrategy)\n\t{\n\t\tOid opno = get_opfamily_member(INTEGER_BTREE_FAM_OID, INT8OID, INT8OID, start_strategy);\n\t\tOid proc = get_opcode(opno);\n\n\t\tAssert(OidIsValid(proc));\n\n\t\tts_scan_iterator_scan_key_init(\n\t\t\tit,\n\t\t\tAnum_dimension_slice_dimension_id_range_start_range_end_idx_range_start,\n\t\t\tstart_strategy,\n\t\t\tproc,\n\t\t\tInt64GetDatum(start_value));\n\t}\n\tif (end_strategy != InvalidStrategy)\n\t{\n\t\tOid opno = get_opfamily_member(INTEGER_BTREE_FAM_OID, INT8OID, INT8OID, end_strategy);\n\t\tOid proc = get_opcode(opno);\n\n\t\tAssert(OidIsValid(proc));\n\n\t\t/*\n\t\t * range_end is stored as exclusive, so add 1 to the value being\n\t\t * searched. Also avoid overflow\n\t\t */\n\t\tif (end_value != PG_INT64_MAX)\n\t\t{\n\t\t\tend_value++;\n\n\t\t\t/*\n\t\t\t * If getting as input INT64_MAX-1, need to remap the incremented\n\t\t\t * value back to INT64_MAX-1\n\t\t\t */\n\t\t\tend_value = REMAP_LAST_COORDINATE(end_value);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/*\n\t\t\t * The point with INT64_MAX gets mapped to INT64_MAX-1 so\n\t\t\t * incrementing that gets you to INT_64MAX\n\t\t\t */\n\t\t\tend_value = PG_INT64_MAX;\n\t\t}\n\n\t\tts_scan_iterator_scan_key_init(\n\t\t\tit,\n\t\t\tAnum_dimension_slice_dimension_id_range_start_range_end_idx_range_end,\n\t\t\tend_strategy,\n\t\t\tproc,\n\t\t\tInt64GetDatum(end_value));\n\t}\n\n\treturn it->ctx.nkeys;\n}\n\n/*\n * Look for all dimension slices where (lower_bound, upper_bound) of the dimension_slice contains\n * the given (start_value, end_value) range\n *\n */\nDimensionVec *\nts_dimension_slice_scan_range_limit(int32 dimension_id, StrategyNumber start_strategy,\n\t\t\t\t\t\t\t\t\tint64 start_value, StrategyNumber end_strategy, int64 end_value,\n\t\t\t\t\t\t\t\t\tint limit, const ScanTupLock *tuplock)\n{\n\tDimensionVec *slices = ts_dimension_vec_create(limit > 0 ? limit : DIMENSION_VEC_DEFAULT_SIZE);\n\tScanIterator it = ts_dimension_slice_scan_iterator_create(tuplock, CurrentMemoryContext);\n\n\tts_dimension_slice_scan_iterator_set_range(&it,\n\t\t\t\t\t\t\t\t\t\t\t   dimension_id,\n\t\t\t\t\t\t\t\t\t\t\t   start_strategy,\n\t\t\t\t\t\t\t\t\t\t\t   start_value,\n\t\t\t\t\t\t\t\t\t\t\t   end_strategy,\n\t\t\t\t\t\t\t\t\t\t\t   end_value);\n\tit.ctx.limit = limit;\n\n\tts_scanner_foreach(&it)\n\t{\n\t\tconst TupleInfo *ti = ts_scan_iterator_tuple_info(&it);\n\t\tDimensionSlice *slice;\n\t\tMemoryContext old;\n\n\t\tswitch (ti->lockresult)\n\t\t{\n\t\t\tcase TM_SelfModified:\n\t\t\tcase TM_Ok:\n\t\t\t\told = MemoryContextSwitchTo(ti->mctx);\n\t\t\t\tslice = dimension_slice_from_slot(ti->slot);\n\t\t\t\tAssert(NULL != slice);\n\t\t\t\tslices = ts_dimension_vec_add_slice(&slices, slice);\n\t\t\t\tMemoryContextSwitchTo(old);\n\t\t\t\tbreak;\n\t\t\tcase TM_Deleted:\n\t\t\tcase TM_Updated:\n\t\t\t\t/* Treat as not found */\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\telog(ERROR, \"unexpected tuple lock status: %d\", ti->lockresult);\n\t\t\t\tpg_unreachable();\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tAssert(limit <= 0 || slices->num_slices <= limit);\n\tts_scan_iterator_close(&it);\n\n\treturn ts_dimension_vec_sort(&slices);\n}\n\n/*\n * Scan for slices that collide/overlap with the given range.\n *\n * Returns a dimension vector of colliding slices.\n */\nDimensionVec *\nts_dimension_slice_collision_scan_limit(int32 dimension_id, int64 range_start, int64 range_end,\n\t\t\t\t\t\t\t\t\t\tint limit)\n{\n\tScanKeyData scankey[3];\n\tDimensionVec *slices = ts_dimension_vec_create(limit > 0 ? limit : DIMENSION_VEC_DEFAULT_SIZE);\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_dimension_slice_dimension_id_range_start_range_end_idx_dimension_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(dimension_id));\n\tScanKeyInit(&scankey[1],\n\t\t\t\tAnum_dimension_slice_dimension_id_range_start_range_end_idx_range_start,\n\t\t\t\tBTLessStrategyNumber,\n\t\t\t\tF_INT8LT,\n\t\t\t\tInt64GetDatum(range_end));\n\tScanKeyInit(&scankey[2],\n\t\t\t\tAnum_dimension_slice_dimension_id_range_start_range_end_idx_range_end,\n\t\t\t\tBTGreaterStrategyNumber,\n\t\t\t\tF_INT8GT,\n\t\t\t\tInt64GetDatum(range_start));\n\n\tdimension_slice_scan_limit_internal(DIMENSION_SLICE_DIMENSION_ID_RANGE_START_RANGE_END_IDX,\n\t\t\t\t\t\t\t\t\t\tscankey,\n\t\t\t\t\t\t\t\t\t\t3,\n\t\t\t\t\t\t\t\t\t\tdimension_vec_tuple_found,\n\t\t\t\t\t\t\t\t\t\t(void *) &slices,\n\t\t\t\t\t\t\t\t\t\tlimit,\n\t\t\t\t\t\t\t\t\t\tAccessShareLock,\n\t\t\t\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\t\t\t\tCurrentMemoryContext);\n\n\treturn ts_dimension_vec_sort(&slices);\n}\n\nDimensionVec *\nts_dimension_slice_scan_by_dimension(int32 dimension_id, int limit)\n{\n\tScanKeyData scankey[1];\n\tDimensionVec *slices = ts_dimension_vec_create(limit > 0 ? limit : DIMENSION_VEC_DEFAULT_SIZE);\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_dimension_slice_dimension_id_range_start_range_end_idx_dimension_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(dimension_id));\n\n\tdimension_slice_scan_limit_internal(DIMENSION_SLICE_DIMENSION_ID_RANGE_START_RANGE_END_IDX,\n\t\t\t\t\t\t\t\t\t\tscankey,\n\t\t\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t\t\tdimension_vec_tuple_found,\n\t\t\t\t\t\t\t\t\t\t(void *) &slices,\n\t\t\t\t\t\t\t\t\t\tlimit,\n\t\t\t\t\t\t\t\t\t\tAccessShareLock,\n\t\t\t\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\t\t\t\tCurrentMemoryContext);\n\n\treturn ts_dimension_vec_sort(&slices);\n}\n\n/*\n * Return slices that occur \"before\" the given point.\n *\n * The slices will be allocated on the given memory context. Note, however, that\n * the returned dimension vector is allocated on the current memory context.\n */\nDimensionVec *\nts_dimension_slice_scan_by_dimension_before_point(int32 dimension_id, int64 point, int limit,\n\t\t\t\t\t\t\t\t\t\t\t\t  ScanDirection scandir, MemoryContext mctx)\n{\n\tScanKeyData scankey[3];\n\tDimensionVec *slices = ts_dimension_vec_create(limit > 0 ? limit : DIMENSION_VEC_DEFAULT_SIZE);\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_dimension_slice_dimension_id_range_start_range_end_idx_dimension_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(dimension_id));\n\tScanKeyInit(&scankey[1],\n\t\t\t\tAnum_dimension_slice_dimension_id_range_start_range_end_idx_range_start,\n\t\t\t\tBTLessStrategyNumber,\n\t\t\t\tF_INT8LT,\n\t\t\t\tInt64GetDatum(point));\n\tScanKeyInit(&scankey[2],\n\t\t\t\tAnum_dimension_slice_dimension_id_range_start_range_end_idx_range_end,\n\t\t\t\tBTLessStrategyNumber,\n\t\t\t\tF_INT8LT,\n\t\t\t\tInt64GetDatum(point));\n\n\tdimension_slice_scan_limit_direction_internal(\n\t\tDIMENSION_SLICE_DIMENSION_ID_RANGE_START_RANGE_END_IDX,\n\t\tscankey,\n\t\t3,\n\t\tdimension_vec_tuple_found,\n\t\t(void *) &slices,\n\t\tlimit,\n\t\tscandir,\n\t\tAccessShareLock,\n\t\tNULL,\n\t\tmctx);\n\n\treturn ts_dimension_vec_sort(&slices);\n}\n\nstatic ScanTupleResult\ndimension_slice_tuple_delete(TupleInfo *ti, void *data)\n{\n\tbool isnull;\n\tDatum dimension_slice_id = slot_getattr(ti->slot, Anum_dimension_slice_id, &isnull);\n\n\tif (ti->lockresult != TM_Ok)\n\t{\n\t\tif (IsolationUsesXactSnapshot())\n\t\t{\n\t\t\t/* For Repeatable Read and Serializable isolation level report error\n\t\t\t * if we cannot lock the tuple\n\t\t\t */\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),\n\t\t\t\t\t errmsg(\"could not serialize access due to concurrent update\")));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t\t errmsg(\"unable to lock hypertable catalog tuple, lock result is %d for \"\n\t\t\t\t\t\t\t\"hypertable \"\n\t\t\t\t\t\t\t\"ID (%d)\",\n\t\t\t\t\t\t\tti->lockresult,\n\t\t\t\t\t\t\tDatumGetInt32(dimension_slice_id))));\n\t\t}\n\t}\n\n\tbool *delete_constraints = data;\n\tCatalogSecurityContext sec_ctx;\n\n\tAssert(!isnull);\n\n\t/* delete chunk constraints */\n\tif (NULL != delete_constraints && *delete_constraints)\n\t\tts_chunk_constraint_delete_by_dimension_slice_id(DatumGetInt32(dimension_slice_id));\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_delete_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\tts_catalog_restore_user(&sec_ctx);\n\n\treturn SCAN_CONTINUE;\n}\n\nint\nts_dimension_slice_delete_by_dimension_id(int32 dimension_id, bool delete_constraints)\n{\n\tScanKeyData scankey[1];\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_dimension_slice_dimension_id_range_start_range_end_idx_dimension_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(dimension_id));\n\n\tScanTupLock scantuplock = {\n\t\t.waitpolicy = LockWaitBlock,\n\t\t.lockmode = LockTupleExclusive,\n\t};\n\n\treturn dimension_slice_scan_limit_internal(\n\t\tDIMENSION_SLICE_DIMENSION_ID_RANGE_START_RANGE_END_IDX,\n\t\tscankey,\n\t\t1,\n\t\tdimension_slice_tuple_delete,\n\t\t(void *) &delete_constraints,\n\t\t0,\n\t\tRowExclusiveLock,\n\t\t&scantuplock,\n\t\tCurrentMemoryContext);\n}\n\nint\nts_dimension_slice_delete_by_id(int32 dimension_slice_id, bool delete_constraints)\n{\n\tFormData_dimension_slice form;\n\tItemPointerData tid;\n\t/* lock the tuple entry in the catalog table */\n\tbool found = lock_dimension_slice_tuple(dimension_slice_id, &tid, &form);\n\tEnsure(found, \"dimension slice id %d not found\", dimension_slice_id);\n\n\tdimension_slice_delete_catalog_tuple(&tid);\n\treturn true;\n}\n\nstatic ScanTupleResult\ndimension_slice_fill(TupleInfo *ti, void *data)\n{\n\tswitch (ti->lockresult)\n\t{\n\t\tcase TM_SelfModified:\n\t\tcase TM_Ok:\n\t\t{\n\t\t\tDimensionSlice **slice = (DimensionSlice **) data;\n\t\t\tbool should_free;\n\t\t\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\n\t\t\tmemcpy(&(*slice)->fd, GETSTRUCT(tuple), sizeof(FormData_dimension_slice));\n\n\t\t\tif (should_free)\n\t\t\t\theap_freetuple(tuple);\n\t\t\tbreak;\n\t\t}\n\t\tcase TM_Deleted:\n\t\tcase TM_Updated:\n\t\t\t/* Same as not found */\n\t\t\tbreak;\n\t\tdefault:\n\t\t\telog(ERROR, \"unexpected tuple lock status: %d\", ti->lockresult);\n\t\t\tpg_unreachable();\n\t\t\tbreak;\n\t}\n\n\treturn SCAN_DONE;\n}\n\n/*\n * Scan for an existing slice that exactly matches the given slice's dimension\n * and range. If a match is found, the given slice is updated with slice ID\n * and the tuple is locked.\n *\n * Returns true if the dimension slice was found (and locked), false\n * otherwise.\n */\nbool\nts_dimension_slice_scan_for_existing(const DimensionSlice *slice, const ScanTupLock *tuplock)\n{\n\tScanKeyData scankey[3];\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_dimension_slice_dimension_id_range_start_range_end_idx_dimension_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(slice->fd.dimension_id));\n\tScanKeyInit(&scankey[1],\n\t\t\t\tAnum_dimension_slice_dimension_id_range_start_range_end_idx_range_start,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT8EQ,\n\t\t\t\tInt64GetDatum(slice->fd.range_start));\n\tScanKeyInit(&scankey[2],\n\t\t\t\tAnum_dimension_slice_dimension_id_range_start_range_end_idx_range_end,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT8EQ,\n\t\t\t\tInt64GetDatum(slice->fd.range_end));\n\n\treturn dimension_slice_scan_limit_internal(\n\t\tDIMENSION_SLICE_DIMENSION_ID_RANGE_START_RANGE_END_IDX,\n\t\tscankey,\n\t\t3,\n\t\tdimension_slice_fill,\n\t\t(void *) &slice,\n\t\t1,\n\t\tAccessShareLock,\n\t\ttuplock,\n\t\tCurrentMemoryContext);\n}\n\nDimensionSlice *\nts_dimension_slice_from_tuple(TupleInfo *ti)\n{\n\tDimensionSlice *slice;\n\tMemoryContext old;\n\n\tlock_result_ok_or_abort(ti);\n\told = MemoryContextSwitchTo(ti->mctx);\n\tslice = dimension_slice_from_slot(ti->slot);\n\tMemoryContextSwitchTo(old);\n\n\treturn slice;\n}\n\nstatic ScanTupleResult\ndimension_slice_tuple_found(TupleInfo *ti, void *data)\n{\n\tDimensionSlice **slice = (DimensionSlice **) data;\n\t*slice = ts_dimension_slice_from_tuple(ti);\n\treturn SCAN_DONE;\n}\n\n/* Scan for a slice by dimension slice id.\n *\n * If you're scanning for a tuple, you have to provide a lock, since, otherwise,\n * concurrent threads can do bad things with the tuple and you probably want\n * it to not change nor disappear. */\nDimensionSlice *\nts_dimension_slice_scan_by_id_and_lock(int32 dimension_slice_id, const ScanTupLock *tuplock,\n\t\t\t\t\t\t\t\t\t   MemoryContext mctx, LOCKMODE lockmode)\n{\n\tDimensionSlice *slice = NULL;\n\tScanKeyData scankey[1];\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_dimension_slice_id_idx_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(dimension_slice_id));\n\n\tdimension_slice_scan_limit_internal(DIMENSION_SLICE_ID_IDX,\n\t\t\t\t\t\t\t\t\t\tscankey,\n\t\t\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t\t\tdimension_slice_tuple_found,\n\t\t\t\t\t\t\t\t\t\t(void *) &slice,\n\t\t\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t\t\tlockmode,\n\t\t\t\t\t\t\t\t\t\ttuplock,\n\t\t\t\t\t\t\t\t\t\tmctx);\n\n\treturn slice;\n}\n\nScanIterator\nts_dimension_slice_scan_iterator_create(const ScanTupLock *tuplock, MemoryContext result_mcxt)\n{\n\tScanIterator it = ts_scan_iterator_create(DIMENSION_SLICE, AccessShareLock, result_mcxt);\n\tit.ctx.flags |= SCANNER_F_NOEND_AND_NOCLOSE;\n\tit.ctx.tuplock = RecoveryInProgress() ? NULL : tuplock;\n\n\treturn it;\n}\n\nvoid\nts_dimension_slice_scan_iterator_set_slice_id(ScanIterator *it, int32 slice_id)\n{\n\tit->ctx.index = catalog_get_index(ts_catalog_get(), DIMENSION_SLICE, DIMENSION_SLICE_ID_IDX);\n\tts_scan_iterator_scan_key_reset(it);\n\tts_scan_iterator_scan_key_init(it,\n\t\t\t\t\t\t\t\t   Anum_dimension_slice_id_idx_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(slice_id));\n}\n\nDimensionSlice *\nts_dimension_slice_scan_iterator_get_by_id(ScanIterator *it, int32 slice_id)\n{\n\tTupleInfo *ti;\n\n\tts_dimension_slice_scan_iterator_set_slice_id(it, slice_id);\n\tts_scan_iterator_start_or_restart_scan(it);\n\tti = ts_scan_iterator_next(it);\n\n\tif (!ti)\n\t\treturn NULL;\n\n\tDimensionSlice *slice = ts_dimension_slice_from_tuple(ti);\n\t/* There should be only one slice with the given id */\n\tAssert(ts_scan_iterator_next(it) == NULL);\n\n\treturn slice;\n}\n\nDimensionSlice *\nts_dimension_slice_copy(const DimensionSlice *original)\n{\n\tDimensionSlice *new = palloc(sizeof(DimensionSlice));\n\n\tAssert(original->storage == NULL);\n\tAssert(original->storage_free == NULL);\n\n\tmemcpy(new, original, sizeof(DimensionSlice));\n\treturn new;\n}\n\n/*\n * Check if two dimensions slices overlap by doing collision detection in one\n * dimension.\n *\n * Returns true if the slices collide, otherwise false.\n */\nbool\nts_dimension_slices_collide(const DimensionSlice *slice1, const DimensionSlice *slice2)\n{\n\tAssert(slice1->fd.dimension_id == slice2->fd.dimension_id);\n\n\treturn (slice1->fd.range_start < slice2->fd.range_end &&\n\t\t\tslice1->fd.range_end > slice2->fd.range_start);\n}\n\n/*\n * Check whether two slices are identical.\n *\n * We require by assertion that the slices are in the same dimension and we only\n * compare the ranges (i.e., the slice ID is not important for equality).\n *\n * Returns true if the slices have identical ranges, otherwise false.\n */\nbool\nts_dimension_slices_equal(const DimensionSlice *slice1, const DimensionSlice *slice2)\n{\n\tAssert(slice1->fd.dimension_id == slice2->fd.dimension_id);\n\n\treturn slice1->fd.range_start == slice2->fd.range_start &&\n\t\t   slice1->fd.range_end == slice2->fd.range_end;\n}\n\n/*-\n * Cut a slice that collides with another slice. The coordinate is the point of\n * insertion, and determines which end of the slice to cut.\n *\n * Case where we cut \"after\" the coordinate:\n *\n * ' [-x--------]\n * '      [--------]\n *\n * Case where we cut \"before\" the coordinate:\n *\n * '      [------x--]\n * ' [--------]\n *\n * Returns true if the slice was cut, otherwise false.\n */\nbool\nts_dimension_slice_cut(DimensionSlice *to_cut, const DimensionSlice *other, int64 coord)\n{\n\tAssert(to_cut->fd.dimension_id == other->fd.dimension_id);\n\n\tcoord = REMAP_LAST_COORDINATE(coord);\n\n\tif (other->fd.range_end <= coord && other->fd.range_end > to_cut->fd.range_start)\n\t{\n\t\t/* Cut \"before\" the coordinate */\n\t\tto_cut->fd.range_start = other->fd.range_end;\n\n\t\treturn true;\n\t}\n\telse if (other->fd.range_start > coord && other->fd.range_start < to_cut->fd.range_end)\n\t{\n\t\t/* Cut \"after\" the coordinate */\n\t\tto_cut->fd.range_end = other->fd.range_start;\n\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nvoid\nts_dimension_slice_free(DimensionSlice *slice)\n{\n\tif (slice->storage_free != NULL)\n\t\tslice->storage_free(slice->storage);\n\tpfree(slice);\n}\n\nstatic bool\ndimension_slice_insert_relation(const Relation rel, DimensionSlice *slice)\n{\n\tTupleDesc desc = RelationGetDescr(rel);\n\tDatum values[Natts_dimension_slice];\n\tbool nulls[Natts_dimension_slice] = { false };\n\tCatalogSecurityContext sec_ctx;\n\n\tif (slice->fd.id > 0)\n\t\t/* Slice already exists in table */\n\t\treturn false;\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tmemset(values, 0, sizeof(values));\n\tslice->fd.id = ts_catalog_table_next_seq_id(ts_catalog_get(), DIMENSION_SLICE);\n\tvalues[AttrNumberGetAttrOffset(Anum_dimension_slice_id)] = Int32GetDatum(slice->fd.id);\n\tvalues[AttrNumberGetAttrOffset(Anum_dimension_slice_dimension_id)] =\n\t\tInt32GetDatum(slice->fd.dimension_id);\n\tvalues[AttrNumberGetAttrOffset(Anum_dimension_slice_range_start)] =\n\t\tInt64GetDatum(slice->fd.range_start);\n\tvalues[AttrNumberGetAttrOffset(Anum_dimension_slice_range_end)] =\n\t\tInt64GetDatum(slice->fd.range_end);\n\n\tts_catalog_insert_values(rel, desc, values, nulls);\n\tts_catalog_restore_user(&sec_ctx);\n\n\treturn true;\n}\n\n/*\n * Insert slices into the catalog.\n *\n * Only slices that don't already exist in the catalog will be inserted. Note\n * that all slices that already exist (i.e., have a valid ID) MUST be locked\n * with a tuple lock (e.g., FOR KEY SHARE) prior to calling this function\n * since they won't be created. Otherwise it is not possible to guarantee that\n * all slices still exist once the transaction commits.\n *\n * Returns the number of slices inserted.\n */\nint\nts_dimension_slice_insert_multi(DimensionSlice **slices, Size num_slices)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tRelation rel;\n\tSize i, n = 0;\n\n\trel = table_open(catalog_get_table_id(catalog, DIMENSION_SLICE), RowExclusiveLock);\n\n\tfor (i = 0; i < num_slices; i++)\n\t{\n\t\tif (slices[i]->fd.id == 0)\n\t\t{\n\t\t\tdimension_slice_insert_relation(rel, slices[i]);\n\t\t\tn++;\n\t\t}\n\t}\n\n\ttable_close(rel, RowExclusiveLock);\n\n\treturn n;\n}\n\nvoid\nts_dimension_slice_insert(DimensionSlice *slice)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tRelation rel;\n\n\trel = table_open(catalog_get_table_id(catalog, DIMENSION_SLICE), RowExclusiveLock);\n\n\tdimension_slice_insert_relation(rel, slice);\n\n\t/* Keeping a row lock to prevent VACUUM or ALTER TABLE from running while working on the table.\n\t * This is known to cause issues in certain situations.\n\t */\n\ttable_close(rel, NoLock);\n}\n\nstatic ScanTupleResult\ndimension_slice_nth_tuple_found(TupleInfo *ti, void *data)\n{\n\tDimensionSlice **slice = (DimensionSlice **) data;\n\tMemoryContext old = MemoryContextSwitchTo(ti->mctx);\n\n\t*slice = dimension_slice_from_slot(ti->slot);\n\tMemoryContextSwitchTo(old);\n\treturn SCAN_CONTINUE;\n}\n\nDimensionSlice *\nts_dimension_slice_nth_latest_slice(int32 dimension_id, int n)\n{\n\tScanKeyData scankey[1];\n\tint num_tuples;\n\tDimensionSlice *ret = NULL;\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_dimension_slice_dimension_id_range_start_range_end_idx_dimension_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(dimension_id));\n\n\tnum_tuples = dimension_slice_scan_limit_direction_internal(\n\t\tDIMENSION_SLICE_DIMENSION_ID_RANGE_START_RANGE_END_IDX,\n\t\tscankey,\n\t\t1,\n\t\tdimension_slice_nth_tuple_found,\n\t\t(void *) &ret,\n\t\tn,\n\t\tBackwardScanDirection,\n\t\tAccessShareLock,\n\t\tNULL,\n\t\tCurrentMemoryContext);\n\tif (num_tuples < n)\n\t\treturn NULL;\n\n\treturn ret;\n}\n\nDimensionSlice *\nts_dimension_slice_nth_earliest_slice(int32 dimension_id, int n)\n{\n\tScanKeyData scankey[1];\n\tint num_tuples;\n\tDimensionSlice *ret = NULL;\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_dimension_slice_dimension_id_range_start_range_end_idx_dimension_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(dimension_id));\n\n\tnum_tuples = dimension_slice_scan_limit_direction_internal(\n\t\tDIMENSION_SLICE_DIMENSION_ID_RANGE_START_RANGE_END_IDX,\n\t\tscankey,\n\t\t1,\n\t\tdimension_slice_nth_tuple_found,\n\t\t(void *) &ret,\n\t\tn,\n\t\tForwardScanDirection,\n\t\tAccessShareLock,\n\t\tNULL,\n\t\tCurrentMemoryContext);\n\tif (num_tuples < n)\n\t\treturn NULL;\n\n\treturn ret;\n}\n\nint32\nts_dimension_slice_oldest_valid_chunk_for_reorder(int32 job_id, int32 dimension_id,\n\t\t\t\t\t\t\t\t\t\t\t\t  StrategyNumber start_strategy, int64 start_value,\n\t\t\t\t\t\t\t\t\t\t\t\t  StrategyNumber end_strategy, int64 end_value)\n{\n\tint32 result_chunk_id = -1;\n\tScanIterator it = ts_dimension_slice_scan_iterator_create(NULL, CurrentMemoryContext);\n\tbool done = false;\n\n\tts_dimension_slice_scan_iterator_set_range(&it,\n\t\t\t\t\t\t\t\t\t\t\t   dimension_id,\n\t\t\t\t\t\t\t\t\t\t\t   start_strategy,\n\t\t\t\t\t\t\t\t\t\t\t   start_value,\n\t\t\t\t\t\t\t\t\t\t\t   end_strategy,\n\t\t\t\t\t\t\t\t\t\t\t   end_value);\n\tts_scan_iterator_start_scan(&it);\n\n\twhile (!done)\n\t{\n\t\tconst TupleInfo *ti = ts_scan_iterator_next(&it);\n\t\tListCell *lc;\n\t\tDimensionSlice *slice;\n\t\tList *chunk_ids = NIL;\n\n\t\tif (NULL == ti)\n\t\t\tbreak;\n\n\t\tslice = dimension_slice_from_slot(ti->slot);\n\t\tts_chunk_constraint_scan_by_dimension_slice_to_list(slice,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t&chunk_ids,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tCurrentMemoryContext);\n\n\t\tforeach (lc, chunk_ids)\n\t\t{\n\t\t\t/* Look for a chunk that a) doesn't have a job stat (reorder ) and b) is not compressed\n\t\t\t * (should not reorder a compressed chunk) */\n\t\t\tint32 chunk_id = lfirst_int(lc);\n\t\t\tBgwPolicyChunkStats *chunk_stat = ts_bgw_policy_chunk_stats_find(job_id, chunk_id);\n\n\t\t\tif ((chunk_stat == NULL || chunk_stat->fd.num_times_job_run == 0) &&\n\t\t\t\tts_chunk_get_compression_status(chunk_id) == CHUNK_COMPRESS_NONE)\n\t\t\t{\n\t\t\t\t/* Save the chunk_id */\n\t\t\t\tresult_chunk_id = chunk_id;\n\t\t\t\tdone = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tts_scan_iterator_close(&it);\n\n\treturn result_chunk_id;\n}\n\nList *\nts_dimension_slice_get_chunkids_to_compress(int32 dimension_id, StrategyNumber start_strategy,\n\t\t\t\t\t\t\t\t\t\t\tint64 start_value, StrategyNumber end_strategy,\n\t\t\t\t\t\t\t\t\t\t\tint64 end_value, bool compress, bool recompress,\n\t\t\t\t\t\t\t\t\t\t\tint32 numchunks)\n{\n\tList *chunk_ids = NIL;\n\tint32 maxchunks = numchunks > 0 ? numchunks : -1;\n\tScanIterator it = ts_dimension_slice_scan_iterator_create(NULL, CurrentMemoryContext);\n\tbool done = false;\n\n\tts_dimension_slice_scan_iterator_set_range(&it,\n\t\t\t\t\t\t\t\t\t\t\t   dimension_id,\n\t\t\t\t\t\t\t\t\t\t\t   start_strategy,\n\t\t\t\t\t\t\t\t\t\t\t   start_value,\n\t\t\t\t\t\t\t\t\t\t\t   end_strategy,\n\t\t\t\t\t\t\t\t\t\t\t   end_value);\n\tts_scan_iterator_start_scan(&it);\n\n\twhile (!done)\n\t{\n\t\tDimensionSlice *slice;\n\t\tTupleInfo *ti;\n\t\tListCell *lc;\n\t\tList *slice_chunk_ids = NIL;\n\n\t\tti = ts_scan_iterator_next(&it);\n\n\t\tif (NULL == ti)\n\t\t\tbreak;\n\n\t\tslice = dimension_slice_from_slot(ti->slot);\n\t\tts_chunk_constraint_scan_by_dimension_slice_to_list(slice,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t&slice_chunk_ids,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tCurrentMemoryContext);\n\t\tforeach (lc, slice_chunk_ids)\n\t\t{\n\t\t\tint32 chunk_id = lfirst_int(lc);\n\t\t\tChunkCompressionStatus st = ts_chunk_get_compression_status(chunk_id);\n\n\t\t\tif ((compress && st == CHUNK_COMPRESS_NONE) ||\n\t\t\t\t(recompress && st == CHUNK_COMPRESS_UNORDERED))\n\t\t\t{\n\t\t\t\t/* found a chunk that is not compressed or needs recompress\n\t\t\t\t * caller needs to check the correct chunk status\n\t\t\t\t */\n\t\t\t\tchunk_ids = lappend_int(chunk_ids, chunk_id);\n\n\t\t\t\tif (maxchunks > 0 && list_length(chunk_ids) >= maxchunks)\n\t\t\t\t{\n\t\t\t\t\tdone = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tts_scan_iterator_close(&it);\n\n\treturn chunk_ids;\n}\n\n/* This function checks for overlap between the range we want to update\n for the OSM chunk and the chunks currently in timescaledb (not managed by OSM)\n */\nbool\nts_osm_chunk_range_overlaps(int32 osm_dimension_slice_id, int32 dimension_id, int64 range_start,\n\t\t\t\t\t\t\tint64 range_end)\n{\n\tbool res;\n\tDimensionVec *vec = dimension_slice_collision_scan(dimension_id, range_start, range_end);\n\t/* there is only one dimension slice for the OSM chunk. The OSM chunk may not\n\t * necessarily appear in the list of overlapping ranges because when first tiered,\n\t * it is given a range [max, infinity)\n\t */\n\tif (vec->num_slices >= 2 ||\n\t\t(vec->num_slices == 1 && vec->slices[0]->fd.id != osm_dimension_slice_id))\n\t\tres = true;\n\telse\n\t\tres = false;\n\tpfree(vec);\n\treturn res;\n}\n\nint\nts_dimension_slice_range_update(DimensionSlice *slice)\n{\n\tFormData_dimension_slice form;\n\tItemPointerData tid;\n\t/* lock the tuple entry in the catalog table */\n\tbool found = lock_dimension_slice_tuple(slice->fd.id, &tid, &form);\n\tEnsure(found, \"hypertable id %d not found\", slice->fd.id);\n\n\tif (form.range_start != slice->fd.range_start || form.range_end != slice->fd.range_end)\n\t{\n\t\tform.range_start = slice->fd.range_start;\n\t\tform.range_end = slice->fd.range_end;\n\t\tdimension_slice_update_catalog_tuple(&tid, &form);\n\t}\n\treturn true;\n}\n"
  },
  {
    "path": "src/dimension_slice.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <nodes/pg_list.h>\n\n#include \"chunk_constraint.h\"\n\n/* Put DIMENSION_SLICE_MAXVALUE point in same slice as DIMENSION_SLICE_MAXVALUE-1, always */\n/* This avoids the problem with coord < range_end where coord and range_end is an int64 */\n#define REMAP_LAST_COORDINATE(coord)                                                               \\\n\t(((coord) == DIMENSION_SLICE_MAXVALUE) ? DIMENSION_SLICE_MAXVALUE - 1 : (coord))\n\n#define DIMENSION_SLICE_MAXVALUE ((int64) PG_INT64_MAX)\n#define DIMENSION_SLICE_MINVALUE ((int64) PG_INT64_MIN)\n\n/* partition functions return int32 */\n#define DIMENSION_SLICE_CLOSED_MAX ((int64) PG_INT32_MAX)\n\n#define VALUE_GT(v1, v2) ((v1) > (v2))\n#define VALUE_LT(v1, v2) ((v1) < (v2))\n/*\n * Compare two values, returning -1, 1, 0 if the left one is, less, greater,\n * or equal to the right one, respectively.\n */\n#define VALUE_CMP(v1, v2) VALUE_GT(v1, v2) - VALUE_LT(v1, v2)\n\n/* Compare the range start values of two slices */\n#define DIMENSION_SLICE_RANGE_START_CMP(s1, s2)                                                    \\\n\tVALUE_CMP((s1)->fd.range_start, (s2)->fd.range_start)\n\n/* Compare the range end values of two slices */\n#define DIMENSION_SLICE_RANGE_END_CMP(s1, s2) VALUE_CMP((s1)->fd.range_end, (s2)->fd.range_end)\n\ntypedef struct DimensionSlice\n{\n\tFormData_dimension_slice fd;\n\tvoid (*storage_free)(void *);\n\tvoid *storage;\n} DimensionSlice;\n\ntypedef struct DimensionVec DimensionVec;\ntypedef struct Hypercube Hypercube;\n\nextern DimensionVec *ts_dimension_slice_scan_limit(int32 dimension_id, int64 coordinate, int limit,\n\t\t\t\t\t\t\t\t\t\t\t\t   const ScanTupLock *slice_lock);\n\nextern void ts_dimension_slice_scan_list(int32 dimension_id, int64 coordinate,\n\t\t\t\t\t\t\t\t\t\t List **matching_dimension_slices,\n\t\t\t\t\t\t\t\t\t\t const ScanTupLock *slice_lock);\n\nextern DimensionVec *\nts_dimension_slice_scan_range_limit(int32 dimension_id, StrategyNumber start_strategy,\n\t\t\t\t\t\t\t\t\tint64 start_value, StrategyNumber end_strategy, int64 end_value,\n\t\t\t\t\t\t\t\t\tint limit, const ScanTupLock *tuplock);\nextern DimensionVec *ts_dimension_slice_collision_scan_limit(int32 dimension_id, int64 range_start,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t int64 range_end, int limit);\nextern TSDLLEXPORT bool ts_dimension_slice_scan_for_existing(const DimensionSlice *slice,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t const ScanTupLock *tuplock);\nextern DimensionSlice *ts_dimension_slice_scan_by_id_and_lock(int32 dimension_slice_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  const ScanTupLock *tuplock,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  MemoryContext mctx,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  LOCKMODE lockmode);\nextern DimensionVec *ts_dimension_slice_scan_by_dimension(int32 dimension_id, int limit);\nextern DimensionVec *ts_dimension_slice_scan_by_dimension_before_point(int32 dimension_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   int64 point, int limit,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   ScanDirection scandir,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   MemoryContext mctx);\nextern int ts_dimension_slice_delete_by_dimension_id(int32 dimension_id, bool delete_constraints);\nextern TSDLLEXPORT int ts_dimension_slice_delete_by_id(int32 dimension_slice_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   bool delete_constraints);\nextern TSDLLEXPORT DimensionSlice *ts_dimension_slice_create(int dimension_id, int64 range_start,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t int64 range_end);\nextern TSDLLEXPORT DimensionSlice *ts_dimension_slice_copy(const DimensionSlice *original);\nextern TSDLLEXPORT bool ts_dimension_slices_collide(const DimensionSlice *slice1,\n\t\t\t\t\t\t\t\t\t\t\t\t\tconst DimensionSlice *slice2);\nextern TSDLLEXPORT bool ts_dimension_slices_equal(const DimensionSlice *slice1,\n\t\t\t\t\t\t\t\t\t\t\t\t  const DimensionSlice *slice2);\nextern bool ts_dimension_slice_cut(DimensionSlice *to_cut, const DimensionSlice *other,\n\t\t\t\t\t\t\t\t   int64 coord);\nextern void ts_dimension_slice_free(DimensionSlice *slice);\nextern int ts_dimension_slice_insert_multi(DimensionSlice **slice, Size num_slices);\nextern TSDLLEXPORT void ts_dimension_slice_insert(DimensionSlice *slice);\nextern int ts_dimension_slice_cmp(const DimensionSlice *left, const DimensionSlice *right);\nextern int ts_dimension_slice_cmp_coordinate(const DimensionSlice *slice, int64 coord);\n\nextern TSDLLEXPORT DimensionSlice *ts_dimension_slice_nth_latest_slice(int32 dimension_id, int n);\nextern TSDLLEXPORT DimensionSlice *ts_dimension_slice_nth_earliest_slice(int32 dimension_id, int n);\nextern TSDLLEXPORT int32 ts_dimension_slice_oldest_valid_chunk_for_reorder(\n\tint32 job_id, int32 dimension_id, StrategyNumber start_strategy, int64 start_value,\n\tStrategyNumber end_strategy, int64 end_value);\nextern TSDLLEXPORT List *ts_dimension_slice_get_chunkids_to_compress(\n\tint32 dimension_id, StrategyNumber start_strategy, int64 start_value,\n\tStrategyNumber end_strategy, int64 end_value, bool compress, bool recompress, int32 numchunks);\n\nextern DimensionSlice *ts_dimension_slice_from_tuple(TupleInfo *ti);\nextern ScanIterator ts_dimension_slice_scan_iterator_create(const ScanTupLock *tuplock,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tMemoryContext result_mcxt);\nextern void ts_dimension_slice_scan_iterator_set_slice_id(ScanIterator *it, int32 slice_id);\nextern DimensionSlice *ts_dimension_slice_scan_iterator_get_by_id(ScanIterator *it, int32 slice_id);\n\nextern int ts_dimension_slice_scan_iterator_set_range(ScanIterator *it, int32 dimension_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  StrategyNumber start_strategy,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  int64 start_value,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  StrategyNumber end_strategy, int64 end_value);\n\nextern bool ts_osm_chunk_range_overlaps(int32 osm_dimension_slice_id, int32 dimension_id,\n\t\t\t\t\t\t\t\t\t\tint64 range_start, int64 range_end);\n\nextern int ts_dimension_slice_range_update(DimensionSlice *slice);\n\n#define dimension_slice_insert(slice) ts_dimension_slice_insert_multi(&(slice), 1)\n\n#define dimension_slice_scan(dimension_id, coordinate, tuplock)                                    \\\n\tts_dimension_slice_scan_limit(dimension_id, coordinate, 0, tuplock)\n\n#define dimension_slice_collision_scan(dimension_id, range_start, range_end)                       \\\n\tts_dimension_slice_collision_scan_limit(dimension_id, range_start, range_end, 0)\n\nDimensionSlice *ts_chunk_get_osm_slice_and_lock(int32 osm_chunk_id, int32 time_dim_id,\n\t\t\t\t\t\t\t\t\t\t\t\tLockTupleMode tuplockmode, LOCKMODE tablelockmode);\n"
  },
  {
    "path": "src/dimension_vector.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include \"dimension_vector.h\"\n\nstatic int\ncmp_slices(const void *left, const void *right)\n{\n\tconst DimensionSlice *left_slice = *((DimensionSlice **) left);\n\tconst DimensionSlice *right_slice = *((DimensionSlice **) right);\n\n\treturn ts_dimension_slice_cmp(left_slice, right_slice);\n}\n\nstatic int\ncmp_coordinate_and_slice(const void *left, const void *right)\n{\n\tint64 coord = *((int64 *) left);\n\tconst DimensionSlice *slice = *((DimensionSlice **) right);\n\n\treturn ts_dimension_slice_cmp_coordinate(slice, coord);\n}\n\nstatic DimensionVec *\ndimension_vec_expand(DimensionVec *vec, int32 new_capacity)\n{\n\tif (vec != NULL && vec->capacity >= new_capacity)\n\t\treturn vec;\n\n\tif (NULL == vec)\n\t\tvec = palloc(DIMENSION_VEC_SIZE(new_capacity));\n\telse\n\t\tvec = repalloc(vec, DIMENSION_VEC_SIZE(new_capacity));\n\n\tvec->capacity = new_capacity;\n\n\treturn vec;\n}\n\nDimensionVec *\nts_dimension_vec_create(int32 initial_num_slices)\n{\n\tDimensionVec *vec = dimension_vec_expand(NULL, initial_num_slices);\n\n\tvec->capacity = initial_num_slices;\n\tvec->num_slices = 0;\n\n\treturn vec;\n}\n\nDimensionVec *\nts_dimension_vec_sort(DimensionVec **vecptr)\n{\n\tDimensionVec *vec = *vecptr;\n\n\tif (vec->num_slices > 1)\n\t\tqsort((void *) vec->slices, vec->num_slices, sizeof(DimensionSlice *), cmp_slices);\n\n\treturn vec;\n}\n\nDimensionVec *\nts_dimension_vec_add_slice(DimensionVec **vecptr, DimensionSlice *slice)\n{\n\tDimensionVec *vec = *vecptr;\n\n\t/* Ensure consistent dimension */\n\tAssert(vec->num_slices == 0 || vec->slices[0]->fd.dimension_id == slice->fd.dimension_id);\n\n\tif (vec->num_slices + 1 > vec->capacity)\n\t\t*vecptr = vec = dimension_vec_expand(vec, vec->capacity + 10);\n\n\tvec->slices[vec->num_slices++] = slice;\n\n\treturn vec;\n}\n\nDimensionVec *\nts_dimension_vec_add_unique_slice(DimensionVec **vecptr, DimensionSlice *slice)\n{\n\tDimensionVec *vec = *vecptr;\n\tint32 existing_slice_index = ts_dimension_vec_find_slice_index(vec, slice->fd.id);\n\n\tif (existing_slice_index == -1)\n\t\treturn ts_dimension_vec_add_slice(vecptr, slice);\n\n\treturn vec;\n}\n\nDimensionVec *\nts_dimension_vec_add_slice_sort(DimensionVec **vecptr, DimensionSlice *slice)\n{\n\t*vecptr = ts_dimension_vec_add_slice(vecptr, slice);\n\treturn ts_dimension_vec_sort(vecptr);\n}\n\nvoid\nts_dimension_vec_remove_slice(DimensionVec **vecptr, int32 index)\n{\n\tDimensionVec *vec = *vecptr;\n\n\tts_dimension_slice_free(vec->slices[index]);\n\tmemmove((void *) &vec->slices[index],\n\t\t\t(void *) &vec->slices[index + 1],\n\t\t\tsizeof(DimensionSlice *) * (vec->num_slices - index - 1));\n\tvec->num_slices--;\n}\n\n#if defined(USE_ASSERT_CHECKING)\nstatic inline bool\ndimension_vec_is_sorted(const DimensionVec *vec)\n{\n\tint i;\n\n\tif (vec->num_slices < 2)\n\t\treturn true;\n\n\tfor (i = 1; i < vec->num_slices; i++)\n\t\tif (cmp_slices((void *) &vec->slices[i - 1], (void *) &vec->slices[i]) > 0)\n\t\t\treturn false;\n\n\treturn true;\n}\n#endif\n\nDimensionSlice *\nts_dimension_vec_find_slice(const DimensionVec *vec, int64 coordinate)\n{\n\tDimensionSlice **res;\n\n\tif (vec->num_slices == 0)\n\t\treturn NULL;\n\n\tAssert(dimension_vec_is_sorted(vec));\n\n\tres = (DimensionSlice **) bsearch(&coordinate,\n\t\t\t\t\t\t\t\t\t  (void *) vec->slices,\n\t\t\t\t\t\t\t\t\t  vec->num_slices,\n\t\t\t\t\t\t\t\t\t  sizeof(DimensionSlice *),\n\t\t\t\t\t\t\t\t\t  cmp_coordinate_and_slice);\n\n\tif (res == NULL)\n\t\treturn NULL;\n\n\treturn *res;\n}\n\nint\nts_dimension_vec_find_slice_index(const DimensionVec *vec, int32 dimension_slice_id)\n{\n\tint i;\n\n\tfor (i = 0; i < vec->num_slices; i++)\n\t\tif (dimension_slice_id == vec->slices[i]->fd.id)\n\t\t\treturn i;\n\n\treturn -1;\n}\n\nconst DimensionSlice *\nts_dimension_vec_get(const DimensionVec *vec, int32 index)\n{\n\tif (index >= vec->num_slices)\n\t\treturn NULL;\n\n\treturn vec->slices[index];\n}\n\nvoid\nts_dimension_vec_free(DimensionVec *vec)\n{\n\tint i;\n\n\tfor (i = 0; i < vec->num_slices; i++)\n\t\tts_dimension_slice_free(vec->slices[i]);\n\tpfree(vec);\n}\n"
  },
  {
    "path": "src/dimension_vector.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include \"dimension_slice.h\"\n#include \"hypertable_restrict_info.h\"\n\n/*\n *\tDimensionVec is a collection of slices (ranges) along one dimension for a\n *\ttime range.\n */\ntypedef struct DimensionVec\n{\n\tint32 capacity;\t\t\t\t/* The capacity of the slices array */\n\tint32 num_slices;\t\t\t/* The current number of slices in slices\n\t\t\t\t\t\t\t\t * array */\n\tDimensionRestrictInfo *dri; /* corresponding restrictinfo */\n\tDimensionSlice *slices[FLEXIBLE_ARRAY_MEMBER];\n} DimensionVec;\n\n#define DIMENSION_VEC_SIZE(num_slices)                                                             \\\n\t(sizeof(DimensionVec) + (sizeof(DimensionSlice *) * num_slices))\n\n#define DIMENSION_VEC_DEFAULT_SIZE 10\n\nextern DimensionVec *ts_dimension_vec_create(int32 initial_num_slices);\nextern DimensionVec *ts_dimension_vec_sort(DimensionVec **vec);\nextern DimensionVec *ts_dimension_vec_add_slice_sort(DimensionVec **vec, DimensionSlice *slice);\nextern DimensionVec *ts_dimension_vec_add_slice(DimensionVec **vecptr, DimensionSlice *slice);\nextern DimensionVec *ts_dimension_vec_add_unique_slice(DimensionVec **vecptr,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   DimensionSlice *slice);\nextern void ts_dimension_vec_remove_slice(DimensionVec **vecptr, int32 index);\nextern DimensionSlice *ts_dimension_vec_find_slice(const DimensionVec *vec, int64 coordinate);\nextern int ts_dimension_vec_find_slice_index(const DimensionVec *vec, int32 dimension_slice_id);\nextern const DimensionSlice *ts_dimension_vec_get(const DimensionVec *vec, int32 index);\nextern void ts_dimension_vec_free(DimensionVec *vec);\n"
  },
  {
    "path": "src/error_utils.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#define GETARG_NOTNULL_OID(var, arg, name)                                                         \\\n\t{                                                                                              \\\n\t\tvar = PG_ARGISNULL(arg) ? InvalidOid : PG_GETARG_OID(arg);                                 \\\n\t\tif (!OidIsValid(var))                                                                      \\\n\t\t\tereport(ERROR,                                                                         \\\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),                                     \\\n\t\t\t\t\t errmsg(\"%s cannot be NULL\", name)));                                          \\\n\t}\n\n#define GETARG_NOTNULL_POINTER(var, arg, name, type)                                               \\\n\t{                                                                                              \\\n\t\tif (PG_ARGISNULL(arg))                                                                     \\\n\t\t\tereport(ERROR,                                                                         \\\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),                                     \\\n\t\t\t\t\t errmsg(\"%s cannot be NULL\", name)));                                          \\\n\t\tvar = (type *) PG_GETARG_POINTER(arg);                                                     \\\n\t}\n\n#define GETARG_NOTNULL_NULLABLE(var, arg, name, type)                                              \\\n\t{                                                                                              \\\n\t\tif (PG_ARGISNULL(arg))                                                                     \\\n\t\t\tereport(ERROR,                                                                         \\\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),                                     \\\n\t\t\t\t\t errmsg(\"%s cannot be NULL\", name)));                                          \\\n\t\tvar = PG_GETARG_##type(arg);                                                               \\\n\t}\n"
  },
  {
    "path": "src/errors.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n/* Defines error codes used\n-- PREFIX TS\n*/\n\n/*\n-- TS000 - GROUP: query errors\n-- TS001 - hypertable does not exist\n-- TS002 - column does not exist\n*/\n#define ERRCODE_TS_QUERY_ERRORS MAKE_SQLSTATE('T', 'S', '0', '0', '0')\n#define ERRCODE_TS_HYPERTABLE_NOT_EXIST MAKE_SQLSTATE('T', 'S', '0', '0', '1')\n#define ERRCODE_TS_DIMENSION_NOT_EXIST MAKE_SQLSTATE('T', 'S', '0', '0', '2')\n#define ERRCODE_TS_CHUNK_NOT_EXIST MAKE_SQLSTATE('T', 'S', '0', '0', '3')\n\n/*\n--TS100 - GROUP: DDL errors\n--TS101 - operation not supported\n--TS102 - bad hypertable definition\n--TS103 - bad hypertable index definition\n--TS110 - hypertable already exists\n--TS120 - node already exists\n--TS130 - user already exists\n--TS140 - tablespace already attached\n--TS150 - tablespace not attached\n--TS160 - duplicate dimension\n*/\n#define ERRCODE_TS_DDL_ERRORS MAKE_SQLSTATE('T', 'S', '1', '0', '0')\n#define ERRCODE_TS_OPERATION_NOT_SUPPORTED MAKE_SQLSTATE('T', 'S', '1', '0', '1')\n#define ERRCODE_TS_BAD_HYPERTABLE_DEFINITION MAKE_SQLSTATE('T', 'S', '1', '0', '2')\n#define ERRCODE_TS_BAD_HYPERTABLE_INDEX_DEFINITION MAKE_SQLSTATE('T', 'S', '1', '0', '3')\n#define ERRCODE_TS_HYPERTABLE_EXISTS MAKE_SQLSTATE('T', 'S', '1', '1', '0')\n#define ERRCODE_TS_NODE_EXISTS MAKE_SQLSTATE('T', 'S', '1', '2', '0')\n#define ERRCODE_TS_USER_EXISTS MAKE_SQLSTATE('T', 'S', '1', '3', '0')\n#define ERRCODE_TS_TABLESPACE_ALREADY_ATTACHED MAKE_SQLSTATE('T', 'S', '1', '4', '0')\n#define ERRCODE_TS_TABLESPACE_NOT_ATTACHED MAKE_SQLSTATE('T', 'S', '1', '5', '0')\n#define ERRCODE_TS_DUPLICATE_DIMENSION MAKE_SQLSTATE('T', 'S', '1', '6', '0')\n#define ERRCODE_TS_INSUFFICIENT_NUM_DATA_NODES MAKE_SQLSTATE('T', 'S', '1', '7', '0')\n\n/*\n--IO500 - GROUP: internal error\n--IO501 - unexpected state/event\n*/\n#define ERRCODE_TS_UNEXPECTED MAKE_SQLSTATE('T', 'S', '5', '0', '1')\n#define ERRCODE_TS_CHUNK_COLLISION MAKE_SQLSTATE('T', 'S', '5', '0', '3')\n"
  },
  {
    "path": "src/estimate.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <catalog/pg_type.h>\n#include <optimizer/optimizer.h>\n#include <parser/parse_oper.h>\n#include <utils/selfuncs.h>\n\n#include \"compat/compat.h\"\n#include \"estimate.h\"\n#include \"func_cache.h\"\n#include \"import/planner.h\"\n#include \"utils.h\"\n\n/*\n * This module contains functions for estimating, e.g., the number of groups\n * formed in various grouping expressions that involve time bucketing.\n */\n\nstatic double estimate_max_spread_expr(PlannerInfo *root, Expr *expr);\nstatic double group_estimate_opexpr(PlannerInfo *root, OpExpr *opexpr, double path_rows);\n\n/* Estimate the max spread on a time var in terms of the internal time representation.\n * Note that this will happen on the hypertable var in most cases. Therefore this is\n * a huge overestimate in many cases where there is a WHERE clause on time.\n */\nstatic double\nestimate_max_spread_var(PlannerInfo *root, Var *var)\n{\n\tVariableStatData vardata;\n\tOid ltop;\n\tDatum max_datum, min_datum;\n\tint64 max, min;\n\tbool valid;\n\n\texamine_variable(root, (Node *) var, 0, &vardata);\n\tget_sort_group_operators(var->vartype, true, false, false, &ltop, NULL, NULL, NULL);\n\tvalid = ts_get_variable_range(root, &vardata, ltop, &min_datum, &max_datum);\n\tReleaseVariableStats(vardata);\n\n\tif (!valid)\n\t\treturn INVALID_ESTIMATE;\n\n\tmax = ts_time_value_to_internal(max_datum, var->vartype);\n\tmin = ts_time_value_to_internal(min_datum, var->vartype);\n\n\treturn (double) (max - min);\n}\n\nstatic double\nestimate_max_spread_opexpr(PlannerInfo *root, OpExpr *opexpr)\n{\n\tchar *function_name = get_opname(opexpr->opno);\n\tExpr *left;\n\tExpr *right;\n\tExpr *nonconst;\n\n\tif (list_length(opexpr->args) != 2 || strlen(function_name) != 1)\n\t\treturn INVALID_ESTIMATE;\n\n\tleft = linitial(opexpr->args);\n\tright = lsecond(opexpr->args);\n\n\tif (IsA(left, Const))\n\t\tnonconst = right;\n\telse if (IsA(right, Const))\n\t\tnonconst = left;\n\telse\n\t\treturn INVALID_ESTIMATE;\n\n\t/* adding or subtracting a constant doesn't affect the range */\n\tif (function_name[0] == '-' || function_name[0] == '+')\n\t\treturn estimate_max_spread_expr(root, nonconst);\n\n\treturn INVALID_ESTIMATE;\n}\n\n/* estimate the max spread (max(value)-min(value)) of the expr */\nstatic double\nestimate_max_spread_expr(PlannerInfo *root, Expr *expr)\n{\n\tswitch (nodeTag(expr))\n\t{\n\t\tcase T_Var:\n\t\t\treturn estimate_max_spread_var(root, (Var *) expr);\n\t\tcase T_OpExpr:\n\t\t\treturn estimate_max_spread_opexpr(root, (OpExpr *) expr);\n\t\tdefault:\n\t\t\treturn INVALID_ESTIMATE;\n\t}\n}\n\n/*\n * Return an estimate for the number of groups formed when expr is divided\n * into intervals of size interval_period.\n */\ndouble\nts_estimate_group_expr_interval(PlannerInfo *root, Expr *expr, double interval_period)\n{\n\tdouble max_period;\n\n\tif (interval_period <= 0)\n\t\treturn INVALID_ESTIMATE;\n\n\tmax_period = estimate_max_spread_expr(root, expr);\n\tif (!IS_VALID_ESTIMATE(max_period))\n\t\treturn INVALID_ESTIMATE;\n\n\treturn clamp_row_est(max_period / interval_period);\n}\n\n/* if performing integer division number of groups is less than the spread divided by the divisor.\n * Note that this is an overestimate. */\nstatic double\ngroup_estimate_integer_division(PlannerInfo *root, Oid opno, Node *left, Node *right)\n{\n\tchar *function_name = get_opname(opno);\n\n\t/* only handle division */\n\tif (function_name[0] == '/' && function_name[1] == '\\0' && IsA(right, Const))\n\t{\n\t\tConst *c = (Const *) right;\n\n\t\tif (c->consttype != INT2OID && c->consttype != INT4OID && c->consttype != INT8OID)\n\t\t\treturn INVALID_ESTIMATE;\n\n\t\treturn ts_estimate_group_expr_interval(root, (Expr *) left, (double) c->constvalue);\n\t}\n\treturn INVALID_ESTIMATE;\n}\n\nstatic double\ngroup_estimate_funcexpr(PlannerInfo *root, FuncExpr *group_estimate_func, double path_rows)\n{\n\tFuncInfo *func_est = ts_func_cache_get_bucketing_func(group_estimate_func->funcid);\n\n\tif (func_est && func_est->group_estimate)\n\t\treturn func_est->group_estimate(root, group_estimate_func, path_rows);\n\treturn INVALID_ESTIMATE;\n}\n\n/* Get a custom estimate for the number of groups of an expression. Return INVALID_ESTIMATE if we\n * don't have any extra knowledge and should just use the default estimate */\nstatic double\ngroup_estimate_expr(PlannerInfo *root, Node *expr, double path_rows)\n{\n\tswitch (nodeTag(expr))\n\t{\n\t\tcase T_FuncExpr:\n\t\t\treturn group_estimate_funcexpr(root, (FuncExpr *) expr, path_rows);\n\t\tcase T_OpExpr:\n\t\t\treturn group_estimate_opexpr(root, (OpExpr *) expr, path_rows);\n\t\tdefault:\n\t\t\treturn INVALID_ESTIMATE;\n\t}\n}\n\nstatic double\ngroup_estimate_opexpr(PlannerInfo *root, OpExpr *opexpr, double path_rows)\n{\n\tNode *first;\n\tNode *second;\n\tdouble estimate;\n\n\tif (list_length(opexpr->args) != 2)\n\t\treturn INVALID_ESTIMATE;\n\n\tfirst = eval_const_expressions(root, linitial(opexpr->args));\n\tsecond = eval_const_expressions(root, lsecond(opexpr->args));\n\n\testimate = group_estimate_integer_division(root, opexpr->opno, first, second);\n\tif (IS_VALID_ESTIMATE(estimate))\n\t\treturn estimate;\n\n\tif (IsA(first, Const))\n\t\treturn group_estimate_expr(root, second, path_rows);\n\tif (IsA(second, Const))\n\t\treturn group_estimate_expr(root, first, path_rows);\n\treturn INVALID_ESTIMATE;\n}\n\n/*\n * Get a custom estimate for the number of groups in a query. Return\n * INVALID_ESTIMATE if we don't have any extra knowledge and should just use\n * the default estimate. This works by getting a custom estimate for any\n * groups where a custom estimate exists and multiplying that by the standard\n * estimate of the groups for which custom estimates don't exist.\n */\ndouble\nts_estimate_group(PlannerInfo *root, double path_rows)\n{\n\tQuery *parse = root->parse;\n\tdouble d_num_groups = 1;\n\tList *group_exprs;\n\tListCell *lc;\n\tbool found = false;\n\tList *new_group_expr = NIL;\n\n\tAssert(parse->groupClause && !parse->groupingSets);\n\n\tgroup_exprs = get_sortgrouplist_exprs(parse->groupClause, parse->targetList);\n\n\tforeach (lc, group_exprs)\n\t{\n\t\tNode *item = lfirst(lc);\n\t\tdouble estimate = group_estimate_expr(root, item, path_rows);\n\n\t\tif (IS_VALID_ESTIMATE(estimate))\n\t\t{\n\t\t\tfound = true;\n\t\t\td_num_groups *= estimate;\n\t\t}\n\t\telse\n\t\t\tnew_group_expr = lappend(new_group_expr, item);\n\t}\n\n\t/* nothing custom */\n\tif (!found)\n\t\treturn INVALID_ESTIMATE;\n\n\t/* multiply by default estimates */\n\tif (new_group_expr != NIL)\n\t\td_num_groups *= estimate_num_groups(root, new_group_expr, path_rows, NULL, NULL);\n\n\tif (d_num_groups > path_rows)\n\t\treturn INVALID_ESTIMATE;\n\n\treturn clamp_row_est(d_num_groups);\n}\n"
  },
  {
    "path": "src/estimate.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#define INVALID_ESTIMATE (-1)\n#define IS_VALID_ESTIMATE(est) ((est) >= 0)\n\nextern double ts_estimate_group_expr_interval(PlannerInfo *root, Expr *expr,\n\t\t\t\t\t\t\t\t\t\t\t  double interval_period);\nextern double ts_estimate_group(PlannerInfo *root, double path_rows);\n"
  },
  {
    "path": "src/event_trigger.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/htup_details.h>\n#include <catalog/pg_constraint.h>\n#include <catalog/pg_foreign_server.h>\n#include <catalog/pg_namespace.h>\n#include <catalog/pg_trigger.h>\n#include <commands/event_trigger.h>\n#include <executor/executor.h>\n#include <utils/array.h>\n#include <utils/builtins.h>\n\n#include \"compat/compat.h\"\n#include \"event_trigger.h\"\n\n#define DDL_INFO_NATTS 9\n#define DROPPED_OBJECTS_NATTS 12\n\n/* Function manager info for the event \"pg_event_trigger_ddl_commands\", which is\n * used to retrieve information on executed DDL commands in an event\n * trigger. The function manager info is initialized on extension load. */\nstatic FmgrInfo ddl_commands_fmgrinfo;\nstatic FmgrInfo dropped_objects_fmgrinfo;\n\n/*\n * Get a list of executed DDL commands in an event trigger.\n *\n * This function calls the function pg_ts_event_trigger_ddl_commands(), which is\n * part of the event trigger API, and retrieves the DDL commands executed in\n * relation to the event trigger. It is only valid to call this function from\n * within an event trigger.\n */\nList *\nts_event_trigger_ddl_commands(void)\n{\n\tReturnSetInfo rsinfo;\n\tLOCAL_FCINFO(fcinfo, 1);\n\tTupleTableSlot *slot;\n\tEState *estate = CreateExecutorState();\n\tList *objects = NIL;\n\n\tInitFunctionCallInfoData(*fcinfo, &ddl_commands_fmgrinfo, 1, InvalidOid, NULL, NULL);\n\tMemSet(&rsinfo, 0, sizeof(rsinfo));\n\trsinfo.type = T_ReturnSetInfo;\n\trsinfo.allowedModes = SFRM_Materialize;\n\trsinfo.econtext = CreateExprContext(estate);\n\tFC_SET_NULL(fcinfo, 0);\n\tfcinfo->resultinfo = (fmNodePtr) &rsinfo;\n\n\tFunctionCallInvoke(fcinfo);\n\n\tslot = MakeSingleTupleTableSlot(rsinfo.setDesc, &TTSOpsMinimalTuple);\n\n\twhile (tuplestore_gettupleslot(rsinfo.setResult, true, false, slot))\n\t{\n\t\tbool should_free;\n\t\tHeapTuple tuple = ExecFetchSlotHeapTuple(slot, false, &should_free);\n\t\tCollectedCommand *cmd;\n\t\tDatum values[DDL_INFO_NATTS];\n\t\tbool nulls[DDL_INFO_NATTS];\n\n\t\theap_deform_tuple(tuple, rsinfo.setDesc, values, nulls);\n\n\t\tif (should_free)\n\t\t\theap_freetuple(tuple);\n\n\t\tif (rsinfo.setDesc->natts > 8 && !nulls[8])\n\t\t{\n\t\t\tcmd = (CollectedCommand *) DatumGetPointer(values[8]);\n\t\t\tobjects = lappend(objects, cmd);\n\t\t}\n\t}\n\n\tExecDropSingleTupleTableSlot(slot);\n\tFreeExprContext(rsinfo.econtext, false);\n\tFreeExecutorState(estate);\n\n\treturn objects;\n}\n\n/* Given a TEXT[] of addrnames return a list of heap allocated char *\n *\n * similar to textarray_to_strvaluelist */\nstatic List *\nextract_addrnames(ArrayType *arr)\n{\n\tDatum *elems;\n\tbool *nulls;\n\tint nelems;\n\tList *list = NIL;\n\tint i;\n\n\tdeconstruct_array(arr, TEXTOID, -1, false, TYPALIGN_INT, &elems, &nulls, &nelems);\n\n\tfor (i = 0; i < nelems; i++)\n\t{\n\t\tif (nulls[i])\n\t\t\telog(ERROR, \"unexpected NULL in name list\");\n\n\t\t/* TextDatumGetCString heap allocates the string */\n\t\tlist = lappend(list, TextDatumGetCString(elems[i]));\n\t}\n\n\treturn list;\n}\n\nstatic EventTriggerDropTableConstraint *\nmake_event_trigger_drop_table_constraint(const char *constraint_name, const char *schema,\n\t\t\t\t\t\t\t\t\t\t const char *table)\n{\n\tEventTriggerDropTableConstraint *obj = palloc(sizeof(EventTriggerDropTableConstraint));\n\n\t*obj =\n\t\t(EventTriggerDropTableConstraint){\n\t\t.obj = {\n\t\t\t.type = EVENT_TRIGGER_DROP_TABLE_CONSTRAINT,\n\t\t},\n\t\t.constraint_name = constraint_name,\n\t\t.schema = schema,\n\t\t.table = table,\n\t};\n\n\treturn obj;\n}\n\nstatic EventTriggerDropRelation *\nmake_event_trigger_drop_index(const char *index_name, const char *schema)\n{\n\tEventTriggerDropRelation *obj = palloc(sizeof(EventTriggerDropRelation));\n\n\t*obj = (EventTriggerDropRelation){\n\t\t.obj = {\n\t\t\t.type = EVENT_TRIGGER_DROP_INDEX,\n\t\t},\n\t\t.name = index_name,\n\t\t.schema = schema,\n\t};\n\n\treturn obj;\n}\n\nstatic EventTriggerDropRelation *\nmake_event_trigger_drop_table(Oid relid, const char *table_name, const char *schema, char relkind)\n{\n\tEventTriggerDropRelation *obj = palloc(sizeof(EventTriggerDropRelation));\n\n\t*obj = (EventTriggerDropRelation){\n\t\t.obj = {\n\t\t\t.type = (relkind == RELKIND_RELATION) ? EVENT_TRIGGER_DROP_TABLE : EVENT_TRIGGER_DROP_FOREIGN_TABLE,\n\t\t},\n    .relid = relid,\n\t\t.name = table_name,\n\t\t.schema = schema,\n\t};\n\n\treturn obj;\n}\n\nstatic EventTriggerDropView *\nmake_event_trigger_drop_view(char *view_name, char *schema)\n{\n\tEventTriggerDropView *obj = palloc(sizeof(*obj));\n\n\t*obj = (EventTriggerDropView){\n\t\t.obj = { .type = EVENT_TRIGGER_DROP_VIEW },\n\t\t.view_name = view_name,\n\t\t.schema = schema,\n\t};\n\treturn obj;\n}\n\nstatic EventTriggerDropSchema *\nmake_event_trigger_drop_schema(const char *schema)\n{\n\tEventTriggerDropSchema *obj = palloc(sizeof(EventTriggerDropSchema));\n\n\t*obj = (EventTriggerDropSchema){\n\t\t.obj = {\n\t\t\t.type = EVENT_TRIGGER_DROP_SCHEMA,\n\t\t},\n\t\t.schema = schema,\n\t};\n\n\treturn obj;\n}\n\nstatic EventTriggerDropTrigger *\nmake_event_trigger_drop_trigger(const char *trigger_name, const char *schema, const char *table)\n{\n\tEventTriggerDropTrigger *obj = palloc(sizeof(EventTriggerDropTrigger));\n\n\t*obj = (EventTriggerDropTrigger){\n\t\t.obj = {\n\t\t\t.type = EVENT_TRIGGER_DROP_TRIGGER,\n\t\t},\n\t\t.trigger_name = trigger_name,\n\t\t.schema = schema,\n\t\t.table = table\n\t};\n\n\treturn obj;\n}\n\nstatic EventTriggerDropForeignServer *\nmake_event_trigger_drop_foreign_server(const char *server_name)\n{\n\tEventTriggerDropForeignServer *obj = palloc(sizeof(EventTriggerDropForeignServer));\n\n\t*obj = (EventTriggerDropForeignServer){\n\t\t.obj = {\n\t\t\t.type = EVENT_TRIGGER_DROP_FOREIGN_SERVER,\n\t\t},\n\t\t.servername = server_name,\n\t};\n\n\treturn obj;\n}\n\nList *\nts_event_trigger_dropped_objects(void)\n{\n\tReturnSetInfo rsinfo;\n\tLOCAL_FCINFO(fcinfo, 0);\n\tTupleTableSlot *slot;\n\tEState *estate = CreateExecutorState();\n\tList *objects = NIL;\n\n\tInitFunctionCallInfoData(*fcinfo, &dropped_objects_fmgrinfo, 0, InvalidOid, NULL, NULL);\n\tMemSet(&rsinfo, 0, sizeof(rsinfo));\n\trsinfo.type = T_ReturnSetInfo;\n\trsinfo.allowedModes = SFRM_Materialize;\n\trsinfo.econtext = CreateExprContext(estate);\n\tfcinfo->resultinfo = (fmNodePtr) &rsinfo;\n\n\tFunctionCallInvoke(fcinfo);\n\n\tslot = MakeSingleTupleTableSlot(rsinfo.setDesc, &TTSOpsMinimalTuple);\n\n\twhile (tuplestore_gettupleslot(rsinfo.setResult, true, false, slot))\n\t{\n\t\tbool should_free;\n\t\tHeapTuple tuple = ExecFetchSlotHeapTuple(slot, false, &should_free);\n\t\tDatum values[DROPPED_OBJECTS_NATTS];\n\t\tbool nulls[DROPPED_OBJECTS_NATTS];\n\t\tOid class_id;\n\t\tchar *objtype;\n\t\tList *addrnames = NIL;\n\t\tvoid *eventobj = NULL;\n\n\t\theap_deform_tuple(tuple, rsinfo.setDesc, values, nulls);\n\n\t\tclass_id = DatumGetObjectId(values[0]);\n\n\t\tswitch (class_id)\n\t\t{\n\t\t\tcase ConstraintRelationId:\n\t\t\t\tobjtype = TextDatumGetCString(values[6]);\n\t\t\t\tif (objtype != NULL && strcmp(objtype, \"table constraint\") == 0)\n\t\t\t\t{\n\t\t\t\t\taddrnames = extract_addrnames(DatumGetArrayTypeP(values[10]));\n\n\t\t\t\t\teventobj = make_event_trigger_drop_table_constraint(lthird(addrnames),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tlinitial(addrnames),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tlsecond(addrnames));\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase RelationRelationId:\n\t\t\t\tobjtype = TextDatumGetCString(values[6]);\n\n\t\t\t\tif (objtype == NULL)\n\t\t\t\t\tbreak;\n\n\t\t\t\taddrnames = extract_addrnames(DatumGetArrayTypeP(values[10]));\n\n\t\t\t\tif (strcmp(objtype, \"index\") == 0)\n\t\t\t\t\teventobj =\n\t\t\t\t\t\tmake_event_trigger_drop_index(lsecond(addrnames), linitial(addrnames));\n\t\t\t\telse if (strcmp(objtype, \"table\") == 0)\n\t\t\t\t{\n\t\t\t\t\teventobj = make_event_trigger_drop_table(DatumGetInt32(values[1]),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t lsecond(addrnames),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t linitial(addrnames),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t RELKIND_RELATION);\n\t\t\t\t}\n\t\t\t\telse if (strcmp(objtype, \"view\") == 0)\n\t\t\t\t{\n\t\t\t\t\tList *addrnames = extract_addrnames(DatumGetArrayTypeP(values[10]));\n\n\t\t\t\t\tobjects = lappend(objects,\n\t\t\t\t\t\t\t\t\t  make_event_trigger_drop_view(lsecond(addrnames),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   linitial(addrnames)));\n\t\t\t\t}\n\t\t\t\telse if (strcmp(objtype, \"foreign table\") == 0)\n\t\t\t\t\teventobj = make_event_trigger_drop_table(DatumGetInt32(values[1]),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t lsecond(addrnames),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t linitial(addrnames),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t RELKIND_FOREIGN_TABLE);\n\t\t\t\tbreak;\n\t\t\tcase NamespaceRelationId:\n\t\t\t\taddrnames = extract_addrnames(DatumGetArrayTypeP(values[10]));\n\t\t\t\teventobj = make_event_trigger_drop_schema(linitial(addrnames));\n\t\t\t\tbreak;\n\t\t\tcase TriggerRelationId:\n\t\t\t\taddrnames = extract_addrnames(DatumGetArrayTypeP(values[10]));\n\t\t\t\teventobj = make_event_trigger_drop_trigger(lthird(addrnames),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   linitial(addrnames),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   lsecond(addrnames));\n\t\t\t\tbreak;\n\t\t\tcase ForeignServerRelationId:\n\t\t\t\taddrnames = extract_addrnames(DatumGetArrayTypeP(values[10]));\n\t\t\t\teventobj = make_event_trigger_drop_foreign_server(linitial(addrnames));\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif (NULL != eventobj)\n\t\t\tobjects = lappend(objects, eventobj);\n\n\t\tif (should_free)\n\t\t\theap_freetuple(tuple);\n\t}\n\n\tExecDropSingleTupleTableSlot(slot);\n\tFreeExprContext(rsinfo.econtext, false);\n\tFreeExecutorState(estate);\n\n\treturn objects;\n}\n\nvoid\n_event_trigger_init(void)\n{\n\tfmgr_info(fmgr_internal_function(\"pg_event_trigger_ddl_commands\"), &ddl_commands_fmgrinfo);\n\tfmgr_info(fmgr_internal_function(\"pg_event_trigger_dropped_objects\"),\n\t\t\t  &dropped_objects_fmgrinfo);\n}\n\nvoid\n_event_trigger_fini(void)\n{\n\t/* Nothing to do */\n}\n"
  },
  {
    "path": "src/event_trigger.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <nodes/pg_list.h>\n\ntypedef enum EventTriggerDropType\n{\n\tEVENT_TRIGGER_DROP_TABLE_CONSTRAINT,\n\tEVENT_TRIGGER_DROP_INDEX,\n\tEVENT_TRIGGER_DROP_TABLE,\n\tEVENT_TRIGGER_DROP_VIEW,\n\tEVENT_TRIGGER_DROP_FOREIGN_TABLE,\n\tEVENT_TRIGGER_DROP_SCHEMA,\n\tEVENT_TRIGGER_DROP_TRIGGER,\n\tEVENT_TRIGGER_DROP_FOREIGN_SERVER,\n} EventTriggerDropType;\n\ntypedef struct EventTriggerDropObject\n{\n\tEventTriggerDropType type;\n} EventTriggerDropObject;\n\ntypedef struct EventTriggerDropTableConstraint\n{\n\tEventTriggerDropObject obj;\n\tconst char *constraint_name;\n\tconst char *schema;\n\tconst char *table;\n} EventTriggerDropTableConstraint;\n\ntypedef struct EventTriggerDropRelation\n{\n\tEventTriggerDropObject obj;\n\tOid relid;\n\tconst char *name;\n\tconst char *schema;\n} EventTriggerDropRelation;\n\ntypedef struct EventTriggerDropView\n{\n\tEventTriggerDropObject obj;\n\tchar *view_name;\n\tchar *schema;\n} EventTriggerDropView;\n\ntypedef struct EventTriggerDropSchema\n{\n\tEventTriggerDropObject obj;\n\tconst char *schema;\n} EventTriggerDropSchema;\n\ntypedef struct EventTriggerDropTrigger\n{\n\tEventTriggerDropObject obj;\n\tconst char *trigger_name;\n\tconst char *schema;\n\tconst char *table;\n} EventTriggerDropTrigger;\n\ntypedef struct EventTriggerDropForeignServer\n{\n\tEventTriggerDropObject obj;\n\tconst char *servername;\n} EventTriggerDropForeignServer;\n\nextern List *ts_event_trigger_dropped_objects(void);\nextern List *ts_event_trigger_ddl_commands(void);\nextern void _event_trigger_init(void);\nextern void _event_trigger_fini(void);\n"
  },
  {
    "path": "src/export.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include \"config.h\"\n\n/* Definitions for symbol exports */\n\n#if defined(_WIN32) && !defined(WIN32)\n#define WIN32\n#endif\n\n/*\n * PGDLLEXPORT is defined as en empty macro until PG15.\n * Since PG16, a macro HAVE_VISIBILITY_ATTRIBUTE is defined if the compiler has\n * support for visibility attribute and the PGDLLEXPORT macro is defined as the\n * same. So, skip redefining PGDLLEXPORT if HAVE_VISIBILITY_ATTRIBUTE is defined.\n * If not, undef the empty PGDLLEXPORT macro and redefine it properly.\n */\n#if !defined(WIN32) && !defined(__CYGWIN__) && !defined(HAVE_VISIBILITY_ATTRIBUTE)\n#if __GNUC__ >= 4\n/* PGDLLEXPORT is defined but will be empty. Redefine it. */\n#undef PGDLLEXPORT\n#define PGDLLEXPORT __attribute__((visibility(\"default\")))\n#else\n#error \"Unsupported GNUC version\"\n#endif /* __GNUC__ */\n#endif\n\n/*\n * On windows, symbols shared across modules have to be marked \"export\" in the\n * main TimescaleDb module and \"import\" in the submodule. Since we want to use the\n * same headers, we TSDLLEXPORT functions as \"export\" in the main module and\n * \"import\" in submodules.\n */\n#ifndef TS_SUBMODULE\n/* In the core timescaledb TSDLLEXPORT is export */\n#define TSDLLEXPORT PGDLLEXPORT\n\n#elif defined(PGDLLIMPORT)\n/* In submodules it works as imports */\n#define TSDLLEXPORT PGDLLIMPORT\n\n#else\n/* If there is no IMPORT defined, it's a nop */\n#define TSDLLEXPORT\n\n#endif\n\n#define TS_FUNCTION_INFO_V1(fn)                                                                    \\\n\tPGDLLEXPORT Datum fn(PG_FUNCTION_ARGS);                                     \\\n\tPG_FUNCTION_INFO_V1(fn)\n"
  },
  {
    "path": "src/expression_utils.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <catalog/pg_type.h>\n#include <nodes/extensible.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodeFuncs.h>\n#include <nodes/plannodes.h>\n#include <nodes/primnodes.h>\n#include <utils/lsyscache.h>\n\n#include \"debug_assert.h\"\n#include \"export.h\"\n#include \"expression_utils.h\"\n\n/*\n * This function is meant to extract the expression components to be used in a ScanKey.\n *\n * It will work on the following expression types:\n * - Var OP Expr\n *\n * Var OP Var is not supported as that will not work with scankeys.\n *\n */\nbool TSDLLEXPORT\nts_extract_expr_args(Expr *expr, Var **var, Expr **arg_value, Oid *opno, Oid *opcode)\n{\n\tList *args;\n\tOid expr_opno, expr_opcode;\n\n\tswitch (nodeTag(expr))\n\t{\n\t\tcase T_OpExpr:\n\t\t{\n\t\t\tOpExpr *opexpr = castNode(OpExpr, expr);\n\t\t\targs = opexpr->args;\n\t\t\texpr_opno = opexpr->opno;\n\t\t\texpr_opcode = opexpr->opfuncid;\n\n\t\t\tif (opexpr->opresulttype != BOOLOID)\n\t\t\t\treturn false;\n\n\t\t\tbreak;\n\t\t}\n\t\tcase T_ScalarArrayOpExpr:\n\t\t{\n\t\t\tScalarArrayOpExpr *sa_opexpr = castNode(ScalarArrayOpExpr, expr);\n\t\t\targs = sa_opexpr->args;\n\t\t\texpr_opno = sa_opexpr->opno;\n\t\t\texpr_opcode = sa_opexpr->opfuncid;\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\treturn false;\n\t}\n\n\tif (list_length(args) != 2)\n\t\treturn false;\n\n\tExpr *leftop = linitial(args);\n\tExpr *rightop = lsecond(args);\n\n\tif (IsA(leftop, RelabelType))\n\t\tleftop = castNode(RelabelType, leftop)->arg;\n\tif (IsA(rightop, RelabelType))\n\t\trightop = castNode(RelabelType, rightop)->arg;\n\n\tif (IsA(leftop, Var) && !IsA(rightop, Var))\n\t{\n\t\t/* ignore system columns */\n\t\tif (castNode(Var, leftop)->varattno <= 0)\n\t\t\treturn false;\n\n\t\t*var = castNode(Var, leftop);\n\n\t\t*arg_value = rightop;\n\t\t*opno = expr_opno;\n\t\tif (opcode)\n\t\t\t*opcode = expr_opcode;\n\t\treturn true;\n\t}\n\telse if (IsA(rightop, Var) && !IsA(leftop, Var))\n\t{\n\t\t/* ignore system columns */\n\t\tif (castNode(Var, rightop)->varattno <= 0)\n\t\t\treturn false;\n\n\t\t*var = castNode(Var, rightop);\n\t\t*arg_value = leftop;\n\t\texpr_opno = get_commutator(expr_opno);\n\t\tif (!OidIsValid(expr_opno))\n\t\t\treturn false;\n\n\t\tif (opcode)\n\t\t{\n\t\t\texpr_opcode = get_opcode(expr_opno);\n\t\t\tif (!OidIsValid(expr_opcode))\n\t\t\t\treturn false;\n\t\t\t*opcode = expr_opcode;\n\t\t}\n\n\t\t*opno = expr_opno;\n\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n/*\n * Build an output targetlist for a custom node that just references all the\n * custom scan targetlist entries.\n */\nList *\nts_build_trivial_custom_output_targetlist(List *scan_targetlist)\n{\n\tList *result = NIL;\n\n\tListCell *lc;\n\tforeach (lc, scan_targetlist)\n\t{\n\t\tTargetEntry *scan_entry = (TargetEntry *) lfirst(lc);\n\n\t\tVar *var = makeVar(INDEX_VAR,\n\t\t\t\t\t\t   scan_entry->resno,\n\t\t\t\t\t\t   exprType((Node *) scan_entry->expr),\n\t\t\t\t\t\t   exprTypmod((Node *) scan_entry->expr),\n\t\t\t\t\t\t   exprCollation((Node *) scan_entry->expr),\n\t\t\t\t\t\t   /* varlevelsup = */ 0);\n\n\t\tTargetEntry *output_entry = makeTargetEntry((Expr *) var,\n\t\t\t\t\t\t\t\t\t\t\t\t\tscan_entry->resno,\n\t\t\t\t\t\t\t\t\t\t\t\t\tscan_entry->resname,\n\t\t\t\t\t\t\t\t\t\t\t\t\tscan_entry->resjunk);\n\n\t\tresult = lappend(result, output_entry);\n\t}\n\n\treturn result;\n}\n\nstatic Node *\nresolve_outer_special_vars_mutator(Node *node, void *context)\n{\n\tif (node == NULL)\n\t{\n\t\treturn NULL;\n\t}\n\n\tif (!IsA(node, Var))\n\t{\n\t\treturn expression_tree_mutator(node, resolve_outer_special_vars_mutator, context);\n\t}\n\n\tVar *var = castNode(Var, node);\n\tCustomScan *custom = castNode(CustomScan, context);\n\tif ((Index) var->varno == (Index) custom->scan.scanrelid)\n\t{\n\t\t/*\n\t\t * This is already the uncompressed chunk var. We can see it referenced\n\t\t * by expressions in the output targetlist of the child scan node.\n\t\t */\n\t\treturn (Node *) copyObject(var);\n\t}\n\n\tif (var->varno == OUTER_VAR)\n\t{\n\t\t/*\n\t\t * Reference into the output targetlist of the child scan node.\n\t\t */\n\t\tTargetEntry *columnar_scan_tentry =\n\t\t\tcastNode(TargetEntry, list_nth(custom->scan.plan.targetlist, var->varattno - 1));\n\n\t\treturn resolve_outer_special_vars_mutator((Node *) columnar_scan_tentry->expr, context);\n\t}\n\n\tif (var->varno == INDEX_VAR)\n\t{\n\t\t/*\n\t\t * This is a reference into the custom scan targetlist, we have to resolve\n\t\t * it as well.\n\t\t */\n\t\tvar = castNode(Var,\n\t\t\t\t\t   castNode(TargetEntry, list_nth(custom->custom_scan_tlist, var->varattno - 1))\n\t\t\t\t\t\t   ->expr);\n\t\tAssert(var->varno > 0);\n\n\t\treturn (Node *) copyObject(var);\n\t}\n\n\tEnsure(false, \"encountered unexpected varno %d as an aggregate argument\", var->varno);\n\treturn node;\n}\n\n/*\n * Walk a plan tree recursively descending into child plans.\n * At each leaf, call the user-supplied callback which may replace the node.\n */\nPlan *\nts_plan_tree_walker(Plan *plan, ts_plan_tree_walkerfunc func, void *context)\n{\n\tif (!plan)\n\t\treturn NULL;\n\n\tif (IsA(plan, List))\n\t{\n\t\tListCell *lc;\n\t\tforeach (lc, castNode(List, plan))\n\t\t{\n\t\t\tlfirst(lc) = ts_plan_tree_walker(lfirst(lc), func, context);\n\t\t}\n\t\treturn plan;\n\t}\n\n\tif (plan->lefttree)\n\t\tplan->lefttree = ts_plan_tree_walker(plan->lefttree, func, context);\n\tif (plan->righttree)\n\t\tplan->righttree = ts_plan_tree_walker(plan->righttree, func, context);\n\n\tif (IsA(plan, Append))\n\t{\n\t\tAppend *append = castNode(Append, plan);\n\t\tappend->appendplans =\n\t\t\t(List *) ts_plan_tree_walker((Plan *) append->appendplans, func, context);\n\t}\n\telse if (IsA(plan, MergeAppend))\n\t{\n\t\tMergeAppend *append = castNode(MergeAppend, plan);\n\t\tappend->mergeplans =\n\t\t\t(List *) ts_plan_tree_walker((Plan *) append->mergeplans, func, context);\n\t}\n\telse if (IsA(plan, CustomScan))\n\t{\n\t\tCustomScan *custom = castNode(CustomScan, plan);\n\t\tcustom->custom_plans =\n\t\t\t(List *) ts_plan_tree_walker((Plan *) custom->custom_plans, func, context);\n\t}\n\tif (IsA(plan, SubqueryScan))\n\t{\n\t\tSubqueryScan *subquery = castNode(SubqueryScan, plan);\n\t\tsubquery->subplan = ts_plan_tree_walker(subquery->subplan, func, context);\n\t\treturn plan;\n\t}\n\n\treturn func(plan, context);\n}\n\n/*\n * Resolve the OUTER_VAR special variables, that are used in the output\n * targetlists of aggregation nodes, replacing them with the uncompressed chunk\n * variables.\n */\nNode *\nts_resolve_outer_special_vars(Node *node, Plan *childplan)\n{\n\treturn resolve_outer_special_vars_mutator(node, childplan);\n}\n"
  },
  {
    "path": "src/expression_utils.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <nodes/primnodes.h>\n#include <utils/lsyscache.h>\n\n#include \"export.h\"\n\nbool TSDLLEXPORT ts_extract_expr_args(Expr *expr, Var **var, Expr **arg_value, Oid *opno,\n\t\t\t\t\t\t\t\t\t  Oid *opcode);\n\nTSDLLEXPORT List *ts_build_trivial_custom_output_targetlist(List *scan_targetlist);\nTSDLLEXPORT Node *ts_resolve_outer_special_vars(Node *node, Plan *childplan);\n\ntypedef Plan *(*ts_plan_tree_walkerfunc)(Plan *, void *);\nextern TSDLLEXPORT Plan *ts_plan_tree_walker(Plan *plan, ts_plan_tree_walkerfunc func,\n\t\t\t\t\t\t\t\t\t\t\t void *context);\n"
  },
  {
    "path": "src/extension.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/transam.h>\n#include <access/xact.h>\n#include <catalog/namespace.h>\n#include <catalog/objectaccess.h>\n#include <commands/event_trigger.h>\n#include <fmgr.h>\n#include <utils/inval.h>\n#include <utils/lsyscache.h>\n\n#if PG_VERSION_NUM < 150000\n/*\n * Some externs are mislabeled when building on Windows so we try to fix them\n * with this hack. This is only needed for versions < 15.\n */\n#include \"compat/compat-msvc-enter.h\"\n#include <commands/extension.h>\n#include <miscadmin.h>\n#include \"compat/compat-msvc-exit.h\"\n#endif\n\n#include <access/relscan.h>\n#include <catalog/indexing.h>\n#include <catalog/pg_extension.h>\n#include <utils/builtins.h>\n#include <utils/fmgroids.h>\n\n#include \"compat/compat.h\"\n#include \"extension.h\"\n#include \"extension_utils.c\"\n#include \"guc.h\"\n#include \"ts_catalog/catalog.h\"\n\n#define TS_UPDATE_SCRIPT_CONFIG_VAR MAKE_EXTOPTION(\"update_script_stage\")\n#define POST_UPDATE \"post\"\n/*\n * The name of the experimental schema.\n *\n * Call ts_extension_schema_name() or ts_experimental_schema_name() for\n * consistency. Don't use this macro directly.\n */\n#define TS_EXPERIMENTAL_SCHEMA_NAME \"timescaledb_experimental\"\nstatic Oid extension_proxy_oid = InvalidOid;\n\n/*\n * ExtensionState tracks the state of extension metadata in the backend.\n *\n * Since we want to cache extension metadata to speed up common checks (e.g.,\n * check for presence of the extension itself), we also need to track the\n * extension state to know when the metadata is valid.\n *\n * We use a proxy_table to be notified of extension drops/creates. Namely,\n * we rely on the fact that postgres will internally create RelCacheInvalidation\n * events when any tables are created or dropped. We rely on the following properties\n * of Postgres's dependency management:\n *\t* The proxy table will be created before the extension itself.\n *\t* The proxy table will be dropped before the extension itself.\n */\n\nstatic enum ExtensionState extstate = EXTENSION_STATE_UNKNOWN;\n\n/*\n * Looking up the extension oid is a catalog lookup that can be costly, and we\n * often need it during the planning, so we cache it here. We update it when\n * the extension status is updated.\n */\nstatic Oid ts_extension_oid = InvalidOid;\n\nstatic const char *extstate_str[] = {\n\t[EXTENSION_STATE_UNKNOWN] = \"unknown\",\n\t[EXTENSION_STATE_TRANSITIONING] = \"transitioning\",\n\t[EXTENSION_STATE_CREATED] = \"created\",\n\t[EXTENSION_STATE_NOT_INSTALLED] = \"not installed\",\n};\n\nstatic bool\nextension_loader_present()\n{\n\tvoid **presentptr = find_rendezvous_variable(RENDEZVOUS_LOADER_PRESENT_NAME);\n\n\treturn (*presentptr != NULL && *((bool *) *presentptr));\n}\n\nvoid\nts_extension_check_version(const char *so_version)\n{\n\tchar *sql_version;\n\n\tif (!IsNormalProcessingMode() || !IsTransactionState() || !extension_exists(EXTENSION_NAME))\n\t\treturn;\n\tsql_version = extension_version(EXTENSION_NAME);\n\n\tif (strcmp(sql_version, so_version) != 0)\n\t{\n\t\t/*\n\t\t * Throw a FATAL error here so that clients will be forced to reconnect\n\t\t * when they have the wrong extension version loaded.\n\t\t */\n\t\tereport(FATAL,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"extension \\\"%s\\\" version mismatch: shared library version %s; SQL version \"\n\t\t\t\t\t\t\"%s\",\n\t\t\t\t\t\tEXTENSION_NAME,\n\t\t\t\t\t\tso_version,\n\t\t\t\t\t\tsql_version)));\n\t}\n\n\tif (!process_shared_preload_libraries_in_progress && !extension_loader_present())\n\t{\n\t\textension_load_without_preload();\n\t}\n}\n\nvoid\nts_extension_check_server_version()\n{\n\t/*\n\t * This is a load-time check for the correct server version since the\n\t * extension may be distributed as a binary\n\t */\n\tchar *server_version_num_guc = GetConfigOptionByName(\"server_version_num\", NULL, false);\n\tlong server_version_num = strtol(server_version_num_guc, NULL, 10);\n\n\tif (!is_supported_pg_version(server_version_num))\n\t{\n\t\tchar *server_version_guc = GetConfigOptionByName(\"server_version\", NULL, false);\n\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"extension \\\"%s\\\" does not support postgres version %s\",\n\t\t\t\t\t\tEXTENSION_NAME,\n\t\t\t\t\t\tserver_version_guc)));\n\t}\n}\n\n/* Sets a new state, returning whether the state has changed */\nstatic bool\nextension_set_state(enum ExtensionState newstate)\n{\n\tif (newstate == extstate)\n\t{\n\t\treturn false;\n\t}\n\tswitch (newstate)\n\t{\n\t\tcase EXTENSION_STATE_TRANSITIONING:\n\t\tcase EXTENSION_STATE_UNKNOWN:\n\t\t\tbreak;\n\t\tcase EXTENSION_STATE_CREATED:\n\t\t\tts_extension_check_version(TIMESCALEDB_VERSION_MOD);\n\t\t\textension_proxy_oid =\n\t\t\t\tts_get_relation_relid(CACHE_SCHEMA_NAME, EXTENSION_PROXY_TABLE, true);\n\t\t\tts_catalog_reset();\n\t\t\tbreak;\n\t\tcase EXTENSION_STATE_NOT_INSTALLED:\n\t\t\textension_proxy_oid = InvalidOid;\n\t\t\tts_catalog_reset();\n\t\t\tbreak;\n\t}\n\telog(DEBUG1,\n\t\t \"extension state changed: %s to %s\",\n\t\t extstate_str[extstate],\n\t\t extstate_str[newstate]);\n\textstate = newstate;\n\treturn true;\n}\n\n/* Updates the state based on the current state, returning whether there had been a change. */\nstatic void\nextension_update_state()\n{\n\tenum ExtensionState new_state =\n\t\textension_current_state(EXTENSION_NAME, CACHE_SCHEMA_NAME, EXTENSION_PROXY_TABLE);\n\n\t/* Never actually set the state to \"not installed\" since there is no good\n\t * way to get out of it in case the extension is installed again in\n\t * another backend. After the extension has been dropped, the proxy table\n\t * no longer exists and when the extension is reinstalled, the proxy table\n\t * will have a different relid. Therefore, there is no way to identify the\n\t * invalidation on the proxy table when CREATE EXTENSION is issued in\n\t * another backend. Nor is it allowed to lookup the new relid in the\n\t * invalidation callback, since that may lead to bad behavior.\n\t *\n\t * Instead, set the state to \"unknown\" so that a \"slow path\" lookup of the\n\t * actual state has to be made next time the state is queried.\n\t */\n\tif (new_state == EXTENSION_STATE_NOT_INSTALLED)\n\t\tnew_state = EXTENSION_STATE_UNKNOWN;\n\n\textension_set_state(new_state);\n\t/*\n\t * Update the extension oid. Note that it is only safe to run\n\t * get_extension_oid() when the extension state is 'CREATED' or\n\t * 'TRANSITIONING', because otherwise we might not be even able to do a\n\t * catalog lookup because we are not in transaction state, and the like.\n\t */\n\tif (new_state == EXTENSION_STATE_CREATED || new_state == EXTENSION_STATE_TRANSITIONING)\n\t{\n\t\tts_extension_oid = get_extension_oid(EXTENSION_NAME, true /* missing_ok */);\n\t\tAssert(OidIsValid(ts_extension_oid));\n\t}\n\telse\n\t{\n\t\tts_extension_oid = InvalidOid;\n\t}\n}\n\nOid\nts_extension_schema_oid(void)\n{\n\tDatum result;\n\tRelation rel;\n\tSysScanDesc scandesc;\n\tHeapTuple tuple;\n\tScanKeyData entry[1];\n\tbool is_null = true;\n\tOid schema = InvalidOid;\n\n\trel = table_open(ExtensionRelationId, AccessShareLock);\n\n\tScanKeyInit(&entry[0],\n\t\t\t\tAnum_pg_extension_extname,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_NAMEEQ,\n\t\t\t\tCStringGetDatum(EXTENSION_NAME));\n\n\tscandesc = systable_beginscan(rel, ExtensionNameIndexId, true, NULL, 1, entry);\n\n\ttuple = systable_getnext(scandesc);\n\n\t/* We assume that there can be at most one matching tuple */\n\tif (HeapTupleIsValid(tuple))\n\t{\n\t\tresult =\n\t\t\theap_getattr(tuple, Anum_pg_extension_extnamespace, RelationGetDescr(rel), &is_null);\n\n\t\tif (!is_null)\n\t\t\tschema = DatumGetObjectId(result);\n\t}\n\n\tsystable_endscan(scandesc);\n\ttable_close(rel, AccessShareLock);\n\n\tif (!OidIsValid(schema))\n\t\telog(ERROR, \"extension schema not found\");\n\treturn schema;\n}\n\nchar *\nts_extension_schema_name(void)\n{\n\treturn get_namespace_name(ts_extension_schema_oid());\n}\n\nconst char *\nts_experimental_schema_name(void)\n{\n\treturn TS_EXPERIMENTAL_SCHEMA_NAME;\n}\n\n/*\n * Invalidate the state of the extension (i.e., whether the extension is\n * installed or not in the current database).\n *\n * Since this function is called from a relcache invalidation callback, it\n * must not, directly or indirectly, call functions that use the cache. This\n * includes, e.g., table scans.\n *\n * Instead, the function just invalidates the state so that the true state is\n * resolved lazily when needed.\n */\nvoid\nts_extension_invalidate(void)\n{\n\telog(DEBUG1,\n\t\t \"extension state invalidated: %s to %s\",\n\t\t extstate_str[extstate],\n\t\t extstate_str[EXTENSION_STATE_UNKNOWN]);\n\textstate = EXTENSION_STATE_UNKNOWN;\n\textension_proxy_oid = InvalidOid;\n}\n\nbool\nts_extension_is_loaded(void)\n{\n\tif (EXTENSION_STATE_UNKNOWN == extstate || EXTENSION_STATE_TRANSITIONING == extstate)\n\t{\n\t\t/* status may have updated without a relcache invalidate event */\n\t\textension_update_state();\n\t}\n\n\tswitch (extstate)\n\t{\n\t\tcase EXTENSION_STATE_CREATED:\n\t\t\tAssert(OidIsValid(ts_extension_oid));\n\t\t\tAssert(OidIsValid(extension_proxy_oid));\n\t\t\treturn true;\n\t\tcase EXTENSION_STATE_NOT_INSTALLED:\n\t\tcase EXTENSION_STATE_UNKNOWN:\n\t\tcase EXTENSION_STATE_TRANSITIONING:\n\n\t\t\t/*\n\t\t\t * Turn off extension during upgrade scripts. This is necessary so\n\t\t\t * that, for example, the catalog does not go looking for things\n\t\t\t * that aren't yet there.\n\t\t\t */\n\t\t\tif (extstate == EXTENSION_STATE_TRANSITIONING)\n\t\t\t{\n\t\t\t\t/* when we are updating the extension, we execute\n\t\t\t\t * scripts in post_update.sql after setting up the\n\t\t\t\t * the dependencies. At this stage, TS\n\t\t\t\t * specific functionality is permitted as we now have\n\t\t\t\t * all catalogs and functions in place\n\t\t\t\t */\n\t\t\t\tconst char *update_script_stage =\n\t\t\t\t\tGetConfigOption(TS_UPDATE_SCRIPT_CONFIG_VAR, true, false);\n\t\t\t\tif (update_script_stage &&\n\t\t\t\t\t(strncmp(update_script_stage, POST_UPDATE, strlen(POST_UPDATE)) == 0) &&\n\t\t\t\t\t(strlen(POST_UPDATE) == strlen(update_script_stage)))\n\t\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn false;\n\t\tdefault:\n\t\t\telog(ERROR, \"unknown state: %d\", extstate);\n\t\t\treturn false;\n\t}\n}\n\nbool\nts_extension_is_loaded_and_not_upgrading(void)\n{\n\t/* When restoring deactivate extension.\n\t *\n\t * We are using IsBinaryUpgrade (and ts_guc_restoring).  If a user set\n\t * `ts_guc_restoring` for a database, it will be stored in\n\t * `pg_db_role_settings` and be included in a dump, which will cause\n\t * `pg_upgrade` to fail.\n\t *\n\t * See dumpDatabaseConfig in pg_dump.c. */\n\tif (ts_guc_restoring || IsBinaryUpgrade)\n\t\treturn false;\n\n\treturn ts_extension_is_loaded();\n}\n\nconst char *\nts_extension_get_so_name(void)\n{\n\treturn EXTENSION_NAME \"-\" TIMESCALEDB_VERSION_MOD;\n}\n\nbool\nts_extension_is_proxy_table_relid(Oid relid)\n{\n\treturn relid == extension_proxy_oid;\n}\n\nTS_FUNCTION_INFO_V1(ts_extension_get_state);\n\nDatum\nts_extension_get_state(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_TEXT_P(cstring_to_text(extstate_str[extstate]));\n}\n"
  },
  {
    "path": "src/extension.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <nodes/parsenodes.h>\n\n#include \"export.h\"\n#include \"extension_constants.h\"\n\nextern void ts_extension_invalidate(void);\nextern TSDLLEXPORT bool ts_extension_is_loaded(void);\nextern bool ts_extension_is_loaded_and_not_upgrading(void);\nextern void ts_extension_check_version(const char *so_version);\nextern void ts_extension_check_server_version(void);\nextern TSDLLEXPORT Oid ts_extension_schema_oid(void);\nextern TSDLLEXPORT char *ts_extension_schema_name(void);\nextern const char *ts_experimental_schema_name(void);\nextern const char *ts_extension_get_so_name(void);\nextern bool ts_extension_is_proxy_table_relid(Oid relid);\n"
  },
  {
    "path": "src/extension_constants.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include \"extension_constants.h\"\n\nconst char *const ts_extension_schema_names[] = {\n\t[TS_CATALOG_SCHEMA] = CATALOG_SCHEMA_NAME,\n\t[TS_FUNCTIONS_SCHEMA] = FUNCTIONS_SCHEMA_NAME,\n\t[TS_INTERNAL_SCHEMA] = INTERNAL_SCHEMA_NAME,\n\t[TS_CACHE_SCHEMA] = CACHE_SCHEMA_NAME,\n\t[TS_EXPERIMENTAL_SCHEMA] = EXPERIMENTAL_SCHEMA_NAME,\n\t[TS_INFORMATION_SCHEMA] = INFORMATION_SCHEMA_NAME,\n};\n"
  },
  {
    "path": "src/extension_constants.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n/* No function definitions here, only potentially globally available defines as this is used by the\n * loader*/\n\n#define EXTENSION_NAME \"timescaledb\"\t  /* Name of the actual extension */\n#define EXTENSION_NAMESPACE \"timescaledb\" /* Namespace for extension objects */\n#define EXTENSION_NAMESPACE_ALIAS \"tsdb\"  /* Namespace for extension objects */\n#define TSL_LIBRARY_NAME \"timescaledb-tsl\"\n#define TS_LIBDIR \"$libdir/\"\n#define EXTENSION_SO TS_LIBDIR \"\" EXTENSION_NAME\n#define EXTENSION_TSL_SO TS_LIBDIR TSL_LIBRARY_NAME \"-\" TIMESCALEDB_VERSION_MOD\n\n#define MAKE_EXTOPTION(NAME) (EXTENSION_NAMESPACE \".\" NAME)\n\n#define MAX_VERSION_LEN (NAMEDATALEN + 1)\n#define MAX_SO_NAME_LEN                                                                            \\\n\t(8 + NAMEDATALEN + 1 + MAX_VERSION_LEN) /* \"$libdir/\"+extname+\"-\"+version                      \\\n\t\t\t\t\t\t\t\t\t\t\t * */\n\ntypedef enum TsExtensionSchemas\n{\n\tTS_CATALOG_SCHEMA,\n\tTS_FUNCTIONS_SCHEMA,\n\tTS_INTERNAL_SCHEMA,\n\tTS_CACHE_SCHEMA,\n\tTS_EXPERIMENTAL_SCHEMA,\n\tTS_INFORMATION_SCHEMA,\n\t_TS_MAX_SCHEMA,\n} TsExtensionSchemas;\n\n#define NUM_TIMESCALEDB_SCHEMAS _TS_MAX_SCHEMA\n\n#define CATALOG_SCHEMA_NAME \"_timescaledb_catalog\"\n#define FUNCTIONS_SCHEMA_NAME \"_timescaledb_functions\"\n#define INTERNAL_SCHEMA_NAME \"_timescaledb_internal\"\n#define CACHE_SCHEMA_NAME \"_timescaledb_cache\"\n#define EXPERIMENTAL_SCHEMA_NAME \"timescaledb_experimental\"\n#define INFORMATION_SCHEMA_NAME \"timescaledb_information\"\n\nextern const char *const ts_extension_schema_names[];\n\n#define RENDEZVOUS_BGW_LOADER_API_VERSION MAKE_EXTOPTION(\"bgw_loader_api_version\")\n"
  },
  {
    "path": "src/extension_utils.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n/* This file will be used by the versioned timescaledb extension and the loader\n * Because we want the loader not to export symbols all files here should be static\n * and be included via #include \"extension_utils.c\" instead of the regular linking process\n */\n\n#include <postgres.h>\n#include <access/genam.h>\n#include <access/relscan.h>\n#include <access/table.h>\n#include <access/xact.h>\n#include <catalog/indexing.h>\n#include <catalog/namespace.h>\n#include <catalog/pg_authid.h>\n#include <catalog/pg_extension.h>\n#include <commands/extension.h>\n#include <miscadmin.h>\n#include <parser/analyze.h>\n#include <utils/acl.h>\n#include <utils/builtins.h>\n#include <utils/fmgroids.h>\n#include <utils/guc.h>\n#include <utils/lsyscache.h>\n#include <utils/rel.h>\n\n#include \"compat/compat.h\"\n#include \"extension_constants.h\"\n#include \"utils.h\"\n\n#define EXTENSION_PROXY_TABLE \"cache_inval_extension\"\n\n#define RENDEZVOUS_LOADER_PRESENT_NAME MAKE_EXTOPTION(\"loader_present\")\n\nenum ExtensionState\n{\n\t/*\n\t * NOT_INSTALLED means that this backend knows that the extension is not\n\t * present. In this state we know that the proxy table is not present.\n\t * This state is never saved since there is no real way to get out of it\n\t * since we cannot signal via the proxy table as its relid is not known\n\t * post installation without a full lookup, which is not allowed in the\n\t * relcache callback.\n\t */\n\tEXTENSION_STATE_NOT_INSTALLED,\n\n\t/*\n\t * UNKNOWN state is used only if we cannot be sure what the state is. This\n\t * can happen in two cases: 1) at the start of a backend or 2) We got a\n\t * relcache event outside of a transaction and thus could not check the\n\t * cache for the presence/absence of the proxy table or extension.\n\t */\n\tEXTENSION_STATE_UNKNOWN,\n\n\t/*\n\t * TRANSITIONING only occurs in the middle of a CREATE EXTENSION or ALTER\n\t * EXTENSION UPDATE\n\t */\n\tEXTENSION_STATE_TRANSITIONING,\n\n\t/*\n\t * CREATED means we know the extension is loaded, metadata is up-to-date,\n\t * and we therefore do not need a full check until a RelCacheInvalidation\n\t * on the proxy table.\n\t */\n\tEXTENSION_STATE_CREATED,\n};\n\nstatic char *\nextension_version(char const *const extension_name)\n{\n\tDatum result;\n\tRelation rel;\n\tSysScanDesc scandesc;\n\tHeapTuple tuple;\n\tScanKeyData entry[1];\n\tbool is_null = true;\n\tchar *sql_version = NULL;\n\n\trel = table_open(ExtensionRelationId, AccessShareLock);\n\n\tScanKeyInit(&entry[0],\n\t\t\t\tAnum_pg_extension_extname,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_NAMEEQ,\n\t\t\t\tCStringGetDatum(extension_name));\n\n\tscandesc = systable_beginscan(rel, ExtensionNameIndexId, true, NULL, 1, entry);\n\n\ttuple = systable_getnext(scandesc);\n\n\t/* We assume that there can be at most one matching tuple */\n\tif (HeapTupleIsValid(tuple))\n\t{\n\t\tresult = heap_getattr(tuple, Anum_pg_extension_extversion, RelationGetDescr(rel), &is_null);\n\n\t\tif (!is_null)\n\t\t{\n\t\t\tsql_version = pstrdup(TextDatumGetCString(result));\n\t\t}\n\t}\n\n\tsystable_endscan(scandesc);\n\ttable_close(rel, AccessShareLock);\n\n\tif (sql_version == NULL)\n\t{\n\t\telog(ERROR, \"extension not found while getting version\");\n\t}\n\treturn sql_version;\n}\n\ninline static bool\nextension_exists(char const *const extension_name)\n{\n\treturn OidIsValid(get_extension_oid(extension_name, true));\n}\n\ninline static bool\nextension_is_transitioning(char const *const extension_name)\n{\n\t/*\n\t * Determine whether the extension is being created or upgraded (as a\n\t * misnomer creating_extension is true during upgrades)\n\t */\n\tif (creating_extension)\n\t{\n\t\treturn get_extension_oid(extension_name, true) == CurrentExtensionObject;\n\t}\n\treturn false;\n}\n\n/* Returns the recomputed current state.\n *\n * (schema_name, table_name) refer to a table that is owned by the extension.\n * thus it is created with the extension and dropped with the extension.\n */\nstatic enum ExtensionState\nextension_current_state(char const *const extension_name, char const *const schema_name,\n\t\t\t\t\t\tchar const *const table_name)\n{\n\t/*\n\t * NormalProcessingMode necessary to avoid accessing cache before its\n\t * ready (which may result in an infinite loop). More concretely we need\n\t * RelationCacheInitializePhase3 to have been already called.\n\t */\n\tif (!IsNormalProcessingMode() || !IsTransactionState() || !OidIsValid(MyDatabaseId))\n\t\treturn EXTENSION_STATE_UNKNOWN;\n\n\t/*\n\t * NOTE: do not check for (schema_name, table_name) existing here. Want to be in\n\t * TRANSITIONING state even before that table is created\n\t */\n\tif (extension_is_transitioning(extension_name))\n\t\treturn EXTENSION_STATE_TRANSITIONING;\n\n\t/*\n\t * We use syscache to check (schema_name, table_name) exists. Must come first.\n\t *\n\t * A table that is owned by the extension is dropped before the extension itself is dropped.\n\t * this logic lets us detect that an extension is being dropped early on in the drop process and\n\t * return `EXTENSION_STATE_NOT_INSTALLED` if that is the case.\n\t * It is best to use a table created early on in the extension creation process because\n\t * that means it will be dropped early in the drop process.\n\t */\n\tif (OidIsValid(ts_get_relation_relid(schema_name, table_name, true)))\n\t{\n\t\tAssert(extension_exists(extension_name));\n\t\treturn EXTENSION_STATE_CREATED;\n\t}\n\n\treturn EXTENSION_STATE_NOT_INSTALLED;\n}\n\n/* Handle extension load request without loader present */\nstatic void\nextension_load_without_preload()\n{\n\t/*\n\t * These are FATAL because otherwise the loader ends up in a weird\n\t * half-loaded state after an ERROR\n\t */\n\n\t/* Only privileged users can get the value of `config file` */\n\tif (has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))\n\t{\n\t\tchar *config_file = GetConfigOptionByName(\"config_file\", NULL, false);\n\n\t\tereport(FATAL,\n\t\t\t\t(errmsg(\"extension \\\"%s\\\" must be preloaded\", EXTENSION_NAME),\n\t\t\t\t errhint(\"Please preload the timescaledb library via \"\n\t\t\t\t\t\t \"shared_preload_libraries.\\n\\n\"\n\t\t\t\t\t\t \"This can be done by editing the config file at: %1$s\\n\"\n\t\t\t\t\t\t \"and adding 'timescaledb' to the list in the shared_preload_libraries \"\n\t\t\t\t\t\t \"config.\\n\"\n\t\t\t\t\t\t \"\t# Modify postgresql.conf:\\n\tshared_preload_libraries = \"\n\t\t\t\t\t\t \"'timescaledb'\\n\\n\"\n\t\t\t\t\t\t \"Another way to do this, if not preloading other libraries, is with \"\n\t\t\t\t\t\t \"the command:\\n\"\n\t\t\t\t\t\t \"\techo \\\"shared_preload_libraries = 'timescaledb'\\\" >> %1$s \\n\\n\"\n\t\t\t\t\t\t \"(Will require a database restart.)\\n\\n\",\n\t\t\t\t\t\t config_file)));\n\t}\n\telse\n\t{\n\t\tereport(FATAL,\n\t\t\t\t(errmsg(\"extension \\\"%s\\\" must be preloaded\", EXTENSION_NAME),\n\t\t\t\t errhint(\"Please preload the timescaledb library via shared_preload_libraries.\\n\\n\"\n\t\t\t\t\t\t \"This can be done by editing the postgres config file \\n\"\n\t\t\t\t\t\t \"and adding 'timescaledb' to the list in the shared_preload_libraries \"\n\t\t\t\t\t\t \"config.\\n\"\n\t\t\t\t\t\t \"\t# Modify postgresql.conf:\\n\tshared_preload_libraries = \"\n\t\t\t\t\t\t \"'timescaledb'\\n\\n\"\n\t\t\t\t\t\t \"Another way to do this, if not preloading other libraries, is with the \"\n\t\t\t\t\t\t \"command:\\n\"\n\t\t\t\t\t\t \"\techo \\\"shared_preload_libraries = 'timescaledb'\\\" >> \"\n\t\t\t\t\t\t \"/path/to/config/file \\n\\n\"\n\t\t\t\t\t\t \"(Will require a database restart.)\\n\\n\")));\n\t}\n}\n"
  },
  {
    "path": "src/foreach_ptr.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <compat/compat.h>\n#include <postgres.h>\n#include <nodes/pg_list.h>\n\n#ifdef PG17_LT\n/* In PG16 foreach_ptr is not available, this is a straight copy of the\n * Postgres code that defines foreach_ptr and foreach_internal.\n *\n * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group\n * Portions Copyright (c) 1994, Regents of the University of California\n */\n\n#ifndef foreach_ptr\n#define foreach_ptr(type, var, lst) foreach_internal(type, *, var, lst, lfirst)\n#endif\n\n#ifndef foreach_internal\n#define foreach_internal(type, pointer, var, lst, func)                                            \\\n\tfor (type pointer var = 0, pointer var##__outerloop = (type pointer) 1; var##__outerloop;      \\\n\t\t var##__outerloop = 0)                                                                     \\\n\t\tfor (ForEachState var##__state = { (lst), 0 };                                             \\\n\t\t\t (var##__state.l != NIL && var##__state.i < var##__state.l->length &&                  \\\n\t\t\t  (var = (type pointer) func(&var##__state.l->elements[var##__state.i]), true));       \\\n\t\t\t var##__state.i++)\n#endif\n#endif /* PG17_LT */\n"
  },
  {
    "path": "src/foreign_key.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n/*\n * The table referenced by a foreign constraint is supposed to have a\n * constraint that prevents removing the referenced rows. The constraint\n * is enforced by a pair of update and delete triggers. Normally this\n * is done by the postgres addFkRecurseReferenced(), but it doesn't work\n * for hypertables because they use inheritance, and that function only\n * recurses into declarative partitioning hierarchy.\n */\n\n#include <postgres.h>\n#include \"access/attmap.h\"\n#include \"catalog/pg_trigger.h\"\n#include \"commands/trigger.h\"\n#include \"parser/parser.h\"\n\n#include \"compat/compat.h\"\n#include \"chunk.h\"\n#include \"chunk_constraint.h\"\n#include \"foreign_key.h\"\n#include \"hypertable.h\"\n\nstatic HeapTuple relation_get_fk_constraint(Oid conrelid, Oid confrelid);\nstatic List *relation_get_referencing_fk(Oid reloid);\nstatic Oid get_fk_index(Relation rel, int nkeys, AttrNumber *confkeys);\nstatic void constraint_get_trigger(Oid conoid, Oid *updtrigoid, Oid *deltrigoid);\nstatic char *ChooseForeignKeyConstraintNameAddition(int numkeys, AttrNumber *keys, Oid relid);\nstatic void createForeignKeyActionTriggers(Form_pg_constraint fk, Oid relid, Oid refRelOid,\n\t\t\t\t\t\t\t\t\t\t   Oid constraintOid, Oid indexOid, Oid parentDelTrigger,\n\t\t\t\t\t\t\t\t\t\t   Oid parentUpdTrigger);\nstatic void clone_constraint_on_chunk(const Chunk *chunk, Relation parentRel, Form_pg_constraint fk,\n\t\t\t\t\t\t\t\t\t  int numfks, AttrNumber *conkey, AttrNumber *confkey,\n\t\t\t\t\t\t\t\t\t  Oid *conpfeqop, Oid *conppeqop, Oid *conffeqop,\n\t\t\t\t\t\t\t\t\t  int numfkdelsetcols, AttrNumber *confdelsetcols,\n\t\t\t\t\t\t\t\t\t  Oid parentDelTrigger, Oid parentUpdTrigger);\n\n/*\n * Copy foreign key constraint fk_tuple to all chunks.\n */\nstatic void\npropagate_fk(Relation ht_rel, HeapTuple fk_tuple, List *chunks)\n{\n\tForm_pg_constraint fk = (Form_pg_constraint) GETSTRUCT(fk_tuple);\n\n\tint numfks;\n\tAttrNumber conkey[INDEX_MAX_KEYS];\n\tAttrNumber confkey[INDEX_MAX_KEYS];\n\tOid conpfeqop[INDEX_MAX_KEYS];\n\tOid conppeqop[INDEX_MAX_KEYS];\n\tOid conffeqop[INDEX_MAX_KEYS];\n\tint numfkdelsetcols;\n\tAttrNumber confdelsetcols[INDEX_MAX_KEYS];\n\n\tDeconstructFkConstraintRow(fk_tuple,\n\t\t\t\t\t\t\t   &numfks,\n\t\t\t\t\t\t\t   conkey,\n\t\t\t\t\t\t\t   confkey,\n\t\t\t\t\t\t\t   conpfeqop,\n\t\t\t\t\t\t\t   conppeqop,\n\t\t\t\t\t\t\t   conffeqop,\n\t\t\t\t\t\t\t   &numfkdelsetcols,\n\t\t\t\t\t\t\t   confdelsetcols);\n\n\tOid parentDelTrigger, parentUpdTrigger;\n\tconstraint_get_trigger(fk->oid, &parentUpdTrigger, &parentDelTrigger);\n\n\tListCell *lc;\n\tforeach (lc, chunks)\n\t{\n\t\tChunk *chunk = lfirst(lc);\n\t\tif (chunk->fd.osm_chunk)\n\t\t\tcontinue;\n\n\t\tclone_constraint_on_chunk(chunk,\n\t\t\t\t\t\t\t\t  ht_rel,\n\t\t\t\t\t\t\t\t  fk,\n\t\t\t\t\t\t\t\t  numfks,\n\t\t\t\t\t\t\t\t  conkey,\n\t\t\t\t\t\t\t\t  confkey,\n\t\t\t\t\t\t\t\t  conpfeqop,\n\t\t\t\t\t\t\t\t  conppeqop,\n\t\t\t\t\t\t\t\t  conffeqop,\n\t\t\t\t\t\t\t\t  numfkdelsetcols,\n\t\t\t\t\t\t\t\t  confdelsetcols,\n\t\t\t\t\t\t\t\t  parentDelTrigger,\n\t\t\t\t\t\t\t\t  parentUpdTrigger);\n\t}\n}\n\n/*\n * Copy all foreign key constraints from the main table to a chunk.\n */\nvoid\nts_chunk_copy_referencing_fk(const Hypertable *ht, const Chunk *chunk)\n{\n\tListCell *lc;\n\tList *chunks = list_make1((Chunk *) chunk);\n\tList *fks = relation_get_referencing_fk(ht->main_table_relid);\n\n\tRelation ht_rel = table_open(ht->main_table_relid, AccessShareLock);\n\tforeach (lc, fks)\n\t{\n\t\tHeapTuple fk_tuple = lfirst(lc);\n\t\tpropagate_fk(ht_rel, fk_tuple, chunks);\n\t}\n\ttable_close(ht_rel, NoLock);\n}\n\n/*\n * Copy one foreign key constraint from the main table to all chunks.\n */\nvoid\nts_fk_propagate(Oid conrelid, Hypertable *ht)\n{\n\tHeapTuple fk_tuple = relation_get_fk_constraint(conrelid, ht->main_table_relid);\n\n\tif (!fk_tuple)\n\t\telog(ERROR, \"foreign key constraint not found\");\n\n\tRelation ht_rel = table_open(ht->main_table_relid, AccessShareLock);\n\tList *chunks = ts_chunk_get_by_hypertable_id(ht->fd.id);\n\tpropagate_fk(ht_rel, fk_tuple, chunks);\n\ttable_close(ht_rel, NoLock);\n}\n\n/*\n * Clone a single constraint to a single chunk.\n */\nstatic void\nclone_constraint_on_chunk(const Chunk *chunk, Relation parentRel, Form_pg_constraint fk, int numfks,\n\t\t\t\t\t\t  AttrNumber *conkey, AttrNumber *confkey, Oid *conpfeqop, Oid *conppeqop,\n\t\t\t\t\t\t  Oid *conffeqop, int numfkdelsetcols, AttrNumber *confdelsetcols,\n\t\t\t\t\t\t  Oid parentDelTrigger, Oid parentUpdTrigger)\n{\n\tAttrNumber mapped_confkey[INDEX_MAX_KEYS];\n\tRelation pkrel = table_open(chunk->table_id, AccessShareLock);\n\n\t/* Map the foreign key columns on the hypertable side to the chunk columns */\n#if PG16_GE\n\tAttrMap *attmap =\n\t\tbuild_attrmap_by_name(RelationGetDescr(pkrel), RelationGetDescr(parentRel), false);\n#else\n\tAttrMap *attmap = build_attrmap_by_name(RelationGetDescr(pkrel), RelationGetDescr(parentRel));\n#endif\n\tfor (int i = 0; i < numfks; i++)\n\t\tmapped_confkey[i] = attmap->attnums[confkey[i] - 1];\n\n\tOid indexoid = get_fk_index(pkrel, numfks, mapped_confkey);\n\t/* Since postgres accepted the constraint, there should be a supporting index. */\n\tEnsure(OidIsValid(indexoid), \"index for constraint not found on chunk\");\n\n\ttable_close(pkrel, NoLock);\n\n\tchar *conname_addition =\n\t\tChooseForeignKeyConstraintNameAddition(numfks, confkey, parentRel->rd_id);\n\tchar *conname = ChooseConstraintName(get_rel_name(fk->conrelid),\n\t\t\t\t\t\t\t\t\t\t conname_addition,\n\t\t\t\t\t\t\t\t\t\t \"fkey\",\n\t\t\t\t\t\t\t\t\t\t fk->connamespace,\n\t\t\t\t\t\t\t\t\t\t NIL);\n\tOid conoid = CreateConstraintEntry(conname,\n\t\t\t\t\t\t\t\t\t   fk->connamespace,\n\t\t\t\t\t\t\t\t\t   CONSTRAINT_FOREIGN,\n\t\t\t\t\t\t\t\t\t   fk->condeferrable,\n\t\t\t\t\t\t\t\t\t   fk->condeferred,\n#if PG18_GE\n\t\t\t\t\t\t\t\t\t   true, /* isEnforced */\n#endif\n\t\t\t\t\t\t\t\t\t   fk->convalidated,\n\t\t\t\t\t\t\t\t\t   fk->oid,\n\t\t\t\t\t\t\t\t\t   fk->conrelid,\n\t\t\t\t\t\t\t\t\t   conkey,\n\t\t\t\t\t\t\t\t\t   numfks,\n\t\t\t\t\t\t\t\t\t   numfks,\n\t\t\t\t\t\t\t\t\t   InvalidOid,\n\t\t\t\t\t\t\t\t\t   indexoid,\n\t\t\t\t\t\t\t\t\t   chunk->table_id,\n\t\t\t\t\t\t\t\t\t   mapped_confkey,\n\t\t\t\t\t\t\t\t\t   conpfeqop,\n\t\t\t\t\t\t\t\t\t   conppeqop,\n\t\t\t\t\t\t\t\t\t   conffeqop,\n\t\t\t\t\t\t\t\t\t   numfks,\n\t\t\t\t\t\t\t\t\t   fk->confupdtype,\n\t\t\t\t\t\t\t\t\t   fk->confdeltype,\n\t\t\t\t\t\t\t\t\t   confdelsetcols,\n\t\t\t\t\t\t\t\t\t   numfkdelsetcols,\n\t\t\t\t\t\t\t\t\t   fk->confmatchtype,\n\t\t\t\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t\t\t\t   false,\n\t\t\t\t\t\t\t\t\t   1,\n\t\t\t\t\t\t\t\t\t   false,\n#if PG18_GE\n\t\t\t\t\t\t\t\t\t   false, /* conPeriod */\n#endif\n\t\t\t\t\t\t\t\t\t   false);\n\n\tObjectAddress address, referenced;\n\tObjectAddressSet(address, ConstraintRelationId, conoid);\n\tObjectAddressSet(referenced, ConstraintRelationId, fk->oid);\n\trecordDependencyOn(&address, &referenced, DEPENDENCY_INTERNAL);\n\n\tCommandCounterIncrement();\n\n\tcreateForeignKeyActionTriggers(fk,\n\t\t\t\t\t\t\t\t   fk->conrelid,\n\t\t\t\t\t\t\t\t   chunk->table_id,\n\t\t\t\t\t\t\t\t   conoid,\n\t\t\t\t\t\t\t\t   indexoid,\n\t\t\t\t\t\t\t\t   parentDelTrigger,\n\t\t\t\t\t\t\t\t   parentUpdTrigger);\n}\n\n/*\n * Generate the column-name portion of the constraint name for a new foreign\n * key given the list of column names that reference the referenced\n * table.  This will be passed to ChooseConstraintName along with the parent\n * table name and the \"fkey\" suffix.\n *\n * We know that less than NAMEDATALEN characters will actually be used, so we\n * can truncate the result once we've generated that many.\n *\n * This function is based on a static function by the same name in tablecmds.c in PostgreSQL.\n */\nstatic char *\nChooseForeignKeyConstraintNameAddition(int numkeys, AttrNumber *keys, Oid relid)\n{\n\tchar buf[NAMEDATALEN * 2];\n\tint buflen = 0;\n\n\tbuf[0] = '\\0';\n\tfor (int i = 0; i < numkeys; i++)\n\t{\n\t\tchar *name = get_attname(relid, keys[i], false);\n\t\tif (buflen > 0)\n\t\t\tbuf[buflen++] = '_'; /* insert _ between names */\n\n\t\t/*\n\t\t * At this point we have buflen <= NAMEDATALEN.  name should be less\n\t\t * than NAMEDATALEN already, but use strlcpy for paranoia.\n\t\t */\n\t\tstrlcpy(buf + buflen, name, NAMEDATALEN);\n\t\tbuflen += strlen(buf + buflen);\n\t\tif (buflen >= NAMEDATALEN)\n\t\t\tbreak;\n\t}\n\treturn pstrdup(buf);\n}\n\n/*\n * createForeignKeyActionTriggers\n *\t\tCreate the referenced-side \"action\" triggers that implement a foreign\n *\t\tkey.\n * This function is based on a static function by the same name in tablecmds.c in PostgreSQL.\n */\nstatic void\ncreateForeignKeyActionTriggers(Form_pg_constraint fk, Oid relid, Oid refRelOid, Oid constraintOid,\n\t\t\t\t\t\t\t   Oid indexOid, Oid parentDelTrigger, Oid parentUpdTrigger)\n{\n\tCreateTrigStmt *fk_trigger;\n\n\t/*\n\t * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON\n\t * DELETE action on the referenced table.\n\t */\n\tfk_trigger = makeNode(CreateTrigStmt);\n\tfk_trigger->replace = false;\n\tfk_trigger->isconstraint = true;\n\tfk_trigger->trigname = \"RI_ConstraintTrigger_a\";\n\tfk_trigger->relation = NULL;\n\tfk_trigger->args = NIL;\n\tfk_trigger->row = true;\n\tfk_trigger->timing = TRIGGER_TYPE_AFTER;\n\tfk_trigger->events = TRIGGER_TYPE_DELETE;\n\tfk_trigger->columns = NIL;\n\tfk_trigger->whenClause = NULL;\n\tfk_trigger->transitionRels = NIL;\n\tfk_trigger->constrrel = NULL;\n\tswitch (fk->confdeltype)\n\t{\n\t\tcase FKCONSTR_ACTION_NOACTION:\n\t\t\tfk_trigger->deferrable = fk->condeferrable;\n\t\t\tfk_trigger->initdeferred = fk->condeferred;\n\t\t\tfk_trigger->funcname = SystemFuncName(\"RI_FKey_noaction_del\");\n\t\t\tbreak;\n\t\tcase FKCONSTR_ACTION_RESTRICT:\n\t\t\tfk_trigger->deferrable = false;\n\t\t\tfk_trigger->initdeferred = false;\n\t\t\tfk_trigger->funcname = SystemFuncName(\"RI_FKey_restrict_del\");\n\t\t\tbreak;\n\t\tcase FKCONSTR_ACTION_CASCADE:\n\t\t\tfk_trigger->deferrable = false;\n\t\t\tfk_trigger->initdeferred = false;\n\t\t\tfk_trigger->funcname = SystemFuncName(\"RI_FKey_cascade_del\");\n\t\t\tbreak;\n\t\tcase FKCONSTR_ACTION_SETNULL:\n\t\t\tfk_trigger->deferrable = false;\n\t\t\tfk_trigger->initdeferred = false;\n\t\t\tfk_trigger->funcname = SystemFuncName(\"RI_FKey_setnull_del\");\n\t\t\tbreak;\n\t\tcase FKCONSTR_ACTION_SETDEFAULT:\n\t\t\tfk_trigger->deferrable = false;\n\t\t\tfk_trigger->initdeferred = false;\n\t\t\tfk_trigger->funcname = SystemFuncName(\"RI_FKey_setdefault_del\");\n\t\t\tbreak;\n\t\tdefault:\n\t\t\telog(ERROR, \"unrecognized FK action type: %d\", (int) fk->confdeltype);\n\t\t\tbreak;\n\t}\n\n\t/*\n\t * clang will complain here about swapped arguments but this is intentional\n\t * as this is the reverse trigger from the referenced table back to the\n\t * referencing table. So we disable that specific warning for the next call.\n\t *\n\t * NOLINTBEGIN(readability-suspicious-call-argument)\n\t */\n\tCreateTrigger(fk_trigger,\n\t\t\t\t  NULL,\n\t\t\t\t  refRelOid,\n\t\t\t\t  relid,\n\t\t\t\t  constraintOid,\n\t\t\t\t  indexOid,\n\t\t\t\t  InvalidOid,\n\t\t\t\t  parentDelTrigger,\n\t\t\t\t  NULL,\n\t\t\t\t  true,\n\t\t\t\t  false);\n\t/* NOLINTEND(readability-suspicious-call-argument) */\n\n\t/* Make changes-so-far visible */\n\tCommandCounterIncrement();\n\n\t/*\n\t * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON\n\t * UPDATE action on the referenced table.\n\t */\n\tfk_trigger = makeNode(CreateTrigStmt);\n\tfk_trigger->replace = false;\n\tfk_trigger->isconstraint = true;\n\tfk_trigger->trigname = \"RI_ConstraintTrigger_a\";\n\tfk_trigger->relation = NULL;\n\tfk_trigger->args = NIL;\n\tfk_trigger->row = true;\n\tfk_trigger->timing = TRIGGER_TYPE_AFTER;\n\tfk_trigger->events = TRIGGER_TYPE_UPDATE;\n\tfk_trigger->columns = NIL;\n\tfk_trigger->whenClause = NULL;\n\tfk_trigger->transitionRels = NIL;\n\tfk_trigger->constrrel = NULL;\n\tswitch (fk->confupdtype)\n\t{\n\t\tcase FKCONSTR_ACTION_NOACTION:\n\t\t\tfk_trigger->deferrable = fk->condeferrable;\n\t\t\tfk_trigger->initdeferred = fk->condeferred;\n\t\t\tfk_trigger->funcname = SystemFuncName(\"RI_FKey_noaction_upd\");\n\t\t\tbreak;\n\t\tcase FKCONSTR_ACTION_RESTRICT:\n\t\t\tfk_trigger->deferrable = false;\n\t\t\tfk_trigger->initdeferred = false;\n\t\t\tfk_trigger->funcname = SystemFuncName(\"RI_FKey_restrict_upd\");\n\t\t\tbreak;\n\t\tcase FKCONSTR_ACTION_CASCADE:\n\t\t\tfk_trigger->deferrable = false;\n\t\t\tfk_trigger->initdeferred = false;\n\t\t\tfk_trigger->funcname = SystemFuncName(\"RI_FKey_cascade_upd\");\n\t\t\tbreak;\n\t\tcase FKCONSTR_ACTION_SETNULL:\n\t\t\tfk_trigger->deferrable = false;\n\t\t\tfk_trigger->initdeferred = false;\n\t\t\tfk_trigger->funcname = SystemFuncName(\"RI_FKey_setnull_upd\");\n\t\t\tbreak;\n\t\tcase FKCONSTR_ACTION_SETDEFAULT:\n\t\t\tfk_trigger->deferrable = false;\n\t\t\tfk_trigger->initdeferred = false;\n\t\t\tfk_trigger->funcname = SystemFuncName(\"RI_FKey_setdefault_upd\");\n\t\t\tbreak;\n\t\tdefault:\n\t\t\telog(ERROR, \"unrecognized FK action type: %d\", (int) fk->confupdtype);\n\t\t\tbreak;\n\t}\n\n\t/*\n\t * clang will complain here about swapped arguments but this is intentional\n\t * as this is the reverse trigger from the referenced table back to the\n\t * referencing table.\n\t *\n\t * NOLINTBEGIN(readability-suspicious-call-argument)\n\t */\n\tCreateTrigger(fk_trigger,\n\t\t\t\t  NULL,\n\t\t\t\t  refRelOid,\n\t\t\t\t  relid,\n\t\t\t\t  constraintOid,\n\t\t\t\t  indexOid,\n\t\t\t\t  InvalidOid,\n\t\t\t\t  parentUpdTrigger,\n\t\t\t\t  NULL,\n\t\t\t\t  true,\n\t\t\t\t  false);\n\t/* NOLINTEND(readability-suspicious-call-argument) */\n\n\t/* Make changes-so-far visible */\n\tCommandCounterIncrement();\n}\n\n/*\n * Return a list of foreign key pg_constraint heap tuples referencing reloid.\n */\nstatic List *\nrelation_get_referencing_fk(Oid reloid)\n{\n\tList *result = NIL;\n\tRelation conrel;\n\tSysScanDesc conscan;\n\tScanKeyData skey[2];\n\tHeapTuple htup;\n\n\t/* Prepare to scan pg_constraint for entries having confrelid = this rel. */\n\tScanKeyInit(&skey[0],\n\t\t\t\tAnum_pg_constraint_confrelid,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_OIDEQ,\n\t\t\t\tObjectIdGetDatum(reloid));\n\n\tScanKeyInit(&skey[1],\n\t\t\t\tAnum_pg_constraint_contype,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_CHAREQ,\n\t\t\t\tCharGetDatum(CONSTRAINT_FOREIGN));\n\n\tconrel = table_open(ConstraintRelationId, AccessShareLock);\n\tconscan = systable_beginscan(conrel, InvalidOid, false, NULL, 2, skey);\n\n\twhile (HeapTupleIsValid(htup = systable_getnext(conscan)))\n\t{\n\t\tresult = lappend(result, heap_copytuple(htup));\n\t}\n\n\tsystable_endscan(conscan);\n\ttable_close(conrel, AccessShareLock);\n\n\treturn result;\n}\n\n/*\n * Return a list of foreign key pg_constraint heap tuples referencing reloid.\n */\nstatic HeapTuple\nrelation_get_fk_constraint(Oid conrelid, Oid confrelid)\n{\n\tRelation conrel;\n\tSysScanDesc conscan;\n\tScanKeyData skey[3];\n\n\t/* Prepare to scan pg_constraint for entries having confrelid = this rel. */\n\tScanKeyInit(&skey[0],\n\t\t\t\tAnum_pg_constraint_conrelid,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_OIDEQ,\n\t\t\t\tObjectIdGetDatum(conrelid));\n\n\tScanKeyInit(&skey[1],\n\t\t\t\tAnum_pg_constraint_confrelid,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_OIDEQ,\n\t\t\t\tObjectIdGetDatum(confrelid));\n\n\tScanKeyInit(&skey[2],\n\t\t\t\tAnum_pg_constraint_contype,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_CHAREQ,\n\t\t\t\tCharGetDatum(CONSTRAINT_FOREIGN));\n\n\tconrel = table_open(ConstraintRelationId, AccessShareLock);\n\tconscan = systable_beginscan(conrel, InvalidOid, false, NULL, 3, skey);\n\n\tHeapTuple htup = systable_getnext(conscan);\n\tif (HeapTupleIsValid(htup))\n\t{\n\t\thtup = heap_copytuple(htup);\n\t}\n\n\tsystable_endscan(conscan);\n\ttable_close(conrel, AccessShareLock);\n\n\treturn htup;\n}\n\n/* Get the UPDATE and DELETE trigger OIDs for the given constraint OID */\nstatic void\nconstraint_get_trigger(Oid conoid, Oid *updtrigoid, Oid *deltrigoid)\n{\n\tRelation rel;\n\tSysScanDesc scan;\n\tScanKeyData skey[1];\n\tHeapTuple htup;\n\n\t*updtrigoid = InvalidOid;\n\t*deltrigoid = InvalidOid;\n\n\tScanKeyInit(&skey[0],\n\t\t\t\tAnum_pg_trigger_tgconstraint,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_OIDEQ,\n\t\t\t\tObjectIdGetDatum(conoid));\n\n\trel = table_open(TriggerRelationId, AccessShareLock);\n\tscan = systable_beginscan(rel, TriggerConstraintIndexId, true, NULL, 1, skey);\n\n\twhile (HeapTupleIsValid(htup = systable_getnext(scan)))\n\t{\n\t\tForm_pg_trigger trigform = (Form_pg_trigger) GETSTRUCT(htup);\n\n\t\tif ((trigform->tgtype & TRIGGER_TYPE_UPDATE) == TRIGGER_TYPE_UPDATE)\n\t\t\t*updtrigoid = trigform->oid;\n\t\tif ((trigform->tgtype & TRIGGER_TYPE_DELETE) == TRIGGER_TYPE_DELETE)\n\t\t\t*deltrigoid = trigform->oid;\n\t}\n\n\tsystable_endscan(scan);\n\ttable_close(rel, AccessShareLock);\n}\n\n/*\n * Return the oid of the index supporting the foreign key constraint.\n */\nstatic Oid\nget_fk_index(Relation rel, int nkeys, AttrNumber *confkeys)\n{\n\tOid indexoid = InvalidOid;\n\tList *indexes = RelationGetIndexList(rel);\n\tListCell *lc;\n\n\tforeach (lc, indexes)\n\t{\n\t\tOid indexoid = lfirst_oid(lc);\n\t\tRelation indexrel = index_open(indexoid, AccessShareLock);\n\n\t\tif (!indexrel->rd_index->indisunique || indexrel->rd_index->indnkeyatts != nkeys)\n\t\t{\n\t\t\tindex_close(indexrel, AccessShareLock);\n\t\t\tcontinue;\n\t\t}\n\n\t\tBitmapset *con_keys = NULL;\n\t\tBitmapset *ind_keys = NULL;\n\n\t\tfor (int i = 0; i < nkeys; i++)\n\t\t{\n\t\t\t/*\n\t\t\t * Since ordering of the constraint definition and index definition can differ,\n\t\t\t * we need to check that all the columns in the constraint are present in the index\n\t\t\t */\n\t\t\tcon_keys = bms_add_member(con_keys, confkeys[i]);\n\t\t\tind_keys = bms_add_member(ind_keys, indexrel->rd_index->indkey.values[i]);\n\t\t}\n\n\t\tbool match = bms_equal(con_keys, ind_keys);\n\n\t\tindex_close(indexrel, AccessShareLock);\n\t\tbms_free(con_keys);\n\t\tbms_free(ind_keys);\n\n\t\tif (match)\n\t\t{\n\t\t\treturn indexoid;\n\t\t}\n\t}\n\n\treturn indexoid;\n}\n\nvoid\nts_chunk_drop_referencing_fk_by_chunk_id(Oid chunk_id)\n{\n\tChunk *chunk = ts_chunk_get_by_id(chunk_id, true);\n\tList *fks = relation_get_referencing_fk(chunk->table_id);\n\tListCell *lc;\n\n\tforeach (lc, fks)\n\t{\n\t\tHeapTuple fk_tuple = lfirst(lc);\n\t\tts_chunk_constraint_drop_from_tuple(fk_tuple);\n\t}\n}\n"
  },
  {
    "path": "src/foreign_key.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <catalog/pg_constraint.h>\n#include <nodes/parsenodes.h>\n\n#include \"chunk.h\"\n#include \"export.h\"\n#include \"hypertable.h\"\n\nextern TSDLLEXPORT void ts_fk_propagate(Oid conrelid, Hypertable *ht);\nextern TSDLLEXPORT void ts_chunk_copy_referencing_fk(const Hypertable *ht, const Chunk *chunk);\nextern TSDLLEXPORT void ts_chunk_drop_referencing_fk_by_chunk_id(Oid chunk_id);\n"
  },
  {
    "path": "src/func_cache.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/heapam.h>\n#include <access/htup.h>\n#include <catalog/pg_namespace_d.h>\n#include <catalog/pg_proc.h>\n#include <catalog/pg_type.h>\n#include <miscadmin.h>\n#include <nodes/pathnodes.h>\n#include <optimizer/optimizer.h>\n#include <parser/parse_oper.h>\n#include <utils/builtins.h>\n#include <utils/hsearch.h>\n#include <utils/lsyscache.h>\n#include <utils/rel.h>\n#include <utils/selfuncs.h>\n#include <utils/syscache.h>\n\n#include \"compat/compat.h\"\n#include \"cache.h\"\n#include \"estimate.h\"\n#include \"extension.h\"\n#include \"func_cache.h\"\n#include \"sort_transform.h\"\n#include \"utils.h\"\n\n/*\n * func_cache - a cache for quick identification of, and access to, functions\n * useful for TimescaleDB. The function info is used in various query\n * optimizations, for instance, we provide custom group estimate functions for\n * use when grouping on time buckets. We also provide functions that allow\n * sorting time buckets using an index on the non-bucketed expression/column.\n */\n\nstatic Expr *\ndate_trunc_sort_transform(FuncExpr *func)\n{\n\t/*\n\t * date_trunc (const, var) => var\n\t *\n\t * proof: date_trunc(c, time1) >= date_trunc(c,time2) iff time1 > time2\n\t */\n\tExpr *second;\n\n\tif (list_length(func->args) != 2 || !IsA(linitial(func->args), Const))\n\t\treturn (Expr *) func;\n\n\tsecond = ts_sort_transform_expr(lsecond(func->args));\n\n\tif (!IsA(second, Var))\n\t\treturn (Expr *) func;\n\n\treturn (Expr *) copyObject(second);\n}\n\n/*\n * Check that time_bucket has a const offset, if an offset is supplied\n */\n#define time_bucket_has_const_offset(func)                                                         \\\n\t(list_length((func)->args) == 2 || IsA(lthird((func)->args), Const))\n\n#define time_bucket_has_const_period(func) IsA(linitial((func)->args), Const)\n#define time_bucket_has_const_timezone(func) IsA(lthird((func)->args), Const)\n\nstatic Expr *\ndo_sort_transform(FuncExpr *func)\n{\n\tExpr *second = ts_sort_transform_expr(lsecond(func->args));\n\n\tif (!IsA(second, Var))\n\t\treturn (Expr *) func;\n\n\treturn (Expr *) copyObject(second);\n}\n\nstatic Expr *\ntime_bucket_gapfill_sort_transform(FuncExpr *func)\n{\n\t/*\n\t * time_bucket(const, var, const) => var\n\t *\n\t * proof: time_bucket(const1, time1) >= time_bucket(const1,time2) iff time1\n\t * > time2\n\t */\n\tAssert(list_length(func->args) == 4 || list_length(func->args) == 5);\n\n\tif (!time_bucket_has_const_period(func) ||\n\t\t(list_length(func->args) == 5 && !time_bucket_has_const_timezone(func)))\n\t\treturn (Expr *) func;\n\n\treturn do_sort_transform(func);\n}\n\nstatic Expr *\ntime_bucket_sort_transform(FuncExpr *func)\n{\n\tAssert(list_length(func->args) >= 2);\n\t/*\n\t * If period and offset are not constants we must not do the optimization\n\t */\n\tif (!time_bucket_has_const_offset(func))\n\t\treturn (Expr *) func;\n\n\tif (!time_bucket_has_const_period(func))\n\t\treturn (Expr *) func;\n\n\treturn do_sort_transform(func);\n}\n\n/*\n * time_bucket with timezone will always have 5 args. For the sort\n * optimization to apply all args need to be Const except timestamp.\n */\nstatic Expr *\ntime_bucket_tz_sort_transform(FuncExpr *func)\n{\n\tAssert(list_length(func->args) == 5);\n\n\tif (!IsA(linitial((func)->args), Const) || !IsA(lthird(func->args), Const) ||\n\t\t!IsA(lfourth(func->args), Const) || !IsA(lfifth(func->args), Const))\n\t\treturn (Expr *) func;\n\n\treturn do_sort_transform(func);\n}\n\n/* For time_bucket this estimate currently works by seeing how many possible\n * buckets there will be if the data spans the entire hypertable. Note that\n * this is an overestimate.\n * */\nstatic double\ntime_bucket_group_estimate(PlannerInfo *root, FuncExpr *expr, double path_rows)\n{\n\tNode *first_arg = eval_const_expressions(root, linitial(expr->args));\n\tExpr *second_arg = lsecond(expr->args);\n\tConst *c;\n\tdouble period;\n\n\tif (!IsA(first_arg, Const))\n\t\treturn INVALID_ESTIMATE;\n\n\tc = (Const *) first_arg;\n\tswitch (c->consttype)\n\t{\n\t\tcase INT2OID:\n\t\t\tperiod = (double) DatumGetInt16(c->constvalue);\n\t\t\tbreak;\n\t\tcase INT4OID:\n\t\t\tperiod = (double) DatumGetInt32(c->constvalue);\n\t\t\tbreak;\n\t\tcase INT8OID:\n\t\t\tperiod = (double) DatumGetInt64(c->constvalue);\n\t\t\tbreak;\n\t\tcase INTERVALOID:\n\t\t\tperiod = (double) ts_get_interval_period_approx(DatumGetIntervalP(c->constvalue));\n\t\t\tbreak;\n\t\tdefault:\n\t\t\treturn INVALID_ESTIMATE;\n\t}\n\treturn ts_estimate_group_expr_interval(root, second_arg, period);\n}\n\n/* For date_trunc this estimate currently works by seeing how many possible\n * buckets there will be if the data spans the entire hypertable. Note that\n * this is an overestimate.\n * */\nstatic double\ndate_trunc_group_estimate(PlannerInfo *root, FuncExpr *expr, double path_rows)\n{\n\tNode *first_arg = eval_const_expressions(root, linitial(expr->args));\n\tExpr *second_arg = lsecond(expr->args);\n\tConst *c;\n\ttext *interval;\n\n\tif (!IsA(first_arg, Const))\n\t\treturn INVALID_ESTIMATE;\n\n\tc = (Const *) first_arg;\n\tinterval = DatumGetTextPP(c->constvalue);\n\n\treturn ts_estimate_group_expr_interval(root,\n\t\t\t\t\t\t\t\t\t\t   second_arg,\n\t\t\t\t\t\t\t\t\t\t   (double) ts_date_trunc_interval_period_approx(interval));\n}\n\ntypedef struct FuncEntry\n{\n\tOid funcid;\n\tFuncInfo *funcinfo;\n} FuncEntry;\n\n/* Information about functions that we put in the cache */\nstatic FuncInfo funcinfo[] = {\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = true,\n\t\t.funcname = \"time_bucket\",\n\t\t.nargs = 2,\n\t\t.arg_types = { INTERVALOID, TIMESTAMPOID },\n\t\t.group_estimate = time_bucket_group_estimate,\n\t\t.sort_transform = time_bucket_sort_transform,\n\t},\n\t/* Interval Bucket with origin */\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = true,\n\t\t.funcname = \"time_bucket\",\n\t\t.nargs = 3,\n\t\t.arg_types = { INTERVALOID, TIMESTAMPOID, TIMESTAMPOID },\n\t\t.group_estimate = time_bucket_group_estimate,\n\t\t.sort_transform = time_bucket_sort_transform,\n\t},\n\t/* Interval Bucket with offset */\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = true,\n\t\t.funcname = \"time_bucket\",\n\t\t.nargs = 3,\n\t\t.arg_types = { INTERVALOID, TIMESTAMPOID, INTERVALOID },\n\t\t.group_estimate = time_bucket_group_estimate,\n\t\t.sort_transform = time_bucket_sort_transform,\n\t},\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = true,\n\t\t.funcname = \"time_bucket\",\n\t\t.nargs = 2,\n\t\t.arg_types = { INTERVALOID, TIMESTAMPTZOID },\n\t\t.group_estimate = time_bucket_group_estimate,\n\t\t.sort_transform = time_bucket_sort_transform,\n\t},\n\t/* Interval Bucket with origin */\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = true,\n\t\t.funcname = \"time_bucket\",\n\t\t.nargs = 3,\n\t\t.arg_types = { INTERVALOID, TIMESTAMPTZOID, TIMESTAMPTZOID },\n\t\t.group_estimate = time_bucket_group_estimate,\n\t\t.sort_transform = time_bucket_sort_transform,\n\t},\n\t/* Interval Bucket with offset */\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = true,\n\t\t.funcname = \"time_bucket\",\n\t\t.nargs = 3,\n\t\t.arg_types = { INTERVALOID, TIMESTAMPTZOID, INTERVALOID },\n\t\t.group_estimate = time_bucket_group_estimate,\n\t\t.sort_transform = time_bucket_sort_transform,\n\t},\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = true,\n\t\t.funcname = \"time_bucket\",\n\t\t.nargs = 2,\n\t\t.arg_types = { INTERVALOID, UUIDOID },\n\t\t.group_estimate = time_bucket_group_estimate,\n\t\t.sort_transform = time_bucket_sort_transform,\n\t},\n\t/* Interval Bucket with origin */\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = true,\n\t\t.funcname = \"time_bucket\",\n\t\t.nargs = 3,\n\t\t.arg_types = { INTERVALOID, UUIDOID, TIMESTAMPTZOID },\n\t\t.group_estimate = time_bucket_group_estimate,\n\t\t.sort_transform = time_bucket_sort_transform,\n\t},\n\t/* Interval Bucket with offset */\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = true,\n\t\t.funcname = \"time_bucket\",\n\t\t.nargs = 3,\n\t\t.arg_types = { INTERVALOID, UUIDOID, INTERVALOID },\n\t\t.group_estimate = time_bucket_group_estimate,\n\t\t.sort_transform = time_bucket_sort_transform,\n\t},\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = true,\n\t\t.funcname = \"time_bucket\",\n\t\t.nargs = 2,\n\t\t.arg_types = { INTERVALOID, DATEOID },\n\t\t.group_estimate = time_bucket_group_estimate,\n\t\t.sort_transform = time_bucket_sort_transform,\n\t},\n\t/* Interval Bucket with origin */\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = true,\n\t\t.funcname = \"time_bucket\",\n\t\t.nargs = 3,\n\t\t.arg_types = { INTERVALOID, DATEOID, DATEOID },\n\t\t.group_estimate = time_bucket_group_estimate,\n\t\t.sort_transform = time_bucket_sort_transform,\n\t},\n\t/* Interval Bucket with offset */\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = true,\n\t\t.funcname = \"time_bucket\",\n\t\t.nargs = 3,\n\t\t.arg_types = { INTERVALOID, DATEOID, INTERVALOID },\n\t\t.group_estimate = time_bucket_group_estimate,\n\t\t.sort_transform = time_bucket_sort_transform,\n\t},\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = true,\n\t\t.funcname = \"time_bucket\",\n\t\t.nargs = 2,\n\t\t.arg_types = { INT2OID, INT2OID },\n\t\t.group_estimate = time_bucket_group_estimate,\n\t\t.sort_transform = time_bucket_sort_transform,\n\t},\n\t/* Int2 Bucket with offset */\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = true,\n\t\t.funcname = \"time_bucket\",\n\t\t.nargs = 3,\n\t\t.arg_types = { INT2OID, INT2OID, INT2OID },\n\t\t.group_estimate = time_bucket_group_estimate,\n\t\t.sort_transform = time_bucket_sort_transform,\n\t},\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = true,\n\t\t.funcname = \"time_bucket\",\n\t\t.nargs = 2,\n\t\t.arg_types = { INT4OID, INT4OID },\n\t\t.group_estimate = time_bucket_group_estimate,\n\t\t.sort_transform = time_bucket_sort_transform,\n\t},\n\t/* Int4 Bucket with offset */\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = true,\n\t\t.funcname = \"time_bucket\",\n\t\t.nargs = 3,\n\t\t.arg_types = { INT4OID, INT4OID, INT4OID },\n\t\t.group_estimate = time_bucket_group_estimate,\n\t\t.sort_transform = time_bucket_sort_transform,\n\t},\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = true,\n\t\t.funcname = \"time_bucket\",\n\t\t.nargs = 2,\n\t\t.arg_types = { INT8OID, INT8OID },\n\t\t.group_estimate = time_bucket_group_estimate,\n\t\t.sort_transform = time_bucket_sort_transform,\n\t},\n\t/* Int8 Bucket with offset */\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = true,\n\t\t.funcname = \"time_bucket\",\n\t\t.nargs = 3,\n\t\t.arg_types = { INT8OID, INT8OID, INT8OID },\n\t\t.group_estimate = time_bucket_group_estimate,\n\t\t.sort_transform = time_bucket_sort_transform,\n\t},\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = true,\n\t\t.funcname = \"time_bucket\",\n\t\t.nargs = 5,\n\t\t.arg_types = { INTERVALOID, TIMESTAMPTZOID, TEXTOID, TIMESTAMPTZOID, INTERVALOID },\n\t\t.group_estimate = time_bucket_group_estimate,\n\t\t.sort_transform = time_bucket_tz_sort_transform,\n\t},\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = false,\n\t\t.funcname = \"time_bucket_gapfill\",\n\t\t.nargs = 4,\n\t\t.arg_types = { INTERVALOID, TIMESTAMPOID, TIMESTAMPOID, TIMESTAMPOID },\n\t\t.group_estimate = time_bucket_group_estimate,\n\t\t.sort_transform = time_bucket_gapfill_sort_transform,\n\t},\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = false,\n\t\t.funcname = \"time_bucket_gapfill\",\n\t\t.nargs = 4,\n\t\t.arg_types = { INTERVALOID, TIMESTAMPTZOID, TIMESTAMPTZOID, TIMESTAMPTZOID },\n\t\t.group_estimate = time_bucket_group_estimate,\n\t\t.sort_transform = time_bucket_gapfill_sort_transform,\n\t},\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = false,\n\t\t.funcname = \"time_bucket_gapfill\",\n\t\t.nargs = 5,\n\t\t.arg_types = { INTERVALOID, TIMESTAMPTZOID, TEXTOID, TIMESTAMPTZOID, TIMESTAMPTZOID },\n\t\t.group_estimate = time_bucket_group_estimate,\n\t\t.sort_transform = time_bucket_gapfill_sort_transform,\n\t},\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = false,\n\t\t.funcname = \"time_bucket_gapfill\",\n\t\t.nargs = 4,\n\t\t.arg_types = { INTERVALOID, DATEOID, DATEOID, DATEOID },\n\t\t.group_estimate = time_bucket_group_estimate,\n\t\t.sort_transform = time_bucket_gapfill_sort_transform,\n\t},\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = false,\n\t\t.funcname = \"time_bucket_gapfill\",\n\t\t.nargs = 4,\n\t\t.arg_types = { INT2OID, INT2OID, INT2OID, INT2OID },\n\t\t.group_estimate = time_bucket_group_estimate,\n\t\t.sort_transform = time_bucket_gapfill_sort_transform,\n\t},\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = false,\n\t\t.funcname = \"time_bucket_gapfill\",\n\t\t.nargs = 4,\n\t\t.arg_types = { INT4OID, INT4OID, INT4OID, INT4OID },\n\t\t.group_estimate = time_bucket_group_estimate,\n\t\t.sort_transform = time_bucket_gapfill_sort_transform,\n\t},\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = false,\n\t\t.funcname = \"time_bucket_gapfill\",\n\t\t.nargs = 4,\n\t\t.arg_types = { INT8OID, INT8OID, INT8OID, INT8OID },\n\t\t.group_estimate = time_bucket_group_estimate,\n\t\t.sort_transform = time_bucket_gapfill_sort_transform,\n\t},\n\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = false,\n\t\t.allowed_in_cagg_definition = false,\n\t\t.funcname = \"first\",\n\t\t.nargs = 2,\n\t\t.arg_types = { ANYELEMENTOID, ANYOID },\n\t\t.group_estimate = NULL,\n\t\t.sort_transform = NULL,\n\t},\n\t{\n\t\t.origin = ORIGIN_TIMESCALE,\n\t\t.is_bucketing_func = false,\n\t\t.allowed_in_cagg_definition = false,\n\t\t.funcname = \"last\",\n\t\t.nargs = 2,\n\t\t.arg_types = { ANYELEMENTOID, ANYOID },\n\t\t.group_estimate = NULL,\n\t\t.sort_transform = NULL,\n\t},\n\n\t{\n\t\t.origin = ORIGIN_POSTGRES,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = false,\n\t\t.funcname = \"date_trunc\",\n\t\t.nargs = 2,\n\t\t.arg_types = { TEXTOID, TIMESTAMPOID },\n\t\t.group_estimate = date_trunc_group_estimate,\n\t\t.sort_transform = date_trunc_sort_transform,\n\t},\n\t{\n\t\t.origin = ORIGIN_POSTGRES,\n\t\t.is_bucketing_func = true,\n\t\t.allowed_in_cagg_definition = false,\n\t\t.funcname = \"date_trunc\",\n\t\t.nargs = 2,\n\t\t.arg_types = { TEXTOID, TIMESTAMPTZOID },\n\t\t.group_estimate = date_trunc_group_estimate,\n\t\t.sort_transform = date_trunc_sort_transform,\n\t},\n};\n\n#define _MAX_CACHE_FUNCTIONS (sizeof(funcinfo) / sizeof(funcinfo[0]))\n\nOid ts_first_func_oid = InvalidOid;\nOid ts_last_func_oid = InvalidOid;\n\nstatic HTAB *func_hash = NULL;\n\nstatic Oid\nproc_get_oid(HeapTuple tuple)\n{\n\tForm_pg_proc form = (Form_pg_proc) GETSTRUCT(tuple);\n\treturn form->oid;\n}\n\nvoid\nts_func_cache_init()\n{\n\tEnsure(!func_hash, \"function cache already initialized\");\n\tHASHCTL hashctl = {\n\t\t.keysize = sizeof(Oid),\n\t\t.entrysize = sizeof(FuncEntry),\n\t\t.hcxt = CacheMemoryContext,\n\t};\n\tOid extension_nsp = ts_extension_schema_oid();\n\tOid experimental_nsp = get_namespace_oid(ts_experimental_schema_name(), false);\n\tHeapTuple tuple;\n\tRelation rel;\n\n\tfunc_hash = hash_create(\"func_cache\",\n\t\t\t\t\t\t\t_MAX_CACHE_FUNCTIONS,\n\t\t\t\t\t\t\t&hashctl,\n\t\t\t\t\t\t\tHASH_ELEM | HASH_BLOBS | HASH_CONTEXT);\n\n\trel = table_open(ProcedureRelationId, AccessShareLock);\n\n\tfor (size_t i = 0; i < _MAX_CACHE_FUNCTIONS; i++)\n\t{\n\t\tFuncInfo *finfo = &funcinfo[i];\n\t\tOid namespaceoid = PG_CATALOG_NAMESPACE;\n\t\toidvector *paramtypes = buildoidvector(finfo->arg_types, finfo->nargs);\n\t\tFuncEntry *fentry;\n\t\tbool hash_found;\n\t\tOid funcid;\n\n\t\tif (finfo->origin == ORIGIN_TIMESCALE)\n\t\t{\n\t\t\tnamespaceoid = extension_nsp;\n\t\t}\n\t\telse if (finfo->origin == ORIGIN_TIMESCALE_EXPERIMENTAL)\n\t\t{\n\t\t\tnamespaceoid = experimental_nsp;\n\t\t}\n\n\t\ttuple = SearchSysCache3(PROCNAMEARGSNSP,\n\t\t\t\t\t\t\t\tPointerGetDatum(finfo->funcname),\n\t\t\t\t\t\t\t\tPointerGetDatum(paramtypes),\n\t\t\t\t\t\t\t\tObjectIdGetDatum(namespaceoid));\n\n\t\tif (!HeapTupleIsValid(tuple))\n\t\t{\n\t\t\t/* The function cache could be accessed during an extension upgrade. Not all expected\n\t\t\t * functions have to exist at this point. */\n\t\t\telog(ts_extension_is_loaded_and_not_upgrading() ? ERROR : NOTICE,\n\t\t\t\t \"cache lookup failed for function \\\"%s\\\" with %d args\",\n\t\t\t\t finfo->funcname,\n\t\t\t\t finfo->nargs);\n\t\t\tcontinue;\n\t\t}\n\n\t\tfuncid = proc_get_oid(tuple);\n\n\t\t/* Special handling for first/last to set up named variables for their oids */\n\t\tif (strcmp(finfo->funcname, \"first\") == 0)\n\t\t\tts_first_func_oid = funcid;\n\t\telse if (strcmp(finfo->funcname, \"last\") == 0)\n\t\t\tts_last_func_oid = funcid;\n\n\t\tfentry = hash_search(func_hash, &funcid, HASH_ENTER, &hash_found);\n\t\tAssert(!hash_found);\n\t\tfentry->funcid = funcid;\n\t\tfentry->funcinfo = finfo;\n\t\tReleaseSysCache(tuple);\n\t}\n\n\ttable_close(rel, AccessShareLock);\n}\n\nFuncInfo *\nts_func_cache_get(Oid funcid)\n{\n\tFuncEntry *entry;\n\n\tif (!func_hash)\n\t\tts_func_cache_init();\n\n\tentry = hash_search(func_hash, &funcid, HASH_FIND, NULL);\n\n\treturn (NULL == entry) ? NULL : entry->funcinfo;\n}\n\nFuncInfo *\nts_func_cache_get_bucketing_func(Oid funcid)\n{\n\tFuncInfo *finfo = ts_func_cache_get(funcid);\n\n\tif (NULL == finfo)\n\t\treturn NULL;\n\n\treturn finfo->is_bucketing_func ? finfo : NULL;\n}\n"
  },
  {
    "path": "src/func_cache.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <nodes/primnodes.h>\n\n#include \"export.h\"\n\n#define FUNC_CACHE_MAX_FUNC_ARGS 10\n\ntypedef Expr *(*sort_transform_func)(FuncExpr *func);\ntypedef double (*group_estimate_func)(PlannerInfo *root, FuncExpr *expr, double path_rows);\n\n/* Describes the function origin */\ntypedef enum\n{\n\t/*\n\t * Function is provided by PostgreSQL.\n\t */\n\tORIGIN_POSTGRES = 0,\n\t/*\n\t * Function is provided by TimescaleDB.\n\t */\n\tORIGIN_TIMESCALE = 1,\n\t/*\n\t * Function is provided by TimescaleDB and is experimental.\n\t * It should be looked for in the experimental schema.\n\t */\n\tORIGIN_TIMESCALE_EXPERIMENTAL = 2,\n} FuncOrigin;\n\ntypedef struct FuncInfo\n{\n\tconst char *funcname;\n\tFuncOrigin origin;\n\tbool is_bucketing_func;\n\tbool allowed_in_cagg_definition;\n\tint nargs;\n\tOid arg_types[FUNC_CACHE_MAX_FUNC_ARGS];\n\tgroup_estimate_func group_estimate;\n\tsort_transform_func sort_transform;\n} FuncInfo;\n\nextern TSDLLEXPORT void ts_func_cache_init(void);\nextern TSDLLEXPORT FuncInfo *ts_func_cache_get(Oid funcid);\nextern TSDLLEXPORT FuncInfo *ts_func_cache_get_bucketing_func(Oid funcid);\n\nextern TSDLLEXPORT Oid ts_first_func_oid;\nextern TSDLLEXPORT Oid ts_last_func_oid;\n"
  },
  {
    "path": "src/gapfill.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <fmgr.h>\n\n#include \"compat/compat.h\"\n#include \"cross_module_fn.h\"\n#include \"export.h\"\n#include \"license_guc.h\"\n\n/*\n * stub function to trigger locf and interpolate in gapfill node\n */\nTS_FUNCTION_INFO_V1(ts_gapfill_marker);\nDatum\nts_gapfill_marker(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_DATUM(ts_cm_functions->gapfill_marker(fcinfo));\n}\n\n#define GAPFILL_TIMEBUCKET_WRAPPER(datatype)                                                       \\\n\tTS_FUNCTION_INFO_V1(ts_gapfill_##datatype##_bucket);                                           \\\n\tDatum ts_gapfill_##datatype##_bucket(PG_FUNCTION_ARGS)                      \\\n\t{                                                                                              \\\n\t\treturn ts_cm_functions->gapfill_##datatype##_time_bucket(fcinfo);                          \\\n\t}\n\nGAPFILL_TIMEBUCKET_WRAPPER(int16);\nGAPFILL_TIMEBUCKET_WRAPPER(int32);\nGAPFILL_TIMEBUCKET_WRAPPER(int64);\nGAPFILL_TIMEBUCKET_WRAPPER(date);\nGAPFILL_TIMEBUCKET_WRAPPER(timestamp);\nGAPFILL_TIMEBUCKET_WRAPPER(timestamptz);\nGAPFILL_TIMEBUCKET_WRAPPER(timestamptz_timezone);\n"
  },
  {
    "path": "src/gitcommit.cmake",
    "content": "# The commands for generating gitcommit.h need to be executed on every make run\n# and not on cmake run to detect branch switches, commit changes or local\n# modifications.\n\nif(GIT_FOUND)\n  # We use \"git describe\" to generate the tag. It will find the latest tag and\n  # also add some additional information if we are not on the tag.\n  execute_process(\n    COMMAND ${GIT_EXECUTABLE} describe --dirty --always --tags\n    WORKING_DIRECTORY ${SOURCE_DIR}\n    OUTPUT_VARIABLE EXT_GIT_COMMIT_TAG\n    RESULT_VARIABLE _describe_RESULT\n    OUTPUT_STRIP_TRAILING_WHITESPACE)\n\n  # Fetch the commit HASH of head (short version) using rev-parse\n  execute_process(\n    COMMAND ${GIT_EXECUTABLE} rev-parse HEAD\n    OUTPUT_VARIABLE EXT_GIT_COMMIT_HASH\n    WORKING_DIRECTORY ${SOURCE_DIR}\n    RESULT_VARIABLE _revparse_RESULT\n    OUTPUT_STRIP_TRAILING_WHITESPACE)\n\n  # Fetch the date of the head commit\n  execute_process(\n    COMMAND ${GIT_EXECUTABLE} log -1 --format=%cI\n    WORKING_DIRECTORY ${SOURCE_DIR}\n    OUTPUT_VARIABLE EXT_GIT_COMMIT_TIME\n    RESULT_VARIABLE _log_RESULT\n    OUTPUT_STRIP_TRAILING_WHITESPACE)\n\n  # Results are non-zero if there were an error\n  if(_describe_RESULT\n     OR _revparse_RESULT\n     OR _log_RESULT)\n    message(STATUS \"Unable to get git commit information\")\n  else()\n    message(\n      STATUS\n        \"Building commit ${EXT_GIT_COMMIT_TAG} (${EXT_GIT_COMMIT_HASH}), ${EXT_GIT_COMMIT_TIME}\"\n    )\n  endif()\nendif()\n\nfile(REMOVE ${OUTPUT_FILE})\nconfigure_file(${INPUT_FILE} ${OUTPUT_FILE})\n"
  },
  {
    "path": "src/gitcommit.h.in",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#ifndef GITCOMMIT_H_\n#define GITCOMMIT_H_\n\n#cmakedefine EXT_GIT_COMMIT_TAG \"@EXT_GIT_COMMIT_TAG@\"\n#cmakedefine EXT_GIT_COMMIT_HASH \"@EXT_GIT_COMMIT_HASH@\"\n#cmakedefine EXT_GIT_COMMIT_TIME \"@EXT_GIT_COMMIT_TIME@\"\n\n#endif /* GITCOMMIT_H_ */\n"
  },
  {
    "path": "src/guc.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <miscadmin.h>\n#include <parser/parse_func.h>\n#include <utils/fmgrprotos.h>\n#include <utils/guc.h>\n#include <utils/regproc.h>\n#include <utils/timestamp.h>\n#include <utils/varlena.h>\n\n#include \"compat/compat.h\"\n#include \"config.h\"\n#include \"extension.h\"\n#include \"guc.h\"\n#include \"hypertable_cache.h\"\n#include \"license_guc.h\"\n\n#ifdef USE_TELEMETRY\n#include \"telemetry/telemetry.h\"\n#endif\n\n#ifdef USE_TELEMETRY\n/* Define which level means on. We use this object to have at least one object\n * of type TelemetryLevel in the code, otherwise pgindent won't work for the\n * type */\nstatic const TelemetryLevel on_level = TELEMETRY_NO_FUNCTIONS;\n\nbool\nts_telemetry_on()\n{\n\treturn ts_guc_telemetry_level >= on_level;\n}\n\nbool\nts_function_telemetry_on()\n{\n\treturn ts_guc_telemetry_level > TELEMETRY_NO_FUNCTIONS;\n}\n\nstatic const struct config_enum_entry telemetry_level_options[] = {\n\t{ \"off\", TELEMETRY_OFF, false },\n\t{ \"no_functions\", TELEMETRY_NO_FUNCTIONS, false },\n\t{ \"basic\", TELEMETRY_BASIC, false },\n\t{ NULL, 0, false }\n};\n#endif\n\n/* Copied from contrib/auto_explain/auto_explain.c */\nstatic const struct config_enum_entry loglevel_options[] = {\n\t{ \"debug5\", DEBUG5, false }, { \"debug4\", DEBUG4, false }, { \"debug3\", DEBUG3, false },\n\t{ \"debug2\", DEBUG2, false }, { \"debug1\", DEBUG1, false }, { \"debug\", DEBUG2, true },\n\t{ \"info\", INFO, false },\t { \"notice\", NOTICE, false }, { \"warning\", WARNING, false },\n\t{ \"log\", LOG, false },\t\t { \"error\", ERROR, false },\t  { \"fatal\", FATAL, false },\n\t{ NULL, 0, false }\n};\n\nstatic const struct config_enum_entry compress_truncate_behaviour_options[] = {\n\t{ \"truncate_only\", COMPRESS_TRUNCATE_ONLY, false },\n\t{ \"truncate_or_delete\", COMPRESS_TRUNCATE_OR_DELETE, false },\n\t{ \"truncate_disabled\", COMPRESS_TRUNCATE_DISABLED, false },\n\t{ NULL, 0, false }\n};\n\nbool ts_guc_enable_direct_compress_copy = false;\nbool ts_guc_enable_direct_compress_copy_sort_batches = true;\nbool ts_guc_enable_direct_compress_copy_client_sorted = false;\nint ts_guc_direct_compress_copy_tuple_sort_limit = 100000;\nTSDLLEXPORT bool ts_guc_enable_direct_compress_insert = false;\nbool ts_guc_enable_direct_compress_insert_sort_batches = true;\nTSDLLEXPORT bool ts_guc_enable_direct_compress_insert_client_sorted = false;\nTSDLLEXPORT bool ts_guc_enable_direct_compress_on_cagg_refresh = false;\nint ts_guc_direct_compress_insert_tuple_sort_limit = 10000;\nbool ts_guc_enable_deprecation_warnings = true;\nbool ts_guc_enable_optimizations = true;\nbool ts_guc_restoring = false;\nbool ts_guc_enable_constraint_aware_append = true;\nbool ts_guc_enable_ordered_append = true;\nbool ts_guc_enable_chunk_append = true;\nbool ts_guc_enable_parallel_chunk_append = true;\nbool ts_guc_enable_runtime_exclusion = true;\nbool ts_guc_enable_constraint_exclusion = true;\nbool ts_guc_enable_qual_propagation = true;\nTSDLLEXPORT bool ts_guc_enable_columnar_scan_filter_pushdown = true;\nbool ts_guc_enable_qual_filtering = true;\nbool ts_guc_enable_cagg_reorder_groupby = true;\nTSDLLEXPORT bool ts_guc_enable_cagg_window_functions = false;\nbool ts_guc_enable_now_constify = true;\nbool ts_guc_enable_foreign_key_propagation = true;\n#if PG16_GE\nTSDLLEXPORT bool ts_guc_enable_cagg_sort_pushdown = true;\n#endif\nTSDLLEXPORT bool ts_guc_enable_cagg_watermark_constify = true;\nTSDLLEXPORT int ts_guc_cagg_max_individual_materializations = 10;\nbool ts_guc_enable_osm_reads = true;\nTSDLLEXPORT bool ts_guc_enable_compressed_direct_batch_delete = true;\nTSDLLEXPORT bool ts_guc_enable_dml_decompression = true;\nTSDLLEXPORT bool ts_guc_enable_dml_decompression_tuple_filtering = true;\nTSDLLEXPORT bool ts_guc_enable_dml_bloom_filter = true;\nTSDLLEXPORT int ts_guc_max_tuples_decompressed_per_dml = 100000;\nTSDLLEXPORT bool ts_guc_enable_compression_wal_markers = false;\nTSDLLEXPORT bool ts_guc_enable_decompression_sorted_merge = true;\nbool ts_guc_enable_chunkwise_aggregation = true;\nbool ts_guc_enable_vectorized_aggregation = true;\nTSDLLEXPORT bool ts_guc_enable_compression_indexscan = false;\nTSDLLEXPORT bool ts_guc_enable_bulk_decompression = true;\nTSDLLEXPORT bool ts_guc_auto_sparse_indexes = true;\nTSDLLEXPORT bool ts_guc_enable_sparse_index_bloom = true;\nTSDLLEXPORT bool ts_guc_enable_composite_bloom_indexes = true;\nTSDLLEXPORT bool ts_guc_read_legacy_bloom1_v1 = false;\n\nbool ts_guc_enable_chunk_skipping = false;\nTSDLLEXPORT bool ts_guc_enable_segmentwise_recompression = true;\nTSDLLEXPORT bool ts_guc_enable_in_memory_recompression = true;\nTSDLLEXPORT bool ts_guc_enable_exclusive_locking_recompression = false;\nTSDLLEXPORT bool ts_guc_enable_bool_compression = true;\nTSDLLEXPORT bool ts_guc_enable_uuid_compression = true;\nTSDLLEXPORT int ts_guc_compression_batch_size_limit = TARGET_COMPRESSED_BATCH_SIZE;\nTSDLLEXPORT bool ts_guc_compression_enable_compressor_batch_limit = false;\nTSDLLEXPORT CompressTruncateBehaviour ts_guc_compress_truncate_behaviour = COMPRESS_TRUNCATE_ONLY;\nbool ts_guc_enable_event_triggers = false;\nbool ts_guc_enable_chunk_auto_publication = false;\nbool ts_guc_debug_skip_scan_info = false;\n\n/* Only settable in debug mode for testing */\nTSDLLEXPORT bool ts_guc_enable_null_compression = true;\nTSDLLEXPORT bool ts_guc_enable_compression_ratio_warnings = true;\n\n/* Enable of disable columnar scans for columnar-oriented storage engines. If\n * disabled, regular sequence scans will be used instead. */\nTSDLLEXPORT bool ts_guc_enable_columnarscan = true;\nTSDLLEXPORT bool ts_guc_enable_columnarindexscan = true;\nTSDLLEXPORT int ts_guc_bgw_log_level = WARNING;\nTSDLLEXPORT bool ts_guc_enable_skip_scan = true;\n#if PG16_GE\nTSDLLEXPORT bool ts_guc_enable_skip_scan_for_distinct_aggregates = true;\n#endif\nTSDLLEXPORT bool ts_guc_enable_compressed_skip_scan = true;\nTSDLLEXPORT bool ts_guc_enable_multikey_skip_scan = true;\nTSDLLEXPORT double ts_guc_skip_scan_run_cost_multiplier = 1.0;\nstatic char *ts_guc_default_segmentby_fn = NULL;\nstatic char *ts_guc_default_orderby_fn = NULL;\nTSDLLEXPORT bool ts_guc_enable_job_execution_logging = false;\nbool ts_guc_enable_tss_callbacks = true;\nTSDLLEXPORT bool ts_guc_enable_delete_after_compression = false;\nTSDLLEXPORT bool ts_guc_enable_merge_on_cagg_refresh = false;\n\nbool ts_guc_enable_partitioned_hypertables = false;\n\n/* default value of ts_guc_max_open_chunks_per_insert and\n * ts_guc_max_cached_chunks_per_hypertable will be set as their respective boot-value when the\n * GUC mechanism starts up */\nint ts_guc_max_open_chunks_per_insert;\nint ts_guc_max_cached_chunks_per_hypertable;\n#ifdef USE_TELEMETRY\nTelemetryLevel ts_guc_telemetry_level = TELEMETRY_DEFAULT;\nchar *ts_telemetry_cloud = NULL;\n#endif\n\nTSDLLEXPORT char *ts_guc_license = TS_LICENSE_DEFAULT;\n\n/*\n * Exit code for the scheduler.\n *\n * Normally it exits with a zero which means that it will not restart. If an\n * error is raised, it exits with error code 1, which will trigger a\n * restart.\n *\n * This variable exists to be able to trigger a restart for a normal exit,\n * which is useful when debugging.\n *\n * See backend/postmaster/bgworker.c\n */\nint ts_debug_bgw_scheduler_exit_status = 0;\n\n#ifdef TS_DEBUG\nbool ts_shutdown_bgw = false;\nchar *ts_current_timestamp_mock = NULL;\n#endif\n\nint ts_guc_debug_toast_tuple_target = 128;\n\nstatic const struct config_enum_entry debug_require_options[] = { { \"allow\", DRO_Allow, false },\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  { \"forbid\", DRO_Forbid, false },\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  { \"require\", DRO_Require, false },\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  { \"force\", DRO_Force, false },\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  { NULL, 0, false } };\n\n#ifdef TS_DEBUG\n\nbool ts_guc_debug_have_int128;\n\nDebugRequireOption ts_guc_debug_require_vector_qual = DRO_Allow;\n\nDebugRequireOption ts_guc_debug_require_vector_agg = DRO_Allow;\n#endif\n\nDebugRequireOption ts_guc_debug_require_batch_sorted_merge = false;\n\nbool ts_guc_debug_compression_path_info = false;\nbool ts_guc_enable_rowlevel_compression_locking = false;\n\nstatic bool ts_guc_enable_hypertable_create = true;\nstatic bool ts_guc_enable_hypertable_compression = true;\nstatic bool ts_guc_enable_cagg_create = true;\nstatic bool ts_guc_enable_policy_create = true;\nstatic char *ts_guc_default_chunk_time_interval = NULL;\n\ntypedef struct\n{\n\tconst char *name;\n\tconst char *description;\n\tbool *enable;\n} FeatureFlag;\n\nstatic FeatureFlag ts_feature_flags[] = {\n\t[FEATURE_HYPERTABLE] = { MAKE_EXTOPTION(\"enable_hypertable_create\"),\n\t\t\t\t\t\t\t \"Enable creation of hypertable\",\n\t\t\t\t\t\t\t &ts_guc_enable_hypertable_create },\n\n\t[FEATURE_HYPERTABLE_COMPRESSION] = { MAKE_EXTOPTION(\"enable_hypertable_compression\"),\n\t\t\t\t\t\t\t\t\t\t \"Enable hypertable compression functions\",\n\t\t\t\t\t\t\t\t\t\t &ts_guc_enable_hypertable_compression },\n\n\t[FEATURE_CAGG] = { MAKE_EXTOPTION(\"enable_cagg_create\"),\n\t\t\t\t\t   \"Enable creation of continuous aggregate\",\n\t\t\t\t\t   &ts_guc_enable_cagg_create },\n\n\t[FEATURE_POLICY] = { MAKE_EXTOPTION(\"enable_policy_create\"),\n\t\t\t\t\t\t \"Enable creation of policies and user-defined actions\",\n\t\t\t\t\t\t &ts_guc_enable_policy_create }\n};\n\nstatic void\nts_feature_flag_add(FeatureFlagType type)\n{\n\tFeatureFlag *flag = &ts_feature_flags[type];\n\tint flag_context = PGC_SIGHUP;\n#ifdef TS_DEBUG\n\tflag_context = PGC_USERSET;\n#endif\n\tDefineCustomBoolVariable(flag->name,\n\t\t\t\t\t\t\t flag->description,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t flag->enable,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t flag_context,\n\t\t\t\t\t\t\t GUC_SUPERUSER_ONLY,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n}\n\nvoid\nts_feature_flag_check(FeatureFlagType type)\n{\n\tFeatureFlag *flag = &ts_feature_flags[type];\n\tif (likely(*flag->enable))\n\t\treturn;\n\tereport(ERROR,\n\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t errmsg(\"You are using a PostgreSQL service. This feature is only available on \"\n\t\t\t\t\t\"Time-series and analytics services. \"\n\t\t\t\t\t\"https://docs.timescale.com/use-timescale/latest/services/\")));\n}\n\n/*\n * We have to understand if we have finished initializing the GUCs, so that we\n * know when it's OK to check their values for mutual consistency.\n */\nstatic bool gucs_are_initialized = false;\n\n/*\n * Warn about the mismatched cache sizes that can lead to cache thrashing.\n */\nstatic void\nvalidate_chunk_cache_sizes(int hypertable_chunks, int insert_chunks)\n{\n\t/*\n\t * Note that this callback is also called when the individual GUCs are\n\t * initialized, so we are going to see temporary mismatched values here.\n\t * That's why we also have to check that the GUC initialization have\n\t * finished.\n\t */\n\tif (gucs_are_initialized && insert_chunks > hypertable_chunks)\n\t{\n\t\tereport(WARNING,\n\t\t\t\t(errmsg(\"insert cache size is larger than hypertable chunk cache size\"),\n\t\t\t\t errdetail(\"insert cache size is %d, hypertable chunk cache size is %d\",\n\t\t\t\t\t\t   insert_chunks,\n\t\t\t\t\t\t   hypertable_chunks),\n\t\t\t\t errhint(\"This is a configuration problem. Either increase \"\n\t\t\t\t\t\t \"timescaledb.max_cached_chunks_per_hypertable (preferred) or decrease \"\n\t\t\t\t\t\t \"timescaledb.max_open_chunks_per_insert.\")));\n\t}\n}\n\nstatic void\nassign_max_cached_chunks_per_hypertable_hook(int newval, void *extra)\n{\n\t/* invalidate the hypertable cache to reset */\n\tts_hypertable_cache_invalidate_callback();\n\n\tvalidate_chunk_cache_sizes(newval, ts_guc_max_open_chunks_per_insert);\n}\n\nstatic void\nassign_max_open_chunks_per_insert_hook(int newval, void *extra)\n{\n\tvalidate_chunk_cache_sizes(ts_guc_max_cached_chunks_per_hypertable, newval);\n}\n\nstatic Oid\nget_segmentby_func(char *input_name)\n{\n\tList *namelist = NIL;\n\n\tif (strlen(input_name) == 0)\n\t{\n\t\treturn InvalidOid;\n\t}\n\n#if PG16_LT\n\tnamelist = stringToQualifiedNameList(input_name);\n#else\n\tnamelist = stringToQualifiedNameList(input_name, NULL);\n#endif\n\tOid argtyp[] = { REGCLASSOID };\n\treturn LookupFuncName(namelist, lengthof(argtyp), argtyp, true);\n}\n\nstatic bool\ncheck_segmentby_func(char **newval, void **extra, GucSource source)\n{\n\t/* if the extension doesn't exist you can't check for the function, have to take it on faith */\n\tif (ts_extension_is_loaded_and_not_upgrading())\n\t{\n\t\tOid segment_func_oid = get_segmentby_func(*newval);\n\n\t\tif (strlen(*newval) > 0 && !OidIsValid(segment_func_oid))\n\t\t{\n\t\t\tGUC_check_errdetail(\"Function \\\"%s\\\" does not exist.\", *newval);\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n\nOid\nts_guc_default_segmentby_fn_oid()\n{\n\treturn get_segmentby_func(ts_guc_default_segmentby_fn);\n}\n\nstatic Oid\nget_orderby_func(char *input_name)\n{\n\tList *namelist = NIL;\n\n\tif (strlen(input_name) == 0)\n\t{\n\t\treturn InvalidOid;\n\t}\n\n#if PG16_LT\n\tnamelist = stringToQualifiedNameList(input_name);\n#else\n\tnamelist = stringToQualifiedNameList(input_name, NULL);\n#endif\n\tOid argtyp[] = { REGCLASSOID, TEXTARRAYOID };\n\treturn LookupFuncName(namelist, lengthof(argtyp), argtyp, true);\n}\n\nstatic bool\ncheck_orderby_func(char **newval, void **extra, GucSource source)\n{\n\t/* if the extension doesn't exist you can't check for the function, have to take it on faith */\n\tif (ts_extension_is_loaded_and_not_upgrading())\n\t{\n\t\tOid func_oid = get_orderby_func(*newval);\n\n\t\tif (strlen(*newval) > 0 && !OidIsValid(func_oid))\n\t\t{\n\t\t\tGUC_check_errdetail(\"Function \\\"%s\\\" does not exist.\", *newval);\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n\nOid\nts_guc_default_orderby_fn_oid()\n{\n\treturn get_orderby_func(ts_guc_default_orderby_fn);\n}\n\n/*\n * Assign hook for chunk skipping.\n *\n * When chunk skipping is enabled, we need to clear the hypertable cache.\n * Otherwise there might be cached entries without a valid range_space entry,\n * which could lead to column stats not being created.\n */\nstatic void\nchunk_skipping_assign_hook(bool newval, void *extra)\n{\n\tif (newval)\n\t\tts_hypertable_cache_invalidate_callback();\n}\n\n#if PG16_LT\n/*\n * guc_malloc is not public in PostgreSQL < 16.\n */\nstatic void *\nguc_malloc(int elevel, size_t size)\n{\n\tvoid *data;\n\n\t/* Avoid unportable behavior of malloc(0) */\n\tif (size == 0)\n\t\tsize = 1;\n\tdata = malloc(size);\n\tif (data == NULL)\n\t\tereport(elevel, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg(\"out of memory\")));\n\treturn data;\n}\n#endif\n\nstatic bool\ncheck_default_chunk_time_interval(char **newval, void **extra, GucSource source)\n{\n\t/*\n\t * If GUC is unset, we treat that as a valid value for \"no default chunk interval\".\n\t * The chunk interval is instead computed in the legacy way using hard-coded defaults.\n\t */\n\tif (*newval == NULL)\n\t{\n\t\tAssert(*extra == NULL);\n\t\treturn true;\n\t}\n\n\t/* Test that the text value is a valid Interval */\n\tLOCAL_FCINFO(fcinfo, 3);\n\tInitFunctionCallInfoData(*fcinfo, NULL, 3, InvalidOid, NULL, NULL);\n\tfcinfo->args[0].value = CStringGetDatum(*newval);\n\tfcinfo->args[0].isnull = false;\n\tfcinfo->args[1].value = ObjectIdGetDatum(INTERVALOID);\n\tfcinfo->args[1].isnull = false;\n\tfcinfo->args[2].value = Int32GetDatum(-1);\n\tfcinfo->args[2].isnull = false;\n\n\tDatum interval = interval_in(fcinfo);\n\n\tif (fcinfo->isnull)\n\t{\n\t\tGUC_check_errdetail(\"The default chunk interval must be a valid INTERVAL.\");\n\t\treturn false;\n\t}\n\n\tInterval *parsed = DatumGetIntervalP(interval);\n\t/* Save the new Interval in extra. The old extra is freed automatically. */\n\t*extra = guc_malloc(ERROR, sizeof(Interval));\n\tmemcpy(*extra, parsed, sizeof(Interval));\n\tpfree(parsed);\n\n\treturn true;\n}\n\nInterval *default_chunk_time_interval = NULL;\n\nstatic void\nassign_default_chunk_time_interval(const char *newval, void *extra)\n{\n\tdefault_chunk_time_interval = extra;\n}\n\nvoid\n_guc_init(void)\n{\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_deprecation_warnings\"),\n\t\t\t\t\t\t\t \"Enable warnings when using deprecated functionality\",\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t &ts_guc_enable_deprecation_warnings,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_direct_compress_copy\"),\n\t\t\t\t\t\t\t \"Enable direct compression during COPY\",\n\t\t\t\t\t\t\t \"Enable experimental support for direct compression during COPY\",\n\t\t\t\t\t\t\t &ts_guc_enable_direct_compress_copy,\n\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_direct_compress_copy_sort_batches\"),\n\t\t\t\t\t\t\t \"Enable batch sorting during direct compress COPY\",\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t &ts_guc_enable_direct_compress_copy_sort_batches,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_direct_compress_copy_client_sorted\"),\n\t\t\t\t\t\t\t \"Enable direct compress COPY with presorted data\",\n\t\t\t\t\t\t\t \"Correct handling of data sorting by the user is required for this \"\n\t\t\t\t\t\t\t \"option.\",\n\t\t\t\t\t\t\t &ts_guc_enable_direct_compress_copy_client_sorted,\n\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomIntVariable(MAKE_EXTOPTION(\"direct_compress_copy_tuple_sort_limit\"),\n\t\t\t\t\t\t\t\"Number of tuples that can be sorted at once in a COPY operation\",\n\t\t\t\t\t\t\t\"This is mainly used to keep the memory footprint down for \"\n\t\t\t\t\t\t\t\"operations like importing large amounts of data in \"\n\t\t\t\t\t\t\t\"single transaction. Setting this to 0 would make it unlimited.\",\n\t\t\t\t\t\t\t&ts_guc_direct_compress_copy_tuple_sort_limit,\n\t\t\t\t\t\t\t100000,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t2147483647,\n\t\t\t\t\t\t\tPGC_USERSET,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\tNULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_direct_compress_insert\"),\n\t\t\t\t\t\t\t \"Enable direct compression during INSERT\",\n\t\t\t\t\t\t\t \"Enable experimental support for direct compression during INSERT\",\n\t\t\t\t\t\t\t &ts_guc_enable_direct_compress_insert,\n\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_direct_compress_insert_sort_batches\"),\n\t\t\t\t\t\t\t \"Enable batch sorting during direct compress INSERT\",\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t &ts_guc_enable_direct_compress_insert_sort_batches,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_direct_compress_insert_client_sorted\"),\n\t\t\t\t\t\t\t \"Enable direct compress INSERT with presorted data\",\n\t\t\t\t\t\t\t \"Correct handling of data sorting by the user is required for this \"\n\t\t\t\t\t\t\t \"option.\",\n\t\t\t\t\t\t\t &ts_guc_enable_direct_compress_insert_client_sorted,\n\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_direct_compress_on_cagg_refresh\"),\n\t\t\t\t\t\t\t \"Enable direct compress on Continuous Aggregate refresh\",\n\t\t\t\t\t\t\t \"Enable experimental support for direct compression during Continuous \"\n\t\t\t\t\t\t\t \"Aggregate refresh\",\n\t\t\t\t\t\t\t &ts_guc_enable_direct_compress_on_cagg_refresh,\n\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomIntVariable(MAKE_EXTOPTION(\"direct_compress_insert_tuple_sort_limit\"),\n\t\t\t\t\t\t\t\"Number of tuples that can be sorted at once in an INSERT operation\",\n\t\t\t\t\t\t\t\"This is mainly used to keep the memory footprint down for \"\n\t\t\t\t\t\t\t\"operations like importing large amounts of data in \"\n\t\t\t\t\t\t\t\"single transaction. Setting this to 0 would make it unlimited.\",\n\t\t\t\t\t\t\t&ts_guc_direct_compress_insert_tuple_sort_limit,\n\t\t\t\t\t\t\t10000,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t2147483647,\n\t\t\t\t\t\t\tPGC_USERSET,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\tNULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_optimizations\"),\n\t\t\t\t\t\t\t \"Enable TimescaleDB query optimizations\",\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t &ts_guc_enable_optimizations,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"restoring\"),\n\t\t\t\t\t\t\t \"Enable restoring mode for timescaledb\",\n\t\t\t\t\t\t\t \"In restoring mode all timescaledb internal hooks are disabled. This \"\n\t\t\t\t\t\t\t \"mode is required for restoring logical dumps of databases with \"\n\t\t\t\t\t\t\t \"timescaledb.\",\n\t\t\t\t\t\t\t &ts_guc_restoring,\n\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t PGC_SUSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_constraint_aware_append\"),\n\t\t\t\t\t\t\t \"Enable constraint-aware append scans\",\n\t\t\t\t\t\t\t \"Enable constraint exclusion at execution time\",\n\t\t\t\t\t\t\t &ts_guc_enable_constraint_aware_append,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_ordered_append\"),\n\t\t\t\t\t\t\t \"Enable ordered append scans\",\n\t\t\t\t\t\t\t \"Enable ordered append optimization for queries that are ordered by \"\n\t\t\t\t\t\t\t \"the time dimension\",\n\t\t\t\t\t\t\t &ts_guc_enable_ordered_append,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_chunk_append\"),\n\t\t\t\t\t\t\t \"Enable chunk append node\",\n\t\t\t\t\t\t\t \"Enable using chunk append node\",\n\t\t\t\t\t\t\t &ts_guc_enable_chunk_append,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_parallel_chunk_append\"),\n\t\t\t\t\t\t\t \"Enable parallel chunk append node\",\n\t\t\t\t\t\t\t \"Enable using parallel aware chunk append node\",\n\t\t\t\t\t\t\t &ts_guc_enable_parallel_chunk_append,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_runtime_exclusion\"),\n\t\t\t\t\t\t\t \"Enable runtime chunk exclusion\",\n\t\t\t\t\t\t\t \"Enable runtime chunk exclusion in ChunkAppend node\",\n\t\t\t\t\t\t\t &ts_guc_enable_runtime_exclusion,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_constraint_exclusion\"),\n\t\t\t\t\t\t\t \"Enable constraint exclusion\",\n\t\t\t\t\t\t\t \"Enable planner constraint exclusion\",\n\t\t\t\t\t\t\t &ts_guc_enable_constraint_exclusion,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_foreign_key_propagation\"),\n\t\t\t\t\t\t\t \"Enable foreign key propagation\",\n\t\t\t\t\t\t\t \"Adjust foreign key lookup queries to target whole hypertable\",\n\t\t\t\t\t\t\t &ts_guc_enable_foreign_key_propagation,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_qual_filtering\"),\n\t\t\t\t\t\t\t \"Enable qualifier filtering for chunks\",\n\t\t\t\t\t\t\t \"Filter qualifiers on chunks when complete chunk would be included by \"\n\t\t\t\t\t\t\t \"filter\",\n\t\t\t\t\t\t\t &ts_guc_enable_qual_filtering,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_qual_propagation\"),\n\t\t\t\t\t\t\t \"Enable qualifier propagation\",\n\t\t\t\t\t\t\t \"Enable propagation of qualifiers in JOINs\",\n\t\t\t\t\t\t\t &ts_guc_enable_qual_propagation,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_columnar_scan_filter_pushdown\"),\n\t\t\t\t\t\t\t \"Enable columnar scan filter pushdown\",\n\t\t\t\t\t\t\t \"Enable pushing down the filters into the compressed scan part of the \"\n\t\t\t\t\t\t\t \"columnar scan\",\n\t\t\t\t\t\t\t &ts_guc_enable_columnar_scan_filter_pushdown,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_dml_decompression\"),\n\t\t\t\t\t\t\t \"Enable DML decompression\",\n\t\t\t\t\t\t\t \"Enable DML decompression when modifying compressed hypertable\",\n\t\t\t\t\t\t\t &ts_guc_enable_dml_decompression,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_dml_decompression_tuple_filtering\"),\n\t\t\t\t\t\t\t \"Enable DML decompression tuple filtering\",\n\t\t\t\t\t\t\t \"Recheck tuples during DML decompression to only decompress batches \"\n\t\t\t\t\t\t\t \"with matching tuples\",\n\t\t\t\t\t\t\t &ts_guc_enable_dml_decompression_tuple_filtering,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_dml_bloom_filter\"),\n\t\t\t\t\t\t\t \"Enable bloom filter pruning for DML on compressed chunks\",\n\t\t\t\t\t\t\t \"When enabled, bloom filters are used to skip compressed batches \"\n\t\t\t\t\t\t\t \"that definitely do not contain matching rows during DELETE and \"\n\t\t\t\t\t\t\t \"UPDATE operations, reducing decompression overhead.\",\n\t\t\t\t\t\t\t &ts_guc_enable_dml_bloom_filter,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_compressed_direct_batch_delete\"),\n\t\t\t\t\t\t\t \"Enable direct deletion of compressed batches\",\n\t\t\t\t\t\t\t \"Enable direct batch deletion in compressed chunks\",\n\t\t\t\t\t\t\t &ts_guc_enable_compressed_direct_batch_delete,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomIntVariable(MAKE_EXTOPTION(\"max_tuples_decompressed_per_dml_transaction\"),\n\t\t\t\t\t\t\t\"The max number of tuples that can be decompressed during an \"\n\t\t\t\t\t\t\t\"INSERT, UPDATE, or DELETE.\",\n\t\t\t\t\t\t\t\" If the number of tuples exceeds this value, an error will \"\n\t\t\t\t\t\t\t\"be thrown and transaction rolled back. \"\n\t\t\t\t\t\t\t\"Setting this to 0 sets this value to unlimited number of \"\n\t\t\t\t\t\t\t\"tuples decompressed.\",\n\t\t\t\t\t\t\t&ts_guc_max_tuples_decompressed_per_dml,\n\t\t\t\t\t\t\t100000,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t2147483647,\n\t\t\t\t\t\t\tPGC_USERSET,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\tNULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_skipscan\"),\n\t\t\t\t\t\t\t \"Enable SkipScan\",\n\t\t\t\t\t\t\t \"Enable SkipScan for DISTINCT queries\",\n\t\t\t\t\t\t\t &ts_guc_enable_skip_scan,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n#if PG16_GE\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_skipscan_for_distinct_aggregates\"),\n\t\t\t\t\t\t\t \"Enable SkipScan for DISTINCT aggregates\",\n\t\t\t\t\t\t\t \"Enable SkipScan for DISTINCT aggregates\",\n\t\t\t\t\t\t\t &ts_guc_enable_skip_scan_for_distinct_aggregates,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n#endif\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_compressed_skipscan\"),\n\t\t\t\t\t\t\t \"Enable SkipScan for compressed chunks\",\n\t\t\t\t\t\t\t \"Enable SkipScan for distinct inputs over compressed chunks\",\n\t\t\t\t\t\t\t &ts_guc_enable_compressed_skip_scan,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_multikey_skipscan\"),\n\t\t\t\t\t\t\t \"Enable SkipScan for multiple distinct keys\",\n\t\t\t\t\t\t\t \"Enable SkipScan for multiple distinct inputs\",\n\t\t\t\t\t\t\t &ts_guc_enable_multikey_skip_scan,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomRealVariable(MAKE_EXTOPTION(\"skip_scan_run_cost_multiplier\"),\n\t\t\t\t\t\t\t \"Multiplier for SkipScan run cost as an option to make the cost \"\n\t\t\t\t\t\t\t \"smaller so that SkipScan can be chosen\",\n\t\t\t\t\t\t\t \"Default is 1.0 i.e. regularly estimated SkipScan run cost, 0.0 will \"\n\t\t\t\t\t\t\t \"make SkipScan to have run cost = 0\",\n\t\t\t\t\t\t\t &ts_guc_skip_scan_run_cost_multiplier,\n\t\t\t\t\t\t\t 1.0,\n\t\t\t\t\t\t\t 0.0,\n\t\t\t\t\t\t\t 1.0,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"debug_skip_scan_info\"),\n\t\t\t\t\t\t\t \"Print debug info about SkipScan\",\n\t\t\t\t\t\t\t \"Print debug info about SkipScan distinct columns\",\n\t\t\t\t\t\t\t &ts_guc_debug_skip_scan_info,\n\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_compression_wal_markers\"),\n\t\t\t\t\t\t\t \"Enable WAL markers for compression ops\",\n\t\t\t\t\t\t\t \"Enable the generation of markers in the WAL stream which mark the \"\n\t\t\t\t\t\t\t \"start and end of compression operations\",\n\t\t\t\t\t\t\t &ts_guc_enable_compression_wal_markers,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_SIGHUP,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_decompression_sorted_merge\"),\n\t\t\t\t\t\t\t \"Enable compressed batches heap merge\",\n\t\t\t\t\t\t\t \"Enable the merge of compressed batches to preserve the compression \"\n\t\t\t\t\t\t\t \"order by\",\n\t\t\t\t\t\t\t &ts_guc_enable_decompression_sorted_merge,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_cagg_reorder_groupby\"),\n\t\t\t\t\t\t\t \"Enable group by reordering\",\n\t\t\t\t\t\t\t \"Enable group by clause reordering for continuous aggregates\",\n\t\t\t\t\t\t\t &ts_guc_enable_cagg_reorder_groupby,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_cagg_window_functions\"),\n\t\t\t\t\t\t\t \"Enable window functions in continuous aggregates\",\n\t\t\t\t\t\t\t \"Allow window functions in continuous aggregate views\",\n\t\t\t\t\t\t\t &ts_guc_enable_cagg_window_functions,\n\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_now_constify\"),\n\t\t\t\t\t\t\t \"Enable now() constify\",\n\t\t\t\t\t\t\t \"Enable constifying now() in query constraints\",\n\t\t\t\t\t\t\t &ts_guc_enable_now_constify,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n#if PG16_GE\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_cagg_sort_pushdown\"),\n\t\t\t\t\t\t\t \"Enable sort pushdown for continuous aggregates\",\n\t\t\t\t\t\t\t \"Enable pushdown of ORDER BY clause for continuous aggregates\",\n\t\t\t\t\t\t\t &ts_guc_enable_cagg_sort_pushdown,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n#endif\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_cagg_watermark_constify\"),\n\t\t\t\t\t\t\t \"Enable cagg watermark constify\",\n\t\t\t\t\t\t\t \"Enable constifying cagg watermark for real-time caggs\",\n\t\t\t\t\t\t\t &ts_guc_enable_cagg_watermark_constify,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_merge_on_cagg_refresh\"),\n\t\t\t\t\t\t\t \"Enable MERGE statement on cagg refresh\",\n\t\t\t\t\t\t\t \"Enable MERGE statement on cagg refresh\",\n\t\t\t\t\t\t\t &ts_guc_enable_merge_on_cagg_refresh,\n\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_chunk_skipping\"),\n\t\t\t\t\t\t\t \"Enable chunk skipping functionality\",\n\t\t\t\t\t\t\t \"Enable using chunk column stats to filter chunks based on column \"\n\t\t\t\t\t\t\t \"filters\",\n\t\t\t\t\t\t\t &ts_guc_enable_chunk_skipping,\n\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t chunk_skipping_assign_hook,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_segmentwise_recompression\"),\n\t\t\t\t\t\t\t \"Enable segmentwise recompression functionality\",\n\t\t\t\t\t\t\t \"Enable segmentwise recompression\",\n\t\t\t\t\t\t\t &ts_guc_enable_segmentwise_recompression,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_in_memory_recompression\"),\n\t\t\t\t\t\t\t \"Enable in-memory recompression functionality\",\n\t\t\t\t\t\t\t \"Enable in-memory recompression\",\n\t\t\t\t\t\t\t &ts_guc_enable_in_memory_recompression,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_exclusive_locking_recompression\"),\n\t\t\t\t\t\t\t \"Enable exclusive locking recompression\",\n\t\t\t\t\t\t\t \"Enable getting exclusive lock on chunk during segmentwise \"\n\t\t\t\t\t\t\t \"recompression\",\n\t\t\t\t\t\t\t &ts_guc_enable_exclusive_locking_recompression,\n\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_bool_compression\"),\n\t\t\t\t\t\t\t \"Enable bool compression functionality\",\n\t\t\t\t\t\t\t \"Enable bool compression\",\n\t\t\t\t\t\t\t &ts_guc_enable_bool_compression,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_uuid_compression\"),\n\t\t\t\t\t\t\t \"Enable uuid compression functionality\",\n\t\t\t\t\t\t\t \"Enable uuid compression\",\n\t\t\t\t\t\t\t &ts_guc_enable_uuid_compression,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomIntVariable(MAKE_EXTOPTION(\"compression_batch_size_limit\"),\n\t\t\t\t\t\t\t\"The max number of tuples that can be batched together during \"\n\t\t\t\t\t\t\t\"compression\",\n\t\t\t\t\t\t\t\"Setting this option to a number between 1 and 32767 will force \"\n\t\t\t\t\t\t\t\"compression \"\n\t\t\t\t\t\t\t\"to limit the size of compressed batches to that amount of \"\n\t\t\t\t\t\t\t\"uncompressed tuples. The setting influences only the compression \"\n\t\t\t\t\t\t\t\"process itself. The value of the setting is taken from the context \"\n\t\t\t\t\t\t\t\"of the session where the compression is performed. It is not \"\n\t\t\t\t\t\t\t\"persisted in any way.\",\n\t\t\t\t\t\t\t&ts_guc_compression_batch_size_limit,\n\t\t\t\t\t\t\tTARGET_COMPRESSED_BATCH_SIZE,\n\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\tGLOBAL_MAX_ROWS_PER_COMPRESSION,\n\t\t\t\t\t\t\tPGC_USERSET,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\tNULL);\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_compressor_batch_limit\"),\n\t\t\t\t\t\t\t \"Enable compressor batch limit\",\n\t\t\t\t\t\t\t \"Enable compressor batch limit for compressors which \"\n\t\t\t\t\t\t\t \"can go over the allocation limit (1 GB). This feature will \"\n\t\t\t\t\t\t\t \"limit those compressors by reducing the size of the batch and thus \"\n\t\t\t\t\t\t\t \"avoid hitting the limit.\",\n\t\t\t\t\t\t\t &ts_guc_compression_enable_compressor_batch_limit,\n\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_event_triggers\"),\n\t\t\t\t\t\t\t \"Enable event triggers for chunks creation\",\n\t\t\t\t\t\t\t \"Enable event triggers for chunks creation\",\n\t\t\t\t\t\t\t &ts_guc_enable_event_triggers,\n\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t PGC_SUSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_chunk_auto_publication\"),\n\t\t\t\t\t\t\t \"Enable automatic chunk publication\",\n\t\t\t\t\t\t\t \"Enable automatically adding newly created chunks to the publication \"\n\t\t\t\t\t\t\t \"of their hypertable\",\n\t\t\t\t\t\t\t &ts_guc_enable_chunk_auto_publication,\n\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n#ifdef TS_DEBUG\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_null_compression\"),\n\t\t\t\t\t\t\t \"Debug only flag to enable NULL compression\",\n\t\t\t\t\t\t\t \"Enable null compression\",\n\t\t\t\t\t\t\t &ts_guc_enable_null_compression,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n#endif\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_compression_ratio_warnings\"),\n\t\t\t\t\t\t\t \"Enable warnings for poor compression ratio\",\n\t\t\t\t\t\t\t \"Enable warnings for poor compression ratio\",\n\t\t\t\t\t\t\t &ts_guc_enable_compression_ratio_warnings,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_tiered_reads\"),\n\t\t\t\t\t\t\t \"Enable tiered data reads\",\n\t\t\t\t\t\t\t \"Enable reading of tiered data by including a foreign table \"\n\t\t\t\t\t\t\t \"representing the data in the object storage into the query plan\",\n\t\t\t\t\t\t\t &ts_guc_enable_osm_reads,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_chunkwise_aggregation\"),\n\t\t\t\t\t\t\t \"Enable chunk-wise aggregation\",\n\t\t\t\t\t\t\t \"Enable the pushdown of aggregations to the\"\n\t\t\t\t\t\t\t \" chunk level\",\n\t\t\t\t\t\t\t &ts_guc_enable_chunkwise_aggregation,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_vectorized_aggregation\"),\n\t\t\t\t\t\t\t \"Enable vectorized aggregation\",\n\t\t\t\t\t\t\t \"Enable vectorized aggregation for compressed data\",\n\t\t\t\t\t\t\t &ts_guc_enable_vectorized_aggregation,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_compression_indexscan\"),\n\t\t\t\t\t\t\t \"Enable compression to take indexscan path\",\n\t\t\t\t\t\t\t \"Enable indexscan during compression, if matching index is found\",\n\t\t\t\t\t\t\t &ts_guc_enable_compression_indexscan,\n\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_bulk_decompression\"),\n\t\t\t\t\t\t\t \"Enable decompression of the entire compressed batches\",\n\t\t\t\t\t\t\t \"Increases throughput of decompression, but might increase query \"\n\t\t\t\t\t\t\t \"memory usage\",\n\t\t\t\t\t\t\t &ts_guc_enable_bulk_decompression,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"auto_sparse_indexes\"),\n\t\t\t\t\t\t\t \"Create sparse indexes on compressed chunks\",\n\t\t\t\t\t\t\t \"The hypertable columns that are used as index keys will have \"\n\t\t\t\t\t\t\t \"suitable sparse indexes when compressed. Must be set at the moment \"\n\t\t\t\t\t\t\t \"of chunk compression, e.g. when the `compress_chunk()` is called.\",\n\t\t\t\t\t\t\t &ts_guc_auto_sparse_indexes,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_sparse_index_bloom\"),\n\t\t\t\t\t\t\t \"Enable creation of the bloom1 sparse index on compressed chunks\",\n\t\t\t\t\t\t\t \"This sparse index speeds up the equality queries on compressed \"\n\t\t\t\t\t\t\t \"columns, and can be disabled when not desired.\",\n\t\t\t\t\t\t\t &ts_guc_enable_sparse_index_bloom,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_composite_bloom_indexes\"),\n\t\t\t\t\t\t\t \"Enable creation of the bloom1 composite index on compressed chunks\",\n\t\t\t\t\t\t\t \"This composite index speeds up the equality queries on compressed \"\n\t\t\t\t\t\t\t \"columns, and can be disabled when not desired.\",\n\t\t\t\t\t\t\t &ts_guc_enable_composite_bloom_indexes,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"read_legacy_bloom1_v1\"),\n\t\t\t\t\t\t\t \"Enable reading the legacy bloom1 version 1 sparse indexes for SELECT \"\n\t\t\t\t\t\t\t \"queries\",\n\t\t\t\t\t\t\t \"These legacy indexes might give false negatives if they were built \"\n\t\t\t\t\t\t\t \"by the TimescaleDB extension compiled with different build options.\",\n\t\t\t\t\t\t\t &ts_guc_read_legacy_bloom1_v1,\n\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_columnarscan\"),\n\t\t\t\t\t\t\t \"Enable ColumnarScan for columnar storage\",\n\t\t\t\t\t\t\t \"Transparently decompress columnar data using ColumnarScan custom \"\n\t\t\t\t\t\t\t \"node. Disabling columnar scan will ignore data stored in columnar \"\n\t\t\t\t\t\t\t \"format in queries.\",\n\t\t\t\t\t\t\t &ts_guc_enable_columnarscan,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_columnarindexscan\"),\n\t\t\t\t\t\t\t \"Enable metadata-only optimization for ColumnarScans\",\n\t\t\t\t\t\t\t \"Enable experimental support for returning results directly from \"\n\t\t\t\t\t\t\t \"compression metadata without decompression\",\n\t\t\t\t\t\t\t &ts_guc_enable_columnarindexscan,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomIntVariable(MAKE_EXTOPTION(\"max_open_chunks_per_insert\"),\n\t\t\t\t\t\t\t\"Maximum open chunks per insert\",\n\t\t\t\t\t\t\t\"Maximum number of open chunk tables per insert\",\n\t\t\t\t\t\t\t&ts_guc_max_open_chunks_per_insert,\n\t\t\t\t\t\t\t1024,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\tPG_INT16_MAX,\n\t\t\t\t\t\t\tPGC_USERSET,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\tassign_max_open_chunks_per_insert_hook,\n\t\t\t\t\t\t\tNULL);\n\n\tDefineCustomIntVariable(MAKE_EXTOPTION(\"max_cached_chunks_per_hypertable\"),\n\t\t\t\t\t\t\t\"Maximum cached chunks\",\n\t\t\t\t\t\t\t\"Maximum number of chunks stored in the cache\",\n\t\t\t\t\t\t\t&ts_guc_max_cached_chunks_per_hypertable,\n\t\t\t\t\t\t\t1024,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t65536,\n\t\t\t\t\t\t\tPGC_USERSET,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\tassign_max_cached_chunks_per_hypertable_hook,\n\t\t\t\t\t\t\tNULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_job_execution_logging\"),\n\t\t\t\t\t\t\t \"Enable job execution logging\",\n\t\t\t\t\t\t\t \"Retain job run status in logging table\",\n\t\t\t\t\t\t\t &ts_guc_enable_job_execution_logging,\n\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t PGC_SIGHUP,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_tss_callbacks\"),\n\t\t\t\t\t\t\t \"Enable ts_stat_statements callbacks\",\n\t\t\t\t\t\t\t \"Enable ts_stat_statements callbacks\",\n\t\t\t\t\t\t\t &ts_guc_enable_tss_callbacks,\n\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t PGC_SUSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_delete_after_compression\"),\n\t\t\t\t\t\t\t \"Delete all rows after compression instead of truncate\",\n\t\t\t\t\t\t\t \"Delete all rows after compression instead of truncate\",\n\t\t\t\t\t\t\t &ts_guc_enable_delete_after_compression,\n\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\tDefineCustomEnumVariable(MAKE_EXTOPTION(\"compress_truncate_behaviour\"),\n\t\t\t\t\t\t\t \"Define behaviour of truncate after compression\",\n\t\t\t\t\t\t\t \"Defines how truncate behaves at the end of compression. \"\n\t\t\t\t\t\t\t \"'truncate_only' forces truncation. 'truncate_disabled' deletes rows \"\n\t\t\t\t\t\t\t \"instead of truncate. 'truncate_or_delete' allows falling back to \"\n\t\t\t\t\t\t\t \"deletion.\",\n\t\t\t\t\t\t\t (int *) &ts_guc_compress_truncate_behaviour,\n\t\t\t\t\t\t\t COMPRESS_TRUNCATE_ONLY,\n\t\t\t\t\t\t\t compress_truncate_behaviour_options,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n#ifdef TS_DEBUG\n\tDefineCustomBoolVariable(MAKE_EXTOPTION(\"enable_partitioned_hypertables\"),\n\t\t\t\t\t\t\t \"Enable hypertables using declarative partitioning\",\n\t\t\t\t\t\t\t \"Enable experimental support for creating hypertables using \"\n\t\t\t\t\t\t\t \"PostgreSQL's native declarative partitioning\",\n\t\t\t\t\t\t\t &ts_guc_enable_partitioned_hypertables,\n\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n#endif\n\n#ifdef USE_TELEMETRY\n\tDefineCustomEnumVariable(MAKE_EXTOPTION(\"telemetry_level\"),\n\t\t\t\t\t\t\t \"Telemetry settings level\",\n\t\t\t\t\t\t\t \"Level used to determine which telemetry to send\",\n\t\t\t\t\t\t\t (int *) &ts_guc_telemetry_level,\n\t\t\t\t\t\t\t TELEMETRY_DEFAULT,\n\t\t\t\t\t\t\t telemetry_level_options,\n\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n#endif\n\n\tDefineCustomStringVariable(/* name= */ MAKE_EXTOPTION(\"compression_segmentby_default_function\"),\n\t\t\t\t\t\t\t   /* short_desc= */ \"Function that sets default segment_by\",\n\t\t\t\t\t\t\t   /* long_desc= */\n\t\t\t\t\t\t\t   \"Function to use for calculating default segment_by setting for \"\n\t\t\t\t\t\t\t   \"compression\",\n\t\t\t\t\t\t\t   /* valueAddr= */ &ts_guc_default_segmentby_fn,\n\t\t\t\t\t\t\t   /* Value= */ \"_timescaledb_functions.get_segmentby_defaults\",\n\t\t\t\t\t\t\t   /* context= */ PGC_USERSET,\n\t\t\t\t\t\t\t   /* flags= */ 0,\n\t\t\t\t\t\t\t   /* check_hook= */ check_segmentby_func,\n\t\t\t\t\t\t\t   /* assign_hook= */ NULL,\n\t\t\t\t\t\t\t   /* show_hook= */ NULL);\n\n\tDefineCustomStringVariable(/* name= */ MAKE_EXTOPTION(\"compression_orderby_default_function\"),\n\t\t\t\t\t\t\t   /* short_desc= */ \"Function that sets default order_by\",\n\t\t\t\t\t\t\t   /* long_desc= */\n\t\t\t\t\t\t\t   \"Function to use for calculating default order_by setting for \"\n\t\t\t\t\t\t\t   \"compression\",\n\t\t\t\t\t\t\t   /* valueAddr= */ &ts_guc_default_orderby_fn,\n\t\t\t\t\t\t\t   /* Value= */ \"_timescaledb_functions.get_orderby_defaults\",\n\t\t\t\t\t\t\t   /* context= */ PGC_USERSET,\n\t\t\t\t\t\t\t   /* flags= */ 0,\n\t\t\t\t\t\t\t   /* check_hook= */ check_orderby_func,\n\t\t\t\t\t\t\t   /* assign_hook= */ NULL,\n\t\t\t\t\t\t\t   /* show_hook= */ NULL);\n\n\tDefineCustomStringVariable(/* name= */ MAKE_EXTOPTION(\"license\"),\n\t\t\t\t\t\t\t   /* short_desc= */ \"TimescaleDB license type\",\n\t\t\t\t\t\t\t   /* long_desc= */ \"Determines which features are enabled\",\n\t\t\t\t\t\t\t   /* valueAddr= */ &ts_guc_license,\n\t\t\t\t\t\t\t   /* bootValue= */ TS_LICENSE_DEFAULT,\n\t\t\t\t\t\t\t   /* context= */ PGC_SUSET,\n\t\t\t\t\t\t\t   /* flags= */ 0,\n\t\t\t\t\t\t\t   /* check_hook= */ ts_license_guc_check_hook,\n\t\t\t\t\t\t\t   /* assign_hook= */ ts_license_guc_assign_hook,\n\t\t\t\t\t\t\t   /* show_hook= */ NULL);\n\n\tDefineCustomEnumVariable(MAKE_EXTOPTION(\"bgw_log_level\"),\n\t\t\t\t\t\t\t \"Log level for the background worker subsystem\",\n\t\t\t\t\t\t\t \"Log level for the scheduler and workers of the background worker \"\n\t\t\t\t\t\t\t \"subsystem. Requires configuration reload to change.\",\n\t\t\t\t\t\t\t /* valueAddr= */ &ts_guc_bgw_log_level,\n\t\t\t\t\t\t\t /* bootValue= */ WARNING,\n\t\t\t\t\t\t\t /* options= */ loglevel_options,\n\t\t\t\t\t\t\t /* context= */ PGC_SUSET,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL);\n\n\t/* this information is useful in general on customer deployments */\n\tDefineCustomBoolVariable(/* name= */ MAKE_EXTOPTION(\"debug_compression_path_info\"),\n\t\t\t\t\t\t\t /* short_desc= */ \"show various compression-related debug info\",\n\t\t\t\t\t\t\t /* long_desc= */ \"this is for debugging/information purposes\",\n\t\t\t\t\t\t\t /* valueAddr= */ &ts_guc_debug_compression_path_info,\n\t\t\t\t\t\t\t /* bootValue= */ false,\n\t\t\t\t\t\t\t /* context= */ PGC_USERSET,\n\t\t\t\t\t\t\t /* flags= */ 0,\n\t\t\t\t\t\t\t /* check_hook= */ NULL,\n\t\t\t\t\t\t\t /* assign_hook= */ NULL,\n\t\t\t\t\t\t\t /* show_hook= */ NULL);\n\n\tDefineCustomBoolVariable(/* name= */ MAKE_EXTOPTION(\"enable_rowlevel_compression_locking\"),\n\t\t\t\t\t\t\t /* short_desc= */ \"Use rowlevel locking during compression\",\n\t\t\t\t\t\t\t /* long_desc= */ \"Use only if you know what you are doing\",\n\t\t\t\t\t\t\t /* valueAddr= */ &ts_guc_enable_rowlevel_compression_locking,\n\t\t\t\t\t\t\t /* bootValue= */ false,\n\t\t\t\t\t\t\t /* context= */ PGC_USERSET,\n\t\t\t\t\t\t\t /* flags= */ 0,\n\t\t\t\t\t\t\t /* check_hook= */ NULL,\n\t\t\t\t\t\t\t /* assign_hook= */ NULL,\n\t\t\t\t\t\t\t /* show_hook= */ NULL);\n\n#ifdef USE_TELEMETRY\n\tDefineCustomStringVariable(/* name= */ \"timescaledb_telemetry.cloud\",\n\t\t\t\t\t\t\t   /* short_desc= */ \"cloud provider\",\n\t\t\t\t\t\t\t   /* long_desc= */ \"cloud provider used for this instance\",\n\t\t\t\t\t\t\t   /* valueAddr= */ &ts_telemetry_cloud,\n\t\t\t\t\t\t\t   /* bootValue= */ NULL,\n\t\t\t\t\t\t\t   /* context= */ PGC_SIGHUP,\n\t\t\t\t\t\t\t   /* flags= */ 0,\n\t\t\t\t\t\t\t   /* check_hook= */ NULL,\n\t\t\t\t\t\t\t   /* assign_hook= */ NULL,\n\t\t\t\t\t\t\t   /* show_hook= */ NULL);\n#endif\n\n\tDefineCustomIntVariable(/* name= */ MAKE_EXTOPTION(\"debug_bgw_scheduler_exit_status\"),\n\t\t\t\t\t\t\t/* short_desc= */ \"exit status to use when shutting down the scheduler\",\n\t\t\t\t\t\t\t/* long_desc= */ \"this is for debugging purposes\",\n\t\t\t\t\t\t\t/* valueAddr= */ &ts_debug_bgw_scheduler_exit_status,\n\t\t\t\t\t\t\t/* bootValue= */ 0,\n\t\t\t\t\t\t\t/* minValue= */ 0,\n\t\t\t\t\t\t\t/* maxValue= */ 255,\n\t\t\t\t\t\t\t/* context= */ PGC_SIGHUP,\n\t\t\t\t\t\t\t/* flags= */ 0,\n\t\t\t\t\t\t\t/* check_hook= */ NULL,\n\t\t\t\t\t\t\t/* assign_hook= */ NULL,\n\t\t\t\t\t\t\t/* show_hook= */ NULL);\n\n\tDefineCustomEnumVariable(/* name= */ MAKE_EXTOPTION(\"debug_require_batch_sorted_merge\"),\n\t\t\t\t\t\t\t /* short_desc= */ \"require batch sorted merge in ColumnarScan node\",\n\t\t\t\t\t\t\t /* long_desc= */ \"this is for debugging purposes\",\n\t\t\t\t\t\t\t /* valueAddr= */ (int *) &ts_guc_debug_require_batch_sorted_merge,\n\t\t\t\t\t\t\t /* bootValue= */ DRO_Allow,\n\t\t\t\t\t\t\t /* options = */ debug_require_options,\n\t\t\t\t\t\t\t /* context= */ PGC_USERSET,\n\t\t\t\t\t\t\t /* flags= */ 0,\n\t\t\t\t\t\t\t /* check_hook= */ NULL,\n\t\t\t\t\t\t\t /* assign_hook= */ NULL,\n\t\t\t\t\t\t\t /* show_hook= */ NULL);\n\n\tDefineCustomStringVariable(/* name= */ MAKE_EXTOPTION(\"default_chunk_time_interval\"),\n\t\t\t\t\t\t\t   /* short_desc= */ \"Default chunk time interval for new hypertables\",\n\t\t\t\t\t\t\t   /* long_desc= */\n\t\t\t\t\t\t\t   \"Chunk time interval to use for a new hypertable, unless a specific \"\n\t\t\t\t\t\t\t   \"chunk time interval is set on the hypertable. The default chunk \"\n\t\t\t\t\t\t\t   \"interval is only used for hypertables with a compatible time \"\n\t\t\t\t\t\t\t   \"type, e.g., timestamp, date, and UUID (v7). Hypertables using an \"\n\t\t\t\t\t\t\t   \"integer partitioning column have hard-coded defaults.\"\n\t\t\t\t\t\t\t   \"Expert-level setting. These parameters are optimized for internal \"\n\t\t\t\t\t\t\t   \"workflows; incorrect configurations can negatively impact query \"\n\t\t\t\t\t\t\t   \"performance and system efficiency.\",\n\t\t\t\t\t\t\t   /* valueAddr= */ &ts_guc_default_chunk_time_interval,\n\t\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t\t   /* context= */ PGC_USERSET,\n\t\t\t\t\t\t\t   /* flags= */ 0,\n\t\t\t\t\t\t\t   /* check_hook= */ check_default_chunk_time_interval,\n\t\t\t\t\t\t\t   /* assign_hook= */ assign_default_chunk_time_interval,\n\t\t\t\t\t\t\t   /* show_hook= */ NULL);\n\n#ifdef TS_DEBUG\n\tDefineCustomBoolVariable(/* name= */ MAKE_EXTOPTION(\"shutdown_bgw_scheduler\"),\n\t\t\t\t\t\t\t /* short_desc= */ \"immediately shutdown the bgw scheduler\",\n\t\t\t\t\t\t\t /* long_desc= */ \"this is for debugging purposes\",\n\t\t\t\t\t\t\t /* valueAddr= */ &ts_shutdown_bgw,\n\t\t\t\t\t\t\t /* bootValue= */ false,\n\t\t\t\t\t\t\t /* context= */ PGC_SIGHUP,\n\t\t\t\t\t\t\t /* flags= */ 0,\n\t\t\t\t\t\t\t /* check_hook= */ NULL,\n\t\t\t\t\t\t\t /* assign_hook= */ NULL,\n\t\t\t\t\t\t\t /* show_hook= */ NULL);\n\n\tDefineCustomStringVariable(/* name= */ MAKE_EXTOPTION(\"current_timestamp_mock\"),\n\t\t\t\t\t\t\t   /* short_desc= */ \"set the current timestamp\",\n\t\t\t\t\t\t\t   /* long_desc= */ \"this is for debugging purposes\",\n\t\t\t\t\t\t\t   /* valueAddr= */ &ts_current_timestamp_mock,\n\t\t\t\t\t\t\t   /* bootValue= */ NULL,\n\t\t\t\t\t\t\t   /* context= */ PGC_USERSET,\n\t\t\t\t\t\t\t   /* flags= */ 0,\n\t\t\t\t\t\t\t   /* check_hook= */ NULL,\n\t\t\t\t\t\t\t   /* assign_hook= */ NULL,\n\t\t\t\t\t\t\t   /* show_hook= */ NULL);\n\n\tDefineCustomIntVariable(/* name= */ MAKE_EXTOPTION(\"debug_toast_tuple_target\"),\n\t\t\t\t\t\t\t/* short_desc= */ \"set toast tuple target on compressed chunks\",\n\t\t\t\t\t\t\t/* long_desc= */ \"this is for debugging purposes\",\n\t\t\t\t\t\t\t/* valueAddr= */ &ts_guc_debug_toast_tuple_target,\n\t\t\t\t\t\t\t/* bootValue = */ 128,\n\t\t\t\t\t\t\t/* minValue = */ 1,\n\t\t\t\t\t\t\t/* maxValue = */ 65535,\n\t\t\t\t\t\t\t/* context= */ PGC_USERSET,\n\t\t\t\t\t\t\t/* flags= */ 0,\n\t\t\t\t\t\t\t/* check_hook= */ NULL,\n\t\t\t\t\t\t\t/* assign_hook= */ NULL,\n\t\t\t\t\t\t\t/* show_hook= */ NULL);\n\n\tDefineCustomBoolVariable(/* name= */ MAKE_EXTOPTION(\"debug_have_int128\"),\n\t\t\t\t\t\t\t /* short_desc= */ \"whether we have int128 support\",\n\t\t\t\t\t\t\t /* long_desc= */ \"this is for debugging purposes\",\n\t\t\t\t\t\t\t /* valueAddr= */ &ts_guc_debug_have_int128,\n#ifdef HAVE_INT128\n\t\t\t\t\t\t\t /* bootValue= */ true,\n#else\n\t\t\t\t\t\t\t /* bootValue= */ false,\n#endif\n\t\t\t\t\t\t\t /* context= */ PGC_INTERNAL,\n\t\t\t\t\t\t\t /* flags= */ 0,\n\t\t\t\t\t\t\t /* check_hook= */ NULL,\n\t\t\t\t\t\t\t /* assign_hook= */ NULL,\n\t\t\t\t\t\t\t /* show_hook= */ NULL);\n\n\tDefineCustomEnumVariable(/* name= */ MAKE_EXTOPTION(\"debug_require_vector_agg\"),\n\t\t\t\t\t\t\t /* short_desc= */\n\t\t\t\t\t\t\t \"ensure that vectorized aggregation is used or not\",\n\t\t\t\t\t\t\t /* long_desc= */ \"this is for debugging purposes\",\n\t\t\t\t\t\t\t /* valueAddr= */ (int *) &ts_guc_debug_require_vector_agg,\n\t\t\t\t\t\t\t /* bootValue= */ DRO_Allow,\n\t\t\t\t\t\t\t /* options = */ debug_require_options,\n\t\t\t\t\t\t\t /* context= */ PGC_USERSET,\n\t\t\t\t\t\t\t /* flags= */ 0,\n\t\t\t\t\t\t\t /* check_hook= */ NULL,\n\t\t\t\t\t\t\t /* assign_hook= */ NULL,\n\t\t\t\t\t\t\t /* show_hook= */ NULL);\n\n\tDefineCustomEnumVariable(/* name= */ MAKE_EXTOPTION(\"debug_require_vector_qual\"),\n\t\t\t\t\t\t\t /* short_desc= */\n\t\t\t\t\t\t\t \"ensure that non-vectorized or vectorized filters are used in \"\n\t\t\t\t\t\t\t \"ColumnarScan node\",\n\t\t\t\t\t\t\t /* long_desc= */\n\t\t\t\t\t\t\t \"this is for debugging purposes, to let us check if the vectorized \"\n\t\t\t\t\t\t\t \"quals are used or not. EXPLAIN differs after PG15 for custom nodes, \"\n\t\t\t\t\t\t\t \"and \"\n\t\t\t\t\t\t\t \"using the test templates is a pain\",\n\t\t\t\t\t\t\t /* valueAddr= */ (int *) &ts_guc_debug_require_vector_qual,\n\t\t\t\t\t\t\t /* bootValue= */ DRO_Allow,\n\t\t\t\t\t\t\t /* options = */ debug_require_options,\n\t\t\t\t\t\t\t /* context= */ PGC_USERSET,\n\t\t\t\t\t\t\t /* flags= */ 0,\n\t\t\t\t\t\t\t /* check_hook= */ NULL,\n\t\t\t\t\t\t\t /* assign_hook= */ NULL,\n\t\t\t\t\t\t\t /* show_hook= */ NULL);\n\n#endif\n\n\t/* register feature flags */\n\tts_feature_flag_add(FEATURE_HYPERTABLE);\n\tts_feature_flag_add(FEATURE_HYPERTABLE_COMPRESSION);\n\tts_feature_flag_add(FEATURE_CAGG);\n\tts_feature_flag_add(FEATURE_POLICY);\n\n\tgucs_are_initialized = true;\n\n\tvalidate_chunk_cache_sizes(ts_guc_max_cached_chunks_per_hypertable,\n\t\t\t\t\t\t\t   ts_guc_max_open_chunks_per_insert);\n}\n"
  },
  {
    "path": "src/guc.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include \"compat/compat.h\"\n#include \"config.h\"\n#include \"export.h\"\n\n#ifdef USE_TELEMETRY\nextern bool ts_telemetry_on(void);\nextern bool ts_function_telemetry_on(void);\n#endif\n\nextern bool ts_guc_enable_deprecation_warnings;\nextern bool ts_guc_enable_optimizations;\nextern bool ts_guc_enable_constraint_aware_append;\nextern bool ts_guc_enable_ordered_append;\nextern bool ts_guc_enable_chunk_append;\nextern bool ts_guc_enable_parallel_chunk_append;\nextern bool ts_guc_enable_qual_propagation;\nextern TSDLLEXPORT bool ts_guc_enable_columnar_scan_filter_pushdown;\nextern bool ts_guc_enable_qual_filtering;\nextern bool ts_guc_enable_runtime_exclusion;\nextern bool ts_guc_enable_constraint_exclusion;\nextern bool ts_guc_enable_cagg_reorder_groupby;\nextern TSDLLEXPORT bool ts_guc_enable_cagg_window_functions;\nextern TSDLLEXPORT int ts_guc_cagg_max_individual_materializations;\nextern bool ts_guc_enable_now_constify;\nextern bool ts_guc_enable_foreign_key_propagation;\nextern TSDLLEXPORT bool ts_guc_enable_osm_reads;\n#if PG16_GE\nextern TSDLLEXPORT bool ts_guc_enable_cagg_sort_pushdown;\n#endif\nextern TSDLLEXPORT bool ts_guc_enable_cagg_watermark_constify;\nextern TSDLLEXPORT bool ts_guc_enable_dml_decompression;\nextern TSDLLEXPORT bool ts_guc_enable_dml_decompression_tuple_filtering;\nextern TSDLLEXPORT bool ts_guc_enable_dml_bloom_filter;\nextern bool ts_guc_enable_direct_compress_copy;\nextern bool ts_guc_enable_direct_compress_copy_sort_batches;\nextern bool ts_guc_enable_direct_compress_copy_client_sorted;\nextern int ts_guc_direct_compress_copy_tuple_sort_limit;\nextern TSDLLEXPORT bool ts_guc_enable_direct_compress_insert;\nextern bool ts_guc_enable_direct_compress_insert_sort_batches;\nextern TSDLLEXPORT bool ts_guc_enable_direct_compress_insert_client_sorted;\nextern TSDLLEXPORT bool ts_guc_enable_direct_compress_on_cagg_refresh;\nextern int ts_guc_direct_compress_insert_tuple_sort_limit;\nextern TSDLLEXPORT bool ts_guc_enable_compressed_direct_batch_delete;\nextern TSDLLEXPORT int ts_guc_max_tuples_decompressed_per_dml;\nextern TSDLLEXPORT bool ts_guc_enable_compression_wal_markers;\nextern TSDLLEXPORT bool ts_guc_enable_decompression_sorted_merge;\nextern TSDLLEXPORT bool ts_guc_enable_skip_scan;\nextern TSDLLEXPORT bool ts_guc_enable_chunkwise_aggregation;\nextern TSDLLEXPORT bool ts_guc_enable_vectorized_aggregation;\nextern bool ts_guc_restoring;\nextern int ts_guc_max_open_chunks_per_insert;\nextern int ts_guc_max_cached_chunks_per_hypertable;\nextern TSDLLEXPORT bool ts_guc_enable_job_execution_logging;\nextern bool ts_guc_enable_tss_callbacks;\nextern TSDLLEXPORT bool ts_guc_enable_delete_after_compression;\nextern TSDLLEXPORT bool ts_guc_enable_merge_on_cagg_refresh;\nextern bool ts_guc_enable_chunk_skipping;\nextern TSDLLEXPORT bool ts_guc_enable_segmentwise_recompression;\nextern TSDLLEXPORT bool ts_guc_enable_in_memory_recompression;\nextern TSDLLEXPORT bool ts_guc_enable_exclusive_locking_recompression;\nextern TSDLLEXPORT bool ts_guc_enable_bool_compression;\nextern TSDLLEXPORT bool ts_guc_enable_uuid_compression;\nextern TSDLLEXPORT int ts_guc_compression_batch_size_limit;\nextern TSDLLEXPORT bool ts_guc_compression_enable_compressor_batch_limit;\n#if PG16_GE\nextern TSDLLEXPORT bool ts_guc_enable_skip_scan_for_distinct_aggregates;\n#endif\nextern bool ts_guc_enable_event_triggers;\nextern bool ts_guc_enable_chunk_auto_publication;\nextern TSDLLEXPORT bool ts_guc_enable_compressed_skip_scan;\nextern TSDLLEXPORT bool ts_guc_enable_multikey_skip_scan;\nextern TSDLLEXPORT double ts_guc_skip_scan_run_cost_multiplier;\nextern TSDLLEXPORT bool ts_guc_debug_skip_scan_info;\n\n/* Only settable in debug mode for testing */\nextern TSDLLEXPORT bool ts_guc_enable_null_compression;\nextern TSDLLEXPORT bool ts_guc_enable_compression_ratio_warnings;\n\ntypedef enum CompressTruncateBehaviour\n{\n\tCOMPRESS_TRUNCATE_ONLY,\n\tCOMPRESS_TRUNCATE_OR_DELETE,\n\tCOMPRESS_TRUNCATE_DISABLED,\n} CompressTruncateBehaviour;\nextern TSDLLEXPORT CompressTruncateBehaviour ts_guc_compress_truncate_behaviour;\n\n#ifdef USE_TELEMETRY\ntypedef enum TelemetryLevel\n{\n\tTELEMETRY_OFF,\n\tTELEMETRY_NO_FUNCTIONS,\n\tTELEMETRY_BASIC,\n} TelemetryLevel;\n\nextern TelemetryLevel ts_guc_telemetry_level;\nextern char *ts_telemetry_cloud;\n#endif\n\nextern TSDLLEXPORT char *ts_guc_license;\nextern TSDLLEXPORT bool ts_guc_enable_compression_indexscan;\nextern TSDLLEXPORT bool ts_guc_enable_bulk_decompression;\nextern TSDLLEXPORT bool ts_guc_auto_sparse_indexes;\nextern TSDLLEXPORT bool ts_guc_enable_sparse_index_bloom;\nextern TSDLLEXPORT bool ts_guc_enable_composite_bloom_indexes;\nextern TSDLLEXPORT bool ts_guc_read_legacy_bloom1_v1;\nextern TSDLLEXPORT bool ts_guc_enable_columnarscan;\nextern TSDLLEXPORT bool ts_guc_enable_columnarindexscan;\nextern TSDLLEXPORT int ts_guc_bgw_log_level;\n\n/*\n * Exit code to use when scheduler exits.\n *\n * Used for debugging.\n */\nextern TSDLLEXPORT int ts_debug_bgw_scheduler_exit_status;\n#ifdef TS_DEBUG\nextern bool ts_shutdown_bgw;\nextern char *ts_current_timestamp_mock;\n#else\n#define ts_shutdown_bgw false\n#endif\n\nextern TSDLLEXPORT int ts_guc_debug_toast_tuple_target;\n\ntypedef enum DebugRequireOption\n{\n\tDRO_Allow = 0,\n\tDRO_Forbid,\n\tDRO_Require,\n\tDRO_Force,\n} DebugRequireOption;\n\n#ifdef TS_DEBUG\nextern TSDLLEXPORT DebugRequireOption ts_guc_debug_require_vector_qual;\n\nextern TSDLLEXPORT DebugRequireOption ts_guc_debug_require_vector_agg;\n\n#endif\n\nextern TSDLLEXPORT bool ts_guc_debug_compression_path_info;\nextern TSDLLEXPORT bool ts_guc_enable_rowlevel_compression_locking;\n\nextern TSDLLEXPORT DebugRequireOption ts_guc_debug_require_batch_sorted_merge;\n\nextern bool ts_guc_enable_partitioned_hypertables;\n\nvoid _guc_init(void);\n\ntypedef enum\n{\n\tFEATURE_HYPERTABLE,\n\tFEATURE_HYPERTABLE_COMPRESSION,\n\tFEATURE_CAGG,\n\tFEATURE_POLICY\n} FeatureFlagType;\n\nextern TSDLLEXPORT void ts_feature_flag_check(FeatureFlagType);\nextern TSDLLEXPORT Oid ts_guc_default_segmentby_fn_oid(void);\nextern TSDLLEXPORT Oid ts_guc_default_orderby_fn_oid(void);\n\n#define TARGET_COMPRESSED_BATCH_SIZE 1000\n\n/*\n * We use this limit for sanity checks in case the compressed data is corrupt.\n */\n#define GLOBAL_MAX_ROWS_PER_COMPRESSION INT16_MAX\n"
  },
  {
    "path": "src/histogram.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <catalog/pg_type.h>\n#include <libpq/pqformat.h>\n#include <netinet/in.h>\n#include <nodes/makefuncs.h>\n#include <utils/array.h>\n#include <utils/builtins.h>\n#include <utils/lsyscache.h>\n\n#include \"compat/compat.h\"\n#include \"debug_assert.h\"\n#include \"utils.h\"\n\n/* aggregate histogram:\n *\t histogram(state, val, min, max, nbuckets) returns the histogram array with nbuckets\n *\n * Usage:\n *\t SELECT grouping_element, histogram(field, min, max, nbuckets) FROM table GROUP BY\n *grouping_element.\n *\n * Description:\n * Histogram generates a histogram array based off of a specified range passed into the function.\n * Values falling outside of this range are bucketed into the 0 or nbucket+1 buckets depending on\n * if they are below or above the range, respectively. The resultant histogram therefore contains\n * nbucket+2 buckets accounting for buckets outside the range.\n */\n\nTS_FUNCTION_INFO_V1(ts_hist_sfunc);\nTS_FUNCTION_INFO_V1(ts_hist_combinefunc);\nTS_FUNCTION_INFO_V1(ts_hist_serializefunc);\nTS_FUNCTION_INFO_V1(ts_hist_deserializefunc);\nTS_FUNCTION_INFO_V1(ts_hist_finalfunc);\n\n#define HISTOGRAM_SIZE(state, nbuckets)                                                            \\\n\t(sizeof(*(state)) + ((nbuckets) * sizeof(*(state)->buckets)))\n\ntypedef struct Histogram\n{\n\tint32 nbuckets;\n\tDatum buckets[FLEXIBLE_ARRAY_MEMBER];\n} Histogram;\n\n/* histogram(state, val, min, max, nbuckets) */\nDatum\nts_hist_sfunc(PG_FUNCTION_ARGS)\n{\n\tMemoryContext aggcontext;\n\tHistogram *state = (Histogram *) (PG_ARGISNULL(0) ? NULL : PG_GETARG_POINTER(0));\n\tDatum val_datum = PG_GETARG_DATUM(1);\n\tDatum min_datum = PG_GETARG_DATUM(2);\n\tDatum max_datum = PG_GETARG_DATUM(3);\n\tdouble min = DatumGetFloat8(min_datum);\n\tdouble max = DatumGetFloat8(max_datum);\n\tint nbuckets;\n\n\tif (!AggCheckCallContext(fcinfo, &aggcontext))\n\t{\n\t\t/* cannot be called directly because of internal-type argument */\n\t\tereport(ERROR, (errmsg(\"ts_hist_sfunc called in non-aggregate context\")));\n\t}\n\n\tif (min > max)\n\t{\n\t\t/* cannot generate a histogram with incompatible bounds */\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"lower bound cannot exceed upper bound\")));\n\t}\n\n\tif (state == NULL)\n\t{\n\t\tnbuckets = PG_GETARG_INT32(4) + 2;\n\t\t/* Allocate memory to a new histogram state array */\n\t\tstate = MemoryContextAllocZero(aggcontext, HISTOGRAM_SIZE(state, nbuckets));\n\t\tstate->nbuckets = nbuckets;\n\t}\n\n\t/* Since the number of buckets is an argument to the calls it might differ\n\t * from the number we initialized with so we need to make sure we check\n\t * against what we actually have.\n\t */\n\tnbuckets = state->nbuckets - 2;\n\tif (nbuckets != PG_GETARG_INT32(4))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"number of buckets must not change between calls\")));\n\n\tint32 bucket = DatumGetInt32(DirectFunctionCall4(width_bucket_float8,\n\t\t\t\t\t\t\t\t\t\t\t\t\t val_datum,\n\t\t\t\t\t\t\t\t\t\t\t\t\t min_datum,\n\t\t\t\t\t\t\t\t\t\t\t\t\t max_datum,\n\t\t\t\t\t\t\t\t\t\t\t\t\t Int32GetDatum(nbuckets)));\n\n\t/* Increment the proper histogram bucket */\n\tif (bucket < 0 || bucket >= state->nbuckets)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),\n\t\t\t\t errmsg(\"index %d from \\\"width_bucket\\\" out of range\", bucket),\n\t\t\t\t errhint(\"You probably have a floating point overflow.\")));\n\t}\n\n\tif (DatumGetInt32(state->buckets[bucket]) >= PG_INT32_MAX - 1)\n\t{\n\t\telog(ERROR, \"overflow in histogram\");\n\t}\n\n\tstate->buckets[bucket] = Int32GetDatum(DatumGetInt32(state->buckets[bucket]) + 1);\n\n\tPG_RETURN_POINTER(state);\n}\n\n/* Make a copy of the histogram state */\nstatic inline Histogram *\ncopy_state(MemoryContext aggcontext, Histogram *state)\n{\n\tHistogram *copy;\n\tSize bucket_bytes = state->nbuckets * sizeof(*copy->buckets);\n\n\tcopy = MemoryContextAlloc(aggcontext, sizeof(*copy) + bucket_bytes);\n\tcopy->nbuckets = state->nbuckets;\n\tmemcpy(copy->buckets, state->buckets, bucket_bytes);\n\n\treturn copy;\n}\n\n/* ts_hist_combinefunc(internal, internal) => internal */\nDatum\nts_hist_combinefunc(PG_FUNCTION_ARGS)\n{\n\tMemoryContext aggcontext;\n\n\tHistogram *state1 = (Histogram *) (PG_ARGISNULL(0) ? NULL : PG_GETARG_POINTER(0));\n\tHistogram *state2 = (Histogram *) (PG_ARGISNULL(1) ? NULL : PG_GETARG_POINTER(1));\n\tHistogram *result;\n\n\tif (!AggCheckCallContext(fcinfo, &aggcontext))\n\t{\n\t\t/* cannot be called directly because of internal-type argument */\n\t\telog(ERROR, \"ts_hist_combinefunc called in non-aggregate context\");\n\t}\n\n\tif (state1 == NULL && state2 == NULL)\n\t{\n\t\tPG_RETURN_NULL();\n\t}\n\telse if (state2 == NULL)\n\t{\n\t\tresult = copy_state(aggcontext, state1);\n\t}\n\telse if (state1 == NULL)\n\t{\n\t\tresult = copy_state(aggcontext, state2);\n\t}\n\telse\n\t{\n\t\t/* Since number of buckets is part of the aggregation call the initialization\n\t\t * might be different in the partials so we error out if they are not identical. */\n\t\tif (state1->nbuckets != state2->nbuckets)\n\t\t\telog(ERROR, \"number of buckets must not change between calls\");\n\n\t\tresult = copy_state(aggcontext, state1);\n\n\t\t/* Combine values from state1 and state2 when both states are non-null */\n\t\tfor (int32 i = 0; i < state1->nbuckets; i++)\n\t\t{\n\t\t\t/* Perform addition using int64 to check for overflow */\n\t\t\tint64 val = (int64) DatumGetInt32(result->buckets[i]);\n\t\t\tint64 other = (int64) DatumGetInt32(state2->buckets[i]);\n\t\t\tif (val + other >= PG_INT32_MAX)\n\t\t\t\telog(ERROR, \"overflow in histogram combine\");\n\n\t\t\tresult->buckets[i] = Int32GetDatum((int32) (val + other));\n\t\t}\n\t}\n\n\tPG_RETURN_POINTER(result);\n}\n\n/* ts_hist_serializefunc(internal) => bytea */\nDatum\nts_hist_serializefunc(PG_FUNCTION_ARGS)\n{\n\tHistogram *state;\n\tStringInfoData buf;\n\n\tAssert(!PG_ARGISNULL(0));\n\tstate = (Histogram *) PG_GETARG_POINTER(0);\n\n\tpq_begintypsend(&buf);\n\tpq_sendint32(&buf, state->nbuckets);\n\n\tfor (int32 i = 0; i < state->nbuckets; i++)\n\t\tpq_sendint32(&buf, DatumGetInt32(state->buckets[i]));\n\n\tPG_RETURN_BYTEA_P(pq_endtypsend(&buf));\n}\n\n/* ts_hist_deserializefunc(bytea *, internal) => internal */\nDatum\nts_hist_deserializefunc(PG_FUNCTION_ARGS)\n{\n\tMemoryContext aggcontext;\n\tbytea *serialized;\n\tint32 nbuckets;\n\tint32 i;\n\tStringInfoData buf;\n\tHistogram *state;\n\n\tif (!AggCheckCallContext(fcinfo, &aggcontext))\n\t\telog(ERROR, \"ts_hist_deserializefunc called in non-aggregate context\");\n\n\tAssert(!PG_ARGISNULL(0));\n\tserialized = PG_GETARG_BYTEA_P(0);\n\n\tbuf.data = VARDATA(serialized);\n\tbuf.len = VARSIZE(serialized) - VARHDRSZ;\n\tbuf.maxlen = VARSIZE(serialized) - VARHDRSZ;\n\tbuf.cursor = 0; /* used by pq_getmsgint*/\n\n\tnbuckets = pq_getmsgint(&buf, 4);\n\n\tstate = MemoryContextAllocZero(aggcontext, HISTOGRAM_SIZE(state, nbuckets));\n\tstate->nbuckets = nbuckets;\n\n\tfor (i = 0; i < state->nbuckets; i++)\n\t\tstate->buckets[i] = Int32GetDatum(pq_getmsgint(&buf, 4));\n\n\tPG_RETURN_POINTER(state);\n}\n\n/* hist_finalfunc(internal, val REAL, MIN REAL, MAX REAL, nbuckets INTEGER) => INTEGER[] */\nDatum\nts_hist_finalfunc(PG_FUNCTION_ARGS)\n{\n\tHistogram *state;\n\tint dims[1];\n\tint lbs[1];\n\n\tif (!AggCheckCallContext(fcinfo, NULL))\n\t{\n\t\t/* cannot be called directly because of internal-type argument */\n\t\telog(ERROR, \"ts_hist_finalfunc called in non-aggregate context\");\n\t}\n\n\tstate = (Histogram *) (PG_ARGISNULL(0) ? NULL : PG_GETARG_POINTER(0));\n\n\tif (state == NULL)\n\t\tPG_RETURN_NULL();\n\n\tdims[0] = state->nbuckets;\n\tlbs[0] = 1;\n\n\tPG_RETURN_ARRAYTYPE_P(\n\t\tconstruct_md_array(state->buckets, NULL, 1, dims, lbs, INT4OID, 4, true, 'i'));\n}\n"
  },
  {
    "path": "src/hypercube.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <utils/jsonb.h>\n#include <utils/numeric.h>\n\n#include \"dimension_vector.h\"\n#include \"export.h\"\n#include \"hypercube.h\"\n\n/*\n * A hypercube represents the partition bounds of a hypertable chunk.\n *\n * A hypercube consists of N slices that each represent a range in a particular\n * dimension that make up the hypercube. When a new tuple is inserted into a\n * hypertable, and no chunk exists that can hold that tuple, we need to\n * calculate a new hypercube that encloses the point corresponding to the\n * tuple. When calculating the hypercube, we need to account for alignment\n * requirements in dimensions marked as \"aligned\" and also ensure that there are\n * no collisions with existing chunks. Alignment issues and collisions can occur\n * when the partitioning configuration has changed (e.g., the time interval or\n * number of partitions in a particular dimension changed).\n */\nHypercube *\nts_hypercube_alloc(int16 num_dimensions)\n{\n\tHypercube *hc = palloc0(HYPERCUBE_SIZE(num_dimensions));\n\n\thc->capacity = num_dimensions;\n\treturn hc;\n}\n\nvoid\nts_hypercube_free(Hypercube *hc)\n{\n\tint i;\n\n\tfor (i = 0; i < hc->num_slices; i++)\n\t\tts_dimension_slice_free(hc->slices[i]);\n\n\tpfree(hc);\n}\n\n#if defined(USE_ASSERT_CHECKING)\nstatic inline bool\nhypercube_is_sorted(const Hypercube *hc)\n{\n\tint i;\n\n\tif (hc->num_slices < 2)\n\t\treturn true;\n\n\tfor (i = 1; i < hc->num_slices; i++)\n\t\tif (hc->slices[i]->fd.dimension_id < hc->slices[i - 1]->fd.dimension_id)\n\t\t\treturn false;\n\n\treturn true;\n}\n#endif\n\nHypercube *\nts_hypercube_copy(const Hypercube *hc)\n{\n\tHypercube *copy;\n\tsize_t nbytes = HYPERCUBE_SIZE(hc->capacity);\n\tint i;\n\n\tcopy = palloc(nbytes);\n\tmemcpy(copy, hc, nbytes);\n\n\tfor (i = 0; i < hc->num_slices; i++)\n\t\tcopy->slices[i] = ts_dimension_slice_copy(hc->slices[i]);\n\n\treturn copy;\n}\n\nbool\nts_hypercube_equal(const Hypercube *hc1, const Hypercube *hc2)\n{\n\tint i;\n\n\tif (hc1->num_slices != hc2->num_slices)\n\t\treturn false;\n\n\tfor (i = 0; i < hc1->num_slices; i++)\n\t\tif (ts_dimension_slice_cmp(hc1->slices[i], hc2->slices[i]) != 0)\n\t\t\treturn false;\n\n\treturn true;\n}\n\nstatic int\ncmp_slices_by_dimension_id(const void *left, const void *right)\n{\n\tconst DimensionSlice *left_slice = *((DimensionSlice **) left);\n\tconst DimensionSlice *right_slice = *((DimensionSlice **) right);\n\n\tif (left_slice->fd.dimension_id == right_slice->fd.dimension_id)\n\t\treturn 0;\n\tif (left_slice->fd.dimension_id < right_slice->fd.dimension_id)\n\t\treturn -1;\n\treturn 1;\n}\n\nDimensionSlice *\nts_hypercube_add_slice_from_range(Hypercube *hc, int32 dimension_id, int64 start, int64 end)\n{\n\tDimensionSlice *slice;\n\n\tAssert(hc->capacity > hc->num_slices);\n\n\tslice = ts_dimension_slice_create(dimension_id, start, end);\n\thc->slices[hc->num_slices++] = slice;\n\n\t/* Check if we require a sort to maintain dimension order */\n\tif (hc->num_slices > 1 &&\n\t\tslice->fd.dimension_id < hc->slices[hc->num_slices - 2]->fd.dimension_id)\n\t\tts_hypercube_slice_sort(hc);\n\n\tAssert(hypercube_is_sorted(hc));\n\n\treturn slice;\n}\n\nDimensionSlice *\nts_hypercube_add_slice(Hypercube *hc, const DimensionSlice *slice)\n{\n\tDimensionSlice *new_slice;\n\n\tnew_slice = ts_hypercube_add_slice_from_range(hc,\n\t\t\t\t\t\t\t\t\t\t\t\t  slice->fd.dimension_id,\n\t\t\t\t\t\t\t\t\t\t\t\t  slice->fd.range_start,\n\t\t\t\t\t\t\t\t\t\t\t\t  slice->fd.range_end);\n\tnew_slice->fd.id = slice->fd.id;\n\n\treturn new_slice;\n}\n\n/*\n * Sort the hypercubes slices in ascending dimension ID order. This allows us to\n * iterate slices in a consistent order.\n */\nvoid\nts_hypercube_slice_sort(Hypercube *hc)\n{\n\tqsort((void *) hc->slices,\n\t\t  hc->num_slices,\n\t\t  sizeof(DimensionSlice *),\n\t\t  cmp_slices_by_dimension_id);\n}\n\nconst DimensionSlice *\nts_hypercube_get_slice_by_dimension_id(const Hypercube *hc, int32 dimension_id)\n{\n\tDimensionSlice slice = {\n\t\t.fd.dimension_id = dimension_id,\n\t};\n\tvoid *ptr = &slice;\n\n\tif (hc->num_slices == 0)\n\t\treturn NULL;\n\n\tAssert(hypercube_is_sorted(hc));\n\n\tptr = bsearch((void *) &ptr,\n\t\t\t\t  (void *) hc->slices,\n\t\t\t\t  hc->num_slices,\n\t\t\t\t  sizeof(DimensionSlice *),\n\t\t\t\t  cmp_slices_by_dimension_id);\n\n\tif (NULL == ptr)\n\t\treturn NULL;\n\n\treturn *((DimensionSlice **) ptr);\n}\n\n/*\n * Given a set of constraints, build the corresponding hypercube.\n */\nHypercube *\nts_hypercube_from_constraints(const ChunkConstraints *constraints, ScanIterator *slice_it)\n{\n\tHypercube *hc;\n\tint i;\n\tMemoryContext old;\n\n\told = MemoryContextSwitchTo(ts_scan_iterator_get_result_memory_context(slice_it));\n\thc = ts_hypercube_alloc(constraints->num_dimension_constraints);\n\tMemoryContextSwitchTo(old);\n\n\tfor (i = 0; i < constraints->num_constraints; i++)\n\t{\n\t\tChunkConstraint *cc = chunk_constraints_get(constraints, i);\n\n\t\tif (is_dimension_constraint(cc))\n\t\t{\n\t\t\tDimensionSlice *slice;\n\n\t\t\tAssert(hc->num_slices < constraints->num_dimension_constraints);\n\n\t\t\t/* When building the hypercube, we reference the dimension slices\n\t\t\t * to construct the hypercube.\n\t\t\t *\n\t\t\t * However, we cannot add a tuple lock when running in recovery\n\t\t\t * mode since that prevents SELECT statements (which reach this\n\t\t\t * point) from running on a read-only secondary (which runs in\n\t\t\t * ephemeral recovery mode), so we only take the lock if we are not\n\t\t\t * in recovery mode.\n\t\t\t */\n\t\t\tslice = ts_dimension_slice_scan_iterator_get_by_id(slice_it, cc->fd.dimension_slice_id);\n\t\t\tif (!slice)\n\t\t\t{\n\t\t\t\telog(ERROR, \"could not find dimension slice with id %d\", cc->fd.dimension_slice_id);\n\t\t\t}\n\t\t\thc->slices[hc->num_slices++] = slice;\n\t\t}\n\t}\n\n\tts_hypercube_slice_sort(hc);\n\n\tAssert(hypercube_is_sorted(hc));\n\n\treturn hc;\n}\n\n/*\n * Find slices in the hypercube that already exists in metadata.\n *\n * If a slice exists in metadata, the slice ID will be filled in on the\n * existing slice in the hypercube. Optionally, also lock the slice when\n * found.\n */\nint\nts_hypercube_find_existing_slices(const Hypercube *cube, const ScanTupLock *tuplock)\n{\n\tint i;\n\tint num_found = 0;\n\n\tfor (i = 0; i < cube->num_slices; i++)\n\t{\n\t\t/*\n\t\t * Check if there's already an existing slice with the calculated\n\t\t * range. If a slice already exists, use that slice's ID instead\n\t\t * of a new one.\n\t\t */\n\t\tbool found = ts_dimension_slice_scan_for_existing(cube->slices[i], tuplock);\n\n\t\tif (found)\n\t\t\tnum_found++;\n\t}\n\n\treturn num_found;\n}\n\n/*\n * Calculate the hypercube that encloses the given point.\n *\n * The hypercube's dimensions are calculated one by one, and depend on the\n * current partitioning in each dimension of the N-dimensional hyperspace,\n * including any alignment requirements.\n *\n * For non-aligned dimensions, we simply calculate the hypercube's slice range\n * in that dimension given current partitioning configuration. If there is\n * already an identical slice for that dimension, we will reuse it rather than\n * creating a new one.\n *\n * For aligned dimensions, we first try to find an existing slice that covers\n * the insertion point. If an existing slice is found, we reuse it or otherwise\n * we calculate a new slice as described for non-aligned dimensions.\n *\n * If a hypercube has dimension slices that are not reused ones, we might need\n * to cut them to ensure alignment and avoid collisions with other chunk\n * hypercubes. This happens in a later step.\n */\nHypercube *\nts_hypercube_calculate_from_point(const Hyperspace *hs, const Point *p, const ScanTupLock *tuplock)\n{\n\tHypercube *cube;\n\tint i;\n\n\tcube = ts_hypercube_alloc(hs->num_dimensions);\n\n\t/* For each dimension, calculate the hypercube's slice in that dimension */\n\tfor (i = 0; i < hs->num_dimensions; i++)\n\t{\n\t\tconst Dimension *dim = &hs->dimensions[i];\n\t\tint64 value = p->coordinates[i];\n\t\tbool found = false;\n\t\tbool check_for_existing_slice = false;\n\n\t\t/* Assert that dimensions are in ascending order */\n\t\tAssert(i == 0 || dim->fd.id > hs->dimensions[i - 1].fd.id);\n\n\t\tif (dim->fd.aligned)\n\t\t{\n\t\t\tDimensionVec *vec;\n\n\t\t\tvec = ts_dimension_slice_scan_limit(dim->fd.id, value, 1, tuplock);\n\n\t\t\tif (vec->num_slices > 0)\n\t\t\t{\n\t\t\t\tcube->slices[i] = vec->slices[0];\n\t\t\t\tfound = true;\n\t\t\t}\n\t\t}\n\n\t\tif (!found)\n\t\t{\n\t\t\t/*\n\t\t\t * No existing slice found, or we are not aligning, so calculate\n\t\t\t * the range of a new slice\n\t\t\t */\n\t\t\tcube->slices[i] = ts_dimension_calculate_default_slice(dim, value);\n\t\t\tcheck_for_existing_slice = true;\n\t\t}\n\n\t\tif (check_for_existing_slice)\n\t\t{\n\t\t\t/*\n\t\t\t * Check if there's already an existing slice with the calculated\n\t\t\t * range. If a slice already exists, use that slice's ID instead\n\t\t\t * of a new one.\n\t\t\t */\n\t\t\tts_dimension_slice_scan_for_existing(cube->slices[i], tuplock);\n\t\t}\n\t}\n\n\tcube->num_slices = hs->num_dimensions;\n\n\tAssert(hypercube_is_sorted(cube));\n\n\treturn cube;\n}\n\n/*\n * Check if two hypercubes collide (overlap).\n *\n * This is basically an axis-aligned bounding box collision detection,\n * generalized to N dimensions. We check for dimension slice collisions in each\n * dimension and only if all dimensions collide there is a hypercube collision.\n */\nbool\nts_hypercubes_collide(const Hypercube *cube1, const Hypercube *cube2)\n{\n\tint i;\n\n\tAssert(cube1->num_slices == cube2->num_slices);\n\n\tfor (i = 0; i < cube1->num_slices; i++)\n\t\tif (!ts_dimension_slices_collide(cube1->slices[i], cube2->slices[i]))\n\t\t\treturn false;\n\n\treturn true;\n}\n"
  },
  {
    "path": "src/hypercube.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include \"dimension_slice.h\"\n#include \"scan_iterator.h\"\n\n/*\n * Hypercube is a collection of slices from N distinct dimensions, i.e., the\n * N-dimensional analogue of a cube.\n */\ntypedef struct Hypercube\n{\n\tint16 capacity;\t  /* capacity of slices[] */\n\tint16 num_slices; /* actual number of slices (should equal\n\t\t\t\t\t   * capacity after create) */\n\t/* Slices are stored in dimension order */\n\tDimensionSlice *slices[FLEXIBLE_ARRAY_MEMBER];\n} Hypercube;\n\n#define HYPERCUBE_SIZE(num_dimensions)                                                             \\\n\t(sizeof(Hypercube) + (sizeof(DimensionSlice *) * (num_dimensions)))\n\nextern TSDLLEXPORT Hypercube *ts_hypercube_alloc(int16 num_dimensions);\nextern TSDLLEXPORT void ts_hypercube_free(Hypercube *hc);\n\nextern TSDLLEXPORT DimensionSlice *\nts_hypercube_add_slice_from_range(Hypercube *hc, int32 dimension_id, int64 start, int64 end);\nextern TSDLLEXPORT DimensionSlice *ts_hypercube_add_slice(Hypercube *hc,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  const DimensionSlice *slice);\nextern Hypercube *ts_hypercube_from_constraints(const ChunkConstraints *constraints,\n\t\t\t\t\t\t\t\t\t\t\t\tScanIterator *slice_it);\nextern int ts_hypercube_find_existing_slices(const Hypercube *cube, const ScanTupLock *tuplock);\nextern Hypercube *ts_hypercube_calculate_from_point(const Hyperspace *hs, const Point *p,\n\t\t\t\t\t\t\t\t\t\t\t\t\tconst ScanTupLock *tuplock);\nextern bool ts_hypercubes_collide(const Hypercube *cube1, const Hypercube *cube2);\nextern TSDLLEXPORT const DimensionSlice *ts_hypercube_get_slice_by_dimension_id(const Hypercube *hc,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tint32 dimension_id);\nextern TSDLLEXPORT Hypercube *ts_hypercube_copy(const Hypercube *hc);\nextern bool ts_hypercube_equal(const Hypercube *hc1, const Hypercube *hc2);\nextern void ts_hypercube_slice_sort(Hypercube *hc);\n"
  },
  {
    "path": "src/hypertable.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n\n#include <access/heapam.h>\n#include <access/htup_details.h>\n#include <access/relscan.h>\n#include <catalog/indexing.h>\n#include <catalog/namespace.h>\n#include <catalog/pg_collation.h>\n#include <catalog/pg_constraint.h>\n#include <catalog/pg_database_d.h>\n#include <catalog/pg_inherits.h>\n#include <catalog/pg_namespace_d.h>\n#include <catalog/pg_proc.h>\n#include <catalog/pg_type.h>\n#include <commands/dbcommands.h>\n#include <commands/schemacmds.h>\n#include <commands/tablecmds.h>\n#include <commands/tablespace.h>\n#include <commands/trigger.h>\n#include <executor/spi.h>\n#include <funcapi.h>\n#include <lib/stringinfo.h>\n#include <miscadmin.h>\n#include <nodes/makefuncs.h>\n#include <nodes/memnodes.h>\n#include <nodes/parsenodes.h>\n#include <nodes/value.h>\n#include <parser/parse_coerce.h>\n#include <parser/parse_func.h>\n#include <storage/lmgr.h>\n#include <utils/acl.h>\n#include <utils/builtins.h>\n#include <utils/lsyscache.h>\n#include <utils/memutils.h>\n#include <utils/snapmgr.h>\n#include <utils/syscache.h>\n\n#include \"hypertable.h\"\n\n#include \"compat/compat.h\"\n#include \"bgw_policy/policy.h\"\n#include \"chunk.h\"\n#include \"chunk_adaptive.h\"\n#include \"copy.h\"\n#include \"cross_module_fn.h\"\n#include \"debug_assert.h\"\n#include \"dimension.h\"\n#include \"dimension_slice.h\"\n#include \"dimension_vector.h\"\n#include \"error_utils.h\"\n#include \"errors.h\"\n#include \"extension.h\"\n#include \"guc.h\"\n#include \"hypercube.h\"\n#include \"hypertable_cache.h\"\n#include \"indexing.h\"\n#include \"license_guc.h\"\n#include \"osm_callbacks.h\"\n#include \"partition_chunk.h\"\n#include \"scan_iterator.h\"\n#include \"scanner.h\"\n#include \"subspace_store.h\"\n#include \"trigger.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"ts_catalog/chunk_column_stats.h\"\n#include \"ts_catalog/compression_settings.h\"\n#include \"ts_catalog/continuous_agg.h\"\n#include \"ts_catalog/metadata.h\"\n#include \"utils.h\"\n\nOid\nts_rel_get_owner(Oid relid)\n{\n\tHeapTuple tuple;\n\tOid ownerid;\n\n\tif (!OidIsValid(relid))\n\t\tereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), errmsg(\"invalid relation OID\")));\n\n\ttuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));\n\n\tif (!HeapTupleIsValid(tuple))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_TABLE),\n\t\t\t\t errmsg(\"relation with OID %u does not exist\", relid)));\n\n\townerid = ((Form_pg_class) GETSTRUCT(tuple))->relowner;\n\n\tReleaseSysCache(tuple);\n\n\treturn ownerid;\n}\n\nbool\nts_hypertable_has_privs_of(Oid hypertable_oid, Oid userid)\n{\n\treturn has_privs_of_role(userid, ts_rel_get_owner(hypertable_oid));\n}\n\n/*\n * The error output for permission denied errors such as these changed in PG11,\n * it modifies places where relation is specified to note the specific object\n * type we note that permissions are denied for the hypertable for all PG\n * versions so that tests need not change due to one word changes in error\n * messages and because it is more clear this way.\n */\nOid\nts_hypertable_permissions_check(Oid hypertable_oid, Oid userid)\n{\n\tOid ownerid = ts_rel_get_owner(hypertable_oid);\n\n\tif (!has_privs_of_role(userid, ownerid))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),\n\t\t\t\t errmsg(\"must be owner of hypertable \\\"%s\\\"\", get_rel_name(hypertable_oid))));\n\n\treturn ownerid;\n}\n\nvoid\nts_hypertable_permissions_check_by_id(int32 hypertable_id)\n{\n\tOid table_relid = ts_hypertable_id_to_relid(hypertable_id, false);\n\tts_hypertable_permissions_check(table_relid, GetUserId());\n}\n\nstatic Oid\nget_chunk_sizing_func_oid(const FormData_hypertable *fd)\n{\n\tOid argtype[] = { INT4OID, INT8OID, INT8OID };\n\treturn LookupFuncName(list_make2(makeString((char *) NameStr(fd->chunk_sizing_func_schema)),\n\t\t\t\t\t\t\t\t\t makeString((char *) NameStr(fd->chunk_sizing_func_name))),\n\t\t\t\t\t\t  sizeof(argtype) / sizeof(argtype[0]),\n\t\t\t\t\t\t  argtype,\n\t\t\t\t\t\t  false);\n}\n\nstatic HeapTuple\nhypertable_formdata_make_tuple(const FormData_hypertable *fd, TupleDesc desc)\n{\n\tDatum values[Natts_hypertable];\n\tbool nulls[Natts_hypertable] = { false };\n\n\tmemset(values, 0, sizeof(Datum) * Natts_hypertable);\n\n\tvalues[AttrNumberGetAttrOffset(Anum_hypertable_id)] = Int32GetDatum(fd->id);\n\tvalues[AttrNumberGetAttrOffset(Anum_hypertable_schema_name)] = NameGetDatum(&fd->schema_name);\n\tvalues[AttrNumberGetAttrOffset(Anum_hypertable_table_name)] = NameGetDatum(&fd->table_name);\n\tvalues[AttrNumberGetAttrOffset(Anum_hypertable_associated_schema_name)] =\n\t\tNameGetDatum(&fd->associated_schema_name);\n\tAssert(&fd->associated_table_prefix != NULL);\n\tvalues[AttrNumberGetAttrOffset(Anum_hypertable_associated_table_prefix)] =\n\t\tNameGetDatum(&fd->associated_table_prefix);\n\tvalues[AttrNumberGetAttrOffset(Anum_hypertable_num_dimensions)] =\n\t\tInt16GetDatum(fd->num_dimensions);\n\n\tvalues[AttrNumberGetAttrOffset(Anum_hypertable_chunk_sizing_func_schema)] =\n\t\tNameGetDatum(&fd->chunk_sizing_func_schema);\n\tvalues[AttrNumberGetAttrOffset(Anum_hypertable_chunk_sizing_func_name)] =\n\t\tNameGetDatum(&fd->chunk_sizing_func_name);\n\tvalues[AttrNumberGetAttrOffset(Anum_hypertable_chunk_target_size)] =\n\t\tInt64GetDatum(fd->chunk_target_size);\n\n\tvalues[AttrNumberGetAttrOffset(Anum_hypertable_compression_state)] =\n\t\tInt16GetDatum(fd->compression_state);\n\tif (fd->compressed_hypertable_id == INVALID_HYPERTABLE_ID)\n\t\tnulls[AttrNumberGetAttrOffset(Anum_hypertable_compressed_hypertable_id)] = true;\n\telse\n\t\tvalues[AttrNumberGetAttrOffset(Anum_hypertable_compressed_hypertable_id)] =\n\t\t\tInt32GetDatum(fd->compressed_hypertable_id);\n\tvalues[AttrNumberGetAttrOffset(Anum_hypertable_status)] = Int32GetDatum(fd->status);\n\n\treturn heap_form_tuple(desc, values, nulls);\n}\n\nvoid\nts_hypertable_formdata_fill(FormData_hypertable *fd, const TupleInfo *ti)\n{\n\tbool nulls[Natts_hypertable];\n\tDatum values[Natts_hypertable];\n\tbool should_free;\n\tHeapTuple tuple;\n\n\ttuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\theap_deform_tuple(tuple, ts_scanner_get_tupledesc(ti), values, nulls);\n\n\tAssert(!nulls[AttrNumberGetAttrOffset(Anum_hypertable_id)]);\n\tAssert(!nulls[AttrNumberGetAttrOffset(Anum_hypertable_schema_name)]);\n\tAssert(!nulls[AttrNumberGetAttrOffset(Anum_hypertable_table_name)]);\n\tAssert(!nulls[AttrNumberGetAttrOffset(Anum_hypertable_associated_schema_name)]);\n\tAssert(!nulls[AttrNumberGetAttrOffset(Anum_hypertable_associated_table_prefix)]);\n\tAssert(!nulls[AttrNumberGetAttrOffset(Anum_hypertable_num_dimensions)]);\n\tAssert(!nulls[AttrNumberGetAttrOffset(Anum_hypertable_chunk_sizing_func_schema)]);\n\tAssert(!nulls[AttrNumberGetAttrOffset(Anum_hypertable_chunk_sizing_func_name)]);\n\tAssert(!nulls[AttrNumberGetAttrOffset(Anum_hypertable_chunk_target_size)]);\n\tAssert(!nulls[AttrNumberGetAttrOffset(Anum_hypertable_compression_state)]);\n\tAssert(!nulls[AttrNumberGetAttrOffset(Anum_hypertable_status)]);\n\n\tfd->id = DatumGetInt32(values[AttrNumberGetAttrOffset(Anum_hypertable_id)]);\n\tnamestrcpy(&fd->schema_name,\n\t\t\t   DatumGetCString(values[AttrNumberGetAttrOffset(Anum_hypertable_schema_name)]));\n\tnamestrcpy(&fd->table_name,\n\t\t\t   DatumGetCString(values[AttrNumberGetAttrOffset(Anum_hypertable_table_name)]));\n\tnamestrcpy(&fd->associated_schema_name,\n\t\t\t   DatumGetCString(\n\t\t\t\t   values[AttrNumberGetAttrOffset(Anum_hypertable_associated_schema_name)]));\n\tnamestrcpy(&fd->associated_table_prefix,\n\t\t\t   DatumGetCString(\n\t\t\t\t   values[AttrNumberGetAttrOffset(Anum_hypertable_associated_table_prefix)]));\n\n\tfd->num_dimensions =\n\t\tDatumGetInt16(values[AttrNumberGetAttrOffset(Anum_hypertable_num_dimensions)]);\n\n\tnamestrcpy(&fd->chunk_sizing_func_schema,\n\t\t\t   DatumGetCString(\n\t\t\t\t   values[AttrNumberGetAttrOffset(Anum_hypertable_chunk_sizing_func_schema)]));\n\tnamestrcpy(&fd->chunk_sizing_func_name,\n\t\t\t   DatumGetCString(\n\t\t\t\t   values[AttrNumberGetAttrOffset(Anum_hypertable_chunk_sizing_func_name)]));\n\n\tfd->chunk_target_size =\n\t\tDatumGetInt64(values[AttrNumberGetAttrOffset(Anum_hypertable_chunk_target_size)]);\n\tfd->compression_state =\n\t\tDatumGetInt16(values[AttrNumberGetAttrOffset(Anum_hypertable_compression_state)]);\n\n\tif (nulls[AttrNumberGetAttrOffset(Anum_hypertable_compressed_hypertable_id)])\n\t\tfd->compressed_hypertable_id = INVALID_HYPERTABLE_ID;\n\telse\n\t\tfd->compressed_hypertable_id = DatumGetInt32(\n\t\t\tvalues[AttrNumberGetAttrOffset(Anum_hypertable_compressed_hypertable_id)]);\n\tfd->status = DatumGetInt32(values[AttrNumberGetAttrOffset(Anum_hypertable_status)]);\n\n\tif (should_free)\n\t\theap_freetuple(tuple);\n}\n\nHypertable *\nts_hypertable_from_tupleinfo(const TupleInfo *ti)\n{\n\tHypertable *h = MemoryContextAllocZero(ti->mctx, sizeof(Hypertable));\n\n\tts_hypertable_formdata_fill(&h->fd, ti);\n\th->main_table_relid =\n\t\tts_get_relation_relid(NameStr(h->fd.schema_name), NameStr(h->fd.table_name), true);\n\th->space = ts_dimension_scan(h->fd.id, h->main_table_relid, h->fd.num_dimensions, ti->mctx);\n\th->chunk_cache =\n\t\tts_subspace_store_init(h->space, ti->mctx, ts_guc_max_cached_chunks_per_hypertable);\n\th->chunk_sizing_func = get_chunk_sizing_func_oid(&h->fd);\n\n\tif (ts_guc_enable_chunk_skipping)\n\t{\n\t\th->range_space =\n\t\t\tts_chunk_column_stats_range_space_scan(h->fd.id, h->main_table_relid, ti->mctx);\n\t}\n\n\treturn h;\n}\n\n/*\n * Find either the hypertable or the materialized hypertable, if the relid is\n * a continuous aggregate, for the relid.\n *\n * If allow_matht is false, relid should be a cagg or a hypertable.\n * If allow_matht is true, materialized hypertable is also permitted as relid\n */\nHypertable *\nts_resolve_hypertable_from_table_or_cagg(Cache *hcache, Oid relid, bool allow_matht)\n{\n\tconst char *rel_name;\n\tHypertable *ht;\n\n\trel_name = get_rel_name(relid);\n\n\tif (!rel_name)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_TABLE),\n\t\t\t\t errmsg(\"invalid hypertable or continuous aggregate\")));\n\n\tht = ts_hypertable_cache_get_entry(hcache, relid, CACHE_FLAG_MISSING_OK);\n\n\tif (ht)\n\t{\n\t\tconst ContinuousAggHypertableStatus status = ts_continuous_agg_hypertable_status(ht->fd.id);\n\t\tswitch (status)\n\t\t{\n\t\t\tcase HypertableIsMaterialization:\n\t\t\tcase HypertableIsMaterializationAndRaw:\n\t\t\t\tif (!allow_matht)\n\t\t\t\t{\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t\t errmsg(\"operation not supported on materialized hypertable\"),\n\t\t\t\t\t\t\t errhint(\"Try the operation on the continuous aggregate instead.\"),\n\t\t\t\t\t\t\t errdetail(\"Hypertable \\\"%s\\\" is a materialized hypertable.\",\n\t\t\t\t\t\t\t\t\t   rel_name)));\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\telse\n\t{\n\t\tContinuousAgg *const cagg = ts_continuous_agg_find_by_relid(relid);\n\n\t\tif (!cagg)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_TS_HYPERTABLE_NOT_EXIST),\n\t\t\t\t\t errmsg(\"\\\"%s\\\" is not a hypertable or a continuous aggregate\", rel_name),\n\t\t\t\t\t errhint(\"The operation is only possible on a hypertable or continuous\"\n\t\t\t\t\t\t\t \" aggregate.\")));\n\n\t\tht = ts_hypertable_get_by_id(cagg->data.mat_hypertable_id);\n\n\t\tif (!ht)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t\t errmsg(\"no materialized table for continuous aggregate\"),\n\t\t\t\t\t errdetail(\"Continuous aggregate \\\"%s\\\" had a materialized hypertable\"\n\t\t\t\t\t\t\t   \" with id %d but it was not found in the hypertable \"\n\t\t\t\t\t\t\t   \"catalog.\",\n\t\t\t\t\t\t\t   rel_name,\n\t\t\t\t\t\t\t   cagg->data.mat_hypertable_id)));\n\t}\n\n\treturn ht;\n}\n\nstatic ScanTupleResult\nhypertable_tuple_get_relid(TupleInfo *ti, void *data)\n{\n\tOid *relid = data;\n\tFormData_hypertable fd;\n\tOid schema_oid;\n\n\tts_hypertable_formdata_fill(&fd, ti);\n\tschema_oid = get_namespace_oid(NameStr(fd.schema_name), true);\n\n\tif (OidIsValid(schema_oid))\n\t\t*relid = get_relname_relid(NameStr(fd.table_name), schema_oid);\n\n\treturn SCAN_DONE;\n}\n\nOid\nts_hypertable_id_to_relid(int32 hypertable_id, bool return_invalid)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tOid relid = InvalidOid;\n\tScanKeyData scankey[1];\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, HYPERTABLE),\n\t\t.index = catalog_get_index(catalog, HYPERTABLE, HYPERTABLE_ID_INDEX),\n\t\t.nkeys = 1,\n\t\t.scankey = scankey,\n\t\t.tuple_found = hypertable_tuple_get_relid,\n\t\t.data = &relid,\n\t\t.lockmode = AccessShareLock,\n\t\t.scandirection = ForwardScanDirection,\n\t};\n\n\t/* Perform an index scan on the hypertable pkey. */\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_hypertable_pkey_idx_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(hypertable_id));\n\n\tts_scanner_scan(&scanctx);\n\n\tif (!OidIsValid(relid) && !return_invalid)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_TS_HYPERTABLE_NOT_EXIST),\n\t\t\t\t errmsg(\"hypertable with id %d does not exist\", hypertable_id)));\n\n\treturn relid;\n}\n\nint32\nts_hypertable_relid_to_id(Oid relid)\n{\n\tCache *hcache;\n\tHypertable *ht = ts_hypertable_cache_get_cache_and_entry(relid, CACHE_FLAG_MISSING_OK, &hcache);\n\tint result = ht ? ht->fd.id : INVALID_HYPERTABLE_ID;\n\n\tts_cache_release(&hcache);\n\treturn result;\n}\n\nstatic int\nhypertable_scan_limit_internal(ScanKeyData *scankey, int num_scankeys, int indexid,\n\t\t\t\t\t\t\t   tuple_found_func on_tuple_found, void *scandata, int limit,\n\t\t\t\t\t\t\t   LOCKMODE lock, MemoryContext mctx, tuple_filter_func filter)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, HYPERTABLE),\n\t\t.index = catalog_get_index(catalog, HYPERTABLE, indexid),\n\t\t.nkeys = num_scankeys,\n\t\t.scankey = scankey,\n\t\t.data = scandata,\n\t\t.limit = limit,\n\t\t.tuple_found = on_tuple_found,\n\t\t.lockmode = lock,\n\t\t.filter = filter,\n\t\t.scandirection = ForwardScanDirection,\n\t\t.result_mctx = mctx,\n\t};\n\n\treturn ts_scanner_scan(&scanctx);\n}\n\n/* update the tuple at this tid. The assumption is that we already hold a\n * tuple exclusive lock and no other transaction can modify this tuple\n * The sequence of operations for any update is:\n * lock the tuple using lock_hypertable_tuple.\n * then update the required fields\n * call hypertable_update_catalog_tuple to complete the update.\n * This ensures correct tuple locking and tuple updates in the presence of\n * concurrent transactions. Failure to follow this results in catalog corruption\n */\nstatic void\nhypertable_update_catalog_tuple(ItemPointer tid, FormData_hypertable *update)\n{\n\tHeapTuple new_tuple;\n\tCatalogSecurityContext sec_ctx;\n\tCatalog *catalog = ts_catalog_get();\n\tOid table = catalog_get_table_id(catalog, HYPERTABLE);\n\tRelation hypertable_rel = relation_open(table, RowExclusiveLock);\n\n\tnew_tuple = hypertable_formdata_make_tuple(update, hypertable_rel->rd_att);\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_update_tid(hypertable_rel, tid, new_tuple);\n\tts_catalog_restore_user(&sec_ctx);\n\theap_freetuple(new_tuple);\n\trelation_close(hypertable_rel, NoLock);\n}\n\nstatic bool\nlock_hypertable_tuple(int32 htid, ItemPointer tid, FormData_hypertable *form)\n{\n\tbool success = false;\n\tScanTupLock scantuplock = {\n\t\t.waitpolicy = LockWaitBlock,\n\t\t.lockmode = LockTupleExclusive,\n\t};\n\tScanIterator iterator = ts_scan_iterator_create(HYPERTABLE, RowShareLock, CurrentMemoryContext);\n\titerator.ctx.index = catalog_get_index(ts_catalog_get(), HYPERTABLE, HYPERTABLE_ID_INDEX);\n\titerator.ctx.tuplock = &scantuplock;\n\t/* Keeping the lock since we presumably want to update the tuple */\n\titerator.ctx.flags = SCANNER_F_KEEPLOCK;\n\n\t/* see table_tuple_lock for details about flags that are set in TupleExclusive mode */\n\tscantuplock.lockflags = TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS;\n\tif (!IsolationUsesXactSnapshot())\n\t{\n\t\t/* in read committed mode, we follow all updates to this tuple */\n\t\tscantuplock.lockflags |= TUPLE_LOCK_FLAG_FIND_LAST_VERSION;\n\t}\n\n\tts_scan_iterator_scan_key_init(&iterator,\n\t\t\t\t\t\t\t\t   Anum_hypertable_pkey_idx_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(htid));\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tif (ti->lockresult != TM_Ok)\n\t\t{\n\t\t\tif (IsolationUsesXactSnapshot())\n\t\t\t{\n\t\t\t\t/* For Repeatable Read and Serializable isolation level report error\n\t\t\t\t * if we cannot lock the tuple\n\t\t\t\t */\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),\n\t\t\t\t\t\t errmsg(\"could not serialize access due to concurrent update\")));\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t\t\t errmsg(\"unable to lock hypertable catalog tuple, lock result is %d for \"\n\t\t\t\t\t\t\t\t\"hypertable \"\n\t\t\t\t\t\t\t\t\"ID (%d)\",\n\t\t\t\t\t\t\t\tti->lockresult,\n\t\t\t\t\t\t\t\thtid)));\n\t\t\t}\n\t\t}\n\t\tts_hypertable_formdata_fill(form, ti);\n\t\tItemPointer result_tid = ts_scanner_get_tuple_tid(ti);\n\t\ttid->ip_blkid = result_tid->ip_blkid;\n\t\ttid->ip_posid = result_tid->ip_posid;\n\t\tsuccess = true;\n\t\tbreak;\n\t}\n\tts_scan_iterator_close(&iterator);\n\treturn success;\n}\n\nint\nts_hypertable_scan_with_memory_context(const char *schema, const char *table,\n\t\t\t\t\t\t\t\t\t   tuple_found_func tuple_found, void *data, LOCKMODE lockmode,\n\t\t\t\t\t\t\t\t\t   MemoryContext mctx)\n{\n\tScanKeyData scankey[2];\n\tNameData schema_name = { .data = { 0 } };\n\tNameData table_name = { .data = { 0 } };\n\n\tif (schema)\n\t\tnamestrcpy(&schema_name, schema);\n\n\tif (table)\n\t\tnamestrcpy(&table_name, table);\n\n\t/* Perform an index scan on schema and table. */\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_hypertable_name_idx_table,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_NAMEEQ,\n\t\t\t\tNameGetDatum(&table_name));\n\tScanKeyInit(&scankey[1],\n\t\t\t\tAnum_hypertable_name_idx_schema,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_NAMEEQ,\n\t\t\t\tNameGetDatum(&schema_name));\n\n\treturn hypertable_scan_limit_internal(scankey,\n\t\t\t\t\t\t\t\t\t\t  2,\n\t\t\t\t\t\t\t\t\t\t  HYPERTABLE_NAME_INDEX,\n\t\t\t\t\t\t\t\t\t\t  tuple_found,\n\t\t\t\t\t\t\t\t\t\t  data,\n\t\t\t\t\t\t\t\t\t\t  1,\n\t\t\t\t\t\t\t\t\t\t  lockmode,\n\t\t\t\t\t\t\t\t\t\t  mctx,\n\t\t\t\t\t\t\t\t\t\t  NULL);\n}\n\nTSDLLEXPORT ObjectAddress\nts_hypertable_create_trigger(const Hypertable *ht, CreateTrigStmt *stmt, const char *query)\n{\n\tObjectAddress root_trigger_addr;\n\tList *chunks;\n\tListCell *lc;\n\tint sec_ctx;\n\tOid saved_uid;\n\tOid owner;\n\n\tAssert(ht != NULL);\n\n\t/* create the trigger on the root table */\n\t/* ACL permissions checks happen within this call */\n\troot_trigger_addr = CreateTrigger(stmt,\n\t\t\t\t\t\t\t\t\t  query,\n\t\t\t\t\t\t\t\t\t  InvalidOid,\n\t\t\t\t\t\t\t\t\t  InvalidOid,\n\t\t\t\t\t\t\t\t\t  InvalidOid,\n\t\t\t\t\t\t\t\t\t  InvalidOid,\n\t\t\t\t\t\t\t\t\t  InvalidOid,\n\t\t\t\t\t\t\t\t\t  InvalidOid,\n\t\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t\t  false,\n\t\t\t\t\t\t\t\t\t  false);\n\n\t/* and forward it to the chunks */\n\tCommandCounterIncrement();\n\n\tif (!stmt->row)\n\t\treturn root_trigger_addr;\n\n\t/* switch to the hypertable owner's role -- note that this logic must be the same as\n\t * `ts_trigger_create_all_on_chunk` */\n\towner = ts_rel_get_owner(ht->main_table_relid);\n\tGetUserIdAndSecContext(&saved_uid, &sec_ctx);\n\tif (saved_uid != owner)\n\t\tSetUserIdAndSecContext(owner, sec_ctx | SECURITY_LOCAL_USERID_CHANGE);\n\n\tchunks = find_inheritance_children(ht->main_table_relid, NoLock);\n\n\tforeach (lc, chunks)\n\t{\n\t\tOid chunk_oid = lfirst_oid(lc);\n\t\tchar *relschema = get_namespace_name(get_rel_namespace(chunk_oid));\n\t\tchar *relname = get_rel_name(chunk_oid);\n\t\tchar relkind = get_rel_relkind(chunk_oid);\n\n\t\tAssert(relkind == RELKIND_RELATION || relkind == RELKIND_FOREIGN_TABLE);\n\n\t\t/* Only create triggers on standard relations and not on, e.g., foreign\n\t\t * table chunks */\n\t\tif (relkind == RELKIND_RELATION)\n\t\t\tts_trigger_create_on_chunk(root_trigger_addr.objectId, relschema, relname);\n\t}\n\n\tif (saved_uid != owner)\n\t\tSetUserIdAndSecContext(saved_uid, sec_ctx);\n\n\treturn root_trigger_addr;\n}\n\nTSDLLEXPORT void\nts_hypertable_drop_invalidation_replication_slot(const char *slot_name)\n{\n\tCatalogSecurityContext sec_ctx;\n\tNameData slot;\n\tnamestrcpy(&slot, slot_name);\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tDirectFunctionCall1(pg_drop_replication_slot, NameGetDatum(&slot));\n\tts_catalog_restore_user(&sec_ctx);\n}\n\n/* based on RemoveObjects */\nTSDLLEXPORT void\nts_hypertable_drop_trigger(Oid relid, const char *trigger_name)\n{\n\tList *chunks = find_inheritance_children(relid, NoLock);\n\tListCell *lc;\n\n\tif (OidIsValid(relid))\n\t{\n\t\tObjectAddress objaddr = {\n\t\t\t.classId = TriggerRelationId,\n\t\t\t.objectId = get_trigger_oid(relid, trigger_name, true),\n\t\t};\n\t\tif (OidIsValid(objaddr.objectId))\n\t\t\tperformDeletion(&objaddr, DROP_RESTRICT, 0);\n\t}\n\n\tforeach (lc, chunks)\n\t{\n\t\tOid chunk_oid = lfirst_oid(lc);\n\t\tObjectAddress objaddr = {\n\t\t\t.classId = TriggerRelationId,\n\t\t\t.objectId = get_trigger_oid(chunk_oid, trigger_name, true),\n\t\t};\n\n\t\tif (OidIsValid(objaddr.objectId))\n\t\t\tperformDeletion(&objaddr, DROP_RESTRICT, 0);\n\t}\n}\n\nstatic ScanTupleResult\nhypertable_tuple_delete(TupleInfo *ti, void *data)\n{\n\tCatalogSecurityContext sec_ctx;\n\tbool isnull;\n\tbool compressed_hypertable_id_isnull;\n\tint hypertable_id = DatumGetInt32(slot_getattr(ti->slot, Anum_hypertable_id, &isnull));\n\tint compressed_hypertable_id =\n\t\tDatumGetInt32(slot_getattr(ti->slot,\n\t\t\t\t\t\t\t\t   Anum_hypertable_compressed_hypertable_id,\n\t\t\t\t\t\t\t\t   &compressed_hypertable_id_isnull));\n\n\tts_tablespace_delete(hypertable_id, NULL, InvalidOid);\n\tts_chunk_delete_by_hypertable_id(hypertable_id);\n\tts_dimension_delete_by_hypertable_id(hypertable_id, true);\n\n\t/* Also remove any policy argument / job that uses this hypertable */\n\tts_bgw_policy_delete_by_hypertable_id(hypertable_id);\n\n\t/* Also remove any rows in _timescaledb_catalog.chunk_column_stats corresponding to this\n\t * hypertable\n\t */\n\tts_chunk_column_stats_delete_by_hypertable_id(hypertable_id);\n\n\t/* Remove any dependent continuous aggs */\n\tts_continuous_agg_drop_hypertable_callback(hypertable_id);\n\n\tif (!compressed_hypertable_id_isnull)\n\t{\n\t\tHypertable *compressed_hypertable = ts_hypertable_get_by_id(compressed_hypertable_id);\n\t\t/* The hypertable may have already been deleted by a cascade */\n\t\tif (compressed_hypertable != NULL)\n\t\t\tts_hypertable_drop(compressed_hypertable, DROP_RESTRICT);\n\t}\n\n\thypertable_drop_hook_type osm_htdrop_hook = ts_get_osm_hypertable_drop_hook();\n\t/* Invoke the OSM callback if set */\n\tif (osm_htdrop_hook)\n\t{\n\t\tName schema_name =\n\t\t\tDatumGetName(slot_getattr(ti->slot, Anum_hypertable_schema_name, &isnull));\n\t\tName table_name = DatumGetName(slot_getattr(ti->slot, Anum_hypertable_table_name, &isnull));\n\n\t\tosm_htdrop_hook(NameStr(*schema_name), NameStr(*table_name));\n\t}\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_delete_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\tts_catalog_restore_user(&sec_ctx);\n\n\treturn SCAN_CONTINUE;\n}\n\nint\nts_hypertable_delete_by_name(const char *schema_name, const char *table_name)\n{\n\tScanKeyData scankey[2];\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_hypertable_name_idx_table,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_NAMEEQ,\n\t\t\t\tCStringGetDatum(table_name));\n\n\tScanKeyInit(&scankey[1],\n\t\t\t\tAnum_hypertable_name_idx_schema,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_NAMEEQ,\n\t\t\t\tCStringGetDatum(schema_name));\n\treturn hypertable_scan_limit_internal(scankey,\n\t\t\t\t\t\t\t\t\t\t  2,\n\t\t\t\t\t\t\t\t\t\t  HYPERTABLE_NAME_INDEX,\n\t\t\t\t\t\t\t\t\t\t  hypertable_tuple_delete,\n\t\t\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t\t\t  0,\n\t\t\t\t\t\t\t\t\t\t  RowExclusiveLock,\n\t\t\t\t\t\t\t\t\t\t  CurrentMemoryContext,\n\t\t\t\t\t\t\t\t\t\t  NULL);\n}\n\nint\nts_hypertable_delete_by_id(int32 hypertable_id)\n{\n\tScanKeyData scankey[1];\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_hypertable_pkey_idx_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(hypertable_id));\n\n\treturn hypertable_scan_limit_internal(scankey,\n\t\t\t\t\t\t\t\t\t\t  1,\n\t\t\t\t\t\t\t\t\t\t  HYPERTABLE_ID_INDEX,\n\t\t\t\t\t\t\t\t\t\t  hypertable_tuple_delete,\n\t\t\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t\t\t  1,\n\t\t\t\t\t\t\t\t\t\t  RowExclusiveLock,\n\t\t\t\t\t\t\t\t\t\t  CurrentMemoryContext,\n\t\t\t\t\t\t\t\t\t\t  NULL);\n}\n\nvoid\nts_hypertable_drop(Hypertable *hypertable, DropBehavior behavior)\n{\n\t/* The actual table might have been deleted already, but we still need to\n\t * clean up the catalog entry. */\n\tif (OidIsValid(hypertable->main_table_relid))\n\t{\n\t\tObjectAddress hypertable_addr = (ObjectAddress){\n\t\t\t.classId = RelationRelationId,\n\t\t\t.objectId = hypertable->main_table_relid,\n\t\t};\n\n\t\t/* Drop the postgres table */\n\t\tts_compression_settings_delete(hypertable->main_table_relid);\n\t\tperformDeletion(&hypertable_addr, behavior, 0);\n\t}\n\n\t/* Clean up catalog */\n\tts_hypertable_delete_by_name(NameStr(hypertable->fd.schema_name),\n\t\t\t\t\t\t\t\t NameStr(hypertable->fd.table_name));\n}\n\nstatic ScanTupleResult\nreset_associated_tuple_found(TupleInfo *ti, void *data)\n{\n\tHeapTuple new_tuple;\n\tFormData_hypertable fd;\n\tCatalogSecurityContext sec_ctx;\n\n\tts_hypertable_formdata_fill(&fd, ti);\n\tnamestrcpy(&fd.associated_schema_name, INTERNAL_SCHEMA_NAME);\n\tnew_tuple = hypertable_formdata_make_tuple(&fd, ts_scanner_get_tupledesc(ti));\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_update_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti), new_tuple);\n\tts_catalog_restore_user(&sec_ctx);\n\theap_freetuple(new_tuple);\n\n\treturn SCAN_CONTINUE;\n}\n\n/*\n * Reset the matching associated schema to the internal schema.\n */\nint\nts_hypertable_reset_associated_schema_name(const char *associated_schema)\n{\n\tScanKeyData scankey[1];\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_hypertable_associated_schema_name,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_NAMEEQ,\n\t\t\t\tCStringGetDatum(associated_schema));\n\n\treturn hypertable_scan_limit_internal(scankey,\n\t\t\t\t\t\t\t\t\t\t  1,\n\t\t\t\t\t\t\t\t\t\t  INVALID_INDEXID,\n\t\t\t\t\t\t\t\t\t\t  reset_associated_tuple_found,\n\t\t\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t\t\t  0,\n\t\t\t\t\t\t\t\t\t\t  RowExclusiveLock,\n\t\t\t\t\t\t\t\t\t\t  CurrentMemoryContext,\n\t\t\t\t\t\t\t\t\t\t  NULL);\n}\n\nint\nts_hypertable_set_name(Hypertable *ht, const char *newname)\n{\n\tFormData_hypertable form;\n\tItemPointerData tid;\n\t/* lock the tuple entry in the catalog table */\n\tbool found = lock_hypertable_tuple(ht->fd.id, &tid, &form);\n\tEnsure(found, \"hypertable id %d not found\", ht->fd.id);\n\n\tnamestrcpy(&form.table_name, newname);\n\thypertable_update_catalog_tuple(&tid, &form);\n\treturn true;\n}\n\nint\nts_hypertable_set_schema(Hypertable *ht, const char *newname)\n{\n\tFormData_hypertable form;\n\tItemPointerData tid;\n\t/* lock the tuple entry in the catalog table */\n\tbool found = lock_hypertable_tuple(ht->fd.id, &tid, &form);\n\tEnsure(found, \"hypertable id %d not found\", ht->fd.id);\n\n\tnamestrcpy(&form.schema_name, newname);\n\thypertable_update_catalog_tuple(&tid, &form);\n\treturn true;\n}\n\nint\nts_hypertable_set_num_dimensions(Hypertable *ht, int16 num_dimensions)\n{\n\tFormData_hypertable form;\n\tItemPointerData tid;\n\t/* lock the tuple entry in the catalog table */\n\tbool found = lock_hypertable_tuple(ht->fd.id, &tid, &form);\n\tEnsure(found, \"hypertable id %d not found\", ht->fd.id);\n\n\tAssert(num_dimensions > 0);\n\tform.num_dimensions = num_dimensions;\n\thypertable_update_catalog_tuple(&tid, &form);\n\treturn true;\n}\n\n#define DEFAULT_ASSOCIATED_TABLE_PREFIX_FORMAT \"_hyper_%d\"\nstatic const size_t MAXIMUM_PREFIX_LENGTH = NAMEDATALEN - 16;\n\nstatic void\nhypertable_insert_relation(Relation rel, FormData_hypertable *fd)\n{\n\tHeapTuple new_tuple;\n\tCatalogSecurityContext sec_ctx;\n\n\tnew_tuple = hypertable_formdata_make_tuple(fd, RelationGetDescr(rel));\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_insert(rel, new_tuple);\n\tts_catalog_restore_user(&sec_ctx);\n\theap_freetuple(new_tuple);\n}\n\nstatic void\nhypertable_insert(int32 hypertable_id, Name schema_name, Name table_name,\n\t\t\t\t  Name associated_schema_name, Name associated_table_prefix,\n\t\t\t\t  Name chunk_sizing_func_schema, Name chunk_sizing_func_name,\n\t\t\t\t  int64 chunk_target_size, int16 num_dimensions, bool compressed)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tRelation rel;\n\tFormData_hypertable fd;\n\n\tfd.id = hypertable_id;\n\tif (fd.id == INVALID_HYPERTABLE_ID)\n\t{\n\t\tCatalogSecurityContext sec_ctx;\n\t\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\t\tfd.id = ts_catalog_table_next_seq_id(ts_catalog_get(), HYPERTABLE);\n\t\tts_catalog_restore_user(&sec_ctx);\n\t}\n\n\tnamestrcpy(&fd.schema_name, NameStr(*schema_name));\n\tnamestrcpy(&fd.table_name, NameStr(*table_name));\n\tnamestrcpy(&fd.associated_schema_name, NameStr(*associated_schema_name));\n\n\tif (NULL == associated_table_prefix)\n\t{\n\t\tNameData default_associated_table_prefix;\n\t\tmemset(NameStr(default_associated_table_prefix), '\\0', NAMEDATALEN);\n\t\tsnprintf(NameStr(default_associated_table_prefix),\n\t\t\t\t NAMEDATALEN,\n\t\t\t\t DEFAULT_ASSOCIATED_TABLE_PREFIX_FORMAT,\n\t\t\t\t fd.id);\n\t\tnamestrcpy(&fd.associated_table_prefix, NameStr(default_associated_table_prefix));\n\t}\n\telse\n\t{\n\t\tnamestrcpy(&fd.associated_table_prefix, NameStr(*associated_table_prefix));\n\t}\n\tif (strnlen(NameStr(fd.associated_table_prefix), NAMEDATALEN) > MAXIMUM_PREFIX_LENGTH)\n\t\telog(ERROR, \"associated_table_prefix too long\");\n\n\tfd.num_dimensions = num_dimensions;\n\n\tnamestrcpy(&fd.chunk_sizing_func_schema, NameStr(*chunk_sizing_func_schema));\n\tnamestrcpy(&fd.chunk_sizing_func_name, NameStr(*chunk_sizing_func_name));\n\n\tfd.chunk_target_size = chunk_target_size;\n\tif (fd.chunk_target_size < 0)\n\t\tfd.chunk_target_size = 0;\n\n\tif (compressed)\n\t\tfd.compression_state = HypertableInternalCompressionTable;\n\telse\n\t\tfd.compression_state = HypertableCompressionOff;\n\n\t/* when creating a hypertable, there is never an associated compressed dual */\n\tfd.compressed_hypertable_id = INVALID_HYPERTABLE_ID;\n\n\t/* new hypertable does not have OSM chunk */\n\tfd.status = HYPERTABLE_STATUS_DEFAULT;\n\n\trel = table_open(catalog_get_table_id(catalog, HYPERTABLE), RowExclusiveLock);\n\thypertable_insert_relation(rel, &fd);\n\ttable_close(rel, RowExclusiveLock);\n}\n\nstatic ScanTupleResult\nhypertable_tuple_found(TupleInfo *ti, void *data)\n{\n\tHypertable **entry = (Hypertable **) data;\n\n\t*entry = ts_hypertable_from_tupleinfo(ti);\n\treturn SCAN_DONE;\n}\n\nHypertable *\nts_hypertable_get_by_name(const char *schema, const char *name)\n{\n\tHypertable *ht = NULL;\n\n\thypertable_scan(schema, name, hypertable_tuple_found, (void *) &ht, AccessShareLock);\n\n\treturn ht;\n}\n\nHypertable *\nts_hypertable_get_by_id(int32 hypertable_id)\n{\n\tScanKeyData scankey[1];\n\tHypertable *ht = NULL;\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_hypertable_pkey_idx_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(hypertable_id));\n\n\thypertable_scan_limit_internal(scankey,\n\t\t\t\t\t\t\t\t   1,\n\t\t\t\t\t\t\t\t   HYPERTABLE_ID_INDEX,\n\t\t\t\t\t\t\t\t   hypertable_tuple_found,\n\t\t\t\t\t\t\t\t   (void *) &ht,\n\t\t\t\t\t\t\t\t   1,\n\t\t\t\t\t\t\t\t   AccessShareLock,\n\t\t\t\t\t\t\t\t   CurrentMemoryContext,\n\t\t\t\t\t\t\t\t   NULL);\n\treturn ht;\n}\n\nstatic void\nhypertable_chunk_store_free(void *entry)\n{\n\tts_chunk_free((Chunk *) entry);\n}\n\n/*\n * Add the chunk to the cache that allows fast lookup of chunks\n * for a given hyperspace Point.\n */\nChunk *\nts_hypertable_chunk_store_add(const Hypertable *h, const Chunk *input_chunk)\n{\n\tMemoryContext old_mcxt;\n\n\t/* Add the chunk to the subspace store */\n\told_mcxt = MemoryContextSwitchTo(ts_subspace_store_mcxt(h->chunk_cache));\n\tChunk *cached_chunk = ts_chunk_copy(input_chunk);\n\tts_subspace_store_add(h->chunk_cache,\n\t\t\t\t\t\t  cached_chunk->cube,\n\t\t\t\t\t\t  cached_chunk,\n\t\t\t\t\t\t  hypertable_chunk_store_free);\n\tMemoryContextSwitchTo(old_mcxt);\n\n\treturn cached_chunk;\n}\n\n/*\n * Create a chunk for the point, given that it does not exist yet.\n *\n * If the chunk already exists (i.e., another process beat us to it), then\n * lock the chunk with the specified lockmode. If the chunk is created, it\n * will always be locked with AccessExclusivelock.\n */\nChunk *\nts_hypertable_create_chunk_for_point(const Hypertable *h, const Point *point,\n\t\t\t\t\t\t\t\t\t LOCKMODE chunk_lockmode)\n{\n\tAssert(ts_subspace_store_get(h->chunk_cache, point) == NULL);\n\n\tChunk *chunk = ts_chunk_create_for_point(h,\n\t\t\t\t\t\t\t\t\t\t\t point,\n\t\t\t\t\t\t\t\t\t\t\t NameStr(h->fd.associated_schema_name),\n\t\t\t\t\t\t\t\t\t\t\t NameStr(h->fd.associated_table_prefix),\n\t\t\t\t\t\t\t\t\t\t\t chunk_lockmode);\n\n\t/* Also add the chunk to the hypertable's chunk store */\n\tChunk *cached_chunk = ts_hypertable_chunk_store_add(h, chunk);\n\treturn cached_chunk;\n}\n\n/*\n * Find the chunk responsible for the given point.\n *\n * In case of a cache miss, a point scan will try to find a matching chunk. A\n * matching chunk will be locked in the given lockmode (unless NoLock is\n * specified) and added to the cache.\n *\n * If lockmode is higher than NoLock, all dimension slices will also be locked\n * in LockTupleKeyShare.\n *\n * If no chunk is found, NULL is returned. The returned chunk is owned by the\n * cache and may become invalid after some subsequent call to this function.\n * Leaks memory, so call in a short-lived context.\n */\nChunk *\nts_hypertable_find_chunk_for_point(const Hypertable *h, const Point *point, LOCKMODE lockmode)\n{\n\tChunk *chunk = ts_subspace_store_get(h->chunk_cache, point);\n\n\tif (!chunk)\n\t{\n\t\tchunk = ts_chunk_find_for_point(h, point, lockmode);\n\n\t\tif (chunk)\n\t\t\tchunk = ts_hypertable_chunk_store_add(h, chunk);\n\t}\n\telse if (!ts_chunk_lock_if_exists(chunk->table_id, lockmode))\n\t{\n\t\treturn NULL;\n\t}\n\n#ifdef USE_ASSERT_CHECKING\n\tif (chunk)\n\t{\n\t\tRelation chunk_rel = RelationIdGetRelation(chunk->table_id);\n\t\tAssert(CheckRelationLockedByMe(chunk_rel, lockmode, true));\n\t\tRelationClose(chunk_rel);\n\t}\n#endif\n\n\treturn chunk;\n}\n\nbool\nts_hypertable_has_tablespace(const Hypertable *ht, Oid tspc_oid)\n{\n\tTablespaces *tspcs = ts_tablespace_scan(ht->fd.id);\n\n\treturn ts_tablespaces_contain(tspcs, tspc_oid);\n}\n\nstatic int\nhypertable_get_chunk_round_robin_index(const Hypertable *ht, const Hypercube *hc)\n{\n\tconst Dimension *dim;\n\tconst DimensionSlice *slice;\n\tint offset = 0;\n\n\tAssert(NULL != ht);\n\tAssert(NULL != hc);\n\n\tdim = hyperspace_get_closed_dimension(ht->space, 0);\n\n\tif (NULL == dim)\n\t{\n\t\tdim = hyperspace_get_open_dimension(ht->space, 0);\n\t\t/* Add some randomness between hypertables so that\n\t\t * if there is no space partitions, but multiple hypertables\n\t\t * the initial index is different for different hypertables.\n\t\t * This protects against creating a lot of chunks on the same\n\t\t * data node when many hypertables are created at roughly\n\t\t * the same time, e.g., from a bootstrap script.\n\t\t */\n\t\toffset = (int) ht->fd.id;\n\t}\n\n\tAssert(NULL != dim);\n\n\tslice = ts_hypercube_get_slice_by_dimension_id(hc, dim->fd.id);\n\n\tAssert(NULL != slice);\n\n\treturn ts_dimension_get_slice_ordinal(dim, slice) + offset;\n}\n\n/*\n * Select a tablespace to use for a given chunk.\n *\n * Selection happens based on the first closed (space) dimension, if available,\n * otherwise the first closed (time) one.\n *\n * We try to do \"sticky\" selection to consistently pick the same tablespace for\n * chunks in the same closed (space) dimension. This ensures chunks in the same\n * \"space\" partition will live on the same disk.\n */\nTablespace *\nts_hypertable_select_tablespace(const Hypertable *ht, const Chunk *chunk)\n{\n\tTablespaces *tspcs = ts_tablespace_scan(ht->fd.id);\n\tint i;\n\n\tif (NULL == tspcs || tspcs->num_tablespaces == 0)\n\t\treturn NULL;\n\n\ti = hypertable_get_chunk_round_robin_index(ht, chunk->cube);\n\n\t/* Use the index of the slice to find the tablespace */\n\treturn &tspcs->tablespaces[i % tspcs->num_tablespaces];\n}\n\nconst char *\nts_hypertable_select_tablespace_name(const Hypertable *ht, const Chunk *chunk)\n{\n\tTablespace *tspc = ts_hypertable_select_tablespace(ht, chunk);\n\tOid main_tspc_oid;\n\n\tif (tspc != NULL)\n\t\treturn NameStr(tspc->fd.tablespace_name);\n\n\t/* Use main table tablespace, if any */\n\tmain_tspc_oid = get_rel_tablespace(ht->main_table_relid);\n\tif (OidIsValid(main_tspc_oid))\n\t\treturn get_tablespace_name(main_tspc_oid);\n\n\treturn NULL;\n}\n\n/*\n * Get the tablespace at an offset from the given tablespace.\n */\nTablespace *\nts_hypertable_get_tablespace_at_offset_from(int32 hypertable_id, Oid tablespace_oid, int16 offset)\n{\n\tTablespaces *tspcs = ts_tablespace_scan(hypertable_id);\n\tint i = 0;\n\n\tif (NULL == tspcs || tspcs->num_tablespaces == 0)\n\t\treturn NULL;\n\n\tfor (i = 0; i < tspcs->num_tablespaces; i++)\n\t{\n\t\tif (tablespace_oid == tspcs->tablespaces[i].tablespace_oid)\n\t\t\treturn &tspcs->tablespaces[(i + offset) % tspcs->num_tablespaces];\n\t}\n\n\treturn NULL;\n}\n\nstatic inline Oid\nhypertable_relid_lookup(Oid relid)\n{\n\tCache *hcache;\n\tHypertable *ht = ts_hypertable_cache_get_cache_and_entry(relid, CACHE_FLAG_MISSING_OK, &hcache);\n\tOid result = (ht == NULL) ? InvalidOid : ht->main_table_relid;\n\n\tts_cache_release(&hcache);\n\n\treturn result;\n}\n\n/*\n * Returns a hypertable's relation ID (OID) iff the given RangeVar corresponds to\n * a hypertable, otherwise InvalidOid.\n */\nOid\nts_hypertable_relid(RangeVar *rv)\n{\n\treturn hypertable_relid_lookup(RangeVarGetRelid(rv, NoLock, true));\n}\n\nbool\nts_is_hypertable(Oid relid)\n{\n\tif (!OidIsValid(relid))\n\t\treturn false;\n\n\treturn OidIsValid(hypertable_relid_lookup(relid));\n}\n\n/*\n * Check that the current user can create chunks in a hypertable's associated\n * schema.\n *\n * This function is typically called from create_hypertable() to verify that the\n * table owner has CREATE permissions for the schema (if it already exists) or\n * the database (if the schema does not exist and needs to be created).\n */\nstatic Oid\nhypertable_check_associated_schema_permissions(const char *schema_name, Oid user_oid)\n{\n\tOid schema_oid;\n\n\t/*\n\t * If the schema name is NULL, it implies the internal catalog schema and\n\t * anyone should be able to create chunks there.\n\t */\n\tif (NULL == schema_name)\n\t\treturn InvalidOid;\n\n\tschema_oid = get_namespace_oid(schema_name, true);\n\n\t/* Anyone can create chunks in the internal schema */\n\tif (strncmp(schema_name, INTERNAL_SCHEMA_NAME, NAMEDATALEN) == 0)\n\t{\n\t\tAssert(OidIsValid(schema_oid));\n\t\treturn schema_oid;\n\t}\n\n\tif (!OidIsValid(schema_oid))\n\t{\n\t\t/*\n\t\t * Schema does not exist, so we must check that the user has\n\t\t * privileges to create the schema in the current database\n\t\t */\n\t\tif (object_aclcheck(DatabaseRelationId, MyDatabaseId, user_oid, ACL_CREATE) != ACLCHECK_OK)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),\n\t\t\t\t\t errmsg(\"permissions denied: cannot create schema \\\"%s\\\" in database \\\"%s\\\"\",\n\t\t\t\t\t\t\tschema_name,\n\t\t\t\t\t\t\tget_database_name(MyDatabaseId))));\n\t}\n\telse if (object_aclcheck(NamespaceRelationId, schema_oid, user_oid, ACL_CREATE) != ACLCHECK_OK)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),\n\t\t\t\t errmsg(\"permissions denied: cannot create chunks in schema \\\"%s\\\"\", schema_name)));\n\n\treturn schema_oid;\n}\n\ninline static bool\ntable_has_rules(Relation rel)\n{\n\treturn rel->rd_rules != NULL;\n}\n\nbool\nts_hypertable_has_chunks(Oid table_relid, LOCKMODE lockmode)\n{\n\treturn find_inheritance_children(table_relid, lockmode) != NIL;\n}\n\nstatic void\nhypertable_create_schema(const char *schema_name)\n{\n\tCreateSchemaStmt stmt = {\n\t\t.schemaname = (char *) schema_name,\n\t\t.authrole = NULL,\n\t\t.schemaElts = NIL,\n\t\t.if_not_exists = true,\n\t};\n\n\tCreateSchemaCommand(&stmt, \"(generated CREATE SCHEMA command)\", -1, -1);\n}\n\n/*\n * Check that existing table constraints are supported.\n *\n * Hypertables do not support the following constraints:\n *\n * - NO INHERIT constraints cannot be enforced on a hypertable since they only\n *   exist on the parent table, which will have no tuples.\n * - FOREIGN KEY constraints referencing a hypertable.\n */\nstatic void\nhypertable_validate_constraints(Oid relid)\n{\n\tRelation catalog;\n\tSysScanDesc scan;\n\tScanKeyData scankey;\n\tHeapTuple tuple;\n\n\tcatalog = table_open(ConstraintRelationId, AccessShareLock);\n\n\tScanKeyInit(&scankey,\n\t\t\t\tAnum_pg_constraint_conrelid,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_OIDEQ,\n\t\t\t\tObjectIdGetDatum(relid));\n\n\tscan = systable_beginscan(catalog, ConstraintRelidTypidNameIndexId, true, NULL, 1, &scankey);\n\n\twhile (HeapTupleIsValid(tuple = systable_getnext(scan)))\n\t{\n\t\tForm_pg_constraint form = (Form_pg_constraint) GETSTRUCT(tuple);\n\n\t\tif (form->contype == CONSTRAINT_FOREIGN)\n\t\t{\n\t\t\tif (ts_hypertable_relid_to_id(form->confrelid) != INVALID_HYPERTABLE_ID &&\n\t\t\t\t!is_partitioning_allowed(form->confrelid))\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t errmsg(\"hypertables cannot be used as foreign key references of \"\n\t\t\t\t\t\t\t\t\"hypertables\")));\n\t\t}\n\n\t\tif (form->contype == CONSTRAINT_CHECK && form->connoinherit)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_TABLE_DEFINITION),\n\t\t\t\t\t errmsg(\"cannot have NO INHERIT constraints on hypertable \\\"%s\\\"\",\n\t\t\t\t\t\t\tget_rel_name(relid)),\n\t\t\t\t\t errhint(\"Remove all NO INHERIT constraints from table \\\"%s\\\" before \"\n\t\t\t\t\t\t\t \"making it a hypertable.\",\n\t\t\t\t\t\t\t get_rel_name(relid))));\n\t}\n\n\tsystable_endscan(scan);\n\n\t/* Check for foreign keys that reference this table */\n\tScanKeyInit(&scankey,\n\t\t\t\tAnum_pg_constraint_confrelid,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_OIDEQ,\n\t\t\t\tObjectIdGetDatum(relid));\n\n\tscan = systable_beginscan(catalog, 0, false, NULL, 1, &scankey);\n\n\twhile (HeapTupleIsValid(tuple = systable_getnext(scan)))\n\t{\n\t\tForm_pg_constraint form = (Form_pg_constraint) GETSTRUCT(tuple);\n\n\t\t/*\n\t\t * Hypertable <-> hypertable foreign keys are not supported.\n\t\t */\n\t\tif (form->contype == CONSTRAINT_FOREIGN &&\n\t\t\tts_hypertable_relid_to_id(form->conrelid) != INVALID_HYPERTABLE_ID)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_TABLE_DEFINITION),\n\t\t\t\t\t errmsg(\"cannot have FOREIGN KEY constraints to hypertable \\\"%s\\\"\",\n\t\t\t\t\t\t\tget_rel_name(relid)),\n\t\t\t\t\t errhint(\"Remove all FOREIGN KEY constraints to table \\\"%s\\\" before \"\n\t\t\t\t\t\t\t \"making it a hypertable.\",\n\t\t\t\t\t\t\t get_rel_name(relid))));\n\t}\n\n\tsystable_endscan(scan);\n\ttable_close(catalog, AccessShareLock);\n}\n\nstatic Datum\ncreate_hypertable_datum(FunctionCallInfo fcinfo, const Hypertable *ht, bool created,\n\t\t\t\t\t\tbool is_generic)\n{\n\tTupleDesc tupdesc;\n\tHeapTuple tuple;\n\n\tif (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"function returning record called in \"\n\t\t\t\t\t\t\"context that cannot accept type record\")));\n\n\ttupdesc = BlessTupleDesc(tupdesc);\n\n\tif (is_generic)\n\t{\n\t\tDatum values[Natts_generic_create_hypertable];\n\t\tbool nulls[Natts_generic_create_hypertable] = { false };\n\n\t\tvalues[AttrNumberGetAttrOffset(Anum_generic_create_hypertable_id)] =\n\t\t\tInt32GetDatum(ht->fd.id);\n\t\tvalues[AttrNumberGetAttrOffset(Anum_generic_create_hypertable_created)] =\n\t\t\tBoolGetDatum(created);\n\t\ttuple = heap_form_tuple(tupdesc, values, nulls);\n\t}\n\telse\n\t{\n\t\tDatum values[Natts_create_hypertable];\n\t\tbool nulls[Natts_create_hypertable] = { false };\n\n\t\tvalues[AttrNumberGetAttrOffset(Anum_create_hypertable_id)] = Int32GetDatum(ht->fd.id);\n\t\tvalues[AttrNumberGetAttrOffset(Anum_create_hypertable_schema_name)] =\n\t\t\tNameGetDatum(&ht->fd.schema_name);\n\t\tvalues[AttrNumberGetAttrOffset(Anum_create_hypertable_table_name)] =\n\t\t\tNameGetDatum(&ht->fd.table_name);\n\t\tvalues[AttrNumberGetAttrOffset(Anum_create_hypertable_created)] = BoolGetDatum(created);\n\t\ttuple = heap_form_tuple(tupdesc, values, nulls);\n\t}\n\n\treturn HeapTupleGetDatum(tuple);\n}\n\nTS_FUNCTION_INFO_V1(ts_hypertable_create);\nTS_FUNCTION_INFO_V1(ts_hypertable_create_general);\n\n/*\n * Create a hypertable from an existing table. The specific version of create hypertable API\n * process the function arguments before calling this function.\n */\nstatic Datum\nts_hypertable_create_internal(FunctionCallInfo fcinfo, Oid table_relid,\n\t\t\t\t\t\t\t  DimensionInfo *open_dim_info, DimensionInfo *closed_dim_info,\n\t\t\t\t\t\t\t  Name associated_schema_name, Name associated_table_prefix,\n\t\t\t\t\t\t\t  bool create_default_indexes, bool if_not_exists, bool migrate_data,\n\t\t\t\t\t\t\t  text *target_size, Oid sizing_func, bool is_generic)\n{\n\tCache *hcache;\n\tHypertable *ht;\n\tDatum retval;\n\tbool created;\n\tuint32 flags = 0;\n\n\tts_feature_flag_check(FEATURE_HYPERTABLE);\n\n\tChunkSizingInfo chunk_sizing_info = {\n\t\t.table_relid = table_relid,\n\t\t.target_size = target_size,\n\t\t.func = sizing_func,\n\t\t.colname = NameStr(open_dim_info->colname),\n\t\t.check_for_index = !create_default_indexes,\n\t};\n\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\tht = ts_hypertable_cache_get_cache_and_entry(table_relid, CACHE_FLAG_MISSING_OK, &hcache);\n\tif (ht)\n\t{\n\t\tif (if_not_exists)\n\t\t\tereport(NOTICE,\n\t\t\t\t\t(errcode(ERRCODE_TS_HYPERTABLE_EXISTS),\n\t\t\t\t\t errmsg(\"table \\\"%s\\\" is already a hypertable, skipping\",\n\t\t\t\t\t\t\tget_rel_name(table_relid))));\n\t\telse\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_TS_HYPERTABLE_EXISTS),\n\t\t\t\t\t errmsg(\"table \\\"%s\\\" is already a hypertable\", get_rel_name(table_relid))));\n\t\tcreated = false;\n\t}\n\telse\n\t{\n\t\t/* Release previously pinned cache */\n\t\tts_cache_release(&hcache);\n\n\t\tif (closed_dim_info && !closed_dim_info->num_slices_is_set)\n\t\t{\n\t\t\t/* If the number of partitions isn't specified, default to setting it\n\t\t\t * to the number of data nodes */\n\t\t\tint16 num_partitions = closed_dim_info->num_slices;\n\t\t\tclosed_dim_info->num_slices = num_partitions;\n\t\t\tclosed_dim_info->num_slices_is_set = true;\n\t\t}\n\n\t\tif (if_not_exists)\n\t\t\tflags |= HYPERTABLE_CREATE_IF_NOT_EXISTS;\n\t\tif (!create_default_indexes)\n\t\t\tflags |= HYPERTABLE_CREATE_DISABLE_DEFAULT_INDEXES;\n\t\tif (migrate_data)\n\t\t\tflags |= HYPERTABLE_CREATE_MIGRATE_DATA;\n\n\t\tcreated = ts_hypertable_create_from_info(table_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t INVALID_HYPERTABLE_ID,\n\t\t\t\t\t\t\t\t\t\t\t\t flags,\n\t\t\t\t\t\t\t\t\t\t\t\t open_dim_info,\n\t\t\t\t\t\t\t\t\t\t\t\t closed_dim_info,\n\t\t\t\t\t\t\t\t\t\t\t\t associated_schema_name,\n\t\t\t\t\t\t\t\t\t\t\t\t associated_table_prefix,\n\t\t\t\t\t\t\t\t\t\t\t\t &chunk_sizing_info);\n\n\t\tAssert(created);\n\t\tht = ts_hypertable_cache_get_cache_and_entry(table_relid, CACHE_FLAG_NONE, &hcache);\n\t}\n\n\tretval = create_hypertable_datum(fcinfo, ht, created, is_generic);\n\tts_cache_release(&hcache);\n\n\tPG_RETURN_DATUM(retval);\n}\n\n/*\n * Process create_hypertable parameters for time specific implementation.\n *\n * Arguments:\n * relation                REGCLASS\n * time_column_name        NAME\n * partitioning_column     NAME = NULL\n * number_partitions       INTEGER = NULL\n * associated_schema_name  NAME = NULL\n * associated_table_prefix NAME = NULL\n * chunk_time_interval     anyelement = NULL::BIGINT\n * create_default_indexes  BOOLEAN = TRUE\n * if_not_exists           BOOLEAN = FALSE\n * partitioning_func       REGPROC = NULL\n * migrate_data            BOOLEAN = FALSE\n * chunk_target_size       TEXT = NULL\n * chunk_sizing_func       OID = NULL\n * time_partitioning_func  REGPROC = NULL\n */\nDatum\nts_hypertable_create(PG_FUNCTION_ARGS)\n{\n\tOid table_relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\tName open_dim_name = PG_ARGISNULL(1) ? NULL : PG_GETARG_NAME(1);\n\tName closed_dim_name = PG_ARGISNULL(2) ? NULL : PG_GETARG_NAME(2);\n\tint16 num_partitions = PG_ARGISNULL(3) ? -1 : PG_GETARG_INT16(3);\n\tName associated_schema_name = PG_ARGISNULL(4) ? NULL : PG_GETARG_NAME(4);\n\tName associated_table_prefix = PG_ARGISNULL(5) ? NULL : PG_GETARG_NAME(5);\n\tDatum default_interval = PG_ARGISNULL(6) ? UnassignedDatum : PG_GETARG_DATUM(6);\n\tOid interval_type = PG_ARGISNULL(6) ? InvalidOid : get_fn_expr_argtype(fcinfo->flinfo, 6);\n\tbool create_default_indexes =\n\t\tPG_ARGISNULL(7) ? false : PG_GETARG_BOOL(7); /* Defaults to true in the sql code */\n\tbool if_not_exists = PG_ARGISNULL(8) ? false : PG_GETARG_BOOL(8);\n\tregproc closed_partitioning_func = PG_ARGISNULL(9) ? InvalidOid : PG_GETARG_OID(9);\n\tbool migrate_data = PG_ARGISNULL(10) ? false : PG_GETARG_BOOL(10);\n\ttext *target_size = PG_ARGISNULL(11) ? NULL : PG_GETARG_TEXT_P(11);\n\tOid sizing_func = PG_ARGISNULL(12) ? InvalidOid : PG_GETARG_OID(12);\n\tregproc open_partitioning_func = PG_ARGISNULL(13) ? InvalidOid : PG_GETARG_OID(13);\n\n\tif (!OidIsValid(table_relid))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"relation cannot be NULL\")));\n\n\tif (get_rel_name(table_relid) == NULL)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_TABLE),\n\t\t\t\t errmsg(\"relation with oid %d not found\", table_relid)));\n\t}\n\n\tif (!open_dim_name)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"partition column cannot be NULL\")));\n\n\tDimensionInfo *open_dim_info =\n\t\tts_dimension_info_create_open(table_relid,\n\t\t\t\t\t\t\t\t\t  open_dim_name,\t\t /* column name */\n\t\t\t\t\t\t\t\t\t  default_interval,\t\t /* interval */\n\t\t\t\t\t\t\t\t\t  interval_type,\t\t /* interval type */\n\t\t\t\t\t\t\t\t\t  open_partitioning_func /* partitioning func */\n\t\t);\n\n\tDimensionInfo *closed_dim_info = NULL;\n\tif (closed_dim_name)\n\t\tclosed_dim_info =\n\t\t\tts_dimension_info_create_closed(table_relid,\n\t\t\t\t\t\t\t\t\t\t\tclosed_dim_name,\t\t /* column name */\n\t\t\t\t\t\t\t\t\t\t\tnum_partitions,\t\t\t /* number partitions */\n\t\t\t\t\t\t\t\t\t\t\tclosed_partitioning_func /* partitioning func */\n\t\t\t);\n\n\treturn ts_hypertable_create_internal(fcinfo,\n\t\t\t\t\t\t\t\t\t\t table_relid,\n\t\t\t\t\t\t\t\t\t\t open_dim_info,\n\t\t\t\t\t\t\t\t\t\t closed_dim_info,\n\t\t\t\t\t\t\t\t\t\t associated_schema_name,\n\t\t\t\t\t\t\t\t\t\t associated_table_prefix,\n\t\t\t\t\t\t\t\t\t\t create_default_indexes,\n\t\t\t\t\t\t\t\t\t\t if_not_exists,\n\t\t\t\t\t\t\t\t\t\t migrate_data,\n\t\t\t\t\t\t\t\t\t\t target_size,\n\t\t\t\t\t\t\t\t\t\t sizing_func,\n\t\t\t\t\t\t\t\t\t\t false);\n}\n\nstatic Oid\nget_sizing_func_oid()\n{\n\tconst char *sizing_func_name = \"calculate_chunk_interval\";\n\tconst int sizing_func_nargs = 3;\n\tstatic Oid sizing_func_arg_types[] = { INT4OID, INT8OID, INT8OID };\n\n\treturn ts_get_function_oid(sizing_func_name,\n\t\t\t\t\t\t\t   FUNCTIONS_SCHEMA_NAME,\n\t\t\t\t\t\t\t   sizing_func_nargs,\n\t\t\t\t\t\t\t   sizing_func_arg_types);\n}\n\n/*\n * Process create_hypertable parameters for generic implementation.\n *\n * Arguments:\n * relation                REGCLASS\n * dimension               dimension_info\n * create_default_indexes  BOOLEAN = TRUE\n * if_not_exists           BOOLEAN = FALSE\n * migrate_data            BOOLEAN = FALSE\n */\nDatum\nts_hypertable_create_general(PG_FUNCTION_ARGS)\n{\n\tOid table_relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\tDimensionInfo *dim_info = NULL;\n\tGETARG_NOTNULL_POINTER(dim_info, 1, \"dimension\", DimensionInfo);\n\tbool create_default_indexes = PG_ARGISNULL(2) ? false : PG_GETARG_BOOL(2);\n\tbool if_not_exists = PG_ARGISNULL(3) ? false : PG_GETARG_BOOL(3);\n\tbool migrate_data = PG_ARGISNULL(4) ? false : PG_GETARG_BOOL(4);\n\n\t/*\n\t * We do not support closed (hash) dimensions for the main partitioning\n\t * column. Check that first. The behavior then becomes consistent with the\n\t * earlier \"ts_hypertable_create\" implementation.\n\t */\n\tif (IS_CLOSED_DIMENSION(dim_info))\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_WRONG_OBJECT_TYPE),\n\t\t\t\t errmsg(\"cannot partition using a closed dimension on primary column\"),\n\t\t\t\t errhint(\"Use range partitioning on the primary column.\")));\n\t}\n\n\t/*\n\t * Current implementation requires to provide a valid chunk sizing function\n\t * that is being used to populate hypertable catalog information.\n\t */\n\tOid sizing_func = get_sizing_func_oid();\n\n\t/*\n\t * Fill in the rest of the info.\n\t */\n\tdim_info->table_relid = table_relid;\n\n\treturn ts_hypertable_create_internal(fcinfo,\n\t\t\t\t\t\t\t\t\t\t table_relid,\n\t\t\t\t\t\t\t\t\t\t dim_info,\n\t\t\t\t\t\t\t\t\t\t NULL, /* closed_dim_info */\n\t\t\t\t\t\t\t\t\t\t NULL, /* associated_schema_name */\n\t\t\t\t\t\t\t\t\t\t NULL, /* associated_table_prefix */\n\t\t\t\t\t\t\t\t\t\t create_default_indexes,\n\t\t\t\t\t\t\t\t\t\t if_not_exists,\n\t\t\t\t\t\t\t\t\t\t migrate_data,\n\t\t\t\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t\t\t\t sizing_func,\n\t\t\t\t\t\t\t\t\t\t true);\n}\n\n/* Go through columns of parent table and check for column data types. */\nstatic void\nts_validate_basetable_columns(Relation *rel)\n{\n\tint attno;\n\tTupleDesc tupdesc;\n\n\ttupdesc = RelationGetDescr(*rel);\n\tfor (attno = 1; attno <= tupdesc->natts; attno++)\n\t{\n\t\tForm_pg_attribute attr = TupleDescAttr(tupdesc, attno - 1);\n\t\t/* skip dropped columns */\n\t\tif (attr->attisdropped)\n\t\t\tcontinue;\n\t\tOid typid = attr->atttypid;\n\t\tswitch (typid)\n\t\t{\n\t\t\tcase CHAROID:\n\t\t\tcase VARCHAROID:\n\t\t\t\tereport(WARNING,\n\t\t\t\t\t\t(errmsg(\"column type \\\"%s\\\" used for \\\"%s\\\" does not follow best practices\",\n\t\t\t\t\t\t\t\tformat_type_be(typid),\n\t\t\t\t\t\t\t\tNameStr(attr->attname)),\n\t\t\t\t\t\t errhint(\"Use datatype TEXT instead.\")));\n\t\t\t\tbreak;\n\t\t\tcase TIMESTAMPOID:\n\t\t\t\tereport(WARNING,\n\t\t\t\t\t\t(errmsg(\"column type \\\"%s\\\" used for \\\"%s\\\" does not follow best practices\",\n\t\t\t\t\t\t\t\tformat_type_be(typid),\n\t\t\t\t\t\t\t\tNameStr(attr->attname)),\n\t\t\t\t\t\t errhint(\"Use datatype TIMESTAMPTZ instead.\")));\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/* Creates a new hypertable.\n *\n * Flags are one of HypertableCreateFlags.\n * All parameters after tim_dim_info can be NUL\n * returns 'true' if new hypertable was created, false if 'if_not_exists' and the hypertable already\n * exists.\n */\nbool\nts_hypertable_create_from_info(Oid table_relid, int32 hypertable_id, uint32 flags,\n\t\t\t\t\t\t\t   DimensionInfo *time_dim_info, DimensionInfo *closed_dim_info,\n\t\t\t\t\t\t\t   Name associated_schema_name, Name associated_table_prefix,\n\t\t\t\t\t\t\t   ChunkSizingInfo *chunk_sizing_info)\n{\n\tCache *hcache;\n\tHypertable *ht;\n\tOid associated_schema_oid;\n\tOid user_oid = GetUserId();\n\tOid tspc_oid = get_rel_tablespace(table_relid);\n\tbool table_has_data;\n\tNameData schema_name, table_name, default_associated_schema_name;\n\tRelation rel;\n\tbool if_not_exists = (flags & HYPERTABLE_CREATE_IF_NOT_EXISTS) != 0;\n\n\t/* quick exit in the easy if-not-exists case to avoid all locking */\n\tif (if_not_exists && ts_is_hypertable(table_relid))\n\t{\n\t\tereport(NOTICE,\n\t\t\t\t(errcode(ERRCODE_TS_HYPERTABLE_EXISTS),\n\t\t\t\t errmsg(\"table \\\"%s\\\" is already a hypertable, skipping\",\n\t\t\t\t\t\tget_rel_name(table_relid))));\n\n\t\treturn false;\n\t}\n\n\t/*\n\t * Serialize hypertable creation to avoid having multiple transactions\n\t * creating the same hypertable simultaneously. The lock should conflict\n\t * with itself and RowExclusive, to prevent simultaneous inserts on the\n\t * table. Also since TRUNCATE (part of data migrations) takes an\n\t * AccessExclusiveLock take that lock level here too so that we don't have\n\t * lock upgrades, which are susceptible to deadlocks. If we aren't\n\t * migrating data, then shouldn't have much contention on the table thus\n\t * not worth optimizing.\n\t */\n\trel = table_open(table_relid, AccessExclusiveLock);\n\n\t/* recheck after getting lock */\n\tif (ts_is_hypertable(table_relid))\n\t{\n\t\t/*\n\t\t * Unlock and return. Note that unlocking is analogous to what PG does\n\t\t * for ALTER TABLE ADD COLUMN IF NOT EXIST\n\t\t */\n\t\ttable_close(rel, AccessExclusiveLock);\n\n\t\tif (if_not_exists)\n\t\t{\n\t\t\tereport(NOTICE,\n\t\t\t\t\t(errcode(ERRCODE_TS_HYPERTABLE_EXISTS),\n\t\t\t\t\t errmsg(\"table \\\"%s\\\" is already a hypertable, skipping\",\n\t\t\t\t\t\t\tget_rel_name(table_relid))));\n\t\t\treturn false;\n\t\t}\n\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_TS_HYPERTABLE_EXISTS),\n\t\t\t\t errmsg(\"table \\\"%s\\\" is already a hypertable\", get_rel_name(table_relid))));\n\t}\n\n\t/*\n\t * Hypertables also get created as part of caggs. Report warnings\n\t * only for hypertables created via call to create_hypertable().\n\t */\n\tif (hypertable_id == INVALID_HYPERTABLE_ID)\n\t\tts_validate_basetable_columns(&rel);\n\n\t/*\n\t * Check that the user has permissions to make this table into a\n\t * hypertable\n\t */\n\tts_hypertable_permissions_check(table_relid, user_oid);\n\n\t/* Is this the right kind of relation? */\n\tswitch (get_rel_relkind(table_relid))\n\t{\n\t\tcase RELKIND_PARTITIONED_TABLE:\n\t\t\tif (!ts_guc_enable_partitioned_hypertables)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_WRONG_OBJECT_TYPE),\n\t\t\t\t\t\t errmsg(\"table \\\"%s\\\" is already partitioned\", get_rel_name(table_relid)),\n\t\t\t\t\t\t errdetail(\n\t\t\t\t\t\t\t \"It is not possible to turn partitioned tables into hypertables.\")));\n\t\t\tbreak;\n\t\tcase RELKIND_MATVIEW:\n\t\tcase RELKIND_RELATION:\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg(\"invalid relation type\")));\n\t}\n\n\t/*\n\t * Check that the table is not part of any publication\n\t */\n\tif (GetRelationPublications(table_relid) != NIL || GetAllTablesPublications() != NIL)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_TS_OPERATION_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot create hypertable for table \\\"%s\\\" because it is part of a \"\n\t\t\t\t\t\t\"publication\",\n\t\t\t\t\t\tget_rel_name(table_relid))));\n\t}\n\n\t/* Check that the table doesn't have any unsupported constraints */\n\thypertable_validate_constraints(table_relid);\n\n\t/* No need to check for data in partitioned tables */\n\ttable_has_data = get_rel_relkind(table_relid) == RELKIND_PARTITIONED_TABLE ?\n\t\t\t\t\t\t false :\n\t\t\t\t\t\t ts_relation_has_tuples(rel);\n\n\tif ((flags & HYPERTABLE_CREATE_MIGRATE_DATA) == 0 && table_has_data)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"table \\\"%s\\\" is not empty\", get_rel_name(table_relid)),\n\t\t\t\t errhint(\"You can migrate data by specifying 'migrate_data => true' when calling \"\n\t\t\t\t\t\t \"this function.\")));\n\n\tif (is_inheritance_table(table_relid))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"table \\\"%s\\\" is already partitioned\", get_rel_name(table_relid)),\n\t\t\t\t errdetail(\n\t\t\t\t\t \"It is not possible to turn tables that use inheritance into hypertables.\")));\n\n\tif (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"table \\\"%s\\\" cannot be temporary\", get_rel_name(table_relid)),\n\t\t\t\t errdetail(\"It is not supported to turn temporary tables into hypertables.\")));\n\n\tif (table_has_rules(rel))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"hypertables do not support rules\"),\n\t\t\t\t errdetail(\"Table \\\"%s\\\" has attached rules, which do not work on hypertables.\",\n\t\t\t\t\t\t   get_rel_name(table_relid)),\n\t\t\t\t errhint(\"Remove the rules before creating a hypertable.\")));\n\n\t/*\n\t * Must close the relation to decrease the reference count for the relation\n\t * as PG18+ will check the reference count when adding constraints for the table.\n\t */\n\ttable_close(rel, NoLock);\n\t/*\n\t * Create the associated schema where chunks are stored, or, check\n\t * permissions if it already exists\n\t */\n\tif (NULL == associated_schema_name)\n\t{\n\t\tnamestrcpy(&default_associated_schema_name, INTERNAL_SCHEMA_NAME);\n\t\tassociated_schema_name = &default_associated_schema_name;\n\t}\n\n\tassociated_schema_oid =\n\t\thypertable_check_associated_schema_permissions(NameStr(*associated_schema_name), user_oid);\n\n\t/* Create the associated schema if it doesn't already exist */\n\tif (!OidIsValid(associated_schema_oid))\n\t\thypertable_create_schema(NameStr(*associated_schema_name));\n\n\t/*\n\t * Hypertables do not support arbitrary triggers, so if the table already\n\t * has unsupported triggers we bail out\n\t */\n\tts_check_unsupported_triggers(table_relid);\n\n\tif (NULL == chunk_sizing_info)\n\t\tchunk_sizing_info = ts_chunk_sizing_info_get_default_disabled(table_relid);\n\n\t/* Validate and set chunk sizing information */\n\tif (OidIsValid(chunk_sizing_info->func))\n\t{\n\t\tts_chunk_adaptive_sizing_info_validate(chunk_sizing_info);\n\n\t\tif (chunk_sizing_info->target_size_bytes > 0)\n\t\t{\n\t\t\tereport(NOTICE,\n\t\t\t\t\t(errcode(ERRCODE_WARNING),\n\t\t\t\t\t errmsg(\"adaptive chunking is a BETA feature and is not recommended for \"\n\t\t\t\t\t\t\t\"production deployments\")));\n\t\t\ttime_dim_info->adaptive_chunking = true;\n\t\t}\n\t}\n\telse\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"chunk sizing function cannot be NULL\")));\n\t}\n\n\t/* Validate that the dimensions are OK */\n\tts_dimension_info_validate(time_dim_info);\n\n\tif (DIMENSION_INFO_IS_SET(closed_dim_info))\n\t\tts_dimension_info_validate(closed_dim_info);\n\n\t/* Checks pass, now we can create the catalog information */\n\tnamestrcpy(&schema_name, get_namespace_name(get_rel_namespace(table_relid)));\n\tnamestrcpy(&table_name, get_rel_name(table_relid));\n\n\thypertable_insert(hypertable_id,\n\t\t\t\t\t  &schema_name,\n\t\t\t\t\t  &table_name,\n\t\t\t\t\t  associated_schema_name,\n\t\t\t\t\t  associated_table_prefix,\n\t\t\t\t\t  &chunk_sizing_info->func_schema,\n\t\t\t\t\t  &chunk_sizing_info->func_name,\n\t\t\t\t\t  chunk_sizing_info->target_size_bytes,\n\t\t\t\t\t  DIMENSION_INFO_IS_SET(closed_dim_info) ? 2 : 1,\n\t\t\t\t\t  false);\n\n\t/* Get the a Hypertable object via the cache */\n\ttime_dim_info->ht =\n\t\tts_hypertable_cache_get_cache_and_entry(table_relid, CACHE_FLAG_NONE, &hcache);\n\n\t/* Add validated dimensions */\n\tts_dimension_add_from_info(time_dim_info);\n\n\tif (DIMENSION_INFO_IS_SET(closed_dim_info))\n\t{\n\t\tclosed_dim_info->ht = time_dim_info->ht;\n\t\tts_dimension_add_from_info(closed_dim_info);\n\t}\n\n\t/* Refresh the cache to get the updated hypertable with added dimensions */\n\tts_cache_release(&hcache);\n\tht = ts_hypertable_cache_get_cache_and_entry(table_relid, CACHE_FLAG_NONE, &hcache);\n\n\t/* Verify that existing indexes are compatible with a hypertable */\n\tts_indexing_verify_indexes(ht);\n\n\t/* Attach tablespace, if any */\n\tif (OidIsValid(tspc_oid))\n\t{\n\t\tNameData tspc_name;\n\n\t\tnamestrcpy(&tspc_name, get_tablespace_name(tspc_oid));\n\t\tts_tablespace_attach_internal(&tspc_name, table_relid, false);\n\t}\n\n\tif ((flags & HYPERTABLE_CREATE_DISABLE_DEFAULT_INDEXES) == 0)\n\t\tts_indexing_create_default_indexes(ht);\n\n\t/*\n\t * Migrate data from the main table to chunks\n\t */\n\tif (table_has_data)\n\t{\n\t\tereport(NOTICE,\n\t\t\t\t(errmsg(\"migrating data to chunks\"),\n\t\t\t\t errdetail(\"Migration might take a while depending on the amount of data.\")));\n\n\t\ttimescaledb_move_from_table_to_chunks(ht, RowExclusiveLock);\n\t}\n\n\tts_cache_release(&hcache);\n\n\treturn true;\n}\n\n/* Used as a tuple found function */\nstatic ScanTupleResult\nhypertable_rename_schema_name(TupleInfo *ti, void *data)\n{\n\tconst char **schema_names = (const char **) data;\n\tconst char *old_schema_name = schema_names[0];\n\tconst char *new_schema_name = schema_names[1];\n\tbool updated = false;\n\tFormData_hypertable fd;\n\n\tts_hypertable_formdata_fill(&fd, ti);\n\n\t/*\n\t * Because we are doing a heap scan with no scankey, we don't know which\n\t * schema name to change, if any\n\t */\n\tif (namestrcmp(&fd.schema_name, old_schema_name) == 0)\n\t{\n\t\tnamestrcpy(&fd.schema_name, new_schema_name);\n\t\tupdated = true;\n\t}\n\tif (namestrcmp(&fd.associated_schema_name, old_schema_name) == 0)\n\t{\n\t\tnamestrcpy(&fd.associated_schema_name, new_schema_name);\n\t\tupdated = true;\n\t}\n\tif (namestrcmp(&fd.chunk_sizing_func_schema, old_schema_name) == 0)\n\t{\n\t\tnamestrcpy(&fd.chunk_sizing_func_schema, new_schema_name);\n\t\tupdated = true;\n\t}\n\n\t/* Only update the catalog if we explicitly something */\n\tif (updated)\n\t{\n\t\tHeapTuple new_tuple = hypertable_formdata_make_tuple(&fd, ts_scanner_get_tupledesc(ti));\n\t\tts_catalog_update_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti), new_tuple);\n\t\theap_freetuple(new_tuple);\n\t}\n\n\t/* Keep going so we can change the name for all hypertables */\n\treturn SCAN_CONTINUE;\n}\n\n/* Go through internal hypertable table and rename all matching schemas */\nvoid\nts_hypertables_rename_schema_name(const char *old_name, const char *new_name)\n{\n\tconst char *schema_names[2] = { old_name, new_name };\n\tCatalog *catalog = ts_catalog_get();\n\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, HYPERTABLE),\n\t\t.index = InvalidOid,\n\t\t.tuple_found = hypertable_rename_schema_name,\n\t\t.data = (void *) schema_names,\n\t\t.lockmode = RowExclusiveLock,\n\t\t.scandirection = ForwardScanDirection,\n\t};\n\n\tts_scanner_scan(&scanctx);\n}\n\nbool\nts_is_partitioning_column(const Hypertable *ht, AttrNumber column_attno)\n{\n\tuint16 i;\n\n\tfor (i = 0; i < ht->space->num_dimensions; i++)\n\t{\n\t\tif (column_attno == ht->space->dimensions[i].column_attno)\n\t\t\treturn true;\n\t}\n\treturn false;\n}\n\nbool\nts_is_partitioning_column_name(const Hypertable *ht, NameData column_name)\n{\n\tuint16 i;\n\n\tfor (i = 0; i < ht->space->num_dimensions; i++)\n\t{\n\t\tif (namestrcmp(&ht->space->dimensions[i].fd.column_name, NameStr(column_name)) == 0)\n\t\t\treturn true;\n\t}\n\treturn false;\n}\n\nstatic void\ninteger_now_func_validate(Oid now_func_oid, Oid open_dim_type)\n{\n\tHeapTuple tuple;\n\tForm_pg_proc now_func;\n\n\t/* this function should only be called for hypertables with an open integer time dimension */\n\tAssert(IS_INTEGER_TYPE(open_dim_type));\n\n\tif (!OidIsValid(now_func_oid))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_FUNCTION), (errmsg(\"invalid custom time function\"))));\n\n\ttuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(now_func_oid));\n\tif (!HeapTupleIsValid(tuple))\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_NO_DATA_FOUND),\n\t\t\t\t errmsg(\"cache lookup failed for function %u\", now_func_oid)));\n\t}\n\n\tnow_func = (Form_pg_proc) GETSTRUCT(tuple);\n\n\tif ((now_func->provolatile != PROVOLATILE_IMMUTABLE &&\n\t\t now_func->provolatile != PROVOLATILE_STABLE) ||\n\t\tnow_func->pronargs != 0)\n\t{\n\t\tReleaseSysCache(tuple);\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid custom time function\"),\n\t\t\t\t errhint(\"A custom time function must take no arguments and be STABLE.\")));\n\t}\n\n\tif (now_func->prorettype != open_dim_type)\n\t{\n\t\tReleaseSysCache(tuple);\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid custom time function\"),\n\t\t\t\t errhint(\"The return type of the custom time function must be the same as\"\n\t\t\t\t\t\t \" the type of the time column of the hypertable.\")));\n\t}\n\tReleaseSysCache(tuple);\n}\n\nTS_FUNCTION_INFO_V1(ts_hypertable_set_integer_now_func);\n\nDatum\nts_hypertable_set_integer_now_func(PG_FUNCTION_ARGS)\n{\n\tOid table_relid = PG_GETARG_OID(0);\n\tOid now_func_oid = PG_GETARG_OID(1);\n\tbool replace_if_exists = PG_GETARG_BOOL(2);\n\tHypertable *hypertable;\n\tCache *hcache;\n\tconst Dimension *open_dim;\n\tOid open_dim_type;\n\tAclResult aclresult;\n\n\tts_hypertable_permissions_check(table_relid, GetUserId());\n\thypertable = ts_hypertable_cache_get_cache_and_entry(table_relid, CACHE_FLAG_NONE, &hcache);\n\n\tif (TS_HYPERTABLE_IS_INTERNAL_COMPRESSION_TABLE(hypertable))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"custom time function not supported on internal columnstore table\")));\n\n\t/* validate that the open dimension uses numeric type */\n\topen_dim = hyperspace_get_open_dimension(hypertable->space, 0);\n\n\tif (!replace_if_exists)\n\t\tif (*NameStr(open_dim->fd.integer_now_func_schema) != '\\0' ||\n\t\t\t*NameStr(open_dim->fd.integer_now_func) != '\\0')\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_DUPLICATE_OBJECT),\n\t\t\t\t\t errmsg(\"custom time function already set for hypertable \\\"%s\\\"\",\n\t\t\t\t\t\t\tget_rel_name(table_relid))));\n\n\topen_dim_type = ts_dimension_get_partition_type(open_dim);\n\tif (!IS_INTEGER_TYPE(open_dim_type))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"custom time function not supported\"),\n\t\t\t\t errhint(\"A custom time function can only be set for hypertables\"\n\t\t\t\t\t\t \" that have integer time dimensions.\")));\n\n\tinteger_now_func_validate(now_func_oid, open_dim_type);\n\n\taclresult = object_aclcheck(ProcedureRelationId, now_func_oid, GetUserId(), ACL_EXECUTE);\n\tif (aclresult != ACLCHECK_OK)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),\n\t\t\t\t errmsg(\"permission denied for function %s\", get_func_name(now_func_oid))));\n\n\tts_dimension_update(hypertable,\n\t\t\t\t\t\t&open_dim->fd.column_name,\n\t\t\t\t\t\tDIMENSION_TYPE_OPEN,\n\t\t\t\t\t\tNULL,\n\t\t\t\t\t\tNULL,\n\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t&now_func_oid);\n\tts_cache_release(&hcache);\n\tPG_RETURN_NULL();\n}\n\n/*Assume permissions are already checked\n * set compression state as enabled\n */\nbool\nts_hypertable_set_compressed(Hypertable *ht, int32 compressed_hypertable_id)\n{\n\tFormData_hypertable form;\n\tItemPointerData tid;\n\t/* lock the tuple entry in the catalog table */\n\tbool found = lock_hypertable_tuple(ht->fd.id, &tid, &form);\n\tEnsure(found, \"hypertable id %d not found\", ht->fd.id);\n\n\tAssert(!TS_HYPERTABLE_IS_INTERNAL_COMPRESSION_TABLE(ht));\n\tform.compression_state = HypertableCompressionEnabled;\n\tform.compressed_hypertable_id = compressed_hypertable_id;\n\thypertable_update_catalog_tuple(&tid, &form);\n\treturn true;\n}\n\n/* set compression_state as disabled and remove any\n * associated compressed hypertable id\n */\nbool\nts_hypertable_unset_compressed(Hypertable *ht)\n{\n\tFormData_hypertable form;\n\tItemPointerData tid;\n\t/* lock the tuple entry in the catalog table */\n\tbool found = lock_hypertable_tuple(ht->fd.id, &tid, &form);\n\tEnsure(found, \"hypertable id %d not found\", ht->fd.id);\n\n\tAssert(!TS_HYPERTABLE_IS_INTERNAL_COMPRESSION_TABLE(ht));\n\tform.compression_state = HypertableCompressionOff;\n\tform.compressed_hypertable_id = INVALID_HYPERTABLE_ID;\n\thypertable_update_catalog_tuple(&tid, &form);\n\treturn true;\n}\n\nbool\nts_hypertable_set_compress_interval(Hypertable *ht, int64 compress_interval)\n{\n\tAssert(!TS_HYPERTABLE_IS_INTERNAL_COMPRESSION_TABLE(ht));\n\n\tDimension *time_dimension =\n\t\tts_hyperspace_get_mutable_dimension(ht->space, DIMENSION_TYPE_OPEN, 0);\n\n\treturn ts_dimension_set_compress_interval(time_dimension, compress_interval) > 0;\n}\n\n/* create a compressed hypertable\n * table_relid - already created table which we are going to\n *               set up as a compressed hypertable\n * hypertable_id - id to be used while creating hypertable with\n *                  compression property set\n * NOTE:\n * compressed hypertable has no dimensions.\n */\nbool\nts_hypertable_create_compressed(Oid table_relid, int32 hypertable_id)\n{\n\tOid user_oid = GetUserId();\n\tOid tspc_oid = get_rel_tablespace(table_relid);\n\tNameData schema_name, table_name, associated_schema_name;\n\tChunkSizingInfo *chunk_sizing_info;\n\tLockRelationOid(table_relid, AccessExclusiveLock);\n\t/*\n\t * Check that the user has permissions to make this table to a compressed\n\t * hypertable\n\t */\n\tts_hypertable_permissions_check(table_relid, user_oid);\n\tif (ts_is_hypertable(table_relid))\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_TS_HYPERTABLE_EXISTS),\n\t\t\t\t errmsg(\"table \\\"%s\\\" is already a hypertable\", get_rel_name(table_relid))));\n\t}\n\n\tnamestrcpy(&schema_name, get_namespace_name(get_rel_namespace(table_relid)));\n\tnamestrcpy(&table_name, get_rel_name(table_relid));\n\n\t/* we don't use the chunking size info for managing the compressed table.\n\t * But need this to satisfy hypertable constraints\n\t */\n\tchunk_sizing_info = ts_chunk_sizing_info_get_default_disabled(table_relid);\n\tts_chunk_sizing_func_validate(chunk_sizing_info->func, chunk_sizing_info);\n\n\t/* Checks pass, now we can create the catalog information */\n\tnamestrcpy(&schema_name, get_namespace_name(get_rel_namespace(table_relid)));\n\tnamestrcpy(&table_name, get_rel_name(table_relid));\n\tnamestrcpy(&associated_schema_name, INTERNAL_SCHEMA_NAME);\n\n\t/* compressed hypertable has no dimensions of its own , shares the original hypertable dims*/\n\thypertable_insert(hypertable_id,\n\t\t\t\t\t  &schema_name,\n\t\t\t\t\t  &table_name,\n\t\t\t\t\t  &associated_schema_name,\n\t\t\t\t\t  NULL,\n\t\t\t\t\t  &chunk_sizing_info->func_schema,\n\t\t\t\t\t  &chunk_sizing_info->func_name,\n\t\t\t\t\t  chunk_sizing_info->target_size_bytes,\n\t\t\t\t\t  0 /*num_dimensions*/,\n\t\t\t\t\t  true);\n\n\t/* No indexes are created for the compressed hypertable here */\n\n\t/* Attach tablespace, if any */\n\tif (OidIsValid(tspc_oid))\n\t{\n\t\tNameData tspc_name;\n\n\t\tnamestrcpy(&tspc_name, get_tablespace_name(tspc_oid));\n\t\tts_tablespace_attach_internal(&tspc_name, table_relid, false);\n\t}\n\n\treturn true;\n}\n\n/*\n * Construct an expression for a dimensional column which is compatible with the max() function.\n * Normally, this is just the column name, but in the case of UUIDv7 there is no max() function\n * defined for the type so in that case the expression extracts the timestamp from the UUID.\n */\nstatic const char *\nget_expr_for_dim_max(const char *colname, Oid timetype)\n{\n\tif (timetype == UUIDOID)\n\t{\n\t\tStringInfoData expr;\n\n\t\tinitStringInfo(&expr);\n\t\tappendStringInfo(&expr,\n\t\t\t\t\t\t \"%s.uuid_timestamp(%s)\",\n\t\t\t\t\t\t ts_extension_schema_name(),\n\t\t\t\t\t\t quote_identifier(colname));\n\t\treturn expr.data;\n\t}\n\n\treturn quote_identifier(colname);\n}\n\n/*\n * Get the max value of an open dimension.\n */\nint64\nts_hypertable_get_open_dim_max_value(const Hypertable *ht, int dimension_index, bool *isnull)\n{\n\tStringInfoData command;\n\tconst Dimension *dim;\n\tint res;\n\tbool max_isnull;\n\tDatum maxdat;\n\tOid timetype;\n\n\tdim = hyperspace_get_open_dimension(ht->space, dimension_index);\n\n\tif (NULL == dim)\n\t\telog(ERROR, \"invalid open dimension index %d\", dimension_index);\n\n\ttimetype = ts_dimension_get_partition_type(dim);\n\n\t/*\n\t * Query for the last bucket in the materialized hypertable.\n\t * Since this might be run as part of a parallel operation\n\t * we cannot use SET search_path here to lock down the\n\t * search_path and instead have to fully schema-qualify\n\t * everything.\n\t */\n\tinitStringInfo(&command);\n\tappendStringInfo(&command,\n\t\t\t\t\t \"SELECT pg_catalog.max(%s) FROM %s.%s\",\n\t\t\t\t\t get_expr_for_dim_max(NameStr(dim->fd.column_name), timetype),\n\t\t\t\t\t quote_identifier(NameStr(ht->fd.schema_name)),\n\t\t\t\t\t quote_identifier(NameStr(ht->fd.table_name)));\n\n\tif (SPI_connect() != SPI_OK_CONNECT)\n\t\telog(ERROR, \"could not connect to SPI\");\n\n\tres = SPI_execute(command.data, true /* read_only */, 0 /*count*/);\n\n\tif (res < 0)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t (errmsg(\"could not find the maximum time value for hypertable \\\"%s\\\"\",\n\t\t\t\t\t\t get_rel_name(ht->main_table_relid)))));\n\n\t/* In most cases the result type is the same as the time type. However, with UUIDs we first\n\t * extract the timestamptz so the result type is timestamptz instead. */\n\tOid result_type = timetype == UUIDOID ? TIMESTAMPTZOID : timetype;\n\n\tEnsure(SPI_gettypeid(SPI_tuptable->tupdesc, 1) == result_type,\n\t\t   \"partition types for result (%d) and dimension (%d) do not match\",\n\t\t   SPI_gettypeid(SPI_tuptable->tupdesc, 1),\n\t\t   ts_dimension_get_partition_type(dim));\n\tmaxdat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &max_isnull);\n\n\tif (isnull)\n\t\t*isnull = max_isnull;\n\n\tint64 max_value =\n\t\tmax_isnull ? ts_time_get_min(result_type) : ts_time_value_to_internal(maxdat, result_type);\n\n\tres = SPI_finish();\n\tif (res != SPI_OK_FINISH)\n\t\telog(ERROR, \"SPI_finish failed: %s\", SPI_result_code_string(res));\n\n\treturn max_value;\n}\n\nbool\nts_hypertable_has_compression_table(const Hypertable *ht)\n{\n\tif (ht->fd.compressed_hypertable_id != INVALID_HYPERTABLE_ID)\n\t{\n\t\tAssert(ht->fd.compression_state == HypertableCompressionEnabled);\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n/*\n * hypertable status update is done in two steps, similar to\n * chunk_update_status\n * This is again equivalent to a SELECT FOR UPDATE, followed by UPDATE\n * 1. RowShareLock to SELECT for UPDATE\n * 2. UPDATE status using RowExclusiveLock\n */\nbool\nts_hypertable_update_status_osm(Hypertable *ht)\n{\n\tFormData_hypertable form;\n\tItemPointerData tid;\n\t/* lock the tuple entry in the catalog table */\n\tbool found = lock_hypertable_tuple(ht->fd.id, &tid, &form);\n\tEnsure(found, \"hypertable id %d not found\", ht->fd.id);\n\n\tif (form.status != ht->fd.status)\n\t{\n\t\tform.status = ht->fd.status;\n\t\thypertable_update_catalog_tuple(&tid, &form);\n\t}\n\treturn true;\n}\n\nbool\nts_hypertable_update_chunk_sizing(Hypertable *ht)\n{\n\tFormData_hypertable form;\n\tItemPointerData tid;\n\t/* lock the tuple entry in the catalog table */\n\tbool found = lock_hypertable_tuple(ht->fd.id, &tid, &form);\n\tEnsure(found, \"hypertable id %d not found\", ht->fd.id);\n\n\tif (OidIsValid(ht->chunk_sizing_func))\n\t{\n\t\tconst Dimension *dim = ts_hyperspace_get_dimension(ht->space, DIMENSION_TYPE_OPEN, 0);\n\t\tChunkSizingInfo info = {\n\t\t\t.table_relid = ht->main_table_relid,\n\t\t\t.colname = dim == NULL ? NULL : NameStr(dim->fd.column_name),\n\t\t\t.func = ht->chunk_sizing_func,\n\t\t};\n\n\t\tts_chunk_adaptive_sizing_info_validate(&info);\n\n\t\tnamestrcpy(&form.chunk_sizing_func_schema, NameStr(info.func_schema));\n\t\tnamestrcpy(&form.chunk_sizing_func_name, NameStr(info.func_name));\n\t}\n\telse\n\t\telog(ERROR, \"chunk sizing function cannot be NULL\");\n\tform.chunk_target_size = ht->fd.chunk_target_size;\n\thypertable_update_catalog_tuple(&tid, &form);\n\treturn true;\n}\n\nDimensionSlice *\nts_chunk_get_osm_slice_and_lock(int32 osm_chunk_id, int32 time_dim_id, LockTupleMode tuplockmode,\n\t\t\t\t\t\t\t\tLOCKMODE tablelockmode)\n{\n\tChunkConstraints *constraints =\n\t\tts_chunk_constraint_scan_by_chunk_id(osm_chunk_id, 1, CurrentMemoryContext);\n\n\tfor (int i = 0; i < constraints->num_constraints; i++)\n\t{\n\t\tChunkConstraint *cc = chunk_constraints_get(constraints, i);\n\t\tif (is_dimension_constraint(cc))\n\t\t{\n\t\t\tScanTupLock tuplock = {\n\t\t\t\t.lockmode = tuplockmode,\n\t\t\t\t.waitpolicy = LockWaitBlock,\n\t\t\t};\n\t\t\t/*\n\t\t\t * We cannot acquire a tuple lock when running in recovery mode\n\t\t\t * since that prevents scans on tiered hypertables from running\n\t\t\t * on a read-only secondary. Acquiring a tuple lock requires\n\t\t\t * assigning a transaction id for the current transaction state\n\t\t\t * which is not possible in recovery mode. So we only acquire the\n\t\t\t * lock if we are not in recovery mode.\n\t\t\t */\n\t\t\tScanTupLock *const tuplock_ptr = RecoveryInProgress() ? NULL : &tuplock;\n\n\t\t\tif (!IsolationUsesXactSnapshot())\n\t\t\t{\n\t\t\t\t/* in read committed mode, we follow all updates to this tuple */\n\t\t\t\ttuplock.lockflags |= TUPLE_LOCK_FLAG_FIND_LAST_VERSION;\n\t\t\t}\n\n\t\t\tDimensionSlice *dimslice =\n\t\t\t\tts_dimension_slice_scan_by_id_and_lock(cc->fd.dimension_slice_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   tuplock_ptr,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   CurrentMemoryContext,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   tablelockmode);\n\t\t\tif (dimslice->fd.dimension_id == time_dim_id)\n\t\t\t\treturn dimslice;\n\t\t}\n\t}\n\treturn NULL;\n}\n/*\n * hypertable_osm_range_update\n * 0 hypertable REGCLASS,\n * 1 range_start=NULL::bigint,\n * 2 range_end=NULL,\n * 3 empty=false\n * If empty is set to true then the range will be set to invalid range\n * but the overlap flag will be unset, indicating that no data is managed\n * by OSM and therefore timescaledb optimizations can be applied.\n */\nTS_FUNCTION_INFO_V1(ts_hypertable_osm_range_update);\nDatum\nts_hypertable_osm_range_update(PG_FUNCTION_ARGS)\n{\n\tOid relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\tHypertable *ht;\n\tconst Dimension *time_dim;\n\tCache *hcache;\n\n\tOid time_type; /* required for resolving the argument types, should match the hypertable\n\t\t\t\t\t  partitioning column type */\n\n\t/*\n\t * This function is not meant to be run on a read-only secondary. It is\n\t * only used by OSM to update chunk's range in timescaledb catalog when\n\t * tiering configuration changes (a new chunk is created, a chunk drop\n\t * etc); OSM would already be holding a lock on a dimension slice tuple\n\t * by this moment (which is not possible on read-only instance).\n\t * Technically this function can be executed from SQL (e.g. from psql) when\n\t * in recovery mode; in that instance an ERROR would be thrown when trying\n\t * to update the dimension slice tuple, no harm will be done.\n\t */\n\tAssert(!RecoveryInProgress());\n\n\thcache = ts_hypertable_cache_pin();\n\tht = ts_resolve_hypertable_from_table_or_cagg(hcache, relid, true);\n\tAssert(ht != NULL);\n\ttime_dim = hyperspace_get_open_dimension(ht->space, 0);\n\n\tif (time_dim == NULL)\n\t\tereport(ERROR,\n\t\t\t\terrcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\terrmsg(\"could not find time dimension for hypertable %s.%s\",\n\t\t\t\t\t   quote_identifier(NameStr(ht->fd.schema_name)),\n\t\t\t\t\t   quote_identifier(NameStr(ht->fd.table_name))));\n\n\ttime_type = ts_dimension_get_partition_type(time_dim);\n\n\tint32 osm_chunk_id = ts_chunk_get_osm_chunk_id(ht->fd.id);\n\tif (osm_chunk_id == INVALID_CHUNK_ID)\n\t\tereport(ERROR,\n\t\t\t\terrcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\terrmsg(\"no OSM chunk found for hypertable %s.%s\",\n\t\t\t\t\t   quote_identifier(NameStr(ht->fd.schema_name)),\n\t\t\t\t\t   quote_identifier(NameStr(ht->fd.table_name))));\n\t/*\n\t * range_start, range_end arguments must be converted to internal representation\n\t * a NULL start value is interpreted as INT64_MAX - 1 and a NULL end value is\n\t * interpreted as INT64_MAX.\n\t * Passing both start and end NULL values will reset the range to the default range an\n\t * OSM chunk is given upon creation, which is [INT64_MAX - 1, INT64_MAX]\n\t */\n\tif ((PG_ARGISNULL(1) && !PG_ARGISNULL(2)) || (!PG_ARGISNULL(1) && PG_ARGISNULL(2)))\n\t\tereport(ERROR,\n\t\t\t\terrcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\terrmsg(\"range_start and range_end parameters must be both NULL or both non-NULL\"));\n\n\tOid argtypes[2];\n\tfor (int i = 0; i < 2; i++)\n\t{\n\t\targtypes[i] = get_fn_expr_argtype(fcinfo->flinfo, i + 1);\n\t\tif (!can_coerce_type(1, &argtypes[i], &time_type, COERCION_IMPLICIT) &&\n\t\t\t!PG_ARGISNULL(i + 1))\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"invalid time argument type \\\"%s\\\"\", format_type_be(argtypes[i])),\n\t\t\t\t\t errhint(\"Try casting the argument to \\\"%s\\\".\", format_type_be(time_type))));\n\t}\n\n\tint64 range_start_internal, range_end_internal;\n\tif (PG_ARGISNULL(1))\n\t\trange_start_internal = PG_INT64_MAX - 1;\n\telse\n\t\trange_start_internal =\n\t\t\tts_time_value_to_internal(PG_GETARG_DATUM(1), get_fn_expr_argtype(fcinfo->flinfo, 1));\n\tif (PG_ARGISNULL(2))\n\t\trange_end_internal = PG_INT64_MAX;\n\telse\n\t\trange_end_internal =\n\t\t\tts_time_value_to_internal(PG_GETARG_DATUM(2), get_fn_expr_argtype(fcinfo->flinfo, 2));\n\n\tif (range_start_internal > range_end_internal)\n\t\tereport(ERROR,\n\t\t\t\terrcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\terrmsg(\"dimension slice range_end cannot be less than range_start\"));\n\n\tbool osm_chunk_empty = PG_GETARG_BOOL(3);\n\n\tbool overlap = false, range_invalid = false;\n\n\t/* Lock tuple FOR UPDATE */\n\tDimensionSlice *slice = ts_chunk_get_osm_slice_and_lock(osm_chunk_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttime_dim->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tLockTupleExclusive,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tRowShareLock);\n\n\tif (!slice)\n\t\tereport(ERROR, errmsg(\"could not find time dimension slice for chunk %d\", osm_chunk_id));\n\n\tint32 dimension_slice_id = slice->fd.id;\n\toverlap = ts_osm_chunk_range_overlaps(dimension_slice_id,\n\t\t\t\t\t\t\t\t\t\t  slice->fd.dimension_id,\n\t\t\t\t\t\t\t\t\t\t  range_start_internal,\n\t\t\t\t\t\t\t\t\t\t  range_end_internal);\n\t/*\n\t * It should not be possible for OSM chunks to overlap with the range\n\t * managed by timescaledb. OSM extension should update the range of the\n\t * OSM chunk to [INT64_MAX -1, infinity) when it detects that it is\n\t * noncontiguous, so we should not end up detecting overlaps anyway.\n\t * But throw an error in case we encounter this situation.\n\t */\n\tif (overlap)\n\t\tereport(ERROR,\n\t\t\t\terrcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\terrmsg(\"attempting to set overlapping range for tiered chunk of %s.%s\",\n\t\t\t\t\t   NameStr(ht->fd.schema_name),\n\t\t\t\t\t   NameStr(ht->fd.table_name)),\n\t\t\t\terrhint(\"Range should be set to invalid for tiered chunk\"));\n\trange_invalid = ts_osm_chunk_range_is_invalid(range_start_internal, range_end_internal);\n\t/* Update the hypertable flags regarding the validity of the OSM range */\n\tif (range_invalid)\n\t{\n\t\t/* range is set to infinity so the OSM chunk is considered last */\n\t\trange_start_internal = PG_INT64_MAX - 1;\n\t\trange_end_internal = PG_INT64_MAX;\n\t\tif (!osm_chunk_empty)\n\t\t\tht->fd.status =\n\t\t\t\tts_set_flags_32(ht->fd.status, HYPERTABLE_STATUS_OSM_CHUNK_NONCONTIGUOUS);\n\t\telse\n\t\t\tht->fd.status =\n\t\t\t\tts_clear_flags_32(ht->fd.status, HYPERTABLE_STATUS_OSM_CHUNK_NONCONTIGUOUS);\n\t}\n\telse\n\t\tht->fd.status = ts_clear_flags_32(ht->fd.status, HYPERTABLE_STATUS_OSM_CHUNK_NONCONTIGUOUS);\n\n\tts_hypertable_update_status_osm(ht);\n\tts_cache_release(&hcache);\n\n\tslice->fd.range_start = range_start_internal;\n\tslice->fd.range_end = range_end_internal;\n\tts_dimension_slice_range_update(slice);\n\n\tPG_RETURN_BOOL(overlap);\n}\n\nTSDLLEXPORT bool\nts_hypertable_has_continuous_aggregates(int32 hypertable_id)\n{\n\tbool found = false;\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(CONTINUOUS_AGG, AccessShareLock, CurrentMemoryContext);\n\n\titerator.ctx.limit = 1; /* we only need to know if there is at least one */\n\titerator.ctx.index =\n\t\tcatalog_get_index(ts_catalog_get(), CONTINUOUS_AGG, CONTINUOUS_AGG_RAW_HYPERTABLE_ID_IDX);\n\tts_scan_iterator_scan_key_init(&iterator,\n\t\t\t\t\t\t\t\t   Anum_continuous_agg_raw_hypertable_id_idx_raw_hypertable_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(hypertable_id));\n\n\tts_scan_iterator_start_scan(&iterator);\n\tif (ts_scan_iterator_next(&iterator))\n\t\tfound = true;\n\tts_scan_iterator_close(&iterator);\n\n\treturn found;\n}\n"
  },
  {
    "path": "src/hypertable.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <nodes/primnodes.h>\n#include <utils/array.h>\n\n#include \"chunk_adaptive.h\"\n#include \"dimension.h\"\n#include \"export.h\"\n#include \"hypertable_cache.h\"\n#include \"scan_iterator.h\"\n#include \"scanner.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"ts_catalog/tablespace.h\"\n\n#define INVALID_HYPERTABLE_ID 0\n\ntypedef struct SubspaceStore SubspaceStore;\ntypedef struct Chunk Chunk;\ntypedef struct Hypercube Hypercube;\ntypedef struct ChunkRangeSpace ChunkRangeSpace;\n\nenum\n{\n\tHypertableCompressionOff = 0,\n\tHypertableCompressionEnabled = 1,\n\tHypertableInternalCompressionTable = 2,\n};\n\n#define TS_HYPERTABLE_HAS_COMPRESSION_TABLE(ht) ts_hypertable_has_compression_table(ht)\n\n#define TS_HYPERTABLE_HAS_COMPRESSION_ENABLED(ht)                                                  \\\n\t((ht)->fd.compression_state == HypertableCompressionEnabled)\n\n#define TS_HYPERTABLE_IS_INTERNAL_COMPRESSION_TABLE(ht)                                            \\\n\t((ht)->fd.compression_state == HypertableInternalCompressionTable)\ntypedef struct Hypertable\n{\n\tFormData_hypertable fd;\n\tOid main_table_relid;\n\tOid chunk_sizing_func;\n\tHyperspace *space;\n\tSubspaceStore *chunk_cache;\n\tChunkRangeSpace *range_space;\n} Hypertable;\n\n/* create_hypertable record attribute numbers */\nenum Anum_create_hypertable\n{\n\tAnum_create_hypertable_id = 1,\n\tAnum_create_hypertable_schema_name,\n\tAnum_create_hypertable_table_name,\n\tAnum_create_hypertable_created,\n\t_Anum_create_hypertable_max,\n};\n\n#define Natts_create_hypertable (_Anum_create_hypertable_max - 1)\n\n/* Create a generic hypertable */\nenum Anum_generic_create_hypertable\n{\n\tAnum_generic_create_hypertable_id = 1,\n\tAnum_generic_create_hypertable_created,\n\t_Anum_generic_create_hypertable_max,\n};\n\n#define Natts_generic_create_hypertable (_Anum_generic_create_hypertable_max - 1)\n\nextern TSDLLEXPORT Oid ts_rel_get_owner(Oid relid);\n\ntypedef enum HypertableCreateFlags\n{\n\tHYPERTABLE_CREATE_DISABLE_DEFAULT_INDEXES = 1 << 0,\n\tHYPERTABLE_CREATE_IF_NOT_EXISTS = 1 << 1,\n\tHYPERTABLE_CREATE_MIGRATE_DATA = 1 << 2,\n} HypertableCreateFlags;\n\nextern TSDLLEXPORT bool ts_hypertable_create_from_info(Oid table_relid, int32 hypertable_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   uint32 flags, DimensionInfo *time_dim_info,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   DimensionInfo *closed_dim_info,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   Name associated_schema_name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   Name associated_table_prefix,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   ChunkSizingInfo *chunk_sizing_info);\nextern TSDLLEXPORT bool ts_hypertable_create_compressed(Oid table_relid, int32 hypertable_id);\n\nextern TSDLLEXPORT Hypertable *ts_hypertable_get_by_id(int32 hypertable_id);\nextern Hypertable *ts_hypertable_get_by_name(const char *schema, const char *name);\nextern TSDLLEXPORT bool ts_hypertable_get_attributes_by_name(const char *schema, const char *name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t FormData_hypertable *form);\nextern TSDLLEXPORT bool ts_hypertable_has_privs_of(Oid hypertable_oid, Oid userid);\nextern TSDLLEXPORT Oid ts_hypertable_permissions_check(Oid hypertable_oid, Oid userid);\n\nextern TSDLLEXPORT void ts_hypertable_permissions_check_by_id(int32 hypertable_id);\nextern Hypertable *ts_hypertable_from_tupleinfo(const TupleInfo *ti);\nextern Hypertable *ts_resolve_hypertable_from_table_or_cagg(Cache *hcache, Oid relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbool allow_matht);\nextern int ts_hypertable_scan_with_memory_context(const char *schema, const char *table,\n\t\t\t\t\t\t\t\t\t\t\t\t  tuple_found_func tuple_found, void *data,\n\t\t\t\t\t\t\t\t\t\t\t\t  LOCKMODE lockmode, MemoryContext mctx);\nextern bool ts_hypertable_update_status_osm(Hypertable *ht);\nextern bool ts_hypertable_update_chunk_sizing(Hypertable *ht);\nextern int ts_hypertable_set_name(Hypertable *ht, const char *newname);\nextern int ts_hypertable_set_schema(Hypertable *ht, const char *newname);\nextern int ts_hypertable_set_num_dimensions(Hypertable *ht, int16 num_dimensions);\nextern int ts_hypertable_delete_by_name(const char *schema_name, const char *table_name);\nextern int ts_hypertable_delete_by_id(int32 hypertable_id);\nextern TSDLLEXPORT ObjectAddress ts_hypertable_create_trigger(const Hypertable *ht,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  CreateTrigStmt *stmt,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  const char *query);\nextern TSDLLEXPORT void ts_hypertable_drop_invalidation_replication_slot(const char *slot_name);\nextern TSDLLEXPORT void ts_hypertable_drop_trigger(Oid relid, const char *trigger_name);\nextern TSDLLEXPORT void ts_hypertable_drop(Hypertable *hypertable, DropBehavior behavior);\n\nextern int ts_hypertable_reset_associated_schema_name(const char *associated_schema);\nextern TSDLLEXPORT Oid ts_hypertable_id_to_relid(int32 hypertable_id, bool return_invalid);\nextern TSDLLEXPORT int32 ts_hypertable_relid_to_id(Oid relid);\nextern TSDLLEXPORT Chunk *ts_hypertable_find_chunk_for_point(const Hypertable *h,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t const Point *point, LOCKMODE lockmode);\nextern TSDLLEXPORT Chunk *ts_hypertable_chunk_store_add(const Hypertable *h,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst Chunk *input_chunk);\nextern TSDLLEXPORT Chunk *ts_hypertable_create_chunk_for_point(const Hypertable *h,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   const Point *point,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   LOCKMODE chunk_lockmode);\nextern Oid ts_hypertable_relid(RangeVar *rv);\nextern TSDLLEXPORT bool ts_is_hypertable(Oid relid);\nextern bool ts_hypertable_has_tablespace(const Hypertable *ht, Oid tspc_oid);\nextern Tablespace *ts_hypertable_select_tablespace(const Hypertable *ht, const Chunk *chunk);\nextern const char *ts_hypertable_select_tablespace_name(const Hypertable *ht, const Chunk *chunk);\nextern Tablespace *ts_hypertable_get_tablespace_at_offset_from(int32 hypertable_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   Oid tablespace_oid, int16 offset);\nextern TSDLLEXPORT bool ts_hypertable_has_chunks(Oid table_relid, LOCKMODE lockmode);\nextern void ts_hypertables_rename_schema_name(const char *old_name, const char *new_name);\nextern bool ts_is_partitioning_column(const Hypertable *ht, AttrNumber column_attno);\nextern bool ts_is_partitioning_column_name(const Hypertable *ht, NameData column_name);\nextern TSDLLEXPORT bool ts_hypertable_set_compressed(Hypertable *ht,\n\t\t\t\t\t\t\t\t\t\t\t\t\t int32 compressed_hypertable_id);\nextern TSDLLEXPORT bool ts_hypertable_unset_compressed(Hypertable *ht);\nextern TSDLLEXPORT bool ts_hypertable_set_compress_interval(Hypertable *ht,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tint64 compress_interval);\nextern TSDLLEXPORT int64 ts_hypertable_get_open_dim_max_value(const Hypertable *ht,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  int dimension_index, bool *isnull);\n\nextern TSDLLEXPORT bool ts_hypertable_has_compression_table(const Hypertable *ht);\nextern TSDLLEXPORT bool ts_hypertable_has_continuous_aggregates(int32 hypertable_id);\nextern TSDLLEXPORT void ts_hypertable_formdata_fill(FormData_hypertable *fd, const TupleInfo *ti);\n\n#define hypertable_scan(schema, table, tuple_found, data, lockmode)                                \\\n\tts_hypertable_scan_with_memory_context(schema,                                                 \\\n\t\t\t\t\t\t\t\t\t\t   table,                                                  \\\n\t\t\t\t\t\t\t\t\t\t   tuple_found,                                            \\\n\t\t\t\t\t\t\t\t\t\t   data,                                                   \\\n\t\t\t\t\t\t\t\t\t\t   lockmode,                                               \\\n\t\t\t\t\t\t\t\t\t\t   CurrentMemoryContext)\n\n#define hypertable_adaptive_chunking_enabled(ht)                                                   \\\n\t(OidIsValid((ht)->chunk_sizing_func) && (ht)->fd.chunk_target_size > 0)\n"
  },
  {
    "path": "src/hypertable_cache.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <catalog/namespace.h>\n#include <utils/builtins.h>\n#include <utils/catcache.h>\n#include <utils/lsyscache.h>\n\n#include \"cache.h\"\n#include \"dimension.h\"\n#include \"errors.h\"\n#include \"hypertable.h\"\n#include \"hypertable_cache.h\"\n#include \"scanner.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"ts_catalog/tablespace.h\"\n\nstatic void *hypertable_cache_create_entry(Cache *cache, CacheQuery *query);\nstatic void hypertable_cache_missing_error(const Cache *cache, const CacheQuery *query);\n\ntypedef struct HypertableCacheQuery\n{\n\tCacheQuery q;\n\tOid relid;\n\tconst char *schema;\n\tconst char *table;\n} HypertableCacheQuery;\n\nstatic void *\nhypertable_cache_get_key(CacheQuery *query)\n{\n\treturn &((HypertableCacheQuery *) query)->relid;\n}\n\ntypedef struct\n{\n\tOid relid;\n\tHypertable *hypertable;\n} HypertableCacheEntry;\n\nstatic bool\nhypertable_cache_valid_result(const void *result)\n{\n\tif (result == NULL)\n\t\treturn false;\n\treturn ((HypertableCacheEntry *) result)->hypertable != NULL;\n}\n\nstatic Cache *\nhypertable_cache_create()\n{\n\tMemoryContext ctx =\n\t\tAllocSetContextCreate(CacheMemoryContext, \"Hypertable cache\", ALLOCSET_DEFAULT_SIZES);\n\n\tCache *cache = MemoryContextAlloc(ctx, sizeof(Cache));\n\tCache\t\ttemplate =\n\t{\n\t\t.hctl =\n\t\t{\n\t\t\t.keysize = sizeof(Oid),\n\t\t\t.entrysize = sizeof(HypertableCacheEntry),\n\t\t\t.hcxt = ctx,\n\t\t},\n\t\t.name = \"hypertable_cache\",\n\t\t.numelements = 16,\n\t\t.flags = HASH_ELEM | HASH_CONTEXT | HASH_BLOBS,\n\t\t.get_key = hypertable_cache_get_key,\n\t\t.create_entry = hypertable_cache_create_entry,\n\t\t.missing_error = hypertable_cache_missing_error,\n\t\t.valid_result = hypertable_cache_valid_result,\n\t};\n\n\t*cache = template;\n\n\tts_cache_init(cache);\n\n\treturn cache;\n}\n\nstatic Cache *hypertable_cache_current = NULL;\n\nstatic ScanTupleResult\nhypertable_tuple_found(TupleInfo *ti, void *data)\n{\n\tHypertableCacheEntry *entry = data;\n\n\tentry->hypertable = ts_hypertable_from_tupleinfo(ti);\n\treturn SCAN_DONE;\n}\n\nstatic void *\nhypertable_cache_create_entry(Cache *cache, CacheQuery *query)\n{\n\tHypertableCacheQuery *hq = (HypertableCacheQuery *) query;\n\tHypertableCacheEntry *cache_entry = query->result;\n\tint number_found;\n\n\tif (NULL == hq->schema)\n\t\thq->schema = get_namespace_name(get_rel_namespace(hq->relid));\n\n\tif (NULL == hq->table)\n\t\thq->table = get_rel_name(hq->relid);\n\n\tnumber_found = ts_hypertable_scan_with_memory_context(hq->schema,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  hq->table,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  hypertable_tuple_found,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  query->result,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  AccessShareLock,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  ts_cache_memory_ctx(cache));\n\n\tswitch (number_found)\n\t{\n\t\tcase 0:\n\t\t\t/* Negative cache entry: table is not a hypertable */\n\t\t\tcache_entry->hypertable = NULL;\n\t\t\tbreak;\n\t\tcase 1:\n\t\t\tAssert(strncmp(NameStr(cache_entry->hypertable->fd.schema_name),\n\t\t\t\t\t\t   hq->schema,\n\t\t\t\t\t\t   NAMEDATALEN) == 0);\n\t\t\tAssert(strncmp(NameStr(cache_entry->hypertable->fd.table_name),\n\t\t\t\t\t\t   hq->table,\n\t\t\t\t\t\t   NAMEDATALEN) == 0);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\telog(ERROR, \"got an unexpected number of records: %d\", number_found);\n\t\t\tbreak;\n\t}\n\n\treturn cache_entry->hypertable == NULL ? NULL : cache_entry;\n}\n\nstatic void\nhypertable_cache_missing_error(const Cache *cache, const CacheQuery *query)\n{\n\tHypertableCacheQuery *hq = (HypertableCacheQuery *) query;\n\tconst char *const rel_name = get_rel_name(hq->relid);\n\n\tif (rel_name == NULL)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_TABLE),\n\t\t\t\t errmsg(\"OID %u does not refer to a table\", hq->relid)));\n\telse\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_TS_HYPERTABLE_NOT_EXIST),\n\t\t\t\t errmsg(\"table \\\"%s\\\" is not a hypertable\", rel_name)));\n}\n\nvoid\nts_hypertable_cache_invalidate_callback(void)\n{\n\tts_cache_invalidate(&hypertable_cache_current);\n\thypertable_cache_current = hypertable_cache_create();\n}\n\n#ifdef TS_DEBUG\nTS_FUNCTION_INFO_V1(ts_hypertable_cache_clear);\n\n/*\n * Force a cache clearing. This function is used for debugging purposes.\n */\nDatum\nts_hypertable_cache_clear(PG_FUNCTION_ARGS)\n{\n\tts_hypertable_cache_invalidate_callback();\n\tPG_RETURN_VOID();\n}\n#endif\n\n/* Get hypertable cache entry. If the entry is not in the cache, add it. */\nHypertable *\nts_hypertable_cache_get_entry(Cache *const cache, const Oid relid, const unsigned int flags)\n{\n\tif (!OidIsValid(relid))\n\t{\n\t\tif (flags & CACHE_FLAG_MISSING_OK)\n\t\t\treturn NULL;\n\t\telse\n\t\t\tereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg(\"invalid Oid\")));\n\t}\n\n\treturn ts_hypertable_cache_get_entry_with_table(cache, relid, NULL, NULL, flags);\n}\n\n/*\n * Returns cache into the argument and hypertable as the function result.\n * If hypertable is not found, fails with an error.\n */\nHypertable *\nts_hypertable_cache_get_cache_and_entry(const Oid relid, const unsigned int flags,\n\t\t\t\t\t\t\t\t\t\tCache **const cache)\n{\n\t*cache = ts_hypertable_cache_pin();\n\treturn ts_hypertable_cache_get_entry(*cache, relid, flags);\n}\n\nHypertable *\nts_hypertable_cache_get_entry_rv(Cache *cache, const RangeVar *rv)\n{\n\treturn ts_hypertable_cache_get_entry(cache,\n\t\t\t\t\t\t\t\t\t\t RangeVarGetRelid(rv, NoLock, true),\n\t\t\t\t\t\t\t\t\t\t CACHE_FLAG_MISSING_OK);\n}\n\nTSDLLEXPORT Hypertable *\nts_hypertable_cache_get_entry_by_id(Cache *cache, const int32 hypertable_id)\n{\n\treturn ts_hypertable_cache_get_entry(cache,\n\t\t\t\t\t\t\t\t\t\t ts_hypertable_id_to_relid(hypertable_id, true),\n\t\t\t\t\t\t\t\t\t\t CACHE_FLAG_MISSING_OK);\n}\n\nHypertable *\nts_hypertable_cache_get_entry_with_table(Cache *cache, const Oid relid, const char *schema,\n\t\t\t\t\t\t\t\t\t\t const char *table, const unsigned int flags)\n{\n\tHypertableCacheQuery query = {\n\t\t.q.flags = flags,\n\t\t.relid = relid,\n\t\t.schema = schema,\n\t\t.table = table,\n\t};\n\tHypertableCacheEntry *entry = ts_cache_fetch(cache, &query.q);\n\tAssert((flags & CACHE_FLAG_MISSING_OK) ? true : (entry != NULL && entry->hypertable != NULL));\n\treturn entry == NULL ? NULL : entry->hypertable;\n}\n\nextern TSDLLEXPORT Cache *\nts_hypertable_cache_pin()\n{\n\treturn ts_cache_pin(hypertable_cache_current);\n}\n\nvoid\n_hypertable_cache_init(void)\n{\n\tCreateCacheMemoryContext();\n\thypertable_cache_current = hypertable_cache_create();\n}\n\nvoid\n_hypertable_cache_fini(void)\n{\n\tts_cache_invalidate(&hypertable_cache_current);\n}\n"
  },
  {
    "path": "src/hypertable_cache.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include \"cache.h\"\n#include \"export.h\"\n#include \"hypertable.h\"\n\n/* When a hypertable entry ht is fetched using the cache\n * i.e. ts_hypertable_cache_get_entry and variants, all related information such as\n *  hyperspaces, dimensions etc are also fetched into the cache. These are allocated in\n *  the cache's memory context.\n *  If the cache pin is released by calling ts_cache_release or variants, the memory\n *  associated with hypertable, its space dimensions etc. have also been released.\n *  As a best practice, call ts_cache_release right before returning from the function\n *  where the cache entry was acquired. This prevents inadvertent errors if someone\n *  modifies this function later and uses an indirectly linked object from the cache.\n *  Example:\n *  void my_func(...)\n *  {\n *\n *     Hypertable * ht = ts_hypertable_cache_get_xxx(...)\n *     ......\n *\n *    if ( error )\n *    {\n *        elog(ERROR, ... ); <----- ts_cache_release not needed here.\n *    }\n *\n *    .....\n *    ts_cache_release();\n *    return ..;\n *  }\n *  Note that any exceptions/errors i.e. elog/ereport etc. will trigger an automatic\n *  cache release. So there is no need for additional  ts_cache_release() calls.\n */\nextern TSDLLEXPORT Hypertable *ts_hypertable_cache_get_entry(Cache *const cache, const Oid relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t const unsigned int flags);\nextern TSDLLEXPORT Hypertable *ts_hypertable_cache_get_cache_and_entry(const Oid relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   const unsigned int flags,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   Cache **const cache);\nextern TSDLLEXPORT Hypertable *ts_hypertable_cache_get_entry_rv(Cache *cache, const RangeVar *rv);\nextern TSDLLEXPORT Hypertable *\nts_hypertable_cache_get_entry_with_table(Cache *cache, const Oid relid, const char *schema,\n\t\t\t\t\t\t\t\t\t\t const char *table, const unsigned int flags);\nextern TSDLLEXPORT Hypertable *ts_hypertable_cache_get_entry_by_id(Cache *cache,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   const int32 hypertable_id);\n\nextern void ts_hypertable_cache_invalidate_callback(void);\n\nextern TSDLLEXPORT Cache *ts_hypertable_cache_pin(void);\n\nextern void _hypertable_cache_init(void);\nextern void _hypertable_cache_fini(void);\n"
  },
  {
    "path": "src/hypertable_restrict_info.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n\n#include <catalog/pg_inherits.h>\n#include <optimizer/optimizer.h>\n#include <parser/parse_coerce.h>\n#include <parser/parsetree.h>\n#include <tcop/tcopprot.h>\n#include <utils/array.h>\n#include <utils/builtins.h>\n#include <utils/lsyscache.h>\n#include <utils/typcache.h>\n\n#include \"hypertable_restrict_info.h\"\n\n#include \"chunk.h\"\n#include \"chunk_scan.h\"\n#include \"dimension.h\"\n#include \"dimension_slice.h\"\n#include \"dimension_vector.h\"\n#include \"expression_utils.h\"\n#include \"guc.h\"\n#include \"hypercube.h\"\n#include \"partitioning.h\"\n#include \"scan_iterator.h\"\n#include \"ts_catalog/chunk_column_stats.h\"\n#include \"utils.h\"\n\ntypedef struct DimensionValues\n{\n\tList *values;\n\tbool use_or; /* ORed or ANDed values */\n\tOid type;\t /* Oid type for values */\n} DimensionValues;\n\nstatic DimensionValues *dimension_values_create(List *values, Oid type, bool use_or);\n\nstatic DimensionRestrictInfoOpen *\ndimension_restrict_info_open_create(const Dimension *d)\n{\n\tDimensionRestrictInfoOpen *new = palloc(sizeof(DimensionRestrictInfoOpen));\n\n\tnew->base.dimension = d;\n\tnew->lower_strategy = InvalidStrategy;\n\tnew->upper_strategy = InvalidStrategy;\n\treturn new;\n}\n\nstatic DimensionRestrictInfoClosed *\ndimension_restrict_info_closed_create(const Dimension *d)\n{\n\tDimensionRestrictInfoClosed *new = palloc(sizeof(DimensionRestrictInfoClosed));\n\n\tnew->partitions = NIL;\n\tnew->base.dimension = d;\n\tnew->strategy = InvalidStrategy;\n\treturn new;\n}\n\nstatic DimensionRestrictInfo *\ndimension_restrict_info_create(const Dimension *d)\n{\n\tswitch (d->type)\n\t{\n\t\tcase DIMENSION_TYPE_OPEN:\n\t\t\treturn &dimension_restrict_info_open_create(d)->base;\n\t\tcase DIMENSION_TYPE_CLOSED:\n\t\t\treturn &dimension_restrict_info_closed_create(d)->base;\n\t\tdefault:\n\t\t\telog(ERROR, \"unknown dimension type\");\n\t\t\treturn NULL;\n\t}\n}\n\n/*\n * Given a column from a hypertable, create a DimensionRestrictInfo entry\n * representing it. This gets used by the usual hypertable restrict info\n * machinery to identify if the query clauses are using expressions involving\n * this column. Note that this column is NOT a partitioning column but we are\n * tracking ranges for this column in the _timescaledb_catalog.chunk_column_stats\n * catalog table.\n *\n * The idea is to do chunk exclusion when queries have WHERE clauses using this\n * column. The logic at the \"Dimension\" entry level are the same, so we reuse the\n * same representation to benefit from it.\n */\nstatic DimensionRestrictInfo *\nchunk_column_stats_restrict_info_create(const Hypertable *ht, const Form_chunk_column_stats d)\n{\n\t/* create a dummy dimension structure for this range entry */\n\tDimension *dim = ts_chunk_column_stats_fill_dummy_dimension(d, ht->main_table_relid);\n\n\t/* similar to open dimensions */\n\treturn &dimension_restrict_info_open_create(dim)->base;\n}\n\n/*\n * Check if the restriction on this dimension is trivial, that is, the entire\n * range of the dimension matches.\n */\nstatic bool\ndimension_restrict_info_is_trivial(const DimensionRestrictInfo *dri)\n{\n\tswitch (dri->dimension->type)\n\t{\n\t\tcase DIMENSION_TYPE_OPEN:\n\t\tcase DIMENSION_TYPE_STATS:\n\t\t{\n\t\t\tDimensionRestrictInfoOpen *open = (DimensionRestrictInfoOpen *) dri;\n\t\t\treturn open->lower_strategy == InvalidStrategy &&\n\t\t\t\t   open->upper_strategy == InvalidStrategy;\n\t\t}\n\t\tcase DIMENSION_TYPE_CLOSED:\n\t\t\treturn ((DimensionRestrictInfoClosed *) dri)->strategy == InvalidStrategy;\n\t\tdefault:\n\t\t\tAssert(false);\n\t\t\treturn false;\n\t}\n}\n\n/*\n * Add restriction for open (time) dimension.\n * Values are expected to be int64 (already converted by caller).\n */\nstatic bool\ndimension_restrict_info_open_add(DimensionRestrictInfoOpen *dri, StrategyNumber strategy,\n\t\t\t\t\t\t\t\t DimensionValues *dimvalues)\n{\n\tListCell *item;\n\tbool restriction_added = false;\n\n\t/*\n\t * For IN/ANY with multiple equality values on an open dimension,\n\t * use the bounding range [min, max] as an over-approximation.\n\t * This may include extra chunks, which PG constraint exclusion\n\t * will prune later. Much better than returning all chunks.\n\t */\n\tif (dimvalues->use_or && list_length(dimvalues->values) > 1)\n\t{\n\t\tif (strategy != BTEqualStrategyNumber)\n\t\t\treturn false;\n\n\t\tint64 min_val = PG_INT64_MAX;\n\t\tint64 max_val = PG_INT64_MIN;\n\t\tListCell *lc;\n\t\tforeach (lc, dimvalues->values)\n\t\t{\n\t\t\tint64 value = DatumGetInt64(PointerGetDatum(lfirst(lc)));\n\t\t\tif (value < min_val)\n\t\t\t\tmin_val = value;\n\t\t\tif (value > max_val)\n\t\t\t\tmax_val = value;\n\t\t}\n\t\tdri->lower_bound = min_val;\n\t\tdri->upper_bound = max_val;\n\t\tdri->lower_strategy = BTGreaterEqualStrategyNumber;\n\t\tdri->upper_strategy = BTLessEqualStrategyNumber;\n\t\treturn true;\n\t}\n\n\tforeach (item, dimvalues->values)\n\t{\n\t\tint64 value = DatumGetInt64(PointerGetDatum(lfirst(item)));\n\n\t\tswitch (strategy)\n\t\t{\n\t\t\tcase BTLessEqualStrategyNumber:\n\t\t\tcase BTLessStrategyNumber:\n\t\t\t\tif (dri->upper_strategy == InvalidStrategy || value < dri->upper_bound)\n\t\t\t\t{\n\t\t\t\t\tdri->upper_strategy = strategy;\n\t\t\t\t\tdri->upper_bound = value;\n\t\t\t\t\trestriction_added = true;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase BTGreaterEqualStrategyNumber:\n\t\t\tcase BTGreaterStrategyNumber:\n\t\t\t\tif (dri->lower_strategy == InvalidStrategy || value > dri->lower_bound)\n\t\t\t\t{\n\t\t\t\t\tdri->lower_strategy = strategy;\n\t\t\t\t\tdri->lower_bound = value;\n\t\t\t\t\trestriction_added = true;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase BTEqualStrategyNumber:\n\t\t\t\tdri->lower_bound = value;\n\t\t\t\tdri->upper_bound = value;\n\t\t\t\tdri->lower_strategy = BTGreaterEqualStrategyNumber;\n\t\t\t\tdri->upper_strategy = BTLessEqualStrategyNumber;\n\t\t\t\trestriction_added = true;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\t/* unsupported strategy */\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn restriction_added;\n}\n\nstatic List *\ndimension_restrict_info_get_partitions(DimensionRestrictInfoClosed *dri, Oid collation,\n\t\t\t\t\t\t\t\t\t   List *values, Oid value_type)\n{\n\tList *partitions = NIL;\n\tListCell *item;\n\n\tforeach (item, values)\n\t{\n\t\tDatum value = ts_dimension_transform_value(dri->base.dimension,\n\t\t\t\t\t\t\t\t\t\t\t\t   collation,\n\t\t\t\t\t\t\t\t\t\t\t\t   PointerGetDatum(lfirst(item)),\n\t\t\t\t\t\t\t\t\t\t\t\t   value_type,\n\t\t\t\t\t\t\t\t\t\t\t\t   NULL);\n\n\t\tpartitions = list_append_unique_int(partitions, DatumGetInt32(value));\n\t}\n\n\treturn partitions;\n}\n\nstatic bool\ndimension_restrict_info_closed_add(DimensionRestrictInfoClosed *dri, StrategyNumber strategy,\n\t\t\t\t\t\t\t\t   Oid collation, DimensionValues *dimvalues)\n{\n\tList *partitions;\n\tbool restriction_added = false;\n\n\tif (strategy != BTEqualStrategyNumber)\n\t{\n\t\treturn false;\n\t}\n\n\tpartitions =\n\t\tdimension_restrict_info_get_partitions(dri, collation, dimvalues->values, dimvalues->type);\n\n\t/* the intersection is empty when using ALL operator (ANDing values)  */\n\tif (list_length(partitions) > 1 && !dimvalues->use_or)\n\t{\n\t\tdri->strategy = strategy;\n\t\tdri->partitions = NIL;\n\t\treturn true;\n\t}\n\n\tif (dri->strategy == InvalidStrategy)\n\t/* first time through */\n\t{\n\t\tdri->partitions = partitions;\n\t\tdri->strategy = strategy;\n\t\trestriction_added = true;\n\t}\n\telse\n\t{\n\t\t/* intersection with NULL is NULL */\n\t\tif (dri->partitions == NIL)\n\t\t\treturn true;\n\n\t\t/*\n\t\t * We are always ANDing the expressions thus intersection is used.\n\t\t */\n\t\tdri->partitions = list_intersection_int(dri->partitions, partitions);\n\n\t\t/* no intersection is also a restriction  */\n\t\trestriction_added = true;\n\t}\n\treturn restriction_added;\n}\n\nHypertableRestrictInfo *\nts_hypertable_restrict_info_create(RelOptInfo *rel, Hypertable *ht)\n{\n\t/* If chunk skipping is disabled, we have to empty range_space\n\t * in case it was cached earlier.\n\t */\n\tChunkRangeSpace *range_space = ht->range_space;\n\tif (!ts_guc_enable_chunk_skipping)\n\t\trange_space = NULL;\n\n\tint num_dimensions =\n\t\tht->space->num_dimensions + (range_space ? range_space->num_range_cols : 0);\n\tHypertableRestrictInfo *res = palloc0(sizeof(HypertableRestrictInfo) +\n\t\t\t\t\t\t\t\t\t\t  (sizeof(DimensionRestrictInfo *) * num_dimensions));\n\tint i;\n\tint range_index = 0;\n\n\tres->num_dimensions = num_dimensions;\n\n\tfor (i = 0; i < ht->space->num_dimensions; i++)\n\t{\n\t\tDimensionRestrictInfo *dri = dimension_restrict_info_create(&ht->space->dimensions[i]);\n\n\t\tres->dimension_restriction[i] = dri;\n\t\trange_index++;\n\t}\n\n\t/*\n\t * We convert the range_space entries into dummy \"DimensionRestrictInfo\" entries. This allows\n\t * the hypertable restrict info machinery to consider these as well.\n\t */\n\tfor (i = 0; range_space != NULL && i < range_space->num_range_cols; i++)\n\t{\n\t\tDimensionRestrictInfo *dri =\n\t\t\tchunk_column_stats_restrict_info_create(ht, &ht->range_space->range_cols[i]);\n\n\t\tres->dimension_restriction[range_index++] = dri;\n\t}\n\n\treturn res;\n}\n\nstatic DimensionRestrictInfo *\nhypertable_restrict_info_get(HypertableRestrictInfo *hri, AttrNumber attno)\n{\n\tint i;\n\n\tfor (i = 0; i < hri->num_dimensions; i++)\n\t{\n\t\tif (hri->dimension_restriction[i]->dimension->column_attno == attno)\n\t\t\treturn hri->dimension_restriction[i];\n\t}\n\treturn NULL;\n}\n\ntypedef DimensionValues *(*get_dimension_values)(Const *c, bool use_or);\n\nstatic void\nhypertable_restrict_info_add_expr(HypertableRestrictInfo *hri, PlannerInfo *root, Var *v,\n\t\t\t\t\t\t\t\t  Expr *expr, Oid op_oid, get_dimension_values func_get_dim_values,\n\t\t\t\t\t\t\t\t  bool use_or)\n{\n\tDimensionRestrictInfo *dri;\n\tConst *c;\n\tRangeTblEntry *rte;\n\tOid columntype;\n\tTypeCacheEntry *tce;\n\tint strategy;\n\tOid lefttype, righttype;\n\tDimensionValues *dimvalues;\n\n\tdri = hypertable_restrict_info_get(hri, v->varattno);\n\t/* the attribute is not a dimension */\n\tif (dri == NULL)\n\t\treturn;\n\n\texpr = (Expr *) eval_const_expressions(root, (Node *) expr);\n\n\tif (!IsA(expr, Const) || !OidIsValid(op_oid) || !op_strict(op_oid))\n\t\treturn;\n\n\tc = (Const *) expr;\n\n\t/* quick check for a NULL constant */\n\tif (c->constisnull)\n\t\treturn;\n\n\trte = rt_fetch(v->varno, root->parse->rtable);\n\n\tcolumntype = get_atttype(rte->relid, dri->dimension->column_attno);\n\ttce = lookup_type_cache(columntype, TYPECACHE_BTREE_OPFAMILY);\n\n\tif (!op_in_opfamily(op_oid, tce->btree_opf))\n\t\treturn;\n\n\tget_op_opfamily_properties(op_oid, tce->btree_opf, false, &strategy, &lefttype, &righttype);\n\n\t/*\n\t * For arrays (ScalarArrayOpExpr), we work with the element type.\n\t * Non-constant arrays were already filtered out above by the IsA(expr, Const)\n\t * check after eval_const_expressions.\n\t */\n\tOid consttype = c->consttype;\n\tOid const_element_type = get_element_type(consttype);\n\tbool is_array = OidIsValid(const_element_type);\n\tif (is_array)\n\t\tconsttype = const_element_type;\n\n\t/*\n\t * Coerce literal values to column type if needed. Coercion is required when\n\t * types differ and we use a partitioning function. The partitioning functions\n\t * always expect the column type. It is always used for closed dimensions\n\t * (space partitioning), and can be set for open dimensions too.\n\t *\n\t * Open dimensions without custom partitioning function don't need coercion\n\t * because the ts_time_value_to_internal_or_infinite() handles the cross-type\n\t * comparisons (e.g., date vs timestamp) and integer types directly.\n\t *\n\t * In Postgres, the cross-type integer inequalities (e.g. int4 column <= int8\n\t * literal) work without coercion using cross-type functions like int48le().\n\t * However, our partition function interface uses the column type, not the\n\t * literal type.\n\t *\n\t * We only use implicit coercions because narrowing casts (int8 -> int4) can\n\t * fail at runtime with \"integer out of range\". When no implicit coercion\n\t * exists, we skip chunk exclusion for this clause - correct but slower.\n\t */\n\tbool needs_coercion = (consttype != columntype) && (IS_CLOSED_DIMENSION(dri->dimension) ||\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdri->dimension->partitioning != NULL);\n\n\tif (needs_coercion)\n\t{\n\t\tOid funcid;\n\t\tCoercionPathType pathtype =\n\t\t\tfind_coercion_pathway(columntype, consttype, COERCION_IMPLICIT, &funcid);\n\n\t\tif (pathtype != COERCION_PATH_FUNC)\n\t\t{\n\t\t\t/*\n\t\t\t * No usable implicit coercion, skip this clause for TimescaleDB\n\t\t\t * chunk exclusion. It might be still handled by Postgres constraint\n\t\t\t * exclusion.\n\t\t\t *\n\t\t\t * COERCION_PATH_RELABELTYPE (binary compatible) won't occur\n\t\t\t * here because PostgreSQL coerces such literals at parse time and\n\t\t\t * eval_const_expressions() folds any remaining RelabelType(Const).\n\t\t\t */\n\t\t\treturn;\n\t\t}\n\n\t\tAssert(OidIsValid(funcid));\n\n\t\tif (is_array)\n\t\t{\n\t\t\tArrayIterator iterator =\n\t\t\t\tarray_create_iterator(DatumGetArrayTypeP(c->constvalue), 0, NULL);\n\t\t\tDatum elem = (Datum) NULL;\n\t\t\tbool isnull;\n\t\t\tList *values = NIL;\n\n\t\t\twhile (array_iterate(iterator, &elem, &isnull))\n\t\t\t{\n\t\t\t\tif (!isnull)\n\t\t\t\t{\n\t\t\t\t\tDatum coerced = OidFunctionCall1Coll(funcid, c->constcollid, elem);\n\t\t\t\t\tvalues = lappend(values, DatumGetPointer(coerced));\n\t\t\t\t}\n\t\t\t}\n\t\t\tarray_free_iterator(iterator);\n\t\t\tdimvalues = dimension_values_create(values, columntype, use_or);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tDatum coerced = OidFunctionCall1Coll(funcid, c->constcollid, c->constvalue);\n\t\t\tdimvalues =\n\t\t\t\tdimension_values_create(list_make1(DatumGetPointer(coerced)), columntype, use_or);\n\t\t}\n\t}\n\telse\n\t{\n\t\tdimvalues = func_get_dim_values(c, use_or);\n\t}\n\n\t/*\n\t * Add restriction based on dimension type.\n\t */\n\tif (IS_CLOSED_DIMENSION(dri->dimension))\n\t{\n\t\tif (dimension_restrict_info_closed_add((DimensionRestrictInfoClosed *) dri,\n\t\t\t\t\t\t\t\t\t\t\t   strategy,\n\t\t\t\t\t\t\t\t\t\t\t   c->constcollid,\n\t\t\t\t\t\t\t\t\t\t\t   dimvalues))\n\t\t\thri->num_base_restrictions++;\n\t}\n\telse\n\t{\n\t\t/* Open and stats dimensions: convert values to int64 */\n\t\tList *int64_values = NIL;\n\t\tListCell *lc;\n\t\tOid valuetype = dimvalues->type;\n\n\t\tforeach (lc, dimvalues->values)\n\t\t{\n\t\t\tDatum value = PointerGetDatum(lfirst(lc));\n\t\t\tint64 internal;\n\n\t\t\tif (dri->dimension->partitioning != NULL)\n\t\t\t{\n\t\t\t\t/* Apply partitioning function first, then convert result to int64 */\n\t\t\t\tOid restype;\n\t\t\t\tvalue = ts_dimension_transform_value(dri->dimension,\n\t\t\t\t\t\t\t\t\t\t\t\t\t c->constcollid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t value,\n\t\t\t\t\t\t\t\t\t\t\t\t\t valuetype,\n\t\t\t\t\t\t\t\t\t\t\t\t\t &restype);\n\t\t\t\tinternal = ts_time_value_to_internal_or_infinite(value, restype);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tinternal = ts_time_value_to_internal_or_infinite(value, valuetype);\n\t\t\t}\n\t\t\tint64_values = lappend(int64_values, DatumGetPointer(Int64GetDatum(internal)));\n\t\t}\n\t\tdimvalues->values = int64_values;\n\t\tdimvalues->type = INT8OID;\n\n\t\tif (dimension_restrict_info_open_add((DimensionRestrictInfoOpen *) dri,\n\t\t\t\t\t\t\t\t\t\t\t strategy,\n\t\t\t\t\t\t\t\t\t\t\t dimvalues))\n\t\t\thri->num_base_restrictions++;\n\t}\n}\n\nstatic DimensionValues *\ndimension_values_create(List *values, Oid type, bool use_or)\n{\n\tDimensionValues *dimvalues;\n\n\tdimvalues = palloc(sizeof(DimensionValues));\n\tdimvalues->values = values;\n\tdimvalues->use_or = use_or;\n\tdimvalues->type = type;\n\n\treturn dimvalues;\n}\n\nstatic DimensionValues *\ndimension_values_create_from_array(Const *c, bool user_or)\n{\n\tArrayIterator iterator = array_create_iterator(DatumGetArrayTypeP(c->constvalue), 0, NULL);\n\tDatum elem = (Datum) NULL;\n\tbool isnull;\n\tList *values = NIL;\n\tOid base_el_type;\n\n\twhile (array_iterate(iterator, &elem, &isnull))\n\t{\n\t\tif (!isnull)\n\t\t\tvalues = lappend(values, DatumGetPointer(elem));\n\t}\n\n\t/* it's an array type, lets get the base element type */\n\tbase_el_type = get_element_type(c->consttype);\n\tif (!OidIsValid(base_el_type))\n\t\telog(ERROR,\n\t\t\t \"invalid base element type for array type: \\\"%s\\\"\",\n\t\t\t format_type_be(c->consttype));\n\n\treturn dimension_values_create(values, base_el_type, user_or);\n}\n\nstatic DimensionValues *\ndimension_values_create_from_single_element(Const *c, bool user_or)\n{\n\treturn dimension_values_create(list_make1(DatumGetPointer(c->constvalue)),\n\t\t\t\t\t\t\t\t   c->consttype,\n\t\t\t\t\t\t\t\t   user_or);\n}\n\nstatic void\nhypertable_restrict_info_add_restrict_info(HypertableRestrictInfo *hri, PlannerInfo *root,\n\t\t\t\t\t\t\t\t\t\t   RestrictInfo *ri)\n{\n\tOid opno;\n\tVar *var;\n\tExpr *arg_value;\n\n\tExpr *e = ri->clause;\n\n\t/* Same as constraint_exclusion */\n\tif (contain_mutable_functions((Node *) e))\n\t\treturn;\n\n\tif (ts_extract_expr_args(e, &var, &arg_value, &opno, NULL))\n\t{\n\t\tget_dimension_values value_func;\n\t\tbool use_or;\n\n\t\tswitch (nodeTag(e))\n\t\t{\n\t\t\tcase T_OpExpr:\n\t\t\t{\n\t\t\t\tvalue_func = dimension_values_create_from_single_element;\n\t\t\t\tuse_or = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase T_ScalarArrayOpExpr:\n\t\t\t{\n\t\t\t\tvalue_func = dimension_values_create_from_array;\n\t\t\t\tuse_or = castNode(ScalarArrayOpExpr, e)->useOr;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\t/* we don't support other node types */\n\t\t\t\treturn;\n\t\t}\n\t\thypertable_restrict_info_add_expr(hri, root, var, arg_value, opno, value_func, use_or);\n\t}\n}\n\nvoid\nts_hypertable_restrict_info_add(HypertableRestrictInfo *hri, PlannerInfo *root,\n\t\t\t\t\t\t\t\tList *base_restrict_infos)\n{\n\tListCell *lc;\n\n\tforeach (lc, base_restrict_infos)\n\t{\n\t\tRestrictInfo *ri = lfirst(lc);\n\n\t\thypertable_restrict_info_add_restrict_info(hri, root, ri);\n\t}\n}\n\n/*\n * Scan for dimension slices matching query constraints.\n *\n * Matching slices are appended to to the given dimension vector. Note that we\n * keep the table and index open as long as we do not change the number of\n * scan keys. If the keys change, but the number of keys is the same, we can\n * simply \"rescan\". If the number of keys change, however, we need to end the\n * scan and start again.\n */\nstatic DimensionVec *\nscan_and_append_slices(ScanIterator *it, int old_nkeys, DimensionVec **dv, bool unique)\n{\n\tif (old_nkeys != -1 && old_nkeys != it->ctx.nkeys)\n\t\tts_scan_iterator_end(it);\n\n\tts_scan_iterator_start_or_restart_scan(it);\n\n\twhile (ts_scan_iterator_next(it))\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(it);\n\t\tDimensionSlice *slice = ts_dimension_slice_from_tuple(ti);\n\n\t\tif (NULL != slice)\n\t\t{\n\t\t\tif (unique)\n\t\t\t\t*dv = ts_dimension_vec_add_unique_slice(dv, slice);\n\t\t\telse\n\t\t\t\t*dv = ts_dimension_vec_add_slice(dv, slice);\n\t\t}\n\t}\n\n\treturn *dv;\n}\n\n/* search dimension_slice catalog table for slices that meet hri restriction\n */\nstatic List *\ngather_restriction_dimension_vectors(const HypertableRestrictInfo *hri)\n{\n\tList *dimension_vecs = NIL;\n\tScanIterator it;\n\tint i;\n\tint old_nkeys = -1;\n\n\tit = ts_dimension_slice_scan_iterator_create(NULL, CurrentMemoryContext);\n\n\tfor (i = 0; i < hri->num_dimensions; i++)\n\t{\n\t\tDimensionRestrictInfo *dri = hri->dimension_restriction[i];\n\t\tDimensionVec *dv;\n\n\t\tAssert(NULL != dri);\n\t\t/* dimension ranges don't need dimension slices */\n\t\tdv = ts_dimension_vec_create(\n\t\t\tdri->dimension->type == DIMENSION_TYPE_STATS ? 1 : DIMENSION_VEC_DEFAULT_SIZE);\n\t\tdv->dri = dri;\n\n\t\tswitch (dri->dimension->type)\n\t\t{\n\t\t\tcase DIMENSION_TYPE_OPEN:\n\t\t\t{\n\t\t\t\tconst DimensionRestrictInfoOpen *open = (const DimensionRestrictInfoOpen *) dri;\n\n\t\t\t\tts_dimension_slice_scan_iterator_set_range(&it,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   open->base.dimension->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   open->upper_strategy,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   open->upper_bound,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   open->lower_strategy,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   open->lower_bound);\n\n\t\t\t\t/*\n\t\t\t\t * If we have a condition on the second index column\n\t\t\t\t * range_start, use a backward scan direction, so that the index\n\t\t\t\t * is able to use the second column as well to choose the\n\t\t\t\t * starting point for the scan.\n\t\t\t\t * If not, prefer forward direction, because backwards scan is\n\t\t\t\t * slightly slower for some reason.\n\t\t\t\t * Ideally we need some other index type than btree for this,\n\t\t\t\t * because the btree index is not so suited for queries like\n\t\t\t\t * \"find an interval that contains a given point\", which is what\n\t\t\t\t * we're doing here.\n\t\t\t\t * There is a comment in the Postgres code (_bt_start()) that\n\t\t\t\t * explains the logic of selecting a starting point for a btree\n\t\t\t\t * index scan in more detail.\n\t\t\t\t */\n\t\t\t\tit.ctx.scandirection = open->upper_strategy != InvalidStrategy ?\n\t\t\t\t\t\t\t\t\t\t   BackwardScanDirection :\n\t\t\t\t\t\t\t\t\t\t   ForwardScanDirection;\n\n\t\t\t\tdv = scan_and_append_slices(&it, old_nkeys, &dv, false);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase DIMENSION_TYPE_CLOSED:\n\t\t\t{\n\t\t\t\tconst DimensionRestrictInfoClosed *closed =\n\t\t\t\t\t(const DimensionRestrictInfoClosed *) dri;\n\n\t\t\t\t/* Shouldn't have trivial restriction infos here. */\n\t\t\t\tAssert(closed->strategy == BTEqualStrategyNumber);\n\n\t\t\t\tListCell *cell;\n\t\t\t\tforeach (cell, closed->partitions)\n\t\t\t\t{\n\t\t\t\t\tint32 partition = lfirst_int(cell);\n\n\t\t\t\t\t/*\n\t\t\t\t\t * slice_end >= value && slice_start <= value.\n\t\t\t\t\t * See the comment about scan direction above.\n\t\t\t\t\t */\n\t\t\t\t\tit.ctx.scandirection = BackwardScanDirection;\n\t\t\t\t\tts_dimension_slice_scan_iterator_set_range(&it,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   dri->dimension->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   BTLessEqualStrategyNumber,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   partition,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   BTGreaterEqualStrategyNumber,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   partition);\n\n\t\t\t\t\tdv = scan_and_append_slices(&it, old_nkeys, &dv, true);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase DIMENSION_TYPE_STATS:\n\t\t\t{\n\t\t\t\t/* an empty dv will be appended for this as a placeholder */\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\telog(ERROR, \"unknown dimension type\");\n\t\t\t\treturn NULL;\n\t\t}\n\n\t\tAssert(dv->num_slices >= 0);\n\n\t\t/*\n\t\t * If there is a dimension where no slices match, the result will be\n\t\t * empty. But only do so if it's not a DIMENSION_TYPE_STATS entry.\n\t\t *\n\t\t * For DIMENSION_TYPE_STATS entries, we get the list of chunks\n\t\t * directly later on from \"chunk_column_stats\" catalog. They do not\n\t\t * have dimension slices.\n\t\t */\n\t\tif (dv->num_slices == 0 && dri->dimension->type != DIMENSION_TYPE_STATS)\n\t\t{\n\t\t\tts_scan_iterator_close(&it);\n\n\t\t\treturn NIL;\n\t\t}\n\n\t\tdv = ts_dimension_vec_sort(&dv);\n\t\tdimension_vecs = lappend(dimension_vecs, dv);\n\t\told_nkeys = it.ctx.nkeys;\n\t}\n\n\tts_scan_iterator_close(&it);\n\n\tAssert(list_length(dimension_vecs) == hri->num_dimensions);\n\n\treturn dimension_vecs;\n}\n\nChunk **\nts_hypertable_restrict_info_get_chunks(HypertableRestrictInfo *hri, Hypertable *ht,\n\t\t\t\t\t\t\t\t\t   bool include_osm, unsigned int *num_chunks)\n{\n\t/*\n\t * Remove the dimensions for which we don't have a restriction, that is,\n\t * the entire range of the dimension matches. Such dimensions do not\n\t * influence the result set, because their every slice matches, so we can\n\t * just ignore them when searching for the matching chunks.\n\t */\n\tconst int old_dimensions = hri->num_dimensions;\n\thri->num_dimensions = 0;\n\tfor (int i = 0; i < old_dimensions; i++)\n\t{\n\t\tDimensionRestrictInfo *dri = hri->dimension_restriction[i];\n\t\tif (!dimension_restrict_info_is_trivial(dri))\n\t\t{\n\t\t\thri->dimension_restriction[hri->num_dimensions] = dri;\n\t\t\thri->num_dimensions++;\n\t\t}\n\t}\n\n\tList *chunk_ids = NIL;\n\tif (hri->num_dimensions == 0)\n\t{\n\t\t/*\n\t\t * No restrictions on hyperspace. Just enumerate all the chunks.\n\t\t */\n\t\tchunk_ids = ts_chunk_get_chunk_ids_by_hypertable_id(ht->fd.id);\n\n\t\t/*\n\t\t * If the hypertable has an OSM chunk it would end up in the list\n\t\t * as well. We need to remove it when OSM reads are disabled via GUC\n\t\t * variable.\n\t\t */\n\t\tif (!include_osm || !ts_guc_enable_osm_reads)\n\t\t{\n\t\t\tint32 osm_chunk_id = ts_chunk_get_osm_chunk_id(ht->fd.id);\n\n\t\t\tchunk_ids = list_delete_int(chunk_ids, osm_chunk_id);\n\t\t}\n\t}\n\telse\n\t{\n\t\t/*\n\t\t * Have some restrictions, enumerate the matching dimension slices.\n\t\t */\n\t\tList *dimension_vectors = gather_restriction_dimension_vectors(hri);\n\t\tif (list_length(dimension_vectors) == 0)\n\t\t{\n\t\t\t/*\n\t\t\t * No dimension slices match for some dimension for which there is\n\t\t\t * a restriction. This means that no chunks match.\n\t\t\t */\n\t\t\tchunk_ids = NIL;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* Find the chunks matching these dimension ranges/slices. */\n\t\t\tchunk_ids = ts_chunk_id_find_in_subspace(ht, dimension_vectors);\n\t\t}\n\n\t\tint32 osm_chunk_id = ts_chunk_get_osm_chunk_id(ht->fd.id);\n\n\t\tif (osm_chunk_id != INVALID_CHUNK_ID)\n\t\t{\n\t\t\tif (!ts_guc_enable_osm_reads)\n\t\t\t{\n\t\t\t\tchunk_ids = list_delete_int(chunk_ids, osm_chunk_id);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * At this point the OSM chunk was either:\n\t\t\t\t * 1. added to the list because it has a valid range that agrees with the\n\t\t\t\t * restrictions;\n\t\t\t\t * 2. not added because it has a valid range and it was excluded;\n\t\t\t\t * 3. not added because it has an invalid range and it was excluded.\n\t\t\t\t * If the chunk's range is invalid, only then should we consider adding it,\n\t\t\t\t * otherwise the exclusion logic should have correctly included or excluded it from\n\t\t\t\t * the list. Also, if the range is invalid but the NONCONTIGUOUS flag is not set,\n\t\t\t\t * indicating that the chunk is empty, we don't need to do a scan so we do not add\n\t\t\t\t * it either.\n\t\t\t\t */\n\t\t\t\tconst Dimension *time_dim = hyperspace_get_open_dimension(ht->space, 0);\n\t\t\t\tDimensionSlice *slice = ts_chunk_get_osm_slice_and_lock(osm_chunk_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttime_dim->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tLockTupleKeyShare,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tRowShareLock);\n\t\t\t\tbool range_invalid =\n\t\t\t\t\tts_osm_chunk_range_is_invalid(slice->fd.range_start, slice->fd.range_end);\n\n\t\t\t\tif (range_invalid &&\n\t\t\t\t\tts_flags_are_set_32(ht->fd.status, HYPERTABLE_STATUS_OSM_CHUNK_NONCONTIGUOUS))\n\t\t\t\t\tchunk_ids = list_append_unique_int(chunk_ids, osm_chunk_id);\n\t\t\t}\n\t\t}\n\t}\n\n\t/*\n\t * Sort the ids to have more favorable (closer to sequential) data access\n\t * patterns to our catalog tables and indexes.\n\t * We don't care about the locking order here, because this code uses\n\t * AccessShareLock that doesn't conflict with itself.\n\t */\n\tlist_sort(chunk_ids, list_int_cmp);\n\n\treturn ts_chunk_scan_by_chunk_ids(ht->space, chunk_ids, num_chunks);\n}\n\n/*\n * Compare two chunks along first dimension and chunk ID (in that priority and\n * order).\n */\nstatic int\nchunk_cmp_impl(const Chunk *c1, const Chunk *c2)\n{\n\tint cmp = ts_dimension_slice_cmp(c1->cube->slices[0], c2->cube->slices[0]);\n\n\tif (cmp == 0)\n\t\tcmp = VALUE_CMP(c1->fd.id, c2->fd.id);\n\n\treturn cmp;\n}\n\nstatic int\nchunk_cmp(const void *c1, const void *c2)\n{\n\treturn chunk_cmp_impl(*((const Chunk **) c1), *((const Chunk **) c2));\n}\n\nstatic int\nchunk_cmp_reverse(const void *c1, const void *c2)\n{\n\treturn chunk_cmp_impl(*((const Chunk **) c2), *((const Chunk **) c1));\n}\n\n/*\n * get chunk oids ordered by time dimension\n *\n * if \"chunks\" is NULL, we get all the chunks from the catalog. Otherwise we\n * restrict ourselves to the passed in chunks list.\n *\n * nested_oids is a list of lists, chunks that occupy the same time slice will be\n * in the same list. In the list [[1,2,3],[4,5,6]] chunks 1, 2 and 3 are space partitions of\n * the same time slice and 4, 5 and 6 are space partitions of the next time slice.\n *\n */\nChunk **\nts_hypertable_restrict_info_get_chunks_ordered(HypertableRestrictInfo *hri, Hypertable *ht,\n\t\t\t\t\t\t\t\t\t\t\t   bool include_osm, Chunk **chunks, bool reverse,\n\t\t\t\t\t\t\t\t\t\t\t   List **nested_oids, unsigned int *num_chunks)\n{\n\tList *slot_chunk_oids = NIL;\n\tDimensionSlice *slice = NULL;\n\tunsigned int i;\n\n\tif (chunks == NULL)\n\t{\n\t\tchunks = ts_hypertable_restrict_info_get_chunks(hri, ht, include_osm, num_chunks);\n\t}\n\n\tif (*num_chunks == 0)\n\t\treturn NULL;\n\n\tAssert(ht->space->num_dimensions > 0);\n\tAssert(IS_OPEN_DIMENSION(&ht->space->dimensions[0]));\n\n\tif (reverse)\n\t\tqsort((void *) chunks, *num_chunks, sizeof(Chunk *), chunk_cmp_reverse);\n\telse\n\t\tqsort((void *) chunks, *num_chunks, sizeof(Chunk *), chunk_cmp);\n\n\tfor (i = 0; i < *num_chunks; i++)\n\t{\n\t\tChunk *chunk = chunks[i];\n\n\t\tif (NULL != slice && ts_dimension_slice_cmp(slice, chunk->cube->slices[0]) != 0 &&\n\t\t\tslot_chunk_oids != NIL)\n\t\t{\n\t\t\t*nested_oids = lappend(*nested_oids, slot_chunk_oids);\n\t\t\tslot_chunk_oids = NIL;\n\t\t}\n\n\t\tif (NULL != nested_oids)\n\t\t\tslot_chunk_oids = lappend_oid(slot_chunk_oids, chunk->table_id);\n\n\t\tslice = chunk->cube->slices[0];\n\t}\n\n\tif (slot_chunk_oids != NIL)\n\t\t*nested_oids = lappend(*nested_oids, slot_chunk_oids);\n\n\treturn chunks;\n}\n"
  },
  {
    "path": "src/hypertable_restrict_info.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include \"hypertable.h\"\n\ntypedef struct DimensionRestrictInfo\n{\n\tconst Dimension *dimension;\n} DimensionRestrictInfo;\n\ntypedef struct DimensionRestrictInfoOpen\n{\n\tDimensionRestrictInfo base;\n\tint64 lower_bound; /* internal time representation */\n\tStrategyNumber lower_strategy;\n\tint64 upper_bound; /* internal time representation */\n\tStrategyNumber upper_strategy;\n} DimensionRestrictInfoOpen;\n\ntypedef struct DimensionRestrictInfoClosed\n{\n\tDimensionRestrictInfo base;\n\tList *partitions;\t\t /* hash values */\n\tStrategyNumber strategy; /* either Invalid or equal */\n} DimensionRestrictInfoClosed;\n\n/* HypertableRestrictInfo represents restrictions on a hypertable. It uses\n * range exclusion logic to figure out which chunks can match the description */\ntypedef struct HypertableRestrictInfo\n{\n\tint num_base_restrictions; /* number of base restrictions\n\t\t\t\t\t\t\t\t* successfully added */\n\tint num_dimensions;\n\tDimensionRestrictInfo *dimension_restriction[FLEXIBLE_ARRAY_MEMBER]; /* array of dimension\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  * restrictions */\n} HypertableRestrictInfo;\n\nextern HypertableRestrictInfo *ts_hypertable_restrict_info_create(RelOptInfo *rel, Hypertable *ht);\n\n/* Add restrictions based on a List of RestrictInfo */\nextern void ts_hypertable_restrict_info_add(HypertableRestrictInfo *hri, PlannerInfo *root,\n\t\t\t\t\t\t\t\t\t\t\tList *base_restrict_infos);\n\n/* Get a list of chunk oids for chunks whose constraints match the restriction clauses */\nextern Chunk **ts_hypertable_restrict_info_get_chunks(HypertableRestrictInfo *hri, Hypertable *ht,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  bool include_osm, unsigned int *num_chunks);\n\nextern Chunk **ts_hypertable_restrict_info_get_chunks_ordered(HypertableRestrictInfo *hri,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  Hypertable *ht, bool include_osm,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  Chunk **chunks, bool reverse,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  List **nested_oids,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  unsigned int *num_chunks);\n"
  },
  {
    "path": "src/import/allpaths.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n/*\n * This file contains source code that was copied and/or modified from\n * the PostgreSQL database, which is licensed under the open-source\n * PostgreSQL License. Please see the NOTICE at the top level\n * directory for a copy of the PostgreSQL License.\n */\n#include <postgres.h>\n#include <access/tsmapi.h>\n#include <catalog/pg_proc.h>\n#include <foreign/fdwapi.h>\n#include <miscadmin.h>\n#include <nodes/nodeFuncs.h>\n#include <nodes/parsenodes.h>\n#include <nodes/plannodes.h>\n#include <optimizer/appendinfo.h>\n#include <optimizer/clauses.h>\n#include <optimizer/cost.h>\n#include <optimizer/optimizer.h>\n#include <optimizer/pathnode.h>\n#include <optimizer/paths.h>\n#include <optimizer/plancat.h>\n#include <optimizer/planner.h>\n#include <optimizer/prep.h>\n#include <utils/lsyscache.h>\n#include <utils/rel.h>\n\n#include <math.h>\n\n#include \"allpaths.h\"\n#include \"chunk.h\"\n#include \"cross_module_fn.h\"\n#include \"planner/planner.h\"\n\nstatic void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte);\n\n/* copied from allpaths.c */\nstatic void\nset_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)\n{\n\t/* Call the FDW's GetForeignPaths function to generate path(s) */\n\trel->fdwroutine->GetForeignPaths(root, rel, rte->relid);\n}\n\n/* copied from allpaths.c */\nstatic void\nset_tablesample_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)\n{\n\tRelids required_outer;\n\tPath *path;\n\n\t/*\n\t * We don't support pushing join clauses into the quals of a samplescan,\n\t * but it could still have required parameterization due to LATERAL refs\n\t * in its tlist or TABLESAMPLE arguments.\n\t */\n\trequired_outer = rel->lateral_relids;\n\n\t/* Consider sampled scan */\n\tpath = create_samplescan_path(root, rel, required_outer);\n\n\t/*\n\t * If the sampling method does not support repeatable scans, we must avoid\n\t * plans that would scan the rel multiple times.  Ideally, we'd simply\n\t * avoid putting the rel on the inside of a nestloop join; but adding such\n\t * a consideration to the planner seems like a great deal of complication\n\t * to support an uncommon usage of second-rate sampling methods.  Instead,\n\t * if there is a risk that the query might perform an unsafe join, just\n\t * wrap the SampleScan in a Materialize node.  We can check for joins by\n\t * counting the membership of all_baserels (note that this correctly\n\t * counts inheritance trees as single rels).  If we're inside a subquery,\n\t * we can't easily check whether a join might occur in the outer query, so\n\t * just assume one is possible.\n\t *\n\t * GetTsmRoutine is relatively expensive compared to the other tests here,\n\t * so check repeatable_across_scans last, even though that's a bit odd.\n\t */\n\tif ((root->query_level > 1 || bms_membership(root->all_baserels) != BMS_SINGLETON) &&\n\t\t!(GetTsmRoutine(rte->tablesample->tsmhandler)->repeatable_across_scans))\n\t{\n\t\tpath = (Path *) create_material_path(rel, path);\n\t}\n\n\tadd_path(rel, path);\n\n\t/* For the moment, at least, there are no other paths to consider */\n}\n\n/* copied from allpaths.c */\nstatic void\nts_create_plain_partial_paths(PlannerInfo *root, RelOptInfo *rel)\n{\n\tint parallel_workers;\n\n\tparallel_workers =\n\t\tcompute_parallel_worker(rel, rel->pages, -1, max_parallel_workers_per_gather);\n\n\t/* If any limit was set to zero, the user doesn't want a parallel scan. */\n\tif (parallel_workers <= 0)\n\t\treturn;\n\n\t/* Add an unordered partial path based on a parallel sequential scan. */\n\tadd_partial_path(rel, create_seqscan_path(root, rel, NULL, parallel_workers));\n}\n\n/* copied from allpaths.c */\nstatic void\nset_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)\n{\n\tRelids required_outer;\n\n\t/*\n\t * We don't support pushing join clauses into the quals of a seqscan, but\n\t * it could still have required parameterization due to LATERAL refs in\n\t * its tlist.\n\t */\n\trequired_outer = rel->lateral_relids;\n\n\t/* Consider sequential scan */\n\tadd_path(rel, create_seqscan_path(root, rel, required_outer, 0));\n\n\t/* If appropriate, consider parallel sequential scan */\n\tif (rel->consider_parallel && required_outer == NULL)\n\t\tts_create_plain_partial_paths(root, rel);\n\n\t/* Consider index scans */\n\tcreate_index_paths(root, rel);\n\n\t/* Consider TID scans */\n\tcreate_tidscan_paths(root, rel);\n}\n\n/* copied from allpaths.c */\nvoid\nts_set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *parent_rel, Index parent_rt_index,\n\t\t\t\t\t\t   RangeTblEntry *parent_rte)\n{\n\tList *live_childrels = NIL;\n\tListCell *l;\n\n\t/*\n\t * Generate access paths for each member relation, and remember the\n\t * non-dummy children.\n\t */\n\tforeach (l, root->append_rel_list)\n\t{\n\t\tAppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l);\n\n\t\t/* append_rel_list contains all append rels; ignore others */\n\t\tif (appinfo->parent_relid != parent_rt_index)\n\t\t\tcontinue;\n\n\t\t/* Re-locate the child RTE and RelOptInfo */\n\t\tconst int child_rt_index = appinfo->child_relid;\n\t\tRelOptInfo *child_rel = root->simple_rel_array[child_rt_index];\n\n\t\t/*\n\t\t * If set_append_rel_size() decided the parent appendrel was\n\t\t * parallel-unsafe at some point after visiting this child rel, we\n\t\t * need to propagate the unsafety marking down to the child, so that\n\t\t * we don't generate useless partial paths for it.\n\t\t */\n\t\tif (!parent_rel->consider_parallel)\n\t\t\tchild_rel->consider_parallel = false;\n\n\t\t/*\n\t\t * We want to disable planning the index scans on uncompressed chunk\n\t\t * tables of fully compressed chunks. It would be expensive and useless\n\t\t * because the uncompressed chunk tables are empty in this case.\n\t\t *\n\t\t * Note about the 'if' condition: compressed chunk tables expanded from\n\t\t * normal hypertables always have the type TS_REL_CHUNK_STANDALONE. The\n\t\t * direct select from a compressed chunk table would also produce this\n\t\t * type. Another possibility is a direct select from an internal\n\t\t * compression hypertable, where the compressed chunks would have the\n\t\t * type TS_REL_CHUNK_CHILD. We have to filter out all these cases here.\n\t\t *\n\t\t * For standalone chunks or UPDATE/DELETE, we do the same thing in\n\t\t * timescaledb_get_relation_info_hook().\n\t\t */\n\t\tHypertable *ht;\n\t\tTsRelType reltype = ts_classify_relation(root, child_rel, &ht);\n\t\tif (reltype == TS_REL_CHUNK_CHILD && !TS_HYPERTABLE_IS_INTERNAL_COMPRESSION_TABLE(ht))\n\t\t{\n\t\t\tconst Chunk *chunk = ts_planner_chunk_fetch(root, child_rel);\n\n\t\t\t/*\n\t\t\t * This function is called only in tandem with our own hypertable\n\t\t\t * expansion, so the Chunk struct must be initialized already.\n\t\t\t */\n\t\t\tAssert(chunk != NULL);\n\n\t\t\tif (!ts_chunk_is_partial(chunk) && ts_chunk_is_compressed(chunk))\n\t\t\t{\n\t\t\t\tchild_rel->indexlist = NIL;\n\t\t\t}\n\t\t}\n\n\t\t/*\n\t\t * Compute the child's access paths.\n\t\t */\n\t\tRangeTblEntry *child_rte = root->simple_rte_array[child_rt_index];\n\t\tset_rel_pathlist(root, child_rel, child_rt_index, child_rte);\n\n\t\t/*\n\t\t * If child is dummy, ignore it.\n\t\t */\n\t\tif (IS_DUMMY_REL(child_rel))\n\t\t\tcontinue;\n\n\t\t/*\n\t\t * Child is live, so add it to the live_childrels list for use below.\n\t\t */\n\t\tlive_childrels = lappend(live_childrels, child_rel);\n\n\t\t/* If consider startup costs on chunks because we can apply SkipScan, should also consider\n\t\t * startup costs on a hypertable */\n\t\tif (child_rel->consider_startup)\n\t\t\tparent_rel->consider_startup = true;\n\t}\n\n\t/* Add paths to the append relation. */\n\tadd_paths_to_append_rel(root, parent_rel, live_childrels);\n}\n\n/* based on the function in allpaths.c, with the irrelevant branches removed */\nstatic void\nset_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte)\n{\n\tif (IS_DUMMY_REL(rel))\n\t{\n\t\t/* We already proved the relation empty, so nothing more to do */\n\t}\n\telse\n\t{\n\t\tAssert(!rte->inh);\n\t\tswitch (rel->rtekind)\n\t\t{\n\t\t\tcase RTE_RELATION:\n\t\t\t\tif (rte->relkind == RELKIND_FOREIGN_TABLE)\n\t\t\t\t{\n\t\t\t\t\t/* Foreign table */\n\t\t\t\t\tset_foreign_pathlist(root, rel, rte);\n\t\t\t\t}\n\t\t\t\telse if (rte->tablesample != NULL)\n\t\t\t\t{\n\t\t\t\t\t/* Sampled relation */\n\t\t\t\t\tset_tablesample_rel_pathlist(root, rel, rte);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t/* Plain relation */\n\t\t\t\t\tset_plain_rel_pathlist(root, rel, rte);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase RTE_SUBQUERY:\n\t\t\tcase RTE_FUNCTION:\n\t\t\tcase RTE_TABLEFUNC:\n\t\t\tcase RTE_VALUES:\n\t\t\tcase RTE_CTE:\n\t\t\tcase RTE_NAMEDTUPLESTORE:\n\t\t\tcase RTE_RESULT:\n\t\t\tdefault:\n\t\t\t\telog(ERROR, \"unexpected rtekind: %d\", (int) rel->rtekind);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/*\n\t * Allow a plugin to editorialize on the set of Paths for this base\n\t * relation.  It could add new paths (such as CustomPaths) by calling\n\t * add_path(), or add_partial_path() if parallel aware.  It could also\n\t * delete or modify paths added by the core code.\n\t */\n\tif (set_rel_pathlist_hook)\n\t\t(*set_rel_pathlist_hook)(root, rel, rti, rte);\n\n\t/*\n\t * If this is a baserel, we should normally consider gathering any partial\n\t * paths we may have created for it.  We have to do this after calling the\n\t * set_rel_pathlist_hook, else it cannot add partial paths to be included\n\t * here.\n\t *\n\t * However, if this is an inheritance child, skip it.  Otherwise, we could\n\t * end up with a very large number of gather nodes, each trying to grab\n\t * its own pool of workers.  Instead, we'll consider gathering partial\n\t * paths for the parent appendrel.\n\t *\n\t * Also, if this is the topmost scan/join rel (that is, the only baserel),\n\t * we postpone gathering until the final scan/join targetlist is available\n\t * (see grouping_planner).\n\t */\n\tif (rel->reloptkind == RELOPT_BASEREL && bms_membership(root->all_baserels) != BMS_SINGLETON)\n\t\tgenerate_gather_paths(root, rel, false);\n\n\t/* Now find the cheapest of the paths for this rel */\n\tset_cheapest(rel);\n\n#ifdef OPTIMIZER_DEBUG\n\tdebug_print_rel(root, rel);\n#endif\n}\n\n/*\n * set_dummy_rel_pathlist, copied from allpaths.c.\n *\n * This was a public function prior to PG12.\n */\nstatic void\nset_dummy_rel_pathlist(RelOptInfo *rel)\n{\n\t/* Set dummy size estimates --- we leave attr_widths[] as zeroes */\n\trel->rows = 0;\n\trel->reltarget->width = 0;\n\n\t/* Discard any pre-existing paths; no further need for them */\n\trel->pathlist = NIL;\n\trel->partial_pathlist = NIL;\n\n\t/* Set up the dummy path */\n\tadd_path(rel,\n\t\t\t (Path *)\n\t\t\t\t create_append_path(NULL, rel, NIL, NIL, NIL, rel->lateral_relids, 0, false, -1));\n\n\t/*\n\t * We set the cheapest-path fields immediately, just in case they were\n\t * pointing at some discarded path.  This is redundant when we're called\n\t * from set_rel_size(), but not when called from elsewhere, and doing it\n\t * twice is harmless anyway.\n\t */\n\tset_cheapest(rel);\n}\n\n/* copied from allpaths.c */\nstatic void\nset_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)\n{\n\t/*\n\t * The flag has previously been initialized to false, so we can just\n\t * return if it becomes clear that we can't safely set it.\n\t */\n\tAssert(!rel->consider_parallel);\n\n\t/* Don't call this if parallelism is disallowed for the entire query. */\n\tAssert(root->glob->parallelModeOK);\n\n\t/* This should only be called for baserels and appendrel children. */\n\tAssert(IS_SIMPLE_REL(rel));\n\n\t/* Assorted checks based on rtekind. */\n\tswitch (rte->rtekind)\n\t{\n\t\tcase RTE_RELATION:\n\n\t\t\t/*\n\t\t\t * Currently, parallel workers can't access the leader's temporary\n\t\t\t * tables.  We could possibly relax this if the wrote all of its\n\t\t\t * local buffers at the start of the query and made no changes\n\t\t\t * thereafter (maybe we could allow hint bit changes), and if we\n\t\t\t * taught the workers to read them.  Writing a large number of\n\t\t\t * temporary buffers could be expensive, though, and we don't have\n\t\t\t * the rest of the necessary infrastructure right now anyway.  So\n\t\t\t * for now, bail out if we see a temporary table.\n\t\t\t */\n\t\t\tif (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)\n\t\t\t\treturn;\n\n\t\t\t/*\n\t\t\t * Table sampling can be pushed down to workers if the sample\n\t\t\t * function and its arguments are safe.\n\t\t\t */\n\t\t\tif (rte->tablesample != NULL)\n\t\t\t{\n\t\t\t\tchar proparallel = func_parallel(rte->tablesample->tsmhandler);\n\n\t\t\t\tif (proparallel != PROPARALLEL_SAFE)\n\t\t\t\t\treturn;\n\t\t\t\tif (!is_parallel_safe(root, (Node *) rte->tablesample->args))\n\t\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * Ask FDWs whether they can support performing a ForeignScan\n\t\t\t * within a worker.  Most often, the answer will be no.  For\n\t\t\t * example, if the nature of the FDW is such that it opens a TCP\n\t\t\t * connection with a remote server, each parallel worker would end\n\t\t\t * up with a separate connection, and these connections might not\n\t\t\t * be appropriately coordinated between workers and the leader.\n\t\t\t */\n\t\t\tif (rte->relkind == RELKIND_FOREIGN_TABLE)\n\t\t\t{\n\t\t\t\tAssert(rel->fdwroutine);\n\t\t\t\tif (!rel->fdwroutine->IsForeignScanParallelSafe)\n\t\t\t\t\treturn;\n\t\t\t\tif (!rel->fdwroutine->IsForeignScanParallelSafe(root, rel, rte))\n\t\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * There are additional considerations for appendrels, which we'll\n\t\t\t * deal with in set_append_rel_size and set_append_rel_pathlist.\n\t\t\t * For now, just set consider_parallel based on the rel's own\n\t\t\t * quals and targetlist.\n\t\t\t */\n\t\t\tbreak;\n\n\t\tcase RTE_SUBQUERY:\n\n\t\t\t/*\n\t\t\t * There's no intrinsic problem with scanning a subquery-in-FROM\n\t\t\t * (as distinct from a SubPlan or InitPlan) in a parallel worker.\n\t\t\t * If the subquery doesn't happen to have any parallel-safe paths,\n\t\t\t * then flagging it as consider_parallel won't change anything,\n\t\t\t * but that's true for plain tables, too.  We must set\n\t\t\t * consider_parallel based on the rel's own quals and targetlist,\n\t\t\t * so that if a subquery path is parallel-safe but the quals and\n\t\t\t * projection we're sticking onto it are not, we correctly mark\n\t\t\t * the SubqueryScanPath as not parallel-safe.  (Note that\n\t\t\t * set_subquery_pathlist() might push some of these quals down\n\t\t\t * into the subquery itself, but that doesn't change anything.)\n\t\t\t *\n\t\t\t * We can't push sub-select containing LIMIT/OFFSET to workers as\n\t\t\t * there is no guarantee that the row order will be fully\n\t\t\t * deterministic, and applying LIMIT/OFFSET will lead to\n\t\t\t * inconsistent results at the top-level.  (In some cases, where\n\t\t\t * the result is ordered, we could relax this restriction.  But it\n\t\t\t * doesn't currently seem worth expending extra effort to do so.)\n\t\t\t */\n\t\t\t{\n\t\t\t\tQuery *subquery = castNode(Query, rte->subquery);\n\n\t\t\t\tif (limit_needed(subquery))\n\t\t\t\t\treturn;\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase RTE_JOIN:\n\t\t\t/* Shouldn't happen; we're only considering baserels here. */\n\t\t\tAssert(false);\n\t\t\treturn;\n\n\t\tcase RTE_FUNCTION:\n\t\t\t/* Check for parallel-restricted functions. */\n\t\t\tif (!is_parallel_safe(root, (Node *) rte->functions))\n\t\t\t\treturn;\n\t\t\tbreak;\n\n\t\tcase RTE_TABLEFUNC:\n\t\t\t/* not parallel safe */\n\t\t\treturn;\n\n\t\tcase RTE_VALUES:\n\t\t\t/* Check for parallel-restricted functions. */\n\t\t\tif (!is_parallel_safe(root, (Node *) rte->values_lists))\n\t\t\t\treturn;\n\t\t\tbreak;\n\n\t\tcase RTE_CTE:\n\n\t\t\t/*\n\t\t\t * CTE tuplestores aren't shared among parallel workers, so we\n\t\t\t * force all CTE scans to happen in the leader.  Also, populating\n\t\t\t * the CTE would require executing a subplan that's not available\n\t\t\t * in the worker, might be parallel-restricted, and must get\n\t\t\t * executed only once.\n\t\t\t */\n\t\t\treturn;\n\n\t\tcase RTE_NAMEDTUPLESTORE:\n\n\t\t\t/*\n\t\t\t * tuplestore cannot be shared, at least without more\n\t\t\t * infrastructure to support that.\n\t\t\t */\n\t\t\treturn;\n\t\tcase RTE_RESULT:\n\t\t\t/* RESULT RTEs, in themselves, are no problem. */\n\t\t\tbreak;\n#if PG18_GE\n\t\tcase RTE_GROUP:\n\t\t\t/* Shouldn't happen; we're only considering baserels here. */\n\t\t\tAssert(false);\n\t\t\treturn;\n#endif\n\t}\n\n\t/*\n\t * If there's anything in baserestrictinfo that's parallel-restricted, we\n\t * give up on parallelizing access to this relation.  We could consider\n\t * instead postponing application of the restricted quals until we're\n\t * above all the parallelism in the plan tree, but it's not clear that\n\t * that would be a win in very many cases, and it might be tricky to make\n\t * outer join clauses work correctly.  It would likely break equivalence\n\t * classes, too.\n\t */\n\tif (!is_parallel_safe(root, (Node *) rel->baserestrictinfo))\n\t\treturn;\n\n\t/*\n\t * Likewise, if the relation's outputs are not parallel-safe, give up.\n\t * (Usually, they're just Vars, but sometimes they're not.)\n\t */\n\tif (!is_parallel_safe(root, (Node *) rel->reltarget->exprs))\n\t\treturn;\n\n\t/* We have a winner. */\n\trel->consider_parallel = true;\n}\n\n/* copied from allpaths.c, REL_18_3 */\nstatic void\nts_set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte)\n{\n\tint\t\t\tparentRTindex = rti;\n\tbool\t\thas_live_children;\n\tdouble\t\tparent_tuples;\n\tdouble\t\tparent_rows;\n\tdouble\t\tparent_size;\n\tdouble\t   *parent_attrsizes;\n\tint\t\t\tnattrs;\n\tListCell   *l;\n\n\t/* Guard against stack overflow due to overly deep inheritance tree. */\n\tcheck_stack_depth();\n\n\tAssert(IS_SIMPLE_REL(rel));\n\n\t/*\n\t * If this is a partitioned baserel, set the consider_partitionwise_join\n\t * flag; currently, we only consider partitionwise joins with the baserel\n\t * if its targetlist doesn't contain a whole-row Var.\n\t */\n\tif (enable_partitionwise_join &&\n\t\trel->reloptkind == RELOPT_BASEREL &&\n\t\trte->relkind == RELKIND_PARTITIONED_TABLE &&\n#if PG16_GE\n\t\tbms_is_empty(rel->attr_needed[InvalidAttrNumber - rel->min_attr]))\n#else\n\t\trel->attr_needed[InvalidAttrNumber - rel->min_attr] == NULL)\n#endif\n\t\trel->consider_partitionwise_join = true;\n\n\t/*\n\t * Initialize to compute size estimates for whole append relation.\n\t *\n\t * We handle tuples estimates by setting \"tuples\" to the total number of\n\t * tuples accumulated from each live child, rather than using \"rows\".\n\t * Although an appendrel itself doesn't directly enforce any quals, its\n\t * child relations may.  Therefore, setting \"tuples\" equal to \"rows\" for\n\t * an appendrel isn't always appropriate, and can lead to inaccurate cost\n\t * estimates.  For example, when estimating the number of distinct values\n\t * from an appendrel, we would be unable to adjust the estimate based on\n\t * the restriction selectivity (see estimate_num_groups).\n\t *\n\t * We handle width estimates by weighting the widths of different child\n\t * rels proportionally to their number of rows.  This is sensible because\n\t * the use of width estimates is mainly to compute the total relation\n\t * \"footprint\" if we have to sort or hash it.  To do this, we sum the\n\t * total equivalent size (in \"double\" arithmetic) and then divide by the\n\t * total rowcount estimate.  This is done separately for the total rel\n\t * width and each attribute.\n\t *\n\t * Note: if you consider changing this logic, beware that child rels could\n\t * have zero rows and/or width, if they were excluded by constraints.\n\t */\n\thas_live_children = false;\n\tparent_tuples = 0;\n\tparent_rows = 0;\n\tparent_size = 0;\n\tnattrs = rel->max_attr - rel->min_attr + 1;\n\tparent_attrsizes = (double *) palloc0(nattrs * sizeof(double));\n\n\tforeach(l, root->append_rel_list)\n\t{\n\t\tAppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l);\n\t\tint\t\t\tchildRTindex;\n\t\tRangeTblEntry *childRTE;\n\t\tRelOptInfo *childrel;\n#if PG16_GE\n\t\tList *childrinfos;\n\t\tListCell   *lc;\n#endif\n\t\tListCell   *parentvars;\n\t\tListCell   *childvars;\n\n\t\t/* append_rel_list contains all append rels; ignore others */\n\t\tif (appinfo->parent_relid != (Index) parentRTindex)\n\t\t\tcontinue;\n\n\t\tchildRTindex = appinfo->child_relid;\n\t\tchildRTE = root->simple_rte_array[childRTindex];\n\n\t\t/*\n\t\t * The child rel's RelOptInfo was already created during\n\t\t * add_other_rels_to_query.\n\t\t */\n\t\tchildrel = find_base_rel(root, childRTindex);\n\t\tAssert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);\n\n\t\t/* We may have already proven the child to be dummy. */\n\t\tif (IS_DUMMY_REL(childrel))\n\t\t\tcontinue;\n\n\t\t/*\n\t\t * We have to copy the parent's targetlist and quals to the child,\n\t\t * with appropriate substitution of variables.  However, the\n\t\t * baserestrictinfo quals were already copied/substituted when the\n\t\t * child RelOptInfo was built.  So we don't need any additional setup\n\t\t * before applying constraint exclusion.\n\t\t */\n\t\tif (relation_excluded_by_constraints(root, childrel, childRTE))\n\t\t{\n\t\t\t/*\n\t\t\t * This child need not be scanned, so we can omit it from the\n\t\t\t * appendrel.\n\t\t\t */\n\t\t\tset_dummy_rel_pathlist(childrel);\n\t\t\tcontinue;\n\t\t}\n\n\t\t/*\n\t\t * Constraint exclusion failed, so copy the parent's join quals and\n\t\t * targetlist to the child, with appropriate variable substitutions.\n\t\t *\n\t\t * We skip join quals that came from above outer joins that can null\n\t\t * this rel, since they would be of no value while generating paths\n\t\t * for the child.  This saves some effort while processing the child\n\t\t * rel, and it also avoids an implementation restriction in\n\t\t * adjust_appendrel_attrs (it can't apply nullingrels to a non-Var).\n\t\t */\n#if PG16_GE\n\t\tchildrinfos = NIL;\n\t\tforeach(lc, rel->joininfo)\n\t\t{\n\t\t\tRestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);\n\n\t\t\tif (!bms_overlap(rinfo->clause_relids, rel->nulling_relids))\n\t\t\t\tchildrinfos = lappend(childrinfos,\n\t\t\t\t\t\t\t\t\t  adjust_appendrel_attrs(root,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t (Node *) rinfo,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t 1, &appinfo));\n\t\t}\n\t\tchildrel->joininfo = childrinfos;\n#else\n\t\tchildrel->joininfo =\n\t\t\t(List *) adjust_appendrel_attrs(root, (Node *) rel->joininfo, 1, &appinfo);\n#endif\n\n\t\t/*\n\t\t * Now for the child's targetlist.\n\t\t *\n\t\t * NB: the resulting childrel->reltarget->exprs may contain arbitrary\n\t\t * expressions, which otherwise would not occur in a rel's targetlist.\n\t\t * Code that might be looking at an appendrel child must cope with\n\t\t * such.  (Normally, a rel's targetlist would only include Vars and\n\t\t * PlaceHolderVars.)  XXX we do not bother to update the cost or width\n\t\t * fields of childrel->reltarget; not clear if that would be useful.\n\t\t */\n\t\tchildrel->reltarget->exprs = (List *)\n\t\t\tadjust_appendrel_attrs(root,\n\t\t\t\t\t\t\t\t   (Node *) rel->reltarget->exprs,\n\t\t\t\t\t\t\t\t   1, &appinfo);\n\n\t\t/*\n\t\t * We have to make child entries in the EquivalenceClass data\n\t\t * structures as well.  This is needed either if the parent\n\t\t * participates in some eclass joins (because we will want to consider\n\t\t * inner-indexscan joins on the individual children) or if the parent\n\t\t * has useful pathkeys (because we should try to build MergeAppend\n\t\t * paths that produce those sort orderings).\n\t\t */\n\t\tif (rel->has_eclass_joins || has_useful_pathkeys(root, rel))\n\t\t\tadd_child_rel_equivalences(root, appinfo, rel, childrel);\n\t\tchildrel->has_eclass_joins = rel->has_eclass_joins;\n\n\t\t/*\n\t\t * Note: we could compute appropriate attr_needed data for the child's\n\t\t * variables, by transforming the parent's attr_needed through the\n\t\t * translated_vars mapping.  However, currently there's no need\n\t\t * because attr_needed is only examined for base relations not\n\t\t * otherrels.  So we just leave the child's attr_needed empty.\n\t\t */\n\n\t\t/*\n\t\t * If we consider partitionwise joins with the parent rel, do the same\n\t\t * for partitioned child rels.\n\t\t *\n\t\t * Note: here we abuse the consider_partitionwise_join flag by setting\n\t\t * it for child rels that are not themselves partitioned.  We do so to\n\t\t * tell try_partitionwise_join() that the child rel is sufficiently\n\t\t * valid to be used as a per-partition input, even if it later gets\n\t\t * proven to be dummy.  (It's not usable until we've set up the\n\t\t * reltarget and EC entries, which we just did.)\n\t\t */\n\t\tif (rel->consider_partitionwise_join)\n\t\t\tchildrel->consider_partitionwise_join = true;\n\n\t\t/*\n\t\t * If parallelism is allowable for this query in general, see whether\n\t\t * it's allowable for this childrel in particular.  But if we've\n\t\t * already decided the appendrel is not parallel-safe as a whole,\n\t\t * there's no point in considering parallelism for this child.  For\n\t\t * consistency, do this before calling set_rel_size() for the child.\n\t\t */\n\t\tif (root->glob->parallelModeOK && rel->consider_parallel)\n\t\t\tset_rel_consider_parallel(root, childrel, childRTE);\n\n\t\t/*\n\t\t * Compute the child's size.\n\t\t */\n\t\tts_set_rel_size(root, childrel, childRTindex, childRTE);\n\n\t\t/*\n\t\t * It is possible that constraint exclusion detected a contradiction\n\t\t * within a child subquery, even though we didn't prove one above. If\n\t\t * so, we can skip this child.\n\t\t */\n\t\tif (IS_DUMMY_REL(childrel))\n\t\t\tcontinue;\n\n\t\t/* We have at least one live child. */\n\t\thas_live_children = true;\n\n\t\t/*\n\t\t * If any live child is not parallel-safe, treat the whole appendrel\n\t\t * as not parallel-safe.  In future we might be able to generate plans\n\t\t * in which some children are farmed out to workers while others are\n\t\t * not; but we don't have that today, so it's a waste to consider\n\t\t * partial paths anywhere in the appendrel unless it's all safe.\n\t\t * (Child rels visited before this one will be unmarked in\n\t\t * set_append_rel_pathlist().)\n\t\t */\n\t\tif (!childrel->consider_parallel)\n\t\t\trel->consider_parallel = false;\n\n\t\t/*\n\t\t * Accumulate size information from each live child.\n\t\t */\n\t\tAssert(childrel->rows > 0);\n\n\t\tparent_tuples += childrel->tuples;\n\t\tparent_rows += childrel->rows;\n\t\tparent_size += childrel->reltarget->width * childrel->rows;\n\n\t\t/*\n\t\t * Accumulate per-column estimates too.  We need not do anything for\n\t\t * PlaceHolderVars in the parent list.  If child expression isn't a\n\t\t * Var, or we didn't record a width estimate for it, we have to fall\n\t\t * back on a datatype-based estimate.\n\t\t *\n\t\t * By construction, child's targetlist is 1-to-1 with parent's.\n\t\t */\n\t\tforboth(parentvars, rel->reltarget->exprs,\n\t\t\t\tchildvars, childrel->reltarget->exprs)\n\t\t{\n\t\t\tVar\t\t   *parentvar = (Var *) lfirst(parentvars);\n\t\t\tNode\t   *childvar = (Node *) lfirst(childvars);\n\n\t\t\tif (IsA(parentvar, Var) && parentvar->varno == parentRTindex)\n\t\t\t{\n\t\t\t\tint\t\t\tpndx = parentvar->varattno - rel->min_attr;\n\t\t\t\tint32\t\tchild_width = 0;\n\n\t\t\t\tif (IsA(childvar, Var) &&\n\t\t\t\t\t(Index) ((Var *) childvar)->varno == childrel->relid)\n\t\t\t\t{\n\t\t\t\t\tint\t\t\tcndx = ((Var *) childvar)->varattno - childrel->min_attr;\n\n\t\t\t\t\tchild_width = childrel->attr_widths[cndx];\n\t\t\t\t}\n\t\t\t\tif (child_width <= 0)\n\t\t\t\t\tchild_width = get_typavgwidth(exprType(childvar),\n\t\t\t\t\t\t\t\t\t\t\t\t  exprTypmod(childvar));\n\t\t\t\tAssert(child_width > 0);\n\t\t\t\tparent_attrsizes[pndx] += child_width * childrel->rows;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (has_live_children)\n\t{\n\t\t/*\n\t\t * Save the finished size estimates.\n\t\t */\n\t\tint\t\t\ti;\n\n\t\tAssert(parent_rows > 0);\n\t\trel->tuples = parent_tuples;\n\t\trel->rows = parent_rows;\n\t\trel->reltarget->width = rint(parent_size / parent_rows);\n\t\tfor (i = 0; i < nattrs; i++)\n\t\t\trel->attr_widths[i] = rint(parent_attrsizes[i] / parent_rows);\n\n\t\t/*\n\t\t * Note that we leave rel->pages as zero; this is important to avoid\n\t\t * double-counting the appendrel tree in total_table_pages.\n\t\t */\n\t}\n\telse\n\t{\n\t\t/*\n\t\t * All children were excluded by constraints, so mark the whole\n\t\t * appendrel dummy.  We must do this in this phase so that the rel's\n\t\t * dummy-ness is visible when we generate paths for other rels.\n\t\t */\n\t\tset_dummy_rel_pathlist(rel);\n\t}\n\n\tpfree(parent_attrsizes);\n}\n\n/* copied from allpaths.c */\nstatic void\nset_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)\n{\n\t/* Mark rel with estimated output rows, width, etc */\n\tset_foreign_size_estimates(root, rel);\n\n\t/* Let FDW adjust the size estimates, if it can */\n\trel->fdwroutine->GetForeignRelSize(root, rel, rte->relid);\n\n\t/* ... but do not let it set the rows estimate to zero */\n\trel->rows = clamp_row_est(rel->rows);\n}\n\n/* copied from allpaths.c */\nstatic void\nset_tablesample_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)\n{\n\tTableSampleClause *tsc = rte->tablesample;\n\tTsmRoutine *tsm;\n\tBlockNumber pages;\n\tdouble tuples;\n\n\t/*\n\t * Test any partial indexes of rel for applicability.  We must do this\n\t * first since partial unique indexes can affect size estimates.\n\t */\n\tcheck_index_predicates(root, rel);\n\n\t/*\n\t * Call the sampling method's estimation function to estimate the number\n\t * of pages it will read and the number of tuples it will return.  (Note:\n\t * we assume the function returns sane values.)\n\t */\n\ttsm = GetTsmRoutine(tsc->tsmhandler);\n\ttsm->SampleScanGetSampleSize(root, rel, tsc->args, &pages, &tuples);\n\n\t/*\n\t * For the moment, because we will only consider a SampleScan path for the\n\t * rel, it's okay to just overwrite the pages and tuples estimates for the\n\t * whole relation.  If we ever consider multiple path types for sampled\n\t * rels, we'll need more complication.\n\t */\n\trel->pages = pages;\n\trel->tuples = tuples;\n\n\t/* Mark rel with estimated output rows, width, etc */\n\tset_baserel_size_estimates(root, rel);\n}\n\n/* copied from allpaths.c */\nstatic void\nset_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)\n{\n\t/*\n\t * Test any partial indexes of rel for applicability.  We must do this\n\t * first since partial unique indexes can affect size estimates.\n\t */\n\tcheck_index_predicates(root, rel);\n\n\t/* Mark rel with estimated output rows, width, etc */\n\tset_baserel_size_estimates(root, rel);\n}\n\n/* extracted from the same function in allpaths.c\n * assumes that the root table is either excluded by constraints, or is an\n * inheritance base table, and that chunks are regular tables\n */\nvoid\nts_set_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte)\n{\n\tif (rel->reloptkind == RELOPT_BASEREL && relation_excluded_by_constraints(root, rel, rte))\n\t{\n\t\t/*\n\t\t * We proved we don't need to scan the rel via constraint exclusion,\n\t\t * so set up a single dummy path for it.  Here we only check this for\n\t\t * regular baserels; if it's an otherrel, CE was already checked in\n\t\t * set_append_rel_size().\n\t\t *\n\t\t * In this case, we go ahead and set up the relation's path right away\n\t\t * instead of leaving it for set_rel_pathlist to do.  This is because\n\t\t * we don't have a convention for marking a rel as dummy except by\n\t\t * assigning a dummy path to it.\n\t\t */\n\t\tset_dummy_rel_pathlist(rel);\n\t}\n\telse if (rte->inh)\n\t{\n\t\t/* It's an \"append relation\", process accordingly */\n\t\tts_set_append_rel_size(root, rel, rti, rte);\n\t}\n\telse\n\t{\n\t\tswitch (rel->rtekind)\n\t\t{\n\t\t\tcase RTE_RELATION:\n\t\t\t\tif (rte->relkind == RELKIND_FOREIGN_TABLE)\n\t\t\t\t{\n\t\t\t\t\t/* Foreign table */\n\t\t\t\t\tset_foreign_size(root, rel, rte);\n\t\t\t\t}\n\t\t\t\telse if (rte->relkind == RELKIND_PARTITIONED_TABLE)\n\t\t\t\t{\n\t\t\t\t\t/*\n\t\t\t\t\t * We could get here if asked to scan a partitioned table\n\t\t\t\t\t * with ONLY.  In that case we shouldn't scan any of the\n\t\t\t\t\t * partitions, so mark it as a dummy rel.\n\t\t\t\t\t */\n\t\t\t\t\tset_dummy_rel_pathlist(rel);\n\t\t\t\t}\n\t\t\t\telse if (rte->tablesample != NULL)\n\t\t\t\t{\n\t\t\t\t\t/* Sampled relation */\n\t\t\t\t\tset_tablesample_rel_size(root, rel, rte);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t/* Plain relation */\n\t\t\t\t\tset_plain_rel_size(root, rel, rte);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase RTE_SUBQUERY:\n\t\t\tcase RTE_FUNCTION:\n\t\t\tcase RTE_TABLEFUNC:\n\t\t\tcase RTE_VALUES:\n\t\t\tcase RTE_CTE:\n\t\t\tcase RTE_NAMEDTUPLESTORE:\n\t\t\tcase RTE_RESULT:\n\t\t\tdefault:\n\t\t\t\telog(ERROR, \"unexpected rtekind: %d\", (int) rel->rtekind);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/*\n\t * We insist that all non-dummy rels have a nonzero rowcount estimate.\n\t */\n\tAssert(rel->rows > 0 || IS_DUMMY_REL(rel));\n}\n"
  },
  {
    "path": "src/import/allpaths.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <nodes/pathnodes.h>\n\n#include \"export.h\"\n\nextern void ts_set_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte);\nextern void ts_set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *parent_rel,\n\t\t\t\t\t\t\t\t\t   Index parent_rt_index, RangeTblEntry *parent_rte);\n"
  },
  {
    "path": "src/import/heapswap.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n/*\n * This file contains source code that was copied and/or modified from\n * the PostgreSQL database, which is licensed under the open-source\n * PostgreSQL License. Please see the NOTICE at the top level\n * directory for a copy of the PostgreSQL License.\n */\n#include <postgres.h>\n#include <access/htup.h>\n#include <access/multixact.h>\n#include <access/relation.h>\n#include <access/table.h>\n#include <access/toast_internals.h>\n#include <access/xact.h>\n#include <catalog/catalog.h>\n#include <catalog/dependency.h>\n#include <catalog/heap.h>\n#include <catalog/index.h>\n#include <catalog/indexing.h>\n#include <catalog/objectaccess.h>\n#include <catalog/pg_am.h>\n#include <catalog/pg_class.h>\n#include <commands/defrem.h>\n#include <commands/progress.h>\n#include <commands/tablecmds.h>\n#include <executor/spi.h>\n#include <nodes/makefuncs.h>\n#include <nodes/value.h>\n#include <storage/lmgr.h>\n#include <storage/lockdefs.h>\n#include <utils/backend_progress.h>\n#include <utils/inval.h>\n#include <utils/lsyscache.h>\n#include <utils/rel.h>\n#include <utils/relcache.h>\n#include <utils/relmapper.h>\n#include <utils/snapmgr.h>\n#include <utils/syscache.h>\n\n#include \"compat/compat.h\"\n#include \"heapswap.h\"\n\n#if PG16_LT\ntypedef Oid RelFileNumber;\n#define RelFileNumberIsValid OidIsValid\n#define RelationMapOidToFilenumber RelationMapOidToFilenode\n#define rd_newRelfilelocatorSubid rd_newRelfilenodeSubid\n#define rd_firstRelfilelocatorSubid rd_firstRelfilenodeSubid\n#define RelationAssumeNewRelfilelocator RelationAssumeNewRelfilenode\n#endif\n\n/**\n * The code in this file is imported from PostgreSQL and slightly modified to:\n *\n * 1. Make swap_relation_files() a public function.\n * 2. Optionally build indexes in finish_heap_swap().\n *\n * The above changes are needed to decouple index building from heap swaps, needed for reorder and\n * merge chunks.\n */\n\n/*\n * Swap the physical files of two given relations.\n *\n * We swap the physical identity (reltablespace, relfilenumber) while keeping\n * the same logical identities of the two relations.  relpersistence is also\n * swapped, which is critical since it determines where buffers live for each\n * relation.\n *\n * We can swap associated TOAST data in either of two ways: recursively swap\n * the physical content of the toast tables (and their indexes), or swap the\n * TOAST links in the given relations' pg_class entries.  The former is needed\n * to manage rewrites of shared catalogs (where we cannot change the pg_class\n * links) while the latter is the only way to handle cases in which a toast\n * table is added or removed altogether.\n *\n * Additionally, the first relation is marked with relfrozenxid set to\n * frozenXid.  It seems a bit ugly to have this here, but the caller would\n * have to do it anyway, so having it here saves a heap_update.  Note: in\n * the swap-toast-links case, we assume we don't need to change the toast\n * table's relfrozenxid: the new version of the toast table should already\n * have relfrozenxid set to RecentXmin, which is good enough.\n *\n * Lastly, if r2 and its toast table and toast index (if any) are mapped,\n * their OIDs are emitted into mapped_tables[].  This is hacky but beats\n * having to look the information up again later in finish_heap_swap.\n */\nvoid\nts_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class, bool swap_toast_by_content,\n\t\t\t\t\t   bool is_internal, TransactionId frozenXid, MultiXactId cutoffMulti,\n\t\t\t\t\t   Oid *mapped_tables)\n{\n\tRelation relRelation;\n\tHeapTuple reltup1, reltup2;\n\tForm_pg_class relform1, relform2;\n\tRelFileNumber relfilenumber1, relfilenumber2;\n\tRelFileNumber swaptemp;\n\n\tchar swptmpchr;\n\tOid relam1, relam2;\n\n\t/* We need writable copies of both pg_class tuples. */\n\trelRelation = table_open(RelationRelationId, RowExclusiveLock);\n\n\treltup1 = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(r1));\n\tif (!HeapTupleIsValid(reltup1))\n\t\telog(ERROR, \"cache lookup failed for relation %u\", r1);\n\trelform1 = (Form_pg_class) GETSTRUCT(reltup1);\n\n\treltup2 = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(r2));\n\tif (!HeapTupleIsValid(reltup2))\n\t\telog(ERROR, \"cache lookup failed for relation %u\", r2);\n\trelform2 = (Form_pg_class) GETSTRUCT(reltup2);\n\n\trelfilenumber1 = relform1->relfilenode;\n\trelfilenumber2 = relform2->relfilenode;\n\trelam1 = relform1->relam;\n\trelam2 = relform2->relam;\n\n\tif (RelFileNumberIsValid(relfilenumber1) && RelFileNumberIsValid(relfilenumber2))\n\t{\n\t\t/*\n\t\t * Normal non-mapped relations: swap relfilenumbers, reltablespaces,\n\t\t * relpersistence\n\t\t */\n\t\tAssert(!target_is_pg_class);\n\n\t\tswaptemp = relform1->relfilenode;\n\t\trelform1->relfilenode = relform2->relfilenode;\n\t\trelform2->relfilenode = swaptemp;\n\n\t\tswaptemp = relform1->reltablespace;\n\t\trelform1->reltablespace = relform2->reltablespace;\n\t\trelform2->reltablespace = swaptemp;\n\n\t\tswaptemp = relform1->relam;\n\t\trelform1->relam = relform2->relam;\n\t\trelform2->relam = swaptemp;\n\n\t\tswptmpchr = relform1->relpersistence;\n\t\trelform1->relpersistence = relform2->relpersistence;\n\t\trelform2->relpersistence = swptmpchr;\n\n\t\t/* Also swap toast links, if we're swapping by links */\n\t\tif (!swap_toast_by_content)\n\t\t{\n\t\t\tswaptemp = relform1->reltoastrelid;\n\t\t\trelform1->reltoastrelid = relform2->reltoastrelid;\n\t\t\trelform2->reltoastrelid = swaptemp;\n\t\t}\n\t}\n\telse\n\t{\n\t\t/*\n\t\t * Mapped-relation case.  Here we have to swap the relation mappings\n\t\t * instead of modifying the pg_class columns.  Both must be mapped.\n\t\t */\n\t\tif (RelFileNumberIsValid(relfilenumber1) || RelFileNumberIsValid(relfilenumber2))\n\t\t\telog(ERROR,\n\t\t\t\t \"cannot swap mapped relation \\\"%s\\\" with non-mapped relation\",\n\t\t\t\t NameStr(relform1->relname));\n\n\t\t/*\n\t\t * We can't change the tablespace nor persistence of a mapped rel, and\n\t\t * we can't handle toast link swapping for one either, because we must\n\t\t * not apply any critical changes to its pg_class row.  These cases\n\t\t * should be prevented by upstream permissions tests, so these checks\n\t\t * are non-user-facing emergency backstop.\n\t\t */\n\t\tif (relform1->reltablespace != relform2->reltablespace)\n\t\t\telog(ERROR,\n\t\t\t\t \"cannot change tablespace of mapped relation \\\"%s\\\"\",\n\t\t\t\t NameStr(relform1->relname));\n\t\tif (relform1->relpersistence != relform2->relpersistence)\n\t\t\telog(ERROR,\n\t\t\t\t \"cannot change persistence of mapped relation \\\"%s\\\"\",\n\t\t\t\t NameStr(relform1->relname));\n\t\tif (relform1->relam != relform2->relam)\n\t\t\telog(ERROR,\n\t\t\t\t \"cannot change access method of mapped relation \\\"%s\\\"\",\n\t\t\t\t NameStr(relform1->relname));\n\t\tif (!swap_toast_by_content && (relform1->reltoastrelid || relform2->reltoastrelid))\n\t\t\telog(ERROR,\n\t\t\t\t \"cannot swap toast by links for mapped relation \\\"%s\\\"\",\n\t\t\t\t NameStr(relform1->relname));\n\n\t\t/*\n\t\t * Fetch the mappings --- shouldn't fail, but be paranoid\n\t\t */\n\t\trelfilenumber1 = RelationMapOidToFilenumber(r1, relform1->relisshared);\n\t\tif (!RelFileNumberIsValid(relfilenumber1))\n\t\t\telog(ERROR,\n\t\t\t\t \"could not find relation mapping for relation \\\"%s\\\", OID %u\",\n\t\t\t\t NameStr(relform1->relname),\n\t\t\t\t r1);\n\t\trelfilenumber2 = RelationMapOidToFilenumber(r2, relform2->relisshared);\n\t\tif (!RelFileNumberIsValid(relfilenumber2))\n\t\t\telog(ERROR,\n\t\t\t\t \"could not find relation mapping for relation \\\"%s\\\", OID %u\",\n\t\t\t\t NameStr(relform2->relname),\n\t\t\t\t r2);\n\n\t\t/*\n\t\t * Send replacement mappings to relmapper.  Note these won't actually\n\t\t * take effect until CommandCounterIncrement.\n\t\t */\n\t\tRelationMapUpdateMap(r1, relfilenumber2, relform1->relisshared, false);\n\t\tRelationMapUpdateMap(r2, relfilenumber1, relform2->relisshared, false);\n\n\t\t/* Pass OIDs of mapped r2 tables back to caller */\n\t\t*mapped_tables++ = r2;\n\t}\n\n\t/*\n\t * Recognize that rel1's relfilenumber (swapped from rel2) is new in this\n\t * subtransaction. The rel2 storage (swapped from rel1) may or may not be\n\t * new.\n\t */\n\t{\n\t\tRelation rel1, rel2;\n\n\t\trel1 = relation_open(r1, NoLock);\n\t\trel2 = relation_open(r2, NoLock);\n\t\trel2->rd_createSubid = rel1->rd_createSubid;\n\t\trel2->rd_newRelfilelocatorSubid = rel1->rd_newRelfilelocatorSubid;\n\t\trel2->rd_firstRelfilelocatorSubid = rel1->rd_firstRelfilelocatorSubid;\n\t\tRelationAssumeNewRelfilelocator(rel1);\n\t\trelation_close(rel1, NoLock);\n\t\trelation_close(rel2, NoLock);\n\t}\n\n\t/*\n\t * In the case of a shared catalog, these next few steps will only affect\n\t * our own database's pg_class row; but that's okay, because they are all\n\t * noncritical updates.  That's also an important fact for the case of a\n\t * mapped catalog, because it's possible that we'll commit the map change\n\t * and then fail to commit the pg_class update.\n\t */\n\n\t/* set rel1's frozen Xid and minimum MultiXid */\n\tif (relform1->relkind != RELKIND_INDEX)\n\t{\n\t\tAssert(!TransactionIdIsValid(frozenXid) || TransactionIdIsNormal(frozenXid));\n\t\trelform1->relfrozenxid = frozenXid;\n\t\trelform1->relminmxid = cutoffMulti;\n\t}\n\n\t/* swap size statistics too, since new rel has freshly-updated stats */\n\t{\n\t\tint32 swap_pages;\n\t\tfloat4 swap_tuples;\n\t\tint32 swap_allvisible;\n\n\t\tswap_pages = relform1->relpages;\n\t\trelform1->relpages = relform2->relpages;\n\t\trelform2->relpages = swap_pages;\n\n\t\tswap_tuples = relform1->reltuples;\n\t\trelform1->reltuples = relform2->reltuples;\n\t\trelform2->reltuples = swap_tuples;\n\n\t\tswap_allvisible = relform1->relallvisible;\n\t\trelform1->relallvisible = relform2->relallvisible;\n\t\trelform2->relallvisible = swap_allvisible;\n\t}\n\n\t/*\n\t * Update the tuples in pg_class --- unless the target relation of the\n\t * swap is pg_class itself.  In that case, there is zero point in making\n\t * changes because we'd be updating the old data that we're about to throw\n\t * away.  Because the real work being done here for a mapped relation is\n\t * just to change the relation map settings, it's all right to not update\n\t * the pg_class rows in this case. The most important changes will instead\n\t * performed later, in finish_heap_swap() itself.\n\t */\n\tif (!target_is_pg_class)\n\t{\n\t\tCatalogIndexState indstate;\n\n\t\tindstate = CatalogOpenIndexes(relRelation);\n\t\tCatalogTupleUpdateWithInfo(relRelation, &reltup1->t_self, reltup1, indstate);\n\t\tCatalogTupleUpdateWithInfo(relRelation, &reltup2->t_self, reltup2, indstate);\n\t\tCatalogCloseIndexes(indstate);\n\t}\n\telse\n\t{\n\t\t/* no update ... but we do still need relcache inval */\n\t\tCacheInvalidateRelcacheByTuple(reltup1);\n\t\tCacheInvalidateRelcacheByTuple(reltup2);\n\t}\n\n\t/*\n\t * Now that pg_class has been updated with its relevant information for\n\t * the swap, update the dependency of the relations to point to their new\n\t * table AM, if it has changed.\n\t */\n\tif (relam1 != relam2)\n\t{\n\t\tif (changeDependencyFor(RelationRelationId, r1, AccessMethodRelationId, relam1, relam2) !=\n\t\t\t1)\n\t\t\telog(ERROR,\n\t\t\t\t \"could not change access method dependency for relation \\\"%s.%s\\\"\",\n\t\t\t\t get_namespace_name(get_rel_namespace(r1)),\n\t\t\t\t get_rel_name(r1));\n\t\tif (changeDependencyFor(RelationRelationId, r2, AccessMethodRelationId, relam2, relam1) !=\n\t\t\t1)\n\t\t\telog(ERROR,\n\t\t\t\t \"could not change access method dependency for relation \\\"%s.%s\\\"\",\n\t\t\t\t get_namespace_name(get_rel_namespace(r2)),\n\t\t\t\t get_rel_name(r2));\n\t}\n\n\t/*\n\t * Post alter hook for modified relations. The change to r2 is always\n\t * internal, but r1 depends on the invocation context.\n\t */\n\tInvokeObjectPostAlterHookArg(RelationRelationId, r1, 0, InvalidOid, is_internal);\n\tInvokeObjectPostAlterHookArg(RelationRelationId, r2, 0, InvalidOid, true);\n\n\t/*\n\t * If we have toast tables associated with the relations being swapped,\n\t * deal with them too.\n\t */\n\tif (relform1->reltoastrelid || relform2->reltoastrelid)\n\t{\n\t\tif (swap_toast_by_content)\n\t\t{\n\t\t\tif (relform1->reltoastrelid && relform2->reltoastrelid)\n\t\t\t{\n\t\t\t\t/* Recursively swap the contents of the toast tables */\n\t\t\t\tts_swap_relation_files(relform1->reltoastrelid,\n\t\t\t\t\t\t\t\t\t   relform2->reltoastrelid,\n\t\t\t\t\t\t\t\t\t   target_is_pg_class,\n\t\t\t\t\t\t\t\t\t   swap_toast_by_content,\n\t\t\t\t\t\t\t\t\t   is_internal,\n\t\t\t\t\t\t\t\t\t   frozenXid,\n\t\t\t\t\t\t\t\t\t   cutoffMulti,\n\t\t\t\t\t\t\t\t\t   mapped_tables);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t/* caller messed up */\n\t\t\t\telog(ERROR, \"cannot swap toast files by content when there's only one\");\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/*\n\t\t\t * We swapped the ownership links, so we need to change dependency\n\t\t\t * data to match.\n\t\t\t *\n\t\t\t * NOTE: it is possible that only one table has a toast table.\n\t\t\t *\n\t\t\t * NOTE: at present, a TOAST table's only dependency is the one on\n\t\t\t * its owning table.  If more are ever created, we'd need to use\n\t\t\t * something more selective than deleteDependencyRecordsFor() to\n\t\t\t * get rid of just the link we want.\n\t\t\t */\n\t\t\tObjectAddress baseobject, toastobject;\n\t\t\tlong count;\n\n\t\t\t/*\n\t\t\t * We disallow this case for system catalogs, to avoid the\n\t\t\t * possibility that the catalog we're rebuilding is one of the\n\t\t\t * ones the dependency changes would change.  It's too late to be\n\t\t\t * making any data changes to the target catalog.\n\t\t\t */\n\t\t\tif (IsSystemClass(r1, relform1))\n\t\t\t\telog(ERROR, \"cannot swap toast files by links for system catalogs\");\n\n\t\t\t/* Delete old dependencies */\n\t\t\tif (relform1->reltoastrelid)\n\t\t\t{\n\t\t\t\tcount =\n\t\t\t\t\tdeleteDependencyRecordsFor(RelationRelationId, relform1->reltoastrelid, false);\n\t\t\t\tif (count != 1)\n\t\t\t\t\telog(ERROR, \"expected one dependency record for TOAST table, found %ld\", count);\n\t\t\t}\n\t\t\tif (relform2->reltoastrelid)\n\t\t\t{\n\t\t\t\tcount =\n\t\t\t\t\tdeleteDependencyRecordsFor(RelationRelationId, relform2->reltoastrelid, false);\n\t\t\t\tif (count != 1)\n\t\t\t\t\telog(ERROR, \"expected one dependency record for TOAST table, found %ld\", count);\n\t\t\t}\n\n\t\t\t/* Register new dependencies */\n\t\t\tbaseobject.classId = RelationRelationId;\n\t\t\tbaseobject.objectSubId = 0;\n\t\t\ttoastobject.classId = RelationRelationId;\n\t\t\ttoastobject.objectSubId = 0;\n\n\t\t\tif (relform1->reltoastrelid)\n\t\t\t{\n\t\t\t\tbaseobject.objectId = r1;\n\t\t\t\ttoastobject.objectId = relform1->reltoastrelid;\n\t\t\t\trecordDependencyOn(&toastobject, &baseobject, DEPENDENCY_INTERNAL);\n\t\t\t}\n\n\t\t\tif (relform2->reltoastrelid)\n\t\t\t{\n\t\t\t\tbaseobject.objectId = r2;\n\t\t\t\ttoastobject.objectId = relform2->reltoastrelid;\n\t\t\t\trecordDependencyOn(&toastobject, &baseobject, DEPENDENCY_INTERNAL);\n\t\t\t}\n\t\t}\n\t}\n\n\t/*\n\t * If we're swapping two toast tables by content, do the same for their\n\t * valid index. The swap can actually be safely done only if the relations\n\t * have indexes.\n\t */\n\tif (swap_toast_by_content && relform1->relkind == RELKIND_TOASTVALUE &&\n\t\trelform2->relkind == RELKIND_TOASTVALUE)\n\t{\n\t\tOid toastIndex1, toastIndex2;\n\n\t\t/* Get valid index for each relation */\n\t\ttoastIndex1 = toast_get_valid_index(r1, AccessExclusiveLock);\n\t\ttoastIndex2 = toast_get_valid_index(r2, AccessExclusiveLock);\n\n\t\tts_swap_relation_files(toastIndex1,\n\t\t\t\t\t\t\t   toastIndex2,\n\t\t\t\t\t\t\t   target_is_pg_class,\n\t\t\t\t\t\t\t   swap_toast_by_content,\n\t\t\t\t\t\t\t   is_internal,\n\t\t\t\t\t\t\t   InvalidTransactionId,\n\t\t\t\t\t\t\t   InvalidMultiXactId,\n\t\t\t\t\t\t\t   mapped_tables);\n\t}\n\n\t/* Clean up. */\n\theap_freetuple(reltup1);\n\theap_freetuple(reltup2);\n\n\ttable_close(relRelation, RowExclusiveLock);\n\n\t/*\n\t * Close both relcache entries' smgr links.  We need this kludge because\n\t * both links will be invalidated during upcoming CommandCounterIncrement.\n\t * Whichever of the rels is the second to be cleared will have a dangling\n\t * reference to the other's smgr entry.  Rather than trying to avoid this\n\t * by ordering operations just so, it's easiest to close the links first.\n\t * (Fortunately, since one of the entries is local in our transaction,\n\t * it's sufficient to clear out our own relcache this way; the problem\n\t * cannot arise for other backends when they see our update on the\n\t * non-transient relation.)\n\t *\n\t * Caution: the placement of this step interacts with the decision to\n\t * handle toast rels by recursion.  When we are trying to rebuild pg_class\n\t * itself, the smgr close on pg_class must happen after all accesses in\n\t * this function.\n\t */\n\n#if PG17_LT\n\t/* Not needed as of 21d9c3ee4ef7 in the upstream */\n\tRelationCloseSmgrByOid(r1);\n\tRelationCloseSmgrByOid(r2);\n#endif\n}\n\n/*\n * Remove the transient table that was built by make_new_heap, and finish\n * cleaning up (including rebuilding all indexes on the old heap).\n */\nvoid\nts_finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog,\n\t\t\t\t\tbool swap_toast_by_content, bool check_constraints, bool is_internal,\n\t\t\t\t\tbool reindex, TransactionId frozenXid, MultiXactId cutoffMulti,\n\t\t\t\t\tchar newrelpersistence)\n{\n\tObjectAddress object;\n\tOid mapped_tables[4];\n\tint i;\n\n\t/* Report that we are now swapping relation files */\n\tpgstat_progress_update_param(PROGRESS_CLUSTER_PHASE, PROGRESS_CLUSTER_PHASE_SWAP_REL_FILES);\n\n\t/* Zero out possible results from swapped_relation_files */\n\tmemset(mapped_tables, 0, sizeof(mapped_tables));\n\n\t/*\n\t * Swap the contents of the heap relations (including any toast tables).\n\t * Also set old heap's relfrozenxid to frozenXid.\n\t */\n\tts_swap_relation_files(OIDOldHeap,\n\t\t\t\t\t\t   OIDNewHeap,\n\t\t\t\t\t\t   (OIDOldHeap == RelationRelationId),\n\t\t\t\t\t\t   swap_toast_by_content,\n\t\t\t\t\t\t   is_internal,\n\t\t\t\t\t\t   frozenXid,\n\t\t\t\t\t\t   cutoffMulti,\n\t\t\t\t\t\t   mapped_tables);\n\n\t/*\n\t * If it's a system catalog, queue a sinval message to flush all catcaches\n\t * on the catalog when we reach CommandCounterIncrement.\n\t */\n\tif (is_system_catalog)\n\t\tCacheInvalidateCatalog(OIDOldHeap);\n\n\tif (reindex)\n\t{\n\t\tint reindex_flags;\n\t\tReindexParams reindex_params = { 0 };\n\n\t\t/*\n\t\t * Rebuild each index on the relation (but not the toast table, which\n\t\t * is all-new at this point).  It is important to do this before the\n\t\t * DROP step because if we are processing a system catalog that will\n\t\t * be used during DROP, we want to have its indexes available.  There\n\t\t * is no advantage to the other order anyway because this is all\n\t\t * transactional, so no chance to reclaim disk space before commit. We\n\t\t * do not need a final CommandCounterIncrement() because\n\t\t * reindex_relation does it.\n\t\t *\n\t\t * Note: because index_build is called via reindex_relation, it will\n\t\t * never set indcheckxmin true for the indexes.  This is OK even\n\t\t * though in some sense we are building new indexes rather than\n\t\t * rebuilding existing ones, because the new heap won't contain any\n\t\t * HOT chains at all, let alone broken ones, so it can't be necessary\n\t\t * to set indcheckxmin.\n\t\t */\n\t\treindex_flags = REINDEX_REL_SUPPRESS_INDEX_USE;\n\t\tif (check_constraints)\n\t\t\treindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS;\n\n\t\t/*\n\t\t * Ensure that the indexes have the same persistence as the parent\n\t\t * relation.\n\t\t */\n\t\tif (newrelpersistence == RELPERSISTENCE_UNLOGGED)\n\t\t\treindex_flags |= REINDEX_REL_FORCE_INDEXES_UNLOGGED;\n\t\telse if (newrelpersistence == RELPERSISTENCE_PERMANENT)\n\t\t\treindex_flags |= REINDEX_REL_FORCE_INDEXES_PERMANENT;\n\n\t\t/* Report that we are now reindexing relations */\n\t\tpgstat_progress_update_param(PROGRESS_CLUSTER_PHASE, PROGRESS_CLUSTER_PHASE_REBUILD_INDEX);\n\n#if PG17_LT\n\t\treindex_relation(OIDOldHeap, reindex_flags, &reindex_params);\n#else\n\t\treindex_relation(NULL, OIDOldHeap, reindex_flags, &reindex_params);\n#endif\n\t}\n\telse\n\t{\n\t\t/* Must make changes visible after swap. Normally reindex does it\n\t\t * implicitly. */\n\t\tCommandCounterIncrement();\n\t}\n\n\t/* Report that we are now doing clean up */\n\tpgstat_progress_update_param(PROGRESS_CLUSTER_PHASE, PROGRESS_CLUSTER_PHASE_FINAL_CLEANUP);\n\n\t/*\n\t * If the relation being rebuilt is pg_class, swap_relation_files()\n\t * couldn't update pg_class's own pg_class entry (check comments in\n\t * swap_relation_files()), thus relfrozenxid was not updated. That's\n\t * annoying because a potential reason for doing a VACUUM FULL is a\n\t * imminent or actual anti-wraparound shutdown.  So, now that we can\n\t * access the new relation using its indices, update relfrozenxid.\n\t * pg_class doesn't have a toast relation, so we don't need to update the\n\t * corresponding toast relation. Not that there's little point moving all\n\t * relfrozenxid updates here since swap_relation_files() needs to write to\n\t * pg_class for non-mapped relations anyway.\n\t */\n\tif (OIDOldHeap == RelationRelationId)\n\t{\n\t\tRelation relRelation;\n\t\tHeapTuple reltup;\n\t\tForm_pg_class relform;\n\n\t\trelRelation = table_open(RelationRelationId, RowExclusiveLock);\n\n\t\treltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(OIDOldHeap));\n\t\tif (!HeapTupleIsValid(reltup))\n\t\t\telog(ERROR, \"cache lookup failed for relation %u\", OIDOldHeap);\n\t\trelform = (Form_pg_class) GETSTRUCT(reltup);\n\n\t\trelform->relfrozenxid = frozenXid;\n\t\trelform->relminmxid = cutoffMulti;\n\n\t\tCatalogTupleUpdate(relRelation, &reltup->t_self, reltup);\n\n\t\ttable_close(relRelation, RowExclusiveLock);\n\t}\n\n\t/* Destroy new heap with old filenumber */\n\tobject.classId = RelationRelationId;\n\tobject.objectId = OIDNewHeap;\n\tobject.objectSubId = 0;\n\n\t/*\n\t * The new relation is local to our transaction and we know nothing\n\t * depends on it, so DROP_RESTRICT should be OK.\n\t */\n\tperformDeletion(&object, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);\n\n\t/* performDeletion does CommandCounterIncrement at end */\n\n\t/*\n\t * Now we must remove any relation mapping entries that we set up for the\n\t * transient table, as well as its toast table and toast index if any. If\n\t * we fail to do this before commit, the relmapper will complain about new\n\t * permanent map entries being added post-bootstrap.\n\t */\n\tfor (i = 0; OidIsValid(mapped_tables[i]); i++)\n\t\tRelationMapRemoveMapping(mapped_tables[i]);\n\n\t/*\n\t * At this point, everything is kosher except that, if we did toast swap\n\t * by links, the toast table's name corresponds to the transient table.\n\t * The name is irrelevant to the backend because it's referenced by OID,\n\t * but users looking at the catalogs could be confused.  Rename it to\n\t * prevent this problem.\n\t *\n\t * Note no lock required on the relation, because we already hold an\n\t * exclusive lock on it.\n\t */\n\tif (!swap_toast_by_content)\n\t{\n\t\tRelation newrel;\n\n\t\tnewrel = table_open(OIDOldHeap, NoLock);\n\n\t\tif (OidIsValid(newrel->rd_rel->reltoastrelid))\n\t\t{\n\t\t\tOid toastidx;\n\t\t\tchar NewToastName[NAMEDATALEN];\n\n\t\t\t/* Get the associated valid index to be renamed */\n\t\t\ttoastidx = toast_get_valid_index(newrel->rd_rel->reltoastrelid, NoLock);\n\n\t\t\t/* rename the toast table ... */\n\t\t\tsnprintf(NewToastName, NAMEDATALEN, \"pg_toast_%u\", OIDOldHeap);\n\t\t\tRenameRelationInternal(newrel->rd_rel->reltoastrelid, NewToastName, true, false);\n\n\t\t\t/* ... and its valid index too. */\n\t\t\tsnprintf(NewToastName, NAMEDATALEN, \"pg_toast_%u_index\", OIDOldHeap);\n\n\t\t\tRenameRelationInternal(toastidx, NewToastName, true, true);\n\n\t\t\t/*\n\t\t\t * Reset the relrewrite for the toast. The command-counter\n\t\t\t * increment is required here as we are about to update the tuple\n\t\t\t * that is updated as part of RenameRelationInternal.\n\t\t\t */\n\t\t\tCommandCounterIncrement();\n\t\t\tResetRelRewrite(newrel->rd_rel->reltoastrelid);\n\t\t}\n\t\trelation_close(newrel, NoLock);\n\t}\n\n\t/* if it's not a catalog table, clear any missing attribute settings */\n\tif (!is_system_catalog)\n\t{\n\t\tRelation newrel;\n\n\t\tnewrel = table_open(OIDOldHeap, NoLock);\n\t\tRelationClearMissing(newrel);\n\t\trelation_close(newrel, NoLock);\n\t}\n}\n"
  },
  {
    "path": "src/import/heapswap.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n/*\n * This file contains source code that was copied and/or modified from\n * the PostgreSQL database, which is licensed under the open-source\n * PostgreSQL License. Please see the NOTICE at the top level\n * directory for a copy of the PostgreSQL License.\n */\n#pragma once\n\n#include <postgres.h>\n#include <nodes/pg_list.h>\n#include <utils/rel.h>\n\n#include \"export.h\"\n\nextern TSDLLEXPORT void ts_finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog,\n\t\t\t\t\t\t\t\t\t\t\tbool swap_toast_by_content, bool check_constraints,\n\t\t\t\t\t\t\t\t\t\t\tbool is_internal, bool reindex, TransactionId frozenXid,\n\t\t\t\t\t\t\t\t\t\t\tMultiXactId cutoffMulti, char newrelpersistence);\nextern TSDLLEXPORT void ts_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,\n\t\t\t\t\t\t\t\t\t\t\t   bool swap_toast_by_content, bool is_internal,\n\t\t\t\t\t\t\t\t\t\t\t   TransactionId frozenXid, MultiXactId cutoffMulti,\n\t\t\t\t\t\t\t\t\t\t\t   Oid *mapped_tables);\n"
  },
  {
    "path": "src/import/list.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n\n#include <nodes/pg_list.h>\n#include <port/pg_bitutils.h>\n\n#include \"import/list.h\"\n\n/*\n * This file contains source code that was copied and/or modified from\n * the PostgreSQL database, which is licensed under the open-source\n * PostgreSQL License. Please see the NOTICE at the top level\n * directory for a copy of the PostgreSQL License.\n *\n * Copied from PostgreSQL 15.0 (2a7ce2e2ce474504a707ec03e128fde66cfb8b48)\n * without modifications.\n */\n\n/* Overhead for the fixed part of a List header, measured in ListCells */\n#define LIST_HEADER_OVERHEAD                                                                       \\\n\t((int) (((offsetof(List, initial_elements) - 1) / sizeof(ListCell)) + 1))\n\n/*\n * Return a freshly allocated List with room for at least min_size cells.\n *\n * Since empty non-NIL lists are invalid, new_list() sets the initial length\n * to min_size, effectively marking that number of cells as valid; the caller\n * is responsible for filling in their data.\n */\nList *\nts_new_list(NodeTag type, int min_size)\n{\n\tList *newlist;\n\tint max_size;\n\n\tAssert(min_size > 0);\n\n\t/*\n\t * We allocate all the requested cells, and possibly some more, as part of\n\t * the same palloc request as the List header.  This is a big win for the\n\t * typical case of short fixed-length lists.  It can lose if we allocate a\n\t * moderately long list and then it gets extended; we'll be wasting more\n\t * initial_elements[] space than if we'd made the header small.  However,\n\t * rounding up the request as we do in the normal code path provides some\n\t * defense against small extensions.\n\t */\n\n#ifndef DEBUG_LIST_MEMORY_USAGE\n\n\t/*\n\t * Normally, we set up a list with some extra cells, to allow it to grow\n\t * without a repalloc.  Prefer cell counts chosen to make the total\n\t * allocation a power-of-2, since palloc would round it up to that anyway.\n\t * (That stops being true for very large allocations, but very long lists\n\t * are infrequent, so it doesn't seem worth special logic for such cases.)\n\t *\n\t * The minimum allocation is 8 ListCell units, providing either 4 or 5\n\t * available ListCells depending on the machine's word width.  Counting\n\t * palloc's overhead, this uses the same amount of space as a one-cell\n\t * list did in the old implementation, and less space for any longer list.\n\t *\n\t * We needn't worry about integer overflow; no caller passes min_size\n\t * that's more than twice the size of an existing list, so the size limits\n\t * within palloc will ensure that we don't overflow here.\n\t */\n\tmax_size = pg_nextpower2_32(Max(8, min_size + LIST_HEADER_OVERHEAD));\n\tmax_size -= LIST_HEADER_OVERHEAD;\n#else\n\n\t/*\n\t * For debugging, don't allow any extra space.  This forces any cell\n\t * addition to go through enlarge_list() and thus move the existing data.\n\t */\n\tmax_size = min_size;\n#endif\n\n\tnewlist = (List *) palloc(offsetof(List, initial_elements) + (max_size * sizeof(ListCell)));\n\tnewlist->type = type;\n\tnewlist->length = min_size;\n\tnewlist->max_length = max_size;\n\tnewlist->elements = newlist->initial_elements;\n\n\treturn newlist;\n}\n"
  },
  {
    "path": "src/import/list.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include \"export.h\"\n\n/*\n * This file contains source code that was copied and/or modified from\n * the PostgreSQL database, which is licensed under the open-source\n * PostgreSQL License. Please see the NOTICE at the top level\n * directory for a copy of the PostgreSQL License.\n */\n\nextern TSDLLEXPORT List *ts_new_list(NodeTag type, int min_size);\n"
  },
  {
    "path": "src/import/planner.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n/*\n * This file contains source code that was copied and/or modified from\n * the PostgreSQL database, which is licensed under the open-source\n * PostgreSQL License. Please see the NOTICE at the top level\n * directory for a copy of the PostgreSQL License.\n *\n * These function were copied from the PostgreSQL core planner, since\n * they were declared static in the core planner, but we need them for\n * our manipulations.\n */\n#include <postgres.h>\n#include <access/htup_details.h>\n#include <catalog/pg_collation.h>\n#include <catalog/pg_statistic.h>\n#include <executor/nodeAgg.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodeFuncs.h>\n#include <optimizer/clauses.h>\n#include <optimizer/cost.h>\n#include <optimizer/optimizer.h>\n#include <optimizer/paramassign.h>\n#include <optimizer/paths.h>\n#include <optimizer/placeholder.h>\n#include <optimizer/planmain.h>\n#include <optimizer/planner.h>\n#include <optimizer/tlist.h>\n#include <parser/parsetree.h>\n#include <utils/datum.h>\n#include <utils/lsyscache.h>\n#include <utils/rel.h>\n#include <utils/syscache.h>\n\n#include \"compat/compat.h\"\n#include \"planner.h\"\n\nstatic Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root);\nstatic Plan *inject_projection_plan(Plan *subplan, List *tlist, bool parallel_safe);\n\n/* copied verbatim from optimizer/util/appendinfo.c at REL_17_6 */\nvoid\nts_make_inh_translation_list(Relation oldrelation, Relation newrelation, Index newvarno,\n\t\t\t\t\t\t\t AppendRelInfo *appinfo)\n{\n\tList *vars = NIL;\n\tAttrNumber *pcolnos;\n\tTupleDesc old_tupdesc = RelationGetDescr(oldrelation);\n\tTupleDesc new_tupdesc = RelationGetDescr(newrelation);\n\tOid new_relid = RelationGetRelid(newrelation);\n\tint oldnatts = old_tupdesc->natts;\n\tint newnatts = new_tupdesc->natts;\n\tint old_attno;\n\tint new_attno = 0;\n\n\t/* Initialize reverse-translation array with all entries zero */\n\tappinfo->num_child_cols = newnatts;\n\tappinfo->parent_colnos = pcolnos = (AttrNumber *) palloc0(newnatts * sizeof(AttrNumber));\n\n\tfor (old_attno = 0; old_attno < oldnatts; old_attno++)\n\t{\n\t\tForm_pg_attribute att;\n\t\tchar *attname;\n\t\tOid atttypid;\n\t\tint32 atttypmod;\n\t\tOid attcollation;\n\n\t\tatt = TupleDescAttr(old_tupdesc, old_attno);\n\t\tif (att->attisdropped)\n\t\t{\n\t\t\t/* Just put NULL into this list entry */\n\t\t\tvars = lappend(vars, NULL);\n\t\t\tcontinue;\n\t\t}\n\t\tattname = NameStr(att->attname);\n\t\tatttypid = att->atttypid;\n\t\tatttypmod = att->atttypmod;\n\t\tattcollation = att->attcollation;\n\n\t\t/*\n\t\t * When we are generating the \"translation list\" for the parent table\n\t\t * of an inheritance set, no need to search for matches.\n\t\t */\n\t\tif (oldrelation == newrelation)\n\t\t{\n\t\t\tvars = lappend(vars,\n\t\t\t\t\t\t   makeVar(newvarno,\n\t\t\t\t\t\t\t\t   (AttrNumber) (old_attno + 1),\n\t\t\t\t\t\t\t\t   atttypid,\n\t\t\t\t\t\t\t\t   atttypmod,\n\t\t\t\t\t\t\t\t   attcollation,\n\t\t\t\t\t\t\t\t   0));\n\t\t\tpcolnos[old_attno] = old_attno + 1;\n\t\t\tcontinue;\n\t\t}\n\n\t\t/*\n\t\t * Otherwise we have to search for the matching column by name.\n\t\t * There's no guarantee it'll have the same column position, because\n\t\t * of cases like ALTER TABLE ADD COLUMN and multiple inheritance.\n\t\t * However, in simple cases, the relative order of columns is mostly\n\t\t * the same in both relations, so try the column of newrelation that\n\t\t * follows immediately after the one that we just found, and if that\n\t\t * fails, let syscache handle it.\n\t\t */\n\t\tif (new_attno >= newnatts || (att = TupleDescAttr(new_tupdesc, new_attno))->attisdropped ||\n\t\t\tstrcmp(attname, NameStr(att->attname)) != 0)\n\t\t{\n\t\t\tHeapTuple newtup;\n\n\t\t\tnewtup = SearchSysCacheAttName(new_relid, attname);\n\t\t\tif (!HeapTupleIsValid(newtup))\n\t\t\t\telog(ERROR,\n\t\t\t\t\t \"could not find inherited attribute \\\"%s\\\" of relation \\\"%s\\\"\",\n\t\t\t\t\t attname,\n\t\t\t\t\t RelationGetRelationName(newrelation));\n\t\t\tnew_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1;\n\t\t\tAssert(new_attno >= 0 && new_attno < newnatts);\n\t\t\tReleaseSysCache(newtup);\n\n\t\t\tatt = TupleDescAttr(new_tupdesc, new_attno);\n\t\t}\n\n\t\t/* Found it, check type and collation match */\n\t\tif (atttypid != att->atttypid || atttypmod != att->atttypmod)\n\t\t\telog(ERROR,\n\t\t\t\t \"attribute \\\"%s\\\" of relation \\\"%s\\\" does not match parent's type\",\n\t\t\t\t attname,\n\t\t\t\t RelationGetRelationName(newrelation));\n\t\tif (attcollation != att->attcollation)\n\t\t\telog(ERROR,\n\t\t\t\t \"attribute \\\"%s\\\" of relation \\\"%s\\\" does not match parent's collation\",\n\t\t\t\t attname,\n\t\t\t\t RelationGetRelationName(newrelation));\n\n\t\tvars = lappend(vars,\n\t\t\t\t\t   makeVar(newvarno,\n\t\t\t\t\t\t\t   (AttrNumber) (new_attno + 1),\n\t\t\t\t\t\t\t   atttypid,\n\t\t\t\t\t\t\t   atttypmod,\n\t\t\t\t\t\t\t   attcollation,\n\t\t\t\t\t\t\t   0));\n\t\tpcolnos[new_attno] = old_attno + 1;\n\t\tnew_attno++;\n\t}\n\n\tappinfo->translated_vars = vars;\n}\n\n/* copied verbatim from planner.c */\nstruct PathTarget *\nts_make_partial_grouping_target(struct PlannerInfo *root, PathTarget *grouping_target)\n{\n\tstruct Query *parse = root->parse;\n\tPathTarget *partial_target;\n\tstruct List *non_group_cols;\n\tstruct List *non_group_exprs;\n\tint i;\n\tListCell *lc;\n\n\tpartial_target = create_empty_pathtarget();\n\tnon_group_cols = NIL;\n\n\ti = 0;\n\tforeach (lc, grouping_target->exprs)\n\t{\n\t\tstruct Expr *expr = (struct Expr *) lfirst(lc);\n\t\tunsigned int sgref = get_pathtarget_sortgroupref(grouping_target, i);\n\n\t\tif (sgref && parse->groupClause &&\n\t\t\tget_sortgroupref_clause_noerr(sgref, parse->groupClause) != NULL)\n\t\t{\n\t\t\t/*\n\t\t\t * It's a grouping column, so add it to the partial_target as-is.\n\t\t\t * (This allows the upper agg step to repeat the grouping calcs.)\n\t\t\t */\n\t\t\tadd_column_to_pathtarget(partial_target, expr, sgref);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/*\n\t\t\t * Non-grouping column, so just remember the expression for later\n\t\t\t * call to pull_var_clause.\n\t\t\t */\n\t\t\tnon_group_cols = lappend(non_group_cols, expr);\n\t\t}\n\n\t\ti++;\n\t}\n\n\t/*\n\t * If there's a HAVING clause, we'll need the Vars/Aggrefs it uses, too.\n\t */\n\tif (parse->havingQual)\n\t\tnon_group_cols = lappend(non_group_cols, parse->havingQual);\n\n\t/*\n\t * Pull out all the Vars, PlaceHolderVars, and Aggrefs mentioned in\n\t * non-group cols (plus HAVING), and add them to the partial_target if not\n\t * already present.  (An expression used directly as a GROUP BY item will\n\t * be present already.)  Note this includes Vars used in resjunk items, so\n\t * we are covering the needs of ORDER BY and window specifications.\n\t */\n\tnon_group_exprs = pull_var_clause((struct Node *) non_group_cols,\n\t\t\t\t\t\t\t\t\t  PVC_INCLUDE_AGGREGATES | PVC_RECURSE_WINDOWFUNCS |\n\t\t\t\t\t\t\t\t\t\t  PVC_INCLUDE_PLACEHOLDERS);\n\n\tadd_new_columns_to_pathtarget(partial_target, non_group_exprs);\n\n\t/*\n\t * Adjust Aggrefs to put them in partial mode.  At this point all Aggrefs\n\t * are at the top level of the target list, so we can just scan the list\n\t * rather than recursing through the expression trees.\n\t */\n\tforeach (lc, partial_target->exprs)\n\t{\n\t\tstruct Aggref *aggref = (struct Aggref *) lfirst(lc);\n\n\t\tif (IsA(aggref, Aggref))\n\t\t{\n\t\t\tstruct Aggref *newaggref;\n\n\t\t\t/*\n\t\t\t * We shouldn't need to copy the substructure of the Aggref node,\n\t\t\t * but flat-copy the node itself to avoid damaging other trees.\n\t\t\t */\n\t\t\tnewaggref = makeNode(Aggref);\n\t\t\tmemcpy(newaggref, aggref, sizeof(struct Aggref));\n\n\t\t\t/* For now, assume serialization is required */\n\t\t\tmark_partial_aggref(newaggref, AGGSPLIT_INITIAL_SERIAL);\n\n\t\t\tlfirst(lc) = newaggref;\n\t\t}\n\t}\n\n\t/* clean up cruft */\n\tlist_free(non_group_exprs);\n\tlist_free(non_group_cols);\n\n\t/* XXX this causes some redundant cost calculation ... */\n\treturn set_pathtarget_cost_width(root, partial_target);\n}\n\n/* copied verbatim from selfuncs.c */\nbool\nts_get_variable_range(PlannerInfo *root, VariableStatData *vardata, Oid sortop, Datum *min,\n\t\t\t\t\t  Datum *max)\n{\n\tDatum tmin = 0;\n\tDatum tmax = 0;\n\tbool have_data = false;\n\tint16 typLen;\n\tbool typByVal;\n\tOid opfuncoid;\n\tAttStatsSlot sslot;\n\tint i;\n\n\t/*\n\t * XXX It's very tempting to try to use the actual column min and max, if\n\t * we can get them relatively-cheaply with an index probe.  However, since\n\t * this function is called many times during join planning, that could\n\t * have unpleasant effects on planning speed.  Need more investigation\n\t * before enabling this.\n\t */\n#ifdef NOT_USED\n\tif (get_actual_variable_range(root, vardata, sortop, min, max))\n\t\treturn true;\n#endif\n\n\tif (!HeapTupleIsValid(vardata->statsTuple))\n\t{\n\t\t/* no stats available, so default result */\n\t\treturn false;\n\t}\n\n\t/*\n\t * If we can't apply the sortop to the stats data, just fail.  In\n\t * principle, if there's a histogram and no MCVs, we could return the\n\t * histogram endpoints without ever applying the sortop ... but it's\n\t * probably not worth trying, because whatever the caller wants to do with\n\t * the endpoints would likely fail the security check too.\n\t */\n\topfuncoid = get_opcode(sortop);\n\tif (!statistic_proc_security_check(vardata, opfuncoid))\n\t\treturn false;\n\n\tget_typlenbyval(vardata->atttype, &typLen, &typByVal);\n\n\t/*\n\t * If there is a histogram, grab the first and last values.\n\t *\n\t * If there is a histogram that is sorted with some other operator than\n\t * the one we want, fail --- this suggests that there is data we can't\n\t * use.\n\t */\n\tif (get_attstatsslot(&sslot,\n\t\t\t\t\t\t vardata->statsTuple,\n\t\t\t\t\t\t STATISTIC_KIND_HISTOGRAM,\n\t\t\t\t\t\t sortop,\n\t\t\t\t\t\t ATTSTATSSLOT_VALUES))\n\t{\n\t\tif (sslot.nvalues > 0)\n\t\t{\n\t\t\ttmin = datumCopy(sslot.values[0], typByVal, typLen);\n\t\t\ttmax = datumCopy(sslot.values[sslot.nvalues - 1], typByVal, typLen);\n\t\t\thave_data = true;\n\t\t}\n\t\tfree_attstatsslot(&sslot);\n\t}\n\telse if (get_attstatsslot(&sslot, vardata->statsTuple, STATISTIC_KIND_HISTOGRAM, InvalidOid, 0))\n\t{\n\t\tfree_attstatsslot(&sslot);\n\t\treturn false;\n\t}\n\n\t/*\n\t * If we have most-common-values info, look for extreme MCVs.  This is\n\t * needed even if we also have a histogram, since the histogram excludes\n\t * the MCVs.  However, usually the MCVs will not be the extreme values, so\n\t * avoid unnecessary data copying.\n\t */\n\tif (get_attstatsslot(&sslot,\n\t\t\t\t\t\t vardata->statsTuple,\n\t\t\t\t\t\t STATISTIC_KIND_MCV,\n\t\t\t\t\t\t InvalidOid,\n\t\t\t\t\t\t ATTSTATSSLOT_VALUES))\n\t{\n\t\tbool tmin_is_mcv = false;\n\t\tbool tmax_is_mcv = false;\n\t\tFmgrInfo opproc;\n\n\t\tfmgr_info(opfuncoid, &opproc);\n\n\t\tfor (i = 0; i < sslot.nvalues; i++)\n\t\t{\n\t\t\tif (!have_data)\n\t\t\t{\n\t\t\t\ttmin = tmax = sslot.values[i];\n\t\t\t\ttmin_is_mcv = tmax_is_mcv = have_data = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (DatumGetBool(\n\t\t\t\t\tFunctionCall2Coll(&opproc, DEFAULT_COLLATION_OID, sslot.values[i], tmin)))\n\t\t\t{\n\t\t\t\ttmin = sslot.values[i];\n\t\t\t\ttmin_is_mcv = true;\n\t\t\t}\n\t\t\tif (DatumGetBool(\n\t\t\t\t\tFunctionCall2Coll(&opproc, DEFAULT_COLLATION_OID, tmax, sslot.values[i])))\n\t\t\t{\n\t\t\t\ttmax = sslot.values[i];\n\t\t\t\ttmax_is_mcv = true;\n\t\t\t}\n\t\t}\n\t\tif (tmin_is_mcv)\n\t\t\ttmin = datumCopy(tmin, typByVal, typLen);\n\t\tif (tmax_is_mcv)\n\t\t\ttmax = datumCopy(tmax, typByVal, typLen);\n\t\tfree_attstatsslot(&sslot);\n\t}\n\n\t*min = tmin;\n\t*max = tmax;\n\treturn have_data;\n}\n\n/*\n * ts_make_sort --- basic routine to build a Sort plan node\n *\n * Caller must have built the sortColIdx, sortOperators, collations, and\n * nullsFirst arrays already.\n */\nSort *\nts_make_sort(Plan *lefttree, int numCols, AttrNumber *sortColIdx, Oid *sortOperators,\n\t\t\t Oid *collations, bool *nullsFirst)\n{\n\tSort *node = makeNode(Sort);\n\tPlan *plan = &node->plan;\n\n\tplan->targetlist = lefttree->targetlist;\n\tplan->qual = NIL;\n\tplan->lefttree = lefttree;\n\tplan->righttree = NULL;\n\tnode->numCols = numCols;\n\tnode->sortColIdx = sortColIdx;\n\tnode->sortOperators = sortOperators;\n\tnode->collations = collations;\n\tnode->nullsFirst = nullsFirst;\n\n\treturn node;\n}\n\n/*\n * make_sort_from_pathkeys\n *    Create sort plan to sort according to given pathkeys\n *\n *    'lefttree' is the node which yields input tuples\n *    'pathkeys' is the list of pathkeys by which the result is to be sorted\n *    'relids' is the set of relations required by prepare_sort_from_pathkeys()\n */\nSort *\nts_make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids)\n{\n\tint numsortkeys;\n\tAttrNumber *sortColIdx;\n\tOid *sortOperators;\n\tOid *collations;\n\tbool *nullsFirst;\n\n\t/* Compute sort column info, and adjust lefttree as needed */\n\tlefttree = ts_prepare_sort_from_pathkeys(lefttree,\n\t\t\t\t\t\t\t\t\t\t\t pathkeys,\n\t\t\t\t\t\t\t\t\t\t\t relids,\n\t\t\t\t\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t\t\t\t\t &numsortkeys,\n\t\t\t\t\t\t\t\t\t\t\t &sortColIdx,\n\t\t\t\t\t\t\t\t\t\t\t &sortOperators,\n\t\t\t\t\t\t\t\t\t\t\t &collations,\n\t\t\t\t\t\t\t\t\t\t\t &nullsFirst);\n\n\t/* Now build the Sort node */\n\treturn ts_make_sort(lefttree, numsortkeys, sortColIdx, sortOperators, collations, nullsFirst);\n}\n\n/*\n * ts_prepare_sort_from_pathkeys\n *\t  Prepare to sort according to given pathkeys\n *\n * This is used to set up for Sort, MergeAppend, and Gather Merge nodes.  It\n * calculates the executor's representation of the sort key information, and\n * adjusts the plan targetlist if needed to add resjunk sort columns.\n *\n * Input parameters:\n *\t  'lefttree' is the plan node which yields input tuples\n *\t  'pathkeys' is the list of pathkeys by which the result is to be sorted\n *\t  'relids' identifies the child relation being sorted, if any\n *\t  'reqColIdx' is NULL or an array of required sort key column numbers\n *\t  'adjust_tlist_in_place' is true if lefttree must be modified in-place\n *\n * We must convert the pathkey information into arrays of sort key column\n * numbers, sort operator OIDs, collation OIDs, and nulls-first flags,\n * which is the representation the executor wants.  These are returned into\n * the output parameters *p_numsortkeys etc.\n *\n * When looking for matches to an EquivalenceClass's members, we will only\n * consider child EC members if they belong to given 'relids'.  This protects\n * against possible incorrect matches to child expressions that contain no\n * Vars.\n *\n * If reqColIdx isn't NULL then it contains sort key column numbers that\n * we should match.  This is used when making child plans for a MergeAppend;\n * it's an error if we can't match the columns.\n *\n * If the pathkeys include expressions that aren't simple Vars, we will\n * usually need to add resjunk items to the input plan's targetlist to\n * compute these expressions, since a Sort or MergeAppend node itself won't\n * do any such calculations.  If the input plan type isn't one that can do\n * projections, this means adding a Result node just to do the projection.\n * However, the caller can pass adjust_tlist_in_place = true to force the\n * lefttree tlist to be modified in-place regardless of whether the node type\n * can project --- we use this for fixing the tlist of MergeAppend itself.\n *\n * Returns the node which is to be the input to the Sort (either lefttree,\n * or a Result stacked atop lefttree).\n *\n * static function copied from createplan.c\n */\nPlan *\nts_prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids,\n\t\t\t\t\t\t\t  const AttrNumber *reqColIdx, bool adjust_tlist_in_place,\n\t\t\t\t\t\t\t  int *p_numsortkeys, AttrNumber **p_sortColIdx, Oid **p_sortOperators,\n\t\t\t\t\t\t\t  Oid **p_collations, bool **p_nullsFirst)\n{\n\tList *tlist = lefttree->targetlist;\n\tListCell *i;\n\tint numsortkeys;\n\tAttrNumber *sortColIdx;\n\tOid *sortOperators;\n\tOid *collations;\n\tbool *nullsFirst;\n\n\t/*\n\t * We will need at most list_length(pathkeys) sort columns; possibly less\n\t */\n\tnumsortkeys = list_length(pathkeys);\n\tsortColIdx = (AttrNumber *) palloc(numsortkeys * sizeof(AttrNumber));\n\tsortOperators = (Oid *) palloc(numsortkeys * sizeof(Oid));\n\tcollations = (Oid *) palloc(numsortkeys * sizeof(Oid));\n\tnullsFirst = (bool *) palloc(numsortkeys * sizeof(bool));\n\n\tnumsortkeys = 0;\n\n\tforeach (i, pathkeys)\n\t{\n\t\tPathKey *pathkey = (PathKey *) lfirst(i);\n\t\tEquivalenceClass *ec = pathkey->pk_eclass;\n\t\tEquivalenceMember *em;\n\t\tTargetEntry *tle = NULL;\n\t\tOid pk_datatype = InvalidOid;\n\t\tOid sortop;\n\t\tListCell *j;\n\n\t\tif (ec->ec_has_volatile)\n\t\t{\n\t\t\t/*\n\t\t\t * If the pathkey's EquivalenceClass is volatile, then it must\n\t\t\t * have come from an ORDER BY clause, and we have to match it to\n\t\t\t * that same targetlist entry.\n\t\t\t */\n\t\t\tif (ec->ec_sortref == 0) /* can't happen */\n\t\t\t\telog(ERROR, \"volatile EquivalenceClass has no sortref\");\n\t\t\ttle = get_sortgroupref_tle(ec->ec_sortref, tlist);\n\t\t\tAssert(tle);\n\t\t\tAssert(list_length(ec->ec_members) == 1);\n\t\t\tpk_datatype = ((EquivalenceMember *) linitial(ec->ec_members))->em_datatype;\n\t\t}\n\t\telse if (reqColIdx != NULL)\n\t\t{\n\t\t\t/*\n\t\t\t * If we are given a sort column number to match, only consider\n\t\t\t * the single TLE at that position.  It's possible that there is\n\t\t\t * no such TLE, in which case fall through and generate a resjunk\n\t\t\t * targetentry (we assume this must have happened in the parent\n\t\t\t * plan as well).  If there is a TLE but it doesn't match the\n\t\t\t * pathkey's EC, we do the same, which is probably the wrong thing\n\t\t\t * but we'll leave it to caller to complain about the mismatch.\n\t\t\t */\n\t\t\ttle = get_tle_by_resno(tlist, reqColIdx[numsortkeys]);\n\t\t\tif (tle)\n\t\t\t{\n\t\t\t\tem = find_ec_member_matching_expr(ec, tle->expr, relids);\n\t\t\t\tif (em)\n\t\t\t\t{\n\t\t\t\t\t/* found expr at right place in tlist */\n\t\t\t\t\tpk_datatype = em->em_datatype;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\ttle = NULL;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/*\n\t\t\t * Otherwise, we can sort by any non-constant expression listed in\n\t\t\t * the pathkey's EquivalenceClass.  For now, we take the first\n\t\t\t * tlist item found in the EC. If there's no match, we'll generate\n\t\t\t * a resjunk entry using the first EC member that is an expression\n\t\t\t * in the input's vars.  (The non-const restriction only matters\n\t\t\t * if the EC is below_outer_join; but if it isn't, it won't\n\t\t\t * contain consts anyway, else we'd have discarded the pathkey as\n\t\t\t * redundant.)\n\t\t\t *\n\t\t\t * XXX if we have a choice, is there any way of figuring out which\n\t\t\t * might be cheapest to execute?  (For example, int4lt is likely\n\t\t\t * much cheaper to execute than numericlt, but both might appear\n\t\t\t * in the same equivalence class...)  Not clear that we ever will\n\t\t\t * have an interesting choice in practice, so it may not matter.\n\t\t\t */\n\t\t\tforeach (j, tlist)\n\t\t\t{\n\t\t\t\ttle = (TargetEntry *) lfirst(j);\n\t\t\t\tem = find_ec_member_matching_expr(ec, tle->expr, relids);\n\t\t\t\tif (em)\n\t\t\t\t{\n\t\t\t\t\t/* found expr already in tlist */\n\t\t\t\t\tpk_datatype = em->em_datatype;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\ttle = NULL;\n\t\t\t}\n\t\t}\n\n\t\tif (!tle)\n\t\t{\n\t\t\t/*\n\t\t\t * No matching tlist item; look for a computable expression.\n\t\t\t */\n\t\t\tem = find_computable_ec_member(NULL, ec, tlist, relids, false);\n\t\t\tif (!em)\n\t\t\t\telog(ERROR, \"could not find pathkey item to sort\");\n\t\t\tpk_datatype = em->em_datatype;\n\n\t\t\t/*\n\t\t\t * Do we need to insert a Result node?\n\t\t\t */\n\t\t\tif (!adjust_tlist_in_place && !is_projection_capable_plan(lefttree))\n\t\t\t{\n\t\t\t\t/* copy needed so we don't modify input's tlist below */\n\t\t\t\ttlist = copyObject(tlist);\n\t\t\t\tlefttree = inject_projection_plan(lefttree, tlist, lefttree->parallel_safe);\n\t\t\t}\n\n\t\t\t/* Don't bother testing is_projection_capable_plan again */\n\t\t\tadjust_tlist_in_place = true;\n\n\t\t\t/*\n\t\t\t * Add resjunk entry to input's tlist\n\t\t\t */\n\t\t\ttle = makeTargetEntry(copyObject(em->em_expr), list_length(tlist) + 1, NULL, true);\n\t\t\ttlist = lappend(tlist, tle);\n\t\t\tlefttree->targetlist = tlist; /* just in case NIL before */\n\t\t}\n\n\t\t/*\n\t\t * Look up the correct sort operator from the PathKey's slightly\n\t\t * abstracted representation.\n\t\t */\n\t\tsortop = get_opfamily_member(pathkey->pk_opfamily,\n\t\t\t\t\t\t\t\t\t pk_datatype,\n\t\t\t\t\t\t\t\t\t pk_datatype,\n\t\t\t\t\t\t\t\t\t pathkey->pk_cmptype);\n\t\tif (!OidIsValid(sortop)) /* should not happen */\n\t\t\telog(ERROR,\n\t\t\t\t \"missing operator %d(%u,%u) in opfamily %u\",\n\t\t\t\t pathkey->pk_cmptype,\n\t\t\t\t pk_datatype,\n\t\t\t\t pk_datatype,\n\t\t\t\t pathkey->pk_opfamily);\n\n\t\t/* Add the column to the sort arrays */\n\t\tsortColIdx[numsortkeys] = tle->resno;\n\t\tsortOperators[numsortkeys] = sortop;\n\t\tcollations[numsortkeys] = ec->ec_collation;\n\t\tnullsFirst[numsortkeys] = pathkey->pk_nulls_first;\n\t\tnumsortkeys++;\n\t}\n\n\t/* Return results */\n\t*p_numsortkeys = numsortkeys;\n\t*p_sortColIdx = sortColIdx;\n\t*p_sortOperators = sortOperators;\n\t*p_collations = collations;\n\t*p_nullsFirst = nullsFirst;\n\n\treturn lefttree;\n}\n\n/*\n * copied verbatim from createplan.c\n */\nList *\nts_build_path_tlist(PlannerInfo *root, Path *path)\n{\n\tList *tlist = NIL;\n\tIndex *sortgrouprefs = path->pathtarget->sortgrouprefs;\n\tint resno = 1;\n\tListCell *v;\n\n\tforeach (v, path->pathtarget->exprs)\n\t{\n\t\tNode *node = (Node *) lfirst(v);\n\t\tTargetEntry *tle;\n\n\t\t/*\n\t\t * If it's a parameterized path, there might be lateral references in\n\t\t * the tlist, which need to be replaced with Params.  There's no need\n\t\t * to remake the TargetEntry nodes, so apply this to each list item\n\t\t * separately.\n\t\t */\n\t\tif (path->param_info)\n\t\t\tnode = ts_replace_nestloop_params(root, node);\n\n\t\ttle = makeTargetEntry((Expr *) node, resno, NULL, false);\n\t\tif (sortgrouprefs)\n\t\t\ttle->ressortgroupref = sortgrouprefs[resno - 1];\n\n\t\ttlist = lappend(tlist, tle);\n\t\tresno++;\n\t}\n\treturn tlist;\n}\n\n/*\n * ts_replace_nestloop_params\n *    Replace outer-relation Vars and PlaceHolderVars in the given expression\n *    with nestloop Params\n *\n * All Vars and PlaceHolderVars belonging to the relation(s) identified by\n * root->curOuterRels are replaced by Params, and entries are added to\n * root->curOuterParams if not already present.\n */\nNode *\nts_replace_nestloop_params(PlannerInfo *root, Node *expr)\n{\n\t/* No setup needed for tree walk, so away we go */\n\treturn replace_nestloop_params_mutator(expr, root);\n}\n\nstatic Node *\nreplace_nestloop_params_mutator(Node *node, PlannerInfo *root)\n{\n\tif (node == NULL)\n\t\treturn NULL;\n\tif (IsA(node, Var))\n\t{\n\t\tVar *var = (Var *) node;\n\t\t/* Upper-level Vars should be long gone at this point */\n\t\tAssert(var->varlevelsup == 0);\n\t\t/* If not to be replaced, we can just return the Var unmodified */\n\t\tif (!bms_is_member(var->varno, root->curOuterRels))\n\t\t\treturn node;\n\t\t/* Replace the Var with a nestloop Param */\n\t\treturn (Node *) replace_nestloop_param_var(root, var);\n\t}\n\tif (IsA(node, PlaceHolderVar))\n\t{\n\t\tPlaceHolderVar *phv = (PlaceHolderVar *) node;\n\n\t\t/* Upper-level PlaceHolderVars should be long gone at this point */\n\t\tAssert(phv->phlevelsup == 0);\n\n#if PG16_LT\n\t\t/*\n\t\t * Check whether we need to replace the PHV.  We use bms_overlap as a\n\t\t * cheap/quick test to see if the PHV might be evaluated in the outer\n\t\t * rels, and then grab its PlaceHolderInfo to tell for sure.\n\t\t */\n\t\tif (!bms_overlap(phv->phrels, root->curOuterRels) ||\n\t\t\t!bms_is_subset(find_placeholder_info(root, phv, false)->ph_eval_at, root->curOuterRels))\n#else\n\t\t/* Check whether we need to replace the PHV */\n\t\tif (!bms_is_subset(find_placeholder_info(root, phv)->ph_eval_at, root->curOuterRels))\n#endif\n\t\t{\n\t\t\t/*\n\t\t\t * We can't replace the whole PHV, but we might still need to\n\t\t\t * replace Vars or PHVs within its expression, in case it ends up\n\t\t\t * actually getting evaluated here.  (It might get evaluated in\n\t\t\t * this plan node, or some child node; in the latter case we don't\n\t\t\t * really need to process the expression here, but we haven't got\n\t\t\t * enough info to tell if that's the case.)  Flat-copy the PHV\n\t\t\t * node and then recurse on its expression.\n\t\t\t *\n\t\t\t * Note that after doing this, we might have different\n\t\t\t * representations of the contents of the same PHV in different\n\t\t\t * parts of the plan tree.  This is OK because equal() will just\n\t\t\t * match on phid/phlevelsup, so setrefs.c will still recognize an\n\t\t\t * upper-level reference to a lower-level copy of the same PHV.\n\t\t\t */\n\t\t\tPlaceHolderVar *newphv = makeNode(PlaceHolderVar);\n\n\t\t\tmemcpy(newphv, phv, sizeof(PlaceHolderVar));\n\t\t\tnewphv->phexpr = (Expr *) replace_nestloop_params_mutator((Node *) phv->phexpr, root);\n\t\t\treturn (Node *) newphv;\n\t\t}\n\t\t/* Replace the PlaceHolderVar with a nestloop Param */\n\t\treturn (Node *) replace_nestloop_param_placeholdervar(root, phv);\n\t}\n\treturn expression_tree_mutator(node, replace_nestloop_params_mutator, (void *) root);\n}\n\n/*\n * make_result\n *\t  Build a Result plan node\n */\nstatic Result *\nmake_result(List *tlist, Node *resconstantqual, Plan *subplan)\n{\n\tResult *node = makeNode(Result);\n\tPlan *plan = &node->plan;\n\n\tplan->targetlist = tlist;\n\tplan->qual = NIL;\n\tplan->lefttree = subplan;\n\tplan->righttree = NULL;\n\tnode->resconstantqual = resconstantqual;\n\n\treturn node;\n}\n\n/*\n * Copy cost and size info from a lower plan node to an inserted node.\n * (Most callers alter the info after copying it.)\n */\nstatic void\ncopy_plan_costsize(Plan *dest, Plan *src)\n{\n\tdest->startup_cost = src->startup_cost;\n\tdest->total_cost = src->total_cost;\n\tdest->plan_rows = src->plan_rows;\n\tdest->plan_width = src->plan_width;\n\t/* Assume the inserted node is not parallel-aware. */\n\tdest->parallel_aware = false;\n\t/* Assume the inserted node is parallel-safe, if child plan is. */\n\tdest->parallel_safe = src->parallel_safe;\n}\n\n/*\n * inject_projection_plan\n *\t  Insert a Result node to do a projection step.\n *\n * This is used in a few places where we decide on-the-fly that we need a\n * projection step as part of the tree generated for some Path node.\n * We should try to get rid of this in favor of doing it more honestly.\n *\n * One reason it's ugly is we have to be told the right parallel_safe marking\n * to apply (since the tlist might be unsafe even if the child plan is safe).\n */\nstatic Plan *\ninject_projection_plan(Plan *subplan, List *tlist, bool parallel_safe)\n{\n\tPlan *plan;\n\n\tplan = (Plan *) make_result(tlist, NULL, subplan);\n\n\t/*\n\t * In principle, we should charge tlist eval cost plus cpu_per_tuple per\n\t * row for the Result node.  But the former has probably been factored in\n\t * already and the latter was not accounted for during Path construction,\n\t * so being formally correct might just make the EXPLAIN output look less\n\t * consistent not more so.  Hence, just copy the subplan's cost.\n\t */\n\tcopy_plan_costsize(plan, subplan);\n\tplan->parallel_safe = parallel_safe;\n\n\treturn plan;\n}\n\n/* In PG18, child ems are not added to ec_members\n * but need to be maintained in separate Lists.\n *\n * https://github.com/postgres/postgres/commit/d69d45a5\n */\n/* copied from add_child_eq_member */\n#if PG18_GE\nvoid\nts_add_child_eq_member(PlannerInfo *root, EquivalenceClass *ec, EquivalenceMember *em,\n\t\t\t\t\t   int child_relid)\n{\n\t/*\n\t * Allocate the array to store child members; an array of Lists indexed by\n\t * relid, or expand the existing one, if necessary.\n\t */\n\tif (unlikely(ec->ec_childmembers_size < root->simple_rel_array_size))\n\t{\n\t\tec->ec_relids = bms_add_members(ec->ec_relids, em->em_relids);\n\t\tif (ec->ec_childmembers == NULL)\n\t\t\tec->ec_childmembers = palloc0_array(List *, root->simple_rel_array_size);\n\t\telse\n\t\t\tec->ec_childmembers = repalloc0_array(ec->ec_childmembers,\n\t\t\t\t\t\t\t\t\t\t\t\t  List *,\n\t\t\t\t\t\t\t\t\t\t\t\t  ec->ec_childmembers_size,\n\t\t\t\t\t\t\t\t\t\t\t\t  root->simple_rel_array_size);\n\t\tec->ec_childmembers_size = root->simple_rel_array_size;\n\t}\n\t/* add member to the ec_childmembers List for the given child_relid */\n\tec->ec_childmembers[child_relid] = lappend(ec->ec_childmembers[child_relid], em);\n}\n#endif\n"
  },
  {
    "path": "src/import/planner.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n/*\n * This file contains source code that was copied and/or modified from\n * the PostgreSQL database, which is licensed under the open-source\n * PostgreSQL License. Please see the NOTICE at the top level\n * directory for a copy of the PostgreSQL License.\n *\n * These function were copied from the PostgreSQL core planner, since\n * they were declared static in the core planner, but we need them for\n * our manipulations.\n */\n\n#include <postgres.h>\n#include <nodes/execnodes.h>\n#include <utils/rel.h>\n#include <utils/selfuncs.h>\n\n#include \"export.h\"\n\nextern TSDLLEXPORT void ts_make_inh_translation_list(Relation oldrelation, Relation newrelation,\n\t\t\t\t\t\t\t\t\t\t\t\t\t Index newvarno, AppendRelInfo *appinfo);\n\nextern TSDLLEXPORT struct PathTarget *ts_make_partial_grouping_target(struct PlannerInfo *root,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  PathTarget *grouping_target);\n\nextern bool ts_get_variable_range(PlannerInfo *root, VariableStatData *vardata, Oid sortop,\n\t\t\t\t\t\t\t\t  Datum *min, Datum *max);\n\nextern TSDLLEXPORT Plan *\nts_prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids,\n\t\t\t\t\t\t\t  const AttrNumber *reqColIdx, bool adjust_tlist_in_place,\n\t\t\t\t\t\t\t  int *p_numsortkeys, AttrNumber **p_sortColIdx, Oid **p_sortOperators,\n\t\t\t\t\t\t\t  Oid **p_collations, bool **p_nullsFirst);\n\nextern TSDLLEXPORT Sort *ts_make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids);\n\nextern TSDLLEXPORT Sort *ts_make_sort(Plan *lefttree, int numCols, AttrNumber *sortColIdx,\n\t\t\t\t\t\t\t\t\t  Oid *sortOperators, Oid *collations, bool *nullsFirst);\n\nextern TSDLLEXPORT PathKey *ts_make_pathkey_from_sortop(PlannerInfo *root, Expr *expr,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tRelids nullable_relids, Oid ordering_op,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbool nulls_first, Index sortref,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbool create_it);\n\nextern TSDLLEXPORT List *ts_build_path_tlist(PlannerInfo *root, Path *path);\n\nextern TSDLLEXPORT Node *ts_replace_nestloop_params(PlannerInfo *root, Node *expr);\n\nextern void ts_ExecSetTupleBound(int64 tuples_needed, PlanState *child_node);\n\n#if PG18_GE\n/* In PG18, child ems are not added to ec_members\n * but need to be maintained in separate Lists.\n *\n * https://github.com/postgres/postgres/commit/d69d45a5\n */\n/* copied from add_child_eq_member */\nextern TSDLLEXPORT void ts_add_child_eq_member(PlannerInfo *root, EquivalenceClass *ec,\n\t\t\t\t\t\t\t\t\t\t\t   EquivalenceMember *em, int child_relid);\n#endif\n"
  },
  {
    "path": "src/import/ts_explain.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n/*\n * This file contains source code that was copied and/or modified from\n * the PostgreSQL database, which is licensed under the open-source\n * PostgreSQL License. Please see the NOTICE at the top level\n * directory for a copy of the PostgreSQL License.\n */\n\n#include \"ts_explain.h\"\n\n#include <commands/explain.h>\n#include <nodes/makefuncs.h>\n#include <utils/ruleutils.h>\n\n#include \"compat/compat.h\"\n\n/*\n * Show a generic expression\n */\nstatic void\nts_show_expression(Node *node, const char *qlabel, PlanState *planstate, List *ancestors,\n\t\t\t\t   bool useprefix, ExplainState *es)\n{\n\tList *context;\n\tchar *exprstr;\n\n\t/* Set up deparsing context */\n\tcontext = set_deparse_context_plan(es->deparse_cxt, planstate->plan, ancestors);\n\n\t/* Deparse the expression */\n\texprstr = deparse_expression(node, context, useprefix, false);\n\n\t/* And add to es->str */\n\tExplainPropertyText(qlabel, exprstr, es);\n}\n\n/*\n * Show a qualifier expression (which is a List with implicit AND semantics)\n */\nstatic void\nts_show_qual(List *qual, const char *qlabel, PlanState *planstate, List *ancestors, bool useprefix,\n\t\t\t ExplainState *es)\n{\n\tNode *node;\n\n\t/* No work if empty qual */\n\tif (qual == NIL)\n\t\treturn;\n\n\t/* Convert AND list to explicit AND */\n\tnode = (Node *) make_ands_explicit(qual);\n\n\t/* And show it */\n\tts_show_expression(node, qlabel, planstate, ancestors, useprefix, es);\n}\n\n/*\n * Show a qualifier expression for a scan plan node\n */\nvoid\nts_show_scan_qual(List *qual, const char *qlabel, PlanState *planstate, List *ancestors,\n\t\t\t\t  ExplainState *es)\n{\n\tbool useprefix;\n\n\tuseprefix = (IsA(planstate->plan, SubqueryScan) || es->verbose);\n\tts_show_qual(qual, qlabel, planstate, ancestors, useprefix, es);\n}\n\n/*\n * If it's EXPLAIN ANALYZE, show instrumentation information for a plan node\n *\n * \"which\" identifies which instrumentation counter to print\n */\nvoid\nts_show_instrumentation_count(const char *qlabel, int which, PlanState *planstate, ExplainState *es)\n{\n\tdouble nfiltered;\n\tdouble nloops;\n\n\tif (!es->analyze || !planstate->instrument)\n\t\treturn;\n\n\tif (which == 2)\n\t\tnfiltered = planstate->instrument->nfiltered2;\n\telse\n\t\tnfiltered = planstate->instrument->nfiltered1;\n\tnloops = planstate->instrument->nloops;\n\n\t/* In text mode, suppress zero counts; they're not interesting enough */\n\tif (nfiltered > 0 || es->format != EXPLAIN_FORMAT_TEXT)\n\t{\n\t\tif (nloops > 0)\n\t\t\tExplainPropertyFloat(qlabel, NULL, nfiltered / nloops, 0, es);\n\t\telse\n\t\t\tExplainPropertyFloat(qlabel, NULL, 0.0, 0, es);\n\t}\n}\n"
  },
  {
    "path": "src/import/ts_explain.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n/*\n * This file contains source code that was copied and/or modified from\n * the PostgreSQL database, which is licensed under the open-source\n * PostgreSQL License. Please see the NOTICE at the top level\n * directory for a copy of the PostgreSQL License.\n */\n\n#include <postgres.h>\n\n#include <commands/explain.h>\n#include <nodes/execnodes.h>\n#include <nodes/pg_list.h>\n\n#include <compat/compat.h>\n#include \"export.h\"\n\n#if PG18_GE\n#include <commands/explain_format.h>\n#include <commands/explain_state.h>\n#endif\n\nextern TSDLLEXPORT void ts_show_scan_qual(List *qual, const char *qlabel, PlanState *planstate,\n\t\t\t\t\t\t\t\t\t\t  List *ancestors, ExplainState *es);\n\nextern TSDLLEXPORT void ts_show_instrumentation_count(const char *qlabel, int which,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  PlanState *planstate, ExplainState *es);\n"
  },
  {
    "path": "src/indexing.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/xact.h>\n#include <catalog/index.h>\n#include <catalog/indexing.h>\n#include <catalog/namespace.h>\n#include <catalog/pg_inherits.h>\n#include <commands/defrem.h>\n#include <commands/event_trigger.h>\n#include <commands/tablecmds.h>\n#include <commands/tablespace.h>\n#include <fmgr.h>\n#include <nodes/makefuncs.h>\n#include <nodes/parsenodes.h>\n#include <nodes/value.h>\n#include <parser/parse_utilcmd.h>\n#include <utils/builtins.h>\n#include <utils/lsyscache.h>\n#include <utils/syscache.h>\n\n#include \"annotations.h\"\n#include \"dimension.h\"\n#include \"errors.h\"\n#include \"guc.h\"\n#include \"hypertable_cache.h\"\n#include \"indexing.h\"\n#include \"partitioning.h\"\n\nstatic bool\nindex_has_attribute(const List *indexelems, const char *attrname)\n{\n\tListCell *lc;\n\n\tforeach (lc, indexelems)\n\t{\n\t\tNode *node = lfirst(lc);\n\t\tconst char *colname = NULL;\n\n\t\t/*\n\t\t * The type of the element varies depending on whether the list is\n\t\t * from an index or a constraint\n\t\t */\n\t\tswitch (nodeTag(node))\n\t\t{\n\t\t\tcase T_IndexElem:\n\t\t\t\tcolname = ((IndexElem *) node)->name;\n\t\t\t\tbreak;\n\t\t\tcase T_String:\n\t\t\t\tcolname = strVal(node);\n\t\t\t\tbreak;\n\t\t\tcase T_List:\n\t\t\t{\n\t\t\t\tList *pair = lfirst_node(List, lc);\n\n\t\t\t\tif (list_length(pair) == 2 && IsA(linitial(pair), IndexElem) &&\n\t\t\t\t\tIsA(lsecond(pair), List))\n\t\t\t\t{\n\t\t\t\t\tcolname = ((IndexElem *) linitial(pair))->name;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\t\tTS_FALLTHROUGH;\n\t\t\tdefault:\n\t\t\t\telog(ERROR, \"unsupported index list element\");\n\t\t}\n\n\t\tif (colname != NULL && strncmp(colname, attrname, NAMEDATALEN) == 0)\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n/*\n * Verify that index columns cover all partitioning dimensions.\n *\n * A UNIQUE, PRIMARY KEY or EXCLUSION index on a chunk must cover all\n * partitioning dimensions to guarantee uniqueness (or exclusion) across the\n * entire hypertable. Therefore we check that all dimensions are present among\n * the index columns.\n */\nvoid\nts_indexing_verify_columns(const Hyperspace *hs, const List *indexelems)\n{\n\tint i;\n\n\tfor (i = 0; i < hs->num_dimensions; i++)\n\t{\n\t\tconst Dimension *dim = &hs->dimensions[i];\n\n\t\tif (!index_has_attribute(indexelems, NameStr(dim->fd.column_name)))\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_TS_BAD_HYPERTABLE_INDEX_DEFINITION),\n\t\t\t\t\t errmsg(\"cannot create a unique index without the column \\\"%s\\\" (used in \"\n\t\t\t\t\t\t\t\"partitioning)\",\n\t\t\t\t\t\t\tNameStr(dim->fd.column_name)),\n\t\t\t\t\t errhint(\n\t\t\t\t\t\t \"If you're creating a hypertable on a table with a primary key, ensure \"\n\t\t\t\t\t\t \"the partitioning column is part of the primary or composite key.\")));\n\t}\n}\n\n/*\n * Verify index columns.\n *\n * We only care about UNIQUE, PRIMARY KEY or EXCLUSION indexes.\n */\nvoid\nts_indexing_verify_index(const Hyperspace *hs, const IndexStmt *stmt)\n{\n\tif (stmt->unique || stmt->excludeOpNames != NULL)\n\t\tts_indexing_verify_columns(hs, stmt->indexParams);\n}\n\n/*\n * Build a list of string Values representing column names that an index covers.\n */\nstatic List *\nbuild_indexcolumn_list(const Relation idxrel)\n{\n\tList *columns = NIL;\n\tint i;\n\n\tfor (i = 0; i < idxrel->rd_att->natts; i++)\n\t{\n\t\tForm_pg_attribute idxattr = TupleDescAttr(idxrel->rd_att, i);\n\n\t\tcolumns = lappend(columns, makeString(NameStr(idxattr->attname)));\n\t}\n\n\treturn columns;\n}\n\nstatic void\ncreate_default_index(const Hypertable *ht, List *indexelems)\n{\n\tIndexStmt stmt = {\n\t\t.type = T_IndexStmt,\n\t\t.accessMethod = DEFAULT_INDEX_TYPE,\n\t\t.idxname = NULL,\n\t\t.relation = makeRangeVar((char *) NameStr(ht->fd.schema_name),\n\t\t\t\t\t\t\t\t (char *) NameStr(ht->fd.table_name),\n\t\t\t\t\t\t\t\t 0),\n\t\t.tableSpace = get_tablespace_name(get_rel_tablespace(ht->main_table_relid)),\n\t\t.indexParams = indexelems,\n\t};\n\n\tDefineIndexCompat(ht->main_table_relid,\n\t\t\t\t\t  &stmt,\n\t\t\t\t\t  InvalidOid, /* indexRelationId */\n\t\t\t\t\t  InvalidOid, /* parentIndexId */\n\t\t\t\t\t  InvalidOid, /* parentConstraintId */\n\t\t\t\t\t  -1,\t\t  /* total_parts */\n\t\t\t\t\t  false,\t  /* is_alter_table */\n\t\t\t\t\t  false,\t  /* check_rights */\n\t\t\t\t\t  false,\t  /* check_not_in_use */\n\t\t\t\t\t  false,\t  /* skip_build */\n\t\t\t\t\t  true);\t  /* quiet */\n}\n\nstatic const Node *\nget_open_dim_expr(const Dimension *dim)\n{\n\tif (dim == NULL || dim->partitioning == NULL)\n\t\treturn NULL;\n\n\treturn dim->partitioning->partfunc.func_fmgr.fn_expr;\n}\n\nstatic const char *\nget_open_dim_name(const Dimension *dim)\n{\n\tif (dim == NULL || dim->partitioning != NULL)\n\t\treturn NULL;\n\n\treturn NameStr(dim->fd.column_name);\n}\n\nstatic void\ncreate_default_indexes(const Hypertable *ht, const Dimension *time_dim, const Dimension *space_dim,\n\t\t\t\t\t   bool has_time_idx, bool has_time_space_idx)\n{\n\tconst char *dimname = get_open_dim_name(time_dim);\n\tIndexElem telem = {\n\t\t.type = T_IndexElem,\n\t\t.name = dimname ? (char *) dimname : NULL,\n\t\t.ordering = SORTBY_DESC,\n\t\t.expr = (Node *) get_open_dim_expr(time_dim),\n\t};\n\n\t/* In case we'd allow tables that are only space partitioned */\n\tif (NULL == time_dim)\n\t\treturn;\n\n\t/* Create (\"time\") index */\n\tif (!has_time_idx)\n\t\tcreate_default_index(ht, list_make1(&telem));\n\n\t/* Create (\"space\", \"time\") index */\n\tif (space_dim != NULL && !has_time_space_idx)\n\t{\n\t\tIndexElem selem = {\n\t\t\t.type = T_IndexElem,\n\t\t\t.name = pstrdup(NameStr(space_dim->fd.column_name)),\n\t\t\t.ordering = SORTBY_ASC,\n\t\t};\n\n\t\tcreate_default_index(ht, list_make2(&selem, &telem));\n\t}\n}\n\n/*\n * Verify that unique, primary and exclusion indexes on a hypertable cover all\n * partitioning columns and create any default indexes.\n *\n * Default indexes are assumed to cover the first open (\"time\") dimension, and,\n * optionally, the first closed (\"space\") dimension.\n */\nstatic void\nindexing_create_and_verify_hypertable_indexes(const Hypertable *ht, bool create_default,\n\t\t\t\t\t\t\t\t\t\t\t  bool verify)\n{\n\tRelation tblrel = table_open(ht->main_table_relid, AccessShareLock);\n\tconst Dimension *time_dim = ts_hyperspace_get_dimension(ht->space, DIMENSION_TYPE_OPEN, 0);\n\tconst Dimension *space_dim = ts_hyperspace_get_dimension(ht->space, DIMENSION_TYPE_CLOSED, 0);\n\tList *indexlist = RelationGetIndexList(tblrel);\n\tbool has_time_idx = false;\n\tbool has_time_space_idx = false;\n\tListCell *lc;\n\n\tforeach (lc, indexlist)\n\t{\n\t\tRelation idxrel = index_open(lfirst_oid(lc), AccessShareLock);\n\n\t\tif (verify && (idxrel->rd_index->indisunique || idxrel->rd_index->indisexclusion))\n\t\t\tts_indexing_verify_columns(ht->space, build_indexcolumn_list(idxrel));\n\n\t\t/* Check for existence of \"default\" indexes */\n\t\tif (create_default && NULL != time_dim)\n\t\t{\n\t\t\tForm_pg_attribute idxattr_time, idxattr_space;\n\n\t\t\tswitch (idxrel->rd_att->natts)\n\t\t\t{\n\t\t\t\tcase 1:\n\t\t\t\t\t/* (\"time\") index */\n\t\t\t\t\tidxattr_time = TupleDescAttr(idxrel->rd_att, 0);\n\t\t\t\t\tif (namestrcmp(&idxattr_time->attname, NameStr(time_dim->fd.column_name)) == 0)\n\t\t\t\t\t\thas_time_idx = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 2:\n\t\t\t\t\t/* (\"space\", \"time\") index */\n\t\t\t\t\tidxattr_space = TupleDescAttr(idxrel->rd_att, 0);\n\t\t\t\t\tidxattr_time = TupleDescAttr(idxrel->rd_att, 1);\n\t\t\t\t\tif (space_dim != NULL &&\n\t\t\t\t\t\tnamestrcmp(&idxattr_space->attname, NameStr(space_dim->fd.column_name)) ==\n\t\t\t\t\t\t\t0 &&\n\t\t\t\t\t\tnamestrcmp(&idxattr_time->attname, NameStr(time_dim->fd.column_name)) == 0)\n\t\t\t\t\t\thas_time_space_idx = true;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tindex_close(idxrel, AccessShareLock);\n\t}\n\n\tif (create_default)\n\t\tcreate_default_indexes(ht, time_dim, space_dim, has_time_idx, has_time_space_idx);\n\n\ttable_close(tblrel, AccessShareLock);\n}\n\nbool TSDLLEXPORT\nts_indexing_relation_has_primary_or_unique_index(Relation htrel)\n{\n\tList *indexoidlist = RelationGetIndexList(htrel);\n\tListCell *lc;\n\tbool result = false;\n\n\tif (OidIsValid(htrel->rd_pkindex))\n\t\treturn true;\n\n\tforeach (lc, indexoidlist)\n\t{\n\t\tOid indexoid = lfirst_oid(lc);\n\t\tHeapTuple index_tuple;\n\t\tForm_pg_index index;\n\n\t\tindex_tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));\n\t\tif (!HeapTupleIsValid(index_tuple)) /* should not happen */\n\t\t\telog(ERROR,\n\t\t\t\t \"cache lookup failed for index %u in \\\"%s\\\" \",\n\t\t\t\t indexoid,\n\t\t\t\t RelationGetRelationName(htrel));\n\t\tindex = (Form_pg_index) GETSTRUCT(index_tuple);\n\t\tresult = index->indisunique;\n\t\tReleaseSysCache(index_tuple);\n\t\tif (result)\n\t\t\tbreak;\n\t}\n\n\tlist_free(indexoidlist);\n\treturn result;\n}\n\n/* create the index on the root table of a hypertable.\n * based on postgres CREATE INDEX\n * https://github.com/postgres/postgres/blob/ebfe20dc706bd3238a9bdf3b44cd8f82337e86a8/src/backend/tcop/utility.c#L1291-L1374\n * despite not allowing CONCURRENT index creation now, we expect to do so soon, so this code\n * retains those code paths\n */\nextern ObjectAddress\nts_indexing_root_table_create_index(IndexStmt *stmt, const char *queryString,\n\t\t\t\t\t\t\t\t\tbool is_multitransaction)\n{\n\tOid relid;\n\tLOCKMODE lockmode;\n\tObjectAddress root_table_address;\n\tint total_parts = -1;\n\n\tif (stmt->concurrent)\n\t\tPreventInTransactionBlock(true, \"CREATE INDEX CONCURRENTLY\");\n\n\t/*\n\t * Look up the relation OID just once, right here at the\n\t * beginning, so that we don't end up repeating the name\n\t * lookup later and latching onto a different relation\n\t * partway through.  To avoid lock upgrade hazards, it's\n\t * important that we take the strongest lock that will\n\t * eventually be needed here, so the lockmode calculation\n\t * needs to match what DefineIndex() does.\n\t */\n\tlockmode = stmt->concurrent ? ShareUpdateExclusiveLock : ShareLock;\n\trelid =\n\t\tRangeVarGetRelidExtended(stmt->relation, lockmode, 0, RangeVarCallbackOwnsRelation, NULL);\n\n\t/*\n\t * single-transaction CREATE INDEX on a hypertable tables recurses to\n\t * chunks, so we must acquire locks early to avoid deadlocks.\n\t *\n\t * We also take the opportunity to verify that all\n\t * chunks are something we can put an index on, to\n\t * avoid building some indexes only to fail later.\n\t */\n\tif (!is_multitransaction)\n\t{\n\t\tListCell *lc;\n\t\tList *inheritors = NIL;\n\n\t\tinheritors = find_all_inheritors(relid, lockmode, NULL);\n\t\tforeach (lc, inheritors)\n\t\t{\n\t\t\tchar relkind = get_rel_relkind(lfirst_oid(lc));\n\n\t\t\tif (relkind != RELKIND_RELATION && relkind != RELKIND_MATVIEW &&\n\t\t\t\trelkind != RELKIND_FOREIGN_TABLE &&\n\t\t\t\t!(relkind == RELKIND_PARTITIONED_TABLE && ts_guc_enable_partitioned_hypertables))\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),\n\t\t\t\t\t\t errmsg(\"cannot create index on hypertable \\\"%s\\\"\",\n\t\t\t\t\t\t\t\tstmt->relation->relname),\n\t\t\t\t\t\t errdetail(\"Table \\\"%s\\\" contains chunks of the wrong type.\",\n\t\t\t\t\t\t\t\t   stmt->relation->relname)));\n\t\t}\n\t\ttotal_parts = list_length(inheritors) - 1;\n\t\tlist_free(inheritors);\n\t}\n\n\t/* Run parse analysis ... */\n\tstmt = transformIndexStmt(relid, stmt, queryString);\n\n\t/* ... and do it */\n\tEventTriggerAlterTableStart((Node *) stmt);\n\n\t(void) total_parts;\n\troot_table_address = DefineIndexCompat(relid, /* OID of heap relation */\n\t\t\t\t\t\t\t\t\t\t   stmt,\n\t\t\t\t\t\t\t\t\t\t   InvalidOid,\t/* no predefined OID */\n\t\t\t\t\t\t\t\t\t\t   InvalidOid,\t/* parentIndexId */\n\t\t\t\t\t\t\t\t\t\t   InvalidOid,\t/* parentConstraintId */\n\t\t\t\t\t\t\t\t\t\t   total_parts, /* total_parts */\n\t\t\t\t\t\t\t\t\t\t   false,\t\t/* is_alter_table */\n\t\t\t\t\t\t\t\t\t\t   true,\t\t/* check_rights */\n\t\t\t\t\t\t\t\t\t\t   false,\t\t/* check_not_in_use */\n\t\t\t\t\t\t\t\t\t\t   false,\t\t/* skip_build */\n\t\t\t\t\t\t\t\t\t\t   false);\t\t/* quiet */\n\n\treturn root_table_address;\n}\n\nvoid\nts_indexing_verify_indexes(const Hypertable *ht)\n{\n\tindexing_create_and_verify_hypertable_indexes(ht, false, true);\n}\n\nvoid\nts_indexing_create_default_indexes(const Hypertable *ht)\n{\n\tindexing_create_and_verify_hypertable_indexes(ht, true, false);\n}\n\nTSDLLEXPORT Oid\nts_indexing_find_clustered_index(Oid table_relid)\n{\n\tRelation rel;\n\tListCell *index;\n\tOid index_relid = InvalidOid;\n\n\trel = table_open(table_relid, AccessShareLock);\n\n\t/* We need to find the index that has indisclustered set. */\n\tforeach (index, RelationGetIndexList(rel))\n\t{\n\t\tHeapTuple idxtuple;\n\t\tForm_pg_index indexForm;\n\n\t\tindex_relid = lfirst_oid(index);\n\t\tidxtuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_relid));\n\t\tif (!HeapTupleIsValid(idxtuple))\n\t\t\telog(ERROR,\n\t\t\t\t \"cache lookup failed for index %u when looking for a clustered index\",\n\t\t\t\t index_relid);\n\t\tindexForm = (Form_pg_index) GETSTRUCT(idxtuple);\n\n\t\tif (indexForm->indisclustered)\n\t\t{\n\t\t\tReleaseSysCache(idxtuple);\n\t\t\tbreak;\n\t\t}\n\t\tReleaseSysCache(idxtuple);\n\t\tindex_relid = InvalidOid;\n\t}\n\n\ttable_close(rel, AccessShareLock);\n\n\treturn index_relid;\n}\n\ntypedef enum IndexValidity\n{\n\tIndexInvalid = 0,\n\tIndexValid,\n} IndexValidity;\n\nstatic bool\nts_indexing_mark_as(Oid index_id, IndexValidity validity)\n{\n\tRelation pg_index;\n\tHeapTuple indexTuple;\n\tHeapTuple new_tuple;\n\tForm_pg_index indexForm;\n\tbool was_valid;\n\n\t/* Open pg_index and fetch a writable copy of the index's tuple */\n\tpg_index = table_open(IndexRelationId, RowExclusiveLock);\n\n\tindexTuple = SearchSysCacheCopy1(INDEXRELID, ObjectIdGetDatum(index_id));\n\n\tif (!HeapTupleIsValid(indexTuple))\n\t\telog(ERROR, \"cache lookup failed when marking index %u\", index_id);\n\n\tnew_tuple = heap_copytuple(indexTuple);\n\tindexForm = (Form_pg_index) GETSTRUCT(new_tuple);\n\n\twas_valid = indexForm->indisvalid;\n\n\t/* Perform the requested state change on the copy */\n\tswitch (validity)\n\t{\n\t\tcase IndexValid:\n\t\t\tAssert(indexForm->indislive);\n\t\t\tAssert(indexForm->indisready);\n\t\t\tindexForm->indisvalid = true;\n\t\t\tbreak;\n\t\tcase IndexInvalid:\n\t\t\tindexForm->indisvalid = false;\n\t\t\tindexForm->indisclustered = false;\n\t\t\tbreak;\n\t}\n\n\t/* ... and write it back */\n\tCatalogTupleUpdate(pg_index, &indexTuple->t_self, new_tuple);\n\n\ttable_close(pg_index, RowExclusiveLock);\n\treturn was_valid;\n}\n\nvoid\nts_indexing_mark_as_valid(Oid index_id)\n{\n\tts_indexing_mark_as(index_id, IndexValid);\n}\n\n/* returns if the index was valid */\nbool\nts_indexing_mark_as_invalid(Oid index_id)\n{\n\treturn ts_indexing_mark_as(index_id, IndexInvalid);\n}\n\nTS_FUNCTION_INFO_V1(ts_index_matches);\nDatum\nts_index_matches(PG_FUNCTION_ARGS)\n{\n\tOid index1 = PG_GETARG_OID(0);\n\tOid index2 = PG_GETARG_OID(1);\n\tbool result;\n\n\tif (!OidIsValid(index1) || !OidIsValid(index2))\n\t\tereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"invalid index\")));\n\n\tresult = ts_indexing_compare(index1, index2);\n\n\tPG_RETURN_BOOL(result);\n}\n\n/* Returns true if the indexes are equivalent */\nbool\nts_indexing_compare(Oid index1, Oid index2)\n{\n\tRelation indexrel1 = index_open(index1, AccessShareLock);\n\tRelation indexrel2 = index_open(index2, AccessShareLock);\n\tRelation rel1 = table_open(indexrel1->rd_index->indrelid, AccessShareLock);\n\tRelation rel2 = table_open(indexrel2->rd_index->indrelid, AccessShareLock);\n\n\tif (indexrel1->rd_rel->relkind != RELKIND_INDEX || indexrel2->rd_rel->relkind != RELKIND_INDEX)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_WRONG_OBJECT_TYPE),\n\t\t\t\t errmsg(\"expected both \\\"%s\\\" and \\\"%s\\\" to be indexes\",\n\t\t\t\t\t\tRelationGetRelationName(indexrel1),\n\t\t\t\t\t\tRelationGetRelationName(indexrel2))));\n\t}\n\n\tIndexInfo *info1 = BuildIndexInfo(indexrel1);\n\tIndexInfo *info2 = BuildIndexInfo(indexrel2);\n\n#if PG16_GE\n\tAttrMap *attmap = build_attrmap_by_name(RelationGetDescr(rel1), RelationGetDescr(rel2), false);\n#else\n\tAttrMap *attmap = build_attrmap_by_name(RelationGetDescr(rel1), RelationGetDescr(rel2));\n#endif\n\n\tbool result = CompareIndexInfo(info1,\n\t\t\t\t\t\t\t\t   info2,\n\t\t\t\t\t\t\t\t   indexrel1->rd_indcollation,\n\t\t\t\t\t\t\t\t   indexrel2->rd_indcollation,\n\t\t\t\t\t\t\t\t   indexrel1->rd_opfamily,\n\t\t\t\t\t\t\t\t   indexrel2->rd_opfamily,\n\t\t\t\t\t\t\t\t   attmap);\n\n\tif (result)\n\t{\n\t\t/*\n\t\t * CompareIndexInfo does not compare indoption, which means it will\n\t\t * consider two indexes with different ASC/DESC or NULLS FIRST/LAST\n\t\t * options as equivalent.\n\t\t */\n\t\tfor (int i = 0; i < IndexRelationGetNumberOfKeyAttributes(indexrel1); i++)\n\t\t{\n\t\t\tif (indexrel1->rd_indoption[i] != indexrel2->rd_indoption[i])\n\t\t\t{\n\t\t\t\tresult = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tindex_close(indexrel1, NoLock);\n\tindex_close(indexrel2, NoLock);\n\ttable_close(rel1, NoLock);\n\ttable_close(rel2, NoLock);\n\n\treturn result;\n}\n"
  },
  {
    "path": "src/indexing.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <nodes/parsenodes.h>\n#include <nodes/pg_list.h>\n\n#include \"dimension.h\"\n#include \"export.h\"\n\nextern void ts_indexing_verify_columns(const Hyperspace *hs, const List *indexelems);\nextern void ts_indexing_verify_index(const Hyperspace *hs, const IndexStmt *stmt);\nextern void ts_indexing_verify_indexes(const Hypertable *ht);\nextern void ts_indexing_create_default_indexes(const Hypertable *ht);\nextern ObjectAddress ts_indexing_root_table_create_index(IndexStmt *stmt, const char *queryString,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t bool is_multitransaction);\nextern TSDLLEXPORT Oid ts_indexing_find_clustered_index(Oid table_relid);\n\nextern void ts_indexing_mark_as_valid(Oid index_id);\nextern bool ts_indexing_mark_as_invalid(Oid index_id);\nextern bool TSDLLEXPORT ts_indexing_relation_has_primary_or_unique_index(Relation htrel);\nextern TSDLLEXPORT bool ts_indexing_compare(Oid index1, Oid index2);\nextern TSDLLEXPORT Datum ts_index_matches(PG_FUNCTION_ARGS);\n"
  },
  {
    "path": "src/init.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n\n#include <access/xact.h>\n#include <commands/extension.h>\n#include <miscadmin.h>\n#include <parser/analyze.h>\n#include <storage/ipc.h>\n#include <utils/guc.h>\n\n#include \"compat/compat.h\"\n#include \"bgw/launcher_interface.h\"\n#include \"config.h\"\n#include \"extension.h\"\n#include \"guc.h\"\n#include \"license_guc.h\"\n#include \"nodes/constraint_aware_append/constraint_aware_append.h\"\n#include \"partition_chunk.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"version.h\"\n\n#ifdef PG_MODULE_MAGIC\nPG_MODULE_MAGIC;\n#endif\n\nextern void _hypertable_cache_init(void);\nextern void _hypertable_cache_fini(void);\n\nextern void _cache_invalidate_init(void);\nextern void _cache_invalidate_fini(void);\n\nextern void _planner_init(void);\nextern void _planner_fini(void);\n\nextern void _process_utility_init(void);\nextern void _process_utility_fini(void);\n\nextern void _event_trigger_init(void);\nextern void _event_trigger_fini(void);\n\nextern void _conn_plain_init();\nextern void _conn_plain_fini();\n\nextern void _executor_init(void);\nextern void _executor_fini(void);\n\n#ifdef TS_USE_OPENSSL\nextern void _conn_ssl_init();\nextern void _conn_ssl_fini();\n#endif\n\n#ifdef TS_DEBUG\nextern void _conn_mock_init();\nextern void _conn_mock_fini();\n#endif\n\nextern void _chunk_append_init();\n\n#if PG16_LT\nextern void TSDLLEXPORT _PG_init(void);\n#endif\n\nTS_FUNCTION_INFO_V1(ts_post_load_init);\n\n/* Called when the backend exits */\nstatic void\ncleanup_on_pg_proc_exit(int code, Datum arg)\n{\n\t/*\n\t * Order of items should be strict reverse order of _PG_init. Please\n\t * document any exceptions.\n\t */\n#ifdef TS_DEBUG\n\t_conn_mock_fini();\n#endif\n#ifdef TS_USE_OPENSSL\n\t_conn_ssl_fini();\n#endif\n\t_conn_plain_fini();\n\t_process_utility_fini();\n\t_event_trigger_fini();\n\t_planner_fini();\n\t_cache_invalidate_fini();\n\t_hypertable_cache_fini();\n\t_cache_fini();\n\t_executor_fini();\n}\n\nvoid\n_PG_init(void)\n{\n\tstatic bool init_done = false;\n\n\t/*\n\t * Check extension_is loaded to catch certain errors such as calls to\n\t * functions defined on the wrong extension version\n\t */\n\tts_extension_check_version(TIMESCALEDB_VERSION_MOD);\n\tts_extension_check_server_version();\n\tts_bgw_check_loader_api_version();\n\n\t/* We can call _PG_init() several times if we do an eager load, so abort\n\t * init if we do. */\n\n\tif (init_done)\n\t\treturn;\n\n\t_cache_init();\n\t_hypertable_cache_init();\n\t_cache_invalidate_init();\n\t_planner_init();\n\t_constraint_aware_append_init();\n\t_chunk_append_init();\n\t_event_trigger_init();\n\t_process_utility_init();\n\t_guc_init();\n\t_conn_plain_init();\n\t_executor_init();\n#ifdef TS_USE_OPENSSL\n\t_conn_ssl_init();\n#endif\n#ifdef TS_DEBUG\n\t_conn_mock_init();\n#endif\n\n\t/* Register a cleanup function to be called when the backend exits */\n\ton_proc_exit(cleanup_on_pg_proc_exit, 0);\n\tinit_done = true;\n}\n\nTSDLLEXPORT Datum\nts_post_load_init(PG_FUNCTION_ARGS)\n{\n\t/*\n\t * Unfortunately, if we load the tsl during _PG_init parallel workers try\n\t * to load the tsl before timescale itself, causing link-time errors. To\n\t * prevent this we defer loading until here.\n\t */\n\tts_license_enable_module_loading();\n\n\tPG_RETURN_VOID();\n}\n"
  },
  {
    "path": "src/jsonb_utils.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n\n#include <common/jsonapi.h>\n#include <fmgr.h>\n#include <utils/builtins.h>\n#include <utils/fmgroids.h>\n#include <utils/json.h>\n#include <utils/jsonb.h>\n\n#include \"compat/compat.h\"\n#include \"export.h\"\n#include \"jsonb_utils.h\"\n#include \"utils.h\"\n\nstatic void ts_jsonb_add_pair(JsonbParseState *state, JsonbValue *key, JsonbValue *value);\n\nvoid\nts_jsonb_add_null(JsonbParseState *state, const char *key)\n{\n\tJsonbValue json_value;\n\n\tjson_value.type = jbvNull;\n\tts_jsonb_add_value(state, key, &json_value);\n}\n\nvoid\nts_jsonb_add_bool(JsonbParseState *state, const char *key, bool boolean)\n{\n\tJsonbValue json_value;\n\n\tjson_value.type = jbvBool;\n\tjson_value.val.boolean = boolean;\n\n\tts_jsonb_add_value(state, key, &json_value);\n}\n\nvoid\nts_jsonb_add_str(JsonbParseState *state, const char *key, const char *value)\n{\n\tJsonbValue json_value;\n\n\tAssert(value != NULL);\n\t/* If there is a null entry, don't add it to the JSON */\n\tif (value == NULL)\n\t\treturn;\n\n\tjson_value.type = jbvString;\n\tjson_value.val.string.val = (char *) value;\n\tjson_value.val.string.len = strlen(value);\n\n\tts_jsonb_add_value(state, key, &json_value);\n}\n\nstatic void\nts_jsonb_add_str_element(JsonbParseState *state, const char *elem)\n{\n\tJsonbValue json_value;\n\n\tAssert(elem != NULL);\n\t/* If there is a null entry, don't add it to the JSON */\n\tif (elem == NULL)\n\t\treturn;\n\n\tjson_value.type = jbvString;\n\tjson_value.val.string.val = (char *) elem;\n\tjson_value.val.string.len = strlen(elem);\n\n\tpushJsonbValue(&state, WJB_ELEM, &json_value);\n}\n\nvoid\nts_jsonb_add_str_array(JsonbParseState *state, const char *key, const char **values, int num_values)\n{\n\tJsonbValue json_key;\n\tAssert(key != NULL);\n\tAssert(values != NULL);\n\tAssert(num_values > 0);\n\tAssert(key[0] != '\\0');\n\tif (key == NULL || values == NULL || num_values <= 0 || key[0] == '\\0')\n\t\treturn;\n\n\tjson_key.type = jbvString;\n\tjson_key.val.string.val = (char *) key;\n\tjson_key.val.string.len = strlen(key);\n\tpushJsonbValue(&state, WJB_KEY, &json_key);\n\n\tpushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);\n\tfor (int i = 0; i < num_values; i++)\n\t{\n\t\tif (values[i] == NULL || values[i][0] == '\\0')\n\t\t\tcontinue;\n\t\tts_jsonb_add_str_element(state, values[i]);\n\t}\n\tpushJsonbValue(&state, WJB_END_ARRAY, NULL);\n}\n\nstatic PGFunction\nget_convert_func(Oid typeid)\n{\n\tswitch (typeid)\n\t{\n\t\tcase INT2OID:\n\t\t\treturn int2_numeric;\n\t\tcase INT4OID:\n\t\t\treturn int4_numeric;\n\t\tcase INT8OID:\n\t\t\treturn int8_numeric;\n\t\tdefault:\n\t\t\treturn NULL;\n\t}\n}\n\nvoid\nts_jsonb_set_value_by_type(JsonbValue *value, Oid typeid, Datum datum)\n{\n\tswitch (typeid)\n\t{\n\t\tcase INT2OID:\n\t\tcase INT4OID:\n\t\tcase INT8OID:\n\t\tcase NUMERICOID:\n\t\t{\n\t\t\tPGFunction func = get_convert_func(typeid);\n\t\t\tvalue->type = jbvNumeric;\n\t\t\tvalue->val.numeric = DatumGetNumeric(func ? DirectFunctionCall1(func, datum) : datum);\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t{\n\t\t\tchar *str = ts_datum_to_string(datum, typeid);\n\t\t\tvalue->type = jbvString;\n\t\t\tvalue->val.string.val = str;\n\t\t\tvalue->val.string.len = strlen(str);\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nvoid\nts_jsonb_add_int32(JsonbParseState *state, const char *key, const int32 int_value)\n{\n\tJsonbValue json_value;\n\n\tts_jsonb_set_value_by_type(&json_value, INT4OID, Int32GetDatum(int_value));\n\tts_jsonb_add_value(state, key, &json_value);\n}\n\nvoid\nts_jsonb_add_int64(JsonbParseState *state, const char *key, const int64 int_value)\n{\n\tJsonbValue json_value;\n\n\tts_jsonb_set_value_by_type(&json_value, INT8OID, Int64GetDatum(int_value));\n\tts_jsonb_add_value(state, key, &json_value);\n}\n\nvoid\nts_jsonb_add_interval(JsonbParseState *state, const char *key, Interval *interval)\n{\n\tJsonbValue json_value;\n\n\tts_jsonb_set_value_by_type(&json_value, INTERVALOID, IntervalPGetDatum(interval));\n\tts_jsonb_add_value(state, key, &json_value);\n}\n\nvoid\nts_jsonb_add_value(JsonbParseState *state, const char *key, JsonbValue *value)\n{\n\tJsonbValue json_key;\n\n\tAssert(key != NULL);\n\tif (value == NULL)\n\t\treturn;\n\n\tjson_key.type = jbvString;\n\tjson_key.val.string.val = (char *) key;\n\tjson_key.val.string.len = strlen(key);\n\n\tts_jsonb_add_pair(state, &json_key, value);\n}\n\nstatic void\nts_jsonb_add_pair(JsonbParseState *state, JsonbValue *key, JsonbValue *value)\n{\n\tAssert(state != NULL);\n\tAssert(key != NULL);\n\tif (value == NULL)\n\t\treturn;\n\n\tpushJsonbValue(&state, WJB_KEY, key);\n\tpushJsonbValue(&state, WJB_VALUE, value);\n}\n\nchar *\nts_jsonb_get_str_field(const Jsonb *jsonb, const char *key)\n{\n\t/*\n\t * `jsonb_object_field_text` returns NULL when the field is not found so\n\t * we cannot use `DirectFunctionCall`\n\t */\n\tLOCAL_FCINFO(fcinfo, 2);\n\tDatum result;\n\n\tInitFunctionCallInfoData(*fcinfo, NULL, 2, InvalidOid, NULL, NULL);\n\n\tFC_SET_ARG(fcinfo, 0, PointerGetDatum(jsonb));\n\tFC_SET_ARG(fcinfo, 1, PointerGetDatum(cstring_to_text(key)));\n\n\tresult = jsonb_object_field_text(fcinfo);\n\n\tif (fcinfo->isnull)\n\t\treturn NULL;\n\n\treturn text_to_cstring(DatumGetTextP(result));\n}\n\nbool\nts_jsonb_get_bool_field(const Jsonb *json, const char *key, bool *field_found)\n{\n\tDatum bool_datum;\n\tchar *bool_str = ts_jsonb_get_str_field(json, key);\n\n\tif (bool_str == NULL)\n\t{\n\t\t*field_found = false;\n\t\treturn false;\n\t}\n\n\tbool_datum = DirectFunctionCall1(boolin, CStringGetDatum(bool_str));\n\n\t*field_found = true;\n\treturn DatumGetBool(bool_datum);\n}\n\nint32\nts_jsonb_get_int32_field(const Jsonb *json, const char *key, bool *field_found)\n{\n\tDatum int_datum;\n\tchar *int_str = ts_jsonb_get_str_field(json, key);\n\n\tif (int_str == NULL)\n\t{\n\t\t*field_found = false;\n\t\treturn 0;\n\t}\n\n\tint_datum = DirectFunctionCall1(int4in, CStringGetDatum(int_str));\n\n\t*field_found = true;\n\treturn DatumGetInt32(int_datum);\n}\n\nint64\nts_jsonb_get_int64_field(const Jsonb *json, const char *key, bool *field_found)\n{\n\tDatum int_datum;\n\tchar *int_str = ts_jsonb_get_str_field(json, key);\n\n\tif (int_str == NULL)\n\t{\n\t\t*field_found = false;\n\t\treturn 0;\n\t}\n\n\tint_datum = DirectFunctionCall1(int8in, CStringGetDatum(int_str));\n\n\t*field_found = true;\n\treturn DatumGetInt64(int_datum);\n}\n\nInterval *\nts_jsonb_get_interval_field(const Jsonb *json, const char *key)\n{\n\tDatum interval_datum;\n\tchar *interval_str = ts_jsonb_get_str_field(json, key);\n\n\tif (interval_str == NULL)\n\t\treturn NULL;\n\n\tinterval_datum =\n\t\tDirectFunctionCall3(interval_in, CStringGetDatum(interval_str), InvalidOid, -1);\n\n\treturn DatumGetIntervalP(interval_datum);\n}\n\nbool\nts_jsonb_equal(const Jsonb *left, const Jsonb *right)\n{\n\t/* Quick exit if both are NULL or point to same thing. */\n\tif (left == right)\n\t\treturn true;\n\n\tif (left == NULL || right == NULL)\n\t\treturn false;\n\n\tAssert(left != NULL && right != NULL);\n\n\tDatum result = DirectFunctionCall2(jsonb_eq, PointerGetDatum(left), PointerGetDatum(right));\n\n\treturn DatumGetBool(result);\n}\n\n/*\n * searches for any occurrences of a matching key value pair. compatible with nested and\n * array jsonbs\n */\nbool\nts_jsonb_has_key_value_str_field(Jsonb *jb, const char *key, const char *value)\n{\n\tJsonbIterator *it;\n\tJsonbValue v;\n\tJsonbIteratorToken r;\n\n\tif (jb == NULL || JB_ROOT_COUNT(jb) == 0)\n\t\treturn false;\n\n\tif (JB_ROOT_IS_SCALAR(jb))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"cannot find from scalar\")));\n\n\tit = JsonbIteratorInit(&jb->root);\n\n\twhile ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)\n\t{\n\t\tif (r == WJB_KEY && v.type == jbvString && ((int) strlen(key) == v.val.string.len) &&\n\t\t\tstrncmp(key, v.val.string.val, v.val.string.len) == 0)\n\t\t{\n\t\t\tr = JsonbIteratorNext(&it, &v, false);\n\t\t\tAssert(r == WJB_VALUE || r == WJB_BEGIN_ARRAY);\n\n\t\t\tif (v.type == jbvArray)\n\t\t\t{\n\t\t\t\t/* iterate over the array members and consume them all as this function should only\n\t\t\t\t * match single values and not arrays */\n\t\t\t\tint i = 0;\n\t\t\t\tint n_elems = v.val.array.nElems;\n\t\t\t\twhile (i < n_elems && (r = JsonbIteratorNext(&it, &v, false)) == WJB_ELEM)\n\t\t\t\t{\n\t\t\t\t\t++i;\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\telse if (v.type != jbvString)\n\t\t\t{\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t\t errmsg(\"Jsonb value is of type \\\"%s\\\", but expected type \\\"string\\\"\",\n\t\t\t\t\t\t\t\tJsonbTypeName(&v))));\n\t\t\t}\n\n\t\t\tif (v.type == jbvString && ((int) strlen(value) == v.val.string.len) &&\n\t\t\t\tstrncmp(value, v.val.string.val, v.val.string.len) == 0)\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false;\n}\n"
  },
  {
    "path": "src/jsonb_utils.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <utils/datetime.h>\n#include <utils/json.h>\n#include <utils/jsonb.h>\n\n#include \"export.h\"\n\nextern TSDLLEXPORT void ts_jsonb_add_null(JsonbParseState *state, const char *key);\nextern TSDLLEXPORT void ts_jsonb_add_bool(JsonbParseState *state, const char *key, bool boolean);\nextern TSDLLEXPORT void ts_jsonb_add_str(JsonbParseState *state, const char *key,\n\t\t\t\t\t\t\t\t\t\t const char *value);\nextern TSDLLEXPORT void ts_jsonb_add_str_array(JsonbParseState *state, const char *key,\n\t\t\t\t\t\t\t\t\t\t\t   const char **values, int num_values);\nextern TSDLLEXPORT void ts_jsonb_add_interval(JsonbParseState *state, const char *key,\n\t\t\t\t\t\t\t\t\t\t\t  Interval *interval);\nextern TSDLLEXPORT void ts_jsonb_add_int32(JsonbParseState *state, const char *key,\n\t\t\t\t\t\t\t\t\t\t   const int32 value);\nextern TSDLLEXPORT void ts_jsonb_add_int64(JsonbParseState *state, const char *key,\n\t\t\t\t\t\t\t\t\t\t   const int64 value);\nextern TSDLLEXPORT void ts_jsonb_set_value_by_type(JsonbValue *value, Oid typeid, Datum datum);\n\nextern void ts_jsonb_add_value(JsonbParseState *state, const char *key, JsonbValue *value);\n\nextern TSDLLEXPORT char *ts_jsonb_get_str_field(const Jsonb *jsonb, const char *key);\nextern TSDLLEXPORT Interval *ts_jsonb_get_interval_field(const Jsonb *jsonb, const char *key);\nextern TSDLLEXPORT bool ts_jsonb_get_bool_field(const Jsonb *json, const char *key,\n\t\t\t\t\t\t\t\t\t\t\t\tbool *field_found);\nextern TSDLLEXPORT int32 ts_jsonb_get_int32_field(const Jsonb *json, const char *key,\n\t\t\t\t\t\t\t\t\t\t\t\t  bool *field_found);\nextern TSDLLEXPORT int64 ts_jsonb_get_int64_field(const Jsonb *json, const char *key,\n\t\t\t\t\t\t\t\t\t\t\t\t  bool *field_found);\nextern TSDLLEXPORT bool ts_jsonb_equal(const Jsonb *left, const Jsonb *right);\nextern TSDLLEXPORT bool ts_jsonb_has_key_value_str_field(Jsonb *jb, const char *key,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t const char *value);\n"
  },
  {
    "path": "src/license_guc.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <catalog/pg_authid.h>\n#include <fmgr.h>\n#include <miscadmin.h>\n#include <utils/acl.h>\n#include <utils/builtins.h>\n#include <utils/guc.h>\n\n#include \"cross_module_fn.h\"\n#include \"export.h\"\n#include \"extension_constants.h\"\n#include \"license_guc.h\"\n\nstatic bool load_enabled = false;\nstatic GucSource load_source = PGC_S_DEFAULT;\nstatic void *tsl_handle = NULL;\nstatic PGFunction tsl_init_fn = NULL;\nstatic bool tsl_register_proc_exit = false;\n\n/*\n * License Functions.\n *\n * License validation is performed via guc update-hooks.\n * In this file we check if the type of license supplied warrants loading an\n * additional module.\n *\n * GUC checks work in two parts:\n *\n *   1. In the check function, all validation of the new value is performed\n *      and any auxiliary state is setup but not installed. This function\n *      is not allowed to throw exceptions.\n *\n *   2. In the assign function all user-visible state is installed. This\n *      function *MUST NOT FAIL* as it can be called from such places as\n *      transaction commitment, and will cause database restarts if it fails.\n *\n * Therefore license validation also works in two parts, corresponding to\n * check and assign:\n *\n *   1. In the first stage we check the license type, load the submodule into\n *      memory if needed (but don't link any of the cross-module functions yet).\n *\n *   2. In the second stage we link all of the cross-module functions and init\n *      tsl module.\n *\n * In order for restoring libraries to work (e.g. in parallel workers), loading\n * the submodule must happen strictly after the main timescaledb module is\n * loaded. In order to ensure that the initial value doesn't break this, we\n * disable loading submodules until the post_load_init.\n *\n * No license change from user session is allowed. License can be changed only\n * if it is set from server configuration file or the server command line.\n */\n\ntypedef enum\n{\n\tLICENSE_UNDEF,\n\tLICENSE_APACHE,\n\tLICENSE_TIMESCALE\n} LicenseType;\n\nstatic LicenseType\nlicense_type_of(const char *string)\n{\n\tif (string == NULL)\n\t\treturn LICENSE_UNDEF;\n\tif (strcmp(string, TS_LICENSE_TIMESCALE) == 0)\n\t\treturn LICENSE_TIMESCALE;\n\tif (strcmp(string, TS_LICENSE_APACHE) == 0)\n\t\treturn LICENSE_APACHE;\n\treturn LICENSE_UNDEF;\n}\n\nbool\nts_license_is_apache(void)\n{\n\treturn license_type_of(ts_guc_license) == LICENSE_APACHE;\n}\n\nTSDLLEXPORT void\nts_license_enable_module_loading(void)\n{\n\tint result;\n\n\tif (load_enabled)\n\t\treturn;\n\n\tload_enabled = true;\n\n\t/* re-set the license to actually load the submodule if needed */\n\tresult = set_config_option(MAKE_EXTOPTION(\"license\"),\n\t\t\t\t\t\t\t   ts_guc_license,\n\t\t\t\t\t\t\t   PGC_SUSET,\n\t\t\t\t\t\t\t   load_source,\n\t\t\t\t\t\t\t   GUC_ACTION_SET,\n\t\t\t\t\t\t\t   true,\n\t\t\t\t\t\t\t   0,\n\t\t\t\t\t\t\t   false);\n\n\tif (result <= 0)\n\t\telog(ERROR, \"invalid value for timescaledb.license: \\\"%s\\\"\", ts_guc_license);\n}\n\n/*\n * TSL module load function.\n *\n * Load the module, but do not start it. Set tsl_handle and\n * tsl_init_fn module init function pointer (tsl/src/init.c).\n *\n * This function is idempotent, and will not reload the module\n * if called multiple times.\n */\nstatic bool\ntsl_module_load(void)\n{\n\tvoid *function;\n\tvoid *handle;\n\n\tif (tsl_handle != NULL)\n\t\treturn true;\n\n\tfunction = load_external_function(EXTENSION_TSL_SO, \"ts_module_init\", false, &handle);\n\tif (function == NULL || handle == NULL)\n\t\treturn false;\n\ttsl_init_fn = function;\n\ttsl_handle = handle;\n\t/* the on_proc_exit callback is registered by the tsl_init_fn after load */\n\ttsl_register_proc_exit = true;\n\treturn true;\n}\n\nstatic void\ntsl_module_init(void)\n{\n\tAssert(tsl_handle != NULL);\n\tAssert(tsl_init_fn != NULL);\n\tDirectFunctionCall1(tsl_init_fn, BoolGetDatum(tsl_register_proc_exit));\n\t/* register the on_proc_exit only when the module is reloaded */\n\tif (tsl_register_proc_exit)\n\t\ttsl_register_proc_exit = false;\n}\n\n/*\n * Check hook function set by license guc.\n *\n * Used to validate license string before the assign hook\n * ts_license_guc_assign_hook() call.\n */\nbool\nts_license_guc_check_hook(char **newval, void **extra, GucSource source)\n{\n\tLicenseType type = license_type_of(*newval);\n\n\t/* Allow setting a license only if is is set from postgresql.conf\n\t * or the server command line */\n\tswitch (type)\n\t{\n\t\tcase LICENSE_APACHE:\n\t\tcase LICENSE_TIMESCALE:\n\t\t\tif (source == PGC_S_FILE || source == PGC_S_ARGV || source == PGC_S_DEFAULT)\n\t\t\t\tbreak;\n\t\t\tGUC_check_errdetail(\"Cannot change a license in a running session.\");\n\t\t\tGUC_check_errhint(\n\t\t\t\t\"Change the license in the configuration file or server command line.\");\n\t\t\treturn false;\n\t\tcase LICENSE_UNDEF:\n\t\t\tGUC_check_errdetail(\"Unrecognized license type.\");\n\t\t\tGUC_check_errhint(\"Supported license types are 'timescale' or 'apache'.\");\n\t\t\treturn false;\n\t}\n\n\t/* If loading is delayed, save the GucSource for later retry\n\t * in the ts_license_enable_module_loading() */\n\tif (!load_enabled)\n\t{\n\t\tload_source = source;\n\t\treturn true;\n\t}\n\n\tif (type == LICENSE_TIMESCALE && !tsl_module_load())\n\t{\n\t\tGUC_check_errdetail(\"Could not find TSL timescaledb module.\");\n\t\tGUC_check_errhint(\"Check that \\\"%s\\\" is available.\", EXTENSION_TSL_SO);\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n * Assign hook function set by license guc, executed right after\n * ts_license_guc_check_hook() hook call.\n *\n * Executes tsl module init function (tsl/src/init.c) which sets the\n * cross-module function pointers.\n */\nvoid\nts_license_guc_assign_hook(const char *newval, void *extra)\n{\n\tif (load_enabled && license_type_of(newval) == LICENSE_TIMESCALE)\n\t\ttsl_module_init();\n}\n"
  },
  {
    "path": "src/license_guc.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <fmgr.h>\n#include <utils/guc.h>\n\n#include <export.h>\n#include <guc.h>\n\n#define TS_LICENSE_APACHE \"apache\"\n#define TS_LICENSE_TIMESCALE \"timescale\"\n\n/*\n * If compiled with APACHE_ONLY, default to using only Apache code.\n */\n#ifdef APACHE_ONLY\n#define TS_LICENSE_DEFAULT TS_LICENSE_APACHE\n#else\n#define TS_LICENSE_DEFAULT TS_LICENSE_TIMESCALE\n#endif\n\nextern bool ts_license_guc_check_hook(char **newval, void **extra, GucSource source);\nextern void ts_license_guc_assign_hook(const char *newval, void *extra);\n\nextern TSDLLEXPORT void ts_license_enable_module_loading(void);\nextern bool ts_license_is_apache(void);\n"
  },
  {
    "path": "src/loader/CMakeLists.txt",
    "content": "set(SOURCES\n    loader.c\n    bgw_message_queue.c\n    bgw_counter.c\n    bgw_launcher.c\n    bgw_interface.c\n    function_telemetry.c\n    lwlocks.c)\n\nset(TEST_SOURCES ${PROJECT_SOURCE_DIR}/test/src/symbol_conflict.c)\n\nadd_library(${PROJECT_NAME}-loader MODULE ${SOURCES})\n\nif(CMAKE_BUILD_TYPE MATCHES Debug)\n  # Include code for tests in Debug build\n  target_sources(${PROJECT_NAME}-loader PRIVATE ${TEST_SOURCES})\n  # This define generates extension-specific code for symbol conflict testing\n  target_compile_definitions(${PROJECT_NAME}-loader PUBLIC MODULE_NAME=loader)\nendif(CMAKE_BUILD_TYPE MATCHES Debug)\n\nset_target_properties(${PROJECT_NAME}-loader\n                      PROPERTIES OUTPUT_NAME ${PROJECT_NAME} PREFIX \"\")\n\ninstall(TARGETS ${PROJECT_NAME}-loader DESTINATION ${PG_PKGLIBDIR})\n"
  },
  {
    "path": "src/loader/README.md",
    "content": "# Loader\n\nThe loader has two main purposes:\n\n1) Load the correct versioned library for each database. Multiple databases in\n   the same Postgres instance may have different versions of TimescaleDB\n   installed. The loader is responsible for loading the shared library\n   corresponding to the correct TimescaleDB version for the database as soon as\n   possible. For example, a database containing TimescaleDB version 0.8.0 will\n   have timescaledb-0.8.0.so loaded.\n\n2) Starting a background task called the launcher at server startup. The\n   launcher is responsible for launching schedulers (one for each database) that\n   are responsible for checking whether the TimescaleDB extension is installed\n   in a database. In case of no TimescaleDB extension, the scheduler exits until\n   it is reactivated for that database, which happens, for instance, when the\n   extension is installed. If a scheduler finds an extension, its task is to\n   schedule jobs for that database. The launcher controls when schedulers are\n   started up or shut down in response to events that necessitate such actions.\n   It also instantiates a counter from which TimescaleDB background workers are\n   allocated to be sure we are not using more `worker_processes` than we should.\n\n\n# Messages the launcher may receive\nThe launcher implements a simple message queue to be notified when it should\ntake certain actions, like starting or restarting a scheduler for a given\ndatabase.\n\n## Message types sent to the launcher:\n\n`start`: Used to start the scheduler by the user. It is meant to be an\nidempotent start, as in, if it is run multiple times, it is the same as if it\nwere run once. It is used mainly to reactivate a scheduler that the user had\nstopped. It does not reset the vxid of a scheduler and the started scheduler\nwill not wait on txn finish.\n\n`stop`: Used to stop the scheduler immediately. It does not wait on a vxid and\nit is idempotent. \n\n`restart`: Used to either stop and restart the scheduler if it is running or\nstart it if it is not. Technically, this would be better named `force_restart`\nas that better describes the action to start or restart the scheduler. The\nscheduler is immediately restarted, but waits on the vxid of the txn that sent\nthe message. It is not idempotent, and will restart newly started schedulers,\neven while they are waiting. However, if the scheduler is already started or\nallocated, its \"slot\" is never released back to the pool, so as not to allow a\njob worker to \"steal\" a scheduler's slot during a restart.\n\n## When/which messages are sent:\n\nServer startup: no message sent. However, the launcher takes essentially the\n`start` action for each database (without the message handling/signalling bit).\nIt cannot figure out whether a scheduler should exist for a given database\nbecause it can only connect to shared catalogs. The scheduler is responsible for\nshutting down if it should not exist (because either TimescaleDB is not\ninstalled in the database or the version of TimescaleDB installed does not have\na scheduler function to call). \n\n`CREATE DATABASE`: essentially the same as server startup. The launcher checks\nfor new databases each time it wakes up and will start schedulers for any that\nit has not seen before.\n\n`CREATE EXTENSION`: the create script sends a `restart` message. It does not use\nthe `start` message because we need to wait waiting on the vxid of the process\nthat is running `CREATE EXTENSION`. There is also the possibility that the\nidempotency of the `start` action, even if it waited on a vxid, would cause race\nconditions in cases where the server has just started or the database has been\ncreated. \n\n`ALTER EXTENSION UPDATE`: the pre-update script sends a `restart` message. This\nensures that the current scheduler is shut down as the action starts, it then\nwaits on the vxid of the calling txn to figure out the correct version of the\nextension to use. \n\n`DROP EXTENSION`: sends a `restart` message, which is necessary because a\nrollback of the drop extension command can still happen. The scheduler therefore\nwaits on the vxid of the txn running `DROP EXTENSION` and then will take the\ncorrect action depending on whether the extension exists when the txn finishes.\n\n`DROP DATABASE`: sends a `stop` message, causing immediate shutdown of the\nscheduler. This is necessary as the database cannot be dropped if there are any\nopen connections to it (the scheduler maintains a connection to the db).\n\n# Launcher per-DB state machine\n\nThe following is the state machine that the launcher maintains\nfor each database. The CAPITAL labels are the possible states,\nand the `lowercase` names for messages that trigger the accompanying\ntransitions. Transitions without labels are taken automatically\nwhenever available resources exist.\n```\n\n                   stop\n      ENABLED+--------------+\n         +   ^--------------|\n         |   start/restart ||\n         |                 ||\n         |                 ||\n         v                 +v\n      ALLOCATED+------> DISABLED\n        ^+       stop       ^\n        ||                  |\nrestart ||                  |\n        ||                  |\n        +v                  |\n      STARTED+--------------+\n                stop / scheduler quit\n\n```\n\n## The following is a detailed description of the transitions\n\nNote that `set vxid` sets a vxid variable on the scheduler. This variable is\npassed down to the scheduler and the scheduler waits on that vxid when it first\nstarts. \n\nTransitions that happen automatically (at least once per poll period).\n* `ENABLED->ALLOCATED`: Reserved slot for worker\n* `ALLOCATED->STARTED`: Scheduler started\n* `STARTED->DISABLED`: Iff scheduler has stopped. Release slot.\n\nTransitions that happen upon getting a STOP MESSAGE:\n* `ENABLED->DISABLED`: No action\n* `ALLOCATED->DISABLED`: Release slot.\n* `STARTED->DISABLED`: Terminate scheduler & release slot\n* `DISABLED->DISABLED`: No Action\n\nTransitions that happen upon getting a START MESSAGE\n* Database not yet registered: Register, set to ENABLED and take ENABLED action below.\n* `ENABLED->ENABLED`: Try automatic transitions\n* `ALLOCATED->ALLOCATED`: Try automatic transitions\n* `STARTED->STARTED`: No action\n* `DISABLED->ENABLED`: Try automatic transitions\n\nTransitions that happen upon getting a RESTART MESSAGE\n* Database not yet registered: Register it set to ENABLED, take ENABLED actions\n* `ENABLED->ENABLED`: Set vxid, try automatic transitions\n* `ALLOCATED->ALLOCATED`: Set vxid, try automatic transitions\n* `STARTED->ALLOCATED`: Terminate scheduler, do /not/ release slot, set vxid, then try automatic transitions\n* `DISABLED->ENABLED`: Set vxid, try automatic transitions \n"
  },
  {
    "path": "src/loader/bgw_counter.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n/* needed for initializing shared memory and using various locks */\n#include <postgres.h>\n\n#include <miscadmin.h>\n#include <storage/ipc.h>\n#include <storage/latch.h>\n#include <storage/lwlock.h>\n#include <storage/shmem.h>\n#include <storage/spin.h>\n#include <utils/guc.h>\n#include <utils/hsearch.h>\n\n#include \"bgw_counter.h\"\n#include \"extension_constants.h\"\n\n#define BGW_COUNTER_STATE_NAME \"ts_bgw_counter_state\"\n\nint ts_guc_max_background_workers = 16;\n\n/*\n * We need a bit of shared state here to deal with keeping track of the total\n * number of background workers we've launched across the instance since we\n * don't want to exceed some configured value.  We considered, briefly, the\n * possibility of using pg_sema for this, unfortunately it does not appear to\n * be accessible to code outside of postgres core in any meaningful way. So\n * we're not using that.\n */\ntypedef struct CounterState\n{\n\t/*\n\t * Using an slock because we're only taking it for very brief periods to\n\t * read a single value so no need for an lwlock\n\t */\n\tslock_t mutex; /* controls modification of total_workers */\n\tint total_workers;\n} CounterState;\n\nstatic CounterState *ct = NULL;\n\nstatic void\nbgw_counter_state_init()\n{\n\tbool found;\n\n\tLWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);\n\tct = ShmemInitStruct(BGW_COUNTER_STATE_NAME, sizeof(CounterState), &found);\n\tif (!found)\n\t{\n\t\tmemset(ct, 0, sizeof(CounterState));\n\t\tSpinLockInit(&ct->mutex);\n\t\tct->total_workers = 0;\n\t}\n\tLWLockRelease(AddinShmemInitLock);\n}\n\nextern void\nts_bgw_counter_setup_gucs(void)\n{\n\tDefineCustomIntVariable(MAKE_EXTOPTION(\"max_background_workers\"),\n\t\t\t\t\t\t\t\"Maximum background worker processes allocated to TimescaleDB\",\n\t\t\t\t\t\t\t\"Max background worker processes allocated to TimescaleDB - set to at \"\n\t\t\t\t\t\t\t\"least 1 + number of databases in Postgres instance to use background \"\n\t\t\t\t\t\t\t\"workers \",\n\t\t\t\t\t\t\t&ts_guc_max_background_workers,\n\t\t\t\t\t\t\tts_guc_max_background_workers,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t1000, /* no reasonable way to have more than\n\t\t\t\t\t\t\t\t   * 1000 background workers */\n\t\t\t\t\t\t\tPGC_POSTMASTER,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\tNULL);\n}\n\n/*\n * This gets called by the loader (and therefore the postmaster) at\n * shared_preload_libraries time\n */\nextern void\nts_bgw_counter_shmem_alloc(void)\n{\n\tRequestAddinShmemSpace(sizeof(CounterState));\n}\n\nextern void\nts_bgw_counter_shmem_startup(void)\n{\n\tbgw_counter_state_init();\n}\n\nextern void\nts_bgw_counter_reinit(void)\n{\n\t/* set counter back to zero on startup */\n\tSpinLockAcquire(&ct->mutex);\n\tct->total_workers = 0;\n\tSpinLockRelease(&ct->mutex);\n}\n\nextern bool\nts_bgw_total_workers_increment_by(int increment_by)\n{\n\tbool incremented = false;\n\tint max_workers = ts_guc_max_background_workers;\n\n\tSpinLockAcquire(&ct->mutex);\n\tif (ct->total_workers + increment_by <= max_workers)\n\t{\n\t\tct->total_workers += increment_by;\n\t\tincremented = true;\n\t}\n\tSpinLockRelease(&ct->mutex);\n\treturn incremented;\n}\n\nextern bool\nts_bgw_total_workers_increment()\n{\n\treturn ts_bgw_total_workers_increment_by(1);\n}\n\nextern void\nts_bgw_total_workers_decrement_by(int decrement_by)\n{\n\t/*\n\t * Launcher is 1 worker, and when it dies we reinitialize, so we should\n\t * never be below 1\n\t */\n\tSpinLockAcquire(&ct->mutex);\n\tif (ct->total_workers - decrement_by >= 1)\n\t{\n\t\tct->total_workers -= decrement_by;\n\t\tSpinLockRelease(&ct->mutex);\n\t}\n\telse\n\t{\n\t\tSpinLockRelease(&ct->mutex);\n\t\tereport(FATAL,\n\t\t\t\t(errmsg(\"TimescaleDB background worker cannot decrement workers below 1\"),\n\t\t\t\t errhint(\"The background worker scheduler is in an invalid state and may not be \"\n\t\t\t\t\t\t \"keeping track of workers allocated to TimescaleDB properly, please \"\n\t\t\t\t\t\t \"submit a bug report.\")));\n\t}\n}\n\nextern void\nts_bgw_total_workers_decrement()\n{\n\tts_bgw_total_workers_decrement_by(1);\n}\n\nextern int\nts_bgw_total_workers_get()\n{\n\tint nworkers;\n\n\tSpinLockAcquire(&ct->mutex);\n\tnworkers = ct->total_workers;\n\tSpinLockRelease(&ct->mutex);\n\treturn nworkers;\n}\n"
  },
  {
    "path": "src/loader/bgw_counter.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\nextern int ts_guc_max_background_workers;\n\nextern void ts_bgw_counter_shmem_alloc(void);\nextern void ts_bgw_counter_shmem_startup(void);\n\nextern void ts_bgw_counter_setup_gucs(void);\n\nextern void ts_bgw_counter_reinit(void);\nextern bool ts_bgw_total_workers_increment(void);\nextern void ts_bgw_total_workers_decrement(void);\nextern int ts_bgw_total_workers_get(void);\nextern bool ts_bgw_total_workers_increment_by(int increment_by);\nextern void ts_bgw_total_workers_decrement_by(int decrement_by);\n"
  },
  {
    "path": "src/loader/bgw_interface.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n\n#include <fmgr.h>\n#include <miscadmin.h>\n\n#include \"../compat/compat.h\"\n#include \"../extension_constants.h\"\n#include \"bgw_counter.h\"\n#include \"bgw_interface.h\"\n#include \"bgw_message_queue.h\"\n\n/* This is where versioned-extension facing functions live. They shouldn't live anywhere else. */\n\n/* All loader changes should always be backward compatible.\n * Update ts_bgw_loader_api_version if the loader changes are needed for newer extension updates.\n * e.g. adding a LWLock to loader is required for some future change coming to OSM extension version\n * xxxx. RENDEZVOUS_BGW_LOADER_API_VERSION is used to verify if the loader in use is compatible with\n * the current TimescaleDB version. This check happens in bgw/bgw_launcher.c When\n * ts_bgw_loader_api_version is updated, check the compatibility in bgw/bgw_launcher.c as well\n */\nconst int32 ts_bgw_loader_api_version = 4;\n\nTS_FUNCTION_INFO_V1(ts_bgw_worker_reserve);\nTS_FUNCTION_INFO_V1(ts_bgw_worker_release);\nTS_FUNCTION_INFO_V1(ts_bgw_num_unreserved);\nTS_FUNCTION_INFO_V1(ts_bgw_db_workers_start);\n\nTS_FUNCTION_INFO_V1(ts_bgw_db_workers_stop);\n\nTS_FUNCTION_INFO_V1(ts_bgw_db_workers_restart);\n\nvoid\nts_bgw_interface_register_api_version()\n{\n\tvoid **versionptr = find_rendezvous_variable(RENDEZVOUS_BGW_LOADER_API_VERSION);\n\n\t/* Cast away the const to store in the rendezvous variable */\n\t*versionptr = (void *) &ts_bgw_loader_api_version;\n}\n\nDatum\nts_bgw_worker_reserve(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_BOOL(ts_bgw_total_workers_increment());\n}\n\nDatum\nts_bgw_worker_release(PG_FUNCTION_ARGS)\n{\n\tts_bgw_total_workers_decrement();\n\tPG_RETURN_VOID();\n}\n\nDatum\nts_bgw_num_unreserved(PG_FUNCTION_ARGS)\n{\n\tint unreserved_workers;\n\n\tunreserved_workers = ts_guc_max_background_workers - ts_bgw_total_workers_get();\n\tPG_RETURN_INT32(unreserved_workers);\n}\n\nDatum\nts_bgw_db_workers_start(PG_FUNCTION_ARGS)\n{\n\tif (!superuser())\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),\n\t\t\t\t (errmsg(\"must be superuser to start background workers\"))));\n\n\tPG_RETURN_BOOL(ts_bgw_message_send_and_wait(START, MyDatabaseId));\n}\n\nDatum\nts_bgw_db_workers_stop(PG_FUNCTION_ARGS)\n{\n\tif (!superuser())\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),\n\t\t\t\t (errmsg(\"must be superuser to stop background workers\"))));\n\n\tPG_RETURN_BOOL(ts_bgw_message_send_and_wait(STOP, MyDatabaseId));\n}\n\nDatum\nts_bgw_db_workers_restart(PG_FUNCTION_ARGS)\n{\n\tif (!superuser())\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),\n\t\t\t\t (errmsg(\"must be superuser to restart background workers\"))));\n\n\tPG_RETURN_BOOL(ts_bgw_message_send_and_wait(RESTART, MyDatabaseId));\n}\n"
  },
  {
    "path": "src/loader/bgw_interface.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\nextern void ts_bgw_interface_register_api_version(void);\nextern const int32 ts_bgw_loader_api_version;\n"
  },
  {
    "path": "src/loader/bgw_launcher.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n\n/* BGW includes below */\n/* These are always necessary for a bgworker */\n#include <miscadmin.h>\n#include <postmaster/bgworker.h>\n#include <storage/ipc.h>\n#include <storage/latch.h>\n#include <storage/lwlock.h>\n#include <storage/proc.h>\n#include <storage/shmem.h>\n\n/* for setting our wait event during waitlatch*/\n#include <pgstat.h>\n\n/* needed for getting database list*/\n#include <access/heapam.h>\n#include <access/htup_details.h>\n#include <access/xact.h>\n#include <catalog/pg_database.h>\n#include <utils/snapmgr.h>\n\n/* and checking db list for whether we're in a template*/\n#include <utils/syscache.h>\n\n/* for calling external function*/\n#include <fmgr.h>\n\n/* for signal handling (specifically die() function) */\n#include <tcop/tcopprot.h>\n\n/* for looking up sending proc information for message handling */\n#include <storage/procarray.h>\n\n/* for allocating the htab storage */\n#include <utils/memutils.h>\n\n/* for getting settings correct before loading the versioned scheduler */\n#include \"catalog/pg_db_role_setting.h\"\n\n#include \"../compat/compat.h\"\n#include \"../extension_constants.h\"\n#include \"bgw_counter.h\"\n#include \"bgw_launcher.h\"\n#include \"bgw_message_queue.h\"\n#include \"loader.h\"\n\n#define BGW_DB_SCHEDULER_FUNCNAME \"ts_bgw_scheduler_main\"\n#define BGW_ENTRYPOINT_FUNCNAME \"ts_bgw_db_scheduler_entrypoint\"\n\ntypedef enum AckResult\n{\n\tACK_FAILURE = 0,\n\tACK_SUCCESS,\n} AckResult;\n\n/* See state machine in README.md */\ntypedef enum SchedulerState\n{\n\t/* Scheduler should be started but has not been allocated or started */\n\tENABLED = 0,\n\t/* The scheduler has been allocated a spot in timescaleDB's worker counter */\n\tALLOCATED,\n\t/* Scheduler has been started */\n\tSTARTED,\n\n\t/*\n\t * Scheduler is stopped and should not be started automatically. START and\n\t * RESTART messages can re-enable the scheduler.\n\t */\n\tDISABLED\n} SchedulerState;\n\n#ifdef TS_DEBUG\n#define BGW_LAUNCHER_RESTART_TIME_S 0\n#else\n#define BGW_LAUNCHER_RESTART_TIME_S 60\n#endif\n\nstatic volatile sig_atomic_t got_SIGHUP = false;\n\nint ts_guc_bgw_scheduler_restart_time_sec = BGW_NEVER_RESTART;\n\nstatic void\nlauncher_sighup(SIGNAL_ARGS)\n{\n\t/* based on av_sighup_handler */\n\tint save_errno = errno;\n\n\tgot_SIGHUP = true;\n\tSetLatch(MyLatch);\n\n\terrno = save_errno;\n}\n\n/*\n * Main bgw launcher for the cluster.\n *\n * Run through the TimescaleDB loader, so needs to have a small footprint as\n * any interactions it has will need to remain backwards compatible for the\n * foreseeable future.\n *\n * Notes: multiple databases in an instance (PG cluster) can have TimescaleDB\n * installed. They are not necessarily the same version of TimescaleDB (though\n * they could be) Shared memory is allocated and background workers are\n * registered at shared_preload_libraries time We do not know what databases\n * exist, nor which databases TimescaleDB is installed in (if any) at\n * shared_preload_libraries time.\n */\n\nTS_FUNCTION_INFO_V1(ts_bgw_cluster_launcher_main);\nTS_FUNCTION_INFO_V1(ts_bgw_db_scheduler_entrypoint);\ntypedef struct DbHashEntry\n{\n\tOid db_oid;\t\t\t\t\t\t\t\t\t /* key for the hash table, must be first */\n\tBackgroundWorkerHandle *db_scheduler_handle; /* needed to shut down\n\t\t\t\t\t\t\t\t\t\t\t\t  * properly */\n\tSchedulerState state;\n\tVirtualTransactionId vxid;\n\tint state_transition_failures;\n} DbHashEntry;\n\nstatic void scheduler_state_trans_enabled_to_allocated(DbHashEntry *entry);\n\nstatic void\nbgw_on_postmaster_death(void)\n{\n\ton_exit_reset(); /* don't call exit hooks cause we want to bail\n\t\t\t\t\t  * out quickly */\n\tereport(FATAL,\n\t\t\t(errcode(ERRCODE_ADMIN_SHUTDOWN),\n\t\t\t errmsg(\"postmaster exited while TimescaleDB background worker launcher was working\")));\n}\n\nstatic void\nreport_bgw_limit_exceeded(DbHashEntry *entry)\n{\n\tif (entry->state_transition_failures == 0)\n\t\tereport(LOG,\n\t\t\t\t(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),\n\t\t\t\t errmsg(\"TimescaleDB background worker limit of %d exceeded\",\n\t\t\t\t\t\tts_guc_max_background_workers),\n\t\t\t\t errhint(\"Consider increasing timescaledb.max_background_workers.\")));\n\tentry->state_transition_failures++;\n}\n\nstatic void\nreport_error_on_worker_register_failure(DbHashEntry *entry)\n{\n\tif (entry->state_transition_failures == 0)\n\t\tereport(LOG,\n\t\t\t\t(errcode(ERRCODE_INSUFFICIENT_RESOURCES),\n\t\t\t\t errmsg(\"no available background worker slots\"),\n\t\t\t\t errhint(\"Consider increasing max_worker_processes in tandem with \"\n\t\t\t\t\t\t \"timescaledb.max_background_workers.\")));\n\tentry->state_transition_failures++;\n}\n\n/*\n * Aliasing a few things in bgworker.h so that we exit correctly on postmaster\n * death so we don't have to duplicate code basically telling it we shouldn't\n * call exit hooks cause we want to bail out quickly - similar to how the\n * quickdie function works when we receive a sigquit. This should work\n * similarly because postmaster death is a similar severity of issue.\n * Additionally, we're wrapping these calls to make sure we never have a NULL\n * handle, if we have a null handle, we return normal things.\n */\nstatic BgwHandleStatus\nget_background_worker_pid(BackgroundWorkerHandle *handle, pid_t *pidp)\n{\n\tBgwHandleStatus status;\n\tpid_t pid;\n\n\tif (handle == NULL)\n\t\tstatus = BGWH_STOPPED;\n\telse\n\t{\n\t\tstatus = GetBackgroundWorkerPid(handle, &pid);\n\t\tif (pidp != NULL)\n\t\t\t*pidp = pid;\n\t}\n\n\tif (status == BGWH_POSTMASTER_DIED)\n\t\tbgw_on_postmaster_death();\n\treturn status;\n}\n\nstatic void\nwait_for_background_worker_startup(BackgroundWorkerHandle *handle, pid_t *pidp)\n{\n\tBgwHandleStatus status;\n\n\tif (handle == NULL)\n\t\tstatus = BGWH_STOPPED;\n\telse\n\t\tstatus = WaitForBackgroundWorkerStartup(handle, pidp);\n\n\t/*\n\t * We don't care whether we get BGWH_STOPPED or BGWH_STARTED here, because\n\t * the worker could have started and stopped very quickly before we read\n\t * it. We can't get BGWH_NOT_YET_STARTED as that's what we're waiting for.\n\t * We do care if the Postmaster died however.\n\t */\n\n\tif (status == BGWH_POSTMASTER_DIED)\n\t\tbgw_on_postmaster_death();\n\n\tAssert(status == BGWH_STOPPED || status == BGWH_STARTED);\n}\n\nstatic void\nwait_for_background_worker_shutdown(BackgroundWorkerHandle *handle)\n{\n\tBgwHandleStatus status;\n\n\tif (handle == NULL)\n\t\tstatus = BGWH_STOPPED;\n\telse\n\t\tstatus = WaitForBackgroundWorkerShutdown(handle);\n\n\t/* We can only ever get BGWH_STOPPED stopped unless the Postmaster died. */\n\tif (status == BGWH_POSTMASTER_DIED)\n\t\tbgw_on_postmaster_death();\n\n\tAssert(status == BGWH_STOPPED);\n}\n\nstatic void\nterminate_background_worker(BackgroundWorkerHandle *handle)\n{\n\tif (handle == NULL)\n\t\treturn;\n\telse\n\t\tTerminateBackgroundWorker(handle);\n}\n\nstatic bool\ncheck_scheduler_restart_time(int *newval, void **extra, GucSource source)\n{\n\tif (*newval == -1 || *newval >= 10)\n\t\treturn true;\n\tGUC_check_errdetail(\"Scheduler restart time must be be either -1 or at least 10 seconds.\");\n\treturn false;\n}\n\nextern void\nts_bgw_cluster_launcher_init(void)\n{\n\tBackgroundWorker worker;\n\n\tDefineCustomIntVariable(/* name= */ MAKE_EXTOPTION(\"bgw_scheduler_restart_time\"),\n\t\t\t\t\t\t\t/* short_desc= */\n\t\t\t\t\t\t\t\"Restart time for scheduler in seconds\",\n\t\t\t\t\t\t\t/* long_desc= */\n\t\t\t\t\t\t\t\"The number of seconds until the scheduler restart on failure, or zero \"\n\t\t\t\t\t\t\t\"if it should never restart.\",\n\t\t\t\t\t\t\t/* valueAddr= */ &ts_guc_bgw_scheduler_restart_time_sec,\n\t\t\t\t\t\t\t/* bootValue= */ BGW_NEVER_RESTART,\n\t\t\t\t\t\t\t/* minValue= */ -1,\n\t\t\t\t\t\t\t/* maxValue= */ 3600,\n\t\t\t\t\t\t\t/* context= */ PGC_SIGHUP,\n\t\t\t\t\t\t\t/* flags= */ GUC_UNIT_S,\n\t\t\t\t\t\t\t/* check_hook= */ check_scheduler_restart_time,\n\t\t\t\t\t\t\t/* assign_hook= */ NULL,\n\t\t\t\t\t\t\t/* show_hook= */ NULL);\n\n\tmemset(&worker, 0, sizeof(worker));\n\t/* set up worker settings for our main worker */\n\tsnprintf(worker.bgw_name, BGW_MAXLEN, TS_BGW_TYPE_LAUNCHER);\n\tworker.bgw_flags = BGWORKER_SHMEM_ACCESS | BGWORKER_BACKEND_DATABASE_CONNECTION;\n\tworker.bgw_restart_time = BGW_LAUNCHER_RESTART_TIME_S;\n\n\t/*\n\t * Starting at BgWorkerStart_RecoveryFinished means we won't ever get\n\t * started on a hot_standby see\n\t * https://www.postgresql.org/docs/10/static/bgworker.html as it's not\n\t * documented in bgworker.c.\n\t */\n\tworker.bgw_start_time = BgWorkerStart_RecoveryFinished;\n\tworker.bgw_notify_pid = 0;\n\tsnprintf(worker.bgw_library_name, BGW_MAXLEN, EXTENSION_NAME);\n\tsnprintf(worker.bgw_function_name, BGW_MAXLEN, \"ts_bgw_cluster_launcher_main\");\n\tRegisterBackgroundWorker(&worker);\n}\n\n/*\n * Register a background worker that calls the main TimescaleDB background\n * worker launcher library (i.e. loader) and uses the scheduler entrypoint\n * function.  The scheduler entrypoint will deal with starting a new worker,\n * and waiting on any txns that it needs to, if we pass along a vxid in the\n * bgw_extra field of the BgWorker.\n */\nstatic bool\nregister_entrypoint_for_db(Oid db_id, VirtualTransactionId vxid, BackgroundWorkerHandle **handle)\n{\n\tBackgroundWorker worker;\n\tint restart_time_sec = ts_guc_bgw_scheduler_restart_time_sec;\n\n\t/* BGW_NEVER_RESTART is typically -1, but we check that explicitly here in\n\t * case PostgreSQL changes it. Compiler should optimize this away if they\n\t * are the same. */\n\tif (restart_time_sec == -1)\n\t\trestart_time_sec = BGW_NEVER_RESTART;\n\n\tmemset(&worker, 0, sizeof(worker));\n\tsnprintf(worker.bgw_type, BGW_MAXLEN, TS_BGW_TYPE_SCHEDULER);\n\tsnprintf(worker.bgw_name, BGW_MAXLEN, \"%s for database %d\", TS_BGW_TYPE_SCHEDULER, db_id);\n\tworker.bgw_flags = BGWORKER_SHMEM_ACCESS | BGWORKER_BACKEND_DATABASE_CONNECTION;\n\tworker.bgw_restart_time = restart_time_sec;\n\tworker.bgw_start_time = BgWorkerStart_RecoveryFinished;\n\tsnprintf(worker.bgw_library_name, BGW_MAXLEN, EXTENSION_NAME);\n\tsnprintf(worker.bgw_function_name, BGW_MAXLEN, BGW_ENTRYPOINT_FUNCNAME);\n\tworker.bgw_notify_pid = MyProcPid;\n\tworker.bgw_main_arg = ObjectIdGetDatum(db_id);\n\tmemcpy(worker.bgw_extra, &vxid, sizeof(VirtualTransactionId));\n\n\treturn RegisterDynamicBackgroundWorker(&worker, handle);\n}\n\n/* Initializes the launcher's hash table of schedulers.\n * Return value is guaranteed to be not-null, because otherwise the function\n * will have thrown an error.\n */\nstatic HTAB *\ninit_database_htab(void)\n{\n\tHASHCTL info = { .keysize = sizeof(Oid),\n\t\t\t\t\t .entrysize = sizeof(DbHashEntry),\n\t\t\t\t\t .hcxt = TopMemoryContext };\n\n\treturn hash_create(\"launcher_db_htab\",\n\t\t\t\t\t   ts_guc_max_background_workers,\n\t\t\t\t\t   &info,\n\t\t\t\t\t   HASH_BLOBS | HASH_CONTEXT | HASH_ELEM);\n}\n\n/* Insert a scheduler entry into the hash table. Correctly set entry values. */\nstatic DbHashEntry *\ndb_hash_entry_create_if_not_exists(HTAB *db_htab, Oid db_oid)\n{\n\tDbHashEntry *db_he;\n\tbool found;\n\n\tdb_he = (DbHashEntry *) hash_search(db_htab, &db_oid, HASH_ENTER, &found);\n\tif (!found)\n\t{\n\t\tdb_he->db_scheduler_handle = NULL;\n\t\tdb_he->state = ENABLED;\n\t\tSetInvalidVirtualTransactionId(db_he->vxid);\n\t\tdb_he->state_transition_failures = 0;\n\n\t\t/*\n\t\t * Try to allocate a spot right away to give schedulers priority over\n\t\t * other bgws. This is especially important on initial server startup\n\t\t * where we want to reserve slots for all schedulers before starting\n\t\t * any. This is done so that background workers started by schedulers\n\t\t * don't race for open slots with other schedulers on startup.\n\t\t */\n\t\tscheduler_state_trans_enabled_to_allocated(db_he);\n\t}\n\n\treturn db_he;\n}\n\n/*\n * Result from signalling a backend.\n *\n * Error codes are non-zero, and success is zero.\n */\nenum SignalBackendResult\n{\n\tSIGNAL_BACKEND_SUCCESS = 0,\n\tSIGNAL_BACKEND_ERROR,\n\tSIGNAL_BACKEND_NOPERMISSION,\n\tSIGNAL_BACKEND_NOSUPERUSER,\n};\n\n/*\n * Terminate a background worker.\n *\n * This is copied from pg_signal_backend() in\n * src/backend/storage/ipc/signalfuncs.c but tweaked to not require a database\n * connection since the launcher does not have one.\n */\nstatic enum SignalBackendResult\nts_signal_backend(int pid, int sig)\n{\n\tPGPROC *proc = BackendPidGetProc(pid);\n\n\tif (unlikely(proc == NULL))\n\t{\n\t\tereport(WARNING, (errmsg(\"PID %d is not a PostgreSQL backend process\", pid)));\n\t\treturn SIGNAL_BACKEND_ERROR;\n\t}\n\n\tif (unlikely(kill(pid, sig)))\n\t{\n\t\t/* Again, just a warning to allow loops */\n\t\tereport(WARNING, (errmsg(\"could not send signal to process %d: %m\", pid)));\n\t\treturn SIGNAL_BACKEND_ERROR;\n\t}\n\n\treturn SIGNAL_BACKEND_SUCCESS;\n}\n\n/*\n * Terminate backends by backend type.\n *\n * We iterate through all backends and mark those that match the given backend\n * type as terminated.\n *\n * Note that there is potentially a delay between marking backends as\n * terminated and their actual termination, so the backends have to be able to\n * run even if there are multiple instances accessing the same data.\n *\n * Parts of this code is taken from pg_stat_get_activity() in\n * src/backend/utils/adt/pgstatfuncs.c.\n */\nstatic void\nterminate_backends_by_backend_type(const char *backend_type)\n{\n\tAssert(backend_type);\n\n\tconst int num_backends = pgstat_fetch_stat_numbackends();\n\tfor (int curr_backend = 1; curr_backend <= num_backends; ++curr_backend)\n\t{\n\t\tconst LocalPgBackendStatus *local_beentry =\n\t\t\tpgstat_get_local_beentry_by_index_compat(curr_backend);\n\t\tconst PgBackendStatus *beentry = &local_beentry->backendStatus;\n\t\tconst char *bgw_type = GetBackgroundWorkerTypeByPid(beentry->st_procpid);\n\t\tif (bgw_type && strcmp(backend_type, bgw_type) == 0)\n\t\t{\n\t\t\tint error = ts_signal_backend(beentry->st_procpid, SIGTERM);\n\t\t\tif (error)\n\t\t\t\telog(LOG, \"failed to terminate backend with pid %d\", beentry->st_procpid);\n\t\t}\n\t}\n}\n\n/*\n * Model this on autovacuum.c -> get_database_list.\n *\n * Note that we are not doing all the things around memory context that they\n * do, because the hashtable we're using to store db entries is automatically\n * created in its own memory context (a child of TopMemoryContext) This can\n * get called at two different times 1) when the cluster launcher starts and\n * is looking for dbs and 2) if it restarts due to a postmaster signal.\n */\nstatic void\npopulate_database_htab(HTAB *db_htab)\n{\n\tRelation rel;\n\tTableScanDesc scan;\n\tHeapTuple tup;\n\n\t/*\n\t * by this time we should already be connected to the db, and only have\n\t * access to shared catalogs\n\t */\n\tStartTransactionCommand();\n\t(void) GetTransactionSnapshot();\n\n\trel = table_open(DatabaseRelationId, AccessShareLock);\n\tscan = table_beginscan_catalog(rel, 0, NULL);\n\n\twhile (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))\n\t{\n\t\tForm_pg_database pgdb = (Form_pg_database) GETSTRUCT(tup);\n\n\t\tif (!pgdb->datallowconn || pgdb->datistemplate)\n\t\t\tcontinue; /* don't bother with dbs that don't allow\n\t\t\t\t\t   * connections or are templates */\n\t\tdb_hash_entry_create_if_not_exists(db_htab, pgdb->oid);\n\t}\n\ttable_endscan(scan);\n\ttable_close(rel, AccessShareLock);\n\n\tCommitTransactionCommand();\n}\n\nstatic void\nscheduler_modify_state(DbHashEntry *entry, SchedulerState new_state)\n{\n\tAssert(entry->state != new_state);\n\tentry->state_transition_failures = 0;\n\tentry->state = new_state;\n}\n\n/* TRANSITION FUNCTIONS */\nstatic void\nscheduler_state_trans_disabled_to_enabled(DbHashEntry *entry)\n{\n\tAssert(entry->state == DISABLED);\n\tAssert(entry->db_scheduler_handle == NULL);\n\tscheduler_modify_state(entry, ENABLED);\n}\n\nstatic void\nscheduler_state_trans_enabled_to_allocated(DbHashEntry *entry)\n{\n\tAssert(entry->state == ENABLED);\n\tAssert(entry->db_scheduler_handle == NULL);\n\t/* Reserve a spot for this scheduler with BGW counter */\n\tif (!ts_bgw_total_workers_increment())\n\t{\n\t\treport_bgw_limit_exceeded(entry);\n\t\treturn;\n\t}\n\tscheduler_modify_state(entry, ALLOCATED);\n}\n\nstatic void\nscheduler_state_trans_started_to_allocated(DbHashEntry *entry)\n{\n\tAssert(entry->state == STARTED);\n\tAssert(get_background_worker_pid(entry->db_scheduler_handle, NULL) == BGWH_STOPPED);\n\tif (entry->db_scheduler_handle != NULL)\n\t{\n\t\tpfree(entry->db_scheduler_handle);\n\t\tentry->db_scheduler_handle = NULL;\n\t}\n\tscheduler_modify_state(entry, ALLOCATED);\n}\n\nstatic void\nscheduler_state_trans_allocated_to_started(DbHashEntry *entry)\n{\n\tpid_t worker_pid;\n\tbool worker_registered;\n\n\tAssert(entry->state == ALLOCATED);\n\tAssert(entry->db_scheduler_handle == NULL);\n\n\tworker_registered =\n\t\tregister_entrypoint_for_db(entry->db_oid, entry->vxid, &entry->db_scheduler_handle);\n\n\tif (!worker_registered)\n\t{\n\t\treport_error_on_worker_register_failure(entry);\n\t\treturn;\n\t}\n\twait_for_background_worker_startup(entry->db_scheduler_handle, &worker_pid);\n\tSetInvalidVirtualTransactionId(entry->vxid);\n\tscheduler_modify_state(entry, STARTED);\n}\n\nstatic void\nscheduler_state_trans_enabled_to_disabled(DbHashEntry *entry)\n{\n\tAssert(entry->state == ENABLED);\n\tAssert(entry->db_scheduler_handle == NULL);\n\tscheduler_modify_state(entry, DISABLED);\n}\n\nstatic void\nscheduler_state_trans_allocated_to_disabled(DbHashEntry *entry)\n{\n\tAssert(entry->state == ALLOCATED);\n\tAssert(entry->db_scheduler_handle == NULL);\n\n\tts_bgw_total_workers_decrement();\n\tscheduler_modify_state(entry, DISABLED);\n}\n\nstatic void\nscheduler_state_trans_started_to_disabled(DbHashEntry *entry)\n{\n\tAssert(entry->state == STARTED);\n\tAssert(get_background_worker_pid(entry->db_scheduler_handle, NULL) == BGWH_STOPPED);\n\n\tts_bgw_total_workers_decrement();\n\tif (entry->db_scheduler_handle != NULL)\n\t{\n\t\tpfree(entry->db_scheduler_handle);\n\t\tentry->db_scheduler_handle = NULL;\n\t}\n\tscheduler_modify_state(entry, DISABLED);\n}\n\nstatic void\nscheduler_state_trans_automatic(DbHashEntry *entry)\n{\n\tswitch (entry->state)\n\t{\n\t\tcase ENABLED:\n\t\t\tscheduler_state_trans_enabled_to_allocated(entry);\n\t\t\tif (entry->state == ALLOCATED)\n\t\t\t\tscheduler_state_trans_allocated_to_started(entry);\n\t\t\tbreak;\n\t\tcase ALLOCATED:\n\t\t\tscheduler_state_trans_allocated_to_started(entry);\n\t\t\tbreak;\n\t\tcase STARTED:\n\t\t\tif (get_background_worker_pid(entry->db_scheduler_handle, NULL) == BGWH_STOPPED)\n\t\t\t\tscheduler_state_trans_started_to_disabled(entry);\n\t\t\tbreak;\n\t\tcase DISABLED:\n\t\t\tbreak;\n\t}\n}\n\nstatic void\nscheduler_state_trans_automatic_all(HTAB *db_htab)\n{\n\tHASH_SEQ_STATUS hash_seq;\n\tDbHashEntry *current_entry;\n\n\thash_seq_init(&hash_seq, db_htab);\n\twhile ((current_entry = hash_seq_search(&hash_seq)) != NULL)\n\t\tscheduler_state_trans_automatic(current_entry);\n}\n\n/* This is called when we're going to shut down so we don't leave things messy*/\nstatic void\nlauncher_pre_shmem_cleanup(int code, Datum arg)\n{\n\tHTAB *db_htab = *(HTAB **) DatumGetPointer(arg);\n\tHASH_SEQ_STATUS hash_seq;\n\tDbHashEntry *current_entry;\n\n\t/* db_htab will be NULL if we fail during init_database_htab */\n\tif (db_htab != NULL)\n\t{\n\t\thash_seq_init(&hash_seq, db_htab);\n\n\t\t/*\n\t\t * Stop everyone (or at least tell the Postmaster we don't care about\n\t\t * them anymore)\n\t\t */\n\t\twhile ((current_entry = hash_seq_search(&hash_seq)) != NULL)\n\t\t{\n\t\t\tif (current_entry->db_scheduler_handle != NULL)\n\t\t\t{\n\t\t\t\tterminate_background_worker(current_entry->db_scheduler_handle);\n\t\t\t\tpfree(current_entry->db_scheduler_handle);\n\t\t\t}\n\t\t}\n\n\t\thash_destroy(db_htab);\n\t}\n\n\t/*\n\t * Reset our pid in the queue so that others know we've died and don't\n\t * wait forever\n\t */\n\tts_bgw_message_queue_shmem_cleanup();\n}\n\n/*\n *************\n * Actions for message types we could receive off of the bgw_message_queue.\n *************\n */\n\n/*\n * This should be idempotent. If we find the background worker and it's not\n * stopped, do nothing. In order to maintain idempotency, a scheduler in the\n * ENABLED, ALLOCATED or STARTED state cannot get a new vxid to wait on. (We\n * cannot pass in a new vxid to wait on for an already-started scheduler in any\n * case). This means that actions like restart, which are not idempotent, will\n * not have their effects changed by subsequent start actions, no matter the\n * state they are in when the start action is received.\n */\nstatic AckResult\nmessage_start_action(HTAB *db_htab, BgwMessage *message)\n{\n\tDbHashEntry *entry;\n\n\tentry = db_hash_entry_create_if_not_exists(db_htab, message->db_oid);\n\n\tif (entry->state == DISABLED)\n\t\tscheduler_state_trans_disabled_to_enabled(entry);\n\n\tscheduler_state_trans_automatic(entry);\n\n\treturn (entry->state == STARTED ? ACK_SUCCESS : ACK_FAILURE);\n}\n\nstatic AckResult\nmessage_stop_action(HTAB *db_htab, BgwMessage *message)\n{\n\tDbHashEntry *entry;\n\n\t/*\n\t * If the entry does not exist try to create it so we can put it in the\n\t * DISABLED state. Otherwise, it will be created during the next poll and\n\t * then will end up in the ENABLED state and proceed to being STARTED. But\n\t * this is not the behavior we want.\n\t */\n\tentry = db_hash_entry_create_if_not_exists(db_htab, message->db_oid);\n\n\tswitch (entry->state)\n\t{\n\t\tcase ENABLED:\n\t\t\tscheduler_state_trans_enabled_to_disabled(entry);\n\t\t\tbreak;\n\t\tcase ALLOCATED:\n\t\t\tscheduler_state_trans_allocated_to_disabled(entry);\n\t\t\tbreak;\n\t\tcase STARTED:\n\t\t\tterminate_background_worker(entry->db_scheduler_handle);\n\t\t\twait_for_background_worker_shutdown(entry->db_scheduler_handle);\n\t\t\tscheduler_state_trans_started_to_disabled(entry);\n\t\t\tbreak;\n\t\tcase DISABLED:\n\t\t\tbreak;\n\t}\n\treturn entry->state == DISABLED ? ACK_SUCCESS : ACK_FAILURE;\n}\n\n/*\n * This function will stop and restart a scheduler in the STARTED state,  ENABLE\n * a scheduler if it does not exist or is in the DISABLED state and set the vxid\n * to wait on for a scheduler in any state. It is not idempotent. Additionally,\n * one might think that this function would simply be a combination of stop and\n * start above, but it is not as we maintain the worker's \"slot\" by never\n * releasing the worker from our \"pool\" of background workers as stopping and\n * starting would.  We don't want a race condition where some other db steals\n * the scheduler of the other by requesting a worker at the wrong time. (This is\n * accomplished by moving from STARTED to ALLOCATED after shutting down the\n * worker, never releasing the entry and transitioning all the way back to\n * ENABLED).\n */\nstatic AckResult\nmessage_restart_action(HTAB *db_htab, BgwMessage *message, VirtualTransactionId vxid)\n{\n\tDbHashEntry *entry;\n\n\tentry = db_hash_entry_create_if_not_exists(db_htab, message->db_oid);\n\n\tentry->vxid = vxid;\n\n\tswitch (entry->state)\n\t{\n\t\tcase ENABLED:\n\t\t\tbreak;\n\t\tcase ALLOCATED:\n\t\t\tbreak;\n\t\tcase STARTED:\n\t\t\tterminate_background_worker(entry->db_scheduler_handle);\n\t\t\twait_for_background_worker_shutdown(entry->db_scheduler_handle);\n\t\t\tscheduler_state_trans_started_to_allocated(entry);\n\t\t\tbreak;\n\t\tcase DISABLED:\n\t\t\tscheduler_state_trans_disabled_to_enabled(entry);\n\t}\n\n\tscheduler_state_trans_automatic(entry);\n\treturn entry->state == STARTED ? ACK_SUCCESS : ACK_FAILURE;\n}\n\n/*\n * Handle 1 message.\n */\nstatic bool\nlauncher_handle_message(HTAB *db_htab)\n{\n\tBgwMessage *message = ts_bgw_message_receive();\n\tPGPROC *sender;\n\tVirtualTransactionId vxid;\n\tAckResult action_result = ACK_FAILURE;\n\n\tif (message == NULL)\n\t\treturn false;\n\n\tsender = BackendPidGetProc(message->sender_pid);\n\tif (sender == NULL)\n\t{\n\t\tereport(LOG,\n\t\t\t\t(errmsg(\"TimescaleDB background worker launcher received message from non-existent \"\n\t\t\t\t\t\t\"backend\")));\n\t\treturn true;\n\t}\n\n\tGET_VXID_FROM_PGPROC(vxid, *sender);\n\n\tswitch (message->message_type)\n\t{\n\t\tcase START:\n\t\t\taction_result = message_start_action(db_htab, message);\n\t\t\tbreak;\n\t\tcase STOP:\n\t\t\taction_result = message_stop_action(db_htab, message);\n\t\t\tbreak;\n\t\tcase RESTART:\n\t\t\taction_result = message_restart_action(db_htab, message, vxid);\n\t\t\tbreak;\n\t}\n\n\tts_bgw_message_send_ack(message, action_result);\n\treturn true;\n}\n\n/*\n * The default, `bgworker_die()`, can't be used due to the fact that it\n * handles signals synchronously, rather than waiting for a\n * CHECK_FOR_INTERRUPTS(). `die()` (which is arguably misnamed) sets flags\n * that will cause the backend to exit on the next call to\n * CHECK_FOR_INTERRUPTS(), which can happen either in our code or in functions\n * within the Postgres codebase that we call. This means that we don't need to\n * wait for the next time control is returned to our loop to exit, which would\n * be necessary if we set our own flag and checked it in a loop\n * condition. However, because it cannot exit 0, the launcher will be\n * restarted by the postmaster, even when it has received a SIGTERM, which we\n * decided was the proper behavior. If users want to disable the launcher,\n * they can set `timescaledb.max_background_workers = 0` and then we will\n * `proc_exit(0)` before doing anything else.\n */\n\nextern Datum\nts_bgw_cluster_launcher_main(PG_FUNCTION_ARGS)\n{\n\tHTAB **htab_storage;\n\n\tHTAB *db_htab;\n\n\tpqsignal(SIGINT, StatementCancelHandler);\n\tpqsignal(SIGTERM, die);\n\tpqsignal(SIGHUP, launcher_sighup);\n\n\t/* Some SIGHUPS may already have been dropped, so we must load the file here */\n\tgot_SIGHUP = false;\n\tProcessConfigFile(PGC_SIGHUP);\n\tBackgroundWorkerUnblockSignals();\n\tereport(DEBUG1, (errmsg(\"TimescaleDB background worker launcher started\")));\n\n\t/* set counter back to zero on restart */\n\tts_bgw_counter_reinit();\n\tif (!ts_bgw_total_workers_increment())\n\t{\n\t\t/*\n\t\t * Should be the first thing happening so if we already exceeded our\n\t\t * limits it means we have a limit of 0 and we should just exit We\n\t\t * have to exit(0) because if we exit in error we get restarted by the\n\t\t * postmaster.\n\t\t */\n\t\tereport(LOG,\n\t\t\t\t(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),\n\t\t\t\t errmsg(\"TimescaleDB background worker is set to 0\"),\n\t\t\t\t errhint(\"TimescaleDB background worker launcher shutting down.\")));\n\t\tproc_exit(0);\n\t}\n\t/* Connect to the db, no db name yet, so can only access shared catalogs */\n\tBackgroundWorkerInitializeConnection(NULL, NULL, 0);\n\tpgstat_report_appname(MyBgworkerEntry->bgw_name);\n\tereport(LOG, (errmsg(\"TimescaleDB background worker launcher connected to shared catalogs\")));\n\n\thtab_storage = (HTAB **) MemoryContextAllocZero(TopMemoryContext, sizeof(void *));\n\n\t/*\n\t * We must setup the cleanup function _before_ initializing any state it\n\t * touches (specifically the bgw_message_queue and db_htab). Failing to do\n\t * this can cause cascading failures when the launcher fails in\n\t * init_database_htab (eg. due to running out of shared memory) but\n\t * doesn't deregister itself from the shared bgw_message_queue.\n\t */\n\tbefore_shmem_exit(launcher_pre_shmem_cleanup, PointerGetDatum((void *) htab_storage));\n\n\tts_bgw_message_queue_set_reader();\n\n\tdb_htab = init_database_htab();\n\t*htab_storage = db_htab;\n\n\t/*\n\t * If the launcher was restarted and discovers old schedulers, these has\n\t * to be terminated to avoid exhausting the worker slots.\n\t *\n\t * We cannot easily pick up the old schedulers since we do not have access\n\t * to the slots array in PostgreSQL, so instead we scan for something that\n\t * looks like schedulers for databases, and kill them. New ones will then\n\t * be spawned below.\n\t */\n\tterminate_backends_by_backend_type(TS_BGW_TYPE_SCHEDULER);\n\tpopulate_database_htab(db_htab);\n\n\twhile (true)\n\t{\n\t\tint wl_rc;\n\t\tbool handled_msgs = false;\n\n\t\tCHECK_FOR_INTERRUPTS();\n\t\tpopulate_database_htab(db_htab);\n\t\thandled_msgs = launcher_handle_message(db_htab);\n\t\tscheduler_state_trans_automatic_all(db_htab);\n\t\tif (handled_msgs)\n\t\t\tcontinue;\n\n\t\twl_rc = WaitLatch(MyLatch,\n\t\t\t\t\t\t  WL_LATCH_SET | WL_POSTMASTER_DEATH | WL_TIMEOUT,\n\t\t\t\t\t\t  (long) ts_guc_bgw_launcher_poll_time,\n\t\t\t\t\t\t  PG_WAIT_EXTENSION);\n\t\tResetLatch(MyLatch);\n\t\tif (wl_rc & WL_POSTMASTER_DEATH)\n\t\t\tbgw_on_postmaster_death();\n\n\t\tif (got_SIGHUP)\n\t\t{\n\t\t\tgot_SIGHUP = false;\n\t\t\tProcessConfigFile(PGC_SIGHUP);\n\t\t}\n\t}\n\tPG_RETURN_VOID();\n}\n\n/*\n * Inside the entrypoint, we must check again if we're in a template db\n * even though we excluded template dbs in populate_database_htab because\n * we can be called on, say, CREATE EXTENSION in a template db and then\n * we'll not stop til next server shutdown so if we hit this point and are\n * in a template db, we throw an error and shut down Check in the syscache\n * rather than searching through the entire database catalog again.\n * Modelled on autovacuum.c -> do_autovacuum.\n */\nstatic void\ndatabase_checks(void)\n{\n\tForm_pg_database pgdb;\n\tHeapTuple tuple;\n\n\ttuple = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId));\n\tif (!HeapTupleIsValid(tuple))\n\t\tereport(ERROR,\n\t\t\t\t(errmsg(\"TimescaleDB background worker failed to find entry for database in \"\n\t\t\t\t\t\t\"syscache\")));\n\n\tpgdb = (Form_pg_database) GETSTRUCT(tuple);\n\n\tif (!pgdb->datallowconn)\n\t\tereport(ERROR,\n\t\t\t\t(errmsg(\"background worker \\\"%s\\\" trying to connect to database that does not \"\n\t\t\t\t\t\t\"allow connections, exiting\",\n\t\t\t\t\t\tMyBgworkerEntry->bgw_name)));\n\n\tif (pgdb->datistemplate)\n\t\tereport(ERROR,\n\t\t\t\t(errmsg(\"background worker \\\"%s\\\" trying to connect to template database, exiting\",\n\t\t\t\t\t\tMyBgworkerEntry->bgw_name)));\n\n\tReleaseSysCache(tuple);\n}\n\n/*\n * Before we morph into the scheduler, we also need to reload configs from their\n * defaults if the database default has changed. Defaults are changed in the\n * post_restore function where we change the db default for the restoring guc\n * wait until the txn commits and then must see if the txn made the change.\n * Checks for changes are normally run at connection startup, but because we\n * have to connect in order to wait on the txn we have to re-run after the wait.\n * This function is based on the postgres function in postinit.c by the same\n * name.\n */\n\nstatic void\nprocess_settings(Oid databaseid)\n{\n\tRelation relsetting;\n\tSnapshot snapshot;\n\n\tif (!IsUnderPostmaster)\n\t\treturn;\n\n\trelsetting = table_open(DbRoleSettingRelationId, AccessShareLock);\n\n\t/* read all the settings under the same snapshot for efficiency */\n\tsnapshot = RegisterSnapshot(GetCatalogSnapshot(DbRoleSettingRelationId));\n\n\t/* Later settings are ignored if set earlier. */\n\tApplySetting(snapshot, databaseid, InvalidOid, relsetting, PGC_S_DATABASE);\n\tApplySetting(snapshot, InvalidOid, InvalidOid, relsetting, PGC_S_GLOBAL);\n\n\tUnregisterSnapshot(snapshot);\n\ttable_close(relsetting, AccessShareLock);\n}\n\n/*\n * Get the versioned scheduler for the database.\n *\n * This captures any errors generated while fetching information and print\n * them out, but does not propagate the error further since that might trigger\n * a restart.\n */\nstatic PGFunction\nget_versioned_scheduler()\n{\n\tvolatile PGFunction versioned_scheduler_main = NULL;\n\tPG_TRY();\n\t{\n\t\tbool ts_installed = false;\n\t\tchar version[MAX_VERSION_LEN];\n\n\t\t/*\n\t\t * now we can start our transaction and get the version currently\n\t\t * installed\n\t\t */\n\t\tStartTransactionCommand();\n\t\t(void) GetTransactionSnapshot();\n\n\t\t/*\n\t\t * Check whether a database is a template database and raise an error if\n\t\t * so, as we don't want to run in template dbs.\n\t\t */\n\t\tdatabase_checks();\n\t\t/*  Process any config changes caused by an ALTER DATABASE */\n\t\tprocess_settings(MyDatabaseId);\n\t\tts_installed = ts_loader_extension_exists();\n\t\tif (ts_installed)\n\t\t\tstrlcpy(version, ts_loader_extension_version(), MAX_VERSION_LEN);\n\n\t\tts_loader_extension_check();\n\t\tCommitTransactionCommand();\n\t\tif (ts_installed)\n\t\t{\n\t\t\tchar soname[MAX_SO_NAME_LEN];\n\t\t\tsnprintf(soname, MAX_SO_NAME_LEN, \"%s-%s\", EXTENSION_SO, version);\n\t\t\tversioned_scheduler_main =\n\t\t\t\tload_external_function(soname, BGW_DB_SCHEDULER_FUNCNAME, false, NULL);\n\t\t\tif (versioned_scheduler_main == NULL)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errmsg(\"TimescaleDB version %s does not have a background worker, exiting\",\n\t\t\t\t\t\t\t\tsoname)));\n\t\t}\n\t}\n\tPG_CATCH();\n\t{\n\t\tEmitErrorReport();\n\t\tFlushErrorState();\n\t}\n\tPG_END_TRY();\n\treturn versioned_scheduler_main;\n}\n\n/*\n * This can be run either from the cluster launcher at db_startup time, or\n * in the case of an install/uninstall/update of the extension, in the\n * first case, we have no vxid that we're waiting on. In the second case,\n * we do, because we have to wait so that we see the effects of said txn.\n * So we wait for it to finish, then we  morph into the new db_scheduler\n * worker using whatever version is now installed (or exit gracefully if\n * no version is now installed).\n */\nextern Datum\nts_bgw_db_scheduler_entrypoint(PG_FUNCTION_ARGS)\n{\n\tOid db_id = DatumGetObjectId(MyBgworkerEntry->bgw_main_arg);\n\tVirtualTransactionId vxid;\n\n\tpqsignal(SIGINT, StatementCancelHandler);\n\tpqsignal(SIGTERM, die);\n\tBackgroundWorkerUnblockSignals();\n\n\t/*\n\t * Connecting to a database that does not allow connections will generate\n\t * a FATAL error, which might trigger restarts, so we override this check\n\t * and do it ourselves.\n\t */\n\tBackgroundWorkerInitializeConnectionByOid(db_id, InvalidOid, BGWORKER_BYPASS_ALLOWCONN);\n\tpgstat_report_appname(MyBgworkerEntry->bgw_name);\n\n\t/*\n\t * Wait until whatever vxid that potentially called us finishes before we\n\t * happens in a txn so it's cleaned up correctly if we get a sigkill in\n\t * the meantime, but we will need stop after and take a new txn so we can\n\t * see the correct state after its effects\n\t */\n\tStartTransactionCommand();\n\t(void) GetTransactionSnapshot();\n\tmemcpy(&vxid, MyBgworkerEntry->bgw_extra, sizeof(VirtualTransactionId));\n\tif (VirtualTransactionIdIsValid(vxid))\n\t\tVirtualXactLock(vxid, true);\n\tCommitTransactionCommand();\n\n\t/*\n\t * Essentially we morph into the versioned worker here, if there is one.\n\t *\n\t * If an error is generated here, we should trigger a restart (if the\n\t * scheduler is configured for that).\n\t */\n\tPGFunction versioned_scheduler_main = get_versioned_scheduler();\n\tif (versioned_scheduler_main)\n\t\tDirectFunctionCall1(versioned_scheduler_main, ObjectIdGetDatum(InvalidOid));\n\tPG_RETURN_VOID();\n}\n"
  },
  {
    "path": "src/loader/bgw_launcher.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <fmgr.h>\n\n#define TS_BGW_TYPE_LAUNCHER \"TimescaleDB Background Worker Launcher\"\n#define TS_BGW_TYPE_SCHEDULER \"TimescaleDB Background Worker Scheduler\"\n\nextern int ts_guc_bgw_scheduler_restart_time_sec;\n\nextern void ts_bgw_cluster_launcher_init(void);\n\n/*called by postmaster at launcher bgw startup*/\nTSDLLEXPORT extern Datum ts_bgw_cluster_launcher_main(PG_FUNCTION_ARGS);\nTSDLLEXPORT extern Datum ts_bgw_db_scheduler_entrypoint(PG_FUNCTION_ARGS);\n"
  },
  {
    "path": "src/loader/bgw_message_queue.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n\n#include <access/xact.h>\n#include <miscadmin.h>\n#include <pgstat.h>\n#include <storage/lwlock.h>\n#include <storage/proc.h>\n#include <storage/procarray.h>\n#include <storage/shm_mq.h>\n#include <storage/shmem.h>\n#include <storage/spin.h>\n\n#include \"../compat/compat.h\"\n\n#include \"bgw_message_queue.h\"\n\n#define BGW_MQ_MAX_MESSAGES 16\n#define BGW_MQ_NAME \"ts_bgw_message_queue\"\n#define BGW_MQ_TRANCHE_NAME \"ts_bgw_mq_tranche\"\n\n#define BGW_MQ_NUM_WAITS 100\n\n/* WaitLatch expects a long */\n#define BGW_MQ_WAIT_INTERVAL 1000L\n\n#define BGW_ACK_RETRIES 20\n\n/* WaitLatch expects a long */\n#define BGW_ACK_WAIT_INTERVAL 100L\n#define BGW_ACK_QUEUE_SIZE (MAXALIGN(shm_mq_minimum_size + sizeof(int)))\n\n/* We're using a relatively simple implementation of a circular queue similar to:\n * http://opendatastructures.org/ods-python/2_3_ArrayQueue_Array_Based_.html */\ntypedef struct MessageQueue\n{\n\tpid_t reader_pid; /* Should only be set once at cluster launcher\n\t\t\t\t\t   * startup */\n\tslock_t mutex;\t  /* Controls access to the reader pid */\n\tLWLock *lock;\t  /* Pointer to the lock to control\n\t\t\t\t\t   * adding/removing elements from queue */\n\tuint8 read_upto;\n\tuint8 num_elements;\n\tBgwMessage buffer[BGW_MQ_MAX_MESSAGES];\n} MessageQueue;\n\ntypedef enum QueueResponseType\n{\n\tMESSAGE_SENT = 0,\n\tQUEUE_FULL,\n\tREADER_DETACHED\n} QueueResponseType;\n\nstatic MessageQueue *mq = NULL;\n\n/*\n * This is run during the shmem_startup_hook.\n * On Linux, it's only run once, but in EXEC_BACKEND mode / on Windows/ other systems\n * that do forking differently, it is run in every backend at startup\n */\nstatic void\nqueue_init()\n{\n\tbool found;\n\n\tLWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);\n\tmq = ShmemInitStruct(BGW_MQ_NAME, sizeof(MessageQueue), &found);\n\tif (!found)\n\t{\n\t\tmemset(mq, 0, sizeof(MessageQueue));\n\t\tmq->reader_pid = InvalidPid;\n\t\tSpinLockInit(&mq->mutex);\n\t\tmq->lock = &(GetNamedLWLockTranche(BGW_MQ_TRANCHE_NAME))->lock;\n\t}\n\tLWLockRelease(AddinShmemInitLock);\n}\n\n/* This gets called when shared memory is initialized in a backend\n * (shmem_startup_hook) */\nextern void\nts_bgw_message_queue_shmem_startup(void)\n{\n\tqueue_init();\n}\n\n/* This is called in the loader during server startup to allocate a shared\n * memory segment*/\nextern void\nts_bgw_message_queue_alloc(void)\n{\n\tRequestAddinShmemSpace(sizeof(MessageQueue));\n\tRequestNamedLWLockTranche(BGW_MQ_TRANCHE_NAME, 1);\n}\n\n/*\n * Notes on managing the queue/locking: We decided that for this application,\n * simplicity of locking scheme was more important than being very good about\n * concurrency as the frequency of these messages will be low and the number\n * of messages on this queue should be low, given that they mostly happen when\n * we update the extension. Therefore we decided to simply take an exclusive\n * lock whenever we were modifying anything in the shared memory segment to\n * avoid collisions.\n */\nstatic pid_t\nqueue_get_reader(MessageQueue *queue)\n{\n\tpid_t reader;\n\tvolatile MessageQueue *vq = queue;\n\n\tSpinLockAcquire(&vq->mutex);\n\treader = vq->reader_pid;\n\tSpinLockRelease(&vq->mutex);\n\treturn reader;\n}\n\nstatic void\nqueue_set_reader(MessageQueue *queue)\n{\n\tvolatile MessageQueue *vq = queue;\n\tpid_t reader_pid;\n\n\tSpinLockAcquire(&vq->mutex);\n\tif (vq->reader_pid == InvalidPid)\n\t{\n\t\tvq->reader_pid = MyProcPid;\n\t}\n\treader_pid = vq->reader_pid;\n\tSpinLockRelease(&vq->mutex);\n\tif (reader_pid != MyProcPid)\n\t\tereport(ERROR,\n\t\t\t\t(errmsg(\"only one reader allowed for TimescaleDB background worker message queue\"),\n\t\t\t\t errhint(\"Current process is %d.\", reader_pid)));\n}\n\nstatic void\nqueue_reset_reader(MessageQueue *queue)\n{\n\tvolatile MessageQueue *vq = queue;\n\tbool reset = false;\n\n\tSpinLockAcquire(&vq->mutex);\n\tif (vq->reader_pid == MyProcPid)\n\t{\n\t\treset = true;\n\t\tvq->reader_pid = InvalidPid;\n\t}\n\tSpinLockRelease(&vq->mutex);\n\n\tif (!reset)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t errmsg(\"multiple TimescaleDB background worker launchers have been started when \"\n\t\t\t\t\t\t\"only one is allowed\")));\n}\n\n/* Add a message to the queue - we can do this if the queue is not full */\nstatic QueueResponseType\nqueue_add(MessageQueue *queue, BgwMessage *message)\n{\n\tQueueResponseType message_result = QUEUE_FULL;\n\n\tLWLockAcquire(queue->lock, LW_EXCLUSIVE);\n\tif (queue->num_elements < BGW_MQ_MAX_MESSAGES)\n\t{\n\t\tmemcpy(&queue->buffer[(queue->read_upto + queue->num_elements) % BGW_MQ_MAX_MESSAGES],\n\t\t\t   message,\n\t\t\t   sizeof(BgwMessage));\n\t\tqueue->num_elements++;\n\t\tmessage_result = MESSAGE_SENT;\n\t}\n\tLWLockRelease(queue->lock);\n\n\tif (queue_get_reader(queue) != InvalidPid)\n\t\tSetLatch(&BackendPidGetProc(queue_get_reader(queue))->procLatch);\n\telse\n\t\tmessage_result = READER_DETACHED;\n\treturn message_result;\n}\n\nstatic BgwMessage *\nqueue_remove(MessageQueue *queue)\n{\n\tBgwMessage *message = NULL;\n\n\tLWLockAcquire(queue->lock, LW_EXCLUSIVE);\n\tif (queue_get_reader(queue) != MyProcPid)\n\t\tereport(ERROR,\n\t\t\t\t(errmsg(\n\t\t\t\t\t\"cannot read if not reader for TimescaleDB background worker message queue\")));\n\n\tif (queue->num_elements > 0)\n\t{\n\t\tmessage = palloc(sizeof(BgwMessage));\n\t\tmemcpy(message, &queue->buffer[queue->read_upto], sizeof(BgwMessage));\n\t\tqueue->read_upto = (queue->read_upto + 1) % BGW_MQ_MAX_MESSAGES;\n\t\tqueue->num_elements--;\n\t}\n\tLWLockRelease(queue->lock);\n\treturn message;\n}\n\n/* Construct a message */\nstatic BgwMessage *\nbgw_message_create(BgwMessageType message_type, Oid db_oid)\n{\n\tBgwMessage *message = palloc(sizeof(BgwMessage));\n\tdsm_segment *seg;\n\n\tseg = dsm_create(BGW_ACK_QUEUE_SIZE, 0);\n\n\t*message = (BgwMessage){ .message_type = message_type,\n\t\t\t\t\t\t\t .sender_pid = MyProcPid,\n\t\t\t\t\t\t\t .db_oid = db_oid,\n\t\t\t\t\t\t\t .ack_dsm_handle = dsm_segment_handle(seg) };\n\n\treturn message;\n}\n\n/*\n * Our own version of shm_mq_wait_for_attach that waits with a timeout so that\n * should our counterparty die before attaching, we don't end up hanging.\n */\nstatic shm_mq_result\nts_shm_mq_wait_for_attach(MessageQueue *queue, shm_mq_handle *ack_queue_handle)\n{\n\tint n;\n\tPGPROC *reader_proc;\n\n\tfor (n = 1; n <= BGW_MQ_NUM_WAITS; n++)\n\t{\n\t\t/* The reader is the sender on the ack queue */\n\t\treader_proc = shm_mq_get_sender(shm_mq_get_queue(ack_queue_handle));\n\t\tif (reader_proc != NULL)\n\t\t\treturn SHM_MQ_SUCCESS;\n\t\telse if (queue_get_reader(queue) == InvalidPid)\n\t\t\treturn SHM_MQ_DETACHED; /* Reader died after we enqueued our\n\t\t\t\t\t\t\t\t\t * message */\n\t\tWaitLatch(MyLatch,\n\t\t\t\t  WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,\n\t\t\t\t  BGW_MQ_WAIT_INTERVAL,\n\t\t\t\t  WAIT_EVENT_MESSAGE_QUEUE_INTERNAL);\n\n\t\tResetLatch(MyLatch);\n\t\tCHECK_FOR_INTERRUPTS();\n\t}\n\treturn SHM_MQ_DETACHED;\n}\n\nstatic bool\nenqueue_message_wait_for_ack(MessageQueue *queue, BgwMessage *message,\n\t\t\t\t\t\t\t shm_mq_handle *ack_queue_handle)\n{\n\tSize bytes_received = 0;\n\tQueueResponseType send_result;\n\tbool *data = NULL;\n\tshm_mq_result mq_res;\n\tbool ack_received = false;\n\tint n;\n\n\t/*\n\t * We don't want the process restarting workers to really distinguish the\n\t * reasons workers might or might not be restarted, and we don't really\n\t * want them to error when workers can't be started, as there are multiple\n\t * valid reasons for that. So we'll simply return false for the ack even\n\t * if we can't attach to the queue etc.\n\t */\n\tsend_result = queue_add(queue, message);\n\tif (send_result != MESSAGE_SENT)\n\t\treturn false;\n\n\tmq_res = ts_shm_mq_wait_for_attach(queue, ack_queue_handle);\n\tif (mq_res != SHM_MQ_SUCCESS)\n\t\treturn false;\n\n\t/* Get a response, non-blocking, with retries */\n\tfor (n = 1; n <= BGW_ACK_RETRIES; n++)\n\t{\n\t\tmq_res = shm_mq_receive(ack_queue_handle, &bytes_received, (void **) &data, true);\n\t\tif (mq_res != SHM_MQ_WOULD_BLOCK)\n\t\t\tbreak;\n\t\tereport(DEBUG1, (errmsg(\"TimescaleDB ack message receive failure, retrying\")));\n\t\tWaitLatch(MyLatch,\n\t\t\t\t  WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,\n\t\t\t\t  BGW_ACK_WAIT_INTERVAL,\n\t\t\t\t  WAIT_EVENT_MESSAGE_QUEUE_INTERNAL);\n\t\tResetLatch(MyLatch);\n\t\tCHECK_FOR_INTERRUPTS();\n\t}\n\n\tif (mq_res != SHM_MQ_SUCCESS)\n\t\treturn false;\n\n\tack_received = (bytes_received != 0) && *data;\n\n\treturn ack_received;\n}\n\n/*\n * Write element to queue, wait/error if queue is full\n * consumes message and deallocates\n */\nextern bool\nts_bgw_message_send_and_wait(BgwMessageType message_type, Oid db_oid)\n{\n\tshm_mq *ack_queue;\n\tdsm_segment *seg;\n\tshm_mq_handle *ack_queue_handle;\n\tBgwMessage *message;\n\tbool ack_received = false;\n\n\tmessage = bgw_message_create(message_type, db_oid);\n\n\tseg = dsm_find_mapping(message->ack_dsm_handle);\n\tif (seg == NULL)\n\t\tereport(ERROR,\n\t\t\t\t(errmsg(\"TimescaleDB background worker dynamic shared memory segment not mapped\")));\n\tack_queue = shm_mq_create(dsm_segment_address(seg), BGW_ACK_QUEUE_SIZE);\n\tshm_mq_set_receiver(ack_queue, MyProc);\n\tack_queue_handle = shm_mq_attach(ack_queue, seg, NULL);\n\tif (ack_queue_handle != NULL)\n\t\tack_received = enqueue_message_wait_for_ack(mq, message, ack_queue_handle);\n\tdsm_detach(seg); /* Queue detach happens in dsm detach callback */\n\tpfree(message);\n\treturn ack_received;\n}\n\n/*\n * Called only by the launcher\n */\nextern BgwMessage *\nts_bgw_message_receive(void)\n{\n\treturn queue_remove(mq);\n}\n\nextern void\nts_bgw_message_queue_set_reader(void)\n{\n\tqueue_set_reader(mq);\n}\n\ntypedef enum MessageAckSent\n{\n\tACK_SENT = 0,\n\tDSM_SEGMENT_UNAVAILABLE,\n\tQUEUE_NOT_ATTACHED,\n\tSEND_FAILURE\n} MessageAckSent;\n\nstatic const char *message_ack_sent_err[] = { [ACK_SENT] = \"Sent ack successfully\",\n\t\t\t\t\t\t\t\t\t\t\t  [DSM_SEGMENT_UNAVAILABLE] = \"DSM Segment unavailable\",\n\t\t\t\t\t\t\t\t\t\t\t  [QUEUE_NOT_ATTACHED] = \"Ack queue unable to attach\",\n\t\t\t\t\t\t\t\t\t\t\t  [SEND_FAILURE] = \"Unable to send ack on queue\" };\n\nstatic MessageAckSent\nsend_ack(dsm_segment *seg, bool success)\n{\n\tshm_mq *ack_queue;\n\tshm_mq_handle *ack_queue_handle;\n\tshm_mq_result ack_res;\n\tint n;\n\n\tack_queue = dsm_segment_address(seg);\n\tif (ack_queue == NULL)\n\t\treturn DSM_SEGMENT_UNAVAILABLE;\n\n\tshm_mq_set_sender(ack_queue, MyProc);\n\tack_queue_handle = shm_mq_attach(ack_queue, seg, NULL);\n\tif (ack_queue_handle == NULL)\n\t\treturn QUEUE_NOT_ATTACHED;\n\n\t/* Send the message off, non blocking, with retries */\n\tfor (n = 1; n <= BGW_ACK_RETRIES; n++)\n\t{\n\t\tack_res = shm_mq_send(ack_queue_handle, sizeof(bool), &success, true, true);\n\t\tif (ack_res != SHM_MQ_WOULD_BLOCK)\n\t\t\tbreak;\n\t\tereport(DEBUG1, (errmsg(\"TimescaleDB ack message send failure, retrying\")));\n\t\tWaitLatch(MyLatch,\n\t\t\t\t  WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,\n\t\t\t\t  BGW_ACK_WAIT_INTERVAL,\n\t\t\t\t  WAIT_EVENT_MESSAGE_QUEUE_INTERNAL);\n\t\tResetLatch(MyLatch);\n\t\tCHECK_FOR_INTERRUPTS();\n\t}\n\n\t/* we are responsible for pfree'ing the handle, the dsm infrastructure only\n\t * deals with the queue itself\n\t */\n\tpfree(ack_queue_handle);\n\tif (ack_res != SHM_MQ_SUCCESS)\n\t\treturn SEND_FAILURE;\n\n\treturn ACK_SENT;\n}\n\n/*\n * Called by launcher once it has taken action based on the contents of the message\n * consumes message and deallocates\n */\nextern void\nts_bgw_message_send_ack(BgwMessage *message, bool success)\n{\n\tdsm_segment *seg;\n\n\t/*\n\t * PG 9.6 does not check to see if we had a CurrentResourceOwner inside of\n\t * dsm.c->dsm_create_descriptor.  Basically, it assumed we were in a\n\t * transaction if we ever attached to the dsm, whereas PG 10 addressed\n\t * that and did proper NULL checking. So, if we are in 9.6, we start a\n\t * transaction and then commit it at the end of ack sending, to be sure\n\t * everything is cleaned up properly etc.\n\t */\n\tseg = dsm_attach(message->ack_dsm_handle);\n\tif (seg != NULL)\n\t{\n\t\tMessageAckSent ack_res;\n\n\t\tack_res = send_ack(seg, success);\n\t\tif (ack_res != ACK_SENT)\n\t\t\tereport(DEBUG1,\n\t\t\t\t\t(errmsg(\"TimescaleDB background worker launcher unable to send ack to backend \"\n\t\t\t\t\t\t\t\"pid %d\",\n\t\t\t\t\t\t\tmessage->sender_pid),\n\t\t\t\t\t errhint(\"Reason: %s\", message_ack_sent_err[ack_res])));\n\t\tdsm_detach(seg);\n\t}\n\tpfree(message);\n}\n\n/*\n * This gets called before shmem exit in the launcher (even if we're exiting\n * in error, but not if we're exiting due to possible shmem corruption)\n */\nstatic void\nqueue_shmem_cleanup(MessageQueue *queue)\n{\n\tqueue_reset_reader(queue);\n}\n\nextern void\nts_bgw_message_queue_shmem_cleanup(void)\n{\n\tqueue_shmem_cleanup(mq);\n}\n"
  },
  {
    "path": "src/loader/bgw_message_queue.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <storage/dsm.h>\n\ntypedef enum BgwMessageType\n{\n\tSTOP = 0,\n\tSTART,\n\tRESTART\n} BgwMessageType;\n\ntypedef struct BgwMessage\n{\n\tBgwMessageType message_type;\n\n\tpid_t sender_pid;\n\tOid db_oid;\n\tdsm_handle ack_dsm_handle;\n\n} BgwMessage;\n\nextern bool ts_bgw_message_send_and_wait(BgwMessageType message, Oid db_oid);\n\n/* called only by the launcher*/\nextern void ts_bgw_message_queue_set_reader(void);\nextern BgwMessage *ts_bgw_message_receive(void);\nextern void ts_bgw_message_send_ack(BgwMessage *message, bool success);\n\n/*called at server startup*/\nextern void ts_bgw_message_queue_alloc(void);\n\n/*called in every backend during shmem startup hook*/\nextern void ts_bgw_message_queue_shmem_startup(void);\nextern void ts_bgw_message_queue_shmem_cleanup(void);\n"
  },
  {
    "path": "src/loader/function_telemetry.c",
    "content": "\n/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <fmgr.h>\n\n#include <port/atomics.h>\n#include <storage/lwlock.h>\n#include <storage/shmem.h>\n\n#include \"loader/function_telemetry.h\"\n\n// Function telemetry hash table size. Sized to be large enough that we're\n// unlikely to run out of entries, but small enough that it won't have a\n// noticeable impact.\n#define FN_TELEMETRY_HASH_SIZE 10000\n\nstatic FnTelemetryRendezvous rendezvous;\n\nvoid\nts_function_telemetry_shmem_startup()\n{\n\tFnTelemetryRendezvous **rendezvous_ptr;\n\tHASHCTL hash_info;\n\tHTAB *function_telemetry_hash;\n\tLWLock **lock;\n\tbool found;\n\n\t// NOTE: dshash would be better once it's stable\n\thash_info.keysize = sizeof(Oid);\n\thash_info.entrysize = sizeof(FnTelemetryHashEntry);\n\n\tLWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);\n\n\t/*\n\t * GetNamedLWLockTranche must only be run once on windows, otherwise it\n\t * segfaults. Since the shmem_startup_hook is run on every backend, we use\n\t * a ShmemInitStruct to detect if this function has been called before.\n\t */\n\tlock = (LWLock **) ShmemInitStruct(\"fn_telemetry_detect_first_run\", sizeof(LWLock *), &found);\n\tif (!found)\n\t\t*lock = &(GetNamedLWLockTranche(FN_TELEMETRY_LWLOCK_TRANCHE_NAME))->lock;\n\n\tfunction_telemetry_hash = ShmemInitHash(\"timescaledb function telemetry hash\",\n\t\t\t\t\t\t\t\t\t\t\tFN_TELEMETRY_HASH_SIZE,\n\t\t\t\t\t\t\t\t\t\t\tFN_TELEMETRY_HASH_SIZE,\n\t\t\t\t\t\t\t\t\t\t\t&hash_info,\n\t\t\t\t\t\t\t\t\t\t\tHASH_ELEM | HASH_BLOBS);\n\tLWLockRelease(AddinShmemInitLock);\n\n\trendezvous.lock = *lock;\n\trendezvous.function_counts = function_telemetry_hash;\n\n\trendezvous_ptr =\n\t\t(FnTelemetryRendezvous **) find_rendezvous_variable(RENDEZVOUS_FUNCTION_TELEMENTRY);\n\t*rendezvous_ptr = &rendezvous;\n}\n\nvoid\nts_function_telemetry_shmem_alloc()\n{\n\tSize size = hash_estimate_size(FN_TELEMETRY_HASH_SIZE, sizeof(FnTelemetryHashEntry));\n\tRequestAddinShmemSpace(add_size(size, sizeof(LWLock *)));\n\tRequestNamedLWLockTranche(FN_TELEMETRY_LWLOCK_TRANCHE_NAME, 1);\n}\n"
  },
  {
    "path": "src/loader/function_telemetry.h",
    "content": "\n/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#define RENDEZVOUS_FUNCTION_TELEMENTRY \"ts_function_telemetry\"\n#define FN_TELEMETRY_LWLOCK_TRANCHE_NAME \"ts_fn_telemetry_lwlock_tranche\"\n\ntypedef struct FnTelemetryRendezvous\n{\n\tLWLock *lock;\n\tHTAB *function_counts;\n} FnTelemetryRendezvous;\n\ntypedef struct FnTelemetryHashEntry\n{\n\tOid key;\n\tpg_atomic_uint64 count;\n} FnTelemetryHashEntry;\n\nextern void ts_function_telemetry_shmem_startup(void);\n\nextern void ts_function_telemetry_shmem_alloc(void);\n"
  },
  {
    "path": "src/loader/loader.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <access/heapam.h>\n#include <access/parallel.h>\n#include <access/xact.h>\n#include <catalog/pg_database.h>\n#include <commands/dbcommands.h>\n#include <commands/defrem.h>\n#include <commands/user.h>\n#include <nodes/print.h>\n#include <parser/analyze.h>\n#include <pg_config.h>\n#include <postmaster/bgworker.h>\n#include <storage/ipc.h>\n#include <tcop/utility.h>\n#include <utils/guc.h>\n#include <utils/inval.h>\n\n#if PG_VERSION_NUM < 150000\n#include \"compat/compat-msvc-enter.h\"\n#include <commands/extension.h>\n#include <miscadmin.h>\n#include \"compat/compat-msvc-exit.h\"\n#endif\n\n#include \"compat/compat.h\"\n#include \"config.h\"\n#include \"export.h\"\n#include \"extension_constants.h\"\n#include \"extension_utils.c\"\n\n#include \"loader/bgw_counter.h\"\n#include \"loader/bgw_interface.h\"\n#include \"loader/bgw_launcher.h\"\n#include \"loader/bgw_message_queue.h\"\n#include \"loader/function_telemetry.h\"\n#include \"loader/loader.h\"\n#include \"loader/lwlocks.h\"\n\n/*\n * Loading process:\n *\n *   1. _PG_init starts up cluster-wide background worker stuff, and sets the\n *      post_parse_analyze_hook (a postgres-defined hook which is called after\n *      every statement is parsed) to our function post_analyze_hook\n *   2. When a command is run with timescale not loaded, post_analyze_hook:\n *        a. Gets the extension version.\n *        b. Loads the versioned extension.\n *        c. Grabs the post_parse_analyze_hook from the versioned extension\n *           (src/init.c:post_analyze_hook) and stores it in\n *           extension_post_parse_analyze_hook.\n *        d. Sets the post_parse_analyze_hook back to what it was before we\n *           loaded the versioned extension (this hook eventually called our\n *           post_analyze_hook, but may not be our function, for instance, if\n *           another extension is loaded).\n *        e. Calls extension_post_parse_analyze_hook.\n *        f. Calls the prev_post_parse_analyze_hook.\n *\n * Some notes on design:\n *\n * We do not check for the installation of the extension upon loading the extension and instead rely\n * on a hook for a few reasons:\n *\n * 1) We probably can't:\n *    - The shared_preload_libraries is called in PostmasterMain which is way before InitPostgres is\n *      called. Note: This happens even before the fork of the backend, so we don't even know which\n *      database this is for.\n *    - This means we cannot query for the existence of the extension yet because the caches are\n *      initialized in InitPostgres.\n *\n * 2) We actually don't want to load the extension in two cases:\n *    a) We are upgrading the extension.\n *    b) We set the guc timescaledb.disable_load.\n *\n * 3) We include a section for the bgw launcher and some workers below the rest, separated with its\n *    own notes, some function definitions are included as they are referenced by other loader\n *    functions.\n *\n */\n\n#ifdef PG_MODULE_MAGIC\nPG_MODULE_MAGIC;\n#endif\n\n#define POST_LOAD_INIT_FN \"ts_post_load_init\"\n#define GUC_LAUNCHER_POLL_TIME_MS MAKE_EXTOPTION(\"bgw_launcher_poll_time\")\n\n/*\n * The loader really shouldn't load if we're in a parallel worker as there is a\n * separate infrastructure for loading libraries inside of parallel workers. The\n * issue is that IsParallelWorker() doesn't work on Windows because the var used\n * is not dll exported correctly, so we have an alternate macro that looks for\n * the parallel worker flags in MyBgworkerEntry, if it exists.\n */\n\n#define CalledInParallelWorker()                                                                   \\\n\t(MyBgworkerEntry != NULL && (MyBgworkerEntry->bgw_flags & BGWORKER_CLASS_PARALLEL) != 0)\n\n#if PG16_LT\nextern void TSDLLEXPORT _PG_init(void);\n#endif\n\n/* was the versioned-extension loaded*/\nstatic bool loader_present = true;\n\nint ts_guc_bgw_launcher_poll_time = BGW_LAUNCHER_POLL_TIME_MS;\n\n/* This is the hook that existed before the loader was installed */\nstatic post_parse_analyze_hook_type prev_post_parse_analyze_hook;\nstatic shmem_startup_hook_type prev_shmem_startup_hook;\nstatic shmem_request_hook_type prev_shmem_request_hook;\n\ntypedef struct TsExtension\n{\n\t/*\n\t * Static data\n\t */\n\n\t/* Name of the extension (must be part of the so file name) */\n\tchar const *const name;\n\t/* Name of the schema for table_name. */\n\tchar const *const schema_name;\n\t/* Name of the table whose existence indicates the extension is loaded. */\n\tchar const *const table_name;\n\t/* Name of the GUC for disabling loading this extension. */\n\tchar const *const guc_disable_load_name;\n\n\t/*\n\t * Run-time state\n\t */\n\n\t/* Current value of this extension's disable GUC. */\n\tbool guc_disable_load;\n\n\t/* Shared object library version loaded; empty if none. */\n\tchar soversion[MAX_VERSION_LEN];\n\n\t/* TODO Remove.  Neither timescaledb nor OSM actually have this hook,\n\t * never have, and we don't plan to add them. */\n\tpost_parse_analyze_hook_type post_parse_analyze_hook;\n} TsExtension;\n\nTsExtension extensions[] = {\n\t/* Redundant default initializers are here because we compile with\n\t * `-Werror -Wmissing-field-initializers` for our PG13 build... */\n\t{\n\t\t.name = EXTENSION_NAME,\n\t\t.schema_name = CACHE_SCHEMA_NAME,\n\t\t.table_name = EXTENSION_PROXY_TABLE,\n\t\t.guc_disable_load_name = MAKE_EXTOPTION(\"disable_load\"),\n\t\t.guc_disable_load = false,\n\t\t.soversion = \"\",\n\t\t.post_parse_analyze_hook = NULL,\n\t},\n\t{\n\t\t.name = \"timescaledb_osm\",\n\t\t.schema_name = \"_osm_catalog\",\n\t\t.table_name = \"metadata\",\n\t\t.guc_disable_load_name = \"timescaledb_osm.disable_load\",\n\t\t.guc_disable_load = false,\n\t\t.soversion = \"\",\n\t\t.post_parse_analyze_hook = NULL,\n\t},\n\t{\n\t\t.name = \"timescaledb_lake\",\n\t\t.schema_name = \"_timescaledb_lake_catalog\",\n\t\t.table_name = \"metadata\",\n\t\t.guc_disable_load_name = \"timescaledb_lake.disable_load\",\n\t\t.guc_disable_load = false,\n\t\t.soversion = \"\",\n\t\t.post_parse_analyze_hook = NULL,\n\t},\n};\n\ninline static void extension_check(TsExtension * /*ext*/);\nstatic void call_extension_post_parse_analyze_hook(ParseState *pstate, Query *query,\n\t\t\t\t\t\t\t\t\t\t\t\t   TsExtension const * /*ext*/,\n\t\t\t\t\t\t\t\t\t\t\t\t   JumbleState *jstate);\n\nstatic bool\nextension_is_loaded(TsExtension const *const ext)\n{\n\t/* The extension is loaded when the version is set to a non-null string */\n\treturn ext->soversion[0] != '\\0';\n}\n\nextern char *\nts_loader_extension_version(void)\n{\n\treturn extension_version(EXTENSION_NAME);\n}\n\nextern bool\nts_loader_extension_exists(void)\n{\n\treturn extension_exists(EXTENSION_NAME);\n}\n\nstatic bool\ndrop_statement_drops_extension(DropStmt const *const stmt, TsExtension const *const ext)\n{\n\tif (!extension_exists(ext->name))\n\t\treturn false;\n\n\tif (stmt->removeType == OBJECT_EXTENSION)\n\t{\n\t\tif (list_length(stmt->objects) == 1)\n\t\t{\n\t\t\tchar *ext_name;\n\t\t\tvoid *name = linitial(stmt->objects);\n\n\t\t\text_name = strVal(name);\n\t\t\tif (strcmp(ext_name, ext->name) == 0)\n\t\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\nstatic Oid\nextension_owner(TsExtension const *const ext)\n{\n\tDatum result;\n\tRelation rel;\n\tSysScanDesc scandesc;\n\tHeapTuple tuple;\n\tScanKeyData entry[1];\n\tbool is_null = true;\n\tOid extension_owner = InvalidOid;\n\n\trel = table_open(ExtensionRelationId, AccessShareLock);\n\n\tScanKeyInit(&entry[0],\n\t\t\t\tAnum_pg_extension_extname,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_NAMEEQ,\n\t\t\t\tCStringGetDatum(ext->name));\n\n\tscandesc = systable_beginscan(rel, ExtensionNameIndexId, true, NULL, 1, entry);\n\n\ttuple = systable_getnext(scandesc);\n\n\t/* We assume that there can be at most one matching tuple */\n\tif (HeapTupleIsValid(tuple))\n\t{\n\t\tresult = heap_getattr(tuple, Anum_pg_extension_extowner, RelationGetDescr(rel), &is_null);\n\n\t\tif (!is_null)\n\t\t\textension_owner = ObjectIdGetDatum(result);\n\t}\n\n\tsystable_endscan(scandesc);\n\ttable_close(rel, AccessShareLock);\n\n\tif (!OidIsValid(extension_owner))\n\t\telog(ERROR, \"extension not found while getting owner\");\n\n\treturn extension_owner;\n}\n\nstatic bool\ndrop_owned_statement_drops_extension(DropOwnedStmt const *const stmt, TsExtension const *const ext)\n{\n\tOid extension_owner_oid;\n\tList *role_ids;\n\tListCell *lc;\n\n\tif (!extension_exists(ext->name))\n\t\treturn false;\n\n\tAssert(IsTransactionState());\n\textension_owner_oid = extension_owner(ext);\n\n\trole_ids = roleSpecsToIds(stmt->roles);\n\n\t/* Check privileges */\n\tforeach (lc, role_ids)\n\t{\n\t\tOid role_id = lfirst_oid(lc);\n\n\t\tif (role_id == extension_owner_oid)\n\t\t\treturn true;\n\t}\n\treturn false;\n}\n\nstatic bool\nshould_load_on_variable_set(Node const *const utility_stmt, TsExtension const *const ext)\n{\n\tVariableSetStmt *stmt = (VariableSetStmt *) utility_stmt;\n\n\tswitch (stmt->kind)\n\t{\n\t\tcase VAR_SET_VALUE:\n\t\tcase VAR_SET_DEFAULT:\n\t\tcase VAR_RESET:\n\t\t\t/* Do not load when setting the guc to disable load */\n\t\t\treturn stmt->name == NULL || strcmp(stmt->name, ext->guc_disable_load_name) != 0;\n\t\tdefault:\n\t\t\treturn true;\n\t}\n}\n\nstatic bool\nshould_load_on_alter_extension(Node const *const utility_stmt, TsExtension const *const ext)\n{\n\tAlterExtensionStmt *stmt = (AlterExtensionStmt *) utility_stmt;\n\n\tif (strcmp(stmt->extname, ext->name) != 0)\n\t\treturn true;\n\n\t/* disallow loading two .so from different versions */\n\tif (extension_is_loaded(ext))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"extension \\\"%s\\\" cannot be updated after the old version has already been \"\n\t\t\t\t\t\t\"loaded\",\n\t\t\t\t\t\tstmt->extname),\n\t\t\t\t errhint(\"Start a new session and execute ALTER EXTENSION as the first command. \"\n\t\t\t\t\t\t \"Make sure to pass the \\\"-X\\\" flag to psql.\")));\n\t/* do not load the current (old) version's .so */\n\treturn false;\n}\n\nstatic bool\nshould_load_on_create_extension(Node const *const utility_stmt, TsExtension const *const ext)\n{\n\tCreateExtensionStmt *stmt = (CreateExtensionStmt *) utility_stmt;\n\n\tif (strcmp(stmt->extname, ext->name) != 0)\n\t\treturn false;\n\n\t/* If set, a library has already been loaded */\n\tif (!extension_is_loaded(ext))\n\t\treturn true;\n\n\t/*\n\t * If the extension exists and the create statement has an IF NOT EXISTS\n\t * option, we continue without loading and let CREATE EXTENSION bail out\n\t * with a standard NOTICE. We can only do this if the extension actually\n\t * exists (is created), or else we might potentially load the shared\n\t * library of another version of the extension. Loading typically happens\n\t * on CREATE EXTENSION (via CREATE FUNCTION as SQL files are installed)\n\t * even if we do not explicitly load the library here. If we load another\n\t * version of the library, in addition to the currently loaded version, we\n\t * might taint the backend.\n\t */\n\tif (extension_exists(ext->name) && stmt->if_not_exists)\n\t\treturn false;\n\n\t/*\n\t * If the extension does not exist (e.g., was dropped via DROP SCHEMA\n\t * CASCADE) but the same version of the shared library is already loaded\n\t * in this session, allow the CREATE EXTENSION to proceed without\n\t * reloading. The .so is already in memory with all hooks in place, so\n\t * CREATE EXTENSION just needs to install the SQL objects.\n\t *\n\t * We only allow this when no explicit VERSION is specified (meaning the\n\t * default version from the control file will be used, which matches the\n\t * loaded .so) or when the specified VERSION matches the loaded version.\n\t */\n\tif (!extension_exists(ext->name))\n\t{\n\t\tchar *requested_version = NULL;\n\t\tListCell *lc;\n\n\t\tforeach (lc, stmt->options)\n\t\t{\n\t\t\tDefElem *d = (DefElem *) lfirst(lc);\n\n\t\t\tif (strcmp(d->defname, \"new_version\") == 0)\n\t\t\t{\n\t\t\t\trequested_version = defGetString(d);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (requested_version == NULL || strcmp(requested_version, ext->soversion) == 0)\n\t\t\treturn false;\n\t}\n\n\t/* disallow loading two .so from different versions */\n\tereport(ERROR,\n\t\t\t(errcode(ERRCODE_DUPLICATE_OBJECT),\n\t\t\t errmsg(\"extension \\\"%s\\\" has already been loaded with another version\", stmt->extname),\n\t\t\t errdetail(\"The loaded version is \\\"%s\\\".\", ext->soversion),\n\t\t\t errhint(\"Start a new session and execute CREATE EXTENSION as the first command. \"\n\t\t\t\t\t \"Make sure to pass the \\\"-X\\\" flag to psql.\")));\n\treturn false;\n}\n\nstatic bool\nload_utility_cmd(Node const *const utility_stmt, TsExtension const *const ext)\n{\n\tswitch (nodeTag(utility_stmt))\n\t{\n\t\tcase T_VariableSetStmt:\n\t\t\treturn should_load_on_variable_set(utility_stmt, ext);\n\t\tcase T_AlterExtensionStmt:\n\t\t\treturn should_load_on_alter_extension(utility_stmt, ext);\n\t\tcase T_CreateExtensionStmt:\n\t\t\treturn should_load_on_create_extension(utility_stmt, ext);\n\t\tcase T_DropStmt:\n\t\t\treturn !drop_statement_drops_extension((DropStmt *) utility_stmt, ext);\n\t\tdefault:\n\t\t\treturn true;\n\t}\n}\n\nstatic void\nstop_workers_on_db_drop(DropdbStmt *drop_db_statement)\n{\n\t/*\n\t * Don't check if extension exists here because even though the current\n\t * database might not have TimescaleDB installed the database we are\n\t * dropping might.\n\t */\n\tOid dropped_db_oid = get_database_oid(drop_db_statement->dbname, drop_db_statement->missing_ok);\n\n\tif (OidIsValid(dropped_db_oid))\n\t{\n\t\tereport(LOG,\n\t\t\t\t(errmsg(\"TimescaleDB background worker scheduler for database %u will be stopped\",\n\t\t\t\t\t\tdropped_db_oid)));\n\t\tts_bgw_message_send_and_wait(STOP, dropped_db_oid);\n\t}\n}\n\nstatic bool\ndatabase_allowconn(const Oid db_oid)\n{\n\tRelation pg_database;\n\tScanKeyData entry[1];\n\tSysScanDesc scan;\n\tHeapTuple dbtuple;\n\tbool allowconn = false;\n\n\tpg_database = table_open(DatabaseRelationId, AccessShareLock);\n\tScanKeyInit(&entry[0],\n\t\t\t\tAnum_pg_database_oid,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_OIDEQ,\n\t\t\t\tObjectIdGetDatum(db_oid));\n\tscan = systable_beginscan(pg_database, DatabaseOidIndexId, true, NULL, 1, entry);\n\n\tdbtuple = systable_getnext(scan);\n\n\t/* We assume that there can be at most one matching tuple */\n\tif (!HeapTupleIsValid(dbtuple))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_DATABASE),\n\t\t\t\t errmsg(\"database with OID \\\"%u\\\" does not exist\", db_oid)));\n\n\tallowconn = ((Form_pg_database) GETSTRUCT(dbtuple))->datallowconn;\n\n\tsystable_endscan(scan);\n\ttable_close(pg_database, AccessShareLock);\n\n\treturn allowconn;\n}\n\nstatic void\npost_analyze_hook(ParseState *pstate, Query *query, JumbleState *jstate)\n{\n\tif (query->commandType == CMD_UTILITY)\n\t{\n\t\tswitch (nodeTag(query->utilityStmt))\n\t\t{\n\t\t\tcase T_AlterDatabaseStmt:\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * On ALTER DATABASE SET TABLESPACE we need to stop background\n\t\t\t\t * workers for the command to succeed.\n\t\t\t\t */\n\t\t\t\tAlterDatabaseStmt *stmt = (AlterDatabaseStmt *) query->utilityStmt;\n\t\t\t\tif (list_length(stmt->options) == 1)\n\t\t\t\t{\n\t\t\t\t\tDefElem *option = linitial(stmt->options);\n\t\t\t\t\tif (option->defname && strcmp(option->defname, \"tablespace\") == 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tOid db_oid = get_database_oid(stmt->dbname, false);\n\n\t\t\t\t\t\tif (OidIsValid(db_oid))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tts_bgw_message_send_and_wait(RESTART, db_oid);\n\t\t\t\t\t\t\tereport(WARNING,\n\t\t\t\t\t\t\t\t\t(errmsg(\"you may need to manually restart any running \"\n\t\t\t\t\t\t\t\t\t\t\t\"background workers after this command\")));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase T_CreatedbStmt:\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * If we create a database and the database used as template\n\t\t\t\t * has background workers we need to stop those background\n\t\t\t\t * workers connected to the template database.\n\t\t\t\t */\n\t\t\t\tCreatedbStmt *stmt = (CreatedbStmt *) query->utilityStmt;\n\t\t\t\tListCell *lc;\n\n\t\t\t\tforeach (lc, stmt->options)\n\t\t\t\t{\n\t\t\t\t\tDefElem *option = lfirst(lc);\n\t\t\t\t\tif (option->defname != NULL && option->arg != NULL &&\n\t\t\t\t\t\tstrcmp(option->defname, \"template\") == 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tOid db_oid = get_database_oid(defGetString(option), false);\n\n\t\t\t\t\t\tif (OidIsValid(db_oid) && database_allowconn(db_oid))\n\t\t\t\t\t\t\tts_bgw_message_send_and_wait(RESTART, db_oid);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase T_DropdbStmt:\n\t\t\t{\n\t\t\t\tDropdbStmt *stmt = (DropdbStmt *) query->utilityStmt;\n\n\t\t\t\t/*\n\t\t\t\t * If we drop a database, we need to intercept and stop any of our\n\t\t\t\t * schedulers that might be connected to said db.\n\t\t\t\t */\n\t\t\t\tstop_workers_on_db_drop(stmt);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase T_DropStmt:\n\t\t\t\tfor (size_t i = 0; i < sizeof(extensions) / sizeof(TsExtension); ++i)\n\t\t\t\t{\n\t\t\t\t\tif (drop_statement_drops_extension((DropStmt *) query->utilityStmt,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   &extensions[i]))\n\t\t\t\t\t{\n\t\t\t\t\t\t/*\n\t\t\t\t\t\t * if we drop the extension we should restart (in case of\n\t\t\t\t\t\t * a rollback) the scheduler\n\t\t\t\t\t\t */\n\t\t\t\t\t\tts_bgw_message_send_and_wait(RESTART, MyDatabaseId);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase T_DropOwnedStmt:\n\t\t\t\tfor (size_t i = 0; i < sizeof(extensions) / sizeof(TsExtension); ++i)\n\t\t\t\t{\n\t\t\t\t\tif (drop_owned_statement_drops_extension((DropOwnedStmt *) query->utilityStmt,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t &extensions[i]))\n\t\t\t\t\t{\n\t\t\t\t\t\tts_bgw_message_send_and_wait(RESTART, MyDatabaseId);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase T_RenameStmt:\n\t\t\t\tif (((RenameStmt *) query->utilityStmt)->renameType == OBJECT_DATABASE)\n\t\t\t\t{\n\t\t\t\t\tRenameStmt *stmt = (RenameStmt *) query->utilityStmt;\n\t\t\t\t\tOid db_oid = get_database_oid(stmt->subname, stmt->missing_ok);\n\n\t\t\t\t\tif (OidIsValid(db_oid))\n\t\t\t\t\t{\n\t\t\t\t\t\tts_bgw_message_send_and_wait(STOP, db_oid);\n\t\t\t\t\t\tereport(WARNING,\n\t\t\t\t\t\t\t\t(errmsg(\"you need to manually restart any running \"\n\t\t\t\t\t\t\t\t\t\t\"background workers after this command\")));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tdefault:\n\n\t\t\t\tbreak;\n\t\t}\n\t}\n\tfor (size_t i = 0; i < sizeof(extensions) / sizeof(TsExtension); ++i)\n\t{\n\t\tTsExtension *const ext = &extensions[i];\n\n\t\t/* timescaledb.disable_load prevents loading of all extensions.\n\t\t * timescaledb_osm.disable_load prevents loading of timescaledb_osm.\n\t\t * If we ever had a third extension to load, we might need to make\n\t\t * this smarter, but not today. */\n\t\tbool const disable_load = extensions[0].guc_disable_load || ext->guc_disable_load;\n\n\t\tif (!disable_load &&\n\t\t\t(query->commandType != CMD_UTILITY || load_utility_cmd(query->utilityStmt, ext)))\n\t\t{\n\t\t\textension_check(ext);\n\t\t}\n\n\t\t/*\n\t\t * Call the extension's hook. This is necessary since the extension is\n\t\t * installed during the hook. If we did not do this the extension's hook\n\t\t * would not be called during the first command because the extension\n\t\t * would not have yet been installed. Thus the loader captures the\n\t\t * extension hook and calls it explicitly after the check for installing\n\t\t * the extension.\n\t\t */\n\t\tcall_extension_post_parse_analyze_hook(pstate, query, ext, jstate);\n\t}\n\n\tif (prev_post_parse_analyze_hook != NULL)\n\t{\n\t\tprev_post_parse_analyze_hook(pstate, query, jstate);\n\t}\n}\n\nstatic void\ntimescaledb_shmem_startup_hook(void)\n{\n\tif (prev_shmem_startup_hook)\n\t\tprev_shmem_startup_hook();\n\tts_bgw_counter_shmem_startup();\n\tts_bgw_message_queue_shmem_startup();\n\tts_lwlocks_shmem_startup();\n\tts_function_telemetry_shmem_startup();\n}\n\n/*\n * PG15 requires all shared memory requests to be requested in a dedicated\n * hook. We group all our shared memory requests in this function and use\n * it as a normal function for PG < 14 and as a hook for PG 15+.\n */\nstatic void\ntimescaledb_shmem_request_hook(void)\n{\n\tif (prev_shmem_request_hook)\n\t\tprev_shmem_request_hook();\n\n\tts_bgw_counter_shmem_alloc();\n\tts_bgw_message_queue_alloc();\n\tts_lwlocks_shmem_alloc();\n\tts_function_telemetry_shmem_alloc();\n}\n\nstatic void\nextension_mark_loader_present()\n{\n\tvoid **presentptr = find_rendezvous_variable(RENDEZVOUS_LOADER_PRESENT_NAME);\n\n\t*presentptr = &loader_present;\n}\n\nvoid\n_PG_init(void)\n{\n\tif (!process_shared_preload_libraries_in_progress)\n\t{\n\t\textension_load_without_preload();\n\t}\n\textension_mark_loader_present();\n\n\telog(INFO, \"timescaledb loaded\");\n\n\tts_bgw_cluster_launcher_init();\n\tts_bgw_counter_setup_gucs();\n\tts_bgw_interface_register_api_version();\n\n\t/* This is a safety-valve variable to prevent loading the full extension */\n\tfor (size_t i = 0; i < sizeof(extensions) / sizeof(TsExtension); ++i)\n\t{\n\t\tTsExtension *const ext = &extensions[i];\n\t\tDefineCustomBoolVariable(ext->guc_disable_load_name,\n\t\t\t\t\t\t\t\t \"Disable the loading of the actual extension\",\n\t\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t\t &ext->guc_disable_load,\n\t\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t\t PGC_USERSET,\n\t\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t\t NULL);\n\t}\n\n\tDefineCustomIntVariable(GUC_LAUNCHER_POLL_TIME_MS,\n\t\t\t\t\t\t\t\"Launcher timeout value in milliseconds\",\n\t\t\t\t\t\t\t\"Configure the time the launcher waits \"\n\t\t\t\t\t\t\t\"to look for new TimescaleDB instances\",\n\t\t\t\t\t\t\t&ts_guc_bgw_launcher_poll_time,\n\t\t\t\t\t\t\tBGW_LAUNCHER_POLL_TIME_MS, /* 10 ms or 60 seconds */\n\t\t\t\t\t\t\t10,\t\t\t\t\t\t   /* min: 10ms */\n\t\t\t\t\t\t\tPG_INT32_MAX,\t\t\t   /* PG_INT16_MAX would be too small  */\n\t\t\t\t\t\t\tPGC_POSTMASTER,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\tNULL);\n\n\t/*\n\t * Cannot check for extension here since not inside a transaction yet. Nor\n\t * do we even have an assigned database yet.\n\t * Using the post_parse_analyze_hook since it's the earliest available\n\t * hook.\n\t */\n\tprev_post_parse_analyze_hook = post_parse_analyze_hook;\n\t/* register shmem startup hook for the background worker stuff */\n\tprev_shmem_startup_hook = shmem_startup_hook;\n\n\tpost_parse_analyze_hook = post_analyze_hook;\n\tshmem_startup_hook = timescaledb_shmem_startup_hook;\n\n\tprev_shmem_request_hook = shmem_request_hook;\n\tshmem_request_hook = timescaledb_shmem_request_hook;\n}\n\ninline static void\ndo_load(TsExtension *const ext)\n{\n\tchar *version = extension_version(ext->name);\n\tchar soname[MAX_SO_NAME_LEN];\n\tpost_parse_analyze_hook_type old_hook;\n\n\t/* If the right version of the library is already loaded, we will just\n\t * skip the actual loading. If the wrong version of the library is loaded,\n\t * we need to kill the session since it will not be able to continue\n\t * operate. */\n\tif (extension_is_loaded(ext))\n\t{\n\t\tif (strcmp(ext->soversion, version) == 0)\n\t\t\treturn;\n\t\tereport(FATAL,\n\t\t\t\t(errcode(ERRCODE_DUPLICATE_OBJECT),\n\t\t\t\t errmsg(\"\\\"%s\\\" already loaded with a different version\", ext->name),\n\t\t\t\t errdetail(\"The new version is \\\"%s\\\", this session is using version \\\"%s\\\". The \"\n\t\t\t\t\t\t   \"session will be restarted.\",\n\t\t\t\t\t\t   version,\n\t\t\t\t\t\t   ext->soversion)));\n\t}\n\n\tstrlcpy(ext->soversion, version, MAX_VERSION_LEN);\n\tsnprintf(soname, MAX_SO_NAME_LEN, \"%s%s-%s\", TS_LIBDIR, ext->name, version);\n\n\t/*\n\t * In a parallel worker, we're not responsible for loading libraries, it's\n\t * handled by the parallel worker infrastructure which restores the\n\t * library state.\n\t */\n\tif (CalledInParallelWorker())\n\t{\n\t\treturn;\n\t}\n\n\t/*\n\t * Set the config option to let versions 0.9.0 and 0.9.1 know that the\n\t * loader was preloaded, newer versions use rendezvous variables instead.\n\t */\n\tif ((strcmp(version, \"0.9.0\") == 0 || strcmp(version, \"0.9.1\") == 0) &&\n\t\tstrcmp(ext->name, EXTENSION_NAME) == 0)\n\t{\n\t\tSetConfigOption(MAKE_EXTOPTION(\"loader_present\"), \"on\", PGC_USERSET, PGC_S_SESSION);\n\t}\n\n\t/*\n\t * we need to capture the loaded extension's post analyze hook, giving it\n\t * a NULL as previous\n\t */\n\told_hook = post_parse_analyze_hook;\n\tpost_parse_analyze_hook = NULL;\n\n\t/*\n\t * We want to call the post_parse_analyze_hook from the versioned\n\t * extension after we've loaded the versioned so. When the file is loaded\n\t * it sets post_parse_analyze_hook, which we capture and store in\n\t * extension_post_parse_analyze_hook to call at the end _PG_init\n\t */\n\tPG_TRY();\n\t{\n\t\tPGFunction ts_post_load_init =\n\t\t\tload_external_function(soname, POST_LOAD_INIT_FN, false, NULL);\n\t\tif (ts_post_load_init != NULL)\n\t\t{\n\t\t\tDirectFunctionCall1(ts_post_load_init, CharGetDatum(0));\n\t\t}\n\t}\n\tPG_CATCH();\n\t{\n\t\text->post_parse_analyze_hook = post_parse_analyze_hook;\n\t\tpost_parse_analyze_hook = old_hook;\n\t\tPG_RE_THROW();\n\t}\n\tPG_END_TRY();\n\n\text->post_parse_analyze_hook = post_parse_analyze_hook;\n\tpost_parse_analyze_hook = old_hook;\n}\n\ninline static void\nextension_check(TsExtension *const ext)\n{\n\tswitch (extension_current_state(ext->name, ext->schema_name, ext->table_name))\n\t{\n\t\tcase EXTENSION_STATE_TRANSITIONING:\n\t\t\t/*\n\t\t\t * Always load as soon as the extension is transitioning. This is\n\t\t\t * necessary so that the extension load before any CREATE FUNCTION\n\t\t\t * calls. Otherwise, the CREATE FUNCTION calls will load the .so\n\t\t\t * without capturing the post_parse_analyze_hook.\n\t\t\t */\n\t\tcase EXTENSION_STATE_CREATED:\n\t\t\tdo_load(ext);\n\t\t\treturn;\n\t\tcase EXTENSION_STATE_UNKNOWN:\n\t\tcase EXTENSION_STATE_NOT_INSTALLED:\n\t\t\treturn;\n\t}\n}\n\nextern void\nts_loader_extension_check(void)\n{\n\tfor (size_t i = 0; i < sizeof(extensions) / sizeof(TsExtension); ++i)\n\t{\n\t\textension_check(&extensions[i]);\n\t}\n}\n\nstatic void\ncall_extension_post_parse_analyze_hook(ParseState *pstate, Query *query,\n\t\t\t\t\t\t\t\t\t   TsExtension const *const ext, JumbleState *jstate)\n{\n\tif (extension_is_loaded(ext) && ext->post_parse_analyze_hook != NULL)\n\t{\n\t\text->post_parse_analyze_hook(pstate, query, jstate);\n\t}\n}\n"
  },
  {
    "path": "src/loader/loader.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\nextern char *ts_loader_extension_version(void);\n\nextern bool ts_loader_extension_exists(void);\n\nextern void ts_loader_extension_check(void);\n\n/* WaitLatch expects a long, so make sure to cast the value */\n/* Default value for timescaledb.launcher_poll_time */\n#ifdef TS_DEBUG\n#define BGW_LAUNCHER_POLL_TIME_MS 10\n#else\n#define BGW_LAUNCHER_POLL_TIME_MS 60000\n#endif\n\n/* GUC to control launcher timeout */\nextern int ts_guc_bgw_launcher_poll_time;\n"
  },
  {
    "path": "src/loader/lwlocks.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <fmgr.h>\n#include <miscadmin.h>\n#include <storage/lwlock.h>\n#include <storage/shmem.h>\n\n#include \"loader/lwlocks.h\"\n\n#define TS_LWLOCKS_SHMEM_NAME \"ts_lwlocks_shmem\"\n#define CHUNK_APPEND_LWLOCK_TRANCHE_NAME \"ts_chunk_append_lwlock_tranche\"\n#define OSM_PARALLEL_LWLOCK_TRANCHE_NAME \"ts_osm_parallel_lwlock_tranche\"\n\n/*\n * since shared memory can only be setup in a library loaded as\n * shared_preload_libraries we have to setup this struct here\n */\ntypedef struct TSLWLocks\n{\n\tLWLock *chunk_append;\n\tLWLock *osm_parallel_lwlock;\n} TSLWLocks;\n\nstatic TSLWLocks *ts_lwlocks = NULL;\n\nvoid\nts_lwlocks_shmem_startup()\n{\n\tbool found;\n\tLWLock **lock_pointer, **osm_lock_pointer;\n\n\tLWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);\n\tts_lwlocks = ShmemInitStruct(TS_LWLOCKS_SHMEM_NAME, sizeof(TSLWLocks), &found);\n\tif (!found)\n\t{\n\t\tmemset(ts_lwlocks, 0, sizeof(TSLWLocks));\n\t\tts_lwlocks->chunk_append = &(GetNamedLWLockTranche(CHUNK_APPEND_LWLOCK_TRANCHE_NAME))->lock;\n\t\tts_lwlocks->osm_parallel_lwlock =\n\t\t\t&(GetNamedLWLockTranche(OSM_PARALLEL_LWLOCK_TRANCHE_NAME))->lock;\n\t}\n\tLWLockRelease(AddinShmemInitLock);\n\n\t/*\n\t * We use a lock specific rendezvous variable to decouple the struct\n\t * from the individual lock users to have no constraints on the struct\n\t * across timescaledb versions.\n\t */\n\tlock_pointer = (LWLock **) find_rendezvous_variable(RENDEZVOUS_CHUNK_APPEND_LWLOCK);\n\t*lock_pointer = ts_lwlocks->chunk_append;\n\tosm_lock_pointer = (LWLock **) find_rendezvous_variable(RENDEZVOUS_OSM_PARALLEL_LWLOCK);\n\t*osm_lock_pointer = ts_lwlocks->osm_parallel_lwlock;\n}\n\n/*\n * from postgres code comments:\n * Extensions (or core code) can obtain an LWLocks by calling\n * RequestNamedLWLockTranche() during postmaster startup.  Subsequently,\n * call GetNamedLWLockTranche() to obtain a pointer to an array containing\n * the number of LWLocks requested.\n */\nvoid\nts_lwlocks_shmem_alloc()\n{\n\tRequestNamedLWLockTranche(CHUNK_APPEND_LWLOCK_TRANCHE_NAME, 1);\n\tRequestNamedLWLockTranche(OSM_PARALLEL_LWLOCK_TRANCHE_NAME, 1);\n\tRequestAddinShmemSpace(sizeof(TSLWLocks));\n}\n"
  },
  {
    "path": "src/loader/lwlocks.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#define RENDEZVOUS_CHUNK_APPEND_LWLOCK \"ts_chunk_append_lwlock\"\n#define RENDEZVOUS_OSM_PARALLEL_LWLOCK \"ts_osm_parallel_lwlock\"\n\nvoid ts_lwlocks_shmem_startup(void);\nvoid ts_lwlocks_shmem_alloc(void);\n"
  },
  {
    "path": "src/net/CMakeLists.txt",
    "content": "set(SOURCES\n    ${CMAKE_CURRENT_SOURCE_DIR}/conn.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/conn_plain.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/http.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/http_response.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/http_request.c)\n\nif(USE_OPENSSL)\n  list(APPEND SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/conn_ssl.c)\nendif(USE_OPENSSL)\n\ntarget_sources(${PROJECT_NAME} PRIVATE ${SOURCES})\n"
  },
  {
    "path": "src/net/conn.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n\n#include \"conn_internal.h\"\n#include \"debug_assert.h\"\n\nstatic ConnOps *conn_ops[_CONNECTION_MAX] = { NULL };\n\nstatic const char *conn_names[] = {\n\t[CONNECTION_PLAIN] = \"PLAIN\",\n\t[CONNECTION_SSL] = \"SSL\",\n\t[CONNECTION_MOCK] = \"MOCK\",\n};\n\nstatic Connection *\nconnection_internal_create(ConnectionType type, ConnOps *ops)\n{\n\tConnection *conn = palloc(ops->size);\n\n\tif (NULL == conn)\n\t\treturn NULL;\n\n\tmemset(conn, 0, ops->size);\n\tconn->ops = ops;\n\tconn->type = type;\n\n\treturn conn;\n}\n\nConnection *\nts_connection_create(ConnectionType type)\n{\n\tConnection *conn;\n\n\tif (type == _CONNECTION_MAX)\n\t{\n\t\telog(NOTICE, \"invalid connection type\");\n\t\treturn NULL;\n\t}\n\n\tif (NULL == conn_ops[type])\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"%s connections are not supported\", conn_names[type]),\n\t\t\t\t errhint(\"Enable %s support when compiling the extension.\", conn_names[type])));\n\n\tconn = connection_internal_create(type, conn_ops[type]);\n\n\tEnsure(conn, \"unable to create connection\");\n\n\tif (NULL != conn->ops->init)\n\t\tif (conn->ops->init(conn) < 0)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t\t errmsg(\"%s connection could not be initialized\", conn_names[type])));\n\n\treturn conn;\n}\n\n/*\n * Connect to a remote endpoint (host, service/port).\n *\n * The connection will be made to the host's service endpoint given by\n * 'servname' (e.g., 'http'), unless a valid port number is given.\n */\nint\nts_connection_connect(Connection *conn, const char *host, const char *servname, int port)\n{\n/* Windows defines 'connect()' as a macro, so we need to undef it here to use it in ops->connect */\n#ifdef WIN32\n#undef connect\n#endif\n\treturn conn->ops->connect(conn, host, servname, port);\n}\n\nssize_t\nts_connection_write(Connection *conn, const char *buf, size_t writelen)\n{\n\treturn conn->ops->write(conn, buf, writelen);\n}\n\nssize_t\nts_connection_read(Connection *conn, char *buf, size_t buflen)\n{\n\treturn conn->ops->read(conn, buf, buflen);\n}\n\nvoid\nts_connection_close(Connection *conn)\n{\n\tif (NULL != conn->ops)\n\t\tconn->ops->close(conn);\n}\n\nint\nts_connection_set_timeout_millis(Connection *conn, unsigned long millis)\n{\n\tif (NULL != conn->ops->set_timeout)\n\t\treturn conn->ops->set_timeout(conn, millis);\n\n\treturn -1;\n}\n\nvoid\nts_connection_destroy(Connection *conn)\n{\n\tif (conn == NULL)\n\t\treturn;\n\n\tts_connection_close(conn);\n\tconn->ops = NULL;\n\tpfree(conn);\n}\n\nint\nts_connection_register(ConnectionType type, ConnOps *ops)\n{\n\tif (type == _CONNECTION_MAX)\n\t\treturn -1;\n\n\tconn_ops[type] = ops;\n\n\treturn 0;\n}\n\nconst char *\nts_connection_get_and_clear_error(Connection *conn)\n{\n\tif (NULL != conn->ops->errmsg)\n\t\treturn conn->ops->errmsg(conn);\n\n\treturn \"unknown connection error\";\n}\n"
  },
  {
    "path": "src/net/conn.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\ntypedef struct ConnOps ConnOps;\n\ntypedef enum ConnectionType\n{\n\tCONNECTION_PLAIN,\n\tCONNECTION_SSL,\n\tCONNECTION_MOCK,\n\t_CONNECTION_MAX,\n} ConnectionType;\n\ntypedef struct Connection\n{\n\tConnectionType type;\n#ifdef WIN32\n\tSOCKET sock;\n#else\n\tint sock;\n#endif\n\tConnOps *ops;\n\tint err;\n} Connection;\n\nextern Connection *ts_connection_create(ConnectionType type);\nextern int ts_connection_connect(Connection *conn, const char *host, const char *servname,\n\t\t\t\t\t\t\t\t int port);\nextern ssize_t ts_connection_read(Connection *conn, char *buf, size_t buflen);\nextern ssize_t ts_connection_write(Connection *conn, const char *buf, size_t writelen);\nextern void ts_connection_close(Connection *conn);\nextern void ts_connection_destroy(Connection *conn);\nextern int ts_connection_set_timeout_millis(Connection *conn, unsigned long millis);\nextern const char *ts_connection_get_and_clear_error(Connection *conn);\n"
  },
  {
    "path": "src/net/conn_internal.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include \"conn.h\"\n\ntypedef struct ConnOps\n{\n\tsize_t size; /* Size of the connection object */\n\tint (*init)(Connection *conn);\n\tint (*connect)(Connection *conn, const char *host, const char *servname, int port);\n\tvoid (*close)(Connection *conn);\n\tssize_t (*write)(Connection *conn, const char *buf, size_t writelen);\n\tssize_t (*read)(Connection *conn, char *buf, size_t readlen);\n\tint (*set_timeout)(Connection *conn, unsigned long millis);\n\tconst char *(*errmsg)(Connection *conn);\n} ConnOps;\n\nextern int ts_connection_register(ConnectionType type, ConnOps *ops);\n"
  },
  {
    "path": "src/net/conn_plain.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <unistd.h>\n#include <postgres.h>\n\n#include <sys/socket.h>\n#include <sys/time.h>\n\n#include \"compat/compat.h\"\n#include \"conn_internal.h\"\n#include \"conn_plain.h\"\n#include \"port.h\"\n\n#define DEFAULT_TIMEOUT_MSEC 3000\n#define MAX_PORT 65535\n\nstatic void\nset_error(int err)\n{\n#ifdef WIN32\n\tWSASetLastError(err);\n#else\n\terrno = err;\n#endif\n}\n\nstatic int\nget_error(void)\n{\n#ifdef WIN32\n\treturn WSAGetLastError();\n#else\n\treturn errno;\n#endif\n}\n\n/*  Create socket and connect */\nint\nts_plain_connect(Connection *conn, const char *host, const char *servname, int port)\n{\n\tchar strport[6];\n\tstruct addrinfo *ainfo, hints = {\n\t\t.ai_family = AF_UNSPEC,\n\t\t.ai_socktype = SOCK_STREAM,\n\t};\n\tint ret;\n\n\tif (NULL == servname && (port <= 0 || port > MAX_PORT))\n\t{\n\t\tset_error(EINVAL);\n\t\treturn -1;\n\t}\n\n\t/* Explicit port given. Use it instead of servname */\n\tif (port > 0 && port <= MAX_PORT)\n\t{\n\t\tsnprintf(strport, sizeof(strport), \"%d\", port);\n\t\tservname = strport;\n\t\thints.ai_flags = AI_NUMERICSERV;\n\t}\n\n\t/* Lookup the endpoint ip address */\n\tret = getaddrinfo(host, servname, &hints, &ainfo);\n\n\tif (ret != 0)\n\t{\n\t\tret = SOCKET_ERROR;\n\n#ifdef WIN32\n\t\tWSASetLastError(WSAHOST_NOT_FOUND);\n#else\n\n\t\t/*\n\t\t * The closest match for a name resolution error. Strictly, this error\n\t\t * should not be used here, but to fix we need to support using\n\t\t * gai_strerror()\n\t\t */\n\t\terrno = EADDRNOTAVAIL;\n#endif\n\t\tgoto out;\n\t}\n\n#ifdef WIN32\n\n\t/*\n\t * PostgreSQL redefines the socket() call on Windows and creates a\n\t * non-blocking socket by default. We avoid this by calling WSASocket\n\t * directly.\n\t */\n\tconn->sock = WSASocket(ainfo->ai_family,\n\t\t\t\t\t\t   ainfo->ai_socktype,\n\t\t\t\t\t\t   ainfo->ai_protocol,\n\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t   0,\n\t\t\t\t\t\t   WSA_FLAG_OVERLAPPED);\n\n\tif (conn->sock == INVALID_SOCKET)\n\t\tret = SOCKET_ERROR;\n#else\n\tret = conn->sock = socket(ainfo->ai_family, ainfo->ai_socktype, ainfo->ai_protocol);\n#endif\n\n\tif (IS_SOCKET_ERROR(ret))\n\t\tgoto out_addrinfo;\n\n\t/*\n\t * Set send / recv timeout so that write and read don't block forever. Set\n\t * separately so that one of the actions failing doesn't block the other.\n\t */\n\tif (ts_plain_set_timeout(conn, DEFAULT_TIMEOUT_MSEC) < 0)\n\t{\n\t\tret = SOCKET_ERROR;\n\t\tgoto out_addrinfo;\n\t}\n\n#ifdef WIN32\n\tret = WSAConnect(conn->sock, ainfo->ai_addr, ainfo->ai_addrlen, NULL, NULL, NULL, NULL);\n#else\n\t/* connect the socket */\n\tret = connect(conn->sock, ainfo->ai_addr, ainfo->ai_addrlen);\n#endif\n\nout_addrinfo:\n\tfreeaddrinfo(ainfo);\n\nout:\n\tif (IS_SOCKET_ERROR(ret))\n\t{\n\t\tconn->err = ret;\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic ssize_t\nplain_write(Connection *conn, const char *buf, size_t writelen)\n{\n\tssize_t ret;\n#ifdef WIN32\n\tDWORD b;\n\tWSABUF wbuf = {\n\t\t.len = writelen,\n\t\t.buf = (char *) buf,\n\t};\n\n\tconn->err = WSASend(conn->sock, &wbuf, 1, &b, 0, NULL, NULL);\n\n\tif (IS_SOCKET_ERROR(conn->err))\n\t\tret = -1;\n\telse\n\t\tret = b;\n#else\n\tret = send(conn->sock, buf, writelen, 0);\n\n\tif (ret < 0)\n\t\tconn->err = ret;\n#endif\n\n\treturn ret;\n}\n\nstatic ssize_t\nplain_read(Connection *conn, char *buf, size_t buflen)\n{\n\tssize_t ret;\n#ifdef WIN32\n\tDWORD b, flags = 0;\n\tWSABUF wbuf = {\n\t\t.len = buflen,\n\t\t.buf = buf,\n\t};\n\n\tconn->err = WSARecv(conn->sock, &wbuf, 1, &b, &flags, NULL, NULL);\n\n\tif (IS_SOCKET_ERROR(conn->err))\n\t\tret = -1;\n\telse\n\t\tret = b;\n#else\n\tret = recv(conn->sock, buf, buflen, 0);\n\n\tif (ret < 0)\n\t\tconn->err = ret;\n#endif\n\n\treturn ret;\n}\n\nvoid\nts_plain_close(Connection *conn)\n{\n#ifdef WIN32\n\tclosesocket(conn->sock);\n#else\n\tclose(conn->sock);\n#endif\n}\n\nint\nts_plain_set_timeout(Connection *conn, unsigned long millis)\n{\n#ifdef WIN32\n\t/* Timeout is in milliseconds on Windows */\n\tDWORD timeout = millis;\n\tint optlen = sizeof(DWORD);\n#else\n\t/* we deliberately use a long constant here instead of a fixed width one because tv_sec is\n\t * declared as a long */\n\tstruct timeval timeout = {\n\t\t.tv_sec = millis / 1000L,\n\t\t.tv_usec = (millis % 1000L) * 1000L,\n\t};\n\tint optlen = sizeof(struct timeval);\n#endif\n\n\t/*\n\t * Set send / recv timeout so that write and read don't block forever. Set\n\t * separately so that one of the actions failing doesn't block the other.\n\t */\n\tconn->err = setsockopt(conn->sock, SOL_SOCKET, SO_RCVTIMEO, (const char *) &timeout, optlen);\n\n\tif (conn->err != 0)\n\t\treturn -1;\n\n\tconn->err = setsockopt(conn->sock, SOL_SOCKET, SO_SNDTIMEO, (const char *) &timeout, optlen);\n\n\tif (conn->err != 0)\n\t\treturn -1;\n\n\treturn 0;\n}\n\nconst char *\nts_plain_errmsg(Connection *conn)\n{\n\tconst char *errmsg = \"no connection error\";\n\n\tif (IS_SOCKET_ERROR(conn->err))\n\t\terrmsg = strerror(get_error());\n\tconn->err = 0;\n\n\treturn errmsg;\n}\n\nstatic ConnOps plain_ops = {\n\t.size = sizeof(Connection),\n\t.init = NULL,\n\t.connect = ts_plain_connect,\n\t.close = ts_plain_close,\n\t.write = plain_write,\n\t.read = plain_read,\n\t.errmsg = ts_plain_errmsg,\n};\n\nextern void _conn_plain_init(void);\nextern void _conn_plain_fini(void);\n\nvoid\n_conn_plain_init(void)\n{\n\t/*\n\t * WSAStartup is required on Windows before using the Winsock API.\n\t * However, PostgreSQL already handles this for us, so it is disabled here\n\t * by default. Set WSA_STARTUP_ENABLED to perform this initialization\n\t * anyway.\n\t */\n#if defined(WIN32) && defined(WSA_STARTUP_ENABLED)\n\tWSADATA wsadata;\n\tint res;\n\n\tres = WSAStartup(MAKEWORD(2, 2), &wsadata);\n\n\tif (res != 0)\n\t{\n\t\telog(ERROR, \"WSAStartup failed: %d\", res);\n\t\treturn;\n\t}\n#endif\n\tts_connection_register(CONNECTION_PLAIN, &plain_ops);\n}\n\nvoid\n_conn_plain_fini(void)\n{\n#if defined(WIN32) && defined(WSA_STARTUP_ENABLED)\n\tint ret = WSACleanup();\n\n\tif (ret != 0)\n\t\telog(WARNING, \"WSACleanup failed\");\n#endif\n}\n"
  },
  {
    "path": "src/net/conn_plain.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\ntypedef struct Connection Connection;\n\n#ifdef WIN32\n#define IS_SOCKET_ERROR(err) (err == SOCKET_ERROR)\n#else\n#define SOCKET_ERROR -1\n#define IS_SOCKET_ERROR(err) (err < 0)\n#endif\n\nextern int ts_plain_connect(Connection *conn, const char *host, const char *servname, int port);\nextern void ts_plain_close(Connection *conn);\nextern int ts_plain_set_timeout(Connection *conn, unsigned long millis);\nextern const char *ts_plain_errmsg(Connection *conn);\n"
  },
  {
    "path": "src/net/conn_ssl.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n\n#include <openssl/err.h>\n#include <openssl/ssl.h>\n\n#include \"conn_internal.h\"\n#include \"conn_plain.h\"\n\ntypedef struct SSLConnection\n{\n\tConnection conn;\n\tSSL_CTX *ssl_ctx;\n\tSSL *ssl;\n\tunsigned long errcode;\n} SSLConnection;\n\nstatic void\nssl_set_error(SSLConnection *conn, int err)\n{\n\tconn->errcode = ERR_get_error();\n\tconn->conn.err = err;\n}\n\nstatic SSL_CTX *\nssl_ctx_create(void)\n{\n\tSSL_CTX *ctx;\n\tint options;\n\n#if (OPENSSL_VERSION_NUMBER >= 0x1010000fL)\n\t/* OpenSSL >= v1.1 */\n\tctx = SSL_CTX_new(TLS_method());\n\n\toptions = SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1;\n#elif (OPENSSL_VERSION_NUMBER >= 0x1000000fL)\n\t/* OpenSSL >= v1.0 */\n\tctx = SSL_CTX_new(SSLv23_method());\n\n\toptions = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1;\n#else\n#error \"Unsupported OpenSSL version\"\n#endif\n\n\t/*\n\t * Because we have a blocking socket, we don't want to be bothered with\n\t * retries.\n\t */\n\tif (NULL != ctx)\n\t{\n\t\tSSL_CTX_set_options(ctx, options);\n\t\tSSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);\n\t}\n\n\treturn ctx;\n}\n\nstatic int\nssl_setup(SSLConnection *conn, const char *host)\n{\n\tint ret;\n\n\tconn->ssl_ctx = ssl_ctx_create();\n\n\tif (NULL == conn->ssl_ctx)\n\t{\n\t\tssl_set_error(conn, -1);\n\t\treturn -1;\n\t}\n\n\tERR_clear_error();\n\n\tconn->ssl = SSL_new(conn->ssl_ctx);\n\n\tif (conn->ssl == NULL)\n\t{\n\t\tssl_set_error(conn, -1);\n\t\treturn -1;\n\t}\n\n\tERR_clear_error();\n\n\tret = SSL_set_fd(conn->ssl, conn->conn.sock);\n\n\tif (ret == 0)\n\t{\n\t\tssl_set_error(conn, -1);\n\t\treturn -1;\n\t}\n\t/*\n\t * Tell the server during the handshake which hostname we are attempting\n\t * to connect to in case the server supports multiple hosts.\n\t */\n\tif (!SSL_set_tlsext_host_name(conn->ssl, host))\n\t{\n\t\tssl_set_error(conn, -1);\n\t\treturn -1;\n\t}\n\n\tret = SSL_connect(conn->ssl);\n\n\tif (ret <= 0)\n\t{\n\t\tssl_set_error(conn, ret);\n\t\tret = -1;\n\t}\n\n\treturn ret;\n}\n\nstatic int\nssl_connect(Connection *conn, const char *host, const char *servname, int port)\n{\n\tint ret;\n\n\t/* First do the base connection setup */\n\tret = ts_plain_connect(conn, host, servname, port);\n\n\tif (ret < 0)\n\t\treturn -1;\n\n\treturn ssl_setup((SSLConnection *) conn, host);\n}\n\nstatic ssize_t\nssl_write(Connection *conn, const char *buf, size_t writelen)\n{\n\tSSLConnection *sslconn = (SSLConnection *) conn;\n\n\tint ret = SSL_write(sslconn->ssl, buf, writelen);\n\n\tif (ret < 0)\n\t\tssl_set_error(sslconn, ret);\n\n\treturn ret;\n}\n\nstatic ssize_t\nssl_read(Connection *conn, char *buf, size_t buflen)\n{\n\tSSLConnection *sslconn = (SSLConnection *) conn;\n\n\tint ret = SSL_read(sslconn->ssl, buf, buflen);\n\n\tif (ret < 0)\n\t\tssl_set_error(sslconn, ret);\n\n\treturn ret;\n}\n\nstatic void\nssl_close(Connection *conn)\n{\n\tSSLConnection *sslconn = (SSLConnection *) conn;\n\n\tif (sslconn->ssl != NULL)\n\t{\n\t\tSSL_free(sslconn->ssl);\n\t\tsslconn->ssl = NULL;\n\t}\n\n\tif (sslconn->ssl_ctx != NULL)\n\t{\n\t\tSSL_CTX_free(sslconn->ssl_ctx);\n\t\tsslconn->ssl_ctx = NULL;\n\t}\n\n\tts_plain_close(conn);\n}\n\nstatic const char *\nssl_errmsg(Connection *conn)\n{\n\tSSLConnection *sslconn = (SSLConnection *) conn;\n\tconst char *reason;\n\tstatic char errbuf[32];\n\tint err = conn->err;\n\tunsigned long ecode = sslconn->errcode;\n\n\t/* Clear errors */\n\tconn->err = 0;\n\tsslconn->errcode = 0;\n\n\tif (NULL != sslconn->ssl)\n\t{\n\t\tint sslerr = SSL_get_error(sslconn->ssl, err);\n\n\t\tswitch (sslerr)\n\t\t{\n\t\t\tcase SSL_ERROR_NONE:\n\t\t\tcase SSL_ERROR_SSL:\n\t\t\t\t/* ecode should be set and handled below */\n\t\t\t\tbreak;\n\t\t\tcase SSL_ERROR_ZERO_RETURN:\n\t\t\t\treturn \"SSL error zero return\";\n\t\t\tcase SSL_ERROR_WANT_READ:\n\t\t\t\treturn \"SSL error want read\";\n\t\t\tcase SSL_ERROR_WANT_WRITE:\n\t\t\t\treturn \"SSL error want write\";\n\t\t\tcase SSL_ERROR_WANT_CONNECT:\n\t\t\t\treturn \"SSL error want connect\";\n\t\t\tcase SSL_ERROR_WANT_ACCEPT:\n\t\t\t\treturn \"SSL error want accept\";\n\t\t\tcase SSL_ERROR_WANT_X509_LOOKUP:\n\t\t\t\treturn \"SSL error want X509 lookup\";\n\t\t\tcase SSL_ERROR_SYSCALL:\n\t\t\t\tif (ecode == 0)\n\t\t\t\t{\n\t\t\t\t\tif (err == 0)\n\t\t\t\t\t\treturn \"EOF in SSL operation\";\n\t\t\t\t\telse if (IS_SOCKET_ERROR(err))\n\t\t\t\t\t{\n\t\t\t\t\t\t/* reset error for plan_errmsg() */\n\t\t\t\t\t\tconn->err = err;\n\t\t\t\t\t\treturn ts_plain_errmsg(conn);\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\treturn \"unknown SSL syscall error\";\n\t\t\t\t}\n\t\t\t\treturn \"SSL error syscall\";\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (ecode == 0)\n\t{\n\t\t/* Assume this was an error of the underlying socket */\n\t\tif (IS_SOCKET_ERROR(err))\n\t\t{\n\t\t\t/* reset error for plan_errmsg() */\n\t\t\tconn->err = err;\n\t\t\treturn ts_plain_errmsg(conn);\n\t\t}\n\n\t\treturn \"no SSL error\";\n\t}\n\n\treason = ERR_reason_error_string(ecode);\n\n\tif (NULL != reason)\n\t\treturn reason;\n\n\tsnprintf(errbuf, sizeof(errbuf), \"SSL error code %lu\", ecode);\n\n\treturn errbuf;\n}\n\nstatic ConnOps ssl_ops = {\n\t.size = sizeof(SSLConnection),\n\t.init = NULL,\n\t.connect = ssl_connect,\n\t.close = ssl_close,\n\t.write = ssl_write,\n\t.read = ssl_read,\n\t.set_timeout = ts_plain_set_timeout,\n\t.errmsg = ssl_errmsg,\n};\n\nextern void _conn_ssl_init(void);\nextern void _conn_ssl_fini(void);\n\nvoid\n_conn_ssl_init(void)\n{\n#if (OPENSSL_VERSION_NUMBER < 0x1010000fL)\n\t/* OpenSSL < 1.1.0 requires explicit initialization */\n\tSSL_library_init();\n\t/* Always returns 1 */\n\tSSL_load_error_strings();\n#endif\n\tts_connection_register(CONNECTION_SSL, &ssl_ops);\n}\n\nvoid\n_conn_ssl_fini(void)\n{\n#if (OPENSSL_VERSION_NUMBER < 0x1010000fL)\n\t/* OpenSSL < 1.1.0 requires explicit cleanup */\n\tERR_free_strings();\n#endif\n}\n"
  },
  {
    "path": "src/net/http.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n\n#include \"conn.h\"\n#include \"http.h\"\n\nstatic const char *http_error_strings[] = {\n\t[HTTP_ERROR_NONE] = \"no HTTP error\",\n\t[HTTP_ERROR_WRITE] = \"HTTP connection write error\",\n\t[HTTP_ERROR_READ] = \"HTTP connection read error\",\n\t[HTTP_ERROR_CONN_CLOSED] = \"HTTP connection closed\",\n\t[HTTP_ERROR_REQUEST_BUILD] = \"could not build HTTP request\",\n\t[HTTP_ERROR_RESPONSE_PARSE] = \"could not parse HTTP response\",\n\t[HTTP_ERROR_RESPONSE_INCOMPLETE] = \"incomplete HTTP response\",\n\t[HTTP_ERROR_INVALID_BUFFER_STATE] = \"invalid HTTP buffer state\",\n\t[HTTP_ERROR_UNKNOWN] = \"unknown HTTP error\",\n};\n\nstatic const char *http_version_strings[] = {\n\t[HTTP_VERSION_10] = \"HTTP/1.0\",\n\t[HTTP_VERSION_11] = \"HTTP/1.1\",\n\t[HTTP_VERSION_INVALID] = \"invalid HTTP version\",\n};\n\nconst char *\nts_http_strerror(HttpError http_errno)\n{\n\treturn http_error_strings[http_errno];\n}\n\nHttpVersion\nts_http_version_from_string(const char *version)\n{\n\tint i;\n\n\tfor (i = 0; i < HTTP_VERSION_INVALID; i++)\n\t\tif (pg_strcasecmp(http_version_strings[i], version) == 0)\n\t\t\treturn i;\n\n\treturn HTTP_VERSION_INVALID;\n}\n\nconst char *\nts_http_version_string(HttpVersion version)\n{\n\treturn http_version_strings[version];\n}\n\n/*\n * Send an HTTP request and receive the HTTP response on the given connection.\n *\n * Returns HTTP_ERROR_NONE (0) on success or a HTTP-specific error on failure.\n */\nHttpError\nts_http_send_and_recv(Connection *conn, HttpRequest *req, HttpResponseState *state)\n{\n\tconst char *built_request;\n\tsize_t request_len;\n\toff_t write_off = 0;\n\tHttpError err = HTTP_ERROR_NONE;\n\tint ret;\n\n\tbuilt_request = ts_http_request_build(req, &request_len);\n\n\tif (NULL == built_request)\n\t\treturn HTTP_ERROR_REQUEST_BUILD;\n\n\twhile (request_len > 0)\n\t{\n\t\tret = ts_connection_write(conn, built_request + write_off, request_len);\n\n\t\tif (ret < 0 || (size_t) ret > request_len)\n\t\t\treturn HTTP_ERROR_WRITE;\n\n\t\tif (ret == 0)\n\t\t\treturn HTTP_ERROR_CONN_CLOSED;\n\n\t\twrite_off += ret;\n\t\trequest_len -= ret;\n\t}\n\n\twhile (err == HTTP_ERROR_NONE && !ts_http_response_state_is_done(state))\n\t{\n\t\tssize_t remaining = 0;\n\t\tchar *buf = ts_http_response_state_next_buffer(state, &remaining);\n\n\t\tif (remaining < 0)\n\t\t\terr = HTTP_ERROR_INVALID_BUFFER_STATE;\n\t\telse if (remaining == 0)\n\t\t\terr = HTTP_ERROR_RESPONSE_INCOMPLETE;\n\t\telse\n\t\t{\n\t\t\tssize_t bytes_read = ts_connection_read(conn, buf, remaining);\n\n\t\t\tif (bytes_read < 0)\n\t\t\t\terr = HTTP_ERROR_READ;\n\t\t\t/* Check for error or closed socket/EOF (ret == 0) */\n\t\t\telse if (bytes_read == 0)\n\t\t\t\terr = HTTP_ERROR_CONN_CLOSED;\n\t\t\telse if (!ts_http_response_state_parse(state, bytes_read))\n\t\t\t\terr = HTTP_ERROR_RESPONSE_PARSE;\n\t\t}\n\t}\n\n\treturn err;\n}\n"
  },
  {
    "path": "src/net/http.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include <utils/jsonb.h>\n\n#define HTTP_HOST \"Host\"\n#define HTTP_CONTENT_LENGTH \"Content-Length\"\n#define HTTP_CONTENT_TYPE \"Content-Type\"\n#define MAX_RAW_BUFFER_SIZE 4096\n#define MAX_REQUEST_DATA_SIZE 2048\n\ntypedef struct HttpHeader\n{\n\tchar *name;\n\tint name_len;\n\tchar *value;\n\tint value_len;\n\tstruct HttpHeader *next;\n} HttpHeader;\n\n/******* http_request.c *******/\n/*  We can add more methods later, but for now we do not need others */\ntypedef enum HttpRequestMethod\n{\n\tHTTP_GET,\n\tHTTP_POST,\n} HttpRequestMethod;\n\ntypedef enum HttpVersion\n{\n\tHTTP_VERSION_10,\n\tHTTP_VERSION_11,\n\tHTTP_VERSION_INVALID,\n} HttpVersion;\n\ntypedef enum HttpError\n{\n\tHTTP_ERROR_NONE = 0,\n\tHTTP_ERROR_WRITE, /* Connection write error, check errno */\n\tHTTP_ERROR_READ,  /* Connection read error, check errno */\n\tHTTP_ERROR_CONN_CLOSED,\n\tHTTP_ERROR_REQUEST_BUILD,\n\tHTTP_ERROR_RESPONSE_PARSE,\n\tHTTP_ERROR_RESPONSE_INCOMPLETE,\n\tHTTP_ERROR_INVALID_BUFFER_STATE,\n\tHTTP_ERROR_UNKNOWN, /* Should always be last */\n} HttpError;\n\n/*  NOTE: HttpRequest* structs are all responsible */\n/*  for allocating and deallocating the char* */\ntypedef struct HttpRequest HttpRequest;\ntypedef struct Connection Connection;\n\nextern HttpVersion ts_http_version_from_string(const char *version);\nextern const char *ts_http_version_string(HttpVersion version);\n\nextern void ts_http_request_init(HttpRequest *req, HttpRequestMethod method);\nextern HttpRequest *ts_http_request_create(HttpRequestMethod method);\nextern void ts_http_request_destroy(HttpRequest *req);\n\n/* Assume that uri is null-terminated */\nextern void ts_http_request_set_uri(HttpRequest *req, const char *uri);\nextern void ts_http_request_set_version(HttpRequest *req, HttpVersion version);\n\n/* Assume that name and value are null-terminated */\nextern void ts_http_request_set_header(HttpRequest *req, const char *name, const char *value);\nextern void ts_http_request_set_body_jsonb(HttpRequest *req, const Jsonb *json);\n\n/*  Serialize the request into char *dst. Return the length of request in optional size pointer*/\nextern const char *ts_http_request_build(HttpRequest *req, size_t *buf_size);\n\n/******* http_response.c *******/\n\ntypedef struct HttpResponseState HttpResponseState;\n\nextern void ts_http_response_state_init(HttpResponseState *state);\nextern HttpResponseState *ts_http_response_state_create(void);\nextern void ts_http_response_state_destroy(HttpResponseState *state);\n\n/*  Accessor Functions */\nextern bool ts_http_response_state_is_done(HttpResponseState *state);\nextern bool ts_http_response_state_valid_status(HttpResponseState *state);\nextern char *ts_http_response_state_next_buffer(HttpResponseState *state, ssize_t *bufsize);\nextern ssize_t ts_http_response_state_buffer_remaining(HttpResponseState *state);\nextern const char *ts_http_response_state_body_start(HttpResponseState *state);\nextern size_t ts_http_response_state_content_length(HttpResponseState *state);\nextern int ts_http_response_state_status_code(HttpResponseState *state);\n\n/*  Returns false if encountered an error during parsing */\nextern bool ts_http_response_state_parse(HttpResponseState *state, size_t bytes);\n\nextern const char *ts_http_strerror(HttpError http_errno);\nextern HttpError ts_http_send_and_recv(Connection *conn, HttpRequest *req,\n\t\t\t\t\t\t\t\t\t   HttpResponseState *state);\n"
  },
  {
    "path": "src/net/http_request.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <lib/stringinfo.h>\n#include <utils/memutils.h>\n\n#include \"http.h\"\n\n#define SPACE ' '\n#define COLON ':'\n#define CARRIAGE '\\r'\n#define NEW_LINE '\\n'\n\n/*  So that http_response.c can find this function */\nHttpHeader *ts_http_header_create(const char *name, size_t name_len, const char *value,\n\t\t\t\t\t\t\t\t  size_t value_len, HttpHeader *next);\n\nHttpHeader *\nts_http_header_create(const char *name, size_t name_len, const char *value, size_t value_len,\n\t\t\t\t\t  HttpHeader *next)\n{\n\tHttpHeader *new_header = palloc(sizeof(HttpHeader));\n\n\tmemset(new_header, 0, sizeof(*new_header));\n\tnew_header->name = palloc(name_len + 1);\n\tif (name_len > 0)\n\t\tmemcpy(new_header->name, name, name_len);\n\tnew_header->name[name_len] = '\\0';\n\tnew_header->name_len = name_len;\n\n\tnew_header->value = palloc(value_len + 1);\n\tif (value_len > 0)\n\t\tmemcpy(new_header->value, value, value_len);\n\tnew_header->value[value_len] = '\\0';\n\tnew_header->value_len = value_len;\n\n\tnew_header->next = next;\n\treturn new_header;\n}\n\n/*  NOTE: The setter functions for HttpRequest should all */\n/*  ensure that every char * in this struct is null-terminated */\ntypedef struct HttpRequest\n{\n\tHttpRequestMethod method;\n\tchar *uri;\n\tsize_t uri_len;\n\tHttpVersion version;\n\tHttpHeader *headers;\n\tchar *body;\n\tsize_t body_len;\n\tMemoryContext context;\n} HttpRequest;\n\nstatic const char *http_method_strings[] = { [HTTP_GET] = \"GET\", [HTTP_POST] = \"POST\" };\n\n#define METHOD_STRING(x) http_method_strings[x]\n#define VERSION_STRING(x) ts_http_version_string(x)\n\n/* appendBinaryStringInfo is UB if data is NULL. This function wraps it in a check that datalen > 0\n */\nstatic void\nappendOptionalBinaryStringInfo(StringInfo str, const char *data, int datalen)\n{\n\tif (datalen <= 0)\n\t\treturn;\n\n\tAssert(data != NULL);\n\tappendBinaryStringInfo(str, data, datalen);\n}\n\nvoid\nts_http_request_init(HttpRequest *req, HttpRequestMethod method)\n{\n\treq->method = method;\n}\n\nHttpRequest *\nts_http_request_create(HttpRequestMethod method)\n{\n\tMemoryContext request_context =\n\t\tAllocSetContextCreate(CurrentMemoryContext, \"Http Request\", ALLOCSET_DEFAULT_SIZES);\n\tMemoryContext old = MemoryContextSwitchTo(request_context);\n\tHttpRequest *req = palloc0(sizeof(HttpRequest));\n\n\treq->context = request_context;\n\tts_http_request_init(req, method);\n\n\tMemoryContextSwitchTo(old);\n\treturn req;\n}\n\nvoid\nts_http_request_destroy(HttpRequest *req)\n{\n\tMemoryContextDelete(req->context);\n}\n\nvoid\nts_http_request_set_uri(HttpRequest *req, const char *uri)\n{\n\tMemoryContext old = MemoryContextSwitchTo(req->context);\n\tint uri_len = strlen(uri);\n\n\treq->uri = palloc(uri_len + 1);\n\tmemcpy(req->uri, uri, uri_len);\n\treq->uri[uri_len] = '\\0';\n\treq->uri_len = uri_len;\n\tMemoryContextSwitchTo(old);\n}\n\nvoid\nts_http_request_set_version(HttpRequest *req, HttpVersion version)\n{\n\treq->version = version;\n}\n\nstatic void\nset_header(HttpRequest *req, const char *name, const char *value)\n{\n\tint name_len = strlen(name);\n\tint value_len = strlen(value);\n\n\treq->headers = ts_http_header_create(name, name_len, value, value_len, req->headers);\n}\n\nvoid\nts_http_request_set_header(HttpRequest *req, const char *name, const char *value)\n{\n\tMemoryContext old = MemoryContextSwitchTo(req->context);\n\tset_header(req, name, value);\n\tMemoryContextSwitchTo(old);\n}\n\nvoid\nts_http_request_set_body_jsonb(HttpRequest *req, const Jsonb *json)\n{\n\tMemoryContext old = MemoryContextSwitchTo(req->context);\n\tStringInfoData jtext;\n\tinitStringInfo(&jtext);\n\tchar content_length[10];\n\n\tJsonbToCString(&jtext, (JsonbContainer *) &json->root, VARSIZE(json));\n\treq->body = jtext.data;\n\treq->body_len = jtext.len;\n\tsnprintf(content_length, sizeof(content_length), \"%d\", jtext.len);\n\tset_header(req, HTTP_CONTENT_TYPE, \"application/json\");\n\tset_header(req, HTTP_CONTENT_LENGTH, content_length);\n\tMemoryContextSwitchTo(old);\n}\n\nstatic void\nhttp_request_serialize_method(HttpRequest *req, StringInfo buf)\n{\n\tconst char *method = METHOD_STRING(req->method);\n\n\tappendStringInfoString(buf, method);\n}\n\nstatic void\nhttp_request_serialize_version(HttpRequest *req, StringInfo buf)\n{\n\tconst char *version = VERSION_STRING(req->version);\n\n\tappendStringInfoString(buf, version);\n}\n\nstatic void\nhttp_request_serialize_uri(HttpRequest *req, StringInfo buf)\n{\n\tappendOptionalBinaryStringInfo(buf, req->uri, req->uri_len);\n}\n\nstatic void\nhttp_request_serialize_char(char to_serialize, StringInfo buf)\n{\n\tappendStringInfoChar(buf, to_serialize);\n}\n\nstatic void\nhttp_request_serialize_body(HttpRequest *req, StringInfo buf)\n{\n\tappendOptionalBinaryStringInfo(buf, req->body, req->body_len);\n}\n\nstatic void\nhttp_header_serialize(HttpHeader *header, StringInfo buf)\n{\n\tappendOptionalBinaryStringInfo(buf, header->name, header->name_len);\n\thttp_request_serialize_char(COLON, buf);\n\thttp_request_serialize_char(SPACE, buf);\n\tappendOptionalBinaryStringInfo(buf, header->value, header->value_len);\n}\n\nstatic int\nhttp_header_get_content_length(HttpHeader *header)\n{\n\tint content_length = -1;\n\n\tif (!strncmp(header->name, HTTP_CONTENT_LENGTH, header->name_len))\n\t\tsscanf(header->value, \"%d\", &content_length);\n\treturn content_length;\n}\n\nconst char *\nts_http_request_build(HttpRequest *req, size_t *buf_size)\n{\n\t/* serialize into this buf, which is allocated on caller's memory context */\n\tStringInfoData buf;\n\tHttpHeader *cur_header;\n\tint content_length = 0;\n\tbool verified_content_length = false;\n\n\tinitStringInfo(&buf);\n\n\thttp_request_serialize_method(req, &buf);\n\thttp_request_serialize_char(SPACE, &buf);\n\n\thttp_request_serialize_uri(req, &buf);\n\thttp_request_serialize_char(SPACE, &buf);\n\n\thttp_request_serialize_version(req, &buf);\n\thttp_request_serialize_char(CARRIAGE, &buf);\n\thttp_request_serialize_char(NEW_LINE, &buf);\n\n\tcur_header = req->headers;\n\n\twhile (cur_header != NULL)\n\t{\n\t\tcontent_length = http_header_get_content_length(cur_header);\n\t\tif (content_length != -1)\n\t\t{\n\t\t\t/* make sure it's equal to body_len */\n\t\t\tif ((size_t) content_length != req->body_len)\n\t\t\t{\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t\telse\n\t\t\t\tverified_content_length = true;\n\t\t}\n\t\thttp_header_serialize(cur_header, &buf);\n\t\thttp_request_serialize_char(CARRIAGE, &buf);\n\t\thttp_request_serialize_char(NEW_LINE, &buf);\n\n\t\tcur_header = cur_header->next;\n\t}\n\thttp_request_serialize_char(CARRIAGE, &buf);\n\thttp_request_serialize_char(NEW_LINE, &buf);\n\n\tif (!verified_content_length)\n\t{\n\t\t/* Then there was no header field for Content-Length */\n\t\tif (req->body_len != 0)\n\t\t{\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\thttp_request_serialize_body(req, &buf);\n\t/* Now everything lives in buf.data */\n\tif (buf_size != NULL)\n\t\t*buf_size = buf.len;\n\treturn buf.data;\n}\n"
  },
  {
    "path": "src/net/http_response.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <lib/stringinfo.h>\n#include <utils/memutils.h>\n\n#include \"http.h\"\n\n#define CARRIAGE_RETURN '\\r'\n#define NEW_LINE '\\n'\n#define SEP_CHAR ':'\n#define HTTP_VERSION_BUFFER_SIZE 128\n\nextern HttpHeader *ts_http_header_create(const char *name, size_t name_len, const char *value,\n\t\t\t\t\t\t\t\t\t\t size_t value_len, HttpHeader *next);\n\ntypedef enum HttpParseState\n{\n\tHTTP_STATE_STATUS,\n\tHTTP_STATE_INTERM,\t\t/* received a single \\r */\n\tHTTP_STATE_HEADER_NAME, /* received \\r\\n */\n\tHTTP_STATE_HEADER_VALUE,\n\tHTTP_STATE_ALMOST_DONE,\n\tHTTP_STATE_BODY,\n\tHTTP_STATE_ERROR,\n\tHTTP_STATE_DONE,\n} HttpParseState;\n\ntypedef struct HttpResponseState\n{\n\tMemoryContext context;\n\tchar version[HTTP_VERSION_BUFFER_SIZE];\n\tchar raw_buffer[MAX_RAW_BUFFER_SIZE];\n\t/* The next read should copy data into the buffer starting here */\n\toff_t offset;\n\toff_t parse_offset;\n\tsize_t cur_header_name_len;\n\tsize_t cur_header_value_len;\n\tchar *cur_header_name;\n\tchar *cur_header_value;\n\tHttpHeader *headers;\n\tint status_code;\n\tsize_t content_length;\n\tchar *body_start;\n\tHttpParseState state;\n} HttpResponseState;\n\nvoid\nts_http_response_state_init(HttpResponseState *state)\n{\n\tstate->status_code = -1;\n\tstate->state = HTTP_STATE_STATUS;\n}\n\nHttpResponseState *\nts_http_response_state_create()\n{\n\tMemoryContext context =\n\t\tAllocSetContextCreate(CurrentMemoryContext, \"Http Response\", ALLOCSET_DEFAULT_SIZES);\n\tMemoryContext old = MemoryContextSwitchTo(context);\n\tHttpResponseState *ret = palloc(sizeof(HttpResponseState));\n\n\tmemset(ret, 0, sizeof(*ret));\n\n\tret->context = context;\n\tts_http_response_state_init(ret);\n\tMemoryContextSwitchTo(old);\n\treturn ret;\n}\n\nvoid\nts_http_response_state_destroy(HttpResponseState *state)\n{\n\tMemoryContextDelete(state->context);\n}\n\nbool\nts_http_response_state_valid_status(HttpResponseState *state)\n{\n\t/* If the status code hasn't been parsed yet, return */\n\tif (state->status_code == -1)\n\t\treturn true;\n\t/* If it's a bad status code, then bad! */\n\tif (state->status_code / 100 == 2)\n\t\treturn true;\n\treturn false;\n}\n\nbool\nts_http_response_state_is_done(HttpResponseState *state)\n{\n\treturn (state->state == HTTP_STATE_DONE);\n}\n\n/*\n * Return the remaining buffer space.\n *\n * Returns 0 or a positive number, or -1 for invalid state.\n *\n */\nssize_t\nts_http_response_state_buffer_remaining(HttpResponseState *state)\n{\n\tAssert(state->offset <= MAX_RAW_BUFFER_SIZE);\n\n\treturn MAX_RAW_BUFFER_SIZE - state->offset;\n}\n\n/*\n * Return a pointer to the next buffer to write to.\n *\n * Optionally, return the buffer size via the bufsize parameter.\n */\nchar *\nts_http_response_state_next_buffer(HttpResponseState *state, ssize_t *bufsize)\n{\n\tAssert(state->offset <= MAX_RAW_BUFFER_SIZE);\n\n\tif (NULL != bufsize)\n\t\t*bufsize = ts_http_response_state_buffer_remaining(state);\n\n\t/*\n\t * This should not happen, be we return NULL in this case and let caller\n\t * deal with it\n\t */\n\tif (state->offset > MAX_RAW_BUFFER_SIZE)\n\t\treturn NULL;\n\n\treturn state->raw_buffer + state->offset;\n}\n\nconst char *\nts_http_response_state_body_start(HttpResponseState *state)\n{\n\treturn state->body_start;\n}\n\nint\nts_http_response_state_status_code(HttpResponseState *state)\n{\n\treturn state->status_code;\n}\n\nsize_t\nts_http_response_state_content_length(HttpResponseState *state)\n{\n\treturn state->content_length;\n}\n\nstatic bool\nhttp_parse_version(HttpResponseState *state)\n{\n\treturn ts_http_version_from_string(state->version) != HTTP_VERSION_INVALID;\n}\n\nstatic void\nhttp_parse_status(HttpResponseState *state, const char next)\n{\n\tchar *raw_buf = palloc(state->parse_offset + 1);\n\n\tswitch (next)\n\t{\n\t\tcase CARRIAGE_RETURN:\n\n\t\t\t/*\n\t\t\t * Then we are at the end of status and can use sscanf\n\t\t\t *\n\t\t\t * Need a second %s inside the sscanf so that we make sure to get\n\t\t\t * all of the digits of the status code\n\t\t\t */\n\t\t\tmemcpy(raw_buf, state->raw_buffer, state->parse_offset);\n\t\t\traw_buf[state->parse_offset] = '\\0';\n\t\t\tstate->state = HTTP_STATE_ERROR;\n\t\t\tmemset(state->version, '\\0', sizeof(state->version));\n\n\t\t\tif (sscanf(raw_buf, \"%127s%*[ ]%d%*[ ]%*s\", state->version, &state->status_code) == 2)\n\t\t\t{\n\t\t\t\tif (http_parse_version(state))\n\t\t\t\t\tstate->state = HTTP_STATE_INTERM;\n\t\t\t\telse\n\t\t\t\t\tstate->state = HTTP_STATE_ERROR;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase NEW_LINE:\n\t\t\tstate->state = HTTP_STATE_ERROR;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\t/* Don't try to parse Status line until we see '\\r' */\n\t\t\tbreak;\n\t}\n\n\tpfree(raw_buf);\n}\n\nstatic void\nhttp_response_state_add_header(HttpResponseState *state, const char *name, size_t name_len,\n\t\t\t\t\t\t\t   const char *value, size_t value_len)\n{\n\tMemoryContext old = MemoryContextSwitchTo(state->context);\n\tHttpHeader *new_header =\n\t\tts_http_header_create(name, name_len, value, value_len, state->headers);\n\n\tstate->headers = new_header;\n\tMemoryContextSwitchTo(old);\n}\n\nstatic void\nhttp_parse_interm(HttpResponseState *state, const char next)\n{\n\tint temp_length;\n\n\tswitch (next)\n\t{\n\t\tcase NEW_LINE:\n\t\t\tstate->state = HTTP_STATE_HEADER_NAME;\n\t\t\t/* Store another header */\n\t\t\thttp_response_state_add_header(state,\n\t\t\t\t\t\t\t\t\t\t   state->cur_header_name,\n\t\t\t\t\t\t\t\t\t\t   state->cur_header_name_len,\n\t\t\t\t\t\t\t\t\t\t   state->cur_header_value,\n\t\t\t\t\t\t\t\t\t\t   state->cur_header_value_len);\n\n\t\t\t/* Check if the line we just read is Content-Length */\n\t\t\tif (state->cur_header_name != NULL &&\n\t\t\t\tstrncmp(HTTP_CONTENT_LENGTH, state->cur_header_name, state->cur_header_name_len) ==\n\t\t\t\t\t0)\n\t\t\t{\n\t\t\t\tif (sscanf(state->cur_header_value, \"%d\", &temp_length) == 1)\n\t\t\t\t{\n\t\t\t\t\tstate->content_length = temp_length;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tstate->state = HTTP_STATE_ERROR;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tstate->cur_header_name_len = 0;\n\t\t\tstate->cur_header_value_len = 0;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tstate->state = HTTP_STATE_ERROR;\n\t\t\tbreak;\n\t}\n}\n\nstatic void\nhttp_parse_header_name(HttpResponseState *state, const char next)\n{\n\tswitch (next)\n\t{\n\t\tcase SEP_CHAR:\n\t\t\tstate->state = HTTP_STATE_HEADER_VALUE;\n\t\t\tstate->cur_header_value = state->raw_buffer + state->parse_offset + 1;\n\t\t\tbreak;\n\t\tcase CARRIAGE_RETURN:\n\t\t\tif (state->cur_header_name_len == 0)\n\t\t\t{\n\t\t\t\tstate->state = HTTP_STATE_ALMOST_DONE;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * I'm guessing getting a carriage return in the middle of\n\t\t\t\t * field\n\t\t\t\t */\n\t\t\t\t/* name is bad... */\n\t\t\t\tstate->state = HTTP_STATE_ERROR;\n\t\t\t\tbreak;\n\t\t\t}\n\t\tdefault:\n\t\t\t/* Header names are only alphabetic chars */\n\t\t\tif (('a' <= next && next <= 'z') || ('A' <= next && next <= 'Z') || next == '-')\n\t\t\t{\n\t\t\t\t/* Good, then the next call will save this char */\n\t\t\t\tstate->cur_header_name_len++;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tstate->state = HTTP_STATE_ERROR;\n\t\t\tbreak;\n\t}\n}\n\n/*  We do not customize to header_name. Assume all non \\r or \\n chars are allowed. */\nstatic void\nhttp_parse_header_value(HttpResponseState *state, const char next)\n{\n\t/* Allow everything except... \\r, \\n */\n\tswitch (next)\n\t{\n\t\tcase CARRIAGE_RETURN:\n\t\t\tstate->state = HTTP_STATE_INTERM;\n\t\t\tbreak;\n\t\tcase NEW_LINE:\n\t\t\t/* \\n is not allowed */\n\t\t\tstate->state = HTTP_STATE_ERROR;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tstate->cur_header_value_len++;\n\t\t\tbreak;\n\t}\n}\n\nstatic void\nhttp_parse_almost_done(HttpResponseState *state, const char next)\n{\n\t/* Don't do anything, this is intermediate state */\n\tswitch (next)\n\t{\n\t\tcase NEW_LINE:\n\t\t\tstate->state = HTTP_STATE_BODY;\n\t\t\tstate->body_start = state->raw_buffer + state->parse_offset + 1;\n\t\t\t/* Special case if there is no body */\n\t\t\tif (state->content_length == 0)\n\t\t\t\tstate->state = HTTP_STATE_DONE;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tstate->state = HTTP_STATE_ERROR;\n\t\t\tbreak;\n\t}\n}\n\nbool\nts_http_response_state_parse(HttpResponseState *state, size_t bytes)\n{\n\tstate->offset += bytes;\n\n\tif (state->offset > MAX_RAW_BUFFER_SIZE)\n\t\tstate->offset = MAX_RAW_BUFFER_SIZE;\n\n\t/* Each state function will do the state AND transition */\n\twhile (state->parse_offset < state->offset)\n\t{\n\t\tchar next = state->raw_buffer[state->parse_offset];\n\n\t\tswitch (state->state)\n\t\t{\n\t\t\tcase HTTP_STATE_STATUS:\n\t\t\t\thttp_parse_status(state, next);\n\t\t\t\tstate->parse_offset++;\n\t\t\t\tbreak;\n\t\t\tcase HTTP_STATE_INTERM:\n\t\t\t\thttp_parse_interm(state, next);\n\t\t\t\tstate->parse_offset++;\n\t\t\t\tstate->cur_header_name = state->raw_buffer + state->parse_offset;\n\t\t\t\tbreak;\n\t\t\tcase HTTP_STATE_HEADER_NAME:\n\t\t\t\thttp_parse_header_name(state, next);\n\t\t\t\tstate->parse_offset++;\n\t\t\t\tbreak;\n\t\t\tcase HTTP_STATE_HEADER_VALUE:\n\t\t\t\thttp_parse_header_value(state, next);\n\t\t\t\tstate->parse_offset++;\n\t\t\t\tbreak;\n\t\t\tcase HTTP_STATE_ALMOST_DONE:\n\t\t\t\thttp_parse_almost_done(state, next);\n\t\t\t\tstate->parse_offset++;\n\t\t\t\tbreak;\n\t\t\tcase HTTP_STATE_BODY:\n\t\t\t\t/* Stay here until we have read content_length */\n\t\t\t\tif ((state->body_start + state->content_length) <=\n\t\t\t\t\t(state->raw_buffer + state->offset))\n\t\t\t\t{\n\t\t\t\t\t/* Then we are done */\n\t\t\t\t\tstate->state = HTTP_STATE_DONE;\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tstate->parse_offset++;\n\t\t\t\tbreak;\n\t\t\tcase HTTP_STATE_ERROR:\n\t\t\t\treturn false;\n\t\t\tcase HTTP_STATE_DONE:\n\t\t\t\treturn true;\n\t\t}\n\t}\n\treturn true;\n}\n"
  },
  {
    "path": "src/nodes/CMakeLists.txt",
    "content": "set(SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/modify_hypertable.c\n            ${CMAKE_CURRENT_SOURCE_DIR}/modify_hypertable_exec.c)\ntarget_sources(${PROJECT_NAME} PRIVATE ${SOURCES})\nadd_subdirectory(chunk_append)\nadd_subdirectory(constraint_aware_append)\n"
  },
  {
    "path": "src/nodes/chunk_append/CMakeLists.txt",
    "content": "# Add all *.c to sources in upperlevel directory\nset(SOURCES\n    ${CMAKE_CURRENT_SOURCE_DIR}/chunk_append.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/exec.c ${CMAKE_CURRENT_SOURCE_DIR}/planner.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/transform.c)\ntarget_sources(${PROJECT_NAME} PRIVATE ${SOURCES})\n"
  },
  {
    "path": "src/nodes/chunk_append/chunk_append.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <nodes/nodeFuncs.h>\n#include <optimizer/optimizer.h>\n#include <optimizer/pathnode.h>\n#include <optimizer/paths.h>\n#include <optimizer/tlist.h>\n#include <utils/builtins.h>\n#include <utils/typcache.h>\n\n#include \"func_cache.h\"\n#include \"guc.h\"\n#include \"nodes/chunk_append/chunk_append.h\"\n#include \"planner/planner.h\"\n\nstatic Var *find_equality_join_var(Var *sort_var, Index ht_relid, List *join_conditions);\n\nstatic CustomPathMethods chunk_append_path_methods = {\n\t.CustomName = \"ChunkAppend\",\n\t.PlanCustomPath = ts_chunk_append_plan_create,\n};\n\nbool\nts_is_chunk_append_path(Path *path)\n{\n\treturn IsA(path, CustomPath) &&\n\t\t   castNode(CustomPath, path)->methods == &chunk_append_path_methods;\n}\n\nstatic bool\nhas_joins(FromExpr *jointree)\n{\n\treturn list_length(jointree->fromlist) != 1 || !IsA(linitial(jointree->fromlist), RangeTblRef);\n}\n\n/*\n * Create the appropriate subpath for the outer MergeAppend\n * node depending on the number of paths in the current group:\n * Combine two or more group members into a mergeAppend node\n * or append a single member as is.\n * Members of the same group contain data of the same chunk,\n * so they are combined into a MergeAppend node corresponding\n * to that single chunk.\n */\nstatic void\ncreate_group_subpath(PlannerInfo *root, RelOptInfo *rel, List *group, List *pathkeys,\n\t\t\t\t\t Relids required_outer, List *partitioned_rels, List **nested_children)\n{\n\tif (list_length(group) > 1)\n\t{\n\t\tMergeAppendPath *append =\n\t\t\tcreate_merge_append_path(root, rel, group, pathkeys, required_outer);\n\t\t*nested_children = lappend(*nested_children, append);\n\t}\n\telse\n\t{\n\t\t/* If group only has 1 member we can add it directly */\n\t\t*nested_children = lappend(*nested_children, linitial(group));\n\t}\n}\n\nChunkAppendPath *\nts_chunk_append_path_copy(ChunkAppendPath *ca, List *subpaths, PathTarget *pathtarget)\n{\n\tListCell *lc;\n\tdouble total_cost = 0, rows = 0;\n\tChunkAppendPath *new = palloc(sizeof(ChunkAppendPath));\n\tmemcpy(new, ca, sizeof(ChunkAppendPath));\n\tnew->cpath.custom_paths = subpaths;\n\n\tforeach (lc, subpaths)\n\t{\n\t\tPath *child = lfirst(lc);\n\t\ttotal_cost += child->total_cost;\n\t\trows += child->rows;\n\t}\n\tnew->cpath.path.total_cost = total_cost;\n\tnew->cpath.path.rows = rows;\n\tnew->cpath.path.pathtarget = copy_pathtarget(pathtarget);\n\n\treturn new;\n}\n\nPath *\nts_chunk_append_path_create(PlannerInfo *root, RelOptInfo *rel, Hypertable *ht, Path *subpath,\n\t\t\t\t\t\t\tbool parallel_aware, bool ordered, List *nested_oids)\n{\n\tChunkAppendPath *path;\n\tListCell *lc;\n\tdouble rows = 0.0;\n\tCost total_cost = 0.0;\n\tList *children = NIL;\n\n\tpath = (ChunkAppendPath *) newNode(sizeof(ChunkAppendPath), T_CustomPath);\n\n\tpath->cpath.path.pathtype = T_CustomScan;\n\tpath->cpath.path.parent = rel;\n\tpath->cpath.path.pathtarget = rel->reltarget;\n\tpath->cpath.path.param_info = subpath->param_info;\n\n\t/*\n\t * We keep the pathkeys from the original path here because\n\t * the original path was either a MergeAppendPath and this\n\t * will become an ordered append or the original path is an\n\t * AppendPath and since we do not reorder children the order\n\t * will be kept intact. For the AppendPath case with pathkeys\n\t * it was most likely an Append with only a single child.\n\t * We could skip the ChunkAppend path creation if there is\n\t * only a single child but we decided earlier that ChunkAppend\n\t * would be beneficial for this query so we treat it the same\n\t * as if it had multiple children.\n\t */\n\tAssert(IsA(subpath, AppendPath) || IsA(subpath, MergeAppendPath));\n\tpath->cpath.path.pathkeys = subpath->pathkeys;\n\n\tpath->cpath.path.parallel_aware = ts_guc_enable_parallel_chunk_append ? parallel_aware : false;\n\tpath->cpath.path.parallel_safe = subpath->parallel_safe;\n\tpath->cpath.path.parallel_workers = subpath->parallel_workers;\n\n\t/*\n\t * Set flags. We can set CUSTOMPATH_SUPPORT_BACKWARD_SCAN and\n\t * CUSTOMPATH_SUPPORT_MARK_RESTORE. The only interesting flag is the first\n\t * one (backward scan), but since we are not scanning a real relation we\n\t * need not indicate that we support backward scans. Lower-level index\n\t * scanning nodes will scan backward if necessary, so once tuples get to\n\t * this node they will be in a given order already.\n\t */\n\tpath->cpath.flags = 0;\n\tpath->cpath.methods = &chunk_append_path_methods;\n\n\t/*\n\t * Figure out whether there's a hard limit on the number of rows that\n\t * query_planner's result subplan needs to return.  Even if we know a\n\t * hard limit overall, it doesn't apply if the query has any\n\t * grouping/aggregation operations, or SRFs in the tlist.\n\t */\n\tif (root->parse->groupClause || root->parse->groupingSets || root->parse->distinctClause ||\n\t\troot->parse->hasAggs || root->parse->hasWindowFuncs || root->hasHavingQual ||\n\t\thas_joins(root->parse->jointree) || root->limit_tuples > PG_INT32_MAX ||\n\t\troot->parse->hasTargetSRFs ||\n\t\t!pathkeys_contained_in(root->sort_pathkeys, subpath->pathkeys))\n\t\tpath->limit_tuples = -1;\n\telse\n\t\tpath->limit_tuples = (int) root->limit_tuples;\n\n\t/*\n\t * check if we should do startup and runtime exclusion\n\t */\n\tforeach (lc, rel->baserestrictinfo)\n\t{\n\t\tRestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);\n\n\t\t/*\n\t\t * The external parameters (e.g. from parameterized prepared statements)\n\t\t * are constant during query run time, so we can use them for startup\n\t\t * exclusion.\n\t\t * The join parameters have multiple values, so they are only used for\n\t\t * runtime exclusion.\n\t\t */\n\t\tif (contain_mutable_functions((Node *) rinfo->clause) ||\n\t\t\tts_contains_external_param((Node *) rinfo->clause))\n\t\t{\n\t\t\tpath->startup_exclusion = true;\n\t\t}\n\n\t\tif (ts_guc_enable_runtime_exclusion && ts_contains_join_param((Node *) rinfo->clause))\n\t\t{\n\t\t\tListCell *lc_var;\n\n\t\t\t/* We have two types of exclusion:\n\t\t\t *\n\t\t\t * Parent exclusion fires if the entire hypertable can be excluded.\n\t\t\t * This happens if doing things like joining against a parameter\n\t\t\t * value that is an empty array or NULL. It doesn't happen often,\n\t\t\t * but when it does, it speeds up the query immensely. It's also cheap\n\t\t\t * to check for this condition as you check this once per hypertable\n\t\t\t * at runtime.\n\t\t\t *\n\t\t\t * Child exclusion works by seeing if there is a contradiction between\n\t\t\t * the chunks constraints and the expression on parameter values. For example,\n\t\t\t * it can evaluate whether a time parameter from a subquery falls outside\n\t\t\t * the range of the chunk. It is more widely applicable than the parent\n\t\t\t * exclusion but is also more expensive to evaluate since you have to perform\n\t\t\t * the check on every chunk. Child exclusion can only apply if one of the quals\n\t\t\t * involves a partitioning column.\n\t\t\t *\n\t\t\t */\n\t\t\tpath->runtime_exclusion_parent = true;\n\t\t\tforeach (lc_var, pull_var_clause((Node *) rinfo->clause, 0))\n\t\t\t{\n\t\t\t\tVar *var = lfirst(lc_var);\n\t\t\t\t/*\n\t\t\t\t * varattno 0 is whole row and varattno less than zero are\n\t\t\t\t * system columns so we skip those even though\n\t\t\t\t * ts_is_partitioning_column would return the correct\n\t\t\t\t * answer for those as well\n\t\t\t\t */\n\t\t\t\tif ((Index) var->varno == rel->relid && var->varattno > 0 &&\n\t\t\t\t\tts_is_partitioning_column(ht, var->varattno))\n\t\t\t\t{\n\t\t\t\t\tpath->runtime_exclusion_children = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/*\n\t * For parameterized paths (e.g., inner side of LATERAL joins), also check\n\t * ppi_clauses for runtime exclusion. Any clause in ppi_clauses references\n\t * outer relations by definition.\n\t */\n\tif (ts_guc_enable_runtime_exclusion && subpath->param_info != NULL &&\n\t\tsubpath->param_info->ppi_clauses != NIL)\n\t{\n\t\tpath->runtime_exclusion_parent = true;\n\n\t\t/* Check if any clause involves a partitioning column for child exclusion */\n\t\tforeach (lc, subpath->param_info->ppi_clauses)\n\t\t{\n\t\t\tRestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);\n\t\t\tListCell *lc_var;\n\n\t\t\tforeach (lc_var, pull_var_clause((Node *) rinfo->clause, 0))\n\t\t\t{\n\t\t\t\tVar *var = lfirst(lc_var);\n\t\t\t\tif ((Index) var->varno == rel->relid && var->varattno > 0 &&\n\t\t\t\t\tts_is_partitioning_column(ht, var->varattno))\n\t\t\t\t{\n\t\t\t\t\tpath->runtime_exclusion_children = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (path->runtime_exclusion_children)\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/*\n\t * Our strategy is to use child exclusion if possible (if a partitioning\n\t * column is used) and fall back to parent exclusion if we can't use child\n\t * exclusion. Please note: there is no point to using both child and parent\n\t * exclusion at the same time since child exclusion would always exclude\n\t * the same chunks that parent exclusion would.\n\t */\n\n\tif (path->runtime_exclusion_parent && path->runtime_exclusion_children)\n\t\tpath->runtime_exclusion_parent = false;\n\n\t/*\n\t * Make sure our subpath is either an Append or MergeAppend node\n\t */\n\tswitch (nodeTag(subpath))\n\t{\n\t\tcase T_AppendPath:\n\t\t{\n\t\t\tAppendPath *append = castNode(AppendPath, subpath);\n\n\t\t\tif (append->path.parallel_aware && append->first_partial_path > 0)\n\t\t\t\tpath->first_partial_path = append->first_partial_path;\n\t\t\tchildren = append->subpaths;\n\t\t\tbreak;\n\t\t}\n\t\tcase T_MergeAppendPath:\n\t\t\t/*\n\t\t\t * check if ordered append is applicable, only assert ordered here\n\t\t\t * checked properly in ts_ordered_append_should_optimize\n\t\t\t */\n\t\t\tAssert(ordered);\n\n\t\t\t/*\n\t\t\t * we only push down LIMIT for ordered append\n\t\t\t */\n\t\t\tpath->pushdown_limit = true;\n\n\t\t\tchildren = castNode(MergeAppendPath, subpath)->subpaths;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\telog(ERROR, \"invalid child of chunk append: %s\", ts_get_node_name((Node *) subpath));\n\t\t\tbreak;\n\t}\n\n\tif (!ordered)\n\t{\n\t\tpath->cpath.custom_paths = children;\n\t}\n\telse if (ht->space->num_dimensions == 1)\n\t{\n\t\tList *nested_children = NIL;\n\t\t/*\n\t\t * Convert the sort nodes that refer to the same chunk into a single\n\t\t * mergeAppend node to combine compressed and uncompressed chunk output.\n\t\t *\n\t\t * NB: We assume that the sort nodes referring the same chunk appear\n\t\t * one after the other and so we iterate through the children examining\n\t\t * consecutive pairs. Is it possible that this assumption is wrong?\n\t\t */\n\t\tList *group = NIL;\n\t\tOid relid = InvalidOid;\n\n\t\tforeach (lc, children)\n\t\t{\n\t\t\tPath *child = (Path *) lfirst(lc);\n\t\t\t/* Check if this is in new group */\n\t\t\tif (child->parent->relid != relid)\n\t\t\t{\n\t\t\t\t/* if previous group had members, process them */\n\t\t\t\tif (group)\n\t\t\t\t{\n\t\t\t\t\tcreate_group_subpath(root,\n\t\t\t\t\t\t\t\t\t\t rel,\n\t\t\t\t\t\t\t\t\t\t group,\n\t\t\t\t\t\t\t\t\t\t path->cpath.path.pathkeys,\n\t\t\t\t\t\t\t\t\t\t PATH_REQ_OUTER(subpath),\n\t\t\t\t\t\t\t\t\t\t NIL,\n\t\t\t\t\t\t\t\t\t\t &nested_children);\n\t\t\t\t\tgroup = NIL;\n\t\t\t\t}\n\t\t\t\trelid = child->parent->relid;\n\t\t\t}\n\n\t\t\t/* Form the new group */\n\t\t\tgroup = lappend(group, child);\n\t\t}\n\n\t\tif (group)\n\t\t{\n\t\t\tcreate_group_subpath(root,\n\t\t\t\t\t\t\t\t rel,\n\t\t\t\t\t\t\t\t group,\n\t\t\t\t\t\t\t\t path->cpath.path.pathkeys,\n\t\t\t\t\t\t\t\t PATH_REQ_OUTER(subpath),\n\t\t\t\t\t\t\t\t NIL,\n\t\t\t\t\t\t\t\t &nested_children);\n\t\t}\n\n\t\tpath->cpath.custom_paths = nested_children;\n\t\tchildren = nested_children;\n\t}\n\telse\n\t{\n\t\t/*\n\t\t * For space partitioning we need to change the shape of the plan\n\t\t * into a MergeAppend for each time slice with all space partitions below\n\t\t * The final plan for space partitioning will look like this:\n\t\t *\n\t\t * Custom Scan (ChunkAppend)\n\t\t *   Hypertable: space\n\t\t *   ->  Merge Append\n\t\t *         Sort Key: _hyper_9_56_chunk.\"time\"\n\t\t *         ->  Index Scan\n\t\t *         ->  Index Scan\n\t\t *         ->  Index Scan\n\t\t *   ->  Merge Append\n\t\t *         Sort Key: _hyper_9_55_chunk.\"time\"\n\t\t *         ->  Index Scan\n\t\t *         ->  Index Scan\n\t\t *         ->  Index Scan\n\t\t *\n\t\t * We do not check sort order at this stage but injecting of Sort\n\t\t * nodes happens when the plan is created instead.\n\t\t */\n\t\tListCell *flat = list_head(children);\n\t\tList *nested_children = NIL;\n\t\tbool has_scan_childs = false;\n\n\t\tforeach (lc, nested_oids)\n\t\t{\n\t\t\tListCell *lc_oid;\n\t\t\tList *current_oids = lfirst(lc);\n\t\t\tList *merge_childs = NIL;\n\t\t\tMergeAppendPath *append;\n\n\t\t\t/*\n\t\t\t * For each lc_oid, there will be 0, 1, or 2 matches in flat_list: 0 matches\n\t\t\t * if child was pruned, 1 match if the chunk is uncompressed or fully compressed,\n\t\t\t * 2 matches if the chunk is partially compressed.\n\t\t\t * If there are 2 matches they will also be consecutive (see assumption above)\n\t\t\t */\n\t\t\tforeach (lc_oid, current_oids)\n\t\t\t{\n\t\t\t\tbool is_not_pruned = true;\n#ifdef USE_ASSERT_CHECKING\n\t\t\t\tint nmatches = 0;\n#endif\n\t\t\t\t/* Before entering the \"DO\" loop, check for a valid path entry */\n\t\t\t\tif (flat == NULL)\n\t\t\t\t\tbreak;\n\n\t\t\t\tdo\n\t\t\t\t{\n\t\t\t\t\tPath *child = (Path *) lfirst(flat);\n\t\t\t\t\tOid parent_relid = child->parent->relid;\n\t\t\t\t\tis_not_pruned =\n\t\t\t\t\t\tlfirst_oid(lc_oid) == root->simple_rte_array[parent_relid]->relid;\n\t\t\t\t\t/* postgres may have pruned away some children already */\n\t\t\t\t\tif (is_not_pruned)\n\t\t\t\t\t{\n#ifdef USE_ASSERT_CHECKING\n\t\t\t\t\t\tnmatches++;\n#endif\n\t\t\t\t\t\tmerge_childs = lappend(merge_childs, child);\n\t\t\t\t\t\tflat = lnext(children, flat);\n\t\t\t\t\t\tif (flat == NULL)\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\t/* if current one matched then need to check next one for match */\n\t\t\t\t} while (is_not_pruned);\n#ifdef USE_ASSERT_CHECKING\n\t\t\t\tAssert(nmatches <= 2);\n#endif\n\t\t\t}\n\n\t\t\tif (list_length(merge_childs) > 1)\n\t\t\t{\n\t\t\t\tappend = create_merge_append_path(root,\n\t\t\t\t\t\t\t\t\t\t\t\t  rel,\n\t\t\t\t\t\t\t\t\t\t\t\t  merge_childs,\n\t\t\t\t\t\t\t\t\t\t\t\t  path->cpath.path.pathkeys,\n\t\t\t\t\t\t\t\t\t\t\t\t  PATH_REQ_OUTER(subpath));\n\t\t\t\tnested_children = lappend(nested_children, append);\n\t\t\t}\n\t\t\telse if (list_length(merge_childs) == 1)\n\t\t\t{\n\t\t\t\thas_scan_childs = true;\n\t\t\t\tnested_children = lappend(nested_children, linitial(merge_childs));\n\t\t\t}\n\t\t}\n\n\t\tAssert(flat == NULL);\n\n\t\t/*\n\t\t * if we do not have scans as direct children of this\n\t\t * node we disable startup and runtime exclusion\n\t\t * in this node\n\t\t */\n\t\tif (!has_scan_childs)\n\t\t{\n\t\t\tpath->startup_exclusion = false;\n\t\t\tpath->runtime_exclusion_parent = false;\n\t\t\tpath->runtime_exclusion_children = false;\n\t\t}\n\n\t\tpath->cpath.custom_paths = nested_children;\n\t}\n\n\tforeach (lc, path->cpath.custom_paths)\n\t{\n\t\tPath *child = lfirst(lc);\n\n\t\t/*\n\t\t * If there is a LIMIT clause we only include as many chunks as\n\t\t * planner thinks are needed to satisfy LIMIT clause.\n\t\t * We do this to prevent planner choosing parallel plan which might\n\t\t * otherwise look preferable cost wise.\n\t\t */\n\t\tif (!path->pushdown_limit || path->limit_tuples == -1 || rows < path->limit_tuples)\n\t\t{\n\t\t\ttotal_cost += child->total_cost;\n\t\t\trows += child->rows;\n\t\t}\n\t}\n\n\tpath->cpath.path.rows = rows;\n\tpath->cpath.path.total_cost = total_cost;\n\n\tif (path->cpath.custom_paths != NIL)\n\t\tpath->cpath.path.startup_cost = ((Path *) linitial(path->cpath.custom_paths))->startup_cost;\n\n\treturn &path->cpath.path;\n}\n\n/*\n * Check if conditions for doing ordered append optimization are fulfilled\n */\nbool\nts_ordered_append_should_optimize(PlannerInfo *root, RelOptInfo *rel, Hypertable *ht,\n\t\t\t\t\t\t\t\t  int *order_attno, bool *reverse)\n{\n\tSortGroupClause *sort = linitial(root->parse->sortClause);\n\tTargetEntry *tle = get_sortgroupref_tle(sort->tleSortGroupRef, root->parse->targetList);\n\tRangeTblEntry *rte = root->simple_rte_array[rel->relid];\n\tTypeCacheEntry *tce;\n\tchar *column;\n\tIndex ht_relid = rel->relid;\n\tIndex sort_relid;\n\tVar *ht_var;\n\tVar *sort_var;\n\n\t/* these are checked in caller so we only Assert */\n\tAssert(ts_guc_enable_optimizations && ts_guc_enable_ordered_append &&\n\t\t   ts_guc_enable_chunk_append);\n\n\t/*\n\t * only do this optimization for queries with an ORDER BY clause,\n\t * caller checked this, so only asserting\n\t */\n\tAssert(root->parse->sortClause != NIL);\n\n\tif (IsA(tle->expr, Var))\n\t{\n\t\t/* direct column reference */\n\t\tsort_var = castNode(Var, tle->expr);\n\t}\n\telse if (IsA(tle->expr, FuncExpr) && list_length(root->parse->sortClause) == 1)\n\t{\n\t\t/*\n\t\t * check for bucketing functions\n\t\t *\n\t\t * If ORDER BY clause only has 1 expression and the expression is a\n\t\t * bucketing function we can still do Ordered Append, the 1 expression\n\t\t * limit could only be safely removed if we ensure chunk boundaries\n\t\t * are not crossed.\n\t\t *\n\t\t * The following example demonstrates this requirement:\n\t\t *\n\t\t * Chunk 1 has (time, device_id)\n\t\t * 0 1\n\t\t * 0 2\n\t\t *\n\t\t * Chunk 2 has (time, device_id)\n\t\t * 10 1\n\t\t * 10 2\n\t\t *\n\t\t * The ORDER BY clause is time_bucket(100,time), device_id\n\t\t * The result when transforming to an ordered append would be the following:\n\t\t * (time_bucket(100, time), device_id)\n\t\t * 0 1\n\t\t * 0 2\n\t\t * 0 1\n\t\t * 0 2\n\t\t *\n\t\t * The order of the device_ids is wrong so we cannot safely remove the MergeAppend\n\t\t * unless we eliminate the possibility that a bucket spans multiple chunks.\n\t\t */\n\t\tFuncInfo *info = ts_func_cache_get_bucketing_func(castNode(FuncExpr, tle->expr)->funcid);\n\t\tExpr *transformed;\n\n\t\tif (!info || !info->sort_transform)\n\t\t\treturn false;\n\n\t\ttransformed = info->sort_transform(castNode(FuncExpr, tle->expr));\n\n\t\tif (!IsA(transformed, Var))\n\t\t\treturn false;\n\n\t\tsort_var = castNode(Var, transformed);\n\t}\n\telse\n\t\treturn false;\n\n\t/* ordered append won't work for system columns / whole row orderings */\n\tif (sort_var->varattno <= 0)\n\t\treturn false;\n\n\tsort_relid = sort_var->varno;\n\ttce = lookup_type_cache(sort_var->vartype,\n\t\t\t\t\t\t\tTYPECACHE_EQ_OPR | TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);\n\n\t/* check sort operation is either less than or greater than */\n\tif (sort->sortop != tce->lt_opr && sort->sortop != tce->gt_opr)\n\t\treturn false;\n\n\t/*\n\t * check the ORDER BY column actually belongs to our hypertable\n\t */\n\tif (sort_relid == ht_relid)\n\t{\n\t\t/* ORDER BY column belongs to our hypertable */\n\t\tht_var = sort_var;\n\t}\n\telse\n\t{\n\t\t/*\n\t\t * If the ORDER BY does not match our hypertable, but we are joining\n\t\t * against another hypertable on the time column, then doing an ordered\n\t\t * append here is still beneficial, because we can skip the sort\n\t\t * step for the MergeJoin.\n\t\t */\n\t\tBitmapset *outer_relids = root->simple_rel_array[sort_relid]->relids;\n\t\tBitmapset *inner_relids = root->simple_rel_array[ht_relid]->relids;\n\t\tList *join_conditions =\n\t\t\tgenerate_join_implied_equalities(root,\n\t\t\t\t\t\t\t\t\t\t\t bms_union(outer_relids, inner_relids),\n\t\t\t\t\t\t\t\t\t\t\t outer_relids,\n\t\t\t\t\t\t\t\t\t\t\t rel\n#if PG16_GE\n\t\t\t\t\t\t\t\t\t\t\t ,\n\t\t\t\t\t\t\t\t\t\t\t /* sjinfo = */ NULL\n#endif\n\t\t\t);\n\n\t\t/*\n\t\t * The outer join clauses don't form ECs and stay in joininfo, and we\n\t\t * want to check them too.\n\t\t * There are also non-equality join conditions in joininfo, but they're\n\t\t * not relevant for MergeJoin anyway and will be skipped.\n\t\t */\n\t\tjoin_conditions = list_concat(join_conditions, rel->joininfo);\n\n\t\tif (join_conditions == NIL)\n\t\t\treturn false;\n\n\t\tht_var = find_equality_join_var(sort_var, ht_relid, join_conditions);\n\n\t\tif (ht_var == NULL)\n\t\t\treturn false;\n\t}\n\n\t/* Check hypertable column is the first dimension of the hypertable */\n\tcolumn = strVal(list_nth(rte->eref->colnames, AttrNumberGetAttrOffset(ht_var->varattno)));\n\tif (namestrcmp(&ht->space->dimensions[0].fd.column_name, column) != 0)\n\t\treturn false;\n\n\tAssert(order_attno != NULL && reverse != NULL);\n\t*order_attno = ht_var->varattno;\n\t*reverse = sort->sortop == tce->lt_opr ? false : true;\n\n\treturn true;\n}\n\n/*\n * Find equality join between column referenced by sort_var and Relation\n * with relid ht_relid\n */\nstatic Var *\nfind_equality_join_var(Var *sort_var, Index ht_relid, List *join_conditions)\n{\n\tListCell *lc;\n\tIndex sort_relid = sort_var->varno;\n\n\tforeach (lc, join_conditions)\n\t{\n\t\tRestrictInfo *ri = castNode(RestrictInfo, lfirst(lc));\n\n\t\t/*\n\t\t * Only interested in join clauses here.\n\t\t */\n\t\tif (!ri->can_join)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\t/*\n\t\t * The clause must be a mergejoinable equality operator.\n\t\t */\n\t\tif (ri->mergeopfamilies == NIL)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\tOpExpr *op = castNode(OpExpr, ri->clause);\n\n\t\tif (!IsA(linitial(op->args), Var))\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\t\tVar *left = castNode(Var, linitial(op->args));\n\n\t\tif (!IsA(lsecond(op->args), Var))\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\t\tVar *right = castNode(Var, lsecond(op->args));\n\n\t\t/* Is this a join condition referencing our hypertable */\n\t\tif (((Index) left->varno == sort_relid && (Index) right->varno == ht_relid &&\n\t\t\t left->varattno == sort_var->varattno))\n\t\t{\n\t\t\treturn right;\n\t\t}\n\n\t\tif (((Index) left->varno == ht_relid && (Index) right->varno == sort_relid &&\n\t\t\t right->varattno == sort_var->varattno))\n\t\t{\n\t\t\treturn left;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n"
  },
  {
    "path": "src/nodes/chunk_append/chunk_append.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <nodes/extensible.h>\n\n#include \"hypertable.h\"\n\n/*\n * Indexes into the settings list (first element of custom_private).\n */\ntypedef enum\n{\n\tCAS_StartupExclusion = 0,\n\tCAS_RuntimeExclusionParent = 1,\n\tCAS_RuntimeExclusionChildren = 2,\n\tCAS_Limit = 3,\n\tCAS_FirstPartialPath = 4,\n\tCAS_Count\n} ChunkAppendSettingsIndex;\n\n/*\n * Indexes into custom_private for ChunkAppend.\n */\ntypedef enum\n{\n\tCAP_Settings = 0,\n\tCAP_ChunkRIClauses = 1,\n\tCAP_RTIndexes = 2,\n\tCAP_SortOptions = 3,\n\tCAP_ParentClauses = 4,\n\tCAP_Count\n} ChunkAppendPrivateIndex;\n\ntypedef struct ChunkAppendPath\n{\n\tCustomPath cpath;\n\tbool startup_exclusion;\n\tbool runtime_exclusion_parent;\n\tbool runtime_exclusion_children;\n\tbool pushdown_limit;\n\tint limit_tuples;\n\tint first_partial_path;\n} ChunkAppendPath;\n\nextern TSDLLEXPORT ChunkAppendPath *ts_chunk_append_path_copy(ChunkAppendPath *ca, List *subpaths,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  PathTarget *pathtarget);\nextern Path *ts_chunk_append_path_create(PlannerInfo *root, RelOptInfo *rel, Hypertable *ht,\n\t\t\t\t\t\t\t\t\t\t Path *subpath, bool parallel_aware, bool ordered,\n\t\t\t\t\t\t\t\t\t\t List *nested_oids);\nextern Plan *ts_chunk_append_plan_create(PlannerInfo *root, RelOptInfo *rel, CustomPath *path,\n\t\t\t\t\t\t\t\t\t\t List *tlist, List *clauses, List *custom_plans);\nextern Node *ts_chunk_append_state_create(CustomScan *cscan);\n\nextern bool ts_ordered_append_should_optimize(PlannerInfo *root, RelOptInfo *rel, Hypertable *ht,\n\t\t\t\t\t\t\t\t\t\t\t  int *order_attno, bool *reverse);\n\nextern TSDLLEXPORT bool ts_is_chunk_append_path(Path *path);\nextern TSDLLEXPORT bool ts_is_chunk_append_plan(Plan *plan);\n\nextern Scan *ts_chunk_append_get_scan_plan(Plan *plan);\n\nvoid _chunk_append_init(void);\n\nextern TSDLLEXPORT List *ts_constify_restrictinfos(PlannerInfo *root, List *restrictinfos);\nextern TSDLLEXPORT List *ts_constify_restrictinfo_params(PlannerInfo *root, EState *state,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t List *restrictinfos);\n"
  },
  {
    "path": "src/nodes/chunk_append/exec.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <catalog/pg_collation.h>\n#include <executor/executor.h>\n#include <executor/nodeSubplan.h>\n#include <fmgr.h>\n#include <miscadmin.h>\n#include <nodes/bitmapset.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodeFuncs.h>\n#include <optimizer/cost.h>\n#include <optimizer/optimizer.h>\n#include <optimizer/plancat.h>\n#include <optimizer/prep.h>\n#include <optimizer/restrictinfo.h>\n#include <parser/parsetree.h>\n#include <rewrite/rewriteManip.h>\n#include <utils/builtins.h>\n#include <utils/memutils.h>\n#include <utils/ruleutils.h>\n#include <utils/typcache.h>\n\n#include <math.h>\n\n#include \"compat/compat.h\"\n#include \"loader/lwlocks.h\"\n#include \"nodes/chunk_append/chunk_append.h\"\n#include \"planner/planner.h\"\n#include \"transform.h\"\n#include \"ts_catalog/chunk_column_stats.h\"\n\n#if PG18_GE\n#include <commands/explain_format.h>\n#endif\n\n#define INVALID_SUBPLAN_INDEX (-1)\n#define NO_MATCHING_SUBPLANS (-2)\n\ntypedef enum SubplanState\n{\n\tSUBPLAN_STATE_INCLUDED = 1 << 0, /* Used and not removed by startup exclusion */\n\tSUBPLAN_STATE_FINISHED = 1 << 1, /* The subplan is finished */\n} SubplanState;\n\n/* ParallelChunkAppendState is stored in shared memory to coordinate the parallel workers.\n *\n * subplan_state is accessed by two different indexes. This is done because a C struct can have only\n * one FLEXIBLE_ARRAY_MEMBER, two pieces of information must be stored per subplan in shared memory,\n * and computing a mapping between both indexes is avoided in the current implementation.\n *\n * The first index is the position of the subplan in initial_subplans. This index is used to\n * get/set the flag SUBPLAN_STATE_INCLUDED.\n *\n * The second index is the position of a subplan in filtered_subplans. This index is used to get/set\n * the flag SUBPLAN_STATE_FINISHED.\n */\ntypedef struct ParallelChunkAppendState\n{\n\tint next_plan;\n\tint filtered_first_partial_plan;\n\tuint32 subplan_state[FLEXIBLE_ARRAY_MEMBER]; /* See SubplanState */\n} ParallelChunkAppendState;\n\ntypedef struct ChunkAppendState\n{\n\tCustomScanState csstate;\n\tPlanState **subplanstates;\n\n\tMemoryContext exclusion_ctx;\n\n\tint num_subplans;\n\tint first_partial_plan;\n\tint filtered_first_partial_plan;\n\tint current;\n\n\tOid ht_reloid;\n\tbool startup_exclusion;\n\tbool runtime_exclusion_parent;\n\tbool runtime_exclusion_children;\n\tbool runtime_initialized;\n\tuint32 limit;\n\n#ifdef USE_ASSERT_CHECKING\n\tbool init_done;\n#endif\n\n\t/* list of subplans after planning */\n\tList *initial_subplans;\n\t/* list of constraints indexed like initial_subplans */\n\tList *initial_constraints;\n\t/* list of restrictinfo clauses indexed like initial_subplans */\n\tList *initial_ri_clauses;\n\t/* List of restrictinfo clauses on the parent hypertable */\n\tList *initial_parent_clauses;\n\n\t/* list of subplans after startup exclusion */\n\tList *filtered_subplans;\n\t/* list of relation constraints after startup exclusion */\n\tList *filtered_constraints;\n\t/* list of restrictinfo clauses after startup exclusion */\n\tList *filtered_ri_clauses;\n\t/* included subplans by startup exclusion */\n\tBitmapset *included_subplans_by_se;\n\n\t/* valid subplans for runtime exclusion */\n\tBitmapset *valid_subplans;\n\tBitmapset *params;\n\n\t/* sort options if this append is ordered, only used for EXPLAIN */\n\tList *sort_options;\n\n\t/* number of loops and exclusions for EXPLAIN */\n\tint runtime_number_loops;\n\tint runtime_number_exclusions_parent;\n\tint runtime_number_exclusions_children;\n\n\tLWLock *lock;\n\tParallelContext *pcxt;\n\tParallelChunkAppendState *pstate;\n\tEState *estate;\n\tint eflags;\n\tvoid (*choose_next_subplan)(struct ChunkAppendState *);\n} ChunkAppendState;\n\nstatic TupleTableSlot *chunk_append_exec(CustomScanState *node);\nstatic void chunk_append_begin(CustomScanState *node, EState *estate, int eflags);\nstatic void chunk_append_end(CustomScanState *node);\nstatic void chunk_append_rescan(CustomScanState *node);\nstatic void chunk_append_explain(CustomScanState *node, List *ancestors, ExplainState *es);\nstatic Size chunk_append_estimate_dsm(CustomScanState *node, ParallelContext *pcxt);\nstatic void chunk_append_initialize_dsm(CustomScanState *node, ParallelContext *pcxt,\n\t\t\t\t\t\t\t\t\t\tvoid *coordinate);\nstatic void chunk_append_reinitialize_dsm(CustomScanState *node, ParallelContext *pcxt,\n\t\t\t\t\t\t\t\t\t\t  void *coordinate);\nstatic void chunk_append_initialize_worker(CustomScanState *node, shm_toc *toc, void *coordinate);\n\nstatic CustomExecMethods chunk_append_state_methods = {\n\t.BeginCustomScan = chunk_append_begin,\n\t.ExecCustomScan = chunk_append_exec,\n\t.EndCustomScan = chunk_append_end,\n\t.ReScanCustomScan = chunk_append_rescan,\n\t.ExplainCustomScan = chunk_append_explain,\n\t.EstimateDSMCustomScan = chunk_append_estimate_dsm,\n\t.InitializeDSMCustomScan = chunk_append_initialize_dsm,\n\t.ReInitializeDSMCustomScan = chunk_append_reinitialize_dsm,\n\t.InitializeWorkerCustomScan = chunk_append_initialize_worker,\n};\n\nstatic void choose_next_subplan_non_parallel(ChunkAppendState *state);\nstatic void choose_next_subplan_for_worker(ChunkAppendState *state);\n\nstatic bool can_exclude_chunk(List *constraints, List *baserestrictinfo);\nstatic void do_startup_exclusion(ChunkAppendState *state);\nstatic Node *constify_param_mutator(Node *node, void *context);\n\nstatic void initialize_constraints(ChunkAppendState *state, List *initial_rt_indexes);\nstatic LWLock *chunk_append_get_lock_pointer(void);\n\nstatic void show_sort_group_keys(ChunkAppendState *planstate, List *ancestors, ExplainState *es);\nstatic void show_sortorder_options(StringInfo buf, Node *sortexpr, Oid sortOperator, Oid collation,\n\t\t\t\t\t\t\t\t   bool nullsFirst);\n\nstatic void perform_plan_init(ChunkAppendState *state, EState *estate, int eflags);\n\nNode *\nts_chunk_append_state_create(CustomScan *cscan)\n{\n\tChunkAppendState *state;\n\n\tAssert(list_length(cscan->custom_private) == CAP_Count);\n\tList *settings = list_nth(cscan->custom_private, CAP_Settings);\n\tAssert(list_length(settings) == CAS_Count);\n\n\tstate = (ChunkAppendState *) newNode(sizeof(ChunkAppendState), T_CustomScanState);\n\n\tstate->csstate.methods = &chunk_append_state_methods;\n\n\tstate->initial_subplans = cscan->custom_plans;\n\tstate->initial_ri_clauses = list_nth(cscan->custom_private, CAP_ChunkRIClauses);\n\tstate->sort_options = list_nth(cscan->custom_private, CAP_SortOptions);\n\tstate->initial_parent_clauses = list_nth(cscan->custom_private, CAP_ParentClauses);\n\n\tstate->startup_exclusion = list_nth_int(settings, CAS_StartupExclusion);\n\tstate->runtime_exclusion_parent = list_nth_int(settings, CAS_RuntimeExclusionParent);\n\tstate->runtime_exclusion_children = list_nth_int(settings, CAS_RuntimeExclusionChildren);\n\tstate->limit = list_nth_int(settings, CAS_Limit);\n\tstate->first_partial_plan = list_nth_int(settings, CAS_FirstPartialPath);\n\n\tstate->filtered_subplans = state->initial_subplans;\n\tstate->filtered_ri_clauses = state->initial_ri_clauses;\n\tstate->filtered_first_partial_plan = state->first_partial_plan;\n\n\tstate->current = INVALID_SUBPLAN_INDEX;\n\tstate->choose_next_subplan = choose_next_subplan_non_parallel;\n\n\tstate->exclusion_ctx = AllocSetContextCreate(CurrentMemoryContext,\n\t\t\t\t\t\t\t\t\t\t\t\t \"ChunkApppend exclusion\",\n\t\t\t\t\t\t\t\t\t\t\t\t ALLOCSET_DEFAULT_SIZES);\n\n\treturn (Node *) state;\n}\n\nstatic void\ndo_startup_exclusion(ChunkAppendState *state)\n{\n\tList *filtered_children = NIL;\n\tList *filtered_ri_clauses = NIL;\n\tList *filtered_constraints = NIL;\n\tListCell *lc_plan;\n\tListCell *lc_clauses;\n\tListCell *lc_constraints;\n\tint i = -1;\n\tint filtered_first_partial_plan = state->first_partial_plan;\n\n\t/*\n\t * create skeleton plannerinfo for estimate_expression_value\n\t */\n\tPlannerGlobal glob = {\n\t\t.boundParams = state->csstate.ss.ps.state->es_param_list_info,\n\t};\n\tPlannerInfo root = {\n\t\t.glob = &glob,\n\t};\n\n\t/* Reset included subplans */\n\tstate->included_subplans_by_se = NULL;\n\n\t/*\n\t * clauses and constraints should always have the same length as initial_subplans\n\t */\n\tAssert(list_length(state->initial_subplans) == list_length(state->initial_ri_clauses));\n\tAssert(list_length(state->initial_subplans) == list_length(state->initial_constraints));\n\n\tforthree (lc_plan,\n\t\t\t  state->initial_subplans,\n\t\t\t  lc_constraints,\n\t\t\t  state->initial_constraints,\n\t\t\t  lc_clauses,\n\t\t\t  state->initial_ri_clauses)\n\t{\n\t\tList *restrictinfos = NIL;\n\t\tList *ri_clauses = lfirst(lc_clauses);\n\t\tListCell *lc;\n\t\tScan *scan = ts_chunk_append_get_scan_plan(lfirst(lc_plan));\n\n\t\ti++;\n\n\t\t/*\n\t\t * If this is a base rel (chunk), check if it can be\n\t\t * excluded from the scan. Otherwise, fall through.\n\t\t */\n\t\tif (scan != NULL && scan->scanrelid)\n\t\t{\n\t\t\tforeach (lc, ri_clauses)\n\t\t\t{\n\t\t\t\tRestrictInfo *ri = makeNode(RestrictInfo);\n\t\t\t\tri->clause = lfirst(lc);\n\t\t\t\trestrictinfos = lappend(restrictinfos, ri);\n\t\t\t}\n\t\t\trestrictinfos = ts_constify_restrictinfos(&root, restrictinfos);\n\n\t\t\tif (can_exclude_chunk(lfirst(lc_constraints), restrictinfos))\n\t\t\t{\n\t\t\t\tif (i < state->first_partial_plan)\n\t\t\t\t\tfiltered_first_partial_plan--;\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * if this node does runtime exclusion on the children we keep the constified\n\t\t\t * expressions to save us some work during runtime exclusion\n\t\t\t */\n\t\t\tif (state->runtime_exclusion_children)\n\t\t\t{\n\t\t\t\tList *const_ri_clauses = NIL;\n\t\t\t\tforeach (lc, restrictinfos)\n\t\t\t\t{\n\t\t\t\t\tRestrictInfo *ri = lfirst(lc);\n\t\t\t\t\tconst_ri_clauses = lappend(const_ri_clauses, ri->clause);\n\t\t\t\t}\n\t\t\t\tri_clauses = const_ri_clauses;\n\t\t\t}\n\t\t}\n\n\t\tstate->included_subplans_by_se = bms_add_member(state->included_subplans_by_se, i);\n\t\tfiltered_children = lappend(filtered_children, lfirst(lc_plan));\n\t\tfiltered_ri_clauses = lappend(filtered_ri_clauses, ri_clauses);\n\t\tfiltered_constraints = lappend(filtered_constraints, lfirst(lc_constraints));\n\t}\n\n\tstate->filtered_subplans = filtered_children;\n\tstate->filtered_ri_clauses = filtered_ri_clauses;\n\tstate->filtered_constraints = filtered_constraints;\n\tstate->filtered_first_partial_plan = filtered_first_partial_plan;\n\n\tAssert(list_length(state->filtered_subplans) ==\n\t\t   bms_num_members(state->included_subplans_by_se));\n}\n\n/*\n * Complete initialization of the supplied CustomScanState.\n * Standard fields have been initialized by ExecInitCustomScan,\n * but any private fields should be initialized here.\n */\nstatic void\nchunk_append_begin(CustomScanState *node, EState *estate, int eflags)\n{\n\tCustomScan *cscan = castNode(CustomScan, node->ss.ps.plan);\n\tChunkAppendState *state = (ChunkAppendState *) node;\n\n\t/* CustomScan hard-codes the scan and result tuple slot to a fixed\n\t * TTSOpsVirtual ops (meaning it expects the slot ops of the child tuple to\n\t * also have this type). Oddly, when reading slots from subscan nodes\n\t * (children), there is no knowing what tuple slot ops the child slot will\n\t * have (e.g., for ChunkAppend it is common that the child is a\n\t * seqscan/indexscan that produces a TTSOpsBufferHeapTuple\n\t * slot). Unfortunately, any mismatch between slot types when projecting is\n\t * asserted by PostgreSQL. To avoid this issue, we mark the scanops as\n\t * non-fixed and reinitialize the projection state with this new setting.\n\t *\n\t * Alternatively, we could copy the child tuple into the scan slot to get\n\t * the expected ops before projection, but this would require materializing\n\t * and copying the tuple unnecessarily.\n\t */\n\tnode->ss.ps.scanopsfixed = false;\n\n\t/* Since we sometimes return the scan slot directly from the subnode, the\n\t * result slot is not fixed either. */\n\tnode->ss.ps.resultopsfixed = false;\n\tExecAssignScanProjectionInfoWithVarno(&node->ss, INDEX_VAR);\n\n\tinitialize_constraints(state, list_nth(cscan->custom_private, CAP_RTIndexes));\n\n\t/* In parallel mode with a parallel_aware plan, the parallel leader performs the startup\n\t * exclusion and stores the result in shared memory (the flag SUBPLAN_STATE_INCLUDED of\n\t * pstate->subplan_state is set for all included plans).\n\t *\n\t * The parallel workers use the information from shared memory to include the same plans as the\n\t * parallel leader. This ensures that all workers work on the same subplans and we have an\n\t * agreement about the number of subplans. This is necessary to ensure that the parallel workers\n\t * work correctly and that the next subplan to be processed in the shared memory\n\t * (pstate->next_plan) pointers to the same plan in all workers.\n\t *\n\t * If the workers perform the startup exclusion individually, they may choose different subplans\n\t * (e.g., due to a \"constant\" function that claims to be constant but returns different\n\t * results). In that case, we have a disagreement about the plans between the workers. This\n\t * would lead to hard-to-debug problems and out-of-bounds reads when pstate->next_plan is used\n\t * for subplan selection.\n\t *\n\t */\n\tif (IsParallelWorker() && node->ss.ps.plan->parallel_aware)\n\t{\n\t\t/* We are inside a parallel worker running a parallel plan. Chunk exclusion was performed by\n\t\t * the leader, and based on it, we will initialize the included subplans later, in\n\t\t * chunk_append_initialize_worker. We have to store estate and eflags here that are needed\n\t\t * for that initialization.\n\t\t *\n\t\t * Note: When force_parallel_mode debug GUC is set, a normal sequential ChunkAppend plan can\n\t\t * run inside a parallel worker. In this case, we have to perform the chunk exclusion right\n\t\t * away. We distinguish it by that the parallel_aware flag of the plan is not set.\n\t\t */\n\t\tstate->estate = estate;\n\t\tstate->eflags = eflags;\n\t\treturn;\n\t}\n\n\tif (state->startup_exclusion)\n\t\tdo_startup_exclusion(state);\n\n\tperform_plan_init(state, estate, eflags);\n}\n\n/*\n * Perform an initialization of the filtered_subplans.\n */\nstatic void\nperform_plan_init(ChunkAppendState *state, EState *estate, int eflags)\n{\n\tListCell *lc;\n\tint i;\n\n#ifdef USE_ASSERT_CHECKING\n\tAssert(state->init_done == false);\n\tstate->init_done = true;\n#endif\n\n\tstate->num_subplans = list_length(state->filtered_subplans);\n\n\tif (state->num_subplans == 0)\n\t{\n\t\tstate->current = NO_MATCHING_SUBPLANS;\n\t\treturn;\n\t}\n\n\tstate->subplanstates = (PlanState **) palloc0(state->num_subplans * sizeof(PlanState *));\n\n\ti = 0;\n\tforeach (lc, state->filtered_subplans)\n\t{\n\t\t/*\n\t\t * we use an array for the states but put it in custom_ps as well\n\t\t * so explain and planstate_tree_walker can find it\n\t\t */\n\t\tstate->subplanstates[i] = ExecInitNode(lfirst(lc), estate, eflags);\n\t\tstate->csstate.custom_ps = lappend(state->csstate.custom_ps, state->subplanstates[i]);\n\n\t\t/*\n\t\t * pass down limit to child nodes\n\t\t */\n\t\tif (state->limit)\n\t\t\tExecSetTupleBound(state->limit, state->subplanstates[i]);\n\n\t\ti++;\n\t}\n\n\tif (state->runtime_exclusion_parent || state->runtime_exclusion_children)\n\t{\n\t\tstate->params = state->subplanstates[0]->plan->allParam;\n\t\t/*\n\t\t * make sure all params are initialized for runtime exclusion\n\t\t */\n\t\tstate->csstate.ss.ps.chgParam = bms_copy(state->subplanstates[0]->plan->allParam);\n\t}\n}\n\nstatic bool\ncan_exclude_constraints_using_clauses(ChunkAppendState *state, List *constraints, List *clauses,\n\t\t\t\t\t\t\t\t\t  PlannerInfo *root, PlanState *ps)\n{\n\tbool can_exclude;\n\tListCell *lc;\n\tMemoryContext old = MemoryContextSwitchTo(state->exclusion_ctx);\n\tList *restrictinfos = NIL;\n\n\tforeach (lc, clauses)\n\t{\n\t\tRestrictInfo *ri = makeNode(RestrictInfo);\n\t\tri->clause = lfirst(lc);\n\t\trestrictinfos = lappend(restrictinfos, ri);\n\t}\n\trestrictinfos = ts_constify_restrictinfo_params(root, ps->state, restrictinfos);\n\n\tcan_exclude = can_exclude_chunk(constraints, restrictinfos);\n\n\tMemoryContextReset(state->exclusion_ctx);\n\tMemoryContextSwitchTo(old);\n\treturn can_exclude;\n}\n\n/*\n * build bitmap of valid subplans for runtime exclusion\n */\nstatic void\ninitialize_runtime_exclusion(ChunkAppendState *state)\n{\n\tListCell *lc_clauses, *lc_constraints;\n\tint i = 0;\n\n\tPlannerGlobal glob = {\n\t\t.boundParams = state->csstate.ss.ps.state->es_param_list_info,\n\t};\n\tPlannerInfo root = {\n\t\t.glob = &glob,\n\t};\n\n\tstate->runtime_initialized = true;\n\n\tif (state->num_subplans == 0)\n\t{\n\t\treturn;\n\t}\n\n\tstate->runtime_number_loops++;\n\n\tif (state->runtime_exclusion_parent)\n\t{\n\t\t/* try to exclude all the chunks using the parents clauses.\n\t\t * here, all constraints are true but exclusion can still\n\t\t * happen because of things like ANY(empty set), and NULL\n\t\t * inference\n\t\t */\n\t\tif (can_exclude_constraints_using_clauses(state,\n\t\t\t\t\t\t\t\t\t\t\t\t  list_make1(makeBoolConst(true, false)),\n\t\t\t\t\t\t\t\t\t\t\t\t  state->initial_parent_clauses,\n\t\t\t\t\t\t\t\t\t\t\t\t  &root,\n\t\t\t\t\t\t\t\t\t\t\t\t  &state->csstate.ss.ps))\n\t\t{\n\t\t\tstate->runtime_number_exclusions_parent++;\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (!state->runtime_exclusion_children)\n\t{\n\t\tfor (i = 0; i < state->num_subplans; i++)\n\t\t{\n\t\t\tstate->valid_subplans = bms_add_member(state->valid_subplans, i);\n\t\t}\n\t\treturn;\n\t}\n\n\tAssert(state->num_subplans == list_length(state->filtered_ri_clauses));\n\n\tlc_clauses = list_head(state->filtered_ri_clauses);\n\tlc_constraints = list_head(state->filtered_constraints);\n\n\t/*\n\t * mark subplans as active/inactive in valid_subplans\n\t */\n\tfor (i = 0; i < state->num_subplans; i++)\n\t{\n\t\tPlanState *ps = state->subplanstates[i];\n\t\tScan *scan = ts_chunk_append_get_scan_plan(ps->plan);\n\n\t\tif (scan == NULL || scan->scanrelid == 0)\n\t\t{\n\t\t\tstate->valid_subplans = bms_add_member(state->valid_subplans, i);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tbool can_exclude = can_exclude_constraints_using_clauses(state,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t lfirst(lc_constraints),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t lfirst(lc_clauses),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t &root,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t ps);\n\n\t\t\tif (!can_exclude)\n\t\t\t\tstate->valid_subplans = bms_add_member(state->valid_subplans, i);\n\t\t\telse\n\t\t\t\tstate->runtime_number_exclusions_children++;\n\t\t}\n\n\t\tlc_clauses = lnext(state->filtered_ri_clauses, lc_clauses);\n\t\tlc_constraints = lnext(state->filtered_constraints, lc_constraints);\n\t}\n}\n\n/*\n * Fetch the next scan tuple.\n *\n * If any tuples remain, it should fill ps_ResultTupleSlot with the next\n * tuple in the current scan direction, and then return the tuple slot.\n * If not, NULL or an empty slot should be returned.\n */\nstatic TupleTableSlot *\nchunk_append_exec(CustomScanState *node)\n{\n\tChunkAppendState *state = (ChunkAppendState *) node;\n\tExprContext *econtext = node->ss.ps.ps_ExprContext;\n\tProjectionInfo *projinfo = node->ss.ps.ps_ProjInfo;\n\tTupleTableSlot *subslot;\n\n\tAssert(state->init_done == true);\n\n\tif (state->current == INVALID_SUBPLAN_INDEX)\n\t\tstate->choose_next_subplan(state);\n\n\twhile (true)\n\t{\n\t\tPlanState *subnode;\n\n\t\tCHECK_FOR_INTERRUPTS();\n\n\t\tif (state->current == NO_MATCHING_SUBPLANS)\n\t\t\treturn ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);\n\n\t\tAssert(state->current >= 0 && state->current < state->num_subplans);\n\t\tsubnode = state->subplanstates[state->current];\n\n\t\t/*\n\t\t * get a tuple from the subplan\n\t\t */\n\t\tsubslot = ExecProcNode(subnode);\n\n\t\tif (!TupIsNull(subslot))\n\t\t{\n\t\t\t/*\n\t\t\t * If the subplan gave us something check if we need\n\t\t\t * to do projection otherwise return as is.\n\t\t\t */\n\t\t\tif (projinfo == NULL)\n\t\t\t\treturn subslot;\n\n\t\t\tResetExprContext(econtext);\n\t\t\tecontext->ecxt_scantuple = subslot;\n\n\t\t\treturn ExecProject(projinfo);\n\t\t}\n\n\t\tstate->choose_next_subplan(state);\n\n\t\t/* loop back and try to get a tuple from the new subplan */\n\t}\n}\n\nstatic int\nget_next_subplan(ChunkAppendState *state, int last_plan)\n{\n\tif (last_plan == NO_MATCHING_SUBPLANS)\n\t\treturn NO_MATCHING_SUBPLANS;\n\n\tif (state->runtime_exclusion_parent || state->runtime_exclusion_children)\n\t{\n\t\tif (!state->runtime_initialized)\n\t\t\tinitialize_runtime_exclusion(state);\n\n\t\t/*\n\t\t * bms_next_member will return -2 (NO_MATCHING_SUBPLANS) if there are\n\t\t * no more members\n\t\t */\n\t\treturn bms_next_member(state->valid_subplans, last_plan);\n\t}\n\telse\n\t{\n\t\tint next_plan = last_plan + 1;\n\n\t\tif (next_plan >= state->num_subplans)\n\t\t\treturn NO_MATCHING_SUBPLANS;\n\n\t\treturn next_plan;\n\t}\n}\n\nstatic void\nchoose_next_subplan_non_parallel(ChunkAppendState *state)\n{\n\tstate->current = get_next_subplan(state, state->current);\n}\n\nstatic void\nchoose_next_subplan_for_worker(ChunkAppendState *state)\n{\n\tParallelChunkAppendState *pstate = state->pstate;\n\tint next_plan;\n\tint start;\n\n\tLWLockAcquire(state->lock, LW_EXCLUSIVE);\n\n\t/* mark just completed subplan as finished */\n\tif (state->current >= 0)\n\t\tpstate->subplan_state[state->current] =\n\t\t\tts_set_flags_32(pstate->subplan_state[state->current], SUBPLAN_STATE_FINISHED);\n\n\tif (pstate->next_plan == INVALID_SUBPLAN_INDEX)\n\t\tnext_plan = get_next_subplan(state, INVALID_SUBPLAN_INDEX);\n\telse\n\t\tnext_plan = pstate->next_plan;\n\n\tif (next_plan == NO_MATCHING_SUBPLANS)\n\t{\n\t\t/* all subplans are finished */\n\t\tpstate->next_plan = NO_MATCHING_SUBPLANS;\n\t\tstate->current = NO_MATCHING_SUBPLANS;\n\t\tLWLockRelease(state->lock);\n\t\treturn;\n\t}\n\n\tstart = next_plan;\n\n\t/* skip finished subplans */\n\twhile (ts_flags_are_set_32(pstate->subplan_state[next_plan], SUBPLAN_STATE_FINISHED))\n\t{\n\t\tnext_plan = get_next_subplan(state, next_plan);\n\n\t\t/* wrap around if we reach end of subplan list */\n\t\tif (next_plan < 0)\n\t\t\tnext_plan = get_next_subplan(state, INVALID_SUBPLAN_INDEX);\n\n\t\tif (next_plan == start || next_plan < 0)\n\t\t{\n\t\t\t/*\n\t\t\t * back at start of search so all subplans are finished\n\t\t\t *\n\t\t\t * next_plan should not be < 0 because this means there\n\t\t\t * are no valid subplans and then the function would\n\t\t\t * have returned at the check before the while loop but\n\t\t\t * static analysis marked this so might as well include\n\t\t\t * that in the check\n\t\t\t */\n\t\t\tAssert(next_plan >= 0);\n\t\t\tpstate->next_plan = NO_MATCHING_SUBPLANS;\n\t\t\tstate->current = NO_MATCHING_SUBPLANS;\n\t\t\tLWLockRelease(state->lock);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tAssert(next_plan >= 0 && next_plan < state->num_subplans);\n\tstate->current = next_plan;\n\n\t/*\n\t * if this is not a partial plan we mark it as finished\n\t * immediately so it does not get assigned another worker\n\t */\n\tif (next_plan < state->filtered_first_partial_plan)\n\t\tpstate->subplan_state[next_plan] =\n\t\t\tts_set_flags_32(pstate->subplan_state[next_plan], SUBPLAN_STATE_FINISHED);\n\n\t/* advance next_plan for next worker */\n\tpstate->next_plan = get_next_subplan(state, state->current);\n\t/*\n\t * if we reach the end of the list of subplans we set next_plan\n\t * to INVALID_SUBPLAN_INDEX to allow rechecking unfinished subplans\n\t * on next call\n\t */\n\tif (pstate->next_plan < 0)\n\t\tpstate->next_plan = INVALID_SUBPLAN_INDEX;\n\n\tLWLockRelease(state->lock);\n}\n\n/*\n * Clean up any private data associated with the CustomScanState.\n *\n * This method is required, but it does not need to do anything if there\n * is no associated data or it will be cleaned up automatically.\n */\nstatic void\nchunk_append_end(CustomScanState *node)\n{\n\tChunkAppendState *state = (ChunkAppendState *) node;\n\tint i;\n\n\tfor (i = 0; i < state->num_subplans; i++)\n\t{\n\t\tExecEndNode(state->subplanstates[i]);\n\t}\n}\n\n/*\n * Rewind the current scan to the beginning and prepare to rescan the relation.\n */\nstatic void\nchunk_append_rescan(CustomScanState *node)\n{\n\tChunkAppendState *state = (ChunkAppendState *) node;\n\tint i;\n\n\tfor (i = 0; i < state->num_subplans; i++)\n\t{\n\t\tif (node->ss.ps.chgParam != NULL)\n\t\t\tUpdateChangedParamSet(state->subplanstates[i], node->ss.ps.chgParam);\n\n\t\tExecReScan(state->subplanstates[i]);\n\t}\n\tstate->current = INVALID_SUBPLAN_INDEX;\n\n\t/*\n\t * detect changed params and reset runtime exclusion state\n\t */\n\tif ((state->runtime_exclusion_parent || state->runtime_exclusion_children) &&\n\t\tbms_overlap(node->ss.ps.chgParam, state->params))\n\t{\n\t\tbms_free(state->valid_subplans);\n\t\tstate->valid_subplans = NULL;\n\t\tstate->runtime_initialized = false;\n\t}\n}\n\n/*\n * Estimate the amount of dynamic shared memory that will be required\n * for parallel operation.\n * This may be higher than the amount that will actually be used,\n * but it must not be lower. The return value is in bytes.\n * This callback is optional, and need only be supplied if this\n * custom scan provider supports parallel execution.\n */\nstatic Size\nchunk_append_estimate_dsm(CustomScanState *node, ParallelContext *pcxt)\n{\n\tChunkAppendState *state = (ChunkAppendState *) node;\n\treturn add_size(offsetof(ParallelChunkAppendState, subplan_state),\n\t\t\t\t\tsizeof(uint32) * list_length(state->initial_subplans));\n}\n\n/*\n * Initialize the parallel state.\n */\nstatic void\ninit_pstate(ChunkAppendState *state, ParallelChunkAppendState *pstate)\n{\n\tAssert(state != NULL);\n\tAssert(pstate != NULL);\n\tAssert(state->csstate.pscan_len > 0);\n\n\t/* The parallel worker state has to be (re-)initialized by the parallel leader */\n\tAssert(!IsParallelWorker());\n\n\tmemset(pstate, 0, state->csstate.pscan_len);\n\n\tpstate->next_plan = INVALID_SUBPLAN_INDEX;\n\tpstate->filtered_first_partial_plan = state->filtered_first_partial_plan;\n\n\t/* Mark active subplans in parallel state */\n\tint plan = -1;\n\twhile ((plan = bms_next_member(state->included_subplans_by_se, plan)) >= 0)\n\t{\n\t\tpstate->subplan_state[plan] =\n\t\t\tts_set_flags_32(pstate->subplan_state[plan], SUBPLAN_STATE_INCLUDED);\n\t}\n}\n\n/*\n * Initialize the dynamic shared memory that will be required for\n * parallel operation.\n * coordinate points to a shared memory area of size equal to the return\n * value of EstimateDSMCustomScan.\n * This callback is optional, and need only be supplied if this custom scan\n * provider supports parallel execution.\n */\nstatic void\nchunk_append_initialize_dsm(CustomScanState *node, ParallelContext *pcxt, void *coordinate)\n{\n\tChunkAppendState *state = (ChunkAppendState *) node;\n\tParallelChunkAppendState *pstate = (ParallelChunkAppendState *) coordinate;\n\tinit_pstate(state, pstate);\n\n\tstate->lock = chunk_append_get_lock_pointer();\n\n\t/*\n\t * Leader should use the same subplan selection as normal worker threads. If the user wishes to\n\t * disallow running plans on the leader they should do so via the parallel_leader_participation\n\t * GUC.\n\t */\n\tstate->choose_next_subplan = choose_next_subplan_for_worker;\n\tstate->current = INVALID_SUBPLAN_INDEX;\n\tstate->pcxt = pcxt;\n\tstate->pstate = pstate;\n}\n\n/*\n * Re-initialize the dynamic shared memory required for parallel operation\n * when the custom-scan plan node is about to be re-scanned.\n * This callback is optional, and need only be supplied if this custom scan\n * provider supports parallel execution.\n * Recommended practice is that this callback reset only shared state,\n * while the ReScanCustomScan callback resets only local state.\n * Currently, this callback will be called before ReScanCustomScan,\n * but it's best not to rely on that ordering.\n */\nstatic void\nchunk_append_reinitialize_dsm(CustomScanState *node, ParallelContext *pcxt, void *coordinate)\n{\n\tChunkAppendState *state = (ChunkAppendState *) node;\n\tParallelChunkAppendState *pstate = (ParallelChunkAppendState *) coordinate;\n\tinit_pstate(state, pstate);\n}\n\n/*\n * Initialize a parallel worker's local state based on the shared state\n * set up by the leader during InitializeDSMCustomScan.\n *\n * This callback is optional, and need only be supplied if this custom scan\n * provider supports parallel execution.\n */\nstatic void\nchunk_append_initialize_worker(CustomScanState *node, shm_toc *toc, void *coordinate)\n{\n\tChunkAppendState *state = (ChunkAppendState *) node;\n\tParallelChunkAppendState *pstate = (ParallelChunkAppendState *) coordinate;\n\n\tAssert(IsParallelWorker());\n\tAssert(node->ss.ps.plan->parallel_aware);\n\tAssert(pstate != NULL);\n\tAssert(state->estate != NULL);\n\n\t/* Read information about included plans by startup exclusion from the parallel state */\n\tstate->filtered_first_partial_plan = pstate->filtered_first_partial_plan;\n\n\tList *filtered_subplans = NIL;\n\tList *filtered_ri_clauses = NIL;\n\tList *filtered_constraints = NIL;\n\n\tfor (int plan = 0; plan < list_length(state->initial_subplans); plan++)\n\t{\n\t\tif (ts_flags_are_set_32(pstate->subplan_state[plan], SUBPLAN_STATE_INCLUDED))\n\t\t{\n\t\t\tfiltered_subplans =\n\t\t\t\tlappend(filtered_subplans, list_nth(state->filtered_subplans, plan));\n\t\t\tfiltered_ri_clauses =\n\t\t\t\tlappend(filtered_ri_clauses, list_nth(state->filtered_ri_clauses, plan));\n\t\t\tfiltered_constraints =\n\t\t\t\tlappend(filtered_constraints, list_nth(state->filtered_constraints, plan));\n\t\t}\n\t}\n\n\tstate->filtered_subplans = filtered_subplans;\n\tstate->filtered_ri_clauses = filtered_ri_clauses;\n\tstate->filtered_constraints = filtered_constraints;\n\n\tAssert(list_length(state->filtered_subplans) == list_length(state->filtered_ri_clauses));\n\tAssert(list_length(state->filtered_ri_clauses) == list_length(state->filtered_constraints));\n\n\tstate->lock = chunk_append_get_lock_pointer();\n\tstate->choose_next_subplan = choose_next_subplan_for_worker;\n\tstate->current = INVALID_SUBPLAN_INDEX;\n\tstate->pstate = pstate;\n\n\tperform_plan_init(state, state->estate, state->eflags);\n\tAssert(state->num_subplans == list_length(state->filtered_subplans));\n}\n\n/*\n * get a pointer to the LWLock used for coordinating\n * parallel workers\n */\nstatic LWLock *\nchunk_append_get_lock_pointer()\n{\n\tLWLock **lock = (LWLock **) find_rendezvous_variable(RENDEZVOUS_CHUNK_APPEND_LWLOCK);\n\n\tif (*lock == NULL)\n\t\telog(ERROR, \"LWLock for coordinating parallel workers not initialized\");\n\n\treturn *lock;\n}\n\n/*\n * Convert restriction clauses to constants expressions (i.e., if there are\n * mutable functions, they need to be evaluated to constants).  For instance,\n * something like:\n *\n * ...WHERE time > now - interval '1 hour'\n *\n * becomes\n *\n * ...WHERE time > '2017-06-02 11:26:43.935712+02'\n */\nList *\nts_constify_restrictinfos(PlannerInfo *root, List *restrictinfos)\n{\n\tList *additional_list = NIL;\n\n\tListCell *lc;\n\n\tforeach (lc, restrictinfos)\n\t{\n\t\tRestrictInfo *rinfo = lfirst(lc);\n\t\tExpr *constified = (Expr *) estimate_expression_value(root, (Node *) rinfo->clause);\n\n\t\t/*\n\t\t * Note that we have to use equal() here, because the expression mutators\n\t\t * always return a deep copy of the expression tree, even if nothing was\n\t\t * modified.\n\t\t */\n\t\tif (!equal(rinfo->clause, constified))\n\t\t{\n\t\t\t/*\n\t\t\t * We have constified something, so try applying the time_bucket\n\t\t\t * transformations again. This might allow us to exclude chunks\n\t\t\t * based on a parameterized time_bucket expression.\n\t\t\t */\n\t\t\tExpr *additional_clause = ts_transform_time_bucket_comparison(constified);\n\t\t\tif (additional_clause != NULL)\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * We successfully added a filter clause based on a\n\t\t\t\t * parameterized time_bucket comparison, but it might contain\n\t\t\t\t * stable operators like comparison of timestamp to timestamptz,\n\t\t\t\t * so we have to evaluate them as well.\n\t\t\t\t */\n\t\t\t\tadditional_clause = ts_transform_cross_datatype_comparison(additional_clause);\n\t\t\t\tadditional_clause =\n\t\t\t\t\t(Expr *) estimate_expression_value(root, (Node *) additional_clause);\n\t\t\t\tadditional_list =\n\t\t\t\t\tlappend(additional_list, make_simple_restrictinfo(root, additional_clause));\n\t\t\t}\n\t\t}\n\t\trinfo->clause = constified;\n\t}\n\n\treturn list_concat(restrictinfos, additional_list);\n}\n\nList *\nts_constify_restrictinfo_params(PlannerInfo *root, EState *state, List *restrictinfos)\n{\n\tListCell *lc;\n\n\tforeach (lc, restrictinfos)\n\t{\n\t\tRestrictInfo *rinfo = lfirst(lc);\n\n\t\trinfo->clause = (Expr *) constify_param_mutator((Node *) rinfo->clause, state);\n\t\trinfo->clause = (Expr *) estimate_expression_value(root, (Node *) rinfo->clause);\n\t}\n\n\treturn restrictinfos;\n}\n\nstatic Node *\nconstify_param_mutator(Node *node, void *context)\n{\n\tif (node == NULL)\n\t\treturn NULL;\n\n\t/* Don't descend into subplans to constify their parameters, because they may not be valid yet\n\t */\n\tif (IsA(node, SubPlan))\n\t\treturn node;\n\n\tif (IsA(node, Param))\n\t{\n\t\tParam *param = castNode(Param, node);\n\t\tEState *estate = (EState *) context;\n\n\t\tif (param->paramkind == PARAM_EXEC)\n\t\t{\n\t\t\tTypeCacheEntry *tce = lookup_type_cache(param->paramtype, 0);\n\t\t\tParamExecData prm = estate->es_param_exec_vals[param->paramid];\n\n\t\t\tif (prm.execPlan != NULL)\n\t\t\t{\n\t\t\t\tExprContext *econtext = GetPerTupleExprContext(estate);\n\t\t\t\tExecSetParamPlan(prm.execPlan, econtext);\n\t\t\t\t// reload prm as it may have been changed by ExecSetParamPlan call above.\n\t\t\t\tprm = estate->es_param_exec_vals[param->paramid];\n\t\t\t}\n\n\t\t\tif (prm.execPlan == NULL)\n\t\t\t\treturn (Node *) makeConst(param->paramtype,\n\t\t\t\t\t\t\t\t\t\t  param->paramtypmod,\n\t\t\t\t\t\t\t\t\t\t  param->paramcollid,\n\t\t\t\t\t\t\t\t\t\t  tce->typlen,\n\t\t\t\t\t\t\t\t\t\t  prm.value,\n\t\t\t\t\t\t\t\t\t\t  prm.isnull,\n\t\t\t\t\t\t\t\t\t\t  tce->typbyval);\n\t\t}\n\t\treturn node;\n\t}\n\n\treturn expression_tree_mutator(node, constify_param_mutator, context);\n}\n\n/*\n * stripped down version of postgres get_relation_constraints\n */\nstatic List *\nca_get_relation_constraints(Oid relationObjectId, Index varno, bool include_notnull)\n{\n\tList *result = NIL;\n\tRelation relation;\n\tTupleConstr *constr;\n\n\t/*\n\t * We assume the relation has already been safely locked.\n\t */\n\trelation = table_open(relationObjectId, AccessShareLock);\n\n\tconstr = relation->rd_att->constr;\n\tif (constr != NULL)\n\t{\n\t\tint num_check = constr->num_check;\n\t\tint i;\n\n\t\tfor (i = 0; i < num_check; i++)\n\t\t{\n\t\t\tNode *cexpr;\n\n\t\t\t/*\n\t\t\t * If this constraint hasn't been fully validated yet, we must\n\t\t\t * ignore it here.\n\t\t\t */\n\t\t\tif (!constr->check[i].ccvalid)\n\t\t\t\tcontinue;\n\n\t\t\tcexpr = stringToNode(constr->check[i].ccbin);\n\n\t\t\t/*\n\t\t\t * Run each expression through const-simplification and\n\t\t\t * canonicalization.  This is not just an optimization, but is\n\t\t\t * necessary, because we will be comparing it to\n\t\t\t * similarly-processed qual clauses, and may fail to detect valid\n\t\t\t * matches without this.  This must match the processing done to\n\t\t\t * qual clauses in preprocess_expression()!  (We can skip the\n\t\t\t * stuff involving subqueries, however, since we don't allow any\n\t\t\t * in check constraints.)\n\t\t\t */\n\t\t\tcexpr = eval_const_expressions(NULL, cexpr);\n\t\t\tcexpr = (Node *) canonicalize_qual((Expr *) cexpr, true);\n\n\t\t\t/* Fix Vars to have the desired varno */\n\t\t\tif (varno != 1)\n\t\t\t\tChangeVarNodes(cexpr, 1, varno, 0);\n\n\t\t\t/*\n\t\t\t * Finally, convert to implicit-AND format (that is, a List) and\n\t\t\t * append the resulting item(s) to our output list.\n\t\t\t */\n\t\t\tresult = list_concat(result, make_ands_implicit((Expr *) cexpr));\n\t\t}\n\n\t\t/* Add NOT NULL constraints in expression form, if requested */\n\t\tif (include_notnull && constr->has_not_null)\n\t\t{\n\t\t\tint natts = relation->rd_att->natts;\n\n\t\t\tfor (i = 1; i <= natts; i++)\n\t\t\t{\n\t\t\t\tForm_pg_attribute att = TupleDescAttr(relation->rd_att, i - 1);\n\n\t\t\t\tif (att->attnotnull && !att->attisdropped)\n\t\t\t\t{\n\t\t\t\t\tNullTest *ntest = makeNode(NullTest);\n\n\t\t\t\t\tntest->arg = (Expr *)\n\t\t\t\t\t\tmakeVar(varno, i, att->atttypid, att->atttypmod, att->attcollation, 0);\n\t\t\t\t\tntest->nulltesttype = IS_NOT_NULL;\n\n\t\t\t\t\t/*\n\t\t\t\t\t * argisrow=false is correct even for a composite column,\n\t\t\t\t\t * because attnotnull does not represent a SQL-spec IS NOT\n\t\t\t\t\t * NULL test in such a case, just IS DISTINCT FROM NULL.\n\t\t\t\t\t */\n\t\t\t\t\tntest->argisrow = false;\n\t\t\t\t\tntest->location = -1;\n\t\t\t\t\tresult = lappend(result, ntest);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (ts_guc_enable_chunk_skipping)\n\t\t{\n\t\t\t/* Add column range min/max ranges in 'CHECK CONSTRAINT' form */\n\t\t\tresult = list_concat(result,\n\t\t\t\t\t\t\t\t ts_chunk_column_stats_construct_check_constraints(relation,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   relationObjectId,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   varno));\n\t\t}\n\t}\n\n\ttable_close(relation, NoLock);\n\n\treturn result;\n}\n\n/*\n * Exclude child relations (chunks) at execution time based on constraints.\n *\n * constraints is the list of constraint expressions of the relation\n * baserestrictinfo is the list of RestrictInfos\n */\nstatic bool\ncan_exclude_chunk(List *constraints, List *baserestrictinfo)\n{\n\t/*\n\t * Detect constant-FALSE-or-NULL restriction clauses. If we have such a\n\t * clause, no rows from the chunk are going to match. Unlike the postgres\n\t * analog of this code in relation_excluded_by_constraints, we can't expect\n\t * a single const false restrictinfo in this case, because we don't try to\n\t * fold the restrictinfos after evaluating the mutable functions.\n\t * We have to check this separately from the subsequent predicate_refuted_by.\n\t * That function can also work with the normal CHECK constraints, and they\n\t * don't fail if the constraint evaluates to null given the restriction info.\n\t * That's why it has to prove that the CHECK constraint evaluates to false,\n\t * and this doesn't follow from having a const null restrictinfo.\n\t */\n\tListCell *lc;\n\tforeach (lc, baserestrictinfo)\n\t{\n\t\tRestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);\n\t\tExpr *clause = rinfo->clause;\n\n\t\tif (clause && IsA(clause, Const) &&\n\t\t\t(((Const *) clause)->constisnull || !DatumGetBool(((Const *) clause)->constvalue)))\n\t\t\treturn true;\n\t}\n\n\t/*\n\t * The constraints are effectively ANDed together, so we can just try to\n\t * refute the entire collection at once.  This may allow us to make proofs\n\t * that would fail if we took them individually.\n\t *\n\t * Note: we use rel->baserestrictinfo, not safe_restrictions as might seem\n\t * an obvious optimization.  Some of the clauses might be OR clauses that\n\t * have volatile and nonvolatile subclauses, and it's OK to make\n\t * deductions with the nonvolatile parts.\n\t *\n\t * We need strong refutation because we have to prove that the constraints\n\t * would yield false, not just NULL.\n\t */\n\tif (predicate_refuted_by(constraints, baserestrictinfo, false))\n\t\treturn true;\n\n\treturn false;\n}\n\n/*\n * Fetch the constraints for a relation and adjust range table indexes\n * if necessary.\n */\nstatic void\ninitialize_constraints(ChunkAppendState *state, List *initial_rt_indexes)\n{\n\tListCell *lc_clauses, *lc_plan, *lc_relid;\n\tList *constraints = NIL;\n\tEState *estate = state->csstate.ss.ps.state;\n\n\tif (initial_rt_indexes == NIL)\n\t\treturn;\n\n\tAssert(list_length(state->initial_subplans) == list_length(state->initial_ri_clauses));\n\tAssert(list_length(state->initial_subplans) == list_length(initial_rt_indexes));\n\n\tforthree (lc_plan,\n\t\t\t  state->initial_subplans,\n\t\t\t  lc_clauses,\n\t\t\t  state->initial_ri_clauses,\n\t\t\t  lc_relid,\n\t\t\t  initial_rt_indexes)\n\t{\n\t\tScan *scan = ts_chunk_append_get_scan_plan(lfirst(lc_plan));\n\t\tIndex initial_index = lfirst_oid(lc_relid);\n\t\tList *relation_constraints = NIL;\n\n\t\tif (scan != NULL && scan->scanrelid > 0)\n\t\t{\n\t\t\tIndex rt_index = scan->scanrelid;\n\t\t\tRangeTblEntry *rte = rt_fetch(rt_index, estate->es_range_table);\n\t\t\trelation_constraints = ca_get_relation_constraints(rte->relid, rt_index, true);\n\n\t\t\t/*\n\t\t\t * Adjust the RangeTableEntry indexes in the restrictinfo\n\t\t\t * clauses because during planning subquery indexes may be\n\t\t\t * different from the final index after flattening.\n\t\t\t */\n\t\t\tif (rt_index != initial_index)\n\t\t\t\tChangeVarNodes(lfirst(lc_clauses), initial_index, scan->scanrelid, 0);\n\t\t}\n\t\tconstraints = lappend(constraints, relation_constraints);\n\t}\n\tstate->initial_constraints = constraints;\n\tstate->filtered_constraints = constraints;\n}\n\n/*\n * Output additional information for EXPLAIN of a custom-scan plan node.\n * This callback is optional. Common data stored in the ScanState,\n * such as the target list and scan relation, will be shown even without\n * this callback, but the callback allows the display of additional,\n * private state.\n */\nstatic void\nchunk_append_explain(CustomScanState *node, List *ancestors, ExplainState *es)\n{\n\tChunkAppendState *state = (ChunkAppendState *) node;\n\n\tif (state->sort_options != NIL)\n\t\tshow_sort_group_keys(state, ancestors, es);\n\n\tif (es->verbose || es->format != EXPLAIN_FORMAT_TEXT)\n\t\tExplainPropertyBool(\"Startup Exclusion\", state->startup_exclusion, es);\n\n\tif (es->verbose || es->format != EXPLAIN_FORMAT_TEXT)\n\t\tExplainPropertyBool(\"Runtime Exclusion\",\n\t\t\t\t\t\t\t(state->runtime_exclusion_parent || state->runtime_exclusion_children),\n\t\t\t\t\t\t\tes);\n\n\tif (state->startup_exclusion)\n\t\tExplainPropertyInteger(\"Chunks excluded during startup\",\n\t\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t\t   list_length(state->initial_subplans) - list_length(node->custom_ps),\n\t\t\t\t\t\t\t   es);\n\n\tif (state->runtime_exclusion_parent && state->runtime_number_loops > 0)\n\t{\n\t\tint avg_excluded = state->runtime_number_exclusions_parent / state->runtime_number_loops;\n\t\tExplainPropertyInteger(\"Hypertables excluded during runtime\", NULL, avg_excluded, es);\n\t}\n\n\tif (state->runtime_exclusion_children && state->runtime_number_loops > 0)\n\t{\n\t\tint avg_excluded = state->runtime_number_exclusions_children / state->runtime_number_loops;\n\t\tExplainPropertyInteger(\"Chunks excluded during runtime\", NULL, avg_excluded, es);\n\t}\n}\n\n/*\n * adjusted from postgresql explain.c\n * since we have to keep the state in custom_private our sort state\n * is in lists instead of arrays\n */\nstatic void\nshow_sort_group_keys(ChunkAppendState *state, List *ancestors, ExplainState *es)\n{\n\tPlan *plan = state->csstate.ss.ps.plan;\n\tList *context;\n\tList *result = NIL;\n\tStringInfoData sortkeybuf;\n\tbool useprefix;\n\tint keyno;\n\tint nkeys = list_length(linitial(state->sort_options));\n\tList *sort_indexes = linitial(state->sort_options);\n\tList *sort_ops = lsecond(state->sort_options);\n\tList *sort_collations = lthird(state->sort_options);\n\tList *sort_nulls = lfourth(state->sort_options);\n\n\tif (nkeys <= 0)\n\t\treturn;\n\n\tinitStringInfo(&sortkeybuf);\n\n\t/* Set up deparsing context */\n\tcontext = set_deparse_context_plan(es->deparse_cxt, plan, ancestors);\n\tuseprefix = (list_length(es->rtable) > 1 || es->verbose);\n\n\tfor (keyno = 0; keyno < nkeys; keyno++)\n\t{\n\t\t/* find key expression in tlist */\n\t\tAttrNumber keyresno = list_nth_oid(sort_indexes, keyno);\n\t\tTargetEntry *target =\n\t\t\tget_tle_by_resno(castNode(CustomScan, plan)->custom_scan_tlist, keyresno);\n\t\tchar *exprstr;\n\n\t\tif (!target)\n\t\t\telog(ERROR, \"no tlist entry for key %d\", keyresno);\n\t\t/* Deparse the expression, showing any top-level cast */\n\t\texprstr = deparse_expression((Node *) target->expr, context, useprefix, true);\n\t\tresetStringInfo(&sortkeybuf);\n\t\tappendStringInfoString(&sortkeybuf, exprstr);\n\t\t/* Append sort order information, if relevant */\n\t\tif (sort_ops != NIL)\n\t\t\tshow_sortorder_options(&sortkeybuf,\n\t\t\t\t\t\t\t\t   (Node *) target->expr,\n\t\t\t\t\t\t\t\t   list_nth_oid(sort_ops, keyno),\n\t\t\t\t\t\t\t\t   list_nth_oid(sort_collations, keyno),\n\t\t\t\t\t\t\t\t   list_nth_oid(sort_nulls, keyno));\n\t\t/* Emit one property-list item per sort key */\n\t\tresult = lappend(result, pstrdup(sortkeybuf.data));\n\t}\n\n\tExplainPropertyList(\"Order\", result, es);\n}\n\n/* copied verbatim from postgresql explain.c */\nstatic void\nshow_sortorder_options(StringInfo buf, Node *sortexpr, Oid sortOperator, Oid collation,\n\t\t\t\t\t   bool nullsFirst)\n{\n\tOid sortcoltype = exprType(sortexpr);\n\tbool reverse = false;\n\tTypeCacheEntry *typentry;\n\n\ttypentry = lookup_type_cache(sortcoltype, TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);\n\n\t/*\n\t * Print COLLATE if it's not default.  There are some cases where this is\n\t * redundant, eg if expression is a column whose declared collation is\n\t * that collation, but it's hard to distinguish that here.\n\t */\n\tif (OidIsValid(collation) && collation != DEFAULT_COLLATION_OID)\n\t{\n\t\tchar *collname = get_collation_name(collation);\n\n\t\tif (collname == NULL)\n\t\t\telog(ERROR, \"cache lookup failed for collation %u\", collation);\n\t\tappendStringInfo(buf, \" COLLATE %s\", quote_identifier(collname));\n\t}\n\n\t/* Print direction if not ASC, or USING if non-default sort operator */\n\tif (sortOperator == typentry->gt_opr)\n\t{\n\t\tappendStringInfoString(buf, \" DESC\");\n\t\treverse = true;\n\t}\n\telse if (sortOperator != typentry->lt_opr)\n\t{\n\t\tchar *opname = get_opname(sortOperator);\n\n\t\tif (opname == NULL)\n\t\t\telog(ERROR, \"cache lookup failed for operator %u\", sortOperator);\n\t\tappendStringInfo(buf, \" USING %s\", opname);\n\t\t/* Determine whether operator would be considered ASC or DESC */\n\t\t(void) get_equality_op_for_ordering_op(sortOperator, &reverse);\n\t}\n\n\t/* Add NULLS FIRST/LAST only if it wouldn't be default */\n\tif (nullsFirst && !reverse)\n\t{\n\t\tappendStringInfoString(buf, \" NULLS FIRST\");\n\t}\n\telse if (!nullsFirst && reverse)\n\t{\n\t\tappendStringInfoString(buf, \" NULLS LAST\");\n\t}\n}\n"
  },
  {
    "path": "src/nodes/chunk_append/planner.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <catalog/pg_namespace.h>\n#include <nodes/extensible.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodeFuncs.h>\n#include <optimizer/appendinfo.h>\n#include <optimizer/optimizer.h>\n#include <optimizer/pathnode.h>\n#include <optimizer/paths.h>\n#include <optimizer/placeholder.h>\n#include <optimizer/planmain.h>\n#include <optimizer/prep.h>\n#include <optimizer/subselect.h>\n#include <optimizer/tlist.h>\n#include <parser/parsetree.h>\n\n#include \"guc.h\"\n#include \"import/planner.h\"\n#include \"nodes/chunk_append/chunk_append.h\"\n#include \"nodes/chunk_append/transform.h\"\n#include \"nodes/modify_hypertable.h\"\n#include \"nodes/vector_agg.h\"\n\nstatic Sort *make_sort(Plan *lefttree, int numCols, AttrNumber *sortColIdx, Oid *sortOperators,\n\t\t\t\t\t   Oid *collations, bool *nullsFirst);\nstatic Plan *adjust_childscan(PlannerInfo *root, Plan *plan, Path *path, List *pathkeys,\n\t\t\t\t\t\t\t  List *tlist, AttrNumber *sortColIdx);\n\nstatic CustomScanMethods chunk_append_plan_methods = {\n\t.CustomName = \"ChunkAppend\",\n\t.CreateCustomScanState = ts_chunk_append_state_create,\n};\n\nbool\nts_is_chunk_append_plan(Plan *plan)\n{\n\treturn IsA(plan, CustomScan) &&\n\t\t   castNode(CustomScan, plan)->methods == &chunk_append_plan_methods;\n}\n\nvoid\n_chunk_append_init(void)\n{\n\tTryRegisterCustomScanMethods(&chunk_append_plan_methods);\n}\n\nstatic Plan *\nadjust_childscan(PlannerInfo *root, Plan *plan, Path *path, List *pathkeys, List *tlist,\n\t\t\t\t AttrNumber *sortColIdx)\n{\n\tint childSortCols;\n\tOid *sortOperators;\n\tOid *collations;\n\tbool *nullsFirst;\n\tAttrNumber *childColIdx;\n\n\t/* Compute sort column info, and adjust subplan's tlist as needed */\n\tplan = ts_prepare_sort_from_pathkeys(plan,\n\t\t\t\t\t\t\t\t\t\t pathkeys,\n\t\t\t\t\t\t\t\t\t\t path->parent->relids,\n\t\t\t\t\t\t\t\t\t\t sortColIdx,\n\t\t\t\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t\t\t\t &childSortCols,\n\t\t\t\t\t\t\t\t\t\t &childColIdx,\n\t\t\t\t\t\t\t\t\t\t &sortOperators,\n\t\t\t\t\t\t\t\t\t\t &collations,\n\t\t\t\t\t\t\t\t\t\t &nullsFirst);\n\n\t/* inject sort node if child sort order does not match desired order */\n\tif (!pathkeys_contained_in(pathkeys, path->pathkeys))\n\t{\n\t\tAssert(!IsA(plan, Sort));\n\n\t\tplan = (Plan *)\n\t\t\tmake_sort(plan, childSortCols, childColIdx, sortOperators, collations, nullsFirst);\n\t}\n\treturn plan;\n}\n\nPlan *\nts_chunk_append_plan_create(PlannerInfo *root, RelOptInfo *rel, CustomPath *path, List *tlist,\n\t\t\t\t\t\t\tList *clauses, List *custom_plans)\n{\n\tListCell *lc_child;\n\tList *parent_clauses = NIL;\n\tList *chunk_ri_clauses = NIL;\n\tList *chunk_rt_indexes = NIL;\n\tList *sort_options = NIL;\n\tList *custom_private = NIL;\n\tuint32 limit = 0;\n\tList *orig_tlist = NIL;\n\n\tChunkAppendPath *capath = (ChunkAppendPath *) path;\n\tCustomScan *cscan = makeNode(CustomScan);\n\n\tcscan->flags = path->flags;\n\tcscan->methods = &chunk_append_plan_methods;\n\tcscan->scan.scanrelid = rel->relid;\n\n\torig_tlist = ts_build_path_tlist(root, (Path *) path);\n\ttlist = orig_tlist;\n\n\t/*\n\t * If this is a child of ModifyHypertable we need to adjust\n\t * targetlists to not have any ROWID_VAR references as postgres\n\t * asserts that scan targetlists do not have them in setrefs.c\n\t *\n\t * We keep orig_tlist unaltered to let adjust_appendrel_attrs()\n\t * replace ROWID_VARs for chunks' targetlists (it would assert\n\t * trying to modify a \"wholerow\" target entry that has already\n\t * been adjusted by ts_replace_rowid_vars(); we see these in\n\t * foreign tables).\n\t */\n\tif (root->parse->commandType != CMD_SELECT)\n\t\ttlist = ts_replace_rowid_vars(root, tlist, rel->relid);\n\n\tcscan->scan.plan.targetlist = tlist;\n\n\t/*\n\t * For parameterized paths (e.g., LATERAL joins), transform outer-relation\n\t * Vars to NestLoop Params. We store clauses in custom_private rather than\n\t * custom_exprs because chunk_ri_clauses reference chunk relations (after\n\t * adjust_appendrel_attrs), but setrefs.c processing of custom_exprs expects\n\t * Vars to reference the parent relation (scanrelid). Since custom_private\n\t * bypasses setrefs, we must transform outer Vars to Params ourselves.\n\t */\n\tif (path->path.param_info && root->curOuterRels)\n\t{\n\t\tList *transformed_clauses = NIL;\n\t\tListCell *lc;\n\t\tforeach (lc, clauses)\n\t\t{\n\t\t\tRestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);\n\t\t\tRestrictInfo *newrinfo = makeNode(RestrictInfo);\n\n\t\t\tmemcpy(newrinfo, rinfo, sizeof(RestrictInfo));\n\t\t\tnewrinfo->clause = (Expr *) ts_replace_nestloop_params(root, (Node *) rinfo->clause);\n\t\t\ttransformed_clauses = lappend(transformed_clauses, newrinfo);\n\t\t}\n\t\tclauses = transformed_clauses;\n\t}\n\n\tListCell *lc_plan, *lc_path;\n\tforboth (lc_path, path->custom_paths, lc_plan, custom_plans)\n\t{\n\t\tPlan *child_plan = lfirst(lc_plan);\n\t\tPath *child_path = lfirst(lc_path);\n\n\t\t/* push down targetlist to children */\n\t\tif (child_path->parent->reloptkind == RELOPT_OTHER_MEMBER_REL)\n\t\t{\n\t\t\t/* if this is an append child we need to adjust targetlist references */\n\t\t\tAppendRelInfo *appinfo = ts_get_appendrelinfo(root, child_path->parent->relid, false);\n\n\t\t\tchild_plan->targetlist =\n\t\t\t\tcastNode(List, adjust_appendrel_attrs(root, (Node *) orig_tlist, 1, &appinfo));\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/*\n\t\t\t * This can also be a MergeAppend path building the entire\n\t\t\t * hypertable, in case we have a single partial chunk.\n\t\t\t */\n\t\t\tchild_plan->targetlist = tlist;\n\t\t}\n\t}\n\n\tif (path->path.pathkeys != NIL)\n\t{\n\t\t/*\n\t\t * If this is an ordered append node we need to ensure the columns\n\t\t * required for sorting are present in the targetlist and all children\n\t\t * return sorted output. Children not returning sorted output will be\n\t\t * wrapped in a sort node.\n\t\t */\n\t\tint numCols;\n\t\tAttrNumber *sortColIdx;\n\t\tOid *sortOperators;\n\t\tOid *collations;\n\t\tbool *nullsFirst;\n\t\tList *pathkeys = path->path.pathkeys;\n\t\tList *sort_indexes = NIL;\n\t\tList *sort_ops = NIL;\n\t\tList *sort_collations = NIL;\n\t\tList *sort_nulls = NIL;\n\t\tint i;\n\n\t\t/* Compute sort column info, and adjust MergeAppend's tlist as needed */\n\t\tts_prepare_sort_from_pathkeys(&cscan->scan.plan,\n\t\t\t\t\t\t\t\t\t  pathkeys,\n\t\t\t\t\t\t\t\t\t  path->path.parent->relids,\n\t\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t\t  true,\n\t\t\t\t\t\t\t\t\t  &numCols,\n\t\t\t\t\t\t\t\t\t  &sortColIdx,\n\t\t\t\t\t\t\t\t\t  &sortOperators,\n\t\t\t\t\t\t\t\t\t  &collations,\n\t\t\t\t\t\t\t\t\t  &nullsFirst);\n\n\t\t/*\n\t\t * collect sort information to make available to explain\n\t\t */\n\t\tfor (i = 0; i < numCols; i++)\n\t\t{\n\t\t\tsort_indexes = lappend_oid(sort_indexes, sortColIdx[i]);\n\t\t\tsort_ops = lappend_oid(sort_ops, sortOperators[i]);\n\t\t\tsort_collations = lappend_oid(sort_collations, collations[i]);\n\t\t\tsort_nulls = lappend_oid(sort_nulls, nullsFirst[i]);\n\t\t}\n\n\t\tsort_options = list_make4(sort_indexes, sort_ops, sort_collations, sort_nulls);\n\n\t\tforboth (lc_path, path->custom_paths, lc_plan, custom_plans)\n\t\t{\n\t\t\t/*\n\t\t\t * If the planner injected a Result node to do projection\n\t\t\t * we can safely remove the Result node if it does not have\n\t\t\t * a one-time filter because ChunkAppend can do projection.\n\t\t\t */\n\t\t\tif (IsA(lfirst(lc_plan), Result) &&\n\t\t\t\tcastNode(Result, lfirst(lc_plan))->resconstantqual == NULL)\n\t\t\t\tlfirst(lc_plan) = ((Plan *) lfirst(lc_plan))->lefttree;\n\n\t\t\t/*\n\t\t\t * This could be a MergeAppend due to space partitioning, or\n\t\t\t * due to partially compressed chunks. The MergeAppend plan adds\n\t\t\t * sort to it children, and has the proper sorting itself, so no\n\t\t\t * need to do anything for it.\n\t\t\t * We can also have plain chunk scans here which might require a\n\t\t\t * Sort.\n\t\t\t */\n\t\t\tif (!IsA(lfirst(lc_plan), MergeAppend))\n\t\t\t{\n\t\t\t\tlfirst(lc_plan) = adjust_childscan(root,\n\t\t\t\t\t\t\t\t\t\t\t\t   lfirst(lc_plan),\n\t\t\t\t\t\t\t\t\t\t\t\t   lfirst(lc_path),\n\t\t\t\t\t\t\t\t\t\t\t\t   path->path.pathkeys,\n\t\t\t\t\t\t\t\t\t\t\t\t   orig_tlist,\n\t\t\t\t\t\t\t\t\t\t\t\t   sortColIdx);\n\t\t\t}\n\t\t}\n\t}\n\n\t/* decouple input tlist from output tlist in case output tlist gets modified later */\n\tcscan->custom_scan_tlist = list_copy(tlist);\n\tcscan->custom_plans = custom_plans;\n\n\t/*\n\t * If we do either startup or runtime exclusion, we need to pass restrictinfo\n\t * clauses into executor.\n\t */\n\tif (capath->startup_exclusion || capath->runtime_exclusion_children)\n\t{\n\t\tforeach (lc_child, cscan->custom_plans)\n\t\t{\n\t\t\tScan *scan = ts_chunk_append_get_scan_plan(lfirst(lc_child));\n\n\t\t\tif (scan == NULL || scan->scanrelid == 0)\n\t\t\t{\n\t\t\t\tchunk_ri_clauses = lappend(chunk_ri_clauses, NIL);\n\t\t\t\tchunk_rt_indexes = lappend_oid(chunk_rt_indexes, 0);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tList *chunk_clauses = NIL;\n\t\t\t\tListCell *lc;\n\t\t\t\tAppendRelInfo *appinfo = ts_get_appendrelinfo(root, scan->scanrelid, false);\n\n\t\t\t\tforeach (lc, clauses)\n\t\t\t\t{\n\t\t\t\t\tNode *clause = (Node *) ts_transform_cross_datatype_comparison(\n\t\t\t\t\t\tcastNode(RestrictInfo, lfirst(lc))->clause);\n\t\t\t\t\tclause = adjust_appendrel_attrs(root, clause, 1, &appinfo);\n\t\t\t\t\tchunk_clauses = lappend(chunk_clauses, clause);\n\t\t\t\t}\n\t\t\t\tchunk_ri_clauses = lappend(chunk_ri_clauses, chunk_clauses);\n\t\t\t\tchunk_rt_indexes = lappend_oid(chunk_rt_indexes, scan->scanrelid);\n\t\t\t}\n\t\t}\n\n\t\tAssert(list_length(cscan->custom_plans) == list_length(chunk_ri_clauses));\n\t\tAssert(list_length(chunk_ri_clauses) == list_length(chunk_rt_indexes));\n\t}\n\n\t/* pass down the parent clauses if doing parent exclusion */\n\tif (capath->runtime_exclusion_parent)\n\t{\n\t\tListCell *lc;\n\t\tforeach (lc, clauses)\n\t\t{\n\t\t\tparent_clauses = lappend(parent_clauses, castNode(RestrictInfo, lfirst(lc))->clause);\n\t\t}\n\t}\n\n\tif (capath->pushdown_limit && capath->limit_tuples > 0)\n\t\tlimit = capath->limit_tuples;\n\n\tcustom_private = list_make1(list_make5_int(capath->startup_exclusion,\n\t\t\t\t\t\t\t\t\t\t\t   capath->runtime_exclusion_parent,\n\t\t\t\t\t\t\t\t\t\t\t   capath->runtime_exclusion_children,\n\t\t\t\t\t\t\t\t\t\t\t   limit,\n\t\t\t\t\t\t\t\t\t\t\t   capath->first_partial_path));\n\tcustom_private = lappend(custom_private, chunk_ri_clauses);\n\tcustom_private = lappend(custom_private, chunk_rt_indexes);\n\tcustom_private = lappend(custom_private, sort_options);\n\tcustom_private = lappend(custom_private, parent_clauses);\n\n\tcscan->custom_private = custom_private;\n\n\treturn &cscan->scan.plan;\n}\n\n/*\n * make_sort --- basic routine to build a Sort plan node\n *\n * Caller must have built the sortColIdx, sortOperators, collations, and\n * nullsFirst arrays already.\n */\nstatic Sort *\nmake_sort(Plan *lefttree, int numCols, AttrNumber *sortColIdx, Oid *sortOperators, Oid *collations,\n\t\t  bool *nullsFirst)\n{\n\tSort *node = makeNode(Sort);\n\tPlan *plan = &node->plan;\n\n\tplan->targetlist = lefttree->targetlist;\n\tplan->qual = NIL;\n\tplan->lefttree = lefttree;\n\tplan->righttree = NULL;\n\tnode->numCols = numCols;\n\tnode->sortColIdx = sortColIdx;\n\tnode->sortOperators = sortOperators;\n\tnode->collations = collations;\n\tnode->nullsFirst = nullsFirst;\n\n\treturn node;\n}\n\nScan *\nts_chunk_append_get_scan_plan(Plan *plan)\n{\n\tif (plan == NULL)\n\t\treturn NULL;\n\n\tswitch (nodeTag(plan))\n\t{\n\t\tcase T_BitmapHeapScan:\n\t\tcase T_BitmapIndexScan:\n\t\tcase T_CteScan:\n\t\tcase T_ForeignScan:\n\t\tcase T_FunctionScan:\n\t\tcase T_IndexOnlyScan:\n\t\tcase T_IndexScan:\n\t\tcase T_SampleScan:\n\t\tcase T_SeqScan:\n\t\tcase T_SubqueryScan:\n\t\tcase T_TidScan:\n\t\tcase T_ValuesScan:\n\t\tcase T_WorkTableScan:\n\t\tcase T_TidRangeScan:\n\t\t\treturn (Scan *) plan;\n\t\tcase T_CustomScan:\n\t\t{\n\t\t\tCustomScan *custom = castNode(CustomScan, plan);\n\t\t\tif (custom->scan.scanrelid > 0)\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * The custom plan node is a scan itself. This handles the\n\t\t\t\t * ColumnarScan node.\n\t\t\t\t */\n\t\t\t\treturn (Scan *) plan;\n\t\t\t}\n\n\t\t\tif (strcmp(custom->methods->CustomName, VECTOR_AGG_NODE_NAME) == 0)\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * This is a vectorized aggregation node, we have to recurse\n\t\t\t\t * into its child, similar to the normal aggregation node.\n\t\t\t\t *\n\t\t\t\t * Unfortunately we have to hardcode the node name here, because\n\t\t\t\t * we can't depend on the TSL library.\n\t\t\t\t */\n\t\t\t\treturn ts_chunk_append_get_scan_plan(linitial(custom->custom_plans));\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase T_Sort:\n\t\tcase T_Result:\n\t\tcase T_Agg:\n\t\t\tif (plan->lefttree != NULL)\n\t\t\t{\n\t\t\t\tAssert(plan->righttree == NULL);\n\t\t\t\t/* Let ts_chunk_append_get_scan_plan handle the subplan */\n\t\t\t\treturn ts_chunk_append_get_scan_plan(plan->lefttree);\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\n\treturn NULL;\n}\n"
  },
  {
    "path": "src/nodes/chunk_append/transform.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <catalog/pg_namespace.h>\n#include <catalog/pg_type.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodeFuncs.h>\n#include <optimizer/optimizer.h>\n#include <utils/lsyscache.h>\n\n#include \"nodes/chunk_append/transform.h\"\n#include \"utils.h\"\n\n#define DATATYPE_PAIR(left, right, type1, type2)                                                   \\\n\t(((left) == (type1) && (right) == (type2)) || ((left) == (type2) && (right) == (type1)))\n\n/*\n * Cross datatype comparisons between DATE/TIMESTAMP/TIMESTAMPTZ\n * are not immutable which prevents their usage for chunk exclusion.\n * Unfortunately estimate_expression_value will not estimate those\n * expressions which makes them unusable for execution time chunk\n * exclusion with constraint aware append.\n * To circumvent this we inject casts and use an operator\n * with the same datatype on both sides when constifying\n * restrictinfo. This allows estimate_expression_value\n * to evaluate those expressions and makes them accessible for\n * execution time chunk exclusion.\n *\n * The following transformations are done:\n * TIMESTAMP OP TIMESTAMPTZ => TIMESTAMP OP (TIMESTAMPTZ::TIMESTAMP)\n * TIMESTAMPTZ OP DATE => TIMESTAMPTZ OP (DATE::TIMESTAMPTZ)\n *\n * No transformation is required for TIMESTAMP OP DATE because\n * those operators are marked immutable.\n */\nExpr *\nts_transform_cross_datatype_comparison(Expr *clause)\n{\n\tif (!IsA(clause, OpExpr) || list_length(castNode(OpExpr, clause)->args) != 2)\n\t{\n\t\treturn clause;\n\t}\n\n\tOpExpr *op = castNode(OpExpr, clause);\n\tOid left_type = exprType(linitial(op->args));\n\tOid right_type = exprType(lsecond(op->args));\n\n\t/*\n\t * Postgres doesn't allow non-bool or set returning functions in the WHERE\n\t * clause.\n\t */\n\tAssert(op->opresulttype == BOOLOID && !op->opretset);\n\n\tif (!IsA(linitial(op->args), Var) && !IsA(lsecond(op->args), Var))\n\t\treturn clause;\n\n\tif (DATATYPE_PAIR(left_type, right_type, TIMESTAMPOID, TIMESTAMPTZOID) ||\n\t\tDATATYPE_PAIR(left_type, right_type, TIMESTAMPTZOID, DATEOID))\n\t{\n\t\tchar *opname = get_opname(op->opno);\n\t\tOid source_type, target_type, opno, cast_oid;\n\n\t\t/*\n\t\t * if Var is on left side we put cast on right side otherwise\n\t\t * it will be left\n\t\t */\n\t\tif (IsA(linitial(op->args), Var))\n\t\t{\n\t\t\tsource_type = right_type;\n\t\t\ttarget_type = left_type;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tsource_type = left_type;\n\t\t\ttarget_type = right_type;\n\t\t}\n\n\t\topno = ts_get_operator(opname, PG_CATALOG_NAMESPACE, target_type, target_type);\n\t\tcast_oid = ts_get_cast_func(source_type, target_type);\n\n\t\tif (OidIsValid(opno) && OidIsValid(cast_oid))\n\t\t{\n\t\t\tExpr *left = copyObject(linitial(op->args));\n\t\t\tExpr *right = copyObject(lsecond(op->args));\n\n\t\t\tif (source_type == left_type)\n\t\t\t\tleft = (Expr *) makeFuncExpr(cast_oid,\n\t\t\t\t\t\t\t\t\t\t\t target_type,\n\t\t\t\t\t\t\t\t\t\t\t list_make1(left),\n\t\t\t\t\t\t\t\t\t\t\t InvalidOid,\n\t\t\t\t\t\t\t\t\t\t\t InvalidOid,\n\t\t\t\t\t\t\t\t\t\t\t 0);\n\t\t\telse\n\t\t\t\tright = (Expr *) makeFuncExpr(cast_oid,\n\t\t\t\t\t\t\t\t\t\t\t  target_type,\n\t\t\t\t\t\t\t\t\t\t\t  list_make1(right),\n\t\t\t\t\t\t\t\t\t\t\t  InvalidOid,\n\t\t\t\t\t\t\t\t\t\t\t  InvalidOid,\n\t\t\t\t\t\t\t\t\t\t\t  0);\n\n\t\t\treturn make_opclause(opno, BOOLOID, false, left, right, InvalidOid, InvalidOid);\n\t\t}\n\t}\n\treturn clause;\n}\n"
  },
  {
    "path": "src/nodes/chunk_append/transform.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <nodes/extensible.h>\n\n#include \"export.h\"\n\nextern TSDLLEXPORT Expr *ts_transform_cross_datatype_comparison(Expr *clause);\n"
  },
  {
    "path": "src/nodes/constraint_aware_append/CMakeLists.txt",
    "content": "set(SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/constraint_aware_append.c)\ntarget_sources(${PROJECT_NAME} PRIVATE ${SOURCES})\n"
  },
  {
    "path": "src/nodes/constraint_aware_append/constraint_aware_append.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <catalog/pg_cast.h>\n#include <catalog/pg_class.h>\n#include <catalog/pg_namespace.h>\n#include <catalog/pg_operator.h>\n#include <commands/explain.h>\n#include <executor/executor.h>\n#include <nodes/extensible.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodeFuncs.h>\n#include <nodes/nodes.h>\n#include <nodes/parsenodes.h>\n#include <nodes/plannodes.h>\n#include <optimizer/appendinfo.h>\n#include <optimizer/cost.h>\n#include <optimizer/optimizer.h>\n#include <optimizer/plancat.h>\n#include <parser/parsetree.h>\n#include <rewrite/rewriteManip.h>\n#include <storage/lockdefs.h>\n#include <utils/lsyscache.h>\n#include <utils/memutils.h>\n#include <utils/syscache.h>\n\n#include \"compat/compat.h\"\n#include \"constraint_aware_append.h\"\n#include \"debug_assert.h\"\n#include \"guc.h\"\n#include \"nodes/chunk_append/transform.h\"\n#include \"utils.h\"\n\n#if PG18_GE\n#include <commands/explain_format.h>\n#endif\n\n/*\n * Exclude child relations (chunks) at execution time based on constraints.\n *\n * This functions tries to reuse as much functionality as possible from standard\n * constraint exclusion in PostgreSQL that normally happens at planning\n * time. Therefore, we need to fake a number of planning-related data\n * structures.\n */\nstatic bool\nexcluded_by_constraint(PlannerInfo *root, RangeTblEntry *rte, Index rt_index, List *restrictinfos)\n{\n\tRelOptInfo rel = {\n\t\t.type = T_RelOptInfo,\n\t\t.relid = rt_index,\n\t\t.reloptkind = RELOPT_OTHER_MEMBER_REL,\n\t\t.baserestrictinfo = restrictinfos,\n\t};\n\n\treturn relation_excluded_by_constraints(root, &rel, rte);\n}\n\nstatic Plan *\nget_plans_for_exclusion(Plan *plan)\n{\n\t/* Optimization: If we want to be able to prune */\n\t/* when the node is a T_Result or T_Sort, then we need to peek */\n\t/* into the subplans of this Result node. */\n\n\tswitch (nodeTag(plan))\n\t{\n\t\tcase T_Result:\n\t\tcase T_Sort:\n\t\t\tEnsure(plan->lefttree != NULL, \"subplan is null\");\n\t\t\treturn get_plans_for_exclusion(plan->lefttree);\n\n\t\tdefault:\n\t\t\treturn plan;\n\t}\n}\n\nstatic bool\ncan_exclude_chunk(PlannerInfo *root, RangeTblEntry *rte, Index rt_index, List *restrictinfos)\n{\n\treturn rte->rtekind == RTE_RELATION && rte->relkind == RELKIND_RELATION && !rte->inh &&\n\t\t   excluded_by_constraint(root, rte, rt_index, restrictinfos);\n}\n\n/*\n * Convert restriction clauses to constants expressions (i.e., if there are\n * mutable functions, they need to be evaluated to constants).  For instance,\n * something like:\n *\n * ...WHERE time > now - interval '1 hour'\n *\n * becomes\n *\n * ...WHERE time > '2017-06-02 11:26:43.935712+02'\n */\nstatic List *\nconstify_restrictinfos(PlannerInfo *root, List *restrictinfos)\n{\n\tListCell *lc;\n\n\tforeach (lc, restrictinfos)\n\t{\n\t\tRestrictInfo *rinfo = lfirst(lc);\n\n\t\trinfo->clause = (Expr *) estimate_expression_value(root, (Node *) rinfo->clause);\n\t}\n\n\treturn restrictinfos;\n}\n\n/*\n * Initialize the scan state and prune any subplans from the Append node below\n * us in the plan tree. Pruning happens by evaluating the subplan's table\n * constraints against a folded version of the restriction clauses in the query.\n */\nstatic void\nca_append_begin(CustomScanState *node, EState *estate, int eflags)\n{\n\tConstraintAwareAppendState *state = (ConstraintAwareAppendState *) node;\n\tCustomScan *cscan = (CustomScan *) node->ss.ps.plan;\n\tPlan *subplan = copyObject(state->subplan);\n\tList *chunk_ri_clauses = lsecond(cscan->custom_private);\n\tList *chunk_relids = lthird(cscan->custom_private);\n\tList **appendplans, *old_appendplans;\n\tListCell *lc_plan;\n\tListCell *lc_clauses;\n\tListCell *lc_relid;\n\n\t/*\n\t * create skeleton plannerinfo to reuse some PostgreSQL planner functions\n\t */\n\tQuery parse = {\n\t\t.resultRelation = InvalidOid,\n\t};\n\tPlannerGlobal glob = {\n\t\t.boundParams = NULL,\n\t};\n\tPlannerInfo root = {\n\t\t.glob = &glob,\n\t\t.parse = &parse,\n\t};\n\n\t/* CustomScan hard-codes the scan and result tuple slot to a fixed\n\t * TTSOpsVirtual ops (meaning it expects the slot ops of the child tuple to\n\t * also have this type). Oddly, when reading slots from subscan nodes\n\t * (children), there is no knowing what tuple slot ops the child slot will\n\t * have (e.g., for ChunkAppend it is common that the child is a\n\t * seqscan/indexscan that produces a TTSOpsBufferHeapTuple\n\t * slot). Unfortunately, any mismatch between slot types when projecting is\n\t * asserted by PostgreSQL. To avoid this issue, we mark the scanops as\n\t * non-fixed and reinitialize the projection state with this new setting.\n\t *\n\t * Alternatively, we could copy the child tuple into the scan slot to get\n\t * the expected ops before projection, but this would require materializing\n\t * and copying the tuple unnecessarily.\n\t */\n\tnode->ss.ps.scanopsfixed = false;\n\n\t/* Since we sometimes return the scan slot directly from the subnode, the\n\t * result slot is not fixed either. */\n\tnode->ss.ps.resultopsfixed = false;\n\tExecAssignScanProjectionInfoWithVarno(&node->ss, INDEX_VAR);\n\n\tswitch (nodeTag(subplan))\n\t{\n\t\tcase T_Append:\n\t\t{\n\t\t\tAppend *append = (Append *) subplan;\n\n\t\t\told_appendplans = append->appendplans;\n\t\t\tappend->appendplans = NIL;\n\t\t\tappendplans = &append->appendplans;\n\t\t\tbreak;\n\t\t}\n\t\tcase T_MergeAppend:\n\t\t{\n\t\t\tMergeAppend *append = (MergeAppend *) subplan;\n\n\t\t\told_appendplans = append->mergeplans;\n\t\t\tappend->mergeplans = NIL;\n\t\t\tappendplans = &append->mergeplans;\n\t\t\tbreak;\n\t\t}\n\t\tcase T_Result:\n\n\t\t\t/*\n\t\t\t * Append plans are turned into a Result node if empty. This can\n\t\t\t * happen if children are pruned first by constraint exclusion\n\t\t\t * while we also remove the main table from the appendplans list,\n\t\t\t * leaving an empty list. In that case, there is nothing to do.\n\t\t\t */\n\t\t\treturn;\n\t\tdefault:\n\t\t\telog(ERROR,\n\t\t\t\t \"invalid child of constraint-aware append: %s\",\n\t\t\t\t ts_get_node_name((Node *) subplan));\n\t}\n\n\t/*\n\t * clauses should always have the same length as appendplans because\n\t * that's the base for building the lists\n\t */\n\tAssert(list_length(old_appendplans) == list_length(chunk_ri_clauses));\n\tAssert(list_length(chunk_relids) == list_length(chunk_ri_clauses));\n\n\tforthree (lc_plan, old_appendplans, lc_clauses, chunk_ri_clauses, lc_relid, chunk_relids)\n\t{\n\t\tPlan *plan = get_plans_for_exclusion(lfirst(lc_plan));\n\n\t\tswitch (nodeTag(plan))\n\t\t{\n\t\t\tcase T_SeqScan:\n\t\t\tcase T_SampleScan:\n\t\t\tcase T_IndexScan:\n\t\t\tcase T_IndexOnlyScan:\n\t\t\tcase T_BitmapIndexScan:\n\t\t\tcase T_BitmapHeapScan:\n\t\t\tcase T_TidScan:\n\t\t\tcase T_SubqueryScan:\n\t\t\tcase T_FunctionScan:\n\t\t\tcase T_ValuesScan:\n\t\t\tcase T_CteScan:\n\t\t\tcase T_WorkTableScan:\n\t\t\tcase T_ForeignScan:\n\t\t\tcase T_CustomScan:\n\t\t\tcase T_TidRangeScan:\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * If this is a base rel (chunk), check if it can be\n\t\t\t\t * excluded from the scan. Otherwise, fall through.\n\t\t\t\t */\n\n\t\t\t\tIndex scanrelid = ((Scan *) plan)->scanrelid;\n\t\t\t\tList *restrictinfos = NIL;\n\t\t\t\tList *ri_clauses = lfirst(lc_clauses);\n\t\t\t\tListCell *lc;\n\t\t\t\tRangeTblEntry *rte;\n\n\t\t\t\tAssert(scanrelid);\n\n\t\t\t\tforeach (lc, ri_clauses)\n\t\t\t\t{\n\t\t\t\t\tRestrictInfo *ri = makeNode(RestrictInfo);\n\t\t\t\t\tri->clause = lfirst(lc);\n\n\t\t\t\t\t/*\n\t\t\t\t\t * The index of the RangeTblEntry might have changed between planning\n\t\t\t\t\t * because of flattening, so we need to adjust the expressions\n\t\t\t\t\t * for the RestrictInfos if they are not equal.\n\t\t\t\t\t */\n\t\t\t\t\tif (lfirst_oid(lc_relid) != scanrelid)\n\t\t\t\t\t\tChangeVarNodes((Node *) ri->clause, lfirst_oid(lc_relid), scanrelid, 0);\n\n\t\t\t\t\trestrictinfos = lappend(restrictinfos, ri);\n\t\t\t\t}\n\n\t\t\t\t/*\n\t\t\t\t * The function excluded_by_constraint(), which is called when\n\t\t\t\t * excluding chunks, assumes that a relation is already locked\n\t\t\t\t * when called. In most cases the relation is already locked\n\t\t\t\t * when getting here, but not in case of some parallel scans\n\t\t\t\t * where the parallel worker hasn't locked it yet.\n\t\t\t\t */\n\t\t\t\trte = rt_fetch(scanrelid, estate->es_range_table);\n\t\t\t\tLockRelationOid(rte->relid, AccessShareLock);\n\t\t\t\trestrictinfos = constify_restrictinfos(&root, restrictinfos);\n\n\t\t\t\tif (can_exclude_chunk(&root, rte, scanrelid, restrictinfos))\n\t\t\t\t\tcontinue;\n\n\t\t\t\t*appendplans = lappend(*appendplans, lfirst(lc_plan));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\telog(ERROR,\n\t\t\t\t\t \"invalid child of constraint-aware append: %s\",\n\t\t\t\t\t ts_get_node_name((Node *) plan));\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tstate->num_append_subplans = list_length(*appendplans);\n\tstate->num_chunks_excluded = list_length(old_appendplans) - state->num_append_subplans;\n\tif (state->num_append_subplans > 0)\n\t\tnode->custom_ps = list_make1(ExecInitNode(subplan, estate, eflags));\n}\n\nstatic TupleTableSlot *\nca_append_exec(CustomScanState *node)\n{\n\tConstraintAwareAppendState *state = (ConstraintAwareAppendState *) node;\n\tTupleTableSlot *subslot;\n\tExprContext *econtext = node->ss.ps.ps_ExprContext;\n\n\t/*\n\t * Check if all append subplans were pruned. In that case there is nothing\n\t * to do.\n\t */\n\tif (state->num_append_subplans == 0)\n\t\treturn NULL;\n\n\tResetExprContext(econtext);\n\n\twhile (true)\n\t{\n\t\tsubslot = ExecProcNode(linitial(node->custom_ps));\n\n\t\tif (TupIsNull(subslot))\n\t\t\treturn NULL;\n\n\t\tif (!node->ss.ps.ps_ProjInfo)\n\t\t\treturn subslot;\n\n\t\tecontext->ecxt_scantuple = subslot;\n\n\t\treturn ExecProject(node->ss.ps.ps_ProjInfo);\n\t}\n}\n\nstatic void\nca_append_end(CustomScanState *node)\n{\n\tif (node->custom_ps != NIL)\n\t{\n\t\tExecEndNode(linitial(node->custom_ps));\n\t}\n}\n\nstatic void\nca_append_rescan(CustomScanState *node)\n{\n\tif (node->custom_ps != NIL)\n\t{\n\t\tExecReScan(linitial(node->custom_ps));\n\t}\n}\n\nstatic void\nca_append_explain(CustomScanState *node, List *ancestors, ExplainState *es)\n{\n\tCustomScan *cscan = (CustomScan *) node->ss.ps.plan;\n\tConstraintAwareAppendState *state = (ConstraintAwareAppendState *) node;\n\tOid relid = linitial_oid(linitial(cscan->custom_private));\n\n\tExplainPropertyText(\"Hypertable\", get_rel_name(relid), es);\n\tExplainPropertyInteger(\"Chunks excluded during startup\", NULL, state->num_chunks_excluded, es);\n}\n\nstatic CustomExecMethods constraint_aware_append_state_methods = {\n\t.BeginCustomScan = ca_append_begin,\n\t.ExecCustomScan = ca_append_exec,\n\t.EndCustomScan = ca_append_end,\n\t.ReScanCustomScan = ca_append_rescan,\n\t.ExplainCustomScan = ca_append_explain,\n};\n\nstatic Node *\nconstraint_aware_append_state_create(CustomScan *cscan)\n{\n\tConstraintAwareAppendState *state;\n\tAppend *append = linitial(cscan->custom_plans);\n\n\tstate = (ConstraintAwareAppendState *) newNode(sizeof(ConstraintAwareAppendState),\n\t\t\t\t\t\t\t\t\t\t\t\t   T_CustomScanState);\n\tstate->csstate.methods = &constraint_aware_append_state_methods;\n\tstate->subplan = &append->plan;\n\n\treturn (Node *) state;\n}\n\nstatic CustomScanMethods constraint_aware_append_plan_methods = {\n\t.CustomName = \"ConstraintAwareAppend\",\n\t.CreateCustomScanState = constraint_aware_append_state_create,\n};\n\nstatic Plan *\nconstraint_aware_append_plan_create(PlannerInfo *root, RelOptInfo *rel, CustomPath *path,\n\t\t\t\t\t\t\t\t\tList *tlist, List *clauses, List *custom_plans)\n{\n\tCustomScan *cscan = makeNode(CustomScan);\n\tPlan *subplan;\n\tRangeTblEntry *rte = planner_rt_fetch(rel->relid, root);\n\tList *chunk_ri_clauses = NIL;\n\tList *chunk_relids = NIL;\n\tList *children = NIL;\n\tListCell *lc_child;\n\n\t/*\n\t * Postgres will inject Result nodes above mergeappend when target lists don't match\n\t * because the nodes themselves do not perform projection. The ConstraintAwareAppend\n\t * node can do this projection itself, however, so just throw away the result node\n\t * Removing the Result node is only safe if there is no one-time filter\n\t */\n\tif (IsA(linitial(custom_plans), Result) &&\n\t\tcastNode(Result, linitial(custom_plans))->resconstantqual == NULL)\n\t{\n\t\tResult *result = castNode(Result, linitial(custom_plans));\n\n\t\tif (result->plan.righttree != NULL)\n\t\t\telog(ERROR, \"unexpected right tree below result node in constraint aware append\");\n\n\t\tcustom_plans = list_make1(result->plan.lefttree);\n\t}\n\tsubplan = linitial(custom_plans);\n\n\tcscan->scan.scanrelid = 0;\t\t\t /* Not a real relation we are scanning */\n\tcscan->scan.plan.targetlist = tlist; /* Target list we expect as output */\n\tcscan->custom_plans = custom_plans;\n\n\t/*\n\t * create per chunk RestrictInfo\n\t *\n\t * We also need to walk the expression trees of the restriction clauses and\n\t * update any Vars that reference the main table to instead reference the child\n\t * table (chunk) we want to exclude.\n\t */\n\tswitch (nodeTag(linitial(custom_plans)))\n\t{\n\t\tcase T_MergeAppend:\n\t\t\tchildren = castNode(MergeAppend, linitial(custom_plans))->mergeplans;\n\t\t\tbreak;\n\t\tcase T_Append:\n\t\t\tchildren = castNode(Append, linitial(custom_plans))->appendplans;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\telog(ERROR,\n\t\t\t\t \"invalid child of constraint-aware append: %s\",\n\t\t\t\t ts_get_node_name((Node *) linitial(custom_plans)));\n\t\t\tbreak;\n\t}\n\n\t/*\n\t * we only iterate over the child chunks of this node\n\t * so the list of metadata exactly matches the list of\n\t * child nodes in the executor\n\t */\n\tforeach (lc_child, children)\n\t{\n\t\tPlan *plan = get_plans_for_exclusion(lfirst(lc_child));\n\n\t\tswitch (nodeTag(plan))\n\t\t{\n\t\t\tcase T_SeqScan:\n\t\t\tcase T_SampleScan:\n\t\t\tcase T_IndexScan:\n\t\t\tcase T_IndexOnlyScan:\n\t\t\tcase T_BitmapIndexScan:\n\t\t\tcase T_BitmapHeapScan:\n\t\t\tcase T_TidScan:\n\t\t\tcase T_SubqueryScan:\n\t\t\tcase T_FunctionScan:\n\t\t\tcase T_ValuesScan:\n\t\t\tcase T_CteScan:\n\t\t\tcase T_WorkTableScan:\n\t\t\tcase T_ForeignScan:\n\t\t\tcase T_CustomScan:\n\t\t\tcase T_TidRangeScan:\n\t\t\t{\n\t\t\t\tList *chunk_clauses = NIL;\n\t\t\t\tListCell *lc;\n\t\t\t\tIndex scanrelid = ((Scan *) plan)->scanrelid;\n\t\t\t\tAppendRelInfo *appinfo = ts_get_appendrelinfo(root, scanrelid, false);\n\n\t\t\t\tforeach (lc, clauses)\n\t\t\t\t{\n\t\t\t\t\tNode *clause = (Node *) ts_transform_cross_datatype_comparison(\n\t\t\t\t\t\tcastNode(RestrictInfo, lfirst(lc))->clause);\n\t\t\t\t\tclause = adjust_appendrel_attrs(root, clause, 1, &appinfo);\n\t\t\t\t\tchunk_clauses = lappend(chunk_clauses, clause);\n\t\t\t\t}\n\t\t\t\tchunk_ri_clauses = lappend(chunk_ri_clauses, chunk_clauses);\n\t\t\t\tchunk_relids = lappend_oid(chunk_relids, scanrelid);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\telog(ERROR,\n\t\t\t\t\t \"invalid child of constraint-aware append: %s\",\n\t\t\t\t\t ts_get_node_name((Node *) plan));\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tcscan->custom_private = list_make3(list_make1_oid(rte->relid), chunk_ri_clauses, chunk_relids);\n\tcscan->custom_scan_tlist = subplan->targetlist; /* Target list of tuples\n\t\t\t\t\t\t\t\t\t\t\t\t\t * we expect as input */\n\tcscan->flags = path->flags;\n\tcscan->methods = &constraint_aware_append_plan_methods;\n\n\treturn &cscan->scan.plan;\n}\n\nstatic CustomPathMethods constraint_aware_append_path_methods = {\n\t.CustomName = \"ConstraintAwareAppend\",\n\t.PlanCustomPath = constraint_aware_append_plan_create,\n};\n\nPath *\nts_constraint_aware_append_path_create(PlannerInfo *root, Path *subpath)\n{\n\tConstraintAwareAppendPath *path;\n\n\tpath = (ConstraintAwareAppendPath *) newNode(sizeof(ConstraintAwareAppendPath), T_CustomPath);\n\tpath->cpath.path.pathtype = T_CustomScan;\n\tpath->cpath.path.rows = subpath->rows;\n\tpath->cpath.path.startup_cost = subpath->startup_cost;\n\tpath->cpath.path.total_cost = subpath->total_cost;\n\tpath->cpath.path.parent = subpath->parent;\n\tpath->cpath.path.pathkeys = subpath->pathkeys;\n\tpath->cpath.path.param_info = subpath->param_info;\n\tpath->cpath.path.pathtarget = subpath->pathtarget;\n\n\tpath->cpath.path.parallel_aware = false;\n\tpath->cpath.path.parallel_safe = subpath->parallel_safe;\n\tpath->cpath.path.parallel_workers = subpath->parallel_workers;\n\n\t/*\n\t * Set flags. We can set CUSTOMPATH_SUPPORT_BACKWARD_SCAN and\n\t * CUSTOMPATH_SUPPORT_MARK_RESTORE. The only interesting flag is the first\n\t * one (backward scan), but since we are not scanning a real relation we\n\t * need not indicate that we support backward scans. Lower-level index\n\t * scanning nodes will scan backward if necessary, so once tuples get to\n\t * this node they will be in a given order already.\n\t */\n\tpath->cpath.flags = 0;\n\tpath->cpath.custom_paths = list_make1(subpath);\n\tpath->cpath.methods = &constraint_aware_append_path_methods;\n\n\t/*\n\t * Make sure our subpath is either an Append or MergeAppend node\n\t */\n\tswitch (nodeTag(subpath))\n\t{\n\t\tcase T_AppendPath:\n\t\tcase T_MergeAppendPath:\n\t\t\tbreak;\n\t\tdefault:\n\t\t\telog(ERROR,\n\t\t\t\t \"invalid child of constraint-aware append: %s\",\n\t\t\t\t ts_get_node_name((Node *) subpath));\n\t\t\tbreak;\n\t}\n\n\treturn &path->cpath.path;\n}\n\nbool\nts_constraint_aware_append_possible(Path *path)\n{\n\tRelOptInfo *rel = path->parent;\n\tListCell *lc;\n\tint num_children;\n\n\tif (!ts_guc_enable_optimizations || !ts_guc_enable_constraint_aware_append ||\n\t\tconstraint_exclusion == CONSTRAINT_EXCLUSION_OFF)\n\t\treturn false;\n\n\tswitch (nodeTag(path))\n\t{\n\t\tcase T_AppendPath:\n\t\t\tnum_children = list_length(castNode(AppendPath, path)->subpaths);\n\t\t\tbreak;\n\t\tcase T_MergeAppendPath:\n\t\t\tnum_children = list_length(castNode(MergeAppendPath, path)->subpaths);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n\n\t/* Never use constraint-aware append with only one child, since PostgreSQL\n\t * will later prune the (Merge)Append node from such plans, leaving us with\n\t * an unexpected child node. */\n\tif (num_children <= 1)\n\t\treturn false;\n\n\t/*\n\t * If there are clauses that have mutable functions, this path is ripe for\n\t * execution-time optimization.\n\t */\n\tforeach (lc, rel->baserestrictinfo)\n\t{\n\t\tRestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);\n\n\t\tif (contain_mutable_functions((Node *) rinfo->clause))\n\t\t\treturn true;\n\t}\n\treturn false;\n}\n\nbool\nts_is_constraint_aware_append_path(Path *path)\n{\n\treturn IsA(path, CustomPath) &&\n\t\t   castNode(CustomPath, path)->methods == &constraint_aware_append_path_methods;\n}\n\nvoid\n_constraint_aware_append_init(void)\n{\n\tTryRegisterCustomScanMethods(&constraint_aware_append_plan_methods);\n}\n"
  },
  {
    "path": "src/nodes/constraint_aware_append/constraint_aware_append.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <nodes/extensible.h>\n\ntypedef struct ConstraintAwareAppendPath\n{\n\tCustomPath cpath;\n} ConstraintAwareAppendPath;\n\ntypedef struct ConstraintAwareAppendState\n{\n\tCustomScanState csstate;\n\tPlan *subplan;\n\tSize num_append_subplans;\n\tSize num_chunks_excluded;\n} ConstraintAwareAppendState;\n\ntypedef struct Hypertable Hypertable;\n\nextern bool ts_constraint_aware_append_possible(Path *path);\nextern TSDLLEXPORT Path *ts_constraint_aware_append_path_create(PlannerInfo *root, Path *subpath);\nextern TSDLLEXPORT bool ts_is_constraint_aware_append_path(Path *path);\nextern void _constraint_aware_append_init(void);\n"
  },
  {
    "path": "src/nodes/modify_hypertable.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <nodes/execnodes.h>\n#include <nodes/makefuncs.h>\n#include <utils/syscache.h>\n\n#include \"compat/compat.h\"\n#include \"chunk_tuple_routing.h\"\n#include \"cross_module_fn.h\"\n#include \"guc.h\"\n#include \"indexing.h\"\n#include \"nodes/chunk_append/chunk_append.h\"\n#include \"nodes/modify_hypertable.h\"\n\n#if PG18_GE\n#include <commands/explain_format.h>\n#endif\n\nstatic AttrNumber\nrel_get_natts(Oid relid)\n{\n\tHeapTuple tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));\n\n\tif (!HeapTupleIsValid(tp))\n\t\telog(ERROR, \"cache lookup failed for relation %u\", relid);\n\tAttrNumber natts = ((Form_pg_class) GETSTRUCT(tp))->relnatts;\n\tReleaseSysCache(tp);\n\treturn natts;\n}\n\nstatic bool\nrel_has_dropped_attrs(Oid relid)\n{\n\tAttrNumber natts = rel_get_natts(relid);\n\tfor (AttrNumber attno = 1; attno <= natts; attno++)\n\t{\n\t\tHeapTuple tp = SearchSysCache2(ATTNUM, ObjectIdGetDatum(relid), Int16GetDatum(attno));\n\t\tif (!HeapTupleIsValid(tp))\n\t\t\tcontinue;\n\t\tForm_pg_attribute att_tup = (Form_pg_attribute) GETSTRUCT(tp);\n\t\tbool result = att_tup->attisdropped || att_tup->atthasmissing;\n\t\tReleaseSysCache(tp);\n\t\tif (result)\n\t\t\treturn true;\n\t}\n\treturn false;\n}\n\nstatic bool\nshould_use_direct_compress(ModifyHypertableState *state)\n{\n\tif (!ts_guc_enable_direct_compress_insert)\n\t\treturn false;\n\n\tModifyTableState *mtstate = linitial_node(ModifyTableState, state->cscan_state.custom_ps);\n\tResultRelInfo *resultRelInfo = mtstate->resultRelInfo;\n\tHypertable *ht = state->ctr->hypertable;\n\n\tif (!TS_HYPERTABLE_HAS_COMPRESSION_ENABLED(ht))\n\t\treturn false;\n\n\tif (resultRelInfo->ri_TrigDesc)\n\t{\n\t\tereport(WARNING,\n\t\t\t\t(errmsg(\"disabling direct compress because the destination table has triggers\")));\n\t\treturn false;\n\t}\n\n\tif (ts_indexing_relation_has_primary_or_unique_index(state->ctr->root_rel))\n\t{\n\t\tereport(WARNING,\n\t\t\t\t(errmsg(\"disabling direct compress because the destination table has unique \"\n\t\t\t\t\t\t\"constraints\")));\n\t\treturn false;\n\t}\n\n\tPlan *subplan = mtstate->ps.plan->lefttree;\n\tif (subplan->plan_rows < 10)\n\t{\n\t\tereport(WARNING, (errmsg(\"disabling direct compress because of too small batch size\")));\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n * ModifyHypertable is a plan node that implements DML for hypertables.\n * It is a wrapper around the ModifyTable plan node that calls the wrapped ModifyTable\n * plan.\n */\nstatic void\nmodify_hypertable_begin(CustomScanState *node, EState *estate, int eflags)\n{\n\tModifyHypertableState *state = (ModifyHypertableState *) node;\n\tModifyTableState *mtstate;\n\tPlanState *ps;\n\n\tModifyTable *mt = castNode(ModifyTable, &state->mt->plan);\n\t/*\n\t * To make statement trigger defined on the hypertable work\n\t * we need to set the hypertable as the rootRelation otherwise\n\t * statement trigger defined only on the hypertable will not fire.\n\t */\n\tif (mt->operation == CMD_DELETE || mt->operation == CMD_UPDATE || mt->operation == CMD_MERGE)\n\t\tmt->rootRelation = mt->nominalRelation;\n\tps = ExecInitNode(&mt->plan, estate, eflags);\n\tnode->custom_ps = list_make1(ps);\n\tmtstate = castNode(ModifyTableState, ps);\n\n\t/*\n\t * If this is not the primary ModifyTable node, postgres added it to the\n\t * beginning of es_auxmodifytables, to be executed by ExecPostprocessPlan.\n\t * Unfortunately that strips off the HypertableInsert node leading to\n\t * tuple routing not working in INSERTs inside CTEs. To make INSERTs\n\t * inside CTEs work we have to fix es_auxmodifytables and add back the\n\t * ModifyHypertableState.\n\t */\n\tif (estate->es_auxmodifytables && linitial(estate->es_auxmodifytables) == mtstate)\n\t\tlinitial(estate->es_auxmodifytables) = node;\n\n\tstate->ht =\n\t\tts_hypertable_cache_get_cache_and_entry(RelationGetRelid(\n\t\t\t\t\t\t\t\t\t\t\t\t\tmtstate->resultRelInfo->ri_RelationDesc),\n\t\t\t\t\t\t\t\t\t\t\t\tCACHE_FLAG_MISSING_OK,\n\t\t\t\t\t\t\t\t\t\t\t\t&state->ht_cache);\n\n\t/*\n\t * If we are inserting into a chunk directly, rri will point to the chunk\n\t * itself, so we need to get the hypertable from the chunk.\n\t */\n\tif (!state->ht)\n\t{\n\t\tChunk *chunk =\n\t\t\tts_chunk_get_by_relid(RelationGetRelid(mtstate->resultRelInfo->ri_RelationDesc), true);\n\t\tstate->ht = ts_hypertable_cache_get_entry(state->ht_cache,\n\t\t\t\t\t\t\t\t\t\t\t\t  chunk->hypertable_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t  CACHE_FLAG_NONE);\n\t}\n\tstate->has_continuous_aggregate = ts_hypertable_has_continuous_aggregates(state->ht->fd.id);\n\n\tif (mtstate->operation == CMD_INSERT || mtstate->operation == CMD_MERGE)\n\t{\n\t\t/* setup chunk tuple routing state for INSERT/MERGE */\n\t\tstate->ctr = ts_chunk_tuple_routing_create(estate, state->ht, mtstate->resultRelInfo);\n\t\tstate->ctr->mht_state = state;\n\n\t\tif (mtstate->operation == CMD_INSERT && should_use_direct_compress(state))\n\t\t{\n\t\t\tstate->columnstore_insert = true;\n\t\t\tstate->ctr->create_compressed_chunk = true;\n\t\t}\n\n\t\tif (mtstate->operation == CMD_MERGE)\n\t\t\tstate->ctr->has_dropped_attrs =\n\t\t\t\trel_has_dropped_attrs(state->ctr->hypertable->main_table_relid);\n\n\t\t/* setup per tuple exprcontext for tuple routing */\n\t\tif (!estate->es_per_tuple_exprcontext)\n\t\t\testate->es_per_tuple_exprcontext = CreateExprContext(estate);\n\t}\n}\n\nstatic TupleTableSlot *\nmodify_hypertable_exec(CustomScanState *node)\n{\n\tModifyTableState *mtstate = linitial_node(ModifyTableState, node->custom_ps);\n\treturn ExecModifyTable(node, &mtstate->ps);\n}\n\nstatic void\nmodify_hypertable_end(CustomScanState *node)\n{\n\tModifyHypertableState *state = (ModifyHypertableState *) node;\n\n\t/*\n\t * Restore targetlists that were temporarily nullified during EXPLAIN\n\t * VERBOSE (see modify_hypertable_explain). This prevents corruption of\n\t * cached plans for prepared statements.\n\t */\n\tif (state->explain_saved_tlist)\n\t{\n\t\tModifyTableState *mtstate = linitial_node(ModifyTableState, node->custom_ps);\n\t\tPlan *lefttree = mtstate->ps.plan->lefttree;\n\t\tlefttree->targetlist = state->explain_saved_tlist;\n\t\tif (IsA(lefttree, CustomScan) && state->explain_saved_custom_scan_tlist)\n\t\t{\n\t\t\tcastNode(CustomScan, lefttree)->custom_scan_tlist =\n\t\t\t\tstate->explain_saved_custom_scan_tlist;\n\t\t}\n\t\tstate->explain_saved_tlist = NULL;\n\t\tstate->explain_saved_custom_scan_tlist = NULL;\n\t}\n\n\tif (state->compressor)\n\t{\n\t\tts_cm_functions->compressor_flush(state->compressor, state->bulk_writer);\n\t\tts_cm_functions->compressor_free(state->compressor, state->bulk_writer);\n\t\tstate->compressor = NULL;\n\t\tstate->bulk_writer = NULL;\n\t}\n\tExecEndNode(linitial(node->custom_ps));\n\tif (state->ctr)\n\t\tts_chunk_tuple_routing_destroy(state->ctr);\n\n\tts_cache_release(&state->ht_cache);\n}\n\nstatic void\nmodify_hypertable_rescan(CustomScanState *node)\n{\n\tExecReScan(linitial(node->custom_ps));\n}\n\n/*\n * Check if the plan is a ChunkAppend, possibly wrapped in one or more\n * Result nodes (for projection and/or pseudoconstant gating quals like EXISTS).\n */\nstatic bool\nis_chunk_append_or_projection(Plan *plan)\n{\n\twhile (IsA(plan, Result) && plan->lefttree != NULL)\n\t\tplan = plan->lefttree;\n\treturn ts_is_chunk_append_plan(plan);\n}\n\nstatic void\nmodify_hypertable_explain(CustomScanState *node, List *ancestors, ExplainState *es)\n{\n\tModifyHypertableState *state = (ModifyHypertableState *) node;\n\tModifyTableState *mtstate = linitial_node(ModifyTableState, node->custom_ps);\n\n\t/*\n\t * The targetlist for this node will have references that cannot be resolved by\n\t * EXPLAIN. So for EXPLAIN VERBOSE we clear the targetlist so that EXPLAIN does not\n\t * complain. PostgreSQL does something equivalent and does not print the targetlist\n\t * for ModifyTable for EXPLAIN VERBOSE.\n\t *\n\t * We save the original pointers and restore them in modify_hypertable_end\n\t * to avoid corrupting cached Plan trees (e.g. for prepared statements).\n\t */\n\tconst CmdType operation = ((ModifyTable *) mtstate->ps.plan)->operation;\n\tif ((operation == CMD_MERGE || operation == CMD_DELETE) && es->verbose &&\n\t\tis_chunk_append_or_projection(mtstate->ps.plan->lefttree))\n\t{\n\t\tPlan *lefttree = mtstate->ps.plan->lefttree;\n\t\tstate->explain_saved_tlist = lefttree->targetlist;\n\t\tlefttree->targetlist = NULL;\n\n\t\tif (IsA(lefttree, CustomScan))\n\t\t{\n\t\t\tstate->explain_saved_custom_scan_tlist =\n\t\t\t\tcastNode(CustomScan, lefttree)->custom_scan_tlist;\n\t\t\tcastNode(CustomScan, lefttree)->custom_scan_tlist = NULL;\n\t\t}\n\t}\n\n\t/*\n\t * Since we hijack the ModifyTable node, instrumentation on ModifyTable will\n\t * be missing so we set it to instrumentation of ModifyHypertable node.\n\t */\n\tif (mtstate->ps.instrument)\n\t{\n\t\t/*\n\t\t * INSERT .. ON CONFLICT statements record few metrics in the ModifyTable node.\n\t\t * So, copy them into ModifyHypertable node before replacing them.\n\t\t */\n\t\tnode->ss.ps.instrument->ntuples2 = mtstate->ps.instrument->ntuples2;\n\t\tnode->ss.ps.instrument->nfiltered1 = mtstate->ps.instrument->nfiltered1;\n\t}\n\tmtstate->ps.instrument = node->ss.ps.instrument;\n\n\t/*\n\t * For INSERT we have to read the number of decompressed batches and\n\t * tuples from the ChunkTupleRouting state below the ModifyTable.\n\t */\n\tif ((mtstate->operation == CMD_INSERT || mtstate->operation == CMD_MERGE) &&\n\t\touterPlanState(mtstate))\n\t{\n\t\tSharedCounters *counters = state->ctr->counters;\n\n\t\tstate->batches_deleted += counters->batches_deleted;\n\t\tstate->batches_filtered_decompressed += counters->batches_filtered_decompressed;\n\t\tstate->batches_decompressed += counters->batches_decompressed;\n\t\tstate->tuples_decompressed += counters->tuples_decompressed;\n\t\tstate->batches_scanned += counters->batches_scanned;\n\t\tstate->batches_checked_by_bloom += counters->batches_checked_by_bloom;\n\t\tstate->batches_pruned_by_bloom += counters->batches_pruned_by_bloom;\n\t\tstate->batches_without_bloom += counters->batches_without_bloom;\n\t\tstate->batches_bloom_false_positives += counters->batches_bloom_false_positives;\n\t}\n\tif (state->batches_scanned > 0)\n\t\tExplainPropertyInteger(\"Batches scanned\", NULL, state->batches_scanned, es);\n\tif (state->batches_filtered_compressed > 0)\n\t\tExplainPropertyInteger(\"Compressed batches filtered\",\n\t\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t\t   state->batches_filtered_compressed,\n\t\t\t\t\t\t\t   es);\n\tif (state->batches_filtered_decompressed > 0)\n\t\tExplainPropertyInteger(\"Batches filtered after decompression\",\n\t\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t\t   state->batches_filtered_decompressed,\n\t\t\t\t\t\t\t   es);\n\tif (state->batches_decompressed > 0)\n\t\tExplainPropertyInteger(\"Batches decompressed\", NULL, state->batches_decompressed, es);\n\tif (state->tuples_decompressed > 0)\n\t\tExplainPropertyInteger(\"Tuples decompressed\", NULL, state->tuples_decompressed, es);\n\tif (state->batches_deleted > 0)\n\t\tExplainPropertyInteger(\"Batches deleted\", NULL, state->batches_deleted, es);\n\tif (state->batches_checked_by_bloom > 0)\n\t\tExplainPropertyInteger(\"Batches checked by bloom\",\n\t\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t\t   state->batches_checked_by_bloom,\n\t\t\t\t\t\t\t   es);\n\tif (state->batches_pruned_by_bloom > 0)\n\t\tExplainPropertyInteger(\"Batches pruned by bloom\", NULL, state->batches_pruned_by_bloom, es);\n\tif (state->batches_without_bloom > 0)\n\t\tExplainPropertyInteger(\"Batches without bloom\", NULL, state->batches_without_bloom, es);\n\tif (state->batches_bloom_false_positives > 0)\n\t\tExplainPropertyInteger(\"Batches bloom false positives\",\n\t\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t\t   state->batches_bloom_false_positives,\n\t\t\t\t\t\t\t   es);\n\tif (ts_guc_enable_direct_compress_insert && state->mt->operation == CMD_INSERT)\n\t\tExplainPropertyBool(\"Direct Compress\", state->columnstore_insert, es);\n}\n\nstatic CustomExecMethods modify_hypertable_state_methods = {\n\t.CustomName = \"ModifyHypertableState\",\n\t.BeginCustomScan = modify_hypertable_begin,\n\t.EndCustomScan = modify_hypertable_end,\n\t.ExecCustomScan = modify_hypertable_exec,\n\t.ReScanCustomScan = modify_hypertable_rescan,\n\t.ExplainCustomScan = modify_hypertable_explain,\n};\n\nstatic Node *\nmodify_hypertable_state_create(CustomScan *cscan)\n{\n\tModifyHypertableState *state;\n\tModifyTable *mt = castNode(ModifyTable, linitial(cscan->custom_plans));\n\n\tstate = (ModifyHypertableState *) newNode(sizeof(ModifyHypertableState), T_CustomScanState);\n\tstate->cscan_state.methods = &modify_hypertable_state_methods;\n\tstate->mt = mt;\n\tstate->mt->arbiterIndexes = linitial(cscan->custom_private);\n\n\treturn (Node *) state;\n}\n\nstatic CustomScanMethods modify_hypertable_plan_methods = {\n\t.CustomName = \"ModifyHypertable\",\n\t.CreateCustomScanState = modify_hypertable_state_create,\n};\n\nbool\nts_is_modify_hypertable_plan(Plan *plan)\n{\n\treturn IsA(plan, CustomScan) &&\n\t\t   castNode(CustomScan, plan)->methods == &modify_hypertable_plan_methods;\n}\n\n/*\n * Make a targetlist to meet CustomScan expectations.\n *\n * When a CustomScan isn't scanning a real relation (scanrelid=0), it will build\n * a virtual TupleDesc for the scan \"input\" based on custom_scan_tlist. The\n * \"output\" targetlist is then expected to reference the attributes of the\n * input's TupleDesc. Without projection, the targetlist will be only Vars with\n * varno set to INDEX_VAR (to indicate reference to the TupleDesc instead of a\n * real relation) and matching the order of the attributes in the TupleDesc.\n *\n * Any other order, or non-Vars, will lead to the CustomScan performing\n * projection.\n *\n * Since the CustomScan for hypertable insert just wraps ModifyTable, no\n * projection is needed, so we'll build a targetlist to avoid this.\n */\nstatic List *\nmake_var_targetlist(const List *tlist)\n{\n\tList *new_tlist = NIL;\n\tListCell *lc;\n\tint resno = 1;\n\n\tforeach (lc, tlist)\n\t{\n\t\tTargetEntry *tle = lfirst_node(TargetEntry, lc);\n\t\tVar *var = makeVarFromTargetEntry(INDEX_VAR, tle);\n\n\t\tvar->varattno = resno;\n\t\tnew_tlist = lappend(new_tlist, makeTargetEntry(&var->xpr, resno, tle->resname, false));\n\t\tresno++;\n\t}\n\n\treturn new_tlist;\n}\n\n/*\n * Construct the HypertableInsert's target list based on the ModifyTable's\n * target list, which now exists after having been created by\n * set_plan_references().\n */\nvoid\nts_modify_hypertable_fixup_tlist(Plan *plan)\n{\n\tif (IsA(plan, CustomScan))\n\t{\n\t\tCustomScan *cscan = (CustomScan *) plan;\n\n\t\tif (cscan->methods == &modify_hypertable_plan_methods)\n\t\t{\n\t\t\tModifyTable *mt = linitial_node(ModifyTable, cscan->custom_plans);\n\n\t\t\tif (mt->plan.targetlist == NIL)\n\t\t\t{\n\t\t\t\tcscan->custom_scan_tlist = NIL;\n\t\t\t\tcscan->scan.plan.targetlist = NIL;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t/* The input is the output of the child ModifyTable node */\n\t\t\t\tcscan->custom_scan_tlist = mt->plan.targetlist;\n\n\t\t\t\t/* The output is a direct mapping of the input */\n\t\t\t\tcscan->scan.plan.targetlist = make_var_targetlist(mt->plan.targetlist);\n\t\t\t}\n\t\t}\n\t}\n}\n\nList *\nts_replace_rowid_vars(PlannerInfo *root, List *tlist, int varno)\n{\n\tListCell *lc;\n\ttlist = list_copy(tlist);\n\tforeach (lc, tlist)\n\t{\n\t\tTargetEntry *tle = lfirst_node(TargetEntry, lc);\n\n\t\tif (IsA(tle->expr, Var) && castNode(Var, tle->expr)->varno == ROWID_VAR)\n\t\t{\n\t\t\ttle = copyObject(tle);\n\t\t\tVar *var = castNode(Var, copyObject(tle->expr));\n\t\t\tRowIdentityVarInfo *ridinfo =\n\t\t\t\t(RowIdentityVarInfo *) list_nth(root->row_identity_vars, var->varattno - 1);\n\t\t\tvar = copyObject(ridinfo->rowidvar);\n\t\t\tvar->varno = varno;\n\t\t\tvar->varnosyn = 0;\n\t\t\tvar->varattnosyn = 0;\n\t\t\ttle->expr = (Expr *) var;\n\t\t\tlfirst(lc) = tle;\n\t\t}\n\t}\n\treturn tlist;\n}\n\nstatic Plan *\nmodify_hypertable_plan_create(PlannerInfo *root, RelOptInfo *rel, CustomPath *best_path,\n\t\t\t\t\t\t\t  List *tlist, List *clauses, List *custom_plans)\n{\n\tCustomScan *cscan = makeNode(CustomScan);\n\tModifyTable *mt = linitial_node(ModifyTable, custom_plans);\n\n\tcscan->methods = &modify_hypertable_plan_methods;\n\tcscan->custom_plans = custom_plans;\n\tcscan->scan.scanrelid = 0;\n\n\t/* Copy costs, etc., from the original plan */\n\tcscan->scan.plan.startup_cost = mt->plan.startup_cost;\n\tcscan->scan.plan.total_cost = mt->plan.total_cost;\n\tcscan->scan.plan.plan_rows = mt->plan.plan_rows;\n\tcscan->scan.plan.plan_width = mt->plan.plan_width;\n\n\t/* The tlist is always NIL since the ModifyTable subplan doesn't have its\n\t * targetlist set until set_plan_references (setrefs.c) is run */\n\tAssert(tlist == NIL);\n\n\t/* Target list handling here needs special attention. Intuitively, we'd like\n\t * to adopt the target list of the ModifyTable subplan we wrap without\n\t * further projection. For a CustomScan this means setting the \"input\"\n\t * custom_scan_tlist to the ModifyTable's target list and having an \"output\"\n\t * targetlist that references the TupleDesc that is created from the\n\t * custom_scan_tlist at execution time. Now, while this seems\n\t * straight-forward, there are several things with how ModifyTable nodes are\n\t * handled in the planner that complicates this:\n\t *\n\t * - First, ModifyTable doesn't have a targetlist set at this point, and\n\t *   it is only set later in set_plan_references (setrefs.c) if there's a\n\t *\t RETURNING clause.\n\t *\n\t * - Second, top-level plan nodes, except for ModifyTable nodes, need to\n\t *\t have a targetlist matching root->processed_tlist. This is asserted in\n\t *\t apply_tlist_labeling, which is called in create_plan (createplan.c)\n\t *\t immediately after this function returns. ModifyTable is exempted\n\t *\t because it doesn't always have a targetlist that matches\n\t *\t processed_tlist. So, even if we had access to ModifyTable's\n\t *\t targetlist here we wouldn't be able to use it since we're a\n\t *\t CustomScan and thus not exempted.\n\t *\n\t * - Third, a CustomScan's targetlist should reference the attributes of the\n\t *   TupleDesc that gets created from the custom_scan_tlist at the start of\n\t *   execution. This means we need to make the targetlist into all Vars with\n\t *   attribute numbers that correspond to the TupleDesc instead of result\n\t *   relation in the ModifyTable.\n\t *\n\t * To get around these issues, we set the targetlist here to\n\t * root->processed_tlist, and at the end of planning when the ModifyTable's\n\t * targetlist is set, we go back and fix up the CustomScan's targetlist.\n\t */\n\tcscan->scan.plan.targetlist = copyObject(root->processed_tlist);\n\n\t/*\n\t * For UPDATE/DELETE/MERGE processed_tlist will have ROWID_VAR. We\n\t * need to remove those because set_customscan_references will bail\n\t * if it sees ROWID_VAR entries in the targetlist.\n\t */\n\tif (mt->operation == CMD_UPDATE || mt->operation == CMD_DELETE || mt->operation == CMD_MERGE)\n\t{\n\t\tcscan->scan.plan.targetlist =\n\t\t\tts_replace_rowid_vars(root, cscan->scan.plan.targetlist, mt->nominalRelation);\n\n\t\t/*\n\t\t * When the ModifyTable's lefttree contains a ChunkAppend (possibly\n\t\t * wrapped in one or more Result nodes for projection and/or\n\t\t * pseudoconstant gating quals like EXISTS), ChunkAppend will have\n\t\t * already replaced ROWID_VAR entries in its own targetlist to avoid\n\t\t * assertions in set_customscan_references. However, the wrapping\n\t\t * Result nodes' targetlists still contain the original ROWID_VAR\n\t\t * entries. When set_plan_references later calls set_upper_references\n\t\t * on these Result nodes, it tries to resolve the ROWID_VAR entries\n\t\t * against the child's (already replaced) targetlist so we have to\n\t\t * replace ROWID_VAR entries in all Result nodes' targetlists between\n\t\t * ModifyTable and ChunkAppend.\n\t\t */\n\t\tif (is_chunk_append_or_projection(mt->plan.lefttree))\n\t\t{\n\t\t\tPlan *plan = mt->plan.lefttree;\n\t\t\twhile (IsA(plan, Result) && plan->lefttree != NULL)\n\t\t\t{\n\t\t\t\tplan->targetlist =\n\t\t\t\t\tts_replace_rowid_vars(root, plan->targetlist, mt->nominalRelation);\n\t\t\t\tplan = plan->lefttree;\n\t\t\t}\n\t\t}\n\t}\n\tcscan->custom_scan_tlist = cscan->scan.plan.targetlist;\n\n\t/*\n\t * we save the original list of arbiter indexes here\n\t * because we modify that list during execution and\n\t * we still need the original list in case that plan\n\t * gets reused.\n\t */\n\tcscan->custom_private = list_make1(mt->arbiterIndexes);\n\n\treturn &cscan->scan.plan;\n}\n\nstatic CustomPathMethods modify_hypertable_path_methods = {\n\t.CustomName = \"ModifyHypertablePath\",\n\t.PlanCustomPath = modify_hypertable_plan_create,\n};\n\nPath *\nts_modify_hypertable_path_create(PlannerInfo *root, ModifyTablePath *mtpath, RelOptInfo *rel)\n{\n\tModifyHypertablePath *mht_path = palloc0(sizeof(ModifyHypertablePath));\n\n\t/* Copy costs, etc. */\n\tmemcpy(&mht_path->cpath.path, &mtpath->path, sizeof(Path));\n\tmht_path->cpath.path.type = T_CustomPath;\n\tmht_path->cpath.path.pathtype = T_CustomScan;\n\tmht_path->cpath.custom_paths = list_make1(mtpath);\n\tmht_path->cpath.methods = &modify_hypertable_path_methods;\n\n\treturn &mht_path->cpath.path;\n}\n"
  },
  {
    "path": "src/nodes/modify_hypertable.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <foreign/fdwapi.h>\n#include <nodes/execnodes.h>\n\n#include \"chunk_tuple_routing.h\"\n#include \"hypertable.h\"\n\n/* Forward declarations */\ntypedef struct ModifyTableContext ModifyTableContext;\ntypedef struct RowCompressor RowCompressor;\ntypedef struct BulkWriter BulkWriter;\n\ntypedef struct ModifyHypertablePath\n{\n\tCustomPath cpath;\n} ModifyHypertablePath;\n\n/*\n * State for the hypertable_modify custom scan node.\n *\n * This struct definition is also used in ts_stat_statements, so any new fields\n * should only be added at the end of the struct.\n */\ntypedef struct ModifyHypertableState\n{\n\tCustomScanState cscan_state;\n\tModifyTable *mt;\n\tChunkTupleRouting *ctr;\n\tHypertable *ht;\n\tCache *ht_cache;\n\tbool has_continuous_aggregate;\n\n\tRowCompressor *compressor;\n\tBulkWriter *bulk_writer;\n\tOid compressor_relid;\n\tbool columnstore_insert;\n\n\tbool comp_chunks_processed;\n\tSnapshot snapshot;\n\tint64 tuples_decompressed;\n\tint64 batches_decompressed;\n\tint64 batches_filtered_decompressed;\n\tint64 batches_deleted;\n\tint64 tuples_deleted;\n\tint64 batches_scanned;\n\n\t/* bloom stats */\n\tint64 batches_checked_by_bloom;\n\tint64 batches_pruned_by_bloom;\n\tint64 batches_without_bloom;\n\tint64 batches_bloom_false_positives;\n\n\t/* bloom, betadata and null filters */\n\tint64 batches_filtered_compressed;\n\n\t/*\n\t * When EXPLAIN VERBOSE is used, we temporarily nullify the targetlist of the\n\t * lefttree of the ModifyTable to avoid printing out the full targetlist since\n\t * they can't be resolved by EXPLAIN. To not corrupt cached plans we need to\n\t * restore them to their original value afterwards.\n\t */\n\tList *explain_saved_tlist;\n\tList *explain_saved_custom_scan_tlist;\n\n} ModifyHypertableState;\n\nextern TSDLLEXPORT bool ts_is_modify_hypertable_plan(Plan *plan);\n\nextern void ts_modify_hypertable_fixup_tlist(Plan *plan);\nextern Path *ts_modify_hypertable_path_create(PlannerInfo *root, ModifyTablePath *mtpath,\n\t\t\t\t\t\t\t\t\t\t\t  RelOptInfo *input_rel);\nextern List *ts_replace_rowid_vars(PlannerInfo *root, List *tlist, int varno);\n\nTupleTableSlot *ExecModifyTable(CustomScanState *cs_node, PlanState *pstate);\nTupleTableSlot *ExecInsert(ModifyTableContext *context, ResultRelInfo *resultRelInfo,\n\t\t\t\t\t\t   ChunkTupleRouting *ctr, TupleTableSlot *slot, bool canSetTag);\n"
  },
  {
    "path": "src/nodes/modify_hypertable_exec.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n/*\n * This file is based on postgresql backend/executor/nodeModifyTable.c\n * Ordering of functions in this file is based on the order of functions in\n * nodeModifyTable.c\n */\n\n/* clang-format off */\n\n/* INTERFACE ROUTINES\n *\t\tExecInitModifyTable - initialize the ModifyTable node\n *\t\tExecModifyTable\t\t- retrieve the next tuple from the node\n *\t\tExecEndModifyTable\t- shut down the ModifyTable node\n *\t\tExecReScanModifyTable - rescan the ModifyTable node\n *\n *\t NOTES\n *\t\tThe ModifyTable node receives input from its outerPlan, which is\n *\t\tthe data to insert for INSERT cases, the changed columns' new\n *\t\tvalues plus row-locating info for UPDATE and MERGE cases, or just the\n *\t\trow-locating info for DELETE cases.\n *\n *\t\tThe relation to modify can be an ordinary table, a foreign table, or a\n *\t\tview.  If it's a view, either it has sufficient INSTEAD OF triggers or\n *\t\tthis node executes only MERGE ... DO NOTHING.  If the original MERGE\n *\t\ttargeted a view not in one of those two categories, earlier processing\n *\t\talready pointed the ModifyTable result relation to an underlying\n *\t\trelation of that other view.  This node does process\n *\t\tri_WithCheckOptions, which may have expressions from those other,\n *\t\tautomatically updatable views.\n *\n *\t\tMERGE runs a join between the source relation and the target table.\n *\t\tIf any WHEN NOT MATCHED [BY TARGET] clauses are present, then the join\n *\t\tis an outer join that might output tuples without a matching target\n *\t\ttuple.  In this case, any unmatched target tuples will have NULL\n *\t\trow-locating info, and only INSERT can be run.  But for matched target\n *\t\ttuples, the row-locating info is used to determine the tuple to UPDATE\n *\t\tor DELETE.  When all clauses are WHEN MATCHED or WHEN NOT MATCHED BY\n *\t\tSOURCE, all tuples produced by the join will include a matching target\n *\t\ttuple, so all tuples contain row-locating info.\n *\n *\t\tIf the query specifies RETURNING, then the ModifyTable returns a\n *\t\tRETURNING tuple after completing each row insert, update, or delete.\n *\t\tIt must be called again to continue the operation.  Without RETURNING,\n *\t\twe just loop within the node until all the work is done, then\n *\t\treturn NULL.  This avoids useless call/return overhead.\n */\n\n#include <postgres.h>\n#include <access/tupdesc.h>\n#include <access/xact.h>\n#include <catalog/pg_attribute.h>\n#include <catalog/pg_type.h>\n#include <executor/execPartition.h>\n#include <executor/nodeModifyTable.h>\n#include <foreign/foreign.h>\n#include <nodes/execnodes.h>\n#include <nodes/extensible.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodeFuncs.h>\n#include <nodes/pg_list.h>\n#include <nodes/plannodes.h>\n#include <optimizer/optimizer.h>\n#include <optimizer/plancat.h>\n#include <parser/parsetree.h>\n#include <storage/lmgr.h>\n#include <utils/builtins.h>\n#include <utils/lsyscache.h>\n#include <utils/rel.h>\n#include <utils/snapmgr.h>\n\n#include \"cross_module_fn.h\"\n#include \"guc.h\"\n#include \"hypertable_cache.h\"\n#include \"modify_hypertable.h\"\n#include \"nodes/chunk_append/chunk_append.h\"\n#include \"utils.h\"\n\n/*\n * Context struct for a ModifyTable operation, containing basic execution\n * state and some output variables populated by ExecUpdateAct() and\n * ExecDeleteAct() to report the result of their actions to callers.\n */\ntypedef struct ModifyTableContext\n{\n\t/* Operation state */\n\tModifyHypertableState *ht_state;\n\tModifyTableState *mtstate;\n\tEPQState *epqstate;\n\tEState *estate;\n\n\t/*\n\t * Slot containing tuple obtained from ModifyTable's subplan.  Used to\n\t * access \"junk\" columns that are not going to be stored.\n\t */\n\tTupleTableSlot *planSlot;\n\n\t/* MERGE specific */\n\tMergeActionState *relaction; /* MERGE action in progress */\n\t/*\n\t * Information about the changes that were made concurrently to a tuple\n\t * being updated or deleted\n\t */\n\tTM_FailureData tmfd;\n\n\t/*\n\t * The tuple produced by EvalPlanQual to retry from, if a\n\t * cross-partition UPDATE requires it\n\t */\n\tTupleTableSlot *cpUpdateRetrySlot;\n\n\t/*\n\t * The tuple projected by the INSERT's RETURNING clause, when doing a\n\t * cross-partition UPDATE\n\t */\n\tTupleTableSlot *cpUpdateReturningSlot;\n\n\t/*\n\t * Lock mode to acquire on the latest tuple version before performing\n\t * EvalPlanQual on it\n\t */\n\tLockTupleMode lockmode;\n} ModifyTableContext;\n\n/*\n * Context struct containing output data specific to UPDATE operations.\n */\ntypedef struct UpdateContext\n{\n\tbool\t\tcrossPartUpdate;\t/* was it a cross-partition update? */\n#if PG16_LT\n\tbool updateIndexes; /* index update required? */\n#else\n\tTU_UpdateIndexes updateIndexes; /* Which index updates are required? */\n#endif\n\n\t/*\n\t * Lock mode to acquire on the latest tuple version before performing\n\t * EvalPlanQual on it\n\t */\n\tLockTupleMode lockmode;\n} UpdateContext;\n\n\nstatic void ExecBatchInsert(ModifyTableState *mtstate,\n\t\t\t\t\t\t\tResultRelInfo *resultRelInfo,\n\t\t\t\t\t\t\tTupleTableSlot **slots,\n\t\t\t\t\t\t\tTupleTableSlot **planSlots,\n\t\t\t\t\t\t\tint numSlots,\n\t\t\t\t\t\t\tEState *estate,\n\t\t\t\t\t\t\tbool canSetTag);\nstatic void ExecPendingInserts(EState *estate);\n/*\nstatic void ExecCrossPartitionUpdateForeignKey(ModifyTableContext *context,\n\t\t\t\t\t\t\t\t\t\t\t   ResultRelInfo *sourcePartInfo,\n\t\t\t\t\t\t\t\t\t\t\t   ResultRelInfo *destPartInfo,\n\t\t\t\t\t\t\t\t\t\t\t   ItemPointer tupleid,\n\t\t\t\t\t\t\t\t\t\t\t   TupleTableSlot *oldslot,\n\t\t\t\t\t\t\t\t\t\t\t   TupleTableSlot *newslot);\n*/\nstatic bool ExecOnConflictUpdate(ModifyTableContext *context,\n\t\t\t\t\t\t\t\t ResultRelInfo *resultRelInfo,\n\t\t\t\t\t\t\t\t ItemPointer conflictTid,\n\t\t\t\t\t\t\t\t TupleTableSlot *excludedSlot,\n\t\t\t\t\t\t\t\t bool canSetTag,\n\t\t\t\t\t\t\t\t TupleTableSlot **returning);\n\nstatic TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,\n\t\t\t\t\t\t\t\t\t\t\t   EState *estate,\n\t\t\t\t\t\t\t\t\t\t\t   ChunkTupleRouting *ctr,\n\t\t\t\t\t\t\t\t\t\t\t   ResultRelInfo *targetRelInfo,\n\t\t\t\t\t\t\t\t\t\t\t   TupleTableSlot *slot,\n\t\t\t\t\t\t\t\t\t\t\t   ResultRelInfo **partRelInfo);\n\nstatic TupleTableSlot *ExecMerge(ModifyTableContext *context,\n\t\t\t\t\t\t\t\t ResultRelInfo *resultRelInfo,\n\t\t\t\t\t\t\t\t ChunkTupleRouting *ctr,\n\t\t\t\t\t\t\t\t ItemPointer tupleid,\n\t\t\t\t\t\t\t\t HeapTuple oldtuple,\n\t\t\t\t\t\t\t\t bool canSetTag);\n/*\nstatic void ExecInitMerge(ModifyTableState *mtstate, EState *estate);\n*/\nstatic TupleTableSlot *ExecMergeMatched(ModifyTableContext *context,\n\t\t\t\t\t\t\t\t\t\tResultRelInfo *resultRelInfo,\n\t\t\t\t\t\t\t\t\t\tItemPointer tupleid,\n\t\t\t\t\t\t\t\t\t\tHeapTuple oldtuple,\n\t\t\t\t\t\t\t\t\t\tbool canSetTag,\n\t\t\t\t\t\t\t\t\t\tbool *matched);\nstatic TupleTableSlot *ExecMergeNotMatched(ModifyTableContext *context,\n\t\t\t\t\t\t\t\t\t\t   ResultRelInfo *resultRelInfo,\n\t\t\t\t\t\t\t\t\t\t   ChunkTupleRouting *ctr,\n\t\t\t\t\t\t\t\t\t\t   bool canSetTag);\n\n\n/*\n * Verify that the tuples to be produced by INSERT match the\n * target relation's rowtype\n *\n * We do this to guard against stale plans.  If plan invalidation is\n * functioning properly then we should never get a failure here, but better\n * safe than sorry.  Note that this is called after we have obtained lock\n * on the target rel, so the rowtype can't change underneath us.\n *\n * The plan output is represented by its targetlist, because that makes\n * handling the dropped-column case easier.\n *\n * We used to use this for UPDATE as well, but now the equivalent checks\n * are done in ExecBuildUpdateProjection.\n */\nstatic void\nExecCheckPlanOutput(Relation resultRel, List *targetList)\n{\n\tTupleDesc\tresultDesc = RelationGetDescr(resultRel);\n\tint\t\t\tattno = 0;\n\tListCell   *lc;\n\n\tforeach(lc, targetList)\n\t{\n\t\tTargetEntry *tle = (TargetEntry *) lfirst(lc);\n\t\tForm_pg_attribute attr;\n\n\t\tAssert(!tle->resjunk);\t/* caller removed junk items already */\n\n\t\tif (attno >= resultDesc->natts)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_DATATYPE_MISMATCH),\n\t\t\t\t\t errmsg(\"table row type and query-specified row type do not match\"),\n\t\t\t\t\t errdetail(\"Query has too many columns.\")));\n\t\tattr = TupleDescAttr(resultDesc, attno);\n\t\tattno++;\n\n\t\t/*\n\t\t * Special cases here should match planner's expand_insert_targetlist.\n\t\t */\n\t\tif (attr->attisdropped)\n\t\t{\n\t\t\t/*\n\t\t\t * For a dropped column, we can't check atttypid (it's likely 0).\n\t\t\t * In any case the planner has most likely inserted an INT4 null.\n\t\t\t * What we insist on is just *some* NULL constant.\n\t\t\t */\n\t\t\tif (!IsA(tle->expr, Const) ||\n\t\t\t\t!((Const *) tle->expr)->constisnull)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_DATATYPE_MISMATCH),\n\t\t\t\t\t\t errmsg(\"table row type and query-specified row type do not match\"),\n\t\t\t\t\t\t errdetail(\"Query provides a value for a dropped column at ordinal position %d.\",\n\t\t\t\t\t\t\t\t   attno)));\n\t\t}\n\t\telse if (attr->attgenerated)\n\t\t{\n\t\t\t/*\n\t\t\t * For a generated column, the planner will have inserted a null\n\t\t\t * of the column's base type (to avoid possibly failing on domain\n\t\t\t * not-null constraints).  It doesn't seem worth insisting on that\n\t\t\t * exact type though, since a null value is type-independent.  As\n\t\t\t * above, just insist on *some* NULL constant.\n\t\t\t */\n\t\t\tif (!IsA(tle->expr, Const) ||\n\t\t\t\t!((Const *) tle->expr)->constisnull)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_DATATYPE_MISMATCH),\n\t\t\t\t\t\t errmsg(\"table row type and query-specified row type do not match\"),\n\t\t\t\t\t\t errdetail(\"Query provides a value for a generated column at ordinal position %d.\",\n\t\t\t\t\t\t\t\t   attno)));\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* Normal case: demand type match */\n\t\t\tif (exprType((Node *) tle->expr) != attr->atttypid)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_DATATYPE_MISMATCH),\n\t\t\t\t\t\t errmsg(\"table row type and query-specified row type do not match\"),\n\t\t\t\t\t\t errdetail(\"Table has type %s at ordinal position %d, but query expects %s.\",\n\t\t\t\t\t\t\t\t   format_type_be(attr->atttypid),\n\t\t\t\t\t\t\t\t   attno,\n\t\t\t\t\t\t\t\t   format_type_be(exprType((Node *) tle->expr)))));\n\t\t}\n\t}\n\tif (attno != resultDesc->natts)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_DATATYPE_MISMATCH),\n\t\t\t\t errmsg(\"table row type and query-specified row type do not match\"),\n\t\t\t\t errdetail(\"Query has too few columns.\")));\n}\n\n/*\n * ExecProcessReturning --- evaluate a RETURNING list\n *\n * resultRelInfo: current result rel\n * cmdType: operation performed (INSERT, UPDATE, or DELETE)\n * oldSlot: slot holding old tuple deleted or updated\n * newSlot: slot holding new tuple inserted or updated\n * planSlot: slot holding tuple returned by top subplan node\n *\n * Note: If oldSlot and newSlot are NULL, the FDW should have already provided\n * econtext's scan tuple and its old & new tuples are not needed (FDW direct-\n * modify is disabled if the RETURNING list refers to any OLD/NEW values).\n *\n * Returns a slot holding the result tuple\n */\nstatic TupleTableSlot *\nExecProcessReturning(ResultRelInfo *resultRelInfo,\n\t\t\t\t\t CmdType cmdType,\n\t\t\t\t\t TupleTableSlot *oldSlot,\n\t\t\t\t\t TupleTableSlot *newSlot,\n\t\t\t\t\t TupleTableSlot *planSlot)\n{\n\tProjectionInfo *projectReturning = resultRelInfo->ri_projectReturning;\n\tExprContext *econtext = projectReturning->pi_exprContext;\n\tTupleTableSlot *tupleSlot = (cmdType == CMD_DELETE) ? oldSlot : newSlot;\n\n\t/* Make tuple and any needed join variables available to ExecProject */\n\tif (tupleSlot)\n\t\tecontext->ecxt_scantuple = tupleSlot;\n\tecontext->ecxt_outertuple = planSlot;\n\n#if PG18_GE\n\t{\n\t\tEState\t   *estate = econtext->ecxt_estate;\n\n\t\t/* Make old/new tuples available to ExecProject, if required */\n\t\tif (oldSlot)\n\t\t\tecontext->ecxt_oldtuple = oldSlot;\n\t\telse if (projectReturning->pi_state.flags & EEO_FLAG_HAS_OLD)\n\t\t\tecontext->ecxt_oldtuple = ExecGetAllNullSlot(estate, resultRelInfo);\n\t\telse\n\t\t\tecontext->ecxt_oldtuple = NULL;\n\n\t\tif (newSlot)\n\t\t\tecontext->ecxt_newtuple = newSlot;\n\t\telse if (projectReturning->pi_state.flags & EEO_FLAG_HAS_NEW)\n\t\t\tecontext->ecxt_newtuple = ExecGetAllNullSlot(estate, resultRelInfo);\n\t\telse\n\t\t\tecontext->ecxt_newtuple = NULL;\n\n\t\t/*\n\t\t * Tell ExecProject whether or not the OLD/NEW rows actually exist.\n\t\t * This is required to evaluate ReturningExpr nodes and also in\n\t\t * ExecEvalSysVar() and ExecEvalWholeRowVar().\n\t\t */\n\t\tif (oldSlot == NULL)\n\t\t\tprojectReturning->pi_state.flags |= EEO_FLAG_OLD_IS_NULL;\n\t\telse\n\t\t\tprojectReturning->pi_state.flags &= ~EEO_FLAG_OLD_IS_NULL;\n\n\t\tif (newSlot == NULL)\n\t\t\tprojectReturning->pi_state.flags |= EEO_FLAG_NEW_IS_NULL;\n\t\telse\n\t\t\tprojectReturning->pi_state.flags &= ~EEO_FLAG_NEW_IS_NULL;\n\t}\n#else\n\n\t/*\n\t * RETURNING expressions might reference the tableoid column, so\n\t * reinitialize tts_tableOid before evaluating them.\n\t */\n\tecontext->ecxt_scantuple->tts_tableOid =\n\t\tRelationGetRelid(resultRelInfo->ri_RelationDesc);\n#endif\n\n\t/* Compute the RETURNING expressions */\n\treturn ExecProject(projectReturning);\n}\n\n/*\n * ExecCheckTupleVisible -- verify tuple is visible\n *\n * It would not be consistent with guarantees of the higher isolation levels to\n * proceed with avoiding insertion (taking speculative insertion's alternative\n * path) on the basis of another tuple that is not visible to MVCC snapshot.\n * Check for the need to raise a serialization failure, and do so as necessary.\n */\nstatic void\nExecCheckTupleVisible(EState *estate,\n\t\t\t\t\t  Relation rel,\n\t\t\t\t\t  TupleTableSlot *slot)\n{\n\tif (!IsolationUsesXactSnapshot())\n\t\treturn;\n\n\tif (!table_tuple_satisfies_snapshot(rel, slot, estate->es_snapshot))\n\t{\n\t\tDatum\t\txminDatum;\n\t\tTransactionId xmin;\n\t\tbool\t\tisnull;\n\n\t\txminDatum = slot_getsysattr(slot, MinTransactionIdAttributeNumber, &isnull);\n\t\tAssert(!isnull);\n\t\txmin = DatumGetTransactionId(xminDatum);\n\n\t\t/*\n\t\t * We should not raise a serialization failure if the conflict is\n\t\t * against a tuple inserted by our own transaction, even if it's not\n\t\t * visible to our snapshot.  (This would happen, for example, if\n\t\t * conflicting keys are proposed for insertion in a single command.)\n\t\t */\n\t\tif (!TransactionIdIsCurrentTransactionId(xmin))\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),\n\t\t\t\t\t errmsg(\"could not serialize access due to concurrent update\")));\n\t}\n}\n\n/*\n * ExecCheckTIDVisible -- convenience variant of ExecCheckTupleVisible()\n */\nstatic void\nExecCheckTIDVisible(EState *estate,\n\t\t\t\t\tResultRelInfo *relinfo,\n\t\t\t\t\tItemPointer tid,\n\t\t\t\t\tTupleTableSlot *tempSlot)\n{\n\tRelation\trel = relinfo->ri_RelationDesc;\n\n\t/* Redundantly check isolation level */\n\tif (!IsolationUsesXactSnapshot())\n\t\treturn;\n\n\tif (!table_tuple_fetch_row_version(rel, tid, SnapshotAny, tempSlot))\n\t\telog(ERROR, \"failed to fetch conflicting tuple for ON CONFLICT\");\n\tExecCheckTupleVisible(estate, rel, tempSlot);\n\tExecClearTuple(tempSlot);\n}\n\n/*\n * ExecInitInsertProjection\n *\t\tDo one-time initialization of projection data for INSERT tuples.\n *\n * INSERT queries may need a projection to filter out junk attrs in the tlist.\n *\n * This is also a convenient place to verify that the\n * output of an INSERT matches the target table.\n */\nstatic void\nExecInitInsertProjection(ModifyTableState *mtstate,\n\t\t\t\t\t\t ResultRelInfo *resultRelInfo)\n{\n\tModifyTable *node = (ModifyTable *) mtstate->ps.plan;\n\tPlan\t   *subplan = outerPlan(node);\n\tEState\t   *estate = mtstate->ps.state;\n\tList\t   *insertTargetList = NIL;\n\tbool\t\tneed_projection = false;\n\tListCell   *l;\n\n\t/* Extract non-junk columns of the subplan's result tlist. */\n\tforeach(l, subplan->targetlist)\n\t{\n\t\tTargetEntry *tle = (TargetEntry *) lfirst(l);\n\n\t\tif (!tle->resjunk)\n\t\t\tinsertTargetList = lappend(insertTargetList, tle);\n\t\telse\n\t\t\tneed_projection = true;\n\t}\n\n\t/*\n\t * The junk-free list must produce a tuple suitable for the result\n\t * relation.\n\t */\n\tExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, insertTargetList);\n\n\t/* We'll need a slot matching the table's format. */\n\tresultRelInfo->ri_newTupleSlot =\n\t\ttable_slot_create(resultRelInfo->ri_RelationDesc,\n\t\t\t\t\t\t  &estate->es_tupleTable);\n\n\t/* Build ProjectionInfo if needed (it probably isn't). */\n\tif (need_projection)\n\t{\n\t\tTupleDesc\trelDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);\n\n\t\t/* need an expression context to do the projection */\n\t\tif (mtstate->ps.ps_ExprContext == NULL)\n\t\t\tExecAssignExprContext(estate, &mtstate->ps);\n\n\t\tresultRelInfo->ri_projectNew =\n\t\t\tExecBuildProjectionInfo(insertTargetList,\n\t\t\t\t\t\t\t\t\tmtstate->ps.ps_ExprContext,\n\t\t\t\t\t\t\t\t\tresultRelInfo->ri_newTupleSlot,\n\t\t\t\t\t\t\t\t\t&mtstate->ps,\n\t\t\t\t\t\t\t\t\trelDesc);\n\t}\n\n\tresultRelInfo->ri_projectNewInfoValid = true;\n}\n\n/*\n * ExecInitUpdateProjection\n *\t\tDo one-time initialization of projection data for UPDATE tuples.\n *\n * UPDATE always needs a projection, because (1) there's always some junk\n * attrs, and (2) we may need to merge values of not-updated columns from\n * the old tuple into the final tuple.  In UPDATE, the tuple arriving from\n * the subplan contains only new values for the changed columns, plus row\n * identity info in the junk attrs.\n *\n * This is \"one-time\" for any given result rel, but we might touch more than\n * one result rel in the course of an inherited UPDATE, and each one needs\n * its own projection due to possible column order variation.\n *\n * This is also a convenient place to verify that the output of an UPDATE\n * matches the target table (ExecBuildUpdateProjection does that).\n */\nstatic void\nExecInitUpdateProjection(ModifyTableState *mtstate,\n\t\t\t\t\t\t ResultRelInfo *resultRelInfo)\n{\n\tModifyTable *node = (ModifyTable *) mtstate->ps.plan;\n\tPlan\t   *subplan = outerPlan(node);\n\tEState\t   *estate = mtstate->ps.state;\n\tTupleDesc\trelDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);\n\tint\t\t\twhichrel;\n\tList\t   *updateColnos;\n\n\t/*\n\t * Usually, mt_lastResultIndex matches the target rel.  If it happens not\n\t * to, we can get the index the hard way with an integer division.\n\t */\n\twhichrel = mtstate->mt_lastResultIndex;\n\tif (resultRelInfo != mtstate->resultRelInfo + whichrel)\n\t{\n\t\twhichrel = resultRelInfo - mtstate->resultRelInfo;\n\t\tAssert(whichrel >= 0 && whichrel < mtstate->mt_nrels);\n\t}\n\n\tupdateColnos = (List *) list_nth(node->updateColnosLists, whichrel);\n\n\t/*\n\t * For UPDATE, we use the old tuple to fill up missing values in the tuple\n\t * produced by the subplan to get the new tuple.  We need two slots, both\n\t * matching the table's desired format.\n\t */\n\tresultRelInfo->ri_oldTupleSlot =\n\t\ttable_slot_create(resultRelInfo->ri_RelationDesc,\n\t\t\t\t\t\t  &estate->es_tupleTable);\n\tresultRelInfo->ri_newTupleSlot =\n\t\ttable_slot_create(resultRelInfo->ri_RelationDesc,\n\t\t\t\t\t\t  &estate->es_tupleTable);\n\n\t/* need an expression context to do the projection */\n\tif (mtstate->ps.ps_ExprContext == NULL)\n\t\tExecAssignExprContext(estate, &mtstate->ps);\n\n\tresultRelInfo->ri_projectNew =\n\t\tExecBuildUpdateProjection(subplan->targetlist,\n\t\t\t\t\t\t\t\t  false,\t/* subplan did the evaluation */\n\t\t\t\t\t\t\t\t  updateColnos,\n\t\t\t\t\t\t\t\t  relDesc,\n\t\t\t\t\t\t\t\t  mtstate->ps.ps_ExprContext,\n\t\t\t\t\t\t\t\t  resultRelInfo->ri_newTupleSlot,\n\t\t\t\t\t\t\t\t  &mtstate->ps);\n\n\tresultRelInfo->ri_projectNewInfoValid = true;\n}\n\n/*\n * ExecGetInsertNewTuple\n *\t\tThis prepares a \"new\" tuple ready to be inserted into given result\n *\t\trelation, by removing any junk columns of the plan's output tuple\n *\t\tand (if necessary) coercing the tuple to the right tuple format.\n */\nstatic TupleTableSlot *\nExecGetInsertNewTuple(ResultRelInfo *relinfo,\n\t\t\t\t\t  TupleTableSlot *planSlot)\n{\n\tProjectionInfo *newProj = relinfo->ri_projectNew;\n\tExprContext *econtext;\n\n\t/*\n\t * If there's no projection to be done, just make sure the slot is of the\n\t * right type for the target rel.  If the planSlot is the right type we\n\t * can use it as-is, else copy the data into ri_newTupleSlot.\n\t */\n\tif (newProj == NULL)\n\t{\n\t\tif (relinfo->ri_newTupleSlot->tts_ops != planSlot->tts_ops)\n\t\t{\n\t\t\tExecCopySlot(relinfo->ri_newTupleSlot, planSlot);\n\t\t\treturn relinfo->ri_newTupleSlot;\n\t\t}\n\t\telse\n\t\t\treturn planSlot;\n\t}\n\n\t/*\n\t * Else project; since the projection output slot is ri_newTupleSlot, this\n\t * will also fix any slot-type problem.\n\t *\n\t * Note: currently, this is dead code, because INSERT cases don't receive\n\t * any junk columns so there's never a projection to be done.\n\t */\n\tecontext = newProj->pi_exprContext;\n\tecontext->ecxt_outertuple = planSlot;\n\treturn ExecProject(newProj);\n}\n\n/*\n * ExecPrepareTupleRouting --- prepare for routing one tuple\n *\n * Determine the partition in which the tuple in slot is to be inserted,\n * and return its ResultRelInfo in *partRelInfo.  The return value is\n * a slot holding the tuple of the partition rowtype.\n *\n * This also sets the transition table information in mtstate based on the\n * selected partition.\n */\nstatic TupleTableSlot *\nExecPrepareTupleRouting(ModifyTableState *mtstate,\n\t\t\t\t\t\tEState *estate,\n\t\t\t\t\t\tChunkTupleRouting *ctr,\n\t\t\t\t\t\tResultRelInfo *targetRelInfo,\n\t\t\t\t\t\tTupleTableSlot *slot,\n\t\t\t\t\t\tResultRelInfo **partRelInfo)\n{\n\tChunkInsertState *cis = ctr->cis;\n\t/* Convert the tuple to the chunk's rowtype, if necessary */\n\tif (cis->hyper_to_chunk_map != NULL && ctr->has_dropped_attrs == false)\n\t\tslot = execute_attr_map_slot(cis->hyper_to_chunk_map->attrMap, slot, cis->slot);\n\n\t*partRelInfo = cis->result_relation_info;\n\treturn slot;\n}\n\n/* ----------------------------------------------------------------\n *\t\tExecInsert\n *\n *\t\tFor INSERT, we have to insert the tuple into the target relation\n *\t\t(or partition thereof) and insert appropriate tuples into the index\n *\t\trelations.\n *\n *\t\tslot contains the new tuple value to be stored.\n *\t\tplanSlot is the output of the ModifyTable's subplan; we use it\n *\t\tto access \"junk\" columns that are not going to be stored.\n *\n *\t\tReturns RETURNING result if any, otherwise NULL.\n *\n *\t\tThis may change the currently active tuple conversion map in\n *\t\tmtstate->mt_transition_capture, so the callers must take care to\n *\t\tsave the previous value to avoid losing track of it.\n * ----------------------------------------------------------------\n *\n * copied and modified version of ExecInsert from executor/nodeModifyTable.c\n */\nTupleTableSlot *\nExecInsert(ModifyTableContext *context,\n\t\t   ResultRelInfo *resultRelInfo,\n\t\t   ChunkTupleRouting *ctr,\n\t\t   TupleTableSlot *slot,\n\t\t   bool canSetTag)\n{\n\tModifyTableState *mtstate = context->mtstate;\n\tEState\t   *estate = context->estate;\n\tRelation\tresultRelationDesc;\n\tList\t   *recheckIndexes = NIL;\n\tTupleTableSlot *planSlot = context->planSlot;\n\tTupleTableSlot *result = NULL;\n\tTransitionCaptureState *ar_insert_trig_tcs;\n\tModifyTable *node = (ModifyTable *) mtstate->ps.plan;\n\tOnConflictAction onconflict = node->onConflictAction;\n\t/*\n\t * If the input result relation is a partitioned table, find the leaf\n\t * partition to insert the tuple into.\n\t */\n\tif (ctr)\n\t{\n\t\tResultRelInfo *partRelInfo;\n\n\t\tslot = ExecPrepareTupleRouting(mtstate, estate, ctr,\n\t\t\t\t\t\t\t\t\t   resultRelInfo, slot,\n\t\t\t\t\t\t\t\t\t   &partRelInfo);\n\t\tresultRelInfo = partRelInfo;\n\t}\n\n\tExecMaterializeSlot(slot);\n\n\tresultRelationDesc = resultRelInfo->ri_RelationDesc;\n\n\t/*\n\t * Open the table's indexes, if we have not done so already, so that we\n\t * can add new index entries for the inserted tuple.\n\t */\n\tif (resultRelationDesc->rd_rel->relhasindex &&\n\t\tresultRelInfo->ri_IndexRelationDescs == NULL)\n\t\tExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);\n\n\t/*\n\t * BEFORE ROW INSERT Triggers.\n\t *\n\t * Note: We fire BEFORE ROW TRIGGERS for every attempted insertion in an\n\t * INSERT ... ON CONFLICT statement.  We cannot check for constraint\n\t * violations before firing these triggers, because they can change the\n\t * values to insert.  Also, they can run arbitrary user-defined code with\n\t * side-effects that we can't cancel by just not inserting the tuple.\n\t */\n\tif (resultRelInfo->ri_TrigDesc &&\n\t\t\tresultRelInfo->ri_TrigDesc->trig_insert_before_row)\n\t{\n\t\tif (!ExecBRInsertTriggers(estate, resultRelInfo, slot))\n\t\t\treturn NULL; /* \"do nothing\" */\n\t}\n\n\t/* INSTEAD OF ROW INSERT Triggers */\n\tif (resultRelInfo->ri_TrigDesc &&\n\t\tresultRelInfo->ri_TrigDesc->trig_insert_instead_row)\n\t{\n\t\tif (!ExecIRInsertTriggers(estate, resultRelInfo, slot))\n\t\t\treturn NULL;\t\t/* \"do nothing\" */\n\t}\n\telse if (resultRelInfo->ri_FdwRoutine)\n\t{\n\t\tereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\terrmsg(\"inserting into foreign tables not supported in hypertable context\")));\n\t}\n\telse\n\t{\n\t\tWCOKind wco_kind;\n\n\t\t/*\n\t\t * Constraints and GENERATED expressions might reference the tableoid\n\t\t * column, so (re-)initialize tts_tableOid before evaluating them.\n\t\t */\n\t\tslot->tts_tableOid = RelationGetRelid(resultRelationDesc);\n\n\t\t/*\n\t\t * Compute stored generated columns\n\t\t */\n\t\tif (resultRelationDesc->rd_att->constr &&\n\t\t\tresultRelationDesc->rd_att->constr->has_generated_stored)\n\t\t\tExecComputeStoredGenerated(resultRelInfo, estate, slot,\n\t\t\t\t\t\t\t\t\t   CMD_INSERT);\n\n\t\t/*\n\t\t * Check any RLS WITH CHECK policies.\n\t\t *\n\t\t * Normally we should check INSERT policies. But if the insert is the\n\t\t * result of a partition key update that moved the tuple to a new\n\t\t * partition, we should instead check UPDATE policies, because we are\n\t\t * executing policies defined on the target table, and not those\n\t\t * defined on the child partitions.\n\t\t *\n\t\t * If we're running MERGE, we refer to the action that we're executing\n\t\t * to know if we're doing an INSERT or UPDATE to a partition table.\n\t\t */\n\t\tif (mtstate->operation == CMD_UPDATE)\n\t\t\twco_kind = WCO_RLS_UPDATE_CHECK;\n\t\telse if (mtstate->operation == CMD_MERGE)\n\t\t\twco_kind = (\n#if PG17_GE\n\t\t\t\t\t\t   mtstate->mt_merge_action->mas_action->commandType\n#else\n\t\t\t\t\t\t   context->relaction->mas_action->commandType\n#endif\n\t\t\t\t\t\t   == CMD_UPDATE) ?\n\t\t\t\t\t\t   WCO_RLS_UPDATE_CHECK :\n\t\t\t\t\t\t   WCO_RLS_INSERT_CHECK;\n\t\telse\n\t\t\twco_kind = WCO_RLS_INSERT_CHECK;\n\n\t\t/*\n\t\t * ExecWithCheckOptions() will skip any WCOs which are not of the kind\n\t\t * we are looking for at this point.\n\t\t */\n\t\tif (resultRelInfo->ri_WithCheckOptions != NIL)\n\t\t\tExecWithCheckOptions(wco_kind, resultRelInfo, slot, estate);\n\n\t\t/*\n\t\t * Check the constraints of the tuple.\n\t\t */\n\t\tif (resultRelationDesc->rd_att->constr)\n\t\t\tExecConstraints(resultRelInfo, slot, estate);\n\n\t\t/*\n\t\t * Also check the tuple against the partition constraint, if there is\n\t\t * one; except that if we got here via tuple-routing, we don't need to\n\t\t * if there's no BR trigger defined on the partition.\n\t\t */\n\t\tif (resultRelationDesc->rd_rel->relispartition &&\n\t\t\t(resultRelInfo->ri_RootResultRelInfo == NULL ||\n\t\t\t (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_insert_before_row)))\n\t\t\tExecPartitionCheck(resultRelInfo, slot, estate, true);\n\n\t\tif (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)\n\t\t{\n\t\t\t/* Perform a speculative insertion. */\n\t\t\tuint32 specToken;\n\t\t\tItemPointerData conflictTid;\n\t\t\tbool specConflict;\n\t\t\tList *arbiterIndexes;\n\n\t\t\tarbiterIndexes = resultRelInfo->ri_onConflictArbiterIndexes;\n\n\t\t\t/*\n\t\t\t * Do a non-conclusive check for conflicts first.\n\t\t\t *\n\t\t\t * We're not holding any locks yet, so this doesn't guarantee that\n\t\t\t * the later insert won't conflict.  But it avoids leaving behind\n\t\t\t * a lot of canceled speculative insertions, if you run a lot of\n\t\t\t * INSERT ON CONFLICT statements that do conflict.\n\t\t\t *\n\t\t\t * We loop back here if we find a conflict below, either during\n\t\t\t * the pre-check, or when we re-check after inserting the tuple\n\t\t\t * speculatively.\n\t\t\t */\n\t\tvlock:\n\t\t\tspecConflict = false;\n\t\t\tif (!ExecCheckIndexConstraints(resultRelInfo,\n\t\t\t\t\t\t\t\t\t\t   slot,\n\t\t\t\t\t\t\t\t\t\t   estate,\n\t\t\t\t\t\t\t\t\t\t   &conflictTid,\n#if PG18_GE\n\t\t\t\t\t\t\t\t\t\t   NULL,\n#endif\n\t\t\t\t\t\t\t\t\t\t   arbiterIndexes))\n\t\t\t{\n\t\t\t\t/* committed conflict tuple found */\n\t\t\t\tif (onconflict == ONCONFLICT_UPDATE)\n\t\t\t\t{\n\t\t\t\t\t/*\n\t\t\t\t\t * In case of ON CONFLICT DO UPDATE, execute the UPDATE\n\t\t\t\t\t * part.  Be prepared to retry if the UPDATE fails because\n\t\t\t\t\t * of another concurrent UPDATE/DELETE to the conflict\n\t\t\t\t\t * tuple.\n\t\t\t\t\t */\n\t\t\t\t\tTupleTableSlot *returning = NULL;\n\n\t\t\t\t\tif (ExecOnConflictUpdate(context,\n\t\t\t\t\t\t\t\t\t\t\t resultRelInfo,\n\t\t\t\t\t\t\t\t\t\t\t &conflictTid,\n\t\t\t\t\t\t\t\t\t\t\t slot,\n\t\t\t\t\t\t\t\t\t\t\t canSetTag,\n\t\t\t\t\t\t\t\t\t\t\t &returning))\n\t\t\t\t\t{\n\t\t\t\t\t\tInstrCountTuples2(&mtstate->ps, 1);\n\t\t\t\t\t\treturn returning;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\tgoto vlock;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t/*\n\t\t\t\t\t * In case of ON CONFLICT DO NOTHING, do nothing. However,\n\t\t\t\t\t * verify that the tuple is visible to the executor's MVCC\n\t\t\t\t\t * snapshot at higher isolation levels.\n\t\t\t\t\t *\n\t\t\t\t\t * Using ExecGetReturningSlot() to store the tuple for the\n\t\t\t\t\t * recheck isn't that pretty, but we can't trivially use\n\t\t\t\t\t * the input slot, because it might not be of a compatible\n\t\t\t\t\t * type. As there's no conflicting usage of\n\t\t\t\t\t * ExecGetReturningSlot() in the DO NOTHING case...\n\t\t\t\t\t */\n\t\t\t\t\tAssert(onconflict == ONCONFLICT_NOTHING);\n\t\t\t\t\tExecCheckTIDVisible(estate,\n\t\t\t\t\t\t\t\t\t\tresultRelInfo,\n\t\t\t\t\t\t\t\t\t\t&conflictTid,\n\t\t\t\t\t\t\t\t\t\tExecGetReturningSlot(estate, resultRelInfo));\n\t\t\t\t\tInstrCountTuples2(&mtstate->ps, 1);\n\t\t\t\t\treturn NULL;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * Before we start insertion proper, acquire our \"speculative\n\t\t\t * insertion lock\".  Others can use that to wait for us to decide\n\t\t\t * if we're going to go ahead with the insertion, instead of\n\t\t\t * waiting for the whole transaction to complete.\n\t\t\t */\n\t\t\tspecToken = SpeculativeInsertionLockAcquire(GetCurrentTransactionId());\n\n\t\t\t/* insert the tuple, with the speculative token */\n\t\t\ttable_tuple_insert_speculative(resultRelationDesc,\n\t\t\t\t\t\t\t\t\t\t   slot,\n\t\t\t\t\t\t\t\t\t\t   estate->es_output_cid,\n\t\t\t\t\t\t\t\t\t\t   0,\n\t\t\t\t\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t\t\t\t\t   specToken);\n\n\t\t\t/* insert index entries for tuple */\n\t\t\trecheckIndexes = ExecInsertIndexTuplesCompat(resultRelInfo,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t slot,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t estate,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t true,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t &specConflict,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t arbiterIndexes,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t false);\n\n\t\t\t/* adjust the tuple's state accordingly */\n\t\t\ttable_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict);\n\n\t\t\t/*\n\t\t\t * Wake up anyone waiting for our decision.  They will re-check\n\t\t\t * the tuple, see that it's no longer speculative, and wait on our\n\t\t\t * XID as if this was a regularly inserted tuple all along.  Or if\n\t\t\t * we killed the tuple, they will see it's dead, and proceed as if\n\t\t\t * the tuple never existed.\n\t\t\t */\n\t\t\tSpeculativeInsertionLockRelease(GetCurrentTransactionId());\n\n\t\t\t/*\n\t\t\t * If there was a conflict, start from the beginning.  We'll do\n\t\t\t * the pre-check again, which will now find the conflicting tuple\n\t\t\t * (unless it aborts before we get there).\n\t\t\t */\n\t\t\tif (specConflict)\n\t\t\t{\n\t\t\t\tlist_free(recheckIndexes);\n\t\t\t\tgoto vlock;\n\t\t\t}\n\n\t\t\t/* Since there was no insertion conflict, we're done */\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* insert the tuple normally */\n\t\t\ttable_tuple_insert(resultRelationDesc, slot, estate->es_output_cid, 0, NULL);\n\n\t\t\t/* insert index entries for tuple */\n\t\t\tif (resultRelInfo->ri_NumIndices > 0)\n\t\t\t\trecheckIndexes = ExecInsertIndexTuplesCompat(resultRelInfo,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t slot,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t estate,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t NIL,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t false);\n\t\t}\n\t}\n\n\tif (context->ht_state->has_continuous_aggregate)\n\t{\n\t\tbool should_free;\n\t\tHeapTuple tuple = ExecFetchSlotHeapTuple(slot, false, &should_free);\n\t\tts_cm_functions->continuous_agg_dml_invalidate(context->ht_state->ht->fd.id, resultRelationDesc, tuple, NULL, false);\n\t\tif (should_free)\n\t\t\theap_freetuple(tuple);\n\t}\n\n\tif (canSetTag)\n\t\t(estate->es_processed)++;\n\n\t/*\n\t * If this insert is the result of a partition key update that moved the\n\t * tuple to a new partition, put this row into the transition NEW TABLE,\n\t * if there is one. We need to do this separately for DELETE and INSERT\n\t * because they happen on different tables.\n\t */\n\tar_insert_trig_tcs = mtstate->mt_transition_capture;\n\tif (mtstate->operation == CMD_UPDATE && mtstate->mt_transition_capture &&\n\t\tmtstate->mt_transition_capture->tcs_update_new_table)\n\t{\n\t\tExecARUpdateTriggers(estate,\n\t\t\t\t\t\t\t resultRelInfo,\n\t\t\t\t\t\t\t NULL, /* src_partinfo */\n\t\t\t\t\t\t\t NULL, /* dst_partinfo */\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t slot,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t mtstate->mt_transition_capture,\n\t\t\t\t\t\t\t false /* is_crosspart_update */\n\t\t);\n\n\t\t/*\n\t\t * We've already captured the NEW TABLE row, so make sure any AR\n\t\t * INSERT trigger fired below doesn't capture it again.\n\t\t */\n\t\tar_insert_trig_tcs = NULL;\n\t}\n\n\t/* AFTER ROW INSERT Triggers */\n\tExecARInsertTriggers(estate, resultRelInfo, slot, recheckIndexes, ar_insert_trig_tcs);\n\n\tlist_free(recheckIndexes);\n\n\t/*\n\t * Check any WITH CHECK OPTION constraints from parent views.  We are\n\t * required to do this after testing all constraints and uniqueness\n\t * violations per the SQL spec, so we do it after actually inserting the\n\t * record into the heap and all indexes.\n\t *\n\t * ExecWithCheckOptions will elog(ERROR) if a violation is found, so the\n\t * tuple will never be seen, if it violates the WITH CHECK OPTION.\n\t *\n\t * ExecWithCheckOptions() will skip any WCOs which are not of the kind we\n\t * are looking for at this point.\n\t */\n\tif (resultRelInfo->ri_WithCheckOptions != NIL)\n\t\tExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate);\n\n\t/* Process RETURNING if present */\n\tif (resultRelInfo->ri_projectReturning)\n\t\tresult = ExecProcessReturning(resultRelInfo, CMD_INSERT, NULL, slot, planSlot);\n\n\treturn result;\n}\n\n/* ----------------------------------------------------------------\n *\t\tExecBatchInsert\n *\n *\t\tInsert multiple tuples in an efficient way.\n *\t\tCurrently, this handles inserting into a foreign table without\n *\t\tRETURNING clause.\n * ----------------------------------------------------------------\n */\nstatic void\nExecBatchInsert(ModifyTableState *mtstate,\n\t\t\t\tResultRelInfo *resultRelInfo,\n\t\t\t\tTupleTableSlot **slots,\n\t\t\t\tTupleTableSlot **planSlots,\n\t\t\t\tint numSlots,\n\t\t\t\tEState *estate,\n\t\t\t\tbool canSetTag)\n{\n\tint\t\t\ti;\n\tint\t\t\tnumInserted = numSlots;\n\tTupleTableSlot *slot = NULL;\n\tTupleTableSlot **rslots;\n\n\t/*\n\t * insert into foreign table: let the FDW do it\n\t */\n\trslots = resultRelInfo->ri_FdwRoutine->ExecForeignBatchInsert(estate,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  resultRelInfo,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  slots,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  planSlots,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  &numInserted);\n\n\tfor (i = 0; i < numInserted; i++)\n\t{\n\t\tslot = rslots[i];\n\n\t\t/*\n\t\t * AFTER ROW Triggers might reference the tableoid column, so\n\t\t * (re-)initialize tts_tableOid before evaluating them.\n\t\t */\n\t\tslot->tts_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);\n\n\t\t/* AFTER ROW INSERT Triggers */\n\t\tExecARInsertTriggers(estate, resultRelInfo, slot, NIL,\n\t\t\t\t\t\t\t mtstate->mt_transition_capture);\n\n\t\t/*\n\t\t * Check any WITH CHECK OPTION constraints from parent views.  See the\n\t\t * comment in ExecInsert.\n\t\t */\n\t\tif (resultRelInfo->ri_WithCheckOptions != NIL)\n\t\t\tExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate);\n\t}\n\n\tif (canSetTag && numInserted > 0)\n\t\testate->es_processed += numInserted;\n\n\t/* Clean up all the slots, ready for the next batch */\n\tfor (i = 0; i < numSlots; i++)\n\t{\n\t\tExecClearTuple(slots[i]);\n\t\tExecClearTuple(planSlots[i]);\n\t}\n\tresultRelInfo->ri_NumSlots = 0;\n}\n\n/*\n * ExecPendingInserts -- flushes all pending inserts to the foreign tables\n */\nstatic void\nExecPendingInserts(EState *estate)\n{\n\tListCell   *l1,\n\t\t\t   *l2;\n\n\tforboth(l1, estate->es_insert_pending_result_relations,\n\t\t\tl2, estate->es_insert_pending_modifytables)\n\t{\n\t\tResultRelInfo *resultRelInfo = (ResultRelInfo *) lfirst(l1);\n\t\tModifyTableState *mtstate = (ModifyTableState *) lfirst(l2);\n\n\t\tAssert(mtstate);\n\t\tExecBatchInsert(mtstate, resultRelInfo,\n\t\t\t\t\t\tresultRelInfo->ri_Slots,\n\t\t\t\t\t\tresultRelInfo->ri_PlanSlots,\n\t\t\t\t\t\tresultRelInfo->ri_NumSlots,\n\t\t\t\t\t\testate, mtstate->canSetTag);\n\t}\n\n\tlist_free(estate->es_insert_pending_result_relations);\n\tlist_free(estate->es_insert_pending_modifytables);\n\testate->es_insert_pending_result_relations = NIL;\n\testate->es_insert_pending_modifytables = NIL;\n}\n\n/*\n * ExecDeletePrologue -- subroutine for ExecDelete\n *\n * Prepare executor state for DELETE.  Actually, the only thing we have to do\n * here is execute BEFORE ROW triggers.  We return false if one of them makes\n * the delete a no-op; otherwise, return true.\n */\nstatic bool\nExecDeletePrologue(ModifyTableContext *context, ResultRelInfo *resultRelInfo,\n\t\t\t\t   ItemPointer tupleid, HeapTuple oldtuple,\n\t\t\t\t   TupleTableSlot **epqreturnslot, TM_Result *result)\n{\n\tif (result)\n\t\t*result = TM_Ok;\n\n\t/* BEFORE ROW DELETE triggers */\n\tif (resultRelInfo->ri_TrigDesc &&\n\t\tresultRelInfo->ri_TrigDesc->trig_delete_before_row)\n\t{\n\t\t/* Flush any pending inserts, so rows are visible to the triggers */\n\t\tif (context->estate->es_insert_pending_result_relations != NIL)\n\t\t\tExecPendingInserts(context->estate);\n\n\t\treturn ExecBRDeleteTriggersCompat(context->estate, context->epqstate,\n\t\t\t\t\t\t\t\t\tresultRelInfo, tupleid, oldtuple,\n\t\t\t\t\t\t\t\t\tepqreturnslot, result, &context->tmfd,\n\t\t\t\t\t\t\t\t\tcontext->mtstate->operation == CMD_MERGE);\n\t}\n\n\treturn true;\n}\n\n/*\n * ExecDeleteAct -- subroutine for ExecDelete\n *\n * Actually delete the tuple from a plain table.\n *\n * Caller is in charge of doing EvalPlanQual as necessary\n */\nstatic TM_Result\nExecDeleteAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,\n\t\t\t  ItemPointer tupleid, bool changingPart)\n{\n\tEState\t   *estate = context->estate;\n\n\treturn table_tuple_delete(resultRelInfo->ri_RelationDesc, tupleid,\n\t\t\t\t\t\t\t  estate->es_output_cid,\n\t\t\t\t\t\t\t  estate->es_snapshot,\n\t\t\t\t\t\t\t  estate->es_crosscheck_snapshot,\n\t\t\t\t\t\t\t  true /* wait for commit */ ,\n\t\t\t\t\t\t\t  &context->tmfd,\n\t\t\t\t\t\t\t  changingPart);\n}\n\n/*\n * ExecDeleteEpilogue -- subroutine for ExecDelete\n *\n * Closing steps of tuple deletion; this invokes AFTER FOR EACH ROW triggers,\n * including the UPDATE triggers if the deletion is being done as part of a\n * cross-partition tuple move.\n */\nstatic void\nExecDeleteEpilogue(ModifyTableContext *context, ResultRelInfo *resultRelInfo,\n\t\t\t\t   ItemPointer tupleid, HeapTuple oldtuple, bool changingPart)\n{\n\tModifyTableState *mtstate = context->mtstate;\n\tEState\t   *estate = context->estate;\n\tTransitionCaptureState *ar_delete_trig_tcs;\n\n\t/*\n\t * If this delete is the result of a partition key update that moved the\n\t * tuple to a new partition, put this row into the transition OLD TABLE,\n\t * if there is one. We need to do this separately for DELETE and INSERT\n\t * because they happen on different tables.\n\t */\n\tar_delete_trig_tcs = mtstate->mt_transition_capture;\n\tif (mtstate->operation == CMD_UPDATE && mtstate->mt_transition_capture &&\n\t\tmtstate->mt_transition_capture->tcs_update_old_table)\n\t{\n\t\tExecARUpdateTriggers(estate, resultRelInfo,\n\t\t\t\t\t\t\t NULL, NULL,\n\t\t\t\t\t\t\t tupleid, oldtuple,\n\t\t\t\t\t\t\t NULL, NULL, mtstate->mt_transition_capture,\n\t\t\t\t\t\t\t false);\n\n\t\t/*\n\t\t * We've already captured the OLD TABLE row, so make sure any AR\n\t\t * DELETE trigger fired below doesn't capture it again.\n\t\t */\n\t\tar_delete_trig_tcs = NULL;\n\t}\n\n\t/* AFTER ROW DELETE Triggers */\n\tExecARDeleteTriggers(estate, resultRelInfo, tupleid, oldtuple,\n\t\t\t\t\t\t ar_delete_trig_tcs, changingPart);\n}\n\n/* ----------------------------------------------------------------\n *\t\tExecDelete\n *\n *\t\tDELETE is like UPDATE, except that we delete the tuple and no\n *\t\tindex modifications are needed.\n *\n *\t\tWhen deleting from a table, tupleid identifies the tuple to delete and\n *\t\toldtuple is NULL.  When deleting through a view INSTEAD OF trigger,\n *\t\toldtuple is passed to the triggers and identifies what to delete, and\n *\t\ttupleid is invalid.  When deleting from a foreign table, tupleid is\n *\t\tinvalid; the FDW has to figure out which row to delete using data from\n *\t\tthe planSlot.  oldtuple is passed to foreign table triggers; it is\n *\t\tNULL when the foreign table has no relevant triggers.  We use\n *\t\ttupleDeleted to indicate whether the tuple is actually deleted,\n *\t\tcallers can use it to decide whether to continue the operation.  When\n *\t\tthis DELETE is a part of an UPDATE of partition-key, then the slot\n *\t\treturned by EvalPlanQual() is passed back using output parameter\n *\t\tepqreturnslot.\n *\n *\t\tReturns RETURNING result if any, otherwise NULL.\n * ----------------------------------------------------------------\n */\nstatic TupleTableSlot *\nExecDelete(ModifyTableContext *context,\n\t\t   ResultRelInfo *resultRelInfo,\n\t\t   ItemPointer tupleid,\n\t\t   HeapTuple oldtuple,\n\t\t   bool processReturning,\n\t\t   bool changingPart,\n\t\t   bool canSetTag,\n\t\t   TM_Result *tmresult,\n\t\t   bool *tupleDeleted,\n\t\t   TupleTableSlot **epqreturnslot)\n{\n\tEState\t   *estate = context->estate;\n\tRelation\tresultRelationDesc = resultRelInfo->ri_RelationDesc;\n\tTupleTableSlot *slot = NULL;\n\tTM_Result\tresult;\n\n\tif (tupleDeleted)\n\t\t*tupleDeleted = false;\n\n\t/*\n\t * Prepare for the delete.  This includes BEFORE ROW triggers, so we're\n\t * done if it says we are.\n\t */\n\tif (!ExecDeletePrologue(context, resultRelInfo, tupleid, oldtuple,\n\t\t\t\t\t\t\tepqreturnslot, tmresult))\n\t\treturn NULL;\n\n\t/* INSTEAD OF ROW DELETE Triggers */\n\tif (resultRelInfo->ri_TrigDesc &&\n\t\tresultRelInfo->ri_TrigDesc->trig_delete_instead_row)\n\t{\n\t\tbool\t\tdodelete;\n\n\t\tAssert(oldtuple != NULL);\n\t\tdodelete = ExecIRDeleteTriggers(estate, resultRelInfo, oldtuple);\n\n\t\tif (!dodelete)\t\t\t/* \"do nothing\" */\n\t\t\treturn NULL;\n\t}\n\telse if (resultRelInfo->ri_FdwRoutine)\n\t{\n\t\tereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\terrmsg(\"deleting from foreign tables not supported in hypertable context\")));\n\t}\n\telse\n\t{\n\t\t/*\n\t\t * delete the tuple\n\t\t *\n\t\t * Note: if context->estate->es_crosscheck_snapshot isn't\n\t\t * InvalidSnapshot, we check that the row to be deleted is visible to\n\t\t * that snapshot, and throw a can't-serialize error if not. This is a\n\t\t * special-case behavior needed for referential integrity updates in\n\t\t * transaction-snapshot mode transactions.\n\t\t */\nldelete:\n\t\tresult = ExecDeleteAct(context, resultRelInfo, tupleid, changingPart);\n\n\t\tif (tmresult)\n\t\t\t*tmresult = result;\n\n\t\tswitch (result)\n\t\t{\n\t\t\tcase TM_SelfModified:\n\n\t\t\t\t/*\n\t\t\t\t * The target tuple was already updated or deleted by the\n\t\t\t\t * current command, or by a later command in the current\n\t\t\t\t * transaction.  The former case is possible in a join DELETE\n\t\t\t\t * where multiple tuples join to the same target tuple. This\n\t\t\t\t * is somewhat questionable, but Postgres has always allowed\n\t\t\t\t * it: we just ignore additional deletion attempts.\n\t\t\t\t *\n\t\t\t\t * The latter case arises if the tuple is modified by a\n\t\t\t\t * command in a BEFORE trigger, or perhaps by a command in a\n\t\t\t\t * volatile function used in the query.  In such situations we\n\t\t\t\t * should not ignore the deletion, but it is equally unsafe to\n\t\t\t\t * proceed.  We don't want to discard the original DELETE\n\t\t\t\t * while keeping the triggered actions based on its deletion;\n\t\t\t\t * and it would be no better to allow the original DELETE\n\t\t\t\t * while discarding updates that it triggered.  The row update\n\t\t\t\t * carries some information that might be important according\n\t\t\t\t * to business rules; so throwing an error is the only safe\n\t\t\t\t * course.\n\t\t\t\t *\n\t\t\t\t * If a trigger actually intends this type of interaction, it\n\t\t\t\t * can re-execute the DELETE and then return NULL to cancel\n\t\t\t\t * the outer delete.\n\t\t\t\t */\n\t\t\t\tif (context->tmfd.cmax != estate->es_output_cid)\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION),\n\t\t\t\t\t\t\t errmsg(\"tuple to be deleted was already modified by an operation triggered by the current command\"),\n\t\t\t\t\t\t\t errhint(\"Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.\")));\n\n\t\t\t\t/* Else, already deleted by self; nothing to do */\n\t\t\t\treturn NULL;\n\n\t\t\tcase TM_Ok:\n\t\t\t\tbreak;\n\n\t\t\tcase TM_Updated:\n\t\t\t\t{\n\t\t\t\t\tTupleTableSlot *inputslot;\n\t\t\t\t\tTupleTableSlot *epqslot;\n\n\t\t\t\t\tif (IsolationUsesXactSnapshot())\n\t\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t\t(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),\n\t\t\t\t\t\t\t\t errmsg(\"could not serialize access due to concurrent update\")));\n\n\t\t\t\t\t/*\n\t\t\t\t\t * Already know that we're going to need to do EPQ, so\n\t\t\t\t\t * fetch tuple directly into the right slot.\n\t\t\t\t\t */\n\t\t\t\t\tEvalPlanQualBegin(context->epqstate);\n\t\t\t\t\tinputslot = EvalPlanQualSlot(context->epqstate, resultRelationDesc,\n\t\t\t\t\t\t\t\t\t\t\t\t resultRelInfo->ri_RangeTableIndex);\n\n\t\t\t\t\tresult = table_tuple_lock(resultRelationDesc, tupleid,\n\t\t\t\t\t\t\t\t\t\t\t  estate->es_snapshot,\n\t\t\t\t\t\t\t\t\t\t\t  inputslot, estate->es_output_cid,\n\t\t\t\t\t\t\t\t\t\t\t  LockTupleExclusive, LockWaitBlock,\n\t\t\t\t\t\t\t\t\t\t\t  TUPLE_LOCK_FLAG_FIND_LAST_VERSION,\n\t\t\t\t\t\t\t\t\t\t\t  &context->tmfd);\n\n\t\t\t\t\tswitch (result)\n\t\t\t\t\t{\n\t\t\t\t\t\tcase TM_Ok:\n\t\t\t\t\t\t\tAssert(context->tmfd.traversed);\n\t\t\t\t\t\t\tepqslot = EvalPlanQual(context->epqstate,\n\t\t\t\t\t\t\t\t\t\t\t\t   resultRelationDesc,\n\t\t\t\t\t\t\t\t\t\t\t\t   resultRelInfo->ri_RangeTableIndex,\n\t\t\t\t\t\t\t\t\t\t\t\t   inputslot);\n\t\t\t\t\t\t\tif (TupIsNull(epqslot))\n\t\t\t\t\t\t\t\t/* Tuple not passing quals anymore, exiting... */\n\t\t\t\t\t\t\t\treturn NULL;\n\n\t\t\t\t\t\t\t/*\n\t\t\t\t\t\t\t * If requested, skip delete and pass back the\n\t\t\t\t\t\t\t * updated row.\n\t\t\t\t\t\t\t */\n\t\t\t\t\t\t\tif (epqreturnslot)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t*epqreturnslot = epqslot;\n\t\t\t\t\t\t\t\treturn NULL;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\tgoto ldelete;\n\n\t\t\t\t\t\tcase TM_SelfModified:\n\n\t\t\t\t\t\t\t/*\n\t\t\t\t\t\t\t * This can be reached when following an update\n\t\t\t\t\t\t\t * chain from a tuple updated by another session,\n\t\t\t\t\t\t\t * reaching a tuple that was already updated in\n\t\t\t\t\t\t\t * this transaction. If previously updated by this\n\t\t\t\t\t\t\t * command, ignore the delete, otherwise error\n\t\t\t\t\t\t\t * out.\n\t\t\t\t\t\t\t *\n\t\t\t\t\t\t\t * See also TM_SelfModified response to\n\t\t\t\t\t\t\t * table_tuple_delete() above.\n\t\t\t\t\t\t\t */\n\t\t\t\t\t\t\tif (context->tmfd.cmax != estate->es_output_cid)\n\t\t\t\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t\t\t\t(errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION),\n\t\t\t\t\t\t\t\t\t\t errmsg(\"tuple to be deleted was already modified by an operation triggered by the current command\"),\n\t\t\t\t\t\t\t\t\t\t errhint(\"Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.\")));\n\t\t\t\t\t\t\treturn NULL;\n\n\t\t\t\t\t\tcase TM_Deleted:\n\t\t\t\t\t\t\t/* tuple already deleted; nothing to do */\n\t\t\t\t\t\t\treturn NULL;\n\n\t\t\t\t\t\tdefault:\n\n\t\t\t\t\t\t\t/*\n\t\t\t\t\t\t\t * TM_Invisible should be impossible because we're\n\t\t\t\t\t\t\t * waiting for updated row versions, and would\n\t\t\t\t\t\t\t * already have errored out if the first version\n\t\t\t\t\t\t\t * is invisible.\n\t\t\t\t\t\t\t *\n\t\t\t\t\t\t\t * TM_Updated should be impossible, because we're\n\t\t\t\t\t\t\t * locking the latest version via\n\t\t\t\t\t\t\t * TUPLE_LOCK_FLAG_FIND_LAST_VERSION.\n\t\t\t\t\t\t\t */\n\t\t\t\t\t\t\telog(ERROR, \"unexpected table_tuple_lock status: %u\",\n\t\t\t\t\t\t\t\t result);\n\t\t\t\t\t\t\treturn NULL;\n\t\t\t\t\t}\n\n\t\t\t\t\tAssert(false);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\tcase TM_Deleted:\n\t\t\t\tif (IsolationUsesXactSnapshot())\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),\n\t\t\t\t\t\t\t errmsg(\"could not serialize access due to concurrent delete\")));\n\t\t\t\t/* tuple already deleted; nothing to do */\n\t\t\t\treturn NULL;\n\n\t\t\tdefault:\n\t\t\t\telog(ERROR, \"unrecognized table_tuple_delete status: %u\",\n\t\t\t\t\t result);\n\t\t\t\treturn NULL;\n\t\t}\n\n\t\t/*\n\t\t * Note: Normally one would think that we have to delete index tuples\n\t\t * associated with the heap tuple now...\n\t\t *\n\t\t * ... but in POSTGRES, we have no need to do this because VACUUM will\n\t\t * take care of it later.  We can't delete index tuples immediately\n\t\t * anyway, since the tuple is still visible to other transactions.\n\t\t */\n\t}\n\n\tif (context->ht_state->has_continuous_aggregate)\n\t{\n\t\tbool should_free;\n\t\tTupleTableSlot *cagg_slot = table_slot_create(resultRelationDesc, NULL);\n\t\ttable_tuple_fetch_row_version(resultRelationDesc, tupleid, SnapshotAny, cagg_slot);\n\t\tHeapTuple tuple = ExecFetchSlotHeapTuple(cagg_slot, false, &should_free);\n\t\tts_cm_functions->continuous_agg_dml_invalidate(context->ht_state->ht->fd.id, resultRelationDesc, tuple, NULL, false);\n\t\tif (should_free)\n\t\t\theap_freetuple(tuple);\n\t\tExecDropSingleTupleTableSlot(cagg_slot);\n\t}\n\n\tif (canSetTag)\n\t\t(estate->es_processed)++;\n\n\t/* Tell caller that the delete actually happened. */\n\tif (tupleDeleted)\n\t\t*tupleDeleted = true;\n\n\tExecDeleteEpilogue(context, resultRelInfo, tupleid, oldtuple, changingPart);\n\n\t/* Process RETURNING if present and if requested */\n\tif (processReturning && resultRelInfo->ri_projectReturning)\n\t{\n\t\t/*\n\t\t * We have to put the target tuple into a slot, which means first we\n\t\t * gotta fetch it.  We can use the trigger tuple slot.\n\t\t */\n\t\tTupleTableSlot *rslot;\n\n\t\tif (resultRelInfo->ri_FdwRoutine)\n\t\t{\n\t\t\t/* FDW must have provided a slot containing the deleted row */\n\t\t\tAssert(!TupIsNull(slot));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tslot = ExecGetReturningSlot(estate, resultRelInfo);\n\t\t\tif (oldtuple != NULL)\n\t\t\t{\n\t\t\t\tExecForceStoreHeapTuple(oldtuple, slot, false);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (!table_tuple_fetch_row_version(resultRelationDesc, tupleid,\n\t\t\t\t\t\t\t\t\t\t\t\t   SnapshotAny, slot))\n\t\t\t\t\telog(ERROR, \"failed to fetch deleted tuple for DELETE RETURNING\");\n\t\t\t}\n\t\t}\n\n\t\trslot = ExecProcessReturning(resultRelInfo, CMD_DELETE, slot, NULL, context->planSlot);\n\n\t\t/*\n\t\t * Before releasing the target tuple again, make sure rslot has a\n\t\t * local copy of any pass-by-reference values.\n\t\t */\n\t\tExecMaterializeSlot(rslot);\n\n\t\tExecClearTuple(slot);\n\n\t\treturn rslot;\n\t}\n\n\treturn NULL;\n}\n\n/*\n * ExecUpdatePrologue -- subroutine for ExecUpdate\n *\n * Prepare executor state for UPDATE.  This includes running BEFORE ROW\n * triggers.  We return false if one of them makes the update a no-op;\n * otherwise, return true.\n */\nstatic bool\nExecUpdatePrologue(ModifyTableContext *context, ResultRelInfo *resultRelInfo,\n\t\t\t\t   ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot,\n\t\t\t\t   TM_Result *result)\n{\n\tRelation\tresultRelationDesc = resultRelInfo->ri_RelationDesc;\n\n\tif (result)\n\t\t*result = TM_Ok;\n\n\tExecMaterializeSlot(slot);\n\n\t/*\n\t * Open the table's indexes, if we have not done so already, so that we\n\t * can add new index entries for the updated tuple.\n\t */\n\tif (resultRelationDesc->rd_rel->relhasindex &&\n\t\tresultRelInfo->ri_IndexRelationDescs == NULL)\n\t\tExecOpenIndices(resultRelInfo, false);\n\n\t/* BEFORE ROW UPDATE triggers */\n\tif (resultRelInfo->ri_TrigDesc &&\n\t\tresultRelInfo->ri_TrigDesc->trig_update_before_row)\n\t{\n\t\t/* Flush any pending inserts, so rows are visible to the triggers */\n\t\tif (context->estate->es_insert_pending_result_relations != NIL)\n\t\t\tExecPendingInserts(context->estate);\n\n\t\treturn ExecBRUpdateTriggersCompat(context->estate, context->epqstate,\n\t\t\t\t\t\t\t\t\tresultRelInfo, tupleid, oldtuple, slot,\n\t\t\t\t\t\t\t\t\tresult, &context->tmfd,\n\t\t\t\t\t\t\t\t\tcontext->mtstate->operation == CMD_MERGE);\n\t}\n\n\treturn true;\n}\n\n/*\n * ExecUpdatePrepareSlot -- subroutine for ExecUpdateAct\n *\n * Apply the final modifications to the tuple slot before the update.\n * (This is split out because we also need it in the foreign-table code path.)\n */\nstatic void\nExecUpdatePrepareSlot(ResultRelInfo *resultRelInfo,\n\t\t\t\t\t  TupleTableSlot *slot,\n\t\t\t\t\t  EState *estate)\n{\n\tRelation\tresultRelationDesc = resultRelInfo->ri_RelationDesc;\n\n\t/*\n\t * Constraints and GENERATED expressions might reference the tableoid\n\t * column, so (re-)initialize tts_tableOid before evaluating them.\n\t */\n\tslot->tts_tableOid = RelationGetRelid(resultRelationDesc);\n\n\t/*\n\t * Compute stored generated columns\n\t */\n\tif (resultRelationDesc->rd_att->constr &&\n\t\tresultRelationDesc->rd_att->constr->has_generated_stored)\n\t\tExecComputeStoredGenerated(resultRelInfo, estate, slot,\n\t\t\t\t\t\t\t\t   CMD_UPDATE);\n}\n\n/*\n * ExecUpdateAct -- subroutine for ExecUpdate\n *\n * Actually update the tuple, when operating on a plain table.  If the\n * table is a partition, and the command was called referencing an ancestor\n * partitioned table, this routine migrates the resulting tuple to another\n * partition.\n *\n * The caller is in charge of keeping indexes current as necessary.  The\n * caller is also in charge of doing EvalPlanQual if the tuple is found to\n * be concurrently updated.  However, in case of a cross-partition update,\n * this routine does it.\n */\nstatic TM_Result\nExecUpdateAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,\n\t\t\t  ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot,\n\t\t\t  bool canSetTag, UpdateContext *updateCxt)\n{\n\tEState\t   *estate = context->estate;\n\tRelation\tresultRelationDesc = resultRelInfo->ri_RelationDesc;\n\tbool\t\tpartition_constraint_failed;\n\tTM_Result\tresult;\n\n\tupdateCxt->crossPartUpdate = false;\n\n\t/* Fill in GENERATEd columns */\n\tExecUpdatePrepareSlot(resultRelInfo, slot, estate);\n\n\t/* ensure slot is independent, consider e.g. EPQ */\n\tExecMaterializeSlot(slot);\n\n\t/*\n\t * If partition constraint fails, this row might get moved to another\n\t * partition, in which case we should check the RLS CHECK policy just\n\t * before inserting into the new partition, rather than doing it here.\n\t * This is because a trigger on that partition might again change the row.\n\t * So skip the WCO checks if the partition constraint fails.\n\t */\n\tpartition_constraint_failed =\n\t\tresultRelationDesc->rd_rel->relispartition &&\n\t\t!ExecPartitionCheck(resultRelInfo, slot, estate, false);\n\n\t/* Check any RLS UPDATE WITH CHECK policies */\n\tif (!partition_constraint_failed &&\n\t\tresultRelInfo->ri_WithCheckOptions != NIL)\n\t{\n\t\t/*\n\t\t * ExecWithCheckOptions() will skip any WCOs which are not of the kind\n\t\t * we are looking for at this point.\n\t\t */\n\t\tExecWithCheckOptions(WCO_RLS_UPDATE_CHECK,\n\t\t\t\t\t\t\t resultRelInfo, slot, estate);\n\t}\n\n\t/*\n\t * If a partition check failed, try to move the row into the right\n\t * partition.\n\t */\n\tif (partition_constraint_failed)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot update partition key of hypertable\"),\n\t\t\t\t errdetail(\"The partition constraint failed, and the row was not moved to another partition.\"),\n\t\t\t\t errhint(\"Use DELETE and INSERT to change the partition key.\")));\n\t}\n\n\t/*\n\t * Check the constraints of the tuple.  We've already checked the\n\t * partition constraint above; however, we must still ensure the tuple\n\t * passes all other constraints, so we will call ExecConstraints() and\n\t * have it validate all remaining checks.\n\t */\n\tif (resultRelationDesc->rd_att->constr)\n\t\tExecConstraints(resultRelInfo, slot, estate);\n\n\t/*\n\t * replace the heap tuple\n\t *\n\t * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that\n\t * the row to be updated is visible to that snapshot, and throw a\n\t * can't-serialize error if not. This is a special-case behavior needed\n\t * for referential integrity updates in transaction-snapshot mode\n\t * transactions.\n\t */\n\tresult = table_tuple_update(resultRelationDesc, tupleid, slot,\n\t\t\t\t\t\t\t\testate->es_output_cid,\n\t\t\t\t\t\t\t\testate->es_snapshot,\n\t\t\t\t\t\t\t\testate->es_crosscheck_snapshot,\n\t\t\t\t\t\t\t\ttrue /* wait for commit */ ,\n\t\t\t\t\t\t\t\t&context->tmfd, &updateCxt->lockmode,\n\t\t\t\t\t\t\t\t&updateCxt->updateIndexes);\n\n\treturn result;\n}\n\n/*\n * ExecUpdateEpilogue -- subroutine for ExecUpdate\n *\n * Closing steps of updating a tuple.  Must be called if ExecUpdateAct\n * returns indicating that the tuple was updated.\n */\nstatic void\nExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,\n\t\t\t\t   ResultRelInfo *resultRelInfo, ItemPointer tupleid,\n\t\t\t\t   HeapTuple oldtuple, TupleTableSlot *slot)\n{\n\tModifyTableState *mtstate = context->mtstate;\n\tList\t   *recheckIndexes = NIL;\n\n\t/* insert index entries for tuple if necessary */\n#if PG15\n\tif (resultRelInfo->ri_NumIndices > 0 && updateCxt->updateIndexes)\n\t\trecheckIndexes = ExecInsertIndexTuples(resultRelInfo,\n\t\t\t\t\t\t\t\t\t\t\t   slot, context->estate,\n\t\t\t\t\t\t\t\t\t\t\t   true, false,\n\t\t\t\t\t\t\t\t\t\t\t   NULL, NIL);\n#else\n\tif (resultRelInfo->ri_NumIndices > 0 && (updateCxt->updateIndexes != TU_None))\n\t\trecheckIndexes = ExecInsertIndexTuples(resultRelInfo,\n\t\t\t\t\t\t\t\t\t\t\t   slot, context->estate,\n\t\t\t\t\t\t\t\t\t\t\t   true, false,\n\t\t\t\t\t\t\t\t\t\t\t   NULL, NIL,\n\t\t\t\t\t\t\t\t\t\t\t   (updateCxt->updateIndexes == TU_Summarizing));\n#endif\n\n\t/* AFTER ROW UPDATE Triggers */\n\tExecARUpdateTriggers(context->estate, resultRelInfo,\n\t\t\t\t\t\t NULL, NULL,\n\t\t\t\t\t\t tupleid, oldtuple, slot,\n\t\t\t\t\t\t recheckIndexes,\n\t\t\t\t\t\t mtstate->operation == CMD_INSERT ?\n\t\t\t\t\t\t mtstate->mt_oc_transition_capture :\n\t\t\t\t\t\t mtstate->mt_transition_capture,\n\t\t\t\t\t\t false);\n\n\tlist_free(recheckIndexes);\n\n\t/*\n\t * Check any WITH CHECK OPTION constraints from parent views.  We are\n\t * required to do this after testing all constraints and uniqueness\n\t * violations per the SQL spec, so we do it after actually updating the\n\t * record in the heap and all indexes.\n\t *\n\t * ExecWithCheckOptions() will skip any WCOs which are not of the kind we\n\t * are looking for at this point.\n\t */\n\tif (resultRelInfo->ri_WithCheckOptions != NIL)\n\t\tExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo,\n\t\t\t\t\t\t\t slot, context->estate);\n}\n\n/* ----------------------------------------------------------------\n *\t\tExecUpdate\n *\n *\t\tnote: we can't run UPDATE queries with transactions\n *\t\toff because UPDATEs are actually INSERTs and our\n *\t\tscan will mistakenly loop forever, updating the tuple\n *\t\tit just inserted..  This should be fixed but until it\n *\t\tis, we don't want to get stuck in an infinite loop\n *\t\twhich corrupts your database..\n *\n *\t\tWhen updating a table, tupleid identifies the tuple to update and\n *\t\toldtuple is NULL.  When updating through a view INSTEAD OF trigger,\n *\t\toldtuple is passed to the triggers and identifies what to update, and\n *\t\ttupleid is invalid.  When updating a foreign table, tupleid is\n *\t\tinvalid; the FDW has to figure out which row to update using data from\n *\t\tthe planSlot.  oldtuple is passed to foreign table triggers; it is\n *\t\tNULL when the foreign table has no relevant triggers.\n *\n *\t\tslot contains the new tuple value to be stored.\n *\t\tplanSlot is the output of the ModifyTable's subplan; we use it\n *\t\tto access values from other input tables (for RETURNING),\n *\t\trow-ID junk columns, etc.\n *\n *\t\tReturns RETURNING result if any, otherwise NULL.  On exit, if tupleid\n *\t\thad identified the tuple to update, it will identify the tuple\n *\t\tactually updated after EvalPlanQual.\n * ----------------------------------------------------------------\n */\nstatic TupleTableSlot *\nExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,\n\t\t   ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *oldSlot,\n\t\t   TupleTableSlot *slot, bool canSetTag)\n{\n\tEState\t   *estate = context->estate;\n\tRelation\tresultRelationDesc = resultRelInfo->ri_RelationDesc;\n\tUpdateContext updateCxt = {0};\n\tTM_Result\tresult;\n\n\t/*\n\t * abort the operation if not running transactions\n\t */\n\tif (IsBootstrapProcessingMode())\n\t\telog(ERROR, \"cannot UPDATE during bootstrap\");\n\n\t/*\n\t * Prepare for the update.  This includes BEFORE ROW triggers, so we're\n\t * done if it says we are.\n\t */\n\tif (!ExecUpdatePrologue(context, resultRelInfo, tupleid, oldtuple, slot, NULL))\n\t\treturn NULL;\n\n\t/* INSTEAD OF ROW UPDATE Triggers */\n\tif (resultRelInfo->ri_TrigDesc &&\n\t\tresultRelInfo->ri_TrigDesc->trig_update_instead_row)\n\t{\n\t\tif (!ExecIRUpdateTriggers(estate, resultRelInfo,\n\t\t\t\t\t\t\t\t  oldtuple, slot))\n\t\t\treturn NULL;\t\t/* \"do nothing\" */\n\t}\n\telse if (resultRelInfo->ri_FdwRoutine)\n\t{\n\t\tereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\terrmsg(\"updating foreign tables not supported in hypertable context\")));\n\t}\n\telse\n\t{\n#if PG16_GE\n\t\tItemPointerData lockedtid;\n#endif\n\n\t\t/*\n\t\t * If we generate a new candidate tuple after EvalPlanQual testing, we\n\t\t * must loop back here to try again.  (We don't need to redo triggers,\n\t\t * however.  If there are any BEFORE triggers then trigger.c will have\n\t\t * done table_tuple_lock to lock the correct tuple, so there's no need\n\t\t * to do them again.)\n\t\t */\nredo_act:\n#if PG16_GE\n\t\tlockedtid = *tupleid;\n#endif\n\t\tresult = ExecUpdateAct(context, resultRelInfo, tupleid, oldtuple, slot,\n\t\t\t\t\t\t\t   canSetTag, &updateCxt);\n\n\t\t/*\n\t\t * If ExecUpdateAct reports that a cross-partition update was done,\n\t\t * then the RETURNING tuple (if any) has been projected and there's\n\t\t * nothing else for us to do.\n\t\t */\n\t\tif (updateCxt.crossPartUpdate)\n\t\t\treturn context->cpUpdateReturningSlot;\n\n\t\tswitch (result)\n\t\t{\n\t\t\tcase TM_SelfModified:\n\n\t\t\t\t/*\n\t\t\t\t * The target tuple was already updated or deleted by the\n\t\t\t\t * current command, or by a later command in the current\n\t\t\t\t * transaction.  The former case is possible in a join UPDATE\n\t\t\t\t * where multiple tuples join to the same target tuple. This\n\t\t\t\t * is pretty questionable, but Postgres has always allowed it:\n\t\t\t\t * we just execute the first update action and ignore\n\t\t\t\t * additional update attempts.\n\t\t\t\t *\n\t\t\t\t * The latter case arises if the tuple is modified by a\n\t\t\t\t * command in a BEFORE trigger, or perhaps by a command in a\n\t\t\t\t * volatile function used in the query.  In such situations we\n\t\t\t\t * should not ignore the update, but it is equally unsafe to\n\t\t\t\t * proceed.  We don't want to discard the original UPDATE\n\t\t\t\t * while keeping the triggered actions based on it; and we\n\t\t\t\t * have no principled way to merge this update with the\n\t\t\t\t * previous ones.  So throwing an error is the only safe\n\t\t\t\t * course.\n\t\t\t\t *\n\t\t\t\t * If a trigger actually intends this type of interaction, it\n\t\t\t\t * can re-execute the UPDATE (assuming it can figure out how)\n\t\t\t\t * and then return NULL to cancel the outer update.\n\t\t\t\t */\n\t\t\t\tif (context->tmfd.cmax != estate->es_output_cid)\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION),\n\t\t\t\t\t\t\t errmsg(\"tuple to be updated was already modified by an operation triggered by the current command\"),\n\t\t\t\t\t\t\t errhint(\"Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.\")));\n\n\t\t\t\t/* Else, already updated by self; nothing to do */\n\t\t\t\treturn NULL;\n\n\t\t\tcase TM_Ok:\n\t\t\t\tbreak;\n\n\t\t\tcase TM_Updated:\n\t\t\t\t{\n\t\t\t\t\tTupleTableSlot *inputslot;\n\t\t\t\t\tTupleTableSlot *epqslot;\n\t\t\t\t\tTupleTableSlot *oldSlot;\n\n\t\t\t\t\tif (IsolationUsesXactSnapshot())\n\t\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t\t(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),\n\t\t\t\t\t\t\t\t errmsg(\"could not serialize access due to concurrent update\")));\n\n\t\t\t\t\t/*\n\t\t\t\t\t * Already know that we're going to need to do EPQ, so\n\t\t\t\t\t * fetch tuple directly into the right slot.\n\t\t\t\t\t */\n\t\t\t\t\tinputslot = EvalPlanQualSlot(context->epqstate, resultRelationDesc,\n\t\t\t\t\t\t\t\t\t\t\t\t resultRelInfo->ri_RangeTableIndex);\n\n\t\t\t\t\tresult = table_tuple_lock(resultRelationDesc, tupleid,\n\t\t\t\t\t\t\t\t\t\t\t  estate->es_snapshot,\n\t\t\t\t\t\t\t\t\t\t\t  inputslot, estate->es_output_cid,\n\t\t\t\t\t\t\t\t\t\t\t  updateCxt.lockmode, LockWaitBlock,\n\t\t\t\t\t\t\t\t\t\t\t  TUPLE_LOCK_FLAG_FIND_LAST_VERSION,\n\t\t\t\t\t\t\t\t\t\t\t  &context->tmfd);\n\n\t\t\t\t\tswitch (result)\n\t\t\t\t\t{\n\t\t\t\t\t\tcase TM_Ok:\n\t\t\t\t\t\t\tAssert(context->tmfd.traversed);\n\n\t\t\t\t\t\t\tepqslot = EvalPlanQual(context->epqstate,\n\t\t\t\t\t\t\t\t\t\t\t\t   resultRelationDesc,\n\t\t\t\t\t\t\t\t\t\t\t\t   resultRelInfo->ri_RangeTableIndex,\n\t\t\t\t\t\t\t\t\t\t\t\t   inputslot);\n\t\t\t\t\t\t\tif (TupIsNull(epqslot))\n\t\t\t\t\t\t\t\t/* Tuple not passing quals anymore, exiting... */\n\t\t\t\t\t\t\t\treturn NULL;\n\n\t\t\t\t\t\t\t/* Make sure ri_oldTupleSlot is initialized. */\n\t\t\t\t\t\t\tif (unlikely(!resultRelInfo->ri_projectNewInfoValid))\n\t\t\t\t\t\t\t\tExecInitUpdateProjection(context->mtstate,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t resultRelInfo);\n\n#if PG16_GE\n\t\t\t\t\t\t\tif (resultRelInfo->ri_needLockTagTuple)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tUnlockTuple(resultRelationDesc,\n\t\t\t\t\t\t\t\t\t\t\t&lockedtid, InplaceUpdateTupleLock);\n\t\t\t\t\t\t\t\tLockTuple(resultRelationDesc,\n\t\t\t\t\t\t\t\t\t\t  tupleid, InplaceUpdateTupleLock);\n\t\t\t\t\t\t\t}\n#endif\n\n\t\t\t\t\t\t\t/* Fetch the most recent version of old tuple. */\n\t\t\t\t\t\t\toldSlot = resultRelInfo->ri_oldTupleSlot;\n\t\t\t\t\t\t\tif (!table_tuple_fetch_row_version(resultRelationDesc,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   tupleid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   SnapshotAny,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   oldSlot))\n\t\t\t\t\t\t\t\telog(ERROR, \"failed to fetch tuple being updated\");\n\t\t\t\t\t\t\tslot = ExecGetUpdateNewTuple(resultRelInfo,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t epqslot, oldSlot);\n\t\t\t\t\t\t\tgoto redo_act;\n\n\t\t\t\t\t\tcase TM_Deleted:\n\t\t\t\t\t\t\t/* tuple already deleted; nothing to do */\n\t\t\t\t\t\t\treturn NULL;\n\n\t\t\t\t\t\tcase TM_SelfModified:\n\n\t\t\t\t\t\t\t/*\n\t\t\t\t\t\t\t * This can be reached when following an update\n\t\t\t\t\t\t\t * chain from a tuple updated by another session,\n\t\t\t\t\t\t\t * reaching a tuple that was already updated in\n\t\t\t\t\t\t\t * this transaction. If previously modified by\n\t\t\t\t\t\t\t * this command, ignore the redundant update,\n\t\t\t\t\t\t\t * otherwise error out.\n\t\t\t\t\t\t\t *\n\t\t\t\t\t\t\t * See also TM_SelfModified response to\n\t\t\t\t\t\t\t * table_tuple_update() above.\n\t\t\t\t\t\t\t */\n\t\t\t\t\t\t\tif (context->tmfd.cmax != estate->es_output_cid)\n\t\t\t\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t\t\t\t(errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION),\n\t\t\t\t\t\t\t\t\t\t errmsg(\"tuple to be updated was already modified by an operation triggered by the current command\"),\n\t\t\t\t\t\t\t\t\t\t errhint(\"Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.\")));\n\t\t\t\t\t\t\treturn NULL;\n\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t/* see table_tuple_lock call in ExecDelete() */\n\t\t\t\t\t\t\telog(ERROR, \"unexpected table_tuple_lock status: %u\",\n\t\t\t\t\t\t\t\t result);\n\t\t\t\t\t\t\treturn NULL;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\n\t\t\tcase TM_Deleted:\n\t\t\t\tif (IsolationUsesXactSnapshot())\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),\n\t\t\t\t\t\t\t errmsg(\"could not serialize access due to concurrent delete\")));\n\t\t\t\t/* tuple already deleted; nothing to do */\n\t\t\t\treturn NULL;\n\n\t\t\tdefault:\n\t\t\t\telog(ERROR, \"unrecognized table_tuple_update status: %u\",\n\t\t\t\t\t result);\n\t\t\t\treturn NULL;\n\t\t}\n\t}\n\n\tif (canSetTag)\n\t\t(estate->es_processed)++;\n\n\tExecUpdateEpilogue(context, &updateCxt, resultRelInfo, tupleid, oldtuple,\n\t\t\t\t\t   slot);\n\n\tif (context->ht_state->has_continuous_aggregate)\n\t{\n\t\tTupleTableSlot *invalidation_slot = NULL;\n\t\tbool should_free_old = false, should_free_new = false;\n\t\tif (!oldtuple)\n\t\t{\n\t\t\tinvalidation_slot = table_slot_create(resultRelationDesc, NULL);\n\t\t\ttable_tuple_fetch_row_version(resultRelationDesc, tupleid, SnapshotAny, invalidation_slot);\n\t\t\toldtuple = ExecFetchSlotHeapTuple(invalidation_slot, false, &should_free_old);\n\t\t}\n\t\tHeapTuple newtuple = ExecFetchSlotHeapTuple(slot, false, &should_free_new);\n\t\tts_cm_functions->continuous_agg_dml_invalidate(context->ht_state->ht->fd.id, resultRelInfo->ri_RelationDesc, oldtuple, newtuple, true);\n\t\tif (should_free_old)\n\t\t\theap_freetuple(oldtuple);\n\t\tif (should_free_new)\n\t\t\theap_freetuple(newtuple);\n\t\tif (invalidation_slot)\n\t\t\tExecDropSingleTupleTableSlot(invalidation_slot);\n\t}\n\n\t/* Process RETURNING if present */\n\tif (resultRelInfo->ri_projectReturning)\n\t\treturn ExecProcessReturning(resultRelInfo, CMD_UPDATE, oldSlot, slot, context->planSlot);\n\n\treturn NULL;\n}\n\n/*\n * ExecOnConflictUpdate --- execute UPDATE of INSERT ON CONFLICT DO UPDATE\n *\n * Try to lock tuple for update as part of speculative insertion.  If\n * a qual originating from ON CONFLICT DO UPDATE is satisfied, update\n * (but still lock row, even though it may not satisfy estate's\n * snapshot).\n *\n * Returns true if we're done (with or without an update), or false if\n * the caller must retry the INSERT from scratch.\n */\nstatic bool\nExecOnConflictUpdate(ModifyTableContext *context,\n\t\t\t\t\t ResultRelInfo *resultRelInfo,\n\t\t\t\t\t ItemPointer conflictTid,\n\t\t\t\t\t TupleTableSlot *excludedSlot,\n\t\t\t\t\t bool canSetTag,\n\t\t\t\t\t TupleTableSlot **returning)\n{\n\tModifyTableState *mtstate = context->mtstate;\n\tExprContext *econtext = mtstate->ps.ps_ExprContext;\n\tRelation\trelation = resultRelInfo->ri_RelationDesc;\n\tExprState  *onConflictSetWhere = resultRelInfo->ri_onConflict->oc_WhereClause;\n\tTupleTableSlot *existing = resultRelInfo->ri_onConflict->oc_Existing;\n\tTM_FailureData tmfd;\n\tLockTupleMode lockmode;\n\tTM_Result\ttest;\n\tDatum\t\txminDatum;\n\tTransactionId xmin;\n\tbool\t\tisnull;\n\n\t/*\n\t * Parse analysis should have blocked ON CONFLICT for all system\n\t * relations, which includes these.  There's no fundamental obstacle to\n\t * supporting this; we'd just need to handle LOCKTAG_TUPLE like the other\n\t * ExecUpdate() caller.\n\t */\n#if PG16_GE\n\tAssert(!resultRelInfo->ri_needLockTagTuple);\n#endif\n\n\t/* Determine lock mode to use */\n\tlockmode = ExecUpdateLockMode(context->estate, resultRelInfo);\n\n\t/*\n\t * Lock tuple for update.  Don't follow updates when tuple cannot be\n\t * locked without doing so.  A row locking conflict here means our\n\t * previous conclusion that the tuple is conclusively committed is not\n\t * true anymore.\n\t */\n\ttest = table_tuple_lock(relation, conflictTid,\n\t\t\t\t\t\t\tcontext->estate->es_snapshot,\n\t\t\t\t\t\t\texisting, context->estate->es_output_cid,\n\t\t\t\t\t\t\tlockmode, LockWaitBlock, 0,\n\t\t\t\t\t\t\t&tmfd);\n\tswitch (test)\n\t{\n\t\tcase TM_Ok:\n\t\t\t/* success! */\n\t\t\tbreak;\n\n\t\tcase TM_Invisible:\n\n\t\t\t/*\n\t\t\t * This can occur when a just inserted tuple is updated again in\n\t\t\t * the same command. E.g. because multiple rows with the same\n\t\t\t * conflicting key values are inserted.\n\t\t\t *\n\t\t\t * This is somewhat similar to the ExecUpdate() TM_SelfModified\n\t\t\t * case.  We do not want to proceed because it would lead to the\n\t\t\t * same row being updated a second time in some unspecified order,\n\t\t\t * and in contrast to plain UPDATEs there's no historical behavior\n\t\t\t * to break.\n\t\t\t *\n\t\t\t * It is the user's responsibility to prevent this situation from\n\t\t\t * occurring.  These problems are why the SQL standard similarly\n\t\t\t * specifies that for SQL MERGE, an exception must be raised in\n\t\t\t * the event of an attempt to update the same row twice.\n\t\t\t */\n\t\t\txminDatum = slot_getsysattr(existing,\n\t\t\t\t\t\t\t\t\t\tMinTransactionIdAttributeNumber,\n\t\t\t\t\t\t\t\t\t\t&isnull);\n\t\t\tAssert(!isnull);\n\t\t\txmin = DatumGetTransactionId(xminDatum);\n\n\t\t\tif (TransactionIdIsCurrentTransactionId(xmin))\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_CARDINALITY_VIOLATION),\n\t\t\t\t/* translator: %s is a SQL command name */\n\t\t\t\t\t\t errmsg(\"%s command cannot affect row a second time\",\n\t\t\t\t\t\t\t\t\"ON CONFLICT DO UPDATE\"),\n\t\t\t\t\t\t errhint(\"Ensure that no rows proposed for insertion within the same command have duplicate constrained values.\")));\n\n\t\t\t/* This shouldn't happen */\n\t\t\telog(ERROR, \"attempted to lock invisible tuple\");\n\t\t\tbreak;\n\n\t\tcase TM_SelfModified:\n\n\t\t\t/*\n\t\t\t * This state should never be reached. As a dirty snapshot is used\n\t\t\t * to find conflicting tuples, speculative insertion wouldn't have\n\t\t\t * seen this row to conflict with.\n\t\t\t */\n\t\t\telog(ERROR, \"unexpected self-updated tuple\");\n\t\t\tbreak;\n\n\t\tcase TM_Updated:\n\t\t\tif (IsolationUsesXactSnapshot())\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),\n\t\t\t\t\t\t errmsg(\"could not serialize access due to concurrent update\")));\n\n\t\t\t/*\n\t\t\t * As long as we don't support an UPDATE of INSERT ON CONFLICT for\n\t\t\t * a partitioned table we shouldn't reach to a case where tuple to\n\t\t\t * be lock is moved to another partition due to concurrent update\n\t\t\t * of the partition key.\n\t\t\t */\n\t\t\tAssert(!ItemPointerIndicatesMovedPartitions(&tmfd.ctid));\n\n\t\t\t/*\n\t\t\t * Tell caller to try again from the very start.\n\t\t\t *\n\t\t\t * It does not make sense to use the usual EvalPlanQual() style\n\t\t\t * loop here, as the new version of the row might not conflict\n\t\t\t * anymore, or the conflicting tuple has actually been deleted.\n\t\t\t */\n\t\t\tExecClearTuple(existing);\n\t\t\treturn false;\n\n\t\tcase TM_Deleted:\n\t\t\tif (IsolationUsesXactSnapshot())\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),\n\t\t\t\t\t\t errmsg(\"could not serialize access due to concurrent delete\")));\n\n\t\t\t/* see TM_Updated case */\n\t\t\tAssert(!ItemPointerIndicatesMovedPartitions(&tmfd.ctid));\n\t\t\tExecClearTuple(existing);\n\t\t\treturn false;\n\n\t\tdefault:\n\t\t\telog(ERROR, \"unrecognized table_tuple_lock status: %u\", test);\n\t}\n\n\t/* Success, the tuple is locked. */\n\n\t/*\n\t * Verify that the tuple is visible to our MVCC snapshot if the current\n\t * isolation level mandates that.\n\t *\n\t * It's not sufficient to rely on the check within ExecUpdate() as e.g.\n\t * CONFLICT ... WHERE clause may prevent us from reaching that.\n\t *\n\t * This means we only ever continue when a new command in the current\n\t * transaction could see the row, even though in READ COMMITTED mode the\n\t * tuple will not be visible according to the current statement's\n\t * snapshot.  This is in line with the way UPDATE deals with newer tuple\n\t * versions.\n\t */\n\tExecCheckTupleVisible(context->estate, relation, existing);\n\n\t/*\n\t * Make tuple and any needed join variables available to ExecQual and\n\t * ExecProject.  The EXCLUDED tuple is installed in ecxt_innertuple, while\n\t * the target's existing tuple is installed in the scantuple.  EXCLUDED\n\t * has been made to reference INNER_VAR in setrefs.c, but there is no\n\t * other redirection.\n\t */\n\tecontext->ecxt_scantuple = existing;\n\tecontext->ecxt_innertuple = excludedSlot;\n\tecontext->ecxt_outertuple = NULL;\n\n\tif (!ExecQual(onConflictSetWhere, econtext))\n\t{\n\t\tExecClearTuple(existing);\t/* see return below */\n\t\tInstrCountFiltered1(&mtstate->ps, 1);\n\t\treturn true;\t\t\t/* done with the tuple */\n\t}\n\n\tif (resultRelInfo->ri_WithCheckOptions != NIL)\n\t{\n\t\t/*\n\t\t * Check target's existing tuple against UPDATE-applicable USING\n\t\t * security barrier quals (if any), enforced here as RLS checks/WCOs.\n\t\t *\n\t\t * The rewriter creates UPDATE RLS checks/WCOs for UPDATE security\n\t\t * quals, and stores them as WCOs of \"kind\" WCO_RLS_CONFLICT_CHECK,\n\t\t * but that's almost the extent of its special handling for ON\n\t\t * CONFLICT DO UPDATE.\n\t\t *\n\t\t * The rewriter will also have associated UPDATE applicable straight\n\t\t * RLS checks/WCOs for the benefit of the ExecUpdate() call that\n\t\t * follows.  INSERTs and UPDATEs naturally have mutually exclusive WCO\n\t\t * kinds, so there is no danger of spurious over-enforcement in the\n\t\t * INSERT or UPDATE path.\n\t\t */\n\t\tExecWithCheckOptions(WCO_RLS_CONFLICT_CHECK, resultRelInfo,\n\t\t\t\t\t\t\t existing,\n\t\t\t\t\t\t\t mtstate->ps.state);\n\t}\n\n\t/* Project the new tuple version */\n\tExecProject(resultRelInfo->ri_onConflict->oc_ProjInfo);\n\n\t/*\n\t * Note that it is possible that the target tuple has been modified in\n\t * this session, after the above table_tuple_lock. We choose to not error\n\t * out in that case, in line with ExecUpdate's treatment of similar cases.\n\t * This can happen if an UPDATE is triggered from within ExecQual(),\n\t * ExecWithCheckOptions() or ExecProject() above, e.g. by selecting from a\n\t * wCTE in the ON CONFLICT's SET.\n\t */\n\n\t/* Execute UPDATE with projection */\n\t*returning = ExecUpdate(context, resultRelInfo,\n\t\t\t\t\t\t\tconflictTid, NULL, existing,\n\t\t\t\t\t\t\tresultRelInfo->ri_onConflict->oc_ProjSlot,\n\t\t\t\t\t\t\tcanSetTag);\n\n\t/*\n\t * Clear out existing tuple, as there might not be another conflict among\n\t * the next input rows. Don't want to hold resources till the end of the\n\t * query.  First though, make sure that the returning slot has a local\n\t * copy of any pass-by-reference values.\n\t */\n\tif (*returning != NULL)\n\t\tExecMaterializeSlot(*returning);\n\tExecClearTuple(existing);\n\treturn true;\n}\n\n\nstatic void fireASTriggers(ModifyTableState *node);\nstatic void fireBSTriggers(ModifyTableState *node);\nstatic void checkDMLOnFrozenChunk(ResultRelInfo *resultRelInfo);\n\n/* ----------------------------------------------------------------\n *\t   ExecModifyTable\n *\n *\t\tPerform table modifications as required, and return RETURNING results\n *\t\tif needed.\n * ----------------------------------------------------------------\n *\n * modified version of ExecModifyTable from executor/nodeModifyTable.c\n */\nTupleTableSlot *\nExecModifyTable(CustomScanState *cs_node, PlanState *pstate)\n{\n\tModifyHypertableState *ht_state = (ModifyHypertableState *) cs_node;\n\tModifyTableState *node = castNode(ModifyTableState, pstate);\n\tModifyTableContext context;\n\tEState *estate = node->ps.state;\n\tCmdType operation = node->operation;\n\tResultRelInfo *resultRelInfo;\n\tPlanState *subplanstate;\n\tTupleTableSlot *slot;\n\tTupleTableSlot *oldSlot;\n\tItemPointer tupleid;\n\tItemPointerData tuple_ctid;\n\tHeapTupleData oldtupdata;\n\tHeapTuple oldtuple;\n\tList *relinfos = NIL;\n\tListCell *lc;\n\tChunkTupleRouting *ctr = ht_state->ctr;\n\n\tCHECK_FOR_INTERRUPTS();\n\n\t/*\n\t * This should NOT get called during EvalPlanQual; we should have passed a\n\t * subplan tree to EvalPlanQual, instead.  Use a runtime test not just\n\t * Assert because this condition is easy to miss in testing.  (Note:\n\t * although ModifyTable should not get executed within an EvalPlanQual\n\t * operation, we do have to allow it to be initialized and shut down in\n\t * case it is within a CTE subplan.  Hence this test must be here, not in\n\t * ExecInitModifyTable.)\n\t */\n\tif (estate->es_epq_active != NULL)\n\t\telog(ERROR, \"ModifyTable should not be called during EvalPlanQual\");\n\n\t/*\n\t * If we've already completed processing, don't try to do more.  We need\n\t * this test because ExecPostprocessPlan might call us an extra time, and\n\t * our subplan's nodes aren't necessarily robust against being called\n\t * extra times.\n\t */\n\tif (node->mt_done)\n\t\treturn NULL;\n\n\t/*\n\t * On first call, fire BEFORE STATEMENT triggers before proceeding.\n\t */\n\tif (node->fireBSTriggers)\n\t{\n\t\tfireBSTriggers(node);\n\t\tnode->fireBSTriggers = false;\n\t}\n\n\t/* Preload local variables */\n\tresultRelInfo = node->resultRelInfo + node->mt_lastResultIndex;\n\tsubplanstate = outerPlanState(node);\n\n\t/*\n\t * Check for frozen chunk DML operation.\n\t * INSERTS are blocked in chunk tuple routing.\n\t */\n\tif (operation != CMD_INSERT)\n\t\tcheckDMLOnFrozenChunk(resultRelInfo);\n\n\t/* Set global context */\n\tcontext.ht_state = ht_state;\n\tcontext.mtstate = node;\n\tcontext.epqstate = &node->mt_epqstate;\n\tcontext.estate = estate;\n\n\t/*\n\t * For UPDATE/DELETE on compressed hypertable, decompress chunks and\n\t * move rows to uncompressed chunks.\n\t */\n\tif ((operation == CMD_DELETE || operation == CMD_UPDATE) && !ht_state->comp_chunks_processed)\n\t{\n\t\t/* Modify snapshot only if something got decompressed */\n\t\tif (ts_cm_functions->decompress_target_segments &&\n\t\t\tts_cm_functions->decompress_target_segments(ht_state))\n\t\t{\n\t\t\tht_state->comp_chunks_processed = true;\n\t\t\t/*\n\t\t\t * save snapshot set during ExecutorStart(), since this is the same\n\t\t\t * snapshot used to SeqScan of uncompressed chunks\n\t\t\t */\n\t\t\tht_state->snapshot = estate->es_snapshot;\n\n\t\t\tCommandCounterIncrement();\n\t\t\t/* use a static copy of current transaction snapshot\n\t\t\t * this needs to be a copy so we don't read trigger updates\n\t\t\t */\n\t\t\testate->es_snapshot = RegisterSnapshot(GetTransactionSnapshot());\n\t\t\t/* mark rows visible */\n\t\t\testate->es_output_cid = GetCurrentCommandId(true);\n\n\t\t\tif (ts_guc_max_tuples_decompressed_per_dml > 0)\n\t\t\t{\n\t\t\t\tif (ht_state->tuples_decompressed > ts_guc_max_tuples_decompressed_per_dml)\n\t\t\t\t{\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),\n\t\t\t\t\t\t\t errmsg(\"tuple decompression limit exceeded by operation\"),\n\t\t\t\t\t\t\t errdetail(\"current limit: %d, tuples decompressed: %lld\",\n\t\t\t\t\t\t\t\t\t   ts_guc_max_tuples_decompressed_per_dml,\n\t\t\t\t\t\t\t\t\t   (long long int) ht_state->tuples_decompressed),\n\t\t\t\t\t\t\t errhint(\"Consider increasing \"\n\t\t\t\t\t\t\t\t\t \"timescaledb.max_tuples_decompressed_per_dml_transaction or \"\n\t\t\t\t\t\t\t\t\t \"set to 0 (unlimited).\")));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t/* Account for tuples deleted via batch DELETE in compressed chunks */\n\t\tif (operation == CMD_DELETE && ht_state->tuples_deleted > 0)\n\t\t\testate->es_processed += ht_state->tuples_deleted;\n\t}\n\t/*\n\t * Fetch rows from subplan, and execute the required table modification\n\t * for each row.\n\t */\n\tfor (;;)\n\t{\n\t\tOid resultoid = InvalidOid;\n\t\t/*\n\t\t * Reset the per-output-tuple exprcontext.  This is needed because\n\t\t * triggers expect to use that context as workspace.  It's a bit ugly\n\t\t * to do this below the top level of the plan, however.  We might need\n\t\t * to rethink this later.\n\t\t */\n\t\tResetPerTupleExprContext(estate);\n\n\t\t/*\n\t\t * Reset per-tuple memory context used for processing on conflict and\n\t\t * returning clauses, to free any expression evaluation storage\n\t\t * allocated in the previous cycle.\n\t\t */\n\t\tif (pstate->ps_ExprContext)\n\t\t\tResetExprContext(pstate->ps_ExprContext);\n\n#if PG17_GE\n\t\t/*\n\t\t * If there is a pending MERGE ... WHEN NOT MATCHED [BY TARGET] action\n\t\t * to execute, do so now --- see the comments in ExecMerge().\n\t\t */\n\t\tif (node->mt_merge_pending_not_matched != NULL)\n\t\t{\n\t\t\tcontext.planSlot = node->mt_merge_pending_not_matched;\n\n\t\t\tslot = ExecMergeNotMatched(&context, node->resultRelInfo, ctr, node->canSetTag);\n\n\t\t\t/* Clear the pending action */\n\t\t\tnode->mt_merge_pending_not_matched = NULL;\n\n\t\t\t/*\n\t\t\t * If we got a RETURNING result, return it to the caller.  We'll\n\t\t\t * continue the work on next call.\n\t\t\t */\n\t\t\tif (slot)\n\t\t\t\treturn slot;\n\n\t\t\tcontinue; /* continue with the next tuple */\n\t\t}\n#endif\n\n\t\tcontext.planSlot = ExecProcNode(subplanstate);\n\n\t\t/* No more tuples to process? */\n\t\tif (TupIsNull(context.planSlot))\n\t\t\tbreak;\n\n\t\tif (operation == CMD_INSERT || operation == CMD_MERGE)\n\t\t{\n\t\t\tTupleTableSlot *slot = context.planSlot;\n\t\t\tif (operation == CMD_MERGE)\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * XXX do we need an additional support of NOT MATCHED BY SOURCE\n\t\t\t\t * for PG >= 17? See PostgreSQL commit 0294df2f1f84\n\t\t\t\t */\n#if PG17_GE\n\t\t\t\tList *actionStates = ctr->root_rri->ri_MergeActions[MERGE_WHEN_NOT_MATCHED_BY_TARGET];\n#else\n\t\t\t\tList *actionStates = ctr->root_rri->ri_notMatchedMergeAction;\n#endif\n\t\t\t\tListCell *l;\n\t\t\t\tforeach (l, actionStates)\n\t\t\t\t{\n\t\t\t\t\tMergeActionState *action = (MergeActionState *) lfirst(l);\n\t\t\t\t\tCmdType commandType = action->mas_action->commandType;\n\t\t\t\t\tif (commandType == CMD_INSERT)\n\t\t\t\t\t{\n\t\t\t\t\t\taction->mas_proj->pi_exprContext->ecxt_innertuple = slot;\n\t\t\t\t\t\tslot = ExecProject(action->mas_proj);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/* do tuple routing in short lived memory context */\n\t\t\tMemoryContext oldctx = MemoryContextSwitchTo(estate->es_per_tuple_exprcontext->ecxt_per_tuple_memory);\n\t\t\tPoint *point = ts_hyperspace_calculate_point(ctr->hypertable->space, slot);\n\n\t\t\t/* Find or create the insert state matching the point */\n\t\t\tctr->cis = ts_chunk_tuple_routing_find_chunk(ctr, point);\n\t\t\tbool update_counter = ctr->cis->onConflictAction == ONCONFLICT_UPDATE;\n\t\t\tts_chunk_tuple_routing_decompress_for_insert(ctr->cis, ctr->root_rri, slot, ctr->estate, update_counter);\n\t\t\tMemoryContextSwitchTo(oldctx);\n\n\t\t\t/* ON CONFLICT DO NOTHING optimization for columnstore */\n\t\t\tif (operation == CMD_INSERT && ctr->cis->skip_current_tuple)\n\t\t\t{\n\t\t\t\tctr->cis->skip_current_tuple = false;\n\t\t\t\tif (node->ps.instrument)\n\t\t\t\t\tnode->ps.instrument->ntuples2++;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t/* direct compress */\n\t\t\tif (operation == CMD_INSERT && ht_state->columnstore_insert)\n\t\t\t{\n\t\t\t\tctr->cis->columnstore_insert = true;\n\t\t\t\t/* Flush on chunk change */\n\t\t\t\tif (ht_state->compressor && ht_state->compressor_relid != RelationGetRelid(ctr->cis->rel))\n\t\t\t\t{\n\t\t\t\t  ts_cm_functions->compressor_flush(ht_state->compressor, ht_state->bulk_writer);\n\t\t\t\t  ts_cm_functions->compressor_free(ht_state->compressor, ht_state->bulk_writer);\n\t\t\t\t  ht_state->compressor = NULL;\n\t\t\t\t  ht_state->compressor_relid = InvalidOid;\n\t\t\t\t}\n\n\t\t\t\tif (!ht_state->compressor)\n\t\t\t\t{\n\t\t\t\t\tbool sort = ts_guc_enable_direct_compress_insert_sort_batches && !ts_guc_enable_direct_compress_insert_client_sorted;\n\t\t\t\t\tht_state->compressor = ts_cm_functions->compressor_init(ctr->cis->rel, &ht_state->bulk_writer, sort, ts_guc_direct_compress_insert_tuple_sort_limit);\n\t\t\t\t\tht_state->compressor_relid = RelationGetRelid(ctr->cis->rel);\n\n\t\t\t\t\tif (ht_state->has_continuous_aggregate)\n\t\t\t\t\t{\n\t\t\t\t\t\tts_cm_functions->compressor_set_invalidation(ht_state->compressor, ctr->hypertable, RelationGetRelid(ctr->cis->rel));\n\t\t\t\t\t}\n\n\t\t\t\t\t/* if client does not commit to global ordering, set chunk to unordered */\n\t\t\t\t\tif (!ts_guc_enable_direct_compress_insert_client_sorted)\n\t\t\t\t\t{\n\t\t\t\t\t\tChunk *chunk = ts_chunk_get_by_id(ctr->cis->chunk_id, true);\n\t\t\t\t\t\tif (!ts_chunk_is_unordered(chunk))\n\t\t\t\t\t\t\tts_chunk_set_unordered(chunk);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t/*\n\t\t\t\t * Compute generated stored columns before compressing.\n\t\t\t\t * The direct compress path skips ExecInsert, so generated\n\t\t\t\t * columns would otherwise remain NULL.\n\t\t\t\t */\n\t\t\t\tRelation rel = ctr->cis->rel;\n\t\t\t\tif (rel->rd_att->constr && rel->rd_att->constr->has_generated_stored)\n\t\t\t\t{\n\t\t\t\t\tslot->tts_tableOid = RelationGetRelid(rel);\n\t\t\t\t\tExecComputeStoredGenerated(ctr->root_rri, estate, slot, CMD_INSERT);\n\t\t\t\t}\n\n\t\t\t\tts_cm_functions->compressor_add_slot(ht_state->compressor, ht_state->bulk_writer, slot);\n\t\t\t\testate->es_processed++;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * copy INSERT merge action list to result relation info of corresponding chunk\n\t\t\t *\n\t\t\t * XXX do we need an additional support of NOT MATCHED BY SOURCE\n\t\t\t * for PG >= 17? See PostgreSQL commit 0294df2f1f84\n\t\t\t */\n\t\t\tif (operation == CMD_MERGE)\n#if PG17_GE\n\t\t\t\tctr->cis->result_relation_info->ri_MergeActions[MERGE_WHEN_NOT_MATCHED_BY_TARGET] =\n\t\t\t\t\tresultRelInfo->ri_MergeActions[MERGE_WHEN_NOT_MATCHED_BY_TARGET];\n#else\n\t\t\t\tctr->cis->result_relation_info->ri_notMatchedMergeAction = resultRelInfo->ri_notMatchedMergeAction;\n#endif\n\n\t\t}\n\n\t\t/*\n\t\t * When there are multiple result relations, each tuple contains a\n\t\t * junk column that gives the OID of the rel from which it came.\n\t\t * Extract it and select the correct result relation.\n\t\t */\n\t\tif (AttributeNumberIsValid(node->mt_resultOidAttno))\n\t\t{\n\t\t\tDatum datum;\n\t\t\tbool isNull;\n\n\t\t\tdatum = ExecGetJunkAttribute(context.planSlot, node->mt_resultOidAttno, &isNull);\n\t\t\tif (isNull)\n\t\t\t{\n\t\t\t\tif (operation == CMD_MERGE)\n\t\t\t\t{\n\t\t\t\t\tEvalPlanQualSetSlot(&node->mt_epqstate, context.planSlot);\n\t\t\t\t\tslot =\n\t\t\t\t\t\tExecMerge(&context, node->resultRelInfo, ctr, NULL, NULL, node->canSetTag);\n\t\t\t\t\tif (slot)\n\t\t\t\t\t\treturn slot;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\telog(ERROR, \"tableoid is NULL\");\n\t\t\t}\n\t\t\tresultoid = DatumGetObjectId(datum);\n\n\t\t\t/* If it's not the same as last time, we need to locate the rel */\n\t\t\tif (resultoid != node->mt_lastResultOid)\n\t\t\t{\n\t\t\t\tresultRelInfo = ExecLookupResultRelByOid(node, resultoid, false, true);\n\t\t\t\tcheckDMLOnFrozenChunk(resultRelInfo);\n\t\t\t}\n\t\t}\n\n\t\t/*\n\t\t * If resultRelInfo->ri_usesFdwDirectModify is true, all we need to do\n\t\t * here is compute the RETURNING expressions.\n\t\t */\n\t\tif (resultRelInfo->ri_usesFdwDirectModify)\n\t\t{\n\t\t\tAssert(resultRelInfo->ri_projectReturning);\n\n\t\t\t/*\n\t\t\t * A scan slot containing the data that was actually inserted,\n\t\t\t * updated or deleted has already been made available to\n\t\t\t * ExecProcessReturning by IterateDirectModify, so no need to\n\t\t\t * provide it here.\n\t\t\t */\n\t\t\tslot = ExecProcessReturning(resultRelInfo, operation, NULL, NULL, context.planSlot);\n\n\t\t\treturn slot;\n\t\t}\n\n\t\tEvalPlanQualSetSlot(&node->mt_epqstate, context.planSlot);\n\t\tslot = context.planSlot;\n\n\t\ttupleid = NULL;\n\t\toldtuple = NULL;\n\n\t\t/*\n\t\t * For UPDATE/DELETE, fetch the row identity info for the tuple to be\n\t\t * updated/deleted.  For a heap relation, that's a TID; otherwise we\n\t\t * may have a wholerow junk attr that carries the old tuple in toto.\n\t\t * Keep this in step with the part of ExecInitModifyTable that sets up\n\t\t * ri_RowIdAttNo.\n\t\t */\n\t\tif (operation == CMD_UPDATE || operation == CMD_DELETE || operation == CMD_MERGE)\n\t\t{\n\t\t\tchar relkind;\n\t\t\tDatum datum;\n\t\t\tbool isNull;\n\n\t\t\trelkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;\n\t\t\t/* Since this is a hypertable relkind should be RELKIND_RELATION for a local\n\t\t\t * chunk or  RELKIND_FOREIGN_TABLE for a chunk that is a foreign table\n\t\t\t * (OSM chunks)\n\t\t\t */\n\t\t\tAssert(relkind == RELKIND_RELATION || relkind == RELKIND_FOREIGN_TABLE);\n\n\t\t\tif (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||\n\t\t\t\trelkind == RELKIND_PARTITIONED_TABLE)\n\t\t\t{\n\t\t\t\t/* ri_RowIdAttNo refers to a ctid attribute */\n\t\t\t\tAssert(AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo));\n\t\t\t\tdatum = ExecGetJunkAttribute(slot, resultRelInfo->ri_RowIdAttNo, &isNull);\n\n\t\t\t\t/*\n\t\t\t\t * For commands other than MERGE, any tuples having a null row\n\t\t\t\t * identifier are errors.  For MERGE, we may need to handle\n\t\t\t\t * them as WHEN NOT MATCHED clauses if any, so do that.\n\t\t\t\t *\n\t\t\t\t * Note that we use the node's toplevel resultRelInfo, not any\n\t\t\t\t * specific partition's.\n\t\t\t\t */\n\t\t\t\tif (isNull)\n\t\t\t\t{\n\t\t\t\t\tif (operation == CMD_MERGE)\n\t\t\t\t\t{\n\t\t\t\t\t\tEvalPlanQualSetSlot(&node->mt_epqstate, context.planSlot);\n\t\t\t\t\t\tslot = ExecMerge(&context,\n\t\t\t\t\t\t\t\t\t\t node->resultRelInfo,\n\t\t\t\t\t\t\t\t\t\t ctr,\n\t\t\t\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t\t\t\t node->canSetTag);\n\t\t\t\t\t\tif (slot)\n\t\t\t\t\t\t\treturn slot;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\telog(ERROR, \"ctid is NULL\");\n\t\t\t\t}\n\n\t\t\t\ttupleid = (ItemPointer) DatumGetPointer(datum);\n\t\t\t\ttuple_ctid = *tupleid; /* be sure we don't free ctid!! */\n\t\t\t\ttupleid = &tuple_ctid;\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * Use the wholerow attribute, when available, to reconstruct the\n\t\t\t * old relation tuple.  The old tuple serves one or both of two\n\t\t\t * purposes: 1) it serves as the OLD tuple for row triggers, 2) it\n\t\t\t * provides values for any unchanged columns for the NEW tuple of\n\t\t\t * an UPDATE, because the subplan does not produce all the columns\n\t\t\t * of the target table.\n\t\t\t *\n\t\t\t * Note that the wholerow attribute does not carry system columns,\n\t\t\t * so foreign table triggers miss seeing those, except that we\n\t\t\t * know enough here to set t_tableOid.  Quite separately from\n\t\t\t * this, the FDW may fetch its own junk attrs to identify the row.\n\t\t\t *\n\t\t\t * Other relevant relkinds, currently limited to views, always\n\t\t\t * have a wholerow attribute.\n\t\t\t */\n\t\t\telse if (AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))\n\t\t\t{\n\t\t\t\tdatum = ExecGetJunkAttribute(slot, resultRelInfo->ri_RowIdAttNo, &isNull);\n#if PG17_GE\n\t\t\t\tif (isNull)\n\t\t\t\t{\n\t\t\t\t\tif (operation == CMD_MERGE)\n\t\t\t\t\t{\n\t\t\t\t\t\tEvalPlanQualSetSlot(&node->mt_epqstate, context.planSlot);\n\t\t\t\t\t\tslot = ExecMerge(&context,\n\t\t\t\t\t\t\t\t\t\t node->resultRelInfo,\n\t\t\t\t\t\t\t\t\t\t ctr,\n\t\t\t\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t\t\t\t node->canSetTag);\n\t\t\t\t\t\tif (slot)\n\t\t\t\t\t\t\treturn slot;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\telog(ERROR, \"wholerow is NULL\");\n\t\t\t\t}\n#else\n\t\t\t\t/* shouldn't ever get a null result... */\n\t\t\t\tif (isNull)\n\t\t\t\t\telog(ERROR, \"wholerow is NULL\");\n#endif\n\n\t\t\t\toldtupdata.t_data = DatumGetHeapTupleHeader(datum);\n\t\t\t\toldtupdata.t_len = HeapTupleHeaderGetDatumLength(oldtupdata.t_data);\n\t\t\t\tItemPointerSetInvalid(&(oldtupdata.t_self));\n\t\t\t\t/* Historically, view triggers see invalid t_tableOid. */\n\t\t\t\toldtupdata.t_tableOid = (relkind == RELKIND_VIEW) ?\n\t\t\t\t\t\t\t\t\t\t\tInvalidOid :\n\t\t\t\t\t\t\t\t\t\t\tRelationGetRelid(resultRelInfo->ri_RelationDesc);\n\n\t\t\t\toldtuple = &oldtupdata;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t/* Only foreign tables are allowed to omit a row-ID attr */\n\t\t\t\tAssert(relkind == RELKIND_FOREIGN_TABLE);\n\t\t\t}\n\t\t}\n\n\t\tswitch (operation)\n\t\t{\n\t\t\tcase CMD_INSERT:\n\t\t\t\t/* Initialize projection info if first time for this table */\n\t\t\t\tif (unlikely(!resultRelInfo->ri_projectNewInfoValid))\n\t\t\t\t\tExecInitInsertProjection(node, resultRelInfo);\n\t\t\t\tslot = ExecGetInsertNewTuple(resultRelInfo, context.planSlot);\n\t\t\t\tslot = ExecInsert(&context, resultRelInfo, ctr, slot, node->canSetTag);\n\t\t\t\tbreak;\n\t\t\tcase CMD_UPDATE:\n\t\t\t\t/* Initialize projection info if first time for this table */\n\t\t\t\tif (unlikely(!resultRelInfo->ri_projectNewInfoValid))\n\t\t\t\t\tExecInitUpdateProjection(node, resultRelInfo);\n\n\t\t\t\t/*\n\t\t\t\t * Make the new tuple by combining plan's output tuple with\n\t\t\t\t * the old tuple being updated.\n\t\t\t\t */\n\t\t\t\toldSlot = resultRelInfo->ri_oldTupleSlot;\n\t\t\t\tif (oldtuple != NULL)\n\t\t\t\t{\n\t\t\t\t\t/* Use the wholerow junk attr as the old tuple. */\n\t\t\t\t\tExecForceStoreHeapTuple(oldtuple, oldSlot, false);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t/* Fetch the most recent version of old tuple. */\n\t\t\t\t\tRelation relation = resultRelInfo->ri_RelationDesc;\n\n\t\t\t\t\tAssert(tupleid != NULL);\n\t\t\t\t\tif (!table_tuple_fetch_row_version(relation, tupleid, SnapshotAny, oldSlot))\n\t\t\t\t\t\telog(ERROR, \"failed to fetch tuple being updated\");\n\t\t\t\t}\n\t\t\t\tslot = ExecGetUpdateNewTuple(resultRelInfo, context.planSlot, oldSlot);\n\t\t\t\tcontext.relaction = NULL;\n\t\t\t\t/* Now apply the update. */\n\t\t\t\tslot =\n\t\t\t\t\tExecUpdate(&context, resultRelInfo, tupleid, oldtuple,\n\t\t\t\t\t\t\t   oldSlot, slot, node->canSetTag);\n\t\t\t\tbreak;\n\t\t\tcase CMD_DELETE:\n\t\t\t\tslot = ExecDelete(&context, resultRelInfo, tupleid, oldtuple,\n\t\t\t\t\t\t\t\t  true, false, node->canSetTag, NULL, NULL, NULL);\n\t\t\t\tbreak;\n\t\t\tcase CMD_MERGE:\n\t\t\t\tslot = ExecMerge(&context, resultRelInfo, ctr, tupleid, oldtuple, node->canSetTag);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\telog(ERROR, \"unknown operation\");\n\t\t\t\tbreak;\n\t\t}\n\n\t\t/*\n\t\t * If we got a RETURNING result, return it to caller.  We'll continue\n\t\t * the work on next call.\n\t\t */\n\t\tif (slot)\n\t\t\treturn slot;\n\t}\n\n\t/*\n\t * Insert remaining tuples for batch insert.\n\t */\n\trelinfos = estate->es_opened_result_relations;\n\n\tif (ht_state->comp_chunks_processed)\n\t{\n\t\tUnregisterSnapshot(estate->es_snapshot);\n\t\testate->es_snapshot = ht_state->snapshot;\n\t\tht_state->comp_chunks_processed = false;\n\t}\n\n\tforeach (lc, relinfos)\n\t{\n\t\tresultRelInfo = lfirst(lc);\n\t\tif (resultRelInfo->ri_NumSlots > 0)\n\t\t\tExecBatchInsert(node,\n\t\t\t\t\t\t\tresultRelInfo,\n\t\t\t\t\t\t\tresultRelInfo->ri_Slots,\n\t\t\t\t\t\t\tresultRelInfo->ri_PlanSlots,\n\t\t\t\t\t\t\tresultRelInfo->ri_NumSlots,\n\t\t\t\t\t\t\testate,\n\t\t\t\t\t\t\tnode->canSetTag);\n\t}\n\n\t/*\n\t * We're done, but fire AFTER STATEMENT triggers before exiting.\n\t */\n\tfireASTriggers(node);\n\n\tnode->mt_done = true;\n\n\treturn NULL;\n}\n\n/*\n * Process BEFORE EACH STATEMENT triggers\n *\n * copied verbatim from executor/nodeModifyTable.c\n */\nstatic void\nfireBSTriggers(ModifyTableState *node)\n{\n\tModifyTable *plan = (ModifyTable *) node->ps.plan;\n\tResultRelInfo *resultRelInfo = node->rootResultRelInfo;\n\n\tswitch (node->operation)\n\t{\n\t\tcase CMD_INSERT:\n\t\t\tExecBSInsertTriggers(node->ps.state, resultRelInfo);\n\t\t\tif (plan->onConflictAction == ONCONFLICT_UPDATE)\n\t\t\t\tExecBSUpdateTriggers(node->ps.state, resultRelInfo);\n\t\t\tbreak;\n\t\tcase CMD_UPDATE:\n\t\t\tExecBSUpdateTriggers(node->ps.state, resultRelInfo);\n\t\t\tbreak;\n\t\tcase CMD_DELETE:\n\t\t\tExecBSDeleteTriggers(node->ps.state, resultRelInfo);\n\t\t\tbreak;\n\t\tcase CMD_MERGE:\n\t\t\tif (node->mt_merge_subcommands & MERGE_INSERT)\n\t\t\t\tExecBSInsertTriggers(node->ps.state, resultRelInfo);\n\t\t\tif (node->mt_merge_subcommands & MERGE_UPDATE)\n\t\t\t\tExecBSUpdateTriggers(node->ps.state, resultRelInfo);\n\t\t\tif (node->mt_merge_subcommands & MERGE_DELETE)\n\t\t\t\tExecBSDeleteTriggers(node->ps.state, resultRelInfo);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\telog(ERROR, \"unknown operation\");\n\t\t\tbreak;\n\t}\n}\n\n/*\n * Process AFTER EACH STATEMENT triggers\n *\n * copied verbatim from executor/nodeModifyTable.c\n */\nstatic void\nfireASTriggers(ModifyTableState *node)\n{\n\tModifyTable *plan = (ModifyTable *) node->ps.plan;\n\tResultRelInfo *resultRelInfo = node->rootResultRelInfo;\n\n\tswitch (node->operation)\n\t{\n\t\tcase CMD_INSERT:\n\t\t\tif (plan->onConflictAction == ONCONFLICT_UPDATE)\n\t\t\t\tExecASUpdateTriggers(node->ps.state, resultRelInfo, node->mt_oc_transition_capture);\n\t\t\tExecASInsertTriggers(node->ps.state, resultRelInfo, node->mt_transition_capture);\n\t\t\tbreak;\n\t\tcase CMD_UPDATE:\n\t\t\tExecASUpdateTriggers(node->ps.state, resultRelInfo, node->mt_transition_capture);\n\t\t\tbreak;\n\t\tcase CMD_DELETE:\n\t\t\tExecASDeleteTriggers(node->ps.state, resultRelInfo, node->mt_transition_capture);\n\t\t\tbreak;\n\t\tcase CMD_MERGE:\n\t\t\tif (node->mt_merge_subcommands & MERGE_INSERT)\n\t\t\t\tExecASInsertTriggers(node->ps.state, resultRelInfo, node->mt_transition_capture);\n\t\t\tif (node->mt_merge_subcommands & MERGE_UPDATE)\n\t\t\t\tExecASUpdateTriggers(node->ps.state, resultRelInfo, node->mt_transition_capture);\n\t\t\tif (node->mt_merge_subcommands & MERGE_DELETE)\n\t\t\t\tExecASDeleteTriggers(node->ps.state, resultRelInfo, node->mt_transition_capture);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\telog(ERROR, \"unknown operation\");\n\t\t\tbreak;\n\t}\n}\n\nstatic void checkDMLOnFrozenChunk(ResultRelInfo *resultRelInfo)\n{\n\tChunk *chunk = ts_chunk_get_by_relid(resultRelInfo->ri_RelationDesc->rd_id, false);\n\tif (chunk && ts_chunk_is_frozen(chunk))\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"cannot update/delete rows from chunk \\\"%s\\\" as it is frozen\",\n\t\t\t\t\t\tget_rel_name(resultRelInfo->ri_RelationDesc->rd_id))));\n\t}\n}\n\n\n/*\n * Check and execute the first qualifying MATCHED action. The current target\n * tuple is identified by tupleid.\n *\n * We start from the first WHEN MATCHED action and check if the WHEN quals\n * pass, if any. If the WHEN quals for the first action do not pass, we check\n * the second, then the third and so on. If we reach to the end, no action is\n * taken and we return true, indicating that no further action is required\n * for this tuple.\n *\n * If we do find a qualifying action, then we attempt to execute the action.\n *\n * If the tuple is concurrently updated, EvalPlanQual is run with the updated\n * tuple to recheck the join quals. Note that the additional quals associated\n * with individual actions are evaluated by this routine via ExecQual, while\n * EvalPlanQual checks for the join quals. If EvalPlanQual tells us that the\n * updated tuple still passes the join quals, then we restart from the first\n * action to look for a qualifying action. Otherwise, we return false --\n * meaning that a NOT MATCHED action must now be executed for the current\n * source tuple.\n */\n\nTupleTableSlot *\nExecMergeMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer tupleid,\n\t\t\t\t HeapTuple oldtuple, bool canSetTag, bool *matched)\n{\n\tModifyTableState *mtstate = context->mtstate;\n\tTupleTableSlot *newslot = NULL;\n\tEState *estate = context->estate;\n\tExprContext *econtext = mtstate->ps.ps_ExprContext;\n\tbool isNull;\n\tEPQState *epqstate = &mtstate->mt_epqstate;\n\tListCell *l;\n\n\tTupleTableSlot *rslot = NULL;\n\n\tAssert(*matched == true);\n\n\t/*\n\t * If there are no WHEN MATCHED actions, we are done.\n\t */\n#if PG17_GE\n\tif (resultRelInfo->ri_MergeActions[MERGE_WHEN_MATCHED] == NIL)\n\t\treturn NULL;\n#else\n\tif (resultRelInfo->ri_matchedMergeAction == NIL)\n\t{\n\t\t*matched = true;\n\t\treturn NULL;\n\t}\n#endif\n\t/*\n\t * Make tuple and any needed join variables available to ExecQual and\n\t * ExecProject. The target's existing tuple is installed in the\n\t * scantuple. Again, this target relation's slot is required only in\n\t * the case of a MATCHED tuple and UPDATE/DELETE actions.\n\t */\n\tecontext->ecxt_scantuple = resultRelInfo->ri_oldTupleSlot;\n\tecontext->ecxt_innertuple = context->planSlot;\n\tecontext->ecxt_outertuple = NULL;\n\nlmerge_matched:;\n\n/*\n * This routine is only invoked for matched rows, and we must have\n * found the tupleid of the target row in that case; fetch that\n * tuple.\n *\n * We use SnapshotAny for this because we might get called again\n * after EvalPlanQual returns us a new tuple, which may not be\n * visible to our MVCC snapshot.\n */\n\n#if PG17_GE\n\tif (oldtuple != NULL)\n\t\tExecForceStoreHeapTuple(oldtuple, resultRelInfo->ri_oldTupleSlot, false);\n\telse\n#endif\n\t\tif (!table_tuple_fetch_row_version(resultRelInfo->ri_RelationDesc,\n\t\t\t\t\t\t\t\t\t\t   tupleid,\n\t\t\t\t\t\t\t\t\t\t   SnapshotAny,\n\t\t\t\t\t\t\t\t\t\t   resultRelInfo->ri_oldTupleSlot))\n\t\telog(ERROR, \"failed to fetch the target tuple\");\n#if PG17_GE\n\tforeach (l, resultRelInfo->ri_MergeActions[MERGE_WHEN_MATCHED])\n\t{\n#else\n\tforeach (l, resultRelInfo->ri_matchedMergeAction)\n\t{\n#endif\n\t\tMergeActionState *relaction = (MergeActionState *) lfirst(l);\n\t\tCmdType commandType = relaction->mas_action->commandType;\n\t\tTM_Result result;\n\t\tUpdateContext updateCxt = { 0 };\n\n\t\t/*\n\t\t * Test condition, if any.\n\t\t *\n\t\t * In the absence of any condition, we perform the action\n\t\t * unconditionally (no need to check separately since\n\t\t * ExecQual() will return true if there are no conditions to\n\t\t * evaluate).\n\t\t */\n\t\tif (!ExecQual(relaction->mas_whenqual, econtext))\n\t\t\tcontinue;\n\n\t\t/*\n\t\t * Check if the existing target tuple meets the USING checks\n\t\t * of UPDATE/DELETE RLS policies. If those checks fail, we\n\t\t * throw an error.\n\t\t *\n\t\t * The WITH CHECK quals are applied in ExecUpdate() and hence\n\t\t * we need not do anything special to handle them.\n\t\t *\n\t\t * NOTE: We must do this after WHEN quals are evaluated, so\n\t\t * that we check policies only when they matter.\n\t\t */\n\t\tif (resultRelInfo->ri_WithCheckOptions)\n\t\t{\n\t\t\tExecWithCheckOptions(commandType == CMD_UPDATE ? WCO_RLS_MERGE_UPDATE_CHECK :\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t WCO_RLS_MERGE_DELETE_CHECK,\n\t\t\t\t\t\t\t\t resultRelInfo,\n\t\t\t\t\t\t\t\t resultRelInfo->ri_oldTupleSlot,\n\t\t\t\t\t\t\t\t context->mtstate->ps.state);\n\t\t}\n\n\t\t/* Perform stated action */\n\t\tswitch (commandType)\n\t\t{\n\t\t\tcase CMD_UPDATE:\n\n\t\t\t\t/*\n\t\t\t\t * Project the output tuple, and use that to update\n\t\t\t\t * the table. We don't need to filter out junk\n\t\t\t\t * attributes, because the UPDATE action's targetlist\n\t\t\t\t * doesn't have any.\n\t\t\t\t */\n\t\t\t\tnewslot = ExecProject(relaction->mas_proj);\n\n#if PG17_GE\n\t\t\t\tmtstate->mt_merge_action = relaction;\n#else\n\t\t\t\tcontext->relaction = relaction;\n#endif\n\t\t\t\tcontext->cpUpdateRetrySlot = NULL;\n\n\t\t\t\tif (!ExecUpdatePrologue(context, resultRelInfo, tupleid, NULL, newslot, &result))\n\t\t\t\t{\n#if PG16_LT\n\t\t\t\t\tresult = TM_Ok;\n#else\n\t\t\t\t\tif (result == TM_Ok)\n\t\t\t\t\t\treturn NULL;\n#endif\n\n\t\t\t\t\t/* if not TM_OK, it is concurrent update/delete */\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tExecUpdatePrepareSlot(resultRelInfo, newslot, context->estate);\n\t\t\t\tresult = ExecUpdateAct(context,\n\t\t\t\t\t\t\t\t\t\t  resultRelInfo,\n\t\t\t\t\t\t\t\t\t\t  tupleid,\n\t\t\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t\t\t  newslot,\n\t\t\t\t\t\t\t\t\t\t  mtstate->canSetTag,\n\t\t\t\t\t\t\t\t\t\t  &updateCxt);\n\t\t\t\tif (result == TM_Ok)\n\t\t\t\t{\n\t\t\t\t\tExecUpdateEpilogue(context,\n\t\t\t\t\t\t\t\t\t   &updateCxt,\n\t\t\t\t\t\t\t\t\t   resultRelInfo,\n\t\t\t\t\t\t\t\t\t   tupleid,\n\t\t\t\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t\t\t\t   newslot);\n\t\t\t\t\tmtstate->mt_merge_updated += 1;\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\n\t\t\tcase CMD_DELETE:\n#if PG17_GE\n\t\t\t\tmtstate->mt_merge_action = relaction;\n#else\n\t\t\t\tcontext->relaction = relaction;\n#endif\n\t\t\t\tif (!ExecDeletePrologue(context, resultRelInfo, tupleid, NULL, NULL, &result))\n\t\t\t\t{\n#if PG16_LT\n\t\t\t\t\tresult = TM_Ok;\n#else\n\t\t\t\t\tif (result == TM_Ok)\n\t\t\t\t\t\treturn NULL; /* \"do nothing\" */\n\n\t\t\t\t\t\t/* if not TM_OK, it is concurrent update/delete */\n#endif\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tresult = ExecDeleteAct(context, resultRelInfo, tupleid, false);\n\t\t\t\tif (result == TM_Ok)\n\t\t\t\t{\n\t\t\t\t\tExecDeleteEpilogue(context, resultRelInfo, tupleid, NULL, false);\n\t\t\t\t\tmtstate->mt_merge_deleted = 1;\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase CMD_NOTHING:\n\t\t\t\t/* Doing nothing is always OK */\n\t\t\t\tresult = TM_Ok;\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\telog(ERROR, \"unknown action in MERGE WHEN MATCHED clause\");\n\t\t}\n\n\t\tswitch (result)\n\t\t{\n\t\t\tcase TM_Ok:\n\t\t\t\t/* all good; perform final actions */\n\t\t\t\tif (canSetTag)\n\t\t\t\t\t(estate->es_processed)++;\n\n\t\t\t\tbreak;\n\n\t\t\tcase TM_SelfModified:\n\t\t\t\tif (context->tmfd.cmax != estate->es_output_cid)\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION),\n\t\t\t\t\t\t\t errmsg(\"tuple to be updated or deleted was already modified by an \"\n\t\t\t\t\t\t\t\t\t\"operation triggered by the current command\"),\n\t\t\t\t\t\t\t errhint(\"Consider using an AFTER trigger instead of a BEFORE trigger \"\n\t\t\t\t\t\t\t\t\t \"to propagate changes to other rows.\")));\n\n\t\t\t\tif (TransactionIdIsCurrentTransactionId(context->tmfd.xmax))\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_CARDINALITY_VIOLATION),\n\t\t\t\t\t\t\t /* translator: %s is a SQL command name */\n\t\t\t\t\t\t\t errmsg(\"%s command cannot affect row a second time\", \"MERGE\"),\n\t\t\t\t\t\t\t errhint(\"Ensure that not more than one source row matches any one \"\n\t\t\t\t\t\t\t\t\t \"target row.\")));\n\t\t\t\t/* This shouldn't happen */\n\t\t\t\telog(ERROR, \"attempted to update or delete invisible tuple\");\n\t\t\t\tbreak;\n\n\t\t\tcase TM_Deleted:\n\t\t\t\tif (IsolationUsesXactSnapshot())\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),\n\t\t\t\t\t\t\t errmsg(\"could not serialize access due to concurrent delete\")));\n\n\t\t\t\t/*\n\t\t\t\t * If the tuple was already deleted, return to let\n\t\t\t\t * caller handle it under NOT MATCHED clauses.\n\t\t\t\t */\n\t\t\t\t*matched = false;\n\t\t\t\treturn NULL;\n\n\t\t\tcase TM_Updated:\n\t\t\t{\n\t\t\t\tRelation resultRelationDesc;\n\t\t\t\tTupleTableSlot *epqslot, *inputslot;\n\t\t\t\tLockTupleMode lockmode;\n\n\t\t\t\t/*\n\t\t\t\t * The target tuple was concurrently updated\n\t\t\t\t * by some other transaction.\n\t\t\t\t */\n\n\t\t\t\t/*\n\t\t\t\t * If cpUpdateRetrySlot is set,\n\t\t\t\t * ExecCrossPartitionUpdate() must have\n\t\t\t\t * detected that the tuple was concurrently\n\t\t\t\t * updated, so we restart the search for an\n\t\t\t\t * appropriate WHEN MATCHED clause to process\n\t\t\t\t * the updated tuple.\n\t\t\t\t *\n\t\t\t\t * In this case, ExecDelete() would already\n\t\t\t\t * have performed EvalPlanQual() on the\n\t\t\t\t * latest version of the tuple, which in turn\n\t\t\t\t * would already have been loaded into\n\t\t\t\t * ri_oldTupleSlot, so no need to do either\n\t\t\t\t * of those things.\n\t\t\t\t *\n\t\t\t\t * XXX why do we not check the WHEN NOT\n\t\t\t\t * MATCHED list in this case?\n\t\t\t\t */\n\t\t\t\tif (!TupIsNull(context->cpUpdateRetrySlot))\n\t\t\t\t\tgoto lmerge_matched;\n\n\t\t\t\t/*\n\t\t\t\t * Otherwise, we run the EvalPlanQual() with\n\t\t\t\t * the new version of the tuple. If\n\t\t\t\t * EvalPlanQual() does not return a tuple,\n\t\t\t\t * then we switch to the NOT MATCHED list of\n\t\t\t\t * actions. If it does return a tuple and the\n\t\t\t\t * join qual is still satisfied, then we just\n\t\t\t\t * need to recheck the MATCHED actions,\n\t\t\t\t * starting from the top, and execute the\n\t\t\t\t * first qualifying action.\n\t\t\t\t */\n\t\t\t\tresultRelationDesc = resultRelInfo->ri_RelationDesc;\n\t\t\t\tlockmode = ExecUpdateLockMode(estate, resultRelInfo);\n\n\t\t\t\tinputslot = EvalPlanQualSlot(epqstate,\n\t\t\t\t\t\t\t\t\t\t\t resultRelationDesc,\n\t\t\t\t\t\t\t\t\t\t\t resultRelInfo->ri_RangeTableIndex);\n\n\t\t\t\tresult = table_tuple_lock(resultRelationDesc,\n\t\t\t\t\t\t\t\t\t\t  tupleid,\n\t\t\t\t\t\t\t\t\t\t  estate->es_snapshot,\n\t\t\t\t\t\t\t\t\t\t  inputslot,\n\t\t\t\t\t\t\t\t\t\t  estate->es_output_cid,\n\t\t\t\t\t\t\t\t\t\t  lockmode,\n\t\t\t\t\t\t\t\t\t\t  LockWaitBlock,\n\t\t\t\t\t\t\t\t\t\t  TUPLE_LOCK_FLAG_FIND_LAST_VERSION,\n\t\t\t\t\t\t\t\t\t\t  &context->tmfd);\n\t\t\t\tswitch (result)\n\t\t\t\t{\n\t\t\t\t\tcase TM_Ok:\n\t\t\t\t\t\t// TODO: update this to match PG17\n\t\t\t\t\t\tepqslot = EvalPlanQual(epqstate,\n\t\t\t\t\t\t\t\t\t\t\t   resultRelationDesc,\n\t\t\t\t\t\t\t\t\t\t\t   resultRelInfo->ri_RangeTableIndex,\n\t\t\t\t\t\t\t\t\t\t\t   inputslot);\n\n\t\t\t\t\t\t/*\n\t\t\t\t\t\t * If we got no tuple, or the tuple\n\t\t\t\t\t\t * we get has a NULL ctid, go back to\n\t\t\t\t\t\t * caller: this one is not a MATCHED\n\t\t\t\t\t\t * tuple anymore, so they can retry\n\t\t\t\t\t\t * with NOT MATCHED actions.\n\t\t\t\t\t\t */\n\t\t\t\t\t\tif (TupIsNull(epqslot))\n\t\t\t\t\t\t\treturn false;\n\n\t\t\t\t\t\t(void) ExecGetJunkAttribute(epqslot, resultRelInfo->ri_RowIdAttNo, &isNull);\n\t\t\t\t\t\tif (isNull)\n\t\t\t\t\t\t\treturn false;\n\n\t\t\t\t\t\t/*\n\t\t\t\t\t\t * When a tuple was updated and\n\t\t\t\t\t\t * migrated to another partition\n\t\t\t\t\t\t * concurrently, the current MERGE\n\t\t\t\t\t\t * implementation can't follow.\n\t\t\t\t\t\t * There's probably a better way to\n\t\t\t\t\t\t * handle this case, but it'd require\n\t\t\t\t\t\t * recognizing the relation to which\n\t\t\t\t\t\t * the tuple moved, and setting our\n\t\t\t\t\t\t * current resultRelInfo to that.\n\t\t\t\t\t\t */\n\t\t\t\t\t\tif (ItemPointerIndicatesMovedPartitions(&context->tmfd.ctid))\n\t\t\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t\t\t(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),\n\t\t\t\t\t\t\t\t\t errmsg(\"tuple to be deleted was already moved to another \"\n\t\t\t\t\t\t\t\t\t\t\t\"partition due to concurrent update\")));\n\n\t\t\t\t\t\t/*\n\t\t\t\t\t\t * A non-NULL ctid means that we are\n\t\t\t\t\t\t * still dealing with MATCHED case.\n\t\t\t\t\t\t * Restart the loop so that we apply\n\t\t\t\t\t\t * all the MATCHED rules again, to\n\t\t\t\t\t\t * ensure that the first qualifying\n\t\t\t\t\t\t * WHEN MATCHED action is executed.\n\t\t\t\t\t\t *\n\t\t\t\t\t\t * Update tupleid to that of the new\n\t\t\t\t\t\t * tuple, for the refetch we do at\n\t\t\t\t\t\t * the top.\n\t\t\t\t\t\t */\n\t\t\t\t\t\tEnsure(tupleid != NULL, \"matched tupleid during merge cannot be null\");\n\t\t\t\t\t\tItemPointerCopy(&context->tmfd.ctid, tupleid);\n\t\t\t\t\t\tgoto lmerge_matched;\n\n\t\t\t\t\tcase TM_Deleted:\n\n\t\t\t\t\t\t/*\n\t\t\t\t\t\t * tuple already deleted; tell caller\n\t\t\t\t\t\t * to run NOT MATCHED actions\n\t\t\t\t\t\t */\n\t\t\t\t\t\t*matched = false;\n\t\t\t\t\t\treturn NULL;\n\n\t\t\t\t\tcase TM_SelfModified:\n\n\t\t\t\t\t\t/*\n\t\t\t\t\t\t * This can be reached when following\n\t\t\t\t\t\t * an update chain from a tuple\n\t\t\t\t\t\t * updated by another session,\n\t\t\t\t\t\t * reaching a tuple that was already\n\t\t\t\t\t\t * updated in this transaction. If\n\t\t\t\t\t\t * previously modified by this\n\t\t\t\t\t\t * command, ignore the redundant\n\t\t\t\t\t\t * update, otherwise error out.\n\t\t\t\t\t\t *\n\t\t\t\t\t\t * See also response to\n\t\t\t\t\t\t * TM_SelfModified in\n\t\t\t\t\t\t * ht_ExecUpdate().\n\t\t\t\t\t\t */\n\t\t\t\t\t\tif (context->tmfd.cmax != estate->es_output_cid)\n\t\t\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t\t\t(errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION),\n\t\t\t\t\t\t\t\t\t errmsg(\"tuple to be updated or deleted was already modified \"\n\t\t\t\t\t\t\t\t\t\t\t\"by an operation triggered by the current command\"),\n\t\t\t\t\t\t\t\t\t errhint(\"Consider using an AFTER trigger instead of a BEFORE \"\n\t\t\t\t\t\t\t\t\t\t\t \"trigger to propagate changes to other rows.\")));\n\t\t\t\t\t\tif (TransactionIdIsCurrentTransactionId(context->tmfd.xmax))\n\t\t\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t\t\t(errcode(ERRCODE_CARDINALITY_VIOLATION),\n\t\t\t\t\t\t\t\t\t /* translator: %s is a SQL command name */\n\t\t\t\t\t\t\t\t\t errmsg(\"%s command cannot affect row a second time\", \"MERGE\"),\n\t\t\t\t\t\t\t\t\t errhint(\"Ensure that not more than one source row matches any \"\n\t\t\t\t\t\t\t\t\t\t\t \"one target row.\")));\n\n\t\t\t\t\t\t/* This shouldn't happen */\n\t\t\t\t\t\telog(ERROR, \"attempted to update or delete invisible tuple\");\n\t\t\t\t\t\treturn NULL;\n\n\t\t\t\t\tdefault:\n\t\t\t\t\t\t/*\n\t\t\t\t\t\t * see table_tuple_lock call in\n\t\t\t\t\t\t * ht_ExecDelete()\n\t\t\t\t\t\t */\n\t\t\t\t\t\telog(ERROR, \"unexpected table_tuple_lock status: %u\", result);\n\t\t\t\t\t\treturn NULL;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcase TM_Invisible:\n\t\t\tcase TM_WouldBlock:\n\t\t\tcase TM_BeingModified:\n\t\t\t\t/* these should not occur */\n\t\t\t\telog(ERROR, \"unexpected tuple operation result: %d\", result);\n\t\t\t\tbreak;\n\t\t}\n\n#if PG17_GE\n\t\t/* Process RETURNING if present */\n\t\tif (resultRelInfo->ri_projectReturning)\n\t\t{\n\t\t\tswitch (commandType)\n\t\t\t{\n\t\t\t\tcase CMD_UPDATE:\n\t\t\t\t\t/* Variable newslot should be set for CMD_UPDATE above */\n\t\t\t\t\tAssert(newslot != NULL);\n\t\t\t\t\trslot = ExecProcessReturning(resultRelInfo, CMD_UPDATE,\n\t\t\t\t\t\t\t\t\t\t\t\t resultRelInfo->ri_oldTupleSlot,\n\t\t\t\t\t\t\t\t\t\t\t\t newslot, context->planSlot);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase CMD_DELETE:\n\t\t\t\t\trslot = ExecProcessReturning(resultRelInfo, CMD_DELETE,\n\t\t\t\t\t\t\t\t\t\t\t\t resultRelInfo->ri_oldTupleSlot,\n\t\t\t\t\t\t\t\t\t\t\t\t NULL, context->planSlot);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase CMD_NOTHING:\n\t\t\t\t\tbreak;\n\n\t\t\t\tdefault:\n\t\t\t\t\telog(ERROR, \"unrecognized commandType: %d\", (int) commandType);\n\t\t\t}\n\t\t}\n#endif\n\n\t\t/*\n\t\t * We've activated one of the WHEN clauses, so we don't\n\t\t * search further. This is required behaviour, not an\n\t\t * optimization.\n\t\t */\n\t\tbreak;\n\t}\n\n\t/*\n\t * Successfully executed an action or no qualifying action was found.\n\t */\n\treturn rslot;\n}\n\n/*\n * Execute the first qualifying NOT MATCHED action.\n */\nstatic TupleTableSlot *\nExecMergeNotMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo,\n\t\t\t\t\tChunkTupleRouting *ctr, bool canSetTag)\n{\n\tModifyTableState *mtstate = context->mtstate;\n\tExprContext *econtext = mtstate->ps.ps_ExprContext;\n\tList *actionStates = NIL;\n\tListCell *l;\n\tTupleTableSlot *rslot = NULL;\n\n\t/*\n\t * For INSERT actions, the root relation's merge action is OK since\n\t * the INSERT's targetlist and the WHEN conditions can only refer to\n\t * the source relation and hence it does not matter which result\n\t * relation we work with.\n\t *\n\t * XXX does this mean that we can avoid creating copies of\n\t * actionStates on partitioned tables, for not-matched actions?\n\t *\n\t * XXX do we need an additional support of NOT MATCHED BY SOURCE\n\t * for PG >= 17? See PostgreSQL commit 0294df2f1f84\n\t */\n#if PG17_GE\n\tactionStates = ctr->cis->result_relation_info->ri_MergeActions[MERGE_WHEN_NOT_MATCHED_BY_TARGET];\n#else\n\tactionStates = ctr->cis->result_relation_info->ri_notMatchedMergeAction;\n#endif\n\t/*\n\t * Make source tuple available to ExecQual and ExecProject. We don't\n\t * need the target tuple, since the WHEN quals and targetlist can't\n\t * refer to the target columns.\n\t */\n\tecontext->ecxt_scantuple = NULL;\n\tecontext->ecxt_innertuple = context->planSlot;\n\tecontext->ecxt_outertuple = NULL;\n\n\tforeach (l, actionStates)\n\t{\n\t\tMergeActionState *action = (MergeActionState *) lfirst(l);\n\t\tCmdType commandType = action->mas_action->commandType;\n\t\tTupleTableSlot *newslot;\n\n\t\t/*\n\t\t * Test condition, if any.\n\t\t *\n\t\t * In the absence of any condition, we perform the action\n\t\t * unconditionally (no need to check separately since\n\t\t * ExecQual() will return true if there are no conditions to\n\t\t * evaluate).\n\t\t */\n\t\tif (!ExecQual(action->mas_whenqual, econtext))\n\t\t\tcontinue;\n\n\t\t/* Perform stated action */\n\t\tswitch (commandType)\n\t\t{\n\t\t\tcase CMD_INSERT:\n\n\t\t\t\t/*\n\t\t\t\t * Project the tuple.  In case of a partitioned\n\t\t\t\t * table, the projection was already built to use the\n\t\t\t\t * root's descriptor, so we don't need to map the\n\t\t\t\t * tuple here.\n\t\t\t\t */\n\t\t\t\tnewslot = ExecProject(action->mas_proj);\n#if PG17_GE\n\t\t\t\tmtstate->mt_merge_action = action;\n#else\n\t\t\t\tcontext->relaction = action;\n#endif\n\t\t\t\tif (ctr->has_dropped_attrs)\n\t\t\t\t{\n\t\t\t\t\tAttrMap *map;\n\t\t\t\t\tTupleDesc parenttupdesc, chunktupdesc;\n\t\t\t\t\tTupleTableSlot *chunk_slot = NULL;\n\n\t\t\t\t\tparenttupdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);\n\t\t\t\t\tchunktupdesc = RelationGetDescr(ctr->cis->result_relation_info->ri_RelationDesc);\n\t\t\t\t\t/* map from parent to chunk */\n#if PG16_LT\n\t\t\t\t\tmap = build_attrmap_by_name_if_req(parenttupdesc, chunktupdesc);\n#else\n\t\t\t\t\tmap = build_attrmap_by_name_if_req(parenttupdesc, chunktupdesc, false);\n#endif\n\t\t\t\t\tif (map != NULL)\n\t\t\t\t\t\tchunk_slot =\n\t\t\t\t\t\t\texecute_attr_map_slot(map,\n\t\t\t\t\t\t\t\t\t\t\t\t  newslot,\n\t\t\t\t\t\t\t\t\t\t\t\t  MakeSingleTupleTableSlot(chunktupdesc,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   &TTSOpsVirtual));\n\t\t\t\t\trslot = ExecInsert(context,\n\t\t\t\t\t\t\t\t\t   resultRelInfo,\n\t\t\t\t\t\t\t\t\t   ctr,\n\t\t\t\t\t\t\t\t\t   (chunk_slot ? chunk_slot : newslot),\n\t\t\t\t\t\t\t\t\t   canSetTag);\n\t\t\t\t\tif (chunk_slot)\n\t\t\t\t\t\tExecDropSingleTupleTableSlot(chunk_slot);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\trslot = ExecInsert(context, resultRelInfo, ctr, newslot, canSetTag);\n\t\t\t\tmtstate->mt_merge_inserted = 1;\n\t\t\t\tbreak;\n\t\t\tcase CMD_NOTHING:\n\t\t\t\t/* Do nothing */\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\telog(ERROR, \"unknown action in MERGE WHEN NOT MATCHED clause\");\n\t\t}\n\n\t\t/*\n\t\t * We've activated one of the WHEN clauses, so we don't\n\t\t * search further. This is required behaviour, not an\n\t\t * optimization.\n\t\t */\n\t\tbreak;\n\t}\n\n\treturn rslot;\n}\n\n/*\n * Perform MERGE.\n */\nTupleTableSlot *\nExecMerge(ModifyTableContext *context, ResultRelInfo *resultRelInfo, ChunkTupleRouting *ctr,\n\t\t  ItemPointer tupleid, HeapTuple oldtuple, bool canSetTag)\n{\n\tbool matched;\n\tTupleTableSlot *rslot = NULL;\n\n\t/*-----\n\t * If we are dealing with a WHEN MATCHED case (tupleid is valid), we\n\t * execute the first action for which the additional WHEN MATCHED AND\n\t * quals pass.  If an action without quals is found, that action is\n\t * executed.\n\t *\n\t * Similarly, if we are dealing with WHEN NOT MATCHED case, we look at\n\t * the given WHEN NOT MATCHED actions in sequence until one passes.\n\t *\n\t * Things get interesting in case of concurrent update/delete of the\n\t * target tuple. Such concurrent update/delete is detected while we are\n\t * executing a WHEN MATCHED action.\n\t *\n\t * A concurrent update can:\n\t *\n\t * 1. modify the target tuple so that it no longer satisfies the\n\t *    additional quals attached to the current WHEN MATCHED action\n\t *\n\t *    In this case, we are still dealing with a WHEN MATCHED case.\n\t *    We recheck the list of WHEN MATCHED actions from the start and\n\t *    choose the first one that satisfies the new target tuple.\n\t *\n\t * 2. modify the target tuple so that the join quals no longer pass and\n\t *    hence the source tuple no longer has a match.\n\t *\n\t *    In this case, the source tuple no longer matches the target tuple,\n\t *    so we now instead find a qualifying WHEN NOT MATCHED action to\n\t *    execute.\n\t *\n\t * XXX Hmmm, what if the updated tuple would now match one that was\n\t * considered NOT MATCHED so far?\n\t *\n\t * A concurrent delete changes a WHEN MATCHED case to WHEN NOT MATCHED.\n\t *\n\t * ExecMergeMatched takes care of following the update chain and\n\t * re-finding the qualifying WHEN MATCHED action, as long as the updated\n\t * target tuple still satisfies the join quals, i.e., it remains a WHEN\n\t * MATCHED case. If the tuple gets deleted or the join quals fail, it\n\t * returns and we try ExecMergeNotMatched. Given that ExecMergeMatched\n\t * always make progress by following the update chain and we never switch\n\t * from ExecMergeNotMatched to ExecMergeMatched, there is no risk of a\n\t * livelock.\n\t */\n#if PG17_GE\n\tmatched = tupleid != NULL || oldtuple != NULL;\n#else\n\tmatched = tupleid != NULL;\n#endif\n\tif (matched)\n\t\trslot = ExecMergeMatched(context, resultRelInfo, tupleid, oldtuple, canSetTag, &matched);\n\n\t/*\n\t * Either we were dealing with a NOT MATCHED tuple or\n\t * ExecMergeMatched() returned \"false\", indicating the previously\n\t * MATCHED tuple no longer matches.\n\t */\n\tif (!matched)\n\t{\n#if PG17_GE\n\t\tif (rslot == NULL)\n\t\t\trslot = ExecMergeNotMatched(context, resultRelInfo, ctr, canSetTag);\n\t\telse\n\t\t\tcontext->mtstate->mt_merge_pending_not_matched = context->planSlot;\n#else\n\t\t(void) ExecMergeNotMatched(context, resultRelInfo, ctr, canSetTag);\n#endif\n\t}\n\n\treturn rslot;\n}\n"
  },
  {
    "path": "src/nodes/vector_agg.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n/*\n * This file defines the node name of Vector Aggregation custom node, to be\n * used in the Apache part of the Timescale extension. The node itself is in the\n * the TSL part.\n */\n#define VECTOR_AGG_NODE_NAME \"VectorAgg\"\n"
  },
  {
    "path": "src/osm_callbacks.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include \"osm_callbacks.h\"\n\n#include <fmgr.h>\n\n#define OSM_CALLBACKS \"osm_callbacks\"\n#define OSM_CALLBACKS_VAR_NAME \"osm_callbacks_versioned\"\n\nstatic OsmCallbacks_Versioned *\nts_get_osm_callbacks(void)\n{\n\tOsmCallbacks_Versioned **ptr =\n\t\t(OsmCallbacks_Versioned **) find_rendezvous_variable(OSM_CALLBACKS_VAR_NAME);\n\n\treturn *ptr;\n}\n\n/* This interface and version of the struct will be removed once we have a new version of OSM on all\n * instances\n */\nstatic OsmCallbacks *\nts_get_osm_callbacks_old(void)\n{\n\tOsmCallbacks **ptr = (OsmCallbacks **) find_rendezvous_variable(OSM_CALLBACKS);\n\n\treturn *ptr;\n}\n\nchunk_insert_check_hook_type\nts_get_osm_chunk_insert_hook()\n{\n\tOsmCallbacks_Versioned *ptr = ts_get_osm_callbacks();\n\tif (ptr)\n\t{\n\t\tif (ptr->version_num == 1)\n\t\t\treturn ptr->chunk_insert_check_hook;\n\t}\n\telse\n\t{\n\t\tOsmCallbacks *ptr_old = ts_get_osm_callbacks_old();\n\t\tif (ptr_old)\n\t\t\treturn ptr_old->chunk_insert_check_hook;\n\t}\n\treturn NULL;\n}\n\nhypertable_drop_hook_type\nts_get_osm_hypertable_drop_hook()\n{\n\tOsmCallbacks_Versioned *ptr = ts_get_osm_callbacks();\n\tif (ptr)\n\t{\n\t\tif (ptr->version_num == 1)\n\t\t\treturn ptr->hypertable_drop_hook;\n\t}\n\telse\n\t{\n\t\tOsmCallbacks *ptr_old = ts_get_osm_callbacks_old();\n\t\tif (ptr_old)\n\t\t\treturn ptr_old->hypertable_drop_hook;\n\t}\n\treturn NULL;\n}\n\nhypertable_drop_chunks_hook_type\nts_get_osm_hypertable_drop_chunks_hook()\n{\n\tOsmCallbacks_Versioned *ptr = ts_get_osm_callbacks();\n\tif (ptr && ptr->version_num == 1)\n\t\treturn ptr->hypertable_drop_chunks_hook;\n\treturn NULL;\n}\n"
  },
  {
    "path": "src/osm_callbacks.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <catalog/objectaddress.h>\n\n/* range_start and range_end are in PG internal timestamp format. */\ntypedef int (*chunk_insert_check_hook_type)(Oid ht_oid, int64 range_start, int64 range_end);\ntypedef void (*hypertable_drop_hook_type)(const char *schema_name, const char *table_name);\ntypedef List *(*hypertable_drop_chunks_hook_type)(Oid osm_chunk_oid,\n\t\t\t\t\t\t\t\t\t\t\t\t  const char *hypertable_schema_name,\n\t\t\t\t\t\t\t\t\t\t\t\t  const char *hypertable_name, int64 range_start,\n\t\t\t\t\t\t\t\t\t\t\t\t  int64 range_end);\n\n/*\n * Object Storage Manager callbacks.\n *\n * chunk_insert_check_hook - checks whether the specified range is managed by OSM\n * hypertable_drop_hook - used for OSM catalog cleanups\n */\n/* This struct is retained for backward compatibility. We'll remove this in one\n * of the upcoming releases\n */\ntypedef struct\n{\n\tchunk_insert_check_hook_type chunk_insert_check_hook;\n\thypertable_drop_hook_type hypertable_drop_hook;\n} OsmCallbacks;\n\ntypedef struct\n{\n\tint64 version_num;\n\tchunk_insert_check_hook_type chunk_insert_check_hook;\n\thypertable_drop_hook_type hypertable_drop_hook;\n\thypertable_drop_chunks_hook_type hypertable_drop_chunks_hook;\n} OsmCallbacks_Versioned;\n\nextern chunk_insert_check_hook_type ts_get_osm_chunk_insert_hook(void);\nextern hypertable_drop_hook_type ts_get_osm_hypertable_drop_hook(void);\nextern hypertable_drop_chunks_hook_type ts_get_osm_hypertable_drop_chunks_hook(void);\n"
  },
  {
    "path": "src/partition_chunk.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/attmap.h>\n#include <access/toast_compression.h>\n#include <catalog/heap.h>\n#include <catalog/pg_constraint.h>\n#include <commands/tablecmds.h>\n#include <executor/executor.h>\n#include <nodes/makefuncs.h>\n#include <nodes/parsenodes.h>\n#include <rewrite/rewriteManip.h>\n#include <utils/partcache.h>\n\n#include \"chunk.h\"\n#include \"extension.h\"\n#include \"guc.h\"\n#include \"hypercube.h\"\n#include \"hypertable.h\"\n#include \"partition_chunk.h\"\n\nvoid _executor_init(void);\nvoid _executor_fini(void);\nstatic ExecutorEnd_hook_type prev_executor_end_hook = NULL;\n\n/*\n * Cache and the memory context to store recently created chunks to be attached\n * as partitions.\n */\nstatic HTAB *PartChunkCache = NULL;\nstatic MemoryContext PartChunkCacheCxt = NULL;\n\n/*\n * Transaction callback to clean up the partition chunk cache on abort.\n * Memory context is deleted by the portal context cleanup. Just nullify the\n * pointers here.\n */\nstatic void\npartcache_xact_callback(XactEvent event, void *arg)\n{\n\tswitch (event)\n\t{\n\t\tcase XACT_EVENT_ABORT:\n\t\tcase XACT_EVENT_PARALLEL_ABORT:\n\t\t\tPartChunkCache = NULL;\n\t\t\tPartChunkCacheCxt = NULL;\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\t/* do nothing? */\n\t\t\tbreak;\n\t}\n}\n\n/*\n * Insert a chunk into the partition cache.\n */\nvoid\nts_partition_cache_insert_chunk(const Hypertable *ht, Oid chunk_relid)\n{\n\tPartChunkCacheEntry *entry;\n\tbool found;\n\n\tif (PartChunkCache == NULL)\n\t{\n\t\tif (PartChunkCacheCxt == NULL)\n\t\t\tPartChunkCacheCxt = AllocSetContextCreate(PortalContext,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  \"partition chunk cache\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t  ALLOCSET_DEFAULT_SIZES);\n\n\t\tHASHCTL ctl;\n\t\tctl.hcxt =\n\t\t\tAllocSetContextCreate(PortalContext, \"partition chunk cache\", ALLOCSET_DEFAULT_SIZES);\n\t\tctl.keysize = sizeof(Oid);\n\t\tctl.entrysize = sizeof(PartChunkCacheEntry);\n\n\t\tPartChunkCache = hash_create(\"partition chunk cache\",\n\t\t\t\t\t\t\t\t\t 256, /* start small, grows automatically */\n\t\t\t\t\t\t\t\t\t &ctl,\n\t\t\t\t\t\t\t\t\t HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);\n\t\tRegisterXactCallback(partcache_xact_callback, NULL);\n\t}\n\n\tentry = hash_search(PartChunkCache, &ht->main_table_relid, HASH_ENTER, &found);\n\tif (!found)\n\t\tentry->chunk_oids = NIL;\n\n\tMemoryContext oldctx = MemoryContextSwitchTo(PartChunkCacheCxt);\n\tentry->chunk_oids = lappend_oid(entry->chunk_oids, chunk_relid);\n\tMemoryContextSwitchTo(oldctx);\n}\n\n/*\n * Get a partition cache entry by hypertable relid.\n */\nPartChunkCacheEntry *\nts_partition_cache_get_by_hypertable(Oid ht_relid)\n{\n\tPartChunkCacheEntry *entry;\n\n\tif (PartChunkCache == NULL)\n\t\treturn NULL;\n\n\tentry = (PartChunkCacheEntry *) hash_search(PartChunkCache, &ht_relid, HASH_FIND, NULL);\n\n\treturn entry;\n}\n\n/*\n * Destroy the partition chunk cache.\n */\nvoid\nts_partition_cache_destroy(void)\n{\n\tif (PartChunkCache != NULL)\n\t{\n\t\thash_destroy(PartChunkCache);\n\t\tPartChunkCache = NULL;\n\t\tPartChunkCacheCxt = NULL;\n\t}\n}\n\n/*\n * Fill the attribute and constraint lists by copying from the parent hypertable attributes.\n * Partition chunk's attributes are derived from the hypertable's attributes including storage,\n * compression, generation expressions, and default values.\n *\n * The constraints list is filled with the CHECK and NOT NULL constraints on attributes.\n *\n * This code is adapted from MergeAttributes() in tablecmds.c.\n */\nvoid\nts_partition_chunk_prepare_attributes(Oid ht_relid, List **attlist, List **constraints)\n{\n\tRelation rel = table_open(ht_relid, AccessShareLock);\n\tTupleDesc tupleDesc = RelationGetDescr(rel);\n\tTupleConstr *constr = tupleDesc->constr;\n\tAttrMap *newattmap = make_attrmap(tupleDesc->natts);\n\tList *inherited_defaults = NIL;\n\tList *cols_with_defaults = NIL;\n\tint child_attno = 0;\n\n\tfor (int parent_attno = 1; parent_attno <= tupleDesc->natts; parent_attno++)\n\t{\n\t\tForm_pg_attribute attribute = TupleDescAttr(tupleDesc, parent_attno - 1);\n\t\tchar *attributeName = NameStr(attribute->attname);\n\t\tColumnDef *newdef;\n\n\t\t/*\n\t\t * Ignore dropped columns in the parent.\n\t\t */\n\t\tif (attribute->attisdropped)\n\t\t\tcontinue; /* leave newattmap->attnums entry as zero */\n\n\t\t/*\n\t\t * Create new column definition\n\t\t */\n\t\tnewdef = makeColumnDef(attributeName,\n\t\t\t\t\t\t\t   attribute->atttypid,\n\t\t\t\t\t\t\t   attribute->atttypmod,\n\t\t\t\t\t\t\t   attribute->attcollation);\n\t\tnewdef->type = T_ColumnDef;\n\t\tnewdef->is_not_null = attribute->attnotnull;\n\t\tnewdef->storage = attribute->attstorage;\n\t\tnewdef->generated = attribute->attgenerated;\n\t\tif (CompressionMethodIsValid(attribute->attcompression))\n\t\t\tnewdef->compression = pstrdup(GetCompressionMethodName(attribute->attcompression));\n\n\t\tnewdef->inhcount = 0;\n\t\tnewdef->is_local = false;\n\t\tnewattmap->attnums[parent_attno - 1] = ++child_attno;\n\n\t\t/*\n\t\t * Locate default/generation expression if any\n\t\t */\n\t\tif (attribute->atthasdef)\n\t\t{\n\t\t\tNode *this_default = NULL;\n\n\t\t\t/* Find default in constraint structure */\n\t\t\tif (constr != NULL)\n\t\t\t{\n\t\t\t\tAttrDefault *attrdef = constr->defval;\n\n\t\t\t\tfor (int i = 0; i < constr->num_defval; i++)\n\t\t\t\t{\n\t\t\t\t\tif (attrdef[i].adnum == parent_attno)\n\t\t\t\t\t{\n\t\t\t\t\t\tthis_default = stringToNode(attrdef[i].adbin);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (this_default == NULL)\n\t\t\t\telog(ERROR,\n\t\t\t\t\t \"default expression not found for attribute %d of relation \\\"%s\\\"\",\n\t\t\t\t\t parent_attno,\n\t\t\t\t\t RelationGetRelationName(rel));\n\n\t\t\t/*\n\t\t\t * If it's a GENERATED default, it might contain Vars that\n\t\t\t * need to be mapped to the inherited column(s)' new numbers.\n\t\t\t * We can't do that till newattmap is ready, so just remember\n\t\t\t * all the inherited default expressions for the moment.\n\t\t\t */\n\t\t\tinherited_defaults = lappend(inherited_defaults, this_default);\n\t\t\tcols_with_defaults = lappend(cols_with_defaults, newdef);\n\t\t}\n\t\t*attlist = lappend(*attlist, newdef);\n\t}\n\n\t/*\n\t * Now process any inherited default expressions, adjusting attnos\n\t * using the completed newattmap map.\n\t */\n\tListCell *lc1, *lc2;\n\tforboth (lc1, inherited_defaults, lc2, cols_with_defaults)\n\t{\n\t\tNode *this_default = (Node *) lfirst(lc1);\n\t\tColumnDef *def = (ColumnDef *) lfirst(lc2);\n\t\tbool found_whole_row;\n\n\t\t/* Adjust Vars to match new table's column numbering */\n\t\tthis_default =\n\t\t\tmap_variable_attnos(this_default, 1, 0, newattmap, InvalidOid, &found_whole_row);\n\n\t\t/*\n\t\t * For the moment we have to reject whole-row variables.  We could\n\t\t * convert them, if we knew the new table's rowtype OID, but that\n\t\t * hasn't been assigned yet.  (A variable could only appear in a\n\t\t * generation expression, so the error message is correct.)\n\t\t */\n\t\tif (found_whole_row)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"cannot convert whole-row table reference\"),\n\t\t\t\t\t errdetail(\"Generation expression for column \\\"%s\\\" contains a whole-row \"\n\t\t\t\t\t\t\t   \"reference to table \\\"%s\\\".\",\n\t\t\t\t\t\t\t   def->colname,\n\t\t\t\t\t\t\t   RelationGetRelationName(rel))));\n\n\t\tAssert(def->raw_default == NULL);\n\t\tdef->cooked_default = this_default;\n\t}\n\n\t/*\n\t * Now copy the CHECK constraints of this parent, adjusting attnos\n\t * using the completed newattmap map.\n\t */\n\tif (constr && constr->num_check > 0)\n\t{\n\t\tfor (int i = 0; i < constr->num_check; i++)\n\t\t{\n\t\t\tNode *expr;\n\t\t\tbool found_whole_row;\n\n\t\t\t/* ignore if the constraint is non-inheritable */\n\t\t\tif (constr->check[i].ccnoinherit)\n\t\t\t\tcontinue;\n\n\t\t\t/* Adjust Vars to match new table's column numbering */\n\t\t\texpr = map_variable_attnos(stringToNode(constr->check[i].ccbin),\n\t\t\t\t\t\t\t\t\t   1,\n\t\t\t\t\t\t\t\t\t   0,\n\t\t\t\t\t\t\t\t\t   newattmap,\n\t\t\t\t\t\t\t\t\t   InvalidOid,\n\t\t\t\t\t\t\t\t\t   &found_whole_row);\n\n\t\t\t/*\n\t\t\t * For the moment we have to reject whole-row variables. We\n\t\t\t * could convert them, if we knew the new table's rowtype OID,\n\t\t\t * but that hasn't been assigned yet.\n\t\t\t */\n\t\t\tif (found_whole_row)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t errmsg(\"cannot convert whole-row table reference\")));\n\n\t\t\tConstraint *c = makeNode(Constraint);\n\t\t\tc->type = T_Constraint;\n\t\t\tc->contype = CONSTR_CHECK;\n\t\t\tc->conname = pstrdup(constr->check[i].ccname);\n\t\t\tc->skip_validation = !constr->check[i].ccvalid;\n\t\t\tc->initially_valid = true;\n\t\t\tc->location = -1;\n\t\t\tc->is_no_inherit = constr->check[i].ccnoinherit;\n\t\t\tc->raw_expr = NULL;\n\t\t\tc->cooked_expr = nodeToString(expr);\n\t\t\t*constraints = lappend(*constraints, c);\n#if PG18_GE\n\t\t\tc->is_enforced = constr->check[i].ccenforced;\n#endif\n\t\t}\n\t}\n\n#if PG18_GE\n\t/* A row is added into pg_constraints for each NOT NULL constraint since PG18 */\n\tList *notnulls = RelationGetNotNullConstraints(ht_relid, false, false);\n\tforeach_ptr(Constraint, nn, notnulls)\n\t{\n\t\tAssert(nn->contype == CONSTR_NOTNULL);\n\t\t*constraints = lappend(*constraints, nn);\n\t}\n#endif\n\n\tfree_attrmap(newattmap);\n\ttable_close(rel, NoLock);\n}\n\n/*\n * Attach a standalone chunk to a partitioned hypertable as a partition.\n */\nstatic void\npartition_chunk_attach(const Hypertable *ht, const Chunk *chunk)\n{\n\t/* Currently only single-dimensional partitioned hypertables are supported */\n\tAssert(chunk->cube->num_slices == 1);\n\n\tconst Dimension *dim =\n\t\tts_hyperspace_get_dimension_by_id(ht->space, chunk->cube->slices[0]->fd.dimension_id);\n\tOid dimtype = ts_dimension_get_partition_type(dim);\n\n\tA_Const prd_lower =  {\n\t\t.type = T_A_Const,\n\t\t.val.sval = {\n\t\t\t.type = T_String,\n\t\t\t.sval = ts_internal_to_time_string(chunk->cube->slices[0]->fd.range_start, dimtype),\n\t\t},\n\t\t.location = -1\n\t};\n\tA_Const prd_upper =  {\n\t\t.type = T_A_Const,\n\t\t.val.sval = {\n\t\t\t.type = T_String,\n\t\t\t.sval = ts_internal_to_time_string(chunk->cube->slices[0]->fd.range_end, dimtype),\n\t\t},\n\t\t.location = -1\n\t};\n\tPartitionBoundSpec pbspec = {\n\t\t.type = T_PartitionBoundSpec,\n\t\t.is_default = false,\n\t\t.location = -1,\n\t\t.strategy = PARTITION_STRATEGY_RANGE,\n\t\t.lowerdatums = list_make1(&prd_lower),\n\t\t.upperdatums = list_make1(&prd_upper),\n\t};\n\tPartitionCmd partcmd = {\n\t\t.type = T_PartitionCmd,\n\t\t.name = makeRangeVar((char *) NameStr(chunk->fd.schema_name),\n\t\t\t\t\t\t\t (char *) NameStr(chunk->fd.table_name),\n\t\t\t\t\t\t\t 0),\n\t\t.bound = &pbspec,\n\t\t.concurrent = false,\n\t};\n\tAlterTableCmd altercmd = {\n\t\t.type = T_AlterTableCmd,\n\t\t.subtype = AT_AttachPartition,\n\t\t.def = (Node *) &partcmd,\n\t\t.missing_ok = false,\n\t};\n\tAlterTableStmt alterstmt = {\n\t\t.type = T_AlterTableStmt,\n\t\t.cmds = list_make1(&altercmd),\n\t\t.missing_ok = false,\n\t\t.objtype = OBJECT_TABLE,\n\t\t.relation = makeRangeVar((char *) NameStr(ht->fd.schema_name),\n\t\t\t\t\t\t\t\t (char *) NameStr(ht->fd.table_name),\n\t\t\t\t\t\t\t\t 0),\n\t};\n\n\tLOCKMODE lockmode = AlterTableGetLockLevel(alterstmt.cmds);\n\tAlterTableUtilityContext atcontext = {\n\t\t.relid = AlterTableLookupRelation(&alterstmt, lockmode),\n\t};\n\n\tAlterTable(&alterstmt, lockmode, &atcontext);\n}\n\n/*\n * ExecutoreEnd hook to attach cached partition chunks to their hypertables.\n */\n\nstatic void\nts_executor_end_hook(QueryDesc *queryDesc)\n{\n\tListCell *lc;\n\n\tif (prev_executor_end_hook)\n\t\tprev_executor_end_hook(queryDesc);\n\telse\n\t\tstandard_ExecutorEnd(queryDesc);\n\n\t/*\n\t * Chunks cannot be created as a partition or attached as partition until\n\t * this point since Postgres does not allow such operations when there is\n\t * an open reference to the parent table. ModifyTable node opens the parent\n\t * table and it only gets closed in ExecEndPlan.\n\t */\n\tif (queryDesc->operation == CMD_INSERT && PartChunkCache != NULL && ts_extension_is_loaded())\n\t{\n\t\tCache *hcache = ts_hypertable_cache_pin();\n\t\tHASH_SEQ_STATUS status;\n\t\tPartChunkCacheEntry *entry;\n\n\t\thash_seq_init(&status, PartChunkCache);\n\t\twhile ((entry = hash_seq_search(&status)) != NULL)\n\t\t{\n\t\t\tforeach (lc, entry->chunk_oids)\n\t\t\t{\n\t\t\t\tHypertable *ht =\n\t\t\t\t\tts_hypertable_cache_get_entry(hcache, entry->ht_relid, CACHE_FLAG_MISSING_OK);\n\n\t\t\t\tif (ht)\n\t\t\t\t\tpartition_chunk_attach(ht, ts_chunk_get_by_relid(lfirst_oid(lc), true));\n\t\t\t}\n\t\t}\n\n\t\tts_cache_release(&hcache);\n\t\tts_partition_cache_destroy();\n\t}\n}\n\nvoid\n_executor_init(void)\n{\n\tprev_executor_end_hook = ExecutorEnd_hook;\n\tExecutorEnd_hook = ts_executor_end_hook;\n}\n\nvoid\n_executor_fini(void)\n{\n\tExecutorEnd_hook = prev_executor_end_hook;\n}\n"
  },
  {
    "path": "src/partition_chunk.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include \"export.h\"\n#include \"guc.h\"\n\n#define is_partitioning_allowed(relid)                                                             \\\n\t(ts_guc_enable_partitioned_hypertables && (get_rel_relkind(relid) == RELKIND_PARTITIONED_TABLE))\n\n/*\n * Cache entry for chunks to be attached as partitions\n */\ntypedef struct PartChunkCacheEntry\n{\n\tOid ht_relid;\n\tList *chunk_oids;\n} PartChunkCacheEntry;\nextern void ts_partition_cache_insert_chunk(const Hypertable *ht, Oid chunk_relid);\nextern PartChunkCacheEntry *ts_partition_cache_get_by_hypertable(Oid ht_relid);\nextern void ts_partition_cache_destroy(void);\n\nextern void ts_partition_chunk_prepare_attributes(Oid ht_relid, List **attlist, List **constraints);\n"
  },
  {
    "path": "src/partitioning.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/hash.h>\n#include <access/htup_details.h>\n#include <catalog/namespace.h>\n#include <catalog/pg_type.h>\n#include <miscadmin.h>\n#include <nodes/makefuncs.h>\n#include <nodes/pg_list.h>\n#include <parser/parse_coerce.h>\n#include <utils/acl.h>\n#include <utils/builtins.h>\n#include <utils/cash.h>\n#include <utils/catcache.h>\n#include <utils/date.h>\n#include <utils/inet.h>\n#include <utils/jsonb.h>\n#include <utils/lsyscache.h>\n#include <utils/memutils.h>\n#include <utils/numeric.h>\n#include <utils/rangetypes.h>\n#include <utils/syscache.h>\n#include <utils/timestamp.h>\n\n#include \"compat/compat.h\"\n#include \"partitioning.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"utils.h\"\n\n#define IS_VALID_CLOSED_PARTITIONING_FUNC(proform, argtype)                                        \\\n\t((proform)->prorettype == INT4OID && ((proform)->provolatile == PROVOLATILE_IMMUTABLE) &&      \\\n\t (proform)->pronargs == 1 &&                                                                   \\\n\t ((proform)->proargtypes.values[0] == (argtype) ||                                             \\\n\t  (proform)->proargtypes.values[0] == ANYELEMENTOID))\n\n#define IS_VALID_OPEN_PARTITIONING_FUNC(proform, argtype)                                          \\\n\t(IS_VALID_OPEN_DIM_TYPE((proform)->prorettype) &&                                              \\\n\t ((proform)->provolatile == PROVOLATILE_IMMUTABLE) && (proform)->pronargs == 1 &&              \\\n\t ((proform)->proargtypes.values[0] == (argtype) ||                                             \\\n\t  (proform)->proargtypes.values[0] == ANYELEMENTOID))\n\n#define IS_VALID_PARTITIONING_FUNC(proform, dimtype, argtype)                                      \\\n\t(((dimtype) == DIMENSION_TYPE_OPEN) ? IS_VALID_OPEN_PARTITIONING_FUNC(proform, argtype) :      \\\n\t\t\t\t\t\t\t\t\t\t  IS_VALID_CLOSED_PARTITIONING_FUNC(proform, argtype))\n\nstatic bool\nclosed_dim_partitioning_func_filter(Form_pg_proc form, void *arg)\n{\n\tOid *argtype = arg;\n\n\treturn IS_VALID_CLOSED_PARTITIONING_FUNC(form, *argtype);\n}\n\nstatic bool\nopen_dim_partitioning_func_filter(Form_pg_proc form, void *arg)\n{\n\tOid *argtype = arg;\n\n\treturn IS_VALID_OPEN_PARTITIONING_FUNC(form, *argtype);\n}\n\nbool\nts_partitioning_func_is_valid(regproc funcoid, DimensionType dimtype, Oid argtype)\n{\n\tHeapTuple tuple;\n\tbool isvalid;\n\tAclResult aclresult;\n\n\ttuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));\n\n\tif (!HeapTupleIsValid(tuple))\n\t\telog(ERROR, \"cache lookup failed for function %u\", funcoid);\n\n\taclresult = object_aclcheck(ProcedureRelationId, funcoid, GetUserId(), ACL_EXECUTE);\n\tif (aclresult != ACLCHECK_OK)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),\n\t\t\t\t errmsg(\"permission denied for function %s\", get_func_name(funcoid))));\n\n\tisvalid = IS_VALID_PARTITIONING_FUNC((Form_pg_proc) GETSTRUCT(tuple), dimtype, argtype);\n\n\tReleaseSysCache(tuple);\n\n\treturn isvalid;\n}\n\nOid\nts_partitioning_func_get_closed_default(void)\n{\n\tOid argtype = ANYELEMENTOID;\n\n\treturn ts_lookup_proc_filtered(DEFAULT_PARTITIONING_FUNC_SCHEMA,\n\t\t\t\t\t\t\t\t   DEFAULT_PARTITIONING_FUNC_NAME,\n\t\t\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t\t\t   closed_dim_partitioning_func_filter,\n\t\t\t\t\t\t\t\t   &argtype);\n}\n\nstatic bool\nts_partitioning_func_is_closed_default(const char *schema, const char *funcname)\n{\n\tAssert(schema != NULL && funcname != NULL);\n\n\treturn strcmp(DEFAULT_PARTITIONING_FUNC_SCHEMA, schema) == 0 &&\n\t\t   strcmp(DEFAULT_PARTITIONING_FUNC_NAME, funcname) == 0;\n}\n\n/*\n * Resolve the partitioning function set for a hypertable.\n */\nstatic void\npartitioning_func_set_func_fmgr(PartitioningFunc *pf, Oid argtype, DimensionType dimtype)\n{\n\tOid funcoid;\n\tproc_filter filter = dimtype == DIMENSION_TYPE_CLOSED ? closed_dim_partitioning_func_filter :\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\topen_dim_partitioning_func_filter;\n\n\tif (dimtype != DIMENSION_TYPE_CLOSED && dimtype != DIMENSION_TYPE_OPEN)\n\t\telog(ERROR, \"invalid dimension type %u\", dimtype);\n\n\tfuncoid = ts_lookup_proc_filtered(NameStr(pf->schema),\n\t\t\t\t\t\t\t\t\t  NameStr(pf->name),\n\t\t\t\t\t\t\t\t\t  &pf->rettype,\n\t\t\t\t\t\t\t\t\t  filter,\n\t\t\t\t\t\t\t\t\t  &argtype);\n\n\tif (!OidIsValid(funcoid))\n\t{\n\t\tif (dimtype == DIMENSION_TYPE_CLOSED)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errmsg(\"invalid partitioning function\"),\n\t\t\t\t\t errhint(\"A partitioning function for a closed (space) dimension \"\n\t\t\t\t\t\t\t \"must be IMMUTABLE and have the signature (anyelement) -> integer\")));\n\t\telse\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errmsg(\"invalid partitioning function\"),\n\t\t\t\t\t errhint(\"A partitioning function for a open (time) dimension \"\n\t\t\t\t\t\t\t \"must be IMMUTABLE, take one argument, and return a supported time \"\n\t\t\t\t\t\t\t \"type\")));\n\t}\n\n\tfmgr_info_cxt(funcoid, &pf->func_fmgr, CurrentMemoryContext);\n}\n\nstatic Oid\nfind_text_coercion_func(Oid type)\n{\n\tOid funcid;\n\tbool is_varlena;\n\tCoercionPathType cpt;\n\n\t/*\n\t * First look for an explicit cast type. Needed since the output of for\n\t * example character(20) not the same as character(20)::text\n\t */\n\tcpt = find_coercion_pathway(TEXTOID, type, COERCION_EXPLICIT, &funcid);\n\n\tif (cpt != COERCION_PATH_FUNC)\n\t\tgetTypeOutputInfo(type, &funcid, &is_varlena);\n\n\treturn funcid;\n}\n\n#define TYPECACHE_HASH_FLAGS (TYPECACHE_HASH_PROC | TYPECACHE_HASH_PROC_FINFO)\n\nPartitioningInfo *\nts_partitioning_info_create(const char *schema, const char *partfunc, const char *partcol,\n\t\t\t\t\t\t\tDimensionType dimtype, Oid relid)\n{\n\tPartitioningInfo *pinfo;\n\tOid columntype, varcollid, funccollid = InvalidOid;\n\tVar *var;\n\tFuncExpr *expr;\n\n\tif (schema == NULL || partfunc == NULL || partcol == NULL)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),\n\t\t\t\t errmsg(\"partitioning function information cannot be null\")));\n\n\tpinfo = palloc0(sizeof(PartitioningInfo));\n\tnamestrcpy(&pinfo->partfunc.name, partfunc);\n\tnamestrcpy(&pinfo->column, partcol);\n\tpinfo->column_attnum = get_attnum(relid, NameStr(pinfo->column));\n\tpinfo->dimtype = dimtype;\n\n\t/* handle the case that the attribute has been dropped */\n\tif (pinfo->column_attnum == InvalidAttrNumber)\n\t\treturn NULL;\n\n\tnamestrcpy(&pinfo->partfunc.schema, schema);\n\n\t/* Lookup the type cache entry to access the hash function for the type */\n\tcolumntype = get_atttype(relid, pinfo->column_attnum);\n\n\tif (dimtype == DIMENSION_TYPE_CLOSED)\n\t{\n\t\tTypeCacheEntry *tce = lookup_type_cache(columntype, TYPECACHE_HASH_FLAGS);\n\n\t\tif (!OidIsValid(tce->hash_proc) && ts_partitioning_func_is_closed_default(schema, partfunc))\n\t\t\telog(ERROR, \"could not find hash function for type %s\", format_type_be(columntype));\n\t}\n\n\tpartitioning_func_set_func_fmgr(&pinfo->partfunc, columntype, dimtype);\n\n\t/*\n\t * Prepare a function expression for this function. The partition hash\n\t * function needs this to be able to resolve the type of the value to be\n\t * hashed.\n\t */\n\tvarcollid = get_typcollation(columntype);\n\n\tvar = makeVar(1, pinfo->column_attnum, columntype, -1, varcollid, 0);\n\n\texpr = makeFuncExpr(pinfo->partfunc.func_fmgr.fn_oid,\n\t\t\t\t\t\tpinfo->partfunc.rettype,\n\t\t\t\t\t\tlist_make1(var),\n\t\t\t\t\t\tfunccollid,\n\t\t\t\t\t\tvarcollid,\n\t\t\t\t\t\tCOERCE_EXPLICIT_CALL);\n\n\tfmgr_info_set_expr((Node *) expr, &pinfo->partfunc.func_fmgr);\n\n\treturn pinfo;\n}\n\n/*\n * Apply a dimension's partitioning function to a value.\n *\n * We need to avoid FunctionCall1(), because we'd like to customize the error\n * message in case of NULL return values.\n */\nTSDLLEXPORT Datum\nts_partitioning_func_apply(PartitioningInfo *pinfo, Oid collation, Datum value)\n{\n\tLOCAL_FCINFO(fcinfo, 1);\n\tDatum result;\n\n\tInitFunctionCallInfoData(*fcinfo, &pinfo->partfunc.func_fmgr, 1, collation, NULL, NULL);\n\n\tFC_SET_ARG(fcinfo, 0, value);\n\n\tresult = FunctionCallInvoke(fcinfo);\n\n\tif (fcinfo->isnull)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),\n\t\t\t\t errmsg(\"partitioning function \\\"%s.%s\\\" returned NULL\",\n\t\t\t\t\t\tNameStr(pinfo->partfunc.schema),\n\t\t\t\t\t\tNameStr(pinfo->partfunc.name))));\n\t}\n\n\treturn result;\n}\n\n/*\n * Helper function to find the right partition value from a tuple,\n * for space partitioned hypertables. Since attributes in tuple can\n * be of different order when compared to physical table columns order,\n * we pass partition_col_idx which points to correct space partitioned\n * column in the given tuple.\n */\nTSDLLEXPORT Datum\nts_partitioning_func_apply_slot(PartitioningInfo *pinfo, TupleTableSlot *slot, bool *isnull)\n{\n\tDatum value;\n\tbool null;\n\tOid collation;\n\n\tvalue = slot_getattr(slot, pinfo->column_attnum, &null);\n\n\tif (NULL != isnull)\n\t\t*isnull = null;\n\n\tif (null)\n\t\treturn 0;\n\n\tcollation =\n\t\tTupleDescAttr(slot->tts_tupleDescriptor, AttrNumberGetAttrOffset(pinfo->column_attnum))\n\t\t\t->attcollation;\n\n\treturn ts_partitioning_func_apply(pinfo, collation, value);\n}\n\n/*\n * Resolve the type of the argument passed to a function.\n *\n * The type is resolved from the function expression in the function call info.\n */\nstatic Oid\nresolve_function_argtype(FunctionCallInfo fcinfo)\n{\n\tFuncExpr *fe;\n\tNode *node;\n\tOid argtype;\n\n\t/* Get the function expression from the call info */\n\tfe = (FuncExpr *) fcinfo->flinfo->fn_expr;\n\n\tif (NULL == fe || !IsA(fe, FuncExpr))\n\t\telog(ERROR, \"no function expression set when invoking partitioning function\");\n\n\tif (list_length(fe->args) != 1)\n\t\telog(ERROR, \"unexpected number of arguments in function expression\");\n\n\tnode = linitial(fe->args);\n\n\tswitch (nodeTag(node))\n\t{\n\t\tcase T_Var:\n\t\t\targtype = castNode(Var, node)->vartype;\n\t\t\tbreak;\n\t\tcase T_Const:\n\t\t\targtype = castNode(Const, node)->consttype;\n\t\t\tbreak;\n\t\tcase T_CoerceViaIO:\n\t\t\targtype = castNode(CoerceViaIO, node)->resulttype;\n\t\t\tbreak;\n\t\tcase T_FuncExpr:\n\t\t\t/* Argument is function, so our input is its result type */\n\t\t\targtype = castNode(FuncExpr, node)->funcresulttype;\n\t\t\tbreak;\n\t\tcase T_Param:\n\t\t\targtype = castNode(Param, node)->paramtype;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\telog(ERROR, \"unsupported expression argument node type: %s\", ts_get_node_name(node));\n\t}\n\n\treturn argtype;\n}\n\n/*\n * Partitioning function cache.\n *\n * Holds type information to avoid repeated lookups. The cache is allocated on a\n * child memory context of the context that created the associated FmgrInfo\n * struct. For partitioning functions invoked on the insert path, this is\n * typically the Hypertable cache's memory context. Hence, the type cache lives\n * for the duration of the hypertable cache and can be reused across multiple\n * invocations of the partitioning function, even across transactions.\n *\n * If the partitioning function is invoked outside the insert path, the FmgrInfo\n * and its memory context has a lifetime corresponding to that invocation.\n */\ntypedef struct PartFuncCache\n{\n\tOid argtype;\n\tOid coerce_funcid;\n\tTypeCacheEntry *tce;\n} PartFuncCache;\n\nstatic PartFuncCache *\npart_func_cache_create(Oid argtype, TypeCacheEntry *tce, Oid coerce_funcid, MemoryContext mcxt)\n{\n\tPartFuncCache *pfc;\n\n\tpfc = MemoryContextAlloc(mcxt, sizeof(PartFuncCache));\n\tpfc->argtype = argtype;\n\tpfc->tce = tce;\n\tpfc->coerce_funcid = coerce_funcid;\n\n\treturn pfc;\n}\n\n/* _timescaledb_catalog.ts_get_partition_for_key(key anyelement) RETURNS INT */\nTSDLLEXPORT Datum ts_get_partition_for_key(PG_FUNCTION_ARGS);\n\nTS_FUNCTION_INFO_V1(ts_get_partition_for_key);\n\n/*\n * Partition hash function that first converts all inputs to text before\n * hashing.\n */\nDatum\nts_get_partition_for_key(PG_FUNCTION_ARGS)\n{\n\tDatum arg = PG_GETARG_DATUM(0);\n\tPartFuncCache *pfc = fcinfo->flinfo->fn_extra;\n\tstruct varlena *data;\n\tuint32 hash_u;\n\tint32 res;\n\n\tif (PG_NARGS() != 1)\n\t\telog(ERROR, \"unexpected number of arguments to partitioning function\");\n\n\tif (NULL == pfc)\n\t{\n\t\tOid funcid = InvalidOid;\n\t\tOid argtype = resolve_function_argtype(fcinfo);\n\n\t\tif (argtype != TEXTOID)\n\t\t{\n\t\t\t/* Not TEXT input -> need to convert to text */\n\t\t\tfuncid = find_text_coercion_func(argtype);\n\n\t\t\tif (!OidIsValid(funcid))\n\t\t\t\telog(ERROR, \"could not coerce type %u to text\", argtype);\n\t\t}\n\n\t\tpfc = part_func_cache_create(argtype, NULL, funcid, fcinfo->flinfo->fn_mcxt);\n\t\tfcinfo->flinfo->fn_extra = pfc;\n\t}\n\n\tif (pfc->argtype != TEXTOID)\n\t{\n\t\targ = OidFunctionCall1(pfc->coerce_funcid, arg);\n\t\targ = CStringGetTextDatum(DatumGetCString(arg));\n\t}\n\n\tdata = DatumGetTextPP(arg);\n\thash_u = DatumGetUInt32(hash_any((unsigned char *) VARDATA_ANY(data), VARSIZE_ANY_EXHDR(data)));\n\n\tres = (int32) (hash_u & 0x7fffffff); /* Only positive numbers */\n\n\tPG_FREE_IF_COPY(data, 0);\n\tPG_RETURN_INT32(res);\n}\n\nTSDLLEXPORT Datum ts_get_partition_hash(PG_FUNCTION_ARGS);\n\nTS_FUNCTION_INFO_V1(ts_get_partition_hash);\n\n/*\n * Compute a partition hash value for any input type.\n *\n * ts_get_partition_hash() takes a single argument of anyelement type. We compute\n * the hash based on the argument type information that we expect to find in the\n * function expression in the function call context. If no such expression\n * exists, or the type cannot be resolved from the expression, the function\n * throws an error.\n */\nDatum\nts_get_partition_hash(PG_FUNCTION_ARGS)\n{\n\tDatum arg = PG_GETARG_DATUM(0);\n\tPartFuncCache *pfc = fcinfo->flinfo->fn_extra;\n\tDatum hash;\n\tint32 res;\n\tOid collation;\n\n\tif (PG_NARGS() != 1)\n\t\telog(ERROR, \"unexpected number of arguments to partitioning function\");\n\n\tif (NULL == pfc)\n\t{\n\t\tOid argtype = resolve_function_argtype(fcinfo);\n\t\tTypeCacheEntry *tce = lookup_type_cache(argtype, TYPECACHE_HASH_FLAGS);\n\n\t\tpfc = part_func_cache_create(argtype, tce, InvalidOid, fcinfo->flinfo->fn_mcxt);\n\t\tfcinfo->flinfo->fn_extra = pfc;\n\t}\n\n\tif (!OidIsValid(pfc->tce->hash_proc))\n\t\telog(ERROR, \"could not find hash function for type %u\", pfc->argtype);\n\n\t/* use the supplied collation, if it exists, otherwise use the default for\n\t * the type\n\t */\n\tcollation = PG_GET_COLLATION();\n\tif (!OidIsValid(collation))\n\t\tcollation = pfc->tce->typcollation;\n\n\thash = FunctionCall1Coll(&pfc->tce->hash_proc_finfo, collation, arg);\n\n\t/* Only positive numbers */\n\tres = (int32) (DatumGetUInt32(hash) & 0x7fffffff);\n\n\tPG_RETURN_INT32(res);\n}\n"
  },
  {
    "path": "src/partitioning.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#define KEYSPACE_PT_NO_PARTITIONING -1\n\n#include <postgres.h>\n#include <access/attnum.h>\n#include <access/htup_details.h>\n#include <fmgr.h>\n#include <utils/typcache.h>\n\n#include \"dimension.h\"\n#include \"ts_catalog/catalog.h\"\n\n#define OPEN_START_TIME -1\n#define OPEN_END_TIME PG_INT64_MAX\n\n#define DEFAULT_PARTITIONING_FUNC_SCHEMA FUNCTIONS_SCHEMA_NAME\n#define DEFAULT_PARTITIONING_FUNC_NAME \"get_partition_hash\"\n\ntypedef struct PartitioningFunc\n{\n\tNameData schema;\n\tNameData name;\n\tOid rettype;\n\n\t/*\n\t * Function manager info to call the partitioning function on the\n\t * partitioning column's text representation.\n\t */\n\tFmgrInfo func_fmgr;\n} PartitioningFunc;\n\ntypedef struct PartitioningInfo\n{\n\tNameData column;\n\tAttrNumber column_attnum;\n\tDimensionType dimtype;\n\tPartitioningFunc partfunc;\n} PartitioningInfo;\n\nextern Oid ts_partitioning_func_get_closed_default(void);\nextern bool ts_partitioning_func_is_valid(regproc funcoid, DimensionType dimtype, Oid argtype);\n\nextern PartitioningInfo *ts_partitioning_info_create(const char *schema, const char *partfunc,\n\t\t\t\t\t\t\t\t\t\t\t\t\t const char *partcol, DimensionType dimtype,\n\t\t\t\t\t\t\t\t\t\t\t\t\t Oid relid);\nextern TSDLLEXPORT Datum ts_partitioning_func_apply(PartitioningInfo *pinfo, Oid collation,\n\t\t\t\t\t\t\t\t\t\t\t\t\tDatum value);\n\n/* NOTE: assume the tuple belongs to the root table, use ts_partitioning_func_apply for chunk tuples\n */\nextern TSDLLEXPORT Datum ts_partitioning_func_apply_slot(PartitioningInfo *pinfo,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t TupleTableSlot *slot, bool *isnull);\n"
  },
  {
    "path": "src/planner/CMakeLists.txt",
    "content": "set(SOURCES\n    ${CMAKE_CURRENT_SOURCE_DIR}/planner.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/agg_bookend.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/constify_now.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/constraint_cleanup.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/expand_hypertable.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/space_constraint.c)\ntarget_sources(${PROJECT_NAME} PRIVATE ${SOURCES})\n"
  },
  {
    "path": "src/planner/agg_bookend.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n/*\n *\t  Optimization for FIRST/LAST aggregate functions.\n *\n * This module tries to replace FIRST/LAST aggregate functions by subqueries\n * of the form\n *\t\t(SELECT value FROM tab\n *\t\t WHERE sort IS NOT NULL AND existing-quals\n *\t\t ORDER BY sort ASC/DESC\n *\t\t LIMIT 1)\n * Given a suitable index on sort column, this can be much faster than the\n * generic scan-all-the-rows aggregation plan.  We can handle multiple\n * FIRST/LAST aggregates by generating multiple subqueries, and their\n * orderings can be different.  However, if the query also contains some\n * other aggregates (eg. MIN/MAX), we will skip optimization since we can't\n * optimize across different aggregate functions.\n *\n *\t  Most of the code is borrowed from:\n *\t  src/backend/optimizer/plan/planagg.c\n *\n *\n */\n\n/*\n * This file contains source code that was copied and/or modified from\n * the PostgreSQL database, which is licensed under the open-source\n * PostgreSQL License. Please see the NOTICE at the top level\n * directory for a copy of the PostgreSQL License.\n */\n\n#include <postgres.h>\n\n#include <access/htup_details.h>\n#include <access/stratnum.h>\n#include <catalog/namespace.h>\n#include <catalog/pg_aggregate.h>\n#include <catalog/pg_proc.h>\n#include <catalog/pg_type.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodeFuncs.h>\n#include <optimizer/cost.h>\n#include <optimizer/optimizer.h>\n#include <optimizer/pathnode.h>\n#include <optimizer/paths.h>\n#include <optimizer/planmain.h>\n#include <optimizer/subselect.h>\n#include <optimizer/tlist.h>\n#include <parser/parse_clause.h>\n#include <parser/parse_func.h>\n#include <parser/parsetree.h>\n#include <rewrite/rewriteManip.h>\n#include <utils/builtins.h>\n#include <utils/lsyscache.h>\n#include <utils/regproc.h>\n#include <utils/syscache.h>\n#include <utils/typcache.h>\n\n#include \"compat/compat.h\"\n#include \"extension.h\"\n#include \"func_cache.h\"\n#include \"planner.h\"\n#include \"utils.h\"\n\ntypedef struct FirstLastAggInfo\n{\n\tMinMaxAggInfo *m_agg_info; /* reusing MinMaxAggInfo to avoid code\n\t\t\t\t\t\t\t\t* duplication */\n\tExpr *sort;\t\t\t\t   /* Expression to use for ORDER BY */\n} FirstLastAggInfo;\n\ntypedef struct MutatorContext\n{\n\tMinMaxAggPath *mm_path;\n} MutatorContext;\n\nstatic bool find_first_last_aggs_walker(Node *node, List **context);\nstatic bool build_first_last_path(PlannerInfo *root, FirstLastAggInfo *fl_info, Oid eqop,\n\t\t\t\t\t\t\t\t  Oid sortop, bool reverse_sort, bool nulls_first);\nstatic void first_last_qp_callback(PlannerInfo *root, void *extra);\nstatic Node *mutate_aggref_node(Node *node, MutatorContext *context);\nstatic void replace_aggref_in_tlist(MinMaxAggPath *minmaxagg_path);\n\n/*\n * mutate_aggref_node\n *\n * Mutator function used by recursive `expression_tree_mutator`\n * to replace Aggref node with Param node\n */\nNode *\nmutate_aggref_node(Node *node, MutatorContext *context)\n{\n\tif (node == NULL)\n\t\treturn NULL;\n\tif (IsA(node, Aggref))\n\t{\n\t\tAggref *aggref = (Aggref *) node;\n\n\t\t/* See if the Aggref should be replaced by a Param */\n\t\tif (context->mm_path != NULL && list_length(aggref->args) == 2)\n\t\t{\n\t\t\tTargetEntry *curTarget = (TargetEntry *) linitial(aggref->args);\n\t\t\tListCell *cell;\n\n\t\t\tforeach (cell, context->mm_path->mmaggregates)\n\t\t\t{\n\t\t\t\tMinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(cell);\n\n\t\t\t\tif (mminfo->aggfnoid == aggref->aggfnoid && equal(mminfo->target, curTarget->expr))\n\t\t\t\t\treturn (Node *) copyObject(mminfo->param);\n\t\t\t}\n\t\t}\n\t}\n\treturn expression_tree_mutator(node, mutate_aggref_node, (void *) context);\n}\n\n/*\n * replace_aggref_in_tlist\n *\n * If MinMaxAggPath is chosen, instead of running aggregate\n * function we will execute subquery that we've generated. Since we\n * use subquery we need to replace target list Aggref node with Param\n * node. Param node passes output value from the subquery.\n *\n */\nvoid\nreplace_aggref_in_tlist(MinMaxAggPath *minmaxagg_path)\n{\n\tMutatorContext context;\n\n\tcontext.mm_path = minmaxagg_path;\n\n\t((Path *) minmaxagg_path)->pathtarget->exprs =\n\t\t(List *) mutate_aggref_node((Node *) ((Path *) minmaxagg_path)->pathtarget->exprs,\n\t\t\t\t\t\t\t\t\t(void *) &context);\n}\n\nstatic StrategyNumber\nget_func_strategy(Oid func_oid)\n{\n\t/* Ensure function cache is initialized */\n\tif (!OidIsValid(ts_first_func_oid) || !OidIsValid(ts_last_func_oid))\n\t\tts_func_cache_init();\n\n\tif (func_oid == ts_first_func_oid)\n\t\treturn BTLessStrategyNumber;\n\tif (func_oid == ts_last_func_oid)\n\t\treturn BTGreaterStrategyNumber;\n\n\treturn InvalidStrategy;\n}\n\nstatic bool\nis_first_last_node(Node *node, List **context)\n{\n\tif (node == NULL)\n\t\treturn false;\n\tif (IsA(node, Aggref))\n\t{\n\t\tAggref *aggref = (Aggref *) node;\n\n\t\tif (aggref->aggfnoid == ts_first_func_oid || aggref->aggfnoid == ts_last_func_oid)\n\t\t\treturn true;\n\t}\n\treturn expression_tree_walker(node, is_first_last_node, (void *) context);\n}\n\nstatic bool\ncontains_first_last_node(List *sortClause, List *targetList)\n{\n\tList *exprs = get_sortgrouplist_exprs(sortClause, targetList);\n\tListCell *cell;\n\tList *context = NIL;\n\n\tforeach (cell, exprs)\n\t{\n\t\tNode *expr = lfirst(cell);\n\t\tbool found = is_first_last_node(expr, &context);\n\n\t\tif (found)\n\t\t\treturn true;\n\t}\n\treturn false;\n}\n\n/*\n * preprocess_first_last_aggregates - preprocess FIRST/LAST aggregates\n *\n * Check to see whether the query contains FIRST/LAST aggregate functions that\n * might be optimizable via index scans.  If it does, and all the aggregates\n * are potentially optimizable, then create a MinMaxAggPath(reusing MinMax path implementation)\\\n * and add it to the (UPPERREL_GROUP_AGG, NULL) upperrel.\n *\n * This method is called from create_upper_paths_hook in the UPPERREL_GROUP_AGG stage.\n *\n * Note: we are passed the preprocessed targetlist separately, because it's\n * not necessarily equal to root->parse->targetList.\n *\n * Most of the code is borrowed from: preprocess_minmax_aggregates (planagg.c). Few\n * major differences:\n *  - generate FirstLastAggInfo that wraps MinMaxAggInfo\n *  - generate subquery (path) for FIRST/LAST (we reuse MinMaxAggPath)\n *  - replace Aggref node with Param node\n *\t- reject ORDER BY on FIRST/LAST\n */\nvoid\nts_preprocess_first_last_aggregates(PlannerInfo *root, List *tlist)\n{\n\tQuery *parse = root->parse;\n\tFromExpr *jtnode;\n\tRangeTblRef *rtr;\n\tRangeTblEntry *rte;\n\tList *first_last_aggs;\n\tRelOptInfo *grouped_rel;\n\tListCell *lc;\n\tList *mm_agg_list;\n\tMinMaxAggPath *minmaxagg_path;\n\n\t/* minmax_aggs list should be empty at this point */\n\tAssert(root->minmax_aggs == NIL);\n\n\t/* Nothing to do if query has no aggregates */\n\tif (!parse->hasAggs)\n\t\treturn;\n\n\tAssert(!parse->setOperations);\t/* shouldn't get here if a setop */\n\tAssert(parse->rowMarks == NIL); /* nor if FOR UPDATE */\n\n\t/*\n\t * Reject unoptimizable cases.\n\t *\n\t * We don't handle the case when agg function is in ORDER BY. The reason\n\t * being is that we replace Aggref node before sort keys are being\n\t * generated.\n\t *\n\t * We don't handle GROUP BY or windowing, because our current\n\t * implementations of grouping require looking at all the rows anyway, and\n\t * so there's not much point in optimizing FIRST/LAST.\n\t */\n\tif (parse->groupClause || list_length(parse->groupingSets) > 1 || parse->hasWindowFuncs ||\n\t\tcontains_first_last_node(parse->sortClause, tlist))\n\t\treturn;\n\n\t/*\n\t * Reject if query contains any CTEs; there's no way to build an indexscan\n\t * on one so we couldn't succeed here.  (If the CTEs are unreferenced,\n\t * that's not true, but it doesn't seem worth expending cycles to check.)\n\t */\n\tif (parse->cteList)\n\t\treturn;\n\n\t/*\n\t * We also restrict the query to reference exactly one table, since join\n\t * conditions can't be handled reasonably.  (We could perhaps handle a\n\t * query containing cartesian-product joins, but it hardly seems worth the\n\t * trouble.)  However, the single table could be buried in several levels\n\t * of FromExpr due to subqueries.  Note the \"single\" table could be an\n\t * inheritance parent, too, including the case of a UNION ALL subquery\n\t * that's been flattened to an appendrel.\n\t */\n\tjtnode = parse->jointree;\n\twhile (IsA(jtnode, FromExpr))\n\t{\n\t\tif (list_length(jtnode->fromlist) != 1)\n\t\t\treturn;\n\t\tjtnode = linitial(jtnode->fromlist);\n\t}\n\tif (!IsA(jtnode, RangeTblRef))\n\t\treturn;\n\trtr = (RangeTblRef *) jtnode;\n\trte = planner_rt_fetch(rtr->rtindex, root);\n\tif (rte->rtekind == RTE_RELATION)\n\t\t/* ordinary relation, ok */;\n\telse if (rte->rtekind == RTE_SUBQUERY && rte->inh)\n\t\t/* flattened UNION ALL subquery, ok */;\n\telse\n\t\treturn;\n\n\t/*\n\t * Scan the tlist and HAVING qual to find all the aggregates and verify\n\t * all are FIRST/LAST aggregates.  Stop as soon as we find one that isn't.\n\t */\n\tfirst_last_aggs = NIL;\n\tif (find_first_last_aggs_walker((Node *) tlist, &first_last_aggs))\n\t\treturn;\n\tif (find_first_last_aggs_walker(parse->havingQual, &first_last_aggs))\n\t\treturn;\n\n\t/*\n\t * OK, there is at least the possibility of performing the optimization.\n\t * Build an access path for each aggregate.  If any of the aggregates\n\t * prove to be non-indexable, give up; there is no point in optimizing\n\t * just some of them.\n\t */\n\tforeach (lc, first_last_aggs)\n\t{\n\t\tFirstLastAggInfo *fl_info = (FirstLastAggInfo *) lfirst(lc);\n\t\tMinMaxAggInfo *mminfo = fl_info->m_agg_info;\n\t\tOid eqop;\n\t\tbool reverse;\n\n\t\t/*\n\t\t * We'll need the equality operator that goes with the aggregate's\n\t\t * ordering operator.\n\t\t */\n\t\teqop = get_equality_op_for_ordering_op(mminfo->aggsortop, &reverse);\n\t\tif (!OidIsValid(eqop)) /* shouldn't happen */\n\t\t\telog(ERROR,\n\t\t\t\t \"could not find equality operator for ordering operator %u\",\n\t\t\t\t mminfo->aggsortop);\n\n\t\t/*\n\t\t * We can use either an ordering that gives NULLS FIRST or one that\n\t\t * gives NULLS LAST; furthermore there's unlikely to be much\n\t\t * performance difference between them, so it doesn't seem worth\n\t\t * costing out both ways if we get a hit on the first one.  NULLS\n\t\t * FIRST is more likely to be available if the operator is a\n\t\t * reverse-sort operator, so try that first if reverse.\n\t\t */\n\t\tif (build_first_last_path(root, fl_info, eqop, mminfo->aggsortop, reverse, reverse))\n\t\t\tcontinue;\n\t\tif (build_first_last_path(root, fl_info, eqop, mminfo->aggsortop, reverse, !reverse))\n\t\t\tcontinue;\n\n\t\t/* No indexable path for this aggregate, so fail */\n\t\treturn;\n\t}\n\n\t/*\n\t * OK, we can do the query this way. We are using MinMaxAggPath to store\n\t * First/Last Agg path since the logic is almost the same. MinMaxAggPath\n\t * is used later on by planner so by reusing it we don't need to re-invent\n\t * planner.\n\t *\n\t * Prepare to create a MinMaxAggPath node.\n\t *\n\t * First, create an output Param node for each agg.  (If we end up not\n\t * using the MinMaxAggPath, we'll waste a PARAM_EXEC slot for each agg,\n\t * which is not worth worrying about.  We can't wait till create_plan time\n\t * to decide whether to make the Param, unfortunately.)\n\t */\n\tmm_agg_list = NIL;\n\tforeach (lc, first_last_aggs)\n\t{\n\t\tFirstLastAggInfo *fl_info = (FirstLastAggInfo *) lfirst(lc);\n\t\tMinMaxAggInfo *mminfo = fl_info->m_agg_info;\n\n\t\tmminfo->param = SS_make_initplan_output_param(root,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  exprType((Node *) mminfo->target),\n\t\t\t\t\t\t\t\t\t\t\t\t\t  -1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  exprCollation((Node *) mminfo->target));\n\t\tmm_agg_list = lcons(mminfo, mm_agg_list);\n\t}\n\n\t/*\n\t * Create a MinMaxAggPath node with the appropriate estimated costs and\n\t * other needed data, and add it to the UPPERREL_GROUP_AGG upperrel, where\n\t * it will compete against the standard aggregate implementation.  (It\n\t * will likely always win, but we need not assume that here.)\n\t *\n\t * Note: grouping_planner won't have created this upperrel yet, but it's\n\t * fine for us to create it first.  We will not have inserted the correct\n\t * consider_parallel value in it, but MinMaxAggPath paths are currently\n\t * never parallel-safe anyway, so that doesn't matter.  Likewise, it\n\t * doesn't matter that we haven't filled FDW-related fields in the rel.\n\t */\n\tgrouped_rel = fetch_upper_rel(root, UPPERREL_GROUP_AGG, NULL);\n\tminmaxagg_path = create_minmaxagg_path(root,\n\t\t\t\t\t\t\t\t\t\t   grouped_rel,\n\t\t\t\t\t\t\t\t\t\t   create_pathtarget(root, tlist),\n\t\t\t\t\t\t\t\t\t\t   mm_agg_list,\n\t\t\t\t\t\t\t\t\t\t   (List *) parse->havingQual);\n\t/* Let's replace Aggref node since we will use subquery we've generated  */\n\treplace_aggref_in_tlist(minmaxagg_path);\n\tadd_path(grouped_rel, (Path *) minmaxagg_path);\n}\n\n/*\n * find_first_last_aggs_walker\n *\t\tRecursively scan the Aggref nodes in an expression tree, and check\n *\t\tthat each one is a FIRST/LAST aggregate.  If so, build a list of the\n *\t\tdistinct aggregate calls in the tree.\n *\n * Returns TRUE if a non-FIRST/LAST aggregate is found, FALSE otherwise.\n * (This seemingly-backward definition is used because expression_tree_walker\n * aborts the scan on TRUE return, which is what we want.)\n *\n * Found aggregates are added to the list at *context; it's up to the caller\n * to initialize the list to NIL.\n *\n * This does not descend into subqueries, and so should be used only after\n * reduction of sublinks to subplans.  There mustn't be outer-aggregate\n * references either.\n *\n * Major differences from find_minmax_aggs_walker (planagg.c):\n * - only allow Aggref with two arguments\n * - wrap agg info in FirstLastAggInfo\n */\nstatic bool\nfind_first_last_aggs_walker(Node *node, List **context)\n{\n\tif (node == NULL)\n\t\treturn false;\n\tif (IsA(node, Aggref))\n\t{\n\t\tAggref *aggref = (Aggref *) node;\n\t\tOid aggsortop;\n\t\tTargetEntry *value;\n\t\tTargetEntry *sort;\n\t\tMinMaxAggInfo *mminfo;\n\t\tListCell *l;\n\t\tFirstLastAggInfo *fl_info;\n\t\tOid sort_oid;\n\t\tTypeCacheEntry *sort_tce;\n\t\tStrategyNumber func_strategy;\n\n\t\tAssert(aggref->agglevelsup == 0);\n\t\tif (list_length(aggref->args) != 2)\n\t\t\treturn true; /* it couldn't be first/last */\n\n\t\t/*\n\t\t * ORDER BY is usually irrelevant for FIRST/LAST, but it can change\n\t\t * the outcome if the aggsortop's operator class recognizes\n\t\t * non-identical values as equal.  For example, 4.0 and 4.00 are equal\n\t\t * according to numeric_ops, yet distinguishable.  If FIRST() receives\n\t\t * more than one value equal to 4.0 and no value less than 4.0, it is\n\t\t * unspecified which of those equal values FIRST() returns.  An ORDER\n\t\t * BY expression that differs for each of those equal values of the\n\t\t * argument expression makes the result predictable once again.  This\n\t\t * is a niche requirement, and we do not implement it with subquery\n\t\t * paths. In any case, this test lets us reject ordered-set aggregates\n\t\t * quickly.\n\t\t */\n\t\tif (aggref->aggorder != NIL)\n\t\t\treturn true;\n\t\t/* note: we do not care if DISTINCT is mentioned ... */\n\n\t\t/*\n\t\t * We might implement the optimization when a FILTER clause is present\n\t\t * by adding the filter to the quals of the generated subquery.  For\n\t\t * now, just punt.\n\t\t */\n\t\tif (aggref->aggfilter != NULL)\n\t\t\treturn true;\n\n\t\t/* We sort by second argument (eg. time) */\n\t\tsort_oid = lsecond_oid(aggref->aggargtypes);\n\n\t\tfunc_strategy = get_func_strategy(aggref->aggfnoid);\n\t\tif (func_strategy == InvalidStrategy)\n\t\t\treturn true; /* not first/last aggregate */\n\n\t\tsort_tce = lookup_type_cache(sort_oid, TYPECACHE_BTREE_OPFAMILY);\n\t\taggsortop = get_opfamily_member(sort_tce->btree_opf, sort_oid, sort_oid, func_strategy);\n\t\tif (!OidIsValid(aggsortop))\n\t\t\telog(ERROR,\n\t\t\t\t \"Cannot resolve sort operator for function \\\"%s\\\" and type \\\"%s\\\"\",\n\t\t\t\t format_procedure(aggref->aggfnoid),\n\t\t\t\t format_type_be(sort_oid));\n\n\t\t/* Used in projection */\n\t\tvalue = (TargetEntry *) linitial(aggref->args);\n\t\t/* Used in ORDER BY */\n\t\tsort = (TargetEntry *) lsecond(aggref->args);\n\n\t\tif (contain_mutable_functions((Node *) sort->expr))\n\t\t\treturn true; /* not potentially indexable */\n\n\t\tif (type_is_rowtype(exprType((Node *) sort->expr)))\n\t\t\treturn true; /* IS NOT NULL would have weird semantics */\n\n\t\t/*\n\t\t * Check whether it's already in the list, and add it if not.\n\t\t */\n\t\tforeach (l, *context)\n\t\t{\n\t\t\tmminfo = (MinMaxAggInfo *) lfirst(l);\n\t\t\tif (mminfo->aggfnoid == aggref->aggfnoid && equal(mminfo->target, value->expr))\n\t\t\t\treturn false;\n\t\t}\n\n\t\tmminfo = makeNode(MinMaxAggInfo);\n\t\tmminfo->aggfnoid = aggref->aggfnoid;\n\t\tmminfo->aggsortop = aggsortop;\n\t\tmminfo->target = value->expr;\n\t\tmminfo->subroot = NULL;\n\t\tmminfo->path = NULL;\n\t\tmminfo->pathcost = 0;\n\t\tmminfo->param = NULL;\n\n\t\tfl_info = palloc(sizeof(FirstLastAggInfo));\n\n\t\tfl_info->m_agg_info = mminfo;\n\t\tfl_info->sort = sort->expr;\n\t\t*context = lappend(*context, fl_info);\n\n\t\t/*\n\t\t * We need not recurse into the argument, since it can't contain any\n\t\t * aggregates.\n\t\t */\n\t\treturn false;\n\t}\n\tAssert(!IsA(node, SubLink));\n\treturn expression_tree_walker(node, find_first_last_aggs_walker, (void *) context);\n}\n\n/*\n * build_first_last_path\n *\t\tGiven a FIRST/LAST aggregate, try to build an indexscan Path it can be\n *\t\toptimized with.\n *\t\tWe will generate subquery with value and sort target, where we\n *\t\tSELECT value and we ORDER BY sort.\n *\n * If successful, stash the best path in *mminfo and return TRUE.\n * Otherwise, return FALSE.\n *\n * Major differences when compared to build_minmax_path(planagg.c):\n * - generates different subquery\n * - works with two target entries (value and sortby)\n * - resets EquivalenceClass(es)\n *\n */\nstatic bool\nbuild_first_last_path(PlannerInfo *root, FirstLastAggInfo *fl_info, Oid eqop, Oid sortop,\n\t\t\t\t\t  bool reverse_sort, bool nulls_first)\n{\n\tPlannerInfo *subroot;\n\tQuery *parse;\n\tTargetEntry *value_target;\n\tTargetEntry *sort_target;\n\tList *tlist;\n\tNullTest *ntest;\n\tSortGroupClause *sortcl;\n\tRelOptInfo *final_rel;\n\tPath *sorted_path;\n\tCost path_cost;\n\tdouble path_fraction;\n\tMinMaxAggInfo *mminfo;\n\tListCell *lc;\n\n\t/*\n\t * We are going to construct what is effectively a sub-SELECT query, so\n\t * clone the current query level's state and adjust it to make it look\n\t * like a subquery.  Any outer references will now be one level higher\n\t * than before.  (This means that when we are done, there will be no Vars\n\t * of level 1, which is why the subquery can become an initplan.)\n\t */\n\tsubroot = (PlannerInfo *) palloc(sizeof(PlannerInfo));\n\tmemcpy(subroot, root, sizeof(PlannerInfo));\n\tsubroot->query_level++;\n\tsubroot->parent_root = root;\n\t/* reset subplan-related stuff */\n\tsubroot->plan_params = NIL;\n\tsubroot->outer_params = NULL;\n\tsubroot->init_plans = NIL;\n\t/* reset EquivalenceClass since we will create it later on */\n\tsubroot->eq_classes = NIL;\n\n\tsubroot->parse = parse = copyObject(root->parse);\n\tIncrementVarSublevelsUp((Node *) parse, 1, 1);\n\n#if PG16_GE\n\t/* Reset placeholdersFrozen: https://github.com/postgres/postgres/commit/b3ff6c74 */\n\tsubroot->placeholdersFrozen = false;\n#endif\n\n\t/* append_rel_list might contain outer Vars? */\n\tsubroot->append_rel_list = copyObject(root->append_rel_list);\n\tIncrementVarSublevelsUp((Node *) subroot->append_rel_list, 1, 1);\n\t/* There shouldn't be any OJ info to translate, as yet */\n\tAssert(subroot->join_info_list == NIL);\n\t/* and we haven't made equivalence classes, either */\n\tAssert(subroot->eq_classes == NIL);\n\t/* and we haven't created PlaceHolderInfos, either */\n\tAssert(subroot->placeholder_list == NIL);\n\n\tmminfo = fl_info->m_agg_info;\n\n\t/*----------\n\t * Generate modified query of the form\n\t *\t\t(SELECT value FROM tab\n\t *\t\t WHERE sort IS NOT NULL AND existing-quals\n\t *\t\t ORDER BY sort ASC/DESC\n\t *\t\t LIMIT 1)\n\t *----------\n\t */\n\n\t/*\n\t * Value and sort target entries but sort target is eliminated later on\n\t * from target list\n\t */\n\tvalue_target =\n\t\tmakeTargetEntry(copyObject(mminfo->target), (AttrNumber) 1, pstrdup(\"value\"), false);\n\tsort_target = makeTargetEntry(copyObject(fl_info->sort), (AttrNumber) 2, pstrdup(\"sort\"), true);\n\ttlist = list_make2(value_target, sort_target);\n\tsubroot->processed_tlist = parse->targetList = tlist;\n\n\t/* No HAVING, no DISTINCT, no aggregates anymore */\n\tparse->havingQual = NULL;\n\tsubroot->hasHavingQual = false;\n\tparse->distinctClause = NIL;\n\tparse->hasDistinctOn = false;\n\tparse->hasAggs = false;\n\n\t/*\n\t * Build \"sort IS NOT NULL\" expression. Note that target can still be NULL.\n\t * We don't need it if the order is NULLS LAST.\n\t */\n\tif (nulls_first)\n\t{\n\t\tntest = makeNode(NullTest);\n\t\tntest->nulltesttype = IS_NOT_NULL;\n\t\tntest->arg = copyObject(fl_info->sort);\n\t\t/* we checked it wasn't a rowtype in find_minmax_aggs_walker */\n\t\tntest->argisrow = false;\n\t\tntest->location = -1;\n\n\t\t/* User might have had that in WHERE already */\n\t\tif (!list_member((List *) parse->jointree->quals, ntest))\n\t\t\tparse->jointree->quals = (Node *) lcons(ntest, (List *) parse->jointree->quals);\n\t}\n\n\t/* Build suitable ORDER BY clause */\n\tsortcl = makeNode(SortGroupClause);\n\tsortcl->tleSortGroupRef = assignSortGroupRef(sort_target, tlist);\n\tsortcl->eqop = eqop;\n\tsortcl->sortop = sortop;\n\tsortcl->nulls_first = nulls_first;\n\tsortcl->hashable = false; /* no need to make this accurate */\n#if PG18_GE\n\t/* Track sort direction in SortGroupClause\n\t * https://github.com/postgres/postgres/commit/0d2aa4d4\n\t */\n\tsortcl->reverse_sort = reverse_sort;\n#endif\n\tparse->sortClause = list_make1(sortcl);\n\n\t/* set up expressions for LIMIT 1 */\n\tparse->limitOffset = NULL;\n\tparse->limitCount = (Node *)\n\t\tmakeConst(INT8OID, -1, InvalidOid, sizeof(int64), Int64GetDatum(1), false, FLOAT8PASSBYVAL);\n\n\t/*\n\t * Generate the best paths for this query, telling query_planner that we\n\t * have LIMIT 1.\n\t */\n\tsubroot->tuple_fraction = 1.0;\n\tsubroot->limit_tuples = 1.0;\n\n\t/* min/max optimizations usually happen before\n\t * inheritance-relations are expanded, and thus query_planner will\n\t * try to expand our hypertables if they are marked as\n\t * inheritance-relations. Since we do not want this, we must mark\n\t * hypertables as non-inheritance now.\n\t */\n\tforeach (lc, subroot->parse->rtable)\n\t{\n\t\tRangeTblEntry *rte = lfirst_node(RangeTblEntry, lc);\n\n\t\tif (ts_rte_is_hypertable(rte))\n\t\t{\n\t\t\tListCell *prev = NULL;\n\t\t\tListCell *next = list_head(subroot->append_rel_list);\n\t\t\tAssert(rte->inh);\n\t\t\trte->inh = false;\n\t\t\t/* query planner gets confused when entries in the\n\t\t\t * append_rel_list refer to entries in the relarray that\n\t\t\t * don't exist. Since we need to expand hypertables in the\n\t\t\t * subquery, all of the chunk entries will be invalid in\n\t\t\t * this manner, so we remove them from the list.\n\t\t\t */\n\t\t\t/*  Performance Enhancement: This can be made non-quadratic by:\n\t\t\t *  1) Loop once over all RTEs, storing the relid of any RTE that is a hypertable in\n\t\t\t * a bitset and setting its 'inh' flag to false 2) Loop over the append_rel_list,\n\t\t\t * removing any AppendRelInfo that has a parent relid which is in the previously\n\t\t\t * created bitset (i.e., is a hypertable)\n\t\t\t */\n\t\t\twhile (next != NULL)\n\t\t\t{\n\t\t\t\tAppendRelInfo *app = lfirst(next);\n\t\t\t\tif (app->parent_reloid == rte->relid)\n\t\t\t\t{\n\t\t\t\t\tsubroot->append_rel_list = list_delete_cell(subroot->append_rel_list, next);\n\t\t\t\t\tnext = prev != NULL ? lnext(subroot->append_rel_list, next) :\n\t\t\t\t\t\t\t\t\t\t  list_head(subroot->append_rel_list);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tprev = next;\n\t\t\t\t\tnext = lnext(subroot->append_rel_list, next);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfinal_rel = query_planner(subroot, first_last_qp_callback, NULL);\n\n\t/* we need to disable inheritance so the chunks are re-expanded correctly in the subroot */\n\tforeach (lc, root->parse->rtable)\n\t{\n\t\tRangeTblEntry *rte = lfirst_node(RangeTblEntry, lc);\n\n\t\tif (ts_rte_is_hypertable(rte))\n\t\t\trte->inh = true;\n\t}\n\n\t/*\n\t * Since we didn't go through subquery_planner() to handle the subquery,\n\t * we have to do some of the same cleanup it would do, in particular cope\n\t * with params and initplans used within this subquery.  (This won't\n\t * matter if we end up not using the subplan.)\n\t */\n\tSS_identify_outer_params(subroot);\n\tSS_charge_for_initplans(subroot, final_rel);\n\n\t/*\n\t * Get the best presorted path, that being the one that's cheapest for\n\t * fetching just one row.  If there's no such path, fail.\n\t */\n\tif (final_rel->rows > 1.0)\n\t\tpath_fraction = 1.0 / final_rel->rows;\n\telse\n\t\tpath_fraction = 1.0;\n\n\tsorted_path = get_cheapest_fractional_path_for_pathkeys(final_rel->pathlist,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsubroot->query_pathkeys,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tpath_fraction);\n\tif (!sorted_path)\n\t\treturn false;\n\n\t/*\n\t * The path might not return exactly what we want, so fix that.  (We\n\t * assume that this won't change any conclusions about which was the\n\t * cheapest path.)\n\t */\n\tsorted_path = apply_projection_to_path(subroot,\n\t\t\t\t\t\t\t\t\t\t   final_rel,\n\t\t\t\t\t\t\t\t\t\t   sorted_path,\n\t\t\t\t\t\t\t\t\t\t   create_pathtarget(subroot, tlist));\n\n\t/*\n\t * Determine cost to get just the first row of the presorted path.\n\t *\n\t * Note: cost calculation here should match\n\t * compare_fractional_path_costs().\n\t */\n\tpath_cost = sorted_path->startup_cost +\n\t\t\t\tpath_fraction * (sorted_path->total_cost - sorted_path->startup_cost);\n\n\t/* Save state for further processing */\n\tmminfo->subroot = subroot;\n\tmminfo->path = sorted_path;\n\tmminfo->pathcost = path_cost;\n\n\treturn true;\n}\n\n/*\n * Compute query_pathkeys and other pathkeys during query_planner()\n */\nstatic void\nfirst_last_qp_callback(PlannerInfo *root, void *extra)\n{\n\troot->group_pathkeys = NIL;\n\troot->window_pathkeys = NIL;\n\troot->distinct_pathkeys = NIL;\n\n\troot->sort_pathkeys =\n\t\tmake_pathkeys_for_sortclauses(root, root->parse->sortClause, root->parse->targetList);\n\n\troot->query_pathkeys = root->sort_pathkeys;\n}\n"
  },
  {
    "path": "src/planner/constify_now.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <access/xact.h>\n#include <datatype/timestamp.h>\n#include <nodes/makefuncs.h>\n#include <optimizer/optimizer.h>\n#include <utils/fmgroids.h>\n\n#include \"cache.h\"\n#include \"dimension.h\"\n#include \"hypertable.h\"\n#include \"hypertable_cache.h\"\n#include \"planner.h\"\n\n/*\n * This implements an optimization to allow now() expression to be\n * used during plan time chunk exclusions. Since now() is stable it\n * would not normally be considered for plan time chunk exclusion.\n * To enable this behaviour we convert `column > now()` expressions\n * into `column > const AND column > now()`. Assuming that times\n * always moves forward this is safe even for prepared statements.\n *\n * We consider the following expressions valid for this optimization:\n * - Var > now()\n * - Var >= now()\n * - Var > now() - Interval\n * - Var > now() + Interval\n * - Var >= now() - Interval\n * - Var >= now() + Interval\n *\n * Interval needs to be Const in those expressions.\n */\nstatic const Dimension *\nget_hypertable_dimension(Oid relid, int flags)\n{\n\tHypertable *ht = ts_planner_get_hypertable(relid, flags);\n\tif (!ht)\n\t\treturn NULL;\n\treturn hyperspace_get_open_dimension(ht->space, 0);\n}\n\nbool\nis_valid_now_func(Node *node)\n{\n\tif (IsA(node, FuncExpr) && castNode(FuncExpr, node)->funcid == F_NOW)\n\t\treturn true;\n\n\tif (IsA(node, SQLValueFunction) &&\n\t\tcastNode(SQLValueFunction, node)->type == SVFOP_CURRENT_TIMESTAMP)\n\t\treturn true;\n\n\treturn false;\n}\n\nstatic bool\nis_valid_now_expr(OpExpr *op, List *rtable)\n{\n\tint flags = CACHE_FLAG_MISSING_OK | CACHE_FLAG_NOCREATE;\n\t/* Var > or Var >= */\n\tif ((op->opfuncid != F_TIMESTAMPTZ_GT && op->opfuncid != F_TIMESTAMPTZ_GE) ||\n\t\t!IsA(linitial(op->args), Var))\n\t\treturn false;\n\n\t/*\n\t * Check that the constraint is actually on a partitioning\n\t * column. We only check for match on first open dimension\n\t * because that will be the time column.\n\t */\n\tVar *var = linitial_node(Var, op->args);\n\tif (var->varlevelsup != 0)\n\t\treturn false;\n\tAssert((int) var->varno <= list_length(rtable));\n\tRangeTblEntry *rte = list_nth(rtable, var->varno - 1);\n\n\t/*\n\t * If this query on a view we might have a subquery here\n\t * and need to peek into the subquery range table to check\n\t * if the constraints are on a hypertable.\n\t */\n\tif (rte->rtekind == RTE_SUBQUERY)\n\t{\n\t\t/*\n\t\t * Unfortunately the mechanism used to warm up the\n\t\t * hypertable cache does not apply to hypertables\n\t\t * referenced indirectly eg through VIEWs. So we\n\t\t * have to do the lookup for this hypertable without\n\t\t * CACHE_FLAG_NOCREATE flag.\n\t\t */\n\t\tflags = CACHE_FLAG_MISSING_OK;\n\t\tTargetEntry *tle = list_nth(rte->subquery->targetList, var->varattno - 1);\n\t\tif (!IsA(tle->expr, Var))\n\t\t\treturn false;\n\t\tvar = castNode(Var, tle->expr);\n\t\tif (var->varlevelsup != 0)\n\t\t\treturn false;\n#if PG18_GE\n\t\t/* PG18 introduced RTEs for group clauses so\n\t\t * we can use rtable to look up GROUP BY expressions.\n\t\t *\n\t\t * https://github.com/postgres/postgres/commit/247dea89\n\t\t */\n\t\tRangeTblEntry *group_rte = list_nth(rte->subquery->rtable, var->varno - 1);\n\t\tif (group_rte->rtekind == RTE_GROUP)\n\t\t{\n\t\t\tAssert(var->varattno > 0);\n\t\t\tExpr *node = list_nth(group_rte->groupexprs, var->varattno - 1);\n\t\t\tif (!IsA(node, Var))\n\t\t\t\treturn false;\n\t\t\tvar = castNode(Var, node);\n\t\t\tAssert(var->varno > 0);\n\t\t\tif (var->varlevelsup != 0)\n\t\t\t\treturn false;\n\t\t}\n#endif\n\t\trte = list_nth(rte->subquery->rtable, var->varno - 1);\n\t}\n\n\tconst Dimension *dim = get_hypertable_dimension(rte->relid, flags);\n\tif (!dim || dim->fd.column_type != TIMESTAMPTZOID || dim->column_attno != var->varattno)\n\t\treturn false;\n\n\t/* Var > now() or Var >= now() */\n\tif (is_valid_now_func(lsecond(op->args)))\n\t\treturn true;\n\n\tif (!IsA(lsecond(op->args), OpExpr))\n\t\treturn false;\n\n\t/* Var >|>= now() +|- Const */\n\tOpExpr *op_inner = lsecond_node(OpExpr, op->args);\n\tif ((op_inner->opfuncid != F_TIMESTAMPTZ_MI_INTERVAL &&\n\t\t op_inner->opfuncid != F_TIMESTAMPTZ_PL_INTERVAL) ||\n\t\t!is_valid_now_func(linitial(op_inner->args)) || !IsA(lsecond(op_inner->args), Const))\n\t\treturn false;\n\n\t/*\n\t * The consttype check should not be necessary since the\n\t * operators we whitelist above already mandates it.\n\t */\n\tConst *c = lsecond_node(Const, op_inner->args);\n\tAssert(c->consttype == INTERVALOID);\n\tif (c->constisnull || c->consttype != INTERVALOID)\n\t\treturn false;\n\n\treturn true;\n}\n\nstatic Const *\nmake_now_const()\n{\n\treturn makeConst(TIMESTAMPTZOID,\n\t\t\t\t\t -1,\n\t\t\t\t\t InvalidOid,\n\t\t\t\t\t sizeof(TimestampTz),\n#ifdef TS_DEBUG\n\t\t\t\t\t ts_get_mock_time_or_current_time(),\n#else\n\t\t\t\t\t TimestampTzGetDatum(GetCurrentTransactionStartTimestamp()),\n#endif\n\t\t\t\t\t false,\n\t\t\t\t\t FLOAT8PASSBYVAL);\n}\n\n/* returns a copy of the expression with the now() call constified */\n/*\n * op will be OpExpr with Var > now() - Expr\n */\nstatic OpExpr *\nconstify_now_expr(PlannerInfo *root, OpExpr *op)\n{\n\top = copyObject(op);\n\top->location = PLANNER_LOCATION_MAGIC;\n\tif (is_valid_now_func(lsecond(op->args)))\n\t{\n\t\tlsecond(op->args) = make_now_const();\n\n\t\treturn op;\n\t}\n\telse\n\t{\n\t\tOpExpr *op_inner = lsecond_node(OpExpr, op->args);\n\t\tConst *const_offset = lsecond_node(Const, op_inner->args);\n\t\tAssert(const_offset->consttype == INTERVALOID);\n\t\tInterval *offset = DatumGetIntervalP(const_offset->constvalue);\n\t\t/*\n\t\t * Sanity check that this is a supported expression. We should never\n\t\t * end here if it isn't since this is checked in is_valid_now_expr.\n\t\t */\n\t\tAssert(is_valid_now_func(linitial(op_inner->args)));\n\t\tConst *now = make_now_const();\n\t\tlinitial(op_inner->args) = now;\n\n\t\t/*\n\t\t * If the interval has a day component then the calculation needs\n\t\t * to take into account daylight saving time switches and thereby a\n\t\t * day would not always be exactly 24 hours. We mitigate this by\n\t\t * adding a safety buffer to account for these dst switches when\n\t\t * dealing with intervals with day component. These calculations\n\t\t * will be repeated with exact values during execution.\n\t\t * Since dst switches seem to range between -1 and 2 hours we set\n\t\t * the safety buffer to 4 hours.\n\t\t * When dealing with Intervals with month component timezone changes\n\t\t * can result in multiple day differences in the outcome of these\n\t\t * calculations due to different month lengths. When dealing with\n\t\t * months we add a 7 day safety buffer.\n\t\t * For all these calculations it is fine if we exclude less chunks\n\t\t * than strictly required for the operation, additional exclusion\n\t\t * with exact values will happen in the executor. But under no\n\t\t * circumstances must we exclude too much cause there would be\n\t\t * no way for the executor to get those chunks back.\n\t\t */\n\t\tif (offset->day != 0 || offset->month != 0)\n\t\t{\n\t\t\tTimestampTz now_value = DatumGetTimestampTz(now->constvalue);\n\t\t\tif (offset->month != 0)\n\t\t\t\tnow_value -= 7 * USECS_PER_DAY;\n\t\t\tif (offset->day != 0)\n\t\t\t\tnow_value -= 4 * USECS_PER_HOUR;\n\n\t\t\tnow->constvalue = TimestampTzGetDatum(now_value);\n\t\t}\n\n\t\t/*\n\t\t * Normally estimate_expression_value is not safe to use during planning\n\t\t * since it also evaluates stable expressions. Since we only allow a\n\t\t * very limited subset of expressions for this optimization it is safe\n\t\t * for those expressions we allowed earlier.\n\t\t * estimate_expression_value should always be able to completely constify\n\t\t * the expression due to the restrictions we impose on the expressions\n\t\t * supported.\n\t\t */\n\t\tlsecond(op->args) = estimate_expression_value(root, (Node *) op_inner);\n\t\tAssert(IsA(lsecond(op->args), Const));\n\t\top->location = PLANNER_LOCATION_MAGIC;\n\t\treturn op;\n\t}\n}\n\nNode *\nts_constify_now(PlannerInfo *root, List *rtable, Node *node)\n{\n\tAssert(node);\n\n\tswitch (nodeTag(node))\n\t{\n\t\tcase T_OpExpr:\n\t\t\tif (is_valid_now_expr(castNode(OpExpr, node), rtable))\n\t\t\t{\n\t\t\t\tList *args =\n\t\t\t\t\tlist_make2(copyObject(node), constify_now_expr(root, castNode(OpExpr, node)));\n\t\t\t\treturn (Node *) makeBoolExpr(AND_EXPR, args, -1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase T_BoolExpr:\n\t\t{\n\t\t\tList *additions = NIL;\n\t\t\tListCell *lc;\n\t\t\tBoolExpr *be = castNode(BoolExpr, node);\n\n\t\t\t/* We only look for top-level AND */\n\t\t\tif (be->boolop != AND_EXPR)\n\t\t\t\treturn node;\n\n\t\t\tforeach (lc, be->args)\n\t\t\t{\n\t\t\t\tadditions = lappend(additions, ts_constify_now(root, rtable, (Node *) lfirst(lc)));\n\t\t\t}\n\n\t\t\tif (additions)\n\t\t\t\tbe->args = additions;\n\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\n\treturn node;\n}\n"
  },
  {
    "path": "src/planner/constraint_cleanup.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <nodes/pathnodes.h>\n\n#include \"planner.h\"\n\n/*\n * This code deals with removing the intermediate constraints\n * we added before planning to improve chunk exclusion.\n */\n\nstatic bool\nrestrictinfo_is_marked(RestrictInfo *ri)\n{\n\tswitch (nodeTag(ri->clause))\n\t{\n\t\tcase T_OpExpr:\n\t\t\treturn castNode(OpExpr, ri->clause)->location == PLANNER_LOCATION_MAGIC;\n\t\tcase T_ScalarArrayOpExpr:\n\t\t\treturn castNode(ScalarArrayOpExpr, ri->clause)->location == PLANNER_LOCATION_MAGIC;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\treturn false;\n}\n\n/*\n * Remove marked constraints from RestrictInfo clause.\n */\nstatic List *\nrestrictinfo_cleanup(List *restrictinfos, bool *pfiltered)\n{\n\tList *filtered_ri = NIL;\n\tListCell *lc;\n\tbool filtered = false;\n\tif (!restrictinfos)\n\t\treturn NULL;\n\n\tforeach (lc, restrictinfos)\n\t{\n\t\tRestrictInfo *ri = (RestrictInfo *) lfirst(lc);\n\t\tif (restrictinfo_is_marked(ri))\n\t\t{\n\t\t\tfiltered = true;\n\t\t\tcontinue;\n\t\t}\n\t\tfiltered_ri = lappend(filtered_ri, ri);\n\t}\n\n\tif (pfiltered)\n\t\t*pfiltered = filtered;\n\n\treturn filtered ? filtered_ri : restrictinfos;\n}\n\n/*\n * Remove marked constraints from IndexPath.\n */\nstatic void\nindexpath_cleanup(IndexPath *path)\n{\n\tListCell *lc;\n\tList *filtered_ic = NIL;\n\tpath->indexinfo->indrestrictinfo = restrictinfo_cleanup(path->indexinfo->indrestrictinfo, NULL);\n\n\tforeach (lc, path->indexclauses)\n\t{\n\t\tIndexClause *iclause = lfirst_node(IndexClause, lc);\n\t\tif (restrictinfo_is_marked(iclause->rinfo))\n\t\t\tcontinue;\n\n\t\tfiltered_ic = lappend(filtered_ic, iclause);\n\t}\n\tpath->indexclauses = filtered_ic;\n}\n\nvoid\nts_planner_constraint_cleanup(PlannerInfo *root, RelOptInfo *rel)\n{\n\tListCell *lc;\n\tbool filtered = false;\n\tif (rel->baserestrictinfo)\n\t\trel->baserestrictinfo = restrictinfo_cleanup(rel->baserestrictinfo, &filtered);\n\n\t/*\n\t * If we added constraints those will be present in baserestrictinfo.\n\t * If we did not remove anything from baserestrictinfo in the step\n\t * above we can skip looking in the paths.\n\t */\n\tif (filtered)\n\t{\n\t\t/*\n\t\t * For seqscan cleaning up baserestrictinfo is enough but for\n\t\t * BitmapHeapPath and IndexPath we need some extra steps.\n\t\t */\n\t\tforeach (lc, rel->pathlist)\n\t\t{\n\t\t\tswitch (nodeTag(lfirst(lc)))\n\t\t\t{\n\t\t\t\tcase T_BitmapHeapPath:\n\t\t\t\t{\n\t\t\t\t\tBitmapHeapPath *path = lfirst_node(BitmapHeapPath, lc);\n\t\t\t\t\tif (IsA(path->bitmapqual, IndexPath))\n\t\t\t\t\t\tindexpath_cleanup(castNode(IndexPath, path->bitmapqual));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase T_IndexPath:\n\t\t\t\t{\n\t\t\t\t\tindexpath_cleanup(castNode(IndexPath, lfirst(lc)));\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/planner/expand_hypertable.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n/* This planner optimization reduces planning times when a hypertable has many chunks.\n * It does this by expanding hypertable chunks manually, eliding the `expand_inherited_tables`\n * logic used by PG.\n *\n * Slow planning time were previously seen because `expand_inherited_tables` expands all chunks of\n * a hypertable, without regard to constraints present in the query. Then, `get_relation_info` is\n * called on all chunks before constraint exclusion. Getting the statistics on many chunks ends\n * up being expensive because RelationGetNumberOfBlocks has to open the file for each relation.\n * This gets even worse under high concurrency.\n *\n * This logic solves this by expanding only the chunks needed to fulfil the query instead of all\n * chunks. In effect, it moves chunk exclusion up in the planning process. But, we actually don't\n * use constraint exclusion here, but rather a variant of range exclusion implemented by\n * HypertableRestrictInfo.\n * */\n\n#include <postgres.h>\n#include <catalog/pg_constraint.h>\n#include <catalog/pg_inherits.h>\n#include <catalog/pg_namespace.h>\n#include <catalog/pg_type.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodeFuncs.h>\n#include <nodes/plannodes.h>\n#include <optimizer/cost.h>\n#include <optimizer/optimizer.h>\n#include <optimizer/pathnode.h>\n#include <optimizer/planmain.h>\n#include <optimizer/planner.h>\n#include <optimizer/prep.h>\n#include <optimizer/restrictinfo.h>\n#include <optimizer/tlist.h>\n#include <parser/parse_func.h>\n#include <parser/parse_type.h>\n#include <parser/parsetree.h>\n#include <partitioning/partbounds.h>\n#include <utils/builtins.h>\n#include <utils/date.h>\n#include <utils/errcodes.h>\n#include <utils/fmgroids.h>\n#include <utils/fmgrprotos.h>\n#include <utils/lsyscache.h>\n#include <utils/syscache.h>\n#include <utils/timestamp.h>\n#include <utils/typcache.h>\n#include <utils/uuid.h>\n\n#include \"compat/compat.h\"\n#include \"annotations.h\"\n#include \"chunk.h\"\n#include \"cross_module_fn.h\"\n#include \"guc.h\"\n#include \"hypercube.h\"\n#include \"hypertable.h\"\n#include \"hypertable_restrict_info.h\"\n#include \"import/planner.h\"\n#include \"nodes/chunk_append/chunk_append.h\"\n#include \"planner.h\"\n#include \"time_utils.h\"\n#include \"uuid.h\"\n\ntypedef struct CollectQualCtx\n{\n\tPlannerInfo *root;\n\tRelOptInfo *rel;\n\tList *restrictions;\n\tList *propagate_conditions;\n\tList *all_quals;\n\tint join_level;\n} CollectQualCtx;\n\nstatic void propagate_join_quals(PlannerInfo *root, RelOptInfo *rel, CollectQualCtx *ctx);\n\nstatic bool\nis_time_bucket_function(Expr *node)\n{\n\tif (IsA(node, FuncExpr) &&\n\t\tstrncmp(get_func_name(castNode(FuncExpr, node)->funcid), \"time_bucket\", NAMEDATALEN) == 0)\n\t\treturn true;\n\n\treturn false;\n}\n\nstatic void\nts_add_append_rel_infos(PlannerInfo *root, List *appinfos)\n{\n\tListCell *lc;\n\n\troot->append_rel_list = list_concat(root->append_rel_list, appinfos);\n\n\t/* root->append_rel_array is required to be able to hold all the\n\t * additional entries by previous call to expand_planner_arrays */\n\tAssert(root->append_rel_array);\n\n\tforeach (lc, appinfos)\n\t{\n\t\tAppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);\n\t\tint child_relid = appinfo->child_relid;\n\t\tAssert(child_relid < root->simple_rel_array_size);\n\n\t\troot->append_rel_array[child_relid] = appinfo;\n\t}\n}\n\n/*\n * Pre-check to determine if an expression is eligible for constification.\n * A more thorough check is in constify_timestamptz_op_interval.\n */\nstatic bool\nis_timestamptz_op_interval(Expr *expr)\n{\n\tOpExpr *op;\n\tConst *c1, *c2;\n\n\tif (!IsA(expr, OpExpr))\n\t\treturn false;\n\n\top = castNode(OpExpr, expr);\n\n\tif (op->opresulttype != TIMESTAMPTZOID || op->args->length != 2 ||\n\t\t!IsA(linitial(op->args), Const) || !IsA(llast(op->args), Const))\n\t\treturn false;\n\n\tc1 = linitial_node(Const, op->args);\n\tc2 = llast_node(Const, op->args);\n\n\treturn (c1->consttype == TIMESTAMPTZOID && c2->consttype == INTERVALOID) ||\n\t\t   (c1->consttype == INTERVALOID && c2->consttype == TIMESTAMPTZOID);\n}\n\nstatic Const *\nintegral_timeval_to_const(int64 value, Oid type)\n{\n\tbool typbyval = get_typbyval(type);\n\n\tswitch (type)\n\t{\n\t\tcase INT2OID:\n\t\t\treturn makeConst(type, -1, InvalidOid, 2, Int16GetDatum(value), false, typbyval);\n\t\tcase INT4OID:\n\t\t\treturn makeConst(type, -1, InvalidOid, 4, Int32GetDatum(value), false, typbyval);\n\t\tcase INT8OID:\n\t\t\treturn makeConst(type, -1, InvalidOid, 8, Int64GetDatum(value), false, typbyval);\n\t\tcase DATEOID:\n\t\t\treturn makeConst(type,\n\t\t\t\t\t\t\t -1,\n\t\t\t\t\t\t\t InvalidOid,\n\t\t\t\t\t\t\t sizeof(DateADT),\n\t\t\t\t\t\t\t DateADTGetDatum(value),\n\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t typbyval);\n\t\tcase TIMESTAMPOID:\n\t\t\treturn makeConst(type,\n\t\t\t\t\t\t\t -1,\n\t\t\t\t\t\t\t InvalidOid,\n\t\t\t\t\t\t\t sizeof(Timestamp),\n\t\t\t\t\t\t\t TimestampGetDatum(value),\n\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t typbyval);\n\t\tcase TIMESTAMPTZOID:\n\t\t\treturn makeConst(type,\n\t\t\t\t\t\t\t -1,\n\t\t\t\t\t\t\t InvalidOid,\n\t\t\t\t\t\t\t sizeof(TimestampTz),\n\t\t\t\t\t\t\t TimestampTzGetDatum(value),\n\t\t\t\t\t\t\t false,\n\t\t\t\t\t\t\t typbyval);\n\t\tcase UUIDOID:\n\t\t{\n\t\t\t/*\n\t\t\t * UUIDv7 doesn't support timestamps smaller than the UNIX epoch. However, caggs often\n\t\t\t * refresh from \"beginning of time\" so we need to restrict the lower boundary value to\n\t\t\t * the UNIX epoch.\n\t\t\t */\n\t\t\tif (value <= UNIX_EPOCH_AS_TIMESTAMP)\n\t\t\t\tvalue = UNIX_EPOCH_AS_TIMESTAMP;\n\n\t\t\tpg_uuid_t *uuid = ts_create_uuid_v7_from_timestamptz((TimestampTz) value, true);\n\t\t\treturn makeConst(type, -1, InvalidOid, UUID_LEN, UUIDPGetDatum(uuid), false, typbyval);\n\t\t}\n\t\tdefault:\n\t\t\telog(ERROR, \"unsupported datatype in %s: %s\", __func__, format_type_be(type));\n\t\t\tpg_unreachable();\n\t}\n}\n\nstatic int64\nconst_to_integral_timeval(const Const *cnst)\n{\n\tAssert(!cnst->constisnull);\n\n\tswitch (cnst->consttype)\n\t{\n\t\tcase INT2OID:\n\t\t\treturn (int64) (DatumGetInt16(cnst->constvalue));\n\t\tcase INT4OID:\n\t\t\treturn (int64) (DatumGetInt32(cnst->constvalue));\n\t\tcase INT8OID:\n\t\t\treturn DatumGetInt64(cnst->constvalue);\n\t\tcase DATEOID:\n\t\t\treturn DatumGetDateADT(cnst->constvalue);\n\t\tcase TIMESTAMPOID:\n\t\t\treturn DatumGetTimestamp(cnst->constvalue);\n\t\tcase TIMESTAMPTZOID:\n\t\t\treturn DatumGetTimestampTz(cnst->constvalue);\n\t\tcase UUIDOID:\n\t\t{\n\t\t\t/*\n\t\t\t * While it is possible to extract the timestamp from a UUID, there is currently no use\n\t\t\t * case where this function is used since the UUID-based time_bucket() function returns\n\t\t\t * a timestamptz. Thus, any value compared to is also a timestamptz and not a UUID.\n\t\t\t * */\n\t\t\tTS_FALLTHROUGH;\n\t\t}\n\t\tdefault:\n\t\t\telog(ERROR,\n\t\t\t\t \"unsupported datatype in %s: %s\",\n\t\t\t\t __func__,\n\t\t\t\t format_type_be(cnst->consttype));\n\t\t\tpg_unreachable();\n\t}\n}\n\n/*\n * Constify expressions of the following form in WHERE clause:\n *\n * column OP timestamptz - interval\n * column OP timestamptz + interval\n * column OP interval + timestamptz\n *\n * Iff interval has no month component.\n *\n * Since the operators for timestamptz OP interval are marked\n * as stable they will not be constified during planning.\n * However, intervals without a month component can be safely\n * constified during planning as the result of those calculations\n * do not depend on the timezone setting.\n */\nstatic OpExpr *\nconstify_timestamptz_op_interval(PlannerInfo *root, OpExpr *constraint)\n{\n\tExpr *left, *right;\n\tOpExpr *op;\n\tbool var_on_left = false;\n\tInterval *interval;\n\tConst *c_ts, *c_int;\n\tDatum constified;\n\tPGFunction opfunc;\n\tOid ts_pl_int, ts_mi_int, int_pl_ts;\n\n\t/* checked in caller already so only asserting */\n\tAssert(constraint->args->length == 2);\n\n\tleft = linitial(constraint->args);\n\tright = llast(constraint->args);\n\n\tif (IsA(left, Var) && IsA(right, OpExpr))\n\t{\n\t\top = castNode(OpExpr, right);\n\t\tvar_on_left = true;\n\t}\n\telse if (IsA(left, OpExpr) && IsA(right, Var))\n\t{\n\t\top = castNode(OpExpr, left);\n\t}\n\telse\n\t\treturn constraint;\n\n\tts_pl_int = ts_get_operator(\"+\", PG_CATALOG_NAMESPACE, TIMESTAMPTZOID, INTERVALOID);\n\tts_mi_int = ts_get_operator(\"-\", PG_CATALOG_NAMESPACE, TIMESTAMPTZOID, INTERVALOID);\n\tint_pl_ts = ts_get_operator(\"+\", PG_CATALOG_NAMESPACE, INTERVALOID, TIMESTAMPTZOID);\n\n\tif (op->opno == ts_pl_int)\n\t{\n\t\t/* TIMESTAMPTZ + INTERVAL */\n\t\topfunc = timestamptz_pl_interval;\n\t\tc_ts = linitial_node(Const, op->args);\n\t\tc_int = llast_node(Const, op->args);\n\t}\n\telse if (op->opno == ts_mi_int)\n\t{\n\t\t/* TIMESTAMPTZ - INTERVAL */\n\t\topfunc = timestamptz_mi_interval;\n\t\tc_ts = linitial_node(Const, op->args);\n\t\tc_int = llast_node(Const, op->args);\n\t}\n\telse if (op->opno == int_pl_ts)\n\t{\n\t\t/* INTERVAL + TIMESTAMPTZ */\n\t\topfunc = timestamptz_pl_interval;\n\t\tc_int = linitial_node(Const, op->args);\n\t\tc_ts = llast_node(Const, op->args);\n\t}\n\telse\n\t\treturn constraint;\n\n\t/*\n\t * arg types should match operator and were checked in precheck\n\t * so only asserting here\n\t */\n\tAssert(c_ts->consttype == TIMESTAMPTZOID);\n\tAssert(c_int->consttype == INTERVALOID);\n\tif (c_ts->constisnull || c_int->constisnull)\n\t\treturn constraint;\n\n\tinterval = DatumGetIntervalP(c_int->constvalue);\n\n\t/*\n\t * constification is only safe when the interval has no month component\n\t * because month length is variable and calculation depends on local timezone\n\t */\n\tif (interval->month != 0)\n\t\treturn constraint;\n\n\tconstified = DirectFunctionCall2(opfunc, c_ts->constvalue, c_int->constvalue);\n\n\t/*\n\t * Since constifying intervals with day component does depend on the timezone\n\t * this can lead to different results around daylight saving time switches.\n\t * So we add a safety buffer when the interval has day components to counteract.\n\t */\n\tif (interval->day != 0)\n\t{\n\t\tbool add;\n\t\tTimestampTz constified_tstz = DatumGetTimestampTz(constified);\n\n\t\tswitch (constraint->opfuncid)\n\t\t{\n\t\t\tcase F_TIMESTAMPTZ_LE:\n\t\t\tcase F_TIMESTAMPTZ_LT:\n\t\t\t\tadd = true;\n\t\t\t\tbreak;\n\t\t\tcase F_TIMESTAMPTZ_GE:\n\t\t\tcase F_TIMESTAMPTZ_GT:\n\t\t\t\tadd = false;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\treturn constraint;\n\t\t}\n\t\t/*\n\t\t * If Var is on wrong side reverse the direction.\n\t\t */\n\t\tif (!var_on_left)\n\t\t\tadd = !add;\n\n\t\t/*\n\t\t * The safety buffer is chosen to be 4 hours because daylight saving time\n\t\t * changes seem to be in the range between -1 and 2 hours.\n\t\t */\n\t\tif (add)\n\t\t\tconstified_tstz += 4 * USECS_PER_HOUR;\n\t\telse\n\t\t\tconstified_tstz -= 4 * USECS_PER_HOUR;\n\n\t\tconstified = TimestampTzGetDatum(constified_tstz);\n\t}\n\n\tc_ts = copyObject(c_ts);\n\tc_ts->constvalue = constified;\n\n\tif (var_on_left)\n\t\tright = (Expr *) c_ts;\n\telse\n\t\tleft = (Expr *) c_ts;\n\n\treturn (OpExpr *) make_opclause(constraint->opno,\n\t\t\t\t\t\t\t\t\tconstraint->opresulttype,\n\t\t\t\t\t\t\t\t\tconstraint->opretset,\n\t\t\t\t\t\t\t\t\tleft,\n\t\t\t\t\t\t\t\t\tright,\n\t\t\t\t\t\t\t\t\tconstraint->opcollid,\n\t\t\t\t\t\t\t\t\tconstraint->inputcollid);\n}\n\ntypedef struct TimeBucketInfo\n{\n\tOid rettype;\t/* Type of the return value */\n\tConst *width;\t/* Bucket width */\n\tNode *timeval;\t/* Bucket \"time\" value */\n\tOid timetype;\t/* Type of the time value */\n\tuint16 numargs; /* Number of bucket function arguments */\n} TimeBucketInfo;\n\n/*\n * Representation of a parse time bucket Qual:\n *\n *  <time_bucket() OP value>\n */\ntypedef struct TimeBucketQual\n{\n\tTimeBucketInfo tb;\n\tint strategy;\n\tConst *value;\n} TimeBucketQual;\n\n/*\n * Parse an expression of form <time_bucket(width, column) OP value> and extract the important\n * components into a TimeBucketQual struct.\n *\n * Returns false if the expression does not fit the expected format.\n */\nstatic bool\nextract_time_bucket_qual(Expr *node, TimeBucketQual *tbqual)\n{\n\tif (!IsA(node, OpExpr))\n\t\treturn false;\n\n\tOpExpr *op = castNode(OpExpr, node);\n\n\tif (list_length((op)->args) != 2)\n\t\treturn false;\n\n\tExpr *left = linitial((op)->args);\n\tExpr *right = lsecond((op)->args);\n\tFuncExpr *time_bucket;\n\n\tMemSet(tbqual, 0, sizeof(TimeBucketQual));\n\n\tOid opno = InvalidOid;\n\n\tif (IsA(left, FuncExpr) && IsA(right, Const))\n\t{\n\t\ttime_bucket = castNode(FuncExpr, left);\n\t\ttbqual->value = castNode(Const, right);\n\t\topno = op->opno;\n\t}\n\telse if (IsA(right, FuncExpr) && IsA(left, Const))\n\t{\n\t\ttime_bucket = castNode(FuncExpr, right);\n\t\ttbqual->value = castNode(Const, left);\n\t\topno = get_commutator(op->opno);\n\t}\n\telse\n\t{\n\t\treturn false;\n\t}\n\n\tif (!is_time_bucket_function((Expr *) time_bucket) || tbqual->value->constisnull)\n\t\treturn false;\n\n\tConst *width = linitial(time_bucket->args);\n\t/* Get the time/partitioning column argument */\n\tNode *timearg = lsecond(time_bucket->args);\n\n\tif (!IsA(width, Const) || width->constisnull)\n\t\treturn false;\n\n\ttbqual->tb.numargs = list_length(time_bucket->args);\n\ttbqual->tb.width = width;\n\ttbqual->tb.timeval = timearg;\n\ttbqual->tb.timetype = exprType(timearg);\n\ttbqual->tb.rettype = exprType((Node *) time_bucket);\n\n\t/* 3 or more args should have Const 3rd arg */\n\tif (list_length(time_bucket->args) > 2 && !IsA(lthird(time_bucket->args), Const))\n\t\treturn false;\n\n\t/* 5 args variants should have Const 4th and 5th arg */\n\tif (list_length(time_bucket->args) == 5 &&\n\t\t(!IsA(lfourth(time_bucket->args), Const) || !IsA(lfifth(time_bucket->args), Const)))\n\t\treturn false;\n\n\tAssert(list_length(time_bucket->args) == 2 || list_length(time_bucket->args) == 3 ||\n\t\t   list_length(time_bucket->args) == 5);\n\n\tTypeCacheEntry *tce = lookup_type_cache(tbqual->tb.rettype, TYPECACHE_BTREE_OPFAMILY);\n\ttbqual->strategy = get_op_opfamily_strategy(opno, tce->btree_opf);\n\n\treturn true;\n}\n\n/*\n * Convert at time_bucket() width argument (typically Interval or integer) to a microseconds\n * integer. Also check that the width (interval) doesn't overflow the time value.\n */\nstatic bool\ntime_bucket_width_to_integral(const Const *width, Oid bucket_type, int64 integral_value,\n\t\t\t\t\t\t\t  int64 *integral_width)\n{\n\tswitch (width->consttype)\n\t{\n\t\tcase INT2OID:\n\t\tcase INT4OID:\n\t\tcase INT8OID:\n\n\t\t\t/* We can support the offset variants of time_bucket as the\n\t\t\t * amount of shifting they do is never bigger than the bucketing\n\t\t\t * width.\n\t\t\t */\n\t\t\t*integral_width = const_to_integral_timeval(width);\n\n\t\t\tif (integral_value >= ts_time_get_max(bucket_type) - *integral_width)\n\t\t\t\treturn false;\n\t\t\tbreak;\n\t\tcase INTERVALOID:\n\t\t{\n\t\t\tInterval *interval = DatumGetIntervalP(width->constvalue);\n\t\t\t/*\n\t\t\t * Optimization can't be applied when interval has month component.\n\t\t\t */\n\t\t\tif (interval->month != 0)\n\t\t\t\treturn false;\n\n\t\t\tif (bucket_type == DATEOID)\n\t\t\t{\n\t\t\t\t/* bail out if interval->time can't be exactly represented as a double */\n\t\t\t\tif (interval->time >= 0x3FFFFFFFFFFFFFLL)\n\t\t\t\t\treturn false;\n\n\t\t\t\t*integral_width =\n\t\t\t\t\tinterval->day + ceil((double) interval->time / (double) USECS_PER_DAY);\n\n\t\t\t\tif (integral_value >= (TS_DATE_END - *integral_width))\n\t\t\t\t\treturn false;\n\t\t\t}\n\t\t\telse if (bucket_type == TIMESTAMPOID || bucket_type == TIMESTAMPTZOID)\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * If width interval has day component we merge it with time component\n\t\t\t\t */\n\t\t\t\t*integral_width = interval->time;\n\n\t\t\t\tif (interval->day != 0)\n\t\t\t\t{\n\t\t\t\t\t/*\n\t\t\t\t\t * if our transformed restriction would overflow we skip adding it\n\t\t\t\t\t */\n\t\t\t\t\tif (interval->time >= TS_TIMESTAMP_END - interval->day * USECS_PER_DAY)\n\t\t\t\t\t\treturn false;\n\n\t\t\t\t\t*integral_width += interval->day * USECS_PER_DAY;\n\t\t\t\t}\n\n\t\t\t\tif (integral_value >= (TS_TIMESTAMP_END - *integral_width))\n\t\t\t\t\treturn false;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\treturn false;\n\t}\n\treturn true;\n}\n\n/*\n * Transform time_bucket calls of the following form in WHERE clause:\n *\n * time_bucket(width, column) OP value\n *\n * Since time_bucket always returns the lower bound of the bucket\n * for lower bound comparisons the width is not relevant and the\n * following transformation can be applied:\n *\n * time_bucket(width, column) > value\n * column > value\n *\n * Example with values:\n *\n * time_bucket(10, column) > 109\n * column > 109\n *\n * For upper bound comparisons width needs to be taken into account\n * and we need to extend the upper bound by width to capture all\n * possible values.\n *\n * time_bucket(width, column) < value\n * column < value + width\n *\n * Example with values:\n *\n * time_bucket(10, column) < 100\n * column < 100 + 10\n *\n * Expressions with value on the left side will be switched around\n * when building the expression for RestrictInfo.\n *\n * If the transformation cannot be applied, returns NULL.\n */\nExpr *\nts_transform_time_bucket_comparison(Expr *node)\n{\n\tTimeBucketQual tbqual;\n\n\tif (!extract_time_bucket_qual(node, &tbqual))\n\t\treturn NULL;\n\n\t/*\n\t * The qual is an expression <time_bucket OP value> or <value OP time_bucket>. Convert the value\n\t * to integral time format.\n\t */\n\tint64 integral_value = const_to_integral_timeval(tbqual.value);\n\tConst *newvalue = NULL;\n\n\t/*\n\t * We strip the time_bucket() from the expression, leaving the input \"time\" argument. Depending\n\t * on the comparison OP, the value might need adjustment. Then the value is converted to the\n\t * input/column type for time_bucket(). In most cases, the value's original type and the bucket\n\t * input type is the same (e.g. TIMESTAMPTZ), but in some cases they differ. For example, it is\n\t * possible to compare an int8 bucket function with an int4 value. In the case of UUID bucket,\n\t * the bucket function's input type (UUID) is different from the output type (TIMESTAMPTZ), so\n\t * the timestamp value needs to be converted to a boundary UUID.\n\t */\n\tswitch (tbqual.strategy)\n\t{\n\t\tcase BTGreaterStrategyNumber:\n\t\tcase BTGreaterEqualStrategyNumber:\n\t\t\t/*\n\t\t\t * Since time_bucket will always shift the input to the left this\n\t\t\t * transformation is always safe even in the presence of offset variants.\n\t\t\t *\n\t\t\t * Handle expressions of form:\n\t\t\t *\n\t\t\t *  - column > value\n\t\t\t *  - column >= value\n\t\t\t */\n\t\t\tnewvalue = integral_timeval_to_const(integral_value, tbqual.tb.timetype);\n\t\t\tbreak;\n\t\tcase BTLessStrategyNumber:\n\t\tcase BTLessEqualStrategyNumber:\n\t\t{\n\t\t\t/*\n\t\t\t * Handle expressions of form:\n\t\t\t *\n\t\t\t *  - column < value + width\n\t\t\t *  - column <= value + width\n\t\t\t *  */\n\t\t\tint64 integral_width = 0;\n\n\t\t\tif (!time_bucket_width_to_integral(tbqual.tb.width,\n\t\t\t\t\t\t\t\t\t\t\t   tbqual.tb.rettype,\n\t\t\t\t\t\t\t\t\t\t\t   integral_value,\n\t\t\t\t\t\t\t\t\t\t\t   &integral_width))\n\t\t\t\treturn NULL;\n\n\t\t\tif (tbqual.strategy == BTLessStrategyNumber && tbqual.tb.numargs == 2 &&\n\t\t\t\tintegral_value % integral_width == 0)\n\t\t\t\tnewvalue = integral_timeval_to_const(integral_value, tbqual.tb.timetype);\n\t\t\telse\n\t\t\t\tnewvalue =\n\t\t\t\t\tintegral_timeval_to_const(integral_value + integral_width, tbqual.tb.timetype);\n\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\treturn NULL;\n\t}\n\n\tAssert(newvalue != NULL);\n\n\t/* Create a new \"unwrapped\" OpExpr using the time_bucket() input/column type */\n\tTypeCacheEntry *tce = lookup_type_cache(tbqual.tb.timetype, TYPECACHE_BTREE_OPFAMILY);\n\tOid opno = get_opfamily_member(tce->btree_opf,\n\t\t\t\t\t\t\t\t   tce->btree_opintype,\n\t\t\t\t\t\t\t\t   tce->btree_opintype,\n\t\t\t\t\t\t\t\t   tbqual.strategy);\n\n\tOpExpr *op = (OpExpr *) copyObject(node);\n\top->args = list_make2(tbqual.tb.timeval, newvalue);\n\top->opno = opno;\n\n\t/* The operator might have changed, so reset the function ID */\n\top->opfuncid = InvalidOid;\n\n\treturn &op->xpr;\n}\n\n/*\n * Since baserestrictinfo is not yet set by the planner, we have to derive\n * it ourselves. It's safe for us to miss some restrict info clauses (this\n * will just result in more chunks being included) so this does not need\n * to be as comprehensive as the PG native derivation. This is inspired\n * by the derivation in `deconstruct_recurse` in PG\n *\n * TODO: as of 2025, the baserestrictinfo and joininfo is already set when the\n * hypertable expansion code is called, so this does duplicate work. If any bugs\n * are found in this code, it should be switched to use the RelOptInfos and\n * equivalence classes instead of the parse tree. The chunk exclusion code for\n * the non-join clauses was already changed to use the former.\n */\nstatic Node *\nprocess_quals(Node *quals, CollectQualCtx *ctx, bool is_outer_join)\n{\n\tListCell *lc;\n\n\tListCell *prev pg_attribute_unused() = NULL;\n\tList *additional_quals = NIL;\n\n\tfor (lc = list_head((List *) quals); lc != NULL; prev = lc, lc = lnext((List *) quals, lc))\n\t{\n\t\tExpr *qual = lfirst(lc);\n\t\tRelids relids = pull_varnos(ctx->root, (Node *) qual);\n\t\tint num_rels = bms_num_members(relids);\n\n\t\t/* stop processing if not for current rel */\n\t\tif (num_rels != 1 || !bms_is_member(ctx->rel->relid, relids))\n\t\t\tcontinue;\n\n\t\tif (IsA(qual, OpExpr) && list_length(castNode(OpExpr, qual)->args) == 2)\n\t\t{\n\t\t\tOpExpr *op = castNode(OpExpr, qual);\n\t\t\tExpr *left = linitial(op->args);\n\t\t\tExpr *right = lsecond(op->args);\n\n\t\t\tif ((IsA(left, Var) && is_timestamptz_op_interval(right)) ||\n\t\t\t\t(IsA(right, Var) && is_timestamptz_op_interval(left)))\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * check for constraints with TIMESTAMPTZ OP INTERVAL calculations\n\t\t\t\t */\n\t\t\t\tqual = (Expr *) constify_timestamptz_op_interval(ctx->root, op);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * check for time_bucket comparisons\n\t\t\t\t * time_bucket(Const, time_colum) > Const\n\t\t\t\t */\n\t\t\t\tExpr *transformed = ts_transform_time_bucket_comparison(qual);\n\t\t\t\tif (transformed != NULL)\n\t\t\t\t{\n\t\t\t\t\t/*\n\t\t\t\t\t * if we could transform the expression we add it to the list of\n\t\t\t\t\t * quals so it can be used as an index condition\n\t\t\t\t\t */\n\t\t\t\t\tadditional_quals = lappend(additional_quals, transformed);\n\n\t\t\t\t\t/*\n\t\t\t\t\t * Also use the transformed qual for chunk exclusion.\n\t\t\t\t\t */\n\t\t\t\t\tqual = transformed;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t/* Do not include this restriction if this is an outer join. Including\n\t\t * the restriction would exclude chunks and thus rows of the outer\n\t\t * relation when it should show all rows */\n\t\tif (!is_outer_join)\n\t\t\tctx->restrictions =\n\t\t\t\tlappend(ctx->restrictions, make_simple_restrictinfo(ctx->root, qual));\n\t}\n\treturn (Node *) list_concat((List *) quals, additional_quals);\n}\n\nstatic Node *\ntimebucket_annotate(Node *quals, CollectQualCtx *ctx)\n{\n\tListCell *lc;\n\tList *additional_quals = NIL;\n\n\tforeach (lc, castNode(List, quals))\n\t{\n\t\tExpr *qual = lfirst(lc);\n\t\tRelids relids = pull_varnos(ctx->root, (Node *) qual);\n\t\tint num_rels = bms_num_members(relids);\n\n\t\t/* stop processing if not for current rel */\n\t\tif (num_rels != 1 || !bms_is_member(ctx->rel->relid, relids))\n\t\t\tcontinue;\n\n\t\t/*\n\t\t * check for time_bucket comparisons\n\t\t * time_bucket(Const, time_colum) > Const\n\t\t */\n\t\tExpr *transformed = ts_transform_time_bucket_comparison(qual);\n\t\tif (transformed != NULL)\n\t\t{\n\t\t\t/*\n\t\t\t * if we could transform the expression we add it to the list of\n\t\t\t * quals so it can be used as an index condition\n\t\t\t */\n\t\t\tadditional_quals = lappend(additional_quals, transformed);\n\n\t\t\t/*\n\t\t\t * Also use the transformed qual for chunk exclusion.\n\t\t\t */\n\t\t\tqual = transformed;\n\t\t}\n\n\t\tctx->restrictions = lappend(ctx->restrictions, make_simple_restrictinfo(ctx->root, qual));\n\t}\n\treturn (Node *) list_concat((List *) quals, additional_quals);\n}\n\n/*\n * collect JOIN information\n *\n * This function adds information to the CollectQualCtx\n *\n * propagate_conditions\n *\n * This list contains toplevel or INNER JOIN equality conditions.\n * This list is used for propagating quals to the other side of\n * a JOIN.\n */\nstatic void\ncollect_join_quals(Node *quals, CollectQualCtx *ctx, bool can_propagate)\n{\n\tListCell *lc;\n\n\tforeach (lc, (List *) quals)\n\t{\n\t\tExpr *qual = lfirst(lc);\n\t\tRelids relids = pull_varnos(ctx->root, (Node *) qual);\n\t\tint num_rels = bms_num_members(relids);\n\n\t\t/*\n\t\t * collect quals to propagate to join relations\n\t\t */\n\t\tif (num_rels == 1 && can_propagate && IsA(qual, OpExpr) &&\n\t\t\tlist_length(castNode(OpExpr, qual)->args) == 2)\n\t\t\tctx->all_quals = lappend(ctx->all_quals, qual);\n\n\t\tif (!bms_is_member(ctx->rel->relid, relids))\n\t\t\tcontinue;\n\n\t\t/* collect equality JOIN conditions for current rel */\n\t\tif (num_rels == 2 && IsA(qual, OpExpr) && list_length(castNode(OpExpr, qual)->args) == 2)\n\t\t{\n\t\t\tOpExpr *op = castNode(OpExpr, qual);\n\t\t\tExpr *left = linitial(op->args);\n\t\t\tExpr *right = lsecond(op->args);\n\n\t\t\tif (IsA(left, Var) && IsA(right, Var))\n\t\t\t{\n\t\t\t\tVar *ht_var =\n\t\t\t\t\tcastNode(Var,\n\t\t\t\t\t\t\t (Index) castNode(Var, left)->varno == ctx->rel->relid ? left : right);\n\t\t\t\tTypeCacheEntry *tce = lookup_type_cache(ht_var->vartype, TYPECACHE_EQ_OPR);\n\n\t\t\t\tif (op->opno == tce->eq_opr)\n\t\t\t\t{\n\t\t\t\t\tif (can_propagate)\n\t\t\t\t\t\tctx->propagate_conditions = lappend(ctx->propagate_conditions, op);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t}\n}\n\nstatic bool\ncollect_quals_walker(Node *node, CollectQualCtx *ctx)\n{\n\tif (node == NULL)\n\t\treturn false;\n\n\tif (IsA(node, FromExpr))\n\t{\n\t\tFromExpr *f = castNode(FromExpr, node);\n\t\tf->quals = process_quals(f->quals, ctx, false);\n\t\t/* if this is a nested join we don't propagate join quals */\n\t\tcollect_join_quals(f->quals, ctx, ctx->join_level == 0);\n\t}\n\telse if (IsA(node, JoinExpr))\n\t{\n\t\tJoinExpr *j = castNode(JoinExpr, node);\n\t\tj->quals = process_quals(j->quals, ctx, IS_OUTER_JOIN(j->jointype));\n\t\tcollect_join_quals(j->quals, ctx, ctx->join_level == 0 && !IS_OUTER_JOIN(j->jointype));\n\n\t\tif (IS_OUTER_JOIN(j->jointype))\n\t\t{\n\t\t\tctx->join_level++;\n\t\t\tbool result = expression_tree_walker(node, collect_quals_walker, ctx);\n\t\t\tctx->join_level--;\n\t\t\treturn result;\n\t\t}\n\t}\n\n\treturn expression_tree_walker(node, collect_quals_walker, ctx);\n}\n\nstatic int\nchunk_cmp_chunk_reloid(const void *c1, const void *c2)\n{\n\tOid lhs = (*(Chunk **) c1)->table_id;\n\tOid rhs = (*(Chunk **) c2)->table_id;\n\n\tif (lhs < rhs)\n\t\treturn -1;\n\tif (lhs > rhs)\n\t\treturn 1;\n\treturn 0;\n}\n\nstatic Chunk **\nfind_children_chunks(HypertableRestrictInfo *hri, Hypertable *ht, bool include_osm,\n\t\t\t\t\t unsigned int *num_chunks)\n{\n\t/*\n\t * Unlike find_all_inheritors we do not include parent because if there\n\t * are restrictions the parent table cannot fulfill them and since we do\n\t * have a trigger blocking inserts on the parent table it cannot contain\n\t * any rows.\n\t */\n\tChunk **chunks = ts_hypertable_restrict_info_get_chunks(hri, ht, include_osm, num_chunks);\n\n\t/*\n\t * Sort the chunks by oid ascending to roughly match the order provided\n\t * by find_inheritance_children. This is mostly needed to avoid test\n\t * reference changes.\n\t */\n\tqsort((void *) chunks, *num_chunks, sizeof(Chunk *), chunk_cmp_chunk_reloid);\n\n\treturn chunks;\n}\n\nstatic bool\nshould_order_append(PlannerInfo *root, RelOptInfo *rel, Hypertable *ht, int *order_attno,\n\t\t\t\t\tbool *reverse)\n{\n\t/* check if optimizations are enabled */\n\tif (!ts_guc_enable_optimizations || !ts_guc_enable_ordered_append ||\n\t\t!ts_guc_enable_chunk_append)\n\t\treturn false;\n\n\t/*\n\t * only do this optimization for hypertables with 1 dimension and queries\n\t * with an ORDER BY clause\n\t */\n\tif (root->parse->sortClause == NIL)\n\t\treturn false;\n\n\treturn ts_ordered_append_should_optimize(root, rel, ht, order_attno, reverse);\n}\n\n/*\n * Some time conditions are not directly applicable for the chunk exclusion, but\n * imply a simpler time comparison condition which can be used for hypertable\n * expansion. Return a list of any simplified restrictions we could build for\n * the restrictions in the given list.\n */\nstatic List *\nget_simplified_restrictions(PlannerInfo *root, List *restrictions)\n{\n\tList *simplified_restrictions = NIL;\n\tListCell *lc;\n\tforeach (lc, restrictions)\n\t{\n\t\tRestrictInfo *ri = castNode(RestrictInfo, lfirst(lc));\n\t\tExpr *qual = ri->clause;\n\t\tif (IsA(qual, OpExpr) && list_length(castNode(OpExpr, qual)->args) == 2)\n\t\t{\n\t\t\tOpExpr *op = castNode(OpExpr, qual);\n\t\t\tExpr *left = linitial(op->args);\n\t\t\tExpr *right = lsecond(op->args);\n\n\t\t\tif ((IsA(left, Var) && is_timestamptz_op_interval(right)) ||\n\t\t\t\t(IsA(right, Var) && is_timestamptz_op_interval(left)))\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * Check for constraints with TIMESTAMPTZ OP INTERVAL calculations.\n\t\t\t\t */\n\t\t\t\tExpr *transformed = (Expr *) constify_timestamptz_op_interval(root, op);\n\t\t\t\tif (transformed != (Expr *) op)\n\t\t\t\t{\n\t\t\t\t\tRestrictInfo *ri_copy = copyObject(ri);\n\t\t\t\t\tri_copy->clause = transformed;\n\t\t\t\t\tsimplified_restrictions = lappend(simplified_restrictions, ri_copy);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * check for time_bucket comparisons\n\t\t\t\t * time_bucket(Const, time_colum) > Const\n\t\t\t\t */\n\t\t\t\tExpr *transformed = ts_transform_time_bucket_comparison(qual);\n\t\t\t\tif (transformed != NULL)\n\t\t\t\t{\n\t\t\t\t\t/*\n\t\t\t\t\t * Also use the transformed qual for chunk exclusion.\n\t\t\t\t\t */\n\t\t\t\t\tRestrictInfo *ri_copy = copyObject(ri);\n\t\t\t\t\tri_copy->clause = transformed;\n\t\t\t\t\tsimplified_restrictions = lappend(simplified_restrictions, ri_copy);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn simplified_restrictions;\n}\n\n/**\n * Get chunks from restrict info.\n *\n * If appends are returned in order appends_ordered on rel->fdw_private is set to true.\n * To make verifying pathkeys easier in set_rel_pathlist the hypertable attno of the column\n * ordered by is stored in rel->fdw_private.\n * If the hypertable uses space partitioning the nested oids are stored in nested_oids\n * on rel->fdw_private when appends are ordered.\n */\nstatic Chunk **\nget_chunks(PlannerInfo *root, RelOptInfo *rel, Hypertable *ht, bool include_osm,\n\t\t   unsigned int *num_chunks, HypertableRestrictInfo **hri_out)\n{\n\tbool reverse;\n\tint order_attno;\n\n\tHypertableRestrictInfo *hri = ts_hypertable_restrict_info_create(rel, ht);\n\n\t/*\n\t * This is where the magic happens: use our HypertableRestrictInfo\n\t * infrastructure to deduce the appropriate chunks using our range\n\t * exclusion.\n\t */\n\tts_hypertable_restrict_info_add(hri, root, rel->baserestrictinfo);\n\n\tList *simplified_restrictions = get_simplified_restrictions(root, rel->baserestrictinfo);\n\tts_hypertable_restrict_info_add(hri, root, simplified_restrictions);\n\n\t/* Limit to hypertables without multiple dimensions for now */\n\tif (hri->num_base_restrictions >= 1 && hri->num_dimensions == 1 &&\n\t\tht->space->num_dimensions == 1)\n\t{\n\t\t*hri_out = hri;\n\t}\n\n\t/*\n\t * If fdw_private has not been setup by caller there is no point checking\n\t * for ordered append as we can't pass the required metadata in fdw_private\n\t * to signal that this is safe to transform in ordered append plan in\n\t * set_rel_pathlist.\n\t */\n\tif (rel->fdw_private != NULL && should_order_append(root, rel, ht, &order_attno, &reverse))\n\t{\n\t\tTimescaleDBPrivate *priv = ts_get_private_reloptinfo(rel);\n\t\tList **nested_oids = NULL;\n\n\t\tpriv->appends_ordered = true;\n\t\tpriv->order_attno = order_attno;\n\n\t\t/*\n\t\t * for space partitioning we need extra information about the\n\t\t * time slices of the chunks\n\t\t */\n\t\tif (ht->space->num_dimensions > 1)\n\t\t\tnested_oids = &priv->nested_oids;\n\n\t\treturn ts_hypertable_restrict_info_get_chunks_ordered(hri,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  ht,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  include_osm,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  reverse,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  nested_oids,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  num_chunks);\n\t}\n\n\treturn find_children_chunks(hri, ht, include_osm, num_chunks);\n}\n\nstatic bool\ntimebucket_annotate_walker(Node *node, CollectQualCtx *ctx)\n{\n\tif (node == NULL)\n\t\treturn false;\n\n\tif (IsA(node, FromExpr))\n\t{\n\t\tFromExpr *f = castNode(FromExpr, node);\n\t\tf->quals = timebucket_annotate(f->quals, ctx);\n\t}\n\telse if (IsA(node, JoinExpr))\n\t{\n\t\tJoinExpr *j = castNode(JoinExpr, node);\n\t\tj->quals = timebucket_annotate(j->quals, ctx);\n\t}\n\n\treturn expression_tree_walker(node, timebucket_annotate_walker, ctx);\n}\n\nvoid\nts_plan_expand_timebucket_annotate(PlannerInfo *root, RelOptInfo *rel)\n{\n\tCollectQualCtx ctx = {\n\t\t.root = root,\n\t\t.rel = rel,\n\t\t.restrictions = NIL,\n\t\t.all_quals = NIL,\n\t\t.propagate_conditions = NIL,\n\t};\n\n\t/* Walk the tree and find restrictions or chunk exclusion functions */\n\ttimebucket_annotate_walker((Node *) root->parse->jointree, &ctx);\n\n\tif (ctx.propagate_conditions != NIL)\n\t\tpropagate_join_quals(root, rel, &ctx);\n}\n\n/*\n * Build a list of baserestrictinfo with any Var OP Const constraints on the primary\n * dimension removed.\n */\nstatic List *\nfilter_baserestrictions(Hypertable *ht, List *base_restrictions)\n{\n\tAttrNumber dim_attno = ht->space->dimensions[0].column_attno;\n\tList *filtered_restrictions = NIL;\n\tListCell *lc;\n\tforeach (lc, base_restrictions)\n\t{\n\t\tRestrictInfo *ri = castNode(RestrictInfo, lfirst(lc));\n\t\tExpr *qual = ri->clause;\n\t\tif (IsA(qual, OpExpr))\n\t\t{\n\t\t\tOpExpr *op = castNode(OpExpr, qual);\n\t\t\tNode *left = strip_implicit_coercions(linitial(op->args));\n\t\t\tNode *right = strip_implicit_coercions(lsecond(op->args));\n\t\t\tif ((IsA(left, Var) && IsA(right, Const) &&\n\t\t\t\t castNode(Var, left)->varattno == dim_attno) ||\n\t\t\t\t(IsA(right, Var) && IsA(left, Const) &&\n\t\t\t\t castNode(Var, right)->varattno == dim_attno))\n\t\t\t{\n\t\t\t\t/* only consider simple column to constant comparisons */\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tfiltered_restrictions = lappend(filtered_restrictions, ri);\n\t}\n\treturn filtered_restrictions;\n}\n\n/*\n * Returns true if the given chunk is fully included by the restrictions\n * on the primary dimension.\n */\nstatic bool\nchunk_fully_covered(HypertableRestrictInfo *hri, Chunk *chunk)\n{\n\tDimensionRestrictInfoOpen *dri = (DimensionRestrictInfoOpen *) hri->dimension_restriction[0];\n\tEnsure(dri->base.dimension->type == DIMENSION_TYPE_OPEN, \"primary dimension must be open\");\n\tEnsure(hri->num_base_restrictions > 0, \"must have base restrictions\");\n\n\tif (IS_OSM_CHUNK(chunk) ||\n\t\t(dri->lower_strategy == InvalidStrategy && dri->upper_strategy == InvalidStrategy) ||\n\t\t(chunk->cube->slices[0]->fd.range_start == TS_TIME_NOBEGIN ||\n\t\t chunk->cube->slices[0]->fd.range_end == TS_TIME_NOEND))\n\t\treturn false;\n\n\t/*\n\t * DimensionRetrictInfo strategy should only be one BTGreaterStrategyNumber\n\t * or BTGreaterEqualStrategyNumber on the lower boundary and\n\t * BTLessStrategyNumber or BTLessEqualStrategyNumber on the upper boundary.\n\t *\n\t * BTEqualStrategyNumber gets changed to BTGreaterEqualStrategyNumber\n\t * on lower boundary and BTLessEqualStrategyNumber on upper boundary.\n\t */\n\tif (dri->lower_strategy != InvalidStrategy)\n\t{\n\t\tswitch (dri->lower_strategy)\n\t\t{\n\t\t\tcase BTGreaterStrategyNumber:\n\t\t\t\tif (chunk->cube->slices[0]->fd.range_start <= dri->lower_bound)\n\t\t\t\t\treturn false;\n\t\t\t\tbreak;\n\t\t\tcase BTGreaterEqualStrategyNumber:\n\t\t\t\tif (chunk->cube->slices[0]->fd.range_start < dri->lower_bound)\n\t\t\t\t\treturn false;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\t/* Should never happen */\n\t\t\t\telog(ERROR, \"unexpected dimension restrictinfo strategy: %d\", dri->upper_strategy);\n\t\t}\n\t}\n\tif (dri->upper_strategy != InvalidStrategy)\n\t{\n\t\tswitch (dri->upper_strategy)\n\t\t{\n\t\t\tcase BTLessStrategyNumber:\n\t\t\t\tif (chunk->cube->slices[0]->fd.range_end > dri->upper_bound)\n\t\t\t\t\treturn false;\n\t\t\t\tbreak;\n\t\t\tcase BTLessEqualStrategyNumber:\n\t\t\t\tif (chunk->cube->slices[0]->fd.range_end - 1 > dri->upper_bound)\n\t\t\t\t\treturn false;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\t/* Should never happen */\n\t\t\t\telog(ERROR, \"unexpected dimension restrictinfo strategy: %d\", dri->upper_strategy);\n\t\t}\n\t}\n\treturn true;\n}\n\n/* Inspired by expand_inherited_rtentry but expands\n * a hypertable chunks into an append relation. */\nvoid\nts_plan_expand_hypertable_chunks(Hypertable *ht, PlannerInfo *root, RelOptInfo *rel,\n\t\t\t\t\t\t\t\t bool include_osm)\n{\n\tRangeTblEntry *rte = rt_fetch(rel->relid, root->parse->rtable);\n\tOid parent_oid = rte->relid;\n\tRelation oldrelation;\n\tQuery *parse = root->parse;\n\tIndex rti = rel->relid;\n\tList *appinfos = NIL;\n\tCollectQualCtx ctx = {\n\t\t.root = root,\n\t\t.rel = rel,\n\t\t.restrictions = NIL,\n\t\t.all_quals = NIL,\n\t\t.propagate_conditions = NIL,\n\t\t.join_level = 0,\n\t};\n\tIndex first_chunk_index = 0;\n\n\t/* double check our permissions are valid */\n\tAssert(rti != (Index) parse->resultRelation);\n\n\t/* Walk the tree and find restrictions */\n\tcollect_quals_walker((Node *) root->parse->jointree, &ctx);\n\t/* check join_level bookkeeping is balanced */\n\tAssert(ctx.join_level == 0);\n\n\tif (ctx.propagate_conditions != NIL)\n\t\tpropagate_join_quals(root, rel, &ctx);\n\n\tChunk **chunks = NULL;\n\tunsigned int num_chunks = 0;\n\n\tHypertableRestrictInfo *hri = NULL;\n\tchunks = get_chunks(root, rel, ht, include_osm, &num_chunks, &hri);\n\t/* Can have zero chunks. */\n\tAssert(num_chunks == 0 || chunks != NULL);\n\n\t/* nothing to do here if we have no chunks */\n\tif (!num_chunks)\n\t\treturn;\n\n\t/*\n\t * Handle PlanRowMark for FOR UPDATE/SHARE and FK constraint enforcement.\n\t * This replicates expand_inherited_rtentry() in inherit.c.\n\t */\n\tPlanRowMark *oldrc = get_plan_rowmark(root->rowMarks, rti);\n\tbool old_isParent = false;\n\tint old_allMarkTypes = 0;\n\tif (oldrc)\n\t{\n\t\told_isParent = oldrc->isParent;\n\t\toldrc->isParent = true;\n\t\told_allMarkTypes = oldrc->allMarkTypes;\n\t}\n\n\tfor (unsigned int i = 0; i < num_chunks; i++)\n\t{\n\t\t/*\n\t\t * Add the information about chunks to the baserel info cache for\n\t\t * classify_relation().\n\t\t */\n\t\tts_add_baserel_cache_entry_for_chunk(chunks[i]->table_id, ht);\n\t}\n\n\toldrelation = table_open(parent_oid, NoLock);\n\n\t/*\n\t * the simple_*_array structures have already been set, we need to add the\n\t * children to them.\n\t */\n\texpand_planner_arrays(root, num_chunks);\n\n\tfor (unsigned int i = 0; i < num_chunks; i++)\n\t{\n\t\tChunk *chunk = chunks[i];\n\t\tOid child_oid = chunk->table_id;\n\t\tRelation newrelation;\n\t\tRangeTblEntry *childrte;\n\t\tIndex child_rtindex;\n\t\tAppendRelInfo *appinfo;\n\t\tLOCKMODE chunk_lock = rte->rellockmode;\n\n\t\t/* Open rel if needed */\n\n\t\tAssert(child_oid != parent_oid);\n\t\tnewrelation = table_open(child_oid, chunk_lock);\n\n\t\t/* chunks cannot be temp tables */\n\t\tAssert(!RELATION_IS_OTHER_TEMP(newrelation));\n\n\t\t/*\n\t\t * Build an RTE for the child, and attach to query's rangetable list.\n\t\t * We copy most fields of the parent's RTE, but replace relation OID\n\t\t * and relkind, and set inh = false.  Also, set requiredPerms to zero\n\t\t * since all required permissions checks are done on the original RTE.\n\t\t * Likewise, set the child's securityQuals to empty, because we only\n\t\t * want to apply the parent's RLS conditions regardless of what RLS\n\t\t * properties individual children may have.  (This is an intentional\n\t\t * choice to make inherited RLS work like regular permissions checks.)\n\t\t * The parent securityQuals will be propagated to children along with\n\t\t * other base restriction clauses, so we don't need to do it here.\n\t\t */\n\t\tchildrte = copyObject(rte);\n\t\tchildrte->relid = child_oid;\n\t\tchildrte->relkind = newrelation->rd_rel->relkind;\n\t\tchildrte->inh = false;\n\t\t/* clear the magic bit */\n\t\tchildrte->ctename = NULL;\n#if PG16_LT\n\t\tchildrte->requiredPerms = 0;\n#else\n\t\t/* Since PG16, the permission info is maintained separately. Unlink\n\t\t * the old perminfo from the RTE to disable permission checking.\n\t\t */\n\t\tchildrte->perminfoindex = 0;\n#endif\n\t\tchildrte->securityQuals = NIL;\n\t\tparse->rtable = lappend(parse->rtable, childrte);\n\t\tchild_rtindex = list_length(parse->rtable);\n\t\tif (first_chunk_index == 0)\n\t\t\tfirst_chunk_index = child_rtindex;\n\t\troot->simple_rte_array[child_rtindex] = childrte;\n\t\tAssert(root->simple_rel_array[child_rtindex] == NULL);\n\n\t\tappinfo = makeNode(AppendRelInfo);\n\t\tappinfo->parent_relid = rti;\n\t\tappinfo->child_relid = child_rtindex;\n\t\tappinfo->parent_reltype = oldrelation->rd_rel->reltype;\n\t\tappinfo->child_reltype = newrelation->rd_rel->reltype;\n\t\tts_make_inh_translation_list(oldrelation, newrelation, child_rtindex, appinfo);\n\t\tappinfo->parent_reloid = parent_oid;\n\t\tappinfos = lappend(appinfos, appinfo);\n\n\t\t/*\n\t\t * Create child PlanRowMark if parent has one. This replicates\n\t\t * expand_single_inheritance_child() in inherit.c.\n\t\t */\n\t\tif (oldrc)\n\t\t{\n\t\t\tPlanRowMark *childrc = makeNode(PlanRowMark);\n\n\t\t\tchildrc->rti = child_rtindex;\n\t\t\tchildrc->prti = oldrc->rti;\n\t\t\tchildrc->rowmarkId = oldrc->rowmarkId;\n\t\t\tchildrc->markType = select_rowmark_type(childrte, oldrc->strength);\n\t\t\tchildrc->allMarkTypes = (1 << childrc->markType);\n\t\t\tchildrc->strength = oldrc->strength;\n\t\t\tchildrc->waitPolicy = oldrc->waitPolicy;\n\t\t\tchildrc->isParent = false; /* chunks are never partitioned */\n\n\t\t\toldrc->allMarkTypes |= childrc->allMarkTypes;\n\n\t\t\troot->rowMarks = lappend(root->rowMarks, childrc);\n\t\t}\n\n\t\t/* Close child relations, but keep locks */\n\t\tif (child_oid != parent_oid)\n\t\t\ttable_close(newrelation, NoLock);\n\t}\n\n\ttable_close(oldrelation, NoLock);\n\n\t/*\n\t * Add required junk columns for row marks. This replicates the logic\n\t * after the expansion loop in expand_inherited_rtentry() in inherit.c.\n\t */\n\tif (oldrc)\n\t{\n\t\tint new_allMarkTypes = oldrc->allMarkTypes;\n\t\tVar *var;\n\t\tTargetEntry *tle;\n\t\tchar resname[32];\n\t\tList *newvars = NIL;\n\n\t\t/*\n\t\t * TID junk var: only needed if parent had only ROW_MARK_COPY but children\n\t\t * added non-COPY marks. This can only happen if the parent is a foreign\n\t\t * table with regular table children. Since hypertable parents are always\n\t\t * regular tables, preprocess_targetlist() (preptlist.c) already adds TID\n\t\t * for the parent before expansion, so this path is unreachable.\n\t\t */\n\t\tEnsure(!(new_allMarkTypes & ~(1 << ROW_MARK_COPY) &&\n\t\t\t\t !(old_allMarkTypes & ~(1 << ROW_MARK_COPY))),\n\t\t\t   \"unexpected: TID junk var needed for hypertable (parent should always be regular \"\n\t\t\t   \"table)\");\n\n\t\t/* Add whole-row junk Var if needed, unless we had it already */\n\t\tif ((new_allMarkTypes & (1 << ROW_MARK_COPY)) && !(old_allMarkTypes & (1 << ROW_MARK_COPY)))\n\t\t{\n\t\t\tvar = makeWholeRowVar(planner_rt_fetch(oldrc->rti, root), oldrc->rti, 0, false);\n\t\t\tsnprintf(resname, sizeof(resname), \"wholerow%u\", oldrc->rowmarkId);\n\t\t\ttle = makeTargetEntry((Expr *) var,\n\t\t\t\t\t\t\t\t  list_length(root->processed_tlist) + 1,\n\t\t\t\t\t\t\t\t  pstrdup(resname),\n\t\t\t\t\t\t\t\t  true);\n\t\t\troot->processed_tlist = lappend(root->processed_tlist, tle);\n\t\t\tnewvars = lappend(newvars, var);\n\t\t}\n\n\t\t/* Add tableoid junk Var, unless we had it already */\n\t\tif (!old_isParent)\n\t\t{\n\t\t\tvar = makeVar(oldrc->rti, TableOidAttributeNumber, OIDOID, -1, InvalidOid, 0);\n\t\t\tsnprintf(resname, sizeof(resname), \"tableoid%u\", oldrc->rowmarkId);\n\t\t\ttle = makeTargetEntry((Expr *) var,\n\t\t\t\t\t\t\t\t  list_length(root->processed_tlist) + 1,\n\t\t\t\t\t\t\t\t  pstrdup(resname),\n\t\t\t\t\t\t\t\t  true);\n\t\t\troot->processed_tlist = lappend(root->processed_tlist, tle);\n\t\t\tnewvars = lappend(newvars, var);\n\t\t}\n\n\t\t/*\n\t\t * Add the newly added Vars to parent's reltarget.  We needn't worry\n\t\t * about the children's reltargets, they'll be made later.\n\t\t */\n\t\tadd_vars_to_targetlist_compat(root, newvars, bms_make_singleton(0));\n\t}\n\n\tts_add_append_rel_infos(root, appinfos);\n\n\t/* PostgreSQL will not set up the child rels for use, due to the games\n\t * we're playing with inheritance, so we must do it ourselves.\n\t * build_simple_rel will look things up in the append_rel_array, so we can\n\t * only use it after that array has been set up.\n\t */\n\tList *base_restrictions = rel->baserestrictinfo;\n\tList *filtered_restrictions = NIL;\n\tbool try_restriction_filtering =\n\t\tts_guc_enable_qual_filtering && hri && ht->space->num_dimensions == 1;\n\n\tif (try_restriction_filtering)\n\t{\n\t\tfiltered_restrictions = filter_baserestrictions(ht, base_restrictions);\n\t\t/* Dont try filtering if all restrictions remain after filtering */\n\t\tif (list_length(base_restrictions) == list_length(filtered_restrictions))\n\t\t\ttry_restriction_filtering = false;\n\t}\n\n\tfor (unsigned int i = 0; i < num_chunks; i++)\n\t{\n\t\tbool can_clear_restrictinfo = false;\n\t\tIndex child_rtindex = first_chunk_index + i;\n\t\tChunk *chunk = chunks[i];\n\t\tif (try_restriction_filtering)\n\t\t{\n\t\t\tcan_clear_restrictinfo = chunk_fully_covered(hri, chunk);\n\t\t}\n\n\t\t/* build_simple_rel will copy baserestrictinfo to the child rel and\n\t\t * do the necessary attribute mapping. If we can determine that the chunk\n\t\t * is fully covered by the primary dimension restriction we can remove\n\t\t * primary dimension restrictions from baserestrictinfo.\n\t\t */\n\t\tif (can_clear_restrictinfo)\n\t\t\trel->baserestrictinfo = filtered_restrictions;\n\n\t\t/* build_simple_rel will add the child to the relarray */\n\t\tRelOptInfo *child_rel = build_simple_rel(root, child_rtindex, rel);\n\n\t\tif (can_clear_restrictinfo)\n\t\t\trel->baserestrictinfo = base_restrictions;\n\n\t\t/*\n\t\t * Can't touch fdw_private for OSM chunks, it might be managed by the\n\t\t * OSM extension, or, in the tests, by postgres_fdw.\n\t\t */\n\t\tif (!IS_OSM_CHUNK(chunk))\n\t\t{\n\t\t\tAssert(chunk->table_id == root->simple_rte_array[child_rtindex]->relid);\n\t\t\tts_get_private_reloptinfo(child_rel)->cached_chunk_struct = chunk;\n\t\t}\n\t}\n}\n\nstatic bool\nrestrictinfo_has_qual(List *restrictions, OpExpr *qual)\n{\n\tListCell *lc_ri;\n\tforeach (lc_ri, restrictions)\n\t{\n\t\tif (equal(castNode(RestrictInfo, lfirst(lc_ri))->clause, (Expr *) qual))\n\t\t\treturn true;\n\t}\n\treturn false;\n}\n\nvoid\npropagate_join_quals(PlannerInfo *root, RelOptInfo *rel, CollectQualCtx *ctx)\n{\n\tListCell *lc;\n\n\tif (!ts_guc_enable_qual_propagation)\n\t\treturn;\n\n\t/* propagate join constraints */\n\tforeach (lc, ctx->propagate_conditions)\n\t{\n\t\tListCell *lc_qual;\n\t\tOpExpr *op = lfirst(lc);\n\t\tVar *rel_var, *other_var;\n\n\t\t/*\n\t\t * propagate_conditions only has OpExpr with 2 Var as arguments\n\t\t * this is enforced in process_quals\n\t\t */\n\t\tAssert(IsA(op, OpExpr) && list_length(castNode(OpExpr, op)->args) == 2);\n\t\tAssert(IsA(linitial(op->args), Var) && IsA(lsecond(op->args), Var));\n\n\t\t/*\n\t\t * check this join condition refers to current hypertable\n\t\t * our Var might be on either side of the expression\n\t\t */\n\t\tif ((Index) linitial_node(Var, op->args)->varno == rel->relid)\n\t\t{\n\t\t\trel_var = linitial_node(Var, op->args);\n\t\t\tother_var = lsecond_node(Var, op->args);\n\t\t}\n\t\telse if ((Index) lsecond_node(Var, op->args)->varno == rel->relid)\n\t\t{\n\t\t\trel_var = lsecond_node(Var, op->args);\n\t\t\tother_var = linitial_node(Var, op->args);\n\t\t}\n\t\telse\n\t\t\tcontinue;\n\n\t\tforeach (lc_qual, ctx->all_quals)\n\t\t{\n\t\t\tOpExpr *qual = lfirst(lc_qual);\n\t\t\tExpr *left = linitial(qual->args);\n\t\t\tExpr *right = lsecond(qual->args);\n\t\t\tOpExpr *propagated;\n\n\t\t\t/*\n\t\t\t * check this is Var OP Expr / Expr OP Var\n\t\t\t * Var needs to reference the relid of the JOIN condition and\n\t\t\t * Expr must not contain volatile functions\n\t\t\t */\n\t\t\tif (IsA(left, Var) && castNode(Var, left)->varno == other_var->varno &&\n\t\t\t\tcastNode(Var, left)->varattno == other_var->varattno && !IsA(right, Var) &&\n\t\t\t\t!contain_volatile_functions((Node *) right))\n\t\t\t{\n\t\t\t\tpropagated = copyObject(qual);\n\t\t\t\tpropagated->args = list_make2(rel_var, lsecond(propagated->args));\n\t\t\t}\n\t\t\telse if (IsA(right, Var) && castNode(Var, right)->varno == other_var->varno &&\n\t\t\t\t\t castNode(Var, right)->varattno == other_var->varattno && !IsA(left, Var) &&\n\t\t\t\t\t !contain_volatile_functions((Node *) left))\n\t\t\t{\n\t\t\t\tpropagated = copyObject(qual);\n\t\t\t\tpropagated->args = list_make2(linitial(propagated->args), rel_var);\n\t\t\t}\n\t\t\telse\n\t\t\t\tcontinue;\n\n\t\t\t/*\n\t\t\t * check if this is a new qual\n\t\t\t */\n\t\t\tif (restrictinfo_has_qual(ctx->restrictions, propagated))\n\t\t\t\tcontinue;\n\n\t\t\tRelids relids = pull_varnos(ctx->root, (Node *) propagated);\n\t\t\tRestrictInfo *restrictinfo;\n\n\t\t\trestrictinfo = make_restrictinfo_compat(root,\n\t\t\t\t\t\t\t\t\t\t\t\t\t(Expr *) propagated,\n\t\t\t\t\t\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\t\t\t\t\tctx->root->qual_security_level,\n\t\t\t\t\t\t\t\t\t\t\t\t\trelids,\n\t\t\t\t\t\t\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\t\t\t\t\t\t\tNULL);\n\t\t\tctx->restrictions = lappend(ctx->restrictions, restrictinfo);\n\t\t\t/*\n\t\t\t * since hypertable expansion happens later, the propagated\n\t\t\t * constraints will not be pushed down to the actual scans but stay\n\t\t\t * as join filter. So we add them either as join filter or to\n\t\t\t * baserestrictinfo depending on whether they reference only\n\t\t\t * the currently processed relation or multiple relations.\n\t\t\t */\n\t\t\tif (bms_num_members(relids) == 1 && bms_is_member(rel->relid, relids))\n\t\t\t{\n\t\t\t\tif (!restrictinfo_has_qual(rel->baserestrictinfo, propagated))\n\t\t\t\t\trel->baserestrictinfo = lappend(rel->baserestrictinfo, restrictinfo);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\troot->parse->jointree->quals =\n\t\t\t\t\t(Node *) lappend((List *) root->parse->jointree->quals, propagated);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/planner/planner.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/tsmapi.h>\n#include <access/xact.h>\n#include <catalog/namespace.h>\n#include <commands/extension.h>\n#include <executor/nodeAgg.h>\n#include <miscadmin.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodeFuncs.h>\n#include <nodes/parsenodes.h>\n#include <nodes/plannodes.h>\n#include <optimizer/appendinfo.h>\n#include <optimizer/clauses.h>\n#include <optimizer/optimizer.h>\n#include <optimizer/pathnode.h>\n#include <optimizer/paths.h>\n#include <optimizer/plancat.h>\n#include <optimizer/planner.h>\n#include <optimizer/restrictinfo.h>\n#include <optimizer/tlist.h>\n#include <parser/parse_param.h>\n#include <parser/parse_relation.h>\n#include <parser/parsetree.h>\n#include <utils/elog.h>\n#include <utils/fmgroids.h>\n#include <utils/guc.h>\n#include <utils/lsyscache.h>\n#include <utils/memutils.h>\n#include <utils/selfuncs.h>\n#include <utils/timestamp.h>\n\n#include <math.h>\n\n#include \"annotations.h\"\n#include \"chunk.h\"\n#include \"cross_module_fn.h\"\n#include \"debug_assert.h\"\n#include \"dimension.h\"\n#include \"dimension_slice.h\"\n#include \"dimension_vector.h\"\n#include \"extension.h\"\n#include \"func_cache.h\"\n#include \"guc.h\"\n#include \"hypertable.h\"\n#include \"hypertable_cache.h\"\n#include \"import/allpaths.h\"\n#include \"license_guc.h\"\n#include \"nodes/chunk_append/chunk_append.h\"\n#include \"nodes/constraint_aware_append/constraint_aware_append.h\"\n#include \"nodes/modify_hypertable.h\"\n#include \"partitioning.h\"\n#include \"planner/planner.h\"\n#include \"sort_transform.h\"\n#include \"utils.h\"\n\n#include \"compat/compat.h\"\n#include <common/hashfn.h>\n\n#ifdef USE_TELEMETRY\n#include \"telemetry/functions.h\"\n#endif\n\n/* define parameters necessary to generate the baserel info hash table interface */\ntypedef struct BaserelInfoEntry\n{\n\tOid reloid;\n\tHypertable *ht;\n\n\tuint32 status; /* hash status */\n} BaserelInfoEntry;\n\n#define SH_PREFIX BaserelInfo\n#define SH_ELEMENT_TYPE BaserelInfoEntry\n#define SH_KEY_TYPE Oid\n#define SH_KEY reloid\n#define SH_EQUAL(tb, a, b) ((a) == (b))\n#define SH_HASH_KEY(tb, key) murmurhash32(key)\n#define SH_SCOPE static\n#define SH_DECLARE\n#define SH_DEFINE\n\n// We don't need most of the generated functions and there is no way to not\n// generate them.\n#ifdef __GNUC__\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wunused-function\"\n#endif\n\n// Generate the baserel info hash table functions.\n#include \"lib/simplehash.h\"\n#ifdef __GNUC__\n\n#pragma GCC diagnostic pop\n#endif\n\nvoid _planner_init(void);\nvoid _planner_fini(void);\n\nstatic planner_hook_type prev_planner_hook;\nstatic set_rel_pathlist_hook_type prev_set_rel_pathlist_hook;\nstatic get_relation_info_hook_type prev_get_relation_info_hook;\nstatic create_upper_paths_hook_type prev_create_upper_paths_hook;\nstatic void cagg_reorder_groupby_clause(RangeTblEntry *subq_rte, Index rtno, List *outer_sortcl,\n\t\t\t\t\t\t\t\t\t\tList *outer_tlist);\n\n/*\n * We mark range table entries (RTEs) in a query with TS_CTE_EXPAND if we'd like\n * to control table expansion ourselves. We exploit the ctename for this purpose\n * since it is not used for regular (base) relations.\n *\n * Note that we cannot use this mark as a general way to identify hypertable\n * RTEs. Child RTEs, for instance, will inherit this value from the parent RTE\n * during expansion. While we can prevent this happening in our custom table\n * expansion, we also have to account for the case when our custom expansion\n * is turned off with a GUC.\n */\nstatic const char *TS_CTE_EXPAND = \"ts_expand\";\nstatic const char *TS_FK_EXPAND = \"ts_fk_expand\";\n\n/*\n * A simplehash hash table that records the chunks and their corresponding\n * hypertables, and also the plain baserels. We use it to tell whether a\n * relation is a hypertable chunk, inside the classify_relation function.\n * It is valid inside the scope of timescaledb_planner().\n * That function can be called recursively, e.g. when we evaluate a SQL function,\n * and this cache is initialized only at the top-level call.\n */\nstatic struct BaserelInfo_hash *ts_baserel_info = NULL;\n\n/*\n * Add information about a chunk to the baserel info cache. Used to cache the\n * chunk info at the plan time chunk exclusion.\n */\nvoid\nts_add_baserel_cache_entry_for_chunk(Oid chunk_reloid, Hypertable *hypertable)\n{\n\tAssert(hypertable != NULL);\n\tAssert(ts_baserel_info != NULL);\n\n\tbool found = false;\n\tBaserelInfoEntry *entry = BaserelInfo_insert(ts_baserel_info, chunk_reloid, &found);\n\tif (found)\n\t{\n\t\t/* Already cached. */\n\t\tAssert(entry->ht != NULL);\n\t\treturn;\n\t}\n\n\tAssert(ts_chunk_get_hypertable_id_by_reloid(chunk_reloid) == hypertable->fd.id);\n\n\t/* Fill the cache entry. */\n\tentry->ht = hypertable;\n}\n\nstatic void\nrte_mark_for_expansion(RangeTblEntry *rte)\n{\n\tAssert(rte->rtekind == RTE_RELATION);\n\tAssert(rte->ctename == NULL);\n\trte->ctename = (char *) TS_CTE_EXPAND;\n\t/*\n\t * Do not mark partitioned hypertables for inheritance, as Postgres\n\t * is supposed to expand them.\n\t */\n\tif (rte->relkind != RELKIND_PARTITIONED_TABLE)\n\t\trte->inh = false;\n}\n\nstatic void\nrte_mark_for_fk_expansion(RangeTblEntry *rte)\n{\n\tAssert(rte->rtekind == RTE_RELATION);\n\tAssert(rte->ctename == NULL);\n\trte->ctename = (char *) TS_FK_EXPAND;\n\t/*\n\t * If this is for an FK lookup query inherit should be false\n\t * initially for hypertables.\n\t */\n\tAssert(!rte->inh);\n}\n\nbool\nts_rte_is_marked_for_expansion(const RangeTblEntry *rte)\n{\n\tif (NULL == rte->ctename)\n\t\treturn false;\n\n\tif (rte->ctename == TS_CTE_EXPAND || rte->ctename == TS_FK_EXPAND)\n\t\treturn true;\n\n\treturn strcmp(rte->ctename, TS_CTE_EXPAND) == 0;\n}\n\n/*\n * Planner-global hypertable cache.\n *\n * Each invocation of the planner (and our hooks) should reference the same\n * cache object. Since we warm the cache when pre-processing the query (prior to\n * invoking the planner), we'd like to ensure that we use the same cache object\n * throughout the planning of that query so that we can trust that the cache\n * holds the objects it was warmed with. Since the planner can be invoked\n * recursively, we also need to stack and pop cache objects.\n */\nstatic List *planner_hcaches = NIL;\n\nstatic Cache *\nplanner_hcache_push(void)\n{\n\tCache *hcache = ts_hypertable_cache_pin();\n\n\tplanner_hcaches = lcons(hcache, planner_hcaches);\n\n\treturn hcache;\n}\n\nstatic void\nplanner_hcache_pop(bool release)\n{\n\tCache *hcache;\n\n\tAssert(list_length(planner_hcaches) > 0);\n\n\thcache = linitial(planner_hcaches);\n\n\tplanner_hcaches = list_delete_first(planner_hcaches);\n\n\tif (release)\n\t{\n\t\tts_cache_release(&hcache);\n\t\t/* If we pop a stack and discover a new hypertable cache, the basrel\n\t\t * cache can contain invalid entries, so we reset it. */\n\t\tif (planner_hcaches != NIL && hcache != linitial(planner_hcaches))\n\t\t\tBaserelInfo_reset(ts_baserel_info);\n\t}\n}\n\nstatic bool\nplanner_hcache_exists(void)\n{\n\treturn planner_hcaches != NIL;\n}\n\nstatic Cache *\nplanner_hcache_get(void)\n{\n\tif (planner_hcaches == NIL)\n\t\treturn NULL;\n\n\treturn (Cache *) linitial(planner_hcaches);\n}\n\n/*\n * Get the Hypertable corresponding to the given relid.\n *\n * This function gets a hypertable from a pre-warmed hypertable cache. If\n * noresolve is specified (true), then it will do a cache-only lookup (i.e., it\n * will not try to scan metadata for a new entry to put in the cache). This\n * allows fast lookups during planning to also determine if something is _not_ a\n * hypertable.\n */\nHypertable *\nts_planner_get_hypertable(const Oid relid, const unsigned int flags)\n{\n\tCache *cache = planner_hcache_get();\n\n\tif (NULL == cache)\n\t\treturn NULL;\n\n\treturn ts_hypertable_cache_get_entry(cache, relid, flags);\n}\n\nbool\nts_rte_is_hypertable(const RangeTblEntry *rte)\n{\n\tHypertable *ht = ts_planner_get_hypertable(rte->relid, CACHE_FLAG_CHECK);\n\n\treturn ht != NULL;\n}\n\n#define IS_UPDL_CMD(parse)                                                                         \\\n\t((parse)->commandType == CMD_UPDATE || (parse)->commandType == CMD_DELETE)\n\ntypedef struct\n{\n\tQuery *rootquery;\n\tQuery *current_query;\n\tPlannerInfo *root;\n} PreprocessQueryContext;\n\nstatic void preprocess_fk_checks(Query *query, Cache *hcache, PreprocessQueryContext *context);\n\nvoid\nreplace_now_mock_walker(PlannerInfo *root, Node *clause, Oid funcid)\n{\n\t/* whenever we encounter a FuncExpr with now(), replace it with the supplied funcid */\n\tswitch (nodeTag(clause))\n\t{\n\t\tcase T_FuncExpr:\n\t\t{\n\t\t\tif (is_valid_now_func(clause))\n\t\t\t{\n\t\t\t\tFuncExpr *fe = castNode(FuncExpr, clause);\n\t\t\t\tfe->funcid = funcid;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase T_OpExpr:\n\t\t{\n\t\t\tListCell *lc;\n\t\t\tOpExpr *oe = castNode(OpExpr, clause);\n\t\t\tforeach (lc, oe->args)\n\t\t\t{\n\t\t\t\treplace_now_mock_walker(root, (Node *) lfirst(lc), funcid);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase T_BoolExpr:\n\t\t{\n\t\t\tListCell *lc;\n\t\t\tBoolExpr *be = castNode(BoolExpr, clause);\n\t\t\tforeach (lc, be->args)\n\t\t\t{\n\t\t\t\treplace_now_mock_walker(root, (Node *) lfirst(lc), funcid);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\treturn;\n\t}\n}\n\n/*\n * Preprocess the query tree, including, e.g., subqueries.\n *\n * Preprocessing includes:\n *\n * 1. Identifying all range table entries (RTEs) that reference\n *    hypertables. This will also warm the hypertable cache for faster lookup\n *    of both hypertables (cache hit) and non-hypertables (cache miss),\n *    without having to scan the metadata in either case.\n *\n * 2. Turning off inheritance for hypertable RTEs that we expand ourselves.\n *\n * 3. Reordering of GROUP BY clauses for continuous aggregates.\n *\n * 4. Constifying now() expressions for primary time dimension.\n */\nstatic bool\npreprocess_query(Node *node, PreprocessQueryContext *context)\n{\n\tif (node == NULL)\n\t\treturn false;\n\n\tif (IsA(node, FromExpr) && ts_guc_enable_optimizations)\n\t{\n\t\tFromExpr *from = castNode(FromExpr, node);\n\t\tif (from->quals)\n\t\t{\n\t\t\tif (ts_guc_enable_now_constify)\n\t\t\t{\n\t\t\t\tfrom->quals =\n\t\t\t\t\tts_constify_now(context->root, context->current_query->rtable, from->quals);\n#ifdef TS_DEBUG\n\t\t\t\t/*\n\t\t\t\t * only replace if GUC is also set. This is used for testing purposes only,\n\t\t\t\t * so no need to change the output for other tests in DEBUG builds\n\t\t\t\t */\n\t\t\t\tif (ts_current_timestamp_mock != NULL && strlen(ts_current_timestamp_mock) != 0)\n\t\t\t\t{\n\t\t\t\t\tOid funcid_mock;\n\t\t\t\t\tconst char *funcname = \"ts_now_mock()\";\n\t\t\t\t\tfuncid_mock = DatumGetObjectId(\n\t\t\t\t\t\tDirectFunctionCall1(regprocedurein, CStringGetDatum(funcname)));\n\t\t\t\t\treplace_now_mock_walker(context->root, from->quals, funcid_mock);\n\t\t\t\t}\n#endif\n\t\t\t}\n\t\t\t/*\n\t\t\t * We only amend space constraints for UPDATE/DELETE and SELECT FOR UPDATE\n\t\t\t * as for normal SELECT we use our own hypertable expansion which can handle\n\t\t\t * constraints on hashed space dimensions without further help.\n\t\t\t */\n\t\t\tif (context->current_query->commandType != CMD_SELECT ||\n\t\t\t\tcontext->current_query->rowMarks != NIL)\n\t\t\t{\n\t\t\t\tfrom->quals = ts_add_space_constraints(context->root,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   context->current_query->rtable,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   from->quals);\n\t\t\t}\n\t\t}\n\t}\n\n\telse if (IsA(node, Query))\n\t{\n\t\tQuery *query = castNode(Query, node);\n\t\tQuery *prev_query;\n\t\tCache *hcache = planner_hcache_get();\n\t\tListCell *lc;\n\t\tIndex rti = 1;\n\t\tbool ret;\n\n\t\tif (ts_guc_enable_foreign_key_propagation)\n\t\t{\n\t\t\tpreprocess_fk_checks(query, hcache, context);\n\t\t}\n\n\t\tforeach (lc, query->rtable)\n\t\t{\n\t\t\tRangeTblEntry *rte = lfirst_node(RangeTblEntry, lc);\n\t\t\tHypertable *ht;\n\n\t\t\tswitch (rte->rtekind)\n\t\t\t{\n\t\t\t\tcase RTE_SUBQUERY:\n\t\t\t\t\tif (ts_guc_enable_optimizations && ts_guc_enable_cagg_reorder_groupby &&\n\t\t\t\t\t\tquery->commandType == CMD_SELECT)\n\t\t\t\t\t{\n\t\t\t\t\t\t/* applicable to selects on continuous aggregates */\n\t\t\t\t\t\tList *outer_tlist = query->targetList;\n\t\t\t\t\t\tList *outer_sortcl = query->sortClause;\n\t\t\t\t\t\tcagg_reorder_groupby_clause(rte, rti, outer_sortcl, outer_tlist);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase RTE_RELATION:\n\t\t\t\t\t/* This lookup will warm the cache with all hypertables in the query */\n\t\t\t\t\tht = ts_hypertable_cache_get_entry(hcache, rte->relid, CACHE_FLAG_MISSING_OK);\n\n\t\t\t\t\tif (ht)\n\t\t\t\t\t{\n\t\t\t\t\t\t/* Mark hypertable RTEs we'd like to expand ourselves */\n\t\t\t\t\t\tif (ts_guc_enable_optimizations && ts_guc_enable_constraint_exclusion &&\n\t\t\t\t\t\t\t!IS_UPDL_CMD(context->rootquery) && query->resultRelation == 0 &&\n\t\t\t\t\t\t\trte->inh)\n\t\t\t\t\t\t\trte_mark_for_expansion(rte);\n\n\t\t\t\t\t\tif (TS_HYPERTABLE_HAS_COMPRESSION_TABLE(ht))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tint compr_htid = ht->fd.compressed_hypertable_id;\n\n\t\t\t\t\t\t\t/* Also warm the cache with the compressed\n\t\t\t\t\t\t\t * companion hypertable */\n\t\t\t\t\t\t\tts_hypertable_cache_get_entry_by_id(hcache, compr_htid);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\t/* To properly keep track of SELECT FROM ONLY <chunk> we\n\t\t\t\t\t\t * have to mark the rte here because postgres will set\n\t\t\t\t\t\t * rte->inh to false (when it detects the chunk has no\n\t\t\t\t\t\t * children which is true for all our chunks) before it\n\t\t\t\t\t\t * reaches set_rel_pathlist hook. But chunks from queries\n\t\t\t\t\t\t * like SELECT ..  FROM ONLY <chunk> has rte->inh set to\n\t\t\t\t\t\t * false and other chunks have rte->inh set to true.\n\t\t\t\t\t\t * We want to distinguish between the two cases here by\n\t\t\t\t\t\t * marking the chunk when rte->inh is true.\n\t\t\t\t\t\t */\n\t\t\t\t\t\tChunk *chunk =\n\t\t\t\t\t\t\tts_chunk_get_by_relid_locked(rte->relid, NoLock, NULL, false);\n\t\t\t\t\t\tif (chunk && rte->inh)\n\t\t\t\t\t\t\trte_mark_for_expansion(rte);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\trti++;\n\t\t}\n\t\tprev_query = context->current_query;\n\t\tcontext->current_query = query;\n\t\tret = query_tree_walker(query, preprocess_query, context, 0);\n\t\tcontext->current_query = prev_query;\n\t\treturn ret;\n\t}\n\n\treturn expression_tree_walker(node, preprocess_query, context);\n}\n\n/*\n * Detect FOREIGN KEY lookup queries and mark the RTE for expansion.\n * Unfortunately postgres will create lookup queries for foreign keys\n * with `ONLY` preventing hypertable expansion. Only for declarative\n * partitioned tables the queries will be created without `ONLY`.\n * We try to detect these queries here and undo the `ONLY` flag for\n * these specific queries.\n *\n * The implementation of this on the postgres side can be found in\n * src/backend/utils/adt/ri_triggers.c\n */\nstatic void\npreprocess_fk_checks(Query *query, Cache *hcache, PreprocessQueryContext *context)\n{\n\t/*\n\t * RI_FKey_cascade_del\n\t *\n\t * DELETE FROM [ONLY] <fktable> WHERE $1 = fkatt1 [AND ...]\n\t */\n\tif (query->commandType == CMD_DELETE && list_length(query->rtable) == 1 &&\n\t\tquery->jointree->quals && IsA(query->jointree->quals, OpExpr) &&\n\t\t(context->root->glob->boundParams || query_contains_extern_params(query)))\n\t{\n\t\tRangeTblEntry *rte = linitial_node(RangeTblEntry, query->rtable);\n\t\tif (!rte->inh && rte->rtekind == RTE_RELATION)\n\t\t{\n\t\t\tHypertable *ht =\n\t\t\t\tts_hypertable_cache_get_entry(hcache, rte->relid, CACHE_FLAG_MISSING_OK);\n\t\t\tif (ht)\n\t\t\t{\n\t\t\t\trte->inh = true;\n\t\t\t}\n\t\t}\n\t}\n\n\t/*\n\t * RI_FKey_cascade_upd\n\t *\n\t *  UPDATE [ONLY] <fktable> SET fkatt1 = $1 [, ...]\n\t *      WHERE $n = fkatt1 [AND ...]\n\t */\n\tif (query->commandType == CMD_UPDATE && list_length(query->rtable) == 1 &&\n\t\tquery->jointree->quals && IsA(query->jointree->quals, OpExpr) &&\n\t\t(context->root->glob->boundParams || query_contains_extern_params(query)))\n\t{\n\t\tRangeTblEntry *rte = linitial_node(RangeTblEntry, query->rtable);\n\t\tif (!rte->inh && rte->rtekind == RTE_RELATION)\n\t\t{\n\t\t\tHypertable *ht =\n\t\t\t\tts_hypertable_cache_get_entry(hcache, rte->relid, CACHE_FLAG_MISSING_OK);\n\t\t\tif (ht)\n\t\t\t{\n\t\t\t\trte->inh = true;\n\t\t\t}\n\t\t}\n\t}\n\n\t/*\n\t * RI_FKey_check\n\t *\n\t * The RI_FKey_check query string built is\n\t *  SELECT 1 FROM [ONLY] <pktable> x WHERE pkatt1 = $1 [AND ...]\n\t *       FOR KEY SHARE OF x\n\t */\n\tif (query->commandType == CMD_SELECT && query->hasForUpdate &&\n\t\tlist_length(query->rtable) == 1 &&\n\t\t(context->root->glob->boundParams || query_contains_extern_params(query)))\n\t{\n\t\tRangeTblEntry *rte = linitial_node(RangeTblEntry, query->rtable);\n\t\tif (!rte->inh && rte->rtekind == RTE_RELATION && rte->rellockmode == RowShareLock &&\n\t\t\tlist_length(query->jointree->fromlist) == 1 && query->jointree->quals &&\n\t\t\tstrcmp(rte->eref->aliasname, \"x\") == 0)\n\t\t{\n\t\t\tHypertable *ht =\n\t\t\t\tts_hypertable_cache_get_entry(hcache, rte->relid, CACHE_FLAG_MISSING_OK);\n\t\t\tif (ht)\n\t\t\t{\n\t\t\t\trte_mark_for_fk_expansion(rte);\n\t\t\t\tif (TS_HYPERTABLE_HAS_COMPRESSION_ENABLED(ht))\n\t\t\t\t\tquery->rowMarks = NIL;\n\t\t\t}\n\t\t}\n\t}\n\t/*\n\t * RI_Initial_Check query\n\t *\n\t * The RI_Initial_Check query string built is:\n\t *  SELECT fk.keycols FROM [ONLY] relname fk\n\t *   LEFT OUTER JOIN [ONLY] pkrelname pk\n\t *   ON (pk.pkkeycol1=fk.keycol1 [AND ...])\n\t *   WHERE pk.pkkeycol1 IS NULL AND\n\t * For MATCH SIMPLE:\n\t *   (fk.keycol1 IS NOT NULL [AND ...])\n\t * For MATCH FULL:\n\t *   (fk.keycol1 IS NOT NULL [OR ...])\n\t */\n\tif (query->commandType == CMD_SELECT && list_length(query->rtable) == 3)\n\t{\n\t\tRangeTblEntry *rte1 = linitial_node(RangeTblEntry, query->rtable);\n\t\tRangeTblEntry *rte2 = lsecond_node(RangeTblEntry, query->rtable);\n\t\tif (!rte1->inh && !rte2->inh && rte1->rtekind == RTE_RELATION &&\n\t\t\trte2->rtekind == RTE_RELATION && strcmp(rte1->eref->aliasname, \"fk\") == 0 &&\n\t\t\tstrcmp(rte2->eref->aliasname, \"pk\") == 0)\n\t\t{\n\t\t\tif (ts_hypertable_cache_get_entry(hcache, rte1->relid, CACHE_FLAG_MISSING_OK))\n\t\t\t{\n\t\t\t\trte_mark_for_fk_expansion(rte1);\n\t\t\t}\n\t\t\tif (ts_hypertable_cache_get_entry(hcache, rte2->relid, CACHE_FLAG_MISSING_OK))\n\t\t\t{\n\t\t\t\trte_mark_for_fk_expansion(rte2);\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic PlannedStmt *\ntimescaledb_planner(Query *parse, const char *query_string, int cursor_opts,\n\t\t\t\t\tParamListInfo bound_params)\n{\n\tPlannedStmt *stmt;\n\tListCell *lc;\n\t/*\n\t * Volatile is needed because these are the local variables that are\n\t * modified between setjmp/longjmp calls.\n\t */\n\tvolatile bool reset_baserel_info = false;\n\n\t/*\n\t * If we are in an aborted transaction, reject all queries.\n\t * While this state will not happen during normal operation it\n\t * can happen when executing plpgsql procedures.\n\t */\n\tif (IsAbortedTransactionBlockState())\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),\n\t\t\t\t errmsg(\"current transaction is aborted, \"\n\t\t\t\t\t\t\"commands ignored until end of transaction block\")));\n\n\tplanner_hcache_push();\n\tif (ts_baserel_info == NULL)\n\t{\n\t\t/*\n\t\t * The calls to timescaledb_planner can be recursive (e.g. when\n\t\t * evaluating an immutable SQL function at planning time). We want to\n\t\t * create and destroy the per-query baserel info table only at the\n\t\t * top-level call, hence this flag.\n\t\t */\n\t\treset_baserel_info = true;\n\n\t\t/*\n\t\t * This is a per-query cache, so we create it in the current memory\n\t\t * context for the top-level call of this function, which hopefully\n\t\t * should exist for the duration of the query. Message or portal\n\t\t * memory contexts could also be suitable, but they don't exist for\n\t\t * SPI calls.\n\t\t */\n\t\tts_baserel_info = BaserelInfo_create(CurrentMemoryContext,\n\t\t\t\t\t\t\t\t\t\t\t /* nelements = */ 1,\n\t\t\t\t\t\t\t\t\t\t\t /* private_data = */ NULL);\n\t}\n\n\tPG_TRY();\n\t{\n\t\tPreprocessQueryContext context = { 0 };\n\t\tPlannerGlobal glob = {\n\t\t\t.boundParams = bound_params,\n\t\t};\n\t\tPlannerInfo root = {\n\t\t\t.glob = &glob,\n\t\t};\n\n\t\tcontext.root = &root;\n\t\tcontext.rootquery = parse;\n\t\tcontext.current_query = parse;\n\n\t\tif (ts_extension_is_loaded_and_not_upgrading())\n\t\t{\n#ifdef USE_TELEMETRY\n\t\t\tts_telemetry_function_info_gather(parse);\n#endif\n\t\t\t/*\n\t\t\t * Preprocess the hypertables in the query and warm up the caches.\n\t\t\t */\n\t\t\tpreprocess_query((Node *) parse, &context);\n\n\t\t\tif (ts_guc_enable_optimizations)\n\t\t\t\tts_cm_functions->preprocess_query_tsl(parse, &cursor_opts);\n\t\t}\n\n\t\tif (prev_planner_hook != NULL)\n\t\t\t/* Call any earlier hooks */\n\t\t\tstmt = (prev_planner_hook) (parse, query_string, cursor_opts, bound_params);\n\t\telse\n\t\t\t/* Call the standard planner */\n\t\t\tstmt = standard_planner(parse, query_string, cursor_opts, bound_params);\n\n\t\tif (ts_extension_is_loaded_and_not_upgrading())\n\t\t{\n\t\t\t/*\n\t\t\t * Our top-level HypertableInsert plan node that wraps ModifyTable needs\n\t\t\t * to have a final target list that is the same as the ModifyTable plan\n\t\t\t * node, and we only have access to its final target list after\n\t\t\t * set_plan_references() (setrefs.c) has run at the end of\n\t\t\t * standard_planner. Therefore, we fixup the final target list for\n\t\t\t * HypertableInsert here.\n\t\t\t */\n\t\t\tts_modify_hypertable_fixup_tlist(stmt->planTree);\n\n\t\t\tforeach (lc, stmt->subplans)\n\t\t\t{\n\t\t\t\tPlan *subplan = (Plan *) lfirst(lc);\n\n\t\t\t\tif (subplan)\n\t\t\t\t\tts_modify_hypertable_fixup_tlist(subplan);\n\t\t\t}\n\n\t\t\tts_cm_functions->tsl_postprocess_plan(stmt);\n\t\t}\n\n\t\tif (reset_baserel_info)\n\t\t{\n\t\t\tAssert(ts_baserel_info != NULL);\n\t\t\tBaserelInfo_destroy(ts_baserel_info);\n\t\t\tts_baserel_info = NULL;\n\t\t}\n\t}\n\tPG_CATCH();\n\t{\n\t\tif (reset_baserel_info)\n\t\t{\n\t\t\tAssert(ts_baserel_info != NULL);\n\t\t\tBaserelInfo_destroy(ts_baserel_info);\n\t\t\tts_baserel_info = NULL;\n\t\t}\n\n\t\t/* Pop the cache, but do not release since caches are auto-released on\n\t\t * error */\n\t\tplanner_hcache_pop(false);\n\t\tPG_RE_THROW();\n\t}\n\tPG_END_TRY();\n\n\tplanner_hcache_pop(true);\n\n\treturn stmt;\n}\n\nstatic RangeTblEntry *\nget_parent_rte(const PlannerInfo *root, Index rti)\n{\n\tListCell *lc;\n\n\t/* Fast path when arrays are setup */\n\tif (root->append_rel_array != NULL && root->append_rel_array[rti] != NULL)\n\t{\n\t\tAppendRelInfo *appinfo = root->append_rel_array[rti];\n\t\treturn planner_rt_fetch(appinfo->parent_relid, root);\n\t}\n\n\tforeach (lc, root->append_rel_list)\n\t{\n\t\tAppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);\n\n\t\tif (appinfo->child_relid == rti)\n\t\t\treturn planner_rt_fetch(appinfo->parent_relid, root);\n\t}\n\n\treturn NULL;\n}\n\n/*\n * Fetch cached baserel entry. If it does not exists, create an entry for this\n * relid.\n * If this relid corresponds to a chunk, cache additional chunk\n * related metadata: like chunk_status and pointer to hypertable entry.\n * It is okay to cache a pointer to the hypertable, since this cache is\n * confined to the lifetime of the query and not used across queries.\n * If the parent reolid is known, the caller can specify it to avoid the costly\n * lookup. Otherwise pass InvalidOid.\n */\nstatic BaserelInfoEntry *\nget_or_add_baserel_from_cache(Oid chunk_reloid, Oid parent_reloid)\n{\n\tHypertable *ht = NULL;\n\t/* First, check if this reloid is in cache. */\n\tbool found = false;\n\tBaserelInfoEntry *entry = BaserelInfo_insert(ts_baserel_info, chunk_reloid, &found);\n\tif (found)\n\t{\n\t\treturn entry;\n\t}\n\n\tif (OidIsValid(parent_reloid))\n\t{\n\t\tht = ts_planner_get_hypertable(parent_reloid, CACHE_FLAG_CHECK);\n\n#ifdef USE_ASSERT_CHECKING\n\t\t/* Sanity check on the caller-specified hypertable reloid. */\n\t\tint32 parent_hypertable_id = ts_chunk_get_hypertable_id_by_reloid(chunk_reloid);\n\t\tif (parent_hypertable_id != INVALID_HYPERTABLE_ID)\n\t\t{\n\t\t\tAssert(ts_hypertable_id_to_relid(parent_hypertable_id, false) == parent_reloid);\n\n\t\t\tif (ht != NULL)\n\t\t\t{\n\t\t\t\tAssert(ht->fd.id == parent_hypertable_id);\n\t\t\t}\n\t\t}\n#endif\n\t}\n\telse\n\t{\n\t\t/* Hypertable reloid not specified by the caller, look it up by\n\t\t * an expensive metadata scan.\n\t\t */\n\t\tint32 hypertable_id = ts_chunk_get_hypertable_id_by_reloid(chunk_reloid);\n\n\t\tif (hypertable_id != INVALID_HYPERTABLE_ID)\n\t\t{\n\t\t\t/* Hypertable reloid not specified by the caller, look it up. */\n\t\t\tparent_reloid = ts_hypertable_id_to_relid(hypertable_id, /* return_invalid */ false);\n\n\t\t\tht = ts_planner_get_hypertable(parent_reloid, CACHE_FLAG_NONE);\n\t\t\tAssert(ht != NULL);\n\t\t\tAssert(ht->fd.id == hypertable_id);\n\t\t}\n\t}\n\n\t/* Cache the result. */\n\tentry->ht = ht;\n\treturn entry;\n}\n\n/*\n * Classify a planned relation.\n *\n * This makes use of cache warming that happened during Query preprocessing in\n * the first planner hook.\n */\nTsRelType\nts_classify_relation(const PlannerInfo *root, const RelOptInfo *rel, Hypertable **ht)\n{\n\tAssert(ht != NULL);\n\t*ht = NULL;\n\n\tif (rel->reloptkind != RELOPT_BASEREL && rel->reloptkind != RELOPT_OTHER_MEMBER_REL)\n\t{\n\t\treturn TS_REL_OTHER;\n\t}\n\n\tRangeTblEntry *rte = planner_rt_fetch(rel->relid, root);\n\n\tif (rte->relkind == RELKIND_FOREIGN_TABLE)\n\t{\n\t\t/*\n\t\t * OSM chunk or other foreign chunk. We can't even access the\n\t\t * fdw_private for it, because it's a foreign chunk managed by a\n\t\t * different extension. Try to ignore it as much as possible.\n\t\t */\n\t\treturn TS_REL_OTHER;\n\t}\n\n\tif (!OidIsValid(rte->relid))\n\t{\n\t\treturn TS_REL_OTHER;\n\t}\n\n\tif (rel->reloptkind == RELOPT_BASEREL)\n\t{\n\t\t/*\n\t\t * To correctly classify relations in subqueries we cannot call\n\t\t * ts_planner_get_hypertable with CACHE_FLAG_CHECK which includes\n\t\t * CACHE_FLAG_NOCREATE flag because the rel might not be in cache yet.\n\t\t */\n\t\t*ht = ts_planner_get_hypertable(rte->relid, CACHE_FLAG_MISSING_OK);\n\n\t\tif (*ht != NULL)\n\t\t{\n\t\t\treturn TS_REL_HYPERTABLE;\n\t\t}\n\n\t\t/*\n\t\t * This is either a chunk seen as a standalone table, a compressed chunk\n\t\t * table, or a non-chunk baserel. We need a costly chunk metadata scan\n\t\t * to distinguish between them, so we cache the result of this lookup to\n\t\t * avoid doing it repeatedly.\n\t\t */\n\t\tBaserelInfoEntry *entry = get_or_add_baserel_from_cache(rte->relid, InvalidOid);\n\t\t*ht = entry->ht;\n\n\t\tif (*ht)\n\t\t{\n\t\t\t/*\n\t\t\t * Note that this works in a slightly weird way for compressed\n\t\t\t * chunks expanded from a normal hypertable, always saying that they\n\t\t\t * are standalone. In practice we filter them out by also checking\n\t\t\t * that the respective hypertable is not an internal compression\n\t\t\t * hypertable.\n\t\t\t */\n\t\t\treturn TS_REL_CHUNK_STANDALONE;\n\t\t}\n\n\t\treturn TS_REL_OTHER;\n\t}\n\n\tAssert(rel->reloptkind == RELOPT_OTHER_MEMBER_REL);\n\n\tRangeTblEntry *parent_rte = get_parent_rte(root, rel->relid);\n\n\t/*\n\t * An entry of reloptkind RELOPT_OTHER_MEMBER_REL might still\n\t * be a hypertable or a chunk here if it was pulled up from a\n\t * subquery as happens with UNION ALL for example. So we have to\n\t * check for that to properly detect that pattern.\n\t */\n\tif (parent_rte->rtekind == RTE_SUBQUERY)\n\t{\n\t\t*ht = ts_planner_get_hypertable(rte->relid,\n\t\t\t\t\t\t\t\t\t\trte->inh ? CACHE_FLAG_MISSING_OK : CACHE_FLAG_CHECK);\n\n\t\tif (*ht)\n\t\t\treturn TS_REL_HYPERTABLE;\n\n\t\t/*\n\t\t * This is either a chunk seen as a standalone table or a non-chunk baserel.\n\t\t * We need a costly chunk metadata scan to distinguish between them, so we\n\t\t * cache the result of this lookup to avoid doing it repeatedly.\n\t\t */\n\t\tBaserelInfoEntry *entry = get_or_add_baserel_from_cache(rte->relid, InvalidOid);\n\t\t*ht = entry->ht;\n\n\t\tif (*ht)\n\t\t\treturn TS_REL_CHUNK_STANDALONE;\n\n\t\treturn TS_REL_OTHER;\n\t}\n\n\tif (parent_rte->relid == rte->relid)\n\t{\n\t\t/*\n\t\t * A PostgreSQL table expansion peculiarity -- \"self child\", the root\n\t\t * table that is expanded as a child of itself. This happens when our\n\t\t * expansion code is turned off.\n\t\t */\n\t\t*ht = ts_planner_get_hypertable(rte->relid, CACHE_FLAG_CHECK);\n\t\treturn *ht != NULL ? TS_REL_HYPERTABLE_CHILD : TS_REL_OTHER;\n\t}\n\n\t/*\n\t * Either an other baserel or a chunk seen when expanding the hypertable.\n\t * Use the baserel cache to determine what it is.\n\t */\n\tBaserelInfoEntry *entry = get_or_add_baserel_from_cache(rte->relid, parent_rte->relid);\n\t*ht = entry->ht;\n\tif (*ht)\n\t{\n\t\treturn TS_REL_CHUNK_CHILD;\n\t}\n\n\treturn TS_REL_OTHER;\n}\n\nstatic inline bool\nshould_chunk_append(Hypertable *ht, PlannerInfo *root, RelOptInfo *rel, Path *path, bool ordered,\n\t\t\t\t\tint order_attno)\n{\n\tif (path->param_info != NULL && ordered)\n\t{\n\t\t/*\n\t\t * Ordered ChunkAppend might create MergeAppend path for individual\n\t\t * chunks when we have space partitioning or partial chunks. MergeAppend\n\t\t * paths cannot be parameterized. Refuse to use parameterized ordered\n\t\t * ChunkAppend altogether, because the more precise conditions are\n\t\t * difficult to check.\n\t\t */\n\t\treturn false;\n\t}\n\n\tif (\n\t\t/*\n\t\t * We only support chunk exclusion on UPDATE/DELETE when no JOIN is involved on PG14+.\n\t\t */\n\t\t((root->parse->commandType == CMD_DELETE || root->parse->commandType == CMD_UPDATE) &&\n\t\t bms_num_members(root->all_baserels) > 1) ||\n\t\t!ts_guc_enable_chunk_append)\n\t\treturn false;\n\n\tswitch (nodeTag(path))\n\t{\n\t\tcase T_AppendPath:\n\t\t\t/*\n\t\t\t * If there are clauses that have mutable functions, or clauses that reference\n\t\t\t * Params this Path might benefit from startup or runtime exclusion\n\t\t\t */\n\t\t\t{\n\t\t\t\tAppendPath *append = castNode(AppendPath, path);\n\t\t\t\tListCell *lc;\n\n\t\t\t\t/* Don't create ChunkAppend with no children */\n\t\t\t\tif (list_length(append->subpaths) == 0)\n\t\t\t\t\treturn false;\n\n\t\t\t\tforeach (lc, rel->baserestrictinfo)\n\t\t\t\t{\n\t\t\t\t\tRestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);\n\n\t\t\t\t\tif (contain_mutable_functions((Node *) rinfo->clause) ||\n\t\t\t\t\t\tts_contains_external_param((Node *) rinfo->clause) ||\n\t\t\t\t\t\tts_contains_join_param((Node *) rinfo->clause))\n\t\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\tcase T_MergeAppendPath:\n\t\t\t/*\n\t\t\t * Can we do ordered append\n\t\t\t */\n\t\t\t{\n\t\t\t\tMergeAppendPath *merge = castNode(MergeAppendPath, path);\n\t\t\t\tPathKey *pk;\n\t\t\t\tListCell *lc;\n\n\t\t\t\tif (!ordered || path->pathkeys == NIL || list_length(merge->subpaths) == 0)\n\t\t\t\t\treturn false;\n\n\t\t\t\t/*\n\t\t\t\t * Do not try to do ordered append if the OSM chunk range is noncontiguous\n\t\t\t\t */\n\t\t\t\tif (ht && ts_chunk_get_osm_chunk_id(ht->fd.id) != INVALID_CHUNK_ID)\n\t\t\t\t{\n\t\t\t\t\tif (ts_flags_are_set_32(ht->fd.status,\n\t\t\t\t\t\t\t\t\t\t\tHYPERTABLE_STATUS_OSM_CHUNK_NONCONTIGUOUS))\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t/*\n\t\t\t\t * If we only have 1 child node there is no need for the\n\t\t\t\t * ordered append optimization. We might still benefit from\n\t\t\t\t * a ChunkAppend node here due to runtime chunk exclusion\n\t\t\t\t * when we have non-immutable constraints.\n\t\t\t\t */\n\t\t\t\tif (list_length(merge->subpaths) == 1)\n\t\t\t\t{\n\t\t\t\t\tforeach (lc, rel->baserestrictinfo)\n\t\t\t\t\t{\n\t\t\t\t\t\tRestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);\n\n\t\t\t\t\t\tif (contain_mutable_functions((Node *) rinfo->clause) ||\n\t\t\t\t\t\t\tts_contains_external_param((Node *) rinfo->clause) ||\n\t\t\t\t\t\t\tts_contains_join_param((Node *) rinfo->clause))\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tpk = linitial_node(PathKey, path->pathkeys);\n\n\t\t\t\t/*\n\t\t\t\t * Check PathKey is compatible with Ordered Append ordering\n\t\t\t\t * we created when expanding hypertable.\n\t\t\t\t * Even though ordered is true on the RelOptInfo we have to\n\t\t\t\t * double check that current Path fulfills requirements for\n\t\t\t\t * Ordered Append transformation because the RelOptInfo may\n\t\t\t\t * be used for multiple Paths.\n\t\t\t\t */\n\t\t\t\tExpr *em_expr = ts_find_em_expr_for_rel(pk->pk_eclass, rel);\n\n\t\t\t\t/*\n\t\t\t\t * If this is a join the ordering information might not be\n\t\t\t\t * for the current rel and have no EquivalenceMember.\n\t\t\t\t */\n\n\t\t\t\tif (!em_expr)\n\t\t\t\t\treturn false;\n\n\t\t\t\tif (IsA(em_expr, Var) && castNode(Var, em_expr)->varattno == order_attno)\n\t\t\t\t\treturn true;\n\n\t\t\t\tif (IsA(em_expr, FuncExpr) && list_length(path->pathkeys) == 1)\n\t\t\t\t{\n\t\t\t\t\tFuncExpr *func = castNode(FuncExpr, em_expr);\n\t\t\t\t\tFuncInfo *info = ts_func_cache_get_bucketing_func(func->funcid);\n\t\t\t\t\tExpr *transformed;\n\n\t\t\t\t\tif (info && info->sort_transform)\n\t\t\t\t\t{\n\t\t\t\t\t\ttransformed = info->sort_transform(func);\n\t\t\t\t\t\tif (IsA(transformed, Var) &&\n\t\t\t\t\t\t\tcastNode(Var, transformed)->varattno == order_attno)\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\nstatic inline bool\nshould_constraint_aware_append(PlannerInfo *root, Hypertable *ht, Path *path)\n{\n\t/* Constraint-aware append currently expects children that scans a real\n\t * \"relation\" (e.g., not an \"upper\" relation).\n\t */\n\tif (root->parse->commandType != CMD_SELECT)\n\t\treturn false;\n\n\treturn ts_constraint_aware_append_possible(path);\n}\n\nstatic bool\nrte_should_expand(const RangeTblEntry *rte)\n{\n\tbool is_hypertable = ts_rte_is_hypertable(rte);\n\n\treturn is_hypertable && !rte->inh && ts_rte_is_marked_for_expansion(rte) &&\n\t\t   rte->relkind != RELKIND_PARTITIONED_TABLE;\n}\n\nstatic void\nexpand_hypertables(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte)\n{\n\tbool set_pathlist_for_current_rel = false;\n\tdouble total_pages;\n\tbool reenabled_inheritance = false;\n\n\tfor (int i = 1; i < root->simple_rel_array_size; i++)\n\t{\n\t\tRangeTblEntry *in_rte = root->simple_rte_array[i];\n\n#if PG18_GE\n\t\t/* RTE could be removed due to self-join\n\t\t * elimination optimization.\n\t\t *\n\t\t * https://github.com/postgres/postgres/commit/5f6f95\n\t\t */\n\t\tif (!in_rte)\n\t\t\tcontinue;\n#endif\n\n\t\tif (rte_should_expand(in_rte) && root->simple_rel_array[i])\n\t\t{\n\t\t\tRelOptInfo *in_rel = root->simple_rel_array[i];\n\t\t\tHypertable *ht = ts_planner_get_hypertable(in_rte->relid, CACHE_FLAG_NOCREATE);\n\n\t\t\tAssert(ht != NULL && in_rel != NULL);\n\t\t\tts_plan_expand_hypertable_chunks(ht, root, in_rel, in_rte->ctename != TS_FK_EXPAND);\n\n\t\t\tin_rte->inh = true;\n\t\t\treenabled_inheritance = true;\n\t\t\t/* Redo set_rel_consider_parallel, as results of the call may no longer be valid\n\t\t\t * here (due to adding more tables to the set of tables under consideration here).\n\t\t\t * This is especially true if dealing with foreign data wrappers. */\n\n\t\t\t/*\n\t\t\t * An entry of reloptkind RELOPT_OTHER_MEMBER_REL might still\n\t\t\t * be a hypertable here if it was pulled up from a subquery\n\t\t\t * as happens with UNION ALL for example.\n\t\t\t */\n\t\t\tif (in_rel->reloptkind == RELOPT_BASEREL ||\n\t\t\t\tin_rel->reloptkind == RELOPT_OTHER_MEMBER_REL)\n\t\t\t{\n\t\t\t\tAssert(in_rte->relkind == RELKIND_RELATION);\n\t\t\t\tts_set_rel_size(root, in_rel, i, in_rte);\n\t\t\t}\n\n\t\t\t/* if we're activating inheritance during a hypertable's pathlist\n\t\t\t * creation then we're past the point at which postgres will add\n\t\t\t * paths for the children, and we have to do it ourselves. We delay\n\t\t\t * the actual setting of the pathlists until after this loop,\n\t\t\t * because set_append_rel_pathlist will eventually call this hook again.\n\t\t\t */\n\t\t\tif (in_rte == rte)\n\t\t\t{\n\t\t\t\tAssert(rti == (Index) i);\n\t\t\t\tset_pathlist_for_current_rel = true;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!reenabled_inheritance)\n\t\treturn;\n\n\ttotal_pages = 0;\n\tfor (int i = 1; i < root->simple_rel_array_size; i++)\n\t{\n\t\tRelOptInfo *brel = root->simple_rel_array[i];\n\n\t\tif (brel == NULL)\n\t\t\tcontinue;\n\n\t\tAssert(brel->relid == (Index) i); /* sanity check on array */\n\n\t\tif (IS_DUMMY_REL(brel))\n\t\t\tcontinue;\n\n\t\tif (IS_SIMPLE_REL(brel))\n\t\t\ttotal_pages += (double) brel->pages;\n\t}\n\troot->total_table_pages = total_pages;\n\n\tif (set_pathlist_for_current_rel)\n\t{\n\t\trel->pathlist = NIL;\n\t\trel->partial_pathlist = NIL;\n\n\t\tts_set_append_rel_pathlist(root, rel, rti, rte);\n\t}\n}\n\nstatic void\napply_optimizations(PlannerInfo *root, TsRelType reltype, RelOptInfo *rel, RangeTblEntry *rte,\n\t\t\t\t\tHypertable *ht)\n{\n\tif (!ts_guc_enable_optimizations)\n\t\treturn;\n\n\tswitch (reltype)\n\t{\n\t\tcase TS_REL_HYPERTABLE_CHILD:\n\t\t\t/* empty table so nothing to optimize */\n\t\t\tbreak;\n\t\tcase TS_REL_CHUNK_STANDALONE:\n\t\tcase TS_REL_CHUNK_CHILD:\n\t\t{\n\t\t\t/*\n\t\t\t * Since the sort optimization adds new paths to the rel it has\n\t\t\t * to happen before any optimizations that replace pathlist.\n\t\t\t */\n\t\t\tList *transformed_query_pathkeys = ts_sort_transform_get_pathkeys(root, rel, rte, ht);\n\t\t\tif (transformed_query_pathkeys != NIL)\n\t\t\t{\n\t\t\t\tList *orig_query_pathkeys = root->query_pathkeys;\n\t\t\t\troot->query_pathkeys = transformed_query_pathkeys;\n\n\t\t\t\t/* Create index paths with transformed pathkeys */\n\t\t\t\tcreate_index_paths(root, rel);\n\n\t\t\t\t/*\n\t\t\t\t * Call the TSL hooks with the transformed pathkeys as well, so\n\t\t\t\t * that the decompression paths also use this optimization.\n\t\t\t\t */\n\t\t\t\tif (ts_cm_functions->set_rel_pathlist_query != NULL)\n\t\t\t\t\tts_cm_functions->set_rel_pathlist_query(root, rel, rel->relid, rte, ht);\n\n\t\t\t\troot->query_pathkeys = orig_query_pathkeys;\n\n\t\t\t\t/*\n\t\t\t\t * change returned paths to use original pathkeys. have to go through\n\t\t\t\t * all paths since create_index_paths might have modified existing\n\t\t\t\t * pathkey. Always safe to do transform since ordering of\n\t\t\t\t * transformed_query_pathkey implements ordering of\n\t\t\t\t * orig_query_pathkeys.\n\t\t\t\t */\n\t\t\t\tts_sort_transform_replace_pathkeys(rel->pathlist,\n\t\t\t\t\t\t\t\t\t\t\t\t   transformed_query_pathkeys,\n\t\t\t\t\t\t\t\t\t\t\t\t   orig_query_pathkeys);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (ts_cm_functions->set_rel_pathlist_query != NULL)\n\t\t\t\t\tts_cm_functions->set_rel_pathlist_query(root, rel, rel->relid, rte, ht);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\n\tif (reltype == TS_REL_HYPERTABLE &&\n\t\t(root->parse->commandType == CMD_SELECT || root->parse->commandType == CMD_DELETE ||\n\t\t root->parse->commandType == CMD_UPDATE))\n\t{\n\t\tTimescaleDBPrivate *private = ts_get_private_reloptinfo(rel);\n\t\tbool ordered = private->appends_ordered;\n\t\tint order_attno = private->order_attno;\n\t\tList *nested_oids = private->nested_oids;\n\t\tListCell *lc;\n\n\t\tAssert(ht != NULL);\n\n\t\tforeach (lc, rel->pathlist)\n\t\t{\n\t\t\tPath **pathptr = (Path **) &lfirst(lc);\n\n\t\t\tswitch (nodeTag(*pathptr))\n\t\t\t{\n\t\t\t\tcase T_AppendPath:\n\t\t\t\tcase T_MergeAppendPath:\n\t\t\t\t\tif (should_chunk_append(ht, root, rel, *pathptr, ordered, order_attno))\n\t\t\t\t\t\t*pathptr = ts_chunk_append_path_create(root,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   ht,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   *pathptr,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   false,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   ordered,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   nested_oids);\n\t\t\t\t\telse if (should_constraint_aware_append(root, ht, *pathptr))\n\t\t\t\t\t\t*pathptr = ts_constraint_aware_append_path_create(root, *pathptr);\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tforeach (lc, rel->partial_pathlist)\n\t\t{\n\t\t\tPath **pathptr = (Path **) &lfirst(lc);\n\n\t\t\tswitch (nodeTag(*pathptr))\n\t\t\t{\n\t\t\t\tcase T_AppendPath:\n\t\t\t\tcase T_MergeAppendPath:\n\t\t\t\t\tif (should_chunk_append(ht, root, rel, *pathptr, false, 0))\n\t\t\t\t\t\t*pathptr =\n\t\t\t\t\t\t\tts_chunk_append_path_create(root, rel, ht, *pathptr, true, false, NIL);\n\t\t\t\t\telse if (should_constraint_aware_append(root, ht, *pathptr))\n\t\t\t\t\t\t*pathptr = ts_constraint_aware_append_path_create(root, *pathptr);\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic bool\nvalid_hook_call(void)\n{\n\treturn ts_extension_is_loaded_and_not_upgrading() && planner_hcache_exists();\n}\n\nstatic bool\ndml_involves_hypertable(PlannerInfo *root, Hypertable *ht, Index rti)\n{\n\tIndex result_rti = root->parse->resultRelation;\n\tRangeTblEntry *result_rte = planner_rt_fetch(result_rti, root);\n\n\treturn result_rti == rti || ht->main_table_relid == result_rte->relid;\n}\n\nstatic void\ntimescaledb_set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte)\n{\n\tTsRelType reltype;\n\tHypertable *ht;\n\n\t/*\n\t * Quick exit if this is a relation we're not interested in.\n\t *\n\t * If the rtekind is a named tuple store, it is a named tuple store *for*\n\t * the relation rte->relid (e.g., a transition table for a trigger), but\n\t * not the relation itself.\n\t */\n\tif (!valid_hook_call() || rte->rtekind == RTE_NAMEDTUPLESTORE || !OidIsValid(rte->relid) ||\n\t\tIS_DUMMY_REL(rel))\n\t{\n\t\tif (prev_set_rel_pathlist_hook != NULL)\n\t\t\t(*prev_set_rel_pathlist_hook)(root, rel, rti, rte);\n\t\treturn;\n\t}\n\n\treltype = ts_classify_relation(root, rel, &ht);\n\n\t/* Check for unexpanded hypertable */\n\tif (!rte->inh && ts_rte_is_marked_for_expansion(rte))\n\t\texpand_hypertables(root, rel, rti, rte);\n\n\tif (ts_guc_enable_optimizations)\n\t\tts_planner_constraint_cleanup(root, rel);\n\n\t/* Call other extensions. Do it after table expansion. */\n\tif (prev_set_rel_pathlist_hook != NULL)\n\t\t(*prev_set_rel_pathlist_hook)(root, rel, rti, rte);\n\n\tswitch (reltype)\n\t{\n\t\tcase TS_REL_HYPERTABLE_CHILD:\n\t\t\tif (ts_guc_enable_optimizations && IS_UPDL_CMD(root->parse))\n\t\t\t\tts_planner_constraint_cleanup(root, rel);\n\n\t\t\tbreak;\n\t\tcase TS_REL_CHUNK_STANDALONE:\n\t\tcase TS_REL_CHUNK_CHILD:\n\t\t\t/* Check for UPDATE/DELETE/MERGE (DML) on compressed chunks */\n\t\t\tif (IS_UPDL_CMD(root->parse) && dml_involves_hypertable(root, ht, rti))\n\t\t\t{\n\t\t\t\tif (ts_cm_functions->set_rel_pathlist_dml != NULL)\n\t\t\t\t\tts_cm_functions->set_rel_pathlist_dml(root, rel, rti, rte, ht);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t/*\n\t\t\t * For MERGE command if there is an UPDATE or DELETE action, then\n\t\t\t * do not allow this to succeed on compressed chunks\n\t\t\t */\n\t\t\tif (root->parse->commandType == CMD_MERGE && dml_involves_hypertable(root, ht, rti))\n\t\t\t{\n\t\t\t\tListCell *ml;\n\t\t\t\tforeach (ml, root->parse->mergeActionList)\n\t\t\t\t{\n\t\t\t\t\tMergeAction *action = (MergeAction *) lfirst(ml);\n\t\t\t\t\tif (action->commandType == CMD_UPDATE || action->commandType == CMD_DELETE)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (ts_cm_functions->set_rel_pathlist_dml != NULL)\n\t\t\t\t\t\t\tts_cm_functions->set_rel_pathlist_dml(root, rel, rti, rte, ht);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tTS_FALLTHROUGH;\n\t\tdefault:\n\t\t\t/*\n\t\t\t * Set the indexlist for a hypertable parent to NIL since we\n\t\t\t * should not try to do any index scans on hypertable parents,\n\t\t\t * similar to how it works for partitioned tables.\n\t\t\t *\n\t\t\t * This can happen when building a merge join path and computing\n\t\t\t * cost for it. See get_actual_variable_range().\n\t\t\t *\n\t\t\t * This has to be after the hypertable is expanded, since the\n\t\t\t * indexlist is used during hypertable expansion.\n\t\t\t */\n\t\t\tif (reltype == TS_REL_HYPERTABLE)\n\t\t\t\trel->indexlist = NIL;\n\t\t\tapply_optimizations(root, reltype, rel, rte, ht);\n\t\t\tbreak;\n\t}\n}\n\n/* This hook is meant to editorialize about the information the planner gets\n * about a relation. We use it to attach our own metadata to hypertable and\n * chunk relations that we need during planning. We also expand hypertables\n * here. */\nstatic void\ntimescaledb_get_relation_info_hook(PlannerInfo *root, Oid relation_objectid, bool inhparent,\n\t\t\t\t\t\t\t\t   RelOptInfo *rel)\n{\n\tif (prev_get_relation_info_hook != NULL)\n\t\tprev_get_relation_info_hook(root, relation_objectid, inhparent, rel);\n\n\tif (!valid_hook_call())\n\t\treturn;\n\n\tRangeTblEntry *rte = planner_rt_fetch(rel->relid, root);\n\tQuery *query = root->parse;\n\tHypertable *ht;\n\tconst TsRelType type = ts_classify_relation(root, rel, &ht);\n\tAclMode requiredPerms = 0;\n\n#if PG16_LT\n\trequiredPerms = rte->requiredPerms;\n#else\n\tif (rte->perminfoindex > 0)\n\t{\n\t\tRTEPermissionInfo *perminfo = getRTEPermissionInfo(query->rteperminfos, rte);\n\t\trequiredPerms = perminfo->requiredPerms;\n\t}\n#endif\n\n\tswitch (type)\n\t{\n\t\tcase TS_REL_HYPERTABLE:\n\t\t{\n\t\t\t/* Mark hypertable RTEs we'd like to expand ourselves.\n\t\t\t * Hypertables inside inlineable functions don't get marked during the query\n\t\t\t * preprocessing step. Therefore we do an extra try here. However, we need to\n\t\t\t * be careful for UPDATE/DELETE as Postgres (in at least version 12) plans them\n\t\t\t * in a complicated way (see planner.c:inheritance_planner). First, it runs the\n\t\t\t * UPDATE/DELETE through the planner as a simulated SELECT. It uses the results\n\t\t\t * of this fake planning to adapt its own UPDATE/DELETE plan. Then it's planned\n\t\t\t * a second time as a real UPDATE/DELETE, but with requiredPerms set to 0, as it\n\t\t\t * assumes permission checking has been done already during the first planner call.\n\t\t\t * We don't want to touch the UPDATE/DELETEs, so we need to check all the regular\n\t\t\t * conditions here that are checked during preprocess_query, as well as the\n\t\t\t * condition that requiredPerms is not requiring UPDATE/DELETE on this rel.\n\t\t\t */\n\t\t\tif (ts_guc_enable_optimizations && ts_guc_enable_constraint_exclusion && inhparent &&\n\t\t\t\trte->ctename == NULL && !IS_UPDL_CMD(query) && query->resultRelation == 0 &&\n\t\t\t\t(requiredPerms & (ACL_UPDATE | ACL_DELETE)) == 0)\n\t\t\t{\n\t\t\t\trte_mark_for_expansion(rte);\n\t\t\t}\n\t\t\tts_create_private_reloptinfo(rel);\n\t\t\tts_plan_expand_timebucket_annotate(root, rel);\n\t\t\tbreak;\n\t\t}\n\t\tcase TS_REL_CHUNK_STANDALONE:\n\t\tcase TS_REL_CHUNK_CHILD:\n\t\t\tts_create_private_reloptinfo(rel);\n\n\t\t\t/*\n\t\t\t * We don't want to plan index scans on empty uncompressed tables of\n\t\t\t * fully compressed chunks. It takes a lot of time, and these tables\n\t\t\t * are empty anyway. Just reset the indexlist in this case. For\n\t\t\t * uncompressed or partially compressed chunks, the uncompressed\n\t\t\t * tables are not empty, so we plan the index scans as usual.\n\t\t\t *\n\t\t\t * Normally the index list is reset in ts_set_append_rel_pathlist(),\n\t\t\t * based on the Chunk struct cached by our hypertable expansion, but\n\t\t\t * in cases when these functions don't run, we have to do it here.\n\t\t\t */\n\t\t\tconst bool use_columnar_scan =\n\t\t\t\tts_guc_enable_columnarscan && TS_HYPERTABLE_HAS_COMPRESSION_TABLE(ht);\n\t\t\tconst bool is_standalone_chunk = (type == TS_REL_CHUNK_STANDALONE) &&\n\t\t\t\t\t\t\t\t\t\t\t !TS_HYPERTABLE_IS_INTERNAL_COMPRESSION_TABLE(ht);\n\t\t\tconst bool is_child_chunk_in_update =\n\t\t\t\t(type == TS_REL_CHUNK_CHILD) && IS_UPDL_CMD(query);\n\n\t\t\tif (use_columnar_scan && (is_standalone_chunk || is_child_chunk_in_update))\n\t\t\t{\n\t\t\t\tconst Chunk *chunk = ts_planner_chunk_fetch(root, rel);\n\n\t\t\t\tif (!ts_chunk_is_partial(chunk) && ts_chunk_is_compressed(chunk))\n\t\t\t\t{\n\t\t\t\t\trel->indexlist = NIL;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\tcase TS_REL_HYPERTABLE_CHILD:\n\t\t\t/* When postgres expands an inheritance tree it also adds the\n\t\t\t * parent hypertable as child relation. Since for a hypertable the\n\t\t\t * parent will never have any data we can mark this relation as\n\t\t\t * dummy relation so it gets ignored in later steps. This is only\n\t\t\t * relevant for code paths that use the postgres inheritance code\n\t\t\t * as we don't include the hypertable as child when expanding the\n\t\t\t * hypertable ourself.\n\t\t\t */\n\t\t\tif (IS_UPDL_CMD(root->parse))\n\t\t\t\tmark_dummy_rel(rel);\n\t\t\tbreak;\n\t\tcase TS_REL_OTHER:\n\t\t\tbreak;\n\t}\n}\n\nstatic bool\njoin_involves_hypertable(const PlannerInfo *root, const RelOptInfo *rel)\n{\n\tint relid = -1;\n\n\twhile ((relid = bms_next_member(rel->relids, relid)) >= 0)\n\t{\n\t\tconst RangeTblEntry *rte = planner_rt_fetch(relid, root);\n\n\t\tif (rte != NULL)\n\t\t\t/* This might give a false positive for chunks in case of PostgreSQL\n\t\t\t * expansion since the ctename is copied from the parent hypertable\n\t\t\t * to the chunk */\n\t\t\treturn ts_rte_is_marked_for_expansion(rte);\n\t}\n\treturn false;\n}\n\nstatic bool\ninvolves_hypertable(PlannerInfo *root, RelOptInfo *rel)\n{\n\tif (rel->reloptkind == RELOPT_JOINREL)\n\t\treturn join_involves_hypertable(root, rel);\n\n\tHypertable *ht;\n\treturn ts_classify_relation(root, rel, &ht) == TS_REL_HYPERTABLE;\n}\n\n/*\n * Replace ModifyTablePath paths on hypertables.\n *\n * From the ModifyTable description: \"Each ModifyTable node contains\n * a list of one or more subplans, much like an Append node.  There\n * is one subplan per result relation.\"\n *\n * The subplans produce the tuples for INSERT, while the result relation is the\n * table we'd like to insert into.\n *\n * Conceptually, the plan modification looks like this:\n *\n * Original plan:\n *\n *\t\t  ^\n *\t\t  |\n *\t[ ModifyTable ] -> resultRelation\n *\t\t  ^\n *\t\t  | Tuple\n *\t\t  |\n *\t  [ subplan ]\n *\n *\n * Modified plan:\n *\n *\t[ ModifyHypertable ]\n *\t\t  ^\n *\t\t  |\n *\t[ ModifyTable ] -> resultRelation\n *\t\t  ^\n *\t\t  | Tuple\n *\t\t  |\n *\t  [ subplan ]\n *\n */\nstatic List *\nreplace_modify_hypertable_paths(PlannerInfo *root, List *pathlist, RelOptInfo *input_rel)\n{\n\tList *new_pathlist = NIL;\n\tListCell *lc;\n\n\tforeach (lc, pathlist)\n\t{\n\t\tPath *path = lfirst(lc);\n\n\t\tif (IsA(path, ModifyTablePath))\n\t\t{\n\t\t\tModifyTablePath *mt = castNode(ModifyTablePath, path);\n\t\t\tRangeTblEntry *rte = planner_rt_fetch(mt->nominalRelation, root);\n\t\t\tHypertable *ht = ts_planner_get_hypertable(rte->relid, CACHE_FLAG_CHECK);\n\n\t\t\t/* Direct INSERT into internal compressed hypertable is not supported.\n\t\t\t * Compressed chunks have no dimensions so we could not do tuple routing.\n\t\t\t * Additionally internal compressed hypertable has no columns so you\n\t\t\t * couldn't even insert any actual data.\n\t\t\t */\n\t\t\tif (ht && ht->fd.compression_state == HypertableInternalCompressionTable)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t errmsg(\"direct insert into internal compressed hypertable is not \"\n\t\t\t\t\t\t\t\t\"supported\")));\n\n\t\t\t/* Check for DML on chunk directly */\n\t\t\tif (!ht)\n\t\t\t{\n\t\t\t\tChunk *chunk = ts_chunk_get_by_relid(rte->relid, false);\n\t\t\t\tif (!chunk)\n\t\t\t\t{\n\t\t\t\t\t/* Not a hypertable or chunk, continue */\n\t\t\t\t\tnew_pathlist = lappend(new_pathlist, path);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tht = ts_hypertable_get_by_id(chunk->fd.hypertable_id);\n\t\t\t\tif (ht->fd.compression_state == HypertableInternalCompressionTable)\n\t\t\t\t{\n\t\t\t\t\t/*\n\t\t\t\t\t * For operations on internal compressed chunks we block modifications\n\t\t\t\t\t * if the chunk belongs to a frozen chunk.\n\t\t\t\t\t * Direct modifications of uncompressed chunks is intercepted by chunk\n\t\t\t\t\t * tuple routing.\n\t\t\t\t\t * In all other cases of direct modification of chunks we dont interfere\n\t\t\t\t\t * and do not add a ModifyHypertable node.\n\t\t\t\t\t */\n\t\t\t\t\tChunk *uncompressed = ts_chunk_get_compressed_chunk_parent(chunk);\n\t\t\t\t\tif (ts_chunk_is_frozen(uncompressed))\n\t\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t\t\t\t\t errmsg(\"cannot modify compressed chunk belonging to a frozen \"\n\t\t\t\t\t\t\t\t\t\t\"chunk\")));\n\n\t\t\t\t\tnew_pathlist = lappend(new_pathlist, path);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tswitch (mt->operation)\n\t\t\t{\n\t\t\t\tcase CMD_INSERT:\n\t\t\t\tcase CMD_UPDATE:\n\t\t\t\tcase CMD_DELETE:\n\t\t\t\t{\n\t\t\t\t\tpath = ts_modify_hypertable_path_create(root, mt, input_rel);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase CMD_MERGE:\n\t\t\t\t{\n\t\t\t\t\tList *firstMergeActionList = linitial(mt->mergeActionLists);\n\t\t\t\t\tListCell *l;\n\t\t\t\t\t/*\n\t\t\t\t\t * Iterate over merge action to check if there is an INSERT sql.\n\t\t\t\t\t * If so, then add ModifyHypertable node.\n\t\t\t\t\t */\n\t\t\t\t\tforeach (l, firstMergeActionList)\n\t\t\t\t\t{\n\t\t\t\t\t\tMergeAction *action = (MergeAction *) lfirst(l);\n\t\t\t\t\t\tif (action->commandType == CMD_INSERT)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath = ts_modify_hypertable_path_create(root, mt, input_rel);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tnew_pathlist = lappend(new_pathlist, path);\n\t}\n\n\treturn new_pathlist;\n}\n\nstatic void\ntimescaledb_create_upper_paths_hook(PlannerInfo *root, UpperRelationKind stage,\n\t\t\t\t\t\t\t\t\tRelOptInfo *input_rel, RelOptInfo *output_rel, void *extra)\n{\n\tQuery *parse = root->parse;\n\tTsRelType reltype = TS_REL_OTHER;\n\tHypertable *ht = NULL;\n\n\tif (prev_create_upper_paths_hook != NULL)\n\t\tprev_create_upper_paths_hook(root, stage, input_rel, output_rel, extra);\n\n\tif (!ts_extension_is_loaded_and_not_upgrading())\n\t\treturn;\n\n\tif (input_rel != NULL)\n\t\treltype = ts_classify_relation(root, input_rel, &ht);\n\n\tif (output_rel != NULL)\n\t{\n\t\t/* Modify for INSERTs on a hypertable */\n\t\tif (output_rel->pathlist != NIL)\n\t\t\toutput_rel->pathlist =\n\t\t\t\treplace_modify_hypertable_paths(root, output_rel->pathlist, input_rel);\n\t}\n\n\tif (stage == UPPERREL_GROUP_AGG && output_rel != NULL && ts_guc_enable_optimizations &&\n\t\tinput_rel != NULL && !IS_DUMMY_REL(input_rel) && involves_hypertable(root, input_rel))\n\t{\n\t\tif (parse->hasAggs)\n\t\t\tts_preprocess_first_last_aggregates(root, root->processed_tlist);\n\t}\n\n\tif (ts_cm_functions->create_upper_paths_hook != NULL)\n\t\tts_cm_functions\n\t\t\t->create_upper_paths_hook(root, stage, input_rel, output_rel, reltype, ht, extra);\n}\n\nstatic bool\ncontains_join_param_walker(Node *node, void *context)\n{\n\tif (node == NULL)\n\t{\n\t\treturn false;\n\t}\n\n\tif (IsA(node, Param) && castNode(Param, node)->paramkind == PARAM_EXEC)\n\t\treturn true;\n\n\treturn expression_tree_walker(node, contains_join_param_walker, context);\n}\n\nbool\nts_contains_join_param(Node *node)\n{\n\treturn contains_join_param_walker(node, NULL);\n}\n\nstatic bool\ncontains_external_param_walker(Node *node, void *context)\n{\n\tif (node == NULL)\n\t{\n\t\treturn false;\n\t}\n\n\tif (IsA(node, Param) && castNode(Param, node)->paramkind == PARAM_EXTERN)\n\t\treturn true;\n\n\treturn expression_tree_walker(node, contains_external_param_walker, context);\n}\n\nbool\nts_contains_external_param(Node *node)\n{\n\treturn contains_external_param_walker(node, NULL);\n}\n\nstatic List *\nfill_missing_groupclause(List *new_groupclause, List *orig_groupclause)\n{\n\tif (new_groupclause != NIL)\n\t{\n\t\tListCell *gl;\n\t\tforeach (gl, orig_groupclause)\n\t\t{\n\t\t\tSortGroupClause *gc = lfirst_node(SortGroupClause, gl);\n\n\t\t\tif (list_member_ptr(new_groupclause, gc))\n\t\t\t\tcontinue; /* already in list */\n\t\t\tnew_groupclause = lappend(new_groupclause, gc);\n\t\t}\n\t}\n\treturn new_groupclause;\n}\n\nstatic bool\ncheck_cagg_view_rte(RangeTblEntry *rte)\n{\n\tContinuousAgg *cagg = NULL;\n\tListCell *rtlc;\n\tbool found = false;\n\tQuery *viewq = rte->subquery;\n\tAssert(rte->rtekind == RTE_SUBQUERY);\n\n\tif (list_length(viewq->rtable) != 3) /* a view has 3 entries */\n\t{\n\t\treturn false;\n\t}\n\n\t/* should cache this information for cont. aggregates */\n\tforeach (rtlc, viewq->rtable)\n\t{\n\t\tRangeTblEntry *rte = lfirst_node(RangeTblEntry, rtlc);\n\n\t\tif (!OidIsValid(rte->relid))\n\t\t\tbreak;\n\n\t\tcagg = ts_continuous_agg_find_by_relid(rte->relid);\n\t\tif (cagg != NULL)\n\t\t\tfound = true;\n\t}\n\treturn found;\n}\n\n/* Note that it modifies the passed in Query\n* select * from (select a, b, max(c), min(d) from ...\n\t\t\t\t group by a, b)\n  order by b;\n* is transformed as\n* SELECT * from (select a, b, max(c), min(d) from ..\n*                 group by B desc, A  <------ note the change in order here\n*              )\n*  order by b desc;\n*  we transform only if order by is a subset of group-by\n* transformation is applicable only to continuous aggregates\n* Parameters:\n* subq_rte - rte for subquery (inner query that will be modified)\n* outer_sortcl -- outer query's sort clause\n* outer_tlist - outer query's target list\n*/\nstatic void\ncagg_reorder_groupby_clause(RangeTblEntry *subq_rte, Index rtno, List *outer_sortcl,\n\t\t\t\t\t\t\tList *outer_tlist)\n{\n\tbool not_found = true;\n\tQuery *subq;\n\tListCell *lc;\n\tAssert(subq_rte->rtekind == RTE_SUBQUERY);\n\tsubq = subq_rte->subquery;\n\tif (outer_sortcl && subq->groupClause && subq->sortClause == NIL &&\n\t\tcheck_cagg_view_rte(subq_rte))\n\t{\n\t\tList *new_groupclause = NIL;\n\t\t/* we are going to modify this. so make a copy and use it\n\t\t if we replace */\n\t\tList *subq_groupclause_copy = copyObject(subq->groupClause);\n\t\tforeach (lc, outer_sortcl)\n\t\t{\n\t\t\tSortGroupClause *outer_sc = (SortGroupClause *) lfirst(lc);\n\t\t\tTargetEntry *outer_tle = get_sortgroupclause_tle(outer_sc, outer_tlist);\n\t\t\tnot_found = true;\n\t\t\tif (IsA(outer_tle->expr, Var) && ((Index) ((Var *) outer_tle->expr)->varno == rtno))\n\t\t\t{\n\t\t\t\tint outer_attno = ((Var *) outer_tle->expr)->varattno;\n\t\t\t\tTargetEntry *subq_tle = list_nth(subq->targetList, outer_attno - 1);\n\t\t\t\tif (subq_tle->ressortgroupref > 0)\n\t\t\t\t{\n\t\t\t\t\t/* get group clause corresponding to this */\n\t\t\t\t\tSortGroupClause *subq_gclause =\n\t\t\t\t\t\tget_sortgroupref_clause(subq_tle->ressortgroupref, subq_groupclause_copy);\n\t\t\t\t\tsubq_gclause->sortop = outer_sc->sortop;\n\t\t\t\t\tsubq_gclause->nulls_first = outer_sc->nulls_first;\n#if PG18_GE\n\t\t\t\t\t/* Track sort direction in SortGroupClause\n\t\t\t\t\t * https://github.com/postgres/postgres/commit/0d2aa4d4\n\t\t\t\t\t */\n\t\t\t\t\tsubq_gclause->reverse_sort = outer_sc->reverse_sort;\n#endif\n\t\t\t\t\tAssert(subq_gclause->eqop == outer_sc->eqop);\n\t\t\t\t\tnew_groupclause = lappend(new_groupclause, subq_gclause);\n\t\t\t\t\tnot_found = false;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (not_found)\n\t\t\t\tbreak;\n\t\t}\n\t\t/* all order by found in group by clause */\n\t\tif (new_groupclause != NIL && not_found == false)\n\t\t{\n\t\t\t/* use new groupby clause for this subquery/view */\n\t\t\tsubq->groupClause = fill_missing_groupclause(new_groupclause, subq_groupclause_copy);\n\t\t}\n\t}\n}\n\nvoid\n_planner_init(void)\n{\n\tprev_planner_hook = planner_hook;\n\tplanner_hook = timescaledb_planner;\n\tprev_set_rel_pathlist_hook = set_rel_pathlist_hook;\n\tset_rel_pathlist_hook = timescaledb_set_rel_pathlist;\n\n\tprev_get_relation_info_hook = get_relation_info_hook;\n\tget_relation_info_hook = timescaledb_get_relation_info_hook;\n\n\tprev_create_upper_paths_hook = create_upper_paths_hook;\n\tcreate_upper_paths_hook = timescaledb_create_upper_paths_hook;\n}\n\nvoid\n_planner_fini(void)\n{\n\tplanner_hook = prev_planner_hook;\n\tset_rel_pathlist_hook = prev_set_rel_pathlist_hook;\n\tget_relation_info_hook = prev_get_relation_info_hook;\n\tcreate_upper_paths_hook = prev_create_upper_paths_hook;\n}\n"
  },
  {
    "path": "src/planner/planner.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <nodes/parsenodes.h>\n#include <nodes/pathnodes.h>\n#include <nodes/pg_list.h>\n#include <parser/parsetree.h>\n\n#include \"chunk.h\"\n#include \"export.h\"\n#include \"guc.h\"\n#include \"hypertable.h\"\n#include <storage/lockdefs.h>\n\n/*\n * Constraints created during planning to improve chunk exclusion\n * will be marked with this value as location so they can be easily\n * identified and removed when they are no longer needed.\n * Removal happens in timescaledb_set_rel_pathlist hook.\n */\n#define PLANNER_LOCATION_MAGIC -29811\n\ntypedef struct Chunk Chunk;\ntypedef struct Hypertable Hypertable;\ntypedef struct TimescaleDBPrivate\n{\n\tbool appends_ordered;\n\t/* attno of the time dimension in the parent table if appends are ordered */\n\tint order_attno;\n\tList *nested_oids;\n\tList *chunk_oids;\n\n\t/* Cached chunk data for the chunk relinfo. */\n\tChunk *cached_chunk_struct;\n\n\t/* Cached equivalence members for compressed chunks. List of (EC, EM) Lists. */\n\tList *compressed_ec_em_pairs;\n} TimescaleDBPrivate;\n\nextern TSDLLEXPORT bool ts_rte_is_hypertable(const RangeTblEntry *rte);\nextern TSDLLEXPORT bool ts_rte_is_marked_for_expansion(const RangeTblEntry *rte);\nextern TSDLLEXPORT bool ts_contains_external_param(Node *node);\nextern TSDLLEXPORT bool ts_contains_join_param(Node *node);\n\nstatic inline TimescaleDBPrivate *\nts_create_private_reloptinfo(RelOptInfo *rel)\n{\n\tAssert(rel->fdw_private == NULL);\n\trel->fdw_private = palloc0(sizeof(TimescaleDBPrivate));\n\treturn rel->fdw_private;\n}\n\nstatic inline TimescaleDBPrivate *\nts_get_private_reloptinfo(RelOptInfo *rel)\n{\n\t/* If rel->fdw_private is not set up here it means the rel got missclassified\n\t * and did not get expanded by our code but by postgres native code.\n\t * This is not a problem by itself, but probably an oversight on our part.\n\t */\n\tAssert(rel->fdw_private);\n\treturn rel->fdw_private ? rel->fdw_private : ts_create_private_reloptinfo(rel);\n}\n\n/*\n * TsRelType provides consistent classification of planned relations across\n * planner hooks.\n */\ntypedef enum TsRelType\n{\n\tTS_REL_HYPERTABLE,\t\t /* A hypertable with no parent */\n\tTS_REL_CHUNK_STANDALONE, /* Chunk with no parent (i.e., it's part of the\n\t\t\t\t\t\t\t  * plan as a standalone table. For example,\n\t\t\t\t\t\t\t  * querying the chunk directly and not via the\n\t\t\t\t\t\t\t  * parent hypertable). */\n\tTS_REL_HYPERTABLE_CHILD, /* Self child. With PostgreSQL's table expansion,\n\t\t\t\t\t\t\t  * the root table is expanded as a child of\n\t\t\t\t\t\t\t  * itself. This happens when our expansion code\n\t\t\t\t\t\t\t  * is turned off. */\n\tTS_REL_CHUNK_CHILD,\t\t /* Chunk with parent and the result of table\n\t\t\t\t\t\t\t  * expansion. */\n\tTS_REL_OTHER,\t\t\t /* Anything which is none of the above */\n} TsRelType;\n\nextern TSDLLEXPORT Hypertable *ts_planner_get_hypertable(const Oid relid, const unsigned int flags);\nextern void ts_preprocess_first_last_aggregates(PlannerInfo *root, List *tlist);\nextern void ts_plan_expand_hypertable_chunks(Hypertable *ht, PlannerInfo *root, RelOptInfo *rel,\n\t\t\t\t\t\t\t\t\t\t\t bool include_osm);\nextern void ts_plan_expand_timebucket_annotate(PlannerInfo *root, RelOptInfo *rel);\nextern Expr *ts_transform_time_bucket_comparison(Expr *);\nextern Node *ts_constify_now(PlannerInfo *root, List *rtable, Node *node);\nextern void ts_planner_constraint_cleanup(PlannerInfo *root, RelOptInfo *rel);\nextern Node *ts_add_space_constraints(PlannerInfo *root, List *rtable, Node *node);\n\nextern TSDLLEXPORT void ts_add_baserel_cache_entry_for_chunk(Oid chunk_reloid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t Hypertable *hypertable);\nTsRelType TSDLLEXPORT ts_classify_relation(const PlannerInfo *root, const RelOptInfo *rel,\n\t\t\t\t\t\t\t\t\t\t   Hypertable **ht);\n\n/*\n * Chunk-equivalent of planner_rt_fetch(), but returns the corresponding chunk\n * instead of range table entry.\n *\n * Returns NULL if this rel is not a chunk.\n *\n * This cache should be pre-warmed by hypertable expansion, but it\n * doesn't run in the following cases:\n *\n * 1. if it was a direct query on the chunk;\n *\n * 2. if it is not a SELECT QUERY.\n */\nstatic inline const Chunk *\nts_planner_chunk_fetch(const PlannerInfo *root, RelOptInfo *rel)\n{\n\tTimescaleDBPrivate *rel_private;\n\n\t/* The rel can only be a chunk if it is part of a hypertable expansion\n\t * (RELOPT_OTHER_MEMBER_REL) or a directly query on the chunk\n\t * (RELOPT_BASEREL) */\n\tif (rel->reloptkind != RELOPT_OTHER_MEMBER_REL && rel->reloptkind != RELOPT_BASEREL)\n\t\treturn NULL;\n\n\t/* The rel_private entry should have been created as part of classifying\n\t * the relation in timescaledb_get_relation_info_hook(). Therefore,\n\t * ts_get_private_reloptinfo() asserts that it is already set but falls\n\t * back to creating rel_private in release builds for safety. */\n\trel_private = ts_get_private_reloptinfo(rel);\n\n\tif (NULL == rel_private->cached_chunk_struct)\n\t{\n\t\tRangeTblEntry *rte = planner_rt_fetch(rel->relid, root);\n\n\t\t/*\n\t\t * Get the chunk and cache it. Do not use a slice tuple lock because\n\t\t * that will assign a transaction ID, which is not necessary for\n\t\t * queries.\n\t\t */\n\t\trel_private->cached_chunk_struct =\n\t\t\tts_chunk_get_by_relid_locked(rte->relid,\n\t\t\t\t\t\t\t\t\t\t AccessShareLock,\n\t\t\t\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t\t\t\t /* fail_if_not_found = */ true);\n\t}\n\n\treturn rel_private->cached_chunk_struct;\n}\n"
  },
  {
    "path": "src/planner/space_constraint.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <access/xact.h>\n#include <datatype/timestamp.h>\n#include <nodes/makefuncs.h>\n#include <nodes/pg_list.h>\n#include <optimizer/optimizer.h>\n#include <parser/parse_func.h>\n#include <utils/fmgroids.h>\n#include <utils/typcache.h>\n\n#include \"cache.h\"\n#include \"dimension.h\"\n#include \"hypertable.h\"\n#include \"hypertable_cache.h\"\n#include \"partitioning.h\"\n#include \"planner.h\"\n\n/*\n * Returns space dimension for a specific column. Returns NULL\n * if the column is not a space dimension.\n */\nstatic Dimension *\nget_space_dimension(Oid relid, AttrNumber varattno)\n{\n\tHypertable *ht = ts_planner_get_hypertable(relid, CACHE_FLAG_CHECK);\n\tif (!ht)\n\t\treturn NULL;\n\n\tfor (uint16 i = 0; i < ht->space->num_dimensions; i++)\n\t{\n\t\tDimension *dim = &ht->space->dimensions[i];\n\t\tif (dim->type == DIMENSION_TYPE_CLOSED && dim->column_attno == varattno)\n\t\t{\n\t\t\treturn dim;\n\t\t}\n\t}\n\treturn NULL;\n}\n\n/*\n * Check if this operator is compatible with the constraints on\n * the space dimension. This is the equality operator between\n * left and right in the btree operator family.\n */\nbool\nts_is_equality_operator(Oid opno, Oid left, Oid right)\n{\n\tTypeCacheEntry *tce;\n\n\tif (left == right)\n\t{\n\t\t/*\n\t\t * When left and right match lookup_type_cache can\n\t\t * directly return the equality operator saving us\n\t\t * one roundtrip.\n\t\t */\n\t\ttce = lookup_type_cache(left, TYPECACHE_EQ_OPR);\n\n\t\treturn tce && opno == tce->eq_opr;\n\t}\n\telse\n\t{\n\t\t/*\n\t\t * The left and right type might not match when comparing\n\t\t * different integer types eg comparing int2 or int8\n\t\t * columns with integer literals which default to int4.\n\t\t */\n\t\ttce = lookup_type_cache(left, TYPECACHE_BTREE_OPFAMILY);\n\t\tif (!tce)\n\t\t\treturn false;\n\n\t\tOid eqop = get_opfamily_member(tce->btree_opf, left, right, BTEqualStrategyNumber);\n\t\treturn opno == eqop;\n\t}\n}\n\n/*\n * Valid constraints are: Var = Const\n * Var has to refer to a space partitioning column\n */\nstatic bool\nis_valid_space_constraint(OpExpr *op, List *rtable)\n{\n\tAssert(IsA(op, OpExpr));\n\tif (!IsA(linitial(op->args), Var) || !IsA(lsecond(op->args), Const))\n\t\treturn false;\n\n\tVar *var = linitial_node(Var, op->args);\n\tif (var->varlevelsup != 0)\n\t\treturn false;\n\n\tConst *value = lsecond_node(Const, op->args);\n\tif (!ts_is_equality_operator(op->opno, var->vartype, value->consttype))\n\t\treturn false;\n\n\t/*\n\t * Check that the constraint is actually on a partitioning column.\n\t */\n\tAssert((int) var->varno <= list_length(rtable));\n\tRangeTblEntry *rte = list_nth(rtable, var->varno - 1);\n\tDimension *dim = get_space_dimension(rte->relid, var->varattno);\n\n\tif (!dim)\n\t\treturn false;\n\n\treturn true;\n}\n\n/*\n * Valid constraints are:\n *   Var = ANY(ARRAY[Const,Const])\n *   Var IN (Const,Const)\n * Var has to refer to a space partitioning column\n */\nstatic bool\nis_valid_scalar_space_constraint(ScalarArrayOpExpr *op, List *rtable)\n{\n\tAssert(IsA(op, ScalarArrayOpExpr));\n\tif (!IsA(linitial(op->args), Var) || !IsA(lsecond(op->args), ArrayExpr))\n\t\treturn false;\n\n\tVar *var = linitial_node(Var, op->args);\n\tArrayExpr *arr = castNode(ArrayExpr, lsecond(op->args));\n\tif (arr->multidims || !op->useOr || var->varlevelsup != 0)\n\t\treturn false;\n\n\tif (!ts_is_equality_operator(op->opno, var->vartype, arr->element_typeid))\n\t\treturn false;\n\n\t/*\n\t * Check that the constraint is actually on a partitioning column.\n\t */\n\tAssert((int) var->varno <= list_length(rtable));\n\tRangeTblEntry *rte = list_nth(rtable, var->varno - 1);\n\tDimension *dim = get_space_dimension(rte->relid, var->varattno);\n\n\tif (!dim)\n\t\treturn false;\n\n\tListCell *lc;\n\tforeach (lc, arr->elements)\n\t{\n\t\tswitch (nodeTag(lfirst(lc)))\n\t\t{\n\t\t\tcase T_Const:\n\t\t\t\tbreak;\n\t\t\tcase T_FuncExpr:\n\t\t\t{\n\t\t\t\tFuncExpr *element = lfirst_node(FuncExpr, lc);\n\t\t\t\tif (element->funcformat != COERCE_IMPLICIT_CAST ||\n\t\t\t\t\t!IsA(linitial(element->args), Const))\n\t\t\t\t\treturn false;\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\treturn false;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn true;\n}\n\nstatic FuncExpr *\nmake_partfunc_call(Oid funcid, Oid rettype, List *args, Oid inputcollid)\n{\n\t/* build FuncExpr to use in eval_const_expressions */\n\treturn makeFuncExpr(funcid /* funcid */,\n\t\t\t\t\t\trettype /* rettype */,\n\t\t\t\t\t\targs /* args */,\n\t\t\t\t\t\tInvalidOid /* funccollid */,\n\t\t\t\t\t\tinputcollid /* inputcollid */,\n\t\t\t\t\t\tCOERCE_EXPLICIT_CALL /* fformat */);\n}\n\n/*\n * Transform a constraint like: device_id = 1\n * into\n * ((device_id = 1) AND (_timescaledb_functions.get_partition_hash(device_id) = 242423622))\n */\nstatic OpExpr *\ntransform_space_constraint(PlannerInfo *root, List *rtable, OpExpr *op)\n{\n\tVar *var = linitial_node(Var, op->args);\n\tConst *value = lsecond_node(Const, op->args);\n\tConst *part_value;\n\tRangeTblEntry *rte = list_nth(rtable, var->varno - 1);\n\tDimension *dim = get_space_dimension(rte->relid, var->varattno);\n\tOid rettype = dim->partitioning->partfunc.rettype;\n\tTypeCacheEntry *tce = lookup_type_cache(rettype, TYPECACHE_EQ_OPR);\n\n\t/* build FuncExpr to use in eval_const_expressions */\n\tFuncExpr *partcall = make_partfunc_call(dim->partitioning->partfunc.func_fmgr.fn_oid,\n\t\t\t\t\t\t\t\t\t\t\trettype,\n\t\t\t\t\t\t\t\t\t\t\tlist_make1(value),\n\t\t\t\t\t\t\t\t\t\t\tvar->varcollid);\n\n\t/*\n\t * We should always be able to constify here\n\t */\n\tpart_value = castNode(Const, eval_const_expressions(root, (Node *) partcall));\n\n\t/* build FuncExpr with column reference to use in constraint */\n\tpartcall->args = list_make1(copyObject(var));\n\n\tOpExpr *ret = (OpExpr *) make_opclause(tce->eq_opr /* opno */,\n\t\t\t\t\t\t\t\t\t\t   BOOLOID /*opresulttype */,\n\t\t\t\t\t\t\t\t\t\t   false /* opretset */,\n\t\t\t\t\t\t\t\t\t\t   (Expr *) partcall /* left */,\n\t\t\t\t\t\t\t\t\t\t   (Expr *) part_value /* right */,\n\t\t\t\t\t\t\t\t\t\t   InvalidOid /* opcollid */,\n\t\t\t\t\t\t\t\t\t\t   InvalidOid /* inputcollid */);\n\tret->location = PLANNER_LOCATION_MAGIC;\n\treturn ret;\n}\n\n/*\n * Transforms a constraint like: s1 = ANY ('{s1_2,s1_2}'::text[])\n * into\n * ((s1 = ANY ('{s1_2,s1_2}'::text[])) AND (_timescaledb_functions.get_partition_hash(s1) = ANY\n * ('{1583420735,1583420735}'::integer[])))\n */\nstatic ScalarArrayOpExpr *\ntransform_scalar_space_constraint(PlannerInfo *root, List *rtable, ScalarArrayOpExpr *op)\n{\n\tVar *var = linitial_node(Var, op->args);\n\tRangeTblEntry *rte = list_nth(rtable, var->varno - 1);\n\tDimension *dim = get_space_dimension(rte->relid, var->varattno);\n\tOid rettype = dim->partitioning->partfunc.rettype;\n\tTypeCacheEntry *tce = lookup_type_cache(rettype, TYPECACHE_EQ_OPR);\n\tList *part_values = NIL;\n\tListCell *lc;\n\n\t/* build FuncExpr to use in eval_const_expressions */\n\tFuncExpr *partcall = make_partfunc_call(dim->partitioning->partfunc.func_fmgr.fn_oid,\n\t\t\t\t\t\t\t\t\t\t\trettype,\n\t\t\t\t\t\t\t\t\t\t\tNIL,\n\t\t\t\t\t\t\t\t\t\t\tvar->varcollid);\n\n\tforeach (lc, lsecond_node(ArrayExpr, op->args)->elements)\n\t{\n\t\tAssert(IsA(lfirst(lc), Const) ||\n\t\t\t   (IsA(lfirst(lc), FuncExpr) &&\n\t\t\t\tlfirst_node(FuncExpr, lc)->funcformat == COERCE_IMPLICIT_CAST));\n\n\t\t/*\n\t\t * We can skip NULL here as elements are ORed and partitioning dimensions\n\t\t * have NOT NULL constraint.\n\t\t */\n\t\tif (IsA(lfirst(lc), Const) && lfirst_node(Const, lc)->constisnull)\n\t\t\tcontinue;\n\n\t\tList *args = list_make1(lfirst(lc));\n\t\tpartcall->args = args;\n\t\tpart_values =\n\t\t\tlappend(part_values, castNode(Const, eval_const_expressions(root, (Node *) partcall)));\n\t}\n\t/* build FuncExpr with column reference to use in constraint */\n\tpartcall->args = list_make1(copyObject(var));\n\n\tArrayExpr *arr2 = makeNode(ArrayExpr);\n\tarr2->array_collid = InvalidOid;\n\tarr2->array_typeid = get_array_type(rettype);\n\tarr2->element_typeid = rettype;\n\tarr2->multidims = false;\n\tarr2->location = -1;\n\tarr2->elements = part_values;\n\n\tScalarArrayOpExpr *op2 = makeNode(ScalarArrayOpExpr);\n\top2->opno = tce->eq_opr;\n\top2->args = list_make2(partcall, arr2);\n\top2->inputcollid = InvalidOid;\n\top2->useOr = true;\n\top2->location = PLANNER_LOCATION_MAGIC;\n\n\treturn op2;\n}\n\n/*\n * Transform constraints for hash-based partitioning columns to make\n * them usable by postgres constraint exclusion.\n *\n * If we have an equality condition on a space partitioning column, we add\n * a corresponding condition on get_partition_hash on this column. These\n * conditions match the constraints on chunks, so postgres' constraint\n * exclusion is able to use them and exclude the chunks.\n *\n */\nNode *\nts_add_space_constraints(PlannerInfo *root, List *rtable, Node *node)\n{\n\tAssert(node);\n\n\tswitch (nodeTag(node))\n\t{\n\t\tcase T_ScalarArrayOpExpr:\n\t\t{\n\t\t\tif (is_valid_scalar_space_constraint(castNode(ScalarArrayOpExpr, node), rtable))\n\t\t\t{\n\t\t\t\tList *args =\n\t\t\t\t\tlist_make2(node,\n\t\t\t\t\t\t\t   transform_scalar_space_constraint(root,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t rtable,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t castNode(ScalarArrayOpExpr,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  node)));\n\t\t\t\treturn (Node *) makeBoolExpr(AND_EXPR, args, -1);\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\t\tcase T_OpExpr:\n\t\t\tif (is_valid_space_constraint(castNode(OpExpr, node), rtable))\n\t\t\t{\n\t\t\t\tList *args =\n\t\t\t\t\tlist_make2(node,\n\t\t\t\t\t\t\t   transform_space_constraint(root, rtable, castNode(OpExpr, node)));\n\t\t\t\treturn (Node *) makeBoolExpr(AND_EXPR, args, -1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase T_BoolExpr:\n\t\t{\n\t\t\tListCell *lc;\n\t\t\tBoolExpr *be = castNode(BoolExpr, node);\n\n\t\t\tif (be->boolop == AND_EXPR)\n\t\t\t{\n\t\t\t\tList *additions = NIL;\n\t\t\t\t/*\n\t\t\t\t * If this is a top-level AND we can just append our transformed constraints\n\t\t\t\t * to the list of ANDed expressions.\n\t\t\t\t */\n\t\t\t\tforeach (lc, be->args)\n\t\t\t\t{\n\t\t\t\t\tswitch (nodeTag(lfirst(lc)))\n\t\t\t\t\t{\n\t\t\t\t\t\tcase T_OpExpr:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tOpExpr *op = lfirst_node(OpExpr, lc);\n\t\t\t\t\t\t\tif (is_valid_space_constraint(op, rtable))\n\t\t\t\t\t\t\t\tadditions = lappend(additions,\n\t\t\t\t\t\t\t\t\t\t\t\t\ttransform_space_constraint(root, rtable, op));\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcase T_ScalarArrayOpExpr:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tScalarArrayOpExpr *op = lfirst_node(ScalarArrayOpExpr, lc);\n\t\t\t\t\t\t\tif (is_valid_scalar_space_constraint(op, rtable))\n\t\t\t\t\t\t\t\tadditions =\n\t\t\t\t\t\t\t\t\tlappend(additions,\n\t\t\t\t\t\t\t\t\t\t\ttransform_scalar_space_constraint(root, rtable, op));\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (additions)\n\t\t\t\t\tbe->args = list_concat(be->args, additions);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\n\treturn node;\n}\n"
  },
  {
    "path": "src/process_utility.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <access/htup_details.h>\n#include <access/xact.h>\n#include <catalog/heap.h>\n#include <catalog/index.h>\n#include <catalog/namespace.h>\n#include <catalog/objectaddress.h>\n#include <catalog/pg_am.h>\n#include <catalog/pg_authid.h>\n#include <catalog/pg_class_d.h>\n#include <catalog/pg_constraint.h>\n#include <catalog/pg_inherits.h>\n#include <catalog/pg_trigger.h>\n#include <commands/alter.h>\n#include <commands/cluster.h>\n#include <commands/copy.h>\n#include <commands/defrem.h>\n#include <commands/event_trigger.h>\n#include <commands/prepare.h>\n#include <commands/tablecmds.h>\n#include <commands/tablespace.h>\n#include <commands/trigger.h>\n#include <commands/user.h>\n#include <commands/vacuum.h>\n#include <executor/spi.h>\n#include <miscadmin.h>\n#include <nodes/lockoptions.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodes.h>\n#include <nodes/parsenodes.h>\n#include <optimizer/optimizer.h>\n#include <parser/parse_expr.h>\n#include <parser/parse_relation.h>\n#include <parser/parse_type.h>\n#include <parser/parse_utilcmd.h>\n#include <storage/lmgr.h>\n#include <storage/lockdefs.h>\n#include <tcop/utility.h>\n#include <utils/acl.h>\n#include <utils/builtins.h>\n#include <utils/elog.h>\n#include <utils/guc.h>\n#include <utils/inval.h>\n#include <utils/lsyscache.h>\n#include <utils/palloc.h>\n#include <utils/regproc.h>\n#include <utils/rel.h>\n#include <utils/ruleutils.h>\n#include <utils/snapmgr.h>\n#include <utils/syscache.h>\n\n#include \"compat/compat.h\"\n#include \"annotations.h\"\n#include \"chunk.h\"\n#include \"chunk_index.h\"\n#include \"copy.h\"\n#include \"cross_module_fn.h\"\n#include \"debug_assert.h\"\n#include \"debug_point.h\"\n#include \"dimension_vector.h\"\n#include \"errors.h\"\n#include \"event_trigger.h\"\n#include \"export.h\"\n#include \"extension.h\"\n#include \"extension_constants.h\"\n#include \"foreign_key.h\"\n#include \"hypercube.h\"\n#include \"hypertable.h\"\n#include \"hypertable_cache.h\"\n#include \"indexing.h\"\n#include \"license_guc.h\"\n#include \"partition_chunk.h\"\n#include \"partitioning.h\"\n#include \"process_utility.h\"\n#include \"scan_iterator.h\"\n#include \"time_utils.h\"\n#include \"trigger.h\"\n#include \"ts_catalog/array_utils.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"ts_catalog/chunk_column_stats.h\"\n#include \"ts_catalog/chunk_rewrite.h\"\n#include \"ts_catalog/compression_settings.h\"\n#include \"ts_catalog/continuous_agg.h\"\n#include \"ts_catalog/continuous_aggs_watermark.h\"\n#include \"tss_callbacks.h\"\n#include \"utils.h\"\n#include \"with_clause/alter_table_with_clause.h\"\n#include \"with_clause/create_materialized_view_with_clause.h\"\n#include \"with_clause/create_table_with_clause.h\"\n#include \"with_clause/with_clause_parser.h\"\n\n#ifdef USE_TELEMETRY\n#include \"telemetry/functions.h\"\n#endif\n\nvoid _process_utility_init(void);\nvoid _process_utility_fini(void);\n\nstatic ProcessUtility_hook_type prev_ProcessUtility_hook;\n\nstatic bool expect_chunk_modification = false;\nstatic ProcessUtilityContext last_process_utility_context = PROCESS_UTILITY_TOPLEVEL;\nstatic void check_no_timescale_options(AlterTableCmd *cmd, Oid reloid);\nstatic DDLResult process_altertable_set_options(AlterTableCmd *cmd, Hypertable *ht);\nstatic DDLResult process_altertable_reset_options(AlterTableCmd *cmd, Hypertable *ht);\nstatic void ts_bgw_job_update_owner(Relation rel, HeapTuple tuple, TupleDesc tupledesc,\n\t\t\t\t\t\t\t\t\tOid newrole_oid);\n\n/* Call the default ProcessUtility and handle PostgreSQL version differences */\nstatic void\nprev_ProcessUtility(ProcessUtilityArgs *args)\n{\n\tProcessUtility_hook_type hook =\n\t\tprev_ProcessUtility_hook ? prev_ProcessUtility_hook : standard_ProcessUtility;\n\n\thook(args->pstmt,\n\t\t args->query_string,\n\t\t args->readonly_tree,\n\t\t args->context,\n\t\t args->params,\n\t\t args->queryEnv,\n\t\t args->dest,\n\t\t args->completion_tag);\n\n\t/*\n\t * Reset the last_process_utility_context value that is saved at the\n\t * entrance of the TS ProcessUtility hook and can be used for transaction\n\t * checks inside refresh_cagg and other procedures.\n\t */\n\tts_process_utility_context_reset();\n}\n\nstatic void\ncheck_chunk_alter_table_operation_allowed(Oid relid, AlterTableStmt *stmt)\n{\n\tconst Chunk *chunk;\n\n\tif (expect_chunk_modification)\n\t\treturn;\n\n\tchunk = ts_chunk_get_by_relid(relid, false /* fail_if_not_found */);\n\n\tif (chunk != NULL)\n\t{\n\t\tbool all_allowed = true;\n\t\tListCell *lc;\n\n\t\t/* only allow if all commands are allowed */\n\t\tforeach (lc, stmt->cmds)\n\t\t{\n\t\t\tAlterTableCmd *cmd = (AlterTableCmd *) lfirst(lc);\n\n\t\t\tswitch (cmd->subtype)\n\t\t\t{\n\t\t\t\tcase AT_SetOptions:\n\t\t\t\tcase AT_ResetOptions:\n\t\t\t\tcase AT_SetRelOptions:\n\t\t\t\tcase AT_ResetRelOptions:\n\t\t\t\tcase AT_ReplaceRelOptions:\n\t\t\t\tcase AT_SetStatistics:\n\t\t\t\tcase AT_SetStorage:\n\t\t\t\tcase AT_DropCluster:\n\t\t\t\tcase AT_ClusterOn:\n\t\t\t\tcase AT_EnableRowSecurity:\n\t\t\t\tcase AT_DisableRowSecurity:\n\t\t\t\tcase AT_SetTableSpace:\n\t\t\t\tcase AT_ReAddStatistics:\n\t\t\t\tcase AT_SetCompression:\n\t\t\t\tcase AT_SetAccessMethod:\n\t\t\t\tcase AT_SetLogged:\n\t\t\t\tcase AT_SetUnLogged:\n\t\t\t\t\t/* allowed on chunks */\n\t\t\t\t\tbreak;\n\t\t\t\tcase AT_AddConstraint:\n\t\t\t\t{\n\t\t\t\t\t/* if this is an OSM chunk, block the operation */\n\t\t\t\t\tif (chunk->fd.osm_chunk)\n\t\t\t\t\t{\n\t\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t\t\t errmsg(\"operation not supported on OSM chunk tables\")));\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase AT_DropConstraint:\n\t\t\t\t{\n\t\t\t\t\t/* if this is an OSM chunk, block the operation */\n\t\t\t\t\tif (chunk->fd.osm_chunk)\n\t\t\t\t\t{\n\t\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t\t\t errmsg(\"operation not supported on OSM chunk tables\")));\n\t\t\t\t\t}\n\n\t\t\t\t\tChunkConstraints *ccs =\n\t\t\t\t\t\tts_chunk_constraint_scan_by_chunk_id(chunk->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t 10,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t CurrentMemoryContext);\n\t\t\t\t\tAssert(cmd->name);\n\n\t\t\t\t\tfor (int i = 0; i < ccs->num_constraints; i++)\n\t\t\t\t\t{\n\t\t\t\t\t\tChunkConstraint *cc = &ccs->constraints[i];\n\n\t\t\t\t\t\tif (namestrcmp(&cc->fd.constraint_name, cmd->name) == 0)\n\t\t\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t\t\t\t errmsg(\"cannot drop inherited constraint\"),\n\t\t\t\t\t\t\t\t\t errhint(\"Drop the constraint on the hypertable instead.\")));\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\t/* disable by default */\n\t\t\t\t\tall_allowed = false;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (!all_allowed)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"operation not supported on chunk tables\")));\n\t}\n}\n\n/* we block some ALTER commands on continuous aggregate materialization tables\n */\nstatic void\ncheck_continuous_agg_alter_table_allowed(Hypertable *ht, AlterTableStmt *stmt)\n{\n\tListCell *lc;\n\tContinuousAggHypertableStatus status = ts_continuous_agg_hypertable_status(ht->fd.id);\n\tif ((status & HypertableIsMaterialization) == 0)\n\t\treturn;\n\n\t/* only allow if all commands are allowed */\n\tforeach (lc, stmt->cmds)\n\t{\n\t\tAlterTableCmd *cmd = (AlterTableCmd *) lfirst(lc);\n\n\t\tswitch (cmd->subtype)\n\t\t{\n\t\t\tcase AT_AddIndex:\n\t\t\tcase AT_ReAddIndex:\n\t\t\tcase AT_SetRelOptions:\n\t\t\tcase AT_ReplicaIdentity:\n\t\t\t\t/* allowed on materialization tables */\n\t\t\t\tcontinue;\n\t\t\tdefault:\n\t\t\t\t/* disable by default */\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t errmsg(\"operation not supported on materialization tables\")));\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/* check if hypertable has compressed chunks */\nstatic bool\nts_hypertable_has_compressed_chunks(const Hypertable *ht)\n{\n\tif (!TS_HYPERTABLE_HAS_COMPRESSION_ENABLED(ht))\n\t\treturn false;\n\n\treturn ts_chunk_exists_with_compression(ht->fd.id);\n}\n\nstatic void\ncheck_alter_table_allowed_on_ht_with_compression(Hypertable *ht, AlterTableStmt *stmt)\n{\n\tListCell *lc;\n\tif (!TS_HYPERTABLE_HAS_COMPRESSION_ENABLED(ht))\n\t\treturn;\n\n\t/* only allow if all commands are allowed */\n\tforeach (lc, stmt->cmds)\n\t{\n\t\tAlterTableCmd *cmd = (AlterTableCmd *) lfirst(lc);\n\n\t\tswitch (cmd->subtype)\n\t\t{\n\t\t\t/*\n\t\t\t * ALLOWED:\n\t\t\t *\n\t\t\t * This is a whitelist of allowed commands.\n\t\t\t */\n\t\t\tcase AT_AddIndex:\n\t\t\tcase AT_AddConstraint:\n\t\t\tcase AT_ReAddIndex:\n\t\t\tcase AT_ResetRelOptions:\n\t\t\tcase AT_ReplaceRelOptions:\n\t\t\tcase AT_SetRelOptions:\n\t\t\tcase AT_ClusterOn:\n\t\t\tcase AT_DropCluster:\n\t\t\tcase AT_ChangeOwner:\n\t\t\t\t/* this is passed down in `process_altertable_change_owner` */\n\t\t\tcase AT_SetTableSpace:\n\t\t\t\t/* this is passed down in `process_altertable_set_tablespace_end` */\n\t\t\tcase AT_SetStatistics:\t/* should this be pushed down in some way? */\n\t\t\tcase AT_AddColumn:\t\t/* this is passed down */\n\t\t\tcase AT_ColumnDefault:\t/* this is passed down */\n\t\t\tcase AT_DropColumn:\t\t/* this is passed down */\n\t\t\tcase AT_DropConstraint: /* this is passed down */\n\t\t\tcase AT_ReplicaIdentity:\n\t\t\tcase AT_ReAddStatistics:\n\t\t\tcase AT_SetCompression:\n\t\t\tcase AT_DropNotNull:\n\t\t\tcase AT_SetNotNull:\n\t\t\tcase AT_SetAccessMethod:\n\t\t\tcase AT_SetLogged:\n\t\t\tcase AT_SetUnLogged:\n\t\t\t\tcontinue;\n\t\t\t\t/*\n\t\t\t\t * BLOCKED:\n\t\t\t\t *\n\t\t\t\t * List things that we want to explicitly block for documentation purposes\n\t\t\t\t * But also block everything else as well.\n\t\t\t\t */\n\t\t\tcase AT_AlterColumnType:\n\t\t\t\t/* Allow AT_AlterColumnType when no compressed chunks exist */\n\t\t\t\tif (!ts_hypertable_has_compressed_chunks(ht))\n\t\t\t\t\tcontinue;\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t errmsg(\"operation not supported on hypertables with compressed chunks\")));\n\t\t\t\tbreak;\n\t\t\tcase AT_EnableRowSecurity:\n\t\t\tcase AT_DisableRowSecurity:\n\t\t\tcase AT_ForceRowSecurity:\n\t\t\tcase AT_NoForceRowSecurity:\n\t\t\tdefault:\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t errmsg(\"operation not supported on hypertables that have columnstore \"\n\t\t\t\t\t\t\t\t\"enabled\")));\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nstatic void\ncheck_altertable_add_column_for_compressed(ParseState *parse_state, Hypertable *ht, ColumnDef *col)\n{\n\tif (col->constraints)\n\t{\n\t\tbool has_default = false;\n\t\tbool has_notnull = col->is_not_null;\n\t\tListCell *lc;\n\t\tforeach (lc, col->constraints)\n\t\t{\n\t\t\tConstraint *constraint = lfirst_node(Constraint, lc);\n\t\t\tswitch (constraint->contype)\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * These will fail in combination with ADD COLUMN because this will\n\t\t\t\t * be a single column constraint and we require all partitioning\n\t\t\t\t * columns to be part if the unique/primary key constraint.\n\t\t\t\t */\n\t\t\t\tcase CONSTR_PRIMARY:\n\t\t\t\tcase CONSTR_UNIQUE:\n\t\t\t\t\tbreak;\n\t\t\t\t/*\n\t\t\t\t * We can safelly ignore NULL constraints because it does nothing\n\t\t\t\t * and according to Postgres docs is useless and exist just for\n\t\t\t\t * compatibility with other database systems\n\t\t\t\t * https://www.postgresql.org/docs/current/ddl-constraints.html#id-1.5.4.6.6\n\t\t\t\t */\n\t\t\t\tcase CONSTR_NULL:\n\t\t\t\t\tcontinue;\n\t\t\t\tcase CONSTR_NOTNULL:\n\t\t\t\t\thas_notnull = true;\n\t\t\t\t\tcontinue;\n\t\t\t\t\t/*\n\t\t\t\t\t * check constraints are validated at end of alter table command\n\t\t\t\t\t * in validate_check_constraint\n\t\t\t\t\t */\n\t\t\t\tcase CONSTR_CHECK:\n\t\t\t\t\tcontinue;\n\t\t\t\tcase CONSTR_DEFAULT:\n\t\t\t\t{\n\t\t\t\t\t/*\n\t\t\t\t\t * Since default expressions might trigger a table rewrite we\n\t\t\t\t\t * only allow Const here for now.\n\t\t\t\t\t */\n\t\t\t\t\tOid typeoid;\n\t\t\t\t\tint32 typmod;\n\t\t\t\t\ttypenameTypeIdAndMod(parse_state, col->typeName, &typeoid, &typmod);\n\t\t\t\t\tNode *transformed = cookDefault(parse_state,\n\t\t\t\t\t\t\t\t\t\t\t\t\tconstraint->raw_expr,\n\t\t\t\t\t\t\t\t\t\t\t\t\ttypeoid,\n\t\t\t\t\t\t\t\t\t\t\t\t\ttypmod,\n\t\t\t\t\t\t\t\t\t\t\t\t\tcol->colname,\n\t\t\t\t\t\t\t\t\t\t\t\t\tcol->generated);\n\t\t\t\t\tNode *constified = eval_const_expressions(NULL, transformed);\n\t\t\t\t\tif (!IsA(constified, Const))\n\t\t\t\t\t{\n\t\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t\t\t errmsg(\"cannot add column with non-constant default expression \"\n\t\t\t\t\t\t\t\t\t\t\"to a hypertable that has columnstore enabled\")));\n\t\t\t\t\t}\n\t\t\t\t\thas_default = true;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t\t errmsg(\"cannot add column with constraints \"\n\t\t\t\t\t\t\t\t\t\"to a hypertable that has columnstore enabled\")));\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\t/* require a default for columns added with NOT NULL */\n\t\tif (has_notnull && !has_default)\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"cannot add column with NOT NULL constraint without default \"\n\t\t\t\t\t\t\t\"to a hypertable that has columnstore enabled\")));\n\t\t}\n\t}\n\tif (col->is_not_null || col->identitySequence != NULL)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot add column with constraints to a hypertable that has \"\n\t\t\t\t\t\t\"columnstore enabled\")));\n\t}\n\t/* not possible to get non-null value here this is set when\n\t * ALTER TABLE ALTER COLUMN ... SET TYPE < > USING ...\n\t * but check anyway.\n\t */\n\tAssert(col->raw_default == NULL && col->cooked_default == NULL);\n}\n\nstatic void\nrelation_not_only(RangeVar *rv)\n{\n\tif (!rv->inh)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"ONLY option not supported on hypertable operations\")));\n}\n\nstatic bool\ncheck_table_in_rangevar_list(List *rvlist, Name schema_name, Name table_name)\n{\n\tListCell *l;\n\n\tforeach (l, rvlist)\n\t{\n\t\tRangeVar *rvar = lfirst_node(RangeVar, l);\n\n\t\tif (strcmp(rvar->relname, NameStr(*table_name)) == 0 &&\n\t\t\tstrcmp(rvar->schemaname, NameStr(*schema_name)) == 0)\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nstatic void\nadd_chunk_oid(Hypertable *ht, Oid chunk_relid, void *vargs)\n{\n\tProcessUtilityArgs *args = vargs;\n\tGrantStmt *stmt = castNode(GrantStmt, args->parsetree);\n\tChunk *chunk = ts_chunk_get_by_relid(chunk_relid, true);\n\t/*\n\t * If chunk is in the same schema as the hypertable it could already be part of\n\t * the objects list in the case of \"GRANT ALL IN SCHEMA\" for example\n\t */\n\tif (!check_table_in_rangevar_list(stmt->objects, &chunk->fd.schema_name, &chunk->fd.table_name))\n\t{\n\t\tRangeVar *rv =\n\t\t\tmakeRangeVar(NameStr(chunk->fd.schema_name), NameStr(chunk->fd.table_name), -1);\n\t\tstmt->objects = lappend(stmt->objects, rv);\n\t}\n}\n\nstatic DDLResult\nprocess_drop_schema_start(DropStmt *stmt)\n{\n\t/*\n\t * An error will be raised when we start dropping the functions used by a\n\t * background worker, so there is no point in doing anything here.\n\t */\n\tif (stmt->behavior == DROP_RESTRICT)\n\t\treturn DDL_CONTINUE;\n\n\t/*\n\t * Here we are relying on that if we fail to drop one of the\n\t * procedures/functions, this transaction will be rolled back so these\n\t * changes will not be committed.\n\t */\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(BGW_JOB, RowExclusiveLock, CurrentMemoryContext);\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tListCell *cell;\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tbool schema_isnull, job_id_isnull;\n\t\tint32 job_id = DatumGetInt32(slot_getattr(ti->slot, Anum_bgw_job_id, &job_id_isnull));\n\t\tName proc_schema =\n\t\t\tDatumGetName(slot_getattr(ti->slot, Anum_bgw_job_proc_schema, &schema_isnull));\n\t\tEnsure(!job_id_isnull, \"corrupt job entry: job id is null\");\n\t\tEnsure(!schema_isnull, \"corrupt job entry: schema for job %d is null\", job_id);\n\t\tforeach (cell, stmt->objects)\n\t\t{\n\t\t\tString *object = lfirst_node(String, cell);\n\t\t\tif (namestrcmp(proc_schema, strVal(object)) == 0)\n\t\t\t{\n\t\t\t\tCatalogSecurityContext sec_ctx;\n\t\t\t\tAssert(stmt->behavior == DROP_CASCADE);\n\t\t\t\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\t\t\t\tereport(NOTICE, errmsg(\"drop cascades to job %d\", job_id));\n\t\t\t\tts_catalog_delete_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\t\t\t\tts_catalog_restore_user(&sec_ctx);\n\t\t\t}\n\t\t}\n\t}\n\treturn DDL_CONTINUE;\n}\n\n/*\n * Start of dropping a procedure.\n *\n * We can abort the drop here by throwing an error.\n */\nstatic void\nprocess_drop_procedure_start(DropStmt *stmt)\n{\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(BGW_JOB, RowExclusiveLock, CurrentMemoryContext);\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tListCell *cell;\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tbool schema_isnull, name_isnull, job_id_isnull;\n\t\tName proc_schema =\n\t\t\tDatumGetName(slot_getattr(ti->slot, Anum_bgw_job_proc_schema, &schema_isnull));\n\t\tName proc_name = DatumGetName(slot_getattr(ti->slot, Anum_bgw_job_proc_name, &name_isnull));\n\t\tint32 job_id = DatumGetInt32(slot_getattr(ti->slot, Anum_bgw_job_id, &job_id_isnull));\n\t\tEnsure(!job_id_isnull, \"corrupt job entry: job id was null\");\n\t\tEnsure(!schema_isnull, \"corrupt job entry: schema for job %d was null\", job_id);\n\t\tEnsure(!name_isnull, \"corrupt job entry: name for job %d was null\", job_id);\n\t\tTS_DEBUG_LOG(\"looking at job %d with %s.%s\",\n\t\t\t\t\t job_id,\n\t\t\t\t\t NameStr(*proc_schema),\n\t\t\t\t\t NameStr(*proc_name));\n\t\tforeach (cell, stmt->objects)\n\t\t{\n\t\t\tObjectWithArgs *object = castNode(ObjectWithArgs, lfirst(cell));\n\t\t\tRangeVar *rel = makeRangeVarFromNameList(object->objname);\n\t\t\tif (namestrcmp(proc_schema, rel->schemaname) == 0 &&\n\t\t\t\tnamestrcmp(proc_name, rel->relname) == 0)\n\t\t\t{\n\t\t\t\tAssert(stmt->removeType == OBJECT_PROCEDURE || stmt->removeType == OBJECT_FUNCTION);\n\t\t\t\tif (stmt->behavior == DROP_RESTRICT)\n\t\t\t\t{\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\terrcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),\n\t\t\t\t\t\t\terrmsg(\"cannot drop %s because background job %d depends on it\",\n\t\t\t\t\t\t\t\t   NameListToString(object->objname),\n\t\t\t\t\t\t\t\t   job_id),\n\t\t\t\t\t\t\terrhint(\"Use delete_job() to drop the job first.\"));\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tCatalogSecurityContext sec_ctx;\n\t\t\t\t\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\t\t\t\t\tereport(NOTICE, errmsg(\"drop cascades to job %d\", job_id));\n\t\t\t\t\tts_catalog_delete_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\t\t\t\t\tts_catalog_restore_user(&sec_ctx);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void\nreplace_attr_if_changed(AttrNumber attno, const char *newvalue, Name name_buf, Datum *values,\n\t\t\t\t\t\tbool *replace)\n{\n\tif (newvalue)\n\t{\n\t\tconst Name orig_value = DatumGetName(values[AttrNumberGetAttrOffset(attno)]);\n\t\tif (namestrcmp(orig_value, newvalue) != 0)\n\t\t{\n\t\t\tnamestrcpy(name_buf, newvalue);\n\t\t\tvalues[AttrNumberGetAttrOffset(attno)] = NameGetDatum(name_buf);\n\t\t\treplace[AttrNumberGetAttrOffset(attno)] = true;\n\t\t}\n\t}\n}\n\n/*\n * Update the schema or name of a procedure in the jobs tuple.\n */\nstatic void\nts_bgw_job_update_proc(Relation rel, HeapTuple tuple, TupleDesc tupledesc, const char *newschema,\n\t\t\t\t\t   const char *newname)\n{\n\tbool isnull[Natts_bgw_job];\n\tDatum values[Natts_bgw_job];\n\tbool replace[Natts_bgw_job] = { false };\n\n\t/* Allocated here to make sure that they are alive at the call of\n\t * heap_modify_tuple */\n\tNameData proc_name_buf;\n\tNameData proc_schema_buf;\n\n\theap_deform_tuple(tuple, tupledesc, values, isnull);\n\n\treplace_attr_if_changed(Anum_bgw_job_proc_name, newname, &proc_name_buf, values, replace);\n\treplace_attr_if_changed(Anum_bgw_job_proc_schema, newschema, &proc_schema_buf, values, replace);\n\n\tHeapTuple new_tuple = heap_modify_tuple(tuple, tupledesc, values, isnull, replace);\n\tts_catalog_update(rel, new_tuple);\n\theap_freetuple(new_tuple);\n}\n\nstatic void\nts_bgw_job_rename_schema_name(const char *old_schema_name, const char *new_schema_name)\n{\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(BGW_JOB, RowExclusiveLock, CurrentMemoryContext);\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tbool should_free, curr_schema_isnull, curr_name_isnull;\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tName curr_proc_schema =\n\t\t\tDatumGetName(slot_getattr(ti->slot, Anum_bgw_job_proc_schema, &curr_schema_isnull));\n\t\tName curr_proc_name =\n\t\t\tDatumGetName(slot_getattr(ti->slot, Anum_bgw_job_proc_name, &curr_name_isnull));\n\t\tif (!curr_schema_isnull && namestrcmp(curr_proc_schema, old_schema_name) == 0)\n\t\t{\n\t\t\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\t\t\tts_bgw_job_update_proc(ti->scanrel,\n\t\t\t\t\t\t\t\t   tuple,\n\t\t\t\t\t\t\t\t   ts_scanner_get_tupledesc(ti),\n\t\t\t\t\t\t\t\t   new_schema_name,\n\t\t\t\t\t\t\t\t   NameStr(*curr_proc_name));\n\n\t\t\tif (should_free)\n\t\t\t\theap_freetuple(tuple);\n\t\t}\n\t}\n}\n\nstatic DDLResult\nts_bgw_job_rename_proc(ObjectAddress address, const char *newschema, const char *newname)\n{\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(BGW_JOB, RowExclusiveLock, CurrentMemoryContext);\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tbool should_free, curr_schema_isnull, curr_name_isnull;\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tName curr_proc_schema =\n\t\t\tDatumGetName(slot_getattr(ti->slot, Anum_bgw_job_proc_schema, &curr_schema_isnull));\n\t\tName curr_proc_name =\n\t\t\tDatumGetName(slot_getattr(ti->slot, Anum_bgw_job_proc_name, &curr_name_isnull));\n\t\tconst char *old_proc_schema = get_namespace_name(get_func_namespace(address.objectId));\n\t\tconst char *old_proc_name = get_func_name(address.objectId);\n\t\tif (!curr_schema_isnull && !curr_name_isnull &&\n\t\t\tnamestrcmp(curr_proc_name, old_proc_name) == 0 &&\n\t\t\tnamestrcmp(curr_proc_schema, old_proc_schema) == 0)\n\t\t{\n\t\t\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\t\t\tts_bgw_job_update_proc(ti->scanrel,\n\t\t\t\t\t\t\t\t   tuple,\n\t\t\t\t\t\t\t\t   ts_scanner_get_tupledesc(ti),\n\t\t\t\t\t\t\t\t   newschema,\n\t\t\t\t\t\t\t\t   newname);\n\n\t\t\tif (should_free)\n\t\t\t\theap_freetuple(tuple);\n\t\t}\n\t}\n\treturn DDL_CONTINUE;\n}\n\nstatic void\nprocess_alterprocedureschema(ProcessUtilityArgs *args)\n{\n\tAlterObjectSchemaStmt *stmt = (AlterObjectSchemaStmt *) args->parsetree;\n\tRelation relation;\n\n\tAssert(stmt->objectType == OBJECT_PROCEDURE || stmt->objectType == OBJECT_FUNCTION);\n\tObjectAddress address =\n\t\tget_object_address(stmt->objectType, stmt->object, &relation, AccessExclusiveLock, false);\n\tts_bgw_job_rename_proc(address, stmt->newschema, NULL);\n}\n\n/* We use this for both materialized views and views. */\nstatic void\nprocess_alterviewschema(ProcessUtilityArgs *args)\n{\n\tAlterObjectSchemaStmt *stmt = (AlterObjectSchemaStmt *) args->parsetree;\n\tOid relid;\n\tchar *schema;\n\tchar *name;\n\n\tAssert(stmt->objectType == OBJECT_MATVIEW || stmt->objectType == OBJECT_VIEW);\n\n\tif (NULL == stmt->relation)\n\t\treturn;\n\n\trelid = RangeVarGetRelid(stmt->relation, NoLock, true);\n\tif (!OidIsValid(relid))\n\t\treturn;\n\n\tschema = get_namespace_name(get_rel_namespace(relid));\n\tname = get_rel_name(relid);\n\n\tts_continuous_agg_rename_view(schema, name, stmt->newschema, name, &stmt->objectType);\n}\n\nstatic void\nprocess_altertableschema(ProcessUtilityArgs *args)\n{\n\tAlterObjectSchemaStmt *alterstmt = (AlterObjectSchemaStmt *) args->parsetree;\n\tOid relid;\n\tCache *hcache;\n\tHypertable *ht;\n\n\tAssert(alterstmt->objectType == OBJECT_TABLE);\n\n\tif (NULL == alterstmt->relation)\n\t\treturn;\n\n\trelid = RangeVarGetRelid(alterstmt->relation, NoLock, true);\n\n\tif (!OidIsValid(relid))\n\t\treturn;\n\n\tht = ts_hypertable_cache_get_cache_and_entry(relid, CACHE_FLAG_MISSING_OK, &hcache);\n\n\tif (ht == NULL)\n\t{\n\t\tContinuousAgg *cagg = ts_continuous_agg_find_by_relid(relid);\n\n\t\tif (cagg)\n\t\t{\n\t\t\talterstmt->objectType = OBJECT_MATVIEW;\n\t\t\tprocess_alterviewschema(args);\n\t\t\tts_cache_release(&hcache);\n\t\t\treturn;\n\t\t}\n\n\t\tChunk *chunk = ts_chunk_get_by_relid(relid, false);\n\n\t\tif (NULL != chunk)\n\t\t\tts_chunk_set_schema(chunk, alterstmt->newschema);\n\t}\n\telse\n\t{\n\t\tts_hypertable_set_schema(ht, alterstmt->newschema);\n\t}\n\n\tts_cache_release(&hcache);\n}\n\n/* Change the schema of a hypertable or a chunk */\nstatic DDLResult\nprocess_alterobjectschema(ProcessUtilityArgs *args)\n{\n\tAlterObjectSchemaStmt *alterstmt = (AlterObjectSchemaStmt *) args->parsetree;\n\n\tswitch (alterstmt->objectType)\n\t{\n\t\tcase OBJECT_TABLE:\n\t\t\tprocess_altertableschema(args);\n\t\t\tbreak;\n\t\tcase OBJECT_MATVIEW:\n\t\tcase OBJECT_VIEW:\n\t\t\tprocess_alterviewschema(args);\n\t\t\tbreak;\n\t\tcase OBJECT_PROCEDURE:\n\t\tcase OBJECT_FUNCTION:\n\t\t\tprocess_alterprocedureschema(args);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\treturn DDL_CONTINUE;\n}\n\nstatic DDLResult\nprocess_copy(ProcessUtilityArgs *args)\n{\n\tCopyStmt *stmt = (CopyStmt *) args->parsetree;\n\n\tts_begin_tss_store_callback();\n\n\t/*\n\t * Needed to add the appropriate number of tuples to the completion tag\n\t */\n\tuint64 processed;\n\tHypertable *ht = NULL;\n\tCache *hcache = NULL;\n\tOid relid;\n\n\tif (stmt->relation)\n\t{\n\t\trelid = RangeVarGetRelid(stmt->relation, NoLock, true);\n\n\t\tif (!OidIsValid(relid))\n\t\t\treturn DDL_CONTINUE;\n\n\t\tht = ts_hypertable_cache_get_cache_and_entry(relid, CACHE_FLAG_MISSING_OK, &hcache);\n\n\t\tif (!ht)\n\t\t{\n\t\t\tChunk *chunk = ts_chunk_get_by_relid(relid, false);\n\n\t\t\t/* target is neither hypertable nor chunk so let postgres handle it */\n\t\t\tif (!chunk)\n\t\t\t{\n\t\t\t\tts_cache_release(&hcache);\n\t\t\t\treturn DDL_CONTINUE;\n\t\t\t}\n\n\t\t\tht = ts_hypertable_get_by_id(chunk->fd.hypertable_id);\n\t\t\tif (ht->fd.compression_state == HypertableInternalCompressionTable)\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * For operations on internal compressed chunks we block modifications\n\t\t\t\t * if the chunk belongs to a frozen chunk otherwise let postgres handle it.\n\t\t\t\t * Uncompressed frozen chunks are intercepted as part of tuple routing.\n\t\t\t\t */\n\t\t\t\tChunk *uncompressed = ts_chunk_get_compressed_chunk_parent(chunk);\n\t\t\t\tif (ts_chunk_is_frozen(uncompressed))\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t\t\t\t errmsg(\"cannot COPY into chunk belonging to a frozen \"\n\t\t\t\t\t\t\t\t\t\"chunk\")));\n\n\t\t\t\tts_cache_release(&hcache);\n\t\t\t\treturn DDL_CONTINUE;\n\t\t\t}\n\t\t}\n\t}\n\n\t/* We only copy for COPY FROM (which copies into a hypertable). Since\n\t * hypertable data are in the hypertable chunks and no data would be\n\t * copied, we skip the copy for COPY TO, but print an informative\n\t * message. */\n\tif (!stmt->is_from || !stmt->relation)\n\t{\n\t\tif (ht && stmt->relation)\n\t\t\tereport(NOTICE,\n\t\t\t\t\t(errmsg(\"hypertable data are in the chunks, no data will be copied\"),\n\t\t\t\t\t errdetail(\"Data for hypertables are stored in the chunks of a hypertable so \"\n\t\t\t\t\t\t\t   \"COPY TO of a hypertable will not copy any data.\"),\n\t\t\t\t\t errhint(\"Use \\\"COPY (SELECT * FROM <hypertable>) TO ...\\\" to copy all data in \"\n\t\t\t\t\t\t\t \"hypertable, or copy each chunk individually.\")));\n\t\tif (hcache)\n\t\t\tts_cache_release(&hcache);\n\n\t\treturn DDL_CONTINUE;\n\t}\n\n\tPreventCommandIfReadOnly(\"COPY FROM\");\n\n\t/* Performs acl check in here inside `copy_security_check` */\n\ttimescaledb_DoCopy(stmt, args->query_string, &processed, ht);\n\n\targs->completion_tag->commandTag = CMDTAG_COPY;\n\targs->completion_tag->nprocessed = processed;\n\n\tts_cache_release(&hcache);\n\n\tts_end_tss_store_callback(args->query_string,\n\t\t\t\t\t\t\t  args->pstmt->stmt_location,\n\t\t\t\t\t\t\t  args->pstmt->stmt_len,\n\t\t\t\t\t\t\t  args->pstmt->queryId,\n\t\t\t\t\t\t\t  args->completion_tag->nprocessed);\n\n\treturn DDL_DONE;\n}\n\ntypedef void (*process_chunk_t)(Hypertable *ht, Oid chunk_relid, void *arg);\ntypedef void (*mt_process_chunk_t)(int32 hypertable_id, Oid chunk_relid, void *arg);\n\n/*\n * Applies a function to each chunk of a hypertable.\n *\n * Returns the number of processed chunks, or -1 if the table was not a\n * hypertable.\n */\nstatic int\nforeach_chunk(Hypertable *ht, process_chunk_t process_chunk, void *arg)\n{\n\tList *chunks;\n\tListCell *lc;\n\tint n = 0;\n\n\tif (NULL == ht)\n\t\treturn -1;\n\n\tchunks = find_inheritance_children(ht->main_table_relid, NoLock);\n\n\tforeach (lc, chunks)\n\t{\n\t\tprocess_chunk(ht, lfirst_oid(lc), arg);\n\t\tn++;\n\t}\n\n\treturn n;\n}\n\n/*\n * Applies a function to each compressed internal chunk of a hypertable.\n *\n * Returns the number of processed chunks, or -1 if the table was not a\n * hypertable.\n */\nstatic int\nforeach_compressed_chunk(Hypertable *ht, process_chunk_t process_chunk, void *arg)\n{\n\tList *chunks;\n\tListCell *lc;\n\tint n = 0;\n\n\tif (!ht || !ht->fd.compressed_hypertable_id)\n\t\treturn -1;\n\n\tchunks = ts_chunk_get_by_hypertable_id(ht->fd.compressed_hypertable_id);\n\n\tforeach (lc, chunks)\n\t{\n\t\tChunk *chunk = lfirst(lc);\n\t\tprocess_chunk(ht, chunk->table_id, arg);\n\t\tn++;\n\t}\n\n\treturn n;\n}\n\nstatic int\nforeach_chunk_multitransaction(Oid relid, MemoryContext mctx, mt_process_chunk_t process_chunk,\n\t\t\t\t\t\t\t   void *arg)\n{\n\tCache *hcache;\n\tHypertable *ht;\n\tint32 hypertable_id;\n\tList *chunks;\n\tListCell *lc;\n\tint num_chunks = -1;\n\n\tStartTransactionCommand();\n\tMemoryContextSwitchTo(mctx);\n\tLockRelationOid(relid, AccessShareLock);\n\n\tht = ts_hypertable_cache_get_cache_and_entry(relid, CACHE_FLAG_MISSING_OK, &hcache);\n\tif (NULL == ht)\n\t{\n\t\tts_cache_release(&hcache);\n\t\tCommitTransactionCommand();\n\t\treturn -1;\n\t}\n\n\thypertable_id = ht->fd.id;\n\tchunks = find_inheritance_children(ht->main_table_relid, NoLock);\n\n\tts_cache_release(&hcache);\n\tCommitTransactionCommand();\n\n\tnum_chunks = list_length(chunks);\n\tforeach (lc, chunks)\n\t{\n\t\tprocess_chunk(hypertable_id, lfirst_oid(lc), arg);\n\t}\n\n\tlist_free(chunks);\n\n\treturn num_chunks;\n}\n\ntypedef struct VacuumCtx\n{\n\tbool is_vacuumfull;\n\tVacuumRelation *ht_vacuum_rel;\n\tList *chunk_rels;\n\tList *rebuild_columnstore_chunk_oids;\n} VacuumCtx;\n\nstatic bool\nchunk_has_missing_attrs(Chunk *chunk)\n{\n\tbool has_missing_attrs = false;\n\tRelation ht_rel = relation_open(chunk->hypertable_relid, AccessShareLock);\n\tRelation chunk_rel = relation_open(chunk->table_id, AccessShareLock);\n\tTupleDesc tupdesc = RelationGetDescr(chunk_rel);\n\n\tfor (int i = 0; i < tupdesc->natts; i++)\n\t{\n\t\tForm_pg_attribute att = TupleDescAttr(tupdesc, i);\n\n\t\tif (att->atthasmissing)\n\t\t{\n\t\t\thas_missing_attrs = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\trelation_close(chunk_rel, AccessShareLock);\n\trelation_close(ht_rel, AccessShareLock);\n\treturn has_missing_attrs;\n}\n\nstatic void\nregister_chunk_for_rebuild_if_needed(Oid chunk_relid, VacuumCtx *ctx)\n{\n\t/* Only VACUUM FULL does a complete table rewrite */\n\tif (!ctx->is_vacuumfull)\n\t\treturn;\n\n\tChunk *chunk = ts_chunk_get_by_relid(chunk_relid, false);\n\tif (!chunk)\n\t\treturn;\n\n\t/*\n\t * VACUUM FULL will materialize missing attributes. Propagate\n\t * these changes to columnstore data by rebuilding it.\n\t */\n\tif (chunk_has_missing_attrs(chunk))\n\t{\n\t\t/*\n\t\t * Frozen chunks are not allowed to add columns with defaults\n\t\t */\n\t\tEnsure(!ts_chunk_is_frozen(chunk),\n\t\t\t   \"chunk \\\"%s.%s\\\" was altered unsafely after it was frozen\",\n\t\t\t   NameStr(chunk->fd.schema_name),\n\t\t\t   NameStr(chunk->fd.table_name));\n\t\tctx->rebuild_columnstore_chunk_oids =\n\t\t\tlappend_oid(ctx->rebuild_columnstore_chunk_oids, chunk_relid);\n\t}\n}\n\n/* Adds a chunk to the list of tables to be vacuumed */\nstatic void\nadd_chunk_to_vacuum(Hypertable *ht, Oid chunk_relid, void *arg)\n{\n\tVacuumCtx *ctx = (VacuumCtx *) arg;\n\tChunk *chunk = ts_chunk_get_by_relid(chunk_relid, true);\n\tVacuumRelation *chunk_vacuum_rel;\n\tRangeVar *chunk_range_var;\n\n\tchunk_range_var = copyObject(ctx->ht_vacuum_rel->relation);\n\tchunk_range_var->relname = NameStr(chunk->fd.table_name);\n\tchunk_range_var->schemaname = NameStr(chunk->fd.schema_name);\n\tchunk_vacuum_rel =\n\t\tmakeVacuumRelation(chunk_range_var, chunk_relid, ctx->ht_vacuum_rel->va_cols);\n\tctx->chunk_rels = lappend(ctx->chunk_rels, chunk_vacuum_rel);\n\n\t/* If we have a compressed chunk make sure to analyze it as well */\n\tif (chunk->fd.compressed_chunk_id != INVALID_CHUNK_ID)\n\t{\n\t\tChunk *comp_chunk = ts_chunk_get_by_id(chunk->fd.compressed_chunk_id, false);\n\t\t/* Compressed chunk might be missing due to concurrent operations */\n\t\tif (comp_chunk)\n\t\t{\n\t\t\tchunk_vacuum_rel = makeVacuumRelation(NULL, comp_chunk->table_id, NIL);\n\t\t\tctx->chunk_rels = lappend(ctx->chunk_rels, chunk_vacuum_rel);\n\t\t}\n\t}\n\n\tregister_chunk_for_rebuild_if_needed(chunk_relid, ctx);\n}\n\n/*\n * Construct a list of VacuumRelations for all vacuumable rels in\n * the current database.  This is similar to the PostgresQL get_all_vacuum_rels\n * from vacuum.c.\n */\nstatic List *\nts_get_all_vacuum_rels(bool is_vacuumcmd, VacuumCtx *ctx)\n{\n\tList *vacrels = NIL;\n\tRelation pgclass;\n\tTableScanDesc scan;\n\tHeapTuple tuple;\n\tCache *hcache = ts_hypertable_cache_pin();\n\n\tpgclass = table_open(RelationRelationId, AccessShareLock);\n\n\tscan = table_beginscan_catalog(pgclass, 0, NULL);\n\n\twhile ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)\n\t{\n\t\tForm_pg_class classform = (Form_pg_class) GETSTRUCT(tuple);\n\t\tOid relid;\n\n\t\trelid = classform->oid;\n\n\t\t/* check permissions of relation */\n\t\tif (!vacuum_is_permitted_for_relation_compat(relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t classform,\n\t\t\t\t\t\t\t\t\t\t\t\t\t is_vacuumcmd ? VACOPT_VACUUM : VACOPT_ANALYZE))\n\t\t\tcontinue;\n\n\t\t/*\n\t\t * We include partitioned tables here; depending on which operation is\n\t\t * to be performed, caller will decide whether to process or ignore\n\t\t * them.\n\t\t */\n\t\tif (classform->relkind != RELKIND_RELATION && classform->relkind != RELKIND_MATVIEW &&\n\t\t\tclassform->relkind != RELKIND_PARTITIONED_TABLE)\n\t\t\tcontinue;\n\n\t\t/*\n\t\t * Build VacuumRelation(s) specifying the table OIDs to be processed.\n\t\t * We omit a RangeVar since it wouldn't be appropriate to complain\n\t\t * about failure to open one of these relations later.\n\t\t */\n\t\tvacrels = lappend(vacrels, makeVacuumRelation(NULL, relid, NIL));\n\n\t\tregister_chunk_for_rebuild_if_needed(relid, ctx);\n\t}\n\n\ttable_endscan(scan);\n\ttable_close(pgclass, AccessShareLock);\n\tts_cache_release(&hcache);\n\n\treturn vacrels;\n}\n\n/* Vacuums/Analyzes a hypertable and all of it's chunks */\nstatic DDLResult\nprocess_vacuum(ProcessUtilityArgs *args)\n{\n\tVacuumStmt *stmt = (VacuumStmt *) args->parsetree;\n\tbool is_toplevel = (args->context == PROCESS_UTILITY_TOPLEVEL);\n\tVacuumCtx ctx = {\n\t\t.is_vacuumfull = false,\n\t\t.ht_vacuum_rel = NULL,\n\t\t.chunk_rels = NIL,\n\t\t.rebuild_columnstore_chunk_oids = NIL,\n\t};\n\tListCell *lc;\n\tHypertable *ht;\n\tList *vacuum_rels = NIL;\n\tbool is_vacuumcmd;\n\t/* save original VacuumRelation list */\n\tList *saved_stmt_rels = stmt->rels;\n\n\tis_vacuumcmd = stmt->is_vacuumcmd;\n\n\t/* Look for new option ONLY_DATABASE_STATS and FULL */\n\tforeach (lc, stmt->options)\n\t{\n\t\tDefElem *opt = (DefElem *) lfirst(lc);\n#if PG16_GE\n\t\t/* if \"only_database_stats\" is defined then don't execute our custom code and return to\n\t\t * the postgres execution for the proper validations */\n\t\tif (is_vacuumcmd && strcmp(opt->defname, \"only_database_stats\") == 0)\n\t\t\treturn DDL_CONTINUE;\n#endif\n\t\tif (strcmp(opt->defname, \"full\") == 0)\n\t\t\tctx.is_vacuumfull = defGetBoolean(opt);\n\t}\n\n\tif (stmt->rels == NIL)\n\t\tvacuum_rels = ts_get_all_vacuum_rels(is_vacuumcmd, &ctx);\n\telse\n\t{\n\t\tCache *hcache = ts_hypertable_cache_pin();\n\n\t\tforeach (lc, stmt->rels)\n\t\t{\n\t\t\tVacuumRelation *vacuum_rel = lfirst_node(VacuumRelation, lc);\n\t\t\tOid table_relid = vacuum_rel->oid;\n\n\t\t\tif (!OidIsValid(table_relid) && vacuum_rel->relation != NULL)\n\t\t\t\ttable_relid = RangeVarGetRelid(vacuum_rel->relation, NoLock, true);\n\n\t\t\tif (OidIsValid(table_relid))\n\t\t\t{\n\t\t\t\tht = ts_hypertable_cache_get_entry(hcache, table_relid, CACHE_FLAG_MISSING_OK);\n\n\t\t\t\tif (ht)\n\t\t\t\t{\n\t\t\t\t\tctx.ht_vacuum_rel = vacuum_rel;\n\t\t\t\t\tforeach_chunk(ht, add_chunk_to_vacuum, &ctx);\n\t\t\t\t}\n\t\t\t}\n\t\t\tvacuum_rels = lappend(vacuum_rels, vacuum_rel);\n\t\t}\n\t\tts_cache_release(&hcache);\n\t}\n\n\tstmt->rels = list_concat(ctx.chunk_rels, vacuum_rels);\n\n\t/* The list of rels to vacuum could be empty if we are only vacuuming a\n\t * tiered hypertable with no local chunks. In that case, we don't want to vacuum locally. */\n\tif (list_length(stmt->rels) > 0)\n\t{\n\t\tPreventCommandDuringRecovery(is_vacuumcmd ? \"VACUUM\" : \"ANALYZE\");\n\n\t\tif (list_length(ctx.rebuild_columnstore_chunk_oids) > 0)\n\t\t{\n\t\t\t/* there are chunks that need rebuilding */\n\t\t\tListCell *lc;\n\t\t\tforeach (lc, ctx.rebuild_columnstore_chunk_oids)\n\t\t\t{\n\t\t\t\tOid chunk_relid = lfirst_oid(lc);\n\t\t\t\t(void) DirectFunctionCall1(ts_cm_functions->rebuild_columnstore,\n\t\t\t\t\t\t\t\t\t\t   ObjectIdGetDatum(chunk_relid));\n\t\t\t}\n\t\t}\n\t\t/* ACL permission checks inside vacuum_rel and analyze_rel called by this ExecVacuum */\n\t\tExecVacuum(args->parse_state, stmt, is_toplevel);\n\t}\n\t/*\n\tRestore original list. stmt->rels which has references to\n\tVacuumRelation list is freed up, however VacuumStmt is not\n\tcleaned up because of which there is a crash.\n\t*/\n\tstmt->rels = saved_stmt_rels;\n\treturn DDL_DONE;\n}\n\nstatic void\nprocess_truncate_chunk(Hypertable *ht, Oid chunk_relid, void *arg)\n{\n\tTruncateStmt *stmt = arg;\n\tObjectAddress objaddr = {\n\t\t.classId = RelationRelationId,\n\t\t.objectId = chunk_relid,\n\t};\n\n\tperformDeletion(&objaddr, stmt->behavior, 0);\n}\n\nstatic bool\nrelation_should_recurse(RangeVar *rv)\n{\n\treturn rv->inh;\n}\n\n/* handle forwarding TRUNCATEs to the chunks of a hypertable */\nstatic void\nhandle_truncate_hypertable(ProcessUtilityArgs *args, TruncateStmt *stmt, Hypertable *ht)\n{\n\t/* Delete the metadata */\n\tts_chunk_delete_by_hypertable_id(ht->fd.id);\n\n\t/* Drop the chunk tables */\n\tforeach_chunk(ht, process_truncate_chunk, stmt);\n}\n\n/*\n * Truncate a hypertable.\n */\nstatic DDLResult\nprocess_truncate(ProcessUtilityArgs *args)\n{\n\tTruncateStmt *stmt = (TruncateStmt *) args->parsetree;\n\tCache *hcache = ts_hypertable_cache_pin();\n\tListCell *cell;\n\tList *hypertables = NIL, *mat_hypertables = NIL;\n\tList *relations = NIL;\n\tbool list_changed = false;\n\tMemoryContext oldctx, parsetreectx = GetMemoryChunkContext(args->parsetree);\n\n\t/* For all hypertables, we drop the now empty chunks. We also propagate the\n\t * TRUNCATE call to the compressed version of the hypertable, if it exists.\n\t */\n\tforeach (cell, stmt->relations)\n\t{\n\t\tRangeVar *rv = lfirst(cell);\n\t\tOid relid;\n\t\tbool list_append = false;\n\n\t\tif (!rv)\n\t\t\tcontinue;\n\n\t\t/* Grab AccessExclusiveLock, same as regular TRUNCATE processing grabs\n\t\t * below. We just do it preemptively here. */\n\t\trelid = RangeVarGetRelid(rv, AccessExclusiveLock, true);\n\n\t\tif (!OidIsValid(relid))\n\t\t{\n\t\t\t/* We should add invalid relations to the list to raise error on the\n\t\t\t * standard_ProcessUtility when we're trying to TRUNCATE a nonexistent relation */\n\t\t\tlist_append = true;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tswitch (get_rel_relkind(relid))\n\t\t\t{\n\t\t\t\tcase RELKIND_VIEW:\n\t\t\t\t{\n\t\t\t\t\tContinuousAgg *cagg = ts_continuous_agg_find_by_relid(relid);\n\n\t\t\t\t\tif (cagg)\n\t\t\t\t\t{\n\t\t\t\t\t\tHypertable *mat_ht, *raw_ht;\n\n\t\t\t\t\t\tif (!relation_should_recurse(rv))\n\t\t\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t\t\t(errcode(ERRCODE_WRONG_OBJECT_TYPE),\n\t\t\t\t\t\t\t\t\t errmsg(\"cannot truncate only a continuous aggregate\")));\n\n\t\t\t\t\t\tmat_ht = ts_hypertable_get_by_id(cagg->data.mat_hypertable_id);\n\t\t\t\t\t\tAssert(mat_ht != NULL);\n\n\t\t\t\t\t\t/* Create list item into the same context of the list */\n\t\t\t\t\t\toldctx = MemoryContextSwitchTo(parsetreectx);\n\t\t\t\t\t\trv = makeRangeVar(NameStr(mat_ht->fd.schema_name),\n\t\t\t\t\t\t\t\t\t\t  NameStr(mat_ht->fd.table_name),\n\t\t\t\t\t\t\t\t\t\t  -1);\n\t\t\t\t\t\tMemoryContextSwitchTo(oldctx);\n\n\t\t\t\t\t\t/* Invalidate the entire continuous aggregate since it no\n\t\t\t\t\t\t * longer has any data */\n\t\t\t\t\t\traw_ht = ts_hypertable_get_by_id(cagg->data.raw_hypertable_id);\n\t\t\t\t\t\tAssert(raw_ht != NULL);\n\t\t\t\t\t\tts_cm_functions->continuous_agg_invalidate_mat_ht(raw_ht,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  mat_ht,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  TS_TIME_NOBEGIN,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  TS_TIME_NOEND);\n\n\t\t\t\t\t\t/* Additionally, this cagg's materialization hypertable could be the\n\t\t\t\t\t\t * underlying hypertable for other caggs defined on top of it, in that case\n\t\t\t\t\t\t * we must update the hypertable invalidation log */\n\t\t\t\t\t\tContinuousAggHypertableStatus agg_status;\n\n\t\t\t\t\t\tagg_status = ts_continuous_agg_hypertable_status(mat_ht->fd.id);\n\t\t\t\t\t\tif (agg_status & HypertableIsRawTable)\n\t\t\t\t\t\t\tts_cm_functions->continuous_agg_invalidate_raw_ht(mat_ht,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  TS_TIME_NOBEGIN,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  TS_TIME_NOEND);\n\n\t\t\t\t\t\t/* mark list as changed because we'll add the materialization hypertable */\n\t\t\t\t\t\tlist_changed = true;\n\n\t\t\t\t\t\t/* list of materialization hypertables to reset the watermark */\n\t\t\t\t\t\tmat_hypertables = lappend(mat_hypertables, mat_ht);\n\n\t\t\t\t\t\t/* include the materialization hypertable to the list to be handled by the\n\t\t\t\t\t\t * proper hypertable and chunk truncate code-path later */\n\t\t\t\t\t\thypertables = lappend(hypertables, mat_ht);\n\t\t\t\t\t}\n\n\t\t\t\t\tlist_append = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase RELKIND_RELATION:\n\t\t\t\t/* TRUNCATE for foreign tables not implemented yet. This will raise an error. */\n\t\t\t\tcase RELKIND_FOREIGN_TABLE:\n\t\t\t\t{\n\t\t\t\t\tlist_append = true;\n\n\t\t\t\t\tHypertable *ht =\n\t\t\t\t\t\tts_hypertable_cache_get_entry(hcache, relid, CACHE_FLAG_MISSING_OK);\n\n\t\t\t\t\tif (ht)\n\t\t\t\t\t{\n\t\t\t\t\t\tContinuousAggHypertableStatus agg_status;\n\n\t\t\t\t\t\tagg_status = ts_continuous_agg_hypertable_status(ht->fd.id);\n\n\t\t\t\t\t\tif ((agg_status & HypertableIsMaterialization) != 0)\n\t\t\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t\t\t\t errmsg(\"cannot TRUNCATE a hypertable underlying a continuous \"\n\t\t\t\t\t\t\t\t\t\t\t\"aggregate\"),\n\t\t\t\t\t\t\t\t\t errhint(\"TRUNCATE the continuous aggregate instead.\")));\n\n\t\t\t\t\t\tif (agg_status == HypertableIsRawTable)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t/* The truncation invalidates all associated continuous aggregates */\n\t\t\t\t\t\t\tts_cm_functions->continuous_agg_invalidate_raw_ht(ht,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  TS_TIME_NOBEGIN,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  TS_TIME_NOEND);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!relation_should_recurse(rv))\n\t\t\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t\t\t(errcode(ERRCODE_WRONG_OBJECT_TYPE),\n\t\t\t\t\t\t\t\t\t errmsg(\"cannot truncate only a hypertable\"),\n\t\t\t\t\t\t\t\t\t errhint(\"Do not specify the ONLY keyword, or use truncate\"\n\t\t\t\t\t\t\t\t\t\t\t \" only on the chunks directly.\")));\n\n\t\t\t\t\t\thypertables = lappend(hypertables, ht);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tChunk *chunk = ts_chunk_get_by_relid(relid, false);\n\t\t\t\t\tif (chunk != NULL)\n\t\t\t\t\t{ /* this is a chunk */\n\t\t\t\t\t\tht = ts_hypertable_cache_get_entry(hcache,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   chunk->hypertable_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   CACHE_FLAG_NONE);\n\n\t\t\t\t\t\t/*\n\t\t\t\t\t\t * Block direct TRUNCATE on frozen chunk.\n\t\t\t\t\t\t */\n\t\t\t\t\t\tif (ts_chunk_is_frozen(chunk))\n\t\t\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t\t\t\t errmsg(\"cannot TRUNCATE frozen chunk \\\"%s\\\"\",\n\t\t\t\t\t\t\t\t\t\t\tget_rel_name(relid)),\n\t\t\t\t\t\t\t\t\t errhint(\"Unfreeze the chunk to TRUNCATE it.\")));\n\n\t\t\t\t\t\tAssert(ht != NULL);\n\n\t\t\t\t\t\t/* If the hypertable has continuous aggregates, then invalidate\n\t\t\t\t\t\t * the truncated region. */\n\t\t\t\t\t\tif (ts_continuous_agg_hypertable_status(ht->fd.id) == HypertableIsRawTable)\n\t\t\t\t\t\t\tts_continuous_agg_invalidate_chunk(ht, chunk);\n\t\t\t\t\t\t/* Truncate the compressed chunk too */\n\t\t\t\t\t\tif (chunk->fd.compressed_chunk_id != INVALID_CHUNK_ID)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tChunk *compressed_chunk =\n\t\t\t\t\t\t\t\tts_chunk_get_by_id(chunk->fd.compressed_chunk_id, false);\n\t\t\t\t\t\t\tif (compressed_chunk != NULL)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t/* Create list item into the same context of the list. */\n\t\t\t\t\t\t\t\toldctx = MemoryContextSwitchTo(parsetreectx);\n\t\t\t\t\t\t\t\trv = makeRangeVar(NameStr(compressed_chunk->fd.schema_name),\n\t\t\t\t\t\t\t\t\t\t\t\t  NameStr(compressed_chunk->fd.table_name),\n\t\t\t\t\t\t\t\t\t\t\t\t  -1);\n\t\t\t\t\t\t\t\tMemoryContextSwitchTo(oldctx);\n\t\t\t\t\t\t\t\tlist_changed = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t/* if the chunk has statistics enabled on it then reset them */\n\t\t\t\t\t\tts_chunk_column_stats_reset_by_chunk_id(chunk->fd.id);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\t/*\n\t\t\t\t\t * Do nothing for other relation types. This is mostly to\n\t\t\t\t\t * placate the static analyzers.\n\t\t\t\t\t */\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t/* Append the relation to the list in the same parse tree memory context */\n\t\tif (list_append)\n\t\t{\n\t\t\tMemoryContext oldctx = MemoryContextSwitchTo(parsetreectx);\n\t\t\trelations = lappend(relations, rv);\n\t\t\tMemoryContextSwitchTo(oldctx);\n\t\t}\n\t}\n\n\t/* Update relations list just when changed to include only tables\n\t * that hold data.\n\t */\n\tif (list_changed)\n\t\tstmt->relations = relations;\n\n\tif (stmt->relations != NIL)\n\t{\n\t\t/* Call standard PostgreSQL handler for remaining tables */\n\t\tprev_ProcessUtility(args);\n\t}\n\n\t/* For all hypertables, we drop the now empty chunks */\n\tforeach (cell, hypertables)\n\t{\n\t\tHypertable *ht = lfirst(cell);\n\n\t\tAssert(ht != NULL);\n\n\t\thandle_truncate_hypertable(args, stmt, ht);\n\n\t\t/* propagate to the compressed hypertable */\n\t\tif (TS_HYPERTABLE_HAS_COMPRESSION_TABLE(ht))\n\t\t{\n\t\t\tHypertable *compressed_ht =\n\t\t\t\tts_hypertable_cache_get_entry_by_id(hcache, ht->fd.compressed_hypertable_id);\n\t\t\tTruncateStmt compressed_stmt = *stmt;\n\t\t\tcompressed_stmt.relations =\n\t\t\t\tlist_make1(makeRangeVar(NameStr(compressed_ht->fd.schema_name),\n\t\t\t\t\t\t\t\t\t\tNameStr(compressed_ht->fd.table_name),\n\t\t\t\t\t\t\t\t\t\t-1));\n\n\t\t\t/* TRUNCATE the compressed hypertable */\n\t\t\tExecuteTruncate(&compressed_stmt);\n\n\t\t\thandle_truncate_hypertable(args, stmt, compressed_ht);\n\t\t}\n\t}\n\n\t/* For all materialization hypertables, reset the watermark */\n\tforeach (cell, mat_hypertables)\n\t{\n\t\tHypertable *mat_ht = lfirst(cell);\n\n\t\tAssert(mat_ht != NULL);\n\n\t\t/* Force update the watermark */\n\t\tbool isnull;\n\t\tint64 watermark = ts_hypertable_get_open_dim_max_value(mat_ht, 0, &isnull);\n\t\tts_cagg_watermark_update(mat_ht, watermark, isnull, true);\n\t}\n\n\tts_cache_release(&hcache);\n\n\treturn DDL_DONE;\n}\n\nstatic void\nprocess_drop_table_chunk(Hypertable *ht, Oid chunk_relid, void *arg)\n{\n\tDropStmt *stmt = arg;\n\tObjectAddress objaddr = {\n\t\t.classId = RelationRelationId,\n\t\t.objectId = chunk_relid,\n\t};\n\n\tts_compression_settings_delete(chunk_relid);\n\tts_chunk_rewrite_delete(chunk_relid, false);\n\tperformDeletion(&objaddr, stmt->behavior, 0);\n}\n\n/* Block drop compressed chunks directly and drop corresponding compressed chunks if\n * cascade is on. */\nstatic void\nprocess_drop_chunk(ProcessUtilityArgs *args, DropStmt *stmt)\n{\n\tListCell *lc;\n\tCache *hcache = ts_hypertable_cache_pin();\n\n\tforeach (lc, stmt->objects)\n\t{\n\t\tList *object = lfirst(lc);\n\t\tRangeVar *relation = makeRangeVarFromNameList(object);\n\t\tScanTupLock slice_lock = {\n\t\t\t.lockmode = LockTupleExclusive,\n\t\t\t.waitpolicy = LockWaitBlock,\n\t\t\t.lockflags = TUPLE_LOCK_FLAG_FIND_LAST_VERSION,\n\t\t};\n\t\tChunk *chunk;\n\n\t\tif (NULL == relation)\n\t\t\tcontinue;\n\n\t\tchunk = ts_chunk_get_by_name_with_memory_context(relation->schemaname,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t relation->relname,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t AccessExclusiveLock,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t &slice_lock,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t CurrentMemoryContext,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t false);\n\n\t\tif (chunk != NULL)\n\t\t{\n\t\t\tHypertable *ht;\n\n\t\t\tif (ts_chunk_contains_compressed_data(chunk))\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t errmsg(\"dropping columnstore chunks not supported\"),\n\t\t\t\t\t\t errhint(\"Please drop the corresponding chunk on the rowstore hypertable \"\n\t\t\t\t\t\t\t\t \"instead.\")));\n\n\t\t\t/* if cascade is enabled, delete the compressed chunk with cascade too. Otherwise\n\t\t\t *  it would be blocked if there are dependent objects */\n\t\t\tif (stmt->behavior == DROP_CASCADE && chunk->fd.compressed_chunk_id != INVALID_CHUNK_ID)\n\t\t\t{\n\t\t\t\tChunk *compressed_chunk =\n\t\t\t\t\tts_chunk_get_by_id_with_slice_lock(chunk->fd.compressed_chunk_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   AccessExclusiveLock,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   &slice_lock,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   false);\n\t\t\t\t/* The chunk may have been delete by a CASCADE */\n\t\t\t\tif (compressed_chunk != NULL)\n\t\t\t\t\tts_chunk_drop(compressed_chunk, stmt->behavior, DEBUG1);\n\t\t\t}\n\n\t\t\tht = ts_hypertable_cache_get_entry(hcache, chunk->hypertable_relid, CACHE_FLAG_NONE);\n\n\t\t\tAssert(ht != NULL);\n\n\t\t\t/* If the hypertable has continuous aggregates, then invalidate\n\t\t\t * the dropped region. */\n\t\t\tif (ts_continuous_agg_hypertable_status(ht->fd.id) == HypertableIsRawTable)\n\t\t\t\tts_continuous_agg_invalidate_chunk(ht, chunk);\n\t\t}\n\t}\n\n\tts_cache_release(&hcache);\n}\n\n/*\n * We need to drop hypertable chunks and associated compressed hypertables\n * when dropping hypertables to maintain correct semantics wrt CASCADE modifiers.\n * Also block dropping compressed hypertables directly.\n */\nstatic DDLResult\nprocess_drop_hypertable(ProcessUtilityArgs *args, DropStmt *stmt)\n{\n\tCache *hcache = ts_hypertable_cache_pin();\n\tListCell *lc;\n\tDDLResult result = DDL_CONTINUE;\n\n\tforeach (lc, stmt->objects)\n\t{\n\t\tList *object = lfirst(lc);\n\t\tRangeVar *relation = makeRangeVarFromNameList(object);\n\t\tOid relid;\n\n\t\tif (NULL == relation)\n\t\t\tcontinue;\n\n\t\trelid = RangeVarGetRelid(relation, NoLock, true);\n\n\t\tif (OidIsValid(relid))\n\t\t{\n\t\t\tHypertable *ht;\n\n\t\t\tht = ts_hypertable_cache_get_entry(hcache, relid, CACHE_FLAG_MISSING_OK);\n\n\t\t\tif (ht)\n\t\t\t{\n\t\t\t\tif (TS_HYPERTABLE_IS_INTERNAL_COMPRESSION_TABLE(ht))\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t\t errmsg(\"dropping columnstore hypertables not supported\"),\n\t\t\t\t\t\t\t errhint(\"Please drop the corresponding rowstore hypertable \"\n\t\t\t\t\t\t\t\t\t \"instead.\")));\n\n\t\t\t\t/*\n\t\t\t\t *  We need to drop hypertable chunks before the hypertable to avoid the need\n\t\t\t\t *  to CASCADE such drops;\n\t\t\t\t */\n\t\t\t\tforeach_chunk(ht, process_drop_table_chunk, stmt);\n\t\t\t\t/* The usual path for deleting an associated compressed hypertable uses\n\t\t\t\t * DROP_RESTRICT But if we are using DROP_CASCADE we should propagate that down to\n\t\t\t\t * the compressed hypertable.\n\t\t\t\t */\n\t\t\t\tif (stmt->behavior == DROP_CASCADE && TS_HYPERTABLE_HAS_COMPRESSION_TABLE(ht))\n\t\t\t\t{\n\t\t\t\t\tHypertable *compressed_hypertable =\n\t\t\t\t\t\tts_hypertable_get_by_id(ht->fd.compressed_hypertable_id);\n\t\t\t\t\tList *chunks = ts_chunk_get_by_hypertable_id(ht->fd.compressed_hypertable_id);\n\t\t\t\t\tforeach (lc, chunks)\n\t\t\t\t\t{\n\t\t\t\t\t\tChunk *chunk = lfirst(lc);\n\n\t\t\t\t\t\tif (OidIsValid(chunk->table_id))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tObjectAddress chunk_addr = (ObjectAddress){\n\t\t\t\t\t\t\t\t.classId = RelationRelationId,\n\t\t\t\t\t\t\t\t.objectId = chunk->table_id,\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t/* Drop the postgres table */\n\t\t\t\t\t\t\tperformDeletion(&chunk_addr, stmt->behavior, 0);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tts_hypertable_drop(compressed_hypertable, DROP_CASCADE);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresult = DDL_DONE;\n\t\t}\n\t}\n\n\tts_cache_release(&hcache);\n\n\treturn result;\n}\n\n/*\n *  We need to ensure that DROP INDEX uses only one hypertable per query,\n *  otherwise query string might not be reusable for execution on a\n *  data node.\n */\nstatic void\nprocess_drop_hypertable_index(ProcessUtilityArgs *args, DropStmt *stmt)\n{\n\tCache *hcache = ts_hypertable_cache_pin();\n\tListCell *lc;\n\n\tforeach (lc, stmt->objects)\n\t{\n\t\tList *object = lfirst(lc);\n\t\tRangeVar *relation = makeRangeVarFromNameList(object);\n\t\tOid ht_relid, index_relid;\n\t\tHypertable *ht;\n\n\t\tif (NULL == relation)\n\t\t\tcontinue;\n\n\t\tindex_relid = RangeVarGetRelid(relation, NoLock, true);\n\t\tif (!OidIsValid(index_relid))\n\t\t\tcontinue;\n\n\t\tht_relid = IndexGetRelation(index_relid, true);\n\t\tif (!OidIsValid(ht_relid))\n\t\t\tcontinue;\n\n\t\tht = ts_hypertable_cache_get_entry(hcache, ht_relid, CACHE_FLAG_MISSING_OK);\n\t\tif (ht)\n\t\t{\n\t\t\tList *chunk_indexes = ts_chunk_index_get_mappings(ht, index_relid);\n\t\t\tListCell *lc_index;\n\t\t\tforeach (lc_index, chunk_indexes)\n\t\t\t{\n\t\t\t\tChunkIndexMapping *mapping = lfirst(lc_index);\n\t\t\t\tOid chunk_relid = mapping->indexoid;\n\t\t\t\tchar *schema_name = get_namespace_name(get_rel_namespace(chunk_relid));\n\t\t\t\tchar *index_name = get_rel_name(chunk_relid);\n\t\t\t\tstmt->objects =\n\t\t\t\t\tlappend(stmt->objects,\n\t\t\t\t\t\t\tlist_make2(makeString(schema_name), makeString(index_name)));\n\t\t\t}\n\t\t}\n\t}\n\tts_cache_release(&hcache);\n}\n\n/* Note that DROP TABLESPACE does not have a hook in event triggers so cannot go\n * through process_ddl_sql_drop */\nstatic DDLResult\nprocess_drop_tablespace(ProcessUtilityArgs *args)\n{\n\tDropTableSpaceStmt *stmt = (DropTableSpaceStmt *) args->parsetree;\n\tint count = ts_tablespace_count_attached(stmt->tablespacename);\n\n\tif (count > 0)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"tablespace \\\"%s\\\" is still attached to %d hypertables\",\n\t\t\t\t\t\tstmt->tablespacename,\n\t\t\t\t\t\tcount),\n\t\t\t\t errhint(\"Detach the tablespace from all hypertables before removing it.\")));\n\n\treturn DDL_CONTINUE;\n}\n\nstatic void\nprocess_grant_add_by_rel(GrantStmt *stmt, RangeVar *relation)\n{\n\tstmt->objects = lappend(stmt->objects, relation);\n}\n\n/*\n * If it is a \"GRANT/REVOKE ON ALL TABLES IN SCHEMA\" operation then we need to check if\n * the rangevar was already added when we added all objects inside the SCHEMA\n *\n * This could get a little expensive for schemas containing a lot of objects..\n */\nstatic void\nprocess_grant_add_by_name(GrantStmt *stmt, bool was_schema_op, Name schema_name, Name table_name)\n{\n\tbool already_added = false;\n\n\tif (was_schema_op)\n\t\talready_added = check_table_in_rangevar_list(stmt->objects, schema_name, table_name);\n\n\tif (!already_added)\n\t\tprocess_grant_add_by_rel(stmt,\n\t\t\t\t\t\t\t\t makeRangeVar(NameStr(*schema_name), NameStr(*table_name), -1));\n}\n\nstatic void\nprocess_relations_in_namespace(GrantStmt *stmt, Name schema_name, Oid namespaceId, char relkind)\n{\n\tScanKeyData key[2];\n\tRelation rel;\n\tTableScanDesc scan;\n\tHeapTuple tuple;\n\n\tScanKeyInit(&key[0],\n\t\t\t\tAnum_pg_class_relnamespace,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_OIDEQ,\n\t\t\t\tObjectIdGetDatum(namespaceId));\n\tScanKeyInit(&key[1],\n\t\t\t\tAnum_pg_class_relkind,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_CHAREQ,\n\t\t\t\tCharGetDatum(relkind));\n\n\trel = table_open(RelationRelationId, AccessShareLock);\n\tscan = table_beginscan_catalog(rel, 2, key);\n\n\twhile ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)\n\t{\n\t\tName relname = palloc(NAMEDATALEN);\n\t\tnamestrcpy(relname, NameStr(((Form_pg_class) GETSTRUCT(tuple))->relname));\n\n\t\t/* these are being added for the first time into this list */\n\t\tprocess_grant_add_by_name(stmt, false, schema_name, relname);\n\t}\n\n\ttable_endscan(scan);\n\ttable_close(rel, AccessShareLock);\n}\n\n/*\n * For \"GRANT ALL ON ALL TABLES IN SCHEMA\" GrantStmt, the targtype field is ACL_TARGET_ALL_IN_SCHEMA\n * whereas in regular \"GRANT ON TABLE table_name\", the targtype field is ACL_TARGET_OBJECT. In the\n * latter case the objects list contains a list of relation range vars whereas in the former it is\n * the list of schema names.\n *\n * To make things work we change the targtype field from ACL_TARGET_ALL_IN_SCHEMA to\n * ACL_TARGET_OBJECT and then create a new list of rangevars of all relation type entities in it and\n * assign it to the \"stmt->objects\" field.\n *\n */\nstatic void\nprocess_grant_add_by_schema(GrantStmt *stmt)\n{\n\tListCell *cell;\n\tList *nspnames = stmt->objects;\n\n\t/*\n\t * We will be adding rangevars to the \"stmt->objects\" field in the loop below. So\n\t * we track the nspnames separately above and NIL out the objects list\n\t */\n\tstmt->objects = NIL;\n\tforeach (cell, nspnames)\n\t{\n\t\tchar *nspname = strVal(lfirst(cell));\n\t\tOid namespaceId = LookupExplicitNamespace(nspname, false);\n\t\tName schema;\n\n\t\tschema = (Name) palloc(NAMEDATALEN);\n\n\t\tnamestrcpy(schema, nspname);\n\n\t\t/* Inspired from objectsInSchemaToOids PG function */\n\t\tprocess_relations_in_namespace(stmt, schema, namespaceId, RELKIND_RELATION);\n\t\tprocess_relations_in_namespace(stmt, schema, namespaceId, RELKIND_VIEW);\n\t\tprocess_relations_in_namespace(stmt, schema, namespaceId, RELKIND_MATVIEW);\n\t\tprocess_relations_in_namespace(stmt, schema, namespaceId, RELKIND_FOREIGN_TABLE);\n\t\tprocess_relations_in_namespace(stmt, schema, namespaceId, RELKIND_PARTITIONED_TABLE);\n\t}\n\n\t/* change targtype to ACL_TARGET_OBJECT now */\n\tstmt->targtype = ACL_TARGET_OBJECT;\n}\n\n/*\n * Handle GRANT / REVOKE.\n *\n * A revoke is a GrantStmt with 'is_grant' set to false.\n */\nstatic DDLResult\nprocess_grant_and_revoke(ProcessUtilityArgs *args)\n{\n\tGrantStmt *stmt = (GrantStmt *) args->parsetree;\n\tDDLResult result = DDL_CONTINUE;\n\n\t/* We let the calling function handle anything that is not\n\t * ACL_TARGET_OBJECT or ACL_TARGET_ALL_IN_SCHEMA */\n\tif (stmt->targtype != ACL_TARGET_OBJECT && stmt->targtype != ACL_TARGET_ALL_IN_SCHEMA)\n\t\treturn DDL_CONTINUE;\n\n\tswitch (stmt->objtype)\n\t{\n\t\tcase OBJECT_TABLESPACE:\n\t\t\t/*\n\t\t\t * If we are granting on a tablespace, we need to apply the REVOKE\n\t\t\t * first to be able to check remaining permissions.\n\t\t\t */\n\t\t\tprev_ProcessUtility(args);\n\t\t\tts_tablespace_validate_revoke(stmt);\n\t\t\tresult = DDL_DONE;\n\t\t\tbreak;\n\n\t\tcase OBJECT_TABLE:\n\t\t\t/*\n\t\t\t * Collect the hypertables in the grant statement. We only need to\n\t\t\t * consider those when sending grants to other data nodes.\n\t\t\t */\n\t\t\t{\n\t\t\t\tCache *hcache;\n\t\t\t\tListCell *cell;\n\t\t\t\tList *saved_schema_objects = NIL;\n\t\t\t\tbool was_schema_op = false;\n\n\t\t\t\t/*\n\t\t\t\t * If it's a GRANT/REVOKE ALL IN SCHEMA then we need to collect all\n\t\t\t\t * objects in this schema and convert this into an ACL_TARGET_OBJECT\n\t\t\t\t * entry with its objects field pointing to rangevars\n\t\t\t\t */\n\t\t\t\tif (stmt->targtype == ACL_TARGET_ALL_IN_SCHEMA)\n\t\t\t\t{\n\t\t\t\t\tsaved_schema_objects = stmt->objects;\n\t\t\t\t\tprocess_grant_add_by_schema(stmt);\n\t\t\t\t\twas_schema_op = true;\n\t\t\t\t}\n\n\t\t\t\thcache = ts_hypertable_cache_pin();\n\t\t\t\t/* First process all continuous aggregates in the list and add\n\t\t\t\t * the associated hypertables and views to the list of objects\n\t\t\t\t * to process */\n\t\t\t\tforeach (cell, stmt->objects)\n\t\t\t\t{\n\t\t\t\t\tRangeVar *relation = lfirst_node(RangeVar, cell);\n\t\t\t\t\tContinuousAgg *const cagg = ts_continuous_agg_find_by_rv(relation);\n\t\t\t\t\tif (cagg)\n\t\t\t\t\t{\n\t\t\t\t\t\tHypertable *mat_hypertable =\n\t\t\t\t\t\t\tts_hypertable_get_by_id(cagg->data.mat_hypertable_id);\n\t\t\t\t\t\tprocess_grant_add_by_name(stmt,\n\t\t\t\t\t\t\t\t\t\t\t\t  was_schema_op,\n\t\t\t\t\t\t\t\t\t\t\t\t  &mat_hypertable->fd.schema_name,\n\t\t\t\t\t\t\t\t\t\t\t\t  &mat_hypertable->fd.table_name);\n\t\t\t\t\t\tprocess_grant_add_by_name(stmt,\n\t\t\t\t\t\t\t\t\t\t\t\t  was_schema_op,\n\t\t\t\t\t\t\t\t\t\t\t\t  &cagg->data.direct_view_schema,\n\t\t\t\t\t\t\t\t\t\t\t\t  &cagg->data.direct_view_name);\n\t\t\t\t\t\tprocess_grant_add_by_name(stmt,\n\t\t\t\t\t\t\t\t\t\t\t\t  was_schema_op,\n\t\t\t\t\t\t\t\t\t\t\t\t  &cagg->data.partial_view_schema,\n\t\t\t\t\t\t\t\t\t\t\t\t  &cagg->data.partial_view_name);\n\t\t\t\t\t}\n\n\t\t\t\t\t/*\n\t\t\t\t\t * If this is a hypertable and it has a compressed\n\t\t\t\t\t * hypertable associated with it, add it to the list of\n\t\t\t\t\t * hypertables to process.\n\t\t\t\t\t */\n\t\t\t\t\tHypertable *hypertable = ts_hypertable_cache_get_entry_rv(hcache, relation);\n\t\t\t\t\tif (hypertable && TS_HYPERTABLE_HAS_COMPRESSION_TABLE(hypertable))\n\t\t\t\t\t{\n\t\t\t\t\t\tHypertable *compressed_hypertable =\n\t\t\t\t\t\t\tts_hypertable_get_by_id(hypertable->fd.compressed_hypertable_id);\n\t\t\t\t\t\tAssert(compressed_hypertable);\n\t\t\t\t\t\tprocess_grant_add_by_name(stmt,\n\t\t\t\t\t\t\t\t\t\t\t\t  was_schema_op,\n\t\t\t\t\t\t\t\t\t\t\t\t  &compressed_hypertable->fd.schema_name,\n\t\t\t\t\t\t\t\t\t\t\t\t  &compressed_hypertable->fd.table_name);\n\t\t\t\t\t\tList *chunks =\n\t\t\t\t\t\t\tts_chunk_get_by_hypertable_id(hypertable->fd.compressed_hypertable_id);\n\t\t\t\t\t\tListCell *cell;\n\t\t\t\t\t\tforeach (cell, chunks)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tChunk *chunk = lfirst(cell);\n\t\t\t\t\t\t\tprocess_grant_add_by_name(stmt,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  was_schema_op,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  &chunk->fd.schema_name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  &chunk->fd.table_name);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t/* Process all hypertables, including those added in the loop above */\n\t\t\t\tforeach (cell, stmt->objects)\n\t\t\t\t{\n\t\t\t\t\tRangeVar *relation = lfirst_node(RangeVar, cell);\n\t\t\t\t\tHypertable *ht = ts_hypertable_cache_get_entry_rv(hcache, relation);\n\n\t\t\t\t\tif (ht)\n\t\t\t\t\t{\n\t\t\t\t\t\tforeach_chunk(ht, add_chunk_oid, args);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tts_cache_release(&hcache);\n\n\t\t\t\tresult = DDL_DONE;\n\t\t\t\tif (stmt->objects != NIL)\n\t\t\t\t\tprev_ProcessUtility(args);\n\n\t\t\t\t/* Restore ALL IN SCHEMA command type and it's objects */\n\t\t\t\tif (was_schema_op)\n\t\t\t\t{\n\t\t\t\t\tstmt->targtype = ACL_TARGET_ALL_IN_SCHEMA;\n\t\t\t\t\tstmt->objects = saved_schema_objects;\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\n\treturn result;\n}\n\nstatic DDLResult\nprocess_grant_and_revoke_role(ProcessUtilityArgs *args)\n{\n\tGrantRoleStmt *stmt = (GrantRoleStmt *) args->parsetree;\n\n\t/*\n\t * Need to apply the REVOKE first to be able to check remaining\n\t * permissions\n\t */\n\tprev_ProcessUtility(args);\n\n\t/* We only care about revokes and setting privileges on a specific object */\n\tif (stmt->is_grant)\n\t\treturn DDL_DONE;\n\n\tts_tablespace_validate_revoke_role(stmt);\n\n\treturn DDL_DONE;\n}\n\nstatic void\nprocess_drop_view_start(ProcessUtilityArgs *args, DropStmt *stmt)\n{\n\tListCell *cell;\n\tforeach (cell, stmt->objects)\n\t{\n\t\tList *const object = lfirst(cell);\n\t\tRangeVar *const rv = makeRangeVarFromNameList(object);\n\t\tContinuousAgg *const cagg = ts_continuous_agg_find_by_rv(rv);\n\n\t\tif (cagg)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"cannot drop continuous aggregate using DROP VIEW\"),\n\t\t\t\t\t errhint(\"Use DROP MATERIALIZED VIEW to drop a continuous aggregate.\")));\n\t}\n}\n\nstatic void\nprocess_drop_continuous_aggregates(ProcessUtilityArgs *args, DropStmt *stmt)\n{\n\tListCell *lc;\n\tint caggs_count = 0;\n\n\tforeach (lc, stmt->objects)\n\t{\n\t\tList *const object = lfirst(lc);\n\t\tRangeVar *const rv = makeRangeVarFromNameList(object);\n\t\tContinuousAgg *const cagg = ts_continuous_agg_find_by_rv(rv);\n\n\t\tif (cagg)\n\t\t{\n\t\t\t/* If there is at least one cagg, the drop should be treated as a\n\t\t\t * DROP VIEW. */\n\t\t\tstmt->removeType = OBJECT_VIEW;\n\t\t\t++caggs_count;\n\t\t}\n\t}\n\n\t/* We check that there were only continuous aggregates or that there were\n\t   no continuous aggregates. Otherwise, we have a mixture of tables and\n\t   views and are looking for views only.*/\n\tif (caggs_count > 0 && caggs_count < list_length(stmt->objects))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"mixing continuous aggregates and other objects not allowed\"),\n\t\t\t\t errhint(\"Drop continuous aggregates and other objects in separate statements.\")));\n}\n\nstatic bool\nfetch_role_info(RoleSpec *rolespec, Oid *roleid)\n{\n\t/* Special role specifiers should not be present when dropping a role,\n\t * but if they are, we just ignore them */\n\tif (rolespec->roletype != ROLESPEC_CSTRING)\n\t\treturn false;\n\n\t/* Fetch the heap tuple from system table. If heaptuple is not valid it\n\t * means we did not find a role. We ignore it since the real execution\n\t * will handle this. */\n\tHeapTuple tuple = SearchSysCache1(AUTHNAME, PointerGetDatum(rolespec->rolename));\n\tif (!HeapTupleIsValid(tuple))\n\t\treturn false;\n\n\tForm_pg_authid roleform = (Form_pg_authid) GETSTRUCT(tuple);\n\t*roleid = roleform->oid;\n\tReleaseSysCache(tuple);\n\treturn true;\n}\n\nstatic DDLResult\nprocess_drop_role(ProcessUtilityArgs *args)\n{\n\tDropRoleStmt *stmt = (DropRoleStmt *) args->parsetree;\n\tListCell *cell;\n\tforeach (cell, stmt->roles)\n\t{\n\t\tRoleSpec *rolespec = lfirst(cell);\n\t\tOid roleid;\n\n\t\tif (!fetch_role_info(rolespec, &roleid))\n\t\t\tcontinue;\n\n\t\tScanIterator iterator =\n\t\t\tts_scan_iterator_create(BGW_JOB, AccessShareLock, CurrentMemoryContext);\n\t\tts_scanner_foreach(&iterator)\n\t\t{\n\t\t\tbool isnull;\n\t\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\t\tDatum value = slot_getattr(ti->slot, Anum_bgw_job_owner, &isnull);\n\t\t\tif (!isnull && DatumGetObjectId(value) == roleid)\n\t\t\t{\n\t\t\t\tDatum value = slot_getattr(ti->slot, Anum_bgw_job_id, &isnull);\n\t\t\t\tEnsure(!isnull, \"job id was null\");\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),\n\t\t\t\t\t\t errmsg(\"role \\\"%s\\\" cannot be dropped because some objects depend on it\",\n\t\t\t\t\t\t\t\trolespec->rolename),\n\t\t\t\t\t\t errdetail(\"owner of job %d\", DatumGetInt32(value))));\n\t\t\t}\n\t\t}\n\t}\n\treturn DDL_CONTINUE;\n}\n\nstatic DDLResult\nprocess_drop_start(ProcessUtilityArgs *args)\n{\n\tDropStmt *stmt = (DropStmt *) args->parsetree;\n\n\tswitch (stmt->removeType)\n\t{\n\t\tcase OBJECT_TABLE:\n\t\t\tprocess_drop_hypertable(args, stmt);\n\t\t\tTS_FALLTHROUGH;\n\t\tcase OBJECT_FOREIGN_TABLE:\n\t\t\t/* Chunks can be either normal tables, or foreign tables in the case of a tiered\n\t\t\t * hypertable */\n\t\t\tprocess_drop_chunk(args, stmt);\n\t\t\tbreak;\n\t\tcase OBJECT_INDEX:\n\t\t\tprocess_drop_hypertable_index(args, stmt);\n\t\t\tbreak;\n\t\tcase OBJECT_MATVIEW:\n\t\t\tprocess_drop_continuous_aggregates(args, stmt);\n\t\t\tbreak;\n\t\tcase OBJECT_VIEW:\n\t\t\tprocess_drop_view_start(args, stmt);\n\t\t\tbreak;\n\t\tcase OBJECT_PROCEDURE:\n\t\tcase OBJECT_FUNCTION:\n\t\t\tprocess_drop_procedure_start(stmt);\n\t\t\tbreak;\n\t\tcase OBJECT_SCHEMA:\n\t\t\tprocess_drop_schema_start(stmt);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\n\treturn DDL_CONTINUE;\n}\n\nstatic void\nreindex_chunk(Hypertable *ht, Oid chunk_relid, void *arg)\n{\n\tProcessUtilityArgs *args = arg;\n\tReindexStmt *stmt = (ReindexStmt *) args->parsetree;\n\tChunk *chunk = ts_chunk_get_by_relid(chunk_relid, true);\n\n\tswitch (stmt->kind)\n\t{\n\t\tcase REINDEX_OBJECT_TABLE:\n\t\t\tstmt->relation->relname = NameStr(chunk->fd.table_name);\n\t\t\tstmt->relation->schemaname = NameStr(chunk->fd.schema_name);\n\t\t\tExecReindex(NULL, stmt, false);\n\t\t\tbreak;\n\t\tcase REINDEX_OBJECT_INDEX:\n\t\t\t/* Not supported, a.t.m. See note in process_reindex(). */\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n}\n\n/*\n * Reindex a hypertable and all its chunks. Currently works only for REINDEX\n * TABLE.\n */\nstatic DDLResult\nprocess_reindex(ProcessUtilityArgs *args)\n{\n\tReindexStmt *stmt = (ReindexStmt *) args->parsetree;\n\tOid relid;\n\tCache *hcache;\n\tHypertable *ht;\n\tDDLResult result = DDL_CONTINUE;\n\n\tif (NULL == stmt->relation)\n\t\t/* Not a case we are interested in */\n\t\treturn DDL_CONTINUE;\n\n\trelid = RangeVarGetRelid(stmt->relation, NoLock, true);\n\n\tif (!OidIsValid(relid))\n\t\treturn DDL_CONTINUE;\n\n\thcache = ts_hypertable_cache_pin();\n\n\tswitch (stmt->kind)\n\t{\n\t\tcase REINDEX_OBJECT_TABLE:\n\t\t\tht = ts_hypertable_cache_get_entry(hcache, relid, CACHE_FLAG_MISSING_OK);\n\n\t\t\tif (NULL != ht)\n\t\t\t{\n\t\t\t\tPreventCommandDuringRecovery(\"REINDEX\");\n\t\t\t\tts_hypertable_permissions_check_by_id(ht->fd.id);\n\t\t\t\tif (get_reindex_options(stmt) & REINDEXOPT_CONCURRENTLY)\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t\t errmsg(\"concurrent index creation on hypertables is not supported\")));\n\n\t\t\t\tif (foreach_chunk(ht, reindex_chunk, args) >= 0)\n\t\t\t\t\tresult = DDL_DONE;\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase REINDEX_OBJECT_INDEX:\n\t\t\tht = ts_hypertable_cache_get_entry(hcache,\n\t\t\t\t\t\t\t\t\t\t\t   IndexGetRelation(relid, true),\n\t\t\t\t\t\t\t\t\t\t\t   CACHE_FLAG_MISSING_OK);\n\n\t\t\tif (NULL != ht)\n\t\t\t{\n\t\t\t\tts_hypertable_permissions_check_by_id(ht->fd.id);\n\n\t\t\t\t/*\n\t\t\t\t * Recursing to chunks is currently not supported. Need to\n\t\t\t\t * look up all chunk indexes that corresponds to the\n\t\t\t\t * hypertable's index.\n\t\t\t\t */\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t errmsg(\"reindexing of a specific index on a hypertable is unsupported\"),\n\t\t\t\t\t\t errhint(\n\t\t\t\t\t\t\t \"As a workaround, it is possible to run REINDEX TABLE to reindex all \"\n\t\t\t\t\t\t\t \"indexes on a hypertable, including all indexes on chunks.\")));\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\n\tts_cache_release(&hcache);\n\n\treturn result;\n}\n\nstatic void\nprocess_rename_view(Oid relid, RenameStmt *stmt)\n{\n\tchar *schema = get_namespace_name(get_rel_namespace(relid));\n\tchar *name = get_rel_name(relid);\n\tts_continuous_agg_rename_view(schema, name, schema, stmt->newname, &stmt->renameType);\n}\n\n/*\n * Rename a hypertable, chunk or continuous aggregate.\n */\nstatic void\nprocess_rename_table(ProcessUtilityArgs *args, Cache *hcache, Oid relid, RenameStmt *stmt)\n{\n\tHypertable *ht = ts_hypertable_cache_get_entry(hcache, relid, CACHE_FLAG_MISSING_OK);\n\n\tif (NULL == ht)\n\t{\n\t\tContinuousAgg *cagg = ts_continuous_agg_find_by_relid(relid);\n\n\t\tif (cagg)\n\t\t{\n\t\t\tstmt->renameType = OBJECT_MATVIEW;\n\t\t\tprocess_rename_view(relid, stmt);\n\t\t\treturn;\n\t\t}\n\n\t\tChunk *chunk = ts_chunk_get_by_relid(relid, false);\n\n\t\tif (NULL != chunk)\n\t\t\tts_chunk_set_name(chunk, stmt->newname);\n\t}\n\telse\n\t{\n\t\tts_hypertable_set_name(ht, stmt->newname);\n\t}\n}\n\nstatic DDLResult\nprocess_rename_column(ProcessUtilityArgs *args, Cache *hcache, Oid relid, RenameStmt *stmt)\n{\n\tHypertable *ht = ts_hypertable_cache_get_entry(hcache, relid, CACHE_FLAG_MISSING_OK);\n\tDimension *dim;\n\tbool is_cagg = false;\n\n\tif (!ht)\n\t{\n\t\tChunk *chunk = ts_chunk_get_by_relid(relid, false);\n\n\t\tif (chunk)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"cannot rename column \\\"%s\\\" of hypertable chunk \\\"%s\\\"\",\n\t\t\t\t\t\t\tstmt->subname,\n\t\t\t\t\t\t\tget_rel_name(relid)),\n\t\t\t\t\t errhint(\"Rename the hypertable column instead.\")));\n\n\t\t/* This was not a hypertable and not a chunk, but it could be a\n\t\t * continuous aggregate.\n\t\t *\n\t\t * If this is a continuous aggregate, the rename should be done on the\n\t\t * materialized table. Since the partial view and the direct view are\n\t\t * not referencing the materialized table, we need to handle it here,\n\t\t * and in addition, the dimension table contains the column name, we\n\t\t * need to update the name there. */\n\t\tContinuousAgg *cagg = ts_continuous_agg_find_by_relid(relid);\n\t\tif (cagg)\n\t\t{\n\t\t\tis_cagg = true;\n\n\t\t\tRenameStmt *direct_view_stmt = castNode(RenameStmt, copyObject(stmt));\n\t\t\tdirect_view_stmt->relation = makeRangeVar(NameStr(cagg->data.direct_view_schema),\n\t\t\t\t\t\t\t\t\t\t\t\t\t  NameStr(cagg->data.direct_view_name),\n\t\t\t\t\t\t\t\t\t\t\t\t\t  -1);\n\t\t\tExecRenameStmt(direct_view_stmt);\n\n\t\t\tRenameStmt *partial_view_stmt = castNode(RenameStmt, copyObject(stmt));\n\t\t\tpartial_view_stmt->relation = makeRangeVar(NameStr(cagg->data.partial_view_schema),\n\t\t\t\t\t\t\t\t\t\t\t\t\t   NameStr(cagg->data.partial_view_name),\n\t\t\t\t\t\t\t\t\t\t\t\t\t   -1);\n\t\t\tExecRenameStmt(partial_view_stmt);\n\n\t\t\t/* Fetch the main table and it's relid and use that for the\n\t\t\t * processing below. This is necessary to rebuild the view based\n\t\t\t * on the table with the renamed columns. */\n\t\t\tht = ts_hypertable_get_by_id(cagg->data.mat_hypertable_id);\n\t\t\trelid = ht->main_table_relid;\n\n\t\t\tRenameStmt *mat_hypertable_stmt = castNode(RenameStmt, copyObject(stmt));\n\t\t\tmat_hypertable_stmt->relation =\n\t\t\t\tmakeRangeVar(NameStr(ht->fd.schema_name), NameStr(ht->fd.table_name), -1);\n\t\t\tExecRenameStmt(mat_hypertable_stmt);\n\n\t\t\t/*\n\t\t\t * Also rename the user view column now so that\n\t\t\t * cagg_rename_view_columns() can update stored query trees for\n\t\t\t * all views (including the user view) in a single pass. We\n\t\t\t * return DDL_DONE below to skip PostgreSQL's standard rename\n\t\t\t * which would otherwise fail on the already-renamed column.\n\t\t\t * We need CommandCounterIncrement here so that\n\t\t\t * cagg_rename_view_columns() sees the new column names when it\n\t\t\t * opens the relations and reads their TupleDescs.\n\t\t\t */\n\t\t\tExecRenameStmt(stmt);\n\t\t\tCommandCounterIncrement();\n\t\t}\n\t}\n\telse\n\t{\n\t\t/* Block renaming columns on the materialization table of a continuous\n\t\t * agg, but only if this was an explicit request for rename on a\n\t\t * materialization table. */\n\t\tif ((ts_continuous_agg_hypertable_status(ht->fd.id) & HypertableIsMaterialization) != 0)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"renaming columns on materialization tables is not supported\"),\n\t\t\t\t\t errdetail(\"Column \\\"%s\\\" in materialization table \\\"%s\\\".\",\n\t\t\t\t\t\t\t   stmt->subname,\n\t\t\t\t\t\t\t   get_rel_name(relid)),\n\t\t\t\t\t errhint(\"Rename the column on the continuous aggregate instead.\")));\n\t}\n\n\t/*\n\t * If there were a hypertable or a continuous aggregate, we need to rename\n\t * the dimension that we used as well as rebuilding the view. Otherwise,\n\t * we don't do anything.\n\t *\n\t * If it's not a dimension then we also need to check if column statistics\n\t * have been enabled on this column.\n\t * */\n\tif (ht)\n\t{\n\t\t/* The column rename needs to be processed before the compression settings updated\n\t\t * because the composite bloom filter renaming need to have the old column names\n\t\t * and this comes from the compression settings.\n\t\t */\n\t\tif (ts_cm_functions->process_rename_cmd)\n\t\t\tts_cm_functions->process_rename_cmd(relid, hcache, stmt);\n\n\t\t/* The compression settings update can only proceed after the columns are renamed */\n\t\tts_compression_settings_rename_column_cascade(ht->main_table_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  stmt->subname,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  stmt->newname);\n\t\tdim = ts_hyperspace_get_mutable_dimension_by_name(ht->space,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  DIMENSION_TYPE_ANY,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  stmt->subname);\n\n\t\tif (dim)\n\t\t\tts_dimension_set_name(dim, stmt->newname);\n\t\telse\n\t\t{\n\t\t\tForm_chunk_column_stats form =\n\t\t\t\tts_chunk_column_stats_lookup(ht->fd.id, INVALID_CHUNK_ID, stmt->subname);\n\t\t\tif (form != NULL)\n\t\t\t{\n\t\t\t\tts_chunk_column_stats_set_name(form, stmt->newname);\n\n\t\t\t\t/* refresh the ht entry to accommodate this rename */\n\t\t\t\tif (ht->range_space)\n\t\t\t\t\tpfree(ht->range_space);\n\t\t\t\tht->range_space =\n\t\t\t\t\tts_chunk_column_stats_range_space_scan(ht->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   ht->main_table_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   ts_cache_memory_ctx(hcache));\n\t\t\t}\n\t\t}\n\t}\n\n\t/*\n\t * For continuous aggregates we renamed the user view column above via\n\t * ExecRenameStmt, so tell the caller to skip PostgreSQL's standard\n\t * rename which would fail on the already-renamed column.\n\t */\n\treturn is_cagg ? DDL_DONE : DDL_CONTINUE;\n}\n\nstatic void\nprocess_rename_index(ProcessUtilityArgs *args, Cache *hcache, Oid relid, RenameStmt *stmt)\n{\n\tOid tablerelid = IndexGetRelation(relid, true);\n\tHypertable *ht;\n\n\tif (!OidIsValid(tablerelid))\n\t\treturn;\n\n\tht = ts_hypertable_cache_get_entry(hcache, tablerelid, CACHE_FLAG_MISSING_OK);\n\tif (ht)\n\t{\n\t\tts_chunk_index_rename(ht, relid, stmt->newname);\n\t}\n}\n\n/* Visit all internal catalog tables with a schema column to check for applicable rename */\nstatic void\nprocess_rename_schema(RenameStmt *stmt)\n{\n\tint i = 0;\n\n\t/* Block any renames of our internal schemas */\n\tfor (i = 0; i < NUM_TIMESCALEDB_SCHEMAS; i++)\n\t{\n\t\tif (strncmp(stmt->subname, ts_extension_schema_names[i], NAMEDATALEN) == 0)\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_TS_OPERATION_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"cannot rename schemas used by the TimescaleDB extension\")));\n\t\t\treturn;\n\t\t}\n\t}\n\n\tts_bgw_job_rename_schema_name(stmt->subname, stmt->newname);\n\tts_chunks_rename_schema_name(stmt->subname, stmt->newname);\n\tts_dimensions_rename_schema_name(stmt->subname, stmt->newname);\n\tts_hypertables_rename_schema_name(stmt->subname, stmt->newname);\n\tts_continuous_agg_rename_schema_name(stmt->subname, stmt->newname);\n}\n\nstatic void\nprocess_rename_procedure(ProcessUtilityArgs *args)\n{\n\tRenameStmt *stmt = (RenameStmt *) args->parsetree;\n\tRelation relation;\n\tObjectAddress address =\n\t\tget_object_address(stmt->renameType, stmt->object, &relation, AccessExclusiveLock, false);\n\tts_bgw_job_rename_proc(address, NULL, stmt->newname);\n}\n\nstatic void\nrename_hypertable_constraint(Hypertable *ht, Oid chunk_relid, void *arg)\n{\n\tRenameStmt *stmt = (RenameStmt *) arg;\n\tChunk *chunk = ts_chunk_get_by_relid(chunk_relid, true);\n\n\tts_chunk_constraint_rename_hypertable_constraint(chunk->fd.id, stmt->subname, stmt->newname);\n}\n\nstatic void\nalter_hypertable_constraint(Hypertable *ht, Oid chunk_relid, void *arg)\n{\n\tAlterTableCmd *cmd = (AlterTableCmd *) arg;\n\tchar *hypertable_constraint_name;\n\n#if PG18_LT\n\tConstraint *cmd_constraint;\n\tAssert(IsA(cmd->def, Constraint));\n\tcmd_constraint = (Constraint *) cmd->def;\n#else\n\t/* PG18 adds ATAlterConstraint struct which is used\n\t * instead of Constraint struct\n\t *\n\t * https://github.com/postgres/postgres/commit/80d7f990\n\t */\n\tATAlterConstraint *cmd_constraint;\n\tAssert(IsA(cmd->def, ATAlterConstraint));\n\tcmd_constraint = (ATAlterConstraint *) cmd->def;\n#endif\n\n\thypertable_constraint_name = cmd_constraint->conname;\n\n\tcmd_constraint->conname =\n\t\tts_chunk_constraint_get_name_from_hypertable_constraint(chunk_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\thypertable_constraint_name);\n\n\tAlterTableInternal(chunk_relid, list_make1(cmd), false);\n\n\t/* Restore for next iteration */\n\tcmd_constraint->conname = hypertable_constraint_name;\n}\n\nstatic void\nvalidate_hypertable_constraint(Hypertable *ht, Oid chunk_relid, void *arg)\n{\n\tAlterTableCmd *cmd = (AlterTableCmd *) arg;\n\tAlterTableCmd *chunk_cmd = copyObject(cmd);\n\n\tchunk_cmd->name =\n\t\tts_chunk_constraint_get_name_from_hypertable_constraint(chunk_relid, cmd->name);\n\n\tif (chunk_cmd->name == NULL)\n\t\treturn;\n\n\t/* do not pass down the VALIDATE RECURSE subtype */\n\tchunk_cmd->subtype = AT_ValidateConstraint;\n\tAlterTableInternal(chunk_relid, list_make1(chunk_cmd), false);\n}\n\nstatic void\nrename_hypertable_trigger(Hypertable *ht, Oid chunk_relid, void *arg)\n{\n\tRenameStmt *stmt = copyObject(castNode(RenameStmt, arg));\n\tChunk *chunk = ts_chunk_get_by_relid(chunk_relid, true);\n\n\tstmt->relation = makeRangeVar(NameStr(chunk->fd.schema_name), NameStr(chunk->fd.table_name), 0);\n\trenametrig(stmt);\n}\n\nstatic void\nprocess_rename_constraint_or_trigger(ProcessUtilityArgs *args, Cache *hcache, Oid relid,\n\t\t\t\t\t\t\t\t\t RenameStmt *stmt)\n{\n\tHypertable *ht;\n\n\tht = ts_hypertable_cache_get_entry(hcache, relid, CACHE_FLAG_MISSING_OK);\n\n\tAssert(stmt->relation != NULL);\n\n\tif (NULL != ht)\n\t{\n\t\trelation_not_only(stmt->relation);\n\n\t\tif (stmt->renameType == OBJECT_TABCONSTRAINT)\n\t\t\tforeach_chunk(ht, rename_hypertable_constraint, stmt);\n\t\telse if (stmt->renameType == OBJECT_TRIGGER)\n\t\t\tforeach_chunk(ht, rename_hypertable_trigger, stmt);\n\t}\n\telse if (stmt->renameType == OBJECT_TABCONSTRAINT)\n\t{\n\t\tChunk *chunk = ts_chunk_get_by_relid(relid, false);\n\n\t\tif (NULL != chunk)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"renaming constraints on chunks is not supported\")));\n\t}\n}\n\nstatic DDLResult\nprocess_rename(ProcessUtilityArgs *args)\n{\n\tRenameStmt *stmt = (RenameStmt *) args->parsetree;\n\tOid relid = InvalidOid;\n\tCache *hcache;\n\n\t/* Only get the relid if it exists for this stmt */\n\tif (NULL != stmt->relation)\n\t{\n\t\trelid = RangeVarGetRelid(stmt->relation, NoLock, true);\n\t\tif (!OidIsValid(relid))\n\t\t\treturn DDL_CONTINUE;\n\t}\n\n\thcache = ts_hypertable_cache_pin();\n\n\tDDLResult result = DDL_CONTINUE;\n\n\tswitch (stmt->renameType)\n\t{\n\t\tcase OBJECT_TABLE:\n\t\t\tprocess_rename_table(args, hcache, relid, stmt);\n\t\t\tbreak;\n\t\tcase OBJECT_COLUMN:\n\t\t\tresult = process_rename_column(args, hcache, relid, stmt);\n\t\t\tbreak;\n\t\tcase OBJECT_INDEX:\n\t\t\tprocess_rename_index(args, hcache, relid, stmt);\n\t\t\tbreak;\n\t\tcase OBJECT_TABCONSTRAINT:\n\t\tcase OBJECT_TRIGGER:\n\t\t\tprocess_rename_constraint_or_trigger(args, hcache, relid, stmt);\n\t\t\tbreak;\n\t\tcase OBJECT_MATVIEW:\n\t\tcase OBJECT_VIEW:\n\t\t\tprocess_rename_view(relid, stmt);\n\t\t\tbreak;\n\t\tcase OBJECT_SCHEMA:\n\t\t\tprocess_rename_schema(stmt);\n\t\t\tbreak;\n\t\tcase OBJECT_PROCEDURE:\n\t\tcase OBJECT_FUNCTION:\n\t\t\tprocess_rename_procedure(args);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\n\tts_cache_release(&hcache);\n\treturn result;\n}\n\nstatic void\nprocess_altertable_change_owner_chunk(Hypertable *ht, Oid chunk_relid, void *arg)\n{\n\tAlterTableCmd *cmd = arg;\n\tOid roleid = get_rolespec_oid(cmd->newowner, false);\n\n\tATExecChangeOwner(chunk_relid, roleid, false, AccessExclusiveLock);\n}\n\nstatic void\nprocess_altertable_change_owner_bgw_jobs(int32 hypertable_id, Oid newrole_oid)\n{\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(BGW_JOB, RowExclusiveLock, CurrentMemoryContext);\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tbool should_free, isnull;\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tDatum htid = slot_getattr(ti->slot, Anum_bgw_job_hypertable_id, &isnull);\n\t\tif (!isnull && DatumGetInt32(htid) == hypertable_id)\n\t\t{\n\t\t\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\t\t\tts_bgw_job_update_owner(ti->scanrel, tuple, ts_scanner_get_tupledesc(ti), newrole_oid);\n\t\t\tif (should_free)\n\t\t\t\theap_freetuple(tuple);\n\t\t}\n\t}\n}\n\nstatic void\nprocess_altertable_change_owner(Hypertable *ht, AlterTableCmd *cmd)\n{\n\tOid newrole_oid = get_rolespec_oid(cmd->newowner, false);\n\n\tAssert(IsA(cmd->newowner, RoleSpec));\n\n\tprocess_altertable_change_owner_bgw_jobs(ht->fd.id, newrole_oid);\n\tforeach_chunk(ht, process_altertable_change_owner_chunk, cmd);\n\n\tif (TS_HYPERTABLE_HAS_COMPRESSION_TABLE(ht))\n\t{\n\t\tHypertable *compressed_hypertable =\n\t\t\tts_hypertable_get_by_id(ht->fd.compressed_hypertable_id);\n\t\tAlterTableInternal(compressed_hypertable->main_table_relid, list_make1(cmd), false);\n\t\tListCell *lc;\n\t\tList *chunks = ts_chunk_get_by_hypertable_id(ht->fd.compressed_hypertable_id);\n\t\tforeach (lc, chunks)\n\t\t{\n\t\t\tChunk *chunk = lfirst(lc);\n\t\t\tAlterTableInternal(chunk->table_id, list_make1(cmd), false);\n\t\t}\n\t\tprocess_altertable_change_owner(compressed_hypertable, cmd);\n\t}\n}\n\ntypedef struct ChunkConstraintInfo\n{\n\tconst AlterTableCmd *cmd;\n\tconst char *constraint_name;\n\tOid hypertable_constraint_oid;\n} ChunkConstraintInfo;\n\n/*\n * Unique constraints are validated by postgres during creation\n * but the postgres process does not cover data present in compressed\n * chunks or data split between compressed and uncompressed chunks.\n * When adding unique constraints to chunks with compressed data we\n * have to check for constraint violation ourself.\n */\nstatic void\nvalidate_index_constraints(Chunk *chunk, const IndexStmt *stmt)\n{\n\tif ((stmt->primary || stmt->unique) && ts_chunk_is_compressed(chunk))\n\t{\n\t\tStringInfoData command;\n\t\tOid nspcid = get_rel_namespace(chunk->table_id);\n\t\tListCell *lc;\n\t\tList *dpcontext = deparse_context_for(get_rel_name(chunk->table_id), chunk->table_id);\n\n\t\tinitStringInfo(&command);\n\t\tappendStringInfo(&command,\n\t\t\t\t\t\t \"SELECT EXISTS(SELECT FROM %s.%s\",\n\t\t\t\t\t\t quote_identifier(get_namespace_name(nspcid)),\n\t\t\t\t\t\t quote_identifier(get_rel_name(chunk->table_id)));\n\n\t\t/*\n\t\t * Before PG15 NULLs were always considered distinct, with\n\t\t * PG15 the behaviour became configurable.\n\t\t */\n\t\tif (!stmt->nulls_not_distinct)\n\t\t{\n\t\t\tint i = 0;\n\t\t\tappendStringInfo(&command, \" WHERE \");\n\t\t\tforeach (lc, stmt->indexParams)\n\t\t\t{\n\t\t\t\ti++;\n\t\t\t\tIndexElem *elem = lfirst_node(IndexElem, lc);\n\t\t\t\tappendStringInfo(&command,\n\t\t\t\t\t\t\t\t \"%s IS NOT NULL\",\n\t\t\t\t\t\t\t\t elem->name ?\n\t\t\t\t\t\t\t\t\t quote_identifier(elem->name) :\n\t\t\t\t\t\t\t\t\t deparse_expression(elem->expr, dpcontext, false, false));\n\t\t\t\tif (i < list_length(stmt->indexParams))\n\t\t\t\t\tappendStringInfo(&command, \" AND \");\n\t\t\t}\n\t\t\tAssert(i > 0);\n\t\t}\n\n\t\tappendStringInfo(&command, \" GROUP BY \");\n\t\tint j = 0;\n\t\tforeach (lc, stmt->indexParams)\n\t\t{\n\t\t\tj++;\n\t\t\tIndexElem *elem = lfirst_node(IndexElem, lc);\n\t\t\tappendStringInfo(&command,\n\t\t\t\t\t\t\t \"%s\",\n\t\t\t\t\t\t\t elem->name ? quote_identifier(elem->name) :\n\t\t\t\t\t\t\t\t\t\t  deparse_expression(elem->expr, dpcontext, false, false));\n\t\t\tif (j < list_length(stmt->indexParams))\n\t\t\t\tappendStringInfo(&command, \",\");\n\t\t}\n\t\tAssert(j > 0);\n\n\t\tappendStringInfo(&command, \" HAVING count(*) > 1\");\n\n\t\tappendStringInfo(&command, \")\");\n\n\t\tif (SPI_connect() != SPI_OK_CONNECT)\n\t\t\telog(ERROR, \"could not connect to SPI\");\n\n\t\t/* Lock down search_path */\n\t\tint save_nestlevel = NewGUCNestLevel();\n\t\tRestrictSearchPath();\n\n\t\tint res = SPI_execute(command.data, true /* read_only */, 0 /*count*/);\n\n\t\tif (res < 0)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t\t (errmsg(\"could not verify unique constraint on \\\"%s\\\"\",\n\t\t\t\t\t\t\t get_rel_name(chunk->table_id)))));\n\n\t\tbool isnull;\n\t\tDatum has_conflicts =\n\t\t\tSPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);\n\t\tAssert(!isnull);\n\n\t\tif (isnull || DatumGetBool(has_conflicts))\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_UNIQUE_VIOLATION),\n\t\t\t\t\t (errmsg(\"duplicate key value violates unique constraint\"))));\n\n\t\t/* Restore search_path */\n\t\tAtEOXact_GUC(false, save_nestlevel);\n\n\t\tres = SPI_finish();\n\t\tif (res != SPI_OK_FINISH)\n\t\t\telog(ERROR, \"SPI_finish failed: %s\", SPI_result_code_string(res));\n\t}\n}\n\nstatic void\nvalidate_check_constraint(Chunk *chunk, Constraint *con)\n{\n\tif (ts_chunk_is_compressed(chunk))\n\t{\n\t\tStringInfoData command;\n\t\tOid nspcid = get_rel_namespace(chunk->table_id);\n\n\t\tParseState *pstate = make_parsestate(NULL);\n\t\tRelation rel = table_open(chunk->table_id, AccessExclusiveLock);\n\t\tParseNamespaceItem *nsitem =\n\t\t\taddRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true);\n\t\taddNSItemToQuery(pstate, nsitem, true, true, true);\n\t\tList *context = deparse_context_for(get_rel_name(chunk->table_id), chunk->table_id);\n\t\tNode *tf = transformExpr(pstate, con->raw_expr, EXPR_KIND_CHECK_CONSTRAINT);\n\t\tchar *deparsed = deparse_expression(tf, context, false, false);\n\n\t\tinitStringInfo(&command);\n\t\tappendStringInfo(&command,\n\t\t\t\t\t\t \"SELECT EXISTS(SELECT FROM %s.%s WHERE NOT (%s))\",\n\t\t\t\t\t\t quote_identifier(get_namespace_name(nspcid)),\n\t\t\t\t\t\t quote_identifier(RelationGetRelationName(rel)),\n\t\t\t\t\t\t deparsed);\n\n\t\tif (SPI_connect() != SPI_OK_CONNECT)\n\t\t\telog(ERROR, \"could not connect to SPI\");\n\n\t\t/* Lock down search_path */\n\t\tint save_nestlevel = NewGUCNestLevel();\n\t\tRestrictSearchPath();\n\n\t\tint res = SPI_execute(command.data, true /* read_only */, 0 /*count*/);\n\n\t\tif (res < 0)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t\t (errmsg(\"could not verify check constraint on \\\"%s\\\"\",\n\t\t\t\t\t\t\t get_rel_name(chunk->table_id)))));\n\n\t\tbool isnull;\n\t\tDatum has_conflicts =\n\t\t\tSPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);\n\t\tAssert(!isnull);\n\n\t\tif (isnull || DatumGetBool(has_conflicts))\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_CHECK_VIOLATION),\n\t\t\t\t\t errmsg(\"check constraint \\\"%s\\\" of relation \\\"%s\\\" is violated by some row\",\n\t\t\t\t\t\t\tcon->conname,\n\t\t\t\t\t\t\tRelationGetRelationName(rel)),\n\t\t\t\t\t errtableconstraint(rel, con->conname)));\n\n\t\ttable_close(rel, NoLock);\n\t\t/* Restore search_path */\n\t\tAtEOXact_GUC(false, save_nestlevel);\n\n\t\tres = SPI_finish();\n\t\tif (res != SPI_OK_FINISH)\n\t\t\telog(ERROR, \"SPI_finish failed: %s\", SPI_result_code_string(res));\n\t}\n}\n\nstatic void\nprocess_add_constraint_chunk(Hypertable *ht, Oid chunk_relid, void *arg)\n{\n\tconst ChunkConstraintInfo *info = arg;\n\tChunk *chunk = ts_chunk_get_by_relid(chunk_relid, true);\n\n\tswitch (info->cmd->subtype)\n\t{\n\t\tcase AT_AddIndex:\n\t\t\tif (ts_chunk_is_compressed(chunk))\n\t\t\t\tvalidate_index_constraints(chunk, castNode(IndexStmt, info->cmd->def));\n\n\t\t\tbreak;\n\t\tcase AT_AddConstraint:\n#if PG16_LT\n\t\tcase AT_AddConstraintRecurse:\n#endif\n\t\t{\n\t\t\tConstraint *con = castNode(Constraint, info->cmd->def);\n\t\t\tswitch (con->contype)\n\t\t\t{\n\t\t\t\t\t/*\n\t\t\t\t\t * Unique and primary key constraints are checked as part of\n\t\t\t\t\t * creation of the index enforcing it so nothing to do here.\n\t\t\t\t\t */\n\t\t\t\tcase CONSTR_UNIQUE:\n\t\t\t\tcase CONSTR_PRIMARY:\n\t\t\t\t\t/*\n\t\t\t\t\t * Foreign key constraints are checked by postgres since\n\t\t\t\t\t * the check happens through SPI and we adjust those queries\n\t\t\t\t\t * to include compressed data.\n\t\t\t\t\t */\n\t\t\t\tcase CONSTR_FOREIGN:\n\t\t\t\t\tbreak;\n#if PG18_GE\n\t\t\t\t\t/* NULL and NOT NULL constraints have been added to\n\t\t\t\t\t * pg_constraints in PG18, we can safely ignore them at end\n\t\t\t\t\t * just like at beginning.\n\t\t\t\t\t *\n\t\t\t\t\t * https://github.com/postgres/postgres/commit/b0e96f31\n\t\t\t\t\t */\n\t\t\t\tcase CONSTR_NULL:\n\t\t\t\tcase CONSTR_NOTNULL:\n\t\t\t\t\tbreak;\n#endif\n\t\t\t\tcase CONSTR_CHECK:\n\t\t\t\t{\n\t\t\t\t\tvalidate_check_constraint(chunk, con);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\tif (ts_chunk_is_compressed(chunk))\n\t\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t\t\t errmsg(\n\t\t\t\t\t\t\t\t\t \"operation not supported on hypertables that have columnstore \"\n\t\t\t\t\t\t\t\t\t \"data\"),\n\t\t\t\t\t\t\t\t errhint(\"Convert the data to rowstore before retrying the \"\n\t\t\t\t\t\t\t\t\t\t \"operation.\")));\n\t\t\t}\n\t\t\tbreak;\n\t\t\t/* Other AT commands might not be allowed on compressed chunks, but\n\t\t\t * they are checked at hypertable level in that case */\n\t\t}\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\n\tts_chunk_constraint_create_on_chunk(ht, chunk, info->hypertable_constraint_oid);\n}\n\nstatic void\nprocess_altertable_add_constraint(Hypertable *ht, const AlterTableCmd *cmd,\n\t\t\t\t\t\t\t\t  const char *constraint_name)\n{\n\tChunkConstraintInfo info = {\n\t\t.cmd = cmd,\n\t\t.constraint_name = constraint_name,\n\t\t.hypertable_constraint_oid =\n\t\t\tget_relation_constraint_oid(ht->main_table_relid, constraint_name, false),\n\t};\n\n\tforeach_chunk(ht, process_add_constraint_chunk, &info);\n}\n\nstatic void\nprocess_altertable_alter_constraint_end(Hypertable *ht, AlterTableCmd *cmd)\n{\n\tforeach_chunk(ht, alter_hypertable_constraint, cmd);\n}\n\nstatic void\nprocess_altertable_validate_constraint_end(Hypertable *ht, AlterTableCmd *cmd)\n{\n\tforeach_chunk(ht, validate_hypertable_constraint, cmd);\n}\n\n/*\n * Validate that SET NOT NULL is ok for this chunk.\n */\nstatic void\nvalidate_set_not_null(Hypertable *ht, Oid chunk_relid, void *arg)\n{\n\tChunk *chunk = ts_chunk_get_by_relid(chunk_relid, true);\n\tif (ts_chunk_is_compressed(chunk))\n\t{\n\t\tStringInfoData command;\n\t\tAlterTableCmd *cmd = (AlterTableCmd *) arg;\n\t\tconst CompressionSettings *settings = ts_compression_settings_get(chunk->table_id);\n\t\tOid nspcid = get_rel_namespace(settings->fd.compress_relid);\n\n\t\tinitStringInfo(&command);\n\t\tappendStringInfo(&command,\n\t\t\t\t\t\t \"SELECT EXISTS(SELECT FROM %s.%s WHERE \",\n\t\t\t\t\t\t quote_identifier(get_namespace_name(nspcid)),\n\t\t\t\t\t\t quote_identifier(get_rel_name(settings->fd.compress_relid)));\n\n\t\tif (ts_array_is_member(settings->fd.segmentby, cmd->name))\n\t\t{\n\t\t\t/* For segmentby we can check directly whether NULLS are present */\n\t\t\tappendStringInfo(&command, \"%s IS NULL\", quote_identifier(cmd->name));\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* For other columns we need to check whether we have a DEFAULT in which\n\t\t\t * case NULL as column value would be fine.\n\t\t\t * */\n\t\t\tHeapTuple atttuple = SearchSysCacheAttName(ht->main_table_relid, cmd->name);\n\t\t\tForm_pg_attribute attform = ((Form_pg_attribute) GETSTRUCT(atttuple));\n\n\t\t\tif (attform->atthasdef)\n\t\t\t\tappendStringInfo(&command,\n\t\t\t\t\t\t\t\t \"%s IS NOT NULL AND \"\n\t\t\t\t\t\t\t\t \"_timescaledb_functions.compressed_data_has_nulls(%s)\",\n\t\t\t\t\t\t\t\t quote_identifier(cmd->name),\n\t\t\t\t\t\t\t\t quote_identifier(cmd->name));\n\t\t\telse\n\t\t\t\tappendStringInfo(&command,\n\t\t\t\t\t\t\t\t \"%s IS NULL OR \"\n\t\t\t\t\t\t\t\t \"_timescaledb_functions.compressed_data_has_nulls(%s)\",\n\t\t\t\t\t\t\t\t quote_identifier(cmd->name),\n\t\t\t\t\t\t\t\t quote_identifier(cmd->name));\n\t\t\tReleaseSysCache(atttuple);\n\t\t}\n\t\tappendStringInfo(&command, \")\");\n\n\t\tif (SPI_connect() != SPI_OK_CONNECT)\n\t\t\telog(ERROR, \"could not connect to SPI\");\n\n\t\tint res = SPI_execute(command.data, true /* read_only */, 0 /*count*/);\n\n\t\tif (res < 0)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t\t (errmsg(\"could not verify presence of NULL values on \\\"%s\\\"\",\n\t\t\t\t\t\t\t get_rel_name(chunk_relid)))));\n\n\t\tbool isnull;\n\t\tDatum has_nulls = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);\n\n\t\tif (isnull || DatumGetBool(has_nulls))\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_NOT_NULL_VIOLATION),\n\t\t\t\t\t (errmsg(\"column \\\"%s\\\" of relation \\\"%s\\\" contains null values\",\n\t\t\t\t\t\t\t cmd->name,\n\t\t\t\t\t\t\t get_rel_name(chunk_relid)))));\n\n\t\tres = SPI_finish();\n\t\tif (res != SPI_OK_FINISH)\n\t\t\telog(ERROR, \"SPI_finish failed: %s\", SPI_result_code_string(res));\n\t}\n}\n\n/*\n * This function checks that we are not dropping NOT NULL from partitioning columns and\n * that no NULL data is present when adding NOT NULL constraint.\n */\nstatic void\nprocess_altertable_alter_not_null(Hypertable *ht, AlterTableCmd *cmd)\n{\n\tif (cmd->subtype == AT_SetNotNull)\n\t\tforeach_chunk(ht, validate_set_not_null, cmd);\n\n\tif (cmd->subtype == AT_DropNotNull)\n\t{\n\t\tfor (int i = 0; i < ht->space->num_dimensions; i++)\n\t\t{\n\t\t\tDimension *dim = &ht->space->dimensions[i];\n\n\t\t\tif (IS_OPEN_DIMENSION(dim) &&\n\t\t\t\tstrncmp(NameStr(dim->fd.column_name), cmd->name, NAMEDATALEN) == 0)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_TS_OPERATION_NOT_SUPPORTED),\n\t\t\t\t\t\t errmsg(\"cannot drop not-null constraint from a time-partitioned column\")));\n\t\t}\n\t}\n}\n\nstatic void\nprocess_altertable_drop_column(Hypertable *ht, AlterTableCmd *cmd)\n{\n\tint i;\n\tbool dropped;\n\n\tfor (i = 0; i < ht->space->num_dimensions; i++)\n\t{\n\t\tDimension *dim = &ht->space->dimensions[i];\n\n\t\tif (namestrcmp(&dim->fd.column_name, cmd->name) == 0)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_TABLE_DEFINITION),\n\t\t\t\t\t errmsg(\"cannot drop column named in partition key\"),\n\t\t\t\t\t errdetail(\"Cannot drop column that is a hypertable partitioning (space or \"\n\t\t\t\t\t\t\t   \"time) dimension.\")));\n\t}\n\n\t/* Delete dimension range entries on this column, if any.  */\n\tts_chunk_column_stats_drop(ht, cmd->name, &dropped);\n}\n\n/*\n * Verify that a constraint is supported on a hypertable.\n */\nstatic void\nverify_constraint_hypertable(Hypertable *ht, Node *constr_node)\n{\n\tConstrType contype;\n\tconst char *indexname;\n\tList *keys;\n\n\tif (IsA(constr_node, Constraint))\n\t{\n\t\tConstraint *constr = (Constraint *) constr_node;\n\n\t\tcontype = constr->contype;\n\t\tkeys = (contype == CONSTR_EXCLUSION) ? constr->exclusions : constr->keys;\n\t\tindexname = constr->indexname;\n\n\t\tif (contype == CONSTR_FOREIGN)\n\t\t{\n\t\t\tOid confrelid = ts_hypertable_relid(constr->pktable);\n\t\t\tif (OidIsValid(confrelid) && !is_partitioning_allowed(ht->main_table_relid))\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t errmsg(\"hypertables cannot be used as foreign key references of \"\n\t\t\t\t\t\t\t\t\"hypertables\")));\n\t\t}\n\n\t\t/* NO INHERIT constraints do not really make sense on a hypertable */\n\t\tif (constr->is_no_inherit)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_TABLE_DEFINITION),\n\t\t\t\t\t errmsg(\"cannot have NO INHERIT constraints on hypertable \\\"%s\\\"\",\n\t\t\t\t\t\t\tget_rel_name(ht->main_table_relid))));\n\t}\n\telse if (IsA(constr_node, IndexStmt))\n\t{\n\t\tIndexStmt *stmt = (IndexStmt *) constr_node;\n\n\t\tcontype = stmt->primary ? CONSTR_PRIMARY : CONSTR_UNIQUE;\n\t\tkeys = stmt->indexParams;\n\t\tindexname = stmt->idxname;\n\t}\n\telse\n\t{\n\t\telog(ERROR, \"unexpected constraint type\");\n\t\treturn;\n\t}\n\n\tswitch (contype)\n\t{\n\t\tcase CONSTR_FOREIGN:\n\t\t\tbreak;\n\t\tcase CONSTR_UNIQUE:\n\t\tcase CONSTR_PRIMARY:\n\n\t\t\t/*\n\t\t\t * If this constraints is created using an existing index we need\n\t\t\t * not re-verify it's columns\n\t\t\t */\n\t\t\tif (indexname != NULL)\n\t\t\t\treturn;\n\n\t\t\tts_indexing_verify_columns(ht->space, keys);\n\t\t\tbreak;\n\t\tcase CONSTR_EXCLUSION:\n\t\t\tts_indexing_verify_columns(ht->space, keys);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n}\n\nstatic void\nverify_constraint(RangeVar *relation, Constraint *constr)\n{\n\tCache *hcache = ts_hypertable_cache_pin();\n\tHypertable *ht = ts_hypertable_cache_get_entry_rv(hcache, relation);\n\n\tif (ht)\n\t\tverify_constraint_hypertable(ht, (Node *) constr);\n\n\tts_cache_release(&hcache);\n}\n\nstatic void\nverify_constraint_list(RangeVar *relation, List *constraint_list)\n{\n\tListCell *lc;\n\n\tforeach (lc, constraint_list)\n\t{\n\t\tConstraint *constraint = lfirst(lc);\n\n\t\tverify_constraint(relation, constraint);\n\t}\n}\n\ntypedef struct HypertableIndexOptions\n{\n\t/*\n\t * true if we should run one transaction per chunk, otherwise use one\n\t * transaction for all the chunks\n\t */\n\tbool multitransaction;\n\tint n_ht_atts;\n\n\t/* Concurrency testing options. */\n#ifdef DEBUG\n\t/*\n\t * If barrier_table is a valid Oid we try to acquire a lock on it at the\n\t * start of each chunks sub-transaction.\n\t */\n\tOid barrier_table;\n\n\t/*\n\t * if max_chunks >= 0 we'll create indices on at most max_chunks, and\n\t * leave the table marked as Invalid when the command ends.\n\t */\n\tint32 max_chunks;\n#endif\n} HypertableIndexOptions;\n\ntypedef struct CreateIndexInfo\n{\n\tIndexStmt *stmt;\n\tObjectAddress obj;\n\tOid main_table_relid;\n\tHypertableIndexOptions extended_options;\n\tMemoryContext mctx;\n} CreateIndexInfo;\n\n/*\n * Create index on a chunk.\n *\n * A chunk index is created based on the original IndexStmt that created the\n * \"parent\" index on the hypertable.\n */\nstatic void\nprocess_index_chunk(Hypertable *ht, Oid chunk_relid, void *arg)\n{\n\tCreateIndexInfo *info = (CreateIndexInfo *) arg;\n\tRelation hypertable_index_rel;\n\tRelation chunk_rel;\n\tIndexInfo *indexinfo;\n\tChunk *chunk;\n\n\tchunk = ts_chunk_get_by_relid(chunk_relid, true);\n\tif (IS_OSM_CHUNK(chunk)) /*cannot create index on foreign OSM chunk */\n\t{\n\t\tereport(NOTICE, (errmsg(\"skipping index creation for tiered data\")));\n\t\treturn;\n\t}\n\n\tvalidate_index_constraints(chunk, info->stmt);\n\n\tchunk_rel = table_open(chunk_relid, ShareLock);\n\thypertable_index_rel = index_open(info->obj.objectId, AccessShareLock);\n\tindexinfo = BuildIndexInfo(hypertable_index_rel);\n\n\tif (chunk_index_columns_changed(info->extended_options.n_ht_atts, RelationGetDescr(chunk_rel)))\n\t\tts_adjust_indexinfo_attnos(indexinfo, info->main_table_relid, chunk_rel);\n\n\tts_chunk_index_create_from_adjusted_index_info(ht->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t   hypertable_index_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t   chunk->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t   chunk_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t   indexinfo);\n\n\tindex_close(hypertable_index_rel, NoLock);\n\ttable_close(chunk_rel, NoLock);\n}\n\nstatic void\nprocess_index_chunk_multitransaction(int32 hypertable_id, Oid chunk_relid, void *arg)\n{\n\tCreateIndexInfo *info = (CreateIndexInfo *) arg;\n\tCatalogSecurityContext sec_ctx;\n\tChunk *chunk;\n\tRelation hypertable_index_rel;\n\tRelation chunk_rel;\n\tIndexInfo *indexinfo;\n\n\tAssert(info->extended_options.multitransaction);\n\n\t/* Start a new transaction for each relation. */\n\tStartTransactionCommand();\n\tPushActiveSnapshot(GetTransactionSnapshot());\n\n#ifdef DEBUG\n\tif (info->extended_options.max_chunks == 0)\n\t{\n\t\tPopActiveSnapshot();\n\t\tCommitTransactionCommand();\n\t\treturn;\n\t}\n\n\t/*\n\t * if max_chunks is < 0 then we're indexing all the chunks, if it's >= 0\n\t * then we're only indexing some of the chunks, and leaving the root index\n\t * marked as invalid\n\t */\n\tif (info->extended_options.max_chunks > 0)\n\t\tinfo->extended_options.max_chunks -= 1;\n\n\tif (OidIsValid(info->extended_options.barrier_table))\n\t{\n\t\t/*\n\t\t * For isolation tests, and debugging, it's useful to be able to\n\t\t * pause CREATE INDEX immediately before it starts working on chunks.\n\t\t * We acquire and immediately release a lock on a barrier table to do\n\t\t * this.\n\t\t */\n\t\tRelation barrier = relation_open(info->extended_options.barrier_table, AccessExclusiveLock);\n\n\t\trelation_close(barrier, AccessExclusiveLock);\n\t}\n#endif\n\n\t/*\n\t * Change user since chunks are typically located in an internal schema\n\t * and chunk indexes require metadata changes. In the single-transaction\n\t * case, we do this once for the entire table.\n\t */\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\n\t/*\n\t * Hold a lock on the hypertable index, and the chunk to prevent\n\t * from being altered. Since we use the same relids across transactions,\n\t * there is a potential issue if the id gets reassigned between one\n\t * sub-transaction and the next. CLUSTER has a similar issue.\n\t *\n\t * We grab a ShareLock on the chunk, because that's what CREATE INDEX\n\t * does. For the hypertable's index, we are ok using the weaker\n\t * AccessShareLock, since we only need to prevent the index itself from\n\t * being ALTERed or DROPped during this part of index creation.\n\t */\n\tchunk_rel = table_open(chunk_relid, ShareLock);\n\tchunk = ts_chunk_get_by_relid(chunk_relid, true);\n\n\t/*\n\t * Validation happens when creating the hypertable's index, which goes\n\t * through the usual DefineIndex mechanism.\n\t */\n\tif (!IS_OSM_CHUNK(chunk)) /*cannot create index on foreign OSM chunk */\n\t{\n\t\thypertable_index_rel = index_open(info->obj.objectId, AccessShareLock);\n\t\tindexinfo = BuildIndexInfo(hypertable_index_rel);\n\t\tif (chunk_index_columns_changed(info->extended_options.n_ht_atts,\n\t\t\t\t\t\t\t\t\t\tRelationGetDescr(chunk_rel)))\n\t\t\tts_adjust_indexinfo_attnos(indexinfo, info->main_table_relid, chunk_rel);\n\n\t\tts_chunk_index_create_from_adjusted_index_info(hypertable_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   hypertable_index_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   chunk->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   chunk_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   indexinfo);\n\n\t\tindex_close(hypertable_index_rel, NoLock);\n\t}\n\telse\n\t{\n\t\tereport(NOTICE, (errmsg(\"skipping index creation for tiered data\")));\n\t}\n\n\tvalidate_index_constraints(chunk, info->stmt);\n\n\ttable_close(chunk_rel, NoLock);\n\n\tts_catalog_restore_user(&sec_ctx);\n\n\tPopActiveSnapshot();\n\tCommitTransactionCommand();\n}\n\ntypedef enum HypertableIndexFlags\n{\n\tHypertableIndexFlagMultiTransaction = 0,\n#ifdef DEBUG\n\tHypertableIndexFlagBarrierTable,\n\tHypertableIndexFlagMaxChunks,\n#endif\n} HypertableIndexFlags;\n\nstatic const WithClauseDefinition index_with_clauses[] = {\n\t[HypertableIndexFlagMultiTransaction] = {.arg_names = {\"transaction_per_chunk\", NULL}, .type_id = BOOLOID,},\n#ifdef DEBUG\n\t[HypertableIndexFlagBarrierTable] = {.arg_names = {\"barrier_table\", NULL}, .type_id = REGCLASSOID,},\n\t[HypertableIndexFlagMaxChunks] = {.arg_names = {\"max_chunks\", NULL}, .type_id = INT4OID, .default_val = (Datum)-1},\n#endif\n};\n\nstatic bool\nmultitransaction_create_index_mark_valid(CreateIndexInfo info)\n{\n#ifdef DEBUG\n\treturn info.extended_options.max_chunks < 0;\n#else\n\treturn true;\n#endif\n}\n\n/*\n * Create an index on a hypertable\n *\n * We override CREATE INDEX on hypertables in order to ensure that the index is\n * created on all of the hypertable's chunks, and to ensure that locks on all\n * of said chunks are acquired at the correct time.\n */\nstatic DDLResult\nprocess_index_start(ProcessUtilityArgs *args)\n{\n\tIndexStmt *stmt = (IndexStmt *) args->parsetree;\n\tCache *hcache;\n\tHypertable *ht;\n\tList *postgres_options = NIL;\n\tList *hypertable_options = NIL;\n\tWithClauseResult *parsed_with_clauses;\n\tCreateIndexInfo info = {\n\t\t.stmt = stmt,\n#ifdef DEBUG\n\t\t.extended_options = {0, .max_chunks = -1,},\n#endif\n\t};\n\tObjectAddress root_table_index;\n\tRelation main_table_relation;\n\tTupleDesc main_table_desc;\n\tRelation main_table_index_relation;\n\tLockRelId main_table_index_lock_relid;\n\tint sec_ctx;\n\tOid uid = InvalidOid, saved_uid = InvalidOid;\n\tContinuousAgg *cagg = NULL;\n\n\tAssert(IsA(stmt, IndexStmt));\n\n\t/*\n\t * PG11 adds some cases where the relation is not there, namely on\n\t * declaratively partitioned tables, with partitioned indexes:\n\t * https://github.com/postgres/postgres/commit/8b08f7d4820fd7a8ef6152a9dd8c6e3cb01e5f99\n\t * we don't deal with them so we will just return immediately\n\t */\n\tif (NULL == stmt->relation)\n\t\treturn DDL_CONTINUE;\n\n\thcache = ts_hypertable_cache_pin();\n\tht = ts_hypertable_cache_get_entry_rv(hcache, stmt->relation);\n\n\tif (!ht)\n\t{\n\t\t/* Check if the relation is a Continuous Aggregate */\n\t\tcagg = ts_continuous_agg_find_by_rv(stmt->relation);\n\n\t\tif (cagg)\n\t\t{\n\t\t\tht = ts_hypertable_get_by_id(cagg->data.mat_hypertable_id);\n\t\t}\n\n\t\tif (!ht)\n\t\t{\n\t\t\tts_cache_release(&hcache);\n\t\t\treturn DDL_CONTINUE;\n\t\t}\n\n\t\tif (stmt->unique)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"continuous aggregates do not support UNIQUE indexes\")));\n\n\t\t/* Make the RangeVar for the underlying materialization hypertable */\n\t\tstmt->relation = makeRangeVar(NameStr(ht->fd.schema_name), NameStr(ht->fd.table_name), -1);\n\t}\n\n\tts_hypertable_permissions_check_by_id(ht->fd.id);\n\n\tts_with_clause_filter(stmt->options, &hypertable_options, NULL, &postgres_options);\n\n\tstmt->options = postgres_options;\n\n\tparsed_with_clauses = ts_with_clauses_parse(hypertable_options,\n\t\t\t\t\t\t\t\t\t\t\t\tindex_with_clauses,\n\t\t\t\t\t\t\t\t\t\t\t\tTS_ARRAY_LEN(index_with_clauses));\n\n\tinfo.extended_options.multitransaction =\n\t\tDatumGetBool(parsed_with_clauses[HypertableIndexFlagMultiTransaction].parsed);\n#ifdef DEBUG\n\tinfo.extended_options.max_chunks =\n\t\tDatumGetInt32(parsed_with_clauses[HypertableIndexFlagMaxChunks].parsed);\n\tinfo.extended_options.barrier_table =\n\t\tDatumGetObjectId(parsed_with_clauses[HypertableIndexFlagBarrierTable].parsed);\n#endif\n\n\t/* Make sure this index is allowed */\n\tif (stmt->concurrent)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"hypertables do not support concurrent \"\n\t\t\t\t\t\t\"index creation\")));\n\n\tif (info.extended_options.multitransaction &&\n\t\t(stmt->unique || stmt->primary || stmt->isconstraint))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\n\t\t\t\t\t \"cannot use timescaledb.transaction_per_chunk with UNIQUE or PRIMARY KEY\")));\n\n\tts_indexing_verify_index(ht->space, stmt);\n\n\tif (info.extended_options.multitransaction)\n\t\tPreventInTransactionBlock(true,\n\t\t\t\t\t\t\t\t  \"CREATE INDEX ... WITH (timescaledb.transaction_per_chunk)\");\n\n\tif (cagg)\n\t{\n\t\t/*\n\t\t * If this is an index creation for cagg, then we need to switch user as the current\n\t\t * user might not have permissions on the internal schema where cagg index will be\n\t\t * created.\n\t\t * Need to restore user soon after this step.\n\t\t */\n\t\tts_cagg_permissions_check(ht->main_table_relid, GetUserId());\n\t\tSWITCH_TO_TS_USER(NameStr(cagg->data.direct_view_schema), uid, saved_uid, sec_ctx);\n\t}\n\n\t/* CREATE INDEX on the root table of the hypertable */\n\troot_table_index = ts_indexing_root_table_create_index(stmt,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   args->query_string,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   info.extended_options.multitransaction);\n\n\tif (cagg)\n\t\tRESTORE_USER(uid, saved_uid, sec_ctx);\n\n\t/* root_table_index will have 0 objectId if the index already exists\n\t * and if_not_exists is true. In that case there is nothing else\n\t * to do here. */\n\tif (!OidIsValid(root_table_index.objectId) && stmt->if_not_exists)\n\t{\n\t\tts_cache_release(&hcache);\n\t\treturn DDL_DONE;\n\t}\n\tAssert(OidIsValid(root_table_index.objectId));\n\n\t/* support ONLY ON clause, index on root table already created */\n\tif (!stmt->relation->inh)\n\t{\n\t\tts_cache_release(&hcache);\n\t\treturn DDL_DONE;\n\t}\n\n\tinfo.obj.objectId = root_table_index.objectId;\n\t/* collect information required for per chunk index creation */\n\tmain_table_relation = table_open(ht->main_table_relid, AccessShareLock);\n\tmain_table_desc = RelationGetDescr(main_table_relation);\n\n\tmain_table_index_relation = index_open(info.obj.objectId, AccessShareLock);\n\tmain_table_index_lock_relid = main_table_index_relation->rd_lockInfo.lockRelId;\n\n\tinfo.extended_options.n_ht_atts = main_table_desc->natts;\n\tinfo.main_table_relid = ht->main_table_relid;\n\n\tindex_close(main_table_index_relation, NoLock);\n\ttable_close(main_table_relation, NoLock);\n\n\t/* create chunk indexes using the same transaction for all the chunks */\n\tif (!info.extended_options.multitransaction)\n\t{\n\t\tCatalogSecurityContext sec_ctx;\n\t\t/*\n\t\t * Change user since chunk's are typically located in an internal\n\t\t * schema and chunk indexes require metadata changes. In the\n\t\t * multi-transaction case, we do this once per chunk.\n\t\t */\n\t\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\t\t/* Recurse to each chunk and create a corresponding index. */\n\t\tforeach_chunk(ht, process_index_chunk, &info);\n\n\t\tts_catalog_restore_user(&sec_ctx);\n\t\tts_cache_release(&hcache);\n\n\t\treturn DDL_DONE;\n\t}\n\n\t/* create chunk indexes using a separate transaction for each chunk */\n\n\t/*\n\t * Lock the index for the remainder of the command. Since we're using\n\t * multiple transactions for index creation, a regular\n\t * transaction-level lock won't prevent the index from being\n\t * concurrently ALTERed or DELETEd. Instead, we grab a session level\n\t * lock on the index, which we'll release when the command is\n\t * finished. (This is the same strategy postgres uses in CREATE INDEX\n\t * CONCURRENTLY)\n\t */\n\tLockRelationIdForSession(&main_table_index_lock_relid, AccessShareLock);\n\n\t/*\n\t * mark the hypertable's index as invalid until all the chunk indexes\n\t * are created. This allows us to determine if the CREATE INDEX\n\t * completed successfully or  not\n\t */\n\tts_indexing_mark_as_invalid(info.obj.objectId);\n\tCacheInvalidateRelcacheByRelid(info.main_table_relid);\n\tCacheInvalidateRelcacheByRelid(info.obj.objectId);\n\n\tts_cache_release(&hcache);\n\n\t/* we need a long-lived context in which to store the list of chunks since the per-transaction\n\t * context will get freed at the end of each transaction. Fortunately we're within just such a\n\t * context now; the PortalContext. */\n\tinfo.mctx = CurrentMemoryContext;\n\tPopActiveSnapshot();\n\tCommitTransactionCommand();\n\n\tforeach_chunk_multitransaction(info.main_table_relid,\n\t\t\t\t\t\t\t\t   info.mctx,\n\t\t\t\t\t\t\t\t   process_index_chunk_multitransaction,\n\t\t\t\t\t\t\t\t   &info);\n\n\tStartTransactionCommand();\n\tPushActiveSnapshot(GetTransactionSnapshot());\n\tMemoryContextSwitchTo(info.mctx);\n\n\tif (multitransaction_create_index_mark_valid(info))\n\t{\n\t\t/* we're done, the index is now valid */\n\t\tts_indexing_mark_as_valid(info.obj.objectId);\n\n\t\tCacheInvalidateRelcacheByRelid(info.main_table_relid);\n\t\tCacheInvalidateRelcacheByRelid(info.obj.objectId);\n\t}\n\n\tPopActiveSnapshot();\n\tCommitTransactionCommand();\n\tStartTransactionCommand();\n\n\tUnlockRelationIdForSession(&main_table_index_lock_relid, AccessShareLock);\n\n\tDEBUG_WAITPOINT(\"process_index_start_indexing_done\");\n\n\treturn DDL_DONE;\n}\n\nstatic int\nchunk_index_mappings_cmp(const void *p1, const void *p2)\n{\n\tconst ChunkIndexMapping *lhs = *((ChunkIndexMapping *const *) p1);\n\tconst ChunkIndexMapping *rhs = *((ChunkIndexMapping *const *) p2);\n\n\tif (lhs->chunkoid < rhs->chunkoid)\n\t\treturn -1;\n\tif (lhs->chunkoid > rhs->chunkoid)\n\t\treturn 1;\n\treturn 0;\n}\n\n/*\n * Cluster a hypertable.\n *\n * The functionality to cluster all chunks of a hypertable is based on the\n * regular cluster function's mode to cluster multiple tables. Since clustering\n * involves taking exclusive locks on all tables for extensive periods of time,\n * each subtable is clustered in its own transaction. This will release all\n * locks on subtables once they are done.\n */\nstatic DDLResult\nprocess_cluster_start(ProcessUtilityArgs *args)\n{\n\tClusterStmt *stmt = (ClusterStmt *) args->parsetree;\n\tCache *hcache;\n\tHypertable *ht;\n\tDDLResult result = DDL_CONTINUE;\n\n\tAssert(IsA(stmt, ClusterStmt));\n\n\t/* If this is a re-cluster on all tables, there is nothing we need to do */\n\tif (NULL == stmt->relation)\n\t\treturn DDL_CONTINUE;\n\n\thcache = ts_hypertable_cache_pin();\n\tht = ts_hypertable_cache_get_entry_rv(hcache, stmt->relation);\n\n\tif (NULL != ht)\n\t{\n\t\tbool is_top_level = (args->context == PROCESS_UTILITY_TOPLEVEL);\n\t\tOid index_relid;\n\t\tRelation index_rel;\n\t\tList *chunk_indexes;\n\t\tListCell *lc;\n\t\tMemoryContext old, mcxt;\n\t\tLockRelId cluster_index_lockid;\n\t\tChunkIndexMapping **mappings = NULL;\n\t\tint i;\n\n\t\tts_hypertable_permissions_check_by_id(ht->fd.id);\n\n\t\t/*\n\t\t * If CLUSTER is run inside a user transaction block; we bail out or\n\t\t * otherwise we'd be holding locks way too long.\n\t\t */\n\t\tPreventInTransactionBlock(is_top_level, \"CLUSTER\");\n\n\t\tif (NULL == stmt->indexname)\n\t\t{\n\t\t\tindex_relid = ts_indexing_find_clustered_index(ht->main_table_relid);\n\t\t\tif (!OidIsValid(index_relid))\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t\t\t errmsg(\"there is no previously clustered index for table \\\"%s\\\"\",\n\t\t\t\t\t\t\t\tget_rel_name(ht->main_table_relid))));\n\t\t}\n\t\telse\n\t\t\tindex_relid =\n\t\t\t\tget_relname_relid(stmt->indexname, get_rel_namespace(ht->main_table_relid));\n\n\t\tif (!OidIsValid(index_relid))\n\t\t{\n\t\t\t/* Let regular process utility handle */\n\t\t\tts_cache_release(&hcache);\n\t\t\treturn DDL_CONTINUE;\n\t\t}\n\n\t\t/*\n\t\t * DROP INDEX locks the table then the index, to prevent deadlocks we\n\t\t * lock them in the same order. The main table lock will be released\n\t\t * when the current transaction commits, and never taken again. We\n\t\t * will use the index relation to grab a session lock on the index,\n\t\t * which we will hold throughout CLUSTER\n\t\t */\n\t\tLockRelationOid(ht->main_table_relid, AccessShareLock);\n\t\tindex_rel = index_open(index_relid, AccessShareLock);\n\t\tcluster_index_lockid = index_rel->rd_lockInfo.lockRelId;\n\n\t\tindex_close(index_rel, NoLock);\n\n\t\t/*\n\t\t * mark the main table as clustered, even though it has no data, so\n\t\t * future calls to CLUSTER don't need to pass in the index\n\t\t */\n\t\tts_chunk_index_mark_clustered(ht->main_table_relid, index_relid);\n\n\t\t/* we will keep holding this lock throughout CLUSTER */\n\t\tLockRelationIdForSession(&cluster_index_lockid, AccessShareLock);\n\n\t\t/*\n\t\t * The list of chunks and their indexes need to be on a memory context\n\t\t * that will survive moving to a new transaction for each chunk\n\t\t */\n\t\tmcxt = AllocSetContextCreate(PortalContext, \"Hypertable cluster\", ALLOCSET_DEFAULT_SIZES);\n\n\t\t/*\n\t\t * Get a list of chunks and indexes that correspond to the\n\t\t * hypertable's index\n\t\t */\n\t\told = MemoryContextSwitchTo(mcxt);\n\t\tchunk_indexes = ts_chunk_index_get_mappings(ht, index_relid);\n\n\t\tif (list_length(chunk_indexes) > 0)\n\t\t{\n\t\t\t/* Sort the mappings on chunk OID. This makes the verbose output more\n\t\t\t * predictable in tests, but isn't strictly necessary. We could also do\n\t\t\t * it only for \"verbose\" output, but this doesn't seem worth it as the\n\t\t\t * cost of sorting is quickly amortized over the actual work to cluster\n\t\t\t * the chunks. */\n\t\t\tmappings = (ChunkIndexMapping **) palloc(sizeof(ChunkIndexMapping *) *\n\t\t\t\t\t\t\t\t\t\t\t\t\t list_length(chunk_indexes));\n\n\t\t\ti = 0;\n\t\t\tforeach (lc, chunk_indexes)\n\t\t\t\tmappings[i++] = lfirst(lc);\n\n\t\t\tqsort((void *) mappings,\n\t\t\t\t  list_length(chunk_indexes),\n\t\t\t\t  sizeof(ChunkIndexMapping *),\n\t\t\t\t  chunk_index_mappings_cmp);\n\t\t}\n\n\t\tMemoryContextSwitchTo(old);\n\n\t\thcache->release_on_commit = false;\n\n\t\t/* Commit to get out of starting transaction */\n\t\tPopActiveSnapshot();\n\t\tCommitTransactionCommand();\n\n\t\tfor (i = 0; i < list_length(chunk_indexes); i++)\n\t\t{\n\t\t\tChunkIndexMapping *cim = mappings[i];\n\n\t\t\t/* Start a new transaction for each relation. */\n\t\t\tStartTransactionCommand();\n\t\t\t/* functions in indexes may want a snapshot set */\n\t\t\tPushActiveSnapshot(GetTransactionSnapshot());\n\n\t\t\t/*\n\t\t\t * We must mark each chunk index as clustered before calling\n\t\t\t * cluster_rel() because it expects indexes that need to be\n\t\t\t * rechecked (due to new transaction) to already have that mark\n\t\t\t * set\n\t\t\t */\n\t\t\tts_chunk_index_mark_clustered(cim->chunkoid, cim->indexoid);\n\n\t\t\t/* Do the job. */\n\n\t\t\t/*\n\t\t\t * Since we keep OIDs between transactions, there is a potential\n\t\t\t * issue if an OID gets reassigned between two subtransactions\n\t\t\t */\n#if PG18_LT\n\t\t\tcluster_rel(cim->chunkoid, cim->indexoid, get_cluster_options(stmt));\n#else\n\t\t\tRelation rel = table_open(cim->chunkoid, AccessExclusiveLock);\n\t\t\tcluster_rel(rel, cim->indexoid, get_cluster_options(stmt));\n#endif\n\t\t\tPopActiveSnapshot();\n\t\t\tCommitTransactionCommand();\n\t\t}\n\n\t\thcache->release_on_commit = true;\n\t\t/* Start a new transaction for the cleanup work. */\n\t\tStartTransactionCommand();\n\n\t\t/* Clean up working storage */\n\t\tMemoryContextDelete(mcxt);\n\n\t\tUnlockRelationIdForSession(&cluster_index_lockid, AccessShareLock);\n\t\tresult = DDL_DONE;\n\t}\n\n\tts_cache_release(&hcache);\n\treturn result;\n}\n\ntypedef struct CreateTableInfo\n{\n\tbool hypertable;\n\tWithClauseResult *with_clauses;\n} CreateTableInfo;\n\nstatic CreateTableInfo create_table_info = { 0 };\n\n/*\n * Scan the table for a suitable default partitioning column.\n *\n * The default partitioning column is the first timestamp column\n *\n * Caller is expected to have appropriate lock on the table.\n */\nstatic char *\nget_default_partition_column(Oid relid)\n{\n\tRelation rel;\n\tTupleDesc tupdesc;\n\tint i;\n\tchar *column_name = NULL;\n\n\trel = relation_open(relid, NoLock);\n\ttupdesc = RelationGetDescr(rel);\n\n\tfor (i = 0; i < tupdesc->natts; i++)\n\t{\n\t\tForm_pg_attribute att = TupleDescAttr(tupdesc, i);\n\n\t\tif (att->attisdropped)\n\t\t\tcontinue;\n\n\t\tif (att->atttypid == TIMESTAMPOID || att->atttypid == TIMESTAMPTZOID)\n\t\t{\n\t\t\tcolumn_name = pstrdup(NameStr(att->attname));\n\t\t\tbreak;\n\t\t}\n\t}\n\n\trelation_close(rel, NoLock);\n\treturn column_name;\n}\n\n/*\n * Process create table statements.\n *\n * NOTE that this function should be called after parse analysis (in an end DDL\n * trigger or by running parse analysis manually).\n */\nstatic void\nprocess_create_table_end(Node *parsetree)\n{\n\tCreateStmt *stmt = (CreateStmt *) parsetree;\n\tListCell *lc;\n\n\tverify_constraint_list(stmt->relation, stmt->constraints);\n\n\t/*\n\t * Only after parse analysis does tableElts contain only ColumnDefs. So,\n\t * if we capture this in processUtility, we should be prepared to have\n\t * constraint nodes and TableLikeClauses intermixed\n\t */\n\tforeach (lc, stmt->tableElts)\n\t{\n\t\tColumnDef *coldef;\n\n\t\tswitch (nodeTag(lfirst(lc)))\n\t\t{\n\t\t\tcase T_ColumnDef:\n\t\t\t\tcoldef = lfirst(lc);\n\t\t\t\tverify_constraint_list(stmt->relation, coldef->constraints);\n\t\t\t\tbreak;\n\t\t\tcase T_Constraint:\n\n\t\t\t\t/*\n\t\t\t\t * There should be no Constraints in the list after parse\n\t\t\t\t * analysis, but this case is included anyway for completeness\n\t\t\t\t */\n\t\t\t\tverify_constraint(stmt->relation, lfirst(lc));\n\t\t\t\tbreak;\n\t\t\tcase T_TableLikeClause:\n\t\t\t\t/* Some as above case */\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (create_table_info.hypertable)\n\t{\n\t\tOid table_relid = RangeVarGetRelid(stmt->relation, NoLock, true);\n\t\tchar *time_column = NULL;\n\t\tif (stmt->partspec != NULL)\n\t\t{\n\t\t\ttime_column = ((PartitionElem *) linitial(stmt->partspec->partParams))->name;\n\t\t}\n\t\telse if (create_table_info.with_clauses[CreateTableFlagTimeColumn].is_default)\n\t\t{\n\t\t\ttime_column = get_default_partition_column(table_relid);\n\t\t\tif (time_column)\n\t\t\t\tereport(NOTICE,\n\t\t\t\t\t\t(errmsg(\"using column \\\"%s\\\" as partitioning column\", time_column),\n\t\t\t\t\t\t errhint(\"Use \\\"timescaledb.partition_column\\\" to specify a different \"\n\t\t\t\t\t\t\t\t \"column to use as \"\n\t\t\t\t\t\t\t\t \"partitioning column.\")));\n\t\t\telse\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_COLUMN),\n\t\t\t\t\t\t errmsg(\"partition column could not be determined\"),\n\t\t\t\t\t\t errhint(\n\t\t\t\t\t\t\t \"Use \\\"timescaledb.partition_column\\\" to specify the column to use as \"\n\t\t\t\t\t\t\t \"partitioning column.\")));\n\t\t}\n\t\telse\n\t\t\ttime_column = TextDatumGetCString(\n\t\t\t\tcreate_table_info.with_clauses[CreateTableFlagTimeColumn].parsed);\n\n\t\tNameData time_column_name;\n\t\tNameData associated_schema_name;\n\t\tNameData associated_table_prefix;\n\t\tnamestrcpy(&time_column_name, time_column);\n\t\tuint32 flags = 0;\n\t\tbool has_associated_schema = false;\n\t\tbool has_associated_table_prefix = false;\n\n\t\tif (get_attnum(table_relid, time_column) == InvalidAttrNumber)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_COLUMN),\n\t\t\t\t\t errmsg(\"column \\\"%s\\\" does not exist\", time_column)));\n\n\t\tOid interval_type = InvalidOid;\n\t\tDatum interval = UnassignedDatum;\n\n\t\tif (!create_table_info.with_clauses[CreateTableFlagChunkTimeInterval].is_default)\n\t\t{\n\t\t\tAttrNumber time_attno = get_attnum(table_relid, time_column);\n\t\t\tOid time_type = get_atttype(table_relid, time_attno);\n\n\t\t\tinterval =\n\t\t\t\tts_create_table_parse_chunk_time_interval(create_table_info.with_clauses\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  [CreateTableFlagChunkTimeInterval],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  time_type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  &interval_type);\n\t\t}\n\n\t\tif (!create_table_info.with_clauses[CreateTableFlagCreateDefaultIndexes].is_default)\n\t\t{\n\t\t\tif (!DatumGetBool(\n\t\t\t\t\tcreate_table_info.with_clauses[CreateTableFlagCreateDefaultIndexes].parsed))\n\t\t\t\tflags |= HYPERTABLE_CREATE_DISABLE_DEFAULT_INDEXES;\n\t\t}\n\n\t\tif (!create_table_info.with_clauses[CreateTableFlagAssociatedSchema].is_default)\n\t\t{\n\t\t\thas_associated_schema = true;\n\t\t\tnamestrcpy(&associated_schema_name,\n\t\t\t\t\t   TextDatumGetCString(\n\t\t\t\t\t\t   create_table_info.with_clauses[CreateTableFlagAssociatedSchema].parsed));\n\t\t}\n\n\t\tif (!create_table_info.with_clauses[CreateTableFlagAssociatedTablePrefix].is_default)\n\t\t{\n\t\t\thas_associated_table_prefix = true;\n\t\t\tnamestrcpy(&associated_table_prefix,\n\t\t\t\t\t   TextDatumGetCString(\n\t\t\t\t\t\t   create_table_info.with_clauses[CreateTableFlagAssociatedTablePrefix]\n\t\t\t\t\t\t\t   .parsed));\n\t\t}\n\n\t\tDimensionInfo *open_dim_info =\n\t\t\tts_dimension_info_create_open(table_relid,\n\t\t\t\t\t\t\t\t\t\t  &time_column_name, /* column name */\n\t\t\t\t\t\t\t\t\t\t  interval,\t\t\t /* interval */\n\t\t\t\t\t\t\t\t\t\t  interval_type,\t /* interval type */\n\t\t\t\t\t\t\t\t\t\t  InvalidOid\t\t /* partitioning func */\n\t\t\t);\n\n\t\tChunkSizingInfo *csi = ts_chunk_sizing_info_get_default_disabled(table_relid);\n\t\tcsi->colname = time_column;\n\n\t\tCatalogSecurityContext sec_ctx;\n\t\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\t\tint32 ht_id = ts_catalog_table_next_seq_id(ts_catalog_get(), HYPERTABLE);\n\t\tts_catalog_restore_user(&sec_ctx);\n\n\t\tif (ts_hypertable_create_from_info(table_relid,\n\t\t\t\t\t\t\t\t\t\t   ht_id,\n\t\t\t\t\t\t\t\t\t\t   flags,\t\t  /* flags */\n\t\t\t\t\t\t\t\t\t\t   open_dim_info, /* open_dim_info */\n\t\t\t\t\t\t\t\t\t\t   NULL,\t\t  /* closed_dim_info */\n\t\t\t\t\t\t\t\t\t\t   has_associated_schema ?\n\t\t\t\t\t\t\t\t\t\t\t   &associated_schema_name :\n\t\t\t\t\t\t\t\t\t\t\t   NULL, /* associated_schema_name */\n\t\t\t\t\t\t\t\t\t\t   has_associated_table_prefix ?\n\t\t\t\t\t\t\t\t\t\t\t   &associated_table_prefix :\n\t\t\t\t\t\t\t\t\t\t\t   NULL, /* associated_table_prefix */\n\t\t\t\t\t\t\t\t\t\t   csi))\n\t\t{\n\t\t\tbool enable_columnstore;\n\t\t\tif (ts_license_is_apache() &&\n\t\t\t\tcreate_table_info.with_clauses[CreateTableFlagColumnstore].is_default)\n\t\t\t\tenable_columnstore = false;\n\t\t\telse\n\t\t\t\tenable_columnstore =\n\t\t\t\t\tDatumGetBool(create_table_info.with_clauses[CreateTableFlagColumnstore].parsed);\n\n\t\t\tif (enable_columnstore)\n\t\t\t{\n\t\t\t\tHypertable *ht = ts_hypertable_get_by_id(ht_id);\n\t\t\t\tts_cm_functions->columnstore_setup(ht, create_table_info.with_clauses);\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic inline const char *\ntypename_get_unqual_name(TypeName *tn)\n{\n\treturn strVal(llast(tn->names));\n}\n\nstatic void\nprocess_alter_column_type_start(ParseState *pstate, Hypertable *ht, AlterTableCmd *cmd)\n{\n\tint i;\n\n\t/* check if it's a partitioning column */\n\tfor (i = 0; i < ht->space->num_dimensions; i++)\n\t{\n\t\tDimension *dim = &ht->space->dimensions[i];\n\n\t\tif (IS_CLOSED_DIMENSION(dim) &&\n\t\t\tstrncmp(NameStr(dim->fd.column_name), cmd->name, NAMEDATALEN) == 0)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_TS_OPERATION_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"cannot change the type of a hash-partitioned column\")));\n\n\t\tif (dim->partitioning != NULL &&\n\t\t\tstrncmp(NameStr(dim->fd.column_name), cmd->name, NAMEDATALEN) == 0)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_TS_OPERATION_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"cannot change the type of a column with a custom partitioning \"\n\t\t\t\t\t\t\t\"function\")));\n\t}\n\n\t/*\n\t * Check if column has statistics enabled on it, if yes we need to check that the\n\t * new type is a permissible type.\n\t */\n\tForm_chunk_column_stats form =\n\t\tts_chunk_column_stats_lookup(ht->fd.id, INVALID_CHUNK_ID, cmd->name);\n\n\tif (form != NULL)\n\t{\n\t\tColumnDef *def = (ColumnDef *) cmd->def;\n\t\tTypeName *typeName = def->typeName;\n\t\tOid newtypid = typenameTypeId(pstate, typeName);\n\n\t\t/*\n\t\t * We only support a subset of types for ranges right now. If it's a\n\t\t * supported type and ranges have been calculated then we should\n\t\t * still be able to use them for chunk exclusion.\n\t\t *\n\t\t * So, we only do a basic check for compatible new data type. Nothing\n\t\t * else needs to be done.\n\t\t */\n\t\tswitch (newtypid)\n\t\t{\n\t\t\tcase INT2OID:\n\t\t\tcase INT4OID:\n\t\t\tcase INT8OID:\n\t\t\tcase TIMESTAMPOID:\n\t\t\tcase TIMESTAMPTZOID:\n\t\t\tcase DATEOID:\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t\t errmsg(\"data type \\\"%s\\\" unsupported for statistics calculation\",\n\t\t\t\t\t\t\t\tformat_type_be(newtypid)),\n\t\t\t\t\t\t errhint(\"Integer-like, timestamp-like data types supported currently.\"\n\t\t\t\t\t\t\t\t \" Disable the stats using disable_column_stats function\"\n\t\t\t\t\t\t\t\t \" before changing the type\")));\n\t\t}\n\t}\n}\n\nstatic void\nprocess_alter_column_type_end(Hypertable *ht, AlterTableCmd *cmd)\n{\n\tColumnDef *coldef = (ColumnDef *) cmd->def;\n\tOid new_type = TypenameGetTypid(typename_get_unqual_name(coldef->typeName));\n\tDimension *dim =\n\t\tts_hyperspace_get_mutable_dimension_by_name(ht->space, DIMENSION_TYPE_ANY, cmd->name);\n\n\tif (NULL == dim)\n\t\treturn;\n\n\tts_dimension_set_type(dim, new_type);\n\tts_process_utility_set_expect_chunk_modification(true);\n\tts_chunk_recreate_all_constraints_for_dimension(ht, dim->fd.id);\n\tts_process_utility_set_expect_chunk_modification(false);\n}\n\nstatic void\nprocess_altertable_clusteron_end(Hypertable *ht, AlterTableCmd *cmd)\n{\n\tOid index_relid = ts_get_relation_relid(NameStr(ht->fd.schema_name), cmd->name, true);\n\n\t/* If this is part of changing the type of a column that is used in a clustered index\n\t * the above lookup might fail. But in this case we don't need to mark the index clustered\n\t * as postgres takes care of that already */\n\tif (!OidIsValid(index_relid))\n\t\treturn;\n\n\tList *chunk_indexes = ts_chunk_index_get_mappings(ht, index_relid);\n\tListCell *lc;\n\n\tforeach (lc, chunk_indexes)\n\t{\n\t\tChunkIndexMapping *cim = lfirst(lc);\n\n\t\tts_chunk_index_mark_clustered(cim->chunkoid, cim->indexoid);\n\t}\n}\n\n/*\n * Generic function to recurse ALTER TABLE commands to chunks.\n *\n * Call with foreach_chunk().\n */\nstatic void\nprocess_altertable_chunk(Hypertable *ht, Oid chunk_relid, void *arg)\n{\n\tAlterTableCmd *cmd = arg;\n\n\t/* Don't propagate ALTER TABLE SET to foreign tables */\n\tif (get_rel_relkind(chunk_relid) == RELKIND_FOREIGN_TABLE &&\n\t\t(cmd->subtype == AT_SetOptions || cmd->subtype == AT_ResetOptions ||\n\t\t cmd->subtype == AT_SetRelOptions || cmd->subtype == AT_ReplaceRelOptions ||\n\t\t cmd->subtype == AT_ResetRelOptions))\n\t\treturn;\n\n\tAlterTableInternal(chunk_relid, list_make1(cmd), false);\n}\n\nstatic void\nprocess_altertable_chunk_replica_identity(Hypertable *ht, Oid chunk_relid, void *arg)\n{\n\tAlterTableCmd *cmd = castNode(AlterTableCmd, copyObject(arg));\n\tReplicaIdentityStmt *stmt = castNode(ReplicaIdentityStmt, cmd->def);\n\tchar relkind = get_rel_relkind(chunk_relid);\n\n\t/* If this is not a local chunk (e.g., it is foreign table representing a\n\t * data node or OSM chunk), then we don't set replica identity locally */\n\tif (relkind != RELKIND_RELATION)\n\t\treturn;\n\n\tif (stmt->identity_type == REPLICA_IDENTITY_INDEX)\n\t{\n\t\tChunk *chunk = ts_chunk_get_by_relid(chunk_relid, true);\n\t\tOid hyper_schema_oid = get_rel_namespace(ht->main_table_relid);\n\t\tOid hyper_index_oid = get_relname_relid(stmt->name, hyper_schema_oid);\n\n\t\tAssert(OidIsValid(hyper_index_oid));\n\n\t\tRelation chunk_rel = table_open(chunk_relid, AccessExclusiveLock);\n\t\tOid chunk_index_relid =\n\t\t\tts_chunk_index_get_by_hypertable_indexrelid(chunk_rel, hyper_index_oid);\n\t\ttable_close(chunk_rel, NoLock);\n\t\tif (!OidIsValid(chunk_index_relid))\n\t\t\telog(ERROR,\n\t\t\t\t \"chunk \\\"%s.%s\\\" has no index corresponding to hypertable index \\\"%s\\\"\",\n\t\t\t\t NameStr(chunk->fd.schema_name),\n\t\t\t\t NameStr(chunk->fd.table_name),\n\t\t\t\t stmt->name);\n\n\t\tstmt->name = get_rel_name(chunk_index_relid);\n\t}\n\n\tAlterTableInternal(chunk_relid, list_make1(cmd), false);\n}\n\nstatic void\nprocess_altertable_replica_identity(Hypertable *ht, AlterTableCmd *cmd)\n{\n\tReplicaIdentityStmt *stmt = castNode(ReplicaIdentityStmt, cmd->def);\n\n\tif (stmt->identity_type == REPLICA_IDENTITY_INDEX)\n\t{\n\t\tOid hyper_schema_oid = get_rel_namespace(ht->main_table_relid);\n\t\tOid hyper_index_oid;\n\n\t\thyper_index_oid = get_relname_relid(stmt->name, hyper_schema_oid);\n\n\t\tif (!OidIsValid(hyper_index_oid))\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t\t errmsg(\"index \\\"%s\\\" for table \\\"%s.%s\\\" does not exist\",\n\t\t\t\t\t\t\tstmt->name,\n\t\t\t\t\t\t\tNameStr(ht->fd.schema_name),\n\t\t\t\t\t\t\tNameStr(ht->fd.table_name))));\n\t}\n\n\tforeach_chunk(ht, process_altertable_chunk_replica_identity, cmd);\n}\n\nstatic void\nprocess_altertable_set_tablespace_end(Hypertable *ht, AlterTableCmd *cmd)\n{\n\tNameData tspc_name;\n\tTablespaces *tspcs;\n\n\tnamestrcpy(&tspc_name, cmd->name);\n\n\ttspcs = ts_tablespace_scan(ht->fd.id);\n\n\tif (tspcs->num_tablespaces > 1)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"cannot set new tablespace when multiple tablespaces are attached to \"\n\t\t\t\t\t\t\"hypertable \\\"%s\\\"\",\n\t\t\t\t\t\tget_rel_name(ht->main_table_relid)),\n\t\t\t\t errhint(\"Detach tablespaces before altering the hypertable.\")));\n\n\tif (tspcs->num_tablespaces == 1)\n\t{\n\t\tAssert(ts_hypertable_has_tablespace(ht, tspcs->tablespaces[0].tablespace_oid));\n\t\tts_tablespace_delete(ht->fd.id,\n\t\t\t\t\t\t\t NameStr(tspcs->tablespaces[0].fd.tablespace_name),\n\t\t\t\t\t\t\t tspcs->tablespaces[0].tablespace_oid);\n\t}\n\n\tts_tablespace_attach_internal(&tspc_name, ht->main_table_relid, true);\n\tforeach_chunk(ht, process_altertable_chunk, cmd);\n\tif (TS_HYPERTABLE_HAS_COMPRESSION_TABLE(ht))\n\t{\n\t\tHypertable *compressed_hypertable =\n\t\t\tts_hypertable_get_by_id(ht->fd.compressed_hypertable_id);\n\t\tAlterTableInternal(compressed_hypertable->main_table_relid, list_make1(cmd), false);\n\n\t\tList *chunks = ts_chunk_get_by_hypertable_id(ht->fd.compressed_hypertable_id);\n\t\tListCell *lc;\n\t\tforeach (lc, chunks)\n\t\t{\n\t\t\tChunk *chunk = lfirst(lc);\n\t\t\tAlterTableInternal(chunk->table_id, list_make1(cmd), false);\n\t\t}\n\t\tprocess_altertable_set_tablespace_end(compressed_hypertable, cmd);\n\t}\n}\n\nstatic void\nprocess_altertable_end_index(Node *parsetree, CollectedCommand *cmd)\n{\n\tAlterTableStmt *stmt = (AlterTableStmt *) parsetree;\n\tOid indexrelid = AlterTableLookupRelation(stmt, NoLock);\n\tOid tablerelid = IndexGetRelation(indexrelid, false);\n\tCache *hcache;\n\tHypertable *ht;\n\n\tif (!OidIsValid(tablerelid))\n\t\treturn;\n\n\tht = ts_hypertable_cache_get_cache_and_entry(tablerelid, CACHE_FLAG_MISSING_OK, &hcache);\n\n\tif (NULL != ht)\n\t{\n\t\tListCell *lc;\n\n\t\tforeach (lc, stmt->cmds)\n\t\t{\n\t\t\tAlterTableCmd *cmd = (AlterTableCmd *) lfirst(lc);\n\n\t\t\tswitch (cmd->subtype)\n\t\t\t{\n\t\t\t\tcase AT_SetTableSpace:\n\t\t\t\t\tts_chunk_index_set_tablespace(ht, indexrelid, cmd->name);\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tts_cache_release(&hcache);\n}\n\nstatic void\nprocess_altertable_chunk_propagate_to_compressed(AlterTableCmd *cmd, Oid relid)\n{\n\tChunk *chunk = ts_chunk_get_by_relid(relid, false);\n\n\tif (chunk == NULL)\n\t\treturn;\n\n\tif (ts_chunk_contains_compressed_data(chunk))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"changing tablespace of columnstore chunk is not supported\"),\n\t\t\t\t errhint(\"Please use the corresponding chunk on the rowstore hypertable \"\n\t\t\t\t\t\t \"instead.\")));\n\n\t/* set tablespace for compressed chunk */\n\tif (chunk->fd.compressed_chunk_id != INVALID_CHUNK_ID)\n\t{\n\t\tChunk *compressed_chunk = ts_chunk_get_by_id(chunk->fd.compressed_chunk_id, true);\n\n\t\tAlterTableInternal(compressed_chunk->table_id, list_make1(cmd), false);\n\t}\n}\n\nstatic DDLResult\nprocess_altertable_start_table(ProcessUtilityArgs *args)\n{\n\tAlterTableStmt *stmt = (AlterTableStmt *) args->parsetree;\n\tOid reloid = AlterTableLookupRelation(stmt, NoLock);\n\tCache *hcache;\n\tHypertable *ht;\n\tListCell *lc;\n\n\tif (!OidIsValid(reloid))\n\t\treturn DDL_CONTINUE;\n\n\tcheck_chunk_alter_table_operation_allowed(reloid, stmt);\n\n\tht = ts_hypertable_cache_get_cache_and_entry(reloid, CACHE_FLAG_MISSING_OK, &hcache);\n\tif (ht != NULL)\n\t{\n\t\tts_hypertable_permissions_check_by_id(ht->fd.id);\n\t\tcheck_continuous_agg_alter_table_allowed(ht, stmt);\n\t\tcheck_alter_table_allowed_on_ht_with_compression(ht, stmt);\n\n\t\tif (!stmt->relation->inh)\n\t\t{\n\t\t\t/* only allow ALTER TABLE ... SET (option) with ONLY */\n\t\t\tforeach (lc, stmt->cmds)\n\t\t\t{\n\t\t\t\tAlterTableCmd *cmd = (AlterTableCmd *) lfirst(lc);\n\t\t\t\tswitch (cmd->subtype)\n\t\t\t\t{\n\t\t\t\t\tcase AT_SetRelOptions:\n\t\t\t\t\tcase AT_ResetRelOptions:\n\t\t\t\t\tcase AT_ReplaceRelOptions:\n\t\t\t\t\tcase AT_SetOptions:\n\t\t\t\t\tcase AT_ResetOptions:\n\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\tdefault:\n\t\t\t\t\t\trelation_not_only(stmt->relation);\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tforeach (lc, stmt->cmds)\n\t{\n\t\tAlterTableCmd *cmd = (AlterTableCmd *) lfirst(lc);\n\n\t\tswitch (cmd->subtype)\n\t\t{\n\t\t\tcase AT_AddIndex:\n\t\t\t{\n\t\t\t\tIndexStmt *istmt = (IndexStmt *) cmd->def;\n\n\t\t\t\tAssert(IsA(cmd->def, IndexStmt));\n\n\t\t\t\tif (NULL != ht && istmt->isconstraint)\n\t\t\t\t\tverify_constraint_hypertable(ht, cmd->def);\n\t\t\t}\n\t\t\tbreak;\n\t\t\tcase AT_SetNotNull:\n\t\t\tcase AT_DropNotNull:\n\t\t\t\tif (ht)\n\t\t\t\t\tprocess_altertable_alter_not_null(ht, cmd);\n\t\t\t\tbreak;\n\t\t\tcase AT_AddColumn:\n#if PG16_LT\n\t\t\tcase AT_AddColumnRecurse:\n#endif\n\t\t\t{\n\t\t\t\tColumnDef *col;\n\t\t\t\tListCell *constraint_lc;\n\n\t\t\t\tAssert(IsA(cmd->def, ColumnDef));\n\t\t\t\tcol = (ColumnDef *) cmd->def;\n\t\t\t\tif (ht && TS_HYPERTABLE_HAS_COMPRESSION_ENABLED(ht))\n\t\t\t\t\tcheck_altertable_add_column_for_compressed(args->parse_state, ht, col);\n\t\t\t\tif (ht)\n\t\t\t\t\tforeach (constraint_lc, col->constraints)\n\t\t\t\t\t\tverify_constraint_hypertable(ht, lfirst(constraint_lc));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase AT_DropColumn:\n#if PG16_LT\n\t\t\tcase AT_DropColumnRecurse:\n#endif\n\t\t\t\tif (ht)\n\t\t\t\t\tprocess_altertable_drop_column(ht, cmd);\n\t\t\t\tbreak;\n\t\t\tcase AT_AddConstraint:\n#if PG16_LT\n\t\t\tcase AT_AddConstraintRecurse:\n#endif\n\t\t\t\tAssert(IsA(cmd->def, Constraint));\n\n\t\t\t\tif (ht)\n\t\t\t\t\tverify_constraint_hypertable(ht, cmd->def);\n\t\t\t\tbreak;\n\t\t\tcase AT_AlterColumnType:\n\t\t\t\tAssert(IsA(cmd->def, ColumnDef));\n\n\t\t\t\tif (ht)\n\t\t\t\t\tprocess_alter_column_type_start(args->parse_state, ht, cmd);\n\t\t\t\tbreak;\n\t\t\tcase AT_AttachPartition:\n\t\t\t{\n\t\t\t\tRangeVar *relation;\n\t\t\t\tPartitionCmd *partstmt;\n\n\t\t\t\tpartstmt = (PartitionCmd *) cmd->def;\n\t\t\t\trelation = partstmt->name;\n\t\t\t\tAssert(relation);\n\n\t\t\t\tif (OidIsValid(ts_hypertable_relid(relation)))\n\t\t\t\t{\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t\t errmsg(\"hypertables do not support native \"\n\t\t\t\t\t\t\t\t\t\"postgres partitioning\")));\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase AT_SetRelOptions:\n\t\t\t{\n\t\t\t\tif (ht != NULL)\n\t\t\t\t{\n\t\t\t\t\tEventTriggerAlterTableStart(args->parsetree);\n\t\t\t\t\t/* If we dealt with the option, we remove it from the\n\t\t\t\t\t * list. We do not set the result variable since there\n\t\t\t\t\t * could be other options that are not dealt with. */\n\t\t\t\t\tif (process_altertable_set_options(cmd, ht) == DDL_DONE)\n\t\t\t\t\t\tstmt->cmds = foreach_delete_current(stmt->cmds, lc);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tcheck_no_timescale_options(cmd, reloid);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase AT_ResetRelOptions:\n\t\t\tcase AT_ReplaceRelOptions:\n\t\t\t\tif (ht)\n\t\t\t\t{\n\t\t\t\t\tprocess_altertable_reset_options(cmd, ht);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tcheck_no_timescale_options(cmd, reloid);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase AT_SetTableSpace:\n\t\t\tcase AT_SetLogged:\n\t\t\tcase AT_SetUnLogged:\n\t\t\t\tif (!ht)\n\t\t\t\t\tprocess_altertable_chunk_propagate_to_compressed(cmd, reloid);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tts_cache_release(&hcache);\n\n\t/* If there are any commands remaining in the list, we need to deal with\n\t * them. Otherwise, we just skip the rest. */\n\treturn (list_length(stmt->cmds) > 0) ? DDL_CONTINUE : DDL_DONE;\n}\n\nstatic void\ncontinuous_agg_with_clause_perm_check(ContinuousAgg *cagg, Oid view_relid)\n{\n\tOid ownerid = ts_rel_get_owner(view_relid);\n\n\tif (!has_privs_of_role(GetUserId(), ownerid))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),\n\t\t\t\t errmsg(\"must be owner of continuous aggregate \\\"%s\\\"\", get_rel_name(view_relid))));\n}\n\nstatic List *\nprocess_altercontinuousagg_set_with(ContinuousAgg *cagg, Oid view_relid, const List *defelems)\n{\n\tWithClauseResult *parse_results;\n\tList *pg_options = NIL, *other_namespace_options = NIL, *cagg_options = NIL;\n\n\tcontinuous_agg_with_clause_perm_check(cagg, view_relid);\n\n\tts_with_clause_filter(defelems, &cagg_options, &other_namespace_options, &pg_options);\n\n\tif (list_length(pg_options) > 0)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"only timescaledb parameters allowed in WITH clause for continuous \"\n\t\t\t\t\t\t\"aggregate\")));\n\n\tif (list_length(cagg_options) > 0)\n\t{\n\t\tparse_results = ts_create_materialized_view_with_clause_parse(cagg_options);\n\t\tts_cm_functions->continuous_agg_update_options(cagg, parse_results);\n\t}\n\tif (list_length(other_namespace_options) > 0)\n\t{\n\t\treturn other_namespace_options;\n\t}\n\telse\n\t\treturn NIL;\n}\n\n/* Run an alter table command on a relation */\nstatic void\nalter_table_by_relation(RangeVar *relation, AlterTableCmd *cmd)\n{\n\tconst Oid relid = RangeVarGetRelid(relation, NoLock, true);\n\tAlterTableInternal(relid, list_make1(cmd), false);\n}\n\n/* Run an alter table command on a relation given by name */\nstatic void\nalter_table_by_name(Name schema_name, Name table_name, AlterTableCmd *cmd)\n{\n\talter_table_by_relation(makeRangeVar(NameStr(*schema_name), NameStr(*table_name), -1), cmd);\n}\n\n/* Alter a hypertable and do some extra processing */\nstatic void\nalter_hypertable_by_id(int32 hypertable_id, AlterTableStmt *stmt, AlterTableCmd *cmd,\n\t\t\t\t\t   void (*extra)(Hypertable *, AlterTableCmd *))\n{\n\tCache *hcache = ts_hypertable_cache_pin();\n\tHypertable *ht = ts_hypertable_cache_get_entry_by_id(hcache, hypertable_id);\n\tAssert(ht); /* Broken continuous aggregate */\n\tts_hypertable_permissions_check_by_id(ht->fd.id);\n\tcheck_alter_table_allowed_on_ht_with_compression(ht, stmt);\n\trelation_not_only(stmt->relation);\n\tAlterTableInternal(ht->main_table_relid, list_make1(cmd), false);\n\t(*extra)(ht, cmd);\n\tts_cache_release(&hcache);\n}\n\nstatic DDLResult\nprocess_altertable_start_matview(ProcessUtilityArgs *args)\n{\n\tAlterTableStmt *stmt = (AlterTableStmt *) args->parsetree;\n\tconst Oid view_relid = RangeVarGetRelid(stmt->relation, NoLock, true);\n\tContinuousAgg *cagg;\n\tListCell *lc;\n\n\tDDLResult ddl_res = DDL_DONE;\n\tif (!OidIsValid(view_relid))\n\t\treturn DDL_CONTINUE;\n\n\tcagg = ts_continuous_agg_find_by_relid(view_relid);\n\n\tif (cagg == NULL)\n\t\treturn DDL_CONTINUE;\n\n\tcontinuous_agg_with_clause_perm_check(cagg, view_relid);\n\n\tforeach (lc, stmt->cmds)\n\t{\n\t\tAlterTableCmd *cmd = (AlterTableCmd *) lfirst(lc);\n\n\t\tswitch (cmd->subtype)\n\t\t{\n\t\t\tcase AT_SetRelOptions:\n\t\t\t\tif (!IsA(cmd->def, List))\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t\t\t errmsg(\"expected set options to contain a list\")));\n\t\t\t\tList *other_namespace_opt =\n\t\t\t\t\tprocess_altercontinuousagg_set_with(cagg, view_relid, (List *) cmd->def);\n\t\t\t\t/* pass on SET options to other extensions like timescaledb-lake. only if\n\t\t\t\t * there are additional PG related ones, we error out\n\t\t\t\t */\n\t\t\t\tif (other_namespace_opt != NIL)\n\t\t\t\t{\n\t\t\t\t\tcmd->def = (Node *) other_namespace_opt;\n\t\t\t\t\tddl_res = DDL_CONTINUE;\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase AT_ChangeOwner:\n\t\t\t\talter_table_by_relation(stmt->relation, cmd);\n\t\t\t\talter_table_by_name(&cagg->data.partial_view_schema,\n\t\t\t\t\t\t\t\t\t&cagg->data.partial_view_name,\n\t\t\t\t\t\t\t\t\tcmd);\n\t\t\t\talter_table_by_name(&cagg->data.direct_view_schema,\n\t\t\t\t\t\t\t\t\t&cagg->data.direct_view_name,\n\t\t\t\t\t\t\t\t\tcmd);\n\t\t\t\talter_hypertable_by_id(cagg->data.mat_hypertable_id,\n\t\t\t\t\t\t\t\t\t   stmt,\n\t\t\t\t\t\t\t\t\t   cmd,\n\t\t\t\t\t\t\t\t\t   process_altertable_change_owner);\n\t\t\t\tbreak;\n\n\t\t\tcase AT_SetTableSpace:\n\t\t\t\talter_hypertable_by_id(cagg->data.mat_hypertable_id,\n\t\t\t\t\t\t\t\t\t   stmt,\n\t\t\t\t\t\t\t\t\t   cmd,\n\t\t\t\t\t\t\t\t\t   process_altertable_set_tablespace_end);\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t errmsg(\"cannot alter only SET options of a continuous \"\n\t\t\t\t\t\t\t\t\"aggregate\")));\n\t\t}\n\t}\n\treturn ddl_res;\n}\n\nstatic DDLResult\nprocess_altertable_start_view(ProcessUtilityArgs *args)\n{\n\tAlterTableStmt *stmt = (AlterTableStmt *) args->parsetree;\n\tOid relid = AlterTableLookupRelation(stmt, NoLock);\n\tContinuousAgg *cagg;\n\tContinuousAggViewType vtyp;\n\tconst char *view_name;\n\tconst char *view_schema;\n\n\t/* Check if this is a materialized view and give error if it is. */\n\tcagg = ts_continuous_agg_find_by_relid(relid);\n\n\tif (cagg)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot alter continuous aggregate using ALTER VIEW\"),\n\t\t\t\t errhint(\"Use ALTER MATERIALIZED VIEW to alter a continuous aggregate.\")));\n\n\t/* Check if this is an internal view of a continuous aggregate and give\n\t * error if attempts are made to alter them. */\n\tview_name = get_rel_name(relid);\n\tview_schema = get_namespace_name(get_rel_namespace(relid));\n\tcagg = ts_continuous_agg_find_by_view_name(view_schema, view_name, ContinuousAggAnyView);\n\n\tif (cagg == NULL)\n\t\treturn DDL_CONTINUE;\n\n\tvtyp = ts_continuous_agg_view_type(&cagg->data, view_schema, view_name);\n\n\tif (vtyp == ContinuousAggPartialView || vtyp == ContinuousAggDirectView)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot alter the internal view of a continuous aggregate\")));\n\n\treturn DDL_DONE;\n}\n\nstatic DDLResult\nprocess_altertable_start(ProcessUtilityArgs *args)\n{\n\tAlterTableStmt *stmt = (AlterTableStmt *) args->parsetree;\n\tswitch (stmt->objtype)\n\t{\n\t\tcase OBJECT_TABLE:\n\t\t\treturn process_altertable_start_table(args);\n\t\tcase OBJECT_MATVIEW:\n\t\t\treturn process_altertable_start_matview(args);\n\t\tcase OBJECT_VIEW:\n\t\t\treturn process_altertable_start_view(args);\n\t\tdefault:\n\t\t\treturn DDL_CONTINUE;\n\t}\n}\n\nstatic void\nprocess_altertable_end_subcmd(Hypertable *ht, Node *parsetree, ObjectAddress *obj)\n{\n\tAlterTableCmd *cmd = (AlterTableCmd *) parsetree;\n\n\tAssert(IsA(parsetree, AlterTableCmd));\n\n\tswitch (cmd->subtype)\n\t{\n\t\tcase AT_ChangeOwner:\n\t\t\tprocess_altertable_change_owner(ht, cmd);\n\t\t\tbreak;\n\t\tcase AT_AddIndexConstraint:\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"hypertables do not support adding a constraint \"\n\t\t\t\t\t\t\t\"using an existing index\")));\n\t\t\tbreak;\n\t\tcase AT_AddIndex:\n\t\t{\n\t\t\tIndexStmt *stmt = (IndexStmt *) cmd->def;\n\t\t\tconst char *idxname = stmt->idxname;\n\n\t\t\tAssert(IsA(cmd->def, IndexStmt));\n\n\t\t\tAssert(stmt->isconstraint);\n\n\t\t\tif (idxname == NULL)\n\t\t\t\tidxname = get_rel_name(obj->objectId);\n\n\t\t\tprocess_altertable_add_constraint(ht, cmd, idxname);\n\t\t}\n\t\tbreak;\n\t\tcase AT_AddConstraint:\n#if PG16_LT\n\t\tcase AT_AddConstraintRecurse:\n#endif\n\t\t{\n\t\t\tConstraint *stmt = (Constraint *) cmd->def;\n\t\t\tconst char *conname = stmt->conname;\n\n\t\t\tAssert(IsA(cmd->def, Constraint));\n\n\t\t\tif (conname == NULL)\n\t\t\t\tconname = get_rel_name(obj->objectId);\n\n\t\t\t/*\n\t\t\t * Implicit constraints (e.g., those created by PRIMARY KEY or UNIQUE\n\t\t\t * constraints) have already been processed when the index was created.\n\t\t\t * These will have no objectId in the ObjectAddress passed to this\n\t\t\t * function and no conname.\n\t\t\t */\n\t\t\tif (conname)\n\t\t\t\tprocess_altertable_add_constraint(ht, cmd, conname);\n\t\t}\n\t\tbreak;\n\t\tcase AT_AlterColumnType:\n\t\t\tAssert(IsA(cmd->def, ColumnDef));\n\t\t\tprocess_alter_column_type_end(ht, cmd);\n\t\t\tbreak;\n\t\tcase AT_EnableTrig:\n\t\tcase AT_EnableAlwaysTrig:\n\t\tcase AT_EnableReplicaTrig:\n\t\tcase AT_DisableTrig:\n\t\tcase AT_EnableTrigAll:\n\t\tcase AT_DisableTrigAll:\n\t\tcase AT_EnableTrigUser:\n\t\tcase AT_DisableTrigUser:\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"hypertables do not support  \"\n\t\t\t\t\t\t\t\"enabling or disabling triggers.\")));\n\t\t\t/* Break here to silence compiler */\n\t\t\tbreak;\n\t\tcase AT_ClusterOn:\n\t\t\tprocess_altertable_clusteron_end(ht, cmd);\n\t\t\tbreak;\n\t\tcase AT_ReplicaIdentity:\n\t\t\tprocess_altertable_replica_identity(ht, cmd);\n\t\t\tbreak;\n\t\tcase AT_EnableRule:\n\t\tcase AT_EnableAlwaysRule:\n\t\tcase AT_EnableReplicaRule:\n\t\tcase AT_DisableRule:\n\t\t\t/* should never actually get here but just in case */\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"hypertables do not support rules\")));\n\t\t\t/* Break here to silence compiler */\n\t\t\tbreak;\n\t\tcase AT_AlterConstraint:\n\t\t\tprocess_altertable_alter_constraint_end(ht, cmd);\n\t\t\tbreak;\n\t\tcase AT_ValidateConstraint:\n#if PG16_LT\n\t\tcase AT_ValidateConstraintRecurse:\n#endif\n\t\t\tprocess_altertable_validate_constraint_end(ht, cmd);\n\t\t\tbreak;\n\t\tcase AT_SetLogged:\n\t\tcase AT_SetUnLogged:\n\t\t\tforeach_chunk(ht, process_altertable_chunk, cmd);\n\t\t\tforeach_compressed_chunk(ht, process_altertable_chunk, cmd);\n\t\t\tbreak;\n\t\tcase AT_DropCluster:\n\t\tcase AT_SetNotNull:\n\t\tcase AT_DropNotNull:\n\t\tcase AT_SetRelOptions:\n\t\tcase AT_ResetRelOptions:\n\t\tcase AT_ReplaceRelOptions:\n\t\tcase AT_DropOids:\n\t\tcase AT_SetOptions:\n\t\tcase AT_ResetOptions:\n\t\tcase AT_ReAddStatistics:\n\t\tcase AT_SetCompression:\n\t\t\tforeach_chunk(ht, process_altertable_chunk, cmd);\n\t\t\tbreak;\n\t\tcase AT_SetTableSpace:\n\t\t\tprocess_altertable_set_tablespace_end(ht, cmd);\n\t\t\tbreak;\n\t\tcase AT_AddInherit:\n\t\tcase AT_DropInherit:\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"hypertables do not support inheritance\")));\n\t\tcase AT_SetStatistics:\n\t\tcase AT_SetStorage:\n\t\tcase AT_ColumnDefault:\n\t\tcase AT_CookedColumnDefault:\n\t\tcase AT_AddOf:\n\t\tcase AT_DropOf:\n\t\tcase AT_AddIdentity:\n\t\tcase AT_SetIdentity:\n\t\tcase AT_DropIdentity:\n\t\t\t/* all of the above are handled by default recursion */\n\t\t\tbreak;\n\t\tcase AT_EnableRowSecurity:\n\t\tcase AT_DisableRowSecurity:\n\t\tcase AT_ForceRowSecurity:\n\t\tcase AT_NoForceRowSecurity:\n\t\t\t/* RLS commands should not recurse to chunks */\n\t\t\tbreak;\n\t\tcase AT_ReAddConstraint:\n\t\tcase AT_ReAddIndex:\n\n\t\t\t/*\n\t\t\t * all of the above are internal commands that are hit in tests\n\t\t\t * and correctly handled\n\t\t\t */\n\t\t\tbreak;\n\t\tcase AT_AddColumn:\n#if PG16_LT\n\t\tcase AT_AddColumnRecurse:\n#endif\n\t\t\t/* this is handled for compressed hypertables by tsl code */\n\t\t\tbreak;\n\t\tcase AT_DropColumn:\n#if PG16_LT\n\t\tcase AT_DropColumnRecurse:\n#endif\n\t\tcase AT_DropExpression:\n\n\t\t\t/*\n\t\t\t * adding and dropping columns handled in\n\t\t\t * process_altertable_start_table\n\t\t\t */\n\t\t\tbreak;\n\t\tcase AT_DropConstraint:\n#if PG16_LT\n\t\tcase AT_DropConstraintRecurse:\n#endif\n\t\t\t/* drop constraints handled by process_ddl_sql_drop */\n\t\t\tbreak;\n\t\tcase AT_ReAddComment:\t\t\t   /* internal command never hit in our test\n\t\t\t\t\t\t\t\t\t\t\t* code, so don't know how to handle */\n\t\tcase AT_AddColumnToView:\t\t   /* only used with views */\n\t\tcase AT_AlterColumnGenericOptions: /* only used with foreign tables */\n\t\tcase AT_GenericOptions:\t\t\t   /* only used with foreign tables */\n\t\tcase AT_ReAddDomainConstraint:\t   /* We should handle this in future,\n\t\t\t\t\t\t\t\t\t\t\t* new subset of constraints in PG11\n\t\t\t\t\t\t\t\t\t\t\t* currently not hit in test code */\n\t\tcase AT_AttachPartition:\t\t   /* handled in\n\t\t\t\t\t\t\t\t\t\t\t* process_altertable_start_table but also\n\t\t\t\t\t\t\t\t\t\t\t* here as failsafe */\n\t\tcase AT_DetachPartition:\n\t\tcase AT_DetachPartitionFinalize:\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"operation not supported on hypertables %d\", cmd->subtype)));\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\tif (ts_cm_functions->process_altertable_cmd)\n\t\tts_cm_functions->process_altertable_cmd(ht, cmd);\n}\n\nstatic void\nprocess_altertable_end_subcmds(Hypertable *ht, List *cmds)\n{\n\tListCell *lc;\n\n\tforeach (lc, cmds)\n\t{\n\t\tCollectedATSubcmd *cmd = lfirst(lc);\n\n\t\tprocess_altertable_end_subcmd(ht, cmd->parsetree, &cmd->address);\n\t}\n}\n\nstatic void\nprocess_altertable_end_table(Node *parsetree, CollectedCommand *cmd)\n{\n\tAlterTableStmt *stmt = (AlterTableStmt *) parsetree;\n\tOid relid;\n\tCache *hcache;\n\tHypertable *ht;\n\n\tAssert(IsA(stmt, AlterTableStmt));\n\n\trelid = RangeVarGetRelid(stmt->relation, NoLock, true);\n\n\tif (!OidIsValid(relid))\n\t\treturn;\n\n\tht = ts_hypertable_cache_get_cache_and_entry(relid, CACHE_FLAG_MISSING_OK, &hcache);\n\n\tif (ht)\n\t{\n\t\tswitch (cmd->type)\n\t\t{\n\t\t\tcase SCT_Simple:\n\t\t\t\tprocess_altertable_end_subcmd(ht,\n\t\t\t\t\t\t\t\t\t\t\t  linitial(stmt->cmds),\n\t\t\t\t\t\t\t\t\t\t\t  &cmd->d.simple.secondaryObject);\n\t\t\t\tbreak;\n\t\t\tcase SCT_AlterTable:\n\t\t\t\tprocess_altertable_end_subcmds(ht, cmd->d.alterTable.subcmds);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/*\n\t * Check any ALTER TABLE command is adding a FOREIGN KEY constraint\n\t * referencing a hypertable.\n\t */\n\tif (cmd->type == SCT_AlterTable)\n\t{\n\t\tAlterTableStmt *stmt = castNode(AlterTableStmt, parsetree);\n\t\tListCell *lc;\n\n\t\tforeach (lc, stmt->cmds)\n\t\t{\n\t\t\tAlterTableCmd *subcmd = (AlterTableCmd *) lfirst(lc);\n\n\t\t\tif (subcmd->subtype != AT_AddConstraint ||\n\t\t\t\tcastNode(Constraint, subcmd->def)->contype != CONSTR_FOREIGN)\n\t\t\t\tcontinue;\n\n\t\t\tConstraint *c = castNode(Constraint, subcmd->def);\n\t\t\tOid confrelid = RangeVarGetRelid(c->pktable, AccessShareLock, true);\n\t\t\tHypertable *pk =\n\t\t\t\tts_hypertable_cache_get_entry(hcache, confrelid, CACHE_FLAG_MISSING_OK);\n\t\t\tif (pk)\n\t\t\t{\n\t\t\t\tif (ht && !is_partitioning_allowed(ht->main_table_relid))\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t\t errmsg(\"hypertables cannot be used as foreign key references of \"\n\t\t\t\t\t\t\t\t\t\"hypertables\")));\n\t\t\t\tts_fk_propagate(relid, pk);\n\t\t\t}\n\t\t}\n\t}\n\n\tts_cache_release(&hcache);\n}\n\nstatic void\nprocess_altertable_end(Node *parsetree, CollectedCommand *cmd)\n{\n\tAlterTableStmt *stmt = (AlterTableStmt *) parsetree;\n\n\tswitch (stmt->objtype)\n\t{\n\t\tcase OBJECT_TABLE:\n\t\t\tprocess_altertable_end_table(parsetree, cmd);\n\t\t\tbreak;\n\t\tcase OBJECT_INDEX:\n\t\t\tprocess_altertable_end_index(parsetree, cmd);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n}\n\nstatic DDLResult\nprocess_create_trigger_start(ProcessUtilityArgs *args)\n{\n\tCreateTrigStmt *stmt = (CreateTrigStmt *) args->parsetree;\n\tCache *hcache;\n\tHypertable *ht;\n\tObjectAddress PG_USED_FOR_ASSERTS_ONLY address;\n\tOid relid = RangeVarGetRelid(stmt->relation, NoLock, true);\n\tint16 tgtype;\n\n\tTRIGGER_CLEAR_TYPE(tgtype);\n\tif (stmt->row)\n\t\tTRIGGER_SETT_ROW(tgtype);\n\ttgtype |= stmt->timing;\n\ttgtype |= stmt->events;\n\n\thcache = ts_hypertable_cache_pin();\n\tht = ts_hypertable_cache_get_entry(hcache, relid, CACHE_FLAG_MISSING_OK);\n\tif (ht == NULL)\n\t{\n\t\tts_cache_release(&hcache);\n\t\t/* check if it's a cagg. We don't support triggers on them yet */\n\t\tif (ts_continuous_agg_find_by_relid(relid) != NULL)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"triggers are not supported on continuous aggregate\")));\n\n\t\tif (stmt->transitionRels)\n\t\t\tif (ts_chunk_get_by_relid(relid, false) != NULL)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t errmsg(\"triggers with transition tables are not supported on \"\n\t\t\t\t\t\t\t\t\"hypertable chunks\")));\n\t\treturn DDL_CONTINUE;\n\t}\n\n\t/*\n\t * We do not support ROW triggers with transition tables on hypertables\n\t * since these are not supported on inheritance children, and we use\n\t * inheritance for our chunks (it is actually not supported for\n\t * declarative partition tables either).\n\t */\n\tif (stmt->transitionRels && TRIGGER_FOR_ROW(tgtype))\n\t{\n\t\tts_cache_release(&hcache);\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"ROW triggers with transition tables are not supported on hypertables\")));\n\t}\n\n\t/*\n\t * We currently don't support delete triggers with transition tables on\n\t * compressed tables because deleting a complete segment will not build\n\t * a transition table for the delete.\n\t */\n\tif (stmt->transitionRels && TRIGGER_FOR_DELETE(tgtype) &&\n\t\tTS_HYPERTABLE_HAS_COMPRESSION_ENABLED(ht))\n\t{\n\t\tts_cache_release(&hcache);\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"DELETE triggers with transition tables not supported\")));\n\t}\n\n\t/*\n\t * If it is not a ROW trigger, we do not need to create the ROW triggers\n\t * on the chunks, so we can return early.\n\t */\n\tif (!stmt->row)\n\t{\n\t\tts_cache_release(&hcache);\n\t\treturn DDL_CONTINUE;\n\t}\n\n\taddress = ts_hypertable_create_trigger(ht, stmt, args->query_string);\n\tAssert(OidIsValid(address.objectId));\n\n\tts_cache_release(&hcache);\n\treturn DDL_DONE;\n}\n\nstatic DDLResult\nprocess_create_rule_start(ProcessUtilityArgs *args)\n{\n\tRuleStmt *stmt = (RuleStmt *) args->parsetree;\n\n\tif (!OidIsValid(ts_hypertable_relid(stmt->relation)))\n\t\treturn DDL_CONTINUE;\n\n\tereport(ERROR,\n\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg(\"hypertables do not support rules\")));\n\n\treturn DDL_CONTINUE;\n}\n\n/*\n * Update the owner of a background job given by a heap tuple.\n *\n * Note that there is no check for correct privileges here and this is the\n * responsibility of the caller.\n */\nstatic void\nts_bgw_job_update_owner(Relation rel, HeapTuple tuple, TupleDesc tupledesc, Oid newrole_oid)\n{\n\tbool isnull[Natts_bgw_job];\n\tDatum values[Natts_bgw_job];\n\tbool replace[Natts_bgw_job] = { false };\n\tHeapTuple new_tuple;\n\n\theap_deform_tuple(tuple, tupledesc, values, isnull);\n\n\tif (DatumGetObjectId(values[AttrNumberGetAttrOffset(Anum_bgw_job_owner)]) != newrole_oid)\n\t{\n\t\tvalues[AttrNumberGetAttrOffset(Anum_bgw_job_owner)] = Int32GetDatum(newrole_oid);\n\t\treplace[AttrNumberGetAttrOffset(Anum_bgw_job_owner)] = true;\n\t\tnew_tuple = heap_modify_tuple(tuple, tupledesc, values, isnull, replace);\n\t\tts_catalog_update(rel, new_tuple);\n\t\theap_freetuple(new_tuple);\n\t}\n}\n\nstatic DDLResult\nprocess_reassign_owned_start(ProcessUtilityArgs *args)\n{\n\tReassignOwnedStmt *stmt = (ReassignOwnedStmt *) args->parsetree;\n\tList *role_ids = roleSpecsToIds(stmt->roles);\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(BGW_JOB, RowExclusiveLock, CurrentMemoryContext);\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tbool should_free, isnull;\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tDatum value = slot_getattr(ti->slot, Anum_bgw_job_owner, &isnull);\n\t\tif (!isnull && list_member_oid(role_ids, DatumGetObjectId(value)))\n\t\t{\n\t\t\tOid newrole_oid = get_rolespec_oid(stmt->newrole, false);\n\t\t\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\n\t\t\t/* We do not need to check privileges here since ReassignOwnedObjects() will check\n\t\t\t * the privileges and error out if they are not correct. */\n\t\t\tts_bgw_job_update_owner(ti->scanrel, tuple, ts_scanner_get_tupledesc(ti), newrole_oid);\n\n\t\t\tif (should_free)\n\t\t\t\theap_freetuple(tuple);\n\t\t}\n\t}\n\treturn DDL_CONTINUE;\n}\n\nstatic void\ncheck_no_timescale_options(AlterTableCmd *cmd, Oid reloid)\n{\n\tList *pg_options = NIL, *compress_options = NIL;\n\tEnsure(IsA(cmd->def, List), \"wrong node type used as ALTER TABLE command definition\");\n\tList *inpdef = (List *) cmd->def;\n\tts_with_clause_filter(inpdef, &compress_options, NULL, &pg_options);\n\n\tif (compress_options != NIL)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"timescaledb table options can only be specified for hypertables\"),\n\t\t\t\t errdetail(\"%s is not a hypertable\", get_rel_name(reloid))));\n\t}\n}\n\n/* ALTER TABLE <name> SET ( timescaledb.compress, ...) */\nstatic DDLResult\nprocess_altertable_set_options(AlterTableCmd *cmd, Hypertable *ht)\n{\n\tList *pg_options = NIL, *tsdb_options = NIL;\n\tWithClauseResult *parse_results = NULL;\n\n\t/* split postgres and timescaledb options */\n\tts_with_clause_filter(castNode(List, cmd->def), &tsdb_options, NULL, &pg_options);\n\n\tif (!tsdb_options)\n\t\treturn DDL_CONTINUE;\n\n\tparse_results = ts_alter_table_with_clause_parse(tsdb_options);\n\n\tif (ht && !parse_results[AlterTableFlagChunkTimeInterval].is_default)\n\t{\n\t\tDimension *dim = ts_hyperspace_get_mutable_dimension(ht->space, DIMENSION_TYPE_OPEN, 0);\n\t\tEnsure(dim, \"hypertable without open dimension\");\n\t\tOid time_type = get_atttype(dim->main_table_relid, dim->column_attno);\n\t\tOid interval_type = InvalidOid;\n\t\tDatum interval =\n\t\t\tts_create_table_parse_chunk_time_interval(parse_results\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  [AlterTableFlagChunkTimeInterval],\n\t\t\t\t\t\t\t\t\t\t\t\t\t  time_type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  &interval_type);\n\n\t\tint64 chunk_interval = ts_interval_value_to_internal(interval, interval_type);\n\t\tts_dimension_set_chunk_interval(dim, chunk_interval);\n\t}\n\n\tif (!parse_results[AlterTableFlagColumnstore].is_default ||\n\t\t!parse_results[AlterTableFlagOrderBy].is_default ||\n\t\t!parse_results[AlterTableFlagSegmentBy].is_default ||\n\t\t!parse_results[AlterTableFlagCompressChunkTimeInterval].is_default ||\n\t\t!parse_results[AlterTableFlagIndex].is_default)\n\t\tts_cm_functions->process_compress_table(ht, parse_results);\n\n\tcmd->def = (Node *) pg_options;\n\n\treturn cmd->def ? DDL_CONTINUE : DDL_DONE;\n}\n\nstatic DDLResult\nprocess_altertable_reset_options(AlterTableCmd *cmd, Hypertable *ht)\n{\n\tList *pg_options = NIL, *tsdb_options = NIL;\n\tWithClauseResult *parse_results = NULL;\n\n\t/* split postgres and timescaledb options */\n\tts_with_clause_filter(castNode(List, cmd->def), &tsdb_options, NULL, &pg_options);\n\n\tif (!tsdb_options)\n\t\treturn DDL_CONTINUE;\n\n\tparse_results = ts_alter_table_reset_with_clause_parse(tsdb_options);\n\tif (parse_results[AlterTableFlagOrderBy].is_default &&\n\t\tparse_results[AlterTableFlagSegmentBy].is_default &&\n\t\tparse_results[AlterTableFlagIndex].is_default)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"only columnstore options segmentby and orderby can be reset\")));\n\t}\n\n\tCompressionSettings *settings = ts_compression_settings_get(ht->main_table_relid);\n\tif (!settings)\n\t{\n\t\treturn DDL_CONTINUE;\n\t}\n\n\tif (!parse_results[AlterTableFlagSegmentBy].is_default)\n\t{\n\t\tsettings->fd.segmentby = NULL;\n\t}\n\n\tif (!parse_results[AlterTableFlagOrderBy].is_default)\n\t{\n\t\tsettings->fd.index = ts_remove_orderby_sparse_index(settings);\n\t\tsettings->fd.orderby = NULL;\n\t\tsettings->fd.orderby_desc = NULL;\n\t\tsettings->fd.orderby_nullsfirst = NULL;\n\t}\n\n\tif (!parse_results[AlterTableFlagIndex].is_default)\n\t{\n\t\tsettings->fd.index = NULL;\n\t\tts_add_orderby_sparse_index(settings);\n\t}\n\n\tts_compression_settings_update(settings);\n\n\treturn DDL_CONTINUE;\n}\n\nstatic DDLResult\nprocess_viewstmt(ProcessUtilityArgs *args)\n{\n\tViewStmt *stmt = castNode(ViewStmt, args->parsetree);\n\tList *pg_options = NIL;\n\tList *cagg_options = NIL;\n\n\t/* Check if user is passing continuous aggregate parameters and print a\n\t * useful error message if that is the case. */\n\tts_with_clause_filter(stmt->options, &cagg_options, NULL, &pg_options);\n\tif (cagg_options)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot create continuous aggregate with CREATE VIEW\"),\n\t\t\t\t errhint(\"Use CREATE MATERIALIZED VIEW to create a continuous aggregate.\")));\n\treturn DDL_CONTINUE;\n}\n\nstatic DDLResult\nprocess_create_table_as(ProcessUtilityArgs *args)\n{\n\tCreateTableAsStmt *stmt = castNode(CreateTableAsStmt, args->parsetree);\n\tWithClauseResult *parse_results = NULL;\n\tbool is_cagg = false;\n\tList *pg_options = NIL, *cagg_options = NIL, *other_namespace_options = NIL;\n\n\tif (stmt->objtype == OBJECT_MATVIEW)\n\t{\n\t\t/* Check for creation of continuous aggregate */\n\t\tts_with_clause_filter(stmt->into->options,\n\t\t\t\t\t\t\t  &cagg_options,\n\t\t\t\t\t\t\t  &other_namespace_options,\n\t\t\t\t\t\t\t  &pg_options);\n\n\t\tif (cagg_options)\n\t\t{\n\t\t\tparse_results = ts_create_materialized_view_with_clause_parse(cagg_options);\n\t\t\tis_cagg = DatumGetBool(parse_results[CreateMaterializedViewFlagContinuous].parsed);\n\t\t}\n\n\t\tif (!is_cagg)\n\t\t\treturn DDL_CONTINUE;\n\n\t\tif (pg_options != NIL)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"unsupported combination of storage parameters\"),\n\t\t\t\t\t errdetail(\"A continuous aggregate does not support standard storage \"\n\t\t\t\t\t\t\t   \"parameters.\"),\n\t\t\t\t\t errhint(\"Use only parameters with the \\\"timescaledb.\\\" prefix when \"\n\t\t\t\t\t\t\t \"creating a continuous aggregate.\")));\n\t\tif (other_namespace_options)\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_COLUMN),\n\t\t\t\t\t errmsg(\"non \\\"timescaledb\\\" namespace options can be set only via ALTER\")));\n\t\t}\n\t\tif (!stmt->into->skipData)\n\t\t\tPreventInTransactionBlock(args->context == PROCESS_UTILITY_TOPLEVEL,\n\t\t\t\t\t\t\t\t\t  \"CREATE MATERIALIZED VIEW ... WITH DATA\");\n\n\t\treturn ts_cm_functions->process_cagg_viewstmt(args->parsetree,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  args->query_string,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  args->pstmt,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  parse_results);\n\t}\n\n\treturn DDL_CONTINUE;\n}\n\n/*\n * Get the default partition column from the column definitions. The default\n * partition column is the first column of type TIMESTAMP/TZ.\n */\nstatic char *\nget_default_partition_column_by_definitions(List *definitions)\n{\n\tchar *column_name = NULL;\n\tListCell *lc, *lc2;\n\n\tforeach (lc, definitions)\n\t{\n\t\tNode *node = lfirst(lc);\n\t\tif (!IsA(node, ColumnDef))\n\t\t\tcontinue;\n\n\t\tColumnDef *coldef = castNode(ColumnDef, node);\n\n\t\tif (coldef->typeName->names == NIL)\n\t\t\tcontinue;\n\n\t\tforeach (lc2, coldef->typeName->names)\n\t\t{\n\t\t\tchar *typename = strVal(lfirst(lc2));\n\t\t\tif (strstr(typename, \"timestamp\") || strstr(typename, \"timestamptz\"))\n\t\t\t{\n\t\t\t\tcolumn_name = pstrdup(coldef->colname);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\treturn column_name;\n}\n\nstatic DDLResult\nprocess_create_stmt(ProcessUtilityArgs *args)\n{\n\tCreateStmt *stmt = castNode(CreateStmt, args->parsetree);\n\n\tList *pg_options = NIL, *hypertable_options = NIL;\n\tts_with_clause_filter(stmt->options, &hypertable_options, NULL, &pg_options);\n\tstmt->options = pg_options;\n\n\tcreate_table_info.hypertable = false;\n\tcreate_table_info.with_clauses = NULL;\n\t/*\n\t * We can only convert the table into a hypertable after postgres has created\n\t * the initial table so we store the information passed in the WITH clause\n\t * and do some initial sanity check and do the actual work of creating the hypertable\n\t * in process_create_table_end.\n\t */\n\tif (hypertable_options)\n\t{\n\t\tcreate_table_info.with_clauses = ts_create_table_with_clause_parse(hypertable_options);\n\t\tcreate_table_info.hypertable =\n\t\t\tDatumGetBool(create_table_info.with_clauses[CreateTableFlagHypertable].parsed);\n\n\t\tif (!create_table_info.hypertable)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_COLUMN),\n\t\t\t\t\t errmsg(\"timescaledb options requires hypertable option\"),\n\t\t\t\t\t errhint(\"Use \\\"timescaledb.hypertable\\\" to enable creating a hypertable.\")));\n\n\t\tif (ts_guc_enable_partitioned_hypertables)\n\t\t{\n\t\t\tif (stmt->partspec == NULL)\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * For partitioned hypertables, we need to decide the default partition column here\n\t\t\t\t * instead of process_create_table_end() as opposed to regular hypertable case. This\n\t\t\t\t * is because in partitioned hypertable case, we need to set the PartitionSpec in\n\t\t\t\t * CreateStmt.\n\t\t\t\t */\n\t\t\t\tchar *time_column = NULL;\n\t\t\t\tif (create_table_info.with_clauses[CreateTableFlagTimeColumn].is_default)\n\t\t\t\t{\n\t\t\t\t\ttime_column = get_default_partition_column_by_definitions(stmt->tableElts);\n\t\t\t\t\tif (time_column)\n\t\t\t\t\t\tereport(NOTICE,\n\t\t\t\t\t\t\t\t(errmsg(\"using column \\\"%s\\\" as partitioning column\", time_column),\n\t\t\t\t\t\t\t\t errhint(\n\t\t\t\t\t\t\t\t\t \"Use \\\"timescaledb.partition_column\\\" to specify a different \"\n\t\t\t\t\t\t\t\t\t \"column to use as \"\n\t\t\t\t\t\t\t\t\t \"partitioning column.\")));\n\t\t\t\t\telse\n\t\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_COLUMN),\n\t\t\t\t\t\t\t\t errmsg(\"partition column could not be determined\"),\n\t\t\t\t\t\t\t\t errhint(\"Use \\\"timescaledb.partition_column\\\" to specify the \"\n\t\t\t\t\t\t\t\t\t\t \"column to use as \"\n\t\t\t\t\t\t\t\t\t\t \"partitioning column.\")));\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\ttime_column = TextDatumGetCString(\n\t\t\t\t\t\tcreate_table_info.with_clauses[CreateTableFlagTimeColumn].parsed);\n\n\t\t\t\tPartitionElem *pelem = makeNode(PartitionElem);\n\t\t\t\tpelem->name = time_column;\n\t\t\t\tpelem->location = -1;\n\n\t\t\t\tPartitionSpec *partspec = makeNode(PartitionSpec);\n#if PG16_LT\n\t\t\t\tpartspec->strategy = pstrdup(\"range\");\n#else\n\t\t\t\tpartspec->strategy = PARTITION_STRATEGY_RANGE;\n#endif\n\t\t\t\tpartspec->partParams = list_make1(pelem);\n\t\t\t\tpartspec->location = -1;\n\t\t\t\tstmt->partspec = partspec;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t/* User has specified PARTITION BY clause */\n#if PG16_LT\n\t\t\t\tif (strcmp(stmt->partspec->strategy, \"range\") != 0)\n#else\n\t\t\t\tif (stmt->partspec->strategy != PARTITION_STRATEGY_RANGE)\n#endif\n\t\t\t\t{\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t\t\t errmsg(\"only RANGE partitioning is supported for partitioned \"\n\t\t\t\t\t\t\t\t\t\"hypertables\")));\n\t\t\t\t}\n\n\t\t\t\tif (list_length(stmt->partspec->partParams) != 1)\n\t\t\t\t{\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t\t errmsg(\"only single column partitioning is supported for partitioned \"\n\t\t\t\t\t\t\t\t\t\"hypertables\")));\n\t\t\t\t}\n\n\t\t\t\tif (!create_table_info.with_clauses[CreateTableFlagTimeColumn].is_default)\n\t\t\t\t{\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t\t\t errmsg(\"cannot specify both PARTITION BY and \"\n\t\t\t\t\t\t\t\t\t\"timescaledb.partition_column\")));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn DDL_CONTINUE;\n}\n\nstatic DDLResult\nprocess_refresh_mat_view_start(ProcessUtilityArgs *args)\n{\n\tRefreshMatViewStmt *stmt = castNode(RefreshMatViewStmt, args->parsetree);\n\tOid view_relid = RangeVarGetRelid(stmt->relation, NoLock, true);\n\tconst ContinuousAgg *cagg;\n\n\tif (!OidIsValid(view_relid))\n\t\treturn DDL_CONTINUE;\n\n\tcagg = ts_continuous_agg_find_by_relid(view_relid);\n\n\tif (cagg)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"operation not supported on continuous aggregate\"),\n\t\t\t\t errdetail(\"A continuous aggregate does not support REFRESH MATERIALIZED VIEW.\"),\n\t\t\t\t errhint(\"Use \\\"refresh_continuous_aggregate\\\" or set up a policy to refresh the \"\n\t\t\t\t\t\t \"continuous aggregate.\")));\n\n\treturn DDL_CONTINUE;\n}\n\nstatic DDLResult\npreprocess_execute(ProcessUtilityArgs *args)\n{\n#ifdef USE_TELEMETRY\n\tListCell *lc;\n\tExecuteStmt *stmt = (ExecuteStmt *) args->parsetree;\n\tPreparedStatement *entry = FetchPreparedStatement(stmt->name, false);\n\tif (!entry)\n\t\treturn DDL_CONTINUE;\n\n\tforeach (lc, entry->plansource->query_list)\n\t{\n\t\tQuery *query = lfirst_node(Query, lc);\n\t\tts_telemetry_function_info_gather(query);\n\t}\n#endif\n\treturn DDL_CONTINUE;\n}\n\n/*\n * Handle DDL commands before they have been processed by PostgreSQL.\n */\nstatic DDLResult\nprocess_ddl_command_start(ProcessUtilityArgs *args)\n{\n\tbool check_read_only = true;\n\tts_process_utility_handler_t handler;\n\n\tswitch (nodeTag(args->parsetree))\n\t{\n\t\tcase T_AlterObjectSchemaStmt:\n\t\t\thandler = process_alterobjectschema;\n\t\t\tbreak;\n\t\tcase T_TruncateStmt:\n\t\t\thandler = process_truncate;\n\t\t\tbreak;\n\t\tcase T_AlterTableStmt:\n\t\t\thandler = process_altertable_start;\n\t\t\tbreak;\n\t\tcase T_RenameStmt:\n\t\t\thandler = process_rename;\n\t\t\tbreak;\n\t\tcase T_IndexStmt:\n\t\t\thandler = process_index_start;\n\t\t\tbreak;\n\t\tcase T_CreateTrigStmt:\n\t\t\thandler = process_create_trigger_start;\n\t\t\tbreak;\n\t\tcase T_RuleStmt:\n\t\t\thandler = process_create_rule_start;\n\t\t\tbreak;\n\t\tcase T_ReassignOwnedStmt:\n\t\t\thandler = process_reassign_owned_start;\n\t\t\tbreak;\n\t\tcase T_DropStmt:\n\t\t\t/*\n\t\t\t * Drop associated metadata/chunks but also continue on to drop\n\t\t\t * the main table. Because chunks are deleted before the main\n\t\t\t * table is dropped, the drop respects CASCADE in the expected\n\t\t\t * way.\n\t\t\t */\n\t\t\thandler = process_drop_start;\n\t\t\tbreak;\n\t\tcase T_DropRoleStmt:\n\t\t\thandler = process_drop_role;\n\t\t\tbreak;\n\t\tcase T_DropTableSpaceStmt:\n\t\t\thandler = process_drop_tablespace;\n\t\t\tbreak;\n\t\tcase T_GrantStmt:\n\t\t\thandler = process_grant_and_revoke;\n\t\t\tbreak;\n\t\tcase T_GrantRoleStmt:\n\t\t\thandler = process_grant_and_revoke_role;\n\t\t\tbreak;\n\t\tcase T_CopyStmt:\n\t\t\tcheck_read_only = false;\n\t\t\thandler = process_copy;\n\t\t\tbreak;\n\t\tcase T_VacuumStmt:\n\t\t\thandler = process_vacuum;\n\t\t\tbreak;\n\t\tcase T_ReindexStmt:\n\t\t\thandler = process_reindex;\n\t\t\tbreak;\n\t\tcase T_ClusterStmt:\n\t\t\thandler = process_cluster_start;\n\t\t\tbreak;\n\t\tcase T_ViewStmt:\n\t\t\thandler = process_viewstmt;\n\t\t\tbreak;\n\t\tcase T_RefreshMatViewStmt:\n\t\t\thandler = process_refresh_mat_view_start;\n\t\t\tbreak;\n\t\tcase T_CreateTableAsStmt:\n\t\t\thandler = process_create_table_as;\n\t\t\tbreak;\n\t\tcase T_CreateStmt:\n\t\t\thandler = process_create_stmt;\n\t\t\tbreak;\n\n\t\tcase T_ExecuteStmt:\n\t\t\tcheck_read_only = false;\n\t\t\thandler = preprocess_execute;\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\thandler = NULL;\n\t\t\tbreak;\n\t}\n\n\tif (handler == NULL)\n\t\treturn DDL_CONTINUE;\n\n\tif (check_read_only)\n\t\tPreventCommandIfReadOnly(CreateCommandName(args->parsetree));\n\n\treturn handler(args);\n}\n\n/*\n * Handle DDL commands after they've been processed by PostgreSQL.\n */\nstatic void\nprocess_ddl_command_end(CollectedCommand *cmd)\n{\n\tswitch (nodeTag(cmd->parsetree))\n\t{\n\t\tcase T_CreateStmt:\n\t\t\tprocess_create_table_end(cmd->parsetree);\n\t\t\tbreak;\n\t\tcase T_AlterTableStmt:\n\t\t\tprocess_altertable_end(cmd->parsetree, cmd);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n}\n\nstatic void\nprocess_drop_constraint_on_chunk(Hypertable *ht, Oid chunk_relid, void *arg)\n{\n\tconst char *hypertable_constraint_name = arg;\n\tChunk *chunk = ts_chunk_get_by_relid(chunk_relid, true);\n\n\t/* drop both metadata and table; sql_drop won't be called recursively */\n\tts_chunk_constraint_delete_by_hypertable_constraint_name(chunk->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t hypertable_constraint_name);\n}\n\nstatic void\nprocess_drop_table_constraint(EventTriggerDropObject *obj)\n{\n\tEventTriggerDropTableConstraint *constraint = (EventTriggerDropTableConstraint *) obj;\n\tHypertable *ht;\n\n\tAssert(obj->type == EVENT_TRIGGER_DROP_TABLE_CONSTRAINT);\n\n\t/* do not use relids because underlying table could be gone */\n\tht = ts_hypertable_get_by_name(constraint->schema, constraint->table);\n\n\tif (ht != NULL)\n\t{\n\t\tCatalogSecurityContext sec_ctx;\n\n\t\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\n\t\t/* Recurse to each chunk and drop the corresponding constraint */\n\t\tforeach_chunk(ht, process_drop_constraint_on_chunk, (void *) constraint->constraint_name);\n\n\t\tts_catalog_restore_user(&sec_ctx);\n\t}\n\telse\n\t{\n\t\t/* Cannot get the full chunk here because it's table might be dropped */\n\t\tint32 chunk_id;\n\t\tbool found = ts_chunk_get_id(constraint->schema, constraint->table, &chunk_id, true);\n\n\t\tif (found)\n\t\t\tts_chunk_constraint_delete_by_constraint_name(chunk_id, constraint->constraint_name);\n\t}\n}\n\nstatic void\nprocess_drop_table(EventTriggerDropObject *obj)\n{\n\tEventTriggerDropRelation *table = (EventTriggerDropRelation *) obj;\n\n\tAssert(obj->type == EVENT_TRIGGER_DROP_TABLE || obj->type == EVENT_TRIGGER_DROP_FOREIGN_TABLE);\n\tts_chunk_delete_by_relid_and_relname(table->relid, table->schema, table->name, DROP_RESTRICT);\n\tts_hypertable_delete_by_name(table->schema, table->name);\n\t/*\n\t * Normally, dependent catalogs (like compression settings) are cleaned up\n\t * when deleting the hypertable or chunk. However, in some cases, e.g.,\n\t * when a hypertable delete cascades to chunks, the chunk relids cannot be\n\t * resolved from the schema and name because the chunk relations are\n\t * already dropped by PostgreSQL when the \"drop eventtrigger\" is\n\t * called. Therefore, also try to delete dependent catalog entries here\n\t * since the eventtrigger gives us the relid of dropped objects.\n\t */\n\tts_compression_settings_delete_any(table->relid);\n\tts_chunk_rewrite_delete(table->relid, false);\n}\n\nstatic void\nprocess_sql_drop_schema(EventTriggerDropObject *obj)\n{\n\tEventTriggerDropSchema *schema = (EventTriggerDropSchema *) obj;\n\tint count;\n\n\tAssert(obj->type == EVENT_TRIGGER_DROP_SCHEMA);\n\n\tif (strcmp(schema->schema, INTERNAL_SCHEMA_NAME) == 0)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot drop the internal schema for extension \\\"%s\\\"\", EXTENSION_NAME),\n\t\t\t\t errhint(\"Use DROP EXTENSION to remove the extension and the schema.\")));\n\n\t/*\n\t * Check for any remaining hypertables that use the schema as its\n\t * associated schema. For matches, we reset their associated schema to the\n\t * INTERNAL schema\n\t */\n\tcount = ts_hypertable_reset_associated_schema_name(schema->schema);\n\n\tif (count > 0)\n\t\tereport(NOTICE,\n\t\t\t\t(errmsg(\"the chunk storage schema changed to \\\"%s\\\" for %d hypertable%c\",\n\t\t\t\t\t\tINTERNAL_SCHEMA_NAME,\n\t\t\t\t\t\tcount,\n\t\t\t\t\t\t(count > 1) ? 's' : '\\0')));\n}\n\nstatic void\nprocess_drop_trigger(EventTriggerDropObject *obj)\n{\n\tEventTriggerDropTrigger *trigger_event = (EventTriggerDropTrigger *) obj;\n\tHypertable *ht;\n\n\tAssert(obj->type == EVENT_TRIGGER_DROP_TRIGGER);\n\n\t/* do not use relids because underlying table could be gone */\n\tht = ts_hypertable_get_by_name(trigger_event->schema, trigger_event->table);\n\n\tif (ht != NULL)\n\t{\n\t\t/* Recurse to each chunk and drop the corresponding trigger */\n\t\tts_hypertable_drop_trigger(ht->main_table_relid, trigger_event->trigger_name);\n\t}\n}\n\nstatic void\nprocess_drop_view(EventTriggerDropView *dropped_view)\n{\n\tts_continuous_agg_drop(dropped_view->schema, dropped_view->view_name);\n}\n\nstatic void\nprocess_ddl_sql_drop(EventTriggerDropObject *obj)\n{\n\tswitch (obj->type)\n\t{\n\t\tcase EVENT_TRIGGER_DROP_TABLE_CONSTRAINT:\n\t\t\tprocess_drop_table_constraint(obj);\n\t\t\tbreak;\n\t\tcase EVENT_TRIGGER_DROP_TABLE:\n\t\t\tprocess_drop_table(obj);\n\t\t\tbreak;\n\t\tcase EVENT_TRIGGER_DROP_SCHEMA:\n\t\t\tprocess_sql_drop_schema(obj);\n\t\t\tbreak;\n\t\tcase EVENT_TRIGGER_DROP_TRIGGER:\n\t\t\tprocess_drop_trigger(obj);\n\t\t\tbreak;\n\t\tcase EVENT_TRIGGER_DROP_VIEW:\n\t\t\tprocess_drop_view((EventTriggerDropView *) obj);\n\t\t\tbreak;\n\t\tcase EVENT_TRIGGER_DROP_FOREIGN_TABLE:\n\t\tcase EVENT_TRIGGER_DROP_FOREIGN_SERVER:\n\t\tcase EVENT_TRIGGER_DROP_INDEX:\n\t\t\tbreak;\n\t}\n}\n\n/*\n * ProcessUtility hook for DDL commands that have not yet been processed by\n * PostgreSQL.\n */\nstatic void\ntimescaledb_ddl_command_start(PlannedStmt *pstmt, const char *query_string, bool readonly_tree,\n\t\t\t\t\t\t\t  ProcessUtilityContext context, ParamListInfo params,\n\t\t\t\t\t\t\t  QueryEnvironment *queryEnv, DestReceiver *dest,\n\t\t\t\t\t\t\t  QueryCompletion *completion_tag)\n{\n\tlast_process_utility_context = context;\n\n\tProcessUtilityArgs args = { .query_string = query_string,\n\t\t\t\t\t\t\t\t.context = context,\n\t\t\t\t\t\t\t\t.params = params,\n\t\t\t\t\t\t\t\t.readonly_tree = readonly_tree,\n\t\t\t\t\t\t\t\t.dest = dest,\n\t\t\t\t\t\t\t\t.completion_tag = completion_tag,\n\t\t\t\t\t\t\t\t.pstmt = pstmt,\n\t\t\t\t\t\t\t\t.parsetree = pstmt->utilityStmt,\n\t\t\t\t\t\t\t\t.queryEnv = queryEnv,\n\t\t\t\t\t\t\t\t.parse_state = make_parsestate(NULL) };\n\n\tbool altering_timescaledb = false;\n\tDDLResult result;\n\n\targs.parse_state->p_sourcetext = query_string;\n\n\tif (IsA(args.parsetree, AlterExtensionStmt))\n\t{\n\t\tAlterExtensionStmt *stmt = (AlterExtensionStmt *) args.parsetree;\n\n\t\taltering_timescaledb = (strcmp(stmt->extname, EXTENSION_NAME) == 0);\n\t}\n\n\t/*\n\t * We don't want to load the extension if we just got the command to alter\n\t * it.\n\t */\n\tif (altering_timescaledb || !ts_extension_is_loaded_and_not_upgrading())\n\t{\n\t\tprev_ProcessUtility(&args);\n\t\treturn;\n\t}\n\n\t/*\n\t * Since we might alter the parsetree and strip timescaledb options\n\t * before passing it to Postgres, we need to make a copy of the original\n\t * statement in case it is cached.\n\t */\n\targs.pstmt = copyObject(pstmt);\n\targs.parsetree = args.pstmt->utilityStmt;\n\n\tresult = process_ddl_command_start(&args);\n\n\tif (result == DDL_CONTINUE)\n\t\tprev_ProcessUtility(&args);\n}\n\nstatic void\nprocess_ddl_event_command_end(EventTriggerData *trigdata)\n{\n\tListCell *lc;\n\n\t/* Inhibit collecting new commands while in the trigger */\n\tEventTriggerInhibitCommandCollection();\n\n\tswitch (nodeTag(trigdata->parsetree))\n\t{\n\t\tcase T_AlterTableStmt:\n\t\tcase T_CreateTrigStmt:\n\t\tcase T_CreateStmt:\n\t\tcase T_IndexStmt:\n\t\t\tforeach (lc, ts_event_trigger_ddl_commands())\n\t\t\t\tprocess_ddl_command_end(lfirst(lc));\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\n\tEventTriggerUndoInhibitCommandCollection();\n}\n\nstatic void\nprocess_ddl_event_sql_drop(EventTriggerData *trigdata)\n{\n\tListCell *lc;\n\tList *dropped_objects = ts_event_trigger_dropped_objects();\n\n\tforeach (lc, dropped_objects)\n\t\tprocess_ddl_sql_drop(lfirst(lc));\n}\n\nTS_FUNCTION_INFO_V1(ts_timescaledb_process_ddl_event);\n\n/*\n * Event trigger hook for DDL commands that have already been handled by\n * PostgreSQL (i.e., \"ddl_command_end\" and \"sql_drop\" events).\n */\nDatum\nts_timescaledb_process_ddl_event(PG_FUNCTION_ARGS)\n{\n\tEventTriggerData *trigdata = (EventTriggerData *) fcinfo->context;\n\n\tif (!CALLED_AS_EVENT_TRIGGER(fcinfo))\n\t\telog(ERROR, \"not fired by event trigger manager\");\n\n\tif (!ts_extension_is_loaded_and_not_upgrading())\n\t\tPG_RETURN_NULL();\n\n\tif (strcmp(\"ddl_command_end\", trigdata->event) == 0)\n\t\tprocess_ddl_event_command_end(trigdata);\n\telse if (strcmp(\"sql_drop\", trigdata->event) == 0)\n\t\tprocess_ddl_event_sql_drop(trigdata);\n\n\tPG_RETURN_NULL();\n}\n\nextern void\nts_process_utility_set_expect_chunk_modification(bool expect)\n{\n\texpect_chunk_modification = expect;\n}\n\nbool\nts_process_utility_is_top_level(void)\n{\n\treturn last_process_utility_context == PROCESS_UTILITY_TOPLEVEL;\n}\n\nbool\nts_process_utility_is_context_nonatomic(void)\n{\n\tProcessUtilityContext context = last_process_utility_context;\n\treturn context == PROCESS_UTILITY_TOPLEVEL || context == PROCESS_UTILITY_QUERY_NONATOMIC;\n}\n\nvoid\nts_process_utility_context_reset(void)\n{\n\tlast_process_utility_context = PROCESS_UTILITY_TOPLEVEL;\n}\n\nstatic void\nprocess_utility_xact_abort(XactEvent event, void *arg)\n{\n\tswitch (event)\n\t{\n\t\tcase XACT_EVENT_ABORT:\n\t\tcase XACT_EVENT_PARALLEL_ABORT:\n\n\t\t\t/*\n\t\t\t * Reset the expect_chunk_modification flag because it this is an\n\t\t\t * internal safety flag that is set to true only temporarily\n\t\t\t * during chunk operations. It should never remain true across\n\t\t\t * transactions.\n\t\t\t */\n\t\t\texpect_chunk_modification = false;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n}\n\nstatic void\nprocess_utility_subxact_abort(SubXactEvent event, SubTransactionId mySubid,\n\t\t\t\t\t\t\t  SubTransactionId parentSubid, void *arg)\n{\n\tswitch (event)\n\t{\n\t\tcase SUBXACT_EVENT_ABORT_SUB:\n\t\t\t/* see note in process_utility_xact_abort */\n\t\t\texpect_chunk_modification = false;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n}\n\nvoid\n_process_utility_init(void)\n{\n\tprev_ProcessUtility_hook = ProcessUtility_hook;\n\tProcessUtility_hook = timescaledb_ddl_command_start;\n\tRegisterXactCallback(process_utility_xact_abort, NULL);\n\tRegisterSubXactCallback(process_utility_subxact_abort, NULL);\n}\n\nvoid\n_process_utility_fini(void)\n{\n\tProcessUtility_hook = prev_ProcessUtility_hook;\n\tUnregisterXactCallback(process_utility_xact_abort, NULL);\n\tUnregisterSubXactCallback(process_utility_subxact_abort, NULL);\n}\n"
  },
  {
    "path": "src/process_utility.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include <nodes/plannodes.h>\n#include <parser/parse_node.h>\n#include <tcop/utility.h>\n\n#include \"cache.h\"\n\ntypedef struct ProcessUtilityArgs\n{\n\tCache *hcache;\n\tPlannedStmt *pstmt;\n\tQueryEnvironment *queryEnv;\n\tParseState *parse_state;\n\tNode *parsetree;\n\tconst char *query_string;\n\tProcessUtilityContext context;\n\tParamListInfo params;\n\tDestReceiver *dest;\n\tQueryCompletion *completion_tag;\n\tbool readonly_tree;\n} ProcessUtilityArgs;\n\ntypedef enum\n{\n\tDDL_CONTINUE,\n\tDDL_DONE\n} DDLResult;\n\ntypedef DDLResult (*ts_process_utility_handler_t)(ProcessUtilityArgs *args);\n\nextern void ts_process_utility_set_expect_chunk_modification(bool expect);\n\n/*\n * Procedures that use multiple transactions cannot be run in a transaction\n * block (from a function, from dynamic SQL) or in a subtransaction (from a\n * procedure block with an EXCEPTION clause). Such procedures use\n * PreventInTransactionBlock function to check whether they can be run.\n *\n * Though currently such checks are incomplete, because\n * PreventInTransactionBlock requires isTopLevel argument to throw a\n * consistent error when the call originates from a function. This\n * isTopLevel flag (that is a bit poorly named - see below) is not readily\n * available inside C procedures. The source of truth for it -\n * ProcessUtilityContext parameter is passed to ProcessUtility hooks, but\n * is not included with the function calls. There is an undocumented\n * SPI_inside_nonatomic_context function, that would have been sufficient\n * for isTopLevel flag, but it currently returns false when SPI connection\n * is absent (that is a valid scenario when C procedures are called from\n * top-lelev SQL instead of PLPG procedures or DO blocks) so it cannot be\n * used.\n *\n * To work around this the value of ProcessUtilityContext parameter is\n * saved when TS ProcessUtility hook is entered and can be accessed from\n * C procedures using new ts_process_utility_is_context_nonatomic function.\n * The result is called \"non-atomic\" instead of \"top-level\" because the way\n * how isTopLevel flag is determined from the ProcessUtilityContext value\n * in standard_ProcessUtility is insufficient for C procedures - it\n * excludes PROCESS_UTILITY_QUERY_NONATOMIC value (used when called from\n * PLPG procedure without an EXCEPTION clause) that is a valid use case for\n * C procedures with transactions. See details in the description of\n * ExecuteCallStmt function.\n *\n * It is expected that calls to C procedures are done with CALL and always\n * pass though the ProcessUtility hook. The ProcessUtilityContext\n * parameter is set to PROCESS_UTILITY_TOPLEVEL value by default. In\n * unlikely case when a C procedure is called without passing through\n * ProcessUtility hook and the call is done in atomic context, then\n * PreventInTransactionBlock checks will pass, but SPI_commit will fail\n * when checking that all current active snapshots are portal-owned\n * snapshots (the same behaviour that was observed before this change).\n * In atomic context there will be an additional snapshot set in\n * _SPI_execute_plan, see the snapshot handling invariants description\n * in that function.\n */\nextern TSDLLEXPORT bool ts_process_utility_is_context_nonatomic(void);\n\n/*\n * Check if we are at top level.\n */\nextern TSDLLEXPORT bool ts_process_utility_is_top_level(void);\n\n/*\n * Currently in TS ProcessUtility hook the saved ProcessUtilityContext\n * value is reset back to PROCESS_UTILITY_TOPLEVEL on normal exit but\n * is NOT reset in case of ereport exit. C procedures can call this\n * function to reset the saved value before doing the checks that can\n * result in ereport exit.\n */\nextern TSDLLEXPORT void ts_process_utility_context_reset(void);\n"
  },
  {
    "path": "src/scan_iterator.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n\n#include \"scan_iterator.h\"\n\nTSDLLEXPORT void\nts_scan_iterator_set_index(ScanIterator *iterator, CatalogTable table, int indexid)\n{\n\titerator->ctx.index = catalog_get_index(ts_catalog_get(), table, indexid);\n}\n\nvoid\nts_scan_iterator_end(ScanIterator *iterator)\n{\n\tts_scanner_end_scan(&iterator->ctx);\n}\n\nvoid\nts_scan_iterator_close(ScanIterator *iterator)\n{\n\t/* Ending a scan is a no-op if already ended */\n\tts_scanner_end_scan(&iterator->ctx);\n\tts_scanner_close(&iterator->ctx);\n}\n\nTSDLLEXPORT void\nts_scan_iterator_scan_key_init(ScanIterator *iterator, AttrNumber attributeNumber,\n\t\t\t\t\t\t\t   StrategyNumber strategy, RegProcedure procedure, Datum argument)\n{\n\tMemoryContext oldmcxt;\n\n\tAssert(iterator->ctx.scankey == NULL || iterator->ctx.scankey == iterator->scankey);\n\titerator->ctx.scankey = iterator->scankey;\n\n\tif (iterator->ctx.nkeys >= EMBEDDED_SCAN_KEY_SIZE)\n\t\telog(ERROR, \"cannot scan more than %d keys\", EMBEDDED_SCAN_KEY_SIZE);\n\n\t/*\n\t * For rescans, when the scan key is reinitialized during the scan, make\n\t * sure the scan key is initialized on the long-lived scankey memory\n\t * context.\n\t */\n\toldmcxt = MemoryContextSwitchTo(iterator->ctx.internal.scan_mcxt);\n\tScanKeyInit(&iterator->scankey[iterator->ctx.nkeys++],\n\t\t\t\tattributeNumber,\n\t\t\t\tstrategy,\n\t\t\t\tprocedure,\n\t\t\t\targument);\n\tMemoryContextSwitchTo(oldmcxt);\n}\n\nTSDLLEXPORT void\nts_scan_iterator_rescan(ScanIterator *iterator)\n{\n\tts_scanner_rescan(&iterator->ctx, NULL);\n}\n"
  },
  {
    "path": "src/scan_iterator.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <utils/palloc.h>\n\n#include \"scanner.h\"\n#include \"ts_catalog/catalog.h\"\n\n#define EMBEDDED_SCAN_KEY_SIZE 5\n\ntypedef struct ScanIterator\n{\n\tScannerCtx ctx;\n\tTupleInfo *tinfo;\n\tScanKeyData scankey[EMBEDDED_SCAN_KEY_SIZE];\n} ScanIterator;\n\n#define ts_scan_iterator_create(catalog_table_id, lock_mode, mctx)                                   \\\n\t(ScanIterator)                                                                                   \\\n\t{                                                                                                \\\n\t\t.ctx = {                                                                                   \\\n\t\t\t.internal = {\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t.ended = true,\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t.scan_mcxt = CurrentMemoryContext,\t\t\t\t\t\t\\\n\t\t\t},\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t.table = catalog_get_table_id(ts_catalog_get(), catalog_table_id),                     \\\n\t\t\t.nkeys = 0,                                                                            \\\n\t\t\t.scandirection = ForwardScanDirection,                                                 \\\n\t\t\t.lockmode = lock_mode,                                                                 \\\n\t\t\t.result_mctx = mctx,                                                                   \\\n\t\t\t.flags = SCANNER_F_NOFLAGS,\t\t\t\t\t\t\t\t\t\\\n\t\t}, \\\n\t}\n\n#define ts_scan_iterator_create_with_catalog_snapshot(catalog_table_id, lock_mode, mctx)             \\\n\t(ScanIterator)                                                                                   \\\n\t{                                                                                                \\\n\t\t.ctx = {                                                                                   \\\n\t\t\t.internal = {\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t.ended = true,\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t.scan_mcxt = CurrentMemoryContext,\t\t\t\t\t\t\\\n\t\t\t},\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t.table = catalog_get_table_id(ts_catalog_get(), catalog_table_id),                     \\\n\t\t\t.nkeys = 0,                                                                            \\\n\t\t\t.scandirection = ForwardScanDirection,                                                 \\\n\t\t\t.lockmode = lock_mode,                                                                 \\\n\t\t\t.result_mctx = mctx,                                                                   \\\n\t\t\t.flags = SCANNER_F_NOFLAGS,\t\t\t\t\t\t\t\t\t\\\n\t\t\t.use_catalog_snapshot = true,\t\t\t\t\t\t\\\n\t\t}, \\\n\t}\n\nstatic inline TupleInfo *\nts_scan_iterator_tuple_info(const ScanIterator *iterator)\n{\n\treturn iterator->tinfo;\n}\n\nstatic inline TupleTableSlot *\nts_scan_iterator_slot(const ScanIterator *iterator)\n{\n\treturn iterator->tinfo->slot;\n}\n\nstatic inline HeapTuple\nts_scan_iterator_fetch_heap_tuple(const ScanIterator *iterator, bool materialize, bool *should_free)\n{\n\treturn ts_scanner_fetch_heap_tuple(iterator->tinfo, materialize, should_free);\n}\n\nstatic inline TupleDesc\nts_scan_iterator_tupledesc(const ScanIterator *iterator)\n{\n\treturn ts_scanner_get_tupledesc(iterator->tinfo);\n}\n\nstatic inline MemoryContext\nts_scan_iterator_get_result_memory_context(const ScanIterator *iterator)\n{\n\treturn iterator->ctx.result_mctx;\n}\n\nstatic inline void *\nts_scan_iterator_alloc_result(const ScanIterator *iterator, Size size)\n{\n\treturn MemoryContextAllocZero(iterator->ctx.result_mctx, size);\n}\n\nstatic inline void\nts_scan_iterator_start_scan(ScanIterator *iterator)\n{\n\tts_scanner_start_scan(&(iterator)->ctx);\n}\n\nstatic inline TupleInfo *\nts_scan_iterator_next(ScanIterator *iterator)\n{\n\titerator->tinfo = ts_scanner_next(&(iterator)->ctx);\n\treturn iterator->tinfo;\n}\n\nstatic inline void\nts_scan_iterator_scan_key_reset(ScanIterator *iterator)\n{\n\titerator->ctx.nkeys = 0;\n}\n\nstatic inline bool\nts_scan_iterator_is_started(ScanIterator *iterator)\n{\n\treturn iterator->ctx.internal.started;\n}\n\nvoid TSDLLEXPORT ts_scan_iterator_set_index(ScanIterator *iterator, CatalogTable table,\n\t\t\t\t\t\t\t\t\t\t\tint indexid);\nvoid TSDLLEXPORT ts_scan_iterator_end(ScanIterator *iterator);\nvoid TSDLLEXPORT ts_scan_iterator_close(ScanIterator *iterator);\nvoid TSDLLEXPORT ts_scan_iterator_scan_key_init(ScanIterator *iterator, AttrNumber attributeNumber,\n\t\t\t\t\t\t\t\t\t\t\t\tStrategyNumber strategy, RegProcedure procedure,\n\t\t\t\t\t\t\t\t\t\t\t\tDatum argument);\n\n/*\n * Reset the scan to use a new scan key.\n *\n * Note that the scan key should typically be reinitialized before a rescan.\n */\nvoid TSDLLEXPORT ts_scan_iterator_rescan(ScanIterator *iterator);\n\nstatic inline void\nts_scan_iterator_start_or_restart_scan(ScanIterator *iterator)\n{\n\tif (ts_scan_iterator_is_started(iterator))\n\t\tts_scan_iterator_rescan(iterator);\n\telse\n\t\tts_scan_iterator_start_scan(iterator);\n}\n\n/* You must use `ts_scan_iterator_close` if terminating this loop early */\n#define ts_scanner_foreach(scan_iterator)                                                          \\\n\tfor (ts_scan_iterator_start_scan((scan_iterator));                                             \\\n\t\t ts_scan_iterator_next(scan_iterator) != NULL;)\n"
  },
  {
    "path": "src/scanner.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/htup_details.h>\n#include <access/relscan.h>\n#include <access/xact.h>\n#include <executor/tuptable.h>\n#include <storage/bufmgr.h>\n#include <storage/lmgr.h>\n#include <storage/procarray.h>\n#include <utils/palloc.h>\n#include <utils/rel.h>\n#include <utils/snapmgr.h>\n\n#include \"scanner.h\"\n\nenum ScannerType\n{\n\tScannerTypeTable,\n\tScannerTypeIndex,\n};\n\n/*\n * Scanner can implement both index and heap scans in a single interface.\n */\ntypedef struct Scanner\n{\n\tRelation (*openscan)(ScannerCtx *ctx);\n\tScanDesc (*beginscan)(ScannerCtx *ctx);\n\tbool (*getnext)(ScannerCtx *ctx);\n\tvoid (*rescan)(ScannerCtx *ctx);\n\tvoid (*endscan)(ScannerCtx *ctx);\n\tvoid (*closescan)(ScannerCtx *ctx);\n} Scanner;\n\n/* Functions implementing heap scans */\nstatic Relation\ntable_scanner_open(ScannerCtx *ctx)\n{\n\tctx->tablerel = table_open(ctx->table, ctx->lockmode);\n\treturn ctx->tablerel;\n}\n\nstatic ScanDesc\ntable_scanner_beginscan(ScannerCtx *ctx)\n{\n\tctx->internal.scan.table_scan =\n\t\ttable_beginscan(ctx->tablerel, ctx->snapshot, ctx->nkeys, ctx->scankey);\n\n\treturn ctx->internal.scan;\n}\n\nstatic bool\ntable_scanner_getnext(ScannerCtx *ctx)\n{\n\tbool success = table_scan_getnextslot(ctx->internal.scan.table_scan,\n\t\t\t\t\t\t\t\t\t\t  ForwardScanDirection,\n\t\t\t\t\t\t\t\t\t\t  ctx->internal.tinfo.slot);\n\n\treturn success;\n}\n\nstatic void\ntable_scanner_rescan(ScannerCtx *ctx)\n{\n\ttable_rescan(ctx->internal.scan.table_scan, ctx->scankey);\n}\n\nstatic void\ntable_scanner_endscan(ScannerCtx *ctx)\n{\n\ttable_endscan(ctx->internal.scan.table_scan);\n}\n\nstatic void\ntable_scanner_close(ScannerCtx *ctx)\n{\n\tLOCKMODE lockmode = (ctx->flags & SCANNER_F_KEEPLOCK) ? NoLock : ctx->lockmode;\n\n\ttable_close(ctx->tablerel, lockmode);\n}\n\n/* Functions implementing index scans */\nstatic Relation\nindex_scanner_open(ScannerCtx *ctx)\n{\n\tctx->tablerel = table_open(ctx->table, ctx->lockmode);\n\tctx->indexrel = index_open(ctx->index, ctx->lockmode);\n\treturn ctx->indexrel;\n}\n\nstatic ScanDesc\nindex_scanner_beginscan(ScannerCtx *ctx)\n{\n\tInternalScannerCtx *ictx = &ctx->internal;\n\n\tictx->scan.index_scan = index_beginscan_compat(ctx->tablerel,\n\t\t\t\t\t\t\t\t\t\t\t\t   ctx->indexrel,\n\t\t\t\t\t\t\t\t\t\t\t\t   ctx->snapshot,\n\t\t\t\t\t\t\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t   ctx->nkeys,\n\t\t\t\t\t\t\t\t\t\t\t\t   ctx->norderbys);\n\tictx->scan.index_scan->xs_want_itup = ctx->want_itup;\n\tindex_rescan(ictx->scan.index_scan, ctx->scankey, ctx->nkeys, NULL, ctx->norderbys);\n\treturn ictx->scan;\n}\n\nstatic bool\nindex_scanner_getnext(ScannerCtx *ctx)\n{\n\tInternalScannerCtx *ictx = &ctx->internal;\n\tbool success;\n\n\tsuccess = index_getnext_slot(ictx->scan.index_scan, ctx->scandirection, ictx->tinfo.slot);\n\tictx->tinfo.ituple = ictx->scan.index_scan->xs_itup;\n\tictx->tinfo.ituple_desc = ictx->scan.index_scan->xs_itupdesc;\n\n\treturn success;\n}\n\nstatic void\nindex_scanner_rescan(ScannerCtx *ctx)\n{\n\tindex_rescan(ctx->internal.scan.index_scan, ctx->scankey, ctx->nkeys, NULL, ctx->norderbys);\n}\n\nstatic void\nindex_scanner_endscan(ScannerCtx *ctx)\n{\n\tindex_endscan(ctx->internal.scan.index_scan);\n}\n\nstatic void\nindex_scanner_close(ScannerCtx *ctx)\n{\n\tLOCKMODE lockmode = (ctx->flags & SCANNER_F_KEEPLOCK) ? NoLock : ctx->lockmode;\n\tindex_close(ctx->indexrel, ctx->lockmode);\n\ttable_close(ctx->tablerel, lockmode);\n}\n\n/*\n * Two scanners by type: heap and index scanners.\n */\nstatic Scanner scanners[] = {\n\t[ScannerTypeTable] = {\n\t\t.openscan = table_scanner_open,\n\t\t.beginscan = table_scanner_beginscan,\n\t\t.getnext = table_scanner_getnext,\n\t\t.rescan = table_scanner_rescan,\n\t\t.endscan = table_scanner_endscan,\n\t\t.closescan = table_scanner_close,\n\t},\n\t[ScannerTypeIndex] = {\n\t\t.openscan = index_scanner_open,\n\t\t.beginscan = index_scanner_beginscan,\n\t\t.getnext = index_scanner_getnext,\n\t\t.rescan = index_scanner_rescan,\n\t\t.endscan = index_scanner_endscan,\n\t\t.closescan = index_scanner_close,\n\t}\n};\n\nstatic inline Scanner *\nscanner_ctx_get_scanner(ScannerCtx *ctx)\n{\n\tif (OidIsValid(ctx->index))\n\t\treturn &scanners[ScannerTypeIndex];\n\telse\n\t\treturn &scanners[ScannerTypeTable];\n}\n\nTSDLLEXPORT void\nts_scanner_rescan(ScannerCtx *ctx, const ScanKey scankey)\n{\n\tScanner *scanner = scanner_ctx_get_scanner(ctx);\n\tMemoryContext oldmcxt;\n\n\t/* If scankey is NULL, the existing scan key was already updated or the\n\t * old should be reused */\n\tif (NULL != scankey)\n\t\tmemcpy(ctx->scankey, scankey, sizeof(*ctx->scankey));\n\n\toldmcxt = MemoryContextSwitchTo(ctx->internal.scan_mcxt);\n\tscanner->rescan(ctx);\n\tMemoryContextSwitchTo(oldmcxt);\n}\n\nstatic void\nprepare_scan(ScannerCtx *ctx)\n{\n\tctx->internal.ended = false;\n\tctx->internal.registered_snapshot = false;\n\n\tif (ctx->internal.scan_mcxt == NULL)\n\t\tctx->internal.scan_mcxt = CurrentMemoryContext;\n\n\tif (ctx->snapshot == NULL)\n\t{\n\t\t/*\n\t\t * We use SnapshotSelf by default, for historical reasons mostly, but\n\t\t * we probably want to move to an MVCC snapshot as the default. The\n\t\t * difference is that a Self snapshot is an \"instant\" snapshot and can\n\t\t * see its own changes. More importantly, however, unlike an MVCC\n\t\t * snapshot, a Self snapshot is not subject to the strictness of\n\t\t * SERIALIZABLE isolation mode.\n\t\t *\n\t\t * This is important in case of, e.g., concurrent chunk creation by\n\t\t * two transactions; we'd like a transaction to use a new chunk as\n\t\t * soon as the creating transaction commits, so that there aren't\n\t\t * multiple transactions creating the same chunk and all but one fails\n\t\t * with a conflict. However, under SERIALIZABLE mode a transaction is\n\t\t * only allowed to read data from transactions that were committed\n\t\t * prior to transaction start. This means that two or more\n\t\t * transactions that create the same chunk must have all but the first\n\t\t * committed transaction fail.\n\t\t *\n\t\t * Therefore, we probably want to exempt internal bookkeeping metadata\n\t\t * from full SERIALIZABLE semantics (at least in the case of chunk\n\t\t * creation), or otherwise the INSERT behavior will be different for\n\t\t * hypertables compared to regular tables under SERIALIZABLE\n\t\t * mode.\n\t\t */\n\t\tMemoryContext oldmcxt = MemoryContextSwitchTo(ctx->internal.scan_mcxt);\n\n\t\tif (ctx->use_catalog_snapshot)\n\t\t{\n\t\t\tctx->snapshot = RegisterSnapshot(GetCatalogSnapshot(ctx->table));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tctx->snapshot = RegisterSnapshot(GetSnapshotData(SnapshotSelf));\n\t\t\t/*\n\t\t\t * Invalidate the PG catalog snapshot to ensure it is refreshed and\n\t\t\t * up-to-date with the snapshot used to scan TimescaleDB\n\t\t\t * metadata. Since TimescaleDB metadata is often joined with PG\n\t\t\t * catalog data (e.g., calling get_relname_relid() to fill in chunk\n\t\t\t * table Oid), this avoids any potential problems where the different\n\t\t\t * snapshots used to scan TimescaleDB metadata and PG catalog metadata\n\t\t\t * aren't in sync.\n\t\t\t *\n\t\t\t * Ideally, a catalog snapshot would be used to scan TimescaleDB\n\t\t\t * metadata, but that will change the behavior of chunk creation in\n\t\t\t * SERIALIZED mode, as described above.\n\t\t\t */\n\t\t\tInvalidateCatalogSnapshot();\n\t\t}\n\t\tctx->internal.registered_snapshot = true;\n\t\tMemoryContextSwitchTo(oldmcxt);\n\t}\n}\n\nTSDLLEXPORT Relation\nts_scanner_open(ScannerCtx *ctx)\n{\n\tScanner *scanner = scanner_ctx_get_scanner(ctx);\n\tMemoryContext oldmcxt;\n\tRelation rel;\n\n\tAssert(NULL == ctx->tablerel);\n\n\tprepare_scan(ctx);\n\tAssert(ctx->internal.scan_mcxt != NULL);\n\toldmcxt = MemoryContextSwitchTo(ctx->internal.scan_mcxt);\n\trel = scanner->openscan(ctx);\n\tMemoryContextSwitchTo(oldmcxt);\n\n\treturn rel;\n}\n\n/*\n * Start either a heap or index scan depending on the information in the\n * ScannerCtx. ScannerCtx must be setup by caller with the proper information\n * for the scan, including filters and callbacks for found tuples.\n */\nTSDLLEXPORT void\nts_scanner_start_scan(ScannerCtx *ctx)\n{\n\tInternalScannerCtx *ictx = &ctx->internal;\n\tScanner *scanner;\n\tTupleDesc tuple_desc;\n\tMemoryContext oldmcxt;\n\n\tif (ictx->started)\n\t{\n\t\tAssert(!ictx->ended);\n\t\tAssert(ctx->tablerel);\n\t\tAssert(OidIsValid(ctx->table));\n\t\treturn;\n\t}\n\n\tif (ctx->tablerel == NULL)\n\t{\n\t\tAssert(NULL == ctx->indexrel);\n\t\tts_scanner_open(ctx);\n\t}\n\telse\n\t{\n\t\t/*\n\t\t * Relations already opened by caller: Only need to prepare the scan\n\t\t * and set relation Oids so that the scanner knows which scanner\n\t\t * implementation to use. Respect the auto-closing behavior set by the\n\t\t * user, which is to auto close if unspecified.\n\t\t */\n\t\tprepare_scan(ctx);\n\t\tctx->table = RelationGetRelid(ctx->tablerel);\n\n\t\tif (NULL != ctx->indexrel)\n\t\t\tctx->index = RelationGetRelid(ctx->indexrel);\n\t}\n\n\tAssert(ctx->internal.scan_mcxt != NULL);\n\toldmcxt = MemoryContextSwitchTo(ctx->internal.scan_mcxt);\n\n\tscanner = scanner_ctx_get_scanner(ctx);\n\tscanner->beginscan(ctx);\n\n\ttuple_desc = RelationGetDescr(ctx->tablerel);\n\n\tictx->tinfo.scanrel = ctx->tablerel;\n\tictx->tinfo.mctx = ctx->result_mctx == NULL ? CurrentMemoryContext : ctx->result_mctx;\n\tictx->tinfo.slot = MakeSingleTupleTableSlot(tuple_desc, table_slot_callbacks(ctx->tablerel));\n\tMemoryContextSwitchTo(oldmcxt);\n\n\t/* Call pre-scan handler, if any. */\n\tif (ctx->prescan != NULL)\n\t\tctx->prescan(ctx->data);\n\n\tictx->started = true;\n}\n\nstatic inline bool\nts_scanner_limit_reached(ScannerCtx *ctx)\n{\n\treturn ctx->limit > 0 && ctx->internal.tinfo.count >= ctx->limit;\n}\n\nstatic void\nscanner_cleanup(ScannerCtx *ctx)\n{\n\tInternalScannerCtx *ictx = &ctx->internal;\n\n\tif (ictx->registered_snapshot)\n\t{\n\t\tUnregisterSnapshot(ctx->snapshot);\n\t\tctx->snapshot = NULL;\n\t}\n\n\tif (NULL != ictx->tinfo.slot)\n\t{\n\t\tExecDropSingleTupleTableSlot(ictx->tinfo.slot);\n\t\tictx->tinfo.slot = NULL;\n\t}\n\n\tif (NULL != ictx->scan_mcxt)\n\t{\n\t\tictx->scan_mcxt = NULL;\n\t}\n}\n\nTSDLLEXPORT void\nts_scanner_end_scan(ScannerCtx *ctx)\n{\n\tInternalScannerCtx *ictx = &ctx->internal;\n\tScanner *scanner = scanner_ctx_get_scanner(ctx);\n\tMemoryContext oldmcxt;\n\n\tif (ictx->ended)\n\t\treturn;\n\n\t/* Call post-scan handler, if any. */\n\tif (ctx->postscan != NULL)\n\t\tctx->postscan(ictx->tinfo.count, ctx->data);\n\n\toldmcxt = MemoryContextSwitchTo(ctx->internal.scan_mcxt);\n\tscanner->endscan(ctx);\n\tMemoryContextSwitchTo(oldmcxt);\n\n\tscanner_cleanup(ctx);\n\tictx->ended = true;\n\tictx->started = false;\n}\n\nTSDLLEXPORT void\nts_scanner_close(ScannerCtx *ctx)\n{\n\tScanner *scanner = scanner_ctx_get_scanner(ctx);\n\n\tAssert(ctx->internal.ended);\n\n\tif (NULL != ctx->tablerel)\n\t{\n\t\tscanner->closescan(ctx);\n\t\tctx->tablerel = NULL;\n\t\tctx->indexrel = NULL;\n\t}\n}\n\nTSDLLEXPORT TupleInfo *\nts_scanner_next(ScannerCtx *ctx)\n{\n\tInternalScannerCtx *ictx = &ctx->internal;\n\tScanner *scanner = scanner_ctx_get_scanner(ctx);\n\tbool is_valid = false;\n\n\tif (!ts_scanner_limit_reached(ctx))\n\t{\n\t\tMemoryContext oldmcxt = MemoryContextSwitchTo(ctx->internal.scan_mcxt);\n\t\tis_valid = scanner->getnext(ctx);\n\t\tMemoryContextSwitchTo(oldmcxt);\n\t}\n\n\twhile (is_valid)\n\t{\n\t\tif (ctx->filter == NULL || ctx->filter(&ictx->tinfo, ctx->data) == SCAN_INCLUDE)\n\t\t{\n\t\t\tictx->tinfo.count++;\n\n\t\t\tif (ctx->tuplock)\n\t\t\t{\n\t\t\t\tTupleTableSlot *slot = ictx->tinfo.slot;\n\n\t\t\t\tAssert(ctx->snapshot);\n\t\t\t\tictx->tinfo.lockresult = table_tuple_lock(ctx->tablerel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  &(slot->tts_tid),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  ctx->snapshot,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  slot,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  GetCurrentCommandId(false),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  ctx->tuplock->lockmode,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  ctx->tuplock->waitpolicy,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  ctx->tuplock->lockflags,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  &ictx->tinfo.lockfd);\n\t\t\t}\n\n\t\t\t/* stop at a valid tuple */\n\t\t\treturn &ictx->tinfo;\n\t\t}\n\n\t\tif (ts_scanner_limit_reached(ctx))\n\t\t\tis_valid = false;\n\t\telse\n\t\t{\n\t\t\tMemoryContext oldmcxt = MemoryContextSwitchTo(ctx->internal.scan_mcxt);\n\t\t\tis_valid = scanner->getnext(ctx);\n\t\t\tMemoryContextSwitchTo(oldmcxt);\n\t\t}\n\t}\n\n\tif (!(ctx->flags & SCANNER_F_NOEND))\n\t\tts_scanner_end_scan(ctx);\n\n\tif (!(ctx->flags & SCANNER_F_NOEND_AND_NOCLOSE))\n\t\tts_scanner_close(ctx);\n\n\treturn NULL;\n}\n\n/*\n * Perform either a heap or index scan depending on the information in the\n * ScannerCtx. ScannerCtx must be setup by caller with the proper information\n * for the scan, including filters and callbacks for found tuples.\n *\n * Return the number of tuples that were found.\n */\nTSDLLEXPORT int\nts_scanner_scan(ScannerCtx *ctx)\n{\n\tTupleInfo *tinfo;\n\n\tMemSet(&ctx->internal, 0, sizeof(ctx->internal));\n\n\tfor (ts_scanner_start_scan(ctx); (tinfo = ts_scanner_next(ctx));)\n\t{\n\t\tif (ctx->tuple_found != NULL)\n\t\t{\n\t\t\tScanTupleResult scan_result = ctx->tuple_found(tinfo, ctx->data);\n\n\t\t\t/* Call tuple_found handler. Abort the scan if the handler wants us to */\n\t\t\tif (scan_result == SCAN_DONE)\n\t\t\t{\n\t\t\t\tif (!(ctx->flags & SCANNER_F_NOEND))\n\t\t\t\t\tts_scanner_end_scan(ctx);\n\n\t\t\t\tif (!(ctx->flags & SCANNER_F_NOEND_AND_NOCLOSE))\n\t\t\t\t\tts_scanner_close(ctx);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\telse if (scan_result == SCAN_RESTART_WITH_NEW_SNAPSHOT)\n\t\t\t{\n\t\t\t\tts_scanner_end_scan(ctx);\n\t\t\t\tctx->internal.tinfo.count = 0;\n\t\t\t\tctx->snapshot = RegisterSnapshot(GetLatestSnapshot());\n\t\t\t\tts_scanner_start_scan(ctx);\n\t\t\t\t/* Since we register the snapshot manually above,\n\t\t\t\t * we need to mark it as registered in the scanner but only after we\n\t\t\t\t * start the scan since the scanner resets this flag and sets it only\n\t\t\t\t * if the snapshot gets registered during scan preparation phase.\n\t\t\t\t */\n\t\t\t\tctx->internal.registered_snapshot = true;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn ctx->internal.tinfo.count;\n}\n\nTSDLLEXPORT bool\nts_scanner_scan_one(ScannerCtx *ctx, bool fail_if_not_found, const char *item_type)\n{\n\t/* Since this function ignores the custom limit, we assume that no custom limit is set by the\n\t * caller. */\n\tAssert(ctx->limit == 0);\n\n\t/* We are interested in a maximum of two tuples to determine whether the addressed tuple is\n\t * unique. */\n\tctx->limit = 2;\n\n\tint num_found = ts_scanner_scan(ctx);\n\n\tswitch (num_found)\n\t{\n\t\tcase 0:\n\t\t\tif (fail_if_not_found)\n\t\t\t{\n\t\t\t\telog(ERROR, \"%s not found\", item_type);\n\t\t\t}\n\t\t\treturn false;\n\t\tcase 1:\n\t\t\treturn true;\n\t\tdefault:\n\t\t\telog(ERROR, \"more than one %s found\", item_type);\n\t\t\treturn false;\n\t}\n}\n\nItemPointer\nts_scanner_get_tuple_tid(TupleInfo *ti)\n{\n\treturn &ti->slot->tts_tid;\n}\n\nHeapTuple\nts_scanner_fetch_heap_tuple(const TupleInfo *ti, bool materialize, bool *should_free)\n{\n\treturn ExecFetchSlotHeapTuple(ti->slot, materialize, should_free);\n}\n\nTupleDesc\nts_scanner_get_tupledesc(const TupleInfo *ti)\n{\n\treturn ti->slot->tts_tupleDescriptor;\n}\n"
  },
  {
    "path": "src/scanner.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <access/genam.h>\n#include <access/heapam.h>\n#include <nodes/lockoptions.h>\n#include <utils/fmgroids.h>\n\n#include \"compat/compat.h\"\n#include \"utils.h\"\n\ntypedef struct ScanTupLock\n{\n\tLockTupleMode lockmode;\n\tLockWaitPolicy waitpolicy;\n\tunsigned int lockflags;\n} ScanTupLock;\n\n/* Tuple information passed on to handlers when scanning for tuples. */\ntypedef struct TupleInfo\n{\n\tRelation scanrel;\n\tTupleTableSlot *slot;\n\t/* return index tuple if it was requested -- only for index scans */\n\tIndexTuple ituple;\n\tTupleDesc ituple_desc;\n\n\t/*\n\t * If the user requested a tuple lock, the result of the lock is passed on\n\t * in lockresult.\n\t */\n\tTM_Result lockresult;\n\t/* Failure data in case of failed tuple lock */\n\tTM_FailureData lockfd;\n\tint count;\n\n\t/*\n\t * The memory context (optionally) set initially in the ScannerCtx. This\n\t * can be used to allocate data on in the tuple handle function.\n\t */\n\tMemoryContext mctx;\n} TupleInfo;\n\ntypedef enum ScanTupleResult\n{\n\tSCAN_DONE,\n\tSCAN_CONTINUE,\n\tSCAN_RESTART_WITH_NEW_SNAPSHOT\n} ScanTupleResult;\n\ntypedef enum ScanFilterResult\n{\n\tSCAN_EXCLUDE,\n\tSCAN_INCLUDE\n} ScanFilterResult;\n\ntypedef ScanTupleResult (*tuple_found_func)(TupleInfo *ti, void *data);\ntypedef ScanFilterResult (*tuple_filter_func)(const TupleInfo *ti, void *data);\ntypedef void (*postscan_func)(int num_tuples, void *data);\n\ntypedef union ScanDesc\n{\n\tIndexScanDesc index_scan;\n\tTableScanDesc table_scan;\n} ScanDesc;\n\ntypedef enum ScannerFlags\n{\n\tSCANNER_F_NOFLAGS = 0x00,\n\tSCANNER_F_KEEPLOCK = 0x01,\n\tSCANNER_F_NOEND = 0x02,\n\tSCANNER_F_NOEND_AND_NOCLOSE = 0x04 | SCANNER_F_NOEND,\n} ScannerFlags;\n\n/*\n * InternalScannerCtx is used for internal state during scanning and shouldn't\n * be initialized or touched by the user.\n */\ntypedef struct InternalScannerCtx\n{\n\tTupleInfo tinfo;\n\tScanDesc scan;\n\t/*\n\t * PG scan functions must be called on a memory context that lives\n\t * throughout the entire scan. Use the scan_mcxt to ensure that\n\t * functions aren't called on, e.g., a per-tuple context.\n\t */\n\tMemoryContext scan_mcxt;\n\tbool registered_snapshot;\n\tbool started;\n\tbool ended;\n} InternalScannerCtx;\n\ntypedef struct ScannerCtx\n{\n\tInternalScannerCtx internal;\n\t/* Fields below this line can be initialized by the user */\n\tOid table;\n\tOid index;\n\tRelation tablerel;\n\tRelation indexrel;\n\tScanKey scankey;\n\tint flags;\n\tint nkeys, norderbys, limit; /* Limit on number of tuples to return. 0 or\n\t\t\t\t\t\t\t\t  * less means no limit */\n\tbool want_itup;\n\tLOCKMODE lockmode;\n\tMemoryContext result_mctx; /* The memory context to allocate the result\n\t\t\t\t\t\t\t\t* on */\n\tconst ScanTupLock *tuplock;\n\tScanDirection scandirection;\n\tSnapshot snapshot;\t\t   /* Snapshot requested by the caller. Set automatically\n\t\t\t\t\t\t\t\t* when NULL */\n\tbool use_catalog_snapshot; /* If true, use a catalog snapshot to scan data, unless snapshot is\n\t\t\t\t\t\t\t\t  already set */\n\tvoid *data;\t\t\t\t   /* User-provided data passed on to filter()\n\t\t\t\t\t\t\t\t* and tuple_found() */\n\n\t/*\n\t * Optional handler called before a scan starts, but relation locks are\n\t * acquired.\n\t */\n\tvoid (*prescan)(void *data);\n\n\t/*\n\t * Optional handler called after a scan finishes and before relation locks\n\t * are released. Passes on the number of tuples found.\n\t */\n\tvoid (*postscan)(int num_tuples, void *data);\n\n\t/*\n\t * Optional handler to filter tuples. Should return SCAN_INCLUDE for\n\t * tuples that should be passed on to tuple_found, or SCAN_EXCLUDE\n\t * otherwise.\n\t */\n\tScanFilterResult (*filter)(const TupleInfo *ti, void *data);\n\n\t/*\n\t * Handler for found tuples. Should return SCAN_CONTINUE to continue the\n\t * scan or SCAN_DONE to finish without scanning further tuples.\n\t */\n\tScanTupleResult (*tuple_found)(TupleInfo *ti, void *data);\n} ScannerCtx;\n\n/* Performs an index scan or heap scan and returns the number of matching\n * tuples. */\nextern TSDLLEXPORT Relation ts_scanner_open(ScannerCtx *ctx);\nextern TSDLLEXPORT void ts_scanner_close(ScannerCtx *ctx);\nextern TSDLLEXPORT int ts_scanner_scan(ScannerCtx *ctx);\nextern TSDLLEXPORT bool ts_scanner_scan_one(ScannerCtx *ctx, bool fail_if_not_found,\n\t\t\t\t\t\t\t\t\t\t\tconst char *item_type);\nextern TSDLLEXPORT void ts_scanner_start_scan(ScannerCtx *ctx);\nextern TSDLLEXPORT void ts_scanner_end_scan(ScannerCtx *ctx);\nextern TSDLLEXPORT void ts_scanner_rescan(ScannerCtx *ctx, const ScanKey scankey);\nextern TSDLLEXPORT TupleInfo *ts_scanner_next(ScannerCtx *ctx);\nextern TSDLLEXPORT ItemPointer ts_scanner_get_tuple_tid(TupleInfo *ti);\nextern TSDLLEXPORT HeapTuple ts_scanner_fetch_heap_tuple(const TupleInfo *ti, bool materialize,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t bool *should_free);\nextern TSDLLEXPORT TupleDesc ts_scanner_get_tupledesc(const TupleInfo *ti);\n"
  },
  {
    "path": "src/sort_transform.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <catalog/pg_type.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodeFuncs.h>\n#include <nodes/plannodes.h>\n#include <optimizer/paths.h>\n#include <optimizer/planner.h>\n#include <parser/parsetree.h>\n#include <utils/fmgroids.h>\n#include <utils/guc.h>\n#include <utils/lsyscache.h>\n\n#include \"compat/compat.h\"\n#include \"cross_module_fn.h\"\n#include \"func_cache.h\"\n#include \"hypertable.h\"\n#include \"import/allpaths.h\"\n#include \"sort_transform.h\"\n\n/* This optimizations allows GROUP BY clauses that transform time in\n * order-preserving ways to use indexes on the time field. It works\n * by transforming sorting clauses from their more complex versions\n * to simplified ones that can use the plain index, if the transform\n * is order preserving.\n *\n * For example, an ordering on date_trunc('minute', time) can be transformed\n * to an ordering on time.\n */\n\nstatic Expr *\ntransform_timestamp_cast(FuncExpr *func)\n{\n\t/*\n\t * transform cast from timestamptz to timestamp\n\t *\n\t * timestamp(var) => var\n\t *\n\t * proof: timestamp(time1) >= timestamp(time2) iff time1 > time2\n\t *\n\t */\n\n\tExpr *first;\n\n\tif (list_length(func->args) != 1)\n\t\treturn (Expr *) func;\n\n\tfirst = ts_sort_transform_expr(linitial(func->args));\n\tif (!IsA(first, Var))\n\t\treturn (Expr *) func;\n\n\treturn (Expr *) copyObject(first);\n}\n\nstatic Expr *\ntransform_timestamptz_cast(FuncExpr *func)\n{\n\t/*\n\t * Transform cast from date to timestamptz, or timestamp to timestamptz,\n\t * or abstime to timestamptz Handles only single-argument versions of the\n\t * cast to avoid explicit timezone specifiers\n\t *\n\t *\n\t * timestamptz(var) => var\n\t *\n\t * proof: timestamptz(time1) >= timestamptz(time2) iff time1 > time2\n\t *\n\t */\n\n\tExpr *first;\n\n\tif (list_length(func->args) != 1)\n\t\treturn (Expr *) func;\n\n\tfirst = ts_sort_transform_expr(linitial(func->args));\n\tif (!IsA(first, Var))\n\t\treturn (Expr *) func;\n\n\treturn (Expr *) copyObject(first);\n}\n\nstatic inline Expr *\ntransform_time_op_const_interval(OpExpr *op)\n{\n\t/*\n\t * optimize timestamp(tz) +/- const interval\n\t *\n\t * Sort of ts + 1 minute fulfilled by sort of ts\n\t */\n\tif (list_length(op->args) == 2 && IsA(lsecond(op->args), Const))\n\t{\n\t\tOid left = exprType((Node *) linitial(op->args));\n\t\tOid right = exprType((Node *) lsecond(op->args));\n\n\t\tif ((left == TIMESTAMPOID && right == INTERVALOID) ||\n\t\t\t(left == TIMESTAMPTZOID && right == INTERVALOID) ||\n\t\t\t(left == DATEOID && right == INTERVALOID))\n\t\t{\n\t\t\tInterval *interval = DatumGetIntervalP((lsecond_node(Const, op->args))->constvalue);\n\t\t\tif (interval->month != 0 || interval->day != 0)\n\t\t\t\treturn (Expr *) op;\n\n\t\t\tchar *name = get_opname(op->opno);\n\n\t\t\tif (strncmp(name, \"-\", NAMEDATALEN) == 0 || strncmp(name, \"+\", NAMEDATALEN) == 0)\n\t\t\t{\n\t\t\t\tExpr *first = ts_sort_transform_expr((Expr *) linitial(op->args));\n\n\t\t\t\tif (IsA(first, Var))\n\t\t\t\t\treturn copyObject(first);\n\t\t\t}\n\t\t}\n\t}\n\treturn (Expr *) op;\n}\n\nstatic inline Expr *\ntransform_int_op_const(OpExpr *op)\n{\n\t/*\n\t * Optimize int op const (or const op int), whenever possible. e.g. sort\n\t * of  some_int + const fulfilled by sort of some_int same for the\n\t * following operator: + - / *\n\t *\n\t * Note that / is not commutative and const / var does NOT work (namely it\n\t * reverses sort order, which we don't handle yet)\n\t */\n\tif (list_length(op->args) == 2 &&\n\t\t(IsA(lsecond(op->args), Const) || IsA(linitial(op->args), Const)))\n\t{\n\t\tOid left = exprType((Node *) linitial(op->args));\n\t\tOid right = exprType((Node *) lsecond(op->args));\n\n\t\tif ((left == INT8OID && right == INT8OID) || (left == INT4OID && right == INT4OID) ||\n\t\t\t(left == INT2OID && right == INT2OID))\n\t\t{\n\t\t\tchar *name = get_opname(op->opno);\n\n\t\t\tif (name[1] == '\\0')\n\t\t\t{\n\t\t\t\tswitch (name[0])\n\t\t\t\t{\n\t\t\t\t\tcase '-':\n\t\t\t\t\tcase '+':\n\t\t\t\t\tcase '*':\n\t\t\t\t\t\t/* commutative cases */\n\t\t\t\t\t\tif (IsA(linitial(op->args), Const))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tExpr *nonconst = ts_sort_transform_expr((Expr *) lsecond(op->args));\n\n\t\t\t\t\t\t\tif (IsA(nonconst, Var))\n\t\t\t\t\t\t\t\treturn copyObject(nonconst);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tExpr *nonconst = ts_sort_transform_expr((Expr *) linitial(op->args));\n\n\t\t\t\t\t\t\tif (IsA(nonconst, Var))\n\t\t\t\t\t\t\t\treturn copyObject(nonconst);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase '/':\n\t\t\t\t\t\t/* only if second arg is const */\n\t\t\t\t\t\tif (IsA(lsecond(op->args), Const))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tExpr *nonconst = ts_sort_transform_expr((Expr *) linitial(op->args));\n\n\t\t\t\t\t\t\tif (IsA(nonconst, Var))\n\t\t\t\t\t\t\t\treturn copyObject(nonconst);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\t/*\n\t\t\t\t\t\t * Do nothing for unknown operators. The explicit empty\n\t\t\t\t\t\t * branch is to placate the static analyzers.\n\t\t\t\t\t\t */\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn (Expr *) op;\n}\n\n/* sort_transforms_expr returns a simplified sort expression in a form\n * more common for indexes. Must return same data type & collation too.\n *\n * Sort transforms have the following correctness condition:\n *\tAny ordering provided by the returned expression is a valid\n *\tordering under the original expression. The reverse need not\n *\tbe true to apply the transformation to the last member of pathkeys\n *\tbut it would need to be true to apply the transformation to\n *\tarbitrary members of pathkeys.\n *\n * Namely if orig_expr(X) > orig_expr(Y) then\n *\t\t\t new_expr(X) > new_expr(Y).\n *\n * Note that if orig_expr(X) = orig_expr(Y) then\n *\t\t\t the ordering under new_expr is unconstrained.\n * */\nExpr *\nts_sort_transform_expr(Expr *orig_expr)\n{\n\tif (IsA(orig_expr, FuncExpr))\n\t{\n\t\tFuncExpr *func = (FuncExpr *) orig_expr;\n\t\tFuncInfo *finfo = ts_func_cache_get_bucketing_func(func->funcid);\n\n\t\tif (NULL != finfo)\n\t\t{\n\t\t\tif (NULL == finfo->sort_transform)\n\t\t\t\treturn orig_expr;\n\n\t\t\treturn finfo->sort_transform(func);\n\t\t}\n\n\t\t/* Functions of one argument that convert something to timestamp(tz). */\n\t\tif (func->funcid == F_TIMESTAMP_DATE || func->funcid == F_TIMESTAMP_TIMESTAMPTZ)\n\t\t{\n\t\t\treturn transform_timestamp_cast(func);\n\t\t}\n\n\t\tif (func->funcid == F_TIMESTAMPTZ_DATE || func->funcid == F_TIMESTAMPTZ_TIMESTAMP)\n\t\t{\n\t\t\treturn transform_timestamptz_cast(func);\n\t\t}\n\t}\n\tif (IsA(orig_expr, OpExpr))\n\t{\n\t\tOpExpr *op = (OpExpr *) orig_expr;\n\t\tOid type_first = exprType((Node *) linitial(op->args));\n\n\t\tif (type_first == TIMESTAMPOID || type_first == TIMESTAMPTZOID || type_first == DATEOID)\n\t\t{\n\t\t\treturn transform_time_op_const_interval(op);\n\t\t}\n\t\tif (type_first == INT2OID || type_first == INT4OID || type_first == INT8OID)\n\t\t{\n\t\t\treturn transform_int_op_const(op);\n\t\t}\n\t}\n\treturn orig_expr;\n}\n\n/*\tsort_transform_ec creates a new EquivalenceClass with transformed\n *\texpressions if any of the members of the original EC can be transformed for the sort.\n */\n\nstatic EquivalenceClass *\nsort_transform_ec(PlannerInfo *root, EquivalenceClass *orig, Relids child_relids)\n{\n\tEquivalenceClass *newec = NULL;\n\tbool propagate_to_children = false;\n\n\t/* check all members, adding only transformable members to new ec */\n\tEquivalenceMember *ec_mem;\n#if PG18_GE\n\t/* Use specialized iterator to include child ems.\n\t *\n\t * https://github.com/postgres/postgres/commit/d69d45a5\n\t */\n\tEquivalenceMemberIterator it;\n\n\tsetup_eclass_member_iterator(&it, orig, child_relids);\n\twhile ((ec_mem = eclass_member_iterator_next(&it)) != NULL)\n\t{\n#else\n\tListCell *lc_member;\n\tforeach (lc_member, orig->ec_members)\n\t{\n\t\tec_mem = (EquivalenceMember *) lfirst(lc_member);\n#endif\n\t\tExpr *transformed_expr = ts_sort_transform_expr(ec_mem->em_expr);\n\n\t\tif (transformed_expr != ec_mem->em_expr)\n\t\t{\n\t\t\tEquivalenceMember *em;\n\t\t\tOid type_oid = exprType((Node *) transformed_expr);\n\t\t\tList *opfamilies = list_copy(orig->ec_opfamilies);\n\n#if PG16_LT\n\t\t\t/*\n\t\t\t * if the transform already exists for even one member, assume\n\t\t\t * exists for all\n\t\t\t */\n\t\t\tEquivalenceClass *exist = get_eclass_for_sort_expr(root,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   transformed_expr,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   ec_mem->em_nullable_relids,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   opfamilies,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   type_oid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   orig->ec_collation,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   orig->ec_sortref,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   ec_mem->em_relids,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   false);\n#else\n\t\t\tEquivalenceClass *exist = get_eclass_for_sort_expr(root,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   transformed_expr,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   opfamilies,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   type_oid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   orig->ec_collation,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   orig->ec_sortref,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   ec_mem->em_relids,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   false);\n#endif\n\n\t\t\tif (exist != NULL)\n\t\t\t{\n\t\t\t\treturn exist;\n\t\t\t}\n\n\t\t\tem = makeNode(EquivalenceMember);\n\n\t\t\tem->em_expr = transformed_expr;\n\t\t\tem->em_relids = bms_copy(ec_mem->em_relids);\n#if PG16_LT\n\t\t\tem->em_nullable_relids = bms_copy(ec_mem->em_nullable_relids);\n#else\n\t\t\tem->em_parent = ec_mem->em_parent;\n#endif\n\t\t\tem->em_is_const = ec_mem->em_is_const;\n\t\t\tem->em_is_child = ec_mem->em_is_child;\n\t\t\tem->em_datatype = type_oid;\n\n\t\t\tif (newec == NULL)\n\t\t\t{\n\t\t\t\t/* lazy create the ec. */\n\t\t\t\tnewec = makeNode(EquivalenceClass);\n\t\t\t\tnewec->ec_opfamilies = opfamilies;\n\t\t\t\tnewec->ec_collation = orig->ec_collation;\n\t\t\t\tnewec->ec_members = NIL;\n#if PG18_GE\n\t\t\t\tnewec->ec_childmembers = NULL;\n\t\t\t\tnewec->ec_childmembers_size = 0;\n#endif\n\t\t\t\tnewec->ec_sources = list_copy(orig->ec_sources);\n\t\t\t\tnewec->ec_derives_list = list_copy(orig->ec_derives_list);\n\t\t\t\tnewec->ec_relids = bms_copy(orig->ec_relids);\n\t\t\t\tnewec->ec_has_const = orig->ec_has_const;\n\n\t\t\t\t/* Even if the original EC has volatile (it has time_bucket_gapfill)\n\t\t\t\t * this ordering is purely on the time column, so it is non-volatile\n\t\t\t\t * and should be propagated to the children.\n\t\t\t\t */\n\t\t\t\tnewec->ec_has_volatile = false;\n#if PG16_LT\n\t\t\t\tnewec->ec_below_outer_join = orig->ec_below_outer_join;\n#endif\n\t\t\t\tnewec->ec_broken = orig->ec_broken;\n\t\t\t\tnewec->ec_sortref = orig->ec_sortref;\n\t\t\t\tnewec->ec_merged = orig->ec_merged;\n\n\t\t\t\t/* Volatile ECs only ever have one member, that of the root,\n\t\t\t\t * so if the original EC was volatile, we need to propagate the\n\t\t\t\t * new EC to the children ourselves.\n\t\t\t\t */\n\t\t\t\tpropagate_to_children = orig->ec_has_volatile;\n\t\t\t\t/* Even though time_bucket_gapfill is marked as VOLATILE to\n\t\t\t\t * prevent the planner from removing the call, it's still safe\n\t\t\t\t * to use values from child tables in lieu of the output of the\n\t\t\t\t * root table. Among other things, this allows us to use the\n\t\t\t\t * sort-order from the child tables for the output.\n\t\t\t\t */\n\t\t\t\torig->ec_has_volatile = false;\n\t\t\t}\n#if PG18_LT\n\t\t\tnewec->ec_members = lappend(newec->ec_members, em);\n#else\n\t\t\t/* Update the child member lists when adding child ems.\n\t\t\t *\n\t\t\t * https://github.com/postgres/postgres/commit/d69d45a5\n\t\t\t */\n\t\t\tif (em->em_is_child)\n\t\t\t\tts_add_child_eq_member(root, newec, em, it.current_relid);\n\t\t\telse\n\t\t\t\tnewec->ec_members = lappend(newec->ec_members, em);\n#endif\n\n\t\t\tint i = -1;\n\t\t\twhile ((i = bms_next_member(em->em_relids, i)) >= 0)\n\t\t\t{\n\t\t\t\tRelOptInfo *rel = root->simple_rel_array[i];\n\n\t\t\t\trel->eclass_indexes =\n\t\t\t\t\tbms_add_member(rel->eclass_indexes, list_length(root->eq_classes));\n\t\t\t}\n\t\t}\n\t}\n\n\t/* if any transforms were found return new ec */\n\tif (newec != NULL)\n\t{\n\t\troot->eq_classes = lappend(root->eq_classes, newec);\n\t\tif (propagate_to_children)\n\t\t{\n\t\t\tBitmapset *parents = bms_copy(newec->ec_relids);\n\t\t\tListCell *lc;\n\t\t\tint parent;\n\n\t\t\tbms_get_singleton_member(parents, &parent);\n\n\t\t\tforeach (lc, root->append_rel_list)\n\t\t\t{\n\t\t\t\tAppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);\n\t\t\t\tif (appinfo->parent_relid == (Index) parent)\n\t\t\t\t{\n\t\t\t\t\tRelOptInfo *parent_rel = root->simple_rel_array[appinfo->parent_relid];\n\t\t\t\t\tRelOptInfo *child_rel = root->simple_rel_array[appinfo->child_relid];\n\t\t\t\t\tadd_child_rel_equivalences(root, appinfo, parent_rel, child_rel);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn newec;\n\t}\n\treturn NULL;\n}\n\n/*\n *\tThis optimization transforms between equivalent sort operations to try\n *\tto find useful indexes.\n *\n *\tFor example: an ORDER BY date_trunc('minute', time) can be implemented by\n *\tan ordering of time.\n */\nList *\nts_sort_transform_get_pathkeys(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte,\n\t\t\t\t\t\t\t   Hypertable *ht)\n{\n\t/*\n\t * We attack this problem in three steps:\n\t *\n\t * 1) Create a pathkey for the transformed (simplified) sort.\n\t *\n\t * 2) Use the transformed pathkey to find new useful index paths.\n\t *\n\t * 3) Transform the  pathkey of the new paths back into the original form\n\t * to make this transparent to upper levels in the planner.\n\t *\n\t */\n\tListCell *lc;\n\tList *transformed_query_pathkeys = NIL;\n\tPathKey *last_pk;\n\tPathKey *new_pk;\n\tEquivalenceClass *transformed;\n\n\t/*\n\t * nothing to do for empty pathkeys\n\t */\n\tif (root->query_pathkeys == NIL)\n\t\treturn NIL;\n\n\t/*\n\t * These sort transformations are only safe for single member ORDER BY\n\t * clauses or as last member of the ORDER BY clause.\n\t * Using it for other ORDER BY clauses will result in wrong ordering.\n\t */\n\tlast_pk = llast(root->query_pathkeys);\n\n\t/*\n\t * We can only transform the original pathkey if it references our hypertable.\n\t * If it references another one, we might be able to successfully transform\n\t * it to a join EC that references both hypertables, but when we replace it\n\t * back, we'll get into an incorrect state where the pathkey for the scan\n\t * references only a different hypertable and doesn't have an EC member for\n\t * ours.\n\t */\n\tint desired_ec_relid = rel->relid;\n\tif (rel->reloptkind == RELOPT_OTHER_MEMBER_REL)\n\t{\n\t\t/*\n\t\t * The EC relids contain only inheritance parents, not individual\n\t\t * children.\n\t\t */\n\t\tAppendRelInfo *appinfo = root->append_rel_array[rel->relid];\n\t\tdesired_ec_relid = appinfo->parent_relid;\n\t}\n\n\tEquivalenceClass *last_pk_eclass = last_pk->pk_eclass;\n\n\tif (!bms_is_member(desired_ec_relid, last_pk_eclass->ec_relids))\n\t{\n\t\treturn NIL;\n\t}\n\n\tRelids child_relids = NULL;\n#if PG18_GE\n\t/* In PG18, iterating over child ems requires you to\n\t * use child relids with a special iterator. Here we gather\n\t * them by collecting them from childmembers array.\n\t *\n\t * https://github.com/postgres/postgres/commit/d69d45a5\n\t */\n\tfor (int i = 0; i < last_pk_eclass->ec_childmembers_size; i++)\n\t{\n\t\tif (list_length(last_pk_eclass->ec_childmembers[i]) > 0)\n\t\t{\n\t\t\tchild_relids = bms_add_member(child_relids, i);\n\t\t}\n\t}\n#endif\n\n\t/*\n\t * Try to apply the transformation.\n\t */\n\ttransformed = sort_transform_ec(root, last_pk_eclass, child_relids);\n\n\tif (transformed == NULL)\n\t\treturn NIL;\n\n\tnew_pk = make_canonical_pathkey(root,\n\t\t\t\t\t\t\t\t\ttransformed,\n\t\t\t\t\t\t\t\t\tlast_pk->pk_opfamily,\n\t\t\t\t\t\t\t\t\tlast_pk->pk_cmptype,\n\t\t\t\t\t\t\t\t\tlast_pk->pk_nulls_first);\n\n\t/*\n\t * create complete transformed pathkeys\n\t */\n\tforeach (lc, root->query_pathkeys)\n\t{\n\t\tif (lfirst(lc) != last_pk)\n\t\t\ttransformed_query_pathkeys = lappend(transformed_query_pathkeys, lfirst(lc));\n\t\telse\n\t\t\ttransformed_query_pathkeys = lappend(transformed_query_pathkeys, new_pk);\n\t}\n\n\treturn transformed_query_pathkeys;\n}\n\n/*\n * After we have created new paths with transformed pathkeys, replace them back\n * with the original pathkeys.\n */\nvoid\nts_sort_transform_replace_pathkeys(void *node, List *transformed_pathkeys, List *original_pathkeys)\n{\n\tif (node == NULL)\n\t{\n\t\treturn;\n\t}\n\n\tif (IsA(node, List))\n\t{\n\t\tList *list = castNode(List, node);\n\t\tListCell *lc;\n\t\tforeach (lc, list)\n\t\t{\n\t\t\tts_sort_transform_replace_pathkeys(lfirst(lc), transformed_pathkeys, original_pathkeys);\n\t\t}\n\t\treturn;\n\t}\n\n\tPath *path = (Path *) node;\n\tif (compare_pathkeys(path->pathkeys, transformed_pathkeys) == PATHKEYS_EQUAL)\n\t{\n\t\tpath->pathkeys = original_pathkeys;\n\t}\n\n\tif (IsA(path, CustomPath))\n\t{\n\t\tCustomPath *custom = castNode(CustomPath, path);\n\t\tts_sort_transform_replace_pathkeys(custom->custom_paths,\n\t\t\t\t\t\t\t\t\t\t   transformed_pathkeys,\n\t\t\t\t\t\t\t\t\t\t   original_pathkeys);\n\n\t\t/* Need to handle tsl-specific custom path types */\n\t\tif (ts_cm_functions->sort_transform_replace_pathkeys != NULL)\n\t\t{\n\t\t\tts_cm_functions->sort_transform_replace_pathkeys(path,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t transformed_pathkeys,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t original_pathkeys);\n\t\t}\n\t}\n\telse if (IsA(path, MergeAppendPath))\n\t{\n\t\tMergeAppendPath *append = castNode(MergeAppendPath, path);\n\t\tts_sort_transform_replace_pathkeys(append->subpaths,\n\t\t\t\t\t\t\t\t\t\t   transformed_pathkeys,\n\t\t\t\t\t\t\t\t\t\t   original_pathkeys);\n\t}\n\telse if (IsA(path, AppendPath))\n\t{\n\t\tAppendPath *append = castNode(AppendPath, path);\n\t\tts_sort_transform_replace_pathkeys(append->subpaths,\n\t\t\t\t\t\t\t\t\t\t   transformed_pathkeys,\n\t\t\t\t\t\t\t\t\t\t   original_pathkeys);\n\t}\n\telse if (IsA(path, ProjectionPath))\n\t{\n\t\tProjectionPath *projection = castNode(ProjectionPath, path);\n\t\tts_sort_transform_replace_pathkeys(projection->subpath,\n\t\t\t\t\t\t\t\t\t\t   transformed_pathkeys,\n\t\t\t\t\t\t\t\t\t\t   original_pathkeys);\n\t}\n}\n"
  },
  {
    "path": "src/sort_transform.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include \"hypertable.h\"\n#include \"import/planner.h\"\n\nextern Expr *ts_sort_transform_expr(Expr *expr);\n\nextern List *ts_sort_transform_get_pathkeys(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte,\n\t\t\t\t\t\t\t\t\t\t\tHypertable *ht);\n\nextern void ts_sort_transform_replace_pathkeys(void *node, List *transformed_pathkeys,\n\t\t\t\t\t\t\t\t\t\t\t   List *original_pathkeys);\n"
  },
  {
    "path": "src/subspace_store.README.md",
    "content": "A subspace store allows you to save data associated with a\nmultidimensional-subspace. We use this to cache per-chunk values, such as\n`chunk`s or `chunk_insert_state`. Subspaces are defined conceptually via a\nHypercube (that is a collection of slices -- one for each dimension). Thus, a\nsubspace is a \"rectangular\" cutout in a multidimensional space.\n\nthat is given a hypertable with (ts Timestamp, i int) with intervals of one hour\nand 2 hash-partitions the subspaces could be:\n\n```\n     00:00   01:00   02:00   03:00\n---|-------|-------|-------|-------|--- - -\n 1 |       |       |       |       |\n---|-------|-------|-------|-------|--- - -\n 2 |       |       |       |       |\n---|-------|-------|-------|-------|--- - -\n 3 |       |       |       |       |\n---|-------|-------|-------|-------|--- - -\n```\n\nEach subspace is cached in a tree structure, with each level of the tree\ncorresponding to a dimension the hypertable is partitioned on, with the first\nlevel always being an open (time) dimension time dimension. Thus, the\naforementioned hypertable will have the following tree:\n\n```\nSubspaceStore\n    |\n    V\nSubspaceStoreInternalNode (time)\n       | (.vector)\n       V\n  |  o  | ... | ... | ... |\n     |\n     V\n  DimensionSlice (00:00 - 01:00)\n     |\n     V\n    SubspaceStoreInternalNode (dim 1)\n     |\n     V\n     .\n     .\n     .\n     |\n     V\n    ChunkInsertState (or other leaf object)\n```\n\nEach `SubspaceStoreInternalNode` has a field `descendants` storing a count of\nthe number of leaf objects for that subtree, which we used to ensure\n`SubspaceStore`s don't grow beyond their maximum size. Currently our strategy\nwhen adding to a full `SubspaceStore` is to evict the all elements referenced\nfrom the first entry of the top-most vector. The assumption is that the topmost\nvector indexes based on time, and that the first element stores state for those\nchunks with the earliest time. If we usually perform operations in time-order,\nthese are the elements least likely to be reused. This eviction-strategy is the\nonly reason that the first level of a `SubspaceStore` is always a open (time)\ndimension.\n"
  },
  {
    "path": "src/subspace_store.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <utils/memutils.h>\n\n#include \"dimension.h\"\n#include \"dimension_slice.h\"\n#include \"dimension_vector.h\"\n#include \"hypercube.h\"\n#include \"subspace_store.h\"\n\n/*\n * In terms of datastructures, the subspace store is actually a tree. At the\n * root of a tree is a DimensionVec representing the different DimensionSlices\n * for the first dimension. Each of the DimensionSlices of the\n * first dimension point to a DimensionVec of the second dimension. This recurses\n * for the N dimensions. The leaf DimensionSlice points to the data being stored.\n *\n * */\n\ntypedef struct SubspaceStoreInternalNode\n{\n\tDimensionVec *vector;\n\tuint16 descendants;\n\tbool last_internal_node;\n} SubspaceStoreInternalNode;\n\ntypedef struct SubspaceStore\n{\n\tMemoryContext mcxt;\n\tuint16 num_dimensions;\n\t/* limit growth of store by  limiting number of slices in first dimension,\t0 for no limit */\n\tuint16 max_items;\n\tSubspaceStoreInternalNode *origin; /* origin of the tree */\n} SubspaceStore;\n\nstatic inline SubspaceStoreInternalNode *\nsubspace_store_internal_node_create(bool last_internal_node)\n{\n\tSubspaceStoreInternalNode *node = palloc(sizeof(SubspaceStoreInternalNode));\n\n\tnode->vector = ts_dimension_vec_create(DIMENSION_VEC_DEFAULT_SIZE);\n\tnode->descendants = 0;\n\tnode->last_internal_node = last_internal_node;\n\treturn node;\n}\n\nstatic inline void\nsubspace_store_internal_node_free(void *node)\n{\n\tts_dimension_vec_free(((SubspaceStoreInternalNode *) node)->vector);\n\tpfree(node);\n}\n\nstatic size_t\nsubspace_store_internal_node_descendants(SubspaceStoreInternalNode *node, int index)\n{\n\tconst DimensionSlice *slice = ts_dimension_vec_get(node->vector, index);\n\n\tif (slice == NULL)\n\t\treturn 0;\n\n\tif (node->last_internal_node)\n\t\treturn 1;\n\n\treturn ((SubspaceStoreInternalNode *) slice->storage)->descendants;\n}\n\nSubspaceStore *\nts_subspace_store_init(const Hyperspace *space, MemoryContext mcxt, int16 max_items)\n{\n\tMemoryContext old = MemoryContextSwitchTo(mcxt);\n\tSubspaceStore *sst = palloc(sizeof(SubspaceStore));\n\n\tsst->origin = subspace_store_internal_node_create(space->num_dimensions == 1);\n\tsst->num_dimensions = space->num_dimensions;\n\t/* max_items = 0 is treated as unlimited */\n\tsst->max_items = max_items;\n\tsst->mcxt = mcxt;\n\tMemoryContextSwitchTo(old);\n\treturn sst;\n}\n\nvoid\nts_subspace_store_add(SubspaceStore *subspace_store, const Hypercube *hypercube, void *object,\n\t\t\t\t\t  void (*object_free)(void *))\n{\n\tSubspaceStoreInternalNode *node = subspace_store->origin;\n\tDimensionSlice *last = NULL;\n\tMemoryContext old = MemoryContextSwitchTo(subspace_store->mcxt);\n\tint i;\n\n\tAssert(hypercube->num_slices == subspace_store->num_dimensions);\n\n\tfor (i = 0; i < hypercube->num_slices; i++)\n\t{\n\t\tconst DimensionSlice *target = hypercube->slices[i];\n\t\tDimensionSlice *match;\n\n\t\tAssert(target->storage == NULL);\n\n\t\tif (node == NULL)\n\t\t{\n\t\t\t/*\n\t\t\t * We should have one internal node per dimension in the\n\t\t\t * hypertable. If we don't have one for the current dimension,\n\t\t\t * create one now. (There will always be one for time)\n\t\t\t */\n\t\t\tAssert(last != NULL);\n\t\t\tlast->storage = subspace_store_internal_node_create(i == (hypercube->num_slices - 1));\n\t\t\tlast->storage_free = subspace_store_internal_node_free;\n\t\t\tnode = last->storage;\n\t\t}\n\n\t\tAssert(subspace_store->max_items == 0 ||\n\t\t\t   node->descendants <= (size_t) subspace_store->max_items);\n\n\t\t/*\n\t\t * We only call this function on a cache miss, so number of leaves\n\t\t * will definitely increase see `Assert(last != NULL && last->storage\n\t\t * == NULL);` at bottom.\n\t\t */\n\t\tnode->descendants += 1;\n\n\t\tAssert(0 == node->vector->num_slices ||\n\t\t\t   node->vector->slices[0]->fd.dimension_id == target->fd.dimension_id);\n\n\t\t/* Do we have enough space to store the object? */\n\t\tif (subspace_store->max_items > 0 && node->descendants > subspace_store->max_items)\n\t\t{\n\t\t\t/*\n\t\t\t * Always delete the slice corresponding to the earliest time\n\t\t\t * range. In the normal case that inserts are performed in\n\t\t\t * time-order this is the one least likely to be reused. (Note\n\t\t\t * that we made sure that the first dimension is a time dimension\n\t\t\t * when creating the subspace_store). If out-of-order inserts are\n\t\t\t * become significant we may wish to change this to something more\n\t\t\t * sophisticated like LRU.\n\t\t\t */\n\t\t\tsize_t items_removed = subspace_store_internal_node_descendants(node, i);\n\n\t\t\t/*\n\t\t\t * descendants at the root is inclusive of the descendants at the\n\t\t\t * children, so if we have an overflow it must be in the time dim\n\t\t\t */\n\t\t\tAssert(i == 0);\n\n\t\t\tAssert(subspace_store->max_items + 1 == node->descendants);\n\n\t\t\tts_dimension_vec_remove_slice(&node->vector, i);\n\n\t\t\t/*\n\t\t\t * Note we would have to do this to ancestors if this was not the\n\t\t\t * root.\n\t\t\t */\n\t\t\tnode->descendants -= items_removed;\n\t\t}\n\n\t\tmatch = ts_dimension_vec_find_slice(node->vector, target->fd.range_start);\n\n\t\t/* Do we have a slot in this vector for the new object? */\n\t\tif (match == NULL)\n\t\t{\n\t\t\tDimensionSlice *copy;\n\n\t\t\t/*\n\t\t\t * create a new copy of the range this slice covers, to store the\n\t\t\t * object in\n\t\t\t */\n\t\t\tcopy = ts_dimension_slice_copy(target);\n\n\t\t\tts_dimension_vec_add_slice_sort(&node->vector, copy);\n\t\t\tmatch = copy;\n\t\t}\n\n\t\tAssert(subspace_store->max_items == 0 ||\n\t\t\t   node->descendants <= (size_t) subspace_store->max_items);\n\n\t\tlast = match;\n\t\t/* internal slices point to the next SubspaceStoreInternalNode */\n\t\tnode = last->storage;\n\t}\n\n\tAssert(last != NULL && last->storage == NULL);\n\tlast->storage = object; /* at the end we store the object */\n\tlast->storage_free = object_free;\n\tMemoryContextSwitchTo(old);\n}\n\nvoid *\nts_subspace_store_get(const SubspaceStore *subspace_store, const Point *target)\n{\n\tint i;\n\tDimensionVec *vec = subspace_store->origin->vector;\n\tDimensionSlice *match = NULL;\n\n\tAssert(target->cardinality == subspace_store->num_dimensions);\n\n\t/* The internal compressed hypertable has no dimensions as\n\t * chunks are created explicitly by compress_chunk and linked\n\t * to the source chunk. */\n\tif (subspace_store->num_dimensions == 0)\n\t\treturn NULL;\n\n\tfor (i = 0; i < target->cardinality; i++)\n\t{\n\t\tmatch = ts_dimension_vec_find_slice(vec, target->coordinates[i]);\n\n\t\tif (NULL == match)\n\t\t\treturn NULL;\n\n\t\tvec = ((SubspaceStoreInternalNode *) match->storage)->vector;\n\t}\n\tAssert(match != NULL);\n\treturn match->storage;\n}\n\nvoid\nts_subspace_store_free(SubspaceStore *subspace_store)\n{\n\tsubspace_store_internal_node_free(subspace_store->origin);\n\tpfree(subspace_store);\n}\n\nMemoryContext\nts_subspace_store_mcxt(const SubspaceStore *subspace_store)\n{\n\treturn subspace_store->mcxt;\n}\n"
  },
  {
    "path": "src/subspace_store.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include \"dimension.h\"\n\n/* A subspace store allows you to save data associated with\n * a multidimensional-subspace. Subspaces are defined conceptually\n * via a Hypercube (that is a collection of slices -- one for each dimension).\n * Thus, a subspace is a \"rectangular\" cutout in a multidimensional space.\n */\n\ntypedef struct Hypercube Hypercube;\ntypedef struct Point Point;\ntypedef struct SubspaceStore SubspaceStore;\n\nextern SubspaceStore *ts_subspace_store_init(const Hyperspace *space, MemoryContext mcxt,\n\t\t\t\t\t\t\t\t\t\t\t int16 max_items);\n\n/* Store an object associate with the subspace represented by a hypercube */\nextern void ts_subspace_store_add(SubspaceStore *subspace_store, const Hypercube *hypercube,\n\t\t\t\t\t\t\t\t  void *object, void (*object_free)(void *));\n\n/* Get the object stored for the subspace that a point is in.\n * Return the object stored or NULL if this subspace is not in the store.\n */\nextern void *ts_subspace_store_get(const SubspaceStore *subspace_store, const Point *target);\nextern void ts_subspace_store_free(SubspaceStore *subspace_store);\nextern MemoryContext ts_subspace_store_mcxt(const SubspaceStore *subspace_store);\n"
  },
  {
    "path": "src/telemetry/CMakeLists.txt",
    "content": "# Add all *.c to sources in upperlevel directory\nset(SOURCES\n    ${CMAKE_CURRENT_SOURCE_DIR}/functions.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/replication.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/stats.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/telemetry_metadata.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/telemetry.c)\ntarget_sources(${PROJECT_NAME} PRIVATE ${SOURCES})\n"
  },
  {
    "path": "src/telemetry/functions.c",
    "content": "\n/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <fmgr.h>\n\n#include <access/genam.h>\n#include <access/htup_details.h>\n#include <access/table.h>\n#include <catalog/indexing.h>\n#include <catalog/pg_depend.h>\n#include <catalog/pg_extension.h>\n#include <catalog/pg_proc.h>\n#include <commands/extension.h>\n#include <nodes/nodeFuncs.h>\n#include <port/atomics.h>\n#include <storage/lwlock.h>\n#include <utils/fmgroids.h>\n#include <utils/hsearch.h>\n\n#include <utils/regproc.h>\n\n#include \"functions.h\"\n\n#include \"guc.h\"\n#include \"loader/function_telemetry.h\"\n\nstatic bool skip_telemetry = false;\nstatic LWLock *function_counts_lock = NULL;\nstatic HTAB *function_counts;\n\n/***************************\n * Telemetry draining code *\n ***************************/\n\ntypedef struct AllowedFnHashEntry\n{\n\tOid fn;\n} AllowedFnHashEntry;\n\n// Get a HTAB of AllowedFnHashEntrys containing all and only those functions\n// that are within visible_extensions. This function should be equivalent to\n// the SQL\n//     SELECT objid\n//     FROM pg_catalog.pg_depend, pg_catalog.pg_extension extension\n//      WHERE refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass\n//        AND refobjid = extension.oid\n//        AND deptype = 'e'\n//        AND extname IN ('timescaledb','promscale','timescaledb_toolkit')\n//        AND classid = 'pg_catalog.pg_proc'::regclass;\nstatic HTAB *\nallowed_extension_functions(const char **visible_extensions, int num_visible_extensions)\n{\n\tHASHCTL hash_info = {\n\t\t.keysize = sizeof(Oid),\n\t\t.entrysize = sizeof(AllowedFnHashEntry),\n\t\t.hcxt = CurrentMemoryContext,\n\t};\n\tHTAB *allowed_fns = hash_create(\"fn telemetry allowed_functions\",\n\t\t\t\t\t\t\t\t\t1000,\n\t\t\t\t\t\t\t\t\t&hash_info,\n\t\t\t\t\t\t\t\t\tHASH_ELEM | HASH_BLOBS | HASH_CONTEXT);\n\n\tRelation depRel = table_open(DependRelationId, AccessShareLock);\n\n\tOid *visible_extension_ids = palloc(num_visible_extensions * sizeof(Oid));\n\n\t// get the Oid for each of the visible extensions\n\tfor (int i = 0; i < num_visible_extensions; i++)\n\t\tvisible_extension_ids[i] = get_extension_oid(visible_extensions[i], true);\n\n\t// go through the objects owned by each visible extension, and store the\n\t// ones that are functions in the set.\n\tfor (int i = 0; i < num_visible_extensions; i++)\n\t{\n\t\tHeapTuple tup;\n\t\tScanKeyData key[2];\n\t\tSysScanDesc scan;\n\t\tOid extension_id = visible_extension_ids[i];\n\n\t\tif (!OidIsValid(extension_id))\n\t\t\tcontinue;\n\n\t\t// Look in the (referenced object class, referenced object) index for\n\t\t// the allowed extensions.\n\t\tScanKeyInit(&key[0],\n\t\t\t\t\tAnum_pg_depend_refclassid,\n\t\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\t\tF_OIDEQ,\n\t\t\t\t\tObjectIdGetDatum(ExtensionRelationId));\n\t\tScanKeyInit(&key[1],\n\t\t\t\t\tAnum_pg_depend_refobjid,\n\t\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\t\tF_OIDEQ,\n\t\t\t\t\tObjectIdGetDatum(extension_id));\n\n\t\tscan = systable_beginscan(depRel, DependReferenceIndexId, true, NULL, 2, key);\n\n\t\twhile (HeapTupleIsValid(tup = systable_getnext(scan)))\n\t\t{\n\t\t\tForm_pg_depend deprec = (Form_pg_depend) GETSTRUCT(tup);\n\t\t\t// Filter for those objects that have an extension dependencies\n\t\t\t// exist in pg_proc, those are the functions that live in the extension\n\t\t\tif (deprec->deptype == 'e' && deprec->classid == ProcedureRelationId)\n\t\t\t{\n\t\t\t\tAllowedFnHashEntry *entry =\n\t\t\t\t\thash_search(allowed_fns, &deprec->objid, HASH_ENTER, NULL);\n\t\t\t\tentry->fn = deprec->objid;\n\t\t\t}\n\t\t}\n\n\t\tsystable_endscan(scan);\n\t}\n\n\ttable_close(depRel, AccessShareLock);\n\n\treturn allowed_fns;\n}\n\nstatic fn_telemetry_entry_vec *\nread_shared_map()\n{\n\tHASH_SEQ_STATUS hash_seq;\n\tlong i;\n\tlong num_entries = hash_get_num_entries(function_counts);\n\tfn_telemetry_entry_vec *entries =\n\t\tfn_telemetry_entry_vec_create(CurrentMemoryContext, num_entries);\n\n\tLWLockAcquire(function_counts_lock, LW_SHARED);\n\n\thash_seq_init(&hash_seq, function_counts);\n\t// limit to num_entries so we don't hold the lock for a realloc\n\tfor (i = 0; i < num_entries; i++)\n\t{\n\t\tFnTelemetryEntry entry;\n\t\tFnTelemetryHashEntry *hash_entry = hash_seq_search(&hash_seq);\n\t\tif (!hash_entry)\n\t\t\tbreak;\n\n\t\tentry.fn = hash_entry->key;\n\t\t/*\n\t\t * We never remove entries here, merely set their counts to 0. At\n\t\t * steady-state we expect the functions used by most workloads to be\n\t\t * effectively constant, so by keeping the hashmap entries allocated we\n\t\t * reduce contention during the telemetry-gathering stage. If memory\n\t\t * usage become an issue we can delete based off some heuristic, eg. if\n\t\t * the count starts out as 0.\n\t\t */\n\t\tentry.count = pg_atomic_read_u64(&hash_entry->count);\n\t\tif (entry.count != 0)\n\t\t\tfn_telemetry_entry_vec_append(entries, entry);\n\t}\n\tif (i == num_entries)\n\t\thash_seq_term(&hash_seq);\n\n\tLWLockRelease(function_counts_lock);\n\n\treturn entries;\n}\n\n/*\n * Read the function telemetry shared-memory hashmap for telemetry send.\n *\n * This function gathers (function_id, count) pairs from the shared hashmap,\n * and filters the set for the functions we're allowed to send back.\n *\n * In general, we should never send telemetry information about any functions\n * except for core functions and those is a specified list of extensions\n * (when originally written, the set of related_extensions along with\n * timescaledb itself), so this function is designed to make it difficult to do\n * so.\n *\n * @param visible_extensions list of extensions whose functions should be\n *                           returned\n * @param num_visible_extensions length of visible_extensions\n * @return vector of FnTelemetryEntry containing (function_id, count)s for the\n *         functions in visible_extensions.\n *\n */\nfn_telemetry_entry_vec *\nts_function_telemetry_read(const char **visible_extensions, int num_visible_extensions)\n{\n\tfn_telemetry_entry_vec *entries_to_send;\n\tfn_telemetry_entry_vec *all_entries;\n\tHTAB *allowed_ext_fns;\n\n\tif (function_counts == NULL)\n\t{\n\t\tFnTelemetryRendezvous **rendezvous =\n\t\t\t(FnTelemetryRendezvous **) find_rendezvous_variable(RENDEZVOUS_FUNCTION_TELEMENTRY);\n\n\t\tif (*rendezvous == NULL)\n\t\t\treturn NULL;\n\n\t\tfunction_counts = (*rendezvous)->function_counts;\n\t\tfunction_counts_lock = (*rendezvous)->lock;\n\t}\n\n\tall_entries = read_shared_map();\n\tentries_to_send =\n\t\tfn_telemetry_entry_vec_create(CurrentMemoryContext, all_entries->num_elements);\n\tallowed_ext_fns = allowed_extension_functions(visible_extensions, num_visible_extensions);\n\n\tfor (uint32 i = 0; i < all_entries->num_elements; i++)\n\t{\n\t\tFnTelemetryEntry *entry = fn_telemetry_entry_vec_at(all_entries, i);\n\t\tbool is_builtin = entry->fn >= 1 && entry->fn <= 9999;\n\t\tbool is_visible = is_builtin || hash_search(allowed_ext_fns, &entry->fn, HASH_FIND, NULL);\n\t\tif (is_visible)\n\t\t\tfn_telemetry_entry_vec_append(entries_to_send, *entry);\n\t}\n\n\treturn entries_to_send;\n}\n\n/*\n * Reset the counts in the function telemetry shared-memory hashmap.\n *\n * This function resets the shared function counts after we send back telemetry\n * in preparation for the next recording cycle. Note that there is no way to\n * atomically read and reset the counts in the shared hashmap, so writes that\n * occur between sending the old counts and resetting for the next cycle will be\n * lost. Since this this telemetry is only ever an approximation of reality, we\n * believe this loss is acceptable considering that the alternatives are\n * resetting the counts whenever the telemetry is read (potentially even more\n * lossy), or holding the lock for the entire telemetry send (to long a\n * contention window).\n */\nvoid\nts_function_telemetry_reset_counts()\n{\n\tHASH_SEQ_STATUS hash_seq;\n\tif (!function_counts)\n\t\treturn;\n\n\tLWLockAcquire(function_counts_lock, LW_SHARED);\n\n\thash_seq_init(&hash_seq, function_counts);\n\t// limit to num_entries so we don't hold the lock for a realloc\n\twhile (true)\n\t{\n\t\tFnTelemetryHashEntry *hash_entry = hash_seq_search(&hash_seq);\n\t\tif (!hash_entry)\n\t\t\tbreak;\n\n\t\t/*\n\t\t * We never remove entries here, merely set their counts to 0. At\n\t\t * steady-state we expect the functions used by most workloads to be\n\t\t * effectively constant, so by keeping the hashmap entries allocated we\n\t\t * reduce contention during the telemetry-gathering stage. If memory\n\t\t * usage become an issue we can delete based off some heuristic, eg. if\n\t\t * the count starts out as 0, though we will have to use a stronger\n\t\t * lock.\n\t\t */\n\t\tpg_atomic_write_u64(&hash_entry->count, 0);\n\t}\n\n\tLWLockRelease(function_counts_lock);\n}\n\n/****************************\n * Telemetry gathering code *\n ****************************/\n\nstatic bool\nfunction_telemetry_increment(Oid func_id, HTAB **local_counts)\n{\n\tFnTelemetryEntry *entry;\n\tbool found;\n\n\t// if this is the first function we've seen initialize local_counts\n\tif (!*local_counts)\n\t{\n\t\tHASHCTL hash_info = {\n\t\t\t.keysize = sizeof(Oid),\n\t\t\t.entrysize = sizeof(FnTelemetryEntry),\n\t\t\t.hcxt = CurrentMemoryContext,\n\t\t};\n\t\t*local_counts = hash_create(\"fn telemetry local function hash\",\n\t\t\t\t\t\t\t\t\t10,\n\t\t\t\t\t\t\t\t\t&hash_info,\n\t\t\t\t\t\t\t\t\tHASH_ELEM | HASH_BLOBS | HASH_CONTEXT);\n\t}\n\n\tentry = hash_search(*local_counts, &func_id, HASH_ENTER, &found);\n\tif (!found)\n\t\tentry->count = 0;\n\n\tentry->count += 1;\n\n\treturn true;\n}\n\nstatic bool\nfunction_gather_checker(Oid func_id, void *context)\n{\n\tfunction_telemetry_increment(func_id, (HTAB **) context);\n\treturn false;\n}\n\nstatic bool\nfunction_gather_walker(Node *node, void *context)\n{\n\tbool end_early;\n\n\tif (node == NULL)\n\t\treturn false;\n\n\tend_early = check_functions_in_node(node, function_gather_checker, context);\n\tif (end_early)\n\t\treturn true;\n\n\tif (IsA(node, Query))\n\t{\n\t\t/* Recurse into subselects */\n\t\treturn query_tree_walker((Query *) node, function_gather_walker, context, 0);\n\t}\n\treturn expression_tree_walker(node, function_gather_walker, context);\n}\n\nstatic HTAB *\nrecord_function_counts(Query *query)\n{\n\tHTAB *query_function_counts = NULL;\n\tquery_tree_walker(query, function_gather_walker, (void *) &query_function_counts, 0);\n\treturn query_function_counts;\n}\n\n/*\n * Store a map of (function_oid, count) into shared memory so it can be seen by\n * the telemetry worker. This insertion works in two phases:\n *   1. Under a SHARED lock, we increment the counts of all those functions that\n *      are already present in the map, using atomic fetch-add to prevent races.\n *   2. Under an EXCLUSIVE lock, we insert entries for all those functions that\n *      were not already in the map.\n * At steady state we expect that vast majority the time all the functions a\n * query uses will already be in the shared map, so this strategy should\n * minimize contention between queries.\n *\n * @param query_function_counts A hashtable of FnTelemetryEntry storing\n *                              function usage counts\n */\nstatic void\nstore_function_counts_in_shared_mem(HTAB *query_function_counts)\n{\n\tHASH_SEQ_STATUS hash_seq;\n\tFnTelemetryEntry *local_entry = NULL;\n\tfn_telemetry_entry_vec missing_entries;\n\tfn_telemetry_entry_vec_init(&missing_entries, CurrentMemoryContext, 0);\n\n\t/*\n\t * Increment the counts of any functions already in the table under a\n\t * shared lock; the atomicity of increments will handle concurrency.\n\t */\n\tLWLockAcquire(function_counts_lock, LW_SHARED);\n\thash_seq_init(&hash_seq, query_function_counts);\n\n\twhile ((local_entry = hash_seq_search(&hash_seq)))\n\t{\n\t\tFnTelemetryHashEntry *shared_entry =\n\t\t\thash_search(function_counts, &local_entry->fn, HASH_FIND, NULL);\n\n\t\tif (shared_entry)\n\t\t\tpg_atomic_fetch_add_u64(&shared_entry->count, local_entry->count);\n\t\telse\n\t\t\tfn_telemetry_entry_vec_append(&missing_entries, *local_entry);\n\t}\n\n\tLWLockRelease(function_counts_lock);\n\n\t/*\n\t * If any functions did not have an entries create them under an\n\t * exclusive lock\n\t */\n\tif (missing_entries.num_elements > 0)\n\t{\n\t\tLWLockAcquire(function_counts_lock, LW_EXCLUSIVE);\n\t\tfor (uint32 i = 0; i < missing_entries.num_elements; i++)\n\t\t{\n\t\t\tbool found = false;\n\t\t\tFnTelemetryEntry *missing_entry = fn_telemetry_entry_vec_at(&missing_entries, i);\n\t\t\tFnTelemetryHashEntry *shared_entry =\n\t\t\t\thash_search(function_counts, &missing_entry->fn, HASH_ENTER_NULL, &found);\n\n\t\t\tif (!shared_entry)\n\t\t\t\tbreak;\n\n\t\t\tif (found)\n\t\t\t\tpg_atomic_fetch_add_u64(&shared_entry->count, missing_entry->count);\n\t\t\telse\n\t\t\t\tpg_atomic_init_u64(&shared_entry->count, missing_entry->count);\n\t\t}\n\t\tLWLockRelease(function_counts_lock);\n\t}\n}\n\n/*\n * Gather function usage telemetry for a query.\n *\n * This function walks a query looking for function Oids, counts their\n * occurrence, and stores the (function_id, count) set into the shared-memory\n * function telemetry hashtable for later processing by the telemetry background\n * worker.\n */\nvoid\nts_telemetry_function_info_gather(Query *query)\n{\n\tHTAB *query_function_counts;\n\n\tif (skip_telemetry || !ts_function_telemetry_on())\n\t\treturn;\n\n\t// At the first time through initialize the shared state\n\tif (function_counts == NULL)\n\t{\n\t\tFnTelemetryRendezvous **rendezvous =\n\t\t\t(FnTelemetryRendezvous **) find_rendezvous_variable(RENDEZVOUS_FUNCTION_TELEMENTRY);\n\n\t\tif (*rendezvous == NULL)\n\t\t{\n\t\t\tskip_telemetry = true;\n\t\t\treturn;\n\t\t}\n\n\t\tfunction_counts = (*rendezvous)->function_counts;\n\t\tfunction_counts_lock = (*rendezvous)->lock;\n\t}\n\n\tquery_function_counts = record_function_counts(query);\n\n\tif (query_function_counts)\n\t\tstore_function_counts_in_shared_mem(query_function_counts);\n}\n"
  },
  {
    "path": "src/telemetry/functions.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\ntypedef struct FnTelemetryEntry\n{\n\tOid fn;\n\tuint64 count;\n} FnTelemetryEntry;\n\n#define VEC_PREFIX fn_telemetry_entry\n#define VEC_ELEMENT_TYPE FnTelemetryEntry\n#define VEC_DECLARE 1\n#define VEC_DEFINE 1\n#define VEC_SCOPE static inline\n#include <adts/vec.h>\n\nextern void ts_telemetry_function_info_gather(Query *query);\n\nextern fn_telemetry_entry_vec *ts_function_telemetry_read(const char **visible_extensions,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  int num_visible_extensions);\nextern void ts_function_telemetry_reset_counts(void);\n"
  },
  {
    "path": "src/telemetry/replication.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <executor/spi.h>\n#include <utils/guc.h>\n\n#include \"replication.h\"\n\nReplicationInfo\nts_telemetry_replication_info_gather(void)\n{\n\tint res;\n\tbool isnull;\n\tDatum data;\n\tReplicationInfo info = {\n\t\t.got_num_wal_senders = false,\n\t\t.got_is_wal_receiver = false,\n\t};\n\n\tif (SPI_connect() != SPI_OK_CONNECT)\n\t\treturn info;\n\n\t/* Lock down search_path */\n\tint save_nestlevel = NewGUCNestLevel();\n\tRestrictSearchPath();\n\n\tres = SPI_execute(\"SELECT cast(count(pid) as int) from pg_catalog.pg_stat_get_wal_senders() \"\n\t\t\t\t\t  \"WHERE pid is not null\",\n\t\t\t\t\t  true, /* read_only */\n\t\t\t\t\t  0\t\t/*count*/\n\t);\n\n\tif (res >= 0)\n\t{\n\t\tdata = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);\n\t\tinfo.num_wal_senders = DatumGetInt32(data);\n\t\tinfo.got_num_wal_senders = true;\n\t}\n\n\t/* use count() > 0 in case they start having pg_stat_get_wal_receiver()\n\t * return no rows when the DB isn't a replica */\n\tres = SPI_execute(\"SELECT count(pid) > 0 from pg_catalog.pg_stat_get_wal_receiver() WHERE pid \"\n\t\t\t\t\t  \"is not null\",\n\t\t\t\t\t  true, /* read_only */\n\t\t\t\t\t  0\t\t/*count*/\n\t);\n\tif (res >= 0)\n\t{\n\t\tdata = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);\n\t\tinfo.is_wal_receiver = DatumGetBool(data);\n\t\tinfo.got_is_wal_receiver = true;\n\t}\n\n\tres = SPI_finish();\n\tif (res != SPI_OK_FINISH)\n\t\telog(ERROR, \"SPI_finish failed: %s\", SPI_result_code_string(res));\n\n\t/* Restore search_path */\n\tAtEOXact_GUC(false, save_nestlevel);\n\n\treturn info;\n}\n"
  },
  {
    "path": "src/telemetry/replication.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include \"utils.h\"\n\ntypedef struct ReplicationInfo\n{\n\tbool got_num_wal_senders;\n\tint32 num_wal_senders;\n\n\tbool got_is_wal_receiver;\n\tbool is_wal_receiver;\n} ReplicationInfo;\n\nextern ReplicationInfo ts_telemetry_replication_info_gather(void);\n"
  },
  {
    "path": "src/telemetry/stats.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/genam.h>\n#include <access/htup_details.h>\n#include <access/table.h>\n#include <access/tableam.h>\n#include <catalog/indexing.h>\n#include <catalog/namespace.h>\n#include <catalog/pg_class.h>\n#include <catalog/pg_namespace.h>\n#include <fmgr.h>\n#include <storage/lmgr.h>\n#include <utils/builtins.h>\n#include <utils/snapmgr.h>\n#include <utils/syscache.h>\n\n#include \"chunk.h\"\n#include \"debug_point.h\"\n#include \"extension.h\"\n#include \"hypertable_cache.h\"\n#include \"stats.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"ts_catalog/continuous_agg.h\"\n#include \"utils.h\"\n\ntypedef struct StatsContext\n{\n\tTelemetryStats *stats;\n\tSnapshot snapshot;\n} StatsContext;\n\n/*\n * Determine the type of a hypertable.\n */\nstatic StatsRelType\nclassify_hypertable(const Hypertable *ht)\n{\n\tif (TS_HYPERTABLE_IS_INTERNAL_COMPRESSION_TABLE(ht))\n\t{\n\t\t/*\n\t\t * This is an internal compression table, but could be for a\n\t\t * regular hypertable, or for an internal materialized\n\t\t * hypertable (cagg). The latter case is currently not handled\n\t\t */\n\t\treturn RELTYPE_COMPRESSION_HYPERTABLE;\n\t}\n\telse\n\t{\n\t\t/*\n\t\t * Not dealing with an internal compression hypertable, but\n\t\t * could be a materialized hypertable (cagg).\n\t\t */\n\t\treturn RELTYPE_HYPERTABLE;\n\t}\n}\n\nstatic StatsRelType\nclassify_chunk(Cache *htcache, const Hypertable **ht, const Chunk *chunk)\n{\n\tStatsRelType parent_reltype;\n\n\tAssert(NULL != chunk);\n\t/* Classify the chunk's parent */\n\t*ht = ts_hypertable_cache_get_entry(htcache, chunk->hypertable_relid, CACHE_FLAG_MISSING_OK);\n\tAssert(NULL != *ht);\n\tparent_reltype = classify_hypertable(*ht);\n\n\t/* Classify the chunk's parent */\n\tswitch (parent_reltype)\n\t{\n\t\tcase RELTYPE_HYPERTABLE:\n\t\t\treturn RELTYPE_CHUNK;\n\t\tcase RELTYPE_MATERIALIZED_HYPERTABLE:\n\t\t\treturn RELTYPE_MATERIALIZED_CHUNK;\n\t\tcase RELTYPE_COMPRESSION_HYPERTABLE:\n\t\t\treturn RELTYPE_COMPRESSION_CHUNK;\n\t\tdefault:\n\t\t\t/* Shouldn't really get here */\n\t\t\treturn RELTYPE_OTHER;\n\t}\n}\n\nstatic StatsRelType\nclassify_table(const Form_pg_class class, Cache *htcache, const Hypertable **ht,\n\t\t\t   const Chunk **chunk)\n{\n\tAssert(class->relkind == RELKIND_RELATION);\n\n\tif (class->relispartition)\n\t\treturn RELTYPE_PARTITION;\n\n\t/* Check if it is a hypertable */\n\t*ht = ts_hypertable_cache_get_entry(htcache, class->oid, CACHE_FLAG_MISSING_OK);\n\n\tif (*ht)\n\t\treturn classify_hypertable(*ht);\n\n\t/* Check if it is a chunk */\n\t*chunk = ts_chunk_get_by_relid(class->oid, false);\n\n\tif (NULL != *chunk)\n\t\treturn classify_chunk(htcache, ht, *chunk);\n\n\treturn RELTYPE_TABLE;\n}\n\nstatic StatsRelType\nclassify_partitioned_table(const Form_pg_class class)\n{\n\tAssert(class->relkind == RELKIND_PARTITIONED_TABLE);\n\n\t/*\n\t * If the partitioned table itself is a partition, then it is a partition\n\t * in a multi-dimensional partitioned table. Treat it as a partition so\n\t * that only \"root\" tables are counted as partitioned tables.\n\t */\n\tif (class->relispartition)\n\t\treturn RELTYPE_PARTITION;\n\n\treturn RELTYPE_PARTITIONED_TABLE;\n}\n\nstatic StatsRelType\nclassify_foreign_table(Cache *htcache, Oid relid, const Hypertable **ht, const Chunk **chunk)\n{\n\t*chunk = ts_chunk_get_by_relid(relid, false);\n\n\tif (*chunk)\n\t\treturn classify_chunk(htcache, ht, *chunk);\n\n\t/*\n\t * Currently don't care about non-chunk foreign tables, so classify as\n\t * \"other\".\n\t */\n\treturn RELTYPE_OTHER;\n}\n\nstatic StatsRelType\nclassify_view(const Form_pg_class class, Cache *htcache, const ContinuousAgg **cagg)\n{\n\tconst Catalog *catalog = ts_catalog_get();\n\n\tif (class->relnamespace == catalog->extension_schema_id[TS_INTERNAL_SCHEMA])\n\t\treturn RELTYPE_OTHER;\n\n\t*cagg = ts_continuous_agg_find_by_relid(class->oid);\n\n\tif (*cagg)\n\t\treturn RELTYPE_CONTINUOUS_AGG;\n\n\treturn RELTYPE_VIEW;\n}\n\nstatic StatsRelType\nclassify_relation(const Form_pg_class class, Cache *htcache, const Hypertable **ht,\n\t\t\t\t  const Chunk **chunk, const ContinuousAgg **cagg)\n{\n\t*chunk = NULL;\n\t*ht = NULL;\n\t*cagg = NULL;\n\n\tswitch (class->relkind)\n\t{\n\t\tcase RELKIND_RELATION:\n\t\t\treturn classify_table(class, htcache, ht, chunk);\n\t\tcase RELKIND_PARTITIONED_TABLE:\n\t\t\treturn classify_partitioned_table(class);\n\t\tcase RELKIND_FOREIGN_TABLE:\n\t\t\treturn classify_foreign_table(htcache, class->oid, ht, chunk);\n\t\tcase RELKIND_MATVIEW:\n\t\t\treturn RELTYPE_MATVIEW;\n\t\tcase RELKIND_VIEW:\n\t\t\treturn classify_view(class, htcache, cagg);\n\t\tdefault:\n\t\t\treturn RELTYPE_OTHER;\n\t}\n}\n\nstatic void\nadd_storage(StorageStats *stats, Form_pg_class class)\n{\n\tRelationSize relsize;\n\n\trelsize = ts_relation_size_impl(class->oid);\n\tstats->relsize.total_size += relsize.total_size;\n\tstats->relsize.heap_size += relsize.heap_size;\n\tstats->relsize.toast_size += relsize.toast_size;\n\tstats->relsize.index_size += relsize.index_size;\n}\n\nstatic void\nprocess_relation(BaseStats *stats, Form_pg_class class)\n{\n\tstats->relcount++;\n\n\t/*\n\t * As of PG14, pg_class.reltuples is set to -1 when the row count is\n\t * unknown. Make sure we only add the count when the information is\n\t * available.\n\t */\n\tif (class->reltuples > 0)\n\t\tstats->reltuples += class->reltuples;\n\n\tif (RELKIND_HAS_STORAGE(class->relkind))\n\t\tadd_storage((StorageStats *) stats, class);\n}\n\nstatic void\nprocess_hypertable(HyperStats *hyp, Form_pg_class class, const Hypertable *ht)\n{\n\tprocess_relation(&hyp->storage.base, class);\n\n\tif (TS_HYPERTABLE_HAS_COMPRESSION_ENABLED(ht))\n\t\thyp->compressed_hypertable_count++;\n}\n\nstatic void\nprocess_continuous_agg(CaggStats *cs, Form_pg_class class, const ContinuousAgg *cagg)\n{\n\tconst Hypertable *mat_ht = ts_hypertable_get_by_id(cagg->data.mat_hypertable_id);\n\n\tAssert(cagg);\n\n\tprocess_relation(&cs->hyp.storage.base, class);\n\n\tif (TS_HYPERTABLE_HAS_COMPRESSION_ENABLED(mat_ht))\n\t\tcs->hyp.compressed_hypertable_count++;\n\n\tif (!cagg->data.materialized_only)\n\t\tcs->uses_real_time_aggregation_count++;\n\n\tcs->finalized++;\n\n\tif (cagg->data.parent_mat_hypertable_id != INVALID_HYPERTABLE_ID)\n\t\tcs->nested++;\n}\n\nstatic void\nprocess_partition(HyperStats *stats, Form_pg_class class, bool ischunk)\n{\n\tstats->child_count++;\n\t/*\n\t * Note that reltuples should be correct even for compressed chunks, since\n\t * we \"freeze\" those stats when a chunk is compressed, and for foreign\n\t * table chunks, since we import those stats from data nodes.\n\t *\n\t * Also, as of PG14, the parent tables include the cumulative stats for\n\t * all children, so no need to count the partitions separately since the\n\t * sum will be in the root.\n\t */\n\tif (ischunk && class->reltuples > 0)\n\t{\n\t\tstats->storage.base.reltuples += class->reltuples;\n\t}\n\n\tadd_storage(&stats->storage, class);\n}\n\n/*\n * Add a chunk's stats to the parent table.\n */\nstatic void\nadd_chunk_stats(HyperStats *stats, Form_pg_class class, const Chunk *chunk,\n\t\t\t\tconst Form_compression_chunk_size fd_compr)\n{\n\tprocess_partition(stats, class, true);\n\n\tif (ts_chunk_is_compressed(chunk))\n\t\tstats->compressed_chunk_count++;\n\n\tif (fd_compr)\n\t{\n\t\tstats->compressed_heap_size += fd_compr->compressed_heap_size;\n\t\tstats->compressed_indexes_size += fd_compr->compressed_index_size;\n\t\tstats->compressed_toast_size += fd_compr->compressed_toast_size;\n\t\tstats->uncompressed_heap_size += fd_compr->uncompressed_heap_size;\n\t\tstats->uncompressed_indexes_size += fd_compr->uncompressed_index_size;\n\t\tstats->uncompressed_toast_size += fd_compr->uncompressed_toast_size;\n\t\tstats->uncompressed_row_count += fd_compr->numrows_pre_compression;\n\t\tstats->compressed_row_count += fd_compr->numrows_post_compression;\n\t\tstats->compressed_row_frozen_immediately_count += fd_compr->numrows_frozen_immediately;\n\n\t\t/* Also add compressed sizes to total number for entire table */\n\t\tstats->storage.relsize.heap_size += fd_compr->compressed_heap_size;\n\t\tstats->storage.relsize.toast_size += fd_compr->compressed_toast_size;\n\t\tstats->storage.relsize.index_size += fd_compr->compressed_index_size;\n\t}\n}\n\nstatic bool\nget_chunk_compression_stats(StatsContext *statsctx, const Chunk *chunk,\n\t\t\t\t\t\t\tForm_compression_chunk_size compr_stats)\n{\n\tTupleInfo *ti;\n\tScanIterator it;\n\tbool found = false;\n\n\tif (!ts_chunk_is_compressed(chunk))\n\t\treturn false;\n\n\tit = ts_scan_iterator_create(COMPRESSION_CHUNK_SIZE, AccessShareLock, CurrentMemoryContext);\n\tts_scan_iterator_set_index(&it, COMPRESSION_CHUNK_SIZE, COMPRESSION_CHUNK_SIZE_PKEY);\n\tit.ctx.snapshot = statsctx->snapshot;\n\n\tts_scan_iterator_scan_key_reset(&it);\n\tts_scan_iterator_scan_key_init(&it,\n\t\t\t\t\t\t\t\t   Anum_compression_chunk_size_pkey_chunk_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(chunk->fd.id));\n\tts_scan_iterator_start_or_restart_scan(&it);\n\tti = ts_scan_iterator_next(&it);\n\n\tif (ti)\n\t{\n\t\tForm_compression_chunk_size fd;\n\t\tbool should_free;\n\t\tHeapTuple tuple = ts_scan_iterator_fetch_heap_tuple(&it, false, &should_free);\n\n\t\tfd = (Form_compression_chunk_size) GETSTRUCT(tuple);\n\t\tmemcpy(compr_stats, fd, sizeof(*fd));\n\n\t\tif (should_free)\n\t\t\theap_freetuple(tuple);\n\n\t\tfound = true;\n\t}\n\n\tts_scan_iterator_close(&it);\n\n\treturn found;\n}\n\n/*\n * Process a relation identified as being a chunk.\n *\n * The chunk could be part of a\n *\n *  - Hypertable\n *  - Distributed hypertable\n *  - Distributed hypertable member\n *  - Materialized hypertable (cagg) chunk\n *  - Internal compression table for hypertable\n *  - Internal compression table for materialized hypertable (cagg)\n *\n * Note that we want to count regular chunks and compressed chunks as part of\n * the same hypertable, although they are children of different tables\n * internally. The same applies to chunks that belong to a continuous\n * aggregate, although in that case there is actually a two-level indirection:\n * The main cagg view is the user-facing relation we'd like to collect stats\n * for, while its chunks are actually stored in a materialized hypertable,\n * and, in a second tier, in a compressed hypertable.\n */\nstatic void\nprocess_chunk(StatsContext *statsctx, StatsRelType chunk_reltype, Form_pg_class class,\n\t\t\t  const Chunk *chunk)\n{\n\tTelemetryStats *stats = statsctx->stats;\n\tFormData_compression_chunk_size comp_stats_data;\n\tForm_compression_chunk_size compr_stats = NULL;\n\n\tAssert(chunk);\n\n\t/*\n\t * Ignore compression chunks since we have a separate metadata table with\n\t * stats for them\n\t */\n\tif (chunk_reltype == RELTYPE_COMPRESSION_CHUNK)\n\t\treturn;\n\n\tif (get_chunk_compression_stats(statsctx, chunk, &comp_stats_data))\n\t\tcompr_stats = &comp_stats_data;\n\n\tswitch (chunk_reltype)\n\t{\n\t\tcase RELTYPE_CHUNK:\n\t\t\tadd_chunk_stats(&stats->hypertables, class, chunk, compr_stats);\n\t\t\tbreak;\n\t\tcase RELTYPE_MATERIALIZED_CHUNK:\n\t\t\tadd_chunk_stats(&stats->continuous_aggs.hyp, class, chunk, compr_stats);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tpg_unreachable();\n\t\t\tbreak;\n\t}\n}\n\nstatic bool\nis_pg_schema(Oid namespaceid)\n{\n\tstatic Oid information_schema_oid = InvalidOid;\n\n\tif (namespaceid == PG_CATALOG_NAMESPACE || namespaceid == PG_TOAST_NAMESPACE)\n\t\treturn true;\n\n\tif (!OidIsValid(information_schema_oid))\n\t\tinformation_schema_oid = get_namespace_oid(\"information_schema\", false);\n\n\treturn namespaceid == information_schema_oid;\n}\n\nstatic bool\nis_ts_schema(const Catalog *catalog, Oid namespaceid)\n{\n\tint i;\n\n\tfor (i = 0; i < _TS_MAX_SCHEMA; i++)\n\t{\n\t\tif (namespaceid != catalog->extension_schema_id[TS_INTERNAL_SCHEMA] &&\n\t\t\tnamespaceid == catalog->extension_schema_id[i])\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nstatic bool\nshould_ignore_relation(const Catalog *catalog, Form_pg_class class)\n{\n\treturn (is_pg_schema(class->relnamespace) || isAnyTempNamespace(class->relnamespace) ||\n\t\t\tis_ts_schema(catalog, class->relnamespace) || ts_is_catalog_table(class->oid));\n}\n\n/*\n * Scan the entire pg_class catalog table for all relations. For each\n * relation, classify it and gather stats based on the classification.\n */\nvoid\nts_telemetry_stats_gather(TelemetryStats *stats)\n{\n\tconst Catalog *catalog = ts_catalog_get();\n\tRelation rel;\n\tSysScanDesc scan;\n\tCache *htcache = ts_hypertable_cache_pin();\n\tMemoryContext oldmcxt, relmcxt;\n\tStatsContext statsctx = {\n\t\t.stats = stats,\n\t\t.snapshot = GetActiveSnapshot(),\n\t};\n\n\tMemSet(stats, 0, sizeof(*stats));\n\trel = table_open(RelationRelationId, AccessShareLock);\n\tscan = systable_beginscan(rel, ClassOidIndexId, false, NULL, 0, NULL);\n\trelmcxt = AllocSetContextCreate(CurrentMemoryContext, \"RelationStats\", ALLOCSET_DEFAULT_SIZES);\n\n\twhile (true)\n\t{\n\t\tHeapTuple tup;\n\t\tForm_pg_class class;\n\t\tStatsRelType reltype;\n\t\tconst Chunk *chunk = NULL;\n\t\tconst Hypertable *ht = NULL;\n\t\tconst ContinuousAgg *cagg = NULL;\n\n\t\ttup = systable_getnext(scan);\n\n\t\tif (!HeapTupleIsValid(tup))\n\t\t\tbreak;\n\n\t\tclass = (Form_pg_class) GETSTRUCT(tup);\n\n\t\tif (should_ignore_relation(catalog, class))\n\t\t\tcontinue;\n\n\t\t/* Lock the relation to ensure it does not disappear while we process\n\t\t * it */\n\t\tLockRelationOid(class->oid, AccessShareLock);\n\n\t\t/* Now that the lock is acquired, ensure the relation still\n\t\t * exists. Otherwise, ignore the relation and release the useless\n\t\t * lock. */\n\t\tif (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(class->oid)))\n\t\t{\n\t\t\tUnlockRelationOid(class->oid, AccessShareLock);\n\t\t\tcontinue;\n\t\t}\n\n\t\t/*\n\t\t * Use temporary per-relation memory context to not accumulate cruft\n\t\t * during processing of pg_class.\n\t\t */\n\t\toldmcxt = MemoryContextSwitchTo(relmcxt);\n\t\tMemoryContextReset(relmcxt);\n\n\t\treltype = classify_relation(class, htcache, &ht, &chunk, &cagg);\n\n\t\tDEBUG_WAITPOINT(\"telemetry_classify_relation\");\n\n\t\tswitch (reltype)\n\t\t{\n\t\t\tcase RELTYPE_HYPERTABLE:\n\t\t\t\tAssert(NULL != ht);\n\t\t\t\tprocess_hypertable(&stats->hypertables, class, ht);\n\t\t\t\tbreak;\n\t\t\tcase RELTYPE_TABLE:\n\t\t\t\tprocess_relation(&stats->tables.base, class);\n\t\t\t\tbreak;\n\t\t\tcase RELTYPE_PARTITIONED_TABLE:\n\t\t\t\tprocess_relation(&stats->partitioned_tables.storage.base, class);\n\t\t\t\tbreak;\n\t\t\tcase RELTYPE_CHUNK:\n\t\t\tcase RELTYPE_COMPRESSION_CHUNK:\n\t\t\tcase RELTYPE_MATERIALIZED_CHUNK:\n\t\t\t\tAssert(NULL != chunk);\n\t\t\t\tprocess_chunk(&statsctx, reltype, class, chunk);\n\t\t\t\tbreak;\n\t\t\tcase RELTYPE_PARTITION:\n\t\t\t\tprocess_partition(&stats->partitioned_tables, class, false);\n\t\t\t\tbreak;\n\t\t\tcase RELTYPE_VIEW:\n\t\t\t\t/* Filter internal cagg views */\n\t\t\t\tif (class->relnamespace != catalog->extension_schema_id[TS_INTERNAL_SCHEMA])\n\t\t\t\t\tprocess_relation(&stats->views, class);\n\t\t\t\tbreak;\n\t\t\tcase RELTYPE_MATVIEW:\n\t\t\t\tprocess_relation(&stats->materialized_views.base, class);\n\t\t\t\tbreak;\n\t\t\tcase RELTYPE_CONTINUOUS_AGG:\n\t\t\t\tAssert(NULL != cagg);\n\t\t\t\tprocess_continuous_agg(&stats->continuous_aggs, class, cagg);\n\t\t\t\tbreak;\n\t\t\t\t/* No stats collected for types below */\n\t\t\tcase RELTYPE_COMPRESSION_HYPERTABLE:\n\t\t\tcase RELTYPE_MATERIALIZED_HYPERTABLE:\n\t\t\tcase RELTYPE_OTHER:\n\t\t\t\tbreak;\n\t\t}\n\n\t\tUnlockRelationOid(class->oid, AccessShareLock);\n\t\tMemoryContextSwitchTo(oldmcxt);\n\t}\n\n\tsystable_endscan(scan);\n\ttable_close(rel, AccessShareLock);\n\tts_cache_release(&htcache);\n\tMemoryContextDelete(relmcxt);\n}\n"
  },
  {
    "path": "src/telemetry/stats.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n#include <postgres.h>\n\n#include \"utils.h\"\n\ntypedef enum StatsRelType\n{\n\tRELTYPE_HYPERTABLE,\n\tRELTYPE_MATERIALIZED_HYPERTABLE,\n\tRELTYPE_COMPRESSION_HYPERTABLE,\n\tRELTYPE_CONTINUOUS_AGG,\n\tRELTYPE_TABLE,\n\tRELTYPE_PARTITIONED_TABLE,\n\tRELTYPE_PARTITION,\n\tRELTYPE_VIEW,\n\tRELTYPE_MATVIEW,\n\tRELTYPE_CHUNK,\n\tRELTYPE_COMPRESSION_CHUNK,\n\tRELTYPE_MATERIALIZED_CHUNK,\n\tRELTYPE_OTHER,\n} StatsRelType;\n\ntypedef enum StatsType\n{\n\tSTATS_TYPE_BASE,\n\tSTATS_TYPE_STORAGE,\n\tSTATS_TYPE_HYPER,\n\tSTATS_TYPE_CAGG,\n} StatsType;\n\ntypedef struct BaseStats\n{\n\tint64 relcount;\n\tint64 reltuples;\n} BaseStats;\n\ntypedef struct StorageStats\n{\n\tBaseStats base;\n\tRelationSize relsize;\n} StorageStats;\n\ntypedef struct HyperStats\n{\n\tStorageStats storage;\n\tint64 replicated_hypertable_count;\n\tint64 child_count;\n\tint64 replica_chunk_count; /* only includes \"additional\" replica chunks */\n\tint64 compressed_chunk_count;\n\tint64 compressed_hypertable_count;\n\tint64 compressed_size;\n\tint64 compressed_heap_size;\n\tint64 compressed_indexes_size;\n\tint64 compressed_toast_size;\n\tint64 compressed_row_count;\n\tint64 compressed_row_frozen_immediately_count;\n\tint64 uncompressed_heap_size;\n\tint64 uncompressed_indexes_size;\n\tint64 uncompressed_toast_size;\n\tint64 uncompressed_row_count;\n} HyperStats;\n\ntypedef struct CaggStats\n{\n\tHyperStats hyp; /* \"hyper\" as field name leads to name conflict on Windows compiler */\n\tint64 uses_real_time_aggregation_count;\n\tint64 finalized;\n\tint64 nested;\n} CaggStats;\n\ntypedef struct TelemetryStats\n{\n\tHyperStats hypertables;\n\tHyperStats partitioned_tables;\n\tStorageStats tables;\n\tStorageStats materialized_views;\n\tCaggStats continuous_aggs;\n\tBaseStats views;\n} TelemetryStats;\n\ntypedef struct TelemetryJobStats\n{\n\tint64 total_runs;\n\tint64 total_successes;\n\tint64 total_failures;\n\tint64 total_crashes;\n\tint32 max_consecutive_failures;\n\tint32 max_consecutive_crashes;\n\tInterval *total_duration;\n\tInterval *total_duration_failures;\n} TelemetryJobStats;\n\nextern void ts_telemetry_stats_gather(TelemetryStats *stats);\n"
  },
  {
    "path": "src/telemetry/telemetry.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/xact.h>\n#include <catalog/pg_collation.h>\n#include <commands/extension.h>\n#include <fmgr.h>\n#include <miscadmin.h>\n#include <storage/ipc.h>\n#include <utils/builtins.h>\n#include <utils/json.h>\n#include <utils/jsonb.h>\n#include <utils/regproc.h>\n#include <utils/snapmgr.h>\n\n#include \"compat/compat.h\"\n#include \"bgw_policy/policy.h\"\n#include \"config.h\"\n#include \"extension.h\"\n#include \"functions.h\"\n#include \"guc.h\"\n#include \"hypertable.h\"\n#include \"jsonb_utils.h\"\n#include \"license_guc.h\"\n#include \"net/http.h\"\n#include \"replication.h\"\n#include \"stats.h\"\n#include \"telemetry.h\"\n#include \"telemetry_metadata.h\"\n#include \"ts_catalog/compression_chunk_size.h\"\n#include \"ts_catalog/metadata.h\"\n#include \"version.h\"\n\n#include \"cross_module_fn.h\"\n\n#include <executor/spi.h>\n\n#define TS_TELEMETRY_VERSION 2\n#define TS_VERSION_JSON_FIELD \"current_timescaledb_version\"\n#define TS_IS_UPTODATE_JSON_FIELD \"is_up_to_date\"\n\n/*  HTTP request details */\n#define MAX_REQUEST_SIZE 4096\n\n#define REQ_TELEMETRY_VERSION \"telemetry_version\"\n#define REQ_DB_UUID \"db_uuid\"\n#define REQ_EXPORTED_DB_UUID \"exported_db_uuid\"\n#define REQ_INSTALL_TIME \"installed_time\"\n#define REQ_INSTALL_METHOD \"install_method\"\n#define REQ_OS \"os_name\"\n#define REQ_OS_VERSION \"os_version\"\n#define REQ_OS_RELEASE \"os_release\"\n#define REQ_OS_VERSION_PRETTY \"os_name_pretty\"\n#define REQ_PS_VERSION \"postgresql_version\"\n#define REQ_TS_VERSION \"timescaledb_version\"\n#define REQ_BUILD_OS \"build_os_name\"\n#define REQ_BUILD_OS_VERSION \"build_os_version\"\n#define REQ_BUILD_ARCHITECTURE_BIT_SIZE \"build_architecture_bit_size\"\n#define REQ_BUILD_ARCHITECTURE \"build_architecture\"\n#define REQ_DATA_VOLUME \"data_volume\"\n\n#define REQ_NUM_POLICY_CAGG_FIXED \"num_continuous_aggs_policies_fixed\"\n#define REQ_NUM_POLICY_COMPRESSION_FIXED \"num_compression_policies_fixed\"\n#define REQ_NUM_POLICY_REORDER_FIXED \"num_reorder_policies_fixed\"\n#define REQ_NUM_POLICY_RETENTION_FIXED \"num_retention_policies_fixed\"\n#define REQ_NUM_USER_DEFINED_ACTIONS_FIXED \"num_user_defined_actions_fixed\"\n#define REQ_NUM_POLICY_CAGG \"num_continuous_aggs_policies\"\n#define REQ_NUM_POLICY_COMPRESSION \"num_compression_policies\"\n#define REQ_NUM_POLICY_REORDER \"num_reorder_policies\"\n#define REQ_NUM_POLICY_RETENTION \"num_retention_policies\"\n#define REQ_NUM_USER_DEFINED_ACTIONS \"num_user_defined_actions\"\n#define REQ_RELATED_EXTENSIONS \"related_extensions\"\n#define REQ_METADATA \"db_metadata\"\n#define REQ_TELEMETRY_EVENT \"db_telemetry_events\"\n#define REQ_LICENSE_EDITION_APACHE \"apache_only\"\n#define REQ_LICENSE_EDITION_COMMUNITY \"community\"\n#define REQ_TS_LAST_TUNE_TIME \"last_tuned_time\"\n#define REQ_TS_LAST_TUNE_VERSION \"last_tuned_version\"\n#define REQ_INSTANCE_METADATA \"instance_metadata\"\n#define REQ_TS_TELEMETRY_CLOUD \"cloud\"\n\n#define REQ_NUM_WAL_SENDERS \"num_wal_senders\"\n#define REQ_IS_WAL_RECEIVER \"is_wal_receiver\"\n\n#define PG_PROMETHEUS \"pg_prometheus\"\n#define PG_VECTOR \"vector\"\n#define TS_AI \"ai\"\n#define TS_VECTORSCALE \"vectorscale\"\n#define PROMSCALE \"promscale\"\n#define POSTGIS \"postgis\"\n#define TIMESCALE_ANALYTICS \"timescale_analytics\"\n#define TIMESCALEDB_TOOLKIT \"timescaledb_toolkit\"\n\n#define REQ_JOB_STATS_BY_JOB_TYPE \"stats_by_job_type\"\n#define REQ_NUM_ERR_BY_SQLERRCODE \"errors_by_sqlerrcode\"\n\nstatic const char *related_extensions[] = {\n\tPG_PROMETHEUS,\t\t PROMSCALE, POSTGIS, TIMESCALE_ANALYTICS,\n\tTIMESCALEDB_TOOLKIT, PG_VECTOR, TS_AI,\t TS_VECTORSCALE,\n};\n\n/* This function counts background worker jobs by type. */\nstatic BgwJobTypeCount\nbgw_job_type_counts()\n{\n\tListCell *lc;\n\tList *jobs = ts_bgw_job_get_all(sizeof(BgwJob), CurrentMemoryContext);\n\tBgwJobTypeCount counts = { 0 };\n\n\tforeach (lc, jobs)\n\t{\n\t\tBgwJob *job = lfirst(lc);\n\n\t\tif (namestrcmp(&job->fd.proc_schema, FUNCTIONS_SCHEMA_NAME) == 0)\n\t\t{\n\t\t\tif (namestrcmp(&job->fd.proc_name, \"policy_refresh_continuous_aggregate\") == 0)\n\t\t\t{\n\t\t\t\tif (job->fd.fixed_schedule)\n\t\t\t\t\tcounts.policy_cagg_fixed++;\n\t\t\t\telse\n\t\t\t\t\tcounts.policy_cagg++;\n\t\t\t}\n\t\t\telse if (namestrcmp(&job->fd.proc_name, \"policy_compression\") == 0)\n\t\t\t{\n\t\t\t\tif (job->fd.fixed_schedule)\n\t\t\t\t\tcounts.policy_compression_fixed++;\n\t\t\t\telse\n\t\t\t\t\tcounts.policy_compression++;\n\t\t\t}\n\t\t\telse if (namestrcmp(&job->fd.proc_name, \"policy_reorder\") == 0)\n\t\t\t{\n\t\t\t\tif (job->fd.fixed_schedule)\n\t\t\t\t\tcounts.policy_reorder_fixed++;\n\t\t\t\telse\n\t\t\t\t\tcounts.policy_reorder++;\n\t\t\t}\n\t\t\telse if (namestrcmp(&job->fd.proc_name, \"policy_retention\") == 0)\n\t\t\t{\n\t\t\t\tif (job->fd.fixed_schedule)\n\t\t\t\t\tcounts.policy_retention_fixed++;\n\t\t\t\telse\n\t\t\t\t\tcounts.policy_retention++;\n\t\t\t}\n\t\t\telse if (namestrcmp(&job->fd.proc_name, \"policy_telemetry\") == 0)\n\t\t\t\tcounts.policy_telemetry++;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (job->fd.fixed_schedule)\n\t\t\t\tcounts.user_defined_action_fixed++;\n\t\t\telse\n\t\t\t\tcounts.user_defined_action++;\n\t\t}\n\t}\n\n\treturn counts;\n}\n\nstatic bool\nchar_in_valid_version_digits(const char c)\n{\n\tswitch (c)\n\t{\n\t\tcase '.':\n\t\tcase '-':\n\t\t\treturn true;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\n/*\n * Makes sure the server version string is less than MAX_VERSION_STR_LEN\n * chars, and all digits are \"valid\". Valid chars are either\n * alphanumeric or in the array valid_version_digits above.\n *\n * Returns false if either of these conditions are false.\n */\nbool\nts_validate_server_version(const char *json, VersionResult *result)\n{\n\tDatum version = DirectFunctionCall2(json_object_field_text,\n\t\t\t\t\t\t\t\t\t\tCStringGetTextDatum(json),\n\t\t\t\t\t\t\t\t\t\tPointerGetDatum(cstring_to_text(TS_VERSION_JSON_FIELD)));\n\n\tmemset(result, 0, sizeof(VersionResult));\n\n\tresult->versionstr = text_to_cstring(DatumGetTextPP(version));\n\n\tif (result->versionstr == NULL)\n\t{\n\t\tresult->errhint = \"no version string in response\";\n\t\treturn false;\n\t}\n\n\tif (strlen(result->versionstr) > MAX_VERSION_STR_LEN)\n\t{\n\t\tresult->errhint = \"version string is too long\";\n\t\treturn false;\n\t}\n\n\tfor (size_t i = 0; i < strlen(result->versionstr); i++)\n\t{\n\t\tif (!isalpha(result->versionstr[i]) && !isdigit(result->versionstr[i]) &&\n\t\t\t!char_in_valid_version_digits(result->versionstr[i]))\n\t\t{\n\t\t\tresult->errhint = \"version string has invalid characters\";\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/*\n * Parse the JSON response from the TS endpoint. There should be a field\n * called \"current_timescaledb_version\". Check this against the local\n * version, and notify the user if it is behind.\n */\nvoid\nts_check_version_response(const char *json)\n{\n\tVersionResult result;\n\tbool is_uptodate = DatumGetBool(\n\t\tDirectFunctionCall2Coll(texteq,\n\t\t\t\t\t\t\t\tC_COLLATION_OID,\n\t\t\t\t\t\t\t\tDirectFunctionCall2Coll(json_object_field_text,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tC_COLLATION_OID,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tCStringGetTextDatum(json),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tPointerGetDatum(cstring_to_text(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tTS_IS_UPTODATE_JSON_FIELD))),\n\t\t\t\t\t\t\t\tPointerGetDatum(cstring_to_text(\"true\"))));\n\n\tif (is_uptodate)\n\t\telog(NOTICE, \"the \\\"%s\\\" extension is up-to-date\", EXTENSION_NAME);\n\telse\n\t{\n\t\tif (!ts_validate_server_version(json, &result))\n\t\t{\n\t\t\telog(NOTICE, \"server did not return a valid TimescaleDB version: %s\", result.errhint);\n\t\t\treturn;\n\t\t}\n\n\t\tereport(LOG,\n\t\t\t\t(errmsg(\"the \\\"%s\\\" extension is not up-to-date\", EXTENSION_NAME),\n\t\t\t\t errhint(\"The most up-to-date version is %s, the installed version is %s.\",\n\t\t\t\t\t\t result.versionstr,\n\t\t\t\t\t\t TIMESCALEDB_VERSION_MOD)));\n\t}\n}\n\nstatic int32\nget_architecture_bit_size()\n{\n\treturn BUILD_POINTER_BYTES * 8;\n}\n\nstatic void\nadd_job_counts(JsonbParseState *state)\n{\n\tBgwJobTypeCount counts = bgw_job_type_counts();\n\n\tts_jsonb_add_int32(state, REQ_NUM_POLICY_CAGG, counts.policy_cagg);\n\tts_jsonb_add_int32(state, REQ_NUM_POLICY_CAGG_FIXED, counts.policy_cagg_fixed);\n\tts_jsonb_add_int32(state, REQ_NUM_POLICY_COMPRESSION, counts.policy_compression);\n\tts_jsonb_add_int32(state, REQ_NUM_POLICY_COMPRESSION_FIXED, counts.policy_compression_fixed);\n\tts_jsonb_add_int32(state, REQ_NUM_POLICY_REORDER, counts.policy_reorder);\n\tts_jsonb_add_int32(state, REQ_NUM_POLICY_REORDER_FIXED, counts.policy_reorder_fixed);\n\tts_jsonb_add_int32(state, REQ_NUM_POLICY_RETENTION, counts.policy_retention);\n\tts_jsonb_add_int32(state, REQ_NUM_POLICY_RETENTION_FIXED, counts.policy_retention_fixed);\n\tts_jsonb_add_int32(state, REQ_NUM_USER_DEFINED_ACTIONS, counts.user_defined_action);\n\tts_jsonb_add_int32(state, REQ_NUM_USER_DEFINED_ACTIONS_FIXED, counts.user_defined_action_fixed);\n}\n\nstatic JsonbValue *\nadd_errors_by_sqlerrcode_internal(JsonbParseState *parse_state, const char *job_type,\n\t\t\t\t\t\t\t\t  Jsonb *sqlerrs_jsonb)\n{\n\tJsonbIterator *it;\n\tJsonbIteratorToken type;\n\tJsonbValue val;\n\tJsonbValue *ret;\n\tJsonbValue key = {\n\t\t.type = jbvString,\n\t\t.val.string.val = pstrdup(job_type),\n\t\t.val.string.len = strlen(job_type),\n\t};\n\n\tret = pushJsonbValue(&parse_state, WJB_KEY, &key);\n\tret = pushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);\n\n\t/* we don't expect nested values here */\n\tit = JsonbIteratorInit(&sqlerrs_jsonb->root);\n\ttype = JsonbIteratorNext(&it, &val, true /*skip_nested*/);\n\tif (type != WJB_BEGIN_OBJECT)\n\t\telog(ERROR, \"invalid JSON format\");\n\twhile ((type = JsonbIteratorNext(&it, &val, true)))\n\t{\n\t\tconst char *errcode;\n\t\tint64 errcnt;\n\n\t\tif (type == WJB_END_OBJECT)\n\t\t\tbreak;\n\t\telse if (type == WJB_KEY)\n\t\t{\n\t\t\terrcode = pnstrdup(val.val.string.val, val.val.string.len);\n\t\t\t/* get the corresponding value for this key */\n\t\t\ttype = JsonbIteratorNext(&it, &val, true);\n\t\t\tif (type != WJB_VALUE)\n\t\t\t\telog(ERROR, \"unexpected jsonb type\");\n\t\t\terrcnt =\n\t\t\t\tDatumGetInt64(DirectFunctionCall1(numeric_int8, NumericGetDatum(val.val.numeric)));\n\t\t\tts_jsonb_add_int64(parse_state, errcode, errcnt);\n\t\t}\n\t\telse\n\t\t\telog(ERROR, \"unexpected jsonb type\");\n\t}\n\n\tret = pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);\n\treturn ret;\n}\n/* this function queries the database through SPI and gets back a set of records\n that look like (job_type TEXT, jsonb_object_agg JSONB).\n For example, (user_defined_action, {\"P0001\": 2, \"42883\": 5})\n (we are expecting about 6 rows depending\n on how we write the query and if we exclude any jobs)\n Then for each returned row adds a new kv pair to the jsonb,\n which looks like \"job_type\": {\"errtype1\": errcnt1, ...} */\nstatic void\nadd_errors_by_sqlerrcode(JsonbParseState *parse_state)\n{\n\tint res;\n\tStringInfoData command;\n\tMemoryContext orig_context = CurrentMemoryContext;\n\n\tconst char *command_string = \"SELECT \"\n\t\t\t\t\t\t\t\t \"job_type, jsonb_object_agg(sqlerrcode, count) \"\n\t\t\t\t\t\t\t\t \"FROM\"\n\t\t\t\t\t\t\t\t \"(\"\n\t\t\t\t\t\t\t\t \"\tSELECT (\"\n\t\t\t\t\t\t\t\t \"\t\tCASE \"\n\t\t\t\t\t\t\t\t \"\t\t\tWHEN proc_schema = \\'_timescaledb_functions\\'\"\n\t\t\t\t\t\t\t\t \" \t\t\tAND proc_name ~ \"\n\t\t\t\t\t\t\t\t \"\\'^policy_(retention|compression|reorder|refresh_continuous_\"\n\t\t\t\t\t\t\t\t \"aggregate|telemetry|job_error_retention)$\\' \"\n\t\t\t\t\t\t\t\t \"\t\t\tTHEN proc_name \"\n\t\t\t\t\t\t\t\t \"\t\t\tELSE \\'user_defined_action\\'\"\n\t\t\t\t\t\t\t\t \"\t\tEND\"\n\t\t\t\t\t\t\t\t \"\t) as job_type, \"\n\t\t\t\t\t\t\t\t \"\tsqlerrcode, \"\n\t\t\t\t\t\t\t\t \"\tpg_catalog.COUNT(*) \"\n\t\t\t\t\t\t\t\t \"\tFROM \"\n\t\t\t\t\t\t\t\t \"\ttimescaledb_information.job_errors \"\n\t\t\t\t\t\t\t\t \"\tWHERE sqlerrcode IS NOT NULL \"\n\t\t\t\t\t\t\t\t \"\tGROUP BY job_type, sqlerrcode \"\n\t\t\t\t\t\t\t\t \"\tORDER BY job_type\"\n\t\t\t\t\t\t\t\t \") q \"\n\t\t\t\t\t\t\t\t \"GROUP BY q.job_type\";\n\n\tif (SPI_connect() != SPI_OK_CONNECT)\n\t\telog(ERROR, \"could not connect to SPI\");\n\n\t/* Lock down search_path */\n\tint save_nestlevel = NewGUCNestLevel();\n\tRestrictSearchPath();\n\n\tinitStringInfo(&command);\n\n\tappendStringInfoString(&command, command_string);\n\tres = SPI_execute(command.data, true /*read only*/, 0 /* count */);\n\tif (res < 0)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t (errmsg(\"could not get errors by sqlerrcode and job type\"))));\n\n\t/* we expect about 6 rows returned, each row is a record (TEXT, JSONB) */\n\tfor (uint64 i = 0; i < SPI_processed; i++)\n\t{\n\t\tDatum record_jobtype, record_jsonb;\n\t\tbool isnull_jobtype, isnull_jsonb;\n\n\t\trecord_jobtype =\n\t\t\tSPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 1, &isnull_jobtype);\n\t\tif (isnull_jobtype)\n\t\t\telog(ERROR, \"null job type returned\");\n\t\trecord_jsonb =\n\t\t\tSPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 2, &isnull_jsonb);\n\t\t/* this jsonb looks like {\"P0001\": 32, \"42883\": 6} */\n\t\tJsonb *sqlerrs_jsonb = isnull_jsonb ? NULL : DatumGetJsonbP(record_jsonb);\n\n\t\tif (sqlerrs_jsonb == NULL)\n\t\t\tcontinue;\n\t\t/* the jsonb object cannot be created in the SPI context or it will be lost */\n\t\tMemoryContext spi_context = MemoryContextSwitchTo(orig_context);\n\t\tadd_errors_by_sqlerrcode_internal(parse_state,\n\t\t\t\t\t\t\t\t\t\t  TextDatumGetCString(record_jobtype),\n\t\t\t\t\t\t\t\t\t\t  sqlerrs_jsonb);\n\t\tMemoryContextSwitchTo(spi_context);\n\t}\n\n\t/* Restore search_path */\n\tAtEOXact_GUC(false, save_nestlevel);\n\n\tres = SPI_finish();\n\n\tAssert(res == SPI_OK_FINISH);\n}\n\nstatic JsonbValue *\nadd_job_stats_internal(JsonbParseState *state, const char *job_type, TelemetryJobStats *stats)\n{\n\tJsonbValue key = {\n\t\t.type = jbvString,\n\t\t.val.string.val = pstrdup(job_type),\n\t\t.val.string.len = strlen(job_type),\n\t};\n\tpushJsonbValue(&state, WJB_KEY, &key);\n\tpushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);\n\n\tts_jsonb_add_int64(state, \"total_runs\", stats->total_runs);\n\tts_jsonb_add_int64(state, \"total_successes\", stats->total_successes);\n\tts_jsonb_add_int64(state, \"total_failures\", stats->total_failures);\n\tts_jsonb_add_int64(state, \"total_crashes\", stats->total_crashes);\n\tts_jsonb_add_int32(state, \"max_consecutive_failures\", stats->max_consecutive_failures);\n\tts_jsonb_add_int32(state, \"max_consecutive_crashes\", stats->max_consecutive_crashes);\n\tts_jsonb_add_interval(state, \"total_duration\", stats->total_duration);\n\tts_jsonb_add_interval(state, \"total_duration_failures\", stats->total_duration_failures);\n\n\treturn pushJsonbValue(&state, WJB_END_OBJECT, NULL);\n}\n\nstatic void\nadd_job_stats_by_job_type(JsonbParseState *parse_state)\n{\n\tStringInfoData command;\n\tint res;\n\tMemoryContext orig_context = CurrentMemoryContext;\n\tSPITupleTable *tuptable = NULL;\n\n\tconst char *command_string =\n\t\t\"SELECT (\"\n\t\t\"\tCASE \"\n\t\t\"\t\tWHEN j.proc_schema = \\'_timescaledb_functions\\' AND j.proc_name ~ \"\n\t\t\"\\'^policy_(retention|compression|reorder|refresh_continuous_aggregate|telemetry|job_stat_\"\n\t\t\"history_retention)$\\' \"\n\t\t\"\t\tTHEN j.proc_name::TEXT \"\n\t\t\"\t\tELSE \\'user_defined_action\\' \"\n\t\t\"\tEND\"\n\t\t\")  AS job_type, \"\n\t\t\"\tSUM(total_runs)::BIGINT AS total_runs, \"\n\t\t\"\tSUM(total_successes)::BIGINT AS total_successes, \"\n\t\t\"\tSUM(total_failures)::BIGINT AS total_failures, \"\n\t\t\"\tSUM(total_crashes)::BIGINT AS total_crashes, \"\n\t\t\"\tSUM(total_duration) AS total_duration, \"\n\t\t\"\tSUM(total_duration_failures) AS total_duration_failures, \"\n\t\t\"\tMAX(consecutive_failures) AS max_consecutive_failures, \"\n\t\t\"\tMAX(consecutive_crashes) AS max_consecutive_crashes \"\n\t\t\"FROM \"\n\t\t\"\t_timescaledb_internal.bgw_job_stat s \"\n\t\t\"\tJOIN _timescaledb_catalog.bgw_job j on j.id = s.job_id \"\n\t\t\"GROUP BY job_type \"\n\t\t\"ORDER BY job_type\";\n\n\tif (SPI_connect() != SPI_OK_CONNECT)\n\t\telog(ERROR, \"could not connect to SPI\");\n\n\t/* Lock down search_path */\n\tint save_nestlevel = NewGUCNestLevel();\n\tRestrictSearchPath();\n\n\tinitStringInfo(&command);\n\n\tappendStringInfoString(&command, command_string);\n\tres = SPI_execute(command.data, true /* read_only */, 0 /*count*/);\n\tif (res < 0)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t (errmsg(\"could not get job statistics by job type\"))));\n\t/*\n\t * a row returned looks like this:\n\t * (job_type, total_runs, total_successes, total_failures, total_crashes, total_duration,\n\t * total_duration_failures, max_consec_fails, max_consec_crashes)\n\t * (\"policy_telemetry\", 12, 10, 1, 1, 00:00:11, 00:00:01, 1, 1)\n\t */\n\tfor (uint64 i = 0; i < SPI_processed; i++)\n\t{\n\t\ttuptable = SPI_tuptable;\n\t\tTupleDesc tupdesc = tuptable->tupdesc;\n\t\tDatum jobtype_datum;\n\t\tDatum total_runs, total_successes, total_failures, total_crashes;\n\t\tDatum total_duration, total_duration_failures, max_consec_crashes, max_consec_fails;\n\n\t\tbool isnull_jobtype, isnull_runs, isnull_successes, isnull_failures, isnull_crashes;\n\t\tbool isnull_duration, isnull_duration_failures, isnull_consec_crashes, isnull_consec_fails;\n\n\t\tjobtype_datum =\n\t\t\tSPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 1, &isnull_jobtype);\n\t\tif (isnull_jobtype)\n\t\t\telog(ERROR, \"null job type returned\");\n\t\ttotal_runs = SPI_getbinval(tuptable->vals[i], tupdesc, 2, &isnull_runs);\n\t\ttotal_successes = SPI_getbinval(tuptable->vals[i], tupdesc, 3, &isnull_successes);\n\t\ttotal_failures = SPI_getbinval(tuptable->vals[i], tupdesc, 4, &isnull_failures);\n\t\ttotal_crashes = SPI_getbinval(tuptable->vals[i], tupdesc, 5, &isnull_crashes);\n\t\ttotal_duration = SPI_getbinval(tuptable->vals[i], tupdesc, 6, &isnull_duration);\n\t\ttotal_duration_failures =\n\t\t\tSPI_getbinval(tuptable->vals[i], tupdesc, 7, &isnull_duration_failures);\n\t\tmax_consec_fails = SPI_getbinval(tuptable->vals[i], tupdesc, 8, &isnull_consec_fails);\n\t\tmax_consec_crashes = SPI_getbinval(tuptable->vals[i], tupdesc, 9, &isnull_consec_crashes);\n\n\t\tif (isnull_jobtype || isnull_runs || isnull_successes || isnull_failures ||\n\t\t\tisnull_crashes || isnull_duration || isnull_consec_crashes || isnull_consec_fails)\n\t\t{\n\t\t\telog(ERROR, \"null record field returned\");\n\t\t}\n\n\t\tMemoryContext spi_context = MemoryContextSwitchTo(orig_context);\n\t\tTelemetryJobStats stats = { .total_runs = DatumGetInt64(total_runs),\n\t\t\t\t\t\t\t\t\t.total_successes = DatumGetInt64(total_successes),\n\t\t\t\t\t\t\t\t\t.total_failures = DatumGetInt64(total_failures),\n\t\t\t\t\t\t\t\t\t.total_crashes = DatumGetInt64(total_crashes),\n\t\t\t\t\t\t\t\t\t.max_consecutive_failures = DatumGetInt32(max_consec_fails),\n\t\t\t\t\t\t\t\t\t.max_consecutive_crashes = DatumGetInt32(max_consec_crashes),\n\t\t\t\t\t\t\t\t\t.total_duration = DatumGetIntervalP(total_duration),\n\t\t\t\t\t\t\t\t\t.total_duration_failures =\n\t\t\t\t\t\t\t\t\t\tDatumGetIntervalP(total_duration_failures) };\n\t\tadd_job_stats_internal(parse_state, TextDatumGetCString(jobtype_datum), &stats);\n\t\tMemoryContextSwitchTo(spi_context);\n\t}\n\n\t/* Restore search_path */\n\tAtEOXact_GUC(false, save_nestlevel);\n\n\tres = SPI_finish();\n\tAssert(res == SPI_OK_FINISH);\n}\n\nstatic int64\nget_database_size()\n{\n\treturn DatumGetInt64(DirectFunctionCall1(pg_database_size_oid, ObjectIdGetDatum(MyDatabaseId)));\n}\n\nstatic void\nadd_related_extensions(JsonbParseState *state)\n{\n\tpushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);\n\n\tfor (size_t i = 0; i < sizeof(related_extensions) / sizeof(char *); i++)\n\t{\n\t\tconst char *ext = related_extensions[i];\n\n\t\tts_jsonb_add_bool(state, ext, OidIsValid(get_extension_oid(ext, true)));\n\t}\n\n\tpushJsonbValue(&state, WJB_END_OBJECT, NULL);\n}\n\nstatic char *\nget_pgversion_string()\n{\n\tStringInfoData buf;\n\tint major, patch;\n\n\t/*\n\t * We have to read the server version from GUC and not use any of\n\t * the macros. By using any of the macros we would get the version\n\t * the extension is compiled against instead of the version actually\n\t * running.\n\t */\n\tchar *server_version_num_guc = GetConfigOptionByName(\"server_version_num\", NULL, false);\n\tlong server_version_num = strtol(server_version_num_guc, NULL, 10);\n\n\tmajor = server_version_num / 10000;\n\tpatch = server_version_num % 100;\n\n\tAssert(major >= PG_MAJOR_MIN);\n\tinitStringInfo(&buf);\n\tappendStringInfo(&buf, \"%d.%d\", major, patch);\n\n\treturn buf.data;\n}\n\n#define ISO8601_FORMAT \"YYYY-MM-DD\\\"T\\\"HH24:MI:SSOF\"\n\nstatic char *\nformat_iso8601(Datum value)\n{\n\treturn TextDatumGetCString(\n\t\tDirectFunctionCall2(timestamptz_to_char, value, CStringGetTextDatum(ISO8601_FORMAT)));\n}\n\n#define REQ_RELKIND_COUNT \"num_relations\"\n#define REQ_RELKIND_RELTUPLES \"num_reltuples\"\n\n#define REQ_RELKIND_HEAP_SIZE \"heap_size\"\n#define REQ_RELKIND_TOAST_SIZE \"toast_size\"\n#define REQ_RELKIND_INDEXES_SIZE \"indexes_size\"\n\n#define REQ_RELKIND_CHILDREN \"num_children\"\n#define REQ_RELKIND_REPLICA_CHUNKS \"num_replica_chunks\"\n#define REQ_RELKIND_COMPRESSED_CHUNKS \"num_compressed_chunks\"\n#define REQ_RELKIND_COMPRESSED_HYPERTABLES \"num_compressed_hypertables\"\n#define REQ_RELKIND_COMPRESSED_CAGGS \"num_compressed_caggs\"\n\n#define REQ_RELKIND_UNCOMPRESSED_HEAP_SIZE \"uncompressed_heap_size\"\n#define REQ_RELKIND_UNCOMPRESSED_TOAST_SIZE \"uncompressed_toast_size\"\n#define REQ_RELKIND_UNCOMPRESSED_INDEXES_SIZE \"uncompressed_indexes_size\"\n#define REQ_RELKIND_UNCOMPRESSED_ROWCOUNT \"uncompressed_row_count\"\n#define REQ_RELKIND_COMPRESSED_HEAP_SIZE \"compressed_heap_size\"\n#define REQ_RELKIND_COMPRESSED_TOAST_SIZE \"compressed_toast_size\"\n#define REQ_RELKIND_COMPRESSED_INDEXES_SIZE \"compressed_indexes_size\"\n#define REQ_RELKIND_COMPRESSED_ROWCOUNT \"compressed_row_count\"\n#define REQ_RELKIND_COMPRESSED_ROWCOUNT_FROZEN_IMMEDIATELY \"compressed_row_count_frozen_immediately\"\n\n#define REQ_RELKIND_CAGG_USES_REAL_TIME_AGGREGATION_COUNT \"num_caggs_using_real_time_aggregation\"\n#define REQ_RELKIND_CAGG_FINALIZED \"num_caggs_finalized\"\n#define REQ_RELKIND_CAGG_NESTED \"num_caggs_nested\"\n\nstatic JsonbValue *\nadd_compression_stats_object(JsonbParseState *parse_state, StatsRelType reltype,\n\t\t\t\t\t\t\t const HyperStats *hs)\n{\n\tJsonbValue name = {\n\t\t.type = jbvString,\n\t\t.val.string.val = pstrdup(\"compression\"),\n\t\t.val.string.len = strlen(\"compression\"),\n\t};\n\tpushJsonbValue(&parse_state, WJB_KEY, &name);\n\tpushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);\n\n\tts_jsonb_add_int64(parse_state, REQ_RELKIND_COMPRESSED_CHUNKS, hs->compressed_chunk_count);\n\n\tif (reltype == RELTYPE_CONTINUOUS_AGG)\n\t\tts_jsonb_add_int64(parse_state,\n\t\t\t\t\t\t   REQ_RELKIND_COMPRESSED_CAGGS,\n\t\t\t\t\t\t   hs->compressed_hypertable_count);\n\telse\n\t\tts_jsonb_add_int64(parse_state,\n\t\t\t\t\t\t   REQ_RELKIND_COMPRESSED_HYPERTABLES,\n\t\t\t\t\t\t   hs->compressed_hypertable_count);\n\n\tts_jsonb_add_int64(parse_state, REQ_RELKIND_COMPRESSED_ROWCOUNT, hs->compressed_row_count);\n\tts_jsonb_add_int64(parse_state, REQ_RELKIND_COMPRESSED_HEAP_SIZE, hs->compressed_heap_size);\n\tts_jsonb_add_int64(parse_state, REQ_RELKIND_COMPRESSED_TOAST_SIZE, hs->compressed_toast_size);\n\tts_jsonb_add_int64(parse_state,\n\t\t\t\t\t   REQ_RELKIND_COMPRESSED_INDEXES_SIZE,\n\t\t\t\t\t   hs->compressed_indexes_size);\n\tts_jsonb_add_int64(parse_state,\n\t\t\t\t\t   REQ_RELKIND_COMPRESSED_ROWCOUNT_FROZEN_IMMEDIATELY,\n\t\t\t\t\t   hs->compressed_row_frozen_immediately_count);\n\tts_jsonb_add_int64(parse_state, REQ_RELKIND_UNCOMPRESSED_ROWCOUNT, hs->uncompressed_row_count);\n\tts_jsonb_add_int64(parse_state, REQ_RELKIND_UNCOMPRESSED_HEAP_SIZE, hs->uncompressed_heap_size);\n\tts_jsonb_add_int64(parse_state,\n\t\t\t\t\t   REQ_RELKIND_UNCOMPRESSED_TOAST_SIZE,\n\t\t\t\t\t   hs->uncompressed_toast_size);\n\tts_jsonb_add_int64(parse_state,\n\t\t\t\t\t   REQ_RELKIND_UNCOMPRESSED_INDEXES_SIZE,\n\t\t\t\t\t   hs->uncompressed_indexes_size);\n\n\treturn pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);\n}\n\nstatic JsonbValue *\nadd_relkind_stats_object(JsonbParseState *parse_state, const char *relkindname,\n\t\t\t\t\t\t const BaseStats *stats, StatsRelType reltype, StatsType statstype)\n{\n\tJsonbValue name = {\n\t\t.type = jbvString,\n\t\t.val.string.val = pstrdup(relkindname),\n\t\t.val.string.len = strlen(relkindname),\n\t};\n\tpushJsonbValue(&parse_state, WJB_KEY, &name);\n\tpushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);\n\n\tts_jsonb_add_int64(parse_state, REQ_RELKIND_COUNT, stats->relcount);\n\n\tif (statstype >= STATS_TYPE_STORAGE)\n\t{\n\t\tconst StorageStats *ss = (const StorageStats *) stats;\n\t\tts_jsonb_add_int64(parse_state, REQ_RELKIND_RELTUPLES, stats->reltuples);\n\t\tts_jsonb_add_int64(parse_state, REQ_RELKIND_HEAP_SIZE, ss->relsize.heap_size);\n\t\tts_jsonb_add_int64(parse_state, REQ_RELKIND_TOAST_SIZE, ss->relsize.toast_size);\n\t\tts_jsonb_add_int64(parse_state, REQ_RELKIND_INDEXES_SIZE, ss->relsize.index_size);\n\t}\n\n\tif (statstype >= STATS_TYPE_HYPER)\n\t{\n\t\tconst HyperStats *hs = (const HyperStats *) stats;\n\t\tts_jsonb_add_int64(parse_state, REQ_RELKIND_CHILDREN, hs->child_count);\n\n\t\tif (reltype != RELTYPE_PARTITIONED_TABLE)\n\t\t\tadd_compression_stats_object(parse_state, reltype, hs);\n\t}\n\n\tif (statstype == STATS_TYPE_CAGG)\n\t{\n\t\tconst CaggStats *cs = (const CaggStats *) stats;\n\n\t\tts_jsonb_add_int64(parse_state,\n\t\t\t\t\t\t   REQ_RELKIND_CAGG_USES_REAL_TIME_AGGREGATION_COUNT,\n\t\t\t\t\t\t   cs->uses_real_time_aggregation_count);\n\t\tts_jsonb_add_int64(parse_state, REQ_RELKIND_CAGG_FINALIZED, cs->finalized);\n\t\tts_jsonb_add_int64(parse_state, REQ_RELKIND_CAGG_NESTED, cs->nested);\n\t}\n\n\treturn pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);\n}\n\nstatic void\nadd_function_call_telemetry(JsonbParseState *state)\n{\n\tfn_telemetry_entry_vec *functions;\n\tconst char *visible_extensions[(sizeof(related_extensions) / sizeof(char *)) + 1];\n\n\tif (!ts_function_telemetry_on())\n\t{\n\t\tJsonbValue value = {\n\t\t\t.type = jbvNull,\n\t\t};\n\n\t\tpushJsonbValue(&state, WJB_VALUE, &value);\n\t\treturn;\n\t}\n\n\tvisible_extensions[0] = EXTENSION_NAME;\n\tfor (size_t i = 1; i < sizeof(visible_extensions) / sizeof(char *); i++)\n\t\tvisible_extensions[i] = related_extensions[i - 1];\n\n\tfunctions =\n\t\tts_function_telemetry_read(visible_extensions, sizeof(visible_extensions) / sizeof(char *));\n\n\tpushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);\n\n\tif (functions)\n\t{\n\t\tfor (uint32 i = 0; i < functions->num_elements; i++)\n\t\t{\n\t\t\tFnTelemetryEntry *entry = fn_telemetry_entry_vec_at(functions, i);\n\t\t\tchar *proc_sig = format_procedure_qualified(entry->fn);\n\t\t\tts_jsonb_add_int64(state, proc_sig, entry->count);\n\t\t}\n\t}\n\n\tpushJsonbValue(&state, WJB_END_OBJECT, NULL);\n}\n\nstatic void\nadd_replication_telemetry(JsonbParseState *state)\n{\n\tReplicationInfo info = ts_telemetry_replication_info_gather();\n\tif (info.got_num_wal_senders)\n\t\tts_jsonb_add_int32(state, REQ_NUM_WAL_SENDERS, info.num_wal_senders);\n\n\tif (info.got_is_wal_receiver)\n\t\tts_jsonb_add_bool(state, REQ_IS_WAL_RECEIVER, info.is_wal_receiver);\n}\n\n#define REQ_RELS \"relations\"\n#define REQ_RELS_TABLES \"tables\"\n#define REQ_RELS_PARTITIONED_TABLES \"partitioned_tables\"\n#define REQ_RELS_MATVIEWS \"materialized_views\"\n#define REQ_RELS_VIEWS \"views\"\n#define REQ_RELS_HYPERTABLES \"hypertables\"\n#define REQ_RELS_CONTINUOUS_AGGS \"continuous_aggregates\"\n#define REQ_FUNCTIONS_USED \"functions_used\"\n#define REQ_REPLICATION \"replication\"\n#define REQ_ACCESS_METHODS \"access_methods\"\n\n/*\n * Add the result of a query as a sub-object to the JSONB.\n *\n * Each row from the query generates a separate object keyed by one of the\n * columns. Each row will be represented as an object and stored under the\n * \"key\" column. For example, with this query:\n *\n *    select amname as name,\n *           sum(relpages) as pages,\n *\t\t\t count(*) as instances\n *\t\tfrom pg_class join pg_am on relam = pg_am.oid\n *\t  group by pg_am.oid;\n *\n * might generate the object\n *\n * {\n *    \"brin\" : {\n *       \"instances\" : 44,\n *       \"pages\" : 432\n *    },\n *    \"btree\" : {\n *       \"instances\" : 99,\n *       \"pages\" : 1234\n *    }\n * }\n */\nstatic void\nadd_query_result_dict(JsonbParseState *state, const char *query)\n{\n\tMemoryContext orig_context = CurrentMemoryContext;\n\n\tint res;\n\tif (SPI_connect() != SPI_OK_CONNECT)\n\t\telog(ERROR, \"could not connect to SPI\");\n\n\t/* Lock down search_path */\n\tint save_nestlevel = NewGUCNestLevel();\n\tRestrictSearchPath();\n\n\tres = SPI_execute(query, true, 0);\n\tEnsure(res >= 0, \"could not execute query\");\n\n\tMemoryContext spi_context = MemoryContextSwitchTo(orig_context);\n\n\t(void) pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);\n\tfor (uint64 r = 0; r < SPI_processed; r++)\n\t{\n\t\tchar *key_string = SPI_getvalue(SPI_tuptable->vals[r], SPI_tuptable->tupdesc, 1);\n\t\tJsonbValue key = {\n\t\t\t.type = jbvString,\n\t\t\t.val.string.val = pstrdup(key_string),\n\t\t\t.val.string.len = strlen(key_string),\n\t\t};\n\n\t\t(void) pushJsonbValue(&state, WJB_KEY, &key);\n\n\t\t(void) pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);\n\t\tfor (int c = 1; c < SPI_tuptable->tupdesc->natts; ++c)\n\t\t{\n\t\t\tbool isnull;\n\t\t\tDatum val_datum =\n\t\t\t\tSPI_getbinval(SPI_tuptable->vals[r], SPI_tuptable->tupdesc, c + 1, &isnull);\n\t\t\tif (!isnull)\n\t\t\t{\n\t\t\t\tchar *key_string = SPI_fname(SPI_tuptable->tupdesc, c + 1);\n\t\t\t\tJsonbValue value;\n\t\t\t\tts_jsonb_set_value_by_type(&value,\n\t\t\t\t\t\t\t\t\t\t   SPI_gettypeid(SPI_tuptable->tupdesc, c + 1),\n\t\t\t\t\t\t\t\t\t\t   val_datum);\n\t\t\t\tts_jsonb_add_value(state, key_string, &value);\n\t\t\t}\n\t\t}\n\t\tpushJsonbValue(&state, WJB_END_OBJECT, NULL);\n\t}\n\n\t/* Restore search_path */\n\tAtEOXact_GUC(false, save_nestlevel);\n\n\tMemoryContextSwitchTo(spi_context);\n\tres = SPI_finish();\n\tAssert(res == SPI_OK_FINISH);\n\t(void) pushJsonbValue(&state, WJB_END_OBJECT, NULL);\n}\n\nstatic Jsonb *\nbuild_telemetry_report()\n{\n\tJsonbParseState *parse_state = NULL;\n\tJsonbValue key;\n\tJsonbValue *result;\n\tTelemetryStats relstats;\n\tVersionOSInfo osinfo;\n\n\tpushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);\n\n\tts_jsonb_add_int32(parse_state, REQ_TELEMETRY_VERSION, TS_TELEMETRY_VERSION);\n\tts_jsonb_add_str(parse_state,\n\t\t\t\t\t REQ_DB_UUID,\n\t\t\t\t\t DatumGetCString(DirectFunctionCall1(uuid_out, ts_metadata_get_uuid())));\n\tts_jsonb_add_str(parse_state,\n\t\t\t\t\t REQ_EXPORTED_DB_UUID,\n\t\t\t\t\t DatumGetCString(\n\t\t\t\t\t\t DirectFunctionCall1(uuid_out, ts_metadata_get_exported_uuid())));\n\tts_jsonb_add_str(parse_state,\n\t\t\t\t\t REQ_INSTALL_TIME,\n\t\t\t\t\t format_iso8601(ts_metadata_get_install_timestamp()));\n\tts_jsonb_add_str(parse_state, REQ_INSTALL_METHOD, TIMESCALEDB_INSTALL_METHOD);\n\n\tif (ts_version_get_os_info(&osinfo))\n\t{\n\t\tts_jsonb_add_str(parse_state, REQ_OS, osinfo.sysname);\n\t\tts_jsonb_add_str(parse_state, REQ_OS_VERSION, osinfo.version);\n\t\tts_jsonb_add_str(parse_state, REQ_OS_RELEASE, osinfo.release);\n\t\tif (osinfo.has_pretty_version)\n\t\t\tts_jsonb_add_str(parse_state, REQ_OS_VERSION_PRETTY, osinfo.pretty_version);\n\t}\n\telse\n\t\tts_jsonb_add_str(parse_state, REQ_OS, \"Unknown\");\n\n\tts_jsonb_add_str(parse_state, REQ_PS_VERSION, get_pgversion_string());\n\tts_jsonb_add_str(parse_state, REQ_TS_VERSION, TIMESCALEDB_VERSION_MOD);\n\tts_jsonb_add_str(parse_state, REQ_BUILD_OS, BUILD_OS_NAME);\n\tts_jsonb_add_str(parse_state, REQ_BUILD_OS_VERSION, BUILD_OS_VERSION);\n\tts_jsonb_add_str(parse_state, REQ_BUILD_ARCHITECTURE, BUILD_PROCESSOR);\n\tts_jsonb_add_int32(parse_state, REQ_BUILD_ARCHITECTURE_BIT_SIZE, get_architecture_bit_size());\n\tts_jsonb_add_int64(parse_state, REQ_DATA_VOLUME, get_database_size());\n\t/* add job execution stats */\n\tkey.type = jbvString;\n\tkey.val.string.val = REQ_NUM_ERR_BY_SQLERRCODE;\n\tkey.val.string.len = strlen(REQ_NUM_ERR_BY_SQLERRCODE);\n\tpushJsonbValue(&parse_state, WJB_KEY, &key);\n\tpushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);\n\n\tadd_errors_by_sqlerrcode(parse_state);\n\n\tpushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);\n\n\tkey.type = jbvString;\n\tkey.val.string.val = REQ_JOB_STATS_BY_JOB_TYPE;\n\tkey.val.string.len = strlen(REQ_JOB_STATS_BY_JOB_TYPE);\n\tpushJsonbValue(&parse_state, WJB_KEY, &key);\n\tpushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);\n\n\tadd_job_stats_by_job_type(parse_state);\n\n\tpushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);\n\n\t/* Add relation stats */\n\tts_telemetry_stats_gather(&relstats);\n\tkey.type = jbvString;\n\tkey.val.string.val = REQ_RELS;\n\tkey.val.string.len = strlen(REQ_RELS);\n\tpushJsonbValue(&parse_state, WJB_KEY, &key);\n\tpushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);\n\n\tadd_relkind_stats_object(parse_state,\n\t\t\t\t\t\t\t REQ_RELS_TABLES,\n\t\t\t\t\t\t\t &relstats.tables.base,\n\t\t\t\t\t\t\t RELTYPE_TABLE,\n\t\t\t\t\t\t\t STATS_TYPE_STORAGE);\n\tadd_relkind_stats_object(parse_state,\n\t\t\t\t\t\t\t REQ_RELS_PARTITIONED_TABLES,\n\t\t\t\t\t\t\t &relstats.partitioned_tables.storage.base,\n\t\t\t\t\t\t\t RELTYPE_PARTITIONED_TABLE,\n\t\t\t\t\t\t\t STATS_TYPE_HYPER);\n\tadd_relkind_stats_object(parse_state,\n\t\t\t\t\t\t\t REQ_RELS_MATVIEWS,\n\t\t\t\t\t\t\t &relstats.materialized_views.base,\n\t\t\t\t\t\t\t RELTYPE_MATVIEW,\n\t\t\t\t\t\t\t STATS_TYPE_STORAGE);\n\tadd_relkind_stats_object(parse_state,\n\t\t\t\t\t\t\t REQ_RELS_VIEWS,\n\t\t\t\t\t\t\t &relstats.views,\n\t\t\t\t\t\t\t RELTYPE_VIEW,\n\t\t\t\t\t\t\t STATS_TYPE_BASE);\n\tadd_relkind_stats_object(parse_state,\n\t\t\t\t\t\t\t REQ_RELS_HYPERTABLES,\n\t\t\t\t\t\t\t &relstats.hypertables.storage.base,\n\t\t\t\t\t\t\t RELTYPE_HYPERTABLE,\n\t\t\t\t\t\t\t STATS_TYPE_HYPER);\n\n\tadd_relkind_stats_object(parse_state,\n\t\t\t\t\t\t\t REQ_RELS_CONTINUOUS_AGGS,\n\t\t\t\t\t\t\t &relstats.continuous_aggs.hyp.storage.base,\n\t\t\t\t\t\t\t RELTYPE_CONTINUOUS_AGG,\n\t\t\t\t\t\t\t STATS_TYPE_CAGG);\n\n\tpushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);\n\n\tadd_job_counts(parse_state);\n\n\t/* Add related extensions, which is a nested JSON */\n\tkey.type = jbvString;\n\tkey.val.string.val = REQ_RELATED_EXTENSIONS;\n\tkey.val.string.len = strlen(REQ_RELATED_EXTENSIONS);\n\tpushJsonbValue(&parse_state, WJB_KEY, &key);\n\tadd_related_extensions(parse_state);\n\n\t/* license */\n\tkey.type = jbvString;\n\tkey.val.string.val = REQ_LICENSE_INFO;\n\tkey.val.string.len = strlen(REQ_LICENSE_INFO);\n\tpushJsonbValue(&parse_state, WJB_KEY, &key);\n\tpushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);\n\tif (ts_license_is_apache())\n\t\tts_jsonb_add_str(parse_state, REQ_LICENSE_EDITION, REQ_LICENSE_EDITION_APACHE);\n\telse\n\t\tts_jsonb_add_str(parse_state, REQ_LICENSE_EDITION, REQ_LICENSE_EDITION_COMMUNITY);\n\tpushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);\n\n\t/* add tuned info, which is optional */\n\tchar *last_tune_time = GetConfigOptionByName(\"timescaledb.last_tune_time\", NULL, true);\n\tif (last_tune_time != NULL)\n\t\tts_jsonb_add_str(parse_state, REQ_TS_LAST_TUNE_TIME, last_tune_time);\n\n\tchar *last_tune_version = GetConfigOptionByName(\"timescaledb.last_tune_version\", NULL, true);\n\tif (last_tune_version != NULL)\n\t\tts_jsonb_add_str(parse_state, REQ_TS_LAST_TUNE_VERSION, last_tune_version);\n\n\t/* add cloud to telemetry when set */\n\tif (ts_telemetry_cloud != NULL)\n\t{\n\t\tkey.type = jbvString;\n\t\tkey.val.string.val = REQ_INSTANCE_METADATA;\n\t\tkey.val.string.len = strlen(REQ_INSTANCE_METADATA);\n\t\tpushJsonbValue(&parse_state, WJB_KEY, &key);\n\n\t\tpushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);\n\t\tts_jsonb_add_str(parse_state, REQ_TS_TELEMETRY_CLOUD, ts_telemetry_cloud);\n\t\tpushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);\n\t}\n\n\t/* Add additional content from metadata */\n\tkey.type = jbvString;\n\tkey.val.string.val = REQ_METADATA;\n\tkey.val.string.len = strlen(REQ_METADATA);\n\tpushJsonbValue(&parse_state, WJB_KEY, &key);\n\tpushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);\n\tts_telemetry_metadata_add_values(parse_state);\n\tpushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);\n\n\t/* Add telemetry events */\n\tkey.type = jbvString;\n\tkey.val.string.val = REQ_TELEMETRY_EVENT;\n\tkey.val.string.len = strlen(REQ_TELEMETRY_EVENT);\n\tpushJsonbValue(&parse_state, WJB_KEY, &key);\n\tts_telemetry_events_add(parse_state);\n\n\t/* Add function call telemetry */\n\tkey.type = jbvString;\n\tkey.val.string.val = REQ_FUNCTIONS_USED;\n\tkey.val.string.len = strlen(REQ_FUNCTIONS_USED);\n\tpushJsonbValue(&parse_state, WJB_KEY, &key);\n\tadd_function_call_telemetry(parse_state);\n\n\t/* Add replication object */\n\tkey.type = jbvString;\n\tkey.val.string.val = REQ_REPLICATION;\n\tkey.val.string.len = strlen(REQ_REPLICATION);\n\tpushJsonbValue(&parse_state, WJB_KEY, &key);\n\n\tpushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);\n\tadd_replication_telemetry(parse_state);\n\tpushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);\n\n\tkey.type = jbvString;\n\tkey.val.string.val = REQ_ACCESS_METHODS;\n\tkey.val.string.len = strlen(REQ_ACCESS_METHODS);\n\t(void) pushJsonbValue(&parse_state, WJB_KEY, &key);\n\tadd_query_result_dict(parse_state,\n\t\t\t\t\t\t  \"SELECT amname AS name, sum(relpages) AS pages, count(*) AS \"\n\t\t\t\t\t\t  \"instances FROM pg_class JOIN pg_am ON relam = pg_am.oid \"\n\t\t\t\t\t\t  \"GROUP BY amname\");\n\n\t/* end of telemetry object */\n\tresult = pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);\n\n\treturn JsonbValueToJsonb(result);\n}\n\nHttpRequest *\nts_build_version_request(const char *host, const char *path)\n{\n\tHttpRequest *req;\n\tJsonb *json = build_telemetry_report();\n\n\t/* Fill in HTTP request */\n\treq = ts_http_request_create(HTTP_POST);\n\tts_http_request_set_uri(req, path);\n\tts_http_request_set_version(req, HTTP_VERSION_10);\n\tts_http_request_set_header(req, HTTP_HOST, host);\n\tts_http_request_set_body_jsonb(req, json);\n\n\treturn req;\n}\n\nstatic ConnectionType\nconnection_type(const char *service)\n{\n\tif (strcmp(\"http\", service) == 0)\n\t\treturn CONNECTION_PLAIN;\n\telse if (strcmp(\"https\", service) == 0)\n\t\treturn CONNECTION_SSL;\n\n\tereport(NOTICE,\n\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t errmsg(\"scheme \\\"%s\\\" not supported for telemetry\", service)));\n\treturn _CONNECTION_MAX;\n}\n\nConnection *\nts_telemetry_connect(const char *host, const char *service)\n{\n\tConnection *conn = ts_connection_create(connection_type(service));\n\n\tif (conn)\n\t{\n\t\tint ret = ts_connection_connect(conn, host, service, 0);\n\n\t\tif (ret < 0)\n\t\t{\n\t\t\tconst char *errstr = ts_connection_get_and_clear_error(conn);\n\n\t\t\tts_connection_destroy(conn);\n\t\t\tconn = NULL;\n\n\t\t\tereport(NOTICE,\n\t\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t\t errmsg(\"telemetry could not connect to \\\"%s\\\"\", host),\n\t\t\t\t\t errdetail(\"%s\", errstr)));\n\t\t}\n\t}\n\n\treturn conn;\n}\n\nbool\nts_telemetry_main_wrapper()\n{\n\treturn ts_telemetry_main(TELEMETRY_HOST, TELEMETRY_PATH, TELEMETRY_SCHEME);\n}\n\nbool\nts_telemetry_main(const char *host, const char *path, const char *service)\n{\n\tHttpError err;\n\tConnection *conn;\n\tHttpRequest *req;\n\tHttpResponseState *rsp;\n\t/* Declared volatile to suppress the incorrect -Wclobbered warning. */\n\tvolatile bool started = false;\n\tbool snapshot_set = false;\n\tconst char *volatile json = NULL;\n\n\tif (!ts_telemetry_on())\n\t\treturn false;\n\n\tif (!IsTransactionOrTransactionBlock())\n\t{\n\t\tstarted = true;\n\t\tStartTransactionCommand();\n\t}\n\n\tconn = ts_telemetry_connect(host, service);\n\n\tif (conn == NULL)\n\t\tgoto cleanup;\n\n\tif (!ActiveSnapshotSet())\n\t{\n\t\t/* Need a valid snapshot to build telemetry information */\n\t\tPushActiveSnapshot(GetTransactionSnapshot());\n\t\tsnapshot_set = true;\n\t}\n\n\treq = ts_build_version_request(host, path);\n\n\tif (snapshot_set)\n\t\tPopActiveSnapshot();\n\n\trsp = ts_http_response_state_create();\n\n\terr = ts_http_send_and_recv(conn, req, rsp);\n\n\tts_http_request_destroy(req);\n\tts_connection_destroy(conn);\n\n\tif (err != HTTP_ERROR_NONE)\n\t{\n\t\telog(NOTICE, \"telemetry error: %s\", ts_http_strerror(err));\n\t\tgoto cleanup;\n\t}\n\n\tif (!ts_http_response_state_valid_status(rsp))\n\t{\n\t\telog(NOTICE,\n\t\t\t \"telemetry got unexpected HTTP response status: %d\",\n\t\t\t ts_http_response_state_status_code(rsp));\n\t\tgoto cleanup;\n\t}\n\n\tts_function_telemetry_reset_counts();\n\tts_telemetry_event_truncate();\n\n\t/*\n\t * Do the version-check. Response is the body of a well-formed HTTP\n\t * response, since otherwise the previous line will throw an error.\n\t */\n\tPG_TRY();\n\t{\n\t\tjson = ts_http_response_state_body_start(rsp);\n\t\tts_check_version_response(json);\n\t}\n\tPG_CATCH();\n\t{\n\t\t/* If the response is malformed, ts_check_version_response() will\n\t\t * throw an error, so we capture the error here and print debugging\n\t\t * information. */\n\t\tFlushErrorState();\n\t\tereport(NOTICE,\n\t\t\t\t(errcode(ERRCODE_DATA_EXCEPTION),\n\t\t\t\t errmsg(\"malformed telemetry response body\"),\n\t\t\t\t errdetail(\"host=%s, service=%s, path=%s: %s\",\n\t\t\t\t\t\t   host,\n\t\t\t\t\t\t   service,\n\t\t\t\t\t\t   path,\n\t\t\t\t\t\t   json ? json : \"<EMPTY>\")));\n\t\t/* Do not throw an error in this case, there is really nothing wrong\n\t\t   with the system. It's only telemetry that is having problems, so we\n\t\t   just wrap this up and exit. */\n\t\tif (started)\n\t\t\tAbortCurrentTransaction();\n\t\treturn false;\n\t}\n\tPG_END_TRY();\n\n\tts_http_response_state_destroy(rsp);\n\n\tif (started)\n\t\tCommitTransactionCommand();\n\n\treturn true;\n\ncleanup:\n\tif (started)\n\t\tAbortCurrentTransaction();\n\n\treturn false;\n}\n\nTS_FUNCTION_INFO_V1(ts_telemetry_get_report_jsonb);\n\nDatum\nts_telemetry_get_report_jsonb(PG_FUNCTION_ARGS)\n{\n\tJsonb *jb = build_telemetry_report();\n\tts_function_telemetry_reset_counts();\n\tPG_RETURN_JSONB_P(jb);\n}\n"
  },
  {
    "path": "src/telemetry/telemetry.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <fmgr.h>\n#include <utils/builtins.h>\n\n#include \"compat/compat.h\"\n#include \"net/conn.h\"\n#include \"net/http.h\"\n#include \"utils.h\"\n#include \"version.h\"\n\n#define REQ_LICENSE_INFO \"license\"\n#define REQ_LICENSE_EDITION \"edition\"\n\n#define TELEMETRY_SCHEME \"https\"\n#define TELEMETRY_HOST \"telemetry.timescale.com\"\n#define TELEMETRY_PATH \"/v1/metrics\"\n\n#define MAX_VERSION_STR_LEN 128\n\ntypedef struct BgwJobTypeCount\n{\n\tint32 policy_cagg;\n\tint32 policy_cagg_fixed;\n\tint32 policy_compression;\n\tint32 policy_compression_fixed;\n\tint32 policy_reorder;\n\tint32 policy_reorder_fixed;\n\tint32 policy_retention;\n\tint32 policy_retention_fixed;\n\tint32 policy_telemetry;\n\tint32 user_defined_action;\n\tint32 user_defined_action_fixed;\n} BgwJobTypeCount;\n\ntypedef struct VersionResult\n{\n\tconst char *versionstr;\n\tconst char *errhint;\n} VersionResult;\n\nextern HttpRequest *ts_build_version_request(const char *host, const char *path);\nextern Connection *ts_telemetry_connect(const char *host, const char *service);\nextern bool ts_validate_server_version(const char *json, VersionResult *result);\nextern void ts_check_version_response(const char *json);\n\n/*\n *\tThis function is intended as the main function for a BGW.\n *  Its job is to send metrics and fetch the most up-to-date version of\n *  Timescale via HTTPS.\n */\nextern bool ts_telemetry_main(const char *host, const char *path, const char *service);\nextern TSDLLEXPORT bool ts_telemetry_main_wrapper(void);\nextern TSDLLEXPORT Datum ts_telemetry_get_report_jsonb(PG_FUNCTION_ARGS);\n"
  },
  {
    "path": "src/telemetry/telemetry_metadata.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <catalog/pg_type.h>\n#include <commands/tablecmds.h>\n#include <utils/builtins.h>\n#include <utils/jsonb.h>\n#include <utils/timestamp.h>\n\n#include \"jsonb_utils.h\"\n#include \"scan_iterator.h\"\n#include \"telemetry/telemetry_metadata.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"ts_catalog/metadata.h\"\n#include \"uuid.h\"\n\nvoid\nts_telemetry_event_truncate(void)\n{\n\tRangeVar rv = {\n\t\t.schemaname = CATALOG_SCHEMA_NAME,\n\t\t.relname = TELEMETRY_EVENT_TABLE_NAME,\n\t};\n\tExecuteTruncate(&(TruncateStmt){\n\t\t.type = T_TruncateStmt,\n\t\t.relations = list_make1(&rv),\n\t\t.behavior = DROP_RESTRICT,\n\t});\n}\n\nvoid\nts_telemetry_events_add(JsonbParseState *state)\n{\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(TELEMETRY_EVENT, AccessShareLock, CurrentMemoryContext);\n\tpushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = iterator.tinfo;\n\t\tTupleDesc tupdesc = ti->slot->tts_tupleDescriptor;\n\t\tbool created_isnull, tag_isnull, value_isnull;\n\t\tDatum created = slot_getattr(ti->slot, Anum_telemetry_event_created, &created_isnull);\n\t\tDatum tag = slot_getattr(ti->slot, Anum_telemetry_event_tag, &tag_isnull);\n\t\tDatum body = slot_getattr(ti->slot, Anum_telemetry_event_body, &value_isnull);\n\n\t\tpushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);\n\t\tif (!created_isnull)\n\t\t\tts_jsonb_add_str(state,\n\t\t\t\t\t\t\t NameStr(\n\t\t\t\t\t\t\t\t TupleDescAttr(tupdesc, Anum_telemetry_event_created - 1)->attname),\n\t\t\t\t\t\t\t DatumGetCString(DirectFunctionCall1(timestamptz_out, created)));\n\n\t\tif (!tag_isnull)\n\t\t\tts_jsonb_add_str(state,\n\t\t\t\t\t\t\t NameStr(TupleDescAttr(tupdesc, Anum_telemetry_event_tag - 1)->attname),\n\t\t\t\t\t\t\t pstrdup(NameStr(*DatumGetName(tag))));\n\n\t\tif (!value_isnull)\n\t\t{\n\t\t\tJsonbValue jsonb_value;\n\t\t\tJsonbToJsonbValue(DatumGetJsonbPCopy(body), &jsonb_value);\n\t\t\tts_jsonb_add_value(state,\n\t\t\t\t\t\t\t   NameStr(\n\t\t\t\t\t\t\t\t   TupleDescAttr(tupdesc, Anum_telemetry_event_body - 1)->attname),\n\t\t\t\t\t\t\t   &jsonb_value);\n\t\t}\n\t\tpushJsonbValue(&state, WJB_END_OBJECT, NULL);\n\t}\n\tpushJsonbValue(&state, WJB_END_ARRAY, NULL);\n}\n\n/*\n * add all entries from _timescaledb_catalog.metadata\n */\nvoid\nts_telemetry_metadata_add_values(JsonbParseState *state)\n{\n\tDatum key, value;\n\tbool key_isnull, value_isnull, include_entry;\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(METADATA, AccessShareLock, CurrentMemoryContext);\n\titerator.ctx.index = catalog_get_index(ts_catalog_get(), METADATA, METADATA_PKEY_IDX);\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = iterator.tinfo;\n\n\t\tkey = slot_getattr(ti->slot, Anum_metadata_key, &key_isnull);\n\n\t\tinclude_entry =\n\t\t\t!key_isnull &&\n\t\t\tDatumGetBool(slot_getattr(ti->slot, Anum_metadata_include_in_telemetry, &key_isnull));\n\n\t\tif (include_entry)\n\t\t{\n\t\t\tName key_name = DatumGetName(key);\n\n\t\t\t/* skip keys included as toplevel items */\n\t\t\tif (namestrcmp(key_name, METADATA_UUID_KEY_NAME) != 0 &&\n\t\t\t\tnamestrcmp(key_name, METADATA_EXPORTED_UUID_KEY_NAME) != 0 &&\n\t\t\t\tnamestrcmp(key_name, METADATA_TIMESTAMP_KEY_NAME) != 0)\n\t\t\t{\n\t\t\t\tvalue = slot_getattr(ti->slot, Anum_metadata_value, &value_isnull);\n\n\t\t\t\tif (!value_isnull)\n\t\t\t\t\tts_jsonb_add_str(state,\n\t\t\t\t\t\t\t\t\t pstrdup(NameStr(*key_name)),\n\t\t\t\t\t\t\t\t\t pstrdup(TextDatumGetCString(value)));\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/telemetry/telemetry_metadata.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <utils/jsonb.h>\n\n#include <export.h>\n\nextern void ts_telemetry_metadata_add_values(JsonbParseState *state);\nextern void ts_telemetry_events_add(JsonbParseState *state);\nextern void ts_telemetry_event_truncate(void);\n"
  },
  {
    "path": "src/time_bucket.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <catalog/pg_type.h>\n#include <fmgr.h>\n#include <utils/builtins.h>\n#include <utils/date.h>\n#include <utils/datetime.h>\n#include <utils/elog.h>\n#include <utils/fmgrprotos.h>\n#include <utils/timestamp.h>\n#include <utils/uuid.h>\n\n#include \"export.h\"\n#include \"time_bucket.h\"\n#include \"utils.h\"\n#include \"uuid.h\"\n\n#define TIME_BUCKET(period, timestamp, offset, min, max, result)                                   \\\n\tdo                                                                                             \\\n\t{                                                                                              \\\n\t\tif ((period) <= 0)                                                                         \\\n\t\t\tereport(ERROR,                                                                         \\\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),                                     \\\n\t\t\t\t\t errmsg(\"period must be greater than 0\")));                                    \\\n\t\tif ((offset) != 0)                                                                         \\\n\t\t{                                                                                          \\\n\t\t\t/* We need to ensure that the timestamp is in range _after_ the */                     \\\n\t\t\t/* offset is applied: when the offset is positive we need to make */                   \\\n\t\t\t/* sure the resultant time is at least min, and when negative that */                  \\\n\t\t\t/* it is less than the max. */                                                         \\\n\t\t\t(offset) = (offset) % (period);                                                        \\\n\t\t\tif (((offset) > 0 && (timestamp) < (min) + (offset)) ||                                \\\n\t\t\t\t((offset) < 0 && (timestamp) > (max) + (offset)))                                  \\\n\t\t\t\tereport(ERROR,                                                                     \\\n\t\t\t\t\t\t(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),                              \\\n\t\t\t\t\t\t errmsg(\"timestamp out of range\")));                                       \\\n\t\t\t(timestamp) -= (offset);                                                               \\\n\t\t}                                                                                          \\\n\t\t(result) = ((timestamp) / (period)) * (period);                                            \\\n\t\tif ((timestamp) < 0 && (timestamp) % (period))                                             \\\n\t\t{                                                                                          \\\n\t\t\tif ((result) < (min) + (period))                                                       \\\n\t\t\t\tereport(ERROR,                                                                     \\\n\t\t\t\t\t\t(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),                              \\\n\t\t\t\t\t\t errmsg(\"timestamp out of range\")));                                       \\\n\t\t\telse                                                                                   \\\n\t\t\t\t(result) = (result) - (period);                                                    \\\n\t\t}                                                                                          \\\n\t\t(result) += (offset);                                                                      \\\n\t} while (0)\n\nTS_FUNCTION_INFO_V1(ts_int16_bucket);\nTSDLLEXPORT Datum\nts_int16_bucket(PG_FUNCTION_ARGS)\n{\n\tint16 result;\n\tint16 period = PG_GETARG_INT16(0);\n\tint16 timestamp = PG_GETARG_INT16(1);\n\tint16 offset = PG_NARGS() > 2 ? PG_GETARG_INT16(2) : 0;\n\n\tTIME_BUCKET(period, timestamp, offset, PG_INT16_MIN, PG_INT16_MAX, result);\n\n\tPG_RETURN_INT16(result);\n}\n\nTS_FUNCTION_INFO_V1(ts_int32_bucket);\nTSDLLEXPORT Datum\nts_int32_bucket(PG_FUNCTION_ARGS)\n{\n\tint32 result;\n\tint32 period = PG_GETARG_INT32(0);\n\tint32 timestamp = PG_GETARG_INT32(1);\n\tint32 offset = PG_NARGS() > 2 ? PG_GETARG_INT32(2) : 0;\n\n\tTIME_BUCKET(period, timestamp, offset, PG_INT32_MIN, PG_INT32_MAX, result);\n\n\tPG_RETURN_INT32(result);\n}\n\nTS_FUNCTION_INFO_V1(ts_int64_bucket);\nTSDLLEXPORT Datum\nts_int64_bucket(PG_FUNCTION_ARGS)\n{\n\tint64 result;\n\tint64 period = PG_GETARG_INT64(0);\n\tint64 timestamp = PG_GETARG_INT64(1);\n\tint64 offset = PG_NARGS() > 2 ? PG_GETARG_INT64(2) : 0;\n\n\tTIME_BUCKET(period, timestamp, offset, PG_INT64_MIN, PG_INT64_MAX, result);\n\n\tPG_RETURN_INT64(result);\n}\n\n#define JAN_3_2000 (2 * USECS_PER_DAY)\n\n/*\n * The default origin is Monday 2000-01-03. We don't use PG epoch since it starts on a saturday.\n * This makes time-buckets by a week more intuitive and aligns it with date_trunc. Since month\n * bucketing ignores the day component this makes origin for month buckets 2000-01-01.\n */\n#define DEFAULT_ORIGIN (JAN_3_2000)\n#define TIME_BUCKET_TS(period, timestamp, result, shift)                                           \\\n\tdo                                                                                             \\\n\t{                                                                                              \\\n\t\tif ((period) <= 0)                                                                         \\\n\t\t\tereport(ERROR,                                                                         \\\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),                                     \\\n\t\t\t\t\t errmsg(\"period must be greater than 0\")));                                    \\\n\t\t/* shift = shift % period, but use TMODULO */                                              \\\n\t\tTMODULO(shift, result, period);                                                            \\\n                                                                                                   \\\n\t\tif (((shift) > 0 && (timestamp) < DT_NOBEGIN + (shift)) ||                                 \\\n\t\t\t((shift) < 0 && (timestamp) > DT_NOEND + (shift)))                                     \\\n\t\t\tereport(ERROR,                                                                         \\\n\t\t\t\t\t(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),                                  \\\n\t\t\t\t\t errmsg(\"timestamp out of range\")));                                           \\\n\t\t(timestamp) -= (shift);                                                                    \\\n                                                                                                   \\\n\t\t/* result = (timestamp / period) * period */                                               \\\n\t\tTMODULO(timestamp, result, period);                                                        \\\n\t\tif ((timestamp) < 0)                                                                       \\\n\t\t{                                                                                          \\\n\t\t\t/*                                                                                     \\\n\t\t\t * need to subtract another period if remainder < 0 this only happens                  \\\n\t\t\t * if timestamp is negative to begin with and there is a remainder                     \\\n\t\t\t * after division. Need to subtract another period since division                      \\\n\t\t\t * truncates toward 0 in C99.                                                          \\\n\t\t\t */                                                                                    \\\n\t\t\t(result) = ((result) * (period)) - (period);                                           \\\n\t\t}                                                                                          \\\n\t\telse                                                                                       \\\n\t\t\t(result) *= (period);                                                                  \\\n                                                                                                   \\\n\t\t(result) += (shift);                                                                       \\\n\t} while (0)\n\nstatic void\nvalidate_month_bucket(Interval *interval)\n{\n\t/*\n\t * Bucketing by a month and non-month cannot be mixed.\n\t */\n\tAssert(interval->month);\n\n\tif (interval->day || interval->time)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"month intervals cannot have day or time component\")));\n\t}\n}\n\n/*\n * To bucket by month we get the year and month of a date and convert\n * that to the nth month since origin. This allows us to treat month\n * bucketing similar to int bucketing. During this process we ignore\n * the day component and therefore only support bucketing by full months.\n */\nstatic DateADT\nbucket_month(int32 period, DateADT date, DateADT origin)\n{\n\tint32 year, month, day;\n\tint32 result;\n\n\tj2date(date + POSTGRES_EPOCH_JDATE, &year, &month, &day);\n\tint32 timestamp = (year * 12) + month - 1;\n\n\tj2date(origin + POSTGRES_EPOCH_JDATE, &year, &month, &day);\n\tint32 offset = (year * 12) + month - 1;\n\n\tTIME_BUCKET(period, timestamp, offset, PG_INT32_MIN, PG_INT32_MAX, result);\n\n\tyear = result / 12;\n\tmonth = result % 12;\n\tday = 1;\n\n\treturn date2j(year, month + 1, day) - POSTGRES_EPOCH_JDATE;\n}\n\n/* Returns the period in the same representation as Postgres Timestamps.\n * Note that this is not our internal representation (microseconds).\n * Always returns an exact value.*/\nstatic inline int64\nget_interval_period_timestamp_units(Interval *interval)\n{\n\tif (interval->month != 0)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"interval defined in terms of month, year, century etc. not supported\")));\n\t}\n\treturn interval->time + (interval->day * USECS_PER_DAY);\n}\n\nTS_FUNCTION_INFO_V1(ts_timestamp_bucket);\n\nTSDLLEXPORT Datum\nts_timestamp_bucket(PG_FUNCTION_ARGS)\n{\n\tInterval *interval = PG_GETARG_INTERVAL_P(0);\n\tTimestamp timestamp = PG_GETARG_TIMESTAMP(1);\n\n\t/*\n\t * USE NARGS and not IS_NULL to differentiate a NULL argument from a call\n\t * with 2 parameters\n\t */\n\tTimestamp origin = (PG_NARGS() > 2 ? PG_GETARG_TIMESTAMP(2) : DEFAULT_ORIGIN);\n\tTimestamp result;\n\n\tif (TIMESTAMP_NOT_FINITE(timestamp))\n\t\tPG_RETURN_TIMESTAMP(timestamp);\n\n\tif (interval->month)\n\t{\n\t\tDateADT origin_date = 0;\n\t\tvalidate_month_bucket(interval);\n\n\t\tDateADT date = DatumGetDateADT(DirectFunctionCall1(timestamp_date, PG_GETARG_DATUM(1)));\n\t\tif (origin != DEFAULT_ORIGIN)\n\t\t\torigin_date =\n\t\t\t\tDatumGetDateADT(DirectFunctionCall1(timestamp_date, TimestampGetDatum(origin)));\n\n\t\tdate = bucket_month(interval->month, date, origin_date);\n\n\t\tPG_RETURN_DATUM(DirectFunctionCall1(date_timestamp, DateADTGetDatum(date)));\n\t}\n\telse\n\t{\n\t\tint64 period = get_interval_period_timestamp_units(interval);\n\n\t\tTIME_BUCKET_TS(period, timestamp, result, origin);\n\n\t\tPG_RETURN_TIMESTAMP(result);\n\t}\n}\n\nTS_FUNCTION_INFO_V1(ts_timestamp_offset_bucket);\n\nTSDLLEXPORT Datum\nts_timestamp_offset_bucket(PG_FUNCTION_ARGS)\n{\n\tDatum period = PG_GETARG_DATUM(0);\n\tDatum timestamp = PG_GETARG_DATUM(1);\n\n\tif (TIMESTAMP_NOT_FINITE(DatumGetTimestamp(timestamp)))\n\t\tPG_RETURN_DATUM(timestamp);\n\n\t/* Apply offset. */\n\ttimestamp = DirectFunctionCall2(timestamp_mi_interval, timestamp, PG_GETARG_DATUM(2));\n\ttimestamp = DirectFunctionCall2(ts_timestamp_bucket, period, timestamp);\n\n\t/* Remove offset. */\n\ttimestamp = DirectFunctionCall2(timestamp_pl_interval, timestamp, PG_GETARG_DATUM(2));\n\tPG_RETURN_DATUM(timestamp);\n}\n\nTS_FUNCTION_INFO_V1(ts_timestamptz_bucket);\n\nTSDLLEXPORT Datum\nts_timestamptz_bucket(PG_FUNCTION_ARGS)\n{\n\tInterval *interval = PG_GETARG_INTERVAL_P(0);\n\tTimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(1);\n\n\t/*\n\t * USE NARGS and not IS_NULL to differentiate a NULL argument from a call\n\t * with 2 parameters\n\t */\n\tTimestampTz origin = (PG_NARGS() > 2 ? PG_GETARG_TIMESTAMPTZ(2) : DEFAULT_ORIGIN);\n\tTimestampTz result;\n\n\tif (TIMESTAMP_NOT_FINITE(timestamp))\n\t\tPG_RETURN_TIMESTAMPTZ(timestamp);\n\n\tif (interval->month)\n\t{\n\t\tDateADT origin_date = 0;\n\t\tvalidate_month_bucket(interval);\n\n\t\tDateADT date = DatumGetDateADT(DirectFunctionCall1(timestamp_date, PG_GETARG_DATUM(1)));\n\t\tif (origin != DEFAULT_ORIGIN)\n\t\t\torigin_date =\n\t\t\t\tDatumGetDateADT(DirectFunctionCall1(timestamp_date, TimestampTzGetDatum(origin)));\n\n\t\tdate = bucket_month(interval->month, date, origin_date);\n\n\t\tPG_RETURN_DATUM(DirectFunctionCall1(date_timestamp, DateADTGetDatum(date)));\n\t}\n\telse\n\t{\n\t\tint64 period = get_interval_period_timestamp_units(interval);\n\n\t\tTIME_BUCKET_TS(period, timestamp, result, origin);\n\n\t\tPG_RETURN_TIMESTAMPTZ(result);\n\t}\n}\n\nTS_FUNCTION_INFO_V1(ts_timestamptz_offset_bucket);\n\nTSDLLEXPORT Datum\nts_timestamptz_offset_bucket(PG_FUNCTION_ARGS)\n{\n\tDatum period = PG_GETARG_DATUM(0);\n\tDatum timestamp = PG_GETARG_DATUM(1);\n\n\tif (TIMESTAMP_NOT_FINITE(DatumGetTimestampTz(timestamp)))\n\t\tPG_RETURN_DATUM(timestamp);\n\n\t/* Apply offset. */\n\ttimestamp = DirectFunctionCall2(timestamptz_mi_interval, timestamp, PG_GETARG_DATUM(2));\n\ttimestamp = DirectFunctionCall2(ts_timestamptz_bucket, period, timestamp);\n\n\t/* Remove offset. */\n\ttimestamp = DirectFunctionCall2(timestamptz_pl_interval, timestamp, PG_GETARG_DATUM(2));\n\tPG_RETURN_DATUM(timestamp);\n}\n\nTS_FUNCTION_INFO_V1(ts_timestamptz_timezone_bucket);\n\n/*\n * time_bucket(bucket_width INTERVAL, ts TIMESTAMPTZ, timezone TEXT, origin TIMESTAMPTZ DEFAULT\n * NULL, \"offset\" INTERVAL DEFAULT NULL) RETURNS TIMESTAMPTZ\n */\nTSDLLEXPORT Datum\nts_timestamptz_timezone_bucket(PG_FUNCTION_ARGS)\n{\n\tDatum period = PG_GETARG_DATUM(0);\n\tDatum timestamp = PG_GETARG_DATUM(1);\n\tDatum tzname = PG_GETARG_DATUM(2);\n\n\t/*\n\t * When called from SQL we will always have 5 args because default values\n\t * will be filled in for missing arguments. When called from C with\n\t * DirectFunctionCall number of arguments might be less than 5.\n\t */\n\tbool have_origin = PG_NARGS() > 3 && !PG_ARGISNULL(3);\n\tbool have_offset = PG_NARGS() > 4 && !PG_ARGISNULL(4);\n\n\t/*\n\t * We need to check for NULL arguments here because the function cannot be\n\t * defined STRICT due to the optional arguments.\n\t */\n\tif (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2))\n\t\tPG_RETURN_NULL();\n\n\t/*\n\t * Apply offset in UTC space to avoid DST issues (issue #7059).\n\t * If we applied the offset after converting to local time, we could\n\t * create non-existent times during DST transitions.\n\t */\n\tif (have_offset)\n\t{\n\t\ttimestamp = DirectFunctionCall2(timestamptz_mi_interval, timestamp, PG_GETARG_DATUM(4));\n\t}\n\n\t/* Convert to local timestamp according to timezone */\n\tDatum local_ts = DirectFunctionCall2(timestamptz_zone, tzname, timestamp);\n\n\tif (have_origin)\n\t{\n\t\tDatum origin = DirectFunctionCall2(timestamptz_zone, tzname, PG_GETARG_DATUM(3));\n\t\tlocal_ts = DirectFunctionCall3(ts_timestamp_bucket, period, local_ts, origin);\n\t}\n\telse\n\t{\n\t\tlocal_ts = DirectFunctionCall2(ts_timestamp_bucket, period, local_ts);\n\t}\n\n\t/* Convert back to timestamptz */\n\tDatum result = DirectFunctionCall2(timestamp_zone, tzname, local_ts);\n\n\t/*\n\t * During DST fall-back, the same local time maps to two different UTC\n\t * times. PostgreSQL's timestamp_zone picks the later (standard time)\n\t * interpretation. If the original timestamp was in daylight time, the\n\t * bucket could start AFTER the timestamp. Fix by subtracting periods\n\t * until the bucket is at or before the timestamp (issue #9136).\n\t */\n\twhile (DatumGetTimestampTz(result) > DatumGetTimestampTz(timestamp))\n\t{\n\t\tresult = DirectFunctionCall2(timestamptz_mi_interval, result, period);\n\t}\n\n\tif (have_offset)\n\t{\n\t\t/* Remove offset in UTC space. */\n\t\tresult = DirectFunctionCall2(timestamptz_pl_interval, result, PG_GETARG_DATUM(4));\n\t}\n\n\tPG_RETURN_DATUM(result);\n}\n\nstatic inline void\ncheck_period_is_daily(int64 period)\n{\n\tint64 day = USECS_PER_DAY;\n\n\tif (period < day)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"interval must not have sub-day precision\")));\n\t}\n\tif (period % day != 0)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"interval must be a multiple of a day\")));\n\t}\n}\n\nTS_FUNCTION_INFO_V1(ts_date_bucket);\n\nTSDLLEXPORT Datum\nts_date_bucket(PG_FUNCTION_ARGS)\n{\n\tInterval *interval = PG_GETARG_INTERVAL_P(0);\n\tDateADT date = PG_GETARG_DATEADT(1);\n\tDateADT origin = 0;\n\tTimestamp origin_ts = DEFAULT_ORIGIN;\n\tTimestamp timestamp, result;\n\n\tif (DATE_NOT_FINITE(date))\n\t\tPG_RETURN_DATEADT(date);\n\n\t/* convert to timestamp (NOT tz), bucket, convert back to date */\n\ttimestamp = DatumGetTimestamp(DirectFunctionCall1(date_timestamp, PG_GETARG_DATUM(1)));\n\tAssert(!TIMESTAMP_NOT_FINITE(timestamp));\n\n\tif (PG_NARGS() > 2)\n\t{\n\t\torigin = PG_GETARG_DATEADT(2);\n\t\tif (!interval->month)\n\t\t\torigin_ts =\n\t\t\t\tDatumGetTimestamp(DirectFunctionCall1(date_timestamp, DateADTGetDatum(origin)));\n\t}\n\n\tif (interval->month)\n\t{\n\t\tvalidate_month_bucket(interval);\n\n\t\tdate = bucket_month(interval->month, date, origin);\n\t\tPG_RETURN_DATEADT(date);\n\t}\n\telse\n\t{\n\t\tint64 period = get_interval_period_timestamp_units(interval);\n\t\t/* check the period aligns on a date */\n\t\tcheck_period_is_daily(period);\n\n\t\tTIME_BUCKET_TS(period, timestamp, result, origin_ts);\n\t\tPG_RETURN_DATUM(DirectFunctionCall1(timestamp_date, TimestampGetDatum(result)));\n\t}\n}\n\nTS_FUNCTION_INFO_V1(ts_date_offset_bucket);\n\nTSDLLEXPORT Datum\nts_date_offset_bucket(PG_FUNCTION_ARGS)\n{\n\tDatum period = PG_GETARG_DATUM(0);\n\tDatum date = PG_GETARG_DATUM(1);\n\n\tif (DATE_NOT_FINITE(DatumGetDateADT(date)))\n\t\tPG_RETURN_DATUM(date);\n\n\t/* Apply offset. */\n\tDatum time = DirectFunctionCall2(date_mi_interval, date, PG_GETARG_DATUM(2));\n\tdate = DirectFunctionCall1(timestamp_date, time);\n\tdate = DirectFunctionCall2(ts_date_bucket, period, date);\n\n\t/* Remove offset. */\n\ttime = DirectFunctionCall2(date_pl_interval, date, PG_GETARG_DATUM(2));\n\tdate = DirectFunctionCall1(timestamp_date, time);\n\tPG_RETURN_DATUM(date);\n}\n\nstatic const char *\nuuid_to_str(const pg_uuid_t *uuid)\n{\n\treturn DatumGetCString(DirectFunctionCall1(uuid_out, UUIDPGetDatum(uuid)));\n}\n\nTS_FUNCTION_INFO_V1(ts_uuid_bucket);\n\nDatum\nts_uuid_bucket(PG_FUNCTION_ARGS)\n{\n\tpg_uuid_t *uuid = PG_GETARG_UUID_P(1);\n\tDatum origin = (PG_NARGS() > 2 ? PG_GETARG_DATUM(2) : TimestampTzGetDatum(DEFAULT_ORIGIN));\n\tTimestampTz timestamp;\n\n\tif (!ts_uuid_v7_extract_timestamptz(uuid, &timestamp, false))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"not a version 7 UUID: %s\", uuid_to_str(uuid))));\n\n\tPG_RETURN_DATUM(DirectFunctionCall3(ts_timestamptz_bucket,\n\t\t\t\t\t\t\t\t\t\tPG_GETARG_DATUM(0),\n\t\t\t\t\t\t\t\t\t\tTimestampTzGetDatum(timestamp),\n\t\t\t\t\t\t\t\t\t\torigin));\n}\n\nTS_FUNCTION_INFO_V1(ts_uuid_offset_bucket);\n\nTSDLLEXPORT Datum\nts_uuid_offset_bucket(PG_FUNCTION_ARGS)\n{\n\tpg_uuid_t *uuid = PG_GETARG_UUID_P(1);\n\tTimestampTz timestamp;\n\n\tif (!ts_uuid_v7_extract_timestamptz(uuid, &timestamp, false))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"not a version 7 UUID: %s\", uuid_to_str(uuid))));\n\tLOCAL_FCINFO(fcinfo_local, 3);\n\tDatum result;\n\n\tInitFunctionCallInfoData(*fcinfo_local, NULL, 3, InvalidOid, NULL, NULL);\n\n\tfcinfo_local->args[0].value = PG_GETARG_DATUM(0); /* Period */\n\tfcinfo_local->args[0].isnull = PG_ARGISNULL(0);\n\tfcinfo_local->args[1].value = TimestampTzGetDatum(timestamp);\n\tfcinfo_local->args[1].isnull = PG_ARGISNULL(1);\n\tfcinfo_local->args[2].value = PG_GETARG_DATUM(2);\n\tfcinfo_local->args[2].isnull = PG_ARGISNULL(2);\n\n\tresult = ts_timestamptz_offset_bucket(fcinfo_local);\n\n\t/* Timestamp offset bucket does not return NULL */\n\tAssert(!fcinfo_local->isnull);\n\n\treturn result;\n}\n\nTS_FUNCTION_INFO_V1(ts_uuid_timezone_bucket);\n\n/*\n * time_bucket(bucket_width INTERVAL, ts uuid, timezone TEXT, origin uuid DEFAULT\n * NULL, \"offset\" INTERVAL DEFAULT NULL) RETURNS TIMESTAMPTZ\n */\nTSDLLEXPORT Datum\nts_uuid_timezone_bucket(PG_FUNCTION_ARGS)\n{\n\t/*\n\t * We need to check for NULL arguments here because the function cannot be\n\t * defined STRICT due to the optional arguments.\n\t */\n\tif (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2))\n\t\tPG_RETURN_NULL();\n\n\tpg_uuid_t *uuid = PG_GETARG_UUID_P(1);\n\tTimestampTz timestamp;\n\n\tif (!ts_uuid_v7_extract_timestamptz(uuid, &timestamp, false))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"not a version 7 UUID: %s\", uuid_to_str(uuid))));\n\n\tLOCAL_FCINFO(fcinfo_local, 4);\n\tDatum result;\n\n\tInitFunctionCallInfoData(*fcinfo_local, NULL, 4, InvalidOid, NULL, NULL);\n\n\tfcinfo_local->args[0].value = PG_GETARG_DATUM(0); /* Period */\n\tfcinfo_local->args[0].isnull = PG_ARGISNULL(0);\n\tfcinfo_local->args[1].value = TimestampTzGetDatum(timestamp);\n\tfcinfo_local->args[1].isnull = PG_ARGISNULL(1);\n\tfcinfo_local->args[2].value = PG_GETARG_DATUM(2);\n\tfcinfo_local->args[2].isnull = PG_ARGISNULL(2);\n\tfcinfo_local->args[3].value = PG_GETARG_DATUM(3);\n\tfcinfo_local->args[3].isnull = PG_ARGISNULL(3);\n\n\tresult = ts_timestamptz_timezone_bucket(fcinfo_local);\n\n\t/* The only case where the timestamp bucket function returns NULL is when passed NULL input\n\t * arguments, but we already checked for that above. */\n\tAssert(!fcinfo_local->isnull);\n\n\treturn result;\n}\n\nTSDLLEXPORT int64\nts_time_bucket_by_type(int64 interval, int64 timestamp, Oid timestamp_type)\n{\n\tNullableDatum null_datum = INIT_NULL_DATUM;\n\treturn ts_time_bucket_by_type_extended(interval,\n\t\t\t\t\t\t\t\t\t\t   timestamp,\n\t\t\t\t\t\t\t\t\t\t   timestamp_type,\n\t\t\t\t\t\t\t\t\t\t   null_datum,\n\t\t\t\t\t\t\t\t\t\t   null_datum);\n}\n\n/* when working with time_buckets stored in our catalog, we may not know ahead of time which\n * bucketing function to use, this function dynamically dispatches to the correct time_bucket_<foo>\n * based on an inputted timestamp_type\n */\nTSDLLEXPORT int64\nts_time_bucket_by_type_extended(int64 interval, int64 timestamp, Oid timestamp_type,\n\t\t\t\t\t\t\t\tNullableDatum offset, NullableDatum origin)\n{\n\t/* Defined offset and origin in one function is not supported */\n\tAssert(offset.isnull == true || origin.isnull == true);\n\n\tDatum timestamp_in_time_type = ts_internal_to_time_value(timestamp, timestamp_type);\n\tDatum interval_in_interval_type;\n\tDatum time_bucketed;\n\tDatum (*bucket_function)(PG_FUNCTION_ARGS);\n\n\tswitch (timestamp_type)\n\t{\n\t\tcase INT2OID:\n\t\t\tinterval_in_interval_type = ts_internal_to_interval_value(interval, timestamp_type);\n\t\t\tbucket_function = ts_int16_bucket;\n\t\t\tbreak;\n\t\tcase INT4OID:\n\t\t\tinterval_in_interval_type = ts_internal_to_interval_value(interval, timestamp_type);\n\t\t\tbucket_function = ts_int32_bucket;\n\t\t\tbreak;\n\t\tcase INT8OID:\n\t\t\tinterval_in_interval_type = ts_internal_to_interval_value(interval, timestamp_type);\n\t\t\tbucket_function = ts_int64_bucket;\n\t\t\tbreak;\n\t\tcase TIMESTAMPOID:\n\t\t\tinterval_in_interval_type = ts_internal_to_interval_value(interval, INTERVALOID);\n\t\t\tif (offset.isnull)\n\t\t\t\tbucket_function = ts_timestamp_bucket; /* handles also origin */\n\t\t\telse\n\t\t\t\tbucket_function = ts_timestamp_offset_bucket;\n\t\t\tbreak;\n\t\tcase TIMESTAMPTZOID:\n\t\t\tinterval_in_interval_type = ts_internal_to_interval_value(interval, INTERVALOID);\n\t\t\tif (offset.isnull)\n\t\t\t\tbucket_function = ts_timestamptz_bucket; /* handles also origin */\n\t\t\telse\n\t\t\t\tbucket_function = ts_timestamptz_offset_bucket;\n\t\t\tbreak;\n\t\tcase DATEOID:\n\t\t\tinterval_in_interval_type = ts_internal_to_interval_value(interval, INTERVALOID);\n\t\t\tif (offset.isnull)\n\t\t\t\tbucket_function = ts_date_bucket; /* handles also origin */\n\t\t\telse\n\t\t\t\tbucket_function = ts_date_offset_bucket;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\telog(ERROR, \"invalid time_bucket type \\\"%s\\\"\", format_type_be(timestamp_type));\n\t}\n\n\tif (!offset.isnull)\n\t{\n\t\ttime_bucketed = DirectFunctionCall3(bucket_function,\n\t\t\t\t\t\t\t\t\t\t\tinterval_in_interval_type,\n\t\t\t\t\t\t\t\t\t\t\ttimestamp_in_time_type,\n\t\t\t\t\t\t\t\t\t\t\toffset.value);\n\t}\n\telse if (!origin.isnull)\n\t{\n\t\ttime_bucketed = DirectFunctionCall3(bucket_function,\n\t\t\t\t\t\t\t\t\t\t\tinterval_in_interval_type,\n\t\t\t\t\t\t\t\t\t\t\ttimestamp_in_time_type,\n\t\t\t\t\t\t\t\t\t\t\torigin.value);\n\t}\n\telse\n\t{\n\t\ttime_bucketed =\n\t\t\tDirectFunctionCall2(bucket_function, interval_in_interval_type, timestamp_in_time_type);\n\t}\n\n\treturn ts_time_value_to_internal(time_bucketed, timestamp_type);\n}\n"
  },
  {
    "path": "src/time_bucket.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <fmgr.h>\n\n#include \"export.h\"\n\nextern TSDLLEXPORT Datum ts_int16_bucket(PG_FUNCTION_ARGS);\nextern TSDLLEXPORT Datum ts_int32_bucket(PG_FUNCTION_ARGS);\nextern TSDLLEXPORT Datum ts_int64_bucket(PG_FUNCTION_ARGS);\nextern TSDLLEXPORT Datum ts_date_bucket(PG_FUNCTION_ARGS);\nextern TSDLLEXPORT Datum ts_timestamp_bucket(PG_FUNCTION_ARGS);\nextern TSDLLEXPORT Datum ts_timestamp_offset_bucket(PG_FUNCTION_ARGS);\nextern TSDLLEXPORT Datum ts_timestamptz_bucket(PG_FUNCTION_ARGS);\nextern TSDLLEXPORT Datum ts_timestamptz_timezone_bucket(PG_FUNCTION_ARGS);\nextern TSDLLEXPORT int64 ts_time_bucket_by_type(int64 interval, int64 timestamp, Oid type);\nextern TSDLLEXPORT int64 ts_time_bucket_by_type_extended(int64 interval, int64 timestamp, Oid type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t NullableDatum offset,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t NullableDatum origin);\n"
  },
  {
    "path": "src/time_utils.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/xact.h>\n#include <catalog/pg_type.h>\n#include <fmgr.h>\n#include <parser/parse_coerce.h>\n#include <utils/builtins.h>\n#include <utils/date.h>\n#include <utils/fmgrprotos.h>\n#include <utils/rangetypes.h>\n#include <utils/timestamp.h>\n#include <utils/uuid.h>\n\n#include \"guc.h\"\n#include \"time_utils.h\"\n#include \"utils.h\"\n#include \"uuid.h\"\n\nTS_FUNCTION_INFO_V1(ts_make_range_from_internal_time);\nTS_FUNCTION_INFO_V1(ts_get_internal_time_min);\nTS_FUNCTION_INFO_V1(ts_get_internal_time_max);\n\n/*\n * Subtract an interval from the current time and return the result as a Datum\n * of the specified time type.\n *\n * In debug mode, uses mock time if configured for testing purposes.\n */\nTSDLLEXPORT Datum\nts_subtract_interval_from_now(const Interval *interval, Oid timetype)\n{\n#ifdef TS_DEBUG\n\tDatum res = ts_get_mock_time_or_current_time();\n#else\n\tDatum res = TimestampTzGetDatum(GetCurrentTransactionStartTimestamp());\n#endif\n\n\tswitch (timetype)\n\t{\n\t\tcase TIMESTAMPOID:\n\t\t\tres = DirectFunctionCall1(timestamptz_timestamp, res);\n\t\t\treturn DirectFunctionCall2(timestamp_mi_interval, res, IntervalPGetDatum(interval));\n\t\tcase TIMESTAMPTZOID:\n\t\t\treturn DirectFunctionCall2(timestamptz_mi_interval, res, IntervalPGetDatum(interval));\n\t\tcase DATEOID:\n\t\t\tres = DirectFunctionCall1(timestamptz_timestamp, res);\n\t\t\tres = DirectFunctionCall2(timestamp_mi_interval, res, IntervalPGetDatum(interval));\n\t\t\treturn DirectFunctionCall1(timestamp_date, res);\n\t\tcase UUIDOID:\n\t\t{\n\t\t\t/*\n\t\t\t * For UUIDv7-partitioned hypertables, compute (now - interval) and convert\n\t\t\t * to a UUIDv7 boundary value suitable for range comparisons.\n\t\t\t */\n\t\t\tres = DirectFunctionCall2(timestamptz_mi_interval, res, IntervalPGetDatum(interval));\n\t\t\tTimestampTz boundary_ts = DatumGetTimestampTz(res);\n\t\t\tpg_uuid_t *uuid = ts_create_uuid_v7_from_unixtime_us(boundary_ts, true, true);\n\t\t\treturn UUIDPGetDatum(uuid);\n\t\t}\n\t\tdefault:\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"unsupported time type %s\", format_type_be(timetype))));\n\t\t\tpg_unreachable();\n\t}\n}\n\nDatum\nts_time_datum_convert_arg(Datum arg, Oid *argtype, Oid timetype)\n{\n\tOid type = *argtype;\n\n\tif (!OidIsValid(type) || type == UNKNOWNOID)\n\t{\n\t\tOid infuncid = InvalidOid;\n\t\tOid typeioparam;\n\n\t\ttype = timetype;\n\t\tgetTypeInputInfo(type, &infuncid, &typeioparam);\n\n\t\tswitch (get_func_nargs(infuncid))\n\t\t{\n\t\t\tcase 1:\n\t\t\t\t/* Functions that take one input argument, e.g., the Date function */\n\t\t\t\targ = OidFunctionCall1(infuncid, arg);\n\t\t\t\tbreak;\n\t\t\tcase 3:\n\t\t\t\t/* Timestamp functions take three input arguments */\n\t\t\t\targ = OidFunctionCall3(infuncid,\n\t\t\t\t\t\t\t\t\t   arg,\n\t\t\t\t\t\t\t\t\t   ObjectIdGetDatum(InvalidOid),\n\t\t\t\t\t\t\t\t\t   Int32GetDatum(-1));\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t\t errmsg(\"invalid time argument\"),\n\t\t\t\t\t\t errhint(\"Time argument requires an explicit cast.\")));\n\t\t}\n\n\t\t*argtype = type;\n\t}\n\n\treturn arg;\n}\n\n/*\n * Get the internal time value from a pseudo-type function argument.\n *\n * API functions that take supported time types as arguments often use a\n * pseudo-type parameter to represent these. For instance, the \"any\"\n * pseudo-type is often used to represent any of these supported types.\n *\n * The downside of \"any\", however, is that it lacks type information and often\n * forces users to add explicit type casts. For instance, with the following\n * API call\n *\n * drop_chunk('conditions', '2020-10-01');\n *\n * the argument type will be UNKNOWNOID. And the user would have to add\n * an explicit type cast:\n *\n * drop_chunks('conditions', '2020-10-01'::date);\n *\n * However, we can handle the UNKNOWNOID case since we have the time type\n * information in internal metadata (e.g., the time column type of a\n * hypertable) and we can try to convert the argument to that type.\n *\n * Thus, there are two cases:\n *\n * 1. An explicit cast was done --> the type is given in argtype.\n * 2. No cast was done --> we try to convert the argument to the known time\n *    type.\n *\n * If an unsupported type is given, or the typeless argument has a nonsensical\n * string, then there will be an error raised.\n */\nint64\nts_time_value_from_arg(Datum arg, Oid argtype, Oid timetype, bool need_now_func)\n{\n\t/* If no explicit cast was done by the user, try to convert the argument\n\t * to the time type. */\n\targ = ts_time_datum_convert_arg(arg, &argtype, timetype);\n\n\tif (IS_INTEGER_TYPE(timetype) && (argtype == INTERVALOID || IS_TIMESTAMP_TYPE(argtype)))\n\t{\n\t\tif (need_now_func)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"invalid time argument type \\\"%s\\\"\", format_type_be(argtype)),\n\t\t\t\t\t errhint(\"Try casting the argument to \\\"%s\\\".\", format_type_be(timetype))));\n\t\t/*\n\t\t * The argument type is INTERVAL or TIMESTAMP-like for INTEGER column; this signifies that\n\t\t * chunks are retained based on chunk creation time. Chunk creation time is represented\n\t\t * as TIMESTAMPTZ, the input argument should be typecast to TIMESTAMPTZ.\n\t\t */\n\t\tif (argtype == INTERVALOID)\n\t\t\targ = ts_subtract_interval_from_now(DatumGetIntervalP(arg), TIMESTAMPTZOID);\n\n\t\treturn DatumGetInt64(arg);\n\t}\n\n\tif (argtype == INTERVALOID)\n\t{\n\t\targ = ts_subtract_interval_from_now(DatumGetIntervalP(arg), timetype);\n\t\targtype = timetype;\n\t}\n\telse if (argtype != timetype && !can_coerce_type(1, &argtype, &timetype, COERCION_IMPLICIT))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid time argument type \\\"%s\\\"\", format_type_be(argtype)),\n\t\t\t\t errhint(\"Try casting the argument to \\\"%s\\\".\", format_type_be(timetype))));\n\n\treturn ts_time_value_to_internal(arg, argtype);\n}\n\n/*\n * Try to coerce a type to a supported time type.\n *\n * To support custom time types in hypertables, we need to know the type's\n * boundaries in order to, e.g., construct dimensional chunk constraints. The\n * custom time type will inherit the valid time range of the supported time\n * type it can be casted to.\n *\n * Currently, we only support custom time types that are binary compatible\n * with bigint and then it also inherits the valid time range of a bigint.\n */\nstatic Oid\ncoerce_to_time_type(Oid type)\n{\n\tif (ts_type_is_int8_binary_compatible(type))\n\t\treturn INT8OID;\n\n\tereport(ERROR,\n\t\t\terrcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\terrmsg(\"unsupported time type \\\"%s\\\"\",\n\t\t\t\t   DatumGetPointer(DirectFunctionCall1(regtypeout, type))));\n\tpg_unreachable();\n}\n\n/*\n * Get the min time datum for a time type.\n *\n * Note that the min is not the same the actual \"min\" of the underlying\n * storage for date and timestamps.\n */\nDatum\nts_time_datum_get_min(Oid timetype)\n{\n\tswitch (timetype)\n\t{\n\t\tcase DATEOID:\n\t\t\treturn DateADTGetDatum(TS_DATE_MIN);\n\t\tcase TIMESTAMPOID:\n\t\t\treturn TimestampGetDatum(TS_TIMESTAMP_MIN);\n\t\tcase TIMESTAMPTZOID:\n\t\t\treturn TimestampTzGetDatum(TS_TIMESTAMP_MIN);\n\t\tcase INT2OID:\n\t\t\treturn Int16GetDatum(PG_INT16_MIN);\n\t\tcase INT4OID:\n\t\t\treturn Int32GetDatum(PG_INT32_MIN);\n\t\tcase INT8OID:\n\t\t\treturn Int64GetDatum(PG_INT64_MIN);\n\t\tcase UUIDOID:\n\t\t\treturn Int64GetDatum(TS_TIME_UUID_MIN);\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\n\treturn ts_time_datum_get_min(coerce_to_time_type(timetype));\n}\n\n/*\n * Get the end time datum for a time type.\n *\n * Note that the end is not the same as \"max\" (hence not named max). The end\n * is exclusive for date and timestamps, and might not be the same as the max\n * value for the underlying storage type (e.g., TIMESTAMP_END is before\n * PG_INT64_MAX). Instead, the max value for dates and timestamps represent\n * -Infinity and +Infinity.\n */\nDatum\nts_time_datum_get_end(Oid timetype)\n{\n\tswitch (timetype)\n\t{\n\t\tcase DATEOID:\n\t\t\treturn DateADTGetDatum(TS_DATE_END);\n\t\tcase TIMESTAMPOID:\n\t\t\treturn TimestampGetDatum(TS_TIMESTAMP_END);\n\t\tcase TIMESTAMPTZOID:\n\t\t\treturn TimestampTzGetDatum(TS_TIMESTAMP_END);\n\t\tcase INT2OID:\n\t\tcase INT4OID:\n\t\tcase INT8OID:\n\t\tcase UUIDOID:\n\t\t\telog(ERROR, \"END is not defined for \\\"%s\\\"\", format_type_be(timetype));\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\n\treturn ts_time_datum_get_end(coerce_to_time_type(timetype));\n}\n\nDatum\nts_time_datum_get_max(Oid timetype)\n{\n\tswitch (timetype)\n\t{\n\t\tcase DATEOID:\n\t\t\treturn DateADTGetDatum(TS_DATE_END - 1);\n\t\tcase TIMESTAMPOID:\n\t\t\treturn TimestampGetDatum(TS_TIMESTAMP_END - 1);\n\t\tcase TIMESTAMPTZOID:\n\t\t\treturn TimestampTzGetDatum(TS_TIMESTAMP_END - 1);\n\t\tcase INT2OID:\n\t\t\treturn Int16GetDatum(PG_INT16_MAX);\n\t\tcase INT4OID:\n\t\t\treturn Int32GetDatum(PG_INT32_MAX);\n\t\tcase INT8OID:\n\t\t\treturn Int64GetDatum(PG_INT64_MAX);\n\t\tcase UUIDOID:\n\t\t\treturn Int64GetDatum(TS_TIME_UUID_MAX);\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\n\treturn ts_time_datum_get_max(coerce_to_time_type(timetype));\n}\n\nDatum\nts_time_datum_get_nobegin(Oid timetype)\n{\n\tswitch (timetype)\n\t{\n\t\tcase DATEOID:\n\t\t\treturn DateADTGetDatum(DATEVAL_NOBEGIN);\n\t\tcase TIMESTAMPOID:\n\t\t\treturn TimestampGetDatum(DT_NOBEGIN);\n\t\tcase TIMESTAMPTZOID:\n\t\t\treturn TimestampTzGetDatum(DT_NOBEGIN);\n\t\tcase INT2OID:\n\t\tcase INT4OID:\n\t\tcase INT8OID:\n\t\tcase UUIDOID:\n\t\t\telog(ERROR, \"NOBEGIN is not defined for \\\"%s\\\"\", format_type_be(timetype));\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\n\treturn ts_time_datum_get_nobegin(coerce_to_time_type(timetype));\n}\n\nDatum\nts_time_datum_get_nobegin_or_min(Oid timetype)\n{\n\tif (IS_TIMESTAMP_TYPE(timetype))\n\t\treturn ts_time_datum_get_nobegin(timetype);\n\n\treturn ts_time_datum_get_min(timetype);\n}\n\nDatum\nts_time_datum_get_noend(Oid timetype)\n{\n\tswitch (timetype)\n\t{\n\t\tcase DATEOID:\n\t\t\treturn DateADTGetDatum(DATEVAL_NOEND);\n\t\tcase TIMESTAMPOID:\n\t\t\treturn TimestampGetDatum(DT_NOEND);\n\t\tcase TIMESTAMPTZOID:\n\t\t\treturn TimestampTzGetDatum(DT_NOEND);\n\t\tcase INT2OID:\n\t\tcase INT4OID:\n\t\tcase INT8OID:\n\t\tcase UUIDOID:\n\t\t\telog(ERROR, \"NOEND is not defined for \\\"%s\\\"\", format_type_be(timetype));\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\n\treturn ts_time_datum_get_noend(coerce_to_time_type(timetype));\n}\n\n/*\n * Get the min for a time type in internal time.\n */\nint64\nts_time_get_min(Oid timetype)\n{\n\tswitch (timetype)\n\t{\n\t\tcase DATEOID:\n\t\t\treturn TS_DATE_INTERNAL_MIN;\n\t\tcase TIMESTAMPOID:\n\t\t\treturn TS_TIMESTAMP_INTERNAL_MIN;\n\t\tcase TIMESTAMPTZOID:\n\t\t\treturn TS_TIMESTAMP_INTERNAL_MIN;\n\t\tcase INT2OID:\n\t\t\treturn PG_INT16_MIN;\n\t\tcase INT4OID:\n\t\t\treturn PG_INT32_MIN;\n\t\tcase INT8OID:\n\t\t\treturn PG_INT64_MIN;\n\t\tcase UUIDOID:\n\t\t\treturn TS_TIME_UUID_MIN;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\n\treturn ts_time_get_min(coerce_to_time_type(timetype));\n}\n\n/*\n * Get the max for a time type in internal time.\n */\nint64\nts_time_get_max(Oid timetype)\n{\n\tswitch (timetype)\n\t{\n\t\tcase DATEOID:\n\t\t\treturn TS_DATE_INTERNAL_END - 1;\n\t\tcase TIMESTAMPOID:\n\t\t\treturn TS_TIMESTAMP_INTERNAL_END - 1;\n\t\tcase TIMESTAMPTZOID:\n\t\t\treturn TS_TIMESTAMP_INTERNAL_END - 1;\n\t\tcase INT2OID:\n\t\t\treturn PG_INT16_MAX;\n\t\tcase INT4OID:\n\t\t\treturn PG_INT32_MAX;\n\t\tcase INT8OID:\n\t\t\treturn PG_INT64_MAX;\n\t\tcase UUIDOID:\n\t\t\treturn TS_TIME_UUID_MAX;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\n\treturn ts_time_get_max(coerce_to_time_type(timetype));\n}\n\n/*\n * Get the end value time for a time type in internal time.\n *\n * The end is not a valid time value (it is exclusive).\n */\nint64\nts_time_get_end(Oid timetype)\n{\n\tswitch (timetype)\n\t{\n\t\tcase DATEOID:\n\t\t\treturn TS_DATE_INTERNAL_END;\n\t\tcase TIMESTAMPOID:\n\t\t\treturn TS_TIMESTAMP_INTERNAL_END;\n\t\tcase TIMESTAMPTZOID:\n\t\t\treturn TS_TIMESTAMP_INTERNAL_END;\n\t\tcase INT2OID:\n\t\tcase INT4OID:\n\t\tcase INT8OID:\n\t\tcase UUIDOID:\n\t\t\telog(ERROR, \"END is not defined for \\\"%s\\\"\", format_type_be(timetype));\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\n\treturn ts_time_get_end(coerce_to_time_type(timetype));\n}\n\n/*\n * Return the end (exclusive) or fall back to max.\n *\n * Integer time types have no definition for END, so we fall back to max.\n */\nint64\nts_time_get_end_or_max(Oid timetype)\n{\n\tif (IS_TIMESTAMP_TYPE(timetype))\n\t\treturn ts_time_get_end(timetype);\n\n\treturn ts_time_get_max(timetype);\n}\n\nint64\nts_time_get_nobegin(Oid timetype)\n{\n\tswitch (timetype)\n\t{\n\t\tcase DATEOID:\n\t\tcase TIMESTAMPOID:\n\t\tcase TIMESTAMPTZOID:\n\t\t\treturn TS_TIME_NOBEGIN;\n\t\tcase INT2OID:\n\t\tcase INT4OID:\n\t\tcase INT8OID:\n\t\tcase UUIDOID:\n\t\t\telog(ERROR, \"-Infinity not defined for \\\"%s\\\"\", format_type_be(timetype));\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\n\treturn ts_time_get_nobegin(coerce_to_time_type(timetype));\n}\n\nint64\nts_time_get_nobegin_or_min(Oid timetype)\n{\n\tif (IS_TIMESTAMP_TYPE(timetype))\n\t\treturn ts_time_get_nobegin(timetype);\n\n\treturn ts_time_get_min(timetype);\n}\n\nint64\nts_time_get_noend(Oid timetype)\n{\n\tswitch (timetype)\n\t{\n\t\tcase DATEOID:\n\t\tcase TIMESTAMPOID:\n\t\tcase TIMESTAMPTZOID:\n\t\t\treturn TS_TIME_NOEND;\n\t\tcase INT2OID:\n\t\tcase INT4OID:\n\t\tcase INT8OID:\n\t\tcase UUIDOID:\n\t\t\telog(ERROR, \"+Infinity not defined for \\\"%s\\\"\", format_type_be(timetype));\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\n\treturn ts_time_get_noend(coerce_to_time_type(timetype));\n}\n\nint64\nts_time_get_noend_or_max(Oid timetype)\n{\n\tif (IS_TIMESTAMP_TYPE(timetype))\n\t\treturn ts_time_get_noend(timetype);\n\n\treturn ts_time_get_max(timetype);\n}\n\n/*\n * Add an interval to a time value in a saturating way.\n *\n * In contrast to, e.g., PG's timestamp_pl_interval, this function adds an\n * interval in a saturating way without throwing an error in case of\n * overflow. Instead it clamps to max for integer types end NOEND for date and\n * timestamp types.\n */\nint64\nts_time_saturating_add(int64 timeval, int64 interval, Oid timetype)\n{\n\tif (timeval > 0 && interval > 0 && timeval > (ts_time_get_max(timetype) - interval))\n\t\treturn ts_time_get_noend_or_max(timetype);\n\n\tif (timeval < 0 && interval < 0 && timeval < (ts_time_get_min(timetype) - interval))\n\t\treturn ts_time_get_nobegin_or_min(timetype);\n\n\treturn timeval + interval;\n}\n\n/*\n * Subtract an interval from a time value in a saturating way.\n *\n * In contrast to, e.g., PG's timestamp_mi_interval, this function subtracts\n * an interval in a saturating way without throwing an error in case of\n * overflow. Instead, it clamps to min for integer types and NOBEGIN for date\n * and timestamp types.\n */\nint64\nts_time_saturating_sub(int64 timeval, int64 interval, Oid timetype)\n{\n\tif (timeval < 0 && interval > 0 && timeval < (ts_time_get_min(timetype) + interval))\n\t\treturn ts_time_get_nobegin_or_min(timetype);\n\n\tif (timeval > 0 && interval < 0 && timeval > (ts_time_get_max(timetype) + interval))\n\t\treturn ts_time_get_noend_or_max(timetype);\n\n\treturn timeval - interval;\n}\n\nint64\nts_subtract_integer_from_now_saturating(Oid now_func, int64 interval, Oid timetype)\n{\n\tDatum now = OidFunctionCall0(now_func);\n\tint64 time_min = ts_time_get_min(timetype);\n\tint64 time_max = ts_time_get_max(timetype);\n\tint64 nowval, res;\n\n\tAssert(IS_INTEGER_TYPE(timetype));\n\tswitch (timetype)\n\t{\n\t\tcase INT2OID:\n\t\t{\n\t\t\tnowval = DatumGetInt16(now);\n\t\t\tbreak;\n\t\t}\n\t\tcase INT4OID:\n\t\t{\n\t\t\tnowval = DatumGetInt32(now);\n\t\t\tbreak;\n\t\t}\n\t\tcase INT8OID:\n\t\t{\n\t\t\tnowval = DatumGetInt64(now);\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\telog(ERROR, \"unsupported integer time type \\\"%s\\\"\", format_type_be(timetype));\n\t}\n\tif (nowval > 0 && interval < 0 && nowval > time_max + interval)\n\t\tres = time_max;\n\telse if (nowval < 0 && interval > 0 && nowval < time_min + interval)\n\t\tres = time_min;\n\telse\n\t\tres = nowval - interval;\n\treturn res;\n}\n\n#ifdef TS_DEBUG\n/* return mock time for testing */\nDatum\nts_get_mock_time_or_current_time(void)\n{\n\tDatum res;\n\tif (ts_current_timestamp_mock != NULL && strlen(ts_current_timestamp_mock) != 0)\n\t{\n\t\tres = DirectFunctionCall3(timestamptz_in,\n\t\t\t\t\t\t\t\t  CStringGetDatum(ts_current_timestamp_mock),\n\t\t\t\t\t\t\t\t  0,\n\t\t\t\t\t\t\t\t  Int32GetDatum(-1));\n\t\treturn res;\n\t}\n\tres = TimestampTzGetDatum(GetCurrentTransactionStartTimestamp());\n\treturn res;\n}\n#endif\n\nTS_FUNCTION_INFO_V1(ts_now_mock);\n\n/* return mock time for testing */\nDatum\nts_now_mock(PG_FUNCTION_ARGS)\n{\n\tDatum res;\n#ifdef TS_DEBUG\n\tif (ts_current_timestamp_mock != NULL && strlen(ts_current_timestamp_mock) != 0)\n\t{\n\t\tres = DirectFunctionCall3(timestamptz_in,\n\t\t\t\t\t\t\t\t  CStringGetDatum(ts_current_timestamp_mock),\n\t\t\t\t\t\t\t\t  0,\n\t\t\t\t\t\t\t\t  Int32GetDatum(-1));\n\t\treturn res;\n\t}\n#endif\n\tres = TimestampTzGetDatum(GetCurrentTimestamp());\n\treturn res;\n}\n\nDatum\nts_make_range_from_internal_time(PG_FUNCTION_ARGS)\n{\n\tOid rngtypid = get_fn_expr_rettype(fcinfo->flinfo);\n\tTypeCacheEntry *typcache = range_get_typcache(fcinfo, rngtypid);\n#if PG16_GE\n\tNode *escontext = fcinfo->context;\n#endif\n\tRangeBound lower = {\n\t\t.val = PG_ARGISNULL(1) ? 0 : PG_GETARG_DATUM(1),\n\t\t.infinite = PG_ARGISNULL(1),\n\t\t.inclusive = true,\n\t\t.lower = true,\n\t};\n\tRangeBound upper = {\n\t\t.val = PG_ARGISNULL(2) ? 0 : PG_GETARG_DATUM(2),\n\t\t.infinite = PG_ARGISNULL(2),\n\t\t.inclusive = false,\n\t\t.lower = false,\n\t};\n\n\t/* Need to check the types of the lower and upper values. They should\n\t * match the returned range. */\n\tPG_RETURN_RANGE_P(make_range_compat(typcache, &lower, &upper, false, escontext));\n}\n\nDatum\nts_get_internal_time_min(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_INT64(ts_time_get_min(PG_GETARG_OID(0)));\n}\n\nDatum\nts_get_internal_time_max(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_INT64(ts_time_get_max(PG_GETARG_OID(0)));\n}\n"
  },
  {
    "path": "src/time_utils.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <datatype/timestamp.h>\n\n#include \"export.h\"\n\n/* TimescaleDB-specific ranges for valid timestamps and dates: */\n#define TS_EPOCH_DIFF (POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE)\n#define TS_EPOCH_DIFF_MICROSECONDS (TS_EPOCH_DIFF * USECS_PER_DAY)\n\n/* For Timestamps, we need to be able to go from UNIX epoch to POSTGRES epoch\n * and thus add the difference between the two epochs. This will constrain the\n * max supported timestamp by the same amount. */\n#define TS_TIMESTAMP_MIN MIN_TIMESTAMP\n#define TS_TIMESTAMP_MAX (TS_TIMESTAMP_END - 1)\n#define TS_TIMESTAMP_END (END_TIMESTAMP - TS_EPOCH_DIFF_MICROSECONDS)\n#define TS_TIMESTAMP_INTERNAL_MIN (TS_TIMESTAMP_MIN + TS_EPOCH_DIFF_MICROSECONDS)\n#define TS_TIMESTAMP_INTERNAL_MAX (TS_TIMESTAMP_INTERNAL_END - 1)\n#define TS_TIMESTAMP_INTERNAL_END (TS_TIMESTAMP_END + TS_EPOCH_DIFF_MICROSECONDS)\n\n/* For Dates, we're limited by the timestamp range (since we internally first\n * convert dates to timestamps). Naturally the TimescaleDB-specific timestamp\n * limits apply as well. */\n#define TS_DATE_MIN (DATETIME_MIN_JULIAN - POSTGRES_EPOCH_JDATE)\n#define TS_DATE_MAX (TS_DATE_END - 1)\n#define TS_DATE_END (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE - TS_EPOCH_DIFF)\n#define TS_DATE_INTERNAL_MIN (TS_TIMESTAMP_MIN + TS_EPOCH_DIFF_MICROSECONDS)\n#define TS_DATE_INTERNAL_MAX (TS_DATE_INTERNAL_END - 1)\n#define TS_DATE_INTERNAL_END (TS_TIMESTAMP_END + TS_EPOCH_DIFF_MICROSECONDS)\n\n/*\n * -Infinity and +Infinity in internal (Unix) time.\n */\n#define TS_TIME_NOBEGIN (PG_INT64_MIN)\n#define TS_TIME_NOEND (PG_INT64_MAX)\n\n/*\n * A UUIDv7 timestamp is 6 bytes milliseconds in Unix epoch (unsigned).\n *\n * Since RFC9562 specifies the timestamp as unsigned, the minimum value is\n * 0. Further, the sub-millisecond part cannot be used as time since the bits\n * are optional and it is not possible to know if they are random or represent\n * a time fraction. Therefore, the max value is limited to the milliseconds.\n */\n#define TS_TIME_UUID_MS_MIN (0x000000000000)\n#define TS_TIME_UUID_MIN (0x000000000000 * 1000) /* microseconds */\n#define TS_TIME_UUID_MS_MAX (0xFFFFFFFFFFFF)\n#define TS_TIME_UUID_MAX (TS_TIME_UUID_MS_MAX * 1000) /* microseconds */\n\n#define IS_INTEGER_TYPE(type) (type == INT2OID || type == INT4OID || type == INT8OID)\n#define IS_TIMESTAMP_TYPE(type) (type == TIMESTAMPOID || type == TIMESTAMPTZOID || type == DATEOID)\n#define IS_UUID_TYPE(type) (type == UUIDOID)\n#define IS_VALID_TIME_TYPE(type)                                                                   \\\n\t(IS_INTEGER_TYPE(type) || IS_TIMESTAMP_TYPE(type) || IS_UUID_TYPE(type))\n\n#define TS_TIME_DATUM_IS_MIN(timeval, type) (timeval == ts_time_datum_get_min(type))\n#define TS_TIME_DATUM_IS_MAX(timeval, type) (timeval == ts_time_datum_get_max(type))\n#define TS_TIME_DATUM_IS_END(timeval, type)                                                        \\\n\t(IS_TIMESTAMP_TYPE(type) && timeval == ts_time_datum_get_end(type)))\n#define TS_TIME_DATUM_IS_NOBEGIN(timeval, type)                                                    \\\n\t(IS_TIMESTAMP_TYPE(type) && (timeval == ts_time_datum_get_nobegin(type)))\n#define TS_TIME_DATUM_IS_NOEND(timeval, type)                                                      \\\n\t(IS_TIMESTAMP_TYPE(type) && (timeval == ts_time_datum_get_noend(type)))\n\n#define TS_TIME_DATUM_NOT_FINITE(timeval, type)                                                    \\\n\t(IS_INTEGER_TYPE(type) || TS_TIME_DATUM_IS_NOBEGIN(timeval, type) ||                           \\\n\t TS_TIME_DATUM_IS_NOEND(timeval, type))\n\n#define TS_TIME_IS_MIN(timeval, type) (timeval == ts_time_get_min(type))\n#define TS_TIME_IS_MAX(timeval, type) (timeval == ts_time_get_max(type))\n#define TS_TIME_IS_END(timeval, type) (IS_TIMESTAMP_TYPE(type) && timeval == ts_time_get_end(type))\n#define TS_TIME_IS_NOBEGIN(timeval, type)                                                          \\\n\t(IS_TIMESTAMP_TYPE(type) && timeval == ts_time_get_nobegin(type))\n#define TS_TIME_IS_NOEND(timeval, type)                                                            \\\n\t(IS_TIMESTAMP_TYPE(type) && timeval == ts_time_get_noend(type))\n\n#define TS_TIME_NOT_FINITE(timeval, type)                                                          \\\n\t(IS_INTEGER_TYPE(type) || TS_TIME_IS_NOBEGIN(timeval, type) || TS_TIME_IS_NOEND(timeval, type))\n\nextern TSDLLEXPORT int64 ts_time_value_from_arg(Datum arg, Oid argtype, Oid timetype,\n\t\t\t\t\t\t\t\t\t\t\t\tbool need_now_func);\nextern TSDLLEXPORT Datum ts_time_datum_convert_arg(Datum arg, Oid *argtype, Oid timetype);\nextern TSDLLEXPORT Datum ts_time_datum_get_min(Oid timetype);\nextern TSDLLEXPORT Datum ts_time_datum_get_max(Oid timetype);\nextern TSDLLEXPORT Datum ts_time_datum_get_end(Oid timetype);\nextern TSDLLEXPORT Datum ts_time_datum_get_nobegin(Oid timetype);\nextern TSDLLEXPORT Datum ts_time_datum_get_nobegin_or_min(Oid timetype);\nextern TSDLLEXPORT Datum ts_time_datum_get_noend(Oid timetype);\nextern TSDLLEXPORT int64 ts_time_get_min(Oid timetype);\nextern TSDLLEXPORT int64 ts_time_get_max(Oid timetype);\nextern TSDLLEXPORT int64 ts_time_get_end(Oid timetype);\nextern TSDLLEXPORT int64 ts_time_get_end_or_max(Oid timetype);\nextern TSDLLEXPORT int64 ts_time_get_nobegin(Oid timetype);\nextern TSDLLEXPORT int64 ts_time_get_nobegin_or_min(Oid timetype);\nextern TSDLLEXPORT int64 ts_time_get_noend(Oid timetype);\nextern TSDLLEXPORT int64 ts_time_get_noend_or_max(Oid timetype);\nextern TSDLLEXPORT int64 ts_time_saturating_add(int64 timeval, int64 interval, Oid timetype);\nextern TSDLLEXPORT int64 ts_time_saturating_sub(int64 timeval, int64 interval, Oid timetype);\nextern TSDLLEXPORT int64 ts_subtract_integer_from_now_saturating(Oid now_func, int64 interval,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t Oid timetype);\nextern TSDLLEXPORT Datum ts_subtract_interval_from_now(const Interval *interval, Oid timetype);\n#ifdef TS_DEBUG\nextern TSDLLEXPORT Datum ts_get_mock_time_or_current_time(void);\n#endif\n\nbool is_valid_now_func(Node *node);\n"
  },
  {
    "path": "src/timezones.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n#include \"timezones.h\"\n#include <access/xact.h>\n#include <datatype/timestamp.h>\n#include <pgtime.h>\n#include <port.h>\n#include <utils/timestamp.h>\n\n/* Checks if the given TZ name is valid. */\nbool\nts_is_valid_timezone_name(const char *tz_name)\n{\n\tpg_tz *tz;\n\tint tzoff;\n\tstruct pg_tm tm;\n\tfsec_t fsec;\n\tconst char *abbrev;\n\tbool found = false;\n\tTimestampTz now = GetCurrentTransactionStartTimestamp();\n\tpg_tzenum *tzenum = pg_tzenumerate_start();\n\n\twhile (true)\n\t{\n\t\ttz = pg_tzenumerate_next(tzenum);\n\t\tif (!tz)\n\t\t\tbreak;\n\n\t\t/*\n\t\t * Convert now() to time in this TZ and skip if conversion fails.\n\t\t * This check is the same that pg_timezone_names() does.\n\t\t */\n\t\tif (timestamp2tm(now, &tzoff, &tm, &fsec, &abbrev, tz) != 0)\n\t\t\tcontinue;\n\n\t\tif ((!strcmp(tz_name, pg_get_timezone_name(tz))) || (abbrev && !strcmp(tz_name, abbrev)))\n\t\t{\n\t\t\tfound = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tpg_tzenumerate_end(tzenum);\n\treturn found;\n}\n"
  },
  {
    "path": "src/timezones.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#pragma once\n\n#include \"export.h\"\n\nextern TSDLLEXPORT bool ts_is_valid_timezone_name(const char *tz_name);\n"
  },
  {
    "path": "src/trigger.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/xact.h>\n#include <commands/trigger.h>\n#include <miscadmin.h>\n#include <parser/analyze.h>\n#include <tcop/tcopprot.h>\n#include <utils/builtins.h>\n#include <utils/lsyscache.h>\n#include <utils/rel.h>\n#include <utils/syscache.h>\n\n#include \"trigger.h\"\n\n/*\n * Replicate a trigger on a chunk.\n *\n * Given a trigger OID (e.g., a Hypertable trigger), create the equivalent\n * trigger on a chunk.\n *\n * Note: it is assumed that this function is called under a user that has\n * permissions to modify the chunk since CreateTrigger() performs permissions\n * checks.\n */\nvoid\nts_trigger_create_on_chunk(Oid trigger_oid, const char *chunk_schema_name,\n\t\t\t\t\t\t   const char *chunk_table_name)\n{\n\tDatum datum_def = DirectFunctionCall1(pg_get_triggerdef, ObjectIdGetDatum(trigger_oid));\n\tconst char *def = TextDatumGetCString(datum_def);\n\tList *deparsed_list;\n\tNode *deparsed_node;\n\tCreateTrigStmt *stmt;\n\n\tdeparsed_list = pg_parse_query(def);\n\tAssert(list_length(deparsed_list) == 1);\n\tdeparsed_node = linitial(deparsed_list);\n\n\tdo\n\t{\n\t\tRawStmt *rawstmt = (RawStmt *) deparsed_node;\n\t\tParseState *pstate = make_parsestate(NULL);\n\t\tQuery *query;\n\n\t\tAssert(IsA(deparsed_node, RawStmt));\n\t\tpstate->p_sourcetext = def;\n\t\tquery = transformTopLevelStmt(pstate, rawstmt);\n\t\tfree_parsestate(pstate);\n\t\tstmt = (CreateTrigStmt *) query->utilityStmt;\n\t} while (0);\n\n\tAssert(IsA(stmt, CreateTrigStmt));\n\tstmt->relation->relname = (char *) chunk_table_name;\n\tstmt->relation->schemaname = (char *) chunk_schema_name;\n\t/* Using OR REPLACE option introduced on Postgres 14 */\n\tstmt->replace = true;\n\n\tCreateTrigger(stmt,\n\t\t\t\t  def,\n\t\t\t\t  InvalidOid,\n\t\t\t\t  InvalidOid,\n\t\t\t\t  InvalidOid,\n\t\t\t\t  InvalidOid,\n\t\t\t\t  InvalidOid,\n\t\t\t\t  InvalidOid,\n\t\t\t\t  NULL,\n\t\t\t\t  false,\n\t\t\t\t  false);\n\n\tCommandCounterIncrement(); /* needed to prevent pg_class being updated\n\t\t\t\t\t\t\t\t* twice */\n}\n\ntypedef bool (*trigger_handler)(const Trigger *trigger, void *arg);\n\nstatic inline void\nfor_each_trigger(Oid relid, trigger_handler on_trigger, void *arg)\n{\n\tRelation rel;\n\n\trel = table_open(relid, AccessShareLock);\n\n\tif (rel->trigdesc != NULL)\n\t{\n\t\tint i;\n\n\t\t/*\n\t\t * The TriggerDesc from rel->trigdesc seems to be modified during\n\t\t * iterations of the loop and sometimes gets reallocated so we\n\t\t * access trigdesc only through rel->trigdesc.\n\t\t */\n\t\tfor (i = 0; i < rel->trigdesc->numtriggers; i++)\n\t\t{\n\t\t\tTrigger *trigger = &rel->trigdesc->triggers[i];\n\n\t\t\tif (!on_trigger(trigger, arg))\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\ttable_close(rel, AccessShareLock);\n}\n\nstatic bool\ncreate_trigger_handler(const Trigger *trigger, void *arg)\n{\n\tconst Chunk *chunk = arg;\n\n\tif ((TRIGGER_USES_TRANSITION_TABLE(trigger->tgoldtable) ||\n\t\t TRIGGER_USES_TRANSITION_TABLE(trigger->tgnewtable)) &&\n\t\tTRIGGER_FOR_ROW(trigger->tgtype))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"ROW triggers with transition tables are not supported on hypertable \"\n\t\t\t\t\t\t\"chunks\")));\n\n\tif (trigger && TRIGGER_FOR_ROW(trigger->tgtype) && !trigger->tgisinternal)\n\t\tts_trigger_create_on_chunk(trigger->tgoid,\n\t\t\t\t\t\t\t\t   NameStr(chunk->fd.schema_name),\n\t\t\t\t\t\t\t\t   NameStr(chunk->fd.table_name));\n\n\treturn true;\n}\n\n/*\n * Create all hypertable triggers on a new chunk.\n *\n * Since chunk creation typically happens automatically on hypertable INSERT, we\n * need to execute the trigger creation under the role of the hypertable owner.\n * This is due to the use of CreateTrigger(), which does permissions checks. The\n * user role inserting might have INSERT permissions, but not TRIGGER\n * permissions (needed to create triggers on a table).\n *\n * We assume that the owner of the Hypertable is also the owner of the new\n * chunk.\n */\nvoid\nts_trigger_create_all_on_chunk(const Chunk *chunk)\n{\n\tint sec_ctx;\n\tOid saved_uid;\n\tOid owner;\n\n\t/* We do not create triggers on foreign table chunks */\n\tif (chunk->relkind == RELKIND_FOREIGN_TABLE)\n\t\treturn;\n\n\tAssert(chunk->relkind == RELKIND_RELATION);\n\towner = ts_rel_get_owner(chunk->hypertable_relid);\n\n\tGetUserIdAndSecContext(&saved_uid, &sec_ctx);\n\n\tif (saved_uid != owner)\n\t\tSetUserIdAndSecContext(owner, sec_ctx | SECURITY_LOCAL_USERID_CHANGE);\n\n\tfor_each_trigger(chunk->hypertable_relid, create_trigger_handler, (Chunk *) chunk);\n\n\tif (saved_uid != owner)\n\t\tSetUserIdAndSecContext(saved_uid, sec_ctx);\n}\n\nstatic bool\ncheck_for_transition_table(const Trigger *trigger, void *arg)\n{\n\tif ((TRIGGER_USES_TRANSITION_TABLE(trigger->tgnewtable) ||\n\t\t TRIGGER_USES_TRANSITION_TABLE(trigger->tgoldtable)) &&\n\t\tTRIGGER_FOR_ROW(trigger->tgtype))\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"ROW triggers with transition tables are not supported on hypertables\")));\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nvoid\nts_check_unsupported_triggers(Oid relid)\n{\n\tfor_each_trigger(relid, check_for_transition_table, NULL);\n}\n"
  },
  {
    "path": "src/trigger.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include \"chunk.h\"\n#include \"hypertable.h\"\n#include <catalog/pg_trigger.h>\n\nextern void ts_trigger_create_on_chunk(Oid trigger_oid, const char *chunk_schema_name,\n\t\t\t\t\t\t\t\t\t   const char *chunk_table_name);\nextern TSDLLEXPORT void ts_trigger_create_all_on_chunk(const Chunk *chunk);\nextern void ts_check_unsupported_triggers(Oid relid);\n"
  },
  {
    "path": "src/ts_catalog/CMakeLists.txt",
    "content": "set(SOURCES\n    ${CMAKE_CURRENT_SOURCE_DIR}/array_utils.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/catalog.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/chunk_column_stats.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/chunk_rewrite.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/compression_chunk_size.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/compression_settings.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/continuous_agg.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/continuous_aggs_watermark.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/metadata.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/tablespace.c)\ntarget_sources(${PROJECT_NAME} PRIVATE ${SOURCES})\n"
  },
  {
    "path": "src/ts_catalog/array_utils.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n\n#include <catalog/pg_collation.h>\n#include <catalog/pg_type.h>\n#include <fmgr.h>\n#include <utils/array.h>\n#include <utils/builtins.h>\n#include <utils/fmgroids.h>\n\n#include \"compat/compat.h\"\n#include \"array_utils.h\"\n#include \"debug_assert.h\"\n\n/*\n * Array helper function for internal catalog arrays.\n * These are not suitable for arbitrary dimension\n * arrays but only for 1-dimensional arrays as we use\n * them in our catalog.\n */\n\nextern TSDLLEXPORT int\nts_array_length(ArrayType *arr)\n{\n\tif (!arr || ARR_NDIM(arr) == 0)\n\t\treturn 0;\n\n\tAssert(ARR_NDIM(arr) == 1);\n\n\treturn ARR_DIMS(arr)[0];\n}\n\nextern TSDLLEXPORT bool\nts_array_equal(ArrayType *left, ArrayType *right)\n{\n\t/* Quick exit if both are NULL or point to same thing. */\n\tif (left == right)\n\t\treturn true;\n\n\tif (left == NULL || right == NULL)\n\t\treturn false;\n\n\tif (ARR_NDIM(left) == 0 || ARR_NDIM(right) == 0)\n\t{\n\t\tif (ARR_NDIM(left) == 0 && ARR_NDIM(right) == 0)\n\t\t\treturn true;\n\t\telse\n\t\t\treturn false;\n\t}\n\n\tAssert(left != NULL && right != NULL && ARR_NDIM(left) == 1 && ARR_NDIM(right) == 1);\n\n\tDatum result = OidFunctionCall2Coll(F_ARRAY_EQ,\n\t\t\t\t\t\t\t\t\t\tDEFAULT_COLLATION_OID,\n\t\t\t\t\t\t\t\t\t\tPointerGetDatum(left),\n\t\t\t\t\t\t\t\t\t\tPointerGetDatum(right));\n\n\treturn DatumGetBool(result);\n}\n\nextern TSDLLEXPORT bool\nts_array_is_member(ArrayType *arr, const char *name)\n{\n\tbool ret = false;\n\tDatum datum;\n\tbool null;\n\tif (!arr || ARR_NDIM(arr) == 0)\n\t\treturn ret;\n\n\tAssert(ARR_NDIM(arr) == 1);\n\tAssert(arr->elemtype == TEXTOID);\n\tAssert(name);\n\n\tArrayIterator it = array_create_iterator(arr, 0, NULL);\n\twhile (array_iterate(it, &datum, &null))\n\t{\n\t\tAssert(!null);\n\t\t/*\n\t\t * Our internal catalog arrays should either be NULL or\n\t\t * have non-NULL members. During normal operation it should\n\t\t * never have NULL members. If we have NULL members either\n\t\t * the catalog is corrupted or some catalog tampering has\n\t\t * happened.\n\t\t */\n\t\tEnsure(!null, \"array element was NULL\");\n\t\tif (strncmp(TextDatumGetCString(datum), name, NAMEDATALEN) == 0)\n\t\t{\n\t\t\tret = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tarray_free_iterator(it);\n\treturn ret;\n}\n\nextern TSDLLEXPORT void\nts_array_append_stringinfo(ArrayType *arr, StringInfo info)\n{\n\tbool first = true;\n\tDatum datum;\n\tbool null;\n\n\tif (!arr)\n\t\treturn;\n\n\tAssert(ARR_NDIM(arr) <= 1);\n\tAssert(arr->elemtype == TEXTOID);\n\n\tArrayIterator it = array_create_iterator(arr, 0, NULL);\n\twhile (array_iterate(it, &datum, &null))\n\t{\n\t\tAssert(!null);\n\t\t/*\n\t\t * Our internal catalog arrays should either be NULL or\n\t\t * have non-NULL members. During normal operation it should\n\t\t * never have NULL members. If we have NULL members either\n\t\t * the catalog is corrupted or some catalog tampering has\n\t\t * happened.\n\t\t */\n\t\tEnsure(!null, \"array element was NULL\");\n\t\tif (!first)\n\t\t\tappendStringInfoString(info, \", \");\n\t\telse\n\t\t\tfirst = false;\n\n\t\tappendStringInfo(info, \"%s\", TextDatumGetCString(datum));\n\t}\n\n\tarray_free_iterator(it);\n}\n\nextern TSDLLEXPORT int\nts_array_position(ArrayType *arr, const char *name)\n{\n\tint pos = 0;\n\tDatum datum;\n\tbool found = false;\n\tbool null;\n\tif (!arr || ARR_NDIM(arr) == 0)\n\t\treturn pos;\n\n\tAssert(ARR_NDIM(arr) == 1);\n\tAssert(arr->elemtype == TEXTOID);\n\n\tArrayIterator it = array_create_iterator(arr, 0, NULL);\n\twhile (array_iterate(it, &datum, &null))\n\t{\n\t\tpos++;\n\t\t/*\n\t\t * Our internal catalog arrays should either be NULL or\n\t\t * have non-NULL members. During normal operation it should\n\t\t * never have NULL members. If we have NULL members either\n\t\t * the catalog is corrupted or some catalog tampering has\n\t\t * happened.\n\t\t */\n\t\tEnsure(!null, \"array element was NULL\");\n\t\tif (strncmp(TextDatumGetCString(datum), name, NAMEDATALEN) == 0)\n\t\t{\n\t\t\tfound = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tarray_free_iterator(it);\n\treturn found ? pos : 0;\n}\n\nextern TSDLLEXPORT ArrayType *\nts_array_replace_text(ArrayType *arr, const char *old, const char *new)\n{\n\tif (!arr || ARR_NDIM(arr) == 0)\n\t\treturn NULL;\n\n\tAssert(ARR_NDIM(arr) == 1);\n\tAssert(arr->elemtype == TEXTOID);\n\n\tDatum datum;\n\tbool null;\n\tint pos = 1;\n\tArrayIterator it = array_create_iterator(arr, 0, NULL);\n\n\twhile (array_iterate(it, &datum, &null))\n\t{\n\t\t/*\n\t\t * Our internal catalog arrays should either be NULL or\n\t\t * have non-NULL members. During normal operation it should\n\t\t * never have NULL members. If we have NULL members either\n\t\t * the catalog is corrupted or some catalog tampering has\n\t\t * happened.\n\t\t */\n\t\tEnsure(!null, \"array element was NULL\");\n\t\tif (strncmp(TextDatumGetCString(datum), old, NAMEDATALEN) == 0)\n\t\t{\n\t\t\tdatum = array_set_element(PointerGetDatum(arr),\n\t\t\t\t\t\t\t\t\t  1,\n\t\t\t\t\t\t\t\t\t  &pos,\n\t\t\t\t\t\t\t\t\t  CStringGetTextDatum(new),\n\t\t\t\t\t\t\t\t\t  false,\n\t\t\t\t\t\t\t\t\t  -1,\n\t\t\t\t\t\t\t\t\t  -1,\n\t\t\t\t\t\t\t\t\t  false,\n\t\t\t\t\t\t\t\t\t  TYPALIGN_INT);\n\t\t\tarr = DatumGetArrayTypeP(datum);\n\t\t}\n\t\tpos++;\n\t}\n\n\tarray_free_iterator(it);\n\treturn arr;\n}\n\nextern TSDLLEXPORT bool\nts_array_get_element_bool(ArrayType *arr, int position)\n{\n\tAssert(arr);\n\tAssert(ARR_NDIM(arr) == 1);\n\tAssert(arr->elemtype == BOOLOID);\n\tbool isnull;\n\n\tDatum value = array_get_element(PointerGetDatum(arr), 1, &position, -1, 1, true, 'c', &isnull);\n\tEnsure(!isnull, \"invalid array position\");\n\n\treturn DatumGetBool(value);\n}\n\nextern TSDLLEXPORT const char *\nts_array_get_element_text(ArrayType *arr, int position)\n{\n\tAssert(arr);\n\tAssert(ARR_NDIM(arr) == 1);\n\tAssert(arr->elemtype == TEXTOID);\n\tbool isnull;\n\n\tDatum value =\n\t\tarray_get_element(PointerGetDatum(arr), 1, &position, -1, -1, false, 'i', &isnull);\n\tEnsure(!isnull, \"invalid array position\");\n\n\treturn TextDatumGetCString(value);\n}\n\nextern TSDLLEXPORT ArrayType *\nts_array_add_element_text(ArrayType *arr, const char *value)\n{\n\tif (!arr && value == NULL)\n\t{\n\t\t/* return empty array */\n\t\treturn construct_array(NULL, 0, TEXTOID, -1, false, TYPALIGN_INT);\n\t}\n\n\tDatum val = CStringGetTextDatum(value);\n\tif (!arr)\n\t{\n\t\treturn construct_array(&val, 1, TEXTOID, -1, false, TYPALIGN_INT);\n\t}\n\telse\n\t{\n\t\tAssert(ARR_NDIM(arr) == 1);\n\t\tAssert(arr->elemtype == TEXTOID);\n\n\t\tDatum d = PointerGetDatum(arr);\n\n\t\tint position = ts_array_length(arr);\n\t\tAssert(position);\n\t\tposition++;\n\n\t\td = array_set_element(d, 1, &position, val, false, -1, -1, false, TYPALIGN_INT);\n\n\t\treturn DatumGetArrayTypeP(d);\n\t}\n}\n\nextern TSDLLEXPORT ArrayType *\nts_array_add_element_bool(ArrayType *arr, bool value)\n{\n\tif (!arr)\n\t{\n\t\tDatum val = BoolGetDatum(value);\n\t\treturn construct_array(&val, 1, BOOLOID, 1, true, TYPALIGN_CHAR);\n\t}\n\telse\n\t{\n\t\tAssert(ARR_NDIM(arr) == 1);\n\t\tAssert(arr->elemtype == BOOLOID);\n\n\t\tDatum d = PointerGetDatum(arr);\n\n\t\tint position = ts_array_length(arr);\n\t\tAssert(position);\n\t\tposition++;\n\n\t\td = array_set_element(d,\n\t\t\t\t\t\t\t  1,\n\t\t\t\t\t\t\t  &position,\n\t\t\t\t\t\t\t  BoolGetDatum(value),\n\t\t\t\t\t\t\t  false,\n\t\t\t\t\t\t\t  -1,\n\t\t\t\t\t\t\t  1,\n\t\t\t\t\t\t\t  true,\n\t\t\t\t\t\t\t  TYPALIGN_CHAR);\n\n\t\treturn DatumGetArrayTypeP(d);\n\t}\n}\n"
  },
  {
    "path": "src/ts_catalog/array_utils.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include <lib/stringinfo.h>\n#include <utils/array.h>\n\n#include \"export.h\"\n\n/*\n * Array helper function for internal catalog arrays.\n * These are not suitable for arbitrary dimension\n * arrays but only for 1-dimensional arrays as we use\n * them in our catalog.\n */\n\nextern TSDLLEXPORT int ts_array_length(ArrayType *arr);\nextern TSDLLEXPORT bool ts_array_equal(ArrayType *left, ArrayType *right);\nextern TSDLLEXPORT bool ts_array_is_member(ArrayType *arr, const char *name);\nextern TSDLLEXPORT void ts_array_append_stringinfo(ArrayType *arr, StringInfo info);\nextern TSDLLEXPORT int ts_array_position(ArrayType *arr, const char *name);\n\nextern TSDLLEXPORT bool ts_array_get_element_bool(ArrayType *arr, int position);\nextern TSDLLEXPORT const char *ts_array_get_element_text(ArrayType *arr, int position);\n\nextern TSDLLEXPORT ArrayType *ts_array_add_element_bool(ArrayType *arr, bool value);\nextern TSDLLEXPORT ArrayType *ts_array_add_element_text(ArrayType *arr, const char *value);\n\nextern TSDLLEXPORT ArrayType *ts_array_replace_text(ArrayType *arr, const char *old,\n\t\t\t\t\t\t\t\t\t\t\t\t\tconst char *new);\n"
  },
  {
    "path": "src/ts_catalog/catalog.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/htup_details.h>\n#include <access/xact.h>\n#include <catalog/indexing.h>\n#include <catalog/namespace.h>\n#include <catalog/pg_namespace.h>\n#include <commands/dbcommands.h>\n#include <commands/sequence.h>\n#include <miscadmin.h>\n#include <utils/builtins.h>\n#include <utils/inval.h>\n#include <utils/lsyscache.h>\n#include <utils/regproc.h>\n#include <utils/syscache.h>\n\n#include \"compat/compat.h\"\n#include \"cache_invalidate.h\"\n#include \"extension.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"utils.h\"\n\nstatic const TableInfoDef catalog_table_names[_MAX_CATALOG_TABLES + 1] = {\n\t[HYPERTABLE] = {\n\t\t.schema_name = CATALOG_SCHEMA_NAME,\n\t\t.table_name = HYPERTABLE_TABLE_NAME,\n\t},\n\t[DIMENSION] = {\n\t\t.schema_name = CATALOG_SCHEMA_NAME,\n\t\t.table_name = DIMENSION_TABLE_NAME,\n\t},\n\t[DIMENSION_SLICE] = {\n\t\t.schema_name = CATALOG_SCHEMA_NAME,\n\t\t.table_name = DIMENSION_SLICE_TABLE_NAME,\n\t},\n\t[CHUNK] = {\n\t\t.schema_name = CATALOG_SCHEMA_NAME,\n\t\t.table_name = CHUNK_TABLE_NAME,\n\t},\n\t[CHUNK_CONSTRAINT] = {\n\t\t.schema_name = CATALOG_SCHEMA_NAME,\n\t\t.table_name = CHUNK_CONSTRAINT_TABLE_NAME,\n\t},\n\t[CHUNK_REWRITE] = {\n\t\t.schema_name = CATALOG_SCHEMA_NAME,\n\t\t.table_name = CHUNK_REWRITE_TABLE_NAME,\n\t},\n\t[TABLESPACE] = {\n\t\t.schema_name = CATALOG_SCHEMA_NAME,\n\t\t.table_name = TABLESPACE_TABLE_NAME,\n\t},\n\t[BGW_JOB] = {\n\t\t.schema_name = CATALOG_SCHEMA_NAME,\n\t\t.table_name = BGW_JOB_TABLE_NAME,\n\t},\n\t[BGW_JOB_STAT] = {\n\t\t.schema_name = INTERNAL_SCHEMA_NAME,\n\t\t.table_name = BGW_JOB_STAT_TABLE_NAME,\n\t},\n\t[BGW_JOB_STAT_HISTORY] = {\n\t\t.schema_name = INTERNAL_SCHEMA_NAME,\n\t\t.table_name = BGW_JOB_STAT_HISTORY_TABLE_NAME,\n\t},\n\t[METADATA] = {\n\t\t.schema_name = CATALOG_SCHEMA_NAME,\n\t\t.table_name = METADATA_TABLE_NAME,\n\t},\n\t[BGW_POLICY_CHUNK_STATS] = {\n\t\t.schema_name = INTERNAL_SCHEMA_NAME,\n\t\t.table_name = BGW_POLICY_CHUNK_STATS_TABLE_NAME,\n\t},\n\t[CONTINUOUS_AGG] = {\n\t\t.schema_name = CATALOG_SCHEMA_NAME,\n\t\t.table_name = CONTINUOUS_AGG_TABLE_NAME,\n\t},\n\t[CONTINUOUS_AGGS_HYPERTABLE_INVALIDATION_LOG] = {\n\t\t.schema_name = CATALOG_SCHEMA_NAME,\n\t\t.table_name = CONTINUOUS_AGGS_HYPERTABLE_INVALIDATION_LOG_TABLE_NAME,\n\t},\n\t[CONTINUOUS_AGGS_INVALIDATION_THRESHOLD] = {\n\t\t.schema_name = CATALOG_SCHEMA_NAME,\n\t\t.table_name = CONTINUOUS_AGGS_INVALIDATION_THRESHOLD_TABLE_NAME,\n\t},\n\t[CONTINUOUS_AGGS_MATERIALIZATION_INVALIDATION_LOG] = {\n\t\t.schema_name = CATALOG_SCHEMA_NAME,\n\t\t.table_name = CONTINUOUS_AGGS_MATERIALIZATION_INVALIDATION_LOG_TABLE_NAME,\n\t},\n\t[CONTINUOUS_AGGS_MATERIALIZATION_RANGES] = {\n\t\t.schema_name = CATALOG_SCHEMA_NAME,\n\t\t.table_name = CONTINUOUS_AGGS_MATERIALIZATION_RANGES_TABLE_NAME,\n\t},\n\t[COMPRESSION_SETTINGS] = {\n\t\t.schema_name = CATALOG_SCHEMA_NAME,\n\t\t.table_name = COMPRESSION_SETTINGS_TABLE_NAME,\n\t},\n\t[COMPRESSION_CHUNK_SIZE] = {\n\t\t.schema_name = CATALOG_SCHEMA_NAME,\n\t\t.table_name = COMPRESSION_CHUNK_SIZE_TABLE_NAME,\n\t},\n\t[CONTINUOUS_AGGS_BUCKET_FUNCTION] = {\n\t\t.schema_name = CATALOG_SCHEMA_NAME,\n\t\t.table_name = CONTINUOUS_AGGS_BUCKET_FUNCTION_TABLE_NAME,\n\t},\n\t[CONTINUOUS_AGGS_WATERMARK] = {\n\t\t.schema_name = CATALOG_SCHEMA_NAME,\n\t\t.table_name = CONTINUOUS_AGGS_WATERMARK_TABLE_NAME,\n\t},\n\t[TELEMETRY_EVENT] = {\n\t\t.schema_name = CATALOG_SCHEMA_NAME,\n\t\t.table_name = TELEMETRY_EVENT_TABLE_NAME,\n\t},\n\t[CHUNK_COLUMN_STATS] = {\n\t\t.schema_name = CATALOG_SCHEMA_NAME,\n\t\t.table_name = CHUNK_COLUMN_STATS_TABLE_NAME,\n\t},\n\t[_MAX_CATALOG_TABLES] = {\n\t\t.schema_name = \"invalid schema\",\n\t\t.table_name = \"invalid table\",\n\t}\n};\n\nstatic const TableIndexDef catalog_table_index_definitions[_MAX_CATALOG_TABLES] = {\n\t[HYPERTABLE] = {\n\t\t.length = _MAX_HYPERTABLE_INDEX,\n\t\t.names = (char *[]) {\n\t\t\t[HYPERTABLE_ID_INDEX] = \"hypertable_pkey\",\n\t\t\t[HYPERTABLE_NAME_INDEX] = \"hypertable_table_name_schema_name_key\",\n\t\t},\n\t},\n\t[DIMENSION] = {\n\t\t.length = _MAX_DIMENSION_INDEX,\n\t\t.names = (char *[]) {\n\t\t\t[DIMENSION_ID_IDX] = \"dimension_pkey\",\n\t\t\t[DIMENSION_HYPERTABLE_ID_COLUMN_NAME_IDX] = \"dimension_hypertable_id_column_name_key\",\n\t\t},\n\t},\n\t[DIMENSION_SLICE] = {\n\t\t.length = _MAX_DIMENSION_SLICE_INDEX,\n\t\t.names = (char *[]) {\n\t\t\t[DIMENSION_SLICE_ID_IDX] = \"dimension_slice_pkey\",\n\t\t\t[DIMENSION_SLICE_DIMENSION_ID_RANGE_START_RANGE_END_IDX] = \"dimension_slice_dimension_id_range_start_range_end_key\",\n\t\t},\n\t},\n\t[CHUNK_COLUMN_STATS] = {\n\t\t.length = _MAX_CHUNK_COLUMN_STATS_INDEX,\n\t\t.names = (char *[]) {\n\t\t\t[CHUNK_COLUMN_STATS_ID_IDX] = \"chunk_column_stats_pkey\",\n\t\t\t[CHUNK_COLUMN_STATS_HT_ID_CHUNK_ID_COLUMN_NAME_IDX] = \"chunk_column_stats_ht_id_chunk_id_colname_key\",\n\t\t},\n\t},\n\t[CHUNK] = {\n\t\t.length = _MAX_CHUNK_INDEX,\n\t\t.names = (char *[]) {\n\t\t\t[CHUNK_ID_INDEX] = \"chunk_pkey\",\n\t\t\t[CHUNK_HYPERTABLE_ID_INDEX] = \"chunk_hypertable_id_idx\",\n\t\t\t[CHUNK_SCHEMA_NAME_INDEX] = \"chunk_schema_name_table_name_key\",\n\t\t\t[CHUNK_COMPRESSED_CHUNK_ID_INDEX] = \"chunk_compressed_chunk_id_idx\",\n\t\t\t[CHUNK_OSM_CHUNK_INDEX] = \"chunk_osm_chunk_idx\",\n\t\t\t[CHUNK_HYPERTABLE_ID_CREATION_TIME_INDEX] = \"chunk_hypertable_id_creation_time_idx\",\n\t\t},\n\t},\n\t[CHUNK_CONSTRAINT] = {\n\t\t.length = _MAX_CHUNK_CONSTRAINT_INDEX,\n\t\t.names = (char *[]) {\n\t\t\t[CHUNK_CONSTRAINT_CHUNK_ID_CONSTRAINT_NAME_IDX] = \"chunk_constraint_chunk_id_constraint_name_key\",\n\t\t\t[CHUNK_CONSTRAINT_DIMENSION_SLICE_ID_IDX] = \"chunk_constraint_dimension_slice_id_idx\",\n\t\t},\n\t},\n\t[CHUNK_REWRITE] = {\n\t\t.length = _MAX_CHUNK_REWRITE_INDEX,\n\t\t.names = (char *[]) {\n\t\t\t[CHUNK_REWRITE_IDX] = \"chunk_rewrite_key\",\n\t\t},\n\t},\n\t[TABLESPACE] = {\n\t\t.length = _MAX_TABLESPACE_INDEX,\n\t\t.names = (char *[]) {\n\t\t\t[TABLESPACE_PKEY_IDX] = \"tablespace_pkey\",\n\t\t\t[TABLESPACE_HYPERTABLE_ID_TABLESPACE_NAME_IDX] = \"tablespace_hypertable_id_tablespace_name_key\",\n\t\t},\n\t},\n\t[BGW_JOB] = {\n\t\t.length = _MAX_BGW_JOB_INDEX,\n\t\t.names = (char *[]) {\n\t\t\t[BGW_JOB_PKEY_IDX] = \"bgw_job_pkey\",\n\t\t\t[BGW_JOB_PROC_HYPERTABLE_ID_IDX] = \"bgw_job_proc_hypertable_id_idx\",\n\t\t},\n\t},\n\t[BGW_JOB_STAT] = {\n\t\t.length = _MAX_BGW_JOB_STAT_INDEX,\n\t\t.names = (char *[]) {\n\t\t\t[BGW_JOB_STAT_PKEY_IDX] = \"bgw_job_stat_pkey\",\n\t\t},\n\t},\n\t[BGW_JOB_STAT_HISTORY] = {\n\t\t.length = _MAX_BGW_JOB_STAT_HISTORY_INDEX,\n\t\t.names = (char *[]) {\n\t\t\t[BGW_JOB_STAT_HISTORY_PKEY_IDX] = \"bgw_job_stat_history_pkey\",\n\t\t},\n\t},\n\t[METADATA] = {\n\t\t.length = _MAX_METADATA_INDEX,\n\t\t.names = (char *[]) {\n\t\t\t[METADATA_PKEY_IDX] = \"metadata_pkey\",\n\t\t},\n\t},\n\t[BGW_POLICY_CHUNK_STATS] = {\n\t\t.length = _MAX_BGW_POLICY_CHUNK_STATS_INDEX,\n\t\t.names = (char *[]) {\n\t\t\t[BGW_POLICY_CHUNK_STATS_JOB_ID_CHUNK_ID_IDX] = \"bgw_policy_chunk_stats_job_id_chunk_id_key\",\n\t\t}\n\t},\n\t[CONTINUOUS_AGG] = {\n\t\t.length = _MAX_CONTINUOUS_AGG_INDEX,\n\t\t.names = (char *[]) {\n\t\t\t[CONTINUOUS_AGG_PARTIAL_VIEW_SCHEMA_PARTIAL_VIEW_NAME_KEY] = \"continuous_agg_partial_view_schema_partial_view_name_key\",\n\t\t\t[CONTINUOUS_AGG_PKEY] = TS_CAGG_CATALOG_IDX,\n\t\t\t[CONTINUOUS_AGG_USER_VIEW_SCHEMA_USER_VIEW_NAME_KEY] = \"continuous_agg_user_view_schema_user_view_name_key\",\n\t\t\t[CONTINUOUS_AGG_RAW_HYPERTABLE_ID_IDX] = \"continuous_agg_raw_hypertable_id_idx\"\n\t\t},\n\t},\n\t[CONTINUOUS_AGGS_HYPERTABLE_INVALIDATION_LOG] = {\n\t\t.length = _MAX_CONTINUOUS_AGGS_HYPERTABLE_INVALIDATION_LOG_INDEX,\n\t\t.names = (char *[]) {\n\t\t\t[CONTINUOUS_AGGS_HYPERTABLE_INVALIDATION_LOG_IDX] = \"continuous_aggs_hypertable_invalidation_log_idx\",\n\t\t},\n\t},\n\t[CONTINUOUS_AGGS_INVALIDATION_THRESHOLD] = {\n\t\t.length = _MAX_CONTINUOUS_AGGS_INVALIDATION_THRESHOLD_INDEX,\n\t\t.names = (char *[]) {\n\t\t\t[CONTINUOUS_AGGS_INVALIDATION_THRESHOLD_PKEY] = \"continuous_aggs_invalidation_threshold_pkey\",\n\t\t},\n\t},\n\t[CONTINUOUS_AGGS_MATERIALIZATION_INVALIDATION_LOG] = {\n\t\t.length = _MAX_CONTINUOUS_AGGS_MATERIALIZATION_INVALIDATION_LOG_INDEX,\n\t\t.names = (char *[]) {\n\t\t\t[CONTINUOUS_AGGS_MATERIALIZATION_INVALIDATION_LOG_IDX] = \"continuous_aggs_materialization_invalidation_log_idx\",\n\t\t},\n\t},\n\t[CONTINUOUS_AGGS_MATERIALIZATION_RANGES] = {\n\t\t.length = _MAX_CONTINUOUS_AGGS_MATERIALIZATION_RANGES_INDEX,\n\t\t.names = (char *[]) {\n\t\t\t[CONTINUOUS_AGGS_MATERIALIZATION_RANGES_IDX] = \"continuous_aggs_materialization_ranges_idx\",\n\t\t},\n\t},\n\t[CONTINUOUS_AGGS_WATERMARK] = {\n\t\t.length = _MAX_CONTINUOUS_AGGS_WATERMARK_INDEX,\n\t\t.names = (char *[]) {\n\t\t\t[CONTINUOUS_AGGS_WATERMARK_PKEY] = \"continuous_aggs_watermark_pkey\",\n\t\t},\n\t},\n\t[COMPRESSION_SETTINGS] = {\n\t\t.length =  _MAX_COMPRESSION_SETTINGS_INDEX,\n\t\t.names = (char *[]) {\n\t\t\t[COMPRESSION_SETTINGS_PKEY] = \"compression_settings_pkey\",\n\t\t\t[COMPRESSION_SETTINGS_COMPRESS_RELID_IDX] = \"compression_settings_compress_relid_idx\",\n\t\t},\n\t},\n\t[COMPRESSION_CHUNK_SIZE] = {\n\t\t.length =  _MAX_COMPRESSION_CHUNK_SIZE_INDEX,\n\t\t.names = (char *[]) {\n\t\t\t[COMPRESSION_CHUNK_SIZE_PKEY] = \"compression_chunk_size_pkey\",\n\t\t},\n\t},\n\t[CONTINUOUS_AGGS_BUCKET_FUNCTION] = {\n\t\t.length = _MAX_CONTINUOUS_AGGS_BUCKET_FUNCTION_INDEX,\n\t\t.names = (char *[]) {\n\t\t\t[CONTINUOUS_AGGS_BUCKET_FUNCTION_PKEY_IDX] = \"continuous_aggs_bucket_function_pkey\",\n\t\t},\n\t}\n};\n\nstatic const char *catalog_table_serial_id_names[_MAX_CATALOG_TABLES] = {\n\t[HYPERTABLE] = CATALOG_SCHEMA_NAME \".hypertable_id_seq\",\n\t[DIMENSION] = CATALOG_SCHEMA_NAME \".dimension_id_seq\",\n\t[DIMENSION_SLICE] = CATALOG_SCHEMA_NAME \".dimension_slice_id_seq\",\n\t[CHUNK] = CATALOG_SCHEMA_NAME \".chunk_id_seq\",\n\t[CHUNK_CONSTRAINT] = CATALOG_SCHEMA_NAME \".chunk_constraint_name\",\n\t[CHUNK_REWRITE] = NULL,\n\t[TABLESPACE] = CATALOG_SCHEMA_NAME \".tablespace_id_seq\",\n\t[BGW_JOB] = CATALOG_SCHEMA_NAME \".bgw_job_id_seq\",\n\t[BGW_JOB_STAT] = NULL,\n\t[BGW_JOB_STAT_HISTORY] = INTERNAL_SCHEMA_NAME \".bgw_job_stat_history_id_seq\",\n\t[CONTINUOUS_AGGS_HYPERTABLE_INVALIDATION_LOG] = NULL,\n\t[CONTINUOUS_AGGS_INVALIDATION_THRESHOLD] = NULL,\n\t[CONTINUOUS_AGGS_MATERIALIZATION_INVALIDATION_LOG] = NULL,\n\t[COMPRESSION_SETTINGS] = NULL,\n\t[COMPRESSION_CHUNK_SIZE] = NULL,\n\t[CHUNK_COLUMN_STATS] = CATALOG_SCHEMA_NAME \".chunk_column_stats_id_seq\",\n};\n\ntypedef struct InternalFunctionDef\n{\n\tchar *name;\n\tint args;\n} InternalFunctionDef;\n\nstatic const InternalFunctionDef internal_function_definitions[_MAX_INTERNAL_FUNCTIONS] = {\n\t[DDL_ADD_CHUNK_CONSTRAINT] = {\n\t\t.name = \"chunk_constraint_add_table_constraint\",\n\t\t.args = 1,\n\t},\n\t[DDL_CONSTRAINT_CLONE] = {\n\t\t.name = \"constraint_clone\",\n\t\t.args = 2,\n\t},\n};\n\n/* Names for proxy tables used for cache invalidation. Must match names in\n * sql/cache.sql */\nstatic const char *cache_proxy_table_names[_MAX_CACHE_TYPES] = {\n\t[CACHE_TYPE_HYPERTABLE] = \"cache_inval_hypertable\",\n\t[CACHE_TYPE_BGW_JOB] = \"cache_inval_bgw_job\",\n\t[CACHE_TYPE_EXTENSION] = \"cache_inval_extension\",\n};\n\n/* Catalog information for the current database. */\nstatic Catalog s_catalog = {\n\t.initialized = false,\n};\n\nstatic CatalogDatabaseInfo database_info = {\n\t.database_id = InvalidOid,\n};\n\nstatic bool\ncatalog_is_valid(Catalog *catalog)\n{\n\treturn catalog != NULL && catalog->initialized;\n}\n\n/*\n * Get the user ID of the catalog owner.\n */\nstatic Oid\ncatalog_owner(void)\n{\n\tHeapTuple tuple;\n\tOid owner_oid;\n\tOid nsp_oid = get_namespace_oid(CATALOG_SCHEMA_NAME, false);\n\n\ttuple = SearchSysCache1(NAMESPACEOID, ObjectIdGetDatum(nsp_oid));\n\n\tif (!HeapTupleIsValid(tuple))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_SCHEMA),\n\t\t\t\t errmsg(\"schema with OID %u does not exist\", nsp_oid)));\n\n\towner_oid = ((Form_pg_namespace) GETSTRUCT(tuple))->nspowner;\n\n\tReleaseSysCache(tuple);\n\n\treturn owner_oid;\n}\n\nstatic const char *\ncatalog_table_name(CatalogTable table)\n{\n\treturn catalog_table_names[table].table_name;\n}\n\nstatic void\ncatalog_database_info_init(CatalogDatabaseInfo *info)\n{\n\tinfo->database_id = MyDatabaseId;\n\tnamestrcpy(&info->database_name, get_database_name(MyDatabaseId));\n\tinfo->schema_id = get_namespace_oid(CATALOG_SCHEMA_NAME, false);\n\tinfo->owner_uid = catalog_owner();\n\n\tif (!OidIsValid(info->schema_id))\n\t\telog(ERROR, \"OID lookup failed for schema \\\"%s\\\"\", CATALOG_SCHEMA_NAME);\n}\n\nTSDLLEXPORT CatalogDatabaseInfo *\nts_catalog_database_info_get()\n{\n\tif (!ts_extension_is_loaded())\n\t\telog(ERROR, \"tried calling catalog_database_info_get when extension isn't loaded\");\n\n\tif (!OidIsValid(database_info.database_id))\n\t{\n\t\tif (!IsTransactionState())\n\t\t\telog(ERROR, \"cannot initialize catalog_database_info outside of a transaction\");\n\n\t\tmemset(&database_info, 0, sizeof(CatalogDatabaseInfo));\n\t\tcatalog_database_info_init(&database_info);\n\t}\n\n\treturn &database_info;\n}\n\n/*\n * The rest of the arguments are used to populate the first arg.\n */\nvoid\nts_catalog_table_info_init(CatalogTableInfo *tables_info, int max_tables,\n\t\t\t\t\t\t   const TableInfoDef *table_ary, const TableIndexDef *index_ary,\n\t\t\t\t\t\t   const char **serial_id_ary)\n{\n\tint i;\n\n\tfor (i = 0; i < max_tables; i++)\n\t{\n\t\tOid id;\n\t\tconst char *sequence_name;\n\t\tSize number_indexes, j;\n\n\t\tid = ts_get_relation_relid((char *) table_ary[i].schema_name,\n\t\t\t\t\t\t\t\t   (char *) table_ary[i].table_name,\n\t\t\t\t\t\t\t\t   false);\n\n\t\tif (!OidIsValid(id))\n\t\t\telog(ERROR,\n\t\t\t\t \"OID lookup failed for table \\\"%s.%s\\\"\",\n\t\t\t\t table_ary[i].schema_name,\n\t\t\t\t table_ary[i].table_name);\n\n\t\ttables_info[i].id = id;\n\n\t\tnumber_indexes = index_ary[i].length;\n\t\tAssert(number_indexes <= _MAX_TABLE_INDEXES);\n\n\t\tfor (j = 0; j < number_indexes; j++)\n\t\t{\n\t\t\tid = ts_get_relation_relid(table_ary[i].schema_name, index_ary[i].names[j], true);\n\n\t\t\tif (!OidIsValid(id))\n\t\t\t\telog(ERROR, \"OID lookup failed for table index \\\"%s\\\"\", index_ary[i].names[j]);\n\n\t\t\ttables_info[i].index_ids[j] = id;\n\t\t}\n\n\t\ttables_info[i].name = table_ary[i].table_name;\n\t\ttables_info[i].schema_name = table_ary[i].schema_name;\n\t\tsequence_name = serial_id_ary[i];\n\n\t\tif (NULL != sequence_name)\n\t\t{\n\t\t\tRangeVar *sequence;\n\n#if PG16_LT\n\t\t\tsequence = makeRangeVarFromNameList(stringToQualifiedNameList(sequence_name));\n#else\n\t\t\tsequence = makeRangeVarFromNameList(stringToQualifiedNameList(sequence_name, NULL));\n#endif\n\t\t\ttables_info[i].serial_relid = RangeVarGetRelid(sequence, NoLock, false);\n\t\t}\n\t\telse\n\t\t\ttables_info[i].serial_relid = InvalidOid;\n\t}\n}\n\nTSDLLEXPORT Catalog *\nts_catalog_get(void)\n{\n\tint i;\n\n\tif (!OidIsValid(MyDatabaseId))\n\t\telog(ERROR, \"invalid database ID\");\n\n\tif (!ts_extension_is_loaded())\n\t\telog(ERROR, \"tried calling catalog_get when extension isn't loaded\");\n\n\tif (s_catalog.initialized || !IsTransactionState())\n\t\treturn &s_catalog;\n\n\tmemset(&s_catalog, 0, sizeof(Catalog));\n\tts_catalog_table_info_init(s_catalog.tables,\n\t\t\t\t\t\t\t   _MAX_CATALOG_TABLES,\n\t\t\t\t\t\t\t   catalog_table_names,\n\t\t\t\t\t\t\t   catalog_table_index_definitions,\n\t\t\t\t\t\t\t   catalog_table_serial_id_names);\n\n\tfor (i = 0; i < _TS_MAX_SCHEMA; i++)\n\t\ts_catalog.extension_schema_id[i] = get_namespace_oid(ts_extension_schema_names[i], false);\n\n\tfor (i = 0; i < _MAX_CACHE_TYPES; i++)\n\t\ts_catalog.caches[i].inval_proxy_id =\n\t\t\tget_relname_relid(cache_proxy_table_names[i],\n\t\t\t\t\t\t\t  s_catalog.extension_schema_id[TS_CACHE_SCHEMA]);\n\n\tts_cache_invalidate_set_proxy_tables(s_catalog.caches[CACHE_TYPE_HYPERTABLE].inval_proxy_id,\n\t\t\t\t\t\t\t\t\t\t s_catalog.caches[CACHE_TYPE_BGW_JOB].inval_proxy_id);\n\n\tfor (i = 0; i < _MAX_INTERNAL_FUNCTIONS; i++)\n\t{\n\t\tInternalFunctionDef def = internal_function_definitions[i];\n\t\tFuncCandidateList funclist =\n\t\t\tFuncnameGetCandidates(list_make2(makeString(FUNCTIONS_SCHEMA_NAME),\n\t\t\t\t\t\t\t\t\t\t\t makeString(def.name)),\n\t\t\t\t\t\t\t\t  def.args,\n\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t  false,\n\t\t\t\t\t\t\t\t  false, /* include_out_arguments */\n\t\t\t\t\t\t\t\t  false,\n\t\t\t\t\t\t\t\t  false);\n\n\t\tif (funclist == NULL || funclist->next)\n\t\t\telog(ERROR,\n\t\t\t\t \"OID lookup failed for the function \\\"%s\\\" with %d args\",\n\t\t\t\t def.name,\n\t\t\t\t def.args);\n\n\t\ts_catalog.functions[i].function_id = funclist->oid;\n\t}\n\ts_catalog.initialized = true;\n\n\treturn &s_catalog;\n}\n\nvoid\nts_catalog_reset(void)\n{\n\ts_catalog.initialized = false;\n\tdatabase_info.database_id = InvalidOid;\n\n\tts_cache_invalidate_set_proxy_tables(InvalidOid, InvalidOid);\n}\n\nstatic CatalogTable\ncatalog_get_table(Catalog *catalog, Oid relid)\n{\n\tunsigned int i;\n\n\tif (!catalog_is_valid(catalog))\n\t{\n\t\tconst char *schema_name = get_namespace_name(get_rel_namespace(relid));\n\t\tconst char *relname = get_rel_name(relid);\n\n\t\tfor (i = 0; i < _MAX_CATALOG_TABLES; i++)\n\t\t\tif (strcmp(catalog_table_names[i].schema_name, schema_name) == 0 &&\n\t\t\t\tstrcmp(catalog_table_name(i), relname) == 0)\n\t\t\t\treturn (CatalogTable) i;\n\n\t\treturn INVALID_CATALOG_TABLE;\n\t}\n\n\tfor (i = 0; i < _MAX_CATALOG_TABLES; i++)\n\t\tif (catalog->tables[i].id == relid)\n\t\t\treturn (CatalogTable) i;\n\n\treturn INVALID_CATALOG_TABLE;\n}\n\nbool\nts_is_catalog_table(Oid relid)\n{\n\treturn catalog_get_table(ts_catalog_get(), relid) != INVALID_CATALOG_TABLE;\n}\n\n/*\n * Get the next serial ID for a catalog table, if one exists for the given table.\n */\nTSDLLEXPORT int64\nts_catalog_table_next_seq_id(const Catalog *catalog, CatalogTable table)\n{\n\tOid relid = catalog->tables[table].serial_relid;\n\n\tif (!OidIsValid(relid))\n\t\telog(ERROR,\n\t\t\t \"no serial ID column for table \\\"%s.%s\\\"\",\n\t\t\t catalog_table_names[table].schema_name,\n\t\t\t catalog_table_name(table));\n\n\treturn DatumGetInt64(DirectFunctionCall1(nextval_oid, ObjectIdGetDatum(relid)));\n}\n\nOid\nts_catalog_get_cache_proxy_id(Catalog *catalog, CacheType type)\n{\n\tif (!catalog_is_valid(catalog))\n\t{\n\t\t/*\n\t\t * The catalog can be invalid during upgrade scripts. Try a non-cached\n\t\t * relation lookup, but we need to be in a transaction for\n\t\t * get_namespace_oid() to work.\n\t\t */\n\t\tif (!IsTransactionState())\n\t\t\treturn InvalidOid;\n\n\t\treturn ts_get_relation_relid(CACHE_SCHEMA_NAME,\n\t\t\t\t\t\t\t\t\t (char *) cache_proxy_table_names[type],\n\t\t\t\t\t\t\t\t\t true);\n\t}\n\n\treturn catalog->caches[type].inval_proxy_id;\n}\n\n/*\n * Become the user that owns the catalog schema.\n *\n * This might be necessary for users that do operations that require changes to\n * the catalog.\n *\n * The caller should pass a CatalogSecurityContext where the current security\n * context will be saved. The original security context can later be restored\n * with ts_catalog_restore_user().\n */\nTSDLLEXPORT bool\nts_catalog_database_info_become_owner(CatalogDatabaseInfo *database_info,\n\t\t\t\t\t\t\t\t\t  CatalogSecurityContext *sec_ctx)\n{\n\tGetUserIdAndSecContext(&sec_ctx->saved_uid, &sec_ctx->saved_security_context);\n\n\tif (sec_ctx->saved_uid != database_info->owner_uid)\n\t{\n\t\tSetUserIdAndSecContext(database_info->owner_uid,\n\t\t\t\t\t\t\t   sec_ctx->saved_security_context | SECURITY_LOCAL_USERID_CHANGE);\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n/*\n * Restore the security context of the original user after becoming the catalog\n * owner. The user should pass the original CatalogSecurityContext that was used\n * with ts_catalog_database_info_become_owner().\n */\nTSDLLEXPORT void\nts_catalog_restore_user(CatalogSecurityContext *sec_ctx)\n{\n\tSetUserIdAndSecContext(sec_ctx->saved_uid, sec_ctx->saved_security_context);\n}\n\n/*\n * Insert a new row into a catalog table.\n */\nvoid\nts_catalog_insert_only(Relation rel, HeapTuple tuple)\n{\n\tCatalogTupleInsert(rel, tuple);\n\tts_catalog_invalidate_cache(RelationGetRelid(rel), CMD_INSERT);\n}\n\nvoid\nts_catalog_insert(Relation rel, HeapTuple tuple)\n{\n\tts_catalog_insert_only(rel, tuple);\n\t/* Make changes visible */\n\tCommandCounterIncrement();\n}\n\n/*\n * Insert a new row into a catalog table.\n */\nTSDLLEXPORT void\nts_catalog_insert_values(Relation rel, TupleDesc tupdesc, Datum *values, bool *nulls)\n{\n\tHeapTuple tuple = heap_form_tuple(tupdesc, values, nulls);\n\n\tts_catalog_insert(rel, tuple);\n\theap_freetuple(tuple);\n}\n\nTSDLLEXPORT void\nts_catalog_insert_datums(Relation rel, TupleDesc tupdesc, NullableDatum *datums)\n{\n\tHeapTuple tuple = ts_heap_form_tuple(tupdesc, datums);\n\n\tts_catalog_insert(rel, tuple);\n\theap_freetuple(tuple);\n}\n\nvoid\nts_catalog_update_tid_only(Relation rel, ItemPointer tid, HeapTuple tuple)\n{\n\tCatalogTupleUpdate(rel, tid, tuple);\n\tts_catalog_invalidate_cache(RelationGetRelid(rel), CMD_UPDATE);\n}\n\nvoid\nts_catalog_update_tid(Relation rel, ItemPointer tid, HeapTuple tuple)\n{\n\tts_catalog_update_tid_only(rel, tid, tuple);\n\t/* Make changes visible */\n\tCommandCounterIncrement();\n}\n\nTSDLLEXPORT void\nts_catalog_update(Relation rel, HeapTuple tuple)\n{\n\tts_catalog_update_tid(rel, &tuple->t_self, tuple);\n}\n\nvoid\nts_catalog_delete_tid_only(Relation rel, ItemPointer tid)\n{\n\tCatalogTupleDelete(rel, tid);\n\tts_catalog_invalidate_cache(RelationGetRelid(rel), CMD_DELETE);\n}\n\nvoid\nts_catalog_delete_tid(Relation rel, ItemPointer tid)\n{\n\tts_catalog_delete_tid_only(rel, tid);\n\tCommandCounterIncrement();\n}\n\n/*\n * Invalidate TimescaleDB catalog caches.\n *\n * This function should be called whenever a TimescaleDB catalog table changes\n * in a way that might invalidate associated caches. It is currently called in\n * two distinct ways:\n *\n * 1. If a catalog table changes via the catalog API in catalog.c\n * 2. Via a trigger if a SQL INSERT/UPDATE/DELETE occurs on a catalog table\n *\n * Since triggers (2) require full parsing, planning and execution of SQL\n * statements, they aren't supported for simple catalog updates via (1) in\n * native code and are therefore discouraged. Ideally, catalog updates should\n * happen consistently via method (1) in the future, obviating the need for\n * triggers on catalog tables that cause side effects.\n *\n * The invalidation event is signaled to other backends (processes) via the\n * relcache invalidation mechanism on a dummy relation (table).\n *\n * Parameters: The OID of the catalog table that changed, and the operation\n * involved (e.g., INSERT, UPDATE, DELETE).\n */\nvoid\nts_catalog_invalidate_cache(Oid catalog_relid, CmdType operation)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tCatalogTable table = catalog_get_table(catalog, catalog_relid);\n\tOid relid;\n\n\tswitch (table)\n\t{\n\t\tcase CHUNK:\n\t\tcase CHUNK_CONSTRAINT:\n\t\tcase DIMENSION_SLICE:\n\t\t\tif (operation == CMD_UPDATE || operation == CMD_DELETE)\n\t\t\t{\n\t\t\t\trelid = ts_catalog_get_cache_proxy_id(catalog, CACHE_TYPE_HYPERTABLE);\n\t\t\t\tCacheInvalidateRelcacheByRelid(relid);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase HYPERTABLE:\n\t\tcase DIMENSION:\n\t\tcase CONTINUOUS_AGG:\n\t\tcase CHUNK_COLUMN_STATS:\n\t\t\trelid = ts_catalog_get_cache_proxy_id(catalog, CACHE_TYPE_HYPERTABLE);\n\t\t\tCacheInvalidateRelcacheByRelid(relid);\n\t\t\tbreak;\n\t\tcase BGW_JOB:\n\t\t\trelid = ts_catalog_get_cache_proxy_id(catalog, CACHE_TYPE_BGW_JOB);\n\t\t\tCacheInvalidateRelcacheByRelid(relid);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n}\n\n/* Scanner helper functions specifically for the catalog tables */\nTSDLLEXPORT bool\nts_catalog_scan_one(CatalogTable table, int indexid, ScanKeyData *scankey, int num_keys,\n\t\t\t\t\ttuple_found_func tuple_found, LOCKMODE lockmode, char *table_name, void *data)\n{\n\tCatalog *catalog = ts_catalog_get();\n\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, table),\n\t\t.index = catalog_get_index(catalog, table, indexid),\n\t\t.nkeys = num_keys,\n\t\t.scankey = scankey,\n\t\t.tuple_found = tuple_found,\n\t\t.data = data,\n\t\t.lockmode = lockmode,\n\t\t.scandirection = ForwardScanDirection,\n\t};\n\n\treturn ts_scanner_scan_one(&scanctx, false, table_name);\n}\n\nTSDLLEXPORT void\nts_catalog_scan_all(CatalogTable table, int indexid, ScanKeyData *scankey, int num_keys,\n\t\t\t\t\ttuple_found_func tuple_found, LOCKMODE lockmode, void *data)\n{\n\tCatalog *catalog = ts_catalog_get();\n\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, table),\n\t\t.index = catalog_get_index(catalog, table, indexid),\n\t\t.nkeys = num_keys,\n\t\t.scankey = scankey,\n\t\t.tuple_found = tuple_found,\n\t\t.data = data,\n\t\t.lockmode = lockmode,\n\t\t.scandirection = ForwardScanDirection,\n\t};\n\n\tts_scanner_scan(&scanctx);\n}\n\n/*\n * Copied verbatim from postgres source CatalogIndexInsert which is static\n * in postgres source code.\n * We need to have this function available because we do not want to use\n * simple_heap_insert which is used by CatalogTupleInsert which would\n * prevent using bulk inserts.\n */\nextern TSDLLEXPORT void\nts_catalog_index_insert(ResultRelInfo *indstate, HeapTuple heapTuple)\n{\n\tint i;\n\tint numIndexes;\n\tRelationPtr relationDescs;\n\tRelation heapRelation;\n\tTupleTableSlot *slot;\n\tIndexInfo **indexInfoArray;\n\tDatum values[INDEX_MAX_KEYS];\n\tbool isnull[INDEX_MAX_KEYS];\n\n\t/*\n\t * HOT update does not require index inserts. But with asserts enabled we\n\t * want to check that it'd be legal to currently insert into the\n\t * table/index.\n\t */\n#ifndef USE_ASSERT_CHECKING\n\tif (HeapTupleIsHeapOnly(heapTuple))\n\t\treturn;\n#endif\n\n\t/*\n\t * Get information from the state structure.  Fall out if nothing to do.\n\t */\n\tnumIndexes = indstate->ri_NumIndices;\n\tif (numIndexes == 0)\n\t\treturn;\n\trelationDescs = indstate->ri_IndexRelationDescs;\n\tindexInfoArray = indstate->ri_IndexRelationInfo;\n\theapRelation = indstate->ri_RelationDesc;\n\n\t/* Need a slot to hold the tuple being examined */\n\tslot = MakeSingleTupleTableSlot(RelationGetDescr(heapRelation), &TTSOpsHeapTuple);\n\tExecStoreHeapTuple(heapTuple, slot, false);\n\n\t/*\n\t * for each index, form and insert the index tuple\n\t */\n\tfor (i = 0; i < numIndexes; i++)\n\t{\n\t\tIndexInfo *indexInfo;\n\t\tRelation index;\n\n\t\tindexInfo = indexInfoArray[i];\n\t\tindex = relationDescs[i];\n\n\t\t/* If the index is marked as read-only, ignore it */\n\t\tif (!indexInfo->ii_ReadyForInserts)\n\t\t\tcontinue;\n\n\t\t/*\n\t\t * Expressional and partial indexes on system catalogs are not\n\t\t * supported, nor exclusion constraints, nor deferred uniqueness\n\t\t */\n\t\tAssert(indexInfo->ii_Expressions == NIL);\n\t\tAssert(indexInfo->ii_Predicate == NIL);\n\t\tAssert(indexInfo->ii_ExclusionOps == NULL);\n\t\tAssert(index->rd_index->indimmediate);\n\t\tAssert(indexInfo->ii_NumIndexKeyAttrs != 0);\n\n\t\t/* see earlier check above */\n#ifdef USE_ASSERT_CHECKING\n\t\tif (HeapTupleIsHeapOnly(heapTuple))\n\t\t{\n\t\t\tAssert(!ReindexIsProcessingIndex(RelationGetRelid(index)));\n\t\t\tcontinue;\n\t\t}\n#endif /* USE_ASSERT_CHECKING */\n\n\t\t/*\n\t\t * FormIndexDatum fills in its values and isnull parameters with the\n\t\t * appropriate values for the column(s) of the index.\n\t\t */\n\t\tFormIndexDatum(indexInfo,\n\t\t\t\t\t   slot,\n\t\t\t\t\t   NULL, /* no expression eval to do */\n\t\t\t\t\t   values,\n\t\t\t\t\t   isnull);\n\n\t\t/*\n\t\t * The index AM does the rest.\n\t\t */\n\t\tindex_insert(index,\t\t\t\t   /* index relation */\n\t\t\t\t\t values,\t\t\t   /* array of index Datums */\n\t\t\t\t\t isnull,\t\t\t   /* is-null flags */\n\t\t\t\t\t &(heapTuple->t_self), /* tid of heap tuple */\n\t\t\t\t\t heapRelation,\n\t\t\t\t\t index->rd_index->indisunique ? UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,\n\t\t\t\t\t false,\n\t\t\t\t\t indexInfo);\n\t}\n\n\tExecDropSingleTupleTableSlot(slot);\n}\n"
  },
  {
    "path": "src/ts_catalog/catalog.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <access/heapam.h>\n#include <nodes/nodes.h>\n#include <utils/jsonb.h>\n#include <utils/rel.h>\n\n#include \"export.h\"\n#include \"extension_constants.h\"\n#include \"scanner.h\"\n\n/*\n * TimescaleDB catalog.\n *\n * The TimescaleDB catalog contains schema metadata for hypertables, among other\n * things. The metadata is stored in regular tables. This header file contains\n * definitions for those tables and should match any table declarations in\n * sql/pre_install/tables.sql.\n *\n * A source file that includes this header has access to a catalog object,\n * which contains cached information about catalog tables, such as relation\n * OIDs.\n *\n * Generally, definitions and naming should roughly follow how things are done\n * in Postgres internally.\n */\ntypedef enum CatalogTable\n{\n\tHYPERTABLE = 0,\n\tDIMENSION,\n\tDIMENSION_SLICE,\n\tCHUNK,\n\tCHUNK_CONSTRAINT,\n\tCHUNK_REWRITE,\n\tTABLESPACE,\n\tBGW_JOB,\n\tBGW_JOB_STAT,\n\tBGW_JOB_STAT_HISTORY,\n\tMETADATA,\n\tBGW_POLICY_CHUNK_STATS,\n\tCONTINUOUS_AGG,\n\tCONTINUOUS_AGGS_HYPERTABLE_INVALIDATION_LOG,\n\tCONTINUOUS_AGGS_INVALIDATION_THRESHOLD,\n\tCONTINUOUS_AGGS_MATERIALIZATION_INVALIDATION_LOG,\n\tCONTINUOUS_AGGS_MATERIALIZATION_RANGES,\n\tCOMPRESSION_SETTINGS,\n\tCOMPRESSION_CHUNK_SIZE,\n\tCONTINUOUS_AGGS_BUCKET_FUNCTION,\n\tCONTINUOUS_AGGS_WATERMARK,\n\tTELEMETRY_EVENT,\n\tCHUNK_COLUMN_STATS,\n\t/* Don't forget updating catalog.c when adding new tables! */\n\t_MAX_CATALOG_TABLES,\n} CatalogTable;\n\ntypedef struct TableInfoDef\n{\n\tconst char *schema_name;\n\tconst char *table_name;\n} TableInfoDef;\n\ntypedef struct TableIndexDef\n{\n\tint length;\n\tchar **names;\n} TableIndexDef;\n\n#define TS_CAGG_CATALOG_IDX \"continuous_agg_pkey\"\n\n#define INVALID_CATALOG_TABLE _MAX_CATALOG_TABLES\n#define INVALID_INDEXID -1\n\n#define CATALOG_INTERNAL_FUNC(catalog, func) (catalog->functions[func].function_id)\n\n#define CatalogInternalCall1(func, datum1)                                                         \\\n\tOidFunctionCall1(CATALOG_INTERNAL_FUNC(ts_catalog_get(), func), datum1)\n#define CatalogInternalCall2(func, datum1, datum2)                                                 \\\n\tOidFunctionCall2(CATALOG_INTERNAL_FUNC(ts_catalog_get(), func), datum1, datum2)\n#define CatalogInternalCall3(func, datum1, datum2, datum3)                                         \\\n\tOidFunctionCall3(CATALOG_INTERNAL_FUNC(ts_catalog_get(), func), datum1, datum2, datum3)\n#define CatalogInternalCall4(func, datum1, datum2, datum3, datum4)                                 \\\n\tOidFunctionCall4(CATALOG_INTERNAL_FUNC(ts_catalog_get(), func), datum1, datum2, datum3, datum4)\n\ntypedef enum InternalFunction\n{\n\tDDL_ADD_CHUNK_CONSTRAINT,\n\tDDL_CONSTRAINT_CLONE,\n\t_MAX_INTERNAL_FUNCTIONS,\n} InternalFunction;\n\n/******************************\n *\n * Hypertable table definitions\n *\n ******************************/\n\n#define HYPERTABLE_TABLE_NAME \"hypertable\"\n\n/* Hypertable table attribute numbers */\nenum Anum_hypertable\n{\n\tAnum_hypertable_id = 1,\n\tAnum_hypertable_schema_name,\n\tAnum_hypertable_table_name,\n\tAnum_hypertable_associated_schema_name,\n\tAnum_hypertable_associated_table_prefix,\n\tAnum_hypertable_num_dimensions,\n\tAnum_hypertable_chunk_sizing_func_schema,\n\tAnum_hypertable_chunk_sizing_func_name,\n\tAnum_hypertable_chunk_target_size,\n\tAnum_hypertable_compression_state,\n\tAnum_hypertable_compressed_hypertable_id,\n\tAnum_hypertable_status,\n\t_Anum_hypertable_max,\n};\n\n#define Natts_hypertable (_Anum_hypertable_max - 1)\n\ntypedef struct FormData_hypertable\n{\n\tint32 id;\n\tNameData schema_name;\n\tNameData table_name;\n\tNameData associated_schema_name;\n\tNameData associated_table_prefix;\n\tint16 num_dimensions;\n\tNameData chunk_sizing_func_schema;\n\tNameData chunk_sizing_func_name;\n\tint64 chunk_target_size;\n\tint16 compression_state;\n\tint32 compressed_hypertable_id;\n\tint32 status;\n} FormData_hypertable;\n\ntypedef FormData_hypertable *Form_hypertable;\n\n/* Hypertable primary index attribute numbers */\nenum Anum_hypertable_pkey_idx\n{\n\tAnum_hypertable_pkey_idx_id = 1,\n\t_Anum_hypertable_pkey_max,\n};\n\n#define Natts_hypertable_pkey_idx (_Anum_hypertable_pkey_max - 1)\n\n/* Hypertable name (schema,table) index attribute numbers */\nenum Anum_hypertable_name_idx\n{\n\tAnum_hypertable_name_idx_table = 1,\n\tAnum_hypertable_name_idx_schema,\n\t_Anum_hypertable_name_max,\n};\n\n#define Natts_hypertable_name_idx (_Anum_hypertable_name_max - 1)\n\nenum\n{\n\tHYPERTABLE_ID_INDEX = 0,\n\tHYPERTABLE_NAME_INDEX,\n\t_MAX_HYPERTABLE_INDEX,\n};\n\n/******************************\n *\n * Dimension table definitions\n *\n ******************************/\n\n#define DIMENSION_TABLE_NAME \"dimension\"\n\nenum Anum_dimension\n{\n\tAnum_dimension_id = 1,\n\tAnum_dimension_hypertable_id,\n\tAnum_dimension_column_name,\n\tAnum_dimension_column_type,\n\tAnum_dimension_aligned,\n\tAnum_dimension_num_slices,\n\tAnum_dimension_partitioning_func_schema,\n\tAnum_dimension_partitioning_func,\n\tAnum_dimension_interval_length,\n\tAnum_dimension_compress_interval_length,\n\tAnum_dimension_integer_now_func_schema,\n\tAnum_dimension_integer_now_func,\n\t_Anum_dimension_max,\n};\n\n#define Natts_dimension (_Anum_dimension_max - 1)\n\ntypedef struct FormData_dimension\n{\n\tint32 id;\n\tint32 hypertable_id;\n\tNameData column_name;\n\tOid column_type;\n\tbool aligned;\n\t/* closed (space) columns */\n\tint16 num_slices;\n\tNameData partitioning_func_schema;\n\tNameData partitioning_func;\n\t/* open (time) columns */\n\tint64 interval_length;\n\tint64 compress_interval_length;\n\tNameData integer_now_func_schema;\n\tNameData integer_now_func;\n} FormData_dimension;\n\ntypedef FormData_dimension *Form_dimension;\n\nenum Anum_dimension_id_idx\n{\n\tAnum_dimension_id_idx_id = 1,\n\t_Anum_dimension_id_idx_max,\n};\n\n#define Natts_dimension_id_idx (_Anum_dimension_id_idx_max - 1)\n\nenum Anum_dimension_hypertable_id_column_name_idx\n{\n\tAnum_dimension_hypertable_id_column_name_idx_hypertable_id = 1,\n\tAnum_dimension_hypertable_id_column_name_idx_column_name,\n\t_Anum_dimension_hypertable_id_idx_max,\n};\n\n#define Natts_dimension_hypertable_id_idx (_Anum_dimension_hypertable_id_idx_max - 1)\n\nenum\n{\n\tDIMENSION_ID_IDX = 0,\n\tDIMENSION_HYPERTABLE_ID_COLUMN_NAME_IDX,\n\t_MAX_DIMENSION_INDEX,\n};\n\n/******************************\n *\n * Dimension slice table definitions\n *\n ******************************/\n\n#define DIMENSION_SLICE_TABLE_NAME \"dimension_slice\"\n\nenum Anum_dimension_slice\n{\n\tAnum_dimension_slice_id = 1,\n\tAnum_dimension_slice_dimension_id,\n\tAnum_dimension_slice_range_start,\n\tAnum_dimension_slice_range_end,\n\t_Anum_dimension_slice_max,\n};\n\n#define Natts_dimension_slice (_Anum_dimension_slice_max - 1)\n\ntypedef struct FormData_dimension_slice\n{\n\tint32 id;\n\tint32 dimension_id;\n\tint64 range_start;\n\tint64 range_end;\n} FormData_dimension_slice;\n\ntypedef FormData_dimension_slice *Form_dimension_slice;\n\nenum Anum_dimension_slice_id_idx\n{\n\tAnum_dimension_slice_id_idx_id = 1,\n\t_Anum_dimension_slice_id_idx_max,\n};\n\n#define Natts_dimension_slice_id_idx (_Anum_dimension_slice_id_idx_max - 1)\n\nenum Anum_dimension_slice_dimension_id_range_start_range_end_idx\n{\n\tAnum_dimension_slice_dimension_id_range_start_range_end_idx_dimension_id = 1,\n\tAnum_dimension_slice_dimension_id_range_start_range_end_idx_range_start,\n\tAnum_dimension_slice_dimension_id_range_start_range_end_idx_range_end,\n\t_Anum_dimension_slice_dimension_id_range_start_range_end_idx_max,\n};\n\n#define Natts_dimension_slice_dimension_id_range_start_range_end_idx                               \\\n\t(_Anum_dimension_slice_dimension_id_range_start_range_end_idx_max - 1)\n\nenum\n{\n\tDIMENSION_SLICE_ID_IDX = 0,\n\tDIMENSION_SLICE_DIMENSION_ID_RANGE_START_RANGE_END_IDX,\n\t_MAX_DIMENSION_SLICE_INDEX,\n};\n\n/******************************\n *\n * Dimension range table definitions\n *\n ******************************/\n\n#define CHUNK_COLUMN_STATS_TABLE_NAME \"chunk_column_stats\"\n\nenum Anum_chunk_column_stats\n{\n\tAnum_chunk_column_stats_id = 1,\n\tAnum_chunk_column_stats_hypertable_id,\n\tAnum_chunk_column_stats_chunk_id,\n\tAnum_chunk_column_stats_column_name,\n\tAnum_chunk_column_stats_range_start,\n\tAnum_chunk_column_stats_range_end,\n\tAnum_chunk_column_stats_valid,\n\t_Anum_chunk_column_stats_max,\n};\n\n#define Natts_chunk_column_stats (_Anum_chunk_column_stats_max - 1)\n\ntypedef struct FormData_chunk_column_stats\n{\n\tint32 id;\n\tint32 hypertable_id;\n\tint32 chunk_id;\n\tNameData column_name;\n\tint64 range_start;\n\tint64 range_end;\n\tbool valid;\n} FormData_chunk_column_stats;\n\ntypedef FormData_chunk_column_stats *Form_chunk_column_stats;\n\nenum Anum_chunk_column_stats_id_idx\n{\n\tAnum_chunk_column_stats_id_idx_id = 1,\n\t_Anum_chunk_column_stats_id_idx_max,\n};\n\n#define Natts_chunk_column_stats_id_idx (_Anum_chunk_column_stats_id_idx_max - 1)\n\nenum Anum_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx\n{\n\tAnum_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx_hypertable_id = 1,\n\tAnum_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx_chunk_id,\n\tAnum_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx_column_name,\n\tAnum_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx_range_start,\n\tAnum_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx_range_end,\n\t_Anum_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx_max,\n};\n\n#define Natts_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx              \\\n\t(_Anum_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx_max - 1)\n\nenum\n{\n\tCHUNK_COLUMN_STATS_ID_IDX = 0,\n\tCHUNK_COLUMN_STATS_HT_ID_CHUNK_ID_COLUMN_NAME_IDX,\n\t_MAX_CHUNK_COLUMN_STATS_INDEX,\n};\n\n/*************************\n *\n * Chunk table definitions\n *\n *************************/\n\n#define CHUNK_TABLE_NAME \"chunk\"\n\nenum Anum_chunk\n{\n\tAnum_chunk_id = 1,\n\tAnum_chunk_hypertable_id,\n\tAnum_chunk_schema_name,\n\tAnum_chunk_table_name,\n\tAnum_chunk_compressed_chunk_id,\n\tAnum_chunk_status,\n\tAnum_chunk_osm_chunk,\n\tAnum_chunk_creation_time,\n\t_Anum_chunk_max,\n};\n\n#define Natts_chunk (_Anum_chunk_max - 1)\n\ntypedef struct FormData_chunk\n{\n\tint32 id;\n\tint32 hypertable_id;\n\tNameData schema_name;\n\tNameData table_name;\n\tint32 compressed_chunk_id;\n\tint32 status;\n\tbool osm_chunk;\n\tTimestampTz creation_time;\n} FormData_chunk;\n\ntypedef FormData_chunk *Form_chunk;\n\nenum\n{\n\tCHUNK_ID_INDEX = 0,\n\tCHUNK_HYPERTABLE_ID_INDEX,\n\tCHUNK_SCHEMA_NAME_INDEX,\n\tCHUNK_COMPRESSED_CHUNK_ID_INDEX,\n\tCHUNK_OSM_CHUNK_INDEX,\n\tCHUNK_HYPERTABLE_ID_CREATION_TIME_INDEX,\n\t_MAX_CHUNK_INDEX,\n};\n\nenum Anum_chunk_idx\n{\n\tAnum_chunk_idx_id = 1,\n};\n\nenum Anum_chunk_hypertable_id_idx\n{\n\tAnum_chunk_hypertable_id_idx_hypertable_id = 1,\n};\n\nenum Anum_chunk_compressed_chunk_id_idx\n{\n\tAnum_chunk_compressed_chunk_id_idx_compressed_chunk_id = 1,\n};\n\nenum Anum_chunk_schema_name_idx\n{\n\tAnum_chunk_schema_name_idx_schema_name = 1,\n\tAnum_chunk_schema_name_idx_table_name,\n};\n\nenum Anum_chunk_osm_chunk_idx\n{\n\tAnum_chunk_osm_chunk_idx_osm_chunk = 1,\n\tAnum_chunk_osm_chunk_idx_hypertable_id,\n};\n\nenum Anum_chunk_hypertable_id_creation_time_idx\n{\n\tAnum_chunk_hypertable_id_creation_time_idx_hypertable_id = 1,\n\tAnum_chunk_hypertable_id_creation_time_idx_creation_time,\n};\n\n/************************************\n *\n * Chunk constraint table definitions\n *\n ************************************/\n\n#define CHUNK_CONSTRAINT_TABLE_NAME \"chunk_constraint\"\n\nenum Anum_chunk_constraint\n{\n\tAnum_chunk_constraint_chunk_id = 1,\n\tAnum_chunk_constraint_dimension_slice_id,\n\tAnum_chunk_constraint_constraint_name,\n\tAnum_chunk_constraint_hypertable_constraint_name,\n\t_Anum_chunk_constraint_max,\n};\n\n#define Natts_chunk_constraint (_Anum_chunk_constraint_max - 1)\n\n/* Do Not use GET_STRUCT with FormData_chunk_constraint. It contains NULLS */\ntypedef struct FormData_chunk_constraint\n{\n\tint32 chunk_id;\n\tint32 dimension_slice_id;\n\tNameData constraint_name;\n\tNameData hypertable_constraint_name;\n} FormData_chunk_constraint;\n\ntypedef FormData_chunk_constraint *Form_chunk_constraint;\n\nenum\n{\n\tCHUNK_CONSTRAINT_CHUNK_ID_CONSTRAINT_NAME_IDX = 0,\n\tCHUNK_CONSTRAINT_DIMENSION_SLICE_ID_IDX,\n\t_MAX_CHUNK_CONSTRAINT_INDEX,\n};\n\nenum Anum_chunk_constraint_dimension_slice_id_idx\n{\n\tAnum_chunk_constraint_dimension_slice_id_idx_dimension_slice_id = 1,\n\t_Anum_chunk_constraint_dimension_slice_id_idx_max,\n};\n\nenum Anum_chunk_constraint_chunk_id_constraint_name_idx\n{\n\tAnum_chunk_constraint_chunk_id_constraint_name_idx_chunk_id = 1,\n\tAnum_chunk_constraint_chunk_id_constraint_name_idx_constraint_name,\n\t_Anum_chunk_constraint_chunk_id_constraint_name_idx_max,\n};\n\n/************************************\n *\n * Tablespace table definitions\n *\n ************************************/\n\n#define TABLESPACE_TABLE_NAME \"tablespace\"\n\nenum Anum_tablespace\n{\n\tAnum_tablespace_id = 1,\n\tAnum_tablespace_hypertable_id,\n\tAnum_tablespace_tablespace_name,\n\t_Anum_tablespace_max,\n};\n\n#define Natts_tablespace (_Anum_tablespace_max - 1)\n\ntypedef struct FormData_tablespace\n{\n\tint32 id;\n\tint32 hypertable_id;\n\tNameData tablespace_name;\n} FormData_tablespace;\n\ntypedef FormData_tablespace *Form_tablespace;\n\nenum\n{\n\tTABLESPACE_PKEY_IDX = 0,\n\tTABLESPACE_HYPERTABLE_ID_TABLESPACE_NAME_IDX,\n\t_MAX_TABLESPACE_INDEX,\n};\n\nenum Anum_tablespace_pkey_idx\n{\n\tAnum_tablespace_pkey_idx_tablespace_id = 1,\n\t_Anum_tablespace_pkey_idx_max,\n};\n\ntypedef struct FormData_tablespace_pkey_idx\n{\n\tint32 tablespace_id;\n} FormData_tablespace_pkey_idx;\n\nenum Anum_tablespace_hypertable_id_tablespace_name_idx\n{\n\tAnum_tablespace_hypertable_id_tablespace_name_idx_hypertable_id = 1,\n\tAnum_tablespace_hypertable_id_tablespace_name_idx_tablespace_name,\n\t_Anum_tablespace_hypertable_id_tablespace_name_idx_max,\n};\n\ntypedef struct FormData_tablespace_hypertable_id_tablespace_name_idx\n{\n\tint32 hypertable_id;\n\tNameData tablespace_name;\n} FormData_tablespace_hypertable_id_tablespace_name_idx;\n\n/************************************\n *\n * bgw_job table definitions\n *\n ************************************/\n\n#define BGW_JOB_TABLE_NAME \"bgw_job\"\n\nenum Anum_bgw_job\n{\n\tAnum_bgw_job_id = 1,\n\tAnum_bgw_job_application_name,\n\tAnum_bgw_job_schedule_interval,\n\tAnum_bgw_job_max_runtime,\n\tAnum_bgw_job_max_retries,\n\tAnum_bgw_job_retry_period,\n\tAnum_bgw_job_proc_schema,\n\tAnum_bgw_job_proc_name,\n\tAnum_bgw_job_owner,\n\tAnum_bgw_job_scheduled,\n\tAnum_bgw_job_fixed_schedule,\n\tAnum_bgw_job_initial_start,\n\tAnum_bgw_job_hypertable_id,\n\tAnum_bgw_job_config,\n\tAnum_bgw_job_check_schema,\n\tAnum_bgw_job_check_name,\n\tAnum_bgw_job_timezone,\n\t_Anum_bgw_job_max,\n};\n\n#define Natts_bgw_job (_Anum_bgw_job_max - 1)\n\n/* fixed_schedule needs to come before the varlen fields\n for GETSTRUCT to work */\ntypedef struct FormData_bgw_job\n{\n\tint32 id;\n\tNameData application_name;\n\tInterval schedule_interval;\n\tInterval max_runtime;\n\tint32 max_retries;\n\tInterval retry_period;\n\tNameData proc_schema;\n\tNameData proc_name;\n\tOid owner;\n\tbool scheduled;\n\tbool fixed_schedule;\n\tTimestampTz initial_start;\n\tint32 hypertable_id;\n\tJsonb *config;\n\tNameData check_schema;\n\tNameData check_name;\n\ttext *timezone;\n} FormData_bgw_job;\n\ntypedef FormData_bgw_job *Form_bgw_job;\n\nenum\n{\n\tBGW_JOB_PKEY_IDX = 0,\n\tBGW_JOB_PROC_HYPERTABLE_ID_IDX,\n\t_MAX_BGW_JOB_INDEX,\n};\n\nenum Anum_bgw_job_pkey_idx\n{\n\tAnum_bgw_job_pkey_idx_id = 1,\n\t_Anum_bgw_job_pkey_idx_max,\n};\n\n#define Natts_bjw_job_pkey_idx (_Anum_bgw_job_pkey_idx_max - 1)\n\nenum Anum_bgw_job_proc_hypertable_id_idx\n{\n\tAnum_bgw_job_proc_hypertable_id_idx_proc_schema = 1,\n\tAnum_bgw_job_proc_hypertable_id_idx_proc_name,\n\tAnum_bgw_job_proc_hypertable_id_idx_hypertable_id,\n\t_Anum_bgw_job_proc_hypertable_id_idx_max,\n};\n\n#define Natts_bgw_job_proc_hypertable_id_idx (_Anum_bgw_job_proc_hypertable_id_idx_max - 1)\n\n/************************************\n *\n * bgw_job_stat table definitions\n *\n ************************************/\n\n#define BGW_JOB_STAT_TABLE_NAME \"bgw_job_stat\"\n\nenum Anum_bgw_job_stat\n{\n\tAnum_bgw_job_stat_job_id = 1,\n\tAnum_bgw_job_stat_last_start,\n\tAnum_bgw_job_stat_last_finish,\n\tAnum_bgw_job_stat_next_start,\n\tAnum_bgw_job_stat_last_successful_finish,\n\tAnum_bgw_job_stat_last_run_success,\n\tAnum_bgw_job_stat_total_runs,\n\tAnum_bgw_job_stat_total_duration,\n\tAnum_bgw_job_stat_total_duration_failures,\n\tAnum_bgw_job_stat_total_success,\n\tAnum_bgw_job_stat_total_failures,\n\tAnum_bgw_job_stat_total_crashes,\n\tAnum_bgw_job_stat_consecutive_failures,\n\tAnum_bgw_job_stat_consecutive_crashes,\n\tAnum_bgw_job_stat_flags,\n\t_Anum_bgw_job_stat_max,\n};\n\n#define Natts_bgw_job_stat (_Anum_bgw_job_stat_max - 1)\n\ntypedef struct FormData_bgw_job_stat\n{\n\tint32 id;\n\tTimestampTz last_start;\n\tTimestampTz last_finish;\n\tTimestampTz next_start;\n\tTimestampTz last_successful_finish;\n\tbool last_run_success;\n\tint64 total_runs;\n\tInterval total_duration;\n\tInterval total_duration_failures;\n\tint64 total_success;\n\tint64 total_failures;\n\tint64 total_crashes;\n\tint32 consecutive_failures;\n\tint32 consecutive_crashes;\n\tint32 flags;\n} FormData_bgw_job_stat;\n\ntypedef FormData_bgw_job_stat *Form_bgw_job_stat;\n\nenum\n{\n\tBGW_JOB_STAT_PKEY_IDX = 0,\n\t_MAX_BGW_JOB_STAT_INDEX,\n};\n\nenum Anum_bgw_job_stat_pkey_idx\n{\n\tAnum_bgw_job_stat_pkey_idx_job_id = 1,\n\t_Anum_bgw_job_stat_pkey_idx_max,\n};\n\n#define Natts_bjw_job_stat_pkey_idx (_Anum_bgw_job_stat_pkey_idx_max - 1)\n\n#define BGW_JOB_STAT_HISTORY_TABLE_NAME \"bgw_job_stat_history\"\n\nenum Anum_bgw_job_stat_history\n{\n\tAnum_bgw_job_stat_history_id = 1,\n\tAnum_bgw_job_stat_history_job_id,\n\tAnum_bgw_job_stat_history_pid,\n\tAnum_bgw_job_stat_history_execution_start,\n\tAnum_bgw_job_stat_history_execution_finish,\n\tAnum_bgw_job_stat_history_succeeded,\n\tAnum_bgw_job_stat_history_data,\n\t_Anum_bgw_job_stat_history_max,\n};\n\n#define Natts_bgw_job_stat_history (_Anum_bgw_job_stat_history_max - 1)\n\ntypedef struct FormData_bgw_job_stat_history\n{\n\tint64 id;\n\tint32 job_id;\n\tint32 pid;\n\tTimestampTz execution_start;\n\tTimestampTz execution_finish;\n\tbool succeeded;\n\tJsonb *data;\n} FormData_bgw_job_stat_history;\n\ntypedef FormData_bgw_job_stat_history *Form_bgw_job_stat_history;\n\nenum\n{\n\tBGW_JOB_STAT_HISTORY_PKEY_IDX = 0,\n\t_MAX_BGW_JOB_STAT_HISTORY_INDEX,\n};\n\nenum Anum_bgw_job_stat_history_pkey_idx\n{\n\tAnum_bgw_job_stat_history_pkey_idx_id = 1,\n\t_Anum_bgw_job_stat_history_pkey_idx_max,\n};\n\n#define Natts_bjw_job_stat_history_pkey_idx (_Anum_bgw_job_stat_history_pkey_idx_max - 1)\n\n/******************************\n *\n * metadata table definitions\n *\n ******************************/\n\n#define METADATA_TABLE_NAME \"metadata\"\n\nenum Anum_metadata\n{\n\tAnum_metadata_key = 1,\n\tAnum_metadata_value,\n\tAnum_metadata_include_in_telemetry,\n\t_Anum_metadata_max,\n};\n\n#define Natts_metadata (_Anum_metadata_max - 1)\n\ntypedef struct FormData_metadata\n{\n\tNameData key;\n\ttext *value;\n} FormData_metadata;\n\ntypedef FormData_metadata *Form_metadata;\n\n/* metadata primary index attribute numbers */\nenum Anum_metadata_pkey_idx\n{\n\tAnum_metadata_pkey_idx_id = 1,\n\t_Anum_metadata_pkey_max,\n};\n\n#define Natts_metadata_pkey_idx (_Anum_metadata_pkey_max - 1)\n\nenum\n{\n\tMETADATA_PKEY_IDX = 0,\n\t_MAX_METADATA_INDEX,\n};\n\n/*\n * telemetry_event table definition\n */\n\n#define TELEMETRY_EVENT_TABLE_NAME \"telemetry_event\"\n\nenum Anum_telemetry_event\n{\n\tAnum_telemetry_event_created = 1,\n\tAnum_telemetry_event_tag,\n\tAnum_telemetry_event_body,\n\t_Anum_telemetry_event_max,\n};\n\n#define Natts_telemetry_event_max (_Anum_telemetry_event_max - 1)\n\n/****** BGW_POLICY_CHUNK_STATS TABLE definitions */\n#define BGW_POLICY_CHUNK_STATS_TABLE_NAME \"bgw_policy_chunk_stats\"\n\nenum Anum_bgw_policy_chunk_stats\n{\n\tAnum_bgw_policy_chunk_stats_job_id = 1,\n\tAnum_bgw_policy_chunk_stats_chunk_id,\n\tAnum_bgw_policy_chunk_stats_num_times_job_run,\n\tAnum_bgw_policy_chunk_stats_last_time_job_run,\n\t_Anum_bgw_policy_chunk_stats_max,\n};\n\n#define Natts_bgw_policy_chunk_stats (_Anum_bgw_policy_chunk_stats_max - 1)\n\ntypedef struct FormData_bgw_policy_chunk_stats\n{\n\tint32 job_id;\n\tint32 chunk_id;\n\tint32 num_times_job_run;\n\tTimestampTz last_time_job_run;\n} FormData_bgw_policy_chunk_stats;\n\ntypedef FormData_bgw_policy_chunk_stats *Form_bgw_job_chunk_stats;\n\nenum\n{\n\tBGW_POLICY_CHUNK_STATS_JOB_ID_CHUNK_ID_IDX = 0,\n\t_MAX_BGW_POLICY_CHUNK_STATS_INDEX,\n};\n\nenum Anum_bgw_policy_chunk_stats_job_id_chunk_id_idx\n{\n\tAnum_bgw_policy_chunk_stats_job_id_chunk_id_idx_job_id = 1,\n\tAnum_bgw_policy_chunk_stats_job_id_chunk_id_idx_chunk_id,\n\t_Anum_bgw_policy_chunk_stats_job_id_chunk_id_idx_max,\n};\n\ntypedef struct FormData_bgw_policy_chunk_stats_job_id_chunk_id_idx\n{\n\tint32 job_id;\n\tint32 chunk_id;\n} FormData_bgw_policy_chunk_stats_job_id_chunk_id_idx;\n\n/******************************************\n *\n * continuous_agg table definitions\n *\n ******************************************/\n#define CONTINUOUS_AGG_TABLE_NAME \"continuous_agg\"\ntypedef enum Anum_continuous_agg\n{\n\tAnum_continuous_agg_mat_hypertable_id = 1,\n\tAnum_continuous_agg_raw_hypertable_id,\n\tAnum_continuous_agg_parent_mat_hypertable_id,\n\tAnum_continuous_agg_user_view_schema,\n\tAnum_continuous_agg_user_view_name,\n\tAnum_continuous_agg_partial_view_schema,\n\tAnum_continuous_agg_partial_view_name,\n\tAnum_continuous_agg_direct_view_schema,\n\tAnum_continuous_agg_direct_view_name,\n\tAnum_continuous_agg_materialize_only,\n\t_Anum_continuous_agg_max,\n} Anum_continuous_agg;\n\n#define Natts_continuous_agg (_Anum_continuous_agg_max - 1)\n\ntypedef struct FormData_continuous_agg\n{\n\tint32 mat_hypertable_id;\n\tint32 raw_hypertable_id;\n\tint32 parent_mat_hypertable_id; /* Nested Continuous Aggregate */\n\tNameData user_view_schema;\n\tNameData user_view_name;\n\tNameData partial_view_schema;\n\tNameData partial_view_name;\n\tNameData direct_view_schema;\n\tNameData direct_view_name;\n\tbool materialized_only;\n} FormData_continuous_agg;\n\ntypedef FormData_continuous_agg *Form_continuous_agg;\n\nenum\n{\n\tCONTINUOUS_AGG_PARTIAL_VIEW_SCHEMA_PARTIAL_VIEW_NAME_KEY = 0,\n\tCONTINUOUS_AGG_PKEY,\n\tCONTINUOUS_AGG_USER_VIEW_SCHEMA_USER_VIEW_NAME_KEY,\n\tCONTINUOUS_AGG_RAW_HYPERTABLE_ID_IDX,\n\t_MAX_CONTINUOUS_AGG_INDEX,\n};\n\ntypedef enum Anum_continuous_agg_partial_view_schema_partial_view_name_key\n{\n\tAnum_continuous_agg_partial_view_schema_partial_view_name_key_partial_view_schema = 1,\n\tAnum_continuous_agg_partial_view_schema_partial_view_name_key_partial_view_name,\n\t_Anum_continuous_agg_partial_view_schema_partial_view_name_key_max,\n} Anum_continuous_agg_partial_view_schema_partial_view_name_key;\n\n#define Natts_continuous_agg_partial_view_schema_partial_view_name_key                             \\\n\t(_Anum_continuous_agg_partial_view_schema_partial_view_name_key_max - 1)\n\ntypedef enum Anum_continuous_agg_pkey\n{\n\tAnum_continuous_agg_pkey_mat_hypertable_id = 1,\n\t_Anum_continuous_agg_pkey_max,\n} Anum_continuous_agg_pkey;\n\n#define Natts_continuous_agg_pkey (_Anum_continuous_agg_pkey_max - 1)\n\ntypedef enum Anum_continuous_agg_user_view_schema_user_view_name_key\n{\n\tAnum_continuous_agg_user_view_schema_user_view_name_key_user_view_schema = 1,\n\tAnum_continuous_agg_user_view_schema_user_view_name_key_user_view_name,\n\t_Anum_continuous_agg_user_view_schema_user_view_name_key_max,\n} Anum_continuous_agg_user_view_schema_user_view_name_key;\n\n#define Natts_continuous_agg_user_view_schema_user_view_name_key                                   \\\n\t(_Anum_continuous_agg_user_view_schema_user_view_name_key_max - 1)\n\ntypedef enum Anum_continuous_agg_raw_hypertable_id_idx\n{\n\tAnum_continuous_agg_raw_hypertable_id_idx_raw_hypertable_id = 1,\n\t_Anum_continuous_agg_raw_hypertable_id_idx_max,\n} Anum_continuous_agg_raw_hypertable_id_idx;\n\n#define Natts_continuous_agg_raw_hypertable_id_idx                                                 \\\n\t(_Anum_continuous_agg_raw_hypertable_id_idx_max - 1)\n\n/*** continuous_aggs_bucket_function table definitions ***/\n\n#define CONTINUOUS_AGGS_BUCKET_FUNCTION_TABLE_NAME \"continuous_aggs_bucket_function\"\ntypedef enum Anum_continuous_aggs_bucket_function\n{\n\tAnum_continuous_aggs_bucket_function_mat_hypertable_id = 1,\n\tAnum_continuous_aggs_bucket_function_function,\n\tAnum_continuous_aggs_bucket_function_bucket_width,\n\tAnum_continuous_aggs_bucket_function_bucket_origin,\n\tAnum_continuous_aggs_bucket_function_bucket_offset,\n\tAnum_continuous_aggs_bucket_function_bucket_timezone,\n\tAnum_continuous_aggs_bucket_function_bucket_fixed_width,\n\t_Anum_continuous_aggs_bucket_function_max,\n} Anum_continuous_aggs_bucket_function;\n\n#define Natts_continuous_aggs_bucket_function (_Anum_continuous_aggs_bucket_function_max - 1)\n\nenum\n{\n\tCONTINUOUS_AGGS_BUCKET_FUNCTION_PKEY_IDX = 0,\n\t_MAX_CONTINUOUS_AGGS_BUCKET_FUNCTION_INDEX,\n};\n\ntypedef enum Anum_continuous_aggs_bucket_function_pkey\n{\n\tAnum_continuous_aggs_bucket_function_pkey_mat_hypertable_id = 1,\n\t_Anum_continuous_aggs_bucket_function_pkey_max,\n} Anum_continuous_aggs_bucket_function_pkey;\n\n#define Natts_continuous_aggs_bucket_function_pkey                                                 \\\n\t(_Anum_continuous_aggs_bucket_function_pkey_max - 1)\n\n/*\n * CONTINUOUS_AGGS_HYPERTABLE_INVALIDATION_LOG_TABLE definitions\n *\n * The definition of CONTINUOUS_AGGS_HYPERTABLE_INVALIDATION_PLUGIN_NAME is\n * generated from config.h.in and can be found in the generated file.\n */\n#define CONTINUOUS_AGGS_HYPERTABLE_INVALIDATION_LOG_TABLE_NAME                                     \\\n\t\"continuous_aggs_hypertable_invalidation_log\"\n\ntypedef enum Anum_continuous_aggs_hypertable_invalidation_log\n{\n\tAnum_continuous_aggs_hypertable_invalidation_log_hypertable_id = 1,\n\tAnum_continuous_aggs_hypertable_invalidation_log_lowest_modified_value,\n\tAnum_continuous_aggs_hypertable_invalidation_log_greatest_modified_value,\n\t_Anum_continuous_aggs_hypertable_invalidation_log_max,\n} Anum_continuous_aggs_hypertable_invalidation_log;\n\n#define Natts_continuous_aggs_hypertable_invalidation_log                                          \\\n\t(_Anum_continuous_aggs_hypertable_invalidation_log_max - 1)\n\ntypedef struct FormData_continuous_aggs_hypertable_invalidation_log\n{\n\tint32 hypertable_id;\n\tint64 lowest_modified_value;\n\tint64 greatest_modified_value;\n} FormData_continuous_aggs_hypertable_invalidation_log;\n\ntypedef FormData_continuous_aggs_hypertable_invalidation_log\n\t*Form_continuous_aggs_hypertable_invalidation_log;\n\nenum\n{\n\tCONTINUOUS_AGGS_HYPERTABLE_INVALIDATION_LOG_IDX = 0,\n\t_MAX_CONTINUOUS_AGGS_HYPERTABLE_INVALIDATION_LOG_INDEX,\n};\ntypedef enum Anum_continuous_aggs_hypertable_invalidation_log_idx\n{\n\tAnum_continuous_aggs_hypertable_invalidation_log_idx_hypertable_id = 1,\n\tAnum_continuous_aggs_hypertable_invalidation_log_idx_lowest_modified_value,\n\t_Anum_continuous_aggs_hypertable_invalidation_log_idx_max,\n} Anum_continuous_aggs_hypertable_invalidation_log_idx;\n\n#define Natts_continuous_aggs_hypertable_invalidation_log_idx                                      \\\n\t(_Anum_continuous_aggs_hypertable_invalidation_log_idx_max - 1)\n\n/****** CONTINUOUS_AGGS_INVALIDATION_THRESHOLD_TABLE definitions*/\n#define CONTINUOUS_AGGS_INVALIDATION_THRESHOLD_TABLE_NAME \"continuous_aggs_invalidation_threshold\"\ntypedef enum Anum_continuous_aggs_invalidation_threshold\n{\n\tAnum_continuous_aggs_invalidation_threshold_hypertable_id = 1,\n\tAnum_continuous_aggs_invalidation_threshold_watermark,\n\t_Anum_continuous_aggs_invalidation_threshold_max,\n} Anum_continuous_aggs_invalidation_threshold;\n\n#define Natts_continuous_aggs_invalidation_threshold                                               \\\n\t(_Anum_continuous_aggs_invalidation_threshold_max - 1)\n\ntypedef struct FormData_continuous_aggs_invalidation_threshold\n{\n\tint32 hypertable_id;\n\tint64 watermark;\n} FormData_continuous_aggs_invalidation_threshold;\n\ntypedef FormData_continuous_aggs_invalidation_threshold\n\t*Form_continuous_aggs_invalidation_threshold;\n\nenum\n{\n\tCONTINUOUS_AGGS_INVALIDATION_THRESHOLD_PKEY = 0,\n\t_MAX_CONTINUOUS_AGGS_INVALIDATION_THRESHOLD_INDEX,\n};\ntypedef enum Anum_continuous_aggs_invalidation_threshold_pkey\n{\n\tAnum_continuous_aggs_invalidation_threshold_pkey_hypertable_id = 1,\n\t_Anum_continuous_aggs_invalidation_threshold_pkey_max,\n} Anum_continuous_aggs_invalidation_threshold_pkey;\n\n#define Natts_continuous_aggs_invalidation_threshold_pkey                                          \\\n\t(_Anum_continuous_aggs_invalidation_threshold_pkey_max - 1)\n\n/****** CONTINUOUS_AGGS_MATERIALIZATION_INVALIDATION_LOG_TABLE definitions*/\n#define CONTINUOUS_AGGS_MATERIALIZATION_INVALIDATION_LOG_TABLE_NAME                                \\\n\t\"continuous_aggs_materialization_invalidation_log\"\ntypedef enum Anum_continuous_aggs_materialization_invalidation_log\n{\n\tAnum_continuous_aggs_materialization_invalidation_log_materialization_id = 1,\n\tAnum_continuous_aggs_materialization_invalidation_log_lowest_modified_value,\n\tAnum_continuous_aggs_materialization_invalidation_log_greatest_modified_value,\n\t_Anum_continuous_aggs_materialization_invalidation_log_max,\n} Anum_continuous_aggs_materialization_invalidation_log;\n\n#define Natts_continuous_aggs_materialization_invalidation_log                                     \\\n\t(_Anum_continuous_aggs_materialization_invalidation_log_max - 1)\n\ntypedef struct FormData_continuous_aggs_materialization_invalidation_log\n{\n\tint32 materialization_id;\n\tint64 lowest_modified_value;\n\tint64 greatest_modified_value;\n} FormData_continuous_aggs_materialization_invalidation_log;\n\ntypedef FormData_continuous_aggs_materialization_invalidation_log\n\t*Form_continuous_aggs_materialization_invalidation_log;\n\nenum\n{\n\tCONTINUOUS_AGGS_MATERIALIZATION_INVALIDATION_LOG_IDX = 0,\n\t_MAX_CONTINUOUS_AGGS_MATERIALIZATION_INVALIDATION_LOG_INDEX,\n};\ntypedef enum Anum_continuous_aggs_materialization_invalidation_log_idx\n{\n\tAnum_continuous_aggs_materialization_invalidation_log_idx_materialization_id = 1,\n\tAnum_continuous_aggs_materialization_invalidation_log_idx_lowest_modified_value,\n\t_Anum_continuous_aggs_materialization_invalidation_log_idx_max,\n} Anum_continuous_aggs_materialization_invalidation_log_idx;\n\n#define Natts_continuous_aggs_materialization_invalidation_log_idx                                 \\\n\t(_Anum_continuous_aggs_materialization_invalidation_log_idx_max - 1)\n\n/****** CONTINUOUS_AGGS_MATERIALIZATION_RANGES_TABLE definitions*/\n#define CONTINUOUS_AGGS_MATERIALIZATION_RANGES_TABLE_NAME \"continuous_aggs_materialization_ranges\"\ntypedef enum Anum_continuous_aggs_materialization_ranges\n{\n\tAnum_continuous_aggs_materialization_ranges_materialization_id = 1,\n\tAnum_continuous_aggs_materialization_ranges_lowest_modified_value,\n\tAnum_continuous_aggs_materialization_ranges_greatest_modified_value,\n\t_Anum_continuous_aggs_materialization_ranges_max,\n} Anum_continuous_aggs_materialization_ranges;\n\n#define Natts_continuous_aggs_materialization_ranges                                               \\\n\t(_Anum_continuous_aggs_materialization_ranges_max - 1)\n\ntypedef struct FormData_continuous_aggs_materialization_ranges\n{\n\tint32 materialization_id;\n\tint64 lowest_modified_value;\n\tint64 greatest_modified_value;\n} FormData_continuous_aggs_materialization_ranges;\n\ntypedef FormData_continuous_aggs_materialization_ranges\n\t*Form_continuous_aggs_materialization_ranges;\n\nenum\n{\n\tCONTINUOUS_AGGS_MATERIALIZATION_RANGES_IDX = 0,\n\t_MAX_CONTINUOUS_AGGS_MATERIALIZATION_RANGES_INDEX,\n};\ntypedef enum Anum_continuous_aggs_materialization_ranges_idx\n{\n\tAnum_continuous_aggs_materialization_ranges_idx_materialization_id = 1,\n\tAnum_continuous_aggs_materialization_ranges_idx_lowest_modified_value,\n\t_Anum_continuous_aggs_materialization_ranges_idx_max,\n} Anum_continuous_aggs_materialization_ranges_idx;\n\n#define Natts_continuous_aggs_materialization_ranges_idx                                           \\\n\t(_Anum_continuous_aggs_materialization_ranges_idx_max - 1)\n\n/****** CONTINUOUS_AGGS_WATERMARK_TABLE definitions*/\n#define CONTINUOUS_AGGS_WATERMARK_TABLE_NAME \"continuous_aggs_watermark\"\ntypedef enum Anum_continuous_aggs_watermark\n{\n\tAnum_continuous_aggs_watermark_mat_hypertable_id = 1,\n\tAnum_continuous_aggs_watermark_watermark,\n\t_Anum_continuous_aggs_watermark_max,\n} Anum_continuous_aggs_watermark;\n\n#define Natts_continuous_aggs_watermark (_Anum_continuous_aggs_watermark_max - 1)\n\ntypedef struct FormData_continuous_aggs_watermark\n{\n\tint32 mat_hypertable_id;\n\tint64 watermark;\n} FormData_continuous_aggs_watermark;\n\ntypedef FormData_continuous_aggs_watermark *Form_continuous_aggs_watermark;\n\nenum\n{\n\tCONTINUOUS_AGGS_WATERMARK_PKEY = 0,\n\t_MAX_CONTINUOUS_AGGS_WATERMARK_INDEX,\n};\n\ntypedef enum Anum_continuous_aggs_watermark_pkey\n{\n\tAnum_continuous_aggs_watermark_pkey_mat_hypertable_id = 1,\n\t_Anum_continuous_aggs_watermark_pkey_max,\n} Anum_continuous_aggs_watermark_pkey;\n\n#define Natts_continuous_aggs_watermark_pkey (_Anum_continuous_aggs_watermark_pkey_max - 1)\n\n#define COMPRESSION_SETTINGS_TABLE_NAME \"compression_settings\"\n\ntypedef enum Anum_compression_settings\n{\n\tAnum_compression_settings_relid = 1,\n\tAnum_compression_settings_compress_relid,\n\tAnum_compression_settings_segmentby,\n\tAnum_compression_settings_orderby,\n\tAnum_compression_settings_orderby_desc,\n\tAnum_compression_settings_orderby_nullsfirst,\n\tAnum_compression_settings_index,\n\t_Anum_compression_settings_max,\n} Anum_compression_settings;\n\n#define Natts_compression_settings (_Anum_compression_settings_max - 1)\n\ntypedef struct FormData_compression_settings\n{\n\tOid relid;\n\tOid compress_relid;\n\tArrayType *segmentby;\n\tArrayType *orderby;\n\tArrayType *orderby_desc;\n\tArrayType *orderby_nullsfirst;\n\tJsonb *index;\n} FormData_compression_settings;\n\ntypedef FormData_compression_settings *Form_compression_settings;\n\nenum\n{\n\tCOMPRESSION_SETTINGS_PKEY = 0,\n\tCOMPRESSION_SETTINGS_COMPRESS_RELID_IDX,\n\t_MAX_COMPRESSION_SETTINGS_INDEX,\n};\n\ntypedef enum Anum_compression_settings_pkey\n{\n\tAnum_compression_settings_pkey_relid = 1,\n\t_Anum_compression_settings_pkey_max,\n} Anum_compression_settings_pkey;\n\n#define Natts_compression_chunk_size_pkey (_Anum_compression_chunk_size_pkey_max - 1)\n\ntypedef enum Anum_compression_settings_compress_relid_idx\n{\n\tAnum_compression_settings_compress_relid_idx_relid = 1,\n\t_Anum_compression_settings_compress_relid_idx_max,\n} Anum_compression_settings_compress_relid_idx;\n\n#define Natts_compression_settings_compress_relid_idx                                              \\\n\t(_Anum_compression_settings_compress_relid_idx_max - 1)\n\n#define COMPRESSION_CHUNK_SIZE_TABLE_NAME \"compression_chunk_size\"\ntypedef enum Anum_compression_chunk_size\n{\n\tAnum_compression_chunk_size_chunk_id = 1,\n\tAnum_compression_chunk_size_compressed_chunk_id,\n\tAnum_compression_chunk_size_uncompressed_heap_size,\n\tAnum_compression_chunk_size_uncompressed_toast_size,\n\tAnum_compression_chunk_size_uncompressed_index_size,\n\tAnum_compression_chunk_size_compressed_heap_size,\n\tAnum_compression_chunk_size_compressed_toast_size,\n\tAnum_compression_chunk_size_compressed_index_size,\n\tAnum_compression_chunk_size_numrows_pre_compression,\n\tAnum_compression_chunk_size_numrows_post_compression,\n\tAnum_compression_chunk_size_numrows_frozen_immediately,\n\t_Anum_compression_chunk_size_max,\n} Anum_compression_chunk_size;\n\n#define Natts_compression_chunk_size (_Anum_compression_chunk_size_max - 1)\n\ntypedef struct FormData_compression_chunk_size\n{\n\tint32 chunk_id;\n\tint32 compressed_chunk_id;\n\tint64 uncompressed_heap_size;\n\tint64 uncompressed_toast_size;\n\tint64 uncompressed_index_size;\n\tint64 compressed_heap_size;\n\tint64 compressed_toast_size;\n\tint64 compressed_index_size;\n\tint64 numrows_pre_compression;\n\tint64 numrows_post_compression;\n\tint64 numrows_frozen_immediately;\n} FormData_compression_chunk_size;\n\ntypedef FormData_compression_chunk_size *Form_compression_chunk_size;\n\nenum\n{\n\tCOMPRESSION_CHUNK_SIZE_PKEY = 0,\n\t_MAX_COMPRESSION_CHUNK_SIZE_INDEX,\n};\ntypedef enum Anum_compression_chunk_size_pkey\n{\n\tAnum_compression_chunk_size_pkey_chunk_id = 1,\n\t_Anum_compression_chunk_size_pkey_max,\n} Anum_compression_chunk_size_pkey;\n\n#define Natts_compression_chunk_size_pkey (_Anum_compression_chunk_size_pkey_max - 1)\n\n#define CHUNK_REWRITE_TABLE_NAME \"chunk_rewrite\"\n\ntypedef enum Anum_chunk_rewrite\n{\n\tAnum_chunk_rewrite_chunk_relid = 1,\n\tAnum_chunk_rewrite_new_relid,\n\t_Anum_chunk_rewrite_max,\n} Anum_chunk_rewrite;\n\n#define Natts_chunk_rewrite (_Anum_chunk_rewrite_max - 1)\n\ntypedef struct FormData_chunk_rewrite\n{\n\tOid chunk_relid;\n\tOid new_relid;\n} FormData_chunk_rewrite;\n\ntypedef FormData_chunk_rewrite *Form_chunk_rewrite;\n\nenum\n{\n\tCHUNK_REWRITE_IDX = 0,\n\t_MAX_CHUNK_REWRITE_INDEX,\n};\ntypedef enum Anum_chunk_rewrite_pkey\n{\n\tAnum_chunk_rewrite_key_chunk_relid = 1,\n\t_Anum_chunk_rewrite_key_max,\n} Anum_chunk_rewrite_pkey;\n\n#define Natts_chunk_rewrite_key (_Anum_chunk_rewrite_key_max - 1)\n\n/*\n * The maximum number of indexes a catalog table can have.\n * This needs to be bumped in case of new catalog tables that have more indexes.\n */\n#define _MAX_TABLE_INDEXES 6\n\ntypedef enum CacheType\n{\n\tCACHE_TYPE_HYPERTABLE,\n\tCACHE_TYPE_BGW_JOB,\n\tCACHE_TYPE_EXTENSION,\n\t_MAX_CACHE_TYPES\n} CacheType;\n\ntypedef struct CatalogTableInfo\n{\n\tconst char *schema_name;\n\tconst char *name;\n\tOid id;\n\tOid serial_relid;\n\tOid index_ids[_MAX_TABLE_INDEXES];\n} CatalogTableInfo;\n\ntypedef struct CatalogDatabaseInfo\n{\n\tNameData database_name;\n\tOid database_id;\n\tOid schema_id;\n\tOid owner_uid;\n} CatalogDatabaseInfo;\n\ntypedef struct Catalog\n{\n\tCatalogTableInfo tables[_MAX_CATALOG_TABLES];\n\tOid extension_schema_id[_TS_MAX_SCHEMA];\n\n\tstruct\n\t{\n\t\tOid inval_proxy_id;\n\t} caches[_MAX_CACHE_TYPES];\n\n\tstruct\n\t{\n\t\tOid function_id;\n\t} functions[_MAX_INTERNAL_FUNCTIONS];\n\n\tbool initialized;\n} Catalog;\n\ntypedef struct CatalogSecurityContext\n{\n\tOid saved_uid;\n\tint saved_security_context;\n} CatalogSecurityContext;\n\n#define HYPERTABLE_STATUS_DEFAULT 0\n/* flag set when hypertable has an attached OSM chunk */\n#define HYPERTABLE_STATUS_OSM 1\n/*\n * Currently, the time slice range metadata is updated in\n * the timescaledb catalog with the min and max of the range managed by OSM.\n * However, this range has to be contiguous in order to\n * update our catalog with its min and max value. If it is not contiguous,\n * then we cannot store the min and max in our catalog because tuple routing\n * will not work properly with gaps in the range.\n * When attempting to insert into one of the gaps, which do not in fact contain\n * tiered data, we error out because this is perceived as an attempt to insert\n * into tiered chunks, which are immutable.\n * When the range is noncontiguous, we store [INT64_MAX - 1, INT64_MAX) and set\n * this flag.\n * This flag also serves to allow or block the ordered append optimization. When\n * the range covered by OSM is contiguous, then it is possible to do ordered\n * append.\n */\n#define HYPERTABLE_STATUS_OSM_CHUNK_NONCONTIGUOUS 2\n\nextern void ts_catalog_table_info_init(CatalogTableInfo *tables, int max_table,\n\t\t\t\t\t\t\t\t\t   const TableInfoDef *table_ary,\n\t\t\t\t\t\t\t\t\t   const TableIndexDef *index_ary, const char **serial_id_ary);\n\nextern TSDLLEXPORT CatalogDatabaseInfo *ts_catalog_database_info_get(void);\nextern TSDLLEXPORT Catalog *ts_catalog_get(void);\nextern void ts_catalog_reset(void);\nextern bool ts_is_catalog_table(Oid relid);\n\n/* Functions should operate on a passed-in Catalog struct */\nstatic inline Oid\ncatalog_get_table_id(Catalog *catalog, CatalogTable tableid)\n{\n\treturn catalog->tables[tableid].id;\n}\n\nstatic inline Oid\ncatalog_get_index(Catalog *catalog, CatalogTable tableid, int indexid)\n{\n\treturn (indexid == INVALID_INDEXID) ? InvalidOid : catalog->tables[tableid].index_ids[indexid];\n}\n\nextern TSDLLEXPORT int64 ts_catalog_table_next_seq_id(const Catalog *catalog, CatalogTable table);\nextern Oid ts_catalog_get_cache_proxy_id(Catalog *catalog, CacheType type);\n\n/* Functions that modify the actual catalog table on disk */\nextern TSDLLEXPORT bool ts_catalog_database_info_become_owner(CatalogDatabaseInfo *database_info,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  CatalogSecurityContext *sec_ctx);\nextern TSDLLEXPORT void ts_catalog_restore_user(CatalogSecurityContext *sec_ctx);\n\nextern TSDLLEXPORT void ts_catalog_insert_only(Relation rel, HeapTuple tuple);\nextern TSDLLEXPORT void ts_catalog_insert(Relation rel, HeapTuple tuple);\nextern TSDLLEXPORT void ts_catalog_insert_values(Relation rel, TupleDesc tupdesc, Datum *values,\n\t\t\t\t\t\t\t\t\t\t\t\t bool *nulls);\nextern TSDLLEXPORT void ts_catalog_insert_datums(Relation rel, TupleDesc tupdesc,\n\t\t\t\t\t\t\t\t\t\t\t\t NullableDatum *datums);\nextern TSDLLEXPORT void ts_catalog_update_tid_only(Relation rel, ItemPointer tid, HeapTuple tuple);\nextern TSDLLEXPORT void ts_catalog_update_tid(Relation rel, ItemPointer tid, HeapTuple tuple);\nextern TSDLLEXPORT void ts_catalog_update(Relation rel, HeapTuple tuple);\nextern TSDLLEXPORT void ts_catalog_delete_tid_only(Relation rel, ItemPointer tid);\nextern TSDLLEXPORT void ts_catalog_delete_tid(Relation rel, ItemPointer tid);\nextern TSDLLEXPORT void ts_catalog_invalidate_cache(Oid catalog_relid, CmdType operation);\nextern TSDLLEXPORT void ts_catalog_index_insert(ResultRelInfo *indstate, HeapTuple heapTuple);\n\nbool TSDLLEXPORT ts_catalog_scan_one(CatalogTable table, int indexid, ScanKeyData *scankey,\n\t\t\t\t\t\t\t\t\t int num_keys, tuple_found_func tuple_found, LOCKMODE lockmode,\n\t\t\t\t\t\t\t\t\t char *policy_type, void *data);\nvoid TSDLLEXPORT ts_catalog_scan_all(CatalogTable table, int indexid, ScanKeyData *scankey,\n\t\t\t\t\t\t\t\t\t int num_keys, tuple_found_func tuple_found, LOCKMODE lockmode,\n\t\t\t\t\t\t\t\t\t void *data);\n"
  },
  {
    "path": "src/ts_catalog/chunk_column_stats.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/attnum.h>\n#include <access/htup.h>\n#include <access/htup_details.h>\n#include <access/skey.h>\n#include <access/stratnum.h>\n#include <access/tupdesc.h>\n#include <catalog/pg_collation.h>\n#include <executor/spi.h>\n#include <executor/tuptable.h>\n#include <funcapi.h>\n#include <nodes/makefuncs.h>\n#include <optimizer/optimizer.h>\n#include <parser/parse_coerce.h>\n#include <parser/parse_collate.h>\n#include <parser/parse_expr.h>\n#include <parser/parse_relation.h>\n#include <rewrite/rewriteManip.h>\n#include <storage/lmgr.h>\n#include <storage/lockdefs.h>\n#include <utils/datum.h>\n#include <utils/syscache.h>\n\n#include \"chunk.h\"\n#include \"chunk_column_stats.h\"\n#include \"dimension_slice.h\"\n#include \"guc.h\"\n#include \"ts_catalog/catalog.h\"\n\n/*\n * Enable chunk column stats attributes\n */\nenum Anum_enable_chunk_column_stats\n{\n\tAnum_enable_chunk_column_stats_id = 1,\n\tAnum_enable_chunk_column_stats_enabled,\n\t_Anum_enable_chunk_column_stats_max,\n};\n\n#define Natts_enable_chunk_column_stats (_Anum_enable_chunk_column_stats_max - 1)\n\nTS_FUNCTION_INFO_V1(ts_chunk_column_stats_enable);\n\n/*\n * Disable chunk column stats attributes\n */\nenum Anum_disable_chunk_column_stats\n{\n\tAnum_disable_chunk_column_stats_hypertable_id = 1,\n\tAnum_disable_chunk_column_stats_column_name,\n\tAnum_disable_chunk_column_stats_disabled,\n\t_Anum_disable_chunk_column_stats_max,\n};\n\n#define Natts_disable_chunk_column_stats (_Anum_disable_chunk_column_stats_max - 1)\n\nTS_FUNCTION_INFO_V1(ts_chunk_column_stats_disable);\n\n/*\n * Create a datum to be returned by ts_chunk_column_stats_enable DDL function\n */\nstatic Datum\nchunk_column_stats_enable_datum(FunctionCallInfo fcinfo, int32 id, bool enabled)\n{\n\tTupleDesc tupdesc;\n\tHeapTuple tuple;\n\tDatum values[Natts_enable_chunk_column_stats];\n\tbool nulls[Natts_enable_chunk_column_stats] = { false };\n\n\tif (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"function returning record called in \"\n\t\t\t\t\t\t\"context that cannot accept type record\")));\n\n\ttupdesc = BlessTupleDesc(tupdesc);\n\n\tAssert(tupdesc->natts == Natts_enable_chunk_column_stats);\n\tvalues[AttrNumberGetAttrOffset(Anum_enable_chunk_column_stats_id)] = Int32GetDatum(id);\n\tvalues[AttrNumberGetAttrOffset(Anum_enable_chunk_column_stats_enabled)] = BoolGetDatum(enabled);\n\ttuple = heap_form_tuple(tupdesc, values, nulls);\n\n\treturn HeapTupleGetDatum(tuple);\n}\n\n/*\n * Create a datum to be returned by ts_chunk_column_stats_disable DDL function\n */\nstatic Datum\nchunk_column_stats_disable_datum(FunctionCallInfo fcinfo, int32 hypertable_id, Name colname,\n\t\t\t\t\t\t\t\t bool disabled)\n{\n\tTupleDesc tupdesc;\n\tHeapTuple tuple;\n\tDatum values[Natts_disable_chunk_column_stats];\n\tbool nulls[Natts_disable_chunk_column_stats] = { false };\n\n\tif (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"function returning record called in \"\n\t\t\t\t\t\t\"context that cannot accept type record\")));\n\n\ttupdesc = BlessTupleDesc(tupdesc);\n\n\tAssert(tupdesc->natts == Natts_disable_chunk_column_stats);\n\tvalues[AttrNumberGetAttrOffset(Anum_disable_chunk_column_stats_hypertable_id)] =\n\t\tInt32GetDatum(hypertable_id);\n\tvalues[AttrNumberGetAttrOffset(Anum_disable_chunk_column_stats_column_name)] =\n\t\tNameGetDatum(colname);\n\tvalues[AttrNumberGetAttrOffset(Anum_disable_chunk_column_stats_disabled)] =\n\t\tBoolGetDatum(disabled);\n\ttuple = heap_form_tuple(tupdesc, values, nulls);\n\n\treturn HeapTupleGetDatum(tuple);\n}\n\nstatic int32\nchunk_column_stats_insert_relation(const Relation rel, Form_chunk_column_stats info)\n{\n\tTupleDesc desc = RelationGetDescr(rel);\n\tDatum values[Natts_chunk_column_stats] = { 0 };\n\tbool nulls[Natts_chunk_column_stats] = { false };\n\tCatalogSecurityContext sec_ctx;\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tinfo->id = ts_catalog_table_next_seq_id(ts_catalog_get(), CHUNK_COLUMN_STATS);\n\tvalues[AttrNumberGetAttrOffset(Anum_chunk_column_stats_id)] = Int32GetDatum(info->id);\n\tvalues[AttrNumberGetAttrOffset(Anum_chunk_column_stats_hypertable_id)] =\n\t\tInt32GetDatum(info->hypertable_id);\n\tvalues[AttrNumberGetAttrOffset(Anum_chunk_column_stats_chunk_id)] =\n\t\tInt32GetDatum(info->chunk_id);\n\tvalues[AttrNumberGetAttrOffset(Anum_chunk_column_stats_column_name)] =\n\t\tNameGetDatum(&info->column_name);\n\tvalues[AttrNumberGetAttrOffset(Anum_chunk_column_stats_range_start)] =\n\t\tInt64GetDatum(info->range_start);\n\tvalues[AttrNumberGetAttrOffset(Anum_chunk_column_stats_range_end)] =\n\t\tInt64GetDatum(info->range_end);\n\tvalues[AttrNumberGetAttrOffset(Anum_chunk_column_stats_valid)] = BoolGetDatum(info->valid);\n\n\tif (info->chunk_id == INVALID_CHUNK_ID)\n\t\tnulls[AttrNumberGetAttrOffset(Anum_chunk_column_stats_chunk_id)] = true;\n\n\tts_catalog_insert_values(rel, desc, values, nulls);\n\tts_catalog_restore_user(&sec_ctx);\n\n\treturn info->id;\n}\n\nstatic int32\nchunk_column_stats_insert(Form_chunk_column_stats info)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tRelation rel;\n\tint32 ccol_stats_id;\n\n\trel = table_open(catalog_get_table_id(catalog, CHUNK_COLUMN_STATS), RowExclusiveLock);\n\tccol_stats_id = chunk_column_stats_insert_relation(rel, info);\n\ttable_close(rel, RowExclusiveLock);\n\treturn ccol_stats_id;\n}\n\nstatic ScanTupleResult\nchunk_column_stats_tuple_update(TupleInfo *ti, void *data)\n{\n\tbool should_free;\n\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\tFormData_chunk_column_stats *fd = (FormData_chunk_column_stats *) data;\n\n\tDatum values[Natts_chunk_column_stats] = { 0 };\n\tbool isnull[Natts_chunk_column_stats] = { 0 };\n\tbool doReplace[Natts_chunk_column_stats] = { 0 };\n\n\tvalues[AttrNumberGetAttrOffset(Anum_chunk_column_stats_range_start)] =\n\t\tInt64GetDatum(fd->range_start);\n\tdoReplace[AttrNumberGetAttrOffset(Anum_chunk_column_stats_range_start)] = true;\n\n\tvalues[AttrNumberGetAttrOffset(Anum_chunk_column_stats_range_end)] =\n\t\tInt64GetDatum(fd->range_end);\n\tdoReplace[AttrNumberGetAttrOffset(Anum_chunk_column_stats_range_end)] = true;\n\n\tvalues[AttrNumberGetAttrOffset(Anum_chunk_column_stats_valid)] = BoolGetDatum(fd->valid);\n\tdoReplace[AttrNumberGetAttrOffset(Anum_chunk_column_stats_valid)] = true;\n\n\tHeapTuple new_tuple =\n\t\theap_modify_tuple(tuple, ts_scanner_get_tupledesc(ti), values, isnull, doReplace);\n\n\tts_catalog_update(ti->scanrel, new_tuple);\n\n\theap_freetuple(new_tuple);\n\tif (should_free)\n\t\theap_freetuple(tuple);\n\n\treturn SCAN_DONE;\n}\n\nstatic int\nchunk_column_stats_scan_internal(ScanKeyData *scankey, int nkeys, tuple_found_func tuple_found,\n\t\t\t\t\t\t\t\t void *data, int limit, int dimension_index, LOCKMODE lockmode,\n\t\t\t\t\t\t\t\t MemoryContext mctx)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, CHUNK_COLUMN_STATS),\n\t\t.index = catalog_get_index(catalog, CHUNK_COLUMN_STATS, dimension_index),\n\t\t.nkeys = nkeys,\n\t\t.limit = limit,\n\t\t.scankey = scankey,\n\t\t.data = data,\n\t\t.tuple_found = tuple_found,\n\t\t.lockmode = lockmode,\n\t\t.scandirection = ForwardScanDirection,\n\t\t.result_mctx = mctx,\n\t};\n\n\treturn ts_scanner_scan(&scanctx);\n}\n\nint\nts_chunk_column_stats_update_by_id(int32 chunk_column_stats_id,\n\t\t\t\t\t\t\t\t   FormData_chunk_column_stats *fd_range)\n{\n\tScanKeyData scankey[1];\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_chunk_column_stats_id_idx_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(chunk_column_stats_id));\n\n\treturn chunk_column_stats_scan_internal(scankey,\n\t\t\t\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t\t\t\tchunk_column_stats_tuple_update,\n\t\t\t\t\t\t\t\t\t\t\tfd_range,\n\t\t\t\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t\t\t\tCHUNK_COLUMN_STATS_ID_IDX,\n\t\t\t\t\t\t\t\t\t\t\tRowExclusiveLock,\n\t\t\t\t\t\t\t\t\t\t\tCurrentMemoryContext);\n}\n\nstatic void\nts_chunk_column_stats_validate(Form_chunk_column_stats info, const Oid hypertable_relid,\n\t\t\t\t\t\t\t   bool if_not_exists)\n{\n\tHeapTuple tuple;\n\tDatum datum;\n\tbool isnull;\n\tOid column_type;\n\n\t/* Check that the column exists and has not been dropped */\n\ttuple = SearchSysCacheAttName(hypertable_relid, NameStr(info->column_name));\n\n\tif (!HeapTupleIsValid(tuple))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_COLUMN),\n\t\t\t\t errmsg(\"column \\\"%s\\\" does not exist\", NameStr(info->column_name))));\n\n\tdatum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_atttypid, &isnull);\n\tAssert(!isnull);\n\n\tcolumn_type = DatumGetObjectId(datum);\n\n\tReleaseSysCache(tuple);\n\n\t/* we only support a subset of data types for range calculations right now */\n\tswitch (column_type)\n\t{\n\t\tcase INT2OID:\n\t\tcase INT4OID:\n\t\tcase INT8OID:\n\t\tcase TIMESTAMPOID:\n\t\tcase TIMESTAMPTZOID:\n\t\tcase DATEOID:\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"data type \\\"%s\\\" unsupported for range calculation\",\n\t\t\t\t\t\t\tformat_type_be(column_type)),\n\t\t\t\t\t errhint(\"Integer-like, timestamp-like data types supported currently\")));\n\t}\n}\n\n/*\n * Track min/max range for a given column in a hypertable\n */\nstatic Datum\nts_chunk_column_stats_add_internal(FunctionCallInfo fcinfo, Oid table_relid, Name colname,\n\t\t\t\t\t\t\t\t   bool if_not_exists)\n{\n\tHypertable *ht;\n\tCache *hcache;\n\tDatum retval = 0;\n\tint32 ccol_stats_id = 0;\n\tFormData_chunk_column_stats fd = { 0 };\n\tForm_chunk_column_stats form;\n\tbool enabled = true;\n\n\tts_hypertable_permissions_check(table_relid, GetUserId());\n\tnamestrcpy(&fd.column_name, NameStr(*colname));\n\tLockRelationOid(table_relid, AccessShareLock);\n\n\tts_chunk_column_stats_validate(&fd, table_relid, if_not_exists);\n\n\tht = ts_hypertable_cache_get_cache_and_entry(table_relid, CACHE_FLAG_NONE, &hcache);\n\n\t/*\n\t * Add an entry in the _timescaledb_catalog.chunk_column_stats table. We add\n\t * a special entry in the catalog which contains the hypertable_id, the colname,\n\t * an invalid id (for the chunk) and PG_INT64_MAX, PG_INT64_MIN as range values\n\t * to indicate that ranges should be calculated for this column for chunks.\n\t *\n\t * We have a uniqueness check on ht_id, colname, chunk_id\n\t *\n\t * Check if the entry already exists, first.\n\t */\n\tform = ts_chunk_column_stats_lookup(ht->fd.id, INVALID_CHUNK_ID, NameStr(*colname));\n\tif (form != NULL)\n\t{\n\t\tif (!if_not_exists)\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_DUPLICATE_OBJECT),\n\t\t\t\t\t errmsg(\"already enabled for column \\\"%s\\\"\", NameStr(*colname))));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tereport(NOTICE,\n\t\t\t\t\t(errcode(ERRCODE_DUPLICATE_OBJECT),\n\t\t\t\t\t errmsg(\"already enabled for column \\\"%s\\\", skipping\", NameStr(*colname))));\n\t\t\t/* return the existing id */\n\t\t\tccol_stats_id = form->id;\n\t\t\t/* we still return true since it's already enabled */\n\t\t\tenabled = true;\n\t\t\tgoto do_return;\n\t\t}\n\t}\n\n\tfd.hypertable_id = ht->fd.id;\n\tfd.chunk_id = INVALID_CHUNK_ID;\n\tfd.range_start = PG_INT64_MIN;\n\tfd.range_end = PG_INT64_MAX;\n\tfd.valid = true;\n\tccol_stats_id = chunk_column_stats_insert(&fd);\n\n\t/* refresh the ht entry to accommodate this new chunk_column_stats entry */\n\tif (ht->range_space)\n\t\tpfree(ht->range_space);\n\tht->range_space = ts_chunk_column_stats_range_space_scan(ht->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t ht->main_table_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t ts_cache_memory_ctx(hcache));\n\n\t/*\n\t * If the hypertable has chunks, to make it compatible\n\t * we add artificial min/max range entries which will cover -inf / inf\n\t * range for all these existing chunks.\n\t *\n\t * TODO: Maybe have a future version which calculates actual ranges for\n\t * compressed chunks in this function itself? Or have an option to this\n\t * function which specifies if we should calculate ranges for compressed\n\t * chunks.\n\t */\n\tif (ts_hypertable_has_chunks(ht->main_table_relid, AccessShareLock))\n\t{\n\t\tListCell *lc;\n\t\tList *chunk_id_list = ts_chunk_get_chunk_ids_by_hypertable_id(ht->fd.id);\n\t\tCatalog *catalog = ts_catalog_get();\n\t\tRelation rel;\n\n\t\trel = table_open(catalog_get_table_id(catalog, CHUNK_COLUMN_STATS), RowExclusiveLock);\n\t\tforeach (lc, chunk_id_list)\n\t\t{\n\t\t\t/* other fields are set appropriately in fd above. Only change chunk_id */\n\t\t\tfd.chunk_id = lfirst_int(lc);\n\t\t\tchunk_column_stats_insert_relation(rel, &fd);\n\t\t}\n\n\t\ttable_close(rel, RowExclusiveLock);\n\t}\n\ndo_return:\n\t/* return the id of the main entry for this dimension range */\n\tfd.id = ccol_stats_id;\n\tretval = chunk_column_stats_enable_datum(fcinfo, fd.id, enabled);\n\tts_cache_release(&hcache);\n\n\tPG_RETURN_DATUM(retval);\n}\n\n/*\n * Add min/max range tracking for a column in a hypertable.\n *\n * Arguments:\n * 0. Relation ID of table\n * 1. Column name\n * 2. IF NOT EXISTS option (bool)\n */\nDatum\nts_chunk_column_stats_enable(PG_FUNCTION_ARGS)\n{\n\tOid hypertable_relid;\n\tNameData colname;\n\tbool if_not_exists;\n\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\tif (!ts_guc_enable_chunk_skipping)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"chunk skipping functionality disabled, \"\n\t\t\t\t\t\t\"enable it by first setting timescaledb.enable_chunk_skipping to on\")));\n\n\tif (PG_ARGISNULL(0))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"hypertable cannot be NULL\")));\n\thypertable_relid = PG_GETARG_OID(0);\n\n\tif (PG_ARGISNULL(1))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"column name cannot be NULL\")));\n\tnamestrcpy(&colname, NameStr(*PG_GETARG_NAME(1)));\n\n\tif_not_exists = PG_ARGISNULL(2) ? false : PG_GETARG_BOOL(2);\n\n\treturn ts_chunk_column_stats_add_internal(fcinfo, hypertable_relid, &colname, if_not_exists);\n}\n\n/*\n * Remove min/max range tracking for a column in a hypertable.\n *\n * Arguments:\n * 0. Relation ID of hypertable\n * 1. Column name\n * 2. IF NOT EXISTS option (bool)\n */\nDatum\nts_chunk_column_stats_disable(PG_FUNCTION_ARGS)\n{\n\tOid hypertable_relid;\n\tNameData colname;\n\tbool if_not_exists;\n\tHypertable *ht;\n\tCache *hcache;\n\tDatum retval = 0;\n\tint delete_count = 0;\n\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\tif (!ts_guc_enable_chunk_skipping)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"chunk skipping functionality disabled, \"\n\t\t\t\t\t\t\"enable it by first setting timescaledb.enable_chunk_skipping to on\")));\n\n\tif (PG_ARGISNULL(0))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"hypertable cannot be NULL\")));\n\thypertable_relid = PG_GETARG_OID(0);\n\n\tif (PG_ARGISNULL(1))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"column name cannot be NULL\")));\n\tnamestrcpy(&colname, NameStr(*PG_GETARG_NAME(1)));\n\n\tif_not_exists = PG_ARGISNULL(2) ? false : PG_GETARG_BOOL(2);\n\n\tts_hypertable_permissions_check(hypertable_relid, GetUserId());\n\tLockRelationOid(hypertable_relid, ShareUpdateExclusiveLock);\n\tht = ts_hypertable_cache_get_cache_and_entry(hypertable_relid, CACHE_FLAG_NONE, &hcache);\n\n\t/*\n\t * Remove entries from _timescaledb_catalog.chunk_column_stats table.\n\t *\n\t * There's a special entry in the catalog which contains the hypertable_id, the colname,\n\t * an invalid id (for the chunk) and PG_INT64_MAX, PG_INT64_MIN as range values\n\t * to indicate that ranges should be calculated for this column for chunks.\n\t *\n\t * Check if the entry already exists, first.\n\t */\n\tif (ts_chunk_column_stats_lookup(ht->fd.id, INVALID_CHUNK_ID, NameStr(colname)) == NULL)\n\t{\n\t\tif (!if_not_exists)\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_DUPLICATE_OBJECT),\n\t\t\t\t\t errmsg(\"statistics not enabled for column \\\"%s\\\"\", NameStr(colname))));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tereport(NOTICE,\n\t\t\t\t\t(errcode(ERRCODE_DUPLICATE_OBJECT),\n\t\t\t\t\t errmsg(\"statistics not enabled for column \\\"%s\\\", skipping\",\n\t\t\t\t\t\t\tNameStr(colname))));\n\t\t\tgoto do_return;\n\t\t}\n\t}\n\n\t/* Delete all entries matching this hypertable_id and column_name. */\n\tdelete_count = ts_chunk_column_stats_delete_by_ht_colname(ht->fd.id, NameStr(colname));\n\n\t/* refresh the ht entry to accommodate this deleted chunk_column_stats entry */\n\tif (ht->range_space)\n\t\tpfree(ht->range_space);\n\tht->range_space = ts_chunk_column_stats_range_space_scan(ht->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t ht->main_table_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t ts_cache_memory_ctx(hcache));\n\ndo_return:\n\tretval = chunk_column_stats_disable_datum(fcinfo, ht->fd.id, &colname, delete_count > 0);\n\tts_cache_release(&hcache);\n\n\tPG_RETURN_DATUM(retval);\n}\n\n/*\n * Dimension range entries are similar to OPEN DIMENSION entries. So, most of\n * the default fields are similar to them.\n */\nDimension *\nts_chunk_column_stats_fill_dummy_dimension(FormData_chunk_column_stats *r, Oid main_table_relid)\n{\n\tDimension *d = palloc0(sizeof(Dimension));\n\n\td->fd.id = r->id;\n\td->fd.hypertable_id = r->hypertable_id;\n\td->fd.aligned = true;\n\tnamestrcpy(&d->fd.column_name, NameStr(r->column_name));\n\td->fd.interval_length = 1; /* a dummy interval length for the dummy dimension */\n\n\t/* similar to open dimensions except that we don't participate in partitioning */\n\td->type = DIMENSION_TYPE_STATS;\n\td->column_attno = get_attnum(main_table_relid, NameStr(d->fd.column_name));\n\td->main_table_relid = main_table_relid;\n\n\t/* rest of the fields are zeroed out */\n\treturn d;\n}\n\n/*\n * Create a CHECK constraint for a min/max range chunk_column_stats entry\n */\nstatic Constraint *\ncreate_col_stats_check_constraint(const Form_chunk_column_stats info, Oid main_table_relid,\n\t\t\t\t\t\t\t\t  Oid chunk_relid, const char *name)\n{\n\tConstraint *constr = NULL;\n\tNode *rangedef;\n\tColumnRef *colref;\n\tList *compexprs = NIL;\n\tOid col_type;\n\tint attno;\n\n\tif (info->range_start == PG_INT64_MIN && info->range_end == PG_INT64_MAX)\n\t\treturn NULL;\n\n\tcolref = makeNode(ColumnRef);\n\tcolref->fields = list_make1(makeString(pstrdup(NameStr(info->column_name))));\n\tcolref->location = -1;\n\n\t/*\n\t * Get the column type for later converting the internal format\n\t * to string.\n\t *\n\t * Get the attribute number in the HT for this column, and map to the chunk\n\t */\n\tattno = get_attnum(main_table_relid, NameStr(info->column_name));\n\tattno = ts_map_attno(main_table_relid, chunk_relid, attno);\n\tcol_type = get_atttype(main_table_relid, attno);\n\n\trangedef = (Node *) colref;\n\n\t/* Elide range constraint for +INF or -INF */\n\tif (info->range_start != PG_INT64_MIN)\n\t{\n\t\tA_Const *start_const = makeNode(A_Const);\n\t\tmemcpy(&start_const->val,\n\t\t\t   makeString(ts_internal_to_time_string(info->range_start, col_type)),\n\t\t\t   sizeof(start_const->val));\n\t\tstart_const->location = -1;\n\t\tA_Expr *ge_expr = makeSimpleA_Expr(AEXPR_OP, \">=\", rangedef, (Node *) start_const, -1);\n\t\tcompexprs = lappend(compexprs, ge_expr);\n\t}\n\n\tif (info->range_end != PG_INT64_MAX)\n\t{\n\t\tA_Const *end_const = makeNode(A_Const);\n\t\tmemcpy(&end_const->val,\n\t\t\t   makeString(ts_internal_to_time_string(info->range_end, col_type)),\n\t\t\t   sizeof(end_const->val));\n\t\tend_const->location = -1;\n\t\tA_Expr *lt_expr = makeSimpleA_Expr(AEXPR_OP, \"<\", rangedef, (Node *) end_const, -1);\n\t\tcompexprs = lappend(compexprs, lt_expr);\n\t}\n\n\tconstr = makeNode(Constraint);\n\tconstr->contype = CONSTR_CHECK;\n\tconstr->conname = name ? pstrdup(name) : NULL;\n\tconstr->deferrable = false;\n\tconstr->skip_validation = true;\n\tconstr->initially_valid = true;\n\n\tAssert(list_length(compexprs) >= 1);\n\n\tif (list_length(compexprs) == 2)\n\t\tconstr->raw_expr = (Node *) makeBoolExpr(AND_EXPR, compexprs, -1);\n\telse if (list_length(compexprs) == 1)\n\t\tconstr->raw_expr = linitial(compexprs);\n\n\treturn constr;\n}\n\n/*\n * Fill in the form for chunk_column_stats.\n *\n * Note that it is necessary to deform the tuple since it is not possible to\n * use GETSTRUCT when chunk_id can be NULL.\n */\nstatic void\nfill_form_from_slot(TupleTableSlot *slot, Form_chunk_column_stats form)\n{\n\tbool should_free;\n\tHeapTuple tuple = ExecFetchSlotHeapTuple(slot, false, &should_free);\n\tDatum values[_Anum_chunk_column_stats_max];\n\tbool nulls[_Anum_chunk_column_stats_max];\n\n\theap_deform_tuple(tuple, slot->tts_tupleDescriptor, values, nulls);\n\n\tform->id = DatumGetInt32(values[AttrNumberGetAttrOffset(Anum_chunk_column_stats_id)]);\n\tform->hypertable_id =\n\t\tDatumGetInt32(values[AttrNumberGetAttrOffset(Anum_chunk_column_stats_hypertable_id)]);\n\n\tif (nulls[AttrNumberGetAttrOffset(Anum_chunk_column_stats_chunk_id)])\n\t\tform->chunk_id = INVALID_CHUNK_ID;\n\telse\n\t\tform->chunk_id =\n\t\t\tDatumGetInt32(values[AttrNumberGetAttrOffset(Anum_chunk_column_stats_chunk_id)]);\n\n\tnamestrcpy(&form->column_name,\n\t\t\t   NameStr(*DatumGetName(\n\t\t\t\t   values[AttrNumberGetAttrOffset(Anum_chunk_column_stats_column_name)])));\n\tform->range_end =\n\t\tDatumGetInt64(values[AttrNumberGetAttrOffset(Anum_chunk_column_stats_range_end)]);\n\tform->range_start =\n\t\tDatumGetInt64(values[AttrNumberGetAttrOffset(Anum_chunk_column_stats_range_start)]);\n\tform->valid = DatumGetBool(values[AttrNumberGetAttrOffset(Anum_chunk_column_stats_valid)]);\n\n\tif (should_free)\n\t\theap_freetuple(tuple);\n}\n\nstatic ScanTupleResult\nchunk_column_stats_tuple_found(TupleInfo *ti, void *data)\n{\n\tChunkRangeSpace *rs = data;\n\tForm_chunk_column_stats d = &rs->range_cols[rs->num_range_cols++];\n\n\tfill_form_from_slot(ti->slot, d);\n\n\treturn SCAN_CONTINUE;\n}\n\nChunkRangeSpace *\nts_chunk_column_stats_range_space_scan(int32 hypertable_id, Oid ht_reloid, MemoryContext mctx)\n{\n\t/* We won't have more entries than the number of columns in the HT */\n\tint num_range_cols = ts_get_relnatts(ht_reloid);\n\tChunkRangeSpace *range_space =\n\t\tMemoryContextAllocZero(mctx, CHUNKRANGESPACE_SIZE(num_range_cols));\n\tScanKeyData scankey[2];\n\n\trange_space->hypertable_id = hypertable_id;\n\trange_space->capacity = num_range_cols;\n\trange_space->num_range_cols = 0;\n\n\t/* Perform an index scan on hypertable_id, invalid chunk_id. */\n\tScanKeyInit(\n\t\t&scankey[0],\n\t\tAnum_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx_hypertable_id,\n\t\tBTEqualStrategyNumber,\n\t\tF_INT4EQ,\n\t\tInt32GetDatum(hypertable_id));\n\n\tScanKeyEntryInitialize(\n\t\t&scankey[1],\n\t\tSK_ISNULL | SK_SEARCHNULL,\n\t\tAnum_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx_chunk_id,\n\t\tBTEqualStrategyNumber,\n\t\tInvalidOid,\n\t\tInvalidOid,\n\t\tInvalidOid,\n\t\tInt32GetDatum(INVALID_CHUNK_ID));\n\n\tchunk_column_stats_scan_internal(scankey,\n\t\t\t\t\t\t\t\t\t 2,\n\t\t\t\t\t\t\t\t\t chunk_column_stats_tuple_found,\n\t\t\t\t\t\t\t\t\t range_space,\n\t\t\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t\t\t CHUNK_COLUMN_STATS_HT_ID_CHUNK_ID_COLUMN_NAME_IDX,\n\t\t\t\t\t\t\t\t\t AccessShareLock,\n\t\t\t\t\t\t\t\t\t mctx);\n\n\tif (range_space->num_range_cols == 0)\n\t{\n\t\tpfree(range_space);\n\t\treturn NULL;\n\t}\n\n\treturn range_space;\n}\n\nstatic ScanTupleResult\nform_range_tuple_found(TupleInfo *ti, void *data)\n{\n\tForm_chunk_column_stats rg = data;\n\tfill_form_from_slot(ti->slot, rg);\n\treturn SCAN_DONE;\n}\n\nForm_chunk_column_stats\nts_chunk_column_stats_lookup(int32 hypertable_id, int32 chunk_id, const char *col_name)\n{\n\tScanKeyData scankey[3];\n\tForm_chunk_column_stats form_range = palloc0(sizeof(FormData_chunk_column_stats));\n\tform_range->chunk_id = INVALID_CHUNK_ID; /* for clarity */\n\n\t/* Perform an index scan on hypertable_id, chunk_id, col_name. */\n\tScanKeyInit(\n\t\t&scankey[0],\n\t\tAnum_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx_hypertable_id,\n\t\tBTEqualStrategyNumber,\n\t\tF_INT4EQ,\n\t\tInt32GetDatum(hypertable_id));\n\n\tif (chunk_id == INVALID_CHUNK_ID)\n\t{\n\t\tScanKeyEntryInitialize(\n\t\t\t&scankey[1],\n\t\t\tSK_ISNULL | SK_SEARCHNULL,\n\t\t\tAnum_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx_chunk_id,\n\t\t\tBTEqualStrategyNumber,\n\t\t\tInvalidOid,\n\t\t\tInvalidOid,\n\t\t\tInvalidOid,\n\t\t\tInt32GetDatum(chunk_id));\n\t}\n\telse\n\t{\n\t\tScanKeyInit(\n\t\t\t&scankey[1],\n\t\t\tAnum_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx_chunk_id,\n\t\t\tBTEqualStrategyNumber,\n\t\t\tF_INT4EQ,\n\t\t\tInt32GetDatum(chunk_id));\n\t}\n\n\tScanKeyInit(\n\t\t&scankey[2],\n\t\tAnum_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx_column_name,\n\t\tBTEqualStrategyNumber,\n\t\tF_NAMEEQ,\n\t\tCStringGetDatum(col_name));\n\n\tchunk_column_stats_scan_internal(scankey,\n\t\t\t\t\t\t\t\t\t 3,\n\t\t\t\t\t\t\t\t\t form_range_tuple_found,\n\t\t\t\t\t\t\t\t\t form_range,\n\t\t\t\t\t\t\t\t\t 1,\n\t\t\t\t\t\t\t\t\t CHUNK_COLUMN_STATS_HT_ID_CHUNK_ID_COLUMN_NAME_IDX,\n\t\t\t\t\t\t\t\t\t AccessShareLock,\n\t\t\t\t\t\t\t\t\t CurrentMemoryContext);\n\n\tif (strlen(NameStr(form_range->column_name)) == 0)\n\t{\n\t\tpfree(form_range);\n\t\treturn NULL;\n\t}\n\n\treturn form_range;\n}\n\nstatic bool\nchunk_get_minmax(const Chunk *chunk, Oid col_type, const char *col_name, Datum *minmax)\n{\n\tStringInfoData command;\n\tint res;\n\n\t/* Lock down search_path */\n\tint save_nestlevel = NewGUCNestLevel();\n\tRestrictSearchPath();\n\n\tinitStringInfo(&command);\n\tappendStringInfo(&command,\n\t\t\t\t\t \"SELECT pg_catalog.min(%s), pg_catalog.max(%s) FROM %s.%s\",\n\t\t\t\t\t quote_identifier(col_name),\n\t\t\t\t\t quote_identifier(col_name),\n\t\t\t\t\t quote_identifier(NameStr(chunk->fd.schema_name)),\n\t\t\t\t\t quote_identifier(NameStr(chunk->fd.table_name)));\n\n\t/*\n\t * SPI_connect will switch MemoryContext so we need to keep track\n\t * of caller context as we need to copy the values into caller\n\t * context.\n\t */\n\tMemoryContext caller = CurrentMemoryContext;\n\n\tif (SPI_connect() != SPI_OK_CONNECT)\n\t\telog(ERROR, \"could not connect to SPI\");\n\n\tres = SPI_execute(command.data, true /* read_only */, 0 /*count*/);\n\tif (res < 0)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t (errmsg(\"could not get the min/max values for column \\\"%s\\\" of chunk \\\"%s.%s\\\"\",\n\t\t\t\t\t\t col_name,\n\t\t\t\t\t\t chunk->fd.schema_name.data,\n\t\t\t\t\t\t chunk->fd.table_name.data))));\n\n\tpfree(command.data);\n\n\tDatum min, max;\n\tbool isnull_min = false, isnull_max = false;\n\tmin = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull_min);\n\tmax = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 2, &isnull_max);\n\tAssert(SPI_gettypeid(SPI_tuptable->tupdesc, 1) == col_type);\n\tAssert(SPI_gettypeid(SPI_tuptable->tupdesc, 2) == col_type);\n\n\tbool found = !isnull_min && !isnull_max;\n\tif (found)\n\t{\n\t\tbool typbyval;\n\t\tint16 typlen;\n\t\tget_typlenbyval(col_type, &typlen, &typbyval);\n\n\t\t/* Copy the values into caller context */\n\t\tMemoryContext spi = MemoryContextSwitchTo(caller);\n\t\tminmax[0] = datumCopy(min, typbyval, typlen);\n\t\tminmax[1] = datumCopy(max, typbyval, typlen);\n\t\tMemoryContextSwitchTo(spi);\n\t}\n\n\t/* Restore search_path */\n\tAtEOXact_GUC(false, save_nestlevel);\n\n\tres = SPI_finish();\n\tif (res != SPI_OK_FINISH)\n\t\telog(ERROR, \"SPI_finish failed: %s\", SPI_result_code_string(res));\n\n\treturn found;\n}\n\n/*\n * Update column dimension ranges in the catalog for the\n * provided chunk (it's assumed that the chunk is locked\n * appropriately).\n *\n * Calculate actual ranges for the given chunk for the columns\n * insert these entries. This allows for the\n * chunk to be picked up when queries use these columns in\n * WHERE clauses with these ranges.\n *\n * Returns the number of column entries that have been added or\n * updated.\n */\nint\nts_chunk_column_stats_calculate(const Hypertable *ht, const Chunk *chunk)\n{\n\tSize i = 0;\n\tChunkRangeSpace *rs = ht->range_space;\n\tMemoryContext work_mcxt, orig_mcxt;\n\n\t/* Quick check. Bail out early if none */\n\tif (rs == NULL)\n\t\treturn i;\n\n\twork_mcxt =\n\t\tAllocSetContextCreate(CurrentMemoryContext, \"dimension-range-work\", ALLOCSET_DEFAULT_SIZES);\n\torig_mcxt = MemoryContextSwitchTo(work_mcxt);\n\n\tfor (int range_index = 0; range_index < rs->num_range_cols; range_index++)\n\t{\n\t\tDatum minmax[2];\n\t\tAttrNumber attno;\n\t\tchar *col_name = NameStr(rs->range_cols[range_index].column_name);\n\t\tOid col_type;\n\n\t\tattno = get_attnum(ht->main_table_relid, col_name);\n\t\tattno = ts_map_attno(ht->main_table_relid, chunk->table_id, attno);\n\t\tcol_type = get_atttype(chunk->table_id, attno);\n\n\t\t/* calculate the min/max range for this column on this chunk */\n\t\tif (chunk_get_minmax(chunk, col_type, col_name, minmax))\n\t\t{\n\t\t\tForm_chunk_column_stats range;\n\t\t\tint64 min = ts_time_value_to_internal(minmax[0], col_type);\n\t\t\tint64 max = ts_time_value_to_internal(minmax[1], col_type);\n\n\t\t\t/* The end value is exclusive to the range, so incr by 1 */\n\t\t\tif (max != DIMENSION_SLICE_MAXVALUE)\n\t\t\t{\n\t\t\t\tmax++;\n\t\t\t\t/* Again, check overflow */\n\t\t\t\tmax = REMAP_LAST_COORDINATE(max);\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * Check if an entry exists for this ht, chunk_id, colname combo. If it exists\n\t\t\t * and it's not -inf/+inf then it's probably a case of re-computation of the\n\t\t\t * ranges. In such a case, we compare the stored range_start and range_end entries\n\t\t\t * and compare with the min/max calculated.\n\t\t\t *\n\t\t\t * if min < range_start, then new_range_start = min\n\t\t\t * if max > range_end, then new_range_end = max\n\t\t\t *\n\t\t\t * We need to update the existing entry with changes in the range.\n\t\t\t * Also, in case of updates, the entry might be marked \"invalid\" so it needs to be\n\t\t\t * made \"valid\" again as well.\n\t\t\t */\n\t\t\trange = ts_chunk_column_stats_lookup(ht->fd.id, chunk->fd.id, col_name);\n\n\t\t\t/* Add a new entry if none exists */\n\t\t\tif (range == NULL)\n\t\t\t{\n\t\t\t\tFormData_chunk_column_stats fd = { 0 };\n\t\t\t\tfd.hypertable_id = ht->fd.id;\n\t\t\t\tfd.chunk_id = chunk->fd.id;\n\t\t\t\tnamestrcpy(&fd.column_name, col_name);\n\t\t\t\tfd.range_start = min;\n\t\t\t\tfd.range_end = max;\n\t\t\t\tfd.valid = true;\n\n\t\t\t\tchunk_column_stats_insert(&fd);\n\t\t\t\ti++;\n\t\t\t}\n\t\t\t/* update case */\n\t\t\telse if (range->range_start != min || range->range_end != max || !range->valid)\n\t\t\t{\n\t\t\t\trange->range_start = min;\n\t\t\t\trange->range_end = max;\n\t\t\t\trange->valid = true;\n\t\t\t\tts_chunk_column_stats_update_by_id(range->id, range);\n\t\t\t\ti++;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t\tereport(WARNING, errmsg(\"unable to calculate min/max values for column ranges\"));\n\t}\n\n\tMemoryContextSwitchTo(orig_mcxt);\n\tMemoryContextDelete(work_mcxt);\n\n\treturn i;\n}\n\n/*\n * Insert column dimension ranges in the catalog for the\n * provided chunk (it's assumed that the chunk is locked\n * appropriately).\n *\n * We insert -inf/+inf entries for the given chunk which means\n * default selection till the actual ranges get calculated later.\n *\n * Returns the number of column entries that have been inserted.\n */\nint\nts_chunk_column_stats_insert(const Hypertable *ht, const Chunk *chunk)\n{\n\tSize range_index = 0;\n\tChunkRangeSpace *rs = ht->range_space;\n\tMemoryContext work_mcxt, orig_mcxt;\n\n\t/* Quick check. Bail out early if none */\n\tif (rs == NULL)\n\t\treturn range_index;\n\n\twork_mcxt =\n\t\tAllocSetContextCreate(CurrentMemoryContext, \"dimension-range-work\", ALLOCSET_DEFAULT_SIZES);\n\torig_mcxt = MemoryContextSwitchTo(work_mcxt);\n\n\tfor (range_index = 0; range_index < rs->num_range_cols; range_index++)\n\t{\n\t\tAttrNumber attno;\n\t\tchar *col_name = NameStr(rs->range_cols[range_index].column_name);\n\t\tFormData_chunk_column_stats fd = { 0 };\n\n\t\t/* Get the attribute number in the HT for this column, and map to the chunk */\n\t\tattno = get_attnum(ht->main_table_relid, col_name);\n\t\tattno = ts_map_attno(ht->main_table_relid, chunk->table_id, attno);\n\n\t\t/* insert an entry for this ht_id, chunk_id for this col_name with -inf/+inf range */\n\t\tfd.hypertable_id = ht->fd.id;\n\t\tfd.chunk_id = chunk->fd.id;\n\t\tnamestrcpy(&fd.column_name, col_name);\n\t\tfd.range_start = PG_INT64_MIN;\n\t\tfd.range_end = PG_INT64_MAX;\n\t\tfd.valid = true;\n\n\t\tchunk_column_stats_insert(&fd);\n\t}\n\n\tMemoryContextSwitchTo(orig_mcxt);\n\tMemoryContextDelete(work_mcxt);\n\n\treturn range_index;\n}\n\n/*\n * Check if there is a min/max range tracking on this column which is being dropped.\n * Need to delete the entries from _timescaledb_catalog.chunk_column_stats table in that case.\n */\nvoid\nts_chunk_column_stats_drop(const Hypertable *ht, const char *col_name, bool *dropped)\n{\n\t/* delete all entries belonging to this HT and pointing to this column */\n\t*dropped = (ts_chunk_column_stats_delete_by_ht_colname(ht->fd.id, col_name) > 0);\n}\n\nstatic ScanTupleResult\nchunk_column_stats_tuple_delete(TupleInfo *ti, void *data)\n{\n\tCatalogSecurityContext sec_ctx;\n\tint *count = data;\n\n\t/* delete catalog entry */\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_delete_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\tts_catalog_restore_user(&sec_ctx);\n\t*count = *count + 1;\n\n\treturn SCAN_CONTINUE;\n}\n\nint\nts_chunk_column_stats_delete_by_ht_colname(int32 hypertable_id, const char *col_name)\n{\n\tScanKeyData scankey[2];\n\tint count = 0;\n\n\t/* Perform an index scan on hypertable_id, col_name. */\n\tScanKeyInit(\n\t\t&scankey[0],\n\t\tAnum_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx_hypertable_id,\n\t\tBTEqualStrategyNumber,\n\t\tF_INT4EQ,\n\t\tInt32GetDatum(hypertable_id));\n\tScanKeyInit(\n\t\t&scankey[1],\n\t\tAnum_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx_column_name,\n\t\tBTEqualStrategyNumber,\n\t\tF_NAMEEQ,\n\t\tCStringGetDatum((col_name)));\n\n\tchunk_column_stats_scan_internal(scankey,\n\t\t\t\t\t\t\t\t\t 2,\n\t\t\t\t\t\t\t\t\t chunk_column_stats_tuple_delete,\n\t\t\t\t\t\t\t\t\t &count,\n\t\t\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t\t\t CHUNK_COLUMN_STATS_HT_ID_CHUNK_ID_COLUMN_NAME_IDX,\n\t\t\t\t\t\t\t\t\t RowExclusiveLock,\n\t\t\t\t\t\t\t\t\t CurrentMemoryContext);\n\n\treturn count;\n}\n\nint\nts_chunk_column_stats_delete_by_chunk_id(int32 chunk_id)\n{\n\tScanKeyData scankey[1];\n\tint count = 0;\n\n\tAssert(chunk_id != INVALID_CHUNK_ID);\n\n\t/* Perform an index scan on chunk_id. */\n\tScanKeyInit(\n\t\t&scankey[0],\n\t\tAnum_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx_chunk_id,\n\t\tBTEqualStrategyNumber,\n\t\tF_INT4EQ,\n\t\tInt32GetDatum(chunk_id));\n\n\tchunk_column_stats_scan_internal(scankey,\n\t\t\t\t\t\t\t\t\t 1,\n\t\t\t\t\t\t\t\t\t chunk_column_stats_tuple_delete,\n\t\t\t\t\t\t\t\t\t &count,\n\t\t\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t\t\t CHUNK_COLUMN_STATS_HT_ID_CHUNK_ID_COLUMN_NAME_IDX,\n\t\t\t\t\t\t\t\t\t RowExclusiveLock,\n\t\t\t\t\t\t\t\t\t CurrentMemoryContext);\n\n\treturn count;\n}\n\nint\nts_chunk_column_stats_reset_by_chunk_id(int32 chunk_id)\n{\n\tScanKeyData scankey[1];\n\tFormData_chunk_column_stats fd = { 0 };\n\n\t/* reset the range to min and max for all entries belonging to this chunk */\n\tfd.range_start = PG_INT64_MIN;\n\tfd.range_end = PG_INT64_MAX;\n\tfd.valid = true;\n\n\tAssert(chunk_id != INVALID_CHUNK_ID);\n\n\t/* Perform an index scan on chunk_id. */\n\tScanKeyInit(\n\t\t&scankey[0],\n\t\tAnum_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx_chunk_id,\n\t\tBTEqualStrategyNumber,\n\t\tF_INT4EQ,\n\t\tInt32GetDatum(chunk_id));\n\n\treturn chunk_column_stats_scan_internal(scankey,\n\t\t\t\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t\t\t\tchunk_column_stats_tuple_update,\n\t\t\t\t\t\t\t\t\t\t\t&fd,\n\t\t\t\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t\t\t\tCHUNK_COLUMN_STATS_HT_ID_CHUNK_ID_COLUMN_NAME_IDX,\n\t\t\t\t\t\t\t\t\t\t\tRowExclusiveLock,\n\t\t\t\t\t\t\t\t\t\t\tCurrentMemoryContext);\n}\n\nint\nts_chunk_column_stats_delete_by_hypertable_id(int32 hypertable_id)\n{\n\tScanKeyData scankey[1];\n\tint count = 0;\n\n\t/* Perform an index scan on hypertable_id. */\n\tScanKeyInit(\n\t\t&scankey[0],\n\t\tAnum_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx_hypertable_id,\n\t\tBTEqualStrategyNumber,\n\t\tF_INT4EQ,\n\t\tInt32GetDatum(hypertable_id));\n\n\tchunk_column_stats_scan_internal(scankey,\n\t\t\t\t\t\t\t\t\t 1,\n\t\t\t\t\t\t\t\t\t chunk_column_stats_tuple_delete,\n\t\t\t\t\t\t\t\t\t &count,\n\t\t\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t\t\t CHUNK_COLUMN_STATS_HT_ID_CHUNK_ID_COLUMN_NAME_IDX,\n\t\t\t\t\t\t\t\t\t RowExclusiveLock,\n\t\t\t\t\t\t\t\t\t CurrentMemoryContext);\n\n\treturn count;\n}\n\n/*\n * For min/max ranges we are interested in the occurrence of a value which\n * possibly lies in multiple entries from _timescaledb_catalog.chunk_column_stats.\n *\n * The check for enclosure needs to be run as a FILTER on top of all the matching\n * entries for the hypertable, column combo. So, we only use ht_id, col_name for\n * the scan below.\n */\nstatic int\nchunk_column_stats_scan_iterator_set(ScanIterator *it, int32 hypertable_id, const char *col_name)\n{\n\tCatalog *catalog = ts_catalog_get();\n\n\tit->ctx.index = catalog_get_index(catalog,\n\t\t\t\t\t\t\t\t\t  CHUNK_COLUMN_STATS,\n\t\t\t\t\t\t\t\t\t  CHUNK_COLUMN_STATS_HT_ID_CHUNK_ID_COLUMN_NAME_IDX);\n\tts_scan_iterator_scan_key_reset(it);\n\tts_scan_iterator_scan_key_init(\n\t\tit,\n\t\tAnum_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx_hypertable_id,\n\t\tBTEqualStrategyNumber,\n\t\tF_INT4EQ,\n\t\tInt32GetDatum(hypertable_id));\n\n\tts_scan_iterator_scan_key_init(\n\t\tit,\n\t\tAnum_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx_column_name,\n\t\tBTEqualStrategyNumber,\n\t\tF_NAMEEQ,\n\t\tCStringGetDatum((col_name)));\n\n\tit->ctx.scandirection = ForwardScanDirection;\n\n\treturn it->ctx.nkeys;\n}\n\n/*\n * We need to get all chunks matching the hypertable ID and the column name.\n * For each chunk obtained, we need to run a FILTER using the strategies\n * and the lower/upper bound values provided.\n *\n * The EXPLAIN plan is basically like below:\n *\n * Index Scan using chunk_column_stats_ht_id_chunk_id_colname_range_start_end_key\n *\t\t\t\t\t\t\t\t\t\t\t\ton _timescaledb_catalog.chunk_column_stats\n * Output: chunk_id\n * Index Cond: ((chunk_column_stats.hypertable_id = :ht_id) AND\n *\t\t\t\t\t\t\t\t(chunk_column_stats.column_name = ':colname'))\n * Filter: ((chunk_column_stats.range_end BTREE_OP lower_bound/upper_bound) OR\n *\t\t\t\t\t(chunk_column_stats.range_start BTREE_OP lower_bound/upper_bound))\n *\n * The strategies and lower_bound/upper_bound values get assigned in\n * dimension_restrict_info_range_add function.\n *\n * We need to run the \"Filter\" above ourselves because there's no other PG mechanism for OR\n * types of checks like these.\n */\nList *\nts_chunk_column_stats_get_chunk_ids_by_scan(DimensionRestrictInfo *dri)\n{\n\tScanIterator it;\n\tList *chunkids = NIL;\n\tDimensionRestrictInfoOpen *open;\n\n\tAssert(dri && dri->dimension->type == DIMENSION_TYPE_STATS);\n\n\t/* setup the scanner */\n\tit = ts_scan_iterator_create(CHUNK_COLUMN_STATS, AccessShareLock, CurrentMemoryContext);\n\tit.ctx.flags |= SCANNER_F_NOEND_AND_NOCLOSE;\n\tit.ctx.tuplock = NULL;\n\n\topen = (DimensionRestrictInfoOpen *) dri;\n\n\t/*\n\t * We need to get all chunks matching the hypertable ID and the column name.\n\t */\n\tchunk_column_stats_scan_iterator_set(&it,\n\t\t\t\t\t\t\t\t\t\t dri->dimension->fd.hypertable_id,\n\t\t\t\t\t\t\t\t\t\t NameStr(dri->dimension->fd.column_name));\n\t/*\n\t * For each chunk obtained, we need to run a FILTER using the strategies\n\t * and the lower/upper bound values provided.\n\t */\n\tts_scan_iterator_start_or_restart_scan(&it);\n\tts_scanner_foreach(&it)\n\t{\n\t\tFormData_chunk_column_stats fd;\n\t\tbool matched = false;\n\t\tbool chunk_id_isnull;\n\n\t\tchunk_id_isnull = slot_attisnull(\n\t\t\tit.tinfo->slot,\n\t\t\tAnum_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx_chunk_id);\n\t\t/*\n\t\t * We have an entry with INVALID_CHUNK_ID which will match all cases due to\n\t\t * -INF/+INF range entries for it. Ignore that.\n\t\t */\n\t\tif (chunk_id_isnull)\n\t\t\tgoto done;\n\n\t\tfill_form_from_slot(it.tinfo->slot, &fd);\n\n\t\t/*\n\t\t * If an entry is marked \"invalid\" then it means that the ranges cannot be relied\n\t\t * on. So, we assume the worse case and include this chunk for the scan.\n\t\t *\n\t\t * (Entry is typically marked \"invalid\" when a compressed chunk becomes partial\n\t\t * due to DML in it.)\n\t\t *\n\t\t * Also, if we have a valid chunnk with -inf/+inf entries then it matches all\n\t\t * queries\n\t\t */\n\t\tif (!fd.valid || (fd.range_start == PG_INT64_MIN && fd.range_end == PG_INT64_MAX))\n\t\t{\n\t\t\tmatched = true;\n\t\t\tgoto done;\n\t\t}\n\n\t\t/*\n\t\t * All data is in int8 format so we do regular comparisons. Also, it's an OR\n\t\t * check so prepare to short circuit if one evaluates to true.\n\t\t *\n\t\t * No real way to know if checking range_start or range_end first will be more\n\t\t * effective. So let's start with range_end checks first.\n\t\t */\n\t\tswitch (open->upper_strategy)\n\t\t{\n\t\t\tcase BTLessEqualStrategyNumber: /* e.g: id <= 90 */\n\t\t\t{\n\t\t\t\tmatched = fd.range_start <= open->upper_bound;\n\t\t\t}\n\t\t\tbreak;\n\t\t\tcase BTLessStrategyNumber: /* e.g: id < 90 */\n\t\t\t{\n\t\t\t\tmatched = fd.range_start < open->upper_bound;\n\t\t\t}\n\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\topen->upper_strategy = InvalidStrategy;\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif (open->upper_strategy != InvalidStrategy && !matched)\n\t\t\tgoto done;\n\n\t\t/* range_end checks didn't match, check for range_start now */\n\t\tswitch (open->lower_strategy)\n\t\t{\n\t\t\tcase BTGreaterEqualStrategyNumber:\n\t\t\t{\n\t\t\t\t/* range_end is exclusive */\n\t\t\t\tmatched = (fd.range_end - 1) >= open->lower_bound;\n\t\t\t}\n\t\t\tbreak;\n\t\t\tcase BTGreaterStrategyNumber:\n\t\t\t{\n\t\t\t\t/* range_end is exclusive */\n\t\t\t\tmatched = (fd.range_end - 1) > open->lower_bound;\n\t\t\t}\n\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\t/* unsupported strategy */\n\t\t\t\tbreak;\n\t\t}\n\n\tdone:\n\t\tif (matched)\n\t\t\tchunkids = lappend_int(chunkids, fd.chunk_id);\n\t}\n\tts_scan_iterator_close(&it);\n\n\treturn chunkids;\n}\n\n/*\n * Update all entries for this ht_id, old_colname to point to the new_colname\n */\nint\nts_chunk_column_stats_set_name(FormData_chunk_column_stats *in_fd, char *new_colname)\n{\n\tScanIterator it;\n\tNameData new_column_name;\n\tint count = 0;\n\n\tnamestrcpy(&new_column_name, new_colname);\n\t/* setup the scanner */\n\tit = ts_scan_iterator_create(CHUNK_COLUMN_STATS, AccessShareLock, CurrentMemoryContext);\n\tit.ctx.flags |= SCANNER_F_NOEND_AND_NOCLOSE;\n\tit.ctx.tuplock = NULL;\n\n\t/*\n\t * We need to get all chunks matching the hypertable ID and the column name.\n\t */\n\tchunk_column_stats_scan_iterator_set(&it, in_fd->hypertable_id, NameStr(in_fd->column_name));\n\t/*\n\t * For each entry obtained, we need to update the column_name to point to the\n\t * new_colname\n\t */\n\tts_scan_iterator_start_or_restart_scan(&it);\n\tts_scanner_foreach(&it)\n\t{\n\t\tDatum values[Natts_chunk_column_stats] = { 0 };\n\t\tbool isnull[Natts_chunk_column_stats] = { false };\n\t\tbool doReplace[Natts_chunk_column_stats] = { 0 };\n\t\tbool should_free;\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&it);\n\n\t\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\n\t\tvalues[AttrNumberGetAttrOffset(Anum_chunk_column_stats_column_name)] =\n\t\t\tNameGetDatum(&new_column_name);\n\t\tdoReplace[AttrNumberGetAttrOffset(Anum_chunk_column_stats_column_name)] = true;\n\n\t\tHeapTuple new_tuple =\n\t\t\theap_modify_tuple(tuple, ts_scanner_get_tupledesc(ti), values, isnull, doReplace);\n\n\t\tts_catalog_update(ti->scanrel, new_tuple);\n\n\t\theap_freetuple(new_tuple);\n\t\tif (should_free)\n\t\t\theap_freetuple(tuple);\n\n\t\tcount++;\n\t}\n\n\tts_scan_iterator_close(&it);\n\treturn count;\n}\n\nstatic ScanTupleResult\ninvalidate_range_tuple_found(TupleInfo *ti, void *data)\n{\n\tbool should_free;\n\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\tbool valid = false;\n\n\tDatum values[Natts_chunk_column_stats] = { 0 };\n\tbool isnull[Natts_chunk_column_stats] = { 0 };\n\tbool doReplace[Natts_chunk_column_stats] = { 0 };\n\n\tvalues[AttrNumberGetAttrOffset(Anum_chunk_column_stats_valid)] = BoolGetDatum(valid);\n\tdoReplace[AttrNumberGetAttrOffset(Anum_chunk_column_stats_valid)] = true;\n\n\tHeapTuple new_tuple =\n\t\theap_modify_tuple(tuple, ts_scanner_get_tupledesc(ti), values, isnull, doReplace);\n\n\tts_catalog_update(ti->scanrel, new_tuple);\n\n\theap_freetuple(new_tuple);\n\tif (should_free)\n\t\theap_freetuple(tuple);\n\n\treturn SCAN_CONTINUE;\n}\n\n/*\n * Mark all entries for a given chunk_id as \"invalid\"\n */\nvoid\nts_chunk_column_stats_set_invalid(int32 hypertable_id, int32 chunk_id)\n{\n\tScanKeyData scankey[2];\n\n\tAssert(chunk_id != INVALID_CHUNK_ID);\n\n\t/* Perform an index scan on hypertable_id, chunk_id. */\n\tScanKeyInit(\n\t\t&scankey[0],\n\t\tAnum_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx_hypertable_id,\n\t\tBTEqualStrategyNumber,\n\t\tF_INT4EQ,\n\t\tInt32GetDatum(hypertable_id));\n\tScanKeyInit(\n\t\t&scankey[1],\n\t\tAnum_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx_chunk_id,\n\t\tBTEqualStrategyNumber,\n\t\tF_INT4EQ,\n\t\tInt32GetDatum(chunk_id));\n\n\tchunk_column_stats_scan_internal(scankey,\n\t\t\t\t\t\t\t\t\t 2,\n\t\t\t\t\t\t\t\t\t invalidate_range_tuple_found,\n\t\t\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t\t\t CHUNK_COLUMN_STATS_HT_ID_CHUNK_ID_COLUMN_NAME_IDX,\n\t\t\t\t\t\t\t\t\t RowExclusiveLock,\n\t\t\t\t\t\t\t\t\t CurrentMemoryContext);\n}\n\ntypedef struct CheckList\n{\n\tOid chunk_relid;\n\tOid main_table_relid;\n\tList *cclist;\n} CheckList;\n\nstatic ScanTupleResult\nconstruct_check_constraint_range_tuple(TupleInfo *ti, void *data)\n{\n\tbool should_free;\n\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\tFormData_chunk_column_stats fd;\n\tConstraint *constr;\n\tCheckList *checklist = data;\n\n\tfill_form_from_slot(ti->slot, &fd);\n\n\tconstr = create_col_stats_check_constraint(&fd,\n\t\t\t\t\t\t\t\t\t\t\t   checklist->main_table_relid,\n\t\t\t\t\t\t\t\t\t\t\t   checklist->chunk_relid,\n\t\t\t\t\t\t\t\t\t\t\t   NULL);\n\n\tif (constr)\n\t\tchecklist->cclist = lappend(checklist->cclist, constr);\n\n\tif (should_free)\n\t\theap_freetuple(tuple);\n\n\treturn SCAN_CONTINUE;\n}\n/*\n * Given an input relationObjectId, check that it's a chunk and if yes, check that it\n * has min/max ranges on it and return a list of constructed check constraint\n * entries for each such entry.\n */\nList *\nts_chunk_column_stats_construct_check_constraints(Relation relation, Oid reloid, Index varno)\n{\n\tFormData_chunk fd;\n\tCheckList clist = { 0 };\n\tListCell *lc;\n\tScanKeyData scankey[2];\n\tParseState *pstate = NULL;\n\tList *result = NIL;\n\n\t/* check if it's not a chunk and return early in that case */\n\tif (!ts_chunk_simple_scan_by_reloid(reloid, &fd, true))\n\t\treturn NIL;\n\n\tclist.chunk_relid = reloid;\n\tclist.main_table_relid = ts_hypertable_id_to_relid(fd.hypertable_id, false);\n\n\tAssert(fd.id != INVALID_CHUNK_ID);\n\n\t/* Perform an index scan on hypertable_id, chunk_id. */\n\tScanKeyInit(\n\t\t&scankey[0],\n\t\tAnum_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx_hypertable_id,\n\t\tBTEqualStrategyNumber,\n\t\tF_INT4EQ,\n\t\tInt32GetDatum(fd.hypertable_id));\n\tScanKeyInit(\n\t\t&scankey[1],\n\t\tAnum_chunk_column_stats_ht_id_chunk_id_column_name_range_start_range_end_idx_chunk_id,\n\t\tBTEqualStrategyNumber,\n\t\tF_INT4EQ,\n\t\tInt32GetDatum(fd.id));\n\n\tchunk_column_stats_scan_internal(scankey,\n\t\t\t\t\t\t\t\t\t 2,\n\t\t\t\t\t\t\t\t\t construct_check_constraint_range_tuple,\n\t\t\t\t\t\t\t\t\t &clist,\n\t\t\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t\t\t CHUNK_COLUMN_STATS_HT_ID_CHUNK_ID_COLUMN_NAME_IDX,\n\t\t\t\t\t\t\t\t\t RowExclusiveLock,\n\t\t\t\t\t\t\t\t\t CurrentMemoryContext);\n\n\tif (clist.cclist)\n\t{\n\t\tpstate = make_parsestate(NULL);\n\t\t/* The overall query should be holding an appropriate lock already on this relation */\n\t\tParseNamespaceItem *nsitem =\n\t\t\taddRangeTableEntryForRelation(pstate, relation, AccessShareLock, NULL, false, false);\n\t\taddNSItemToQuery(pstate, nsitem, true, true, true);\n\t}\n\n\tforeach (lc, clist.cclist)\n\t{\n\t\tNode *expr;\n\t\tConstraint *constr = lfirst(lc);\n\n\t\t/* Transform raw parsetree to executable expression. */\n\t\texpr = transformExpr(pstate, constr->raw_expr, EXPR_KIND_CHECK_CONSTRAINT);\n\n\t\t/* Make sure it yields a boolean result. */\n\t\texpr = coerce_to_boolean(pstate, expr, \"CHECK\");\n\n\t\t/* Take care of collations. */\n\t\tassign_expr_collations(pstate, expr);\n\n\t\texpr = eval_const_expressions(NULL, expr);\n\t\texpr = (Node *) canonicalize_qual((Expr *) expr, true);\n\n\t\t/* Fix Vars to have the desired varno */\n\t\tif (varno != 1)\n\t\t\tChangeVarNodes(expr, 1, varno, 0);\n\n\t\t/*\n\t\t * Finally, convert to implicit-AND format (that is, a List) and\n\t\t * append the resulting item(s) to our output list.\n\t\t */\n\t\tresult = list_concat(result, make_ands_implicit((Expr *) expr));\n\t}\n\n\treturn result;\n}\n"
  },
  {
    "path": "src/ts_catalog/chunk_column_stats.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include \"chunk.h\"\n#include \"export.h\"\n#include \"hypertable_restrict_info.h\"\n\n/*\n * A rangespace tracks all columns that need min/max value ranges calculation\n * for chunks from a given hypertable\n */\ntypedef struct ChunkRangeSpace\n{\n\tint32 hypertable_id;\n\tuint16 capacity;\n\tuint16 num_range_cols;\n\tFormData_chunk_column_stats range_cols[FLEXIBLE_ARRAY_MEMBER];\n} ChunkRangeSpace;\n\n#define CHUNKRANGESPACE_SIZE(num_columns)                                                          \\\n\t(sizeof(ChunkRangeSpace) + (sizeof(NameData) * (num_columns)))\n\nextern ChunkRangeSpace *ts_chunk_column_stats_range_space_scan(int32 hypertable_id, Oid ht_reloid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   MemoryContext mctx);\n\nextern int ts_chunk_column_stats_update_by_id(int32 chunk_column_stats_id,\n\t\t\t\t\t\t\t\t\t\t\t  FormData_chunk_column_stats *fd_range);\n\nextern Form_chunk_column_stats ts_chunk_column_stats_lookup(int32 hypertable_id, int32 chunk_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst char *col_name);\n\nextern TSDLLEXPORT int ts_chunk_column_stats_calculate(const Hypertable *ht, const Chunk *chunk);\nextern int ts_chunk_column_stats_insert(const Hypertable *ht, const Chunk *chunk);\n\nextern void ts_chunk_column_stats_drop(const Hypertable *ht, const char *col_name, bool *dropped);\nextern int ts_chunk_column_stats_delete_by_ht_colname(int32 hypertable_id, const char *col_name);\nextern TSDLLEXPORT int ts_chunk_column_stats_delete_by_chunk_id(int32 chunk_id);\nextern TSDLLEXPORT int ts_chunk_column_stats_reset_by_chunk_id(int32 chunk_id);\nextern int ts_chunk_column_stats_delete_by_hypertable_id(int32 hypertable_id);\nextern Dimension *ts_chunk_column_stats_fill_dummy_dimension(FormData_chunk_column_stats *r,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t Oid main_table_relid);\nextern List *ts_chunk_column_stats_get_chunk_ids_by_scan(DimensionRestrictInfo *dri);\nextern void ts_chunk_column_stats_set_invalid(int32 hypertable_id, int32 chunk_id);\nextern int ts_chunk_column_stats_set_name(FormData_chunk_column_stats *in_fd, char *new_colname);\nextern List *ts_chunk_column_stats_construct_check_constraints(Relation relation, Oid reloid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   Index varno);\n"
  },
  {
    "path": "src/ts_catalog/chunk_rewrite.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/attnum.h>\n#include <access/htup.h>\n#include <access/htup_details.h>\n#include <access/stratnum.h>\n#include <access/tableam.h>\n#include <catalog/dependency.h>\n#include <catalog/objectaddress.h>\n#include <catalog/pg_class_d.h>\n#include <executor/tuptable.h>\n#include <miscadmin.h>\n#include <nodes/lockoptions.h>\n#include <nodes/parsenodes.h>\n#include <storage/itemptr.h>\n#include <storage/lmgr.h>\n#include <storage/lockdefs.h>\n#include <utils/acl.h>\n#include <utils/lsyscache.h>\n#include <utils/syscache.h>\n\n#include \"chunk_rewrite.h\"\n#include \"scan_iterator.h\"\n#include \"ts_catalog/catalog.h\"\n\n/*\n * chunk_rewrite:\n *\n * This catalog table tracks pending rewrite operations for chunks. It is used when merging chunks\n * in concurrent mode to track temporarily written relations/heaps that might orphaned in case\n * of a failed rewrite. For example, a multi-transactional chunk merge could fail in the second\n * transaction and leave behind orphaned rewritten heaps it created in the first transaction.\n *\n * Each entry in the catalog is a mapping from a current relation to its new heap. For merges, this\n * is a many-to-one relation for each merge.\n *\n * Future operations, like concurrent split, might also use this catalog table.\n */\n\nstatic HeapTuple\nchunk_rewrite_make_tuple(Oid chunk_relid, Oid new_relid, TupleDesc desc)\n{\n\tDatum values[Natts_chunk_rewrite];\n\tbool nulls[Natts_chunk_rewrite] = { false };\n\n\tmemset(values, 0, sizeof(Datum) * Natts_chunk_rewrite);\n\n\tvalues[AttrNumberGetAttrOffset(Anum_chunk_rewrite_chunk_relid)] = ObjectIdGetDatum(chunk_relid);\n\tvalues[AttrNumberGetAttrOffset(Anum_chunk_rewrite_new_relid)] = ObjectIdGetDatum(new_relid);\n\n\treturn heap_form_tuple(desc, values, nulls);\n}\n\n/*\n * Add an entry to the chunk_rewrite table.\n */\nvoid\nts_chunk_rewrite_add(Oid chunk_relid, Oid new_relid)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tOid cat_relid = catalog_get_table_id(catalog, CHUNK_REWRITE);\n\tHeapTuple new_tuple;\n\tCatalogSecurityContext sec_ctx;\n\tRelation catrel;\n\n\tcatrel = table_open(cat_relid, RowExclusiveLock);\n\tnew_tuple = chunk_rewrite_make_tuple(chunk_relid, new_relid, catrel->rd_att);\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_insert_only(catrel, new_tuple);\n\tts_catalog_restore_user(&sec_ctx);\n\theap_freetuple(new_tuple);\n\ttable_close(catrel, NoLock);\n}\n\n/*\n * Look up an entry based on the original chunk_id. The entry is locked FOR UPDATE.\n */\nbool\nts_chunk_rewrite_get_with_lock(Oid chunk_relid, Form_chunk_rewrite form, ItemPointer tid)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tScanIterator it;\n\tScanTupLock tuplock = {\n\t\t.waitpolicy = LockWaitBlock,\n\t\t.lockmode = LockTupleExclusive,\n\t};\n\tbool found = false;\n\n\tit = ts_scan_iterator_create(CHUNK_REWRITE, RowShareLock, CurrentMemoryContext);\n\tit.ctx.tuplock = &tuplock;\n\tit.ctx.flags = SCANNER_F_KEEPLOCK;\n\tit.ctx.index = catalog_get_index(catalog, CHUNK_REWRITE, CHUNK_REWRITE_IDX);\n\tts_scan_iterator_scan_key_init(&it,\n\t\t\t\t\t\t\t\t   Anum_chunk_rewrite_key_chunk_relid,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_OIDEQ,\n\t\t\t\t\t\t\t\t   ObjectIdGetDatum(chunk_relid));\n\n\tts_scanner_foreach(&it)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&it);\n\n\t\tswitch (ti->lockresult)\n\t\t{\n\t\t\tcase TM_Ok:\n\t\t\t\tfound = true;\n\n\t\t\t\tif (tid)\n\t\t\t\t\tItemPointerCopy(&ti->slot->tts_tid, tid);\n\n\t\t\t\tif (form)\n\t\t\t\t{\n\t\t\t\t\tbool should_free;\n\t\t\t\t\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\t\t\t\t\tmemcpy(form, GETSTRUCT(tuple), sizeof(FormData_chunk_rewrite));\n\n\t\t\t\t\tif (should_free)\n\t\t\t\t\t\theap_freetuple(tuple);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase TM_Deleted:\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t\t\t errmsg(\"chunk merge state deleted by concurrent transaction\")));\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t\t\t errmsg(\"unable to lock chunk rewrite catalog tuple, lock result is %d for \"\n\t\t\t\t\t\t\t\t\"chunk (%u)\",\n\t\t\t\t\t\t\t\tti->lockresult,\n\t\t\t\t\t\t\t\tchunk_relid)));\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tts_scan_iterator_close(&it);\n\n\treturn found;\n}\n\n/*\n * Delete an entry from the chunk_rewrite table based on TID.\n */\nvoid\nts_chunk_rewrite_delete_by_tid(const ItemPointer tid)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tOid cat_relid = catalog_get_table_id(catalog, CHUNK_REWRITE);\n\tCatalogSecurityContext sec_ctx;\n\tRelation catrel;\n\n\tcatrel = table_open(cat_relid, RowExclusiveLock);\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_delete_tid_only(catrel, tid);\n\tts_catalog_restore_user(&sec_ctx);\n\ttable_close(catrel, NoLock);\n}\n\n/*\n * Delete an entry from the chunk_rewrite table and drop the orphaned heap (if it exists).\n *\n * The delete result indicates whether both the entry and the orphaned heap was dropped,\n * or if only the entry was deleted (in case the heap was already dropped), or a failure occurred.\n *\n * If the \"conditional\" parameter is specified, the entry will only be deleted if the referenced\n * heap relation can be immediately locked without waiting. This is useful in order to skip entries\n * that are locked by ongoing merges.\n */\nChunkRewriteDeleteResult\nts_chunk_rewrite_delete(Oid chunk_relid, bool conditional)\n{\n\tItemPointerData tid;\n\tFormData_chunk_rewrite form;\n\tChunkRewriteDeleteResult result;\n\n\tif (!ts_chunk_rewrite_get_with_lock(chunk_relid, &form, &tid))\n\t\treturn ChunkRewriteEntryDoesNotExist;\n\n\tif (conditional)\n\t{\n\t\tif (!ConditionalLockRelationOid(form.new_relid, AccessExclusiveLock))\n\t\t\treturn ChunkRewriteOngoing;\n\t}\n\n\t/*\n\t * Check if the new heap still exists by trying to get a lock.\n\t */\n\tRelation newrel = try_table_open(form.new_relid, AccessExclusiveLock);\n\n\tif (newrel)\n\t{\n\t\tObjectAddress tableaddr;\n\t\t/* New heap still exists, so delete it */\n\t\ttable_close(newrel, NoLock);\n\t\tObjectAddressSet(tableaddr, RelationRelationId, form.new_relid);\n\t\tperformDeletion(&tableaddr, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);\n\t\tresult = ChunkRewriteEntryDeletedAndTableDropped;\n\t}\n\telse\n\t{\n\t\tresult = ChunkRewriteEntryDeleted;\n\t}\n\n\tts_chunk_rewrite_delete_by_tid(&tid);\n\n\treturn result;\n}\n\nTS_FUNCTION_INFO_V1(ts_chunk_rewrite_cleanup);\n\n/*\n * Clean up failed chunk rewrites.\n *\n * This function cleans up all non-ongoing chunk rewrites listed in the chunk_rewrite catalog,\n * including any \"orphaned\" heaps referenced in the table.\n *\n */\nDatum\nts_chunk_rewrite_cleanup(PG_FUNCTION_ARGS)\n{\n\tScanIterator it;\n\tObjectAddresses *objaddrs = new_object_addresses();\n\tunsigned int cleanup_count = 0;\n\tunsigned int skipped_count = 0;\n\tOid userid = GetUserId();\n\tCatalogSecurityContext sec_ctx;\n\tScanTupLock tuplock = {\n\t\t.lockmode = LockTupleExclusive,\n\t\t.waitpolicy = LockWaitSkip,\n\t\t.lockflags = TUPLE_LOCK_FLAG_FIND_LAST_VERSION,\n\t};\n\n\tit = ts_scan_iterator_create(CHUNK_REWRITE, RowShareLock, CurrentMemoryContext);\n\tit.ctx.tuplock = &tuplock;\n\n\tts_scanner_foreach(&it)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&it);\n\t\tbool isnull = false;\n\t\tbool entry_cleaned = false;\n\n\t\tDatum chunk_relid_dat = slot_getattr(ti->slot, Anum_chunk_rewrite_chunk_relid, &isnull);\n\t\tAssert(!isnull);\n\t\tDatum new_relid_dat = slot_getattr(ti->slot, Anum_chunk_rewrite_new_relid, &isnull);\n\t\tAssert(!isnull);\n\n\t\tif (ti->lockresult == TM_Ok)\n\t\t{\n\t\t\tOid chunk_relid = DatumGetObjectId(chunk_relid_dat);\n\t\t\tOid new_relid = DatumGetObjectId(new_relid_dat);\n\n\t\t\t/*\n\t\t\t * A concurrent merge might be in progress, so try to lock the \"new\"\n\t\t\t * relation and only delete it if the lock can be acquired\n\t\t\t * immediately. If a lock cannot be acquired, a merge is probably\n\t\t\t * ongoing and it might still complete successfully.\n\t\t\t */\n\t\t\tif (ConditionalLockRelationOid(new_relid, AccessExclusiveLock))\n\t\t\t{\n\t\t\t\tObjectAddress new_objaddr = {\n\t\t\t\t\t.objectId = DatumGetObjectId(new_relid),\n\t\t\t\t\t.classId = RelationRelationId,\n\t\t\t\t};\n\t\t\t\t/*\n\t\t\t\t * Check that the merge relation still exists and get its owner.\n\t\t\t\t */\n\t\t\t\tHeapTuple tuple;\n\t\t\t\tOid ownerid = InvalidOid;\n\n\t\t\t\ttuple = SearchSysCache1(RELOID, ObjectIdGetDatum(chunk_relid));\n\n\t\t\t\tif (HeapTupleIsValid(tuple))\n\t\t\t\t\townerid = ((Form_pg_class) GETSTRUCT(tuple))->relowner;\n\n\t\t\t\tReleaseSysCache(tuple);\n\n\t\t\t\t/*\n\t\t\t\t * Only clean an entry if the user has the privileges of the owner of the relation.\n\t\t\t\t * Also clean up if the relation doesn't exist anymore (relowner is InvalidOid).\n\t\t\t\t */\n\t\t\t\tif (!OidIsValid(ownerid) || has_privs_of_role(userid, ownerid))\n\t\t\t\t{\n\t\t\t\t\t/*\n\t\t\t\t\t * Check that the relation still exists. If it does, add to delete objects.\n\t\t\t\t\t * Otherwise release lock.\n\t\t\t\t\t */\n\t\t\t\t\tif (SearchSysCacheExists1(RELOID, ObjectIdGetDatum(new_relid)))\n\t\t\t\t\t\tadd_exact_object_address(&new_objaddr, objaddrs);\n\t\t\t\t\telse\n\t\t\t\t\t\tUnlockRelationOid(new_relid, AccessExclusiveLock);\n\n\t\t\t\t\tItemPointer tid = ts_scanner_get_tuple_tid(ti);\n\n\t\t\t\t\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\t\t\t\t\tts_catalog_delete_tid_only(it.ctx.tablerel, tid);\n\t\t\t\t\tts_catalog_restore_user(&sec_ctx);\n\t\t\t\t\tentry_cleaned = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (entry_cleaned)\n\t\t{\n\t\t\tcleanup_count++;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tOid chunk_relid = DatumGetObjectId(chunk_relid_dat);\n\t\t\telog(DEBUG1, \"chunk merge in progress for \\\"%s\\\", skipping\", get_rel_name(chunk_relid));\n\t\t\tskipped_count++;\n\t\t}\n\t}\n\n\tts_scan_iterator_close(&it);\n\tperformMultipleDeletions(objaddrs, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);\n\n\telog(NOTICE,\n\t\t \"cleaned up %u orphaned rewrite relations, skipped %u\",\n\t\t cleanup_count,\n\t\t skipped_count);\n\n\tPG_RETURN_VOID();\n}\n"
  },
  {
    "path": "src/ts_catalog/chunk_rewrite.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include \"ts_catalog/catalog.h\"\n\nextern TSDLLEXPORT void ts_chunk_rewrite_add(Oid chunk_relid, Oid new_relid);\nextern TSDLLEXPORT bool ts_chunk_rewrite_get_with_lock(Oid chunk_relid, Form_chunk_rewrite form,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   ItemPointer tid);\nextern TSDLLEXPORT void ts_chunk_rewrite_delete_by_tid(const ItemPointer tid);\n\ntypedef enum ChunkRewriteDeleteResult\n{\n\tChunkRewriteOngoing,\n\tChunkRewriteEntryDeleted,\n\tChunkRewriteEntryDeletedAndTableDropped,\n\tChunkRewriteEntryDoesNotExist,\n} ChunkRewriteDeleteResult;\n\nextern TSDLLEXPORT ChunkRewriteDeleteResult ts_chunk_rewrite_delete(Oid chunk_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbool conditional);\n"
  },
  {
    "path": "src/ts_catalog/compression_chunk_size.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/htup_details.h>\n#include <executor/tuptable.h>\n\n#include \"export.h\"\n#include \"scan_iterator.h\"\n#include \"scanner.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"ts_catalog/compression_chunk_size.h\"\n\nstatic void\ninit_scan_by_uncompressed_chunk_id(ScanIterator *iterator, int32 uncompressed_chunk_id)\n{\n\titerator->ctx.index =\n\t\tcatalog_get_index(ts_catalog_get(), COMPRESSION_CHUNK_SIZE, COMPRESSION_CHUNK_SIZE_PKEY);\n\tts_scan_iterator_scan_key_init(iterator,\n\t\t\t\t\t\t\t\t   Anum_compression_chunk_size_pkey_chunk_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(uncompressed_chunk_id));\n}\n\nTSDLLEXPORT int\nts_compression_chunk_size_delete(int32 uncompressed_chunk_id)\n{\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(COMPRESSION_CHUNK_SIZE, RowExclusiveLock, CurrentMemoryContext);\n\tint count = 0;\n\n\tinit_scan_by_uncompressed_chunk_id(&iterator, uncompressed_chunk_id);\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tts_catalog_delete_tid_only(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\t\tcount++;\n\t}\n\n\t/* Make catalog changes visible */\n\tif (count > 0)\n\t\tCommandCounterIncrement();\n\n\treturn count;\n}\n\nTSDLLEXPORT bool\nts_compression_chunk_size_get(int32 chunk_id, Form_compression_chunk_size form)\n{\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(COMPRESSION_CHUNK_SIZE, AccessExclusiveLock, CurrentMemoryContext);\n\tbool found = false;\n\n\tAssert(form != NULL);\n\n\tinit_scan_by_uncompressed_chunk_id(&iterator, chunk_id);\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tbool should_free;\n\t\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\t\tmemcpy(form, GETSTRUCT(tuple), sizeof(*form));\n\t\tfound = true;\n\t\tAssert(form->chunk_id == chunk_id);\n\n\t\tif (should_free)\n\t\t\theap_freetuple(tuple);\n\n\t\tbreak;\n\t}\n\n\tts_scan_iterator_close(&iterator);\n\n\treturn found;\n}\n\nTSDLLEXPORT bool\nts_compression_chunk_size_update(int32 chunk_id, Form_compression_chunk_size form)\n{\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(COMPRESSION_CHUNK_SIZE, RowExclusiveLock, CurrentMemoryContext);\n\tbool found = false;\n\tCatalogSecurityContext sec_ctx;\n\n\tAssert(form != NULL);\n\n\tinit_scan_by_uncompressed_chunk_id(&iterator, chunk_id);\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tbool should_free;\n\t\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\t\tHeapTuple copy = heap_copytuple(tuple);\n\t\tForm_compression_chunk_size tupform = (Form_compression_chunk_size) GETSTRUCT(copy);\n\n\t\t/* Don't update chunk IDs so copy from existing tuple */\n\t\tform->chunk_id = tupform->chunk_id;\n\t\tform->compressed_chunk_id = tupform->compressed_chunk_id;\n\n\t\tmemcpy(tupform, form, sizeof(FormData_compression_chunk_size));\n\t\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\t\tts_catalog_update_tid_only(ti->scanrel, ts_scanner_get_tuple_tid(ti), copy);\n\t\tts_catalog_restore_user(&sec_ctx);\n\t\tfound = true;\n\n\t\theap_freetuple(copy);\n\n\t\tif (should_free)\n\t\t\theap_freetuple(tuple);\n\n\t\tbreak;\n\t}\n\n\tts_scan_iterator_close(&iterator);\n\n\treturn found;\n}\n"
  },
  {
    "path": "src/ts_catalog/compression_chunk_size.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <compat/compat.h>\n#include <postgres.h>\n\n#include <ts_catalog/catalog.h>\n\nextern TSDLLEXPORT int ts_compression_chunk_size_delete(int32 uncompressed_chunk_id);\nextern TSDLLEXPORT bool ts_compression_chunk_size_get(int32 chunk_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  Form_compression_chunk_size form);\nextern TSDLLEXPORT bool ts_compression_chunk_size_update(int32 chunk_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t Form_compression_chunk_size form);\n"
  },
  {
    "path": "src/ts_catalog/compression_settings.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <catalog/pg_inherits.h>\n#include <parser/parse_func.h>\n#include <utils/builtins.h>\n\n#include \"foreach_ptr.h\"\n#include \"jsonb_utils.h\"\n#include \"scan_iterator.h\"\n#include \"scanner.h\"\n#include \"ts_catalog/array_utils.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"ts_catalog/compression_settings.h\"\n#include <common/md5.h>\n#include <utils/palloc.h>\n\nTSDLLEXPORT const char *ts_sparse_index_type_names[] = { \"bloom\", \"minmax\" };\nTSDLLEXPORT const char *ts_sparse_index_source_names[] = { \"config\", \"default\", \"orderby\" };\nTSDLLEXPORT const char *ts_sparse_index_common_keys[] = { \"type\", \"column\", \"source\", NULL };\nstatic ScanTupleResult compression_settings_tuple_update(TupleInfo *ti, void *data);\nstatic HeapTuple compression_settings_formdata_make_tuple(const FormData_compression_settings *fd,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  TupleDesc desc);\nstatic Bitmapset *resolve_columns_to_attnos(List *column_names, Oid relid);\n\n/*\n * Compare two compression settings for equality\n */\nbool\nts_compression_settings_equal(const CompressionSettings *left, const CompressionSettings *right)\n{\n\treturn ts_array_equal(left->fd.segmentby, right->fd.segmentby) &&\n\t\t   ts_array_equal(left->fd.orderby, right->fd.orderby) &&\n\t\t   ts_array_equal(left->fd.orderby_desc, right->fd.orderby_desc) &&\n\t\t   ts_array_equal(left->fd.orderby_nullsfirst, right->fd.orderby_nullsfirst) &&\n\t\t   ts_jsonb_equal(left->fd.index, right->fd.index);\n}\n\n/*\n * Compare two compression settings for equality while ignoring default values.\n *\n * This essentially means that any NULL\n * values should be considered a match because they represent default\n * values which are determined at chunk level.\n *\n * This also means first argument needs to be the hypertable because chunks\n * cannot have implicit defaults.\n */\nbool\nts_compression_settings_equal_with_defaults(const CompressionSettings *ht,\n\t\t\t\t\t\t\t\t\t\t\tconst CompressionSettings *chunk)\n{\n\tAssert(!OidIsValid(ht->fd.compress_relid));\n\treturn (ht->fd.segmentby == NULL || ts_array_equal(ht->fd.segmentby, chunk->fd.segmentby)) &&\n\t\t   (ht->fd.orderby == NULL || ts_array_equal(ht->fd.orderby, chunk->fd.orderby)) &&\n\t\t   (ht->fd.orderby_desc == NULL ||\n\t\t\tts_array_equal(ht->fd.orderby_desc, chunk->fd.orderby_desc)) &&\n\t\t   (ht->fd.orderby_nullsfirst == NULL ||\n\t\t\tts_array_equal(ht->fd.orderby_nullsfirst, chunk->fd.orderby_nullsfirst)) &&\n\t\t   (ht->fd.index == NULL || ts_jsonb_equal(ht->fd.index, chunk->fd.index));\n}\n\nCompressionSettings *\nts_compression_settings_materialize(const CompressionSettings *src, Oid relid, Oid compress_relid)\n{\n\tCompressionSettings *dst = ts_compression_settings_create(relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  compress_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  src->fd.segmentby,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  src->fd.orderby,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  src->fd.orderby_desc,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  src->fd.orderby_nullsfirst,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  src->fd.index);\n\n\treturn dst;\n}\n\nCompressionSettings *\nts_compression_settings_create(Oid relid, Oid compress_relid, ArrayType *segmentby,\n\t\t\t\t\t\t\t   ArrayType *orderby, ArrayType *orderby_desc,\n\t\t\t\t\t\t\t   ArrayType *orderby_nullsfirst, Jsonb *sparse_index)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tCatalogSecurityContext sec_ctx;\n\tRelation rel;\n\tFormData_compression_settings fd;\n\n\tAssert(OidIsValid(relid));\n\n\t/*\n\t * The default compression settings will always have orderby settings but the user may have\n\t * chosen to overwrite it. For both cases all 3 orderby arrays must either have the same number\n\t * of entries or be all NULL.\n\t */\n\tAssert((orderby && orderby_desc && orderby_nullsfirst) ||\n\t\t   (!orderby && !orderby_desc && !orderby_nullsfirst));\n\n\tfd.relid = relid;\n\tfd.compress_relid = compress_relid;\n\tfd.segmentby = segmentby;\n\tfd.orderby = orderby;\n\tfd.orderby_desc = orderby_desc;\n\tfd.orderby_nullsfirst = orderby_nullsfirst;\n\tfd.index = sparse_index;\n\n\trel = table_open(catalog_get_table_id(catalog, COMPRESSION_SETTINGS), RowExclusiveLock);\n\n\tHeapTuple new_tuple = compression_settings_formdata_make_tuple(&fd, RelationGetDescr(rel));\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_insert(rel, new_tuple);\n\tts_catalog_restore_user(&sec_ctx);\n\theap_freetuple(new_tuple);\n\n\ttable_close(rel, RowExclusiveLock);\n\n\treturn ts_compression_settings_get(relid);\n}\n\nstatic void\ncompression_settings_fill_from_tuple(CompressionSettings *settings, TupleInfo *ti)\n{\n\tFormData_compression_settings *fd = &settings->fd;\n\tDatum values[Natts_compression_settings];\n\tbool nulls[Natts_compression_settings];\n\tbool should_free;\n\n\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\n\theap_deform_tuple(tuple, ts_scanner_get_tupledesc(ti), values, nulls);\n\n\tMemoryContext old = MemoryContextSwitchTo(ti->mctx);\n\n\tfd->relid = DatumGetObjectId(values[AttrNumberGetAttrOffset(Anum_compression_settings_relid)]);\n\n\tif (nulls[AttrNumberGetAttrOffset(Anum_compression_settings_compress_relid)])\n\t\tfd->compress_relid = InvalidOid;\n\telse\n\t\tfd->compress_relid = DatumGetObjectId(\n\t\t\tvalues[AttrNumberGetAttrOffset(Anum_compression_settings_compress_relid)]);\n\n\tif (nulls[AttrNumberGetAttrOffset(Anum_compression_settings_segmentby)])\n\t\tfd->segmentby = NULL;\n\telse\n\t\tfd->segmentby = DatumGetArrayTypePCopy(\n\t\t\tvalues[AttrNumberGetAttrOffset(Anum_compression_settings_segmentby)]);\n\n\tif (nulls[AttrNumberGetAttrOffset(Anum_compression_settings_orderby)])\n\t\tfd->orderby = NULL;\n\telse\n\t\tfd->orderby = DatumGetArrayTypePCopy(\n\t\t\tvalues[AttrNumberGetAttrOffset(Anum_compression_settings_orderby)]);\n\n\tif (nulls[AttrNumberGetAttrOffset(Anum_compression_settings_orderby_desc)])\n\t\tfd->orderby_desc = NULL;\n\telse\n\t\tfd->orderby_desc = DatumGetArrayTypePCopy(\n\t\t\tvalues[AttrNumberGetAttrOffset(Anum_compression_settings_orderby_desc)]);\n\n\tif (nulls[AttrNumberGetAttrOffset(Anum_compression_settings_orderby_nullsfirst)])\n\t\tfd->orderby_nullsfirst = NULL;\n\telse\n\t\tfd->orderby_nullsfirst = DatumGetArrayTypePCopy(\n\t\t\tvalues[AttrNumberGetAttrOffset(Anum_compression_settings_orderby_nullsfirst)]);\n\n\tif (nulls[AttrNumberGetAttrOffset(Anum_compression_settings_index)])\n\t\tfd->index = NULL;\n\telse\n\t\tfd->index =\n\t\t\tDatumGetJsonbPCopy(values[AttrNumberGetAttrOffset(Anum_compression_settings_index)]);\n\n\tMemoryContextSwitchTo(old);\n\n\tif (should_free)\n\t\theap_freetuple(tuple);\n}\n\nstatic void\ncompression_settings_iterator_init(ScanIterator *iterator, Oid relid, bool by_compress_relid)\n{\n\tint indexid =\n\t\tby_compress_relid ? COMPRESSION_SETTINGS_COMPRESS_RELID_IDX : COMPRESSION_SETTINGS_PKEY;\n\titerator->ctx.index = catalog_get_index(ts_catalog_get(), COMPRESSION_SETTINGS, indexid);\n\tts_scan_iterator_scan_key_init(iterator,\n\t\t\t\t\t\t\t\t   by_compress_relid ?\n\t\t\t\t\t\t\t\t\t   Anum_compression_settings_compress_relid_idx_relid :\n\t\t\t\t\t\t\t\t\t   Anum_compression_settings_pkey_relid,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_OIDEQ,\n\t\t\t\t\t\t\t\t   ObjectIdGetDatum(relid));\n}\n\n/*\n * Get compression settings for a relation.\n *\n * When 'by_compress_relid' is false, the 'relid' refers to the \"main\"\n * relation being compressed. When it is true the 'relid' refers to the\n * relation containing the associated compressed data.\n */\nstatic CompressionSettings *\ncompression_settings_get(Oid relid, bool by_compress_relid)\n{\n\tCompressionSettings *settings = NULL;\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(COMPRESSION_SETTINGS, AccessShareLock, CurrentMemoryContext);\n\tcompression_settings_iterator_init(&iterator, relid, by_compress_relid);\n\n\tts_scanner_start_scan(&iterator.ctx);\n\tTupleInfo *ti = ts_scanner_next(&iterator.ctx);\n\tif (!ti)\n\t\treturn NULL;\n\n\tsettings = palloc0(sizeof(CompressionSettings));\n\tcompression_settings_fill_from_tuple(settings, ti);\n\tts_scan_iterator_close(&iterator);\n\treturn settings;\n}\n\n/*\n * Get the compression settings for the relation referred to by 'relid'.\n */\nTSDLLEXPORT CompressionSettings *\nts_compression_settings_get(Oid relid)\n{\n\treturn compression_settings_get(relid, false);\n}\n\n/*\n * Get the compression settings for a relation given its associated compressed\n * relation.\n *\n * Ideally, settings should only be looked up by \"primary key\", i.e., the\n * non-compressed chunk's 'relid', and in that case this function wouldn't be\n * needed. It might be possible to remove this function in the future.\n */\nTSDLLEXPORT CompressionSettings *\nts_compression_settings_get_by_compress_relid(Oid relid)\n{\n\tCompressionSettings *settings = compression_settings_get(relid, true);\n\tEnsure(settings, \"compression settings not found for %s\", get_rel_name(relid));\n\treturn settings;\n}\n\n/*\n * Delete compression settings for a relation.\n *\n * When 'by_compress_relid' is false, the 'relid' refers to the \"main\"\n * relation being compressed. When it is true the 'relid' refers to the\n * relation containing the associated compressed data.\n */\nstatic bool\ncompression_settings_delete(Oid relid, bool by_compress_relid)\n{\n\tif (!OidIsValid(relid))\n\t\treturn false;\n\n\tint count = 0;\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(COMPRESSION_SETTINGS, RowExclusiveLock, CurrentMemoryContext);\n\tcompression_settings_iterator_init(&iterator, relid, by_compress_relid);\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tts_catalog_delete_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\t\tcount++;\n\t}\n\treturn count > 0;\n}\n\n/*\n * Delete entries matching the non-compressed relation.\n */\nTSDLLEXPORT bool\nts_compression_settings_delete(Oid relid)\n{\n\treturn compression_settings_delete(relid, false);\n}\n\n/*\n * Delete entries matching a compressed relation.\n */\nTSDLLEXPORT bool\nts_compression_settings_delete_by_compress_relid(Oid relid)\n{\n\treturn compression_settings_delete(relid, true);\n}\n\n/*\n * Delete entries matching either the primary key (non-compressed relation) or\n * the secondary key (compressed relation).\n */\nTSDLLEXPORT bool\nts_compression_settings_delete_any(Oid relid)\n{\n\tif (!ts_compression_settings_delete(relid))\n\t\treturn ts_compression_settings_delete_by_compress_relid(relid);\n\treturn true;\n}\n\nstatic void\ncompression_settings_rename_column(CompressionSettings *settings, const char *old, const char *new)\n{\n\tJsonb *replacejsonb = NULL;\n\tbool replaced = false;\n\n\tsettings->fd.segmentby = ts_array_replace_text(settings->fd.segmentby, old, new);\n\tsettings->fd.orderby = ts_array_replace_text(settings->fd.orderby, old, new);\n\n\treplacejsonb = settings->fd.index;\n\tif (replacejsonb)\n\t{\n\t\tSparseIndexSettings *parsed_settings = ts_convert_to_sparse_index_settings(replacejsonb);\n\t\tif (parsed_settings)\n\t\t{\n\t\t\tforeach_ptr(SparseIndexSettingsObject, obj, parsed_settings->objects)\n\t\t\t{\n\t\t\t\tAssert(obj != NULL);\n\t\t\t\tif (!obj)\n\t\t\t\t\tcontinue;\n\n\t\t\t\tforeach_ptr(SparseIndexSettingsPair, pair, obj->pairs)\n\t\t\t\t{\n\t\t\t\t\tAssert(pair != NULL);\n\t\t\t\t\tif (!pair)\n\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\tListCell *value_cell = NULL;\n\t\t\t\t\tforeach (value_cell, pair->values)\n\t\t\t\t\t{\n\t\t\t\t\t\tconst char *value = (const char *) lfirst(value_cell);\n\t\t\t\t\t\tAssert(value != NULL);\n\t\t\t\t\t\tif (!value)\n\t\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t\tif (strcmp(value, old) == 0)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvalue_cell->ptr_value =\n\t\t\t\t\t\t\t\tts_sparse_index_settings_pstrdup(parsed_settings, new);\n\t\t\t\t\t\t\treplaced = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (replaced)\n\t\t\t{\n\t\t\t\treplacejsonb = ts_convert_from_sparse_index_settings(parsed_settings);\n\t\t\t}\n\t\t\tts_free_sparse_index_settings(parsed_settings);\n\t\t}\n\t}\n\n\tsettings->fd.index = replaced ? replacejsonb : settings->fd.index;\n\tts_compression_settings_update(settings);\n}\n\nTSDLLEXPORT void\nts_compression_settings_rename_column_cascade(Oid parent_relid, const char *old, const char *new)\n{\n\tCompressionSettings *settings = ts_compression_settings_get(parent_relid);\n\n\tif (settings)\n\t\tcompression_settings_rename_column(settings, old, new);\n\n\tList *children = find_inheritance_children(parent_relid, NoLock);\n\tListCell *lc;\n\n\tforeach (lc, children)\n\t{\n\t\tOid relid = lfirst_oid(lc);\n\n\t\tsettings = ts_compression_settings_get(relid);\n\n\t\tif (settings)\n\t\t\tcompression_settings_rename_column(settings, old, new);\n\t}\n}\n\nTSDLLEXPORT int\nts_compression_settings_update(CompressionSettings *settings)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tFormData_compression_settings *fd = &settings->fd;\n\tScanKeyData scankey[1];\n\n\tif (settings->fd.orderby && (settings->fd.segmentby || settings->fd.index))\n\t{\n\t\tDatum datum;\n\t\tbool isnull;\n\n\t\tArrayIterator it = array_create_iterator(settings->fd.orderby, 0, NULL);\n\t\twhile (array_iterate(it, &datum, &isnull))\n\t\t{\n\t\t\tif (settings->fd.segmentby &&\n\t\t\t\tts_array_is_member(settings->fd.segmentby, TextDatumGetCString(datum)))\n\t\t\t{\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_SYNTAX_ERROR),\n\t\t\t\t\t\t errmsg(\"cannot use column \\\"%s\\\" for both ordering and segmenting\",\n\t\t\t\t\t\t\t\tTextDatumGetCString(datum)),\n\t\t\t\t\t\t errhint(\"Use separate columns for the timescaledb.compress_orderby and\"\n\t\t\t\t\t\t\t\t \" timescaledb.compress_segmentby options.\")));\n\t\t\t}\n\n\t\t\tif (settings->fd.index &&\n\t\t\t\tts_contains_sparse_index_config(settings,\n\t\t\t\t\t\t\t\t\t\t\t\tTextDatumGetCString(datum),\n\t\t\t\t\t\t\t\t\t\t\t\tts_sparse_index_type_names\n\t\t\t\t\t\t\t\t\t\t\t\t\t[_SparseIndexTypeEnumBloom],\n\t\t\t\t\t\t\t\t\t\t\t\t/* skip_column_arrays = */ true))\n\t\t\t{\n\t\t\t\t/* disallow single column bloom index on orderby columns, composite bloom is\n\t\t\t\t * allowed, that is why we set 'skip_column_arrays' to true */\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_SYNTAX_ERROR),\n\t\t\t\t\t\t errmsg(\"the orderby column \\\"%s\\\" cannot have a bloom sparse index \",\n\t\t\t\t\t\t\t\tTextDatumGetCString(datum)),\n\t\t\t\t\t\t errdetail(\"For orderby columns, a minmax sparse index is added \"\n\t\t\t\t\t\t\t\t   \"automatically and cannot have bloom sparse index.\")));\n\t\t\t}\n\t\t}\n\t}\n\n\tif (settings->fd.index && settings->fd.segmentby)\n\t{\n\t\tDatum datum;\n\t\tbool isnull;\n\n\t\tArrayIterator it = array_create_iterator(settings->fd.segmentby, 0, NULL);\n\t\twhile (array_iterate(it, &datum, &isnull))\n\t\t{\n\t\t\tfor (int i = 0; i < _SparseIndexTypeEnumMax; i++)\n\t\t\t{\n\t\t\t\tif (ts_contains_sparse_index_config(settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\tTextDatumGetCString(datum),\n\t\t\t\t\t\t\t\t\t\t\t\t\tts_sparse_index_type_names[i],\n\t\t\t\t\t\t\t\t\t\t\t\t\t/* skip_column_arrays = */ false))\n\t\t\t\t{\n\t\t\t\t\t/* segmentby columns cannot have sparse indexes of any type, including composite\n\t\t\t\t\t * bloom that is why we set 'skip_column_arrays' to false, which will look\n\t\t\t\t\t * inside column name arrays, so composite bloom filters are checked too */\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_SYNTAX_ERROR),\n\t\t\t\t\t\t\t errmsg(\"the segmentby column \\\"%s\\\" can not have sparse \"\n\t\t\t\t\t\t\t\t\t\"indexes\",\n\t\t\t\t\t\t\t\t\tTextDatumGetCString(datum))));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/*\n\t * The default compression settings will always have orderby settings but the user may have\n\t * chosen to overwrite it. For both cases all 3 orderby arrays must either have the same number\n\t * of entries or be all NULL.\n\t */\n\tAssert(\n\t\t(settings->fd.orderby && settings->fd.orderby_desc && settings->fd.orderby_nullsfirst) ||\n\t\t(!settings->fd.orderby && !settings->fd.orderby_desc && !settings->fd.orderby_nullsfirst));\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_compression_settings_pkey_relid,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_OIDEQ,\n\t\t\t\tObjectIdGetDatum(fd->relid));\n\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, COMPRESSION_SETTINGS),\n\t\t.index = catalog_get_index(catalog, COMPRESSION_SETTINGS, COMPRESSION_SETTINGS_PKEY),\n\t\t.nkeys = 1,\n\t\t.scankey = scankey,\n\t\t.data = settings,\n\t\t.tuple_found = compression_settings_tuple_update,\n\t\t.lockmode = RowExclusiveLock,\n\t\t.scandirection = ForwardScanDirection,\n\t};\n\treturn ts_scanner_scan(&scanctx);\n}\n\nstatic HeapTuple\ncompression_settings_formdata_make_tuple(const FormData_compression_settings *fd, TupleDesc desc)\n{\n\tDatum values[Natts_compression_settings] = { 0 };\n\tbool nulls[Natts_compression_settings] = { false };\n\n\tvalues[AttrNumberGetAttrOffset(Anum_compression_settings_relid)] = ObjectIdGetDatum(fd->relid);\n\n\tif (OidIsValid(fd->compress_relid))\n\t\tvalues[AttrNumberGetAttrOffset(Anum_compression_settings_compress_relid)] =\n\t\t\tObjectIdGetDatum(fd->compress_relid);\n\telse\n\t\tnulls[AttrNumberGetAttrOffset(Anum_compression_settings_compress_relid)] = true;\n\n\tif (fd->segmentby)\n\t\tvalues[AttrNumberGetAttrOffset(Anum_compression_settings_segmentby)] =\n\t\t\tPointerGetDatum(fd->segmentby);\n\telse\n\t\tnulls[AttrNumberGetAttrOffset(Anum_compression_settings_segmentby)] = true;\n\n\tif (fd->orderby)\n\t\tvalues[AttrNumberGetAttrOffset(Anum_compression_settings_orderby)] =\n\t\t\tPointerGetDatum(fd->orderby);\n\telse\n\t\tnulls[AttrNumberGetAttrOffset(Anum_compression_settings_orderby)] = true;\n\n\tif (fd->orderby_desc)\n\t\tvalues[AttrNumberGetAttrOffset(Anum_compression_settings_orderby_desc)] =\n\t\t\tPointerGetDatum(fd->orderby_desc);\n\telse\n\t\tnulls[AttrNumberGetAttrOffset(Anum_compression_settings_orderby_desc)] = true;\n\n\tif (fd->orderby_nullsfirst)\n\t\tvalues[AttrNumberGetAttrOffset(Anum_compression_settings_orderby_nullsfirst)] =\n\t\t\tPointerGetDatum(fd->orderby_nullsfirst);\n\telse\n\t\tnulls[AttrNumberGetAttrOffset(Anum_compression_settings_orderby_nullsfirst)] = true;\n\n\tif (fd->index)\n\t\tvalues[AttrNumberGetAttrOffset(Anum_compression_settings_index)] =\n\t\t\tJsonbPGetDatum(fd->index);\n\telse\n\t\tnulls[AttrNumberGetAttrOffset(Anum_compression_settings_index)] = true;\n\n\treturn heap_form_tuple(desc, values, nulls);\n}\n\nstatic ScanTupleResult\ncompression_settings_tuple_update(TupleInfo *ti, void *data)\n{\n\tCompressionSettings *settings = data;\n\tHeapTuple new_tuple;\n\tCatalogSecurityContext sec_ctx;\n\n\tnew_tuple =\n\t\tcompression_settings_formdata_make_tuple(&settings->fd, ts_scanner_get_tupledesc(ti));\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_update_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti), new_tuple);\n\tts_catalog_restore_user(&sec_ctx);\n\theap_freetuple(new_tuple);\n\treturn SCAN_DONE;\n}\n\nvoid\nts_convert_sparse_index_config_to_jsonb(JsonbParseState *parse_state, SparseIndexConfigBase *config)\n{\n\tMinmaxIndexColumnConfig *minmax_config = NULL;\n\tBloomFilterConfig *bloom_config = NULL;\n\n\tpushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);\n\tts_jsonb_add_str(parse_state,\n\t\t\t\t\t ts_sparse_index_common_keys[SparseIndexKeyType],\n\t\t\t\t\t ts_sparse_index_type_names[config->type]); /* type */\n\tswitch (config->type)\n\t{\n\t\tcase _SparseIndexTypeEnumMinmax:\n\t\t\tminmax_config = (MinmaxIndexColumnConfig *) config;\n\t\t\tts_jsonb_add_str(parse_state,\n\t\t\t\t\t\t\t ts_sparse_index_common_keys[SparseIndexKeyCol],\n\t\t\t\t\t\t\t minmax_config->col); /* column */\n\t\t\tbreak;\n\t\tcase _SparseIndexTypeEnumBloom:\n\t\t\tbloom_config = (BloomFilterConfig *) config;\n\n\t\t\tif (bloom_config->num_columns > 1)\n\t\t\t{\n\t\t\t\t/* add the column names as an array */\n\t\t\t\tconst char *column_names[MAX_BLOOM_FILTER_COLUMNS] = { 0 };\n\t\t\t\tfor (int i = 0; i < bloom_config->num_columns; i++)\n\t\t\t\t{\n\t\t\t\t\tcolumn_names[i] = bloom_config->columns[i].name;\n\t\t\t\t}\n\n\t\t\t\tts_jsonb_add_str_array(parse_state,\n\t\t\t\t\t\t\t\t\t   ts_sparse_index_common_keys[SparseIndexKeyCol],\n\t\t\t\t\t\t\t\t\t   column_names,\n\t\t\t\t\t\t\t\t\t   bloom_config->num_columns);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tts_jsonb_add_str(parse_state,\n\t\t\t\t\t\t\t\t ts_sparse_index_common_keys[SparseIndexKeyCol],\n\t\t\t\t\t\t\t\t bloom_config->columns[0].name); /* column */\n\t\t\t}\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\telog(ERROR, \"invalid sparse index type: %d\", config->type);\n\t};\n\tts_jsonb_add_str(parse_state,\n\t\t\t\t\t ts_sparse_index_common_keys[SparseIndexKeySource],\n\t\t\t\t\t ts_sparse_index_source_names[config->source]); /* source */\n\tpushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);\n}\n\nbool\nts_contains_sparse_index_config(CompressionSettings *settings, const char *attname,\n\t\t\t\t\t\t\t\tconst char *sparse_index_type, bool skip_column_arrays)\n{\n\tbool result = false;\n\tif (settings == NULL || settings->fd.index == NULL || attname == NULL)\n\t\treturn false;\n\n\tSparseIndexSettings *parsed = ts_convert_to_sparse_index_settings(settings->fd.index);\n\n\tif (parsed == NULL)\n\t{\n\t\treturn false;\n\t}\n\n\tforeach_ptr(SparseIndexSettingsObject, obj, parsed->objects)\n\t{\n\t\tconst char *index_type = NULL;\n\t\tbool attname_found = false;\n\t\tforeach_ptr(SparseIndexSettingsPair, pair, obj->pairs)\n\t\t{\n\t\t\tif (strcmp(pair->key, ts_sparse_index_common_keys[SparseIndexKeyCol]) == 0)\n\t\t\t{\n\t\t\t\tif (skip_column_arrays && list_length(pair->values) > 1)\n\t\t\t\t{\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tforeach_ptr(const char, value, pair->values)\n\t\t\t\t{\n\t\t\t\t\tif (strcmp(value, attname) == 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tattname_found = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (strcmp(pair->key, ts_sparse_index_common_keys[SparseIndexKeyType]) == 0)\n\t\t\t{\n\t\t\t\tindex_type = (const char *) lfirst(list_head(pair->values));\n\t\t\t\tif (strcmp(index_type, sparse_index_type) != 0)\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\telse if (attname_found)\n\t\t\t\t{\n\t\t\t\t\tresult = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (attname_found && index_type != NULL)\n\t\t\t{\n\t\t\t\tresult = strcmp(index_type, sparse_index_type) == 0;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tts_free_sparse_index_settings(parsed);\n\treturn result;\n}\n\n/* adds orderby sparse index settings into fd.index */\nJsonb *\nts_add_orderby_sparse_index(CompressionSettings *settings)\n{\n\tDatum datum;\n\tbool isnull;\n\tJsonbParseState *parse_state = NULL;\n\tJsonbIterator *it_json;\n\tJsonbValue v;\n\tJsonbIteratorToken r;\n\tJsonb *sparse_index = settings->fd.index;\n\n\t/* nothing to do if no orderby columns */\n\tif (!settings->fd.orderby)\n\t{\n\t\treturn sparse_index;\n\t}\n\n\tpushJsonbValue(&parse_state, WJB_BEGIN_ARRAY, NULL);\n\n\t/* add existing sparse index */\n\tif (settings->fd.index)\n\t{\n\t\tit_json = JsonbIteratorInit(&sparse_index->root);\n\t\tJsonbIteratorNext(&it_json, &v, false); /* WJB_BEGIN_ARRAY */\n\t\twhile ((r = JsonbIteratorNext(&it_json, &v, true)) != WJB_END_ARRAY)\n\t\t{\n\t\t\tAssert(r == WJB_ELEM);\n\t\t\tpushJsonbValue(&parse_state, r, &v);\n\t\t}\n\t}\n\n\t/* add orderby sparse settings */\n\tArrayIterator it = array_create_iterator(settings->fd.orderby, 0, NULL);\n\twhile (array_iterate(it, &datum, &isnull))\n\t{\n\t\t/*\n\t\t * check if sparse index for column already exists\n\t\t * Validation is done by ts_compression_settings_update\n\t\t */\n\t\tif (settings->fd.index &&\n\t\t\tts_jsonb_has_key_value_str_field(settings->fd.index,\n\t\t\t\t\t\t\t\t\t\t\t ts_sparse_index_common_keys[SparseIndexKeyCol],\n\t\t\t\t\t\t\t\t\t\t\t TextDatumGetCString(datum)))\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\tMinmaxIndexColumnConfig config;\n\t\tconfig.base.type = _SparseIndexTypeEnumMinmax;\n\t\tconfig.col = TextDatumGetCString(datum);\n\t\tconfig.base.source = _SparseIndexSourceEnumOrderby;\n\t\tts_convert_sparse_index_config_to_jsonb(parse_state, (SparseIndexConfigBase *) &config);\n\t}\n\n\treturn JsonbValueToJsonb(pushJsonbValue(&parse_state, WJB_END_ARRAY, NULL));\n}\n\n/* removed orderby sparse index settings from fd.index */\nJsonb *\nts_remove_orderby_sparse_index(CompressionSettings *settings)\n{\n\tJsonbParseState *parse_state = NULL;\n\tJsonbContainer *container;\n\tJsonbIterator *it_json;\n\tJsonbIteratorToken r;\n\tJsonbValue v;\n\n\tJsonb *sparse_index = settings->fd.index;\n\tbool removed = false;\n\tbool has_object = false;\n\tconst char *key_name_source = ts_sparse_index_common_keys[SparseIndexKeySource];\n\tconst char *value_name_orderby = ts_sparse_index_source_names[_SparseIndexSourceEnumOrderby];\n\n\t/* nothing to do if no orderby columns */\n\tif (!settings->fd.orderby || !sparse_index)\n\t{\n\t\treturn sparse_index;\n\t}\n\n\tpushJsonbValue(&parse_state, WJB_BEGIN_ARRAY, NULL);\n\n\tit_json = JsonbIteratorInit(&sparse_index->root);\n\tJsonbIteratorNext(&it_json, &v, false); /* WJB_BEGIN_ARRAY */\n\twhile ((r = JsonbIteratorNext(&it_json, &v, true)) != WJB_END_ARRAY)\n\t{\n\t\tEnsure(r == WJB_ELEM && v.type == jbvBinary && JsonContainerIsObject(v.val.binary.data),\n\t\t\t   \"sparse index format must be an array of objects\");\n\n\t\tcontainer = v.val.binary.data;\n\t\tJsonbValue value;\n\t\tgetKeyJsonValueFromContainer(container, key_name_source, strlen(key_name_source), &value);\n\t\tif (value.type == jbvString && ((int) strlen(value_name_orderby) == value.val.string.len) &&\n\t\t\tstrncmp(value_name_orderby, value.val.string.val, value.val.string.len) == 0)\n\t\t{\n\t\t\tremoved = true;\n\t\t\tcontinue;\n\t\t}\n\n\t\thas_object = true;\n\t\tpushJsonbValue(&parse_state, r, &v);\n\t}\n\n\t/* this is a possible edge case, log it just in case */\n\tif (!removed)\n\t\telog(LOG, \"orderby settings existed, but no orderby sparse index was removed\");\n\n\treturn has_object ? JsonbValueToJsonb(pushJsonbValue(&parse_state, WJB_END_ARRAY, NULL)) : NULL;\n}\n\nint\nts_qsort_attrnumber_cmp(const void *a, const void *b)\n{\n\tSparseIndexColumn *col_a = (SparseIndexColumn *) a;\n\tSparseIndexColumn *col_b = (SparseIndexColumn *) b;\n\n\treturn ((int) (col_a->attnum)) - ((int) (col_b->attnum));\n}\n\nSparseIndexSettings *\nts_convert_to_sparse_index_settings(Jsonb *jsonb)\n{\n\tenum ParseState\n\t{\n\t\tPARSE_STATE_INIT,\n\t\tPARSE_STATE_KEY,\n\t\tPARSE_STATE_VALUE,\n\t\tPARSE_STATE_ARRAY_ENTRIES\n\t};\n\n\tMemoryContext tmp_context, new_context;\n\tenum ParseState state = PARSE_STATE_INIT;\n\tJsonbValue jsonb_value;\n\tJsonbIterator *it;\n\tJsonbIteratorToken r;\n\tint num_arrays = 0;\n\n\tAssert(jsonb != NULL);\n\n\tif (jsonb == NULL)\n\t\treturn NULL;\n\n\tif (JB_ROOT_IS_SCALAR(jsonb))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"cannot convert scalar to SparseIndexSettings\")));\n\n\tif (JB_ROOT_COUNT(jsonb) == 0)\n\t\treturn NULL;\n\n\tit = JsonbIteratorInit(&jsonb->root);\n\n\tnew_context = AllocSetContextCreate(CurrentMemoryContext,\n\t\t\t\t\t\t\t\t\t\t\"SparseIndexSettings\",\n\t\t\t\t\t\t\t\t\t\tALLOCSET_DEFAULT_MINSIZE,\n\t\t\t\t\t\t\t\t\t\tALLOCSET_DEFAULT_INITSIZE,\n\t\t\t\t\t\t\t\t\t\tALLOCSET_DEFAULT_MAXSIZE);\n\n\tSparseIndexSettings *parsed_settings =\n\t\tMemoryContextAllocZero(new_context, sizeof(SparseIndexSettings));\n\n\tparsed_settings->objects = NIL;\n\tparsed_settings->context = new_context;\n\n\twhile ((r = JsonbIteratorNext(&it, &jsonb_value, false)) != WJB_DONE)\n\t{\n\t\tswitch (state)\n\t\t{\n\t\t\tcase PARSE_STATE_INIT:\n\t\t\t\tif (r == WJB_END_OBJECT || r == WJB_END_ARRAY)\n\t\t\t\t{\n\t\t\t\t\t/* Ignore*/\n\t\t\t\t}\n\t\t\t\telse if (r == WJB_BEGIN_OBJECT)\n\t\t\t\t{\n\t\t\t\t\tSparseIndexSettingsObject *current_object = NULL;\n\n\t\t\t\t\t/* If the previous object has no pairs, reuse its space for the new object */\n\t\t\t\t\tif (list_length(parsed_settings->objects) > 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tcurrent_object = llast(parsed_settings->objects);\n\t\t\t\t\t\tif (list_length(current_object->pairs) > 0)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t/* We can't reuse the previous object, so we need to create a new one */\n\t\t\t\t\t\t\tcurrent_object = NULL;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (current_object == NULL)\n\t\t\t\t\t{\n\t\t\t\t\t\t/* Create a new object */\n\t\t\t\t\t\ttmp_context = MemoryContextSwitchTo(parsed_settings->context);\n\t\t\t\t\t\tcurrent_object = palloc0(sizeof(SparseIndexSettingsObject));\n\t\t\t\t\t\tparsed_settings->objects =\n\t\t\t\t\t\t\tlappend(parsed_settings->objects, current_object);\n\t\t\t\t\t\tMemoryContextSwitchTo(tmp_context);\n\t\t\t\t\t}\n\t\t\t\t\tstate = PARSE_STATE_KEY;\n\t\t\t\t}\n\t\t\t\telse if (r == WJB_KEY)\n\t\t\t\t{\n\t\t\t\t\tEnsure(jsonb_value.type == jbvString,\n\t\t\t\t\t\t   \"Jsonb value is of type \\\"%s\\\", but expected of type string, in state \"\n\t\t\t\t\t\t   \"INIT\",\n\t\t\t\t\t\t   JsonbTypeName(&jsonb_value));\n\n\t\t\t\t\tEnsure(list_length(parsed_settings->objects) > 0,\n\t\t\t\t\t\t   \"Jsonb value has a key, but no object has been started, in state INIT\");\n\n\t\t\t\t\tSparseIndexSettingsObject *current_object = llast(parsed_settings->objects);\n\t\t\t\t\tAssert(current_object != NULL);\n\t\t\t\t\ttmp_context = MemoryContextSwitchTo(parsed_settings->context);\n\t\t\t\t\tchar *tmp_str =\n\t\t\t\t\t\tpnstrdup(jsonb_value.val.string.val, jsonb_value.val.string.len);\n\t\t\t\t\tSparseIndexSettingsPair *current_pair =\n\t\t\t\t\t\tpalloc0(sizeof(SparseIndexSettingsPair));\n\t\t\t\t\tcurrent_object->pairs = lappend(current_object->pairs, current_pair);\n\t\t\t\t\tcurrent_pair->key = tmp_str;\n\t\t\t\t\tcurrent_pair->values = NIL;\n\t\t\t\t\tMemoryContextSwitchTo(tmp_context);\n\t\t\t\t\tstate = PARSE_STATE_VALUE;\n\t\t\t\t}\n\t\t\t\telse if (r == WJB_BEGIN_ARRAY)\n\t\t\t\t{\n\t\t\t\t\tnum_arrays++;\n\t\t\t\t\t/* We can ignore one begin array, but more than one is not allowed as we don't\n\t\t\t\t\t * support nested arrays */\n\t\t\t\t\tif (num_arrays > 1)\n\t\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t\t\t\t errmsg(\"Jsonb value is of type \\\"%s\\\", but expected of type begin \"\n\t\t\t\t\t\t\t\t\t\t\"object or end object, in state INIT\",\n\t\t\t\t\t\t\t\t\t\tJsonbTypeName(&jsonb_value))));\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t\t\t errmsg(\"Jsonb value is of type \\\"%s\\\", but expected of type begin \"\n\t\t\t\t\t\t\t\t\t\"object or end object, in state INIT\",\n\t\t\t\t\t\t\t\t\tJsonbTypeName(&jsonb_value))));\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase PARSE_STATE_KEY:\n\t\t\t\tif (r == WJB_KEY)\n\t\t\t\t{\n\t\t\t\t\tEnsure(jsonb_value.type == jbvString,\n\t\t\t\t\t\t   \"Jsonb value is of type \\\"%s\\\", but expected of type string, in state \"\n\t\t\t\t\t\t   \"KEY\",\n\t\t\t\t\t\t   JsonbTypeName(&jsonb_value));\n\n\t\t\t\t\tSparseIndexSettingsObject *current_object = llast(parsed_settings->objects);\n\t\t\t\t\tAssert(current_object != NULL);\n\t\t\t\t\ttmp_context = MemoryContextSwitchTo(parsed_settings->context);\n\t\t\t\t\tchar *tmp_str =\n\t\t\t\t\t\tpnstrdup(jsonb_value.val.string.val, jsonb_value.val.string.len);\n\t\t\t\t\tSparseIndexSettingsPair *current_pair =\n\t\t\t\t\t\tpalloc0(sizeof(SparseIndexSettingsPair));\n\t\t\t\t\tcurrent_object->pairs = lappend(current_object->pairs, current_pair);\n\t\t\t\t\tcurrent_pair->key = tmp_str;\n\t\t\t\t\tcurrent_pair->values = NIL;\n\t\t\t\t\tMemoryContextSwitchTo(tmp_context);\n\t\t\t\t\tstate = PARSE_STATE_VALUE;\n\t\t\t\t}\n\t\t\t\telse if (r == WJB_END_OBJECT)\n\t\t\t\t{\n\t\t\t\t\tstate = PARSE_STATE_INIT;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t\t\t errmsg(\"Jsonb value is of type \\\"%s\\\", but expected of type key or \"\n\t\t\t\t\t\t\t\t\t\"end object, in state KEY\",\n\t\t\t\t\t\t\t\t\tJsonbTypeName(&jsonb_value))));\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase PARSE_STATE_VALUE:\n\t\t\t\tif (r == WJB_VALUE)\n\t\t\t\t{\n\t\t\t\t\tSparseIndexSettingsObject *current_object = llast(parsed_settings->objects);\n\t\t\t\t\tAssert(current_object != NULL);\n\n\t\t\t\t\tEnsure(jsonb_value.type == jbvString,\n\t\t\t\t\t\t   \"Jsonb value is of type \\\"%s\\\", but expected of type string, in state \"\n\t\t\t\t\t\t   \"VALUE\",\n\t\t\t\t\t\t   JsonbTypeName(&jsonb_value));\n\n\t\t\t\t\ttmp_context = MemoryContextSwitchTo(parsed_settings->context);\n\t\t\t\t\tchar *tmp_str =\n\t\t\t\t\t\tpnstrdup(jsonb_value.val.string.val, jsonb_value.val.string.len);\n\t\t\t\t\tSparseIndexSettingsPair *current_pair = llast(current_object->pairs);\n\n\t\t\t\t\t/* The pair should have been created in the KEY state, but no values should have\n\t\t\t\t\t * been added yet */\n\t\t\t\t\tAssert(current_pair != NULL);\n\t\t\t\t\tAssert(current_pair->values == NIL);\n\n\t\t\t\t\tcurrent_pair->values = lappend(current_pair->values, tmp_str);\n\t\t\t\t\tMemoryContextSwitchTo(tmp_context);\n\t\t\t\t\tstate = PARSE_STATE_KEY;\n\t\t\t\t}\n\t\t\t\telse if (r == WJB_BEGIN_ARRAY)\n\t\t\t\t{\n#ifdef USE_ASSERT_CHECKING\n\t\t\t\t\tSparseIndexSettingsObject *current_object = llast(parsed_settings->objects);\n\t\t\t\t\tAssert(current_object != NULL);\n\n\t\t\t\t\t/* The pair list should have been created in the key state */\n\t\t\t\t\tAssert(list_length(current_object->pairs) > 0);\n\n\t\t\t\t\tSparseIndexSettingsPair *current_pair = llast(current_object->pairs);\n\t\t\t\t\tAssert(current_pair != NULL);\n\t\t\t\t\t/* The values list should be empty when we arrive to the array start */\n\t\t\t\t\tAssert(current_pair->values == NIL);\n#endif\n\t\t\t\t\tstate = PARSE_STATE_ARRAY_ENTRIES;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t\t\t errmsg(\"Jsonb value is of type \\\"%s\\\", but expected of type value or \"\n\t\t\t\t\t\t\t\t\t\"array, in state VALUE\",\n\t\t\t\t\t\t\t\t\tJsonbTypeName(&jsonb_value))));\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase PARSE_STATE_ARRAY_ENTRIES:\n\t\t\t\tif (r == WJB_ELEM)\n\t\t\t\t{\n\t\t\t\t\tSparseIndexSettingsObject *current_object = llast(parsed_settings->objects);\n\t\t\t\t\tAssert(current_object != NULL);\n\n\t\t\t\t\tSparseIndexSettingsPair *current_pair = llast(current_object->pairs);\n\t\t\t\t\tAssert(current_pair != NULL);\n\n\t\t\t\t\tEnsure(jsonb_value.type == jbvString,\n\t\t\t\t\t\t   \"Jsonb value is of type \\\"%s\\\", but expected of type string, in state \"\n\t\t\t\t\t\t   \"ARRAY_ENTRIES\",\n\t\t\t\t\t\t   JsonbTypeName(&jsonb_value));\n\n\t\t\t\t\ttmp_context = MemoryContextSwitchTo(parsed_settings->context);\n\t\t\t\t\tchar *tmp_str =\n\t\t\t\t\t\tpnstrdup(jsonb_value.val.string.val, jsonb_value.val.string.len);\n\t\t\t\t\tcurrent_pair->values = lappend(current_pair->values, tmp_str);\n\t\t\t\t\tMemoryContextSwitchTo(tmp_context);\n\t\t\t\t\t/* state remains ARRAY_ENTRIES */\n\t\t\t\t}\n\t\t\t\telse if (r == WJB_END_ARRAY)\n\t\t\t\t{\n\t\t\t\t\tstate = PARSE_STATE_INIT;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t\t\t errmsg(\"Jsonb value is of type \\\"%s\\\", but expected of type elem or \"\n\t\t\t\t\t\t\t\t\t\"end array, in state ARRAY_ENTRIES\",\n\t\t\t\t\t\t\t\t\tJsonbTypeName(&jsonb_value))));\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/* If the last object has no pairs, remove it */\n\tif (list_length(parsed_settings->objects) > 0)\n\t{\n\t\tSparseIndexSettingsObject *current_object = llast(parsed_settings->objects);\n\t\tAssert(current_object != NULL);\n\t\tif (list_length(current_object->pairs) == 0)\n\t\t{\n\t\t\tparsed_settings->objects = list_delete_last(parsed_settings->objects);\n\t\t}\n\t}\n\n\t/* If there are no objects, free the parsed settings */\n\tif (list_length(parsed_settings->objects) == 0)\n\t{\n\t\tts_free_sparse_index_settings(parsed_settings);\n\t\tparsed_settings = NULL;\n\t}\n\treturn parsed_settings;\n}\n\nJsonb *\nts_convert_from_sparse_index_settings(SparseIndexSettings *settings)\n{\n\tJsonbParseState *parse_state = NULL;\n\n\tAssert(settings != NULL);\n\tif (settings == NULL)\n\t\treturn NULL;\n\n\tif (list_length(settings->objects) == 0)\n\t\treturn NULL;\n\n\tpushJsonbValue(&parse_state, WJB_BEGIN_ARRAY, NULL);\n\tforeach_ptr(SparseIndexSettingsObject, obj, settings->objects)\n\t{\n\t\tpushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);\n\t\tAssert(list_length(obj->pairs) > 0);\n\t\tforeach_ptr(SparseIndexSettingsPair, pair, obj->pairs)\n\t\t{\n\t\t\tAssert(pair->key != NULL);\n\t\t\tAssert(list_length(pair->values) > 0);\n\t\t\tJsonbValue key = { .type = jbvString,\n\t\t\t\t\t\t\t   .val = {\n\t\t\t\t\t\t\t\t   .string = { .val = pair->key, .len = strlen(pair->key) } } };\n\t\t\tpushJsonbValue(&parse_state, WJB_KEY, &key);\n\n\t\t\tif (list_length(pair->values) == 1)\n\t\t\t{\n\t\t\t\tconst char *value = (const char *) lfirst(list_head(pair->values));\n\t\t\t\tAssert(value != NULL);\n\t\t\t\tint len = strlen(value);\n\t\t\t\tAssert(len > 0);\n\t\t\t\tJsonbValue value_jsonb = {\n\t\t\t\t\t.type = jbvString, .val = { .string = { .val = pstrdup(value), .len = len } }\n\t\t\t\t};\n\t\t\t\tpushJsonbValue(&parse_state, WJB_VALUE, &value_jsonb);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tpushJsonbValue(&parse_state, WJB_BEGIN_ARRAY, NULL);\n\t\t\t\tforeach_ptr(const char, value, pair->values)\n\t\t\t\t{\n\t\t\t\t\tAssert(value != NULL);\n\t\t\t\t\tint len = strlen(value);\n\t\t\t\t\tAssert(len > 0);\n\t\t\t\t\tJsonbValue value_jsonb = { .type = jbvString,\n\t\t\t\t\t\t\t\t\t\t\t   .val = { .string = { .val = pstrdup(value),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t.len = len } } };\n\t\t\t\t\tpushJsonbValue(&parse_state, WJB_ELEM, &value_jsonb);\n\t\t\t\t}\n\t\t\t\tpushJsonbValue(&parse_state, WJB_END_ARRAY, NULL);\n\t\t\t}\n\t\t}\n\t\tpushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);\n\t}\n\treturn JsonbValueToJsonb(pushJsonbValue(&parse_state, WJB_END_ARRAY, NULL));\n}\n\nvoid\nts_free_sparse_index_settings(SparseIndexSettings *settings)\n{\n\tif (settings == NULL)\n\t\treturn;\n\n\tif (settings->context != NULL)\n\t\tMemoryContextDelete(settings->context);\n}\n\nconst char *\nts_sparse_index_settings_to_cstring(const SparseIndexSettings *settings)\n{\n\tStringInfoData buf;\n\tint i = 0, j = 0, k = 0;\n\n\tinitStringInfo(&buf);\n\tappendStringInfo(&buf, \"[\");\n\n\tforeach_ptr(SparseIndexSettingsObject, obj, settings->objects)\n\t{\n\t\tif (i > 0)\n\t\t\tappendStringInfo(&buf, \", \");\n\t\tappendStringInfo(&buf, \"{\");\n\n\t\tj = 0;\n\t\tforeach_ptr(SparseIndexSettingsPair, pair, obj->pairs)\n\t\t{\n\t\t\tif (j > 0)\n\t\t\t\tappendStringInfo(&buf, \", \");\n\t\t\tescape_json(&buf, pair->key);\n\t\t\tappendStringInfo(&buf, \": \");\n\n\t\t\tif (list_length(pair->values) == 1)\n\t\t\t{\n\t\t\t\tescape_json(&buf, (const char *) lfirst(list_head(pair->values)));\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tk = 0;\n\t\t\t\tappendStringInfo(&buf, \"[\");\n\t\t\t\tforeach_ptr(const char, value, pair->values)\n\t\t\t\t{\n\t\t\t\t\tif (k > 0)\n\t\t\t\t\t\tappendStringInfo(&buf, \", \");\n\t\t\t\t\tescape_json(&buf, value);\n\t\t\t\t\tk++;\n\t\t\t\t}\n\t\t\t\tappendStringInfo(&buf, \"]\");\n\t\t\t}\n\t\t\tj++;\n\t\t}\n\t\tappendStringInfo(&buf, \"}\");\n\t\ti++;\n\t}\n\n\tappendStringInfo(&buf, \"]\");\n\treturn buf.data;\n}\n\nchar *\nts_sparse_index_settings_pstrdup(SparseIndexSettings *settings, const char *str)\n{\n\tAssert(settings != NULL);\n\tAssert(str != NULL);\n\tAssert(settings->context != NULL);\n\n\tMemoryContext old_context = MemoryContextSwitchTo(settings->context);\n\tchar *new_str = pstrdup(str);\n\tMemoryContextSwitchTo(old_context);\n\treturn new_str;\n}\n\n/* returns a list of PerColumnCompressionSettings objects */\nList *\nts_get_per_column_compression_settings(const SparseIndexSettings *settings)\n{\n\tif (settings == NULL)\n\t\treturn NIL;\n\n\tList *result_settings = NIL;\n\tint obj_id = 0;\n\n\tforeach_ptr(SparseIndexSettingsObject, obj, settings->objects)\n\t{\n\t\tconst char *index_type = NULL;\n\t\tSparseIndexSettingsPair *column_names_pair = NULL;\n\n\t\tAssert(obj != NULL);\n\n\t\tforeach_ptr(SparseIndexSettingsPair, pair, obj->pairs)\n\t\t{\n\t\t\tif (strcmp(pair->key, ts_sparse_index_common_keys[SparseIndexKeyType]) == 0)\n\t\t\t{\n\t\t\t\tAssert(list_length(pair->values) > 0);\n\t\t\t\tindex_type = (const char *) lfirst(list_head(pair->values));\n\t\t\t}\n\t\t\telse if (strcmp(pair->key, ts_sparse_index_common_keys[SparseIndexKeyCol]) == 0)\n\t\t\t{\n\t\t\t\tcolumn_names_pair = pair;\n\t\t\t}\n\t\t}\n\n\t\t/* we may have an empty object, that is the default */\n\t\tif (index_type != NULL && column_names_pair != NULL)\n\t\t{\n\t\t\t/* find the column names and iterate over them */\n\t\t\tint num_columns = list_length(column_names_pair->values);\n\t\t\tforeach_ptr(const char, column_name, column_names_pair->values)\n\t\t\t{\n\t\t\t\tAssert(column_name != NULL);\n\n\t\t\t\tPerColumnCompressionSettings *per_column_setting = NULL;\n\t\t\t\t/* check if the column name is already in the list */\n\t\t\t\tforeach_ptr(PerColumnCompressionSettings, tmp, result_settings)\n\t\t\t\t{\n\t\t\t\t\tif (strcmp(tmp->column_name, column_name) == 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tper_column_setting = tmp;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t/* if no object is found, create a new one */\n\t\t\t\tif (per_column_setting == NULL)\n\t\t\t\t{\n\t\t\t\t\tper_column_setting = palloc0(sizeof(PerColumnCompressionSettings));\n\t\t\t\t\tper_column_setting->column_name = column_name;\n\t\t\t\t\tper_column_setting->minmax_obj_id = -1;\n\t\t\t\t\tper_column_setting->single_bloom_obj_id = -1;\n\t\t\t\t\tper_column_setting->composite_bloom_index_obj_ids = NULL;\n\t\t\t\t\tresult_settings = lappend(result_settings, per_column_setting);\n\t\t\t\t}\n\n\t\t\t\tif (strcmp(index_type, ts_sparse_index_type_names[_SparseIndexTypeEnumMinmax]) == 0)\n\t\t\t\t{\n\t\t\t\t\tAssert(num_columns == 1);\n\t\t\t\t\tper_column_setting->minmax_obj_id = obj_id;\n\t\t\t\t}\n\t\t\t\telse if (strcmp(index_type,\n\t\t\t\t\t\t\t\tts_sparse_index_type_names[_SparseIndexTypeEnumBloom]) == 0)\n\t\t\t\t{\n\t\t\t\t\tif (num_columns == 1)\n\t\t\t\t\t{\n\t\t\t\t\t\tper_column_setting->single_bloom_obj_id = obj_id;\n\t\t\t\t\t}\n\t\t\t\t\telse if (num_columns > 1)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (per_column_setting->composite_bloom_index_obj_ids == NULL)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tper_column_setting->composite_bloom_index_obj_ids =\n\t\t\t\t\t\t\t\tbms_make_singleton(obj_id);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tper_column_setting->composite_bloom_index_obj_ids =\n\t\t\t\t\t\t\t\tbms_add_member(per_column_setting->composite_bloom_index_obj_ids,\n\t\t\t\t\t\t\t\t\t\t\t   obj_id);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tobj_id++;\n\t}\n\treturn result_settings;\n}\n\nPerColumnCompressionSettings *\nts_get_per_column_compression_settings_by_column_name(List *per_column_settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  const char *column_name)\n{\n\tAssert(column_name != NULL);\n\n\tListCell *per_column_setting_cell = NULL;\n\tforeach (per_column_setting_cell, per_column_settings)\n\t{\n\t\tPerColumnCompressionSettings *tmp =\n\t\t\t(PerColumnCompressionSettings *) lfirst(per_column_setting_cell);\n\t\tif (strcmp(tmp->column_name, column_name) == 0)\n\t\t{\n\t\t\treturn tmp;\n\t\t}\n\t}\n\treturn NULL;\n}\n\nList *\nts_get_column_names_from_parsed_object(SparseIndexSettingsObject *obj)\n{\n\tAssert(obj != NULL);\n\tforeach_ptr(SparseIndexSettingsPair, pair, obj->pairs)\n\t{\n\t\tif (strcmp(pair->key, ts_sparse_index_common_keys[SparseIndexKeyCol] /* \"column\" */) == 0)\n\t\t{\n\t\t\treturn pair->values;\n\t\t}\n\t}\n\treturn NULL;\n}\n\nstatic Bitmapset *\nresolve_columns_to_attnos(List *column_names, Oid relid)\n{\n\tAssert(column_names != NULL);\n\tAssert(OidIsValid(relid));\n\n\tBitmapset *result = NULL;\n\tListCell *name_cell = NULL;\n\n\tforeach (name_cell, column_names)\n\t{\n\t\tconst char *name = (const char *) lfirst(name_cell);\n\t\tAttrNumber attno = get_attnum(relid, name);\n\t\tif (AttributeNumberIsValid(attno))\n\t\t{\n\t\t\tresult = bms_add_member(result, attno);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_COLUMN),\n\t\t\t\t\t errmsg(\"column \\\"%s\\\" of relation \\\"%ld\\\" does not exist\",\n\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\t(long) relid)));\n\t\t}\n\t}\n\treturn result;\n}\n\n/*\n * Resolve the column names in the parsed settings to attribute numbers for the given relation\n * and return a list of bitmapsets corresponding to each object in the parsed settings.\n */\nTsBmsList\nts_resolve_columns_to_attnos_from_parsed_settings(SparseIndexSettings *settings, Oid relid)\n{\n\tAssert(settings != NULL);\n\tAssert(OidIsValid(relid));\n\n\tTsBmsList result = NIL;\n\tforeach_ptr(SparseIndexSettingsObject, obj, settings->objects)\n\t{\n\t\tList *column_names = ts_get_column_names_from_parsed_object(obj);\n\t\tBitmapset *attnos = resolve_columns_to_attnos(column_names, relid);\n\t\tresult = lappend(result, attnos);\n\t}\n\n\treturn result;\n}\n\nList *\nts_get_values_by_key_from_parsed_object(SparseIndexSettingsObject *obj, const char *key)\n{\n\tAssert(obj != NULL);\n\tAssert(key != NULL);\n\n\tList *result = NIL;\n\tforeach_ptr(SparseIndexSettingsPair, pair, obj->pairs)\n\t{\n\t\tif (strcmp(pair->key, key) == 0)\n\t\t{\n\t\t\tresult = pair->values;\n\t\t\treturn result;\n\t\t}\n\t}\n\treturn NIL;\n}\n"
  },
  {
    "path": "src/ts_catalog/compression_settings.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <catalog/pg_type.h>\n\n#include \"bmslist_utils.h\"\n#include \"ts_catalog/catalog.h\"\n\ntypedef struct CompressionSettings\n{\n\tFormData_compression_settings fd;\n} CompressionSettings;\n\ntypedef enum SparseIndexTypeEnum\n{\n\t_SparseIndexTypeEnumBloom = 0,\n\t_SparseIndexTypeEnumMinmax,\n\t_SparseIndexTypeEnumMax\n} SparseIndexTypeEnum;\n\ntypedef enum SparseIndexSourceEnum\n{\n\t_SparseIndexSourceEnumConfig = 0,\n\t_SparseIndexSourceEnumDefault,\n\t_SparseIndexSourceEnumOrderby,\n\t_SparseIndexSourceEnumMax\n} SparseIndexSourceEnum;\n\ntypedef enum SparseIndexConfigKeys\n{\n\tSparseIndexKeyType = 0,\n\tSparseIndexKeyCol,\n\tSparseIndexKeySource,\n\tSparseIndexKeyCustom\n} SparseIndexConfigKeys;\n\nextern TSDLLEXPORT const char *ts_sparse_index_type_names[];\nextern TSDLLEXPORT const char *ts_sparse_index_source_names[];\nextern TSDLLEXPORT const char *ts_sparse_index_common_keys[];\n\ntypedef struct SparseIndexConfigBase\n{\n\tSparseIndexTypeEnum type;\n\tSparseIndexSourceEnum source;\n} SparseIndexConfigBase;\n\ntypedef struct MinmaxIndexColumnConfig\n{\n\tSparseIndexConfigBase base;\n\tconst char *col;\n} MinmaxIndexColumnConfig;\n\ntypedef struct SparseIndexColumn\n{\n\t/* composite bloom indexes will have multiple SparseIndexColumn entries and\n\t * they will be sorted by the attribute number */\n\tAttrNumber attnum;\n\tconst char *name;\n\tOid type;\n} SparseIndexColumn;\n\n#define MAX_BLOOM_FILTER_COLUMNS 8\n\ntypedef struct BloomFilterConfig\n{\n\tSparseIndexConfigBase base;\n\tint num_columns;\n\tSparseIndexColumn *columns;\n} BloomFilterConfig;\n\n/*\n * The SparseIndexSettings structure is used to parse the compression\n * settings from the JSONB structure.\n * With this we can turn the stored JSONB into this structure, modify it and\n * turn it back into JSONB and we can avoid the messy and error prone JSONB\n * manipulation.\n *\n * The structure is a list of objects, each object is a list of pairs, each\n * pair is a key and a list of values. This allows us to store and manipulate\n * JSONB structures like this:\n *\n * [\n *   {\"type\": \"bloom\", \"column\": \"big1\", \"source\": \"config\"},\n *   {\"type\": \"bloom\", \"column\": [\"value\", \"big1\", \"big2\"], \"source\": \"config\"},\n *   {\"type\": \"bloom\", \"column\": [\"o\", \"big2\"], \"source\": \"config\"},\n *   {\"type\": \"minmax\", \"column\": \"ts\", \"source\": \"orderby\"}\n * ]\n *\n * Notice that the \"column\" key can have a string or an array of strings as value.\n */\ntypedef struct SparseIndexSettingsPair\n{\n\tchar *key;\n\tList *values; /* List of strings */\n} SparseIndexSettingsPair;\ntypedef struct SparseIndexSettingsObject\n{\n\tList *pairs; /* List of SparseIndexSettingsPair */\n} SparseIndexSettingsObject;\n\ntypedef struct SparseIndexSettings\n{\n\tMemoryContext context;\n\tList *objects; /* List of SparseIndexSettingsObject */\n} SparseIndexSettings;\n\ntypedef struct PerColumnCompressionSettings\n{\n\tconst char *column_name;\n\n\t/* the index of the minmax index object that the column participates in, -1 if not present */\n\tint minmax_obj_id;\n\n\t/* the index of the single bloom index object that the column participates in, -1 if not present\n\t */\n\tint single_bloom_obj_id;\n\n\t/* the object ids of the composite bloom index objects that the column participates in */\n\tBitmapset *composite_bloom_index_obj_ids;\n} PerColumnCompressionSettings;\n\nTSDLLEXPORT int ts_qsort_attrnumber_cmp(const void *a, const void *b);\n\nTSDLLEXPORT CompressionSettings *\nts_compression_settings_create(Oid relid, Oid compress_relid, ArrayType *segmentby,\n\t\t\t\t\t\t\t   ArrayType *orderby, ArrayType *orderby_desc,\n\t\t\t\t\t\t\t   ArrayType *orderby_nullsfirst, Jsonb *sparse_index);\nTSDLLEXPORT CompressionSettings *ts_compression_settings_get(Oid relid);\nTSDLLEXPORT CompressionSettings *ts_compression_settings_get_by_compress_relid(Oid relid);\nTSDLLEXPORT CompressionSettings *ts_compression_settings_materialize(const CompressionSettings *src,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t Oid relid, Oid compress_relid);\nTSDLLEXPORT bool ts_compression_settings_delete(Oid relid);\nTSDLLEXPORT bool ts_compression_settings_delete_by_compress_relid(Oid relid);\nTSDLLEXPORT bool ts_compression_settings_delete_any(Oid relid);\nTSDLLEXPORT bool ts_compression_settings_equal(const CompressionSettings *left,\n\t\t\t\t\t\t\t\t\t\t\t   const CompressionSettings *right);\nTSDLLEXPORT bool ts_compression_settings_equal_with_defaults(const CompressionSettings *ht,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t const CompressionSettings *chunk);\n\nTSDLLEXPORT int ts_compression_settings_update(CompressionSettings *settings);\nTSDLLEXPORT void ts_compression_settings_rename_column_cascade(Oid parent_relid, const char *old,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   const char *new);\nTSDLLEXPORT void ts_convert_sparse_index_config_to_jsonb(JsonbParseState *parse_state,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t SparseIndexConfigBase *config);\n\nTSDLLEXPORT\nbool ts_contains_sparse_index_config(CompressionSettings *settings, const char *attname,\n\t\t\t\t\t\t\t\t\t const char *sparse_index_type, bool skip_column_arrays);\nTSDLLEXPORT Jsonb *ts_add_orderby_sparse_index(CompressionSettings *settings);\n\nTSDLLEXPORT Jsonb *ts_remove_orderby_sparse_index(CompressionSettings *settings);\n\nextern TSDLLEXPORT SparseIndexSettings *ts_convert_to_sparse_index_settings(Jsonb *jsonb);\nextern TSDLLEXPORT Jsonb *ts_convert_from_sparse_index_settings(SparseIndexSettings *settings);\nextern TSDLLEXPORT void ts_free_sparse_index_settings(SparseIndexSettings *settings);\nextern TSDLLEXPORT const char *\nts_sparse_index_settings_to_cstring(const SparseIndexSettings *settings);\nextern TSDLLEXPORT char *ts_sparse_index_settings_pstrdup(SparseIndexSettings *settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  const char *str);\nextern TSDLLEXPORT List *\nts_get_per_column_compression_settings(const SparseIndexSettings *settings);\nextern TSDLLEXPORT PerColumnCompressionSettings *\nts_get_per_column_compression_settings_by_column_name(List *per_column_settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  const char *column_name);\nextern TSDLLEXPORT List *ts_get_column_names_from_parsed_object(SparseIndexSettingsObject *obj);\n\nextern TSDLLEXPORT TsBmsList\nts_resolve_columns_to_attnos_from_parsed_settings(SparseIndexSettings *settings, Oid relid);\n\nextern TSDLLEXPORT List *ts_get_values_by_key_from_parsed_object(SparseIndexSettingsObject *obj,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t const char *key);\n"
  },
  {
    "path": "src/ts_catalog/continuous_agg.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n/*\n * This file handles commands on continuous aggs that should be allowed in\n * apache only mode. Right now this consists mostly of drop commands\n */\n\n#include <postgres.h>\n\n#include <access/htup_details.h>\n#include <catalog/dependency.h>\n#include <catalog/namespace.h>\n#include <catalog/pg_trigger.h>\n#include <commands/trigger.h>\n#include <executor/spi.h>\n#include <fmgr.h>\n#include <lib/stringinfo.h>\n#include <nodes/makefuncs.h>\n#include <replication/slot.h>\n#include <storage/lmgr.h>\n#include <utils/acl.h>\n#include <utils/builtins.h>\n#include <utils/date.h>\n#include <utils/lsyscache.h>\n#include <utils/timestamp.h>\n\n#include \"compat/compat.h\"\n\n#include \"bgw/job.h\"\n#include \"cross_module_fn.h\"\n#include \"errors.h\"\n#include \"func_cache.h\"\n#include \"hypercube.h\"\n#include \"hypertable.h\"\n#include \"hypertable_cache.h\"\n#include \"scan_iterator.h\"\n#include \"time_bucket.h\"\n#include \"time_utils.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"ts_catalog/compression_settings.h\"\n#include \"ts_catalog/continuous_agg.h\"\n#include \"ts_catalog/continuous_aggs_watermark.h\"\n#include \"utils.h\"\n#include \"with_clause/alter_table_with_clause.h\"\n\n#define BUCKET_FUNCTION_SERIALIZE_VERSION 1\n#define CHECK_NAME_MATCH(name1, name2) (namestrcmp(name1, name2) == 0)\n\nstatic void\ninit_scan_by_mat_hypertable_id(ScanIterator *iterator, const int32 mat_hypertable_id)\n{\n\titerator->ctx.index = catalog_get_index(ts_catalog_get(), CONTINUOUS_AGG, CONTINUOUS_AGG_PKEY);\n\n\tts_scan_iterator_scan_key_init(iterator,\n\t\t\t\t\t\t\t\t   Anum_continuous_agg_pkey_mat_hypertable_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(mat_hypertable_id));\n}\n\nstatic void\ninit_scan_cagg_bucket_function_by_mat_hypertable_id(ScanIterator *iterator,\n\t\t\t\t\t\t\t\t\t\t\t\t\tconst int32 mat_hypertable_id)\n{\n\titerator->ctx.index = catalog_get_index(ts_catalog_get(),\n\t\t\t\t\t\t\t\t\t\t\tCONTINUOUS_AGGS_BUCKET_FUNCTION,\n\t\t\t\t\t\t\t\t\t\t\tCONTINUOUS_AGGS_BUCKET_FUNCTION_PKEY_IDX);\n\n\tts_scan_iterator_scan_key_init(iterator,\n\t\t\t\t\t\t\t\t   Anum_continuous_aggs_bucket_function_pkey_mat_hypertable_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(mat_hypertable_id));\n}\n\nstatic void\ninit_scan_by_raw_hypertable_id(ScanIterator *iterator, const int32 raw_hypertable_id)\n{\n\titerator->ctx.index =\n\t\tcatalog_get_index(ts_catalog_get(), CONTINUOUS_AGG, CONTINUOUS_AGG_RAW_HYPERTABLE_ID_IDX);\n\n\tts_scan_iterator_scan_key_init(iterator,\n\t\t\t\t\t\t\t\t   Anum_continuous_agg_raw_hypertable_id_idx_raw_hypertable_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(raw_hypertable_id));\n}\n\nstatic void\ninit_invalidation_threshold_scan_by_hypertable_id(ScanIterator *iterator,\n\t\t\t\t\t\t\t\t\t\t\t\t  const int32 raw_hypertable_id)\n{\n\titerator->ctx.index = catalog_get_index(ts_catalog_get(),\n\t\t\t\t\t\t\t\t\t\t\tCONTINUOUS_AGGS_INVALIDATION_THRESHOLD,\n\t\t\t\t\t\t\t\t\t\t\tCONTINUOUS_AGGS_INVALIDATION_THRESHOLD_PKEY);\n\n\tts_scan_iterator_scan_key_init(iterator,\n\t\t\t\t\t\t\t\t   Anum_continuous_aggs_invalidation_threshold_pkey_hypertable_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(raw_hypertable_id));\n}\n\nstatic void\ninit_hypertable_invalidation_log_scan_by_hypertable_id(ScanIterator *iterator,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   const int32 raw_hypertable_id)\n{\n\titerator->ctx.index = catalog_get_index(ts_catalog_get(),\n\t\t\t\t\t\t\t\t\t\t\tCONTINUOUS_AGGS_HYPERTABLE_INVALIDATION_LOG,\n\t\t\t\t\t\t\t\t\t\t\tCONTINUOUS_AGGS_HYPERTABLE_INVALIDATION_LOG_IDX);\n\n\tts_scan_iterator_scan_key_init(\n\t\titerator,\n\t\tAnum_continuous_aggs_hypertable_invalidation_log_idx_hypertable_id,\n\t\tBTEqualStrategyNumber,\n\t\tF_INT4EQ,\n\t\tInt32GetDatum(raw_hypertable_id));\n}\n\nstatic void\ninit_materialization_invalidation_log_scan_by_materialization_id(ScanIterator *iterator,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t const int32 materialization_id)\n{\n\titerator->ctx.index = catalog_get_index(ts_catalog_get(),\n\t\t\t\t\t\t\t\t\t\t\tCONTINUOUS_AGGS_MATERIALIZATION_INVALIDATION_LOG,\n\t\t\t\t\t\t\t\t\t\t\tCONTINUOUS_AGGS_MATERIALIZATION_INVALIDATION_LOG_IDX);\n\n\tts_scan_iterator_scan_key_init(\n\t\titerator,\n\t\tAnum_continuous_aggs_materialization_invalidation_log_idx_materialization_id,\n\t\tBTEqualStrategyNumber,\n\t\tF_INT4EQ,\n\t\tInt32GetDatum(materialization_id));\n}\n\nstatic void\ninit_materialization_ranges_scan_by_materialization_id(ScanIterator *iterator,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   const int32 materialization_id)\n{\n\titerator->ctx.index = catalog_get_index(ts_catalog_get(),\n\t\t\t\t\t\t\t\t\t\t\tCONTINUOUS_AGGS_MATERIALIZATION_RANGES,\n\t\t\t\t\t\t\t\t\t\t\tCONTINUOUS_AGGS_MATERIALIZATION_RANGES_IDX);\n\n\tts_scan_iterator_scan_key_init(iterator,\n\t\t\t\t\t\t\t\t   Anum_continuous_aggs_materialization_ranges_materialization_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(materialization_id));\n}\n\nstatic int32\nnumber_of_continuous_aggs_attached(int32 raw_hypertable_id)\n{\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(CONTINUOUS_AGG, AccessShareLock, CurrentMemoryContext);\n\tint32 count = 0;\n\n\tinit_scan_by_raw_hypertable_id(&iterator, raw_hypertable_id);\n\tts_scanner_foreach(&iterator) { count++; }\n\treturn count;\n}\n\nstatic void\ninvalidation_threshold_delete(int32 raw_hypertable_id)\n{\n\tScanIterator iterator = ts_scan_iterator_create(CONTINUOUS_AGGS_INVALIDATION_THRESHOLD,\n\t\t\t\t\t\t\t\t\t\t\t\t\tRowExclusiveLock,\n\t\t\t\t\t\t\t\t\t\t\t\t\tCurrentMemoryContext);\n\n\tinit_invalidation_threshold_scan_by_hypertable_id(&iterator, raw_hypertable_id);\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tts_catalog_delete_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\t}\n}\n\nstatic void\ncagg_bucket_function_delete(int32 mat_hypertable_id)\n{\n\tScanIterator iterator = ts_scan_iterator_create(CONTINUOUS_AGGS_BUCKET_FUNCTION,\n\t\t\t\t\t\t\t\t\t\t\t\t\tRowExclusiveLock,\n\t\t\t\t\t\t\t\t\t\t\t\t\tCurrentMemoryContext);\n\n\tinit_scan_cagg_bucket_function_by_mat_hypertable_id(&iterator, mat_hypertable_id);\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tts_catalog_delete_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\t}\n}\n\nstatic void\nhypertable_invalidation_log_delete(int32 raw_hypertable_id)\n{\n\tScanIterator iterator = ts_scan_iterator_create(CONTINUOUS_AGGS_HYPERTABLE_INVALIDATION_LOG,\n\t\t\t\t\t\t\t\t\t\t\t\t\tRowExclusiveLock,\n\t\t\t\t\t\t\t\t\t\t\t\t\tCurrentMemoryContext);\n\n\tinit_hypertable_invalidation_log_scan_by_hypertable_id(&iterator, raw_hypertable_id);\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tts_catalog_delete_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\t}\n}\n\nvoid\nts_get_invalidation_replication_slot_name(char *slotname, Size szslot)\n{\n\tsnprintf(slotname, szslot, \"ts_%u_cagg\", MyDatabaseId);\n}\n\nstatic void\nts_materialization_invalidation_log_delete(int32 mat_hypertable_id)\n{\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(CONTINUOUS_AGGS_MATERIALIZATION_INVALIDATION_LOG,\n\t\t\t\t\t\t\t\tRowExclusiveLock,\n\t\t\t\t\t\t\t\tCurrentMemoryContext);\n\n\telog(DEBUG1, \"materialization log delete for hypertable %d\", mat_hypertable_id);\n\tinit_materialization_invalidation_log_scan_by_materialization_id(&iterator, mat_hypertable_id);\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tts_catalog_delete_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\t}\n}\n\nstatic void\nts_materialization_ranges_delete(int32 mat_hypertable_id)\n{\n\tScanIterator iterator = ts_scan_iterator_create(CONTINUOUS_AGGS_MATERIALIZATION_RANGES,\n\t\t\t\t\t\t\t\t\t\t\t\t\tRowExclusiveLock,\n\t\t\t\t\t\t\t\t\t\t\t\t\tCurrentMemoryContext);\n\n\telog(DEBUG1, \"materialization log delete for hypertable %d\", mat_hypertable_id);\n\tinit_materialization_ranges_scan_by_materialization_id(&iterator, mat_hypertable_id);\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tts_catalog_delete_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\t}\n}\n\nstatic HeapTuple\ncontinuous_agg_formdata_make_tuple(const FormData_continuous_agg *fd, TupleDesc desc)\n{\n\tDatum values[Natts_continuous_agg];\n\tbool nulls[Natts_continuous_agg] = { false };\n\n\tmemset(values, 0, sizeof(Datum) * Natts_continuous_agg);\n\n\tvalues[AttrNumberGetAttrOffset(Anum_continuous_agg_mat_hypertable_id)] =\n\t\tInt32GetDatum(fd->mat_hypertable_id);\n\tvalues[AttrNumberGetAttrOffset(Anum_continuous_agg_raw_hypertable_id)] =\n\t\tInt32GetDatum(fd->raw_hypertable_id);\n\n\tif (fd->parent_mat_hypertable_id == INVALID_HYPERTABLE_ID)\n\t\tnulls[AttrNumberGetAttrOffset(Anum_continuous_agg_parent_mat_hypertable_id)] = true;\n\telse\n\t{\n\t\tvalues[AttrNumberGetAttrOffset(Anum_continuous_agg_parent_mat_hypertable_id)] =\n\t\t\tInt32GetDatum(fd->parent_mat_hypertable_id);\n\t}\n\n\tvalues[AttrNumberGetAttrOffset(Anum_continuous_agg_user_view_schema)] =\n\t\tNameGetDatum(&fd->user_view_schema);\n\tvalues[AttrNumberGetAttrOffset(Anum_continuous_agg_user_view_name)] =\n\t\tNameGetDatum(&fd->user_view_name);\n\n\tvalues[AttrNumberGetAttrOffset(Anum_continuous_agg_partial_view_schema)] =\n\t\tNameGetDatum(&fd->partial_view_schema);\n\tvalues[AttrNumberGetAttrOffset(Anum_continuous_agg_partial_view_name)] =\n\t\tNameGetDatum(&fd->partial_view_name);\n\n\tvalues[AttrNumberGetAttrOffset(Anum_continuous_agg_direct_view_schema)] =\n\t\tNameGetDatum(&fd->direct_view_schema);\n\tvalues[AttrNumberGetAttrOffset(Anum_continuous_agg_direct_view_name)] =\n\t\tNameGetDatum(&fd->direct_view_name);\n\n\tvalues[AttrNumberGetAttrOffset(Anum_continuous_agg_materialize_only)] =\n\t\tBoolGetDatum(fd->materialized_only);\n\n\treturn heap_form_tuple(desc, values, nulls);\n}\n\nstatic void\ncontinuous_agg_formdata_fill(FormData_continuous_agg *fd, const TupleInfo *ti)\n{\n\tbool should_free;\n\tHeapTuple tuple;\n\tDatum values[Natts_continuous_agg];\n\tbool nulls[Natts_continuous_agg] = { false };\n\n\ttuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\theap_deform_tuple(tuple, ts_scanner_get_tupledesc(ti), values, nulls);\n\n\tfd->mat_hypertable_id =\n\t\tDatumGetInt32(values[AttrNumberGetAttrOffset(Anum_continuous_agg_mat_hypertable_id)]);\n\tfd->raw_hypertable_id =\n\t\tDatumGetInt32(values[AttrNumberGetAttrOffset(Anum_continuous_agg_raw_hypertable_id)]);\n\n\tif (nulls[AttrNumberGetAttrOffset(Anum_continuous_agg_parent_mat_hypertable_id)])\n\t\tfd->parent_mat_hypertable_id = INVALID_HYPERTABLE_ID;\n\telse\n\t\tfd->parent_mat_hypertable_id = DatumGetInt32(\n\t\t\tvalues[AttrNumberGetAttrOffset(Anum_continuous_agg_parent_mat_hypertable_id)]);\n\n\tnamestrcpy(&fd->user_view_schema,\n\t\t\t   DatumGetCString(\n\t\t\t\t   values[AttrNumberGetAttrOffset(Anum_continuous_agg_user_view_schema)]));\n\tnamestrcpy(&fd->user_view_name,\n\t\t\t   DatumGetCString(\n\t\t\t\t   values[AttrNumberGetAttrOffset(Anum_continuous_agg_user_view_name)]));\n\n\tnamestrcpy(&fd->partial_view_schema,\n\t\t\t   DatumGetCString(\n\t\t\t\t   values[AttrNumberGetAttrOffset(Anum_continuous_agg_partial_view_schema)]));\n\tnamestrcpy(&fd->partial_view_name,\n\t\t\t   DatumGetCString(\n\t\t\t\t   values[AttrNumberGetAttrOffset(Anum_continuous_agg_partial_view_name)]));\n\n\tnamestrcpy(&fd->direct_view_schema,\n\t\t\t   DatumGetCString(\n\t\t\t\t   values[AttrNumberGetAttrOffset(Anum_continuous_agg_direct_view_schema)]));\n\tnamestrcpy(&fd->direct_view_name,\n\t\t\t   DatumGetCString(\n\t\t\t\t   values[AttrNumberGetAttrOffset(Anum_continuous_agg_direct_view_name)]));\n\n\tfd->materialized_only =\n\t\tDatumGetBool(values[AttrNumberGetAttrOffset(Anum_continuous_agg_materialize_only)]);\n\tif (should_free)\n\t\theap_freetuple(tuple);\n}\n\n/*\n * Fill the fields of a integer based bucketing function\n */\nstatic void\ncagg_fill_bucket_function_integer_based(ContinuousAggBucketFunction *bf, bool *isnull,\n\t\t\t\t\t\t\t\t\t\tDatum *values)\n{\n\t/* Bucket width */\n\tAssert(!isnull[AttrNumberGetAttrOffset(Anum_continuous_aggs_bucket_function_bucket_width)]);\n\tconst char *bucket_width_str = TextDatumGetCString(\n\t\tvalues[AttrNumberGetAttrOffset(Anum_continuous_aggs_bucket_function_bucket_width)]);\n\tAssert(strlen(bucket_width_str) > 0);\n\tbf->bucket_integer_width = pg_strtoint64(bucket_width_str);\n\n\t/* Bucket origin cannot be used with integer based buckets */\n\tAssert(isnull[AttrNumberGetAttrOffset(Anum_continuous_aggs_bucket_function_bucket_origin)] ==\n\t\t   true);\n\n\t/* Bucket offset */\n\tif (!isnull[AttrNumberGetAttrOffset(Anum_continuous_aggs_bucket_function_bucket_offset)])\n\t{\n\t\tconst char *offset_str = TextDatumGetCString(\n\t\t\tvalues[AttrNumberGetAttrOffset(Anum_continuous_aggs_bucket_function_bucket_offset)]);\n\t\tbf->bucket_integer_offset = pg_strtoint64(offset_str);\n\t}\n\n\t/* Timezones cannot be used with integer based buckets */\n\tAssert(isnull[AttrNumberGetAttrOffset(Anum_continuous_aggs_bucket_function_bucket_timezone)] ==\n\t\t   true);\n}\n\n/*\n * Fill the fields of a time based bucketing function\n */\nstatic void\ncagg_fill_bucket_function_time_based(ContinuousAggBucketFunction *bf, bool *isnull, Datum *values)\n{\n\t/*\n\t * bucket_width\n\t *\n\t * The value is stored as TEXT since we have to store the interval value of time\n\t * buckets and also the number value of integer based buckets.\n\t */\n\tAssert(!isnull[AttrNumberGetAttrOffset(Anum_continuous_aggs_bucket_function_bucket_width)]);\n\tconst char *bucket_width_str = TextDatumGetCString(\n\t\tvalues[AttrNumberGetAttrOffset(Anum_continuous_aggs_bucket_function_bucket_width)]);\n\tAssert(strlen(bucket_width_str) > 0);\n\tbf->bucket_time_width = DatumGetIntervalP(\n\t\tDirectFunctionCall3(interval_in, CStringGetDatum(bucket_width_str), InvalidOid, -1));\n\n\t/* Bucket origin */\n\tif (!isnull[AttrNumberGetAttrOffset(Anum_continuous_aggs_bucket_function_bucket_origin)])\n\t{\n\t\tconst char *origin_str = TextDatumGetCString(\n\t\t\tvalues[AttrNumberGetAttrOffset(Anum_continuous_aggs_bucket_function_bucket_origin)]);\n\t\tbf->bucket_time_origin = DatumGetTimestamp(DirectFunctionCall3(timestamptz_in,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   CStringGetDatum(origin_str),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   ObjectIdGetDatum(InvalidOid),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   Int32GetDatum(-1)));\n\t}\n\telse\n\t{\n\t\tTIMESTAMP_NOBEGIN(bf->bucket_time_origin);\n\t}\n\n\t/* Bucket offset */\n\tif (!isnull[AttrNumberGetAttrOffset(Anum_continuous_aggs_bucket_function_bucket_offset)])\n\t{\n\t\tconst char *offset_str = TextDatumGetCString(\n\t\t\tvalues[AttrNumberGetAttrOffset(Anum_continuous_aggs_bucket_function_bucket_offset)]);\n\t\tbf->bucket_time_offset = DatumGetIntervalP(\n\t\t\tDirectFunctionCall3(interval_in, CStringGetDatum(offset_str), InvalidOid, -1));\n\t}\n\n\t/* Bucket timezone */\n\tif (!isnull[AttrNumberGetAttrOffset(Anum_continuous_aggs_bucket_function_bucket_timezone)])\n\t{\n\t\tbf->bucket_time_timezone = TextDatumGetCString(\n\t\t\tvalues[AttrNumberGetAttrOffset(Anum_continuous_aggs_bucket_function_bucket_timezone)]);\n\t}\n}\n\nstatic void\ncontinuous_agg_fill_bucket_function(int32 mat_hypertable_id, ContinuousAggBucketFunction *bf)\n{\n\tScanIterator iterator;\n\tint count = 0;\n\n\titerator = ts_scan_iterator_create(CONTINUOUS_AGGS_BUCKET_FUNCTION,\n\t\t\t\t\t\t\t\t\t   AccessShareLock,\n\t\t\t\t\t\t\t\t\t   CurrentMemoryContext);\n\tinit_scan_cagg_bucket_function_by_mat_hypertable_id(&iterator, mat_hypertable_id);\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tDatum values[Natts_continuous_aggs_bucket_function];\n\t\tbool isnull[Natts_continuous_aggs_bucket_function];\n\t\tbool should_free;\n\n\t\tHeapTuple tuple = ts_scan_iterator_fetch_heap_tuple(&iterator, false, &should_free);\n\n\t\t/*\n\t\t * Our usual GETSTRUCT() approach doesn't work when TEXT fields are involved,\n\t\t * thus a more robust approach with heap_deform_tuple() is used here.\n\t\t */\n\t\theap_deform_tuple(tuple, ts_scan_iterator_tupledesc(&iterator), values, isnull);\n\n\t\t/* Bucket function */\n\t\tAssert(!isnull[AttrNumberGetAttrOffset(Anum_continuous_aggs_bucket_function_function)]);\n\t\tconst char *bucket_function_str = TextDatumGetCString(\n\t\t\tvalues[AttrNumberGetAttrOffset(Anum_continuous_aggs_bucket_function_function)]);\n\t\tbf->bucket_function = DatumGetObjectId(\n\t\t\tDirectFunctionCall1(regprocedurein, CStringGetDatum(bucket_function_str)));\n\n\t\tbf->bucket_time_based = ts_continuous_agg_bucket_on_interval(bf->bucket_function);\n\n\t\tif (bf->bucket_time_based)\n\t\t{\n\t\t\tcagg_fill_bucket_function_time_based(bf, isnull, values);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tcagg_fill_bucket_function_integer_based(bf, isnull, values);\n\t\t}\n\n\t\t/* Bucket fixed width */\n\t\tAssert(!isnull[AttrNumberGetAttrOffset(\n\t\t\tAnum_continuous_aggs_bucket_function_bucket_fixed_width)]);\n\t\tbf->bucket_fixed_interval = DatumGetBool(values[AttrNumberGetAttrOffset(\n\t\t\tAnum_continuous_aggs_bucket_function_bucket_fixed_width)]);\n\n\t\tcount++;\n\n\t\tif (should_free)\n\t\t\theap_freetuple(tuple);\n\t}\n\n\t/*\n\t * This function should never be called unless we know that the corresponding\n\t * cagg exists and uses a variable-sized bucket. There should be exactly one\n\t * entry in .continuous_aggs_bucket_function catalog table for such a cagg.\n\t */\n\tif (count != 1)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t errmsg(\"invalid or missing information about the bucketing function for cagg\"),\n\t\t\t\t errdetail(\"%d\", mat_hypertable_id)));\n\t}\n}\n\nstatic void\ncontinuous_agg_init(ContinuousAgg *cagg, const Form_continuous_agg fd)\n{\n\tOid nspid = get_namespace_oid(NameStr(fd->user_view_schema), false);\n\tHypertable *cagg_ht = ts_hypertable_get_by_id(fd->mat_hypertable_id);\n\tif (!cagg_ht)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t errmsg(\"continuous aggregate hypertable with ID %d does not exist\",\n\t\t\t\t\t\tfd->mat_hypertable_id)));\n\tconst Dimension *time_dim;\n\ttime_dim = hyperspace_get_open_dimension(cagg_ht->space, 0);\n\tif (!time_dim)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"continuous aggregate hypertable with ID %d has no open dimension\",\n\t\t\t\t\t\tfd->mat_hypertable_id)));\n\tcagg->partition_type = ts_dimension_get_partition_type(time_dim);\n\tcagg->relid = get_relname_relid(NameStr(fd->user_view_name), nspid);\n\tmemcpy(&cagg->data, fd, sizeof(cagg->data));\n\n\tAssert(OidIsValid(cagg->relid));\n\tAssert(OidIsValid(cagg->partition_type));\n\n\tcagg->bucket_function = palloc0(sizeof(ContinuousAggBucketFunction));\n\tcontinuous_agg_fill_bucket_function(cagg->data.mat_hypertable_id, cagg->bucket_function);\n}\n\nTSDLLEXPORT ContinuousAggInfo\nts_continuous_agg_get_all_caggs_info(int32 raw_hypertable_id)\n{\n\tContinuousAggInfo all_caggs_info;\n\n\tList *caggs = ts_continuous_aggs_find_by_raw_table_id(raw_hypertable_id);\n\tListCell *lc;\n\n\tall_caggs_info.mat_hypertable_ids = NIL;\n\tall_caggs_info.bucket_functions = NIL;\n\n\tAssert(list_length(caggs) > 0);\n\n\tforeach (lc, caggs)\n\t{\n\t\tContinuousAgg *cagg = lfirst(lc);\n\n\t\tall_caggs_info.bucket_functions =\n\t\t\tlappend(all_caggs_info.bucket_functions, cagg->bucket_function);\n\n\t\tall_caggs_info.mat_hypertable_ids =\n\t\t\tlappend_int(all_caggs_info.mat_hypertable_ids, cagg->data.mat_hypertable_id);\n\t}\n\treturn all_caggs_info;\n}\n\nTSDLLEXPORT ContinuousAggHypertableStatus\nts_continuous_agg_hypertable_status(int32 hypertable_id)\n{\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(CONTINUOUS_AGG, AccessShareLock, CurrentMemoryContext);\n\tContinuousAggHypertableStatus status = HypertableIsNotContinuousAgg;\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tFormData_continuous_agg data;\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\n\t\tcontinuous_agg_formdata_fill(&data, ti);\n\n\t\tif (data.raw_hypertable_id == hypertable_id)\n\t\t\tstatus |= HypertableIsRawTable;\n\t\tif (data.mat_hypertable_id == hypertable_id)\n\t\t\tstatus |= HypertableIsMaterialization;\n\n\t\tif (status == HypertableIsMaterializationAndRaw)\n\t\t{\n\t\t\tts_scan_iterator_close(&iterator);\n\t\t\treturn status;\n\t\t}\n\t}\n\n\treturn status;\n}\n\nTSDLLEXPORT List *\nts_continuous_aggs_find_by_raw_table_id(int32 raw_hypertable_id)\n{\n\tList *continuous_aggs = NIL;\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(CONTINUOUS_AGG, AccessShareLock, CurrentMemoryContext);\n\n\tinit_scan_by_raw_hypertable_id(&iterator, raw_hypertable_id);\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tContinuousAgg *ca;\n\t\tFormData_continuous_agg data;\n\t\tMemoryContext oldmctx;\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\n\t\tcontinuous_agg_formdata_fill(&data, ti);\n\n\t\toldmctx = MemoryContextSwitchTo(ts_scan_iterator_get_result_memory_context(&iterator));\n\t\tca = palloc0(sizeof(*ca));\n\t\tcontinuous_agg_init(ca, &data);\n\t\tcontinuous_aggs = lappend(continuous_aggs, ca);\n\t\tMemoryContextSwitchTo(oldmctx);\n\t}\n\n\treturn continuous_aggs;\n}\n\n/* Find a continuous aggregate by the materialized hypertable id */\nContinuousAgg *\nts_continuous_agg_find_by_mat_hypertable_id(int32 mat_hypertable_id, bool missing_ok)\n{\n\tContinuousAgg *ca = NULL;\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(CONTINUOUS_AGG, RowExclusiveLock, CurrentMemoryContext);\n\n\tinit_scan_by_mat_hypertable_id(&iterator, mat_hypertable_id);\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tFormData_continuous_agg form;\n\n\t\tcontinuous_agg_formdata_fill(&form, ti);\n\n\t\t/* Note that this scan can only match at most once, so we assert on\n\t\t * `ca` here. */\n\t\tAssert(ca == NULL);\n\t\tca = ts_scan_iterator_alloc_result(&iterator, sizeof(*ca));\n\t\tcontinuous_agg_init(ca, &form);\n\n\t\tAssert(ca && ca->data.mat_hypertable_id == mat_hypertable_id);\n\t}\n\tts_scan_iterator_close(&iterator);\n\n\tif (ca == NULL && !missing_ok)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid materialized hypertable ID: %d\", mat_hypertable_id)));\n\t}\n\n\treturn ca;\n}\n\nstatic bool\ncontinuous_agg_find_by_name(const char *schema, const char *name, ContinuousAggViewType type,\n\t\t\t\t\t\t\tFormData_continuous_agg *fd)\n{\n\tScanIterator iterator;\n\tAttrNumber view_name_attrnum = 0;\n\tAttrNumber schema_name_attrnum = 0;\n\tint count = 0;\n\n\tAssert(schema);\n\tAssert(name);\n\n\tswitch (type)\n\t{\n\t\tcase ContinuousAggUserView:\n\t\t\tschema_name_attrnum = Anum_continuous_agg_user_view_schema;\n\t\t\tview_name_attrnum = Anum_continuous_agg_user_view_name;\n\t\t\tbreak;\n\t\tcase ContinuousAggPartialView:\n\t\t\tschema_name_attrnum = Anum_continuous_agg_partial_view_schema;\n\t\t\tview_name_attrnum = Anum_continuous_agg_partial_view_name;\n\t\t\tbreak;\n\t\tcase ContinuousAggDirectView:\n\t\t\tschema_name_attrnum = Anum_continuous_agg_direct_view_schema;\n\t\t\tview_name_attrnum = Anum_continuous_agg_direct_view_name;\n\t\t\tbreak;\n\t\tcase ContinuousAggAnyView:\n\t\t\tbreak;\n\t}\n\n\titerator = ts_scan_iterator_create(CONTINUOUS_AGG, AccessShareLock, CurrentMemoryContext);\n\n\tif (type != ContinuousAggAnyView)\n\t{\n\t\tts_scan_iterator_scan_key_init(&iterator,\n\t\t\t\t\t\t\t\t\t   schema_name_attrnum,\n\t\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t\t   F_NAMEEQ,\n\t\t\t\t\t\t\t\t\t   CStringGetDatum(schema));\n\t\tts_scan_iterator_scan_key_init(&iterator,\n\t\t\t\t\t\t\t\t\t   view_name_attrnum,\n\t\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t\t   F_NAMEEQ,\n\t\t\t\t\t\t\t\t\t   CStringGetDatum(name));\n\t}\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tContinuousAggViewType vtype = type;\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tFormData_continuous_agg data;\n\n\t\tcontinuous_agg_formdata_fill(&data, ti);\n\n\t\tif (vtype == ContinuousAggAnyView)\n\t\t\tvtype = ts_continuous_agg_view_type(&data, schema, name);\n\n\t\tif (vtype != ContinuousAggAnyView)\n\t\t{\n\t\t\tmemcpy(fd, &data, sizeof(*fd));\n\t\t\tcount++;\n\t\t}\n\t}\n\n\tAssert(count <= 1);\n\n\treturn count == 1;\n}\n\nContinuousAgg *\nts_continuous_agg_find_by_view_name(const char *schema, const char *name,\n\t\t\t\t\t\t\t\t\tContinuousAggViewType type)\n{\n\tFormData_continuous_agg fd;\n\tContinuousAgg *ca;\n\n\tif (!continuous_agg_find_by_name(schema, name, type, &fd))\n\t\treturn NULL;\n\n\tca = palloc0(sizeof(ContinuousAgg));\n\tcontinuous_agg_init(ca, &fd);\n\n\treturn ca;\n}\n\nContinuousAgg *\nts_continuous_agg_find_userview_name(const char *schema, const char *name)\n{\n\treturn ts_continuous_agg_find_by_view_name(schema, name, ContinuousAggUserView);\n}\n\n/*\n * Find a continuous agg object by the main relid.\n *\n * The relid is the user-facing object ID that represents the continuous\n * aggregate (i.e., the query view's ID).\n */\nContinuousAgg *\nts_continuous_agg_find_by_relid(Oid relid)\n{\n\tconst char *relname = get_rel_name(relid);\n\tconst char *schemaname = get_namespace_name(get_rel_namespace(relid));\n\n\tif (NULL == relname || NULL == schemaname)\n\t\treturn NULL;\n\n\treturn ts_continuous_agg_find_userview_name(schemaname, relname);\n}\n\n/*\n * Find a continuous aggregate by range var.\n */\nContinuousAgg *\nts_continuous_agg_find_by_rv(const RangeVar *rv)\n{\n\tOid relid;\n\tif (rv == NULL)\n\t\treturn NULL;\n\trelid = RangeVarGetRelid(rv, NoLock, true);\n\tif (!OidIsValid(relid))\n\t\treturn NULL;\n\treturn ts_continuous_agg_find_by_relid(relid);\n}\n\nstatic ObjectAddress\nget_and_lock_rel_by_name(const Name schema, const Name name, LOCKMODE mode)\n{\n\tObjectAddress addr;\n\tOid relid = InvalidOid;\n\tOid nspid = get_namespace_oid(NameStr(*schema), true);\n\tif (OidIsValid(nspid))\n\t{\n\t\trelid = get_relname_relid(NameStr(*name), nspid);\n\t\tif (OidIsValid(relid))\n\t\t\tLockRelationOid(relid, mode);\n\t}\n\tObjectAddressSet(addr, RelationRelationId, relid);\n\treturn addr;\n}\n\nstatic ObjectAddress\nget_and_lock_rel_by_hypertable_id(int32 hypertable_id, LOCKMODE mode)\n{\n\tObjectAddress addr;\n\tOid relid = ts_hypertable_id_to_relid(hypertable_id, true);\n\tif (OidIsValid(relid))\n\t\tLockRelationOid(relid, mode);\n\tObjectAddressSet(addr, RelationRelationId, relid);\n\treturn addr;\n}\n\n/*\n * Drops continuous aggs and all related objects.\n *\n * This function is intended to be run by event trigger during CASCADE,\n * which implies that most of the dependent objects potentially could be\n * dropped including associated schema.\n *\n * These objects are:\n *\n * - user view itself\n * - continuous agg catalog entry\n * - partial view\n * - materialization hypertable\n * - trigger on the raw hypertable (hypertable specified in the user view)\n * - copy of the user view query (AKA the direct view)\n *\n * NOTE: The order in which the objects are dropped should be EXACTLY the\n * same as in materialize.c\n *\n * drop_user_view indicates whether to drop the user view.\n *                (should be false if called as part of the drop-user-view callback)\n */\nstatic void\ndrop_continuous_agg(FormData_continuous_agg *cadata, bool drop_user_view)\n{\n\tCatalog *catalog;\n\tScanIterator iterator;\n\tObjectAddress user_view = { 0 };\n\tObjectAddress partial_view = { 0 };\n\tObjectAddress direct_view = { 0 };\n\tObjectAddress raw_hypertable = { 0 };\n\tObjectAddress mat_hypertable = { 0 };\n\tbool raw_hypertable_has_other_caggs;\n\n\t/* Delete the job before taking locks as it kills long-running jobs\n\t * which we would otherwise wait on */\n\tList *jobs = ts_bgw_job_find_by_hypertable_id(cadata->mat_hypertable_id);\n\tListCell *lc;\n\n\tforeach (lc, jobs)\n\t{\n\t\tBgwJob *job = lfirst(lc);\n\t\tts_bgw_job_delete_by_id(job->fd.id);\n\t}\n\n\t/*\n\t * Lock objects.\n\t *\n\t * Following objects might be already dropped in case of CASCADE\n\t * drop including the associated schema object.\n\t *\n\t * NOTE: the lock order matters, see tsl/src/materialization.c.\n\t * Perform all locking upfront.\n\t *\n\t * AccessExclusiveLock is needed to drop triggers and also prevent\n\t * concurrent DML commands.\n\t *\n\t * It is needed also in the case that we are using WAL-based invalidation\n\t * collection since we want to serialize create and drop of continuous\n\t * aggregates.\n\t */\n\tif (drop_user_view)\n\t\tuser_view = get_and_lock_rel_by_name(&cadata->user_view_schema,\n\t\t\t\t\t\t\t\t\t\t\t &cadata->user_view_name,\n\t\t\t\t\t\t\t\t\t\t\t AccessExclusiveLock);\n\traw_hypertable =\n\t\tget_and_lock_rel_by_hypertable_id(cadata->raw_hypertable_id, AccessExclusiveLock);\n\tmat_hypertable =\n\t\tget_and_lock_rel_by_hypertable_id(cadata->mat_hypertable_id, AccessExclusiveLock);\n\n\t/* Lock catalogs */\n\tcatalog = ts_catalog_get();\n\tLockRelationOid(catalog_get_table_id(catalog, BGW_JOB), RowExclusiveLock);\n\tLockRelationOid(catalog_get_table_id(catalog, CONTINUOUS_AGG), RowExclusiveLock);\n\tLockRelationOid(catalog_get_table_id(catalog, CONTINUOUS_AGGS_WATERMARK), RowExclusiveLock);\n\n\traw_hypertable_has_other_caggs =\n\t\tOidIsValid(raw_hypertable.objectId) &&\n\t\tnumber_of_continuous_aggs_attached(cadata->raw_hypertable_id) > 1;\n\n\tif (!raw_hypertable_has_other_caggs)\n\t{\n\t\tLockRelationOid(catalog_get_table_id(catalog, CONTINUOUS_AGGS_HYPERTABLE_INVALIDATION_LOG),\n\t\t\t\t\t\tRowExclusiveLock);\n\t\tLockRelationOid(catalog_get_table_id(catalog, CONTINUOUS_AGGS_INVALIDATION_THRESHOLD),\n\t\t\t\t\t\tRowExclusiveLock);\n\t}\n\n\t/*\n\t * Following objects might be already dropped in case of CASCADE\n\t * drop including the associated schema object.\n\t */\n\tpartial_view = get_and_lock_rel_by_name(&cadata->partial_view_schema,\n\t\t\t\t\t\t\t\t\t\t\t&cadata->partial_view_name,\n\t\t\t\t\t\t\t\t\t\t\tAccessExclusiveLock);\n\n\tdirect_view = get_and_lock_rel_by_name(&cadata->direct_view_schema,\n\t\t\t\t\t\t\t\t\t\t   &cadata->direct_view_name,\n\t\t\t\t\t\t\t\t\t\t   AccessExclusiveLock);\n\n\t/* Delete catalog entry */\n\titerator = ts_scan_iterator_create(CONTINUOUS_AGG, RowExclusiveLock, CurrentMemoryContext);\n\tinit_scan_by_mat_hypertable_id(&iterator, cadata->mat_hypertable_id);\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tFormData_continuous_agg form;\n\n\t\tcontinuous_agg_formdata_fill(&form, ti);\n\n\t\tts_catalog_delete_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\n\t\t/* Delete all related rows */\n\t\tif (!raw_hypertable_has_other_caggs)\n\t\t{\n\t\t\thypertable_invalidation_log_delete(form.raw_hypertable_id);\n\t\t}\n\n\t\tts_materialization_invalidation_log_delete(form.mat_hypertable_id);\n\t\tts_materialization_ranges_delete(form.mat_hypertable_id);\n\n\t\tif (!raw_hypertable_has_other_caggs)\n\t\t{\n\t\t\tinvalidation_threshold_delete(form.raw_hypertable_id);\n\t\t}\n\n\t\t/* Delete watermark */\n\t\tts_cagg_watermark_delete_by_mat_hypertable_id(form.mat_hypertable_id);\n\t}\n\n\tcagg_bucket_function_delete(cadata->mat_hypertable_id);\n\n\t/* Perform actual deletions now */\n\tif (OidIsValid(user_view.objectId))\n\t\tperformDeletion(&user_view, DROP_RESTRICT, 0);\n\n\tif (OidIsValid(mat_hypertable.objectId))\n\t{\n\t\tperformDeletion(&mat_hypertable, DROP_CASCADE, 0);\n\t\tts_compression_settings_delete(mat_hypertable.objectId);\n\t\tts_hypertable_delete_by_id(cadata->mat_hypertable_id);\n\t}\n\n\tif (OidIsValid(partial_view.objectId))\n\t\tperformDeletion(&partial_view, DROP_RESTRICT, 0);\n\n\tif (OidIsValid(direct_view.objectId))\n\t\tperformDeletion(&direct_view, DROP_RESTRICT, 0);\n}\n\n/*\n * This is a called when a hypertable gets dropped.\n *\n * If the hypertable is a raw hypertable for a continuous agg,\n * drop the continuous agg.\n *\n * If the hypertable is a materialization hypertable, error out\n * and force the user to drop the continuous agg instead.\n */\nvoid\nts_continuous_agg_drop_hypertable_callback(int32 hypertable_id)\n{\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(CONTINUOUS_AGG, AccessShareLock, CurrentMemoryContext);\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tFormData_continuous_agg data;\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\n\t\tcontinuous_agg_formdata_fill(&data, ti);\n\n\t\tif (data.raw_hypertable_id == hypertable_id)\n\t\t\tdrop_continuous_agg(&data, true);\n\n\t\tif (data.mat_hypertable_id == hypertable_id)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),\n\t\t\t\t\t errmsg(\"cannot drop the materialized table because it is required by a \"\n\t\t\t\t\t\t\t\"continuous aggregate\")));\n\t}\n}\n\n/* Block dropping the partial and direct view if the continuous aggregate still exists */\nstatic void\ndrop_internal_view(const FormData_continuous_agg *fd)\n{\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(CONTINUOUS_AGG, AccessShareLock, CurrentMemoryContext);\n\tint count = 0;\n\tinit_scan_by_mat_hypertable_id(&iterator, fd->mat_hypertable_id);\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tts_catalog_delete_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\t\tcount++;\n\t}\n\tif (count > 0)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),\n\t\t\t\t errmsg(\n\t\t\t\t\t \"cannot drop the partial/direct view because it is required by a continuous \"\n\t\t\t\t\t \"aggregate\")));\n}\n\n/* This gets called when a view gets dropped. */\nstatic void\ncontinuous_agg_drop_view_callback(FormData_continuous_agg *fd, const char *schema, const char *name)\n{\n\tContinuousAggViewType vtyp;\n\tvtyp = ts_continuous_agg_view_type(fd, schema, name);\n\tswitch (vtyp)\n\t{\n\t\tcase ContinuousAggUserView:\n\t\t\tdrop_continuous_agg(fd, false /* The user view has already been dropped */);\n\t\t\tbreak;\n\t\tcase ContinuousAggPartialView:\n\t\tcase ContinuousAggDirectView:\n\t\t\tdrop_internal_view(fd);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\telog(ERROR, \"unknown continuous aggregate view type\");\n\t}\n}\n\nbool\nts_continuous_agg_drop(const char *view_schema, const char *view_name)\n{\n\tFormData_continuous_agg fd;\n\tbool found = continuous_agg_find_by_name(view_schema, view_name, ContinuousAggAnyView, &fd);\n\n\tif (found)\n\t\tcontinuous_agg_drop_view_callback(&fd, view_schema, view_name);\n\n\treturn found;\n}\n\nstatic inline bool\nts_continuous_agg_is_user_view_schema(FormData_continuous_agg *data, const char *schema)\n{\n\treturn CHECK_NAME_MATCH(&data->user_view_schema, schema);\n}\n\nstatic inline bool\nts_continuous_agg_is_partial_view_schema(FormData_continuous_agg *data, const char *schema)\n{\n\treturn CHECK_NAME_MATCH(&data->partial_view_schema, schema);\n}\n\nstatic inline bool\nts_continuous_agg_is_direct_view_schema(FormData_continuous_agg *data, const char *schema)\n{\n\treturn CHECK_NAME_MATCH(&data->direct_view_schema, schema);\n}\n\nContinuousAggViewType\nts_continuous_agg_view_type(FormData_continuous_agg *data, const char *schema, const char *name)\n{\n\tif (CHECK_NAME_MATCH(&data->user_view_schema, schema) &&\n\t\tCHECK_NAME_MATCH(&data->user_view_name, name))\n\t\treturn ContinuousAggUserView;\n\telse if (CHECK_NAME_MATCH(&data->partial_view_schema, schema) &&\n\t\t\t CHECK_NAME_MATCH(&data->partial_view_name, name))\n\t\treturn ContinuousAggPartialView;\n\telse if (CHECK_NAME_MATCH(&data->direct_view_schema, schema) &&\n\t\t\t CHECK_NAME_MATCH(&data->direct_view_name, name))\n\t\treturn ContinuousAggDirectView;\n\telse\n\t\treturn ContinuousAggAnyView;\n}\n\ntypedef struct CaggRenameCtx\n{\n\tconst char *old_schema;\n\tconst char *old_name;\n\tconst char *new_schema;\n\tconst char *new_name;\n\tObjectType *object_type;\n\tvoid (*process_rename)(FormData_continuous_agg *form, bool *do_update, void *data);\n} CaggRenameCtx;\n\nstatic void\ncontinuous_agg_rename_process_rename_schema(FormData_continuous_agg *form, bool *do_update,\n\t\t\t\t\t\t\t\t\t\t\tvoid *data)\n{\n\tCaggRenameCtx *ctx = (CaggRenameCtx *) data;\n\n\tif (ts_continuous_agg_is_user_view_schema(form, ctx->old_schema))\n\t{\n\t\tnamestrcpy(&form->user_view_schema, ctx->new_schema);\n\t\t*do_update = true;\n\t}\n\n\tif (ts_continuous_agg_is_partial_view_schema(form, ctx->old_schema))\n\t{\n\t\tnamestrcpy(&form->partial_view_schema, ctx->new_schema);\n\t\t*do_update = true;\n\t}\n\n\tif (ts_continuous_agg_is_direct_view_schema(form, ctx->old_schema))\n\t{\n\t\tnamestrcpy(&form->direct_view_schema, ctx->new_schema);\n\t\t*do_update = true;\n\t}\n}\n\nstatic void\ncontinuous_agg_rename_process_rename_view(FormData_continuous_agg *form, bool *do_update,\n\t\t\t\t\t\t\t\t\t\t  void *data)\n{\n\tCaggRenameCtx *ctx = (CaggRenameCtx *) data;\n\tContinuousAggViewType vtyp;\n\n\tvtyp = ts_continuous_agg_view_type(form, ctx->old_schema, ctx->old_name);\n\n\tswitch (vtyp)\n\t{\n\t\tcase ContinuousAggUserView:\n\t\t{\n\t\t\tif (*ctx->object_type == OBJECT_VIEW)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t errmsg(\"cannot alter continuous aggregate using ALTER VIEW\"),\n\t\t\t\t\t\t errhint(\"Use ALTER MATERIALIZED VIEW to alter a continuous aggregate.\")));\n\n\t\t\tAssert(*ctx->object_type == OBJECT_MATVIEW);\n\t\t\t*ctx->object_type = OBJECT_VIEW;\n\n\t\t\tnamestrcpy(&form->user_view_schema, ctx->new_schema);\n\t\t\tnamestrcpy(&form->user_view_name, ctx->new_name);\n\t\t\t*do_update = true;\n\t\t\tbreak;\n\t\t}\n\t\tcase ContinuousAggPartialView:\n\t\t{\n\t\t\tnamestrcpy(&form->partial_view_schema, ctx->new_schema);\n\t\t\tnamestrcpy(&form->partial_view_name, ctx->new_name);\n\t\t\t*do_update = true;\n\t\t\tbreak;\n\t\t}\n\t\tcase ContinuousAggDirectView:\n\t\t{\n\t\t\tnamestrcpy(&form->direct_view_schema, ctx->new_schema);\n\t\t\tnamestrcpy(&form->direct_view_name, ctx->new_name);\n\t\t\t*do_update = true;\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tbreak;\n\t}\n}\n\nstatic ScanTupleResult\ncontinuous_agg_rename(TupleInfo *ti, void *data)\n{\n\tCaggRenameCtx *ctx = (CaggRenameCtx *) data;\n\tFormData_continuous_agg form;\n\tbool do_update = false;\n\tCatalogSecurityContext sec_ctx;\n\n\tcontinuous_agg_formdata_fill(&form, ti);\n\n\tctx->process_rename(&form, &do_update, (void *) ctx);\n\n\tif (do_update)\n\t{\n\t\tHeapTuple new_tuple =\n\t\t\tcontinuous_agg_formdata_make_tuple(&form, ts_scanner_get_tupledesc(ti));\n\t\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\t\tts_catalog_update_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti), new_tuple);\n\t\tts_catalog_restore_user(&sec_ctx);\n\t\theap_freetuple(new_tuple);\n\t}\n\n\treturn SCAN_CONTINUE;\n}\n\nvoid\nts_continuous_agg_rename_schema_name(const char *old_schema, const char *new_schema)\n{\n\tCaggRenameCtx cagg_rename_ctx = {\n\t\t.old_schema = old_schema,\n\t\t.new_schema = new_schema,\n\t\t.process_rename = continuous_agg_rename_process_rename_schema,\n\t};\n\n\tCatalog *catalog = ts_catalog_get();\n\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, CONTINUOUS_AGG),\n\t\t.index = InvalidOid,\n\t\t.tuple_found = continuous_agg_rename,\n\t\t.data = &cagg_rename_ctx,\n\t\t.lockmode = RowExclusiveLock,\n\t\t.scandirection = ForwardScanDirection,\n\t};\n\n\tts_scanner_scan(&scanctx);\n}\n\nvoid\nts_continuous_agg_rename_view(const char *old_schema, const char *old_name, const char *new_schema,\n\t\t\t\t\t\t\t  const char *new_name, ObjectType *object_type)\n{\n\tCaggRenameCtx cagg_rename_ctx = {\n\t\t.old_schema = old_schema,\n\t\t.old_name = old_name,\n\t\t.new_schema = new_schema,\n\t\t.new_name = new_name,\n\t\t.object_type = object_type,\n\t\t.process_rename = continuous_agg_rename_process_rename_view,\n\t};\n\n\tCatalog *catalog = ts_catalog_get();\n\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, CONTINUOUS_AGG),\n\t\t.index = InvalidOid,\n\t\t.tuple_found = continuous_agg_rename,\n\t\t.data = &cagg_rename_ctx,\n\t\t.lockmode = RowExclusiveLock,\n\t\t.scandirection = ForwardScanDirection,\n\t};\n\n\tts_scanner_scan(&scanctx);\n}\n\nstatic int32\nfind_raw_hypertable_for_materialization(int32 mat_hypertable_id)\n{\n\tPG_USED_FOR_ASSERTS_ONLY short count = 0;\n\tint32 htid = INVALID_HYPERTABLE_ID;\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(CONTINUOUS_AGG, RowExclusiveLock, CurrentMemoryContext);\n\n\tinit_scan_by_mat_hypertable_id(&iterator, mat_hypertable_id);\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tbool isnull;\n\t\tDatum datum = slot_getattr(ts_scan_iterator_slot(&iterator),\n\t\t\t\t\t\t\t\t   Anum_continuous_agg_raw_hypertable_id,\n\t\t\t\t\t\t\t\t   &isnull);\n\n\t\tAssert(!isnull);\n\t\thtid = DatumGetInt32(datum);\n\t\tcount++;\n\t}\n\tAssert(count <= 1);\n\tts_scan_iterator_close(&iterator);\n\treturn htid;\n}\n\n/* Continuous aggregate materialization hypertables inherit integer_now func\n * from the raw hypertable (unless it was explicitly reset for cont. aggregate.\n * Walk the materialization hypertable ->raw hypertable tree till\n * we find a hypertable that has integer_now_func set.\n */\nTSDLLEXPORT const Dimension *\nts_continuous_agg_find_integer_now_func_by_materialization_id(int32 mat_htid)\n{\n\tint32 raw_htid = mat_htid;\n\tconst Dimension *par_dim = NULL;\n\twhile (raw_htid != INVALID_HYPERTABLE_ID)\n\t{\n\t\tHypertable *raw_ht = ts_hypertable_get_by_id(raw_htid);\n\t\tconst Dimension *open_dim = hyperspace_get_open_dimension(raw_ht->space, 0);\n\t\tif (strlen(NameStr(open_dim->fd.integer_now_func)) != 0 &&\n\t\t\tstrlen(NameStr(open_dim->fd.integer_now_func_schema)) != 0)\n\t\t{\n\t\t\tpar_dim = open_dim;\n\t\t\tbreak;\n\t\t}\n\t\tmat_htid = raw_htid;\n\t\traw_htid = find_raw_hypertable_for_materialization(mat_htid);\n\t}\n\treturn par_dim;\n}\n\nTSDLLEXPORT void\nts_continuous_agg_invalidate_chunk(Hypertable *ht, Chunk *chunk)\n{\n\tint64 start = ts_chunk_primary_dimension_start(chunk);\n\tint64 end = ts_chunk_primary_dimension_end(chunk);\n\n\tAssert(hyperspace_get_open_dimension(ht->space, 0)->fd.id ==\n\t\t   chunk->cube->slices[0]->fd.dimension_id);\n\tts_cm_functions->continuous_agg_invalidate_raw_ht(ht, start, end);\n}\n\n/* Determines if a bucket is using integer or an interval partitioning */\nbool\nts_continuous_agg_bucket_on_interval(Oid bucket_function)\n{\n\tAssert(OidIsValid(bucket_function));\n\n\tFuncInfo *func_info = ts_func_cache_get(bucket_function);\n\tEnsure(func_info != NULL, \"unable to get function info for Oid %d\", bucket_function);\n\n\t/* The function has to be a currently allowed function or one of the deprecated bucketing\n\t * functions */\n\tAssert(func_info->allowed_in_cagg_definition);\n\n\tOid first_bucket_arg = func_info->arg_types[0];\n\n\treturn first_bucket_arg == INTERVALOID;\n}\n\n/*\n * Calls the desired time bucket function depending on the arguments\n * (i.e., whether it has timezone and offset/origin).\n * This is a common procedure used by ts_compute_* below.\n */\nstatic Datum\ngeneric_time_bucket(const ContinuousAggBucketFunction *bf, Datum timestamp)\n{\n\tFuncInfo *func_info = ts_func_cache_get_bucketing_func(bf->bucket_function);\n\tEnsure(func_info != NULL, \"unable to get bucket function for Oid %d\", bf->bucket_function);\n\n\tbool has_offset = (bf->bucket_time_offset != NULL);\n\n\tif (bf->bucket_time_timezone != NULL)\n\t{\n\t\t/*\n\t\t * Use LOCAL_FCINFO to call ts_timestamptz_timezone_bucket with all\n\t\t * 5 arguments, including origin and offset.\n\t\t */\n\t\tLOCAL_FCINFO(fcinfo, 5);\n\t\tInitFunctionCallInfoData(*fcinfo, NULL, 5, InvalidOid, NULL, NULL);\n\t\tfcinfo->args[0] =\n\t\t\t(NullableDatum){ .value = IntervalPGetDatum(bf->bucket_time_width), .isnull = false };\n\t\tfcinfo->args[1] = (NullableDatum){ .value = timestamp, .isnull = false };\n\t\tfcinfo->args[2] = (NullableDatum){ .value = CStringGetTextDatum(bf->bucket_time_timezone),\n\t\t\t\t\t\t\t\t\t\t   .isnull = false };\n\t\tif (TIMESTAMP_NOT_FINITE(bf->bucket_time_origin))\n\t\t\tfcinfo->args[3] = (NullableDatum){ .value = (Datum) 0, .isnull = true };\n\t\telse\n\t\t\tfcinfo->args[3] = (NullableDatum){ .value = TimestampTzGetDatum(bf->bucket_time_origin),\n\t\t\t\t\t\t\t\t\t\t\t   .isnull = false };\n\t\tif (has_offset)\n\t\t\tfcinfo->args[4] = (NullableDatum){ .value = IntervalPGetDatum(bf->bucket_time_offset),\n\t\t\t\t\t\t\t\t\t\t\t   .isnull = false };\n\t\telse\n\t\t\tfcinfo->args[4] = (NullableDatum){ .value = (Datum) 0, .isnull = true };\n\t\treturn ts_timestamptz_timezone_bucket(fcinfo);\n\t}\n\n\tif (has_offset)\n\t{\n\t\treturn DirectFunctionCall3(ts_timestamp_offset_bucket,\n\t\t\t\t\t\t\t\t   IntervalPGetDatum(bf->bucket_time_width),\n\t\t\t\t\t\t\t\t   timestamp,\n\t\t\t\t\t\t\t\t   IntervalPGetDatum(bf->bucket_time_offset));\n\t}\n\n\tif (TIMESTAMP_NOT_FINITE(bf->bucket_time_origin))\n\t{\n\t\t/* using default origin */\n\t\treturn DirectFunctionCall2(ts_timestamp_bucket,\n\t\t\t\t\t\t\t\t   IntervalPGetDatum(bf->bucket_time_width),\n\t\t\t\t\t\t\t\t   timestamp);\n\t}\n\telse\n\t{\n\t\t/* custom origin specified */\n\t\treturn DirectFunctionCall3(ts_timestamp_bucket,\n\t\t\t\t\t\t\t\t   IntervalPGetDatum(bf->bucket_time_width),\n\t\t\t\t\t\t\t\t   timestamp,\n\t\t\t\t\t\t\t\t   TimestampTzGetDatum(bf->bucket_time_origin));\n\t}\n}\n\n/*\n * Adds one bf->bucket_size interval to the timestamp. This is a common\n * procedure used by ts_compute_* below.\n *\n * If bf->bucket_time_timezone is specified, the math happens in this timezone.\n * Otherwise, it happens in UTC.\n */\nstatic Datum\ngeneric_add_interval(const ContinuousAggBucketFunction *bf, Datum timestamp)\n{\n\tDatum tzname = 0;\n\tbool has_timezone = (bf->bucket_time_timezone != NULL);\n\n\tif (has_timezone)\n\t{\n\t\t/*\n\t\t * Convert 'timestamp' to TIMESTAMP at given timezone.\n\t\t * The code is equal to 'timestamptz AT TIME ZONE tzname'.\n\t\t */\n\t\ttzname = CStringGetTextDatum(bf->bucket_time_timezone);\n\t\ttimestamp = DirectFunctionCall2(timestamptz_zone, tzname, timestamp);\n\t}\n\n\ttimestamp = DirectFunctionCall2(timestamp_pl_interval,\n\t\t\t\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\t\t\t\tIntervalPGetDatum(bf->bucket_time_width));\n\n\tif (has_timezone)\n\t{\n\t\tAssert(tzname != 0);\n\t\ttimestamp = DirectFunctionCall2(timestamp_zone, tzname, timestamp);\n\t}\n\n\treturn timestamp;\n}\n\n/*\n * Computes inscribed refresh_window for variable-sized buckets.\n *\n * The algorithm is simple:\n *\n * end = time_bucket(bucket_size, end)\n *\n * if(start != time_bucket(bucket_size, start))\n *     start = time_bucket(bucket_size, start) + interval bucket_size\n *\n */\nvoid\nts_compute_inscribed_bucketed_refresh_window_variable(int64 *start, int64 *end,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  const ContinuousAggBucketFunction *bf)\n{\n\tDatum start_old, end_old, start_aligned, end_aliged;\n\n\t/*\n\t * It's OK to use TIMESTAMPOID here. Variable-sized buckets can be used\n\t * only for dates, timestamps and timestamptz's. For all these types our\n\t * internal time representation is microseconds relative the UNIX epoch.\n\t * So the results will be correct regardless of the actual type used in\n\t * the CAGG. For more details see ts_internal_to_time_value() implementation.\n\t */\n\tstart_old = ts_internal_to_time_value(*start, TIMESTAMPOID);\n\tend_old = ts_internal_to_time_value(*end, TIMESTAMPOID);\n\n\tstart_aligned = generic_time_bucket(bf, start_old);\n\tend_aliged = generic_time_bucket(bf, end_old);\n\n\tif (DatumGetTimestamp(start_aligned) != DatumGetTimestamp(start_old))\n\t{\n\t\tstart_aligned = generic_add_interval(bf, start_aligned);\n\t}\n\n\t*start = ts_time_value_to_internal(start_aligned, TIMESTAMPOID);\n\t*end = ts_time_value_to_internal(end_aliged, TIMESTAMPOID);\n}\n\n/*\n * Computes circumscribed refresh_window for variable-sized buckets.\n *\n * The algorithm is simple:\n *\n * start = time_bucket(bucket_size, start)\n *\n * if(end != time_bucket(bucket_size, end))\n *     end = time_bucket(bucket_size, end) + interval bucket_size\n */\nvoid\nts_compute_circumscribed_bucketed_refresh_window_variable(int64 *start, int64 *end,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  const ContinuousAggBucketFunction *bf)\n{\n\tDatum start_old, end_old, start_new, end_new;\n\n\t/*\n\t * It's OK to use TIMESTAMPOID here.\n\t * See the comment in ts_compute_inscribed_bucketed_refresh_window_variable()\n\t */\n\tstart_old = ts_internal_to_time_value(*start, TIMESTAMPOID);\n\tend_old = ts_internal_to_time_value(*end, TIMESTAMPOID);\n\tstart_new = generic_time_bucket(bf, start_old);\n\tend_new = generic_time_bucket(bf, end_old);\n\n\t/* Add interval to expand to next bucket if:\n\t * 1. end wasn't at a bucket boundary (end moved during bucketing), OR\n\t * 2. we have a single-point at a bucket boundary (start == end after bucketing) */\n\tif (DatumGetTimestamp(end_new) != DatumGetTimestamp(end_old) ||\n\t\tDatumGetTimestamp(start_new) == DatumGetTimestamp(end_new))\n\t{\n\t\tend_new = generic_add_interval(bf, end_new);\n\t}\n\n\t*start = ts_time_value_to_internal(start_new, TIMESTAMPOID);\n\t*end = ts_time_value_to_internal(end_new, TIMESTAMPOID);\n}\n\n/*\n * Calculates the beginning of the next bucket.\n *\n * The algorithm is just:\n *\n * val = time_bucket(bucket_size, val) + interval bucket_size\n */\nint64\nts_compute_beginning_of_the_next_bucket_variable(int64 timeval,\n\t\t\t\t\t\t\t\t\t\t\t\t const ContinuousAggBucketFunction *bf)\n{\n\tDatum val_new;\n\tDatum val_old;\n\n\t/*\n\t * It's OK to use TIMESTAMPOID here.\n\t * See the comment in ts_compute_inscribed_bucketed_refresh_window_variable()\n\t */\n\tval_old = ts_internal_to_time_value(timeval, TIMESTAMPOID);\n\n\tval_new = generic_time_bucket(bf, val_old);\n\tval_new = generic_add_interval(bf, val_new);\n\treturn ts_time_value_to_internal(val_new, TIMESTAMPOID);\n}\n\nOid\nts_cagg_permissions_check(Oid cagg_oid, Oid userid)\n{\n\tOid ownerid = ts_rel_get_owner(cagg_oid);\n\n\tif (!has_privs_of_role(userid, ownerid))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),\n\t\t\t\t errmsg(\"must be owner of continuous aggregate \\\"%s\\\"\", get_rel_name(cagg_oid))));\n\n\treturn ownerid;\n}\n\nQuery *\nts_continuous_agg_get_query(ContinuousAgg *cagg)\n{\n\tOid cagg_view_oid;\n\tRelation cagg_view_rel;\n\tRuleLock *cagg_view_rules;\n\tRewriteRule *rule;\n\tQuery *cagg_view_query;\n\n\tcagg_view_oid = ts_get_relation_relid(NameStr(cagg->data.partial_view_schema),\n\t\t\t\t\t\t\t\t\t\t  NameStr(cagg->data.partial_view_name),\n\t\t\t\t\t\t\t\t\t\t  false);\n\n\tcagg_view_rel = table_open(cagg_view_oid, AccessShareLock);\n\tcagg_view_rules = cagg_view_rel->rd_rules;\n\tAssert(cagg_view_rules && cagg_view_rules->numLocks == 1);\n\n\trule = cagg_view_rules->rules[0];\n\tif (rule->event != CMD_SELECT)\n\t\tereport(ERROR, (errcode(ERRCODE_TS_UNEXPECTED), errmsg(\"unexpected rule event for view\")));\n\n\tcagg_view_query = (Query *) copyObject(linitial(rule->actions));\n\ttable_close(cagg_view_rel, NoLock);\n\n\treturn cagg_view_query;\n}\n\n/*\n * Get the width of a fixed size bucket\n */\nint64\nts_continuous_agg_fixed_bucket_width(const ContinuousAggBucketFunction *bucket_function)\n{\n\tAssert(bucket_function->bucket_fixed_interval == true);\n\n\tif (bucket_function->bucket_time_based)\n\t{\n\t\tInterval *interval = bucket_function->bucket_time_width;\n\t\tAssert(interval->month == 0);\n\t\treturn interval->time + (interval->day * USECS_PER_DAY);\n\t}\n\telse\n\t{\n\t\treturn bucket_function->bucket_integer_width;\n\t}\n}\n\n/*\n * Get the width of a bucket\n */\nint64\nts_continuous_agg_bucket_width(const ContinuousAggBucketFunction *bucket_function)\n{\n\tint64 bucket_width;\n\n\tif (bucket_function->bucket_fixed_interval == false)\n\t{\n\t\t/*\n\t\t * There are several cases of variable-sized buckets:\n\t\t * 1. Monthly buckets\n\t\t * 2. Buckets with timezones\n\t\t * 3. Cases 1 and 2 at the same time\n\t\t *\n\t\t * For months we simply take 30 days like on interval_to_int64 and\n\t\t * multiply this number by the number of months in the bucket. This\n\t\t * reduces the task to days/hours/minutes scenario.\n\t\t *\n\t\t * Days/hours/minutes case is handled the same way as for fixed-sized\n\t\t * buckets. The refresh window at least two buckets in size is adequate\n\t\t * for such corner cases as DST.\n\t\t */\n\n\t\t/* bucket_function should always be specified for variable-sized buckets */\n\t\tAssert(bucket_function != NULL);\n\t\t/* ... and bucket_function->bucket_time_width too */\n\t\tAssert(bucket_function->bucket_time_width != NULL);\n\n\t\t/* Make a temporary copy of bucket_width */\n\t\tInterval interval = *bucket_function->bucket_time_width;\n\t\tinterval.day += 30 * interval.month;\n\t\tinterval.month = 0;\n\t\tbucket_width = ts_interval_value_to_internal(IntervalPGetDatum(&interval), INTERVALOID);\n\t}\n\telse\n\t{\n\t\tbucket_width = ts_continuous_agg_fixed_bucket_width(bucket_function);\n\t}\n\n\treturn bucket_width;\n}\n"
  },
  {
    "path": "src/ts_catalog/continuous_agg.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <catalog/pg_type.h>\n#include <nodes/parsenodes.h>\n\n#include \"chunk.h\"\n#include \"ts_catalog/catalog.h\"\n\n#include \"compat/compat.h\"\n#include \"with_clause/with_clause_parser.h\"\n\n#define TS_INVALIDATION_SLOT_NAME_MAX (32)\n\n/*switch to ts user for _timescaledb_internal access */\n#define SWITCH_TO_TS_USER(schemaname, newuid, saved_uid, saved_secctx)                             \\\n\tdo                                                                                             \\\n\t{                                                                                              \\\n\t\tif ((schemaname) &&                                                                        \\\n\t\t\tstrncmp(schemaname, INTERNAL_SCHEMA_NAME, strlen(INTERNAL_SCHEMA_NAME)) == 0)          \\\n\t\t\t(newuid) = ts_catalog_database_info_get()->owner_uid;                                  \\\n\t\telse                                                                                       \\\n\t\t\t(newuid) = InvalidOid;                                                                 \\\n\t\tif (OidIsValid((newuid)))                                                                  \\\n\t\t{                                                                                          \\\n\t\t\tGetUserIdAndSecContext(&(saved_uid), &(saved_secctx));                                 \\\n\t\t\tSetUserIdAndSecContext(uid, (saved_secctx) | SECURITY_LOCAL_USERID_CHANGE);            \\\n\t\t}                                                                                          \\\n\t} while (0)\n\n#define RESTORE_USER(newuid, saved_uid, saved_secctx)                                              \\\n\tdo                                                                                             \\\n\t{                                                                                              \\\n\t\tif (OidIsValid((newuid)))                                                                  \\\n\t\t\tSetUserIdAndSecContext(saved_uid, saved_secctx);                                       \\\n\t} while (0);\n\ntypedef enum ContinuousAggViewType\n{\n\tContinuousAggUserView = 0,\n\tContinuousAggPartialView,\n\tContinuousAggDirectView,\n\tContinuousAggAnyView\n} ContinuousAggViewType;\n\ntypedef enum ContinuousAggInvalidateUsing\n{\n\tContinuousAggInvalidateUsingDefault = 0,\n\tContinuousAggInvalidateUsingTrigger,\n\tContinuousAggInvalidateUsingWal,\n} ContinuousAggInvalidateUsing;\n\n/*\n * Information about the bucketing function.\n */\ntypedef struct ContinuousAggBucketFunction\n{\n\t/* Oid of the bucketing function. In the catalog table, the regprocedure is used. This ensures\n\t * that the Oid is mapped to a string when a backup is taken and the string is converted back to\n\t * the Oid when the backup is restored. This way, we can use an Oid in the catalog table even\n\t * when a backup is restored and the Oid may have changed. However, the dependency management in\n\t * PostgreSQL does not track the Oid. If the function is dropped and a new one is created, the\n\t * Oid changes and this value points to a non-existing Oid. This can not happen in real-world\n\t * situations since PostgreSQL protects the bucket_function from deletion until the CAgg is\n\t * defined. */\n\tOid bucket_function;\n\tOid bucket_width_type; /* type of bucket_width */\n\n\t/* Is the interval of the bucket fixed? */\n\tbool bucket_fixed_interval;\n\n\t/* Is the bucket defined on a time datatype ?*/\n\tbool bucket_time_based;\n\n\t/*\n\t * Fields that are used for time based buckets\n\t */\n\tInterval *bucket_time_width;\n\t/*\n\t * Custom origin value stored as UTC timestamp.\n\t * If not specified, stores infinity.\n\t */\n\tTimestampTz bucket_time_origin;\n\t/*\n\t * Bucket offset. Note that we don't support\n\t * both offset and origin at the same time\n\t */\n\tInterval *bucket_time_offset;\n\tchar *bucket_time_timezone;\n\n\t/*\n\t * Fields that are used on integer based buckets\n\t */\n\tint64 bucket_integer_width;\n\tint64 bucket_integer_offset;\n\n} ContinuousAggBucketFunction;\n\ntypedef struct ContinuousAgg\n{\n\tFormData_continuous_agg data;\n\n\t/* Info about the time bucketing function */\n\tContinuousAggBucketFunction *bucket_function;\n\n\t/* Relid of the user-facing view */\n\tOid relid;\n\n\t/* Type of the primary partitioning dimension */\n\tOid partition_type;\n} ContinuousAgg;\n\nstatic inline bool\nContinuousAggIsHierarchical(const ContinuousAgg *cagg)\n{\n\treturn (cagg->data.parent_mat_hypertable_id != INVALID_HYPERTABLE_ID);\n}\n\ntypedef enum ContinuousAggHypertableStatus\n{\n\tHypertableIsNotContinuousAgg = 0,\n\tHypertableIsMaterialization = 1,\n\tHypertableIsRawTable = 2,\n\tHypertableIsMaterializationAndRaw = HypertableIsMaterialization | HypertableIsRawTable,\n} ContinuousAggHypertableStatus;\n\ntypedef struct ContinuousAggInfo\n{\n\t/* (int32) elements */\n\tList *mat_hypertable_ids;\n\t/* (const ContinuousAggBucketFunction *) elements; stores NULL for fixed buckets */\n\tList *bucket_functions;\n} ContinuousAggInfo;\n\ntypedef struct ContinuousAggPolicyOffset\n{\n\tDatum value;\n\tOid type;\n\tbool isnull;\n\tconst char *name;\n} ContinuousAggPolicyOffset;\n\nextern TSDLLEXPORT Oid ts_cagg_permissions_check(Oid cagg_oid, Oid userid);\n\nextern TSDLLEXPORT ContinuousAggInfo ts_continuous_agg_get_all_caggs_info(int32 raw_hypertable_id);\nextern TSDLLEXPORT ContinuousAgg *\nts_continuous_agg_find_by_mat_hypertable_id(int32 mat_hypertable_id, bool missing_ok);\n\nextern TSDLLEXPORT ContinuousAggHypertableStatus\nts_continuous_agg_hypertable_status(int32 hypertable_id);\nextern TSDLLEXPORT List *ts_continuous_aggs_find_by_raw_table_id(int32 raw_hypertable_id);\nextern TSDLLEXPORT ContinuousAgg *ts_continuous_agg_find_by_view_name(const char *schema,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  const char *name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  ContinuousAggViewType type);\nextern TSDLLEXPORT ContinuousAgg *ts_continuous_agg_find_by_relid(Oid relid);\nextern TSDLLEXPORT ContinuousAgg *ts_continuous_agg_find_by_rv(const RangeVar *rv);\n\nextern bool ts_continuous_agg_drop(const char *view_schema, const char *view_name);\nextern void ts_continuous_agg_drop_hypertable_callback(int32 hypertable_id);\n\nextern TSDLLEXPORT ContinuousAggViewType ts_continuous_agg_view_type(FormData_continuous_agg *data,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t const char *schema,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t const char *name);\nextern TSDLLEXPORT void ts_continuous_agg_rename_schema_name(const char *old_schema,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t const char *new_schema);\nextern TSDLLEXPORT void ts_continuous_agg_rename_view(const char *old_schema, const char *old_name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  const char *new_schema, const char *new_name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  ObjectType *object_type);\n\nextern TSDLLEXPORT const Dimension *\nts_continuous_agg_find_integer_now_func_by_materialization_id(int32 mat_htid);\nextern ContinuousAgg *ts_continuous_agg_find_userview_name(const char *schema, const char *name);\n\nextern TSDLLEXPORT void ts_continuous_agg_invalidate_chunk(Hypertable *ht, Chunk *chunk);\n\nextern TSDLLEXPORT bool ts_continuous_agg_bucket_on_interval(Oid bucket_function);\n\nextern TSDLLEXPORT void\nts_compute_inscribed_bucketed_refresh_window_variable(int64 *start, int64 *end,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  const ContinuousAggBucketFunction *bf);\nextern TSDLLEXPORT void\nts_compute_circumscribed_bucketed_refresh_window_variable(int64 *start, int64 *end,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  const ContinuousAggBucketFunction *bf);\nextern TSDLLEXPORT int64 ts_compute_beginning_of_the_next_bucket_variable(\n\tint64 timeval, const ContinuousAggBucketFunction *bf);\n\nextern TSDLLEXPORT Query *ts_continuous_agg_get_query(ContinuousAgg *cagg);\n\nextern TSDLLEXPORT int64\nts_continuous_agg_fixed_bucket_width(const ContinuousAggBucketFunction *bucket_function);\nextern TSDLLEXPORT int64\nts_continuous_agg_bucket_width(const ContinuousAggBucketFunction *bucket_function);\nextern TSDLLEXPORT void ts_get_invalidation_replication_slot_name(char *slotname, Size szslot);\n"
  },
  {
    "path": "src/ts_catalog/continuous_aggs_watermark.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n/*\n * This file handles continuous aggs watermark functions.\n */\n\n#include <postgres.h>\n#include <access/xact.h>\n#include <fmgr.h>\n#include <miscadmin.h>\n#include <utils/acl.h>\n#include <utils/inval.h>\n#include <utils/snapmgr.h>\n\n#include \"debug_point.h\"\n#include \"guc.h\"\n#include \"hypertable.h\"\n#include \"ts_catalog/continuous_agg.h\"\n#include \"ts_catalog/continuous_aggs_watermark.h\"\n\nstatic void\ncagg_watermark_init_scan_by_mat_hypertable_id(ScanIterator *iterator, const int32 mat_hypertable_id)\n{\n\titerator->ctx.index = catalog_get_index(ts_catalog_get(),\n\t\t\t\t\t\t\t\t\t\t\tCONTINUOUS_AGGS_WATERMARK,\n\t\t\t\t\t\t\t\t\t\t\tCONTINUOUS_AGGS_WATERMARK_PKEY);\n\n\tts_scan_iterator_scan_key_init(iterator,\n\t\t\t\t\t\t\t\t   Anum_continuous_aggs_watermark_pkey_mat_hypertable_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(mat_hypertable_id));\n}\n\nint64\nts_cagg_watermark_get(int32 hypertable_id)\n{\n\tPG_USED_FOR_ASSERTS_ONLY short count = 0;\n\tDatum watermark = UnassignedDatum;\n\tbool value_isnull = true;\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(CONTINUOUS_AGGS_WATERMARK, AccessShareLock, CurrentMemoryContext);\n\n\t/*\n\t * The watermark of a CAGG has to be fetched by using the transaction snapshot.\n\t *\n\t * By default, the ts_scanner uses the SnapshotSelf to perform a scan. However, reading the\n\t * watermark must be done using the transaction snapshot in order to ensure that the view on the\n\t * watermark and the materialized part of the CAGG match.\n\t */\n\n\titerator.ctx.snapshot = RegisterSnapshot(GetTransactionSnapshot());\n\tAssert(iterator.ctx.snapshot != NULL);\n\n\tcagg_watermark_init_scan_by_mat_hypertable_id(&iterator, hypertable_id);\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\twatermark = slot_getattr(ts_scan_iterator_slot(&iterator),\n\t\t\t\t\t\t\t\t Anum_continuous_aggs_watermark_watermark,\n\t\t\t\t\t\t\t\t &value_isnull);\n\t\tcount++;\n\t}\n\tAssert(count <= 1);\n\tUnregisterSnapshot(iterator.ctx.snapshot);\n\tts_scan_iterator_close(&iterator);\n\n\tif (value_isnull)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"watermark not defined for continuous aggregate: %d\", hypertable_id)));\n\n\t/* Log the read watermark, needed for MVCC tap tests */\n\tereport(DEBUG5,\n\t\t\t(errcode(ERRCODE_SUCCESSFUL_COMPLETION),\n\t\t\t errmsg(\"watermark for continuous aggregate, '%d' is: \" INT64_FORMAT,\n\t\t\t\t\thypertable_id,\n\t\t\t\t\tDatumGetInt64(watermark))));\n\n\treturn DatumGetInt64(watermark);\n}\n\nTS_FUNCTION_INFO_V1(ts_continuous_agg_watermark);\n\n/*\n * Get the watermark for a real-time aggregation query on a continuous\n * aggregate.\n *\n * The watermark determines where the materialization ends for a continuous\n * aggregate. It is used by real-time aggregation as the threshold between the\n * materialized data and real-time data in the UNION query.\n *\n * The watermark is stored into `_timescaledb_catalog.continuous_aggs_watermark`\n * catalog table by the `refresh_continuous_agregate` procedure. It is defined\n * as the end of the last (highest) bucket in the materialized hypertable of a\n * continuous aggregate.\n *\n * The materialized hypertable ID is given as input argument.\n */\nDatum\nts_continuous_agg_watermark(PG_FUNCTION_ARGS)\n{\n\tconst int32 mat_hypertable_id = PG_GETARG_INT32(0);\n\tContinuousAgg *cagg;\n\tAclResult aclresult;\n\n\tcagg = ts_continuous_agg_find_by_mat_hypertable_id(mat_hypertable_id, false);\n\n\t/*\n\t * Preemptive permission check to ensure the function complains about lack\n\t * of permissions on the cagg rather than the materialized hypertable\n\t */\n\taclresult = pg_class_aclcheck(cagg->relid, GetUserId(), ACL_SELECT);\n\taclcheck_error(aclresult, OBJECT_MATVIEW, get_rel_name(cagg->relid));\n\n\tint64 watermark = ts_cagg_watermark_get(cagg->data.mat_hypertable_id);\n\n\tPG_RETURN_INT64(watermark);\n}\n\nstatic int64\ncagg_compute_watermark(ContinuousAgg *cagg, int64 watermark, bool isnull)\n{\n\tif (isnull)\n\t{\n\t\twatermark = ts_time_get_min(cagg->partition_type);\n\t}\n\telse\n\t{\n\t\t/*\n\t\t * The materialized hypertable is already bucketed, which means the\n\t\t * max is the start of the last bucket. Add one bucket to move to the\n\t\t * point where the materialized data ends.\n\t\t */\n\t\tif (cagg->bucket_function->bucket_fixed_interval == false)\n\t\t{\n\t\t\t/*\n\t\t\t * Since `value` is already bucketed, `bucketed = true` flag can\n\t\t\t * be added to ts_compute_beginning_of_the_next_bucket_variable() as\n\t\t\t * an optimization, if necessary.\n\t\t\t */\n\t\t\twatermark =\n\t\t\t\tts_compute_beginning_of_the_next_bucket_variable(watermark, cagg->bucket_function);\n\t\t}\n\t\telse\n\t\t{\n\t\t\twatermark =\n\t\t\t\tts_time_saturating_add(watermark,\n\t\t\t\t\t\t\t\t\t   ts_continuous_agg_fixed_bucket_width(cagg->bucket_function),\n\t\t\t\t\t\t\t\t\t   cagg->partition_type);\n\t\t}\n\t}\n\n\treturn watermark;\n}\n\nTS_FUNCTION_INFO_V1(ts_continuous_agg_watermark_materialized);\n\n/*\n * Get the materialized watermark for a real-time aggregation query on a\n * continuous aggregate.\n *\n * The difference between this function and `ts_continuous_agg_watermark` is\n * that this one get the max open dimension of the materialization hypertable\n * instead of get the stored value in the catalog table.\n */\nDatum\nts_continuous_agg_watermark_materialized(PG_FUNCTION_ARGS)\n{\n\tconst int32 mat_hypertable_id = PG_GETARG_INT32(0);\n\tContinuousAgg *cagg;\n\tAclResult aclresult;\n\tbool isnull;\n\tHypertable *ht;\n\tint64 watermark;\n\n\tcagg = ts_continuous_agg_find_by_mat_hypertable_id(mat_hypertable_id, false);\n\n\t/*\n\t * Preemptive permission check to ensure the function complains about lack\n\t * of permissions on the cagg rather than the materialized hypertable\n\t */\n\taclresult = pg_class_aclcheck(cagg->relid, GetUserId(), ACL_SELECT);\n\taclcheck_error(aclresult, OBJECT_MATVIEW, get_rel_name(cagg->relid));\n\n\tht = ts_hypertable_get_by_id(cagg->data.mat_hypertable_id);\n\twatermark = ts_hypertable_get_open_dim_max_value(ht, 0, &isnull);\n\n\twatermark = cagg_compute_watermark(cagg, watermark, isnull);\n\n\tPG_RETURN_INT64(watermark);\n}\n\nTSDLLEXPORT void\nts_cagg_watermark_insert(Hypertable *mat_ht, int64 watermark, bool watermark_isnull)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tRelation rel =\n\t\ttable_open(catalog_get_table_id(catalog, CONTINUOUS_AGGS_WATERMARK), RowExclusiveLock);\n\tTupleDesc desc = RelationGetDescr(rel);\n\tDatum values[Natts_continuous_aggs_watermark];\n\tbool nulls[Natts_continuous_aggs_watermark] = { false };\n\tCatalogSecurityContext sec_ctx;\n\n\t/* if trying to insert a NULL watermark then get the MIN value for the time dimension */\n\tif (watermark_isnull)\n\t{\n\t\tconst Dimension *dim = hyperspace_get_open_dimension(mat_ht->space, 0);\n\n\t\tif (NULL == dim)\n\t\t\telog(ERROR, \"invalid open dimension index %d\", 0);\n\n\t\twatermark = ts_time_get_min(ts_dimension_get_partition_type(dim));\n\t}\n\n\tvalues[AttrNumberGetAttrOffset(Anum_continuous_aggs_watermark_mat_hypertable_id)] =\n\t\tInt32GetDatum(mat_ht->fd.id);\n\tvalues[AttrNumberGetAttrOffset(Anum_continuous_aggs_watermark_watermark)] =\n\t\tInt64GetDatum(watermark);\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_insert_values(rel, desc, values, nulls);\n\tts_catalog_restore_user(&sec_ctx);\n\n\ttable_close(rel, NoLock);\n}\n\ntypedef struct WatermarkUpdate\n{\n\tint64 watermark;\n\tbool force_update;\n\tbool invalidate_rel_cache;\n\tOid ht_relid;\n} WatermarkUpdate;\n\nstatic ScanTupleResult\ncagg_watermark_update_scan_internal(TupleInfo *ti, void *data)\n{\n\tWatermarkUpdate *watermark_update = data;\n\tbool should_free;\n\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\tForm_continuous_aggs_watermark form = (Form_continuous_aggs_watermark) GETSTRUCT(tuple);\n\n\t/* If the tuple was modified concurrently, retry the operation and use a new snapshot\n\t * to see the updated tuple. */\n\tif (ti->lockresult == TM_Updated)\n\t\treturn SCAN_RESTART_WITH_NEW_SNAPSHOT;\n\n\tEnsure(ti->lockresult == TM_Ok,\n\t\t   \"unable to lock watermark tuple for cagg %d (lock result %d)\",\n\t\t   watermark_update->ht_relid,\n\t\t   ti->lockresult);\n\n\tif (watermark_update->watermark > form->watermark || watermark_update->force_update)\n\t{\n\t\tHeapTuple new_tuple = heap_copytuple(tuple);\n\t\tform = (Form_continuous_aggs_watermark) GETSTRUCT(new_tuple);\n\t\tform->watermark = watermark_update->watermark;\n\t\tts_catalog_update(ti->scanrel, new_tuple);\n\t\theap_freetuple(new_tuple);\n\n\t\t/*\n\t\t * During query planning, the values of the watermark function are constified using the\n\t\t * constify_cagg_watermark() function. However, this function's value changes when we update\n\t\t * the Cagg (the volatility of the function is STABLE not IMMUTABLE). To ensure that caches,\n\t\t * such as the query plan cache, are properly evicted, we send an invalidation message for\n\t\t * the hypertable.\n\t\t */\n\t\tif (watermark_update->invalidate_rel_cache)\n\t\t{\n\t\t\tDEBUG_WAITPOINT(\"cagg_watermark_update_internal_before_refresh\");\n\t\t\tCacheInvalidateRelcacheByRelid(watermark_update->ht_relid);\n\t\t}\n\t}\n\telse\n\t{\n\t\telog(DEBUG1,\n\t\t\t \"hypertable %d existing watermark >= new watermark \" INT64_FORMAT \" \" INT64_FORMAT,\n\t\t\t form->mat_hypertable_id,\n\t\t\t form->watermark,\n\t\t\t watermark_update->watermark);\n\t\twatermark_update->watermark = form->watermark;\n\t}\n\n\tif (should_free)\n\t\theap_freetuple(tuple);\n\n\treturn SCAN_DONE;\n}\n\nstatic void\ncagg_watermark_update_internal(int32 mat_hypertable_id, Oid ht_relid, int64 new_watermark,\n\t\t\t\t\t\t\t   bool force_update, bool invalidate_rel_cache)\n{\n\tWatermarkUpdate data = { .watermark = new_watermark,\n\t\t\t\t\t\t\t .force_update = force_update,\n\t\t\t\t\t\t\t .invalidate_rel_cache = invalidate_rel_cache,\n\t\t\t\t\t\t\t .ht_relid = ht_relid };\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(CONTINUOUS_AGGS_WATERMARK, RowExclusiveLock, CurrentMemoryContext);\n\n\tcagg_watermark_init_scan_by_mat_hypertable_id(&iterator, mat_hypertable_id);\n\titerator.ctx.tuple_found = cagg_watermark_update_scan_internal;\n\titerator.ctx.data = &data;\n\titerator.ctx.snapshot = RegisterSnapshot(GetLatestSnapshot());\n\tScanTupLock scantuplock = {\n\t\t.waitpolicy = LockWaitBlock,\n\t\t.lockmode = LockTupleExclusive,\n\t\t.lockflags = TUPLE_LOCK_FLAG_FIND_LAST_VERSION,\n\t};\n\titerator.ctx.tuplock = &scantuplock;\n\titerator.ctx.flags = SCANNER_F_KEEPLOCK;\n\n\tbool watermark_updated =\n\t\tts_scanner_scan_one(&iterator.ctx, false, \"continuous aggregate watermark\");\n\tUnregisterSnapshot(iterator.ctx.snapshot);\n\n\tif (!watermark_updated)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"watermark not defined for continuous aggregate: %d\", mat_hypertable_id)));\n\t}\n}\n\nTSDLLEXPORT void\nts_cagg_watermark_update(Hypertable *mat_ht, int64 watermark, bool watermark_isnull,\n\t\t\t\t\t\t bool force_update)\n{\n\tContinuousAgg *cagg = ts_continuous_agg_find_by_mat_hypertable_id(mat_ht->fd.id, false);\n\n\t/* If we have a real-time CAgg, it uses a watermark function. So, we have to invalidate the rel\n\t * cache to force a replanning of prepared statements. See cagg_watermark_update_internal for\n\t * more information. If the GUC enable_cagg_watermark_constify=false then it's not necessary\n\t * to invalidate relation cache. */\n\tbool invalidate_rel_cache =\n\t\t!cagg->data.materialized_only && ts_guc_enable_cagg_watermark_constify;\n\n\twatermark = cagg_compute_watermark(cagg, watermark, watermark_isnull);\n\tcagg_watermark_update_internal(mat_ht->fd.id,\n\t\t\t\t\t\t\t\t   mat_ht->main_table_relid,\n\t\t\t\t\t\t\t\t   watermark,\n\t\t\t\t\t\t\t\t   force_update,\n\t\t\t\t\t\t\t\t   invalidate_rel_cache);\n}\n\nTSDLLEXPORT void\nts_cagg_watermark_delete_by_mat_hypertable_id(int32 mat_hypertable_id)\n{\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(CONTINUOUS_AGGS_WATERMARK, RowExclusiveLock, CurrentMemoryContext);\n\n\tcagg_watermark_init_scan_by_mat_hypertable_id(&iterator, mat_hypertable_id);\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tts_catalog_delete_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\t}\n\tts_scan_iterator_close(&iterator);\n}\n"
  },
  {
    "path": "src/ts_catalog/continuous_aggs_watermark.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include \"export.h\"\n#include \"hypertable.h\"\n\nextern TSDLLEXPORT void ts_cagg_watermark_delete_by_mat_hypertable_id(int32 mat_hypertable_id);\nextern TSDLLEXPORT void ts_cagg_watermark_insert(Hypertable *mat_ht, int64 watermark,\n\t\t\t\t\t\t\t\t\t\t\t\t bool watermark_isnull);\nextern TSDLLEXPORT void ts_cagg_watermark_update(Hypertable *mat_ht, int64 watermark,\n\t\t\t\t\t\t\t\t\t\t\t\t bool watermark_isnull, bool force_update);\n\nextern TSDLLEXPORT int64 ts_cagg_watermark_get(int32 hypertable_id);\n"
  },
  {
    "path": "src/ts_catalog/metadata.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <unistd.h>\n#include <postgres.h>\n#include <access/htup_details.h>\n#include <catalog/pg_type.h>\n#include <fmgr.h>\n#include <stdlib.h>\n#include <utils/builtins.h>\n#include <utils/datum.h>\n#include <utils/fmgroids.h>\n#include <utils/lsyscache.h>\n#include <utils/uuid.h>\n\n#include \"scanner.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"ts_catalog/metadata.h\"\n#include \"uuid.h\"\n\n#include \"compat/compat.h\"\n\n#define TYPE_ERROR(inout, typeid)                                                                  \\\n\telog(ERROR, \"ts_metadata: no %s function for type %u\", inout, typeid);\n\nstatic Datum\nconvert_type_to_text(Datum value, Oid from_type)\n{\n\tbool is_varlena;\n\tOid outfunc;\n\n\tgetTypeOutputInfo(from_type, &outfunc, &is_varlena);\n\n\tif (!OidIsValid(outfunc))\n\t\tTYPE_ERROR(\"output\", from_type);\n\n\treturn DirectFunctionCall1(textin, OidFunctionCall1(outfunc, value));\n}\n\nstatic Datum\nconvert_text_to_type(Datum value, Oid to_type)\n{\n\tOid value_in;\n\tOid value_ioparam;\n\n\tgetTypeInputInfo(to_type, &value_in, &value_ioparam);\n\n\tif (!OidIsValid(value_in))\n\t\tTYPE_ERROR(\"input\", to_type);\n\n\tvalue = OidFunctionCall3(value_in,\n\t\t\t\t\t\t\t CStringGetDatum(TextDatumGetCString(value)),\n\t\t\t\t\t\t\t ObjectIdGetDatum(InvalidOid),\n\t\t\t\t\t\t\t Int32GetDatum(-1));\n\treturn value;\n}\n\ntypedef struct DatumValue\n{\n\t/*\n\t * This form is not used for anything. It is here to reference the type so\n\t * that pgindent works. It can be removed from this struct in case we\n\t * actually use the form type in code\n\t */\n\tFormData_metadata *form;\n\tDatum value;\n\tOid typeid;\n\tbool isnull;\n} DatumValue;\n\nstatic ScanTupleResult\nmetadata_tuple_get_value(TupleInfo *ti, void *data)\n{\n\tDatumValue *dv = data;\n\n\tdv->value = slot_getattr(ti->slot, Anum_metadata_value, &dv->isnull);\n\n\tif (!dv->isnull)\n\t\tdv->value = convert_text_to_type(dv->value, dv->typeid);\n\n\treturn SCAN_DONE;\n}\n\nstatic Datum\nmetadata_get_value_internal(const char *key, Oid value_type, bool *isnull, LOCKMODE lockmode)\n{\n\tScanKeyData scankey[1];\n\tDatumValue dv = {\n\t\t.typeid = value_type,\n\t\t.isnull = true,\n\t};\n\tCatalog *catalog = ts_catalog_get();\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, METADATA),\n\t\t.index = catalog_get_index(catalog, METADATA, METADATA_PKEY_IDX),\n\t\t.nkeys = 1,\n\t\t.scankey = scankey,\n\t\t.tuple_found = metadata_tuple_get_value,\n\t\t.data = &dv,\n\t\t.lockmode = lockmode,\n\t\t.scandirection = ForwardScanDirection,\n\t};\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_metadata_key,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_NAMEEQ,\n\t\t\t\tCStringGetDatum(key));\n\n\tts_scanner_scan(&scanctx);\n\n\tif (NULL != isnull)\n\t\t*isnull = dv.isnull;\n\n\treturn dv.value;\n}\n\nDatum\nts_metadata_get_value(const char *metadata_key, Oid value_type, bool *isnull)\n{\n\treturn metadata_get_value_internal(metadata_key, value_type, isnull, AccessShareLock);\n}\n\n/*\n *  Insert a row into the metadata table. Acquires a lock in\n *  SHARE ROW EXCLUSIVE mode to conflict with itself, and then verifies that\n *  the desired metadata KV pair still does not exist. Otherwise, exits\n *  without inserting to avoid underlying database error on PK conflict.\n *  Returns the value of the key; this is either the requested insert value or\n *  the existing value if nothing was inserted.\n */\nDatum\nts_metadata_insert(const char *metadata_key, Datum metadata_value, Oid type,\n\t\t\t\t   bool include_in_telemetry)\n{\n\tDatum existing_value;\n\tDatum values[Natts_metadata];\n\tbool nulls[Natts_metadata] = { false };\n\tbool isnull = false;\n\tCatalog *catalog = ts_catalog_get();\n\tRelation rel;\n\tNameData key_data;\n\n\trel = table_open(catalog_get_table_id(catalog, METADATA), ShareRowExclusiveLock);\n\n\t/* Check for row existence while we have the lock */\n\texisting_value =\n\t\tmetadata_get_value_internal(metadata_key, type, &isnull, ShareRowExclusiveLock);\n\n\tif (!isnull)\n\t{\n\t\ttable_close(rel, ShareRowExclusiveLock);\n\t\treturn existing_value;\n\t}\n\n\t/* We have to copy the key here because heap_form_tuple will copy NAMEDATALEN\n\t * into the tuple instead of checking length. */\n\tnamestrcpy(&key_data, metadata_key);\n\n\t/* Insert into the catalog table for persistence */\n\tvalues[AttrNumberGetAttrOffset(Anum_metadata_key)] = NameGetDatum(&key_data);\n\tvalues[AttrNumberGetAttrOffset(Anum_metadata_value)] =\n\t\tconvert_type_to_text(metadata_value, type);\n\tvalues[AttrNumberGetAttrOffset(Anum_metadata_include_in_telemetry)] =\n\t\tBoolGetDatum(include_in_telemetry);\n\n\tts_catalog_insert_values(rel, RelationGetDescr(rel), values, nulls);\n\n\ttable_close(rel, ShareRowExclusiveLock);\n\n\treturn metadata_value;\n}\n\nstatic ScanTupleResult\nmetadata_tuple_delete(TupleInfo *ti, void *data)\n{\n\tts_catalog_delete_tid(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\n\treturn SCAN_CONTINUE;\n}\n\nvoid\nts_metadata_drop(const char *metadata_key)\n{\n\tScanKeyData scankey[1];\n\tCatalog *catalog = ts_catalog_get();\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, METADATA),\n\t\t.index = catalog_get_index(catalog, METADATA, METADATA_PKEY_IDX),\n\t\t.nkeys = 1,\n\t\t.scankey = scankey,\n\t\t.tuple_found = metadata_tuple_delete,\n\t\t.data = NULL,\n\t\t.lockmode = RowExclusiveLock,\n\t\t.scandirection = ForwardScanDirection,\n\t};\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_metadata_key,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_NAMEEQ,\n\t\t\t\tCStringGetDatum(metadata_key));\n\n\tts_scanner_scan(&scanctx);\n}\n\nstatic Datum\nget_uuid_by_key(const char *key)\n{\n\tbool isnull;\n\tDatum uuid;\n\n\tuuid = ts_metadata_get_value(key, UUIDOID, &isnull);\n\n\tif (isnull)\n\t\tuuid = ts_metadata_insert(key, UUIDPGetDatum(ts_uuid_create()), UUIDOID, true);\n\treturn uuid;\n}\n\nDatum\nts_metadata_get_uuid(void)\n{\n\treturn get_uuid_by_key(METADATA_UUID_KEY_NAME);\n}\n\nDatum\nts_metadata_get_exported_uuid(void)\n{\n\treturn get_uuid_by_key(METADATA_EXPORTED_UUID_KEY_NAME);\n}\n\nDatum\nts_metadata_get_install_timestamp(void)\n{\n\tbool isnull;\n\tDatum timestamp;\n\n\ttimestamp = ts_metadata_get_value(METADATA_TIMESTAMP_KEY_NAME, TIMESTAMPTZOID, &isnull);\n\n\tif (isnull)\n\t\ttimestamp = ts_metadata_insert(METADATA_TIMESTAMP_KEY_NAME,\n\t\t\t\t\t\t\t\t\t   TimestampTzGetDatum(GetCurrentTimestamp()),\n\t\t\t\t\t\t\t\t\t   TIMESTAMPTZOID,\n\t\t\t\t\t\t\t\t\t   true);\n\n\treturn timestamp;\n}\n"
  },
  {
    "path": "src/ts_catalog/metadata.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include \"export.h\"\n\n#define METADATA_UUID_KEY_NAME \"uuid\"\n#define METADATA_EXPORTED_UUID_KEY_NAME \"exported_uuid\"\n#define METADATA_TIMESTAMP_KEY_NAME \"install_timestamp\"\n\nextern TSDLLEXPORT Datum ts_metadata_get_value(const char *metadata_key, Oid value_type,\n\t\t\t\t\t\t\t\t\t\t\t   bool *isnull);\nextern TSDLLEXPORT Datum ts_metadata_insert(const char *metadata_key, Datum metadata_value,\n\t\t\t\t\t\t\t\t\t\t\tOid value_type, bool include_in_telemetry);\nextern TSDLLEXPORT void ts_metadata_drop(const char *metadata_key);\nextern TSDLLEXPORT Datum ts_metadata_get_uuid(void);\nextern Datum ts_metadata_get_exported_uuid(void);\nextern Datum ts_metadata_get_install_timestamp(void);\n"
  },
  {
    "path": "src/ts_catalog/tablespace.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/xact.h>\n#include <catalog/pg_tablespace_d.h>\n#include <commands/tablecmds.h>\n#include <commands/tablespace.h>\n#include <fmgr.h>\n#include <funcapi.h>\n#include <miscadmin.h>\n#include <utils/acl.h>\n#include <utils/builtins.h>\n#include <utils/fmgroids.h>\n#include <utils/lsyscache.h>\n#include <utils/spccache.h>\n\n#include \"compat/compat.h\"\n#include \"errors.h\"\n#include \"hypertable_cache.h\"\n#include \"scanner.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"ts_catalog/tablespace.h\"\n#include \"utils.h\"\n\n#define TABLESPACE_DEFAULT_CAPACITY 4\n\nstatic Tablespaces *\ntablespaces_alloc(int capacity)\n{\n\tTablespaces *tspcs;\n\n\ttspcs = palloc(sizeof(Tablespaces));\n\ttspcs->capacity = capacity;\n\ttspcs->num_tablespaces = 0;\n\ttspcs->tablespaces = palloc(sizeof(Tablespace) * tspcs->capacity);\n\n\treturn tspcs;\n}\n\nTablespace *\nts_tablespaces_add(Tablespaces *tablespaces, const FormData_tablespace *form, Oid tspc_oid)\n{\n\tTablespace *tspc;\n\n\tif (tablespaces->num_tablespaces >= tablespaces->capacity)\n\t{\n\t\ttablespaces->capacity += TABLESPACE_DEFAULT_CAPACITY;\n\t\tAssert(tablespaces->tablespaces); /* repalloc() does not work with NULL argument */\n\t\ttablespaces->tablespaces =\n\t\t\trepalloc(tablespaces->tablespaces, sizeof(Tablespace) * tablespaces->capacity);\n\t}\n\n\ttspc = &tablespaces->tablespaces[tablespaces->num_tablespaces++];\n\tmemcpy(&tspc->fd, form, sizeof(FormData_tablespace));\n\ttspc->tablespace_oid = tspc_oid;\n\n\treturn tspc;\n}\n\nbool\nts_tablespaces_contain(const Tablespaces *tablespaces, Oid tspc_oid)\n{\n\tint i;\n\n\tfor (i = 0; i < tablespaces->num_tablespaces; i++)\n\t\tif (tspc_oid == tablespaces->tablespaces[i].tablespace_oid)\n\t\t\treturn true;\n\n\treturn false;\n}\n\nstatic ScanTupleResult\ntablespace_tuple_found(TupleInfo *ti, void *data)\n{\n\tTablespaces *tspcs = data;\n\tbool should_free;\n\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\tFormData_tablespace *form = (FormData_tablespace *) GETSTRUCT(tuple);\n\tOid tspcoid = get_tablespace_oid(NameStr(form->tablespace_name), true);\n\n\tif (NULL != tspcs)\n\t\tts_tablespaces_add(tspcs, form, tspcoid);\n\n\tif (should_free)\n\t\theap_freetuple(tuple);\n\n\treturn SCAN_CONTINUE;\n}\n\nstatic int\ntablespace_scan_internal(int indexid, ScanKeyData *scankey, int nkeys, tuple_found_func tuple_found,\n\t\t\t\t\t\t tuple_filter_func tuple_filter, void *data, int limit, LOCKMODE lockmode)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, TABLESPACE),\n\t\t.index = catalog_get_index(catalog, TABLESPACE, indexid),\n\t\t.nkeys = nkeys,\n\t\t.scankey = scankey,\n\t\t.tuple_found = tuple_found,\n\t\t.filter = tuple_filter,\n\t\t.data = data,\n\t\t.limit = limit,\n\t\t.lockmode = lockmode,\n\t\t.scandirection = ForwardScanDirection,\n\t};\n\n\treturn ts_scanner_scan(&scanctx);\n}\n\nTablespaces *\nts_tablespace_scan(int32 hypertable_id)\n{\n\tTablespaces *tspcs = tablespaces_alloc(TABLESPACE_DEFAULT_CAPACITY);\n\tScanKeyData scankey[1];\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_tablespace_hypertable_id_tablespace_name_idx_hypertable_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(hypertable_id));\n\n\ttablespace_scan_internal(TABLESPACE_HYPERTABLE_ID_TABLESPACE_NAME_IDX,\n\t\t\t\t\t\t\t scankey,\n\t\t\t\t\t\t\t 1,\n\t\t\t\t\t\t\t tablespace_tuple_found,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t tspcs,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t AccessShareLock);\n\n\treturn tspcs;\n}\n\ntypedef struct TablespaceScanInfo\n{\n\tCatalogDatabaseInfo *database_info;\n\tCache *hcache;\n\tOid userid;\n\tint num_filtered;\n\tint stopcount;\n\tList *hypertables; /* Hypertables affected, where applicable */\n\tvoid *data;\n} TablespaceScanInfo;\n\nstatic int\ntablespace_scan_by_name(const char *tspcname, tuple_found_func tuple_found, void *data)\n{\n\tScanKeyData scankey[1];\n\tint nkeys = 0;\n\n\tif (NULL != tspcname)\n\t\tScanKeyInit(&scankey[nkeys++],\n\t\t\t\t\tAnum_tablespace_tablespace_name,\n\t\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\t\tF_NAMEEQ,\n\t\t\t\t\tCStringGetDatum(tspcname));\n\n\treturn tablespace_scan_internal(INVALID_INDEXID,\n\t\t\t\t\t\t\t\t\tscankey,\n\t\t\t\t\t\t\t\t\tnkeys,\n\t\t\t\t\t\t\t\t\ttuple_found,\n\t\t\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\t\t\tdata,\n\t\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t\tAccessShareLock);\n}\n\nint\nts_tablespace_count_attached(const char *tspcname)\n{\n\treturn tablespace_scan_by_name(tspcname, NULL, NULL);\n}\n\nstatic void\ntablespace_validate_revoke_internal(const char *tspcname, tuple_found_func tuple_found, void *stmt)\n{\n\tTablespaceScanInfo info = {\n\t\t.database_info = ts_catalog_database_info_get(),\n\t\t.hcache = ts_hypertable_cache_pin(),\n\t\t.data = stmt,\n\t};\n\n\ttablespace_scan_by_name(tspcname, tuple_found, &info);\n\n\tts_cache_release(&info.hcache);\n}\n\nstatic void\nvalidate_revoke_create(Oid tspcoid, Oid role, Oid relid)\n{\n\tAclResult aclresult = object_aclcheck(TableSpaceRelationId, tspcoid, role, ACL_CREATE);\n\n\tif (aclresult != ACLCHECK_OK)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),\n\t\t\t\t errmsg(\"cannot revoke privilege while tablespace \\\"%s\\\" is attached to hypertable \"\n\t\t\t\t\t\t\"\\\"%s\\\"\",\n\t\t\t\t\t\tget_tablespace_name(tspcoid),\n\t\t\t\t\t\tget_rel_name(relid)),\n\t\t\t\t errhint(\"Detach the tablespace before revoking the privilege on it.\")));\n}\n\n/*\n * Verify that the REVOKE of permissions on a tablespace does not make it\n * impossible to use the tablespace for new chunks.\n *\n * This check should be done after the REVOKE has been applied.\n */\nstatic ScanTupleResult\nrevoke_tuple_found(TupleInfo *ti, void *data)\n{\n\tTablespaceScanInfo *info = data;\n\tGrantStmt *stmt = info->data;\n\tListCell *lc_role;\n\tbool isnull;\n\tDatum hyper_id;\n\tDatum tablespace_name;\n\tOid tspcoid;\n\tHypertable *ht;\n\tOid relowner;\n\n\thyper_id = slot_getattr(ti->slot, Anum_tablespace_hypertable_id, &isnull);\n\tAssert(!isnull);\n\ttablespace_name = slot_getattr(ti->slot, Anum_tablespace_tablespace_name, &isnull);\n\tAssert(!isnull);\n\ttspcoid = get_tablespace_oid(NameStr(*DatumGetName(tablespace_name)), false);\n\tht = ts_hypertable_cache_get_entry_by_id(info->hcache, DatumGetInt32(hyper_id));\n\trelowner = ts_rel_get_owner(ht->main_table_relid);\n\n\tforeach (lc_role, stmt->grantees)\n\t{\n\t\tRoleSpec *role = lfirst(lc_role);\n\t\tOid roleoid = get_role_oid_or_public(role->rolename);\n\n\t\t/* Check if this is a role we're interested in */\n\t\tif (!OidIsValid(roleoid))\n\t\t\tcontinue;\n\n\t\t/*\n\t\t * A revoke on a tablespace can only be for 'CREATE' (or ALL), so no\n\t\t * need to check which privilege is revoked.\n\t\t */\n\t\tvalidate_revoke_create(tspcoid, relowner, ht->main_table_relid);\n\t}\n\n\treturn SCAN_CONTINUE;\n}\n\nvoid\nts_tablespace_validate_revoke(GrantStmt *stmt)\n{\n\ttablespace_validate_revoke_internal(strVal(linitial(stmt->objects)), revoke_tuple_found, stmt);\n}\n\n/*\n * Verify that the REVOKE of a role on a tablespace does not make it impossible\n * to use the tablespace for new chunks.\n *\n * This check should be done after the REVOKE has been applied.\n */\nstatic ScanTupleResult\nrevoke_role_tuple_found(TupleInfo *ti, void *data)\n{\n\tTablespaceScanInfo *info = data;\n\tGrantRoleStmt *stmt = info->data;\n\tbool isnull;\n\tDatum hyper_id;\n\tDatum tablespace_name;\n\tOid tspcoid;\n\tHypertable *ht;\n\tOid relowner;\n\tListCell *lc_role;\n\n\thyper_id = slot_getattr(ti->slot, Anum_tablespace_hypertable_id, &isnull);\n\tAssert(!isnull);\n\ttablespace_name = slot_getattr(ti->slot, Anum_tablespace_tablespace_name, &isnull);\n\tAssert(!isnull);\n\ttspcoid = get_tablespace_oid(NameStr(*DatumGetName(tablespace_name)), false);\n\tht = ts_hypertable_cache_get_entry_by_id(info->hcache, DatumGetInt32(hyper_id));\n\trelowner = ts_rel_get_owner(ht->main_table_relid);\n\n\tforeach (lc_role, stmt->grantee_roles)\n\t{\n\t\tRoleSpec *rolespec = lfirst(lc_role);\n\t\tOid grantee = get_rolespec_oid(rolespec, true);\n\n\t\t/* Only interested in revokes on table owners */\n\t\tif (grantee != relowner)\n\t\t\tcontinue;\n\n\t\t/*\n\t\t * No need to check which role that was revoked since we are only\n\t\t * interested in the resulting permissions for the table owner. A\n\t\t * table owner could have CREATE on the tablespace from multiple\n\t\t * roles, so revoking one of those roles might not mean the owner no\n\t\t * longer has CREATE on the tablespace.\n\t\t */\n\t\tvalidate_revoke_create(tspcoid, relowner, ht->main_table_relid);\n\t}\n\n\treturn SCAN_CONTINUE;\n}\n\nvoid\nts_tablespace_validate_revoke_role(GrantRoleStmt *stmt)\n{\n\ttablespace_validate_revoke_internal(NULL, revoke_role_tuple_found, stmt);\n}\n\nstatic int32\ntablespace_insert_relation(Relation rel, int32 hypertable_id, const char *tspcname)\n{\n\tTupleDesc desc = RelationGetDescr(rel);\n\tDatum values[Natts_tablespace];\n\tbool nulls[Natts_tablespace] = { false };\n\tint32 id;\n\n\tmemset(values, 0, sizeof(values));\n\tid = ts_catalog_table_next_seq_id(ts_catalog_get(), TABLESPACE);\n\tvalues[AttrNumberGetAttrOffset(Anum_tablespace_id)] = Int32GetDatum(id);\n\tvalues[AttrNumberGetAttrOffset(Anum_tablespace_hypertable_id)] = Int32GetDatum(hypertable_id);\n\tvalues[AttrNumberGetAttrOffset(Anum_tablespace_tablespace_name)] =\n\t\tDirectFunctionCall1(namein, CStringGetDatum(tspcname));\n\n\tts_catalog_insert_values(rel, desc, values, nulls);\n\n\treturn id;\n}\n\nstatic int32\ntablespace_insert(int32 hypertable_id, const char *tspcname)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tRelation rel;\n\tint32 id;\n\n\trel = table_open(catalog_get_table_id(catalog, TABLESPACE), RowExclusiveLock);\n\tid = tablespace_insert_relation(rel, hypertable_id, tspcname);\n\ttable_close(rel, RowExclusiveLock);\n\n\treturn id;\n}\n\nstatic ScanTupleResult\ntablespace_tuple_delete(TupleInfo *ti, void *data)\n{\n\tTablespaceScanInfo *info = data;\n\tbool should_free;\n\tCatalogSecurityContext sec_ctx;\n\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\tFormData_tablespace *form = (FormData_tablespace *) GETSTRUCT(tuple);\n\n\tts_catalog_database_info_become_owner(info->database_info, &sec_ctx);\n\tts_catalog_delete_tid_only(ti->scanrel, ts_scanner_get_tuple_tid(ti));\n\tts_catalog_restore_user(&sec_ctx);\n\tinfo->hypertables = lappend_int(info->hypertables, form->hypertable_id);\n\n\tif (should_free)\n\t\theap_freetuple(tuple);\n\n\treturn (info->stopcount == 0 || ti->count < info->stopcount) ? SCAN_CONTINUE : SCAN_DONE;\n}\n\nint\nts_tablespace_delete(int32 hypertable_id, const char *tspcname, Oid tspcoid)\n\n{\n\tScanKeyData scankey[2];\n\tTablespaceScanInfo info = {\n\t\t.database_info = ts_catalog_database_info_get(),\n\t\t.stopcount = (NULL != tspcname),\n\t};\n\tint num_deleted, nkeys = 0;\n\n\tScanKeyInit(&scankey[nkeys++],\n\t\t\t\tAnum_tablespace_hypertable_id_tablespace_name_idx_hypertable_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(hypertable_id));\n\n\tif (NULL != tspcname)\n\t\tScanKeyInit(&scankey[nkeys++],\n\t\t\t\t\tAnum_tablespace_hypertable_id_tablespace_name_idx_tablespace_name,\n\t\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\t\tF_NAMEEQ,\n\t\t\t\t\tCStringGetDatum(tspcname));\n\n\tnum_deleted = tablespace_scan_internal(TABLESPACE_HYPERTABLE_ID_TABLESPACE_NAME_IDX,\n\t\t\t\t\t\t\t\t\t\t   scankey,\n\t\t\t\t\t\t\t\t\t\t   nkeys,\n\t\t\t\t\t\t\t\t\t\t   tablespace_tuple_delete,\n\t\t\t\t\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t\t\t\t\t   &info,\n\t\t\t\t\t\t\t\t\t\t   0,\n\t\t\t\t\t\t\t\t\t\t   RowExclusiveLock);\n\n\tif (num_deleted > 0)\n\t\tCommandCounterIncrement();\n\n\treturn num_deleted;\n}\n\nstatic ScanFilterResult\ntablespace_tuple_owner_filter(const TupleInfo *ti, void *data)\n{\n\tTablespaceScanInfo *info = data;\n\tbool isnull;\n\tDatum hyper_id;\n\tHypertable *ht;\n\tScanFilterResult result;\n\n\thyper_id = slot_getattr(ti->slot, Anum_tablespace_hypertable_id, &isnull);\n\tAssert(!isnull);\n\tht = ts_hypertable_cache_get_entry_by_id(info->hcache, DatumGetInt32(hyper_id));\n\tAssert(NULL != ht);\n\n\tif (ts_hypertable_has_privs_of(ht->main_table_relid, info->userid))\n\t\tresult = SCAN_INCLUDE;\n\telse\n\t{\n\t\tresult = SCAN_EXCLUDE;\n\t\tinfo->num_filtered++;\n\t}\n\n\treturn result;\n}\n\n/*\n * Detach a tablespace from all hypertables it is attached to.\n *\n * Output parameters:\n *   - `hypertables`: the list of hypertables that the tablespace was removed from.\n *\n * Returns:\n *   integer giving the number of tablespaces deleted.\n */\nstatic int\ntablespace_delete_from_all(const char *tspcname, Oid userid, List **hypertables)\n{\n\tScanKeyData scankey[1];\n\tTablespaceScanInfo info = {\n\t\t.database_info = ts_catalog_database_info_get(),\n\t\t.hcache = ts_hypertable_cache_pin(),\n\t\t.userid = userid,\n\t};\n\tint num_deleted;\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_tablespace_tablespace_name,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_NAMEEQ,\n\t\t\t\tCStringGetDatum(tspcname));\n\n\tnum_deleted = tablespace_scan_internal(INVALID_INDEXID,\n\t\t\t\t\t\t\t\t\t\t   scankey,\n\t\t\t\t\t\t\t\t\t\t   1,\n\t\t\t\t\t\t\t\t\t\t   tablespace_tuple_delete,\n\t\t\t\t\t\t\t\t\t\t   tablespace_tuple_owner_filter,\n\t\t\t\t\t\t\t\t\t\t   &info,\n\t\t\t\t\t\t\t\t\t\t   0,\n\t\t\t\t\t\t\t\t\t\t   RowExclusiveLock);\n\tts_cache_release(&info.hcache);\n\n\tif (num_deleted > 0)\n\t\tCommandCounterIncrement();\n\n\tif (info.num_filtered > 0)\n\t\tereport(NOTICE,\n\t\t\t\t(errmsg(\"tablespace \\\"%s\\\" remains attached to %d hypertable(s) due to lack of \"\n\t\t\t\t\t\t\"permissions\",\n\t\t\t\t\t\ttspcname,\n\t\t\t\t\t\tinfo.num_filtered)));\n\n\t*hypertables = info.hypertables;\n\n\treturn num_deleted;\n}\n\nTS_FUNCTION_INFO_V1(ts_tablespace_attach);\n\nDatum\nts_tablespace_attach(PG_FUNCTION_ARGS)\n{\n\tName tspcname = PG_ARGISNULL(0) ? NULL : PG_GETARG_NAME(0);\n\tOid hypertable_oid = PG_ARGISNULL(1) ? InvalidOid : PG_GETARG_OID(1);\n\tbool if_not_attached = PG_ARGISNULL(2) ? false : PG_GETARG_BOOL(2);\n\tRelation rel;\n\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\tif (PG_NARGS() < 2 || PG_NARGS() > 3)\n\t\telog(ERROR, \"invalid number of arguments\");\n\n\tts_tablespace_attach_internal(tspcname, hypertable_oid, if_not_attached);\n\n\t/* If the hypertable did not have a tablespace assigned, we set one */\n\trel = relation_open(hypertable_oid, AccessShareLock);\n\tif (!OidIsValid(rel->rd_rel->reltablespace))\n\t{\n\t\tAlterTableCmd *const cmd = makeNode(AlterTableCmd);\n\n\t\tcmd->subtype = AT_SetTableSpace;\n\t\tcmd->name = NameStr(*tspcname);\n\t\tts_alter_table_with_event_trigger(hypertable_oid, fcinfo->context, list_make1(cmd), false);\n\t}\n\trelation_close(rel, AccessShareLock);\n\tPG_RETURN_VOID();\n}\n\nvoid\nts_tablespace_attach_internal(Name tspcname, Oid hypertable_oid, bool if_not_attached)\n{\n\tCache *hcache;\n\tHypertable *ht;\n\tOid tspc_oid;\n\tOid ownerid;\n\tAclResult aclresult;\n\tCatalogSecurityContext sec_ctx;\n\n\tif (NULL == tspcname)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"invalid tablespace name\")));\n\n\tif (!OidIsValid(hypertable_oid))\n\t\tereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"invalid hypertable\")));\n\n\ttspc_oid = get_tablespace_oid(NameStr(*tspcname), true);\n\n\tif (!OidIsValid(tspc_oid))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t errmsg(\"tablespace \\\"%s\\\" does not exist\", NameStr(*tspcname)),\n\t\t\t\t errhint(\"The tablespace needs to be created\"\n\t\t\t\t\t\t \" before attaching it to a hypertable.\")));\n\n\townerid = ts_hypertable_permissions_check(hypertable_oid, GetUserId());\n\n\t/*\n\t * Only check permissions on tablespace if it is not the database default.\n\t * In usual case users can create tables in their database which will use\n\t * the default tablespace of the database. This condition makes sure they\n\t * can also always move a table from another tablespace to the default of\n\t * their own database. Related to this issue in postgres core:\n\t * https://www.postgresql.org/message-id/52DC8AEA.7090507%402ndquadrant.com\n\t * Which was handled in a similar way. (See\n\t * tablecmds.c::ATPrepSetTableSpace)\n\t */\n\tif (tspc_oid != MyDatabaseTableSpace)\n\t{\n\t\t/*\n\t\t * Note that we check against the table owner rather than the current\n\t\t * user here, since we're not actually creating a table using this\n\t\t * tablespace at this point\n\t\t */\n\t\taclresult = object_aclcheck(TableSpaceRelationId, tspc_oid, ownerid, ACL_CREATE);\n\n\t\tif (aclresult != ACLCHECK_OK)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),\n\t\t\t\t\t errmsg(\"permission denied for tablespace \\\"%s\\\" by table owner \\\"%s\\\"\",\n\t\t\t\t\t\t\tNameStr(*tspcname),\n\t\t\t\t\t\t\tGetUserNameFromId(ownerid, true))));\n\t}\n\tht = ts_hypertable_cache_get_cache_and_entry(hypertable_oid, CACHE_FLAG_NONE, &hcache);\n\n\tif (ts_hypertable_has_tablespace(ht, tspc_oid))\n\t{\n\t\tif (if_not_attached)\n\t\t\tereport(NOTICE,\n\t\t\t\t\t(errcode(ERRCODE_TS_TABLESPACE_ALREADY_ATTACHED),\n\t\t\t\t\t errmsg(\"tablespace \\\"%s\\\" is already attached to hypertable \\\"%s\\\", skipping\",\n\t\t\t\t\t\t\tNameStr(*tspcname),\n\t\t\t\t\t\t\tget_rel_name(hypertable_oid))));\n\t\telse\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_TS_TABLESPACE_ALREADY_ATTACHED),\n\t\t\t\t\t errmsg(\"tablespace \\\"%s\\\" is already attached to hypertable \\\"%s\\\"\",\n\t\t\t\t\t\t\tNameStr(*tspcname),\n\t\t\t\t\t\t\tget_rel_name(hypertable_oid))));\n\t}\n\telse\n\t{\n\t\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\t\ttablespace_insert(ht->fd.id, NameStr(*tspcname));\n\t\tts_catalog_restore_user(&sec_ctx);\n\t}\n\n\tts_cache_release(&hcache);\n}\n\nstatic int\ntablespace_detach_one(Oid hypertable_oid, const char *tspcname, Oid tspcoid, bool if_attached)\n{\n\tCache *hcache;\n\tHypertable *ht;\n\tint ret = 0;\n\n\tts_hypertable_permissions_check(hypertable_oid, GetUserId());\n\n\tht = ts_hypertable_cache_get_cache_and_entry(hypertable_oid, CACHE_FLAG_NONE, &hcache);\n\n\tif (ts_hypertable_has_tablespace(ht, tspcoid))\n\t\tret = ts_tablespace_delete(ht->fd.id, tspcname, tspcoid);\n\telse if (if_attached)\n\t\tereport(NOTICE,\n\t\t\t\t(errcode(ERRCODE_TS_TABLESPACE_NOT_ATTACHED),\n\t\t\t\t errmsg(\"tablespace \\\"%s\\\" is not attached to hypertable \\\"%s\\\", skipping\",\n\t\t\t\t\t\ttspcname,\n\t\t\t\t\t\tget_rel_name(hypertable_oid))));\n\telse\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_TS_TABLESPACE_NOT_ATTACHED),\n\t\t\t\t errmsg(\"tablespace \\\"%s\\\" is not attached to hypertable \\\"%s\\\"\",\n\t\t\t\t\t\ttspcname,\n\t\t\t\t\t\tget_rel_name(hypertable_oid))));\n\n\tts_cache_release(&hcache);\n\n\treturn ret;\n}\n\nstatic int\ntablespace_detach_all(Oid hypertable_oid)\n{\n\tCache *hcache;\n\tHypertable *ht;\n\tint ret;\n\n\tts_hypertable_permissions_check(hypertable_oid, GetUserId());\n\n\tht = ts_hypertable_cache_get_cache_and_entry(hypertable_oid, CACHE_FLAG_NONE, &hcache);\n\n\tret = ts_tablespace_delete(ht->fd.id, NULL, InvalidOid);\n\n\tts_cache_release(&hcache);\n\n\treturn ret;\n}\n\nstatic void\ndetach_tablespace_from_hypertable_if_set(Node *detach_cmd, Oid hypertable_oid, Oid tspcoid)\n{\n\tRelation rel;\n\n\tAssert(OidIsValid(hypertable_oid) && OidIsValid(tspcoid));\n\n\trel = relation_open(hypertable_oid, AccessShareLock);\n\tif (OidIsValid(rel->rd_rel->reltablespace) && rel->rd_rel->reltablespace == tspcoid)\n\t{\n\t\tAlterTableCmd *const cmd = makeNode(AlterTableCmd);\n\n\t\tcmd->subtype = AT_SetTableSpace;\n\t\tcmd->name = \"pg_default\";\n\t\tts_alter_table_with_event_trigger(hypertable_oid, detach_cmd, list_make1(cmd), false);\n\t}\n\trelation_close(rel, AccessShareLock);\n}\n\nTS_FUNCTION_INFO_V1(ts_tablespace_detach);\n\nDatum\nts_tablespace_detach(PG_FUNCTION_ARGS)\n{\n\tName tspcname = PG_ARGISNULL(0) ? NULL : PG_GETARG_NAME(0);\n\tOid hypertable_oid = PG_ARGISNULL(1) ? InvalidOid : PG_GETARG_OID(1);\n\tbool if_attached = PG_ARGISNULL(2) ? false : PG_GETARG_BOOL(2);\n\tOid tspcoid;\n\tint ret;\n\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\tif (PG_NARGS() < 1 || PG_NARGS() > 3)\n\t\telog(ERROR, \"invalid number of arguments\");\n\n\tif (NULL == tspcname)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"invalid tablespace name\")));\n\n\tif (!PG_ARGISNULL(1) && !OidIsValid(hypertable_oid))\n\t\tereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"invalid hypertable\")));\n\n\ttspcoid = get_tablespace_oid(NameStr(*tspcname), true);\n\n\tif (!OidIsValid(tspcoid))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t errmsg(\"tablespace \\\"%s\\\" does not exist\", NameStr(*tspcname))));\n\n\tif (OidIsValid(hypertable_oid))\n\t{\n\t\tret = tablespace_detach_one(hypertable_oid, NameStr(*tspcname), tspcoid, if_attached);\n\t\tdetach_tablespace_from_hypertable_if_set(fcinfo->context, hypertable_oid, tspcoid);\n\t}\n\telse\n\t{\n\t\tList *hypertables = NIL;\n\t\tListCell *cell;\n\n\t\tret = tablespace_delete_from_all(NameStr(*tspcname), GetUserId(), &hypertables);\n\t\tforeach (cell, hypertables)\n\t\t{\n\t\t\tconst int32 hypertable_id = lfirst_int(cell);\n\t\t\tdetach_tablespace_from_hypertable_if_set(fcinfo->context,\n\t\t\t\t\t\t\t\t\t\t\t\t\t ts_hypertable_id_to_relid(hypertable_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   false),\n\t\t\t\t\t\t\t\t\t\t\t\t\t tspcoid);\n\t\t}\n\t}\n\n\tPG_RETURN_INT32(ret);\n}\n\nTS_FUNCTION_INFO_V1(ts_tablespace_detach_all_from_hypertable);\n\nDatum\nts_tablespace_detach_all_from_hypertable(PG_FUNCTION_ARGS)\n{\n\tconst Oid hypertable_relid = PG_GETARG_OID(0);\n\tint32 result;\n\tAlterTableCmd *const cmd = makeNode(AlterTableCmd);\n\n\tcmd->subtype = AT_SetTableSpace;\n\tcmd->name = \"pg_default\";\n\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\tif (PG_NARGS() != 1)\n\t\telog(ERROR, \"invalid number of arguments\");\n\n\tif (PG_ARGISNULL(0))\n\t\telog(ERROR, \"invalid argument\");\n\n\tresult = tablespace_detach_all(hypertable_relid);\n\tts_alter_table_with_event_trigger(hypertable_relid, fcinfo->context, list_make1(cmd), false);\n\n\tPG_RETURN_INT32(result);\n}\n\nTS_FUNCTION_INFO_V1(ts_tablespace_show);\n\nDatum\nts_tablespace_show(PG_FUNCTION_ARGS)\n{\n\tFuncCallContext *funcctx;\n\tOid hypertable_oid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\tCache *hcache;\n\tHypertable *ht;\n\tTablespaces *tspcs;\n\n\tif (SRF_IS_FIRSTCALL())\n\t{\n\t\tMemoryContext oldcontext;\n\n\t\tif (!OidIsValid(hypertable_oid))\n\t\t\telog(ERROR, \"invalid argument\");\n\n\t\tfuncctx = SRF_FIRSTCALL_INIT();\n\t\toldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);\n\t\tfuncctx->user_fctx = ts_hypertable_cache_pin();\n\t\tMemoryContextSwitchTo(oldcontext);\n\t}\n\n\tfuncctx = SRF_PERCALL_SETUP();\n\thcache = funcctx->user_fctx;\n\tht = ts_hypertable_cache_get_entry(hcache, hypertable_oid, CACHE_FLAG_NONE);\n\n\ttspcs = ts_tablespace_scan(ht->fd.id);\n\n\tif (NULL != tspcs && funcctx->call_cntr < (uint64) tspcs->num_tablespaces)\n\t{\n\t\tOid tablespace_oid = tspcs->tablespaces[funcctx->call_cntr].tablespace_oid;\n\t\tconst char *tablespace_name = get_tablespace_name(tablespace_oid);\n\t\tDatum name;\n\n\t\tAssert(tablespace_name != NULL);\n\t\tname = DirectFunctionCall1(namein, CStringGetDatum(tablespace_name));\n\n\t\tSRF_RETURN_NEXT(funcctx, name);\n\t}\n\telse\n\t{\n\t\tts_cache_release(&hcache);\n\t\tSRF_RETURN_DONE(funcctx);\n\t}\n}\n"
  },
  {
    "path": "src/ts_catalog/tablespace.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <nodes/parsenodes.h>\n\n#include \"ts_catalog/catalog.h\"\n\ntypedef struct Tablespace\n{\n\tFormData_tablespace fd;\n\tOid tablespace_oid;\n} Tablespace;\n\ntypedef struct Tablespaces\n{\n\tint capacity;\n\tint num_tablespaces;\n\tTablespace *tablespaces;\n} Tablespaces;\n\nextern Tablespace *ts_tablespaces_add(Tablespaces *tablespaces, const FormData_tablespace *form,\n\t\t\t\t\t\t\t\t\t  Oid tspc_oid);\nextern bool ts_tablespaces_contain(const Tablespaces *tablespaces, Oid tspc_oid);\nextern Tablespaces *ts_tablespace_scan(int32 hypertable_id);\nextern TSDLLEXPORT void ts_tablespace_attach_internal(Name tspcname, Oid hypertable_oid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  bool if_not_attached);\nextern int ts_tablespace_delete(int32 hypertable_id, const char *tspcname, Oid tspcoid);\nextern int ts_tablespace_count_attached(const char *tspcname);\nextern void ts_tablespace_validate_revoke(GrantStmt *stmt);\nextern void ts_tablespace_validate_revoke_role(GrantRoleStmt *stmt);\n"
  },
  {
    "path": "src/tss_callbacks.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n/*\n * Currently we finish the execution of some process utility statements\n * and don't execute other hooks in the chain.\n *\n * Because that reason neither ts_stat_statements and pg_stat_statements\n * are able to track some utility statements, for example COPY ... FROM.\n *\n * To be able to track it on ts_stat_statements here we introduce some\n * callbacks in order to hook pgss_store from TimescaleDB and store\n * information about the execution of those statements.\n *\n * Hooking ts_stat_statements from TimescaleDB is controlled by a new GUC\n * named `enable_tss_callbacks`.\n */\n\n#include <postgres.h>\n\n#include <executor/instrument.h>\n#include <fmgr.h>\n#include <utils/elog.h>\n\n#include \"guc.h\"\n#include \"tss_callbacks.h\"\n\nstatic instr_time tss_callback_start_time;\nstatic BufferUsage tss_callback_start_bufusage;\nstatic WalUsage tss_callback_start_walusage;\n\nstatic TSSCallbacks *\nts_get_tss_callbacks(void)\n{\n\tTSSCallbacks **ptr = (TSSCallbacks **) find_rendezvous_variable(TSS_CALLBACKS_VAR_NAME);\n\n\treturn *ptr;\n}\n\nstatic tss_store_hook_type\nts_get_tss_store_hook(void)\n{\n\tTSSCallbacks *ptr = ts_get_tss_callbacks();\n\n\tif (ptr && ptr->version_num == TSS_CALLBACKS_VERSION)\n\t\treturn ptr->tss_store_hook;\n\n\treturn NULL;\n}\n\nbool\nts_is_tss_enabled(void)\n{\n\tif (ts_guc_enable_tss_callbacks)\n\t{\n\t\tTSSCallbacks *ptr = ts_get_tss_callbacks();\n\n\t\tif (ptr)\n\t\t{\n\t\t\tif (ptr->version_num != TSS_CALLBACKS_VERSION)\n\t\t\t{\n\t\t\t\tereport(WARNING,\n\t\t\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t\t\t errmsg(\"version mismatch between timescaledb and ts_stat_statements \"\n\t\t\t\t\t\t\t\t\"callbacks\"),\n\t\t\t\t\t\t errdetail(\"Callbacks versions: TimescaleDB (%d) and ts_stat_statements \"\n\t\t\t\t\t\t\t\t   \"(%d)\",\n\t\t\t\t\t\t\t\t   TSS_CALLBACKS_VERSION,\n\t\t\t\t\t\t\t\t   ptr->version_num)));\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\treturn ptr->tss_enabled_hook_type(0); /* consider top level statement */\n\t\t}\n\t}\n\n\treturn false;\n}\n\nvoid\nts_begin_tss_store_callback(void)\n{\n\tif (!ts_is_tss_enabled())\n\t\treturn;\n\n\ttss_callback_start_bufusage = pgBufferUsage;\n\ttss_callback_start_walusage = pgWalUsage;\n\tINSTR_TIME_SET_CURRENT(tss_callback_start_time);\n}\n\nvoid\nts_end_tss_store_callback(const char *query, int query_location, int query_len, uint64 query_id,\n\t\t\t\t\t\t  uint64 rows)\n{\n\tinstr_time duration;\n\tBufferUsage bufusage;\n\tWalUsage walusage;\n\ttss_store_hook_type hook;\n\n\tif (!ts_is_tss_enabled())\n\t\treturn;\n\n\thook = ts_get_tss_store_hook();\n\n\tif (!hook)\n\t\treturn;\n\n\tINSTR_TIME_SET_CURRENT(duration);\n\tINSTR_TIME_SUBTRACT(duration, tss_callback_start_time);\n\n\t/* calc differences of buffer counters. */\n\tmemset(&bufusage, 0, sizeof(BufferUsage));\n\tBufferUsageAccumDiff(&bufusage, &pgBufferUsage, &tss_callback_start_bufusage);\n\n\t/* calc differences of WAL counters. */\n\tmemset(&walusage, 0, sizeof(WalUsage));\n\tWalUsageAccumDiff(&walusage, &pgWalUsage, &tss_callback_start_walusage);\n\n\thook(query,\n\t\t query_location,\n\t\t query_len,\n\t\t query_id,\n\t\t INSTR_TIME_GET_MICROSEC(duration),\n\t\t rows,\n\t\t &bufusage,\n\t\t &walusage);\n}\n"
  },
  {
    "path": "src/tss_callbacks.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#define TSS_CALLBACKS_VAR_NAME \"tss_callbacks\"\n#define TSS_CALLBACKS_VERSION 1\n\n/* ts_stat_statements -> pgss_store */\ntypedef void (*tss_store_hook_type)(const char *query, int query_location, int query_len,\n\t\t\t\t\t\t\t\t\tuint64 query_id, uint64 total_time, uint64 rows,\n\t\t\t\t\t\t\t\t\tconst BufferUsage *bufusage, const WalUsage *walusage);\n/* ts_stat_statements -> pgss_enabled */\ntypedef bool (*tss_enabled_hook_type)(int level);\n\n/* ts_stat_statements callbacks */\ntypedef struct TSSCallbacks\n{\n\tuint32_t version_num;\n\ttss_store_hook_type tss_store_hook;\n\ttss_enabled_hook_type tss_enabled_hook_type;\n} TSSCallbacks;\n\nextern bool ts_is_tss_enabled(void);\nextern void ts_begin_tss_store_callback(void);\nextern void ts_end_tss_store_callback(const char *query, int query_location, int query_len,\n\t\t\t\t\t\t\t\t\t  uint64 query_id, uint64 rows);\n"
  },
  {
    "path": "src/utils.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/genam.h>\n#include <access/heapam.h>\n#include <access/htup.h>\n#include <access/htup_details.h>\n#include <access/reloptions.h>\n#include <access/xact.h>\n#include <catalog/indexing.h>\n#include <catalog/namespace.h>\n#include <catalog/objectaccess.h>\n#include <catalog/pg_am.h>\n#include <catalog/pg_cast.h>\n#include <catalog/pg_inherits.h>\n#include <catalog/pg_operator.h>\n#include <catalog/pg_type.h>\n#include <commands/event_trigger.h>\n#include <commands/tablecmds.h>\n#include <fmgr.h>\n#include <funcapi.h>\n#include <nodes/makefuncs.h>\n#include <parser/parse_coerce.h>\n#include <parser/parse_func.h>\n#include <parser/scansup.h>\n#include <storage/lockdefs.h>\n#include <utils/acl.h>\n#include <utils/builtins.h>\n#include <utils/catcache.h>\n#include <utils/date.h>\n#include <utils/elog.h>\n#include <utils/fmgroids.h>\n#include <utils/fmgrprotos.h>\n#include <utils/lsyscache.h>\n#include <utils/relcache.h>\n#include <utils/snapmgr.h>\n#include <utils/syscache.h>\n#include <utils/timestamp.h>\n#include <utils/uuid.h>\n\n#include \"compat/compat.h\"\n#include \"chunk.h\"\n#include \"cross_module_fn.h\"\n#include \"debug_point.h\"\n#include \"hypertable_cache.h\"\n#include \"jsonb_utils.h\"\n#include \"time_utils.h\"\n#include \"utils.h\"\n#include \"uuid.h\"\n\ntypedef struct\n{\n\tconst char *name;\n\tAclMode value;\n} priv_map;\n\nTS_FUNCTION_INFO_V1(ts_pg_timestamp_to_unix_microseconds);\nTS_FUNCTION_INFO_V1(ts_makeaclitem);\n\n/*\n * Convert a Postgres TIMESTAMP to BIGINT microseconds relative the UNIX epoch.\n */\nDatum\nts_pg_timestamp_to_unix_microseconds(PG_FUNCTION_ARGS)\n{\n\tTimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0);\n\n\tif (TIMESTAMP_IS_NOBEGIN(timestamp))\n\t\tPG_RETURN_INT64(TS_TIME_NOBEGIN);\n\n\tif (TIMESTAMP_IS_NOEND(timestamp))\n\t\tPG_RETURN_INT64(TS_TIME_NOEND);\n\n\tif (timestamp < TS_TIMESTAMP_MIN)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg(\"timestamp out of range\")));\n\n\tif (timestamp >= TS_TIMESTAMP_END)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg(\"timestamp out of range\")));\n\n\tPG_RETURN_INT64(timestamp + TS_EPOCH_DIFF_MICROSECONDS);\n}\n\nTS_FUNCTION_INFO_V1(ts_pg_unix_microseconds_to_timestamp);\nTS_FUNCTION_INFO_V1(ts_pg_unix_microseconds_to_timestamp_without_timezone);\nTS_FUNCTION_INFO_V1(ts_pg_unix_microseconds_to_date);\n\n/*\n * Convert BIGINT microseconds relative the UNIX epoch to a Postgres TIMESTAMP.\n */\nDatum\nts_pg_unix_microseconds_to_timestamp(PG_FUNCTION_ARGS)\n{\n\tint64 microseconds = PG_GETARG_INT64(0);\n\n\tif (TS_TIME_IS_NOBEGIN(microseconds, TIMESTAMPTZOID))\n\t\tPG_RETURN_DATUM(ts_time_datum_get_nobegin(TIMESTAMPTZOID));\n\n\tif (TS_TIME_IS_NOEND(microseconds, TIMESTAMPTZOID))\n\t\tPG_RETURN_DATUM(ts_time_datum_get_noend(TIMESTAMPTZOID));\n\n\t/*\n\t * Test that the UNIX us timestamp is within bounds. Note that an int64 at\n\t * UNIX epoch and microsecond precision cannot represent the upper limit\n\t * of the supported date range (Julian end date), so INT64_MAX-1 is the\n\t * natural upper bound for this function.\n\t */\n\tif (microseconds < TS_TIMESTAMP_INTERNAL_MIN)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg(\"timestamp out of range\")));\n\n\tPG_RETURN_TIMESTAMPTZ(microseconds - TS_EPOCH_DIFF_MICROSECONDS);\n}\n\nDatum\nts_pg_unix_microseconds_to_date(PG_FUNCTION_ARGS)\n{\n\tint64 microseconds = PG_GETARG_INT64(0);\n\tDatum res;\n\n\tif (TS_TIME_IS_NOBEGIN(microseconds, DATEOID))\n\t\tPG_RETURN_DATUM(ts_time_datum_get_nobegin(DATEOID));\n\n\tif (TS_TIME_IS_NOEND(microseconds, DATEOID))\n\t\tPG_RETURN_DATUM(ts_time_datum_get_noend(DATEOID));\n\n\tres = DirectFunctionCall1(ts_pg_unix_microseconds_to_timestamp, Int64GetDatum(microseconds));\n\tres = DirectFunctionCall1(timestamp_date, res);\n\tPG_RETURN_DATUM(res);\n}\n\nstatic int64 ts_integer_to_internal(Datum time_val, Oid type_oid);\n\n/* Convert valid timescale time column type to internal representation */\nTSDLLEXPORT int64\nts_time_value_to_internal(Datum time_val, Oid type_oid)\n{\n\tDatum res, tz;\n\n\t/* Handle custom time types. We currently only support binary coercible\n\t * types */\n\tif (!IS_VALID_TIME_TYPE(type_oid))\n\t{\n\t\tif (ts_type_is_int8_binary_compatible(type_oid))\n\t\t\treturn DatumGetInt64(time_val);\n\n\t\telog(ERROR, \"unknown time type \\\"%s\\\"\", format_type_be(type_oid));\n\t}\n\n\tif (IS_INTEGER_TYPE(type_oid) || IS_UUID_TYPE(type_oid))\n\t{\n\t\t/* Integer time types have no distinction between min, max and\n\t\t * infinity. We don't want min and max to be turned into infinity for\n\t\t * these types so check for those values first. */\n\t\tif (TS_TIME_DATUM_IS_MIN(time_val, type_oid))\n\t\t\treturn ts_time_get_min(type_oid);\n\n\t\tif (TS_TIME_DATUM_IS_MAX(time_val, type_oid))\n\t\t\treturn ts_time_get_max(type_oid);\n\t}\n\n\tif (TS_TIME_DATUM_IS_NOBEGIN(time_val, type_oid))\n\t\treturn ts_time_get_nobegin(type_oid);\n\n\tif (TS_TIME_DATUM_IS_NOEND(time_val, type_oid))\n\t\treturn ts_time_get_noend(type_oid);\n\n\tswitch (type_oid)\n\t{\n\t\tcase INT8OID:\n\t\tcase INT4OID:\n\t\tcase INT2OID:\n\t\t\treturn ts_integer_to_internal(time_val, type_oid);\n\t\tcase TIMESTAMPOID:\n\t\t\t/*\n\t\t\t * for timestamps, ignore timezones, make believe the timestamp is\n\t\t\t * at UTC\n\t\t\t */\n\t\t\tres = DirectFunctionCall1(ts_pg_timestamp_to_unix_microseconds, time_val);\n\n\t\t\treturn DatumGetInt64(res);\n\t\tcase TIMESTAMPTZOID:\n\t\t\tres = DirectFunctionCall1(ts_pg_timestamp_to_unix_microseconds, time_val);\n\n\t\t\treturn DatumGetInt64(res);\n\t\tcase DATEOID:\n\t\t\ttz = DirectFunctionCall1(date_timestamp, time_val);\n\t\t\tres = DirectFunctionCall1(ts_pg_timestamp_to_unix_microseconds, tz);\n\n\t\t\treturn DatumGetInt64(res);\n\t\tcase UUIDOID:\n\t\t{\n\t\t\tuint64 unixtime_ms = 0;\n\n\t\t\t/*\n\t\t\t * Extract the unix timestamp from the UUID. Note that we cannot\n\t\t\t * use the (optional) sub-milliseconds part because there is no\n\t\t\t * way to know whether it represents time or is random.\n\t\t\t *\n\t\t\t * If the UUID is not v7, error out.\n\t\t\t */\n\t\t\tif (!ts_uuid_v7_extract_unixtime(DatumGetUUIDP(time_val), &unixtime_ms, NULL))\n\t\t\t{\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\terrcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\terrmsg(\"%s is not a version 7 UUID\",\n\t\t\t\t\t\t\t   DatumGetCString(DirectFunctionCall1(uuid_out, time_val))),\n\t\t\t\t\t\terrdetail(\n\t\t\t\t\t\t\t\"UUID \\\"time\\\" partitioning columns only support version 7 UUIDs.\"));\n\t\t\t}\n\n\t\t\t/* Convert to microseconds */\n\t\t\treturn unixtime_ms * 1000;\n\t\t}\n\t\tdefault:\n\t\t\telog(ERROR, \"unknown time type \\\"%s\\\"\", format_type_be(type_oid));\n\t\t\treturn -1;\n\t}\n}\n\nTSDLLEXPORT int64\nts_interval_value_to_internal(Datum time_val, Oid type_oid)\n{\n\tswitch (type_oid)\n\t{\n\t\tcase INT8OID:\n\t\tcase INT4OID:\n\t\tcase INT2OID:\n\t\t\treturn ts_integer_to_internal(time_val, type_oid);\n\t\tcase INTERVALOID:\n\t\t{\n\t\t\tInterval *interval = DatumGetIntervalP(time_val);\n\t\t\tif (interval->month != 0)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t errmsg(\"months and years not supported\"),\n\t\t\t\t\t\t errdetail(\"An interval must be defined as a fixed duration (such as \"\n\t\t\t\t\t\t\t\t   \"weeks, days, hours, minutes, seconds, etc.).\")));\n\t\t\treturn interval->time + (interval->day * USECS_PER_DAY);\n\t\t}\n\t\tdefault:\n\t\t\telog(ERROR, \"unknown interval type \\\"%s\\\"\", format_type_be(type_oid));\n\t\t\treturn -1;\n\t}\n}\n\nstatic int64\nts_integer_to_internal(Datum time_val, Oid type_oid)\n{\n\tswitch (type_oid)\n\t{\n\t\tcase INT8OID:\n\t\t\treturn DatumGetInt64(time_val);\n\t\tcase INT4OID:\n\t\t\treturn (int64) DatumGetInt32(time_val);\n\t\tcase INT2OID:\n\t\t\treturn (int64) DatumGetInt16(time_val);\n\t\tdefault:\n\t\t\telog(ERROR, \"unknown interval type \\\"%s\\\"\", format_type_be(type_oid));\n\t\t\treturn -1;\n\t}\n}\n\nint64\nts_time_value_to_internal_or_infinite(Datum time_val, Oid type_oid)\n{\n\tswitch (type_oid)\n\t{\n\t\tcase TIMESTAMPOID:\n\t\t{\n\t\t\tTimestamp ts = DatumGetTimestamp(time_val);\n\t\t\tif (TIMESTAMP_NOT_FINITE(ts))\n\t\t\t{\n\t\t\t\tif (TIMESTAMP_IS_NOBEGIN(ts))\n\t\t\t\t{\n\t\t\t\t\treturn PG_INT64_MIN;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\treturn PG_INT64_MAX;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * Timestamp is valid in PostgreSQL but exceeds TimescaleDB's\n\t\t\t * supported range (TS_TIMESTAMP_END < END_TIMESTAMP due to the\n\t\t\t * Unix epoch shift). Treat as +infinity to avoid errors during\n\t\t\t * chunk exclusion. No equivalent check is needed on the lower\n\t\t\t * bound since TS_TIMESTAMP_MIN == MIN_TIMESTAMP.\n\t\t\t */\n\t\t\tif (ts >= TS_TIMESTAMP_END)\n\t\t\t\treturn PG_INT64_MAX;\n\n\t\t\treturn ts_time_value_to_internal(time_val, type_oid);\n\t\t}\n\t\tcase TIMESTAMPTZOID:\n\t\t{\n\t\t\tTimestampTz ts = DatumGetTimestampTz(time_val);\n\t\t\tif (TIMESTAMP_NOT_FINITE(ts))\n\t\t\t{\n\t\t\t\tif (TIMESTAMP_IS_NOBEGIN(ts))\n\t\t\t\t{\n\t\t\t\t\treturn PG_INT64_MIN;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\treturn PG_INT64_MAX;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/* See comment in TIMESTAMPOID case above. */\n\t\t\tif (ts >= TS_TIMESTAMP_END)\n\t\t\t\treturn PG_INT64_MAX;\n\n\t\t\treturn ts_time_value_to_internal(time_val, type_oid);\n\t\t}\n\t\tcase DATEOID:\n\t\t{\n\t\t\tDateADT d = DatumGetDateADT(time_val);\n\t\t\tif (DATE_NOT_FINITE(d))\n\t\t\t{\n\t\t\t\tif (DATE_IS_NOBEGIN(d))\n\t\t\t\t{\n\t\t\t\t\treturn PG_INT64_MIN;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\treturn PG_INT64_MAX;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/* See comment in TIMESTAMPOID case above. */\n\t\t\tif (d >= TS_DATE_END)\n\t\t\t\treturn PG_INT64_MAX;\n\n\t\t\treturn ts_time_value_to_internal(time_val, type_oid);\n\t\t}\n\t\tdefault:\n\t\t\treturn ts_time_value_to_internal(time_val, type_oid);\n\t}\n}\n\nTS_FUNCTION_INFO_V1(ts_time_to_internal);\nDatum\nts_time_to_internal(PG_FUNCTION_ARGS)\n{\n\tDatum time = PG_GETARG_DATUM(0);\n\tOid time_type = get_fn_expr_argtype(fcinfo->flinfo, 0);\n\tint64 res = ts_time_value_to_internal(time, time_type);\n\tPG_RETURN_INT64(res);\n}\n\nstatic Datum ts_integer_to_internal_value(int64 value, Oid type);\n\n/*\n * convert int64 to Datum according to type\n * internally we store all times as int64 in the\n * same format postgres does\n */\nTSDLLEXPORT Datum\nts_internal_to_time_value(int64 value, Oid type)\n{\n\tif (TS_TIME_IS_NOBEGIN(value, type))\n\t\treturn ts_time_datum_get_nobegin(type);\n\n\tif (TS_TIME_IS_NOEND(value, type))\n\t\treturn ts_time_datum_get_noend(type);\n\n\tswitch (type)\n\t{\n\t\tcase INT2OID:\n\t\tcase INT4OID:\n\t\tcase INT8OID:\n\t\t\treturn ts_integer_to_internal_value(value, type);\n\t\tcase TIMESTAMPOID:\n\t\tcase TIMESTAMPTZOID:\n\t\t\t/* we continue ts_time_value_to_internal's incorrect handling of TIMESTAMPs for\n\t\t\t * compatibility */\n\t\t\treturn DirectFunctionCall1(ts_pg_unix_microseconds_to_timestamp, Int64GetDatum(value));\n\t\tcase DATEOID:\n\t\t\treturn DirectFunctionCall1(ts_pg_unix_microseconds_to_date, Int64GetDatum(value));\n\t\tcase UUIDOID:\n\t\t{\n\t\t\t/*\n\t\t\t * Convert the internal unixtime in ms to a UUID with the\n\t\t\t * non-timestamp bits set to zero. We do not set the version\n\t\t\t * either, because for ranges we only care about the prefix in\n\t\t\t * order to divide the whole UUID space into a set of slices.\n\t\t\t */\n\t\t\tconst pg_uuid_t *uuid = ts_create_uuid_v7_from_unixtime_us(value, true, false);\n\t\t\treturn UUIDPGetDatum(uuid);\n\t\t}\n\t\tdefault:\n\t\t\tif (ts_type_is_int8_binary_compatible(type))\n\t\t\t\treturn Int64GetDatum(value);\n\t\t\telog(ERROR,\n\t\t\t\t \"unknown time type \\\"%s\\\" in ts_internal_to_time_value\",\n\t\t\t\t format_type_be(type));\n\t\t\tpg_unreachable();\n\t}\n}\n\nTSDLLEXPORT int64\nts_internal_to_time_int64(int64 value, Oid type)\n{\n\tif (TS_TIME_IS_NOBEGIN(value, type))\n\t\treturn ts_time_datum_get_nobegin(type);\n\n\tif (TS_TIME_IS_NOEND(value, type))\n\t\treturn ts_time_datum_get_noend(type);\n\n\tswitch (type)\n\t{\n\t\tcase INT2OID:\n\t\tcase INT4OID:\n\t\tcase INT8OID:\n\t\t\treturn value;\n\t\tcase TIMESTAMPOID:\n\t\tcase TIMESTAMPTZOID:\n\t\tcase UUIDOID:\n\t\t\t/* we continue ts_time_value_to_internal's incorrect handling of TIMESTAMPs for\n\t\t\t * compatibility */\n\t\t\treturn DatumGetInt64(\n\t\t\t\tDirectFunctionCall1(ts_pg_unix_microseconds_to_timestamp, Int64GetDatum(value)));\n\t\tcase DATEOID:\n\t\t\treturn DatumGetInt64(\n\t\t\t\tDirectFunctionCall1(ts_pg_unix_microseconds_to_date, Int64GetDatum(value)));\n\t\tdefault:\n\t\t\telog(ERROR,\n\t\t\t\t \"unknown time type \\\"%s\\\" in ts_internal_to_time_value\",\n\t\t\t\t format_type_be(type));\n\t\t\tpg_unreachable();\n\t}\n}\n\nTSDLLEXPORT char *\nts_datum_to_string(Datum value, Oid type)\n{\n\tOid typoutputfunc;\n\tbool typIsVarlena;\n\tFmgrInfo typoutputinfo;\n\n\tgetTypeOutputInfo(type, &typoutputfunc, &typIsVarlena);\n\tfmgr_info(typoutputfunc, &typoutputinfo);\n\treturn OutputFunctionCall(&typoutputinfo, value);\n}\n\nTSDLLEXPORT char *\nts_internal_to_time_string(int64 value, Oid type)\n{\n\tDatum time_datum = ts_internal_to_time_value(value, type);\n\n\treturn ts_datum_to_string(time_datum, type);\n}\n\nTS_FUNCTION_INFO_V1(ts_pg_unix_microseconds_to_interval);\n\nDatum\nts_pg_unix_microseconds_to_interval(PG_FUNCTION_ARGS)\n{\n\tint64 microseconds = PG_GETARG_INT64(0);\n\tInterval *interval = palloc0(sizeof(*interval));\n\tinterval->day = microseconds / USECS_PER_DAY;\n\tinterval->time = microseconds % USECS_PER_DAY;\n\tPG_RETURN_INTERVAL_P(interval);\n}\n\nTSDLLEXPORT Datum\nts_internal_to_interval_value(int64 value, Oid type)\n{\n\tswitch (type)\n\t{\n\t\tcase INT2OID:\n\t\tcase INT4OID:\n\t\tcase INT8OID:\n\t\t\treturn ts_integer_to_internal_value(value, type);\n\t\tcase INTERVALOID:\n\t\t\treturn DirectFunctionCall1(ts_pg_unix_microseconds_to_interval, Int64GetDatum(value));\n\t\tdefault:\n\t\t\telog(ERROR,\n\t\t\t\t \"unknown time type \\\"%s\\\" in ts_internal_to_interval_value\",\n\t\t\t\t format_type_be(type));\n\t\t\tpg_unreachable();\n\t}\n}\n\nstatic Datum\nts_integer_to_internal_value(int64 value, Oid type)\n{\n\tswitch (type)\n\t{\n\t\tcase INT2OID:\n\t\t\treturn Int16GetDatum(value);\n\t\tcase INT4OID:\n\t\t\treturn Int32GetDatum(value);\n\t\tcase INT8OID:\n\t\t\treturn Int64GetDatum(value);\n\t\tdefault:\n\t\t\telog(ERROR,\n\t\t\t\t \"unknown time type \\\"%s\\\" in ts_internal_to_time_value\",\n\t\t\t\t format_type_be(type));\n\t\t\tpg_unreachable();\n\t}\n}\n\n/* Returns approximate period in microseconds */\nint64\nts_get_interval_period_approx(Interval *interval)\n{\n\treturn interval->time +\n\t\t   ((((int64) interval->month * DAYS_PER_MONTH) + interval->day) * USECS_PER_DAY);\n}\n\n#define DAYS_PER_WEEK 7\n#define DAYS_PER_QUARTER 89\n#define YEARS_PER_DECADE 10\n#define YEARS_PER_CENTURY 100\n#define YEARS_PER_MILLENNIUM 1000\n\n/* Returns approximate period in microseconds */\nint64\nts_date_trunc_interval_period_approx(text *units)\n{\n\tint decode_type, val;\n\tchar *lowunits =\n\t\tdowncase_truncate_identifier(VARDATA_ANY(units), VARSIZE_ANY_EXHDR(units), false);\n\n\tdecode_type = DecodeUnits(0, lowunits, &val);\n\n\tif (decode_type != UNITS)\n\t\treturn -1;\n\n\tswitch (val)\n\t{\n\t\tcase DTK_WEEK:\n\t\t\treturn DAYS_PER_WEEK * USECS_PER_DAY;\n\t\tcase DTK_MILLENNIUM:\n\t\t\treturn YEARS_PER_MILLENNIUM * DAYS_PER_YEAR * USECS_PER_DAY;\n\t\tcase DTK_CENTURY:\n\t\t\treturn YEARS_PER_CENTURY * DAYS_PER_YEAR * USECS_PER_DAY;\n\t\tcase DTK_DECADE:\n\t\t\treturn YEARS_PER_DECADE * DAYS_PER_YEAR * USECS_PER_DAY;\n\t\tcase DTK_YEAR:\n\t\t\treturn 1 * DAYS_PER_YEAR * USECS_PER_DAY;\n\t\tcase DTK_QUARTER:\n\t\t\treturn DAYS_PER_QUARTER * USECS_PER_DAY;\n\t\tcase DTK_MONTH:\n\t\t\treturn DAYS_PER_MONTH * USECS_PER_DAY;\n\t\tcase DTK_DAY:\n\t\t\treturn USECS_PER_DAY;\n\t\tcase DTK_HOUR:\n\t\t\treturn USECS_PER_HOUR;\n\t\tcase DTK_MINUTE:\n\t\t\treturn USECS_PER_MINUTE;\n\t\tcase DTK_SECOND:\n\t\t\treturn USECS_PER_SEC;\n\t\tcase DTK_MILLISEC:\n\t\t\treturn USECS_PER_SEC / 1000;\n\t\tcase DTK_MICROSEC:\n\t\t\treturn 1;\n\t\tdefault:\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"timestamp units \\\"%s\\\" not supported\", lowunits)));\n\t}\n\treturn -1;\n}\n\nOid\nts_inheritance_parent_relid(Oid relid)\n{\n\tRelation catalog;\n\tSysScanDesc scan;\n\tScanKeyData skey;\n\tOid parent = InvalidOid;\n\tHeapTuple tuple;\n\n\tcatalog = table_open(InheritsRelationId, AccessShareLock);\n\tScanKeyInit(&skey,\n\t\t\t\tAnum_pg_inherits_inhrelid,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_OIDEQ,\n\t\t\t\tObjectIdGetDatum(relid));\n\tscan = systable_beginscan(catalog, InheritsRelidSeqnoIndexId, true, NULL, 1, &skey);\n\ttuple = systable_getnext(scan);\n\n\tif (HeapTupleIsValid(tuple))\n\t\tparent = ((Form_pg_inherits) GETSTRUCT(tuple))->inhparent;\n\n\tsystable_endscan(scan);\n\ttable_close(catalog, AccessShareLock);\n\n\treturn parent;\n}\n\nTSDLLEXPORT bool\nts_type_is_int8_binary_compatible(Oid sourcetype)\n{\n\tHeapTuple tuple;\n\tForm_pg_cast castForm;\n\tbool result;\n\n\ttuple =\n\t\tSearchSysCache2(CASTSOURCETARGET, ObjectIdGetDatum(sourcetype), ObjectIdGetDatum(INT8OID));\n\tif (!HeapTupleIsValid(tuple))\n\t\treturn false; /* no cast */\n\tcastForm = (Form_pg_cast) GETSTRUCT(tuple);\n\tresult = castForm->castmethod == COERCION_METHOD_BINARY;\n\tReleaseSysCache(tuple);\n\treturn result;\n}\n\n/*\n * Create a fresh struct pointer that will contain copied contents of the tuple.\n * Note that this function uses GETSTRUCT, which will not work correctly for tuple types\n * that might have variable lengths.\n * Also note that the function assumes no NULLs in the tuple.\n */\nstatic void *\nts_create_struct_from_tuple(HeapTuple tuple, MemoryContext mctx, size_t alloc_size,\n\t\t\t\t\t\t\tsize_t copy_size)\n{\n\tvoid *struct_ptr = MemoryContextAllocZero(mctx, alloc_size);\n\n\t/*\n\t * Make sure the function is not used when the tuple contains NULLs.\n\t * Also compare the aligned sizes in the assert.\n\t */\n\tAssert(copy_size == MAXALIGN(tuple->t_len - tuple->t_data->t_hoff));\n\tmemcpy(struct_ptr, GETSTRUCT(tuple), copy_size);\n\n\treturn struct_ptr;\n}\n\nvoid *\nts_create_struct_from_slot(TupleTableSlot *slot, MemoryContext mctx, size_t alloc_size,\n\t\t\t\t\t\t   size_t copy_size)\n{\n\tbool should_free;\n\tHeapTuple tuple = ExecFetchSlotHeapTuple(slot, false, &should_free);\n\tvoid *result = ts_create_struct_from_tuple(tuple, mctx, alloc_size, copy_size);\n\n\tif (should_free)\n\t\theap_freetuple(tuple);\n\n\treturn result;\n}\n\nbool\nts_function_types_equal(Oid left[], Oid right[], int nargs)\n{\n\tint arg_index;\n\n\tfor (arg_index = 0; arg_index < nargs; arg_index++)\n\t{\n\t\tif (left[arg_index] != right[arg_index])\n\t\t\treturn false;\n\t}\n\treturn true;\n}\n\nOid\nts_get_function_oid(const char *funcname, const char *schema_name, int nargs, Oid arg_types[])\n{\n\tList *qualified_funcname =\n\t\tlist_make2(makeString(pstrdup(schema_name)), makeString(pstrdup(funcname)));\n\tFuncCandidateList func_candidates;\n\n\tfunc_candidates = FuncnameGetCandidates(qualified_funcname,\n\t\t\t\t\t\t\t\t\t\t\tnargs,\n\t\t\t\t\t\t\t\t\t\t\tNIL,\n\t\t\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\t\t\tfalse, /* include_out_arguments */\n\t\t\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\t\t\tfalse);\n\twhile (func_candidates != NULL)\n\t{\n\t\tif (func_candidates->nargs == nargs &&\n\t\t\tts_function_types_equal(func_candidates->args, arg_types, nargs))\n\t\t\treturn func_candidates->oid;\n\t\tfunc_candidates = func_candidates->next;\n\t}\n\n\telog(ERROR,\n\t\t \"failed to find function %s with %d args in schema \\\"%s\\\"\",\n\t\t funcname,\n\t\t nargs,\n\t\t schema_name);\n\n\treturn InvalidOid;\n}\n\n/*\n * Find a partitioning function with a given schema and name.\n *\n * The caller can optionally pass a filter function and a type of the argument\n * that the partitioning function should take.\n */\nOid\nts_lookup_proc_filtered(const char *schema, const char *funcname, Oid *rettype, proc_filter filter,\n\t\t\t\t\t\tvoid *filter_arg)\n{\n\tOid namespace_oid = LookupExplicitNamespace(schema, false);\n\tregproc func = InvalidOid;\n\tCatCList *catlist;\n\tint i;\n\n\t/*\n\t * We could use SearchSysCache3 to get by (name, args, namespace), but\n\t * that would not allow us to check for functions that take either\n\t * ANYELEMENTOID or a dimension-specific in the same search.\n\t */\n\tcatlist = SearchSysCacheList1(PROCNAMEARGSNSP, CStringGetDatum(funcname));\n\n\tfor (i = 0; i < catlist->n_members; i++)\n\t{\n\t\tHeapTuple proctup = &catlist->members[i]->tuple;\n\t\tForm_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup);\n\n\t\tif (procform->pronamespace == namespace_oid &&\n\t\t\t(filter == NULL || filter(procform, filter_arg)))\n\t\t{\n\t\t\tif (rettype)\n\t\t\t\t*rettype = procform->prorettype;\n\n\t\t\tfunc = procform->oid;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tReleaseSysCacheList(catlist);\n\n\treturn func;\n}\n\n/*\n * ts_get_operator\n *\n *    finds an operator given an exact specification (name, namespace,\n *    left and right type IDs).\n */\nOid\nts_get_operator(const char *name, Oid namespace, Oid left, Oid right)\n{\n\tHeapTuple tup;\n\tOid opoid = InvalidOid;\n\n\ttup = SearchSysCache4(OPERNAMENSP,\n\t\t\t\t\t\t  PointerGetDatum(name),\n\t\t\t\t\t\t  ObjectIdGetDatum(left),\n\t\t\t\t\t\t  ObjectIdGetDatum(right),\n\t\t\t\t\t\t  ObjectIdGetDatum(namespace));\n\tif (HeapTupleIsValid(tup))\n\t{\n\t\tForm_pg_operator oprform = (Form_pg_operator) GETSTRUCT(tup);\n\t\topoid = oprform->oid;\n\t\tReleaseSysCache(tup);\n\t}\n\n\treturn opoid;\n}\n\n/*\n * ts_get_cast_func\n *\n * returns Oid of functions that implements cast from source to target\n */\nOid\nts_get_cast_func(Oid source, Oid target)\n{\n\tOid result = InvalidOid;\n\tHeapTuple casttup;\n\n\tcasttup = SearchSysCache2(CASTSOURCETARGET, ObjectIdGetDatum(source), ObjectIdGetDatum(target));\n\tif (HeapTupleIsValid(casttup))\n\t{\n\t\tForm_pg_cast castform = (Form_pg_cast) GETSTRUCT(casttup);\n\n\t\tresult = castform->castfunc;\n\t\tReleaseSysCache(casttup);\n\t}\n\n\treturn result;\n}\n\nAppendRelInfo *\nts_get_appendrelinfo(PlannerInfo *root, Index rti, bool missing_ok)\n{\n\tListCell *lc;\n\t/* use append_rel_array if it has been setup */\n\tif (root->append_rel_array)\n\t{\n\t\tif (root->append_rel_array[rti])\n\t\t\treturn root->append_rel_array[rti];\n\t\tif (!missing_ok)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t\t errmsg(\"no appendrelinfo found for index %d\", rti)));\n\t\treturn NULL;\n\t}\n\n\tforeach (lc, root->append_rel_list)\n\t{\n\t\tAppendRelInfo *appinfo = lfirst(lc);\n\t\tif (appinfo->child_relid == rti)\n\t\t\treturn appinfo;\n\t}\n\tif (!missing_ok)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t errmsg(\"no appendrelinfo found for index %d\", rti)));\n\treturn NULL;\n}\n\n/*\n * Find an equivalence class member expression, all of whose Vars, come from\n * the indicated relation.\n *\n * This function has been copied from find_em_expr_for_rel in\n * contrib/postgres_fdw/postgres_fdw.c in postgres source.\n * This function was moved to postgres main in PG13 but was removed\n * again in PG15. So we use our own implementation for PG15+.\n */\nEquivalenceMember *\nts_find_em_for_rel(EquivalenceClass *ec, RelOptInfo *rel)\n{\n\tEquivalenceMember *em;\n#if PG18_GE\n\t/* Use specialized iterator to include child ems.\n\t *\n\t * https://github.com/postgres/postgres/commit/d69d45a5\n\t */\n\tEquivalenceMemberIterator it;\n\n\tsetup_eclass_member_iterator(&it, ec, bms_make_singleton(rel->relid));\n\twhile ((em = eclass_member_iterator_next(&it)) != NULL)\n\t{\n#else\n\tListCell *lc_em;\n\n\tforeach (lc_em, ec->ec_members)\n\t{\n\t\tem = lfirst(lc_em);\n#endif\n\n\t\tif (bms_is_subset(em->em_relids, rel->relids) && !bms_is_empty(em->em_relids))\n\t\t{\n\t\t\t/*\n\t\t\t * If there is more than one equivalence member whose Vars are\n\t\t\t * taken entirely from this relation, we'll be content to choose\n\t\t\t * any one of those.\n\t\t\t */\n\t\t\treturn em;\n\t\t}\n\t}\n\n\t/* We didn't find any suitable equivalence class member */\n\treturn NULL;\n}\n\nExpr *\nts_find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel)\n{\n\tEquivalenceMember *em = ts_find_em_for_rel(ec, rel);\n\treturn em ? em->em_expr : NULL;\n}\n\nbool\nts_has_row_security(Oid relid)\n{\n\tHeapTuple tuple;\n\tForm_pg_class classform;\n\tbool relrowsecurity;\n\tbool relforcerowsecurity;\n\n\t/* Fetch relation's relrowsecurity and relforcerowsecurity flags */\n\ttuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));\n\tif (!HeapTupleIsValid(tuple))\n\t\telog(ERROR, \"cache lookup failed for relid %u\", relid);\n\tclassform = (Form_pg_class) GETSTRUCT(tuple);\n\trelrowsecurity = classform->relrowsecurity;\n\trelforcerowsecurity = classform->relforcerowsecurity;\n\tReleaseSysCache(tuple);\n\treturn (relrowsecurity || relforcerowsecurity);\n}\n\nList *\nts_get_reloptions(Oid relid)\n{\n\tHeapTuple tuple;\n\tDatum datum;\n\tbool isnull;\n\tList *options = NIL;\n\n\tAssert(OidIsValid(relid));\n\n\ttuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));\n\n\tif (!HeapTupleIsValid(tuple))\n\t\telog(ERROR, \"cache lookup failed for relation %u\", relid);\n\n\tdatum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions, &isnull);\n\n\tif (!isnull && PointerIsValid(DatumGetPointer(datum)))\n\t\toptions = untransformRelOptions(datum);\n\n\tReleaseSysCache(tuple);\n\n\treturn options;\n}\n\n/*\n * Get the integer_now function for a dimension\n */\nOid\nts_get_integer_now_func(const Dimension *open_dim, bool fail_if_not_found)\n{\n\tOid rettype;\n\tOid now_func = InvalidOid;\n\tOid argtypes[] = { 0 };\n\n\trettype = ts_dimension_get_partition_type(open_dim);\n\n\tAssert(IS_INTEGER_TYPE(rettype));\n\n\tif (strlen(NameStr(open_dim->fd.integer_now_func)) == 0 &&\n\t\tstrlen(NameStr(open_dim->fd.integer_now_func_schema)) == 0)\n\t{\n\t\tif (fail_if_not_found)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_FUNCTION),\n\t\t\t\t\t (errmsg(\"integer_now function not set\"))));\n\t\telse\n\t\t\treturn now_func;\n\t}\n\n\tList *name = list_make2(makeString((char *) NameStr(open_dim->fd.integer_now_func_schema)),\n\t\t\t\t\t\t\tmakeString((char *) NameStr(open_dim->fd.integer_now_func)));\n\tnow_func = LookupFuncName(name, 0, argtypes, false);\n\n\tif (get_func_rettype(now_func) != rettype)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_FUNCTION),\n\t\t\t\t (errmsg(\"invalid integer_now function\"),\n\t\t\t\t  errhint(\"return type of function does not match dimension type\"))));\n\n\treturn now_func;\n}\n\n/* subtract passed in interval from the now.\n * Arguments:\n * now_func : function used to compute now.\n * interval : integer value\n * Returns:\n *  now_func() - interval\n */\nint64\nts_sub_integer_from_now(int64 interval, Oid time_dim_type, Oid now_func)\n{\n\tDatum now;\n\tint64 res;\n\n\tAssert(IS_INTEGER_TYPE(time_dim_type));\n\n\tnow = OidFunctionCall0(now_func);\n\tswitch (time_dim_type)\n\t{\n\t\tcase INT2OID:\n\t\t\tres = DatumGetInt16(now) - interval;\n\t\t\tif (res < PG_INT16_MIN || res > PG_INT16_MAX)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_INTERVAL_FIELD_OVERFLOW),\n\t\t\t\t\t\t errmsg(\"integer time overflow\")));\n\t\t\treturn res;\n\t\tcase INT4OID:\n\t\t\tres = DatumGetInt32(now) - interval;\n\t\t\tif (res < PG_INT32_MIN || res > PG_INT32_MAX)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_INTERVAL_FIELD_OVERFLOW),\n\t\t\t\t\t\t errmsg(\"integer time overflow\")));\n\t\t\treturn res;\n\t\tcase INT8OID:\n\t\t{\n\t\t\tbool overflow = pg_sub_s64_overflow(DatumGetInt64(now), interval, &res);\n\t\t\tif (overflow)\n\t\t\t{\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_INTERVAL_FIELD_OVERFLOW),\n\t\t\t\t\t\t errmsg(\"integer time overflow\")));\n\t\t\t}\n\t\t\treturn res;\n\t\t}\n\t\tdefault:\n\t\t\tpg_unreachable();\n\t}\n}\n\nTS_FUNCTION_INFO_V1(ts_subtract_integer_from_now);\nDatum\nts_subtract_integer_from_now(PG_FUNCTION_ARGS)\n{\n\tOid ht_relid = PG_GETARG_OID(0);\n\tint64 lag = PG_GETARG_INT64(1);\n\tCache *hcache;\n\tHypertable *ht = ts_hypertable_cache_get_cache_and_entry(ht_relid, CACHE_FLAG_NONE, &hcache);\n\tconst Dimension *dim = hyperspace_get_open_dimension(ht->space, 0);\n\n\tif (!dim)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"hypertable has no open partitioning dimension\")));\n\t}\n\n\tOid partitioning_type = ts_dimension_get_partition_type(dim);\n\n\tif (!IS_INTEGER_TYPE(partitioning_type))\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"hypertable has no integer partitioning dimension\")));\n\t}\n\n\tOid now_func = ts_get_integer_now_func(dim, true);\n\tif (!OidIsValid(now_func))\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"could not find valid integer_now function for hypertable\")));\n\t}\n\n\tint64 res = ts_sub_integer_from_now(lag, partitioning_type, now_func);\n\tts_cache_release(&hcache);\n\treturn Int64GetDatum(res);\n}\n\nTS_FUNCTION_INFO_V1(ts_relation_size);\nDatum\nts_relation_size(PG_FUNCTION_ARGS)\n{\n\tOid relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\tRelationSize relsize = { 0 };\n\tTupleDesc tupdesc;\n\tHeapTuple tuple;\n\tDatum values[4] = { 0 };\n\tbool nulls[4] = { false };\n\n\t/* Build a tuple descriptor for our result type */\n\tif (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"function returning record called in context \"\n\t\t\t\t\t\t\"that cannot accept type record\")));\n\n\tif (!OidIsValid(relid))\n\t\tPG_RETURN_NULL();\n\n\trelsize = ts_relation_size_impl(relid);\n\n\ttupdesc = BlessTupleDesc(tupdesc);\n\n\tvalues[0] = Int64GetDatum(relsize.total_size);\n\tvalues[1] = Int64GetDatum(relsize.heap_size);\n\tvalues[2] = Int64GetDatum(relsize.index_size);\n\tvalues[3] = Int64GetDatum(relsize.toast_size);\n\n\ttuple = heap_form_tuple(tupdesc, values, nulls);\n\n\treturn HeapTupleGetDatum(tuple);\n}\n\nRelationSize\nts_relation_size_impl(Oid relid)\n{\n\tRelationSize relsize = { 0 };\n\tDatum reloid = ObjectIdGetDatum(relid);\n\tRelation rel;\n\n\tDEBUG_WAITPOINT(\"relation_size_before_lock\");\n\t/* Open relation earlier to keep a lock during all function calls */\n\trel = try_relation_open(relid, AccessShareLock);\n\n\tif (!rel)\n\t\treturn relsize;\n\n\t/* Get to total relation size to be our calculation base */\n\trelsize.total_size = DatumGetInt64(DirectFunctionCall1(pg_total_relation_size, reloid));\n\n\t/* Get the indexes size of the relation (don't consider TOAST indexes) */\n\trelsize.index_size = DatumGetInt64(DirectFunctionCall1(pg_indexes_size, reloid));\n\n\t/* If exists an associated TOAST calculate the total size (including indexes) */\n\tif (OidIsValid(rel->rd_rel->reltoastrelid))\n\t\trelsize.toast_size =\n\t\t\tDatumGetInt64(DirectFunctionCall1(pg_total_relation_size,\n\t\t\t\t\t\t\t\t\t\t\t  ObjectIdGetDatum(rel->rd_rel->reltoastrelid)));\n\telse\n\t\trelsize.toast_size = 0;\n\n\trelation_close(rel, AccessShareLock);\n\n\t/* Calculate the HEAP size based on the total size and indexes plus toast */\n\trelsize.heap_size = relsize.total_size - (relsize.index_size + relsize.toast_size);\n\n\treturn relsize;\n}\n\n/*\n * Try to get cached size for a provided relation across all forks. The\n * size is returned in terms of number of blocks.\n *\n * The function calls the underlying smgrnblocks if there is no cached\n * data. That call populates the cache for subsequent invocations. This\n * cached data gets removed asynchronously by PG relcache invalidations\n * and then the refresh/cache cycle repeats till the next invalidation.\n */\nstatic int64\nts_try_relation_cached_size(Relation rel, bool verbose)\n{\n\tBlockNumber result = InvalidBlockNumber, nblocks = 0;\n\tForkNumber forkNum;\n\tbool cached = true;\n\n\tif (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind))\n\t\treturn (int64) nblocks;\n\n\t/* Get heap size, including FSM and VM */\n\tfor (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++)\n\t{\n\t\tresult = RelationGetSmgr(rel)->smgr_cached_nblocks[forkNum];\n\n\t\tif (result != InvalidBlockNumber)\n\t\t{\n\t\t\tnblocks += result;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (smgrexists(RelationGetSmgr(rel), forkNum))\n\t\t\t{\n\t\t\t\tcached = false;\n\t\t\t\tnblocks += smgrnblocks(RelationGetSmgr(rel), forkNum);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (verbose)\n\t\tereport(DEBUG2,\n\t\t\t\t(errmsg(\"%s for %s\",\n\t\t\t\t\t\tcached ? \"Cached size used\" : \"Fetching actual size\",\n\t\t\t\t\t\tRelationGetRelationName(rel))));\n\n\t/* convert the size into bytes and return */\n\treturn (int64) nblocks * BLCKSZ;\n}\n\nstatic RelationSize\nts_relation_approximate_size_impl(Oid relid)\n{\n\tRelationSize relsize = { 0 };\n\tRelation rel;\n\n\tDEBUG_WAITPOINT(\"relation_approximate_size_before_lock\");\n\t/* Open relation earlier to keep a lock during all function calls */\n\trel = try_relation_open(relid, AccessShareLock);\n\n\tif (!rel)\n\t\treturn relsize;\n\n\t/* Get the main heap size */\n\trelsize.heap_size = ts_try_relation_cached_size(rel, false);\n\n\t/* Get the size of the relation's indexes */\n\tif (rel->rd_rel->relhasindex)\n\t{\n\t\tList *index_oids = RelationGetIndexList(rel);\n\t\tListCell *cell;\n\n\t\tforeach (cell, index_oids)\n\t\t{\n\t\t\tOid idxOid = lfirst_oid(cell);\n\t\t\tRelation idxRel;\n\n\t\t\tidxRel = relation_open(idxOid, AccessShareLock);\n\t\t\trelsize.index_size += ts_try_relation_cached_size(idxRel, false);\n\t\t\trelation_close(idxRel, AccessShareLock);\n\t\t}\n\t}\n\n\t/* If there's an associated TOAST table, calculate the total size (including its indexes) */\n\tif (OidIsValid(rel->rd_rel->reltoastrelid))\n\t{\n\t\tRelation toastRel;\n\t\tList *index_oids;\n\t\tListCell *cell;\n\n\t\ttoastRel = relation_open(rel->rd_rel->reltoastrelid, AccessShareLock);\n\t\trelsize.toast_size = ts_try_relation_cached_size(toastRel, false);\n\n\t\t/* Get the indexes size of the TOAST relation */\n\t\tindex_oids = RelationGetIndexList(toastRel);\n\t\tforeach (cell, index_oids)\n\t\t{\n\t\t\tOid idxOid = lfirst_oid(cell);\n\t\t\tRelation idxRel;\n\n\t\t\tidxRel = relation_open(idxOid, AccessShareLock);\n\t\t\trelsize.toast_size += ts_try_relation_cached_size(idxRel, false);\n\t\t\trelation_close(idxRel, AccessShareLock);\n\t\t}\n\n\t\trelation_close(toastRel, AccessShareLock);\n\t}\n\n\trelation_close(rel, AccessShareLock);\n\n\t/* Add up the total size based on the heap size, indexes and toast */\n\trelsize.total_size = relsize.heap_size + relsize.index_size + relsize.toast_size;\n\n\treturn relsize;\n}\n\nTS_FUNCTION_INFO_V1(ts_relation_approximate_size);\nDatum\nts_relation_approximate_size(PG_FUNCTION_ARGS)\n{\n\tOid relid = PG_GETARG_OID(0);\n\tRelationSize relsize = { 0 };\n\tTupleDesc tupdesc;\n\tHeapTuple tuple;\n\tDatum values[4] = { 0 };\n\tbool nulls[4] = { false };\n\n\t/* Build a tuple descriptor for our result type */\n\tif (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"function returning record called in context \"\n\t\t\t\t\t\t\"that cannot accept type record\")));\n\n\t/* check if object exists, return NULL otherwise */\n\tif (get_rel_name(relid) == NULL)\n\t\tPG_RETURN_NULL();\n\n\trelsize = ts_relation_approximate_size_impl(relid);\n\n\ttupdesc = BlessTupleDesc(tupdesc);\n\n\tvalues[0] = Int64GetDatum(relsize.total_size);\n\tvalues[1] = Int64GetDatum(relsize.heap_size);\n\tvalues[2] = Int64GetDatum(relsize.index_size);\n\tvalues[3] = Int64GetDatum(relsize.toast_size);\n\n\ttuple = heap_form_tuple(tupdesc, values, nulls);\n\n\treturn HeapTupleGetDatum(tuple);\n}\n\nstatic void\ninit_scan_by_hypertable_id(ScanIterator *iterator, int32 hypertable_id)\n{\n\titerator->ctx.index = catalog_get_index(ts_catalog_get(), CHUNK, CHUNK_HYPERTABLE_ID_INDEX);\n\tts_scan_iterator_scan_key_init(iterator,\n\t\t\t\t\t\t\t\t   Anum_chunk_hypertable_id_idx_hypertable_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(hypertable_id));\n}\n\n#define ADD_RELATIONSIZE(total, rel)                                                               \\\n\tdo                                                                                             \\\n\t{                                                                                              \\\n\t\t(total).heap_size += (rel).heap_size;                                                      \\\n\t\t(total).toast_size += (rel).toast_size;                                                    \\\n\t\t(total).index_size += (rel).index_size;                                                    \\\n\t\t(total).total_size += (rel).total_size;                                                    \\\n\t} while (0)\n\nTS_FUNCTION_INFO_V1(ts_hypertable_approximate_size);\nDatum\nts_hypertable_approximate_size(PG_FUNCTION_ARGS)\n{\n\tOid relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\tRelationSize total_relsize = { 0 };\n\tTupleDesc tupdesc;\n\tHeapTuple tuple;\n\tDatum values[4] = { 0 };\n\tbool nulls[4] = { false };\n\tCache *hcache;\n\tHypertable *ht;\n\tScanIterator iterator = ts_scan_iterator_create(CHUNK, RowExclusiveLock, CurrentMemoryContext);\n\n\t/* Build a tuple descriptor for our result type */\n\tif (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"function returning record called in context \"\n\t\t\t\t\t\t\"that cannot accept type record\")));\n\n\tif (!OidIsValid(relid))\n\t\tPG_RETURN_NULL();\n\n\t/* go ahead only if this is a hypertable or a CAgg */\n\thcache = ts_hypertable_cache_pin();\n\tht = ts_resolve_hypertable_from_table_or_cagg(hcache, relid, true);\n\tif (ht == NULL)\n\t{\n\t\tts_cache_release(&hcache);\n\t\tPG_RETURN_NULL();\n\t}\n\n\t/* get the main hypertable size */\n\ttotal_relsize = ts_relation_approximate_size_impl(relid);\n\n\titerator = ts_scan_iterator_create(CHUNK, RowExclusiveLock, CurrentMemoryContext);\n\tinit_scan_by_hypertable_id(&iterator, ht->fd.id);\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tbool isnull, is_osm_chunk;\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tDatum id = slot_getattr(ti->slot, Anum_chunk_id, &isnull);\n\t\tDatum comp_id = DatumGetInt32(slot_getattr(ti->slot, Anum_chunk_id, &isnull));\n\t\tint32 chunk_id, compressed_chunk_id;\n\t\tOid chunk_relid, compressed_chunk_relid;\n\t\tRelationSize chunk_relsize, compressed_chunk_relsize;\n\n\t\tif (isnull)\n\t\t\tcontinue;\n\n\t\tchunk_id = DatumGetInt32(id);\n\n\t\t/* avoid if it's an OSM chunk */\n\t\tis_osm_chunk = slot_getattr(ti->slot, Anum_chunk_osm_chunk, &isnull);\n\t\tAssert(!isnull);\n\t\tif (is_osm_chunk)\n\t\t\tcontinue;\n\n\t\tchunk_relid = ts_chunk_get_relid(chunk_id, false);\n\t\tchunk_relsize = ts_relation_approximate_size_impl(chunk_relid);\n\t\t/* add this chunk's size to the total size */\n\t\tADD_RELATIONSIZE(total_relsize, chunk_relsize);\n\n\t\t/* check if the chunk has a compressed counterpart and add if yes */\n\t\tcomp_id = slot_getattr(ti->slot, Anum_chunk_compressed_chunk_id, &isnull);\n\t\tif (isnull)\n\t\t\tcontinue;\n\n\t\tcompressed_chunk_id = DatumGetInt32(comp_id);\n\t\tcompressed_chunk_relid = ts_chunk_get_relid(compressed_chunk_id, false);\n\t\tcompressed_chunk_relsize = ts_relation_approximate_size_impl(compressed_chunk_relid);\n\t\t/* add this compressed chunk's size to the total size */\n\t\tADD_RELATIONSIZE(total_relsize, compressed_chunk_relsize);\n\t}\n\tts_scan_iterator_close(&iterator);\n\n\ttupdesc = BlessTupleDesc(tupdesc);\n\n\tvalues[0] = Int64GetDatum(total_relsize.heap_size);\n\tvalues[1] = Int64GetDatum(total_relsize.index_size);\n\tvalues[2] = Int64GetDatum(total_relsize.toast_size);\n\tvalues[3] = Int64GetDatum(total_relsize.total_size);\n\n\ttuple = heap_form_tuple(tupdesc, values, nulls);\n\tts_cache_release(&hcache);\n\n\treturn HeapTupleGetDatum(tuple);\n}\n\n#define STR_VALUE(str) #str\n#define NODE_CASE(name)                                                                            \\\n\tcase T_##name:                                                                                 \\\n\t\treturn STR_VALUE(name)\n\n/*\n * Return a string with the name of the node.\n *\n */\nconst char *\nts_get_node_name(Node *node)\n{\n\t/* tags are defined in nodes/nodes.h postgres source */\n\tswitch (nodeTag(node))\n\t{\n\t\t/*\n\t\t * primitive nodes (primnodes.h)\n\t\t */\n\t\tNODE_CASE(Alias);\n\t\tNODE_CASE(RangeVar);\n\t\tNODE_CASE(TableFunc);\n\t\tNODE_CASE(IntoClause);\n\t\tNODE_CASE(Var);\n\t\tNODE_CASE(Const);\n\t\tNODE_CASE(Param);\n\t\tNODE_CASE(Aggref);\n\t\tNODE_CASE(GroupingFunc);\n\t\tNODE_CASE(WindowFunc);\n\t\tNODE_CASE(SubscriptingRef);\n\t\tNODE_CASE(FuncExpr);\n\t\tNODE_CASE(NamedArgExpr);\n\t\tNODE_CASE(OpExpr);\n\t\tNODE_CASE(DistinctExpr);\n\t\tNODE_CASE(NullIfExpr);\n\t\tNODE_CASE(ScalarArrayOpExpr);\n\t\tNODE_CASE(BoolExpr);\n\t\tNODE_CASE(SubLink);\n\t\tNODE_CASE(SubPlan);\n\t\tNODE_CASE(AlternativeSubPlan);\n\t\tNODE_CASE(FieldSelect);\n\t\tNODE_CASE(FieldStore);\n\t\tNODE_CASE(RelabelType);\n\t\tNODE_CASE(CoerceViaIO);\n\t\tNODE_CASE(ArrayCoerceExpr);\n\t\tNODE_CASE(ConvertRowtypeExpr);\n\t\tNODE_CASE(CollateExpr);\n\t\tNODE_CASE(CaseExpr);\n\t\tNODE_CASE(CaseWhen);\n\t\tNODE_CASE(CaseTestExpr);\n\t\tNODE_CASE(ArrayExpr);\n\t\tNODE_CASE(RowExpr);\n\t\tNODE_CASE(RowCompareExpr);\n\t\tNODE_CASE(CoalesceExpr);\n\t\tNODE_CASE(MinMaxExpr);\n\t\tNODE_CASE(SQLValueFunction);\n\t\tNODE_CASE(XmlExpr);\n\t\tNODE_CASE(NullTest);\n\t\tNODE_CASE(BooleanTest);\n\t\tNODE_CASE(CoerceToDomain);\n\t\tNODE_CASE(CoerceToDomainValue);\n\t\tNODE_CASE(SetToDefault);\n\t\tNODE_CASE(CurrentOfExpr);\n\t\tNODE_CASE(NextValueExpr);\n\t\tNODE_CASE(InferenceElem);\n\t\tNODE_CASE(TargetEntry);\n\t\tNODE_CASE(RangeTblRef);\n\t\tNODE_CASE(JoinExpr);\n\t\tNODE_CASE(FromExpr);\n\t\tNODE_CASE(OnConflictExpr);\n\n\t\t/*\n\t\t * plan nodes (plannodes.h)\n\t\t */\n#if PG16_LT\n\t\tNODE_CASE(Plan);\n\t\tNODE_CASE(Scan);\n\t\tNODE_CASE(Join);\n#endif\n\t\tNODE_CASE(Result);\n\t\tNODE_CASE(ProjectSet);\n\t\tNODE_CASE(ModifyTable);\n\t\tNODE_CASE(Append);\n\t\tNODE_CASE(MergeAppend);\n\t\tNODE_CASE(RecursiveUnion);\n\t\tNODE_CASE(BitmapAnd);\n\t\tNODE_CASE(BitmapOr);\n\t\tNODE_CASE(SeqScan);\n\t\tNODE_CASE(SampleScan);\n\t\tNODE_CASE(IndexScan);\n\t\tNODE_CASE(IndexOnlyScan);\n\t\tNODE_CASE(BitmapIndexScan);\n\t\tNODE_CASE(BitmapHeapScan);\n\t\tNODE_CASE(TidScan);\n\t\tNODE_CASE(SubqueryScan);\n\t\tNODE_CASE(FunctionScan);\n\t\tNODE_CASE(ValuesScan);\n\t\tNODE_CASE(TableFuncScan);\n\t\tNODE_CASE(CteScan);\n\t\tNODE_CASE(NamedTuplestoreScan);\n\t\tNODE_CASE(WorkTableScan);\n\t\tNODE_CASE(ForeignScan);\n\t\tNODE_CASE(CustomScan);\n\t\tNODE_CASE(NestLoop);\n\t\tNODE_CASE(MergeJoin);\n\t\tNODE_CASE(HashJoin);\n\t\tNODE_CASE(Material);\n\t\tNODE_CASE(Sort);\n\t\tNODE_CASE(Group);\n\t\tNODE_CASE(Agg);\n\t\tNODE_CASE(WindowAgg);\n\t\tNODE_CASE(Unique);\n\t\tNODE_CASE(Gather);\n\t\tNODE_CASE(GatherMerge);\n\t\tNODE_CASE(Hash);\n\t\tNODE_CASE(SetOp);\n\t\tNODE_CASE(LockRows);\n\t\tNODE_CASE(Limit);\n\n\t\t/*\n\t\t * planner nodes (pathnodes.h)\n\t\t */\n\t\tNODE_CASE(IndexPath);\n\t\tNODE_CASE(BitmapHeapPath);\n\t\tNODE_CASE(BitmapAndPath);\n\t\tNODE_CASE(BitmapOrPath);\n\t\tNODE_CASE(TidPath);\n\t\tNODE_CASE(SubqueryScanPath);\n\t\tNODE_CASE(ForeignPath);\n\t\tNODE_CASE(NestPath);\n\t\tNODE_CASE(MergePath);\n\t\tNODE_CASE(HashPath);\n\t\tNODE_CASE(AppendPath);\n\t\tNODE_CASE(MergeAppendPath);\n\t\tNODE_CASE(GroupResultPath);\n\t\tNODE_CASE(MaterialPath);\n\t\tNODE_CASE(UniquePath);\n\t\tNODE_CASE(GatherPath);\n\t\tNODE_CASE(GatherMergePath);\n\t\tNODE_CASE(ProjectionPath);\n\t\tNODE_CASE(ProjectSetPath);\n\t\tNODE_CASE(SortPath);\n\t\tNODE_CASE(GroupPath);\n\t\tNODE_CASE(UpperUniquePath);\n\t\tNODE_CASE(AggPath);\n\t\tNODE_CASE(GroupingSetsPath);\n\t\tNODE_CASE(MinMaxAggPath);\n\t\tNODE_CASE(WindowAggPath);\n\t\tNODE_CASE(SetOpPath);\n\t\tNODE_CASE(RecursiveUnionPath);\n\t\tNODE_CASE(LockRowsPath);\n\t\tNODE_CASE(ModifyTablePath);\n\t\tNODE_CASE(LimitPath);\n\n\t\tcase T_Path:\n\t\t\tswitch (castNode(Path, node)->pathtype)\n\t\t\t{\n\t\t\t\tNODE_CASE(SeqScan);\n\t\t\t\tNODE_CASE(SampleScan);\n\t\t\t\tNODE_CASE(SubqueryScan);\n\t\t\t\tNODE_CASE(FunctionScan);\n\t\t\t\tNODE_CASE(TableFuncScan);\n\t\t\t\tNODE_CASE(ValuesScan);\n\t\t\t\tNODE_CASE(CteScan);\n\t\t\t\tNODE_CASE(WorkTableScan);\n\t\t\t\tdefault:\n\t\t\t\t\treturn psprintf(\"Path (%d)\", castNode(Path, node)->pathtype);\n\t\t\t}\n\n\t\tcase T_CustomPath:\n\t\t\treturn psprintf(\"CustomPath (%s)\", castNode(CustomPath, node)->methods->CustomName);\n\n\t\tdefault:\n\t\t\treturn psprintf(\"Node (%d)\", nodeTag(node));\n\t}\n}\n\n/*\n * Implementation marked unused in PostgreSQL lsyscache.c\n */\nint\nts_get_relnatts(Oid relid)\n{\n\tHeapTuple tp;\n\tForm_pg_class reltup;\n\tint result;\n\n\ttp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));\n\tif (!HeapTupleIsValid(tp))\n\t\treturn InvalidAttrNumber;\n\n\treltup = (Form_pg_class) GETSTRUCT(tp);\n\tresult = reltup->relnatts;\n\n\tReleaseSysCache(tp);\n\treturn result;\n}\n\n/*\n * Wrap AlterTableInternal() for event trigger handling.\n *\n * AlterTableInternal can be called as a utility command, which is common in a\n * SQL function that alters a table in some form when called in the form\n * SELECT <cmd> INTO <table>. This is transformed into a process utility\n * command (CREATE TABLE AS), which expects an event trigger context to be\n * set up.\n *\n * The \"cmd\" parameter can be set to a higher-level command that caused the\n * alter table to occur. If \"cmd\" is set to NULL, the \"cmds\" list will be used\n * instead.\n */\nvoid\nts_alter_table_with_event_trigger(Oid relid, Node *cmd, List *cmds, bool recurse)\n{\n\tif (cmd == NULL)\n\t\tcmd = (Node *) cmds;\n\n\tEventTriggerAlterTableStart(cmd);\n\tAlterTableInternal(relid, cmds, recurse);\n\tEventTriggerAlterTableEnd();\n}\n\nvoid\nts_copy_relation_acl(const Oid source_relid, const Oid target_relid, const Oid owner_id)\n{\n\tHeapTuple source_tuple;\n\tbool is_null;\n\tDatum acl_datum;\n\tRelation class_rel;\n\n\t/* We open it here since there is no point in trying to update the tuples\n\t * if we cannot open the Relation catalog table */\n\tclass_rel = table_open(RelationRelationId, RowExclusiveLock);\n\n\tsource_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(source_relid));\n\tAssert(HeapTupleIsValid(source_tuple));\n\n\t/* We only bother about setting the ACL if the source relation ACL is\n\t * non-null */\n\tacl_datum = SysCacheGetAttr(RELOID, source_tuple, Anum_pg_class_relacl, &is_null);\n\n\tif (!is_null)\n\t{\n\t\tHeapTuple target_tuple, newtuple;\n\t\tDatum new_val[Natts_pg_class] = { 0 };\n\t\tbool new_null[Natts_pg_class] = { false };\n\t\tbool new_repl[Natts_pg_class] = { false };\n\t\tAcl *acl = DatumGetAclP(acl_datum);\n\n\t\tnew_repl[AttrNumberGetAttrOffset(Anum_pg_class_relacl)] = true;\n\t\tnew_val[AttrNumberGetAttrOffset(Anum_pg_class_relacl)] = PointerGetDatum(acl);\n\n\t\t/*\n\t\t * ts_copy_relation_acl() is typically used to copy ACLs from the hypertable\n\t\t * to a newly created chunk. The creation is done via DefineRelation(),\n\t\t * which takes an AccessExclusiveLock and should be enough to handle any\n\t\t * inplace update issues.\n\t\t */\n\t\tAssertSufficientPgClassUpdateLockHeld(target_relid);\n\n\t\t/* Find the tuple for the target in `pg_class` */\n\t\ttarget_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(target_relid));\n\t\tAssert(HeapTupleIsValid(target_tuple));\n\n\t\t/* Update the relacl for the target tuple to use the acl from the source */\n\t\tnewtuple = heap_modify_tuple(target_tuple,\n\t\t\t\t\t\t\t\t\t RelationGetDescr(class_rel),\n\t\t\t\t\t\t\t\t\t new_val,\n\t\t\t\t\t\t\t\t\t new_null,\n\t\t\t\t\t\t\t\t\t new_repl);\n\t\tCatalogTupleUpdate(class_rel, &newtuple->t_self, newtuple);\n\n\t\t/* We need to update the shared dependencies as well to indicate that\n\t\t * the target is dependent on any roles that the source is\n\t\t * dependent on. */\n\t\tOid *newmembers;\n\t\tint nnewmembers = aclmembers(acl, &newmembers);\n\n\t\t/* The list of old members is intentionally empty since we are using\n\t\t * updateAclDependencies to set the ACL for the target. We can use NULL\n\t\t * because getOidListDiff, which is called from updateAclDependencies,\n\t\t * can handle that. */\n\t\tupdateAclDependencies(RelationRelationId,\n\t\t\t\t\t\t\t  target_relid,\n\t\t\t\t\t\t\t  0,\n\t\t\t\t\t\t\t  owner_id,\n\t\t\t\t\t\t\t  0,\n\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t  nnewmembers,\n\t\t\t\t\t\t\t  newmembers);\n\n\t\theap_freetuple(newtuple);\n\t\tReleaseSysCache(target_tuple);\n\t}\n\n\tReleaseSysCache(source_tuple);\n\ttable_close(class_rel, RowExclusiveLock);\n}\n\n/*\n * Map attno from source relation to target relation by column name\n */\nAttrNumber\nts_map_attno(Oid src_rel, Oid dst_rel, AttrNumber attno)\n{\n\tchar *attname = get_attname(src_rel, attno, false);\n\tAttrNumber dst_attno = get_attnum(dst_rel, attname);\n\n\t/*\n\t * For any chunk mappings we do this should never happen.\n\t */\n\tif (dst_attno == InvalidAttrNumber)\n\t\telog(ERROR,\n\t\t\t \"could not map attribute number from relation \\\"%s\\\" to \\\"%s\\\" for column \\\"%s\\\"\",\n\t\t\t get_rel_name(src_rel),\n\t\t\t get_rel_name(dst_rel),\n\t\t\t attname);\n\n\tpfree(attname);\n\treturn dst_attno;\n}\n\nbool\nts_relation_has_tuples(Relation rel)\n{\n\tTableScanDesc scandesc = table_beginscan(rel, GetActiveSnapshot(), 0, NULL);\n\tTupleTableSlot *slot =\n\t\tMakeSingleTupleTableSlot(RelationGetDescr(rel), table_slot_callbacks(rel));\n\tbool hastuples = table_scan_getnextslot(scandesc, ForwardScanDirection, slot);\n\n\ttable_endscan(scandesc);\n\tExecDropSingleTupleTableSlot(slot);\n\treturn hastuples;\n}\n\nbool\nts_table_has_tuples(Oid table_relid, LOCKMODE lockmode)\n{\n\tRelation rel = table_open(table_relid, lockmode);\n\tbool hastuples = ts_relation_has_tuples(rel);\n\n\ttable_close(rel, lockmode);\n\treturn hastuples;\n}\n\n/*\n * This is copied from PostgreSQL 16.0 since versions before 16.0 does not\n * support lists for privileges.\n */\nstatic AclMode\nts_convert_any_priv_string(text *priv_type_text, const priv_map *privileges)\n{\n\tAclMode result = 0;\n\tchar *priv_type = text_to_cstring(priv_type_text);\n\tchar *chunk;\n\tchar *next_chunk;\n\n\t/* We rely on priv_type being a private, modifiable string */\n\tfor (chunk = priv_type; chunk; chunk = next_chunk)\n\t{\n\t\tint chunk_len;\n\t\tconst priv_map *this_priv;\n\n\t\t/* Split string at commas */\n\t\tnext_chunk = strchr(chunk, ',');\n\t\tif (next_chunk)\n\t\t\t*next_chunk++ = '\\0';\n\n\t\t/* Drop leading/trailing whitespace in this chunk */\n\t\twhile (*chunk && isspace((unsigned char) *chunk))\n\t\t\tchunk++;\n\t\tchunk_len = strlen(chunk);\n\t\twhile (chunk_len > 0 && isspace((unsigned char) chunk[chunk_len - 1]))\n\t\t\tchunk_len--;\n\t\tchunk[chunk_len] = '\\0';\n\n\t\t/* Match to the privileges list */\n\t\tfor (this_priv = privileges; this_priv->name; this_priv++)\n\t\t{\n\t\t\tif (pg_strcasecmp(this_priv->name, chunk) == 0)\n\t\t\t{\n\t\t\t\tresult |= this_priv->value;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!this_priv->name)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"unrecognized privilege type: \\\"%s\\\"\", chunk)));\n\t}\n\n\tpfree(priv_type);\n\treturn result;\n}\n\n/*\n * This is copied from PostgreSQL 16.0 since versions before 16.0 does not\n * support lists for privileges but we need that.\n */\nDatum\nts_makeaclitem(PG_FUNCTION_ARGS)\n{\n\tOid grantee = PG_GETARG_OID(0);\n\tOid grantor = PG_GETARG_OID(1);\n\ttext *privtext = PG_GETARG_TEXT_PP(2);\n\tbool goption = PG_GETARG_BOOL(3);\n\tAclItem *result;\n\tAclMode priv;\n\tstatic const priv_map any_priv_map[] = {\n\t\t{ \"SELECT\", ACL_SELECT },\n\t\t{ \"INSERT\", ACL_INSERT },\n\t\t{ \"UPDATE\", ACL_UPDATE },\n\t\t{ \"DELETE\", ACL_DELETE },\n\t\t{ \"TRUNCATE\", ACL_TRUNCATE },\n\t\t{ \"REFERENCES\", ACL_REFERENCES },\n\t\t{ \"TRIGGER\", ACL_TRIGGER },\n\t\t{ \"EXECUTE\", ACL_EXECUTE },\n\t\t{ \"USAGE\", ACL_USAGE },\n\t\t{ \"CREATE\", ACL_CREATE },\n\t\t{ \"TEMP\", ACL_CREATE_TEMP },\n\t\t{ \"TEMPORARY\", ACL_CREATE_TEMP },\n\t\t{ \"CONNECT\", ACL_CONNECT },\n#if PG16_GE\n\t\t{ \"SET\", ACL_SET },\n\t\t{ \"ALTER SYSTEM\", ACL_ALTER_SYSTEM },\n#endif\n#if PG17_GE\n\t\t{ \"MAINTAIN\", ACL_MAINTAIN },\n#endif\n\t\t{ \"RULE\", 0 }, /* ignore old RULE privileges */\n\t\t{ NULL, 0 }\n\t};\n\n\tpriv = ts_convert_any_priv_string(privtext, any_priv_map);\n\n\tresult = (AclItem *) palloc(sizeof(AclItem));\n\n\tresult->ai_grantee = grantee;\n\tresult->ai_grantor = grantor;\n\n\tACLITEM_SET_PRIVS_GOPTIONS(*result, priv, (goption ? priv : ACL_NO_RIGHTS));\n\n\tPG_RETURN_ACLITEM_P(result);\n}\n\n/*\n * heap_form_tuple using NullableDatum array instead of two arrays for\n * values and nulls\n */\nHeapTuple\nts_heap_form_tuple(TupleDesc tupleDescriptor, NullableDatum *datums)\n{\n\tint numElements = tupleDescriptor->natts;\n\tDatum *values = palloc0(sizeof(Datum) * numElements);\n\tbool *nulls = palloc0(sizeof(bool) * numElements);\n\n\tfor (int i = 0; i < numElements; i++)\n\t{\n\t\tvalues[i] = datums[i].value;\n\t\tnulls[i] = datums[i].isnull;\n\t}\n\n\treturn heap_form_tuple(tupleDescriptor, values, nulls);\n}\n\n/*\n * To not introduce shared object dependencies on functions in extension update\n * scripts we use this stub function as placeholder whenever we need to reference\n * c functions in the update scripts.\n */\nTS_FUNCTION_INFO_V1(ts_update_placeholder);\nDatum\nts_update_placeholder(PG_FUNCTION_ARGS)\n{\n\telog(ERROR, \"this stub function is used only as placeholder during extension updates\");\n\tPG_RETURN_NULL();\n}\n\n/*\n * Get relation information from the syscache in one call.\n *\n * Returns relid and relkind. All are non-optional.\n */\nvoid\nts_get_rel_info_by_name(const char *relnamespace, const char *relname, Oid *relid, char *relkind)\n{\n\tHeapTuple tuple;\n\tForm_pg_class cform;\n\tOid namespaceoid = get_namespace_oid(relnamespace, false);\n\n\ttuple = SearchSysCache2(RELNAMENSP, PointerGetDatum(relname), ObjectIdGetDatum(namespaceoid));\n\n\tif (!HeapTupleIsValid(tuple))\n\t\telog(ERROR, \"cache lookup failed for relation %s.%s\", relnamespace, relname);\n\n\tcform = (Form_pg_class) GETSTRUCT(tuple);\n\t*relid = cform->oid;\n\t*relkind = cform->relkind;\n\tReleaseSysCache(tuple);\n}\n\nOid\nts_get_rel_am(Oid relid)\n{\n\tHeapTuple tuple;\n\tForm_pg_class cform;\n\tOid amoid;\n\n\ttuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));\n\n\tif (!HeapTupleIsValid(tuple))\n\t\telog(ERROR, \"cache lookup failed for relation %u\", relid);\n\n\tcform = (Form_pg_class) GETSTRUCT(tuple);\n\tamoid = cform->relam;\n\tReleaseSysCache(tuple);\n\n\treturn amoid;\n}\n\n/*\n * Set reloption for relation.\n *\n * Most of the code is from ATExecSetRelOptions() in tablecmds.c since that\n * function is static and we also need to do a slightly different job.\n */\nstatic void\nrelation_set_reloption_impl(Relation rel, List *options, LOCKMODE lockmode)\n{\n\tDatum repl_val[Natts_pg_class] = { 0 };\n\tbool repl_null[Natts_pg_class] = { false };\n\tbool repl_repl[Natts_pg_class] = { false };\n\tbool isnull;\n\n\tAssert(rel->rd_rel->relkind == RELKIND_RELATION || rel->rd_rel->relkind == RELKIND_TOASTVALUE);\n\n\tif (options == NIL)\n\t\treturn; /* nothing to do */\n\n\tTS_DEBUG_LOG(\"setting reloptions for %s\", RelationGetRelationName(rel));\n\n\tRelation pgclass = table_open(RelationRelationId, RowExclusiveLock);\n\tOid relid = RelationGetRelid(rel);\n\tHeapTuple tuple = SearchSysCacheLockedCopy1(RELOID, ObjectIdGetDatum(relid));\n\tif (!HeapTupleIsValid(tuple))\n\t\telog(ERROR, \"cache lookup failed for relation %u\", relid);\n#ifdef SYSCACHE_TUPLE_LOCK_NEEDED\n\tItemPointerData otid = tuple->t_self;\n#endif\n\n\t/* Get the old reloptions */\n\tDatum datum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions, &isnull);\n\n\t/* Generate new proposed reloptions (text array) */\n\tDatum newOptions =\n\t\ttransformRelOptions(isnull ? UnassignedDatum : datum, options, NULL, NULL, false, false);\n\t(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);\n\n\tif (newOptions)\n\t\trepl_val[AttrNumberGetAttrOffset(Anum_pg_class_reloptions)] = newOptions;\n\telse\n\t\trepl_null[AttrNumberGetAttrOffset(Anum_pg_class_reloptions)] = true;\n\n\trepl_repl[AttrNumberGetAttrOffset(Anum_pg_class_reloptions)] = true;\n\n\tHeapTuple newtuple =\n\t\theap_modify_tuple(tuple, RelationGetDescr(pgclass), repl_val, repl_null, repl_repl);\n\n\tCatalogTupleUpdate(pgclass, &newtuple->t_self, newtuple);\n\n\t/* Not sure if we need this one, but keeping it as a precaution */\n\tInvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0);\n\n\tUnlockSysCacheTuple(pgclass, &otid);\n\theap_freetuple(newtuple);\n\theap_freetuple(tuple);\n\ttable_close(pgclass, RowExclusiveLock);\n}\n\n/*\n * Set value of reloptions for given relation.\n *\n * This will also set the reloption for the relations' associated relations,\n * in this case the TOAST table. It is based on ATExecSetRelOptions but we\n * split out the code to set the reloptions rather than duplicating it.\n *\n * The lockmode is needed for taking a correct lock on the toast table for the\n * already locked relation. It is only used for\n *\n * rel: Relation to add reloptions to.\n * defList: List of DefElem for the new definitions.\n * lockmode: the mode that the actual tables are locked in.\n */\nvoid\nts_relation_set_reloption(Relation rel, List *options, LOCKMODE lockmode)\n{\n\tAssert(RelationIsValid(rel));\n\trelation_set_reloption_impl(rel, options, lockmode);\n\tif (OidIsValid(rel->rd_rel->reltoastrelid))\n\t{\n\t\tRelation toastrel = table_open(rel->rd_rel->reltoastrelid, lockmode);\n\t\trelation_set_reloption_impl(toastrel, options, lockmode);\n\t\ttable_close(toastrel, NoLock);\n\t}\n}\n\n/* this function fills in a jsonb with the non-null fields of\n the error data and also includes the proc name and schema in the jsonb\n we include these here to avoid adding these fields to the table */\nJsonb *\nts_errdata_to_jsonb(ErrorData *edata, Name proc_schema, Name proc_name)\n{\n\tJsonbParseState *parse_state = NULL;\n\tpushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);\n\tif (edata->sqlerrcode)\n\t\tts_jsonb_add_str(parse_state, \"sqlerrcode\", unpack_sql_state(edata->sqlerrcode));\n\tif (edata->message)\n\t\tts_jsonb_add_str(parse_state, \"message\", edata->message);\n\tif (edata->detail)\n\t\tts_jsonb_add_str(parse_state, \"detail\", edata->detail);\n\tif (edata->hint)\n\t\tts_jsonb_add_str(parse_state, \"hint\", edata->hint);\n\tif (edata->filename)\n\t\tts_jsonb_add_str(parse_state, \"filename\", edata->filename);\n\tif (edata->lineno)\n\t\tts_jsonb_add_int32(parse_state, \"lineno\", edata->lineno);\n\tif (edata->funcname)\n\t\tts_jsonb_add_str(parse_state, \"funcname\", edata->funcname);\n\tif (edata->domain)\n\t\tts_jsonb_add_str(parse_state, \"domain\", edata->domain);\n\tif (edata->context_domain)\n\t\tts_jsonb_add_str(parse_state, \"context_domain\", edata->context_domain);\n\tif (edata->context)\n\t\tts_jsonb_add_str(parse_state, \"context\", edata->context);\n\tif (edata->schema_name)\n\t\tts_jsonb_add_str(parse_state, \"schema_name\", edata->schema_name);\n\tif (edata->table_name)\n\t\tts_jsonb_add_str(parse_state, \"table_name\", edata->table_name);\n\tif (edata->column_name)\n\t\tts_jsonb_add_str(parse_state, \"column_name\", edata->column_name);\n\tif (edata->datatype_name)\n\t\tts_jsonb_add_str(parse_state, \"datatype_name\", edata->datatype_name);\n\tif (edata->constraint_name)\n\t\tts_jsonb_add_str(parse_state, \"constraint_name\", edata->constraint_name);\n\tif (edata->internalquery)\n\t\tts_jsonb_add_str(parse_state, \"internalquery\", edata->internalquery);\n\tif (edata->detail_log)\n\t\tts_jsonb_add_str(parse_state, \"detail_log\", edata->detail_log);\n\tif (strlen(NameStr(*proc_schema)) > 0)\n\t\tts_jsonb_add_str(parse_state, \"proc_schema\", NameStr(*proc_schema));\n\tif (strlen(NameStr(*proc_name)) > 0)\n\t\tts_jsonb_add_str(parse_state, \"proc_name\", NameStr(*proc_name));\n\t/* we add the schema qualified name here as well*/\n\tJsonbValue *result = pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);\n\treturn JsonbValueToJsonb(result);\n}\n\nchar *\nts_get_attr_expr(Relation rel, AttrNumber attno)\n{\n\tTupleConstr *constr = rel->rd_att->constr;\n\tchar *expr = NULL;\n\n\tfor (int i = 0; i < constr->num_defval; i++)\n\t{\n\t\tif (constr->defval[i].adnum == attno)\n\t\t{\n\t\t\texpr = TextDatumGetCString(\n\t\t\t\tDirectFunctionCall2(pg_get_expr,\n\t\t\t\t\t\t\t\t\tCStringGetTextDatum(constr->defval[i].adbin),\n\t\t\t\t\t\t\t\t\tObjectIdGetDatum(RelationGetRelid(rel))));\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn expr;\n}\n\nchar *\nts_list_to_string(List *list, append_cell_func append)\n{\n\tStringInfoData info;\n\tListCell *lc;\n\n\tinitStringInfo(&info);\n\n\tforeach (lc, list)\n\t{\n\t\tif (!lnext(list, lc))\n\t\t\tappendStringInfoString(&info, \"and \");\n\t\tappend(&info, lc);\n\t\tif (lnext(list, lc))\n\t\t{\n\t\t\tif (list_length(list) > 2)\n\t\t\t\tappendStringInfoChar(&info, ',');\n\t\t\tappendStringInfoChar(&info, ' ');\n\t\t}\n\t}\n\treturn info.data;\n}\n"
  },
  {
    "path": "src/utils.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include <access/htup_details.h>\n#include <access/tupdesc.h>\n#include <catalog/namespace.h>\n#include <catalog/pg_proc.h>\n#include <common/int.h>\n#include <debug_assert.h>\n#include <foreign/foreign.h>\n#include <nodes/extensible.h>\n#include <nodes/pathnodes.h>\n#include <optimizer/paths.h>\n#include <utils/builtins.h>\n#include <utils/datetime.h>\n#include <utils/jsonb.h>\n\n#include \"compat/compat.h\"\n#include \"process_utility.h\"\n\n/*\n * Macro for debug messages that should *only* be present in debug builds but\n * which should be removed in release builds. This is typically used for\n * debug builds for development purposes.\n *\n * Note that some debug messages might be relevant to deploy in release build\n * for debugging production systems. This macro is *not* for those cases.\n */\n#ifdef TS_DEBUG\n#define TS_DEBUG_LOG(FMT, ...) elog(DEBUG2, \"%s - \" FMT, __func__, ##__VA_ARGS__)\n#else\n#define TS_DEBUG_LOG(FMT, ...)\n#endif\n\n#define UnassignedDatum (Datum) 0\n\nstatic inline int64\ninterval_to_usec(const Interval *interval)\n{\n\treturn (interval->month * DAYS_PER_MONTH * USECS_PER_DAY) + (interval->day * USECS_PER_DAY) +\n\t\t   interval->time;\n}\n\n/*\n * Get the function name in a PG_FUNCTION.\n *\n * The function name is resolved from the function Oid in the functioncall\n * data. However, this information is not present in case of a direct function\n * call, so fall back to the C-function name.\n */\n#define TS_FUNCNAME()                                                                              \\\n\t(psprintf(\"%s()\", fcinfo->flinfo ? get_func_name(FC_FN_OID(fcinfo)) : __func__))\n\n#define TS_PREVENT_FUNC_IF_READ_ONLY() PreventCommandIfReadOnly(TS_FUNCNAME())\n\n#define TS_PREVENT_IN_TRANSACTION_BLOCK(CMD)                                                       \\\n\tdo                                                                                             \\\n\t{                                                                                              \\\n\t\tbool _isTopLevel = ts_process_utility_is_top_level();                                      \\\n\t\t/* Reset context before calling PreventInTransactionBlock in case it aborts. */            \\\n\t\tts_process_utility_context_reset();                                                        \\\n\t\tPreventInTransactionBlock(_isTopLevel, (CMD));                                             \\\n\t} while (0)\n\n#define MAX(x, y) ((x) > (y) ? x : y)\n#define MIN(x, y) ((x) < (y) ? x : y)\n\nstatic inline bool\ncontains_volatile_functions_checker(Oid func_id, void *context)\n{\n\treturn (func_volatile(func_id) == PROVOLATILE_VOLATILE);\n}\n\n/* find the length of a statically sized array */\n#define TS_ARRAY_LEN(array) (sizeof(array) / sizeof(*array))\n\nextern TSDLLEXPORT bool ts_type_is_int8_binary_compatible(Oid sourcetype);\n\ntypedef bool (*proc_filter)(Form_pg_proc form, void *arg);\n\n/*\n * Convert a column value into the internal time representation.\n * cannot store a timestamp earlier than MIN_TIMESTAMP, or greater than\n *    END_TIMESTAMP - TS_EPOCH_DIFF_MICROSECONDS\n * nor dates that cannot be translated to timestamps\n * Will throw an error for that, or other conversion issues.\n */\nextern TSDLLEXPORT int64 ts_time_value_to_internal(Datum time_val, Oid type);\nextern int64 ts_time_value_to_internal_or_infinite(Datum time_val, Oid type_oid);\n\nextern TSDLLEXPORT int64 ts_interval_value_to_internal(Datum time_val, Oid type_oid);\n\n/*\n * Convert a column from the internal time representation into the specified type\n */\nextern TSDLLEXPORT Datum ts_internal_to_time_value(int64 value, Oid type);\nextern TSDLLEXPORT int64 ts_internal_to_time_int64(int64 value, Oid type);\nextern TSDLLEXPORT Datum ts_internal_to_interval_value(int64 value, Oid type);\nextern TSDLLEXPORT char *ts_datum_to_string(Datum value, Oid type);\nextern TSDLLEXPORT char *ts_internal_to_time_string(int64 value, Oid type);\n\n/*\n * Return the period in microseconds of the first argument to date_trunc.\n * This is approximate -- to be used for planning;\n */\nextern int64 ts_date_trunc_interval_period_approx(text *units);\n/*\n * Return the interval period in microseconds.\n * This is approximate -- to be used for planning;\n */\nextern TSDLLEXPORT int64 ts_get_interval_period_approx(Interval *interval);\n\nextern TSDLLEXPORT Oid ts_inheritance_parent_relid(Oid relid);\n\nextern Oid ts_lookup_proc_filtered(const char *schema, const char *funcname, Oid *rettype,\n\t\t\t\t\t\t\t\t   proc_filter filter, void *filter_arg);\nextern Oid ts_get_operator(const char *name, Oid namespace, Oid left, Oid right);\nextern bool ts_function_types_equal(Oid left[], Oid right[], int nargs);\n\nextern TSDLLEXPORT Oid ts_get_function_oid(const char *funcname, const char *schema_name, int nargs,\n\t\t\t\t\t\t\t\t\t\t   Oid arg_types[]);\n\nextern TSDLLEXPORT Oid ts_get_cast_func(Oid source, Oid target);\n\ntypedef struct Dimension Dimension;\n\nextern TSDLLEXPORT Oid ts_get_integer_now_func(const Dimension *open_dim, bool fail_if_not_found);\nextern TSDLLEXPORT int64 ts_sub_integer_from_now(int64 interval, Oid time_dim_type, Oid now_func);\n\nextern TSDLLEXPORT void *ts_create_struct_from_slot(TupleTableSlot *slot, MemoryContext mctx,\n\t\t\t\t\t\t\t\t\t\t\t\t\tsize_t alloc_size, size_t copy_size);\n\nextern TSDLLEXPORT AppendRelInfo *ts_get_appendrelinfo(PlannerInfo *root, Index rti,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   bool missing_ok);\n\nextern TSDLLEXPORT Expr *ts_find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel);\nextern TSDLLEXPORT EquivalenceMember *ts_find_em_for_rel(EquivalenceClass *ec, RelOptInfo *rel);\n\nextern TSDLLEXPORT bool ts_has_row_security(Oid relid);\n\nextern TSDLLEXPORT List *ts_get_reloptions(Oid relid);\n\n#define STRUCT_FROM_SLOT(slot, mctx, to_type, form_type)                                           \\\n\t(to_type *) ts_create_struct_from_slot(slot, mctx, sizeof(to_type), sizeof(form_type));\n\n/* note PG10 has_superclass but PG96 does not so use this */\n#define is_inheritance_child(relid) (OidIsValid(ts_inheritance_parent_relid((relid))))\n\n#define is_inheritance_parent(relid)                                                               \\\n\t(find_inheritance_children(table_relid, AccessShareLock) != NIL)\n\n#define is_inheritance_table(relid) (is_inheritance_child(relid) || is_inheritance_parent(relid))\n\n#define INIT_NULL_DATUM                                                                            \\\n\t{                                                                                              \\\n\t\t.value = 0, .isnull = true                                                                 \\\n\t}\n\nstatic inline Datum\nts_fetch_att(const void *T, bool attbyval, int attlen)\n{\n\t/* Length should be set to something sensible, otherwise an error will be\n\t * raised by fetch_att, so we assert this here to get a stack for\n\t * violations. */\n\tAssert(!attbyval || (attlen > 0 && attlen <= 8));\n\treturn fetch_att(T, attbyval, attlen);\n}\n\nstatic inline int64\nint64_min(int64 a, int64 b)\n{\n\tif (a <= b)\n\t\treturn a;\n\treturn b;\n}\n\nstatic inline int64\nint64_saturating_add(int64 a, int64 b)\n{\n\tint64 result;\n\tbool overflowed = pg_add_s64_overflow(a, b, &result);\n\tif (overflowed)\n\t\tresult = a < 0 ? PG_INT64_MIN : PG_INT64_MAX;\n\treturn result;\n}\n\nstatic inline int64\nint64_saturating_sub(int64 a, int64 b)\n{\n\tint64 result;\n\tbool overflowed = pg_sub_s64_overflow(a, b, &result);\n\tif (overflowed)\n\t\tresult = b < 0 ? PG_INT64_MAX : PG_INT64_MIN;\n\treturn result;\n}\n\nstatic inline bool\nts_flags_are_set_32(uint32 bitmap, uint32 flags)\n{\n\treturn (bitmap & flags) == flags;\n}\n\nstatic inline pg_nodiscard uint32\nts_set_flags_32(uint32 bitmap, uint32 flags)\n{\n\treturn bitmap | flags;\n}\n\nstatic inline uint32\nts_clear_flags_32(uint32 bitmap, uint32 flags)\n{\n\treturn bitmap & ~flags;\n}\n\n/**\n * Try to register a custom scan method.\n *\n * When registering a custom scan node, it might be called multiple times when\n * different databases have different versions of the extension installed, so\n * this function can be used to try to register a custom scan method but not\n * fail if it has already been registered.\n */\nstatic inline void\nTryRegisterCustomScanMethods(const CustomScanMethods *methods)\n{\n\tif (!GetCustomScanMethods(methods->CustomName, true))\n\t\tRegisterCustomScanMethods(methods);\n}\n\ntypedef struct RelationSize\n{\n\tint64 total_size;\n\tint64 heap_size;\n\tint64 toast_size;\n\tint64 index_size;\n} RelationSize;\n\nextern TSDLLEXPORT RelationSize ts_relation_size_impl(Oid relid);\n\nextern TSDLLEXPORT const char *ts_get_node_name(Node *node);\nextern TSDLLEXPORT int ts_get_relnatts(Oid relid);\nextern TSDLLEXPORT void ts_alter_table_with_event_trigger(Oid relid, Node *cmd, List *cmds,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  bool recurse);\nextern TSDLLEXPORT void ts_copy_relation_acl(const Oid source_relid, const Oid target_relid,\n\t\t\t\t\t\t\t\t\t\t\t const Oid owner_id);\n\nextern TSDLLEXPORT bool ts_relation_has_tuples(Relation rel);\nextern TSDLLEXPORT bool ts_table_has_tuples(Oid table_relid, LOCKMODE lockmode);\n\nextern TSDLLEXPORT AttrNumber ts_map_attno(Oid src_rel, Oid dst_rel, AttrNumber attno);\n\n/*\n * Return Oid for a schema-qualified relation.\n */\nstatic inline Oid\nts_get_relation_relid(char const *schema_name, char const *relation_name, bool return_invalid)\n{\n\tOid schema_oid = get_namespace_oid(schema_name, true);\n\n\tif (OidIsValid(schema_oid))\n\t{\n\t\tOid rel_oid = get_relname_relid(relation_name, schema_oid);\n\n\t\tif (!return_invalid)\n\t\t\tEnsure(OidIsValid(rel_oid), \"relation \\\"%s.%s\\\" not found\", schema_name, relation_name);\n\n\t\treturn rel_oid;\n\t}\n\telse\n\t{\n\t\tif (!return_invalid)\n\t\t\tEnsure(OidIsValid(schema_oid),\n\t\t\t\t   \"schema \\\"%s\\\" not found (during lookup of relation \\\"%s.%s\\\")\",\n\t\t\t\t   schema_name,\n\t\t\t\t   schema_name,\n\t\t\t\t   relation_name);\n\n\t\treturn InvalidOid;\n\t}\n}\n\nstruct Hypertable;\n\nvoid replace_now_mock_walker(PlannerInfo *root, Node *clause, Oid funcid);\n\nextern TSDLLEXPORT HeapTuple ts_heap_form_tuple(TupleDesc tupleDescriptor, NullableDatum *datums);\n\nstatic inline void\nts_datum_set_text_from_cstring(const AttrNumber attno, NullableDatum *datums, const char *value)\n{\n\tif (value != NULL)\n\t{\n\t\tdatums[AttrNumberGetAttrOffset(attno)].value = PointerGetDatum(cstring_to_text(value));\n\t\tdatums[AttrNumberGetAttrOffset(attno)].isnull = false;\n\t}\n\telse\n\t\tdatums[AttrNumberGetAttrOffset(attno)].isnull = true;\n}\n\nstatic inline void\nts_datum_set_bool(const AttrNumber attno, NullableDatum *datums, const bool value,\n\t\t\t\t  const bool isnull)\n{\n\tif (!isnull)\n\t\tdatums[AttrNumberGetAttrOffset(attno)].value = BoolGetDatum(value);\n\tdatums[AttrNumberGetAttrOffset(attno)].isnull = isnull;\n}\n\nstatic inline void\nts_datum_set_int32(const AttrNumber attno, NullableDatum *datums, const int32 value,\n\t\t\t\t   const bool isnull)\n{\n\tif (!isnull)\n\t\tdatums[AttrNumberGetAttrOffset(attno)].value = Int32GetDatum(value);\n\tdatums[AttrNumberGetAttrOffset(attno)].isnull = isnull;\n}\n\nstatic inline void\nts_datum_set_int64(const AttrNumber attno, NullableDatum *datums, const int64 value,\n\t\t\t\t   const bool isnull)\n{\n\tif (!isnull)\n\t\tdatums[AttrNumberGetAttrOffset(attno)].value = Int64GetDatum(value);\n\tdatums[AttrNumberGetAttrOffset(attno)].isnull = isnull;\n}\n\nstatic inline void\nts_datum_set_timestamptz(const AttrNumber attno, NullableDatum *datums, const TimestampTz value,\n\t\t\t\t\t\t const bool isnull)\n{\n\tif (!isnull)\n\t\tdatums[AttrNumberGetAttrOffset(attno)].value = TimestampTzGetDatum(value);\n\tdatums[AttrNumberGetAttrOffset(attno)].isnull = isnull;\n}\n\nstatic inline void\nts_datum_set_jsonb(const AttrNumber attno, NullableDatum *datums, const Jsonb *value)\n{\n\tif (value != NULL)\n\t{\n\t\tdatums[AttrNumberGetAttrOffset(attno)].value = JsonbPGetDatum(value);\n\t\tdatums[AttrNumberGetAttrOffset(attno)].isnull = false;\n\t}\n\telse\n\t\tdatums[AttrNumberGetAttrOffset(attno)].isnull = true;\n}\n\nstatic inline void\nts_datum_set_objectid(const AttrNumber attno, NullableDatum *datums, const Oid value)\n{\n\tif (OidIsValid(value))\n\t{\n\t\tdatums[AttrNumberGetAttrOffset(attno)].value = ObjectIdGetDatum(value);\n\t\tdatums[AttrNumberGetAttrOffset(attno)].isnull = false;\n\t}\n\telse\n\t\tdatums[AttrNumberGetAttrOffset(attno)].isnull = true;\n}\n\ntypedef void (*append_cell_func)(StringInfo, ListCell *);\n\nextern TSDLLEXPORT void ts_get_rel_info_by_name(const char *relnamespace, const char *relname,\n\t\t\t\t\t\t\t\t\t\t\t\tOid *relid, char *relkind);\nextern TSDLLEXPORT Oid ts_get_rel_am(Oid relid);\nextern TSDLLEXPORT void ts_relation_set_reloption(Relation rel, List *options, LOCKMODE lockmode);\nextern TSDLLEXPORT Jsonb *ts_errdata_to_jsonb(ErrorData *edata, Name proc_schema, Name proc_name);\nextern TSDLLEXPORT char *ts_get_attr_expr(Relation rel, AttrNumber attno);\nextern TSDLLEXPORT char *ts_list_to_string(List *list, append_cell_func append);\n"
  },
  {
    "path": "src/uuid.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <fmgr.h>\n#include <port/pg_bswap.h>\n#include <utils/timestamp.h>\n#include <utils/uuid.h>\n\n#include \"compat/compat.h\"\n#include \"uuid.h\"\n\n/*\n * Generates a v4 UUID. Based on function pg_random_uuid() in the pgcrypto contrib module.\n *\n * Note that clib on Mac has a uuid_generate() function, so we call this ts_uuid_create().\n */\npg_uuid_t *\nts_uuid_create(void)\n{\n\t/*\n\t * PG9.6 doesn't expose the internals of pg_uuid_t, so we just treat it as\n\t * a byte array\n\t */\n\tunsigned char *gen_uuid = palloc0(UUID_LEN);\n\tbool rand_success = false;\n\n\trand_success = pg_backend_random((char *) gen_uuid, UUID_LEN);\n\n\t/*\n\t * If pg_backend_random() cannot find sources of randomness, then we use\n\t * the current timestamp as a \"random source\".\n\t * Timestamps are 8 bytes, so we copy this into bytes 9-16 of the UUID.\n\t * If we see all 0s in bytes 0-8 (other than version + * variant), we know\n\t * that there is something wrong with the RNG on this instance.\n\t */\n\tif (!rand_success)\n\t{\n\t\tTimestampTz ts = GetCurrentTimestamp();\n\n\t\tmemcpy(&gen_uuid[8], &ts, sizeof(TimestampTz));\n\t}\n\n\tgen_uuid[6] = (gen_uuid[6] & 0x0f) | 0x40; /* \"version\" field */\n\tgen_uuid[8] = (gen_uuid[8] & 0x3f) | 0x80; /* \"variant\" field */\n\n\treturn (pg_uuid_t *) gen_uuid;\n}\n\nTS_FUNCTION_INFO_V1(ts_uuid_generate);\n\nDatum\nts_uuid_generate(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_UUID_P(ts_uuid_create());\n}\n\n/*\n * Create a UUIDv7 from a unix timestamp in microseconds.\n *\n * Optionally produce a boundary UUID with all otherwise random bits set to\n * zero that can be used in range queries. The version can also be set to zero\n * in order to produce partition ranges that excludes the UUID version.\n */\npg_uuid_t *\nts_create_uuid_v7_from_unixtime_us(int64 unixtime_us, bool boundary, bool set_version)\n{\n\tpg_uuid_t *uuid;\n\tuint64_t timestamp_be = pg_hton64((unixtime_us / 1000) << 16);\n\n\tif (boundary)\n\t{\n\t\tuuid = (pg_uuid_t *) palloc0(UUID_LEN);\n\t}\n\telse\n\t{\n\t\tuuid = (pg_uuid_t *) palloc(UUID_LEN);\n\t\tpg_backend_random(&((char *) uuid)[8], UUID_LEN - 8);\n\t}\n\n\t/* Fill the first 48 bits with the timestamp */\n\tmemcpy(uuid->data, &timestamp_be, 6);\n\n\t/* The microseconds part of the timestamp, scaled to 12 bits, same as in PG18 */\n\tuint32 ts_micros = (unixtime_us % 1000) * (1 << 12) / 1000;\n\n\t/*\n\t * Sub milliseconds timestamps are optional. We store the microseconds part in the\n\t * rand_a field as described in the UUID v7 specification. Following the PG18 logic\n\t * here.\n\t */\n\tuuid->data[6] = (unsigned char) (ts_micros >> 8);\n\tuuid->data[7] = (unsigned char) ts_micros;\n\n\tif (set_version)\n\t{\n\t\t/* Set version 7 (0111) in bits 6-7 of byte 6, keep random bits 0-5 */\n\t\tuuid->data[6] = (uuid->data[6] & 0x0F) | 0x70;\n\n\t\t/* Set variant (10) in bits 4-5 of byte 8, keep random bits 0-3 and 6-7 */\n\t\tuuid->data[8] = (uuid->data[8] & 0x3F) | 0x80;\n\t}\n\n\treturn uuid;\n}\n\npg_uuid_t *\nts_create_uuid_v7_from_timestamptz(TimestampTz ts, bool boundary)\n{\n\tint64 epoch_diff_us = ((int64) (POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * USECS_PER_DAY);\n\tint64 unixtime_us = ts + epoch_diff_us;\n\n\treturn ts_create_uuid_v7_from_unixtime_us(unixtime_us, boundary, true);\n}\n\nTS_FUNCTION_INFO_V1(ts_uuid_generate_v7);\n\nDatum\nts_uuid_generate_v7(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_UUID_P(ts_create_uuid_v7_from_timestamptz(GetCurrentTimestamp(), false));\n}\n\nTS_FUNCTION_INFO_V1(ts_uuid_v7_from_timestamptz);\n\nDatum\nts_uuid_v7_from_timestamptz(PG_FUNCTION_ARGS)\n{\n\tTimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0);\n\n\tPG_RETURN_UUID_P(ts_create_uuid_v7_from_timestamptz(timestamp, false));\n}\n\nTS_FUNCTION_INFO_V1(ts_uuid_v7_from_timestamptz_boundary);\n\nDatum\nts_uuid_v7_from_timestamptz_boundary(PG_FUNCTION_ARGS)\n{\n\tTimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0);\n\n\tPG_RETURN_UUID_P(ts_create_uuid_v7_from_timestamptz(timestamp, true));\n}\n\n#define UUID_VARIANT(uuid) ((uuid)->data[8] & 0xc0)\n#define IS_RFC9562_VARIANT(uuid) (UUID_VARIANT(uuid) == 0x80)\n#define UUID_VERSION(uuid) (((uuid)->data[6] & 0xf0) >> 4)\n\n/*\n * Extract the millisecond Unix epoch timestamp from the UUIDv7, with optional\n * extra sub-millisecond fraction in microseconds.\n */\nbool\nts_uuid_v7_extract_unixtime(const pg_uuid_t *uuid, uint64 *unixtime_ms, uint16 *extra_us)\n{\n\tbool is_uuidv7 = false;\n\n\t/* Check that the variant field corresponds to RFC9562 */\n\tif (IS_RFC9562_VARIANT(uuid))\n\t{\n\t\t/* Get the version from the UUID */\n\t\tis_uuidv7 = (UUID_VERSION(uuid) == 7);\n\t}\n\n\t/* Big endian timestamp in milliseconds from Unix Epoch */\n\tuint64 timestamp_be = 0;\n\tmemcpy(&timestamp_be, uuid->data, 6);\n\n\t/* The timestamp is now milliseconds from Unix Epoch (1970-01-01)*/\n\t*unixtime_ms = (pg_ntoh64(timestamp_be)) >> 16;\n\n\tif (extra_us)\n\t{\n\t\t/* Optionally, get the sub ms part as microseconds, reversing the scaling */\n\t\t*extra_us = ((((uuid->data[6] & 0xF) << 8) | uuid->data[7]) + 1) * 1000 / (1 << 12);\n\t}\n\n\treturn is_uuidv7;\n}\n\nbool\nts_uuid_v7_extract_timestamptz(const pg_uuid_t *uuid, TimestampTz *timestamp, bool with_micros)\n{\n\tuint64 unixtime_millis = 0;\n\tuint16 extra_micros = 0;\n\n\tif (!ts_uuid_v7_extract_unixtime(uuid, &unixtime_millis, &extra_micros))\n\t\treturn false;\n\n\t/* Milliseconds timestamp from PG Epoch (2000-01-01) */\n\tconst uint64 epoch_diff = POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE;\n\tuint64 timestamp_millis = (unixtime_millis - (epoch_diff * SECS_PER_DAY) * 1000ULL);\n\n\t/* Convert to microseconds */\n\t*timestamp = timestamp_millis * 1000;\n\n\t/* Add extra microseconds if requested */\n\tif (with_micros)\n\t\t*timestamp += extra_micros;\n\n\treturn true;\n}\n\nTS_FUNCTION_INFO_V1(ts_timestamptz_from_uuid_v7);\n\nDatum\nts_timestamptz_from_uuid_v7(PG_FUNCTION_ARGS)\n{\n\tpg_uuid_t *uuid = PG_GETARG_UUID_P(0);\n\tTimestampTz ts = 0;\n\n\tif (!ts_uuid_v7_extract_timestamptz(uuid, &ts, false))\n\t\tPG_RETURN_NULL();\n\n\tPG_RETURN_TIMESTAMPTZ(ts);\n}\n\nTS_FUNCTION_INFO_V1(ts_timestamptz_from_uuid_v7_with_microseconds);\n\nDatum\nts_timestamptz_from_uuid_v7_with_microseconds(PG_FUNCTION_ARGS)\n{\n\tpg_uuid_t *uuid = PG_GETARG_UUID_P(0);\n\tTimestampTz ts = 0;\n\n\tif (!ts_uuid_v7_extract_timestamptz(uuid, &ts, true))\n\t\tPG_RETURN_NULL();\n\n\tPG_RETURN_TIMESTAMPTZ(ts);\n}\n\nTS_FUNCTION_INFO_V1(ts_uuid_version);\n\nDatum\nts_uuid_version(PG_FUNCTION_ARGS)\n{\n\tpg_uuid_t *uuid = PG_GETARG_UUID_P(0);\n\tint version;\n\n\t/* Check that the variant field corresponds to RFC9562 */\n\tif (!IS_RFC9562_VARIANT(uuid))\n\t\tPG_RETURN_NULL();\n\n\tversion = UUID_VERSION(uuid); /* Get the version from the UUID */\n\n\tPG_RETURN_INT32(version);\n}\n"
  },
  {
    "path": "src/uuid.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <fmgr.h>\n#include <utils/uuid.h>\n\n#define UNIX_EPOCH_AS_TIMESTAMP (0 - ((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * USECS_PER_DAY))\n\nextern pg_uuid_t *ts_uuid_create(void);\nextern pg_uuid_t *ts_create_uuid_v7_from_unixtime_us(int64 unixtime_us, bool boundary,\n\t\t\t\t\t\t\t\t\t\t\t\t\t bool set_version);\nextern TSDLLEXPORT pg_uuid_t *ts_create_uuid_v7_from_timestamptz(TimestampTz ts, bool boundary);\nextern bool ts_uuid_v7_extract_unixtime(const pg_uuid_t *uuid, uint64 *unixtime_ms,\n\t\t\t\t\t\t\t\t\t\tuint16 *extra_us);\nextern bool ts_uuid_v7_extract_timestamptz(const pg_uuid_t *uuid, TimestampTz *timestamp,\n\t\t\t\t\t\t\t\t\t\t   bool with_micros);\n\nextern TSDLLEXPORT Datum ts_timestamptz_from_uuid_v7(PG_FUNCTION_ARGS);\n"
  },
  {
    "path": "src/version.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <string.h>\n#include <postgres.h>\n#include <access/htup_details.h>\n#include <fmgr.h>\n#include <funcapi.h>\n#include <storage/fd.h>\n#include <utils/builtins.h>\n#include <utils/timestamp.h>\n\n#include \"compat/compat.h\"\n#include \"annotations.h\"\n#include \"config.h\"\n#include \"gitcommit.h\"\n#include \"version.h\"\n\n/* Export the strings to that we can read them using strings(1). We add a\n * prefix so that we can easily find it using grep(1). We only bother about\n * generating them if the relevant symbol is defined. */\n#ifdef EXT_GIT_COMMIT_HASH\nstatic const char commit_hash[] TS_USED = \"commit-hash:\" EXT_GIT_COMMIT_HASH;\n#endif\n\n#ifdef EXT_GIT_COMMIT_TAG\nstatic const char commit_tag[] TS_USED = \"commit-tag:\" EXT_GIT_COMMIT_TAG;\n#endif\n\n#ifdef EXT_GIT_COMMIT_TIME\nstatic const char commit_time[] TS_USED = \"commit-time:\" EXT_GIT_COMMIT_TIME;\n#endif\n\nTS_FUNCTION_INFO_V1(ts_get_git_commit);\n\n/* Return git commit information defined in header file gitcommit.h. We\n * support that some of the fields are defined and will only show the fields\n * that are defined. If no fields are defined, we throw an error notifying the\n * user that there is no git information available at all. */\n#if defined(EXT_GIT_COMMIT_HASH) || defined(EXT_GIT_COMMIT_TAG) || defined(EXT_GIT_COMMIT_TIME)\nDatum\nts_get_git_commit(PG_FUNCTION_ARGS)\n{\n\tTupleDesc tupdesc;\n\tHeapTuple tuple;\n\tDatum values[3] = { 0 };\n\tbool nulls[3] = { false };\n\n\t/* Build a tuple descriptor for our result type */\n\tif (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"function returning record called in context \"\n\t\t\t\t\t\t\"that cannot accept type record\")));\n\n\ttupdesc = BlessTupleDesc(tupdesc);\n\n#ifdef EXT_GIT_COMMIT_TAG\n\tvalues[0] = CStringGetTextDatum(EXT_GIT_COMMIT_TAG);\n#else\n\tnulls[0] = true;\n#endif\n\n#ifdef EXT_GIT_COMMIT_HASH\n\tvalues[1] = CStringGetTextDatum(EXT_GIT_COMMIT_HASH);\n#else\n\tnulls[1] = true;\n#endif\n\n#ifdef EXT_GIT_COMMIT_TIME\n\tvalues[2] = DirectFunctionCall3(timestamptz_in,\n\t\t\t\t\t\t\t\t\tCStringGetDatum(EXT_GIT_COMMIT_TIME),\n\t\t\t\t\t\t\t\t\tInt32GetDatum(-1),\n\t\t\t\t\t\t\t\t\tInt32GetDatum(-1));\n#else\n\tnulls[2] = true;\n#endif\n\n\ttuple = heap_form_tuple(tupdesc, values, nulls);\n\n\treturn HeapTupleGetDatum(tuple);\n}\n#else\nDatum\nts_get_git_commit(PG_FUNCTION_ARGS)\n{\n\tereport(ERROR,\n\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t errmsg(\"extension not built with any Git commit information\")));\n}\n#endif\n\n#ifdef WIN32\n\n#include <Windows.h>\n\nbool\nts_version_get_os_info(VersionOSInfo *info)\n{\n\tDWORD bufsize;\n\tvoid *buffer;\n\tVS_FIXEDFILEINFO *vinfo = NULL;\n\tUINT vinfo_len = 0;\n\n\tmemset(info, 0, sizeof(VersionOSInfo));\n\n\tbufsize = GetFileVersionInfoSizeA(TEXT(\"kernel32.dll\"), NULL);\n\n\tif (bufsize == 0)\n\t\treturn false;\n\n\tbuffer = palloc(bufsize);\n\n\tif (!GetFileVersionInfoA(TEXT(\"kernel32.dll\"), 0, bufsize, buffer))\n\t\tgoto error;\n\n\tif (!VerQueryValueA(buffer, TEXT(\"\\\\\"), &vinfo, &vinfo_len))\n\t\tgoto error;\n\n\tsnprintf(info->sysname, VERSION_INFO_LEN - 1, \"Windows\");\n\tsnprintf(info->version, VERSION_INFO_LEN - 1, \"%u\", HIWORD(vinfo->dwProductVersionMS));\n\tsnprintf(info->release, VERSION_INFO_LEN - 1, \"%u\", LOWORD(vinfo->dwProductVersionMS));\n\n\tpfree(buffer);\n\n\treturn true;\nerror:\n\tpfree(buffer);\n\n\treturn false;\n}\n\n#elif defined(UNIX)\n\n#include <sys/utsname.h>\n\n#define OS_RELEASE_FILE \"/etc/os-release\"\n#define MAX_READ_LEN 1024\n\n#define NAME_FIELD \"PRETTY_NAME=\\\"\"\n\nstatic bool\nget_pretty_version(char *pretty_version)\n{\n\tFILE *version_file;\n\tchar *contents = palloc(MAX_READ_LEN);\n\tsize_t bytes_read;\n\tbool got_pretty_version = false;\n\tint i;\n\n\tmemset(pretty_version, '\\0', VERSION_INFO_LEN);\n\n\t/* we cannot use pg_read_file because it doesn't allow absolute paths */\n\tversion_file = AllocateFile(OS_RELEASE_FILE, PG_BINARY_R);\n\tif (version_file == NULL)\n\t\treturn false;\n\n\tfseeko(version_file, 0, SEEK_SET);\n\n\tbytes_read = fread(contents, 1, (size_t) MAX_READ_LEN, version_file);\n\n\tif (bytes_read <= 0)\n\t\tgoto cleanup;\n\n\tif (bytes_read < MAX_READ_LEN)\n\t\tcontents[bytes_read] = '\\0';\n\telse\n\t\tcontents[MAX_READ_LEN - 1] = '\\0';\n\n\tcontents = strstr(contents, NAME_FIELD);\n\n\tif (contents == NULL)\n\t\tgoto cleanup;\n\n\tcontents += sizeof(NAME_FIELD) - 1;\n\n\tfor (i = 0; i < (VERSION_INFO_LEN - 1); i++)\n\t{\n\t\tchar c = contents[i];\n\n\t\tif (c == '\\0' || c == '\\n' || c == '\\r' || c == '\"')\n\t\t\tbreak;\n\n\t\tpretty_version[i] = c;\n\t}\n\n\tgot_pretty_version = true;\n\ncleanup:\n\tFreeFile(version_file);\n\treturn got_pretty_version;\n}\n\nbool\nts_version_get_os_info(VersionOSInfo *info)\n{\n\t/* Get the OS name  */\n\tstruct utsname os_info;\n\n\tuname(&os_info);\n\n\tmemset(info, 0, sizeof(VersionOSInfo));\n\tstrncpy(info->sysname, os_info.sysname, VERSION_INFO_LEN - 1);\n\tstrncpy(info->version, os_info.version, VERSION_INFO_LEN - 1);\n\tstrncpy(info->release, os_info.release, VERSION_INFO_LEN - 1);\n\tinfo->has_pretty_version = get_pretty_version(info->pretty_version);\n\n\treturn true;\n}\n#else\nbool\nts_version_get_os_info(VersionOSInfo *info)\n{\n\tmemset(info, 0, sizeof(VersionOSInfo));\n\treturn false;\n}\n#endif /* WIN32 */\n\nTS_FUNCTION_INFO_V1(ts_get_os_info);\n\nDatum\nts_get_os_info(PG_FUNCTION_ARGS)\n{\n\tTupleDesc tupdesc;\n\tDatum values[4];\n\tbool nulls[4] = { false };\n\tHeapTuple tuple;\n\tVersionOSInfo info;\n\n\tif (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"function returning record called in context \"\n\t\t\t\t\t\t\"that cannot accept type record\")));\n\n\tif (ts_version_get_os_info(&info))\n\t{\n\t\tvalues[0] = CStringGetTextDatum(info.sysname);\n\t\tvalues[1] = CStringGetTextDatum(info.version);\n\t\tvalues[2] = CStringGetTextDatum(info.release);\n\t\tif (info.has_pretty_version)\n\t\t\tvalues[3] = CStringGetTextDatum(info.pretty_version);\n\t\telse\n\t\t\tnulls[3] = true;\n\t}\n\telse\n\t\tmemset(nulls, true, sizeof(nulls));\n\n\ttuple = heap_form_tuple(tupdesc, values, nulls);\n\n\treturn HeapTupleGetDatum(tuple);\n}\n"
  },
  {
    "path": "src/version.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#define VERSION_INFO_LEN 128\n\ntypedef struct VersionOSInfo\n{\n\tchar sysname[VERSION_INFO_LEN];\n\tchar version[VERSION_INFO_LEN];\n\tchar release[VERSION_INFO_LEN];\n\tchar pretty_version[VERSION_INFO_LEN];\n\tbool has_pretty_version;\n} VersionOSInfo;\n\nextern bool ts_version_get_os_info(VersionOSInfo *info);\n"
  },
  {
    "path": "src/with_clause/CMakeLists.txt",
    "content": "set(SOURCES\n    ${CMAKE_CURRENT_SOURCE_DIR}/alter_table_with_clause.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/create_table_with_clause.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/create_materialized_view_with_clause.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/with_clause_parser.c)\n\ntarget_sources(${PROJECT_NAME} PRIVATE ${SOURCES})\n"
  },
  {
    "path": "src/with_clause/alter_table_with_clause.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <access/htup_details.h>\n#include <catalog/dependency.h>\n#include <catalog/namespace.h>\n#include <catalog/pg_trigger.h>\n#include <catalog/pg_type.h>\n#include <commands/trigger.h>\n#include <fmgr.h>\n#include <parser/parser.h>\n#include <port.h>\n#include <storage/lmgr.h>\n#include <utils/builtins.h>\n#include <utils/elog.h>\n#include <utils/lsyscache.h>\n#include <utils/typcache.h>\n\n#include \"compat/compat.h\"\n#include \"bmslist_utils.h\"\n#include \"cross_module_fn.h\"\n#include \"debug_assert.h\"\n#include \"guc.h\"\n#include \"jsonb_utils.h\"\n#include \"ts_catalog/array_utils.h\"\n#include \"ts_catalog/compression_settings.h\"\n\n#include \"alter_table_with_clause.h\"\n\nstatic const WithClauseDefinition alter_table_with_clause_def[] = {\n\t\t[AlterTableFlagChunkTimeInterval] = {\n\t\t\t.arg_names = {\"chunk_interval\", NULL},\n\t\t\t .type_id = TEXTOID,\n\t\t},\n\t\t[AlterTableFlagColumnstore] = {\n\t\t\t.arg_names = {\"compress\", \"columnstore\", \"enable_columnstore\", NULL},\n\t\t\t.type_id = BOOLOID,\n\t\t\t.default_val = (Datum)false,\n\t\t},\n\t\t[AlterTableFlagSegmentBy] = {\n\t\t\t.arg_names = {\"compress_segmentby\", \"segmentby\", \"segment_by\", NULL},\n\t\t\t .type_id = TEXTOID,\n\t\t},\n\t\t[AlterTableFlagOrderBy] = {\n\t\t\t.arg_names = {\"compress_orderby\", \"orderby\", \"order_by\", NULL},\n\t\t\t .type_id = TEXTOID,\n\t\t},\n\t\t[AlterTableFlagCompressChunkTimeInterval] = {\n\t\t\t.arg_names = {\"compress_chunk_interval\", \"compress_chunk_time_interval\", NULL},\n\t\t\t .type_id = INTERVALOID,\n\t\t},\n\t\t[AlterTableFlagIndex] = {\n\t\t\t.arg_names = {\"compress_index\", \"compress_sparse_index\", \"index\", \"sparse_index\", NULL},\n\t\t\t .type_id = TEXTOID,\n\t\t},\n};\n\nstatic const WithClauseDefinition sparse_index_with_clause_def[] = {\n\t[_SparseIndexTypeEnumBloom] = {\n\t\t.arg_names = {\"compress_bloom\", \"bloom\", NULL},\n\t\t .type_id = TEXTOID,\n\t},\n\t[_SparseIndexTypeEnumMinmax] = {\n\t\t.arg_names = {\"compress_minmax\", \"minmax\", \"compress_min_max\", \"min_max\", NULL},\n\t\t.type_id = TEXTOID,\n\t},\n};\n\nWithClauseResult *\nts_alter_table_with_clause_parse(const List *defelems)\n{\n\treturn ts_with_clauses_parse(defelems,\n\t\t\t\t\t\t\t\t alter_table_with_clause_def,\n\t\t\t\t\t\t\t\t TS_ARRAY_LEN(alter_table_with_clause_def));\n}\n\nWithClauseResult *\nts_alter_table_reset_with_clause_parse(const List *defelems)\n{\n\treturn ts_with_clauses_parse_reset(defelems,\n\t\t\t\t\t\t\t\t\t   alter_table_with_clause_def,\n\t\t\t\t\t\t\t\t\t   TS_ARRAY_LEN(alter_table_with_clause_def));\n}\n\nstatic inline void\nthrow_segment_by_error(char *segment_by)\n{\n\tereport(ERROR,\n\t\t\t(errcode(ERRCODE_SYNTAX_ERROR),\n\t\t\t errmsg(\"unable to parse segmenting option \\\"%s\\\"\", segment_by),\n\t\t\t errhint(\"The option timescaledb.compress_segmentby must\"\n\t\t\t\t\t \" be a set of columns separated by commas.\")));\n}\n\nstatic bool\nselect_stmt_as_expected(SelectStmt *stmt)\n{\n\t/* The only parts of the select stmt that are allowed to be set are the order by or group by.\n\t * Check that no other fields are set */\n\tif (stmt->distinctClause != NIL || stmt->intoClause != NULL || stmt->targetList != NIL ||\n\t\tstmt->whereClause != NULL || stmt->havingClause != NULL || stmt->windowClause != NIL ||\n\t\tstmt->valuesLists != NULL || stmt->limitOffset != NULL || stmt->limitCount != NULL ||\n\t\tstmt->lockingClause != NIL || stmt->withClause != NULL || stmt->op != 0 ||\n\t\tstmt->all != false || stmt->larg != NULL || stmt->rarg != NULL)\n\t\treturn false;\n\treturn true;\n}\n\nstatic ArrayType *\nparse_segment_collist(char *inpstr, Hypertable *hypertable)\n{\n\tStringInfoData buf;\n\tList *parsed;\n\tListCell *lc;\n\tSelectStmt *select;\n\tRawStmt *raw;\n\n\t/* segmentby can have empty array */\n\tif (strlen(inpstr) == 0)\n\t\treturn ts_array_add_element_text(NULL, NULL);\n\n\tinitStringInfo(&buf);\n\n\t/* parse the segment by list exactly how you would a group by */\n\tappendStringInfo(&buf,\n\t\t\t\t\t \"SELECT FROM %s.%s GROUP BY %s\",\n\t\t\t\t\t quote_identifier(NameStr(hypertable->fd.schema_name)),\n\t\t\t\t\t quote_identifier(NameStr(hypertable->fd.table_name)),\n\t\t\t\t\t inpstr);\n\n\tconst MemoryContext oldcontext = CurrentMemoryContext;\n\n\tPG_TRY();\n\t{\n\t\tparsed = raw_parser(buf.data, RAW_PARSE_DEFAULT);\n\t}\n\tPG_CATCH();\n\t{\n\t\t/* We do this fandango to avoid exhausting the error stack if we get\n\t\t * anything else but a syntax error, for example, an out of memory\n\t\t * error. */\n\t\tErrorData *edata;\n\t\tMemoryContextSwitchTo(oldcontext);\n\t\tedata = CopyErrorData();\n\t\tFlushErrorState();\n\t\tif (edata->sqlerrcode == ERRCODE_SYNTAX_ERROR)\n\t\t{\n\t\t\tedata->cursorpos = edata->internalpos = 0;\n\t\t\tedata->detail = edata->message;\n\t\t\tedata->message = psprintf(\"unable to parse segmenting option \\\"%s\\\"\", inpstr);\n\t\t\tedata->hint = psprintf(\"The option timescaledb.compress_segmentby must be a set of \"\n\t\t\t\t\t\t\t\t   \"columns separated by commas.\");\n\t\t}\n\t\tReThrowError(edata);\n\t}\n\tPG_END_TRY();\n\n\tif (list_length(parsed) != 1)\n\t\tthrow_segment_by_error(inpstr);\n\tif (!IsA(linitial(parsed), RawStmt))\n\t\tthrow_segment_by_error(inpstr);\n\traw = linitial(parsed);\n\n\tif (!IsA(raw->stmt, SelectStmt))\n\t\tthrow_segment_by_error(inpstr);\n\tselect = (SelectStmt *) raw->stmt;\n\n\tif (!select_stmt_as_expected(select))\n\t\tthrow_segment_by_error(inpstr);\n\n\tif (select->sortClause != NIL)\n\t\tthrow_segment_by_error(inpstr);\n\n\tArrayType *segmentby = NULL;\n\tforeach (lc, select->groupClause)\n\t{\n\t\tif (!IsA(lfirst(lc), ColumnRef))\n\t\t\tthrow_segment_by_error(inpstr);\n\n\t\tColumnRef *cf = lfirst(lc);\n\t\tif (list_length(cf->fields) != 1)\n\t\t\tthrow_segment_by_error(inpstr);\n\n\t\tif (!IsA(linitial(cf->fields), String))\n\t\t\tthrow_segment_by_error(inpstr);\n\n\t\tchar *colname = strVal(linitial(cf->fields));\n\t\tAttrNumber col_attno = get_attnum(hypertable->main_table_relid, colname);\n\t\tif (col_attno == InvalidAttrNumber)\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_SYNTAX_ERROR),\n\t\t\t\t\t errmsg(\"column \\\"%s\\\" does not exist\", colname),\n\t\t\t\t\t errhint(\"The timescaledb.compress_segmentby option must reference a valid \"\n\t\t\t\t\t\t\t \"column.\")));\n\t\t}\n\n\t\t/* get normalized column name */\n\t\tcolname = get_attname(hypertable->main_table_relid, col_attno, false);\n\n\t\t/* check if segmentby columns are distinct. */\n\t\tif (ts_array_is_member(segmentby, colname))\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_SYNTAX_ERROR),\n\t\t\t\t\t errmsg(\"duplicate column name \\\"%s\\\"\", colname),\n\t\t\t\t\t errhint(\"The timescaledb.compress_segmentby option must reference distinct \"\n\t\t\t\t\t\t\t \"column.\")));\n\n\t\tsegmentby = ts_array_add_element_text(segmentby, pstrdup(colname));\n\t}\n\n\treturn segmentby;\n}\n\nstatic inline void\nthrow_order_by_error(char *order_by)\n{\n\tereport(ERROR,\n\t\t\t(errcode(ERRCODE_SYNTAX_ERROR),\n\t\t\t errmsg(\"unable to parse ordering option \\\"%s\\\"\", order_by),\n\t\t\t errhint(\"The timescaledb.compress_orderby option must be a set of column\"\n\t\t\t\t\t \" names with sort options, separated by commas.\"\n\t\t\t\t\t \" It is the same format as an ORDER BY clause.\")));\n}\n\n/* compress_orderby is parsed same as order by in select queries */\nOrderBySettings\nts_compress_parse_order_collist(char *inpstr, Hypertable *hypertable)\n{\n\tStringInfoData buf;\n\tList *parsed;\n\tListCell *lc;\n\tSelectStmt *select;\n\tRawStmt *raw;\n\tOrderBySettings settings = { 0 };\n\n\tif (strlen(inpstr) == 0)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"ordering column can not be empty\"),\n\t\t\t\t errhint(\"timescaledb.compress_orderby option must reference a valid \"\n\t\t\t\t\t\t \"column or be removed to use default settings.\")));\n\n\tinitStringInfo(&buf);\n\n\t/* parse the segment by list exactly how you would a order by by */\n\tappendStringInfo(&buf,\n\t\t\t\t\t \"SELECT FROM %s.%s ORDER BY %s\",\n\t\t\t\t\t quote_identifier(NameStr(hypertable->fd.schema_name)),\n\t\t\t\t\t quote_identifier(NameStr(hypertable->fd.table_name)),\n\t\t\t\t\t inpstr);\n\n\tconst MemoryContext oldcontext = CurrentMemoryContext;\n\n\tPG_TRY();\n\t{\n\t\tparsed = raw_parser(buf.data, RAW_PARSE_DEFAULT);\n\t}\n\tPG_CATCH();\n\t{\n\t\t/* We do this fandango to avoid exhausting the error stack if we get\n\t\t * anything else but a syntax error, for example, an out of memory\n\t\t * error. */\n\t\tErrorData *edata;\n\t\tMemoryContextSwitchTo(oldcontext);\n\t\tedata = CopyErrorData();\n\t\tFlushErrorState();\n\t\tif (edata->sqlerrcode == ERRCODE_SYNTAX_ERROR)\n\t\t{\n\t\t\tedata->cursorpos = edata->internalpos = 0;\n\t\t\tedata->detail = edata->message;\n\t\t\tedata->message = psprintf(\"unable to parse ordering option \\\"%s\\\"\", inpstr);\n\t\t\tedata->hint = psprintf(\"The timescaledb.compress_orderby option must be a set of column\"\n\t\t\t\t\t\t\t\t   \" names with sort options, separated by commas.\"\n\t\t\t\t\t\t\t\t   \" It is the same format as an ORDER BY clause.\");\n\t\t}\n\t\tReThrowError(edata);\n\t}\n\tPG_END_TRY();\n\n\tif (list_length(parsed) != 1)\n\t\tthrow_order_by_error(inpstr);\n\tif (!IsA(linitial(parsed), RawStmt))\n\t\tthrow_order_by_error(inpstr);\n\traw = linitial(parsed);\n\tif (!IsA(raw->stmt, SelectStmt))\n\t\tthrow_order_by_error(inpstr);\n\tselect = (SelectStmt *) raw->stmt;\n\n\tif (!select_stmt_as_expected(select))\n\t\tthrow_order_by_error(inpstr);\n\n\tif (select->groupClause != NIL)\n\t\tthrow_order_by_error(inpstr);\n\n\tforeach (lc, select->sortClause)\n\t{\n\t\tSortBy *sort_by;\n\t\tColumnRef *cf;\n\t\tCompressedParsedCol *col = (CompressedParsedCol *) palloc(sizeof(*col));\n\t\tbool desc, nullsfirst;\n\n\t\tif (!IsA(lfirst(lc), SortBy))\n\t\t\tthrow_order_by_error(inpstr);\n\t\tsort_by = lfirst(lc);\n\n\t\tif (!IsA(sort_by->node, ColumnRef))\n\t\t\tthrow_order_by_error(inpstr);\n\t\tcf = (ColumnRef *) sort_by->node;\n\n\t\tif (list_length(cf->fields) != 1)\n\t\t\tthrow_order_by_error(inpstr);\n\n\t\tif (!IsA(linitial(cf->fields), String))\n\t\t\tthrow_order_by_error(inpstr);\n\n\t\tnamestrcpy(&col->colname, strVal(linitial(cf->fields)));\n\t\tchar *colname = strVal(linitial(cf->fields));\n\n\t\tAttrNumber col_attno = get_attnum(hypertable->main_table_relid, colname);\n\t\tif (col_attno == InvalidAttrNumber)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_SYNTAX_ERROR),\n\t\t\t\t\t errmsg(\"column \\\"%s\\\" does not exist\", NameStr(col->colname)),\n\t\t\t\t\t errhint(\"The timescaledb.compress_orderby option must reference a valid \"\n\t\t\t\t\t\t\t \"column.\")));\n\n\t\tOid col_type = get_atttype(hypertable->main_table_relid, col_attno);\n\t\tTypeCacheEntry *type = lookup_type_cache(col_type, TYPECACHE_LT_OPR);\n\n\t\tif (!OidIsValid(type->lt_opr))\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_FUNCTION),\n\t\t\t\t\t errmsg(\"invalid ordering column type %s\", format_type_be(col_type)),\n\t\t\t\t\t errdetail(\"Could not identify a less-than operator for the type.\")));\n\n\t\t/* get normalized column name */\n\t\tcolname = get_attname(hypertable->main_table_relid, col_attno, false);\n\n\t\t/* check if orderby columns are distinct. */\n\t\tif (ts_array_is_member(settings.orderby, colname))\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_SYNTAX_ERROR),\n\t\t\t\t\t errmsg(\"duplicate column name \\\"%s\\\"\", colname),\n\t\t\t\t\t errhint(\"The timescaledb.compress_orderby option must reference distinct \"\n\t\t\t\t\t\t\t \"column.\")));\n\n\t\tif (sort_by->sortby_dir != SORTBY_ASC && sort_by->sortby_dir != SORTBY_DESC &&\n\t\t\tsort_by->sortby_dir != SORTBY_DEFAULT)\n\t\t\tthrow_order_by_error(inpstr);\n\n\t\tdesc = sort_by->sortby_dir == SORTBY_DESC;\n\n\t\tif (sort_by->sortby_nulls == SORTBY_NULLS_DEFAULT)\n\t\t{\n\t\t\t/* default null ordering is LAST for ASC, FIRST for DESC */\n\t\t\tnullsfirst = desc;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tnullsfirst = sort_by->sortby_nulls == SORTBY_NULLS_FIRST;\n\t\t}\n\n\t\tsettings.orderby = ts_array_add_element_text(settings.orderby, pstrdup(colname));\n\t\tsettings.orderby_desc = ts_array_add_element_bool(settings.orderby_desc, desc);\n\t\tsettings.orderby_nullsfirst =\n\t\t\tts_array_add_element_bool(settings.orderby_nullsfirst, nullsfirst);\n\t}\n\n\tEnsure(settings.orderby, \"orderby setting is NULL after parsing\");\n\n\treturn settings;\n}\n\nstatic inline void\nthrow_sparse_index_error(char *sparse_index)\n{\n\tereport(ERROR,\n\t\t\t(errcode(ERRCODE_SYNTAX_ERROR),\n\t\t\t errmsg(\"unable to parse sparse index option \\\"%s\\\"\", sparse_index)));\n}\n\nstatic SparseIndexTypeEnum\nsparse_index_type_with_clause_parse(const char *parse, const WithClauseDefinition *args, int nargs)\n{\n\tAssert((int) _SparseIndexTypeEnumMax == nargs);\n\tint i;\n\tfor (i = 0; i < nargs; i++)\n\t{\n\t\tfor (int j = 0; args[i].arg_names[j] != NULL; ++j)\n\t\t{\n\t\t\tif (pg_strcasecmp(parse, args[i].arg_names[j]) == 0)\n\t\t\t{\n\t\t\t\treturn (SparseIndexTypeEnum) i;\n\t\t\t}\n\t\t}\n\t}\n\n\tereport(ERROR,\n\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t errmsg(\"unrecognized sparse index type \\\"%s\\\"\", parse)));\n\n\treturn _SparseIndexTypeEnumMax;\n}\n\nstatic SparseIndexColumn\nparse_sparse_index_column(Hypertable *hypertable, FuncCall *sparse_index_details, int index,\n\t\t\t\t\t\t  SparseIndexTypeEnum type)\n{\n\tSparseIndexColumn column;\n\tAssert(index >= 0);\n\tAssert(list_length(sparse_index_details->args) > index);\n\n\tif (index >= list_length(sparse_index_details->args) || index >= MAX_BLOOM_FILTER_COLUMNS)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_SYNTAX_ERROR),\n\t\t\t\t errmsg(\"sparse index %s has too many columns\", ts_sparse_index_type_names[type])));\n\n\tNode *arg = list_nth(sparse_index_details->args, index);\n\tif (!IsA(arg, ColumnRef))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_SYNTAX_ERROR),\n\t\t\t\t errmsg(\"sparse index column reference must reference a valid column name\")));\n\n\tColumnRef *cf = (ColumnRef *) arg;\n\tif (list_length(cf->fields) != 1 || !IsA(linitial(cf->fields), String))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_SYNTAX_ERROR),\n\t\t\t\t errmsg(\"invalid sparse index column reference syntax\"),\n\t\t\t\t errdetail(\n\t\t\t\t\t \"Wildcard or qualified references like '*' or 'table.col' are not allowed.\")));\n\n\tcolumn.name = strVal(linitial(cf->fields));\n\tcolumn.attnum = get_attnum(hypertable->main_table_relid, column.name);\n\tif (column.attnum == InvalidAttrNumber)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_SYNTAX_ERROR),\n\t\t\t\t errmsg(\"column \\\"%s\\\" does not exist\", column.name),\n\t\t\t\t errhint(\"The sparse index %s option must reference a valid \"\n\t\t\t\t\t\t \"column.\",\n\t\t\t\t\t\t ts_sparse_index_type_names[type])));\n\t}\n\n\t/* get normalized column name */\n\tcolumn.name = get_attname(hypertable->main_table_relid, column.attnum, false);\n\tcolumn.type = get_atttype(hypertable->main_table_relid, column.attnum);\n\n\treturn column;\n}\n\nstatic const char *\ncolumn_name_list_as_string(BloomFilterConfig *config)\n{\n\tStringInfoData buf;\n\tinitStringInfo(&buf);\n\tappendStringInfo(&buf, \"(\");\n\tfor (int i = 0; i < config->num_columns; i++)\n\t{\n\t\tappendStringInfo(&buf, \"'%s'\", config->columns[i].name);\n\t\tif (i < config->num_columns - 1)\n\t\t\tappendStringInfo(&buf, \",\");\n\t}\n\tappendStringInfo(&buf, \")\");\n\treturn buf.data;\n}\n\n/* parses the individual sparse index config entities. being called once for each sparse index\n * config entity in the list. */\nstatic void\nparse_sparse_index_config(JsonbParseState *parse_state, FuncCall *sparse_index_details,\n\t\t\t\t\t\t  Hypertable *hypertable, TsBmsList *sparse_index_columns)\n{\n\tTypeCacheEntry *type_cache;\n\tMinmaxIndexColumnConfig minmax_config;\n\tBloomFilterConfig bloom_config;\n\tSparseIndexConfigBase config;\n\tSparseIndexConfigBase *config_ptr = &config;\n\tSparseIndexColumn first_column;\n\n\tconfig.type =\n\t\tsparse_index_type_with_clause_parse(NameListToString(sparse_index_details->funcname),\n\t\t\t\t\t\t\t\t\t\t\tsparse_index_with_clause_def,\n\t\t\t\t\t\t\t\t\t\t\tTS_ARRAY_LEN(sparse_index_with_clause_def));\n\tconfig.source = _SparseIndexSourceEnumConfig;\n\tint num_columns = list_length(sparse_index_details->args);\n\n\tif (num_columns != 1)\n\t{\n\t\tif (num_columns > 1 && config.type == _SparseIndexTypeEnumBloom)\n\t\t{\n\t\t\t/* This will be enabled once all composite bloom index functionality is rolled out */\n\t\t\tif (!ts_guc_enable_composite_bloom_indexes)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t errmsg(\"composite bloom indexes are disabled\"),\n\t\t\t\t\t\t errhint(\"Set timescaledb.enable_composite_bloom_indexes = true\")));\n\n\t\t\tif (num_columns > MAX_BLOOM_FILTER_COLUMNS)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_SYNTAX_ERROR),\n\t\t\t\t\t\t errmsg(\"bloom index has too many columns: %d > max %d\",\n\t\t\t\t\t\t\t\tnum_columns,\n\t\t\t\t\t\t\t\tMAX_BLOOM_FILTER_COLUMNS)));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_SYNTAX_ERROR),\n\t\t\t\t\t errmsg(\"minmax index can only have one column\")));\n\t\t}\n\t}\n\n\t/* parse the first column separately because we only need one for minmax */\n\tfirst_column = parse_sparse_index_column(hypertable, sparse_index_details, 0, config.type);\n\tBitmapset *attnums_bitmap = bms_make_singleton(first_column.attnum);\n\n\t/* extract custom sparse index type config */\n\tswitch (config.type)\n\t{\n\t\tcase _SparseIndexTypeEnumBloom:\n\t\t\tif (!ts_guc_enable_sparse_index_bloom)\n\t\t\t{\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t\t\t errmsg(\"Creating bloom sparse index is disabled\"),\n\t\t\t\t\t\t errhint(\"Either set \\\"enable_sparse_index_bloom\\\" to true or remove the \"\n\t\t\t\t\t\t\t\t \"bloom filter indexes from \\\"sparse_index\\\" configuration of the \"\n\t\t\t\t\t\t\t\t \"hypertable.\")));\n\t\t\t}\n\n\t\t\tbloom_config.base = config;\n\t\t\tconfig_ptr = (SparseIndexConfigBase *) &bloom_config;\n\t\t\tbloom_config.num_columns = num_columns;\n\n\t\t\tbloom_config.columns = palloc(num_columns * sizeof(SparseIndexColumn));\n\t\t\tbloom_config.columns[0] = first_column;\n\t\t\tfor (int i = 1; i < num_columns; i++)\n\t\t\t{\n\t\t\t\tbloom_config.columns[i] =\n\t\t\t\t\tparse_sparse_index_column(hypertable, sparse_index_details, i, config.type);\n\t\t\t\tattnums_bitmap = bms_add_member(attnums_bitmap, bloom_config.columns[i].attnum);\n\t\t\t\tif (bms_num_members(attnums_bitmap) <= i)\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_SYNTAX_ERROR),\n\t\t\t\t\t\t\t errmsg(\"duplicate column name ('%s') in composite bloom index \"\n\t\t\t\t\t\t\t\t\t\"configuration: %s\",\n\t\t\t\t\t\t\t\t\tbloom_config.columns[i].name,\n\t\t\t\t\t\t\t\t\tcolumn_name_list_as_string(&bloom_config)),\n\t\t\t\t\t\t\t errhint(\n\t\t\t\t\t\t\t\t \"The sparse index option must reference distinct column set.\")));\n\t\t\t}\n\n\t\t\tif (ts_bmslist_contains_set(*sparse_index_columns, attnums_bitmap))\n\t\t\t{\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_SYNTAX_ERROR),\n\t\t\t\t\t\t errmsg(\"duplicate sparse index configuration %s\",\n\t\t\t\t\t\t\t\tcolumn_name_list_as_string(&bloom_config)),\n\t\t\t\t\t\t errhint(\"The sparse index option must reference distinct column set.\")));\n\t\t\t}\n\t\t\t*sparse_index_columns = ts_bmslist_add_set(*sparse_index_columns, attnums_bitmap);\n\n\t\t\tfor (int i = 0; i < num_columns; i++)\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * The column type must be hashable. For some types we use our own hash functions\n\t\t\t\t * which have better characteristics.\n\t\t\t\t */\n\t\t\t\tFmgrInfo *finfo = NULL;\n\t\t\t\tif (ts_cm_functions->bloom1_get_hash_function(bloom_config.columns[i].type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  &finfo) == NULL)\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_FUNCTION),\n\t\t\t\t\t\t\t errmsg(\"invalid bloom filter column type %s\",\n\t\t\t\t\t\t\t\t\tformat_type_be(bloom_config.columns[i].type)),\n\t\t\t\t\t\t\t errdetail(\"Could not identify a hashing function for the type.\")));\n\t\t\t}\n\n\t\t\t/* the convention is that the column names are sorted by attribute number */\n\t\t\tqsort(bloom_config.columns,\n\t\t\t\t  num_columns,\n\t\t\t\t  sizeof(SparseIndexColumn),\n\t\t\t\t  ts_qsort_attrnumber_cmp);\n\t\t\tbreak;\n\n\t\tcase _SparseIndexTypeEnumMinmax:\n\t\t\tif (ts_bmslist_contains_set(*sparse_index_columns, attnums_bitmap))\n\t\t\t{\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_SYNTAX_ERROR),\n\t\t\t\t\t\t errmsg(\"duplicate column name \\\"%s\\\"\", first_column.name),\n\t\t\t\t\t\t errhint(\"The sparse index option must reference distinct \"\n\t\t\t\t\t\t\t\t \"column.\")));\n\t\t\t}\n\t\t\t*sparse_index_columns = ts_bmslist_add_set(*sparse_index_columns, attnums_bitmap);\n\t\t\ttype_cache = lookup_type_cache(first_column.type, TYPECACHE_LT_OPR);\n\n\t\t\t/*\n\t\t\t * a comparison operator is required for min max operations\n\t\t\t */\n\t\t\tif (!OidIsValid(type_cache->lt_opr))\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_FUNCTION),\n\t\t\t\t\t\t errmsg(\"invalid minmax column type %s\", format_type_be(first_column.type)),\n\t\t\t\t\t\t errdetail(\"Could not identify a less-than operator for the type.\")));\n\n\t\t\tminmax_config.base = config;\n\t\t\tconfig_ptr = (SparseIndexConfigBase *) &minmax_config;\n\t\t\tminmax_config.col = first_column.name;\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"Invalid sparse index type\")));\n\t}\n\n\tts_convert_sparse_index_config_to_jsonb(parse_state, config_ptr);\n}\n\nstatic Jsonb *\nparse_sparse_index_config_list(char *inpstr, Hypertable *hypertable)\n{\n\tStringInfoData buf;\n\tList *parsed;\n\tListCell *lc;\n\tSelectStmt *select;\n\tRawStmt *raw;\n\tJsonbParseState *parse_state = NULL;\n\n\t/* sparse index can have empty input. Return [{\"source\":\"config\"}] jsonb */\n\tif (strlen(inpstr) == 0)\n\t{\n\t\tpushJsonbValue(&parse_state, WJB_BEGIN_ARRAY, NULL);\n\t\tpushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);\n\t\tts_jsonb_add_str(parse_state,\n\t\t\t\t\t\t ts_sparse_index_common_keys[SparseIndexKeySource],\n\t\t\t\t\t\t ts_sparse_index_source_names[_SparseIndexSourceEnumConfig]); /* source */\n\t\tJsonbValueToJsonb(pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL));\n\t\treturn JsonbValueToJsonb(pushJsonbValue(&parse_state, WJB_END_ARRAY, NULL));\n\t}\n\n\tinitStringInfo(&buf);\n\n\t/* parse the sparse index list exactly how you would targetlist */\n\tappendStringInfo(&buf, \"SELECT %s\", inpstr);\n\n\tconst MemoryContext oldcontext = CurrentMemoryContext;\n\n\tPG_TRY();\n\t{\n\t\tparsed = raw_parser(buf.data, RAW_PARSE_DEFAULT);\n\t}\n\tPG_CATCH();\n\t{\n\t\t/* We do this fandango to avoid exhausting the error stack if we get\n\t\t * anything else but a syntax error, for example, an out of memory\n\t\t * error. */\n\t\tErrorData *edata;\n\t\tMemoryContextSwitchTo(oldcontext);\n\t\tedata = CopyErrorData();\n\t\tFlushErrorState();\n\t\tif (edata->sqlerrcode == ERRCODE_SYNTAX_ERROR)\n\t\t{\n\t\t\tedata->cursorpos = edata->internalpos = 0;\n\t\t\tedata->detail = edata->message;\n\t\t\tedata->message = psprintf(\"unable to parse sparse index option \\\"%s\\\"\", inpstr);\n\t\t}\n\t\tReThrowError(edata);\n\t}\n\tPG_END_TRY();\n\n\tif (list_length(parsed) != 1)\n\t\tthrow_sparse_index_error(inpstr);\n\tif (!IsA(linitial(parsed), RawStmt))\n\t\tthrow_sparse_index_error(inpstr);\n\traw = linitial(parsed);\n\n\tif (!IsA(raw->stmt, SelectStmt))\n\t\tthrow_sparse_index_error(inpstr);\n\tselect = (SelectStmt *) raw->stmt;\n\n\tif (select->targetList == NULL)\n\t\tthrow_sparse_index_error(inpstr);\n\n\t/* json format will be\n\t * [{\"type\": \"bloom\", \"source\":\"config\", \"column\": \"u\"},\n\t * {\"type\": \"minmax\",\"source\":\"config\", \"column\": \"ts\"},\n\t * {\"type\": \"bloom\", \"source\":\"config\", \"column\": [\"age\", \"gender\"]}]\n\t */\n\tpushJsonbValue(&parse_state, WJB_BEGIN_ARRAY, NULL);\n\n\tTsBmsList sparse_index_columns = ts_bmslist_create();\n\tforeach (lc, select->targetList)\n\t{\n\t\tResTarget *target = lfirst_node(ResTarget, lc);\n\n\t\tif (!IsA(target->val, FuncCall))\n\t\t\tthrow_sparse_index_error(inpstr);\n\n\t\tFuncCall *fc = (FuncCall *) target->val;\n\n\t\tparse_sparse_index_config(parse_state, fc, hypertable, &sparse_index_columns);\n\t}\n\n\tts_bmslist_free(sparse_index_columns);\n\treturn JsonbValueToJsonb(pushJsonbValue(&parse_state, WJB_END_ARRAY, NULL));\n}\n\n/* returns List of CompressedParsedCol\n * compress_segmentby = `col1,col2,col3`\n */\nArrayType *\nts_compress_hypertable_parse_segment_by(WithClauseResult segmentby, Hypertable *hypertable)\n{\n\tif (!segmentby.is_default)\n\t{\n\t\treturn parse_segment_collist(TextDatumGetCString(segmentby.parsed), hypertable);\n\t}\n\telse\n\t\treturn NULL;\n}\n\n/* returns List of CompressedParsedCol\n * E.g. timescaledb.compress_orderby = 'col1 asc nulls first,col2 desc,col3'\n */\nOrderBySettings\nts_compress_hypertable_parse_order_by(WithClauseResult orderby, Hypertable *hypertable)\n{\n\tEnsure(!orderby.is_default, \"with clause is not default\");\n\treturn ts_compress_parse_order_collist(TextDatumGetCString(orderby.parsed), hypertable);\n}\n\n/* returns List of CompressedParsedCol\n * E.g. timescaledb.compress_orderby = 'col1 asc nulls first,col2 desc,col3'\n */\nInterval *\nts_compress_hypertable_parse_chunk_time_interval(WithClauseResult *parsed_options,\n\t\t\t\t\t\t\t\t\t\t\t\t Hypertable *hypertable)\n{\n\tif (parsed_options[AlterTableFlagCompressChunkTimeInterval].is_default == false)\n\t{\n\t\tDatum textarg = parsed_options[AlterTableFlagCompressChunkTimeInterval].parsed;\n\t\treturn DatumGetIntervalP(textarg);\n\t}\n\telse\n\t\treturn NULL;\n}\n\n/* returns List of CompressedParsedCol\n * compress_minmax = `col1,col2,col3`\n */\nJsonb *\nts_compress_hypertable_parse_index(WithClauseResult index, Hypertable *hypertable)\n{\n\tif (!index.is_default)\n\t{\n\t\treturn parse_sparse_index_config_list(TextDatumGetCString(index.parsed), hypertable);\n\t}\n\telse\n\t\treturn NULL;\n}\n"
  },
  {
    "path": "src/with_clause/alter_table_with_clause.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <catalog/pg_type.h>\n\n#include \"chunk.h\"\n#include \"ts_catalog/catalog.h\"\n\n#include \"with_clause_parser.h\"\n\ntypedef enum AlterTableFlags\n{\n\tAlterTableFlagChunkTimeInterval = 0,\n\tAlterTableFlagColumnstore,\n\tAlterTableFlagSegmentBy,\n\tAlterTableFlagOrderBy,\n\tAlterTableFlagCompressChunkTimeInterval,\n\tAlterTableFlagIndex,\n\tAlterTableFlagsMax\n} AlterTableFlags;\n\ntypedef struct\n{\n\tNameData colname;\n\tbool nullsfirst;\n\tbool desc;\n} CompressedParsedCol;\n\ntypedef struct\n{\n\tArrayType *orderby;\n\tArrayType *orderby_desc;\n\tArrayType *orderby_nullsfirst;\n} OrderBySettings;\n\nextern TSDLLEXPORT WithClauseResult *ts_alter_table_with_clause_parse(const List *defelems);\nextern TSDLLEXPORT WithClauseResult *ts_alter_table_reset_with_clause_parse(const List *defelems);\nextern TSDLLEXPORT ArrayType *ts_compress_hypertable_parse_segment_by(WithClauseResult segmentby,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  Hypertable *hypertable);\nextern TSDLLEXPORT OrderBySettings ts_compress_hypertable_parse_order_by(WithClauseResult orderby,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t Hypertable *hypertable);\nextern TSDLLEXPORT Interval *\nts_compress_hypertable_parse_chunk_time_interval(WithClauseResult *parsed_options,\n\t\t\t\t\t\t\t\t\t\t\t\t Hypertable *hypertable);\nextern TSDLLEXPORT OrderBySettings ts_compress_parse_order_collist(char *inpstr,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   Hypertable *hypertable);\nextern TSDLLEXPORT Jsonb *ts_compress_hypertable_parse_index(WithClauseResult index,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t Hypertable *hypertable);\n"
  },
  {
    "path": "src/with_clause/create_materialized_view_with_clause.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n\n#include <catalog/pg_type.h>\n#include <fmgr.h>\n#include <nodes/makefuncs.h>\n\n#include \"compat/compat.h\"\n\n#include \"alter_table_with_clause.h\"\n#include \"create_materialized_view_with_clause.h\"\n#include \"cross_module_fn.h\"\n#include \"ts_catalog/continuous_agg.h\"\n#include \"with_clause_parser.h\"\n\nstatic const WithClauseDefinition continuous_aggregate_with_clause_def[] = {\n    [CreateMaterializedViewFlagContinuous] = {\n        .arg_names = {\"continuous\", NULL},\n        .type_id = BOOLOID,\n        .default_val = (Datum)false,\n    },\n    [CreateMaterializedViewFlagCreateGroupIndexes] = {\n        .arg_names = {\"create_group_indexes\", NULL},\n        .type_id = BOOLOID,\n        .default_val = (Datum)true,\n    },\n    [CreateMaterializedViewFlagMaterializedOnly] = {\n        .arg_names = {\"materialized_only\", NULL},\n        .type_id = BOOLOID,\n        .default_val = (Datum)true,\n    },\n    [CreateMaterializedViewFlagColumnstore] = {\n        .arg_names = {\"columnstore\", \"enable_columnstore\", \"compress\", NULL},\n        .type_id = BOOLOID,\n    },\n    [CreateMaterializedViewFlagChunkTimeInterval] = {\n        .arg_names = {\"chunk_interval\", NULL},\n         .type_id = INTERVALOID,\n    },\n    [CreateMaterializedViewFlagSegmentBy] = {\n        .arg_names = {\"segmentby\", \"segment_by\", \"compress_segmentby\", NULL},\n        .type_id = TEXTOID,\n    },\n    [CreateMaterializedViewFlagOrderBy] = {\n        .arg_names = {\"orderby\", \"order_by\", \"compress_orderby\", NULL},\n         .type_id = TEXTOID,\n    },\n    [CreateMaterializedViewFlagCompressChunkTimeInterval] = {\n        .arg_names = {\"compress_chunk_interval\", \"compress_chunk_time_interval\", NULL},\n         .type_id = INTERVALOID,\n    },\n};\n\nWithClauseResult *\nts_create_materialized_view_with_clause_parse(const List *defelems)\n{\n\treturn ts_with_clauses_parse(defelems,\n\t\t\t\t\t\t\t\t continuous_aggregate_with_clause_def,\n\t\t\t\t\t\t\t\t TS_ARRAY_LEN(continuous_aggregate_with_clause_def));\n}\n\nList *\nts_continuous_agg_get_compression_defelems(const WithClauseResult *with_clauses)\n{\n\tList *ret = NIL;\n\n\tfor (int i = 0; i < AlterTableFlagsMax; i++)\n\t{\n\t\tint option_index = 0;\n\t\tswitch (i)\n\t\t{\n\t\t\tcase AlterTableFlagChunkTimeInterval:\n\t\t\tcase AlterTableFlagIndex:\n\t\t\t\tcontinue;\n\t\t\t\tbreak;\n\t\t\tcase AlterTableFlagColumnstore:\n\t\t\t\toption_index = CreateMaterializedViewFlagColumnstore;\n\t\t\t\tbreak;\n\t\t\tcase AlterTableFlagSegmentBy:\n\t\t\t\toption_index = CreateMaterializedViewFlagSegmentBy;\n\t\t\t\tbreak;\n\t\t\tcase AlterTableFlagOrderBy:\n\t\t\t\toption_index = CreateMaterializedViewFlagOrderBy;\n\t\t\t\tbreak;\n\t\t\tcase AlterTableFlagCompressChunkTimeInterval:\n\t\t\t\toption_index = CreateMaterializedViewFlagCompressChunkTimeInterval;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\telog(ERROR, \"Unhandled compression option\");\n\t\t\t\tbreak;\n\t\t}\n\n\t\tconst WithClauseResult *input = &with_clauses[option_index];\n\t\tWithClauseDefinition def = continuous_aggregate_with_clause_def[option_index];\n\n\t\tif (!input->is_default)\n\t\t{\n\t\t\tNode *value = (Node *) makeString(ts_with_clause_result_deparse_value(input));\n\t\t\tDefElem *elem = makeDefElemExtended(EXTENSION_NAMESPACE,\n\t\t\t\t\t\t\t\t\t\t\t\t(char *) def.arg_names[0],\n\t\t\t\t\t\t\t\t\t\t\t\tvalue,\n\t\t\t\t\t\t\t\t\t\t\t\tDEFELEM_UNSPEC,\n\t\t\t\t\t\t\t\t\t\t\t\t-1);\n\t\t\tret = lappend(ret, elem);\n\t\t}\n\t}\n\treturn ret;\n}\n"
  },
  {
    "path": "src/with_clause/create_materialized_view_with_clause.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include \"with_clause_parser.h\"\n\ntypedef enum CreateMaterializedViewFlags\n{\n\tCreateMaterializedViewFlagContinuous = 0,\n\tCreateMaterializedViewFlagCreateGroupIndexes,\n\tCreateMaterializedViewFlagMaterializedOnly,\n\tCreateMaterializedViewFlagColumnstore,\n\tCreateMaterializedViewFlagChunkTimeInterval,\n\tCreateMaterializedViewFlagSegmentBy,\n\tCreateMaterializedViewFlagOrderBy,\n\tCreateMaterializedViewFlagCompressChunkTimeInterval,\n} CreateMaterializedViewFlags;\n\nextern TSDLLEXPORT WithClauseResult *\nts_create_materialized_view_with_clause_parse(const List *defelems);\n\nextern TSDLLEXPORT List *\nts_continuous_agg_get_compression_defelems(const WithClauseResult *with_clauses);\n"
  },
  {
    "path": "src/with_clause/create_table_with_clause.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <catalog/pg_type.h>\n#include <fmgr.h>\n\n#include \"compat/compat.h\"\n#include \"create_table_with_clause.h\"\n#include \"with_clause_parser.h\"\n\nstatic const WithClauseDefinition create_table_with_clauses_def[] = {\n\t[CreateTableFlagHypertable] = {.arg_names = {\"hypertable\", NULL}, .type_id = BOOLOID,},\n\t[CreateTableFlagColumnstore] = {.arg_names = {\"columnstore\", \"enable_columnstore\", \"compress\", NULL}, .type_id = BOOLOID, .default_val = (Datum)true,},\n\t[CreateTableFlagTimeColumn] = {.arg_names = {\"partition_column\", \"partitioning_column\", NULL}, .type_id = TEXTOID,},\n\t[CreateTableFlagChunkTimeInterval] = {.arg_names = {\"chunk_interval\", NULL}, .type_id = TEXTOID,},\n\t[CreateTableFlagCreateDefaultIndexes] = {.arg_names = {\"create_default_indexes\", NULL}, .type_id = BOOLOID, .default_val = (Datum) true,},\n\t[CreateTableFlagAssociatedSchema] = {.arg_names = {\"associated_schema\", NULL}, .type_id = TEXTOID,},\n\t[CreateTableFlagAssociatedTablePrefix] = {.arg_names = {\"associated_table_prefix\", NULL}, .type_id = TEXTOID,},\n\t[CreateTableFlagSegmentBy] = { .arg_names = {\"segmentby\", \"segment_by\", \"compress_segmentby\", NULL}, .type_id = TEXTOID,},\n\t[CreateTableFlagOrderBy] = { .arg_names = {\"orderby\", \"order_by\", \"compress_orderby\", NULL}, .type_id = TEXTOID,},\n\t[CreateTableFlagIndex] = { .arg_names = {\"compress_index\", \"compress_sparse_index\", \"index\", \"sparse_index\", NULL}, .type_id = TEXTOID,},\n};\n\nWithClauseResult *\nts_create_table_with_clause_parse(const List *defelems)\n{\n\treturn ts_with_clauses_parse(defelems,\n\t\t\t\t\t\t\t\t create_table_with_clauses_def,\n\t\t\t\t\t\t\t\t TS_ARRAY_LEN(create_table_with_clauses_def));\n}\n\nDatum\nts_create_table_parse_chunk_time_interval(WithClauseResult option, Oid column_type,\n\t\t\t\t\t\t\t\t\t\t  Oid *interval_type)\n{\n\tif (option.is_default == false)\n\t{\n\t\tDatum textarg = option.parsed;\n\t\tswitch (column_type)\n\t\t{\n\t\t\tcase INT2OID:\n\t\t\t{\n\t\t\t\t*interval_type = INT2OID;\n\t\t\t\treturn DirectFunctionCall1(int2in, CStringGetDatum(TextDatumGetCString(textarg)));\n\t\t\t}\n\t\t\tcase INT4OID:\n\t\t\t{\n\t\t\t\t*interval_type = INT4OID;\n\t\t\t\treturn DirectFunctionCall1(int4in, CStringGetDatum(TextDatumGetCString(textarg)));\n\t\t\t}\n\t\t\tcase INT8OID:\n\t\t\t{\n\t\t\t\t*interval_type = INT8OID;\n\t\t\t\treturn DirectFunctionCall1(int8in, CStringGetDatum(TextDatumGetCString(textarg)));\n\t\t\t}\n\t\t\tcase TIMESTAMPOID:\n\t\t\tcase TIMESTAMPTZOID:\n\t\t\tcase DATEOID:\n\t\t\tcase UUIDOID:\n\t\t\t{\n\t\t\t\t*interval_type = INTERVALOID;\n\t\t\t\treturn DirectFunctionCall3(interval_in,\n\t\t\t\t\t\t\t\t\t\t   CStringGetDatum(TextDatumGetCString(textarg)),\n\t\t\t\t\t\t\t\t\t\t   InvalidOid,\n\t\t\t\t\t\t\t\t\t\t   -1);\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tEnsure(false,\n\t\t\t\t\t   \"unexpected column type %s when setting chunk interval\",\n\t\t\t\t\t   format_type_be(column_type));\n\t\t}\n\t}\n\t*interval_type = InvalidOid;\n\treturn UnassignedDatum;\n}\n"
  },
  {
    "path": "src/with_clause/create_table_with_clause.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include \"with_clause_parser.h\"\n\ntypedef enum CreateTableFlags\n{\n\tCreateTableFlagHypertable = 0,\n\tCreateTableFlagColumnstore,\n\tCreateTableFlagTimeColumn,\n\tCreateTableFlagChunkTimeInterval,\n\tCreateTableFlagCreateDefaultIndexes,\n\tCreateTableFlagAssociatedSchema,\n\tCreateTableFlagAssociatedTablePrefix,\n\tCreateTableFlagOrderBy,\n\tCreateTableFlagSegmentBy,\n\tCreateTableFlagIndex\n} CreateTableFlags;\n\nWithClauseResult *ts_create_table_with_clause_parse(const List *defelems);\n\nDatum ts_create_table_parse_chunk_time_interval(WithClauseResult option, Oid column_type,\n\t\t\t\t\t\t\t\t\t\t\t\tOid *interval_type);\n"
  },
  {
    "path": "src/with_clause/with_clause_parser.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <fmgr.h>\n\n#include <access/htup_details.h>\n#include <catalog/pg_type.h>\n#include <commands/defrem.h>\n#include <nodes/parsenodes.h>\n#include <utils/builtins.h>\n#include <utils/lsyscache.h>\n#include <utils/syscache.h>\n\n#include \"debug_assert.h\"\n#include \"extension_constants.h\"\n#include \"with_clause_parser.h\"\n\n/*\n * Filter a list of DefElem based on a namespace.\n * This function will iterate through DefElem and output up to two lists:\n *         within_namespace: every element within the namespace\n *     not_within_namespace: all the other elements\n *\n * That is, given a with clause like:\n *     WITH (foo.foo_para, bar.bar_param, baz.baz_param)\n *\n * ts_with_clause_filter(elems, \"foo\", in, not_in) will have\n *        in = foo.foo_para\n *    not_in = bar.bar_param, baz.baz_param\n */\nvoid\nts_with_clause_filter(const List *def_elems, List **within_namespace, List **other_namespace,\n\t\t\t\t\t  List **not_within_namespace)\n{\n\tListCell *cell;\n\n\tforeach (cell, def_elems)\n\t{\n\t\tDefElem *def = (DefElem *) lfirst(cell);\n\n\t\tif (def->defnamespace != NULL &&\n\t\t\t(pg_strcasecmp(def->defnamespace, EXTENSION_NAMESPACE) == 0 ||\n\t\t\t pg_strcasecmp(def->defnamespace, EXTENSION_NAMESPACE_ALIAS) == 0))\n\t\t{\n\t\t\tif (within_namespace != NULL)\n\t\t\t\t*within_namespace = lappend(*within_namespace, def);\n\t\t}\n\t\telse if (def->defnamespace != NULL && other_namespace != NULL)\n\t\t{\n\t\t\t*other_namespace = lappend(*other_namespace, def);\n\t\t}\n\t\telse if (not_within_namespace != NULL)\n\t\t{\n\t\t\t*not_within_namespace = lappend(*not_within_namespace, def);\n\t\t}\n\t}\n}\n\nstatic Datum parse_arg(WithClauseDefinition arg, DefElem *def);\n\nstatic char *\nts_with_clause_definition_names(const WithClauseDefinition *args, Size nargs)\n{\n\tStringInfoData buf;\n\tSize i;\n\n\tinitStringInfo(&buf);\n\n\tfor (i = 0; i < nargs; i++)\n\t{\n\t\tif (i > 0)\n\t\t\tappendStringInfoString(&buf, \", \");\n\t\tappendStringInfoString(&buf, args[i].arg_names[0]);\n\t}\n\n\treturn buf.data;\n}\n\n/*\n * Deserialize and apply the values in a WITH clause based on the on_arg table.\n *\n * This function will go through every element in def_elems and search for a\n * corresponding argument in args, if one is found it will attempt to deserialize\n * the argument, using that table elements deserialize function, then apply it\n * to state.\n *\n * This is used to turn the list into a form more useful for our internal\n * functions\n */\nWithClauseResult *\nts_with_clauses_parse(const List *def_elems, const WithClauseDefinition *args, Size nargs)\n{\n\tListCell *cell;\n\tWithClauseResult *results = palloc0(sizeof(*results) * nargs);\n\tSize i;\n\n\tfor (i = 0; i < nargs; i++)\n\t{\n\t\tresults[i].definition = &args[i];\n\t\tresults[i].parsed = args[i].default_val;\n\t\tresults[i].is_default = true;\n\t}\n\n\tforeach (cell, def_elems)\n\t{\n\t\tDefElem *def = (DefElem *) lfirst(cell);\n\t\tbool argument_recognized = false;\n\n\t\tfor (i = 0; i < nargs; i++)\n\t\t{\n\t\t\tfor (int j = 0; args[i].arg_names[j] != NULL; ++j)\n\t\t\t{\n\t\t\t\tif (pg_strcasecmp(def->defname, args[i].arg_names[j]) == 0)\n\t\t\t\t{\n\t\t\t\t\targument_recognized = true;\n\n\t\t\t\t\tif (!results[i].is_default)\n\t\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t\t(errcode(ERRCODE_AMBIGUOUS_PARAMETER),\n\t\t\t\t\t\t\t\t errmsg(\"duplicate parameter \\\"%s.%s\\\"\",\n\t\t\t\t\t\t\t\t\t\tdef->defnamespace,\n\t\t\t\t\t\t\t\t\t\tdef->defname)));\n\n\t\t\t\t\tresults[i].parsed = parse_arg(args[i], def);\n\t\t\t\t\tresults[i].is_default = false;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (!argument_recognized)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"unrecognized parameter \\\"%s.%s\\\"\", def->defnamespace, def->defname),\n\t\t\t\t\t errhint(\"Valid timescaledb parameters are: %s\",\n\t\t\t\t\t\t\t ts_with_clause_definition_names(args, nargs))));\n\t}\n\n\treturn results;\n}\n\n/*\n * This function handles parsing of WITH clauses for ALTER TABLE RESET.\n * Unlike ts_with_clauses_parse, it does not parse any option values,\n * as RESET clauses only include option names without associated values.\n */\nWithClauseResult *\nts_with_clauses_parse_reset(const List *def_elems, const WithClauseDefinition *args, Size nargs)\n{\n\tListCell *cell;\n\tWithClauseResult *results = palloc0(sizeof(*results) * nargs);\n\tSize i;\n\n\tfor (i = 0; i < nargs; i++)\n\t{\n\t\tresults[i].definition = &args[i];\n\t\tresults[i].parsed = args[i].default_val;\n\t\tresults[i].is_default = true;\n\t}\n\n\tforeach (cell, def_elems)\n\t{\n\t\tDefElem *def = (DefElem *) lfirst(cell);\n\t\tbool argument_recognized = false;\n\n\t\tfor (i = 0; i < nargs; i++)\n\t\t{\n\t\t\tfor (int j = 0; args[i].arg_names[j] != NULL; ++j)\n\t\t\t{\n\t\t\t\tif (pg_strcasecmp(def->defname, args[i].arg_names[j]) == 0)\n\t\t\t\t{\n\t\t\t\t\targument_recognized = true;\n\n\t\t\t\t\tif (!results[i].is_default)\n\t\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t\t(errcode(ERRCODE_AMBIGUOUS_PARAMETER),\n\t\t\t\t\t\t\t\t errmsg(\"duplicate parameter \\\"%s.%s\\\"\",\n\t\t\t\t\t\t\t\t\t\tdef->defnamespace,\n\t\t\t\t\t\t\t\t\t\tdef->defname)));\n\n\t\t\t\t\tresults[i].is_default = false;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (!argument_recognized)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"unrecognized parameter \\\"%s.%s\\\"\", def->defnamespace, def->defname),\n\t\t\t\t\t errhint(\"Valid timescaledb parameters are: %s\",\n\t\t\t\t\t\t\t ts_with_clause_definition_names(args, nargs))));\n\t}\n\n\treturn results;\n}\n\nextern TSDLLEXPORT char *\nts_with_clause_result_deparse_value(const WithClauseResult *result)\n{\n\tEnsure(OidIsValid(result->definition->type_id),\n\t\t   \"argument \\\"%d\\\" has invalid OID\",\n\t\t   result->definition->type_id);\n\n\treturn ts_datum_to_string(result->parsed, result->definition->type_id);\n}\n\nstatic Datum\nparse_arg(WithClauseDefinition arg, DefElem *def)\n{\n\tchar *value;\n\tDatum val;\n\tOid in_fn;\n\tOid typIOParam;\n\n\tif (!OidIsValid(arg.type_id))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_PARAMETER),\n\t\t\t\t errmsg(\"argument \\\"%s.%s\\\" not implemented\", def->defnamespace, def->defname)));\n\n\tif (def->arg != NULL)\n\t\tvalue = defGetString(def);\n\telse if (arg.type_id == BOOLOID)\n\t\t/* for booleans, postgres defines the option timescale.foo to be the same as\n\t\t * timescaledb.foo='true' so if no value is found set it to \"true\" here */\n\t\tvalue = \"true\";\n\telse\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"parameter \\\"%s.%s\\\" must have a value\", def->defnamespace, def->defname)));\n\n\tgetTypeInputInfo(arg.type_id, &in_fn, &typIOParam);\n\n\tAssert(OidIsValid(in_fn));\n\n\t/*\n\t * We could use InputFunctionCallSafe() here but this is just supported\n\t * for PG16 and later, so we opt for checking if the failure is what we\n\t * expected and re-throwing the error otherwise.\n\t */\n\tPG_TRY();\n\t{\n\t\tval = OidInputFunctionCall(in_fn, value, typIOParam, -1);\n\t}\n\tPG_CATCH();\n\t{\n\t\tconst int sqlerrcode = geterrcode();\n\t\t/*\n\t\t * We can deal with the Data Exception category and in the Syntax\n\t\t * Error or Access Rule Violation category, but if the error is an\n\t\t * insufficient resources category, for example, an out of memory\n\t\t * error, we should just re-throw it.\n\t\t *\n\t\t * Errors in other categories are unlikely, but we cannot do anything\n\t\t * with them anyway, so just re-throw them as well.\n\t\t */\n\t\tif (ERRCODE_TO_CATEGORY(sqlerrcode) != ERRCODE_DATA_EXCEPTION &&\n\t\t\tERRCODE_TO_CATEGORY(sqlerrcode) != ERRCODE_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION)\n\t\t{\n\t\t\tPG_RE_THROW();\n\t\t}\n\t\tFlushErrorState();\n\n\t\t/* We are currently using the ErrorContext, but since we are going to\n\t\t * raise an error later, there is no reason to switch memory context\n\t\t * nor restore the resource owner here. */\n\n\t\tForm_pg_type typetup;\n\t\tHeapTuple tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(arg.type_id));\n\t\tif (!HeapTupleIsValid(tup))\n\t\t\telog(ERROR,\n\t\t\t\t \"cache lookup failed for type of %s.%s '%u'\",\n\t\t\t\t def->defnamespace,\n\t\t\t\t def->defname,\n\t\t\t\t arg.type_id);\n\n\t\ttypetup = (Form_pg_type) GETSTRUCT(tup);\n\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid value for %s.%s '%s'\", def->defnamespace, def->defname, value),\n\t\t\t\t errhint(\"%s.%s must be a valid %s\",\n\t\t\t\t\t\t def->defnamespace,\n\t\t\t\t\t\t def->defname,\n\t\t\t\t\t\t NameStr(typetup->typname))));\n\t}\n\tPG_END_TRY();\n\treturn val;\n}\n"
  },
  {
    "path": "src/with_clause/with_clause_parser.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include <nodes/parsenodes.h>\n#include <utils.h>\n\n#include \"compat/compat.h\"\n\ntypedef struct WithClauseDefinition\n{\n\t/* Alternative names for the parameters. The first one is the \"main\" one\n\t * when it comes to printouts.*/\n\tconst char *arg_names[5];\n\tOid type_id;\n\tDatum default_val;\n} WithClauseDefinition;\n\ntypedef struct WithClauseResult\n{\n\tconst WithClauseDefinition *definition;\n\tbool is_default;\n\tDatum parsed;\n} WithClauseResult;\n\nextern TSDLLEXPORT void ts_with_clause_filter(const List *def_elems, List **within_namespace,\n\t\t\t\t\t\t\t\t\t\t\t  List **other_namespace, List **not_within_namespace);\n\nextern TSDLLEXPORT WithClauseResult *\nts_with_clauses_parse(const List *def_elems, const WithClauseDefinition *args, Size nargs);\n\nextern TSDLLEXPORT WithClauseResult *\nts_with_clauses_parse_reset(const List *def_elems, const WithClauseDefinition *args, Size nargs);\n\nextern TSDLLEXPORT char *ts_with_clause_result_deparse_value(const WithClauseResult *result);\n"
  },
  {
    "path": "test/.gitignore",
    "content": "results/\ndump/\nregression.diffs\nregression.out\nunit/testoutputs.tmp\n"
  },
  {
    "path": "test/CMakeLists.txt",
    "content": "set(PRIMARY_TEST_DIR ${CMAKE_CURRENT_LIST_DIR})\nset(PRIMARY_TEST_DIR\n    ${CMAKE_CURRENT_LIST_DIR}\n    PARENT_SCOPE)\n\nset(_local_install_checks)\nset(_install_checks)\n\n# Testing support\ninclude(test-defs.cmake)\n\n# No checks for REGRESS_CHECKS needed here since all the checks are done in the\n# parent CMakeLists.txt.\n\nif(PG_REGRESS)\n  message(STATUS \"Using pg_regress ${PG_REGRESS}\")\n\n  add_custom_target(\n    regresscheck\n    COMMAND\n      ${CMAKE_COMMAND} -E env ${PG_REGRESS_ENV}\n      TEST_PGPORT=${TEST_PGPORT_TEMP_INSTANCE} TEST_SCHEDULE=${TEST_SCHEDULE}\n      TEST_TIMEOUT=${TEST_TIMEOUT} ${CMAKE_CURRENT_SOURCE_DIR}/pg_regress.sh\n      ${PG_REGRESS_OPTS_BASE} ${PG_REGRESS_OPTS_EXTRA} ${PG_REGRESS_OPTS_INOUT}\n      ${PG_REGRESS_OPTS_TEMP_INSTANCE}\n      --temp-config=${TEST_OUTPUT_DIR}/postgresql.conf\n    USES_TERMINAL)\n\n  add_custom_target(\n    regresscheck-rerun\n    COMMAND ${PRIMARY_TEST_DIR}/ci_rerun.sh regresscheck\n    USES_TERMINAL)\n\n  add_custom_target(\n    regresschecklocal\n    COMMAND\n      ${CMAKE_COMMAND} -E env ${PG_REGRESS_ENV} TEST_PGPORT=${TEST_PGPORT_LOCAL}\n      TEST_TIMEOUT=${TEST_TIMEOUT} ${CMAKE_CURRENT_SOURCE_DIR}/pg_regress.sh\n      ${PG_REGRESS_OPTS_BASE} ${PG_REGRESS_OPTS_EXTRA} ${PG_REGRESS_OPTS_INOUT}\n      ${PG_REGRESS_OPTS_LOCAL_INSTANCE}\n    USES_TERMINAL)\n\n  list(APPEND _local_install_checks regresschecklocal)\n  list(APPEND _install_checks regresscheck)\nelseif(REQUIRE_ALL_TESTS)\n  message(\n    FATAL_ERROR \"All tests were required but 'pg_regress' could not be found\")\nendif()\n\nif(PG_ISOLATION_REGRESS)\n  message(STATUS \"Using pg_isolation_regress ${PG_ISOLATION_REGRESS}\")\n\n  add_custom_target(\n    isolationcheck\n    COMMAND\n      ${CMAKE_COMMAND} -E env ${PG_ISOLATION_REGRESS_ENV}\n      SPECS_DIR=${CMAKE_CURRENT_SOURCE_DIR}/isolation/specs\n      TEST_PGPORT=${TEST_PGPORT_TEMP_INSTANCE}\n      ${CMAKE_CURRENT_SOURCE_DIR}/pg_regress.sh ${PG_REGRESS_OPTS_BASE}\n      ${PG_ISOLATION_REGRESS_OPTS_EXTRA} ${PG_ISOLATION_REGRESS_OPTS_INOUT}\n      ${PG_REGRESS_OPTS_TEMP_INSTANCE}\n      --temp-config=${TEST_OUTPUT_DIR}/postgresql.conf\n    USES_TERMINAL)\n\n  add_custom_target(\n    isolationcheck-rerun\n    COMMAND ${PRIMARY_TEST_DIR}/ci_rerun.sh isolationcheck\n    USES_TERMINAL)\n\n  add_custom_target(\n    isolationchecklocal\n    COMMAND\n      ${CMAKE_COMMAND} -E env ${PG_ISOLATION_REGRESS_ENV}\n      SPECS_DIR=${CMAKE_CURRENT_SOURCE_DIR}/isolation/specs\n      TEST_PGPORT=${TEST_PGPORT_LOCAL} ${CMAKE_CURRENT_SOURCE_DIR}/pg_regress.sh\n      ${PG_REGRESS_OPTS_BASE} ${PG_ISOLATION_REGRESS_OPTS_EXTRA}\n      ${PG_ISOLATION_REGRESS_OPTS_INOUT} ${PG_REGRESS_OPTS_LOCAL_INSTANCE}\n    USES_TERMINAL)\n\n  list(APPEND _local_install_checks isolationchecklocal)\n  list(APPEND _install_checks isolationcheck)\nelseif(REQUIRE_ALL_TESTS)\n  message(\n    FATAL_ERROR\n      \"All tests were required but 'pg_isolation_regress' could not be found\")\nendif()\n\nif(TAP_CHECKS)\n  add_custom_target(\n    provecheck\n    COMMAND rm -rf ${CMAKE_CURRENT_BINARY_DIR}/tmp_check\n    COMMAND\n      CONFDIR=${CMAKE_BINARY_DIR}/test PATH=\"${PG_BINDIR}:$ENV{PATH}\"\n      PG_REGRESS=${PG_REGRESS} SRC_DIR=${PG_SOURCE_DIR}\n      CM_SRC_DIR=${CMAKE_SOURCE_DIR} PG_LIBDIR=${PG_LIBDIR}\n      PG_PKGLIBDIR=${PG_PKGLIBDIR} PG_VERSION_MAJOR=${PG_VERSION_MAJOR}\n      ${PRIMARY_TEST_DIR}/pg_prove.sh\n    USES_TERMINAL)\n  list(APPEND _install_checks provecheck)\nelseif(REQUIRE_ALL_TESTS)\n  message(\n    FATAL_ERROR\n      \"All tests were required but TAP_CHECKS was off (see previous messages why)\"\n  )\nendif()\n\n# We add the installcheck target even when _install_checks is empty as tsl code\n# might add dependencies to it even when regress checks are disabled.\nadd_custom_target(installcheck DEPENDS ${_install_checks})\n\n# Define a post test hook that is invoked after the installcheck target\n# finishes. One can use add_dependencies on post hook target to run other\n# targets after tests complete. This is used, e.g., by code coverage.\nadd_custom_target(installcheck-post-hook COMMENT \"Post test hook\")\nadd_custom_command(\n  TARGET installcheck\n  POST_BUILD\n  COMMAND cmake --build ${CMAKE_CURRENT_BINARY_DIR} --target\n          installcheck-post-hook)\n\nadd_custom_target(\n  installcheck-rerun\n  COMMAND ${PRIMARY_TEST_DIR}/ci_rerun.sh installcheck\n  USES_TERMINAL)\n\n# installchecklocal tests against an existing postgres instance\nadd_custom_target(installchecklocal DEPENDS ${_local_install_checks})\n\nadd_subdirectory(sql)\nadd_subdirectory(isolation)\nadd_subdirectory(t)\n\nif(PG_SOURCE_DIR)\n  add_subdirectory(pgtest)\nendif(PG_SOURCE_DIR)\n\nif(CMAKE_BUILD_TYPE MATCHES Debug)\n  add_subdirectory(src)\nendif(CMAKE_BUILD_TYPE MATCHES Debug)\n\nadd_subdirectory(perl)\n"
  },
  {
    "path": "test/README.md",
    "content": "# Testing TimescaleDB\n\n- [Regression tests](pgtest/README.md)\n- [Perl-based TAP tests](perl/README.md)\n- [Background Worker Test Infrastructure](src/bgw/README.md)\n"
  },
  {
    "path": "test/ci_rerun.sh",
    "content": "#!/bin/bash\n\nsubcommand=$1\nout_files=\"regression.out\"\n\ncase \"$subcommand\" in\n  installcheck)\n    out_files=$(find .. -name \"regression.out\")\n    ;;\n  regresscheck-shared)\n    out_files=\"shared/regression.out\"\n    ;;\n  isolationcheck|isolationcheck-t)\n    out_files=\"isolation/regression.out\"\n    ;;\n  *)\n    out_files=\"regression.out\"\n    ;;\nesac\n\nfailed=$(grep -h -P \"^not ok|FAILED\" $out_files | sed -r -e 's!^not ok [0-9]+ +[+-] ([a-z0-9_-]+) .*$!\\1!' -e 's!^(test|    ) ([a-z0-9_-]+) +... FAILED.*$!\\2!')\n\nfailed=\"${failed//$'\\n'/ }\"\n\nmake -k $subcommand TESTS=\"$failed\"\n\n"
  },
  {
    "path": "test/expected/agg_bookends-15.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\set TEST_BASE_NAME agg_bookends\nSELECT format('include/%s_load.sql', :'TEST_BASE_NAME') as \"TEST_LOAD_NAME\",\n       format('include/%s_query.sql', :'TEST_BASE_NAME') as \"TEST_QUERY_NAME\",\n       format('%s/results/%s_results_optimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_OPTIMIZED\",\n       format('%s/results/%s_results_unoptimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_UNOPTIMIZED\"\n\\gset\nSELECT format('\\! diff -u  --label \"Unoptimized result\" --label \"Optimized result\" %s %s', :'TEST_RESULTS_UNOPTIMIZED', :'TEST_RESULTS_OPTIMIZED') as \"DIFF_CMD\"\n\\gset\n\\set PREFIX 'EXPLAIN (analyze, buffers off, costs off, timing off, summary off)'\n\\ir :TEST_LOAD_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE btest(time timestamp NOT NULL, time_alt timestamp, gp INTEGER, temp float, strid TEXT DEFAULT 'testing');\nSELECT schema_name, table_name, created FROM create_hypertable('btest', 'time');\npsql:include/agg_bookends_load.sql:6: WARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\npsql:include/agg_bookends_load.sql:6: WARNING:  column type \"timestamp without time zone\" used for \"time_alt\" does not follow best practices\n schema_name | table_name | created \n-------------+------------+---------\n public      | btest      | t\n\nINSERT INTO btest VALUES('2017-01-20T09:00:01', '2017-01-20T10:00:00', 1, 22.5);\nINSERT INTO btest VALUES('2017-01-20T09:00:21', '2017-01-20T09:00:59', 1, 21.2);\nINSERT INTO btest VALUES('2017-01-20T09:00:47', '2017-01-20T09:00:58', 1, 25.1);\nINSERT INTO btest VALUES('2017-01-20T09:00:02', '2017-01-20T09:00:57', 2, 35.5);\nINSERT INTO btest VALUES('2017-01-20T09:00:21', '2017-01-20T09:00:56', 2, 30.2);\n--TOASTED;\nINSERT INTO btest VALUES('2017-01-20T09:00:43', '2017-01-20T09:01:55', 2, 20.1, repeat('xyz', 1000000) );\nCREATE TABLE btest_numeric (time timestamp NOT NULL, quantity numeric);\nSELECT schema_name, table_name, created FROM create_hypertable('btest_numeric', 'time');\npsql:include/agg_bookends_load.sql:16: WARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n schema_name |  table_name   | created \n-------------+---------------+---------\n public      | btest_numeric | t\n\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- canary for results diff\n-- this should be only output of results diff\nSELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations')) v(setting);\n             setting              | value \n----------------------------------+-------\n timescaledb.enable_optimizations | on\n\n:PREFIX SELECT time, gp, temp FROM btest ORDER BY time;\n--- QUERY PLAN ---\n Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n:PREFIX SELECT last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n                 Index Cond: (\"time\" IS NOT NULL)\n\n:PREFIX SELECT first(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n:PREFIX SELECT gp, last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: _hyper_1_1_chunk.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: _hyper_1_1_chunk.gp\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n:PREFIX SELECT gp, first(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: _hyper_1_1_chunk.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: _hyper_1_1_chunk.gp\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n--check whole row\n:PREFIX SELECT gp, first(btest, time) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: _hyper_1_1_chunk.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: _hyper_1_1_chunk.gp\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n--check toasted col\n:PREFIX SELECT gp, left(last(strid, time), 10) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: _hyper_1_1_chunk.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: _hyper_1_1_chunk.gp\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n:PREFIX SELECT gp, last(temp, strid) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: _hyper_1_1_chunk.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: _hyper_1_1_chunk.gp\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n:PREFIX SELECT gp, last(strid, temp) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: _hyper_1_1_chunk.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: _hyper_1_1_chunk.gp\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\nBEGIN;\n--check null value as last element\nINSERT INTO btest VALUES('2018-01-20T09:00:43', '2017-01-20T09:00:55', 2, NULL);\n:PREFIX SELECT last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest VALUES('2019-01-20T09:00:43', '2018-01-20T09:00:55', 2, 30.5);\n:PREFIX SELECT last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n\n--check null cmp element is skipped\nINSERT INTO btest VALUES('2018-01-20T09:00:43', NULL, 2, 32.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=9.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n\n-- fist returns NULL value\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=9.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n\n-- test first return non NULL value\nINSERT INTO btest VALUES('2016-01-20T09:00:00', '2016-01-20T09:00:00', 2, 36.5);\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=10.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n\n--check non null cmp element insert after null cmp\nINSERT INTO btest VALUES('2020-01-20T09:00:43', '2020-01-20T09:00:43', 2, 35.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=11.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=11.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n--cmp nulls should be ignored and not present in groups\n:PREFIX SELECT gp, last(temp, time_alt) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: btest.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: btest.gp\n         ->  Append (actual rows=11.00 loops=1)\n               ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n               ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n--Previously, some bugs were found with NULLS and numeric types, so test that\nINSERT INTO btest_numeric VALUES ('2019-01-20T09:00:43', NULL);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Index Scan using _hyper_2_6_chunk_btest_numeric_time_idx on _hyper_2_6_chunk (actual rows=1.00 loops=1)\n                 Index Cond: (\"time\" IS NOT NULL)\n\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest_numeric VALUES('2020-01-20T09:00:43', 30.5);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest_numeric (actual rows=1.00 loops=1)\n                 Order: btest_numeric.\"time\" DESC\n                 ->  Index Scan using _hyper_2_7_chunk_btest_numeric_time_idx on _hyper_2_7_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_2_6_chunk_btest_numeric_time_idx on _hyper_2_6_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n\n-- do index scan for last\n:PREFIX SELECT last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n\n-- do index scan for first\n:PREFIX SELECT first(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\"\n                 ->  Index Scan Backward using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                 ->  Index Scan Backward using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                 ->  Index Scan Backward using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                 ->  Index Scan Backward using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (never executed)\n\n-- can't do index scan when ordering on non-index column\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=11.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n-- do index scan for subquery\n:PREFIX SELECT * FROM (SELECT last(temp, time) FROM btest) last;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n\n-- can't do index scan when using group by\n:PREFIX SELECT last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: btest.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: btest.gp\n         ->  Append (actual rows=11.00 loops=1)\n               ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n               ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n-- do index scan when agg function is used in CTE subquery\n:PREFIX WITH last_temp AS (SELECT last(temp, time) FROM btest) SELECT * from last_temp;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n\n-- do index scan when using both FIRST and LAST aggregate functions\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $1)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n   InitPlan 2 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest btest_1 (actual rows=1.00 loops=1)\n                 Order: btest_1.\"time\"\n                 ->  Index Scan Backward using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk _hyper_1_4_chunk_1 (actual rows=1.00 loops=1)\n                 ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (never executed)\n                 ->  Index Scan Backward using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (never executed)\n                 ->  Index Scan Backward using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk _hyper_1_3_chunk_1 (never executed)\n                 ->  Index Scan Backward using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk _hyper_1_5_chunk_1 (never executed)\n\n-- verify results when using both FIRST and LAST\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $1)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n   InitPlan 2 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest btest_1 (actual rows=1.00 loops=1)\n                 Order: btest_1.\"time\"\n                 ->  Index Scan Backward using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk _hyper_1_4_chunk_1 (actual rows=1.00 loops=1)\n                 ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (never executed)\n                 ->  Index Scan Backward using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (never executed)\n                 ->  Index Scan Backward using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk _hyper_1_3_chunk_1 (never executed)\n                 ->  Index Scan Backward using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk _hyper_1_5_chunk_1 (never executed)\n\n-- do index scan when using WHERE\n:PREFIX SELECT last(temp, time) FROM btest WHERE time <= '2017-01-20T09:00:02';\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n                       Index Cond: ((\"time\" IS NOT NULL) AND (\"time\" <= 'Fri Jan 20 09:00:02 2017'::timestamp without time zone))\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n\n-- can't do index scan for MAX and LAST combined (MinMax optimization fails when having different aggregate functions)\n:PREFIX SELECT max(time), last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=11.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n-- can't do index scan when using FIRST/LAST in ORDER BY\n:PREFIX SELECT last(temp, time) FROM btest ORDER BY last(temp, time);\n--- QUERY PLAN ---\n Sort (actual rows=1.00 loops=1)\n   Sort Key: (last(btest.temp, btest.\"time\"))\n   Sort Method: quicksort \n   ->  Aggregate (actual rows=1.00 loops=1)\n         ->  Append (actual rows=11.00 loops=1)\n               ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n               ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n-- do index scan\n:PREFIX SELECT last(temp, time) FROM btest WHERE temp < 30;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=0.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                       Filter: (temp < '30'::double precision)\n                       Rows Removed by Filter: 1\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (actual rows=0.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                       Filter: (temp < '30'::double precision)\n                       Rows Removed by Filter: 1\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (actual rows=0.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                       Filter: (temp < '30'::double precision)\n                       Rows Removed by Filter: 2\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                       Filter: (temp < '30'::double precision)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                       Filter: (temp < '30'::double precision)\n\n-- SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n-- do index scan\n:PREFIX SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\"\n                 ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" >= 'Fri Jan 20 09:00:47 2017'::timestamp without time zone)\n                 ->  Index Scan Backward using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                 ->  Index Scan Backward using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                 ->  Index Scan Backward using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (never executed)\n\n-- can't do index scan when using WINDOW function\n:PREFIX SELECT gp, last(temp, time) OVER (PARTITION BY gp) AS last FROM btest;\n--- QUERY PLAN ---\n WindowAgg (actual rows=11.00 loops=1)\n   ->  Sort (actual rows=11.00 loops=1)\n         Sort Key: btest.gp\n         Sort Method: quicksort \n         ->  Append (actual rows=11.00 loops=1)\n               ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n               ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n-- test constants\n:PREFIX SELECT first(100, 100) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Result (actual rows=1.00 loops=1)\n                 ->  Append (actual rows=1.00 loops=1)\n                       ->  Seq Scan on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n                       ->  Seq Scan on _hyper_1_2_chunk (never executed)\n                       ->  Seq Scan on _hyper_1_3_chunk (never executed)\n                       ->  Seq Scan on _hyper_1_4_chunk (never executed)\n                       ->  Seq Scan on _hyper_1_5_chunk (never executed)\n\n-- create an index so we can test optimization\nCREATE INDEX btest_time_alt_idx ON btest(time_alt);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Merge Append (actual rows=1.00 loops=1)\n                 Sort Key: btest.time_alt DESC\n                 ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_alt_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (time_alt IS NOT NULL)\n                 ->  Index Scan Backward using _hyper_1_2_chunk_btest_time_alt_idx on _hyper_1_2_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (time_alt IS NOT NULL)\n                 ->  Index Scan Backward using _hyper_1_3_chunk_btest_time_alt_idx on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (time_alt IS NOT NULL)\n                 ->  Index Scan Backward using _hyper_1_4_chunk_btest_time_alt_idx on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (time_alt IS NOT NULL)\n                 ->  Index Scan Backward using _hyper_1_5_chunk_btest_time_alt_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (time_alt IS NOT NULL)\n\n--test nested FIRST/LAST - should optimize\n:PREFIX SELECT abs(last(temp, time)) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n\n-- test nested FIRST/LAST in ORDER BY - no optimization possible\n:PREFIX SELECT abs(last(temp, time)) FROM btest ORDER BY abs(last(temp,time));\n--- QUERY PLAN ---\n Sort (actual rows=1.00 loops=1)\n   Sort Key: (abs(last(btest.temp, btest.\"time\")))\n   Sort Method: quicksort \n   ->  Aggregate (actual rows=1.00 loops=1)\n         ->  Append (actual rows=11.00 loops=1)\n               ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n               ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\nROLLBACK;\n-- Test with NULL numeric values\nBEGIN;\nTRUNCATE btest_numeric;\n-- Empty table\n:PREFIX SELECT first(btest_numeric, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Result (actual rows=0.00 loops=1)\n         One-Time Filter: false\n\n:PREFIX SELECT last(btest_numeric, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Result (actual rows=0.00 loops=1)\n         One-Time Filter: false\n\n-- Only NULL values\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Index Scan Backward using _hyper_2_8_chunk_btest_numeric_time_idx on _hyper_2_8_chunk (actual rows=1.00 loops=1)\n\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Index Scan using _hyper_2_8_chunk_btest_numeric_time_idx on _hyper_2_8_chunk (actual rows=1.00 loops=1)\n                 Index Cond: (\"time\" IS NOT NULL)\n\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_2_8_chunk (actual rows=2.00 loops=1)\n\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_2_8_chunk (actual rows=2.00 loops=1)\n\n-- NULL values followed by non-NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest_numeric (actual rows=1.00 loops=1)\n                 Order: btest_numeric.\"time\"\n                 ->  Index Scan Backward using _hyper_2_8_chunk_btest_numeric_time_idx on _hyper_2_8_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan Backward using _hyper_2_9_chunk_btest_numeric_time_idx on _hyper_2_9_chunk (never executed)\n\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest_numeric (actual rows=1.00 loops=1)\n                 Order: btest_numeric.\"time\" DESC\n                 ->  Index Scan using _hyper_2_9_chunk_btest_numeric_time_idx on _hyper_2_9_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_2_8_chunk_btest_numeric_time_idx on _hyper_2_8_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=4.00 loops=1)\n         ->  Seq Scan on _hyper_2_8_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_2_9_chunk (actual rows=2.00 loops=1)\n\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=4.00 loops=1)\n         ->  Seq Scan on _hyper_2_8_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_2_9_chunk (actual rows=2.00 loops=1)\n\nTRUNCATE btest_numeric;\n-- non-NULL values followed by NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest_numeric (actual rows=1.00 loops=1)\n                 Order: btest_numeric.\"time\"\n                 ->  Index Scan Backward using _hyper_2_11_chunk_btest_numeric_time_idx on _hyper_2_11_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan Backward using _hyper_2_10_chunk_btest_numeric_time_idx on _hyper_2_10_chunk (never executed)\n\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest_numeric (actual rows=1.00 loops=1)\n                 Order: btest_numeric.\"time\" DESC\n                 ->  Index Scan using _hyper_2_10_chunk_btest_numeric_time_idx on _hyper_2_10_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_2_11_chunk_btest_numeric_time_idx on _hyper_2_11_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=4.00 loops=1)\n         ->  Seq Scan on _hyper_2_10_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_2_11_chunk (actual rows=2.00 loops=1)\n\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=4.00 loops=1)\n         ->  Seq Scan on _hyper_2_10_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_2_11_chunk (actual rows=2.00 loops=1)\n\nROLLBACK;\n-- we want test results as part of the output too to make sure we produce correct output\n\\set PREFIX ''\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- canary for results diff\n-- this should be only output of results diff\nSELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations')) v(setting);\n             setting              | value \n----------------------------------+-------\n timescaledb.enable_optimizations | on\n\n:PREFIX SELECT time, gp, temp FROM btest ORDER BY time;\n           time           | gp | temp \n--------------------------+----+------\n Fri Jan 20 09:00:01 2017 |  1 | 22.5\n Fri Jan 20 09:00:02 2017 |  2 | 35.5\n Fri Jan 20 09:00:21 2017 |  1 | 21.2\n Fri Jan 20 09:00:21 2017 |  2 | 30.2\n Fri Jan 20 09:00:43 2017 |  2 | 20.1\n Fri Jan 20 09:00:47 2017 |  1 | 25.1\n\n:PREFIX SELECT last(temp, time) FROM btest;\n last \n------\n 25.1\n\n:PREFIX SELECT first(temp, time) FROM btest;\n first \n-------\n  22.5\n\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n last \n------\n 22.5\n\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n first \n-------\n  30.2\n\n:PREFIX SELECT gp, last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n gp | last \n----+------\n  1 | 25.1\n  2 | 20.1\n\n:PREFIX SELECT gp, first(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n gp | first \n----+-------\n  1 |  22.5\n  2 |  35.5\n\n--check whole row\n:PREFIX SELECT gp, first(btest, time) FROM btest GROUP BY gp ORDER BY gp;\n gp |                                 first                                  \n----+------------------------------------------------------------------------\n  1 | (\"Fri Jan 20 09:00:01 2017\",\"Fri Jan 20 10:00:00 2017\",1,22.5,testing)\n  2 | (\"Fri Jan 20 09:00:02 2017\",\"Fri Jan 20 09:00:57 2017\",2,35.5,testing)\n\n--check toasted col\n:PREFIX SELECT gp, left(last(strid, time), 10) FROM btest GROUP BY gp ORDER BY gp;\n gp |    left    \n----+------------\n  1 | testing\n  2 | xyzxyzxyzx\n\n:PREFIX SELECT gp, last(temp, strid) FROM btest GROUP BY gp ORDER BY gp;\n gp | last \n----+------\n  1 | 22.5\n  2 | 20.1\n\n:PREFIX SELECT gp, last(strid, temp) FROM btest GROUP BY gp ORDER BY gp;\n gp |  last   \n----+---------\n  1 | testing\n  2 | testing\n\nBEGIN;\n--check null value as last element\nINSERT INTO btest VALUES('2018-01-20T09:00:43', '2017-01-20T09:00:55', 2, NULL);\n:PREFIX SELECT last(temp, time) FROM btest;\n last \n------\n     \n\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest VALUES('2019-01-20T09:00:43', '2018-01-20T09:00:55', 2, 30.5);\n:PREFIX SELECT last(temp, time) FROM btest;\n last \n------\n 30.5\n\n--check null cmp element is skipped\nINSERT INTO btest VALUES('2018-01-20T09:00:43', NULL, 2, 32.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n last \n------\n 30.5\n\n-- fist returns NULL value\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n first \n-------\n      \n\n-- test first return non NULL value\nINSERT INTO btest VALUES('2016-01-20T09:00:00', '2016-01-20T09:00:00', 2, 36.5);\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n first \n-------\n  36.5\n\n--check non null cmp element insert after null cmp\nINSERT INTO btest VALUES('2020-01-20T09:00:43', '2020-01-20T09:00:43', 2, 35.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n last \n------\n 35.3\n\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n first \n-------\n  36.5\n\n--cmp nulls should be ignored and not present in groups\n:PREFIX SELECT gp, last(temp, time_alt) FROM btest GROUP BY gp ORDER BY gp;\n gp | last \n----+------\n  1 | 22.5\n  2 | 35.3\n\n--Previously, some bugs were found with NULLS and numeric types, so test that\nINSERT INTO btest_numeric VALUES ('2019-01-20T09:00:43', NULL);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n last \n------\n     \n\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest_numeric VALUES('2020-01-20T09:00:43', 30.5);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n last \n------\n 30.5\n\n-- do index scan for last\n:PREFIX SELECT last(temp, time) FROM btest;\n last \n------\n 35.3\n\n-- do index scan for first\n:PREFIX SELECT first(temp, time) FROM btest;\n first \n-------\n  36.5\n\n-- can't do index scan when ordering on non-index column\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n first \n-------\n  36.5\n\n-- do index scan for subquery\n:PREFIX SELECT * FROM (SELECT last(temp, time) FROM btest) last;\n last \n------\n 35.3\n\n-- can't do index scan when using group by\n:PREFIX SELECT last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n last \n------\n 25.1\n 35.3\n\n-- do index scan when agg function is used in CTE subquery\n:PREFIX WITH last_temp AS (SELECT last(temp, time) FROM btest) SELECT * from last_temp;\n last \n------\n 35.3\n\n-- do index scan when using both FIRST and LAST aggregate functions\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n first | last \n-------+------\n  36.5 | 35.3\n\n-- verify results when using both FIRST and LAST\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n first | last \n-------+------\n  36.5 | 35.3\n\n-- do index scan when using WHERE\n:PREFIX SELECT last(temp, time) FROM btest WHERE time <= '2017-01-20T09:00:02';\n last \n------\n 35.5\n\n-- can't do index scan for MAX and LAST combined (MinMax optimization fails when having different aggregate functions)\n:PREFIX SELECT max(time), last(temp, time) FROM btest;\n           max            | last \n--------------------------+------\n Mon Jan 20 09:00:43 2020 | 35.3\n\n-- can't do index scan when using FIRST/LAST in ORDER BY\n:PREFIX SELECT last(temp, time) FROM btest ORDER BY last(temp, time);\n last \n------\n 35.3\n\n-- do index scan\n:PREFIX SELECT last(temp, time) FROM btest WHERE temp < 30;\n last \n------\n 25.1\n\n-- SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n-- do index scan\n:PREFIX SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n first \n-------\n  25.1\n\n-- can't do index scan when using WINDOW function\n:PREFIX SELECT gp, last(temp, time) OVER (PARTITION BY gp) AS last FROM btest;\n gp | last \n----+------\n  1 | 25.1\n  1 | 25.1\n  1 | 25.1\n  2 | 35.3\n  2 | 35.3\n  2 | 35.3\n  2 | 35.3\n  2 | 35.3\n  2 | 35.3\n  2 | 35.3\n  2 | 35.3\n\n-- test constants\n:PREFIX SELECT first(100, 100) FROM btest;\n first \n-------\n   100\n\n-- create an index so we can test optimization\nCREATE INDEX btest_time_alt_idx ON btest(time_alt);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n last \n------\n 35.3\n\n--test nested FIRST/LAST - should optimize\n:PREFIX SELECT abs(last(temp, time)) FROM btest;\n abs  \n------\n 35.3\n\n-- test nested FIRST/LAST in ORDER BY - no optimization possible\n:PREFIX SELECT abs(last(temp, time)) FROM btest ORDER BY abs(last(temp,time));\n abs  \n------\n 35.3\n\nROLLBACK;\n-- Test with NULL numeric values\nBEGIN;\nTRUNCATE btest_numeric;\n-- Empty table\n:PREFIX SELECT first(btest_numeric, time) FROM btest_numeric;\n first \n-------\n \n\n:PREFIX SELECT last(btest_numeric, time) FROM btest_numeric;\n last \n------\n \n\n-- Only NULL values\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n first \n-------\n      \n\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n last \n------\n     \n\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n first \n-------\n \n\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n last \n------\n \n\n-- NULL values followed by non-NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n first \n-------\n      \n\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n last \n------\n    1\n\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n          first           \n--------------------------\n Sun Jan 20 09:00:43 2019\n\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n           last           \n--------------------------\n Sun Jan 20 09:00:43 2019\n\nTRUNCATE btest_numeric;\n-- non-NULL values followed by NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n first \n-------\n      \n\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n last \n------\n    1\n\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n          first           \n--------------------------\n Sun Jan 20 09:00:43 2019\n\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n           last           \n--------------------------\n Sun Jan 20 09:00:43 2019\n\nROLLBACK;\n-- diff results with optimizations disabled and enabled\n\\o :TEST_RESULTS_UNOPTIMIZED\nSET timescaledb.enable_optimizations TO false;\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- canary for results diff\n-- this should be only output of results diff\nSELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations')) v(setting);\n:PREFIX SELECT time, gp, temp FROM btest ORDER BY time;\n:PREFIX SELECT last(temp, time) FROM btest;\n:PREFIX SELECT first(temp, time) FROM btest;\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n:PREFIX SELECT gp, last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, first(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n--check whole row\n:PREFIX SELECT gp, first(btest, time) FROM btest GROUP BY gp ORDER BY gp;\n--check toasted col\n:PREFIX SELECT gp, left(last(strid, time), 10) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, last(temp, strid) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, last(strid, temp) FROM btest GROUP BY gp ORDER BY gp;\nBEGIN;\n--check null value as last element\nINSERT INTO btest VALUES('2018-01-20T09:00:43', '2017-01-20T09:00:55', 2, NULL);\n:PREFIX SELECT last(temp, time) FROM btest;\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest VALUES('2019-01-20T09:00:43', '2018-01-20T09:00:55', 2, 30.5);\n:PREFIX SELECT last(temp, time) FROM btest;\n--check null cmp element is skipped\nINSERT INTO btest VALUES('2018-01-20T09:00:43', NULL, 2, 32.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n-- fist returns NULL value\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n-- test first return non NULL value\nINSERT INTO btest VALUES('2016-01-20T09:00:00', '2016-01-20T09:00:00', 2, 36.5);\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--check non null cmp element insert after null cmp\nINSERT INTO btest VALUES('2020-01-20T09:00:43', '2020-01-20T09:00:43', 2, 35.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--cmp nulls should be ignored and not present in groups\n:PREFIX SELECT gp, last(temp, time_alt) FROM btest GROUP BY gp ORDER BY gp;\n--Previously, some bugs were found with NULLS and numeric types, so test that\nINSERT INTO btest_numeric VALUES ('2019-01-20T09:00:43', NULL);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest_numeric VALUES('2020-01-20T09:00:43', 30.5);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n-- do index scan for last\n:PREFIX SELECT last(temp, time) FROM btest;\n-- do index scan for first\n:PREFIX SELECT first(temp, time) FROM btest;\n-- can't do index scan when ordering on non-index column\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n-- do index scan for subquery\n:PREFIX SELECT * FROM (SELECT last(temp, time) FROM btest) last;\n-- can't do index scan when using group by\n:PREFIX SELECT last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n-- do index scan when agg function is used in CTE subquery\n:PREFIX WITH last_temp AS (SELECT last(temp, time) FROM btest) SELECT * from last_temp;\n-- do index scan when using both FIRST and LAST aggregate functions\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n-- verify results when using both FIRST and LAST\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n-- do index scan when using WHERE\n:PREFIX SELECT last(temp, time) FROM btest WHERE time <= '2017-01-20T09:00:02';\n-- can't do index scan for MAX and LAST combined (MinMax optimization fails when having different aggregate functions)\n:PREFIX SELECT max(time), last(temp, time) FROM btest;\n-- can't do index scan when using FIRST/LAST in ORDER BY\n:PREFIX SELECT last(temp, time) FROM btest ORDER BY last(temp, time);\n-- do index scan\n:PREFIX SELECT last(temp, time) FROM btest WHERE temp < 30;\n-- SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n-- do index scan\n:PREFIX SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n-- can't do index scan when using WINDOW function\n:PREFIX SELECT gp, last(temp, time) OVER (PARTITION BY gp) AS last FROM btest;\n-- test constants\n:PREFIX SELECT first(100, 100) FROM btest;\n-- create an index so we can test optimization\nCREATE INDEX btest_time_alt_idx ON btest(time_alt);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n--test nested FIRST/LAST - should optimize\n:PREFIX SELECT abs(last(temp, time)) FROM btest;\n-- test nested FIRST/LAST in ORDER BY - no optimization possible\n:PREFIX SELECT abs(last(temp, time)) FROM btest ORDER BY abs(last(temp,time));\nROLLBACK;\n-- Test with NULL numeric values\nBEGIN;\nTRUNCATE btest_numeric;\n-- Empty table\n:PREFIX SELECT first(btest_numeric, time) FROM btest_numeric;\n:PREFIX SELECT last(btest_numeric, time) FROM btest_numeric;\n-- Only NULL values\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n-- NULL values followed by non-NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\nTRUNCATE btest_numeric;\n-- non-NULL values followed by NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\nROLLBACK;\n\\o\n\\o :TEST_RESULTS_OPTIMIZED\nSET timescaledb.enable_optimizations TO true;\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- canary for results diff\n-- this should be only output of results diff\nSELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations')) v(setting);\n:PREFIX SELECT time, gp, temp FROM btest ORDER BY time;\n:PREFIX SELECT last(temp, time) FROM btest;\n:PREFIX SELECT first(temp, time) FROM btest;\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n:PREFIX SELECT gp, last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, first(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n--check whole row\n:PREFIX SELECT gp, first(btest, time) FROM btest GROUP BY gp ORDER BY gp;\n--check toasted col\n:PREFIX SELECT gp, left(last(strid, time), 10) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, last(temp, strid) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, last(strid, temp) FROM btest GROUP BY gp ORDER BY gp;\nBEGIN;\n--check null value as last element\nINSERT INTO btest VALUES('2018-01-20T09:00:43', '2017-01-20T09:00:55', 2, NULL);\n:PREFIX SELECT last(temp, time) FROM btest;\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest VALUES('2019-01-20T09:00:43', '2018-01-20T09:00:55', 2, 30.5);\n:PREFIX SELECT last(temp, time) FROM btest;\n--check null cmp element is skipped\nINSERT INTO btest VALUES('2018-01-20T09:00:43', NULL, 2, 32.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n-- fist returns NULL value\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n-- test first return non NULL value\nINSERT INTO btest VALUES('2016-01-20T09:00:00', '2016-01-20T09:00:00', 2, 36.5);\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--check non null cmp element insert after null cmp\nINSERT INTO btest VALUES('2020-01-20T09:00:43', '2020-01-20T09:00:43', 2, 35.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--cmp nulls should be ignored and not present in groups\n:PREFIX SELECT gp, last(temp, time_alt) FROM btest GROUP BY gp ORDER BY gp;\n--Previously, some bugs were found with NULLS and numeric types, so test that\nINSERT INTO btest_numeric VALUES ('2019-01-20T09:00:43', NULL);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest_numeric VALUES('2020-01-20T09:00:43', 30.5);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n-- do index scan for last\n:PREFIX SELECT last(temp, time) FROM btest;\n-- do index scan for first\n:PREFIX SELECT first(temp, time) FROM btest;\n-- can't do index scan when ordering on non-index column\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n-- do index scan for subquery\n:PREFIX SELECT * FROM (SELECT last(temp, time) FROM btest) last;\n-- can't do index scan when using group by\n:PREFIX SELECT last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n-- do index scan when agg function is used in CTE subquery\n:PREFIX WITH last_temp AS (SELECT last(temp, time) FROM btest) SELECT * from last_temp;\n-- do index scan when using both FIRST and LAST aggregate functions\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n-- verify results when using both FIRST and LAST\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n-- do index scan when using WHERE\n:PREFIX SELECT last(temp, time) FROM btest WHERE time <= '2017-01-20T09:00:02';\n-- can't do index scan for MAX and LAST combined (MinMax optimization fails when having different aggregate functions)\n:PREFIX SELECT max(time), last(temp, time) FROM btest;\n-- can't do index scan when using FIRST/LAST in ORDER BY\n:PREFIX SELECT last(temp, time) FROM btest ORDER BY last(temp, time);\n-- do index scan\n:PREFIX SELECT last(temp, time) FROM btest WHERE temp < 30;\n-- SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n-- do index scan\n:PREFIX SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n-- can't do index scan when using WINDOW function\n:PREFIX SELECT gp, last(temp, time) OVER (PARTITION BY gp) AS last FROM btest;\n-- test constants\n:PREFIX SELECT first(100, 100) FROM btest;\n-- create an index so we can test optimization\nCREATE INDEX btest_time_alt_idx ON btest(time_alt);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n--test nested FIRST/LAST - should optimize\n:PREFIX SELECT abs(last(temp, time)) FROM btest;\n-- test nested FIRST/LAST in ORDER BY - no optimization possible\n:PREFIX SELECT abs(last(temp, time)) FROM btest ORDER BY abs(last(temp,time));\nROLLBACK;\n-- Test with NULL numeric values\nBEGIN;\nTRUNCATE btest_numeric;\n-- Empty table\n:PREFIX SELECT first(btest_numeric, time) FROM btest_numeric;\n:PREFIX SELECT last(btest_numeric, time) FROM btest_numeric;\n-- Only NULL values\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n-- NULL values followed by non-NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\nTRUNCATE btest_numeric;\n-- non-NULL values followed by NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\nROLLBACK;\n\\o\n:DIFF_CMD\n--- Unoptimized result\n+++ Optimized result\n@@ -1,6 +1,6 @@\n              setting              | value \n ----------------------------------+-------\n- timescaledb.enable_optimizations | off\n+ timescaledb.enable_optimizations | on\n \n            time           | gp | temp \n-- Test partial aggregation\nCREATE TABLE partial_aggregation (time timestamptz NOT NULL, quantity numeric, longvalue text);\nSELECT schema_name, table_name, created FROM create_hypertable('partial_aggregation', 'time');\n schema_name |     table_name      | created \n-------------+---------------------+---------\n public      | partial_aggregation | t\n\nINSERT INTO partial_aggregation VALUES('2018-01-20T09:00:43', NULL, NULL);\nINSERT INTO partial_aggregation VALUES('2018-01-20T09:00:44', NULL, NULL);\nINSERT INTO partial_aggregation VALUES('2019-01-20T09:00:43', 1, 'hello');\nINSERT INTO partial_aggregation VALUES('2019-01-20T09:00:44', 2, 'world');\nINSERT INTO partial_aggregation VALUES('2020-01-20T09:00:43', 3.1, 'some1');\nINSERT INTO partial_aggregation VALUES('2020-01-20T09:00:44', 3.2, 'more1');\nINSERT INTO partial_aggregation VALUES('2021-01-20T09:00:43', 3.3, 'some2');\nINSERT INTO partial_aggregation VALUES('2021-01-20T09:00:44', 3.4, 'more2');\nINSERT INTO partial_aggregation VALUES('2022-01-20T09:00:43', 4, 'word1');\nINSERT INTO partial_aggregation VALUES('2022-01-20T09:00:44', 5, 'word2');\nINSERT INTO partial_aggregation VALUES('2023-01-20T09:00:43', 6, 'word3');\nINSERT INTO partial_aggregation VALUES('2023-01-20T09:00:44', 7, 'word4');\n-- Use enable_partitionwise_aggregate to create partial aggregates per chunk\nSET enable_partitionwise_aggregate = ON;\nSELECT\n    format('SELECT %3$s, %1$s FROM partial_aggregation WHERE %2$s GROUP BY %3$s ORDER BY 1, 2;',\n            function, condition, grouping)\nFROM\n    unnest(array[\n            'first(time, quantity), last(time, quantity)',\n            'last(longvalue, quantity)',\n            'last(quantity, longvalue)',\n            'last(quantity, time)',\n            'last(time, longvalue)']) AS function,\n    unnest(array[\n            'true',\n            $$time < '2021-01-01'$$,\n            'quantity is null',\n            'quantity is not null',\n            'quantity >= 4']) AS condition,\n    unnest(array[\n            '777::text' /* dummy grouping column */,\n            'longvalue',\n            'quantity',\n            $$time_bucket('1 year', time)$$,\n            $$time_bucket('3 year', time)$$]) AS grouping\n\\gexec\nSELECT 777::text, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE true GROUP BY 777::text ORDER BY 1, 2;\n text |            first             |             last             \n------+------------------------------+------------------------------\n 777  | Sun Jan 20 09:00:43 2019 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT 777::text, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY 777::text ORDER BY 1, 2;\n text |            first             |             last             \n------+------------------------------+------------------------------\n 777  | Sun Jan 20 09:00:43 2019 PST | Mon Jan 20 09:00:44 2020 PST\n\nSELECT 777::text, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY 777::text ORDER BY 1, 2;\n text | first | last \n------+-------+------\n 777  |       | \n\nSELECT 777::text, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY 777::text ORDER BY 1, 2;\n text |            first             |             last             \n------+------------------------------+------------------------------\n 777  | Sun Jan 20 09:00:43 2019 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT 777::text, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY 777::text ORDER BY 1, 2;\n text |            first             |             last             \n------+------------------------------+------------------------------\n 777  | Thu Jan 20 09:00:43 2022 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT 777::text, last(longvalue, quantity) FROM partial_aggregation WHERE true GROUP BY 777::text ORDER BY 1, 2;\n text | last  \n------+-------\n 777  | word4\n\nSELECT 777::text, last(longvalue, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY 777::text ORDER BY 1, 2;\n text | last  \n------+-------\n 777  | more1\n\nSELECT 777::text, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  | \n\nSELECT 777::text, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY 777::text ORDER BY 1, 2;\n text | last  \n------+-------\n 777  | word4\n\nSELECT 777::text, last(longvalue, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY 777::text ORDER BY 1, 2;\n text | last  \n------+-------\n 777  | word4\n\nSELECT 777::text, last(quantity, longvalue) FROM partial_aggregation WHERE true GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    2\n\nSELECT 777::text, last(quantity, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    2\n\nSELECT 777::text, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |     \n\nSELECT 777::text, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    2\n\nSELECT 777::text, last(quantity, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    7\n\nSELECT 777::text, last(quantity, time) FROM partial_aggregation WHERE true GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    7\n\nSELECT 777::text, last(quantity, time) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |  3.2\n\nSELECT 777::text, last(quantity, time) FROM partial_aggregation WHERE quantity is null GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |     \n\nSELECT 777::text, last(quantity, time) FROM partial_aggregation WHERE quantity is not null GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    7\n\nSELECT 777::text, last(quantity, time) FROM partial_aggregation WHERE quantity >= 4 GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    7\n\nSELECT 777::text, last(time, longvalue) FROM partial_aggregation WHERE true GROUP BY 777::text ORDER BY 1, 2;\n text |             last             \n------+------------------------------\n 777  | Sun Jan 20 09:00:44 2019 PST\n\nSELECT 777::text, last(time, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY 777::text ORDER BY 1, 2;\n text |             last             \n------+------------------------------\n 777  | Sun Jan 20 09:00:44 2019 PST\n\nSELECT 777::text, last(time, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  | \n\nSELECT 777::text, last(time, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY 777::text ORDER BY 1, 2;\n text |             last             \n------+------------------------------\n 777  | Sun Jan 20 09:00:44 2019 PST\n\nSELECT 777::text, last(time, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY 777::text ORDER BY 1, 2;\n text |             last             \n------+------------------------------\n 777  | Fri Jan 20 09:00:44 2023 PST\n\nSELECT longvalue, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE true GROUP BY longvalue ORDER BY 1, 2;\n longvalue |            first             |             last             \n-----------+------------------------------+------------------------------\n hello     | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST\n more1     | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST\n more2     | Wed Jan 20 09:00:44 2021 PST | Wed Jan 20 09:00:44 2021 PST\n some1     | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST\n some2     | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:43 2021 PST\n word1     | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST\n word2     | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST\n word3     | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST\n word4     | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST\n world     | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST\n           |                              | \n\nSELECT longvalue, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY longvalue ORDER BY 1, 2;\n longvalue |            first             |             last             \n-----------+------------------------------+------------------------------\n hello     | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST\n more1     | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST\n some1     | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST\n world     | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST\n           |                              | \n\nSELECT longvalue, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | first | last \n-----------+-------+------\n           |       | \n\nSELECT longvalue, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY longvalue ORDER BY 1, 2;\n longvalue |            first             |             last             \n-----------+------------------------------+------------------------------\n hello     | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST\n more1     | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST\n more2     | Wed Jan 20 09:00:44 2021 PST | Wed Jan 20 09:00:44 2021 PST\n some1     | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST\n some2     | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:43 2021 PST\n word1     | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST\n word2     | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST\n word3     | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST\n word4     | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST\n world     | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST\n\nSELECT longvalue, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY longvalue ORDER BY 1, 2;\n longvalue |            first             |             last             \n-----------+------------------------------+------------------------------\n word1     | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST\n word2     | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST\n word3     | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST\n word4     | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT longvalue, last(longvalue, quantity) FROM partial_aggregation WHERE true GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last  \n-----------+-------\n hello     | hello\n more1     | more1\n more2     | more2\n some1     | some1\n some2     | some2\n word1     | word1\n word2     | word2\n word3     | word3\n word4     | word4\n world     | world\n           | \n\nSELECT longvalue, last(longvalue, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last  \n-----------+-------\n hello     | hello\n more1     | more1\n some1     | some1\n world     | world\n           | \n\nSELECT longvalue, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n           | \n\nSELECT longvalue, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last  \n-----------+-------\n hello     | hello\n more1     | more1\n more2     | more2\n some1     | some1\n some2     | some2\n word1     | word1\n word2     | word2\n word3     | word3\n word4     | word4\n world     | world\n\nSELECT longvalue, last(longvalue, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last  \n-----------+-------\n word1     | word1\n word2     | word2\n word3     | word3\n word4     | word4\n\nSELECT longvalue, last(quantity, longvalue) FROM partial_aggregation WHERE true GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n hello     |    1\n more1     |  3.2\n more2     |  3.4\n some1     |  3.1\n some2     |  3.3\n word1     |    4\n word2     |    5\n word3     |    6\n word4     |    7\n world     |    2\n           |     \n\nSELECT longvalue, last(quantity, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n hello     |    1\n more1     |  3.2\n some1     |  3.1\n world     |    2\n           |     \n\nSELECT longvalue, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n           |     \n\nSELECT longvalue, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n hello     |    1\n more1     |  3.2\n more2     |  3.4\n some1     |  3.1\n some2     |  3.3\n word1     |    4\n word2     |    5\n word3     |    6\n word4     |    7\n world     |    2\n\nSELECT longvalue, last(quantity, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n word1     |    4\n word2     |    5\n word3     |    6\n word4     |    7\n\nSELECT longvalue, last(quantity, time) FROM partial_aggregation WHERE true GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n hello     |    1\n more1     |  3.2\n more2     |  3.4\n some1     |  3.1\n some2     |  3.3\n word1     |    4\n word2     |    5\n word3     |    6\n word4     |    7\n world     |    2\n           |     \n\nSELECT longvalue, last(quantity, time) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n hello     |    1\n more1     |  3.2\n some1     |  3.1\n world     |    2\n           |     \n\nSELECT longvalue, last(quantity, time) FROM partial_aggregation WHERE quantity is null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n           |     \n\nSELECT longvalue, last(quantity, time) FROM partial_aggregation WHERE quantity is not null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n hello     |    1\n more1     |  3.2\n more2     |  3.4\n some1     |  3.1\n some2     |  3.3\n word1     |    4\n word2     |    5\n word3     |    6\n word4     |    7\n world     |    2\n\nSELECT longvalue, last(quantity, time) FROM partial_aggregation WHERE quantity >= 4 GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n word1     |    4\n word2     |    5\n word3     |    6\n word4     |    7\n\nSELECT longvalue, last(time, longvalue) FROM partial_aggregation WHERE true GROUP BY longvalue ORDER BY 1, 2;\n longvalue |             last             \n-----------+------------------------------\n hello     | Sun Jan 20 09:00:43 2019 PST\n more1     | Mon Jan 20 09:00:44 2020 PST\n more2     | Wed Jan 20 09:00:44 2021 PST\n some1     | Mon Jan 20 09:00:43 2020 PST\n some2     | Wed Jan 20 09:00:43 2021 PST\n word1     | Thu Jan 20 09:00:43 2022 PST\n word2     | Thu Jan 20 09:00:44 2022 PST\n word3     | Fri Jan 20 09:00:43 2023 PST\n word4     | Fri Jan 20 09:00:44 2023 PST\n world     | Sun Jan 20 09:00:44 2019 PST\n           | \n\nSELECT longvalue, last(time, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY longvalue ORDER BY 1, 2;\n longvalue |             last             \n-----------+------------------------------\n hello     | Sun Jan 20 09:00:43 2019 PST\n more1     | Mon Jan 20 09:00:44 2020 PST\n some1     | Mon Jan 20 09:00:43 2020 PST\n world     | Sun Jan 20 09:00:44 2019 PST\n           | \n\nSELECT longvalue, last(time, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n           | \n\nSELECT longvalue, last(time, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY longvalue ORDER BY 1, 2;\n longvalue |             last             \n-----------+------------------------------\n hello     | Sun Jan 20 09:00:43 2019 PST\n more1     | Mon Jan 20 09:00:44 2020 PST\n more2     | Wed Jan 20 09:00:44 2021 PST\n some1     | Mon Jan 20 09:00:43 2020 PST\n some2     | Wed Jan 20 09:00:43 2021 PST\n word1     | Thu Jan 20 09:00:43 2022 PST\n word2     | Thu Jan 20 09:00:44 2022 PST\n word3     | Fri Jan 20 09:00:43 2023 PST\n word4     | Fri Jan 20 09:00:44 2023 PST\n world     | Sun Jan 20 09:00:44 2019 PST\n\nSELECT longvalue, last(time, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY longvalue ORDER BY 1, 2;\n longvalue |             last             \n-----------+------------------------------\n word1     | Thu Jan 20 09:00:43 2022 PST\n word2     | Thu Jan 20 09:00:44 2022 PST\n word3     | Fri Jan 20 09:00:43 2023 PST\n word4     | Fri Jan 20 09:00:44 2023 PST\n\nSELECT quantity, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE true GROUP BY quantity ORDER BY 1, 2;\n quantity |            first             |             last             \n----------+------------------------------+------------------------------\n        1 | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST\n        2 | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST\n      3.1 | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST\n      3.2 | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST\n      3.3 | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:43 2021 PST\n      3.4 | Wed Jan 20 09:00:44 2021 PST | Wed Jan 20 09:00:44 2021 PST\n        4 | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST\n        5 | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST\n        6 | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST\n        7 | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST\n          |                              | \n\nSELECT quantity, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY quantity ORDER BY 1, 2;\n quantity |            first             |             last             \n----------+------------------------------+------------------------------\n        1 | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST\n        2 | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST\n      3.1 | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST\n      3.2 | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST\n          |                              | \n\nSELECT quantity, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY quantity ORDER BY 1, 2;\n quantity | first | last \n----------+-------+------\n          |       | \n\nSELECT quantity, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY quantity ORDER BY 1, 2;\n quantity |            first             |             last             \n----------+------------------------------+------------------------------\n        1 | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST\n        2 | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST\n      3.1 | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST\n      3.2 | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST\n      3.3 | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:43 2021 PST\n      3.4 | Wed Jan 20 09:00:44 2021 PST | Wed Jan 20 09:00:44 2021 PST\n        4 | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST\n        5 | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST\n        6 | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST\n        7 | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT quantity, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY quantity ORDER BY 1, 2;\n quantity |            first             |             last             \n----------+------------------------------+------------------------------\n        4 | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST\n        5 | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST\n        6 | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST\n        7 | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT quantity, last(longvalue, quantity) FROM partial_aggregation WHERE true GROUP BY quantity ORDER BY 1, 2;\n quantity | last  \n----------+-------\n        1 | hello\n        2 | world\n      3.1 | some1\n      3.2 | more1\n      3.3 | some2\n      3.4 | more2\n        4 | word1\n        5 | word2\n        6 | word3\n        7 | word4\n          | \n\nSELECT quantity, last(longvalue, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY quantity ORDER BY 1, 2;\n quantity | last  \n----------+-------\n        1 | hello\n        2 | world\n      3.1 | some1\n      3.2 | more1\n          | \n\nSELECT quantity, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n          | \n\nSELECT quantity, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY quantity ORDER BY 1, 2;\n quantity | last  \n----------+-------\n        1 | hello\n        2 | world\n      3.1 | some1\n      3.2 | more1\n      3.3 | some2\n      3.4 | more2\n        4 | word1\n        5 | word2\n        6 | word3\n        7 | word4\n\nSELECT quantity, last(longvalue, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY quantity ORDER BY 1, 2;\n quantity | last  \n----------+-------\n        4 | word1\n        5 | word2\n        6 | word3\n        7 | word4\n\nSELECT quantity, last(quantity, longvalue) FROM partial_aggregation WHERE true GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        1 |    1\n        2 |    2\n      3.1 |  3.1\n      3.2 |  3.2\n      3.3 |  3.3\n      3.4 |  3.4\n        4 |    4\n        5 |    5\n        6 |    6\n        7 |    7\n          |     \n\nSELECT quantity, last(quantity, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        1 |    1\n        2 |    2\n      3.1 |  3.1\n      3.2 |  3.2\n          |     \n\nSELECT quantity, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n          |     \n\nSELECT quantity, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        1 |    1\n        2 |    2\n      3.1 |  3.1\n      3.2 |  3.2\n      3.3 |  3.3\n      3.4 |  3.4\n        4 |    4\n        5 |    5\n        6 |    6\n        7 |    7\n\nSELECT quantity, last(quantity, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        4 |    4\n        5 |    5\n        6 |    6\n        7 |    7\n\nSELECT quantity, last(quantity, time) FROM partial_aggregation WHERE true GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        1 |    1\n        2 |    2\n      3.1 |  3.1\n      3.2 |  3.2\n      3.3 |  3.3\n      3.4 |  3.4\n        4 |    4\n        5 |    5\n        6 |    6\n        7 |    7\n          |     \n\nSELECT quantity, last(quantity, time) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        1 |    1\n        2 |    2\n      3.1 |  3.1\n      3.2 |  3.2\n          |     \n\nSELECT quantity, last(quantity, time) FROM partial_aggregation WHERE quantity is null GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n          |     \n\nSELECT quantity, last(quantity, time) FROM partial_aggregation WHERE quantity is not null GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        1 |    1\n        2 |    2\n      3.1 |  3.1\n      3.2 |  3.2\n      3.3 |  3.3\n      3.4 |  3.4\n        4 |    4\n        5 |    5\n        6 |    6\n        7 |    7\n\nSELECT quantity, last(quantity, time) FROM partial_aggregation WHERE quantity >= 4 GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        4 |    4\n        5 |    5\n        6 |    6\n        7 |    7\n\nSELECT quantity, last(time, longvalue) FROM partial_aggregation WHERE true GROUP BY quantity ORDER BY 1, 2;\n quantity |             last             \n----------+------------------------------\n        1 | Sun Jan 20 09:00:43 2019 PST\n        2 | Sun Jan 20 09:00:44 2019 PST\n      3.1 | Mon Jan 20 09:00:43 2020 PST\n      3.2 | Mon Jan 20 09:00:44 2020 PST\n      3.3 | Wed Jan 20 09:00:43 2021 PST\n      3.4 | Wed Jan 20 09:00:44 2021 PST\n        4 | Thu Jan 20 09:00:43 2022 PST\n        5 | Thu Jan 20 09:00:44 2022 PST\n        6 | Fri Jan 20 09:00:43 2023 PST\n        7 | Fri Jan 20 09:00:44 2023 PST\n          | \n\nSELECT quantity, last(time, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY quantity ORDER BY 1, 2;\n quantity |             last             \n----------+------------------------------\n        1 | Sun Jan 20 09:00:43 2019 PST\n        2 | Sun Jan 20 09:00:44 2019 PST\n      3.1 | Mon Jan 20 09:00:43 2020 PST\n      3.2 | Mon Jan 20 09:00:44 2020 PST\n          | \n\nSELECT quantity, last(time, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n          | \n\nSELECT quantity, last(time, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY quantity ORDER BY 1, 2;\n quantity |             last             \n----------+------------------------------\n        1 | Sun Jan 20 09:00:43 2019 PST\n        2 | Sun Jan 20 09:00:44 2019 PST\n      3.1 | Mon Jan 20 09:00:43 2020 PST\n      3.2 | Mon Jan 20 09:00:44 2020 PST\n      3.3 | Wed Jan 20 09:00:43 2021 PST\n      3.4 | Wed Jan 20 09:00:44 2021 PST\n        4 | Thu Jan 20 09:00:43 2022 PST\n        5 | Thu Jan 20 09:00:44 2022 PST\n        6 | Fri Jan 20 09:00:43 2023 PST\n        7 | Fri Jan 20 09:00:44 2023 PST\n\nSELECT quantity, last(time, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY quantity ORDER BY 1, 2;\n quantity |             last             \n----------+------------------------------\n        4 | Thu Jan 20 09:00:43 2022 PST\n        5 | Thu Jan 20 09:00:44 2022 PST\n        6 | Fri Jan 20 09:00:43 2023 PST\n        7 | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('1 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE true GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST |                              | \n Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:44 2019 PST\n Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:44 2020 PST\n Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:44 2021 PST\n Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:44 2022 PST\n Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('1 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST |                              | \n Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:44 2019 PST\n Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:44 2020 PST\n\nSELECT time_bucket('1 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | first | last \n------------------------------+-------+------\n Sun Dec 31 16:00:00 2017 PST |       | \n\nSELECT time_bucket('1 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:44 2019 PST\n Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:44 2020 PST\n Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:44 2021 PST\n Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:44 2022 PST\n Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('1 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:44 2022 PST\n Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('1 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE true GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Sun Dec 31 16:00:00 2017 PST | \n Mon Dec 31 16:00:00 2018 PST | world\n Tue Dec 31 16:00:00 2019 PST | more1\n Thu Dec 31 16:00:00 2020 PST | more2\n Fri Dec 31 16:00:00 2021 PST | word2\n Sat Dec 31 16:00:00 2022 PST | word4\n\nSELECT time_bucket('1 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Sun Dec 31 16:00:00 2017 PST | \n Mon Dec 31 16:00:00 2018 PST | world\n Tue Dec 31 16:00:00 2019 PST | more1\n\nSELECT time_bucket('1 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST | \n\nSELECT time_bucket('1 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Mon Dec 31 16:00:00 2018 PST | world\n Tue Dec 31 16:00:00 2019 PST | more1\n Thu Dec 31 16:00:00 2020 PST | more2\n Fri Dec 31 16:00:00 2021 PST | word2\n Sat Dec 31 16:00:00 2022 PST | word4\n\nSELECT time_bucket('1 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Fri Dec 31 16:00:00 2021 PST | word2\n Sat Dec 31 16:00:00 2022 PST | word4\n\nSELECT time_bucket('1 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE true GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n Mon Dec 31 16:00:00 2018 PST |    2\n Tue Dec 31 16:00:00 2019 PST |  3.1\n Thu Dec 31 16:00:00 2020 PST |  3.3\n Fri Dec 31 16:00:00 2021 PST |    5\n Sat Dec 31 16:00:00 2022 PST |    7\n\nSELECT time_bucket('1 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n Mon Dec 31 16:00:00 2018 PST |    2\n Tue Dec 31 16:00:00 2019 PST |  3.1\n\nSELECT time_bucket('1 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n\nSELECT time_bucket('1 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Mon Dec 31 16:00:00 2018 PST |    2\n Tue Dec 31 16:00:00 2019 PST |  3.1\n Thu Dec 31 16:00:00 2020 PST |  3.3\n Fri Dec 31 16:00:00 2021 PST |    5\n Sat Dec 31 16:00:00 2022 PST |    7\n\nSELECT time_bucket('1 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Fri Dec 31 16:00:00 2021 PST |    5\n Sat Dec 31 16:00:00 2022 PST |    7\n\nSELECT time_bucket('1 year', time), last(quantity, time) FROM partial_aggregation WHERE true GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n Mon Dec 31 16:00:00 2018 PST |    2\n Tue Dec 31 16:00:00 2019 PST |  3.2\n Thu Dec 31 16:00:00 2020 PST |  3.4\n Fri Dec 31 16:00:00 2021 PST |    5\n Sat Dec 31 16:00:00 2022 PST |    7\n\nSELECT time_bucket('1 year', time), last(quantity, time) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n Mon Dec 31 16:00:00 2018 PST |    2\n Tue Dec 31 16:00:00 2019 PST |  3.2\n\nSELECT time_bucket('1 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n\nSELECT time_bucket('1 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Mon Dec 31 16:00:00 2018 PST |    2\n Tue Dec 31 16:00:00 2019 PST |  3.2\n Thu Dec 31 16:00:00 2020 PST |  3.4\n Fri Dec 31 16:00:00 2021 PST |    5\n Sat Dec 31 16:00:00 2022 PST |    7\n\nSELECT time_bucket('1 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Fri Dec 31 16:00:00 2021 PST |    5\n Sat Dec 31 16:00:00 2022 PST |    7\n\nSELECT time_bucket('1 year', time), last(time, longvalue) FROM partial_aggregation WHERE true GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | \n Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:44 2019 PST\n Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST\n Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST\n Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:44 2022 PST\n Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('1 year', time), last(time, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | \n Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:44 2019 PST\n Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST\n\nSELECT time_bucket('1 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST | \n\nSELECT time_bucket('1 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:44 2019 PST\n Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST\n Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST\n Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:44 2022 PST\n Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('1 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:44 2022 PST\n Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('3 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE true GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:43 2019 PST | Mon Jan 20 09:00:44 2020 PST\n Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('3 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:43 2019 PST | Mon Jan 20 09:00:44 2020 PST\n\nSELECT time_bucket('3 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | first | last \n------------------------------+-------+------\n Sun Dec 31 16:00:00 2017 PST |       | \n\nSELECT time_bucket('3 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:43 2019 PST | Mon Jan 20 09:00:44 2020 PST\n Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('3 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Thu Dec 31 16:00:00 2020 PST | Thu Jan 20 09:00:43 2022 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('3 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE true GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Sun Dec 31 16:00:00 2017 PST | more1\n Thu Dec 31 16:00:00 2020 PST | word4\n\nSELECT time_bucket('3 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Sun Dec 31 16:00:00 2017 PST | more1\n\nSELECT time_bucket('3 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST | \n\nSELECT time_bucket('3 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Sun Dec 31 16:00:00 2017 PST | more1\n Thu Dec 31 16:00:00 2020 PST | word4\n\nSELECT time_bucket('3 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Thu Dec 31 16:00:00 2020 PST | word4\n\nSELECT time_bucket('3 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE true GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |    2\n Thu Dec 31 16:00:00 2020 PST |    7\n\nSELECT time_bucket('3 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |    2\n\nSELECT time_bucket('3 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n\nSELECT time_bucket('3 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |    2\n Thu Dec 31 16:00:00 2020 PST |    7\n\nSELECT time_bucket('3 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Thu Dec 31 16:00:00 2020 PST |    7\n\nSELECT time_bucket('3 year', time), last(quantity, time) FROM partial_aggregation WHERE true GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |  3.2\n Thu Dec 31 16:00:00 2020 PST |    7\n\nSELECT time_bucket('3 year', time), last(quantity, time) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |  3.2\n\nSELECT time_bucket('3 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n\nSELECT time_bucket('3 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |  3.2\n Thu Dec 31 16:00:00 2020 PST |    7\n\nSELECT time_bucket('3 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Thu Dec 31 16:00:00 2020 PST |    7\n\nSELECT time_bucket('3 year', time), last(time, longvalue) FROM partial_aggregation WHERE true GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:44 2019 PST\n Thu Dec 31 16:00:00 2020 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('3 year', time), last(time, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:44 2019 PST\n\nSELECT time_bucket('3 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST | \n\nSELECT time_bucket('3 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:44 2019 PST\n Thu Dec 31 16:00:00 2020 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('3 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Thu Dec 31 16:00:00 2020 PST | Fri Jan 20 09:00:44 2023 PST\n\nSET enable_partitionwise_aggregate = OFF;\n"
  },
  {
    "path": "test/expected/agg_bookends-16.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\set TEST_BASE_NAME agg_bookends\nSELECT format('include/%s_load.sql', :'TEST_BASE_NAME') as \"TEST_LOAD_NAME\",\n       format('include/%s_query.sql', :'TEST_BASE_NAME') as \"TEST_QUERY_NAME\",\n       format('%s/results/%s_results_optimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_OPTIMIZED\",\n       format('%s/results/%s_results_unoptimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_UNOPTIMIZED\"\n\\gset\nSELECT format('\\! diff -u  --label \"Unoptimized result\" --label \"Optimized result\" %s %s', :'TEST_RESULTS_UNOPTIMIZED', :'TEST_RESULTS_OPTIMIZED') as \"DIFF_CMD\"\n\\gset\n\\set PREFIX 'EXPLAIN (analyze, buffers off, costs off, timing off, summary off)'\n\\ir :TEST_LOAD_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE btest(time timestamp NOT NULL, time_alt timestamp, gp INTEGER, temp float, strid TEXT DEFAULT 'testing');\nSELECT schema_name, table_name, created FROM create_hypertable('btest', 'time');\npsql:include/agg_bookends_load.sql:6: WARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\npsql:include/agg_bookends_load.sql:6: WARNING:  column type \"timestamp without time zone\" used for \"time_alt\" does not follow best practices\n schema_name | table_name | created \n-------------+------------+---------\n public      | btest      | t\n\nINSERT INTO btest VALUES('2017-01-20T09:00:01', '2017-01-20T10:00:00', 1, 22.5);\nINSERT INTO btest VALUES('2017-01-20T09:00:21', '2017-01-20T09:00:59', 1, 21.2);\nINSERT INTO btest VALUES('2017-01-20T09:00:47', '2017-01-20T09:00:58', 1, 25.1);\nINSERT INTO btest VALUES('2017-01-20T09:00:02', '2017-01-20T09:00:57', 2, 35.5);\nINSERT INTO btest VALUES('2017-01-20T09:00:21', '2017-01-20T09:00:56', 2, 30.2);\n--TOASTED;\nINSERT INTO btest VALUES('2017-01-20T09:00:43', '2017-01-20T09:01:55', 2, 20.1, repeat('xyz', 1000000) );\nCREATE TABLE btest_numeric (time timestamp NOT NULL, quantity numeric);\nSELECT schema_name, table_name, created FROM create_hypertable('btest_numeric', 'time');\npsql:include/agg_bookends_load.sql:16: WARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n schema_name |  table_name   | created \n-------------+---------------+---------\n public      | btest_numeric | t\n\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- canary for results diff\n-- this should be only output of results diff\nSELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations')) v(setting);\n             setting              | value \n----------------------------------+-------\n timescaledb.enable_optimizations | on\n\n:PREFIX SELECT time, gp, temp FROM btest ORDER BY time;\n--- QUERY PLAN ---\n Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n:PREFIX SELECT last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n                 Index Cond: (\"time\" IS NOT NULL)\n\n:PREFIX SELECT first(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n:PREFIX SELECT gp, last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: _hyper_1_1_chunk.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: _hyper_1_1_chunk.gp\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n:PREFIX SELECT gp, first(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: _hyper_1_1_chunk.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: _hyper_1_1_chunk.gp\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n--check whole row\n:PREFIX SELECT gp, first(btest, time) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: _hyper_1_1_chunk.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: _hyper_1_1_chunk.gp\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n--check toasted col\n:PREFIX SELECT gp, left(last(strid, time), 10) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: _hyper_1_1_chunk.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: _hyper_1_1_chunk.gp\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n:PREFIX SELECT gp, last(temp, strid) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: _hyper_1_1_chunk.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: _hyper_1_1_chunk.gp\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n:PREFIX SELECT gp, last(strid, temp) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: _hyper_1_1_chunk.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: _hyper_1_1_chunk.gp\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\nBEGIN;\n--check null value as last element\nINSERT INTO btest VALUES('2018-01-20T09:00:43', '2017-01-20T09:00:55', 2, NULL);\n:PREFIX SELECT last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest VALUES('2019-01-20T09:00:43', '2018-01-20T09:00:55', 2, 30.5);\n:PREFIX SELECT last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n\n--check null cmp element is skipped\nINSERT INTO btest VALUES('2018-01-20T09:00:43', NULL, 2, 32.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=9.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n\n-- fist returns NULL value\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=9.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n\n-- test first return non NULL value\nINSERT INTO btest VALUES('2016-01-20T09:00:00', '2016-01-20T09:00:00', 2, 36.5);\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=10.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n\n--check non null cmp element insert after null cmp\nINSERT INTO btest VALUES('2020-01-20T09:00:43', '2020-01-20T09:00:43', 2, 35.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=11.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=11.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n--cmp nulls should be ignored and not present in groups\n:PREFIX SELECT gp, last(temp, time_alt) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: btest.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: btest.gp\n         ->  Append (actual rows=11.00 loops=1)\n               ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n               ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n--Previously, some bugs were found with NULLS and numeric types, so test that\nINSERT INTO btest_numeric VALUES ('2019-01-20T09:00:43', NULL);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Index Scan using _hyper_2_6_chunk_btest_numeric_time_idx on _hyper_2_6_chunk (actual rows=1.00 loops=1)\n                 Index Cond: (\"time\" IS NOT NULL)\n\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest_numeric VALUES('2020-01-20T09:00:43', 30.5);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest_numeric (actual rows=1.00 loops=1)\n                 Order: btest_numeric.\"time\" DESC\n                 ->  Index Scan using _hyper_2_7_chunk_btest_numeric_time_idx on _hyper_2_7_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_2_6_chunk_btest_numeric_time_idx on _hyper_2_6_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n\n-- do index scan for last\n:PREFIX SELECT last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n\n-- do index scan for first\n:PREFIX SELECT first(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\"\n                 ->  Index Scan Backward using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                 ->  Index Scan Backward using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                 ->  Index Scan Backward using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                 ->  Index Scan Backward using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (never executed)\n\n-- can't do index scan when ordering on non-index column\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=11.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n-- do index scan for subquery\n:PREFIX SELECT * FROM (SELECT last(temp, time) FROM btest) last;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n\n-- can't do index scan when using group by\n:PREFIX SELECT last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: btest.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: btest.gp\n         ->  Append (actual rows=11.00 loops=1)\n               ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n               ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n-- do index scan when agg function is used in CTE subquery\n:PREFIX WITH last_temp AS (SELECT last(temp, time) FROM btest) SELECT * from last_temp;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n\n-- do index scan when using both FIRST and LAST aggregate functions\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $1)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n   InitPlan 2 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest btest_1 (actual rows=1.00 loops=1)\n                 Order: btest_1.\"time\"\n                 ->  Index Scan Backward using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk _hyper_1_4_chunk_1 (actual rows=1.00 loops=1)\n                 ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (never executed)\n                 ->  Index Scan Backward using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (never executed)\n                 ->  Index Scan Backward using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk _hyper_1_3_chunk_1 (never executed)\n                 ->  Index Scan Backward using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk _hyper_1_5_chunk_1 (never executed)\n\n-- verify results when using both FIRST and LAST\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $1)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n   InitPlan 2 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest btest_1 (actual rows=1.00 loops=1)\n                 Order: btest_1.\"time\"\n                 ->  Index Scan Backward using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk _hyper_1_4_chunk_1 (actual rows=1.00 loops=1)\n                 ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (never executed)\n                 ->  Index Scan Backward using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (never executed)\n                 ->  Index Scan Backward using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk _hyper_1_3_chunk_1 (never executed)\n                 ->  Index Scan Backward using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk _hyper_1_5_chunk_1 (never executed)\n\n-- do index scan when using WHERE\n:PREFIX SELECT last(temp, time) FROM btest WHERE time <= '2017-01-20T09:00:02';\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n                       Index Cond: ((\"time\" IS NOT NULL) AND (\"time\" <= 'Fri Jan 20 09:00:02 2017'::timestamp without time zone))\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n\n-- can't do index scan for MAX and LAST combined (MinMax optimization fails when having different aggregate functions)\n:PREFIX SELECT max(time), last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=11.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n-- can't do index scan when using FIRST/LAST in ORDER BY\n:PREFIX SELECT last(temp, time) FROM btest ORDER BY last(temp, time);\n--- QUERY PLAN ---\n Sort (actual rows=1.00 loops=1)\n   Sort Key: (last(btest.temp, btest.\"time\"))\n   Sort Method: quicksort \n   ->  Aggregate (actual rows=1.00 loops=1)\n         ->  Append (actual rows=11.00 loops=1)\n               ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n               ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n-- do index scan\n:PREFIX SELECT last(temp, time) FROM btest WHERE temp < 30;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=0.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                       Filter: (temp < '30'::double precision)\n                       Rows Removed by Filter: 1\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (actual rows=0.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                       Filter: (temp < '30'::double precision)\n                       Rows Removed by Filter: 1\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (actual rows=0.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                       Filter: (temp < '30'::double precision)\n                       Rows Removed by Filter: 2\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                       Filter: (temp < '30'::double precision)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                       Filter: (temp < '30'::double precision)\n\n-- SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n-- do index scan\n:PREFIX SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\"\n                 ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" >= 'Fri Jan 20 09:00:47 2017'::timestamp without time zone)\n                 ->  Index Scan Backward using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                 ->  Index Scan Backward using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                 ->  Index Scan Backward using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (never executed)\n\n-- can't do index scan when using WINDOW function\n:PREFIX SELECT gp, last(temp, time) OVER (PARTITION BY gp) AS last FROM btest;\n--- QUERY PLAN ---\n WindowAgg (actual rows=11.00 loops=1)\n   ->  Sort (actual rows=11.00 loops=1)\n         Sort Key: btest.gp\n         Sort Method: quicksort \n         ->  Append (actual rows=11.00 loops=1)\n               ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n               ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n-- test constants\n:PREFIX SELECT first(100, 100) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Result (actual rows=1.00 loops=1)\n                 ->  Append (actual rows=1.00 loops=1)\n                       ->  Seq Scan on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n                       ->  Seq Scan on _hyper_1_2_chunk (never executed)\n                       ->  Seq Scan on _hyper_1_3_chunk (never executed)\n                       ->  Seq Scan on _hyper_1_4_chunk (never executed)\n                       ->  Seq Scan on _hyper_1_5_chunk (never executed)\n\n-- create an index so we can test optimization\nCREATE INDEX btest_time_alt_idx ON btest(time_alt);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Merge Append (actual rows=1.00 loops=1)\n                 Sort Key: btest.time_alt DESC\n                 ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_alt_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (time_alt IS NOT NULL)\n                 ->  Index Scan Backward using _hyper_1_2_chunk_btest_time_alt_idx on _hyper_1_2_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (time_alt IS NOT NULL)\n                 ->  Index Scan Backward using _hyper_1_3_chunk_btest_time_alt_idx on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (time_alt IS NOT NULL)\n                 ->  Index Scan Backward using _hyper_1_4_chunk_btest_time_alt_idx on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (time_alt IS NOT NULL)\n                 ->  Index Scan Backward using _hyper_1_5_chunk_btest_time_alt_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (time_alt IS NOT NULL)\n\n--test nested FIRST/LAST - should optimize\n:PREFIX SELECT abs(last(temp, time)) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n\n-- test nested FIRST/LAST in ORDER BY - no optimization possible\n:PREFIX SELECT abs(last(temp, time)) FROM btest ORDER BY abs(last(temp,time));\n--- QUERY PLAN ---\n Sort (actual rows=1.00 loops=1)\n   Sort Key: (abs(last(btest.temp, btest.\"time\")))\n   Sort Method: quicksort \n   ->  Aggregate (actual rows=1.00 loops=1)\n         ->  Append (actual rows=11.00 loops=1)\n               ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n               ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\nROLLBACK;\n-- Test with NULL numeric values\nBEGIN;\nTRUNCATE btest_numeric;\n-- Empty table\n:PREFIX SELECT first(btest_numeric, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Result (actual rows=0.00 loops=1)\n         One-Time Filter: false\n\n:PREFIX SELECT last(btest_numeric, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Result (actual rows=0.00 loops=1)\n         One-Time Filter: false\n\n-- Only NULL values\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Index Scan Backward using _hyper_2_8_chunk_btest_numeric_time_idx on _hyper_2_8_chunk (actual rows=1.00 loops=1)\n\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Index Scan using _hyper_2_8_chunk_btest_numeric_time_idx on _hyper_2_8_chunk (actual rows=1.00 loops=1)\n                 Index Cond: (\"time\" IS NOT NULL)\n\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_2_8_chunk (actual rows=2.00 loops=1)\n\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_2_8_chunk (actual rows=2.00 loops=1)\n\n-- NULL values followed by non-NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest_numeric (actual rows=1.00 loops=1)\n                 Order: btest_numeric.\"time\"\n                 ->  Index Scan Backward using _hyper_2_8_chunk_btest_numeric_time_idx on _hyper_2_8_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan Backward using _hyper_2_9_chunk_btest_numeric_time_idx on _hyper_2_9_chunk (never executed)\n\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest_numeric (actual rows=1.00 loops=1)\n                 Order: btest_numeric.\"time\" DESC\n                 ->  Index Scan using _hyper_2_9_chunk_btest_numeric_time_idx on _hyper_2_9_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_2_8_chunk_btest_numeric_time_idx on _hyper_2_8_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=4.00 loops=1)\n         ->  Seq Scan on _hyper_2_8_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_2_9_chunk (actual rows=2.00 loops=1)\n\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=4.00 loops=1)\n         ->  Seq Scan on _hyper_2_8_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_2_9_chunk (actual rows=2.00 loops=1)\n\nTRUNCATE btest_numeric;\n-- non-NULL values followed by NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest_numeric (actual rows=1.00 loops=1)\n                 Order: btest_numeric.\"time\"\n                 ->  Index Scan Backward using _hyper_2_11_chunk_btest_numeric_time_idx on _hyper_2_11_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan Backward using _hyper_2_10_chunk_btest_numeric_time_idx on _hyper_2_10_chunk (never executed)\n\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest_numeric (actual rows=1.00 loops=1)\n                 Order: btest_numeric.\"time\" DESC\n                 ->  Index Scan using _hyper_2_10_chunk_btest_numeric_time_idx on _hyper_2_10_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" IS NOT NULL)\n                 ->  Index Scan using _hyper_2_11_chunk_btest_numeric_time_idx on _hyper_2_11_chunk (never executed)\n                       Index Cond: (\"time\" IS NOT NULL)\n\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=4.00 loops=1)\n         ->  Seq Scan on _hyper_2_10_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_2_11_chunk (actual rows=2.00 loops=1)\n\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=4.00 loops=1)\n         ->  Seq Scan on _hyper_2_10_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_2_11_chunk (actual rows=2.00 loops=1)\n\nROLLBACK;\n-- we want test results as part of the output too to make sure we produce correct output\n\\set PREFIX ''\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- canary for results diff\n-- this should be only output of results diff\nSELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations')) v(setting);\n             setting              | value \n----------------------------------+-------\n timescaledb.enable_optimizations | on\n\n:PREFIX SELECT time, gp, temp FROM btest ORDER BY time;\n           time           | gp | temp \n--------------------------+----+------\n Fri Jan 20 09:00:01 2017 |  1 | 22.5\n Fri Jan 20 09:00:02 2017 |  2 | 35.5\n Fri Jan 20 09:00:21 2017 |  1 | 21.2\n Fri Jan 20 09:00:21 2017 |  2 | 30.2\n Fri Jan 20 09:00:43 2017 |  2 | 20.1\n Fri Jan 20 09:00:47 2017 |  1 | 25.1\n\n:PREFIX SELECT last(temp, time) FROM btest;\n last \n------\n 25.1\n\n:PREFIX SELECT first(temp, time) FROM btest;\n first \n-------\n  22.5\n\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n last \n------\n 22.5\n\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n first \n-------\n  30.2\n\n:PREFIX SELECT gp, last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n gp | last \n----+------\n  1 | 25.1\n  2 | 20.1\n\n:PREFIX SELECT gp, first(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n gp | first \n----+-------\n  1 |  22.5\n  2 |  35.5\n\n--check whole row\n:PREFIX SELECT gp, first(btest, time) FROM btest GROUP BY gp ORDER BY gp;\n gp |                                 first                                  \n----+------------------------------------------------------------------------\n  1 | (\"Fri Jan 20 09:00:01 2017\",\"Fri Jan 20 10:00:00 2017\",1,22.5,testing)\n  2 | (\"Fri Jan 20 09:00:02 2017\",\"Fri Jan 20 09:00:57 2017\",2,35.5,testing)\n\n--check toasted col\n:PREFIX SELECT gp, left(last(strid, time), 10) FROM btest GROUP BY gp ORDER BY gp;\n gp |    left    \n----+------------\n  1 | testing\n  2 | xyzxyzxyzx\n\n:PREFIX SELECT gp, last(temp, strid) FROM btest GROUP BY gp ORDER BY gp;\n gp | last \n----+------\n  1 | 22.5\n  2 | 20.1\n\n:PREFIX SELECT gp, last(strid, temp) FROM btest GROUP BY gp ORDER BY gp;\n gp |  last   \n----+---------\n  1 | testing\n  2 | testing\n\nBEGIN;\n--check null value as last element\nINSERT INTO btest VALUES('2018-01-20T09:00:43', '2017-01-20T09:00:55', 2, NULL);\n:PREFIX SELECT last(temp, time) FROM btest;\n last \n------\n     \n\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest VALUES('2019-01-20T09:00:43', '2018-01-20T09:00:55', 2, 30.5);\n:PREFIX SELECT last(temp, time) FROM btest;\n last \n------\n 30.5\n\n--check null cmp element is skipped\nINSERT INTO btest VALUES('2018-01-20T09:00:43', NULL, 2, 32.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n last \n------\n 30.5\n\n-- fist returns NULL value\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n first \n-------\n      \n\n-- test first return non NULL value\nINSERT INTO btest VALUES('2016-01-20T09:00:00', '2016-01-20T09:00:00', 2, 36.5);\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n first \n-------\n  36.5\n\n--check non null cmp element insert after null cmp\nINSERT INTO btest VALUES('2020-01-20T09:00:43', '2020-01-20T09:00:43', 2, 35.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n last \n------\n 35.3\n\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n first \n-------\n  36.5\n\n--cmp nulls should be ignored and not present in groups\n:PREFIX SELECT gp, last(temp, time_alt) FROM btest GROUP BY gp ORDER BY gp;\n gp | last \n----+------\n  1 | 22.5\n  2 | 35.3\n\n--Previously, some bugs were found with NULLS and numeric types, so test that\nINSERT INTO btest_numeric VALUES ('2019-01-20T09:00:43', NULL);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n last \n------\n     \n\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest_numeric VALUES('2020-01-20T09:00:43', 30.5);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n last \n------\n 30.5\n\n-- do index scan for last\n:PREFIX SELECT last(temp, time) FROM btest;\n last \n------\n 35.3\n\n-- do index scan for first\n:PREFIX SELECT first(temp, time) FROM btest;\n first \n-------\n  36.5\n\n-- can't do index scan when ordering on non-index column\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n first \n-------\n  36.5\n\n-- do index scan for subquery\n:PREFIX SELECT * FROM (SELECT last(temp, time) FROM btest) last;\n last \n------\n 35.3\n\n-- can't do index scan when using group by\n:PREFIX SELECT last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n last \n------\n 25.1\n 35.3\n\n-- do index scan when agg function is used in CTE subquery\n:PREFIX WITH last_temp AS (SELECT last(temp, time) FROM btest) SELECT * from last_temp;\n last \n------\n 35.3\n\n-- do index scan when using both FIRST and LAST aggregate functions\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n first | last \n-------+------\n  36.5 | 35.3\n\n-- verify results when using both FIRST and LAST\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n first | last \n-------+------\n  36.5 | 35.3\n\n-- do index scan when using WHERE\n:PREFIX SELECT last(temp, time) FROM btest WHERE time <= '2017-01-20T09:00:02';\n last \n------\n 35.5\n\n-- can't do index scan for MAX and LAST combined (MinMax optimization fails when having different aggregate functions)\n:PREFIX SELECT max(time), last(temp, time) FROM btest;\n           max            | last \n--------------------------+------\n Mon Jan 20 09:00:43 2020 | 35.3\n\n-- can't do index scan when using FIRST/LAST in ORDER BY\n:PREFIX SELECT last(temp, time) FROM btest ORDER BY last(temp, time);\n last \n------\n 35.3\n\n-- do index scan\n:PREFIX SELECT last(temp, time) FROM btest WHERE temp < 30;\n last \n------\n 25.1\n\n-- SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n-- do index scan\n:PREFIX SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n first \n-------\n  25.1\n\n-- can't do index scan when using WINDOW function\n:PREFIX SELECT gp, last(temp, time) OVER (PARTITION BY gp) AS last FROM btest;\n gp | last \n----+------\n  1 | 25.1\n  1 | 25.1\n  1 | 25.1\n  2 | 35.3\n  2 | 35.3\n  2 | 35.3\n  2 | 35.3\n  2 | 35.3\n  2 | 35.3\n  2 | 35.3\n  2 | 35.3\n\n-- test constants\n:PREFIX SELECT first(100, 100) FROM btest;\n first \n-------\n   100\n\n-- create an index so we can test optimization\nCREATE INDEX btest_time_alt_idx ON btest(time_alt);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n last \n------\n 35.3\n\n--test nested FIRST/LAST - should optimize\n:PREFIX SELECT abs(last(temp, time)) FROM btest;\n abs  \n------\n 35.3\n\n-- test nested FIRST/LAST in ORDER BY - no optimization possible\n:PREFIX SELECT abs(last(temp, time)) FROM btest ORDER BY abs(last(temp,time));\n abs  \n------\n 35.3\n\nROLLBACK;\n-- Test with NULL numeric values\nBEGIN;\nTRUNCATE btest_numeric;\n-- Empty table\n:PREFIX SELECT first(btest_numeric, time) FROM btest_numeric;\n first \n-------\n \n\n:PREFIX SELECT last(btest_numeric, time) FROM btest_numeric;\n last \n------\n \n\n-- Only NULL values\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n first \n-------\n      \n\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n last \n------\n     \n\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n first \n-------\n \n\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n last \n------\n \n\n-- NULL values followed by non-NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n first \n-------\n      \n\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n last \n------\n    1\n\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n          first           \n--------------------------\n Sun Jan 20 09:00:43 2019\n\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n           last           \n--------------------------\n Sun Jan 20 09:00:43 2019\n\nTRUNCATE btest_numeric;\n-- non-NULL values followed by NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n first \n-------\n      \n\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n last \n------\n    1\n\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n          first           \n--------------------------\n Sun Jan 20 09:00:43 2019\n\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n           last           \n--------------------------\n Sun Jan 20 09:00:43 2019\n\nROLLBACK;\n-- diff results with optimizations disabled and enabled\n\\o :TEST_RESULTS_UNOPTIMIZED\nSET timescaledb.enable_optimizations TO false;\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- canary for results diff\n-- this should be only output of results diff\nSELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations')) v(setting);\n:PREFIX SELECT time, gp, temp FROM btest ORDER BY time;\n:PREFIX SELECT last(temp, time) FROM btest;\n:PREFIX SELECT first(temp, time) FROM btest;\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n:PREFIX SELECT gp, last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, first(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n--check whole row\n:PREFIX SELECT gp, first(btest, time) FROM btest GROUP BY gp ORDER BY gp;\n--check toasted col\n:PREFIX SELECT gp, left(last(strid, time), 10) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, last(temp, strid) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, last(strid, temp) FROM btest GROUP BY gp ORDER BY gp;\nBEGIN;\n--check null value as last element\nINSERT INTO btest VALUES('2018-01-20T09:00:43', '2017-01-20T09:00:55', 2, NULL);\n:PREFIX SELECT last(temp, time) FROM btest;\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest VALUES('2019-01-20T09:00:43', '2018-01-20T09:00:55', 2, 30.5);\n:PREFIX SELECT last(temp, time) FROM btest;\n--check null cmp element is skipped\nINSERT INTO btest VALUES('2018-01-20T09:00:43', NULL, 2, 32.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n-- fist returns NULL value\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n-- test first return non NULL value\nINSERT INTO btest VALUES('2016-01-20T09:00:00', '2016-01-20T09:00:00', 2, 36.5);\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--check non null cmp element insert after null cmp\nINSERT INTO btest VALUES('2020-01-20T09:00:43', '2020-01-20T09:00:43', 2, 35.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--cmp nulls should be ignored and not present in groups\n:PREFIX SELECT gp, last(temp, time_alt) FROM btest GROUP BY gp ORDER BY gp;\n--Previously, some bugs were found with NULLS and numeric types, so test that\nINSERT INTO btest_numeric VALUES ('2019-01-20T09:00:43', NULL);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest_numeric VALUES('2020-01-20T09:00:43', 30.5);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n-- do index scan for last\n:PREFIX SELECT last(temp, time) FROM btest;\n-- do index scan for first\n:PREFIX SELECT first(temp, time) FROM btest;\n-- can't do index scan when ordering on non-index column\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n-- do index scan for subquery\n:PREFIX SELECT * FROM (SELECT last(temp, time) FROM btest) last;\n-- can't do index scan when using group by\n:PREFIX SELECT last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n-- do index scan when agg function is used in CTE subquery\n:PREFIX WITH last_temp AS (SELECT last(temp, time) FROM btest) SELECT * from last_temp;\n-- do index scan when using both FIRST and LAST aggregate functions\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n-- verify results when using both FIRST and LAST\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n-- do index scan when using WHERE\n:PREFIX SELECT last(temp, time) FROM btest WHERE time <= '2017-01-20T09:00:02';\n-- can't do index scan for MAX and LAST combined (MinMax optimization fails when having different aggregate functions)\n:PREFIX SELECT max(time), last(temp, time) FROM btest;\n-- can't do index scan when using FIRST/LAST in ORDER BY\n:PREFIX SELECT last(temp, time) FROM btest ORDER BY last(temp, time);\n-- do index scan\n:PREFIX SELECT last(temp, time) FROM btest WHERE temp < 30;\n-- SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n-- do index scan\n:PREFIX SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n-- can't do index scan when using WINDOW function\n:PREFIX SELECT gp, last(temp, time) OVER (PARTITION BY gp) AS last FROM btest;\n-- test constants\n:PREFIX SELECT first(100, 100) FROM btest;\n-- create an index so we can test optimization\nCREATE INDEX btest_time_alt_idx ON btest(time_alt);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n--test nested FIRST/LAST - should optimize\n:PREFIX SELECT abs(last(temp, time)) FROM btest;\n-- test nested FIRST/LAST in ORDER BY - no optimization possible\n:PREFIX SELECT abs(last(temp, time)) FROM btest ORDER BY abs(last(temp,time));\nROLLBACK;\n-- Test with NULL numeric values\nBEGIN;\nTRUNCATE btest_numeric;\n-- Empty table\n:PREFIX SELECT first(btest_numeric, time) FROM btest_numeric;\n:PREFIX SELECT last(btest_numeric, time) FROM btest_numeric;\n-- Only NULL values\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n-- NULL values followed by non-NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\nTRUNCATE btest_numeric;\n-- non-NULL values followed by NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\nROLLBACK;\n\\o\n\\o :TEST_RESULTS_OPTIMIZED\nSET timescaledb.enable_optimizations TO true;\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- canary for results diff\n-- this should be only output of results diff\nSELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations')) v(setting);\n:PREFIX SELECT time, gp, temp FROM btest ORDER BY time;\n:PREFIX SELECT last(temp, time) FROM btest;\n:PREFIX SELECT first(temp, time) FROM btest;\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n:PREFIX SELECT gp, last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, first(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n--check whole row\n:PREFIX SELECT gp, first(btest, time) FROM btest GROUP BY gp ORDER BY gp;\n--check toasted col\n:PREFIX SELECT gp, left(last(strid, time), 10) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, last(temp, strid) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, last(strid, temp) FROM btest GROUP BY gp ORDER BY gp;\nBEGIN;\n--check null value as last element\nINSERT INTO btest VALUES('2018-01-20T09:00:43', '2017-01-20T09:00:55', 2, NULL);\n:PREFIX SELECT last(temp, time) FROM btest;\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest VALUES('2019-01-20T09:00:43', '2018-01-20T09:00:55', 2, 30.5);\n:PREFIX SELECT last(temp, time) FROM btest;\n--check null cmp element is skipped\nINSERT INTO btest VALUES('2018-01-20T09:00:43', NULL, 2, 32.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n-- fist returns NULL value\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n-- test first return non NULL value\nINSERT INTO btest VALUES('2016-01-20T09:00:00', '2016-01-20T09:00:00', 2, 36.5);\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--check non null cmp element insert after null cmp\nINSERT INTO btest VALUES('2020-01-20T09:00:43', '2020-01-20T09:00:43', 2, 35.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--cmp nulls should be ignored and not present in groups\n:PREFIX SELECT gp, last(temp, time_alt) FROM btest GROUP BY gp ORDER BY gp;\n--Previously, some bugs were found with NULLS and numeric types, so test that\nINSERT INTO btest_numeric VALUES ('2019-01-20T09:00:43', NULL);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest_numeric VALUES('2020-01-20T09:00:43', 30.5);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n-- do index scan for last\n:PREFIX SELECT last(temp, time) FROM btest;\n-- do index scan for first\n:PREFIX SELECT first(temp, time) FROM btest;\n-- can't do index scan when ordering on non-index column\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n-- do index scan for subquery\n:PREFIX SELECT * FROM (SELECT last(temp, time) FROM btest) last;\n-- can't do index scan when using group by\n:PREFIX SELECT last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n-- do index scan when agg function is used in CTE subquery\n:PREFIX WITH last_temp AS (SELECT last(temp, time) FROM btest) SELECT * from last_temp;\n-- do index scan when using both FIRST and LAST aggregate functions\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n-- verify results when using both FIRST and LAST\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n-- do index scan when using WHERE\n:PREFIX SELECT last(temp, time) FROM btest WHERE time <= '2017-01-20T09:00:02';\n-- can't do index scan for MAX and LAST combined (MinMax optimization fails when having different aggregate functions)\n:PREFIX SELECT max(time), last(temp, time) FROM btest;\n-- can't do index scan when using FIRST/LAST in ORDER BY\n:PREFIX SELECT last(temp, time) FROM btest ORDER BY last(temp, time);\n-- do index scan\n:PREFIX SELECT last(temp, time) FROM btest WHERE temp < 30;\n-- SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n-- do index scan\n:PREFIX SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n-- can't do index scan when using WINDOW function\n:PREFIX SELECT gp, last(temp, time) OVER (PARTITION BY gp) AS last FROM btest;\n-- test constants\n:PREFIX SELECT first(100, 100) FROM btest;\n-- create an index so we can test optimization\nCREATE INDEX btest_time_alt_idx ON btest(time_alt);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n--test nested FIRST/LAST - should optimize\n:PREFIX SELECT abs(last(temp, time)) FROM btest;\n-- test nested FIRST/LAST in ORDER BY - no optimization possible\n:PREFIX SELECT abs(last(temp, time)) FROM btest ORDER BY abs(last(temp,time));\nROLLBACK;\n-- Test with NULL numeric values\nBEGIN;\nTRUNCATE btest_numeric;\n-- Empty table\n:PREFIX SELECT first(btest_numeric, time) FROM btest_numeric;\n:PREFIX SELECT last(btest_numeric, time) FROM btest_numeric;\n-- Only NULL values\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n-- NULL values followed by non-NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\nTRUNCATE btest_numeric;\n-- non-NULL values followed by NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\nROLLBACK;\n\\o\n:DIFF_CMD\n--- Unoptimized result\n+++ Optimized result\n@@ -1,6 +1,6 @@\n              setting              | value \n ----------------------------------+-------\n- timescaledb.enable_optimizations | off\n+ timescaledb.enable_optimizations | on\n \n            time           | gp | temp \n-- Test partial aggregation\nCREATE TABLE partial_aggregation (time timestamptz NOT NULL, quantity numeric, longvalue text);\nSELECT schema_name, table_name, created FROM create_hypertable('partial_aggregation', 'time');\n schema_name |     table_name      | created \n-------------+---------------------+---------\n public      | partial_aggregation | t\n\nINSERT INTO partial_aggregation VALUES('2018-01-20T09:00:43', NULL, NULL);\nINSERT INTO partial_aggregation VALUES('2018-01-20T09:00:44', NULL, NULL);\nINSERT INTO partial_aggregation VALUES('2019-01-20T09:00:43', 1, 'hello');\nINSERT INTO partial_aggregation VALUES('2019-01-20T09:00:44', 2, 'world');\nINSERT INTO partial_aggregation VALUES('2020-01-20T09:00:43', 3.1, 'some1');\nINSERT INTO partial_aggregation VALUES('2020-01-20T09:00:44', 3.2, 'more1');\nINSERT INTO partial_aggregation VALUES('2021-01-20T09:00:43', 3.3, 'some2');\nINSERT INTO partial_aggregation VALUES('2021-01-20T09:00:44', 3.4, 'more2');\nINSERT INTO partial_aggregation VALUES('2022-01-20T09:00:43', 4, 'word1');\nINSERT INTO partial_aggregation VALUES('2022-01-20T09:00:44', 5, 'word2');\nINSERT INTO partial_aggregation VALUES('2023-01-20T09:00:43', 6, 'word3');\nINSERT INTO partial_aggregation VALUES('2023-01-20T09:00:44', 7, 'word4');\n-- Use enable_partitionwise_aggregate to create partial aggregates per chunk\nSET enable_partitionwise_aggregate = ON;\nSELECT\n    format('SELECT %3$s, %1$s FROM partial_aggregation WHERE %2$s GROUP BY %3$s ORDER BY 1, 2;',\n            function, condition, grouping)\nFROM\n    unnest(array[\n            'first(time, quantity), last(time, quantity)',\n            'last(longvalue, quantity)',\n            'last(quantity, longvalue)',\n            'last(quantity, time)',\n            'last(time, longvalue)']) AS function,\n    unnest(array[\n            'true',\n            $$time < '2021-01-01'$$,\n            'quantity is null',\n            'quantity is not null',\n            'quantity >= 4']) AS condition,\n    unnest(array[\n            '777::text' /* dummy grouping column */,\n            'longvalue',\n            'quantity',\n            $$time_bucket('1 year', time)$$,\n            $$time_bucket('3 year', time)$$]) AS grouping\n\\gexec\nSELECT 777::text, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE true GROUP BY 777::text ORDER BY 1, 2;\n text |            first             |             last             \n------+------------------------------+------------------------------\n 777  | Sun Jan 20 09:00:43 2019 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT 777::text, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY 777::text ORDER BY 1, 2;\n text |            first             |             last             \n------+------------------------------+------------------------------\n 777  | Sun Jan 20 09:00:43 2019 PST | Mon Jan 20 09:00:44 2020 PST\n\nSELECT 777::text, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY 777::text ORDER BY 1, 2;\n text | first | last \n------+-------+------\n 777  |       | \n\nSELECT 777::text, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY 777::text ORDER BY 1, 2;\n text |            first             |             last             \n------+------------------------------+------------------------------\n 777  | Sun Jan 20 09:00:43 2019 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT 777::text, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY 777::text ORDER BY 1, 2;\n text |            first             |             last             \n------+------------------------------+------------------------------\n 777  | Thu Jan 20 09:00:43 2022 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT 777::text, last(longvalue, quantity) FROM partial_aggregation WHERE true GROUP BY 777::text ORDER BY 1, 2;\n text | last  \n------+-------\n 777  | word4\n\nSELECT 777::text, last(longvalue, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY 777::text ORDER BY 1, 2;\n text | last  \n------+-------\n 777  | more1\n\nSELECT 777::text, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  | \n\nSELECT 777::text, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY 777::text ORDER BY 1, 2;\n text | last  \n------+-------\n 777  | word4\n\nSELECT 777::text, last(longvalue, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY 777::text ORDER BY 1, 2;\n text | last  \n------+-------\n 777  | word4\n\nSELECT 777::text, last(quantity, longvalue) FROM partial_aggregation WHERE true GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    2\n\nSELECT 777::text, last(quantity, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    2\n\nSELECT 777::text, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |     \n\nSELECT 777::text, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    2\n\nSELECT 777::text, last(quantity, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    7\n\nSELECT 777::text, last(quantity, time) FROM partial_aggregation WHERE true GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    7\n\nSELECT 777::text, last(quantity, time) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |  3.2\n\nSELECT 777::text, last(quantity, time) FROM partial_aggregation WHERE quantity is null GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |     \n\nSELECT 777::text, last(quantity, time) FROM partial_aggregation WHERE quantity is not null GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    7\n\nSELECT 777::text, last(quantity, time) FROM partial_aggregation WHERE quantity >= 4 GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    7\n\nSELECT 777::text, last(time, longvalue) FROM partial_aggregation WHERE true GROUP BY 777::text ORDER BY 1, 2;\n text |             last             \n------+------------------------------\n 777  | Sun Jan 20 09:00:44 2019 PST\n\nSELECT 777::text, last(time, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY 777::text ORDER BY 1, 2;\n text |             last             \n------+------------------------------\n 777  | Sun Jan 20 09:00:44 2019 PST\n\nSELECT 777::text, last(time, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  | \n\nSELECT 777::text, last(time, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY 777::text ORDER BY 1, 2;\n text |             last             \n------+------------------------------\n 777  | Sun Jan 20 09:00:44 2019 PST\n\nSELECT 777::text, last(time, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY 777::text ORDER BY 1, 2;\n text |             last             \n------+------------------------------\n 777  | Fri Jan 20 09:00:44 2023 PST\n\nSELECT longvalue, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE true GROUP BY longvalue ORDER BY 1, 2;\n longvalue |            first             |             last             \n-----------+------------------------------+------------------------------\n hello     | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST\n more1     | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST\n more2     | Wed Jan 20 09:00:44 2021 PST | Wed Jan 20 09:00:44 2021 PST\n some1     | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST\n some2     | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:43 2021 PST\n word1     | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST\n word2     | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST\n word3     | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST\n word4     | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST\n world     | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST\n           |                              | \n\nSELECT longvalue, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY longvalue ORDER BY 1, 2;\n longvalue |            first             |             last             \n-----------+------------------------------+------------------------------\n hello     | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST\n more1     | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST\n some1     | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST\n world     | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST\n           |                              | \n\nSELECT longvalue, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | first | last \n-----------+-------+------\n           |       | \n\nSELECT longvalue, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY longvalue ORDER BY 1, 2;\n longvalue |            first             |             last             \n-----------+------------------------------+------------------------------\n hello     | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST\n more1     | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST\n more2     | Wed Jan 20 09:00:44 2021 PST | Wed Jan 20 09:00:44 2021 PST\n some1     | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST\n some2     | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:43 2021 PST\n word1     | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST\n word2     | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST\n word3     | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST\n word4     | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST\n world     | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST\n\nSELECT longvalue, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY longvalue ORDER BY 1, 2;\n longvalue |            first             |             last             \n-----------+------------------------------+------------------------------\n word1     | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST\n word2     | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST\n word3     | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST\n word4     | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT longvalue, last(longvalue, quantity) FROM partial_aggregation WHERE true GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last  \n-----------+-------\n hello     | hello\n more1     | more1\n more2     | more2\n some1     | some1\n some2     | some2\n word1     | word1\n word2     | word2\n word3     | word3\n word4     | word4\n world     | world\n           | \n\nSELECT longvalue, last(longvalue, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last  \n-----------+-------\n hello     | hello\n more1     | more1\n some1     | some1\n world     | world\n           | \n\nSELECT longvalue, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n           | \n\nSELECT longvalue, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last  \n-----------+-------\n hello     | hello\n more1     | more1\n more2     | more2\n some1     | some1\n some2     | some2\n word1     | word1\n word2     | word2\n word3     | word3\n word4     | word4\n world     | world\n\nSELECT longvalue, last(longvalue, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last  \n-----------+-------\n word1     | word1\n word2     | word2\n word3     | word3\n word4     | word4\n\nSELECT longvalue, last(quantity, longvalue) FROM partial_aggregation WHERE true GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n hello     |    1\n more1     |  3.2\n more2     |  3.4\n some1     |  3.1\n some2     |  3.3\n word1     |    4\n word2     |    5\n word3     |    6\n word4     |    7\n world     |    2\n           |     \n\nSELECT longvalue, last(quantity, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n hello     |    1\n more1     |  3.2\n some1     |  3.1\n world     |    2\n           |     \n\nSELECT longvalue, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n           |     \n\nSELECT longvalue, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n hello     |    1\n more1     |  3.2\n more2     |  3.4\n some1     |  3.1\n some2     |  3.3\n word1     |    4\n word2     |    5\n word3     |    6\n word4     |    7\n world     |    2\n\nSELECT longvalue, last(quantity, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n word1     |    4\n word2     |    5\n word3     |    6\n word4     |    7\n\nSELECT longvalue, last(quantity, time) FROM partial_aggregation WHERE true GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n hello     |    1\n more1     |  3.2\n more2     |  3.4\n some1     |  3.1\n some2     |  3.3\n word1     |    4\n word2     |    5\n word3     |    6\n word4     |    7\n world     |    2\n           |     \n\nSELECT longvalue, last(quantity, time) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n hello     |    1\n more1     |  3.2\n some1     |  3.1\n world     |    2\n           |     \n\nSELECT longvalue, last(quantity, time) FROM partial_aggregation WHERE quantity is null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n           |     \n\nSELECT longvalue, last(quantity, time) FROM partial_aggregation WHERE quantity is not null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n hello     |    1\n more1     |  3.2\n more2     |  3.4\n some1     |  3.1\n some2     |  3.3\n word1     |    4\n word2     |    5\n word3     |    6\n word4     |    7\n world     |    2\n\nSELECT longvalue, last(quantity, time) FROM partial_aggregation WHERE quantity >= 4 GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n word1     |    4\n word2     |    5\n word3     |    6\n word4     |    7\n\nSELECT longvalue, last(time, longvalue) FROM partial_aggregation WHERE true GROUP BY longvalue ORDER BY 1, 2;\n longvalue |             last             \n-----------+------------------------------\n hello     | Sun Jan 20 09:00:43 2019 PST\n more1     | Mon Jan 20 09:00:44 2020 PST\n more2     | Wed Jan 20 09:00:44 2021 PST\n some1     | Mon Jan 20 09:00:43 2020 PST\n some2     | Wed Jan 20 09:00:43 2021 PST\n word1     | Thu Jan 20 09:00:43 2022 PST\n word2     | Thu Jan 20 09:00:44 2022 PST\n word3     | Fri Jan 20 09:00:43 2023 PST\n word4     | Fri Jan 20 09:00:44 2023 PST\n world     | Sun Jan 20 09:00:44 2019 PST\n           | \n\nSELECT longvalue, last(time, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY longvalue ORDER BY 1, 2;\n longvalue |             last             \n-----------+------------------------------\n hello     | Sun Jan 20 09:00:43 2019 PST\n more1     | Mon Jan 20 09:00:44 2020 PST\n some1     | Mon Jan 20 09:00:43 2020 PST\n world     | Sun Jan 20 09:00:44 2019 PST\n           | \n\nSELECT longvalue, last(time, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n           | \n\nSELECT longvalue, last(time, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY longvalue ORDER BY 1, 2;\n longvalue |             last             \n-----------+------------------------------\n hello     | Sun Jan 20 09:00:43 2019 PST\n more1     | Mon Jan 20 09:00:44 2020 PST\n more2     | Wed Jan 20 09:00:44 2021 PST\n some1     | Mon Jan 20 09:00:43 2020 PST\n some2     | Wed Jan 20 09:00:43 2021 PST\n word1     | Thu Jan 20 09:00:43 2022 PST\n word2     | Thu Jan 20 09:00:44 2022 PST\n word3     | Fri Jan 20 09:00:43 2023 PST\n word4     | Fri Jan 20 09:00:44 2023 PST\n world     | Sun Jan 20 09:00:44 2019 PST\n\nSELECT longvalue, last(time, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY longvalue ORDER BY 1, 2;\n longvalue |             last             \n-----------+------------------------------\n word1     | Thu Jan 20 09:00:43 2022 PST\n word2     | Thu Jan 20 09:00:44 2022 PST\n word3     | Fri Jan 20 09:00:43 2023 PST\n word4     | Fri Jan 20 09:00:44 2023 PST\n\nSELECT quantity, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE true GROUP BY quantity ORDER BY 1, 2;\n quantity |            first             |             last             \n----------+------------------------------+------------------------------\n        1 | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST\n        2 | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST\n      3.1 | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST\n      3.2 | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST\n      3.3 | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:43 2021 PST\n      3.4 | Wed Jan 20 09:00:44 2021 PST | Wed Jan 20 09:00:44 2021 PST\n        4 | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST\n        5 | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST\n        6 | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST\n        7 | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST\n          |                              | \n\nSELECT quantity, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY quantity ORDER BY 1, 2;\n quantity |            first             |             last             \n----------+------------------------------+------------------------------\n        1 | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST\n        2 | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST\n      3.1 | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST\n      3.2 | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST\n          |                              | \n\nSELECT quantity, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY quantity ORDER BY 1, 2;\n quantity | first | last \n----------+-------+------\n          |       | \n\nSELECT quantity, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY quantity ORDER BY 1, 2;\n quantity |            first             |             last             \n----------+------------------------------+------------------------------\n        1 | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST\n        2 | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST\n      3.1 | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST\n      3.2 | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST\n      3.3 | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:43 2021 PST\n      3.4 | Wed Jan 20 09:00:44 2021 PST | Wed Jan 20 09:00:44 2021 PST\n        4 | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST\n        5 | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST\n        6 | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST\n        7 | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT quantity, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY quantity ORDER BY 1, 2;\n quantity |            first             |             last             \n----------+------------------------------+------------------------------\n        4 | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST\n        5 | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST\n        6 | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST\n        7 | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT quantity, last(longvalue, quantity) FROM partial_aggregation WHERE true GROUP BY quantity ORDER BY 1, 2;\n quantity | last  \n----------+-------\n        1 | hello\n        2 | world\n      3.1 | some1\n      3.2 | more1\n      3.3 | some2\n      3.4 | more2\n        4 | word1\n        5 | word2\n        6 | word3\n        7 | word4\n          | \n\nSELECT quantity, last(longvalue, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY quantity ORDER BY 1, 2;\n quantity | last  \n----------+-------\n        1 | hello\n        2 | world\n      3.1 | some1\n      3.2 | more1\n          | \n\nSELECT quantity, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n          | \n\nSELECT quantity, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY quantity ORDER BY 1, 2;\n quantity | last  \n----------+-------\n        1 | hello\n        2 | world\n      3.1 | some1\n      3.2 | more1\n      3.3 | some2\n      3.4 | more2\n        4 | word1\n        5 | word2\n        6 | word3\n        7 | word4\n\nSELECT quantity, last(longvalue, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY quantity ORDER BY 1, 2;\n quantity | last  \n----------+-------\n        4 | word1\n        5 | word2\n        6 | word3\n        7 | word4\n\nSELECT quantity, last(quantity, longvalue) FROM partial_aggregation WHERE true GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        1 |    1\n        2 |    2\n      3.1 |  3.1\n      3.2 |  3.2\n      3.3 |  3.3\n      3.4 |  3.4\n        4 |    4\n        5 |    5\n        6 |    6\n        7 |    7\n          |     \n\nSELECT quantity, last(quantity, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        1 |    1\n        2 |    2\n      3.1 |  3.1\n      3.2 |  3.2\n          |     \n\nSELECT quantity, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n          |     \n\nSELECT quantity, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        1 |    1\n        2 |    2\n      3.1 |  3.1\n      3.2 |  3.2\n      3.3 |  3.3\n      3.4 |  3.4\n        4 |    4\n        5 |    5\n        6 |    6\n        7 |    7\n\nSELECT quantity, last(quantity, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        4 |    4\n        5 |    5\n        6 |    6\n        7 |    7\n\nSELECT quantity, last(quantity, time) FROM partial_aggregation WHERE true GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        1 |    1\n        2 |    2\n      3.1 |  3.1\n      3.2 |  3.2\n      3.3 |  3.3\n      3.4 |  3.4\n        4 |    4\n        5 |    5\n        6 |    6\n        7 |    7\n          |     \n\nSELECT quantity, last(quantity, time) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        1 |    1\n        2 |    2\n      3.1 |  3.1\n      3.2 |  3.2\n          |     \n\nSELECT quantity, last(quantity, time) FROM partial_aggregation WHERE quantity is null GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n          |     \n\nSELECT quantity, last(quantity, time) FROM partial_aggregation WHERE quantity is not null GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        1 |    1\n        2 |    2\n      3.1 |  3.1\n      3.2 |  3.2\n      3.3 |  3.3\n      3.4 |  3.4\n        4 |    4\n        5 |    5\n        6 |    6\n        7 |    7\n\nSELECT quantity, last(quantity, time) FROM partial_aggregation WHERE quantity >= 4 GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        4 |    4\n        5 |    5\n        6 |    6\n        7 |    7\n\nSELECT quantity, last(time, longvalue) FROM partial_aggregation WHERE true GROUP BY quantity ORDER BY 1, 2;\n quantity |             last             \n----------+------------------------------\n        1 | Sun Jan 20 09:00:43 2019 PST\n        2 | Sun Jan 20 09:00:44 2019 PST\n      3.1 | Mon Jan 20 09:00:43 2020 PST\n      3.2 | Mon Jan 20 09:00:44 2020 PST\n      3.3 | Wed Jan 20 09:00:43 2021 PST\n      3.4 | Wed Jan 20 09:00:44 2021 PST\n        4 | Thu Jan 20 09:00:43 2022 PST\n        5 | Thu Jan 20 09:00:44 2022 PST\n        6 | Fri Jan 20 09:00:43 2023 PST\n        7 | Fri Jan 20 09:00:44 2023 PST\n          | \n\nSELECT quantity, last(time, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY quantity ORDER BY 1, 2;\n quantity |             last             \n----------+------------------------------\n        1 | Sun Jan 20 09:00:43 2019 PST\n        2 | Sun Jan 20 09:00:44 2019 PST\n      3.1 | Mon Jan 20 09:00:43 2020 PST\n      3.2 | Mon Jan 20 09:00:44 2020 PST\n          | \n\nSELECT quantity, last(time, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n          | \n\nSELECT quantity, last(time, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY quantity ORDER BY 1, 2;\n quantity |             last             \n----------+------------------------------\n        1 | Sun Jan 20 09:00:43 2019 PST\n        2 | Sun Jan 20 09:00:44 2019 PST\n      3.1 | Mon Jan 20 09:00:43 2020 PST\n      3.2 | Mon Jan 20 09:00:44 2020 PST\n      3.3 | Wed Jan 20 09:00:43 2021 PST\n      3.4 | Wed Jan 20 09:00:44 2021 PST\n        4 | Thu Jan 20 09:00:43 2022 PST\n        5 | Thu Jan 20 09:00:44 2022 PST\n        6 | Fri Jan 20 09:00:43 2023 PST\n        7 | Fri Jan 20 09:00:44 2023 PST\n\nSELECT quantity, last(time, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY quantity ORDER BY 1, 2;\n quantity |             last             \n----------+------------------------------\n        4 | Thu Jan 20 09:00:43 2022 PST\n        5 | Thu Jan 20 09:00:44 2022 PST\n        6 | Fri Jan 20 09:00:43 2023 PST\n        7 | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('1 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE true GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST |                              | \n Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:44 2019 PST\n Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:44 2020 PST\n Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:44 2021 PST\n Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:44 2022 PST\n Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('1 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST |                              | \n Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:44 2019 PST\n Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:44 2020 PST\n\nSELECT time_bucket('1 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | first | last \n------------------------------+-------+------\n Sun Dec 31 16:00:00 2017 PST |       | \n\nSELECT time_bucket('1 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:44 2019 PST\n Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:44 2020 PST\n Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:44 2021 PST\n Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:44 2022 PST\n Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('1 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:44 2022 PST\n Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('1 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE true GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Sun Dec 31 16:00:00 2017 PST | \n Mon Dec 31 16:00:00 2018 PST | world\n Tue Dec 31 16:00:00 2019 PST | more1\n Thu Dec 31 16:00:00 2020 PST | more2\n Fri Dec 31 16:00:00 2021 PST | word2\n Sat Dec 31 16:00:00 2022 PST | word4\n\nSELECT time_bucket('1 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Sun Dec 31 16:00:00 2017 PST | \n Mon Dec 31 16:00:00 2018 PST | world\n Tue Dec 31 16:00:00 2019 PST | more1\n\nSELECT time_bucket('1 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST | \n\nSELECT time_bucket('1 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Mon Dec 31 16:00:00 2018 PST | world\n Tue Dec 31 16:00:00 2019 PST | more1\n Thu Dec 31 16:00:00 2020 PST | more2\n Fri Dec 31 16:00:00 2021 PST | word2\n Sat Dec 31 16:00:00 2022 PST | word4\n\nSELECT time_bucket('1 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Fri Dec 31 16:00:00 2021 PST | word2\n Sat Dec 31 16:00:00 2022 PST | word4\n\nSELECT time_bucket('1 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE true GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n Mon Dec 31 16:00:00 2018 PST |    2\n Tue Dec 31 16:00:00 2019 PST |  3.1\n Thu Dec 31 16:00:00 2020 PST |  3.3\n Fri Dec 31 16:00:00 2021 PST |    5\n Sat Dec 31 16:00:00 2022 PST |    7\n\nSELECT time_bucket('1 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n Mon Dec 31 16:00:00 2018 PST |    2\n Tue Dec 31 16:00:00 2019 PST |  3.1\n\nSELECT time_bucket('1 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n\nSELECT time_bucket('1 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Mon Dec 31 16:00:00 2018 PST |    2\n Tue Dec 31 16:00:00 2019 PST |  3.1\n Thu Dec 31 16:00:00 2020 PST |  3.3\n Fri Dec 31 16:00:00 2021 PST |    5\n Sat Dec 31 16:00:00 2022 PST |    7\n\nSELECT time_bucket('1 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Fri Dec 31 16:00:00 2021 PST |    5\n Sat Dec 31 16:00:00 2022 PST |    7\n\nSELECT time_bucket('1 year', time), last(quantity, time) FROM partial_aggregation WHERE true GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n Mon Dec 31 16:00:00 2018 PST |    2\n Tue Dec 31 16:00:00 2019 PST |  3.2\n Thu Dec 31 16:00:00 2020 PST |  3.4\n Fri Dec 31 16:00:00 2021 PST |    5\n Sat Dec 31 16:00:00 2022 PST |    7\n\nSELECT time_bucket('1 year', time), last(quantity, time) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n Mon Dec 31 16:00:00 2018 PST |    2\n Tue Dec 31 16:00:00 2019 PST |  3.2\n\nSELECT time_bucket('1 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n\nSELECT time_bucket('1 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Mon Dec 31 16:00:00 2018 PST |    2\n Tue Dec 31 16:00:00 2019 PST |  3.2\n Thu Dec 31 16:00:00 2020 PST |  3.4\n Fri Dec 31 16:00:00 2021 PST |    5\n Sat Dec 31 16:00:00 2022 PST |    7\n\nSELECT time_bucket('1 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Fri Dec 31 16:00:00 2021 PST |    5\n Sat Dec 31 16:00:00 2022 PST |    7\n\nSELECT time_bucket('1 year', time), last(time, longvalue) FROM partial_aggregation WHERE true GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | \n Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:44 2019 PST\n Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST\n Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST\n Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:44 2022 PST\n Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('1 year', time), last(time, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | \n Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:44 2019 PST\n Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST\n\nSELECT time_bucket('1 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST | \n\nSELECT time_bucket('1 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:44 2019 PST\n Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST\n Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST\n Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:44 2022 PST\n Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('1 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:44 2022 PST\n Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('3 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE true GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:43 2019 PST | Mon Jan 20 09:00:44 2020 PST\n Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('3 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:43 2019 PST | Mon Jan 20 09:00:44 2020 PST\n\nSELECT time_bucket('3 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | first | last \n------------------------------+-------+------\n Sun Dec 31 16:00:00 2017 PST |       | \n\nSELECT time_bucket('3 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:43 2019 PST | Mon Jan 20 09:00:44 2020 PST\n Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('3 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Thu Dec 31 16:00:00 2020 PST | Thu Jan 20 09:00:43 2022 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('3 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE true GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Sun Dec 31 16:00:00 2017 PST | more1\n Thu Dec 31 16:00:00 2020 PST | word4\n\nSELECT time_bucket('3 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Sun Dec 31 16:00:00 2017 PST | more1\n\nSELECT time_bucket('3 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST | \n\nSELECT time_bucket('3 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Sun Dec 31 16:00:00 2017 PST | more1\n Thu Dec 31 16:00:00 2020 PST | word4\n\nSELECT time_bucket('3 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Thu Dec 31 16:00:00 2020 PST | word4\n\nSELECT time_bucket('3 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE true GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |    2\n Thu Dec 31 16:00:00 2020 PST |    7\n\nSELECT time_bucket('3 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |    2\n\nSELECT time_bucket('3 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n\nSELECT time_bucket('3 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |    2\n Thu Dec 31 16:00:00 2020 PST |    7\n\nSELECT time_bucket('3 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Thu Dec 31 16:00:00 2020 PST |    7\n\nSELECT time_bucket('3 year', time), last(quantity, time) FROM partial_aggregation WHERE true GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |  3.2\n Thu Dec 31 16:00:00 2020 PST |    7\n\nSELECT time_bucket('3 year', time), last(quantity, time) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |  3.2\n\nSELECT time_bucket('3 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n\nSELECT time_bucket('3 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |  3.2\n Thu Dec 31 16:00:00 2020 PST |    7\n\nSELECT time_bucket('3 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Thu Dec 31 16:00:00 2020 PST |    7\n\nSELECT time_bucket('3 year', time), last(time, longvalue) FROM partial_aggregation WHERE true GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:44 2019 PST\n Thu Dec 31 16:00:00 2020 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('3 year', time), last(time, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:44 2019 PST\n\nSELECT time_bucket('3 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST | \n\nSELECT time_bucket('3 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:44 2019 PST\n Thu Dec 31 16:00:00 2020 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('3 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Thu Dec 31 16:00:00 2020 PST | Fri Jan 20 09:00:44 2023 PST\n\nSET enable_partitionwise_aggregate = OFF;\n"
  },
  {
    "path": "test/expected/agg_bookends-17.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\set TEST_BASE_NAME agg_bookends\nSELECT format('include/%s_load.sql', :'TEST_BASE_NAME') as \"TEST_LOAD_NAME\",\n       format('include/%s_query.sql', :'TEST_BASE_NAME') as \"TEST_QUERY_NAME\",\n       format('%s/results/%s_results_optimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_OPTIMIZED\",\n       format('%s/results/%s_results_unoptimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_UNOPTIMIZED\"\n\\gset\nSELECT format('\\! diff -u  --label \"Unoptimized result\" --label \"Optimized result\" %s %s', :'TEST_RESULTS_UNOPTIMIZED', :'TEST_RESULTS_OPTIMIZED') as \"DIFF_CMD\"\n\\gset\n\\set PREFIX 'EXPLAIN (analyze, buffers off, costs off, timing off, summary off)'\n\\ir :TEST_LOAD_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE btest(time timestamp NOT NULL, time_alt timestamp, gp INTEGER, temp float, strid TEXT DEFAULT 'testing');\nSELECT schema_name, table_name, created FROM create_hypertable('btest', 'time');\npsql:include/agg_bookends_load.sql:6: WARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\npsql:include/agg_bookends_load.sql:6: WARNING:  column type \"timestamp without time zone\" used for \"time_alt\" does not follow best practices\n schema_name | table_name | created \n-------------+------------+---------\n public      | btest      | t\n\nINSERT INTO btest VALUES('2017-01-20T09:00:01', '2017-01-20T10:00:00', 1, 22.5);\nINSERT INTO btest VALUES('2017-01-20T09:00:21', '2017-01-20T09:00:59', 1, 21.2);\nINSERT INTO btest VALUES('2017-01-20T09:00:47', '2017-01-20T09:00:58', 1, 25.1);\nINSERT INTO btest VALUES('2017-01-20T09:00:02', '2017-01-20T09:00:57', 2, 35.5);\nINSERT INTO btest VALUES('2017-01-20T09:00:21', '2017-01-20T09:00:56', 2, 30.2);\n--TOASTED;\nINSERT INTO btest VALUES('2017-01-20T09:00:43', '2017-01-20T09:01:55', 2, 20.1, repeat('xyz', 1000000) );\nCREATE TABLE btest_numeric (time timestamp NOT NULL, quantity numeric);\nSELECT schema_name, table_name, created FROM create_hypertable('btest_numeric', 'time');\npsql:include/agg_bookends_load.sql:16: WARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n schema_name |  table_name   | created \n-------------+---------------+---------\n public      | btest_numeric | t\n\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- canary for results diff\n-- this should be only output of results diff\nSELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations')) v(setting);\n             setting              | value \n----------------------------------+-------\n timescaledb.enable_optimizations | on\n\n:PREFIX SELECT time, gp, temp FROM btest ORDER BY time;\n--- QUERY PLAN ---\n Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n:PREFIX SELECT last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n\n:PREFIX SELECT first(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n:PREFIX SELECT gp, last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: _hyper_1_1_chunk.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: _hyper_1_1_chunk.gp\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n:PREFIX SELECT gp, first(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: _hyper_1_1_chunk.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: _hyper_1_1_chunk.gp\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n--check whole row\n:PREFIX SELECT gp, first(btest, time) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: _hyper_1_1_chunk.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: _hyper_1_1_chunk.gp\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n--check toasted col\n:PREFIX SELECT gp, left(last(strid, time), 10) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: _hyper_1_1_chunk.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: _hyper_1_1_chunk.gp\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n:PREFIX SELECT gp, last(temp, strid) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: _hyper_1_1_chunk.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: _hyper_1_1_chunk.gp\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n:PREFIX SELECT gp, last(strid, temp) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: _hyper_1_1_chunk.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: _hyper_1_1_chunk.gp\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\nBEGIN;\n--check null value as last element\nINSERT INTO btest VALUES('2018-01-20T09:00:43', '2017-01-20T09:00:55', 2, NULL);\n:PREFIX SELECT last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest VALUES('2019-01-20T09:00:43', '2018-01-20T09:00:55', 2, 30.5);\n:PREFIX SELECT last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n\n--check null cmp element is skipped\nINSERT INTO btest VALUES('2018-01-20T09:00:43', NULL, 2, 32.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=9.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n\n-- fist returns NULL value\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=9.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n\n-- test first return non NULL value\nINSERT INTO btest VALUES('2016-01-20T09:00:00', '2016-01-20T09:00:00', 2, 36.5);\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=10.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n\n--check non null cmp element insert after null cmp\nINSERT INTO btest VALUES('2020-01-20T09:00:43', '2020-01-20T09:00:43', 2, 35.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=11.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=11.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n--cmp nulls should be ignored and not present in groups\n:PREFIX SELECT gp, last(temp, time_alt) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: btest.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: btest.gp\n         ->  Append (actual rows=11.00 loops=1)\n               ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n               ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n--Previously, some bugs were found with NULLS and numeric types, so test that\nINSERT INTO btest_numeric VALUES ('2019-01-20T09:00:43', NULL);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Index Scan using _hyper_2_6_chunk_btest_numeric_time_idx on _hyper_2_6_chunk (actual rows=1.00 loops=1)\n\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest_numeric VALUES('2020-01-20T09:00:43', 30.5);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest_numeric (actual rows=1.00 loops=1)\n                 Order: btest_numeric.\"time\" DESC\n                 ->  Index Scan using _hyper_2_7_chunk_btest_numeric_time_idx on _hyper_2_7_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan using _hyper_2_6_chunk_btest_numeric_time_idx on _hyper_2_6_chunk (never executed)\n\n-- do index scan for last\n:PREFIX SELECT last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n\n-- do index scan for first\n:PREFIX SELECT first(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\"\n                 ->  Index Scan Backward using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                 ->  Index Scan Backward using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                 ->  Index Scan Backward using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                 ->  Index Scan Backward using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (never executed)\n\n-- can't do index scan when ordering on non-index column\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=11.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n-- do index scan for subquery\n:PREFIX SELECT * FROM (SELECT last(temp, time) FROM btest) last;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n\n-- can't do index scan when using group by\n:PREFIX SELECT last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: btest.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: btest.gp\n         ->  Append (actual rows=11.00 loops=1)\n               ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n               ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n-- do index scan when agg function is used in CTE subquery\n:PREFIX WITH last_temp AS (SELECT last(temp, time) FROM btest) SELECT * from last_temp;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n\n-- do index scan when using both FIRST and LAST aggregate functions\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n   InitPlan 2\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest btest_1 (actual rows=1.00 loops=1)\n                 Order: btest_1.\"time\"\n                 ->  Index Scan Backward using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk _hyper_1_4_chunk_1 (actual rows=1.00 loops=1)\n                 ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (never executed)\n                 ->  Index Scan Backward using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (never executed)\n                 ->  Index Scan Backward using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk _hyper_1_3_chunk_1 (never executed)\n                 ->  Index Scan Backward using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk _hyper_1_5_chunk_1 (never executed)\n\n-- verify results when using both FIRST and LAST\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n   InitPlan 2\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest btest_1 (actual rows=1.00 loops=1)\n                 Order: btest_1.\"time\"\n                 ->  Index Scan Backward using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk _hyper_1_4_chunk_1 (actual rows=1.00 loops=1)\n                 ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (never executed)\n                 ->  Index Scan Backward using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (never executed)\n                 ->  Index Scan Backward using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk _hyper_1_3_chunk_1 (never executed)\n                 ->  Index Scan Backward using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk _hyper_1_5_chunk_1 (never executed)\n\n-- do index scan when using WHERE\n:PREFIX SELECT last(temp, time) FROM btest WHERE time <= '2017-01-20T09:00:02';\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" <= 'Fri Jan 20 09:00:02 2017'::timestamp without time zone)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n\n-- can't do index scan for MAX and LAST combined (MinMax optimization fails when having different aggregate functions)\n:PREFIX SELECT max(time), last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=11.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n-- can't do index scan when using FIRST/LAST in ORDER BY\n:PREFIX SELECT last(temp, time) FROM btest ORDER BY last(temp, time);\n--- QUERY PLAN ---\n Sort (actual rows=1.00 loops=1)\n   Sort Key: (last(btest.temp, btest.\"time\"))\n   Sort Method: quicksort \n   ->  Aggregate (actual rows=1.00 loops=1)\n         ->  Append (actual rows=11.00 loops=1)\n               ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n               ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n-- do index scan\n:PREFIX SELECT last(temp, time) FROM btest WHERE temp < 30;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=0.00 loops=1)\n                       Filter: (temp < '30'::double precision)\n                       Rows Removed by Filter: 1\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (actual rows=0.00 loops=1)\n                       Filter: (temp < '30'::double precision)\n                       Rows Removed by Filter: 1\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (actual rows=0.00 loops=1)\n                       Filter: (temp < '30'::double precision)\n                       Rows Removed by Filter: 2\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n                       Filter: (temp < '30'::double precision)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n                       Filter: (temp < '30'::double precision)\n\n-- SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n-- do index scan\n:PREFIX SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\"\n                 ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" >= 'Fri Jan 20 09:00:47 2017'::timestamp without time zone)\n                 ->  Index Scan Backward using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                 ->  Index Scan Backward using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                 ->  Index Scan Backward using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (never executed)\n\n-- can't do index scan when using WINDOW function\n:PREFIX SELECT gp, last(temp, time) OVER (PARTITION BY gp) AS last FROM btest;\n--- QUERY PLAN ---\n WindowAgg (actual rows=11.00 loops=1)\n   ->  Sort (actual rows=11.00 loops=1)\n         Sort Key: btest.gp\n         Sort Method: quicksort \n         ->  Append (actual rows=11.00 loops=1)\n               ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n               ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n-- test constants\n:PREFIX SELECT first(100, 100) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Result (actual rows=1.00 loops=1)\n                 ->  Append (actual rows=1.00 loops=1)\n                       ->  Seq Scan on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n                       ->  Seq Scan on _hyper_1_2_chunk (never executed)\n                       ->  Seq Scan on _hyper_1_3_chunk (never executed)\n                       ->  Seq Scan on _hyper_1_4_chunk (never executed)\n                       ->  Seq Scan on _hyper_1_5_chunk (never executed)\n\n-- create an index so we can test optimization\nCREATE INDEX btest_time_alt_idx ON btest(time_alt);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Merge Append (actual rows=1.00 loops=1)\n                 Sort Key: btest.time_alt DESC\n                 ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_alt_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (time_alt IS NOT NULL)\n                 ->  Index Scan Backward using _hyper_1_2_chunk_btest_time_alt_idx on _hyper_1_2_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (time_alt IS NOT NULL)\n                 ->  Index Scan Backward using _hyper_1_3_chunk_btest_time_alt_idx on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (time_alt IS NOT NULL)\n                 ->  Index Scan Backward using _hyper_1_4_chunk_btest_time_alt_idx on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (time_alt IS NOT NULL)\n                 ->  Index Scan Backward using _hyper_1_5_chunk_btest_time_alt_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (time_alt IS NOT NULL)\n\n--test nested FIRST/LAST - should optimize\n:PREFIX SELECT abs(last(temp, time)) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n\n-- test nested FIRST/LAST in ORDER BY - no optimization possible\n:PREFIX SELECT abs(last(temp, time)) FROM btest ORDER BY abs(last(temp,time));\n--- QUERY PLAN ---\n Sort (actual rows=1.00 loops=1)\n   Sort Key: (abs(last(btest.temp, btest.\"time\")))\n   Sort Method: quicksort \n   ->  Aggregate (actual rows=1.00 loops=1)\n         ->  Append (actual rows=11.00 loops=1)\n               ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n               ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\nROLLBACK;\n-- Test with NULL numeric values\nBEGIN;\nTRUNCATE btest_numeric;\n-- Empty table\n:PREFIX SELECT first(btest_numeric, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Result (actual rows=0.00 loops=1)\n         One-Time Filter: false\n\n:PREFIX SELECT last(btest_numeric, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Result (actual rows=0.00 loops=1)\n         One-Time Filter: false\n\n-- Only NULL values\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Index Scan Backward using _hyper_2_8_chunk_btest_numeric_time_idx on _hyper_2_8_chunk (actual rows=1.00 loops=1)\n\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Index Scan using _hyper_2_8_chunk_btest_numeric_time_idx on _hyper_2_8_chunk (actual rows=1.00 loops=1)\n\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_2_8_chunk (actual rows=2.00 loops=1)\n\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_2_8_chunk (actual rows=2.00 loops=1)\n\n-- NULL values followed by non-NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest_numeric (actual rows=1.00 loops=1)\n                 Order: btest_numeric.\"time\"\n                 ->  Index Scan Backward using _hyper_2_8_chunk_btest_numeric_time_idx on _hyper_2_8_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan Backward using _hyper_2_9_chunk_btest_numeric_time_idx on _hyper_2_9_chunk (never executed)\n\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest_numeric (actual rows=1.00 loops=1)\n                 Order: btest_numeric.\"time\" DESC\n                 ->  Index Scan using _hyper_2_9_chunk_btest_numeric_time_idx on _hyper_2_9_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan using _hyper_2_8_chunk_btest_numeric_time_idx on _hyper_2_8_chunk (never executed)\n\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=4.00 loops=1)\n         ->  Seq Scan on _hyper_2_8_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_2_9_chunk (actual rows=2.00 loops=1)\n\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=4.00 loops=1)\n         ->  Seq Scan on _hyper_2_8_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_2_9_chunk (actual rows=2.00 loops=1)\n\nTRUNCATE btest_numeric;\n-- non-NULL values followed by NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest_numeric (actual rows=1.00 loops=1)\n                 Order: btest_numeric.\"time\"\n                 ->  Index Scan Backward using _hyper_2_11_chunk_btest_numeric_time_idx on _hyper_2_11_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan Backward using _hyper_2_10_chunk_btest_numeric_time_idx on _hyper_2_10_chunk (never executed)\n\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest_numeric (actual rows=1.00 loops=1)\n                 Order: btest_numeric.\"time\" DESC\n                 ->  Index Scan using _hyper_2_10_chunk_btest_numeric_time_idx on _hyper_2_10_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan using _hyper_2_11_chunk_btest_numeric_time_idx on _hyper_2_11_chunk (never executed)\n\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=4.00 loops=1)\n         ->  Seq Scan on _hyper_2_10_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_2_11_chunk (actual rows=2.00 loops=1)\n\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=4.00 loops=1)\n         ->  Seq Scan on _hyper_2_10_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_2_11_chunk (actual rows=2.00 loops=1)\n\nROLLBACK;\n-- we want test results as part of the output too to make sure we produce correct output\n\\set PREFIX ''\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- canary for results diff\n-- this should be only output of results diff\nSELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations')) v(setting);\n             setting              | value \n----------------------------------+-------\n timescaledb.enable_optimizations | on\n\n:PREFIX SELECT time, gp, temp FROM btest ORDER BY time;\n           time           | gp | temp \n--------------------------+----+------\n Fri Jan 20 09:00:01 2017 |  1 | 22.5\n Fri Jan 20 09:00:02 2017 |  2 | 35.5\n Fri Jan 20 09:00:21 2017 |  1 | 21.2\n Fri Jan 20 09:00:21 2017 |  2 | 30.2\n Fri Jan 20 09:00:43 2017 |  2 | 20.1\n Fri Jan 20 09:00:47 2017 |  1 | 25.1\n\n:PREFIX SELECT last(temp, time) FROM btest;\n last \n------\n 25.1\n\n:PREFIX SELECT first(temp, time) FROM btest;\n first \n-------\n  22.5\n\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n last \n------\n 22.5\n\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n first \n-------\n  30.2\n\n:PREFIX SELECT gp, last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n gp | last \n----+------\n  1 | 25.1\n  2 | 20.1\n\n:PREFIX SELECT gp, first(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n gp | first \n----+-------\n  1 |  22.5\n  2 |  35.5\n\n--check whole row\n:PREFIX SELECT gp, first(btest, time) FROM btest GROUP BY gp ORDER BY gp;\n gp |                                 first                                  \n----+------------------------------------------------------------------------\n  1 | (\"Fri Jan 20 09:00:01 2017\",\"Fri Jan 20 10:00:00 2017\",1,22.5,testing)\n  2 | (\"Fri Jan 20 09:00:02 2017\",\"Fri Jan 20 09:00:57 2017\",2,35.5,testing)\n\n--check toasted col\n:PREFIX SELECT gp, left(last(strid, time), 10) FROM btest GROUP BY gp ORDER BY gp;\n gp |    left    \n----+------------\n  1 | testing\n  2 | xyzxyzxyzx\n\n:PREFIX SELECT gp, last(temp, strid) FROM btest GROUP BY gp ORDER BY gp;\n gp | last \n----+------\n  1 | 22.5\n  2 | 20.1\n\n:PREFIX SELECT gp, last(strid, temp) FROM btest GROUP BY gp ORDER BY gp;\n gp |  last   \n----+---------\n  1 | testing\n  2 | testing\n\nBEGIN;\n--check null value as last element\nINSERT INTO btest VALUES('2018-01-20T09:00:43', '2017-01-20T09:00:55', 2, NULL);\n:PREFIX SELECT last(temp, time) FROM btest;\n last \n------\n     \n\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest VALUES('2019-01-20T09:00:43', '2018-01-20T09:00:55', 2, 30.5);\n:PREFIX SELECT last(temp, time) FROM btest;\n last \n------\n 30.5\n\n--check null cmp element is skipped\nINSERT INTO btest VALUES('2018-01-20T09:00:43', NULL, 2, 32.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n last \n------\n 30.5\n\n-- fist returns NULL value\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n first \n-------\n      \n\n-- test first return non NULL value\nINSERT INTO btest VALUES('2016-01-20T09:00:00', '2016-01-20T09:00:00', 2, 36.5);\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n first \n-------\n  36.5\n\n--check non null cmp element insert after null cmp\nINSERT INTO btest VALUES('2020-01-20T09:00:43', '2020-01-20T09:00:43', 2, 35.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n last \n------\n 35.3\n\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n first \n-------\n  36.5\n\n--cmp nulls should be ignored and not present in groups\n:PREFIX SELECT gp, last(temp, time_alt) FROM btest GROUP BY gp ORDER BY gp;\n gp | last \n----+------\n  1 | 22.5\n  2 | 35.3\n\n--Previously, some bugs were found with NULLS and numeric types, so test that\nINSERT INTO btest_numeric VALUES ('2019-01-20T09:00:43', NULL);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n last \n------\n     \n\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest_numeric VALUES('2020-01-20T09:00:43', 30.5);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n last \n------\n 30.5\n\n-- do index scan for last\n:PREFIX SELECT last(temp, time) FROM btest;\n last \n------\n 35.3\n\n-- do index scan for first\n:PREFIX SELECT first(temp, time) FROM btest;\n first \n-------\n  36.5\n\n-- can't do index scan when ordering on non-index column\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n first \n-------\n  36.5\n\n-- do index scan for subquery\n:PREFIX SELECT * FROM (SELECT last(temp, time) FROM btest) last;\n last \n------\n 35.3\n\n-- can't do index scan when using group by\n:PREFIX SELECT last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n last \n------\n 25.1\n 35.3\n\n-- do index scan when agg function is used in CTE subquery\n:PREFIX WITH last_temp AS (SELECT last(temp, time) FROM btest) SELECT * from last_temp;\n last \n------\n 35.3\n\n-- do index scan when using both FIRST and LAST aggregate functions\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n first | last \n-------+------\n  36.5 | 35.3\n\n-- verify results when using both FIRST and LAST\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n first | last \n-------+------\n  36.5 | 35.3\n\n-- do index scan when using WHERE\n:PREFIX SELECT last(temp, time) FROM btest WHERE time <= '2017-01-20T09:00:02';\n last \n------\n 35.5\n\n-- can't do index scan for MAX and LAST combined (MinMax optimization fails when having different aggregate functions)\n:PREFIX SELECT max(time), last(temp, time) FROM btest;\n           max            | last \n--------------------------+------\n Mon Jan 20 09:00:43 2020 | 35.3\n\n-- can't do index scan when using FIRST/LAST in ORDER BY\n:PREFIX SELECT last(temp, time) FROM btest ORDER BY last(temp, time);\n last \n------\n 35.3\n\n-- do index scan\n:PREFIX SELECT last(temp, time) FROM btest WHERE temp < 30;\n last \n------\n 25.1\n\n-- SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n-- do index scan\n:PREFIX SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n first \n-------\n  25.1\n\n-- can't do index scan when using WINDOW function\n:PREFIX SELECT gp, last(temp, time) OVER (PARTITION BY gp) AS last FROM btest;\n gp | last \n----+------\n  1 | 25.1\n  1 | 25.1\n  1 | 25.1\n  2 | 35.3\n  2 | 35.3\n  2 | 35.3\n  2 | 35.3\n  2 | 35.3\n  2 | 35.3\n  2 | 35.3\n  2 | 35.3\n\n-- test constants\n:PREFIX SELECT first(100, 100) FROM btest;\n first \n-------\n   100\n\n-- create an index so we can test optimization\nCREATE INDEX btest_time_alt_idx ON btest(time_alt);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n last \n------\n 35.3\n\n--test nested FIRST/LAST - should optimize\n:PREFIX SELECT abs(last(temp, time)) FROM btest;\n abs  \n------\n 35.3\n\n-- test nested FIRST/LAST in ORDER BY - no optimization possible\n:PREFIX SELECT abs(last(temp, time)) FROM btest ORDER BY abs(last(temp,time));\n abs  \n------\n 35.3\n\nROLLBACK;\n-- Test with NULL numeric values\nBEGIN;\nTRUNCATE btest_numeric;\n-- Empty table\n:PREFIX SELECT first(btest_numeric, time) FROM btest_numeric;\n first \n-------\n \n\n:PREFIX SELECT last(btest_numeric, time) FROM btest_numeric;\n last \n------\n \n\n-- Only NULL values\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n first \n-------\n      \n\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n last \n------\n     \n\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n first \n-------\n \n\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n last \n------\n \n\n-- NULL values followed by non-NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n first \n-------\n      \n\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n last \n------\n    1\n\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n          first           \n--------------------------\n Sun Jan 20 09:00:43 2019\n\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n           last           \n--------------------------\n Sun Jan 20 09:00:43 2019\n\nTRUNCATE btest_numeric;\n-- non-NULL values followed by NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n first \n-------\n      \n\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n last \n------\n    1\n\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n          first           \n--------------------------\n Sun Jan 20 09:00:43 2019\n\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n           last           \n--------------------------\n Sun Jan 20 09:00:43 2019\n\nROLLBACK;\n-- diff results with optimizations disabled and enabled\n\\o :TEST_RESULTS_UNOPTIMIZED\nSET timescaledb.enable_optimizations TO false;\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- canary for results diff\n-- this should be only output of results diff\nSELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations')) v(setting);\n:PREFIX SELECT time, gp, temp FROM btest ORDER BY time;\n:PREFIX SELECT last(temp, time) FROM btest;\n:PREFIX SELECT first(temp, time) FROM btest;\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n:PREFIX SELECT gp, last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, first(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n--check whole row\n:PREFIX SELECT gp, first(btest, time) FROM btest GROUP BY gp ORDER BY gp;\n--check toasted col\n:PREFIX SELECT gp, left(last(strid, time), 10) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, last(temp, strid) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, last(strid, temp) FROM btest GROUP BY gp ORDER BY gp;\nBEGIN;\n--check null value as last element\nINSERT INTO btest VALUES('2018-01-20T09:00:43', '2017-01-20T09:00:55', 2, NULL);\n:PREFIX SELECT last(temp, time) FROM btest;\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest VALUES('2019-01-20T09:00:43', '2018-01-20T09:00:55', 2, 30.5);\n:PREFIX SELECT last(temp, time) FROM btest;\n--check null cmp element is skipped\nINSERT INTO btest VALUES('2018-01-20T09:00:43', NULL, 2, 32.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n-- fist returns NULL value\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n-- test first return non NULL value\nINSERT INTO btest VALUES('2016-01-20T09:00:00', '2016-01-20T09:00:00', 2, 36.5);\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--check non null cmp element insert after null cmp\nINSERT INTO btest VALUES('2020-01-20T09:00:43', '2020-01-20T09:00:43', 2, 35.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--cmp nulls should be ignored and not present in groups\n:PREFIX SELECT gp, last(temp, time_alt) FROM btest GROUP BY gp ORDER BY gp;\n--Previously, some bugs were found with NULLS and numeric types, so test that\nINSERT INTO btest_numeric VALUES ('2019-01-20T09:00:43', NULL);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest_numeric VALUES('2020-01-20T09:00:43', 30.5);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n-- do index scan for last\n:PREFIX SELECT last(temp, time) FROM btest;\n-- do index scan for first\n:PREFIX SELECT first(temp, time) FROM btest;\n-- can't do index scan when ordering on non-index column\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n-- do index scan for subquery\n:PREFIX SELECT * FROM (SELECT last(temp, time) FROM btest) last;\n-- can't do index scan when using group by\n:PREFIX SELECT last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n-- do index scan when agg function is used in CTE subquery\n:PREFIX WITH last_temp AS (SELECT last(temp, time) FROM btest) SELECT * from last_temp;\n-- do index scan when using both FIRST and LAST aggregate functions\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n-- verify results when using both FIRST and LAST\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n-- do index scan when using WHERE\n:PREFIX SELECT last(temp, time) FROM btest WHERE time <= '2017-01-20T09:00:02';\n-- can't do index scan for MAX and LAST combined (MinMax optimization fails when having different aggregate functions)\n:PREFIX SELECT max(time), last(temp, time) FROM btest;\n-- can't do index scan when using FIRST/LAST in ORDER BY\n:PREFIX SELECT last(temp, time) FROM btest ORDER BY last(temp, time);\n-- do index scan\n:PREFIX SELECT last(temp, time) FROM btest WHERE temp < 30;\n-- SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n-- do index scan\n:PREFIX SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n-- can't do index scan when using WINDOW function\n:PREFIX SELECT gp, last(temp, time) OVER (PARTITION BY gp) AS last FROM btest;\n-- test constants\n:PREFIX SELECT first(100, 100) FROM btest;\n-- create an index so we can test optimization\nCREATE INDEX btest_time_alt_idx ON btest(time_alt);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n--test nested FIRST/LAST - should optimize\n:PREFIX SELECT abs(last(temp, time)) FROM btest;\n-- test nested FIRST/LAST in ORDER BY - no optimization possible\n:PREFIX SELECT abs(last(temp, time)) FROM btest ORDER BY abs(last(temp,time));\nROLLBACK;\n-- Test with NULL numeric values\nBEGIN;\nTRUNCATE btest_numeric;\n-- Empty table\n:PREFIX SELECT first(btest_numeric, time) FROM btest_numeric;\n:PREFIX SELECT last(btest_numeric, time) FROM btest_numeric;\n-- Only NULL values\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n-- NULL values followed by non-NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\nTRUNCATE btest_numeric;\n-- non-NULL values followed by NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\nROLLBACK;\n\\o\n\\o :TEST_RESULTS_OPTIMIZED\nSET timescaledb.enable_optimizations TO true;\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- canary for results diff\n-- this should be only output of results diff\nSELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations')) v(setting);\n:PREFIX SELECT time, gp, temp FROM btest ORDER BY time;\n:PREFIX SELECT last(temp, time) FROM btest;\n:PREFIX SELECT first(temp, time) FROM btest;\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n:PREFIX SELECT gp, last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, first(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n--check whole row\n:PREFIX SELECT gp, first(btest, time) FROM btest GROUP BY gp ORDER BY gp;\n--check toasted col\n:PREFIX SELECT gp, left(last(strid, time), 10) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, last(temp, strid) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, last(strid, temp) FROM btest GROUP BY gp ORDER BY gp;\nBEGIN;\n--check null value as last element\nINSERT INTO btest VALUES('2018-01-20T09:00:43', '2017-01-20T09:00:55', 2, NULL);\n:PREFIX SELECT last(temp, time) FROM btest;\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest VALUES('2019-01-20T09:00:43', '2018-01-20T09:00:55', 2, 30.5);\n:PREFIX SELECT last(temp, time) FROM btest;\n--check null cmp element is skipped\nINSERT INTO btest VALUES('2018-01-20T09:00:43', NULL, 2, 32.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n-- fist returns NULL value\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n-- test first return non NULL value\nINSERT INTO btest VALUES('2016-01-20T09:00:00', '2016-01-20T09:00:00', 2, 36.5);\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--check non null cmp element insert after null cmp\nINSERT INTO btest VALUES('2020-01-20T09:00:43', '2020-01-20T09:00:43', 2, 35.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--cmp nulls should be ignored and not present in groups\n:PREFIX SELECT gp, last(temp, time_alt) FROM btest GROUP BY gp ORDER BY gp;\n--Previously, some bugs were found with NULLS and numeric types, so test that\nINSERT INTO btest_numeric VALUES ('2019-01-20T09:00:43', NULL);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest_numeric VALUES('2020-01-20T09:00:43', 30.5);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n-- do index scan for last\n:PREFIX SELECT last(temp, time) FROM btest;\n-- do index scan for first\n:PREFIX SELECT first(temp, time) FROM btest;\n-- can't do index scan when ordering on non-index column\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n-- do index scan for subquery\n:PREFIX SELECT * FROM (SELECT last(temp, time) FROM btest) last;\n-- can't do index scan when using group by\n:PREFIX SELECT last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n-- do index scan when agg function is used in CTE subquery\n:PREFIX WITH last_temp AS (SELECT last(temp, time) FROM btest) SELECT * from last_temp;\n-- do index scan when using both FIRST and LAST aggregate functions\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n-- verify results when using both FIRST and LAST\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n-- do index scan when using WHERE\n:PREFIX SELECT last(temp, time) FROM btest WHERE time <= '2017-01-20T09:00:02';\n-- can't do index scan for MAX and LAST combined (MinMax optimization fails when having different aggregate functions)\n:PREFIX SELECT max(time), last(temp, time) FROM btest;\n-- can't do index scan when using FIRST/LAST in ORDER BY\n:PREFIX SELECT last(temp, time) FROM btest ORDER BY last(temp, time);\n-- do index scan\n:PREFIX SELECT last(temp, time) FROM btest WHERE temp < 30;\n-- SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n-- do index scan\n:PREFIX SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n-- can't do index scan when using WINDOW function\n:PREFIX SELECT gp, last(temp, time) OVER (PARTITION BY gp) AS last FROM btest;\n-- test constants\n:PREFIX SELECT first(100, 100) FROM btest;\n-- create an index so we can test optimization\nCREATE INDEX btest_time_alt_idx ON btest(time_alt);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n--test nested FIRST/LAST - should optimize\n:PREFIX SELECT abs(last(temp, time)) FROM btest;\n-- test nested FIRST/LAST in ORDER BY - no optimization possible\n:PREFIX SELECT abs(last(temp, time)) FROM btest ORDER BY abs(last(temp,time));\nROLLBACK;\n-- Test with NULL numeric values\nBEGIN;\nTRUNCATE btest_numeric;\n-- Empty table\n:PREFIX SELECT first(btest_numeric, time) FROM btest_numeric;\n:PREFIX SELECT last(btest_numeric, time) FROM btest_numeric;\n-- Only NULL values\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n-- NULL values followed by non-NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\nTRUNCATE btest_numeric;\n-- non-NULL values followed by NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\nROLLBACK;\n\\o\n:DIFF_CMD\n--- Unoptimized result\n+++ Optimized result\n@@ -1,6 +1,6 @@\n              setting              | value \n ----------------------------------+-------\n- timescaledb.enable_optimizations | off\n+ timescaledb.enable_optimizations | on\n \n            time           | gp | temp \n-- Test partial aggregation\nCREATE TABLE partial_aggregation (time timestamptz NOT NULL, quantity numeric, longvalue text);\nSELECT schema_name, table_name, created FROM create_hypertable('partial_aggregation', 'time');\n schema_name |     table_name      | created \n-------------+---------------------+---------\n public      | partial_aggregation | t\n\nINSERT INTO partial_aggregation VALUES('2018-01-20T09:00:43', NULL, NULL);\nINSERT INTO partial_aggregation VALUES('2018-01-20T09:00:44', NULL, NULL);\nINSERT INTO partial_aggregation VALUES('2019-01-20T09:00:43', 1, 'hello');\nINSERT INTO partial_aggregation VALUES('2019-01-20T09:00:44', 2, 'world');\nINSERT INTO partial_aggregation VALUES('2020-01-20T09:00:43', 3.1, 'some1');\nINSERT INTO partial_aggregation VALUES('2020-01-20T09:00:44', 3.2, 'more1');\nINSERT INTO partial_aggregation VALUES('2021-01-20T09:00:43', 3.3, 'some2');\nINSERT INTO partial_aggregation VALUES('2021-01-20T09:00:44', 3.4, 'more2');\nINSERT INTO partial_aggregation VALUES('2022-01-20T09:00:43', 4, 'word1');\nINSERT INTO partial_aggregation VALUES('2022-01-20T09:00:44', 5, 'word2');\nINSERT INTO partial_aggregation VALUES('2023-01-20T09:00:43', 6, 'word3');\nINSERT INTO partial_aggregation VALUES('2023-01-20T09:00:44', 7, 'word4');\n-- Use enable_partitionwise_aggregate to create partial aggregates per chunk\nSET enable_partitionwise_aggregate = ON;\nSELECT\n    format('SELECT %3$s, %1$s FROM partial_aggregation WHERE %2$s GROUP BY %3$s ORDER BY 1, 2;',\n            function, condition, grouping)\nFROM\n    unnest(array[\n            'first(time, quantity), last(time, quantity)',\n            'last(longvalue, quantity)',\n            'last(quantity, longvalue)',\n            'last(quantity, time)',\n            'last(time, longvalue)']) AS function,\n    unnest(array[\n            'true',\n            $$time < '2021-01-01'$$,\n            'quantity is null',\n            'quantity is not null',\n            'quantity >= 4']) AS condition,\n    unnest(array[\n            '777::text' /* dummy grouping column */,\n            'longvalue',\n            'quantity',\n            $$time_bucket('1 year', time)$$,\n            $$time_bucket('3 year', time)$$]) AS grouping\n\\gexec\nSELECT 777::text, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE true GROUP BY 777::text ORDER BY 1, 2;\n text |            first             |             last             \n------+------------------------------+------------------------------\n 777  | Sun Jan 20 09:00:43 2019 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT 777::text, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY 777::text ORDER BY 1, 2;\n text |            first             |             last             \n------+------------------------------+------------------------------\n 777  | Sun Jan 20 09:00:43 2019 PST | Mon Jan 20 09:00:44 2020 PST\n\nSELECT 777::text, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY 777::text ORDER BY 1, 2;\n text | first | last \n------+-------+------\n 777  |       | \n\nSELECT 777::text, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY 777::text ORDER BY 1, 2;\n text |            first             |             last             \n------+------------------------------+------------------------------\n 777  | Sun Jan 20 09:00:43 2019 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT 777::text, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY 777::text ORDER BY 1, 2;\n text |            first             |             last             \n------+------------------------------+------------------------------\n 777  | Thu Jan 20 09:00:43 2022 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT 777::text, last(longvalue, quantity) FROM partial_aggregation WHERE true GROUP BY 777::text ORDER BY 1, 2;\n text | last  \n------+-------\n 777  | word4\n\nSELECT 777::text, last(longvalue, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY 777::text ORDER BY 1, 2;\n text | last  \n------+-------\n 777  | more1\n\nSELECT 777::text, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  | \n\nSELECT 777::text, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY 777::text ORDER BY 1, 2;\n text | last  \n------+-------\n 777  | word4\n\nSELECT 777::text, last(longvalue, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY 777::text ORDER BY 1, 2;\n text | last  \n------+-------\n 777  | word4\n\nSELECT 777::text, last(quantity, longvalue) FROM partial_aggregation WHERE true GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    2\n\nSELECT 777::text, last(quantity, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    2\n\nSELECT 777::text, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |     \n\nSELECT 777::text, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    2\n\nSELECT 777::text, last(quantity, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    7\n\nSELECT 777::text, last(quantity, time) FROM partial_aggregation WHERE true GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    7\n\nSELECT 777::text, last(quantity, time) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |  3.2\n\nSELECT 777::text, last(quantity, time) FROM partial_aggregation WHERE quantity is null GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |     \n\nSELECT 777::text, last(quantity, time) FROM partial_aggregation WHERE quantity is not null GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    7\n\nSELECT 777::text, last(quantity, time) FROM partial_aggregation WHERE quantity >= 4 GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    7\n\nSELECT 777::text, last(time, longvalue) FROM partial_aggregation WHERE true GROUP BY 777::text ORDER BY 1, 2;\n text |             last             \n------+------------------------------\n 777  | Sun Jan 20 09:00:44 2019 PST\n\nSELECT 777::text, last(time, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY 777::text ORDER BY 1, 2;\n text |             last             \n------+------------------------------\n 777  | Sun Jan 20 09:00:44 2019 PST\n\nSELECT 777::text, last(time, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  | \n\nSELECT 777::text, last(time, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY 777::text ORDER BY 1, 2;\n text |             last             \n------+------------------------------\n 777  | Sun Jan 20 09:00:44 2019 PST\n\nSELECT 777::text, last(time, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY 777::text ORDER BY 1, 2;\n text |             last             \n------+------------------------------\n 777  | Fri Jan 20 09:00:44 2023 PST\n\nSELECT longvalue, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE true GROUP BY longvalue ORDER BY 1, 2;\n longvalue |            first             |             last             \n-----------+------------------------------+------------------------------\n hello     | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST\n more1     | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST\n more2     | Wed Jan 20 09:00:44 2021 PST | Wed Jan 20 09:00:44 2021 PST\n some1     | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST\n some2     | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:43 2021 PST\n word1     | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST\n word2     | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST\n word3     | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST\n word4     | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST\n world     | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST\n           |                              | \n\nSELECT longvalue, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY longvalue ORDER BY 1, 2;\n longvalue |            first             |             last             \n-----------+------------------------------+------------------------------\n hello     | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST\n more1     | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST\n some1     | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST\n world     | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST\n           |                              | \n\nSELECT longvalue, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | first | last \n-----------+-------+------\n           |       | \n\nSELECT longvalue, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY longvalue ORDER BY 1, 2;\n longvalue |            first             |             last             \n-----------+------------------------------+------------------------------\n hello     | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST\n more1     | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST\n more2     | Wed Jan 20 09:00:44 2021 PST | Wed Jan 20 09:00:44 2021 PST\n some1     | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST\n some2     | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:43 2021 PST\n word1     | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST\n word2     | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST\n word3     | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST\n word4     | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST\n world     | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST\n\nSELECT longvalue, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY longvalue ORDER BY 1, 2;\n longvalue |            first             |             last             \n-----------+------------------------------+------------------------------\n word1     | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST\n word2     | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST\n word3     | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST\n word4     | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT longvalue, last(longvalue, quantity) FROM partial_aggregation WHERE true GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last  \n-----------+-------\n hello     | hello\n more1     | more1\n more2     | more2\n some1     | some1\n some2     | some2\n word1     | word1\n word2     | word2\n word3     | word3\n word4     | word4\n world     | world\n           | \n\nSELECT longvalue, last(longvalue, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last  \n-----------+-------\n hello     | hello\n more1     | more1\n some1     | some1\n world     | world\n           | \n\nSELECT longvalue, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n           | \n\nSELECT longvalue, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last  \n-----------+-------\n hello     | hello\n more1     | more1\n more2     | more2\n some1     | some1\n some2     | some2\n word1     | word1\n word2     | word2\n word3     | word3\n word4     | word4\n world     | world\n\nSELECT longvalue, last(longvalue, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last  \n-----------+-------\n word1     | word1\n word2     | word2\n word3     | word3\n word4     | word4\n\nSELECT longvalue, last(quantity, longvalue) FROM partial_aggregation WHERE true GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n hello     |    1\n more1     |  3.2\n more2     |  3.4\n some1     |  3.1\n some2     |  3.3\n word1     |    4\n word2     |    5\n word3     |    6\n word4     |    7\n world     |    2\n           |     \n\nSELECT longvalue, last(quantity, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n hello     |    1\n more1     |  3.2\n some1     |  3.1\n world     |    2\n           |     \n\nSELECT longvalue, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n           |     \n\nSELECT longvalue, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n hello     |    1\n more1     |  3.2\n more2     |  3.4\n some1     |  3.1\n some2     |  3.3\n word1     |    4\n word2     |    5\n word3     |    6\n word4     |    7\n world     |    2\n\nSELECT longvalue, last(quantity, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n word1     |    4\n word2     |    5\n word3     |    6\n word4     |    7\n\nSELECT longvalue, last(quantity, time) FROM partial_aggregation WHERE true GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n hello     |    1\n more1     |  3.2\n more2     |  3.4\n some1     |  3.1\n some2     |  3.3\n word1     |    4\n word2     |    5\n word3     |    6\n word4     |    7\n world     |    2\n           |     \n\nSELECT longvalue, last(quantity, time) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n hello     |    1\n more1     |  3.2\n some1     |  3.1\n world     |    2\n           |     \n\nSELECT longvalue, last(quantity, time) FROM partial_aggregation WHERE quantity is null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n           |     \n\nSELECT longvalue, last(quantity, time) FROM partial_aggregation WHERE quantity is not null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n hello     |    1\n more1     |  3.2\n more2     |  3.4\n some1     |  3.1\n some2     |  3.3\n word1     |    4\n word2     |    5\n word3     |    6\n word4     |    7\n world     |    2\n\nSELECT longvalue, last(quantity, time) FROM partial_aggregation WHERE quantity >= 4 GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n word1     |    4\n word2     |    5\n word3     |    6\n word4     |    7\n\nSELECT longvalue, last(time, longvalue) FROM partial_aggregation WHERE true GROUP BY longvalue ORDER BY 1, 2;\n longvalue |             last             \n-----------+------------------------------\n hello     | Sun Jan 20 09:00:43 2019 PST\n more1     | Mon Jan 20 09:00:44 2020 PST\n more2     | Wed Jan 20 09:00:44 2021 PST\n some1     | Mon Jan 20 09:00:43 2020 PST\n some2     | Wed Jan 20 09:00:43 2021 PST\n word1     | Thu Jan 20 09:00:43 2022 PST\n word2     | Thu Jan 20 09:00:44 2022 PST\n word3     | Fri Jan 20 09:00:43 2023 PST\n word4     | Fri Jan 20 09:00:44 2023 PST\n world     | Sun Jan 20 09:00:44 2019 PST\n           | \n\nSELECT longvalue, last(time, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY longvalue ORDER BY 1, 2;\n longvalue |             last             \n-----------+------------------------------\n hello     | Sun Jan 20 09:00:43 2019 PST\n more1     | Mon Jan 20 09:00:44 2020 PST\n some1     | Mon Jan 20 09:00:43 2020 PST\n world     | Sun Jan 20 09:00:44 2019 PST\n           | \n\nSELECT longvalue, last(time, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n           | \n\nSELECT longvalue, last(time, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY longvalue ORDER BY 1, 2;\n longvalue |             last             \n-----------+------------------------------\n hello     | Sun Jan 20 09:00:43 2019 PST\n more1     | Mon Jan 20 09:00:44 2020 PST\n more2     | Wed Jan 20 09:00:44 2021 PST\n some1     | Mon Jan 20 09:00:43 2020 PST\n some2     | Wed Jan 20 09:00:43 2021 PST\n word1     | Thu Jan 20 09:00:43 2022 PST\n word2     | Thu Jan 20 09:00:44 2022 PST\n word3     | Fri Jan 20 09:00:43 2023 PST\n word4     | Fri Jan 20 09:00:44 2023 PST\n world     | Sun Jan 20 09:00:44 2019 PST\n\nSELECT longvalue, last(time, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY longvalue ORDER BY 1, 2;\n longvalue |             last             \n-----------+------------------------------\n word1     | Thu Jan 20 09:00:43 2022 PST\n word2     | Thu Jan 20 09:00:44 2022 PST\n word3     | Fri Jan 20 09:00:43 2023 PST\n word4     | Fri Jan 20 09:00:44 2023 PST\n\nSELECT quantity, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE true GROUP BY quantity ORDER BY 1, 2;\n quantity |            first             |             last             \n----------+------------------------------+------------------------------\n        1 | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST\n        2 | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST\n      3.1 | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST\n      3.2 | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST\n      3.3 | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:43 2021 PST\n      3.4 | Wed Jan 20 09:00:44 2021 PST | Wed Jan 20 09:00:44 2021 PST\n        4 | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST\n        5 | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST\n        6 | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST\n        7 | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST\n          |                              | \n\nSELECT quantity, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY quantity ORDER BY 1, 2;\n quantity |            first             |             last             \n----------+------------------------------+------------------------------\n        1 | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST\n        2 | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST\n      3.1 | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST\n      3.2 | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST\n          |                              | \n\nSELECT quantity, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY quantity ORDER BY 1, 2;\n quantity | first | last \n----------+-------+------\n          |       | \n\nSELECT quantity, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY quantity ORDER BY 1, 2;\n quantity |            first             |             last             \n----------+------------------------------+------------------------------\n        1 | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST\n        2 | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST\n      3.1 | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST\n      3.2 | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST\n      3.3 | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:43 2021 PST\n      3.4 | Wed Jan 20 09:00:44 2021 PST | Wed Jan 20 09:00:44 2021 PST\n        4 | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST\n        5 | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST\n        6 | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST\n        7 | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT quantity, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY quantity ORDER BY 1, 2;\n quantity |            first             |             last             \n----------+------------------------------+------------------------------\n        4 | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST\n        5 | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST\n        6 | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST\n        7 | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT quantity, last(longvalue, quantity) FROM partial_aggregation WHERE true GROUP BY quantity ORDER BY 1, 2;\n quantity | last  \n----------+-------\n        1 | hello\n        2 | world\n      3.1 | some1\n      3.2 | more1\n      3.3 | some2\n      3.4 | more2\n        4 | word1\n        5 | word2\n        6 | word3\n        7 | word4\n          | \n\nSELECT quantity, last(longvalue, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY quantity ORDER BY 1, 2;\n quantity | last  \n----------+-------\n        1 | hello\n        2 | world\n      3.1 | some1\n      3.2 | more1\n          | \n\nSELECT quantity, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n          | \n\nSELECT quantity, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY quantity ORDER BY 1, 2;\n quantity | last  \n----------+-------\n        1 | hello\n        2 | world\n      3.1 | some1\n      3.2 | more1\n      3.3 | some2\n      3.4 | more2\n        4 | word1\n        5 | word2\n        6 | word3\n        7 | word4\n\nSELECT quantity, last(longvalue, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY quantity ORDER BY 1, 2;\n quantity | last  \n----------+-------\n        4 | word1\n        5 | word2\n        6 | word3\n        7 | word4\n\nSELECT quantity, last(quantity, longvalue) FROM partial_aggregation WHERE true GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        1 |    1\n        2 |    2\n      3.1 |  3.1\n      3.2 |  3.2\n      3.3 |  3.3\n      3.4 |  3.4\n        4 |    4\n        5 |    5\n        6 |    6\n        7 |    7\n          |     \n\nSELECT quantity, last(quantity, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        1 |    1\n        2 |    2\n      3.1 |  3.1\n      3.2 |  3.2\n          |     \n\nSELECT quantity, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n          |     \n\nSELECT quantity, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        1 |    1\n        2 |    2\n      3.1 |  3.1\n      3.2 |  3.2\n      3.3 |  3.3\n      3.4 |  3.4\n        4 |    4\n        5 |    5\n        6 |    6\n        7 |    7\n\nSELECT quantity, last(quantity, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        4 |    4\n        5 |    5\n        6 |    6\n        7 |    7\n\nSELECT quantity, last(quantity, time) FROM partial_aggregation WHERE true GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        1 |    1\n        2 |    2\n      3.1 |  3.1\n      3.2 |  3.2\n      3.3 |  3.3\n      3.4 |  3.4\n        4 |    4\n        5 |    5\n        6 |    6\n        7 |    7\n          |     \n\nSELECT quantity, last(quantity, time) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        1 |    1\n        2 |    2\n      3.1 |  3.1\n      3.2 |  3.2\n          |     \n\nSELECT quantity, last(quantity, time) FROM partial_aggregation WHERE quantity is null GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n          |     \n\nSELECT quantity, last(quantity, time) FROM partial_aggregation WHERE quantity is not null GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        1 |    1\n        2 |    2\n      3.1 |  3.1\n      3.2 |  3.2\n      3.3 |  3.3\n      3.4 |  3.4\n        4 |    4\n        5 |    5\n        6 |    6\n        7 |    7\n\nSELECT quantity, last(quantity, time) FROM partial_aggregation WHERE quantity >= 4 GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        4 |    4\n        5 |    5\n        6 |    6\n        7 |    7\n\nSELECT quantity, last(time, longvalue) FROM partial_aggregation WHERE true GROUP BY quantity ORDER BY 1, 2;\n quantity |             last             \n----------+------------------------------\n        1 | Sun Jan 20 09:00:43 2019 PST\n        2 | Sun Jan 20 09:00:44 2019 PST\n      3.1 | Mon Jan 20 09:00:43 2020 PST\n      3.2 | Mon Jan 20 09:00:44 2020 PST\n      3.3 | Wed Jan 20 09:00:43 2021 PST\n      3.4 | Wed Jan 20 09:00:44 2021 PST\n        4 | Thu Jan 20 09:00:43 2022 PST\n        5 | Thu Jan 20 09:00:44 2022 PST\n        6 | Fri Jan 20 09:00:43 2023 PST\n        7 | Fri Jan 20 09:00:44 2023 PST\n          | \n\nSELECT quantity, last(time, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY quantity ORDER BY 1, 2;\n quantity |             last             \n----------+------------------------------\n        1 | Sun Jan 20 09:00:43 2019 PST\n        2 | Sun Jan 20 09:00:44 2019 PST\n      3.1 | Mon Jan 20 09:00:43 2020 PST\n      3.2 | Mon Jan 20 09:00:44 2020 PST\n          | \n\nSELECT quantity, last(time, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n          | \n\nSELECT quantity, last(time, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY quantity ORDER BY 1, 2;\n quantity |             last             \n----------+------------------------------\n        1 | Sun Jan 20 09:00:43 2019 PST\n        2 | Sun Jan 20 09:00:44 2019 PST\n      3.1 | Mon Jan 20 09:00:43 2020 PST\n      3.2 | Mon Jan 20 09:00:44 2020 PST\n      3.3 | Wed Jan 20 09:00:43 2021 PST\n      3.4 | Wed Jan 20 09:00:44 2021 PST\n        4 | Thu Jan 20 09:00:43 2022 PST\n        5 | Thu Jan 20 09:00:44 2022 PST\n        6 | Fri Jan 20 09:00:43 2023 PST\n        7 | Fri Jan 20 09:00:44 2023 PST\n\nSELECT quantity, last(time, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY quantity ORDER BY 1, 2;\n quantity |             last             \n----------+------------------------------\n        4 | Thu Jan 20 09:00:43 2022 PST\n        5 | Thu Jan 20 09:00:44 2022 PST\n        6 | Fri Jan 20 09:00:43 2023 PST\n        7 | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('1 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE true GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST |                              | \n Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:44 2019 PST\n Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:44 2020 PST\n Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:44 2021 PST\n Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:44 2022 PST\n Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('1 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST |                              | \n Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:44 2019 PST\n Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:44 2020 PST\n\nSELECT time_bucket('1 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | first | last \n------------------------------+-------+------\n Sun Dec 31 16:00:00 2017 PST |       | \n\nSELECT time_bucket('1 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:44 2019 PST\n Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:44 2020 PST\n Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:44 2021 PST\n Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:44 2022 PST\n Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('1 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:44 2022 PST\n Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('1 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE true GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Sun Dec 31 16:00:00 2017 PST | \n Mon Dec 31 16:00:00 2018 PST | world\n Tue Dec 31 16:00:00 2019 PST | more1\n Thu Dec 31 16:00:00 2020 PST | more2\n Fri Dec 31 16:00:00 2021 PST | word2\n Sat Dec 31 16:00:00 2022 PST | word4\n\nSELECT time_bucket('1 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Sun Dec 31 16:00:00 2017 PST | \n Mon Dec 31 16:00:00 2018 PST | world\n Tue Dec 31 16:00:00 2019 PST | more1\n\nSELECT time_bucket('1 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST | \n\nSELECT time_bucket('1 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Mon Dec 31 16:00:00 2018 PST | world\n Tue Dec 31 16:00:00 2019 PST | more1\n Thu Dec 31 16:00:00 2020 PST | more2\n Fri Dec 31 16:00:00 2021 PST | word2\n Sat Dec 31 16:00:00 2022 PST | word4\n\nSELECT time_bucket('1 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Fri Dec 31 16:00:00 2021 PST | word2\n Sat Dec 31 16:00:00 2022 PST | word4\n\nSELECT time_bucket('1 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE true GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n Mon Dec 31 16:00:00 2018 PST |    2\n Tue Dec 31 16:00:00 2019 PST |  3.1\n Thu Dec 31 16:00:00 2020 PST |  3.3\n Fri Dec 31 16:00:00 2021 PST |    5\n Sat Dec 31 16:00:00 2022 PST |    7\n\nSELECT time_bucket('1 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n Mon Dec 31 16:00:00 2018 PST |    2\n Tue Dec 31 16:00:00 2019 PST |  3.1\n\nSELECT time_bucket('1 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n\nSELECT time_bucket('1 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Mon Dec 31 16:00:00 2018 PST |    2\n Tue Dec 31 16:00:00 2019 PST |  3.1\n Thu Dec 31 16:00:00 2020 PST |  3.3\n Fri Dec 31 16:00:00 2021 PST |    5\n Sat Dec 31 16:00:00 2022 PST |    7\n\nSELECT time_bucket('1 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Fri Dec 31 16:00:00 2021 PST |    5\n Sat Dec 31 16:00:00 2022 PST |    7\n\nSELECT time_bucket('1 year', time), last(quantity, time) FROM partial_aggregation WHERE true GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n Mon Dec 31 16:00:00 2018 PST |    2\n Tue Dec 31 16:00:00 2019 PST |  3.2\n Thu Dec 31 16:00:00 2020 PST |  3.4\n Fri Dec 31 16:00:00 2021 PST |    5\n Sat Dec 31 16:00:00 2022 PST |    7\n\nSELECT time_bucket('1 year', time), last(quantity, time) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n Mon Dec 31 16:00:00 2018 PST |    2\n Tue Dec 31 16:00:00 2019 PST |  3.2\n\nSELECT time_bucket('1 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n\nSELECT time_bucket('1 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Mon Dec 31 16:00:00 2018 PST |    2\n Tue Dec 31 16:00:00 2019 PST |  3.2\n Thu Dec 31 16:00:00 2020 PST |  3.4\n Fri Dec 31 16:00:00 2021 PST |    5\n Sat Dec 31 16:00:00 2022 PST |    7\n\nSELECT time_bucket('1 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Fri Dec 31 16:00:00 2021 PST |    5\n Sat Dec 31 16:00:00 2022 PST |    7\n\nSELECT time_bucket('1 year', time), last(time, longvalue) FROM partial_aggregation WHERE true GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | \n Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:44 2019 PST\n Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST\n Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST\n Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:44 2022 PST\n Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('1 year', time), last(time, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | \n Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:44 2019 PST\n Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST\n\nSELECT time_bucket('1 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST | \n\nSELECT time_bucket('1 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:44 2019 PST\n Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST\n Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST\n Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:44 2022 PST\n Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('1 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:44 2022 PST\n Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('3 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE true GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:43 2019 PST | Mon Jan 20 09:00:44 2020 PST\n Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('3 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:43 2019 PST | Mon Jan 20 09:00:44 2020 PST\n\nSELECT time_bucket('3 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | first | last \n------------------------------+-------+------\n Sun Dec 31 16:00:00 2017 PST |       | \n\nSELECT time_bucket('3 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:43 2019 PST | Mon Jan 20 09:00:44 2020 PST\n Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('3 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Thu Dec 31 16:00:00 2020 PST | Thu Jan 20 09:00:43 2022 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('3 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE true GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Sun Dec 31 16:00:00 2017 PST | more1\n Thu Dec 31 16:00:00 2020 PST | word4\n\nSELECT time_bucket('3 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Sun Dec 31 16:00:00 2017 PST | more1\n\nSELECT time_bucket('3 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST | \n\nSELECT time_bucket('3 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Sun Dec 31 16:00:00 2017 PST | more1\n Thu Dec 31 16:00:00 2020 PST | word4\n\nSELECT time_bucket('3 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Thu Dec 31 16:00:00 2020 PST | word4\n\nSELECT time_bucket('3 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE true GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |    2\n Thu Dec 31 16:00:00 2020 PST |    7\n\nSELECT time_bucket('3 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |    2\n\nSELECT time_bucket('3 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n\nSELECT time_bucket('3 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |    2\n Thu Dec 31 16:00:00 2020 PST |    7\n\nSELECT time_bucket('3 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Thu Dec 31 16:00:00 2020 PST |    7\n\nSELECT time_bucket('3 year', time), last(quantity, time) FROM partial_aggregation WHERE true GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |  3.2\n Thu Dec 31 16:00:00 2020 PST |    7\n\nSELECT time_bucket('3 year', time), last(quantity, time) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |  3.2\n\nSELECT time_bucket('3 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n\nSELECT time_bucket('3 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |  3.2\n Thu Dec 31 16:00:00 2020 PST |    7\n\nSELECT time_bucket('3 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Thu Dec 31 16:00:00 2020 PST |    7\n\nSELECT time_bucket('3 year', time), last(time, longvalue) FROM partial_aggregation WHERE true GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:44 2019 PST\n Thu Dec 31 16:00:00 2020 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('3 year', time), last(time, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:44 2019 PST\n\nSELECT time_bucket('3 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST | \n\nSELECT time_bucket('3 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:44 2019 PST\n Thu Dec 31 16:00:00 2020 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('3 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Thu Dec 31 16:00:00 2020 PST | Fri Jan 20 09:00:44 2023 PST\n\nSET enable_partitionwise_aggregate = OFF;\n"
  },
  {
    "path": "test/expected/agg_bookends-18.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\set TEST_BASE_NAME agg_bookends\nSELECT format('include/%s_load.sql', :'TEST_BASE_NAME') as \"TEST_LOAD_NAME\",\n       format('include/%s_query.sql', :'TEST_BASE_NAME') as \"TEST_QUERY_NAME\",\n       format('%s/results/%s_results_optimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_OPTIMIZED\",\n       format('%s/results/%s_results_unoptimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_UNOPTIMIZED\"\n\\gset\nSELECT format('\\! diff -u  --label \"Unoptimized result\" --label \"Optimized result\" %s %s', :'TEST_RESULTS_UNOPTIMIZED', :'TEST_RESULTS_OPTIMIZED') as \"DIFF_CMD\"\n\\gset\n\\set PREFIX 'EXPLAIN (analyze, buffers off, costs off, timing off, summary off)'\n\\ir :TEST_LOAD_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE btest(time timestamp NOT NULL, time_alt timestamp, gp INTEGER, temp float, strid TEXT DEFAULT 'testing');\nSELECT schema_name, table_name, created FROM create_hypertable('btest', 'time');\npsql:include/agg_bookends_load.sql:6: WARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\npsql:include/agg_bookends_load.sql:6: WARNING:  column type \"timestamp without time zone\" used for \"time_alt\" does not follow best practices\n schema_name | table_name | created \n-------------+------------+---------\n public      | btest      | t\n\nINSERT INTO btest VALUES('2017-01-20T09:00:01', '2017-01-20T10:00:00', 1, 22.5);\nINSERT INTO btest VALUES('2017-01-20T09:00:21', '2017-01-20T09:00:59', 1, 21.2);\nINSERT INTO btest VALUES('2017-01-20T09:00:47', '2017-01-20T09:00:58', 1, 25.1);\nINSERT INTO btest VALUES('2017-01-20T09:00:02', '2017-01-20T09:00:57', 2, 35.5);\nINSERT INTO btest VALUES('2017-01-20T09:00:21', '2017-01-20T09:00:56', 2, 30.2);\n--TOASTED;\nINSERT INTO btest VALUES('2017-01-20T09:00:43', '2017-01-20T09:01:55', 2, 20.1, repeat('xyz', 1000000) );\nCREATE TABLE btest_numeric (time timestamp NOT NULL, quantity numeric);\nSELECT schema_name, table_name, created FROM create_hypertable('btest_numeric', 'time');\npsql:include/agg_bookends_load.sql:16: WARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n schema_name |  table_name   | created \n-------------+---------------+---------\n public      | btest_numeric | t\n\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- canary for results diff\n-- this should be only output of results diff\nSELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations')) v(setting);\n             setting              | value \n----------------------------------+-------\n timescaledb.enable_optimizations | on\n\n:PREFIX SELECT time, gp, temp FROM btest ORDER BY time;\n--- QUERY PLAN ---\n Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n:PREFIX SELECT last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n\n:PREFIX SELECT first(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n:PREFIX SELECT gp, last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: _hyper_1_1_chunk.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: _hyper_1_1_chunk.gp\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n:PREFIX SELECT gp, first(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: _hyper_1_1_chunk.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: _hyper_1_1_chunk.gp\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n--check whole row\n:PREFIX SELECT gp, first(btest, time) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: _hyper_1_1_chunk.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: _hyper_1_1_chunk.gp\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n--check toasted col\n:PREFIX SELECT gp, left(last(strid, time), 10) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: _hyper_1_1_chunk.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: _hyper_1_1_chunk.gp\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n:PREFIX SELECT gp, last(temp, strid) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: _hyper_1_1_chunk.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: _hyper_1_1_chunk.gp\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\n:PREFIX SELECT gp, last(strid, temp) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: _hyper_1_1_chunk.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: _hyper_1_1_chunk.gp\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n\nBEGIN;\n--check null value as last element\nINSERT INTO btest VALUES('2018-01-20T09:00:43', '2017-01-20T09:00:55', 2, NULL);\n:PREFIX SELECT last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest VALUES('2019-01-20T09:00:43', '2018-01-20T09:00:55', 2, 30.5);\n:PREFIX SELECT last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n\n--check null cmp element is skipped\nINSERT INTO btest VALUES('2018-01-20T09:00:43', NULL, 2, 32.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=9.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n\n-- fist returns NULL value\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=9.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n\n-- test first return non NULL value\nINSERT INTO btest VALUES('2016-01-20T09:00:00', '2016-01-20T09:00:00', 2, 36.5);\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=10.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n\n--check non null cmp element insert after null cmp\nINSERT INTO btest VALUES('2020-01-20T09:00:43', '2020-01-20T09:00:43', 2, 35.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=11.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=11.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n--cmp nulls should be ignored and not present in groups\n:PREFIX SELECT gp, last(temp, time_alt) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: btest.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: btest.gp\n         ->  Append (actual rows=11.00 loops=1)\n               ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n               ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n--Previously, some bugs were found with NULLS and numeric types, so test that\nINSERT INTO btest_numeric VALUES ('2019-01-20T09:00:43', NULL);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Index Scan using _hyper_2_6_chunk_btest_numeric_time_idx on _hyper_2_6_chunk (actual rows=1.00 loops=1)\n\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest_numeric VALUES('2020-01-20T09:00:43', 30.5);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest_numeric (actual rows=1.00 loops=1)\n                 Order: btest_numeric.\"time\" DESC\n                 ->  Index Scan using _hyper_2_7_chunk_btest_numeric_time_idx on _hyper_2_7_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan using _hyper_2_6_chunk_btest_numeric_time_idx on _hyper_2_6_chunk (never executed)\n\n-- do index scan for last\n:PREFIX SELECT last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n\n-- do index scan for first\n:PREFIX SELECT first(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\"\n                 ->  Index Scan Backward using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                 ->  Index Scan Backward using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                 ->  Index Scan Backward using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                 ->  Index Scan Backward using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (never executed)\n\n-- can't do index scan when ordering on non-index column\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=11.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n-- do index scan for subquery\n:PREFIX SELECT * FROM (SELECT last(temp, time) FROM btest) last;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n\n-- can't do index scan when using group by\n:PREFIX SELECT last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n--- QUERY PLAN ---\n Sort (actual rows=2.00 loops=1)\n   Sort Key: btest.gp\n   Sort Method: quicksort \n   ->  HashAggregate (actual rows=2.00 loops=1)\n         Group Key: btest.gp\n         ->  Append (actual rows=11.00 loops=1)\n               ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n               ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n-- do index scan when agg function is used in CTE subquery\n:PREFIX WITH last_temp AS (SELECT last(temp, time) FROM btest) SELECT * from last_temp;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n\n-- do index scan when using both FIRST and LAST aggregate functions\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n   InitPlan 2\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest btest_1 (actual rows=1.00 loops=1)\n                 Order: btest_1.\"time\"\n                 ->  Index Scan Backward using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk _hyper_1_4_chunk_1 (actual rows=1.00 loops=1)\n                 ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (never executed)\n                 ->  Index Scan Backward using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (never executed)\n                 ->  Index Scan Backward using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk _hyper_1_3_chunk_1 (never executed)\n                 ->  Index Scan Backward using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk _hyper_1_5_chunk_1 (never executed)\n\n-- verify results when using both FIRST and LAST\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n   InitPlan 2\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest btest_1 (actual rows=1.00 loops=1)\n                 Order: btest_1.\"time\"\n                 ->  Index Scan Backward using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk _hyper_1_4_chunk_1 (actual rows=1.00 loops=1)\n                 ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (never executed)\n                 ->  Index Scan Backward using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (never executed)\n                 ->  Index Scan Backward using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk _hyper_1_3_chunk_1 (never executed)\n                 ->  Index Scan Backward using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk _hyper_1_5_chunk_1 (never executed)\n\n-- do index scan when using WHERE\n:PREFIX SELECT last(temp, time) FROM btest WHERE time <= '2017-01-20T09:00:02';\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" <= 'Fri Jan 20 09:00:02 2017'::timestamp without time zone)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n\n-- can't do index scan for MAX and LAST combined (MinMax optimization fails when having different aggregate functions)\n:PREFIX SELECT max(time), last(temp, time) FROM btest;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=11.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n         ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n-- can't do index scan when using FIRST/LAST in ORDER BY\n:PREFIX SELECT last(temp, time) FROM btest ORDER BY last(temp, time);\n--- QUERY PLAN ---\n Sort (actual rows=1.00 loops=1)\n   Sort Key: (last(btest.temp, btest.\"time\"))\n   Sort Method: quicksort \n   ->  Aggregate (actual rows=1.00 loops=1)\n         ->  Append (actual rows=11.00 loops=1)\n               ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n               ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n-- do index scan\n:PREFIX SELECT last(temp, time) FROM btest WHERE temp < 30;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=0.00 loops=1)\n                       Filter: (temp < '30'::double precision)\n                       Rows Removed by Filter: 1\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (actual rows=0.00 loops=1)\n                       Filter: (temp < '30'::double precision)\n                       Rows Removed by Filter: 1\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (actual rows=0.00 loops=1)\n                       Filter: (temp < '30'::double precision)\n                       Rows Removed by Filter: 2\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n                       Filter: (temp < '30'::double precision)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n                       Filter: (temp < '30'::double precision)\n\n-- SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n-- do index scan\n:PREFIX SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\"\n                 ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (\"time\" >= 'Fri Jan 20 09:00:47 2017'::timestamp without time zone)\n                 ->  Index Scan Backward using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                 ->  Index Scan Backward using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                 ->  Index Scan Backward using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (never executed)\n\n-- can't do index scan when using WINDOW function\n:PREFIX SELECT gp, last(temp, time) OVER (PARTITION BY gp) AS last FROM btest;\n--- QUERY PLAN ---\n WindowAgg (actual rows=11.00 loops=1)\n   ->  Sort (actual rows=11.00 loops=1)\n         Sort Key: btest.gp\n         Sort Method: quicksort \n         ->  Append (actual rows=11.00 loops=1)\n               ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n               ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\n-- test constants\n:PREFIX SELECT first(100, 100) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Result (actual rows=1.00 loops=1)\n                 ->  Append (actual rows=1.00 loops=1)\n                       ->  Seq Scan on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n                       ->  Seq Scan on _hyper_1_2_chunk (never executed)\n                       ->  Seq Scan on _hyper_1_3_chunk (never executed)\n                       ->  Seq Scan on _hyper_1_4_chunk (never executed)\n                       ->  Seq Scan on _hyper_1_5_chunk (never executed)\n\n-- create an index so we can test optimization\nCREATE INDEX btest_time_alt_idx ON btest(time_alt);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Merge Append (actual rows=1.00 loops=1)\n                 Sort Key: btest.time_alt DESC\n                 ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_alt_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (time_alt IS NOT NULL)\n                 ->  Index Scan Backward using _hyper_1_2_chunk_btest_time_alt_idx on _hyper_1_2_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (time_alt IS NOT NULL)\n                 ->  Index Scan Backward using _hyper_1_3_chunk_btest_time_alt_idx on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (time_alt IS NOT NULL)\n                 ->  Index Scan Backward using _hyper_1_4_chunk_btest_time_alt_idx on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (time_alt IS NOT NULL)\n                 ->  Index Scan Backward using _hyper_1_5_chunk_btest_time_alt_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                       Index Cond: (time_alt IS NOT NULL)\n\n--test nested FIRST/LAST - should optimize\n:PREFIX SELECT abs(last(temp, time)) FROM btest;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)\n                 Order: btest.\"time\" DESC\n                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)\n                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)\n                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)\n                 ->  Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed)\n\n-- test nested FIRST/LAST in ORDER BY - no optimization possible\n:PREFIX SELECT abs(last(temp, time)) FROM btest ORDER BY abs(last(temp,time));\n--- QUERY PLAN ---\n Sort (actual rows=1.00 loops=1)\n   Sort Key: (abs(last(btest.temp, btest.\"time\")))\n   Sort Method: quicksort \n   ->  Aggregate (actual rows=1.00 loops=1)\n         ->  Append (actual rows=11.00 loops=1)\n               ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)\n               ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)\n               ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)\n\nROLLBACK;\n-- Test with NULL numeric values\nBEGIN;\nTRUNCATE btest_numeric;\n-- Empty table\n:PREFIX SELECT first(btest_numeric, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Result (actual rows=0.00 loops=1)\n         One-Time Filter: false\n\n:PREFIX SELECT last(btest_numeric, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Result (actual rows=0.00 loops=1)\n         One-Time Filter: false\n\n-- Only NULL values\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Index Scan Backward using _hyper_2_8_chunk_btest_numeric_time_idx on _hyper_2_8_chunk (actual rows=1.00 loops=1)\n\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Index Scan using _hyper_2_8_chunk_btest_numeric_time_idx on _hyper_2_8_chunk (actual rows=1.00 loops=1)\n\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_2_8_chunk (actual rows=2.00 loops=1)\n\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_2_8_chunk (actual rows=2.00 loops=1)\n\n-- NULL values followed by non-NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest_numeric (actual rows=1.00 loops=1)\n                 Order: btest_numeric.\"time\"\n                 ->  Index Scan Backward using _hyper_2_8_chunk_btest_numeric_time_idx on _hyper_2_8_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan Backward using _hyper_2_9_chunk_btest_numeric_time_idx on _hyper_2_9_chunk (never executed)\n\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest_numeric (actual rows=1.00 loops=1)\n                 Order: btest_numeric.\"time\" DESC\n                 ->  Index Scan using _hyper_2_9_chunk_btest_numeric_time_idx on _hyper_2_9_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan using _hyper_2_8_chunk_btest_numeric_time_idx on _hyper_2_8_chunk (never executed)\n\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=4.00 loops=1)\n         ->  Seq Scan on _hyper_2_8_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_2_9_chunk (actual rows=2.00 loops=1)\n\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=4.00 loops=1)\n         ->  Seq Scan on _hyper_2_8_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_2_9_chunk (actual rows=2.00 loops=1)\n\nTRUNCATE btest_numeric;\n-- non-NULL values followed by NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest_numeric (actual rows=1.00 loops=1)\n                 Order: btest_numeric.\"time\"\n                 ->  Index Scan Backward using _hyper_2_11_chunk_btest_numeric_time_idx on _hyper_2_11_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan Backward using _hyper_2_10_chunk_btest_numeric_time_idx on _hyper_2_10_chunk (never executed)\n\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   InitPlan 1\n     ->  Limit (actual rows=1.00 loops=1)\n           ->  Custom Scan (ChunkAppend) on btest_numeric (actual rows=1.00 loops=1)\n                 Order: btest_numeric.\"time\" DESC\n                 ->  Index Scan using _hyper_2_10_chunk_btest_numeric_time_idx on _hyper_2_10_chunk (actual rows=1.00 loops=1)\n                 ->  Index Scan using _hyper_2_11_chunk_btest_numeric_time_idx on _hyper_2_11_chunk (never executed)\n\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=4.00 loops=1)\n         ->  Seq Scan on _hyper_2_10_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_2_11_chunk (actual rows=2.00 loops=1)\n\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n--- QUERY PLAN ---\n Aggregate (actual rows=1.00 loops=1)\n   ->  Append (actual rows=4.00 loops=1)\n         ->  Seq Scan on _hyper_2_10_chunk (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_2_11_chunk (actual rows=2.00 loops=1)\n\nROLLBACK;\n-- we want test results as part of the output too to make sure we produce correct output\n\\set PREFIX ''\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- canary for results diff\n-- this should be only output of results diff\nSELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations')) v(setting);\n             setting              | value \n----------------------------------+-------\n timescaledb.enable_optimizations | on\n\n:PREFIX SELECT time, gp, temp FROM btest ORDER BY time;\n           time           | gp | temp \n--------------------------+----+------\n Fri Jan 20 09:00:01 2017 |  1 | 22.5\n Fri Jan 20 09:00:02 2017 |  2 | 35.5\n Fri Jan 20 09:00:21 2017 |  1 | 21.2\n Fri Jan 20 09:00:21 2017 |  2 | 30.2\n Fri Jan 20 09:00:43 2017 |  2 | 20.1\n Fri Jan 20 09:00:47 2017 |  1 | 25.1\n\n:PREFIX SELECT last(temp, time) FROM btest;\n last \n------\n 25.1\n\n:PREFIX SELECT first(temp, time) FROM btest;\n first \n-------\n  22.5\n\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n last \n------\n 22.5\n\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n first \n-------\n  30.2\n\n:PREFIX SELECT gp, last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n gp | last \n----+------\n  1 | 25.1\n  2 | 20.1\n\n:PREFIX SELECT gp, first(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n gp | first \n----+-------\n  1 |  22.5\n  2 |  35.5\n\n--check whole row\n:PREFIX SELECT gp, first(btest, time) FROM btest GROUP BY gp ORDER BY gp;\n gp |                                 first                                  \n----+------------------------------------------------------------------------\n  1 | (\"Fri Jan 20 09:00:01 2017\",\"Fri Jan 20 10:00:00 2017\",1,22.5,testing)\n  2 | (\"Fri Jan 20 09:00:02 2017\",\"Fri Jan 20 09:00:57 2017\",2,35.5,testing)\n\n--check toasted col\n:PREFIX SELECT gp, left(last(strid, time), 10) FROM btest GROUP BY gp ORDER BY gp;\n gp |    left    \n----+------------\n  1 | testing\n  2 | xyzxyzxyzx\n\n:PREFIX SELECT gp, last(temp, strid) FROM btest GROUP BY gp ORDER BY gp;\n gp | last \n----+------\n  1 | 22.5\n  2 | 20.1\n\n:PREFIX SELECT gp, last(strid, temp) FROM btest GROUP BY gp ORDER BY gp;\n gp |  last   \n----+---------\n  1 | testing\n  2 | testing\n\nBEGIN;\n--check null value as last element\nINSERT INTO btest VALUES('2018-01-20T09:00:43', '2017-01-20T09:00:55', 2, NULL);\n:PREFIX SELECT last(temp, time) FROM btest;\n last \n------\n     \n\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest VALUES('2019-01-20T09:00:43', '2018-01-20T09:00:55', 2, 30.5);\n:PREFIX SELECT last(temp, time) FROM btest;\n last \n------\n 30.5\n\n--check null cmp element is skipped\nINSERT INTO btest VALUES('2018-01-20T09:00:43', NULL, 2, 32.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n last \n------\n 30.5\n\n-- fist returns NULL value\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n first \n-------\n      \n\n-- test first return non NULL value\nINSERT INTO btest VALUES('2016-01-20T09:00:00', '2016-01-20T09:00:00', 2, 36.5);\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n first \n-------\n  36.5\n\n--check non null cmp element insert after null cmp\nINSERT INTO btest VALUES('2020-01-20T09:00:43', '2020-01-20T09:00:43', 2, 35.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n last \n------\n 35.3\n\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n first \n-------\n  36.5\n\n--cmp nulls should be ignored and not present in groups\n:PREFIX SELECT gp, last(temp, time_alt) FROM btest GROUP BY gp ORDER BY gp;\n gp | last \n----+------\n  1 | 22.5\n  2 | 35.3\n\n--Previously, some bugs were found with NULLS and numeric types, so test that\nINSERT INTO btest_numeric VALUES ('2019-01-20T09:00:43', NULL);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n last \n------\n     \n\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest_numeric VALUES('2020-01-20T09:00:43', 30.5);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n last \n------\n 30.5\n\n-- do index scan for last\n:PREFIX SELECT last(temp, time) FROM btest;\n last \n------\n 35.3\n\n-- do index scan for first\n:PREFIX SELECT first(temp, time) FROM btest;\n first \n-------\n  36.5\n\n-- can't do index scan when ordering on non-index column\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n first \n-------\n  36.5\n\n-- do index scan for subquery\n:PREFIX SELECT * FROM (SELECT last(temp, time) FROM btest) last;\n last \n------\n 35.3\n\n-- can't do index scan when using group by\n:PREFIX SELECT last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n last \n------\n 25.1\n 35.3\n\n-- do index scan when agg function is used in CTE subquery\n:PREFIX WITH last_temp AS (SELECT last(temp, time) FROM btest) SELECT * from last_temp;\n last \n------\n 35.3\n\n-- do index scan when using both FIRST and LAST aggregate functions\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n first | last \n-------+------\n  36.5 | 35.3\n\n-- verify results when using both FIRST and LAST\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n first | last \n-------+------\n  36.5 | 35.3\n\n-- do index scan when using WHERE\n:PREFIX SELECT last(temp, time) FROM btest WHERE time <= '2017-01-20T09:00:02';\n last \n------\n 35.5\n\n-- can't do index scan for MAX and LAST combined (MinMax optimization fails when having different aggregate functions)\n:PREFIX SELECT max(time), last(temp, time) FROM btest;\n           max            | last \n--------------------------+------\n Mon Jan 20 09:00:43 2020 | 35.3\n\n-- can't do index scan when using FIRST/LAST in ORDER BY\n:PREFIX SELECT last(temp, time) FROM btest ORDER BY last(temp, time);\n last \n------\n 35.3\n\n-- do index scan\n:PREFIX SELECT last(temp, time) FROM btest WHERE temp < 30;\n last \n------\n 25.1\n\n-- SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n-- do index scan\n:PREFIX SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n first \n-------\n  25.1\n\n-- can't do index scan when using WINDOW function\n:PREFIX SELECT gp, last(temp, time) OVER (PARTITION BY gp) AS last FROM btest;\n gp | last \n----+------\n  1 | 25.1\n  1 | 25.1\n  1 | 25.1\n  2 | 35.3\n  2 | 35.3\n  2 | 35.3\n  2 | 35.3\n  2 | 35.3\n  2 | 35.3\n  2 | 35.3\n  2 | 35.3\n\n-- test constants\n:PREFIX SELECT first(100, 100) FROM btest;\n first \n-------\n   100\n\n-- create an index so we can test optimization\nCREATE INDEX btest_time_alt_idx ON btest(time_alt);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n last \n------\n 35.3\n\n--test nested FIRST/LAST - should optimize\n:PREFIX SELECT abs(last(temp, time)) FROM btest;\n abs  \n------\n 35.3\n\n-- test nested FIRST/LAST in ORDER BY - no optimization possible\n:PREFIX SELECT abs(last(temp, time)) FROM btest ORDER BY abs(last(temp,time));\n abs  \n------\n 35.3\n\nROLLBACK;\n-- Test with NULL numeric values\nBEGIN;\nTRUNCATE btest_numeric;\n-- Empty table\n:PREFIX SELECT first(btest_numeric, time) FROM btest_numeric;\n first \n-------\n \n\n:PREFIX SELECT last(btest_numeric, time) FROM btest_numeric;\n last \n------\n \n\n-- Only NULL values\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n first \n-------\n      \n\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n last \n------\n     \n\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n first \n-------\n \n\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n last \n------\n \n\n-- NULL values followed by non-NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n first \n-------\n      \n\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n last \n------\n    1\n\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n          first           \n--------------------------\n Sun Jan 20 09:00:43 2019\n\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n           last           \n--------------------------\n Sun Jan 20 09:00:43 2019\n\nTRUNCATE btest_numeric;\n-- non-NULL values followed by NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n first \n-------\n      \n\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n last \n------\n    1\n\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n          first           \n--------------------------\n Sun Jan 20 09:00:43 2019\n\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n           last           \n--------------------------\n Sun Jan 20 09:00:43 2019\n\nROLLBACK;\n-- diff results with optimizations disabled and enabled\n\\o :TEST_RESULTS_UNOPTIMIZED\nSET timescaledb.enable_optimizations TO false;\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- canary for results diff\n-- this should be only output of results diff\nSELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations')) v(setting);\n:PREFIX SELECT time, gp, temp FROM btest ORDER BY time;\n:PREFIX SELECT last(temp, time) FROM btest;\n:PREFIX SELECT first(temp, time) FROM btest;\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n:PREFIX SELECT gp, last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, first(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n--check whole row\n:PREFIX SELECT gp, first(btest, time) FROM btest GROUP BY gp ORDER BY gp;\n--check toasted col\n:PREFIX SELECT gp, left(last(strid, time), 10) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, last(temp, strid) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, last(strid, temp) FROM btest GROUP BY gp ORDER BY gp;\nBEGIN;\n--check null value as last element\nINSERT INTO btest VALUES('2018-01-20T09:00:43', '2017-01-20T09:00:55', 2, NULL);\n:PREFIX SELECT last(temp, time) FROM btest;\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest VALUES('2019-01-20T09:00:43', '2018-01-20T09:00:55', 2, 30.5);\n:PREFIX SELECT last(temp, time) FROM btest;\n--check null cmp element is skipped\nINSERT INTO btest VALUES('2018-01-20T09:00:43', NULL, 2, 32.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n-- fist returns NULL value\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n-- test first return non NULL value\nINSERT INTO btest VALUES('2016-01-20T09:00:00', '2016-01-20T09:00:00', 2, 36.5);\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--check non null cmp element insert after null cmp\nINSERT INTO btest VALUES('2020-01-20T09:00:43', '2020-01-20T09:00:43', 2, 35.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--cmp nulls should be ignored and not present in groups\n:PREFIX SELECT gp, last(temp, time_alt) FROM btest GROUP BY gp ORDER BY gp;\n--Previously, some bugs were found with NULLS and numeric types, so test that\nINSERT INTO btest_numeric VALUES ('2019-01-20T09:00:43', NULL);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest_numeric VALUES('2020-01-20T09:00:43', 30.5);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n-- do index scan for last\n:PREFIX SELECT last(temp, time) FROM btest;\n-- do index scan for first\n:PREFIX SELECT first(temp, time) FROM btest;\n-- can't do index scan when ordering on non-index column\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n-- do index scan for subquery\n:PREFIX SELECT * FROM (SELECT last(temp, time) FROM btest) last;\n-- can't do index scan when using group by\n:PREFIX SELECT last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n-- do index scan when agg function is used in CTE subquery\n:PREFIX WITH last_temp AS (SELECT last(temp, time) FROM btest) SELECT * from last_temp;\n-- do index scan when using both FIRST and LAST aggregate functions\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n-- verify results when using both FIRST and LAST\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n-- do index scan when using WHERE\n:PREFIX SELECT last(temp, time) FROM btest WHERE time <= '2017-01-20T09:00:02';\n-- can't do index scan for MAX and LAST combined (MinMax optimization fails when having different aggregate functions)\n:PREFIX SELECT max(time), last(temp, time) FROM btest;\n-- can't do index scan when using FIRST/LAST in ORDER BY\n:PREFIX SELECT last(temp, time) FROM btest ORDER BY last(temp, time);\n-- do index scan\n:PREFIX SELECT last(temp, time) FROM btest WHERE temp < 30;\n-- SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n-- do index scan\n:PREFIX SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n-- can't do index scan when using WINDOW function\n:PREFIX SELECT gp, last(temp, time) OVER (PARTITION BY gp) AS last FROM btest;\n-- test constants\n:PREFIX SELECT first(100, 100) FROM btest;\n-- create an index so we can test optimization\nCREATE INDEX btest_time_alt_idx ON btest(time_alt);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n--test nested FIRST/LAST - should optimize\n:PREFIX SELECT abs(last(temp, time)) FROM btest;\n-- test nested FIRST/LAST in ORDER BY - no optimization possible\n:PREFIX SELECT abs(last(temp, time)) FROM btest ORDER BY abs(last(temp,time));\nROLLBACK;\n-- Test with NULL numeric values\nBEGIN;\nTRUNCATE btest_numeric;\n-- Empty table\n:PREFIX SELECT first(btest_numeric, time) FROM btest_numeric;\n:PREFIX SELECT last(btest_numeric, time) FROM btest_numeric;\n-- Only NULL values\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n-- NULL values followed by non-NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\nTRUNCATE btest_numeric;\n-- non-NULL values followed by NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\nROLLBACK;\n\\o\n\\o :TEST_RESULTS_OPTIMIZED\nSET timescaledb.enable_optimizations TO true;\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- canary for results diff\n-- this should be only output of results diff\nSELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations')) v(setting);\n:PREFIX SELECT time, gp, temp FROM btest ORDER BY time;\n:PREFIX SELECT last(temp, time) FROM btest;\n:PREFIX SELECT first(temp, time) FROM btest;\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n:PREFIX SELECT gp, last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, first(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n--check whole row\n:PREFIX SELECT gp, first(btest, time) FROM btest GROUP BY gp ORDER BY gp;\n--check toasted col\n:PREFIX SELECT gp, left(last(strid, time), 10) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, last(temp, strid) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, last(strid, temp) FROM btest GROUP BY gp ORDER BY gp;\nBEGIN;\n--check null value as last element\nINSERT INTO btest VALUES('2018-01-20T09:00:43', '2017-01-20T09:00:55', 2, NULL);\n:PREFIX SELECT last(temp, time) FROM btest;\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest VALUES('2019-01-20T09:00:43', '2018-01-20T09:00:55', 2, 30.5);\n:PREFIX SELECT last(temp, time) FROM btest;\n--check null cmp element is skipped\nINSERT INTO btest VALUES('2018-01-20T09:00:43', NULL, 2, 32.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n-- fist returns NULL value\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n-- test first return non NULL value\nINSERT INTO btest VALUES('2016-01-20T09:00:00', '2016-01-20T09:00:00', 2, 36.5);\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--check non null cmp element insert after null cmp\nINSERT INTO btest VALUES('2020-01-20T09:00:43', '2020-01-20T09:00:43', 2, 35.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--cmp nulls should be ignored and not present in groups\n:PREFIX SELECT gp, last(temp, time_alt) FROM btest GROUP BY gp ORDER BY gp;\n--Previously, some bugs were found with NULLS and numeric types, so test that\nINSERT INTO btest_numeric VALUES ('2019-01-20T09:00:43', NULL);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest_numeric VALUES('2020-01-20T09:00:43', 30.5);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n-- do index scan for last\n:PREFIX SELECT last(temp, time) FROM btest;\n-- do index scan for first\n:PREFIX SELECT first(temp, time) FROM btest;\n-- can't do index scan when ordering on non-index column\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n-- do index scan for subquery\n:PREFIX SELECT * FROM (SELECT last(temp, time) FROM btest) last;\n-- can't do index scan when using group by\n:PREFIX SELECT last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n-- do index scan when agg function is used in CTE subquery\n:PREFIX WITH last_temp AS (SELECT last(temp, time) FROM btest) SELECT * from last_temp;\n-- do index scan when using both FIRST and LAST aggregate functions\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n-- verify results when using both FIRST and LAST\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n-- do index scan when using WHERE\n:PREFIX SELECT last(temp, time) FROM btest WHERE time <= '2017-01-20T09:00:02';\n-- can't do index scan for MAX and LAST combined (MinMax optimization fails when having different aggregate functions)\n:PREFIX SELECT max(time), last(temp, time) FROM btest;\n-- can't do index scan when using FIRST/LAST in ORDER BY\n:PREFIX SELECT last(temp, time) FROM btest ORDER BY last(temp, time);\n-- do index scan\n:PREFIX SELECT last(temp, time) FROM btest WHERE temp < 30;\n-- SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n-- do index scan\n:PREFIX SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n-- can't do index scan when using WINDOW function\n:PREFIX SELECT gp, last(temp, time) OVER (PARTITION BY gp) AS last FROM btest;\n-- test constants\n:PREFIX SELECT first(100, 100) FROM btest;\n-- create an index so we can test optimization\nCREATE INDEX btest_time_alt_idx ON btest(time_alt);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n--test nested FIRST/LAST - should optimize\n:PREFIX SELECT abs(last(temp, time)) FROM btest;\n-- test nested FIRST/LAST in ORDER BY - no optimization possible\n:PREFIX SELECT abs(last(temp, time)) FROM btest ORDER BY abs(last(temp,time));\nROLLBACK;\n-- Test with NULL numeric values\nBEGIN;\nTRUNCATE btest_numeric;\n-- Empty table\n:PREFIX SELECT first(btest_numeric, time) FROM btest_numeric;\n:PREFIX SELECT last(btest_numeric, time) FROM btest_numeric;\n-- Only NULL values\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n-- NULL values followed by non-NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\nTRUNCATE btest_numeric;\n-- non-NULL values followed by NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\nROLLBACK;\n\\o\n:DIFF_CMD\n--- Unoptimized result\n+++ Optimized result\n@@ -1,6 +1,6 @@\n              setting              | value \n ----------------------------------+-------\n- timescaledb.enable_optimizations | off\n+ timescaledb.enable_optimizations | on\n \n            time           | gp | temp \n-- Test partial aggregation\nCREATE TABLE partial_aggregation (time timestamptz NOT NULL, quantity numeric, longvalue text);\nSELECT schema_name, table_name, created FROM create_hypertable('partial_aggregation', 'time');\n schema_name |     table_name      | created \n-------------+---------------------+---------\n public      | partial_aggregation | t\n\nINSERT INTO partial_aggregation VALUES('2018-01-20T09:00:43', NULL, NULL);\nINSERT INTO partial_aggregation VALUES('2018-01-20T09:00:44', NULL, NULL);\nINSERT INTO partial_aggregation VALUES('2019-01-20T09:00:43', 1, 'hello');\nINSERT INTO partial_aggregation VALUES('2019-01-20T09:00:44', 2, 'world');\nINSERT INTO partial_aggregation VALUES('2020-01-20T09:00:43', 3.1, 'some1');\nINSERT INTO partial_aggregation VALUES('2020-01-20T09:00:44', 3.2, 'more1');\nINSERT INTO partial_aggregation VALUES('2021-01-20T09:00:43', 3.3, 'some2');\nINSERT INTO partial_aggregation VALUES('2021-01-20T09:00:44', 3.4, 'more2');\nINSERT INTO partial_aggregation VALUES('2022-01-20T09:00:43', 4, 'word1');\nINSERT INTO partial_aggregation VALUES('2022-01-20T09:00:44', 5, 'word2');\nINSERT INTO partial_aggregation VALUES('2023-01-20T09:00:43', 6, 'word3');\nINSERT INTO partial_aggregation VALUES('2023-01-20T09:00:44', 7, 'word4');\n-- Use enable_partitionwise_aggregate to create partial aggregates per chunk\nSET enable_partitionwise_aggregate = ON;\nSELECT\n    format('SELECT %3$s, %1$s FROM partial_aggregation WHERE %2$s GROUP BY %3$s ORDER BY 1, 2;',\n            function, condition, grouping)\nFROM\n    unnest(array[\n            'first(time, quantity), last(time, quantity)',\n            'last(longvalue, quantity)',\n            'last(quantity, longvalue)',\n            'last(quantity, time)',\n            'last(time, longvalue)']) AS function,\n    unnest(array[\n            'true',\n            $$time < '2021-01-01'$$,\n            'quantity is null',\n            'quantity is not null',\n            'quantity >= 4']) AS condition,\n    unnest(array[\n            '777::text' /* dummy grouping column */,\n            'longvalue',\n            'quantity',\n            $$time_bucket('1 year', time)$$,\n            $$time_bucket('3 year', time)$$]) AS grouping\n\\gexec\nSELECT 777::text, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE true GROUP BY 777::text ORDER BY 1, 2;\n text |            first             |             last             \n------+------------------------------+------------------------------\n 777  | Sun Jan 20 09:00:43 2019 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT 777::text, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY 777::text ORDER BY 1, 2;\n text |            first             |             last             \n------+------------------------------+------------------------------\n 777  | Sun Jan 20 09:00:43 2019 PST | Mon Jan 20 09:00:44 2020 PST\n\nSELECT 777::text, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY 777::text ORDER BY 1, 2;\n text | first | last \n------+-------+------\n 777  |       | \n\nSELECT 777::text, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY 777::text ORDER BY 1, 2;\n text |            first             |             last             \n------+------------------------------+------------------------------\n 777  | Sun Jan 20 09:00:43 2019 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT 777::text, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY 777::text ORDER BY 1, 2;\n text |            first             |             last             \n------+------------------------------+------------------------------\n 777  | Thu Jan 20 09:00:43 2022 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT 777::text, last(longvalue, quantity) FROM partial_aggregation WHERE true GROUP BY 777::text ORDER BY 1, 2;\n text | last  \n------+-------\n 777  | word4\n\nSELECT 777::text, last(longvalue, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY 777::text ORDER BY 1, 2;\n text | last  \n------+-------\n 777  | more1\n\nSELECT 777::text, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  | \n\nSELECT 777::text, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY 777::text ORDER BY 1, 2;\n text | last  \n------+-------\n 777  | word4\n\nSELECT 777::text, last(longvalue, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY 777::text ORDER BY 1, 2;\n text | last  \n------+-------\n 777  | word4\n\nSELECT 777::text, last(quantity, longvalue) FROM partial_aggregation WHERE true GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    2\n\nSELECT 777::text, last(quantity, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    2\n\nSELECT 777::text, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |     \n\nSELECT 777::text, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    2\n\nSELECT 777::text, last(quantity, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    7\n\nSELECT 777::text, last(quantity, time) FROM partial_aggregation WHERE true GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    7\n\nSELECT 777::text, last(quantity, time) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |  3.2\n\nSELECT 777::text, last(quantity, time) FROM partial_aggregation WHERE quantity is null GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |     \n\nSELECT 777::text, last(quantity, time) FROM partial_aggregation WHERE quantity is not null GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    7\n\nSELECT 777::text, last(quantity, time) FROM partial_aggregation WHERE quantity >= 4 GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  |    7\n\nSELECT 777::text, last(time, longvalue) FROM partial_aggregation WHERE true GROUP BY 777::text ORDER BY 1, 2;\n text |             last             \n------+------------------------------\n 777  | Sun Jan 20 09:00:44 2019 PST\n\nSELECT 777::text, last(time, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY 777::text ORDER BY 1, 2;\n text |             last             \n------+------------------------------\n 777  | Sun Jan 20 09:00:44 2019 PST\n\nSELECT 777::text, last(time, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY 777::text ORDER BY 1, 2;\n text | last \n------+------\n 777  | \n\nSELECT 777::text, last(time, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY 777::text ORDER BY 1, 2;\n text |             last             \n------+------------------------------\n 777  | Sun Jan 20 09:00:44 2019 PST\n\nSELECT 777::text, last(time, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY 777::text ORDER BY 1, 2;\n text |             last             \n------+------------------------------\n 777  | Fri Jan 20 09:00:44 2023 PST\n\nSELECT longvalue, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE true GROUP BY longvalue ORDER BY 1, 2;\n longvalue |            first             |             last             \n-----------+------------------------------+------------------------------\n hello     | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST\n more1     | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST\n more2     | Wed Jan 20 09:00:44 2021 PST | Wed Jan 20 09:00:44 2021 PST\n some1     | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST\n some2     | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:43 2021 PST\n word1     | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST\n word2     | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST\n word3     | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST\n word4     | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST\n world     | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST\n           |                              | \n\nSELECT longvalue, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY longvalue ORDER BY 1, 2;\n longvalue |            first             |             last             \n-----------+------------------------------+------------------------------\n hello     | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST\n more1     | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST\n some1     | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST\n world     | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST\n           |                              | \n\nSELECT longvalue, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | first | last \n-----------+-------+------\n           |       | \n\nSELECT longvalue, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY longvalue ORDER BY 1, 2;\n longvalue |            first             |             last             \n-----------+------------------------------+------------------------------\n hello     | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST\n more1     | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST\n more2     | Wed Jan 20 09:00:44 2021 PST | Wed Jan 20 09:00:44 2021 PST\n some1     | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST\n some2     | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:43 2021 PST\n word1     | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST\n word2     | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST\n word3     | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST\n word4     | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST\n world     | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST\n\nSELECT longvalue, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY longvalue ORDER BY 1, 2;\n longvalue |            first             |             last             \n-----------+------------------------------+------------------------------\n word1     | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST\n word2     | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST\n word3     | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST\n word4     | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT longvalue, last(longvalue, quantity) FROM partial_aggregation WHERE true GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last  \n-----------+-------\n hello     | hello\n more1     | more1\n more2     | more2\n some1     | some1\n some2     | some2\n word1     | word1\n word2     | word2\n word3     | word3\n word4     | word4\n world     | world\n           | \n\nSELECT longvalue, last(longvalue, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last  \n-----------+-------\n hello     | hello\n more1     | more1\n some1     | some1\n world     | world\n           | \n\nSELECT longvalue, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n           | \n\nSELECT longvalue, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last  \n-----------+-------\n hello     | hello\n more1     | more1\n more2     | more2\n some1     | some1\n some2     | some2\n word1     | word1\n word2     | word2\n word3     | word3\n word4     | word4\n world     | world\n\nSELECT longvalue, last(longvalue, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last  \n-----------+-------\n word1     | word1\n word2     | word2\n word3     | word3\n word4     | word4\n\nSELECT longvalue, last(quantity, longvalue) FROM partial_aggregation WHERE true GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n hello     |    1\n more1     |  3.2\n more2     |  3.4\n some1     |  3.1\n some2     |  3.3\n word1     |    4\n word2     |    5\n word3     |    6\n word4     |    7\n world     |    2\n           |     \n\nSELECT longvalue, last(quantity, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n hello     |    1\n more1     |  3.2\n some1     |  3.1\n world     |    2\n           |     \n\nSELECT longvalue, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n           |     \n\nSELECT longvalue, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n hello     |    1\n more1     |  3.2\n more2     |  3.4\n some1     |  3.1\n some2     |  3.3\n word1     |    4\n word2     |    5\n word3     |    6\n word4     |    7\n world     |    2\n\nSELECT longvalue, last(quantity, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n word1     |    4\n word2     |    5\n word3     |    6\n word4     |    7\n\nSELECT longvalue, last(quantity, time) FROM partial_aggregation WHERE true GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n hello     |    1\n more1     |  3.2\n more2     |  3.4\n some1     |  3.1\n some2     |  3.3\n word1     |    4\n word2     |    5\n word3     |    6\n word4     |    7\n world     |    2\n           |     \n\nSELECT longvalue, last(quantity, time) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n hello     |    1\n more1     |  3.2\n some1     |  3.1\n world     |    2\n           |     \n\nSELECT longvalue, last(quantity, time) FROM partial_aggregation WHERE quantity is null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n           |     \n\nSELECT longvalue, last(quantity, time) FROM partial_aggregation WHERE quantity is not null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n hello     |    1\n more1     |  3.2\n more2     |  3.4\n some1     |  3.1\n some2     |  3.3\n word1     |    4\n word2     |    5\n word3     |    6\n word4     |    7\n world     |    2\n\nSELECT longvalue, last(quantity, time) FROM partial_aggregation WHERE quantity >= 4 GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n word1     |    4\n word2     |    5\n word3     |    6\n word4     |    7\n\nSELECT longvalue, last(time, longvalue) FROM partial_aggregation WHERE true GROUP BY longvalue ORDER BY 1, 2;\n longvalue |             last             \n-----------+------------------------------\n hello     | Sun Jan 20 09:00:43 2019 PST\n more1     | Mon Jan 20 09:00:44 2020 PST\n more2     | Wed Jan 20 09:00:44 2021 PST\n some1     | Mon Jan 20 09:00:43 2020 PST\n some2     | Wed Jan 20 09:00:43 2021 PST\n word1     | Thu Jan 20 09:00:43 2022 PST\n word2     | Thu Jan 20 09:00:44 2022 PST\n word3     | Fri Jan 20 09:00:43 2023 PST\n word4     | Fri Jan 20 09:00:44 2023 PST\n world     | Sun Jan 20 09:00:44 2019 PST\n           | \n\nSELECT longvalue, last(time, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY longvalue ORDER BY 1, 2;\n longvalue |             last             \n-----------+------------------------------\n hello     | Sun Jan 20 09:00:43 2019 PST\n more1     | Mon Jan 20 09:00:44 2020 PST\n some1     | Mon Jan 20 09:00:43 2020 PST\n world     | Sun Jan 20 09:00:44 2019 PST\n           | \n\nSELECT longvalue, last(time, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY longvalue ORDER BY 1, 2;\n longvalue | last \n-----------+------\n           | \n\nSELECT longvalue, last(time, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY longvalue ORDER BY 1, 2;\n longvalue |             last             \n-----------+------------------------------\n hello     | Sun Jan 20 09:00:43 2019 PST\n more1     | Mon Jan 20 09:00:44 2020 PST\n more2     | Wed Jan 20 09:00:44 2021 PST\n some1     | Mon Jan 20 09:00:43 2020 PST\n some2     | Wed Jan 20 09:00:43 2021 PST\n word1     | Thu Jan 20 09:00:43 2022 PST\n word2     | Thu Jan 20 09:00:44 2022 PST\n word3     | Fri Jan 20 09:00:43 2023 PST\n word4     | Fri Jan 20 09:00:44 2023 PST\n world     | Sun Jan 20 09:00:44 2019 PST\n\nSELECT longvalue, last(time, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY longvalue ORDER BY 1, 2;\n longvalue |             last             \n-----------+------------------------------\n word1     | Thu Jan 20 09:00:43 2022 PST\n word2     | Thu Jan 20 09:00:44 2022 PST\n word3     | Fri Jan 20 09:00:43 2023 PST\n word4     | Fri Jan 20 09:00:44 2023 PST\n\nSELECT quantity, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE true GROUP BY quantity ORDER BY 1, 2;\n quantity |            first             |             last             \n----------+------------------------------+------------------------------\n        1 | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST\n        2 | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST\n      3.1 | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST\n      3.2 | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST\n      3.3 | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:43 2021 PST\n      3.4 | Wed Jan 20 09:00:44 2021 PST | Wed Jan 20 09:00:44 2021 PST\n        4 | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST\n        5 | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST\n        6 | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST\n        7 | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST\n          |                              | \n\nSELECT quantity, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY quantity ORDER BY 1, 2;\n quantity |            first             |             last             \n----------+------------------------------+------------------------------\n        1 | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST\n        2 | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST\n      3.1 | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST\n      3.2 | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST\n          |                              | \n\nSELECT quantity, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY quantity ORDER BY 1, 2;\n quantity | first | last \n----------+-------+------\n          |       | \n\nSELECT quantity, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY quantity ORDER BY 1, 2;\n quantity |            first             |             last             \n----------+------------------------------+------------------------------\n        1 | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST\n        2 | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST\n      3.1 | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST\n      3.2 | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST\n      3.3 | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:43 2021 PST\n      3.4 | Wed Jan 20 09:00:44 2021 PST | Wed Jan 20 09:00:44 2021 PST\n        4 | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST\n        5 | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST\n        6 | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST\n        7 | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT quantity, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY quantity ORDER BY 1, 2;\n quantity |            first             |             last             \n----------+------------------------------+------------------------------\n        4 | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST\n        5 | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST\n        6 | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST\n        7 | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT quantity, last(longvalue, quantity) FROM partial_aggregation WHERE true GROUP BY quantity ORDER BY 1, 2;\n quantity | last  \n----------+-------\n        1 | hello\n        2 | world\n      3.1 | some1\n      3.2 | more1\n      3.3 | some2\n      3.4 | more2\n        4 | word1\n        5 | word2\n        6 | word3\n        7 | word4\n          | \n\nSELECT quantity, last(longvalue, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY quantity ORDER BY 1, 2;\n quantity | last  \n----------+-------\n        1 | hello\n        2 | world\n      3.1 | some1\n      3.2 | more1\n          | \n\nSELECT quantity, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n          | \n\nSELECT quantity, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY quantity ORDER BY 1, 2;\n quantity | last  \n----------+-------\n        1 | hello\n        2 | world\n      3.1 | some1\n      3.2 | more1\n      3.3 | some2\n      3.4 | more2\n        4 | word1\n        5 | word2\n        6 | word3\n        7 | word4\n\nSELECT quantity, last(longvalue, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY quantity ORDER BY 1, 2;\n quantity | last  \n----------+-------\n        4 | word1\n        5 | word2\n        6 | word3\n        7 | word4\n\nSELECT quantity, last(quantity, longvalue) FROM partial_aggregation WHERE true GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        1 |    1\n        2 |    2\n      3.1 |  3.1\n      3.2 |  3.2\n      3.3 |  3.3\n      3.4 |  3.4\n        4 |    4\n        5 |    5\n        6 |    6\n        7 |    7\n          |     \n\nSELECT quantity, last(quantity, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        1 |    1\n        2 |    2\n      3.1 |  3.1\n      3.2 |  3.2\n          |     \n\nSELECT quantity, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n          |     \n\nSELECT quantity, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        1 |    1\n        2 |    2\n      3.1 |  3.1\n      3.2 |  3.2\n      3.3 |  3.3\n      3.4 |  3.4\n        4 |    4\n        5 |    5\n        6 |    6\n        7 |    7\n\nSELECT quantity, last(quantity, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        4 |    4\n        5 |    5\n        6 |    6\n        7 |    7\n\nSELECT quantity, last(quantity, time) FROM partial_aggregation WHERE true GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        1 |    1\n        2 |    2\n      3.1 |  3.1\n      3.2 |  3.2\n      3.3 |  3.3\n      3.4 |  3.4\n        4 |    4\n        5 |    5\n        6 |    6\n        7 |    7\n          |     \n\nSELECT quantity, last(quantity, time) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        1 |    1\n        2 |    2\n      3.1 |  3.1\n      3.2 |  3.2\n          |     \n\nSELECT quantity, last(quantity, time) FROM partial_aggregation WHERE quantity is null GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n          |     \n\nSELECT quantity, last(quantity, time) FROM partial_aggregation WHERE quantity is not null GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        1 |    1\n        2 |    2\n      3.1 |  3.1\n      3.2 |  3.2\n      3.3 |  3.3\n      3.4 |  3.4\n        4 |    4\n        5 |    5\n        6 |    6\n        7 |    7\n\nSELECT quantity, last(quantity, time) FROM partial_aggregation WHERE quantity >= 4 GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n        4 |    4\n        5 |    5\n        6 |    6\n        7 |    7\n\nSELECT quantity, last(time, longvalue) FROM partial_aggregation WHERE true GROUP BY quantity ORDER BY 1, 2;\n quantity |             last             \n----------+------------------------------\n        1 | Sun Jan 20 09:00:43 2019 PST\n        2 | Sun Jan 20 09:00:44 2019 PST\n      3.1 | Mon Jan 20 09:00:43 2020 PST\n      3.2 | Mon Jan 20 09:00:44 2020 PST\n      3.3 | Wed Jan 20 09:00:43 2021 PST\n      3.4 | Wed Jan 20 09:00:44 2021 PST\n        4 | Thu Jan 20 09:00:43 2022 PST\n        5 | Thu Jan 20 09:00:44 2022 PST\n        6 | Fri Jan 20 09:00:43 2023 PST\n        7 | Fri Jan 20 09:00:44 2023 PST\n          | \n\nSELECT quantity, last(time, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY quantity ORDER BY 1, 2;\n quantity |             last             \n----------+------------------------------\n        1 | Sun Jan 20 09:00:43 2019 PST\n        2 | Sun Jan 20 09:00:44 2019 PST\n      3.1 | Mon Jan 20 09:00:43 2020 PST\n      3.2 | Mon Jan 20 09:00:44 2020 PST\n          | \n\nSELECT quantity, last(time, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY quantity ORDER BY 1, 2;\n quantity | last \n----------+------\n          | \n\nSELECT quantity, last(time, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY quantity ORDER BY 1, 2;\n quantity |             last             \n----------+------------------------------\n        1 | Sun Jan 20 09:00:43 2019 PST\n        2 | Sun Jan 20 09:00:44 2019 PST\n      3.1 | Mon Jan 20 09:00:43 2020 PST\n      3.2 | Mon Jan 20 09:00:44 2020 PST\n      3.3 | Wed Jan 20 09:00:43 2021 PST\n      3.4 | Wed Jan 20 09:00:44 2021 PST\n        4 | Thu Jan 20 09:00:43 2022 PST\n        5 | Thu Jan 20 09:00:44 2022 PST\n        6 | Fri Jan 20 09:00:43 2023 PST\n        7 | Fri Jan 20 09:00:44 2023 PST\n\nSELECT quantity, last(time, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY quantity ORDER BY 1, 2;\n quantity |             last             \n----------+------------------------------\n        4 | Thu Jan 20 09:00:43 2022 PST\n        5 | Thu Jan 20 09:00:44 2022 PST\n        6 | Fri Jan 20 09:00:43 2023 PST\n        7 | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('1 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE true GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST |                              | \n Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:44 2019 PST\n Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:44 2020 PST\n Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:44 2021 PST\n Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:44 2022 PST\n Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('1 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST |                              | \n Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:44 2019 PST\n Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:44 2020 PST\n\nSELECT time_bucket('1 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | first | last \n------------------------------+-------+------\n Sun Dec 31 16:00:00 2017 PST |       | \n\nSELECT time_bucket('1 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:44 2019 PST\n Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:44 2020 PST\n Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:44 2021 PST\n Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:44 2022 PST\n Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('1 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:44 2022 PST\n Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('1 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE true GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Sun Dec 31 16:00:00 2017 PST | \n Mon Dec 31 16:00:00 2018 PST | world\n Tue Dec 31 16:00:00 2019 PST | more1\n Thu Dec 31 16:00:00 2020 PST | more2\n Fri Dec 31 16:00:00 2021 PST | word2\n Sat Dec 31 16:00:00 2022 PST | word4\n\nSELECT time_bucket('1 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Sun Dec 31 16:00:00 2017 PST | \n Mon Dec 31 16:00:00 2018 PST | world\n Tue Dec 31 16:00:00 2019 PST | more1\n\nSELECT time_bucket('1 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST | \n\nSELECT time_bucket('1 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Mon Dec 31 16:00:00 2018 PST | world\n Tue Dec 31 16:00:00 2019 PST | more1\n Thu Dec 31 16:00:00 2020 PST | more2\n Fri Dec 31 16:00:00 2021 PST | word2\n Sat Dec 31 16:00:00 2022 PST | word4\n\nSELECT time_bucket('1 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Fri Dec 31 16:00:00 2021 PST | word2\n Sat Dec 31 16:00:00 2022 PST | word4\n\nSELECT time_bucket('1 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE true GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n Mon Dec 31 16:00:00 2018 PST |    2\n Tue Dec 31 16:00:00 2019 PST |  3.1\n Thu Dec 31 16:00:00 2020 PST |  3.3\n Fri Dec 31 16:00:00 2021 PST |    5\n Sat Dec 31 16:00:00 2022 PST |    7\n\nSELECT time_bucket('1 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n Mon Dec 31 16:00:00 2018 PST |    2\n Tue Dec 31 16:00:00 2019 PST |  3.1\n\nSELECT time_bucket('1 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n\nSELECT time_bucket('1 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Mon Dec 31 16:00:00 2018 PST |    2\n Tue Dec 31 16:00:00 2019 PST |  3.1\n Thu Dec 31 16:00:00 2020 PST |  3.3\n Fri Dec 31 16:00:00 2021 PST |    5\n Sat Dec 31 16:00:00 2022 PST |    7\n\nSELECT time_bucket('1 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Fri Dec 31 16:00:00 2021 PST |    5\n Sat Dec 31 16:00:00 2022 PST |    7\n\nSELECT time_bucket('1 year', time), last(quantity, time) FROM partial_aggregation WHERE true GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n Mon Dec 31 16:00:00 2018 PST |    2\n Tue Dec 31 16:00:00 2019 PST |  3.2\n Thu Dec 31 16:00:00 2020 PST |  3.4\n Fri Dec 31 16:00:00 2021 PST |    5\n Sat Dec 31 16:00:00 2022 PST |    7\n\nSELECT time_bucket('1 year', time), last(quantity, time) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n Mon Dec 31 16:00:00 2018 PST |    2\n Tue Dec 31 16:00:00 2019 PST |  3.2\n\nSELECT time_bucket('1 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n\nSELECT time_bucket('1 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Mon Dec 31 16:00:00 2018 PST |    2\n Tue Dec 31 16:00:00 2019 PST |  3.2\n Thu Dec 31 16:00:00 2020 PST |  3.4\n Fri Dec 31 16:00:00 2021 PST |    5\n Sat Dec 31 16:00:00 2022 PST |    7\n\nSELECT time_bucket('1 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Fri Dec 31 16:00:00 2021 PST |    5\n Sat Dec 31 16:00:00 2022 PST |    7\n\nSELECT time_bucket('1 year', time), last(time, longvalue) FROM partial_aggregation WHERE true GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | \n Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:44 2019 PST\n Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST\n Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST\n Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:44 2022 PST\n Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('1 year', time), last(time, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | \n Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:44 2019 PST\n Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST\n\nSELECT time_bucket('1 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST | \n\nSELECT time_bucket('1 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:44 2019 PST\n Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST\n Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST\n Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:44 2022 PST\n Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('1 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:44 2022 PST\n Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('3 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE true GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:43 2019 PST | Mon Jan 20 09:00:44 2020 PST\n Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('3 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:43 2019 PST | Mon Jan 20 09:00:44 2020 PST\n\nSELECT time_bucket('3 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | first | last \n------------------------------+-------+------\n Sun Dec 31 16:00:00 2017 PST |       | \n\nSELECT time_bucket('3 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:43 2019 PST | Mon Jan 20 09:00:44 2020 PST\n Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('3 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |            first             |             last             \n------------------------------+------------------------------+------------------------------\n Thu Dec 31 16:00:00 2020 PST | Thu Jan 20 09:00:43 2022 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('3 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE true GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Sun Dec 31 16:00:00 2017 PST | more1\n Thu Dec 31 16:00:00 2020 PST | word4\n\nSELECT time_bucket('3 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Sun Dec 31 16:00:00 2017 PST | more1\n\nSELECT time_bucket('3 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST | \n\nSELECT time_bucket('3 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Sun Dec 31 16:00:00 2017 PST | more1\n Thu Dec 31 16:00:00 2020 PST | word4\n\nSELECT time_bucket('3 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last  \n------------------------------+-------\n Thu Dec 31 16:00:00 2020 PST | word4\n\nSELECT time_bucket('3 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE true GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |    2\n Thu Dec 31 16:00:00 2020 PST |    7\n\nSELECT time_bucket('3 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |    2\n\nSELECT time_bucket('3 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n\nSELECT time_bucket('3 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |    2\n Thu Dec 31 16:00:00 2020 PST |    7\n\nSELECT time_bucket('3 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Thu Dec 31 16:00:00 2020 PST |    7\n\nSELECT time_bucket('3 year', time), last(quantity, time) FROM partial_aggregation WHERE true GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |  3.2\n Thu Dec 31 16:00:00 2020 PST |    7\n\nSELECT time_bucket('3 year', time), last(quantity, time) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |  3.2\n\nSELECT time_bucket('3 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |     \n\nSELECT time_bucket('3 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST |  3.2\n Thu Dec 31 16:00:00 2020 PST |    7\n\nSELECT time_bucket('3 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Thu Dec 31 16:00:00 2020 PST |    7\n\nSELECT time_bucket('3 year', time), last(time, longvalue) FROM partial_aggregation WHERE true GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:44 2019 PST\n Thu Dec 31 16:00:00 2020 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('3 year', time), last(time, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:44 2019 PST\n\nSELECT time_bucket('3 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          | last \n------------------------------+------\n Sun Dec 31 16:00:00 2017 PST | \n\nSELECT time_bucket('3 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:44 2019 PST\n Thu Dec 31 16:00:00 2020 PST | Fri Jan 20 09:00:44 2023 PST\n\nSELECT time_bucket('3 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;\n         time_bucket          |             last             \n------------------------------+------------------------------\n Thu Dec 31 16:00:00 2020 PST | Fri Jan 20 09:00:44 2023 PST\n\nSET enable_partitionwise_aggregate = OFF;\n"
  },
  {
    "path": "test/expected/alter.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- Set this variable to avoid using a hard-coded path each time query\n-- results are compared\n\\set QUERY_RESULT_TEST_EQUAL_RELPATH 'include/query_result_test_equal.sql'\n-- DROP a table's column before making it a hypertable\nCREATE TABLE alter_before(id serial, time timestamp, temp float, colorid integer, notes text, notes_2 text);\nALTER TABLE alter_before DROP COLUMN id;\nALTER TABLE alter_before ALTER COLUMN temp SET (n_distinct = 10);\nALTER TABLE alter_before ALTER COLUMN colorid SET (n_distinct = 11);\nALTER TABLE alter_before ALTER COLUMN colorid RESET (n_distinct);\nALTER TABLE alter_before ALTER COLUMN temp SET STATISTICS 100;\nALTER TABLE alter_before ALTER COLUMN notes SET STORAGE EXTERNAL;\nSELECT create_hypertable('alter_before', 'time', chunk_time_interval => 2628000000000);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n     create_hypertable     \n---------------------------\n (1,public,alter_before,t)\n\n-- Test error hint for invalid timescaledb options on ALTER TABLE\n\\set ON_ERROR_STOP 0\n-- Invalid timescaledb option should show hint with valid options\n\\set VERBOSITY default\nALTER TABLE alter_before SET (tsdb.invalid_option = true);\nERROR:  unrecognized parameter \"tsdb.invalid_option\"\nHINT:  Valid timescaledb parameters are: chunk_interval, compress, compress_segmentby, compress_orderby, compress_chunk_interval, compress_index\nALTER TABLE alter_before SET (timescaledb.nonexistent = false);\nERROR:  unrecognized parameter \"timescaledb.nonexistent\"\nHINT:  Valid timescaledb parameters are: chunk_interval, compress, compress_segmentby, compress_orderby, compress_chunk_interval, compress_index\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\nINSERT INTO alter_before VALUES ('2017-03-22T09:18:22', 23.5, 1);\nSELECT * FROM alter_before;\n           time           | temp | colorid | notes | notes_2 \n--------------------------+------+---------+-------+---------\n Wed Mar 22 09:18:22 2017 | 23.5 |       1 |       | \n\n-- Show that deleted column is marked as dropped and that attnums are\n-- now different for the root table and the chunk\n-- PG17 made attstattarget NULLABLE and changed the default from -1 to NULL\nSELECT c.relname, a.attname, a.attnum, a.attoptions, CASE WHEN a.attstattarget = -1 OR (a.attisdropped AND a.attstattarget = 0) THEN NULL ELSE a.attstattarget END attstattarget, a.attstorage FROM pg_attribute a, pg_class c\nWHERE a.attrelid = c.oid\nAND (c.relname LIKE '_hyper_1%_chunk' OR c.relname = 'alter_before')\nAND a.attnum > 0\nORDER BY c.relname, a.attnum;\n     relname      |           attname            | attnum |   attoptions    | attstattarget | attstorage \n------------------+------------------------------+--------+-----------------+---------------+------------\n _hyper_1_1_chunk | time                         |      1 |                 |               | p\n _hyper_1_1_chunk | temp                         |      2 | {n_distinct=10} |           100 | p\n _hyper_1_1_chunk | colorid                      |      3 |                 |               | p\n _hyper_1_1_chunk | notes                        |      4 |                 |               | e\n _hyper_1_1_chunk | notes_2                      |      5 |                 |               | x\n alter_before     | ........pg.dropped.1........ |      1 |                 |               | p\n alter_before     | time                         |      2 |                 |               | p\n alter_before     | temp                         |      3 | {n_distinct=10} |           100 | p\n alter_before     | colorid                      |      4 |                 |               | p\n alter_before     | notes                        |      5 |                 |               | e\n alter_before     | notes_2                      |      6 |                 |               | x\n\n-- DROP a table's column after making it a hypertable and having data\nCREATE TABLE alter_after(id serial, time timestamp, temp float, colorid integer, notes text, notes_2 text);\nSELECT create_hypertable('alter_after', 'time', chunk_time_interval => 2628000000000);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n    create_hypertable     \n--------------------------\n (2,public,alter_after,t)\n\n-- Create first chunk\nINSERT INTO alter_after (time, temp, colorid) VALUES ('2017-03-22T09:18:22', 23.5, 1);\nALTER TABLE alter_after DROP COLUMN id;\nALTER TABLE alter_after ALTER COLUMN temp SET (n_distinct = 10);\nALTER TABLE alter_after ALTER COLUMN colorid SET (n_distinct = 11);\nALTER TABLE alter_after ALTER COLUMN colorid RESET (n_distinct);\nALTER TABLE alter_after ALTER COLUMN colorid SET STATISTICS 101;\nALTER TABLE alter_after ALTER COLUMN notes_2 SET STORAGE EXTERNAL;\n-- Creating new chunks after dropping a column should work just fine\nINSERT INTO alter_after VALUES ('2017-03-22T09:18:23', 21.5, 1),\n                               ('2017-05-22T09:18:22', 36.2, 2),\n                               ('2017-05-22T09:18:23', 15.2, 2);\n-- Make sure tuple conversion also works with COPY\n\\COPY alter_after FROM 'data/alter.tsv' NULL AS '';\n-- Data should look OK\nSELECT * FROM alter_after;\n           time           | temp | colorid | notes | notes_2 \n--------------------------+------+---------+-------+---------\n Wed Mar 22 09:18:22 2017 | 23.5 |       1 |       | \n Wed Mar 22 09:18:23 2017 | 21.5 |       1 |       | \n Mon May 22 09:18:22 2017 | 36.2 |       2 |       | \n Mon May 22 09:18:23 2017 | 15.2 |       2 |       | \n Tue Aug 22 09:19:22 2017 | 21.4 |       3 | nr1   | n2r1\n Wed Aug 23 09:20:17 2017 | 31.5 |       2 | nr2   | n2r2\n\n-- Show that attnums are different for chunks created after DROP\n-- column\nSELECT c.relname, a.attname, a.attnum FROM pg_attribute a, pg_class c\nWHERE a.attrelid = c.oid\nAND (c.relname LIKE '_hyper_2%_chunk' OR c.relname = 'alter_after')\nAND a.attnum > 0\nORDER BY c.relname, a.attnum;\n     relname      |           attname            | attnum \n------------------+------------------------------+--------\n _hyper_2_2_chunk | ........pg.dropped.1........ |      1\n _hyper_2_2_chunk | time                         |      2\n _hyper_2_2_chunk | temp                         |      3\n _hyper_2_2_chunk | colorid                      |      4\n _hyper_2_2_chunk | notes                        |      5\n _hyper_2_2_chunk | notes_2                      |      6\n _hyper_2_3_chunk | time                         |      1\n _hyper_2_3_chunk | temp                         |      2\n _hyper_2_3_chunk | colorid                      |      3\n _hyper_2_3_chunk | notes                        |      4\n _hyper_2_3_chunk | notes_2                      |      5\n _hyper_2_4_chunk | time                         |      1\n _hyper_2_4_chunk | temp                         |      2\n _hyper_2_4_chunk | colorid                      |      3\n _hyper_2_4_chunk | notes                        |      4\n _hyper_2_4_chunk | notes_2                      |      5\n alter_after      | ........pg.dropped.1........ |      1\n alter_after      | time                         |      2\n alter_after      | temp                         |      3\n alter_after      | colorid                      |      4\n alter_after      | notes                        |      5\n alter_after      | notes_2                      |      6\n\n-- Add an ID column again\nALTER TABLE alter_after ADD COLUMN id serial;\nINSERT INTO alter_after (time, temp, colorid) VALUES ('2017-08-22T09:19:14', 12.5, 3);\n--test thing that we are allowed to do on chunks\nALTER TABLE  _timescaledb_internal._hyper_2_3_chunk ALTER COLUMN temp RESET (n_distinct);\nALTER TABLE  _timescaledb_internal._hyper_2_4_chunk ALTER COLUMN temp SET (n_distinct = 20);\nALTER TABLE  _timescaledb_internal._hyper_2_4_chunk ALTER COLUMN temp SET STATISTICS 201;\nALTER TABLE  _timescaledb_internal._hyper_2_4_chunk ALTER COLUMN notes SET STORAGE EXTERNAL;\n-- PG17 made attstattarget NULLABLE and changed the default from -1 to NULL\nSELECT c.relname, a.attname, a.attnum, a.attoptions, CASE WHEN a.attstattarget = -1 OR (a.attisdropped AND a.attstattarget = 0) THEN NULL ELSE a.attstattarget END attstattarget, a.attstorage FROM pg_attribute a, pg_class c\nWHERE a.attrelid = c.oid\nAND (c.relname LIKE '_hyper_2%_chunk' OR c.relname = 'alter_after')\nAND a.attnum > 0\nORDER BY c.relname, a.attnum;\n     relname      |           attname            | attnum |   attoptions    | attstattarget | attstorage \n------------------+------------------------------+--------+-----------------+---------------+------------\n _hyper_2_2_chunk | ........pg.dropped.1........ |      1 |                 |               | p\n _hyper_2_2_chunk | time                         |      2 |                 |               | p\n _hyper_2_2_chunk | temp                         |      3 | {n_distinct=10} |               | p\n _hyper_2_2_chunk | colorid                      |      4 |                 |           101 | p\n _hyper_2_2_chunk | notes                        |      5 |                 |               | x\n _hyper_2_2_chunk | notes_2                      |      6 |                 |               | e\n _hyper_2_2_chunk | id                           |      7 |                 |               | p\n _hyper_2_3_chunk | time                         |      1 |                 |               | p\n _hyper_2_3_chunk | temp                         |      2 |                 |               | p\n _hyper_2_3_chunk | colorid                      |      3 |                 |           101 | p\n _hyper_2_3_chunk | notes                        |      4 |                 |               | x\n _hyper_2_3_chunk | notes_2                      |      5 |                 |               | e\n _hyper_2_3_chunk | id                           |      6 |                 |               | p\n _hyper_2_4_chunk | time                         |      1 |                 |               | p\n _hyper_2_4_chunk | temp                         |      2 | {n_distinct=20} |           201 | p\n _hyper_2_4_chunk | colorid                      |      3 |                 |           101 | p\n _hyper_2_4_chunk | notes                        |      4 |                 |               | e\n _hyper_2_4_chunk | notes_2                      |      5 |                 |               | e\n _hyper_2_4_chunk | id                           |      6 |                 |               | p\n alter_after      | ........pg.dropped.1........ |      1 |                 |               | p\n alter_after      | time                         |      2 |                 |               | p\n alter_after      | temp                         |      3 | {n_distinct=10} |               | p\n alter_after      | colorid                      |      4 |                 |           101 | p\n alter_after      | notes                        |      5 |                 |               | x\n alter_after      | notes_2                      |      6 |                 |               | e\n alter_after      | id                           |      7 |                 |               | p\n\nSELECT * FROM alter_after;\n           time           | temp | colorid | notes | notes_2 | id \n--------------------------+------+---------+-------+---------+----\n Wed Mar 22 09:18:22 2017 | 23.5 |       1 |       |         |  1\n Wed Mar 22 09:18:23 2017 | 21.5 |       1 |       |         |  2\n Mon May 22 09:18:22 2017 | 36.2 |       2 |       |         |  3\n Mon May 22 09:18:23 2017 | 15.2 |       2 |       |         |  4\n Tue Aug 22 09:19:22 2017 | 21.4 |       3 | nr1   | n2r1    |  5\n Wed Aug 23 09:20:17 2017 | 31.5 |       2 | nr2   | n2r2    |  6\n Tue Aug 22 09:19:14 2017 | 12.5 |       3 |       |         |  7\n\n-- test setting reloptions\nALTER TABLE  _timescaledb_internal._hyper_2_3_chunk SET (parallel_workers=2);\nALTER TABLE  _timescaledb_internal._hyper_2_4_chunk SET (parallel_workers=4);\nALTER TABLE  _timescaledb_internal._hyper_2_4_chunk RESET (parallel_workers);\nSELECT relname, reloptions FROM pg_class WHERE relname IN ('_hyper_2_3_chunk','_hyper_2_4_chunk');\n     relname      |      reloptions      \n------------------+----------------------\n _hyper_2_3_chunk | {parallel_workers=2}\n _hyper_2_4_chunk | \n\n-- Need superuser to ALTER chunks in _timescaledb_internal schema\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk WHERE id = 2;\n id | hypertable_id |      schema_name      |    table_name    | compressed_chunk_id | status | osm_chunk \n----+---------------+-----------------------+------------------+---------------------+--------+-----------\n  2 |             2 | _timescaledb_internal | _hyper_2_2_chunk |                     |      0 | f\n\n-- Rename chunk\nALTER TABLE _timescaledb_internal._hyper_2_2_chunk RENAME TO new_chunk_name;\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk WHERE id = 2;\n id | hypertable_id |      schema_name      |   table_name   | compressed_chunk_id | status | osm_chunk \n----+---------------+-----------------------+----------------+---------------------+--------+-----------\n  2 |             2 | _timescaledb_internal | new_chunk_name |                     |      0 | f\n\n-- Set schema\nALTER TABLE _timescaledb_internal.new_chunk_name SET SCHEMA public;\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk WHERE id = 2;\n id | hypertable_id | schema_name |   table_name   | compressed_chunk_id | status | osm_chunk \n----+---------------+-------------+----------------+---------------------+--------+-----------\n  2 |             2 | public      | new_chunk_name |                     |      0 | f\n\n-- Test that we cannot rename chunk columns\n\\set ON_ERROR_STOP 0\nALTER TABLE public.new_chunk_name RENAME COLUMN time TO newtime;\nERROR:  cannot rename column \"time\" of hypertable chunk \"new_chunk_name\"\n\\set ON_ERROR_STOP 1\n-- Test that we can set tablespace of a hypertable\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET client_min_messages = ERROR;\nDROP TABLESPACE IF EXISTS tablespace1;\nDROP TABLESPACE IF EXISTS tablespace2;\nSET client_min_messages = NOTICE;\n--test hypertable with tables space\nCREATE TABLESPACE tablespace1 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE1_PATH;\nCREATE TABLESPACE tablespace2 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE2_PATH;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n-- Test that we can directly change chunk tablespace\nALTER TABLE public.new_chunk_name SET TABLESPACE tablespace1;\nSELECT tablespace FROM pg_tables WHERE tablename = 'new_chunk_name';\n tablespace  \n-------------\n tablespace1\n\n-- drop all tables to make checking the tests below easier\nDROP TABLE alter_before;\nDROP TABLE alter_after;\n-- should return 0 rows\nSELECT tablename, tablespace FROM pg_tables\nWHERE tablename = 'hyper_in_space' OR tablename LIKE '\\_hyper\\__\\__\\_chunk' ORDER BY tablename;\n tablename | tablespace \n-----------+------------\n\nCREATE TABLE hyper_in_space(time bigint, temp float, device int);\nSELECT create_hypertable('hyper_in_space', 'time', 'device', 4, chunk_time_interval=>1);\n      create_hypertable      \n-----------------------------\n (3,public,hyper_in_space,t)\n\nINSERT INTO hyper_in_space(time, temp, device) VALUES (1, 20, 1);\nINSERT INTO hyper_in_space(time, temp, device) VALUES (3, 21, 2);\nINSERT INTO hyper_in_space(time, temp, device) VALUES (5, 23, 1);\nSELECT tablename FROM pg_tables WHERE tablespace = 'tablespace1' ORDER BY tablename;\n tablename \n-----------\n\nSET default_tablespace = tablespace1;\n-- should be inserted in tablespace1 which is now default\nINSERT INTO hyper_in_space(time, temp, device) VALUES (11, 24, 3);\nSELECT tablename, tablespace FROM pg_tables\nWHERE tablename = 'hyper_in_space' OR tablename LIKE '\\_hyper\\__\\__\\_chunk' ORDER BY tablename;\n    tablename     | tablespace  \n------------------+-------------\n _hyper_3_5_chunk | \n _hyper_3_6_chunk | \n _hyper_3_7_chunk | \n _hyper_3_8_chunk | tablespace1\n hyper_in_space   | \n\nSET default_tablespace TO DEFAULT;\nALTER TABLE hyper_in_space SET TABLESPACE tablespace1;\nSELECT tablename FROM pg_tables WHERE tablespace = 'tablespace1' ORDER BY tablename;\n    tablename     \n------------------\n _hyper_3_5_chunk\n _hyper_3_6_chunk\n _hyper_3_7_chunk\n _hyper_3_8_chunk\n hyper_in_space\n\n-- should be inserted in an existing chunk in the new tablespace,\n-- no new chunks\nINSERT INTO hyper_in_space(time, temp, device) VALUES (5, 27, 1);\n-- the new chunk should be create in the new tablespace\nINSERT INTO hyper_in_space(time, temp, device) VALUES (8, 24, 2);\nSELECT tablename, tablespace FROM pg_tables\nWHERE tablename = 'hyper_in_space' OR tablename LIKE '\\_hyper\\__\\__\\_chunk' ORDER BY tablename;\n    tablename     | tablespace  \n------------------+-------------\n _hyper_3_5_chunk | tablespace1\n _hyper_3_6_chunk | tablespace1\n _hyper_3_7_chunk | tablespace1\n _hyper_3_8_chunk | tablespace1\n _hyper_3_9_chunk | tablespace1\n hyper_in_space   | tablespace1\n\n-- should not fail (unlike attach_tablespace)\nALTER TABLE hyper_in_space SET TABLESPACE tablespace1;\n\\set ON_ERROR_STOP 0\n-- not an empty tablespace\nDROP TABLESPACE tablespace1;\nERROR:  tablespace \"tablespace1\" is still attached to 1 hypertables\n\\set ON_ERROR_STOP 1\n-- show_chunks and drop_chunks output should be the same\n\\set QUERY1 'SELECT show_chunks(\\'hyper_in_space\\', 22)::NAME'\n\\set QUERY2 'SELECT drop_chunks(\\'hyper_in_space\\', 22)::NAME'\n\\set ECHO errors\n Different Rows | Total Rows from Query 1 | Total Rows from Query 2 \n----------------+-------------------------+-------------------------\n              0 |                       5 |                       5\n\nSELECT tablename, tablespace FROM pg_tables WHERE tablespace = 'tablespace1' ORDER BY tablename;\n   tablename    | tablespace  \n----------------+-------------\n hyper_in_space | tablespace1\n\n\\set ON_ERROR_STOP 0\n-- should not be able to drop tablespace if a hypertable depends on it\n-- even when there are no chunks\nDROP TABLESPACE tablespace1;\nERROR:  tablespace \"tablespace1\" is still attached to 1 hypertables\n\\set ON_ERROR_STOP 1\nDROP TABLE hyper_in_space;\nCREATE TABLE hyper_in_space(time bigint, temp float, device int) TABLESPACE tablespace1;\nSELECT create_hypertable('hyper_in_space', 'time', 'device', 4, chunk_time_interval=>1);\n      create_hypertable      \n-----------------------------\n (4,public,hyper_in_space,t)\n\nINSERT INTO hyper_in_space(time, temp, device) VALUES (1, 20, 1);\nINSERT INTO hyper_in_space(time, temp, device) VALUES (3, 21, 2);\nINSERT INTO hyper_in_space(time, temp, device) VALUES (5, 23, 1);\nSELECT tablename, tablespace FROM pg_tables\nWHERE tablename = 'hyper_in_space' OR tablename ~ '_hyper_\\d+_\\d+_chunk' ORDER BY tablename;\n     tablename     | tablespace  \n-------------------+-------------\n _hyper_4_10_chunk | tablespace1\n _hyper_4_11_chunk | tablespace1\n _hyper_4_12_chunk | tablespace1\n hyper_in_space    | tablespace1\n\nSELECT attach_tablespace('tablespace2', 'hyper_in_space');\n attach_tablespace \n-------------------\n \n\n\\set ON_ERROR_STOP 0\n-- should fail as >1 tablespaces are attached\nALTER TABLE hyper_in_space SET TABLESPACE tablespace1;\nERROR:  cannot set new tablespace when multiple tablespaces are attached to hypertable \"hyper_in_space\"\n\\set ON_ERROR_STOP 1\nSELECT detach_tablespace('tablespace2', 'hyper_in_space');\n detach_tablespace \n-------------------\n                 1\n\nSELECT * FROM _timescaledb_catalog.tablespace;\n id | hypertable_id | tablespace_name \n----+---------------+-----------------\n  3 |             4 | tablespace1\n\n-- make sure when using ALTER TABLE, table spaces are not accumulated\n-- as in case of attach_tablespace\n-- should have one result\nSELECT * FROM _timescaledb_catalog.tablespace;\n id | hypertable_id | tablespace_name \n----+---------------+-----------------\n  3 |             4 | tablespace1\n\nALTER TABLE hyper_in_space SET TABLESPACE tablespace2;\n-- should have one result\nSELECT * FROM _timescaledb_catalog.tablespace;\n id | hypertable_id | tablespace_name \n----+---------------+-----------------\n  5 |             4 | tablespace2\n\nALTER TABLE hyper_in_space SET TABLESPACE tablespace1;\n-- should have one result, (same as the first in the block)\nSELECT * FROM _timescaledb_catalog.tablespace;\n id | hypertable_id | tablespace_name \n----+---------------+-----------------\n  6 |             4 | tablespace1\n\nSELECT tablename, tablespace FROM pg_tables\nWHERE tablename = 'hyper_in_space' OR tablename ~ '_hyper_\\d+_\\d+_chunk' ORDER BY tablename;\n     tablename     | tablespace  \n-------------------+-------------\n _hyper_4_10_chunk | tablespace1\n _hyper_4_11_chunk | tablespace1\n _hyper_4_12_chunk | tablespace1\n hyper_in_space    | tablespace1\n\n-- attach tb2 <-> ALTER SET tb1 <-> detach tb1 should work\nSELECT detach_tablespace('tablespace1', 'hyper_in_space');\n detach_tablespace \n-------------------\n                 1\n\nINSERT INTO hyper_in_space(time, temp, device) VALUES (5, 23, 1);\nINSERT INTO hyper_in_space(time, temp, device) VALUES (7, 23, 1);\n-- Since we have detached tablespace1 the new chunk should not be\n-- placed there.\nSELECT tablename, tablespace FROM pg_tables\nWHERE tablename = 'hyper_in_space' OR tablename ~ '_hyper_\\d+_\\d+_chunk' ORDER BY tablename;\n     tablename     | tablespace  \n-------------------+-------------\n _hyper_4_10_chunk | tablespace1\n _hyper_4_11_chunk | tablespace1\n _hyper_4_12_chunk | tablespace1\n _hyper_4_13_chunk | \n hyper_in_space    | \n\nSELECT * FROM _timescaledb_catalog.tablespace;\n id | hypertable_id | tablespace_name \n----+---------------+-----------------\n\n-- tablespace functions should handle the default tablespace just as they do others\nSELECT attach_tablespace('pg_default', 'hyper_in_space');\n attach_tablespace \n-------------------\n \n\nSELECT attach_tablespace('tablespace2', 'hyper_in_space');\n attach_tablespace \n-------------------\n \n\nSELECT tablename, tablespace FROM pg_tables\nWHERE tablename = 'hyper_in_space' OR tablename ~ '_hyper_\\d+_\\d+_chunk' ORDER BY tablename;\n     tablename     | tablespace  \n-------------------+-------------\n _hyper_4_10_chunk | tablespace1\n _hyper_4_11_chunk | tablespace1\n _hyper_4_12_chunk | tablespace1\n _hyper_4_13_chunk | \n hyper_in_space    | tablespace2\n\nSELECT * FROM _timescaledb_catalog.tablespace;\n id | hypertable_id | tablespace_name \n----+---------------+-----------------\n  7 |             4 | pg_default\n  8 |             4 | tablespace2\n\nINSERT INTO hyper_in_space(time, temp, device) VALUES (12, 22, 1);\nINSERT INTO hyper_in_space(time, temp, device) VALUES (13, 23, 3);\nSELECT tablename, tablespace FROM pg_tables\nWHERE tablename = 'hyper_in_space' OR tablename ~ '_hyper_\\d+_\\d+_chunk' ORDER BY tablename;\n     tablename     | tablespace  \n-------------------+-------------\n _hyper_4_10_chunk | tablespace1\n _hyper_4_11_chunk | tablespace1\n _hyper_4_12_chunk | tablespace1\n _hyper_4_13_chunk | \n _hyper_4_14_chunk | \n _hyper_4_15_chunk | tablespace2\n hyper_in_space    | tablespace2\n\nSELECT detach_tablespace('pg_default', 'hyper_in_space');\n detach_tablespace \n-------------------\n                 1\n\nALTER TABLE hyper_in_space SET TABLESPACE pg_default;\nSELECT tablename, tablespace FROM pg_tables\nWHERE tablename = 'hyper_in_space' OR tablename ~ '_hyper_\\d+_\\d+_chunk' ORDER BY tablename;\n     tablename     | tablespace \n-------------------+------------\n _hyper_4_10_chunk | \n _hyper_4_11_chunk | \n _hyper_4_12_chunk | \n _hyper_4_13_chunk | \n _hyper_4_14_chunk | \n _hyper_4_15_chunk | \n hyper_in_space    | \n\nSELECT detach_tablespace('pg_default', 'hyper_in_space');\n detach_tablespace \n-------------------\n                 1\n\nDROP TABLE hyper_in_space;\n-- test altering tablespace on index, issue #903\nCREATE TABLE series(\n  time timestamptz not null,\n  device int,\n  value float,\n  CONSTRAINT series_pk PRIMARY KEY (time, device) USING INDEX TABLESPACE tablespace1);\nSELECT create_hypertable('series', 'time', create_default_indexes => FALSE);\n  create_hypertable  \n---------------------\n (5,public,series,t)\n\nINSERT INTO series VALUES ('2019-04-21 10:12', 1, 1.01);\nCREATE INDEX series_value ON series (value, time) TABLESPACE tablespace2;\nSELECT schemaname, tablename, indexname, tablespace\nFROM pg_indexes\nWHERE indexname LIKE '%series%'\nORDER BY indexname;\n      schemaname       |     tablename     |           indexname            | tablespace  \n-----------------------+-------------------+--------------------------------+-------------\n _timescaledb_internal | _hyper_5_16_chunk | 16_1_series_pk                 | tablespace1\n _timescaledb_internal | _hyper_5_16_chunk | _hyper_5_16_chunk_series_value | tablespace2\n public                | series            | series_pk                      | tablespace1\n public                | series            | series_value                   | tablespace2\n\nALTER INDEX series_pk SET TABLESPACE tablespace2;\nCREATE INDEX ON series (time) TABLESPACE tablespace1;\nALTER INDEX series_value SET TABLESPACE pg_default;\nINSERT INTO series VALUES ('2019-04-29 10:12', 2, 1.31);\nSELECT schemaname, tablename, indexname, tablespace\nFROM pg_indexes\nWHERE indexname LIKE '%series%'\nORDER BY indexname;\n      schemaname       |     tablename     |             indexname             | tablespace  \n-----------------------+-------------------+-----------------------------------+-------------\n _timescaledb_internal | _hyper_5_16_chunk | 16_1_series_pk                    | tablespace2\n _timescaledb_internal | _hyper_5_17_chunk | 17_2_series_pk                    | tablespace2\n _timescaledb_internal | _hyper_5_16_chunk | _hyper_5_16_chunk_series_time_idx | tablespace1\n _timescaledb_internal | _hyper_5_16_chunk | _hyper_5_16_chunk_series_value    | \n _timescaledb_internal | _hyper_5_17_chunk | _hyper_5_17_chunk_series_time_idx | tablespace1\n _timescaledb_internal | _hyper_5_17_chunk | _hyper_5_17_chunk_series_value    | \n public                | series            | series_pk                         | tablespace2\n public                | series            | series_time_idx                   | tablespace1\n public                | series            | series_value                      | \n\nDROP TABLE series;\nDROP TABLESPACE tablespace1;\nDROP TABLESPACE tablespace2;\n-- Make sure we handle ALTER SCHEMA RENAME for hypertable schemas\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA IF NOT EXISTS original_name;\nCREATE TABLE original_name.my_table (\n  date timestamp with time zone NOT NULL,\n  quantity double precision\n);\nSELECT create_hypertable('original_name.my_table','date');\n      create_hypertable       \n------------------------------\n (6,original_name,my_table,t)\n\nINSERT INTO original_name.my_table (date, quantity) VALUES ('2018-07-04T21:00:00+00:00', 8);\nALTER SCHEMA original_name RENAME TO new_name;\nDROP TABLE new_name.my_table;\nDROP SCHEMA new_name;\n-- Now make sure schema is renamed for multiple hypertables, but not hypertables not in the schema\nCREATE SCHEMA IF NOT EXISTS original_name;\nCREATE TABLE original_name.my_table (\n  date timestamp with time zone NOT NULL,\n  quantity double precision\n);\nCREATE TABLE original_name.my_table2 (\n  date timestamp with time zone NOT NULL,\n  quantity double precision\n);\nCREATE TABLE regular_table (\n  date timestamp with time zone NOT NULL,\n  quantity double precision\n);\nSELECT create_hypertable('original_name.my_table','date');\n      create_hypertable       \n------------------------------\n (7,original_name,my_table,t)\n\nSELECT create_hypertable('original_name.my_table2','date');\n       create_hypertable       \n-------------------------------\n (8,original_name,my_table2,t)\n\nSELECT create_hypertable('regular_table','date');\n     create_hypertable      \n----------------------------\n (9,public,regular_table,t)\n\nINSERT INTO original_name.my_table (date, quantity) VALUES ('2018-07-04T21:00:00+00:00', 8);\nINSERT INTO original_name.my_table2 (date, quantity) VALUES ('2018-07-04T21:00:00+00:00', 8);\nINSERT INTO regular_table (date, quantity) VALUES ('2018-07-04T21:00:00+00:00', 8);\nALTER SCHEMA original_name RENAME TO new_name;\nDROP TABLE new_name.my_table;\nDROP TABLE new_name.my_table2;\nDROP TABLE regular_table;\nDROP SCHEMA new_name;\n-- These tables should also drop when we drop the whole schema\nCREATE SCHEMA IF NOT EXISTS original_name;\nCREATE TABLE original_name.my_table (\n  date timestamp with time zone NOT NULL,\n  quantity double precision\n);\nCREATE TABLE original_name.my_table2 (\n  date timestamp with time zone NOT NULL,\n  quantity double precision\n);\nSELECT create_hypertable('original_name.my_table','date');\n       create_hypertable       \n-------------------------------\n (10,original_name,my_table,t)\n\nSELECT create_hypertable('original_name.my_table2','date');\n       create_hypertable        \n--------------------------------\n (11,original_name,my_table2,t)\n\nINSERT INTO original_name.my_table (date, quantity) VALUES ('2018-07-04T21:00:00+00:00', 8);\nINSERT INTO original_name.my_table2 (date, quantity) VALUES ('2018-07-04T21:00:00+00:00', 8);\nALTER SCHEMA original_name RENAME TO new_name;\nDROP SCHEMA new_name CASCADE;\nNOTICE:  drop cascades to 4 other objects\nSELECT * FROM test.relation WHERE schema = 'new_name';\n schema | name | type | owner \n--------+------+------+-------\n\n-- Make sure we can't rename internal schemas\n\\set ON_ERROR_STOP 0\nALTER SCHEMA _timescaledb_internal RENAME TO my_new_schema_name;\nERROR:  cannot rename schemas used by the TimescaleDB extension\nALTER SCHEMA _timescaledb_catalog RENAME TO my_new_schema_name;\nERROR:  cannot rename schemas used by the TimescaleDB extension\nALTER SCHEMA _timescaledb_cache RENAME TO my_new_schema_name;\nERROR:  cannot rename schemas used by the TimescaleDB extension\n\\set ON_ERROR_STOP 1\n-- Make sure we can rename associated schemas\nCREATE TABLE my_table (\n  date timestamp with time zone NOT NULL,\n  quantity double precision\n);\nSELECT create_hypertable('my_table','date', associated_schema_name => 'my_associated_schema');\n   create_hypertable    \n------------------------\n (12,public,my_table,t)\n\nINSERT INTO my_table (date, quantity) VALUES ('2018-07-04T21:00:00+00:00', 8);\nALTER SCHEMA my_associated_schema RENAME TO new_associated_schema;\nINSERT INTO my_table (date, quantity) VALUES ('2018-08-10T23:00:00+00:00', 20);\n-- Make sure the schema name is changed in both catalog tables\nSELECT * from _timescaledb_catalog.hypertable;\n id | schema_name | table_name | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------+------------+------------------------+-------------------------+----------------+--------------------------+--------------------------+-------------------+-------------------+--------------------------+--------\n 12 | public      | my_table   | new_associated_schema  | _hyper_12               |              1 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk from _timescaledb_catalog.chunk;\n id | hypertable_id |      schema_name      |     table_name     | compressed_chunk_id | status | osm_chunk \n----+---------------+-----------------------+--------------------+---------------------+--------+-----------\n 24 |            12 | new_associated_schema | _hyper_12_24_chunk |                     |      0 | f\n 25 |            12 | new_associated_schema | _hyper_12_25_chunk |                     |      0 | f\n\nDROP TABLE my_table;\n-- test renaming unique constraints/indexes\nCREATE TABLE t_hypertable ( id INTEGER NOT NULL, time TIMESTAMPTZ NOT NULL, value FLOAT NOT NULL CHECK (value > 0), UNIQUE(id, time));\nSELECT create_hypertable('t_hypertable', 'time');\n     create_hypertable      \n----------------------------\n (13,public,t_hypertable,t)\n\nINSERT INTO t_hypertable AS h VALUES ( 1, '2020-01-01 00:00:00', 3.2) ON CONFLICT (id, time) DO UPDATE SET value = h.value + EXCLUDED.value;\nINSERT INTO t_hypertable AS h VALUES ( 1, '2021-01-01 00:00:00', 3.2) ON CONFLICT (id, time) DO UPDATE SET value = h.value + EXCLUDED.value;\nBEGIN;\nALTER INDEX t_hypertable_id_time_key RENAME TO t_new_constraint;\n-- chunk_constraint should have updated constraint names\nSELECT hypertable_constraint_name, constraint_name from _timescaledb_catalog.chunk_constraint WHERE hypertable_constraint_name = 't_new_constraint' ORDER BY 1,2;\n hypertable_constraint_name |           constraint_name           \n----------------------------+-------------------------------------\n t_new_constraint           | _hyper_13_26_chunk_t_new_constraint\n t_new_constraint           | _hyper_13_27_chunk_t_new_constraint\n\nINSERT INTO t_hypertable AS h VALUES ( 1, '2020-01-01 00:01:00', 3.2) ON CONFLICT (id, time) DO UPDATE SET value = h.value + EXCLUDED.value;\nROLLBACK;\nBEGIN;\nALTER TABLE t_hypertable RENAME CONSTRAINT t_hypertable_id_time_key TO t_new_constraint;\n-- chunk_constraint should have updated constraint names\nSELECT hypertable_constraint_name, constraint_name from _timescaledb_catalog.chunk_constraint WHERE hypertable_constraint_name = 't_new_constraint' ORDER BY 1,2;\n hypertable_constraint_name |    constraint_name    \n----------------------------+-----------------------\n t_new_constraint           | 26_5_t_new_constraint\n t_new_constraint           | 27_6_t_new_constraint\n\nINSERT INTO t_hypertable AS h VALUES ( 1, '2020-01-01 00:01:00', 3.2) ON CONFLICT (id, time) DO UPDATE SET value = h.value + EXCLUDED.value;\nROLLBACK;\n-- predicate reconstruction when attnos are different in hypertable and chunk\nCREATE TABLE p_hypertable (a integer not null, b integer, c integer);\nSELECT create_hypertable('p_hypertable', 'a', chunk_time_interval => int '3');\n     create_hypertable      \n----------------------------\n (14,public,p_hypertable,t)\n\nBEGIN;\nALTER TABLE p_hypertable DROP COLUMN b, ADD COLUMN d boolean;\nCREATE INDEX idx_ht ON p_hypertable(a, c) WHERE d = FALSE;\nEND;\nINSERT INTO p_hypertable(a, c, d) VALUES (1, 1, FALSE);\n\\d _timescaledb_internal._hyper_14_28_chunk\n Table \"_timescaledb_internal._hyper_14_28_chunk\"\n Column |  Type   | Collation | Nullable | Default \n--------+---------+-----------+----------+---------\n a      | integer |           | not null | \n c      | integer |           |          | \n d      | boolean |           |          | \nIndexes:\n    \"_hyper_14_28_chunk_idx_ht\" btree (a, c) WHERE NOT d\n    \"_hyper_14_28_chunk_p_hypertable_a_idx\" btree (a DESC)\nCheck constraints:\n    \"constraint_34\" CHECK (a >= 0 AND a < 3)\nInherits: p_hypertable\n\nDROP TABLE p_hypertable;\n-- check none of our hooks interact badly with normal alter view handling\nCREATE VIEW v1 AS SELECT random();\n\\set ON_ERROR_STOP 0\n-- should error with unrecognized parameter\nALTER VIEW v1 SET (autovacuum_enabled = false);\nERROR:  unrecognized parameter \"autovacuum_enabled\"\n\\set ON_ERROR_STOP 1\n-- issue 4474\n-- test hypertable with non-default statistics target\n-- and chunk creation triggered by non-owner\nCREATE ROLE role_4474;\nCREATE TABLE i4474(time timestamptz NOT NULL);\nSELECT table_name FROM public.create_hypertable( 'i4474', 'time');\n table_name \n------------\n i4474\n\nGRANT SELECT, INSERT on i4474 TO role_4474;\n-- create chunk as owner\nINSERT INTO i4474 SELECT '2020-01-01';\n-- set statistics\nALTER TABLE i4474 ALTER COLUMN time SET statistics 10;\n-- create chunk as non-owner\nSET ROLE role_4474;\nINSERT INTO i4474 SELECT '2021-01-01';\nRESET ROLE;\nDROP TABLE i4474 CASCADE;\nDROP ROLE role_4474;\n-- verify that setting replica identity works and chunks inherit the\n-- root table's setting\nCREATE TABLE replid(time timestamptz, value int);\nSELECT create_hypertable('replid', 'time', chunk_time_interval => interval '1 day', create_default_indexes => false);\n  create_hypertable   \n----------------------\n (16,public,replid,t)\n\n-- replica identity set to default\nSELECT relreplident FROM pg_class WHERE relname = 'replid';\n relreplident \n--------------\n d\n\nINSERT INTO replid VALUES ('2023-01-01', 1);\n-- the new chunk should have the same replica identity setting\nSELECT relname, relreplident FROM show_chunks('replid') ch INNER JOIN pg_class c ON (ch = c.oid) ORDER BY relname;\n      relname       | relreplident \n--------------------+--------------\n _hyper_16_31_chunk | d\n\n-- test change to replica identity full\nALTER TABLE replid REPLICA IDENTITY FULL;\nSELECT relname, relreplident FROM pg_class WHERE relname = 'replid' ORDER BY relname;\n relname | relreplident \n---------+--------------\n replid  | f\n\n-- the chunk's setting should also change to FULL\nSELECT relname, relreplident FROM show_chunks('replid') ch INNER JOIN pg_class c ON (ch = c.oid) ORDER BY relname;\n      relname       | relreplident \n--------------------+--------------\n _hyper_16_31_chunk | f\n\n-- change to replica identity index\nCREATE UNIQUE INDEX time_key ON replid (time);\nALTER TABLE replid REPLICA IDENTITY USING INDEX time_key;\nSELECT relname, relreplident FROM pg_class WHERE relname = 'replid' ORDER BY relname;\n relname | relreplident \n---------+--------------\n replid  | i\n\nSELECT relname, relreplident FROM show_chunks('replid') ch INNER JOIN pg_class c ON (ch = c.oid) ORDER BY relname;\n      relname       | relreplident \n--------------------+--------------\n _hyper_16_31_chunk | i\n\nSELECT indexrelid::regclass::text AS index_name\nFROM show_chunks('replid') chid\nINNER JOIN pg_index i ON (i.indrelid = chid) AND indisreplident=true\nORDER BY index_name;\n                    index_name                     \n---------------------------------------------------\n _timescaledb_internal._hyper_16_31_chunk_time_key\n\nINSERT INTO replid VALUES ('2023-01-02', 2);\n-- the new chunk will also have replica identity \"index\"\nSELECT relname, relreplident FROM show_chunks('replid') ch INNER JOIN pg_class c ON (ch = c.oid) ORDER BY relname;\n      relname       | relreplident \n--------------------+--------------\n _hyper_16_31_chunk | i\n _hyper_16_32_chunk | i\n\nSELECT indexrelid::regclass::text AS index_name\nFROM show_chunks('replid') chid\nINNER JOIN pg_index i ON (i.indrelid = chid) AND indisreplident=true\nORDER BY index_name;\n                    index_name                     \n---------------------------------------------------\n _timescaledb_internal._hyper_16_31_chunk_time_key\n _timescaledb_internal._hyper_16_32_chunk_time_key\n\n-- drop the replica identity index and create a new chunk. The new\n-- chunk should have replica identity \"NOTHING\" since this is the\n-- behavior of replica identity index when the index is dropped.\nDROP INDEX time_key;\nINSERT INTO replid VALUES ('2023-01-03', 3);\n-- no indexes left\nSELECT relname, relreplident FROM show_chunks('replid') ch INNER JOIN pg_class c ON (ch = c.oid) ORDER BY relname;\n      relname       | relreplident \n--------------------+--------------\n _hyper_16_31_chunk | i\n _hyper_16_32_chunk | i\n _hyper_16_33_chunk | n\n\nSELECT indexrelid::regclass::text AS index_name\nFROM show_chunks('replid') chid\nINNER JOIN pg_index i ON (i.indrelid = chid) AND indisreplident=true\nORDER BY index_name;\n index_name \n------------\n\n-- recreate the unique index after drop and insert to create a new chunk.\n-- This is a regression test for a bug where rd_replidindex was stale\n-- after relcache invalidation from chunk index creation, leading to\n-- \"could not open relation with OID 0\" error.\nCREATE UNIQUE INDEX time_key ON replid (time);\nINSERT INTO replid VALUES ('2023-01-04', 4);\nSELECT relname, relreplident FROM show_chunks('replid') ch INNER JOIN pg_class c ON (ch = c.oid) ORDER BY relname;\n      relname       | relreplident \n--------------------+--------------\n _hyper_16_31_chunk | i\n _hyper_16_32_chunk | i\n _hyper_16_33_chunk | n\n _hyper_16_34_chunk | n\n\n-- Alter replica identity directly on a chunk is not supported\nSELECT ch AS chunk_name FROM show_chunks('replid') ch ORDER BY chunk_name LIMIT 1 \\gset\n\\set ON_ERROR_STOP 0\nALTER TABLE :chunk_name REPLICA IDENTITY FULL;\nERROR:  operation not supported on chunk tables\n\\set ON_ERROR_STOP 1\nSELECT relname, relreplident FROM show_chunks('replid') ch INNER JOIN pg_class c ON (ch = c.oid) ORDER BY relname;\n      relname       | relreplident \n--------------------+--------------\n _hyper_16_31_chunk | i\n _hyper_16_32_chunk | i\n _hyper_16_33_chunk | n\n _hyper_16_34_chunk | n\n\n-- test implicit constraints gh issue #9132\nCREATE TABLE i9132(time timestamptz) WITH (tsdb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nINSERT INTO i9132 VALUES ('2024-01-01'), ('2024-02-02');\nALTER TABLE i9132 ADD COLUMN id serial, ADD CONSTRAINT implicit_pk PRIMARY KEY (id, time);\n"
  },
  {
    "path": "test/expected/alternate_users.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\ir include/insert_single.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE PUBLIC.\"one_Partition\" (\n  \"timeCustom\" BIGINT NOT NULL,\n  device_id TEXT NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL,\n  series_bool BOOLEAN NULL\n);\nCREATE INDEX ON PUBLIC.\"one_Partition\" (device_id, \"timeCustom\" DESC NULLS LAST) WHERE device_id IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"one_Partition\" (\"timeCustom\" DESC NULLS LAST, series_0) WHERE series_0 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"one_Partition\" (\"timeCustom\" DESC NULLS LAST, series_1)  WHERE series_1 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"one_Partition\" (\"timeCustom\" DESC NULLS LAST, series_2) WHERE series_2 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"one_Partition\" (\"timeCustom\" DESC NULLS LAST, series_bool) WHERE series_bool IS NOT NULL;\n\\c :DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA \"one_Partition\" AUTHORIZATION :ROLE_DEFAULT_PERM_USER;\n\\c :DBNAME :ROLE_DEFAULT_PERM_USER;\nSELECT * FROM create_hypertable('\"public\".\"one_Partition\"', 'timeCustom', associated_schema_name=>'one_Partition', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name |  table_name   | created \n---------------+-------------+---------------+---------\n             1 | public      | one_Partition | t\n\n--output command tags\n\\set QUIET off\nBEGIN;\nBEGIN\n\\COPY \"one_Partition\" FROM 'data/ds1_dev1_1.tsv' NULL AS '';\nCOPY 7\nCOMMIT;\nCOMMIT\nINSERT INTO \"one_Partition\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257987600000000000, 'dev1', 1.5, 1),\n(1257987600000000000, 'dev1', 1.5, 2),\n(1257894000000000000, 'dev2', 1.5, 1),\n(1257894002000000000, 'dev1', 2.5, 3);\nINSERT 0 4\nINSERT INTO \"one_Partition\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257894000000000000, 'dev2', 1.5, 2);\nINSERT 0 1\n\\set QUIET on\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- make sure tablespace1 exists\n-- since there is no CREATE TABLESPACE IF EXISTS we drop with if exists and recreate\nSET client_min_messages TO error;\nDROP TABLESPACE IF EXISTS tablespace1;\nRESET client_min_messages;\nCREATE TABLESPACE tablespace1 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE1_PATH;\n--needed for ddl ops:\nCREATE SCHEMA IF NOT EXISTS \"customSchema\" AUTHORIZATION :ROLE_DEFAULT_PERM_USER_2;\n--needed for ROLE_DEFAULT_PERM_USER_2 to write to the 'one_Partition' schema which\n--is owned by ROLE_DEFAULT_PERM_USER\nGRANT CREATE ON SCHEMA \"one_Partition\" TO :ROLE_DEFAULT_PERM_USER_2;\n--test creating and using schema as non-superuser\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\nSELECT * FROM test.relation WHERE schema='public' ORDER BY schema, name;\n schema |     name      | type  |       owner       \n--------+---------------+-------+-------------------\n public | one_Partition | table | default_perm_user\n\n\\set ON_ERROR_STOP 0\nSELECT * FROM \"one_Partition\";\nERROR:  permission denied for table one_Partition\nSELECT set_chunk_time_interval('\"one_Partition\"', 1::bigint);\nERROR:  must be owner of hypertable \"one_Partition\"\nselect add_dimension('\"one_Partition\"', 'device_id', 2);\nERROR:  must be owner of hypertable \"one_Partition\"\nselect attach_tablespace('tablespace1', '\"one_Partition\"');\nERROR:  must be owner of hypertable \"one_Partition\"\n\\set ON_ERROR_STOP 1\nCREATE TABLE \"1dim\"(time timestamp, temp float);\nSELECT create_hypertable('\"1dim\"', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n create_hypertable \n-------------------\n (2,public,1dim,t)\n\nINSERT INTO \"1dim\" VALUES('2017-01-20T09:00:01', 22.5);\nINSERT INTO \"1dim\" VALUES('2017-01-20T09:00:21', 21.2);\nINSERT INTO \"1dim\" VALUES('2017-01-20T09:00:47', 25.1);\nSELECT * FROM \"1dim\";\n           time           | temp \n--------------------------+------\n Fri Jan 20 09:00:01 2017 | 22.5\n Fri Jan 20 09:00:21 2017 | 21.2\n Fri Jan 20 09:00:47 2017 | 25.1\n\n\\ir include/ddl_ops_1.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE PUBLIC.\"Hypertable_1\" (\n  time BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  temp_c int NOT NULL DEFAULT -1,\n  humidity numeric NULL DEFAULT 0,\n  sensor_1 NUMERIC NULL DEFAULT 1,\n  sensor_2 NUMERIC NOT NULL DEFAULT 1,\n  sensor_3 NUMERIC NOT NULL DEFAULT 1,\n  sensor_4 NUMERIC NOT NULL DEFAULT 1\n);\nCREATE INDEX ON PUBLIC.\"Hypertable_1\" (time, \"Device_id\");\nCREATE TABLE \"customSchema\".\"Hypertable_1\" (\n  time BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  temp_c int NOT NULL DEFAULT -1,\n  humidity numeric NULL DEFAULT 0,\n  sensor_1 NUMERIC NULL DEFAULT 1,\n  sensor_2 NUMERIC NOT NULL DEFAULT 1,\n  sensor_3 NUMERIC NOT NULL DEFAULT 1,\n  sensor_4 NUMERIC NOT NULL DEFAULT 1\n);\nCREATE INDEX ON \"customSchema\".\"Hypertable_1\" (time, \"Device_id\");\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1\"', 'time', 'Device_id', 1, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name |  table_name  | created \n---------------+-------------+--------------+---------\n             3 | public      | Hypertable_1 | t\n\nSELECT * FROM create_hypertable('\"customSchema\".\"Hypertable_1\"', 'time', NULL, 1, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name  |  table_name  | created \n---------------+--------------+--------------+---------\n             4 | customSchema | Hypertable_1 | t\n\nSELECT * FROM _timescaledb_catalog.hypertable;\n id | schema_name  |  table_name   | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+--------------+---------------+------------------------+-------------------------+----------------+--------------------------+--------------------------+-------------------+-------------------+--------------------------+--------\n  1 | public       | one_Partition | one_Partition          | _hyper_1                |              1 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n  2 | public       | 1dim          | _timescaledb_internal  | _hyper_2                |              1 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n  3 | public       | Hypertable_1  | _timescaledb_internal  | _hyper_3                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n  4 | customSchema | Hypertable_1  | _timescaledb_internal  | _hyper_4                |              1 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n\nCREATE INDEX ON PUBLIC.\"Hypertable_1\" (time, \"temp_c\");\nCREATE INDEX \"ind_humidity\" ON PUBLIC.\"Hypertable_1\" (time, \"humidity\");\nCREATE INDEX \"ind_sensor_1\" ON PUBLIC.\"Hypertable_1\" (time, \"sensor_1\");\nINSERT INTO PUBLIC.\"Hypertable_1\"(time, \"Device_id\", temp_c, humidity, sensor_1, sensor_2, sensor_3, sensor_4)\nVALUES(1257894000000000000, 'dev1', 30, 70, 1, 2, 3, 100);\nCREATE UNIQUE INDEX \"Unique1\" ON PUBLIC.\"Hypertable_1\" (time, \"Device_id\");\nCREATE UNIQUE INDEX \"Unique1\" ON \"customSchema\".\"Hypertable_1\" (time);\nINSERT INTO \"customSchema\".\"Hypertable_1\"(time, \"Device_id\", temp_c, humidity, sensor_1, sensor_2, sensor_3, sensor_4)\nVALUES(1257894000000000000, 'dev1', 30, 70, 1, 2, 3, 100);\nINSERT INTO \"customSchema\".\"Hypertable_1\"(time, \"Device_id\", temp_c, humidity, sensor_1, sensor_2, sensor_3, sensor_4)\nVALUES(1257894000000000001, 'dev1', 30, 70, 1, 2, 3, 100);\nSELECT * FROM test.show_indexesp('%.%');\n              Table               |                                    Index                                    |         Columns          | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------+-----------------------------------------------------------------------------+--------------------------+------+--------+---------+-----------+------------\n \"one_Partition\"._hyper_1_1_chunk | \"one_Partition\".\"_hyper_1_1_chunk_one_Partition_device_id_timeCustom_idx\"   | {device_id,timeCustom}   |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_1_chunk | \"one_Partition\".\"_hyper_1_1_chunk_one_Partition_timeCustom_series_0_idx\"    | {timeCustom,series_0}    |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_1_chunk | \"one_Partition\".\"_hyper_1_1_chunk_one_Partition_timeCustom_series_1_idx\"    | {timeCustom,series_1}    |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_1_chunk | \"one_Partition\".\"_hyper_1_1_chunk_one_Partition_timeCustom_series_2_idx\"    | {timeCustom,series_2}    |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_1_chunk | \"one_Partition\".\"_hyper_1_1_chunk_one_Partition_timeCustom_series_bool_idx\" | {timeCustom,series_bool} |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_1_chunk | \"one_Partition\".\"_hyper_1_1_chunk_one_Partition_timeCustom_idx\"             | {timeCustom}             |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_2_chunk | \"one_Partition\".\"_hyper_1_2_chunk_one_Partition_device_id_timeCustom_idx\"   | {device_id,timeCustom}   |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_2_chunk | \"one_Partition\".\"_hyper_1_2_chunk_one_Partition_timeCustom_series_0_idx\"    | {timeCustom,series_0}    |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_2_chunk | \"one_Partition\".\"_hyper_1_2_chunk_one_Partition_timeCustom_series_1_idx\"    | {timeCustom,series_1}    |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_2_chunk | \"one_Partition\".\"_hyper_1_2_chunk_one_Partition_timeCustom_series_2_idx\"    | {timeCustom,series_2}    |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_2_chunk | \"one_Partition\".\"_hyper_1_2_chunk_one_Partition_timeCustom_series_bool_idx\" | {timeCustom,series_bool} |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_2_chunk | \"one_Partition\".\"_hyper_1_2_chunk_one_Partition_timeCustom_idx\"             | {timeCustom}             |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_3_chunk | \"one_Partition\".\"_hyper_1_3_chunk_one_Partition_device_id_timeCustom_idx\"   | {device_id,timeCustom}   |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_3_chunk | \"one_Partition\".\"_hyper_1_3_chunk_one_Partition_timeCustom_series_0_idx\"    | {timeCustom,series_0}    |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_3_chunk | \"one_Partition\".\"_hyper_1_3_chunk_one_Partition_timeCustom_series_1_idx\"    | {timeCustom,series_1}    |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_3_chunk | \"one_Partition\".\"_hyper_1_3_chunk_one_Partition_timeCustom_series_2_idx\"    | {timeCustom,series_2}    |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_3_chunk | \"one_Partition\".\"_hyper_1_3_chunk_one_Partition_timeCustom_series_bool_idx\" | {timeCustom,series_bool} |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_3_chunk | \"one_Partition\".\"_hyper_1_3_chunk_one_Partition_timeCustom_idx\"             | {timeCustom}             |      | f      | f       | f         | \n \"customSchema\".\"Hypertable_1\"    | \"customSchema\".\"Hypertable_1_time_Device_id_idx\"                            | {time,Device_id}         |      | f      | f       | f         | \n \"customSchema\".\"Hypertable_1\"    | \"customSchema\".\"Hypertable_1_time_idx\"                                      | {time}                   |      | f      | f       | f         | \n \"customSchema\".\"Hypertable_1\"    | \"customSchema\".\"Unique1\"                                                    | {time}                   |      | t      | f       | f         | \n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper_%');\n                 Table                  |                                  Index                                   |     Columns      | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------+--------------------------------------------------------------------------+------------------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_2_4_chunk | _timescaledb_internal._hyper_2_4_chunk_1dim_time_idx                     | {time}           |      | f      | f       | f         | \n _timescaledb_internal._hyper_3_5_chunk | _timescaledb_internal.\"_hyper_3_5_chunk_Hypertable_1_time_Device_id_idx\" | {time,Device_id} |      | f      | f       | f         | \n _timescaledb_internal._hyper_3_5_chunk | _timescaledb_internal.\"_hyper_3_5_chunk_Hypertable_1_time_idx\"           | {time}           |      | f      | f       | f         | \n _timescaledb_internal._hyper_3_5_chunk | _timescaledb_internal.\"_hyper_3_5_chunk_Hypertable_1_Device_id_time_idx\" | {Device_id,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_3_5_chunk | _timescaledb_internal.\"_hyper_3_5_chunk_Hypertable_1_time_temp_c_idx\"    | {time,temp_c}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_3_5_chunk | _timescaledb_internal._hyper_3_5_chunk_ind_humidity                      | {time,humidity}  |      | f      | f       | f         | \n _timescaledb_internal._hyper_3_5_chunk | _timescaledb_internal._hyper_3_5_chunk_ind_sensor_1                      | {time,sensor_1}  |      | f      | f       | f         | \n _timescaledb_internal._hyper_3_5_chunk | _timescaledb_internal.\"_hyper_3_5_chunk_Unique1\"                         | {time,Device_id} |      | t      | f       | f         | \n _timescaledb_internal._hyper_4_6_chunk | _timescaledb_internal.\"_hyper_4_6_chunk_Hypertable_1_time_Device_id_idx\" | {time,Device_id} |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_6_chunk | _timescaledb_internal.\"_hyper_4_6_chunk_Hypertable_1_time_idx\"           | {time}           |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_6_chunk | _timescaledb_internal.\"_hyper_4_6_chunk_Unique1\"                         | {time}           |      | t      | f       | f         | \n\n--expect error cases\n\\set ON_ERROR_STOP 0\nINSERT INTO \"customSchema\".\"Hypertable_1\"(time, \"Device_id\", temp_c, humidity, sensor_1, sensor_2, sensor_3, sensor_4)\nVALUES(1257894000000000000, 'dev1', 31, 71, 72, 4, 1, 102);\npsql:include/ddl_ops_1.sql:57: ERROR:  duplicate key value violates unique constraint \"_hyper_4_6_chunk_Unique1\"\nCREATE UNIQUE INDEX \"Unique2\" ON PUBLIC.\"Hypertable_1\" (\"Device_id\");\npsql:include/ddl_ops_1.sql:58: ERROR:  cannot create a unique index without the column \"time\" (used in partitioning)\nCREATE UNIQUE INDEX \"Unique2\" ON PUBLIC.\"Hypertable_1\" (time);\npsql:include/ddl_ops_1.sql:59: ERROR:  cannot create a unique index without the column \"Device_id\" (used in partitioning)\nCREATE UNIQUE INDEX \"Unique2\" ON PUBLIC.\"Hypertable_1\" (sensor_1);\npsql:include/ddl_ops_1.sql:60: ERROR:  cannot create a unique index without the column \"time\" (used in partitioning)\nUPDATE ONLY PUBLIC.\"Hypertable_1\" SET time = 0 WHERE TRUE;\nDELETE FROM ONLY PUBLIC.\"Hypertable_1\" WHERE \"Device_id\" = 'dev1';\n\\set ON_ERROR_STOP 1\nCREATE TABLE my_ht (time BIGINT, val integer);\nSELECT * FROM create_hypertable('my_ht', 'time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             5 | public      | my_ht      | t\n\nALTER TABLE my_ht ADD COLUMN val2 integer;\nSELECT * FROM test.show_columns('my_ht');\n Column |  Type   | NotNull \n--------+---------+---------\n time   | bigint  | t\n val    | integer | f\n val2   | integer | f\n\n-- Should error when adding again\n\\set ON_ERROR_STOP 0\nALTER TABLE my_ht ADD COLUMN val2 integer;\npsql:include/ddl_ops_1.sql:73: ERROR:  column \"val2\" of relation \"my_ht\" already exists\n\\set ON_ERROR_STOP 1\n-- Should create\nALTER TABLE my_ht ADD COLUMN IF NOT EXISTS val3 integer;\nSELECT * FROM test.show_columns('my_ht');\n Column |  Type   | NotNull \n--------+---------+---------\n time   | bigint  | t\n val    | integer | f\n val2   | integer | f\n val3   | integer | f\n\n-- Should skip and not error\nALTER TABLE my_ht ADD COLUMN IF NOT EXISTS val3 integer;\npsql:include/ddl_ops_1.sql:81: NOTICE:  column \"val3\" of relation \"my_ht\" already exists, skipping\nSELECT * FROM test.show_columns('my_ht');\n Column |  Type   | NotNull \n--------+---------+---------\n time   | bigint  | t\n val    | integer | f\n val2   | integer | f\n val3   | integer | f\n\n-- Should drop\nALTER TABLE my_ht DROP COLUMN IF EXISTS val3;\nSELECT * FROM test.show_columns('my_ht');\n Column |  Type   | NotNull \n--------+---------+---------\n time   | bigint  | t\n val    | integer | f\n val2   | integer | f\n\n-- Should skip and not error\nALTER TABLE my_ht DROP COLUMN IF EXISTS val3;\npsql:include/ddl_ops_1.sql:89: NOTICE:  column \"val3\" of relation \"my_ht\" does not exist, skipping\nSELECT * FROM test.show_columns('my_ht');\n Column |  Type   | NotNull \n--------+---------+---------\n time   | bigint  | t\n val    | integer | f\n val2   | integer | f\n\n--Test default index creation on create_hypertable().\n--Make sure that we do not duplicate indexes that already exists\n--\n--No existing indexes: both time and space-time indexes created\nBEGIN;\nCREATE TABLE PUBLIC.\"Hypertable_1_with_default_index_enabled\" (\n  \"Time\" BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1\n);\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1_with_default_index_enabled\"', 'Time', 'Device_id', 1, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name |               table_name                | created \n---------------+-------------+-----------------------------------------+---------\n             6 | public      | Hypertable_1_with_default_index_enabled | t\n\nSELECT * FROM test.show_indexes('\"Hypertable_1_with_default_index_enabled\"');\n                            Index                             |     Columns      | Expr | Unique | Primary | Exclusion | Tablespace \n--------------------------------------------------------------+------------------+------+--------+---------+-----------+------------\n \"Hypertable_1_with_default_index_enabled_Device_id_Time_idx\" | {Device_id,Time} |      | f      | f       | f         | \n \"Hypertable_1_with_default_index_enabled_Time_idx\"           | {Time}           |      | f      | f       | f         | \n\nROLLBACK;\n--Space index exists: only time index created\nBEGIN;\nCREATE TABLE PUBLIC.\"Hypertable_1_with_default_index_enabled\" (\n  \"Time\" BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1\n);\nCREATE INDEX ON PUBLIC.\"Hypertable_1_with_default_index_enabled\" (\"Device_id\", \"Time\" DESC);\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1_with_default_index_enabled\"', 'Time', 'Device_id', 1, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name |               table_name                | created \n---------------+-------------+-----------------------------------------+---------\n             7 | public      | Hypertable_1_with_default_index_enabled | t\n\nSELECT * FROM test.show_indexes('\"Hypertable_1_with_default_index_enabled\"');\n                            Index                             |     Columns      | Expr | Unique | Primary | Exclusion | Tablespace \n--------------------------------------------------------------+------------------+------+--------+---------+-----------+------------\n \"Hypertable_1_with_default_index_enabled_Device_id_Time_idx\" | {Device_id,Time} |      | f      | f       | f         | \n \"Hypertable_1_with_default_index_enabled_Time_idx\"           | {Time}           |      | f      | f       | f         | \n\nROLLBACK;\n--Time index exists, only partition index created\nBEGIN;\nCREATE TABLE PUBLIC.\"Hypertable_1_with_default_index_enabled\" (\n  \"Time\" BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1\n);\nCREATE INDEX ON PUBLIC.\"Hypertable_1_with_default_index_enabled\" (\"Time\" DESC);\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1_with_default_index_enabled\"', 'Time', 'Device_id', 1, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name |               table_name                | created \n---------------+-------------+-----------------------------------------+---------\n             8 | public      | Hypertable_1_with_default_index_enabled | t\n\nSELECT * FROM test.show_indexes('\"Hypertable_1_with_default_index_enabled\"');\n                            Index                             |     Columns      | Expr | Unique | Primary | Exclusion | Tablespace \n--------------------------------------------------------------+------------------+------+--------+---------+-----------+------------\n \"Hypertable_1_with_default_index_enabled_Device_id_Time_idx\" | {Device_id,Time} |      | f      | f       | f         | \n \"Hypertable_1_with_default_index_enabled_Time_idx\"           | {Time}           |      | f      | f       | f         | \n\nROLLBACK;\n--No space partitioning, only time index created\nBEGIN;\nCREATE TABLE PUBLIC.\"Hypertable_1_with_default_index_enabled\" (\n  \"Time\" BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1\n);\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1_with_default_index_enabled\"', 'Time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name |               table_name                | created \n---------------+-------------+-----------------------------------------+---------\n             9 | public      | Hypertable_1_with_default_index_enabled | t\n\nSELECT * FROM test.show_indexes('\"Hypertable_1_with_default_index_enabled\"');\n                       Index                        | Columns | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------------------+---------+------+--------+---------+-----------+------------\n \"Hypertable_1_with_default_index_enabled_Time_idx\" | {Time}  |      | f      | f       | f         | \n\nROLLBACK;\n--Disable index creation: no default indexes created\nBEGIN;\nCREATE TABLE PUBLIC.\"Hypertable_1_with_default_index_enabled\" (\n  \"Time\" BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1\n);\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1_with_default_index_enabled\"', 'Time', 'Device_id', 1, create_default_indexes=>FALSE, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name |               table_name                | created \n---------------+-------------+-----------------------------------------+---------\n            10 | public      | Hypertable_1_with_default_index_enabled | t\n\nSELECT * FROM test.show_indexes('\"Hypertable_1_with_default_index_enabled\"');\n Index | Columns | Expr | Unique | Primary | Exclusion | Tablespace \n-------+---------+------+--------+---------+-----------+------------\n\nROLLBACK;\n\\ir include/ddl_ops_2.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nALTER TABLE PUBLIC.\"Hypertable_1\" ADD COLUMN temp_f INTEGER NOT NULL DEFAULT 31;\nALTER TABLE PUBLIC.\"Hypertable_1\" DROP COLUMN temp_c;\nALTER TABLE PUBLIC.\"Hypertable_1\" DROP COLUMN sensor_4;\nALTER TABLE PUBLIC.\"Hypertable_1\" ALTER COLUMN humidity SET DEFAULT 100;\nALTER TABLE PUBLIC.\"Hypertable_1\" ALTER COLUMN sensor_1 DROP DEFAULT;\nALTER TABLE PUBLIC.\"Hypertable_1\" ALTER COLUMN sensor_2 SET DEFAULT NULL;\nALTER TABLE PUBLIC.\"Hypertable_1\" ALTER COLUMN sensor_1 SET NOT NULL;\nALTER TABLE PUBLIC.\"Hypertable_1\" ALTER COLUMN sensor_2 DROP NOT NULL;\nALTER TABLE PUBLIC.\"Hypertable_1\" RENAME COLUMN sensor_2 TO sensor_2_renamed;\nALTER TABLE PUBLIC.\"Hypertable_1\" RENAME COLUMN sensor_3 TO sensor_3_renamed;\nDROP INDEX \"ind_sensor_1\";\nCREATE OR REPLACE FUNCTION empty_trigger_func()\n    RETURNS TRIGGER LANGUAGE PLPGSQL AS\n$BODY$\nBEGIN\nEND\n$BODY$;\nCREATE TRIGGER test_trigger BEFORE UPDATE OR DELETE ON PUBLIC.\"Hypertable_1\"\nFOR EACH STATEMENT EXECUTE FUNCTION empty_trigger_func();\nALTER TABLE PUBLIC.\"Hypertable_1\" ALTER COLUMN sensor_2_renamed SET DATA TYPE int;\nALTER INDEX \"ind_humidity\" RENAME TO \"ind_humdity2\";\n-- Change should be reflected here\nSELECT * FROM test.show_indexesp('%.%');\n              Table               |                                    Index                                    |         Columns          | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------+-----------------------------------------------------------------------------+--------------------------+------+--------+---------+-----------+------------\n \"one_Partition\"._hyper_1_1_chunk | \"one_Partition\".\"_hyper_1_1_chunk_one_Partition_device_id_timeCustom_idx\"   | {device_id,timeCustom}   |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_1_chunk | \"one_Partition\".\"_hyper_1_1_chunk_one_Partition_timeCustom_series_0_idx\"    | {timeCustom,series_0}    |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_1_chunk | \"one_Partition\".\"_hyper_1_1_chunk_one_Partition_timeCustom_series_1_idx\"    | {timeCustom,series_1}    |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_1_chunk | \"one_Partition\".\"_hyper_1_1_chunk_one_Partition_timeCustom_series_2_idx\"    | {timeCustom,series_2}    |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_1_chunk | \"one_Partition\".\"_hyper_1_1_chunk_one_Partition_timeCustom_series_bool_idx\" | {timeCustom,series_bool} |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_1_chunk | \"one_Partition\".\"_hyper_1_1_chunk_one_Partition_timeCustom_idx\"             | {timeCustom}             |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_2_chunk | \"one_Partition\".\"_hyper_1_2_chunk_one_Partition_device_id_timeCustom_idx\"   | {device_id,timeCustom}   |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_2_chunk | \"one_Partition\".\"_hyper_1_2_chunk_one_Partition_timeCustom_series_0_idx\"    | {timeCustom,series_0}    |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_2_chunk | \"one_Partition\".\"_hyper_1_2_chunk_one_Partition_timeCustom_series_1_idx\"    | {timeCustom,series_1}    |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_2_chunk | \"one_Partition\".\"_hyper_1_2_chunk_one_Partition_timeCustom_series_2_idx\"    | {timeCustom,series_2}    |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_2_chunk | \"one_Partition\".\"_hyper_1_2_chunk_one_Partition_timeCustom_series_bool_idx\" | {timeCustom,series_bool} |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_2_chunk | \"one_Partition\".\"_hyper_1_2_chunk_one_Partition_timeCustom_idx\"             | {timeCustom}             |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_3_chunk | \"one_Partition\".\"_hyper_1_3_chunk_one_Partition_device_id_timeCustom_idx\"   | {device_id,timeCustom}   |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_3_chunk | \"one_Partition\".\"_hyper_1_3_chunk_one_Partition_timeCustom_series_0_idx\"    | {timeCustom,series_0}    |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_3_chunk | \"one_Partition\".\"_hyper_1_3_chunk_one_Partition_timeCustom_series_1_idx\"    | {timeCustom,series_1}    |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_3_chunk | \"one_Partition\".\"_hyper_1_3_chunk_one_Partition_timeCustom_series_2_idx\"    | {timeCustom,series_2}    |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_3_chunk | \"one_Partition\".\"_hyper_1_3_chunk_one_Partition_timeCustom_series_bool_idx\" | {timeCustom,series_bool} |      | f      | f       | f         | \n \"one_Partition\"._hyper_1_3_chunk | \"one_Partition\".\"_hyper_1_3_chunk_one_Partition_timeCustom_idx\"             | {timeCustom}             |      | f      | f       | f         | \n \"customSchema\".\"Hypertable_1\"    | \"customSchema\".\"Hypertable_1_time_Device_id_idx\"                            | {time,Device_id}         |      | f      | f       | f         | \n \"customSchema\".\"Hypertable_1\"    | \"customSchema\".\"Hypertable_1_time_idx\"                                      | {time}                   |      | f      | f       | f         | \n \"customSchema\".\"Hypertable_1\"    | \"customSchema\".\"Unique1\"                                                    | {time}                   |      | t      | f       | f         | \n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%');\n                 Table                  |                                  Index                                   |     Columns      | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------+--------------------------------------------------------------------------+------------------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_2_4_chunk | _timescaledb_internal._hyper_2_4_chunk_1dim_time_idx                     | {time}           |      | f      | f       | f         | \n _timescaledb_internal._hyper_3_5_chunk | _timescaledb_internal.\"_hyper_3_5_chunk_Hypertable_1_time_Device_id_idx\" | {time,Device_id} |      | f      | f       | f         | \n _timescaledb_internal._hyper_3_5_chunk | _timescaledb_internal.\"_hyper_3_5_chunk_Hypertable_1_time_idx\"           | {time}           |      | f      | f       | f         | \n _timescaledb_internal._hyper_3_5_chunk | _timescaledb_internal.\"_hyper_3_5_chunk_Hypertable_1_Device_id_time_idx\" | {Device_id,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_3_5_chunk | _timescaledb_internal._hyper_3_5_chunk_ind_humdity2                      | {time,humidity}  |      | f      | f       | f         | \n _timescaledb_internal._hyper_3_5_chunk | _timescaledb_internal.\"_hyper_3_5_chunk_Unique1\"                         | {time,Device_id} |      | t      | f       | f         | \n _timescaledb_internal._hyper_4_6_chunk | _timescaledb_internal.\"_hyper_4_6_chunk_Hypertable_1_time_Device_id_idx\" | {time,Device_id} |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_6_chunk | _timescaledb_internal.\"_hyper_4_6_chunk_Hypertable_1_time_idx\"           | {time}           |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_6_chunk | _timescaledb_internal.\"_hyper_4_6_chunk_Unique1\"                         | {time}           |      | t      | f       | f         | \n\n--create column with same name as previously renamed one\nALTER TABLE PUBLIC.\"Hypertable_1\" ADD COLUMN sensor_3 BIGINT NOT NULL DEFAULT 131;\n--create column with same name as previously dropped one\nALTER TABLE PUBLIC.\"Hypertable_1\" ADD COLUMN sensor_4 BIGINT NOT NULL DEFAULT 131;\n--test proper denials for all security definer functions:\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE TABLE plain_table_su (time timestamp, temp float);\nCREATE TABLE hypertable_su (time timestamp, temp float);\nSELECT create_hypertable('hypertable_su', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n      create_hypertable      \n-----------------------------\n (11,public,hypertable_su,t)\n\nCREATE INDEX \"ind_1\" ON hypertable_su (time);\nINSERT INTO hypertable_su VALUES('2017-01-20T09:00:01', 22.5);\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n--all of the following should produce errors\n\\set ON_ERROR_STOP 0\nSELECT create_hypertable('plain_table_su', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\nERROR:  must be owner of hypertable \"plain_table_su\"\nCREATE INDEX ON plain_table_su (time, temp);\nERROR:  must be owner of table plain_table_su\nCREATE INDEX ON hypertable_su (time, temp);\nERROR:  must be owner of hypertable \"hypertable_su\"\nDROP INDEX \"ind_1\";\nERROR:  must be owner of index ind_1\nALTER INDEX \"ind_1\" RENAME TO \"ind_2\";\nERROR:  must be owner of index ind_1\n\\set ON_ERROR_STOP 1\n--test that I can't do anything to a non-owned hypertable.\n\\set ON_ERROR_STOP 0\nCREATE INDEX ON hypertable_su (time, temp);\nERROR:  must be owner of hypertable \"hypertable_su\"\nSELECT * FROM hypertable_su;\nERROR:  permission denied for table hypertable_su\nINSERT INTO hypertable_su VALUES('2017-01-20T09:00:01', 22.5);\nERROR:  permission denied for table hypertable_su\nALTER TABLE hypertable_su ADD COLUMN val2 integer;\nERROR:  must be owner of table hypertable_su\n\\set ON_ERROR_STOP 1\n--grant read permissions\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nGRANT SELECT ON hypertable_su TO :ROLE_DEFAULT_PERM_USER_2;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\nSELECT * FROM hypertable_su;\n           time           | temp \n--------------------------+------\n Fri Jan 20 09:00:01 2017 | 22.5\n\n\\set ON_ERROR_STOP 0\nCREATE INDEX ON hypertable_su (time, temp);\nERROR:  must be owner of hypertable \"hypertable_su\"\nINSERT INTO hypertable_su VALUES('2017-01-20T09:00:01', 22.5);\nERROR:  permission denied for table hypertable_su\nALTER TABLE hypertable_su ADD COLUMN val2 integer;\nERROR:  must be owner of table hypertable_su\n\\set ON_ERROR_STOP 1\n--grant read, insert permissions\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nGRANT SELECT, INSERT ON hypertable_su TO :ROLE_DEFAULT_PERM_USER_2;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\nINSERT INTO hypertable_su VALUES('2017-01-20T09:00:01', 22.5);\nSELECT * FROM hypertable_su;\n           time           | temp \n--------------------------+------\n Fri Jan 20 09:00:01 2017 | 22.5\n Fri Jan 20 09:00:01 2017 | 22.5\n\n\\set ON_ERROR_STOP 0\nCREATE INDEX ON hypertable_su (time, temp);\nERROR:  must be owner of hypertable \"hypertable_su\"\nALTER TABLE hypertable_su ADD COLUMN val2 integer;\nERROR:  must be owner of table hypertable_su\n\\set ON_ERROR_STOP 1\n--change owner\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nALTER TABLE hypertable_su OWNER TO :ROLE_DEFAULT_PERM_USER_2;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\nINSERT INTO hypertable_su VALUES('2017-01-20T09:00:01', 22.5);\nSELECT * FROM hypertable_su;\n           time           | temp \n--------------------------+------\n Fri Jan 20 09:00:01 2017 | 22.5\n Fri Jan 20 09:00:01 2017 | 22.5\n Fri Jan 20 09:00:01 2017 | 22.5\n\nCREATE INDEX ON hypertable_su (time, temp);\nALTER TABLE hypertable_su ADD COLUMN val2 integer;\n"
  },
  {
    "path": "test/expected/append-15.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\set TEST_BASE_NAME append\nSELECT format('include/%s_load.sql', :'TEST_BASE_NAME') as \"TEST_LOAD_NAME\",\n       format('include/%s_query.sql', :'TEST_BASE_NAME') as \"TEST_QUERY_NAME\",\n       format('%s/results/%s_results_optimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_OPTIMIZED\",\n       format('%s/results/%s_results_unoptimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_UNOPTIMIZED\"\n\\gset\nSELECT format('\\! diff -u --label \"Unoptimized results\" --label \"Optimized results\" %s %s', :'TEST_RESULTS_UNOPTIMIZED', :'TEST_RESULTS_OPTIMIZED') as \"DIFF_CMD\"\n\\gset\nSET timescaledb.enable_now_constify TO false;\n-- disable memoize node to avoid flaky results\nSET enable_memoize TO 'off';\n-- disable index only scans to avoid some flaky results\nSET enable_indexonlyscan TO FALSE;\n\\set PREFIX 'EXPLAIN (analyze, buffers off, costs off, timing off, summary off)'\n\\ir :TEST_LOAD_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- create a now() function for repeatable testing that always returns\n-- the same timestamp. It needs to be marked STABLE\nCREATE OR REPLACE FUNCTION now_s()\nRETURNS timestamptz LANGUAGE PLPGSQL STABLE PARALLEL SAFE AS\n$BODY$\nBEGIN\n    RAISE NOTICE 'Stable function now_s() called!';\n    RETURN '2017-08-22T10:00:00'::timestamptz;\nEND;\n$BODY$;\nCREATE OR REPLACE FUNCTION now_i()\nRETURNS timestamptz LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nBEGIN\n    RAISE NOTICE 'Immutable function now_i() called!';\n    RETURN '2017-08-22T10:00:00'::timestamptz;\nEND;\n$BODY$;\nCREATE OR REPLACE FUNCTION now_v()\nRETURNS timestamptz LANGUAGE PLPGSQL VOLATILE AS\n$BODY$\nBEGIN\n    RAISE NOTICE 'Volatile function now_v() called!';\n    RETURN '2017-08-22T10:00:00'::timestamptz;\nEND;\n$BODY$;\nCREATE OR REPLACE PROCEDURE force_parallel(on_or_off bool)\nLANGUAGE PLPGSQL AS\n$$\nBEGIN\n    IF current_setting('server_version_num')::int < 160000 THEN\n        IF on_or_off THEN\n            set force_parallel_mode = 'on';\n        ELSE\n            set force_parallel_mode = 'off';\n        END IF;\n    ELSE\n        IF on_or_off THEN\n            set debug_parallel_query = 'on';\n        ELSE\n            set debug_parallel_query = 'off';\n        END IF;\n    END IF;\nEND;\n$$;\nCREATE TABLE append_test(time timestamptz, temp float, colorid integer, attr jsonb);\nSELECT create_hypertable('append_test', 'time', chunk_time_interval => 2628000000000);\n    create_hypertable     \n--------------------------\n (1,public,append_test,t)\n\n-- create three chunks\nINSERT INTO append_test VALUES ('2017-03-22T09:18:22', 23.5, 1, '{\"a\": 1, \"b\": 2}'),\n                               ('2017-03-22T09:18:23', 21.5, 1, '{\"a\": 1, \"b\": 2}'),\n                               ('2017-05-22T09:18:22', 36.2, 2, '{\"c\": 3, \"b\": 2}'),\n                               ('2017-05-22T09:18:23', 15.2, 2, '{\"c\": 3}'),\n                               ('2017-08-22T09:18:22', 34.1, 3, '{\"c\": 4}');\nVACUUM (ANALYZE) append_test;\n-- Create another hypertable to join with\nCREATE TABLE join_test(time timestamptz, temp float, colorid integer);\nSELECT create_hypertable('join_test', 'time', chunk_time_interval => 2628000000000);\n   create_hypertable    \n------------------------\n (2,public,join_test,t)\n\nINSERT INTO join_test VALUES ('2017-01-22T09:18:22', 15.2, 1),\n                             ('2017-02-22T09:18:22', 24.5, 2),\n                             ('2017-08-22T09:18:22', 23.1, 3);\nVACUUM (ANALYZE) join_test;\n-- Create another table to join with which is not a hypertable.\nCREATE TABLE join_test_plain(time timestamptz, temp float, colorid integer, attr jsonb);\nINSERT INTO join_test_plain VALUES ('2017-01-22T09:18:22', 15.2, 1, '{\"a\": 1}'),\n                             ('2017-02-22T09:18:22', 24.5, 2, '{\"b\": 2}'),\n                             ('2017-08-22T09:18:22', 23.1, 3, '{\"c\": 3}');\nVACUUM (ANALYZE) join_test_plain;\n-- create hypertable with DATE time dimension\nCREATE TABLE metrics_date(time DATE NOT NULL);\nSELECT create_hypertable('metrics_date','time');\n     create_hypertable     \n---------------------------\n (3,public,metrics_date,t)\n\nINSERT INTO metrics_date SELECT generate_series('2000-01-01'::date, '2000-02-01'::date, '5m'::interval);\nVACUUM (ANALYZE) metrics_date;\n-- create hypertable with TIMESTAMP time dimension\nCREATE TABLE metrics_timestamp(time TIMESTAMP NOT NULL);\nSELECT create_hypertable('metrics_timestamp','time');\npsql:include/append_load.sql:91: WARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n       create_hypertable        \n--------------------------------\n (4,public,metrics_timestamp,t)\n\nINSERT INTO metrics_timestamp SELECT generate_series('2000-01-01'::date, '2000-02-01'::date, '5m'::interval);\nVACUUM (ANALYZE) metrics_timestamp;\n-- create hypertable with TIMESTAMPTZ time dimension\nCREATE TABLE metrics_timestamptz(time TIMESTAMPTZ NOT NULL, device_id INT NOT NULL);\nCREATE INDEX ON metrics_timestamptz(device_id,time);\nSELECT create_hypertable('metrics_timestamptz','time');\n        create_hypertable         \n----------------------------------\n (5,public,metrics_timestamptz,t)\n\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::date, '2000-02-01'::date, '5m'::interval), 1;\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::date, '2000-02-01'::date, '5m'::interval), 2;\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::date, '2000-02-01'::date, '5m'::interval), 3;\nVACUUM (ANALYZE) metrics_timestamptz;\n-- create space partitioned hypertable\nCREATE TABLE metrics_space(time timestamptz NOT NULL, device_id int NOT NULL, v1 float, v2 float, v3 text);\nSELECT create_hypertable('metrics_space','time','device_id',3);\n     create_hypertable      \n----------------------------\n (6,public,metrics_space,t)\n\nINSERT INTO metrics_space\nSELECT time, device_id, device_id + 0.25, device_id + 0.75, device_id\nFROM generate_series('2000-01-01'::timestamptz, '2000-01-14'::timestamptz, '5m'::interval) g1(time),\n  generate_series(1,10,1) g2(device_id)\nORDER BY time, device_id;\nVACUUM (ANALYZE) metrics_space;\n-- test ChunkAppend projection #2661\nCREATE TABLE i2661 (\n  machine_id int4 NOT NULL,\n  \"name\" varchar(255) NOT NULL,\n  \"timestamp\" timestamptz NOT NULL,\n  \"first\" float4 NULL\n);\nSELECT create_hypertable('i2661', 'timestamp');\npsql:include/append_load.sql:123: WARNING:  column type \"character varying\" used for \"name\" does not follow best practices\n create_hypertable  \n--------------------\n (7,public,i2661,t)\n\nINSERT INTO i2661 SELECT 1, 'speed', generate_series('2019-12-31 00:00:00', '2020-01-10 00:00:00', '2m'::interval), 0;\nVACUUM (ANALYZE) i2661;\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- canary for results diff\n-- this should be the only output of the results diff\nSELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations'),('timescaledb.enable_chunk_append')) v(setting);\n             setting              | value \n----------------------------------+-------\n timescaledb.enable_optimizations | on\n timescaledb.enable_chunk_append  | on\n\n-- query should exclude all chunks with optimization on\n:PREFIX\nSELECT * FROM append_test WHERE time > now_s() + '1 month'\nORDER BY time DESC;\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Sort (actual rows=0.00 loops=1)\n   Sort Key: append_test.\"time\" DESC\n   Sort Method: quicksort \n   ->  Custom Scan (ChunkAppend) on append_test (actual rows=0.00 loops=1)\n         Chunks excluded during startup: 3\n\n--query should exclude all chunks and be a MergeAppend\n:PREFIX\nSELECT * FROM append_test WHERE time > now_s() + '1 month'\nORDER BY time DESC limit 1;\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Limit (actual rows=0.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on append_test (actual rows=0.00 loops=1)\n         Order: append_test.\"time\" DESC\n         Chunks excluded during startup: 3\n\n-- when optimized, the plan should be a constraint-aware append and\n-- cover only one chunk. It should be a backward index scan due to\n-- descending index on time. Should also skip the main table, since it\n-- cannot hold tuples\n:PREFIX\nSELECT * FROM append_test WHERE time > now_s() - interval '2 months';\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on append_test (actual rows=1.00 loops=1)\n   Chunks excluded during startup: 2\n   ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         Filter: (\"time\" > (now_s() - '@ 2 mons'::interval))\n\n-- adding ORDER BY and LIMIT should turn the plan into an optimized\n-- ordered append plan\n:PREFIX\nSELECT * FROM append_test WHERE time > now_s() - interval '2 months'\nORDER BY time LIMIT 3;\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Sort (actual rows=1.00 loops=1)\n         Sort Key: append_test.\"time\"\n         Sort Method: quicksort \n         ->  Custom Scan (ChunkAppend) on append_test (actual rows=1.00 loops=1)\n               Chunks excluded during startup: 2\n               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n                     Filter: (\"time\" > (now_s() - '@ 2 mons'::interval))\n\n-- no optimized plan for queries with restrictions that can be\n-- constified at planning time. Regular planning-time constraint\n-- exclusion should occur.\n:PREFIX\nSELECT * FROM append_test WHERE time > now_i() - interval '2 months'\nORDER BY time;\npsql:include/append_query.sql:37: NOTICE:  Immutable function now_i() called!\n--- QUERY PLAN ---\n Sort (actual rows=1.00 loops=1)\n   Sort Key: append_test.\"time\"\n   Sort Method: quicksort \n   ->  Custom Scan (ChunkAppend) on append_test (actual rows=1.00 loops=1)\n         Chunks excluded during startup: 2\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               Filter: (\"time\" > ('Tue Aug 22 10:00:00 2017 PDT'::timestamp with time zone - '@ 2 mons'::interval))\n\n-- currently, we cannot distinguish between stable and volatile\n-- functions as far as applying our modified plan. However, volatile\n-- function should not be pre-evaluated to constants, so no chunk\n-- exclusion should occur.\n:PREFIX\nSELECT * FROM append_test WHERE time > now_v() - interval '2 months'\nORDER BY time;\npsql:include/append_query.sql:45: NOTICE:  Volatile function now_v() called!\npsql:include/append_query.sql:45: NOTICE:  Volatile function now_v() called!\npsql:include/append_query.sql:45: NOTICE:  Volatile function now_v() called!\npsql:include/append_query.sql:45: NOTICE:  Volatile function now_v() called!\npsql:include/append_query.sql:45: NOTICE:  Volatile function now_v() called!\n--- QUERY PLAN ---\n Sort (actual rows=1.00 loops=1)\n   Sort Key: append_test.\"time\"\n   Sort Method: quicksort \n   ->  Custom Scan (ChunkAppend) on append_test (actual rows=1.00 loops=1)\n         Chunks excluded during startup: 0\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=0.00 loops=1)\n               Filter: (\"time\" > (now_v() - '@ 2 mons'::interval))\n               Rows Removed by Filter: 2\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=0.00 loops=1)\n               Filter: (\"time\" > (now_v() - '@ 2 mons'::interval))\n               Rows Removed by Filter: 2\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               Filter: (\"time\" > (now_v() - '@ 2 mons'::interval))\n\n-- prepared statement output should be the same regardless of\n-- optimizations\nPREPARE query_opt AS\nSELECT * FROM append_test WHERE time > now_s() - interval '2 months'\nORDER BY time;\n:PREFIX EXECUTE query_opt;\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Sort (actual rows=1.00 loops=1)\n   Sort Key: append_test.\"time\"\n   Sort Method: quicksort \n   ->  Custom Scan (ChunkAppend) on append_test (actual rows=1.00 loops=1)\n         Chunks excluded during startup: 2\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               Filter: (\"time\" > (now_s() - '@ 2 mons'::interval))\n\nDEALLOCATE query_opt;\n-- aggregates should produce same output\n:PREFIX\nSELECT date_trunc('year', time) t, avg(temp) FROM append_test\nWHERE time > now_s() - interval '4 months'\nGROUP BY t\nORDER BY t DESC;\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n GroupAggregate (actual rows=1.00 loops=1)\n   Group Key: (date_trunc('year'::text, append_test.\"time\"))\n   ->  Sort (actual rows=3.00 loops=1)\n         Sort Key: (date_trunc('year'::text, append_test.\"time\")) DESC\n         Sort Method: quicksort \n         ->  Result (actual rows=3.00 loops=1)\n               ->  Custom Scan (ChunkAppend) on append_test (actual rows=3.00 loops=1)\n                     Chunks excluded during startup: 1\n                     ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n                           Filter: (\"time\" > (now_s() - '@ 4 mons'::interval))\n                     ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n                           Filter: (\"time\" > (now_s() - '@ 4 mons'::interval))\n\n-- querying outside the time range should return nothing. This tests\n-- that ConstraintAwareAppend can handle the case when an Append node\n-- is turned into a Result node due to no children\n:PREFIX\nSELECT date_trunc('year', time) t, avg(temp)\nFROM append_test\nWHERE time < '2016-03-22'\nAND date_part('dow', time) between 1 and 5\nGROUP BY t\nORDER BY t DESC;\n--- QUERY PLAN ---\n GroupAggregate (actual rows=0.00 loops=1)\n   Group Key: (date_trunc('year'::text, \"time\"))\n   ->  Sort (actual rows=0.00 loops=1)\n         Sort Key: (date_trunc('year'::text, \"time\")) DESC\n         Sort Method: quicksort \n         ->  Result (actual rows=0.00 loops=1)\n               One-Time Filter: false\n\n-- a parameterized query can safely constify params, so won't be\n-- optimized by constraint-aware append since regular constraint\n-- exclusion works just fine\nPREPARE query_param AS\nSELECT * FROM append_test WHERE time > $1 ORDER BY time;\n:PREFIX\nEXECUTE query_param(now_s() - interval '2 months');\npsql:include/append_query.sql:82: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Sort (actual rows=1.00 loops=1)\n   Sort Key: _hyper_1_3_chunk.\"time\"\n   Sort Method: quicksort \n   ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n\nDEALLOCATE query_param;\n--test with cte\n:PREFIX\nWITH data AS (\n    SELECT time_bucket(INTERVAL '30 day', TIME) AS btime, AVG(temp) AS VALUE\n    FROM append_test\n    WHERE\n        TIME > now_s() - INTERVAL '400 day'\n    AND colorid > 0\n    GROUP BY btime\n),\nperiod AS (\n    SELECT time_bucket(INTERVAL '30 day', TIME) AS btime\n      FROM  GENERATE_SERIES('2017-03-22T01:01:01', '2017-08-23T01:01:01', INTERVAL '30 day') TIME\n  )\nSELECT period.btime, VALUE\n    FROM period\n    LEFT JOIN DATA USING (btime)\n    ORDER BY period.btime;\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Sort (actual rows=6.00 loops=1)\n   Sort Key: (time_bucket('@ 30 days'::interval, \"time\".\"time\"))\n   Sort Method: quicksort \n   ->  Hash Left Join (actual rows=6.00 loops=1)\n         Hash Cond: (time_bucket('@ 30 days'::interval, \"time\".\"time\") = data.btime)\n         ->  Function Scan on generate_series \"time\" (actual rows=6.00 loops=1)\n         ->  Hash (actual rows=3.00 loops=1)\n               ->  Subquery Scan on data (actual rows=3.00 loops=1)\n                     ->  HashAggregate (actual rows=3.00 loops=1)\n                           Group Key: time_bucket('@ 30 days'::interval, append_test.\"time\")\n                           ->  Result (actual rows=5.00 loops=1)\n                                 ->  Custom Scan (ChunkAppend) on append_test (actual rows=5.00 loops=1)\n                                       Chunks excluded during startup: 0\n                                       ->  Seq Scan on _hyper_1_1_chunk (actual rows=2.00 loops=1)\n                                             Filter: ((colorid > 0) AND (\"time\" > (now_s() - '@ 400 days'::interval)))\n                                       ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n                                             Filter: ((colorid > 0) AND (\"time\" > (now_s() - '@ 400 days'::interval)))\n                                       ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n                                             Filter: ((colorid > 0) AND (\"time\" > (now_s() - '@ 400 days'::interval)))\n\nWITH data AS (\n    SELECT time_bucket(INTERVAL '30 day', TIME) AS btime, AVG(temp) AS VALUE\n    FROM append_test\n    WHERE\n        TIME > now_s() - INTERVAL '400 day'\n    AND colorid > 0\n    GROUP BY btime\n),\nperiod AS (\n    SELECT time_bucket(INTERVAL '30 day', TIME) AS btime\n      FROM  GENERATE_SERIES('2017-03-22T01:01:01', '2017-08-23T01:01:01', INTERVAL '30 day') TIME\n  )\nSELECT period.btime, VALUE\n    FROM period\n    LEFT JOIN DATA USING (btime)\n    ORDER BY period.btime;\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\n            btime             | value \n------------------------------+-------\n Fri Mar 03 16:00:00 2017 PST |  22.5\n Sun Apr 02 17:00:00 2017 PDT |      \n Tue May 02 17:00:00 2017 PDT |  25.7\n Thu Jun 01 17:00:00 2017 PDT |      \n Sat Jul 01 17:00:00 2017 PDT |      \n Mon Jul 31 17:00:00 2017 PDT |  34.1\n\n-- force nested loop join with no materialization. This tests that the\n-- inner ConstraintAwareScan supports resetting its scan for every\n-- iteration of the outer relation loop\nset enable_hashjoin = 'off';\nset enable_mergejoin = 'off';\nset enable_material = 'off';\n:PREFIX\nSELECT * FROM append_test a INNER JOIN join_test j ON (a.colorid = j.colorid)\nWHERE a.time > now_s() - interval '3 hours' AND j.time > now_s() - interval '3 hours';\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Nested Loop (actual rows=1.00 loops=1)\n   Join Filter: (a.colorid = j.colorid)\n   ->  Custom Scan (ChunkAppend) on append_test a (actual rows=1.00 loops=1)\n         Chunks excluded during startup: 2\n         ->  Seq Scan on _hyper_1_3_chunk a_1 (actual rows=1.00 loops=1)\n               Filter: (\"time\" > (now_s() - '@ 3 hours'::interval))\n   ->  Custom Scan (ChunkAppend) on join_test j (actual rows=1.00 loops=1)\n         Chunks excluded during startup: 2\n         ->  Seq Scan on _hyper_2_6_chunk j_1 (actual rows=1.00 loops=1)\n               Filter: (\"time\" > (now_s() - '@ 3 hours'::interval))\n\nreset enable_hashjoin;\nreset enable_mergejoin;\nreset enable_material;\n-- test constraint_exclusion with date time dimension and DATE/TIMESTAMP/TIMESTAMPTZ constraints\n-- the queries should all have 3 chunks\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=4609.00 loops=1)\n   Order: metrics_date.\"time\"\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_3_11_chunk_metrics_date_time_idx on _hyper_3_11_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=4609.00 loops=1)\n   Order: metrics_date.\"time\"\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_3_11_chunk_metrics_date_time_idx on _hyper_3_11_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=4609.00 loops=1)\n   Order: metrics_date.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=2016.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_3_11_chunk_metrics_date_time_idx on _hyper_3_11_chunk (actual rows=1441.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n\n-- test Const OP Var\n-- the queries should all have 3 chunks\n:PREFIX SELECT * FROM metrics_date WHERE '2000-01-15'::date < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=4609.00 loops=1)\n   Order: metrics_date.\"time\"\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_3_11_chunk_metrics_date_time_idx on _hyper_3_11_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_date WHERE '2000-01-15'::timestamp < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=4609.00 loops=1)\n   Order: metrics_date.\"time\"\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_3_11_chunk_metrics_date_time_idx on _hyper_3_11_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_date WHERE '2000-01-15'::timestamptz < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=4609.00 loops=1)\n   Order: metrics_date.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=2016.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_3_11_chunk_metrics_date_time_idx on _hyper_3_11_chunk (actual rows=1441.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n\n-- test 2 constraints\n-- the queries should all have 2 chunks\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::date AND time < '2000-01-21'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=1440.00 loops=1)\n   Order: metrics_date.\"time\"\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: ((\"time\" > '01-15-2000'::date) AND (\"time\" < '01-21-2000'::date))\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=288.00 loops=1)\n         Index Cond: ((\"time\" > '01-15-2000'::date) AND (\"time\" < '01-21-2000'::date))\n\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::timestamp AND time < '2000-01-21'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=1440.00 loops=1)\n   Order: metrics_date.\"time\"\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000'::timestamp without time zone))\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=288.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000'::timestamp without time zone))\n\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::timestamptz AND time < '2000-01-21'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=1440.00 loops=1)\n   Order: metrics_date.\"time\"\n   Chunks excluded during startup: 3\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=288.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000 PST'::timestamp with time zone))\n\n-- test constraint_exclusion with timestamp time dimension and DATE/TIMESTAMP/TIMESTAMPTZ constraints\n-- the queries should all have 3 chunks\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=4896.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_4_16_chunk_metrics_timestamp_time_idx on _hyper_4_16_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=4896.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_4_16_chunk_metrics_timestamp_time_idx on _hyper_4_16_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=4896.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=2016.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_4_16_chunk_metrics_timestamp_time_idx on _hyper_4_16_chunk (actual rows=1441.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n\n-- test Const OP Var\n-- the queries should all have 3 chunks\n:PREFIX SELECT * FROM metrics_timestamp WHERE '2000-01-15'::date < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=4896.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_4_16_chunk_metrics_timestamp_time_idx on _hyper_4_16_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE '2000-01-15'::timestamp < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=4896.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_4_16_chunk_metrics_timestamp_time_idx on _hyper_4_16_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE '2000-01-15'::timestamptz < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=4896.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=2016.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_4_16_chunk_metrics_timestamp_time_idx on _hyper_4_16_chunk (actual rows=1441.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n\n-- test 2 constraints\n-- the queries should all have 2 chunks\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::date AND time < '2000-01-21'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=1727.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: ((\"time\" > '01-15-2000'::date) AND (\"time\" < '01-21-2000'::date))\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=288.00 loops=1)\n         Index Cond: ((\"time\" > '01-15-2000'::date) AND (\"time\" < '01-21-2000'::date))\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::timestamp AND time < '2000-01-21'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=1727.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000'::timestamp without time zone))\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=288.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000'::timestamp without time zone))\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::timestamptz AND time < '2000-01-21'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=1727.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 3\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=288.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000 PST'::timestamp with time zone))\n\n-- test constraint_exclusion with timestamptz time dimension and DATE/TIMESTAMP/TIMESTAMPTZ constraints\n-- the queries should all have 3 chunks\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=14688.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=14688.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=14688.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n   ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n\n-- test Const OP Var\n-- the queries should all have 3 chunks\n:PREFIX SELECT time FROM metrics_timestamptz WHERE '2000-01-15'::date < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=14688.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE '2000-01-15'::timestamp < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=14688.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE '2000-01-15'::timestamptz < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=14688.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n   ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n\n-- test 2 constraints\n-- the queries should all have 2 chunks\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::date AND time < '2000-01-21'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=5181.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 3\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: ((\"time\" > '01-15-2000'::date) AND (\"time\" < '01-21-2000'::date))\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=1152.00 loops=1)\n         Index Cond: ((\"time\" > '01-15-2000'::date) AND (\"time\" < '01-21-2000'::date))\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::timestamp AND time < '2000-01-21'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=5181.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 3\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000'::timestamp without time zone))\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=1152.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000'::timestamp without time zone))\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::timestamptz AND time < '2000-01-21'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=5181.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=1152.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000 PST'::timestamp with time zone))\n\n-- test constraint_exclusion with space partitioning and DATE/TIMESTAMP/TIMESTAMPTZ constraints\n-- exclusion for constraints with non-matching datatypes not working for space partitioning atm\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n-- test Const OP Var\n-- exclusion for constraints with non-matching datatypes not working for space partitioning atm\n:PREFIX SELECT time FROM metrics_space WHERE '2000-01-10'::date < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n\n:PREFIX SELECT time FROM metrics_space WHERE '2000-01-10'::timestamp < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT time FROM metrics_space WHERE '2000-01-10'::timestamptz < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n-- test 2 constraints\n-- exclusion for constraints with non-matching datatypes not working for space partitioning atm\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::date AND time < '2000-01-15'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamp AND time < '2000-01-15'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz AND time < '2000-01-15'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone))\n\n-- test filtering on space partition\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz AND device_id = 1 ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=1152.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_device_id_time_idx on _hyper_6_25_chunk (actual rows=767.00 loops=1)\n         Index Cond: ((device_id = 1) AND (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_device_id_time_idx on _hyper_6_28_chunk (actual rows=385.00 loops=1)\n         Index Cond: ((device_id = 1) AND (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz AND device_id IN (1,2) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=2304.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=1534.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=767.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (device_id = ANY ('{1,2}'::integer[]))\n               Rows Removed by Filter: 2301\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=767.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (device_id = ANY ('{1,2}'::integer[]))\n               Rows Removed by Filter: 2301\n   ->  Merge Append (actual rows=770.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=385.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (device_id = ANY ('{1,2}'::integer[]))\n               Rows Removed by Filter: 1155\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=385.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (device_id = ANY ('{1,2}'::integer[]))\n               Rows Removed by Filter: 1155\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz AND device_id IN (VALUES(1)) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=1152.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_device_id_time_idx on _hyper_6_25_chunk (actual rows=767.00 loops=1)\n         Index Cond: ((device_id = 1) AND (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_device_id_time_idx on _hyper_6_28_chunk (actual rows=385.00 loops=1)\n         Index Cond: ((device_id = 1) AND (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz AND v3 IN (VALUES('1')) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=1152.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=767.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=767.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (v3 = '1'::text)\n               Rows Removed by Filter: 2301\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (v3 = '1'::text)\n               Rows Removed by Filter: 3068\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (v3 = '1'::text)\n               Rows Removed by Filter: 1534\n   ->  Merge Append (actual rows=385.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=385.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (v3 = '1'::text)\n               Rows Removed by Filter: 1155\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (v3 = '1'::text)\n               Rows Removed by Filter: 1540\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (v3 = '1'::text)\n               Rows Removed by Filter: 770\n\n:PREFIX SELECT * FROM metrics_space\nWHERE time = (VALUES ('2019-12-24' at time zone 'UTC'))\n  AND v3 NOT IN (VALUES ('1'));\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=0.00 loops=1)\n   Chunks excluded during startup: 0\n   Chunks excluded during runtime: 9\n   InitPlan 1 (returns $0)\n     ->  Result (actual rows=1.00 loops=1)\n   ->  Index Scan using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (never executed)\n         Index Cond: (\"time\" = $0)\n         Filter: (NOT (hashed SubPlan 2))\n         SubPlan 2\n           ->  Result (never executed)\n   ->  Index Scan using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (never executed)\n         Index Cond: (\"time\" = $0)\n         Filter: (NOT (hashed SubPlan 2))\n   ->  Index Scan using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (never executed)\n         Index Cond: (\"time\" = $0)\n         Filter: (NOT (hashed SubPlan 2))\n   ->  Index Scan using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (never executed)\n         Index Cond: (\"time\" = $0)\n         Filter: (NOT (hashed SubPlan 2))\n   ->  Index Scan using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (never executed)\n         Index Cond: (\"time\" = $0)\n         Filter: (NOT (hashed SubPlan 2))\n   ->  Index Scan using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (never executed)\n         Index Cond: (\"time\" = $0)\n         Filter: (NOT (hashed SubPlan 2))\n   ->  Index Scan using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (never executed)\n         Index Cond: (\"time\" = $0)\n         Filter: (NOT (hashed SubPlan 2))\n   ->  Index Scan using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (never executed)\n         Index Cond: (\"time\" = $0)\n         Filter: (NOT (hashed SubPlan 2))\n   ->  Index Scan using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (never executed)\n         Index Cond: (\"time\" = $0)\n         Filter: (NOT (hashed SubPlan 2))\n\n-- test CURRENT_DATE\n-- should be 0 chunks\n:PREFIX SELECT time FROM metrics_date WHERE time > CURRENT_DATE ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=0.00 loops=1)\n   Order: metrics_date.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_timestamp WHERE time > CURRENT_DATE ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=0.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > CURRENT_DATE ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=0.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_space WHERE time > CURRENT_DATE ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=0.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n\n-- test CURRENT_TIMESTAMP\n-- should be 0 chunks\n:PREFIX SELECT time FROM metrics_date WHERE time > CURRENT_TIMESTAMP ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=0.00 loops=1)\n   Order: metrics_date.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_timestamp WHERE time > CURRENT_TIMESTAMP ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=0.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > CURRENT_TIMESTAMP ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=0.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_space WHERE time > CURRENT_TIMESTAMP ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=0.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n\n-- test now()\n-- should be 0 chunks\n:PREFIX SELECT time FROM metrics_date WHERE time > now() ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=0.00 loops=1)\n   Order: metrics_date.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_timestamp WHERE time > now() ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=0.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > now() ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=0.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_space WHERE time > now() ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=0.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n\n-- query with tablesample and planner exclusion\n:PREFIX\nSELECT * FROM metrics_date TABLESAMPLE BERNOULLI(5) REPEATABLE(0)\nWHERE time > '2000-01-15'\nORDER BY time DESC;\n--- QUERY PLAN ---\n Sort (actual rows=217.00 loops=1)\n   Sort Key: metrics_date.\"time\" DESC\n   Sort Method: quicksort \n   ->  Append (actual rows=217.00 loops=1)\n         ->  Sample Scan on _hyper_3_11_chunk (actual rows=72.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n         ->  Sample Scan on _hyper_3_10_chunk (actual rows=94.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n         ->  Sample Scan on _hyper_3_9_chunk (actual rows=51.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > '01-15-2000'::date)\n               Rows Removed by Filter: 43\n\n-- query with tablesample and startup exclusion\n:PREFIX\nSELECT * FROM metrics_date TABLESAMPLE BERNOULLI(5) REPEATABLE(0)\nWHERE time > '2000-01-15'::text::date\nORDER BY time DESC;\n--- QUERY PLAN ---\n Sort (actual rows=217.00 loops=1)\n   Sort Key: metrics_date.\"time\" DESC\n   Sort Method: quicksort \n   ->  Custom Scan (ChunkAppend) on metrics_date (actual rows=217.00 loops=1)\n         Chunks excluded during startup: 2\n         ->  Sample Scan on _hyper_3_11_chunk (actual rows=72.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > ('2000-01-15'::cstring)::date)\n         ->  Sample Scan on _hyper_3_10_chunk (actual rows=94.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > ('2000-01-15'::cstring)::date)\n         ->  Sample Scan on _hyper_3_9_chunk (actual rows=51.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > ('2000-01-15'::cstring)::date)\n               Rows Removed by Filter: 43\n\n-- query with tablesample, space partitioning and planner exclusion\n:PREFIX\nSELECT * FROM metrics_space TABLESAMPLE BERNOULLI(5) REPEATABLE(0)\nWHERE time > '2000-01-10'::timestamptz\nORDER BY time DESC, device_id;\n--- QUERY PLAN ---\n Sort (actual rows=522.00 loops=1)\n   Sort Key: metrics_space.\"time\" DESC, metrics_space.device_id\n   Sort Method: quicksort \n   ->  Append (actual rows=522.00 loops=1)\n         ->  Sample Scan on _hyper_6_30_chunk (actual rows=35.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Sample Scan on _hyper_6_29_chunk (actual rows=61.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Sample Scan on _hyper_6_28_chunk (actual rows=61.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Sample Scan on _hyper_6_27_chunk (actual rows=65.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Rows Removed by Filter: 113\n         ->  Sample Scan on _hyper_6_26_chunk (actual rows=150.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Rows Removed by Filter: 218\n         ->  Sample Scan on _hyper_6_25_chunk (actual rows=150.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Rows Removed by Filter: 218\n\n-- test runtime exclusion\n-- test runtime exclusion with LATERAL and 2 hypertables\n:PREFIX SELECT m1.time, m2.time FROM metrics_timestamptz m1 LEFT JOIN LATERAL(SELECT time FROM metrics_timestamptz m2 WHERE m1.time = m2.time LIMIT 1) m2 ON true ORDER BY m1.time;\n--- QUERY PLAN ---\n Nested Loop Left Join (actual rows=26787.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1 (actual rows=26787.00 loops=1)\n         Order: m1.\"time\"\n         ->  Index Scan Backward using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m1_1 (actual rows=4032.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m1_2 (actual rows=6048.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m1_3 (actual rows=6048.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m1_4 (actual rows=6048.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m1_5 (actual rows=4611.00 loops=1)\n   ->  Limit (actual rows=1.00 loops=26787)\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m2 (actual rows=1.00 loops=26787)\n               Chunks excluded during runtime: 4\n               ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m2_1 (actual rows=1.00 loops=4032)\n                     Index Cond: (\"time\" = m1.\"time\")\n               ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m2_2 (actual rows=1.00 loops=6048)\n                     Index Cond: (\"time\" = m1.\"time\")\n               ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m2_3 (actual rows=1.00 loops=6048)\n                     Index Cond: (\"time\" = m1.\"time\")\n               ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m2_4 (actual rows=1.00 loops=6048)\n                     Index Cond: (\"time\" = m1.\"time\")\n               ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m2_5 (actual rows=1.00 loops=4611)\n                     Index Cond: (\"time\" = m1.\"time\")\n\n-- test runtime exclusion and startup exclusions\n:PREFIX SELECT m1.time, m2.time FROM metrics_timestamptz m1 LEFT JOIN LATERAL(SELECT time FROM metrics_timestamptz m2 WHERE m1.time = m2.time AND m2.time < '2000-01-10'::text::timestamptz LIMIT 1) m2 ON true ORDER BY m1.time;\n--- QUERY PLAN ---\n Nested Loop Left Join (actual rows=26787.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1 (actual rows=26787.00 loops=1)\n         Order: m1.\"time\"\n         ->  Index Scan Backward using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m1_1 (actual rows=4032.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m1_2 (actual rows=6048.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m1_3 (actual rows=6048.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m1_4 (actual rows=6048.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m1_5 (actual rows=4611.00 loops=1)\n   ->  Limit (actual rows=0.00 loops=26787)\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m2 (actual rows=0.00 loops=26787)\n               Chunks excluded during startup: 3\n               Chunks excluded during runtime: 1\n               ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m2_1 (actual rows=1.00 loops=4032)\n                     Index Cond: ((\"time\" < ('2000-01-10'::cstring)::timestamp with time zone) AND (\"time\" = m1.\"time\"))\n               ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m2_2 (actual rows=1.00 loops=6048)\n                     Index Cond: ((\"time\" < ('2000-01-10'::cstring)::timestamp with time zone) AND (\"time\" = m1.\"time\"))\n\n-- test runtime exclusion does not activate for constraints on non-partitioning columns\n-- should not use runtime exclusion\n:PREFIX SELECT * FROM append_test a LEFT JOIN LATERAL(SELECT * FROM join_test j WHERE a.colorid = j.colorid ORDER BY time DESC LIMIT 1) j ON true ORDER BY a.time LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Nested Loop Left Join (actual rows=1.00 loops=1)\n         ->  Custom Scan (ChunkAppend) on append_test a (actual rows=1.00 loops=1)\n               Order: a.\"time\"\n               ->  Index Scan Backward using _hyper_1_1_chunk_append_test_time_idx on _hyper_1_1_chunk a_1 (actual rows=1.00 loops=1)\n               ->  Index Scan Backward using _hyper_1_2_chunk_append_test_time_idx on _hyper_1_2_chunk a_2 (never executed)\n               ->  Index Scan Backward using _hyper_1_3_chunk_append_test_time_idx on _hyper_1_3_chunk a_3 (never executed)\n         ->  Limit (actual rows=1.00 loops=1)\n               ->  Custom Scan (ChunkAppend) on join_test j (actual rows=1.00 loops=1)\n                     Order: j.\"time\" DESC\n                     Hypertables excluded during runtime: 0\n                     ->  Index Scan using _hyper_2_6_chunk_join_test_time_idx on _hyper_2_6_chunk j_1 (actual rows=0.00 loops=1)\n                           Filter: (a.colorid = colorid)\n                           Rows Removed by Filter: 1\n                     ->  Index Scan using _hyper_2_5_chunk_join_test_time_idx on _hyper_2_5_chunk j_2 (actual rows=0.00 loops=1)\n                           Filter: (a.colorid = colorid)\n                           Rows Removed by Filter: 1\n                     ->  Index Scan using _hyper_2_4_chunk_join_test_time_idx on _hyper_2_4_chunk j_3 (actual rows=1.00 loops=1)\n                           Filter: (a.colorid = colorid)\n\n-- test runtime exclusion with LATERAL and generate_series\n:PREFIX SELECT g.time FROM generate_series('2000-01-01'::timestamptz, '2000-02-01'::timestamptz, '1d'::interval) g(time) LEFT JOIN LATERAL(SELECT time FROM metrics_timestamptz m WHERE m.time=g.time LIMIT 1) m ON true;\n--- QUERY PLAN ---\n Nested Loop Left Join (actual rows=32.00 loops=1)\n   ->  Function Scan on generate_series g (actual rows=32.00 loops=1)\n   ->  Limit (actual rows=1.00 loops=32)\n         ->  Result (actual rows=1.00 loops=32)\n               ->  Custom Scan (ChunkAppend) on metrics_timestamptz m (actual rows=1.00 loops=32)\n                     Chunks excluded during runtime: 4\n                     ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m_1 (actual rows=1.00 loops=5)\n                           Index Cond: (\"time\" = g.\"time\")\n                     ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m_2 (actual rows=1.00 loops=7)\n                           Index Cond: (\"time\" = g.\"time\")\n                     ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m_3 (actual rows=1.00 loops=7)\n                           Index Cond: (\"time\" = g.\"time\")\n                     ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m_4 (actual rows=1.00 loops=7)\n                           Index Cond: (\"time\" = g.\"time\")\n                     ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m_5 (actual rows=1.00 loops=6)\n                           Index Cond: (\"time\" = g.\"time\")\n\n:PREFIX SELECT * FROM generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval) AS g(time) INNER JOIN LATERAL (SELECT time FROM metrics_timestamptz m WHERE time=g.time) m ON true;\n--- QUERY PLAN ---\n Hash Join (actual rows=96.00 loops=1)\n   Hash Cond: (g.\"time\" = m.\"time\")\n   ->  Function Scan on generate_series g (actual rows=32.00 loops=1)\n   ->  Hash (actual rows=26787.00 loops=1)\n         ->  Append (actual rows=26787.00 loops=1)\n               ->  Seq Scan on _hyper_5_17_chunk m_1 (actual rows=4032.00 loops=1)\n               ->  Seq Scan on _hyper_5_18_chunk m_2 (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_19_chunk m_3 (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_20_chunk m_4 (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_21_chunk m_5 (actual rows=4611.00 loops=1)\n\n:PREFIX SELECT * FROM generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval) AS g(time) INNER JOIN LATERAL (SELECT time FROM metrics_timestamptz m WHERE time=g.time ORDER BY time) m ON true;\n--- QUERY PLAN ---\n Nested Loop (actual rows=96.00 loops=1)\n   ->  Function Scan on generate_series g (actual rows=32.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m (actual rows=3.00 loops=32)\n         Chunks excluded during runtime: 4\n         ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m_1 (actual rows=3.00 loops=5)\n               Index Cond: (\"time\" = g.\"time\")\n         ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m_2 (actual rows=3.00 loops=7)\n               Index Cond: (\"time\" = g.\"time\")\n         ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m_3 (actual rows=3.00 loops=7)\n               Index Cond: (\"time\" = g.\"time\")\n         ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m_4 (actual rows=3.00 loops=7)\n               Index Cond: (\"time\" = g.\"time\")\n         ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m_5 (actual rows=3.00 loops=6)\n               Index Cond: (\"time\" = g.\"time\")\n\n:PREFIX SELECT * FROM generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval) AS g(time) INNER JOIN LATERAL (SELECT time FROM metrics_timestamptz m WHERE time>g.time + '1 day' ORDER BY time LIMIT 1) m ON true;\n--- QUERY PLAN ---\n Nested Loop (actual rows=30.00 loops=1)\n   ->  Function Scan on generate_series g (actual rows=32.00 loops=1)\n   ->  Limit (actual rows=1.00 loops=32)\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m (actual rows=1.00 loops=32)\n               Order: m.\"time\"\n               Chunks excluded during startup: 0\n               Chunks excluded during runtime: 2\n               ->  Index Scan Backward using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m_1 (actual rows=1.00 loops=4)\n                     Index Cond: (\"time\" > (g.\"time\" + '@ 1 day'::interval))\n               ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m_2 (actual rows=1.00 loops=7)\n                     Index Cond: (\"time\" > (g.\"time\" + '@ 1 day'::interval))\n               ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m_3 (actual rows=1.00 loops=7)\n                     Index Cond: (\"time\" > (g.\"time\" + '@ 1 day'::interval))\n               ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m_4 (actual rows=1.00 loops=7)\n                     Index Cond: (\"time\" > (g.\"time\" + '@ 1 day'::interval))\n               ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m_5 (actual rows=1.00 loops=7)\n                     Index Cond: (\"time\" > (g.\"time\" + '@ 1 day'::interval))\n\n-- test runtime exclusion with subquery\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 WHERE m1.time=(SELECT max(time) FROM metrics_timestamptz);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz m1 (actual rows=3.00 loops=1)\n   Chunks excluded during runtime: 4\n   InitPlan 2 (returns $1)\n     ->  Result (actual rows=1.00 loops=1)\n           InitPlan 1 (returns $0)\n             ->  Limit (actual rows=1.00 loops=1)\n                   ->  Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=1.00 loops=1)\n                         Order: metrics_timestamptz.\"time\" DESC\n                         ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=1.00 loops=1)\n                               Index Cond: (\"time\" IS NOT NULL)\n                         ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (never executed)\n                               Index Cond: (\"time\" IS NOT NULL)\n                         ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (never executed)\n                               Index Cond: (\"time\" IS NOT NULL)\n                         ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk (never executed)\n                               Index Cond: (\"time\" IS NOT NULL)\n                         ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk (never executed)\n                               Index Cond: (\"time\" IS NOT NULL)\n   ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m1_1 (never executed)\n         Index Cond: (\"time\" = $1)\n   ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m1_2 (never executed)\n         Index Cond: (\"time\" = $1)\n   ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m1_3 (never executed)\n         Index Cond: (\"time\" = $1)\n   ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m1_4 (never executed)\n         Index Cond: (\"time\" = $1)\n   ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m1_5 (actual rows=3.00 loops=1)\n         Index Cond: (\"time\" = $1)\n\n-- test runtime exclusion with correlated subquery\n:PREFIX SELECT m1.time, (SELECT m2.time FROM metrics_timestamptz m2 WHERE m2.time < m1.time ORDER BY m2.time DESC LIMIT 1) FROM metrics_timestamptz m1 WHERE m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Result (actual rows=7776.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1 (actual rows=7776.00 loops=1)\n         Order: m1.\"time\"\n         ->  Index Scan Backward using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m1_1 (actual rows=4032.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m1_2 (actual rows=3744.00 loops=1)\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   SubPlan 1\n     ->  Limit (actual rows=1.00 loops=7776)\n           ->  Custom Scan (ChunkAppend) on metrics_timestamptz m2 (actual rows=1.00 loops=7776)\n                 Order: m2.\"time\" DESC\n                 Chunks excluded during runtime: 3\n                 ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m2_1 (never executed)\n                       Index Cond: (\"time\" < m1.\"time\")\n                 ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m2_2 (never executed)\n                       Index Cond: (\"time\" < m1.\"time\")\n                 ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m2_3 (never executed)\n                       Index Cond: (\"time\" < m1.\"time\")\n                 ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m2_4 (actual rows=1.00 loops=3741)\n                       Index Cond: (\"time\" < m1.\"time\")\n                 ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m2_5 (actual rows=1.00 loops=4035)\n                       Index Cond: (\"time\" < m1.\"time\")\n\n-- test EXISTS\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 WHERE EXISTS(SELECT 1 FROM metrics_timestamptz m2 WHERE m1.time < m2.time) ORDER BY m1.time DESC limit 1000;\n--- QUERY PLAN ---\n Limit (actual rows=1000.00 loops=1)\n   ->  Nested Loop Semi Join (actual rows=1000.00 loops=1)\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1 (actual rows=1003.00 loops=1)\n               Order: m1.\"time\" DESC\n               ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m1_1 (actual rows=1003.00 loops=1)\n               ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m1_2 (never executed)\n               ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m1_3 (never executed)\n               ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m1_4 (never executed)\n               ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m1_5 (never executed)\n         ->  Append (actual rows=1.00 loops=1003)\n               ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m2_1 (actual rows=0.00 loops=1003)\n                     Index Cond: (\"time\" > m1.\"time\")\n               ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m2_2 (actual rows=0.00 loops=1003)\n                     Index Cond: (\"time\" > m1.\"time\")\n               ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m2_3 (actual rows=0.00 loops=1003)\n                     Index Cond: (\"time\" > m1.\"time\")\n               ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m2_4 (actual rows=0.00 loops=1003)\n                     Index Cond: (\"time\" > m1.\"time\")\n               ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m2_5 (actual rows=1.00 loops=1003)\n                     Index Cond: (\"time\" > m1.\"time\")\n\n-- test constraint exclusion for subqueries with append\n-- should include 2 chunks\n:PREFIX SELECT time FROM (SELECT time FROM metrics_timestamptz WHERE time < '2000-01-10'::text::timestamptz ORDER BY time) m;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=7776.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 3\n   ->  Index Scan Backward using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk (actual rows=4032.00 loops=1)\n         Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk (actual rows=3744.00 loops=1)\n         Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n\n-- test constraint exclusion for subqueries with mergeappend\n-- should include 2 chunks\n:PREFIX SELECT device_id, time FROM (SELECT device_id, time FROM metrics_timestamptz WHERE time < '2000-01-10'::text::timestamptz ORDER BY device_id, time) m;\n--- QUERY PLAN ---\n Custom Scan (ConstraintAwareAppend) (actual rows=7776.00 loops=1)\n   Hypertable: metrics_timestamptz\n   Chunks excluded during startup: 3\n   ->  Merge Append (actual rows=7776.00 loops=1)\n         Sort Key: metrics_timestamptz.device_id, metrics_timestamptz.\"time\"\n         ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_17_chunk (actual rows=4032.00 loops=1)\n               Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n         ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_18_chunk (actual rows=3744.00 loops=1)\n               Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n\n-- test LIMIT pushdown\n-- no aggregates/window functions/SRF should pushdown limit\n:PREFIX SELECT FROM metrics_timestamptz ORDER BY time LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=1.00 loops=1)\n         Order: metrics_timestamptz.\"time\"\n         ->  Index Scan Backward using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk (actual rows=1.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk (never executed)\n         ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (never executed)\n         ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (never executed)\n         ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (never executed)\n\n-- aggregates should prevent pushdown\n:PREFIX SELECT count(*) FROM metrics_timestamptz LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Aggregate (actual rows=1.00 loops=1)\n         ->  Append (actual rows=26787.00 loops=1)\n               ->  Seq Scan on _hyper_5_17_chunk (actual rows=4032.00 loops=1)\n               ->  Seq Scan on _hyper_5_18_chunk (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_19_chunk (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n\n:PREFIX SELECT count(*) FROM metrics_space LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Aggregate (actual rows=1.00 loops=1)\n         ->  Append (actual rows=37450.00 loops=1)\n               ->  Seq Scan on _hyper_6_22_chunk (actual rows=5376.00 loops=1)\n               ->  Seq Scan on _hyper_6_23_chunk (actual rows=5376.00 loops=1)\n               ->  Seq Scan on _hyper_6_24_chunk (actual rows=2688.00 loops=1)\n               ->  Seq Scan on _hyper_6_25_chunk (actual rows=8064.00 loops=1)\n               ->  Seq Scan on _hyper_6_26_chunk (actual rows=8064.00 loops=1)\n               ->  Seq Scan on _hyper_6_27_chunk (actual rows=4032.00 loops=1)\n               ->  Seq Scan on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               ->  Seq Scan on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               ->  Seq Scan on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n\n-- HAVING should prevent pushdown\n:PREFIX SELECT 1 FROM metrics_timestamptz HAVING count(*) > 1 LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Aggregate (actual rows=1.00 loops=1)\n         Filter: (count(*) > 1)\n         ->  Append (actual rows=26787.00 loops=1)\n               ->  Seq Scan on _hyper_5_17_chunk (actual rows=4032.00 loops=1)\n               ->  Seq Scan on _hyper_5_18_chunk (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_19_chunk (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n\n:PREFIX SELECT 1 FROM metrics_space HAVING count(*) > 1 LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Aggregate (actual rows=1.00 loops=1)\n         Filter: (count(*) > 1)\n         ->  Append (actual rows=37450.00 loops=1)\n               ->  Seq Scan on _hyper_6_22_chunk (actual rows=5376.00 loops=1)\n               ->  Seq Scan on _hyper_6_23_chunk (actual rows=5376.00 loops=1)\n               ->  Seq Scan on _hyper_6_24_chunk (actual rows=2688.00 loops=1)\n               ->  Seq Scan on _hyper_6_25_chunk (actual rows=8064.00 loops=1)\n               ->  Seq Scan on _hyper_6_26_chunk (actual rows=8064.00 loops=1)\n               ->  Seq Scan on _hyper_6_27_chunk (actual rows=4032.00 loops=1)\n               ->  Seq Scan on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               ->  Seq Scan on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               ->  Seq Scan on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n\n-- DISTINCT should prevent pushdown\nSET enable_hashagg TO false;\n:PREFIX SELECT DISTINCT device_id FROM metrics_timestamptz ORDER BY device_id LIMIT 3;\n--- QUERY PLAN ---\n Limit (actual rows=3.00 loops=1)\n   ->  Unique (actual rows=3.00 loops=1)\n         ->  Merge Append (actual rows=17859.00 loops=1)\n               Sort Key: metrics_timestamptz.device_id\n               ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_17_chunk (actual rows=2689.00 loops=1)\n               ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_18_chunk (actual rows=4033.00 loops=1)\n               ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_19_chunk (actual rows=4033.00 loops=1)\n               ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_20_chunk (actual rows=4033.00 loops=1)\n               ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_21_chunk (actual rows=3075.00 loops=1)\n\n:PREFIX SELECT DISTINCT device_id FROM metrics_space ORDER BY device_id LIMIT 3;\n--- QUERY PLAN ---\n Limit (actual rows=3.00 loops=1)\n   ->  Unique (actual rows=3.00 loops=1)\n         ->  Merge Append (actual rows=7491.00 loops=1)\n               Sort Key: metrics_space.device_id\n               ->  Index Scan using _hyper_6_22_chunk_metrics_space_device_id_time_idx on _hyper_6_22_chunk (actual rows=1345.00 loops=1)\n               ->  Index Scan using _hyper_6_23_chunk_metrics_space_device_id_time_idx on _hyper_6_23_chunk (actual rows=1345.00 loops=1)\n               ->  Index Scan using _hyper_6_24_chunk_metrics_space_device_id_time_idx on _hyper_6_24_chunk (actual rows=1.00 loops=1)\n               ->  Index Scan using _hyper_6_25_chunk_metrics_space_device_id_time_idx on _hyper_6_25_chunk (actual rows=2017.00 loops=1)\n               ->  Index Scan using _hyper_6_26_chunk_metrics_space_device_id_time_idx on _hyper_6_26_chunk (actual rows=2017.00 loops=1)\n               ->  Index Scan using _hyper_6_27_chunk_metrics_space_device_id_time_idx on _hyper_6_27_chunk (actual rows=1.00 loops=1)\n               ->  Index Scan using _hyper_6_28_chunk_metrics_space_device_id_time_idx on _hyper_6_28_chunk (actual rows=386.00 loops=1)\n               ->  Index Scan using _hyper_6_29_chunk_metrics_space_device_id_time_idx on _hyper_6_29_chunk (actual rows=386.00 loops=1)\n               ->  Index Scan using _hyper_6_30_chunk_metrics_space_device_id_time_idx on _hyper_6_30_chunk (actual rows=1.00 loops=1)\n\nRESET enable_hashagg;\n-- JOINs should prevent pushdown\n-- when LIMIT gets pushed to a Sort node it will switch to top-N heapsort\n-- if more tuples then LIMIT are requested this will trigger an error\n-- to trigger this we need a Sort node that is below ChunkAppend\nCREATE TABLE join_limit (time timestamptz, device_id int);\nSELECT table_name FROM create_hypertable('join_limit','time',create_default_indexes:=false);\n table_name \n------------\n join_limit\n\nCREATE INDEX ON join_limit(time,device_id);\nINSERT INTO join_limit\nSELECT time, device_id\nFROM generate_series('2000-01-01'::timestamptz,'2000-01-21','30m') g1(time),\n  generate_series(1,10,1) g2(device_id)\nORDER BY time, device_id;\nVACUUM (ANALYZE) join_limit;\n-- get 2nd chunk oid\nSELECT tableoid AS \"CHUNK_OID\" FROM join_limit WHERE time > '2000-01-07' ORDER BY time LIMIT 1\n\\gset\n--get index name for 2nd chunk\nSELECT indexrelid::regclass AS \"INDEX_NAME\" FROM pg_index WHERE indrelid = :CHUNK_OID\n\\gset\nDROP INDEX :INDEX_NAME;\n:PREFIX SELECT * FROM metrics_timestamptz m1 INNER JOIN join_limit m2 ON m1.time = m2.time AND m1.device_id=m2.device_id WHERE m1.time > '2000-01-07' ORDER BY m1.time, m1.device_id LIMIT 3;\n--- QUERY PLAN ---\n Limit (actual rows=3.00 loops=1)\n   ->  Merge Join (actual rows=3.00 loops=1)\n         Merge Cond: (m2.\"time\" = m1.\"time\")\n         Join Filter: (m1.device_id = m2.device_id)\n         Rows Removed by Join Filter: 4\n         ->  Custom Scan (ChunkAppend) on join_limit m2 (actual rows=3.00 loops=1)\n               Order: m2.\"time\", m2.device_id\n               ->  Sort (actual rows=3.00 loops=1)\n                     Sort Key: m2_1.\"time\", m2_1.device_id\n                     Sort Method: quicksort \n                     ->  Seq Scan on _hyper_8_35_chunk m2_1 (actual rows=2710.00 loops=1)\n                           Filter: (\"time\" > 'Fri Jan 07 00:00:00 2000 PST'::timestamp with time zone)\n                           Rows Removed by Filter: 650\n               ->  Index Scan using _hyper_8_36_chunk_join_limit_time_device_id_idx on _hyper_8_36_chunk m2_2 (never executed)\n               ->  Index Scan using _hyper_8_37_chunk_join_limit_time_device_id_idx on _hyper_8_37_chunk m2_3 (never executed)\n         ->  Materialize (actual rows=22.00 loops=1)\n               ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1 (actual rows=19.00 loops=1)\n                     Order: m1.\"time\"\n                     ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m1_1 (actual rows=19.00 loops=1)\n                           Index Cond: (\"time\" > 'Fri Jan 07 00:00:00 2000 PST'::timestamp with time zone)\n                     ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m1_2 (never executed)\n                     ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m1_3 (never executed)\n                     ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m1_4 (never executed)\n\nDROP TABLE join_limit;\n-- test ChunkAppend projection #2661\n:PREFIX SELECT ts.timestamp, ht.timestamp\nFROM (\n  SELECT generate_series(\n    to_timestamp(FLOOR(EXTRACT (EPOCH FROM '2020-01-01T00:01:00Z'::timestamp) / 300) * 300) AT TIME ZONE 'UTC',\n    '2020-01-01T01:00:00Z',\n    '5 minutes'::interval\n  ) AS timestamp\n) ts\nLEFT JOIN i2661 ht ON\n  (FLOOR(EXTRACT (EPOCH FROM ht.\"timestamp\") / 300) * 300 = EXTRACT (EPOCH FROM ts.timestamp))\n  AND ht.timestamp > '2019-12-30T00:00:00Z'::timestamp\nORDER BY ts.timestamp, ht.timestamp;\n--- QUERY PLAN ---\n Sort (actual rows=33.00 loops=1)\n   Sort Key: ts.\"timestamp\", ht.\"timestamp\"\n   Sort Method: quicksort \n   ->  Merge Left Join (actual rows=33.00 loops=1)\n         Merge Cond: ((EXTRACT(epoch FROM ts.\"timestamp\")) = ((floor((EXTRACT(epoch FROM ht.\"timestamp\") / '300'::numeric)) * '300'::numeric)))\n         ->  Sort (actual rows=13.00 loops=1)\n               Sort Key: (EXTRACT(epoch FROM ts.\"timestamp\"))\n               Sort Method: quicksort \n               ->  Subquery Scan on ts (actual rows=13.00 loops=1)\n                     ->  ProjectSet (actual rows=13.00 loops=1)\n                           ->  Result (actual rows=1.00 loops=1)\n         ->  Sort (actual rows=514.00 loops=1)\n               Sort Key: ((floor((EXTRACT(epoch FROM ht.\"timestamp\") / '300'::numeric)) * '300'::numeric))\n               Sort Method: quicksort \n               ->  Result (actual rows=7201.00 loops=1)\n                     ->  Custom Scan (ChunkAppend) on i2661 ht (actual rows=7201.00 loops=1)\n                           Chunks excluded during startup: 0\n                           ->  Seq Scan on _hyper_7_31_chunk ht_1 (actual rows=1200.00 loops=1)\n                                 Filter: (\"timestamp\" > 'Mon Dec 30 00:00:00 2019'::timestamp without time zone)\n                           ->  Seq Scan on _hyper_7_32_chunk ht_2 (actual rows=5040.00 loops=1)\n                                 Filter: (\"timestamp\" > 'Mon Dec 30 00:00:00 2019'::timestamp without time zone)\n                           ->  Seq Scan on _hyper_7_33_chunk ht_3 (actual rows=961.00 loops=1)\n                                 Filter: (\"timestamp\" > 'Mon Dec 30 00:00:00 2019'::timestamp without time zone)\n\n-- #3030 test chunkappend keeps pathkeys when subpath is append\n-- on PG11 this will not use ChunkAppend but MergeAppend\nSET enable_seqscan TO FALSE;\nCREATE TABLE i3030(time timestamptz NOT NULL, a int, b int);\nSELECT table_name FROM create_hypertable('i3030', 'time', create_default_indexes=>false);\n table_name \n------------\n i3030\n\nCREATE INDEX ON i3030(a,time);\nINSERT INTO i3030 (time,a) SELECT time, a FROM generate_series('2000-01-01'::timestamptz,'2000-01-01 3:00:00'::timestamptz,'1min'::interval) time, generate_series(1,30) a;\nVACUUM (ANALYZE) i3030;\n:PREFIX SELECT * FROM i3030 where time BETWEEN '2000-01-01'::text::timestamptz AND '2000-01-03'::text::timestamptz ORDER BY a,time LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on i3030 (actual rows=1.00 loops=1)\n         Order: i3030.a, i3030.\"time\"\n         Chunks excluded during startup: 0\n         ->  Index Scan using _hyper_9_38_chunk_i3030_a_time_idx on _hyper_9_38_chunk (actual rows=1.00 loops=1)\n               Index Cond: ((\"time\" >= ('2000-01-01'::cstring)::timestamp with time zone) AND (\"time\" <= ('2000-01-03'::cstring)::timestamp with time zone))\n\nDROP TABLE i3030;\nRESET enable_seqscan;\n--parent runtime exclusion tests:\n--optimization works with ANY (array)\n:PREFIX\nSELECT *\nFROM append_test a\nWHERE a.attr @> ANY((SELECT coalesce(array_agg(attr), array[]::jsonb[]) FROM join_test_plain WHERE temp > 100)::jsonb[]);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on append_test a (actual rows=0.00 loops=1)\n   Hypertables excluded during runtime: 1\n   InitPlan 1 (returns $0)\n     ->  Aggregate (actual rows=1.00 loops=1)\n           ->  Seq Scan on join_test_plain (actual rows=0.00 loops=1)\n                 Filter: (temp > '100'::double precision)\n                 Rows Removed by Filter: 3\n   ->  Seq Scan on _hyper_1_1_chunk a_1 (never executed)\n         Filter: (attr @> ANY ($0))\n   ->  Seq Scan on _hyper_1_2_chunk a_2 (never executed)\n         Filter: (attr @> ANY ($0))\n   ->  Seq Scan on _hyper_1_3_chunk a_3 (never executed)\n         Filter: (attr @> ANY ($0))\n\n--optimization does not work for ANY subquery (does not force an initplan)\n:PREFIX\nSELECT *\nFROM append_test a\nWHERE a.attr @> ANY((SELECT attr FROM join_test_plain WHERE temp > 100));\n--- QUERY PLAN ---\n Nested Loop Semi Join (actual rows=0.00 loops=1)\n   Join Filter: (a.attr @> join_test_plain.attr)\n   ->  Append (actual rows=5.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk a_1 (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk a_2 (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk a_3 (actual rows=1.00 loops=1)\n   ->  Materialize (actual rows=0.00 loops=5)\n         ->  Seq Scan on join_test_plain (actual rows=0.00 loops=1)\n               Filter: (temp > '100'::double precision)\n               Rows Removed by Filter: 3\n\n--works on any strict operator without ANY\n:PREFIX\nSELECT *\nFROM append_test a\nWHERE a.attr @> (SELECT attr FROM join_test_plain WHERE temp > 100 limit 1);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on append_test a (actual rows=0.00 loops=1)\n   Hypertables excluded during runtime: 1\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=0.00 loops=1)\n           ->  Seq Scan on join_test_plain (actual rows=0.00 loops=1)\n                 Filter: (temp > '100'::double precision)\n                 Rows Removed by Filter: 3\n   ->  Seq Scan on _hyper_1_1_chunk a_1 (never executed)\n         Filter: (attr @> $0)\n   ->  Seq Scan on _hyper_1_2_chunk a_2 (never executed)\n         Filter: (attr @> $0)\n   ->  Seq Scan on _hyper_1_3_chunk a_3 (never executed)\n         Filter: (attr @> $0)\n\n--optimization works with function calls\nCREATE OR REPLACE FUNCTION select_tag(_min_temp int)\n RETURNS jsonb[]\n LANGUAGE sql\n STABLE PARALLEL SAFE\nAS $function$\n   SELECT coalesce(array_agg(attr), array[]::jsonb[])\n  FROM join_test_plain\n  WHERE temp > _min_temp\n$function$;\n:PREFIX\nSELECT *\nFROM append_test a\nWHERE a.attr @> ANY((SELECT select_tag(100))::jsonb[]);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on append_test a (actual rows=0.00 loops=1)\n   Hypertables excluded during runtime: 1\n   InitPlan 1 (returns $0)\n     ->  Result (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_1_1_chunk a_1 (never executed)\n         Filter: (attr @> ANY ($0))\n   ->  Seq Scan on _hyper_1_2_chunk a_2 (never executed)\n         Filter: (attr @> ANY ($0))\n   ->  Seq Scan on _hyper_1_3_chunk a_3 (never executed)\n         Filter: (attr @> ANY ($0))\n\n--optimization does not work when result is null\n:PREFIX\nSELECT *\nFROM append_test a\nWHERE a.attr @> ANY((SELECT array_agg(attr) FROM join_test_plain WHERE temp > 100)::jsonb[]);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on append_test a (actual rows=0.00 loops=1)\n   Hypertables excluded during runtime: 0\n   InitPlan 1 (returns $0)\n     ->  Aggregate (actual rows=1.00 loops=1)\n           ->  Seq Scan on join_test_plain (actual rows=0.00 loops=1)\n                 Filter: (temp > '100'::double precision)\n                 Rows Removed by Filter: 3\n   ->  Seq Scan on _hyper_1_1_chunk a_1 (actual rows=0.00 loops=1)\n         Filter: (attr @> ANY ($0))\n         Rows Removed by Filter: 2\n   ->  Seq Scan on _hyper_1_2_chunk a_2 (actual rows=0.00 loops=1)\n         Filter: (attr @> ANY ($0))\n         Rows Removed by Filter: 2\n   ->  Seq Scan on _hyper_1_3_chunk a_3 (actual rows=0.00 loops=1)\n         Filter: (attr @> ANY ($0))\n         Rows Removed by Filter: 1\n\n-- Test that ConstraintAwareAppend properly locks relations in\n-- parallel query mode\nset timescaledb.enable_chunk_append=false;\ncall force_parallel(true);\n:PREFIX\nselect time, avg(temp), colorid from append_test\nwhere time > now_s() - interval '3 months 20 days'\ngroup by time, colorid;\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Gather (actual rows=3.00 loops=1)\n   Workers Planned: 1\n   Workers Launched: 1\n   Single Copy: true\n   ->  HashAggregate (actual rows=3.00 loops=1)\n         Group Key: append_test.\"time\", append_test.colorid\n         ->  Custom Scan (ConstraintAwareAppend) (actual rows=3.00 loops=1)\n               Hypertable: append_test\n               Chunks excluded during startup: 1\n               ->  Append (actual rows=3.00 loops=1)\n                     ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n                           Filter: (\"time\" > (now_s() - '@ 3 mons 20 days'::interval))\n                     ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n                           Filter: (\"time\" > (now_s() - '@ 3 mons 20 days'::interval))\n\nreset timescaledb.enable_chunk_append;\nreset max_parallel_workers_per_gather;\ncall force_parallel(false);\n--generate the results into two different files\n\\set ECHO errors\n--- Unoptimized results\n+++ Optimized results\n@@ -1,6 +1,6 @@\n              setting              | value \n ----------------------------------+-------\n- timescaledb.enable_optimizations | off\n+ timescaledb.enable_optimizations | on\n  timescaledb.enable_chunk_append  | on\n \n--- Unoptimized results\n+++ Optimized results\n@@ -1,7 +1,7 @@\n              setting              | value \n ----------------------------------+-------\n- timescaledb.enable_optimizations | off\n- timescaledb.enable_chunk_append  | on\n+ timescaledb.enable_optimizations | on\n+ timescaledb.enable_chunk_append  | off\n \n  time | temp | colorid | attr \n"
  },
  {
    "path": "test/expected/append-16.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\set TEST_BASE_NAME append\nSELECT format('include/%s_load.sql', :'TEST_BASE_NAME') as \"TEST_LOAD_NAME\",\n       format('include/%s_query.sql', :'TEST_BASE_NAME') as \"TEST_QUERY_NAME\",\n       format('%s/results/%s_results_optimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_OPTIMIZED\",\n       format('%s/results/%s_results_unoptimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_UNOPTIMIZED\"\n\\gset\nSELECT format('\\! diff -u --label \"Unoptimized results\" --label \"Optimized results\" %s %s', :'TEST_RESULTS_UNOPTIMIZED', :'TEST_RESULTS_OPTIMIZED') as \"DIFF_CMD\"\n\\gset\nSET timescaledb.enable_now_constify TO false;\n-- disable memoize node to avoid flaky results\nSET enable_memoize TO 'off';\n-- disable index only scans to avoid some flaky results\nSET enable_indexonlyscan TO FALSE;\n\\set PREFIX 'EXPLAIN (analyze, buffers off, costs off, timing off, summary off)'\n\\ir :TEST_LOAD_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- create a now() function for repeatable testing that always returns\n-- the same timestamp. It needs to be marked STABLE\nCREATE OR REPLACE FUNCTION now_s()\nRETURNS timestamptz LANGUAGE PLPGSQL STABLE PARALLEL SAFE AS\n$BODY$\nBEGIN\n    RAISE NOTICE 'Stable function now_s() called!';\n    RETURN '2017-08-22T10:00:00'::timestamptz;\nEND;\n$BODY$;\nCREATE OR REPLACE FUNCTION now_i()\nRETURNS timestamptz LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nBEGIN\n    RAISE NOTICE 'Immutable function now_i() called!';\n    RETURN '2017-08-22T10:00:00'::timestamptz;\nEND;\n$BODY$;\nCREATE OR REPLACE FUNCTION now_v()\nRETURNS timestamptz LANGUAGE PLPGSQL VOLATILE AS\n$BODY$\nBEGIN\n    RAISE NOTICE 'Volatile function now_v() called!';\n    RETURN '2017-08-22T10:00:00'::timestamptz;\nEND;\n$BODY$;\nCREATE OR REPLACE PROCEDURE force_parallel(on_or_off bool)\nLANGUAGE PLPGSQL AS\n$$\nBEGIN\n    IF current_setting('server_version_num')::int < 160000 THEN\n        IF on_or_off THEN\n            set force_parallel_mode = 'on';\n        ELSE\n            set force_parallel_mode = 'off';\n        END IF;\n    ELSE\n        IF on_or_off THEN\n            set debug_parallel_query = 'on';\n        ELSE\n            set debug_parallel_query = 'off';\n        END IF;\n    END IF;\nEND;\n$$;\nCREATE TABLE append_test(time timestamptz, temp float, colorid integer, attr jsonb);\nSELECT create_hypertable('append_test', 'time', chunk_time_interval => 2628000000000);\n    create_hypertable     \n--------------------------\n (1,public,append_test,t)\n\n-- create three chunks\nINSERT INTO append_test VALUES ('2017-03-22T09:18:22', 23.5, 1, '{\"a\": 1, \"b\": 2}'),\n                               ('2017-03-22T09:18:23', 21.5, 1, '{\"a\": 1, \"b\": 2}'),\n                               ('2017-05-22T09:18:22', 36.2, 2, '{\"c\": 3, \"b\": 2}'),\n                               ('2017-05-22T09:18:23', 15.2, 2, '{\"c\": 3}'),\n                               ('2017-08-22T09:18:22', 34.1, 3, '{\"c\": 4}');\nVACUUM (ANALYZE) append_test;\n-- Create another hypertable to join with\nCREATE TABLE join_test(time timestamptz, temp float, colorid integer);\nSELECT create_hypertable('join_test', 'time', chunk_time_interval => 2628000000000);\n   create_hypertable    \n------------------------\n (2,public,join_test,t)\n\nINSERT INTO join_test VALUES ('2017-01-22T09:18:22', 15.2, 1),\n                             ('2017-02-22T09:18:22', 24.5, 2),\n                             ('2017-08-22T09:18:22', 23.1, 3);\nVACUUM (ANALYZE) join_test;\n-- Create another table to join with which is not a hypertable.\nCREATE TABLE join_test_plain(time timestamptz, temp float, colorid integer, attr jsonb);\nINSERT INTO join_test_plain VALUES ('2017-01-22T09:18:22', 15.2, 1, '{\"a\": 1}'),\n                             ('2017-02-22T09:18:22', 24.5, 2, '{\"b\": 2}'),\n                             ('2017-08-22T09:18:22', 23.1, 3, '{\"c\": 3}');\nVACUUM (ANALYZE) join_test_plain;\n-- create hypertable with DATE time dimension\nCREATE TABLE metrics_date(time DATE NOT NULL);\nSELECT create_hypertable('metrics_date','time');\n     create_hypertable     \n---------------------------\n (3,public,metrics_date,t)\n\nINSERT INTO metrics_date SELECT generate_series('2000-01-01'::date, '2000-02-01'::date, '5m'::interval);\nVACUUM (ANALYZE) metrics_date;\n-- create hypertable with TIMESTAMP time dimension\nCREATE TABLE metrics_timestamp(time TIMESTAMP NOT NULL);\nSELECT create_hypertable('metrics_timestamp','time');\npsql:include/append_load.sql:91: WARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n       create_hypertable        \n--------------------------------\n (4,public,metrics_timestamp,t)\n\nINSERT INTO metrics_timestamp SELECT generate_series('2000-01-01'::date, '2000-02-01'::date, '5m'::interval);\nVACUUM (ANALYZE) metrics_timestamp;\n-- create hypertable with TIMESTAMPTZ time dimension\nCREATE TABLE metrics_timestamptz(time TIMESTAMPTZ NOT NULL, device_id INT NOT NULL);\nCREATE INDEX ON metrics_timestamptz(device_id,time);\nSELECT create_hypertable('metrics_timestamptz','time');\n        create_hypertable         \n----------------------------------\n (5,public,metrics_timestamptz,t)\n\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::date, '2000-02-01'::date, '5m'::interval), 1;\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::date, '2000-02-01'::date, '5m'::interval), 2;\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::date, '2000-02-01'::date, '5m'::interval), 3;\nVACUUM (ANALYZE) metrics_timestamptz;\n-- create space partitioned hypertable\nCREATE TABLE metrics_space(time timestamptz NOT NULL, device_id int NOT NULL, v1 float, v2 float, v3 text);\nSELECT create_hypertable('metrics_space','time','device_id',3);\n     create_hypertable      \n----------------------------\n (6,public,metrics_space,t)\n\nINSERT INTO metrics_space\nSELECT time, device_id, device_id + 0.25, device_id + 0.75, device_id\nFROM generate_series('2000-01-01'::timestamptz, '2000-01-14'::timestamptz, '5m'::interval) g1(time),\n  generate_series(1,10,1) g2(device_id)\nORDER BY time, device_id;\nVACUUM (ANALYZE) metrics_space;\n-- test ChunkAppend projection #2661\nCREATE TABLE i2661 (\n  machine_id int4 NOT NULL,\n  \"name\" varchar(255) NOT NULL,\n  \"timestamp\" timestamptz NOT NULL,\n  \"first\" float4 NULL\n);\nSELECT create_hypertable('i2661', 'timestamp');\npsql:include/append_load.sql:123: WARNING:  column type \"character varying\" used for \"name\" does not follow best practices\n create_hypertable  \n--------------------\n (7,public,i2661,t)\n\nINSERT INTO i2661 SELECT 1, 'speed', generate_series('2019-12-31 00:00:00', '2020-01-10 00:00:00', '2m'::interval), 0;\nVACUUM (ANALYZE) i2661;\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- canary for results diff\n-- this should be the only output of the results diff\nSELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations'),('timescaledb.enable_chunk_append')) v(setting);\n             setting              | value \n----------------------------------+-------\n timescaledb.enable_optimizations | on\n timescaledb.enable_chunk_append  | on\n\n-- query should exclude all chunks with optimization on\n:PREFIX\nSELECT * FROM append_test WHERE time > now_s() + '1 month'\nORDER BY time DESC;\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Sort (actual rows=0.00 loops=1)\n   Sort Key: append_test.\"time\" DESC\n   Sort Method: quicksort \n   ->  Custom Scan (ChunkAppend) on append_test (actual rows=0.00 loops=1)\n         Chunks excluded during startup: 3\n\n--query should exclude all chunks and be a MergeAppend\n:PREFIX\nSELECT * FROM append_test WHERE time > now_s() + '1 month'\nORDER BY time DESC limit 1;\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Limit (actual rows=0.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on append_test (actual rows=0.00 loops=1)\n         Order: append_test.\"time\" DESC\n         Chunks excluded during startup: 3\n\n-- when optimized, the plan should be a constraint-aware append and\n-- cover only one chunk. It should be a backward index scan due to\n-- descending index on time. Should also skip the main table, since it\n-- cannot hold tuples\n:PREFIX\nSELECT * FROM append_test WHERE time > now_s() - interval '2 months';\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on append_test (actual rows=1.00 loops=1)\n   Chunks excluded during startup: 2\n   ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         Filter: (\"time\" > (now_s() - '@ 2 mons'::interval))\n\n-- adding ORDER BY and LIMIT should turn the plan into an optimized\n-- ordered append plan\n:PREFIX\nSELECT * FROM append_test WHERE time > now_s() - interval '2 months'\nORDER BY time LIMIT 3;\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Sort (actual rows=1.00 loops=1)\n         Sort Key: append_test.\"time\"\n         Sort Method: quicksort \n         ->  Custom Scan (ChunkAppend) on append_test (actual rows=1.00 loops=1)\n               Chunks excluded during startup: 2\n               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n                     Filter: (\"time\" > (now_s() - '@ 2 mons'::interval))\n\n-- no optimized plan for queries with restrictions that can be\n-- constified at planning time. Regular planning-time constraint\n-- exclusion should occur.\n:PREFIX\nSELECT * FROM append_test WHERE time > now_i() - interval '2 months'\nORDER BY time;\npsql:include/append_query.sql:37: NOTICE:  Immutable function now_i() called!\n--- QUERY PLAN ---\n Sort (actual rows=1.00 loops=1)\n   Sort Key: append_test.\"time\"\n   Sort Method: quicksort \n   ->  Custom Scan (ChunkAppend) on append_test (actual rows=1.00 loops=1)\n         Chunks excluded during startup: 2\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               Filter: (\"time\" > ('Tue Aug 22 10:00:00 2017 PDT'::timestamp with time zone - '@ 2 mons'::interval))\n\n-- currently, we cannot distinguish between stable and volatile\n-- functions as far as applying our modified plan. However, volatile\n-- function should not be pre-evaluated to constants, so no chunk\n-- exclusion should occur.\n:PREFIX\nSELECT * FROM append_test WHERE time > now_v() - interval '2 months'\nORDER BY time;\npsql:include/append_query.sql:45: NOTICE:  Volatile function now_v() called!\npsql:include/append_query.sql:45: NOTICE:  Volatile function now_v() called!\npsql:include/append_query.sql:45: NOTICE:  Volatile function now_v() called!\npsql:include/append_query.sql:45: NOTICE:  Volatile function now_v() called!\npsql:include/append_query.sql:45: NOTICE:  Volatile function now_v() called!\n--- QUERY PLAN ---\n Sort (actual rows=1.00 loops=1)\n   Sort Key: append_test.\"time\"\n   Sort Method: quicksort \n   ->  Custom Scan (ChunkAppend) on append_test (actual rows=1.00 loops=1)\n         Chunks excluded during startup: 0\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=0.00 loops=1)\n               Filter: (\"time\" > (now_v() - '@ 2 mons'::interval))\n               Rows Removed by Filter: 2\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=0.00 loops=1)\n               Filter: (\"time\" > (now_v() - '@ 2 mons'::interval))\n               Rows Removed by Filter: 2\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               Filter: (\"time\" > (now_v() - '@ 2 mons'::interval))\n\n-- prepared statement output should be the same regardless of\n-- optimizations\nPREPARE query_opt AS\nSELECT * FROM append_test WHERE time > now_s() - interval '2 months'\nORDER BY time;\n:PREFIX EXECUTE query_opt;\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Sort (actual rows=1.00 loops=1)\n   Sort Key: append_test.\"time\"\n   Sort Method: quicksort \n   ->  Custom Scan (ChunkAppend) on append_test (actual rows=1.00 loops=1)\n         Chunks excluded during startup: 2\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               Filter: (\"time\" > (now_s() - '@ 2 mons'::interval))\n\nDEALLOCATE query_opt;\n-- aggregates should produce same output\n:PREFIX\nSELECT date_trunc('year', time) t, avg(temp) FROM append_test\nWHERE time > now_s() - interval '4 months'\nGROUP BY t\nORDER BY t DESC;\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n GroupAggregate (actual rows=1.00 loops=1)\n   Group Key: (date_trunc('year'::text, append_test.\"time\"))\n   ->  Sort (actual rows=3.00 loops=1)\n         Sort Key: (date_trunc('year'::text, append_test.\"time\")) DESC\n         Sort Method: quicksort \n         ->  Result (actual rows=3.00 loops=1)\n               ->  Custom Scan (ChunkAppend) on append_test (actual rows=3.00 loops=1)\n                     Chunks excluded during startup: 1\n                     ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n                           Filter: (\"time\" > (now_s() - '@ 4 mons'::interval))\n                     ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n                           Filter: (\"time\" > (now_s() - '@ 4 mons'::interval))\n\n-- querying outside the time range should return nothing. This tests\n-- that ConstraintAwareAppend can handle the case when an Append node\n-- is turned into a Result node due to no children\n:PREFIX\nSELECT date_trunc('year', time) t, avg(temp)\nFROM append_test\nWHERE time < '2016-03-22'\nAND date_part('dow', time) between 1 and 5\nGROUP BY t\nORDER BY t DESC;\n--- QUERY PLAN ---\n GroupAggregate (actual rows=0.00 loops=1)\n   Group Key: (date_trunc('year'::text, \"time\"))\n   ->  Sort (actual rows=0.00 loops=1)\n         Sort Key: (date_trunc('year'::text, \"time\")) DESC\n         Sort Method: quicksort \n         ->  Result (actual rows=0.00 loops=1)\n               One-Time Filter: false\n\n-- a parameterized query can safely constify params, so won't be\n-- optimized by constraint-aware append since regular constraint\n-- exclusion works just fine\nPREPARE query_param AS\nSELECT * FROM append_test WHERE time > $1 ORDER BY time;\n:PREFIX\nEXECUTE query_param(now_s() - interval '2 months');\npsql:include/append_query.sql:82: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Sort (actual rows=1.00 loops=1)\n   Sort Key: _hyper_1_3_chunk.\"time\"\n   Sort Method: quicksort \n   ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n\nDEALLOCATE query_param;\n--test with cte\n:PREFIX\nWITH data AS (\n    SELECT time_bucket(INTERVAL '30 day', TIME) AS btime, AVG(temp) AS VALUE\n    FROM append_test\n    WHERE\n        TIME > now_s() - INTERVAL '400 day'\n    AND colorid > 0\n    GROUP BY btime\n),\nperiod AS (\n    SELECT time_bucket(INTERVAL '30 day', TIME) AS btime\n      FROM  GENERATE_SERIES('2017-03-22T01:01:01', '2017-08-23T01:01:01', INTERVAL '30 day') TIME\n  )\nSELECT period.btime, VALUE\n    FROM period\n    LEFT JOIN DATA USING (btime)\n    ORDER BY period.btime;\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Sort (actual rows=6.00 loops=1)\n   Sort Key: (time_bucket('@ 30 days'::interval, \"time\".\"time\"))\n   Sort Method: quicksort \n   ->  Hash Left Join (actual rows=6.00 loops=1)\n         Hash Cond: (time_bucket('@ 30 days'::interval, \"time\".\"time\") = data.btime)\n         ->  Function Scan on generate_series \"time\" (actual rows=6.00 loops=1)\n         ->  Hash (actual rows=3.00 loops=1)\n               ->  Subquery Scan on data (actual rows=3.00 loops=1)\n                     ->  HashAggregate (actual rows=3.00 loops=1)\n                           Group Key: time_bucket('@ 30 days'::interval, append_test.\"time\")\n                           ->  Result (actual rows=5.00 loops=1)\n                                 ->  Custom Scan (ChunkAppend) on append_test (actual rows=5.00 loops=1)\n                                       Chunks excluded during startup: 0\n                                       ->  Seq Scan on _hyper_1_1_chunk (actual rows=2.00 loops=1)\n                                             Filter: ((colorid > 0) AND (\"time\" > (now_s() - '@ 400 days'::interval)))\n                                       ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n                                             Filter: ((colorid > 0) AND (\"time\" > (now_s() - '@ 400 days'::interval)))\n                                       ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n                                             Filter: ((colorid > 0) AND (\"time\" > (now_s() - '@ 400 days'::interval)))\n\nWITH data AS (\n    SELECT time_bucket(INTERVAL '30 day', TIME) AS btime, AVG(temp) AS VALUE\n    FROM append_test\n    WHERE\n        TIME > now_s() - INTERVAL '400 day'\n    AND colorid > 0\n    GROUP BY btime\n),\nperiod AS (\n    SELECT time_bucket(INTERVAL '30 day', TIME) AS btime\n      FROM  GENERATE_SERIES('2017-03-22T01:01:01', '2017-08-23T01:01:01', INTERVAL '30 day') TIME\n  )\nSELECT period.btime, VALUE\n    FROM period\n    LEFT JOIN DATA USING (btime)\n    ORDER BY period.btime;\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\n            btime             | value \n------------------------------+-------\n Fri Mar 03 16:00:00 2017 PST |  22.5\n Sun Apr 02 17:00:00 2017 PDT |      \n Tue May 02 17:00:00 2017 PDT |  25.7\n Thu Jun 01 17:00:00 2017 PDT |      \n Sat Jul 01 17:00:00 2017 PDT |      \n Mon Jul 31 17:00:00 2017 PDT |  34.1\n\n-- force nested loop join with no materialization. This tests that the\n-- inner ConstraintAwareScan supports resetting its scan for every\n-- iteration of the outer relation loop\nset enable_hashjoin = 'off';\nset enable_mergejoin = 'off';\nset enable_material = 'off';\n:PREFIX\nSELECT * FROM append_test a INNER JOIN join_test j ON (a.colorid = j.colorid)\nWHERE a.time > now_s() - interval '3 hours' AND j.time > now_s() - interval '3 hours';\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Nested Loop (actual rows=1.00 loops=1)\n   Join Filter: (a.colorid = j.colorid)\n   ->  Custom Scan (ChunkAppend) on append_test a (actual rows=1.00 loops=1)\n         Chunks excluded during startup: 2\n         ->  Seq Scan on _hyper_1_3_chunk a_1 (actual rows=1.00 loops=1)\n               Filter: (\"time\" > (now_s() - '@ 3 hours'::interval))\n   ->  Custom Scan (ChunkAppend) on join_test j (actual rows=1.00 loops=1)\n         Chunks excluded during startup: 2\n         ->  Seq Scan on _hyper_2_6_chunk j_1 (actual rows=1.00 loops=1)\n               Filter: (\"time\" > (now_s() - '@ 3 hours'::interval))\n\nreset enable_hashjoin;\nreset enable_mergejoin;\nreset enable_material;\n-- test constraint_exclusion with date time dimension and DATE/TIMESTAMP/TIMESTAMPTZ constraints\n-- the queries should all have 3 chunks\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=4609.00 loops=1)\n   Order: metrics_date.\"time\"\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_3_11_chunk_metrics_date_time_idx on _hyper_3_11_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=4609.00 loops=1)\n   Order: metrics_date.\"time\"\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_3_11_chunk_metrics_date_time_idx on _hyper_3_11_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=4609.00 loops=1)\n   Order: metrics_date.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=2016.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_3_11_chunk_metrics_date_time_idx on _hyper_3_11_chunk (actual rows=1441.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n\n-- test Const OP Var\n-- the queries should all have 3 chunks\n:PREFIX SELECT * FROM metrics_date WHERE '2000-01-15'::date < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=4609.00 loops=1)\n   Order: metrics_date.\"time\"\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_3_11_chunk_metrics_date_time_idx on _hyper_3_11_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_date WHERE '2000-01-15'::timestamp < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=4609.00 loops=1)\n   Order: metrics_date.\"time\"\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_3_11_chunk_metrics_date_time_idx on _hyper_3_11_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_date WHERE '2000-01-15'::timestamptz < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=4609.00 loops=1)\n   Order: metrics_date.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=2016.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_3_11_chunk_metrics_date_time_idx on _hyper_3_11_chunk (actual rows=1441.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n\n-- test 2 constraints\n-- the queries should all have 2 chunks\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::date AND time < '2000-01-21'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=1440.00 loops=1)\n   Order: metrics_date.\"time\"\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: ((\"time\" > '01-15-2000'::date) AND (\"time\" < '01-21-2000'::date))\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=288.00 loops=1)\n         Index Cond: ((\"time\" > '01-15-2000'::date) AND (\"time\" < '01-21-2000'::date))\n\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::timestamp AND time < '2000-01-21'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=1440.00 loops=1)\n   Order: metrics_date.\"time\"\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000'::timestamp without time zone))\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=288.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000'::timestamp without time zone))\n\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::timestamptz AND time < '2000-01-21'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=1440.00 loops=1)\n   Order: metrics_date.\"time\"\n   Chunks excluded during startup: 3\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=288.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000 PST'::timestamp with time zone))\n\n-- test constraint_exclusion with timestamp time dimension and DATE/TIMESTAMP/TIMESTAMPTZ constraints\n-- the queries should all have 3 chunks\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=4896.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_4_16_chunk_metrics_timestamp_time_idx on _hyper_4_16_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=4896.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_4_16_chunk_metrics_timestamp_time_idx on _hyper_4_16_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=4896.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=2016.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_4_16_chunk_metrics_timestamp_time_idx on _hyper_4_16_chunk (actual rows=1441.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n\n-- test Const OP Var\n-- the queries should all have 3 chunks\n:PREFIX SELECT * FROM metrics_timestamp WHERE '2000-01-15'::date < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=4896.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_4_16_chunk_metrics_timestamp_time_idx on _hyper_4_16_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE '2000-01-15'::timestamp < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=4896.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_4_16_chunk_metrics_timestamp_time_idx on _hyper_4_16_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE '2000-01-15'::timestamptz < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=4896.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=2016.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_4_16_chunk_metrics_timestamp_time_idx on _hyper_4_16_chunk (actual rows=1441.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n\n-- test 2 constraints\n-- the queries should all have 2 chunks\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::date AND time < '2000-01-21'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=1727.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: ((\"time\" > '01-15-2000'::date) AND (\"time\" < '01-21-2000'::date))\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=288.00 loops=1)\n         Index Cond: ((\"time\" > '01-15-2000'::date) AND (\"time\" < '01-21-2000'::date))\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::timestamp AND time < '2000-01-21'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=1727.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000'::timestamp without time zone))\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=288.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000'::timestamp without time zone))\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::timestamptz AND time < '2000-01-21'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=1727.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 3\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=288.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000 PST'::timestamp with time zone))\n\n-- test constraint_exclusion with timestamptz time dimension and DATE/TIMESTAMP/TIMESTAMPTZ constraints\n-- the queries should all have 3 chunks\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=14688.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=14688.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=14688.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n   ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n\n-- test Const OP Var\n-- the queries should all have 3 chunks\n:PREFIX SELECT time FROM metrics_timestamptz WHERE '2000-01-15'::date < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=14688.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE '2000-01-15'::timestamp < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=14688.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE '2000-01-15'::timestamptz < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=14688.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n   ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n\n-- test 2 constraints\n-- the queries should all have 2 chunks\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::date AND time < '2000-01-21'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=5181.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 3\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: ((\"time\" > '01-15-2000'::date) AND (\"time\" < '01-21-2000'::date))\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=1152.00 loops=1)\n         Index Cond: ((\"time\" > '01-15-2000'::date) AND (\"time\" < '01-21-2000'::date))\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::timestamp AND time < '2000-01-21'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=5181.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 3\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000'::timestamp without time zone))\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=1152.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000'::timestamp without time zone))\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::timestamptz AND time < '2000-01-21'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=5181.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=1152.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000 PST'::timestamp with time zone))\n\n-- test constraint_exclusion with space partitioning and DATE/TIMESTAMP/TIMESTAMPTZ constraints\n-- exclusion for constraints with non-matching datatypes not working for space partitioning atm\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n-- test Const OP Var\n-- exclusion for constraints with non-matching datatypes not working for space partitioning atm\n:PREFIX SELECT time FROM metrics_space WHERE '2000-01-10'::date < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n\n:PREFIX SELECT time FROM metrics_space WHERE '2000-01-10'::timestamp < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT time FROM metrics_space WHERE '2000-01-10'::timestamptz < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n-- test 2 constraints\n-- exclusion for constraints with non-matching datatypes not working for space partitioning atm\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::date AND time < '2000-01-15'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamp AND time < '2000-01-15'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz AND time < '2000-01-15'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone))\n\n-- test filtering on space partition\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz AND device_id = 1 ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=1152.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_device_id_time_idx on _hyper_6_25_chunk (actual rows=767.00 loops=1)\n         Index Cond: ((device_id = 1) AND (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_device_id_time_idx on _hyper_6_28_chunk (actual rows=385.00 loops=1)\n         Index Cond: ((device_id = 1) AND (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz AND device_id IN (1,2) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=2304.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=1534.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=767.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (device_id = ANY ('{1,2}'::integer[]))\n               Rows Removed by Filter: 2301\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=767.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (device_id = ANY ('{1,2}'::integer[]))\n               Rows Removed by Filter: 2301\n   ->  Merge Append (actual rows=770.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=385.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (device_id = ANY ('{1,2}'::integer[]))\n               Rows Removed by Filter: 1155\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=385.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (device_id = ANY ('{1,2}'::integer[]))\n               Rows Removed by Filter: 1155\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz AND device_id IN (VALUES(1)) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=1152.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_device_id_time_idx on _hyper_6_25_chunk (actual rows=767.00 loops=1)\n         Index Cond: ((device_id = 1) AND (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_device_id_time_idx on _hyper_6_28_chunk (actual rows=385.00 loops=1)\n         Index Cond: ((device_id = 1) AND (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz AND v3 IN (VALUES('1')) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=1152.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=767.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=767.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (v3 = '1'::text)\n               Rows Removed by Filter: 2301\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (v3 = '1'::text)\n               Rows Removed by Filter: 3068\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (v3 = '1'::text)\n               Rows Removed by Filter: 1534\n   ->  Merge Append (actual rows=385.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=385.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (v3 = '1'::text)\n               Rows Removed by Filter: 1155\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (v3 = '1'::text)\n               Rows Removed by Filter: 1540\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (v3 = '1'::text)\n               Rows Removed by Filter: 770\n\n:PREFIX SELECT * FROM metrics_space\nWHERE time = (VALUES ('2019-12-24' at time zone 'UTC'))\n  AND v3 NOT IN (VALUES ('1'));\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=0.00 loops=1)\n   Chunks excluded during startup: 0\n   Chunks excluded during runtime: 9\n   InitPlan 1 (returns $0)\n     ->  Result (actual rows=1.00 loops=1)\n   ->  Index Scan using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (never executed)\n         Index Cond: (\"time\" = $0)\n         Filter: (NOT (hashed SubPlan 2))\n         SubPlan 2\n           ->  Result (never executed)\n   ->  Index Scan using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (never executed)\n         Index Cond: (\"time\" = $0)\n         Filter: (NOT (hashed SubPlan 2))\n   ->  Index Scan using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (never executed)\n         Index Cond: (\"time\" = $0)\n         Filter: (NOT (hashed SubPlan 2))\n   ->  Index Scan using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (never executed)\n         Index Cond: (\"time\" = $0)\n         Filter: (NOT (hashed SubPlan 2))\n   ->  Index Scan using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (never executed)\n         Index Cond: (\"time\" = $0)\n         Filter: (NOT (hashed SubPlan 2))\n   ->  Index Scan using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (never executed)\n         Index Cond: (\"time\" = $0)\n         Filter: (NOT (hashed SubPlan 2))\n   ->  Index Scan using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (never executed)\n         Index Cond: (\"time\" = $0)\n         Filter: (NOT (hashed SubPlan 2))\n   ->  Index Scan using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (never executed)\n         Index Cond: (\"time\" = $0)\n         Filter: (NOT (hashed SubPlan 2))\n   ->  Index Scan using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (never executed)\n         Index Cond: (\"time\" = $0)\n         Filter: (NOT (hashed SubPlan 2))\n\n-- test CURRENT_DATE\n-- should be 0 chunks\n:PREFIX SELECT time FROM metrics_date WHERE time > CURRENT_DATE ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=0.00 loops=1)\n   Order: metrics_date.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_timestamp WHERE time > CURRENT_DATE ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=0.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > CURRENT_DATE ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=0.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_space WHERE time > CURRENT_DATE ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=0.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n\n-- test CURRENT_TIMESTAMP\n-- should be 0 chunks\n:PREFIX SELECT time FROM metrics_date WHERE time > CURRENT_TIMESTAMP ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=0.00 loops=1)\n   Order: metrics_date.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_timestamp WHERE time > CURRENT_TIMESTAMP ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=0.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > CURRENT_TIMESTAMP ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=0.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_space WHERE time > CURRENT_TIMESTAMP ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=0.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n\n-- test now()\n-- should be 0 chunks\n:PREFIX SELECT time FROM metrics_date WHERE time > now() ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=0.00 loops=1)\n   Order: metrics_date.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_timestamp WHERE time > now() ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=0.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > now() ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=0.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_space WHERE time > now() ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=0.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n\n-- query with tablesample and planner exclusion\n:PREFIX\nSELECT * FROM metrics_date TABLESAMPLE BERNOULLI(5) REPEATABLE(0)\nWHERE time > '2000-01-15'\nORDER BY time DESC;\n--- QUERY PLAN ---\n Sort (actual rows=217.00 loops=1)\n   Sort Key: metrics_date.\"time\" DESC\n   Sort Method: quicksort \n   ->  Append (actual rows=217.00 loops=1)\n         ->  Sample Scan on _hyper_3_11_chunk (actual rows=72.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n         ->  Sample Scan on _hyper_3_10_chunk (actual rows=94.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n         ->  Sample Scan on _hyper_3_9_chunk (actual rows=51.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > '01-15-2000'::date)\n               Rows Removed by Filter: 43\n\n-- query with tablesample and startup exclusion\n:PREFIX\nSELECT * FROM metrics_date TABLESAMPLE BERNOULLI(5) REPEATABLE(0)\nWHERE time > '2000-01-15'::text::date\nORDER BY time DESC;\n--- QUERY PLAN ---\n Sort (actual rows=217.00 loops=1)\n   Sort Key: metrics_date.\"time\" DESC\n   Sort Method: quicksort \n   ->  Custom Scan (ChunkAppend) on metrics_date (actual rows=217.00 loops=1)\n         Chunks excluded during startup: 2\n         ->  Sample Scan on _hyper_3_11_chunk (actual rows=72.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > ('2000-01-15'::cstring)::date)\n         ->  Sample Scan on _hyper_3_10_chunk (actual rows=94.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > ('2000-01-15'::cstring)::date)\n         ->  Sample Scan on _hyper_3_9_chunk (actual rows=51.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > ('2000-01-15'::cstring)::date)\n               Rows Removed by Filter: 43\n\n-- query with tablesample, space partitioning and planner exclusion\n:PREFIX\nSELECT * FROM metrics_space TABLESAMPLE BERNOULLI(5) REPEATABLE(0)\nWHERE time > '2000-01-10'::timestamptz\nORDER BY time DESC, device_id;\n--- QUERY PLAN ---\n Sort (actual rows=522.00 loops=1)\n   Sort Key: metrics_space.\"time\" DESC, metrics_space.device_id\n   Sort Method: quicksort \n   ->  Append (actual rows=522.00 loops=1)\n         ->  Sample Scan on _hyper_6_30_chunk (actual rows=35.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Sample Scan on _hyper_6_29_chunk (actual rows=61.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Sample Scan on _hyper_6_28_chunk (actual rows=61.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Sample Scan on _hyper_6_27_chunk (actual rows=65.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Rows Removed by Filter: 113\n         ->  Sample Scan on _hyper_6_26_chunk (actual rows=150.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Rows Removed by Filter: 218\n         ->  Sample Scan on _hyper_6_25_chunk (actual rows=150.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Rows Removed by Filter: 218\n\n-- test runtime exclusion\n-- test runtime exclusion with LATERAL and 2 hypertables\n:PREFIX SELECT m1.time, m2.time FROM metrics_timestamptz m1 LEFT JOIN LATERAL(SELECT time FROM metrics_timestamptz m2 WHERE m1.time = m2.time LIMIT 1) m2 ON true ORDER BY m1.time;\n--- QUERY PLAN ---\n Nested Loop Left Join (actual rows=26787.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1 (actual rows=26787.00 loops=1)\n         Order: m1.\"time\"\n         ->  Index Scan Backward using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m1_1 (actual rows=4032.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m1_2 (actual rows=6048.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m1_3 (actual rows=6048.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m1_4 (actual rows=6048.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m1_5 (actual rows=4611.00 loops=1)\n   ->  Limit (actual rows=1.00 loops=26787)\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m2 (actual rows=1.00 loops=26787)\n               Chunks excluded during runtime: 4\n               ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m2_1 (actual rows=1.00 loops=4032)\n                     Index Cond: (\"time\" = m1.\"time\")\n               ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m2_2 (actual rows=1.00 loops=6048)\n                     Index Cond: (\"time\" = m1.\"time\")\n               ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m2_3 (actual rows=1.00 loops=6048)\n                     Index Cond: (\"time\" = m1.\"time\")\n               ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m2_4 (actual rows=1.00 loops=6048)\n                     Index Cond: (\"time\" = m1.\"time\")\n               ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m2_5 (actual rows=1.00 loops=4611)\n                     Index Cond: (\"time\" = m1.\"time\")\n\n-- test runtime exclusion and startup exclusions\n:PREFIX SELECT m1.time, m2.time FROM metrics_timestamptz m1 LEFT JOIN LATERAL(SELECT time FROM metrics_timestamptz m2 WHERE m1.time = m2.time AND m2.time < '2000-01-10'::text::timestamptz LIMIT 1) m2 ON true ORDER BY m1.time;\n--- QUERY PLAN ---\n Nested Loop Left Join (actual rows=26787.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1 (actual rows=26787.00 loops=1)\n         Order: m1.\"time\"\n         ->  Index Scan Backward using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m1_1 (actual rows=4032.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m1_2 (actual rows=6048.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m1_3 (actual rows=6048.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m1_4 (actual rows=6048.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m1_5 (actual rows=4611.00 loops=1)\n   ->  Limit (actual rows=0.00 loops=26787)\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m2 (actual rows=0.00 loops=26787)\n               Chunks excluded during startup: 3\n               Chunks excluded during runtime: 1\n               ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m2_1 (actual rows=1.00 loops=4032)\n                     Index Cond: ((\"time\" < ('2000-01-10'::cstring)::timestamp with time zone) AND (\"time\" = m1.\"time\"))\n               ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m2_2 (actual rows=1.00 loops=6048)\n                     Index Cond: ((\"time\" < ('2000-01-10'::cstring)::timestamp with time zone) AND (\"time\" = m1.\"time\"))\n\n-- test runtime exclusion does not activate for constraints on non-partitioning columns\n-- should not use runtime exclusion\n:PREFIX SELECT * FROM append_test a LEFT JOIN LATERAL(SELECT * FROM join_test j WHERE a.colorid = j.colorid ORDER BY time DESC LIMIT 1) j ON true ORDER BY a.time LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Nested Loop Left Join (actual rows=1.00 loops=1)\n         ->  Custom Scan (ChunkAppend) on append_test a (actual rows=1.00 loops=1)\n               Order: a.\"time\"\n               ->  Index Scan Backward using _hyper_1_1_chunk_append_test_time_idx on _hyper_1_1_chunk a_1 (actual rows=1.00 loops=1)\n               ->  Index Scan Backward using _hyper_1_2_chunk_append_test_time_idx on _hyper_1_2_chunk a_2 (never executed)\n               ->  Index Scan Backward using _hyper_1_3_chunk_append_test_time_idx on _hyper_1_3_chunk a_3 (never executed)\n         ->  Limit (actual rows=1.00 loops=1)\n               ->  Custom Scan (ChunkAppend) on join_test j (actual rows=1.00 loops=1)\n                     Order: j.\"time\" DESC\n                     Hypertables excluded during runtime: 0\n                     ->  Index Scan using _hyper_2_6_chunk_join_test_time_idx on _hyper_2_6_chunk j_1 (actual rows=0.00 loops=1)\n                           Filter: (a.colorid = colorid)\n                           Rows Removed by Filter: 1\n                     ->  Index Scan using _hyper_2_5_chunk_join_test_time_idx on _hyper_2_5_chunk j_2 (actual rows=0.00 loops=1)\n                           Filter: (a.colorid = colorid)\n                           Rows Removed by Filter: 1\n                     ->  Index Scan using _hyper_2_4_chunk_join_test_time_idx on _hyper_2_4_chunk j_3 (actual rows=1.00 loops=1)\n                           Filter: (a.colorid = colorid)\n\n-- test runtime exclusion with LATERAL and generate_series\n:PREFIX SELECT g.time FROM generate_series('2000-01-01'::timestamptz, '2000-02-01'::timestamptz, '1d'::interval) g(time) LEFT JOIN LATERAL(SELECT time FROM metrics_timestamptz m WHERE m.time=g.time LIMIT 1) m ON true;\n--- QUERY PLAN ---\n Nested Loop Left Join (actual rows=32.00 loops=1)\n   ->  Function Scan on generate_series g (actual rows=32.00 loops=1)\n   ->  Limit (actual rows=1.00 loops=32)\n         ->  Result (actual rows=1.00 loops=32)\n               ->  Custom Scan (ChunkAppend) on metrics_timestamptz m (actual rows=1.00 loops=32)\n                     Chunks excluded during runtime: 4\n                     ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m_1 (actual rows=1.00 loops=5)\n                           Index Cond: (\"time\" = g.\"time\")\n                     ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m_2 (actual rows=1.00 loops=7)\n                           Index Cond: (\"time\" = g.\"time\")\n                     ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m_3 (actual rows=1.00 loops=7)\n                           Index Cond: (\"time\" = g.\"time\")\n                     ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m_4 (actual rows=1.00 loops=7)\n                           Index Cond: (\"time\" = g.\"time\")\n                     ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m_5 (actual rows=1.00 loops=6)\n                           Index Cond: (\"time\" = g.\"time\")\n\n:PREFIX SELECT * FROM generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval) AS g(time) INNER JOIN LATERAL (SELECT time FROM metrics_timestamptz m WHERE time=g.time) m ON true;\n--- QUERY PLAN ---\n Hash Join (actual rows=96.00 loops=1)\n   Hash Cond: (g.\"time\" = m.\"time\")\n   ->  Function Scan on generate_series g (actual rows=32.00 loops=1)\n   ->  Hash (actual rows=26787.00 loops=1)\n         ->  Append (actual rows=26787.00 loops=1)\n               ->  Seq Scan on _hyper_5_17_chunk m_1 (actual rows=4032.00 loops=1)\n               ->  Seq Scan on _hyper_5_18_chunk m_2 (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_19_chunk m_3 (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_20_chunk m_4 (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_21_chunk m_5 (actual rows=4611.00 loops=1)\n\n:PREFIX SELECT * FROM generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval) AS g(time) INNER JOIN LATERAL (SELECT time FROM metrics_timestamptz m WHERE time=g.time ORDER BY time) m ON true;\n--- QUERY PLAN ---\n Nested Loop (actual rows=96.00 loops=1)\n   ->  Function Scan on generate_series g (actual rows=32.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m (actual rows=3.00 loops=32)\n         Chunks excluded during runtime: 4\n         ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m_1 (actual rows=3.00 loops=5)\n               Index Cond: (\"time\" = g.\"time\")\n         ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m_2 (actual rows=3.00 loops=7)\n               Index Cond: (\"time\" = g.\"time\")\n         ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m_3 (actual rows=3.00 loops=7)\n               Index Cond: (\"time\" = g.\"time\")\n         ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m_4 (actual rows=3.00 loops=7)\n               Index Cond: (\"time\" = g.\"time\")\n         ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m_5 (actual rows=3.00 loops=6)\n               Index Cond: (\"time\" = g.\"time\")\n\n:PREFIX SELECT * FROM generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval) AS g(time) INNER JOIN LATERAL (SELECT time FROM metrics_timestamptz m WHERE time>g.time + '1 day' ORDER BY time LIMIT 1) m ON true;\n--- QUERY PLAN ---\n Nested Loop (actual rows=30.00 loops=1)\n   ->  Function Scan on generate_series g (actual rows=32.00 loops=1)\n   ->  Limit (actual rows=1.00 loops=32)\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m (actual rows=1.00 loops=32)\n               Order: m.\"time\"\n               Chunks excluded during startup: 0\n               Chunks excluded during runtime: 2\n               ->  Index Scan Backward using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m_1 (actual rows=1.00 loops=4)\n                     Index Cond: (\"time\" > (g.\"time\" + '@ 1 day'::interval))\n               ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m_2 (actual rows=1.00 loops=7)\n                     Index Cond: (\"time\" > (g.\"time\" + '@ 1 day'::interval))\n               ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m_3 (actual rows=1.00 loops=7)\n                     Index Cond: (\"time\" > (g.\"time\" + '@ 1 day'::interval))\n               ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m_4 (actual rows=1.00 loops=7)\n                     Index Cond: (\"time\" > (g.\"time\" + '@ 1 day'::interval))\n               ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m_5 (actual rows=1.00 loops=7)\n                     Index Cond: (\"time\" > (g.\"time\" + '@ 1 day'::interval))\n\n-- test runtime exclusion with subquery\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 WHERE m1.time=(SELECT max(time) FROM metrics_timestamptz);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz m1 (actual rows=3.00 loops=1)\n   Chunks excluded during runtime: 4\n   InitPlan 2 (returns $1)\n     ->  Result (actual rows=1.00 loops=1)\n           InitPlan 1 (returns $0)\n             ->  Limit (actual rows=1.00 loops=1)\n                   ->  Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=1.00 loops=1)\n                         Order: metrics_timestamptz.\"time\" DESC\n                         ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=1.00 loops=1)\n                               Index Cond: (\"time\" IS NOT NULL)\n                         ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (never executed)\n                               Index Cond: (\"time\" IS NOT NULL)\n                         ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (never executed)\n                               Index Cond: (\"time\" IS NOT NULL)\n                         ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk (never executed)\n                               Index Cond: (\"time\" IS NOT NULL)\n                         ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk (never executed)\n                               Index Cond: (\"time\" IS NOT NULL)\n   ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m1_1 (never executed)\n         Index Cond: (\"time\" = $1)\n   ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m1_2 (never executed)\n         Index Cond: (\"time\" = $1)\n   ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m1_3 (never executed)\n         Index Cond: (\"time\" = $1)\n   ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m1_4 (never executed)\n         Index Cond: (\"time\" = $1)\n   ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m1_5 (actual rows=3.00 loops=1)\n         Index Cond: (\"time\" = $1)\n\n-- test runtime exclusion with correlated subquery\n:PREFIX SELECT m1.time, (SELECT m2.time FROM metrics_timestamptz m2 WHERE m2.time < m1.time ORDER BY m2.time DESC LIMIT 1) FROM metrics_timestamptz m1 WHERE m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Result (actual rows=7776.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1 (actual rows=7776.00 loops=1)\n         Order: m1.\"time\"\n         ->  Index Scan Backward using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m1_1 (actual rows=4032.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m1_2 (actual rows=3744.00 loops=1)\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   SubPlan 1\n     ->  Limit (actual rows=1.00 loops=7776)\n           ->  Custom Scan (ChunkAppend) on metrics_timestamptz m2 (actual rows=1.00 loops=7776)\n                 Order: m2.\"time\" DESC\n                 Chunks excluded during runtime: 3\n                 ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m2_1 (never executed)\n                       Index Cond: (\"time\" < m1.\"time\")\n                 ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m2_2 (never executed)\n                       Index Cond: (\"time\" < m1.\"time\")\n                 ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m2_3 (never executed)\n                       Index Cond: (\"time\" < m1.\"time\")\n                 ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m2_4 (actual rows=1.00 loops=3741)\n                       Index Cond: (\"time\" < m1.\"time\")\n                 ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m2_5 (actual rows=1.00 loops=4035)\n                       Index Cond: (\"time\" < m1.\"time\")\n\n-- test EXISTS\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 WHERE EXISTS(SELECT 1 FROM metrics_timestamptz m2 WHERE m1.time < m2.time) ORDER BY m1.time DESC limit 1000;\n--- QUERY PLAN ---\n Limit (actual rows=1000.00 loops=1)\n   ->  Nested Loop Semi Join (actual rows=1000.00 loops=1)\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1 (actual rows=1003.00 loops=1)\n               Order: m1.\"time\" DESC\n               ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m1_1 (actual rows=1003.00 loops=1)\n               ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m1_2 (never executed)\n               ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m1_3 (never executed)\n               ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m1_4 (never executed)\n               ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m1_5 (never executed)\n         ->  Append (actual rows=1.00 loops=1003)\n               ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m2_1 (actual rows=0.00 loops=1003)\n                     Index Cond: (\"time\" > m1.\"time\")\n               ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m2_2 (actual rows=0.00 loops=1003)\n                     Index Cond: (\"time\" > m1.\"time\")\n               ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m2_3 (actual rows=0.00 loops=1003)\n                     Index Cond: (\"time\" > m1.\"time\")\n               ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m2_4 (actual rows=0.00 loops=1003)\n                     Index Cond: (\"time\" > m1.\"time\")\n               ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m2_5 (actual rows=1.00 loops=1003)\n                     Index Cond: (\"time\" > m1.\"time\")\n\n-- test constraint exclusion for subqueries with append\n-- should include 2 chunks\n:PREFIX SELECT time FROM (SELECT time FROM metrics_timestamptz WHERE time < '2000-01-10'::text::timestamptz ORDER BY time) m;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=7776.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 3\n   ->  Index Scan Backward using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk (actual rows=4032.00 loops=1)\n         Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk (actual rows=3744.00 loops=1)\n         Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n\n-- test constraint exclusion for subqueries with mergeappend\n-- should include 2 chunks\n:PREFIX SELECT device_id, time FROM (SELECT device_id, time FROM metrics_timestamptz WHERE time < '2000-01-10'::text::timestamptz ORDER BY device_id, time) m;\n--- QUERY PLAN ---\n Custom Scan (ConstraintAwareAppend) (actual rows=7776.00 loops=1)\n   Hypertable: metrics_timestamptz\n   Chunks excluded during startup: 3\n   ->  Merge Append (actual rows=7776.00 loops=1)\n         Sort Key: metrics_timestamptz.device_id, metrics_timestamptz.\"time\"\n         ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_17_chunk (actual rows=4032.00 loops=1)\n               Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n         ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_18_chunk (actual rows=3744.00 loops=1)\n               Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n\n-- test LIMIT pushdown\n-- no aggregates/window functions/SRF should pushdown limit\n:PREFIX SELECT FROM metrics_timestamptz ORDER BY time LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=1.00 loops=1)\n         Order: metrics_timestamptz.\"time\"\n         ->  Index Scan Backward using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk (actual rows=1.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk (never executed)\n         ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (never executed)\n         ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (never executed)\n         ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (never executed)\n\n-- aggregates should prevent pushdown\n:PREFIX SELECT count(*) FROM metrics_timestamptz LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Aggregate (actual rows=1.00 loops=1)\n         ->  Append (actual rows=26787.00 loops=1)\n               ->  Seq Scan on _hyper_5_17_chunk (actual rows=4032.00 loops=1)\n               ->  Seq Scan on _hyper_5_18_chunk (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_19_chunk (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n\n:PREFIX SELECT count(*) FROM metrics_space LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Aggregate (actual rows=1.00 loops=1)\n         ->  Append (actual rows=37450.00 loops=1)\n               ->  Seq Scan on _hyper_6_22_chunk (actual rows=5376.00 loops=1)\n               ->  Seq Scan on _hyper_6_23_chunk (actual rows=5376.00 loops=1)\n               ->  Seq Scan on _hyper_6_24_chunk (actual rows=2688.00 loops=1)\n               ->  Seq Scan on _hyper_6_25_chunk (actual rows=8064.00 loops=1)\n               ->  Seq Scan on _hyper_6_26_chunk (actual rows=8064.00 loops=1)\n               ->  Seq Scan on _hyper_6_27_chunk (actual rows=4032.00 loops=1)\n               ->  Seq Scan on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               ->  Seq Scan on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               ->  Seq Scan on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n\n-- HAVING should prevent pushdown\n:PREFIX SELECT 1 FROM metrics_timestamptz HAVING count(*) > 1 LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Aggregate (actual rows=1.00 loops=1)\n         Filter: (count(*) > 1)\n         ->  Append (actual rows=26787.00 loops=1)\n               ->  Seq Scan on _hyper_5_17_chunk (actual rows=4032.00 loops=1)\n               ->  Seq Scan on _hyper_5_18_chunk (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_19_chunk (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n\n:PREFIX SELECT 1 FROM metrics_space HAVING count(*) > 1 LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Aggregate (actual rows=1.00 loops=1)\n         Filter: (count(*) > 1)\n         ->  Append (actual rows=37450.00 loops=1)\n               ->  Seq Scan on _hyper_6_22_chunk (actual rows=5376.00 loops=1)\n               ->  Seq Scan on _hyper_6_23_chunk (actual rows=5376.00 loops=1)\n               ->  Seq Scan on _hyper_6_24_chunk (actual rows=2688.00 loops=1)\n               ->  Seq Scan on _hyper_6_25_chunk (actual rows=8064.00 loops=1)\n               ->  Seq Scan on _hyper_6_26_chunk (actual rows=8064.00 loops=1)\n               ->  Seq Scan on _hyper_6_27_chunk (actual rows=4032.00 loops=1)\n               ->  Seq Scan on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               ->  Seq Scan on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               ->  Seq Scan on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n\n-- DISTINCT should prevent pushdown\nSET enable_hashagg TO false;\n:PREFIX SELECT DISTINCT device_id FROM metrics_timestamptz ORDER BY device_id LIMIT 3;\n--- QUERY PLAN ---\n Limit (actual rows=3.00 loops=1)\n   ->  Unique (actual rows=3.00 loops=1)\n         ->  Merge Append (actual rows=17859.00 loops=1)\n               Sort Key: metrics_timestamptz.device_id\n               ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_17_chunk (actual rows=2689.00 loops=1)\n               ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_18_chunk (actual rows=4033.00 loops=1)\n               ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_19_chunk (actual rows=4033.00 loops=1)\n               ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_20_chunk (actual rows=4033.00 loops=1)\n               ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_21_chunk (actual rows=3075.00 loops=1)\n\n:PREFIX SELECT DISTINCT device_id FROM metrics_space ORDER BY device_id LIMIT 3;\n--- QUERY PLAN ---\n Limit (actual rows=3.00 loops=1)\n   ->  Unique (actual rows=3.00 loops=1)\n         ->  Merge Append (actual rows=7491.00 loops=1)\n               Sort Key: metrics_space.device_id\n               ->  Index Scan using _hyper_6_22_chunk_metrics_space_device_id_time_idx on _hyper_6_22_chunk (actual rows=1345.00 loops=1)\n               ->  Index Scan using _hyper_6_23_chunk_metrics_space_device_id_time_idx on _hyper_6_23_chunk (actual rows=1345.00 loops=1)\n               ->  Index Scan using _hyper_6_24_chunk_metrics_space_device_id_time_idx on _hyper_6_24_chunk (actual rows=1.00 loops=1)\n               ->  Index Scan using _hyper_6_25_chunk_metrics_space_device_id_time_idx on _hyper_6_25_chunk (actual rows=2017.00 loops=1)\n               ->  Index Scan using _hyper_6_26_chunk_metrics_space_device_id_time_idx on _hyper_6_26_chunk (actual rows=2017.00 loops=1)\n               ->  Index Scan using _hyper_6_27_chunk_metrics_space_device_id_time_idx on _hyper_6_27_chunk (actual rows=1.00 loops=1)\n               ->  Index Scan using _hyper_6_28_chunk_metrics_space_device_id_time_idx on _hyper_6_28_chunk (actual rows=386.00 loops=1)\n               ->  Index Scan using _hyper_6_29_chunk_metrics_space_device_id_time_idx on _hyper_6_29_chunk (actual rows=386.00 loops=1)\n               ->  Index Scan using _hyper_6_30_chunk_metrics_space_device_id_time_idx on _hyper_6_30_chunk (actual rows=1.00 loops=1)\n\nRESET enable_hashagg;\n-- JOINs should prevent pushdown\n-- when LIMIT gets pushed to a Sort node it will switch to top-N heapsort\n-- if more tuples then LIMIT are requested this will trigger an error\n-- to trigger this we need a Sort node that is below ChunkAppend\nCREATE TABLE join_limit (time timestamptz, device_id int);\nSELECT table_name FROM create_hypertable('join_limit','time',create_default_indexes:=false);\n table_name \n------------\n join_limit\n\nCREATE INDEX ON join_limit(time,device_id);\nINSERT INTO join_limit\nSELECT time, device_id\nFROM generate_series('2000-01-01'::timestamptz,'2000-01-21','30m') g1(time),\n  generate_series(1,10,1) g2(device_id)\nORDER BY time, device_id;\nVACUUM (ANALYZE) join_limit;\n-- get 2nd chunk oid\nSELECT tableoid AS \"CHUNK_OID\" FROM join_limit WHERE time > '2000-01-07' ORDER BY time LIMIT 1\n\\gset\n--get index name for 2nd chunk\nSELECT indexrelid::regclass AS \"INDEX_NAME\" FROM pg_index WHERE indrelid = :CHUNK_OID\n\\gset\nDROP INDEX :INDEX_NAME;\n:PREFIX SELECT * FROM metrics_timestamptz m1 INNER JOIN join_limit m2 ON m1.time = m2.time AND m1.device_id=m2.device_id WHERE m1.time > '2000-01-07' ORDER BY m1.time, m1.device_id LIMIT 3;\n--- QUERY PLAN ---\n Limit (actual rows=3.00 loops=1)\n   ->  Merge Join (actual rows=3.00 loops=1)\n         Merge Cond: (m2.\"time\" = m1.\"time\")\n         Join Filter: (m2.device_id = m1.device_id)\n         Rows Removed by Join Filter: 4\n         ->  Custom Scan (ChunkAppend) on join_limit m2 (actual rows=3.00 loops=1)\n               Order: m2.\"time\", m2.device_id\n               ->  Sort (actual rows=3.00 loops=1)\n                     Sort Key: m2_1.\"time\", m2_1.device_id\n                     Sort Method: quicksort \n                     ->  Seq Scan on _hyper_8_35_chunk m2_1 (actual rows=2710.00 loops=1)\n                           Filter: (\"time\" > 'Fri Jan 07 00:00:00 2000 PST'::timestamp with time zone)\n                           Rows Removed by Filter: 650\n               ->  Index Scan using _hyper_8_36_chunk_join_limit_time_device_id_idx on _hyper_8_36_chunk m2_2 (never executed)\n               ->  Index Scan using _hyper_8_37_chunk_join_limit_time_device_id_idx on _hyper_8_37_chunk m2_3 (never executed)\n         ->  Materialize (actual rows=22.00 loops=1)\n               ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1 (actual rows=19.00 loops=1)\n                     Order: m1.\"time\"\n                     ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m1_1 (actual rows=19.00 loops=1)\n                           Index Cond: (\"time\" > 'Fri Jan 07 00:00:00 2000 PST'::timestamp with time zone)\n                     ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m1_2 (never executed)\n                     ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m1_3 (never executed)\n                     ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m1_4 (never executed)\n\nDROP TABLE join_limit;\n-- test ChunkAppend projection #2661\n:PREFIX SELECT ts.timestamp, ht.timestamp\nFROM (\n  SELECT generate_series(\n    to_timestamp(FLOOR(EXTRACT (EPOCH FROM '2020-01-01T00:01:00Z'::timestamp) / 300) * 300) AT TIME ZONE 'UTC',\n    '2020-01-01T01:00:00Z',\n    '5 minutes'::interval\n  ) AS timestamp\n) ts\nLEFT JOIN i2661 ht ON\n  (FLOOR(EXTRACT (EPOCH FROM ht.\"timestamp\") / 300) * 300 = EXTRACT (EPOCH FROM ts.timestamp))\n  AND ht.timestamp > '2019-12-30T00:00:00Z'::timestamp\nORDER BY ts.timestamp, ht.timestamp;\n--- QUERY PLAN ---\n Sort (actual rows=33.00 loops=1)\n   Sort Key: ts.\"timestamp\", ht.\"timestamp\"\n   Sort Method: quicksort \n   ->  Merge Left Join (actual rows=33.00 loops=1)\n         Merge Cond: ((EXTRACT(epoch FROM ts.\"timestamp\")) = ((floor((EXTRACT(epoch FROM ht.\"timestamp\") / '300'::numeric)) * '300'::numeric)))\n         ->  Sort (actual rows=13.00 loops=1)\n               Sort Key: (EXTRACT(epoch FROM ts.\"timestamp\"))\n               Sort Method: quicksort \n               ->  Subquery Scan on ts (actual rows=13.00 loops=1)\n                     ->  ProjectSet (actual rows=13.00 loops=1)\n                           ->  Result (actual rows=1.00 loops=1)\n         ->  Sort (actual rows=514.00 loops=1)\n               Sort Key: ((floor((EXTRACT(epoch FROM ht.\"timestamp\") / '300'::numeric)) * '300'::numeric))\n               Sort Method: quicksort \n               ->  Result (actual rows=7201.00 loops=1)\n                     ->  Custom Scan (ChunkAppend) on i2661 ht (actual rows=7201.00 loops=1)\n                           Chunks excluded during startup: 0\n                           ->  Seq Scan on _hyper_7_31_chunk ht_1 (actual rows=1200.00 loops=1)\n                                 Filter: (\"timestamp\" > 'Mon Dec 30 00:00:00 2019'::timestamp without time zone)\n                           ->  Seq Scan on _hyper_7_32_chunk ht_2 (actual rows=5040.00 loops=1)\n                                 Filter: (\"timestamp\" > 'Mon Dec 30 00:00:00 2019'::timestamp without time zone)\n                           ->  Seq Scan on _hyper_7_33_chunk ht_3 (actual rows=961.00 loops=1)\n                                 Filter: (\"timestamp\" > 'Mon Dec 30 00:00:00 2019'::timestamp without time zone)\n\n-- #3030 test chunkappend keeps pathkeys when subpath is append\n-- on PG11 this will not use ChunkAppend but MergeAppend\nSET enable_seqscan TO FALSE;\nCREATE TABLE i3030(time timestamptz NOT NULL, a int, b int);\nSELECT table_name FROM create_hypertable('i3030', 'time', create_default_indexes=>false);\n table_name \n------------\n i3030\n\nCREATE INDEX ON i3030(a,time);\nINSERT INTO i3030 (time,a) SELECT time, a FROM generate_series('2000-01-01'::timestamptz,'2000-01-01 3:00:00'::timestamptz,'1min'::interval) time, generate_series(1,30) a;\nVACUUM (ANALYZE) i3030;\n:PREFIX SELECT * FROM i3030 where time BETWEEN '2000-01-01'::text::timestamptz AND '2000-01-03'::text::timestamptz ORDER BY a,time LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on i3030 (actual rows=1.00 loops=1)\n         Order: i3030.a, i3030.\"time\"\n         Chunks excluded during startup: 0\n         ->  Index Scan using _hyper_9_38_chunk_i3030_a_time_idx on _hyper_9_38_chunk (actual rows=1.00 loops=1)\n               Index Cond: ((\"time\" >= ('2000-01-01'::cstring)::timestamp with time zone) AND (\"time\" <= ('2000-01-03'::cstring)::timestamp with time zone))\n\nDROP TABLE i3030;\nRESET enable_seqscan;\n--parent runtime exclusion tests:\n--optimization works with ANY (array)\n:PREFIX\nSELECT *\nFROM append_test a\nWHERE a.attr @> ANY((SELECT coalesce(array_agg(attr), array[]::jsonb[]) FROM join_test_plain WHERE temp > 100)::jsonb[]);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on append_test a (actual rows=0.00 loops=1)\n   Hypertables excluded during runtime: 1\n   InitPlan 1 (returns $0)\n     ->  Aggregate (actual rows=1.00 loops=1)\n           ->  Seq Scan on join_test_plain (actual rows=0.00 loops=1)\n                 Filter: (temp > '100'::double precision)\n                 Rows Removed by Filter: 3\n   ->  Seq Scan on _hyper_1_1_chunk a_1 (never executed)\n         Filter: (attr @> ANY ($0))\n   ->  Seq Scan on _hyper_1_2_chunk a_2 (never executed)\n         Filter: (attr @> ANY ($0))\n   ->  Seq Scan on _hyper_1_3_chunk a_3 (never executed)\n         Filter: (attr @> ANY ($0))\n\n--optimization does not work for ANY subquery (does not force an initplan)\n:PREFIX\nSELECT *\nFROM append_test a\nWHERE a.attr @> ANY((SELECT attr FROM join_test_plain WHERE temp > 100));\n--- QUERY PLAN ---\n Nested Loop Semi Join (actual rows=0.00 loops=1)\n   Join Filter: (a.attr @> join_test_plain.attr)\n   ->  Append (actual rows=5.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk a_1 (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk a_2 (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk a_3 (actual rows=1.00 loops=1)\n   ->  Materialize (actual rows=0.00 loops=5)\n         ->  Seq Scan on join_test_plain (actual rows=0.00 loops=1)\n               Filter: (temp > '100'::double precision)\n               Rows Removed by Filter: 3\n\n--works on any strict operator without ANY\n:PREFIX\nSELECT *\nFROM append_test a\nWHERE a.attr @> (SELECT attr FROM join_test_plain WHERE temp > 100 limit 1);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on append_test a (actual rows=0.00 loops=1)\n   Hypertables excluded during runtime: 1\n   InitPlan 1 (returns $0)\n     ->  Limit (actual rows=0.00 loops=1)\n           ->  Seq Scan on join_test_plain (actual rows=0.00 loops=1)\n                 Filter: (temp > '100'::double precision)\n                 Rows Removed by Filter: 3\n   ->  Seq Scan on _hyper_1_1_chunk a_1 (never executed)\n         Filter: (attr @> $0)\n   ->  Seq Scan on _hyper_1_2_chunk a_2 (never executed)\n         Filter: (attr @> $0)\n   ->  Seq Scan on _hyper_1_3_chunk a_3 (never executed)\n         Filter: (attr @> $0)\n\n--optimization works with function calls\nCREATE OR REPLACE FUNCTION select_tag(_min_temp int)\n RETURNS jsonb[]\n LANGUAGE sql\n STABLE PARALLEL SAFE\nAS $function$\n   SELECT coalesce(array_agg(attr), array[]::jsonb[])\n  FROM join_test_plain\n  WHERE temp > _min_temp\n$function$;\n:PREFIX\nSELECT *\nFROM append_test a\nWHERE a.attr @> ANY((SELECT select_tag(100))::jsonb[]);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on append_test a (actual rows=0.00 loops=1)\n   Hypertables excluded during runtime: 1\n   InitPlan 1 (returns $0)\n     ->  Result (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_1_1_chunk a_1 (never executed)\n         Filter: (attr @> ANY ($0))\n   ->  Seq Scan on _hyper_1_2_chunk a_2 (never executed)\n         Filter: (attr @> ANY ($0))\n   ->  Seq Scan on _hyper_1_3_chunk a_3 (never executed)\n         Filter: (attr @> ANY ($0))\n\n--optimization does not work when result is null\n:PREFIX\nSELECT *\nFROM append_test a\nWHERE a.attr @> ANY((SELECT array_agg(attr) FROM join_test_plain WHERE temp > 100)::jsonb[]);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on append_test a (actual rows=0.00 loops=1)\n   Hypertables excluded during runtime: 0\n   InitPlan 1 (returns $0)\n     ->  Aggregate (actual rows=1.00 loops=1)\n           ->  Seq Scan on join_test_plain (actual rows=0.00 loops=1)\n                 Filter: (temp > '100'::double precision)\n                 Rows Removed by Filter: 3\n   ->  Seq Scan on _hyper_1_1_chunk a_1 (actual rows=0.00 loops=1)\n         Filter: (attr @> ANY ($0))\n         Rows Removed by Filter: 2\n   ->  Seq Scan on _hyper_1_2_chunk a_2 (actual rows=0.00 loops=1)\n         Filter: (attr @> ANY ($0))\n         Rows Removed by Filter: 2\n   ->  Seq Scan on _hyper_1_3_chunk a_3 (actual rows=0.00 loops=1)\n         Filter: (attr @> ANY ($0))\n         Rows Removed by Filter: 1\n\n-- Test that ConstraintAwareAppend properly locks relations in\n-- parallel query mode\nset timescaledb.enable_chunk_append=false;\ncall force_parallel(true);\n:PREFIX\nselect time, avg(temp), colorid from append_test\nwhere time > now_s() - interval '3 months 20 days'\ngroup by time, colorid;\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Gather (actual rows=3.00 loops=1)\n   Workers Planned: 1\n   Workers Launched: 1\n   Single Copy: true\n   ->  HashAggregate (actual rows=3.00 loops=1)\n         Group Key: append_test.\"time\", append_test.colorid\n         ->  Custom Scan (ConstraintAwareAppend) (actual rows=3.00 loops=1)\n               Hypertable: append_test\n               Chunks excluded during startup: 1\n               ->  Append (actual rows=3.00 loops=1)\n                     ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n                           Filter: (\"time\" > (now_s() - '@ 3 mons 20 days'::interval))\n                     ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n                           Filter: (\"time\" > (now_s() - '@ 3 mons 20 days'::interval))\n\nreset timescaledb.enable_chunk_append;\nreset max_parallel_workers_per_gather;\ncall force_parallel(false);\n--generate the results into two different files\n\\set ECHO errors\n--- Unoptimized results\n+++ Optimized results\n@@ -1,6 +1,6 @@\n              setting              | value \n ----------------------------------+-------\n- timescaledb.enable_optimizations | off\n+ timescaledb.enable_optimizations | on\n  timescaledb.enable_chunk_append  | on\n \n--- Unoptimized results\n+++ Optimized results\n@@ -1,7 +1,7 @@\n              setting              | value \n ----------------------------------+-------\n- timescaledb.enable_optimizations | off\n- timescaledb.enable_chunk_append  | on\n+ timescaledb.enable_optimizations | on\n+ timescaledb.enable_chunk_append  | off\n \n  time | temp | colorid | attr \n"
  },
  {
    "path": "test/expected/append-17.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\set TEST_BASE_NAME append\nSELECT format('include/%s_load.sql', :'TEST_BASE_NAME') as \"TEST_LOAD_NAME\",\n       format('include/%s_query.sql', :'TEST_BASE_NAME') as \"TEST_QUERY_NAME\",\n       format('%s/results/%s_results_optimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_OPTIMIZED\",\n       format('%s/results/%s_results_unoptimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_UNOPTIMIZED\"\n\\gset\nSELECT format('\\! diff -u --label \"Unoptimized results\" --label \"Optimized results\" %s %s', :'TEST_RESULTS_UNOPTIMIZED', :'TEST_RESULTS_OPTIMIZED') as \"DIFF_CMD\"\n\\gset\nSET timescaledb.enable_now_constify TO false;\n-- disable memoize node to avoid flaky results\nSET enable_memoize TO 'off';\n-- disable index only scans to avoid some flaky results\nSET enable_indexonlyscan TO FALSE;\n\\set PREFIX 'EXPLAIN (analyze, buffers off, costs off, timing off, summary off)'\n\\ir :TEST_LOAD_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- create a now() function for repeatable testing that always returns\n-- the same timestamp. It needs to be marked STABLE\nCREATE OR REPLACE FUNCTION now_s()\nRETURNS timestamptz LANGUAGE PLPGSQL STABLE PARALLEL SAFE AS\n$BODY$\nBEGIN\n    RAISE NOTICE 'Stable function now_s() called!';\n    RETURN '2017-08-22T10:00:00'::timestamptz;\nEND;\n$BODY$;\nCREATE OR REPLACE FUNCTION now_i()\nRETURNS timestamptz LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nBEGIN\n    RAISE NOTICE 'Immutable function now_i() called!';\n    RETURN '2017-08-22T10:00:00'::timestamptz;\nEND;\n$BODY$;\nCREATE OR REPLACE FUNCTION now_v()\nRETURNS timestamptz LANGUAGE PLPGSQL VOLATILE AS\n$BODY$\nBEGIN\n    RAISE NOTICE 'Volatile function now_v() called!';\n    RETURN '2017-08-22T10:00:00'::timestamptz;\nEND;\n$BODY$;\nCREATE OR REPLACE PROCEDURE force_parallel(on_or_off bool)\nLANGUAGE PLPGSQL AS\n$$\nBEGIN\n    IF current_setting('server_version_num')::int < 160000 THEN\n        IF on_or_off THEN\n            set force_parallel_mode = 'on';\n        ELSE\n            set force_parallel_mode = 'off';\n        END IF;\n    ELSE\n        IF on_or_off THEN\n            set debug_parallel_query = 'on';\n        ELSE\n            set debug_parallel_query = 'off';\n        END IF;\n    END IF;\nEND;\n$$;\nCREATE TABLE append_test(time timestamptz, temp float, colorid integer, attr jsonb);\nSELECT create_hypertable('append_test', 'time', chunk_time_interval => 2628000000000);\n    create_hypertable     \n--------------------------\n (1,public,append_test,t)\n\n-- create three chunks\nINSERT INTO append_test VALUES ('2017-03-22T09:18:22', 23.5, 1, '{\"a\": 1, \"b\": 2}'),\n                               ('2017-03-22T09:18:23', 21.5, 1, '{\"a\": 1, \"b\": 2}'),\n                               ('2017-05-22T09:18:22', 36.2, 2, '{\"c\": 3, \"b\": 2}'),\n                               ('2017-05-22T09:18:23', 15.2, 2, '{\"c\": 3}'),\n                               ('2017-08-22T09:18:22', 34.1, 3, '{\"c\": 4}');\nVACUUM (ANALYZE) append_test;\n-- Create another hypertable to join with\nCREATE TABLE join_test(time timestamptz, temp float, colorid integer);\nSELECT create_hypertable('join_test', 'time', chunk_time_interval => 2628000000000);\n   create_hypertable    \n------------------------\n (2,public,join_test,t)\n\nINSERT INTO join_test VALUES ('2017-01-22T09:18:22', 15.2, 1),\n                             ('2017-02-22T09:18:22', 24.5, 2),\n                             ('2017-08-22T09:18:22', 23.1, 3);\nVACUUM (ANALYZE) join_test;\n-- Create another table to join with which is not a hypertable.\nCREATE TABLE join_test_plain(time timestamptz, temp float, colorid integer, attr jsonb);\nINSERT INTO join_test_plain VALUES ('2017-01-22T09:18:22', 15.2, 1, '{\"a\": 1}'),\n                             ('2017-02-22T09:18:22', 24.5, 2, '{\"b\": 2}'),\n                             ('2017-08-22T09:18:22', 23.1, 3, '{\"c\": 3}');\nVACUUM (ANALYZE) join_test_plain;\n-- create hypertable with DATE time dimension\nCREATE TABLE metrics_date(time DATE NOT NULL);\nSELECT create_hypertable('metrics_date','time');\n     create_hypertable     \n---------------------------\n (3,public,metrics_date,t)\n\nINSERT INTO metrics_date SELECT generate_series('2000-01-01'::date, '2000-02-01'::date, '5m'::interval);\nVACUUM (ANALYZE) metrics_date;\n-- create hypertable with TIMESTAMP time dimension\nCREATE TABLE metrics_timestamp(time TIMESTAMP NOT NULL);\nSELECT create_hypertable('metrics_timestamp','time');\npsql:include/append_load.sql:91: WARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n       create_hypertable        \n--------------------------------\n (4,public,metrics_timestamp,t)\n\nINSERT INTO metrics_timestamp SELECT generate_series('2000-01-01'::date, '2000-02-01'::date, '5m'::interval);\nVACUUM (ANALYZE) metrics_timestamp;\n-- create hypertable with TIMESTAMPTZ time dimension\nCREATE TABLE metrics_timestamptz(time TIMESTAMPTZ NOT NULL, device_id INT NOT NULL);\nCREATE INDEX ON metrics_timestamptz(device_id,time);\nSELECT create_hypertable('metrics_timestamptz','time');\n        create_hypertable         \n----------------------------------\n (5,public,metrics_timestamptz,t)\n\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::date, '2000-02-01'::date, '5m'::interval), 1;\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::date, '2000-02-01'::date, '5m'::interval), 2;\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::date, '2000-02-01'::date, '5m'::interval), 3;\nVACUUM (ANALYZE) metrics_timestamptz;\n-- create space partitioned hypertable\nCREATE TABLE metrics_space(time timestamptz NOT NULL, device_id int NOT NULL, v1 float, v2 float, v3 text);\nSELECT create_hypertable('metrics_space','time','device_id',3);\n     create_hypertable      \n----------------------------\n (6,public,metrics_space,t)\n\nINSERT INTO metrics_space\nSELECT time, device_id, device_id + 0.25, device_id + 0.75, device_id\nFROM generate_series('2000-01-01'::timestamptz, '2000-01-14'::timestamptz, '5m'::interval) g1(time),\n  generate_series(1,10,1) g2(device_id)\nORDER BY time, device_id;\nVACUUM (ANALYZE) metrics_space;\n-- test ChunkAppend projection #2661\nCREATE TABLE i2661 (\n  machine_id int4 NOT NULL,\n  \"name\" varchar(255) NOT NULL,\n  \"timestamp\" timestamptz NOT NULL,\n  \"first\" float4 NULL\n);\nSELECT create_hypertable('i2661', 'timestamp');\npsql:include/append_load.sql:123: WARNING:  column type \"character varying\" used for \"name\" does not follow best practices\n create_hypertable  \n--------------------\n (7,public,i2661,t)\n\nINSERT INTO i2661 SELECT 1, 'speed', generate_series('2019-12-31 00:00:00', '2020-01-10 00:00:00', '2m'::interval), 0;\nVACUUM (ANALYZE) i2661;\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- canary for results diff\n-- this should be the only output of the results diff\nSELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations'),('timescaledb.enable_chunk_append')) v(setting);\n             setting              | value \n----------------------------------+-------\n timescaledb.enable_optimizations | on\n timescaledb.enable_chunk_append  | on\n\n-- query should exclude all chunks with optimization on\n:PREFIX\nSELECT * FROM append_test WHERE time > now_s() + '1 month'\nORDER BY time DESC;\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Sort (actual rows=0.00 loops=1)\n   Sort Key: append_test.\"time\" DESC\n   Sort Method: quicksort \n   ->  Custom Scan (ChunkAppend) on append_test (actual rows=0.00 loops=1)\n         Chunks excluded during startup: 3\n\n--query should exclude all chunks and be a MergeAppend\n:PREFIX\nSELECT * FROM append_test WHERE time > now_s() + '1 month'\nORDER BY time DESC limit 1;\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Limit (actual rows=0.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on append_test (actual rows=0.00 loops=1)\n         Order: append_test.\"time\" DESC\n         Chunks excluded during startup: 3\n\n-- when optimized, the plan should be a constraint-aware append and\n-- cover only one chunk. It should be a backward index scan due to\n-- descending index on time. Should also skip the main table, since it\n-- cannot hold tuples\n:PREFIX\nSELECT * FROM append_test WHERE time > now_s() - interval '2 months';\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on append_test (actual rows=1.00 loops=1)\n   Chunks excluded during startup: 2\n   ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         Filter: (\"time\" > (now_s() - '@ 2 mons'::interval))\n\n-- adding ORDER BY and LIMIT should turn the plan into an optimized\n-- ordered append plan\n:PREFIX\nSELECT * FROM append_test WHERE time > now_s() - interval '2 months'\nORDER BY time LIMIT 3;\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Sort (actual rows=1.00 loops=1)\n         Sort Key: append_test.\"time\"\n         Sort Method: quicksort \n         ->  Custom Scan (ChunkAppend) on append_test (actual rows=1.00 loops=1)\n               Chunks excluded during startup: 2\n               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n                     Filter: (\"time\" > (now_s() - '@ 2 mons'::interval))\n\n-- no optimized plan for queries with restrictions that can be\n-- constified at planning time. Regular planning-time constraint\n-- exclusion should occur.\n:PREFIX\nSELECT * FROM append_test WHERE time > now_i() - interval '2 months'\nORDER BY time;\npsql:include/append_query.sql:37: NOTICE:  Immutable function now_i() called!\n--- QUERY PLAN ---\n Sort (actual rows=1.00 loops=1)\n   Sort Key: append_test.\"time\"\n   Sort Method: quicksort \n   ->  Custom Scan (ChunkAppend) on append_test (actual rows=1.00 loops=1)\n         Chunks excluded during startup: 2\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               Filter: (\"time\" > ('Tue Aug 22 10:00:00 2017 PDT'::timestamp with time zone - '@ 2 mons'::interval))\n\n-- currently, we cannot distinguish between stable and volatile\n-- functions as far as applying our modified plan. However, volatile\n-- function should not be pre-evaluated to constants, so no chunk\n-- exclusion should occur.\n:PREFIX\nSELECT * FROM append_test WHERE time > now_v() - interval '2 months'\nORDER BY time;\npsql:include/append_query.sql:45: NOTICE:  Volatile function now_v() called!\npsql:include/append_query.sql:45: NOTICE:  Volatile function now_v() called!\npsql:include/append_query.sql:45: NOTICE:  Volatile function now_v() called!\npsql:include/append_query.sql:45: NOTICE:  Volatile function now_v() called!\npsql:include/append_query.sql:45: NOTICE:  Volatile function now_v() called!\n--- QUERY PLAN ---\n Sort (actual rows=1.00 loops=1)\n   Sort Key: append_test.\"time\"\n   Sort Method: quicksort \n   ->  Custom Scan (ChunkAppend) on append_test (actual rows=1.00 loops=1)\n         Chunks excluded during startup: 0\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=0.00 loops=1)\n               Filter: (\"time\" > (now_v() - '@ 2 mons'::interval))\n               Rows Removed by Filter: 2\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=0.00 loops=1)\n               Filter: (\"time\" > (now_v() - '@ 2 mons'::interval))\n               Rows Removed by Filter: 2\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               Filter: (\"time\" > (now_v() - '@ 2 mons'::interval))\n\n-- prepared statement output should be the same regardless of\n-- optimizations\nPREPARE query_opt AS\nSELECT * FROM append_test WHERE time > now_s() - interval '2 months'\nORDER BY time;\n:PREFIX EXECUTE query_opt;\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Sort (actual rows=1.00 loops=1)\n   Sort Key: append_test.\"time\"\n   Sort Method: quicksort \n   ->  Custom Scan (ChunkAppend) on append_test (actual rows=1.00 loops=1)\n         Chunks excluded during startup: 2\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               Filter: (\"time\" > (now_s() - '@ 2 mons'::interval))\n\nDEALLOCATE query_opt;\n-- aggregates should produce same output\n:PREFIX\nSELECT date_trunc('year', time) t, avg(temp) FROM append_test\nWHERE time > now_s() - interval '4 months'\nGROUP BY t\nORDER BY t DESC;\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n GroupAggregate (actual rows=1.00 loops=1)\n   Group Key: (date_trunc('year'::text, append_test.\"time\"))\n   ->  Sort (actual rows=3.00 loops=1)\n         Sort Key: (date_trunc('year'::text, append_test.\"time\")) DESC\n         Sort Method: quicksort \n         ->  Result (actual rows=3.00 loops=1)\n               ->  Custom Scan (ChunkAppend) on append_test (actual rows=3.00 loops=1)\n                     Chunks excluded during startup: 1\n                     ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n                           Filter: (\"time\" > (now_s() - '@ 4 mons'::interval))\n                     ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n                           Filter: (\"time\" > (now_s() - '@ 4 mons'::interval))\n\n-- querying outside the time range should return nothing. This tests\n-- that ConstraintAwareAppend can handle the case when an Append node\n-- is turned into a Result node due to no children\n:PREFIX\nSELECT date_trunc('year', time) t, avg(temp)\nFROM append_test\nWHERE time < '2016-03-22'\nAND date_part('dow', time) between 1 and 5\nGROUP BY t\nORDER BY t DESC;\n--- QUERY PLAN ---\n GroupAggregate (actual rows=0.00 loops=1)\n   Group Key: (date_trunc('year'::text, \"time\"))\n   ->  Sort (actual rows=0.00 loops=1)\n         Sort Key: (date_trunc('year'::text, \"time\")) DESC\n         Sort Method: quicksort \n         ->  Result (actual rows=0.00 loops=1)\n               One-Time Filter: false\n\n-- a parameterized query can safely constify params, so won't be\n-- optimized by constraint-aware append since regular constraint\n-- exclusion works just fine\nPREPARE query_param AS\nSELECT * FROM append_test WHERE time > $1 ORDER BY time;\n:PREFIX\nEXECUTE query_param(now_s() - interval '2 months');\npsql:include/append_query.sql:82: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Sort (actual rows=1.00 loops=1)\n   Sort Key: _hyper_1_3_chunk.\"time\"\n   Sort Method: quicksort \n   ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n\nDEALLOCATE query_param;\n--test with cte\n:PREFIX\nWITH data AS (\n    SELECT time_bucket(INTERVAL '30 day', TIME) AS btime, AVG(temp) AS VALUE\n    FROM append_test\n    WHERE\n        TIME > now_s() - INTERVAL '400 day'\n    AND colorid > 0\n    GROUP BY btime\n),\nperiod AS (\n    SELECT time_bucket(INTERVAL '30 day', TIME) AS btime\n      FROM  GENERATE_SERIES('2017-03-22T01:01:01', '2017-08-23T01:01:01', INTERVAL '30 day') TIME\n  )\nSELECT period.btime, VALUE\n    FROM period\n    LEFT JOIN DATA USING (btime)\n    ORDER BY period.btime;\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Sort (actual rows=6.00 loops=1)\n   Sort Key: (time_bucket('@ 30 days'::interval, \"time\".\"time\"))\n   Sort Method: quicksort \n   ->  Hash Left Join (actual rows=6.00 loops=1)\n         Hash Cond: (time_bucket('@ 30 days'::interval, \"time\".\"time\") = data.btime)\n         ->  Function Scan on generate_series \"time\" (actual rows=6.00 loops=1)\n         ->  Hash (actual rows=3.00 loops=1)\n               ->  Subquery Scan on data (actual rows=3.00 loops=1)\n                     ->  HashAggregate (actual rows=3.00 loops=1)\n                           Group Key: time_bucket('@ 30 days'::interval, append_test.\"time\")\n                           ->  Result (actual rows=5.00 loops=1)\n                                 ->  Custom Scan (ChunkAppend) on append_test (actual rows=5.00 loops=1)\n                                       Chunks excluded during startup: 0\n                                       ->  Seq Scan on _hyper_1_1_chunk (actual rows=2.00 loops=1)\n                                             Filter: ((colorid > 0) AND (\"time\" > (now_s() - '@ 400 days'::interval)))\n                                       ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n                                             Filter: ((colorid > 0) AND (\"time\" > (now_s() - '@ 400 days'::interval)))\n                                       ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n                                             Filter: ((colorid > 0) AND (\"time\" > (now_s() - '@ 400 days'::interval)))\n\nWITH data AS (\n    SELECT time_bucket(INTERVAL '30 day', TIME) AS btime, AVG(temp) AS VALUE\n    FROM append_test\n    WHERE\n        TIME > now_s() - INTERVAL '400 day'\n    AND colorid > 0\n    GROUP BY btime\n),\nperiod AS (\n    SELECT time_bucket(INTERVAL '30 day', TIME) AS btime\n      FROM  GENERATE_SERIES('2017-03-22T01:01:01', '2017-08-23T01:01:01', INTERVAL '30 day') TIME\n  )\nSELECT period.btime, VALUE\n    FROM period\n    LEFT JOIN DATA USING (btime)\n    ORDER BY period.btime;\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\n            btime             | value \n------------------------------+-------\n Fri Mar 03 16:00:00 2017 PST |  22.5\n Sun Apr 02 17:00:00 2017 PDT |      \n Tue May 02 17:00:00 2017 PDT |  25.7\n Thu Jun 01 17:00:00 2017 PDT |      \n Sat Jul 01 17:00:00 2017 PDT |      \n Mon Jul 31 17:00:00 2017 PDT |  34.1\n\n-- force nested loop join with no materialization. This tests that the\n-- inner ConstraintAwareScan supports resetting its scan for every\n-- iteration of the outer relation loop\nset enable_hashjoin = 'off';\nset enable_mergejoin = 'off';\nset enable_material = 'off';\n:PREFIX\nSELECT * FROM append_test a INNER JOIN join_test j ON (a.colorid = j.colorid)\nWHERE a.time > now_s() - interval '3 hours' AND j.time > now_s() - interval '3 hours';\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Nested Loop (actual rows=1.00 loops=1)\n   Join Filter: (a.colorid = j.colorid)\n   ->  Custom Scan (ChunkAppend) on append_test a (actual rows=1.00 loops=1)\n         Chunks excluded during startup: 2\n         ->  Seq Scan on _hyper_1_3_chunk a_1 (actual rows=1.00 loops=1)\n               Filter: (\"time\" > (now_s() - '@ 3 hours'::interval))\n   ->  Custom Scan (ChunkAppend) on join_test j (actual rows=1.00 loops=1)\n         Chunks excluded during startup: 2\n         ->  Seq Scan on _hyper_2_6_chunk j_1 (actual rows=1.00 loops=1)\n               Filter: (\"time\" > (now_s() - '@ 3 hours'::interval))\n\nreset enable_hashjoin;\nreset enable_mergejoin;\nreset enable_material;\n-- test constraint_exclusion with date time dimension and DATE/TIMESTAMP/TIMESTAMPTZ constraints\n-- the queries should all have 3 chunks\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=4609.00 loops=1)\n   Order: metrics_date.\"time\"\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_3_11_chunk_metrics_date_time_idx on _hyper_3_11_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=4609.00 loops=1)\n   Order: metrics_date.\"time\"\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_3_11_chunk_metrics_date_time_idx on _hyper_3_11_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=4609.00 loops=1)\n   Order: metrics_date.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=2016.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_3_11_chunk_metrics_date_time_idx on _hyper_3_11_chunk (actual rows=1441.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n\n-- test Const OP Var\n-- the queries should all have 3 chunks\n:PREFIX SELECT * FROM metrics_date WHERE '2000-01-15'::date < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=4609.00 loops=1)\n   Order: metrics_date.\"time\"\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_3_11_chunk_metrics_date_time_idx on _hyper_3_11_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_date WHERE '2000-01-15'::timestamp < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=4609.00 loops=1)\n   Order: metrics_date.\"time\"\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_3_11_chunk_metrics_date_time_idx on _hyper_3_11_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_date WHERE '2000-01-15'::timestamptz < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=4609.00 loops=1)\n   Order: metrics_date.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=2016.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_3_11_chunk_metrics_date_time_idx on _hyper_3_11_chunk (actual rows=1441.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n\n-- test 2 constraints\n-- the queries should all have 2 chunks\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::date AND time < '2000-01-21'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=1440.00 loops=1)\n   Order: metrics_date.\"time\"\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: ((\"time\" > '01-15-2000'::date) AND (\"time\" < '01-21-2000'::date))\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=288.00 loops=1)\n         Index Cond: ((\"time\" > '01-15-2000'::date) AND (\"time\" < '01-21-2000'::date))\n\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::timestamp AND time < '2000-01-21'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=1440.00 loops=1)\n   Order: metrics_date.\"time\"\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000'::timestamp without time zone))\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=288.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000'::timestamp without time zone))\n\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::timestamptz AND time < '2000-01-21'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=1440.00 loops=1)\n   Order: metrics_date.\"time\"\n   Chunks excluded during startup: 3\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=288.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000 PST'::timestamp with time zone))\n\n-- test constraint_exclusion with timestamp time dimension and DATE/TIMESTAMP/TIMESTAMPTZ constraints\n-- the queries should all have 3 chunks\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=4896.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_4_16_chunk_metrics_timestamp_time_idx on _hyper_4_16_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=4896.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_4_16_chunk_metrics_timestamp_time_idx on _hyper_4_16_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=4896.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=2016.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_4_16_chunk_metrics_timestamp_time_idx on _hyper_4_16_chunk (actual rows=1441.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n\n-- test Const OP Var\n-- the queries should all have 3 chunks\n:PREFIX SELECT * FROM metrics_timestamp WHERE '2000-01-15'::date < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=4896.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_4_16_chunk_metrics_timestamp_time_idx on _hyper_4_16_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE '2000-01-15'::timestamp < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=4896.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_4_16_chunk_metrics_timestamp_time_idx on _hyper_4_16_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE '2000-01-15'::timestamptz < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=4896.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=2016.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_4_16_chunk_metrics_timestamp_time_idx on _hyper_4_16_chunk (actual rows=1441.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n\n-- test 2 constraints\n-- the queries should all have 2 chunks\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::date AND time < '2000-01-21'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=1727.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: ((\"time\" > '01-15-2000'::date) AND (\"time\" < '01-21-2000'::date))\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=288.00 loops=1)\n         Index Cond: ((\"time\" > '01-15-2000'::date) AND (\"time\" < '01-21-2000'::date))\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::timestamp AND time < '2000-01-21'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=1727.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000'::timestamp without time zone))\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=288.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000'::timestamp without time zone))\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::timestamptz AND time < '2000-01-21'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=1727.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 3\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=288.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000 PST'::timestamp with time zone))\n\n-- test constraint_exclusion with timestamptz time dimension and DATE/TIMESTAMP/TIMESTAMPTZ constraints\n-- the queries should all have 3 chunks\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=14688.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=14688.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=14688.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n   ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n\n-- test Const OP Var\n-- the queries should all have 3 chunks\n:PREFIX SELECT time FROM metrics_timestamptz WHERE '2000-01-15'::date < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=14688.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE '2000-01-15'::timestamp < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=14688.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE '2000-01-15'::timestamptz < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=14688.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n   ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n\n-- test 2 constraints\n-- the queries should all have 2 chunks\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::date AND time < '2000-01-21'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=5181.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 3\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: ((\"time\" > '01-15-2000'::date) AND (\"time\" < '01-21-2000'::date))\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=1152.00 loops=1)\n         Index Cond: ((\"time\" > '01-15-2000'::date) AND (\"time\" < '01-21-2000'::date))\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::timestamp AND time < '2000-01-21'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=5181.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 3\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000'::timestamp without time zone))\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=1152.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000'::timestamp without time zone))\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::timestamptz AND time < '2000-01-21'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=5181.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=1152.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000 PST'::timestamp with time zone))\n\n-- test constraint_exclusion with space partitioning and DATE/TIMESTAMP/TIMESTAMPTZ constraints\n-- exclusion for constraints with non-matching datatypes not working for space partitioning atm\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n-- test Const OP Var\n-- exclusion for constraints with non-matching datatypes not working for space partitioning atm\n:PREFIX SELECT time FROM metrics_space WHERE '2000-01-10'::date < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n\n:PREFIX SELECT time FROM metrics_space WHERE '2000-01-10'::timestamp < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT time FROM metrics_space WHERE '2000-01-10'::timestamptz < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n-- test 2 constraints\n-- exclusion for constraints with non-matching datatypes not working for space partitioning atm\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::date AND time < '2000-01-15'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamp AND time < '2000-01-15'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz AND time < '2000-01-15'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone))\n\n-- test filtering on space partition\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz AND device_id = 1 ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=1152.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_device_id_time_idx on _hyper_6_25_chunk (actual rows=767.00 loops=1)\n         Index Cond: ((device_id = 1) AND (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_device_id_time_idx on _hyper_6_28_chunk (actual rows=385.00 loops=1)\n         Index Cond: ((device_id = 1) AND (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz AND device_id IN (1,2) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=2304.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=1534.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=767.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (device_id = ANY ('{1,2}'::integer[]))\n               Rows Removed by Filter: 2301\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=767.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (device_id = ANY ('{1,2}'::integer[]))\n               Rows Removed by Filter: 2301\n   ->  Merge Append (actual rows=770.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=385.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (device_id = ANY ('{1,2}'::integer[]))\n               Rows Removed by Filter: 1155\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=385.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (device_id = ANY ('{1,2}'::integer[]))\n               Rows Removed by Filter: 1155\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz AND device_id IN (VALUES(1)) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=1152.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_device_id_time_idx on _hyper_6_25_chunk (actual rows=767.00 loops=1)\n         Index Cond: ((device_id = 1) AND (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_device_id_time_idx on _hyper_6_28_chunk (actual rows=385.00 loops=1)\n         Index Cond: ((device_id = 1) AND (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz AND v3 IN (VALUES('1')) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=1152.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=767.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=767.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (v3 = '1'::text)\n               Rows Removed by Filter: 2301\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (v3 = '1'::text)\n               Rows Removed by Filter: 3068\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (v3 = '1'::text)\n               Rows Removed by Filter: 1534\n   ->  Merge Append (actual rows=385.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=385.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (v3 = '1'::text)\n               Rows Removed by Filter: 1155\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (v3 = '1'::text)\n               Rows Removed by Filter: 1540\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (v3 = '1'::text)\n               Rows Removed by Filter: 770\n\n:PREFIX SELECT * FROM metrics_space\nWHERE time = (VALUES ('2019-12-24' at time zone 'UTC'))\n  AND v3 NOT IN (VALUES ('1'));\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=0.00 loops=1)\n   Chunks excluded during startup: 0\n   Chunks excluded during runtime: 9\n   InitPlan 1\n     ->  Result (actual rows=1.00 loops=1)\n   ->  Index Scan using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (never executed)\n         Index Cond: (\"time\" = (InitPlan 1).col1)\n         Filter: (NOT (ANY (v3 = (hashed SubPlan 2).col1)))\n         SubPlan 2\n           ->  Result (never executed)\n   ->  Index Scan using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (never executed)\n         Index Cond: (\"time\" = (InitPlan 1).col1)\n         Filter: (NOT (ANY (v3 = (hashed SubPlan 2).col1)))\n   ->  Index Scan using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (never executed)\n         Index Cond: (\"time\" = (InitPlan 1).col1)\n         Filter: (NOT (ANY (v3 = (hashed SubPlan 2).col1)))\n   ->  Index Scan using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (never executed)\n         Index Cond: (\"time\" = (InitPlan 1).col1)\n         Filter: (NOT (ANY (v3 = (hashed SubPlan 2).col1)))\n   ->  Index Scan using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (never executed)\n         Index Cond: (\"time\" = (InitPlan 1).col1)\n         Filter: (NOT (ANY (v3 = (hashed SubPlan 2).col1)))\n   ->  Index Scan using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (never executed)\n         Index Cond: (\"time\" = (InitPlan 1).col1)\n         Filter: (NOT (ANY (v3 = (hashed SubPlan 2).col1)))\n   ->  Index Scan using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (never executed)\n         Index Cond: (\"time\" = (InitPlan 1).col1)\n         Filter: (NOT (ANY (v3 = (hashed SubPlan 2).col1)))\n   ->  Index Scan using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (never executed)\n         Index Cond: (\"time\" = (InitPlan 1).col1)\n         Filter: (NOT (ANY (v3 = (hashed SubPlan 2).col1)))\n   ->  Index Scan using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (never executed)\n         Index Cond: (\"time\" = (InitPlan 1).col1)\n         Filter: (NOT (ANY (v3 = (hashed SubPlan 2).col1)))\n\n-- test CURRENT_DATE\n-- should be 0 chunks\n:PREFIX SELECT time FROM metrics_date WHERE time > CURRENT_DATE ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=0.00 loops=1)\n   Order: metrics_date.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_timestamp WHERE time > CURRENT_DATE ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=0.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > CURRENT_DATE ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=0.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_space WHERE time > CURRENT_DATE ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=0.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n\n-- test CURRENT_TIMESTAMP\n-- should be 0 chunks\n:PREFIX SELECT time FROM metrics_date WHERE time > CURRENT_TIMESTAMP ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=0.00 loops=1)\n   Order: metrics_date.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_timestamp WHERE time > CURRENT_TIMESTAMP ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=0.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > CURRENT_TIMESTAMP ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=0.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_space WHERE time > CURRENT_TIMESTAMP ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=0.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n\n-- test now()\n-- should be 0 chunks\n:PREFIX SELECT time FROM metrics_date WHERE time > now() ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=0.00 loops=1)\n   Order: metrics_date.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_timestamp WHERE time > now() ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=0.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > now() ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=0.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_space WHERE time > now() ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=0.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n\n-- query with tablesample and planner exclusion\n:PREFIX\nSELECT * FROM metrics_date TABLESAMPLE BERNOULLI(5) REPEATABLE(0)\nWHERE time > '2000-01-15'\nORDER BY time DESC;\n--- QUERY PLAN ---\n Sort (actual rows=217.00 loops=1)\n   Sort Key: metrics_date.\"time\" DESC\n   Sort Method: quicksort \n   ->  Append (actual rows=217.00 loops=1)\n         ->  Sample Scan on _hyper_3_11_chunk (actual rows=72.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n         ->  Sample Scan on _hyper_3_10_chunk (actual rows=94.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n         ->  Sample Scan on _hyper_3_9_chunk (actual rows=51.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > '01-15-2000'::date)\n               Rows Removed by Filter: 43\n\n-- query with tablesample and startup exclusion\n:PREFIX\nSELECT * FROM metrics_date TABLESAMPLE BERNOULLI(5) REPEATABLE(0)\nWHERE time > '2000-01-15'::text::date\nORDER BY time DESC;\n--- QUERY PLAN ---\n Sort (actual rows=217.00 loops=1)\n   Sort Key: metrics_date.\"time\" DESC\n   Sort Method: quicksort \n   ->  Custom Scan (ChunkAppend) on metrics_date (actual rows=217.00 loops=1)\n         Chunks excluded during startup: 2\n         ->  Sample Scan on _hyper_3_11_chunk (actual rows=72.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > ('2000-01-15'::cstring)::date)\n         ->  Sample Scan on _hyper_3_10_chunk (actual rows=94.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > ('2000-01-15'::cstring)::date)\n         ->  Sample Scan on _hyper_3_9_chunk (actual rows=51.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > ('2000-01-15'::cstring)::date)\n               Rows Removed by Filter: 43\n\n-- query with tablesample, space partitioning and planner exclusion\n:PREFIX\nSELECT * FROM metrics_space TABLESAMPLE BERNOULLI(5) REPEATABLE(0)\nWHERE time > '2000-01-10'::timestamptz\nORDER BY time DESC, device_id;\n--- QUERY PLAN ---\n Sort (actual rows=522.00 loops=1)\n   Sort Key: metrics_space.\"time\" DESC, metrics_space.device_id\n   Sort Method: quicksort \n   ->  Append (actual rows=522.00 loops=1)\n         ->  Sample Scan on _hyper_6_30_chunk (actual rows=35.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Sample Scan on _hyper_6_29_chunk (actual rows=61.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Sample Scan on _hyper_6_28_chunk (actual rows=61.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Sample Scan on _hyper_6_27_chunk (actual rows=65.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Rows Removed by Filter: 113\n         ->  Sample Scan on _hyper_6_26_chunk (actual rows=150.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Rows Removed by Filter: 218\n         ->  Sample Scan on _hyper_6_25_chunk (actual rows=150.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Rows Removed by Filter: 218\n\n-- test runtime exclusion\n-- test runtime exclusion with LATERAL and 2 hypertables\n:PREFIX SELECT m1.time, m2.time FROM metrics_timestamptz m1 LEFT JOIN LATERAL(SELECT time FROM metrics_timestamptz m2 WHERE m1.time = m2.time LIMIT 1) m2 ON true ORDER BY m1.time;\n--- QUERY PLAN ---\n Nested Loop Left Join (actual rows=26787.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1 (actual rows=26787.00 loops=1)\n         Order: m1.\"time\"\n         ->  Index Scan Backward using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m1_1 (actual rows=4032.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m1_2 (actual rows=6048.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m1_3 (actual rows=6048.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m1_4 (actual rows=6048.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m1_5 (actual rows=4611.00 loops=1)\n   ->  Limit (actual rows=1.00 loops=26787)\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m2 (actual rows=1.00 loops=26787)\n               Chunks excluded during runtime: 4\n               ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m2_1 (actual rows=1.00 loops=4032)\n                     Index Cond: (\"time\" = m1.\"time\")\n               ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m2_2 (actual rows=1.00 loops=6048)\n                     Index Cond: (\"time\" = m1.\"time\")\n               ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m2_3 (actual rows=1.00 loops=6048)\n                     Index Cond: (\"time\" = m1.\"time\")\n               ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m2_4 (actual rows=1.00 loops=6048)\n                     Index Cond: (\"time\" = m1.\"time\")\n               ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m2_5 (actual rows=1.00 loops=4611)\n                     Index Cond: (\"time\" = m1.\"time\")\n\n-- test runtime exclusion and startup exclusions\n:PREFIX SELECT m1.time, m2.time FROM metrics_timestamptz m1 LEFT JOIN LATERAL(SELECT time FROM metrics_timestamptz m2 WHERE m1.time = m2.time AND m2.time < '2000-01-10'::text::timestamptz LIMIT 1) m2 ON true ORDER BY m1.time;\n--- QUERY PLAN ---\n Nested Loop Left Join (actual rows=26787.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1 (actual rows=26787.00 loops=1)\n         Order: m1.\"time\"\n         ->  Index Scan Backward using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m1_1 (actual rows=4032.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m1_2 (actual rows=6048.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m1_3 (actual rows=6048.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m1_4 (actual rows=6048.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m1_5 (actual rows=4611.00 loops=1)\n   ->  Limit (actual rows=0.00 loops=26787)\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m2 (actual rows=0.00 loops=26787)\n               Chunks excluded during startup: 3\n               Chunks excluded during runtime: 1\n               ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m2_1 (actual rows=1.00 loops=4032)\n                     Index Cond: ((\"time\" < ('2000-01-10'::cstring)::timestamp with time zone) AND (\"time\" = m1.\"time\"))\n               ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m2_2 (actual rows=1.00 loops=6048)\n                     Index Cond: ((\"time\" < ('2000-01-10'::cstring)::timestamp with time zone) AND (\"time\" = m1.\"time\"))\n\n-- test runtime exclusion does not activate for constraints on non-partitioning columns\n-- should not use runtime exclusion\n:PREFIX SELECT * FROM append_test a LEFT JOIN LATERAL(SELECT * FROM join_test j WHERE a.colorid = j.colorid ORDER BY time DESC LIMIT 1) j ON true ORDER BY a.time LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Nested Loop Left Join (actual rows=1.00 loops=1)\n         ->  Custom Scan (ChunkAppend) on append_test a (actual rows=1.00 loops=1)\n               Order: a.\"time\"\n               ->  Index Scan Backward using _hyper_1_1_chunk_append_test_time_idx on _hyper_1_1_chunk a_1 (actual rows=1.00 loops=1)\n               ->  Index Scan Backward using _hyper_1_2_chunk_append_test_time_idx on _hyper_1_2_chunk a_2 (never executed)\n               ->  Index Scan Backward using _hyper_1_3_chunk_append_test_time_idx on _hyper_1_3_chunk a_3 (never executed)\n         ->  Limit (actual rows=1.00 loops=1)\n               ->  Custom Scan (ChunkAppend) on join_test j (actual rows=1.00 loops=1)\n                     Order: j.\"time\" DESC\n                     Hypertables excluded during runtime: 0\n                     ->  Index Scan using _hyper_2_6_chunk_join_test_time_idx on _hyper_2_6_chunk j_1 (actual rows=0.00 loops=1)\n                           Filter: (a.colorid = colorid)\n                           Rows Removed by Filter: 1\n                     ->  Index Scan using _hyper_2_5_chunk_join_test_time_idx on _hyper_2_5_chunk j_2 (actual rows=0.00 loops=1)\n                           Filter: (a.colorid = colorid)\n                           Rows Removed by Filter: 1\n                     ->  Index Scan using _hyper_2_4_chunk_join_test_time_idx on _hyper_2_4_chunk j_3 (actual rows=1.00 loops=1)\n                           Filter: (a.colorid = colorid)\n\n-- test runtime exclusion with LATERAL and generate_series\n:PREFIX SELECT g.time FROM generate_series('2000-01-01'::timestamptz, '2000-02-01'::timestamptz, '1d'::interval) g(time) LEFT JOIN LATERAL(SELECT time FROM metrics_timestamptz m WHERE m.time=g.time LIMIT 1) m ON true;\n--- QUERY PLAN ---\n Nested Loop Left Join (actual rows=32.00 loops=1)\n   ->  Function Scan on generate_series g (actual rows=32.00 loops=1)\n   ->  Limit (actual rows=1.00 loops=32)\n         ->  Result (actual rows=1.00 loops=32)\n               ->  Custom Scan (ChunkAppend) on metrics_timestamptz m (actual rows=1.00 loops=32)\n                     Chunks excluded during runtime: 4\n                     ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m_1 (actual rows=1.00 loops=5)\n                           Index Cond: (\"time\" = g.\"time\")\n                     ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m_2 (actual rows=1.00 loops=7)\n                           Index Cond: (\"time\" = g.\"time\")\n                     ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m_3 (actual rows=1.00 loops=7)\n                           Index Cond: (\"time\" = g.\"time\")\n                     ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m_4 (actual rows=1.00 loops=7)\n                           Index Cond: (\"time\" = g.\"time\")\n                     ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m_5 (actual rows=1.00 loops=6)\n                           Index Cond: (\"time\" = g.\"time\")\n\n:PREFIX SELECT * FROM generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval) AS g(time) INNER JOIN LATERAL (SELECT time FROM metrics_timestamptz m WHERE time=g.time) m ON true;\n--- QUERY PLAN ---\n Hash Join (actual rows=96.00 loops=1)\n   Hash Cond: (g.\"time\" = m.\"time\")\n   ->  Function Scan on generate_series g (actual rows=32.00 loops=1)\n   ->  Hash (actual rows=26787.00 loops=1)\n         ->  Append (actual rows=26787.00 loops=1)\n               ->  Seq Scan on _hyper_5_17_chunk m_1 (actual rows=4032.00 loops=1)\n               ->  Seq Scan on _hyper_5_18_chunk m_2 (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_19_chunk m_3 (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_20_chunk m_4 (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_21_chunk m_5 (actual rows=4611.00 loops=1)\n\n:PREFIX SELECT * FROM generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval) AS g(time) INNER JOIN LATERAL (SELECT time FROM metrics_timestamptz m WHERE time=g.time ORDER BY time) m ON true;\n--- QUERY PLAN ---\n Nested Loop (actual rows=96.00 loops=1)\n   ->  Function Scan on generate_series g (actual rows=32.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m (actual rows=3.00 loops=32)\n         Chunks excluded during runtime: 4\n         ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m_1 (actual rows=3.00 loops=5)\n               Index Cond: (\"time\" = g.\"time\")\n         ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m_2 (actual rows=3.00 loops=7)\n               Index Cond: (\"time\" = g.\"time\")\n         ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m_3 (actual rows=3.00 loops=7)\n               Index Cond: (\"time\" = g.\"time\")\n         ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m_4 (actual rows=3.00 loops=7)\n               Index Cond: (\"time\" = g.\"time\")\n         ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m_5 (actual rows=3.00 loops=6)\n               Index Cond: (\"time\" = g.\"time\")\n\n:PREFIX SELECT * FROM generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval) AS g(time) INNER JOIN LATERAL (SELECT time FROM metrics_timestamptz m WHERE time>g.time + '1 day' ORDER BY time LIMIT 1) m ON true;\n--- QUERY PLAN ---\n Nested Loop (actual rows=30.00 loops=1)\n   ->  Function Scan on generate_series g (actual rows=32.00 loops=1)\n   ->  Limit (actual rows=1.00 loops=32)\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m (actual rows=1.00 loops=32)\n               Order: m.\"time\"\n               Chunks excluded during startup: 0\n               Chunks excluded during runtime: 2\n               ->  Index Scan Backward using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m_1 (actual rows=1.00 loops=4)\n                     Index Cond: (\"time\" > (g.\"time\" + '@ 1 day'::interval))\n               ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m_2 (actual rows=1.00 loops=7)\n                     Index Cond: (\"time\" > (g.\"time\" + '@ 1 day'::interval))\n               ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m_3 (actual rows=1.00 loops=7)\n                     Index Cond: (\"time\" > (g.\"time\" + '@ 1 day'::interval))\n               ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m_4 (actual rows=1.00 loops=7)\n                     Index Cond: (\"time\" > (g.\"time\" + '@ 1 day'::interval))\n               ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m_5 (actual rows=1.00 loops=7)\n                     Index Cond: (\"time\" > (g.\"time\" + '@ 1 day'::interval))\n\n-- test runtime exclusion with subquery\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 WHERE m1.time=(SELECT max(time) FROM metrics_timestamptz);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz m1 (actual rows=3.00 loops=1)\n   Chunks excluded during runtime: 4\n   InitPlan 2\n     ->  Result (actual rows=1.00 loops=1)\n           InitPlan 1\n             ->  Limit (actual rows=1.00 loops=1)\n                   ->  Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=1.00 loops=1)\n                         Order: metrics_timestamptz.\"time\" DESC\n                         ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=1.00 loops=1)\n                         ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (never executed)\n                         ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (never executed)\n                         ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk (never executed)\n                         ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk (never executed)\n   ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m1_1 (never executed)\n         Index Cond: (\"time\" = (InitPlan 2).col1)\n   ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m1_2 (never executed)\n         Index Cond: (\"time\" = (InitPlan 2).col1)\n   ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m1_3 (never executed)\n         Index Cond: (\"time\" = (InitPlan 2).col1)\n   ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m1_4 (never executed)\n         Index Cond: (\"time\" = (InitPlan 2).col1)\n   ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m1_5 (actual rows=3.00 loops=1)\n         Index Cond: (\"time\" = (InitPlan 2).col1)\n\n-- test runtime exclusion with correlated subquery\n:PREFIX SELECT m1.time, (SELECT m2.time FROM metrics_timestamptz m2 WHERE m2.time < m1.time ORDER BY m2.time DESC LIMIT 1) FROM metrics_timestamptz m1 WHERE m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Result (actual rows=7776.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1 (actual rows=7776.00 loops=1)\n         Order: m1.\"time\"\n         ->  Index Scan Backward using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m1_1 (actual rows=4032.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m1_2 (actual rows=3744.00 loops=1)\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   SubPlan 1\n     ->  Limit (actual rows=1.00 loops=7776)\n           ->  Custom Scan (ChunkAppend) on metrics_timestamptz m2 (actual rows=1.00 loops=7776)\n                 Order: m2.\"time\" DESC\n                 Chunks excluded during runtime: 3\n                 ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m2_1 (never executed)\n                       Index Cond: (\"time\" < m1.\"time\")\n                 ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m2_2 (never executed)\n                       Index Cond: (\"time\" < m1.\"time\")\n                 ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m2_3 (never executed)\n                       Index Cond: (\"time\" < m1.\"time\")\n                 ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m2_4 (actual rows=1.00 loops=3741)\n                       Index Cond: (\"time\" < m1.\"time\")\n                 ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m2_5 (actual rows=1.00 loops=4035)\n                       Index Cond: (\"time\" < m1.\"time\")\n\n-- test EXISTS\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 WHERE EXISTS(SELECT 1 FROM metrics_timestamptz m2 WHERE m1.time < m2.time) ORDER BY m1.time DESC limit 1000;\n--- QUERY PLAN ---\n Limit (actual rows=1000.00 loops=1)\n   ->  Nested Loop Semi Join (actual rows=1000.00 loops=1)\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1 (actual rows=1003.00 loops=1)\n               Order: m1.\"time\" DESC\n               ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m1_1 (actual rows=1003.00 loops=1)\n               ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m1_2 (never executed)\n               ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m1_3 (never executed)\n               ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m1_4 (never executed)\n               ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m1_5 (never executed)\n         ->  Append (actual rows=1.00 loops=1003)\n               ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m2_1 (actual rows=0.00 loops=1003)\n                     Index Cond: (\"time\" > m1.\"time\")\n               ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m2_2 (actual rows=0.00 loops=1003)\n                     Index Cond: (\"time\" > m1.\"time\")\n               ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m2_3 (actual rows=0.00 loops=1003)\n                     Index Cond: (\"time\" > m1.\"time\")\n               ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m2_4 (actual rows=0.00 loops=1003)\n                     Index Cond: (\"time\" > m1.\"time\")\n               ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m2_5 (actual rows=1.00 loops=1003)\n                     Index Cond: (\"time\" > m1.\"time\")\n\n-- test constraint exclusion for subqueries with append\n-- should include 2 chunks\n:PREFIX SELECT time FROM (SELECT time FROM metrics_timestamptz WHERE time < '2000-01-10'::text::timestamptz ORDER BY time) m;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=7776.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 3\n   ->  Index Scan Backward using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk (actual rows=4032.00 loops=1)\n         Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk (actual rows=3744.00 loops=1)\n         Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n\n-- test constraint exclusion for subqueries with mergeappend\n-- should include 2 chunks\n:PREFIX SELECT device_id, time FROM (SELECT device_id, time FROM metrics_timestamptz WHERE time < '2000-01-10'::text::timestamptz ORDER BY device_id, time) m;\n--- QUERY PLAN ---\n Custom Scan (ConstraintAwareAppend) (actual rows=7776.00 loops=1)\n   Hypertable: metrics_timestamptz\n   Chunks excluded during startup: 3\n   ->  Merge Append (actual rows=7776.00 loops=1)\n         Sort Key: metrics_timestamptz.device_id, metrics_timestamptz.\"time\"\n         ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_17_chunk (actual rows=4032.00 loops=1)\n               Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n         ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_18_chunk (actual rows=3744.00 loops=1)\n               Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n\n-- test LIMIT pushdown\n-- no aggregates/window functions/SRF should pushdown limit\n:PREFIX SELECT FROM metrics_timestamptz ORDER BY time LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=1.00 loops=1)\n         Order: metrics_timestamptz.\"time\"\n         ->  Index Scan Backward using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk (actual rows=1.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk (never executed)\n         ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (never executed)\n         ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (never executed)\n         ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (never executed)\n\n-- aggregates should prevent pushdown\n:PREFIX SELECT count(*) FROM metrics_timestamptz LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Aggregate (actual rows=1.00 loops=1)\n         ->  Append (actual rows=26787.00 loops=1)\n               ->  Seq Scan on _hyper_5_17_chunk (actual rows=4032.00 loops=1)\n               ->  Seq Scan on _hyper_5_18_chunk (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_19_chunk (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n\n:PREFIX SELECT count(*) FROM metrics_space LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Aggregate (actual rows=1.00 loops=1)\n         ->  Append (actual rows=37450.00 loops=1)\n               ->  Seq Scan on _hyper_6_22_chunk (actual rows=5376.00 loops=1)\n               ->  Seq Scan on _hyper_6_23_chunk (actual rows=5376.00 loops=1)\n               ->  Seq Scan on _hyper_6_24_chunk (actual rows=2688.00 loops=1)\n               ->  Seq Scan on _hyper_6_25_chunk (actual rows=8064.00 loops=1)\n               ->  Seq Scan on _hyper_6_26_chunk (actual rows=8064.00 loops=1)\n               ->  Seq Scan on _hyper_6_27_chunk (actual rows=4032.00 loops=1)\n               ->  Seq Scan on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               ->  Seq Scan on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               ->  Seq Scan on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n\n-- HAVING should prevent pushdown\n:PREFIX SELECT 1 FROM metrics_timestamptz HAVING count(*) > 1 LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Aggregate (actual rows=1.00 loops=1)\n         Filter: (count(*) > 1)\n         ->  Append (actual rows=26787.00 loops=1)\n               ->  Seq Scan on _hyper_5_17_chunk (actual rows=4032.00 loops=1)\n               ->  Seq Scan on _hyper_5_18_chunk (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_19_chunk (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n\n:PREFIX SELECT 1 FROM metrics_space HAVING count(*) > 1 LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Aggregate (actual rows=1.00 loops=1)\n         Filter: (count(*) > 1)\n         ->  Append (actual rows=37450.00 loops=1)\n               ->  Seq Scan on _hyper_6_22_chunk (actual rows=5376.00 loops=1)\n               ->  Seq Scan on _hyper_6_23_chunk (actual rows=5376.00 loops=1)\n               ->  Seq Scan on _hyper_6_24_chunk (actual rows=2688.00 loops=1)\n               ->  Seq Scan on _hyper_6_25_chunk (actual rows=8064.00 loops=1)\n               ->  Seq Scan on _hyper_6_26_chunk (actual rows=8064.00 loops=1)\n               ->  Seq Scan on _hyper_6_27_chunk (actual rows=4032.00 loops=1)\n               ->  Seq Scan on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               ->  Seq Scan on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               ->  Seq Scan on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n\n-- DISTINCT should prevent pushdown\nSET enable_hashagg TO false;\n:PREFIX SELECT DISTINCT device_id FROM metrics_timestamptz ORDER BY device_id LIMIT 3;\n--- QUERY PLAN ---\n Limit (actual rows=3.00 loops=1)\n   ->  Unique (actual rows=3.00 loops=1)\n         ->  Merge Append (actual rows=17859.00 loops=1)\n               Sort Key: metrics_timestamptz.device_id\n               ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_17_chunk (actual rows=2689.00 loops=1)\n               ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_18_chunk (actual rows=4033.00 loops=1)\n               ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_19_chunk (actual rows=4033.00 loops=1)\n               ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_20_chunk (actual rows=4033.00 loops=1)\n               ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_21_chunk (actual rows=3075.00 loops=1)\n\n:PREFIX SELECT DISTINCT device_id FROM metrics_space ORDER BY device_id LIMIT 3;\n--- QUERY PLAN ---\n Limit (actual rows=3.00 loops=1)\n   ->  Unique (actual rows=3.00 loops=1)\n         ->  Merge Append (actual rows=7491.00 loops=1)\n               Sort Key: metrics_space.device_id\n               ->  Index Scan using _hyper_6_22_chunk_metrics_space_device_id_time_idx on _hyper_6_22_chunk (actual rows=1345.00 loops=1)\n               ->  Index Scan using _hyper_6_23_chunk_metrics_space_device_id_time_idx on _hyper_6_23_chunk (actual rows=1345.00 loops=1)\n               ->  Index Scan using _hyper_6_24_chunk_metrics_space_device_id_time_idx on _hyper_6_24_chunk (actual rows=1.00 loops=1)\n               ->  Index Scan using _hyper_6_25_chunk_metrics_space_device_id_time_idx on _hyper_6_25_chunk (actual rows=2017.00 loops=1)\n               ->  Index Scan using _hyper_6_26_chunk_metrics_space_device_id_time_idx on _hyper_6_26_chunk (actual rows=2017.00 loops=1)\n               ->  Index Scan using _hyper_6_27_chunk_metrics_space_device_id_time_idx on _hyper_6_27_chunk (actual rows=1.00 loops=1)\n               ->  Index Scan using _hyper_6_28_chunk_metrics_space_device_id_time_idx on _hyper_6_28_chunk (actual rows=386.00 loops=1)\n               ->  Index Scan using _hyper_6_29_chunk_metrics_space_device_id_time_idx on _hyper_6_29_chunk (actual rows=386.00 loops=1)\n               ->  Index Scan using _hyper_6_30_chunk_metrics_space_device_id_time_idx on _hyper_6_30_chunk (actual rows=1.00 loops=1)\n\nRESET enable_hashagg;\n-- JOINs should prevent pushdown\n-- when LIMIT gets pushed to a Sort node it will switch to top-N heapsort\n-- if more tuples then LIMIT are requested this will trigger an error\n-- to trigger this we need a Sort node that is below ChunkAppend\nCREATE TABLE join_limit (time timestamptz, device_id int);\nSELECT table_name FROM create_hypertable('join_limit','time',create_default_indexes:=false);\n table_name \n------------\n join_limit\n\nCREATE INDEX ON join_limit(time,device_id);\nINSERT INTO join_limit\nSELECT time, device_id\nFROM generate_series('2000-01-01'::timestamptz,'2000-01-21','30m') g1(time),\n  generate_series(1,10,1) g2(device_id)\nORDER BY time, device_id;\nVACUUM (ANALYZE) join_limit;\n-- get 2nd chunk oid\nSELECT tableoid AS \"CHUNK_OID\" FROM join_limit WHERE time > '2000-01-07' ORDER BY time LIMIT 1\n\\gset\n--get index name for 2nd chunk\nSELECT indexrelid::regclass AS \"INDEX_NAME\" FROM pg_index WHERE indrelid = :CHUNK_OID\n\\gset\nDROP INDEX :INDEX_NAME;\n:PREFIX SELECT * FROM metrics_timestamptz m1 INNER JOIN join_limit m2 ON m1.time = m2.time AND m1.device_id=m2.device_id WHERE m1.time > '2000-01-07' ORDER BY m1.time, m1.device_id LIMIT 3;\n--- QUERY PLAN ---\n Limit (actual rows=3.00 loops=1)\n   ->  Merge Join (actual rows=3.00 loops=1)\n         Merge Cond: (m2.\"time\" = m1.\"time\")\n         Join Filter: (m2.device_id = m1.device_id)\n         Rows Removed by Join Filter: 4\n         ->  Custom Scan (ChunkAppend) on join_limit m2 (actual rows=3.00 loops=1)\n               Order: m2.\"time\", m2.device_id\n               ->  Sort (actual rows=3.00 loops=1)\n                     Sort Key: m2_1.\"time\", m2_1.device_id\n                     Sort Method: quicksort \n                     ->  Seq Scan on _hyper_8_35_chunk m2_1 (actual rows=2710.00 loops=1)\n                           Filter: (\"time\" > 'Fri Jan 07 00:00:00 2000 PST'::timestamp with time zone)\n                           Rows Removed by Filter: 650\n               ->  Index Scan using _hyper_8_36_chunk_join_limit_time_device_id_idx on _hyper_8_36_chunk m2_2 (never executed)\n               ->  Index Scan using _hyper_8_37_chunk_join_limit_time_device_id_idx on _hyper_8_37_chunk m2_3 (never executed)\n         ->  Materialize (actual rows=22.00 loops=1)\n               ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1 (actual rows=19.00 loops=1)\n                     Order: m1.\"time\"\n                     ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m1_1 (actual rows=19.00 loops=1)\n                           Index Cond: (\"time\" > 'Fri Jan 07 00:00:00 2000 PST'::timestamp with time zone)\n                     ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m1_2 (never executed)\n                     ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m1_3 (never executed)\n                     ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m1_4 (never executed)\n\nDROP TABLE join_limit;\n-- test ChunkAppend projection #2661\n:PREFIX SELECT ts.timestamp, ht.timestamp\nFROM (\n  SELECT generate_series(\n    to_timestamp(FLOOR(EXTRACT (EPOCH FROM '2020-01-01T00:01:00Z'::timestamp) / 300) * 300) AT TIME ZONE 'UTC',\n    '2020-01-01T01:00:00Z',\n    '5 minutes'::interval\n  ) AS timestamp\n) ts\nLEFT JOIN i2661 ht ON\n  (FLOOR(EXTRACT (EPOCH FROM ht.\"timestamp\") / 300) * 300 = EXTRACT (EPOCH FROM ts.timestamp))\n  AND ht.timestamp > '2019-12-30T00:00:00Z'::timestamp\nORDER BY ts.timestamp, ht.timestamp;\n--- QUERY PLAN ---\n Sort (actual rows=33.00 loops=1)\n   Sort Key: ts.\"timestamp\", ht.\"timestamp\"\n   Sort Method: quicksort \n   ->  Merge Left Join (actual rows=33.00 loops=1)\n         Merge Cond: ((EXTRACT(epoch FROM ts.\"timestamp\")) = ((floor((EXTRACT(epoch FROM ht.\"timestamp\") / '300'::numeric)) * '300'::numeric)))\n         ->  Sort (actual rows=13.00 loops=1)\n               Sort Key: (EXTRACT(epoch FROM ts.\"timestamp\"))\n               Sort Method: quicksort \n               ->  Subquery Scan on ts (actual rows=13.00 loops=1)\n                     ->  ProjectSet (actual rows=13.00 loops=1)\n                           ->  Result (actual rows=1.00 loops=1)\n         ->  Sort (actual rows=514.00 loops=1)\n               Sort Key: ((floor((EXTRACT(epoch FROM ht.\"timestamp\") / '300'::numeric)) * '300'::numeric))\n               Sort Method: quicksort \n               ->  Result (actual rows=7201.00 loops=1)\n                     ->  Custom Scan (ChunkAppend) on i2661 ht (actual rows=7201.00 loops=1)\n                           Chunks excluded during startup: 0\n                           ->  Seq Scan on _hyper_7_31_chunk ht_1 (actual rows=1200.00 loops=1)\n                                 Filter: (\"timestamp\" > 'Mon Dec 30 00:00:00 2019'::timestamp without time zone)\n                           ->  Seq Scan on _hyper_7_32_chunk ht_2 (actual rows=5040.00 loops=1)\n                                 Filter: (\"timestamp\" > 'Mon Dec 30 00:00:00 2019'::timestamp without time zone)\n                           ->  Seq Scan on _hyper_7_33_chunk ht_3 (actual rows=961.00 loops=1)\n                                 Filter: (\"timestamp\" > 'Mon Dec 30 00:00:00 2019'::timestamp without time zone)\n\n-- #3030 test chunkappend keeps pathkeys when subpath is append\n-- on PG11 this will not use ChunkAppend but MergeAppend\nSET enable_seqscan TO FALSE;\nCREATE TABLE i3030(time timestamptz NOT NULL, a int, b int);\nSELECT table_name FROM create_hypertable('i3030', 'time', create_default_indexes=>false);\n table_name \n------------\n i3030\n\nCREATE INDEX ON i3030(a,time);\nINSERT INTO i3030 (time,a) SELECT time, a FROM generate_series('2000-01-01'::timestamptz,'2000-01-01 3:00:00'::timestamptz,'1min'::interval) time, generate_series(1,30) a;\nVACUUM (ANALYZE) i3030;\n:PREFIX SELECT * FROM i3030 where time BETWEEN '2000-01-01'::text::timestamptz AND '2000-01-03'::text::timestamptz ORDER BY a,time LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on i3030 (actual rows=1.00 loops=1)\n         Order: i3030.a, i3030.\"time\"\n         Chunks excluded during startup: 0\n         ->  Index Scan using _hyper_9_38_chunk_i3030_a_time_idx on _hyper_9_38_chunk (actual rows=1.00 loops=1)\n               Index Cond: ((\"time\" >= ('2000-01-01'::cstring)::timestamp with time zone) AND (\"time\" <= ('2000-01-03'::cstring)::timestamp with time zone))\n\nDROP TABLE i3030;\nRESET enable_seqscan;\n--parent runtime exclusion tests:\n--optimization works with ANY (array)\n:PREFIX\nSELECT *\nFROM append_test a\nWHERE a.attr @> ANY((SELECT coalesce(array_agg(attr), array[]::jsonb[]) FROM join_test_plain WHERE temp > 100)::jsonb[]);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on append_test a (actual rows=0.00 loops=1)\n   Hypertables excluded during runtime: 1\n   InitPlan 1\n     ->  Aggregate (actual rows=1.00 loops=1)\n           ->  Seq Scan on join_test_plain (actual rows=0.00 loops=1)\n                 Filter: (temp > '100'::double precision)\n                 Rows Removed by Filter: 3\n   ->  Seq Scan on _hyper_1_1_chunk a_1 (never executed)\n         Filter: (attr @> ANY ((InitPlan 1).col1))\n   ->  Seq Scan on _hyper_1_2_chunk a_2 (never executed)\n         Filter: (attr @> ANY ((InitPlan 1).col1))\n   ->  Seq Scan on _hyper_1_3_chunk a_3 (never executed)\n         Filter: (attr @> ANY ((InitPlan 1).col1))\n\n--optimization does not work for ANY subquery (does not force an initplan)\n:PREFIX\nSELECT *\nFROM append_test a\nWHERE a.attr @> ANY((SELECT attr FROM join_test_plain WHERE temp > 100));\n--- QUERY PLAN ---\n Nested Loop Semi Join (actual rows=0.00 loops=1)\n   Join Filter: (a.attr @> join_test_plain.attr)\n   ->  Append (actual rows=5.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk a_1 (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk a_2 (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk a_3 (actual rows=1.00 loops=1)\n   ->  Materialize (actual rows=0.00 loops=5)\n         ->  Seq Scan on join_test_plain (actual rows=0.00 loops=1)\n               Filter: (temp > '100'::double precision)\n               Rows Removed by Filter: 3\n\n--works on any strict operator without ANY\n:PREFIX\nSELECT *\nFROM append_test a\nWHERE a.attr @> (SELECT attr FROM join_test_plain WHERE temp > 100 limit 1);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on append_test a (actual rows=0.00 loops=1)\n   Hypertables excluded during runtime: 1\n   InitPlan 1\n     ->  Limit (actual rows=0.00 loops=1)\n           ->  Seq Scan on join_test_plain (actual rows=0.00 loops=1)\n                 Filter: (temp > '100'::double precision)\n                 Rows Removed by Filter: 3\n   ->  Seq Scan on _hyper_1_1_chunk a_1 (never executed)\n         Filter: (attr @> (InitPlan 1).col1)\n   ->  Seq Scan on _hyper_1_2_chunk a_2 (never executed)\n         Filter: (attr @> (InitPlan 1).col1)\n   ->  Seq Scan on _hyper_1_3_chunk a_3 (never executed)\n         Filter: (attr @> (InitPlan 1).col1)\n\n--optimization works with function calls\nCREATE OR REPLACE FUNCTION select_tag(_min_temp int)\n RETURNS jsonb[]\n LANGUAGE sql\n STABLE PARALLEL SAFE\nAS $function$\n   SELECT coalesce(array_agg(attr), array[]::jsonb[])\n  FROM join_test_plain\n  WHERE temp > _min_temp\n$function$;\n:PREFIX\nSELECT *\nFROM append_test a\nWHERE a.attr @> ANY((SELECT select_tag(100))::jsonb[]);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on append_test a (actual rows=0.00 loops=1)\n   Hypertables excluded during runtime: 1\n   InitPlan 1\n     ->  Result (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_1_1_chunk a_1 (never executed)\n         Filter: (attr @> ANY ((InitPlan 1).col1))\n   ->  Seq Scan on _hyper_1_2_chunk a_2 (never executed)\n         Filter: (attr @> ANY ((InitPlan 1).col1))\n   ->  Seq Scan on _hyper_1_3_chunk a_3 (never executed)\n         Filter: (attr @> ANY ((InitPlan 1).col1))\n\n--optimization does not work when result is null\n:PREFIX\nSELECT *\nFROM append_test a\nWHERE a.attr @> ANY((SELECT array_agg(attr) FROM join_test_plain WHERE temp > 100)::jsonb[]);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on append_test a (actual rows=0.00 loops=1)\n   Hypertables excluded during runtime: 0\n   InitPlan 1\n     ->  Aggregate (actual rows=1.00 loops=1)\n           ->  Seq Scan on join_test_plain (actual rows=0.00 loops=1)\n                 Filter: (temp > '100'::double precision)\n                 Rows Removed by Filter: 3\n   ->  Seq Scan on _hyper_1_1_chunk a_1 (actual rows=0.00 loops=1)\n         Filter: (attr @> ANY ((InitPlan 1).col1))\n         Rows Removed by Filter: 2\n   ->  Seq Scan on _hyper_1_2_chunk a_2 (actual rows=0.00 loops=1)\n         Filter: (attr @> ANY ((InitPlan 1).col1))\n         Rows Removed by Filter: 2\n   ->  Seq Scan on _hyper_1_3_chunk a_3 (actual rows=0.00 loops=1)\n         Filter: (attr @> ANY ((InitPlan 1).col1))\n         Rows Removed by Filter: 1\n\n-- Test that ConstraintAwareAppend properly locks relations in\n-- parallel query mode\nset timescaledb.enable_chunk_append=false;\ncall force_parallel(true);\n:PREFIX\nselect time, avg(temp), colorid from append_test\nwhere time > now_s() - interval '3 months 20 days'\ngroup by time, colorid;\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Gather (actual rows=3.00 loops=1)\n   Workers Planned: 1\n   Workers Launched: 1\n   Single Copy: true\n   ->  HashAggregate (actual rows=3.00 loops=1)\n         Group Key: append_test.\"time\", append_test.colorid\n         ->  Custom Scan (ConstraintAwareAppend) (actual rows=3.00 loops=1)\n               Hypertable: append_test\n               Chunks excluded during startup: 1\n               ->  Append (actual rows=3.00 loops=1)\n                     ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n                           Filter: (\"time\" > (now_s() - '@ 3 mons 20 days'::interval))\n                     ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n                           Filter: (\"time\" > (now_s() - '@ 3 mons 20 days'::interval))\n\nreset timescaledb.enable_chunk_append;\nreset max_parallel_workers_per_gather;\ncall force_parallel(false);\n--generate the results into two different files\n\\set ECHO errors\n--- Unoptimized results\n+++ Optimized results\n@@ -1,6 +1,6 @@\n              setting              | value \n ----------------------------------+-------\n- timescaledb.enable_optimizations | off\n+ timescaledb.enable_optimizations | on\n  timescaledb.enable_chunk_append  | on\n \n--- Unoptimized results\n+++ Optimized results\n@@ -1,7 +1,7 @@\n              setting              | value \n ----------------------------------+-------\n- timescaledb.enable_optimizations | off\n- timescaledb.enable_chunk_append  | on\n+ timescaledb.enable_optimizations | on\n+ timescaledb.enable_chunk_append  | off\n \n  time | temp | colorid | attr \n"
  },
  {
    "path": "test/expected/append-18.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\set TEST_BASE_NAME append\nSELECT format('include/%s_load.sql', :'TEST_BASE_NAME') as \"TEST_LOAD_NAME\",\n       format('include/%s_query.sql', :'TEST_BASE_NAME') as \"TEST_QUERY_NAME\",\n       format('%s/results/%s_results_optimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_OPTIMIZED\",\n       format('%s/results/%s_results_unoptimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_UNOPTIMIZED\"\n\\gset\nSELECT format('\\! diff -u --label \"Unoptimized results\" --label \"Optimized results\" %s %s', :'TEST_RESULTS_UNOPTIMIZED', :'TEST_RESULTS_OPTIMIZED') as \"DIFF_CMD\"\n\\gset\nSET timescaledb.enable_now_constify TO false;\n-- disable memoize node to avoid flaky results\nSET enable_memoize TO 'off';\n-- disable index only scans to avoid some flaky results\nSET enable_indexonlyscan TO FALSE;\n\\set PREFIX 'EXPLAIN (analyze, buffers off, costs off, timing off, summary off)'\n\\ir :TEST_LOAD_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- create a now() function for repeatable testing that always returns\n-- the same timestamp. It needs to be marked STABLE\nCREATE OR REPLACE FUNCTION now_s()\nRETURNS timestamptz LANGUAGE PLPGSQL STABLE PARALLEL SAFE AS\n$BODY$\nBEGIN\n    RAISE NOTICE 'Stable function now_s() called!';\n    RETURN '2017-08-22T10:00:00'::timestamptz;\nEND;\n$BODY$;\nCREATE OR REPLACE FUNCTION now_i()\nRETURNS timestamptz LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nBEGIN\n    RAISE NOTICE 'Immutable function now_i() called!';\n    RETURN '2017-08-22T10:00:00'::timestamptz;\nEND;\n$BODY$;\nCREATE OR REPLACE FUNCTION now_v()\nRETURNS timestamptz LANGUAGE PLPGSQL VOLATILE AS\n$BODY$\nBEGIN\n    RAISE NOTICE 'Volatile function now_v() called!';\n    RETURN '2017-08-22T10:00:00'::timestamptz;\nEND;\n$BODY$;\nCREATE OR REPLACE PROCEDURE force_parallel(on_or_off bool)\nLANGUAGE PLPGSQL AS\n$$\nBEGIN\n    IF current_setting('server_version_num')::int < 160000 THEN\n        IF on_or_off THEN\n            set force_parallel_mode = 'on';\n        ELSE\n            set force_parallel_mode = 'off';\n        END IF;\n    ELSE\n        IF on_or_off THEN\n            set debug_parallel_query = 'on';\n        ELSE\n            set debug_parallel_query = 'off';\n        END IF;\n    END IF;\nEND;\n$$;\nCREATE TABLE append_test(time timestamptz, temp float, colorid integer, attr jsonb);\nSELECT create_hypertable('append_test', 'time', chunk_time_interval => 2628000000000);\n    create_hypertable     \n--------------------------\n (1,public,append_test,t)\n\n-- create three chunks\nINSERT INTO append_test VALUES ('2017-03-22T09:18:22', 23.5, 1, '{\"a\": 1, \"b\": 2}'),\n                               ('2017-03-22T09:18:23', 21.5, 1, '{\"a\": 1, \"b\": 2}'),\n                               ('2017-05-22T09:18:22', 36.2, 2, '{\"c\": 3, \"b\": 2}'),\n                               ('2017-05-22T09:18:23', 15.2, 2, '{\"c\": 3}'),\n                               ('2017-08-22T09:18:22', 34.1, 3, '{\"c\": 4}');\nVACUUM (ANALYZE) append_test;\n-- Create another hypertable to join with\nCREATE TABLE join_test(time timestamptz, temp float, colorid integer);\nSELECT create_hypertable('join_test', 'time', chunk_time_interval => 2628000000000);\n   create_hypertable    \n------------------------\n (2,public,join_test,t)\n\nINSERT INTO join_test VALUES ('2017-01-22T09:18:22', 15.2, 1),\n                             ('2017-02-22T09:18:22', 24.5, 2),\n                             ('2017-08-22T09:18:22', 23.1, 3);\nVACUUM (ANALYZE) join_test;\n-- Create another table to join with which is not a hypertable.\nCREATE TABLE join_test_plain(time timestamptz, temp float, colorid integer, attr jsonb);\nINSERT INTO join_test_plain VALUES ('2017-01-22T09:18:22', 15.2, 1, '{\"a\": 1}'),\n                             ('2017-02-22T09:18:22', 24.5, 2, '{\"b\": 2}'),\n                             ('2017-08-22T09:18:22', 23.1, 3, '{\"c\": 3}');\nVACUUM (ANALYZE) join_test_plain;\n-- create hypertable with DATE time dimension\nCREATE TABLE metrics_date(time DATE NOT NULL);\nSELECT create_hypertable('metrics_date','time');\n     create_hypertable     \n---------------------------\n (3,public,metrics_date,t)\n\nINSERT INTO metrics_date SELECT generate_series('2000-01-01'::date, '2000-02-01'::date, '5m'::interval);\nVACUUM (ANALYZE) metrics_date;\n-- create hypertable with TIMESTAMP time dimension\nCREATE TABLE metrics_timestamp(time TIMESTAMP NOT NULL);\nSELECT create_hypertable('metrics_timestamp','time');\npsql:include/append_load.sql:91: WARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n       create_hypertable        \n--------------------------------\n (4,public,metrics_timestamp,t)\n\nINSERT INTO metrics_timestamp SELECT generate_series('2000-01-01'::date, '2000-02-01'::date, '5m'::interval);\nVACUUM (ANALYZE) metrics_timestamp;\n-- create hypertable with TIMESTAMPTZ time dimension\nCREATE TABLE metrics_timestamptz(time TIMESTAMPTZ NOT NULL, device_id INT NOT NULL);\nCREATE INDEX ON metrics_timestamptz(device_id,time);\nSELECT create_hypertable('metrics_timestamptz','time');\n        create_hypertable         \n----------------------------------\n (5,public,metrics_timestamptz,t)\n\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::date, '2000-02-01'::date, '5m'::interval), 1;\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::date, '2000-02-01'::date, '5m'::interval), 2;\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::date, '2000-02-01'::date, '5m'::interval), 3;\nVACUUM (ANALYZE) metrics_timestamptz;\n-- create space partitioned hypertable\nCREATE TABLE metrics_space(time timestamptz NOT NULL, device_id int NOT NULL, v1 float, v2 float, v3 text);\nSELECT create_hypertable('metrics_space','time','device_id',3);\n     create_hypertable      \n----------------------------\n (6,public,metrics_space,t)\n\nINSERT INTO metrics_space\nSELECT time, device_id, device_id + 0.25, device_id + 0.75, device_id\nFROM generate_series('2000-01-01'::timestamptz, '2000-01-14'::timestamptz, '5m'::interval) g1(time),\n  generate_series(1,10,1) g2(device_id)\nORDER BY time, device_id;\nVACUUM (ANALYZE) metrics_space;\n-- test ChunkAppend projection #2661\nCREATE TABLE i2661 (\n  machine_id int4 NOT NULL,\n  \"name\" varchar(255) NOT NULL,\n  \"timestamp\" timestamptz NOT NULL,\n  \"first\" float4 NULL\n);\nSELECT create_hypertable('i2661', 'timestamp');\npsql:include/append_load.sql:123: WARNING:  column type \"character varying\" used for \"name\" does not follow best practices\n create_hypertable  \n--------------------\n (7,public,i2661,t)\n\nINSERT INTO i2661 SELECT 1, 'speed', generate_series('2019-12-31 00:00:00', '2020-01-10 00:00:00', '2m'::interval), 0;\nVACUUM (ANALYZE) i2661;\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- canary for results diff\n-- this should be the only output of the results diff\nSELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations'),('timescaledb.enable_chunk_append')) v(setting);\n             setting              | value \n----------------------------------+-------\n timescaledb.enable_optimizations | on\n timescaledb.enable_chunk_append  | on\n\n-- query should exclude all chunks with optimization on\n:PREFIX\nSELECT * FROM append_test WHERE time > now_s() + '1 month'\nORDER BY time DESC;\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:12: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Sort (actual rows=0.00 loops=1)\n   Sort Key: append_test.\"time\" DESC\n   Sort Method: quicksort \n   ->  Custom Scan (ChunkAppend) on append_test (actual rows=0.00 loops=1)\n         Chunks excluded during startup: 3\n\n--query should exclude all chunks and be a MergeAppend\n:PREFIX\nSELECT * FROM append_test WHERE time > now_s() + '1 month'\nORDER BY time DESC limit 1;\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:17: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Limit (actual rows=0.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on append_test (actual rows=0.00 loops=1)\n         Order: append_test.\"time\" DESC\n         Chunks excluded during startup: 3\n\n-- when optimized, the plan should be a constraint-aware append and\n-- cover only one chunk. It should be a backward index scan due to\n-- descending index on time. Should also skip the main table, since it\n-- cannot hold tuples\n:PREFIX\nSELECT * FROM append_test WHERE time > now_s() - interval '2 months';\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:24: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on append_test (actual rows=1.00 loops=1)\n   Chunks excluded during startup: 2\n   ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         Filter: (\"time\" > (now_s() - '@ 2 mons'::interval))\n\n-- adding ORDER BY and LIMIT should turn the plan into an optimized\n-- ordered append plan\n:PREFIX\nSELECT * FROM append_test WHERE time > now_s() - interval '2 months'\nORDER BY time LIMIT 3;\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:30: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Sort (actual rows=1.00 loops=1)\n         Sort Key: append_test.\"time\"\n         Sort Method: quicksort \n         ->  Custom Scan (ChunkAppend) on append_test (actual rows=1.00 loops=1)\n               Chunks excluded during startup: 2\n               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n                     Filter: (\"time\" > (now_s() - '@ 2 mons'::interval))\n\n-- no optimized plan for queries with restrictions that can be\n-- constified at planning time. Regular planning-time constraint\n-- exclusion should occur.\n:PREFIX\nSELECT * FROM append_test WHERE time > now_i() - interval '2 months'\nORDER BY time;\npsql:include/append_query.sql:37: NOTICE:  Immutable function now_i() called!\n--- QUERY PLAN ---\n Sort (actual rows=1.00 loops=1)\n   Sort Key: append_test.\"time\"\n   Sort Method: quicksort \n   ->  Custom Scan (ChunkAppend) on append_test (actual rows=1.00 loops=1)\n         Chunks excluded during startup: 2\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               Filter: (\"time\" > ('Tue Aug 22 10:00:00 2017 PDT'::timestamp with time zone - '@ 2 mons'::interval))\n\n-- currently, we cannot distinguish between stable and volatile\n-- functions as far as applying our modified plan. However, volatile\n-- function should not be pre-evaluated to constants, so no chunk\n-- exclusion should occur.\n:PREFIX\nSELECT * FROM append_test WHERE time > now_v() - interval '2 months'\nORDER BY time;\npsql:include/append_query.sql:45: NOTICE:  Volatile function now_v() called!\npsql:include/append_query.sql:45: NOTICE:  Volatile function now_v() called!\npsql:include/append_query.sql:45: NOTICE:  Volatile function now_v() called!\npsql:include/append_query.sql:45: NOTICE:  Volatile function now_v() called!\npsql:include/append_query.sql:45: NOTICE:  Volatile function now_v() called!\n--- QUERY PLAN ---\n Sort (actual rows=1.00 loops=1)\n   Sort Key: append_test.\"time\"\n   Sort Method: quicksort \n   ->  Custom Scan (ChunkAppend) on append_test (actual rows=1.00 loops=1)\n         Chunks excluded during startup: 0\n         ->  Seq Scan on _hyper_1_1_chunk (actual rows=0.00 loops=1)\n               Filter: (\"time\" > (now_v() - '@ 2 mons'::interval))\n               Rows Removed by Filter: 2\n         ->  Seq Scan on _hyper_1_2_chunk (actual rows=0.00 loops=1)\n               Filter: (\"time\" > (now_v() - '@ 2 mons'::interval))\n               Rows Removed by Filter: 2\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               Filter: (\"time\" > (now_v() - '@ 2 mons'::interval))\n\n-- prepared statement output should be the same regardless of\n-- optimizations\nPREPARE query_opt AS\nSELECT * FROM append_test WHERE time > now_s() - interval '2 months'\nORDER BY time;\n:PREFIX EXECUTE query_opt;\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:53: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Sort (actual rows=1.00 loops=1)\n   Sort Key: append_test.\"time\"\n   Sort Method: quicksort \n   ->  Custom Scan (ChunkAppend) on append_test (actual rows=1.00 loops=1)\n         Chunks excluded during startup: 2\n         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n               Filter: (\"time\" > (now_s() - '@ 2 mons'::interval))\n\nDEALLOCATE query_opt;\n-- aggregates should produce same output\n:PREFIX\nSELECT date_trunc('year', time) t, avg(temp) FROM append_test\nWHERE time > now_s() - interval '4 months'\nGROUP BY t\nORDER BY t DESC;\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:62: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n GroupAggregate (actual rows=1.00 loops=1)\n   Group Key: (date_trunc('year'::text, append_test.\"time\"))\n   ->  Sort (actual rows=3.00 loops=1)\n         Sort Key: (date_trunc('year'::text, append_test.\"time\")) DESC\n         Sort Method: quicksort \n         ->  Result (actual rows=3.00 loops=1)\n               ->  Custom Scan (ChunkAppend) on append_test (actual rows=3.00 loops=1)\n                     Chunks excluded during startup: 1\n                     ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n                           Filter: (\"time\" > (now_s() - '@ 4 mons'::interval))\n                     ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n                           Filter: (\"time\" > (now_s() - '@ 4 mons'::interval))\n\n-- querying outside the time range should return nothing. This tests\n-- that ConstraintAwareAppend can handle the case when an Append node\n-- is turned into a Result node due to no children\n:PREFIX\nSELECT date_trunc('year', time) t, avg(temp)\nFROM append_test\nWHERE time < '2016-03-22'\nAND date_part('dow', time) between 1 and 5\nGROUP BY t\nORDER BY t DESC;\n--- QUERY PLAN ---\n GroupAggregate (actual rows=0.00 loops=1)\n   Group Key: (date_trunc('year'::text, \"time\"))\n   ->  Sort (actual rows=0.00 loops=1)\n         Sort Key: (date_trunc('year'::text, \"time\")) DESC\n         Sort Method: quicksort \n         ->  Result (actual rows=0.00 loops=1)\n               One-Time Filter: false\n\n-- a parameterized query can safely constify params, so won't be\n-- optimized by constraint-aware append since regular constraint\n-- exclusion works just fine\nPREPARE query_param AS\nSELECT * FROM append_test WHERE time > $1 ORDER BY time;\n:PREFIX\nEXECUTE query_param(now_s() - interval '2 months');\npsql:include/append_query.sql:82: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Sort (actual rows=1.00 loops=1)\n   Sort Key: _hyper_1_3_chunk.\"time\"\n   Sort Method: quicksort \n   ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n\nDEALLOCATE query_param;\n--test with cte\n:PREFIX\nWITH data AS (\n    SELECT time_bucket(INTERVAL '30 day', TIME) AS btime, AVG(temp) AS VALUE\n    FROM append_test\n    WHERE\n        TIME > now_s() - INTERVAL '400 day'\n    AND colorid > 0\n    GROUP BY btime\n),\nperiod AS (\n    SELECT time_bucket(INTERVAL '30 day', TIME) AS btime\n      FROM  GENERATE_SERIES('2017-03-22T01:01:01', '2017-08-23T01:01:01', INTERVAL '30 day') TIME\n  )\nSELECT period.btime, VALUE\n    FROM period\n    LEFT JOIN DATA USING (btime)\n    ORDER BY period.btime;\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:102: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Sort (actual rows=6.00 loops=1)\n   Sort Key: (time_bucket('@ 30 days'::interval, \"time\".\"time\"))\n   Sort Method: quicksort \n   ->  Hash Left Join (actual rows=6.00 loops=1)\n         Hash Cond: (time_bucket('@ 30 days'::interval, \"time\".\"time\") = data.btime)\n         ->  Function Scan on generate_series \"time\" (actual rows=6.00 loops=1)\n         ->  Hash (actual rows=3.00 loops=1)\n               ->  Subquery Scan on data (actual rows=3.00 loops=1)\n                     ->  HashAggregate (actual rows=3.00 loops=1)\n                           Group Key: time_bucket('@ 30 days'::interval, append_test.\"time\")\n                           ->  Result (actual rows=5.00 loops=1)\n                                 ->  Custom Scan (ChunkAppend) on append_test (actual rows=5.00 loops=1)\n                                       Chunks excluded during startup: 0\n                                       ->  Seq Scan on _hyper_1_1_chunk (actual rows=2.00 loops=1)\n                                             Filter: ((colorid > 0) AND (\"time\" > (now_s() - '@ 400 days'::interval)))\n                                       ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n                                             Filter: ((colorid > 0) AND (\"time\" > (now_s() - '@ 400 days'::interval)))\n                                       ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n                                             Filter: ((colorid > 0) AND (\"time\" > (now_s() - '@ 400 days'::interval)))\n\nWITH data AS (\n    SELECT time_bucket(INTERVAL '30 day', TIME) AS btime, AVG(temp) AS VALUE\n    FROM append_test\n    WHERE\n        TIME > now_s() - INTERVAL '400 day'\n    AND colorid > 0\n    GROUP BY btime\n),\nperiod AS (\n    SELECT time_bucket(INTERVAL '30 day', TIME) AS btime\n      FROM  GENERATE_SERIES('2017-03-22T01:01:01', '2017-08-23T01:01:01', INTERVAL '30 day') TIME\n  )\nSELECT period.btime, VALUE\n    FROM period\n    LEFT JOIN DATA USING (btime)\n    ORDER BY period.btime;\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:119: NOTICE:  Stable function now_s() called!\n            btime             | value \n------------------------------+-------\n Fri Mar 03 16:00:00 2017 PST |  22.5\n Sun Apr 02 17:00:00 2017 PDT |      \n Tue May 02 17:00:00 2017 PDT |  25.7\n Thu Jun 01 17:00:00 2017 PDT |      \n Sat Jul 01 17:00:00 2017 PDT |      \n Mon Jul 31 17:00:00 2017 PDT |  34.1\n\n-- force nested loop join with no materialization. This tests that the\n-- inner ConstraintAwareScan supports resetting its scan for every\n-- iteration of the outer relation loop\nset enable_hashjoin = 'off';\nset enable_mergejoin = 'off';\nset enable_material = 'off';\n:PREFIX\nSELECT * FROM append_test a INNER JOIN join_test j ON (a.colorid = j.colorid)\nWHERE a.time > now_s() - interval '3 hours' AND j.time > now_s() - interval '3 hours';\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:130: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Nested Loop (actual rows=1.00 loops=1)\n   Join Filter: (a.colorid = j.colorid)\n   ->  Custom Scan (ChunkAppend) on append_test a (actual rows=1.00 loops=1)\n         Chunks excluded during startup: 2\n         ->  Seq Scan on _hyper_1_3_chunk a_1 (actual rows=1.00 loops=1)\n               Filter: (\"time\" > (now_s() - '@ 3 hours'::interval))\n   ->  Custom Scan (ChunkAppend) on join_test j (actual rows=1.00 loops=1)\n         Chunks excluded during startup: 2\n         ->  Seq Scan on _hyper_2_6_chunk j_1 (actual rows=1.00 loops=1)\n               Filter: (\"time\" > (now_s() - '@ 3 hours'::interval))\n\nreset enable_hashjoin;\nreset enable_mergejoin;\nreset enable_material;\n-- test constraint_exclusion with date time dimension and DATE/TIMESTAMP/TIMESTAMPTZ constraints\n-- the queries should all have 3 chunks\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=4609.00 loops=1)\n   Order: metrics_date.\"time\"\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_3_11_chunk_metrics_date_time_idx on _hyper_3_11_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=4609.00 loops=1)\n   Order: metrics_date.\"time\"\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_3_11_chunk_metrics_date_time_idx on _hyper_3_11_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=4609.00 loops=1)\n   Order: metrics_date.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=2016.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_3_11_chunk_metrics_date_time_idx on _hyper_3_11_chunk (actual rows=1441.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n\n-- test Const OP Var\n-- the queries should all have 3 chunks\n:PREFIX SELECT * FROM metrics_date WHERE '2000-01-15'::date < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=4609.00 loops=1)\n   Order: metrics_date.\"time\"\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_3_11_chunk_metrics_date_time_idx on _hyper_3_11_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_date WHERE '2000-01-15'::timestamp < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=4609.00 loops=1)\n   Order: metrics_date.\"time\"\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_3_11_chunk_metrics_date_time_idx on _hyper_3_11_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_date WHERE '2000-01-15'::timestamptz < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=4609.00 loops=1)\n   Order: metrics_date.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=2016.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_3_11_chunk_metrics_date_time_idx on _hyper_3_11_chunk (actual rows=1441.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n\n-- test 2 constraints\n-- the queries should all have 2 chunks\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::date AND time < '2000-01-21'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=1440.00 loops=1)\n   Order: metrics_date.\"time\"\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: ((\"time\" > '01-15-2000'::date) AND (\"time\" < '01-21-2000'::date))\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=288.00 loops=1)\n         Index Cond: ((\"time\" > '01-15-2000'::date) AND (\"time\" < '01-21-2000'::date))\n\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::timestamp AND time < '2000-01-21'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=1440.00 loops=1)\n   Order: metrics_date.\"time\"\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000'::timestamp without time zone))\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=288.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000'::timestamp without time zone))\n\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::timestamptz AND time < '2000-01-21'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=1440.00 loops=1)\n   Order: metrics_date.\"time\"\n   Chunks excluded during startup: 3\n   ->  Index Scan Backward using _hyper_3_9_chunk_metrics_date_time_idx on _hyper_3_9_chunk (actual rows=1152.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Scan Backward using _hyper_3_10_chunk_metrics_date_time_idx on _hyper_3_10_chunk (actual rows=288.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000 PST'::timestamp with time zone))\n\n-- test constraint_exclusion with timestamp time dimension and DATE/TIMESTAMP/TIMESTAMPTZ constraints\n-- the queries should all have 3 chunks\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=4896.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_4_16_chunk_metrics_timestamp_time_idx on _hyper_4_16_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=4896.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_4_16_chunk_metrics_timestamp_time_idx on _hyper_4_16_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=4896.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=2016.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_4_16_chunk_metrics_timestamp_time_idx on _hyper_4_16_chunk (actual rows=1441.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n\n-- test Const OP Var\n-- the queries should all have 3 chunks\n:PREFIX SELECT * FROM metrics_timestamp WHERE '2000-01-15'::date < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=4896.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_4_16_chunk_metrics_timestamp_time_idx on _hyper_4_16_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE '2000-01-15'::timestamp < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=4896.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=2016.00 loops=1)\n   ->  Index Scan Backward using _hyper_4_16_chunk_metrics_timestamp_time_idx on _hyper_4_16_chunk (actual rows=1441.00 loops=1)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE '2000-01-15'::timestamptz < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=4896.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=2016.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_4_16_chunk_metrics_timestamp_time_idx on _hyper_4_16_chunk (actual rows=1441.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n\n-- test 2 constraints\n-- the queries should all have 2 chunks\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::date AND time < '2000-01-21'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=1727.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: ((\"time\" > '01-15-2000'::date) AND (\"time\" < '01-21-2000'::date))\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=288.00 loops=1)\n         Index Cond: ((\"time\" > '01-15-2000'::date) AND (\"time\" < '01-21-2000'::date))\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::timestamp AND time < '2000-01-21'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=1727.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000'::timestamp without time zone))\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=288.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000'::timestamp without time zone))\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::timestamptz AND time < '2000-01-21'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=1727.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 3\n   ->  Index Scan Backward using _hyper_4_14_chunk_metrics_timestamp_time_idx on _hyper_4_14_chunk (actual rows=1439.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Scan Backward using _hyper_4_15_chunk_metrics_timestamp_time_idx on _hyper_4_15_chunk (actual rows=288.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000 PST'::timestamp with time zone))\n\n-- test constraint_exclusion with timestamptz time dimension and DATE/TIMESTAMP/TIMESTAMPTZ constraints\n-- the queries should all have 3 chunks\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=14688.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=14688.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=14688.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n   ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n\n-- test Const OP Var\n-- the queries should all have 3 chunks\n:PREFIX SELECT time FROM metrics_timestamptz WHERE '2000-01-15'::date < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=14688.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n   ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n         Index Cond: (\"time\" > '01-15-2000'::date)\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE '2000-01-15'::timestamp < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=14688.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 2\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE '2000-01-15'::timestamptz < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=14688.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: (\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n   ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n\n-- test 2 constraints\n-- the queries should all have 2 chunks\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::date AND time < '2000-01-21'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=5181.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 3\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: ((\"time\" > '01-15-2000'::date) AND (\"time\" < '01-21-2000'::date))\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=1152.00 loops=1)\n         Index Cond: ((\"time\" > '01-15-2000'::date) AND (\"time\" < '01-21-2000'::date))\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::timestamp AND time < '2000-01-21'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=5181.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 3\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000'::timestamp without time zone))\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=1152.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000'::timestamp without time zone))\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::timestamptz AND time < '2000-01-21'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=5181.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (actual rows=4029.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (actual rows=1152.00 loops=1)\n         Index Cond: ((\"time\" > 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 21 00:00:00 2000 PST'::timestamp with time zone))\n\n-- test constraint_exclusion with space partitioning and DATE/TIMESTAMP/TIMESTAMPTZ constraints\n-- exclusion for constraints with non-matching datatypes not working for space partitioning atm\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n-- test Const OP Var\n-- exclusion for constraints with non-matching datatypes not working for space partitioning atm\n:PREFIX SELECT time FROM metrics_space WHERE '2000-01-10'::date < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: (\"time\" > '01-10-2000'::date)\n\n:PREFIX SELECT time FROM metrics_space WHERE '2000-01-10'::timestamp < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT time FROM metrics_space WHERE '2000-01-10'::timestamptz < time ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n-- test 2 constraints\n-- exclusion for constraints with non-matching datatypes not working for space partitioning atm\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::date AND time < '2000-01-15'::date ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: ((\"time\" > '01-10-2000'::date) AND (\"time\" < '01-15-2000'::date))\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamp AND time < '2000-01-15'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000'::timestamp without time zone))\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz AND time < '2000-01-15'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=11520.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=7670.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=3068.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=3068.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=1534.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Merge Append (actual rows=3850.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n               Index Cond: ((\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Sat Jan 15 00:00:00 2000 PST'::timestamp with time zone))\n\n-- test filtering on space partition\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz AND device_id = 1 ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=1152.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_device_id_time_idx on _hyper_6_25_chunk (actual rows=767.00 loops=1)\n         Index Cond: ((device_id = 1) AND (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_device_id_time_idx on _hyper_6_28_chunk (actual rows=385.00 loops=1)\n         Index Cond: ((device_id = 1) AND (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz AND device_id IN (1,2) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=2304.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=1534.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=767.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (device_id = ANY ('{1,2}'::integer[]))\n               Rows Removed by Filter: 2301\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=767.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (device_id = ANY ('{1,2}'::integer[]))\n               Rows Removed by Filter: 2301\n   ->  Merge Append (actual rows=770.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=385.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (device_id = ANY ('{1,2}'::integer[]))\n               Rows Removed by Filter: 1155\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=385.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (device_id = ANY ('{1,2}'::integer[]))\n               Rows Removed by Filter: 1155\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz AND device_id IN (VALUES(1)) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=1152.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_device_id_time_idx on _hyper_6_25_chunk (actual rows=767.00 loops=1)\n         Index Cond: ((device_id = 1) AND (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_device_id_time_idx on _hyper_6_28_chunk (actual rows=385.00 loops=1)\n         Index Cond: ((device_id = 1) AND (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz AND v3 IN (VALUES('1')) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=1152.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=767.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=767.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (v3 = '1'::text)\n               Rows Removed by Filter: 2301\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (v3 = '1'::text)\n               Rows Removed by Filter: 3068\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (v3 = '1'::text)\n               Rows Removed by Filter: 1534\n   ->  Merge Append (actual rows=385.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=385.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (v3 = '1'::text)\n               Rows Removed by Filter: 1155\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (v3 = '1'::text)\n               Rows Removed by Filter: 1540\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Filter: (v3 = '1'::text)\n               Rows Removed by Filter: 770\n\n:PREFIX SELECT * FROM metrics_space\nWHERE time = (VALUES ('2019-12-24' at time zone 'UTC'))\n  AND v3 NOT IN (VALUES ('1'));\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=0.00 loops=1)\n   Chunks excluded during startup: 0\n   Chunks excluded during runtime: 9\n   InitPlan 1\n     ->  Result (actual rows=1.00 loops=1)\n   ->  Index Scan using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (never executed)\n         Index Cond: (\"time\" = (InitPlan 1).col1)\n         Filter: (NOT (ANY (v3 = (hashed SubPlan 2).col1)))\n         SubPlan 2\n           ->  Result (never executed)\n   ->  Index Scan using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (never executed)\n         Index Cond: (\"time\" = (InitPlan 1).col1)\n         Filter: (NOT (ANY (v3 = (hashed SubPlan 2).col1)))\n   ->  Index Scan using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (never executed)\n         Index Cond: (\"time\" = (InitPlan 1).col1)\n         Filter: (NOT (ANY (v3 = (hashed SubPlan 2).col1)))\n   ->  Index Scan using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (never executed)\n         Index Cond: (\"time\" = (InitPlan 1).col1)\n         Filter: (NOT (ANY (v3 = (hashed SubPlan 2).col1)))\n   ->  Index Scan using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (never executed)\n         Index Cond: (\"time\" = (InitPlan 1).col1)\n         Filter: (NOT (ANY (v3 = (hashed SubPlan 2).col1)))\n   ->  Index Scan using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (never executed)\n         Index Cond: (\"time\" = (InitPlan 1).col1)\n         Filter: (NOT (ANY (v3 = (hashed SubPlan 2).col1)))\n   ->  Index Scan using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (never executed)\n         Index Cond: (\"time\" = (InitPlan 1).col1)\n         Filter: (NOT (ANY (v3 = (hashed SubPlan 2).col1)))\n   ->  Index Scan using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (never executed)\n         Index Cond: (\"time\" = (InitPlan 1).col1)\n         Filter: (NOT (ANY (v3 = (hashed SubPlan 2).col1)))\n   ->  Index Scan using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (never executed)\n         Index Cond: (\"time\" = (InitPlan 1).col1)\n         Filter: (NOT (ANY (v3 = (hashed SubPlan 2).col1)))\n\n-- test CURRENT_DATE\n-- should be 0 chunks\n:PREFIX SELECT time FROM metrics_date WHERE time > CURRENT_DATE ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=0.00 loops=1)\n   Order: metrics_date.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_timestamp WHERE time > CURRENT_DATE ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=0.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > CURRENT_DATE ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=0.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_space WHERE time > CURRENT_DATE ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=0.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_DATE)\n\n-- test CURRENT_TIMESTAMP\n-- should be 0 chunks\n:PREFIX SELECT time FROM metrics_date WHERE time > CURRENT_TIMESTAMP ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=0.00 loops=1)\n   Order: metrics_date.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_timestamp WHERE time > CURRENT_TIMESTAMP ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=0.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > CURRENT_TIMESTAMP ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=0.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_space WHERE time > CURRENT_TIMESTAMP ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=0.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > CURRENT_TIMESTAMP)\n\n-- test now()\n-- should be 0 chunks\n:PREFIX SELECT time FROM metrics_date WHERE time > now() ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date (actual rows=0.00 loops=1)\n   Order: metrics_date.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_timestamp WHERE time > now() ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp (actual rows=0.00 loops=1)\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > now() ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=0.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX SELECT time FROM metrics_space WHERE time > now() ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_space (actual rows=0.00 loops=1)\n   Order: metrics_space.\"time\"\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_22_chunk_metrics_space_time_idx on _hyper_6_22_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n         ->  Index Scan Backward using _hyper_6_23_chunk_metrics_space_time_idx on _hyper_6_23_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n         ->  Index Scan Backward using _hyper_6_24_chunk_metrics_space_time_idx on _hyper_6_24_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_25_chunk_metrics_space_time_idx on _hyper_6_25_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n         ->  Index Scan Backward using _hyper_6_26_chunk_metrics_space_time_idx on _hyper_6_26_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n         ->  Index Scan Backward using _hyper_6_27_chunk_metrics_space_time_idx on _hyper_6_27_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n   ->  Merge Append (actual rows=0.00 loops=1)\n         Sort Key: metrics_space.\"time\"\n         ->  Index Scan Backward using _hyper_6_28_chunk_metrics_space_time_idx on _hyper_6_28_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n         ->  Index Scan Backward using _hyper_6_29_chunk_metrics_space_time_idx on _hyper_6_29_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n         ->  Index Scan Backward using _hyper_6_30_chunk_metrics_space_time_idx on _hyper_6_30_chunk (actual rows=0.00 loops=1)\n               Index Cond: (\"time\" > now())\n\n-- query with tablesample and planner exclusion\n:PREFIX\nSELECT * FROM metrics_date TABLESAMPLE BERNOULLI(5) REPEATABLE(0)\nWHERE time > '2000-01-15'\nORDER BY time DESC;\n--- QUERY PLAN ---\n Sort (actual rows=217.00 loops=1)\n   Sort Key: metrics_date.\"time\" DESC\n   Sort Method: quicksort \n   ->  Append (actual rows=217.00 loops=1)\n         ->  Sample Scan on _hyper_3_11_chunk (actual rows=72.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n         ->  Sample Scan on _hyper_3_10_chunk (actual rows=94.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n         ->  Sample Scan on _hyper_3_9_chunk (actual rows=51.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > '01-15-2000'::date)\n               Rows Removed by Filter: 43\n\n-- query with tablesample and startup exclusion\n:PREFIX\nSELECT * FROM metrics_date TABLESAMPLE BERNOULLI(5) REPEATABLE(0)\nWHERE time > '2000-01-15'::text::date\nORDER BY time DESC;\n--- QUERY PLAN ---\n Sort (actual rows=217.00 loops=1)\n   Sort Key: metrics_date.\"time\" DESC\n   Sort Method: quicksort \n   ->  Custom Scan (ChunkAppend) on metrics_date (actual rows=217.00 loops=1)\n         Chunks excluded during startup: 2\n         ->  Sample Scan on _hyper_3_11_chunk (actual rows=72.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > ('2000-01-15'::cstring)::date)\n         ->  Sample Scan on _hyper_3_10_chunk (actual rows=94.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > ('2000-01-15'::cstring)::date)\n         ->  Sample Scan on _hyper_3_9_chunk (actual rows=51.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > ('2000-01-15'::cstring)::date)\n               Rows Removed by Filter: 43\n\n-- query with tablesample, space partitioning and planner exclusion\n:PREFIX\nSELECT * FROM metrics_space TABLESAMPLE BERNOULLI(5) REPEATABLE(0)\nWHERE time > '2000-01-10'::timestamptz\nORDER BY time DESC, device_id;\n--- QUERY PLAN ---\n Sort (actual rows=522.00 loops=1)\n   Sort Key: metrics_space.\"time\" DESC, metrics_space.device_id\n   Sort Method: quicksort \n   ->  Append (actual rows=522.00 loops=1)\n         ->  Sample Scan on _hyper_6_30_chunk (actual rows=35.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Sample Scan on _hyper_6_29_chunk (actual rows=61.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Sample Scan on _hyper_6_28_chunk (actual rows=61.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Sample Scan on _hyper_6_27_chunk (actual rows=65.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Rows Removed by Filter: 113\n         ->  Sample Scan on _hyper_6_26_chunk (actual rows=150.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Rows Removed by Filter: 218\n         ->  Sample Scan on _hyper_6_25_chunk (actual rows=150.00 loops=1)\n               Sampling: bernoulli ('5'::real) REPEATABLE ('0'::double precision)\n               Filter: (\"time\" > 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n               Rows Removed by Filter: 218\n\n-- test runtime exclusion\n-- test runtime exclusion with LATERAL and 2 hypertables\n:PREFIX SELECT m1.time, m2.time FROM metrics_timestamptz m1 LEFT JOIN LATERAL(SELECT time FROM metrics_timestamptz m2 WHERE m1.time = m2.time LIMIT 1) m2 ON true ORDER BY m1.time;\n--- QUERY PLAN ---\n Nested Loop Left Join (actual rows=26787.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1 (actual rows=26787.00 loops=1)\n         Order: m1.\"time\"\n         ->  Index Scan Backward using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m1_1 (actual rows=4032.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m1_2 (actual rows=6048.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m1_3 (actual rows=6048.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m1_4 (actual rows=6048.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m1_5 (actual rows=4611.00 loops=1)\n   ->  Limit (actual rows=1.00 loops=26787)\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m2 (actual rows=1.00 loops=26787)\n               Chunks excluded during runtime: 4\n               ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m2_1 (actual rows=1.00 loops=4032)\n                     Index Cond: (\"time\" = m1.\"time\")\n               ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m2_2 (actual rows=1.00 loops=6048)\n                     Index Cond: (\"time\" = m1.\"time\")\n               ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m2_3 (actual rows=1.00 loops=6048)\n                     Index Cond: (\"time\" = m1.\"time\")\n               ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m2_4 (actual rows=1.00 loops=6048)\n                     Index Cond: (\"time\" = m1.\"time\")\n               ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m2_5 (actual rows=1.00 loops=4611)\n                     Index Cond: (\"time\" = m1.\"time\")\n\n-- test runtime exclusion and startup exclusions\n:PREFIX SELECT m1.time, m2.time FROM metrics_timestamptz m1 LEFT JOIN LATERAL(SELECT time FROM metrics_timestamptz m2 WHERE m1.time = m2.time AND m2.time < '2000-01-10'::text::timestamptz LIMIT 1) m2 ON true ORDER BY m1.time;\n--- QUERY PLAN ---\n Nested Loop Left Join (actual rows=26787.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1 (actual rows=26787.00 loops=1)\n         Order: m1.\"time\"\n         ->  Index Scan Backward using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m1_1 (actual rows=4032.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m1_2 (actual rows=6048.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m1_3 (actual rows=6048.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m1_4 (actual rows=6048.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m1_5 (actual rows=4611.00 loops=1)\n   ->  Limit (actual rows=0.29 loops=26787)\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m2 (actual rows=0.29 loops=26787)\n               Chunks excluded during startup: 3\n               Chunks excluded during runtime: 1\n               ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m2_1 (actual rows=1.00 loops=4032)\n                     Index Cond: ((\"time\" < ('2000-01-10'::cstring)::timestamp with time zone) AND (\"time\" = m1.\"time\"))\n               ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m2_2 (actual rows=0.62 loops=6048)\n                     Index Cond: ((\"time\" < ('2000-01-10'::cstring)::timestamp with time zone) AND (\"time\" = m1.\"time\"))\n\n-- test runtime exclusion does not activate for constraints on non-partitioning columns\n-- should not use runtime exclusion\n:PREFIX SELECT * FROM append_test a LEFT JOIN LATERAL(SELECT * FROM join_test j WHERE a.colorid = j.colorid ORDER BY time DESC LIMIT 1) j ON true ORDER BY a.time LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Nested Loop Left Join (actual rows=1.00 loops=1)\n         ->  Custom Scan (ChunkAppend) on append_test a (actual rows=1.00 loops=1)\n               Order: a.\"time\"\n               ->  Index Scan Backward using _hyper_1_1_chunk_append_test_time_idx on _hyper_1_1_chunk a_1 (actual rows=1.00 loops=1)\n               ->  Index Scan Backward using _hyper_1_2_chunk_append_test_time_idx on _hyper_1_2_chunk a_2 (never executed)\n               ->  Index Scan Backward using _hyper_1_3_chunk_append_test_time_idx on _hyper_1_3_chunk a_3 (never executed)\n         ->  Limit (actual rows=1.00 loops=1)\n               ->  Custom Scan (ChunkAppend) on join_test j (actual rows=1.00 loops=1)\n                     Order: j.\"time\" DESC\n                     Hypertables excluded during runtime: 0\n                     ->  Index Scan using _hyper_2_6_chunk_join_test_time_idx on _hyper_2_6_chunk j_1 (actual rows=0.00 loops=1)\n                           Filter: (a.colorid = colorid)\n                           Rows Removed by Filter: 1\n                     ->  Index Scan using _hyper_2_5_chunk_join_test_time_idx on _hyper_2_5_chunk j_2 (actual rows=0.00 loops=1)\n                           Filter: (a.colorid = colorid)\n                           Rows Removed by Filter: 1\n                     ->  Index Scan using _hyper_2_4_chunk_join_test_time_idx on _hyper_2_4_chunk j_3 (actual rows=1.00 loops=1)\n                           Filter: (a.colorid = colorid)\n\n-- test runtime exclusion with LATERAL and generate_series\n:PREFIX SELECT g.time FROM generate_series('2000-01-01'::timestamptz, '2000-02-01'::timestamptz, '1d'::interval) g(time) LEFT JOIN LATERAL(SELECT time FROM metrics_timestamptz m WHERE m.time=g.time LIMIT 1) m ON true;\n--- QUERY PLAN ---\n Nested Loop Left Join (actual rows=32.00 loops=1)\n   ->  Function Scan on generate_series g (actual rows=32.00 loops=1)\n   ->  Limit (actual rows=1.00 loops=32)\n         ->  Result (actual rows=1.00 loops=32)\n               ->  Custom Scan (ChunkAppend) on metrics_timestamptz m (actual rows=1.00 loops=32)\n                     Chunks excluded during runtime: 4\n                     ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m_1 (actual rows=1.00 loops=5)\n                           Index Cond: (\"time\" = g.\"time\")\n                     ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m_2 (actual rows=1.00 loops=7)\n                           Index Cond: (\"time\" = g.\"time\")\n                     ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m_3 (actual rows=1.00 loops=7)\n                           Index Cond: (\"time\" = g.\"time\")\n                     ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m_4 (actual rows=1.00 loops=7)\n                           Index Cond: (\"time\" = g.\"time\")\n                     ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m_5 (actual rows=1.00 loops=6)\n                           Index Cond: (\"time\" = g.\"time\")\n\n:PREFIX SELECT * FROM generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval) AS g(time) INNER JOIN LATERAL (SELECT time FROM metrics_timestamptz m WHERE time=g.time) m ON true;\n--- QUERY PLAN ---\n Nested Loop (actual rows=96.00 loops=1)\n   ->  Function Scan on generate_series g (actual rows=32.00 loops=1)\n   ->  Append (actual rows=3.00 loops=32)\n         ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m_1 (actual rows=0.47 loops=32)\n               Index Cond: (\"time\" = g.\"time\")\n         ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m_2 (actual rows=0.66 loops=32)\n               Index Cond: (\"time\" = g.\"time\")\n         ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m_3 (actual rows=0.66 loops=32)\n               Index Cond: (\"time\" = g.\"time\")\n         ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m_4 (actual rows=0.66 loops=32)\n               Index Cond: (\"time\" = g.\"time\")\n         ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m_5 (actual rows=0.56 loops=32)\n               Index Cond: (\"time\" = g.\"time\")\n\n:PREFIX SELECT * FROM generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval) AS g(time) INNER JOIN LATERAL (SELECT time FROM metrics_timestamptz m WHERE time=g.time ORDER BY time) m ON true;\n--- QUERY PLAN ---\n Nested Loop (actual rows=96.00 loops=1)\n   ->  Function Scan on generate_series g (actual rows=32.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m (actual rows=3.00 loops=32)\n         Chunks excluded during runtime: 4\n         ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m_1 (actual rows=3.00 loops=5)\n               Index Cond: (\"time\" = g.\"time\")\n         ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m_2 (actual rows=3.00 loops=7)\n               Index Cond: (\"time\" = g.\"time\")\n         ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m_3 (actual rows=3.00 loops=7)\n               Index Cond: (\"time\" = g.\"time\")\n         ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m_4 (actual rows=3.00 loops=7)\n               Index Cond: (\"time\" = g.\"time\")\n         ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m_5 (actual rows=3.00 loops=6)\n               Index Cond: (\"time\" = g.\"time\")\n\n:PREFIX SELECT * FROM generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval) AS g(time) INNER JOIN LATERAL (SELECT time FROM metrics_timestamptz m WHERE time>g.time + '1 day' ORDER BY time LIMIT 1) m ON true;\n--- QUERY PLAN ---\n Nested Loop (actual rows=30.00 loops=1)\n   ->  Function Scan on generate_series g (actual rows=32.00 loops=1)\n   ->  Limit (actual rows=0.94 loops=32)\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m (actual rows=0.94 loops=32)\n               Order: m.\"time\"\n               Chunks excluded during startup: 0\n               Chunks excluded during runtime: 2\n               ->  Index Scan Backward using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m_1 (actual rows=1.00 loops=4)\n                     Index Cond: (\"time\" > (g.\"time\" + '@ 1 day'::interval))\n               ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m_2 (actual rows=1.00 loops=7)\n                     Index Cond: (\"time\" > (g.\"time\" + '@ 1 day'::interval))\n               ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m_3 (actual rows=1.00 loops=7)\n                     Index Cond: (\"time\" > (g.\"time\" + '@ 1 day'::interval))\n               ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m_4 (actual rows=1.00 loops=7)\n                     Index Cond: (\"time\" > (g.\"time\" + '@ 1 day'::interval))\n               ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m_5 (actual rows=0.71 loops=7)\n                     Index Cond: (\"time\" > (g.\"time\" + '@ 1 day'::interval))\n\n-- test runtime exclusion with subquery\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 WHERE m1.time=(SELECT max(time) FROM metrics_timestamptz);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz m1 (actual rows=3.00 loops=1)\n   Chunks excluded during runtime: 4\n   InitPlan 2\n     ->  Result (actual rows=1.00 loops=1)\n           InitPlan 1\n             ->  Limit (actual rows=1.00 loops=1)\n                   ->  Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=1.00 loops=1)\n                         Order: metrics_timestamptz.\"time\" DESC\n                         ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (actual rows=1.00 loops=1)\n                         ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (never executed)\n                         ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (never executed)\n                         ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk (never executed)\n                         ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk (never executed)\n   ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m1_1 (never executed)\n         Index Cond: (\"time\" = (InitPlan 2).col1)\n   ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m1_2 (never executed)\n         Index Cond: (\"time\" = (InitPlan 2).col1)\n   ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m1_3 (never executed)\n         Index Cond: (\"time\" = (InitPlan 2).col1)\n   ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m1_4 (never executed)\n         Index Cond: (\"time\" = (InitPlan 2).col1)\n   ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m1_5 (actual rows=3.00 loops=1)\n         Index Cond: (\"time\" = (InitPlan 2).col1)\n\n-- test runtime exclusion with correlated subquery\n:PREFIX SELECT m1.time, (SELECT m2.time FROM metrics_timestamptz m2 WHERE m2.time < m1.time ORDER BY m2.time DESC LIMIT 1) FROM metrics_timestamptz m1 WHERE m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Result (actual rows=7776.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1 (actual rows=7776.00 loops=1)\n         Order: m1.\"time\"\n         ->  Index Scan Backward using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m1_1 (actual rows=4032.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m1_2 (actual rows=3744.00 loops=1)\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   SubPlan 1\n     ->  Limit (actual rows=1.00 loops=7776)\n           ->  Custom Scan (ChunkAppend) on metrics_timestamptz m2 (actual rows=1.00 loops=7776)\n                 Order: m2.\"time\" DESC\n                 Chunks excluded during runtime: 3\n                 ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m2_1 (never executed)\n                       Index Cond: (\"time\" < m1.\"time\")\n                 ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m2_2 (never executed)\n                       Index Cond: (\"time\" < m1.\"time\")\n                 ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m2_3 (never executed)\n                       Index Cond: (\"time\" < m1.\"time\")\n                 ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m2_4 (actual rows=1.00 loops=3741)\n                       Index Cond: (\"time\" < m1.\"time\")\n                 ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m2_5 (actual rows=1.00 loops=4035)\n                       Index Cond: (\"time\" < m1.\"time\")\n\n-- test EXISTS\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 WHERE EXISTS(SELECT 1 FROM metrics_timestamptz m2 WHERE m1.time < m2.time) ORDER BY m1.time DESC limit 1000;\n--- QUERY PLAN ---\n Limit (actual rows=1000.00 loops=1)\n   ->  Nested Loop Semi Join (actual rows=1000.00 loops=1)\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1 (actual rows=1003.00 loops=1)\n               Order: m1.\"time\" DESC\n               ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m1_1 (actual rows=1003.00 loops=1)\n               ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m1_2 (never executed)\n               ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m1_3 (never executed)\n               ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m1_4 (never executed)\n               ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m1_5 (never executed)\n         ->  Append (actual rows=1.00 loops=1003)\n               ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk m2_1 (actual rows=0.00 loops=1003)\n                     Index Cond: (\"time\" > m1.\"time\")\n               ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m2_2 (actual rows=0.00 loops=1003)\n                     Index Cond: (\"time\" > m1.\"time\")\n               ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m2_3 (actual rows=0.00 loops=1003)\n                     Index Cond: (\"time\" > m1.\"time\")\n               ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m2_4 (actual rows=0.00 loops=1003)\n                     Index Cond: (\"time\" > m1.\"time\")\n               ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m2_5 (actual rows=1.00 loops=1003)\n                     Index Cond: (\"time\" > m1.\"time\")\n\n-- test constraint exclusion for subqueries with append\n-- should include 2 chunks\n:PREFIX SELECT time FROM (SELECT time FROM metrics_timestamptz WHERE time < '2000-01-10'::text::timestamptz ORDER BY time) m;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=7776.00 loops=1)\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 3\n   ->  Index Scan Backward using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk (actual rows=4032.00 loops=1)\n         Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk (actual rows=3744.00 loops=1)\n         Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n\n-- test constraint exclusion for subqueries with mergeappend\n-- should include 2 chunks\n:PREFIX SELECT device_id, time FROM (SELECT device_id, time FROM metrics_timestamptz WHERE time < '2000-01-10'::text::timestamptz ORDER BY device_id, time) m;\n--- QUERY PLAN ---\n Custom Scan (ConstraintAwareAppend) (actual rows=7776.00 loops=1)\n   Hypertable: metrics_timestamptz\n   Chunks excluded during startup: 3\n   ->  Merge Append (actual rows=7776.00 loops=1)\n         Sort Key: metrics_timestamptz.device_id, metrics_timestamptz.\"time\"\n         ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_17_chunk (actual rows=4032.00 loops=1)\n               Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n         ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_18_chunk (actual rows=3744.00 loops=1)\n               Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n\n-- test LIMIT pushdown\n-- no aggregates/window functions/SRF should pushdown limit\n:PREFIX SELECT FROM metrics_timestamptz ORDER BY time LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz (actual rows=1.00 loops=1)\n         Order: metrics_timestamptz.\"time\"\n         ->  Index Scan Backward using _hyper_5_17_chunk_metrics_timestamptz_time_idx on _hyper_5_17_chunk (actual rows=1.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk (never executed)\n         ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk (never executed)\n         ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk (never executed)\n         ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk (never executed)\n\n-- aggregates should prevent pushdown\n:PREFIX SELECT count(*) FROM metrics_timestamptz LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Aggregate (actual rows=1.00 loops=1)\n         ->  Append (actual rows=26787.00 loops=1)\n               ->  Seq Scan on _hyper_5_17_chunk (actual rows=4032.00 loops=1)\n               ->  Seq Scan on _hyper_5_18_chunk (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_19_chunk (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n\n:PREFIX SELECT count(*) FROM metrics_space LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Aggregate (actual rows=1.00 loops=1)\n         ->  Append (actual rows=37450.00 loops=1)\n               ->  Seq Scan on _hyper_6_22_chunk (actual rows=5376.00 loops=1)\n               ->  Seq Scan on _hyper_6_23_chunk (actual rows=5376.00 loops=1)\n               ->  Seq Scan on _hyper_6_24_chunk (actual rows=2688.00 loops=1)\n               ->  Seq Scan on _hyper_6_25_chunk (actual rows=8064.00 loops=1)\n               ->  Seq Scan on _hyper_6_26_chunk (actual rows=8064.00 loops=1)\n               ->  Seq Scan on _hyper_6_27_chunk (actual rows=4032.00 loops=1)\n               ->  Seq Scan on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               ->  Seq Scan on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               ->  Seq Scan on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n\n-- HAVING should prevent pushdown\n:PREFIX SELECT 1 FROM metrics_timestamptz HAVING count(*) > 1 LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Aggregate (actual rows=1.00 loops=1)\n         Filter: (count(*) > 1)\n         ->  Append (actual rows=26787.00 loops=1)\n               ->  Seq Scan on _hyper_5_17_chunk (actual rows=4032.00 loops=1)\n               ->  Seq Scan on _hyper_5_18_chunk (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_19_chunk (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_20_chunk (actual rows=6048.00 loops=1)\n               ->  Seq Scan on _hyper_5_21_chunk (actual rows=4611.00 loops=1)\n\n:PREFIX SELECT 1 FROM metrics_space HAVING count(*) > 1 LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Aggregate (actual rows=1.00 loops=1)\n         Filter: (count(*) > 1)\n         ->  Append (actual rows=37450.00 loops=1)\n               ->  Seq Scan on _hyper_6_22_chunk (actual rows=5376.00 loops=1)\n               ->  Seq Scan on _hyper_6_23_chunk (actual rows=5376.00 loops=1)\n               ->  Seq Scan on _hyper_6_24_chunk (actual rows=2688.00 loops=1)\n               ->  Seq Scan on _hyper_6_25_chunk (actual rows=8064.00 loops=1)\n               ->  Seq Scan on _hyper_6_26_chunk (actual rows=8064.00 loops=1)\n               ->  Seq Scan on _hyper_6_27_chunk (actual rows=4032.00 loops=1)\n               ->  Seq Scan on _hyper_6_28_chunk (actual rows=1540.00 loops=1)\n               ->  Seq Scan on _hyper_6_29_chunk (actual rows=1540.00 loops=1)\n               ->  Seq Scan on _hyper_6_30_chunk (actual rows=770.00 loops=1)\n\n-- DISTINCT should prevent pushdown\nSET enable_hashagg TO false;\n:PREFIX SELECT DISTINCT device_id FROM metrics_timestamptz ORDER BY device_id LIMIT 3;\n--- QUERY PLAN ---\n Limit (actual rows=3.00 loops=1)\n   ->  Unique (actual rows=3.00 loops=1)\n         ->  Merge Append (actual rows=17859.00 loops=1)\n               Sort Key: metrics_timestamptz.device_id\n               ->  Index Scan using _hyper_5_17_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_17_chunk (actual rows=2689.00 loops=1)\n               ->  Index Scan using _hyper_5_18_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_18_chunk (actual rows=4033.00 loops=1)\n               ->  Index Scan using _hyper_5_19_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_19_chunk (actual rows=4033.00 loops=1)\n               ->  Index Scan using _hyper_5_20_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_20_chunk (actual rows=4033.00 loops=1)\n               ->  Index Scan using _hyper_5_21_chunk_metrics_timestamptz_device_id_time_idx on _hyper_5_21_chunk (actual rows=3075.00 loops=1)\n\n:PREFIX SELECT DISTINCT device_id FROM metrics_space ORDER BY device_id LIMIT 3;\n--- QUERY PLAN ---\n Limit (actual rows=3.00 loops=1)\n   ->  Unique (actual rows=3.00 loops=1)\n         ->  Merge Append (actual rows=7491.00 loops=1)\n               Sort Key: metrics_space.device_id\n               ->  Index Scan using _hyper_6_22_chunk_metrics_space_device_id_time_idx on _hyper_6_22_chunk (actual rows=1345.00 loops=1)\n               ->  Index Scan using _hyper_6_23_chunk_metrics_space_device_id_time_idx on _hyper_6_23_chunk (actual rows=1345.00 loops=1)\n               ->  Index Scan using _hyper_6_24_chunk_metrics_space_device_id_time_idx on _hyper_6_24_chunk (actual rows=1.00 loops=1)\n               ->  Index Scan using _hyper_6_25_chunk_metrics_space_device_id_time_idx on _hyper_6_25_chunk (actual rows=2017.00 loops=1)\n               ->  Index Scan using _hyper_6_26_chunk_metrics_space_device_id_time_idx on _hyper_6_26_chunk (actual rows=2017.00 loops=1)\n               ->  Index Scan using _hyper_6_27_chunk_metrics_space_device_id_time_idx on _hyper_6_27_chunk (actual rows=1.00 loops=1)\n               ->  Index Scan using _hyper_6_28_chunk_metrics_space_device_id_time_idx on _hyper_6_28_chunk (actual rows=386.00 loops=1)\n               ->  Index Scan using _hyper_6_29_chunk_metrics_space_device_id_time_idx on _hyper_6_29_chunk (actual rows=386.00 loops=1)\n               ->  Index Scan using _hyper_6_30_chunk_metrics_space_device_id_time_idx on _hyper_6_30_chunk (actual rows=1.00 loops=1)\n\nRESET enable_hashagg;\n-- JOINs should prevent pushdown\n-- when LIMIT gets pushed to a Sort node it will switch to top-N heapsort\n-- if more tuples then LIMIT are requested this will trigger an error\n-- to trigger this we need a Sort node that is below ChunkAppend\nCREATE TABLE join_limit (time timestamptz, device_id int);\nSELECT table_name FROM create_hypertable('join_limit','time',create_default_indexes:=false);\n table_name \n------------\n join_limit\n\nCREATE INDEX ON join_limit(time,device_id);\nINSERT INTO join_limit\nSELECT time, device_id\nFROM generate_series('2000-01-01'::timestamptz,'2000-01-21','30m') g1(time),\n  generate_series(1,10,1) g2(device_id)\nORDER BY time, device_id;\nVACUUM (ANALYZE) join_limit;\n-- get 2nd chunk oid\nSELECT tableoid AS \"CHUNK_OID\" FROM join_limit WHERE time > '2000-01-07' ORDER BY time LIMIT 1\n\\gset\n--get index name for 2nd chunk\nSELECT indexrelid::regclass AS \"INDEX_NAME\" FROM pg_index WHERE indrelid = :CHUNK_OID\n\\gset\nDROP INDEX :INDEX_NAME;\n:PREFIX SELECT * FROM metrics_timestamptz m1 INNER JOIN join_limit m2 ON m1.time = m2.time AND m1.device_id=m2.device_id WHERE m1.time > '2000-01-07' ORDER BY m1.time, m1.device_id LIMIT 3;\n--- QUERY PLAN ---\n Limit (actual rows=3.00 loops=1)\n   ->  Merge Join (actual rows=3.00 loops=1)\n         Merge Cond: (m2.\"time\" = m1.\"time\")\n         Join Filter: (m2.device_id = m1.device_id)\n         Rows Removed by Join Filter: 4\n         ->  Custom Scan (ChunkAppend) on join_limit m2 (actual rows=3.00 loops=1)\n               Order: m2.\"time\", m2.device_id\n               ->  Sort (actual rows=3.00 loops=1)\n                     Sort Key: m2_1.\"time\", m2_1.device_id\n                     Sort Method: quicksort \n                     ->  Seq Scan on _hyper_8_35_chunk m2_1 (actual rows=2710.00 loops=1)\n                           Filter: (\"time\" > 'Fri Jan 07 00:00:00 2000 PST'::timestamp with time zone)\n                           Rows Removed by Filter: 650\n               ->  Index Scan using _hyper_8_36_chunk_join_limit_time_device_id_idx on _hyper_8_36_chunk m2_2 (never executed)\n               ->  Index Scan using _hyper_8_37_chunk_join_limit_time_device_id_idx on _hyper_8_37_chunk m2_3 (never executed)\n         ->  Materialize (actual rows=22.00 loops=1)\n               ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1 (actual rows=19.00 loops=1)\n                     Order: m1.\"time\"\n                     ->  Index Scan Backward using _hyper_5_18_chunk_metrics_timestamptz_time_idx on _hyper_5_18_chunk m1_1 (actual rows=19.00 loops=1)\n                           Index Cond: (\"time\" > 'Fri Jan 07 00:00:00 2000 PST'::timestamp with time zone)\n                     ->  Index Scan Backward using _hyper_5_19_chunk_metrics_timestamptz_time_idx on _hyper_5_19_chunk m1_2 (never executed)\n                     ->  Index Scan Backward using _hyper_5_20_chunk_metrics_timestamptz_time_idx on _hyper_5_20_chunk m1_3 (never executed)\n                     ->  Index Scan Backward using _hyper_5_21_chunk_metrics_timestamptz_time_idx on _hyper_5_21_chunk m1_4 (never executed)\n\nDROP TABLE join_limit;\n-- test ChunkAppend projection #2661\n:PREFIX SELECT ts.timestamp, ht.timestamp\nFROM (\n  SELECT generate_series(\n    to_timestamp(FLOOR(EXTRACT (EPOCH FROM '2020-01-01T00:01:00Z'::timestamp) / 300) * 300) AT TIME ZONE 'UTC',\n    '2020-01-01T01:00:00Z',\n    '5 minutes'::interval\n  ) AS timestamp\n) ts\nLEFT JOIN i2661 ht ON\n  (FLOOR(EXTRACT (EPOCH FROM ht.\"timestamp\") / 300) * 300 = EXTRACT (EPOCH FROM ts.timestamp))\n  AND ht.timestamp > '2019-12-30T00:00:00Z'::timestamp\nORDER BY ts.timestamp, ht.timestamp;\n--- QUERY PLAN ---\n Sort (actual rows=33.00 loops=1)\n   Sort Key: (generate_series('Wed Jan 01 00:00:00 2020'::timestamp without time zone, 'Wed Jan 01 01:00:00 2020'::timestamp without time zone, '@ 5 mins'::interval)), ht.\"timestamp\"\n   Sort Method: quicksort \n   ->  Hash Right Join (actual rows=33.00 loops=1)\n         Hash Cond: ((floor((EXTRACT(epoch FROM ht.\"timestamp\") / '300'::numeric)) * '300'::numeric) = EXTRACT(epoch FROM (generate_series('Wed Jan 01 00:00:00 2020'::timestamp without time zone, 'Wed Jan 01 01:00:00 2020'::timestamp without time zone, '@ 5 mins'::interval))))\n         ->  Custom Scan (ChunkAppend) on i2661 ht (actual rows=7201.00 loops=1)\n               Chunks excluded during startup: 0\n               ->  Seq Scan on _hyper_7_31_chunk ht_1 (actual rows=1200.00 loops=1)\n                     Filter: (\"timestamp\" > 'Mon Dec 30 00:00:00 2019'::timestamp without time zone)\n               ->  Seq Scan on _hyper_7_32_chunk ht_2 (actual rows=5040.00 loops=1)\n                     Filter: (\"timestamp\" > 'Mon Dec 30 00:00:00 2019'::timestamp without time zone)\n               ->  Seq Scan on _hyper_7_33_chunk ht_3 (actual rows=961.00 loops=1)\n                     Filter: (\"timestamp\" > 'Mon Dec 30 00:00:00 2019'::timestamp without time zone)\n         ->  Hash (actual rows=13.00 loops=1)\n               ->  ProjectSet (actual rows=13.00 loops=1)\n                     ->  Result (actual rows=1.00 loops=1)\n\n-- #3030 test chunkappend keeps pathkeys when subpath is append\n-- on PG11 this will not use ChunkAppend but MergeAppend\nSET enable_seqscan TO FALSE;\nCREATE TABLE i3030(time timestamptz NOT NULL, a int, b int);\nSELECT table_name FROM create_hypertable('i3030', 'time', create_default_indexes=>false);\n table_name \n------------\n i3030\n\nCREATE INDEX ON i3030(a,time);\nINSERT INTO i3030 (time,a) SELECT time, a FROM generate_series('2000-01-01'::timestamptz,'2000-01-01 3:00:00'::timestamptz,'1min'::interval) time, generate_series(1,30) a;\nVACUUM (ANALYZE) i3030;\n:PREFIX SELECT * FROM i3030 where time BETWEEN '2000-01-01'::text::timestamptz AND '2000-01-03'::text::timestamptz ORDER BY a,time LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on i3030 (actual rows=1.00 loops=1)\n         Order: i3030.a, i3030.\"time\"\n         Chunks excluded during startup: 0\n         ->  Index Scan using _hyper_9_38_chunk_i3030_a_time_idx on _hyper_9_38_chunk (actual rows=1.00 loops=1)\n               Index Cond: ((\"time\" >= ('2000-01-01'::cstring)::timestamp with time zone) AND (\"time\" <= ('2000-01-03'::cstring)::timestamp with time zone))\n\nDROP TABLE i3030;\nRESET enable_seqscan;\n--parent runtime exclusion tests:\n--optimization works with ANY (array)\n:PREFIX\nSELECT *\nFROM append_test a\nWHERE a.attr @> ANY((SELECT coalesce(array_agg(attr), array[]::jsonb[]) FROM join_test_plain WHERE temp > 100)::jsonb[]);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on append_test a (actual rows=0.00 loops=1)\n   Hypertables excluded during runtime: 1\n   InitPlan 1\n     ->  Aggregate (actual rows=1.00 loops=1)\n           ->  Seq Scan on join_test_plain (actual rows=0.00 loops=1)\n                 Filter: (temp > '100'::double precision)\n                 Rows Removed by Filter: 3\n   ->  Seq Scan on _hyper_1_1_chunk a_1 (never executed)\n         Filter: (attr @> ANY ((InitPlan 1).col1))\n   ->  Seq Scan on _hyper_1_2_chunk a_2 (never executed)\n         Filter: (attr @> ANY ((InitPlan 1).col1))\n   ->  Seq Scan on _hyper_1_3_chunk a_3 (never executed)\n         Filter: (attr @> ANY ((InitPlan 1).col1))\n\n--optimization does not work for ANY subquery (does not force an initplan)\n:PREFIX\nSELECT *\nFROM append_test a\nWHERE a.attr @> ANY((SELECT attr FROM join_test_plain WHERE temp > 100));\n--- QUERY PLAN ---\n Nested Loop Semi Join (actual rows=0.00 loops=1)\n   Join Filter: (a.attr @> join_test_plain.attr)\n   ->  Append (actual rows=5.00 loops=1)\n         ->  Seq Scan on _hyper_1_1_chunk a_1 (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_2_chunk a_2 (actual rows=2.00 loops=1)\n         ->  Seq Scan on _hyper_1_3_chunk a_3 (actual rows=1.00 loops=1)\n   ->  Materialize (actual rows=0.00 loops=5)\n         ->  Seq Scan on join_test_plain (actual rows=0.00 loops=1)\n               Filter: (temp > '100'::double precision)\n               Rows Removed by Filter: 3\n\n--works on any strict operator without ANY\n:PREFIX\nSELECT *\nFROM append_test a\nWHERE a.attr @> (SELECT attr FROM join_test_plain WHERE temp > 100 limit 1);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on append_test a (actual rows=0.00 loops=1)\n   Hypertables excluded during runtime: 1\n   InitPlan 1\n     ->  Limit (actual rows=0.00 loops=1)\n           ->  Seq Scan on join_test_plain (actual rows=0.00 loops=1)\n                 Filter: (temp > '100'::double precision)\n                 Rows Removed by Filter: 3\n   ->  Seq Scan on _hyper_1_1_chunk a_1 (never executed)\n         Filter: (attr @> (InitPlan 1).col1)\n   ->  Seq Scan on _hyper_1_2_chunk a_2 (never executed)\n         Filter: (attr @> (InitPlan 1).col1)\n   ->  Seq Scan on _hyper_1_3_chunk a_3 (never executed)\n         Filter: (attr @> (InitPlan 1).col1)\n\n--optimization works with function calls\nCREATE OR REPLACE FUNCTION select_tag(_min_temp int)\n RETURNS jsonb[]\n LANGUAGE sql\n STABLE PARALLEL SAFE\nAS $function$\n   SELECT coalesce(array_agg(attr), array[]::jsonb[])\n  FROM join_test_plain\n  WHERE temp > _min_temp\n$function$;\n:PREFIX\nSELECT *\nFROM append_test a\nWHERE a.attr @> ANY((SELECT select_tag(100))::jsonb[]);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on append_test a (actual rows=0.00 loops=1)\n   Hypertables excluded during runtime: 1\n   InitPlan 1\n     ->  Result (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_1_1_chunk a_1 (never executed)\n         Filter: (attr @> ANY ((InitPlan 1).col1))\n   ->  Seq Scan on _hyper_1_2_chunk a_2 (never executed)\n         Filter: (attr @> ANY ((InitPlan 1).col1))\n   ->  Seq Scan on _hyper_1_3_chunk a_3 (never executed)\n         Filter: (attr @> ANY ((InitPlan 1).col1))\n\n--optimization does not work when result is null\n:PREFIX\nSELECT *\nFROM append_test a\nWHERE a.attr @> ANY((SELECT array_agg(attr) FROM join_test_plain WHERE temp > 100)::jsonb[]);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on append_test a (actual rows=0.00 loops=1)\n   Hypertables excluded during runtime: 0\n   InitPlan 1\n     ->  Aggregate (actual rows=1.00 loops=1)\n           ->  Seq Scan on join_test_plain (actual rows=0.00 loops=1)\n                 Filter: (temp > '100'::double precision)\n                 Rows Removed by Filter: 3\n   ->  Seq Scan on _hyper_1_1_chunk a_1 (actual rows=0.00 loops=1)\n         Filter: (attr @> ANY ((InitPlan 1).col1))\n         Rows Removed by Filter: 2\n   ->  Seq Scan on _hyper_1_2_chunk a_2 (actual rows=0.00 loops=1)\n         Filter: (attr @> ANY ((InitPlan 1).col1))\n         Rows Removed by Filter: 2\n   ->  Seq Scan on _hyper_1_3_chunk a_3 (actual rows=0.00 loops=1)\n         Filter: (attr @> ANY ((InitPlan 1).col1))\n         Rows Removed by Filter: 1\n\n-- Test that ConstraintAwareAppend properly locks relations in\n-- parallel query mode\nset timescaledb.enable_chunk_append=false;\ncall force_parallel(true);\n:PREFIX\nselect time, avg(temp), colorid from append_test\nwhere time > now_s() - interval '3 months 20 days'\ngroup by time, colorid;\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\npsql:include/append_query.sql:415: NOTICE:  Stable function now_s() called!\n--- QUERY PLAN ---\n Gather (actual rows=3.00 loops=1)\n   Workers Planned: 1\n   Workers Launched: 1\n   Single Copy: true\n   ->  HashAggregate (actual rows=3.00 loops=1)\n         Group Key: append_test.\"time\", append_test.colorid\n         ->  Custom Scan (ConstraintAwareAppend) (actual rows=3.00 loops=1)\n               Hypertable: append_test\n               Chunks excluded during startup: 1\n               ->  Append (actual rows=3.00 loops=1)\n                     ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)\n                           Filter: (\"time\" > (now_s() - '@ 3 mons 20 days'::interval))\n                     ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n                           Filter: (\"time\" > (now_s() - '@ 3 mons 20 days'::interval))\n\nreset timescaledb.enable_chunk_append;\nreset max_parallel_workers_per_gather;\ncall force_parallel(false);\n--generate the results into two different files\n\\set ECHO errors\n--- Unoptimized results\n+++ Optimized results\n@@ -1,6 +1,6 @@\n              setting              | value \n ----------------------------------+-------\n- timescaledb.enable_optimizations | off\n+ timescaledb.enable_optimizations | on\n  timescaledb.enable_chunk_append  | on\n \n--- Unoptimized results\n+++ Optimized results\n@@ -1,7 +1,7 @@\n              setting              | value \n ----------------------------------+-------\n- timescaledb.enable_optimizations | off\n- timescaledb.enable_chunk_append  | on\n+ timescaledb.enable_optimizations | on\n+ timescaledb.enable_chunk_append  | off\n \n  time | temp | colorid | attr \n"
  },
  {
    "path": "test/expected/baserel_cache.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- Test that the baserel cache is not clobbered if there's an error\n-- in a SQL function.\nCREATE TABLE valid_ids\n(\n  id UUID PRIMARY KEY\n);\nCREATE FUNCTION DEFAULT_UUID(TEXT DEFAULT '') RETURNS UUID AS $$\n  BEGIN\n    RETURN COALESCE($1, '')::UUID;\n  EXCEPTION WHEN invalid_text_representation THEN\n    RETURN '00000000-0000-0000-0000-000000000000';\n  END;\n$$ LANGUAGE PLPGSQL IMMUTABLE;\nCREATE FUNCTION KNOWN_ID(UUID, TEXT) RETURNS UUID AS $$\n  SELECT COALESCE(\n    (SELECT id FROM valid_ids WHERE id = $1),\n    DEFAULT_UUID()\n  );\n$$ LANGUAGE SQL;\nSELECT KNOWN_ID(NULL, ''), KNOWN_ID(NULL, '');\n               known_id               |               known_id               \n--------------------------------------+--------------------------------------\n 00000000-0000-0000-0000-000000000000 | 00000000-0000-0000-0000-000000000000\n\n"
  },
  {
    "path": "test/expected/bgw_launcher.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\set TEST_DBNAME_2 :TEST_DBNAME _2\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- start bgw since they are stopped for tests by default\nSELECT _timescaledb_functions.start_background_workers();\n start_background_workers \n--------------------------\n t\n\nCREATE DATABASE :TEST_DBNAME_2;\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\n\\ir include/bgw_launcher_utils.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- Note on testing: need a couple wrappers that pg_sleep in a loop to wait for changes\n-- to appear in pg_stat_activity.\n-- Further Note: PG 9.6 changed what appeared in pg_stat_activity, so the launcher doesn't actually show up.\n-- we can still test its interactions with its children, but can't test some of the things specific to the launcher.\n-- So we've added some bits about the version number as needed.\nCREATE VIEW worker_counts as\nSELECT count(*) filter (WHERE backend_type = 'TimescaleDB Background Worker Launcher') as launcher,\n       count(*) filter (WHERE backend_type = 'TimescaleDB Background Worker Scheduler' AND datname = :'TEST_DBNAME') as single_scheduler,\n       count(*) filter (WHERE backend_type = 'TimescaleDB Background Worker Scheduler' AND datname = :'TEST_DBNAME_2') as single_2_scheduler,\n       count(*) filter (WHERE backend_type = 'TimescaleDB Background Worker Scheduler' AND datname = 'template1') as template1_scheduler\n  FROM pg_stat_activity;\nCREATE FUNCTION wait_worker_counts(launcher_ct INTEGER,  scheduler1_ct INTEGER, scheduler2_ct INTEGER, template1_ct INTEGER) RETURNS BOOLEAN LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\nr INTEGER;\nBEGIN\nFOR i in 1..10\nLOOP\nSELECT COUNT(*) from worker_counts where launcher = launcher_ct\n\tAND single_scheduler = scheduler1_ct AND single_2_scheduler = scheduler2_ct and template1_scheduler = template1_ct into r;\nif(r < 1) THEN\n  PERFORM pg_sleep(0.1);\n  PERFORM pg_stat_clear_snapshot();\nELSE\n\t--We have the correct counts!\n  RETURN TRUE;\nEND IF;\nEND LOOP;\nRETURN FALSE;\nEND\n$BODY$;\nCREATE FUNCTION wait_for_bgw_scheduler(_datname NAME, _count INT DEFAULT 1, _ticks INT DEFAULT 10) RETURNS TEXT LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n  r INTEGER;\nBEGIN\n  FOR i in 1.._ticks\n  LOOP\n    SELECT count(*)\n    FROM pg_stat_activity\n    WHERE\n      application_name = 'TimescaleDB Background Worker Scheduler' AND\n      datname = _datname\n    INTO r;\n\n    IF(r <> _count) THEN\n      PERFORM pg_sleep(0.1);\n      PERFORM pg_stat_clear_snapshot();\n    ELSE\n      RETURN 'BGW Scheduler found.';\n    END IF;\n\n  END LOOP;\n\n  RETURN 'BGW Scheduler NOT found.';\n\nEND\n$BODY$;\nCREATE PROCEDURE kill_database_backends(_datname NAME) LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n  r INTEGER;\nBEGIN\n  FOR i in 1..100\n  LOOP\n    SELECT count(pg_terminate_backend(pg_stat_activity.pid))\n    FROM pg_stat_activity\n    WHERE\n      datname = _datname\n          AND pg_stat_activity.pid <> pg_backend_pid()\n    INTO r;\n\n    IF(r = 0) THEN\n        RETURN;\n    END IF;\n\n    PERFORM pg_sleep(0.1);\n    PERFORM pg_stat_clear_snapshot();\n  END LOOP;\n\n  RAISE 'Failed to terminate backends';\n\nEND\n$BODY$;\n-- When we've connected to test db 2, we should be able to see the cluster launcher\n-- and the scheduler for test db in pg_stat_activity\n-- but test db 2 shouldn't have a scheduler because ext not created yet\nSELECT wait_worker_counts(1,1,0,0);\n wait_worker_counts \n--------------------\n t\n\n-- Now create the extension in test db 2\nSET client_min_messages = ERROR;\nCREATE EXTENSION timescaledb CASCADE;\nRESET client_min_messages;\nSELECT wait_worker_counts(1,1,1,0);\n wait_worker_counts \n--------------------\n t\n\nDROP DATABASE :TEST_DBNAME WITH (FORCE);\n-- Now the db_scheduler for test db should have disappeared\nSELECT wait_worker_counts(1,0,1,0);\n wait_worker_counts \n--------------------\n t\n\n-- Now let's restart the scheduler in test db 2 and make sure our backend_start changed\nSELECT backend_start as orig_backend_start\nFROM pg_stat_activity\nWHERE backend_type = 'TimescaleDB Background Worker Scheduler'\nAND datname = :'TEST_DBNAME_2' \\gset\n-- We'll do this in a txn so that we can see that the worker locks on our txn before continuing\nBEGIN;\nSELECT _timescaledb_functions.restart_background_workers();\n restart_background_workers \n----------------------------\n t\n\nSELECT wait_worker_counts(1,0,1,0);\n wait_worker_counts \n--------------------\n t\n\nSELECT (backend_start > :'orig_backend_start'::timestamptz) backend_start_changed,\n(wait_event = 'virtualxid') wait_event_changed\nFROM pg_stat_activity\nWHERE backend_type = 'TimescaleDB Background Worker Scheduler'\nAND datname = :'TEST_DBNAME_2';\n backend_start_changed | wait_event_changed \n-----------------------+--------------------\n t                     | t\n\nCOMMIT;\nSELECT wait_worker_counts(1,0,1,0);\n wait_worker_counts \n--------------------\n t\n\nSELECT (wait_event IS DISTINCT FROM 'virtualxid') wait_event_changed\nFROM pg_stat_activity\nWHERE backend_type = 'TimescaleDB Background Worker Scheduler'\nAND datname = :'TEST_DBNAME_2';\n wait_event_changed \n--------------------\n t\n\n-- Test stop\nSELECT _timescaledb_functions.stop_background_workers();\n stop_background_workers \n-------------------------\n t\n\nSELECT wait_worker_counts(1,0,0,0);\n wait_worker_counts \n--------------------\n t\n\n-- Make sure it doesn't break if we stop twice in a row\nSELECT _timescaledb_functions.stop_background_workers();\n stop_background_workers \n-------------------------\n t\n\nSELECT wait_worker_counts(1,0,0,0);\n wait_worker_counts \n--------------------\n t\n\n-- test start\nSELECT _timescaledb_functions.start_background_workers();\n start_background_workers \n--------------------------\n t\n\nSELECT wait_worker_counts(1,0,1,0);\n wait_worker_counts \n--------------------\n t\n\n-- make sure start is idempotent\nSELECT backend_start as orig_backend_start\nFROM pg_stat_activity\nWHERE backend_type = 'TimescaleDB Background Worker Scheduler'\nAND datname = :'TEST_DBNAME_2' \\gset\n-- Since we're doing idempotency tests, we're also going to exercise our queue and start 20 times\nSELECT _timescaledb_functions.start_background_workers() as start_background_workers, * FROM generate_series(1,20);\n start_background_workers | generate_series \n--------------------------+-----------------\n t                        |               1\n t                        |               2\n t                        |               3\n t                        |               4\n t                        |               5\n t                        |               6\n t                        |               7\n t                        |               8\n t                        |               9\n t                        |              10\n t                        |              11\n t                        |              12\n t                        |              13\n t                        |              14\n t                        |              15\n t                        |              16\n t                        |              17\n t                        |              18\n t                        |              19\n t                        |              20\n\n-- Here we're waiting to see if something shows up in pg_stat_activity,\n--  so we have to condition our loop in the opposite way. We'll only wait\n--  half a second in total as well so that tests don't take too long.\nCREATE FUNCTION wait_equals(TIMESTAMPTZ, TEXT) RETURNS BOOLEAN LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\nr BOOLEAN;\nBEGIN\nFOR i in 1..5\nLOOP\nSELECT (backend_start = $1::timestamptz) backend_start_unchanged\nFROM pg_stat_activity\nWHERE backend_type = 'TimescaleDB Background Worker Scheduler'\nAND datname = $2 into r;\nif(r) THEN\n  PERFORM pg_sleep(0.1);\n  PERFORM pg_stat_clear_snapshot();\nELSE\n  RETURN FALSE;\nEND IF;\nEND LOOP;\nRETURN TRUE;\nEND\n$BODY$;\nselect wait_equals(:'orig_backend_start', :'TEST_DBNAME_2');\n wait_equals \n-------------\n t\n\n-- Make sure restart starts a worker even if it is stopped\nSELECT _timescaledb_functions.stop_background_workers();\n stop_background_workers \n-------------------------\n t\n\nSELECT wait_worker_counts(1,0,0,0);\n wait_worker_counts \n--------------------\n t\n\nSELECT _timescaledb_functions.restart_background_workers();\n restart_background_workers \n----------------------------\n t\n\nSELECT wait_worker_counts(1,0,1,0);\n wait_worker_counts \n--------------------\n t\n\n-- Make sure drop extension statement restarts the worker and on rollback it keeps running\n-- Now let's restart the scheduler and make sure our backend_start changed\nSELECT backend_start as orig_backend_start\nFROM pg_stat_activity\nWHERE backend_type = 'TimescaleDB Background Worker Scheduler'\nAND datname = :'TEST_DBNAME_2' \\gset\nBEGIN;\nDROP EXTENSION timescaledb;\nSELECT wait_worker_counts(1,0,1,0);\n wait_worker_counts \n--------------------\n t\n\nROLLBACK;\nCREATE FUNCTION wait_greater(TIMESTAMPTZ,TEXT) RETURNS BOOLEAN LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\nr BOOLEAN;\nBEGIN\nFOR i in 1..10\nLOOP\nSELECT (backend_start > $1::timestamptz) backend_start_changed\nFROM pg_stat_activity\nWHERE backend_type = 'TimescaleDB Background Worker Scheduler'\nAND datname = $2 into r;\nif(NOT r) THEN\n  PERFORM pg_sleep(0.1);\n  PERFORM pg_stat_clear_snapshot();\nELSE\n  RETURN TRUE;\nEND IF;\nEND LOOP;\nRETURN FALSE;\nEND\n$BODY$;\nSELECT wait_greater(:'orig_backend_start',:'TEST_DBNAME_2');\n wait_greater \n--------------\n t\n\n-- Make sure canceling the launcher backend causes a restart of schedulers\nSELECT backend_start as orig_backend_start\nFROM pg_stat_activity\nWHERE backend_type = 'TimescaleDB Background Worker Scheduler'\nAND datname = :'TEST_DBNAME_2' \\gset\nSELECT pg_cancel_backend(pid) FROM pg_stat_activity WHERE backend_type = 'TimescaleDB Background Worker Launcher';\n pg_cancel_backend \n-------------------\n t\n\nSELECT wait_worker_counts(1,0,1,0);\n wait_worker_counts \n--------------------\n t\n\nSELECT wait_greater(:'orig_backend_start', :'TEST_DBNAME_2');\n wait_greater \n--------------\n t\n\n-- Make sure running pre_restore function stops background workers\nSELECT timescaledb_pre_restore();\n timescaledb_pre_restore \n-------------------------\n t\n\nSELECT wait_worker_counts(1,0,0,0);\n wait_worker_counts \n--------------------\n t\n\n-- Make sure a restart with restoring on first starts the background worker\nBEGIN;\nSELECT _timescaledb_functions.restart_background_workers();\n restart_background_workers \n----------------------------\n t\n\nSELECT wait_worker_counts(1,0,1,0);\n wait_worker_counts \n--------------------\n t\n\nCOMMIT;\n-- Then the worker dies when it sees that restoring is on after the txn commits\nSELECT wait_worker_counts(1,0,0,0);\n wait_worker_counts \n--------------------\n t\n\n--And post_restore starts them\nBEGIN;\nSELECT timescaledb_post_restore();\n timescaledb_post_restore \n--------------------------\n t\n\nSELECT wait_worker_counts(1,0,1,0);\n wait_worker_counts \n--------------------\n t\n\nCOMMIT;\n-- And they stay started\nSELECT wait_worker_counts(1,0,1,0);\n wait_worker_counts \n--------------------\n t\n\n-- Make sure dropping the extension means that the scheduler is stopped\nBEGIN;\nDROP EXTENSION timescaledb;\nCOMMIT;\nSELECT wait_worker_counts(1,0,0,0);\n wait_worker_counts \n--------------------\n t\n\n-- Test that background workers are stopped with DROP OWNED\nALTER ROLE :ROLE_DEFAULT_PERM_USER WITH SUPERUSER;\n\\c :TEST_DBNAME_2 :ROLE_DEFAULT_PERM_USER\nSET client_min_messages = ERROR;\nCREATE EXTENSION timescaledb CASCADE;\nRESET client_min_messages;\n-- Make sure there is 1 launcher and 1 bgw in test db 2\nSELECT wait_worker_counts(launcher_ct=>1, scheduler1_ct=> 0, scheduler2_ct=>1, template1_ct=>0);\n wait_worker_counts \n--------------------\n t\n\n-- drop a non-owner of the extension results in no change to worker counts\nDROP OWNED BY :ROLE_DEFAULT_PERM_USER_2;\nSELECT wait_worker_counts(launcher_ct=>1, scheduler1_ct=> 0, scheduler2_ct=>1, template1_ct=>0);\n wait_worker_counts \n--------------------\n t\n\n-- drop of owner of extension results in extension drop and a stop to the bgw\nDROP OWNED BY :ROLE_DEFAULT_PERM_USER;\n-- The worker in test db 2 is dead. Note that 0s are respected\nSELECT wait_worker_counts(launcher_ct=>1, scheduler1_ct=>0, scheduler2_ct=>0, template1_ct=>0);\n wait_worker_counts \n--------------------\n t\n\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\nALTER ROLE :ROLE_DEFAULT_PERM_USER WITH NOSUPERUSER;\n-- Connect to the template1 database\n\\c template1\n\\ir include/bgw_launcher_utils.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- Note on testing: need a couple wrappers that pg_sleep in a loop to wait for changes\n-- to appear in pg_stat_activity.\n-- Further Note: PG 9.6 changed what appeared in pg_stat_activity, so the launcher doesn't actually show up.\n-- we can still test its interactions with its children, but can't test some of the things specific to the launcher.\n-- So we've added some bits about the version number as needed.\nCREATE VIEW worker_counts as\nSELECT count(*) filter (WHERE backend_type = 'TimescaleDB Background Worker Launcher') as launcher,\n       count(*) filter (WHERE backend_type = 'TimescaleDB Background Worker Scheduler' AND datname = :'TEST_DBNAME') as single_scheduler,\n       count(*) filter (WHERE backend_type = 'TimescaleDB Background Worker Scheduler' AND datname = :'TEST_DBNAME_2') as single_2_scheduler,\n       count(*) filter (WHERE backend_type = 'TimescaleDB Background Worker Scheduler' AND datname = 'template1') as template1_scheduler\n  FROM pg_stat_activity;\nCREATE FUNCTION wait_worker_counts(launcher_ct INTEGER,  scheduler1_ct INTEGER, scheduler2_ct INTEGER, template1_ct INTEGER) RETURNS BOOLEAN LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\nr INTEGER;\nBEGIN\nFOR i in 1..10\nLOOP\nSELECT COUNT(*) from worker_counts where launcher = launcher_ct\n\tAND single_scheduler = scheduler1_ct AND single_2_scheduler = scheduler2_ct and template1_scheduler = template1_ct into r;\nif(r < 1) THEN\n  PERFORM pg_sleep(0.1);\n  PERFORM pg_stat_clear_snapshot();\nELSE\n\t--We have the correct counts!\n  RETURN TRUE;\nEND IF;\nEND LOOP;\nRETURN FALSE;\nEND\n$BODY$;\nCREATE FUNCTION wait_for_bgw_scheduler(_datname NAME, _count INT DEFAULT 1, _ticks INT DEFAULT 10) RETURNS TEXT LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n  r INTEGER;\nBEGIN\n  FOR i in 1.._ticks\n  LOOP\n    SELECT count(*)\n    FROM pg_stat_activity\n    WHERE\n      application_name = 'TimescaleDB Background Worker Scheduler' AND\n      datname = _datname\n    INTO r;\n\n    IF(r <> _count) THEN\n      PERFORM pg_sleep(0.1);\n      PERFORM pg_stat_clear_snapshot();\n    ELSE\n      RETURN 'BGW Scheduler found.';\n    END IF;\n\n  END LOOP;\n\n  RETURN 'BGW Scheduler NOT found.';\n\nEND\n$BODY$;\nCREATE PROCEDURE kill_database_backends(_datname NAME) LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n  r INTEGER;\nBEGIN\n  FOR i in 1..100\n  LOOP\n    SELECT count(pg_terminate_backend(pg_stat_activity.pid))\n    FROM pg_stat_activity\n    WHERE\n      datname = _datname\n          AND pg_stat_activity.pid <> pg_backend_pid()\n    INTO r;\n\n    IF(r = 0) THEN\n        RETURN;\n    END IF;\n\n    PERFORM pg_sleep(0.1);\n    PERFORM pg_stat_clear_snapshot();\n  END LOOP;\n\n  RAISE 'Failed to terminate backends';\n\nEND\n$BODY$;\nBEGIN;\n-- Then create extension there in a txn and make sure we see a scheduler start\nSET client_min_messages = ERROR;\nCREATE EXTENSION timescaledb CASCADE;\nRESET client_min_messages;\nSELECT wait_worker_counts(1,0,0,1);\n wait_worker_counts \n--------------------\n t\n\nCOMMIT;\n-- End our transaction and it should immediately exit because it's a template database.\nSELECT wait_worker_counts(1,0,0,0);\n wait_worker_counts \n--------------------\n t\n\n\\c :TEST_DBNAME_2\n-- Now try creating a DB from a template with the extension already installed.\n-- Make sure we see a scheduler start.\nCREATE DATABASE :TEST_DBNAME;\nSELECT wait_worker_counts(1,1,0,0);\n wait_worker_counts \n--------------------\n t\n\nDROP DATABASE :TEST_DBNAME WITH (FORCE);\n-- Now make sure that there's no race between create database and create extension.\n-- Although to be honest, this race probably wouldn't manifest in this test.\n\\c template1\nDROP EXTENSION timescaledb;\n\\c :TEST_DBNAME_2\nCREATE DATABASE :TEST_DBNAME;\n\\c :TEST_DBNAME\nSET client_min_messages = ERROR;\nCREATE EXTENSION timescaledb;\nRESET client_min_messages;\n\\c :TEST_DBNAME_2\nSELECT wait_worker_counts(1,1,0,0);\n wait_worker_counts \n--------------------\n t\n\n-- test rename database\nCREATE DATABASE db_rename_test;\n\\c db_rename_test :ROLE_SUPERUSER\nSET client_min_messages=error;\nCREATE EXTENSION timescaledb;\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\nSELECT wait_for_bgw_scheduler('db_rename_test');\n wait_for_bgw_scheduler \n------------------------\n BGW Scheduler found.\n\nALTER DATABASE db_rename_test RENAME TO db_rename_test2;\nWARNING:  you need to manually restart any running background workers after this command\nDROP DATABASE db_rename_test2 WITH (FORCE);\n-- test create database with timescaledb database as template\nSELECT wait_for_bgw_scheduler(:'TEST_DBNAME');\n wait_for_bgw_scheduler \n------------------------\n BGW Scheduler found.\n\nCREATE DATABASE db_from_template WITH TEMPLATE :TEST_DBNAME;\nSELECT wait_for_bgw_scheduler(:'TEST_DBNAME');\n wait_for_bgw_scheduler \n------------------------\n BGW Scheduler found.\n\nDROP DATABASE db_from_template WITH (FORCE);\n-- test alter database set tablespace\nSET client_min_messages TO error;\nDROP TABLESPACE IF EXISTS tablespace1;\nRESET client_min_messages;\nCREATE TABLESPACE tablespace1 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE1_PATH;\n-- Stop background worker before we change the tablespace of the database (otherwise, the database might be used)\nSELECT wait_for_bgw_scheduler(:'TEST_DBNAME');\n wait_for_bgw_scheduler \n------------------------\n BGW Scheduler found.\n\n-- Connect to TEST_DBNAME (_timescaledb_functions.stop_background_workers() is not available in TEST_DBNAME_2)\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT _timescaledb_functions.stop_background_workers();\n stop_background_workers \n-------------------------\n t\n\nSELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE backend_type = 'TimescaleDB Background Worker Launcher';\n pg_terminate_backend \n----------------------\n t\n\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\n-- make sure nobody is using it\nREVOKE CONNECT ON DATABASE :TEST_DBNAME FROM public;\nCALL kill_database_backends(:'TEST_DBNAME');\n-- Change tablespace\nALTER DATABASE :TEST_DBNAME SET TABLESPACE tablespace1;\nWARNING:  you may need to manually restart any running background workers after this command\n-- tear down test and clean up additional database\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT _timescaledb_functions.stop_background_workers() \\gset\nREVOKE CONNECT ON DATABASE :TEST_DBNAME_2 FROM public;\nCALL kill_database_backends(:'TEST_DBNAME_2');\nSELECT * FROM pg_stat_activity WHERE datname = :'TEST_DBNAME_2';\n datid | datname | pid | leader_pid | usesysid | usename | application_name | client_addr | client_hostname | client_port | backend_start | xact_start | query_start | state_change | wait_event_type | wait_event | state | backend_xid | backend_xmin | query_id | query | backend_type \n-------+---------+-----+------------+----------+---------+------------------+-------------+-----------------+-------------+---------------+------------+-------------+--------------+-----------------+------------+-------+-------------+--------------+----------+-------+--------------\n\nDROP DATABASE :TEST_DBNAME_2 WITH (force);\n-- Clean up the template database, removing our test utilities etc\n\\c template1\n\\ir include/bgw_launcher_utils_cleanup.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nDROP FUNCTION wait_worker_counts(integer, integer, integer, integer);\nDROP VIEW worker_counts;\nDROP FUNCTION wait_for_bgw_scheduler(name,int,int);\nDROP PROCEDURE kill_database_backends(name);\n"
  },
  {
    "path": "test/expected/c_unit_tests.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION test.time_to_internal_conversion() RETURNS VOID\nAS :MODULE_PATHNAME, 'ts_test_time_to_internal_conversion' LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION test.interval_to_internal_conversion() RETURNS VOID\nAS :MODULE_PATHNAME, 'ts_test_interval_to_internal_conversion' LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION test.adts() RETURNS VOID\nAS :MODULE_PATHNAME, 'ts_test_adts' LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION test.time_utils() RETURNS VOID\nAS :MODULE_PATHNAME, 'ts_test_time_utils' LANGUAGE C;\nCREATE OR REPLACE FUNCTION test.bmslist_utils() RETURNS VOID\nAS :MODULE_PATHNAME, 'ts_test_bmslist_utils' LANGUAGE C;\nCREATE OR REPLACE FUNCTION test.jsonb_utils() RETURNS VOID\nAS :MODULE_PATHNAME, 'ts_test_jsonb_utils' LANGUAGE C;\nCREATE OR REPLACE FUNCTION test.compression_settings() RETURNS VOID\nAS :MODULE_PATHNAME, 'ts_test_compression_settings' LANGUAGE C;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT test.time_to_internal_conversion();\n time_to_internal_conversion \n-----------------------------\n \n\nSELECT test.interval_to_internal_conversion();\n interval_to_internal_conversion \n---------------------------------\n \n\nSELECT test.adts();\n adts \n------\n \n\nSELECT test.time_utils();\n time_utils \n------------\n \n\nSELECT test.bmslist_utils();\n bmslist_utils \n---------------\n \n\nSELECT test.jsonb_utils();\n jsonb_utils \n-------------\n \n\nSELECT test.compression_settings();\n compression_settings \n----------------------\n \n\n"
  },
  {
    "path": "test/expected/catalog_corruption.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n--- Test handling of missing dimension slices\nCREATE TABLE dim_test(time TIMESTAMPTZ, device int);\nSELECT create_hypertable('dim_test', 'time', chunk_time_interval => INTERVAL '1 day');\n   create_hypertable   \n-----------------------\n (1,public,dim_test,t)\n\n-- Create two chunks\nINSERT INTO dim_test values('2000-01-01 00:00:00', 1);\nINSERT INTO dim_test values('2020-01-01 00:00:00', 1);\nSELECT id AS dim_slice_id FROM _timescaledb_catalog.dimension_slice\n  ORDER BY id DESC LIMIT 1\n  \\gset\n-- Delete the dimension slice for the second chunk\nDELETE FROM _timescaledb_catalog.chunk_constraint WHERE dimension_slice_id = :dim_slice_id;\n\\set ON_ERROR_STOP 0\n-- Select data\nSELECT * FROM dim_test;\nERROR:  chunk _hyper_1_2_chunk has no dimension slices\n-- Select data using ordered append\nSELECT * FROM dim_test ORDER BY time;\nERROR:  chunk _hyper_1_2_chunk has no dimension slices\n\\set ON_ERROR_STOP 1\nDROP TABLE dim_test;\n"
  },
  {
    "path": "test/expected/chunk_adaptive.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- test error handling _timescaledb_functions.calculate_chunk_interval\n\\set ON_ERROR_STOP 0\nSELECT _timescaledb_functions.calculate_chunk_interval(0,0,-0);\nERROR:  could not find a matching hypertable for dimension 0\nSELECT _timescaledb_functions.calculate_chunk_interval(1,0,-1);\nERROR:  chunk_target_size must be positive\n\\set ON_ERROR_STOP 1\n-- Valid chunk sizing function for testing\nCREATE OR REPLACE FUNCTION calculate_chunk_interval(\n        dimension_id INTEGER,\n        dimension_coord BIGINT,\n        chunk_target_size BIGINT\n)\n    RETURNS BIGINT LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\nBEGIN\n    RETURN -1;\nEND\n$BODY$;\n-- Chunk sizing function with bad signature\nCREATE OR REPLACE FUNCTION bad_calculate_chunk_interval(\n        dimension_id INTEGER\n)\n    RETURNS BIGINT LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\nBEGIN\n    RETURN -1;\nEND\n$BODY$;\n-- Set a fixed memory cache size to make tests determinstic\n-- (independent of available machine memory)\nSELECT * FROM test.set_memory_cache_size('2GB');\n set_memory_cache_size \n-----------------------\n            2147483648\n\n-- test NULL handling\n\\set ON_ERROR_STOP 0\nSELECT * FROM set_adaptive_chunking(NULL,NULL);\nERROR:  invalid hypertable: cannot be NULL\n\\set ON_ERROR_STOP 1\nCREATE TABLE test_adaptive(time timestamptz, temp float, location int);\n\\set ON_ERROR_STOP 0\n-- Bad signature of sizing func should fail\nSELECT create_hypertable('test_adaptive', 'time',\n                         chunk_target_size => '1MB',\n                         chunk_sizing_func => 'bad_calculate_chunk_interval');\nERROR:  invalid function signature\n\\set ON_ERROR_STOP 1\n-- Setting sizing func with correct signature should work\nSELECT create_hypertable('test_adaptive', 'time',\n                         chunk_target_size => '1MB',\n                         chunk_sizing_func => 'calculate_chunk_interval');\nWARNING:  target chunk size for adaptive chunking is less than 10 MB\nNOTICE:  adaptive chunking is a BETA feature and is not recommended for production deployments\n     create_hypertable      \n----------------------------\n (1,public,test_adaptive,t)\n\nDROP TABLE test_adaptive;\nCREATE TABLE test_adaptive(time timestamptz, temp float, location int);\n-- Size but no explicit func should use default func\nSELECT create_hypertable('test_adaptive', 'time',\n                         chunk_target_size => '1MB',\n                         create_default_indexes => true);\nWARNING:  target chunk size for adaptive chunking is less than 10 MB\nNOTICE:  adaptive chunking is a BETA feature and is not recommended for production deployments\n     create_hypertable      \n----------------------------\n (2,public,test_adaptive,t)\n\nSELECT table_name, chunk_sizing_func_schema, chunk_sizing_func_name, chunk_target_size\nFROM _timescaledb_catalog.hypertable;\n  table_name   | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size \n---------------+--------------------------+--------------------------+-------------------\n test_adaptive | _timescaledb_functions   | calculate_chunk_interval |           1048576\n\n-- Check that adaptive chunking sets a 1 day default chunk time\n-- interval => 86400000000 microseconds\nSELECT * FROM _timescaledb_catalog.dimension;\n id | hypertable_id | column_name |       column_type        | aligned | num_slices | partitioning_func_schema | partitioning_func | interval_length | compress_interval_length | integer_now_func_schema | integer_now_func \n----+---------------+-------------+--------------------------+---------+------------+--------------------------+-------------------+-----------------+--------------------------+-------------------------+------------------\n  2 |             2 | time        | timestamp with time zone | t       |            |                          |                   |     86400000000 |                          |                         | \n\n-- Change the target size\nSELECT * FROM set_adaptive_chunking('test_adaptive', '2MB');\nWARNING:  target chunk size for adaptive chunking is less than 10 MB\n                chunk_sizing_func                | chunk_target_size \n-------------------------------------------------+-------------------\n _timescaledb_functions.calculate_chunk_interval |           2097152\n\nSELECT table_name, chunk_sizing_func_schema, chunk_sizing_func_name, chunk_target_size\nFROM _timescaledb_catalog.hypertable;\n  table_name   | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size \n---------------+--------------------------+--------------------------+-------------------\n test_adaptive | _timescaledb_functions   | calculate_chunk_interval |           2097152\n\n\\set ON_ERROR_STOP 0\n-- Setting NULL func should fail\nSELECT * FROM set_adaptive_chunking('test_adaptive', '1MB', NULL);\nERROR:  invalid chunk sizing function\n\\set ON_ERROR_STOP 1\n-- Setting NULL size disables adaptive chunking\nSELECT * FROM set_adaptive_chunking('test_adaptive', NULL);\n                chunk_sizing_func                | chunk_target_size \n-------------------------------------------------+-------------------\n _timescaledb_functions.calculate_chunk_interval |                 0\n\nSELECT table_name, chunk_sizing_func_schema, chunk_sizing_func_name, chunk_target_size\nFROM _timescaledb_catalog.hypertable;\n  table_name   | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size \n---------------+--------------------------+--------------------------+-------------------\n test_adaptive | _timescaledb_functions   | calculate_chunk_interval |                 0\n\nSELECT * FROM set_adaptive_chunking('test_adaptive', '1MB');\nWARNING:  target chunk size for adaptive chunking is less than 10 MB\n                chunk_sizing_func                | chunk_target_size \n-------------------------------------------------+-------------------\n _timescaledb_functions.calculate_chunk_interval |           1048576\n\n-- Setting size to 'off' should also disable\nSELECT * FROM set_adaptive_chunking('test_adaptive', 'off');\n                chunk_sizing_func                | chunk_target_size \n-------------------------------------------------+-------------------\n _timescaledb_functions.calculate_chunk_interval |                 0\n\nSELECT table_name, chunk_sizing_func_schema, chunk_sizing_func_name, chunk_target_size\nFROM _timescaledb_catalog.hypertable;\n  table_name   | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size \n---------------+--------------------------+--------------------------+-------------------\n test_adaptive | _timescaledb_functions   | calculate_chunk_interval |                 0\n\n-- Setting 0 size should also disable\nSELECT * FROM set_adaptive_chunking('test_adaptive', '0MB');\n                chunk_sizing_func                | chunk_target_size \n-------------------------------------------------+-------------------\n _timescaledb_functions.calculate_chunk_interval |                 0\n\nSELECT table_name, chunk_sizing_func_schema, chunk_sizing_func_name, chunk_target_size\nFROM _timescaledb_catalog.hypertable;\n  table_name   | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size \n---------------+--------------------------+--------------------------+-------------------\n test_adaptive | _timescaledb_functions   | calculate_chunk_interval |                 0\n\nSELECT * FROM set_adaptive_chunking('test_adaptive', '1MB');\nWARNING:  target chunk size for adaptive chunking is less than 10 MB\n                chunk_sizing_func                | chunk_target_size \n-------------------------------------------------+-------------------\n _timescaledb_functions.calculate_chunk_interval |           1048576\n\n-- No warning about small target size if > 10MB\nSELECT * FROM set_adaptive_chunking('test_adaptive', '11MB');\n                chunk_sizing_func                | chunk_target_size \n-------------------------------------------------+-------------------\n _timescaledb_functions.calculate_chunk_interval |          11534336\n\n-- Setting size to 'estimate' should also estimate size\nSELECT * FROM set_adaptive_chunking('test_adaptive', 'estimate');\n                chunk_sizing_func                | chunk_target_size \n-------------------------------------------------+-------------------\n _timescaledb_functions.calculate_chunk_interval |        1932735283\n\nSELECT table_name, chunk_sizing_func_schema, chunk_sizing_func_name, chunk_target_size\nFROM _timescaledb_catalog.hypertable;\n  table_name   | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size \n---------------+--------------------------+--------------------------+-------------------\n test_adaptive | _timescaledb_functions   | calculate_chunk_interval |        1932735283\n\n-- Use a lower memory setting to test that the calculated chunk_target_size is reduced\nSELECT * FROM test.set_memory_cache_size('512MB');\n set_memory_cache_size \n-----------------------\n             536870912\n\nSELECT * FROM set_adaptive_chunking('test_adaptive', 'estimate');\n                chunk_sizing_func                | chunk_target_size \n-------------------------------------------------+-------------------\n _timescaledb_functions.calculate_chunk_interval |         483183820\n\nSELECT table_name, chunk_sizing_func_schema, chunk_sizing_func_name, chunk_target_size\nFROM _timescaledb_catalog.hypertable;\n  table_name   | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size \n---------------+--------------------------+--------------------------+-------------------\n test_adaptive | _timescaledb_functions   | calculate_chunk_interval |         483183820\n\n-- Reset memory settings\nSELECT * FROM test.set_memory_cache_size('2GB');\n set_memory_cache_size \n-----------------------\n            2147483648\n\n-- Set a reasonable test value\nSELECT * FROM set_adaptive_chunking('test_adaptive', '1MB');\nWARNING:  target chunk size for adaptive chunking is less than 10 MB\n                chunk_sizing_func                | chunk_target_size \n-------------------------------------------------+-------------------\n _timescaledb_functions.calculate_chunk_interval |           1048576\n\n-- Show the interval length before and after adaptation\nSELECT id, hypertable_id, interval_length FROM _timescaledb_catalog.dimension;\n id | hypertable_id | interval_length \n----+---------------+-----------------\n  2 |             2 |     86400000000\n\n-- Generate data to create chunks. We use the hash of the time value\n-- to get determinstic location IDs so that we always spread these\n-- values the same way across space partitions\nINSERT INTO test_adaptive\nSELECT time, random() * 35, _timescaledb_functions.get_partition_hash(time) FROM\ngenerate_series('2017-03-07T18:18:03+00'::timestamptz - interval '175 days',\n                '2017-03-07T18:18:03+00'::timestamptz,\n                '2 minutes') as time;\nSELECT chunk_name, primary_dimension, range_start, range_end\nFROM  timescaledb_information.chunks\nWHERE hypertable_name = 'test_adaptive' ORDER BY chunk_name;\n    chunk_name     | primary_dimension |             range_start             |              range_end              \n-------------------+-------------------+-------------------------------------+-------------------------------------\n _hyper_2_10_chunk | time              | Fri Sep 23 22:08:15.728855 2016 PDT | Sat Oct 01 13:16:09.024252 2016 PDT\n _hyper_2_11_chunk | time              | Sat Oct 01 13:16:09.024252 2016 PDT | Fri Oct 14 03:19:44.231212 2016 PDT\n _hyper_2_12_chunk | time              | Fri Oct 14 03:19:44.231212 2016 PDT | Wed Oct 26 19:20:54.4938 2016 PDT\n _hyper_2_13_chunk | time              | Wed Oct 26 19:20:54.4938 2016 PDT   | Fri Nov 04 04:03:56.248528 2016 PDT\n _hyper_2_14_chunk | time              | Fri Nov 04 04:03:56.248528 2016 PDT | Fri Nov 18 21:58:20.411232 2016 PST\n _hyper_2_15_chunk | time              | Fri Nov 18 21:58:20.411232 2016 PST | Sat Dec 03 16:52:44.573936 2016 PST\n _hyper_2_16_chunk | time              | Sat Dec 03 16:52:44.573936 2016 PST | Sun Dec 18 11:47:08.73664 2016 PST\n _hyper_2_17_chunk | time              | Sun Dec 18 11:47:08.73664 2016 PST  | Mon Jan 02 06:41:32.899344 2017 PST\n _hyper_2_18_chunk | time              | Mon Jan 02 06:41:32.899344 2017 PST | Tue Jan 17 01:35:57.062048 2017 PST\n _hyper_2_19_chunk | time              | Tue Jan 17 01:35:57.062048 2017 PST | Tue Jan 31 20:30:21.224752 2017 PST\n _hyper_2_1_chunk  | time              | Mon Sep 12 17:00:00 2016 PDT        | Tue Sep 13 17:00:00 2016 PDT\n _hyper_2_20_chunk | time              | Tue Jan 31 20:30:21.224752 2017 PST | Wed Feb 15 15:24:45.387456 2017 PST\n _hyper_2_21_chunk | time              | Wed Feb 15 15:24:45.387456 2017 PST | Thu Mar 02 10:19:09.55016 2017 PST\n _hyper_2_22_chunk | time              | Thu Mar 02 10:19:09.55016 2017 PST  | Fri Mar 17 06:13:33.712864 2017 PDT\n _hyper_2_2_chunk  | time              | Tue Sep 13 17:00:00 2016 PDT        | Wed Sep 14 17:00:00 2016 PDT\n _hyper_2_3_chunk  | time              | Wed Sep 14 17:00:00 2016 PDT        | Thu Sep 15 17:00:00 2016 PDT\n _hyper_2_4_chunk  | time              | Thu Sep 15 17:00:00 2016 PDT        | Fri Sep 16 15:02:54.2208 2016 PDT\n _hyper_2_5_chunk  | time              | Fri Sep 16 15:02:54.2208 2016 PDT   | Sun Sep 18 03:12:14.342144 2016 PDT\n _hyper_2_6_chunk  | time              | Sun Sep 18 03:12:14.342144 2016 PDT | Mon Sep 19 15:21:34.463488 2016 PDT\n _hyper_2_7_chunk  | time              | Mon Sep 19 15:21:34.463488 2016 PDT | Wed Sep 21 03:30:54.584832 2016 PDT\n _hyper_2_8_chunk  | time              | Wed Sep 21 03:30:54.584832 2016 PDT | Thu Sep 22 03:45:14.901568 2016 PDT\n _hyper_2_9_chunk  | time              | Thu Sep 22 03:45:14.901568 2016 PDT | Fri Sep 23 22:08:15.728855 2016 PDT\n\n-- Do same thing without an index on the time column. This affects\n-- both the calculation of fill-factor of the chunk and its size\nCREATE TABLE test_adaptive_no_index(time timestamptz, temp float, location int);\n-- Size but no explicit func should use default func\n-- No default indexes should warn and use heap scan for min and max\nSELECT create_hypertable('test_adaptive_no_index', 'time',\n                         chunk_target_size => '1MB',\n                         create_default_indexes => false);\nWARNING:  target chunk size for adaptive chunking is less than 10 MB\nWARNING:  no index on \"time\" found for adaptive chunking on hypertable \"test_adaptive_no_index\"\nNOTICE:  adaptive chunking is a BETA feature and is not recommended for production deployments\n          create_hypertable          \n-------------------------------------\n (3,public,test_adaptive_no_index,t)\n\nSELECT id, hypertable_id, interval_length FROM _timescaledb_catalog.dimension;\n id | hypertable_id | interval_length \n----+---------------+-----------------\n  2 |             2 |   1277664162704\n  3 |             3 |     86400000000\n\nINSERT INTO test_adaptive_no_index\nSELECT time, random() * 35, _timescaledb_functions.get_partition_hash(time) FROM\ngenerate_series('2017-03-07T18:18:03+00'::timestamptz - interval '175 days',\n                '2017-03-07T18:18:03+00'::timestamptz,\n                '2 minutes') as time;\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_23_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_23_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_24_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_23_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_24_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_25_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_24_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_25_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_26_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_25_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_26_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_27_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_26_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_27_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_28_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_27_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_28_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_29_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_28_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_29_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_30_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_29_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_30_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_31_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_30_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_31_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_32_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_31_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_32_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_33_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_32_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_33_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_34_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_33_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_34_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_35_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_34_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_35_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_36_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_35_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_36_chunk\"\nWARNING:  no index on \"time\" found for adaptive chunking on chunk \"_hyper_3_37_chunk\"\nSELECT chunk_name, primary_dimension, range_start, range_end\nFROM  timescaledb_information.chunks\nWHERE hypertable_name = 'test_adaptive_no_index' ORDER BY chunk_name;\n    chunk_name     | primary_dimension |             range_start             |              range_end              \n-------------------+-------------------+-------------------------------------+-------------------------------------\n _hyper_3_23_chunk | time              | Mon Sep 12 17:00:00 2016 PDT        | Tue Sep 13 17:00:00 2016 PDT\n _hyper_3_24_chunk | time              | Tue Sep 13 17:00:00 2016 PDT        | Wed Sep 14 17:00:00 2016 PDT\n _hyper_3_25_chunk | time              | Wed Sep 14 17:00:00 2016 PDT        | Thu Sep 15 17:00:00 2016 PDT\n _hyper_3_26_chunk | time              | Thu Sep 15 17:00:00 2016 PDT        | Sun Sep 18 02:18:45.310968 2016 PDT\n _hyper_3_27_chunk | time              | Sun Sep 18 02:18:45.310968 2016 PDT | Sun Sep 18 06:20:21.359312 2016 PDT\n _hyper_3_28_chunk | time              | Sun Sep 18 06:20:21.359312 2016 PDT | Wed Sep 21 08:25:00.957966 2016 PDT\n _hyper_3_29_chunk | time              | Wed Sep 21 08:25:00.957966 2016 PDT | Thu Sep 22 03:26:42.599807 2016 PDT\n _hyper_3_30_chunk | time              | Thu Sep 22 03:26:42.599807 2016 PDT | Sun Sep 25 18:03:30.59359 2016 PDT\n _hyper_3_31_chunk | time              | Sun Sep 25 18:03:30.59359 2016 PDT  | Sat Oct 08 05:32:02.75732 2016 PDT\n _hyper_3_32_chunk | time              | Sat Oct 08 05:32:02.75732 2016 PDT  | Mon Oct 31 07:33:42.652938 2016 PDT\n _hyper_3_33_chunk | time              | Mon Oct 31 07:33:42.652938 2016 PDT | Wed Nov 23 08:35:22.548556 2016 PST\n _hyper_3_34_chunk | time              | Wed Nov 23 08:35:22.548556 2016 PST | Thu Dec 15 09:48:28.1888 2016 PST\n _hyper_3_35_chunk | time              | Thu Dec 15 09:48:28.1888 2016 PST   | Wed Jan 11 04:57:38.357845 2017 PST\n _hyper_3_36_chunk | time              | Wed Jan 11 04:57:38.357845 2017 PST | Tue Feb 07 00:06:48.52689 2017 PST\n _hyper_3_37_chunk | time              | Tue Feb 07 00:06:48.52689 2017 PST  | Sun Mar 05 19:15:58.695935 2017 PST\n _hyper_3_38_chunk | time              | Sun Mar 05 19:15:58.695935 2017 PST | Sat Apr 01 15:25:08.86498 2017 PDT\n\n-- Test added to check that the correct index (i.e. time index) is being used\n-- to find the min and max. Previously a bug selected the first index listed,\n-- which in this case is location rather than time and therefore could return\n-- the wrong min and max if items at the start and end of the index did not have\n-- the correct min and max timestamps.\n--\n-- In this test, we create chunks with a lot of locations with only one reading\n-- that is at the beginning of the time frame, and then one location in the middle\n-- of the range that has two readings, one that is the same as the others and one\n-- that is larger. The algorithm should use these two readings for min & max; however,\n-- if it's broken (as it was before), it would choose just the reading that is common\n-- to all the locations.\nCREATE TABLE test_adaptive_correct_index(time timestamptz, temp float, location int);\nSELECT create_hypertable('test_adaptive_correct_index', 'time',\n                         chunk_target_size => '100MB',\n                         chunk_time_interval => 86400000000,\n                         create_default_indexes => false);\nWARNING:  no index on \"time\" found for adaptive chunking on hypertable \"test_adaptive_correct_index\"\nNOTICE:  adaptive chunking is a BETA feature and is not recommended for production deployments\n            create_hypertable             \n------------------------------------------\n (4,public,test_adaptive_correct_index,t)\n\nCREATE INDEX ON test_adaptive_correct_index(location);\nCREATE INDEX ON test_adaptive_correct_index(time DESC);\n-- First chunk\nINSERT INTO test_adaptive_correct_index\nSELECT '2018-01-01T00:00:00+00'::timestamptz, val, val + 1 FROM\ngenerate_series(1, 1000) as val;\nINSERT INTO test_adaptive_correct_index\nSELECT time, 0.0, '1500' FROM\ngenerate_series('2018-01-01T00:00:00+00'::timestamptz,\n                '2018-01-01T20:00:00+00'::timestamptz,\n                '10 hours') as time;\nINSERT INTO test_adaptive_correct_index\nSELECT '2018-01-01T00:00:00+00'::timestamptz, val, val + 1 FROM\ngenerate_series(2001, 3000) as val;\n-- Second chunk\nINSERT INTO test_adaptive_correct_index\nSELECT '2018-01-02T00:00:00+00'::timestamptz, val, val + 1 FROM\ngenerate_series(1, 1000) as val;\nINSERT INTO test_adaptive_correct_index\nSELECT time, 0.0, '1500' FROM\ngenerate_series('2018-01-02T00:00:00+00'::timestamptz,\n                '2018-01-02T20:00:00+00'::timestamptz,\n                '10 hours') as time;\nINSERT INTO test_adaptive_correct_index\nSELECT '2018-01-02T00:00:00+00'::timestamptz, val, val + 1 FROM\ngenerate_series(2001, 3000) as val;\n-- Third chunk\nINSERT INTO test_adaptive_correct_index\nSELECT '2018-01-03T00:00:00+00'::timestamptz, val, val + 1 FROM\ngenerate_series(1, 1000) as val;\nINSERT INTO test_adaptive_correct_index\nSELECT time, 0.0, '1500' FROM\ngenerate_series('2018-01-03T00:00:00+00'::timestamptz,\n                '2018-01-03T20:00:00+00'::timestamptz,\n                '10 hours') as time;\nINSERT INTO test_adaptive_correct_index\nSELECT '2018-01-03T00:00:00+00'::timestamptz, val, val + 1 FROM\ngenerate_series(2001, 3000) as val;\n-- This should be the start of the fourth chunk\nINSERT INTO test_adaptive_correct_index\nSELECT '2018-01-04T00:00:00+00'::timestamptz, val, val + 1 FROM\ngenerate_series(1, 1000) as val;\nINSERT INTO test_adaptive_correct_index\nSELECT time, 0.0, '1500' FROM\ngenerate_series('2018-01-04T00:00:00+00'::timestamptz,\n                '2018-01-04T20:00:00+00'::timestamptz,\n                '10 hours') as time;\nINSERT INTO test_adaptive_correct_index\nSELECT '2018-01-04T00:00:00+00'::timestamptz, val, val + 1 FROM\ngenerate_series(2001, 3000) as val;\n-- If working correctly, this goes in the 4th chunk, otherwise its a separate 5th chunk\nINSERT INTO test_adaptive_correct_index\nSELECT '2018-01-05T00:00:00+00'::timestamptz, val, val + 1 FROM\ngenerate_series(1, 1000) as val;\nINSERT INTO test_adaptive_correct_index\nSELECT time, 0.0, '1500' FROM\ngenerate_series('2018-01-05T00:00:00+00'::timestamptz,\n                '2018-01-05T20:00:00+00'::timestamptz,\n                '10 hours') as time;\nINSERT INTO test_adaptive_correct_index\nSELECT '2018-01-05T00:00:00+00'::timestamptz, val, val + 1 FROM\ngenerate_series(2001, 3000) as val;\n-- This should show 4 chunks, rather than 5\nSELECT count(*)\nFROM  timescaledb_information.chunks\nWHERE hypertable_name = 'test_adaptive_correct_index';\n count \n-------\n     4\n\n-- The interval_length should no longer be 86400000000 for our hypertable, so 3rd column so be true.\n-- Note: the exact interval_length is non-deterministic, so we can't use its actual value for tests\nSELECT id, hypertable_id, interval_length > 86400000000 FROM _timescaledb_catalog.dimension;\n id | hypertable_id | ?column? \n----+---------------+----------\n  2 |             2 | t\n  3 |             3 | t\n  4 |             4 | t\n\n-- Drop because it's size and estimated chunk_interval is non-deterministic so\n-- we don't want to make other tests flaky.\nDROP TABLE test_adaptive_correct_index;\n-- Test with space partitioning. This might affect the estimation\n-- since there are more chunks in the same time interval and space\n-- chunks might be unevenly filled.\nCREATE TABLE test_adaptive_space(time timestamptz, temp float, location int);\nSELECT create_hypertable('test_adaptive_space', 'time', 'location', 2,\n                         chunk_target_size => '1MB',\n                         create_default_indexes => true);\nWARNING:  target chunk size for adaptive chunking is less than 10 MB\nNOTICE:  adaptive chunking is a BETA feature and is not recommended for production deployments\n        create_hypertable         \n----------------------------------\n (5,public,test_adaptive_space,t)\n\nSELECT id, hypertable_id, interval_length FROM _timescaledb_catalog.dimension;\n id | hypertable_id | interval_length \n----+---------------+-----------------\n  2 |             2 |   1277664162704\n  3 |             3 |   2315350169045\n  5 |             5 |     86400000000\n  6 |             5 |                \n\nINSERT INTO test_adaptive_space\nSELECT time, random() * 35, _timescaledb_functions.get_partition_hash(time) FROM\ngenerate_series('2017-03-07T18:18:03+00'::timestamptz - interval '175 days',\n                '2017-03-07T18:18:03+00'::timestamptz,\n                '2 minutes') as time;\n\\x\nSELECT chunk_name, range_start, range_end\nFROM  timescaledb_information.chunks\nWHERE hypertable_name = 'test_adaptive_space' ORDER BY chunk_name;\n-[ RECORD 1 ]------------------------------------\nchunk_name  | _hyper_5_43_chunk\nrange_start | Mon Sep 12 17:00:00 2016 PDT\nrange_end   | Tue Sep 13 17:00:00 2016 PDT\n-[ RECORD 2 ]------------------------------------\nchunk_name  | _hyper_5_44_chunk\nrange_start | Mon Sep 12 17:00:00 2016 PDT\nrange_end   | Tue Sep 13 17:00:00 2016 PDT\n-[ RECORD 3 ]------------------------------------\nchunk_name  | _hyper_5_45_chunk\nrange_start | Tue Sep 13 17:00:00 2016 PDT\nrange_end   | Wed Sep 14 17:00:00 2016 PDT\n-[ RECORD 4 ]------------------------------------\nchunk_name  | _hyper_5_46_chunk\nrange_start | Tue Sep 13 17:00:00 2016 PDT\nrange_end   | Wed Sep 14 17:00:00 2016 PDT\n-[ RECORD 5 ]------------------------------------\nchunk_name  | _hyper_5_47_chunk\nrange_start | Wed Sep 14 17:00:00 2016 PDT\nrange_end   | Thu Sep 15 11:47:51.47376 2016 PDT\n-[ RECORD 6 ]------------------------------------\nchunk_name  | _hyper_5_48_chunk\nrange_start | Wed Sep 14 17:00:00 2016 PDT\nrange_end   | Thu Sep 15 11:47:51.47376 2016 PDT\n-[ RECORD 7 ]------------------------------------\nchunk_name  | _hyper_5_49_chunk\nrange_start | Thu Sep 15 11:47:51.47376 2016 PDT\nrange_end   | Sat Sep 17 02:40:49.182352 2016 PDT\n-[ RECORD 8 ]------------------------------------\nchunk_name  | _hyper_5_50_chunk\nrange_start | Thu Sep 15 11:47:51.47376 2016 PDT\nrange_end   | Sat Sep 17 02:40:49.182352 2016 PDT\n-[ RECORD 9 ]------------------------------------\nchunk_name  | _hyper_5_51_chunk\nrange_start | Sat Sep 17 02:40:49.182352 2016 PDT\nrange_end   | Sun Sep 18 17:33:46.890944 2016 PDT\n-[ RECORD 10 ]-----------------------------------\nchunk_name  | _hyper_5_52_chunk\nrange_start | Sat Sep 17 02:40:49.182352 2016 PDT\nrange_end   | Sun Sep 18 17:33:46.890944 2016 PDT\n-[ RECORD 11 ]-----------------------------------\nchunk_name  | _hyper_5_53_chunk\nrange_start | Sun Sep 18 17:33:46.890944 2016 PDT\nrange_end   | Sun Sep 18 20:35:55.67676 2016 PDT\n-[ RECORD 12 ]-----------------------------------\nchunk_name  | _hyper_5_54_chunk\nrange_start | Sun Sep 18 17:33:46.890944 2016 PDT\nrange_end   | Sun Sep 18 20:35:55.67676 2016 PDT\n-[ RECORD 13 ]-----------------------------------\nchunk_name  | _hyper_5_55_chunk\nrange_start | Sun Sep 18 20:35:55.67676 2016 PDT\nrange_end   | Tue Sep 20 18:46:40.16883 2016 PDT\n-[ RECORD 14 ]-----------------------------------\nchunk_name  | _hyper_5_56_chunk\nrange_start | Sun Sep 18 20:35:55.67676 2016 PDT\nrange_end   | Tue Sep 20 18:46:40.16883 2016 PDT\n-[ RECORD 15 ]-----------------------------------\nchunk_name  | _hyper_5_57_chunk\nrange_start | Tue Sep 20 18:46:40.16883 2016 PDT\nrange_end   | Sun Oct 02 16:44:29.071032 2016 PDT\n-[ RECORD 16 ]-----------------------------------\nchunk_name  | _hyper_5_58_chunk\nrange_start | Tue Sep 20 18:46:40.16883 2016 PDT\nrange_end   | Sun Oct 02 16:44:29.071032 2016 PDT\n-[ RECORD 17 ]-----------------------------------\nchunk_name  | _hyper_5_59_chunk\nrange_start | Sun Oct 02 16:44:29.071032 2016 PDT\nrange_end   | Tue Oct 11 00:37:03.738979 2016 PDT\n-[ RECORD 18 ]-----------------------------------\nchunk_name  | _hyper_5_60_chunk\nrange_start | Sun Oct 02 16:44:29.071032 2016 PDT\nrange_end   | Tue Oct 11 00:37:03.738979 2016 PDT\n-[ RECORD 19 ]-----------------------------------\nchunk_name  | _hyper_5_61_chunk\nrange_start | Tue Oct 11 00:37:03.738979 2016 PDT\nrange_end   | Thu Oct 27 03:05:25.740618 2016 PDT\n-[ RECORD 20 ]-----------------------------------\nchunk_name  | _hyper_5_62_chunk\nrange_start | Tue Oct 11 00:37:03.738979 2016 PDT\nrange_end   | Thu Oct 27 03:05:25.740618 2016 PDT\n-[ RECORD 21 ]-----------------------------------\nchunk_name  | _hyper_5_63_chunk\nrange_start | Thu Oct 27 03:05:25.740618 2016 PDT\nrange_end   | Sun Nov 13 12:38:49.541703 2016 PST\n-[ RECORD 22 ]-----------------------------------\nchunk_name  | _hyper_5_64_chunk\nrange_start | Thu Oct 27 03:05:25.740618 2016 PDT\nrange_end   | Sun Nov 13 12:38:49.541703 2016 PST\n-[ RECORD 23 ]-----------------------------------\nchunk_name  | _hyper_5_65_chunk\nrange_start | Sun Nov 13 12:38:49.541703 2016 PST\nrange_end   | Fri Dec 02 17:45:40.237036 2016 PST\n-[ RECORD 24 ]-----------------------------------\nchunk_name  | _hyper_5_66_chunk\nrange_start | Sun Nov 13 12:38:49.541703 2016 PST\nrange_end   | Fri Dec 02 17:45:40.237036 2016 PST\n-[ RECORD 25 ]-----------------------------------\nchunk_name  | _hyper_5_67_chunk\nrange_start | Fri Dec 02 17:45:40.237036 2016 PST\nrange_end   | Wed Dec 21 22:52:30.932369 2016 PST\n-[ RECORD 26 ]-----------------------------------\nchunk_name  | _hyper_5_68_chunk\nrange_start | Fri Dec 02 17:45:40.237036 2016 PST\nrange_end   | Wed Dec 21 22:52:30.932369 2016 PST\n-[ RECORD 27 ]-----------------------------------\nchunk_name  | _hyper_5_69_chunk\nrange_start | Wed Dec 21 22:52:30.932369 2016 PST\nrange_end   | Tue Jan 10 03:59:21.627702 2017 PST\n-[ RECORD 28 ]-----------------------------------\nchunk_name  | _hyper_5_70_chunk\nrange_start | Wed Dec 21 22:52:30.932369 2016 PST\nrange_end   | Tue Jan 10 03:59:21.627702 2017 PST\n-[ RECORD 29 ]-----------------------------------\nchunk_name  | _hyper_5_71_chunk\nrange_start | Tue Jan 10 03:59:21.627702 2017 PST\nrange_end   | Sun Jan 29 09:06:12.323035 2017 PST\n-[ RECORD 30 ]-----------------------------------\nchunk_name  | _hyper_5_72_chunk\nrange_start | Tue Jan 10 03:59:21.627702 2017 PST\nrange_end   | Sun Jan 29 09:06:12.323035 2017 PST\n-[ RECORD 31 ]-----------------------------------\nchunk_name  | _hyper_5_73_chunk\nrange_start | Sun Jan 29 09:06:12.323035 2017 PST\nrange_end   | Fri Feb 17 14:13:03.018368 2017 PST\n-[ RECORD 32 ]-----------------------------------\nchunk_name  | _hyper_5_74_chunk\nrange_start | Sun Jan 29 09:06:12.323035 2017 PST\nrange_end   | Fri Feb 17 14:13:03.018368 2017 PST\n-[ RECORD 33 ]-----------------------------------\nchunk_name  | _hyper_5_75_chunk\nrange_start | Fri Feb 17 14:13:03.018368 2017 PST\nrange_end   | Wed Mar 08 19:19:53.713701 2017 PST\n-[ RECORD 34 ]-----------------------------------\nchunk_name  | _hyper_5_76_chunk\nrange_start | Fri Feb 17 14:13:03.018368 2017 PST\nrange_end   | Wed Mar 08 19:19:53.713701 2017 PST\n\nSELECT *\nFROM  timescaledb_information.dimensions\nWHERE hypertable_name = 'test_adaptive_space' ORDER BY dimension_number;\n-[ RECORD 1 ]-----+----------------------------------------\nhypertable_schema | public\nhypertable_name   | test_adaptive_space\ndimension_number  | 1\ncolumn_name       | time\ncolumn_type       | timestamp with time zone\ndimension_type    | Time\ntime_interval     | @ 19 days 5 hours 6 mins 50.695333 secs\ninteger_interval  | \ninteger_now_func  | \nnum_partitions    | \n-[ RECORD 2 ]-----+----------------------------------------\nhypertable_schema | public\nhypertable_name   | test_adaptive_space\ndimension_number  | 2\ncolumn_name       | location\ncolumn_type       | integer\ndimension_type    | Space\ntime_interval     | \ninteger_interval  | \ninteger_now_func  | \nnum_partitions    | 2\n\n\\x\nSELECT *\nFROM chunks_detailed_size('test_adaptive_space') ORDER BY chunk_name;\n     chunk_schema      |    chunk_name     | table_bytes | index_bytes | toast_bytes | total_bytes | node_name \n-----------------------+-------------------+-------------+-------------+-------------+-------------+-----------\n _timescaledb_internal | _hyper_5_43_chunk |        8192 |       32768 |           0 |       40960 | \n _timescaledb_internal | _hyper_5_44_chunk |        8192 |       32768 |           0 |       40960 | \n _timescaledb_internal | _hyper_5_45_chunk |       49152 |       57344 |           0 |      106496 | \n _timescaledb_internal | _hyper_5_46_chunk |       49152 |       57344 |           0 |      106496 | \n _timescaledb_internal | _hyper_5_47_chunk |       40960 |       49152 |           0 |       90112 | \n _timescaledb_internal | _hyper_5_48_chunk |       40960 |       32768 |           0 |       73728 | \n _timescaledb_internal | _hyper_5_49_chunk |       57344 |       81920 |           0 |      139264 | \n _timescaledb_internal | _hyper_5_50_chunk |       57344 |       81920 |           0 |      139264 | \n _timescaledb_internal | _hyper_5_51_chunk |       57344 |       81920 |           0 |      139264 | \n _timescaledb_internal | _hyper_5_52_chunk |       57344 |       81920 |           0 |      139264 | \n _timescaledb_internal | _hyper_5_53_chunk |        8192 |       32768 |           0 |       40960 | \n _timescaledb_internal | _hyper_5_54_chunk |        8192 |       32768 |           0 |       40960 | \n _timescaledb_internal | _hyper_5_55_chunk |       65536 |      106496 |           0 |      172032 | \n _timescaledb_internal | _hyper_5_56_chunk |       65536 |       98304 |           0 |      163840 | \n _timescaledb_internal | _hyper_5_57_chunk |      253952 |      360448 |           0 |      614400 | \n _timescaledb_internal | _hyper_5_58_chunk |      253952 |      368640 |           0 |      622592 | \n _timescaledb_internal | _hyper_5_59_chunk |      180224 |      303104 |           0 |      483328 | \n _timescaledb_internal | _hyper_5_60_chunk |      188416 |      303104 |           0 |      491520 | \n _timescaledb_internal | _hyper_5_61_chunk |      327680 |      540672 |           0 |      868352 | \n _timescaledb_internal | _hyper_5_62_chunk |      327680 |      532480 |           0 |      860160 | \n _timescaledb_internal | _hyper_5_63_chunk |      360448 |      581632 |           0 |      942080 | \n _timescaledb_internal | _hyper_5_64_chunk |      352256 |      589824 |           0 |      942080 | \n _timescaledb_internal | _hyper_5_65_chunk |      385024 |      598016 |           0 |      983040 | \n _timescaledb_internal | _hyper_5_66_chunk |      393216 |      614400 |           0 |     1007616 | \n _timescaledb_internal | _hyper_5_67_chunk |      385024 |      598016 |           0 |      983040 | \n _timescaledb_internal | _hyper_5_68_chunk |      393216 |      598016 |           0 |      991232 | \n _timescaledb_internal | _hyper_5_69_chunk |      393216 |      622592 |           0 |     1015808 | \n _timescaledb_internal | _hyper_5_70_chunk |      385024 |      606208 |           0 |      991232 | \n _timescaledb_internal | _hyper_5_71_chunk |      385024 |      614400 |           0 |      999424 | \n _timescaledb_internal | _hyper_5_72_chunk |      393216 |      622592 |           0 |     1015808 | \n _timescaledb_internal | _hyper_5_73_chunk |      393216 |      614400 |           0 |     1007616 | \n _timescaledb_internal | _hyper_5_74_chunk |      385024 |      614400 |           0 |      999424 | \n _timescaledb_internal | _hyper_5_75_chunk |      360448 |      581632 |           0 |      942080 | \n _timescaledb_internal | _hyper_5_76_chunk |      368640 |      598016 |           0 |      966656 | \n\nSELECT id, hypertable_id, interval_length FROM _timescaledb_catalog.dimension;\n id | hypertable_id | interval_length \n----+---------------+-----------------\n  2 |             2 |   1277664162704\n  3 |             3 |   2315350169045\n  6 |             5 |                \n  5 |             5 |   1660010695333\n\n-- A previous version stopped working as soon as hypertable_id stopped being\n-- equal to dimension_id (i.e., there was a hypertable with more than 1 dimension).\n-- This test comes after test_adaptive_space, which has 2 dimensions, and makes\n-- sure that it still works.\nCREATE TABLE test_adaptive_after_multiple_dims(time timestamptz, temp float, location int);\nSELECT create_hypertable('test_adaptive_after_multiple_dims', 'time',\n                         chunk_target_size => '100MB',\n                         create_default_indexes => true);\nNOTICE:  adaptive chunking is a BETA feature and is not recommended for production deployments\n               create_hypertable                \n------------------------------------------------\n (6,public,test_adaptive_after_multiple_dims,t)\n\nINSERT INTO test_adaptive_after_multiple_dims VALUES('2018-01-01T00:00:00+00'::timestamptz, 0.0, 5);\n\\c  :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n\\set ON_ERROR_STOP 0\nSELECT * FROM set_adaptive_chunking('test_adaptive', '2MB');\nERROR:  must be owner of hypertable \"test_adaptive\"\n\\set ON_ERROR_STOP 1\n\\c  :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n-- Now make sure renaming schema gets propagated to the func_schema\nDROP TABLE test_adaptive;\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA IF NOT EXISTS my_chunk_func_schema;\nCREATE OR REPLACE FUNCTION my_chunk_func_schema.calculate_chunk_interval(\n        dimension_id INTEGER,\n        dimension_coord BIGINT,\n        chunk_target_size BIGINT\n)\n    RETURNS BIGINT LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\nBEGIN\n    RETURN 2;\nEND\n$BODY$;\nCREATE TABLE test_adaptive(time timestamptz, temp float, location int);\nSELECT create_hypertable('test_adaptive', 'time',\n                         chunk_target_size => '1MB',\n                         chunk_sizing_func => 'my_chunk_func_schema.calculate_chunk_interval');\nWARNING:  target chunk size for adaptive chunking is less than 10 MB\nNOTICE:  adaptive chunking is a BETA feature and is not recommended for production deployments\n     create_hypertable      \n----------------------------\n (7,public,test_adaptive,t)\n\nALTER SCHEMA my_chunk_func_schema RENAME TO new_chunk_func_schema;\nINSERT INTO test_adaptive VALUES (now(), 1.0, 1);\n"
  },
  {
    "path": "test/expected/chunk_merge.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_merge_chunks_across_dimension(chunk REGCLASS, merge_chunk REGCLASS, dimension_id INTEGER)\n RETURNS VOID\n    AS :MODULE_PATHNAME, 'ts_test_merge_chunks_across_dimension' LANGUAGE C VOLATILE;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nCREATE TABLE test1 (\"Time\" timestamptz, i integer, value integer);\nSELECT table_name FROM Create_hypertable('test1', 'Time', chunk_time_interval=> INTERVAL '1 hour');\nNOTICE:  adding not-null constraint to column \"Time\"\n table_name \n------------\n test1\n(1 row)\n\nSELECT table_name FROM  add_dimension('test1', 'i', chunk_time_interval=> 1);\nNOTICE:  adding not-null constraint to column \"i\"\n table_name \n------------\n test1\n(1 row)\n\n-- This creates chunks 1 - 3 on first hypertable.\nINSERT INTO test1 SELECT t, 1, 1.0 FROM generate_series('2018-03-02 1:00'::TIMESTAMPTZ, '2018-03-02 3:00', '1 minute') t;\n-- This creates chunks 4 - 6 on first hypertable.\nINSERT INTO test1 SELECT t, 2, 1.0 FROM generate_series('2018-03-02 1:00'::TIMESTAMPTZ, '2018-03-02 3:00', '1 minute') t;\nCREATE TABLE test2 (\"Time\" timestamptz, i integer, value integer);\nSELECT table_name FROM Create_hypertable('test2', 'Time', chunk_time_interval=> INTERVAL '1 hour');\nNOTICE:  adding not-null constraint to column \"Time\"\n table_name \n------------\n test2\n(1 row)\n\n-- This creates chunks 7 - 9 on second hypertable.\nINSERT INTO test2 SELECT t, 1, 1.0 FROM generate_series('2018-03-02 1:00'::TIMESTAMPTZ, '2018-03-02 3:00', '1 minute') t;\nSELECT * FROM _timescaledb_catalog.chunk;\n id | hypertable_id |      schema_name      |    table_name    | compressed_chunk_id | dropped | status | osm_chunk \n----+---------------+-----------------------+------------------+---------------------+---------+--------+-----------\n  1 |             1 | _timescaledb_internal | _hyper_1_1_chunk |                     | f       |      0 | f\n  2 |             1 | _timescaledb_internal | _hyper_1_2_chunk |                     | f       |      0 | f\n  3 |             1 | _timescaledb_internal | _hyper_1_3_chunk |                     | f       |      0 | f\n  4 |             1 | _timescaledb_internal | _hyper_1_4_chunk |                     | f       |      0 | f\n  5 |             1 | _timescaledb_internal | _hyper_1_5_chunk |                     | f       |      0 | f\n  6 |             1 | _timescaledb_internal | _hyper_1_6_chunk |                     | f       |      0 | f\n  7 |             2 | _timescaledb_internal | _hyper_2_7_chunk |                     | f       |      0 | f\n  8 |             2 | _timescaledb_internal | _hyper_2_8_chunk |                     | f       |      0 | f\n  9 |             2 | _timescaledb_internal | _hyper_2_9_chunk |                     | f       |      0 | f\n(9 rows)\n\n\\set ON_ERROR_STOP 0\n-- Cannot merge chunks from different hypertables\nSELECT _timescaledb_internal.test_merge_chunks_across_dimension('_timescaledb_internal._hyper_1_1_chunk','_timescaledb_internal._hyper_2_7_chunk', 1);\nERROR:  cannot merge chunks from different hypertables\n-- Cannot merge non-adjacent chunks\nSELECT _timescaledb_internal.test_merge_chunks_across_dimension('_timescaledb_internal._hyper_1_1_chunk','_timescaledb_internal._hyper_1_3_chunk', 1);\nERROR:  cannot merge non-adjacent chunks over supplied dimension\n-- Cannot merge same chunk to itself (its not adjacent to itself).\nSELECT _timescaledb_internal.test_merge_chunks_across_dimension('_timescaledb_internal._hyper_1_1_chunk','_timescaledb_internal._hyper_1_1_chunk', 1);\nERROR:  cannot merge non-adjacent chunks over supplied dimension\n-- Cannot merge chunks on with different partitioning schemas.\nSELECT _timescaledb_internal.test_merge_chunks_across_dimension('_timescaledb_internal._hyper_1_1_chunk','_timescaledb_internal._hyper_1_4_chunk', 1);\nERROR:  cannot merge chunks with different partitioning schemas\n-- Cannot merge chunks on with non-existant dimension slice.\n-- NOTE: we are merging the same chunk just so they have the exact same partitioning schema and we don't hit the previous test error.\nSELECT _timescaledb_internal.test_merge_chunks_across_dimension('_timescaledb_internal._hyper_1_1_chunk','_timescaledb_internal._hyper_1_1_chunk', 999);\nERROR:  cannot find slice for merging dimension\n\\set ON_ERROR_STOP 1\n-- Merge on open (time) dimension.\nSELECT _timescaledb_internal.test_merge_chunks_across_dimension('_timescaledb_internal._hyper_1_5_chunk','_timescaledb_internal._hyper_1_6_chunk', 1);\n test_merge_chunks_across_dimension \n------------------------------------\n \n(1 row)\n\n-- Merge on close dimension.\nSELECT _timescaledb_internal.test_merge_chunks_across_dimension('_timescaledb_internal._hyper_1_1_chunk','_timescaledb_internal._hyper_1_4_chunk', 2);\n test_merge_chunks_across_dimension \n------------------------------------\n \n(1 row)\n\nSELECT * FROM _timescaledb_catalog.chunk;\n id | hypertable_id |      schema_name      |    table_name    | compressed_chunk_id | dropped | status | osm_chunk \n----+---------------+-----------------------+------------------+---------------------+---------+--------+-----------\n  1 |             1 | _timescaledb_internal | _hyper_1_1_chunk |                     | f       |      0 | f\n  2 |             1 | _timescaledb_internal | _hyper_1_2_chunk |                     | f       |      0 | f\n  3 |             1 | _timescaledb_internal | _hyper_1_3_chunk |                     | f       |      0 | f\n  5 |             1 | _timescaledb_internal | _hyper_1_5_chunk |                     | f       |      0 | f\n  7 |             2 | _timescaledb_internal | _hyper_2_7_chunk |                     | f       |      0 | f\n  8 |             2 | _timescaledb_internal | _hyper_2_8_chunk |                     | f       |      0 | f\n  9 |             2 | _timescaledb_internal | _hyper_2_9_chunk |                     | f       |      0 | f\n(7 rows)\n\n"
  },
  {
    "path": "test/expected/chunk_publication.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- Test automatic addition of chunks to publications\n-- Publications require superuser privileges\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET client_min_messages = WARNING;\nSET timescaledb.enable_chunk_auto_publication = true;\n-- Test 1: Basic single publication\nCREATE TABLE test_hypertable (time timestamptz NOT NULL, device_id int, value float, extra text);\nSELECT create_hypertable('test_hypertable', 'time', chunk_time_interval => interval '1 day');\n      create_hypertable       \n------------------------------\n (1,public,test_hypertable,t)\n\n-- Insert to create first chunk\nINSERT INTO test_hypertable VALUES ('2024-01-01 00:00:00+00', 1, 1.0, 'data1');\n-- Create publication and add hypertable\nCREATE PUBLICATION test_pub FOR TABLE test_hypertable;\n-- Verify initial state (1 chunk)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub' ORDER BY schemaname, tablename;\n      schemaname       |    tablename     |           attnames           | rowfilter \n-----------------------+------------------+------------------------------+-----------\n _timescaledb_internal | _hyper_1_1_chunk | {time,device_id,value,extra} | \n public                | test_hypertable  | {time,device_id,value,extra} | \n\n-- Insert to create 2 more chunks (total 3 chunks)\nINSERT INTO test_hypertable VALUES ('2024-01-02 00:00:00+00', 2, 2.0, 'data2');\nINSERT INTO test_hypertable VALUES ('2024-01-03 00:00:00+00', 3, 3.0, 'data3');\n-- Verify state (3 chunks)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub' ORDER BY schemaname, tablename;\n      schemaname       |    tablename     |           attnames           | rowfilter \n-----------------------+------------------+------------------------------+-----------\n _timescaledb_internal | _hyper_1_1_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_1_2_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_1_3_chunk | {time,device_id,value,extra} | \n public                | test_hypertable  | {time,device_id,value,extra} | \n\n-- Insert to create 5 more chunks (total 8 chunks)\nINSERT INTO test_hypertable VALUES ('2024-01-04 00:00:00+00', 4, 4.0, 'data4');\nINSERT INTO test_hypertable VALUES ('2024-01-05 00:00:00+00', 5, 5.0, 'data5');\nINSERT INTO test_hypertable VALUES ('2024-01-06 00:00:00+00', 6, 6.0, 'data6');\nINSERT INTO test_hypertable VALUES ('2024-01-07 00:00:00+00', 7, 7.0, 'data7');\nINSERT INTO test_hypertable VALUES ('2024-01-08 00:00:00+00', 8, 8.0, 'data8');\n-- Verify final state (8 chunks)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub' ORDER BY schemaname, tablename;\n      schemaname       |    tablename     |           attnames           | rowfilter \n-----------------------+------------------+------------------------------+-----------\n _timescaledb_internal | _hyper_1_1_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_1_2_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_1_3_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_1_4_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_1_5_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_1_6_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_1_7_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_1_8_chunk | {time,device_id,value,extra} | \n public                | test_hypertable  | {time,device_id,value,extra} | \n\n-- Verify chunk removal via DROP TABLE\nSELECT chunk_schema || '.' || chunk_name as \"CHUNK_TO_DROP\"\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'test_hypertable'\nORDER BY chunk_schema, chunk_name LIMIT 1 \\gset\n-- Verify chunk removal via DROP TABLE\nDROP TABLE :CHUNK_TO_DROP;\n-- Verify chunk was removed from publication (7 chunks remaining)\nSELECT chunk_schema, chunk_name\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'test_hypertable'\nORDER BY chunk_schema, chunk_name;\n     chunk_schema      |    chunk_name    \n-----------------------+------------------\n _timescaledb_internal | _hyper_1_2_chunk\n _timescaledb_internal | _hyper_1_3_chunk\n _timescaledb_internal | _hyper_1_4_chunk\n _timescaledb_internal | _hyper_1_5_chunk\n _timescaledb_internal | _hyper_1_6_chunk\n _timescaledb_internal | _hyper_1_7_chunk\n _timescaledb_internal | _hyper_1_8_chunk\n\nSELECT schemaname, tablename, attnames, rowfilter\nFROM pg_publication_tables\nWHERE pubname = 'test_pub'\nORDER BY schemaname, tablename;\n      schemaname       |    tablename     |           attnames           | rowfilter \n-----------------------+------------------+------------------------------+-----------\n _timescaledb_internal | _hyper_1_2_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_1_3_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_1_4_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_1_5_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_1_6_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_1_7_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_1_8_chunk | {time,device_id,value,extra} | \n public                | test_hypertable  | {time,device_id,value,extra} | \n\n-- Verify chunk removal via drop_chunks()\nSELECT drop_chunks('test_hypertable', older_than => '2024-01-07 00:00:00+00'::timestamptz);\n              drop_chunks               \n----------------------------------------\n _timescaledb_internal._hyper_1_2_chunk\n _timescaledb_internal._hyper_1_3_chunk\n _timescaledb_internal._hyper_1_4_chunk\n _timescaledb_internal._hyper_1_5_chunk\n _timescaledb_internal._hyper_1_6_chunk\n\n-- Verify dropped chunks were removed from publication (2 chunks remaining: 2024-01-07 and 2024-01-08)\nSELECT chunk_schema, chunk_name\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'test_hypertable'\nORDER BY chunk_schema, chunk_name;\n     chunk_schema      |    chunk_name    \n-----------------------+------------------\n _timescaledb_internal | _hyper_1_7_chunk\n _timescaledb_internal | _hyper_1_8_chunk\n\nSELECT schemaname, tablename, attnames, rowfilter\nFROM pg_publication_tables\nWHERE pubname = 'test_pub'\nORDER BY schemaname, tablename;\n      schemaname       |    tablename     |           attnames           | rowfilter \n-----------------------+------------------+------------------------------+-----------\n _timescaledb_internal | _hyper_1_7_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_1_8_chunk | {time,device_id,value,extra} | \n public                | test_hypertable  | {time,device_id,value,extra} | \n\n-- Verify chunk removal via TRUNCATE\nTRUNCATE TABLE test_hypertable;\n-- Verify all chunks were removed from publication (0 chunks remaining)\nSELECT chunk_schema, chunk_name\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'test_hypertable'\nORDER BY chunk_schema, chunk_name;\n chunk_schema | chunk_name \n--------------+------------\n\nSELECT schemaname, tablename, attnames, rowfilter\nFROM pg_publication_tables\nWHERE pubname = 'test_pub'\nORDER BY schemaname, tablename;\n schemaname |    tablename    |           attnames           | rowfilter \n------------+-----------------+------------------------------+-----------\n public     | test_hypertable | {time,device_id,value,extra} | \n\n-- Cleanup\nDROP PUBLICATION test_pub CASCADE;\nDROP TABLE test_hypertable CASCADE;\n-- Test 2: Multiple publications\nCREATE TABLE test_hypertable (time timestamptz NOT NULL, device_id int, value float, extra text);\nSELECT create_hypertable('test_hypertable', 'time', chunk_time_interval => interval '1 day');\n      create_hypertable       \n------------------------------\n (2,public,test_hypertable,t)\n\n-- Insert to create first chunk\nINSERT INTO test_hypertable VALUES ('2024-01-01 00:00:00+00', 1, 1.0, 'data1');\n-- Create pub1 and add hypertable\nCREATE PUBLICATION test_pub1 FOR TABLE test_hypertable;\n-- Verify (1 chunk in pub1)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub1' ORDER BY schemaname, tablename;\n      schemaname       |    tablename     |           attnames           | rowfilter \n-----------------------+------------------+------------------------------+-----------\n _timescaledb_internal | _hyper_2_9_chunk | {time,device_id,value,extra} | \n public                | test_hypertable  | {time,device_id,value,extra} | \n\n-- Insert to create 2 more chunks (total 3 chunks)\nINSERT INTO test_hypertable VALUES ('2024-01-02 00:00:00+00', 2, 2.0, 'data2');\nINSERT INTO test_hypertable VALUES ('2024-01-03 00:00:00+00', 3, 3.0, 'data3');\n-- Verify (3 chunks in pub1)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub1' ORDER BY schemaname, tablename;\n      schemaname       |     tablename     |           attnames           | rowfilter \n-----------------------+-------------------+------------------------------+-----------\n _timescaledb_internal | _hyper_2_10_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_2_11_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_2_9_chunk  | {time,device_id,value,extra} | \n public                | test_hypertable   | {time,device_id,value,extra} | \n\n-- Create pub2 and add hypertable\nCREATE PUBLICATION test_pub2 FOR TABLE test_hypertable;\n-- Verify (3 chunks in pub1, 3 chunks in pub2)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub1' ORDER BY schemaname, tablename;\n      schemaname       |     tablename     |           attnames           | rowfilter \n-----------------------+-------------------+------------------------------+-----------\n _timescaledb_internal | _hyper_2_10_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_2_11_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_2_9_chunk  | {time,device_id,value,extra} | \n public                | test_hypertable   | {time,device_id,value,extra} | \n\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub2' ORDER BY schemaname, tablename;\n      schemaname       |     tablename     |           attnames           | rowfilter \n-----------------------+-------------------+------------------------------+-----------\n _timescaledb_internal | _hyper_2_10_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_2_11_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_2_9_chunk  | {time,device_id,value,extra} | \n public                | test_hypertable   | {time,device_id,value,extra} | \n\n-- Insert to create 5 more chunks (total 8 chunks)\nINSERT INTO test_hypertable VALUES ('2024-01-04 00:00:00+00', 4, 4.0, 'data4');\nINSERT INTO test_hypertable VALUES ('2024-01-05 00:00:00+00', 5, 5.0, 'data5');\nINSERT INTO test_hypertable VALUES ('2024-01-06 00:00:00+00', 6, 6.0, 'data6');\nINSERT INTO test_hypertable VALUES ('2024-01-07 00:00:00+00', 7, 7.0, 'data7');\nINSERT INTO test_hypertable VALUES ('2024-01-08 00:00:00+00', 8, 8.0, 'data8');\n-- Verify (8 chunks in pub1, 8 chunks in pub2)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub1' ORDER BY schemaname, tablename;\n      schemaname       |     tablename     |           attnames           | rowfilter \n-----------------------+-------------------+------------------------------+-----------\n _timescaledb_internal | _hyper_2_10_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_2_11_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_2_12_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_2_13_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_2_14_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_2_15_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_2_16_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_2_9_chunk  | {time,device_id,value,extra} | \n public                | test_hypertable   | {time,device_id,value,extra} | \n\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub2' ORDER BY schemaname, tablename;\n      schemaname       |     tablename     |           attnames           | rowfilter \n-----------------------+-------------------+------------------------------+-----------\n _timescaledb_internal | _hyper_2_10_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_2_11_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_2_12_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_2_13_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_2_14_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_2_15_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_2_16_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_2_9_chunk  | {time,device_id,value,extra} | \n public                | test_hypertable   | {time,device_id,value,extra} | \n\n-- Cleanup\nDROP PUBLICATION test_pub1 CASCADE;\nDROP PUBLICATION test_pub2 CASCADE;\nDROP TABLE test_hypertable CASCADE;\n-- Test 3: Row filtering (WHERE clause with multiple conditions)\nCREATE TABLE test_hypertable (time timestamptz NOT NULL, device_id int, value float, extra text);\nSELECT create_hypertable('test_hypertable', 'time', chunk_time_interval => interval '1 day');\n      create_hypertable       \n------------------------------\n (3,public,test_hypertable,t)\n\n-- Insert to create first chunk\nINSERT INTO test_hypertable VALUES ('2024-01-01 00:00:00+00', 1, 1.0, 'data1');\n-- Create publication with row filter (multiple conditions)\nCREATE PUBLICATION test_pub_row_filter FOR TABLE test_hypertable WHERE (device_id > 10 AND value > 1000);\n-- Verify initial state (1 chunk with row filter)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub_row_filter' ORDER BY schemaname, tablename;\n      schemaname       |     tablename     |           attnames           |                         rowfilter                         \n-----------------------+-------------------+------------------------------+-----------------------------------------------------------\n _timescaledb_internal | _hyper_3_17_chunk | {time,device_id,value,extra} | ((device_id > 10) AND (value > (1000)::double precision))\n public                | test_hypertable   | {time,device_id,value,extra} | ((device_id > 10) AND (value > (1000)::double precision))\n\n-- Insert to create 2 more chunks (total 3 chunks)\nINSERT INTO test_hypertable VALUES ('2024-01-02 00:00:00+00', 2, 2.0, 'data2');\nINSERT INTO test_hypertable VALUES ('2024-01-03 00:00:00+00', 3, 3.0, 'data3');\n-- Verify state (3 chunks with row filters)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub_row_filter' ORDER BY schemaname, tablename;\n      schemaname       |     tablename     |           attnames           |                         rowfilter                         \n-----------------------+-------------------+------------------------------+-----------------------------------------------------------\n _timescaledb_internal | _hyper_3_17_chunk | {time,device_id,value,extra} | ((device_id > 10) AND (value > (1000)::double precision))\n _timescaledb_internal | _hyper_3_18_chunk | {time,device_id,value,extra} | ((device_id > 10) AND (value > (1000)::double precision))\n _timescaledb_internal | _hyper_3_19_chunk | {time,device_id,value,extra} | ((device_id > 10) AND (value > (1000)::double precision))\n public                | test_hypertable   | {time,device_id,value,extra} | ((device_id > 10) AND (value > (1000)::double precision))\n\n-- Insert to create 5 more chunks (total 8 chunks)\nINSERT INTO test_hypertable VALUES ('2024-01-04 00:00:00+00', 4, 4.0, 'data4');\nINSERT INTO test_hypertable VALUES ('2024-01-05 00:00:00+00', 5, 5.0, 'data5');\nINSERT INTO test_hypertable VALUES ('2024-01-06 00:00:00+00', 6, 6.0, 'data6');\nINSERT INTO test_hypertable VALUES ('2024-01-07 00:00:00+00', 7, 7.0, 'data7');\nINSERT INTO test_hypertable VALUES ('2024-01-08 00:00:00+00', 8, 8.0, 'data8');\n-- Verify final state (8 chunks with row filters)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub_row_filter' ORDER BY schemaname, tablename;\n      schemaname       |     tablename     |           attnames           |                         rowfilter                         \n-----------------------+-------------------+------------------------------+-----------------------------------------------------------\n _timescaledb_internal | _hyper_3_17_chunk | {time,device_id,value,extra} | ((device_id > 10) AND (value > (1000)::double precision))\n _timescaledb_internal | _hyper_3_18_chunk | {time,device_id,value,extra} | ((device_id > 10) AND (value > (1000)::double precision))\n _timescaledb_internal | _hyper_3_19_chunk | {time,device_id,value,extra} | ((device_id > 10) AND (value > (1000)::double precision))\n _timescaledb_internal | _hyper_3_20_chunk | {time,device_id,value,extra} | ((device_id > 10) AND (value > (1000)::double precision))\n _timescaledb_internal | _hyper_3_21_chunk | {time,device_id,value,extra} | ((device_id > 10) AND (value > (1000)::double precision))\n _timescaledb_internal | _hyper_3_22_chunk | {time,device_id,value,extra} | ((device_id > 10) AND (value > (1000)::double precision))\n _timescaledb_internal | _hyper_3_23_chunk | {time,device_id,value,extra} | ((device_id > 10) AND (value > (1000)::double precision))\n _timescaledb_internal | _hyper_3_24_chunk | {time,device_id,value,extra} | ((device_id > 10) AND (value > (1000)::double precision))\n public                | test_hypertable   | {time,device_id,value,extra} | ((device_id > 10) AND (value > (1000)::double precision))\n\n-- Cleanup\nDROP PUBLICATION test_pub_row_filter CASCADE;\nDROP TABLE test_hypertable CASCADE;\n-- Test 4: Column filtering\nCREATE TABLE test_hypertable (time timestamptz NOT NULL, device_id int, value float, extra text);\nSELECT create_hypertable('test_hypertable', 'time', chunk_time_interval => interval '1 day');\n      create_hypertable       \n------------------------------\n (4,public,test_hypertable,t)\n\n-- Insert to create first chunk\nINSERT INTO test_hypertable VALUES ('2024-01-01 00:00:00+00', 1, 1.0, 'data1');\n-- Create publication with column filter\nCREATE PUBLICATION test_pub_col_filter FOR TABLE test_hypertable (time, device_id);\n-- Verify initial state (1 chunk with column filter)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub_col_filter' ORDER BY schemaname, tablename;\n      schemaname       |     tablename     |     attnames     | rowfilter \n-----------------------+-------------------+------------------+-----------\n _timescaledb_internal | _hyper_4_25_chunk | {time,device_id} | \n public                | test_hypertable   | {time,device_id} | \n\n-- Insert to create 2 more chunks (total 3 chunks)\nINSERT INTO test_hypertable VALUES ('2024-01-02 00:00:00+00', 2, 2.0, 'data2');\nINSERT INTO test_hypertable VALUES ('2024-01-03 00:00:00+00', 3, 3.0, 'data3');\n-- Verify state (3 chunks with column filters)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub_col_filter' ORDER BY schemaname, tablename;\n      schemaname       |     tablename     |     attnames     | rowfilter \n-----------------------+-------------------+------------------+-----------\n _timescaledb_internal | _hyper_4_25_chunk | {time,device_id} | \n _timescaledb_internal | _hyper_4_26_chunk | {time,device_id} | \n _timescaledb_internal | _hyper_4_27_chunk | {time,device_id} | \n public                | test_hypertable   | {time,device_id} | \n\n-- Insert to create 5 more chunks (total 8 chunks)\nINSERT INTO test_hypertable VALUES ('2024-01-04 00:00:00+00', 4, 4.0, 'data4');\nINSERT INTO test_hypertable VALUES ('2024-01-05 00:00:00+00', 5, 5.0, 'data5');\nINSERT INTO test_hypertable VALUES ('2024-01-06 00:00:00+00', 6, 6.0, 'data6');\nINSERT INTO test_hypertable VALUES ('2024-01-07 00:00:00+00', 7, 7.0, 'data7');\nINSERT INTO test_hypertable VALUES ('2024-01-08 00:00:00+00', 8, 8.0, 'data8');\n-- Verify final state (8 chunks with column filters)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub_col_filter' ORDER BY schemaname, tablename;\n      schemaname       |     tablename     |     attnames     | rowfilter \n-----------------------+-------------------+------------------+-----------\n _timescaledb_internal | _hyper_4_25_chunk | {time,device_id} | \n _timescaledb_internal | _hyper_4_26_chunk | {time,device_id} | \n _timescaledb_internal | _hyper_4_27_chunk | {time,device_id} | \n _timescaledb_internal | _hyper_4_28_chunk | {time,device_id} | \n _timescaledb_internal | _hyper_4_29_chunk | {time,device_id} | \n _timescaledb_internal | _hyper_4_30_chunk | {time,device_id} | \n _timescaledb_internal | _hyper_4_31_chunk | {time,device_id} | \n _timescaledb_internal | _hyper_4_32_chunk | {time,device_id} | \n public                | test_hypertable   | {time,device_id} | \n\n-- Cleanup\nDROP PUBLICATION test_pub_col_filter CASCADE;\nDROP TABLE test_hypertable CASCADE;\n-- Test 5: Combined row + column filtering\nCREATE TABLE test_hypertable (time timestamptz NOT NULL, device_id int, value float, extra text);\nSELECT create_hypertable('test_hypertable', 'time', chunk_time_interval => interval '1 day');\n      create_hypertable       \n------------------------------\n (5,public,test_hypertable,t)\n\n-- Insert to create first chunk\nINSERT INTO test_hypertable VALUES ('2024-01-01 00:00:00+00', 1, 1.0, 'data1');\n-- Create publication with both row and column filters\nCREATE PUBLICATION test_pub_combined FOR TABLE test_hypertable (time, device_id) WHERE (device_id > 10);\n-- Verify initial state (1 chunk with both filters)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub_combined' ORDER BY schemaname, tablename;\n      schemaname       |     tablename     |     attnames     |    rowfilter     \n-----------------------+-------------------+------------------+------------------\n _timescaledb_internal | _hyper_5_33_chunk | {time,device_id} | (device_id > 10)\n public                | test_hypertable   | {time,device_id} | (device_id > 10)\n\n-- Insert to create 2 more chunks (total 3 chunks)\nINSERT INTO test_hypertable VALUES ('2024-01-02 00:00:00+00', 2, 2.0, 'data2');\nINSERT INTO test_hypertable VALUES ('2024-01-03 00:00:00+00', 3, 3.0, 'data3');\n-- Verify state (3 chunks with both filters)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub_combined' ORDER BY schemaname, tablename;\n      schemaname       |     tablename     |     attnames     |    rowfilter     \n-----------------------+-------------------+------------------+------------------\n _timescaledb_internal | _hyper_5_33_chunk | {time,device_id} | (device_id > 10)\n _timescaledb_internal | _hyper_5_34_chunk | {time,device_id} | (device_id > 10)\n _timescaledb_internal | _hyper_5_35_chunk | {time,device_id} | (device_id > 10)\n public                | test_hypertable   | {time,device_id} | (device_id > 10)\n\n-- Insert to create 5 more chunks (total 8 chunks)\nINSERT INTO test_hypertable VALUES ('2024-01-04 00:00:00+00', 4, 4.0, 'data4');\nINSERT INTO test_hypertable VALUES ('2024-01-05 00:00:00+00', 5, 5.0, 'data5');\nINSERT INTO test_hypertable VALUES ('2024-01-06 00:00:00+00', 6, 6.0, 'data6');\nINSERT INTO test_hypertable VALUES ('2024-01-07 00:00:00+00', 7, 7.0, 'data7');\nINSERT INTO test_hypertable VALUES ('2024-01-08 00:00:00+00', 8, 8.0, 'data8');\n-- Verify final state (8 chunks with both filters)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub_combined' ORDER BY schemaname, tablename;\n      schemaname       |     tablename     |     attnames     |    rowfilter     \n-----------------------+-------------------+------------------+------------------\n _timescaledb_internal | _hyper_5_33_chunk | {time,device_id} | (device_id > 10)\n _timescaledb_internal | _hyper_5_34_chunk | {time,device_id} | (device_id > 10)\n _timescaledb_internal | _hyper_5_35_chunk | {time,device_id} | (device_id > 10)\n _timescaledb_internal | _hyper_5_36_chunk | {time,device_id} | (device_id > 10)\n _timescaledb_internal | _hyper_5_37_chunk | {time,device_id} | (device_id > 10)\n _timescaledb_internal | _hyper_5_38_chunk | {time,device_id} | (device_id > 10)\n _timescaledb_internal | _hyper_5_39_chunk | {time,device_id} | (device_id > 10)\n _timescaledb_internal | _hyper_5_40_chunk | {time,device_id} | (device_id > 10)\n public                | test_hypertable   | {time,device_id} | (device_id > 10)\n\n-- Cleanup\nDROP PUBLICATION test_pub_combined CASCADE;\nDROP TABLE test_hypertable CASCADE;\n-- Test 6: FOR ALL TABLES publication\nCREATE TABLE test_hypertable (time timestamptz NOT NULL, device_id int, value float, extra text);\nSELECT create_hypertable('test_hypertable', 'time', chunk_time_interval => interval '1 day');\n      create_hypertable       \n------------------------------\n (6,public,test_hypertable,t)\n\n-- Insert to create first chunk\nINSERT INTO test_hypertable VALUES ('2024-01-01 00:00:00+00', 1, 1.0, 'data1');\n-- Create FOR ALL TABLES publication\nCREATE PUBLICATION test_pub_all_tables FOR ALL TABLES;\n-- Verify initial state (1 chunk)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub_all_tables' AND tablename LIKE '%test_hypertable%' OR tablename LIKE '_hyper_%' ORDER BY schemaname, tablename;\n      schemaname       |     tablename     |           attnames           | rowfilter \n-----------------------+-------------------+------------------------------+-----------\n _timescaledb_internal | _hyper_6_41_chunk | {time,device_id,value,extra} | \n public                | test_hypertable   | {time,device_id,value,extra} | \n\n-- Insert to create 2 more chunks (total 3 chunks)\nINSERT INTO test_hypertable VALUES ('2024-01-02 00:00:00+00', 2, 2.0, 'data2');\nINSERT INTO test_hypertable VALUES ('2024-01-03 00:00:00+00', 3, 3.0, 'data3');\n-- Verify state (3 chunks)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub_all_tables' AND tablename LIKE '%test_hypertable%' OR tablename LIKE '_hyper_%' ORDER BY schemaname, tablename;\n      schemaname       |     tablename     |           attnames           | rowfilter \n-----------------------+-------------------+------------------------------+-----------\n _timescaledb_internal | _hyper_6_41_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_6_42_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_6_43_chunk | {time,device_id,value,extra} | \n public                | test_hypertable   | {time,device_id,value,extra} | \n\n-- Insert to create 5 more chunks (total 8 chunks)\nINSERT INTO test_hypertable VALUES ('2024-01-04 00:00:00+00', 4, 4.0, 'data4');\nINSERT INTO test_hypertable VALUES ('2024-01-05 00:00:00+00', 5, 5.0, 'data5');\nINSERT INTO test_hypertable VALUES ('2024-01-06 00:00:00+00', 6, 6.0, 'data6');\nINSERT INTO test_hypertable VALUES ('2024-01-07 00:00:00+00', 7, 7.0, 'data7');\nINSERT INTO test_hypertable VALUES ('2024-01-08 00:00:00+00', 8, 8.0, 'data8');\n-- Verify final state (8 chunks)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub_all_tables' AND tablename LIKE '%test_hypertable%' OR tablename LIKE '_hyper_%' ORDER BY schemaname, tablename;\n      schemaname       |     tablename     |           attnames           | rowfilter \n-----------------------+-------------------+------------------------------+-----------\n _timescaledb_internal | _hyper_6_41_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_6_42_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_6_43_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_6_44_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_6_45_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_6_46_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_6_47_chunk | {time,device_id,value,extra} | \n _timescaledb_internal | _hyper_6_48_chunk | {time,device_id,value,extra} | \n public                | test_hypertable   | {time,device_id,value,extra} | \n\n-- Cleanup\nDROP PUBLICATION test_pub_all_tables CASCADE;\nDROP TABLE test_hypertable CASCADE;\n-- Test 7: Edge case - Hypertable not in any publication\nCREATE TABLE test_hypertable (time timestamptz NOT NULL, device_id int, value float, extra text);\nSELECT create_hypertable('test_hypertable', 'time', chunk_time_interval => interval '1 day');\n      create_hypertable       \n------------------------------\n (7,public,test_hypertable,t)\n\n-- Insert to create 8 chunks without any publication\nINSERT INTO test_hypertable VALUES ('2024-01-01 00:00:00+00', 1, 1.0, 'data1');\nINSERT INTO test_hypertable VALUES ('2024-01-02 00:00:00+00', 2, 2.0, 'data2');\nINSERT INTO test_hypertable VALUES ('2024-01-03 00:00:00+00', 3, 3.0, 'data3');\nINSERT INTO test_hypertable VALUES ('2024-01-04 00:00:00+00', 4, 4.0, 'data4');\nINSERT INTO test_hypertable VALUES ('2024-01-05 00:00:00+00', 5, 5.0, 'data5');\nINSERT INTO test_hypertable VALUES ('2024-01-06 00:00:00+00', 6, 6.0, 'data6');\nINSERT INTO test_hypertable VALUES ('2024-01-07 00:00:00+00', 7, 7.0, 'data7');\nINSERT INTO test_hypertable VALUES ('2024-01-08 00:00:00+00', 8, 8.0, 'data8');\n-- Verify chunks were created successfully\nSELECT COUNT(*) as chunks_created FROM timescaledb_information.chunks\nWHERE hypertable_name = 'test_hypertable';\n chunks_created \n----------------\n              8\n\n-- Cleanup\nDROP TABLE test_hypertable CASCADE;\n-- Test 8: Edge case - Publication dropped before chunk creation\nCREATE TABLE test_hypertable (time timestamptz NOT NULL, device_id int, value float, extra text);\nSELECT create_hypertable('test_hypertable', 'time', chunk_time_interval => interval '1 day');\n      create_hypertable       \n------------------------------\n (8,public,test_hypertable,t)\n\n-- Insert to create first chunk\nINSERT INTO test_hypertable VALUES ('2024-01-01 00:00:00+00', 1, 1.0, 'data1');\n-- Create publication and add hypertable\nCREATE PUBLICATION test_pub FOR TABLE test_hypertable;\n-- Verify (1 chunk in publication)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub' ORDER BY schemaname, tablename;\n      schemaname       |     tablename     |           attnames           | rowfilter \n-----------------------+-------------------+------------------------------+-----------\n _timescaledb_internal | _hyper_8_57_chunk | {time,device_id,value,extra} | \n public                | test_hypertable   | {time,device_id,value,extra} | \n\n-- Drop the publication\nDROP PUBLICATION test_pub;\n-- Insert to create 2 more chunks (total 3 chunks)\n-- Should succeed with WARNING, not error\nINSERT INTO test_hypertable VALUES ('2024-01-02 00:00:00+00', 2, 2.0, 'data2');\nINSERT INTO test_hypertable VALUES ('2024-01-03 00:00:00+00', 3, 3.0, 'data3');\n-- Verify chunks were created successfully despite missing publication\nSELECT COUNT(*) as chunks_after_pub_drop FROM timescaledb_information.chunks\nWHERE hypertable_name = 'test_hypertable';\n chunks_after_pub_drop \n-----------------------\n                     3\n\n-- Cleanup\nDROP TABLE test_hypertable CASCADE;\n-- Test 9: GUC control of chunk publication\nCREATE TABLE test_hypertable (time timestamptz NOT NULL, device_id int, value float, extra text);\nSELECT create_hypertable('test_hypertable', 'time', chunk_time_interval => interval '1 day');\n      create_hypertable       \n------------------------------\n (9,public,test_hypertable,t)\n\n-- Insert to create first chunk\nINSERT INTO test_hypertable VALUES ('2024-01-01 00:00:00+00', 1, 1.0, 'data1');\n-- Create publication\nCREATE PUBLICATION test_pub_guc FOR TABLE test_hypertable;\n-- Verify initial state (1 chunk)\nSELECT schemaname, tablename FROM pg_publication_tables WHERE pubname = 'test_pub_guc' ORDER BY schemaname, tablename;\n      schemaname       |     tablename     \n-----------------------+-------------------\n _timescaledb_internal | _hyper_9_60_chunk\n public                | test_hypertable\n\n-- Test Part 1: GUC enabled - chunks should be added to publication automatically\n-- Insert to create a new chunk - should be added to publication automatically\nINSERT INTO test_hypertable VALUES ('2024-01-02 00:00:00+00', 2, 2.0, 'data2');\n-- Verify (2 chunks in publication)\nSELECT schemaname, tablename FROM pg_publication_tables WHERE pubname = 'test_pub_guc' ORDER BY schemaname, tablename;\n      schemaname       |     tablename     \n-----------------------+-------------------\n _timescaledb_internal | _hyper_9_60_chunk\n _timescaledb_internal | _hyper_9_61_chunk\n public                | test_hypertable\n\n-- Test Part 2: Disable the GUC and create another chunk\nSET timescaledb.enable_chunk_auto_publication = false;\n-- Insert to create a new chunk - should NOT be added to publication\nINSERT INTO test_hypertable VALUES ('2024-01-03 00:00:00+00', 3, 3.0, 'data3');\n-- Verify (still 2 chunks in publication, chunk 3 should not be there)\nSELECT schemaname, tablename FROM pg_publication_tables WHERE pubname = 'test_pub_guc' ORDER BY schemaname, tablename;\n      schemaname       |     tablename     \n-----------------------+-------------------\n _timescaledb_internal | _hyper_9_60_chunk\n _timescaledb_internal | _hyper_9_61_chunk\n public                | test_hypertable\n\n-- Verify that chunk 3 exists but is not in the publication\nSELECT chunk_schema, chunk_name FROM timescaledb_information.chunks WHERE hypertable_name = 'test_hypertable';\n     chunk_schema      |    chunk_name     \n-----------------------+-------------------\n _timescaledb_internal | _hyper_9_60_chunk\n _timescaledb_internal | _hyper_9_61_chunk\n _timescaledb_internal | _hyper_9_62_chunk\n\n-- Test Part 3: Re-enable the GUC and create another chunk\nSET timescaledb.enable_chunk_auto_publication = true;\n-- Insert to create a new chunk - should be added to publication again\nINSERT INTO test_hypertable VALUES ('2024-01-04 00:00:00+00', 4, 4.0, 'data4');\n-- Verify (3 chunks in publication: chunk 1, 2, and 4; chunk 3 still missing)\nSELECT schemaname, tablename FROM pg_publication_tables WHERE pubname = 'test_pub_guc' ORDER BY schemaname, tablename;\n      schemaname       |     tablename     \n-----------------------+-------------------\n _timescaledb_internal | _hyper_9_60_chunk\n _timescaledb_internal | _hyper_9_61_chunk\n _timescaledb_internal | _hyper_9_63_chunk\n public                | test_hypertable\n\nSELECT chunk_schema, chunk_name FROM timescaledb_information.chunks WHERE hypertable_name = 'test_hypertable';\n     chunk_schema      |    chunk_name     \n-----------------------+-------------------\n _timescaledb_internal | _hyper_9_60_chunk\n _timescaledb_internal | _hyper_9_61_chunk\n _timescaledb_internal | _hyper_9_62_chunk\n _timescaledb_internal | _hyper_9_63_chunk\n\n-- Cleanup\nDROP PUBLICATION test_pub_guc CASCADE;\nDROP TABLE test_hypertable CASCADE;\nRESET client_min_messages;\n"
  },
  {
    "path": "test/expected/chunk_utils.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- Set this variable to avoid using a hard-coded path each time query\n-- results are compared\n\\set QUERY_RESULT_TEST_EQUAL_RELPATH 'include/query_result_test_equal.sql'\nCREATE OR REPLACE FUNCTION dimension_get_time(\n    hypertable_id INT\n)\n    RETURNS _timescaledb_catalog.dimension LANGUAGE SQL STABLE AS\n$BODY$\n    SELECT *\n    FROM _timescaledb_catalog.dimension d\n    WHERE d.hypertable_id = dimension_get_time.hypertable_id AND\n          d.interval_length IS NOT NULL\n$BODY$;\nCREATE TABLE PUBLIC.drop_chunk_test1(time bigint, temp float8, device_id text);\nCREATE TABLE PUBLIC.drop_chunk_test2(time bigint, temp float8, device_id text);\nCREATE TABLE PUBLIC.drop_chunk_test3(time bigint, temp float8, device_id text);\nCREATE INDEX ON drop_chunk_test1(time DESC);\n-- show_chunks() without specifying a table is not allowed\n\\set ON_ERROR_STOP 0\nSELECT show_chunks(NULL);\nERROR:  invalid hypertable or continuous aggregate\n\\set ON_ERROR_STOP 1\nSELECT create_hypertable('public.drop_chunk_test1', 'time', chunk_time_interval => 1, create_default_indexes=>false);\n       create_hypertable       \n-------------------------------\n (1,public,drop_chunk_test1,t)\n\nSELECT create_hypertable('public.drop_chunk_test2', 'time', chunk_time_interval => 1, create_default_indexes=>false);\n       create_hypertable       \n-------------------------------\n (2,public,drop_chunk_test2,t)\n\nSELECT create_hypertable('public.drop_chunk_test3', 'time', chunk_time_interval => 1, create_default_indexes=>false);\n       create_hypertable       \n-------------------------------\n (3,public,drop_chunk_test3,t)\n\n-- Add space dimensions to ensure chunks share dimension slices\nSELECT add_dimension('public.drop_chunk_test1', 'device_id', 2);\n              add_dimension              \n-----------------------------------------\n (4,public,drop_chunk_test1,device_id,t)\n\nSELECT add_dimension('public.drop_chunk_test2', 'device_id', 2);\n              add_dimension              \n-----------------------------------------\n (5,public,drop_chunk_test2,device_id,t)\n\nSELECT add_dimension('public.drop_chunk_test3', 'device_id', 2);\n              add_dimension              \n-----------------------------------------\n (6,public,drop_chunk_test3,device_id,t)\n\nSELECT c.id AS chunk_id, c.hypertable_id, c.schema_name AS chunk_schema, c.table_name AS chunk_table, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\nINNER JOIN  dimension_get_time(h.id) time_dimension ON(true)\nINNER JOIN  _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = time_dimension.id)\nINNER JOIN  _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id AND cc.chunk_id = c.id)\nWHERE h.schema_name = 'public' AND (h.table_name = 'drop_chunk_test1' OR h.table_name = 'drop_chunk_test2')\nORDER BY c.id;\n chunk_id | hypertable_id | chunk_schema | chunk_table | range_start | range_end \n----------+---------------+--------------+-------------+-------------+-----------\n\nSELECT * FROM test.relation WHERE schema = '_timescaledb_internal' AND name LIKE '\\_hyper%';\n schema | name | type | owner \n--------+------+------+-------\n\nSELECT  _timescaledb_functions.get_partition_for_key('dev1'::text);\n get_partition_for_key \n-----------------------\n            1129986420\n\nSELECT  _timescaledb_functions.get_partition_for_key('dev7'::varchar(5));\n get_partition_for_key \n-----------------------\n             449729092\n\nINSERT INTO PUBLIC.drop_chunk_test1 VALUES(1, 1.0, 'dev1');\nINSERT INTO PUBLIC.drop_chunk_test1 VALUES(2, 2.0, 'dev1');\nINSERT INTO PUBLIC.drop_chunk_test1 VALUES(3, 3.0, 'dev1');\nINSERT INTO PUBLIC.drop_chunk_test1 VALUES(4, 4.0, 'dev7');\nINSERT INTO PUBLIC.drop_chunk_test1 VALUES(5, 5.0, 'dev7');\nINSERT INTO PUBLIC.drop_chunk_test1 VALUES(6, 6.0, 'dev7');\nINSERT INTO PUBLIC.drop_chunk_test2 VALUES(1, 1.0, 'dev1');\nINSERT INTO PUBLIC.drop_chunk_test2 VALUES(2, 2.0, 'dev1');\nINSERT INTO PUBLIC.drop_chunk_test2 VALUES(3, 3.0, 'dev1');\nINSERT INTO PUBLIC.drop_chunk_test2 VALUES(4, 4.0, 'dev7');\nINSERT INTO PUBLIC.drop_chunk_test2 VALUES(5, 5.0, 'dev7');\nINSERT INTO PUBLIC.drop_chunk_test2 VALUES(6, 6.0, 'dev7');\nINSERT INTO PUBLIC.drop_chunk_test3 VALUES(1, 1.0, 'dev1');\nINSERT INTO PUBLIC.drop_chunk_test3 VALUES(2, 2.0, 'dev1');\nINSERT INTO PUBLIC.drop_chunk_test3 VALUES(3, 3.0, 'dev1');\nINSERT INTO PUBLIC.drop_chunk_test3 VALUES(4, 4.0, 'dev7');\nINSERT INTO PUBLIC.drop_chunk_test3 VALUES(5, 5.0, 'dev7');\nINSERT INTO PUBLIC.drop_chunk_test3 VALUES(6, 6.0, 'dev7');\nSELECT c.id AS chunk_id, c.hypertable_id, c.schema_name AS chunk_schema, c.table_name AS chunk_table, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\nINNER JOIN  dimension_get_time(h.id) time_dimension ON(true)\nINNER JOIN  _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = time_dimension.id)\nINNER JOIN  _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id AND cc.chunk_id = c.id)\nWHERE h.schema_name = 'public' AND (h.table_name = 'drop_chunk_test1' OR h.table_name = 'drop_chunk_test2')\nORDER BY c.id;\n chunk_id | hypertable_id |     chunk_schema      |    chunk_table    | range_start | range_end \n----------+---------------+-----------------------+-------------------+-------------+-----------\n        1 |             1 | _timescaledb_internal | _hyper_1_1_chunk  |           1 |         2\n        2 |             1 | _timescaledb_internal | _hyper_1_2_chunk  |           2 |         3\n        3 |             1 | _timescaledb_internal | _hyper_1_3_chunk  |           3 |         4\n        4 |             1 | _timescaledb_internal | _hyper_1_4_chunk  |           4 |         5\n        5 |             1 | _timescaledb_internal | _hyper_1_5_chunk  |           5 |         6\n        6 |             1 | _timescaledb_internal | _hyper_1_6_chunk  |           6 |         7\n        7 |             2 | _timescaledb_internal | _hyper_2_7_chunk  |           1 |         2\n        8 |             2 | _timescaledb_internal | _hyper_2_8_chunk  |           2 |         3\n        9 |             2 | _timescaledb_internal | _hyper_2_9_chunk  |           3 |         4\n       10 |             2 | _timescaledb_internal | _hyper_2_10_chunk |           4 |         5\n       11 |             2 | _timescaledb_internal | _hyper_2_11_chunk |           5 |         6\n       12 |             2 | _timescaledb_internal | _hyper_2_12_chunk |           6 |         7\n\nSELECT * FROM test.relation WHERE schema = '_timescaledb_internal' AND name LIKE '\\_hyper%';\n        schema         |       name        | type  |       owner       \n-----------------------+-------------------+-------+-------------------\n _timescaledb_internal | _hyper_1_1_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_1_2_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_1_3_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_1_4_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_1_5_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_1_6_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_2_10_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_2_11_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_2_12_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_2_7_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_2_8_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_2_9_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_3_13_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_3_14_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_3_15_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_3_16_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_3_17_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_3_18_chunk | table | default_perm_user\n\n-- next two calls of show_chunks should give same set of chunks as above when combined\nSELECT show_chunks('drop_chunk_test1');\n              show_chunks               \n----------------------------------------\n _timescaledb_internal._hyper_1_1_chunk\n _timescaledb_internal._hyper_1_2_chunk\n _timescaledb_internal._hyper_1_3_chunk\n _timescaledb_internal._hyper_1_4_chunk\n _timescaledb_internal._hyper_1_5_chunk\n _timescaledb_internal._hyper_1_6_chunk\n\nSELECT * FROM show_chunks('drop_chunk_test2');\n               show_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_2_7_chunk\n _timescaledb_internal._hyper_2_8_chunk\n _timescaledb_internal._hyper_2_9_chunk\n _timescaledb_internal._hyper_2_10_chunk\n _timescaledb_internal._hyper_2_11_chunk\n _timescaledb_internal._hyper_2_12_chunk\n\nCREATE VIEW dependent_view AS SELECT * FROM _timescaledb_internal._hyper_1_1_chunk;\n\\set ON_ERROR_STOP 0\nSELECT drop_chunks('drop_chunk_test1');\nERROR:  invalid time range for dropping chunks\nSELECT drop_chunks('drop_chunk_test1', older_than => 2);\nERROR:  cannot drop table _timescaledb_internal._hyper_1_1_chunk because other objects depend on it\nSELECT drop_chunks('drop_chunk_test1', older_than => NULL::interval);\nERROR:  invalid time range for dropping chunks\nSELECT drop_chunks('drop_chunk_test1', older_than => NULL::int);\nERROR:  invalid time range for dropping chunks\nDROP VIEW dependent_view;\n-- should error because of wrong relative order of time constraints\nSELECT show_chunks('drop_chunk_test1', older_than=>3, newer_than=>4);\nERROR:  invalid time range\n-- Should error because NULL was used for the table name.\nSELECT drop_chunks(NULL, older_than => 3);\nERROR:  invalid hypertable or continuous aggregate\n-- should error because there is no relation with that OID.\nSELECT drop_chunks(3533, older_than => 3);\nERROR:  invalid hypertable or continuous aggregate\n\\set ON_ERROR_STOP 1\n-- show created constraints and dimension slices for each chunk\nSELECT c.table_name, cc.constraint_name, ds.id AS dimension_slice_id, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.chunk_constraint cc ON (c.id = cc.chunk_id)\nFULL OUTER JOIN _timescaledb_catalog.dimension_slice ds ON (ds.id = cc.dimension_slice_id)\nORDER BY c.id;\n    table_name     | constraint_name | dimension_slice_id |     range_start      |      range_end      \n-------------------+-----------------+--------------------+----------------------+---------------------\n _hyper_1_1_chunk  | constraint_1    |                  1 |                    1 |                   2\n _hyper_1_1_chunk  | constraint_2    |                  2 |           1073741823 | 9223372036854775807\n _hyper_1_2_chunk  | constraint_3    |                  3 |                    2 |                   3\n _hyper_1_2_chunk  | constraint_2    |                  2 |           1073741823 | 9223372036854775807\n _hyper_1_3_chunk  | constraint_4    |                  4 |                    3 |                   4\n _hyper_1_3_chunk  | constraint_2    |                  2 |           1073741823 | 9223372036854775807\n _hyper_1_4_chunk  | constraint_5    |                  5 |                    4 |                   5\n _hyper_1_4_chunk  | constraint_6    |                  6 | -9223372036854775808 |          1073741823\n _hyper_1_5_chunk  | constraint_7    |                  7 |                    5 |                   6\n _hyper_1_5_chunk  | constraint_6    |                  6 | -9223372036854775808 |          1073741823\n _hyper_1_6_chunk  | constraint_8    |                  8 |                    6 |                   7\n _hyper_1_6_chunk  | constraint_6    |                  6 | -9223372036854775808 |          1073741823\n _hyper_2_7_chunk  | constraint_9    |                  9 |                    1 |                   2\n _hyper_2_7_chunk  | constraint_10   |                 10 |           1073741823 | 9223372036854775807\n _hyper_2_8_chunk  | constraint_11   |                 11 |                    2 |                   3\n _hyper_2_8_chunk  | constraint_10   |                 10 |           1073741823 | 9223372036854775807\n _hyper_2_9_chunk  | constraint_12   |                 12 |                    3 |                   4\n _hyper_2_9_chunk  | constraint_10   |                 10 |           1073741823 | 9223372036854775807\n _hyper_2_10_chunk | constraint_13   |                 13 |                    4 |                   5\n _hyper_2_10_chunk | constraint_14   |                 14 | -9223372036854775808 |          1073741823\n _hyper_2_11_chunk | constraint_15   |                 15 |                    5 |                   6\n _hyper_2_11_chunk | constraint_14   |                 14 | -9223372036854775808 |          1073741823\n _hyper_2_12_chunk | constraint_16   |                 16 |                    6 |                   7\n _hyper_2_12_chunk | constraint_14   |                 14 | -9223372036854775808 |          1073741823\n _hyper_3_13_chunk | constraint_17   |                 17 |                    1 |                   2\n _hyper_3_13_chunk | constraint_18   |                 18 |           1073741823 | 9223372036854775807\n _hyper_3_14_chunk | constraint_19   |                 19 |                    2 |                   3\n _hyper_3_14_chunk | constraint_18   |                 18 |           1073741823 | 9223372036854775807\n _hyper_3_15_chunk | constraint_20   |                 20 |                    3 |                   4\n _hyper_3_15_chunk | constraint_18   |                 18 |           1073741823 | 9223372036854775807\n _hyper_3_16_chunk | constraint_21   |                 21 |                    4 |                   5\n _hyper_3_16_chunk | constraint_22   |                 22 | -9223372036854775808 |          1073741823\n _hyper_3_17_chunk | constraint_23   |                 23 |                    5 |                   6\n _hyper_3_17_chunk | constraint_22   |                 22 | -9223372036854775808 |          1073741823\n _hyper_3_18_chunk | constraint_24   |                 24 |                    6 |                   7\n _hyper_3_18_chunk | constraint_22   |                 22 | -9223372036854775808 |          1073741823\n\nSELECT * FROM _timescaledb_catalog.dimension_slice ORDER BY id;\n id | dimension_id |     range_start      |      range_end      \n----+--------------+----------------------+---------------------\n  1 |            1 |                    1 |                   2\n  2 |            4 |           1073741823 | 9223372036854775807\n  3 |            1 |                    2 |                   3\n  4 |            1 |                    3 |                   4\n  5 |            1 |                    4 |                   5\n  6 |            4 | -9223372036854775808 |          1073741823\n  7 |            1 |                    5 |                   6\n  8 |            1 |                    6 |                   7\n  9 |            2 |                    1 |                   2\n 10 |            5 |           1073741823 | 9223372036854775807\n 11 |            2 |                    2 |                   3\n 12 |            2 |                    3 |                   4\n 13 |            2 |                    4 |                   5\n 14 |            5 | -9223372036854775808 |          1073741823\n 15 |            2 |                    5 |                   6\n 16 |            2 |                    6 |                   7\n 17 |            3 |                    1 |                   2\n 18 |            6 |           1073741823 | 9223372036854775807\n 19 |            3 |                    2 |                   3\n 20 |            3 |                    3 |                   4\n 21 |            3 |                    4 |                   5\n 22 |            6 | -9223372036854775808 |          1073741823\n 23 |            3 |                    5 |                   6\n 24 |            3 |                    6 |                   7\n\n-- Test that truncating chunks works\nSELECT count(*) FROM _timescaledb_internal._hyper_2_7_chunk;\n count \n-------\n     1\n\nTRUNCATE TABLE _timescaledb_internal._hyper_2_7_chunk;\nSELECT count(*) FROM _timescaledb_internal._hyper_2_7_chunk;\n count \n-------\n     0\n\n-- Drop one chunk \"manually\" and verify that dimension slices and\n-- constraints are cleaned up. Each chunk has two constraints and two\n-- dimension slices. Both constraints should be deleted, but only one\n-- slice should be deleted since the space-dimension slice is shared\n-- with other chunks in the same hypertable\nDROP TABLE _timescaledb_internal._hyper_2_7_chunk;\n-- Two constraints deleted compared to above\nSELECT c.table_name, cc.constraint_name, ds.id AS dimension_slice_id, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.chunk_constraint cc ON (c.id = cc.chunk_id)\nFULL OUTER JOIN _timescaledb_catalog.dimension_slice ds ON (ds.id = cc.dimension_slice_id)\nORDER BY c.id;\n    table_name     | constraint_name | dimension_slice_id |     range_start      |      range_end      \n-------------------+-----------------+--------------------+----------------------+---------------------\n _hyper_1_1_chunk  | constraint_1    |                  1 |                    1 |                   2\n _hyper_1_1_chunk  | constraint_2    |                  2 |           1073741823 | 9223372036854775807\n _hyper_1_2_chunk  | constraint_3    |                  3 |                    2 |                   3\n _hyper_1_2_chunk  | constraint_2    |                  2 |           1073741823 | 9223372036854775807\n _hyper_1_3_chunk  | constraint_4    |                  4 |                    3 |                   4\n _hyper_1_3_chunk  | constraint_2    |                  2 |           1073741823 | 9223372036854775807\n _hyper_1_4_chunk  | constraint_5    |                  5 |                    4 |                   5\n _hyper_1_4_chunk  | constraint_6    |                  6 | -9223372036854775808 |          1073741823\n _hyper_1_5_chunk  | constraint_7    |                  7 |                    5 |                   6\n _hyper_1_5_chunk  | constraint_6    |                  6 | -9223372036854775808 |          1073741823\n _hyper_1_6_chunk  | constraint_8    |                  8 |                    6 |                   7\n _hyper_1_6_chunk  | constraint_6    |                  6 | -9223372036854775808 |          1073741823\n _hyper_2_8_chunk  | constraint_11   |                 11 |                    2 |                   3\n _hyper_2_8_chunk  | constraint_10   |                 10 |           1073741823 | 9223372036854775807\n _hyper_2_9_chunk  | constraint_12   |                 12 |                    3 |                   4\n _hyper_2_9_chunk  | constraint_10   |                 10 |           1073741823 | 9223372036854775807\n _hyper_2_10_chunk | constraint_13   |                 13 |                    4 |                   5\n _hyper_2_10_chunk | constraint_14   |                 14 | -9223372036854775808 |          1073741823\n _hyper_2_11_chunk | constraint_15   |                 15 |                    5 |                   6\n _hyper_2_11_chunk | constraint_14   |                 14 | -9223372036854775808 |          1073741823\n _hyper_2_12_chunk | constraint_16   |                 16 |                    6 |                   7\n _hyper_2_12_chunk | constraint_14   |                 14 | -9223372036854775808 |          1073741823\n _hyper_3_13_chunk | constraint_17   |                 17 |                    1 |                   2\n _hyper_3_13_chunk | constraint_18   |                 18 |           1073741823 | 9223372036854775807\n _hyper_3_14_chunk | constraint_19   |                 19 |                    2 |                   3\n _hyper_3_14_chunk | constraint_18   |                 18 |           1073741823 | 9223372036854775807\n _hyper_3_15_chunk | constraint_20   |                 20 |                    3 |                   4\n _hyper_3_15_chunk | constraint_18   |                 18 |           1073741823 | 9223372036854775807\n _hyper_3_16_chunk | constraint_21   |                 21 |                    4 |                   5\n _hyper_3_16_chunk | constraint_22   |                 22 | -9223372036854775808 |          1073741823\n _hyper_3_17_chunk | constraint_23   |                 23 |                    5 |                   6\n _hyper_3_17_chunk | constraint_22   |                 22 | -9223372036854775808 |          1073741823\n _hyper_3_18_chunk | constraint_24   |                 24 |                    6 |                   7\n _hyper_3_18_chunk | constraint_22   |                 22 | -9223372036854775808 |          1073741823\n\n-- Only one dimension slice deleted\nSELECT * FROM _timescaledb_catalog.dimension_slice ORDER BY id;\n id | dimension_id |     range_start      |      range_end      \n----+--------------+----------------------+---------------------\n  1 |            1 |                    1 |                   2\n  2 |            4 |           1073741823 | 9223372036854775807\n  3 |            1 |                    2 |                   3\n  4 |            1 |                    3 |                   4\n  5 |            1 |                    4 |                   5\n  6 |            4 | -9223372036854775808 |          1073741823\n  7 |            1 |                    5 |                   6\n  8 |            1 |                    6 |                   7\n 10 |            5 |           1073741823 | 9223372036854775807\n 11 |            2 |                    2 |                   3\n 12 |            2 |                    3 |                   4\n 13 |            2 |                    4 |                   5\n 14 |            5 | -9223372036854775808 |          1073741823\n 15 |            2 |                    5 |                   6\n 16 |            2 |                    6 |                   7\n 17 |            3 |                    1 |                   2\n 18 |            6 |           1073741823 | 9223372036854775807\n 19 |            3 |                    2 |                   3\n 20 |            3 |                    3 |                   4\n 21 |            3 |                    4 |                   5\n 22 |            6 | -9223372036854775808 |          1073741823\n 23 |            3 |                    5 |                   6\n 24 |            3 |                    6 |                   7\n\n-- We drop all chunks older than timestamp 2 in all hypertable. This\n-- is added only to avoid making the diff for this commit larger than\n-- necessary and make reviews easier.\nSELECT drop_chunks(format('%1$I.%2$I', schema_name, table_name)::regclass, older_than => 2)\n  FROM _timescaledb_catalog.hypertable;\n               drop_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_1_1_chunk\n _timescaledb_internal._hyper_3_13_chunk\n\nSELECT c.table_name, cc.constraint_name, ds.id AS dimension_slice_id, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.chunk_constraint cc ON (c.id = cc.chunk_id)\nFULL OUTER JOIN _timescaledb_catalog.dimension_slice ds ON (ds.id = cc.dimension_slice_id)\nORDER BY c.id;\n    table_name     | constraint_name | dimension_slice_id |     range_start      |      range_end      \n-------------------+-----------------+--------------------+----------------------+---------------------\n _hyper_1_2_chunk  | constraint_3    |                  3 |                    2 |                   3\n _hyper_1_2_chunk  | constraint_2    |                  2 |           1073741823 | 9223372036854775807\n _hyper_1_3_chunk  | constraint_4    |                  4 |                    3 |                   4\n _hyper_1_3_chunk  | constraint_2    |                  2 |           1073741823 | 9223372036854775807\n _hyper_1_4_chunk  | constraint_5    |                  5 |                    4 |                   5\n _hyper_1_4_chunk  | constraint_6    |                  6 | -9223372036854775808 |          1073741823\n _hyper_1_5_chunk  | constraint_7    |                  7 |                    5 |                   6\n _hyper_1_5_chunk  | constraint_6    |                  6 | -9223372036854775808 |          1073741823\n _hyper_1_6_chunk  | constraint_8    |                  8 |                    6 |                   7\n _hyper_1_6_chunk  | constraint_6    |                  6 | -9223372036854775808 |          1073741823\n _hyper_2_8_chunk  | constraint_11   |                 11 |                    2 |                   3\n _hyper_2_8_chunk  | constraint_10   |                 10 |           1073741823 | 9223372036854775807\n _hyper_2_9_chunk  | constraint_12   |                 12 |                    3 |                   4\n _hyper_2_9_chunk  | constraint_10   |                 10 |           1073741823 | 9223372036854775807\n _hyper_2_10_chunk | constraint_13   |                 13 |                    4 |                   5\n _hyper_2_10_chunk | constraint_14   |                 14 | -9223372036854775808 |          1073741823\n _hyper_2_11_chunk | constraint_15   |                 15 |                    5 |                   6\n _hyper_2_11_chunk | constraint_14   |                 14 | -9223372036854775808 |          1073741823\n _hyper_2_12_chunk | constraint_16   |                 16 |                    6 |                   7\n _hyper_2_12_chunk | constraint_14   |                 14 | -9223372036854775808 |          1073741823\n _hyper_3_14_chunk | constraint_19   |                 19 |                    2 |                   3\n _hyper_3_14_chunk | constraint_18   |                 18 |           1073741823 | 9223372036854775807\n _hyper_3_15_chunk | constraint_20   |                 20 |                    3 |                   4\n _hyper_3_15_chunk | constraint_18   |                 18 |           1073741823 | 9223372036854775807\n _hyper_3_16_chunk | constraint_21   |                 21 |                    4 |                   5\n _hyper_3_16_chunk | constraint_22   |                 22 | -9223372036854775808 |          1073741823\n _hyper_3_17_chunk | constraint_23   |                 23 |                    5 |                   6\n _hyper_3_17_chunk | constraint_22   |                 22 | -9223372036854775808 |          1073741823\n _hyper_3_18_chunk | constraint_24   |                 24 |                    6 |                   7\n _hyper_3_18_chunk | constraint_22   |                 22 | -9223372036854775808 |          1073741823\n\nSELECT * FROM _timescaledb_catalog.dimension_slice ORDER BY id;\n id | dimension_id |     range_start      |      range_end      \n----+--------------+----------------------+---------------------\n  2 |            4 |           1073741823 | 9223372036854775807\n  3 |            1 |                    2 |                   3\n  4 |            1 |                    3 |                   4\n  5 |            1 |                    4 |                   5\n  6 |            4 | -9223372036854775808 |          1073741823\n  7 |            1 |                    5 |                   6\n  8 |            1 |                    6 |                   7\n 10 |            5 |           1073741823 | 9223372036854775807\n 11 |            2 |                    2 |                   3\n 12 |            2 |                    3 |                   4\n 13 |            2 |                    4 |                   5\n 14 |            5 | -9223372036854775808 |          1073741823\n 15 |            2 |                    5 |                   6\n 16 |            2 |                    6 |                   7\n 18 |            6 |           1073741823 | 9223372036854775807\n 19 |            3 |                    2 |                   3\n 20 |            3 |                    3 |                   4\n 21 |            3 |                    4 |                   5\n 22 |            6 | -9223372036854775808 |          1073741823\n 23 |            3 |                    5 |                   6\n 24 |            3 |                    6 |                   7\n\nSELECT c.id AS chunk_id, c.hypertable_id, c.schema_name AS chunk_schema, c.table_name AS chunk_table, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\nINNER JOIN  dimension_get_time(h.id) time_dimension ON(true)\nINNER JOIN  _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = time_dimension.id)\nINNER JOIN  _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id AND cc.chunk_id = c.id)\nWHERE h.schema_name = 'public' AND (h.table_name = 'drop_chunk_test1' OR h.table_name = 'drop_chunk_test2')\nORDER BY c.id;\n chunk_id | hypertable_id |     chunk_schema      |    chunk_table    | range_start | range_end \n----------+---------------+-----------------------+-------------------+-------------+-----------\n        2 |             1 | _timescaledb_internal | _hyper_1_2_chunk  |           2 |         3\n        3 |             1 | _timescaledb_internal | _hyper_1_3_chunk  |           3 |         4\n        4 |             1 | _timescaledb_internal | _hyper_1_4_chunk  |           4 |         5\n        5 |             1 | _timescaledb_internal | _hyper_1_5_chunk  |           5 |         6\n        6 |             1 | _timescaledb_internal | _hyper_1_6_chunk  |           6 |         7\n        8 |             2 | _timescaledb_internal | _hyper_2_8_chunk  |           2 |         3\n        9 |             2 | _timescaledb_internal | _hyper_2_9_chunk  |           3 |         4\n       10 |             2 | _timescaledb_internal | _hyper_2_10_chunk |           4 |         5\n       11 |             2 | _timescaledb_internal | _hyper_2_11_chunk |           5 |         6\n       12 |             2 | _timescaledb_internal | _hyper_2_12_chunk |           6 |         7\n\n-- next two calls of show_chunks should give same set of chunks as above when combined\nSELECT show_chunks('drop_chunk_test1');\n              show_chunks               \n----------------------------------------\n _timescaledb_internal._hyper_1_2_chunk\n _timescaledb_internal._hyper_1_3_chunk\n _timescaledb_internal._hyper_1_4_chunk\n _timescaledb_internal._hyper_1_5_chunk\n _timescaledb_internal._hyper_1_6_chunk\n\nSELECT * FROM show_chunks('drop_chunk_test2');\n               show_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_2_8_chunk\n _timescaledb_internal._hyper_2_9_chunk\n _timescaledb_internal._hyper_2_10_chunk\n _timescaledb_internal._hyper_2_11_chunk\n _timescaledb_internal._hyper_2_12_chunk\n\nSELECT * FROM test.relation WHERE schema = '_timescaledb_internal' AND name LIKE '\\_hyper%';\n        schema         |       name        | type  |       owner       \n-----------------------+-------------------+-------+-------------------\n _timescaledb_internal | _hyper_1_2_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_1_3_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_1_4_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_1_5_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_1_6_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_2_10_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_2_11_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_2_12_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_2_8_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_2_9_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_3_14_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_3_15_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_3_16_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_3_17_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_3_18_chunk | table | default_perm_user\n\n-- show_chunks and drop_chunks output should be the same\n\\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test1\\', older_than => 3)::NAME'\n\\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test1\\', older_than => 3)::NAME'\n\\set ECHO errors\n Different Rows | Total Rows from Query 1 | Total Rows from Query 2 \n----------------+-------------------------+-------------------------\n              0 |                       1 |                       1\n\nSELECT c.id AS chunk_id, c.hypertable_id, c.schema_name AS chunk_schema, c.table_name AS chunk_table, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\nINNER JOIN  dimension_get_time(h.id) time_dimension ON(true)\nINNER JOIN  _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = time_dimension.id)\nINNER JOIN  _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id AND cc.chunk_id = c.id)\nWHERE h.schema_name = 'public' AND (h.table_name = 'drop_chunk_test1' OR h.table_name = 'drop_chunk_test2')\nORDER BY c.id;\n chunk_id | hypertable_id |     chunk_schema      |    chunk_table    | range_start | range_end \n----------+---------------+-----------------------+-------------------+-------------+-----------\n        3 |             1 | _timescaledb_internal | _hyper_1_3_chunk  |           3 |         4\n        4 |             1 | _timescaledb_internal | _hyper_1_4_chunk  |           4 |         5\n        5 |             1 | _timescaledb_internal | _hyper_1_5_chunk  |           5 |         6\n        6 |             1 | _timescaledb_internal | _hyper_1_6_chunk  |           6 |         7\n        8 |             2 | _timescaledb_internal | _hyper_2_8_chunk  |           2 |         3\n        9 |             2 | _timescaledb_internal | _hyper_2_9_chunk  |           3 |         4\n       10 |             2 | _timescaledb_internal | _hyper_2_10_chunk |           4 |         5\n       11 |             2 | _timescaledb_internal | _hyper_2_11_chunk |           5 |         6\n       12 |             2 | _timescaledb_internal | _hyper_2_12_chunk |           6 |         7\n\nSELECT * FROM test.relation WHERE schema = '_timescaledb_internal' AND name LIKE '\\_hyper%';\n        schema         |       name        | type  |       owner       \n-----------------------+-------------------+-------+-------------------\n _timescaledb_internal | _hyper_1_3_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_1_4_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_1_5_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_1_6_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_2_10_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_2_11_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_2_12_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_2_8_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_2_9_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_3_14_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_3_15_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_3_16_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_3_17_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_3_18_chunk | table | default_perm_user\n\n-- next two calls of show_chunks should give same set of chunks as above when combined\nSELECT show_chunks('drop_chunk_test1');\n              show_chunks               \n----------------------------------------\n _timescaledb_internal._hyper_1_3_chunk\n _timescaledb_internal._hyper_1_4_chunk\n _timescaledb_internal._hyper_1_5_chunk\n _timescaledb_internal._hyper_1_6_chunk\n\nSELECT * FROM show_chunks('drop_chunk_test2');\n               show_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_2_8_chunk\n _timescaledb_internal._hyper_2_9_chunk\n _timescaledb_internal._hyper_2_10_chunk\n _timescaledb_internal._hyper_2_11_chunk\n _timescaledb_internal._hyper_2_12_chunk\n\n-- 2,147,483,647 is the largest int so this tests that BIGINTs work\n-- show_chunks and drop_chunks output should be the same\n\\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test3\\', older_than => 2147483648)::NAME'\n\\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test3\\', older_than => 2147483648)::NAME'\n\\set ECHO errors\n Different Rows | Total Rows from Query 1 | Total Rows from Query 2 \n----------------+-------------------------+-------------------------\n              0 |                       5 |                       5\n\nSELECT c.id AS chunk_id, c.hypertable_id, c.schema_name AS chunk_schema, c.table_name AS chunk_table, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\nINNER JOIN  dimension_get_time(h.id) time_dimension ON(true)\nINNER JOIN  _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = time_dimension.id)\nINNER JOIN  _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id AND cc.chunk_id = c.id)\nWHERE h.schema_name = 'public' AND (h.table_name = 'drop_chunk_test1' OR h.table_name = 'drop_chunk_test2' OR h.table_name = 'drop_chunk_test3')\nORDER BY c.id;\n chunk_id | hypertable_id |     chunk_schema      |    chunk_table    | range_start | range_end \n----------+---------------+-----------------------+-------------------+-------------+-----------\n        3 |             1 | _timescaledb_internal | _hyper_1_3_chunk  |           3 |         4\n        4 |             1 | _timescaledb_internal | _hyper_1_4_chunk  |           4 |         5\n        5 |             1 | _timescaledb_internal | _hyper_1_5_chunk  |           5 |         6\n        6 |             1 | _timescaledb_internal | _hyper_1_6_chunk  |           6 |         7\n        8 |             2 | _timescaledb_internal | _hyper_2_8_chunk  |           2 |         3\n        9 |             2 | _timescaledb_internal | _hyper_2_9_chunk  |           3 |         4\n       10 |             2 | _timescaledb_internal | _hyper_2_10_chunk |           4 |         5\n       11 |             2 | _timescaledb_internal | _hyper_2_11_chunk |           5 |         6\n       12 |             2 | _timescaledb_internal | _hyper_2_12_chunk |           6 |         7\n\nSELECT * FROM test.relation WHERE schema = '_timescaledb_internal' AND name LIKE '\\_hyper%';\n        schema         |       name        | type  |       owner       \n-----------------------+-------------------+-------+-------------------\n _timescaledb_internal | _hyper_1_3_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_1_4_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_1_5_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_1_6_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_2_10_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_2_11_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_2_12_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_2_8_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_2_9_chunk  | table | default_perm_user\n\n\\set ON_ERROR_STOP 0\n-- should error because no hypertable\nSELECT drop_chunks('drop_chunk_test4', older_than => 5);\nERROR:  relation \"drop_chunk_test4\" does not exist at character 20\nSELECT show_chunks('drop_chunk_test4');\nERROR:  relation \"drop_chunk_test4\" does not exist at character 20\nSELECT show_chunks('drop_chunk_test4', 5);\nERROR:  relation \"drop_chunk_test4\" does not exist at character 20\n\\set ON_ERROR_STOP 1\nDROP TABLE _timescaledb_internal._hyper_1_6_chunk;\nSELECT c.id AS chunk_id, c.hypertable_id, c.schema_name AS chunk_schema, c.table_name AS chunk_table, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\nINNER JOIN  dimension_get_time(h.id) time_dimension ON(true)\nINNER JOIN  _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = time_dimension.id)\nINNER JOIN  _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id AND cc.chunk_id = c.id)\nWHERE h.schema_name = 'public' AND (h.table_name = 'drop_chunk_test1' OR h.table_name = 'drop_chunk_test2')\nORDER BY c.id;\n chunk_id | hypertable_id |     chunk_schema      |    chunk_table    | range_start | range_end \n----------+---------------+-----------------------+-------------------+-------------+-----------\n        3 |             1 | _timescaledb_internal | _hyper_1_3_chunk  |           3 |         4\n        4 |             1 | _timescaledb_internal | _hyper_1_4_chunk  |           4 |         5\n        5 |             1 | _timescaledb_internal | _hyper_1_5_chunk  |           5 |         6\n        8 |             2 | _timescaledb_internal | _hyper_2_8_chunk  |           2 |         3\n        9 |             2 | _timescaledb_internal | _hyper_2_9_chunk  |           3 |         4\n       10 |             2 | _timescaledb_internal | _hyper_2_10_chunk |           4 |         5\n       11 |             2 | _timescaledb_internal | _hyper_2_11_chunk |           5 |         6\n       12 |             2 | _timescaledb_internal | _hyper_2_12_chunk |           6 |         7\n\nSELECT * FROM test.relation WHERE schema = '_timescaledb_internal' AND name LIKE '\\_hyper%';\n        schema         |       name        | type  |       owner       \n-----------------------+-------------------+-------+-------------------\n _timescaledb_internal | _hyper_1_3_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_1_4_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_1_5_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_2_10_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_2_11_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_2_12_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_2_8_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_2_9_chunk  | table | default_perm_user\n\n-- newer_than tests\n-- show_chunks and drop_chunks output should be the same\n\\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test1\\', newer_than=>5)::NAME'\n\\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test1\\', newer_than=>5, verbose => true)::NAME'\n\\set ECHO errors\npsql:include/query_result_test_equal.sql:16: INFO:  dropping chunk _timescaledb_internal._hyper_1_5_chunk\n Different Rows | Total Rows from Query 1 | Total Rows from Query 2 \n----------------+-------------------------+-------------------------\n              0 |                       1 |                       1\n\nSELECT c.id AS chunk_id, c.hypertable_id, c.schema_name AS chunk_schema, c.table_name AS chunk_table, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\nINNER JOIN  dimension_get_time(h.id) time_dimension ON(true)\nINNER JOIN  _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = time_dimension.id)\nINNER JOIN  _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id AND cc.chunk_id = c.id)\nWHERE h.schema_name = 'public' AND (h.table_name = 'drop_chunk_test1')\nORDER BY c.id;\n chunk_id | hypertable_id |     chunk_schema      |   chunk_table    | range_start | range_end \n----------+---------------+-----------------------+------------------+-------------+-----------\n        3 |             1 | _timescaledb_internal | _hyper_1_3_chunk |           3 |         4\n        4 |             1 | _timescaledb_internal | _hyper_1_4_chunk |           4 |         5\n\nSELECT show_chunks('drop_chunk_test1');\n              show_chunks               \n----------------------------------------\n _timescaledb_internal._hyper_1_3_chunk\n _timescaledb_internal._hyper_1_4_chunk\n\nSELECT * FROM test.relation WHERE schema = '_timescaledb_internal' AND name LIKE '_hyper%';\n        schema         |       name        | type  |       owner       \n-----------------------+-------------------+-------+-------------------\n _timescaledb_internal | _hyper_1_3_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_1_4_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_2_10_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_2_11_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_2_12_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_2_8_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_2_9_chunk  | table | default_perm_user\n\n-- show_chunks and drop_chunks output should be the same\n\\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test1\\', older_than=>4, newer_than=>3)::NAME'\n\\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test1\\', older_than=>4, newer_than=>3)::NAME'\n\\set ECHO errors\n Different Rows | Total Rows from Query 1 | Total Rows from Query 2 \n----------------+-------------------------+-------------------------\n              0 |                       1 |                       1\n\nSELECT c.id AS chunk_id, c.hypertable_id, c.schema_name AS chunk_schema, c.table_name AS chunk_table, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\nINNER JOIN  dimension_get_time(h.id) time_dimension ON(true)\nINNER JOIN  _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = time_dimension.id)\nINNER JOIN  _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id AND cc.chunk_id = c.id)\nWHERE h.schema_name = 'public' AND (h.table_name = 'drop_chunk_test1')\nORDER BY c.id;\n chunk_id | hypertable_id |     chunk_schema      |   chunk_table    | range_start | range_end \n----------+---------------+-----------------------+------------------+-------------+-----------\n        4 |             1 | _timescaledb_internal | _hyper_1_4_chunk |           4 |         5\n\n-- the call of show_chunks should give same set of chunks as above\nSELECT show_chunks('drop_chunk_test1');\n              show_chunks               \n----------------------------------------\n _timescaledb_internal._hyper_1_4_chunk\n\nSELECT c.id AS chunk_id, c.hypertable_id, c.schema_name AS chunk_schema, c.table_name AS chunk_table, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\nINNER JOIN  dimension_get_time(h.id) time_dimension ON(true)\nINNER JOIN  _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = time_dimension.id)\nINNER JOIN  _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id AND cc.chunk_id = c.id)\nWHERE h.schema_name = 'public'\nORDER BY c.id;\n chunk_id | hypertable_id |     chunk_schema      |    chunk_table    | range_start | range_end \n----------+---------------+-----------------------+-------------------+-------------+-----------\n        4 |             1 | _timescaledb_internal | _hyper_1_4_chunk  |           4 |         5\n        8 |             2 | _timescaledb_internal | _hyper_2_8_chunk  |           2 |         3\n        9 |             2 | _timescaledb_internal | _hyper_2_9_chunk  |           3 |         4\n       10 |             2 | _timescaledb_internal | _hyper_2_10_chunk |           4 |         5\n       11 |             2 | _timescaledb_internal | _hyper_2_11_chunk |           5 |         6\n       12 |             2 | _timescaledb_internal | _hyper_2_12_chunk |           6 |         7\n\n-- We support show/drop chunks using timestamps/interval even with integer partitioning\n-- the chunk creation time gets used for these. But we need to use \"created_before, created_after\"\n-- for these\n\\set ON_ERROR_STOP 0\nSELECT show_chunks('drop_chunk_test3', older_than => now());\nERROR:  invalid time argument type \"timestamp with time zone\"\nSELECT show_chunks('drop_chunk_test2', older_than => now());\nERROR:  invalid time argument type \"timestamp with time zone\"\nSELECT show_chunks('drop_chunk_test1', newer_than => INTERVAL '15 minutes');\nERROR:  invalid time argument type \"interval\"\nSELECT show_chunks('drop_chunk_test1', older_than => now(), newer_than => INTERVAL '15 minutes');\nERROR:  invalid time argument type \"timestamp with time zone\"\nSELECT drop_chunks('drop_chunk_test1', older_than => now());\nERROR:  invalid time argument type \"timestamp with time zone\"\n-- mix of older_than/newer_than and created_after/created_before doesn't work\nSELECT show_chunks('drop_chunk_test1', older_than => now(), created_after => INTERVAL '15 minutes');\nERROR:  invalid time argument type \"timestamp with time zone\"\nSELECT show_chunks('drop_chunk_test1', created_before => now(), newer_than => INTERVAL '15 minutes');\nERROR:  invalid time argument type \"interval\"\n\\set ON_ERROR_STOP 1\nSELECT show_chunks('drop_chunk_test3', created_before => now() + INTERVAL '1 hour');\n show_chunks \n-------------\n\nSELECT show_chunks('drop_chunk_test2', created_before => now() + INTERVAL '1 hour');\n               show_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_2_8_chunk\n _timescaledb_internal._hyper_2_9_chunk\n _timescaledb_internal._hyper_2_10_chunk\n _timescaledb_internal._hyper_2_11_chunk\n _timescaledb_internal._hyper_2_12_chunk\n\nSELECT show_chunks('drop_chunk_test1', created_after => INTERVAL '15 minutes');\n              show_chunks               \n----------------------------------------\n _timescaledb_internal._hyper_1_4_chunk\n\nSELECT show_chunks('drop_chunk_test1', created_before => now() + INTERVAL '1 hour', created_after => INTERVAL '1 hour');\n              show_chunks               \n----------------------------------------\n _timescaledb_internal._hyper_1_4_chunk\n\nSELECT drop_chunks('drop_chunk_test1', created_before => now() + INTERVAL '1 hour');\n              drop_chunks               \n----------------------------------------\n _timescaledb_internal._hyper_1_4_chunk\n\nSELECT drop_chunks(format('%1$I.%2$I', schema_name, table_name)::regclass, older_than => 5, newer_than => 4)\n  FROM _timescaledb_catalog.hypertable WHERE schema_name = 'public';\n               drop_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_2_10_chunk\n\nCREATE TABLE PUBLIC.drop_chunk_test_ts(time timestamp, temp float8, device_id text);\nSELECT create_hypertable('public.drop_chunk_test_ts', 'time', chunk_time_interval => interval '1 minute', create_default_indexes=>false);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n        create_hypertable        \n---------------------------------\n (4,public,drop_chunk_test_ts,t)\n\nCREATE TABLE PUBLIC.drop_chunk_test_tstz(time timestamptz, temp float8, device_id text);\nSELECT create_hypertable('public.drop_chunk_test_tstz', 'time', chunk_time_interval => interval '1 minute', create_default_indexes=>false);\n         create_hypertable         \n-----------------------------------\n (5,public,drop_chunk_test_tstz,t)\n\nSET timezone = '+1';\nINSERT INTO PUBLIC.drop_chunk_test_ts VALUES(now()-INTERVAL '5 minutes', 1.0, 'dev1');\nINSERT INTO PUBLIC.drop_chunk_test_ts VALUES(now()+INTERVAL '5 minutes', 1.0, 'dev1');\nINSERT INTO PUBLIC.drop_chunk_test_tstz VALUES(now()-INTERVAL '5 minutes', 1.0, 'dev1');\nINSERT INTO PUBLIC.drop_chunk_test_tstz VALUES(now()+INTERVAL '5 minutes', 1.0, 'dev1');\nSELECT * FROM test.show_subtables('drop_chunk_test_ts');\n                  Child                  | Tablespace \n-----------------------------------------+------------\n _timescaledb_internal._hyper_4_19_chunk | \n _timescaledb_internal._hyper_4_20_chunk | \n\nSELECT * FROM test.show_subtables('drop_chunk_test_tstz');\n                  Child                  | Tablespace \n-----------------------------------------+------------\n _timescaledb_internal._hyper_5_21_chunk | \n _timescaledb_internal._hyper_5_22_chunk | \n\nBEGIN;\n    SELECT show_chunks('drop_chunk_test_ts');\n               show_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_4_19_chunk\n _timescaledb_internal._hyper_4_20_chunk\n\n    SELECT show_chunks('drop_chunk_test_ts', now()::timestamp-interval '1 minute');\n               show_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_4_19_chunk\n\n-- show_chunks and drop_chunks output should be the same\n    \\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test_ts\\', newer_than => interval \\'1 minute\\')::NAME'\n    \\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test_ts\\', newer_than => interval \\'1 minute\\')::NAME'\n    \\set ECHO errors\n Different Rows | Total Rows from Query 1 | Total Rows from Query 2 \n----------------+-------------------------+-------------------------\n              0 |                       1 |                       1\n\n    \\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test_ts\\', older_than => interval \\'6 minute\\')::NAME'\n    \\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test_ts\\', older_than => interval \\'6 minute\\')::NAME'\n    \\set ECHO errors\n Different Rows | Total Rows from Query 1 | Total Rows from Query 2 \n----------------+-------------------------+-------------------------\n              0 |                       0 |                       0\n\n    SELECT * FROM test.show_subtables('drop_chunk_test_ts');\n                  Child                  | Tablespace \n-----------------------------------------+------------\n _timescaledb_internal._hyper_4_19_chunk | \n\n    \\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test_ts\\', older_than => interval \\'1 minute\\')::NAME'\n    \\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test_ts\\', interval \\'1 minute\\')::NAME'\n    \\set ECHO errors\n Different Rows | Total Rows from Query 1 | Total Rows from Query 2 \n----------------+-------------------------+-------------------------\n              0 |                       1 |                       1\n\n    SELECT * FROM test.show_subtables('drop_chunk_test_ts');\n Child | Tablespace \n-------+------------\n\n    SELECT show_chunks('drop_chunk_test_tstz');\n               show_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_5_21_chunk\n _timescaledb_internal._hyper_5_22_chunk\n\n    SELECT show_chunks('drop_chunk_test_tstz', older_than => now() - interval '1 minute', newer_than => now() - interval '6 minute');\n               show_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_5_21_chunk\n\n    SELECT show_chunks('drop_chunk_test_tstz', newer_than => now() - interval '1 minute');\n               show_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_5_22_chunk\n\n    SELECT show_chunks('drop_chunk_test_tstz', older_than => now() - interval '1 minute');\n               show_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_5_21_chunk\n\n    \\set QUERY1 'SELECT show_chunks(older_than => interval \\'1 minute\\', relation => \\'drop_chunk_test_tstz\\')::NAME'\n    \\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test_tstz\\', interval \\'1 minute\\')::NAME'\n    \\set ECHO errors\n Different Rows | Total Rows from Query 1 | Total Rows from Query 2 \n----------------+-------------------------+-------------------------\n              0 |                       1 |                       1\n\n    SELECT * FROM test.show_subtables('drop_chunk_test_tstz');\n                  Child                  | Tablespace \n-----------------------------------------+------------\n _timescaledb_internal._hyper_5_22_chunk | \n\nROLLBACK;\nBEGIN;\n-- show_chunks and drop_chunks output should be the same\n    \\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test_ts\\', newer_than => interval \\'6 minute\\')::NAME'\n    \\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test_ts\\', newer_than => interval \\'6 minute\\')::NAME'\n    \\set ECHO errors\n Different Rows | Total Rows from Query 1 | Total Rows from Query 2 \n----------------+-------------------------+-------------------------\n              0 |                       2 |                       2\n\n    SELECT * FROM test.show_subtables('drop_chunk_test_ts');\n Child | Tablespace \n-------+------------\n\nROLLBACK;\nBEGIN;\n-- show_chunks and drop_chunks output should be the same\n    \\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test_ts\\', older_than => interval \\'1 minute\\')::NAME'\n    \\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test_ts\\', older_than => interval \\'1 minute\\')::NAME'\n    \\set ECHO errors\n Different Rows | Total Rows from Query 1 | Total Rows from Query 2 \n----------------+-------------------------+-------------------------\n              0 |                       1 |                       1\n\n    SELECT * FROM test.show_subtables('drop_chunk_test_ts');\n                  Child                  | Tablespace \n-----------------------------------------+------------\n _timescaledb_internal._hyper_4_20_chunk | \n\n    \\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test_tstz\\', older_than => interval \\'1 minute\\')::NAME'\n    \\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test_tstz\\', older_than => interval \\'1 minute\\')::NAME'\n    \\set ECHO errors\n Different Rows | Total Rows from Query 1 | Total Rows from Query 2 \n----------------+-------------------------+-------------------------\n              0 |                       1 |                       1\n\n    SELECT * FROM test.show_subtables('drop_chunk_test_tstz');\n                  Child                  | Tablespace \n-----------------------------------------+------------\n _timescaledb_internal._hyper_5_22_chunk | \n\nROLLBACK;\nBEGIN;\n-- show_chunks and drop_chunks output should be the same\n    \\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test_ts\\', older_than => now()::timestamp-interval \\'1 minute\\')::NAME'\n    \\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test_ts\\', older_than => now()::timestamp-interval \\'1 minute\\')::NAME'\n    \\set ECHO errors\n Different Rows | Total Rows from Query 1 | Total Rows from Query 2 \n----------------+-------------------------+-------------------------\n              0 |                       1 |                       1\n\n    SELECT * FROM test.show_subtables('drop_chunk_test_ts');\n                  Child                  | Tablespace \n-----------------------------------------+------------\n _timescaledb_internal._hyper_4_20_chunk | \n\n    \\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test_tstz\\', older_than => now()-interval \\'1 minute\\')::NAME'\n    \\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test_tstz\\', older_than => now()-interval \\'1 minute\\')::NAME'\n    \\set ECHO errors\n Different Rows | Total Rows from Query 1 | Total Rows from Query 2 \n----------------+-------------------------+-------------------------\n              0 |                       1 |                       1\n\n    SELECT * FROM test.show_subtables('drop_chunk_test_tstz');\n                  Child                  | Tablespace \n-----------------------------------------+------------\n _timescaledb_internal._hyper_5_22_chunk | \n\nROLLBACK;\nSELECT * FROM test.relation WHERE schema = '_timescaledb_internal' AND name LIKE '\\_hyper%';\n        schema         |       name        | type  |       owner       \n-----------------------+-------------------+-------+-------------------\n _timescaledb_internal | _hyper_2_11_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_2_12_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_2_8_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_2_9_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_4_19_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_4_20_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_5_21_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_5_22_chunk | table | default_perm_user\n\n\\set ON_ERROR_STOP 0\nSELECT drop_chunks(interval '1 minute');\nERROR:  function drop_chunks(interval) does not exist at character 8\nSELECT drop_chunks('drop_chunk_test_ts', (now()-interval '1 minute'));\nERROR:  invalid time argument type \"timestamp with time zone\"\nSELECT drop_chunks('drop_chunk_test3', verbose => true);\nERROR:  invalid time range for dropping chunks\nSELECT drop_chunks('drop_chunk_test3', interval '1 minute');\nERROR:  invalid time argument type \"interval\"\n\\set ON_ERROR_STOP 1\n-- Interval boundary for INTEGER type columns. It uses chunk creation\n-- time to identify the affected chunks.\nSELECT drop_chunks('drop_chunk_test3', created_after => interval '1 minute');\n drop_chunks \n-------------\n\nSELECT * FROM test.relation WHERE schema = '_timescaledb_internal' AND name LIKE '\\_hyper%';\n        schema         |       name        | type  |       owner       \n-----------------------+-------------------+-------+-------------------\n _timescaledb_internal | _hyper_2_11_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_2_12_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_2_8_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_2_9_chunk  | table | default_perm_user\n _timescaledb_internal | _hyper_4_19_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_4_20_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_5_21_chunk | table | default_perm_user\n _timescaledb_internal | _hyper_5_22_chunk | table | default_perm_user\n\nCREATE TABLE PUBLIC.drop_chunk_test_date(time date, temp float8, device_id text);\nSELECT create_hypertable('public.drop_chunk_test_date', 'time', chunk_time_interval => interval '1 day', create_default_indexes=>false);\n         create_hypertable         \n-----------------------------------\n (6,public,drop_chunk_test_date,t)\n\nSET timezone = '+100';\nINSERT INTO PUBLIC.drop_chunk_test_date VALUES(now()-INTERVAL '2 day', 1.0, 'dev1');\nBEGIN;\n-- show_chunks and drop_chunks output should be the same\n    \\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test_date\\', older_than => interval \\'1 day\\')::NAME'\n    \\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test_date\\', older_than => interval \\'1 day\\')::NAME'\n    \\set ECHO errors\n Different Rows | Total Rows from Query 1 | Total Rows from Query 2 \n----------------+-------------------------+-------------------------\n              0 |                       1 |                       1\n\n    SELECT * FROM test.show_subtables('drop_chunk_test_date');\n Child | Tablespace \n-------+------------\n\nROLLBACK;\nBEGIN;\n-- show_chunks and drop_chunks output should be the same\n    \\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test_date\\', older_than => (now()-interval \\'1 day\\')::date)::NAME'\n    \\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test_date\\', older_than => (now()-interval \\'1 day\\')::date)::NAME'\n    \\set ECHO errors\n Different Rows | Total Rows from Query 1 | Total Rows from Query 2 \n----------------+-------------------------+-------------------------\n              0 |                       1 |                       1\n\n    SELECT * FROM test.show_subtables('drop_chunk_test_date');\n Child | Tablespace \n-------+------------\n\nROLLBACK;\nSET timezone TO '-5';\nCREATE TABLE chunk_id_from_relid_test(time bigint, temp float8, device_id int);\nSELECT hypertable_id FROM create_hypertable('chunk_id_from_relid_test', 'time', chunk_time_interval => 10) \\gset\nINSERT INTO chunk_id_from_relid_test VALUES (0, 1.1, 0), (0, 1.3, 11), (12, 2.0, 0), (12, 0.1, 11);\nSELECT _timescaledb_functions.chunk_id_from_relid(tableoid) FROM chunk_id_from_relid_test;\n chunk_id_from_relid \n---------------------\n                  24\n                  24\n                  25\n                  25\n\nDROP TABLE chunk_id_from_relid_test;\nCREATE TABLE chunk_id_from_relid_test(time bigint, temp float8, device_id int);\nSELECT hypertable_id FROM  create_hypertable('chunk_id_from_relid_test',\n    'time', chunk_time_interval => 10,\n    partitioning_column => 'device_id',\n    number_partitions => 3) \\gset\nINSERT INTO chunk_id_from_relid_test VALUES (0, 1.1, 2), (0, 1.3, 11), (12, 2.0, 2), (12, 0.1, 11);\nSELECT _timescaledb_functions.chunk_id_from_relid(tableoid) FROM chunk_id_from_relid_test;\n chunk_id_from_relid \n---------------------\n                  26\n                  27\n                  28\n                  29\n\n\\set ON_ERROR_STOP 0\nSELECT _timescaledb_functions.chunk_id_from_relid('pg_type'::regclass);\nERROR:  chunk not found\nSELECT _timescaledb_functions.chunk_id_from_relid('chunk_id_from_relid_test'::regclass);\nERROR:  chunk not found\n-- test drop/show_chunks on custom partition types\nCREATE FUNCTION extract_time(a jsonb)\nRETURNS TIMESTAMPTZ\nLANGUAGE SQL\nAS $$ SELECT (a->>'time')::TIMESTAMPTZ $$ IMMUTABLE;\nCREATE TABLE test_weird_type(a jsonb);\nSELECT create_hypertable('test_weird_type', 'a',\n    time_partitioning_func=>'extract_time'::regproc,\n    chunk_time_interval=>'2 hours'::interval);\n      create_hypertable       \n------------------------------\n (9,public,test_weird_type,t)\n\nINSERT INTO test_weird_type VALUES ('{\"time\":\"2019/06/06 1:00+0\"}'), ('{\"time\":\"2019/06/06 5:00+0\"}');\nSELECT * FROM test.show_subtables('test_weird_type');\n                  Child                  | Tablespace \n-----------------------------------------+------------\n _timescaledb_internal._hyper_9_30_chunk | \n _timescaledb_internal._hyper_9_31_chunk | \n\nSELECT show_chunks('test_weird_type', older_than=>'2019/06/06 4:00+0'::TIMESTAMPTZ);\n               show_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_9_30_chunk\n\nSELECT show_chunks('test_weird_type', older_than=>'2019/06/06 10:00+0'::TIMESTAMPTZ);\n               show_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_9_30_chunk\n _timescaledb_internal._hyper_9_31_chunk\n\n-- show_chunks and drop_chunks output should be the same\n\\set QUERY1 'SELECT show_chunks(\\'test_weird_type\\', older_than => \\'2019/06/06 5:00+0\\'::TIMESTAMPTZ)::NAME'\n\\set QUERY2 'SELECT drop_chunks(\\'test_weird_type\\', older_than => \\'2019/06/06 5:00+0\\'::TIMESTAMPTZ)::NAME'\n\\set ECHO errors\n Different Rows | Total Rows from Query 1 | Total Rows from Query 2 \n----------------+-------------------------+-------------------------\n              0 |                       1 |                       1\n\nSELECT * FROM test.show_subtables('test_weird_type');\n                  Child                  | Tablespace \n-----------------------------------------+------------\n _timescaledb_internal._hyper_9_31_chunk | \n\nSELECT show_chunks('test_weird_type', older_than=>'2019/06/06 4:00+0'::TIMESTAMPTZ);\n show_chunks \n-------------\n\nSELECT show_chunks('test_weird_type', older_than=>'2019/06/06 10:00+0'::TIMESTAMPTZ);\n               show_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_9_31_chunk\n\n-- show_chunks and drop_chunks output should be the same\n\\set QUERY1 'SELECT show_chunks(\\'test_weird_type\\', older_than => \\'2019/06/06 6:00+0\\'::TIMESTAMPTZ)::NAME'\n\\set QUERY2 'SELECT drop_chunks(\\'test_weird_type\\', older_than => \\'2019/06/06 6:00+0\\'::TIMESTAMPTZ)::NAME'\n\\set ECHO errors\n Different Rows | Total Rows from Query 1 | Total Rows from Query 2 \n----------------+-------------------------+-------------------------\n              0 |                       1 |                       1\n\nSELECT * FROM test.show_subtables('test_weird_type');\n Child | Tablespace \n-------+------------\n\nSELECT show_chunks('test_weird_type', older_than=>'2019/06/06 10:00+0'::TIMESTAMPTZ);\n show_chunks \n-------------\n\nDROP TABLE test_weird_type;\nCREATE FUNCTION extract_int_time(a jsonb)\nRETURNS BIGINT\nLANGUAGE SQL\nAS $$ SELECT (a->>'time')::BIGINT $$ IMMUTABLE;\nCREATE TABLE test_weird_type_i(a jsonb);\nSELECT create_hypertable('test_weird_type_i', 'a',\n    time_partitioning_func=>'extract_int_time'::regproc,\n    chunk_time_interval=>5);\n        create_hypertable        \n---------------------------------\n (10,public,test_weird_type_i,t)\n\nINSERT INTO test_weird_type_i VALUES ('{\"time\":\"0\"}'), ('{\"time\":\"5\"}');\nSELECT * FROM test.show_subtables('test_weird_type_i');\n                  Child                   | Tablespace \n------------------------------------------+------------\n _timescaledb_internal._hyper_10_32_chunk | \n _timescaledb_internal._hyper_10_33_chunk | \n\nSELECT show_chunks('test_weird_type_i', older_than=>5);\n               show_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_10_32_chunk\n\nSELECT show_chunks('test_weird_type_i', older_than=>10);\n               show_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_10_32_chunk\n _timescaledb_internal._hyper_10_33_chunk\n\n-- show_chunks and drop_chunks output should be the same\n\\set QUERY1 'SELECT show_chunks(\\'test_weird_type_i\\', older_than=>5)::NAME'\n\\set QUERY2 'SELECT drop_chunks(\\'test_weird_type_i\\', older_than => 5)::NAME'\n\\set ECHO errors\n Different Rows | Total Rows from Query 1 | Total Rows from Query 2 \n----------------+-------------------------+-------------------------\n              0 |                       1 |                       1\n\nSELECT * FROM test.show_subtables('test_weird_type_i');\n                  Child                   | Tablespace \n------------------------------------------+------------\n _timescaledb_internal._hyper_10_33_chunk | \n\nSELECT show_chunks('test_weird_type_i', older_than=>5);\n show_chunks \n-------------\n\nSELECT show_chunks('test_weird_type_i', older_than=>10);\n               show_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_10_33_chunk\n\n-- show_chunks and drop_chunks output should be the same\n\\set QUERY1 'SELECT show_chunks(\\'test_weird_type_i\\', older_than=>10)::NAME'\n\\set QUERY2 'SELECT drop_chunks(\\'test_weird_type_i\\', older_than => 10)::NAME'\n\\set ECHO errors\n Different Rows | Total Rows from Query 1 | Total Rows from Query 2 \n----------------+-------------------------+-------------------------\n              0 |                       1 |                       1\n\nSELECT * FROM test.show_subtables('test_weird_type_i');\n Child | Tablespace \n-------+------------\n\nSELECT show_chunks('test_weird_type_i', older_than=>10);\n show_chunks \n-------------\n\nDROP TABLE test_weird_type_i CASCADE;\n\\c  :TEST_DBNAME :ROLE_SUPERUSER\nALTER TABLE drop_chunk_test2 OWNER TO :ROLE_DEFAULT_PERM_USER_2;\n--drop chunks 3 will have a chunk we a dependent object (a view)\n--we create the dependent object now\nINSERT INTO PUBLIC.drop_chunk_test3 VALUES(1, 1.0, 'dev1');\nSELECT c.schema_name as chunk_schema, c.table_name as chunk_table\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\nWHERE h.schema_name = 'public' AND h.table_name = 'drop_chunk_test3'\nORDER BY c.id \\gset\ncreate view dependent_view as SELECT * FROM :\"chunk_schema\".:\"chunk_table\";\ncreate view dependent_view2 as SELECT * FROM :\"chunk_schema\".:\"chunk_table\";\nALTER TABLE drop_chunk_test3 OWNER TO :ROLE_DEFAULT_PERM_USER_2;\n\\c  :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n\\set ON_ERROR_STOP 0\nSELECT drop_chunks('drop_chunk_test1', older_than=>4, newer_than=>3);\nERROR:  must be owner of hypertable \"drop_chunk_test1\"\n--works with modified owner tables\n-- show_chunks and drop_chunks output should be the same\n\\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test2\\', older_than=>4, newer_than=>3)::NAME'\n\\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test2\\', older_than=>4, newer_than=>3)::NAME'\n\\set ECHO errors\n Different Rows | Total Rows from Query 1 | Total Rows from Query 2 \n----------------+-------------------------+-------------------------\n              0 |                       1 |                       1\n\n\\set VERBOSITY default\n--this fails because there are dependent objects\nSELECT drop_chunks('drop_chunk_test3', older_than=>100);\nERROR:  cannot drop table _timescaledb_internal._hyper_3_34_chunk because other objects depend on it\nDETAIL:  view dependent_view depends on table _timescaledb_internal._hyper_3_34_chunk\nview dependent_view2 depends on table _timescaledb_internal._hyper_3_34_chunk\nHINT:  Use DROP ... to drop the dependent objects.\n\\set VERBOSITY terse\n\\c  :TEST_DBNAME :ROLE_SUPERUSER\nDROP VIEW dependent_view;\nDROP VIEW dependent_view2;\n\\c  :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n\\set ON_ERROR_STOP 1\n--drop chunks from hypertable with same name in different schema\n-- order of schema in search_path matters --\n\\c :TEST_DBNAME :ROLE_SUPERUSER\ndrop table chunk_id_from_relid_test;\ndrop table drop_chunk_test1;\ndrop table drop_chunk_test2;\ndrop table drop_chunk_test3;\nCREATE SCHEMA try_schema;\nCREATE SCHEMA test1;\nCREATE SCHEMA test2;\nCREATE SCHEMA test3;\nGRANT CREATE ON SCHEMA try_schema, test1, test2, test3 TO :ROLE_DEFAULT_PERM_USER;\nGRANT USAGE ON SCHEMA try_schema, test1, test2, test3 TO :ROLE_DEFAULT_PERM_USER;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nCREATE TABLE try_schema.drop_chunk_test_date(time date, temp float8, device_id text);\nSELECT create_hypertable('try_schema.drop_chunk_test_date', 'time', chunk_time_interval => interval '1 day', create_default_indexes=>false);\n           create_hypertable            \n----------------------------------------\n (11,try_schema,drop_chunk_test_date,t)\n\nINSERT INTO public.drop_chunk_test_date VALUES( '2020-01-10', 100, 'hello');\nINSERT INTO try_schema.drop_chunk_test_date VALUES( '2020-01-10', 100, 'hello');\nset search_path to try_schema, test1, test2, test3, public;\nSELECT show_chunks('public.drop_chunk_test_date', older_than=>'1 day'::interval);\n               show_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_6_35_chunk\n\nSELECT show_chunks('try_schema.drop_chunk_test_date', older_than=>'1 day'::interval);\n               show_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_11_36_chunk\n\nSELECT drop_chunks('drop_chunk_test_date', older_than=> '1 day'::interval);\n               drop_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_11_36_chunk\n\n-- test drop chunks across two tables within the same schema\nCREATE TABLE test1.hyper1 (time bigint, temp float);\nCREATE TABLE test1.hyper2 (time bigint, temp float);\nSELECT create_hypertable('test1.hyper1', 'time', chunk_time_interval => 10);\n  create_hypertable  \n---------------------\n (12,test1,hyper1,t)\n\nSELECT create_hypertable('test1.hyper2', 'time', chunk_time_interval => 10);\n  create_hypertable  \n---------------------\n (13,test1,hyper2,t)\n\nINSERT INTO test1.hyper1 VALUES (10, 0.5);\nINSERT INTO test1.hyper2 VALUES (10, 0.7);\nSELECT show_chunks('test1.hyper1');\n               show_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_12_37_chunk\n\nSELECT show_chunks('test1.hyper2');\n               show_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_13_38_chunk\n\n-- test drop chunks for given table name across all schemas\nCREATE TABLE test2.hyperx (time bigint, temp float);\nCREATE TABLE test3.hyperx (time bigint, temp float);\nSELECT create_hypertable('test2.hyperx', 'time', chunk_time_interval => 10);\n  create_hypertable  \n---------------------\n (14,test2,hyperx,t)\n\nSELECT create_hypertable('test3.hyperx', 'time', chunk_time_interval => 10);\n  create_hypertable  \n---------------------\n (15,test3,hyperx,t)\n\nINSERT INTO test2.hyperx VALUES (10, 0.5);\nINSERT INTO test3.hyperx VALUES (10, 0.7);\nSELECT show_chunks('test2.hyperx');\n               show_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_14_39_chunk\n\nSELECT show_chunks('test3.hyperx');\n               show_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_15_40_chunk\n\n-- This will only drop from one of the tables since the one that is\n-- first in the search path will hide the other one.\nSELECT drop_chunks('hyperx', older_than => 100);\n               drop_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_14_39_chunk\n\nSELECT show_chunks('test2.hyperx');\n show_chunks \n-------------\n\nSELECT show_chunks('test3.hyperx');\n               show_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_15_40_chunk\n\n-- Check CTAS behavior when internal ALTER TABLE gets fired\nCREATE TABLE PUBLIC.drop_chunk_test4(time bigint, temp float8, device_id text);\nCREATE TABLE drop_chunks_table_id AS SELECT hypertable_id\n      FROM create_hypertable('public.drop_chunk_test4', 'time', chunk_time_interval => 1);\n-- TEST for internal api that drops a single chunk\n-- this drops the table and removes entry from the catalog.\n-- does not affect any materialized cagg data\nINSERT INTO test1.hyper1 VALUES (20, 0.5);\nSELECT chunk_schema as \"CHSCHEMA\",  chunk_name as \"CHNAME\"\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'hyper1' and hypertable_schema = 'test1'\nORDER BY chunk_name ;\n       CHSCHEMA        |       CHNAME       \n-----------------------+--------------------\n _timescaledb_internal | _hyper_12_37_chunk\n _timescaledb_internal | _hyper_12_41_chunk\n\n--drop one of the chunks\nSELECT chunk_schema || '.' || chunk_name as \"CHNAME\"\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'hyper1' and hypertable_schema = 'test1'\nORDER BY chunk_name LIMIT 1\n\\gset\nSELECT  _timescaledb_functions.drop_chunk(:'CHNAME');\n drop_chunk \n------------\n t\n\nSELECT chunk_schema as \"CHSCHEMA\",  chunk_name as \"CHNAME\"\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'hyper1' and hypertable_schema = 'test1'\nORDER BY chunk_name ;\n       CHSCHEMA        |       CHNAME       \n-----------------------+--------------------\n _timescaledb_internal | _hyper_12_41_chunk\n\n-- \"created_before/after\" can be used with time partitioning in drop/show chunks\nSELECT show_chunks('drop_chunk_test_tstz', created_before => now() - INTERVAL '1 hour');\n show_chunks \n-------------\n\nSELECT drop_chunks('drop_chunk_test_tstz', created_before => now() + INTERVAL '1 hour');\n               drop_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_5_21_chunk\n _timescaledb_internal._hyper_5_22_chunk\n\nSELECT show_chunks('drop_chunk_test_ts');\n               show_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_4_19_chunk\n _timescaledb_internal._hyper_4_20_chunk\n\n-- \"created_before/after\" accept timestamptz even though partitioning col is just\n-- timestamp\nSELECT show_chunks('drop_chunk_test_ts', created_after => now() - INTERVAL '1 hour', created_before => now());\n               show_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_4_19_chunk\n _timescaledb_internal._hyper_4_20_chunk\n\nSELECT drop_chunks('drop_chunk_test_ts', created_after => INTERVAL '1 hour', created_before => now());\n               drop_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_4_19_chunk\n _timescaledb_internal._hyper_4_20_chunk\n\n-- Test views on top of hypertables\nCREATE TABLE view_test (project_id INT, ts TIMESTAMPTZ NOT NULL);\nSELECT create_hypertable('view_test', by_range('ts', INTERVAL '1 day'));\n create_hypertable \n-------------------\n (17,t)\n\n-- exactly one partition per project_id\nSELECT * FROM add_dimension('view_test', 'project_id', chunk_time_interval => 1); -- exactly one partition per project; works for *integer* types\n dimension_id | schema_name | table_name | column_name | created \n--------------+-------------+------------+-------------+---------\n           22 | try_schema  | view_test  | project_id  | t\n\nINSERT INTO view_test (project_id, ts)\nSELECT g % 25 + 1 AS project_id, i.ts + (g * interval '1 week') / i.total AS ts\nFROM (SELECT timestamptz '2024-01-01 00:00:00+0', 600) i(ts, total),\ngenerate_series(1, i.total) g;\n-- Create a view on top of this hypertable\nCREATE VIEW test_view_part_few AS SELECT project_id,\n    ts\n   FROM view_test\n  WHERE project_id = ANY (ARRAY[5, 10, 15]);\n-- Complicated query on a view involving a range check and a sort\nSELECT * FROM test_view_part_few WHERE ts BETWEEN '2024-01-04 00:00:00+00'AND '2024-01-05 00:00:00' ORDER BY ts LIMIT 1000;\n project_id |              ts              \n------------+------------------------------\n         10 | Wed Jan 03 16:31:12 2024 PST\n         15 | Wed Jan 03 17:55:12 2024 PST\n          5 | Wed Jan 03 22:07:12 2024 PST\n         10 | Wed Jan 03 23:31:12 2024 PST\n         15 | Thu Jan 04 00:55:12 2024 PST\n          5 | Thu Jan 04 05:07:12 2024 PST\n         10 | Thu Jan 04 06:31:12 2024 PST\n         15 | Thu Jan 04 07:55:12 2024 PST\n          5 | Thu Jan 04 12:07:12 2024 PST\n         10 | Thu Jan 04 13:31:12 2024 PST\n         15 | Thu Jan 04 14:55:12 2024 PST\n          5 | Thu Jan 04 19:07:12 2024 PST\n         10 | Thu Jan 04 20:31:12 2024 PST\n         15 | Thu Jan 04 21:55:12 2024 PST\n\n-- Test chunk_status_text function\nCREATE TABLE chunk_status_test(time timestamptz) WITH (tsdb.hypertable,tsdb.partition_column='time',tsdb.columnstore=false);\nINSERT INTO chunk_status_test VALUES ('2025-01-01'),('2025-02-01'),('2025-03-01');\nSELECT _timescaledb_functions.chunk_status_text(i) FROM generate_series(0,15) i;\n           chunk_status_text           \n---------------------------------------\n {}\n {COMPRESSED}\n {UNORDERED}\n {COMPRESSED,UNORDERED}\n {FROZEN}\n {COMPRESSED,FROZEN}\n {UNORDERED,FROZEN}\n {COMPRESSED,UNORDERED,FROZEN}\n {PARTIAL}\n {COMPRESSED,PARTIAL}\n {UNORDERED,PARTIAL}\n {COMPRESSED,UNORDERED,PARTIAL}\n {FROZEN,PARTIAL}\n {COMPRESSED,FROZEN,PARTIAL}\n {UNORDERED,FROZEN,PARTIAL}\n {COMPRESSED,UNORDERED,FROZEN,PARTIAL}\n\nSELECT chunk, _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('chunk_status_test') chunk;\n                   chunk                   | chunk_status_text \n-------------------------------------------+-------------------\n _timescaledb_internal._hyper_18_218_chunk | {}\n _timescaledb_internal._hyper_18_219_chunk | {}\n _timescaledb_internal._hyper_18_220_chunk | {}\n\nSELECT _timescaledb_functions.chunk_status_text(NULL::int);\n chunk_status_text \n-------------------\n \n\nSELECT _timescaledb_functions.chunk_status_text(NULL::regclass);\n chunk_status_text \n-------------------\n \n\n\\set ON_ERROR_STOP 0\nSELECT _timescaledb_functions.chunk_status_text(-1);\nERROR:  invalid chunk status -1\nSELECT _timescaledb_functions.chunk_status_text(16);\nERROR:  invalid chunk status 16\nSELECT _timescaledb_functions.chunk_status_text(1000);\nERROR:  invalid chunk status 1000\nSELECT _timescaledb_functions.chunk_status_text(0::regclass);\nERROR:  invalid Oid\nSELECT _timescaledb_functions.chunk_status_text('pg_class'::regclass);\nERROR:  chunk not found\n\\set ON_ERROR_STOP 1\n-- Test that function exists and returns an array type\nSELECT pg_typeof(_timescaledb_functions.chunk_status_text(0));\n pg_typeof \n-----------\n text[]\n\n"
  },
  {
    "path": "test/expected/chunks.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\unset ECHO\n"
  },
  {
    "path": "test/expected/cluster.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE cluster_test(time timestamptz, temp float, location int);\nSELECT create_hypertable('cluster_test', 'time', chunk_time_interval => interval '1 day');\n     create_hypertable     \n---------------------------\n (1,public,cluster_test,t)\n\n-- Show default indexes\nSELECT * FROM test.show_indexes('cluster_test');\n         Index         | Columns | Expr | Unique | Primary | Exclusion | Tablespace \n-----------------------+---------+------+--------+---------+-----------+------------\n cluster_test_time_idx | {time}  |      | f      | f       | f         | \n\n-- Create two chunks\nINSERT INTO cluster_test VALUES ('2017-01-20T09:00:01', 23.4, 1),\n       ('2017-01-21T09:00:01', 21.3, 2);\n-- Run cluster\nCLUSTER VERBOSE cluster_test USING cluster_test_time_idx;\nINFO:  clustering \"_timescaledb_internal._hyper_1_1_chunk\" using index scan on \"_hyper_1_1_chunk_cluster_test_time_idx\"\nINFO:  clustering \"_timescaledb_internal._hyper_1_2_chunk\" using index scan on \"_hyper_1_2_chunk_cluster_test_time_idx\"\n-- Create a third chunk\nINSERT INTO cluster_test VALUES ('2017-01-22T09:00:01', 19.5, 3);\n-- Show clustered indexes\nSELECT indexrelid::regclass, indisclustered\nFROM pg_index\nWHERE indisclustered = true ORDER BY 1;\n                          indexrelid                          | indisclustered \n--------------------------------------------------------------+----------------\n cluster_test_time_idx                                        | t\n _timescaledb_internal._hyper_1_1_chunk_cluster_test_time_idx | t\n _timescaledb_internal._hyper_1_2_chunk_cluster_test_time_idx | t\n\n-- Reorder just our table\nCLUSTER VERBOSE cluster_test;\nINFO:  clustering \"_timescaledb_internal._hyper_1_1_chunk\" using sequential scan and sort\nINFO:  clustering \"_timescaledb_internal._hyper_1_2_chunk\" using sequential scan and sort\nINFO:  clustering \"_timescaledb_internal._hyper_1_3_chunk\" using index scan on \"_hyper_1_3_chunk_cluster_test_time_idx\"\n-- Show clustered indexes, including new chunk\nSELECT indexrelid::regclass, indisclustered\nFROM pg_index\nWHERE indisclustered = true ORDER BY 1;\n                          indexrelid                          | indisclustered \n--------------------------------------------------------------+----------------\n cluster_test_time_idx                                        | t\n _timescaledb_internal._hyper_1_1_chunk_cluster_test_time_idx | t\n _timescaledb_internal._hyper_1_2_chunk_cluster_test_time_idx | t\n _timescaledb_internal._hyper_1_3_chunk_cluster_test_time_idx | t\n\n-- Reorder all tables (although will only be our test table)\nCLUSTER VERBOSE;\nINFO:  clustering \"public.cluster_test\" using sequential scan and sort\nINFO:  clustering \"_timescaledb_internal._hyper_1_1_chunk\" using sequential scan and sort\nINFO:  clustering \"_timescaledb_internal._hyper_1_2_chunk\" using sequential scan and sort\nINFO:  clustering \"_timescaledb_internal._hyper_1_3_chunk\" using sequential scan and sort\n-- Change the clustered index\nCREATE INDEX ON cluster_test (time, location);\nCLUSTER VERBOSE cluster_test using cluster_test_time_location_idx;\nINFO:  clustering \"_timescaledb_internal._hyper_1_1_chunk\" using sequential scan and sort\nINFO:  clustering \"_timescaledb_internal._hyper_1_2_chunk\" using sequential scan and sort\nINFO:  clustering \"_timescaledb_internal._hyper_1_3_chunk\" using sequential scan and sort\n-- Show updated clustered indexes\nSELECT indexrelid::regclass, indisclustered\nFROM pg_index\nWHERE indisclustered = true ORDER BY 1;\n                              indexrelid                               | indisclustered \n-----------------------------------------------------------------------+----------------\n cluster_test_time_location_idx                                        | t\n _timescaledb_internal._hyper_1_1_chunk_cluster_test_time_location_idx | t\n _timescaledb_internal._hyper_1_2_chunk_cluster_test_time_location_idx | t\n _timescaledb_internal._hyper_1_3_chunk_cluster_test_time_location_idx | t\n\n--check the setting of cluster indexes on hypertables and chunks\nALTER TABLE cluster_test CLUSTER ON cluster_test_time_idx;\nSELECT indexrelid::regclass, indisclustered\nFROM pg_index\nWHERE indisclustered = true\nORDER BY 1,2;\n                          indexrelid                          | indisclustered \n--------------------------------------------------------------+----------------\n cluster_test_time_idx                                        | t\n _timescaledb_internal._hyper_1_1_chunk_cluster_test_time_idx | t\n _timescaledb_internal._hyper_1_2_chunk_cluster_test_time_idx | t\n _timescaledb_internal._hyper_1_3_chunk_cluster_test_time_idx | t\n\nCLUSTER VERBOSE cluster_test;\nINFO:  clustering \"_timescaledb_internal._hyper_1_1_chunk\" using sequential scan and sort\nINFO:  clustering \"_timescaledb_internal._hyper_1_2_chunk\" using sequential scan and sort\nINFO:  clustering \"_timescaledb_internal._hyper_1_3_chunk\" using sequential scan and sort\nALTER TABLE cluster_test SET WITHOUT CLUSTER;\nSELECT indexrelid::regclass, indisclustered\nFROM pg_index\nWHERE indisclustered = true\nORDER BY 1,2;\n indexrelid | indisclustered \n------------+----------------\n\n\\set ON_ERROR_STOP 0\nCLUSTER VERBOSE cluster_test;\nERROR:  there is no previously clustered index for table \"cluster_test\"\n\\set ON_ERROR_STOP 1\nALTER TABLE _timescaledb_internal._hyper_1_1_chunk CLUSTER ON _hyper_1_1_chunk_cluster_test_time_idx;\nSELECT indexrelid::regclass, indisclustered\nFROM pg_index\nWHERE indisclustered = true\nORDER BY 1,2;\n                          indexrelid                          | indisclustered \n--------------------------------------------------------------+----------------\n _timescaledb_internal._hyper_1_1_chunk_cluster_test_time_idx | t\n\nCLUSTER VERBOSE _timescaledb_internal._hyper_1_1_chunk;\nINFO:  clustering \"_timescaledb_internal._hyper_1_1_chunk\" using sequential scan and sort\nALTER TABLE _timescaledb_internal._hyper_1_1_chunk SET WITHOUT CLUSTER;\nSELECT indexrelid::regclass, indisclustered\nFROM pg_index\nWHERE indisclustered = true\nORDER BY 1,2;\n indexrelid | indisclustered \n------------+----------------\n\n\\set ON_ERROR_STOP 0\nCLUSTER VERBOSE _timescaledb_internal._hyper_1_1_chunk;\nERROR:  there is no previously clustered index for table \"_hyper_1_1_chunk\"\n\\set ON_ERROR_STOP 1\n-- test alter column type on hypertable with clustering\nCREATE TABLE cluster_alter(time timestamp, id text, val int);\nCREATE INDEX idstuff ON cluster_alter USING btree (id ASC NULLS LAST, time);\nSELECT table_name FROM create_hypertable('cluster_alter', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n  table_name   \n---------------\n cluster_alter\n\nINSERT INTO cluster_alter VALUES('2020-01-01', '123', 1);\nCLUSTER cluster_alter using idstuff;\n--attempt the alter table\nALTER TABLE cluster_alter ALTER COLUMN id TYPE int USING id::int;\nCLUSTER cluster_alter;\nCLUSTER cluster_alter using idstuff;\n"
  },
  {
    "path": "test/expected/constraint.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE hyper (\n  time BIGINT NOT NULL,\n  device_id TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1 CHECK (sensor_1 > 10)\n);\nSELECT * FROM create_hypertable('hyper', 'time', chunk_time_interval => 10);\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             1 | public      | hyper      | t\n\n--check and not-null constraints are inherited through regular inheritance.\n\\set ON_ERROR_STOP 0\nINSERT INTO hyper(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 9);\nERROR:  new row for relation \"_hyper_1_1_chunk\" violates check constraint \"hyper_sensor_1_check\"\nINSERT INTO hyper(time, device_id,sensor_1) VALUES\n(1257987700000000000, NULL, 11);\nERROR:  null value in column \"device_id\" of relation \"_hyper_1_2_chunk\" violates not-null constraint\nALTER TABLE hyper ALTER COLUMN time DROP NOT NULL;\nERROR:  cannot drop not-null constraint from a time-partitioned column\nALTER TABLE ONLY hyper ALTER COLUMN sensor_1 SET NOT NULL;\nERROR:  ONLY option not supported on hypertable operations\nALTER TABLE ONLY hyper ALTER COLUMN device_id DROP NOT NULL;\nERROR:  ONLY option not supported on hypertable operations\n\\set ON_ERROR_STOP 1\nINSERT INTO hyper(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 11);\nINSERT INTO hyper(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 11);\nALTER TABLE hyper ALTER COLUMN device_id DROP NOT NULL;\nINSERT INTO hyper(time, device_id,sensor_1) VALUES\n(1257987700000000000, NULL, 11);\n--make sure validate works\n\\set ON_ERROR_STOP 0\nALTER TABLE hyper ADD CONSTRAINT bad_check_const CHECK (sensor_1 > 100);\nERROR:  check constraint \"bad_check_const\" of relation \"_hyper_1_3_chunk\" is violated by some row\n\\set ON_ERROR_STOP 1\nALTER TABLE hyper ADD CONSTRAINT bad_check_const CHECK (sensor_1 > 100) NOT VALID;\n\\set ON_ERROR_STOP 0\nALTER TABLE hyper VALIDATE CONSTRAINT bad_check_const;\nERROR:  check constraint \"bad_check_const\" of relation \"_hyper_1_3_chunk\" is violated by some row\n\\set ON_ERROR_STOP 1\n----------------------- UNIQUE CONSTRAINTS ------------------\nCREATE TABLE hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name (\n  time BIGINT NOT NULL UNIQUE,\n  device_id TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1 CHECK (sensor_1 > 10)\n);\nSELECT * FROM create_hypertable('hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name', 'time', chunk_time_interval => 10);\n hypertable_id | schema_name |                           table_name                            | created \n---------------+-------------+-----------------------------------------------------------------+---------\n             2 | public      | hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name | t\n\nINSERT INTO hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 11);\nINSERT INTO hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name(time, device_id,sensor_1) VALUES\n(1257987800000000000, 'dev2', 11);\n\\set ON_ERROR_STOP 0\nINSERT INTO hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 11);\nERROR:  duplicate key value violates unique constraint \"4_1_hyper_unique_with_looooooooooooooooooooooooooooooooooo_time\"\n\\set ON_ERROR_STOP 1\n-- Show constraints on main tables\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\n chunk_id | dimension_slice_id |                         constraint_name                         |                   hypertable_constraint_name                    \n----------+--------------------+-----------------------------------------------------------------+-----------------------------------------------------------------\n        3 |                  3 | constraint_3                                                    | \n        4 |                  4 | constraint_4                                                    | \n        4 |                    | 4_1_hyper_unique_with_looooooooooooooooooooooooooooooooooo_time | hyper_unique_with_looooooooooooooooooooooooooooooooooo_time_key\n        5 |                  5 | constraint_5                                                    | \n        5 |                    | 5_2_hyper_unique_with_looooooooooooooooooooooooooooooooooo_time | hyper_unique_with_looooooooooooooooooooooooooooooooooo_time_key\n\nSELECT * FROM test.show_constraints('hyper');\n      Constraint      | Type |  Columns   | Index |            Expr             | Deferrable | Deferred | Validated \n----------------------+------+------------+-------+-----------------------------+------------+----------+-----------\n bad_check_const      | c    | {sensor_1} | -     | (sensor_1 > (100)::numeric) | f          | f        | f\n hyper_sensor_1_check | c    | {sensor_1} | -     | (sensor_1 > (10)::numeric)  | f          | f        | t\n\nSELECT * FROM test.show_constraints('hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name');\n                           Constraint                            | Type |  Columns   |                              Index                              |            Expr            | Deferrable | Deferred | Validated \n-----------------------------------------------------------------+------+------------+-----------------------------------------------------------------+----------------------------+------------+----------+-----------\n hyper_unique_with_looooooooooooooooooooooooooooo_sensor_1_check | c    | {sensor_1} | -                                                               | (sensor_1 > (10)::numeric) | f          | f        | t\n hyper_unique_with_looooooooooooooooooooooooooooooooooo_time_key | u    | {time}     | hyper_unique_with_looooooooooooooooooooooooooooooooooo_time_key |                            | f          | f        | t\n\n--should have unique constraint not just unique index\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_2_4_chunk');\n                           Constraint                            | Type |  Columns   |                                          Index                                          |                                           Expr                                           | Deferrable | Deferred | Validated \n-----------------------------------------------------------------+------+------------+-----------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------+------------+----------+-----------\n 4_1_hyper_unique_with_looooooooooooooooooooooooooooooooooo_time | u    | {time}     | _timescaledb_internal.\"4_1_hyper_unique_with_looooooooooooooooooooooooooooooooooo_time\" |                                                                                          | f          | f        | t\n constraint_4                                                    | c    | {time}     | -                                                                                       | ((\"time\" >= '1257987700000000000'::bigint) AND (\"time\" < '1257987700000000010'::bigint)) | f          | f        | t\n hyper_unique_with_looooooooooooooooooooooooooooo_sensor_1_check | c    | {sensor_1} | -                                                                                       | (sensor_1 > (10)::numeric)                                                               | f          | f        | t\n\nALTER TABLE hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name DROP CONSTRAINT hyper_unique_with_looooooooooooooooooooooooooooooooooo_time_key;\n-- The constraint should have been removed from the chunk as well\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\n chunk_id | dimension_slice_id | constraint_name | hypertable_constraint_name \n----------+--------------------+-----------------+----------------------------\n        3 |                  3 | constraint_3    | \n        4 |                  4 | constraint_4    | \n        5 |                  5 | constraint_5    | \n\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_2_4_chunk');\n                           Constraint                            | Type |  Columns   | Index |                                           Expr                                           | Deferrable | Deferred | Validated \n-----------------------------------------------------------------+------+------------+-------+------------------------------------------------------------------------------------------+------------+----------+-----------\n constraint_4                                                    | c    | {time}     | -     | ((\"time\" >= '1257987700000000000'::bigint) AND (\"time\" < '1257987700000000010'::bigint)) | f          | f        | t\n hyper_unique_with_looooooooooooooooooooooooooooo_sensor_1_check | c    | {sensor_1} | -     | (sensor_1 > (10)::numeric)                                                               | f          | f        | t\n\n--uniqueness not enforced\nINSERT INTO hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev3', 11);\n--shouldn't be able to create constraint\n\\set ON_ERROR_STOP 0\nALTER TABLE hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name ADD CONSTRAINT hyper_unique_time_key UNIQUE (time);\nERROR:  could not create unique index \"4_3_hyper_unique_time_key\"\n\\set ON_ERROR_STOP 1\nDELETE FROM hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name WHERE device_id = 'dev3';\n-- Try multi-alter table statement with a constraint without a name\nALTER TABLE hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\n      ADD CHECK (time > 0),\n      ADD UNIQUE (time) DEFERRABLE INITIALLY DEFERRED;\n\\set ON_ERROR_STOP 0\nBEGIN;\n--testing deferred checking. The following row has an error, which will not appear until the commit\nINSERT INTO hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev3', 11);\nSELECT 1;\n ?column? \n----------\n        1\n\nCOMMIT;\nERROR:  duplicate key value violates unique constraint \"4_4_hyper_unique_with_looooooooooooooooooooooooooooooooooo_time\"\n\\set ON_ERROR_STOP 1\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\n chunk_id | dimension_slice_id |                         constraint_name                         |                   hypertable_constraint_name                    \n----------+--------------------+-----------------------------------------------------------------+-----------------------------------------------------------------\n        3 |                  3 | constraint_3                                                    | \n        4 |                  4 | constraint_4                                                    | \n        5 |                  5 | constraint_5                                                    | \n        4 |                    | 4_4_hyper_unique_with_looooooooooooooooooooooooooooooooooo_time | hyper_unique_with_looooooooooooooooooooooooooooooooooo_time_key\n        5 |                    | 5_5_hyper_unique_with_looooooooooooooooooooooooooooooooooo_time | hyper_unique_with_looooooooooooooooooooooooooooooooooo_time_key\n\nSELECT * FROM test.show_constraints('hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name');\n                           Constraint                            | Type |  Columns   |                              Index                              |            Expr            | Deferrable | Deferred | Validated \n-----------------------------------------------------------------+------+------------+-----------------------------------------------------------------+----------------------------+------------+----------+-----------\n hyper_unique_with_looooooooooooooooooooooooooooo_sensor_1_check | c    | {sensor_1} | -                                                               | (sensor_1 > (10)::numeric) | f          | f        | t\n hyper_unique_with_looooooooooooooooooooooooooooooooo_time_check | c    | {time}     | -                                                               | (\"time\" > 0)               | f          | f        | t\n hyper_unique_with_looooooooooooooooooooooooooooooooooo_time_key | u    | {time}     | hyper_unique_with_looooooooooooooooooooooooooooooooooo_time_key |                            | t          | t        | t\n\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_2_4_chunk');\n                           Constraint                            | Type |  Columns   |                                          Index                                          |                                           Expr                                           | Deferrable | Deferred | Validated \n-----------------------------------------------------------------+------+------------+-----------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------+------------+----------+-----------\n 4_4_hyper_unique_with_looooooooooooooooooooooooooooooooooo_time | u    | {time}     | _timescaledb_internal.\"4_4_hyper_unique_with_looooooooooooooooooooooooooooooooooo_time\" |                                                                                          | t          | t        | t\n constraint_4                                                    | c    | {time}     | -                                                                                       | ((\"time\" >= '1257987700000000000'::bigint) AND (\"time\" < '1257987700000000010'::bigint)) | f          | f        | t\n hyper_unique_with_looooooooooooooooooooooooooooo_sensor_1_check | c    | {sensor_1} | -                                                                                       | (sensor_1 > (10)::numeric)                                                               | f          | f        | t\n hyper_unique_with_looooooooooooooooooooooooooooooooo_time_check | c    | {time}     | -                                                                                       | (\"time\" > 0)                                                                             | f          | f        | t\n\nALTER TABLE  hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\nDROP CONSTRAINT hyper_unique_with_looooooooooooooooooooooooooooooooooo_time_key,\nDROP CONSTRAINT hyper_unique_with_looooooooooooooooooooooooooooooooo_time_check;\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\n chunk_id | dimension_slice_id | constraint_name | hypertable_constraint_name \n----------+--------------------+-----------------+----------------------------\n        3 |                  3 | constraint_3    | \n        4 |                  4 | constraint_4    | \n        5 |                  5 | constraint_5    | \n\nSELECT * FROM test.show_constraints('hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name');\n                           Constraint                            | Type |  Columns   | Index |            Expr            | Deferrable | Deferred | Validated \n-----------------------------------------------------------------+------+------------+-------+----------------------------+------------+----------+-----------\n hyper_unique_with_looooooooooooooooooooooooooooo_sensor_1_check | c    | {sensor_1} | -     | (sensor_1 > (10)::numeric) | f          | f        | t\n\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_2_4_chunk');\n                           Constraint                            | Type |  Columns   | Index |                                           Expr                                           | Deferrable | Deferred | Validated \n-----------------------------------------------------------------+------+------------+-------+------------------------------------------------------------------------------------------+------------+----------+-----------\n constraint_4                                                    | c    | {time}     | -     | ((\"time\" >= '1257987700000000000'::bigint) AND (\"time\" < '1257987700000000010'::bigint)) | f          | f        | t\n hyper_unique_with_looooooooooooooooooooooooooooo_sensor_1_check | c    | {sensor_1} | -     | (sensor_1 > (10)::numeric)                                                               | f          | f        | t\n\nCREATE UNIQUE INDEX hyper_unique_with_looooooooooooooooooooooooooooooooo_time_idx\nON hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name (time);\n\\set ON_ERROR_STOP 0\n-- Try adding constraint using existing index\nALTER TABLE hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\nADD CONSTRAINT hyper_unique_with_looooooooooooooooooooooooooooooooooo_time_key UNIQUE\nUSING INDEX hyper_unique_with_looooooooooooooooooooooooooooooooo_time_idx;\nNOTICE:  ALTER TABLE / ADD CONSTRAINT USING INDEX will rename index \"hyper_unique_with_looooooooooooooooooooooooooooooooo_time_idx\" to \"hyper_unique_with_looooooooooooooooooooooooooooooooooo_time_key\"\nERROR:  hypertables do not support adding a constraint using an existing index\n\\set ON_ERROR_STOP 1\nDROP INDEX hyper_unique_with_looooooooooooooooooooooooooooooooo_time_idx;\n--now can create\nALTER TABLE  hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\nADD CONSTRAINT hyper_unique_with_looooooooooooooooooooooooooooooooooo_time_key UNIQUE (time);\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_2_4_chunk');\n                           Constraint                            | Type |  Columns   |                                          Index                                          |                                           Expr                                           | Deferrable | Deferred | Validated \n-----------------------------------------------------------------+------+------------+-----------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------+------------+----------+-----------\n 4_6_hyper_unique_with_looooooooooooooooooooooooooooooooooo_time | u    | {time}     | _timescaledb_internal.\"4_6_hyper_unique_with_looooooooooooooooooooooooooooooooooo_time\" |                                                                                          | f          | f        | t\n constraint_4                                                    | c    | {time}     | -                                                                                       | ((\"time\" >= '1257987700000000000'::bigint) AND (\"time\" < '1257987700000000010'::bigint)) | f          | f        | t\n hyper_unique_with_looooooooooooooooooooooooooooo_sensor_1_check | c    | {sensor_1} | -                                                                                       | (sensor_1 > (10)::numeric)                                                               | f          | f        | t\n\n--test adding constraint with same name to different table -- should fail\n\\set ON_ERROR_STOP 0\nALTER TABLE hyper\nADD CONSTRAINT hyper_unique_with_looooooooooooooooooooooooooooooooooo_time_key UNIQUE (time);\nERROR:  relation \"hyper_unique_with_looooooooooooooooooooooooooooooooooo_time_key\" already exists\n\\set ON_ERROR_STOP 1\n--uniquness violation fails\n\\set ON_ERROR_STOP 0\nINSERT INTO hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name(time, device_id,sensor_1)\nVALUES (1257987700000000000, 'dev2', 11);\nERROR:  duplicate key value violates unique constraint \"4_6_hyper_unique_with_looooooooooooooooooooooooooooooooooo_time\"\n\\set ON_ERROR_STOP 1\n--cannot create unique constraint on non-partition column\n\\set ON_ERROR_STOP 0\nALTER TABLE hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\nADD CONSTRAINT hyper_unique_invalid UNIQUE (device_id);\nERROR:  cannot create a unique index without the column \"time\" (used in partitioning)\nALTER TABLE hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\nADD COLUMN new_device_id int UNIQUE;\nERROR:  cannot create a unique index without the column \"time\" (used in partitioning)\nALTER TABLE hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\nDROP COLUMN device_id,\nADD COLUMN new_device_id int UNIQUE;\nERROR:  cannot create a unique index without the column \"time\" (used in partitioning)\n\\set ON_ERROR_STOP 1\n----------------------- RENAME CONSTRAINT  ------------------\nALTER TABLE hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\nRENAME CONSTRAINT hyper_unique_with_looooooooooooooooooooooooooooooooooo_time_key TO new_name;\nALTER TABLE hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name *\nRENAME CONSTRAINT new_name TO new_name2;\nALTER TABLE IF EXISTS hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\nRENAME CONSTRAINT  hyper_unique_with_looooooooooooooooooooooooooooo_sensor_1_check TO check_2;\nSELECT * FROM test.show_constraints('hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name');\n Constraint | Type |  Columns   |   Index   |            Expr            | Deferrable | Deferred | Validated \n------------+------+------------+-----------+----------------------------+------------+----------+-----------\n check_2    | c    | {sensor_1} | -         | (sensor_1 > (10)::numeric) | f          | f        | t\n new_name2  | u    | {time}     | new_name2 |                            | f          | f        | t\n\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_2_4_chunk');\n   Constraint   | Type |  Columns   |                 Index                  |                                           Expr                                           | Deferrable | Deferred | Validated \n----------------+------+------------+----------------------------------------+------------------------------------------------------------------------------------------+------------+----------+-----------\n 4_10_new_name2 | u    | {time}     | _timescaledb_internal.\"4_10_new_name2\" |                                                                                          | f          | f        | t\n check_2        | c    | {sensor_1} | -                                      | (sensor_1 > (10)::numeric)                                                               | f          | f        | t\n constraint_4   | c    | {time}     | -                                      | ((\"time\" >= '1257987700000000000'::bigint) AND (\"time\" < '1257987700000000010'::bigint)) | f          | f        | t\n\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\n chunk_id | dimension_slice_id | constraint_name | hypertable_constraint_name \n----------+--------------------+-----------------+----------------------------\n        3 |                  3 | constraint_3    | \n        4 |                  4 | constraint_4    | \n        5 |                  5 | constraint_5    | \n        4 |                    | 4_10_new_name2  | new_name2\n        5 |                    | 5_11_new_name2  | new_name2\n\n\\set ON_ERROR_STOP 0\nALTER TABLE hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\nRENAME CONSTRAINT new_name TO new_name2;\nERROR:  constraint \"new_name\" for table \"hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\" does not exist\nALTER TABLE hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\nRENAME CONSTRAINT new_name2 TO check_2;\nERROR:  constraint \"check_2\" for relation \"hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\" already exists\nALTER TABLE ONLY hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\nRENAME CONSTRAINT new_name2 TO new_name;\nERROR:  ONLY option not supported on hypertable operations\nALTER TABLE _timescaledb_internal._hyper_2_4_chunk\nRENAME CONSTRAINT \"4_10_new_name2\" TO new_name;\nERROR:  renaming constraints on chunks is not supported\n\\set ON_ERROR_STOP 1\n----------------------- PRIMARY KEY  ------------------\nCREATE TABLE hyper_pk (\n  time BIGINT NOT NULL PRIMARY KEY,\n  device_id TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1 CHECK (sensor_1 > 10)\n);\nSELECT * FROM create_hypertable('hyper_pk', 'time', chunk_time_interval => 10);\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             3 | public      | hyper_pk   | t\n\nINSERT INTO hyper_pk(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 11);\n\\set ON_ERROR_STOP 0\nINSERT INTO hyper_pk(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 11);\nERROR:  duplicate key value violates unique constraint \"6_14_hyper_pk_pkey\"\n\\set ON_ERROR_STOP 1\n--should have unique constraint not just unique index\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_3_6_chunk');\n       Constraint        | Type |  Columns   |                   Index                    |                                           Expr                                           | Deferrable | Deferred | Validated \n-------------------------+------+------------+--------------------------------------------+------------------------------------------------------------------------------------------+------------+----------+-----------\n 6_14_hyper_pk_pkey      | p    | {time}     | _timescaledb_internal.\"6_14_hyper_pk_pkey\" |                                                                                          | f          | f        | t\n constraint_6            | c    | {time}     | -                                          | ((\"time\" >= '1257987700000000000'::bigint) AND (\"time\" < '1257987700000000010'::bigint)) | f          | f        | t\n hyper_pk_sensor_1_check | c    | {sensor_1} | -                                          | (sensor_1 > (10)::numeric)                                                               | f          | f        | t\n\nALTER TABLE hyper_pk DROP CONSTRAINT hyper_pk_pkey;\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_3_6_chunk');\n       Constraint        | Type |  Columns   | Index |                                           Expr                                           | Deferrable | Deferred | Validated \n-------------------------+------+------------+-------+------------------------------------------------------------------------------------------+------------+----------+-----------\n constraint_6            | c    | {time}     | -     | ((\"time\" >= '1257987700000000000'::bigint) AND (\"time\" < '1257987700000000010'::bigint)) | f          | f        | t\n hyper_pk_sensor_1_check | c    | {sensor_1} | -     | (sensor_1 > (10)::numeric)                                                               | f          | f        | t\n\n--uniqueness not enforced\nINSERT INTO hyper_pk(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev3', 11);\n--shouldn't be able to create pk\n\\set ON_ERROR_STOP 0\nALTER TABLE hyper_pk ADD CONSTRAINT hyper_pk_pkey PRIMARY KEY (time);\nERROR:  could not create unique index \"6_15_hyper_pk_pkey\"\nALTER TABLE hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\nADD COLUMN new_device_id int PRIMARY KEY;\nERROR:  cannot create a unique index without the column \"time\" (used in partitioning)\n\\set ON_ERROR_STOP 1\nDELETE FROM hyper_pk WHERE device_id = 'dev3';\n--cannot create pk constraint on non-partition column\n\\set ON_ERROR_STOP 0\nALTER TABLE hyper_pk ADD CONSTRAINT hyper_pk_invalid PRIMARY KEY (device_id);\nERROR:  cannot create a unique index without the column \"time\" (used in partitioning)\n\\set ON_ERROR_STOP 1\n--now can create\nALTER TABLE hyper_pk ADD CONSTRAINT hyper_pk_pkey PRIMARY KEY (time) DEFERRABLE INITIALLY DEFERRED;\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_3_6_chunk');\n       Constraint        | Type |  Columns   |                   Index                    |                                           Expr                                           | Deferrable | Deferred | Validated \n-------------------------+------+------------+--------------------------------------------+------------------------------------------------------------------------------------------+------------+----------+-----------\n 6_16_hyper_pk_pkey      | p    | {time}     | _timescaledb_internal.\"6_16_hyper_pk_pkey\" |                                                                                          | t          | t        | t\n constraint_6            | c    | {time}     | -                                          | ((\"time\" >= '1257987700000000000'::bigint) AND (\"time\" < '1257987700000000010'::bigint)) | f          | f        | t\n hyper_pk_sensor_1_check | c    | {sensor_1} | -                                          | (sensor_1 > (10)::numeric)                                                               | f          | f        | t\n\n--test adding constraint with same name to different table -- should fail\n\\set ON_ERROR_STOP 0\nALTER TABLE hyper ADD CONSTRAINT hyper_pk_pkey UNIQUE (time);\nERROR:  relation \"hyper_pk_pkey\" already exists\n\\set ON_ERROR_STOP 1\n--uniquness violation fails\n\\set ON_ERROR_STOP 0\nBEGIN;\n  --error here deferred until commit\n  INSERT INTO hyper_pk(time, device_id,sensor_1) VALUES\n  (1257987700000000000, 'dev2', 11);\n  SELECT 1;\n ?column? \n----------\n        1\n\nCOMMIT;\nERROR:  duplicate key value violates unique constraint \"6_16_hyper_pk_pkey\"\n\\set ON_ERROR_STOP 1\n----------------------- FOREIGN KEY  ------------------\nCREATE TABLE devices(\n    device_id TEXT NOT NULL,\n    PRIMARY KEY (device_id)\n);\nCREATE TABLE hyper_fk (\n  time BIGINT NOT NULL PRIMARY KEY,\n  device_id TEXT NOT NULL REFERENCES devices(device_id),\n  sensor_1 NUMERIC NULL DEFAULT 1 CHECK (sensor_1 > 10)\n);\nSELECT * FROM create_hypertable('hyper_fk', 'time', chunk_time_interval => 10);\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             4 | public      | hyper_fk   | t\n\n--fail fk constraint\n\\set ON_ERROR_STOP 0\nINSERT INTO hyper_fk(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 11);\nERROR:  insert or update on table \"_hyper_4_7_chunk\" violates foreign key constraint \"7_17_hyper_fk_device_id_fkey\"\n\\set ON_ERROR_STOP 1\nINSERT INTO devices VALUES ('dev2');\nINSERT INTO hyper_fk(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 11);\n--delete should fail\n\\set ON_ERROR_STOP 0\nDELETE FROM devices;\nERROR:  update or delete on table \"devices\" violates foreign key constraint \"hyper_fk_device_id_fkey\" on table \"hyper_fk\"\n\\set ON_ERROR_STOP 1\nALTER TABLE hyper_fk DROP CONSTRAINT hyper_fk_device_id_fkey;\n--should now be able to add non-fk rows\nINSERT INTO hyper_fk(time, device_id,sensor_1) VALUES\n(1257987700000000001, 'dev3', 11);\n--can't add fk because of dev3 row\n\\set ON_ERROR_STOP 0\nALTER TABLE hyper_fk ADD CONSTRAINT hyper_fk_device_id_fkey\nFOREIGN KEY (device_id) REFERENCES devices(device_id);\nERROR:  insert or update on table \"hyper_fk\" violates foreign key constraint \"hyper_fk_device_id_fkey\"\n\\set ON_ERROR_STOP 1\n--but can add a NOT VALID one\nALTER TABLE hyper_fk ADD CONSTRAINT hyper_fk_device_id_fkey\nFOREIGN KEY (device_id) REFERENCES devices(device_id) NOT VALID;\n--which will fail when validated\n\\set ON_ERROR_STOP 0\nALTER TABLE hyper_fk VALIDATE CONSTRAINT hyper_fk_device_id_fkey;\nERROR:  insert or update on table \"hyper_fk\" violates foreign key constraint \"hyper_fk_device_id_fkey\"\n\\set ON_ERROR_STOP 1\nALTER TABLE hyper_fk DROP CONSTRAINT hyper_fk_device_id_fkey;\nDELETE FROM hyper_fk WHERE device_id = 'dev3';\nALTER TABLE hyper_fk ADD CONSTRAINT hyper_fk_device_id_fkey\nFOREIGN KEY (device_id) REFERENCES devices(device_id);\n\\set ON_ERROR_STOP 0\nINSERT INTO hyper_fk(time, device_id,sensor_1) VALUES\n(1257987700000000002, 'dev3', 11);\nERROR:  insert or update on table \"_hyper_4_8_chunk\" violates foreign key constraint \"8_22_hyper_fk_device_id_fkey\"\n\\set ON_ERROR_STOP 1\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_4_8_chunk');\n          Constraint          | Type |   Columns   |                   Index                    |                                           Expr                                           | Deferrable | Deferred | Validated \n------------------------------+------+-------------+--------------------------------------------+------------------------------------------------------------------------------------------+------------+----------+-----------\n 8_20_hyper_fk_pkey           | p    | {time}      | _timescaledb_internal.\"8_20_hyper_fk_pkey\" |                                                                                          | f          | f        | t\n 8_22_hyper_fk_device_id_fkey | f    | {device_id} | devices_pkey                               |                                                                                          | f          | f        | t\n constraint_8                 | c    | {time}      | -                                          | ((\"time\" >= '1257987700000000000'::bigint) AND (\"time\" < '1257987700000000010'::bigint)) | f          | f        | t\n hyper_fk_sensor_1_check      | c    | {sensor_1}  | -                                          | (sensor_1 > (10)::numeric)                                                               | f          | f        | t\n\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\n chunk_id | dimension_slice_id |       constraint_name        | hypertable_constraint_name \n----------+--------------------+------------------------------+----------------------------\n        3 |                  3 | constraint_3                 | \n        4 |                  4 | constraint_4                 | \n        5 |                  5 | constraint_5                 | \n        4 |                    | 4_10_new_name2               | new_name2\n        5 |                    | 5_11_new_name2               | new_name2\n        6 |                  6 | constraint_6                 | \n        6 |                    | 6_16_hyper_pk_pkey           | hyper_pk_pkey\n        8 |                  8 | constraint_8                 | \n        8 |                    | 8_20_hyper_fk_pkey           | hyper_fk_pkey\n        8 |                    | 8_22_hyper_fk_device_id_fkey | hyper_fk_device_id_fkey\n\n--test CASCADE drop behavior\nDROP TABLE devices CASCADE;\nNOTICE:  drop cascades to 2 other objects\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_4_8_chunk');\n       Constraint        | Type |  Columns   |                   Index                    |                                           Expr                                           | Deferrable | Deferred | Validated \n-------------------------+------+------------+--------------------------------------------+------------------------------------------------------------------------------------------+------------+----------+-----------\n 8_20_hyper_fk_pkey      | p    | {time}     | _timescaledb_internal.\"8_20_hyper_fk_pkey\" |                                                                                          | f          | f        | t\n constraint_8            | c    | {time}     | -                                          | ((\"time\" >= '1257987700000000000'::bigint) AND (\"time\" < '1257987700000000010'::bigint)) | f          | f        | t\n hyper_fk_sensor_1_check | c    | {sensor_1} | -                                          | (sensor_1 > (10)::numeric)                                                               | f          | f        | t\n\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\n chunk_id | dimension_slice_id |  constraint_name   | hypertable_constraint_name \n----------+--------------------+--------------------+----------------------------\n        3 |                  3 | constraint_3       | \n        4 |                  4 | constraint_4       | \n        5 |                  5 | constraint_5       | \n        4 |                    | 4_10_new_name2     | new_name2\n        5 |                    | 5_11_new_name2     | new_name2\n        6 |                  6 | constraint_6       | \n        6 |                    | 6_16_hyper_pk_pkey | hyper_pk_pkey\n        8 |                  8 | constraint_8       | \n        8 |                    | 8_20_hyper_fk_pkey | hyper_fk_pkey\n\n--the fk went away.\nINSERT INTO hyper_fk(time, device_id,sensor_1) VALUES\n(1257987700000000002, 'dev3', 11);\nCREATE TABLE devices(\n    device_id TEXT NOT NULL,\n    PRIMARY KEY (device_id)\n);\nINSERT INTO devices VALUES ('dev2'), ('dev3');\nALTER TABLE hyper_fk ADD CONSTRAINT hyper_fk_device_id_fkey\nFOREIGN KEY (device_id) REFERENCES devices(device_id) DEFERRABLE INITIALLY DEFERRED;\n\\set ON_ERROR_STOP 0\nBEGIN;\n  --error deferred until commmit\n  INSERT INTO hyper_fk(time, device_id,sensor_1) VALUES\n  (1257987700000000003, 'dev4', 11);\n  SELECT 1;\n ?column? \n----------\n        1\n\nCOMMIT;\nERROR:  insert or update on table \"_hyper_4_8_chunk\" violates foreign key constraint \"8_23_hyper_fk_device_id_fkey\"\n\\set ON_ERROR_STOP 1\nALTER TABLE hyper_fk ALTER CONSTRAINT hyper_fk_device_id_fkey NOT DEFERRABLE;\n\\set ON_ERROR_STOP 0\nBEGIN;\n  --error detected right away\n  INSERT INTO hyper_fk(time, device_id,sensor_1) VALUES\n  (1257987700000000003, 'dev4', 11);\nERROR:  insert or update on table \"_hyper_4_8_chunk\" violates foreign key constraint \"8_23_hyper_fk_device_id_fkey\"\n  SELECT 1;\nERROR:  current transaction is aborted, commands ignored until end of transaction block\nCOMMIT;\n\\set ON_ERROR_STOP 1\n--this tests that there are no extra chunk_constraints left on hyper_fk\nTRUNCATE hyper_fk;\n----------------------- FOREIGN KEY INTO A HYPERTABLE  ------------------\n--FOREIGN KEY references into a hypertable are currently broken.\n--The referencing table will never find the corresponding row in the hypertable\n--since it will only search the parent. Thus any insert will result in an ERROR\n--Block such foreign keys or fix. (Hard to block on create table so punting for now)\nCREATE TABLE hyper_for_ref (\n  time BIGINT NOT NULL PRIMARY KEY,\n  device_id TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1 CHECK (sensor_1 > 10)\n);\nSELECT * FROM create_hypertable('hyper_for_ref', 'time', chunk_time_interval => 10);\n hypertable_id | schema_name |  table_name   | created \n---------------+-------------+---------------+---------\n             5 | public      | hyper_for_ref | t\n\n\\set ON_ERROR_STOP 0\nCREATE TABLE referrer (\n    time BIGINT NOT NULL REFERENCES hyper_for_ref(time)\n);\n\\set ON_ERROR_STOP 1\nCREATE TABLE referrer2 (\n   time BIGINT NOT NULL\n);\n\\set ON_ERROR_STOP 0\nALTER TABLE referrer2 ADD CONSTRAINT hyper_fk_device_id_fkey\nFOREIGN KEY (time) REFERENCES  hyper_for_ref(time);\n\\set ON_ERROR_STOP 1\n-- github issue 8082: FK referencing hypertable with composite unique index\n-- fails on first insert because chunk indexes are created after FK propagation\nCREATE TABLE messages_ref (\n    time_received TIMESTAMPTZ NOT NULL,\n    message_id BIGSERIAL,\n    message_type SMALLINT NOT NULL\n);\nSELECT create_hypertable('messages_ref', by_range('time_received'));\n create_hypertable \n-------------------\n (6,t)\n\nCREATE UNIQUE INDEX ON messages_ref(time_received, message_id);\n-- Create FK referencing the hypertable BEFORE any data exists\nCREATE TABLE contents_ref (\n    content_id BIGSERIAL,\n    time_received TIMESTAMPTZ NOT NULL,\n    message_id BIGINT NOT NULL,\n    content CHAR(10),\n    FOREIGN KEY (time_received, message_id) REFERENCES messages_ref(time_received, message_id) ON DELETE CASCADE\n);\n-- This insert creates a new chunk. Previously it would fail with\n-- \"index for constraint not found on chunk\" because FK propagation\n-- happened before chunk indexes were created.\nINSERT INTO messages_ref (time_received, message_type) VALUES ('2025-05-05 14:56:58.000 UTC', 2);\nINSERT INTO contents_ref (message_id, time_received, content) VALUES (CURRVAL('messages_ref_message_id_seq'), '2025-05-05 14:56:58.000 UTC', 'HEJ');\n-- Insert into a second chunk\nINSERT INTO messages_ref (time_received, message_type) VALUES ('2025-06-05 14:57:58.000 UTC', 3);\nINSERT INTO contents_ref (message_id, time_received, content) VALUES (CURRVAL('messages_ref_message_id_seq'), '2025-06-05 14:57:58.000 UTC', 'HEJ2');\n-- Verify data\nSELECT message_type FROM messages_ref ORDER BY time_received;\n message_type \n--------------\n            2\n            3\n\nSELECT content FROM contents_ref ORDER BY time_received;\n  content   \n------------\n HEJ       \n HEJ2      \n\n-- Verify FK enforcement\n\\set ON_ERROR_STOP 0\nINSERT INTO contents_ref (message_id, time_received, content) VALUES (9999, '2025-05-05 14:56:58.000 UTC', 'FAIL');\nERROR:  insert or update on table \"contents_ref\" violates foreign key constraint \"contents_ref_time_received_message_id_fkey\"\n\\set ON_ERROR_STOP 1\n-- Verify cascade delete\nDELETE FROM messages_ref WHERE message_type = 2;\nSELECT content FROM contents_ref ORDER BY time_received;\n  content   \n------------\n HEJ2      \n\nDROP TABLE contents_ref;\nDROP TABLE messages_ref;\n----------------------- EXCLUSION CONSTRAINT  ------------------\nCREATE TABLE hyper_ex (\n    time BIGINT,\n    device_id TEXT NOT NULL REFERENCES devices(device_id),\n    sensor_1 NUMERIC NULL DEFAULT 1 CHECK (sensor_1 > 10),\n    canceled boolean DEFAULT false,\n    EXCLUDE USING btree (\n        time WITH =, device_id WITH =\n    ) WHERE (not canceled)\n);\nSELECT * FROM create_hypertable('hyper_ex', 'time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             7 | public      | hyper_ex   | t\n\nINSERT INTO hyper_ex(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 11);\n\\set ON_ERROR_STOP 0\nINSERT INTO hyper_ex(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 12);\nERROR:  conflicting key value violates exclusion constraint \"11_25_hyper_ex_time_device_id_excl\"\n\\set ON_ERROR_STOP 1\nALTER TABLE hyper_ex DROP CONSTRAINT hyper_ex_time_device_id_excl;\n--can now add\nINSERT INTO hyper_ex(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 12);\n--cannot add because of conflicts\n\\set ON_ERROR_STOP 0\nALTER TABLE hyper_ex ADD CONSTRAINT hyper_ex_time_device_id_excl\n    EXCLUDE USING btree (\n        time WITH =, device_id WITH =\n    ) WHERE (not canceled)\n;\nERROR:  could not create exclusion constraint \"11_26_hyper_ex_time_device_id_excl\"\n\\set ON_ERROR_STOP 1\nDELETE FROM hyper_ex WHERE sensor_1 = 12;\nALTER TABLE hyper_ex ADD CONSTRAINT hyper_ex_time_device_id_excl\n    EXCLUDE USING btree (\n        time WITH =, device_id WITH =\n    ) WHERE (not canceled) DEFERRABLE INITIALLY DEFERRED\n;\n\\set ON_ERROR_STOP 0\nBEGIN;\n  --error deferred til commit\n  INSERT INTO hyper_ex(time, device_id,sensor_1) VALUES\n  (1257987700000000000, 'dev2', 12);\n  SELECT 1;\n ?column? \n----------\n        1\n\nCOMMIT;\nERROR:  conflicting key value violates exclusion constraint \"11_27_hyper_ex_time_device_id_excl\"\n\\set ON_ERROR_STOP 1\n--cannot add exclusion constraint without partition key.\nCREATE TABLE hyper_ex_invalid (\n    time BIGINT,\n    device_id TEXT NOT NULL REFERENCES devices(device_id),\n    sensor_1 NUMERIC NULL DEFAULT 1 CHECK (sensor_1 > 10),\n    canceled boolean DEFAULT false,\n    EXCLUDE USING btree (\n        device_id WITH =\n    ) WHERE (not canceled)\n);\n\\set ON_ERROR_STOP 0\nSELECT * FROM create_hypertable('hyper_ex_invalid', 'time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nERROR:  cannot create a unique index without the column \"time\" (used in partitioning)\n\\set ON_ERROR_STOP 1\n--- NO INHERIT constraints (not allowed) ----\nCREATE TABLE hyper_noinherit (\n    time BIGINT,\n    sensor_1 NUMERIC NULL DEFAULT 1 CHECK (sensor_1 > 0) NO INHERIT\n);\nSELECT * FROM test.show_constraints('hyper_noinherit');\n           Constraint           | Type |  Columns   | Index |           Expr            | Deferrable | Deferred | Validated \n--------------------------------+------+------------+-------+---------------------------+------------+----------+-----------\n hyper_noinherit_sensor_1_check | c    | {sensor_1} | -     | (sensor_1 > (0)::numeric) | f          | f        | t\n\n\\set ON_ERROR_STOP 0\nSELECT * FROM create_hypertable('hyper_noinherit', 'time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nERROR:  cannot have NO INHERIT constraints on hypertable \"hyper_noinherit\"\n\\set ON_ERROR_STOP 1\nCREATE TABLE hyper_noinherit_alter (\n    time BIGINT,\n    sensor_1 NUMERIC NULL DEFAULT 1\n);\nSELECT * FROM create_hypertable('hyper_noinherit_alter', 'time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name |      table_name       | created \n---------------+-------------+-----------------------+---------\n             9 | public      | hyper_noinherit_alter | t\n\n\\set ON_ERROR_STOP 0\nALTER TABLE hyper_noinherit_alter ADD CONSTRAINT check_noinherit CHECK (sensor_1 > 0) NO INHERIT;\nERROR:  cannot have NO INHERIT constraints on hypertable \"hyper_noinherit_alter\"\n--  CREATE TABLE WITH DEFERRED CONSTRAINTS --\nCREATE TABLE hyper_unique_deferred (\n  time BIGINT UNIQUE DEFERRABLE INITIALLY DEFERRED,\n  device_id TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1 CHECK (sensor_1 > 10)\n);\nSELECT * FROM create_hypertable('hyper_unique_deferred', 'time', chunk_time_interval => 10);\n hypertable_id | schema_name |      table_name       | created \n---------------+-------------+-----------------------+---------\n            10 | public      | hyper_unique_deferred | t\n\nINSERT INTO hyper_unique_deferred(time, device_id,sensor_1) VALUES (1257987700000000000, 'dev2', 11);\n\\set ON_ERROR_STOP 0\nBEGIN;\n  --error here deferred until commit\n  INSERT INTO hyper_unique_deferred(time, device_id,sensor_1) VALUES (1257987700000000000, 'dev2', 11);\n  SELECT 1;\n ?column? \n----------\n        1\n\nCOMMIT;\nERROR:  duplicate key value violates unique constraint \"12_28_hyper_unique_deferred_time_key\"\n\\set ON_ERROR_STOP 1\n--test deferred on create table\nCREATE TABLE hyper_pk_deferred (\n  time BIGINT NOT NULL PRIMARY KEY DEFERRABLE INITIALLY DEFERRED,\n  device_id TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1 CHECK (sensor_1 > 10)\n);\nSELECT * FROM create_hypertable('hyper_pk_deferred', 'time', chunk_time_interval => 10);\n hypertable_id | schema_name |    table_name     | created \n---------------+-------------+-------------------+---------\n            11 | public      | hyper_pk_deferred | t\n\nINSERT INTO hyper_pk_deferred(time, device_id,sensor_1) VALUES (1257987700000000000, 'dev2', 11);\n\\set ON_ERROR_STOP 0\nBEGIN;\n  --error here deferred until commit\n  INSERT INTO hyper_pk_deferred(time, device_id,sensor_1) VALUES (1257987700000000000, 'dev2', 11);\n  SELECT 1;\n ?column? \n----------\n        1\n\nCOMMIT;\nERROR:  duplicate key value violates unique constraint \"13_29_hyper_pk_deferred_pkey\"\n\\set ON_ERROR_STOP 1\n--test that deferred works on create table too\nCREATE TABLE hyper_fk_deferred (\n  time BIGINT NOT NULL PRIMARY KEY,\n  device_id TEXT NOT NULL REFERENCES devices(device_id) DEFERRABLE INITIALLY DEFERRED,\n  sensor_1 NUMERIC NULL DEFAULT 1 CHECK (sensor_1 > 10)\n);\nSELECT * FROM create_hypertable('hyper_fk_deferred', 'time', chunk_time_interval => 10);\n hypertable_id | schema_name |    table_name     | created \n---------------+-------------+-------------------+---------\n            12 | public      | hyper_fk_deferred | t\n\n\\set ON_ERROR_STOP 0\nBEGIN;\n  --error deferred until commmit\n  INSERT INTO hyper_fk_deferred(time, device_id,sensor_1) VALUES (1257987700000000003, 'dev4', 11);\n  SELECT 1;\n ?column? \n----------\n        1\n\nCOMMIT;\nERROR:  insert or update on table \"_hyper_12_14_chunk\" violates foreign key constraint \"14_30_hyper_fk_deferred_device_id_fkey\"\n\\set ON_ERROR_STOP 1\nCREATE TABLE hyper_ex_deferred (\n    time BIGINT,\n    device_id TEXT NOT NULL REFERENCES devices(device_id),\n    sensor_1 NUMERIC NULL DEFAULT 1 CHECK (sensor_1 > 10),\n    canceled boolean DEFAULT false,\n    EXCLUDE USING btree (\n        time WITH =, device_id WITH =\n    ) WHERE (not canceled) DEFERRABLE INITIALLY DEFERRED\n);\nSELECT * FROM create_hypertable('hyper_ex_deferred', 'time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name |    table_name     | created \n---------------+-------------+-------------------+---------\n            13 | public      | hyper_ex_deferred | t\n\nINSERT INTO hyper_ex_deferred(time, device_id,sensor_1) VALUES (1257987700000000000, 'dev2', 12);\n\\set ON_ERROR_STOP 0\nBEGIN;\n  --error deferred til commit\n  INSERT INTO hyper_ex_deferred(time, device_id,sensor_1) VALUES (1257987700000000000, 'dev2', 12);\n  SELECT 1;\n ?column? \n----------\n        1\n\nCOMMIT;\nERROR:  conflicting key value violates exclusion constraint \"15_33_hyper_ex_deferred_time_device_id_excl\"\n\\set ON_ERROR_STOP 1\n-- Make sure renaming schemas won't break dropping constraints\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE TABLE hyper_unique (\n  time BIGINT NOT NULL UNIQUE,\n  device_id TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1 CHECK (sensor_1 > 10)\n);\nSELECT * FROM create_hypertable('hyper_unique', 'time', chunk_time_interval => 10, associated_schema_name => 'my_associated_schema');\n hypertable_id | schema_name |  table_name  | created \n---------------+-------------+--------------+---------\n            14 | public      | hyper_unique | t\n\nINSERT INTO hyper_unique(time, device_id,sensor_1) VALUES (1257987700000000000, 'dev2', 11);\nALTER SCHEMA my_associated_schema RENAME TO new_associated_schema;\nALTER TABLE hyper_unique DROP CONSTRAINT hyper_unique_time_key;\n-- test for constraint validation crash, see #1183\nCREATE TABLE test_validate(time timestamp NOT NULL, a TEXT, b TEXT);\nSELECT * FROM create_hypertable('test_validate', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n hypertable_id | schema_name |  table_name   | created \n---------------+-------------+---------------+---------\n            15 | public      | test_validate | t\n\nINSERT INTO test_validate values(now(), 'a', 'b');\nALTER TABLE test_validate\nADD COLUMN c TEXT,\nADD CONSTRAINT c_not_null CHECK (c IS NOT NULL) NOT VALID;\nUPDATE test_validate SET c = '';\nALTER TABLE test_validate\nVALIDATE CONSTRAINT c_not_null;\nDROP TABLE test_validate;\n-- test for hypertables constraints both using index tablespaces and not See #2604\nSET client_min_messages = ERROR;\nDROP TABLESPACE IF EXISTS tablespace1;\nSET client_min_messages = NOTICE;\nCREATE TABLESPACE tablespace1 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE1_PATH;\nCREATE TABLE fk_tbl (\nid int,\nCONSTRAINT pkfk PRIMARY KEY (id) USING INDEX TABLESPACE tablespace1);\nCREATE TABLE tbl (\nfk_id int,\nid int,\ntime timestamp,\nCONSTRAINT pk PRIMARY KEY (time, id) USING INDEX TABLESPACE tablespace1 DEFERRABLE INITIALLY DEFERRED);\nSELECT create_hypertable('tbl', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n create_hypertable \n-------------------\n (16,public,tbl,t)\n\nALTER TABLE tbl\nADD CONSTRAINT fk_con\nFOREIGN KEY (fk_id) REFERENCES fk_tbl(id)\nON UPDATE SET NULL\nON DELETE SET NULL;\nINSERT INTO fk_tbl VALUES(1);\nINSERT INTO tbl VALUES (\n1, 1, now()\n);\nDROP TABLE tbl;\nDROP TABLE fk_tbl;\nDROP TABLESPACE IF EXISTS tablespace1;\n"
  },
  {
    "path": "test/expected/copy.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\o /dev/null\n\\ir include/insert_two_partitions.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE PUBLIC.\"two_Partitions\" (\n  \"timeCustom\" BIGINT NOT NULL,\n  device_id TEXT NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL,\n  series_bool BOOLEAN NULL\n);\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (device_id, \"timeCustom\" DESC NULLS LAST) WHERE device_id IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_0) WHERE series_0 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_1)  WHERE series_1 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_2) WHERE series_2 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_bool) WHERE series_bool IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, device_id);\nSELECT * FROM create_hypertable('\"public\".\"two_Partitions\"'::regclass, 'timeCustom'::name, 'device_id'::name, associated_schema_name=>'_timescaledb_internal'::text, number_partitions => 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\\set QUIET off\nBEGIN;\n\\COPY public.\"two_Partitions\" FROM 'data/ds1_dev1_1.tsv' NULL AS '';\nCOMMIT;\nINSERT INTO public.\"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257987600000000000, 'dev1', 1.5, 1),\n(1257987600000000000, 'dev1', 1.5, 2),\n(1257894000000000000, 'dev2', 1.5, 1),\n(1257894002000000000, 'dev1', 2.5, 3);\nINSERT INTO \"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257894000000000000, 'dev2', 1.5, 2);\n\\set QUIET on\n\\o\n--old chunks\nCOPY \"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) FROM STDIN DELIMITER ',';\n\\copy \"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) FROM STDIN DELIMITER ',';\n--new chunks\nCOPY \"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) FROM STDIN DELIMITER ',';\n\\copy \"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) FROM STDIN DELIMITER ',';\nCOPY (SELECT * FROM \"two_Partitions\" ORDER BY \"timeCustom\", device_id, series_0, series_1) TO STDOUT;\n1257894000000000000\tdev1\t1.5\t1\t2\tt\n1257894000000000000\tdev1\t1.5\t2\t\\N\t\\N\n1257894000000000000\tdev2\t1.5\t1\t\\N\t\\N\n1257894000000000000\tdev2\t1.5\t2\t\\N\t\\N\n1257894000000000000\tdev3\t1.5\t2\t\\N\t\\N\n1257894000000000000\tdev3\t1.5\t2\t\\N\t\\N\n1257894000000001000\tdev1\t2.5\t3\t\\N\t\\N\n1257894001000000000\tdev1\t3.5\t4\t\\N\t\\N\n1257894002000000000\tdev1\t2.5\t3\t\\N\t\\N\n1257894002000000000\tdev1\t5.5\t6\t\\N\tt\n1257894002000000000\tdev1\t5.5\t7\t\\N\tf\n1257897600000000000\tdev1\t4.5\t5\t\\N\tf\n1257987600000000000\tdev1\t1.5\t1\t\\N\t\\N\n1257987600000000000\tdev1\t1.5\t2\t\\N\t\\N\n2257894000000000000\tdev3\t1.5\t2\t\\N\t\\N\n2257894000000000000\tdev3\t1.5\t2\t\\N\t\\N\n---test hypertable with FK\nCREATE TABLE \"meta\" (\"id\" serial PRIMARY KEY);\nCREATE TABLE \"hyper\" (\n    \"meta_id\" integer NOT NULL REFERENCES meta(id),\n    \"time\" bigint NOT NULL,\n    \"value\" double precision NOT NULL\n);\nSELECT create_hypertable('hyper', 'time', chunk_time_interval => 100);\n create_hypertable  \n--------------------\n (2,public,hyper,t)\n\nINSERT INTO \"meta\" (\"id\") values (1);\n\\copy hyper (time, meta_id, value) FROM STDIN DELIMITER ',';\nCOPY hyper (time, meta_id, value) FROM STDIN DELIMITER ',';\n\\set ON_ERROR_STOP 0\n\\copy hyper (time, meta_id, value) FROM STDIN DELIMITER ',';\nERROR:  insert or update on table \"_hyper_2_6_chunk\" violates foreign key constraint \"6_1_hyper_meta_id_fkey\"\nCOPY hyper (time, meta_id, value) FROM STDIN DELIMITER ',';\nERROR:  insert or update on table \"_hyper_2_6_chunk\" violates foreign key constraint \"6_1_hyper_meta_id_fkey\"\n\\set ON_ERROR_STOP 1\nCOPY (SELECT * FROM hyper ORDER BY time, meta_id) TO STDOUT;\n1\t1\t1\n1\t2\t1\n--test that copy works with a low setting for max_open_chunks_per_insert\nset timescaledb.max_open_chunks_per_insert = 1;\nCREATE TABLE \"hyper2\" (\n    \"time\" bigint NOT NULL,\n    \"value\" double precision NOT NULL\n);\nSELECT create_hypertable('hyper2', 'time', chunk_time_interval => 10);\n  create_hypertable  \n---------------------\n (3,public,hyper2,t)\n\n\\copy hyper2 from data/copy_data.csv with csv header ;\n-- test copy with blocking trigger\nCREATE FUNCTION gt_10() RETURNS trigger AS\n$func$\nBEGIN\n    IF NEW.\"time\" < 11\n        THEN RETURN NULL;\n    END IF;\n    RETURN NEW;\nEND\n$func$ LANGUAGE plpgsql;\nCREATE TABLE \"trigger_test\" (\n    \"time\" bigint NOT NULL,\n    \"value\" double precision NOT NULL\n);\nSELECT create_hypertable('trigger_test', 'time', chunk_time_interval => 10);\n     create_hypertable     \n---------------------------\n (4,public,trigger_test,t)\n\nCREATE TRIGGER check_time BEFORE INSERT ON trigger_test\nFOR EACH ROW EXECUTE FUNCTION gt_10();\n\\copy trigger_test from data/copy_data.csv with csv header ;\nSELECT * FROM trigger_test ORDER BY time;\n time |       value        \n------+--------------------\n   11 |  0.795640022493899\n   12 |  0.631451691035181\n   13 | 0.0958626130595803\n   14 |  0.929304684977978\n   15 |  0.524866581428796\n   16 |  0.919249163009226\n   17 |  0.878917074296623\n   18 |   0.68551931809634\n   19 |  0.594833800103515\n   20 |  0.819584367796779\n   21 |  0.474171321373433\n   22 |  0.938535195309669\n   23 |  0.333933369256556\n   24 |  0.274582070298493\n   25 |  0.602348630782217\n\n-- Test that if we copy from stdin to a hypertable and violate a null\n-- constraint, it does not crash and generate an appropriate error\n-- message.\nCREATE TABLE test(a INT NOT NULL, b TIMESTAMPTZ);\nSELECT create_hypertable('test', 'b');\n create_hypertable \n-------------------\n (5,public,test,t)\n\n\\set ON_ERROR_STOP 0\nCOPY TEST (a,b) FROM STDIN (delimiter ',', null 'N');\nERROR:  null value in column \"a\" of relation \"_hyper_5_13_chunk\" violates not-null constraint\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET client_min_messages TO NOTICE;\n-- Do a basic test of COPY with a wrong PROGRAM\nCOPY hyper FROM PROGRAM 'error';\nERROR:  program \"error\" failed\n\\set ON_ERROR_STOP 1\n----------------------------------------------------------------\n-- Testing COPY TO.\n----------------------------------------------------------------\n-- COPY TO using a hypertable will not copy any tuples, but should\n-- show a notice.\nCOPY hyper TO STDOUT DELIMITER ',';\nNOTICE:  hypertable data are in the chunks, no data will be copied\n-- COPY TO using a query should display all the tuples and not show a\n-- notice.\nCOPY (SELECT * FROM hyper) TO STDOUT DELIMITER ',';\n1,1,1\n1,2,1\n----------------------------------------------------------------\n-- Testing multi-buffer optimization.\n----------------------------------------------------------------\nCREATE TABLE \"hyper_copy\" (\n    \"time\" bigint NOT NULL,\n    \"value\" double precision NOT NULL\n);\nSELECT create_hypertable('hyper_copy', 'time', chunk_time_interval => 2);\n    create_hypertable    \n-------------------------\n (6,public,hyper_copy,t)\n\n-- First copy call with default client_min_messages, to get rid of the\n-- building index \"_hyper_XXX_chunk_hyper_copy_time_idx\" on table \"_hyper_XXX_chunk\" serially\n-- messages\n\\copy hyper_copy FROM data/copy_data.csv WITH csv header;\nSET client_min_messages TO DEBUG1;\n\\copy hyper_copy FROM data/copy_data.csv WITH csv header;\nDEBUG:  Using optimized multi-buffer copy operation (TS_CIM_MULTI_CONDITIONAL).\nSELECT count(*) FROM hyper_copy;\n count \n-------\n    50\n\n-- Limit number of open chunks\nSET timescaledb.max_open_chunks_per_insert = 1;\n\\copy hyper_copy FROM data/copy_data.csv WITH csv header;\nDEBUG:  Using optimized multi-buffer copy operation (TS_CIM_MULTI_CONDITIONAL).\nSELECT count(*) FROM hyper_copy;\n count \n-------\n    75\n\n-- Before trigger disable the multi-buffer optimization\nCREATE OR REPLACE FUNCTION empty_test_trigger()\n    RETURNS TRIGGER LANGUAGE PLPGSQL AS\n$BODY$\nBEGIN\n    IF TG_OP = 'DELETE' THEN\n        RETURN OLD;\n    END IF;\n    RETURN NEW;\nEND\n$BODY$;\n-- Before trigger (CIM_SINGLE should be used)\nCREATE TRIGGER hyper_copy_trigger_insert_before\n    BEFORE INSERT ON hyper_copy\n    FOR EACH ROW EXECUTE FUNCTION empty_test_trigger();\n\\copy hyper_copy FROM data/copy_data.csv WITH csv header;\nDEBUG:  Using normal unbuffered copy operation (TS_CIM_SINGLE) because triggers are defined on the destination table.\nSELECT count(*) FROM hyper_copy;\n count \n-------\n   100\n\n-- Suppress 'DEBUG:  EventTriggerInvoke XXXX' messages\nRESET client_min_messages;\nDROP TRIGGER hyper_copy_trigger_insert_before ON hyper_copy;\nSET client_min_messages TO DEBUG1;\n-- After trigger (CIM_MULTI_CONDITIONAL should be used)\nCREATE TRIGGER hyper_copy_trigger_insert_after\n    AFTER INSERT ON hyper_copy\n    FOR EACH ROW EXECUTE FUNCTION empty_test_trigger();\n\\copy hyper_copy FROM data/copy_data.csv WITH csv header;\nDEBUG:  Using optimized multi-buffer copy operation (TS_CIM_MULTI_CONDITIONAL).\nSELECT count(*) FROM hyper_copy;\n count \n-------\n   125\n\n-- Insert data into the chunks in random order\nCOPY hyper_copy FROM STDIN DELIMITER ',' NULL AS 'null';\nDEBUG:  Using optimized multi-buffer copy operation (TS_CIM_MULTI_CONDITIONAL).\nSELECT count(*) FROM hyper_copy;\n count \n-------\n   154\n\nRESET client_min_messages;\nRESET timescaledb.max_open_chunks_per_insert;\n----------------------------------------------------------------\n-- Testing multi-buffer optimization\n-- (no index on destination hypertable).\n----------------------------------------------------------------\nCREATE TABLE \"hyper_copy_noindex\" (\n    \"time\" bigint NOT NULL,\n    \"value\" double precision NOT NULL\n);\nSELECT create_hypertable('hyper_copy_noindex', 'time', chunk_time_interval => 10, create_default_indexes => false);\n        create_hypertable        \n---------------------------------\n (7,public,hyper_copy_noindex,t)\n\n-- No trigger\n\\copy hyper_copy_noindex FROM data/copy_data.csv WITH csv header;\nSET client_min_messages TO DEBUG1;\n\\copy hyper_copy_noindex FROM data/copy_data.csv WITH csv header;\nDEBUG:  Using optimized multi-buffer copy operation (TS_CIM_MULTI_CONDITIONAL).\nRESET client_min_messages;\nSELECT count(*) FROM hyper_copy_noindex;\n count \n-------\n    50\n\n-- Before trigger (CIM_SINGLE should be used)\nCREATE TRIGGER hyper_copy_trigger_insert_before\n    BEFORE INSERT ON hyper_copy_noindex\n    FOR EACH ROW EXECUTE FUNCTION empty_test_trigger();\n\\copy hyper_copy_noindex FROM data/copy_data.csv WITH csv header;\nSET client_min_messages TO DEBUG1;\n\\copy hyper_copy_noindex FROM data/copy_data.csv WITH csv header;\nDEBUG:  Using normal unbuffered copy operation (TS_CIM_SINGLE) because triggers are defined on the destination table.\nRESET client_min_messages;\nSELECT count(*) FROM hyper_copy_noindex;\n count \n-------\n   100\n\n-- After trigger (CIM_MULTI_CONDITIONAL should be used)\nDROP TRIGGER hyper_copy_trigger_insert_before ON hyper_copy_noindex;\nCREATE TRIGGER hyper_copy_trigger_insert_after\n    AFTER INSERT ON hyper_copy_noindex\n    FOR EACH ROW EXECUTE FUNCTION empty_test_trigger();\n\\copy hyper_copy_noindex FROM data/copy_data.csv WITH csv header;\nSET client_min_messages TO DEBUG1;\n\\copy hyper_copy_noindex FROM data/copy_data.csv WITH csv header;\nDEBUG:  Using optimized multi-buffer copy operation (TS_CIM_MULTI_CONDITIONAL).\nRESET client_min_messages;\nSELECT count(*) FROM hyper_copy_noindex;\n count \n-------\n   150\n\n----------------------------------------------------------------\n-- Testing multi-buffer optimization\n-- (more chunks than MAX_PARTITION_BUFFERS).\n----------------------------------------------------------------\nCREATE TABLE \"hyper_copy_large\" (\n    \"time\" timestamp NOT NULL,\n    \"value\" double precision NOT NULL\n);\n-- Genate data that will create more than 32 (MAX_PARTITION_BUFFERS)\n-- chunks on the 10 second chunk_time_interval partitioned hypertable.\nINSERT INTO hyper_copy_large\nSELECT time,\nrandom() AS value\nFROM\ngenerate_series('2022-01-01', '2022-01-31', INTERVAL '1 hour') AS g1(time)\nORDER BY time;\nSELECT COUNT(*) FROM hyper_copy_large;\n count \n-------\n   721\n\n-- Migrate data to chunks by using copy\nSELECT create_hypertable('hyper_copy_large', 'time',\n   chunk_time_interval => INTERVAL '1 hour', migrate_data => 'true');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\nNOTICE:  migrating data to chunks\n       create_hypertable       \n-------------------------------\n (8,public,hyper_copy_large,t)\n\nSELECT COUNT(*) FROM hyper_copy_large;\n count \n-------\n   721\n\n----------------------------------------------------------------\n-- Testing multi-buffer optimization\n-- (triggers on chunks).\n----------------------------------------------------------------\nCREATE TABLE \"table_with_chunk_trigger\" (\n    \"time\" bigint NOT NULL,\n    \"value\" double precision NOT NULL\n);\n-- This trigger counts the already inserted tuples in\n-- the table table_with_chunk_trigger.\nCREATE OR REPLACE FUNCTION count_test_chunk_trigger()\n    RETURNS TRIGGER LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n    cnt INTEGER;\nBEGIN\n    SELECT count(*) FROM table_with_chunk_trigger INTO cnt;\n    RAISE WARNING 'Trigger counted % tuples in table table_with_chunk_trigger', cnt;\n    IF TG_OP = 'DELETE' THEN\n        RETURN OLD;\n    END IF;\n    RETURN NEW;\nEND\n$BODY$;\n-- Create hypertable and chunks\nSELECT create_hypertable('table_with_chunk_trigger', 'time', chunk_time_interval => 1);\n           create_hypertable           \n---------------------------------------\n (9,public,table_with_chunk_trigger,t)\n\n-- Insert data to create all missing chunks\n\\copy table_with_chunk_trigger from data/copy_data.csv with csv header;\nSELECT count(*) FROM table_with_chunk_trigger;\n count \n-------\n    25\n\n-- Chunk 1: 1-2, Chunk 2: 2-3, Chunk 3: 3-4, Chunk 4: 4-5\nSELECT chunk_schema, chunk_name FROM timescaledb_information.chunks\n    WHERE hypertable_name = 'table_with_chunk_trigger' AND range_end_integer=5 \\gset\n-- Create before trigger on the 4th chunk\nCREATE TRIGGER table_with_chunk_trigger_before_trigger\n    BEFORE INSERT ON :chunk_schema.:chunk_name\n    FOR EACH ROW EXECUTE FUNCTION count_test_chunk_trigger();\n-- Insert data\n-- 25 tuples are already imported. The trigger is executed before tuples\n-- are copied into the 4th chunk. So, the trigger should report 25+3 = 28\n-- This test requires that the multi-insert buffers of the other chunks\n-- are flushed before the trigger is executed.\nSET client_min_messages TO DEBUG1;\n\\copy table_with_chunk_trigger FROM data/copy_data.csv WITH csv header;\nDEBUG:  Using optimized multi-buffer copy operation (TS_CIM_MULTI_CONDITIONAL).\nWARNING:  Trigger counted 28 tuples in table table_with_chunk_trigger\nRESET client_min_messages;\nSELECT count(*) FROM table_with_chunk_trigger;\n count \n-------\n    50\n\nDROP TRIGGER table_with_chunk_trigger_before_trigger ON :chunk_schema.:chunk_name;\n-- Create after trigger\nCREATE TRIGGER table_with_chunk_trigger_after_trigger\n    AFTER INSERT ON :chunk_schema.:chunk_name\n    FOR EACH ROW EXECUTE FUNCTION count_test_chunk_trigger();\n-- Insert data\n-- 50 tuples are already imported. The trigger is executed after all\n-- tuples are imported. So, the trigger should report 50+25 = 75\nSET client_min_messages TO DEBUG1;\n\\copy table_with_chunk_trigger FROM data/copy_data.csv WITH csv header;\nDEBUG:  Using optimized multi-buffer copy operation (TS_CIM_MULTI_CONDITIONAL).\nWARNING:  Trigger counted 75 tuples in table table_with_chunk_trigger\nRESET client_min_messages;\nSELECT count(*) FROM table_with_chunk_trigger;\n count \n-------\n    75\n\n-- Hypertable with after row trigger and no index\nDROP TABLE table_with_chunk_trigger;\nCREATE TABLE \"table_with_chunk_trigger\" (\n    \"time\" bigint NOT NULL,\n    \"value\" double precision NOT NULL\n);\n-- Create hypertable and chunks\nSELECT create_hypertable('table_with_chunk_trigger', 'time', chunk_time_interval => 1, create_default_indexes => false);\n           create_hypertable            \n----------------------------------------\n (10,public,table_with_chunk_trigger,t)\n\n-- Insert data to create all missing chunks\n\\copy table_with_chunk_trigger from data/copy_data.csv with csv header;\nSELECT count(*) FROM table_with_chunk_trigger;\n count \n-------\n    25\n\n-- Chunk 1: 1-2, Chunk 2: 2-3, Chunk 3: 3-4, Chunk 4: 4-5\nSELECT chunk_schema, chunk_name FROM timescaledb_information.chunks\n    WHERE hypertable_name = 'table_with_chunk_trigger' AND range_end_integer=5 \\gset\n-- Create after trigger\nCREATE TRIGGER table_with_chunk_trigger_after_trigger\n    AFTER INSERT ON :chunk_schema.:chunk_name\n    FOR EACH ROW EXECUTE FUNCTION count_test_chunk_trigger();\n\\copy table_with_chunk_trigger from data/copy_data.csv with csv header;\nWARNING:  Trigger counted 50 tuples in table table_with_chunk_trigger\nSELECT count(*) FROM table_with_chunk_trigger;\n count \n-------\n    50\n\n----------------------------------------------------------------\n-- Testing multi-buffer optimization\n-- (Hypertable without before insert trigger)\n----------------------------------------------------------------\nCREATE TABLE \"table_without_bf_trigger\" (\n    \"time\" bigint NOT NULL,\n    \"value\" double precision NOT NULL\n);\nSELECT create_hypertable('table_without_bf_trigger', 'time', chunk_time_interval => 1);\n           create_hypertable            \n----------------------------------------\n (11,public,table_without_bf_trigger,t)\n\n\\copy table_without_bf_trigger from data/copy_data.csv with csv header;\nSET client_min_messages TO DEBUG1;\n\\copy table_without_bf_trigger from data/copy_data.csv with csv header;\nDEBUG:  Using optimized multi-buffer copy operation (TS_CIM_MULTI_CONDITIONAL).\nRESET client_min_messages;\nSELECT count(*) FROM table_without_bf_trigger;\n count \n-------\n    50\n\n-- After trigger (CIM_MULTI_CONDITIONAL should be used)\nCREATE TRIGGER table_with_chunk_trigger_after_trigger\n    AFTER INSERT ON table_without_bf_trigger\n    FOR EACH ROW EXECUTE FUNCTION empty_test_trigger();\nSET client_min_messages TO DEBUG1;\n\\copy table_without_bf_trigger from data/copy_data.csv with csv header;\nDEBUG:  Using optimized multi-buffer copy operation (TS_CIM_MULTI_CONDITIONAL).\nRESET client_min_messages;\nSELECT count(*) FROM table_without_bf_trigger;\n count \n-------\n    75\n\n----------------------------------------------------------------\n-- Testing multi-buffer optimization\n-- (Chunks with different layouts)\n----------------------------------------------------------------\n-- Time is not the first attribute of the hypertable\nCREATE TABLE \"table_with_layout_change\" (\n    \"value1\" real NOT NULL DEFAULT 1,\n    \"value2\" smallint DEFAULT NULL,\n    \"value3\" bigint DEFAULT NULL,\n    \"time\" bigint NOT NULL,\n    \"value4\" double precision NOT NULL DEFAULT 4,\n    \"value5\" double precision NOT NULL DEFAULT 5\n);\nSELECT create_hypertable('table_with_layout_change', 'time', chunk_time_interval => 1);\n           create_hypertable            \n----------------------------------------\n (12,public,table_with_layout_change,t)\n\n-- Chunk 1 (time = 1)\nCOPY table_with_layout_change FROM STDIN DELIMITER ',' NULL AS 'null';\nSELECT * FROM table_with_layout_change;\n value1 | value2 | value3 | time | value4 | value5 \n--------+--------+--------+------+--------+--------\n    100 |    200 |    300 |    1 |    400 |    500\n\n-- Drop the first attribute\nALTER TABLE table_with_layout_change DROP COLUMN value1;\nSELECT * FROM table_with_layout_change;\n value2 | value3 | time | value4 | value5 \n--------+--------+------+--------+--------\n    200 |    300 |    1 |    400 |    500\n\n-- COPY into existing chunk (time = 1)\nCOPY table_with_layout_change FROM STDIN DELIMITER ',' NULL AS 'null';\n-- Create new chunk (time = 2)\nCOPY table_with_layout_change FROM STDIN DELIMITER ',' NULL AS 'null';\nSELECT * FROM table_with_layout_change ORDER BY time, value2, value3, value4, value5;\n value2 | value3 | time | value4 | value5 \n--------+--------+------+--------+--------\n    200 |    300 |    1 |    400 |    500\n    201 |    301 |    1 |    401 |    501\n    202 |    302 |    2 |    402 |    502\n\n-- Create new chunk (time = 2), insert in different order\nCOPY table_with_layout_change (time, value5, value4, value3, value2) FROM STDIN DELIMITER ',' NULL AS 'null';\nCOPY table_with_layout_change (value5, value4, value3, value2, time) FROM STDIN DELIMITER ',' NULL AS 'null';\nCOPY table_with_layout_change (value5, value4, value3, time, value2) FROM STDIN DELIMITER ',' NULL AS 'null';\nSELECT * FROM table_with_layout_change ORDER BY time, value2, value3, value4, value5;\n value2 | value3 | time | value4 | value5 \n--------+--------+------+--------+--------\n    200 |    300 |    1 |    400 |    500\n    201 |    301 |    1 |    401 |    501\n    202 |    302 |    2 |    402 |    502\n    203 |    303 |    2 |    403 |    503\n    204 |    304 |    2 |    404 |    504\n    205 |    305 |    2 |    405 |    505\n\n-- Drop the last attribute and add a new one\nALTER TABLE table_with_layout_change DROP COLUMN value5;\nALTER TABLE table_with_layout_change ADD COLUMN value6 double precision NOT NULL default 600;\nSELECT * FROM table_with_layout_change ORDER BY time, value2, value3, value4, value6;\n value2 | value3 | time | value4 | value6 \n--------+--------+------+--------+--------\n    200 |    300 |    1 |    400 |    600\n    201 |    301 |    1 |    401 |    600\n    202 |    302 |    2 |    402 |    600\n    203 |    303 |    2 |    403 |    600\n    204 |    304 |    2 |    404 |    600\n    205 |    305 |    2 |    405 |    600\n\n-- COPY in first chunk (time = 1)\nCOPY table_with_layout_change (time, value2, value3, value4, value6) FROM STDIN DELIMITER ',' NULL AS 'null';\n-- COPY in second chunk (time = 2)\nCOPY table_with_layout_change (time, value2, value3, value4, value6) FROM STDIN DELIMITER ',' NULL AS 'null';\n-- COPY in new chunk (time = 3)\nCOPY table_with_layout_change (time, value2, value3, value4, value6) FROM STDIN DELIMITER ',' NULL AS 'null';\n-- COPY in all chunks, different attribute order\nCOPY table_with_layout_change (value3, value4, time, value6, value2) FROM STDIN DELIMITER ',' NULL AS 'null';\nSELECT * FROM table_with_layout_change ORDER BY time, value2, value3, value4, value6;\n value2 | value3 | time | value4 | value6 \n--------+--------+------+--------+--------\n    200 |    300 |    1 |    400 |    600\n    201 |    301 |    1 |    401 |    600\n    206 |    306 |    1 |    406 |    606\n    211 |    311 |    1 |    411 |    611\n    202 |    302 |    2 |    402 |    600\n    203 |    303 |    2 |    403 |    600\n    204 |    304 |    2 |    404 |    600\n    205 |    305 |    2 |    405 |    600\n    207 |    307 |    2 |    407 |    607\n    210 |    310 |    2 |    410 |    610\n    208 |    308 |    3 |    408 |    608\n    209 |    309 |    3 |    409 |    609\n\n-- Drop first column\nALTER TABLE table_with_layout_change DROP COLUMN value2;\nSELECT * FROM table_with_layout_change ORDER BY time, value3, value4, value6;\n value3 | time | value4 | value6 \n--------+------+--------+--------\n    300 |    1 |    400 |    600\n    301 |    1 |    401 |    600\n    306 |    1 |    406 |    606\n    311 |    1 |    411 |    611\n    302 |    2 |    402 |    600\n    303 |    2 |    403 |    600\n    304 |    2 |    404 |    600\n    305 |    2 |    405 |    600\n    307 |    2 |    407 |    607\n    310 |    2 |    410 |    610\n    308 |    3 |    408 |    608\n    309 |    3 |    409 |    609\n\n-- COPY in all exiting chunks and create a new one (time 4)\nCOPY table_with_layout_change (value3, value4, time, value6) FROM STDIN DELIMITER ',' NULL AS 'null';\nSELECT * FROM table_with_layout_change ORDER BY time, value3, value4, value6;\n value3 | time | value4 | value6 \n--------+------+--------+--------\n    300 |    1 |    400 |    600\n    301 |    1 |    401 |    600\n    306 |    1 |    406 |    606\n    311 |    1 |    411 |    611\n    315 |    1 |    415 |    615\n    302 |    2 |    402 |    600\n    303 |    2 |    403 |    600\n    304 |    2 |    404 |    600\n    305 |    2 |    405 |    600\n    307 |    2 |    407 |    607\n    310 |    2 |    410 |    610\n    313 |    2 |    413 |    613\n    308 |    3 |    408 |    608\n    309 |    3 |    409 |    609\n    312 |    3 |    412 |    612\n    314 |    4 |    414 |    614\n\n-- Drop the last two columns\nALTER TABLE table_with_layout_change DROP COLUMN value4;\nALTER TABLE table_with_layout_change DROP COLUMN value6;\n-- COPY in all exiting chunks and create a new one (time 5)\nCOPY table_with_layout_change (value3, time) FROM STDIN DELIMITER ',' NULL AS 'null';\nSELECT * FROM table_with_layout_change ORDER BY time, value3;\n value3 | time \n--------+------\n    300 |    1\n    301 |    1\n    306 |    1\n    311 |    1\n    315 |    1\n    317 |    1\n    302 |    2\n    303 |    2\n    304 |    2\n    305 |    2\n    307 |    2\n    310 |    2\n    313 |    2\n    316 |    2\n    308 |    3\n    309 |    3\n    312 |    3\n    318 |    3\n    314 |    4\n    320 |    4\n    319 |    5\n\n-- Drop the last of the initial attributes and add a new one\nALTER TABLE table_with_layout_change DROP COLUMN value3;\nALTER TABLE table_with_layout_change ADD COLUMN value7 double precision NOT NULL default 700;\n-- COPY in all exiting chunks and create a new one (time 6)\nCOPY table_with_layout_change (value7, time) FROM STDIN DELIMITER ',' NULL AS 'null';\nSELECT * FROM table_with_layout_change ORDER BY time, value7;\n time | value7 \n------+--------\n    1 |    700\n    1 |    700\n    1 |    700\n    1 |    700\n    1 |    700\n    1 |    700\n    1 |    722\n    2 |    700\n    2 |    700\n    2 |    700\n    2 |    700\n    2 |    700\n    2 |    700\n    2 |    700\n    2 |    700\n    2 |    721\n    3 |    700\n    3 |    700\n    3 |    700\n    3 |    700\n    3 |    723\n    4 |    700\n    4 |    700\n    4 |    726\n    5 |    700\n    5 |    724\n    6 |    725\n\n-- verify check constraints work\nCREATE TABLE test_check(a INT, b TIMESTAMPTZ);\nALTER TABLE test_check ADD CONSTRAINT c1 CHECK (a > 7);\nSELECT table_name FROM create_hypertable('test_check', 'b');\n table_name \n------------\n test_check\n\nCOPY test_check(a,b) FROM STDIN (delimiter ',', null 'N');\n\\set ON_ERROR_STOP 0\nCOPY test_check(a,b) FROM STDIN (delimiter ',', null 'N');\nERROR:  new row for relation \"_hyper_13_832_chunk\" violates check constraint \"c1\"\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "test/expected/copy_memory_usage.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- Test that transaction memory usage with COPY doesn't grow.\n-- We need memory usage in PortalContext after the completion of the query, so\n-- we'll have to log it from a trigger that runs after the query is completed.\n\\c :TEST_DBNAME :ROLE_SUPERUSER;\ncreate table uk_price_paid(price integer, \"date\" date, postcode1 text, postcode2 text, type smallint, is_new bool, duration smallint, addr1 text, addr2 text, street text, locality text, town text, district text, country text, category smallint);\n-- Aim to about 100 partitions, the data is from 1995 to 2022.\nselect create_hypertable('uk_price_paid', 'date', chunk_time_interval => interval '90 day');\n     create_hypertable      \n----------------------------\n (1,public,uk_price_paid,t)\n\n-- This is where we log the memory usage.\ncreate table portal_memory_log(id serial, bytes bigint);\n-- Returns the amount of memory currently allocated in a given\n-- memory context. Only works for PortalContext, and doesn't work for PG 12.\ncreate or replace function ts_debug_allocated_bytes(text) returns bigint\n    as :MODULE_PATHNAME, 'ts_debug_allocated_bytes'\n    language c strict volatile;\n-- Log current memory usage into the log table.\ncreate function log_memory() returns trigger as $$\n    begin\n        insert into portal_memory_log\n            values (default, ts_debug_allocated_bytes('PortalContext'));\n        return new;\n    end;\n$$ language plpgsql;\n-- Prepare version dependent TopTransactionContext total memory usage query.\n-- Using prepared statements to avoid contaminating memory usage numbers.\n-- PG18 removed parent column so we have to use path to get TopTransactionContext child entries.\n-- https://github.com/postgres/postgres/commit/f0d11275\ncreate or replace function prepare_transaction_total_memory_usage_stmt() returns void\nlanguage plpgsql as\n$$\nbegin\n    if current_setting('server_version_num')::int < 180000 then\n        prepare total_stmt as select sum(total_bytes)\n        from pg_backend_memory_contexts\n\t\twhere parent = 'TopTransactionContext';\n    else\n        prepare total_stmt as select sum(m.total_bytes)\n        from pg_backend_memory_contexts m\n        inner join pg_backend_memory_contexts p\n            on (m.path[m.level-1] = p.path[p.level])\n        where p.name = 'TopTransactionContext';\n    end if;\nend;\n$$;\n-- Add a trigger that runs after completion of each INSERT/COPY and logs the\n-- current memory usage.\ncreate trigger check_update after insert on uk_price_paid\n    for each statement execute function log_memory();\n-- Memory leaks often happen on cache invalidation, so make sure they are\n-- invalidated often and independently (at co-prime periods).\nset timescaledb.max_open_chunks_per_insert = 2;\nset timescaledb.max_cached_chunks_per_hypertable = 3;\n-- Try increasingly larger data sets by concatenating the same file multiple\n-- times.\n\\copy uk_price_paid from program 'bash -c \"cat <(zcat < data/prices-10k-random-1.tsv.gz)\"';\n\\copy uk_price_paid from program 'bash -c \"cat <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz)\"';\n\\copy uk_price_paid from program 'bash -c \"cat <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz)\"';\n\\copy uk_price_paid from program 'bash -c \"cat <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz)\"';\n\\copy uk_price_paid from program 'bash -c \"cat <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz)\"';\nselect count(*) from portal_memory_log;\n count \n-------\n     5\n\n-- Check that the memory doesn't increase with file size by using linear regression.\nselect * from portal_memory_log where (\n    select regr_slope(bytes, id - 1) / regr_intercept(bytes, id - 1)::float > 0.05\n        from portal_memory_log\n);\n id | bytes \n----+-------\n\n-- Test plpgsql leaks\nCREATE TABLE test_ht(tm timestamptz, val float8);\nSELECT * FROM create_hypertable('test_ht', 'tm');\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             2 | public      | test_ht    | t\n\n-- Use a plpgsql function to insert into the hypertable\nCREATE OR REPLACE FUNCTION to_double(_in text, INOUT _out double precision)\nLANGUAGE plpgsql IMMUTABLE parallel safe\nAS $$\nBEGIN\n    SELECT CAST(_in AS double precision) INTO _out;\nEXCEPTION WHEN others THEN\n    --do nothing: _out already carries default\nEND;\n$$;\n-- TopTransactionContext usage needs to remain the same after every insert\n-- There was a leak earlier in the child CurTransactionContext\nSELECT prepare_transaction_total_memory_usage_stmt();\n prepare_transaction_total_memory_usage_stmt \n---------------------------------------------\n \n\nBEGIN;\nINSERT INTO test_ht VALUES ('1980-01-01 00:00:00-00', to_double('23.11', 0));\nEXECUTE total_stmt;\n  sum  \n-------\n 16384\n\nINSERT INTO test_ht VALUES ('1980-02-01 00:00:00-00', to_double('24.11', 0));\nEXECUTE total_stmt;\n  sum  \n-------\n 16384\n\nCOMMIT;\n"
  },
  {
    "path": "test/expected/copy_where.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n------- TEST 1: Restrictive copy from file\nCREATE TABLE \"copy_golden\" (\n    \"time\" bigint NOT NULL,\n    \"value\" double precision NOT NULL\n);\n\\COPY copy_golden (time, value) FROM data/copy_data.csv WITH CSV HEADER\nSELECT * FROM copy_golden ORDER BY TIME;\n time |       value        \n------+--------------------\n    1 |  0.951734602451324\n    2 |  0.717823888640851\n    3 |  0.543408489786088\n    4 |  0.641131274402142\n    5 |   0.12689296528697\n    6 | 0.0126486560329795\n    7 |  0.213605496101081\n    8 |  0.132784110959619\n    9 |  0.381155731156468\n   10 |  0.284836102742702\n   11 |  0.795640022493899\n   12 |  0.631451691035181\n   13 | 0.0958626130595803\n   14 |  0.929304684977978\n   15 |  0.524866581428796\n   16 |  0.919249163009226\n   17 |  0.878917074296623\n   18 |   0.68551931809634\n   19 |  0.594833800103515\n   20 |  0.819584367796779\n   21 |  0.474171321373433\n   22 |  0.938535195309669\n   23 |  0.333933369256556\n   24 |  0.274582070298493\n   25 |  0.602348630782217\n\nCREATE TABLE \"copy_control\" (\n    \"time\" bigint NOT NULL,\n    \"value\" double precision NOT NULL\n);\n\\COPY copy_control (time, value) FROM data/copy_data.csv WITH CSV HEADER WHERE time > 10;\nSELECT * FROM copy_control ORDER BY TIME;\n time |       value        \n------+--------------------\n   11 |  0.795640022493899\n   12 |  0.631451691035181\n   13 | 0.0958626130595803\n   14 |  0.929304684977978\n   15 |  0.524866581428796\n   16 |  0.919249163009226\n   17 |  0.878917074296623\n   18 |   0.68551931809634\n   19 |  0.594833800103515\n   20 |  0.819584367796779\n   21 |  0.474171321373433\n   22 |  0.938535195309669\n   23 |  0.333933369256556\n   24 |  0.274582070298493\n   25 |  0.602348630782217\n\nCREATE TABLE \"copy_test\" (\n    \"time\" bigint NOT NULL,\n    \"value\" double precision NOT NULL\n);\nSELECT create_hypertable('copy_test', 'time', chunk_time_interval => 10);\n   create_hypertable    \n------------------------\n (1,public,copy_test,t)\n\n\\COPY copy_test (time, value) FROM data/copy_data.csv WITH CSV HEADER WHERE time > 10;\nSELECT * FROM copy_test ORDER BY TIME;\n time |       value        \n------+--------------------\n   11 |  0.795640022493899\n   12 |  0.631451691035181\n   13 | 0.0958626130595803\n   14 |  0.929304684977978\n   15 |  0.524866581428796\n   16 |  0.919249163009226\n   17 |  0.878917074296623\n   18 |   0.68551931809634\n   19 |  0.594833800103515\n   20 |  0.819584367796779\n   21 |  0.474171321373433\n   22 |  0.938535195309669\n   23 |  0.333933369256556\n   24 |  0.274582070298493\n   25 |  0.602348630782217\n\n-- Verify attempting to use subqueries fails the same as non-hypertables\n\\set ON_ERROR_STOP 0\n\\COPY copy_control (time, value) FROM data/copy_data.csv WITH CSV HEADER WHERE time IN (SELECT time FROM copy_golden);\nERROR:  cannot use subquery in COPY FROM WHERE condition at character 74\n\\COPY copy_test (time, value) FROM data/copy_data.csv WITH CSV HEADER WHERE time IN (SELECT time FROM copy_golden);\nERROR:  cannot use subquery in COPY FROM WHERE condition at character 71\n\\set ON_ERROR_STOP 1\nDROP TABLE copy_golden;\nDROP TABLE copy_control;\nDROP TABLE copy_test;\n"
  },
  {
    "path": "test/expected/create_chunks.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n--\n--  This test will create chunks in two dimenisions, time (x) and\n--  space (y), where the time dimension is aligned. The figure below\n--  shows the expected result. The chunk number in the figure\n--  indicates the creation order.\n--\n--  +\n--  +\n--  +     +-----+     +-----+\n--  +     |  2  |     |  3  |\n--  +     |     +---+-+     |\n--  +     +-----+ 5 |6+-----+\n--  +     |  1  +---+-+-----+     +---------+\n--  +     |     |   |4|  7  |     |    8    |\n--  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-\n--  0         5         10        15        20\n--\n-- Partitioning:\n--\n-- Chunk #  |  time  | space\n--    1     |   3    |   2\n--    4     |   1    |   3\n--    5     |   5    |   3\n--\nCREATE TABLE chunk_test(time integer, temp float8, tag integer, color integer);\nSELECT create_hypertable('chunk_test', 'time', 'tag', 2, chunk_time_interval => 3);\n    create_hypertable    \n-------------------------\n (1,public,chunk_test,t)\n\nINSERT INTO chunk_test VALUES (4, 24.3, 1, 1);\nSELECT * FROM _timescaledb_catalog.dimension_slice;\n id | dimension_id |     range_start      | range_end  \n----+--------------+----------------------+------------\n  1 |            1 |                    3 |          6\n  2 |            2 | -9223372036854775808 | 1073741823\n\nINSERT INTO chunk_test VALUES (4, 24.3, 2, 1);\nINSERT INTO chunk_test VALUES (10, 24.3, 2, 1);\nSELECT c.table_name AS chunk_name, d.id AS dimension_id, ds.id AS slice_id, range_start, range_end FROM _timescaledb_catalog.chunk c\nLEFT JOIN _timescaledb_catalog.chunk_constraint cc ON (c.id = cc.chunk_id)\nLEFT JOIN _timescaledb_catalog.dimension_slice ds ON (ds.id = cc.dimension_slice_id)\nLEFT JOIN _timescaledb_catalog.dimension d ON (d.id = ds.dimension_id)\nLEFT JOIN _timescaledb_catalog.hypertable h ON (d.hypertable_id = h.id)\nWHERE h.schema_name = 'public' AND h.table_name = 'chunk_test'\nORDER BY c.id, d.id;\n    chunk_name    | dimension_id | slice_id |     range_start      |      range_end      \n------------------+--------------+----------+----------------------+---------------------\n _hyper_1_1_chunk |            1 |        1 |                    3 |                   6\n _hyper_1_1_chunk |            2 |        2 | -9223372036854775808 |          1073741823\n _hyper_1_2_chunk |            1 |        1 |                    3 |                   6\n _hyper_1_2_chunk |            2 |        3 |           1073741823 | 9223372036854775807\n _hyper_1_3_chunk |            1 |        4 |                    9 |                  12\n _hyper_1_3_chunk |            2 |        3 |           1073741823 | 9223372036854775807\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT set_number_partitions('chunk_test', 3);\n set_number_partitions \n-----------------------\n \n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT set_chunk_time_interval('chunk_test', 1::bigint);\n set_chunk_time_interval \n-------------------------\n \n\nINSERT INTO chunk_test VALUES (8, 24.3, 11233, 1);\nSELECT set_chunk_time_interval('chunk_test', 5::bigint);\n set_chunk_time_interval \n-------------------------\n \n\nSELECT * FROM _timescaledb_catalog.dimension;\n id | hypertable_id | column_name | column_type | aligned | num_slices | partitioning_func_schema | partitioning_func  | interval_length | compress_interval_length | integer_now_func_schema | integer_now_func \n----+---------------+-------------+-------------+---------+------------+--------------------------+--------------------+-----------------+--------------------------+-------------------------+------------------\n  2 |             1 | tag         | integer     | f       |          3 | _timescaledb_functions   | get_partition_hash |                 |                          |                         | \n  1 |             1 | time        | integer     | t       |            |                          |                    |               5 |                          |                         | \n\nINSERT INTO chunk_test VALUES (7, 24.3, 79669, 1);\nINSERT INTO chunk_test VALUES (8, 24.3, 79669, 1);\nINSERT INTO chunk_test VALUES (10, 24.3, 11233, 1);\nINSERT INTO chunk_test VALUES (16, 24.3, 11233, 1);\nSELECT c.table_name AS chunk_name, d.id AS dimension_id, ds.id AS slice_id, range_start, range_end FROM _timescaledb_catalog.chunk c\nLEFT JOIN _timescaledb_catalog.chunk_constraint cc ON (c.id = cc.chunk_id)\nLEFT JOIN _timescaledb_catalog.dimension_slice ds ON (ds.id = cc.dimension_slice_id)\nLEFT JOIN _timescaledb_catalog.dimension d ON (d.id = ds.dimension_id)\nLEFT JOIN _timescaledb_catalog.hypertable h ON (d.hypertable_id = h.id)\nWHERE h.schema_name = 'public' AND h.table_name = 'chunk_test'\nORDER BY c.id, d.id;\n    chunk_name    | dimension_id | slice_id |     range_start      |      range_end      \n------------------+--------------+----------+----------------------+---------------------\n _hyper_1_1_chunk |            1 |        1 |                    3 |                   6\n _hyper_1_1_chunk |            2 |        2 | -9223372036854775808 |          1073741823\n _hyper_1_2_chunk |            1 |        1 |                    3 |                   6\n _hyper_1_2_chunk |            2 |        3 |           1073741823 | 9223372036854775807\n _hyper_1_3_chunk |            1 |        4 |                    9 |                  12\n _hyper_1_3_chunk |            2 |        3 |           1073741823 | 9223372036854775807\n _hyper_1_4_chunk |            1 |        5 |                    8 |                   9\n _hyper_1_4_chunk |            2 |        6 | -9223372036854775808 |           715827882\n _hyper_1_5_chunk |            1 |        7 |                    6 |                   8\n _hyper_1_5_chunk |            2 |        8 |            715827882 |          1431655764\n _hyper_1_6_chunk |            1 |        5 |                    8 |                   9\n _hyper_1_6_chunk |            2 |        8 |            715827882 |          1431655764\n _hyper_1_7_chunk |            1 |        4 |                    9 |                  12\n _hyper_1_7_chunk |            2 |        6 | -9223372036854775808 |           715827882\n _hyper_1_8_chunk |            1 |        9 |                   15 |                  20\n _hyper_1_8_chunk |            2 |        6 | -9223372036854775808 |           715827882\n\n--test the edges of an open partition -- INT_64_MAX and INT_64_MIN.\nCREATE TABLE chunk_test_ends(time bigint, temp float8, tag integer, color integer);\nSELECT create_hypertable('chunk_test_ends', 'time', chunk_time_interval => 5);\n      create_hypertable       \n------------------------------\n (2,public,chunk_test_ends,t)\n\nINSERT INTO chunk_test_ends VALUES ((-9223372036854775808)::bigint, 23.1, 11233, 1);\nINSERT INTO chunk_test_ends VALUES (9223372036854775807::bigint, 24.1, 11233, 1);\n--try to hit cache\nINSERT INTO chunk_test_ends VALUES (9223372036854775807::bigint, 24.2, 11233, 1);\nINSERT INTO chunk_test_ends VALUES (9223372036854775807::bigint, 24.3, 11233, 1), (9223372036854775807::bigint, 24.4, 11233, 1);\nINSERT INTO chunk_test_ends VALUES ((-9223372036854775808)::bigint, 23.2, 11233, 1);\nINSERT INTO chunk_test_ends VALUES ((-9223372036854775808)::bigint, 23.3, 11233, 1), ((-9223372036854775808)::bigint, 23.4, 11233, 1);\nSELECT * FROM chunk_test_ends ORDER BY time asc, tag, temp;\n         time         | temp |  tag  | color \n----------------------+------+-------+-------\n -9223372036854775808 | 23.1 | 11233 |     1\n -9223372036854775808 | 23.2 | 11233 |     1\n -9223372036854775808 | 23.3 | 11233 |     1\n -9223372036854775808 | 23.4 | 11233 |     1\n  9223372036854775807 | 24.1 | 11233 |     1\n  9223372036854775807 | 24.2 | 11233 |     1\n  9223372036854775807 | 24.3 | 11233 |     1\n  9223372036854775807 | 24.4 | 11233 |     1\n\n--further tests of set_chunk_time_interval\nCREATE TABLE chunk_test2(time TIMESTAMPTZ, temp float8, tag integer, color integer);\nSELECT create_hypertable('chunk_test2', 'time', 'tag', 2, chunk_time_interval => 3);\nWARNING:  unexpected interval: smaller than one second\n    create_hypertable     \n--------------------------\n (3,public,chunk_test2,t)\n\nSELECT interval_length\nFROM _timescaledb_catalog.dimension d\nLEFT JOIN _timescaledb_catalog.hypertable h ON (d.hypertable_id = h.id)\nWHERE h.schema_name = 'public' AND h.table_name = 'chunk_test2'\nORDER BY d.id;\n interval_length \n-----------------\n               3\n                \n\n-- should work since time column is non-INT\nSELECT set_chunk_time_interval('chunk_test2', INTERVAL '1 minute');\n set_chunk_time_interval \n-------------------------\n \n\nSELECT interval_length\nFROM _timescaledb_catalog.dimension d\nLEFT JOIN _timescaledb_catalog.hypertable h ON (d.hypertable_id = h.id)\nWHERE h.schema_name = 'public' AND h.table_name = 'chunk_test2'\nORDER BY d.id;\n interval_length \n-----------------\n        60000000\n                \n\n-- should still work for non-INT time columns\nSELECT set_chunk_time_interval('chunk_test2', 1000000);\n set_chunk_time_interval \n-------------------------\n \n\nSELECT interval_length\nFROM _timescaledb_catalog.dimension d\nLEFT JOIN _timescaledb_catalog.hypertable h ON (d.hypertable_id = h.id)\nWHERE h.schema_name = 'public' AND h.table_name = 'chunk_test2'\nORDER BY d.id;\n interval_length \n-----------------\n         1000000\n                \n\n\\set ON_ERROR_STOP 0\nselect set_chunk_time_interval(NULL,NULL::interval);\nERROR:  hypertable cannot be NULL\n-- should fail since time column is an int\nSELECT set_chunk_time_interval('chunk_test', INTERVAL '1 minute');\nERROR:  invalid interval type for integer dimension\n-- should fail since its not a valid way to represent time\nSELECT set_chunk_time_interval('chunk_test', 'foo'::TEXT);\nERROR:  invalid interval type for integer dimension\nSELECT set_chunk_time_interval('chunk_test', NULL::BIGINT);\nERROR:  invalid interval: an explicit interval must be specified\nSELECT set_chunk_time_interval('chunk_test2', NULL::BIGINT);\nERROR:  invalid interval: an explicit interval must be specified\nSELECT set_chunk_time_interval('chunk_test2', NULL::INTERVAL);\nERROR:  invalid interval: an explicit interval must be specified\n\\set ON_ERROR_STOP 1\n-- Issue https://github.com/timescale/timescaledb/issues/7406\nCREATE TABLE test_ht (time TIMESTAMPTZ, v1 INTEGER);\nSELECT create_hypertable('test_ht', by_range('time', INTERVAL '1 hour'));\n create_hypertable \n-------------------\n (4,t)\n\nCREATE TABLE test_tb (time TIMESTAMPTZ, v1 INTEGER);\nCREATE OR REPLACE FUNCTION test_tb_trg_insert() RETURNS TRIGGER AS $$\nBEGIN\n  INSERT INTO test_ht VALUES (NEW.time, NEW.v1);\n  RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\nCREATE TRIGGER test_tb_trg_insert AFTER\nINSERT ON test_tb\nFOR EACH ROW EXECUTE FUNCTION test_tb_trg_insert();\n-- Creating new chunk inside a trigger called by\n-- a DDL statement should not fail.\nCREATE TABLE test_output AS\nWITH inserted AS (\n  INSERT INTO test_tb VALUES (NOW(), 1), (NOW(), 2) RETURNING *\n)\nSELECT * FROM inserted;\n-- Check the DEFAULT REPLICA IDENTITY of the chunks\nSELECT relname, relreplident FROM show_chunks('test_ht') ch JOIN pg_class c ON (ch = c.oid) ORDER BY relname;\n      relname      | relreplident \n-------------------+--------------\n _hyper_4_11_chunk | d\n\n-- Clean up\nTRUNCATE test_ht, test_tb;\nDROP TABLE test_output;\n-- Change the DEFAULT REPLICA IDENTITY of the chunks\nALTER TABLE test_ht REPLICA IDENTITY FULL;\n-- Internally we force new chunks have the same REPLICA IDENTITY\n-- as the parent table.\nCREATE TABLE test_output AS\nWITH inserted AS (\n  INSERT INTO test_tb VALUES (NOW(), 1), (NOW(), 2) RETURNING *\n)\nSELECT * FROM inserted;\n-- Check current new REPLICA IDENTITY FULL in the chunks\nSELECT relname, relreplident FROM show_chunks('test_ht') ch JOIN pg_class c ON (ch = c.oid) ORDER BY relname;\n      relname      | relreplident \n-------------------+--------------\n _hyper_4_12_chunk | f\n\n-- All tables should have the same number of rows\nSELECT count(*) FROM test_tb;\n count \n-------\n     2\n\nSELECT count(*) FROM test_ht;\n count \n-------\n     2\n\nSELECT count(*) FROM test_output;\n count \n-------\n     2\n\n-- test ALTER TABLE SET (tsdb.chunk_interval) on a hypertable\nCREATE TABLE t_with(time timestamptz not null, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time');\nSELECT time_interval FROM timescaledb_information.dimensions WHERE hypertable_name = 't_with';\n time_interval \n---------------\n @ 7 days\n\nALTER TABLE t_with SET (tsdb.chunk_interval = '1 hour');\nSELECT time_interval FROM timescaledb_information.dimensions WHERE hypertable_name = 't_with';\n time_interval \n---------------\n @ 1 hour\n\n"
  },
  {
    "path": "test/expected/create_hypertable.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\ncreate schema test_schema AUTHORIZATION :ROLE_DEFAULT_PERM_USER;\ncreate schema chunk_schema AUTHORIZATION :ROLE_DEFAULT_PERM_USER_2;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\ncreate table test_schema.test_table(time BIGINT, temp float8, device_id text, device_type text, location text, id int, id2 int);\n\\set ON_ERROR_STOP 0\n-- get_create_command should fail since hypertable isn't made yet\nSELECT * FROM _timescaledb_functions.get_create_command('test_table');\nERROR:  hypertable \"test_table\" not found\n\\set ON_ERROR_STOP 1\nSELECT * FROM test.relation WHERE schema = 'test_schema';\n   schema    |    name    | type  |       owner       \n-------------+------------+-------+-------------------\n test_schema | test_table | table | default_perm_user\n\n\\d _timescaledb_catalog.chunk\n                                               Table \"_timescaledb_catalog.chunk\"\n       Column        |           Type           | Collation | Nullable |                        Default                         \n---------------------+--------------------------+-----------+----------+--------------------------------------------------------\n id                  | integer                  |           | not null | nextval('_timescaledb_catalog.chunk_id_seq'::regclass)\n hypertable_id       | integer                  |           | not null | \n schema_name         | name                     |           | not null | \n table_name          | name                     |           | not null | \n compressed_chunk_id | integer                  |           |          | \n status              | integer                  |           | not null | 0\n osm_chunk           | boolean                  |           | not null | false\n creation_time       | timestamp with time zone |           | not null | \nIndexes:\n    \"chunk_pkey\" PRIMARY KEY, btree (id)\n    \"chunk_compressed_chunk_id_idx\" btree (compressed_chunk_id)\n    \"chunk_hypertable_id_creation_time_idx\" btree (hypertable_id, creation_time)\n    \"chunk_hypertable_id_idx\" btree (hypertable_id)\n    \"chunk_osm_chunk_idx\" btree (osm_chunk, hypertable_id)\n    \"chunk_schema_name_table_name_key\" UNIQUE CONSTRAINT, btree (schema_name, table_name)\nForeign-key constraints:\n    \"chunk_compressed_chunk_id_fkey\" FOREIGN KEY (compressed_chunk_id) REFERENCES _timescaledb_catalog.chunk(id)\n    \"chunk_hypertable_id_fkey\" FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable(id)\nReferenced by:\n    TABLE \"_timescaledb_internal.bgw_policy_chunk_stats\" CONSTRAINT \"bgw_policy_chunk_stats_chunk_id_fkey\" FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk(id) ON DELETE CASCADE\n    TABLE \"_timescaledb_catalog.chunk_column_stats\" CONSTRAINT \"chunk_column_stats_chunk_id_fkey\" FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk(id)\n    TABLE \"_timescaledb_catalog.chunk\" CONSTRAINT \"chunk_compressed_chunk_id_fkey\" FOREIGN KEY (compressed_chunk_id) REFERENCES _timescaledb_catalog.chunk(id)\n    TABLE \"_timescaledb_catalog.chunk_constraint\" CONSTRAINT \"chunk_constraint_chunk_id_fkey\" FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk(id)\n    TABLE \"_timescaledb_catalog.compression_chunk_size\" CONSTRAINT \"compression_chunk_size_chunk_id_fkey\" FOREIGN KEY (chunk_id) REFERENCES _timescaledb_catalog.chunk(id) ON DELETE CASCADE\n    TABLE \"_timescaledb_catalog.compression_chunk_size\" CONSTRAINT \"compression_chunk_size_compressed_chunk_id_fkey\" FOREIGN KEY (compressed_chunk_id) REFERENCES _timescaledb_catalog.chunk(id) ON DELETE CASCADE\n\ncreate table test_schema.test_table_no_not_null(time BIGINT, device_id text);\n\\set ON_ERROR_STOP 0\n-- Permission denied with unprivileged role\nSET ROLE :ROLE_DEFAULT_PERM_USER_2;\nselect * from create_hypertable('test_schema.test_table_no_not_null', 'time', 'device_id', 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nERROR:  permission denied for schema test_schema at character 33\n-- CREATE on schema is not enough\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nGRANT ALL ON SCHEMA test_schema TO :ROLE_DEFAULT_PERM_USER_2;\nSET ROLE :ROLE_DEFAULT_PERM_USER_2;\nselect * from create_hypertable('test_schema.test_table_no_not_null', 'time', 'device_id', 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nERROR:  must be owner of hypertable \"test_table_no_not_null\"\n\\set ON_ERROR_STOP 1\n-- Should work with when granted table owner role\nRESET ROLE;\nGRANT :ROLE_DEFAULT_PERM_USER TO :ROLE_DEFAULT_PERM_USER_2;\nSET ROLE :ROLE_DEFAULT_PERM_USER_2;\nselect * from create_hypertable('test_schema.test_table_no_not_null', 'time', 'device_id', 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name |       table_name       | created \n---------------+-------------+------------------------+---------\n             1 | test_schema | test_table_no_not_null | t\n\n\\set ON_ERROR_STOP 0\ninsert into test_schema.test_table_no_not_null (device_id) VALUES('foo');\nERROR:  NULL value in column \"time\" violates not-null constraint\n\\set ON_ERROR_STOP 1\ninsert into test_schema.test_table_no_not_null (time, device_id) VALUES(1, 'foo');\nRESET ROLE;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n\\set ON_ERROR_STOP 0\n-- No permissions on associated schema should fail\nselect * from create_hypertable('test_schema.test_table', 'time', 'device_id', 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'), associated_schema_name => 'chunk_schema');\nERROR:  permissions denied: cannot create chunks in schema \"chunk_schema\"\n\\set ON_ERROR_STOP 1\n-- Granting permissions on chunk_schema should make things work\nRESET ROLE;\nGRANT CREATE ON SCHEMA chunk_schema TO :ROLE_DEFAULT_PERM_USER;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nselect * from create_hypertable('test_schema.test_table', 'time', 'device_id', 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'), associated_schema_name => 'chunk_schema');\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             2 | test_schema | test_table | t\n\n-- Check that the insert block trigger exists\nSELECT * FROM test.show_triggers('test_schema.test_table');\n Trigger | Type | Function \n---------+------+----------\n\nSELECT * FROM _timescaledb_functions.get_create_command('test_table');\n                                                                get_create_command                                                                \n--------------------------------------------------------------------------------------------------------------------------------------------------\n SELECT create_hypertable('test_schema.test_table', 'time', 'device_id', 2, chunk_time_interval => 2592000000000, create_default_indexes=>FALSE);\n\n--test adding one more closed dimension\nselect add_dimension('test_schema.test_table', 'location', 4);\n             add_dimension             \n---------------------------------------\n (5,test_schema,test_table,location,t)\n\nselect * from _timescaledb_catalog.hypertable where table_name = 'test_table';\n id | schema_name | table_name | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------+------------+------------------------+-------------------------+----------------+--------------------------+--------------------------+-------------------+-------------------+--------------------------+--------\n  2 | test_schema | test_table | chunk_schema           | _hyper_2                |              3 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n\nselect * from _timescaledb_catalog.dimension;\n id | hypertable_id | column_name | column_type | aligned | num_slices | partitioning_func_schema | partitioning_func  | interval_length | compress_interval_length | integer_now_func_schema | integer_now_func \n----+---------------+-------------+-------------+---------+------------+--------------------------+--------------------+-----------------+--------------------------+-------------------------+------------------\n  1 |             1 | time        | bigint      | t       |            |                          |                    |   2592000000000 |                          |                         | \n  2 |             1 | device_id   | text        | f       |          2 | _timescaledb_functions   | get_partition_hash |                 |                          |                         | \n  3 |             2 | time        | bigint      | t       |            |                          |                    |   2592000000000 |                          |                         | \n  4 |             2 | device_id   | text        | f       |          2 | _timescaledb_functions   | get_partition_hash |                 |                          |                         | \n  5 |             2 | location    | text        | f       |          4 | _timescaledb_functions   | get_partition_hash |                 |                          |                         | \n\n--test that we can change the number of partitions and that 1 is allowed\nSELECT set_number_partitions('test_schema.test_table', 1, 'location');\n set_number_partitions \n-----------------------\n \n\nselect * from _timescaledb_catalog.dimension WHERE column_name = 'location';\n id | hypertable_id | column_name | column_type | aligned | num_slices | partitioning_func_schema | partitioning_func  | interval_length | compress_interval_length | integer_now_func_schema | integer_now_func \n----+---------------+-------------+-------------+---------+------------+--------------------------+--------------------+-----------------+--------------------------+-------------------------+------------------\n  5 |             2 | location    | text        | f       |          1 | _timescaledb_functions   | get_partition_hash |                 |                          |                         | \n\nSELECT set_number_partitions('test_schema.test_table', 2, 'location');\n set_number_partitions \n-----------------------\n \n\nselect * from _timescaledb_catalog.dimension WHERE column_name = 'location';\n id | hypertable_id | column_name | column_type | aligned | num_slices | partitioning_func_schema | partitioning_func  | interval_length | compress_interval_length | integer_now_func_schema | integer_now_func \n----+---------------+-------------+-------------+---------+------------+--------------------------+--------------------+-----------------+--------------------------+-------------------------+------------------\n  5 |             2 | location    | text        | f       |          2 | _timescaledb_functions   | get_partition_hash |                 |                          |                         | \n\n\\set ON_ERROR_STOP 0\n--must give an explicit dimension when there are multiple space dimensions\nSELECT set_number_partitions('test_schema.test_table', 3);\nERROR:  hypertable \"test_table\" has multiple space dimensions\n--too few\nSELECT set_number_partitions('test_schema.test_table', 0, 'location');\nERROR:  invalid number of partitions: must be between 1 and 32767\n-- Too many\nSELECT set_number_partitions('test_schema.test_table', 32768, 'location');\nERROR:  invalid number of partitions: must be between 1 and 32767\n-- get_create_command only works on tables w/ 1 or 2 dimensions\nSELECT * FROM _timescaledb_functions.get_create_command('test_table');\nERROR:  get_create_command only supports hypertables with up to 2 dimensions\n\\set ON_ERROR_STOP 1\n--test adding one more open dimension\nselect add_dimension('test_schema.test_table', 'id', chunk_time_interval => 1000);\n          add_dimension          \n---------------------------------\n (6,test_schema,test_table,id,t)\n\nselect * from _timescaledb_catalog.hypertable where table_name = 'test_table';\n id | schema_name | table_name | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------+------------+------------------------+-------------------------+----------------+--------------------------+--------------------------+-------------------+-------------------+--------------------------+--------\n  2 | test_schema | test_table | chunk_schema           | _hyper_2                |              4 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n\nselect * from _timescaledb_catalog.dimension;\n id | hypertable_id | column_name | column_type | aligned | num_slices | partitioning_func_schema | partitioning_func  | interval_length | compress_interval_length | integer_now_func_schema | integer_now_func \n----+---------------+-------------+-------------+---------+------------+--------------------------+--------------------+-----------------+--------------------------+-------------------------+------------------\n  1 |             1 | time        | bigint      | t       |            |                          |                    |   2592000000000 |                          |                         | \n  2 |             1 | device_id   | text        | f       |          2 | _timescaledb_functions   | get_partition_hash |                 |                          |                         | \n  3 |             2 | time        | bigint      | t       |            |                          |                    |   2592000000000 |                          |                         | \n  4 |             2 | device_id   | text        | f       |          2 | _timescaledb_functions   | get_partition_hash |                 |                          |                         | \n  5 |             2 | location    | text        | f       |          2 | _timescaledb_functions   | get_partition_hash |                 |                          |                         | \n  6 |             2 | id          | integer     | t       |            |                          |                    |            1000 |                          |                         | \n\n-- Test add_dimension: can use interval types for TIMESTAMPTZ columns\nCREATE TABLE dim_test_time(time TIMESTAMPTZ, time2 TIMESTAMPTZ, time3 BIGINT, temp float8, device int, location int);\nSELECT create_hypertable('dim_test_time', 'time');\n     create_hypertable      \n----------------------------\n (3,public,dim_test_time,t)\n\nSELECT add_dimension('dim_test_time', 'time2', chunk_time_interval => INTERVAL '1 day');\n          add_dimension           \n----------------------------------\n (8,public,dim_test_time,time2,t)\n\n-- Test add_dimension: only integral should work on BIGINT columns\n\\set ON_ERROR_STOP 0\nSELECT add_dimension('dim_test_time', 'time3', chunk_time_interval => INTERVAL '1 day');\nERROR:  invalid interval type for bigint dimension\n-- string is not a valid type\nSELECT add_dimension('dim_test_time', 'time3', chunk_time_interval => 'foo'::TEXT);\nERROR:  invalid interval type for bigint dimension\n\\set ON_ERROR_STOP 1\nSELECT add_dimension('dim_test_time', 'time3', chunk_time_interval => 500);\n          add_dimension           \n----------------------------------\n (9,public,dim_test_time,time3,t)\n\n-- Test add_dimension: integrals should work on TIMESTAMPTZ columns\nCREATE TABLE dim_test_time2(time TIMESTAMPTZ, time2 TIMESTAMPTZ, temp float8, device int, location int);\nSELECT create_hypertable('dim_test_time2', 'time');\n      create_hypertable      \n-----------------------------\n (4,public,dim_test_time2,t)\n\nSELECT add_dimension('dim_test_time2', 'time2', chunk_time_interval => 500);\nWARNING:  unexpected interval: smaller than one second\n           add_dimension            \n------------------------------------\n (11,public,dim_test_time2,time2,t)\n\n--adding a dimension twice should not fail with 'if_not_exists'\nSELECT add_dimension('dim_test_time2', 'time2', chunk_time_interval => 500, if_not_exists => true);\nNOTICE:  column \"time2\" is already a dimension, skipping\n           add_dimension            \n------------------------------------\n (11,public,dim_test_time2,time2,f)\n\n\\set ON_ERROR_STOP 0\n--adding on a non-hypertable\nCREATE TABLE not_hypertable(time TIMESTAMPTZ, temp float8, device int, location int);\nSELECT add_dimension('not_hypertable', 'time', chunk_time_interval => 500);\nERROR:  table \"not_hypertable\" is not a hypertable\n--adding a non-exist column\nSELECT add_dimension('test_schema.test_table', 'nope', 2);\nERROR:  column \"nope\" does not exist\n--adding the same dimension twice should fail\nselect add_dimension('test_schema.test_table', 'location', 2);\nERROR:  column \"location\" is already a dimension\n--adding dimension with both number_partitions and chunk_time_interval should fail\nselect add_dimension('test_schema.test_table', 'id2', number_partitions => 2, chunk_time_interval => 1000);\nERROR:  cannot specify both the number of partitions and an interval\n\\set ON_ERROR_STOP 1\n-- test adding a new dimension on a non-empty table\nCREATE TABLE dim_test(time TIMESTAMPTZ, device int);\nSELECT create_hypertable('dim_test', 'time', chunk_time_interval => INTERVAL '1 day');\n   create_hypertable   \n-----------------------\n (5,public,dim_test,t)\n\nCREATE VIEW dim_test_slices AS\nSELECT c.id AS chunk_id, c.hypertable_id, ds.dimension_id, cc.dimension_slice_id, c.schema_name AS chunk_schema, c.table_name AS chunk_table, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\nINNER JOIN _timescaledb_catalog.dimension td ON (h.id = td.hypertable_id)\nINNER JOIN _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = td.id)\nINNER JOIN _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id AND cc.chunk_id = c.id)\nWHERE h.table_name = 'dim_test'\nORDER BY c.id, ds.dimension_id;\nINSERT INTO dim_test VALUES ('2004-10-10 00:00:00+00', 1);\nINSERT INTO dim_test VALUES ('2004-10-20 00:00:00+00', 2);\nSELECT * FROM dim_test_slices;\n chunk_id | hypertable_id | dimension_id | dimension_slice_id |     chunk_schema      |   chunk_table    |   range_start    |    range_end     \n----------+---------------+--------------+--------------------+-----------------------+------------------+------------------+------------------\n        2 |             5 |           12 |                  3 | _timescaledb_internal | _hyper_5_2_chunk | 1097366400000000 | 1097452800000000\n        3 |             5 |           12 |                  4 | _timescaledb_internal | _hyper_5_3_chunk | 1098230400000000 | 1098316800000000\n\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_5_2_chunk');\n  Constraint  | Type | Columns | Index |                                                                      Expr                                                                      | Deferrable | Deferred | Validated \n--------------+------+---------+-------+------------------------------------------------------------------------------------------------------------------------------------------------+------------+----------+-----------\n constraint_3 | c    | {time}  | -     | ((\"time\" >= 'Sat Oct 09 17:00:00 2004 PDT'::timestamp with time zone) AND (\"time\" < 'Sun Oct 10 17:00:00 2004 PDT'::timestamp with time zone)) | f          | f        | t\n\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_5_3_chunk');\n  Constraint  | Type | Columns | Index |                                                                      Expr                                                                      | Deferrable | Deferred | Validated \n--------------+------+---------+-------+------------------------------------------------------------------------------------------------------------------------------------------------+------------+----------+-----------\n constraint_4 | c    | {time}  | -     | ((\"time\" >= 'Tue Oct 19 17:00:00 2004 PDT'::timestamp with time zone) AND (\"time\" < 'Wed Oct 20 17:00:00 2004 PDT'::timestamp with time zone)) | f          | f        | t\n\n-- add dimension to the existing chunks by adding -inf/inf dimension slices\nSELECT add_dimension('dim_test', 'device', 2);\n         add_dimension         \n-------------------------------\n (13,public,dim_test,device,t)\n\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_5_2_chunk');\n  Constraint  | Type | Columns | Index |                                                                      Expr                                                                      | Deferrable | Deferred | Validated \n--------------+------+---------+-------+------------------------------------------------------------------------------------------------------------------------------------------------+------------+----------+-----------\n constraint_3 | c    | {time}  | -     | ((\"time\" >= 'Sat Oct 09 17:00:00 2004 PDT'::timestamp with time zone) AND (\"time\" < 'Sun Oct 10 17:00:00 2004 PDT'::timestamp with time zone)) | f          | f        | t\n\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_5_3_chunk');\n  Constraint  | Type | Columns | Index |                                                                      Expr                                                                      | Deferrable | Deferred | Validated \n--------------+------+---------+-------+------------------------------------------------------------------------------------------------------------------------------------------------+------------+----------+-----------\n constraint_4 | c    | {time}  | -     | ((\"time\" >= 'Tue Oct 19 17:00:00 2004 PDT'::timestamp with time zone) AND (\"time\" < 'Wed Oct 20 17:00:00 2004 PDT'::timestamp with time zone)) | f          | f        | t\n\nSELECT * FROM dim_test_slices;\n chunk_id | hypertable_id | dimension_id | dimension_slice_id |     chunk_schema      |   chunk_table    |     range_start      |      range_end      \n----------+---------------+--------------+--------------------+-----------------------+------------------+----------------------+---------------------\n        2 |             5 |           12 |                  3 | _timescaledb_internal | _hyper_5_2_chunk |     1097366400000000 |    1097452800000000\n        2 |             5 |           13 |                  5 | _timescaledb_internal | _hyper_5_2_chunk | -9223372036854775808 | 9223372036854775807\n        3 |             5 |           12 |                  4 | _timescaledb_internal | _hyper_5_3_chunk |     1098230400000000 |    1098316800000000\n        3 |             5 |           13 |                  5 | _timescaledb_internal | _hyper_5_3_chunk | -9223372036854775808 | 9223372036854775807\n\n-- newer chunks have proper dimension slices range\nINSERT INTO dim_test VALUES ('2004-10-30 00:00:00+00', 3);\nSELECT * FROM dim_test_slices;\n chunk_id | hypertable_id | dimension_id | dimension_slice_id |     chunk_schema      |   chunk_table    |     range_start      |      range_end      \n----------+---------------+--------------+--------------------+-----------------------+------------------+----------------------+---------------------\n        2 |             5 |           12 |                  3 | _timescaledb_internal | _hyper_5_2_chunk |     1097366400000000 |    1097452800000000\n        2 |             5 |           13 |                  5 | _timescaledb_internal | _hyper_5_2_chunk | -9223372036854775808 | 9223372036854775807\n        3 |             5 |           12 |                  4 | _timescaledb_internal | _hyper_5_3_chunk |     1098230400000000 |    1098316800000000\n        3 |             5 |           13 |                  5 | _timescaledb_internal | _hyper_5_3_chunk | -9223372036854775808 | 9223372036854775807\n        4 |             5 |           12 |                  6 | _timescaledb_internal | _hyper_5_4_chunk |     1099094400000000 |    1099180800000000\n        4 |             5 |           13 |                  7 | _timescaledb_internal | _hyper_5_4_chunk |           1073741823 | 9223372036854775807\n\nSELECT * FROM dim_test ORDER BY time;\n             time             | device \n------------------------------+--------\n Sat Oct 09 17:00:00 2004 PDT |      1\n Tue Oct 19 17:00:00 2004 PDT |      2\n Fri Oct 29 17:00:00 2004 PDT |      3\n\nDROP VIEW dim_test_slices;\nDROP TABLE dim_test;\n-- test add_dimension() with existing data on table with space partitioning\nCREATE TABLE dim_test(time TIMESTAMPTZ, device int, data int);\nSELECT create_hypertable('dim_test', 'time', 'device', 2, chunk_time_interval => INTERVAL '1 day');\n   create_hypertable   \n-----------------------\n (6,public,dim_test,t)\n\nCREATE VIEW dim_test_slices AS\nSELECT c.id AS chunk_id, c.hypertable_id, ds.dimension_id, cc.dimension_slice_id, c.schema_name AS chunk_schema, c.table_name AS chunk_table, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\nINNER JOIN _timescaledb_catalog.dimension td ON (h.id = td.hypertable_id)\nINNER JOIN _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = td.id)\nINNER JOIN _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id AND cc.chunk_id = c.id)\nWHERE h.table_name = 'dim_test'\nORDER BY c.id, ds.dimension_id;\nINSERT INTO dim_test VALUES ('2004-10-10 00:00:00+00', 1, 3);\nINSERT INTO dim_test VALUES ('2004-10-20 00:00:00+00', 2, 2);\nSELECT * FROM dim_test_slices;\n chunk_id | hypertable_id | dimension_id | dimension_slice_id |     chunk_schema      |   chunk_table    |     range_start      |      range_end      \n----------+---------------+--------------+--------------------+-----------------------+------------------+----------------------+---------------------\n        5 |             6 |           14 |                  8 | _timescaledb_internal | _hyper_6_5_chunk |     1097366400000000 |    1097452800000000\n        5 |             6 |           15 |                  9 | _timescaledb_internal | _hyper_6_5_chunk | -9223372036854775808 |          1073741823\n        6 |             6 |           14 |                 10 | _timescaledb_internal | _hyper_6_6_chunk |     1098230400000000 |    1098316800000000\n        6 |             6 |           15 |                 11 | _timescaledb_internal | _hyper_6_6_chunk |           1073741823 | 9223372036854775807\n\n-- new dimension slice will cover full range on existing chunks\nSELECT add_dimension('dim_test', 'data', 1);\n        add_dimension        \n-----------------------------\n (16,public,dim_test,data,t)\n\nSELECT * FROM dim_test_slices;\n chunk_id | hypertable_id | dimension_id | dimension_slice_id |     chunk_schema      |   chunk_table    |     range_start      |      range_end      \n----------+---------------+--------------+--------------------+-----------------------+------------------+----------------------+---------------------\n        5 |             6 |           14 |                  8 | _timescaledb_internal | _hyper_6_5_chunk |     1097366400000000 |    1097452800000000\n        5 |             6 |           15 |                  9 | _timescaledb_internal | _hyper_6_5_chunk | -9223372036854775808 |          1073741823\n        5 |             6 |           16 |                 12 | _timescaledb_internal | _hyper_6_5_chunk | -9223372036854775808 | 9223372036854775807\n        6 |             6 |           14 |                 10 | _timescaledb_internal | _hyper_6_6_chunk |     1098230400000000 |    1098316800000000\n        6 |             6 |           15 |                 11 | _timescaledb_internal | _hyper_6_6_chunk |           1073741823 | 9223372036854775807\n        6 |             6 |           16 |                 12 | _timescaledb_internal | _hyper_6_6_chunk | -9223372036854775808 | 9223372036854775807\n\nINSERT INTO dim_test VALUES ('2004-10-30 00:00:00+00', 3, 1);\nSELECT * FROM dim_test_slices;\n chunk_id | hypertable_id | dimension_id | dimension_slice_id |     chunk_schema      |   chunk_table    |     range_start      |      range_end      \n----------+---------------+--------------+--------------------+-----------------------+------------------+----------------------+---------------------\n        5 |             6 |           14 |                  8 | _timescaledb_internal | _hyper_6_5_chunk |     1097366400000000 |    1097452800000000\n        5 |             6 |           15 |                  9 | _timescaledb_internal | _hyper_6_5_chunk | -9223372036854775808 |          1073741823\n        5 |             6 |           16 |                 12 | _timescaledb_internal | _hyper_6_5_chunk | -9223372036854775808 | 9223372036854775807\n        6 |             6 |           14 |                 10 | _timescaledb_internal | _hyper_6_6_chunk |     1098230400000000 |    1098316800000000\n        6 |             6 |           15 |                 11 | _timescaledb_internal | _hyper_6_6_chunk |           1073741823 | 9223372036854775807\n        6 |             6 |           16 |                 12 | _timescaledb_internal | _hyper_6_6_chunk | -9223372036854775808 | 9223372036854775807\n        7 |             6 |           14 |                 13 | _timescaledb_internal | _hyper_6_7_chunk |     1099094400000000 |    1099180800000000\n        7 |             6 |           15 |                 11 | _timescaledb_internal | _hyper_6_7_chunk |           1073741823 | 9223372036854775807\n        7 |             6 |           16 |                 12 | _timescaledb_internal | _hyper_6_7_chunk | -9223372036854775808 | 9223372036854775807\n\nSELECT * FROM dim_test ORDER BY time;\n             time             | device | data \n------------------------------+--------+------\n Sat Oct 09 17:00:00 2004 PDT |      1 |    3\n Tue Oct 19 17:00:00 2004 PDT |      2 |    2\n Fri Oct 29 17:00:00 2004 PDT |      3 |    1\n\nDROP VIEW dim_test_slices;\nDROP TABLE dim_test;\n-- should not fail on non-empty table with 'if_not_exists' in case the dimension exists\nselect add_dimension('test_schema.test_table', 'location', 2, if_not_exists => true);\nNOTICE:  column \"location\" is already a dimension, skipping\n             add_dimension             \n---------------------------------------\n (5,test_schema,test_table,location,f)\n\n--test partitioning in only time dimension\ncreate table test_schema.test_1dim(time timestamp, temp float);\nselect create_hypertable('test_schema.test_1dim', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n      create_hypertable      \n-----------------------------\n (7,test_schema,test_1dim,t)\n\nSELECT * FROM _timescaledb_functions.get_create_command('test_1dim');\n                                                       get_create_command                                                       \n--------------------------------------------------------------------------------------------------------------------------------\n SELECT create_hypertable('test_schema.test_1dim', 'time', chunk_time_interval => 604800000000, create_default_indexes=>FALSE);\n\nSELECT * FROM test.relation WHERE schema = 'test_schema';\n   schema    |          name          | type  |       owner       \n-------------+------------------------+-------+-------------------\n test_schema | test_1dim              | table | default_perm_user\n test_schema | test_table             | table | default_perm_user\n test_schema | test_table_no_not_null | table | default_perm_user\n\nselect create_hypertable('test_schema.test_1dim', 'time', if_not_exists => true);\nNOTICE:  table \"test_1dim\" is already a hypertable, skipping\n      create_hypertable      \n-----------------------------\n (7,test_schema,test_1dim,f)\n\n-- Should error when creating again without if_not_exists set to true\n\\set ON_ERROR_STOP 0\nselect create_hypertable('test_schema.test_1dim', 'time');\nERROR:  table \"test_1dim\" is already a hypertable\n\\set ON_ERROR_STOP 1\n-- if_not_exist should also work with data in the hypertable\ninsert into test_schema.test_1dim VALUES ('2004-10-19 10:23:54+02', 1.0);\nselect create_hypertable('test_schema.test_1dim', 'time', if_not_exists => true);\nNOTICE:  table \"test_1dim\" is already a hypertable, skipping\n      create_hypertable      \n-----------------------------\n (7,test_schema,test_1dim,f)\n\n-- Should error when creating again without if_not_exists set to true\n\\set ON_ERROR_STOP 0\nselect create_hypertable('test_schema.test_1dim', 'time');\nERROR:  table \"test_1dim\" is already a hypertable\n\\set ON_ERROR_STOP 1\n-- Test partitioning functions\nCREATE OR REPLACE FUNCTION invalid_partfunc(source integer)\n    RETURNS INTEGER LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nBEGIN\n    RETURN NULL;\nEND\n$BODY$;\nCREATE OR REPLACE FUNCTION time_partfunc(source text)\n    RETURNS TIMESTAMPTZ LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nBEGIN\n    RETURN timezone('UTC', to_timestamp(source));\nEND\n$BODY$;\nCREATE TABLE test_schema.test_invalid_func(time timestamptz, temp float8, device text);\n\\set ON_ERROR_STOP 0\n-- should fail due to invalid signature\nSELECT create_hypertable('test_schema.test_invalid_func', 'time', 'device', 2, partitioning_func => 'invalid_partfunc');\nERROR:  invalid partitioning function\nSELECT create_hypertable('test_schema.test_invalid_func', 'time');\n          create_hypertable          \n-------------------------------------\n (8,test_schema,test_invalid_func,t)\n\n-- should also fail due to invalid signature\nSELECT add_dimension('test_schema.test_invalid_func', 'device', 2, partitioning_func => 'invalid_partfunc');\nERROR:  invalid partitioning function\n\\set ON_ERROR_STOP 1\n-- Test open-dimension function\nCREATE TABLE test_schema.open_dim_part_func(time text, temp float8, device text, event_time text);\n\\set ON_ERROR_STOP 0\n-- should fail due to invalid signature\nSELECT create_hypertable('test_schema.open_dim_part_func', 'time', time_partitioning_func => 'invalid_partfunc');\nERROR:  invalid partitioning function\n\\set ON_ERROR_STOP 1\nSELECT create_hypertable('test_schema.open_dim_part_func', 'time', time_partitioning_func => 'time_partfunc');\n          create_hypertable           \n--------------------------------------\n (9,test_schema,open_dim_part_func,t)\n\n\\set ON_ERROR_STOP 0\n-- should fail due to invalid signature\nSELECT add_dimension('test_schema.open_dim_part_func', 'event_time', chunk_time_interval => interval '1 day', partitioning_func => 'invalid_partfunc');\nERROR:  invalid partitioning function\n\\set ON_ERROR_STOP 1\nSELECT add_dimension('test_schema.open_dim_part_func', 'event_time', chunk_time_interval => interval '1 day', partitioning_func => 'time_partfunc');\n                  add_dimension                   \n--------------------------------------------------\n (20,test_schema,open_dim_part_func,event_time,t)\n\n--test data migration\ncreate table test_schema.test_migrate(time timestamp, temp float);\ninsert into test_schema.test_migrate VALUES ('2004-10-19 10:23:54+02', 1.0), ('2004-12-19 10:23:54+02', 2.0);\nselect * from only test_schema.test_migrate;\n           time           | temp \n--------------------------+------\n Tue Oct 19 10:23:54 2004 |    1\n Sun Dec 19 10:23:54 2004 |    2\n\n\\set ON_ERROR_STOP 0\n--should fail without migrate_data => true\nselect create_hypertable('test_schema.test_migrate', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\nERROR:  table \"test_migrate\" is not empty\n\\set ON_ERROR_STOP 1\nselect create_hypertable('test_schema.test_migrate', 'time', migrate_data => true);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\nNOTICE:  migrating data to chunks\n        create_hypertable        \n---------------------------------\n (10,test_schema,test_migrate,t)\n\n--there should be two new chunks\nselect * from _timescaledb_catalog.hypertable where table_name = 'test_migrate';\n id | schema_name |  table_name  | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------+--------------+------------------------+-------------------------+----------------+--------------------------+--------------------------+-------------------+-------------------+--------------------------+--------\n 10 | test_schema | test_migrate | _timescaledb_internal  | _hyper_10               |              1 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n\nselect id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk from _timescaledb_catalog.chunk;\n id | hypertable_id |      schema_name      |     table_name     | compressed_chunk_id | status | osm_chunk \n----+---------------+-----------------------+--------------------+---------------------+--------+-----------\n  1 |             1 | _timescaledb_internal | _hyper_1_1_chunk   |                     |      0 | f\n  8 |             7 | _timescaledb_internal | _hyper_7_8_chunk   |                     |      0 | f\n  9 |            10 | _timescaledb_internal | _hyper_10_9_chunk  |                     |      0 | f\n 10 |            10 | _timescaledb_internal | _hyper_10_10_chunk |                     |      0 | f\n\nselect * from test_schema.test_migrate;\n           time           | temp \n--------------------------+------\n Tue Oct 19 10:23:54 2004 |    1\n Sun Dec 19 10:23:54 2004 |    2\n\n--main table should now be empty\nselect * from only test_schema.test_migrate;\n time | temp \n------+------\n\nselect * from only _timescaledb_internal._hyper_10_9_chunk;\n           time           | temp \n--------------------------+------\n Tue Oct 19 10:23:54 2004 |    1\n\nselect * from only _timescaledb_internal._hyper_10_10_chunk;\n           time           | temp \n--------------------------+------\n Sun Dec 19 10:23:54 2004 |    2\n\ncreate table test_schema.test_migrate_empty(time timestamp, temp float);\nselect create_hypertable('test_schema.test_migrate_empty', 'time', migrate_data => true);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n           create_hypertable           \n---------------------------------------\n (11,test_schema,test_migrate_empty,t)\n\nCREATE TYPE test_type AS (time timestamp, temp float);\nCREATE TABLE test_table_of_type OF test_type;\nSELECT create_hypertable('test_table_of_type', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n        create_hypertable         \n----------------------------------\n (12,public,test_table_of_type,t)\n\nINSERT INTO test_table_of_type VALUES ('2004-10-19 10:23:54+02', 1.0), ('2004-12-19 10:23:54+02', 2.0);\n\\set ON_ERROR_STOP 0\nDROP TYPE test_type;\nERROR:  cannot drop type test_type because other objects depend on it\n\\set ON_ERROR_STOP 1\nDROP TYPE test_type CASCADE;\nNOTICE:  drop cascades to 3 other objects\nCREATE TABLE test_table_of_type (time timestamp, temp float);\nSELECT create_hypertable('test_table_of_type', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n        create_hypertable         \n----------------------------------\n (13,public,test_table_of_type,t)\n\nINSERT INTO test_table_of_type VALUES ('2004-10-19 10:23:54+02', 1.0), ('2004-12-19 10:23:54+02', 2.0);\nCREATE TYPE test_type AS (time timestamp, temp float);\nALTER TABLE test_table_of_type OF test_type;\n\\set ON_ERROR_STOP 0\nDROP TYPE test_type;\nERROR:  cannot drop type test_type because other objects depend on it\n\\set ON_ERROR_STOP 1\nBEGIN;\nDROP TYPE test_type CASCADE;\nNOTICE:  drop cascades to 3 other objects\nROLLBACK;\nALTER TABLE test_table_of_type NOT OF;\nDROP TYPE test_type;\n-- Reset GRANTS\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nREVOKE :ROLE_DEFAULT_PERM_USER FROM :ROLE_DEFAULT_PERM_USER_2;\n-- Test custom partitioning functions\nCREATE OR REPLACE FUNCTION partfunc_not_immutable(source anyelement)\n    RETURNS INTEGER LANGUAGE PLPGSQL AS\n$BODY$\nBEGIN\n    RETURN _timescaledb_functions.get_partition_hash(source);\nEND\n$BODY$;\nCREATE OR REPLACE FUNCTION partfunc_bad_return_type(source anyelement)\n    RETURNS BIGINT LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nBEGIN\n    RETURN _timescaledb_functions.get_partition_hash(source);\nEND\n$BODY$;\nCREATE OR REPLACE FUNCTION partfunc_bad_arg_type(source text)\n    RETURNS INTEGER LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nBEGIN\n    RETURN _timescaledb_functions.get_partition_hash(source);\nEND\n$BODY$;\nCREATE OR REPLACE FUNCTION partfunc_bad_multi_arg(source anyelement, extra_arg integer)\n    RETURNS INTEGER LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nBEGIN\n    RETURN _timescaledb_functions.get_partition_hash(source);\nEND\n$BODY$;\nCREATE OR REPLACE FUNCTION partfunc_valid(source anyelement)\n    RETURNS INTEGER LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nBEGIN\n    RETURN _timescaledb_functions.get_partition_hash(source);\nEND\n$BODY$;\ncreate table test_schema.test_partfunc(time timestamptz, temp float, device int);\n-- Test that create_hypertable fails due to invalid partitioning function\n\\set ON_ERROR_STOP 0\nselect create_hypertable('test_schema.test_partfunc', 'time', 'device', 2, partitioning_func => 'partfunc_not_immutable');\nERROR:  invalid partitioning function\nselect create_hypertable('test_schema.test_partfunc', 'time', 'device', 2, partitioning_func => 'partfunc_bad_return_type');\nERROR:  invalid partitioning function\nselect create_hypertable('test_schema.test_partfunc', 'time', 'device', 2, partitioning_func => 'partfunc_bad_arg_type');\nERROR:  invalid partitioning function\nselect create_hypertable('test_schema.test_partfunc', 'time', 'device', 2, partitioning_func => 'partfunc_bad_multi_arg');\nERROR:  invalid partitioning function\n\\set ON_ERROR_STOP 1\n-- Test that add_dimension fails due to invalid partitioning function\nselect create_hypertable('test_schema.test_partfunc', 'time');\n        create_hypertable         \n----------------------------------\n (14,test_schema,test_partfunc,t)\n\n\\set ON_ERROR_STOP 0\nselect add_dimension('test_schema.test_partfunc', 'device', 2, partitioning_func => 'partfunc_not_immutable');\nERROR:  invalid partitioning function\nselect add_dimension('test_schema.test_partfunc', 'device', 2, partitioning_func => 'partfunc_bad_return_type');\nERROR:  invalid partitioning function\nselect add_dimension('test_schema.test_partfunc', 'device', 2, partitioning_func => 'partfunc_bad_arg_type');\nERROR:  invalid partitioning function\nselect add_dimension('test_schema.test_partfunc', 'device', 2, partitioning_func => 'partfunc_bad_multi_arg');\nERROR:  invalid partitioning function\n\\set ON_ERROR_STOP 1\n-- A valid function should work:\nselect add_dimension('test_schema.test_partfunc', 'device', 2, partitioning_func => 'partfunc_valid');\n              add_dimension              \n-----------------------------------------\n (26,test_schema,test_partfunc,device,t)\n\n-- check get_create_command produces valid command\nCREATE TABLE test_schema.test_sql_cmd(time TIMESTAMPTZ, temp FLOAT8, device_id TEXT, device_type TEXT, location TEXT, id INT, id2 INT);\nSELECT create_hypertable('test_schema.test_sql_cmd','time');\n        create_hypertable        \n---------------------------------\n (15,test_schema,test_sql_cmd,t)\n\nSELECT * FROM _timescaledb_functions.get_create_command('test_sql_cmd');\n                                                        get_create_command                                                         \n-----------------------------------------------------------------------------------------------------------------------------------\n SELECT create_hypertable('test_schema.test_sql_cmd', 'time', chunk_time_interval => 604800000000, create_default_indexes=>FALSE);\n\nSELECT _timescaledb_functions.get_create_command('test_sql_cmd') AS create_cmd; \\gset\n                                                            create_cmd                                                             \n-----------------------------------------------------------------------------------------------------------------------------------\n SELECT create_hypertable('test_schema.test_sql_cmd', 'time', chunk_time_interval => 604800000000, create_default_indexes=>FALSE);\n\nDROP TABLE test_schema.test_sql_cmd CASCADE;\nCREATE TABLE test_schema.test_sql_cmd(time TIMESTAMPTZ, temp FLOAT8, device_id TEXT, device_type TEXT, location TEXT, id INT, id2 INT);\nSELECT test.execute_sql(:'create_cmd');\n                                                            execute_sql                                                            \n-----------------------------------------------------------------------------------------------------------------------------------\n SELECT create_hypertable('test_schema.test_sql_cmd', 'time', chunk_time_interval => 604800000000, create_default_indexes=>FALSE);\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nCREATE TABLE test_table_int(time bigint, junk int);\nSELECT hypertable_id AS \"TEST_TABLE_INT_HYPERTABLE_ID\" FROM create_hypertable('test_table_int', 'time', chunk_time_interval => 1) \\gset\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA IF NOT EXISTS my_schema;\ncreate or replace function my_schema.dummy_now2() returns BIGINT LANGUAGE SQL IMMUTABLE as  'SELECT 1::BIGINT';\ngrant execute on ALL FUNCTIONS IN SCHEMA my_schema to public;\ncreate or replace function dummy_now3() returns BIGINT LANGUAGE SQL IMMUTABLE as  'SELECT 1::BIGINT';\ngrant execute on ALL FUNCTIONS IN SCHEMA my_schema to public;\nREVOKE execute ON function dummy_now3() FROM PUBLIC;\nCREATE SCHEMA IF NOT EXISTS my_user_schema;\nGRANT ALL ON SCHEMA my_user_schema to PUBLIC;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\ncreate or replace function dummy_now() returns BIGINT LANGUAGE SQL IMMUTABLE as  'SELECT 1::BIGINT';\ncreate or replace function my_user_schema.dummy_now4() returns BIGINT LANGUAGE SQL IMMUTABLE as  'SELECT 1::BIGINT';\nselect set_integer_now_func('test_table_int', 'dummy_now');\n set_integer_now_func \n----------------------\n \n\nselect * from _timescaledb_catalog.dimension WHERE hypertable_id = :TEST_TABLE_INT_HYPERTABLE_ID;\n id | hypertable_id | column_name | column_type | aligned | num_slices | partitioning_func_schema | partitioning_func | interval_length | compress_interval_length | integer_now_func_schema | integer_now_func \n----+---------------+-------------+-------------+---------+------------+--------------------------+-------------------+-----------------+--------------------------+-------------------------+------------------\n 29 |            17 | time        | bigint      | t       |            |                          |                   |               1 |                          | public                  | dummy_now\n\n-- show chunks works with \"created_before\" and errors out with time used in \"older_than\"\nSELECT SHOW_CHUNKS('test_table_int', older_than => 10);\n show_chunks \n-------------\n\nSELECT SHOW_CHUNKS('test_table_int', created_before => now());\n show_chunks \n-------------\n\n\\set ON_ERROR_STOP 0\nSELECT SHOW_CHUNKS('test_table_int', older_than => now());\nERROR:  invalid time argument type \"timestamp with time zone\"\nselect set_integer_now_func('test_table_int', 'dummy_now');\nERROR:  custom time function already set for hypertable \"test_table_int\"\nselect set_integer_now_func('test_table_int', 'my_schema.dummy_now2', replace_if_exists => TRUE);\nERROR:  permission denied for schema my_schema at character 47\nselect set_integer_now_func('test_table_int', 'dummy_now3', replace_if_exists => TRUE);\nERROR:  permission denied for function dummy_now3\n-- test invalid oid as the integer_now_func\nselect set_integer_now_func('test_table_int', 1, replace_if_exists => TRUE);\nERROR:  cache lookup failed for function 1\n\\set ON_ERROR_STOP\nselect set_integer_now_func('test_table_int', 'my_user_schema.dummy_now4', replace_if_exists => TRUE);\n set_integer_now_func \n----------------------\n \n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nALTER SCHEMA my_user_schema RENAME TO my_new_schema;\nselect * from _timescaledb_catalog.dimension WHERE hypertable_id = :TEST_TABLE_INT_HYPERTABLE_ID;\n id | hypertable_id | column_name | column_type | aligned | num_slices | partitioning_func_schema | partitioning_func | interval_length | compress_interval_length | integer_now_func_schema | integer_now_func \n----+---------------+-------------+-------------+---------+------------+--------------------------+-------------------+-----------------+--------------------------+-------------------------+------------------\n 29 |            17 | time        | bigint      | t       |            |                          |                   |               1 |                          | my_new_schema           | dummy_now4\n\n-- github issue #4650\nCREATE TABLE sample_table (\n       cpu double precision null,\n       time TIMESTAMP WITH TIME ZONE NOT NULL,\n       sensor_id INTEGER NOT NULL,\n       name varchar(100) default 'this is a default string value',\n       UNIQUE(sensor_id, time)\n);\nALTER TABLE sample_table DROP COLUMN name;\n-- below creation should not report any warnings.\nSELECT * FROM create_hypertable('sample_table', 'time');\n hypertable_id | schema_name |  table_name  | created \n---------------+-------------+--------------+---------\n            18 | public      | sample_table | t\n\n-- cleanup\nDROP TABLE sample_table CASCADE;\n-- github issue 4684\n-- test PARTITION BY HASH\nCREATE TABLE regular(\n   id INT NOT NULL,\n   dev INT NOT NULL,\n   value INT,\n   CONSTRAINT cstr_regular_pky PRIMARY KEY (id)\n) PARTITION BY HASH (id);\nDO $$\nBEGIN\n   FOR i IN 1..2\n   LOOP\n      EXECUTE format('\n         CREATE TABLE %I\n         PARTITION OF regular\n         FOR VALUES WITH (MODULUS 2, REMAINDER %s)',\n         'regular_' || i, i - 1\n      );\n   END LOOP;\nEND;\n$$;\nINSERT INTO regular SELECT generate_series(1,1000), 44,55;\nCREATE TABLE timescale (\n   ts TIMESTAMP WITH TIME ZONE NOT NULL,\n   id INT NOT NULL,\n   dev INT NOT NULL,\n   FOREIGN KEY (id)  REFERENCES regular(id) ON DELETE CASCADE\n);\nSELECT create_hypertable(\n   relation => 'timescale',\n   time_column_name => 'ts'\n);\n    create_hypertable    \n-------------------------\n (19,public,timescale,t)\n\n-- creates chunk1\nINSERT INTO timescale SELECT now(), generate_series(1,200), 43;\n-- creates chunk2\nINSERT INTO timescale SELECT now() + interval '20' day, generate_series(1,200), 43;\n-- creates chunk3\nINSERT INTO timescale SELECT now() + interval '40' day, generate_series(1,200), 43;\n-- show chunks\nSELECT SHOW_CHUNKS('timescale');\n               show_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_19_15_chunk\n _timescaledb_internal._hyper_19_16_chunk\n _timescaledb_internal._hyper_19_17_chunk\n\n\\set ON_ERROR_STOP 0\n-- record goes into chunk1 violating FK constraint as value 1001 is not present in regular table\nINSERT INTO timescale SELECT now(), 1001, 43;\nERROR:  insert or update on table \"_hyper_19_15_chunk\" violates foreign key constraint \"15_1_timescale_id_fkey\"\n-- record goes into chunk2 violating FK constraint as value 1002 is not present in regular table\nINSERT INTO timescale SELECT now() + interval '20' day, 1002, 43;\nERROR:  insert or update on table \"_hyper_19_16_chunk\" violates foreign key constraint \"16_2_timescale_id_fkey\"\n-- record goes into chunk3 violating FK constraint as value 1003 is not present in regular table\nINSERT INTO timescale SELECT now() + interval '40' day, 1003, 43;\nERROR:  insert or update on table \"_hyper_19_17_chunk\" violates foreign key constraint \"17_3_timescale_id_fkey\"\n\\set ON_ERROR_STOP 1\n-- cleanup\nDROP TABLE regular cascade;\nNOTICE:  drop cascades to 4 other objects\nDROP TABLE timescale cascade;\n-- test PARTITION BY RANGE\nCREATE TABLE regular(\n   id INT NOT NULL,\n   dev INT NOT NULL,\n   value INT,\n   CONSTRAINT cstr_regular_pky PRIMARY KEY (id)\n) PARTITION BY RANGE (id);\nCREATE TABLE regular_1_500 PARTITION OF regular\n    FOR VALUES FROM (1) TO (500);\nCREATE TABLE regular_500_1000 PARTITION OF regular\n    FOR VALUES FROM (500) TO (801);\nINSERT INTO regular SELECT generate_series(1,800), 44,55;\nCREATE TABLE timescale (\n   ts TIMESTAMP WITH TIME ZONE NOT NULL,\n   id INT NOT NULL,\n   dev INT NOT NULL,\n   FOREIGN KEY (id)  REFERENCES regular(id) ON DELETE CASCADE\n);\nSELECT create_hypertable(\n   relation => 'timescale',\n   time_column_name => 'ts'\n);\n    create_hypertable    \n-------------------------\n (20,public,timescale,t)\n\n-- creates chunk1\nINSERT INTO timescale SELECT now(), generate_series(1,200), 43;\n-- creates chunk2\nINSERT INTO timescale SELECT now() + interval '20' day, generate_series(200,400), 43;\n-- creates chunk3\nINSERT INTO timescale SELECT now() + interval '40' day, generate_series(400,600), 43;\n-- show chunks\nSELECT SHOW_CHUNKS('timescale');\n               show_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_20_18_chunk\n _timescaledb_internal._hyper_20_19_chunk\n _timescaledb_internal._hyper_20_20_chunk\n\n\\set ON_ERROR_STOP 0\n-- FK constraint violation as value 801 is not present in regular table\nINSERT INTO timescale SELECT now(), 801, 43;\nERROR:  insert or update on table \"_hyper_20_18_chunk\" violates foreign key constraint \"18_4_timescale_id_fkey\"\n-- FK constraint violation as value 902 is not present in regular table\nINSERT INTO timescale SELECT now() + interval '20' day, 902, 43;\nERROR:  insert or update on table \"_hyper_20_19_chunk\" violates foreign key constraint \"19_5_timescale_id_fkey\"\n-- FK constraint violation as value 1003 is not present in regular table\nINSERT INTO timescale SELECT now() + interval '40' day, 1003, 43;\nERROR:  insert or update on table \"_hyper_20_20_chunk\" violates foreign key constraint \"20_6_timescale_id_fkey\"\n\\set ON_ERROR_STOP 1\n-- cleanup\nDROP TABLE regular cascade;\nNOTICE:  drop cascades to 4 other objects\nDROP TABLE timescale cascade;\n-- test PARTITION BY LIST\nCREATE TABLE regular(\n   id INT NOT NULL,\n   dev INT NOT NULL,\n   value INT,\n   CONSTRAINT cstr_regular_pky PRIMARY KEY (id)\n) PARTITION BY LIST (id);\nCREATE TABLE regular_1_2_3_4 PARTITION OF regular FOR VALUES IN (1,2,3,4);\nCREATE TABLE regular_5_6_7_8 PARTITION OF regular FOR VALUES IN (5,6,7,8);\nINSERT INTO regular SELECT generate_series(1,8), 44,55;\nCREATE TABLE timescale (\n   ts TIMESTAMP WITH TIME ZONE NOT NULL,\n   id INT NOT NULL,\n   dev INT NOT NULL,\n   FOREIGN KEY (id)  REFERENCES regular(id) ON DELETE CASCADE\n);\nSELECT create_hypertable(\n   relation => 'timescale',\n   time_column_name => 'ts'\n);\n    create_hypertable    \n-------------------------\n (21,public,timescale,t)\n\ninsert into timescale values (now(), 1,2);\ninsert into timescale values (now(), 2,2);\ninsert into timescale values (now(), 3,2);\ninsert into timescale values (now(), 4,2);\ninsert into timescale values (now(), 5,2);\ninsert into timescale values (now(), 6,2);\ninsert into timescale values (now(), 7,2);\ninsert into timescale values (now(), 8,2);\n\\set ON_ERROR_STOP 0\n-- FK constraint violation as value 9 is not present in regular table\ninsert into timescale values (now(), 9,2);\nERROR:  insert or update on table \"_hyper_21_21_chunk\" violates foreign key constraint \"21_7_timescale_id_fkey\"\n-- FK constraint violation as value 10 is not present in regular table\ninsert into timescale values (now(), 10,2);\nERROR:  insert or update on table \"_hyper_21_21_chunk\" violates foreign key constraint \"21_7_timescale_id_fkey\"\n-- FK constraint violation as value 111 is not present in regular table\ninsert into timescale values (now(), 111,2);\nERROR:  insert or update on table \"_hyper_21_21_chunk\" violates foreign key constraint \"21_7_timescale_id_fkey\"\n\\set ON_ERROR_STOP 1\n-- cleanup\nDROP TABLE regular cascade;\nNOTICE:  drop cascades to 2 other objects\nDROP TABLE timescale cascade;\n-- github issue 4872\n-- If subplan of ChunkAppend is TidRangeScan, then SELECT on\n-- hypertable fails with error \"invalid child of chunk append: Node (26)\"\ncreate table tidrangescan_test (\n  time timestamp with time zone,\n  some_column bigint\n);\nselect create_hypertable('tidrangescan_test', 'time');\n        create_hypertable        \n---------------------------------\n (22,public,tidrangescan_test,t)\n\ninsert into tidrangescan_test (time, some_column) values ('2023-02-12 00:00:00+02:40', 1);\ninsert into tidrangescan_test (time, some_column) values ('2023-02-12 00:00:10+02:40', 2);\ninsert into tidrangescan_test (time, some_column) values ('2023-02-12 00:00:20+02:40', 3);\n-- Below query will generate plan as\n-- Custom Scan (ChunkAppend)\n--   ->  Tid Range Scan\n-- However when traversing ChunkAppend node, Tid Range Scan node is not\n-- recognised as a valid child node of ChunkAppend which causes error\n-- \"invalid child of chunk append: Node (26)\" when below query is executed\nselect * from tidrangescan_test where time > '2023-02-12 00:00:00+02:40'::timestamp with time zone - interval '5 years' and ctid < '(1,1)'::tid ORDER BY time;\n             time             | some_column \n------------------------------+-------------\n Sat Feb 11 13:20:00 2023 PST |           1\n Sat Feb 11 13:20:10 2023 PST |           2\n Sat Feb 11 13:20:20 2023 PST |           3\n\ndrop table tidrangescan_test;\n\\set VERBOSITY default\nset client_min_messages = WARNING;\n-- test creating a hypertable from table referenced by a foreign key fails with\n-- error \"cannot have FOREIGN KEY constraints to hypertable\".\ncreate table test_schema.fk_parent(time timestamptz, id int, unique(time, id));\ncreate table test_schema.fk_child(\n  time timestamptz,\n  id int,\n  foreign key (time, id) references test_schema.fk_parent(time, id)\n);\nselect create_hypertable ('test_schema.fk_child', 'time');\n      create_hypertable      \n-----------------------------\n (23,test_schema,fk_child,t)\n\n\\set ON_ERROR_STOP 0\nselect create_hypertable ('test_schema.fk_parent', 'time');\nERROR:  cannot have FOREIGN KEY constraints to hypertable \"fk_parent\"\nHINT:  Remove all FOREIGN KEY constraints to table \"fk_parent\" before making it a hypertable.\n\\set ON_ERROR_STOP 1\n-- create default indexes on chunks when migrating data\nCREATE TABLE test(time TIMESTAMPTZ, val BIGINT);\nCREATE INDEX test_val_idx ON test(val);\nINSERT INTO test VALUES('2024-01-01 00:00:00-03', 500);\nSELECT FROM create_hypertable('test', 'time', migrate_data=>TRUE);\n--\n\n-- should return ALL indexes for hypertable and chunk\nSELECT * FROM test.show_indexes('test') ORDER BY 1;\n     Index     | Columns | Expr | Unique | Primary | Exclusion | Tablespace \n---------------+---------+------+--------+---------+-----------+------------\n test_val_idx  | {val}   |      | f      | f       | f         | \n test_time_idx | {time}  |      | f      | f       | f         | \n\nSELECT * FROM show_chunks('test') ch, LATERAL test.show_indexes(ch) ORDER BY 1, 2;\n                    ch                    |                         Index                          | Columns | Expr | Unique | Primary | Exclusion | Tablespace \n------------------------------------------+--------------------------------------------------------+---------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_24_23_chunk | _timescaledb_internal._hyper_24_23_chunk_test_val_idx  | {val}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_24_23_chunk | _timescaledb_internal._hyper_24_23_chunk_test_time_idx | {time}  |      | f      | f       | f         | \n\nDROP TABLE test;\n-- don't create default indexes on chunks when migrating data\nCREATE TABLE test(time TIMESTAMPTZ, val BIGINT);\nCREATE INDEX test_val_idx ON test(val);\nINSERT INTO test VALUES('2024-01-01 00:00:00-03', 500);\nSELECT FROM create_hypertable('test', 'time', create_default_indexes => FALSE, migrate_data=>TRUE);\n--\n\n-- should NOT return default indexes for hypertable and chunk\n-- only user indexes should be returned\nSELECT * FROM test.show_indexes('test') ORDER BY 1;\n    Index     | Columns | Expr | Unique | Primary | Exclusion | Tablespace \n--------------+---------+------+--------+---------+-----------+------------\n test_val_idx | {val}   |      | f      | f       | f         | \n\nSELECT * FROM show_chunks('test') ch, LATERAL test.show_indexes(ch) ORDER BY 1, 2;\n                    ch                    |                         Index                         | Columns | Expr | Unique | Primary | Exclusion | Tablespace \n------------------------------------------+-------------------------------------------------------+---------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_25_24_chunk | _timescaledb_internal._hyper_25_24_chunk_test_val_idx | {val}   |      | f      | f       | f         | \n\nDROP TABLE test;\n-- test creating a hypertable with a primary key where the partitioning column is not part of the primary key\nCREATE TABLE test_schema.partition_not_pk (id INT NOT NULL, device_id INT NOT NULL, time TIMESTAMPTZ NOT NULL, a TEXT NOT NULL, PRIMARY KEY (id));\n\\set ON_ERROR_STOP 0\nselect create_hypertable ('test_schema.partition_not_pk', 'time');\nERROR:  cannot create a unique index without the column \"time\" (used in partitioning)\nHINT:  If you're creating a hypertable on a table with a primary key, ensure the partitioning column is part of the primary or composite key.\n\\set ON_ERROR_STOP 1\nDROP TABLE test_schema.partition_not_pk;\n-- test creating a hypertable with a composite key where the partitioning column is not part of the composite key\nCREATE TABLE test_schema.partition_not_pk (id INT NOT NULL, device_id INT NOT NULL, time TIMESTAMPTZ NOT NULL, a TEXT NOT NULL, PRIMARY KEY (id, device_id));\n\\set ON_ERROR_STOP 0\nselect create_hypertable ('test_schema.partition_not_pk', 'time');\nERROR:  cannot create a unique index without the column \"time\" (used in partitioning)\nHINT:  If you're creating a hypertable on a table with a primary key, ensure the partitioning column is part of the primary or composite key.\n\\set ON_ERROR_STOP 1\nDROP TABLE test_schema.partition_not_pk;\n-- test hypertable is not created for a table that is a part of a publication explicitly\nSET client_min_messages = ERROR;\nCREATE TABLE test (timestamp TIMESTAMPTZ NOT NULL);\nCREATE PUBLICATION publication_test;\nALTER PUBLICATION publication_test ADD TABLE test;\n\\set ON_ERROR_STOP 0\nSELECT create_hypertable('test', 'timestamp');\nERROR:  cannot create hypertable for table \"test\" because it is part of a publication\n\\set ON_ERROR_STOP 1\nINSERT INTO test (timestamp) values (now());\nALTER PUBLICATION publication_test DROP TABLE test;\nDROP PUBLICATION publication_test;\nDROP TABLE test;\nCREATE TABLE test (timestamp TIMESTAMPTZ NOT NULL);\nCREATE PUBLICATION publication_test1;\nCREATE PUBLICATION publication_test2;\nALTER PUBLICATION publication_test1 ADD TABLE test;\nALTER PUBLICATION publication_test2 ADD TABLE test;\n\\set ON_ERROR_STOP 0\nSELECT create_hypertable('test', 'timestamp');\nERROR:  cannot create hypertable for table \"test\" because it is part of a publication\n\\set ON_ERROR_STOP 1\nINSERT INTO test (timestamp) values (now());\nALTER PUBLICATION publication_test1 DROP TABLE test;\nALTER PUBLICATION publication_test2 DROP TABLE test;\nDROP PUBLICATION publication_test1;\nDROP PUBLICATION publication_test2;\nDROP TABLE test;\n-- test hypertable is not created for a table that is a part of a publication implicitly\nCREATE PUBLICATION publication_test FOR ALL tables;\nCREATE TABLE test (timestamp TIMESTAMPTZ NOT NULL);\n\\set ON_ERROR_STOP 0\nSELECT create_hypertable('test', 'timestamp');\nERROR:  cannot create hypertable for table \"test\" because it is part of a publication\n\\set ON_ERROR_STOP 1\nDROP PUBLICATION publication_test;\nDROP TABLE test;\nCREATE TABLE test (timestamp TIMESTAMPTZ NOT NULL);\nCREATE PUBLICATION publication_test FOR ALL tables;\n\\set ON_ERROR_STOP 0\nSELECT create_hypertable('test', 'timestamp');\nERROR:  cannot create hypertable for table \"test\" because it is part of a publication\n\\set ON_ERROR_STOP 1\nDROP PUBLICATION publication_test;\nDROP TABLE test;\nRESET client_min_messages;\n-- Test default_chunk_time_interval GUC\n-- Tests that the GUC correctly sets the default chunk interval for hypertables\n-- with different time column types (timestamp, timestamptz, date) and integer types.\n-- Show initial state (should be NULL meaning legacy defaults)\nSHOW timescaledb.default_chunk_time_interval;\n timescaledb.default_chunk_time_interval \n-----------------------------------------\n \n\n-- No default_chunk_time_interval set (NULL) - uses legacy defaults\nCREATE TABLE test_no_guc_timestamptz(time TIMESTAMPTZ NOT NULL, val INT);\nSELECT create_hypertable('test_no_guc_timestamptz', 'time');\n           create_hypertable           \n---------------------------------------\n (28,public,test_no_guc_timestamptz,t)\n\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_no_guc_timestamptz';\n time_interval \n---------------\n @ 7 days\n\nDROP TABLE test_no_guc_timestamptz;\nCREATE TABLE test_no_guc_timestamp(time TIMESTAMP NOT NULL, val INT);\nSELECT create_hypertable('test_no_guc_timestamp', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\nHINT:  Use datatype TIMESTAMPTZ instead.\n          create_hypertable          \n-------------------------------------\n (29,public,test_no_guc_timestamp,t)\n\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_no_guc_timestamp';\n time_interval \n---------------\n @ 7 days\n\nDROP TABLE test_no_guc_timestamp;\nCREATE TABLE test_no_guc_date(time DATE NOT NULL, val INT);\nSELECT create_hypertable('test_no_guc_date', 'time');\n       create_hypertable        \n--------------------------------\n (30,public,test_no_guc_date,t)\n\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_no_guc_date';\n time_interval \n---------------\n @ 7 days\n\nDROP TABLE test_no_guc_date;\nCREATE TABLE test_no_guc_uuid(time UUID NOT NULL, val INT);\nSELECT create_hypertable('test_no_guc_uuid', 'time');\n       create_hypertable        \n--------------------------------\n (31,public,test_no_guc_uuid,t)\n\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_no_guc_uuid';\n time_interval \n---------------\n @ 7 days\n\nDROP TABLE test_no_guc_uuid;\n-- Set default_chunk_time_interval to '1 week' and create hypertables\nSET timescaledb.default_chunk_time_interval = '1 week';\nSHOW timescaledb.default_chunk_time_interval;\n timescaledb.default_chunk_time_interval \n-----------------------------------------\n 1 week\n\nCREATE TABLE test_guc_timestamptz(time TIMESTAMPTZ NOT NULL, val INT);\nSELECT create_hypertable('test_guc_timestamptz', 'time');\n         create_hypertable          \n------------------------------------\n (32,public,test_guc_timestamptz,t)\n\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_guc_timestamptz';\n time_interval \n---------------\n @ 7 days\n\nDROP TABLE test_guc_timestamptz;\nCREATE TABLE test_guc_timestamp(time TIMESTAMP NOT NULL, val INT);\nSELECT create_hypertable('test_guc_timestamp', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\nHINT:  Use datatype TIMESTAMPTZ instead.\n        create_hypertable         \n----------------------------------\n (33,public,test_guc_timestamp,t)\n\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_guc_timestamp';\n time_interval \n---------------\n @ 7 days\n\nDROP TABLE test_guc_timestamp;\nCREATE TABLE test_guc_date(time DATE NOT NULL, val INT);\nSELECT create_hypertable('test_guc_date', 'time');\n      create_hypertable      \n-----------------------------\n (34,public,test_guc_date,t)\n\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_guc_date';\n time_interval \n---------------\n @ 7 days\n\nDROP TABLE test_guc_date;\nCREATE TABLE test_guc_uuid(time UUID NOT NULL, val INT);\nSELECT create_hypertable('test_guc_uuid', 'time');\n      create_hypertable      \n-----------------------------\n (35,public,test_guc_uuid,t)\n\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_guc_uuid';\n time_interval \n---------------\n @ 7 days\n\nDROP TABLE test_guc_uuid;\n-- Set default_chunk_time_interval to '1 day'\nSET timescaledb.default_chunk_time_interval = '1 day';\nSHOW timescaledb.default_chunk_time_interval;\n timescaledb.default_chunk_time_interval \n-----------------------------------------\n 1 day\n\nCREATE TABLE test_guc_1day_timestamptz(time TIMESTAMPTZ NOT NULL, val INT);\nSELECT create_hypertable('test_guc_1day_timestamptz', 'time');\n            create_hypertable            \n-----------------------------------------\n (36,public,test_guc_1day_timestamptz,t)\n\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_guc_1day_timestamptz';\n time_interval \n---------------\n @ 1 day\n\nDROP TABLE test_guc_1day_timestamptz;\n-- Set default_chunk_time_interval to '1 month'\nSET timescaledb.default_chunk_time_interval = '1 month';\nSHOW timescaledb.default_chunk_time_interval;\n timescaledb.default_chunk_time_interval \n-----------------------------------------\n 1 month\n\nCREATE TABLE test_guc_1month_timestamptz(time TIMESTAMPTZ NOT NULL, val INT);\nSELECT create_hypertable('test_guc_1month_timestamptz', 'time');\n             create_hypertable             \n-------------------------------------------\n (37,public,test_guc_1month_timestamptz,t)\n\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_guc_1month_timestamptz';\n time_interval \n---------------\n @ 30 days\n\nDROP TABLE test_guc_1month_timestamptz;\n-- Integer partition types have their own defaults and do not use the GUC\nSET timescaledb.default_chunk_time_interval = '1 week';\nCREATE TABLE test_guc_bigint(time BIGINT NOT NULL, val INT);\nSELECT create_hypertable('test_guc_bigint', 'time');\n       create_hypertable       \n-------------------------------\n (38,public,test_guc_bigint,t)\n\nSELECT integer_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_guc_bigint';\n integer_interval \n------------------\n          1000000\n\nDROP TABLE test_guc_bigint;\nCREATE TABLE test_guc_int(time INT NOT NULL, val INT);\nSELECT create_hypertable('test_guc_int', 'time');\n     create_hypertable      \n----------------------------\n (39,public,test_guc_int,t)\n\nSELECT integer_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_guc_int';\n integer_interval \n------------------\n           100000\n\nDROP TABLE test_guc_int;\nCREATE TABLE test_guc_smallint(time SMALLINT NOT NULL, val INT);\nSELECT create_hypertable('test_guc_smallint', 'time');\n        create_hypertable        \n---------------------------------\n (40,public,test_guc_smallint,t)\n\nSELECT integer_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_guc_smallint';\n integer_interval \n------------------\n            10000\n\nDROP TABLE test_guc_smallint;\n-- Explicit chunk_time_interval should override the GUC\nSET timescaledb.default_chunk_time_interval = '1 week';\nCREATE TABLE test_override_timestamptz(time TIMESTAMPTZ NOT NULL, val INT);\nSELECT create_hypertable('test_override_timestamptz', 'time', chunk_time_interval => INTERVAL '2 days');\n            create_hypertable            \n-----------------------------------------\n (41,public,test_override_timestamptz,t)\n\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_override_timestamptz';\n time_interval \n---------------\n @ 2 days\n\nDROP TABLE test_override_timestamptz;\n-- Reset GUC to NULL (legacy behavior)\nRESET timescaledb.default_chunk_time_interval;\nSHOW timescaledb.default_chunk_time_interval;\n timescaledb.default_chunk_time_interval \n-----------------------------------------\n \n\nCREATE TABLE test_reset_timestamptz(time TIMESTAMPTZ NOT NULL, val INT);\nSELECT create_hypertable('test_reset_timestamptz', 'time');\n          create_hypertable           \n--------------------------------------\n (42,public,test_reset_timestamptz,t)\n\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_reset_timestamptz';\n time_interval \n---------------\n @ 7 days\n\nDROP TABLE test_reset_timestamptz;\n-- Invalid interval should fail\n\\set ON_ERROR_STOP 0\nSET timescaledb.default_chunk_time_interval = 'not_an_interval';\nERROR:  invalid input syntax for type interval: \"not_an_interval\"\nSET timescaledb.default_chunk_time_interval = '123abc';\nERROR:  invalid input syntax for type interval: \"123abc\"\n\\set ON_ERROR_STOP 1\n-- add_dimension does not use the GUC, keeps legacy behavior requiring explicit interval\nRESET timescaledb.default_chunk_time_interval;\nSET timescaledb.default_chunk_time_interval = '3 days';\nCREATE TABLE test_add_dim(time TIMESTAMPTZ NOT NULL, time2 TIMESTAMPTZ NOT NULL, val INT);\nSELECT create_hypertable('test_add_dim', 'time');\n     create_hypertable      \n----------------------------\n (43,public,test_add_dim,t)\n\n-- First dimension uses GUC\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_add_dim';\n time_interval \n---------------\n @ 3 days\n\n-- add_dimension without explicit interval should fail\n\\set ON_ERROR_STOP 0\nSELECT add_dimension('test_add_dim', 'time2');\nERROR:  must specify either the number of partitions or an interval\n\\set ON_ERROR_STOP 1\n-- add_dimension with explicit interval works\nSELECT add_dimension('test_add_dim', 'time2', chunk_time_interval => INTERVAL '1 day');\n          add_dimension           \n----------------------------------\n (56,public,test_add_dim,time2,t)\n\nSELECT column_name, time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_add_dim'\n  ORDER BY dimension_number;\n column_name | time_interval \n-------------+---------------\n time        | @ 3 days\n time2       | @ 1 day\n\nDROP TABLE test_add_dim;\n-- Session-level GUC changes\nRESET timescaledb.default_chunk_time_interval;\nSET timescaledb.default_chunk_time_interval = '5 days';\nCREATE TABLE test_session_1(time TIMESTAMPTZ NOT NULL, val INT);\nSELECT create_hypertable('test_session_1', 'time');\n      create_hypertable       \n------------------------------\n (44,public,test_session_1,t)\n\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_session_1';\n time_interval \n---------------\n @ 5 days\n\n-- Change GUC mid-session\nSET timescaledb.default_chunk_time_interval = '10 days';\nCREATE TABLE test_session_2(time TIMESTAMPTZ NOT NULL, val INT);\nSELECT create_hypertable('test_session_2', 'time');\n      create_hypertable       \n------------------------------\n (45,public,test_session_2,t)\n\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_session_2';\n time_interval \n---------------\n @ 10 days\n\nDROP TABLE test_session_1;\nDROP TABLE test_session_2;\n-- Transaction-level GUC with SET LOCAL\nRESET timescaledb.default_chunk_time_interval;\nSET timescaledb.default_chunk_time_interval = '1 week';\nBEGIN;\nSET LOCAL timescaledb.default_chunk_time_interval = '2 weeks';\nCREATE TABLE test_local_guc(time TIMESTAMPTZ NOT NULL, val INT);\nSELECT create_hypertable('test_local_guc', 'time');\n      create_hypertable       \n------------------------------\n (46,public,test_local_guc,t)\n\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_local_guc';\n time_interval \n---------------\n @ 14 days\n\nCOMMIT;\n-- After commit, GUC should be back to session level (1 week)\nSHOW timescaledb.default_chunk_time_interval;\n timescaledb.default_chunk_time_interval \n-----------------------------------------\n 1 week\n\nDROP TABLE test_local_guc;\n-- UUID partition type with various intervals\nRESET timescaledb.default_chunk_time_interval;\nSET timescaledb.default_chunk_time_interval = '1 day';\nCREATE TABLE test_uuid_1day(time UUID NOT NULL, val INT);\nSELECT create_hypertable('test_uuid_1day', 'time');\n      create_hypertable       \n------------------------------\n (47,public,test_uuid_1day,t)\n\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_uuid_1day';\n time_interval \n---------------\n @ 1 day\n\nDROP TABLE test_uuid_1day;\nSET timescaledb.default_chunk_time_interval = '1 hour';\nCREATE TABLE test_uuid_1hour(time UUID NOT NULL, val INT);\nSELECT create_hypertable('test_uuid_1hour', 'time');\n       create_hypertable       \n-------------------------------\n (48,public,test_uuid_1hour,t)\n\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_uuid_1hour';\n time_interval \n---------------\n @ 1 hour\n\nDROP TABLE test_uuid_1hour;\n-- UUID with explicit override should work\nSET timescaledb.default_chunk_time_interval = '1 week';\nCREATE TABLE test_uuid_override(time UUID NOT NULL, val INT);\nSELECT create_hypertable('test_uuid_override', 'time', chunk_time_interval => INTERVAL '12 hours');\n        create_hypertable         \n----------------------------------\n (49,public,test_uuid_override,t)\n\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_uuid_override';\n time_interval \n---------------\n @ 12 hours\n\nDROP TABLE test_uuid_override;\n-- UUID with no GUC set (legacy default)\nRESET timescaledb.default_chunk_time_interval;\nCREATE TABLE test_uuid_legacy(time UUID NOT NULL, val INT);\nSELECT create_hypertable('test_uuid_legacy', 'time');\n       create_hypertable        \n--------------------------------\n (50,public,test_uuid_legacy,t)\n\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_uuid_legacy';\n time_interval \n---------------\n @ 7 days\n\nDROP TABLE test_uuid_legacy;\n-- Cleanup\nRESET timescaledb.default_chunk_time_interval;\n-- Check that we produce the proper error code for nonexistent tables.\nselect create_hypertable(77777777, 'nonexistent');\nERROR:  relation with oid 77777777 not found\n"
  },
  {
    "path": "test/expected/create_table.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- Test that we can verify constraints on regular tables\nCREATE TABLE test_hyper_pk(time TIMESTAMPTZ PRIMARY KEY, temp FLOAT, device INT);\nCREATE TABLE test_pk(device INT PRIMARY KEY);\nCREATE TABLE test_like(LIKE test_pk);\nSELECT create_hypertable('test_hyper_pk', 'time');\n     create_hypertable      \n----------------------------\n (1,public,test_hyper_pk,t)\n\n\\set ON_ERROR_STOP 0\n-- Foreign key constraints that reference hypertables are currently unsupported\nCREATE TABLE test_fk(time TIMESTAMPTZ REFERENCES test_hyper_pk(time));\n\\set ON_ERROR_STOP 1\nCREATE TABLE test_delete(time timestamp with time zone PRIMARY KEY, temp float);\nSELECT create_hypertable('test_delete', 'time');\n    create_hypertable     \n--------------------------\n (2,public,test_delete,t)\n\nINSERT INTO test_delete VALUES('2017-01-20T09:00:01', 22.5);\nINSERT INTO test_delete VALUES('2017-01-20T09:00:21', 21.2);\nINSERT INTO test_delete VALUES('2017-01-20T09:00:47', 25.1);\nINSERT INTO test_delete VALUES('2020-01-20T09:00:47', 25.1);\nINSERT INTO test_delete VALUES('2021-01-20T09:00:47', 25.1);\nSELECT * FROM test_delete WHERE temp = 25.1 ORDER BY time;\n             time             | temp \n------------------------------+------\n Fri Jan 20 09:00:47 2017 PST | 25.1\n Mon Jan 20 09:00:47 2020 PST | 25.1\n Wed Jan 20 09:00:47 2021 PST | 25.1\n\nCREATE OR replace FUNCTION test_delete_row_count()\nRETURNS void AS $$\nDECLARE\n    v_cnt numeric;\nBEGIN\n    v_cnt := 0;\n\n        DELETE FROM test_delete WHERE temp = 25.1;\n        GET DIAGNOSTICS v_cnt = ROW_COUNT;\n\n        IF v_cnt != 3 THEN\n           RAISE EXCEPTION 'unexpected result';\n        END IF;\nEND;\n$$ LANGUAGE plpgsql;\nSELECT test_delete_row_count();\n test_delete_row_count \n-----------------------\n \n\n"
  },
  {
    "path": "test/expected/create_table_with.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- our user needs permission to create schema for the schema tests\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nGRANT CREATE ON DATABASE :TEST_DBNAME TO :ROLE_DEFAULT_PERM_USER;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n-- create table with non-tsdb option should not be affected\nCREATE TABLE t1(time timestamptz, device text, value float) WITH (autovacuum_enabled);\nDROP TABLE t1;\n-- test error cases\n\\set ON_ERROR_STOP 0\n\\set VERBOSITY default\nCREATE TABLE t2(time float, device text, value float) WITH (tsdb.hypertable);\nERROR:  partition column could not be determined\nHINT:  Use \"timescaledb.partition_column\" to specify the column to use as partitioning column.\nCREATE TABLE t2(time float, device text, value float) WITH (timescaledb.hypertable);\nERROR:  partition column could not be determined\nHINT:  Use \"timescaledb.partition_column\" to specify the column to use as partitioning column.\nCREATE TABLE t2(time timestamptz, device text, value float) WITH (tsdb.hypertable,tsdb.partition_column=NULL);\nERROR:  column \"null\" does not exist\nCREATE TABLE t2(time timestamptz, device text, value float) WITH (tsdb.hypertable,tsdb.partition_column='');\nERROR:  column \"\" does not exist\nCREATE TABLE t2(time timestamptz, device text, value float) WITH (tsdb.hypertable,tsdb.partition_column='foo');\nERROR:  column \"foo\" does not exist\nCREATE TABLE t2(time timestamptz, device text, value float) WITH (tsdb.partition_column='time');\nERROR:  timescaledb options requires hypertable option\nHINT:  Use \"timescaledb.hypertable\" to enable creating a hypertable.\nCREATE TABLE t2(time timestamptz, device text, value float) WITH (timescaledb.partition_column='time');\nERROR:  timescaledb options requires hypertable option\nHINT:  Use \"timescaledb.hypertable\" to enable creating a hypertable.\nCREATE TABLE t2(time timestamptz , device text, value float) WITH (tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval='foo');\nERROR:  invalid input syntax for type interval: \"foo\"\nCREATE TABLE t2(time int2 NOT NULL, device text, value float) WITH (tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval='3 months');\nERROR:  invalid input syntax for type smallint: \"3 months\"\nCREATE TABLE t2(time timestamptz, device text, value float) WITH (tsdb.create_default_indexes='time');\nERROR:  invalid value for tsdb.create_default_indexes 'time'\nHINT:  tsdb.create_default_indexes must be a valid bool\nCREATE TABLE t2(time timestamptz, device text, value float) WITH (tsdb.create_default_indexes=2);\nERROR:  invalid value for tsdb.create_default_indexes '2'\nHINT:  tsdb.create_default_indexes must be a valid bool\nCREATE TABLE t2(time timestamptz, device text, value float) WITH (tsdb.create_default_indexes=-1);\nERROR:  invalid value for tsdb.create_default_indexes '-1'\nHINT:  tsdb.create_default_indexes must be a valid bool\nCREATE TABLE t2(time timestamptz NOT NULL, device text, value float) WITH (tsdb.hypertable,tsdb.partition_column='time',tsdb.columnstore=true);\nERROR:  functionality not supported under the current \"apache\" license. Learn more at https://tsdb.co/pdbir1r3\nHINT:  To access all features and the best time-series experience, try out Timescale Cloud.\nCREATE TABLE t2(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore,tsdb.hypertable,tsdb.partition_column='time');\nERROR:  functionality not supported under the current \"apache\" license. Learn more at https://tsdb.co/pdbir1r3\nHINT:  To access all features and the best time-series experience, try out Timescale Cloud.\n-- Test error hint for invalid timescaledb options during CREATE TABLE\nCREATE TABLE t2(time timestamptz, device text, value float) WITH (tsdb.invalid_option = true);\nERROR:  unrecognized parameter \"tsdb.invalid_option\"\nHINT:  Valid timescaledb parameters are: hypertable, columnstore, partition_column, chunk_interval, create_default_indexes, associated_schema, associated_table_prefix, orderby, segmentby, compress_index\nCREATE TABLE t2(time timestamptz, device text, value float) WITH (timescaledb.nonexistent_param = false);\nERROR:  unrecognized parameter \"timescaledb.nonexistent_param\"\nHINT:  Valid timescaledb parameters are: hypertable, columnstore, partition_column, chunk_interval, create_default_indexes, associated_schema, associated_table_prefix, orderby, segmentby, compress_index\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\nBEGIN;\nCREATE TABLE t3(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time');\nCREATE TABLE t4(time timestamp, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,timescaledb.partition_column='time');\nCREATE TABLE t5(time date, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time',autovacuum_enabled);\nCREATE TABLE t6(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,timescaledb.hypertable,tsdb.partition_column='time');\nCREATE TABLE t7(time timestamptz, device text, value float) WITH (timescaledb.hypertable,tsdb.partition_column='time');\nSELECT hypertable_name FROM timescaledb_information.hypertables ORDER BY 1;\n hypertable_name \n-----------------\n t3\n t4\n t5\n t6\n t7\n\nROLLBACK;\n-- IF NOT EXISTS\nBEGIN;\nCREATE TABLE IF NOT EXISTS t7(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time');\nCREATE TABLE IF NOT EXISTS t7(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time');\nNOTICE:  relation \"t7\" already exists, skipping\nCREATE TABLE IF NOT EXISTS t7(time timestamptz NOT NULL, device text, value float);\nNOTICE:  relation \"t7\" already exists, skipping\nSELECT hypertable_name FROM timescaledb_information.hypertables ORDER BY 1;\n hypertable_name \n-----------------\n t7\n\nROLLBACK;\n-- table won't be converted to hypertable unless it is in the initial CREATE TABLE\nBEGIN;\nCREATE TABLE IF NOT EXISTS t8(time timestamptz NOT NULL, device text, value float);\nCREATE TABLE IF NOT EXISTS t8(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time');\nNOTICE:  relation \"t8\" already exists, skipping\nCREATE TABLE IF NOT EXISTS t8(time timestamptz NOT NULL, device text, value float);\nNOTICE:  relation \"t8\" already exists, skipping\nSELECT hypertable_name FROM timescaledb_information.hypertables ORDER BY 1;\n hypertable_name \n-----------------\n\nROLLBACK;\n-- chunk_interval\nBEGIN;\nCREATE TABLE IF NOT EXISTS t9(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval='8weeks');\nSELECT hypertable_name, column_name, column_type, time_interval FROM timescaledb_information.dimensions;\n hypertable_name | column_name |       column_type        | time_interval \n-----------------+-------------+--------------------------+---------------\n t9              | time        | timestamp with time zone | @ 56 days\n\nROLLBACK;\nBEGIN;\nCREATE TABLE IF NOT EXISTS t9(time timestamp NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval='23 days');\nSELECT hypertable_name, column_name, column_type, time_interval FROM timescaledb_information.dimensions;\n hypertable_name | column_name |         column_type         | time_interval \n-----------------+-------------+-----------------------------+---------------\n t9              | time        | timestamp without time zone | @ 23 days\n\nROLLBACK;\nBEGIN;\nCREATE TABLE IF NOT EXISTS t9(time date NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval='3 months');\nSELECT hypertable_name, column_name, column_type, time_interval FROM timescaledb_information.dimensions;\n hypertable_name | column_name | column_type | time_interval \n-----------------+-------------+-------------+---------------\n t9              | time        | date        | @ 90 days\n\nROLLBACK;\nBEGIN;\nCREATE TABLE IF NOT EXISTS t9(time int2 NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval=12);\nSELECT hypertable_name, column_name, column_type, integer_interval FROM timescaledb_information.dimensions;\n hypertable_name | column_name | column_type | integer_interval \n-----------------+-------------+-------------+------------------\n t9              | time        | smallint    |               12\n\nROLLBACK;\nBEGIN;\nCREATE TABLE IF NOT EXISTS t9(time int4 NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval=3453);\nSELECT hypertable_name, column_name, column_type, integer_interval FROM timescaledb_information.dimensions;\n hypertable_name | column_name | column_type | integer_interval \n-----------------+-------------+-------------+------------------\n t9              | time        | integer     |             3453\n\nROLLBACK;\nBEGIN;\nCREATE TABLE IF NOT EXISTS t9(time int8 NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval=32768);\nSELECT hypertable_name, column_name, column_type, integer_interval FROM timescaledb_information.dimensions;\n hypertable_name | column_name | column_type | integer_interval \n-----------------+-------------+-------------+------------------\n t9              | time        | bigint      |            32768\n\nROLLBACK;\n-- create_default_indexes\nBEGIN;\nCREATE TABLE t10(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time');\nSELECT indexrelid::regclass from pg_index where indrelid='t10'::regclass ORDER BY indexrelid::regclass::text;\n  indexrelid  \n--------------\n t10_time_idx\n\nROLLBACK;\nBEGIN;\nCREATE TABLE t10(time timestamptz NOT NULL PRIMARY KEY, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time');\nSELECT indexrelid::regclass from pg_index where indrelid='t10'::regclass ORDER BY indexrelid::regclass::text;\n indexrelid \n------------\n t10_pkey\n\nROLLBACK;\nBEGIN;\nCREATE TABLE t10(time timestamptz NOT NULL UNIQUE, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time');\nSELECT indexrelid::regclass from pg_index where indrelid='t10'::regclass ORDER BY indexrelid::regclass::text;\n  indexrelid  \n--------------\n t10_time_key\n\nROLLBACK;\nBEGIN;\nCREATE TABLE t10(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time',tsdb.create_default_indexes=true);\nSELECT indexrelid::regclass from pg_index where indrelid='t10'::regclass ORDER BY indexrelid::regclass::text;\n  indexrelid  \n--------------\n t10_time_idx\n\nROLLBACK;\nBEGIN;\nCREATE TABLE t10(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time',tsdb.create_default_indexes=false);\nSELECT indexrelid::regclass from pg_index where indrelid='t10'::regclass ORDER BY indexrelid::regclass::text;\n indexrelid \n------------\n\nROLLBACK;\n-- associated_schema\nBEGIN;\nCREATE TABLE t11(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time');\nSELECT associated_schema_name FROM _timescaledb_catalog.hypertable WHERE table_name = 't11';\n associated_schema_name \n------------------------\n _timescaledb_internal\n\nROLLBACK;\nBEGIN;\nCREATE TABLE t11(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time', tsdb.associated_schema='abc');\nSELECT associated_schema_name FROM _timescaledb_catalog.hypertable WHERE table_name = 't11';\n associated_schema_name \n------------------------\n abc\n\nINSERT INTO t11 SELECT '2025-01-01', 'd1', 0.1;\nSELECT relname from pg_class where relnamespace = 'abc'::regnamespace ORDER BY 1;\n            relname             \n--------------------------------\n _hyper_21_1_chunk\n _hyper_21_1_chunk_t11_time_idx\n\nROLLBACK;\nBEGIN;\nCREATE SCHEMA abc2;\nCREATE TABLE t11(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time', tsdb.associated_schema='abc2');\nSELECT associated_schema_name FROM _timescaledb_catalog.hypertable WHERE table_name = 't11';\n associated_schema_name \n------------------------\n abc2\n\nINSERT INTO t11 SELECT '2025-01-01', 'd1', 0.1;\nSELECT relname from pg_class where relnamespace = 'abc2'::regnamespace ORDER BY 1;\n            relname             \n--------------------------------\n _hyper_22_2_chunk\n _hyper_22_2_chunk_t11_time_idx\n\nROLLBACK;\n-- associated_table_prefix\nBEGIN;\nCREATE TABLE t12(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time');\nSELECT associated_table_prefix FROM _timescaledb_catalog.hypertable WHERE table_name = 't12';\n associated_table_prefix \n-------------------------\n _hyper_23\n\nROLLBACK;\nBEGIN;\nCREATE TABLE t12(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time', tsdb.associated_schema='abc', tsdb.associated_table_prefix='tbl_prefix');\nSELECT associated_table_prefix FROM _timescaledb_catalog.hypertable WHERE table_name = 't12';\n associated_table_prefix \n-------------------------\n tbl_prefix\n\nINSERT INTO t12 SELECT '2025-01-01', 'd1', 0.1;\nSELECT relname from pg_class where relnamespace = 'abc'::regnamespace ORDER BY 1;\n             relname             \n---------------------------------\n tbl_prefix_3_chunk\n tbl_prefix_3_chunk_t12_time_idx\n\nROLLBACK;\n-- default partition column\nBEGIN;\nCREATE TABLE t13(time timestamptz, device text, value float) WITH (tsdb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nCREATE TABLE t14(\"TiMe\" timestamptz, device text, value float) WITH (tsdb.hypertable);\nNOTICE:  using column \"TiMe\" as partitioning column\nSELECT hypertable_name, column_name FROM timescaledb_information.dimensions WHERE hypertable_name IN ('t13','t14') ORDER BY 1;\n hypertable_name | column_name \n-----------------+-------------\n t13             | time\n t14             | TiMe\n\nROLLBACK;\n-- Test default_chunk_time_interval GUC interaction with CREATE TABLE WITH\n-- Tests that the GUC correctly sets the default chunk interval when using\n-- CREATE TABLE WITH syntax instead of create_hypertable().\n-- GUC set to '1 week' should be used by CREATE TABLE WITH\nBEGIN;\nSET timescaledb.default_chunk_time_interval = '1 week';\nCREATE TABLE t_guc_week(time timestamptz NOT NULL, device text, value float)\n  WITH (tsdb.hypertable, tsdb.partition_column='time');\nSELECT hypertable_name, time_interval\n  FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 't_guc_week';\n hypertable_name | time_interval \n-----------------+---------------\n t_guc_week      | @ 7 days\n\nROLLBACK;\n-- GUC set to '1 day' with different time types\nBEGIN;\nSET timescaledb.default_chunk_time_interval = '1 day';\nCREATE TABLE t_guc_timestamptz(time timestamptz NOT NULL, device text, value float)\n  WITH (tsdb.hypertable, tsdb.partition_column='time');\nCREATE TABLE t_guc_timestamp(time timestamp NOT NULL, device text, value float)\n  WITH (tsdb.hypertable, tsdb.partition_column='time');\nCREATE TABLE t_guc_date(time date NOT NULL, device text, value float)\n  WITH (tsdb.hypertable, tsdb.partition_column='time');\nSELECT hypertable_name, time_interval\n  FROM timescaledb_information.dimensions\n  WHERE hypertable_name LIKE 't_guc_%'\n  ORDER BY hypertable_name;\n  hypertable_name  | time_interval \n-------------------+---------------\n t_guc_date        | @ 1 day\n t_guc_timestamp   | @ 1 day\n t_guc_timestamptz | @ 1 day\n\nROLLBACK;\n-- Explicit tsdb.chunk_interval should override the GUC\nBEGIN;\nSET timescaledb.default_chunk_time_interval = '1 week';\nCREATE TABLE t_guc_override(time timestamptz NOT NULL, device text, value float)\n  WITH (tsdb.hypertable, tsdb.partition_column='time', tsdb.chunk_interval='2 days');\nSELECT hypertable_name, time_interval\n  FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 't_guc_override';\n hypertable_name | time_interval \n-----------------+---------------\n t_guc_override  | @ 2 days\n\nROLLBACK;\n-- Integer partition types have their own default and do not use the GUC\nBEGIN;\nSET timescaledb.default_chunk_time_interval = '1 week';\nCREATE TABLE t_guc_int(time int8 NOT NULL, device text, value float)\n  WITH (tsdb.hypertable, tsdb.partition_column='time');\nSELECT hypertable_name, integer_interval\n  FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 't_guc_int';\n hypertable_name | integer_interval \n-----------------+------------------\n t_guc_int       |          1000000\n\nROLLBACK;\n-- No GUC set (NULL) should use legacy defaults\nBEGIN;\nRESET timescaledb.default_chunk_time_interval;\nCREATE TABLE t_no_guc(time timestamptz NOT NULL, device text, value float)\n  WITH (tsdb.hypertable, tsdb.partition_column='time');\nSELECT hypertable_name, time_interval\n  FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 't_no_guc';\n hypertable_name | time_interval \n-----------------+---------------\n t_no_guc        | @ 7 days\n\nROLLBACK;\n-- GUC with UUID partition type\nBEGIN;\nSET timescaledb.default_chunk_time_interval = '12 hours';\nCREATE TABLE t_guc_uuid(time uuid NOT NULL, device text, value float)\n  WITH (tsdb.hypertable, tsdb.partition_column='time');\nSELECT hypertable_name, time_interval\n  FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 't_guc_uuid';\n hypertable_name | time_interval \n-----------------+---------------\n t_guc_uuid      | @ 12 hours\n\nROLLBACK;\n-- Cleanup\nRESET timescaledb.default_chunk_time_interval;\n"
  },
  {
    "path": "test/expected/cursor.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE cursor_test(time timestamptz, device_id int, temp float);\nSELECT create_hypertable('cursor_test','time');\n    create_hypertable     \n--------------------------\n (1,public,cursor_test,t)\n\nINSERT INTO cursor_test SELECT '2000-01-01',1,0.5;\nINSERT INTO cursor_test SELECT '2001-01-01',1,0.5;\nINSERT INTO cursor_test SELECT '2002-01-01',1,0.5;\n\\set ON_ERROR_STOP 0\nBEGIN;\nDECLARE c1 SCROLL CURSOR FOR SELECT * FROM cursor_test;\nFETCH NEXT FROM c1;\n             time             | device_id | temp \n------------------------------+-----------+------\n Sat Jan 01 00:00:00 2000 PST |         1 |  0.5\n\n-- this will produce an error on PG < 14 because PostgreSQL checks\n-- for the existence of a scan node with the relation id for every relation\n-- used in the update plan in the plan of the cursor.\nUPDATE cursor_test SET temp = 0.7 WHERE CURRENT OF c1;\nCOMMIT;\n-- test cursor with no chunks left after runtime exclusion\nBEGIN;\nDECLARE c1 SCROLL CURSOR FOR SELECT * FROM cursor_test WHERE time > now();\nUPDATE cursor_test SET temp = 0.7 WHERE CURRENT OF c1;\nERROR:  cursor \"c1\" is not a simply updatable scan of table \"_hyper_1_1_chunk\"\nCOMMIT;\n-- test cursor with no chunks left after planning exclusion\nBEGIN;\nDECLARE c1 SCROLL CURSOR FOR SELECT * FROM cursor_test WHERE time > '2010-01-01';\nUPDATE cursor_test SET temp = 0.7 WHERE CURRENT OF c1;\nERROR:  cursor \"c1\" is not a simply updatable scan of table \"_hyper_1_1_chunk\"\nCOMMIT;\n\\set ON_ERROR_STOP 1\nSET timescaledb.enable_constraint_exclusion TO off;\nBEGIN;\nDECLARE c1 SCROLL CURSOR FOR SELECT * FROM cursor_test;\nFETCH NEXT FROM c1;\n             time             | device_id | temp \n------------------------------+-----------+------\n Sat Jan 01 00:00:00 2000 PST |         1 |  0.7\n\nUPDATE cursor_test SET temp = 0.7 WHERE CURRENT OF c1;\nCOMMIT;\n"
  },
  {
    "path": "test/expected/custom_type.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET client_min_messages TO WARNING;\nCREATE OR REPLACE FUNCTION customtype_in(cstring) RETURNS customtype AS\n'timestamptz_in'\nLANGUAGE internal IMMUTABLE STRICT;\nCREATE OR REPLACE FUNCTION customtype_out(customtype) RETURNS cstring AS\n'timestamptz_out'\nLANGUAGE internal IMMUTABLE STRICT;\nCREATE OR REPLACE FUNCTION customtype_recv(internal) RETURNS customtype AS\n'timestamptz_recv'\nLANGUAGE internal IMMUTABLE STRICT;\nCREATE OR REPLACE FUNCTION customtype_send(customtype) RETURNS bytea AS\n'timestamptz_send'\nLANGUAGE internal IMMUTABLE STRICT;\nSET client_min_messages TO DEFAULT;\nCREATE TYPE customtype (\n INPUT = customtype_in,\n OUTPUT = customtype_out,\n RECEIVE = customtype_recv,\n SEND = customtype_send,\n LIKE = TIMESTAMPTZ\n);\nCREATE CAST (customtype AS bigint)\nWITHOUT FUNCTION AS ASSIGNMENT;\nCREATE CAST (bigint AS customtype)\nWITHOUT FUNCTION AS IMPLICIT;\nCREATE CAST (customtype AS timestamptz)\nWITHOUT FUNCTION AS ASSIGNMENT;\nCREATE CAST (timestamptz AS customtype)\nWITHOUT FUNCTION AS ASSIGNMENT;\nCREATE OR REPLACE FUNCTION customtype_lt(customtype, customtype) RETURNS bool AS\n'timestamp_lt'\nLANGUAGE internal IMMUTABLE STRICT;\nCREATE OPERATOR < (\n\tLEFTARG = customtype,\n\tRIGHTARG = customtype,\n\tPROCEDURE = customtype_lt,\n\tCOMMUTATOR = >,\n\tNEGATOR = >=,\n\tRESTRICT = scalarltsel,\n\tJOIN = scalarltjoinsel\n);\nCREATE OR REPLACE FUNCTION customtype_ge(customtype, customtype) RETURNS bool AS\n'timestamp_ge'\nLANGUAGE internal IMMUTABLE STRICT;\nCREATE OPERATOR >= (\n\tLEFTARG = customtype,\n\tRIGHTARG = customtype,\n\tPROCEDURE = customtype_ge,\n\tCOMMUTATOR = <=,\n\tNEGATOR = <,\n\tRESTRICT = scalargtsel,\n\tJOIN = scalargtjoinsel\n);\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nCREATE TABLE customtype_test(time_custom customtype, val int);\n\\set ON_ERROR_STOP 0\n-- Using interval type for chunk time interval should fail with custom time type\nSELECT create_hypertable('customtype_test', 'time_custom', chunk_time_interval => INTERVAL '1 day', create_default_indexes=>false);\nERROR:  invalid interval type for customtype dimension\n\\set ON_ERROR_STOP 1\nSELECT create_hypertable('customtype_test', 'time_custom', chunk_time_interval => 10e6::bigint, create_default_indexes=>false);\n      create_hypertable       \n------------------------------\n (1,public,customtype_test,t)\n\nINSERT INTO customtype_test VALUES ('2001-01-01 01:02:03'::customtype, 10);\nINSERT INTO customtype_test VALUES ('2001-01-01 01:02:03'::customtype, 10);\nINSERT INTO customtype_test VALUES ('2001-01-01 01:02:03'::customtype, 10);\nEXPLAIN (buffers off, costs off) SELECT * FROM customtype_test;\n--- QUERY PLAN ---\n Seq Scan on _hyper_1_1_chunk\n\nINSERT INTO customtype_test VALUES ('2001-01-01 01:02:23'::customtype, 11);\nEXPLAIN (buffers off, costs off) SELECT * FROM customtype_test;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on _hyper_1_1_chunk\n   ->  Seq Scan on _hyper_1_2_chunk\n\nSELECT * FROM customtype_test;\n         time_custom          | val \n------------------------------+-----\n Mon Jan 01 01:02:03 2001 PST |  10\n Mon Jan 01 01:02:03 2001 PST |  10\n Mon Jan 01 01:02:03 2001 PST |  10\n Mon Jan 01 01:02:23 2001 PST |  11\n\n"
  },
  {
    "path": "test/expected/ddl.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA IF NOT EXISTS \"customSchema\" AUTHORIZATION :ROLE_DEFAULT_PERM_USER;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n\\ir include/ddl_ops_1.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE PUBLIC.\"Hypertable_1\" (\n  time BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  temp_c int NOT NULL DEFAULT -1,\n  humidity numeric NULL DEFAULT 0,\n  sensor_1 NUMERIC NULL DEFAULT 1,\n  sensor_2 NUMERIC NOT NULL DEFAULT 1,\n  sensor_3 NUMERIC NOT NULL DEFAULT 1,\n  sensor_4 NUMERIC NOT NULL DEFAULT 1\n);\nCREATE INDEX ON PUBLIC.\"Hypertable_1\" (time, \"Device_id\");\nCREATE TABLE \"customSchema\".\"Hypertable_1\" (\n  time BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  temp_c int NOT NULL DEFAULT -1,\n  humidity numeric NULL DEFAULT 0,\n  sensor_1 NUMERIC NULL DEFAULT 1,\n  sensor_2 NUMERIC NOT NULL DEFAULT 1,\n  sensor_3 NUMERIC NOT NULL DEFAULT 1,\n  sensor_4 NUMERIC NOT NULL DEFAULT 1\n);\nCREATE INDEX ON \"customSchema\".\"Hypertable_1\" (time, \"Device_id\");\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1\"', 'time', 'Device_id', 1, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name |  table_name  | created \n---------------+-------------+--------------+---------\n             1 | public      | Hypertable_1 | t\n\nSELECT * FROM create_hypertable('\"customSchema\".\"Hypertable_1\"', 'time', NULL, 1, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name  |  table_name  | created \n---------------+--------------+--------------+---------\n             2 | customSchema | Hypertable_1 | t\n\nSELECT * FROM _timescaledb_catalog.hypertable;\n id | schema_name  |  table_name  | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+--------------+--------------+------------------------+-------------------------+----------------+--------------------------+--------------------------+-------------------+-------------------+--------------------------+--------\n  1 | public       | Hypertable_1 | _timescaledb_internal  | _hyper_1                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n  2 | customSchema | Hypertable_1 | _timescaledb_internal  | _hyper_2                |              1 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n\nCREATE INDEX ON PUBLIC.\"Hypertable_1\" (time, \"temp_c\");\nCREATE INDEX \"ind_humidity\" ON PUBLIC.\"Hypertable_1\" (time, \"humidity\");\nCREATE INDEX \"ind_sensor_1\" ON PUBLIC.\"Hypertable_1\" (time, \"sensor_1\");\nINSERT INTO PUBLIC.\"Hypertable_1\"(time, \"Device_id\", temp_c, humidity, sensor_1, sensor_2, sensor_3, sensor_4)\nVALUES(1257894000000000000, 'dev1', 30, 70, 1, 2, 3, 100);\nCREATE UNIQUE INDEX \"Unique1\" ON PUBLIC.\"Hypertable_1\" (time, \"Device_id\");\nCREATE UNIQUE INDEX \"Unique1\" ON \"customSchema\".\"Hypertable_1\" (time);\nINSERT INTO \"customSchema\".\"Hypertable_1\"(time, \"Device_id\", temp_c, humidity, sensor_1, sensor_2, sensor_3, sensor_4)\nVALUES(1257894000000000000, 'dev1', 30, 70, 1, 2, 3, 100);\nINSERT INTO \"customSchema\".\"Hypertable_1\"(time, \"Device_id\", temp_c, humidity, sensor_1, sensor_2, sensor_3, sensor_4)\nVALUES(1257894000000000001, 'dev1', 30, 70, 1, 2, 3, 100);\nSELECT * FROM test.show_indexesp('%.%');\n             Table             |                      Index                       |     Columns      | Expr | Unique | Primary | Exclusion | Tablespace \n-------------------------------+--------------------------------------------------+------------------+------+--------+---------+-----------+------------\n \"customSchema\".\"Hypertable_1\" | \"customSchema\".\"Hypertable_1_time_Device_id_idx\" | {time,Device_id} |      | f      | f       | f         | \n \"customSchema\".\"Hypertable_1\" | \"customSchema\".\"Hypertable_1_time_idx\"           | {time}           |      | f      | f       | f         | \n \"customSchema\".\"Hypertable_1\" | \"customSchema\".\"Unique1\"                         | {time}           |      | t      | f       | f         | \n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper_%');\n                 Table                  |                                  Index                                   |     Columns      | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------+--------------------------------------------------------------------------+------------------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_Hypertable_1_time_Device_id_idx\" | {time,Device_id} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_Hypertable_1_time_idx\"           | {time}           |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_Hypertable_1_Device_id_time_idx\" | {Device_id,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_Hypertable_1_time_temp_c_idx\"    | {time,temp_c}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_ind_humidity                      | {time,humidity}  |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_ind_sensor_1                      | {time,sensor_1}  |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_Unique1\"                         | {time,Device_id} |      | t      | f       | f         | \n _timescaledb_internal._hyper_2_2_chunk | _timescaledb_internal.\"_hyper_2_2_chunk_Hypertable_1_time_Device_id_idx\" | {time,Device_id} |      | f      | f       | f         | \n _timescaledb_internal._hyper_2_2_chunk | _timescaledb_internal.\"_hyper_2_2_chunk_Hypertable_1_time_idx\"           | {time}           |      | f      | f       | f         | \n _timescaledb_internal._hyper_2_2_chunk | _timescaledb_internal.\"_hyper_2_2_chunk_Unique1\"                         | {time}           |      | t      | f       | f         | \n\n--expect error cases\n\\set ON_ERROR_STOP 0\nINSERT INTO \"customSchema\".\"Hypertable_1\"(time, \"Device_id\", temp_c, humidity, sensor_1, sensor_2, sensor_3, sensor_4)\nVALUES(1257894000000000000, 'dev1', 31, 71, 72, 4, 1, 102);\npsql:include/ddl_ops_1.sql:57: ERROR:  duplicate key value violates unique constraint \"_hyper_2_2_chunk_Unique1\"\nCREATE UNIQUE INDEX \"Unique2\" ON PUBLIC.\"Hypertable_1\" (\"Device_id\");\npsql:include/ddl_ops_1.sql:58: ERROR:  cannot create a unique index without the column \"time\" (used in partitioning)\nCREATE UNIQUE INDEX \"Unique2\" ON PUBLIC.\"Hypertable_1\" (time);\npsql:include/ddl_ops_1.sql:59: ERROR:  cannot create a unique index without the column \"Device_id\" (used in partitioning)\nCREATE UNIQUE INDEX \"Unique2\" ON PUBLIC.\"Hypertable_1\" (sensor_1);\npsql:include/ddl_ops_1.sql:60: ERROR:  cannot create a unique index without the column \"time\" (used in partitioning)\nUPDATE ONLY PUBLIC.\"Hypertable_1\" SET time = 0 WHERE TRUE;\nDELETE FROM ONLY PUBLIC.\"Hypertable_1\" WHERE \"Device_id\" = 'dev1';\n\\set ON_ERROR_STOP 1\nCREATE TABLE my_ht (time BIGINT, val integer);\nSELECT * FROM create_hypertable('my_ht', 'time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             3 | public      | my_ht      | t\n\nALTER TABLE my_ht ADD COLUMN val2 integer;\nSELECT * FROM test.show_columns('my_ht');\n Column |  Type   | NotNull \n--------+---------+---------\n time   | bigint  | t\n val    | integer | f\n val2   | integer | f\n\n-- Should error when adding again\n\\set ON_ERROR_STOP 0\nALTER TABLE my_ht ADD COLUMN val2 integer;\npsql:include/ddl_ops_1.sql:73: ERROR:  column \"val2\" of relation \"my_ht\" already exists\n\\set ON_ERROR_STOP 1\n-- Should create\nALTER TABLE my_ht ADD COLUMN IF NOT EXISTS val3 integer;\nSELECT * FROM test.show_columns('my_ht');\n Column |  Type   | NotNull \n--------+---------+---------\n time   | bigint  | t\n val    | integer | f\n val2   | integer | f\n val3   | integer | f\n\n-- Should skip and not error\nALTER TABLE my_ht ADD COLUMN IF NOT EXISTS val3 integer;\npsql:include/ddl_ops_1.sql:81: NOTICE:  column \"val3\" of relation \"my_ht\" already exists, skipping\nSELECT * FROM test.show_columns('my_ht');\n Column |  Type   | NotNull \n--------+---------+---------\n time   | bigint  | t\n val    | integer | f\n val2   | integer | f\n val3   | integer | f\n\n-- Should drop\nALTER TABLE my_ht DROP COLUMN IF EXISTS val3;\nSELECT * FROM test.show_columns('my_ht');\n Column |  Type   | NotNull \n--------+---------+---------\n time   | bigint  | t\n val    | integer | f\n val2   | integer | f\n\n-- Should skip and not error\nALTER TABLE my_ht DROP COLUMN IF EXISTS val3;\npsql:include/ddl_ops_1.sql:89: NOTICE:  column \"val3\" of relation \"my_ht\" does not exist, skipping\nSELECT * FROM test.show_columns('my_ht');\n Column |  Type   | NotNull \n--------+---------+---------\n time   | bigint  | t\n val    | integer | f\n val2   | integer | f\n\n--Test default index creation on create_hypertable().\n--Make sure that we do not duplicate indexes that already exists\n--\n--No existing indexes: both time and space-time indexes created\nBEGIN;\nCREATE TABLE PUBLIC.\"Hypertable_1_with_default_index_enabled\" (\n  \"Time\" BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1\n);\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1_with_default_index_enabled\"', 'Time', 'Device_id', 1, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name |               table_name                | created \n---------------+-------------+-----------------------------------------+---------\n             4 | public      | Hypertable_1_with_default_index_enabled | t\n\nSELECT * FROM test.show_indexes('\"Hypertable_1_with_default_index_enabled\"');\n                            Index                             |     Columns      | Expr | Unique | Primary | Exclusion | Tablespace \n--------------------------------------------------------------+------------------+------+--------+---------+-----------+------------\n \"Hypertable_1_with_default_index_enabled_Device_id_Time_idx\" | {Device_id,Time} |      | f      | f       | f         | \n \"Hypertable_1_with_default_index_enabled_Time_idx\"           | {Time}           |      | f      | f       | f         | \n\nROLLBACK;\n--Space index exists: only time index created\nBEGIN;\nCREATE TABLE PUBLIC.\"Hypertable_1_with_default_index_enabled\" (\n  \"Time\" BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1\n);\nCREATE INDEX ON PUBLIC.\"Hypertable_1_with_default_index_enabled\" (\"Device_id\", \"Time\" DESC);\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1_with_default_index_enabled\"', 'Time', 'Device_id', 1, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name |               table_name                | created \n---------------+-------------+-----------------------------------------+---------\n             5 | public      | Hypertable_1_with_default_index_enabled | t\n\nSELECT * FROM test.show_indexes('\"Hypertable_1_with_default_index_enabled\"');\n                            Index                             |     Columns      | Expr | Unique | Primary | Exclusion | Tablespace \n--------------------------------------------------------------+------------------+------+--------+---------+-----------+------------\n \"Hypertable_1_with_default_index_enabled_Device_id_Time_idx\" | {Device_id,Time} |      | f      | f       | f         | \n \"Hypertable_1_with_default_index_enabled_Time_idx\"           | {Time}           |      | f      | f       | f         | \n\nROLLBACK;\n--Time index exists, only partition index created\nBEGIN;\nCREATE TABLE PUBLIC.\"Hypertable_1_with_default_index_enabled\" (\n  \"Time\" BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1\n);\nCREATE INDEX ON PUBLIC.\"Hypertable_1_with_default_index_enabled\" (\"Time\" DESC);\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1_with_default_index_enabled\"', 'Time', 'Device_id', 1, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name |               table_name                | created \n---------------+-------------+-----------------------------------------+---------\n             6 | public      | Hypertable_1_with_default_index_enabled | t\n\nSELECT * FROM test.show_indexes('\"Hypertable_1_with_default_index_enabled\"');\n                            Index                             |     Columns      | Expr | Unique | Primary | Exclusion | Tablespace \n--------------------------------------------------------------+------------------+------+--------+---------+-----------+------------\n \"Hypertable_1_with_default_index_enabled_Device_id_Time_idx\" | {Device_id,Time} |      | f      | f       | f         | \n \"Hypertable_1_with_default_index_enabled_Time_idx\"           | {Time}           |      | f      | f       | f         | \n\nROLLBACK;\n--No space partitioning, only time index created\nBEGIN;\nCREATE TABLE PUBLIC.\"Hypertable_1_with_default_index_enabled\" (\n  \"Time\" BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1\n);\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1_with_default_index_enabled\"', 'Time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name |               table_name                | created \n---------------+-------------+-----------------------------------------+---------\n             7 | public      | Hypertable_1_with_default_index_enabled | t\n\nSELECT * FROM test.show_indexes('\"Hypertable_1_with_default_index_enabled\"');\n                       Index                        | Columns | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------------------+---------+------+--------+---------+-----------+------------\n \"Hypertable_1_with_default_index_enabled_Time_idx\" | {Time}  |      | f      | f       | f         | \n\nROLLBACK;\n--Disable index creation: no default indexes created\nBEGIN;\nCREATE TABLE PUBLIC.\"Hypertable_1_with_default_index_enabled\" (\n  \"Time\" BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1\n);\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1_with_default_index_enabled\"', 'Time', 'Device_id', 1, create_default_indexes=>FALSE, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name |               table_name                | created \n---------------+-------------+-----------------------------------------+---------\n             8 | public      | Hypertable_1_with_default_index_enabled | t\n\nSELECT * FROM test.show_indexes('\"Hypertable_1_with_default_index_enabled\"');\n Index | Columns | Expr | Unique | Primary | Exclusion | Tablespace \n-------+---------+------+--------+---------+-----------+------------\n\nROLLBACK;\nSELECT * FROM PUBLIC.\"Hypertable_1\";\n        time         | Device_id | temp_c | humidity | sensor_1 | sensor_2 | sensor_3 | sensor_4 \n---------------------+-----------+--------+----------+----------+----------+----------+----------\n 1257894000000000000 | dev1      |     30 |       70 |        1 |        2 |        3 |      100\n\nSELECT * FROM ONLY PUBLIC.\"Hypertable_1\";\n time | Device_id | temp_c | humidity | sensor_1 | sensor_2 | sensor_3 | sensor_4 \n------+-----------+--------+----------+----------+----------+----------+----------\n\nEXPLAIN (buffers off, costs off) SELECT * FROM ONLY PUBLIC.\"Hypertable_1\";\n--- QUERY PLAN ---\n Seq Scan on \"Hypertable_1\"\n\nSELECT * FROM test.show_columns('PUBLIC.\"Hypertable_1\"');\n  Column   |  Type   | NotNull \n-----------+---------+---------\n time      | bigint  | t\n Device_id | text    | t\n temp_c    | integer | t\n humidity  | numeric | f\n sensor_1  | numeric | f\n sensor_2  | numeric | t\n sensor_3  | numeric | t\n sensor_4  | numeric | t\n\nSELECT * FROM test.show_columns('_timescaledb_internal._hyper_1_1_chunk');\n  Column   |  Type   | NotNull \n-----------+---------+---------\n time      | bigint  | t\n Device_id | text    | t\n temp_c    | integer | t\n humidity  | numeric | f\n sensor_1  | numeric | f\n sensor_2  | numeric | t\n sensor_3  | numeric | t\n sensor_4  | numeric | t\n\n\\ir include/ddl_ops_2.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nALTER TABLE PUBLIC.\"Hypertable_1\" ADD COLUMN temp_f INTEGER NOT NULL DEFAULT 31;\nALTER TABLE PUBLIC.\"Hypertable_1\" DROP COLUMN temp_c;\nALTER TABLE PUBLIC.\"Hypertable_1\" DROP COLUMN sensor_4;\nALTER TABLE PUBLIC.\"Hypertable_1\" ALTER COLUMN humidity SET DEFAULT 100;\nALTER TABLE PUBLIC.\"Hypertable_1\" ALTER COLUMN sensor_1 DROP DEFAULT;\nALTER TABLE PUBLIC.\"Hypertable_1\" ALTER COLUMN sensor_2 SET DEFAULT NULL;\nALTER TABLE PUBLIC.\"Hypertable_1\" ALTER COLUMN sensor_1 SET NOT NULL;\nALTER TABLE PUBLIC.\"Hypertable_1\" ALTER COLUMN sensor_2 DROP NOT NULL;\nALTER TABLE PUBLIC.\"Hypertable_1\" RENAME COLUMN sensor_2 TO sensor_2_renamed;\nALTER TABLE PUBLIC.\"Hypertable_1\" RENAME COLUMN sensor_3 TO sensor_3_renamed;\nDROP INDEX \"ind_sensor_1\";\nCREATE OR REPLACE FUNCTION empty_trigger_func()\n    RETURNS TRIGGER LANGUAGE PLPGSQL AS\n$BODY$\nBEGIN\nEND\n$BODY$;\nCREATE TRIGGER test_trigger BEFORE UPDATE OR DELETE ON PUBLIC.\"Hypertable_1\"\nFOR EACH STATEMENT EXECUTE FUNCTION empty_trigger_func();\nALTER TABLE PUBLIC.\"Hypertable_1\" ALTER COLUMN sensor_2_renamed SET DATA TYPE int;\nALTER INDEX \"ind_humidity\" RENAME TO \"ind_humdity2\";\n-- Change should be reflected here\nSELECT * FROM test.show_indexesp('%.%');\n             Table             |                      Index                       |     Columns      | Expr | Unique | Primary | Exclusion | Tablespace \n-------------------------------+--------------------------------------------------+------------------+------+--------+---------+-----------+------------\n \"customSchema\".\"Hypertable_1\" | \"customSchema\".\"Hypertable_1_time_Device_id_idx\" | {time,Device_id} |      | f      | f       | f         | \n \"customSchema\".\"Hypertable_1\" | \"customSchema\".\"Hypertable_1_time_idx\"           | {time}           |      | f      | f       | f         | \n \"customSchema\".\"Hypertable_1\" | \"customSchema\".\"Unique1\"                         | {time}           |      | t      | f       | f         | \n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%');\n                 Table                  |                                  Index                                   |     Columns      | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------+--------------------------------------------------------------------------+------------------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_Hypertable_1_time_Device_id_idx\" | {time,Device_id} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_Hypertable_1_time_idx\"           | {time}           |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_Hypertable_1_Device_id_time_idx\" | {Device_id,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_ind_humdity2                      | {time,humidity}  |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_Unique1\"                         | {time,Device_id} |      | t      | f       | f         | \n _timescaledb_internal._hyper_2_2_chunk | _timescaledb_internal.\"_hyper_2_2_chunk_Hypertable_1_time_Device_id_idx\" | {time,Device_id} |      | f      | f       | f         | \n _timescaledb_internal._hyper_2_2_chunk | _timescaledb_internal.\"_hyper_2_2_chunk_Hypertable_1_time_idx\"           | {time}           |      | f      | f       | f         | \n _timescaledb_internal._hyper_2_2_chunk | _timescaledb_internal.\"_hyper_2_2_chunk_Unique1\"                         | {time}           |      | t      | f       | f         | \n\n--create column with same name as previously renamed one\nALTER TABLE PUBLIC.\"Hypertable_1\" ADD COLUMN sensor_3 BIGINT NOT NULL DEFAULT 131;\n--create column with same name as previously dropped one\nALTER TABLE PUBLIC.\"Hypertable_1\" ADD COLUMN sensor_4 BIGINT NOT NULL DEFAULT 131;\nSELECT * FROM test.show_columns('PUBLIC.\"Hypertable_1\"');\n      Column      |  Type   | NotNull \n------------------+---------+---------\n time             | bigint  | t\n Device_id        | text    | t\n humidity         | numeric | f\n sensor_1         | numeric | t\n sensor_2_renamed | integer | f\n sensor_3_renamed | numeric | t\n temp_f           | integer | t\n sensor_3         | bigint  | t\n sensor_4         | bigint  | t\n\nSELECT * FROM test.show_columns('_timescaledb_internal._hyper_1_1_chunk');\n      Column      |  Type   | NotNull \n------------------+---------+---------\n time             | bigint  | t\n Device_id        | text    | t\n humidity         | numeric | f\n sensor_1         | numeric | t\n sensor_2_renamed | integer | f\n sensor_3_renamed | numeric | t\n temp_f           | integer | t\n sensor_3         | bigint  | t\n sensor_4         | bigint  | t\n\nSELECT * FROM PUBLIC.\"Hypertable_1\";\n        time         | Device_id | humidity | sensor_1 | sensor_2_renamed | sensor_3_renamed | temp_f | sensor_3 | sensor_4 \n---------------------+-----------+----------+----------+------------------+------------------+--------+----------+----------\n 1257894000000000000 | dev1      |       70 |        1 |                2 |                3 |     31 |      131 |      131\n\n-- alter column tests\nCREATE TABLE alter_test(time timestamptz, temp float, color varchar(10));\n-- create hypertable with two chunks\nSELECT create_hypertable('alter_test', 'time', 'color', 2, chunk_time_interval => 2628000000000);\nWARNING:  column type \"character varying\" used for \"color\" does not follow best practices\n    create_hypertable    \n-------------------------\n (9,public,alter_test,t)\n\nINSERT INTO alter_test VALUES ('2017-01-20T09:00:01', 17.5, 'blue'),\n                              ('2017-01-21T09:00:01', 19.1, 'yellow'),\n                              ('2017-04-20T09:00:01', 89.5, 'green'),\n                              ('2017-04-21T09:00:01', 17.1, 'black');\nSELECT * FROM test.show_columns('alter_test');\n Column |           Type           | NotNull \n--------+--------------------------+---------\n time   | timestamp with time zone | t\n temp   | double precision         | f\n color  | character varying        | f\n\nSELECT * FROM test.show_columnsp('_timescaledb_internal._hyper_9_%chunk');\n                Relation                | Kind | Column |       Column type        | NotNull \n----------------------------------------+------+--------+--------------------------+---------\n _timescaledb_internal._hyper_9_3_chunk | r    | time   | timestamp with time zone | t\n _timescaledb_internal._hyper_9_3_chunk | r    | temp   | double precision         | f\n _timescaledb_internal._hyper_9_3_chunk | r    | color  | character varying        | f\n _timescaledb_internal._hyper_9_4_chunk | r    | time   | timestamp with time zone | t\n _timescaledb_internal._hyper_9_4_chunk | r    | temp   | double precision         | f\n _timescaledb_internal._hyper_9_4_chunk | r    | color  | character varying        | f\n _timescaledb_internal._hyper_9_5_chunk | r    | time   | timestamp with time zone | t\n _timescaledb_internal._hyper_9_5_chunk | r    | temp   | double precision         | f\n _timescaledb_internal._hyper_9_5_chunk | r    | color  | character varying        | f\n\n-- show the column name and type of the partitioning dimension in the\n-- metadata table\nSELECT * FROM _timescaledb_catalog.dimension WHERE hypertable_id = 9;\n id | hypertable_id | column_name |       column_type        | aligned | num_slices | partitioning_func_schema | partitioning_func  | interval_length | compress_interval_length | integer_now_func_schema | integer_now_func \n----+---------------+-------------+--------------------------+---------+------------+--------------------------+--------------------+-----------------+--------------------------+-------------------------+------------------\n 15 |             9 | color       | character varying        | f       |          2 | _timescaledb_functions   | get_partition_hash |                 |                          |                         | \n 14 |             9 | time        | timestamp with time zone | t       |            |                          |                    |   2628000000000 |                          |                         | \n\nEXPLAIN (buffers off, costs off)\nSELECT * FROM alter_test WHERE time > '2017-05-20T10:00:01';\n--- QUERY PLAN ---\n Append\n   ->  Index Scan using _hyper_9_4_chunk_alter_test_time_idx on _hyper_9_4_chunk\n         Index Cond: (\"time\" > 'Sat May 20 10:00:01 2017 PDT'::timestamp with time zone)\n   ->  Index Scan using _hyper_9_5_chunk_alter_test_time_idx on _hyper_9_5_chunk\n         Index Cond: (\"time\" > 'Sat May 20 10:00:01 2017 PDT'::timestamp with time zone)\n\n-- rename column and change its type\nALTER TABLE alter_test RENAME COLUMN time TO time_us;\n--converting timestamptz->timestamp should happen under UTC\nSET timezone = 'UTC';\nALTER TABLE alter_test ALTER COLUMN time_us TYPE timestamp;\nRESET timezone;\nALTER TABLE alter_test RENAME COLUMN color TO colorname;\n\\set ON_ERROR_STOP 0\n-- Changing types on hash-partitioned columns is not safe for some\n-- types and is therefore blocked.\nALTER TABLE alter_test ALTER COLUMN colorname TYPE text;\nERROR:  cannot change the type of a hash-partitioned column\n\\set ON_ERROR_STOP 1\nSELECT * FROM test.show_columns('alter_test');\n  Column   |            Type             | NotNull \n-----------+-----------------------------+---------\n time_us   | timestamp without time zone | t\n temp      | double precision            | f\n colorname | character varying           | f\n\nSELECT * FROM test.show_columnsp('_timescaledb_internal._hyper_9_%chunk');\n                Relation                | Kind |  Column   |         Column type         | NotNull \n----------------------------------------+------+-----------+-----------------------------+---------\n _timescaledb_internal._hyper_9_3_chunk | r    | time_us   | timestamp without time zone | t\n _timescaledb_internal._hyper_9_3_chunk | r    | temp      | double precision            | f\n _timescaledb_internal._hyper_9_3_chunk | r    | colorname | character varying           | f\n _timescaledb_internal._hyper_9_4_chunk | r    | time_us   | timestamp without time zone | t\n _timescaledb_internal._hyper_9_4_chunk | r    | temp      | double precision            | f\n _timescaledb_internal._hyper_9_4_chunk | r    | colorname | character varying           | f\n _timescaledb_internal._hyper_9_5_chunk | r    | time_us   | timestamp without time zone | t\n _timescaledb_internal._hyper_9_5_chunk | r    | temp      | double precision            | f\n _timescaledb_internal._hyper_9_5_chunk | r    | colorname | character varying           | f\n\n-- show that the metadata has been updated\nSELECT * FROM _timescaledb_catalog.dimension WHERE hypertable_id = 9;\n id | hypertable_id | column_name |         column_type         | aligned | num_slices | partitioning_func_schema | partitioning_func  | interval_length | compress_interval_length | integer_now_func_schema | integer_now_func \n----+---------------+-------------+-----------------------------+---------+------------+--------------------------+--------------------+-----------------+--------------------------+-------------------------+------------------\n 15 |             9 | colorname   | character varying           | f       |          2 | _timescaledb_functions   | get_partition_hash |                 |                          |                         | \n 14 |             9 | time_us     | timestamp without time zone | t       |            |                          |                    |   2628000000000 |                          |                         | \n\n-- constraint exclusion should still work with updated column\nEXPLAIN (buffers off, costs off)\nSELECT * FROM alter_test WHERE time_us > '2017-05-20T10:00:01';\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on _hyper_9_4_chunk\n         Filter: (time_us > 'Sat May 20 10:00:01 2017'::timestamp without time zone)\n   ->  Seq Scan on _hyper_9_5_chunk\n         Filter: (time_us > 'Sat May 20 10:00:01 2017'::timestamp without time zone)\n\n\\set ON_ERROR_STOP 0\n-- verify that we cannot change the column type to something incompatible\nALTER TABLE alter_test ALTER COLUMN colorname TYPE varchar(3);\nERROR:  cannot change the type of a hash-partitioned column\n-- conversion that messes up partitioning fails\nALTER TABLE alter_test ALTER COLUMN time_us TYPE timestamptz USING time_us::timestamptz+INTERVAL '1 year';\nERROR:  check constraint \"constraint_4\" of relation \"_hyper_9_3_chunk\" is violated by some row\n-- dropping column that messes up partiitoning fails\nALTER TABLE alter_test DROP COLUMN colorname;\nERROR:  cannot drop column named in partition key\n--ONLY blocked\nALTER TABLE ONLY alter_test RENAME COLUMN colorname TO colorname2;\nERROR:  inherited column \"colorname\" must be renamed in child tables too\nALTER TABLE ONLY alter_test ALTER COLUMN colorname TYPE varchar(10);\nERROR:  ONLY option not supported on hypertable operations\n\\set ON_ERROR_STOP 1\nCREATE TABLE alter_test_bigint(time bigint, temp float);\nSELECT create_hypertable('alter_test_bigint', 'time', chunk_time_interval => 2628000000000);\n        create_hypertable        \n---------------------------------\n (10,public,alter_test_bigint,t)\n\n\\set ON_ERROR_STOP 0\n-- Changing type of time dimension to a non-supported type\n-- shall not be allowed\nALTER TABLE alter_test_bigint\nALTER COLUMN time TYPE TEXT;\nERROR:  cannot change data type of hypertable column \"time\" from bigint to text\n-- dropping open time dimension shall not be allowed.\nALTER TABLE alter_test_bigint\nDROP COLUMN time;\nERROR:  cannot drop column named in partition key\n\\set ON_ERROR_STOP 1\n-- test expression index creation where physical layout of chunks differs from hypertable\nCREATE TABLE i2504(time timestamp NOT NULL, a int, b int, c int, d int);\nselect create_hypertable('i2504', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n  create_hypertable  \n---------------------\n (11,public,i2504,t)\n\nINSERT INTO i2504 VALUES (now(), 1, 2, 3, 4);\nALTER TABLE i2504 DROP COLUMN b;\nINSERT INTO i2504(time, a, c, d) VALUES\n(now() - interval '1 year', 1, 2, 3),\n(now() - interval '2 years', 1, 2, 3);\nCREATE INDEX idx2 ON i2504(a,d) WHERE c IS NOT NULL;\nDROP INDEX idx2;\nCREATE INDEX idx2 ON i2504(a,d) WITH (timescaledb.transaction_per_chunk) WHERE c IS NOT NULL;\n-- Make sure custom composite types are supported as dimensions\nCREATE TYPE TUPLE as (val1 int4, val2 int4);\nCREATE TABLE part_custom_dim (time TIMESTAMPTZ, combo TUPLE, device TEXT);\n\\set ON_ERROR_STOP 0\n-- should fail on PG < 14 because no partitioning function supplied and the given custom type\n-- has no default hash function\n-- on PG14 custom types are hashable\nSELECT create_hypertable('part_custom_dim', 'time', 'combo', 4);\n       create_hypertable       \n-------------------------------\n (12,public,part_custom_dim,t)\n\n\\set ON_ERROR_STOP 1\n-- immutable functions with sub-transaction (issue #4489)\nCREATE FUNCTION i4489(value TEXT DEFAULT '') RETURNS INTEGER\nAS\n$$\nBEGIN\n  RETURN value::INTEGER;\nEXCEPTION WHEN invalid_text_representation THEN\n  RETURN 0;\nEND;\n$$\nLANGUAGE PLPGSQL IMMUTABLE;\n-- should return 1 (one) in both cases\nSELECT i4489('1'), i4489('1');\n i4489 | i4489 \n-------+-------\n     1 |     1\n\n-- should return 0 (zero) in all cases handled by the exception\nSELECT i4489(), i4489();\n i4489 | i4489 \n-------+-------\n     0 |     0\n\nSELECT i4489('a'), i4489('a');\n i4489 | i4489 \n-------+-------\n     0 |     0\n\n-- test ALTER TABLE ONLY for hypertables\nCREATE TABLE at_test(time timestamptz) WITH (tsdb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\n-- adding column only on the parent table should be blocked\n\\set ON_ERROR_STOP 0\nALTER TABLE ONLY at_test ADD COLUMN value INT;\nERROR:  ONLY option not supported on hypertable operations\n\\set ON_ERROR_STOP 1\nALTER TABLE ONLY at_test SET (autovacuum_enabled = false);\nALTER TABLE ONLY at_test RESET (autovacuum_enabled);\n-- test again after creating some chunks\nINSERT INTO at_test VALUES ('2025-01-01');\nINSERT INTO at_test VALUES ('2025-02-01');\nALTER TABLE ONLY at_test SET (autovacuum_enabled = false);\nALTER TABLE ONLY at_test RESET (autovacuum_enabled);\n-- test DDL inside function\nCREATE OR REPLACE FUNCTION ddl_function() RETURNS VOID LANGUAGE PLPGSQL AS $$\nBEGIN\n  DROP TABLE IF EXISTS func_table;\n  CREATE TABLE func_table(time timestamptz) WITH (tsdb.hypertable);\nEND\n$$;\nSELECT ddl_function();\nNOTICE:  table \"func_table\" does not exist, skipping\nNOTICE:  using column \"time\" as partitioning column\n ddl_function \n--------------\n \n\nSELECT hypertable_name from timescaledb_information.hypertables WHERE hypertable_name='func_table';\n hypertable_name \n-----------------\n func_table\n\nSELECT ddl_function();\nNOTICE:  using column \"time\" as partitioning column\n ddl_function \n--------------\n \n\nSELECT hypertable_name from timescaledb_information.hypertables WHERE hypertable_name='func_table';\n hypertable_name \n-----------------\n func_table\n\n"
  },
  {
    "path": "test/expected/ddl_errors.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE PUBLIC.\"Hypertable_1\" (\n  time BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  temp_c int NOT NULL DEFAULT -1\n);\nCREATE INDEX ON PUBLIC.\"Hypertable_1\" (time, \"Device_id\");\n-- Default integer interval is supported as part of\n-- hypertable generalization, verify additional secnarios\n\\set ON_ERROR_STOP 0\nSELECT * FROM create_hypertable(NULL, NULL);\nERROR:  relation cannot be NULL\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1\"', NULL);\nERROR:  partition column cannot be NULL\n-- space dimensions require explicit number of partitions\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1\"', 'time', 'Device_id', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nERROR:  invalid number of partitions for dimension \"Device_id\"\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1_mispelled\"', 'time', 'Device_id', 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nERROR:  relation \"public.Hypertable_1_mispelled\" does not exist at character 33\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1\"', 'time_mispelled', 'Device_id', 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nERROR:  column \"time_mispelled\" does not exist\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1\"', 'Device_id', 'Device_id', 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nERROR:  invalid type for dimension \"Device_id\"\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1\"', 'time', 'Device_id_mispelled', 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nERROR:  column \"Device_id_mispelled\" does not exist\nINSERT INTO PUBLIC.\"Hypertable_1\" VALUES(1,'dev_1', 3);\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1\"', 'time', 'Device_id', 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nERROR:  table \"Hypertable_1\" is not empty\nDELETE FROM  PUBLIC.\"Hypertable_1\" ;\n\\set ON_ERROR_STOP 1\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1\"', 'time', 'Device_id', 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name |  table_name  | created \n---------------+-------------+--------------+---------\n             1 | public      | Hypertable_1 | t\n\n\\set ON_ERROR_STOP 0\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1\"', 'time', 'Device_id', 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nERROR:  table \"Hypertable_1\" is already a hypertable\n\\set ON_ERROR_STOP 1\nINSERT INTO \"Hypertable_1\" VALUES (0, 1, 0);\n\\set ON_ERROR_STOP 0\nALTER TABLE _timescaledb_internal._hyper_1_1_chunk ALTER COLUMN temp_c DROP NOT NULL;\nERROR:  operation not supported on chunk tables\n\\set ON_ERROR_STOP 1\nCREATE TABLE PUBLIC.\"Parent\" (\n  time BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  temp_c int NOT NULL DEFAULT -1\n);\n\\set ON_ERROR_STOP 0\nALTER TABLE \"Hypertable_1\" INHERIT \"Parent\";\nERROR:  hypertables do not support inheritance\nALTER TABLE _timescaledb_internal._hyper_1_1_chunk INHERIT \"Parent\";\nERROR:  operation not supported on chunk tables\nALTER TABLE _timescaledb_internal._hyper_1_1_chunk NO INHERIT \"Parent\";\nERROR:  operation not supported on chunk tables\n\\set ON_ERROR_STOP 1\nCREATE TABLE PUBLIC.\"Child\" () INHERITS (\"Parent\");\n\\set ON_ERROR_STOP 0\nSELECT * FROM create_hypertable('\"public\".\"Parent\"', 'time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nERROR:  table \"Parent\" is already partitioned\nSELECT * FROM create_hypertable('\"public\".\"Child\"', 'time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nERROR:  table \"Child\" is already partitioned\n\\set ON_ERROR_STOP 1\n\\set ON_ERROR_STOP 0\nCREATE TEMPORARY TABLE temp_table (time timestamptz) WITH (tsdb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nERROR:  table \"temp_table\" cannot be temporary\n\\set ON_ERROR_STOP 1\nCREATE TEMP TABLE \"Hypertable_temp\" (\n  time BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  temp_c int NOT NULL DEFAULT -1\n);\n\\set ON_ERROR_STOP 0\nSELECT * FROM create_hypertable('\"Hypertable_temp\"', 'time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nERROR:  table \"Hypertable_temp\" cannot be temporary\nALTER TABLE \"Hypertable_1\" SET UNLOGGED;\n\\set ON_ERROR_STOP 1\nALTER TABLE \"Hypertable_1\" SET LOGGED;\nCREATE TABLE PUBLIC.\"Hypertable_1_rule\" (\n  time BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  temp_c int NOT NULL DEFAULT -1\n);\nCREATE RULE notify_me AS ON UPDATE TO \"Hypertable_1_rule\" DO ALSO NOTIFY \"Hypertable_1_rule\";\n\\set ON_ERROR_STOP 0\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1_rule\"', 'time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nERROR:  hypertables do not support rules\n\\set ON_ERROR_STOP 1\nALTER TABLE \"Hypertable_1_rule\" DISABLE RULE notify_me;\n\\set ON_ERROR_STOP 0\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1_rule\"', 'time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nERROR:  hypertables do not support rules\n\\set ON_ERROR_STOP 1\nDROP RULE notify_me ON \"Hypertable_1_rule\";\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1_rule\"', 'time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name |    table_name     | created \n---------------+-------------+-------------------+---------\n             3 | public      | Hypertable_1_rule | t\n\n\\set ON_ERROR_STOP 0\nCREATE RULE notify_me AS ON UPDATE TO \"Hypertable_1_rule\" DO ALSO NOTIFY \"Hypertable_1_rule\";\nERROR:  hypertables do not support rules\n\\set ON_ERROR_STOP 1\n\\set ON_ERROR_STOP 0\nSELECT add_dimension(NULL,NULL);\nERROR:  hypertable cannot be NULL\n\\set ON_ERROR_STOP 1\n\\set ON_ERROR_STOP 0\nSELECT attach_tablespace(NULL,NULL);\nERROR:  invalid tablespace name\n\\set ON_ERROR_STOP 1\n\\set ON_ERROR_STOP 0\nselect set_number_partitions(NULL,NULL);\nERROR:  hypertable cannot be NULL\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "test/expected/ddl_extra.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE OR REPLACE FUNCTION show_columns_ext(rel regclass)\nRETURNS TABLE(\"Column\" name,\n              \"Type\" text,\n              \"NotNull\" boolean,\n              \"Compression\" text) LANGUAGE SQL STABLE AS\n$BODY$\n    SELECT a.attname,\n    format_type(t.oid, t.typtypmod),\n    a.attnotnull,\n    (CASE WHEN a.attcompression = 'l' THEN 'lz4' WHEN a.attcompression = 'p' THEN 'pglz' ELSE '' END)\n    FROM pg_attribute a, pg_type t\n    WHERE a.attrelid = rel\n    AND a.atttypid = t.oid\n    AND a.attnum >= 0\n    ORDER BY a.attnum;\n$BODY$;\nCREATE TABLE conditions (\n  time TIMESTAMP NOT NULL,\n  location TEXT NOT NULL,\n  temperature DOUBLE PRECISION NULL,\n  humidity DOUBLE PRECISION NULL\n);\nSELECT create_hypertable('conditions', 'time', chunk_time_interval := '1 day'::interval);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n    create_hypertable    \n-------------------------\n (1,public,conditions,t)\n\nINSERT INTO conditions\nSELECT generate_series('2021-10-10 00:00'::timestamp, '2021-10-11 00:00'::timestamp, '1 day'), 'POR', 55, 75;\nCREATE VIEW t AS\n    SELECT 'conditions'::regclass AS r\n    UNION ALL\n    SELECT * FROM show_chunks('conditions');\nSELECT * FROM t, LATERAL show_columns_ext(r) WHERE \"Column\" = 'location' ORDER BY 1, 2;\n                   r                    |  Column  | Type | NotNull | Compression \n----------------------------------------+----------+------+---------+-------------\n conditions                             | location | text | t       | \n _timescaledb_internal._hyper_1_1_chunk | location | text | t       | \n _timescaledb_internal._hyper_1_2_chunk | location | text | t       | \n\nALTER TABLE conditions ALTER COLUMN location SET COMPRESSION pglz;\nSELECT * FROM t, LATERAL show_columns_ext(r) WHERE \"Column\" = 'location' ORDER BY 1, 2;\n                   r                    |  Column  | Type | NotNull | Compression \n----------------------------------------+----------+------+---------+-------------\n conditions                             | location | text | t       | pglz\n _timescaledb_internal._hyper_1_1_chunk | location | text | t       | pglz\n _timescaledb_internal._hyper_1_2_chunk | location | text | t       | pglz\n\nINSERT INTO conditions VALUES ('2021-10-12 00:00'::timestamp, 'BRA', 66, 77);\nSELECT * FROM t, LATERAL show_columns_ext(r) WHERE \"Column\" = 'location' ORDER BY 1, 2;\n                   r                    |  Column  | Type | NotNull | Compression \n----------------------------------------+----------+------+---------+-------------\n conditions                             | location | text | t       | pglz\n _timescaledb_internal._hyper_1_1_chunk | location | text | t       | pglz\n _timescaledb_internal._hyper_1_2_chunk | location | text | t       | pglz\n _timescaledb_internal._hyper_1_3_chunk | location | text | t       | pglz\n\nALTER TABLE conditions ALTER COLUMN location SET COMPRESSION default;\nSELECT * FROM t, LATERAL show_columns_ext(r) WHERE \"Column\" = 'location' ORDER BY 1, 2;\n                   r                    |  Column  | Type | NotNull | Compression \n----------------------------------------+----------+------+---------+-------------\n conditions                             | location | text | t       | \n _timescaledb_internal._hyper_1_1_chunk | location | text | t       | \n _timescaledb_internal._hyper_1_2_chunk | location | text | t       | \n _timescaledb_internal._hyper_1_3_chunk | location | text | t       | \n\n\\set ON_ERROR_STOP 0\n-- failing test because compression is not allowed in \"non-TOASTable\" datatypes\nALTER TABLE conditions ALTER COLUMN temperature SET COMPRESSION pglz;\nERROR:  column data type double precision does not support compression\nSELECT * FROM t, LATERAL show_columns_ext(r) WHERE \"Column\" = 'temperature' ORDER BY 1, 2;\n                   r                    |   Column    |       Type       | NotNull | Compression \n----------------------------------------+-------------+------------------+---------+-------------\n conditions                             | temperature | double precision | f       | \n _timescaledb_internal._hyper_1_1_chunk | temperature | double precision | f       | \n _timescaledb_internal._hyper_1_2_chunk | temperature | double precision | f       | \n _timescaledb_internal._hyper_1_3_chunk | temperature | double precision | f       | \n\n"
  },
  {
    "path": "test/expected/debug_utils.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT _timescaledb_functions.extension_state();\n extension_state \n-----------------\n created\n\nRESET ROLE;\nDO $$\nDECLARE\n    module text;\nBEGIN\n    SELECT probin INTO module FROM pg_proc WHERE proname = 'extension_state' AND pronamespace = '_timescaledb_functions'::regnamespace;\n    EXECUTE format('CREATE FUNCTION extension_state() RETURNS TEXT AS ''%s'', ''ts_extension_get_state'' LANGUAGE C', module);\nEND\n$$;\nDROP EXTENSION timescaledb;\nSELECT * FROM extension_state();\n extension_state \n-----------------\n unknown\n\n\\c\nCREATE EXTENSION timescaledb;\nSELECT * FROM extension_state();\n extension_state \n-----------------\n created\n\n"
  },
  {
    "path": "test/expected/delete.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\o /dev/null\n\\ir include/insert_two_partitions.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE PUBLIC.\"two_Partitions\" (\n  \"timeCustom\" BIGINT NOT NULL,\n  device_id TEXT NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL,\n  series_bool BOOLEAN NULL\n);\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (device_id, \"timeCustom\" DESC NULLS LAST) WHERE device_id IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_0) WHERE series_0 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_1)  WHERE series_1 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_2) WHERE series_2 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_bool) WHERE series_bool IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, device_id);\nSELECT * FROM create_hypertable('\"public\".\"two_Partitions\"'::regclass, 'timeCustom'::name, 'device_id'::name, associated_schema_name=>'_timescaledb_internal'::text, number_partitions => 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\\set QUIET off\nBEGIN;\n\\COPY public.\"two_Partitions\" FROM 'data/ds1_dev1_1.tsv' NULL AS '';\nCOMMIT;\nINSERT INTO public.\"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257987600000000000, 'dev1', 1.5, 1),\n(1257987600000000000, 'dev1', 1.5, 2),\n(1257894000000000000, 'dev2', 1.5, 1),\n(1257894002000000000, 'dev1', 2.5, 3);\nINSERT INTO \"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257894000000000000, 'dev2', 1.5, 2);\n\\set QUIET on\n\\o\nSELECT * FROM \"two_Partitions\" ORDER BY \"timeCustom\", device_id, series_0, series_1;\n     timeCustom      | device_id | series_0 | series_1 | series_2 | series_bool \n---------------------+-----------+----------+----------+----------+-------------\n 1257894000000000000 | dev1      |      1.5 |        1 |        2 | t\n 1257894000000000000 | dev1      |      1.5 |        2 |          | \n 1257894000000000000 | dev2      |      1.5 |        1 |          | \n 1257894000000000000 | dev2      |      1.5 |        2 |          | \n 1257894000000001000 | dev1      |      2.5 |        3 |          | \n 1257894001000000000 | dev1      |      3.5 |        4 |          | \n 1257894002000000000 | dev1      |      2.5 |        3 |          | \n 1257894002000000000 | dev1      |      5.5 |        6 |          | t\n 1257894002000000000 | dev1      |      5.5 |        7 |          | f\n 1257897600000000000 | dev1      |      4.5 |        5 |          | f\n 1257987600000000000 | dev1      |      1.5 |        1 |          | \n 1257987600000000000 | dev1      |      1.5 |        2 |          | \n\nDELETE FROM \"two_Partitions\" WHERE series_0 = 1.5;\nDELETE FROM \"two_Partitions\" WHERE series_0 = 100;\nSELECT * FROM \"two_Partitions\" ORDER BY \"timeCustom\", device_id, series_0, series_1;\n     timeCustom      | device_id | series_0 | series_1 | series_2 | series_bool \n---------------------+-----------+----------+----------+----------+-------------\n 1257894000000001000 | dev1      |      2.5 |        3 |          | \n 1257894001000000000 | dev1      |      3.5 |        4 |          | \n 1257894002000000000 | dev1      |      2.5 |        3 |          | \n 1257894002000000000 | dev1      |      5.5 |        6 |          | t\n 1257894002000000000 | dev1      |      5.5 |        7 |          | f\n 1257897600000000000 | dev1      |      4.5 |        5 |          | f\n\n-- Make sure DELETE isn't optimized if it includes Append plans\n-- Need to turn of nestloop to make append appear the same on PG96 and PG10\nset enable_nestloop = 'off';\nCREATE OR REPLACE FUNCTION series_val()\nRETURNS integer LANGUAGE PLPGSQL STABLE AS\n$BODY$\nBEGIN\n    RETURN 5;\nEND;\n$BODY$;\n-- ConstraintAwareAppend applied for SELECT\nEXPLAIN (buffers off, costs off)\nSELECT FROM \"two_Partitions\"\nWHERE series_1 IN (SELECT series_1 FROM \"two_Partitions\" WHERE series_1 > series_val());\n--- QUERY PLAN ---\n Hash Join\n   Hash Cond: (\"two_Partitions\".series_1 = \"two_Partitions_1\".series_1)\n   ->  Custom Scan (ChunkAppend) on \"two_Partitions\"\n         Chunks excluded during startup: 0\n         ->  Index Only Scan using \"_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx\" on _hyper_1_1_chunk\n               Index Cond: (series_1 > (series_val())::double precision)\n         ->  Index Only Scan using \"_hyper_1_2_chunk_two_Partitions_timeCustom_series_1_idx\" on _hyper_1_2_chunk\n               Index Cond: (series_1 > (series_val())::double precision)\n         ->  Index Only Scan using \"_hyper_1_3_chunk_two_Partitions_timeCustom_series_1_idx\" on _hyper_1_3_chunk\n               Index Cond: (series_1 > (series_val())::double precision)\n         ->  Index Only Scan using \"_hyper_1_4_chunk_two_Partitions_timeCustom_series_1_idx\" on _hyper_1_4_chunk\n               Index Cond: (series_1 > (series_val())::double precision)\n   ->  Hash\n         ->  HashAggregate\n               Group Key: \"two_Partitions_1\".series_1\n               ->  Custom Scan (ChunkAppend) on \"two_Partitions\" \"two_Partitions_1\"\n                     Chunks excluded during startup: 0\n                     ->  Index Only Scan using \"_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx\" on _hyper_1_1_chunk _hyper_1_1_chunk_1\n                           Index Cond: (series_1 > (series_val())::double precision)\n                     ->  Index Only Scan using \"_hyper_1_2_chunk_two_Partitions_timeCustom_series_1_idx\" on _hyper_1_2_chunk _hyper_1_2_chunk_1\n                           Index Cond: (series_1 > (series_val())::double precision)\n                     ->  Index Only Scan using \"_hyper_1_3_chunk_two_Partitions_timeCustom_series_1_idx\" on _hyper_1_3_chunk _hyper_1_3_chunk_1\n                           Index Cond: (series_1 > (series_val())::double precision)\n                     ->  Index Only Scan using \"_hyper_1_4_chunk_two_Partitions_timeCustom_series_1_idx\" on _hyper_1_4_chunk _hyper_1_4_chunk_1\n                           Index Cond: (series_1 > (series_val())::double precision)\n\n-- ConstraintAwareAppend NOT applied for DELETE\nEXPLAIN (buffers off, costs off)\nDELETE FROM \"two_Partitions\"\nWHERE series_1 IN (SELECT series_1 FROM \"two_Partitions\" WHERE series_1 > series_val());\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Delete on \"two_Partitions\"\n         Delete on _hyper_1_1_chunk \"two_Partitions_2\"\n         Delete on _hyper_1_2_chunk \"two_Partitions_3\"\n         Delete on _hyper_1_3_chunk \"two_Partitions_4\"\n         Delete on _hyper_1_4_chunk \"two_Partitions_5\"\n         ->  Hash Join\n               Hash Cond: (\"two_Partitions\".series_1 = \"two_Partitions_1\".series_1)\n               ->  Append\n                     ->  Seq Scan on _hyper_1_1_chunk \"two_Partitions_2\"\n                     ->  Seq Scan on _hyper_1_2_chunk \"two_Partitions_3\"\n                     ->  Seq Scan on _hyper_1_3_chunk \"two_Partitions_4\"\n                     ->  Seq Scan on _hyper_1_4_chunk \"two_Partitions_5\"\n               ->  Hash\n                     ->  HashAggregate\n                           Group Key: \"two_Partitions_1\".series_1\n                           ->  Append\n                                 ->  Index Scan using \"_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx\" on _hyper_1_1_chunk \"two_Partitions_6\"\n                                       Index Cond: (series_1 > (series_val())::double precision)\n                                 ->  Index Scan using \"_hyper_1_2_chunk_two_Partitions_timeCustom_series_1_idx\" on _hyper_1_2_chunk \"two_Partitions_7\"\n                                       Index Cond: (series_1 > (series_val())::double precision)\n                                 ->  Index Scan using \"_hyper_1_3_chunk_two_Partitions_timeCustom_series_1_idx\" on _hyper_1_3_chunk \"two_Partitions_8\"\n                                       Index Cond: (series_1 > (series_val())::double precision)\n                                 ->  Index Scan using \"_hyper_1_4_chunk_two_Partitions_timeCustom_series_1_idx\" on _hyper_1_4_chunk \"two_Partitions_9\"\n                                       Index Cond: (series_1 > (series_val())::double precision)\n\nSELECT * FROM \"two_Partitions\" ORDER BY \"timeCustom\", device_id, series_0, series_1;\n     timeCustom      | device_id | series_0 | series_1 | series_2 | series_bool \n---------------------+-----------+----------+----------+----------+-------------\n 1257894000000001000 | dev1      |      2.5 |        3 |          | \n 1257894001000000000 | dev1      |      3.5 |        4 |          | \n 1257894002000000000 | dev1      |      2.5 |        3 |          | \n 1257894002000000000 | dev1      |      5.5 |        6 |          | t\n 1257894002000000000 | dev1      |      5.5 |        7 |          | f\n 1257897600000000000 | dev1      |      4.5 |        5 |          | f\n\nBEGIN;\nDELETE FROM \"two_Partitions\"\nWHERE series_1 IN (SELECT series_1 FROM \"two_Partitions\" WHERE series_1 > series_val());\nSELECT * FROM \"two_Partitions\" ORDER BY \"timeCustom\", device_id, series_0, series_1;\n     timeCustom      | device_id | series_0 | series_1 | series_2 | series_bool \n---------------------+-----------+----------+----------+----------+-------------\n 1257894000000001000 | dev1      |      2.5 |        3 |          | \n 1257894001000000000 | dev1      |      3.5 |        4 |          | \n 1257894002000000000 | dev1      |      2.5 |        3 |          | \n 1257897600000000000 | dev1      |      4.5 |        5 |          | f\n\nROLLBACK;\nBEGIN;\nDELETE FROM \"two_Partitions\"\nWHERE series_1 IN (SELECT series_1 FROM \"two_Partitions\" WHERE series_1 > series_val()) RETURNING \"timeCustom\";\n     timeCustom      \n---------------------\n 1257894002000000000\n 1257894002000000000\n\nSELECT * FROM \"two_Partitions\" ORDER BY \"timeCustom\", device_id, series_0, series_1;\n     timeCustom      | device_id | series_0 | series_1 | series_2 | series_bool \n---------------------+-----------+----------+----------+----------+-------------\n 1257894000000001000 | dev1      |      2.5 |        3 |          | \n 1257894001000000000 | dev1      |      3.5 |        4 |          | \n 1257894002000000000 | dev1      |      2.5 |        3 |          | \n 1257897600000000000 | dev1      |      4.5 |        5 |          | f\n\nROLLBACK;\n-- test update on chunks directly\nCREATE TABLE direct_delete(time timestamptz) WITH (tsdb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nINSERT INTO direct_delete VALUES ('2020-01-01');\nSELECT show_chunks('direct_delete') AS \"CHUNK\" \\gset\n--should have ModifyHyperable node\nEXPLAIN (costs off, timing off, summary off) DELETE FROM :CHUNK;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Delete on _hyper_2_5_chunk\n         ->  Seq Scan on _hyper_2_5_chunk\n\nEXPLAIN (costs off, timing off, summary off) DELETE FROM ONLY :CHUNK;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Delete on _hyper_2_5_chunk\n         ->  Seq Scan on _hyper_2_5_chunk\n\n-- DELETE should succeed\nBEGIN;\nDELETE FROM :CHUNK RETURNING *;\n             time             \n------------------------------\n Wed Jan 01 00:00:00 2020 PST\n\nROLLBACK;\nBEGIN;\nDELETE FROM ONLY :CHUNK RETURNING *;\n             time             \n------------------------------\n Wed Jan 01 00:00:00 2020 PST\n\nROLLBACK;\n-- Test that EXPLAIN VERBOSE on prepared statements does not corrupt cached plans.\nSET plan_cache_mode = 'force_generic_plan';\nCREATE TABLE explain_verbose_ht( time timestamptz NOT NULL, device int, value float) WITH (tsdb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nINSERT INTO explain_verbose_ht SELECT t, 1, 0.1 FROM generate_series('2026-01-01'::timestamptz, '2026-01-08'::timestamptz, interval '6 hours') t;\n-- Verify the DELETE plan uses ChunkAppend\nEXPLAIN (costs off) DELETE FROM explain_verbose_ht WHERE time > '2025-01-01'::text::timestamptz;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Delete on explain_verbose_ht\n         Delete on _hyper_3_6_chunk explain_verbose_ht_1\n         Delete on _hyper_3_7_chunk explain_verbose_ht_2\n         ->  Custom Scan (ChunkAppend) on explain_verbose_ht\n               Chunks excluded during startup: 0\n               ->  Index Scan using _hyper_3_6_chunk_explain_verbose_ht_time_idx on _hyper_3_6_chunk explain_verbose_ht_1\n                     Index Cond: (\"time\" > ('2025-01-01'::cstring)::timestamp with time zone)\n               ->  Index Scan using _hyper_3_7_chunk_explain_verbose_ht_time_idx on _hyper_3_7_chunk explain_verbose_ht_2\n                     Index Cond: (\"time\" > ('2025-01-01'::cstring)::timestamp with time zone)\n\nPREPARE delete_ht AS DELETE FROM explain_verbose_ht WHERE time > '2025-01-01'::text::timestamptz AND device = 2;\nEXECUTE delete_ht;\nEXPLAIN (verbose, costs off) EXECUTE delete_ht;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Delete on public.explain_verbose_ht\n         Delete on _timescaledb_internal._hyper_3_6_chunk explain_verbose_ht_1\n         Delete on _timescaledb_internal._hyper_3_7_chunk explain_verbose_ht_2\n         ->  Custom Scan (ChunkAppend) on public.explain_verbose_ht\n               Startup Exclusion: true\n               Runtime Exclusion: false\n               Chunks excluded during startup: 0\n               ->  Index Scan using _hyper_3_6_chunk_explain_verbose_ht_time_idx on _timescaledb_internal._hyper_3_6_chunk explain_verbose_ht_1\n                     Output: explain_verbose_ht_1.tableoid, explain_verbose_ht_1.ctid\n                     Index Cond: (explain_verbose_ht_1.\"time\" > ('2025-01-01'::cstring)::timestamp with time zone)\n                     Filter: (explain_verbose_ht_1.device = 2)\n               ->  Index Scan using _hyper_3_7_chunk_explain_verbose_ht_time_idx on _timescaledb_internal._hyper_3_7_chunk explain_verbose_ht_2\n                     Output: explain_verbose_ht_2.tableoid, explain_verbose_ht_2.ctid\n                     Index Cond: (explain_verbose_ht_2.\"time\" > ('2025-01-01'::cstring)::timestamp with time zone)\n                     Filter: (explain_verbose_ht_2.device = 2)\n\nEXECUTE delete_ht;\nDEALLOCATE delete_ht;\n-- repeat test with explain analyze\nPREPARE delete_ht AS DELETE FROM explain_verbose_ht WHERE time > '2025-01-01'::text::timestamptz AND device = 2;\nEXECUTE delete_ht;\nEXPLAIN (verbose, analyze, buffers off, costs off, timing off, summary off) EXECUTE delete_ht;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)\n   ->  Delete on public.explain_verbose_ht (actual rows=0.00 loops=1)\n         Delete on _timescaledb_internal._hyper_3_6_chunk explain_verbose_ht_1\n         Delete on _timescaledb_internal._hyper_3_7_chunk explain_verbose_ht_2\n         ->  Custom Scan (ChunkAppend) on public.explain_verbose_ht (actual rows=0.00 loops=1)\n               Startup Exclusion: true\n               Runtime Exclusion: false\n               Chunks excluded during startup: 0\n               ->  Index Scan using _hyper_3_6_chunk_explain_verbose_ht_time_idx on _timescaledb_internal._hyper_3_6_chunk explain_verbose_ht_1 (actual rows=0.00 loops=1)\n                     Output: explain_verbose_ht_1.tableoid, explain_verbose_ht_1.ctid\n                     Index Cond: (explain_verbose_ht_1.\"time\" > ('2025-01-01'::cstring)::timestamp with time zone)\n                     Filter: (explain_verbose_ht_1.device = 2)\n                     Rows Removed by Filter: 27\n               ->  Index Scan using _hyper_3_7_chunk_explain_verbose_ht_time_idx on _timescaledb_internal._hyper_3_7_chunk explain_verbose_ht_2 (actual rows=0.00 loops=1)\n                     Output: explain_verbose_ht_2.tableoid, explain_verbose_ht_2.ctid\n                     Index Cond: (explain_verbose_ht_2.\"time\" > ('2025-01-01'::cstring)::timestamp with time zone)\n                     Filter: (explain_verbose_ht_2.device = 2)\n                     Rows Removed by Filter: 2\n\nEXECUTE delete_ht;\nDEALLOCATE delete_ht;\nRESET plan_cache_mode;\n-- github issue #6790\n-- test DELETE with WHERE EXISTS on hypertable\nCREATE TABLE i6790(time timestamptz NOT NULL, device int, value float) WITH (tsdb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nINSERT INTO i6790 SELECT t, 1, 0.1 FROM generate_series('2026-01-01'::timestamptz, '2026-01-03'::timestamptz, interval '12 hours') t;\n-- DELETE with simple EXISTS - creates gating Result node wrapping ChunkAppend\nDELETE FROM i6790 WHERE EXISTS (SELECT 1);\n-- all rows should be gone\nSELECT count(*) FROM i6790;\n count \n-------\n     0\n\n-- repopulate for next test\nINSERT INTO i6790 SELECT t, 1, 0.1 FROM generate_series('2026-01-01'::timestamptz, '2026-01-03'::timestamptz, interval '12 hours') t;\n-- DELETE with correlated EXISTS\nDELETE FROM i6790 WHERE EXISTS (SELECT 1 FROM i6790 g WHERE g.device = i6790.device);\nSELECT count(*) FROM i6790;\n count \n-------\n     0\n\n"
  },
  {
    "path": "test/expected/drop_extension.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE drop_test(time timestamp, temp float8, device text);\nSELECT create_hypertable('drop_test', 'time', 'device', 2);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n   create_hypertable    \n------------------------\n (1,public,drop_test,t)\n\nSELECT * FROM _timescaledb_catalog.hypertable;\n id | schema_name | table_name | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------+------------+------------------------+-------------------------+----------------+--------------------------+--------------------------+-------------------+-------------------+--------------------------+--------\n  1 | public      | drop_test  | _timescaledb_internal  | _hyper_1                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n\nINSERT INTO drop_test VALUES('Mon Mar 20 09:17:00.936242 2017', 23.4, 'dev1');\nSELECT * FROM drop_test;\n              time               | temp | device \n---------------------------------+------+--------\n Mon Mar 20 09:17:00.936242 2017 | 23.4 | dev1\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nDROP EXTENSION timescaledb CASCADE;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_1_1_chunk\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n-- Querying the original table should not return any rows since all of\n-- them actually existed in chunks that are now gone\nSELECT * FROM drop_test;\n time | temp | device \n------+------+--------\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- Recreate the extension\nSET client_min_messages=error;\nCREATE EXTENSION timescaledb;\nRESET client_min_messages;\n-- Test that calling twice generates proper error\n\\set ON_ERROR_STOP 0\nCREATE EXTENSION timescaledb;\nERROR:  extension \"timescaledb\" has already been loaded with another version\n\\set ON_ERROR_STOP 1\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n-- CREATE twice with IF NOT EXISTS should be OK\nCREATE EXTENSION IF NOT EXISTS timescaledb;\nNOTICE:  extension \"timescaledb\" already exists, skipping\n-- Make the table a hypertable again\nSELECT create_hypertable('drop_test', 'time', 'device', 2);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n   create_hypertable    \n------------------------\n (1,public,drop_test,t)\n\nSELECT * FROM _timescaledb_catalog.hypertable;\n id | schema_name | table_name | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------+------------+------------------------+-------------------------+----------------+--------------------------+--------------------------+-------------------+-------------------+--------------------------+--------\n  1 | public      | drop_test  | _timescaledb_internal  | _hyper_1                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n\nINSERT INTO drop_test VALUES('Mon Mar 20 09:18:19.100462 2017', 22.1, 'dev1');\nSELECT * FROM drop_test;\n              time               | temp | device \n---------------------------------+------+--------\n Mon Mar 20 09:18:19.100462 2017 | 22.1 | dev1\n\n--test drops thru cascades of other objects\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- Stop background workers to prevent them from interfering with the drop public schema\nSELECT _timescaledb_functions.stop_background_workers();\n stop_background_workers \n-------------------------\n t\n\nSET client_min_messages TO ERROR;\nREVOKE CONNECT ON DATABASE :TEST_DBNAME FROM public;\nSELECT count(pg_terminate_backend(pg_stat_activity.pid)) AS TERMINATED\nFROM pg_stat_activity\nWHERE pg_stat_activity.datname = :'TEST_DBNAME'\nAND pg_stat_activity.pid <> pg_backend_pid() \\gset\nRESET client_min_messages;\n-- drop the public schema and all its objects\nDROP SCHEMA public CASCADE;\nNOTICE:  drop cascades to 3 other objects\n\\dn\n  List of schemas\n Name |   Owner    \n------+------------\n test | super_user\n\n-- Recreate the public schema and extension in the same session.\n-- This should work without requiring a reconnect (issue #5884).\nCREATE SCHEMA public;\nSET client_min_messages=error;\nCREATE EXTENSION timescaledb SCHEMA public;\nRESET client_min_messages;\nSELECT extname FROM pg_extension WHERE extname = 'timescaledb';\n   extname   \n-------------\n timescaledb\n\n-- Verify the extension is functional after re-creation\nCREATE TABLE drop_test2(time timestamptz, temp float8);\nSELECT create_hypertable('drop_test2', 'time');\n    create_hypertable    \n-------------------------\n (1,public,drop_test2,t)\n\nINSERT INTO drop_test2 VALUES('2024-01-01', 23.4);\nSELECT * FROM drop_test2;\n             time             | temp \n------------------------------+------\n Mon Jan 01 00:00:00 2024 PST | 23.4\n\nDROP TABLE drop_test2;\n-- Test that dropping and recreating extension directly also works in the same session\nDROP EXTENSION timescaledb CASCADE;\nSET client_min_messages=error;\nCREATE EXTENSION timescaledb;\nRESET client_min_messages;\nSELECT extname FROM pg_extension WHERE extname = 'timescaledb';\n   extname   \n-------------\n timescaledb\n\n"
  },
  {
    "path": "test/expected/drop_hypertable.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nSELECT * from _timescaledb_catalog.hypertable;\n id | schema_name | table_name | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema | chunk_sizing_func_name | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------+------------+------------------------+-------------------------+----------------+--------------------------+------------------------+-------------------+-------------------+--------------------------+--------\n\nSELECT * from _timescaledb_catalog.dimension;\n id | hypertable_id | column_name | column_type | aligned | num_slices | partitioning_func_schema | partitioning_func | interval_length | compress_interval_length | integer_now_func_schema | integer_now_func \n----+---------------+-------------+-------------+---------+------------+--------------------------+-------------------+-----------------+--------------------------+-------------------------+------------------\n\nCREATE TABLE should_drop (time timestamp, temp float8);\nSELECT create_hypertable('should_drop', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n    create_hypertable     \n--------------------------\n (1,public,should_drop,t)\n\nCREATE TABLE hyper_with_dependencies (time timestamp, temp float8);\nSELECT create_hypertable('hyper_with_dependencies', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n          create_hypertable           \n--------------------------------------\n (2,public,hyper_with_dependencies,t)\n\nCREATE VIEW dependent_view AS SELECT * FROM hyper_with_dependencies;\nINSERT INTO hyper_with_dependencies VALUES (now(), 1.0);\n\\set ON_ERROR_STOP 0\nDROP TABLE hyper_with_dependencies;\nERROR:  cannot drop table hyper_with_dependencies because other objects depend on it\n\\set ON_ERROR_STOP 1\nDROP TABLE hyper_with_dependencies CASCADE;\nNOTICE:  drop cascades to view dependent_view\n-- check that the view is dropped\nSELECT oid FROM pg_class WHERE relname = 'dependent_view';\n oid \n-----\n\nCREATE TABLE chunk_with_dependencies (time timestamp, temp float8);\nSELECT create_hypertable('chunk_with_dependencies', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n          create_hypertable           \n--------------------------------------\n (3,public,chunk_with_dependencies,t)\n\nINSERT INTO chunk_with_dependencies VALUES (now(), 1.0);\nCREATE VIEW dependent_view_chunk AS SELECT * FROM _timescaledb_internal._hyper_3_2_chunk;\n\\set ON_ERROR_STOP 0\nDROP TABLE chunk_with_dependencies;\nERROR:  cannot drop table _timescaledb_internal._hyper_3_2_chunk because other objects depend on it\n\\set ON_ERROR_STOP 1\nDROP TABLE chunk_with_dependencies CASCADE;\nNOTICE:  drop cascades to view dependent_view_chunk\n-- check that the view is dropped\nSELECT oid FROM pg_class WHERE relname = 'dependent_view_chunk';\n oid \n-----\n\n-- Calling create hypertable again will increment hypertable ID\n-- although no new hypertable is created. Make sure we can handle this.\nSELECT create_hypertable('should_drop', 'time', if_not_exists => true);\nNOTICE:  table \"should_drop\" is already a hypertable, skipping\n    create_hypertable     \n--------------------------\n (1,public,should_drop,f)\n\nSELECT * from _timescaledb_catalog.hypertable;\n id | schema_name | table_name  | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------+-------------+------------------------+-------------------------+----------------+--------------------------+--------------------------+-------------------+-------------------+--------------------------+--------\n  1 | public      | should_drop | _timescaledb_internal  | _hyper_1                |              1 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n\nSELECT * from _timescaledb_catalog.dimension;\n id | hypertable_id | column_name |         column_type         | aligned | num_slices | partitioning_func_schema | partitioning_func | interval_length | compress_interval_length | integer_now_func_schema | integer_now_func \n----+---------------+-------------+-----------------------------+---------+------------+--------------------------+-------------------+-----------------+--------------------------+-------------------------+------------------\n  1 |             1 | time        | timestamp without time zone | t       |            |                          |                   |    604800000000 |                          |                         | \n\nDROP TABLE should_drop;\nCREATE TABLE should_drop (time timestamp, temp float8);\nSELECT create_hypertable('should_drop', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n    create_hypertable     \n--------------------------\n (4,public,should_drop,t)\n\nINSERT INTO should_drop VALUES (now(), 1.0);\nSELECT * from _timescaledb_catalog.hypertable;\n id | schema_name | table_name  | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------+-------------+------------------------+-------------------------+----------------+--------------------------+--------------------------+-------------------+-------------------+--------------------------+--------\n  4 | public      | should_drop | _timescaledb_internal  | _hyper_4                |              1 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n\nSELECT * from _timescaledb_catalog.dimension;\n id | hypertable_id | column_name |         column_type         | aligned | num_slices | partitioning_func_schema | partitioning_func | interval_length | compress_interval_length | integer_now_func_schema | integer_now_func \n----+---------------+-------------+-----------------------------+---------+------------+--------------------------+-------------------+-----------------+--------------------------+-------------------------+------------------\n  4 |             4 | time        | timestamp without time zone | t       |            |                          |                   |    604800000000 |                          |                         | \n\n-- test dropping multiple objects at once\nCREATE TABLE t1 (time timestamptz) WITH (tsdb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nINSERT INTO t1 VALUES ('2025-01-01');\nCREATE TABLE t2 (time timestamptz) WITH (tsdb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nINSERT INTO t2 VALUES ('2025-01-01');\nCREATE TABLE t3 (time timestamptz);\nDROP TABLE t1, t2, t3;\n"
  },
  {
    "path": "test/expected/drop_owned-15.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA hypertable_schema;\nGRANT ALL ON SCHEMA hypertable_schema TO :ROLE_DEFAULT_PERM_USER;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nCREATE TABLE hypertable_schema.default_perm_user (time timestamptz, temp float, location int);\nSELECT create_hypertable('hypertable_schema.default_perm_user', 'time', 'location', 2);\n             create_hypertable             \n-------------------------------------------\n (1,hypertable_schema,default_perm_user,t)\n\nINSERT INTO hypertable_schema.default_perm_user VALUES ('2001-01-01 01:01:01', 23.3, 1);\nRESET ROLE;\nCREATE TABLE hypertable_schema.superuser (time timestamptz, temp float, location int);\nSELECT create_hypertable('hypertable_schema.superuser', 'time', 'location', 2);\n         create_hypertable         \n-----------------------------------\n (2,hypertable_schema,superuser,t)\n\nINSERT INTO hypertable_schema.superuser VALUES ('2001-01-01 01:01:01', 23.3, 1);\nSELECT * FROM _timescaledb_catalog.hypertable ORDER BY id;\n id |    schema_name    |    table_name     | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------------+-------------------+------------------------+-------------------------+----------------+--------------------------+--------------------------+-------------------+-------------------+--------------------------+--------\n  1 | hypertable_schema | default_perm_user | _timescaledb_internal  | _hyper_1                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n  2 | hypertable_schema | superuser         | _timescaledb_internal  | _hyper_2                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n id | hypertable_id |      schema_name      |    table_name    | compressed_chunk_id | status | osm_chunk \n----+---------------+-----------------------+------------------+---------------------+--------+-----------\n  1 |             1 | _timescaledb_internal | _hyper_1_1_chunk |                     |      0 | f\n  2 |             2 | _timescaledb_internal | _hyper_2_2_chunk |                     |      0 | f\n\nDROP OWNED BY :ROLE_DEFAULT_PERM_USER;\nSELECT * FROM _timescaledb_catalog.hypertable ORDER BY id;\n id |    schema_name    | table_name | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------------+------------+------------------------+-------------------------+----------------+--------------------------+--------------------------+-------------------+-------------------+--------------------------+--------\n  2 | hypertable_schema | superuser  | _timescaledb_internal  | _hyper_2                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n id | hypertable_id |      schema_name      |    table_name    | compressed_chunk_id | status | osm_chunk \n----+---------------+-----------------------+------------------+---------------------+--------+-----------\n  2 |             2 | _timescaledb_internal | _hyper_2_2_chunk |                     |      0 | f\n\nDROP TABLE  hypertable_schema.superuser;\n--everything should be cleaned up\nSELECT * FROM _timescaledb_catalog.hypertable GROUP BY id;\n id | schema_name | table_name | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema | chunk_sizing_func_name | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------+------------+------------------------+-------------------------+----------------+--------------------------+------------------------+-------------------+-------------------+--------------------------+--------\n\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n id | hypertable_id | schema_name | table_name | compressed_chunk_id | status | osm_chunk \n----+---------------+-------------+------------+---------------------+--------+-----------\n\nSELECT * FROM _timescaledb_catalog.dimension;\n id | hypertable_id | column_name | column_type | aligned | num_slices | partitioning_func_schema | partitioning_func | interval_length | compress_interval_length | integer_now_func_schema | integer_now_func \n----+---------------+-------------+-------------+---------+------------+--------------------------+-------------------+-----------------+--------------------------+-------------------------+------------------\n\nSELECT * FROM _timescaledb_catalog.dimension_slice;\n id | dimension_id | range_start | range_end \n----+--------------+-------------+-----------\n\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\n chunk_id | dimension_slice_id | constraint_name | hypertable_constraint_name \n----------+--------------------+-----------------+----------------------------\n\n-- test drop owned in database without extension installed\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE database test_drop_owned;\n\\c test_drop_owned\nDROP OWNED BY :ROLE_SUPERUSER;\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nDROP DATABASE test_drop_owned WITH (FORCE);\n-- Test that dependencies on roles are added to chunks when creating\n-- new chunks. If that is not done, DROP OWNED BY will not revoke the\n-- privilege on the chunk.\nCREATE TABLE sensor_data(time timestamptz not null, cpu double precision null);\nSELECT * FROM create_hypertable('sensor_data','time');\n hypertable_id | schema_name | table_name  | created \n---------------+-------------+-------------+---------\n             3 | public      | sensor_data | t\n\nINSERT INTO sensor_data\nSELECT time,\n       random() AS cpu\nFROM generate_series('2020-01-01'::timestamptz, '2020-01-24'::timestamptz, INTERVAL '10 minute') AS g1(time);\n\\dp sensor_data\n                                Access privileges\n Schema |    Name     | Type  | Access privileges | Column privileges | Policies \n--------+-------------+-------+-------------------+-------------------+----------\n public | sensor_data | table |                   |                   | \n\n\\dp _timescaledb_internal._hyper_3*\n                                          Access privileges\n        Schema         |       Name       | Type  | Access privileges | Column privileges | Policies \n-----------------------+------------------+-------+-------------------+-------------------+----------\n _timescaledb_internal | _hyper_3_3_chunk | table |                   |                   | \n _timescaledb_internal | _hyper_3_4_chunk | table |                   |                   | \n _timescaledb_internal | _hyper_3_5_chunk | table |                   |                   | \n _timescaledb_internal | _hyper_3_6_chunk | table |                   |                   | \n _timescaledb_internal | _hyper_3_7_chunk | table |                   |                   | \n\nGRANT SELECT ON sensor_data TO :ROLE_DEFAULT_PERM_USER;\n\\dp sensor_data\n                                      Access privileges\n Schema |    Name     | Type  |       Access privileges        | Column privileges | Policies \n--------+-------------+-------+--------------------------------+-------------------+----------\n public | sensor_data | table | super_user=arwdDxt/super_user +|                   | \n        |             |       | default_perm_user=r/super_user |                   | \n\n\\dp _timescaledb_internal._hyper_3*\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_3_3_chunk | table | super_user=arwdDxt/super_user +|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_4_chunk | table | super_user=arwdDxt/super_user +|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_5_chunk | table | super_user=arwdDxt/super_user +|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_6_chunk | table | super_user=arwdDxt/super_user +|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_7_chunk | table | super_user=arwdDxt/super_user +|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n\n-- Insert more chunks after adding the user to the hypertable. These\n-- will now get the privileges of the hypertable.\nINSERT INTO sensor_data\nSELECT time,\n       random() AS cpu\nFROM generate_series('2020-01-20'::timestamptz, '2020-02-05'::timestamptz, INTERVAL '10 minute') AS g1(time);\n\\dp _timescaledb_internal._hyper_3*\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_3_3_chunk | table | super_user=arwdDxt/super_user +|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_4_chunk | table | super_user=arwdDxt/super_user +|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_5_chunk | table | super_user=arwdDxt/super_user +|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_6_chunk | table | super_user=arwdDxt/super_user +|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_7_chunk | table | super_user=arwdDxt/super_user +|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_8_chunk | table | super_user=arwdDxt/super_user +|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n\n-- This should revoke the privileges on both the hypertable and the chunks.\nDROP OWNED BY :ROLE_DEFAULT_PERM_USER;\n\\dp sensor_data\n                                      Access privileges\n Schema |    Name     | Type  |       Access privileges       | Column privileges | Policies \n--------+-------------+-------+-------------------------------+-------------------+----------\n public | sensor_data | table | super_user=arwdDxt/super_user |                   | \n\n\\dp _timescaledb_internal._hyper_3*\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges       | Column privileges | Policies \n-----------------------+------------------+-------+-------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_3_3_chunk | table | super_user=arwdDxt/super_user |                   | \n _timescaledb_internal | _hyper_3_4_chunk | table | super_user=arwdDxt/super_user |                   | \n _timescaledb_internal | _hyper_3_5_chunk | table | super_user=arwdDxt/super_user |                   | \n _timescaledb_internal | _hyper_3_6_chunk | table | super_user=arwdDxt/super_user |                   | \n _timescaledb_internal | _hyper_3_7_chunk | table | super_user=arwdDxt/super_user |                   | \n _timescaledb_internal | _hyper_3_8_chunk | table | super_user=arwdDxt/super_user |                   | \n\n"
  },
  {
    "path": "test/expected/drop_owned-16.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA hypertable_schema;\nGRANT ALL ON SCHEMA hypertable_schema TO :ROLE_DEFAULT_PERM_USER;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nCREATE TABLE hypertable_schema.default_perm_user (time timestamptz, temp float, location int);\nSELECT create_hypertable('hypertable_schema.default_perm_user', 'time', 'location', 2);\n             create_hypertable             \n-------------------------------------------\n (1,hypertable_schema,default_perm_user,t)\n\nINSERT INTO hypertable_schema.default_perm_user VALUES ('2001-01-01 01:01:01', 23.3, 1);\nRESET ROLE;\nCREATE TABLE hypertable_schema.superuser (time timestamptz, temp float, location int);\nSELECT create_hypertable('hypertable_schema.superuser', 'time', 'location', 2);\n         create_hypertable         \n-----------------------------------\n (2,hypertable_schema,superuser,t)\n\nINSERT INTO hypertable_schema.superuser VALUES ('2001-01-01 01:01:01', 23.3, 1);\nSELECT * FROM _timescaledb_catalog.hypertable ORDER BY id;\n id |    schema_name    |    table_name     | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------------+-------------------+------------------------+-------------------------+----------------+--------------------------+--------------------------+-------------------+-------------------+--------------------------+--------\n  1 | hypertable_schema | default_perm_user | _timescaledb_internal  | _hyper_1                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n  2 | hypertable_schema | superuser         | _timescaledb_internal  | _hyper_2                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n id | hypertable_id |      schema_name      |    table_name    | compressed_chunk_id | status | osm_chunk \n----+---------------+-----------------------+------------------+---------------------+--------+-----------\n  1 |             1 | _timescaledb_internal | _hyper_1_1_chunk |                     |      0 | f\n  2 |             2 | _timescaledb_internal | _hyper_2_2_chunk |                     |      0 | f\n\nDROP OWNED BY :ROLE_DEFAULT_PERM_USER;\nSELECT * FROM _timescaledb_catalog.hypertable ORDER BY id;\n id |    schema_name    | table_name | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------------+------------+------------------------+-------------------------+----------------+--------------------------+--------------------------+-------------------+-------------------+--------------------------+--------\n  2 | hypertable_schema | superuser  | _timescaledb_internal  | _hyper_2                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n id | hypertable_id |      schema_name      |    table_name    | compressed_chunk_id | status | osm_chunk \n----+---------------+-----------------------+------------------+---------------------+--------+-----------\n  2 |             2 | _timescaledb_internal | _hyper_2_2_chunk |                     |      0 | f\n\nDROP TABLE  hypertable_schema.superuser;\n--everything should be cleaned up\nSELECT * FROM _timescaledb_catalog.hypertable GROUP BY id;\n id | schema_name | table_name | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema | chunk_sizing_func_name | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------+------------+------------------------+-------------------------+----------------+--------------------------+------------------------+-------------------+-------------------+--------------------------+--------\n\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n id | hypertable_id | schema_name | table_name | compressed_chunk_id | status | osm_chunk \n----+---------------+-------------+------------+---------------------+--------+-----------\n\nSELECT * FROM _timescaledb_catalog.dimension;\n id | hypertable_id | column_name | column_type | aligned | num_slices | partitioning_func_schema | partitioning_func | interval_length | compress_interval_length | integer_now_func_schema | integer_now_func \n----+---------------+-------------+-------------+---------+------------+--------------------------+-------------------+-----------------+--------------------------+-------------------------+------------------\n\nSELECT * FROM _timescaledb_catalog.dimension_slice;\n id | dimension_id | range_start | range_end \n----+--------------+-------------+-----------\n\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\n chunk_id | dimension_slice_id | constraint_name | hypertable_constraint_name \n----------+--------------------+-----------------+----------------------------\n\n-- test drop owned in database without extension installed\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE database test_drop_owned;\n\\c test_drop_owned\nDROP OWNED BY :ROLE_SUPERUSER;\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nDROP DATABASE test_drop_owned WITH (FORCE);\n-- Test that dependencies on roles are added to chunks when creating\n-- new chunks. If that is not done, DROP OWNED BY will not revoke the\n-- privilege on the chunk.\nCREATE TABLE sensor_data(time timestamptz not null, cpu double precision null);\nSELECT * FROM create_hypertable('sensor_data','time');\n hypertable_id | schema_name | table_name  | created \n---------------+-------------+-------------+---------\n             3 | public      | sensor_data | t\n\nINSERT INTO sensor_data\nSELECT time,\n       random() AS cpu\nFROM generate_series('2020-01-01'::timestamptz, '2020-01-24'::timestamptz, INTERVAL '10 minute') AS g1(time);\n\\dp sensor_data\n                                Access privileges\n Schema |    Name     | Type  | Access privileges | Column privileges | Policies \n--------+-------------+-------+-------------------+-------------------+----------\n public | sensor_data | table |                   |                   | \n\n\\dp _timescaledb_internal._hyper_3*\n                                          Access privileges\n        Schema         |       Name       | Type  | Access privileges | Column privileges | Policies \n-----------------------+------------------+-------+-------------------+-------------------+----------\n _timescaledb_internal | _hyper_3_3_chunk | table |                   |                   | \n _timescaledb_internal | _hyper_3_4_chunk | table |                   |                   | \n _timescaledb_internal | _hyper_3_5_chunk | table |                   |                   | \n _timescaledb_internal | _hyper_3_6_chunk | table |                   |                   | \n _timescaledb_internal | _hyper_3_7_chunk | table |                   |                   | \n\nGRANT SELECT ON sensor_data TO :ROLE_DEFAULT_PERM_USER;\n\\dp sensor_data\n                                      Access privileges\n Schema |    Name     | Type  |       Access privileges        | Column privileges | Policies \n--------+-------------+-------+--------------------------------+-------------------+----------\n public | sensor_data | table | super_user=arwdDxt/super_user +|                   | \n        |             |       | default_perm_user=r/super_user |                   | \n\n\\dp _timescaledb_internal._hyper_3*\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_3_3_chunk | table | super_user=arwdDxt/super_user +|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_4_chunk | table | super_user=arwdDxt/super_user +|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_5_chunk | table | super_user=arwdDxt/super_user +|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_6_chunk | table | super_user=arwdDxt/super_user +|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_7_chunk | table | super_user=arwdDxt/super_user +|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n\n-- Insert more chunks after adding the user to the hypertable. These\n-- will now get the privileges of the hypertable.\nINSERT INTO sensor_data\nSELECT time,\n       random() AS cpu\nFROM generate_series('2020-01-20'::timestamptz, '2020-02-05'::timestamptz, INTERVAL '10 minute') AS g1(time);\n\\dp _timescaledb_internal._hyper_3*\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_3_3_chunk | table | super_user=arwdDxt/super_user +|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_4_chunk | table | super_user=arwdDxt/super_user +|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_5_chunk | table | super_user=arwdDxt/super_user +|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_6_chunk | table | super_user=arwdDxt/super_user +|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_7_chunk | table | super_user=arwdDxt/super_user +|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_8_chunk | table | super_user=arwdDxt/super_user +|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n\n-- This should revoke the privileges on both the hypertable and the chunks.\nDROP OWNED BY :ROLE_DEFAULT_PERM_USER;\n\\dp sensor_data\n                                      Access privileges\n Schema |    Name     | Type  |       Access privileges       | Column privileges | Policies \n--------+-------------+-------+-------------------------------+-------------------+----------\n public | sensor_data | table | super_user=arwdDxt/super_user |                   | \n\n\\dp _timescaledb_internal._hyper_3*\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges       | Column privileges | Policies \n-----------------------+------------------+-------+-------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_3_3_chunk | table | super_user=arwdDxt/super_user |                   | \n _timescaledb_internal | _hyper_3_4_chunk | table | super_user=arwdDxt/super_user |                   | \n _timescaledb_internal | _hyper_3_5_chunk | table | super_user=arwdDxt/super_user |                   | \n _timescaledb_internal | _hyper_3_6_chunk | table | super_user=arwdDxt/super_user |                   | \n _timescaledb_internal | _hyper_3_7_chunk | table | super_user=arwdDxt/super_user |                   | \n _timescaledb_internal | _hyper_3_8_chunk | table | super_user=arwdDxt/super_user |                   | \n\n"
  },
  {
    "path": "test/expected/drop_owned-17.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA hypertable_schema;\nGRANT ALL ON SCHEMA hypertable_schema TO :ROLE_DEFAULT_PERM_USER;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nCREATE TABLE hypertable_schema.default_perm_user (time timestamptz, temp float, location int);\nSELECT create_hypertable('hypertable_schema.default_perm_user', 'time', 'location', 2);\n             create_hypertable             \n-------------------------------------------\n (1,hypertable_schema,default_perm_user,t)\n\nINSERT INTO hypertable_schema.default_perm_user VALUES ('2001-01-01 01:01:01', 23.3, 1);\nRESET ROLE;\nCREATE TABLE hypertable_schema.superuser (time timestamptz, temp float, location int);\nSELECT create_hypertable('hypertable_schema.superuser', 'time', 'location', 2);\n         create_hypertable         \n-----------------------------------\n (2,hypertable_schema,superuser,t)\n\nINSERT INTO hypertable_schema.superuser VALUES ('2001-01-01 01:01:01', 23.3, 1);\nSELECT * FROM _timescaledb_catalog.hypertable ORDER BY id;\n id |    schema_name    |    table_name     | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------------+-------------------+------------------------+-------------------------+----------------+--------------------------+--------------------------+-------------------+-------------------+--------------------------+--------\n  1 | hypertable_schema | default_perm_user | _timescaledb_internal  | _hyper_1                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n  2 | hypertable_schema | superuser         | _timescaledb_internal  | _hyper_2                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n id | hypertable_id |      schema_name      |    table_name    | compressed_chunk_id | status | osm_chunk \n----+---------------+-----------------------+------------------+---------------------+--------+-----------\n  1 |             1 | _timescaledb_internal | _hyper_1_1_chunk |                     |      0 | f\n  2 |             2 | _timescaledb_internal | _hyper_2_2_chunk |                     |      0 | f\n\nDROP OWNED BY :ROLE_DEFAULT_PERM_USER;\nSELECT * FROM _timescaledb_catalog.hypertable ORDER BY id;\n id |    schema_name    | table_name | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------------+------------+------------------------+-------------------------+----------------+--------------------------+--------------------------+-------------------+-------------------+--------------------------+--------\n  2 | hypertable_schema | superuser  | _timescaledb_internal  | _hyper_2                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n id | hypertable_id |      schema_name      |    table_name    | compressed_chunk_id | status | osm_chunk \n----+---------------+-----------------------+------------------+---------------------+--------+-----------\n  2 |             2 | _timescaledb_internal | _hyper_2_2_chunk |                     |      0 | f\n\nDROP TABLE  hypertable_schema.superuser;\n--everything should be cleaned up\nSELECT * FROM _timescaledb_catalog.hypertable GROUP BY id;\n id | schema_name | table_name | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema | chunk_sizing_func_name | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------+------------+------------------------+-------------------------+----------------+--------------------------+------------------------+-------------------+-------------------+--------------------------+--------\n\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n id | hypertable_id | schema_name | table_name | compressed_chunk_id | status | osm_chunk \n----+---------------+-------------+------------+---------------------+--------+-----------\n\nSELECT * FROM _timescaledb_catalog.dimension;\n id | hypertable_id | column_name | column_type | aligned | num_slices | partitioning_func_schema | partitioning_func | interval_length | compress_interval_length | integer_now_func_schema | integer_now_func \n----+---------------+-------------+-------------+---------+------------+--------------------------+-------------------+-----------------+--------------------------+-------------------------+------------------\n\nSELECT * FROM _timescaledb_catalog.dimension_slice;\n id | dimension_id | range_start | range_end \n----+--------------+-------------+-----------\n\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\n chunk_id | dimension_slice_id | constraint_name | hypertable_constraint_name \n----------+--------------------+-----------------+----------------------------\n\n-- test drop owned in database without extension installed\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE database test_drop_owned;\n\\c test_drop_owned\nDROP OWNED BY :ROLE_SUPERUSER;\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nDROP DATABASE test_drop_owned WITH (FORCE);\n-- Test that dependencies on roles are added to chunks when creating\n-- new chunks. If that is not done, DROP OWNED BY will not revoke the\n-- privilege on the chunk.\nCREATE TABLE sensor_data(time timestamptz not null, cpu double precision null);\nSELECT * FROM create_hypertable('sensor_data','time');\n hypertable_id | schema_name | table_name  | created \n---------------+-------------+-------------+---------\n             3 | public      | sensor_data | t\n\nINSERT INTO sensor_data\nSELECT time,\n       random() AS cpu\nFROM generate_series('2020-01-01'::timestamptz, '2020-01-24'::timestamptz, INTERVAL '10 minute') AS g1(time);\n\\dp sensor_data\n                                Access privileges\n Schema |    Name     | Type  | Access privileges | Column privileges | Policies \n--------+-------------+-------+-------------------+-------------------+----------\n public | sensor_data | table |                   |                   | \n\n\\dp _timescaledb_internal._hyper_3*\n                                          Access privileges\n        Schema         |       Name       | Type  | Access privileges | Column privileges | Policies \n-----------------------+------------------+-------+-------------------+-------------------+----------\n _timescaledb_internal | _hyper_3_3_chunk | table |                   |                   | \n _timescaledb_internal | _hyper_3_4_chunk | table |                   |                   | \n _timescaledb_internal | _hyper_3_5_chunk | table |                   |                   | \n _timescaledb_internal | _hyper_3_6_chunk | table |                   |                   | \n _timescaledb_internal | _hyper_3_7_chunk | table |                   |                   | \n\nGRANT SELECT ON sensor_data TO :ROLE_DEFAULT_PERM_USER;\n\\dp sensor_data\n                                      Access privileges\n Schema |    Name     | Type  |       Access privileges        | Column privileges | Policies \n--------+-------------+-------+--------------------------------+-------------------+----------\n public | sensor_data | table | super_user=arwdDxtm/super_user+|                   | \n        |             |       | default_perm_user=r/super_user |                   | \n\n\\dp _timescaledb_internal._hyper_3*\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_3_3_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_4_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_5_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_6_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_7_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n\n-- Insert more chunks after adding the user to the hypertable. These\n-- will now get the privileges of the hypertable.\nINSERT INTO sensor_data\nSELECT time,\n       random() AS cpu\nFROM generate_series('2020-01-20'::timestamptz, '2020-02-05'::timestamptz, INTERVAL '10 minute') AS g1(time);\n\\dp _timescaledb_internal._hyper_3*\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_3_3_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_4_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_5_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_6_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_7_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_8_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n\n-- This should revoke the privileges on both the hypertable and the chunks.\nDROP OWNED BY :ROLE_DEFAULT_PERM_USER;\n\\dp sensor_data\n                                      Access privileges\n Schema |    Name     | Type  |       Access privileges        | Column privileges | Policies \n--------+-------------+-------+--------------------------------+-------------------+----------\n public | sensor_data | table | super_user=arwdDxtm/super_user |                   | \n\n\\dp _timescaledb_internal._hyper_3*\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_3_3_chunk | table | super_user=arwdDxtm/super_user |                   | \n _timescaledb_internal | _hyper_3_4_chunk | table | super_user=arwdDxtm/super_user |                   | \n _timescaledb_internal | _hyper_3_5_chunk | table | super_user=arwdDxtm/super_user |                   | \n _timescaledb_internal | _hyper_3_6_chunk | table | super_user=arwdDxtm/super_user |                   | \n _timescaledb_internal | _hyper_3_7_chunk | table | super_user=arwdDxtm/super_user |                   | \n _timescaledb_internal | _hyper_3_8_chunk | table | super_user=arwdDxtm/super_user |                   | \n\n"
  },
  {
    "path": "test/expected/drop_owned-18.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA hypertable_schema;\nGRANT ALL ON SCHEMA hypertable_schema TO :ROLE_DEFAULT_PERM_USER;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nCREATE TABLE hypertable_schema.default_perm_user (time timestamptz, temp float, location int);\nSELECT create_hypertable('hypertable_schema.default_perm_user', 'time', 'location', 2);\n             create_hypertable             \n-------------------------------------------\n (1,hypertable_schema,default_perm_user,t)\n\nINSERT INTO hypertable_schema.default_perm_user VALUES ('2001-01-01 01:01:01', 23.3, 1);\nRESET ROLE;\nCREATE TABLE hypertable_schema.superuser (time timestamptz, temp float, location int);\nSELECT create_hypertable('hypertable_schema.superuser', 'time', 'location', 2);\n         create_hypertable         \n-----------------------------------\n (2,hypertable_schema,superuser,t)\n\nINSERT INTO hypertable_schema.superuser VALUES ('2001-01-01 01:01:01', 23.3, 1);\nSELECT * FROM _timescaledb_catalog.hypertable ORDER BY id;\n id |    schema_name    |    table_name     | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------------+-------------------+------------------------+-------------------------+----------------+--------------------------+--------------------------+-------------------+-------------------+--------------------------+--------\n  1 | hypertable_schema | default_perm_user | _timescaledb_internal  | _hyper_1                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n  2 | hypertable_schema | superuser         | _timescaledb_internal  | _hyper_2                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n id | hypertable_id |      schema_name      |    table_name    | compressed_chunk_id | status | osm_chunk \n----+---------------+-----------------------+------------------+---------------------+--------+-----------\n  1 |             1 | _timescaledb_internal | _hyper_1_1_chunk |                     |      0 | f\n  2 |             2 | _timescaledb_internal | _hyper_2_2_chunk |                     |      0 | f\n\nDROP OWNED BY :ROLE_DEFAULT_PERM_USER;\nSELECT * FROM _timescaledb_catalog.hypertable ORDER BY id;\n id |    schema_name    | table_name | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------------+------------+------------------------+-------------------------+----------------+--------------------------+--------------------------+-------------------+-------------------+--------------------------+--------\n  2 | hypertable_schema | superuser  | _timescaledb_internal  | _hyper_2                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n id | hypertable_id |      schema_name      |    table_name    | compressed_chunk_id | status | osm_chunk \n----+---------------+-----------------------+------------------+---------------------+--------+-----------\n  2 |             2 | _timescaledb_internal | _hyper_2_2_chunk |                     |      0 | f\n\nDROP TABLE  hypertable_schema.superuser;\n--everything should be cleaned up\nSELECT * FROM _timescaledb_catalog.hypertable GROUP BY id;\n id | schema_name | table_name | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema | chunk_sizing_func_name | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------+------------+------------------------+-------------------------+----------------+--------------------------+------------------------+-------------------+-------------------+--------------------------+--------\n\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n id | hypertable_id | schema_name | table_name | compressed_chunk_id | status | osm_chunk \n----+---------------+-------------+------------+---------------------+--------+-----------\n\nSELECT * FROM _timescaledb_catalog.dimension;\n id | hypertable_id | column_name | column_type | aligned | num_slices | partitioning_func_schema | partitioning_func | interval_length | compress_interval_length | integer_now_func_schema | integer_now_func \n----+---------------+-------------+-------------+---------+------------+--------------------------+-------------------+-----------------+--------------------------+-------------------------+------------------\n\nSELECT * FROM _timescaledb_catalog.dimension_slice;\n id | dimension_id | range_start | range_end \n----+--------------+-------------+-----------\n\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\n chunk_id | dimension_slice_id | constraint_name | hypertable_constraint_name \n----------+--------------------+-----------------+----------------------------\n\n-- test drop owned in database without extension installed\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE database test_drop_owned;\n\\c test_drop_owned\nDROP OWNED BY :ROLE_SUPERUSER;\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nDROP DATABASE test_drop_owned WITH (FORCE);\n-- Test that dependencies on roles are added to chunks when creating\n-- new chunks. If that is not done, DROP OWNED BY will not revoke the\n-- privilege on the chunk.\nCREATE TABLE sensor_data(time timestamptz not null, cpu double precision null);\nSELECT * FROM create_hypertable('sensor_data','time');\n hypertable_id | schema_name | table_name  | created \n---------------+-------------+-------------+---------\n             3 | public      | sensor_data | t\n\nINSERT INTO sensor_data\nSELECT time,\n       random() AS cpu\nFROM generate_series('2020-01-01'::timestamptz, '2020-01-24'::timestamptz, INTERVAL '10 minute') AS g1(time);\n\\dp sensor_data\n                                Access privileges\n Schema |    Name     | Type  | Access privileges | Column privileges | Policies \n--------+-------------+-------+-------------------+-------------------+----------\n public | sensor_data | table |                   |                   | \n\n\\dp _timescaledb_internal._hyper_3*\n                                          Access privileges\n        Schema         |       Name       | Type  | Access privileges | Column privileges | Policies \n-----------------------+------------------+-------+-------------------+-------------------+----------\n _timescaledb_internal | _hyper_3_3_chunk | table |                   |                   | \n _timescaledb_internal | _hyper_3_4_chunk | table |                   |                   | \n _timescaledb_internal | _hyper_3_5_chunk | table |                   |                   | \n _timescaledb_internal | _hyper_3_6_chunk | table |                   |                   | \n _timescaledb_internal | _hyper_3_7_chunk | table |                   |                   | \n\nGRANT SELECT ON sensor_data TO :ROLE_DEFAULT_PERM_USER;\n\\dp sensor_data\n                                      Access privileges\n Schema |    Name     | Type  |       Access privileges        | Column privileges | Policies \n--------+-------------+-------+--------------------------------+-------------------+----------\n public | sensor_data | table | super_user=arwdDxtm/super_user+|                   | \n        |             |       | default_perm_user=r/super_user |                   | \n\n\\dp _timescaledb_internal._hyper_3*\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_3_3_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_4_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_5_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_6_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_7_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n\n-- Insert more chunks after adding the user to the hypertable. These\n-- will now get the privileges of the hypertable.\nINSERT INTO sensor_data\nSELECT time,\n       random() AS cpu\nFROM generate_series('2020-01-20'::timestamptz, '2020-02-05'::timestamptz, INTERVAL '10 minute') AS g1(time);\n\\dp _timescaledb_internal._hyper_3*\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_3_3_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_4_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_5_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_6_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_7_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n _timescaledb_internal | _hyper_3_8_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | default_perm_user=r/super_user |                   | \n\n-- This should revoke the privileges on both the hypertable and the chunks.\nDROP OWNED BY :ROLE_DEFAULT_PERM_USER;\n\\dp sensor_data\n                                      Access privileges\n Schema |    Name     | Type  |       Access privileges        | Column privileges | Policies \n--------+-------------+-------+--------------------------------+-------------------+----------\n public | sensor_data | table | super_user=arwdDxtm/super_user |                   | \n\n\\dp _timescaledb_internal._hyper_3*\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_3_3_chunk | table | super_user=arwdDxtm/super_user |                   | \n _timescaledb_internal | _hyper_3_4_chunk | table | super_user=arwdDxtm/super_user |                   | \n _timescaledb_internal | _hyper_3_5_chunk | table | super_user=arwdDxtm/super_user |                   | \n _timescaledb_internal | _hyper_3_6_chunk | table | super_user=arwdDxtm/super_user |                   | \n _timescaledb_internal | _hyper_3_7_chunk | table | super_user=arwdDxtm/super_user |                   | \n _timescaledb_internal | _hyper_3_8_chunk | table | super_user=arwdDxtm/super_user |                   | \n\n"
  },
  {
    "path": "test/expected/drop_rename_hypertable.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\o /dev/null\n\\ir include/insert_two_partitions.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE PUBLIC.\"two_Partitions\" (\n  \"timeCustom\" BIGINT NOT NULL,\n  device_id TEXT NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL,\n  series_bool BOOLEAN NULL\n);\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (device_id, \"timeCustom\" DESC NULLS LAST) WHERE device_id IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_0) WHERE series_0 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_1)  WHERE series_1 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_2) WHERE series_2 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_bool) WHERE series_bool IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, device_id);\nSELECT * FROM create_hypertable('\"public\".\"two_Partitions\"'::regclass, 'timeCustom'::name, 'device_id'::name, associated_schema_name=>'_timescaledb_internal'::text, number_partitions => 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\\set QUIET off\nBEGIN;\n\\COPY public.\"two_Partitions\" FROM 'data/ds1_dev1_1.tsv' NULL AS '';\nCOMMIT;\nINSERT INTO public.\"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257987600000000000, 'dev1', 1.5, 1),\n(1257987600000000000, 'dev1', 1.5, 2),\n(1257894000000000000, 'dev2', 1.5, 1),\n(1257894002000000000, 'dev1', 2.5, 3);\nINSERT INTO \"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257894000000000000, 'dev2', 1.5, 2);\n\\set QUIET on\n\\o\nSELECT * FROM test.show_columnsp('_timescaledb_internal.%_hyper%');\n                                      Relation                                      | Kind |   Column    |   Column type    | NotNull \n------------------------------------------------------------------------------------+------+-------------+------------------+---------\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | timeCustom  | bigint           | t\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | device_id   | text             | t\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | series_0    | double precision | f\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | series_1    | double precision | f\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | series_2    | double precision | f\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | series_bool | boolean          | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_idx\"             | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | series_0    | double precision | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | series_1    | double precision | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | series_2    | double precision | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | series_bool | boolean          | f\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | timeCustom  | bigint           | t\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | device_id   | text             | t\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | series_0    | double precision | f\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | series_1    | double precision | f\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | series_2    | double precision | f\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | series_bool | boolean          | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_idx\"             | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | series_0    | double precision | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | series_1    | double precision | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | series_2    | double precision | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | series_bool | boolean          | f\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | timeCustom  | bigint           | t\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | device_id   | text             | t\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | series_0    | double precision | f\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | series_1    | double precision | f\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | series_2    | double precision | f\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | series_bool | boolean          | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_idx\"             | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | series_0    | double precision | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | series_1    | double precision | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | series_2    | double precision | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | series_bool | boolean          | f\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | timeCustom  | bigint           | t\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | device_id   | text             | t\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | series_0    | double precision | f\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | series_1    | double precision | f\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | series_2    | double precision | f\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | series_bool | boolean          | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_idx\"             | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | series_0    | double precision | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | series_1    | double precision | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | series_2    | double precision | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | series_bool | boolean          | f\n\n-- Test that renaming hypertable works\nSELECT * FROM test.show_columns('_timescaledb_internal._hyper_1_1_chunk');\n   Column    |       Type       | NotNull \n-------------+------------------+---------\n timeCustom  | bigint           | t\n device_id   | text             | t\n series_0    | double precision | f\n series_1    | double precision | f\n series_2    | double precision | f\n series_bool | boolean          | f\n\nALTER TABLE \"two_Partitions\" RENAME TO \"newname\";\nSELECT * FROM \"newname\";\n     timeCustom      | device_id | series_0 | series_1 | series_2 | series_bool \n---------------------+-----------+----------+----------+----------+-------------\n 1257894000000000000 | dev1      |      1.5 |        1 |        2 | t\n 1257894000000000000 | dev1      |      1.5 |        2 |          | \n 1257894000000001000 | dev1      |      2.5 |        3 |          | \n 1257894001000000000 | dev1      |      3.5 |        4 |          | \n 1257894002000000000 | dev1      |      5.5 |        6 |          | t\n 1257894002000000000 | dev1      |      5.5 |        7 |          | f\n 1257894002000000000 | dev1      |      2.5 |        3 |          | \n 1257897600000000000 | dev1      |      4.5 |        5 |          | f\n 1257987600000000000 | dev1      |      1.5 |        1 |          | \n 1257987600000000000 | dev1      |      1.5 |        2 |          | \n 1257894000000000000 | dev2      |      1.5 |        1 |          | \n 1257894000000000000 | dev2      |      1.5 |        2 |          | \n\nSELECT * FROM _timescaledb_catalog.hypertable;\n id | schema_name | table_name | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------+------------+------------------------+-------------------------+----------------+--------------------------+--------------------------+-------------------+-------------------+--------------------------+--------\n  1 | public      | newname    | _timescaledb_internal  | _hyper_1                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA \"newschema\" AUTHORIZATION :ROLE_DEFAULT_PERM_USER;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nALTER TABLE \"newname\" SET SCHEMA \"newschema\";\nSELECT * FROM \"newschema\".\"newname\";\n     timeCustom      | device_id | series_0 | series_1 | series_2 | series_bool \n---------------------+-----------+----------+----------+----------+-------------\n 1257894000000000000 | dev1      |      1.5 |        1 |        2 | t\n 1257894000000000000 | dev1      |      1.5 |        2 |          | \n 1257894000000001000 | dev1      |      2.5 |        3 |          | \n 1257894001000000000 | dev1      |      3.5 |        4 |          | \n 1257894002000000000 | dev1      |      5.5 |        6 |          | t\n 1257894002000000000 | dev1      |      5.5 |        7 |          | f\n 1257894002000000000 | dev1      |      2.5 |        3 |          | \n 1257897600000000000 | dev1      |      4.5 |        5 |          | f\n 1257987600000000000 | dev1      |      1.5 |        1 |          | \n 1257987600000000000 | dev1      |      1.5 |        2 |          | \n 1257894000000000000 | dev2      |      1.5 |        1 |          | \n 1257894000000000000 | dev2      |      1.5 |        2 |          | \n\nSELECT * FROM _timescaledb_catalog.hypertable;\n id | schema_name | table_name | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------+------------+------------------------+-------------------------+----------------+--------------------------+--------------------------+-------------------+-------------------+--------------------------+--------\n  1 | newschema   | newname    | _timescaledb_internal  | _hyper_1                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n\nDROP TABLE \"newschema\".\"newname\";\nSELECT * FROM _timescaledb_catalog.hypertable;\n id | schema_name | table_name | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema | chunk_sizing_func_name | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------+------------+------------------------+-------------------------+----------------+--------------------------+------------------------+-------------------+-------------------+--------------------------+--------\n\nSELECT schema, name FROM test.relation WHERE schema IN ('public', '_timescaledb_catalog', '_timescaledb_internal');\n        schema         |                       name                       \n-----------------------+--------------------------------------------------\n _timescaledb_catalog  | bgw_job\n _timescaledb_catalog  | chunk\n _timescaledb_catalog  | chunk_column_stats\n _timescaledb_catalog  | chunk_constraint\n _timescaledb_catalog  | chunk_rewrite\n _timescaledb_catalog  | compression_algorithm\n _timescaledb_catalog  | compression_chunk_size\n _timescaledb_catalog  | compression_settings\n _timescaledb_catalog  | continuous_agg\n _timescaledb_catalog  | continuous_aggs_bucket_function\n _timescaledb_catalog  | continuous_aggs_hypertable_invalidation_log\n _timescaledb_catalog  | continuous_aggs_invalidation_threshold\n _timescaledb_catalog  | continuous_aggs_materialization_invalidation_log\n _timescaledb_catalog  | continuous_aggs_materialization_ranges\n _timescaledb_catalog  | continuous_aggs_watermark\n _timescaledb_catalog  | dimension\n _timescaledb_catalog  | dimension_slice\n _timescaledb_catalog  | hypertable\n _timescaledb_catalog  | metadata\n _timescaledb_catalog  | tablespace\n _timescaledb_catalog  | telemetry_event\n _timescaledb_internal | bgw_job_stat\n _timescaledb_internal | bgw_job_stat_history\n _timescaledb_internal | bgw_policy_chunk_stats\n _timescaledb_internal | compressed_chunk_stats\n _timescaledb_internal | hypertable_chunk_local_size\n\n-- Test that renaming ordinary table works\nCREATE TABLE renametable (foo int);\nALTER TABLE \"renametable\" RENAME TO \"newname_none_ht\";\nSELECT * FROM \"newname_none_ht\";\n foo \n-----\n\n"
  },
  {
    "path": "test/expected/drop_schema.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA chunk_schema1;\nCREATE SCHEMA chunk_schema2;\nCREATE SCHEMA hypertable_schema;\nCREATE SCHEMA extra_schema;\nGRANT ALL ON SCHEMA hypertable_schema TO :ROLE_DEFAULT_PERM_USER;\nGRANT ALL ON SCHEMA chunk_schema1 TO :ROLE_DEFAULT_PERM_USER;\nGRANT ALL ON SCHEMA chunk_schema2 TO :ROLE_DEFAULT_PERM_USER;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nCREATE TABLE hypertable_schema.test1 (time timestamptz, temp float, location int);\nCREATE TABLE hypertable_schema.test2 (time timestamptz, temp float, location int);\n--create two identical tables with their own chunk schemas\nSELECT create_hypertable('hypertable_schema.test1', 'time', 'location', 2, associated_schema_name => 'chunk_schema1');\n       create_hypertable       \n-------------------------------\n (1,hypertable_schema,test1,t)\n\nSELECT create_hypertable('hypertable_schema.test2', 'time', 'location', 2, associated_schema_name => 'chunk_schema2');\n       create_hypertable       \n-------------------------------\n (2,hypertable_schema,test2,t)\n\nINSERT INTO hypertable_schema.test1 VALUES ('2001-01-01 01:01:01', 23.3, 1);\nINSERT INTO hypertable_schema.test2 VALUES ('2001-01-01 01:01:01', 23.3, 1);\nSELECT * FROM _timescaledb_catalog.hypertable ORDER BY id;\n id |    schema_name    | table_name | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------------+------------+------------------------+-------------------------+----------------+--------------------------+--------------------------+-------------------+-------------------+--------------------------+--------\n  1 | hypertable_schema | test1      | chunk_schema1          | _hyper_1                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n  2 | hypertable_schema | test2      | chunk_schema2          | _hyper_2                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n id | hypertable_id |  schema_name  |    table_name    | compressed_chunk_id | status | osm_chunk \n----+---------------+---------------+------------------+---------------------+--------+-----------\n  1 |             1 | chunk_schema1 | _hyper_1_1_chunk |                     |      0 | f\n  2 |             2 | chunk_schema2 | _hyper_2_2_chunk |                     |      0 | f\n\nRESET ROLE;\n--drop the associated schema. We drop the extra schema to show we can\n--handle multi-schema drops\nDROP SCHEMA chunk_schema1, extra_schema CASCADE;\nNOTICE:  drop cascades to table chunk_schema1._hyper_1_1_chunk\nNOTICE:  the chunk storage schema changed to \"_timescaledb_internal\" for 1 hypertable\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--show that the metadata for the table using the dropped schema is\n--changed. The other table is not affected.\nSELECT * FROM _timescaledb_catalog.hypertable ORDER BY id;\n id |    schema_name    | table_name | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------------+------------+------------------------+-------------------------+----------------+--------------------------+--------------------------+-------------------+-------------------+--------------------------+--------\n  1 | hypertable_schema | test1      | _timescaledb_internal  | _hyper_1                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n  2 | hypertable_schema | test2      | chunk_schema2          | _hyper_2                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n id | hypertable_id |  schema_name  |    table_name    | compressed_chunk_id | status | osm_chunk \n----+---------------+---------------+------------------+---------------------+--------+-----------\n  2 |             2 | chunk_schema2 | _hyper_2_2_chunk |                     |      0 | f\n\n--new chunk should be created in the internal associated schema\nINSERT INTO hypertable_schema.test1 VALUES ('2001-01-01 01:01:01', 23.3, 1);\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n id | hypertable_id |      schema_name      |    table_name    | compressed_chunk_id | status | osm_chunk \n----+---------------+-----------------------+------------------+---------------------+--------+-----------\n  2 |             2 | chunk_schema2         | _hyper_2_2_chunk |                     |      0 | f\n  3 |             1 | _timescaledb_internal | _hyper_1_3_chunk |                     |      0 | f\n\nRESET ROLE;\n--dropping the internal schema should not work\n\\set ON_ERROR_STOP 0\nDROP SCHEMA _timescaledb_internal CASCADE;\nERROR:  cannot drop schema _timescaledb_internal because extension timescaledb requires it\n\\set ON_ERROR_STOP 1\n--dropping the hypertable schema should delete everything\nDROP SCHEMA hypertable_schema CASCADE;\nNOTICE:  drop cascades to 4 other objects\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--everything should be cleaned up\nSELECT * FROM _timescaledb_catalog.hypertable GROUP BY id;\n id | schema_name | table_name | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema | chunk_sizing_func_name | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------+------------+------------------------+-------------------------+----------------+--------------------------+------------------------+-------------------+-------------------+--------------------------+--------\n\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n id | hypertable_id | schema_name | table_name | compressed_chunk_id | status | osm_chunk \n----+---------------+-------------+------------+---------------------+--------+-----------\n\nSELECT * FROM _timescaledb_catalog.dimension;\n id | hypertable_id | column_name | column_type | aligned | num_slices | partitioning_func_schema | partitioning_func | interval_length | compress_interval_length | integer_now_func_schema | integer_now_func \n----+---------------+-------------+-------------+---------+------------+--------------------------+-------------------+-----------------+--------------------------+-------------------------+------------------\n\nSELECT * FROM _timescaledb_catalog.dimension_slice;\n id | dimension_id | range_start | range_end \n----+--------------+-------------+-----------\n\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\n chunk_id | dimension_slice_id | constraint_name | hypertable_constraint_name \n----------+--------------------+-----------------+----------------------------\n\n"
  },
  {
    "path": "test/expected/dump_meta.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\ir include/insert_two_partitions.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE PUBLIC.\"two_Partitions\" (\n  \"timeCustom\" BIGINT NOT NULL,\n  device_id TEXT NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL,\n  series_bool BOOLEAN NULL\n);\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (device_id, \"timeCustom\" DESC NULLS LAST) WHERE device_id IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_0) WHERE series_0 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_1)  WHERE series_1 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_2) WHERE series_2 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_bool) WHERE series_bool IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, device_id);\nSELECT * FROM create_hypertable('\"public\".\"two_Partitions\"'::regclass, 'timeCustom'::name, 'device_id'::name, associated_schema_name=>'_timescaledb_internal'::text, number_partitions => 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name |   table_name   | created \n---------------+-------------+----------------+---------\n             1 | public      | two_Partitions | t\n\n\\set QUIET off\nBEGIN;\nBEGIN\n\\COPY public.\"two_Partitions\" FROM 'data/ds1_dev1_1.tsv' NULL AS '';\nCOPY 7\nCOMMIT;\nCOMMIT\nINSERT INTO public.\"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257987600000000000, 'dev1', 1.5, 1),\n(1257987600000000000, 'dev1', 1.5, 2),\n(1257894000000000000, 'dev2', 1.5, 1),\n(1257894002000000000, 'dev1', 2.5, 3);\nINSERT 0 4\nINSERT INTO \"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257894000000000000, 'dev2', 1.5, 2);\nINSERT 0 1\n\\set QUIET on\n\\ir ../../scripts/dump_meta_data.sql\n--\n-- This file is licensed under the Apache License, see LICENSE-APACHE\n-- at the top level directory of the TimescaleDB distribution.\n-- This script will dump relevant meta data from internal TimescaleDB tables\n-- that can help our engineers trouble shoot.\n--\n-- usage:\n-- psql [your connect flags] -d your_timescale_db < dump_meta_data.sql > dumpfile.txt\n\\echo 'TimescaleDB meta data dump'\nTimescaleDB meta data dump\n</exclude_from_test>\n\\echo 'List of tables'\nList of tables\nSELECT n.nspname as \"Schema\",\n  c.relname as \"Name\",\n  CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 't' THEN 'TOAST table' WHEN 'f' THEN 'foreign table' WHEN 'p' THEN 'partitioned table' WHEN 'I' THEN 'partitioned index' END as \"Type\",\n  pg_catalog.pg_get_userbyid(c.relowner) as \"Owner\"\nFROM pg_catalog.pg_class c\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n     LEFT JOIN pg_catalog.pg_am am ON am.oid = c.relam\nWHERE c.relkind IN ('r','p','')\n      AND n.nspname <> 'pg_catalog'\n      AND n.nspname !~ '^pg_toast'\n      AND n.nspname <> 'information_schema'\n  AND pg_catalog.pg_table_is_visible(c.oid)\nORDER BY 1,2;\n Schema |      Name      | Type  |       Owner       \n--------+----------------+-------+-------------------\n public | two_Partitions | table | default_perm_user\n\n\\echo 'List of hypertables'\nList of hypertables\nSELECT * FROM _timescaledb_catalog.hypertable;\n id | schema_name |   table_name   | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------+----------------+------------------------+-------------------------+----------------+--------------------------+--------------------------+-------------------+-------------------+--------------------------+--------\n  1 | public      | two_Partitions | _timescaledb_internal  | _hyper_1                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n\n\\echo 'List of chunk indexes'\nList of chunk indexes\nSELECT ch.id AS chunk_id, ci.indexrelid::regclass::text AS index_name, h.id AS hypertable_id\nFROM _timescaledb_catalog.hypertable h\nJOIN _timescaledb_catalog.chunk ch ON ch.hypertable_id = h.id\nJOIN pg_index ci ON ci.indrelid = format('%I.%I', ch.schema_name, ch.table_name)::regclass\nORDER BY h.id, ch.id, ci.indrelid::regclass::text;\n chunk_id |                                     index_name                                     | hypertable_id \n----------+------------------------------------------------------------------------------------+---------------\n        1 | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_idx\"             |             1\n        1 | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_device_id_idx\"   |             1\n        1 | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_bool_idx\" |             1\n        1 | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_2_idx\"    |             1\n        1 | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx\"    |             1\n        1 | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_0_idx\"    |             1\n        1 | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_device_id_timeCustom_idx\"   |             1\n        2 | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_idx\"             |             1\n        2 | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_device_id_idx\"   |             1\n        2 | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_bool_idx\" |             1\n        2 | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_2_idx\"    |             1\n        2 | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_1_idx\"    |             1\n        2 | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_0_idx\"    |             1\n        2 | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_device_id_timeCustom_idx\"   |             1\n        3 | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_idx\"             |             1\n        3 | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_device_id_idx\"   |             1\n        3 | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_bool_idx\" |             1\n        3 | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_2_idx\"    |             1\n        3 | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_1_idx\"    |             1\n        3 | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_0_idx\"    |             1\n        3 | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_device_id_timeCustom_idx\"   |             1\n        4 | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_idx\"             |             1\n        4 | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_device_id_idx\"   |             1\n        4 | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_bool_idx\" |             1\n        4 | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_2_idx\"    |             1\n        4 | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_1_idx\"    |             1\n        4 | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_0_idx\"    |             1\n        4 | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_device_id_timeCustom_idx\"   |             1\n\n\\echo 'Size of hypertables'\nSize of hypertables\nSELECT hypertable,\n       table_bytes,\n       index_bytes,\n       toast_bytes,\n       total_bytes\n       FROM (\n       SELECT *, total_bytes-index_bytes-COALESCE(toast_bytes,0) AS table_bytes FROM (\n              SELECT\n              pgc.oid::regclass::text as hypertable,\n              sum(pg_total_relation_size('\"' || c.schema_name || '\".\"' || c.table_name || '\"'))::bigint as total_bytes,\n              sum(pg_indexes_size('\"' || c.schema_name || '\".\"' || c.table_name || '\"'))::bigint AS index_bytes,\n              sum(pg_total_relation_size(reltoastrelid))::bigint AS toast_bytes\n              FROM\n              _timescaledb_catalog.hypertable h,\n              _timescaledb_catalog.chunk c,\n              pg_class pgc,\n              pg_namespace pns\n              WHERE c.hypertable_id = h.id\n              AND pgc.relname = h.table_name\n              AND pns.oid = pgc.relnamespace\n              AND pns.nspname = h.schema_name\n              AND relkind = 'r'\n              GROUP BY pgc.oid\n              ) sub1\n       ) sub2;\n    hypertable    | table_bytes | index_bytes | toast_bytes | total_bytes \n------------------+-------------+-------------+-------------+-------------\n \"two_Partitions\" |       32768 |      417792 |       32768 |      483328\n\n\\echo 'Chunk sizes:'\nChunk sizes:\nSELECT chunk_id,\nchunk_table,\npartitioning_columns,\npartitioning_column_types,\npartitioning_hash_functions,\nranges,\ntable_bytes,\nindex_bytes,\ntoast_bytes,\ntotal_bytes\nFROM (\nSELECT *,\n      total_bytes-index_bytes-COALESCE(toast_bytes,0) AS table_bytes\n      FROM (\n       SELECT c.id as chunk_id,\n       '\"' || c.schema_name || '\".\"' || c.table_name || '\"' as chunk_table,\n       pg_total_relation_size('\"' || c.schema_name || '\".\"' || c.table_name || '\"') AS total_bytes,\n       pg_indexes_size('\"' || c.schema_name || '\".\"' || c.table_name || '\"') AS index_bytes,\n       pg_total_relation_size(reltoastrelid) AS toast_bytes,\n       array_agg(d.column_name ORDER BY d.interval_length, d.column_name ASC) as partitioning_columns,\n       array_agg(d.column_type ORDER BY d.interval_length, d.column_name ASC) as partitioning_column_types,\n       array_agg(d.partitioning_func_schema || '.' || d.partitioning_func ORDER BY d.interval_length, d.column_name ASC) as partitioning_hash_functions,\n       array_agg('[' || _timescaledb_functions.range_value_to_pretty(range_start, column_type) ||\n                 ',' ||\n                 _timescaledb_functions.range_value_to_pretty(range_end, column_type) || ')' ORDER BY d.interval_length, d.column_name ASC) as ranges\n       FROM\n       _timescaledb_catalog.hypertable h,\n       _timescaledb_catalog.chunk c,\n       _timescaledb_catalog.chunk_constraint cc,\n       _timescaledb_catalog.dimension d,\n       _timescaledb_catalog.dimension_slice ds,\n       pg_class pgc,\n       pg_namespace pns\n       WHERE pgc.relname = h.table_name\n             AND pns.oid = pgc.relnamespace\n             AND pns.nspname = h.schema_name\n             AND relkind = 'r'\n             AND c.hypertable_id = h.id\n             AND c.id = cc.chunk_id\n             AND cc.dimension_slice_id = ds.id\n             AND ds.dimension_id = d.id\n       GROUP BY c.id, pgc.reltoastrelid, pgc.oid ORDER BY c.id\n       ) sub1\n) sub2;\n chunk_id |                chunk_table                 |  partitioning_columns  | partitioning_column_types |           partitioning_hash_functions            |                              ranges                               | table_bytes | index_bytes | toast_bytes | total_bytes \n----------+--------------------------------------------+------------------------+---------------------------+--------------------------------------------------+-------------------------------------------------------------------+-------------+-------------+-------------+-------------\n        1 | \"_timescaledb_internal\".\"_hyper_1_1_chunk\" | {timeCustom,device_id} | {bigint,text}             | {NULL,_timescaledb_functions.get_partition_hash} | {\"['1257892416000000000','1257895008000000000')\",\"[1073741823,)\"} |        8192 |      114688 |        8192 |      131072\n        2 | \"_timescaledb_internal\".\"_hyper_1_2_chunk\" | {timeCustom,device_id} | {bigint,text}             | {NULL,_timescaledb_functions.get_partition_hash} | {\"['1257897600000000000','1257900192000000000')\",\"[1073741823,)\"} |        8192 |      106496 |        8192 |      122880\n        3 | \"_timescaledb_internal\".\"_hyper_1_3_chunk\" | {timeCustom,device_id} | {bigint,text}             | {NULL,_timescaledb_functions.get_partition_hash} | {\"['1257985728000000000','1257988320000000000')\",\"[1073741823,)\"} |        8192 |       98304 |        8192 |      114688\n        4 | \"_timescaledb_internal\".\"_hyper_1_4_chunk\" | {timeCustom,device_id} | {bigint,text}             | {NULL,_timescaledb_functions.get_partition_hash} | {\"['1257892416000000000','1257895008000000000')\",\"[,1073741823)\"} |        8192 |       98304 |        8192 |      114688\n\n\\echo 'Hypertable index sizes'\nHypertable index sizes\nSET search_path TO pg_catalog, pg_temp;\nSELECT\n  i.indrelid::regclass AS hypertable,\n  i.indexrelid::regclass AS index_name,\n  public.hypertable_index_size(i.indexrelid::regclass) AS index_bytes\nFROM _timescaledb_catalog.hypertable h\nJOIN pg_index i ON i.indrelid = format('%I.%I',h.schema_name,h.table_name)::regclass\nORDER BY i.indrelid::regclass::text, i.indexrelid::regclass::text;\n       hypertable        |                     index_name                     | index_bytes \n-------------------------+----------------------------------------------------+-------------\n public.\"two_Partitions\" | public.\"two_Partitions_device_id_timeCustom_idx\"   |       73728\n public.\"two_Partitions\" | public.\"two_Partitions_timeCustom_device_id_idx\"   |       73728\n public.\"two_Partitions\" | public.\"two_Partitions_timeCustom_idx\"             |       73728\n public.\"two_Partitions\" | public.\"two_Partitions_timeCustom_series_0_idx\"    |       73728\n public.\"two_Partitions\" | public.\"two_Partitions_timeCustom_series_1_idx\"    |       73728\n public.\"two_Partitions\" | public.\"two_Partitions_timeCustom_series_2_idx\"    |       49152\n public.\"two_Partitions\" | public.\"two_Partitions_timeCustom_series_bool_idx\" |       57344\n\nRESET search_path;\n"
  },
  {
    "path": "test/expected/extension_scripts.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nDROP EXTENSION timescaledb;\n-- test that installation script errors when any of our internal schemas already exists\n\\set ON_ERROR_STOP 0\nCREATE SCHEMA _timescaledb_catalog;\nCREATE EXTENSION timescaledb;\nERROR:  schema \"_timescaledb_catalog\" already exists\nDROP SCHEMA _timescaledb_catalog;\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA _timescaledb_internal;\nCREATE EXTENSION timescaledb;\nERROR:  schema \"_timescaledb_internal\" already exists\nDROP SCHEMA _timescaledb_internal;\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA _timescaledb_cache;\nCREATE EXTENSION timescaledb;\nERROR:  schema \"_timescaledb_cache\" already exists\nDROP SCHEMA _timescaledb_cache;\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA timescaledb_experimental;\nCREATE EXTENSION timescaledb;\nERROR:  schema \"timescaledb_experimental\" already exists\nDROP SCHEMA timescaledb_experimental;\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA timescaledb_information;\nCREATE EXTENSION timescaledb;\nERROR:  schema \"timescaledb_information\" already exists\nDROP SCHEMA timescaledb_information;\n-- test that installation script errors when any of the function in public schema already exists\n-- we don't test every public function but just a few common ones\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE FUNCTION time_bucket(int,int) RETURNS int LANGUAGE SQL AS $$ SELECT 1::int; $$;\nCREATE EXTENSION timescaledb;\nERROR:  function \"time_bucket\" already exists with same argument types\nDROP FUNCTION time_bucket;\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION show_chunks(relation regclass, older_than \"any\" DEFAULT NULL, newer_than \"any\" DEFAULT NULL, created_before \"any\" DEFAULT NULL, created_after \"any\" DEFAULT NULL) RETURNS SETOF regclass language internal as 'pg_partition_ancestors';\nCREATE EXTENSION timescaledb;\nERROR:  function \"show_chunks\" already exists with same argument types\nDROP FUNCTION show_chunks;\n-- Create a user that is not all-lowercase\nCREATE USER \"FooBar\" WITH SUPERUSER;\n\\c :TEST_DBNAME \"FooBar\"\nSET client_min_messages TO error;\nCREATE EXTENSION timescaledb;\nDROP EXTENSION timescaledb;\nRESET client_min_messages;\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nDROP USER \"FooBar\";\nSET client_min_messages TO ERROR;\nCREATE EXTENSION timescaledb;\n"
  },
  {
    "path": "test/expected/generated_as_identity.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE table test_gen (\n    id int generated by default AS IDENTITY primary key,\n    payload text\n);\n SELECT create_hypertable('test_gen', 'id', chunk_time_interval=>10);\n   create_hypertable   \n-----------------------\n (1,public,test_gen,t)\n\ninsert into test_gen (payload) select generate_series(1,15) returning *;\n id | payload \n----+---------\n  1 | 1\n  2 | 2\n  3 | 3\n  4 | 4\n  5 | 5\n  6 | 6\n  7 | 7\n  8 | 8\n  9 | 9\n 10 | 10\n 11 | 11\n 12 | 12\n 13 | 13\n 14 | 14\n 15 | 15\n\nselect * from test_gen;\n id | payload \n----+---------\n  1 | 1\n  2 | 2\n  3 | 3\n  4 | 4\n  5 | 5\n  6 | 6\n  7 | 7\n  8 | 8\n  9 | 9\n 10 | 10\n 11 | 11\n 12 | 12\n 13 | 13\n 14 | 14\n 15 | 15\n\n\\set ON_ERROR_STOP 0\ninsert into test_gen values('1', 'a');\nERROR:  duplicate key value violates unique constraint \"1_1_test_gen_pkey\"\n\\set ON_ERROR_STOP 1\nALTER TABLE test_gen ALTER COLUMN id DROP IDENTITY;\n\\set ON_ERROR_STOP 0\ninsert into test_gen (payload) select generate_series(15,20) returning *;\nERROR:  NULL value in column \"id\" violates not-null constraint\n\\set ON_ERROR_STOP 1\nALTER TABLE test_gen ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY;\n\\set ON_ERROR_STOP 0\ninsert into test_gen (payload) select generate_series(15,20) returning *;\nERROR:  duplicate key value violates unique constraint \"1_1_test_gen_pkey\"\n\\set ON_ERROR_STOP 1\nALTER TABLE test_gen ALTER COLUMN id SET GENERATED BY DEFAULT RESTART 100;\ninsert into test_gen (payload) select generate_series(15,20) returning *;\n id  | payload \n-----+---------\n 100 | 15\n 101 | 16\n 102 | 17\n 103 | 18\n 104 | 19\n 105 | 20\n\nselect * from test_gen;\n id  | payload \n-----+---------\n   1 | 1\n   2 | 2\n   3 | 3\n   4 | 4\n   5 | 5\n   6 | 6\n   7 | 7\n   8 | 8\n   9 | 9\n  10 | 10\n  11 | 11\n  12 | 12\n  13 | 13\n  14 | 14\n  15 | 15\n 100 | 15\n 101 | 16\n 102 | 17\n 103 | 18\n 104 | 19\n 105 | 20\n\nSELECT * FROM test.show_subtables('test_gen');\n                 Child                  | Tablespace \n----------------------------------------+------------\n _timescaledb_internal._hyper_1_1_chunk | \n _timescaledb_internal._hyper_1_2_chunk | \n _timescaledb_internal._hyper_1_3_chunk | \n\n"
  },
  {
    "path": "test/expected/grant_hypertable-15.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE TABLE conditions(\n    time TIMESTAMPTZ NOT NULL,\n    device INTEGER,\n    temperature FLOAT\n);\n-- Create a hypertable and show that it does not have any privileges\nSELECT * FROM create_hypertable('conditions', 'time', chunk_time_interval => '5 days'::interval);\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             1 | public      | conditions | t\n\nINSERT INTO conditions\nSELECT time, (random()*30)::int, random()*80 - 40\nFROM generate_series('2018-12-01 00:00'::timestamp, '2018-12-10 00:00'::timestamp, '1h') AS time;\n\\z conditions\n                               Access privileges\n Schema |    Name    | Type  | Access privileges | Column privileges | Policies \n--------+------------+-------+-------------------+-------------------+----------\n public | conditions | table |                   |                   | \n\n\\z _timescaledb_internal.*chunk\n                                          Access privileges\n        Schema         |       Name       | Type  | Access privileges | Column privileges | Policies \n-----------------------+------------------+-------+-------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table |                   |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table |                   |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table |                   |                   | \n\n-- Add privileges and show that they propagate to the chunks\nGRANT SELECT, INSERT ON conditions TO PUBLIC;\n\\z conditions\n                                     Access privileges\n Schema |    Name    | Type  |       Access privileges       | Column privileges | Policies \n--------+------------+-------+-------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxt/super_user+|                   | \n        |            |       | =ar/super_user                |                   | \n\n\\z _timescaledb_internal.*chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges       | Column privileges | Policies \n-----------------------+------------------+-------+-------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =ar/super_user                |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =ar/super_user                |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =ar/super_user                |                   | \n\n-- Create some more chunks and show that they also get the privileges.\nINSERT INTO conditions\nSELECT time, (random()*30)::int, random()*80 - 40\nFROM generate_series('2018-12-10 00:00'::timestamp, '2018-12-20 00:00'::timestamp, '1h') AS time;\n\\z conditions\n                                     Access privileges\n Schema |    Name    | Type  |       Access privileges       | Column privileges | Policies \n--------+------------+-------+-------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxt/super_user+|                   | \n        |            |       | =ar/super_user                |                   | \n\n\\z _timescaledb_internal.*chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges       | Column privileges | Policies \n-----------------------+------------------+-------+-------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =ar/super_user                |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =ar/super_user                |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =ar/super_user                |                   | \n _timescaledb_internal | _hyper_1_4_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =ar/super_user                |                   | \n _timescaledb_internal | _hyper_1_5_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =ar/super_user                |                   | \n\n-- Revoke one of the privileges and show that it propagate to the\n-- chunks.\nREVOKE INSERT ON conditions FROM PUBLIC;\n\\z conditions\n                                     Access privileges\n Schema |    Name    | Type  |       Access privileges       | Column privileges | Policies \n--------+------------+-------+-------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxt/super_user+|                   | \n        |            |       | =r/super_user                 |                   | \n\n\\z _timescaledb_internal.*chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges       | Column privileges | Policies \n-----------------------+------------------+-------+-------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n _timescaledb_internal | _hyper_1_4_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n _timescaledb_internal | _hyper_1_5_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n\n-- Add some more chunks and show that it inherits the grants from the\n-- hypertable.\nINSERT INTO conditions\nSELECT time, (random()*30)::int, random()*80 - 40\nFROM generate_series('2018-12-20 00:00'::timestamp, '2018-12-30 00:00'::timestamp, '1h') AS time;\n\\z conditions\n                                     Access privileges\n Schema |    Name    | Type  |       Access privileges       | Column privileges | Policies \n--------+------------+-------+-------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxt/super_user+|                   | \n        |            |       | =r/super_user                 |                   | \n\n\\z _timescaledb_internal.*chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges       | Column privileges | Policies \n-----------------------+------------------+-------+-------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n _timescaledb_internal | _hyper_1_4_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n _timescaledb_internal | _hyper_1_5_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n _timescaledb_internal | _hyper_1_6_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n _timescaledb_internal | _hyper_1_7_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n\n-- Change grants of one chunk explicitly and check that it is possible\n\\z _timescaledb_internal._hyper_1_1_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges       | Column privileges | Policies \n-----------------------+------------------+-------+-------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n\nGRANT UPDATE ON _timescaledb_internal._hyper_1_1_chunk TO PUBLIC;\n\\z _timescaledb_internal._hyper_1_1_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges       | Column privileges | Policies \n-----------------------+------------------+-------+-------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =rw/super_user                |                   | \n\nREVOKE SELECT ON _timescaledb_internal._hyper_1_1_chunk FROM PUBLIC;\n\\z _timescaledb_internal._hyper_1_1_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges       | Column privileges | Policies \n-----------------------+------------------+-------+-------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =w/super_user                 |                   | \n\n-- Check that revoking a permission first on the chunk and then on the\n-- hypertable that was added through the hypertable (INSERT and\n-- SELECT, in this case) still do not copy permissions from the\n-- hypertable (so there should not be a select permission to public on\n-- the chunk but there should be one on the hypertable).\nGRANT INSERT ON conditions TO PUBLIC;\n\\z conditions\n                                     Access privileges\n Schema |    Name    | Type  |       Access privileges       | Column privileges | Policies \n--------+------------+-------+-------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxt/super_user+|                   | \n        |            |       | =ar/super_user                |                   | \n\n\\z _timescaledb_internal._hyper_1_2_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges       | Column privileges | Policies \n-----------------------+------------------+-------+-------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =ar/super_user                |                   | \n\nREVOKE SELECT ON _timescaledb_internal._hyper_1_2_chunk FROM PUBLIC;\nREVOKE INSERT ON conditions FROM PUBLIC;\n\\z conditions\n                                     Access privileges\n Schema |    Name    | Type  |       Access privileges       | Column privileges | Policies \n--------+------------+-------+-------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxt/super_user+|                   | \n        |            |       | =r/super_user                 |                   | \n\n\\z _timescaledb_internal._hyper_1_2_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges       | Column privileges | Policies \n-----------------------+------------------+-------+-------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxt/super_user |                   | \n\n-- Check that granting permissions through hypertable does not remove\n-- separate grants on chunk.\nGRANT UPDATE ON _timescaledb_internal._hyper_1_3_chunk TO PUBLIC;\n\\z conditions\n                                     Access privileges\n Schema |    Name    | Type  |       Access privileges       | Column privileges | Policies \n--------+------------+-------+-------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxt/super_user+|                   | \n        |            |       | =r/super_user                 |                   | \n\n\\z _timescaledb_internal._hyper_1_3_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges       | Column privileges | Policies \n-----------------------+------------------+-------+-------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =rw/super_user                |                   | \n\nGRANT INSERT ON conditions TO PUBLIC;\nREVOKE INSERT ON conditions FROM PUBLIC;\n\\z conditions\n                                     Access privileges\n Schema |    Name    | Type  |       Access privileges       | Column privileges | Policies \n--------+------------+-------+-------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxt/super_user+|                   | \n        |            |       | =r/super_user                 |                   | \n\n\\z _timescaledb_internal._hyper_1_3_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges       | Column privileges | Policies \n-----------------------+------------------+-------+-------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =rw/super_user                |                   | \n\n-- Check that GRANT ALL IN SCHEMA adds privileges to the parent\n-- and also goes to chunks in another schema\nGRANT ALL ON ALL TABLES  IN SCHEMA public TO :ROLE_DEFAULT_PERM_USER_2;\n\\z conditions\n                                          Access privileges\n Schema |    Name    | Type  |           Access privileges            | Column privileges | Policies \n--------+------------+-------+----------------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxt/super_user         +|                   | \n        |            |       | =r/super_user                         +|                   | \n        |            |       | default_perm_user_2=arwdDxt/super_user |                   | \n\n\\z _timescaledb_internal.*chunk\n                                                    Access privileges\n        Schema         |       Name       | Type  |           Access privileges            | Column privileges | Policies \n-----------------------+------------------+-------+----------------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxt/super_user         +|                   | \n                       |                  |       | =w/super_user                         +|                   | \n                       |                  |       | default_perm_user_2=arwdDxt/super_user |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxt/super_user         +|                   | \n                       |                  |       | default_perm_user_2=arwdDxt/super_user |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxt/super_user         +|                   | \n                       |                  |       | =rw/super_user                        +|                   | \n                       |                  |       | default_perm_user_2=arwdDxt/super_user |                   | \n _timescaledb_internal | _hyper_1_4_chunk | table | super_user=arwdDxt/super_user         +|                   | \n                       |                  |       | =r/super_user                         +|                   | \n                       |                  |       | default_perm_user_2=arwdDxt/super_user |                   | \n _timescaledb_internal | _hyper_1_5_chunk | table | super_user=arwdDxt/super_user         +|                   | \n                       |                  |       | =r/super_user                         +|                   | \n                       |                  |       | default_perm_user_2=arwdDxt/super_user |                   | \n _timescaledb_internal | _hyper_1_6_chunk | table | super_user=arwdDxt/super_user         +|                   | \n                       |                  |       | =r/super_user                         +|                   | \n                       |                  |       | default_perm_user_2=arwdDxt/super_user |                   | \n _timescaledb_internal | _hyper_1_7_chunk | table | super_user=arwdDxt/super_user         +|                   | \n                       |                  |       | =r/super_user                         +|                   | \n                       |                  |       | default_perm_user_2=arwdDxt/super_user |                   | \n\n-- Check that REVOKE ALL IN SCHEMA removes privileges of the parent\n-- and also goes to chunks in another schema\nREVOKE ALL ON ALL TABLES  IN SCHEMA public FROM :ROLE_DEFAULT_PERM_USER_2;\n\\z conditions\n                                     Access privileges\n Schema |    Name    | Type  |       Access privileges       | Column privileges | Policies \n--------+------------+-------+-------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxt/super_user+|                   | \n        |            |       | =r/super_user                 |                   | \n\n\\z _timescaledb_internal.*chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges       | Column privileges | Policies \n-----------------------+------------------+-------+-------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =w/super_user                 |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxt/super_user |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =rw/super_user                |                   | \n _timescaledb_internal | _hyper_1_4_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n _timescaledb_internal | _hyper_1_5_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n _timescaledb_internal | _hyper_1_6_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n _timescaledb_internal | _hyper_1_7_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n\n-- Create chunks in the same schema as the hypertable and check that\n-- they also get the same privileges as the hypertable\nCREATE TABLE measurements(\n    time TIMESTAMPTZ NOT NULL,\n    device INTEGER,\n    temperature FLOAT\n);\n-- Create a hypertable with chunks in the same schema\nSELECT * FROM create_hypertable('public.measurements', 'time', chunk_time_interval => '5 days'::interval, associated_schema_name => 'public');\n hypertable_id | schema_name |  table_name  | created \n---------------+-------------+--------------+---------\n             2 | public      | measurements | t\n\nINSERT INTO measurements\nSELECT time, (random()*30)::int, random()*80 - 40\nFROM generate_series('2018-12-01 00:00'::timestamp, '2018-12-10 00:00'::timestamp, '1h') AS time;\n-- GRANT ALL and check privileges\nGRANT ALL ON ALL TABLES  IN SCHEMA public TO :ROLE_DEFAULT_PERM_USER_2;\n\\z measurements\n                                           Access privileges\n Schema |     Name     | Type  |           Access privileges            | Column privileges | Policies \n--------+--------------+-------+----------------------------------------+-------------------+----------\n public | measurements | table | super_user=arwdDxt/super_user         +|                   | \n        |              |       | default_perm_user_2=arwdDxt/super_user |                   | \n\n\\z conditions\n                                          Access privileges\n Schema |    Name    | Type  |           Access privileges            | Column privileges | Policies \n--------+------------+-------+----------------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxt/super_user         +|                   | \n        |            |       | =r/super_user                         +|                   | \n        |            |       | default_perm_user_2=arwdDxt/super_user |                   | \n\n\\z public.*chunk\n                                             Access privileges\n Schema |       Name        | Type  |           Access privileges            | Column privileges | Policies \n--------+-------------------+-------+----------------------------------------+-------------------+----------\n public | _hyper_2_10_chunk | table | super_user=arwdDxt/super_user         +|                   | \n        |                   |       | default_perm_user_2=arwdDxt/super_user |                   | \n public | _hyper_2_8_chunk  | table | super_user=arwdDxt/super_user         +|                   | \n        |                   |       | default_perm_user_2=arwdDxt/super_user |                   | \n public | _hyper_2_9_chunk  | table | super_user=arwdDxt/super_user         +|                   | \n        |                   |       | default_perm_user_2=arwdDxt/super_user |                   | \n\n-- REVOKE ALL and check privileges\nREVOKE ALL ON ALL TABLES  IN SCHEMA public FROM :ROLE_DEFAULT_PERM_USER_2;\n\\z measurements\n                                      Access privileges\n Schema |     Name     | Type  |       Access privileges       | Column privileges | Policies \n--------+--------------+-------+-------------------------------+-------------------+----------\n public | measurements | table | super_user=arwdDxt/super_user |                   | \n\n\\z conditions\n                                     Access privileges\n Schema |    Name    | Type  |       Access privileges       | Column privileges | Policies \n--------+------------+-------+-------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxt/super_user+|                   | \n        |            |       | =r/super_user                 |                   | \n\n\\z public.*chunk\n                                         Access privileges\n Schema |       Name        | Type  |       Access privileges       | Column privileges | Policies \n--------+-------------------+-------+-------------------------------+-------------------+----------\n public | _hyper_2_10_chunk | table | super_user=arwdDxt/super_user |                   | \n public | _hyper_2_8_chunk  | table | super_user=arwdDxt/super_user |                   | \n public | _hyper_2_9_chunk  | table | super_user=arwdDxt/super_user |                   | \n\n-- GRANT/REVOKE in an empty schema (Issue #4581)\nCREATE SCHEMA test_grant;\nGRANT ALL ON ALL TABLES IN SCHEMA test_grant TO :ROLE_DEFAULT_PERM_USER_2;\nREVOKE ALL ON ALL TABLES IN SCHEMA test_grant FROM :ROLE_DEFAULT_PERM_USER_2;\n"
  },
  {
    "path": "test/expected/grant_hypertable-16.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE TABLE conditions(\n    time TIMESTAMPTZ NOT NULL,\n    device INTEGER,\n    temperature FLOAT\n);\n-- Create a hypertable and show that it does not have any privileges\nSELECT * FROM create_hypertable('conditions', 'time', chunk_time_interval => '5 days'::interval);\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             1 | public      | conditions | t\n\nINSERT INTO conditions\nSELECT time, (random()*30)::int, random()*80 - 40\nFROM generate_series('2018-12-01 00:00'::timestamp, '2018-12-10 00:00'::timestamp, '1h') AS time;\n\\z conditions\n                               Access privileges\n Schema |    Name    | Type  | Access privileges | Column privileges | Policies \n--------+------------+-------+-------------------+-------------------+----------\n public | conditions | table |                   |                   | \n\n\\z _timescaledb_internal.*chunk\n                                          Access privileges\n        Schema         |       Name       | Type  | Access privileges | Column privileges | Policies \n-----------------------+------------------+-------+-------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table |                   |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table |                   |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table |                   |                   | \n\n-- Add privileges and show that they propagate to the chunks\nGRANT SELECT, INSERT ON conditions TO PUBLIC;\n\\z conditions\n                                     Access privileges\n Schema |    Name    | Type  |       Access privileges       | Column privileges | Policies \n--------+------------+-------+-------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxt/super_user+|                   | \n        |            |       | =ar/super_user                |                   | \n\n\\z _timescaledb_internal.*chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges       | Column privileges | Policies \n-----------------------+------------------+-------+-------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =ar/super_user                |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =ar/super_user                |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =ar/super_user                |                   | \n\n-- Create some more chunks and show that they also get the privileges.\nINSERT INTO conditions\nSELECT time, (random()*30)::int, random()*80 - 40\nFROM generate_series('2018-12-10 00:00'::timestamp, '2018-12-20 00:00'::timestamp, '1h') AS time;\n\\z conditions\n                                     Access privileges\n Schema |    Name    | Type  |       Access privileges       | Column privileges | Policies \n--------+------------+-------+-------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxt/super_user+|                   | \n        |            |       | =ar/super_user                |                   | \n\n\\z _timescaledb_internal.*chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges       | Column privileges | Policies \n-----------------------+------------------+-------+-------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =ar/super_user                |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =ar/super_user                |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =ar/super_user                |                   | \n _timescaledb_internal | _hyper_1_4_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =ar/super_user                |                   | \n _timescaledb_internal | _hyper_1_5_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =ar/super_user                |                   | \n\n-- Revoke one of the privileges and show that it propagate to the\n-- chunks.\nREVOKE INSERT ON conditions FROM PUBLIC;\n\\z conditions\n                                     Access privileges\n Schema |    Name    | Type  |       Access privileges       | Column privileges | Policies \n--------+------------+-------+-------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxt/super_user+|                   | \n        |            |       | =r/super_user                 |                   | \n\n\\z _timescaledb_internal.*chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges       | Column privileges | Policies \n-----------------------+------------------+-------+-------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n _timescaledb_internal | _hyper_1_4_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n _timescaledb_internal | _hyper_1_5_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n\n-- Add some more chunks and show that it inherits the grants from the\n-- hypertable.\nINSERT INTO conditions\nSELECT time, (random()*30)::int, random()*80 - 40\nFROM generate_series('2018-12-20 00:00'::timestamp, '2018-12-30 00:00'::timestamp, '1h') AS time;\n\\z conditions\n                                     Access privileges\n Schema |    Name    | Type  |       Access privileges       | Column privileges | Policies \n--------+------------+-------+-------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxt/super_user+|                   | \n        |            |       | =r/super_user                 |                   | \n\n\\z _timescaledb_internal.*chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges       | Column privileges | Policies \n-----------------------+------------------+-------+-------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n _timescaledb_internal | _hyper_1_4_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n _timescaledb_internal | _hyper_1_5_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n _timescaledb_internal | _hyper_1_6_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n _timescaledb_internal | _hyper_1_7_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n\n-- Change grants of one chunk explicitly and check that it is possible\n\\z _timescaledb_internal._hyper_1_1_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges       | Column privileges | Policies \n-----------------------+------------------+-------+-------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n\nGRANT UPDATE ON _timescaledb_internal._hyper_1_1_chunk TO PUBLIC;\n\\z _timescaledb_internal._hyper_1_1_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges       | Column privileges | Policies \n-----------------------+------------------+-------+-------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =rw/super_user                |                   | \n\nREVOKE SELECT ON _timescaledb_internal._hyper_1_1_chunk FROM PUBLIC;\n\\z _timescaledb_internal._hyper_1_1_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges       | Column privileges | Policies \n-----------------------+------------------+-------+-------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =w/super_user                 |                   | \n\n-- Check that revoking a permission first on the chunk and then on the\n-- hypertable that was added through the hypertable (INSERT and\n-- SELECT, in this case) still do not copy permissions from the\n-- hypertable (so there should not be a select permission to public on\n-- the chunk but there should be one on the hypertable).\nGRANT INSERT ON conditions TO PUBLIC;\n\\z conditions\n                                     Access privileges\n Schema |    Name    | Type  |       Access privileges       | Column privileges | Policies \n--------+------------+-------+-------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxt/super_user+|                   | \n        |            |       | =ar/super_user                |                   | \n\n\\z _timescaledb_internal._hyper_1_2_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges       | Column privileges | Policies \n-----------------------+------------------+-------+-------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =ar/super_user                |                   | \n\nREVOKE SELECT ON _timescaledb_internal._hyper_1_2_chunk FROM PUBLIC;\nREVOKE INSERT ON conditions FROM PUBLIC;\n\\z conditions\n                                     Access privileges\n Schema |    Name    | Type  |       Access privileges       | Column privileges | Policies \n--------+------------+-------+-------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxt/super_user+|                   | \n        |            |       | =r/super_user                 |                   | \n\n\\z _timescaledb_internal._hyper_1_2_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges       | Column privileges | Policies \n-----------------------+------------------+-------+-------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxt/super_user |                   | \n\n-- Check that granting permissions through hypertable does not remove\n-- separate grants on chunk.\nGRANT UPDATE ON _timescaledb_internal._hyper_1_3_chunk TO PUBLIC;\n\\z conditions\n                                     Access privileges\n Schema |    Name    | Type  |       Access privileges       | Column privileges | Policies \n--------+------------+-------+-------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxt/super_user+|                   | \n        |            |       | =r/super_user                 |                   | \n\n\\z _timescaledb_internal._hyper_1_3_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges       | Column privileges | Policies \n-----------------------+------------------+-------+-------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =rw/super_user                |                   | \n\nGRANT INSERT ON conditions TO PUBLIC;\nREVOKE INSERT ON conditions FROM PUBLIC;\n\\z conditions\n                                     Access privileges\n Schema |    Name    | Type  |       Access privileges       | Column privileges | Policies \n--------+------------+-------+-------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxt/super_user+|                   | \n        |            |       | =r/super_user                 |                   | \n\n\\z _timescaledb_internal._hyper_1_3_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges       | Column privileges | Policies \n-----------------------+------------------+-------+-------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =rw/super_user                |                   | \n\n-- Check that GRANT ALL IN SCHEMA adds privileges to the parent\n-- and also goes to chunks in another schema\nGRANT ALL ON ALL TABLES  IN SCHEMA public TO :ROLE_DEFAULT_PERM_USER_2;\n\\z conditions\n                                          Access privileges\n Schema |    Name    | Type  |           Access privileges            | Column privileges | Policies \n--------+------------+-------+----------------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxt/super_user         +|                   | \n        |            |       | =r/super_user                         +|                   | \n        |            |       | default_perm_user_2=arwdDxt/super_user |                   | \n\n\\z _timescaledb_internal.*chunk\n                                                    Access privileges\n        Schema         |       Name       | Type  |           Access privileges            | Column privileges | Policies \n-----------------------+------------------+-------+----------------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxt/super_user         +|                   | \n                       |                  |       | =w/super_user                         +|                   | \n                       |                  |       | default_perm_user_2=arwdDxt/super_user |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxt/super_user         +|                   | \n                       |                  |       | default_perm_user_2=arwdDxt/super_user |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxt/super_user         +|                   | \n                       |                  |       | =rw/super_user                        +|                   | \n                       |                  |       | default_perm_user_2=arwdDxt/super_user |                   | \n _timescaledb_internal | _hyper_1_4_chunk | table | super_user=arwdDxt/super_user         +|                   | \n                       |                  |       | =r/super_user                         +|                   | \n                       |                  |       | default_perm_user_2=arwdDxt/super_user |                   | \n _timescaledb_internal | _hyper_1_5_chunk | table | super_user=arwdDxt/super_user         +|                   | \n                       |                  |       | =r/super_user                         +|                   | \n                       |                  |       | default_perm_user_2=arwdDxt/super_user |                   | \n _timescaledb_internal | _hyper_1_6_chunk | table | super_user=arwdDxt/super_user         +|                   | \n                       |                  |       | =r/super_user                         +|                   | \n                       |                  |       | default_perm_user_2=arwdDxt/super_user |                   | \n _timescaledb_internal | _hyper_1_7_chunk | table | super_user=arwdDxt/super_user         +|                   | \n                       |                  |       | =r/super_user                         +|                   | \n                       |                  |       | default_perm_user_2=arwdDxt/super_user |                   | \n\n-- Check that REVOKE ALL IN SCHEMA removes privileges of the parent\n-- and also goes to chunks in another schema\nREVOKE ALL ON ALL TABLES  IN SCHEMA public FROM :ROLE_DEFAULT_PERM_USER_2;\n\\z conditions\n                                     Access privileges\n Schema |    Name    | Type  |       Access privileges       | Column privileges | Policies \n--------+------------+-------+-------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxt/super_user+|                   | \n        |            |       | =r/super_user                 |                   | \n\n\\z _timescaledb_internal.*chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges       | Column privileges | Policies \n-----------------------+------------------+-------+-------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =w/super_user                 |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxt/super_user |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =rw/super_user                |                   | \n _timescaledb_internal | _hyper_1_4_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n _timescaledb_internal | _hyper_1_5_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n _timescaledb_internal | _hyper_1_6_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n _timescaledb_internal | _hyper_1_7_chunk | table | super_user=arwdDxt/super_user+|                   | \n                       |                  |       | =r/super_user                 |                   | \n\n-- Create chunks in the same schema as the hypertable and check that\n-- they also get the same privileges as the hypertable\nCREATE TABLE measurements(\n    time TIMESTAMPTZ NOT NULL,\n    device INTEGER,\n    temperature FLOAT\n);\n-- Create a hypertable with chunks in the same schema\nSELECT * FROM create_hypertable('public.measurements', 'time', chunk_time_interval => '5 days'::interval, associated_schema_name => 'public');\n hypertable_id | schema_name |  table_name  | created \n---------------+-------------+--------------+---------\n             2 | public      | measurements | t\n\nINSERT INTO measurements\nSELECT time, (random()*30)::int, random()*80 - 40\nFROM generate_series('2018-12-01 00:00'::timestamp, '2018-12-10 00:00'::timestamp, '1h') AS time;\n-- GRANT ALL and check privileges\nGRANT ALL ON ALL TABLES  IN SCHEMA public TO :ROLE_DEFAULT_PERM_USER_2;\n\\z measurements\n                                           Access privileges\n Schema |     Name     | Type  |           Access privileges            | Column privileges | Policies \n--------+--------------+-------+----------------------------------------+-------------------+----------\n public | measurements | table | super_user=arwdDxt/super_user         +|                   | \n        |              |       | default_perm_user_2=arwdDxt/super_user |                   | \n\n\\z conditions\n                                          Access privileges\n Schema |    Name    | Type  |           Access privileges            | Column privileges | Policies \n--------+------------+-------+----------------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxt/super_user         +|                   | \n        |            |       | =r/super_user                         +|                   | \n        |            |       | default_perm_user_2=arwdDxt/super_user |                   | \n\n\\z public.*chunk\n                                             Access privileges\n Schema |       Name        | Type  |           Access privileges            | Column privileges | Policies \n--------+-------------------+-------+----------------------------------------+-------------------+----------\n public | _hyper_2_10_chunk | table | super_user=arwdDxt/super_user         +|                   | \n        |                   |       | default_perm_user_2=arwdDxt/super_user |                   | \n public | _hyper_2_8_chunk  | table | super_user=arwdDxt/super_user         +|                   | \n        |                   |       | default_perm_user_2=arwdDxt/super_user |                   | \n public | _hyper_2_9_chunk  | table | super_user=arwdDxt/super_user         +|                   | \n        |                   |       | default_perm_user_2=arwdDxt/super_user |                   | \n\n-- REVOKE ALL and check privileges\nREVOKE ALL ON ALL TABLES  IN SCHEMA public FROM :ROLE_DEFAULT_PERM_USER_2;\n\\z measurements\n                                      Access privileges\n Schema |     Name     | Type  |       Access privileges       | Column privileges | Policies \n--------+--------------+-------+-------------------------------+-------------------+----------\n public | measurements | table | super_user=arwdDxt/super_user |                   | \n\n\\z conditions\n                                     Access privileges\n Schema |    Name    | Type  |       Access privileges       | Column privileges | Policies \n--------+------------+-------+-------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxt/super_user+|                   | \n        |            |       | =r/super_user                 |                   | \n\n\\z public.*chunk\n                                         Access privileges\n Schema |       Name        | Type  |       Access privileges       | Column privileges | Policies \n--------+-------------------+-------+-------------------------------+-------------------+----------\n public | _hyper_2_10_chunk | table | super_user=arwdDxt/super_user |                   | \n public | _hyper_2_8_chunk  | table | super_user=arwdDxt/super_user |                   | \n public | _hyper_2_9_chunk  | table | super_user=arwdDxt/super_user |                   | \n\n-- GRANT/REVOKE in an empty schema (Issue #4581)\nCREATE SCHEMA test_grant;\nGRANT ALL ON ALL TABLES IN SCHEMA test_grant TO :ROLE_DEFAULT_PERM_USER_2;\nREVOKE ALL ON ALL TABLES IN SCHEMA test_grant FROM :ROLE_DEFAULT_PERM_USER_2;\n"
  },
  {
    "path": "test/expected/grant_hypertable-17.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE TABLE conditions(\n    time TIMESTAMPTZ NOT NULL,\n    device INTEGER,\n    temperature FLOAT\n);\n-- Create a hypertable and show that it does not have any privileges\nSELECT * FROM create_hypertable('conditions', 'time', chunk_time_interval => '5 days'::interval);\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             1 | public      | conditions | t\n\nINSERT INTO conditions\nSELECT time, (random()*30)::int, random()*80 - 40\nFROM generate_series('2018-12-01 00:00'::timestamp, '2018-12-10 00:00'::timestamp, '1h') AS time;\n\\z conditions\n                               Access privileges\n Schema |    Name    | Type  | Access privileges | Column privileges | Policies \n--------+------------+-------+-------------------+-------------------+----------\n public | conditions | table |                   |                   | \n\n\\z _timescaledb_internal.*chunk\n                                          Access privileges\n        Schema         |       Name       | Type  | Access privileges | Column privileges | Policies \n-----------------------+------------------+-------+-------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table |                   |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table |                   |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table |                   |                   | \n\n-- Add privileges and show that they propagate to the chunks\nGRANT SELECT, INSERT ON conditions TO PUBLIC;\n\\z conditions\n                                      Access privileges\n Schema |    Name    | Type  |       Access privileges        | Column privileges | Policies \n--------+------------+-------+--------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxtm/super_user+|                   | \n        |            |       | =ar/super_user                 |                   | \n\n\\z _timescaledb_internal.*chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =ar/super_user                 |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =ar/super_user                 |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =ar/super_user                 |                   | \n\n-- Create some more chunks and show that they also get the privileges.\nINSERT INTO conditions\nSELECT time, (random()*30)::int, random()*80 - 40\nFROM generate_series('2018-12-10 00:00'::timestamp, '2018-12-20 00:00'::timestamp, '1h') AS time;\n\\z conditions\n                                      Access privileges\n Schema |    Name    | Type  |       Access privileges        | Column privileges | Policies \n--------+------------+-------+--------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxtm/super_user+|                   | \n        |            |       | =ar/super_user                 |                   | \n\n\\z _timescaledb_internal.*chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =ar/super_user                 |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =ar/super_user                 |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =ar/super_user                 |                   | \n _timescaledb_internal | _hyper_1_4_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =ar/super_user                 |                   | \n _timescaledb_internal | _hyper_1_5_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =ar/super_user                 |                   | \n\n-- Revoke one of the privileges and show that it propagate to the\n-- chunks.\nREVOKE INSERT ON conditions FROM PUBLIC;\n\\z conditions\n                                      Access privileges\n Schema |    Name    | Type  |       Access privileges        | Column privileges | Policies \n--------+------------+-------+--------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxtm/super_user+|                   | \n        |            |       | =r/super_user                  |                   | \n\n\\z _timescaledb_internal.*chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n _timescaledb_internal | _hyper_1_4_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n _timescaledb_internal | _hyper_1_5_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n\n-- Add some more chunks and show that it inherits the grants from the\n-- hypertable.\nINSERT INTO conditions\nSELECT time, (random()*30)::int, random()*80 - 40\nFROM generate_series('2018-12-20 00:00'::timestamp, '2018-12-30 00:00'::timestamp, '1h') AS time;\n\\z conditions\n                                      Access privileges\n Schema |    Name    | Type  |       Access privileges        | Column privileges | Policies \n--------+------------+-------+--------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxtm/super_user+|                   | \n        |            |       | =r/super_user                  |                   | \n\n\\z _timescaledb_internal.*chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n _timescaledb_internal | _hyper_1_4_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n _timescaledb_internal | _hyper_1_5_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n _timescaledb_internal | _hyper_1_6_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n _timescaledb_internal | _hyper_1_7_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n\n-- Change grants of one chunk explicitly and check that it is possible\n\\z _timescaledb_internal._hyper_1_1_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n\nGRANT UPDATE ON _timescaledb_internal._hyper_1_1_chunk TO PUBLIC;\n\\z _timescaledb_internal._hyper_1_1_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =rw/super_user                 |                   | \n\nREVOKE SELECT ON _timescaledb_internal._hyper_1_1_chunk FROM PUBLIC;\n\\z _timescaledb_internal._hyper_1_1_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =w/super_user                  |                   | \n\n-- Check that revoking a permission first on the chunk and then on the\n-- hypertable that was added through the hypertable (INSERT and\n-- SELECT, in this case) still do not copy permissions from the\n-- hypertable (so there should not be a select permission to public on\n-- the chunk but there should be one on the hypertable).\nGRANT INSERT ON conditions TO PUBLIC;\n\\z conditions\n                                      Access privileges\n Schema |    Name    | Type  |       Access privileges        | Column privileges | Policies \n--------+------------+-------+--------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxtm/super_user+|                   | \n        |            |       | =ar/super_user                 |                   | \n\n\\z _timescaledb_internal._hyper_1_2_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =ar/super_user                 |                   | \n\nREVOKE SELECT ON _timescaledb_internal._hyper_1_2_chunk FROM PUBLIC;\nREVOKE INSERT ON conditions FROM PUBLIC;\n\\z conditions\n                                      Access privileges\n Schema |    Name    | Type  |       Access privileges        | Column privileges | Policies \n--------+------------+-------+--------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxtm/super_user+|                   | \n        |            |       | =r/super_user                  |                   | \n\n\\z _timescaledb_internal._hyper_1_2_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxtm/super_user |                   | \n\n-- Check that granting permissions through hypertable does not remove\n-- separate grants on chunk.\nGRANT UPDATE ON _timescaledb_internal._hyper_1_3_chunk TO PUBLIC;\n\\z conditions\n                                      Access privileges\n Schema |    Name    | Type  |       Access privileges        | Column privileges | Policies \n--------+------------+-------+--------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxtm/super_user+|                   | \n        |            |       | =r/super_user                  |                   | \n\n\\z _timescaledb_internal._hyper_1_3_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =rw/super_user                 |                   | \n\nGRANT INSERT ON conditions TO PUBLIC;\nREVOKE INSERT ON conditions FROM PUBLIC;\n\\z conditions\n                                      Access privileges\n Schema |    Name    | Type  |       Access privileges        | Column privileges | Policies \n--------+------------+-------+--------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxtm/super_user+|                   | \n        |            |       | =r/super_user                  |                   | \n\n\\z _timescaledb_internal._hyper_1_3_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =rw/super_user                 |                   | \n\n-- Check that GRANT ALL IN SCHEMA adds privileges to the parent\n-- and also goes to chunks in another schema\nGRANT ALL ON ALL TABLES  IN SCHEMA public TO :ROLE_DEFAULT_PERM_USER_2;\n\\z conditions\n                                          Access privileges\n Schema |    Name    | Type  |            Access privileges            | Column privileges | Policies \n--------+------------+-------+-----------------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxtm/super_user         +|                   | \n        |            |       | =r/super_user                          +|                   | \n        |            |       | default_perm_user_2=arwdDxtm/super_user |                   | \n\n\\z _timescaledb_internal.*chunk\n                                                     Access privileges\n        Schema         |       Name       | Type  |            Access privileges            | Column privileges | Policies \n-----------------------+------------------+-------+-----------------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxtm/super_user         +|                   | \n                       |                  |       | =w/super_user                          +|                   | \n                       |                  |       | default_perm_user_2=arwdDxtm/super_user |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxtm/super_user         +|                   | \n                       |                  |       | default_perm_user_2=arwdDxtm/super_user |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxtm/super_user         +|                   | \n                       |                  |       | =rw/super_user                         +|                   | \n                       |                  |       | default_perm_user_2=arwdDxtm/super_user |                   | \n _timescaledb_internal | _hyper_1_4_chunk | table | super_user=arwdDxtm/super_user         +|                   | \n                       |                  |       | =r/super_user                          +|                   | \n                       |                  |       | default_perm_user_2=arwdDxtm/super_user |                   | \n _timescaledb_internal | _hyper_1_5_chunk | table | super_user=arwdDxtm/super_user         +|                   | \n                       |                  |       | =r/super_user                          +|                   | \n                       |                  |       | default_perm_user_2=arwdDxtm/super_user |                   | \n _timescaledb_internal | _hyper_1_6_chunk | table | super_user=arwdDxtm/super_user         +|                   | \n                       |                  |       | =r/super_user                          +|                   | \n                       |                  |       | default_perm_user_2=arwdDxtm/super_user |                   | \n _timescaledb_internal | _hyper_1_7_chunk | table | super_user=arwdDxtm/super_user         +|                   | \n                       |                  |       | =r/super_user                          +|                   | \n                       |                  |       | default_perm_user_2=arwdDxtm/super_user |                   | \n\n-- Check that REVOKE ALL IN SCHEMA removes privileges of the parent\n-- and also goes to chunks in another schema\nREVOKE ALL ON ALL TABLES  IN SCHEMA public FROM :ROLE_DEFAULT_PERM_USER_2;\n\\z conditions\n                                      Access privileges\n Schema |    Name    | Type  |       Access privileges        | Column privileges | Policies \n--------+------------+-------+--------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxtm/super_user+|                   | \n        |            |       | =r/super_user                  |                   | \n\n\\z _timescaledb_internal.*chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =w/super_user                  |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxtm/super_user |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =rw/super_user                 |                   | \n _timescaledb_internal | _hyper_1_4_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n _timescaledb_internal | _hyper_1_5_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n _timescaledb_internal | _hyper_1_6_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n _timescaledb_internal | _hyper_1_7_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n\n-- Create chunks in the same schema as the hypertable and check that\n-- they also get the same privileges as the hypertable\nCREATE TABLE measurements(\n    time TIMESTAMPTZ NOT NULL,\n    device INTEGER,\n    temperature FLOAT\n);\n-- Create a hypertable with chunks in the same schema\nSELECT * FROM create_hypertable('public.measurements', 'time', chunk_time_interval => '5 days'::interval, associated_schema_name => 'public');\n hypertable_id | schema_name |  table_name  | created \n---------------+-------------+--------------+---------\n             2 | public      | measurements | t\n\nINSERT INTO measurements\nSELECT time, (random()*30)::int, random()*80 - 40\nFROM generate_series('2018-12-01 00:00'::timestamp, '2018-12-10 00:00'::timestamp, '1h') AS time;\n-- GRANT ALL and check privileges\nGRANT ALL ON ALL TABLES  IN SCHEMA public TO :ROLE_DEFAULT_PERM_USER_2;\n\\z measurements\n                                           Access privileges\n Schema |     Name     | Type  |            Access privileges            | Column privileges | Policies \n--------+--------------+-------+-----------------------------------------+-------------------+----------\n public | measurements | table | super_user=arwdDxtm/super_user         +|                   | \n        |              |       | default_perm_user_2=arwdDxtm/super_user |                   | \n\n\\z conditions\n                                          Access privileges\n Schema |    Name    | Type  |            Access privileges            | Column privileges | Policies \n--------+------------+-------+-----------------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxtm/super_user         +|                   | \n        |            |       | =r/super_user                          +|                   | \n        |            |       | default_perm_user_2=arwdDxtm/super_user |                   | \n\n\\z public.*chunk\n                                              Access privileges\n Schema |       Name        | Type  |            Access privileges            | Column privileges | Policies \n--------+-------------------+-------+-----------------------------------------+-------------------+----------\n public | _hyper_2_10_chunk | table | super_user=arwdDxtm/super_user         +|                   | \n        |                   |       | default_perm_user_2=arwdDxtm/super_user |                   | \n public | _hyper_2_8_chunk  | table | super_user=arwdDxtm/super_user         +|                   | \n        |                   |       | default_perm_user_2=arwdDxtm/super_user |                   | \n public | _hyper_2_9_chunk  | table | super_user=arwdDxtm/super_user         +|                   | \n        |                   |       | default_perm_user_2=arwdDxtm/super_user |                   | \n\n-- REVOKE ALL and check privileges\nREVOKE ALL ON ALL TABLES  IN SCHEMA public FROM :ROLE_DEFAULT_PERM_USER_2;\n\\z measurements\n                                       Access privileges\n Schema |     Name     | Type  |       Access privileges        | Column privileges | Policies \n--------+--------------+-------+--------------------------------+-------------------+----------\n public | measurements | table | super_user=arwdDxtm/super_user |                   | \n\n\\z conditions\n                                      Access privileges\n Schema |    Name    | Type  |       Access privileges        | Column privileges | Policies \n--------+------------+-------+--------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxtm/super_user+|                   | \n        |            |       | =r/super_user                  |                   | \n\n\\z public.*chunk\n                                         Access privileges\n Schema |       Name        | Type  |       Access privileges        | Column privileges | Policies \n--------+-------------------+-------+--------------------------------+-------------------+----------\n public | _hyper_2_10_chunk | table | super_user=arwdDxtm/super_user |                   | \n public | _hyper_2_8_chunk  | table | super_user=arwdDxtm/super_user |                   | \n public | _hyper_2_9_chunk  | table | super_user=arwdDxtm/super_user |                   | \n\n-- GRANT/REVOKE in an empty schema (Issue #4581)\nCREATE SCHEMA test_grant;\nGRANT ALL ON ALL TABLES IN SCHEMA test_grant TO :ROLE_DEFAULT_PERM_USER_2;\nREVOKE ALL ON ALL TABLES IN SCHEMA test_grant FROM :ROLE_DEFAULT_PERM_USER_2;\n"
  },
  {
    "path": "test/expected/grant_hypertable-18.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE TABLE conditions(\n    time TIMESTAMPTZ NOT NULL,\n    device INTEGER,\n    temperature FLOAT\n);\n-- Create a hypertable and show that it does not have any privileges\nSELECT * FROM create_hypertable('conditions', 'time', chunk_time_interval => '5 days'::interval);\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             1 | public      | conditions | t\n\nINSERT INTO conditions\nSELECT time, (random()*30)::int, random()*80 - 40\nFROM generate_series('2018-12-01 00:00'::timestamp, '2018-12-10 00:00'::timestamp, '1h') AS time;\n\\z conditions\n                               Access privileges\n Schema |    Name    | Type  | Access privileges | Column privileges | Policies \n--------+------------+-------+-------------------+-------------------+----------\n public | conditions | table |                   |                   | \n\n\\z _timescaledb_internal.*chunk\n                                          Access privileges\n        Schema         |       Name       | Type  | Access privileges | Column privileges | Policies \n-----------------------+------------------+-------+-------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table |                   |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table |                   |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table |                   |                   | \n\n-- Add privileges and show that they propagate to the chunks\nGRANT SELECT, INSERT ON conditions TO PUBLIC;\n\\z conditions\n                                      Access privileges\n Schema |    Name    | Type  |       Access privileges        | Column privileges | Policies \n--------+------------+-------+--------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxtm/super_user+|                   | \n        |            |       | =ar/super_user                 |                   | \n\n\\z _timescaledb_internal.*chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =ar/super_user                 |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =ar/super_user                 |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =ar/super_user                 |                   | \n\n-- Create some more chunks and show that they also get the privileges.\nINSERT INTO conditions\nSELECT time, (random()*30)::int, random()*80 - 40\nFROM generate_series('2018-12-10 00:00'::timestamp, '2018-12-20 00:00'::timestamp, '1h') AS time;\n\\z conditions\n                                      Access privileges\n Schema |    Name    | Type  |       Access privileges        | Column privileges | Policies \n--------+------------+-------+--------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxtm/super_user+|                   | \n        |            |       | =ar/super_user                 |                   | \n\n\\z _timescaledb_internal.*chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =ar/super_user                 |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =ar/super_user                 |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =ar/super_user                 |                   | \n _timescaledb_internal | _hyper_1_4_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =ar/super_user                 |                   | \n _timescaledb_internal | _hyper_1_5_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =ar/super_user                 |                   | \n\n-- Revoke one of the privileges and show that it propagate to the\n-- chunks.\nREVOKE INSERT ON conditions FROM PUBLIC;\n\\z conditions\n                                      Access privileges\n Schema |    Name    | Type  |       Access privileges        | Column privileges | Policies \n--------+------------+-------+--------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxtm/super_user+|                   | \n        |            |       | =r/super_user                  |                   | \n\n\\z _timescaledb_internal.*chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n _timescaledb_internal | _hyper_1_4_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n _timescaledb_internal | _hyper_1_5_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n\n-- Add some more chunks and show that it inherits the grants from the\n-- hypertable.\nINSERT INTO conditions\nSELECT time, (random()*30)::int, random()*80 - 40\nFROM generate_series('2018-12-20 00:00'::timestamp, '2018-12-30 00:00'::timestamp, '1h') AS time;\n\\z conditions\n                                      Access privileges\n Schema |    Name    | Type  |       Access privileges        | Column privileges | Policies \n--------+------------+-------+--------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxtm/super_user+|                   | \n        |            |       | =r/super_user                  |                   | \n\n\\z _timescaledb_internal.*chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n _timescaledb_internal | _hyper_1_4_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n _timescaledb_internal | _hyper_1_5_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n _timescaledb_internal | _hyper_1_6_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n _timescaledb_internal | _hyper_1_7_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n\n-- Change grants of one chunk explicitly and check that it is possible\n\\z _timescaledb_internal._hyper_1_1_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n\nGRANT UPDATE ON _timescaledb_internal._hyper_1_1_chunk TO PUBLIC;\n\\z _timescaledb_internal._hyper_1_1_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =rw/super_user                 |                   | \n\nREVOKE SELECT ON _timescaledb_internal._hyper_1_1_chunk FROM PUBLIC;\n\\z _timescaledb_internal._hyper_1_1_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =w/super_user                  |                   | \n\n-- Check that revoking a permission first on the chunk and then on the\n-- hypertable that was added through the hypertable (INSERT and\n-- SELECT, in this case) still do not copy permissions from the\n-- hypertable (so there should not be a select permission to public on\n-- the chunk but there should be one on the hypertable).\nGRANT INSERT ON conditions TO PUBLIC;\n\\z conditions\n                                      Access privileges\n Schema |    Name    | Type  |       Access privileges        | Column privileges | Policies \n--------+------------+-------+--------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxtm/super_user+|                   | \n        |            |       | =ar/super_user                 |                   | \n\n\\z _timescaledb_internal._hyper_1_2_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =ar/super_user                 |                   | \n\nREVOKE SELECT ON _timescaledb_internal._hyper_1_2_chunk FROM PUBLIC;\nREVOKE INSERT ON conditions FROM PUBLIC;\n\\z conditions\n                                      Access privileges\n Schema |    Name    | Type  |       Access privileges        | Column privileges | Policies \n--------+------------+-------+--------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxtm/super_user+|                   | \n        |            |       | =r/super_user                  |                   | \n\n\\z _timescaledb_internal._hyper_1_2_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxtm/super_user |                   | \n\n-- Check that granting permissions through hypertable does not remove\n-- separate grants on chunk.\nGRANT UPDATE ON _timescaledb_internal._hyper_1_3_chunk TO PUBLIC;\n\\z conditions\n                                      Access privileges\n Schema |    Name    | Type  |       Access privileges        | Column privileges | Policies \n--------+------------+-------+--------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxtm/super_user+|                   | \n        |            |       | =r/super_user                  |                   | \n\n\\z _timescaledb_internal._hyper_1_3_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =rw/super_user                 |                   | \n\nGRANT INSERT ON conditions TO PUBLIC;\nREVOKE INSERT ON conditions FROM PUBLIC;\n\\z conditions\n                                      Access privileges\n Schema |    Name    | Type  |       Access privileges        | Column privileges | Policies \n--------+------------+-------+--------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxtm/super_user+|                   | \n        |            |       | =r/super_user                  |                   | \n\n\\z _timescaledb_internal._hyper_1_3_chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =rw/super_user                 |                   | \n\n-- Check that GRANT ALL IN SCHEMA adds privileges to the parent\n-- and also goes to chunks in another schema\nGRANT ALL ON ALL TABLES  IN SCHEMA public TO :ROLE_DEFAULT_PERM_USER_2;\n\\z conditions\n                                          Access privileges\n Schema |    Name    | Type  |            Access privileges            | Column privileges | Policies \n--------+------------+-------+-----------------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxtm/super_user         +|                   | \n        |            |       | =r/super_user                          +|                   | \n        |            |       | default_perm_user_2=arwdDxtm/super_user |                   | \n\n\\z _timescaledb_internal.*chunk\n                                                     Access privileges\n        Schema         |       Name       | Type  |            Access privileges            | Column privileges | Policies \n-----------------------+------------------+-------+-----------------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxtm/super_user         +|                   | \n                       |                  |       | =w/super_user                          +|                   | \n                       |                  |       | default_perm_user_2=arwdDxtm/super_user |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxtm/super_user         +|                   | \n                       |                  |       | default_perm_user_2=arwdDxtm/super_user |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxtm/super_user         +|                   | \n                       |                  |       | =rw/super_user                         +|                   | \n                       |                  |       | default_perm_user_2=arwdDxtm/super_user |                   | \n _timescaledb_internal | _hyper_1_4_chunk | table | super_user=arwdDxtm/super_user         +|                   | \n                       |                  |       | =r/super_user                          +|                   | \n                       |                  |       | default_perm_user_2=arwdDxtm/super_user |                   | \n _timescaledb_internal | _hyper_1_5_chunk | table | super_user=arwdDxtm/super_user         +|                   | \n                       |                  |       | =r/super_user                          +|                   | \n                       |                  |       | default_perm_user_2=arwdDxtm/super_user |                   | \n _timescaledb_internal | _hyper_1_6_chunk | table | super_user=arwdDxtm/super_user         +|                   | \n                       |                  |       | =r/super_user                          +|                   | \n                       |                  |       | default_perm_user_2=arwdDxtm/super_user |                   | \n _timescaledb_internal | _hyper_1_7_chunk | table | super_user=arwdDxtm/super_user         +|                   | \n                       |                  |       | =r/super_user                          +|                   | \n                       |                  |       | default_perm_user_2=arwdDxtm/super_user |                   | \n\n-- Check that REVOKE ALL IN SCHEMA removes privileges of the parent\n-- and also goes to chunks in another schema\nREVOKE ALL ON ALL TABLES  IN SCHEMA public FROM :ROLE_DEFAULT_PERM_USER_2;\n\\z conditions\n                                      Access privileges\n Schema |    Name    | Type  |       Access privileges        | Column privileges | Policies \n--------+------------+-------+--------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxtm/super_user+|                   | \n        |            |       | =r/super_user                  |                   | \n\n\\z _timescaledb_internal.*chunk\n                                                Access privileges\n        Schema         |       Name       | Type  |       Access privileges        | Column privileges | Policies \n-----------------------+------------------+-------+--------------------------------+-------------------+----------\n _timescaledb_internal | _hyper_1_1_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =w/super_user                  |                   | \n _timescaledb_internal | _hyper_1_2_chunk | table | super_user=arwdDxtm/super_user |                   | \n _timescaledb_internal | _hyper_1_3_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =rw/super_user                 |                   | \n _timescaledb_internal | _hyper_1_4_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n _timescaledb_internal | _hyper_1_5_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n _timescaledb_internal | _hyper_1_6_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n _timescaledb_internal | _hyper_1_7_chunk | table | super_user=arwdDxtm/super_user+|                   | \n                       |                  |       | =r/super_user                  |                   | \n\n-- Create chunks in the same schema as the hypertable and check that\n-- they also get the same privileges as the hypertable\nCREATE TABLE measurements(\n    time TIMESTAMPTZ NOT NULL,\n    device INTEGER,\n    temperature FLOAT\n);\n-- Create a hypertable with chunks in the same schema\nSELECT * FROM create_hypertable('public.measurements', 'time', chunk_time_interval => '5 days'::interval, associated_schema_name => 'public');\n hypertable_id | schema_name |  table_name  | created \n---------------+-------------+--------------+---------\n             2 | public      | measurements | t\n\nINSERT INTO measurements\nSELECT time, (random()*30)::int, random()*80 - 40\nFROM generate_series('2018-12-01 00:00'::timestamp, '2018-12-10 00:00'::timestamp, '1h') AS time;\n-- GRANT ALL and check privileges\nGRANT ALL ON ALL TABLES  IN SCHEMA public TO :ROLE_DEFAULT_PERM_USER_2;\n\\z measurements\n                                           Access privileges\n Schema |     Name     | Type  |            Access privileges            | Column privileges | Policies \n--------+--------------+-------+-----------------------------------------+-------------------+----------\n public | measurements | table | super_user=arwdDxtm/super_user         +|                   | \n        |              |       | default_perm_user_2=arwdDxtm/super_user |                   | \n\n\\z conditions\n                                          Access privileges\n Schema |    Name    | Type  |            Access privileges            | Column privileges | Policies \n--------+------------+-------+-----------------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxtm/super_user         +|                   | \n        |            |       | =r/super_user                          +|                   | \n        |            |       | default_perm_user_2=arwdDxtm/super_user |                   | \n\n\\z public.*chunk\n                                              Access privileges\n Schema |       Name        | Type  |            Access privileges            | Column privileges | Policies \n--------+-------------------+-------+-----------------------------------------+-------------------+----------\n public | _hyper_2_10_chunk | table | super_user=arwdDxtm/super_user         +|                   | \n        |                   |       | default_perm_user_2=arwdDxtm/super_user |                   | \n public | _hyper_2_8_chunk  | table | super_user=arwdDxtm/super_user         +|                   | \n        |                   |       | default_perm_user_2=arwdDxtm/super_user |                   | \n public | _hyper_2_9_chunk  | table | super_user=arwdDxtm/super_user         +|                   | \n        |                   |       | default_perm_user_2=arwdDxtm/super_user |                   | \n\n-- REVOKE ALL and check privileges\nREVOKE ALL ON ALL TABLES  IN SCHEMA public FROM :ROLE_DEFAULT_PERM_USER_2;\n\\z measurements\n                                       Access privileges\n Schema |     Name     | Type  |       Access privileges        | Column privileges | Policies \n--------+--------------+-------+--------------------------------+-------------------+----------\n public | measurements | table | super_user=arwdDxtm/super_user |                   | \n\n\\z conditions\n                                      Access privileges\n Schema |    Name    | Type  |       Access privileges        | Column privileges | Policies \n--------+------------+-------+--------------------------------+-------------------+----------\n public | conditions | table | super_user=arwdDxtm/super_user+|                   | \n        |            |       | =r/super_user                  |                   | \n\n\\z public.*chunk\n                                         Access privileges\n Schema |       Name        | Type  |       Access privileges        | Column privileges | Policies \n--------+-------------------+-------+--------------------------------+-------------------+----------\n public | _hyper_2_10_chunk | table | super_user=arwdDxtm/super_user |                   | \n public | _hyper_2_8_chunk  | table | super_user=arwdDxtm/super_user |                   | \n public | _hyper_2_9_chunk  | table | super_user=arwdDxtm/super_user |                   | \n\n-- GRANT/REVOKE in an empty schema (Issue #4581)\nCREATE SCHEMA test_grant;\nGRANT ALL ON ALL TABLES IN SCHEMA test_grant TO :ROLE_DEFAULT_PERM_USER_2;\nREVOKE ALL ON ALL TABLES IN SCHEMA test_grant FROM :ROLE_DEFAULT_PERM_USER_2;\n"
  },
  {
    "path": "test/expected/hash.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- Test hashing Const values. We should expect the same hash value for\n-- all integer types when values are compatible\nSELECT _timescaledb_functions.get_partition_hash(1::int);\n get_partition_hash \n--------------------\n          242423622\n\nSELECT _timescaledb_functions.get_partition_hash(1::bigint);\n get_partition_hash \n--------------------\n          242423622\n\nSELECT _timescaledb_functions.get_partition_hash(1::smallint);\n get_partition_hash \n--------------------\n          242423622\n\nSELECT _timescaledb_functions.get_partition_hash(true);\n get_partition_hash \n--------------------\n          242423622\n\n-- Floating point types should also hash the same for compatible values\nSELECT _timescaledb_functions.get_partition_hash(1.0::real);\n get_partition_hash \n--------------------\n          376496956\n\nSELECT _timescaledb_functions.get_partition_hash(1.0::double precision);\n get_partition_hash \n--------------------\n          376496956\n\n-- Float aliases\nSELECT _timescaledb_functions.get_partition_hash(1.0::float);\n get_partition_hash \n--------------------\n          376496956\n\nSELECT _timescaledb_functions.get_partition_hash(1.0::float4);\n get_partition_hash \n--------------------\n          376496956\n\nSELECT _timescaledb_functions.get_partition_hash(1.0::float8);\n get_partition_hash \n--------------------\n          376496956\n\nSELECT _timescaledb_functions.get_partition_hash(1.0::numeric);\n get_partition_hash \n--------------------\n         1324868424\n\n-- 'name' and '\"char\"' are internal PostgreSQL types, which are not\n-- intended for use by the general user. They are included here only\n-- for completeness\n-- https://www.postgresql.org/docs/10/static/datatype-character.html#datatype-character-special-table\nSELECT _timescaledb_functions.get_partition_hash('c'::name);\n get_partition_hash \n--------------------\n         1903644986\n\nSELECT _timescaledb_functions.get_partition_hash('c'::\"char\");\n get_partition_hash \n--------------------\n          203891234\n\n-- String and character hashes should also have the same output for\n-- compatible values\nSELECT _timescaledb_functions.get_partition_hash('c'::char);\n get_partition_hash \n--------------------\n         1903644986\n\nSELECT _timescaledb_functions.get_partition_hash('c'::varchar(2));\n get_partition_hash \n--------------------\n         1903644986\n\nSELECT _timescaledb_functions.get_partition_hash('c'::text);\n get_partition_hash \n--------------------\n         1903644986\n\n-- 'c' is 0x63 in ASCII\nSELECT _timescaledb_functions.get_partition_hash(E'\\\\x63'::bytea);\n get_partition_hash \n--------------------\n         1903644986\n\n-- Time and date types\nSELECT _timescaledb_functions.get_partition_hash(interval '1 day');\n get_partition_hash \n--------------------\n           93502988\n\nSELECT _timescaledb_functions.get_partition_hash('2017-03-22T09:18:23'::timestamp);\n get_partition_hash \n--------------------\n          307315039\n\nSELECT _timescaledb_functions.get_partition_hash('2017-03-22T09:18:23'::timestamptz);\n get_partition_hash \n--------------------\n         1195163597\n\nSELECT _timescaledb_functions.get_partition_hash('2017-03-22'::date);\n get_partition_hash \n--------------------\n          693590295\n\nSELECT _timescaledb_functions.get_partition_hash('10:00:00'::time);\n get_partition_hash \n--------------------\n         1380652790\n\nSELECT _timescaledb_functions.get_partition_hash('10:00:00-1'::timetz);\n get_partition_hash \n--------------------\n          769387140\n\n-- Other types\nSELECT _timescaledb_functions.get_partition_hash(ARRAY[1,2,3]);\n get_partition_hash \n--------------------\n         1822090118\n\nSELECT _timescaledb_functions.get_partition_hash('08002b:010203'::macaddr);\n get_partition_hash \n--------------------\n          294987870\n\nSELECT _timescaledb_functions.get_partition_hash('192.168.100.128/25'::cidr);\n get_partition_hash \n--------------------\n         1612896565\n\nSELECT _timescaledb_functions.get_partition_hash('192.168.100.128'::inet);\n get_partition_hash \n--------------------\n         1952516432\n\nSELECT _timescaledb_functions.get_partition_hash('2001:4f8:3:ba:2e0:81ff:fe22:d1f1'::inet);\n get_partition_hash \n--------------------\n          933321588\n\nSELECT _timescaledb_functions.get_partition_hash('2001:4f8:3:ba:2e0:81ff:fe22:d1f1/128'::cidr);\n get_partition_hash \n--------------------\n          933321588\n\nSELECT _timescaledb_functions.get_partition_hash('{ \"foo\": \"bar\" }'::jsonb);\n get_partition_hash \n--------------------\n          208840587\n\nSELECT _timescaledb_functions.get_partition_hash('4b6a5eec-b344-11e7-abc4-cec278b6b50a'::uuid);\n get_partition_hash \n--------------------\n          504202548\n\nSELECT _timescaledb_functions.get_partition_hash(1::regclass);\n get_partition_hash \n--------------------\n          242423622\n\nSELECT _timescaledb_functions.get_partition_hash(int4range(10, 20));\n get_partition_hash \n--------------------\n         1202375768\n\nSELECT _timescaledb_functions.get_partition_hash(int8range(10, 20));\n get_partition_hash \n--------------------\n         1202375768\n\nSELECT _timescaledb_functions.get_partition_hash(numrange(10, 20));\n get_partition_hash \n--------------------\n         1083987536\n\nSELECT _timescaledb_functions.get_partition_hash(tsrange('2017-03-22T09:18:23', '2017-03-23T09:18:23'));\n get_partition_hash \n--------------------\n         2079608838\n\nSELECT _timescaledb_functions.get_partition_hash(tstzrange('2017-03-22T09:18:23+01', '2017-03-23T09:18:23+00'));\n get_partition_hash \n--------------------\n         1255083771\n\n-- Test hashing Var values\nCREATE TABLE hash_test(id int, value text);\nINSERT INTO hash_test VALUES (1, 'test');\n-- Test Vars\nSELECT _timescaledb_functions.get_partition_hash(id) FROM hash_test;\n get_partition_hash \n--------------------\n          242423622\n\nSELECT _timescaledb_functions.get_partition_hash(value) FROM hash_test;\n get_partition_hash \n--------------------\n         1771415073\n\n-- Test coerced value\nSELECT _timescaledb_functions.get_partition_hash(id::text) FROM hash_test;\n get_partition_hash \n--------------------\n         1516350201\n\n-- Test legacy function that converts values to text first\nSELECT _timescaledb_functions.get_partition_for_key('4b6a5eec-b344-11e7-abc4-cec278b6b50a'::text);\n get_partition_for_key \n-----------------------\n             934882099\n\nSELECT _timescaledb_functions.get_partition_for_key('4b6a5eec-b344-11e7-abc4-cec278b6b50a'::varchar);\n get_partition_for_key \n-----------------------\n             934882099\n\nSELECT _timescaledb_functions.get_partition_for_key(187);\n get_partition_for_key \n-----------------------\n            1161071810\n\nSELECT _timescaledb_functions.get_partition_for_key(187::bigint);\n get_partition_for_key \n-----------------------\n            1161071810\n\nSELECT _timescaledb_functions.get_partition_for_key(187::numeric);\n get_partition_for_key \n-----------------------\n            1161071810\n\nSELECT _timescaledb_functions.get_partition_for_key(187::double precision);\n get_partition_for_key \n-----------------------\n            1161071810\n\nSELECT _timescaledb_functions.get_partition_for_key(int4range(10, 20));\n get_partition_for_key \n-----------------------\n             505239042\n\nSELECT _timescaledb_functions.get_partition_hash('08002b:010203'::macaddr);\n get_partition_hash \n--------------------\n          294987870\n\n-- Test inside IMMUTABLE function (Issue #4575)\nCREATE FUNCTION my_get_partition_hash(INTEGER) RETURNS INTEGER\nAS 'SELECT _timescaledb_functions.get_partition_hash($1);'\nLANGUAGE SQL IMMUTABLE;\nCREATE FUNCTION my_get_partition_for_key(INTEGER) RETURNS INTEGER\nAS 'SELECT _timescaledb_functions.get_partition_for_key($1);'\nLANGUAGE SQL IMMUTABLE;\nSELECT my_get_partition_hash(1);\n my_get_partition_hash \n-----------------------\n             242423622\n\nSELECT my_get_partition_for_key(1);\n my_get_partition_for_key \n--------------------------\n               1516350201\n\n"
  },
  {
    "path": "test/expected/histogram_test-15.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- table 1\nCREATE TABLE \"hitest1\"(key real, val varchar(40));\n-- insertions\nINSERT INTO \"hitest1\" VALUES(0, 'hi');\nINSERT INTO \"hitest1\" VALUES(1, 'sup');\nINSERT INTO \"hitest1\" VALUES(2, 'hello');\nINSERT INTO \"hitest1\" VALUES(3, 'yo');\nINSERT INTO \"hitest1\" VALUES(4, 'howdy');\nINSERT INTO \"hitest1\" VALUES(5, 'hola');\nINSERT INTO \"hitest1\" VALUES(6, 'ya');\nINSERT INTO \"hitest1\" VALUES(1, 'sup');\nINSERT INTO \"hitest1\" VALUES(2, 'hello');\nINSERT INTO \"hitest1\" VALUES(1, 'sup');\n-- table 2\nCREATE TABLE \"hitest2\"(name varchar(30), score integer, qualify boolean);\n-- insertions\nINSERT INTO \"hitest2\" VALUES('Tom', 6, TRUE);\nINSERT INTO \"hitest2\" VALUES('Mary', 4, FALSE);\nINSERT INTO \"hitest2\" VALUES('Jaq', 3, FALSE);\nINSERT INTO \"hitest2\" VALUES('Jane', 10, TRUE);\n-- standard 2 bucket\nSELECT histogram(key, 0, 9, 2) FROM hitest1;\n histogram \n-----------\n {0,8,2,0}\n\n-- standard multi-bucket\nSELECT histogram(key, 0, 9, 5) FROM hitest1;\n    histogram    \n-----------------\n {0,4,3,2,1,0,0}\n\n-- standard 3 bucket\nSELECT val, histogram(key, 0, 7, 3) FROM hitest1 GROUP BY val ORDER BY val;\n  val  |  histogram  \n-------+-------------\n hello | {0,2,0,0,0}\n hi    | {0,1,0,0,0}\n hola  | {0,0,0,1,0}\n howdy | {0,0,1,0,0}\n sup   | {0,3,0,0,0}\n ya    | {0,0,0,1,0}\n yo    | {0,0,1,0,0}\n\n-- standard element beneath lb\nSELECT histogram(key, 1, 7, 3) FROM hitest1;\n  histogram  \n-------------\n {1,5,2,2,0}\n\n-- standard element above ub\nSELECT histogram(key, 0, 3, 3) FROM hitest1;\n  histogram  \n-------------\n {0,1,3,2,4}\n\n-- standard element beneath and above lb and ub, respectively\nSELECT histogram(key, 1, 3, 2) FROM hitest1;\n histogram \n-----------\n {1,3,2,4}\n\n-- standard 1 bucket\nSELECT histogram(key, 1, 3, 1) FROM hitest1;\n histogram \n-----------\n {1,5,4}\n\n-- standard 2 bucket\nSELECT qualify, histogram(score, 0, 10, 2) FROM hitest2 GROUP BY qualify ORDER BY qualify;\n qualify | histogram \n---------+-----------\n f       | {0,2,0,0}\n t       | {0,0,1,1}\n\n-- standard multi-bucket\nSELECT qualify, histogram(score, 0, 10, 5) FROM hitest2 GROUP BY qualify ORDER BY qualify;\n qualify |    histogram    \n---------+-----------------\n f       | {0,0,1,1,0,0,0}\n t       | {0,0,0,0,1,0,1}\n\n-- check number of buckets is constant\n\\set ON_ERROR_STOP 0\nselect histogram(i,10,90,case when i=1 then 1 else 1000000 end) FROM generate_series(1,100) i;\nERROR:  number of buckets must not change between calls\n\\set ON_ERROR_STOP 1\nCREATE TABLE weather (\n       time TIMESTAMPTZ NOT NULL,\n       city TEXT,\n       temperature FLOAT,\n       PRIMARY KEY(time, city)\n);\n-- There is a bug in width_bucket() causing a NaN as a result, so we\n-- check that it is not causing a crash in histogram().\nSELECT * FROM create_hypertable('weather', 'time', 'city', 3);\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             1 | public      | weather    | t\n\nINSERT INTO weather VALUES\n       ('2023-02-10 09:16:51.133584+00','city1',10.4),\n       ('2023-02-10 11:16:51.611618+00','city1',10.3),\n       ('2023-02-10 06:58:59.999999+00','city1',10.3),\n       ('2023-02-10 01:58:59.999999+00','city1',10.3),\n       ('2023-02-09 01:58:59.999999+00','city1',10.3),\n       ('2023-02-10 08:58:59.999999+00','city1',10.3),\n       ('2023-03-23 06:12:02.73765+00 ','city1', 9.7),\n       ('2023-03-23 06:12:06.990998+00','city1',11.7);\n-- This will currently generate an error on PG15 and prior versions\n\\set ON_ERROR_STOP 0\nSELECT histogram(temperature, -1.79769e+308, 1.79769e+308,10) FROM weather GROUP BY city;\nERROR:  index -2147483648 from \"width_bucket\" out of range\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "test/expected/histogram_test-16.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- table 1\nCREATE TABLE \"hitest1\"(key real, val varchar(40));\n-- insertions\nINSERT INTO \"hitest1\" VALUES(0, 'hi');\nINSERT INTO \"hitest1\" VALUES(1, 'sup');\nINSERT INTO \"hitest1\" VALUES(2, 'hello');\nINSERT INTO \"hitest1\" VALUES(3, 'yo');\nINSERT INTO \"hitest1\" VALUES(4, 'howdy');\nINSERT INTO \"hitest1\" VALUES(5, 'hola');\nINSERT INTO \"hitest1\" VALUES(6, 'ya');\nINSERT INTO \"hitest1\" VALUES(1, 'sup');\nINSERT INTO \"hitest1\" VALUES(2, 'hello');\nINSERT INTO \"hitest1\" VALUES(1, 'sup');\n-- table 2\nCREATE TABLE \"hitest2\"(name varchar(30), score integer, qualify boolean);\n-- insertions\nINSERT INTO \"hitest2\" VALUES('Tom', 6, TRUE);\nINSERT INTO \"hitest2\" VALUES('Mary', 4, FALSE);\nINSERT INTO \"hitest2\" VALUES('Jaq', 3, FALSE);\nINSERT INTO \"hitest2\" VALUES('Jane', 10, TRUE);\n-- standard 2 bucket\nSELECT histogram(key, 0, 9, 2) FROM hitest1;\n histogram \n-----------\n {0,8,2,0}\n\n-- standard multi-bucket\nSELECT histogram(key, 0, 9, 5) FROM hitest1;\n    histogram    \n-----------------\n {0,4,3,2,1,0,0}\n\n-- standard 3 bucket\nSELECT val, histogram(key, 0, 7, 3) FROM hitest1 GROUP BY val ORDER BY val;\n  val  |  histogram  \n-------+-------------\n hello | {0,2,0,0,0}\n hi    | {0,1,0,0,0}\n hola  | {0,0,0,1,0}\n howdy | {0,0,1,0,0}\n sup   | {0,3,0,0,0}\n ya    | {0,0,0,1,0}\n yo    | {0,0,1,0,0}\n\n-- standard element beneath lb\nSELECT histogram(key, 1, 7, 3) FROM hitest1;\n  histogram  \n-------------\n {1,5,2,2,0}\n\n-- standard element above ub\nSELECT histogram(key, 0, 3, 3) FROM hitest1;\n  histogram  \n-------------\n {0,1,3,2,4}\n\n-- standard element beneath and above lb and ub, respectively\nSELECT histogram(key, 1, 3, 2) FROM hitest1;\n histogram \n-----------\n {1,3,2,4}\n\n-- standard 1 bucket\nSELECT histogram(key, 1, 3, 1) FROM hitest1;\n histogram \n-----------\n {1,5,4}\n\n-- standard 2 bucket\nSELECT qualify, histogram(score, 0, 10, 2) FROM hitest2 GROUP BY qualify ORDER BY qualify;\n qualify | histogram \n---------+-----------\n f       | {0,2,0,0}\n t       | {0,0,1,1}\n\n-- standard multi-bucket\nSELECT qualify, histogram(score, 0, 10, 5) FROM hitest2 GROUP BY qualify ORDER BY qualify;\n qualify |    histogram    \n---------+-----------------\n f       | {0,0,1,1,0,0,0}\n t       | {0,0,0,0,1,0,1}\n\n-- check number of buckets is constant\n\\set ON_ERROR_STOP 0\nselect histogram(i,10,90,case when i=1 then 1 else 1000000 end) FROM generate_series(1,100) i;\nERROR:  number of buckets must not change between calls\n\\set ON_ERROR_STOP 1\nCREATE TABLE weather (\n       time TIMESTAMPTZ NOT NULL,\n       city TEXT,\n       temperature FLOAT,\n       PRIMARY KEY(time, city)\n);\n-- There is a bug in width_bucket() causing a NaN as a result, so we\n-- check that it is not causing a crash in histogram().\nSELECT * FROM create_hypertable('weather', 'time', 'city', 3);\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             1 | public      | weather    | t\n\nINSERT INTO weather VALUES\n       ('2023-02-10 09:16:51.133584+00','city1',10.4),\n       ('2023-02-10 11:16:51.611618+00','city1',10.3),\n       ('2023-02-10 06:58:59.999999+00','city1',10.3),\n       ('2023-02-10 01:58:59.999999+00','city1',10.3),\n       ('2023-02-09 01:58:59.999999+00','city1',10.3),\n       ('2023-02-10 08:58:59.999999+00','city1',10.3),\n       ('2023-03-23 06:12:02.73765+00 ','city1', 9.7),\n       ('2023-03-23 06:12:06.990998+00','city1',11.7);\n-- This will currently generate an error on PG15 and prior versions\n\\set ON_ERROR_STOP 0\nSELECT histogram(temperature, -1.79769e+308, 1.79769e+308,10) FROM weather GROUP BY city;\n         histogram         \n---------------------------\n {0,0,0,0,0,0,8,0,0,0,0,0}\n\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "test/expected/histogram_test-17.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- table 1\nCREATE TABLE \"hitest1\"(key real, val varchar(40));\n-- insertions\nINSERT INTO \"hitest1\" VALUES(0, 'hi');\nINSERT INTO \"hitest1\" VALUES(1, 'sup');\nINSERT INTO \"hitest1\" VALUES(2, 'hello');\nINSERT INTO \"hitest1\" VALUES(3, 'yo');\nINSERT INTO \"hitest1\" VALUES(4, 'howdy');\nINSERT INTO \"hitest1\" VALUES(5, 'hola');\nINSERT INTO \"hitest1\" VALUES(6, 'ya');\nINSERT INTO \"hitest1\" VALUES(1, 'sup');\nINSERT INTO \"hitest1\" VALUES(2, 'hello');\nINSERT INTO \"hitest1\" VALUES(1, 'sup');\n-- table 2\nCREATE TABLE \"hitest2\"(name varchar(30), score integer, qualify boolean);\n-- insertions\nINSERT INTO \"hitest2\" VALUES('Tom', 6, TRUE);\nINSERT INTO \"hitest2\" VALUES('Mary', 4, FALSE);\nINSERT INTO \"hitest2\" VALUES('Jaq', 3, FALSE);\nINSERT INTO \"hitest2\" VALUES('Jane', 10, TRUE);\n-- standard 2 bucket\nSELECT histogram(key, 0, 9, 2) FROM hitest1;\n histogram \n-----------\n {0,8,2,0}\n\n-- standard multi-bucket\nSELECT histogram(key, 0, 9, 5) FROM hitest1;\n    histogram    \n-----------------\n {0,4,3,2,1,0,0}\n\n-- standard 3 bucket\nSELECT val, histogram(key, 0, 7, 3) FROM hitest1 GROUP BY val ORDER BY val;\n  val  |  histogram  \n-------+-------------\n hello | {0,2,0,0,0}\n hi    | {0,1,0,0,0}\n hola  | {0,0,0,1,0}\n howdy | {0,0,1,0,0}\n sup   | {0,3,0,0,0}\n ya    | {0,0,0,1,0}\n yo    | {0,0,1,0,0}\n\n-- standard element beneath lb\nSELECT histogram(key, 1, 7, 3) FROM hitest1;\n  histogram  \n-------------\n {1,5,2,2,0}\n\n-- standard element above ub\nSELECT histogram(key, 0, 3, 3) FROM hitest1;\n  histogram  \n-------------\n {0,1,3,2,4}\n\n-- standard element beneath and above lb and ub, respectively\nSELECT histogram(key, 1, 3, 2) FROM hitest1;\n histogram \n-----------\n {1,3,2,4}\n\n-- standard 1 bucket\nSELECT histogram(key, 1, 3, 1) FROM hitest1;\n histogram \n-----------\n {1,5,4}\n\n-- standard 2 bucket\nSELECT qualify, histogram(score, 0, 10, 2) FROM hitest2 GROUP BY qualify ORDER BY qualify;\n qualify | histogram \n---------+-----------\n f       | {0,2,0,0}\n t       | {0,0,1,1}\n\n-- standard multi-bucket\nSELECT qualify, histogram(score, 0, 10, 5) FROM hitest2 GROUP BY qualify ORDER BY qualify;\n qualify |    histogram    \n---------+-----------------\n f       | {0,0,1,1,0,0,0}\n t       | {0,0,0,0,1,0,1}\n\n-- check number of buckets is constant\n\\set ON_ERROR_STOP 0\nselect histogram(i,10,90,case when i=1 then 1 else 1000000 end) FROM generate_series(1,100) i;\nERROR:  number of buckets must not change between calls\n\\set ON_ERROR_STOP 1\nCREATE TABLE weather (\n       time TIMESTAMPTZ NOT NULL,\n       city TEXT,\n       temperature FLOAT,\n       PRIMARY KEY(time, city)\n);\n-- There is a bug in width_bucket() causing a NaN as a result, so we\n-- check that it is not causing a crash in histogram().\nSELECT * FROM create_hypertable('weather', 'time', 'city', 3);\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             1 | public      | weather    | t\n\nINSERT INTO weather VALUES\n       ('2023-02-10 09:16:51.133584+00','city1',10.4),\n       ('2023-02-10 11:16:51.611618+00','city1',10.3),\n       ('2023-02-10 06:58:59.999999+00','city1',10.3),\n       ('2023-02-10 01:58:59.999999+00','city1',10.3),\n       ('2023-02-09 01:58:59.999999+00','city1',10.3),\n       ('2023-02-10 08:58:59.999999+00','city1',10.3),\n       ('2023-03-23 06:12:02.73765+00 ','city1', 9.7),\n       ('2023-03-23 06:12:06.990998+00','city1',11.7);\n-- This will currently generate an error on PG15 and prior versions\n\\set ON_ERROR_STOP 0\nSELECT histogram(temperature, -1.79769e+308, 1.79769e+308,10) FROM weather GROUP BY city;\n         histogram         \n---------------------------\n {0,0,0,0,0,0,8,0,0,0,0,0}\n\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "test/expected/histogram_test-18.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- table 1\nCREATE TABLE \"hitest1\"(key real, val varchar(40));\n-- insertions\nINSERT INTO \"hitest1\" VALUES(0, 'hi');\nINSERT INTO \"hitest1\" VALUES(1, 'sup');\nINSERT INTO \"hitest1\" VALUES(2, 'hello');\nINSERT INTO \"hitest1\" VALUES(3, 'yo');\nINSERT INTO \"hitest1\" VALUES(4, 'howdy');\nINSERT INTO \"hitest1\" VALUES(5, 'hola');\nINSERT INTO \"hitest1\" VALUES(6, 'ya');\nINSERT INTO \"hitest1\" VALUES(1, 'sup');\nINSERT INTO \"hitest1\" VALUES(2, 'hello');\nINSERT INTO \"hitest1\" VALUES(1, 'sup');\n-- table 2\nCREATE TABLE \"hitest2\"(name varchar(30), score integer, qualify boolean);\n-- insertions\nINSERT INTO \"hitest2\" VALUES('Tom', 6, TRUE);\nINSERT INTO \"hitest2\" VALUES('Mary', 4, FALSE);\nINSERT INTO \"hitest2\" VALUES('Jaq', 3, FALSE);\nINSERT INTO \"hitest2\" VALUES('Jane', 10, TRUE);\n-- standard 2 bucket\nSELECT histogram(key, 0, 9, 2) FROM hitest1;\n histogram \n-----------\n {0,8,2,0}\n\n-- standard multi-bucket\nSELECT histogram(key, 0, 9, 5) FROM hitest1;\n    histogram    \n-----------------\n {0,4,3,2,1,0,0}\n\n-- standard 3 bucket\nSELECT val, histogram(key, 0, 7, 3) FROM hitest1 GROUP BY val ORDER BY val;\n  val  |  histogram  \n-------+-------------\n hello | {0,2,0,0,0}\n hi    | {0,1,0,0,0}\n hola  | {0,0,0,1,0}\n howdy | {0,0,1,0,0}\n sup   | {0,3,0,0,0}\n ya    | {0,0,0,1,0}\n yo    | {0,0,1,0,0}\n\n-- standard element beneath lb\nSELECT histogram(key, 1, 7, 3) FROM hitest1;\n  histogram  \n-------------\n {1,5,2,2,0}\n\n-- standard element above ub\nSELECT histogram(key, 0, 3, 3) FROM hitest1;\n  histogram  \n-------------\n {0,1,3,2,4}\n\n-- standard element beneath and above lb and ub, respectively\nSELECT histogram(key, 1, 3, 2) FROM hitest1;\n histogram \n-----------\n {1,3,2,4}\n\n-- standard 1 bucket\nSELECT histogram(key, 1, 3, 1) FROM hitest1;\n histogram \n-----------\n {1,5,4}\n\n-- standard 2 bucket\nSELECT qualify, histogram(score, 0, 10, 2) FROM hitest2 GROUP BY qualify ORDER BY qualify;\n qualify | histogram \n---------+-----------\n f       | {0,2,0,0}\n t       | {0,0,1,1}\n\n-- standard multi-bucket\nSELECT qualify, histogram(score, 0, 10, 5) FROM hitest2 GROUP BY qualify ORDER BY qualify;\n qualify |    histogram    \n---------+-----------------\n f       | {0,0,1,1,0,0,0}\n t       | {0,0,0,0,1,0,1}\n\n-- check number of buckets is constant\n\\set ON_ERROR_STOP 0\nselect histogram(i,10,90,case when i=1 then 1 else 1000000 end) FROM generate_series(1,100) i;\nERROR:  number of buckets must not change between calls\n\\set ON_ERROR_STOP 1\nCREATE TABLE weather (\n       time TIMESTAMPTZ NOT NULL,\n       city TEXT,\n       temperature FLOAT,\n       PRIMARY KEY(time, city)\n);\n-- There is a bug in width_bucket() causing a NaN as a result, so we\n-- check that it is not causing a crash in histogram().\nSELECT * FROM create_hypertable('weather', 'time', 'city', 3);\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             1 | public      | weather    | t\n\nINSERT INTO weather VALUES\n       ('2023-02-10 09:16:51.133584+00','city1',10.4),\n       ('2023-02-10 11:16:51.611618+00','city1',10.3),\n       ('2023-02-10 06:58:59.999999+00','city1',10.3),\n       ('2023-02-10 01:58:59.999999+00','city1',10.3),\n       ('2023-02-09 01:58:59.999999+00','city1',10.3),\n       ('2023-02-10 08:58:59.999999+00','city1',10.3),\n       ('2023-03-23 06:12:02.73765+00 ','city1', 9.7),\n       ('2023-03-23 06:12:06.990998+00','city1',11.7);\n-- This will currently generate an error on PG15 and prior versions\n\\set ON_ERROR_STOP 0\nSELECT histogram(temperature, -1.79769e+308, 1.79769e+308,10) FROM weather GROUP BY city;\n         histogram         \n---------------------------\n {0,0,0,0,0,0,8,0,0,0,0,0}\n\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "test/expected/index.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE index_test(time timestamptz, temp float);\nSELECT create_hypertable('index_test', 'time');\n    create_hypertable    \n-------------------------\n (1,public,index_test,t)\n\n-- Default indexes created\nSELECT * FROM test.show_indexes('index_test');\n        Index        | Columns | Expr | Unique | Primary | Exclusion | Tablespace \n---------------------+---------+------+--------+---------+-----------+------------\n index_test_time_idx | {time}  |      | f      | f       | f         | \n\nDROP TABLE index_test;\nCREATE TABLE index_test(time timestamptz, device integer, temp float);\n-- Create index before create_hypertable()\nCREATE UNIQUE INDEX index_test_time_idx ON index_test (time);\n\\set ON_ERROR_STOP 0\n-- Creating a hypertable from a table with an index that doesn't cover\n-- all partitioning columns should fail\nSELECT create_hypertable('index_test', 'time', 'device', 2);\nERROR:  cannot create a unique index without the column \"device\" (used in partitioning)\n\\set ON_ERROR_STOP 1\n-- Partitioning on only time should work\nSELECT create_hypertable('index_test', 'time');\n    create_hypertable    \n-------------------------\n (3,public,index_test,t)\n\nINSERT INTO index_test VALUES ('2017-01-20T09:00:01', 1, 17.5);\n-- Check that index is also created on chunk\nSELECT * FROM test.show_indexes('index_test');\n        Index        | Columns | Expr | Unique | Primary | Exclusion | Tablespace \n---------------------+---------+------+--------+---------+-----------+------------\n index_test_time_idx | {time}  |      | t      | f       | f         | \n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n                 Table                  |                           Index                            | Columns | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------+------------------------------------------------------------+---------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_3_1_chunk | _timescaledb_internal._hyper_3_1_chunk_index_test_time_idx | {time}  |      | t      | f       | f         | \n\n-- Create another chunk\nINSERT INTO index_test VALUES ('2017-05-20T09:00:01', 3, 17.5);\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n                 Table                  |                           Index                            | Columns | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------+------------------------------------------------------------+---------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_3_1_chunk | _timescaledb_internal._hyper_3_1_chunk_index_test_time_idx | {time}  |      | t      | f       | f         | \n _timescaledb_internal._hyper_3_2_chunk | _timescaledb_internal._hyper_3_2_chunk_index_test_time_idx | {time}  |      | t      | f       | f         | \n\n-- Delete the index on only one chunk\nDROP INDEX _timescaledb_internal._hyper_3_1_chunk_index_test_time_idx;\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n                 Table                  |                           Index                            | Columns | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------+------------------------------------------------------------+---------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_3_2_chunk | _timescaledb_internal._hyper_3_2_chunk_index_test_time_idx | {time}  |      | t      | f       | f         | \n\n-- Recreate table with new partitioning\nDROP TABLE index_test;\nCREATE TABLE index_test(id serial, time timestamptz, device integer, temp float);\nSELECT * FROM test.show_columns('index_test');\n Column |           Type           | NotNull \n--------+--------------------------+---------\n id     | integer                  | t\n time   | timestamp with time zone | f\n device | integer                  | f\n temp   | double precision         | f\n\n-- Test that we can handle difference in attnos across hypertable and\n-- chunks by dropping the ID column\nALTER TABLE index_test DROP COLUMN id;\nSELECT * FROM test.show_columns('index_test');\n Column |           Type           | NotNull \n--------+--------------------------+---------\n time   | timestamp with time zone | f\n device | integer                  | f\n temp   | double precision         | f\n\n-- No pre-existing UNIQUE index, so partitioning on two columns should work\nSELECT create_hypertable('index_test', 'time', 'device', 2);\n    create_hypertable    \n-------------------------\n (4,public,index_test,t)\n\nINSERT INTO index_test VALUES ('2017-01-20T09:00:01', 1, 17.5);\n\\set ON_ERROR_STOP 0\n-- Create unique index without all partitioning columns should fail\nCREATE UNIQUE INDEX index_test_time_device_idx ON index_test (time);\nERROR:  cannot create a unique index without the column \"device\" (used in partitioning)\n\\set ON_ERROR_STOP 1\nCREATE UNIQUE INDEX index_test_time_device_idx ON index_test (time, device);\n-- Regular index need not cover all partitioning columns\nCREATE INDEX ON index_test (time, temp);\n-- Create another chunk\nINSERT INTO index_test VALUES ('2017-04-20T09:00:01', 1, 17.5);\n-- New index should have been recursed to chunks\nSELECT * FROM test.show_indexes('index_test');\n           Index            |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------+---------------+------+--------+---------+-----------+------------\n index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n index_test_time_device_idx | {time,device} |      | t      | f       | f         | \n index_test_time_idx        | {time}        |      | f      | f       | f         | \n index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n                 Table                  |                               Index                               |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------+-------------------------------------------------------------------+---------------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_4_3_chunk | _timescaledb_internal._hyper_4_3_chunk_index_test_time_idx        | {time}        |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_3_chunk | _timescaledb_internal._hyper_4_3_chunk_index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_3_chunk | _timescaledb_internal._hyper_4_3_chunk_index_test_time_device_idx | {time,device} |      | t      | f       | f         | \n _timescaledb_internal._hyper_4_3_chunk | _timescaledb_internal._hyper_4_3_chunk_index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_4_chunk | _timescaledb_internal._hyper_4_4_chunk_index_test_time_idx        | {time}        |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_4_chunk | _timescaledb_internal._hyper_4_4_chunk_index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_4_chunk | _timescaledb_internal._hyper_4_4_chunk_index_test_time_device_idx | {time,device} |      | t      | f       | f         | \n _timescaledb_internal._hyper_4_4_chunk | _timescaledb_internal._hyper_4_4_chunk_index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n\nALTER INDEX index_test_time_idx RENAME TO index_test_time_idx2;\n-- Metadata and index should have changed name\nSELECT * FROM test.show_indexes('index_test');\n           Index            |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------+---------------+------+--------+---------+-----------+------------\n index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n index_test_time_device_idx | {time,device} |      | t      | f       | f         | \n index_test_time_idx2       | {time}        |      | f      | f       | f         | \n index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n                 Table                  |                               Index                               |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------+-------------------------------------------------------------------+---------------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_4_3_chunk | _timescaledb_internal._hyper_4_3_chunk_index_test_time_idx2       | {time}        |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_3_chunk | _timescaledb_internal._hyper_4_3_chunk_index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_3_chunk | _timescaledb_internal._hyper_4_3_chunk_index_test_time_device_idx | {time,device} |      | t      | f       | f         | \n _timescaledb_internal._hyper_4_3_chunk | _timescaledb_internal._hyper_4_3_chunk_index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_4_chunk | _timescaledb_internal._hyper_4_4_chunk_index_test_time_idx2       | {time}        |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_4_chunk | _timescaledb_internal._hyper_4_4_chunk_index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_4_chunk | _timescaledb_internal._hyper_4_4_chunk_index_test_time_device_idx | {time,device} |      | t      | f       | f         | \n _timescaledb_internal._hyper_4_4_chunk | _timescaledb_internal._hyper_4_4_chunk_index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n\nDROP INDEX index_test_time_idx2;\nDROP INDEX index_test_time_device_idx;\n-- Index should have been dropped\nSELECT * FROM test.show_indexes('index_test');\n           Index            |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------+---------------+------+--------+---------+-----------+------------\n index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n                 Table                  |                               Index                               |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------+-------------------------------------------------------------------+---------------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_4_3_chunk | _timescaledb_internal._hyper_4_3_chunk_index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_3_chunk | _timescaledb_internal._hyper_4_3_chunk_index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_4_chunk | _timescaledb_internal._hyper_4_4_chunk_index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_4_chunk | _timescaledb_internal._hyper_4_4_chunk_index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n\n-- Create index with long name to see how this is handled on chunks\nCREATE INDEX a_hypertable_index_with_a_very_very_long_name_that_truncates ON index_test (time DESC, temp);\nCREATE INDEX a_hypertable_index_with_a_very_very_long_name_that_truncates_2 ON index_test (time DESC, temp DESC);\nSELECT * FROM test.show_indexes('index_test');\n                             Index                              |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------------------------------+---------------+------+--------+---------+-----------+------------\n a_hypertable_index_with_a_very_very_long_name_that_truncates   | {time,temp}   |      | f      | f       | f         | \n a_hypertable_index_with_a_very_very_long_name_that_truncates_2 | {time,temp}   |      | f      | f       | f         | \n index_test_device_time_idx                                     | {device,time} |      | f      | f       | f         | \n index_test_time_temp_idx                                       | {time,temp}   |      | f      | f       | f         | \n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n                 Table                  |                                         Index                                         |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------+---------------------------------------------------------------------------------------+---------------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_4_3_chunk | _timescaledb_internal._hyper_4_3_chunk_index_test_device_time_idx                     | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_3_chunk | _timescaledb_internal._hyper_4_3_chunk_index_test_time_temp_idx                       | {time,temp}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_3_chunk | _timescaledb_internal._hyper_4_3_chunk_a_hypertable_index_with_a_very_very_long_name_ | {time,temp}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_3_chunk | _timescaledb_internal._hyper_4_3_chunk_a_hypertable_index_with_a_very_very_long_nam_1 | {time,temp}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_4_chunk | _timescaledb_internal._hyper_4_4_chunk_index_test_device_time_idx                     | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_4_chunk | _timescaledb_internal._hyper_4_4_chunk_index_test_time_temp_idx                       | {time,temp}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_4_chunk | _timescaledb_internal._hyper_4_4_chunk_a_hypertable_index_with_a_very_very_long_name_ | {time,temp}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_4_chunk | _timescaledb_internal._hyper_4_4_chunk_a_hypertable_index_with_a_very_very_long_nam_1 | {time,temp}   |      | f      | f       | f         | \n\nDROP INDEX a_hypertable_index_with_a_very_very_long_name_that_truncates;\nDROP INDEX a_hypertable_index_with_a_very_very_long_name_that_truncates_2;\n\\set ON_ERROR_STOP 0\n-- Create index CONCURRENTLY\nCREATE UNIQUE INDEX CONCURRENTLY index_test_time_device_idx ON index_test (time, device);\nERROR:  hypertables do not support concurrent index creation\n\\set ON_ERROR_STOP 1\n-- Test tablespaces. Chunk indexes should end up in same tablespace as\n-- main index.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET client_min_messages = ERROR;\nDROP TABLESPACE IF EXISTS tablespace1;\nDROP TABLESPACE IF EXISTS tablespace2;\nSET client_min_messages = NOTICE;\nCREATE TABLESPACE tablespace1 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE1_PATH;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nCREATE INDEX index_test_time_idx ON index_test (time) TABLESPACE tablespace1;\nSELECT * FROM test.show_indexes('index_test');\n           Index            |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace  \n----------------------------+---------------+------+--------+---------+-----------+-------------\n index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n index_test_time_idx        | {time}        |      | f      | f       | f         | tablespace1\n index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n                 Table                  |                               Index                               |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace  \n----------------------------------------+-------------------------------------------------------------------+---------------+------+--------+---------+-----------+-------------\n _timescaledb_internal._hyper_4_3_chunk | _timescaledb_internal._hyper_4_3_chunk_index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_3_chunk | _timescaledb_internal._hyper_4_3_chunk_index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_3_chunk | _timescaledb_internal._hyper_4_3_chunk_index_test_time_idx        | {time}        |      | f      | f       | f         | tablespace1\n _timescaledb_internal._hyper_4_4_chunk | _timescaledb_internal._hyper_4_4_chunk_index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_4_chunk | _timescaledb_internal._hyper_4_4_chunk_index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_4_chunk | _timescaledb_internal._hyper_4_4_chunk_index_test_time_idx        | {time}        |      | f      | f       | f         | tablespace1\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE TABLESPACE tablespace2 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE2_PATH;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nALTER INDEX index_test_time_idx SET TABLESPACE tablespace2;\nSELECT * FROM test.show_indexes('index_test');\n           Index            |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace  \n----------------------------+---------------+------+--------+---------+-----------+-------------\n index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n index_test_time_idx        | {time}        |      | f      | f       | f         | tablespace2\n index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n                 Table                  |                               Index                               |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace  \n----------------------------------------+-------------------------------------------------------------------+---------------+------+--------+---------+-----------+-------------\n _timescaledb_internal._hyper_4_3_chunk | _timescaledb_internal._hyper_4_3_chunk_index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_3_chunk | _timescaledb_internal._hyper_4_3_chunk_index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_3_chunk | _timescaledb_internal._hyper_4_3_chunk_index_test_time_idx        | {time}        |      | f      | f       | f         | tablespace2\n _timescaledb_internal._hyper_4_4_chunk | _timescaledb_internal._hyper_4_4_chunk_index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_4_chunk | _timescaledb_internal._hyper_4_4_chunk_index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_4_chunk | _timescaledb_internal._hyper_4_4_chunk_index_test_time_idx        | {time}        |      | f      | f       | f         | tablespace2\n\n-- Add constraint index\nALTER TABLE index_test ADD UNIQUE (time, device);\nSELECT * FROM test.show_indexes('index_test');\n           Index            |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace  \n----------------------------+---------------+------+--------+---------+-----------+-------------\n index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n index_test_time_device_key | {time,device} |      | t      | f       | f         | \n index_test_time_idx        | {time}        |      | f      | f       | f         | tablespace2\n index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n                 Table                  |                               Index                               |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace  \n----------------------------------------+-------------------------------------------------------------------+---------------+------+--------+---------+-----------+-------------\n _timescaledb_internal._hyper_4_3_chunk | _timescaledb_internal._hyper_4_3_chunk_index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_3_chunk | _timescaledb_internal._hyper_4_3_chunk_index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_3_chunk | _timescaledb_internal._hyper_4_3_chunk_index_test_time_idx        | {time}        |      | f      | f       | f         | tablespace2\n _timescaledb_internal._hyper_4_3_chunk | _timescaledb_internal.\"3_1_index_test_time_device_key\"            | {time,device} |      | t      | f       | f         | \n _timescaledb_internal._hyper_4_4_chunk | _timescaledb_internal._hyper_4_4_chunk_index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_4_chunk | _timescaledb_internal._hyper_4_4_chunk_index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_4_4_chunk | _timescaledb_internal._hyper_4_4_chunk_index_test_time_idx        | {time}        |      | f      | f       | f         | tablespace2\n _timescaledb_internal._hyper_4_4_chunk | _timescaledb_internal.\"4_2_index_test_time_device_key\"            | {time,device} |      | t      | f       | f         | \n\n-- Constraints are added to chunk_constraint table.\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\n chunk_id | dimension_slice_id |        constraint_name         | hypertable_constraint_name \n----------+--------------------+--------------------------------+----------------------------\n        3 |                  3 | constraint_3                   | \n        3 |                  4 | constraint_4                   | \n        4 |                  5 | constraint_5                   | \n        4 |                  4 | constraint_4                   | \n        3 |                    | 3_1_index_test_time_device_key | index_test_time_device_key\n        4 |                    | 4_2_index_test_time_device_key | index_test_time_device_key\n\nDROP TABLE index_test;\n-- Create table in a tablespace\nCREATE TABLE index_test(time timestamptz, temp float, device int) TABLESPACE tablespace1;\n-- Default indexes should be in the table's tablespace\nSELECT create_hypertable('index_test', 'time');\n    create_hypertable    \n-------------------------\n (5,public,index_test,t)\n\n-- Explicitly defining an index tablespace should work and propagate\n-- to chunks\nCREATE INDEX ON index_test (time, device) TABLESPACE tablespace2;\n-- New indexes without explicit tablespaces should use the default\n-- tablespace\nCREATE INDEX ON index_test (device);\n-- Create chunk\nINSERT INTO index_test VALUES ('2017-01-20T09:00:01', 17.5);\n-- Check that the tablespaces of chunk indexes match the tablespace of\n-- the main index\nSELECT * FROM test.show_indexes('index_test');\n           Index            |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace  \n----------------------------+---------------+------+--------+---------+-----------+-------------\n index_test_device_idx      | {device}      |      | f      | f       | f         | \n index_test_time_device_idx | {time,device} |      | f      | f       | f         | tablespace2\n index_test_time_idx        | {time}        |      | f      | f       | f         | tablespace1\n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n                 Table                  |                               Index                               |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace  \n----------------------------------------+-------------------------------------------------------------------+---------------+------+--------+---------+-----------+-------------\n _timescaledb_internal._hyper_5_5_chunk | _timescaledb_internal._hyper_5_5_chunk_index_test_time_idx        | {time}        |      | f      | f       | f         | tablespace1\n _timescaledb_internal._hyper_5_5_chunk | _timescaledb_internal._hyper_5_5_chunk_index_test_time_device_idx | {time,device} |      | f      | f       | f         | tablespace2\n _timescaledb_internal._hyper_5_5_chunk | _timescaledb_internal._hyper_5_5_chunk_index_test_device_idx      | {device}      |      | f      | f       | f         | tablespace1\n\n-- Creating a new index should propagate to existing chunks, including\n-- the given tablespace\nCREATE INDEX ON index_test (time, temp) TABLESPACE tablespace2;\nSELECT * FROM test.show_indexes('index_test');\n           Index            |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace  \n----------------------------+---------------+------+--------+---------+-----------+-------------\n index_test_device_idx      | {device}      |      | f      | f       | f         | \n index_test_time_device_idx | {time,device} |      | f      | f       | f         | tablespace2\n index_test_time_idx        | {time}        |      | f      | f       | f         | tablespace1\n index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | tablespace2\n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n                 Table                  |                               Index                               |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace  \n----------------------------------------+-------------------------------------------------------------------+---------------+------+--------+---------+-----------+-------------\n _timescaledb_internal._hyper_5_5_chunk | _timescaledb_internal._hyper_5_5_chunk_index_test_time_idx        | {time}        |      | f      | f       | f         | tablespace1\n _timescaledb_internal._hyper_5_5_chunk | _timescaledb_internal._hyper_5_5_chunk_index_test_time_device_idx | {time,device} |      | f      | f       | f         | tablespace2\n _timescaledb_internal._hyper_5_5_chunk | _timescaledb_internal._hyper_5_5_chunk_index_test_device_idx      | {device}      |      | f      | f       | f         | tablespace1\n _timescaledb_internal._hyper_5_5_chunk | _timescaledb_internal._hyper_5_5_chunk_index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | tablespace2\n\n-- Cleanup\nDROP TABLE index_test CASCADE;\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nDROP TABLESPACE tablespace1;\nDROP TABLESPACE tablespace2;\n-- Test expression indexes\nCREATE TABLE index_expr_test(id serial, time timestamptz, temp float, meta jsonb);\n-- Screw up the attribute numbers\nALTER TABLE index_expr_test DROP COLUMN id;\nCREATE INDEX ON index_expr_test ((meta ->> 'field')) ;\nINSERT INTO index_expr_test VALUES ('2017-01-20T09:00:01', 17.5, '{\"field\": \"value1\"}');\nINSERT INTO index_expr_test VALUES ('2017-01-20T09:00:01', 17.5, '{\"field\": \"value2\"}');\nEXPLAIN (verbose, buffers off, costs off)\nSELECT * FROM index_expr_test WHERE meta ->> 'field' = 'value1';\n--- QUERY PLAN ---\n Index Scan using index_expr_test_expr_idx on public.index_expr_test\n   Output: \"time\", temp, meta\n   Index Cond: ((index_expr_test.meta ->> 'field'::text) = 'value1'::text)\n\nSELECT * FROM index_expr_test WHERE meta ->> 'field' = 'value1';\n             time             | temp |        meta         \n------------------------------+------+---------------------\n Fri Jan 20 09:00:01 2017 PST | 17.5 | {\"field\": \"value1\"}\n\n-- Test INDEX DROP error for multiple objects\nCREATE TABLE index_test(time timestamptz, temp float);\nCREATE UNIQUE INDEX index_test_idx ON index_test (time, temp);\nSELECT create_hypertable('index_test', 'time');\n    create_hypertable    \n-------------------------\n (6,public,index_test,t)\n\nCREATE TABLE index_test_2(time timestamptz, temp float);\nCREATE UNIQUE INDEX index_test_2_idx ON index_test_2 (time, temp);\n\\set ON_ERROR_STOP 0\nDROP INDEX index_test_idx, index_test_2_idx;\n\\set ON_ERROR_STOP 1\n-- test expression index with dropped columns\nCREATE TABLE idx_expr_test(filler int, time timestamptz, meta text);\nSELECT table_name FROM create_hypertable('idx_expr_test', 'time');\n  table_name   \n---------------\n idx_expr_test\n\nALTER TABLE idx_expr_test DROP COLUMN filler;\nCREATE INDEX tag_idx ON idx_expr_test(('foo'||meta));\nINSERT INTO idx_expr_test(time, meta) VALUES ('2000-01-01', 'bar');\nDROP TABLE idx_expr_test CASCADE;\n-- test multicolumn expression index with dropped columns\nCREATE TABLE idx_expr_test(filler int, time timestamptz, t1 text, t2 text, t3 text);\nSELECT table_name FROM create_hypertable('idx_expr_test', 'time');\n  table_name   \n---------------\n idx_expr_test\n\nALTER TABLE idx_expr_test DROP COLUMN filler;\nCREATE INDEX tag_idx ON idx_expr_test((t1||t2||t3));\nINSERT INTO idx_expr_test(time, t1, t2, t3) VALUES ('2000-01-01', 'foo', 'bar', 'baz');\nDROP TABLE idx_expr_test CASCADE;\n-- test index with predicate and dropped columns\nCREATE TABLE idx_predicate_test(filler int, time timestamptz);\nSELECT table_name FROM create_hypertable('idx_predicate_test', 'time');\n     table_name     \n--------------------\n idx_predicate_test\n\nALTER TABLE idx_predicate_test DROP COLUMN filler;\nALTER TABLE idx_predicate_test ADD COLUMN b1 bool;\nCREATE INDEX idx_predicate_test_b1 ON idx_predicate_test(b1) WHERE b1=true;\nINSERT INTO idx_predicate_test VALUES ('2000-01-01',true);\nDROP TABLE idx_predicate_test;\n-- test index with table references\nCREATE TABLE idx_tableref_test(time timestamptz);\nSELECT table_name FROM create_hypertable('idx_tableref_test', 'time');\n    table_name     \n-------------------\n idx_tableref_test\n\n-- we use security definer to prevent function inlining\nCREATE OR REPLACE FUNCTION tableref_func(t idx_tableref_test) RETURNS timestamptz LANGUAGE SQL IMMUTABLE SECURITY DEFINER AS $f$ SELECT t.time; $f$;\n-- try creating index with no existing chunks\nCREATE INDEX tableref_idx ON idx_tableref_test(tableref_func(idx_tableref_test));\n-- insert data to trigger chunk creation\nINSERT INTO idx_tableref_test SELECT '2000-01-01';\nDROP INDEX tableref_idx;\n-- try creating index on hypertable with existing chunks\nCREATE INDEX tableref_idx ON idx_tableref_test(tableref_func(idx_tableref_test));\n-- test index creation with if not exists\nCREATE TABLE idx_exists(time timestamptz NOT NULL);\nSELECT table_name FROM create_hypertable('idx_exists', 'time');\n table_name \n------------\n idx_exists\n\n-- should be skipped since this index was already created by create_hypertable\nCREATE INDEX IF NOT EXISTS idx_exists_time_idx ON idx_exists(time DESC);\nNOTICE:  relation \"idx_exists_time_idx\" already exists, skipping\n-- should create index\nCREATE INDEX IF NOT EXISTS idx_exists_time_asc_idx ON idx_exists(time ASC);\n-- should be skipped since it was created in previous command\nCREATE INDEX IF NOT EXISTS idx_exists_time_asc_idx ON idx_exists(time ASC);\nNOTICE:  relation \"idx_exists_time_asc_idx\" already exists, skipping\nDROP INDEX idx_exists_time_asc_idx;\nINSERT INTO idx_exists VALUES ('2000-01-01'),('2001-01-01');\n-- should create index\nCREATE INDEX IF NOT EXISTS idx_exists_time_asc_idx ON idx_exists(time ASC);\n-- should be skipped since it was created in previous command\nCREATE INDEX IF NOT EXISTS idx_exists_time_asc_idx ON idx_exists(time ASC);\nNOTICE:  relation \"idx_exists_time_asc_idx\" already exists, skipping\n-- test reindex\nCREATE TABLE reindex_test(time timestamp, temp float, PRIMARY KEY(time, temp));\nCREATE UNIQUE INDEX reindex_test_time_unique_idx ON reindex_test(time);\n-- create hypertable with three chunks\nSELECT create_hypertable('reindex_test', 'time', chunk_time_interval => 2628000000000);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n     create_hypertable      \n----------------------------\n (12,public,reindex_test,t)\n\nINSERT INTO reindex_test VALUES ('2017-01-20T09:00:01', 17.5),\n                                ('2017-01-21T09:00:01', 19.1),\n                                ('2017-04-20T09:00:01', 89.5),\n                                ('2017-04-21T09:00:01', 17.1),\n                                ('2017-06-20T09:00:01', 18.5),\n                                ('2017-06-21T09:00:01', 11.0);\nSELECT * FROM test.show_columns('reindex_test');\n Column |            Type             | NotNull \n--------+-----------------------------+---------\n time   | timestamp without time zone | t\n temp   | double precision            | t\n\nSELECT * FROM test.show_subtables('reindex_test');\n                  Child                   | Tablespace \n------------------------------------------+------------\n _timescaledb_internal._hyper_12_12_chunk | \n _timescaledb_internal._hyper_12_13_chunk | \n _timescaledb_internal._hyper_12_14_chunk | \n _timescaledb_internal._hyper_12_15_chunk | \n _timescaledb_internal._hyper_12_16_chunk | \n\n-- show reindexing\nREINDEX (VERBOSE) TABLE reindex_test;\nINFO:  index \"12_3_reindex_test_pkey\" was reindexed\nINFO:  index \"_hyper_12_12_chunk_reindex_test_time_unique_idx\" was reindexed\nINFO:  index \"13_4_reindex_test_pkey\" was reindexed\nINFO:  index \"_hyper_12_13_chunk_reindex_test_time_unique_idx\" was reindexed\nINFO:  index \"14_5_reindex_test_pkey\" was reindexed\nINFO:  index \"_hyper_12_14_chunk_reindex_test_time_unique_idx\" was reindexed\nINFO:  index \"15_6_reindex_test_pkey\" was reindexed\nINFO:  index \"_hyper_12_15_chunk_reindex_test_time_unique_idx\" was reindexed\nINFO:  index \"16_7_reindex_test_pkey\" was reindexed\nINFO:  index \"_hyper_12_16_chunk_reindex_test_time_unique_idx\" was reindexed\n\\set ON_ERROR_STOP 0\n-- REINDEX TABLE CONCURRENTLY on hypertables is not supported\nREINDEX TABLE CONCURRENTLY reindex_test;\nERROR:  concurrent index creation on hypertables is not supported\n-- this one currently doesn't recurse to chunks and instead gives an\n-- error\nREINDEX (VERBOSE) INDEX reindex_test_time_unique_idx;\nERROR:  reindexing of a specific index on a hypertable is unsupported\n\\set ON_ERROR_STOP 1\n-- show reindexing on a normal table\nCREATE TABLE reindex_norm(time timestamp, temp float);\nCREATE UNIQUE INDEX reindex_norm_time_unique_idx ON reindex_norm(time);\nINSERT INTO reindex_norm VALUES ('2017-01-20T09:00:01', 17.5),\n                                ('2017-01-21T09:00:01', 19.1),\n                                ('2017-04-20T09:00:01', 89.5),\n                                ('2017-04-21T09:00:01', 17.1),\n                                ('2017-06-20T09:00:01', 18.5),\n                                ('2017-06-21T09:00:01', 11.0);\nREINDEX (VERBOSE) TABLE reindex_norm;\nINFO:  index \"reindex_norm_time_unique_idx\" was reindexed\nREINDEX (VERBOSE) INDEX reindex_norm_time_unique_idx;\nINFO:  index \"reindex_norm_time_unique_idx\" was reindexed\nSELECT * FROM test.show_constraintsp('_timescaledb_internal._hyper_12%');\n                  Table                   |       Constraint       | Type |   Columns   |                     Index                      |                                                                     Expr                                                                     | Deferrable | Deferred | Validated \n------------------------------------------+------------------------+------+-------------+------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------+------------+----------+-----------\n _timescaledb_internal._hyper_12_12_chunk | 12_3_reindex_test_pkey | p    | {time,temp} | _timescaledb_internal.\"12_3_reindex_test_pkey\" |                                                                                                                                              | f          | f        | t\n _timescaledb_internal._hyper_12_12_chunk | constraint_13          | c    | {time}      | -                                              | ((\"time\" >= 'Thu Jan 19 10:00:00 2017'::timestamp without time zone) AND (\"time\" < 'Sat Feb 18 20:00:00 2017'::timestamp without time zone)) | f          | f        | t\n _timescaledb_internal._hyper_12_13_chunk | 13_4_reindex_test_pkey | p    | {time,temp} | _timescaledb_internal.\"13_4_reindex_test_pkey\" |                                                                                                                                              | f          | f        | t\n _timescaledb_internal._hyper_12_13_chunk | constraint_14          | c    | {time}      | -                                              | ((\"time\" >= 'Tue Mar 21 06:00:00 2017'::timestamp without time zone) AND (\"time\" < 'Thu Apr 20 16:00:00 2017'::timestamp without time zone)) | f          | f        | t\n _timescaledb_internal._hyper_12_14_chunk | 14_5_reindex_test_pkey | p    | {time,temp} | _timescaledb_internal.\"14_5_reindex_test_pkey\" |                                                                                                                                              | f          | f        | t\n _timescaledb_internal._hyper_12_14_chunk | constraint_15          | c    | {time}      | -                                              | ((\"time\" >= 'Thu Apr 20 16:00:00 2017'::timestamp without time zone) AND (\"time\" < 'Sun May 21 02:00:00 2017'::timestamp without time zone)) | f          | f        | t\n _timescaledb_internal._hyper_12_15_chunk | 15_6_reindex_test_pkey | p    | {time,temp} | _timescaledb_internal.\"15_6_reindex_test_pkey\" |                                                                                                                                              | f          | f        | t\n _timescaledb_internal._hyper_12_15_chunk | constraint_16          | c    | {time}      | -                                              | ((\"time\" >= 'Sun May 21 02:00:00 2017'::timestamp without time zone) AND (\"time\" < 'Tue Jun 20 12:00:00 2017'::timestamp without time zone)) | f          | f        | t\n _timescaledb_internal._hyper_12_16_chunk | 16_7_reindex_test_pkey | p    | {time,temp} | _timescaledb_internal.\"16_7_reindex_test_pkey\" |                                                                                                                                              | f          | f        | t\n _timescaledb_internal._hyper_12_16_chunk | constraint_17          | c    | {time}      | -                                              | ((\"time\" >= 'Tue Jun 20 12:00:00 2017'::timestamp without time zone) AND (\"time\" < 'Thu Jul 20 22:00:00 2017'::timestamp without time zone)) | f          | f        | t\n\nSELECT * FROM reindex_norm;\n           time           | temp \n--------------------------+------\n Fri Jan 20 09:00:01 2017 | 17.5\n Sat Jan 21 09:00:01 2017 | 19.1\n Thu Apr 20 09:00:01 2017 | 89.5\n Fri Apr 21 09:00:01 2017 | 17.1\n Tue Jun 20 09:00:01 2017 | 18.5\n Wed Jun 21 09:00:01 2017 |   11\n\nCREATE TABLE ht_dropped(time timestamptz, d0 int, d1 int, c0 int, c1 int, c2 int);\nSELECT create_hypertable('ht_dropped','time');\n    create_hypertable     \n--------------------------\n (13,public,ht_dropped,t)\n\nINSERT INTO ht_dropped(time,c0,c1,c2) SELECT '2000-01-01',1,2,3;\nALTER TABLE ht_dropped DROP COLUMN d0;\nINSERT INTO ht_dropped(time,c0,c1,c2) SELECT '2001-01-01',1,2,3;\nALTER TABLE ht_dropped DROP COLUMN d1;\nINSERT INTO ht_dropped(time,c0,c1,c2) SELECT '2002-01-01',1,2,3;\nCREATE INDEX ON ht_dropped(c0,c1,c2) WHERE c1 IS NOT NULL;\nCREATE INDEX ON ht_dropped(c0,c1,c2) WITH(timescaledb.transaction_per_chunk) WHERE c2 IS NOT NULL;\nSELECT\n  oid::TEXT AS \"Chunk\",\n  i.*\nFROM\n  (SELECT tableoid::REGCLASS FROM ht_dropped GROUP BY tableoid) ch (oid)\n  LEFT JOIN LATERAL ( SELECT * FROM test.show_indexes (ch.oid)) i ON TRUE\nORDER BY\n  1, 2;\n                  Chunk                   |                               Index                               |  Columns   | Expr | Unique | Primary | Exclusion | Tablespace \n------------------------------------------+-------------------------------------------------------------------+------------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_13_17_chunk | _timescaledb_internal._hyper_13_17_chunk_ht_dropped_time_idx      | {time}     |      | f      | f       | f         | \n _timescaledb_internal._hyper_13_17_chunk | _timescaledb_internal._hyper_13_17_chunk_ht_dropped_c0_c1_c2_idx  | {c0,c1,c2} |      | f      | f       | f         | \n _timescaledb_internal._hyper_13_17_chunk | _timescaledb_internal._hyper_13_17_chunk_ht_dropped_c0_c1_c2_idx1 | {c0,c1,c2} |      | f      | f       | f         | \n _timescaledb_internal._hyper_13_18_chunk | _timescaledb_internal._hyper_13_18_chunk_ht_dropped_time_idx      | {time}     |      | f      | f       | f         | \n _timescaledb_internal._hyper_13_18_chunk | _timescaledb_internal._hyper_13_18_chunk_ht_dropped_c0_c1_c2_idx  | {c0,c1,c2} |      | f      | f       | f         | \n _timescaledb_internal._hyper_13_18_chunk | _timescaledb_internal._hyper_13_18_chunk_ht_dropped_c0_c1_c2_idx1 | {c0,c1,c2} |      | f      | f       | f         | \n _timescaledb_internal._hyper_13_19_chunk | _timescaledb_internal._hyper_13_19_chunk_ht_dropped_time_idx      | {time}     |      | f      | f       | f         | \n _timescaledb_internal._hyper_13_19_chunk | _timescaledb_internal._hyper_13_19_chunk_ht_dropped_c0_c1_c2_idx  | {c0,c1,c2} |      | f      | f       | f         | \n _timescaledb_internal._hyper_13_19_chunk | _timescaledb_internal._hyper_13_19_chunk_ht_dropped_c0_c1_c2_idx1 | {c0,c1,c2} |      | f      | f       | f         | \n\n-- #3056 check chunk index column name mapping\nCREATE TABLE i3056(c int, order_number int NOT NULL, date_created timestamptz NOT NULL);\nCREATE INDEX ON i3056(order_number) INCLUDE(order_number);\nCREATE INDEX ON i3056(date_created, (order_number % 5)) INCLUDE(order_number);\nSELECT table_name FROM create_hypertable('i3056', 'date_created');\n table_name \n------------\n i3056\n\nALTER TABLE i3056 DROP COLUMN c;\nINSERT INTO i3056(order_number,date_created) VALUES (1, '2000-01-01');\n-- #5908 test CREATE INDEX ON ONLY main table\nCREATE TABLE test(time timestamptz, temp float);\nSELECT create_hypertable('test', 'time');\n create_hypertable  \n--------------------\n (15,public,test,t)\n\nINSERT INTO test (time,temp) VALUES\n       (generate_series(TIMESTAMP '2019-08-01', TIMESTAMP '2019-08-10', INTERVAL '10 minutes'), ROUND(RANDOM()*10)::float);\nSELECT * FROM show_chunks('test');\n               show_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_15_21_chunk\n _timescaledb_internal._hyper_15_22_chunk\n\nSELECT * FROM test.show_indexes('_timescaledb_internal._hyper_15_21_chunk');\n                         Index                          | Columns | Expr | Unique | Primary | Exclusion | Tablespace \n--------------------------------------------------------+---------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_15_21_chunk_test_time_idx | {time}  |      | f      | f       | f         | \n\n-- create index per chunk\nCREATE INDEX _hyper_15_21_chunk_test_temp_idx ON  _timescaledb_internal._hyper_15_21_chunk(temp);\nSELECT * FROM test.show_indexes('_timescaledb_internal._hyper_15_21_chunk');\n                         Index                          | Columns | Expr | Unique | Primary | Exclusion | Tablespace \n--------------------------------------------------------+---------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_15_21_chunk_test_temp_idx | {temp}  |      | f      | f       | f         | \n _timescaledb_internal._hyper_15_21_chunk_test_time_idx | {time}  |      | f      | f       | f         | \n\nSELECT * FROM test.show_indexes('test');\n     Index     | Columns | Expr | Unique | Primary | Exclusion | Tablespace \n---------------+---------+------+--------+---------+-----------+------------\n test_time_idx | {time}  |      | f      | f       | f         | \n\n-- create index only on main table\nCREATE INDEX test_temp_idx ON ONLY test (time);\nSELECT * FROM test.show_indexes('test');\n     Index     | Columns | Expr | Unique | Primary | Exclusion | Tablespace \n---------------+---------+------+--------+---------+-----------+------------\n test_temp_idx | {time}  |      | f      | f       | f         | \n test_time_idx | {time}  |      | f      | f       | f         | \n\nSELECT * FROM test.show_indexes('_timescaledb_internal._hyper_15_21_chunk');\n                         Index                          | Columns | Expr | Unique | Primary | Exclusion | Tablespace \n--------------------------------------------------------+---------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_15_21_chunk_test_temp_idx | {temp}  |      | f      | f       | f         | \n _timescaledb_internal._hyper_15_21_chunk_test_time_idx | {time}  |      | f      | f       | f         | \n\n"
  },
  {
    "path": "test/expected/information_views.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nSELECT * FROM timescaledb_information.hypertables;\n hypertable_schema | hypertable_name | owner | num_dimensions | num_chunks | compression_enabled | tablespaces | primary_dimension | primary_dimension_type \n-------------------+-----------------+-------+----------------+------------+---------------------+-------------+-------------------+------------------------\n\n-- create simple hypertable with 1 chunk\nCREATE TABLE ht1(time TIMESTAMPTZ NOT NULL);\nSELECT create_hypertable('ht1','time');\n create_hypertable \n-------------------\n (1,public,ht1,t)\n\nINSERT INTO ht1 SELECT '2000-01-01'::TIMESTAMPTZ;\n-- create simple hypertable with 1 chunk and toasted data\nCREATE TABLE ht2(time TIMESTAMPTZ NOT NULL, data TEXT);\nSELECT create_hypertable('ht2','time');\n create_hypertable \n-------------------\n (2,public,ht2,t)\n\nINSERT INTO ht2 SELECT '2000-01-01'::TIMESTAMPTZ, repeat('8k',4096);\nSELECT * FROM timescaledb_information.hypertables\nORDER BY hypertable_schema, hypertable_name;\n hypertable_schema | hypertable_name |       owner       | num_dimensions | num_chunks | compression_enabled | tablespaces | primary_dimension |  primary_dimension_type  \n-------------------+-----------------+-------------------+----------------+------------+---------------------+-------------+-------------------+--------------------------\n public            | ht1             | default_perm_user |              1 |          1 | f                   |             | time              | timestamp with time zone\n public            | ht2             | default_perm_user |              1 |          1 | f                   |             | time              | timestamp with time zone\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- create schema open and hypertable with 3 chunks\nCREATE SCHEMA open;\nGRANT USAGE ON SCHEMA open TO :ROLE_DEFAULT_PERM_USER;\nCREATE TABLE open.open_ht(time TIMESTAMPTZ NOT NULL);\nSELECT create_hypertable('open.open_ht','time');\n create_hypertable  \n--------------------\n (3,open,open_ht,t)\n\nINSERT INTO open.open_ht SELECT '2000-01-01'::TIMESTAMPTZ;\nINSERT INTO open.open_ht SELECT '2001-01-01'::TIMESTAMPTZ;\nINSERT INTO open.open_ht SELECT '2002-01-01'::TIMESTAMPTZ;\n-- create schema closed and hypertable\nCREATE SCHEMA closed;\nCREATE TABLE closed.closed_ht(time TIMESTAMPTZ NOT NULL);\nSELECT create_hypertable('closed.closed_ht','time');\n   create_hypertable    \n------------------------\n (4,closed,closed_ht,t)\n\nINSERT INTO closed.closed_ht SELECT '2000-01-01'::TIMESTAMPTZ;\nSELECT * FROM timescaledb_information.hypertables\nORDER BY hypertable_schema, hypertable_name;\n hypertable_schema | hypertable_name |       owner       | num_dimensions | num_chunks | compression_enabled | tablespaces | primary_dimension |  primary_dimension_type  \n-------------------+-----------------+-------------------+----------------+------------+---------------------+-------------+-------------------+--------------------------\n closed            | closed_ht       | super_user        |              1 |          1 | f                   |             | time              | timestamp with time zone\n open              | open_ht         | super_user        |              1 |          3 | f                   |             | time              | timestamp with time zone\n public            | ht1             | default_perm_user |              1 |          1 | f                   |             | time              | timestamp with time zone\n public            | ht2             | default_perm_user |              1 |          1 | f                   |             | time              | timestamp with time zone\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n\\set ON_ERROR_STOP 0\n\\x\nSELECT * FROM timescaledb_information.hypertables\nORDER BY hypertable_schema, hypertable_name;\n-[ RECORD 1 ]----------+-------------------------\nhypertable_schema      | closed\nhypertable_name        | closed_ht\nowner                  | super_user\nnum_dimensions         | 1\nnum_chunks             | 1\ncompression_enabled    | f\ntablespaces            | \nprimary_dimension      | time\nprimary_dimension_type | timestamp with time zone\n-[ RECORD 2 ]----------+-------------------------\nhypertable_schema      | open\nhypertable_name        | open_ht\nowner                  | super_user\nnum_dimensions         | 1\nnum_chunks             | 3\ncompression_enabled    | f\ntablespaces            | \nprimary_dimension      | time\nprimary_dimension_type | timestamp with time zone\n-[ RECORD 3 ]----------+-------------------------\nhypertable_schema      | public\nhypertable_name        | ht1\nowner                  | default_perm_user\nnum_dimensions         | 1\nnum_chunks             | 1\ncompression_enabled    | f\ntablespaces            | \nprimary_dimension      | time\nprimary_dimension_type | timestamp with time zone\n-[ RECORD 4 ]----------+-------------------------\nhypertable_schema      | public\nhypertable_name        | ht2\nowner                  | default_perm_user\nnum_dimensions         | 1\nnum_chunks             | 1\ncompression_enabled    | f\ntablespaces            | \nprimary_dimension      | time\nprimary_dimension_type | timestamp with time zone\n\n-- filter by schema\nSELECT * FROM timescaledb_information.hypertables\nWHERE hypertable_schema = 'closed'\nORDER BY hypertable_schema, hypertable_name;\n-[ RECORD 1 ]----------+-------------------------\nhypertable_schema      | closed\nhypertable_name        | closed_ht\nowner                  | super_user\nnum_dimensions         | 1\nnum_chunks             | 1\ncompression_enabled    | f\ntablespaces            | \nprimary_dimension      | time\nprimary_dimension_type | timestamp with time zone\n\n-- filter by table name\nSELECT * FROM timescaledb_information.hypertables\nWHERE hypertable_name = 'ht1'\nORDER BY hypertable_schema, hypertable_name;\n-[ RECORD 1 ]----------+-------------------------\nhypertable_schema      | public\nhypertable_name        | ht1\nowner                  | default_perm_user\nnum_dimensions         | 1\nnum_chunks             | 1\ncompression_enabled    | f\ntablespaces            | \nprimary_dimension      | time\nprimary_dimension_type | timestamp with time zone\n\n-- filter by owner\nSELECT * FROM timescaledb_information.hypertables\nWHERE owner = 'super_user'\nORDER BY hypertable_schema, hypertable_name;\n-[ RECORD 1 ]----------+-------------------------\nhypertable_schema      | closed\nhypertable_name        | closed_ht\nowner                  | super_user\nnum_dimensions         | 1\nnum_chunks             | 1\ncompression_enabled    | f\ntablespaces            | \nprimary_dimension      | time\nprimary_dimension_type | timestamp with time zone\n-[ RECORD 2 ]----------+-------------------------\nhypertable_schema      | open\nhypertable_name        | open_ht\nowner                  | super_user\nnum_dimensions         | 1\nnum_chunks             | 3\ncompression_enabled    | f\ntablespaces            | \nprimary_dimension      | time\nprimary_dimension_type | timestamp with time zone\n\n\\x\n---Add integer table --\nCREATE TABLE test_table_int(time bigint, junk int);\nSELECT create_hypertable('test_table_int', 'time', chunk_time_interval => 10);\n      create_hypertable      \n-----------------------------\n (5,public,test_table_int,t)\n\nCREATE OR REPLACE function table_int_now() returns BIGINT LANGUAGE SQL IMMUTABLE as  'SELECT 1::BIGINT';\nSELECT set_integer_now_func('test_table_int', 'table_int_now');\n set_integer_now_func \n----------------------\n \n\nINSERT into test_table_int SELECT generate_series( 1, 20), 100;\n\\d timescaledb_information.chunks\n                       View \"timescaledb_information.chunks\"\n         Column         |           Type           | Collation | Nullable | Default \n------------------------+--------------------------+-----------+----------+---------\n hypertable_schema      | name                     |           |          | \n hypertable_name        | name                     |           |          | \n chunk_schema           | name                     |           |          | \n chunk_name             | name                     |           |          | \n primary_dimension      | name                     |           |          | \n primary_dimension_type | regtype                  |           |          | \n range_start            | timestamp with time zone |           |          | \n range_end              | timestamp with time zone |           |          | \n range_start_integer    | bigint                   |           |          | \n range_end_integer      | bigint                   |           |          | \n is_compressed          | boolean                  |           |          | \n chunk_tablespace       | name                     |           |          | \n chunk_creation_time    | timestamp with time zone |           |          | \n\nSELECT hypertable_schema,\n       hypertable_name,\n       chunk_schema,\n       chunk_name,\n       primary_dimension,\n       primary_dimension_type,\n       range_start,\n       range_end,\n       range_start_integer,\n       range_end_integer,\n       is_compressed,\n       chunk_tablespace,\n       data_nodes\nFROM timescaledb_information.chunks WHERE hypertable_name = 'ht1' ORDER BY chunk_name;\nERROR:  column \"data_nodes\" does not exist at character 294\nSELECT hypertable_schema,\n       hypertable_name,\n       chunk_schema,\n       chunk_name,\n       primary_dimension,\n       primary_dimension_type,\n       range_start,\n       range_end,\n       range_start_integer,\n       range_end_integer,\n       is_compressed,\n       chunk_tablespace,\n       data_nodes\nFROM timescaledb_information.chunks WHERE hypertable_name = 'test_table_int' ORDER BY chunk_name;\nERROR:  column \"data_nodes\" does not exist at character 294\n\\x\nSELECT * FROM timescaledb_information.dimensions ORDER BY hypertable_name, dimension_number;\n-[ RECORD 1 ]-----+-------------------------\nhypertable_schema | closed\nhypertable_name   | closed_ht\ndimension_number  | 1\ncolumn_name       | time\ncolumn_type       | timestamp with time zone\ndimension_type    | Time\ntime_interval     | @ 7 days\ninteger_interval  | \ninteger_now_func  | \nnum_partitions    | \n-[ RECORD 2 ]-----+-------------------------\nhypertable_schema | public\nhypertable_name   | ht1\ndimension_number  | 1\ncolumn_name       | time\ncolumn_type       | timestamp with time zone\ndimension_type    | Time\ntime_interval     | @ 7 days\ninteger_interval  | \ninteger_now_func  | \nnum_partitions    | \n-[ RECORD 3 ]-----+-------------------------\nhypertable_schema | public\nhypertable_name   | ht2\ndimension_number  | 1\ncolumn_name       | time\ncolumn_type       | timestamp with time zone\ndimension_type    | Time\ntime_interval     | @ 7 days\ninteger_interval  | \ninteger_now_func  | \nnum_partitions    | \n-[ RECORD 4 ]-----+-------------------------\nhypertable_schema | open\nhypertable_name   | open_ht\ndimension_number  | 1\ncolumn_name       | time\ncolumn_type       | timestamp with time zone\ndimension_type    | Time\ntime_interval     | @ 7 days\ninteger_interval  | \ninteger_now_func  | \nnum_partitions    | \n-[ RECORD 5 ]-----+-------------------------\nhypertable_schema | public\nhypertable_name   | test_table_int\ndimension_number  | 1\ncolumn_name       | time\ncolumn_type       | bigint\ndimension_type    | Time\ntime_interval     | \ninteger_interval  | 10\ninteger_now_func  | table_int_now\nnum_partitions    | \n\n\\x\n"
  },
  {
    "path": "test/expected/insert-15.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nSET enable_seqscan TO off;\n\\ir include/insert_two_partitions.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE PUBLIC.\"two_Partitions\" (\n  \"timeCustom\" BIGINT NOT NULL,\n  device_id TEXT NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL,\n  series_bool BOOLEAN NULL\n);\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (device_id, \"timeCustom\" DESC NULLS LAST) WHERE device_id IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_0) WHERE series_0 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_1)  WHERE series_1 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_2) WHERE series_2 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_bool) WHERE series_bool IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, device_id);\nSELECT * FROM create_hypertable('\"public\".\"two_Partitions\"'::regclass, 'timeCustom'::name, 'device_id'::name, associated_schema_name=>'_timescaledb_internal'::text, number_partitions => 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name |   table_name   | created \n---------------+-------------+----------------+---------\n             1 | public      | two_Partitions | t\n\n\\set QUIET off\nBEGIN;\nBEGIN\n\\COPY public.\"two_Partitions\" FROM 'data/ds1_dev1_1.tsv' NULL AS '';\nCOPY 7\nCOMMIT;\nCOMMIT\nINSERT INTO public.\"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257987600000000000, 'dev1', 1.5, 1),\n(1257987600000000000, 'dev1', 1.5, 2),\n(1257894000000000000, 'dev2', 1.5, 1),\n(1257894002000000000, 'dev1', 2.5, 3);\nINSERT 0 4\nINSERT INTO \"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257894000000000000, 'dev2', 1.5, 2);\nINSERT 0 1\n\\set QUIET on\nSELECT * FROM test.show_columnsp('_timescaledb_internal.%_hyper%');\n                                      Relation                                      | Kind |   Column    |   Column type    | NotNull \n------------------------------------------------------------------------------------+------+-------------+------------------+---------\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | timeCustom  | bigint           | t\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | device_id   | text             | t\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | series_0    | double precision | f\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | series_1    | double precision | f\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | series_2    | double precision | f\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | series_bool | boolean          | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_idx\"             | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | series_0    | double precision | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | series_1    | double precision | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | series_2    | double precision | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | series_bool | boolean          | f\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | timeCustom  | bigint           | t\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | device_id   | text             | t\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | series_0    | double precision | f\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | series_1    | double precision | f\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | series_2    | double precision | f\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | series_bool | boolean          | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_idx\"             | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | series_0    | double precision | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | series_1    | double precision | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | series_2    | double precision | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | series_bool | boolean          | f\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | timeCustom  | bigint           | t\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | device_id   | text             | t\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | series_0    | double precision | f\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | series_1    | double precision | f\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | series_2    | double precision | f\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | series_bool | boolean          | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_idx\"             | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | series_0    | double precision | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | series_1    | double precision | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | series_2    | double precision | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | series_bool | boolean          | f\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | timeCustom  | bigint           | t\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | device_id   | text             | t\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | series_0    | double precision | f\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | series_1    | double precision | f\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | series_2    | double precision | f\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | series_bool | boolean          | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_idx\"             | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | series_0    | double precision | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | series_1    | double precision | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | series_2    | double precision | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | series_bool | boolean          | f\n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%');\n                 Table                  |                                       Index                                        |         Columns          | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------+------------------------------------------------------------------------------------+--------------------------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_device_id_timeCustom_idx\"   | {device_id,timeCustom}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_0_idx\"    | {timeCustom,series_0}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx\"    | {timeCustom,series_1}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_2_idx\"    | {timeCustom,series_2}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_bool_idx\" | {timeCustom,series_bool} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_device_id_idx\"   | {timeCustom,device_id}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_idx\"             | {timeCustom}             |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_device_id_timeCustom_idx\"   | {device_id,timeCustom}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_0_idx\"    | {timeCustom,series_0}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_1_idx\"    | {timeCustom,series_1}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_2_idx\"    | {timeCustom,series_2}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_bool_idx\" | {timeCustom,series_bool} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_device_id_idx\"   | {timeCustom,device_id}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_idx\"             | {timeCustom}             |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_device_id_timeCustom_idx\"   | {device_id,timeCustom}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_0_idx\"    | {timeCustom,series_0}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_1_idx\"    | {timeCustom,series_1}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_2_idx\"    | {timeCustom,series_2}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_bool_idx\" | {timeCustom,series_bool} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_device_id_idx\"   | {timeCustom,device_id}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_idx\"             | {timeCustom}             |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_device_id_timeCustom_idx\"   | {device_id,timeCustom}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_0_idx\"    | {timeCustom,series_0}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_1_idx\"    | {timeCustom,series_1}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_2_idx\"    | {timeCustom,series_2}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_bool_idx\" | {timeCustom,series_bool} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_device_id_idx\"   | {timeCustom,device_id}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_idx\"             | {timeCustom}             |      | f      | f       | f         | \n\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n id | hypertable_id |      schema_name      |    table_name    | compressed_chunk_id | status | osm_chunk \n----+---------------+-----------------------+------------------+---------------------+--------+-----------\n  1 |             1 | _timescaledb_internal | _hyper_1_1_chunk |                     |      0 | f\n  2 |             1 | _timescaledb_internal | _hyper_1_2_chunk |                     |      0 | f\n  3 |             1 | _timescaledb_internal | _hyper_1_3_chunk |                     |      0 | f\n  4 |             1 | _timescaledb_internal | _hyper_1_4_chunk |                     |      0 | f\n\nSELECT * FROM \"two_Partitions\" ORDER BY \"timeCustom\", device_id, series_0, series_1;\n     timeCustom      | device_id | series_0 | series_1 | series_2 | series_bool \n---------------------+-----------+----------+----------+----------+-------------\n 1257894000000000000 | dev1      |      1.5 |        1 |        2 | t\n 1257894000000000000 | dev1      |      1.5 |        2 |          | \n 1257894000000000000 | dev2      |      1.5 |        1 |          | \n 1257894000000000000 | dev2      |      1.5 |        2 |          | \n 1257894000000001000 | dev1      |      2.5 |        3 |          | \n 1257894001000000000 | dev1      |      3.5 |        4 |          | \n 1257894002000000000 | dev1      |      2.5 |        3 |          | \n 1257894002000000000 | dev1      |      5.5 |        6 |          | t\n 1257894002000000000 | dev1      |      5.5 |        7 |          | f\n 1257897600000000000 | dev1      |      4.5 |        5 |          | f\n 1257987600000000000 | dev1      |      1.5 |        1 |          | \n 1257987600000000000 | dev1      |      1.5 |        2 |          | \n\nSELECT * FROM ONLY \"two_Partitions\";\n timeCustom | device_id | series_0 | series_1 | series_2 | series_bool \n------------+-----------+----------+----------+----------+-------------\n\nCREATE TABLE error_test(time timestamp, temp float8, device text NOT NULL);\nSELECT create_hypertable('error_test', 'time', 'device', 2);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n    create_hypertable    \n-------------------------\n (2,public,error_test,t)\n\n\\set QUIET off\nINSERT INTO error_test VALUES ('Mon Mar 20 09:18:20.1 2017', 21.3, 'dev1');\nINSERT 0 1\n\\set ON_ERROR_STOP 0\n-- generate insert error\nINSERT INTO error_test VALUES ('Mon Mar 20 09:18:22.3 2017', 21.1, NULL);\nERROR:  null value in column \"device\" of relation \"_hyper_2_6_chunk\" violates not-null constraint\n\\set ON_ERROR_STOP 1\nINSERT INTO error_test VALUES ('Mon Mar 20 09:18:25.7 2017', 22.4, 'dev2');\nINSERT 0 1\n\\set QUIET on\nSELECT * FROM error_test;\n            time            | temp | device \n----------------------------+------+--------\n Mon Mar 20 09:18:20.1 2017 | 21.3 | dev1\n Mon Mar 20 09:18:25.7 2017 | 22.4 | dev2\n\n--test character(9) partition keys since there were issues with padding causing partitioning errors\nCREATE TABLE tick_character (\n    symbol      character(9) NOT NULL,\n    mid       REAL NOT NULL,\n    spread      REAL NOT NULL,\n    time        TIMESTAMPTZ       NOT NULL\n);\nSELECT create_hypertable ('tick_character', 'time', 'symbol', 2);\n      create_hypertable      \n-----------------------------\n (3,public,tick_character,t)\n\nINSERT INTO tick_character ( symbol, mid, spread, time ) VALUES ( 'GBPJPY', 142.639000, 5.80, 'Mon Mar 20 09:18:22.3 2017') RETURNING time, symbol, mid;\n              time              |  symbol   |   mid   \n--------------------------------+-----------+---------\n Mon Mar 20 09:18:22.3 2017 PDT | GBPJPY    | 142.639\n\nSELECT * FROM tick_character;\n  symbol   |   mid   | spread |              time              \n-----------+---------+--------+--------------------------------\n GBPJPY    | 142.639 |    5.8 | Mon Mar 20 09:18:22.3 2017 PDT\n\nCREATE TABLE  date_col_test(time date, temp float8, device text NOT NULL);\nSELECT create_hypertable('date_col_test', 'time', 'device', 1000, chunk_time_interval => INTERVAL '1 Day');\n     create_hypertable      \n----------------------------\n (4,public,date_col_test,t)\n\nINSERT INTO date_col_test\nVALUES ('2001-02-01', 98, 'dev1'),\n('2001-03-02', 98, 'dev1');\nSELECT * FROM date_col_test WHERE time > '2001-01-01';\n    time    | temp | device \n------------+------+--------\n 02-01-2001 |   98 | dev1\n 03-02-2001 |   98 | dev1\n\n-- Out-of-order insertion regression test.\n-- this used to trip an assert in subspace_store.c checking that\n-- max_open_chunks_per_insert was obeyed\nset timescaledb.max_open_chunks_per_insert=1;\nCREATE TABLE chunk_assert_fail(i bigint, j bigint);\nSELECT create_hypertable('chunk_assert_fail', 'i', 'j', 1000, chunk_time_interval=>1);\n       create_hypertable        \n--------------------------------\n (5,public,chunk_assert_fail,t)\n\ninsert into chunk_assert_fail values (1, 1), (1, 2), (2,1);\nselect * from chunk_assert_fail;\n i | j \n---+---\n 1 | 1\n 1 | 2\n 2 | 1\n\nCREATE TABLE one_space_test(time timestamp, temp float8, device text NOT NULL);\nSELECT create_hypertable('one_space_test', 'time', 'device', 1);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n      create_hypertable      \n-----------------------------\n (6,public,one_space_test,t)\n\nINSERT INTO one_space_test VALUES\n('2001-01-01 01:01:01', 1.0, 'device'),\n('2002-01-01 01:02:01', 1.0, 'device');\nSELECT * FROM one_space_test;\n           time           | temp | device \n--------------------------+------+--------\n Mon Jan 01 01:01:01 2001 |    1 | device\n Tue Jan 01 01:02:01 2002 |    1 | device\n\n--CTE & EXPLAIN ANALYZE TESTS\nWITH insert_cte as (\n\tINSERT INTO one_space_test VALUES\n\t\t('2001-01-01 01:02:01', 1.0, 'device')\n\tRETURNING *)\nSELECT * FROM insert_cte;\n           time           | temp | device \n--------------------------+------+--------\n Mon Jan 01 01:02:01 2001 |    1 | device\n\nEXPLAIN (analyze, buffers off, costs off, timing off) --can't turn summary off in 9.6 so instead grep it away at end.\nWITH insert_cte as (\n\tINSERT INTO one_space_test VALUES\n\t\t('2001-01-01 01:03:01', 1.0, 'device')\n\t)\nSELECT 1 \\g | grep -v \"Planning\" | grep -v \"Execution\"\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   CTE insert_cte\n     ->  Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)\n           ->  Insert on one_space_test (actual rows=0.00 loops=1)\n                 ->  Result (actual rows=1.00 loops=1)\n\n-- INSERTs can exclude chunks based on constraints\nEXPLAIN (buffers off, costs off) INSERT INTO chunk_assert_fail SELECT i, j FROM chunk_assert_fail;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on chunk_assert_fail\n         ->  Append\n               ->  Index Only Scan using _hyper_5_11_chunk_chunk_assert_fail_j_i_idx on _hyper_5_11_chunk\n               ->  Index Only Scan using _hyper_5_12_chunk_chunk_assert_fail_j_i_idx on _hyper_5_12_chunk\n               ->  Index Only Scan using _hyper_5_13_chunk_chunk_assert_fail_j_i_idx on _hyper_5_13_chunk\n\nEXPLAIN (buffers off, costs off) INSERT INTO chunk_assert_fail SELECT i, j FROM chunk_assert_fail WHERE i < 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on chunk_assert_fail\n         ->  Result\n               One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO chunk_assert_fail SELECT i, j FROM chunk_assert_fail WHERE i = 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on chunk_assert_fail\n         ->  Append\n               ->  Index Scan using _hyper_5_11_chunk_chunk_assert_fail_i_idx on _hyper_5_11_chunk\n                     Index Cond: (i = 1)\n               ->  Index Scan using _hyper_5_12_chunk_chunk_assert_fail_i_idx on _hyper_5_12_chunk\n                     Index Cond: (i = 1)\n\nEXPLAIN (buffers off, costs off) INSERT INTO chunk_assert_fail SELECT i, j FROM chunk_assert_fail WHERE i > 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on chunk_assert_fail\n         ->  Index Scan using _hyper_5_13_chunk_chunk_assert_fail_i_idx on _hyper_5_13_chunk\n               Index Cond: (i > 1)\n\nINSERT INTO chunk_assert_fail SELECT i, j FROM chunk_assert_fail WHERE i > 1;\nEXPLAIN (buffers off, costs off) INSERT INTO one_space_test SELECT * FROM one_space_test WHERE time < 'infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on one_space_test\n         ->  Limit\n               ->  Append\n                     ->  Index Scan using _hyper_6_14_chunk_one_space_test_time_idx on _hyper_6_14_chunk\n                           Index Cond: (\"time\" < 'infinity'::timestamp without time zone)\n                     ->  Index Scan using _hyper_6_15_chunk_one_space_test_time_idx on _hyper_6_15_chunk\n                           Index Cond: (\"time\" < 'infinity'::timestamp without time zone)\n\nEXPLAIN (buffers off, costs off) INSERT INTO one_space_test SELECT * FROM one_space_test WHERE time >= 'infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on one_space_test\n         ->  Limit\n               ->  Result\n                     One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO one_space_test SELECT * FROM one_space_test WHERE time <= '-infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on one_space_test\n         ->  Limit\n               ->  Result\n                     One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO one_space_test SELECT * FROM one_space_test WHERE time > '-infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on one_space_test\n         ->  Limit\n               ->  Append\n                     ->  Index Scan using _hyper_6_14_chunk_one_space_test_time_idx on _hyper_6_14_chunk\n                           Index Cond: (\"time\" > '-infinity'::timestamp without time zone)\n                     ->  Index Scan using _hyper_6_15_chunk_one_space_test_time_idx on _hyper_6_15_chunk\n                           Index Cond: (\"time\" > '-infinity'::timestamp without time zone)\n\nINSERT INTO one_space_test SELECT * FROM one_space_test WHERE time < 'infinity' LIMIT 1;\nINSERT INTO one_space_test SELECT * FROM one_space_test WHERE time >= 'infinity' LIMIT 1;\nINSERT INTO one_space_test SELECT * FROM one_space_test WHERE time <= '-infinity' LIMIT 1;\nINSERT INTO one_space_test SELECT * FROM one_space_test WHERE time > '-infinity' LIMIT 1;\nCREATE TABLE timestamp_inf(time TIMESTAMP);\nSELECT create_hypertable('timestamp_inf', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n     create_hypertable      \n----------------------------\n (7,public,timestamp_inf,t)\n\nINSERT INTO timestamp_inf VALUES ('2018/01/02'), ('2019/01/02');\nEXPLAIN (buffers off, costs off) INSERT INTO timestamp_inf SELECT * FROM timestamp_inf\n    WHERE time < 'infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on timestamp_inf\n         ->  Limit\n               ->  Append\n                     ->  Index Only Scan using _hyper_7_16_chunk_timestamp_inf_time_idx on _hyper_7_16_chunk\n                     ->  Index Only Scan using _hyper_7_17_chunk_timestamp_inf_time_idx on _hyper_7_17_chunk\n\nEXPLAIN (buffers off, costs off) INSERT INTO timestamp_inf SELECT * FROM timestamp_inf\n    WHERE time >= 'infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on timestamp_inf\n         ->  Limit\n               ->  Result\n                     One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO timestamp_inf SELECT * FROM timestamp_inf\n    WHERE time <= '-infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on timestamp_inf\n         ->  Limit\n               ->  Result\n                     One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO timestamp_inf SELECT * FROM timestamp_inf\n    WHERE time > '-infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on timestamp_inf\n         ->  Limit\n               ->  Append\n                     ->  Index Only Scan using _hyper_7_16_chunk_timestamp_inf_time_idx on _hyper_7_16_chunk\n                     ->  Index Only Scan using _hyper_7_17_chunk_timestamp_inf_time_idx on _hyper_7_17_chunk\n\nCREATE TABLE date_inf(time DATE);\nSELECT create_hypertable('date_inf', 'time');\n   create_hypertable   \n-----------------------\n (8,public,date_inf,t)\n\nINSERT INTO date_inf VALUES ('2018/01/02'), ('2019/01/02');\nEXPLAIN (buffers off, costs off) INSERT INTO date_inf SELECT * FROM date_inf\n    WHERE time < 'infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on date_inf\n         ->  Limit\n               ->  Append\n                     ->  Index Only Scan using _hyper_8_18_chunk_date_inf_time_idx on _hyper_8_18_chunk\n                     ->  Index Only Scan using _hyper_8_19_chunk_date_inf_time_idx on _hyper_8_19_chunk\n\nEXPLAIN (buffers off, costs off) INSERT INTO date_inf SELECT * FROM date_inf\n    WHERE time >= 'infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on date_inf\n         ->  Limit\n               ->  Result\n                     One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO date_inf SELECT * FROM date_inf\n    WHERE time <= '-infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on date_inf\n         ->  Limit\n               ->  Result\n                     One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO date_inf SELECT * FROM date_inf\n    WHERE time > '-infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on date_inf\n         ->  Limit\n               ->  Append\n                     ->  Index Only Scan using _hyper_8_18_chunk_date_inf_time_idx on _hyper_8_18_chunk\n                     ->  Index Only Scan using _hyper_8_19_chunk_date_inf_time_idx on _hyper_8_19_chunk\n\n-- test INSERT with cached plans / plpgsql functions\n-- https://github.com/timescale/timescaledb/issues/1809\nCREATE TABLE status_table(a int, b int, last_ts timestamptz, UNIQUE(a,b));\nCREATE TABLE metrics(time timestamptz NOT NULL, value float);\nCREATE TABLE metrics2(time timestamptz NOT NULL, value float);\nSELECT (create_hypertable(t,'time')).table_name FROM (VALUES ('metrics'),('metrics2')) v(t);\n table_name \n------------\n metrics\n metrics2\n\nINSERT INTO metrics VALUES ('2000-01-01',random()), ('2000-02-01',random()), ('2000-03-01',random());\nCREATE OR REPLACE FUNCTION insert_test() RETURNS VOID LANGUAGE plpgsql AS\n$$\n  DECLARE\n    r RECORD;\n  BEGIN\n    FOR r IN\n      SELECT * FROM metrics\n    LOOP\n      WITH foo AS (\n        INSERT INTO metrics2 SELECT * FROM metrics RETURNING *\n      )\n      INSERT INTO status_table (a,b, last_ts)\n        VALUES (1,1, now())\n        ON CONFLICT (a,b) DO UPDATE SET last_ts=(SELECT max(time) FROM metrics);\n    END LOOP;\n  END;\n$$;\nSELECT insert_test(), insert_test(), insert_test();\n insert_test | insert_test | insert_test \n-------------+-------------+-------------\n             |             | \n\n-- test Postgres crashes on INSERT ... SELECT ... WHERE NOT EXISTS with empty table\n-- https://github.com/timescale/timescaledb/issues/1883\nCREATE TABLE readings (\n\ttoe TIMESTAMPTZ NOT NULL,\n\tsensor_id INT NOT NULL,\n\tvalue INT NOT NULL\n);\nSELECT create_hypertable(\n\t'readings',\n\t'toe',\n\tchunk_time_interval => interval '1 day',\n\tif_not_exists => TRUE,\n\tmigrate_data => TRUE\n);\n   create_hypertable    \n------------------------\n (11,public,readings,t)\n\nEXPLAIN (buffers off, costs off)\nINSERT INTO readings\nSELECT '2020-05-09 10:34:35.296288+00', 1, 0\nWHERE NOT EXISTS (\n\tSELECT 1\n\tFROM readings\n\tWHERE sensor_id = 1\n\t\tAND toe = '2020-05-09 10:34:35.296288+00'\n);\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   InitPlan 1 (returns $0)\n     ->  Result\n           One-Time Filter: false\n   ->  Insert on readings\n         ->  Result\n               One-Time Filter: (NOT $0)\n\nINSERT INTO readings\nSELECT '2020-05-09 10:34:35.296288+00', 1, 0\nWHERE NOT EXISTS (\n\tSELECT 1\n\tFROM readings\n\tWHERE sensor_id = 1\n\t\tAND toe = '2020-05-09 10:34:35.296288+00'\n);\nDROP TABLE readings;\nCREATE TABLE sample_table (\n       sequence INTEGER NOT NULL,\n       time TIMESTAMP WITHOUT TIME ZONE NOT NULL,\n       value NUMERIC NOT NULL,\n       UNIQUE (sequence, time)\n);\nSELECT * FROM create_hypertable('sample_table', 'time',\n       chunk_time_interval => INTERVAL '1 day');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n hypertable_id | schema_name |  table_name  | created \n---------------+-------------+--------------+---------\n            12 | public      | sample_table | t\n\nINSERT INTO sample_table (sequence,time,value) VALUES\n       (7, generate_series(TIMESTAMP '2019-08-01', TIMESTAMP '2019-08-10', INTERVAL '10 minutes'), ROUND(RANDOM()*10)::int);\n\\set ON_ERROR_STOP 0\nINSERT INTO sample_table (sequence,time,value) VALUES\n       (7, generate_series(TIMESTAMP '2019-07-21', TIMESTAMP '2019-08-01', INTERVAL '10 minutes'), ROUND(RANDOM()*10)::int);\nERROR:  duplicate key value violates unique constraint \"27_1_sample_table_sequence_time_key\"\n\\set ON_ERROR_STOP 1\nINSERT INTO sample_table (sequence,time,value) VALUES\n       (7,generate_series(TIMESTAMP '2019-01-01', TIMESTAMP '2019-07-01', '10 minutes'), ROUND(RANDOM()*10)::int);\nDROP TABLE sample_table;\n-- test on conflict clause on columns with default value\n-- issue #3037\nCREATE TABLE i3037(time timestamptz PRIMARY KEY);\nSELECT create_hypertable('i3037','time');\n  create_hypertable  \n---------------------\n (13,public,i3037,t)\n\nALTER TABLE i3037 ADD COLUMN value float DEFAULT 0;\nINSERT INTO i3037 VALUES ('2000-01-01');\nINSERT INTO i3037 VALUES ('2000-01-01') ON CONFLICT(time) DO UPDATE SET value = EXCLUDED.value;\n-- test inserting into chunks directly\nCREATE TABLE direct_insert(time timestamptz, meta text) WITH (tsdb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nINSERT INTO direct_insert VALUES ('2020-01-01');\nSELECT show_chunks('direct_insert') AS \"CHUNK\" \\gset\n--should have ModifyHyperable node\nEXPLAIN (costs off, timing off, summary off) INSERT INTO :CHUNK VALUES ('2020-01-01');\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on _hyper_14_231_chunk\n         ->  Result\n\n-- correct time range should succeed\nINSERT INTO :CHUNK VALUES ('2020-01-01') RETURNING *;\n             time             | meta \n------------------------------+------\n Wed Jan 01 00:00:00 2020 PST | \n\n-- incorrect time range should fail\n\\set ON_ERROR_STOP 0\nINSERT INTO :CHUNK VALUES ('2020-01-01'), ('2021-01-01') RETURNING *;\nERROR:  new row for relation \"_hyper_14_231_chunk\" violates chunk constraint\nINSERT INTO :CHUNK VALUES ('2025-01-01') RETURNING *;\nERROR:  new row for relation \"_hyper_14_231_chunk\" violates chunk constraint\n\\set ON_ERROR_STOP 1\n-- test that triggers on chunks work\nCREATE FUNCTION test_trigger() RETURNS TRIGGER AS $$\nBEGIN\n  RAISE NOTICE 'trigger called';\n  NEW.meta = 'triggered';\n  RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\nCREATE TRIGGER test_trigger BEFORE INSERT ON direct_insert FOR EACH ROW EXECUTE PROCEDURE test_trigger();\nINSERT INTO :CHUNK VALUES ('2020-01-01') RETURNING *;\nNOTICE:  trigger called\n             time             |   meta    \n------------------------------+-----------\n Wed Jan 01 00:00:00 2020 PST | triggered\n\n-- test upsert\nDELETE FROM direct_insert;\nALTER TABLE direct_insert ADD CONSTRAINT direct_insert_pkey PRIMARY KEY (time);\nINSERT INTO :CHUNK VALUES ('2020-01-01') RETURNING *;\nNOTICE:  trigger called\n             time             |   meta    \n------------------------------+-----------\n Wed Jan 01 00:00:00 2020 PST | triggered\n\n-- DO NOTHING should succeed\nINSERT INTO :CHUNK VALUES ('2020-01-01') ON CONFLICT DO NOTHING RETURNING *;\nNOTICE:  trigger called\n time | meta \n------+------\n\nINSERT INTO :CHUNK VALUES ('2020-01-01') ON CONFLICT (time) DO UPDATE SET meta = 'update' RETURNING *;\nNOTICE:  trigger called\n             time             |  meta  \n------------------------------+--------\n Wed Jan 01 00:00:00 2020 PST | update\n\n\\set ON_ERROR_STOP 0\n-- conflict should fail\nINSERT INTO :CHUNK VALUES ('2020-01-01') RETURNING *;\nNOTICE:  trigger called\nERROR:  duplicate key value violates unique constraint \"231_205_direct_insert_pkey\"\nINSERT INTO :CHUNK VALUES ('2020-01-01') ON CONFLICT (time) DO UPDATE SET time = '2000-01-01' RETURNING *;\nNOTICE:  trigger called\nERROR:  new row for relation \"_hyper_14_231_chunk\" violates check constraint \"constraint_237\"\nINSERT INTO :CHUNK VALUES ('2020-01-01') ON CONFLICT (time) DO UPDATE SET time = EXCLUDED.time + '1 year' RETURNING *;\nNOTICE:  trigger called\nERROR:  new row for relation \"_hyper_14_231_chunk\" violates check constraint \"constraint_237\"\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "test/expected/insert-16.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nSET enable_seqscan TO off;\n\\ir include/insert_two_partitions.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE PUBLIC.\"two_Partitions\" (\n  \"timeCustom\" BIGINT NOT NULL,\n  device_id TEXT NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL,\n  series_bool BOOLEAN NULL\n);\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (device_id, \"timeCustom\" DESC NULLS LAST) WHERE device_id IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_0) WHERE series_0 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_1)  WHERE series_1 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_2) WHERE series_2 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_bool) WHERE series_bool IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, device_id);\nSELECT * FROM create_hypertable('\"public\".\"two_Partitions\"'::regclass, 'timeCustom'::name, 'device_id'::name, associated_schema_name=>'_timescaledb_internal'::text, number_partitions => 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name |   table_name   | created \n---------------+-------------+----------------+---------\n             1 | public      | two_Partitions | t\n\n\\set QUIET off\nBEGIN;\nBEGIN\n\\COPY public.\"two_Partitions\" FROM 'data/ds1_dev1_1.tsv' NULL AS '';\nCOPY 7\nCOMMIT;\nCOMMIT\nINSERT INTO public.\"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257987600000000000, 'dev1', 1.5, 1),\n(1257987600000000000, 'dev1', 1.5, 2),\n(1257894000000000000, 'dev2', 1.5, 1),\n(1257894002000000000, 'dev1', 2.5, 3);\nINSERT 0 4\nINSERT INTO \"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257894000000000000, 'dev2', 1.5, 2);\nINSERT 0 1\n\\set QUIET on\nSELECT * FROM test.show_columnsp('_timescaledb_internal.%_hyper%');\n                                      Relation                                      | Kind |   Column    |   Column type    | NotNull \n------------------------------------------------------------------------------------+------+-------------+------------------+---------\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | timeCustom  | bigint           | t\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | device_id   | text             | t\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | series_0    | double precision | f\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | series_1    | double precision | f\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | series_2    | double precision | f\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | series_bool | boolean          | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_idx\"             | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | series_0    | double precision | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | series_1    | double precision | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | series_2    | double precision | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | series_bool | boolean          | f\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | timeCustom  | bigint           | t\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | device_id   | text             | t\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | series_0    | double precision | f\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | series_1    | double precision | f\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | series_2    | double precision | f\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | series_bool | boolean          | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_idx\"             | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | series_0    | double precision | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | series_1    | double precision | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | series_2    | double precision | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | series_bool | boolean          | f\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | timeCustom  | bigint           | t\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | device_id   | text             | t\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | series_0    | double precision | f\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | series_1    | double precision | f\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | series_2    | double precision | f\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | series_bool | boolean          | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_idx\"             | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | series_0    | double precision | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | series_1    | double precision | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | series_2    | double precision | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | series_bool | boolean          | f\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | timeCustom  | bigint           | t\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | device_id   | text             | t\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | series_0    | double precision | f\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | series_1    | double precision | f\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | series_2    | double precision | f\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | series_bool | boolean          | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_idx\"             | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | series_0    | double precision | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | series_1    | double precision | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | series_2    | double precision | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | series_bool | boolean          | f\n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%');\n                 Table                  |                                       Index                                        |         Columns          | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------+------------------------------------------------------------------------------------+--------------------------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_device_id_timeCustom_idx\"   | {device_id,timeCustom}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_0_idx\"    | {timeCustom,series_0}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx\"    | {timeCustom,series_1}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_2_idx\"    | {timeCustom,series_2}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_bool_idx\" | {timeCustom,series_bool} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_device_id_idx\"   | {timeCustom,device_id}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_idx\"             | {timeCustom}             |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_device_id_timeCustom_idx\"   | {device_id,timeCustom}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_0_idx\"    | {timeCustom,series_0}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_1_idx\"    | {timeCustom,series_1}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_2_idx\"    | {timeCustom,series_2}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_bool_idx\" | {timeCustom,series_bool} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_device_id_idx\"   | {timeCustom,device_id}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_idx\"             | {timeCustom}             |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_device_id_timeCustom_idx\"   | {device_id,timeCustom}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_0_idx\"    | {timeCustom,series_0}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_1_idx\"    | {timeCustom,series_1}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_2_idx\"    | {timeCustom,series_2}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_bool_idx\" | {timeCustom,series_bool} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_device_id_idx\"   | {timeCustom,device_id}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_idx\"             | {timeCustom}             |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_device_id_timeCustom_idx\"   | {device_id,timeCustom}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_0_idx\"    | {timeCustom,series_0}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_1_idx\"    | {timeCustom,series_1}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_2_idx\"    | {timeCustom,series_2}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_bool_idx\" | {timeCustom,series_bool} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_device_id_idx\"   | {timeCustom,device_id}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_idx\"             | {timeCustom}             |      | f      | f       | f         | \n\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n id | hypertable_id |      schema_name      |    table_name    | compressed_chunk_id | status | osm_chunk \n----+---------------+-----------------------+------------------+---------------------+--------+-----------\n  1 |             1 | _timescaledb_internal | _hyper_1_1_chunk |                     |      0 | f\n  2 |             1 | _timescaledb_internal | _hyper_1_2_chunk |                     |      0 | f\n  3 |             1 | _timescaledb_internal | _hyper_1_3_chunk |                     |      0 | f\n  4 |             1 | _timescaledb_internal | _hyper_1_4_chunk |                     |      0 | f\n\nSELECT * FROM \"two_Partitions\" ORDER BY \"timeCustom\", device_id, series_0, series_1;\n     timeCustom      | device_id | series_0 | series_1 | series_2 | series_bool \n---------------------+-----------+----------+----------+----------+-------------\n 1257894000000000000 | dev1      |      1.5 |        1 |        2 | t\n 1257894000000000000 | dev1      |      1.5 |        2 |          | \n 1257894000000000000 | dev2      |      1.5 |        1 |          | \n 1257894000000000000 | dev2      |      1.5 |        2 |          | \n 1257894000000001000 | dev1      |      2.5 |        3 |          | \n 1257894001000000000 | dev1      |      3.5 |        4 |          | \n 1257894002000000000 | dev1      |      2.5 |        3 |          | \n 1257894002000000000 | dev1      |      5.5 |        6 |          | t\n 1257894002000000000 | dev1      |      5.5 |        7 |          | f\n 1257897600000000000 | dev1      |      4.5 |        5 |          | f\n 1257987600000000000 | dev1      |      1.5 |        1 |          | \n 1257987600000000000 | dev1      |      1.5 |        2 |          | \n\nSELECT * FROM ONLY \"two_Partitions\";\n timeCustom | device_id | series_0 | series_1 | series_2 | series_bool \n------------+-----------+----------+----------+----------+-------------\n\nCREATE TABLE error_test(time timestamp, temp float8, device text NOT NULL);\nSELECT create_hypertable('error_test', 'time', 'device', 2);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n    create_hypertable    \n-------------------------\n (2,public,error_test,t)\n\n\\set QUIET off\nINSERT INTO error_test VALUES ('Mon Mar 20 09:18:20.1 2017', 21.3, 'dev1');\nINSERT 0 1\n\\set ON_ERROR_STOP 0\n-- generate insert error\nINSERT INTO error_test VALUES ('Mon Mar 20 09:18:22.3 2017', 21.1, NULL);\nERROR:  null value in column \"device\" of relation \"_hyper_2_6_chunk\" violates not-null constraint\n\\set ON_ERROR_STOP 1\nINSERT INTO error_test VALUES ('Mon Mar 20 09:18:25.7 2017', 22.4, 'dev2');\nINSERT 0 1\n\\set QUIET on\nSELECT * FROM error_test;\n            time            | temp | device \n----------------------------+------+--------\n Mon Mar 20 09:18:20.1 2017 | 21.3 | dev1\n Mon Mar 20 09:18:25.7 2017 | 22.4 | dev2\n\n--test character(9) partition keys since there were issues with padding causing partitioning errors\nCREATE TABLE tick_character (\n    symbol      character(9) NOT NULL,\n    mid       REAL NOT NULL,\n    spread      REAL NOT NULL,\n    time        TIMESTAMPTZ       NOT NULL\n);\nSELECT create_hypertable ('tick_character', 'time', 'symbol', 2);\n      create_hypertable      \n-----------------------------\n (3,public,tick_character,t)\n\nINSERT INTO tick_character ( symbol, mid, spread, time ) VALUES ( 'GBPJPY', 142.639000, 5.80, 'Mon Mar 20 09:18:22.3 2017') RETURNING time, symbol, mid;\n              time              |  symbol   |   mid   \n--------------------------------+-----------+---------\n Mon Mar 20 09:18:22.3 2017 PDT | GBPJPY    | 142.639\n\nSELECT * FROM tick_character;\n  symbol   |   mid   | spread |              time              \n-----------+---------+--------+--------------------------------\n GBPJPY    | 142.639 |    5.8 | Mon Mar 20 09:18:22.3 2017 PDT\n\nCREATE TABLE  date_col_test(time date, temp float8, device text NOT NULL);\nSELECT create_hypertable('date_col_test', 'time', 'device', 1000, chunk_time_interval => INTERVAL '1 Day');\n     create_hypertable      \n----------------------------\n (4,public,date_col_test,t)\n\nINSERT INTO date_col_test\nVALUES ('2001-02-01', 98, 'dev1'),\n('2001-03-02', 98, 'dev1');\nSELECT * FROM date_col_test WHERE time > '2001-01-01';\n    time    | temp | device \n------------+------+--------\n 02-01-2001 |   98 | dev1\n 03-02-2001 |   98 | dev1\n\n-- Out-of-order insertion regression test.\n-- this used to trip an assert in subspace_store.c checking that\n-- max_open_chunks_per_insert was obeyed\nset timescaledb.max_open_chunks_per_insert=1;\nCREATE TABLE chunk_assert_fail(i bigint, j bigint);\nSELECT create_hypertable('chunk_assert_fail', 'i', 'j', 1000, chunk_time_interval=>1);\n       create_hypertable        \n--------------------------------\n (5,public,chunk_assert_fail,t)\n\ninsert into chunk_assert_fail values (1, 1), (1, 2), (2,1);\nselect * from chunk_assert_fail;\n i | j \n---+---\n 1 | 1\n 1 | 2\n 2 | 1\n\nCREATE TABLE one_space_test(time timestamp, temp float8, device text NOT NULL);\nSELECT create_hypertable('one_space_test', 'time', 'device', 1);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n      create_hypertable      \n-----------------------------\n (6,public,one_space_test,t)\n\nINSERT INTO one_space_test VALUES\n('2001-01-01 01:01:01', 1.0, 'device'),\n('2002-01-01 01:02:01', 1.0, 'device');\nSELECT * FROM one_space_test;\n           time           | temp | device \n--------------------------+------+--------\n Mon Jan 01 01:01:01 2001 |    1 | device\n Tue Jan 01 01:02:01 2002 |    1 | device\n\n--CTE & EXPLAIN ANALYZE TESTS\nWITH insert_cte as (\n\tINSERT INTO one_space_test VALUES\n\t\t('2001-01-01 01:02:01', 1.0, 'device')\n\tRETURNING *)\nSELECT * FROM insert_cte;\n           time           | temp | device \n--------------------------+------+--------\n Mon Jan 01 01:02:01 2001 |    1 | device\n\nEXPLAIN (analyze, buffers off, costs off, timing off) --can't turn summary off in 9.6 so instead grep it away at end.\nWITH insert_cte as (\n\tINSERT INTO one_space_test VALUES\n\t\t('2001-01-01 01:03:01', 1.0, 'device')\n\t)\nSELECT 1 \\g | grep -v \"Planning\" | grep -v \"Execution\"\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   CTE insert_cte\n     ->  Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)\n           ->  Insert on one_space_test (actual rows=0.00 loops=1)\n                 ->  Result (actual rows=1.00 loops=1)\n\n-- INSERTs can exclude chunks based on constraints\nEXPLAIN (buffers off, costs off) INSERT INTO chunk_assert_fail SELECT i, j FROM chunk_assert_fail;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on chunk_assert_fail\n         ->  Append\n               ->  Index Only Scan using _hyper_5_11_chunk_chunk_assert_fail_j_i_idx on _hyper_5_11_chunk\n               ->  Index Only Scan using _hyper_5_12_chunk_chunk_assert_fail_j_i_idx on _hyper_5_12_chunk\n               ->  Index Only Scan using _hyper_5_13_chunk_chunk_assert_fail_j_i_idx on _hyper_5_13_chunk\n\nEXPLAIN (buffers off, costs off) INSERT INTO chunk_assert_fail SELECT i, j FROM chunk_assert_fail WHERE i < 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on chunk_assert_fail\n         ->  Result\n               One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO chunk_assert_fail SELECT i, j FROM chunk_assert_fail WHERE i = 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on chunk_assert_fail\n         ->  Append\n               ->  Index Scan using _hyper_5_11_chunk_chunk_assert_fail_i_idx on _hyper_5_11_chunk\n                     Index Cond: (i = 1)\n               ->  Index Scan using _hyper_5_12_chunk_chunk_assert_fail_i_idx on _hyper_5_12_chunk\n                     Index Cond: (i = 1)\n\nEXPLAIN (buffers off, costs off) INSERT INTO chunk_assert_fail SELECT i, j FROM chunk_assert_fail WHERE i > 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on chunk_assert_fail\n         ->  Index Scan using _hyper_5_13_chunk_chunk_assert_fail_i_idx on _hyper_5_13_chunk\n               Index Cond: (i > 1)\n\nINSERT INTO chunk_assert_fail SELECT i, j FROM chunk_assert_fail WHERE i > 1;\nEXPLAIN (buffers off, costs off) INSERT INTO one_space_test SELECT * FROM one_space_test WHERE time < 'infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on one_space_test\n         ->  Limit\n               ->  Append\n                     ->  Index Scan using _hyper_6_14_chunk_one_space_test_time_idx on _hyper_6_14_chunk\n                           Index Cond: (\"time\" < 'infinity'::timestamp without time zone)\n                     ->  Index Scan using _hyper_6_15_chunk_one_space_test_time_idx on _hyper_6_15_chunk\n                           Index Cond: (\"time\" < 'infinity'::timestamp without time zone)\n\nEXPLAIN (buffers off, costs off) INSERT INTO one_space_test SELECT * FROM one_space_test WHERE time >= 'infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on one_space_test\n         ->  Limit\n               ->  Result\n                     One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO one_space_test SELECT * FROM one_space_test WHERE time <= '-infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on one_space_test\n         ->  Limit\n               ->  Result\n                     One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO one_space_test SELECT * FROM one_space_test WHERE time > '-infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on one_space_test\n         ->  Limit\n               ->  Append\n                     ->  Index Scan using _hyper_6_14_chunk_one_space_test_time_idx on _hyper_6_14_chunk\n                           Index Cond: (\"time\" > '-infinity'::timestamp without time zone)\n                     ->  Index Scan using _hyper_6_15_chunk_one_space_test_time_idx on _hyper_6_15_chunk\n                           Index Cond: (\"time\" > '-infinity'::timestamp without time zone)\n\nINSERT INTO one_space_test SELECT * FROM one_space_test WHERE time < 'infinity' LIMIT 1;\nINSERT INTO one_space_test SELECT * FROM one_space_test WHERE time >= 'infinity' LIMIT 1;\nINSERT INTO one_space_test SELECT * FROM one_space_test WHERE time <= '-infinity' LIMIT 1;\nINSERT INTO one_space_test SELECT * FROM one_space_test WHERE time > '-infinity' LIMIT 1;\nCREATE TABLE timestamp_inf(time TIMESTAMP);\nSELECT create_hypertable('timestamp_inf', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n     create_hypertable      \n----------------------------\n (7,public,timestamp_inf,t)\n\nINSERT INTO timestamp_inf VALUES ('2018/01/02'), ('2019/01/02');\nEXPLAIN (buffers off, costs off) INSERT INTO timestamp_inf SELECT * FROM timestamp_inf\n    WHERE time < 'infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on timestamp_inf\n         ->  Limit\n               ->  Append\n                     ->  Index Only Scan using _hyper_7_16_chunk_timestamp_inf_time_idx on _hyper_7_16_chunk\n                     ->  Index Only Scan using _hyper_7_17_chunk_timestamp_inf_time_idx on _hyper_7_17_chunk\n\nEXPLAIN (buffers off, costs off) INSERT INTO timestamp_inf SELECT * FROM timestamp_inf\n    WHERE time >= 'infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on timestamp_inf\n         ->  Limit\n               ->  Result\n                     One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO timestamp_inf SELECT * FROM timestamp_inf\n    WHERE time <= '-infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on timestamp_inf\n         ->  Limit\n               ->  Result\n                     One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO timestamp_inf SELECT * FROM timestamp_inf\n    WHERE time > '-infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on timestamp_inf\n         ->  Limit\n               ->  Append\n                     ->  Index Only Scan using _hyper_7_16_chunk_timestamp_inf_time_idx on _hyper_7_16_chunk\n                     ->  Index Only Scan using _hyper_7_17_chunk_timestamp_inf_time_idx on _hyper_7_17_chunk\n\nCREATE TABLE date_inf(time DATE);\nSELECT create_hypertable('date_inf', 'time');\n   create_hypertable   \n-----------------------\n (8,public,date_inf,t)\n\nINSERT INTO date_inf VALUES ('2018/01/02'), ('2019/01/02');\nEXPLAIN (buffers off, costs off) INSERT INTO date_inf SELECT * FROM date_inf\n    WHERE time < 'infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on date_inf\n         ->  Limit\n               ->  Append\n                     ->  Index Only Scan using _hyper_8_18_chunk_date_inf_time_idx on _hyper_8_18_chunk\n                     ->  Index Only Scan using _hyper_8_19_chunk_date_inf_time_idx on _hyper_8_19_chunk\n\nEXPLAIN (buffers off, costs off) INSERT INTO date_inf SELECT * FROM date_inf\n    WHERE time >= 'infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on date_inf\n         ->  Limit\n               ->  Result\n                     One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO date_inf SELECT * FROM date_inf\n    WHERE time <= '-infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on date_inf\n         ->  Limit\n               ->  Result\n                     One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO date_inf SELECT * FROM date_inf\n    WHERE time > '-infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on date_inf\n         ->  Limit\n               ->  Append\n                     ->  Index Only Scan using _hyper_8_18_chunk_date_inf_time_idx on _hyper_8_18_chunk\n                     ->  Index Only Scan using _hyper_8_19_chunk_date_inf_time_idx on _hyper_8_19_chunk\n\n-- test INSERT with cached plans / plpgsql functions\n-- https://github.com/timescale/timescaledb/issues/1809\nCREATE TABLE status_table(a int, b int, last_ts timestamptz, UNIQUE(a,b));\nCREATE TABLE metrics(time timestamptz NOT NULL, value float);\nCREATE TABLE metrics2(time timestamptz NOT NULL, value float);\nSELECT (create_hypertable(t,'time')).table_name FROM (VALUES ('metrics'),('metrics2')) v(t);\n table_name \n------------\n metrics\n metrics2\n\nINSERT INTO metrics VALUES ('2000-01-01',random()), ('2000-02-01',random()), ('2000-03-01',random());\nCREATE OR REPLACE FUNCTION insert_test() RETURNS VOID LANGUAGE plpgsql AS\n$$\n  DECLARE\n    r RECORD;\n  BEGIN\n    FOR r IN\n      SELECT * FROM metrics\n    LOOP\n      WITH foo AS (\n        INSERT INTO metrics2 SELECT * FROM metrics RETURNING *\n      )\n      INSERT INTO status_table (a,b, last_ts)\n        VALUES (1,1, now())\n        ON CONFLICT (a,b) DO UPDATE SET last_ts=(SELECT max(time) FROM metrics);\n    END LOOP;\n  END;\n$$;\nSELECT insert_test(), insert_test(), insert_test();\n insert_test | insert_test | insert_test \n-------------+-------------+-------------\n             |             | \n\n-- test Postgres crashes on INSERT ... SELECT ... WHERE NOT EXISTS with empty table\n-- https://github.com/timescale/timescaledb/issues/1883\nCREATE TABLE readings (\n\ttoe TIMESTAMPTZ NOT NULL,\n\tsensor_id INT NOT NULL,\n\tvalue INT NOT NULL\n);\nSELECT create_hypertable(\n\t'readings',\n\t'toe',\n\tchunk_time_interval => interval '1 day',\n\tif_not_exists => TRUE,\n\tmigrate_data => TRUE\n);\n   create_hypertable    \n------------------------\n (11,public,readings,t)\n\nEXPLAIN (buffers off, costs off)\nINSERT INTO readings\nSELECT '2020-05-09 10:34:35.296288+00', 1, 0\nWHERE NOT EXISTS (\n\tSELECT 1\n\tFROM readings\n\tWHERE sensor_id = 1\n\t\tAND toe = '2020-05-09 10:34:35.296288+00'\n);\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   InitPlan 1 (returns $0)\n     ->  Result\n           One-Time Filter: false\n   ->  Insert on readings\n         ->  Result\n               One-Time Filter: (NOT $0)\n\nINSERT INTO readings\nSELECT '2020-05-09 10:34:35.296288+00', 1, 0\nWHERE NOT EXISTS (\n\tSELECT 1\n\tFROM readings\n\tWHERE sensor_id = 1\n\t\tAND toe = '2020-05-09 10:34:35.296288+00'\n);\nDROP TABLE readings;\nCREATE TABLE sample_table (\n       sequence INTEGER NOT NULL,\n       time TIMESTAMP WITHOUT TIME ZONE NOT NULL,\n       value NUMERIC NOT NULL,\n       UNIQUE (sequence, time)\n);\nSELECT * FROM create_hypertable('sample_table', 'time',\n       chunk_time_interval => INTERVAL '1 day');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n hypertable_id | schema_name |  table_name  | created \n---------------+-------------+--------------+---------\n            12 | public      | sample_table | t\n\nINSERT INTO sample_table (sequence,time,value) VALUES\n       (7, generate_series(TIMESTAMP '2019-08-01', TIMESTAMP '2019-08-10', INTERVAL '10 minutes'), ROUND(RANDOM()*10)::int);\n\\set ON_ERROR_STOP 0\nINSERT INTO sample_table (sequence,time,value) VALUES\n       (7, generate_series(TIMESTAMP '2019-07-21', TIMESTAMP '2019-08-01', INTERVAL '10 minutes'), ROUND(RANDOM()*10)::int);\nERROR:  duplicate key value violates unique constraint \"27_1_sample_table_sequence_time_key\"\n\\set ON_ERROR_STOP 1\nINSERT INTO sample_table (sequence,time,value) VALUES\n       (7,generate_series(TIMESTAMP '2019-01-01', TIMESTAMP '2019-07-01', '10 minutes'), ROUND(RANDOM()*10)::int);\nDROP TABLE sample_table;\n-- test on conflict clause on columns with default value\n-- issue #3037\nCREATE TABLE i3037(time timestamptz PRIMARY KEY);\nSELECT create_hypertable('i3037','time');\n  create_hypertable  \n---------------------\n (13,public,i3037,t)\n\nALTER TABLE i3037 ADD COLUMN value float DEFAULT 0;\nINSERT INTO i3037 VALUES ('2000-01-01');\nINSERT INTO i3037 VALUES ('2000-01-01') ON CONFLICT(time) DO UPDATE SET value = EXCLUDED.value;\n-- test inserting into chunks directly\nCREATE TABLE direct_insert(time timestamptz, meta text) WITH (tsdb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nINSERT INTO direct_insert VALUES ('2020-01-01');\nSELECT show_chunks('direct_insert') AS \"CHUNK\" \\gset\n--should have ModifyHyperable node\nEXPLAIN (costs off, timing off, summary off) INSERT INTO :CHUNK VALUES ('2020-01-01');\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on _hyper_14_231_chunk\n         ->  Result\n\n-- correct time range should succeed\nINSERT INTO :CHUNK VALUES ('2020-01-01') RETURNING *;\n             time             | meta \n------------------------------+------\n Wed Jan 01 00:00:00 2020 PST | \n\n-- incorrect time range should fail\n\\set ON_ERROR_STOP 0\nINSERT INTO :CHUNK VALUES ('2020-01-01'), ('2021-01-01') RETURNING *;\nERROR:  new row for relation \"_hyper_14_231_chunk\" violates chunk constraint\nINSERT INTO :CHUNK VALUES ('2025-01-01') RETURNING *;\nERROR:  new row for relation \"_hyper_14_231_chunk\" violates chunk constraint\n\\set ON_ERROR_STOP 1\n-- test that triggers on chunks work\nCREATE FUNCTION test_trigger() RETURNS TRIGGER AS $$\nBEGIN\n  RAISE NOTICE 'trigger called';\n  NEW.meta = 'triggered';\n  RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\nCREATE TRIGGER test_trigger BEFORE INSERT ON direct_insert FOR EACH ROW EXECUTE PROCEDURE test_trigger();\nINSERT INTO :CHUNK VALUES ('2020-01-01') RETURNING *;\nNOTICE:  trigger called\n             time             |   meta    \n------------------------------+-----------\n Wed Jan 01 00:00:00 2020 PST | triggered\n\n-- test upsert\nDELETE FROM direct_insert;\nALTER TABLE direct_insert ADD CONSTRAINT direct_insert_pkey PRIMARY KEY (time);\nINSERT INTO :CHUNK VALUES ('2020-01-01') RETURNING *;\nNOTICE:  trigger called\n             time             |   meta    \n------------------------------+-----------\n Wed Jan 01 00:00:00 2020 PST | triggered\n\n-- DO NOTHING should succeed\nINSERT INTO :CHUNK VALUES ('2020-01-01') ON CONFLICT DO NOTHING RETURNING *;\nNOTICE:  trigger called\n time | meta \n------+------\n\nINSERT INTO :CHUNK VALUES ('2020-01-01') ON CONFLICT (time) DO UPDATE SET meta = 'update' RETURNING *;\nNOTICE:  trigger called\n             time             |  meta  \n------------------------------+--------\n Wed Jan 01 00:00:00 2020 PST | update\n\n\\set ON_ERROR_STOP 0\n-- conflict should fail\nINSERT INTO :CHUNK VALUES ('2020-01-01') RETURNING *;\nNOTICE:  trigger called\nERROR:  duplicate key value violates unique constraint \"231_205_direct_insert_pkey\"\nINSERT INTO :CHUNK VALUES ('2020-01-01') ON CONFLICT (time) DO UPDATE SET time = '2000-01-01' RETURNING *;\nNOTICE:  trigger called\nERROR:  new row for relation \"_hyper_14_231_chunk\" violates check constraint \"constraint_237\"\nINSERT INTO :CHUNK VALUES ('2020-01-01') ON CONFLICT (time) DO UPDATE SET time = EXCLUDED.time + '1 year' RETURNING *;\nNOTICE:  trigger called\nERROR:  new row for relation \"_hyper_14_231_chunk\" violates check constraint \"constraint_237\"\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "test/expected/insert-17.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nSET enable_seqscan TO off;\n\\ir include/insert_two_partitions.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE PUBLIC.\"two_Partitions\" (\n  \"timeCustom\" BIGINT NOT NULL,\n  device_id TEXT NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL,\n  series_bool BOOLEAN NULL\n);\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (device_id, \"timeCustom\" DESC NULLS LAST) WHERE device_id IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_0) WHERE series_0 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_1)  WHERE series_1 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_2) WHERE series_2 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_bool) WHERE series_bool IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, device_id);\nSELECT * FROM create_hypertable('\"public\".\"two_Partitions\"'::regclass, 'timeCustom'::name, 'device_id'::name, associated_schema_name=>'_timescaledb_internal'::text, number_partitions => 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name |   table_name   | created \n---------------+-------------+----------------+---------\n             1 | public      | two_Partitions | t\n\n\\set QUIET off\nBEGIN;\nBEGIN\n\\COPY public.\"two_Partitions\" FROM 'data/ds1_dev1_1.tsv' NULL AS '';\nCOPY 7\nCOMMIT;\nCOMMIT\nINSERT INTO public.\"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257987600000000000, 'dev1', 1.5, 1),\n(1257987600000000000, 'dev1', 1.5, 2),\n(1257894000000000000, 'dev2', 1.5, 1),\n(1257894002000000000, 'dev1', 2.5, 3);\nINSERT 0 4\nINSERT INTO \"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257894000000000000, 'dev2', 1.5, 2);\nINSERT 0 1\n\\set QUIET on\nSELECT * FROM test.show_columnsp('_timescaledb_internal.%_hyper%');\n                                      Relation                                      | Kind |   Column    |   Column type    | NotNull \n------------------------------------------------------------------------------------+------+-------------+------------------+---------\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | timeCustom  | bigint           | t\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | device_id   | text             | t\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | series_0    | double precision | f\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | series_1    | double precision | f\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | series_2    | double precision | f\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | series_bool | boolean          | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_idx\"             | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | series_0    | double precision | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | series_1    | double precision | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | series_2    | double precision | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | series_bool | boolean          | f\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | timeCustom  | bigint           | t\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | device_id   | text             | t\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | series_0    | double precision | f\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | series_1    | double precision | f\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | series_2    | double precision | f\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | series_bool | boolean          | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_idx\"             | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | series_0    | double precision | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | series_1    | double precision | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | series_2    | double precision | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | series_bool | boolean          | f\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | timeCustom  | bigint           | t\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | device_id   | text             | t\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | series_0    | double precision | f\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | series_1    | double precision | f\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | series_2    | double precision | f\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | series_bool | boolean          | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_idx\"             | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | series_0    | double precision | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | series_1    | double precision | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | series_2    | double precision | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | series_bool | boolean          | f\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | timeCustom  | bigint           | t\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | device_id   | text             | t\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | series_0    | double precision | f\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | series_1    | double precision | f\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | series_2    | double precision | f\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | series_bool | boolean          | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_idx\"             | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | series_0    | double precision | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | series_1    | double precision | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | series_2    | double precision | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | series_bool | boolean          | f\n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%');\n                 Table                  |                                       Index                                        |         Columns          | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------+------------------------------------------------------------------------------------+--------------------------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_device_id_timeCustom_idx\"   | {device_id,timeCustom}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_0_idx\"    | {timeCustom,series_0}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx\"    | {timeCustom,series_1}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_2_idx\"    | {timeCustom,series_2}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_bool_idx\" | {timeCustom,series_bool} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_device_id_idx\"   | {timeCustom,device_id}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_idx\"             | {timeCustom}             |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_device_id_timeCustom_idx\"   | {device_id,timeCustom}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_0_idx\"    | {timeCustom,series_0}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_1_idx\"    | {timeCustom,series_1}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_2_idx\"    | {timeCustom,series_2}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_bool_idx\" | {timeCustom,series_bool} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_device_id_idx\"   | {timeCustom,device_id}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_idx\"             | {timeCustom}             |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_device_id_timeCustom_idx\"   | {device_id,timeCustom}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_0_idx\"    | {timeCustom,series_0}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_1_idx\"    | {timeCustom,series_1}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_2_idx\"    | {timeCustom,series_2}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_bool_idx\" | {timeCustom,series_bool} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_device_id_idx\"   | {timeCustom,device_id}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_idx\"             | {timeCustom}             |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_device_id_timeCustom_idx\"   | {device_id,timeCustom}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_0_idx\"    | {timeCustom,series_0}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_1_idx\"    | {timeCustom,series_1}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_2_idx\"    | {timeCustom,series_2}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_bool_idx\" | {timeCustom,series_bool} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_device_id_idx\"   | {timeCustom,device_id}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_idx\"             | {timeCustom}             |      | f      | f       | f         | \n\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n id | hypertable_id |      schema_name      |    table_name    | compressed_chunk_id | status | osm_chunk \n----+---------------+-----------------------+------------------+---------------------+--------+-----------\n  1 |             1 | _timescaledb_internal | _hyper_1_1_chunk |                     |      0 | f\n  2 |             1 | _timescaledb_internal | _hyper_1_2_chunk |                     |      0 | f\n  3 |             1 | _timescaledb_internal | _hyper_1_3_chunk |                     |      0 | f\n  4 |             1 | _timescaledb_internal | _hyper_1_4_chunk |                     |      0 | f\n\nSELECT * FROM \"two_Partitions\" ORDER BY \"timeCustom\", device_id, series_0, series_1;\n     timeCustom      | device_id | series_0 | series_1 | series_2 | series_bool \n---------------------+-----------+----------+----------+----------+-------------\n 1257894000000000000 | dev1      |      1.5 |        1 |        2 | t\n 1257894000000000000 | dev1      |      1.5 |        2 |          | \n 1257894000000000000 | dev2      |      1.5 |        1 |          | \n 1257894000000000000 | dev2      |      1.5 |        2 |          | \n 1257894000000001000 | dev1      |      2.5 |        3 |          | \n 1257894001000000000 | dev1      |      3.5 |        4 |          | \n 1257894002000000000 | dev1      |      2.5 |        3 |          | \n 1257894002000000000 | dev1      |      5.5 |        6 |          | t\n 1257894002000000000 | dev1      |      5.5 |        7 |          | f\n 1257897600000000000 | dev1      |      4.5 |        5 |          | f\n 1257987600000000000 | dev1      |      1.5 |        1 |          | \n 1257987600000000000 | dev1      |      1.5 |        2 |          | \n\nSELECT * FROM ONLY \"two_Partitions\";\n timeCustom | device_id | series_0 | series_1 | series_2 | series_bool \n------------+-----------+----------+----------+----------+-------------\n\nCREATE TABLE error_test(time timestamp, temp float8, device text NOT NULL);\nSELECT create_hypertable('error_test', 'time', 'device', 2);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n    create_hypertable    \n-------------------------\n (2,public,error_test,t)\n\n\\set QUIET off\nINSERT INTO error_test VALUES ('Mon Mar 20 09:18:20.1 2017', 21.3, 'dev1');\nINSERT 0 1\n\\set ON_ERROR_STOP 0\n-- generate insert error\nINSERT INTO error_test VALUES ('Mon Mar 20 09:18:22.3 2017', 21.1, NULL);\nERROR:  null value in column \"device\" of relation \"_hyper_2_6_chunk\" violates not-null constraint\n\\set ON_ERROR_STOP 1\nINSERT INTO error_test VALUES ('Mon Mar 20 09:18:25.7 2017', 22.4, 'dev2');\nINSERT 0 1\n\\set QUIET on\nSELECT * FROM error_test;\n            time            | temp | device \n----------------------------+------+--------\n Mon Mar 20 09:18:20.1 2017 | 21.3 | dev1\n Mon Mar 20 09:18:25.7 2017 | 22.4 | dev2\n\n--test character(9) partition keys since there were issues with padding causing partitioning errors\nCREATE TABLE tick_character (\n    symbol      character(9) NOT NULL,\n    mid       REAL NOT NULL,\n    spread      REAL NOT NULL,\n    time        TIMESTAMPTZ       NOT NULL\n);\nSELECT create_hypertable ('tick_character', 'time', 'symbol', 2);\n      create_hypertable      \n-----------------------------\n (3,public,tick_character,t)\n\nINSERT INTO tick_character ( symbol, mid, spread, time ) VALUES ( 'GBPJPY', 142.639000, 5.80, 'Mon Mar 20 09:18:22.3 2017') RETURNING time, symbol, mid;\n              time              |  symbol   |   mid   \n--------------------------------+-----------+---------\n Mon Mar 20 09:18:22.3 2017 PDT | GBPJPY    | 142.639\n\nSELECT * FROM tick_character;\n  symbol   |   mid   | spread |              time              \n-----------+---------+--------+--------------------------------\n GBPJPY    | 142.639 |    5.8 | Mon Mar 20 09:18:22.3 2017 PDT\n\nCREATE TABLE  date_col_test(time date, temp float8, device text NOT NULL);\nSELECT create_hypertable('date_col_test', 'time', 'device', 1000, chunk_time_interval => INTERVAL '1 Day');\n     create_hypertable      \n----------------------------\n (4,public,date_col_test,t)\n\nINSERT INTO date_col_test\nVALUES ('2001-02-01', 98, 'dev1'),\n('2001-03-02', 98, 'dev1');\nSELECT * FROM date_col_test WHERE time > '2001-01-01';\n    time    | temp | device \n------------+------+--------\n 02-01-2001 |   98 | dev1\n 03-02-2001 |   98 | dev1\n\n-- Out-of-order insertion regression test.\n-- this used to trip an assert in subspace_store.c checking that\n-- max_open_chunks_per_insert was obeyed\nset timescaledb.max_open_chunks_per_insert=1;\nCREATE TABLE chunk_assert_fail(i bigint, j bigint);\nSELECT create_hypertable('chunk_assert_fail', 'i', 'j', 1000, chunk_time_interval=>1);\n       create_hypertable        \n--------------------------------\n (5,public,chunk_assert_fail,t)\n\ninsert into chunk_assert_fail values (1, 1), (1, 2), (2,1);\nselect * from chunk_assert_fail;\n i | j \n---+---\n 1 | 1\n 1 | 2\n 2 | 1\n\nCREATE TABLE one_space_test(time timestamp, temp float8, device text NOT NULL);\nSELECT create_hypertable('one_space_test', 'time', 'device', 1);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n      create_hypertable      \n-----------------------------\n (6,public,one_space_test,t)\n\nINSERT INTO one_space_test VALUES\n('2001-01-01 01:01:01', 1.0, 'device'),\n('2002-01-01 01:02:01', 1.0, 'device');\nSELECT * FROM one_space_test;\n           time           | temp | device \n--------------------------+------+--------\n Mon Jan 01 01:01:01 2001 |    1 | device\n Tue Jan 01 01:02:01 2002 |    1 | device\n\n--CTE & EXPLAIN ANALYZE TESTS\nWITH insert_cte as (\n\tINSERT INTO one_space_test VALUES\n\t\t('2001-01-01 01:02:01', 1.0, 'device')\n\tRETURNING *)\nSELECT * FROM insert_cte;\n           time           | temp | device \n--------------------------+------+--------\n Mon Jan 01 01:02:01 2001 |    1 | device\n\nEXPLAIN (analyze, buffers off, costs off, timing off) --can't turn summary off in 9.6 so instead grep it away at end.\nWITH insert_cte as (\n\tINSERT INTO one_space_test VALUES\n\t\t('2001-01-01 01:03:01', 1.0, 'device')\n\t)\nSELECT 1 \\g | grep -v \"Planning\" | grep -v \"Execution\"\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   CTE insert_cte\n     ->  Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)\n           ->  Insert on one_space_test (actual rows=0.00 loops=1)\n                 ->  Result (actual rows=1.00 loops=1)\n\n-- INSERTs can exclude chunks based on constraints\nEXPLAIN (buffers off, costs off) INSERT INTO chunk_assert_fail SELECT i, j FROM chunk_assert_fail;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on chunk_assert_fail\n         ->  Append\n               ->  Index Only Scan using _hyper_5_11_chunk_chunk_assert_fail_j_i_idx on _hyper_5_11_chunk\n               ->  Index Only Scan using _hyper_5_12_chunk_chunk_assert_fail_j_i_idx on _hyper_5_12_chunk\n               ->  Index Only Scan using _hyper_5_13_chunk_chunk_assert_fail_j_i_idx on _hyper_5_13_chunk\n\nEXPLAIN (buffers off, costs off) INSERT INTO chunk_assert_fail SELECT i, j FROM chunk_assert_fail WHERE i < 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on chunk_assert_fail\n         ->  Result\n               One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO chunk_assert_fail SELECT i, j FROM chunk_assert_fail WHERE i = 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on chunk_assert_fail\n         ->  Append\n               ->  Index Scan using _hyper_5_11_chunk_chunk_assert_fail_i_idx on _hyper_5_11_chunk\n                     Index Cond: (i = 1)\n               ->  Index Scan using _hyper_5_12_chunk_chunk_assert_fail_i_idx on _hyper_5_12_chunk\n                     Index Cond: (i = 1)\n\nEXPLAIN (buffers off, costs off) INSERT INTO chunk_assert_fail SELECT i, j FROM chunk_assert_fail WHERE i > 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on chunk_assert_fail\n         ->  Index Scan using _hyper_5_13_chunk_chunk_assert_fail_i_idx on _hyper_5_13_chunk\n               Index Cond: (i > 1)\n\nINSERT INTO chunk_assert_fail SELECT i, j FROM chunk_assert_fail WHERE i > 1;\nEXPLAIN (buffers off, costs off) INSERT INTO one_space_test SELECT * FROM one_space_test WHERE time < 'infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on one_space_test\n         ->  Limit\n               ->  Append\n                     ->  Index Scan using _hyper_6_14_chunk_one_space_test_time_idx on _hyper_6_14_chunk\n                           Index Cond: (\"time\" < 'infinity'::timestamp without time zone)\n                     ->  Index Scan using _hyper_6_15_chunk_one_space_test_time_idx on _hyper_6_15_chunk\n                           Index Cond: (\"time\" < 'infinity'::timestamp without time zone)\n\nEXPLAIN (buffers off, costs off) INSERT INTO one_space_test SELECT * FROM one_space_test WHERE time >= 'infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on one_space_test\n         ->  Limit\n               ->  Result\n                     One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO one_space_test SELECT * FROM one_space_test WHERE time <= '-infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on one_space_test\n         ->  Limit\n               ->  Result\n                     One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO one_space_test SELECT * FROM one_space_test WHERE time > '-infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on one_space_test\n         ->  Limit\n               ->  Append\n                     ->  Index Scan using _hyper_6_14_chunk_one_space_test_time_idx on _hyper_6_14_chunk\n                           Index Cond: (\"time\" > '-infinity'::timestamp without time zone)\n                     ->  Index Scan using _hyper_6_15_chunk_one_space_test_time_idx on _hyper_6_15_chunk\n                           Index Cond: (\"time\" > '-infinity'::timestamp without time zone)\n\nINSERT INTO one_space_test SELECT * FROM one_space_test WHERE time < 'infinity' LIMIT 1;\nINSERT INTO one_space_test SELECT * FROM one_space_test WHERE time >= 'infinity' LIMIT 1;\nINSERT INTO one_space_test SELECT * FROM one_space_test WHERE time <= '-infinity' LIMIT 1;\nINSERT INTO one_space_test SELECT * FROM one_space_test WHERE time > '-infinity' LIMIT 1;\nCREATE TABLE timestamp_inf(time TIMESTAMP);\nSELECT create_hypertable('timestamp_inf', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n     create_hypertable      \n----------------------------\n (7,public,timestamp_inf,t)\n\nINSERT INTO timestamp_inf VALUES ('2018/01/02'), ('2019/01/02');\nEXPLAIN (buffers off, costs off) INSERT INTO timestamp_inf SELECT * FROM timestamp_inf\n    WHERE time < 'infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on timestamp_inf\n         ->  Limit\n               ->  Append\n                     ->  Index Only Scan using _hyper_7_16_chunk_timestamp_inf_time_idx on _hyper_7_16_chunk\n                     ->  Index Only Scan using _hyper_7_17_chunk_timestamp_inf_time_idx on _hyper_7_17_chunk\n\nEXPLAIN (buffers off, costs off) INSERT INTO timestamp_inf SELECT * FROM timestamp_inf\n    WHERE time >= 'infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on timestamp_inf\n         ->  Limit\n               ->  Result\n                     One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO timestamp_inf SELECT * FROM timestamp_inf\n    WHERE time <= '-infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on timestamp_inf\n         ->  Limit\n               ->  Result\n                     One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO timestamp_inf SELECT * FROM timestamp_inf\n    WHERE time > '-infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on timestamp_inf\n         ->  Limit\n               ->  Append\n                     ->  Index Only Scan using _hyper_7_16_chunk_timestamp_inf_time_idx on _hyper_7_16_chunk\n                     ->  Index Only Scan using _hyper_7_17_chunk_timestamp_inf_time_idx on _hyper_7_17_chunk\n\nCREATE TABLE date_inf(time DATE);\nSELECT create_hypertable('date_inf', 'time');\n   create_hypertable   \n-----------------------\n (8,public,date_inf,t)\n\nINSERT INTO date_inf VALUES ('2018/01/02'), ('2019/01/02');\nEXPLAIN (buffers off, costs off) INSERT INTO date_inf SELECT * FROM date_inf\n    WHERE time < 'infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on date_inf\n         ->  Limit\n               ->  Append\n                     ->  Index Only Scan using _hyper_8_18_chunk_date_inf_time_idx on _hyper_8_18_chunk\n                     ->  Index Only Scan using _hyper_8_19_chunk_date_inf_time_idx on _hyper_8_19_chunk\n\nEXPLAIN (buffers off, costs off) INSERT INTO date_inf SELECT * FROM date_inf\n    WHERE time >= 'infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on date_inf\n         ->  Limit\n               ->  Result\n                     One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO date_inf SELECT * FROM date_inf\n    WHERE time <= '-infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on date_inf\n         ->  Limit\n               ->  Result\n                     One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO date_inf SELECT * FROM date_inf\n    WHERE time > '-infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on date_inf\n         ->  Limit\n               ->  Append\n                     ->  Index Only Scan using _hyper_8_18_chunk_date_inf_time_idx on _hyper_8_18_chunk\n                     ->  Index Only Scan using _hyper_8_19_chunk_date_inf_time_idx on _hyper_8_19_chunk\n\n-- test INSERT with cached plans / plpgsql functions\n-- https://github.com/timescale/timescaledb/issues/1809\nCREATE TABLE status_table(a int, b int, last_ts timestamptz, UNIQUE(a,b));\nCREATE TABLE metrics(time timestamptz NOT NULL, value float);\nCREATE TABLE metrics2(time timestamptz NOT NULL, value float);\nSELECT (create_hypertable(t,'time')).table_name FROM (VALUES ('metrics'),('metrics2')) v(t);\n table_name \n------------\n metrics\n metrics2\n\nINSERT INTO metrics VALUES ('2000-01-01',random()), ('2000-02-01',random()), ('2000-03-01',random());\nCREATE OR REPLACE FUNCTION insert_test() RETURNS VOID LANGUAGE plpgsql AS\n$$\n  DECLARE\n    r RECORD;\n  BEGIN\n    FOR r IN\n      SELECT * FROM metrics\n    LOOP\n      WITH foo AS (\n        INSERT INTO metrics2 SELECT * FROM metrics RETURNING *\n      )\n      INSERT INTO status_table (a,b, last_ts)\n        VALUES (1,1, now())\n        ON CONFLICT (a,b) DO UPDATE SET last_ts=(SELECT max(time) FROM metrics);\n    END LOOP;\n  END;\n$$;\nSELECT insert_test(), insert_test(), insert_test();\n insert_test | insert_test | insert_test \n-------------+-------------+-------------\n             |             | \n\n-- test Postgres crashes on INSERT ... SELECT ... WHERE NOT EXISTS with empty table\n-- https://github.com/timescale/timescaledb/issues/1883\nCREATE TABLE readings (\n\ttoe TIMESTAMPTZ NOT NULL,\n\tsensor_id INT NOT NULL,\n\tvalue INT NOT NULL\n);\nSELECT create_hypertable(\n\t'readings',\n\t'toe',\n\tchunk_time_interval => interval '1 day',\n\tif_not_exists => TRUE,\n\tmigrate_data => TRUE\n);\n   create_hypertable    \n------------------------\n (11,public,readings,t)\n\nEXPLAIN (buffers off, costs off)\nINSERT INTO readings\nSELECT '2020-05-09 10:34:35.296288+00', 1, 0\nWHERE NOT EXISTS (\n\tSELECT 1\n\tFROM readings\n\tWHERE sensor_id = 1\n\t\tAND toe = '2020-05-09 10:34:35.296288+00'\n);\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   InitPlan 1\n     ->  Result\n           One-Time Filter: false\n   ->  Insert on readings\n         ->  Result\n               One-Time Filter: (NOT (InitPlan 1).col1)\n\nINSERT INTO readings\nSELECT '2020-05-09 10:34:35.296288+00', 1, 0\nWHERE NOT EXISTS (\n\tSELECT 1\n\tFROM readings\n\tWHERE sensor_id = 1\n\t\tAND toe = '2020-05-09 10:34:35.296288+00'\n);\nDROP TABLE readings;\nCREATE TABLE sample_table (\n       sequence INTEGER NOT NULL,\n       time TIMESTAMP WITHOUT TIME ZONE NOT NULL,\n       value NUMERIC NOT NULL,\n       UNIQUE (sequence, time)\n);\nSELECT * FROM create_hypertable('sample_table', 'time',\n       chunk_time_interval => INTERVAL '1 day');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n hypertable_id | schema_name |  table_name  | created \n---------------+-------------+--------------+---------\n            12 | public      | sample_table | t\n\nINSERT INTO sample_table (sequence,time,value) VALUES\n       (7, generate_series(TIMESTAMP '2019-08-01', TIMESTAMP '2019-08-10', INTERVAL '10 minutes'), ROUND(RANDOM()*10)::int);\n\\set ON_ERROR_STOP 0\nINSERT INTO sample_table (sequence,time,value) VALUES\n       (7, generate_series(TIMESTAMP '2019-07-21', TIMESTAMP '2019-08-01', INTERVAL '10 minutes'), ROUND(RANDOM()*10)::int);\nERROR:  duplicate key value violates unique constraint \"27_1_sample_table_sequence_time_key\"\n\\set ON_ERROR_STOP 1\nINSERT INTO sample_table (sequence,time,value) VALUES\n       (7,generate_series(TIMESTAMP '2019-01-01', TIMESTAMP '2019-07-01', '10 minutes'), ROUND(RANDOM()*10)::int);\nDROP TABLE sample_table;\n-- test on conflict clause on columns with default value\n-- issue #3037\nCREATE TABLE i3037(time timestamptz PRIMARY KEY);\nSELECT create_hypertable('i3037','time');\n  create_hypertable  \n---------------------\n (13,public,i3037,t)\n\nALTER TABLE i3037 ADD COLUMN value float DEFAULT 0;\nINSERT INTO i3037 VALUES ('2000-01-01');\nINSERT INTO i3037 VALUES ('2000-01-01') ON CONFLICT(time) DO UPDATE SET value = EXCLUDED.value;\n-- test inserting into chunks directly\nCREATE TABLE direct_insert(time timestamptz, meta text) WITH (tsdb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nINSERT INTO direct_insert VALUES ('2020-01-01');\nSELECT show_chunks('direct_insert') AS \"CHUNK\" \\gset\n--should have ModifyHyperable node\nEXPLAIN (costs off, timing off, summary off) INSERT INTO :CHUNK VALUES ('2020-01-01');\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on _hyper_14_231_chunk\n         ->  Result\n\n-- correct time range should succeed\nINSERT INTO :CHUNK VALUES ('2020-01-01') RETURNING *;\n             time             | meta \n------------------------------+------\n Wed Jan 01 00:00:00 2020 PST | \n\n-- incorrect time range should fail\n\\set ON_ERROR_STOP 0\nINSERT INTO :CHUNK VALUES ('2020-01-01'), ('2021-01-01') RETURNING *;\nERROR:  new row for relation \"_hyper_14_231_chunk\" violates chunk constraint\nINSERT INTO :CHUNK VALUES ('2025-01-01') RETURNING *;\nERROR:  new row for relation \"_hyper_14_231_chunk\" violates chunk constraint\n\\set ON_ERROR_STOP 1\n-- test that triggers on chunks work\nCREATE FUNCTION test_trigger() RETURNS TRIGGER AS $$\nBEGIN\n  RAISE NOTICE 'trigger called';\n  NEW.meta = 'triggered';\n  RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\nCREATE TRIGGER test_trigger BEFORE INSERT ON direct_insert FOR EACH ROW EXECUTE PROCEDURE test_trigger();\nINSERT INTO :CHUNK VALUES ('2020-01-01') RETURNING *;\nNOTICE:  trigger called\n             time             |   meta    \n------------------------------+-----------\n Wed Jan 01 00:00:00 2020 PST | triggered\n\n-- test upsert\nDELETE FROM direct_insert;\nALTER TABLE direct_insert ADD CONSTRAINT direct_insert_pkey PRIMARY KEY (time);\nINSERT INTO :CHUNK VALUES ('2020-01-01') RETURNING *;\nNOTICE:  trigger called\n             time             |   meta    \n------------------------------+-----------\n Wed Jan 01 00:00:00 2020 PST | triggered\n\n-- DO NOTHING should succeed\nINSERT INTO :CHUNK VALUES ('2020-01-01') ON CONFLICT DO NOTHING RETURNING *;\nNOTICE:  trigger called\n time | meta \n------+------\n\nINSERT INTO :CHUNK VALUES ('2020-01-01') ON CONFLICT (time) DO UPDATE SET meta = 'update' RETURNING *;\nNOTICE:  trigger called\n             time             |  meta  \n------------------------------+--------\n Wed Jan 01 00:00:00 2020 PST | update\n\n\\set ON_ERROR_STOP 0\n-- conflict should fail\nINSERT INTO :CHUNK VALUES ('2020-01-01') RETURNING *;\nNOTICE:  trigger called\nERROR:  duplicate key value violates unique constraint \"231_205_direct_insert_pkey\"\nINSERT INTO :CHUNK VALUES ('2020-01-01') ON CONFLICT (time) DO UPDATE SET time = '2000-01-01' RETURNING *;\nNOTICE:  trigger called\nERROR:  new row for relation \"_hyper_14_231_chunk\" violates check constraint \"constraint_237\"\nINSERT INTO :CHUNK VALUES ('2020-01-01') ON CONFLICT (time) DO UPDATE SET time = EXCLUDED.time + '1 year' RETURNING *;\nNOTICE:  trigger called\nERROR:  new row for relation \"_hyper_14_231_chunk\" violates check constraint \"constraint_237\"\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "test/expected/insert-18.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nSET enable_seqscan TO off;\n\\ir include/insert_two_partitions.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE PUBLIC.\"two_Partitions\" (\n  \"timeCustom\" BIGINT NOT NULL,\n  device_id TEXT NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL,\n  series_bool BOOLEAN NULL\n);\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (device_id, \"timeCustom\" DESC NULLS LAST) WHERE device_id IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_0) WHERE series_0 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_1)  WHERE series_1 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_2) WHERE series_2 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_bool) WHERE series_bool IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, device_id);\nSELECT * FROM create_hypertable('\"public\".\"two_Partitions\"'::regclass, 'timeCustom'::name, 'device_id'::name, associated_schema_name=>'_timescaledb_internal'::text, number_partitions => 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name |   table_name   | created \n---------------+-------------+----------------+---------\n             1 | public      | two_Partitions | t\n\n\\set QUIET off\nBEGIN;\nBEGIN\n\\COPY public.\"two_Partitions\" FROM 'data/ds1_dev1_1.tsv' NULL AS '';\nCOPY 7\nCOMMIT;\nCOMMIT\nINSERT INTO public.\"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257987600000000000, 'dev1', 1.5, 1),\n(1257987600000000000, 'dev1', 1.5, 2),\n(1257894000000000000, 'dev2', 1.5, 1),\n(1257894002000000000, 'dev1', 2.5, 3);\nINSERT 0 4\nINSERT INTO \"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257894000000000000, 'dev2', 1.5, 2);\nINSERT 0 1\n\\set QUIET on\nSELECT * FROM test.show_columnsp('_timescaledb_internal.%_hyper%');\n                                      Relation                                      | Kind |   Column    |   Column type    | NotNull \n------------------------------------------------------------------------------------+------+-------------+------------------+---------\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | timeCustom  | bigint           | t\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | device_id   | text             | t\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | series_0    | double precision | f\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | series_1    | double precision | f\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | series_2    | double precision | f\n _timescaledb_internal._hyper_1_1_chunk                                             | r    | series_bool | boolean          | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_idx\"             | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | series_0    | double precision | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | series_1    | double precision | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | series_2    | double precision | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | series_bool | boolean          | f\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | timeCustom  | bigint           | t\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | device_id   | text             | t\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | series_0    | double precision | f\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | series_1    | double precision | f\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | series_2    | double precision | f\n _timescaledb_internal._hyper_1_2_chunk                                             | r    | series_bool | boolean          | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_idx\"             | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | series_0    | double precision | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | series_1    | double precision | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | series_2    | double precision | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | series_bool | boolean          | f\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | timeCustom  | bigint           | t\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | device_id   | text             | t\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | series_0    | double precision | f\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | series_1    | double precision | f\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | series_2    | double precision | f\n _timescaledb_internal._hyper_1_3_chunk                                             | r    | series_bool | boolean          | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_idx\"             | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | series_0    | double precision | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | series_1    | double precision | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | series_2    | double precision | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | series_bool | boolean          | f\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | timeCustom  | bigint           | t\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | device_id   | text             | t\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | series_0    | double precision | f\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | series_1    | double precision | f\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | series_2    | double precision | f\n _timescaledb_internal._hyper_1_4_chunk                                             | r    | series_bool | boolean          | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_device_id_timeCustom_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_device_id_idx\"   | i    | device_id   | text             | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_idx\"             | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_0_idx\"    | i    | series_0    | double precision | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_1_idx\"    | i    | series_1    | double precision | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_2_idx\"    | i    | series_2    | double precision | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | timeCustom  | bigint           | f\n _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_bool_idx\" | i    | series_bool | boolean          | f\n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%');\n                 Table                  |                                       Index                                        |         Columns          | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------+------------------------------------------------------------------------------------+--------------------------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_device_id_timeCustom_idx\"   | {device_id,timeCustom}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_0_idx\"    | {timeCustom,series_0}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx\"    | {timeCustom,series_1}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_2_idx\"    | {timeCustom,series_2}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_bool_idx\" | {timeCustom,series_bool} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_device_id_idx\"   | {timeCustom,device_id}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_idx\"             | {timeCustom}             |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_device_id_timeCustom_idx\"   | {device_id,timeCustom}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_0_idx\"    | {timeCustom,series_0}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_1_idx\"    | {timeCustom,series_1}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_2_idx\"    | {timeCustom,series_2}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_series_bool_idx\" | {timeCustom,series_bool} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_device_id_idx\"   | {timeCustom,device_id}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"_hyper_1_2_chunk_two_Partitions_timeCustom_idx\"             | {timeCustom}             |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_device_id_timeCustom_idx\"   | {device_id,timeCustom}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_0_idx\"    | {timeCustom,series_0}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_1_idx\"    | {timeCustom,series_1}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_2_idx\"    | {timeCustom,series_2}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_series_bool_idx\" | {timeCustom,series_bool} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_device_id_idx\"   | {timeCustom,device_id}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_3_chunk | _timescaledb_internal.\"_hyper_1_3_chunk_two_Partitions_timeCustom_idx\"             | {timeCustom}             |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_device_id_timeCustom_idx\"   | {device_id,timeCustom}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_0_idx\"    | {timeCustom,series_0}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_1_idx\"    | {timeCustom,series_1}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_2_idx\"    | {timeCustom,series_2}    |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_series_bool_idx\" | {timeCustom,series_bool} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_device_id_idx\"   | {timeCustom,device_id}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_4_chunk | _timescaledb_internal.\"_hyper_1_4_chunk_two_Partitions_timeCustom_idx\"             | {timeCustom}             |      | f      | f       | f         | \n\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n id | hypertable_id |      schema_name      |    table_name    | compressed_chunk_id | status | osm_chunk \n----+---------------+-----------------------+------------------+---------------------+--------+-----------\n  1 |             1 | _timescaledb_internal | _hyper_1_1_chunk |                     |      0 | f\n  2 |             1 | _timescaledb_internal | _hyper_1_2_chunk |                     |      0 | f\n  3 |             1 | _timescaledb_internal | _hyper_1_3_chunk |                     |      0 | f\n  4 |             1 | _timescaledb_internal | _hyper_1_4_chunk |                     |      0 | f\n\nSELECT * FROM \"two_Partitions\" ORDER BY \"timeCustom\", device_id, series_0, series_1;\n     timeCustom      | device_id | series_0 | series_1 | series_2 | series_bool \n---------------------+-----------+----------+----------+----------+-------------\n 1257894000000000000 | dev1      |      1.5 |        1 |        2 | t\n 1257894000000000000 | dev1      |      1.5 |        2 |          | \n 1257894000000000000 | dev2      |      1.5 |        1 |          | \n 1257894000000000000 | dev2      |      1.5 |        2 |          | \n 1257894000000001000 | dev1      |      2.5 |        3 |          | \n 1257894001000000000 | dev1      |      3.5 |        4 |          | \n 1257894002000000000 | dev1      |      2.5 |        3 |          | \n 1257894002000000000 | dev1      |      5.5 |        6 |          | t\n 1257894002000000000 | dev1      |      5.5 |        7 |          | f\n 1257897600000000000 | dev1      |      4.5 |        5 |          | f\n 1257987600000000000 | dev1      |      1.5 |        1 |          | \n 1257987600000000000 | dev1      |      1.5 |        2 |          | \n\nSELECT * FROM ONLY \"two_Partitions\";\n timeCustom | device_id | series_0 | series_1 | series_2 | series_bool \n------------+-----------+----------+----------+----------+-------------\n\nCREATE TABLE error_test(time timestamp, temp float8, device text NOT NULL);\nSELECT create_hypertable('error_test', 'time', 'device', 2);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n    create_hypertable    \n-------------------------\n (2,public,error_test,t)\n\n\\set QUIET off\nINSERT INTO error_test VALUES ('Mon Mar 20 09:18:20.1 2017', 21.3, 'dev1');\nINSERT 0 1\n\\set ON_ERROR_STOP 0\n-- generate insert error\nINSERT INTO error_test VALUES ('Mon Mar 20 09:18:22.3 2017', 21.1, NULL);\nERROR:  null value in column \"device\" of relation \"_hyper_2_6_chunk\" violates not-null constraint\n\\set ON_ERROR_STOP 1\nINSERT INTO error_test VALUES ('Mon Mar 20 09:18:25.7 2017', 22.4, 'dev2');\nINSERT 0 1\n\\set QUIET on\nSELECT * FROM error_test;\n            time            | temp | device \n----------------------------+------+--------\n Mon Mar 20 09:18:20.1 2017 | 21.3 | dev1\n Mon Mar 20 09:18:25.7 2017 | 22.4 | dev2\n\n--test character(9) partition keys since there were issues with padding causing partitioning errors\nCREATE TABLE tick_character (\n    symbol      character(9) NOT NULL,\n    mid       REAL NOT NULL,\n    spread      REAL NOT NULL,\n    time        TIMESTAMPTZ       NOT NULL\n);\nSELECT create_hypertable ('tick_character', 'time', 'symbol', 2);\n      create_hypertable      \n-----------------------------\n (3,public,tick_character,t)\n\nINSERT INTO tick_character ( symbol, mid, spread, time ) VALUES ( 'GBPJPY', 142.639000, 5.80, 'Mon Mar 20 09:18:22.3 2017') RETURNING time, symbol, mid;\n              time              |  symbol   |   mid   \n--------------------------------+-----------+---------\n Mon Mar 20 09:18:22.3 2017 PDT | GBPJPY    | 142.639\n\nSELECT * FROM tick_character;\n  symbol   |   mid   | spread |              time              \n-----------+---------+--------+--------------------------------\n GBPJPY    | 142.639 |    5.8 | Mon Mar 20 09:18:22.3 2017 PDT\n\nCREATE TABLE  date_col_test(time date, temp float8, device text NOT NULL);\nSELECT create_hypertable('date_col_test', 'time', 'device', 1000, chunk_time_interval => INTERVAL '1 Day');\n     create_hypertable      \n----------------------------\n (4,public,date_col_test,t)\n\nINSERT INTO date_col_test\nVALUES ('2001-02-01', 98, 'dev1'),\n('2001-03-02', 98, 'dev1');\nSELECT * FROM date_col_test WHERE time > '2001-01-01';\n    time    | temp | device \n------------+------+--------\n 02-01-2001 |   98 | dev1\n 03-02-2001 |   98 | dev1\n\n-- Out-of-order insertion regression test.\n-- this used to trip an assert in subspace_store.c checking that\n-- max_open_chunks_per_insert was obeyed\nset timescaledb.max_open_chunks_per_insert=1;\nCREATE TABLE chunk_assert_fail(i bigint, j bigint);\nSELECT create_hypertable('chunk_assert_fail', 'i', 'j', 1000, chunk_time_interval=>1);\n       create_hypertable        \n--------------------------------\n (5,public,chunk_assert_fail,t)\n\ninsert into chunk_assert_fail values (1, 1), (1, 2), (2,1);\nselect * from chunk_assert_fail;\n i | j \n---+---\n 1 | 1\n 1 | 2\n 2 | 1\n\nCREATE TABLE one_space_test(time timestamp, temp float8, device text NOT NULL);\nSELECT create_hypertable('one_space_test', 'time', 'device', 1);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n      create_hypertable      \n-----------------------------\n (6,public,one_space_test,t)\n\nINSERT INTO one_space_test VALUES\n('2001-01-01 01:01:01', 1.0, 'device'),\n('2002-01-01 01:02:01', 1.0, 'device');\nSELECT * FROM one_space_test;\n           time           | temp | device \n--------------------------+------+--------\n Mon Jan 01 01:01:01 2001 |    1 | device\n Tue Jan 01 01:02:01 2002 |    1 | device\n\n--CTE & EXPLAIN ANALYZE TESTS\nWITH insert_cte as (\n\tINSERT INTO one_space_test VALUES\n\t\t('2001-01-01 01:02:01', 1.0, 'device')\n\tRETURNING *)\nSELECT * FROM insert_cte;\n           time           | temp | device \n--------------------------+------+--------\n Mon Jan 01 01:02:01 2001 |    1 | device\n\nEXPLAIN (analyze, buffers off, costs off, timing off) --can't turn summary off in 9.6 so instead grep it away at end.\nWITH insert_cte as (\n\tINSERT INTO one_space_test VALUES\n\t\t('2001-01-01 01:03:01', 1.0, 'device')\n\t)\nSELECT 1 \\g | grep -v \"Planning\" | grep -v \"Execution\"\n--- QUERY PLAN ---\n Result (actual rows=1.00 loops=1)\n   CTE insert_cte\n     ->  Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)\n           ->  Insert on one_space_test (actual rows=0.00 loops=1)\n                 ->  Result (actual rows=1.00 loops=1)\n\n-- INSERTs can exclude chunks based on constraints\nEXPLAIN (buffers off, costs off) INSERT INTO chunk_assert_fail SELECT i, j FROM chunk_assert_fail;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on chunk_assert_fail\n         ->  Append\n               ->  Index Only Scan using _hyper_5_11_chunk_chunk_assert_fail_j_i_idx on _hyper_5_11_chunk\n               ->  Index Only Scan using _hyper_5_12_chunk_chunk_assert_fail_j_i_idx on _hyper_5_12_chunk\n               ->  Index Only Scan using _hyper_5_13_chunk_chunk_assert_fail_j_i_idx on _hyper_5_13_chunk\n\nEXPLAIN (buffers off, costs off) INSERT INTO chunk_assert_fail SELECT i, j FROM chunk_assert_fail WHERE i < 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on chunk_assert_fail\n         ->  Result\n               One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO chunk_assert_fail SELECT i, j FROM chunk_assert_fail WHERE i = 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on chunk_assert_fail\n         ->  Append\n               ->  Index Scan using _hyper_5_11_chunk_chunk_assert_fail_i_idx on _hyper_5_11_chunk\n                     Index Cond: (i = 1)\n               ->  Index Scan using _hyper_5_12_chunk_chunk_assert_fail_i_idx on _hyper_5_12_chunk\n                     Index Cond: (i = 1)\n\nEXPLAIN (buffers off, costs off) INSERT INTO chunk_assert_fail SELECT i, j FROM chunk_assert_fail WHERE i > 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on chunk_assert_fail\n         ->  Index Scan using _hyper_5_13_chunk_chunk_assert_fail_i_idx on _hyper_5_13_chunk\n               Index Cond: (i > 1)\n\nINSERT INTO chunk_assert_fail SELECT i, j FROM chunk_assert_fail WHERE i > 1;\nEXPLAIN (buffers off, costs off) INSERT INTO one_space_test SELECT * FROM one_space_test WHERE time < 'infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on one_space_test\n         ->  Limit\n               ->  Append\n                     ->  Index Scan using _hyper_6_14_chunk_one_space_test_time_idx on _hyper_6_14_chunk\n                           Index Cond: (\"time\" < 'infinity'::timestamp without time zone)\n                     ->  Index Scan using _hyper_6_15_chunk_one_space_test_time_idx on _hyper_6_15_chunk\n                           Index Cond: (\"time\" < 'infinity'::timestamp without time zone)\n\nEXPLAIN (buffers off, costs off) INSERT INTO one_space_test SELECT * FROM one_space_test WHERE time >= 'infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on one_space_test\n         ->  Limit\n               ->  Result\n                     One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO one_space_test SELECT * FROM one_space_test WHERE time <= '-infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on one_space_test\n         ->  Limit\n               ->  Result\n                     One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO one_space_test SELECT * FROM one_space_test WHERE time > '-infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on one_space_test\n         ->  Limit\n               ->  Append\n                     ->  Index Scan using _hyper_6_14_chunk_one_space_test_time_idx on _hyper_6_14_chunk\n                           Index Cond: (\"time\" > '-infinity'::timestamp without time zone)\n                     ->  Index Scan using _hyper_6_15_chunk_one_space_test_time_idx on _hyper_6_15_chunk\n                           Index Cond: (\"time\" > '-infinity'::timestamp without time zone)\n\nINSERT INTO one_space_test SELECT * FROM one_space_test WHERE time < 'infinity' LIMIT 1;\nINSERT INTO one_space_test SELECT * FROM one_space_test WHERE time >= 'infinity' LIMIT 1;\nINSERT INTO one_space_test SELECT * FROM one_space_test WHERE time <= '-infinity' LIMIT 1;\nINSERT INTO one_space_test SELECT * FROM one_space_test WHERE time > '-infinity' LIMIT 1;\nCREATE TABLE timestamp_inf(time TIMESTAMP);\nSELECT create_hypertable('timestamp_inf', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n     create_hypertable      \n----------------------------\n (7,public,timestamp_inf,t)\n\nINSERT INTO timestamp_inf VALUES ('2018/01/02'), ('2019/01/02');\nEXPLAIN (buffers off, costs off) INSERT INTO timestamp_inf SELECT * FROM timestamp_inf\n    WHERE time < 'infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on timestamp_inf\n         ->  Limit\n               ->  Append\n                     ->  Index Only Scan using _hyper_7_16_chunk_timestamp_inf_time_idx on _hyper_7_16_chunk\n                     ->  Index Only Scan using _hyper_7_17_chunk_timestamp_inf_time_idx on _hyper_7_17_chunk\n\nEXPLAIN (buffers off, costs off) INSERT INTO timestamp_inf SELECT * FROM timestamp_inf\n    WHERE time >= 'infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on timestamp_inf\n         ->  Limit\n               ->  Result\n                     One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO timestamp_inf SELECT * FROM timestamp_inf\n    WHERE time <= '-infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on timestamp_inf\n         ->  Limit\n               ->  Result\n                     One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO timestamp_inf SELECT * FROM timestamp_inf\n    WHERE time > '-infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on timestamp_inf\n         ->  Limit\n               ->  Append\n                     ->  Index Only Scan using _hyper_7_16_chunk_timestamp_inf_time_idx on _hyper_7_16_chunk\n                     ->  Index Only Scan using _hyper_7_17_chunk_timestamp_inf_time_idx on _hyper_7_17_chunk\n\nCREATE TABLE date_inf(time DATE);\nSELECT create_hypertable('date_inf', 'time');\n   create_hypertable   \n-----------------------\n (8,public,date_inf,t)\n\nINSERT INTO date_inf VALUES ('2018/01/02'), ('2019/01/02');\nEXPLAIN (buffers off, costs off) INSERT INTO date_inf SELECT * FROM date_inf\n    WHERE time < 'infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on date_inf\n         ->  Limit\n               ->  Append\n                     ->  Index Only Scan using _hyper_8_18_chunk_date_inf_time_idx on _hyper_8_18_chunk\n                     ->  Index Only Scan using _hyper_8_19_chunk_date_inf_time_idx on _hyper_8_19_chunk\n\nEXPLAIN (buffers off, costs off) INSERT INTO date_inf SELECT * FROM date_inf\n    WHERE time >= 'infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on date_inf\n         ->  Limit\n               ->  Result\n                     One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO date_inf SELECT * FROM date_inf\n    WHERE time <= '-infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on date_inf\n         ->  Limit\n               ->  Result\n                     One-Time Filter: false\n\nEXPLAIN (buffers off, costs off) INSERT INTO date_inf SELECT * FROM date_inf\n    WHERE time > '-infinity' LIMIT 1;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on date_inf\n         ->  Limit\n               ->  Append\n                     ->  Index Only Scan using _hyper_8_18_chunk_date_inf_time_idx on _hyper_8_18_chunk\n                     ->  Index Only Scan using _hyper_8_19_chunk_date_inf_time_idx on _hyper_8_19_chunk\n\n-- test INSERT with cached plans / plpgsql functions\n-- https://github.com/timescale/timescaledb/issues/1809\nCREATE TABLE status_table(a int, b int, last_ts timestamptz, UNIQUE(a,b));\nCREATE TABLE metrics(time timestamptz NOT NULL, value float);\nCREATE TABLE metrics2(time timestamptz NOT NULL, value float);\nSELECT (create_hypertable(t,'time')).table_name FROM (VALUES ('metrics'),('metrics2')) v(t);\n table_name \n------------\n metrics\n metrics2\n\nINSERT INTO metrics VALUES ('2000-01-01',random()), ('2000-02-01',random()), ('2000-03-01',random());\nCREATE OR REPLACE FUNCTION insert_test() RETURNS VOID LANGUAGE plpgsql AS\n$$\n  DECLARE\n    r RECORD;\n  BEGIN\n    FOR r IN\n      SELECT * FROM metrics\n    LOOP\n      WITH foo AS (\n        INSERT INTO metrics2 SELECT * FROM metrics RETURNING *\n      )\n      INSERT INTO status_table (a,b, last_ts)\n        VALUES (1,1, now())\n        ON CONFLICT (a,b) DO UPDATE SET last_ts=(SELECT max(time) FROM metrics);\n    END LOOP;\n  END;\n$$;\nSELECT insert_test(), insert_test(), insert_test();\n insert_test | insert_test | insert_test \n-------------+-------------+-------------\n             |             | \n\n-- test Postgres crashes on INSERT ... SELECT ... WHERE NOT EXISTS with empty table\n-- https://github.com/timescale/timescaledb/issues/1883\nCREATE TABLE readings (\n\ttoe TIMESTAMPTZ NOT NULL,\n\tsensor_id INT NOT NULL,\n\tvalue INT NOT NULL\n);\nSELECT create_hypertable(\n\t'readings',\n\t'toe',\n\tchunk_time_interval => interval '1 day',\n\tif_not_exists => TRUE,\n\tmigrate_data => TRUE\n);\n   create_hypertable    \n------------------------\n (11,public,readings,t)\n\nEXPLAIN (buffers off, costs off)\nINSERT INTO readings\nSELECT '2020-05-09 10:34:35.296288+00', 1, 0\nWHERE NOT EXISTS (\n\tSELECT 1\n\tFROM readings\n\tWHERE sensor_id = 1\n\t\tAND toe = '2020-05-09 10:34:35.296288+00'\n);\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   InitPlan 1\n     ->  Result\n           One-Time Filter: false\n   ->  Insert on readings\n         ->  Result\n               One-Time Filter: (NOT (InitPlan 1).col1)\n\nINSERT INTO readings\nSELECT '2020-05-09 10:34:35.296288+00', 1, 0\nWHERE NOT EXISTS (\n\tSELECT 1\n\tFROM readings\n\tWHERE sensor_id = 1\n\t\tAND toe = '2020-05-09 10:34:35.296288+00'\n);\nDROP TABLE readings;\nCREATE TABLE sample_table (\n       sequence INTEGER NOT NULL,\n       time TIMESTAMP WITHOUT TIME ZONE NOT NULL,\n       value NUMERIC NOT NULL,\n       UNIQUE (sequence, time)\n);\nSELECT * FROM create_hypertable('sample_table', 'time',\n       chunk_time_interval => INTERVAL '1 day');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n hypertable_id | schema_name |  table_name  | created \n---------------+-------------+--------------+---------\n            12 | public      | sample_table | t\n\nINSERT INTO sample_table (sequence,time,value) VALUES\n       (7, generate_series(TIMESTAMP '2019-08-01', TIMESTAMP '2019-08-10', INTERVAL '10 minutes'), ROUND(RANDOM()*10)::int);\n\\set ON_ERROR_STOP 0\nINSERT INTO sample_table (sequence,time,value) VALUES\n       (7, generate_series(TIMESTAMP '2019-07-21', TIMESTAMP '2019-08-01', INTERVAL '10 minutes'), ROUND(RANDOM()*10)::int);\nERROR:  duplicate key value violates unique constraint \"27_1_sample_table_sequence_time_key\"\n\\set ON_ERROR_STOP 1\nINSERT INTO sample_table (sequence,time,value) VALUES\n       (7,generate_series(TIMESTAMP '2019-01-01', TIMESTAMP '2019-07-01', '10 minutes'), ROUND(RANDOM()*10)::int);\nDROP TABLE sample_table;\n-- test on conflict clause on columns with default value\n-- issue #3037\nCREATE TABLE i3037(time timestamptz PRIMARY KEY);\nSELECT create_hypertable('i3037','time');\n  create_hypertable  \n---------------------\n (13,public,i3037,t)\n\nALTER TABLE i3037 ADD COLUMN value float DEFAULT 0;\nINSERT INTO i3037 VALUES ('2000-01-01');\nINSERT INTO i3037 VALUES ('2000-01-01') ON CONFLICT(time) DO UPDATE SET value = EXCLUDED.value;\n-- test inserting into chunks directly\nCREATE TABLE direct_insert(time timestamptz, meta text) WITH (tsdb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nINSERT INTO direct_insert VALUES ('2020-01-01');\nSELECT show_chunks('direct_insert') AS \"CHUNK\" \\gset\n--should have ModifyHyperable node\nEXPLAIN (costs off, timing off, summary off) INSERT INTO :CHUNK VALUES ('2020-01-01');\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on _hyper_14_231_chunk\n         ->  Result\n\n-- correct time range should succeed\nINSERT INTO :CHUNK VALUES ('2020-01-01') RETURNING *;\n             time             | meta \n------------------------------+------\n Wed Jan 01 00:00:00 2020 PST | \n\n-- incorrect time range should fail\n\\set ON_ERROR_STOP 0\nINSERT INTO :CHUNK VALUES ('2020-01-01'), ('2021-01-01') RETURNING *;\nERROR:  new row for relation \"_hyper_14_231_chunk\" violates chunk constraint\nINSERT INTO :CHUNK VALUES ('2025-01-01') RETURNING *;\nERROR:  new row for relation \"_hyper_14_231_chunk\" violates chunk constraint\n\\set ON_ERROR_STOP 1\n-- test that triggers on chunks work\nCREATE FUNCTION test_trigger() RETURNS TRIGGER AS $$\nBEGIN\n  RAISE NOTICE 'trigger called';\n  NEW.meta = 'triggered';\n  RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\nCREATE TRIGGER test_trigger BEFORE INSERT ON direct_insert FOR EACH ROW EXECUTE PROCEDURE test_trigger();\nINSERT INTO :CHUNK VALUES ('2020-01-01') RETURNING *;\nNOTICE:  trigger called\n             time             |   meta    \n------------------------------+-----------\n Wed Jan 01 00:00:00 2020 PST | triggered\n\n-- test upsert\nDELETE FROM direct_insert;\nALTER TABLE direct_insert ADD CONSTRAINT direct_insert_pkey PRIMARY KEY (time);\nINSERT INTO :CHUNK VALUES ('2020-01-01') RETURNING *;\nNOTICE:  trigger called\n             time             |   meta    \n------------------------------+-----------\n Wed Jan 01 00:00:00 2020 PST | triggered\n\n-- DO NOTHING should succeed\nINSERT INTO :CHUNK VALUES ('2020-01-01') ON CONFLICT DO NOTHING RETURNING *;\nNOTICE:  trigger called\n time | meta \n------+------\n\nINSERT INTO :CHUNK VALUES ('2020-01-01') ON CONFLICT (time) DO UPDATE SET meta = 'update' RETURNING *;\nNOTICE:  trigger called\n             time             |  meta  \n------------------------------+--------\n Wed Jan 01 00:00:00 2020 PST | update\n\n\\set ON_ERROR_STOP 0\n-- conflict should fail\nINSERT INTO :CHUNK VALUES ('2020-01-01') RETURNING *;\nNOTICE:  trigger called\nERROR:  duplicate key value violates unique constraint \"231_205_direct_insert_pkey\"\nINSERT INTO :CHUNK VALUES ('2020-01-01') ON CONFLICT (time) DO UPDATE SET time = '2000-01-01' RETURNING *;\nNOTICE:  trigger called\nERROR:  new row for relation \"_hyper_14_231_chunk\" violates check constraint \"constraint_237\"\nINSERT INTO :CHUNK VALUES ('2020-01-01') ON CONFLICT (time) DO UPDATE SET time = EXCLUDED.time + '1 year' RETURNING *;\nNOTICE:  trigger called\nERROR:  new row for relation \"_hyper_14_231_chunk\" violates check constraint \"constraint_237\"\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "test/expected/insert_many.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE  many_partitions_test(time timestamp, temp float8, device text NOT NULL);\nSELECT create_hypertable('many_partitions_test', 'time', 'device', 1000);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n         create_hypertable         \n-----------------------------------\n (1,public,many_partitions_test,t)\n\n--NOTE: how much slower the first two queries are -- they are creating chunks\nINSERT INTO many_partitions_test\n    SELECT to_timestamp(ser), ser, ser::text FROM generate_series(1,100) ser;\nINSERT INTO many_partitions_test\n    SELECT to_timestamp(ser), ser, ser::text FROM generate_series(101,200) ser;\nINSERT INTO many_partitions_test\n    SELECT to_timestamp(ser), ser, (ser-201)::text FROM generate_series(201,300) ser;\nSELECT * FROM  many_partitions_test ORDER BY time DESC LIMIT 2;\n           time           | temp | device \n--------------------------+------+--------\n Wed Dec 31 16:05:00 1969 |  300 | 99\n Wed Dec 31 16:04:59 1969 |  299 | 98\n\nSELECT count(*) FROM  many_partitions_test;\n count \n-------\n   300\n\nCREATE TABLE many_partitions_test_1m (time timestamp, temp float8, device text NOT NULL);\nSELECT create_hypertable('many_partitions_test_1m', 'time', 'device', 1000);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n          create_hypertable           \n--------------------------------------\n (2,public,many_partitions_test_1m,t)\n\nEXPLAIN (verbose on, buffers off, costs off)\nINSERT INTO many_partitions_test_1m(time, temp, device)\nSELECT time_bucket('1 minute', time) AS period, avg(temp), device\nFROM many_partitions_test\nGROUP BY period, device;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on public.many_partitions_test_1m\n         ->  HashAggregate\n               Output: (time_bucket('@ 1 min'::interval, many_partitions_test.\"time\")), avg(many_partitions_test.temp), many_partitions_test.device\n               Group Key: time_bucket('@ 1 min'::interval, many_partitions_test.\"time\"), many_partitions_test.device\n               ->  Result\n                     Output: time_bucket('@ 1 min'::interval, many_partitions_test.\"time\"), many_partitions_test.device, many_partitions_test.temp\n                     ->  Append\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                                 Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.temp, _hyper_1_1_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                                 Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.temp, _hyper_1_2_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                                 Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.temp, _hyper_1_3_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                                 Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.temp, _hyper_1_4_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_5_chunk\n                                 Output: _hyper_1_5_chunk.\"time\", _hyper_1_5_chunk.temp, _hyper_1_5_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_6_chunk\n                                 Output: _hyper_1_6_chunk.\"time\", _hyper_1_6_chunk.temp, _hyper_1_6_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_7_chunk\n                                 Output: _hyper_1_7_chunk.\"time\", _hyper_1_7_chunk.temp, _hyper_1_7_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_8_chunk\n                                 Output: _hyper_1_8_chunk.\"time\", _hyper_1_8_chunk.temp, _hyper_1_8_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_9_chunk\n                                 Output: _hyper_1_9_chunk.\"time\", _hyper_1_9_chunk.temp, _hyper_1_9_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_10_chunk\n                                 Output: _hyper_1_10_chunk.\"time\", _hyper_1_10_chunk.temp, _hyper_1_10_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_11_chunk\n                                 Output: _hyper_1_11_chunk.\"time\", _hyper_1_11_chunk.temp, _hyper_1_11_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_12_chunk\n                                 Output: _hyper_1_12_chunk.\"time\", _hyper_1_12_chunk.temp, _hyper_1_12_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_13_chunk\n                                 Output: _hyper_1_13_chunk.\"time\", _hyper_1_13_chunk.temp, _hyper_1_13_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_14_chunk\n                                 Output: _hyper_1_14_chunk.\"time\", _hyper_1_14_chunk.temp, _hyper_1_14_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_15_chunk\n                                 Output: _hyper_1_15_chunk.\"time\", _hyper_1_15_chunk.temp, _hyper_1_15_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_16_chunk\n                                 Output: _hyper_1_16_chunk.\"time\", _hyper_1_16_chunk.temp, _hyper_1_16_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_17_chunk\n                                 Output: _hyper_1_17_chunk.\"time\", _hyper_1_17_chunk.temp, _hyper_1_17_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_18_chunk\n                                 Output: _hyper_1_18_chunk.\"time\", _hyper_1_18_chunk.temp, _hyper_1_18_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_19_chunk\n                                 Output: _hyper_1_19_chunk.\"time\", _hyper_1_19_chunk.temp, _hyper_1_19_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_20_chunk\n                                 Output: _hyper_1_20_chunk.\"time\", _hyper_1_20_chunk.temp, _hyper_1_20_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_21_chunk\n                                 Output: _hyper_1_21_chunk.\"time\", _hyper_1_21_chunk.temp, _hyper_1_21_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_22_chunk\n                                 Output: _hyper_1_22_chunk.\"time\", _hyper_1_22_chunk.temp, _hyper_1_22_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_23_chunk\n                                 Output: _hyper_1_23_chunk.\"time\", _hyper_1_23_chunk.temp, _hyper_1_23_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_24_chunk\n                                 Output: _hyper_1_24_chunk.\"time\", _hyper_1_24_chunk.temp, _hyper_1_24_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_25_chunk\n                                 Output: _hyper_1_25_chunk.\"time\", _hyper_1_25_chunk.temp, _hyper_1_25_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_26_chunk\n                                 Output: _hyper_1_26_chunk.\"time\", _hyper_1_26_chunk.temp, _hyper_1_26_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_27_chunk\n                                 Output: _hyper_1_27_chunk.\"time\", _hyper_1_27_chunk.temp, _hyper_1_27_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_28_chunk\n                                 Output: _hyper_1_28_chunk.\"time\", _hyper_1_28_chunk.temp, _hyper_1_28_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_29_chunk\n                                 Output: _hyper_1_29_chunk.\"time\", _hyper_1_29_chunk.temp, _hyper_1_29_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_30_chunk\n                                 Output: _hyper_1_30_chunk.\"time\", _hyper_1_30_chunk.temp, _hyper_1_30_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_31_chunk\n                                 Output: _hyper_1_31_chunk.\"time\", _hyper_1_31_chunk.temp, _hyper_1_31_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_32_chunk\n                                 Output: _hyper_1_32_chunk.\"time\", _hyper_1_32_chunk.temp, _hyper_1_32_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_33_chunk\n                                 Output: _hyper_1_33_chunk.\"time\", _hyper_1_33_chunk.temp, _hyper_1_33_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_34_chunk\n                                 Output: _hyper_1_34_chunk.\"time\", _hyper_1_34_chunk.temp, _hyper_1_34_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_35_chunk\n                                 Output: _hyper_1_35_chunk.\"time\", _hyper_1_35_chunk.temp, _hyper_1_35_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_36_chunk\n                                 Output: _hyper_1_36_chunk.\"time\", _hyper_1_36_chunk.temp, _hyper_1_36_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_37_chunk\n                                 Output: _hyper_1_37_chunk.\"time\", _hyper_1_37_chunk.temp, _hyper_1_37_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_38_chunk\n                                 Output: _hyper_1_38_chunk.\"time\", _hyper_1_38_chunk.temp, _hyper_1_38_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_39_chunk\n                                 Output: _hyper_1_39_chunk.\"time\", _hyper_1_39_chunk.temp, _hyper_1_39_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_40_chunk\n                                 Output: _hyper_1_40_chunk.\"time\", _hyper_1_40_chunk.temp, _hyper_1_40_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_41_chunk\n                                 Output: _hyper_1_41_chunk.\"time\", _hyper_1_41_chunk.temp, _hyper_1_41_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_42_chunk\n                                 Output: _hyper_1_42_chunk.\"time\", _hyper_1_42_chunk.temp, _hyper_1_42_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_43_chunk\n                                 Output: _hyper_1_43_chunk.\"time\", _hyper_1_43_chunk.temp, _hyper_1_43_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_44_chunk\n                                 Output: _hyper_1_44_chunk.\"time\", _hyper_1_44_chunk.temp, _hyper_1_44_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_45_chunk\n                                 Output: _hyper_1_45_chunk.\"time\", _hyper_1_45_chunk.temp, _hyper_1_45_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_46_chunk\n                                 Output: _hyper_1_46_chunk.\"time\", _hyper_1_46_chunk.temp, _hyper_1_46_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_47_chunk\n                                 Output: _hyper_1_47_chunk.\"time\", _hyper_1_47_chunk.temp, _hyper_1_47_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_48_chunk\n                                 Output: _hyper_1_48_chunk.\"time\", _hyper_1_48_chunk.temp, _hyper_1_48_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_49_chunk\n                                 Output: _hyper_1_49_chunk.\"time\", _hyper_1_49_chunk.temp, _hyper_1_49_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_50_chunk\n                                 Output: _hyper_1_50_chunk.\"time\", _hyper_1_50_chunk.temp, _hyper_1_50_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_51_chunk\n                                 Output: _hyper_1_51_chunk.\"time\", _hyper_1_51_chunk.temp, _hyper_1_51_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_52_chunk\n                                 Output: _hyper_1_52_chunk.\"time\", _hyper_1_52_chunk.temp, _hyper_1_52_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_53_chunk\n                                 Output: _hyper_1_53_chunk.\"time\", _hyper_1_53_chunk.temp, _hyper_1_53_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_54_chunk\n                                 Output: _hyper_1_54_chunk.\"time\", _hyper_1_54_chunk.temp, _hyper_1_54_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_55_chunk\n                                 Output: _hyper_1_55_chunk.\"time\", _hyper_1_55_chunk.temp, _hyper_1_55_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_56_chunk\n                                 Output: _hyper_1_56_chunk.\"time\", _hyper_1_56_chunk.temp, _hyper_1_56_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_57_chunk\n                                 Output: _hyper_1_57_chunk.\"time\", _hyper_1_57_chunk.temp, _hyper_1_57_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_58_chunk\n                                 Output: _hyper_1_58_chunk.\"time\", _hyper_1_58_chunk.temp, _hyper_1_58_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_59_chunk\n                                 Output: _hyper_1_59_chunk.\"time\", _hyper_1_59_chunk.temp, _hyper_1_59_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_60_chunk\n                                 Output: _hyper_1_60_chunk.\"time\", _hyper_1_60_chunk.temp, _hyper_1_60_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_61_chunk\n                                 Output: _hyper_1_61_chunk.\"time\", _hyper_1_61_chunk.temp, _hyper_1_61_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_62_chunk\n                                 Output: _hyper_1_62_chunk.\"time\", _hyper_1_62_chunk.temp, _hyper_1_62_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_63_chunk\n                                 Output: _hyper_1_63_chunk.\"time\", _hyper_1_63_chunk.temp, _hyper_1_63_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_64_chunk\n                                 Output: _hyper_1_64_chunk.\"time\", _hyper_1_64_chunk.temp, _hyper_1_64_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_65_chunk\n                                 Output: _hyper_1_65_chunk.\"time\", _hyper_1_65_chunk.temp, _hyper_1_65_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_66_chunk\n                                 Output: _hyper_1_66_chunk.\"time\", _hyper_1_66_chunk.temp, _hyper_1_66_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_67_chunk\n                                 Output: _hyper_1_67_chunk.\"time\", _hyper_1_67_chunk.temp, _hyper_1_67_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_68_chunk\n                                 Output: _hyper_1_68_chunk.\"time\", _hyper_1_68_chunk.temp, _hyper_1_68_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_69_chunk\n                                 Output: _hyper_1_69_chunk.\"time\", _hyper_1_69_chunk.temp, _hyper_1_69_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_70_chunk\n                                 Output: _hyper_1_70_chunk.\"time\", _hyper_1_70_chunk.temp, _hyper_1_70_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_71_chunk\n                                 Output: _hyper_1_71_chunk.\"time\", _hyper_1_71_chunk.temp, _hyper_1_71_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_72_chunk\n                                 Output: _hyper_1_72_chunk.\"time\", _hyper_1_72_chunk.temp, _hyper_1_72_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_73_chunk\n                                 Output: _hyper_1_73_chunk.\"time\", _hyper_1_73_chunk.temp, _hyper_1_73_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_74_chunk\n                                 Output: _hyper_1_74_chunk.\"time\", _hyper_1_74_chunk.temp, _hyper_1_74_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_75_chunk\n                                 Output: _hyper_1_75_chunk.\"time\", _hyper_1_75_chunk.temp, _hyper_1_75_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_76_chunk\n                                 Output: _hyper_1_76_chunk.\"time\", _hyper_1_76_chunk.temp, _hyper_1_76_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_77_chunk\n                                 Output: _hyper_1_77_chunk.\"time\", _hyper_1_77_chunk.temp, _hyper_1_77_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_78_chunk\n                                 Output: _hyper_1_78_chunk.\"time\", _hyper_1_78_chunk.temp, _hyper_1_78_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_79_chunk\n                                 Output: _hyper_1_79_chunk.\"time\", _hyper_1_79_chunk.temp, _hyper_1_79_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_80_chunk\n                                 Output: _hyper_1_80_chunk.\"time\", _hyper_1_80_chunk.temp, _hyper_1_80_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_81_chunk\n                                 Output: _hyper_1_81_chunk.\"time\", _hyper_1_81_chunk.temp, _hyper_1_81_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_82_chunk\n                                 Output: _hyper_1_82_chunk.\"time\", _hyper_1_82_chunk.temp, _hyper_1_82_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_83_chunk\n                                 Output: _hyper_1_83_chunk.\"time\", _hyper_1_83_chunk.temp, _hyper_1_83_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_84_chunk\n                                 Output: _hyper_1_84_chunk.\"time\", _hyper_1_84_chunk.temp, _hyper_1_84_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_85_chunk\n                                 Output: _hyper_1_85_chunk.\"time\", _hyper_1_85_chunk.temp, _hyper_1_85_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_86_chunk\n                                 Output: _hyper_1_86_chunk.\"time\", _hyper_1_86_chunk.temp, _hyper_1_86_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_87_chunk\n                                 Output: _hyper_1_87_chunk.\"time\", _hyper_1_87_chunk.temp, _hyper_1_87_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_88_chunk\n                                 Output: _hyper_1_88_chunk.\"time\", _hyper_1_88_chunk.temp, _hyper_1_88_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_89_chunk\n                                 Output: _hyper_1_89_chunk.\"time\", _hyper_1_89_chunk.temp, _hyper_1_89_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_90_chunk\n                                 Output: _hyper_1_90_chunk.\"time\", _hyper_1_90_chunk.temp, _hyper_1_90_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_91_chunk\n                                 Output: _hyper_1_91_chunk.\"time\", _hyper_1_91_chunk.temp, _hyper_1_91_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_92_chunk\n                                 Output: _hyper_1_92_chunk.\"time\", _hyper_1_92_chunk.temp, _hyper_1_92_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_93_chunk\n                                 Output: _hyper_1_93_chunk.\"time\", _hyper_1_93_chunk.temp, _hyper_1_93_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_94_chunk\n                                 Output: _hyper_1_94_chunk.\"time\", _hyper_1_94_chunk.temp, _hyper_1_94_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_95_chunk\n                                 Output: _hyper_1_95_chunk.\"time\", _hyper_1_95_chunk.temp, _hyper_1_95_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_96_chunk\n                                 Output: _hyper_1_96_chunk.\"time\", _hyper_1_96_chunk.temp, _hyper_1_96_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_97_chunk\n                                 Output: _hyper_1_97_chunk.\"time\", _hyper_1_97_chunk.temp, _hyper_1_97_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_98_chunk\n                                 Output: _hyper_1_98_chunk.\"time\", _hyper_1_98_chunk.temp, _hyper_1_98_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_99_chunk\n                                 Output: _hyper_1_99_chunk.\"time\", _hyper_1_99_chunk.temp, _hyper_1_99_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_100_chunk\n                                 Output: _hyper_1_100_chunk.\"time\", _hyper_1_100_chunk.temp, _hyper_1_100_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_101_chunk\n                                 Output: _hyper_1_101_chunk.\"time\", _hyper_1_101_chunk.temp, _hyper_1_101_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_102_chunk\n                                 Output: _hyper_1_102_chunk.\"time\", _hyper_1_102_chunk.temp, _hyper_1_102_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_103_chunk\n                                 Output: _hyper_1_103_chunk.\"time\", _hyper_1_103_chunk.temp, _hyper_1_103_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_104_chunk\n                                 Output: _hyper_1_104_chunk.\"time\", _hyper_1_104_chunk.temp, _hyper_1_104_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_105_chunk\n                                 Output: _hyper_1_105_chunk.\"time\", _hyper_1_105_chunk.temp, _hyper_1_105_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_106_chunk\n                                 Output: _hyper_1_106_chunk.\"time\", _hyper_1_106_chunk.temp, _hyper_1_106_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_107_chunk\n                                 Output: _hyper_1_107_chunk.\"time\", _hyper_1_107_chunk.temp, _hyper_1_107_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_108_chunk\n                                 Output: _hyper_1_108_chunk.\"time\", _hyper_1_108_chunk.temp, _hyper_1_108_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_109_chunk\n                                 Output: _hyper_1_109_chunk.\"time\", _hyper_1_109_chunk.temp, _hyper_1_109_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_110_chunk\n                                 Output: _hyper_1_110_chunk.\"time\", _hyper_1_110_chunk.temp, _hyper_1_110_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_111_chunk\n                                 Output: _hyper_1_111_chunk.\"time\", _hyper_1_111_chunk.temp, _hyper_1_111_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_112_chunk\n                                 Output: _hyper_1_112_chunk.\"time\", _hyper_1_112_chunk.temp, _hyper_1_112_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_113_chunk\n                                 Output: _hyper_1_113_chunk.\"time\", _hyper_1_113_chunk.temp, _hyper_1_113_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_114_chunk\n                                 Output: _hyper_1_114_chunk.\"time\", _hyper_1_114_chunk.temp, _hyper_1_114_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_115_chunk\n                                 Output: _hyper_1_115_chunk.\"time\", _hyper_1_115_chunk.temp, _hyper_1_115_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_116_chunk\n                                 Output: _hyper_1_116_chunk.\"time\", _hyper_1_116_chunk.temp, _hyper_1_116_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_117_chunk\n                                 Output: _hyper_1_117_chunk.\"time\", _hyper_1_117_chunk.temp, _hyper_1_117_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_118_chunk\n                                 Output: _hyper_1_118_chunk.\"time\", _hyper_1_118_chunk.temp, _hyper_1_118_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_119_chunk\n                                 Output: _hyper_1_119_chunk.\"time\", _hyper_1_119_chunk.temp, _hyper_1_119_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_120_chunk\n                                 Output: _hyper_1_120_chunk.\"time\", _hyper_1_120_chunk.temp, _hyper_1_120_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_121_chunk\n                                 Output: _hyper_1_121_chunk.\"time\", _hyper_1_121_chunk.temp, _hyper_1_121_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_122_chunk\n                                 Output: _hyper_1_122_chunk.\"time\", _hyper_1_122_chunk.temp, _hyper_1_122_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_123_chunk\n                                 Output: _hyper_1_123_chunk.\"time\", _hyper_1_123_chunk.temp, _hyper_1_123_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_124_chunk\n                                 Output: _hyper_1_124_chunk.\"time\", _hyper_1_124_chunk.temp, _hyper_1_124_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_125_chunk\n                                 Output: _hyper_1_125_chunk.\"time\", _hyper_1_125_chunk.temp, _hyper_1_125_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_126_chunk\n                                 Output: _hyper_1_126_chunk.\"time\", _hyper_1_126_chunk.temp, _hyper_1_126_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_127_chunk\n                                 Output: _hyper_1_127_chunk.\"time\", _hyper_1_127_chunk.temp, _hyper_1_127_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_128_chunk\n                                 Output: _hyper_1_128_chunk.\"time\", _hyper_1_128_chunk.temp, _hyper_1_128_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_129_chunk\n                                 Output: _hyper_1_129_chunk.\"time\", _hyper_1_129_chunk.temp, _hyper_1_129_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_130_chunk\n                                 Output: _hyper_1_130_chunk.\"time\", _hyper_1_130_chunk.temp, _hyper_1_130_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_131_chunk\n                                 Output: _hyper_1_131_chunk.\"time\", _hyper_1_131_chunk.temp, _hyper_1_131_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_132_chunk\n                                 Output: _hyper_1_132_chunk.\"time\", _hyper_1_132_chunk.temp, _hyper_1_132_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_133_chunk\n                                 Output: _hyper_1_133_chunk.\"time\", _hyper_1_133_chunk.temp, _hyper_1_133_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_134_chunk\n                                 Output: _hyper_1_134_chunk.\"time\", _hyper_1_134_chunk.temp, _hyper_1_134_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_135_chunk\n                                 Output: _hyper_1_135_chunk.\"time\", _hyper_1_135_chunk.temp, _hyper_1_135_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_136_chunk\n                                 Output: _hyper_1_136_chunk.\"time\", _hyper_1_136_chunk.temp, _hyper_1_136_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_137_chunk\n                                 Output: _hyper_1_137_chunk.\"time\", _hyper_1_137_chunk.temp, _hyper_1_137_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_138_chunk\n                                 Output: _hyper_1_138_chunk.\"time\", _hyper_1_138_chunk.temp, _hyper_1_138_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_139_chunk\n                                 Output: _hyper_1_139_chunk.\"time\", _hyper_1_139_chunk.temp, _hyper_1_139_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_140_chunk\n                                 Output: _hyper_1_140_chunk.\"time\", _hyper_1_140_chunk.temp, _hyper_1_140_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_141_chunk\n                                 Output: _hyper_1_141_chunk.\"time\", _hyper_1_141_chunk.temp, _hyper_1_141_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_142_chunk\n                                 Output: _hyper_1_142_chunk.\"time\", _hyper_1_142_chunk.temp, _hyper_1_142_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_143_chunk\n                                 Output: _hyper_1_143_chunk.\"time\", _hyper_1_143_chunk.temp, _hyper_1_143_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_144_chunk\n                                 Output: _hyper_1_144_chunk.\"time\", _hyper_1_144_chunk.temp, _hyper_1_144_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_145_chunk\n                                 Output: _hyper_1_145_chunk.\"time\", _hyper_1_145_chunk.temp, _hyper_1_145_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_146_chunk\n                                 Output: _hyper_1_146_chunk.\"time\", _hyper_1_146_chunk.temp, _hyper_1_146_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_147_chunk\n                                 Output: _hyper_1_147_chunk.\"time\", _hyper_1_147_chunk.temp, _hyper_1_147_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_148_chunk\n                                 Output: _hyper_1_148_chunk.\"time\", _hyper_1_148_chunk.temp, _hyper_1_148_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_149_chunk\n                                 Output: _hyper_1_149_chunk.\"time\", _hyper_1_149_chunk.temp, _hyper_1_149_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_150_chunk\n                                 Output: _hyper_1_150_chunk.\"time\", _hyper_1_150_chunk.temp, _hyper_1_150_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_151_chunk\n                                 Output: _hyper_1_151_chunk.\"time\", _hyper_1_151_chunk.temp, _hyper_1_151_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_152_chunk\n                                 Output: _hyper_1_152_chunk.\"time\", _hyper_1_152_chunk.temp, _hyper_1_152_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_153_chunk\n                                 Output: _hyper_1_153_chunk.\"time\", _hyper_1_153_chunk.temp, _hyper_1_153_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_154_chunk\n                                 Output: _hyper_1_154_chunk.\"time\", _hyper_1_154_chunk.temp, _hyper_1_154_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_155_chunk\n                                 Output: _hyper_1_155_chunk.\"time\", _hyper_1_155_chunk.temp, _hyper_1_155_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_156_chunk\n                                 Output: _hyper_1_156_chunk.\"time\", _hyper_1_156_chunk.temp, _hyper_1_156_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_157_chunk\n                                 Output: _hyper_1_157_chunk.\"time\", _hyper_1_157_chunk.temp, _hyper_1_157_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_158_chunk\n                                 Output: _hyper_1_158_chunk.\"time\", _hyper_1_158_chunk.temp, _hyper_1_158_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_159_chunk\n                                 Output: _hyper_1_159_chunk.\"time\", _hyper_1_159_chunk.temp, _hyper_1_159_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_160_chunk\n                                 Output: _hyper_1_160_chunk.\"time\", _hyper_1_160_chunk.temp, _hyper_1_160_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_161_chunk\n                                 Output: _hyper_1_161_chunk.\"time\", _hyper_1_161_chunk.temp, _hyper_1_161_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_162_chunk\n                                 Output: _hyper_1_162_chunk.\"time\", _hyper_1_162_chunk.temp, _hyper_1_162_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_163_chunk\n                                 Output: _hyper_1_163_chunk.\"time\", _hyper_1_163_chunk.temp, _hyper_1_163_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_164_chunk\n                                 Output: _hyper_1_164_chunk.\"time\", _hyper_1_164_chunk.temp, _hyper_1_164_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_165_chunk\n                                 Output: _hyper_1_165_chunk.\"time\", _hyper_1_165_chunk.temp, _hyper_1_165_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_166_chunk\n                                 Output: _hyper_1_166_chunk.\"time\", _hyper_1_166_chunk.temp, _hyper_1_166_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_167_chunk\n                                 Output: _hyper_1_167_chunk.\"time\", _hyper_1_167_chunk.temp, _hyper_1_167_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_168_chunk\n                                 Output: _hyper_1_168_chunk.\"time\", _hyper_1_168_chunk.temp, _hyper_1_168_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_169_chunk\n                                 Output: _hyper_1_169_chunk.\"time\", _hyper_1_169_chunk.temp, _hyper_1_169_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_170_chunk\n                                 Output: _hyper_1_170_chunk.\"time\", _hyper_1_170_chunk.temp, _hyper_1_170_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_171_chunk\n                                 Output: _hyper_1_171_chunk.\"time\", _hyper_1_171_chunk.temp, _hyper_1_171_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_172_chunk\n                                 Output: _hyper_1_172_chunk.\"time\", _hyper_1_172_chunk.temp, _hyper_1_172_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_173_chunk\n                                 Output: _hyper_1_173_chunk.\"time\", _hyper_1_173_chunk.temp, _hyper_1_173_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_174_chunk\n                                 Output: _hyper_1_174_chunk.\"time\", _hyper_1_174_chunk.temp, _hyper_1_174_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_175_chunk\n                                 Output: _hyper_1_175_chunk.\"time\", _hyper_1_175_chunk.temp, _hyper_1_175_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_176_chunk\n                                 Output: _hyper_1_176_chunk.\"time\", _hyper_1_176_chunk.temp, _hyper_1_176_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_177_chunk\n                                 Output: _hyper_1_177_chunk.\"time\", _hyper_1_177_chunk.temp, _hyper_1_177_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_178_chunk\n                                 Output: _hyper_1_178_chunk.\"time\", _hyper_1_178_chunk.temp, _hyper_1_178_chunk.device\n\nINSERT INTO many_partitions_test_1m(time, temp, device)\nSELECT time_bucket('1 minute', time) AS period, avg(temp), device\nFROM many_partitions_test\nGROUP BY period, device;\nSELECT * FROM many_partitions_test_1m ORDER BY time, device LIMIT 10;\n           time           | temp | device \n--------------------------+------+--------\n Wed Dec 31 16:00:00 1969 |    1 | 1\n Wed Dec 31 16:00:00 1969 |   10 | 10\n Wed Dec 31 16:00:00 1969 |   11 | 11\n Wed Dec 31 16:00:00 1969 |   12 | 12\n Wed Dec 31 16:00:00 1969 |   13 | 13\n Wed Dec 31 16:00:00 1969 |   14 | 14\n Wed Dec 31 16:00:00 1969 |   15 | 15\n Wed Dec 31 16:00:00 1969 |   16 | 16\n Wed Dec 31 16:00:00 1969 |   17 | 17\n Wed Dec 31 16:00:00 1969 |   18 | 18\n\n"
  },
  {
    "path": "test/expected/insert_returning.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE standard_table (\n    standard_id integer PRIMARY KEY,\n    name text not null\n);\nCREATE TABLE hypertable (\n    time timestamptz not null,\n    name text not null,\n    standard_id integer not null\n);\nselect * from create_hypertable('hypertable', 'time');\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             1 | public      | hypertable | t\n\nINSERT INTO standard_table (standard_id, name)\nVALUES (1, 'standard_1');\nINSERT INTO hypertable (time, name, standard_id)\nVALUES ('2021-01-01 01:01:01+00', 'hypertable_1', 1);\nINSERT INTO hypertable (time, name, standard_id)\nVALUES ('2022-02-02 02:02:02+00', 'hypertable_2', 1)\nRETURNING *, EXISTS (\n  SELECT *\n  FROM standard_table\n  WHERE standard_table.standard_id = hypertable.standard_id\n);\n             time             |     name     | standard_id | exists \n------------------------------+--------------+-------------+--------\n Tue Feb 01 18:02:02 2022 PST | hypertable_2 |           1 | t\n\nINSERT INTO hypertable (time, name, standard_id)\nVALUES ('2023-03-03 03:03:03+00', 'hypertable_3', 1)\nRETURNING *, EXISTS (\n  SELECT *\n  FROM standard_table\n  WHERE standard_table.standard_id = hypertable.standard_id\n);\n             time             |     name     | standard_id | exists \n------------------------------+--------------+-------------+--------\n Thu Mar 02 19:03:03 2023 PST | hypertable_3 |           1 | t\n\nINSERT INTO hypertable (time, name, standard_id)\nVALUES ('2024-04-04 04:04:04+00', 'hypertable_4', 2)\nRETURNING *, EXISTS (\n  SELECT *\n  FROM standard_table\n  WHERE standard_table.standard_id = hypertable.standard_id\n);\n             time             |     name     | standard_id | exists \n------------------------------+--------------+-------------+--------\n Wed Apr 03 21:04:04 2024 PDT | hypertable_4 |           2 | f\n\n"
  },
  {
    "path": "test/expected/insert_returning_old_new.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- Test OLD/NEW references in RETURNING clause (PG18+ feature)\nCREATE TABLE ht_returning(\n    time timestamptz NOT NULL,\n    value int NOT NULL,\n    UNIQUE(time)\n) WITH (tsdb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\n-- Insert initial rows\nINSERT INTO ht_returning(time, value) VALUES ('2024-01-01', 10);\nINSERT INTO ht_returning(time, value) VALUES ('2024-01-02', 20);\n-- Test 1: INSERT ON CONFLICT DO UPDATE with RETURNING OLD.col, NEW.col\n-- This should show the old value (10) and the new value (100)\nINSERT INTO ht_returning(time, value) VALUES ('2024-01-01', 100)\nON CONFLICT (time) DO UPDATE SET value = EXCLUDED.value\nRETURNING OLD.value AS old_val, NEW.value AS new_val;\n old_val | new_val \n---------+---------\n      10 |     100\n\n-- Test 2: INSERT ON CONFLICT DO UPDATE with RETURNING arithmetic on OLD/NEW\nINSERT INTO ht_returning(time, value) VALUES ('2024-01-02', 50)\nON CONFLICT (time) DO UPDATE SET value = EXCLUDED.value\nRETURNING OLD.value AS old_val, NEW.value AS new_val, NEW.value - OLD.value AS diff;\n old_val | new_val | diff \n---------+---------+------\n      20 |      50 |   30\n\n-- Test 3: Plain INSERT with RETURNING NEW.col (OLD should be NULL for fresh inserts)\nINSERT INTO ht_returning(time, value) VALUES ('2024-01-03', 30)\nRETURNING OLD.value AS old_val, NEW.value AS new_val;\n old_val | new_val \n---------+---------\n         |      30\n\n-- Test 4: MERGE with both UPDATE and INSERT paths, returning OLD/NEW values and merge_action()\nCREATE TABLE t1(time timestamptz NOT NULL, value int NOT NULL);\nINSERT INTO t1(time, value) VALUES ('2024-01-01', 5);   -- Will trigger UPDATE (existing row)\nINSERT INTO t1(time, value) VALUES ('2024-01-05', 10);   -- Will trigger INSERT (new row)\nMERGE INTO ht_returning AS t\nUSING t1 AS s\nON t.time = s.time\nWHEN MATCHED THEN\n    UPDATE SET\n        value = t.value + s.value\nWHEN NOT MATCHED THEN\n    INSERT (time, value)\n    VALUES (s.time, s.value)\nRETURNING NEW.time, NEW.value, t.value, OLD.value, s.value, merge_action();\n             time             | value | value | value | value | merge_action \n------------------------------+-------+-------+-------+-------+--------------\n Mon Jan 01 00:00:00 2024 PST |   105 |   105 |   100 |     5 | UPDATE\n Fri Jan 05 00:00:00 2024 PST |    10 |    10 |       |    10 | INSERT\n\n-- Verify final state\nSELECT * FROM ht_returning ORDER BY time;\n             time             | value \n------------------------------+-------\n Mon Jan 01 00:00:00 2024 PST |   105\n Tue Jan 02 00:00:00 2024 PST |    50\n Wed Jan 03 00:00:00 2024 PST |    30\n Fri Jan 05 00:00:00 2024 PST |    10\n\n-- Cleanup\nDROP TABLE ht_returning;\n"
  },
  {
    "path": "test/expected/insert_single.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\ir include/insert_single.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE PUBLIC.\"one_Partition\" (\n  \"timeCustom\" BIGINT NOT NULL,\n  device_id TEXT NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL,\n  series_bool BOOLEAN NULL\n);\nCREATE INDEX ON PUBLIC.\"one_Partition\" (device_id, \"timeCustom\" DESC NULLS LAST) WHERE device_id IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"one_Partition\" (\"timeCustom\" DESC NULLS LAST, series_0) WHERE series_0 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"one_Partition\" (\"timeCustom\" DESC NULLS LAST, series_1)  WHERE series_1 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"one_Partition\" (\"timeCustom\" DESC NULLS LAST, series_2) WHERE series_2 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"one_Partition\" (\"timeCustom\" DESC NULLS LAST, series_bool) WHERE series_bool IS NOT NULL;\n\\c :DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA \"one_Partition\" AUTHORIZATION :ROLE_DEFAULT_PERM_USER;\n\\c :DBNAME :ROLE_DEFAULT_PERM_USER;\nSELECT * FROM create_hypertable('\"public\".\"one_Partition\"', 'timeCustom', associated_schema_name=>'one_Partition', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name |  table_name   | created \n---------------+-------------+---------------+---------\n             1 | public      | one_Partition | t\n\n--output command tags\n\\set QUIET off\nBEGIN;\nBEGIN\n\\COPY \"one_Partition\" FROM 'data/ds1_dev1_1.tsv' NULL AS '';\nCOPY 7\nCOMMIT;\nCOMMIT\nINSERT INTO \"one_Partition\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257987600000000000, 'dev1', 1.5, 1),\n(1257987600000000000, 'dev1', 1.5, 2),\n(1257894000000000000, 'dev2', 1.5, 1),\n(1257894002000000000, 'dev1', 2.5, 3);\nINSERT 0 4\nINSERT INTO \"one_Partition\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257894000000000000, 'dev2', 1.5, 2);\nINSERT 0 1\n\\set QUIET on\nSELECT * FROM test.show_columnsp('\"one_Partition\".%');\n                                  Relation                                   | Kind |   Column    |   Column type    | NotNull \n-----------------------------------------------------------------------------+------+-------------+------------------+---------\n \"one_Partition\"._hyper_1_1_chunk                                            | r    | timeCustom  | bigint           | t\n \"one_Partition\"._hyper_1_1_chunk                                            | r    | device_id   | text             | t\n \"one_Partition\"._hyper_1_1_chunk                                            | r    | series_0    | double precision | f\n \"one_Partition\"._hyper_1_1_chunk                                            | r    | series_1    | double precision | f\n \"one_Partition\"._hyper_1_1_chunk                                            | r    | series_2    | double precision | f\n \"one_Partition\"._hyper_1_1_chunk                                            | r    | series_bool | boolean          | f\n \"one_Partition\".\"_hyper_1_1_chunk_one_Partition_device_id_timeCustom_idx\"   | i    | device_id   | text             | f\n \"one_Partition\".\"_hyper_1_1_chunk_one_Partition_device_id_timeCustom_idx\"   | i    | timeCustom  | bigint           | f\n \"one_Partition\".\"_hyper_1_1_chunk_one_Partition_timeCustom_idx\"             | i    | timeCustom  | bigint           | f\n \"one_Partition\".\"_hyper_1_1_chunk_one_Partition_timeCustom_series_0_idx\"    | i    | timeCustom  | bigint           | f\n \"one_Partition\".\"_hyper_1_1_chunk_one_Partition_timeCustom_series_0_idx\"    | i    | series_0    | double precision | f\n \"one_Partition\".\"_hyper_1_1_chunk_one_Partition_timeCustom_series_1_idx\"    | i    | timeCustom  | bigint           | f\n \"one_Partition\".\"_hyper_1_1_chunk_one_Partition_timeCustom_series_1_idx\"    | i    | series_1    | double precision | f\n \"one_Partition\".\"_hyper_1_1_chunk_one_Partition_timeCustom_series_2_idx\"    | i    | timeCustom  | bigint           | f\n \"one_Partition\".\"_hyper_1_1_chunk_one_Partition_timeCustom_series_2_idx\"    | i    | series_2    | double precision | f\n \"one_Partition\".\"_hyper_1_1_chunk_one_Partition_timeCustom_series_bool_idx\" | i    | timeCustom  | bigint           | f\n \"one_Partition\".\"_hyper_1_1_chunk_one_Partition_timeCustom_series_bool_idx\" | i    | series_bool | boolean          | f\n \"one_Partition\"._hyper_1_2_chunk                                            | r    | timeCustom  | bigint           | t\n \"one_Partition\"._hyper_1_2_chunk                                            | r    | device_id   | text             | t\n \"one_Partition\"._hyper_1_2_chunk                                            | r    | series_0    | double precision | f\n \"one_Partition\"._hyper_1_2_chunk                                            | r    | series_1    | double precision | f\n \"one_Partition\"._hyper_1_2_chunk                                            | r    | series_2    | double precision | f\n \"one_Partition\"._hyper_1_2_chunk                                            | r    | series_bool | boolean          | f\n \"one_Partition\".\"_hyper_1_2_chunk_one_Partition_device_id_timeCustom_idx\"   | i    | device_id   | text             | f\n \"one_Partition\".\"_hyper_1_2_chunk_one_Partition_device_id_timeCustom_idx\"   | i    | timeCustom  | bigint           | f\n \"one_Partition\".\"_hyper_1_2_chunk_one_Partition_timeCustom_idx\"             | i    | timeCustom  | bigint           | f\n \"one_Partition\".\"_hyper_1_2_chunk_one_Partition_timeCustom_series_0_idx\"    | i    | timeCustom  | bigint           | f\n \"one_Partition\".\"_hyper_1_2_chunk_one_Partition_timeCustom_series_0_idx\"    | i    | series_0    | double precision | f\n \"one_Partition\".\"_hyper_1_2_chunk_one_Partition_timeCustom_series_1_idx\"    | i    | timeCustom  | bigint           | f\n \"one_Partition\".\"_hyper_1_2_chunk_one_Partition_timeCustom_series_1_idx\"    | i    | series_1    | double precision | f\n \"one_Partition\".\"_hyper_1_2_chunk_one_Partition_timeCustom_series_2_idx\"    | i    | timeCustom  | bigint           | f\n \"one_Partition\".\"_hyper_1_2_chunk_one_Partition_timeCustom_series_2_idx\"    | i    | series_2    | double precision | f\n \"one_Partition\".\"_hyper_1_2_chunk_one_Partition_timeCustom_series_bool_idx\" | i    | timeCustom  | bigint           | f\n \"one_Partition\".\"_hyper_1_2_chunk_one_Partition_timeCustom_series_bool_idx\" | i    | series_bool | boolean          | f\n \"one_Partition\"._hyper_1_3_chunk                                            | r    | timeCustom  | bigint           | t\n \"one_Partition\"._hyper_1_3_chunk                                            | r    | device_id   | text             | t\n \"one_Partition\"._hyper_1_3_chunk                                            | r    | series_0    | double precision | f\n \"one_Partition\"._hyper_1_3_chunk                                            | r    | series_1    | double precision | f\n \"one_Partition\"._hyper_1_3_chunk                                            | r    | series_2    | double precision | f\n \"one_Partition\"._hyper_1_3_chunk                                            | r    | series_bool | boolean          | f\n \"one_Partition\".\"_hyper_1_3_chunk_one_Partition_device_id_timeCustom_idx\"   | i    | device_id   | text             | f\n \"one_Partition\".\"_hyper_1_3_chunk_one_Partition_device_id_timeCustom_idx\"   | i    | timeCustom  | bigint           | f\n \"one_Partition\".\"_hyper_1_3_chunk_one_Partition_timeCustom_idx\"             | i    | timeCustom  | bigint           | f\n \"one_Partition\".\"_hyper_1_3_chunk_one_Partition_timeCustom_series_0_idx\"    | i    | timeCustom  | bigint           | f\n \"one_Partition\".\"_hyper_1_3_chunk_one_Partition_timeCustom_series_0_idx\"    | i    | series_0    | double precision | f\n \"one_Partition\".\"_hyper_1_3_chunk_one_Partition_timeCustom_series_1_idx\"    | i    | timeCustom  | bigint           | f\n \"one_Partition\".\"_hyper_1_3_chunk_one_Partition_timeCustom_series_1_idx\"    | i    | series_1    | double precision | f\n \"one_Partition\".\"_hyper_1_3_chunk_one_Partition_timeCustom_series_2_idx\"    | i    | timeCustom  | bigint           | f\n \"one_Partition\".\"_hyper_1_3_chunk_one_Partition_timeCustom_series_2_idx\"    | i    | series_2    | double precision | f\n \"one_Partition\".\"_hyper_1_3_chunk_one_Partition_timeCustom_series_bool_idx\" | i    | timeCustom  | bigint           | f\n \"one_Partition\".\"_hyper_1_3_chunk_one_Partition_timeCustom_series_bool_idx\" | i    | series_bool | boolean          | f\n\nSELECT * FROM \"one_Partition\" ORDER BY \"timeCustom\", device_id, series_0, series_1, series_2;\n     timeCustom      | device_id | series_0 | series_1 | series_2 | series_bool \n---------------------+-----------+----------+----------+----------+-------------\n 1257894000000000000 | dev1      |      1.5 |        1 |        2 | t\n 1257894000000000000 | dev1      |      1.5 |        2 |          | \n 1257894000000000000 | dev2      |      1.5 |        1 |          | \n 1257894000000000000 | dev2      |      1.5 |        2 |          | \n 1257894000000001000 | dev1      |      2.5 |        3 |          | \n 1257894001000000000 | dev1      |      3.5 |        4 |          | \n 1257894002000000000 | dev1      |      2.5 |        3 |          | \n 1257894002000000000 | dev1      |      5.5 |        6 |          | t\n 1257894002000000000 | dev1      |      5.5 |        7 |          | f\n 1257897600000000000 | dev1      |      4.5 |        5 |          | f\n 1257987600000000000 | dev1      |      1.5 |        1 |          | \n 1257987600000000000 | dev1      |      1.5 |        2 |          | \n\n--test that we can insert data into a 1-dimensional table (only time partitioning)\nCREATE TABLE \"1dim\"(time timestamp PRIMARY KEY, temp float);\nSELECT create_hypertable('\"1dim\"', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n create_hypertable \n-------------------\n (2,public,1dim,t)\n\nINSERT INTO \"1dim\" VALUES('2017-01-20T09:00:01', 22.5) RETURNING *;\n           time           | temp \n--------------------------+------\n Fri Jan 20 09:00:01 2017 | 22.5\n\nINSERT INTO \"1dim\" VALUES('2017-01-20T09:00:21', 21.2);\nINSERT INTO \"1dim\" VALUES('2017-01-20T09:00:47', 25.1);\nSELECT * FROM \"1dim\";\n           time           | temp \n--------------------------+------\n Fri Jan 20 09:00:01 2017 | 22.5\n Fri Jan 20 09:00:21 2017 | 21.2\n Fri Jan 20 09:00:47 2017 | 25.1\n\nCREATE TABLE regular_table (time timestamp, temp float);\nINSERT INTO regular_table SELECT * FROM \"1dim\";\nSELECT * FROM regular_table;\n           time           | temp \n--------------------------+------\n Fri Jan 20 09:00:01 2017 | 22.5\n Fri Jan 20 09:00:21 2017 | 21.2\n Fri Jan 20 09:00:47 2017 | 25.1\n\nTRUNCATE TABLE regular_table;\nINSERT INTO regular_table VALUES('2017-01-20T09:00:59', 29.2);\nINSERT INTO \"1dim\" SELECT * FROM regular_table;\nSELECT * FROM \"1dim\";\n           time           | temp \n--------------------------+------\n Fri Jan 20 09:00:01 2017 | 22.5\n Fri Jan 20 09:00:21 2017 | 21.2\n Fri Jan 20 09:00:47 2017 | 25.1\n Fri Jan 20 09:00:59 2017 | 29.2\n\nSELECT \"1dim\" FROM \"1dim\";\n               1dim                \n-----------------------------------\n (\"Fri Jan 20 09:00:01 2017\",22.5)\n (\"Fri Jan 20 09:00:21 2017\",21.2)\n (\"Fri Jan 20 09:00:47 2017\",25.1)\n (\"Fri Jan 20 09:00:59 2017\",29.2)\n\n--test that we can insert pre-1970 dates\nCREATE TABLE \"1dim_pre1970\"(time timestamp PRIMARY KEY, temp float);\nSELECT create_hypertable('\"1dim_pre1970\"', 'time', chunk_time_interval=> INTERVAL '1 Month');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n     create_hypertable     \n---------------------------\n (3,public,1dim_pre1970,t)\n\nINSERT INTO \"1dim_pre1970\" VALUES('1969-12-01T19:00:00', 21.2);\nINSERT INTO \"1dim_pre1970\" VALUES('1969-12-20T09:00:00', 25.1);\nINSERT INTO \"1dim_pre1970\" VALUES('1970-01-20T09:00:00', 26.6);\nINSERT INTO \"1dim_pre1970\" VALUES('1969-02-20T09:00:00', 29.9);\n--should show warning\nBEGIN;\nCREATE TABLE \"1dim_usec_interval\"(time timestamp PRIMARY KEY, temp float);\nSELECT create_hypertable('\"1dim_usec_interval\"', 'time', chunk_time_interval=> 10);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\nWARNING:  unexpected interval: smaller than one second\n        create_hypertable        \n---------------------------------\n (4,public,1dim_usec_interval,t)\n\nINSERT INTO \"1dim_usec_interval\" VALUES('1969-12-01T19:00:00', 21.2);\nROLLBACK;\nCREATE TABLE \"1dim_usec_interval\"(time timestamp PRIMARY KEY, temp float);\nSELECT create_hypertable('\"1dim_usec_interval\"', 'time', chunk_time_interval=> 1000000);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n        create_hypertable        \n---------------------------------\n (5,public,1dim_usec_interval,t)\n\nINSERT INTO \"1dim_usec_interval\" VALUES('1969-12-01T19:00:00', 21.2);\nCREATE TABLE \"1dim_neg\"(time INTEGER, temp float);\nSELECT create_hypertable('\"1dim_neg\"', 'time', chunk_time_interval=>10);\n   create_hypertable   \n-----------------------\n (6,public,1dim_neg,t)\n\nINSERT INTO \"1dim_neg\" VALUES (-20, 21.2);\nINSERT INTO \"1dim_neg\" VALUES (-19, 21.2);\nINSERT INTO \"1dim_neg\" VALUES (-1, 21.2);\nINSERT INTO \"1dim_neg\" VALUES (0, 21.2);\nINSERT INTO \"1dim_neg\" VALUES (1, 21.2);\nINSERT INTO \"1dim_neg\" VALUES (19, 21.2);\nINSERT INTO \"1dim_neg\" VALUES (20, 21.2);\nSELECT * FROM \"1dim_pre1970\";\n           time           | temp \n--------------------------+------\n Mon Dec 01 19:00:00 1969 | 21.2\n Sat Dec 20 09:00:00 1969 | 25.1\n Tue Jan 20 09:00:00 1970 | 26.6\n Thu Feb 20 09:00:00 1969 | 29.9\n\nSELECT * FROM \"1dim_neg\";\n time | temp \n------+------\n  -20 | 21.2\n  -19 | 21.2\n   -1 | 21.2\n    0 | 21.2\n    1 | 21.2\n   19 | 21.2\n   20 | 21.2\n\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n id | hypertable_id |      schema_name      |    table_name     | compressed_chunk_id | status | osm_chunk \n----+---------------+-----------------------+-------------------+---------------------+--------+-----------\n  1 |             1 | one_Partition         | _hyper_1_1_chunk  |                     |      0 | f\n  2 |             1 | one_Partition         | _hyper_1_2_chunk  |                     |      0 | f\n  3 |             1 | one_Partition         | _hyper_1_3_chunk  |                     |      0 | f\n  4 |             2 | _timescaledb_internal | _hyper_2_4_chunk  |                     |      0 | f\n  5 |             3 | _timescaledb_internal | _hyper_3_5_chunk  |                     |      0 | f\n  6 |             3 | _timescaledb_internal | _hyper_3_6_chunk  |                     |      0 | f\n  7 |             3 | _timescaledb_internal | _hyper_3_7_chunk  |                     |      0 | f\n  8 |             3 | _timescaledb_internal | _hyper_3_8_chunk  |                     |      0 | f\n 10 |             5 | _timescaledb_internal | _hyper_5_10_chunk |                     |      0 | f\n 11 |             6 | _timescaledb_internal | _hyper_6_11_chunk |                     |      0 | f\n 12 |             6 | _timescaledb_internal | _hyper_6_12_chunk |                     |      0 | f\n 13 |             6 | _timescaledb_internal | _hyper_6_13_chunk |                     |      0 | f\n 14 |             6 | _timescaledb_internal | _hyper_6_14_chunk |                     |      0 | f\n 15 |             6 | _timescaledb_internal | _hyper_6_15_chunk |                     |      0 | f\n\nSELECT * FROM _timescaledb_catalog.dimension_slice;\n id | dimension_id |     range_start     |      range_end      \n----+--------------+---------------------+---------------------\n  1 |            1 | 1257892416000000000 | 1257895008000000000\n  2 |            1 | 1257897600000000000 | 1257900192000000000\n  3 |            1 | 1257985728000000000 | 1257988320000000000\n  4 |            2 |    1484784000000000 |    1485388800000000\n  5 |            3 |      -5184000000000 |      -2592000000000\n  6 |            3 |      -2592000000000 |                   0\n  7 |            3 |                   0 |       2592000000000\n  8 |            3 |     -28512000000000 |     -25920000000000\n 10 |            5 |      -2610000000000 |      -2609999000000\n 11 |            6 |                 -20 |                 -10\n 12 |            6 |                 -10 |                   0\n 13 |            6 |                   0 |                  10\n 14 |            6 |                  10 |                  20\n 15 |            6 |                  20 |                  30\n\n-- Create a three-dimensional table\nCREATE TABLE \"3dim\" (time timestamp, temp float, device text, location text);\nSELECT create_hypertable('\"3dim\"', 'time', 'device', 2);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n create_hypertable \n-------------------\n (7,public,3dim,t)\n\nSELECT add_dimension('\"3dim\"', 'location', 2);\n       add_dimension        \n----------------------------\n (9,public,3dim,location,t)\n\nINSERT INTO \"3dim\" VALUES('2017-01-20T09:00:01', 22.5, 'blue', 'nyc');\nINSERT INTO \"3dim\" VALUES('2017-01-20T09:00:21', 21.2, 'brown', 'sthlm');\nINSERT INTO \"3dim\" VALUES('2017-01-20T09:00:47', 25.1, 'yellow', 'la');\n--show the constraints on the three-dimensional chunk\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_7_16_chunk');\n  Constraint   | Type |  Columns   | Index |                                                                     Expr                                                                     | Deferrable | Deferred | Validated \n---------------+------+------------+-------+----------------------------------------------------------------------------------------------------------------------------------------------+------------+----------+-----------\n constraint_16 | c    | {time}     | -     | ((\"time\" >= 'Thu Jan 19 00:00:00 2017'::timestamp without time zone) AND (\"time\" < 'Thu Jan 26 00:00:00 2017'::timestamp without time zone)) | f          | f        | t\n constraint_17 | c    | {device}   | -     | (_timescaledb_functions.get_partition_hash(device) < 1073741823)                                                                             | f          | f        | t\n constraint_18 | c    | {location} | -     | (_timescaledb_functions.get_partition_hash(location) >= 1073741823)                                                                          | f          | f        | t\n\n--queries should work in three dimensions\nSELECT * FROM \"3dim\";\n           time           | temp | device | location \n--------------------------+------+--------+----------\n Fri Jan 20 09:00:01 2017 | 22.5 | blue   | nyc\n Fri Jan 20 09:00:47 2017 | 25.1 | yellow | la\n Fri Jan 20 09:00:21 2017 | 21.2 | brown  | sthlm\n\n-- test that explain works\nEXPLAIN (BUFFERS FALSE, COSTS FALSE)\nINSERT INTO \"3dim\" VALUES('2017-01-21T09:00:01', 32.9, 'green', 'nyc'),\n                         ('2017-01-21T09:00:47', 27.3, 'purple', 'la') RETURNING *;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on \"3dim\"\n         ->  Values Scan on \"*VALUES*\"\n\nEXPLAIN (BUFFERS FALSE, COSTS FALSE)\nWITH \"3dim_insert\" AS (\n     INSERT INTO \"3dim\" VALUES('2017-01-21T09:01:44', 19.3, 'black', 'la') RETURNING time, temp\n), regular_insert AS (\n   INSERT INTO regular_table VALUES('2017-01-21T10:00:51', 14.3) RETURNING time, temp\n) INSERT INTO \"1dim\" (SELECT time, temp FROM \"3dim_insert\" UNION SELECT time, temp FROM regular_insert);\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   CTE 3dim_insert\n     ->  Custom Scan (ModifyHypertable)\n           ->  Insert on \"3dim\"\n                 ->  Result\n   CTE regular_insert\n     ->  Insert on regular_table\n           ->  Result\n   ->  Insert on \"1dim\"\n         ->  Unique\n               ->  Sort\n                     Sort Key: \"3dim_insert\".\"time\", \"3dim_insert\".temp\n                     ->  Append\n                           ->  CTE Scan on \"3dim_insert\"\n                           ->  CTE Scan on regular_insert\n\n-- test prepared statement INSERT\nPREPARE \"1dim_plan\" (timestamp, float) AS\nINSERT INTO \"1dim\" VALUES($1, $2) ON CONFLICT (time) DO NOTHING;\nEXECUTE \"1dim_plan\" ('2017-04-17 23:35', 31.4);\nEXECUTE \"1dim_plan\" ('2017-04-17 23:35', 32.6);\n-- test prepared statement with generic plan (forced when no parameters)\nPREPARE \"1dim_plan_generic\" AS\nINSERT INTO \"1dim\" VALUES('2017-05-18 17:24', 18.3);\nEXECUTE \"1dim_plan_generic\";\nSELECT * FROM \"1dim\" ORDER BY time;\n           time           | temp \n--------------------------+------\n Fri Jan 20 09:00:01 2017 | 22.5\n Fri Jan 20 09:00:21 2017 | 21.2\n Fri Jan 20 09:00:47 2017 | 25.1\n Fri Jan 20 09:00:59 2017 | 29.2\n Mon Apr 17 23:35:00 2017 | 31.4\n Thu May 18 17:24:00 2017 | 18.3\n\nSELECT * FROM \"3dim\" ORDER BY (time, device);\n           time           | temp | device | location \n--------------------------+------+--------+----------\n Fri Jan 20 09:00:01 2017 | 22.5 | blue   | nyc\n Fri Jan 20 09:00:21 2017 | 21.2 | brown  | sthlm\n Fri Jan 20 09:00:47 2017 | 25.1 | yellow | la\n\n-- Test large intervals as default interval for integer is\n-- supported as part of hypertable generalization\n\\set ON_ERROR_STOP 0\nCREATE TABLE \"inttime_err\"(time INTEGER PRIMARY KEY, temp float);\nSELECT create_hypertable('\"inttime_err\"', 'time', chunk_time_interval=>2147483648);\nERROR:  invalid interval: must be between 1 and 2147483647\n\\set ON_ERROR_STOP 1\nSELECT create_hypertable('\"inttime_err\"', 'time', chunk_time_interval=>2147483647);\n    create_hypertable     \n--------------------------\n (8,public,inttime_err,t)\n\n-- Test large intervals as default interval is supported\n-- for integer types as part of hypertable generalization.\n\\set ON_ERROR_STOP 0\nCREATE TABLE \"smallinttime_err\"(time SMALLINT PRIMARY KEY, temp float);\nSELECT create_hypertable('\"smallinttime_err\"', 'time', chunk_time_interval=>32768);\nERROR:  invalid interval: must be between 1 and 32767\n\\set ON_ERROR_STOP 1\nSELECT create_hypertable('\"smallinttime_err\"', 'time', chunk_time_interval=>32767);\n       create_hypertable       \n-------------------------------\n (9,public,smallinttime_err,t)\n\n--make sure date inserts work even when the timezone changes the\nCREATE TABLE hyper_date(time date, temp float);\nSELECT create_hypertable('\"hyper_date\"', 'time');\n    create_hypertable     \n--------------------------\n (10,public,hyper_date,t)\n\nSET timezone=+1;\nINSERT INTO \"hyper_date\" VALUES('2011-01-26', 22.5);\nRESET timezone;\n--make sure timestamp inserts work even when the timezone changes the\nSET timezone = 'UTC';\nCREATE TABLE \"test_tz\"(time timestamp PRIMARY KEY, temp float);\nSELECT create_hypertable('\"test_tz\"', 'time', chunk_time_interval=> INTERVAL '1 day');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n   create_hypertable   \n-----------------------\n (11,public,test_tz,t)\n\nINSERT INTO \"test_tz\" VALUES('2017-09-22 10:00:00', 21.2);\nINSERT INTO \"test_tz\" VALUES('2017-09-21 19:00:00', 21.2);\nSET timezone = 'US/central';\nINSERT INTO \"test_tz\" VALUES('2017-09-21 19:01:00', 21.2);\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_10_20_chunk');\n  Constraint   | Type | Columns | Index |                                Expr                                | Deferrable | Deferred | Validated \n---------------+------+---------+-------+--------------------------------------------------------------------+------------+----------+-----------\n constraint_23 | c    | {time}  | -     | ((\"time\" >= '01-20-2011'::date) AND (\"time\" < '01-27-2011'::date)) | f          | f        | t\n\nSELECT * FROM test_tz;\n           time           | temp \n--------------------------+------\n Fri Sep 22 10:00:00 2017 | 21.2\n Thu Sep 21 19:00:00 2017 | 21.2\n Thu Sep 21 19:01:00 2017 | 21.2\n\n-- test various memory settings --\nSET timescaledb.max_open_chunks_per_insert = 10;\nSET timescaledb.max_cached_chunks_per_hypertable = 10;\nCREATE TABLE \"nondefault_mem_settings\"(time timestamp PRIMARY KEY, temp float);\nSELECT create_hypertable('\"nondefault_mem_settings\"', 'time', chunk_time_interval=> INTERVAL '1 Month');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n           create_hypertable           \n---------------------------------------\n (12,public,nondefault_mem_settings,t)\n\nINSERT INTO \"nondefault_mem_settings\" VALUES('2000-12-01T19:00:00', 21.2);\nINSERT INTO \"nondefault_mem_settings\" VALUES('2001-12-20T09:00:00', 25.1);\n--lowest possible\nSET timescaledb.max_open_chunks_per_insert = 1;\nSET timescaledb.max_cached_chunks_per_hypertable = 1;\nINSERT INTO \"nondefault_mem_settings\" VALUES\n('2001-01-20T09:00:00', 26.6),\n('2002-02-20T09:00:00', 27.9),\n('2003-02-20T09:00:00', 28.9);\nINSERT INTO \"nondefault_mem_settings\" VALUES\n('2001-03-20T09:00:00', 30.6),\n('2002-03-20T09:00:00', 31.9),\n('2003-03-20T09:00:00', 32.9);\n--warning about mismatched cache sizes\nSET timescaledb.max_open_chunks_per_insert = 100;\nWARNING:  insert cache size is larger than hypertable chunk cache size\nSET timescaledb.max_cached_chunks_per_hypertable = 10;\nWARNING:  insert cache size is larger than hypertable chunk cache size\nINSERT INTO \"nondefault_mem_settings\" VALUES\n('2001-05-20T09:00:00', 36.6),\n('2002-05-20T09:00:00', 37.9),\n('2003-05-20T09:00:00', 38.9);\n--unlimited\nSET timescaledb.max_open_chunks_per_insert = 0;\nSET timescaledb.max_cached_chunks_per_hypertable = 0;\nINSERT INTO \"nondefault_mem_settings\" VALUES\n('2001-04-20T09:00:00', 33.6),\n('2002-04-20T09:00:00', 34.9),\n('2003-04-20T09:00:00', 35.9);\nSELECT * FROM \"nondefault_mem_settings\";\n           time           | temp \n--------------------------+------\n Fri Dec 01 19:00:00 2000 | 21.2\n Thu Dec 20 09:00:00 2001 | 25.1\n Sat Jan 20 09:00:00 2001 | 26.6\n Wed Feb 20 09:00:00 2002 | 27.9\n Thu Feb 20 09:00:00 2003 | 28.9\n Tue Mar 20 09:00:00 2001 | 30.6\n Wed Mar 20 09:00:00 2002 | 31.9\n Thu Mar 20 09:00:00 2003 | 32.9\n Sun May 20 09:00:00 2001 | 36.6\n Mon May 20 09:00:00 2002 | 37.9\n Tue May 20 09:00:00 2003 | 38.9\n Fri Apr 20 09:00:00 2001 | 33.6\n Sat Apr 20 09:00:00 2002 | 34.9\n Sun Apr 20 09:00:00 2003 | 35.9\n\n--test rollback\nBEGIN;\n\\set QUIET off\nCREATE TABLE \"data_records\" (\"time\" bigint NOT NULL, \"value\" integer CHECK (VALUE >= 0));\nCREATE TABLE\nSELECT create_hypertable('data_records', 'time', chunk_time_interval => 2592000000);\n     create_hypertable      \n----------------------------\n (13,public,data_records,t)\n\nINSERT INTO \"data_records\" (\"time\", \"value\") VALUES (0, 1);\nINSERT 0 1\nSAVEPOINT savepoint_1;\nSAVEPOINT\nINSERT INTO \"data_records\" (\"time\", \"value\") VALUES (1, 0);\nINSERT 0 1\nROLLBACK TO SAVEPOINT savepoint_1;\nROLLBACK\nINSERT INTO \"data_records\" (\"time\", \"value\") VALUES (2, 1);\nINSERT 0 1\nSAVEPOINT savepoint_2;\nSAVEPOINT\n\\set ON_ERROR_STOP 0\nINSERT INTO \"data_records\" (\"time\", \"value\") VALUES (3, -1);\nERROR:  new row for relation \"_hyper_13_37_chunk\" violates check constraint \"data_records_value_check\"\n\\set ON_ERROR_STOP 1\nROLLBACK TO SAVEPOINT savepoint_2;\nROLLBACK\nINSERT INTO \"data_records\" (\"time\", \"value\") VALUES (4, 1);\nINSERT 0 1\nSAVEPOINT savepoint_3;\nSAVEPOINT\nINSERT INTO \"data_records\" (\"time\", \"value\") VALUES (5, 0);\nINSERT 0 1\nROLLBACK TO SAVEPOINT savepoint_3;\nROLLBACK\nSELECT * FROM data_records;\n time | value \n------+-------\n    0 |     1\n    2 |     1\n    4 |     1\n\n\\set QUIET on\nROLLBACK;\n-- Test INSERT into hypertable with a generated column whose type is a\n-- domain with a NOT NULL constraint\nCREATE DOMAIN nn_int AS int CHECK (VALUE IS NOT NULL);\nCREATE TABLE generated_col_ht(\n    time timestamptz NOT NULL,\n    val int NOT NULL,\n    doubled nn_int GENERATED ALWAYS AS (val * 2) STORED\n);\nSELECT create_hypertable('generated_col_ht', 'time');\n       create_hypertable        \n--------------------------------\n (14,public,generated_col_ht,t)\n\nINSERT INTO generated_col_ht(time, val) VALUES ('2024-01-01', 5);\nINSERT INTO generated_col_ht(time, val) VALUES ('2024-01-02', 10) RETURNING *;\n             time             | val | doubled \n------------------------------+-----+---------\n Tue Jan 02 00:00:00 2024 CST |  10 |      20\n\nSELECT * FROM generated_col_ht ORDER BY time;\n             time             | val | doubled \n------------------------------+-----+---------\n Mon Jan 01 00:00:00 2024 CST |   5 |      10\n Tue Jan 02 00:00:00 2024 CST |  10 |      20\n\nDROP TABLE generated_col_ht;\nDROP DOMAIN nn_int;\n"
  },
  {
    "path": "test/expected/lateral.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE regular_table(name text, junk text);\nCREATE TABLE ht(time timestamptz NOT NULL, location text);\nSELECT create_hypertable('ht', 'time');\n create_hypertable \n-------------------\n (1,public,ht,t)\n\nINSERT INTO ht(time) select timestamp 'epoch' + (i * interval '1 second') from generate_series(1, 100) as T(i);\nINSERT INTO regular_table values('name', 'junk');\nSELECT * FROM regular_table ik LEFT JOIN LATERAL (select max(time::timestamptz) from ht s where ik.name='name' and s.time < now()) s on true;\n name | junk |             max              \n------+------+------------------------------\n name | junk | Thu Jan 01 00:01:40 1970 PST\n\nselect * from regular_table ik LEFT JOIN LATERAL (select max(time::timestamptz) from ht s where ik.name='name' and s.time > now()) s on true;\n name | junk | max \n------+------+-----\n name | junk | \n\nDROP TABLE regular_table;\nDROP TABLE ht;\nCREATE TABLE orders(id int, user_id int, time TIMESTAMPTZ NOT NULL);\nSELECT create_hypertable('orders', 'time');\n  create_hypertable  \n---------------------\n (2,public,orders,t)\n\nINSERT INTO orders values(1,1,timestamp 'epoch' + '1 second');\nINSERT INTO orders values(2,1,timestamp 'epoch' + '2 second');\nINSERT INTO orders values(3,1,timestamp 'epoch' + '3 second');\nINSERT INTO orders values(4,2,timestamp 'epoch' + '4 second');\nINSERT INTO orders values(5,1,timestamp 'epoch' + '5 second');\nINSERT INTO orders values(6,3,timestamp 'epoch' + '6 second');\nINSERT INTO orders values(7,1,timestamp 'epoch' + '7 second');\nINSERT INTO orders values(8,4,timestamp 'epoch' + '8 second');\nINSERT INTO orders values(9,2,timestamp 'epoch' + '9 second');\n-- Need a LATERAL query with a reference to the upper-level table and\n-- with a restriction on time\n-- Upper-level table constraint should be a constant in order to trigger\n-- creation of a one-time filter in the planner\nSELECT user_id, first_order_time, max_time FROM\n(SELECT user_id, min(time) AS first_order_time FROM orders GROUP BY user_id) o1\nLEFT JOIN LATERAL\n(SELECT max(time) AS max_time FROM orders WHERE o1.user_id = '2' AND time > now()) o2 ON true\nORDER BY user_id, first_order_time, max_time;\n user_id |       first_order_time       | max_time \n---------+------------------------------+----------\n       1 | Thu Jan 01 00:00:01 1970 PST | \n       2 | Thu Jan 01 00:00:04 1970 PST | \n       3 | Thu Jan 01 00:00:06 1970 PST | \n       4 | Thu Jan 01 00:00:08 1970 PST | \n\nSELECT user_id, first_order_time, max_time FROM\n(SELECT user_id, min(time) AS first_order_time FROM orders GROUP BY user_id) o1\nLEFT JOIN LATERAL\n(SELECT max(time) AS max_time FROM orders WHERE o1.user_id = '2' AND time < now()) o2 ON true\nORDER BY user_id, first_order_time, max_time;\n user_id |       first_order_time       |           max_time           \n---------+------------------------------+------------------------------\n       1 | Thu Jan 01 00:00:01 1970 PST | \n       2 | Thu Jan 01 00:00:04 1970 PST | Thu Jan 01 00:00:09 1970 PST\n       3 | Thu Jan 01 00:00:06 1970 PST | \n       4 | Thu Jan 01 00:00:08 1970 PST | \n\n-- Nested LATERALs\nSELECT user_id, first_order_time, time1, min_time FROM\n(SELECT user_id, min(time) AS first_order_time FROM orders GROUP BY user_id) o1\nLEFT JOIN LATERAL\n(SELECT user_id as o2user_id, time AS time1 FROM orders WHERE o1.user_id = '2' AND time < now()) o2 ON true\nLEFT JOIN LATERAL\n(SELECT min(time) as min_time FROM orders WHERE o2.o2user_id = '1' AND time < now()) o3 ON true\nORDER BY user_id, first_order_time, time1, min_time;\n user_id |       first_order_time       |            time1             |           min_time           \n---------+------------------------------+------------------------------+------------------------------\n       1 | Thu Jan 01 00:00:01 1970 PST |                              | \n       2 | Thu Jan 01 00:00:04 1970 PST | Thu Jan 01 00:00:01 1970 PST | Thu Jan 01 00:00:01 1970 PST\n       2 | Thu Jan 01 00:00:04 1970 PST | Thu Jan 01 00:00:02 1970 PST | Thu Jan 01 00:00:01 1970 PST\n       2 | Thu Jan 01 00:00:04 1970 PST | Thu Jan 01 00:00:03 1970 PST | Thu Jan 01 00:00:01 1970 PST\n       2 | Thu Jan 01 00:00:04 1970 PST | Thu Jan 01 00:00:04 1970 PST | \n       2 | Thu Jan 01 00:00:04 1970 PST | Thu Jan 01 00:00:05 1970 PST | Thu Jan 01 00:00:01 1970 PST\n       2 | Thu Jan 01 00:00:04 1970 PST | Thu Jan 01 00:00:06 1970 PST | \n       2 | Thu Jan 01 00:00:04 1970 PST | Thu Jan 01 00:00:07 1970 PST | Thu Jan 01 00:00:01 1970 PST\n       2 | Thu Jan 01 00:00:04 1970 PST | Thu Jan 01 00:00:08 1970 PST | \n       2 | Thu Jan 01 00:00:04 1970 PST | Thu Jan 01 00:00:09 1970 PST | \n       3 | Thu Jan 01 00:00:06 1970 PST |                              | \n       4 | Thu Jan 01 00:00:08 1970 PST |                              | \n\n-- Cleanup\nDROP TABLE orders;\n---- OUTER JOIN tests ---\n--github issue 2500\nCREATE TABLE t1_timescale (a int, b int);\nCREATE TABLE t2 (a int, b int);\nSELECT create_hypertable('t1_timescale', 'a', chunk_time_interval=>1000);\n     create_hypertable     \n---------------------------\n (3,public,t1_timescale,t)\n\nINSERT into t2 values (3, 3), (15 , 15);\nINSERT into t1_timescale select generate_series(5, 25, 1), 77;\nUPDATE t1_timescale SET b = 15 WHERE a = 15;\nSELECT * FROM t1_timescale\nFULL OUTER JOIN  t2 on t1_timescale.b=t2.b and t2.b between 10 and 20\nORDER BY 1, 2, 3, 4;\n a  | b  | a  | b  \n----+----+----+----\n  5 | 77 |    |   \n  6 | 77 |    |   \n  7 | 77 |    |   \n  8 | 77 |    |   \n  9 | 77 |    |   \n 10 | 77 |    |   \n 11 | 77 |    |   \n 12 | 77 |    |   \n 13 | 77 |    |   \n 14 | 77 |    |   \n 15 | 15 | 15 | 15\n 16 | 77 |    |   \n 17 | 77 |    |   \n 18 | 77 |    |   \n 19 | 77 |    |   \n 20 | 77 |    |   \n 21 | 77 |    |   \n 22 | 77 |    |   \n 23 | 77 |    |   \n 24 | 77 |    |   \n 25 | 77 |    |   \n    |    |  3 |  3\n\nSELECT * FROM t1_timescale\nLEFT OUTER JOIN  t2 on t1_timescale.b=t2.b and t2.b between 10 and 20\nWHERE t1_timescale.a=5\nORDER BY 1, 2, 3, 4;\n a | b  | a | b \n---+----+---+---\n 5 | 77 |   |  \n\nSELECT * FROM t1_timescale\nRIGHT JOIN  t2 on t1_timescale.b=t2.b and t2.b between 10 and 20\nORDER BY 1, 2, 3, 4;\n a  | b  | a  | b  \n----+----+----+----\n 15 | 15 | 15 | 15\n    |    |  3 |  3\n\nSELECT * FROM t1_timescale\nRIGHT JOIN  t2 on t1_timescale.b=t2.b and t2.b between 10 and 20\nWHERE t1_timescale.a=5\nORDER BY 1, 2, 3, 4;\n a | b | a | b \n---+---+---+---\n\nSELECT * FROM t1_timescale\nLEFT OUTER JOIN  t2 on t1_timescale.a=t2.a and t2.b between 10 and 20\nWHERE t1_timescale.a IN ( 10, 15, 20, 25)\nORDER BY 1, 2, 3, 4;\n a  | b  | a  | b  \n----+----+----+----\n 10 | 77 |    |   \n 15 | 15 | 15 | 15\n 20 | 77 |    |   \n 25 | 77 |    |   \n\nSELECT * FROM t1_timescale\nRIGHT OUTER JOIN  t2 on t1_timescale.a=t2.a and t2.b between 10 and 20\nORDER BY 1, 2, 3, 4;\n a  | b  | a  | b  \n----+----+----+----\n 15 | 15 | 15 | 15\n    |    |  3 |  3\n\n"
  },
  {
    "path": "test/expected/license.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\\set ECHO queries\nSHOW timescaledb.license;\n timescaledb.license \n---------------------\n apache\n\nSELECT _timescaledb_functions.tsl_loaded();\n tsl_loaded \n------------\n f\n\nSET timescaledb.license='apache';\nERROR:  invalid value for parameter \"timescaledb.license\": \"apache\"\nDETAIL:  Cannot change a license in a running session.\nHINT:  Change the license in the configuration file or server command line.\nSET timescaledb.license='timescale';\nERROR:  invalid value for parameter \"timescaledb.license\": \"timescale\"\nDETAIL:  Cannot change a license in a running session.\nHINT:  Change the license in the configuration file or server command line.\nSET timescaledb.license='something_else';\nERROR:  invalid value for parameter \"timescaledb.license\": \"something_else\"\nDETAIL:  Unrecognized license type.\nHINT:  Supported license types are 'timescale' or 'apache'.\nSELECT locf(1);\nERROR:  function \"locf\" is not supported under the current \"apache\" license\nHINT:  Upgrade your license to 'timescale' to use this free community feature.\nSELECT interpolate(1);\nERROR:  function \"interpolate\" is not supported under the current \"apache\" license\nHINT:  Upgrade your license to 'timescale' to use this free community feature.\nSELECT time_bucket_gapfill(1,1,1,1);\nERROR:  function \"time_bucket_gapfill\" is not supported under the current \"apache\" license\nHINT:  Upgrade your license to 'timescale' to use this free community feature.\nCREATE OR REPLACE FUNCTION custom_func(jobid int, args jsonb) RETURNS VOID AS $$\nDECLARE\nBEGIN\nEND;\n$$ LANGUAGE plpgsql;\nSELECT add_job('custom_func','1h', config:='{\"type\":\"function\"}'::jsonb);\nERROR:  function \"add_job\" is not supported under the current \"apache\" license\nHINT:  Upgrade your license to 'timescale' to use this free community feature.\nDROP FUNCTION custom_func;\nCREATE TABLE metrics(time timestamptz NOT NULL, value float);\nSELECT create_hypertable('metrics', 'time');\n  create_hypertable   \n----------------------\n (1,public,metrics,t)\n\nALTER TABLE metrics SET (timescaledb.compress);\nERROR:  functionality not supported under the current \"apache\" license. Learn more at https://tsdb.co/pdbir1r3\nHINT:  To access all features and the best time-series experience, try out Timescale Cloud.\nINSERT INTO metrics\nVALUES ('2022-01-01 00:00:00', 1), ('2022-01-01 01:00:00', 2), ('2022-01-01 02:00:00', 3);\nCREATE MATERIALIZED VIEW metrics_hourly\nWITH (timescaledb.continuous) AS\nSELECT time_bucket(INTERVAL '1 hour', time) AS bucket,\n   AVG(value),\n   MAX(value),\n   MIN(value)\nFROM metrics\nGROUP BY bucket\nWITH NO DATA;\nERROR:  functionality not supported under the current \"apache\" license. Learn more at https://tsdb.co/pdbir1r3\nHINT:  To access all features and the best time-series experience, try out Timescale Cloud.\nCREATE MATERIALIZED VIEW metrics_hourly\nAS\nSELECT time_bucket(INTERVAL '1 hour', time) AS bucket,\n   AVG(value),\n   MAX(value),\n   MIN(value)\nFROM metrics\nGROUP BY bucket;\nCALL refresh_continuous_aggregate('metrics_hourly', NULL, NULL);\nERROR:  function \"refresh_continuous_aggregate\" is not supported under the current \"apache\" license\nHINT:  Upgrade your license to 'timescale' to use this free community feature.\nDROP MATERIALIZED VIEW metrics_hourly;\nDROP TABLE metrics;\n"
  },
  {
    "path": "test/expected/loader-oss.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\set TEST_DBNAME_2 :TEST_DBNAME _2\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE DATABASE :\"TEST_DBNAME_2\";\nDROP EXTENSION timescaledb;\n--no extension\nSELECT * FROM test.extension;\n                 List of installed extensions\n  Name   | Version |   Schema   |         Description          \n---------+---------+------------+------------------------------\n plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language\n(1 row)\n\nSELECT 1;\n ?column? \n----------\n        1\n(1 row)\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE EXTENSION timescaledb VERSION 'mock-1';\nWARNING:  mock init \"mock-1\"\nSELECT 1;\nWARNING:  mock post_analyze_hook \"mock-1\"\n ?column? \n----------\n        1\n(1 row)\n\nSELECT * FROM test.extension;\nWARNING:  mock post_analyze_hook \"mock-1\"\n                                      List of installed extensions\n    Name     | Version |   Schema   |                            Description                            \n-------------+---------+------------+-------------------------------------------------------------------\n plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language\n timescaledb | mock-1  | public     | Enables scalable inserts and complex queries for time-series data\n(2 rows)\n\nCREATE EXTENSION IF NOT EXISTS timescaledb VERSION 'mock-1';\nWARNING:  mock post_analyze_hook \"mock-1\"\nNOTICE:  extension \"timescaledb\" already exists, skipping\nCREATE EXTENSION IF NOT EXISTS timescaledb VERSION 'mock-2';\nWARNING:  mock post_analyze_hook \"mock-1\"\nNOTICE:  extension \"timescaledb\" already exists, skipping\nDROP EXTENSION timescaledb;\nWARNING:  mock post_analyze_hook \"mock-1\"\n\\set ON_ERROR_STOP 0\n--test that we cannot accidentally load another library version\nCREATE EXTENSION IF NOT EXISTS timescaledb VERSION 'mock-2';\nERROR:  extension \"timescaledb\" has already been loaded with another version\n\\set ON_ERROR_STOP 1\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n--no extension\nSELECT * FROM test.extension;\n                 List of installed extensions\n  Name   | Version |   Schema   |         Description          \n---------+---------+------------+------------------------------\n plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language\n(1 row)\n\nSELECT 1;\n ?column? \n----------\n        1\n(1 row)\n\nCREATE EXTENSION timescaledb VERSION 'mock-1';\nWARNING:  mock init \"mock-1\"\n--same backend as create extension;\nSELECT 1;\nWARNING:  mock post_analyze_hook \"mock-1\"\n ?column? \n----------\n        1\n(1 row)\n\nSELECT * FROM test.extension;\nWARNING:  mock post_analyze_hook \"mock-1\"\n                                      List of installed extensions\n    Name     | Version |   Schema   |                            Description                            \n-------------+---------+------------+-------------------------------------------------------------------\n plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language\n timescaledb | mock-1  | public     | Enables scalable inserts and complex queries for time-series data\n(2 rows)\n\n--start new backend;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT 1;\nWARNING:  mock init \"mock-1\"\nWARNING:  mock post_analyze_hook \"mock-1\"\n ?column? \n----------\n        1\n(1 row)\n\nSELECT 1;\nWARNING:  mock post_analyze_hook \"mock-1\"\n ?column? \n----------\n        1\n(1 row)\n\n--test fn call after load\nSELECT mock_function();\nWARNING:  mock post_analyze_hook \"mock-1\"\nWARNING:  mock function call \"mock-1\"\n mock_function \n---------------\n \n(1 row)\n\nSELECT * FROM test.extension;\nWARNING:  mock post_analyze_hook \"mock-1\"\n                                      List of installed extensions\n    Name     | Version |   Schema   |                            Description                            \n-------------+---------+------------+-------------------------------------------------------------------\n plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language\n timescaledb | mock-1  | public     | Enables scalable inserts and complex queries for time-series data\n(2 rows)\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n--test fn call as first command\nSELECT mock_function();\nWARNING:  mock init \"mock-1\"\nWARNING:  mock post_analyze_hook \"mock-1\"\nWARNING:  mock function call \"mock-1\"\n mock_function \n---------------\n \n(1 row)\n\n--use guc to prevent loading\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET timescaledb.disable_load = 'on';\nSELECT 1;\n ?column? \n----------\n        1\n(1 row)\n\nSELECT 1;\n ?column? \n----------\n        1\n(1 row)\n\nSET timescaledb.disable_load = 'off';\nSELECT 1;\nWARNING:  mock init \"mock-1\"\nWARNING:  mock post_analyze_hook \"mock-1\"\n ?column? \n----------\n        1\n(1 row)\n\n\\set ON_ERROR_STOP 0\nSET timescaledb.disable_load = 'not bool';\nWARNING:  mock post_analyze_hook \"mock-1\"\nERROR:  parameter \"timescaledb.disable_load\" requires a Boolean value\n\\set ON_ERROR_STOP 1\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nRESET ALL;\nWARNING:  mock init \"mock-1\"\nWARNING:  mock post_analyze_hook \"mock-1\"\nSELECT 1;\nWARNING:  mock post_analyze_hook \"mock-1\"\n ?column? \n----------\n        1\n(1 row)\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET timescaledb.disable_load TO DEFAULT;\nSELECT 1;\nWARNING:  mock init \"mock-1\"\nWARNING:  mock post_analyze_hook \"mock-1\"\n ?column? \n----------\n        1\n(1 row)\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nRESET timescaledb.disable_load;\nSELECT 1;\nWARNING:  mock init \"mock-1\"\nWARNING:  mock post_analyze_hook \"mock-1\"\n ?column? \n----------\n        1\n(1 row)\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET timescaledb.other = 'on';\nWARNING:  mock init \"mock-1\"\nWARNING:  mock post_analyze_hook \"mock-1\"\nSELECT 1;\nWARNING:  mock post_analyze_hook \"mock-1\"\n ?column? \n----------\n        1\n(1 row)\n\n\\set ON_ERROR_STOP 0\n--cannot update extension after .so of previous version already loaded\nALTER EXTENSION timescaledb UPDATE TO 'mock-2';\nERROR:  extension \"timescaledb\" cannot be updated after the old version has already been loaded\n\\set ON_ERROR_STOP 1\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\nSELECT * FROM test.extension;\n                 List of installed extensions\n  Name   | Version |   Schema   |         Description          \n---------+---------+------------+------------------------------\n plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language\n(1 row)\n\nCREATE EXTENSION timescaledb VERSION 'mock-1';\nWARNING:  mock init \"mock-1\"\nSELECT * FROM test.extension;\nWARNING:  mock post_analyze_hook \"mock-1\"\n                                      List of installed extensions\n    Name     | Version |   Schema   |                            Description                            \n-------------+---------+------------+-------------------------------------------------------------------\n plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language\n timescaledb | mock-1  | public     | Enables scalable inserts and complex queries for time-series data\n(2 rows)\n\n--start a new backend to update\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\nALTER EXTENSION timescaledb UPDATE TO 'mock-2';\nWARNING:  mock init \"mock-2\"\nSELECT 1;\nWARNING:  mock post_analyze_hook \"mock-2\"\n ?column? \n----------\n        1\n(1 row)\n\nSELECT * FROM test.extension;\nWARNING:  mock post_analyze_hook \"mock-2\"\n                                      List of installed extensions\n    Name     | Version |   Schema   |                            Description                            \n-------------+---------+------------+-------------------------------------------------------------------\n plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language\n timescaledb | mock-2  | public     | Enables scalable inserts and complex queries for time-series data\n(2 rows)\n\n--drop extension\nDROP EXTENSION timescaledb;\nWARNING:  mock post_analyze_hook \"mock-2\"\nSELECT 1;\n ?column? \n----------\n        1\n(1 row)\n\nSELECT * FROM test.extension;\n                 List of installed extensions\n  Name   | Version |   Schema   |         Description          \n---------+---------+------------+------------------------------\n plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language\n(1 row)\n\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\nCREATE EXTENSION timescaledb VERSION 'mock-2';\nWARNING:  mock init \"mock-2\"\nSELECT 1;\nWARNING:  mock post_analyze_hook \"mock-2\"\n ?column? \n----------\n        1\n(1 row)\n\nSELECT * FROM test.extension;\nWARNING:  mock post_analyze_hook \"mock-2\"\n                                      List of installed extensions\n    Name     | Version |   Schema   |                            Description                            \n-------------+---------+------------+-------------------------------------------------------------------\n plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language\n timescaledb | mock-2  | public     | Enables scalable inserts and complex queries for time-series data\n(2 rows)\n\n-- test db 1 still has old version\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT 1;\nWARNING:  mock init \"mock-1\"\nWARNING:  mock post_analyze_hook \"mock-1\"\n ?column? \n----------\n        1\n(1 row)\n\nSELECT * FROM test.extension;\nWARNING:  mock post_analyze_hook \"mock-1\"\n                                      List of installed extensions\n    Name     | Version |   Schema   |                            Description                            \n-------------+---------+------------+-------------------------------------------------------------------\n plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language\n timescaledb | mock-1  | public     | Enables scalable inserts and complex queries for time-series data\n(2 rows)\n\n--try a broken upgrade\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\nSELECT * FROM test.extension;\nWARNING:  mock init \"mock-2\"\nWARNING:  mock post_analyze_hook \"mock-2\"\n                                      List of installed extensions\n    Name     | Version |   Schema   |                            Description                            \n-------------+---------+------------+-------------------------------------------------------------------\n plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language\n timescaledb | mock-2  | public     | Enables scalable inserts and complex queries for time-series data\n(2 rows)\n\n\\set ON_ERROR_STOP 0\nALTER EXTENSION timescaledb UPDATE TO 'mock-3';\nERROR:  extension \"timescaledb\" cannot be updated after the old version has already been loaded\n\\set ON_ERROR_STOP 1\n--should still be on mock-2\nSELECT 1;\nWARNING:  mock post_analyze_hook \"mock-2\"\n ?column? \n----------\n        1\n(1 row)\n\nSELECT * FROM test.extension;\nWARNING:  mock post_analyze_hook \"mock-2\"\n                                      List of installed extensions\n    Name     | Version |   Schema   |                            Description                            \n-------------+---------+------------+-------------------------------------------------------------------\n plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language\n timescaledb | mock-2  | public     | Enables scalable inserts and complex queries for time-series data\n(2 rows)\n\n--drop extension\nDROP EXTENSION timescaledb;\nWARNING:  mock post_analyze_hook \"mock-2\"\nSELECT 1;\n ?column? \n----------\n        1\n(1 row)\n\nSELECT * FROM test.extension;\n                 List of installed extensions\n  Name   | Version |   Schema   |         Description          \n---------+---------+------------+------------------------------\n plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language\n(1 row)\n\n--create extension anew, only upgrade was broken\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\nCREATE EXTENSION timescaledb VERSION 'mock-3';\nWARNING:  mock init \"mock-3\"\nSELECT 1;\nWARNING:  mock post_analyze_hook \"mock-3\"\n ?column? \n----------\n        1\n(1 row)\n\nSELECT * FROM test.extension;\nWARNING:  mock post_analyze_hook \"mock-3\"\n                                      List of installed extensions\n    Name     | Version |   Schema   |                            Description                            \n-------------+---------+------------+-------------------------------------------------------------------\n plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language\n timescaledb | mock-3  | public     | Enables scalable inserts and complex queries for time-series data\n(2 rows)\n\nDROP EXTENSION timescaledb;\nWARNING:  mock post_analyze_hook \"mock-3\"\nSELECT 1;\n ?column? \n----------\n        1\n(1 row)\n\n--mismatched version errors\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\n--mock-4 has mismatched versions, so the .so load should be fatal\nSELECT format($$\\! utils/test_fatal_command.sh %1$s \"CREATE EXTENSION timescaledb VERSION 'mock-4'\"$$, :'TEST_DBNAME_2') as command_to_run \\gset\n:command_to_run\nFATAL:  extension \"timescaledb\" version mismatch: shared library version mock-4-mismatch; SQL version mock-4\nserver closed the connection unexpectedly\n\tThis probably means the server terminated abnormally\n\tbefore or while processing the request.\nconnection to server was lost\n--mock-4 not installed.\nSELECT * FROM test.extension;\n                 List of installed extensions\n  Name   | Version |   Schema   |         Description          \n---------+---------+------------+------------------------------\n plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language\n(1 row)\n\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\n--broken version and drop\nCREATE EXTENSION timescaledb VERSION 'mock-broken';\nWARNING:  mock init \"mock-broken\"\n\\set ON_ERROR_STOP 0\n--intentional broken version\nSELECT * FROM test.extension;\nWARNING:  mock post_analyze_hook \"mock-broken\"\nERROR:  mock broken \"mock-broken\"\nSELECT 1;\nWARNING:  mock post_analyze_hook \"mock-broken\"\nERROR:  mock broken \"mock-broken\"\nSELECT 1;\nWARNING:  mock post_analyze_hook \"mock-broken\"\nERROR:  mock broken \"mock-broken\"\n--cannot drop extension; already loaded broken version\nDROP EXTENSION timescaledb;\nWARNING:  mock post_analyze_hook \"mock-broken\"\nERROR:  mock broken \"mock-broken\"\n\\set ON_ERROR_STOP 1\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\n--can drop extension now. Since drop first command.\nDROP EXTENSION timescaledb;\nSELECT * FROM test.extension;\n                 List of installed extensions\n  Name   | Version |   Schema   |         Description          \n---------+---------+------------+------------------------------\n plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language\n(1 row)\n\n--broken version and update to fixed\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\nCREATE EXTENSION timescaledb VERSION 'mock-broken';\nWARNING:  mock init \"mock-broken\"\n\\set ON_ERROR_STOP 0\n--intentional broken version\nSELECT 1;\nWARNING:  mock post_analyze_hook \"mock-broken\"\nERROR:  mock broken \"mock-broken\"\n--cannot update extension; already loaded bad version\nALTER EXTENSION timescaledb UPDATE TO 'mock-5';\nERROR:  extension \"timescaledb\" cannot be updated after the old version has already been loaded\n\\set ON_ERROR_STOP 1\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\n--can update extension now.\nALTER EXTENSION timescaledb UPDATE TO 'mock-5';\nWARNING:  mock init \"mock-5\"\nSELECT 1;\nWARNING:  mock post_analyze_hook \"mock-5\"\n ?column? \n----------\n        1\n(1 row)\n\nSELECT mock_function();\nWARNING:  mock post_analyze_hook \"mock-5\"\nWARNING:  mock function call \"mock-5\"\n mock_function \n---------------\n \n(1 row)\n\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\nALTER EXTENSION timescaledb UPDATE TO 'mock-6';\nWARNING:  mock init \"mock-6\"\n--The mock-5->mock_6 upgrade is intentionally broken.\n--The mock_function was never changed to point to mock-6 in the update script.\n--Thus mock_function is defined incorrectly to point to the mock-5.so\n--This will now be a FATAL error.\nSELECT format($$\\! utils/test_fatal_command.sh %1$s \"SELECT mock_function()\"$$, :'TEST_DBNAME_2') as command_to_run \\gset\nWARNING:  mock post_analyze_hook \"mock-6\"\n:command_to_run\nWARNING:  mock init \"mock-6\"\nWARNING:  mock post_analyze_hook \"mock-6\"\nFATAL:  extension \"timescaledb\" version mismatch: shared library version mock-5; SQL version mock-6\nserver closed the connection unexpectedly\n\tThis probably means the server terminated abnormally\n\tbefore or while processing the request.\nconnection to server was lost\nSELECT * FROM test.extension;\nWARNING:  mock post_analyze_hook \"mock-6\"\n                                      List of installed extensions\n    Name     | Version |   Schema   |                            Description                            \n-------------+---------+------------+-------------------------------------------------------------------\n plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language\n timescaledb | mock-6  | public     | Enables scalable inserts and complex queries for time-series data\n(2 rows)\n\n--TEST: create extension when old .so already loaded\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n--force load of extension with (SELECT * FROM test.extension;)\nSELECT * FROM test.extension;\nWARNING:  mock init \"mock-1\"\nWARNING:  mock post_analyze_hook \"mock-1\"\n                                      List of installed extensions\n    Name     | Version |   Schema   |                            Description                            \n-------------+---------+------------+-------------------------------------------------------------------\n plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language\n timescaledb | mock-1  | public     | Enables scalable inserts and complex queries for time-series data\n(2 rows)\n\nDROP EXTENSION timescaledb;\nWARNING:  mock post_analyze_hook \"mock-1\"\nSELECT * FROM test.extension;\n                 List of installed extensions\n  Name   | Version |   Schema   |         Description          \n---------+---------+------------+------------------------------\n plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language\n(1 row)\n\n\\set ON_ERROR_STOP 0\nCREATE EXTENSION timescaledb VERSION 'mock-2';\nERROR:  extension \"timescaledb\" has already been loaded with another version\n\\set ON_ERROR_STOP 1\nSELECT * FROM test.extension;\n                 List of installed extensions\n  Name   | Version |   Schema   |         Description          \n---------+---------+------------+------------------------------\n plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language\n(1 row)\n\n--can create in a new session.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE EXTENSION timescaledb VERSION 'mock-2';\nWARNING:  mock init \"mock-2\"\nSELECT * FROM test.extension;\nWARNING:  mock post_analyze_hook \"mock-2\"\n                                      List of installed extensions\n    Name     | Version |   Schema   |                            Description                            \n-------------+---------+------------+-------------------------------------------------------------------\n plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language\n timescaledb | mock-2  | public     | Enables scalable inserts and complex queries for time-series data\n(2 rows)\n\n--make sure parallel workers started after a 'DISCARD ALL' work\nCREATE TABLE test (i int, j double precision);\nWARNING:  mock post_analyze_hook \"mock-2\"\nINSERT INTO test SELECT x, x+0.1 FROM generate_series(1,100) AS x;\nWARNING:  mock post_analyze_hook \"mock-2\"\nDISCARD ALL;\nWARNING:  mock post_analyze_hook \"mock-2\"\nSELECT set_config(CASE WHEN current_setting('server_version_num')::int < 160000 THEN 'force_parallel_mode' ELSE 'debug_parallel_query' END,'on', false);\nWARNING:  mock post_analyze_hook \"mock-2\"\n set_config \n------------\n on\n(1 row)\n\nSET max_parallel_workers_per_gather = 1;\nWARNING:  mock post_analyze_hook \"mock-2\"\nSELECT count(*) FROM test;\nWARNING:  mock post_analyze_hook \"mock-2\"\nWARNING:  mock init \"mock-2\"\n count \n-------\n   100\n(1 row)\n\n-- clean up additional database\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nDROP DATABASE :\"TEST_DBNAME_2\" WITH (FORCE);\nWARNING:  mock init \"mock-2\"\nWARNING:  mock post_analyze_hook \"mock-2\"\n"
  },
  {
    "path": "test/expected/loader-tsl.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\set TEST_DBNAME_2 :TEST_DBNAME _2\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE DATABASE :\"TEST_DBNAME_2\";\nDROP EXTENSION timescaledb;\n--no extension\nSELECT * FROM test.extension;\n  Name   | Version |   Schema   |         Description          \n---------+---------+------------+------------------------------\n plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language\n\nSELECT 1;\n ?column? \n----------\n        1\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE EXTENSION timescaledb VERSION 'mock-1';\nWARNING:  mock init \"mock-1\"\nSELECT 1;\nWARNING:  mock post_analyze_hook \"mock-1\"\n ?column? \n----------\n        1\n\nSELECT * FROM test.extension;\nWARNING:  mock post_analyze_hook \"mock-1\"\n    Name     | Version |   Schema   |                            Description                            \n-------------+---------+------------+-------------------------------------------------------------------\n plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language\n timescaledb | mock-1  | public     | Enables scalable inserts and complex queries for time-series data\n\nCREATE EXTENSION IF NOT EXISTS timescaledb VERSION 'mock-1';\nWARNING:  mock post_analyze_hook \"mock-1\"\nNOTICE:  extension \"timescaledb\" already exists, skipping\nCREATE EXTENSION IF NOT EXISTS timescaledb VERSION 'mock-2';\nWARNING:  mock post_analyze_hook \"mock-1\"\nNOTICE:  extension \"timescaledb\" already exists, skipping\nDROP EXTENSION timescaledb;\nWARNING:  mock post_analyze_hook \"mock-1\"\n\\set ON_ERROR_STOP 0\n--test that we cannot accidentally load another library version\nCREATE EXTENSION IF NOT EXISTS timescaledb VERSION 'mock-2';\nERROR:  extension \"timescaledb\" has already been loaded with another version\n\\set ON_ERROR_STOP 1\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n--no extension\nSELECT * FROM test.extension;\n  Name   | Version |   Schema   |         Description          \n---------+---------+------------+------------------------------\n plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language\n\nSELECT 1;\n ?column? \n----------\n        1\n\nCREATE EXTENSION timescaledb VERSION 'mock-1';\nWARNING:  mock init \"mock-1\"\n--same backend as create extension;\nSELECT 1;\nWARNING:  mock post_analyze_hook \"mock-1\"\n ?column? \n----------\n        1\n\nSELECT * FROM test.extension;\nWARNING:  mock post_analyze_hook \"mock-1\"\n    Name     | Version |   Schema   |                            Description                            \n-------------+---------+------------+-------------------------------------------------------------------\n plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language\n timescaledb | mock-1  | public     | Enables scalable inserts and complex queries for time-series data\n\n--start new backend;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT 1;\nWARNING:  mock init \"mock-1\"\nWARNING:  mock post_analyze_hook \"mock-1\"\n ?column? \n----------\n        1\n\nSELECT 1;\nWARNING:  mock post_analyze_hook \"mock-1\"\n ?column? \n----------\n        1\n\n--test fn call after load\nSELECT mock_function();\nWARNING:  mock post_analyze_hook \"mock-1\"\nWARNING:  mock function call \"mock-1\"\n mock_function \n---------------\n \n\nSELECT * FROM test.extension;\nWARNING:  mock post_analyze_hook \"mock-1\"\n    Name     | Version |   Schema   |                            Description                            \n-------------+---------+------------+-------------------------------------------------------------------\n plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language\n timescaledb | mock-1  | public     | Enables scalable inserts and complex queries for time-series data\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n--test fn call as first command\nSELECT mock_function();\nWARNING:  mock init \"mock-1\"\nWARNING:  mock post_analyze_hook \"mock-1\"\nWARNING:  mock function call \"mock-1\"\n mock_function \n---------------\n \n\n--use guc to prevent loading\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET timescaledb.disable_load = 'on';\nSELECT 1;\n ?column? \n----------\n        1\n\nSELECT 1;\n ?column? \n----------\n        1\n\nSET timescaledb.disable_load = 'off';\nSELECT 1;\nWARNING:  mock init \"mock-1\"\nWARNING:  mock post_analyze_hook \"mock-1\"\n ?column? \n----------\n        1\n\n\\set ON_ERROR_STOP 0\nSET timescaledb.disable_load = 'not bool';\nWARNING:  mock post_analyze_hook \"mock-1\"\nERROR:  parameter \"timescaledb.disable_load\" requires a Boolean value\n\\set ON_ERROR_STOP 1\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nRESET ALL;\nWARNING:  mock init \"mock-1\"\nWARNING:  mock post_analyze_hook \"mock-1\"\nSELECT 1;\nWARNING:  mock post_analyze_hook \"mock-1\"\n ?column? \n----------\n        1\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET timescaledb.disable_load TO DEFAULT;\nSELECT 1;\nWARNING:  mock init \"mock-1\"\nWARNING:  mock post_analyze_hook \"mock-1\"\n ?column? \n----------\n        1\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nRESET timescaledb.disable_load;\nSELECT 1;\nWARNING:  mock init \"mock-1\"\nWARNING:  mock post_analyze_hook \"mock-1\"\n ?column? \n----------\n        1\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET timescaledb.other = 'on';\nWARNING:  mock init \"mock-1\"\nWARNING:  mock post_analyze_hook \"mock-1\"\nSELECT 1;\nWARNING:  mock post_analyze_hook \"mock-1\"\n ?column? \n----------\n        1\n\n\\set ON_ERROR_STOP 0\n--cannot update extension after .so of previous version already loaded\nALTER EXTENSION timescaledb UPDATE TO 'mock-2';\nERROR:  extension \"timescaledb\" cannot be updated after the old version has already been loaded\n\\set ON_ERROR_STOP 1\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\nSELECT * FROM test.extension;\n  Name   | Version |   Schema   |         Description          \n---------+---------+------------+------------------------------\n plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language\n\nCREATE EXTENSION timescaledb VERSION 'mock-1';\nWARNING:  mock init \"mock-1\"\nSELECT * FROM test.extension;\nWARNING:  mock post_analyze_hook \"mock-1\"\n    Name     | Version |   Schema   |                            Description                            \n-------------+---------+------------+-------------------------------------------------------------------\n plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language\n timescaledb | mock-1  | public     | Enables scalable inserts and complex queries for time-series data\n\n--start a new backend to update\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\nALTER EXTENSION timescaledb UPDATE TO 'mock-2';\nWARNING:  mock init \"mock-2\"\nSELECT 1;\nWARNING:  mock post_analyze_hook \"mock-2\"\n ?column? \n----------\n        1\n\nSELECT * FROM test.extension;\nWARNING:  mock post_analyze_hook \"mock-2\"\n    Name     | Version |   Schema   |                            Description                            \n-------------+---------+------------+-------------------------------------------------------------------\n plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language\n timescaledb | mock-2  | public     | Enables scalable inserts and complex queries for time-series data\n\n--drop extension\nDROP EXTENSION timescaledb;\nWARNING:  mock post_analyze_hook \"mock-2\"\nSELECT 1;\n ?column? \n----------\n        1\n\nSELECT * FROM test.extension;\n  Name   | Version |   Schema   |         Description          \n---------+---------+------------+------------------------------\n plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language\n\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\nCREATE EXTENSION timescaledb VERSION 'mock-2';\nWARNING:  mock init \"mock-2\"\nSELECT 1;\nWARNING:  mock post_analyze_hook \"mock-2\"\n ?column? \n----------\n        1\n\nSELECT * FROM test.extension;\nWARNING:  mock post_analyze_hook \"mock-2\"\n    Name     | Version |   Schema   |                            Description                            \n-------------+---------+------------+-------------------------------------------------------------------\n plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language\n timescaledb | mock-2  | public     | Enables scalable inserts and complex queries for time-series data\n\n-- test db 1 still has old version\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT 1;\nWARNING:  mock init \"mock-1\"\nWARNING:  mock post_analyze_hook \"mock-1\"\n ?column? \n----------\n        1\n\nSELECT * FROM test.extension;\nWARNING:  mock post_analyze_hook \"mock-1\"\n    Name     | Version |   Schema   |                            Description                            \n-------------+---------+------------+-------------------------------------------------------------------\n plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language\n timescaledb | mock-1  | public     | Enables scalable inserts and complex queries for time-series data\n\n--try a broken upgrade\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\nSELECT * FROM test.extension;\nWARNING:  mock init \"mock-2\"\nWARNING:  mock post_analyze_hook \"mock-2\"\n    Name     | Version |   Schema   |                            Description                            \n-------------+---------+------------+-------------------------------------------------------------------\n plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language\n timescaledb | mock-2  | public     | Enables scalable inserts and complex queries for time-series data\n\n\\set ON_ERROR_STOP 0\nALTER EXTENSION timescaledb UPDATE TO 'mock-3';\nERROR:  extension \"timescaledb\" cannot be updated after the old version has already been loaded\n\\set ON_ERROR_STOP 1\n--should still be on mock-2\nSELECT 1;\nWARNING:  mock post_analyze_hook \"mock-2\"\n ?column? \n----------\n        1\n\nSELECT * FROM test.extension;\nWARNING:  mock post_analyze_hook \"mock-2\"\n    Name     | Version |   Schema   |                            Description                            \n-------------+---------+------------+-------------------------------------------------------------------\n plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language\n timescaledb | mock-2  | public     | Enables scalable inserts and complex queries for time-series data\n\n--drop extension\nDROP EXTENSION timescaledb;\nWARNING:  mock post_analyze_hook \"mock-2\"\nSELECT 1;\n ?column? \n----------\n        1\n\nSELECT * FROM test.extension;\n  Name   | Version |   Schema   |         Description          \n---------+---------+------------+------------------------------\n plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language\n\n--create extension anew, only upgrade was broken\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\nCREATE EXTENSION timescaledb VERSION 'mock-3';\nWARNING:  mock init \"mock-3\"\nSELECT 1;\nWARNING:  mock post_analyze_hook \"mock-3\"\n ?column? \n----------\n        1\n\nSELECT * FROM test.extension;\nWARNING:  mock post_analyze_hook \"mock-3\"\n    Name     | Version |   Schema   |                            Description                            \n-------------+---------+------------+-------------------------------------------------------------------\n plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language\n timescaledb | mock-3  | public     | Enables scalable inserts and complex queries for time-series data\n\nDROP EXTENSION timescaledb;\nWARNING:  mock post_analyze_hook \"mock-3\"\nSELECT 1;\n ?column? \n----------\n        1\n\n--mismatched version errors\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\n--mock-4 has mismatched versions, so the .so load should be fatal\nSELECT format($$\\! utils/test_fatal_command.sh %1$s \"CREATE EXTENSION timescaledb VERSION 'mock-4'\"$$, :'TEST_DBNAME_2') as command_to_run \\gset\n:command_to_run\nFATAL:  extension \"timescaledb\" version mismatch: shared library version mock-4-mismatch; SQL version mock-4\nserver closed the connection unexpectedly\n\tThis probably means the server terminated abnormally\n\tbefore or while processing the request.\nconnection to server was lost\n--mock-4 not installed.\nSELECT * FROM test.extension;\n  Name   | Version |   Schema   |         Description          \n---------+---------+------------+------------------------------\n plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language\n\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\n--broken version and drop\nCREATE EXTENSION timescaledb VERSION 'mock-broken';\nWARNING:  mock init \"mock-broken\"\n\\set ON_ERROR_STOP 0\n--intentional broken version\nSELECT * FROM test.extension;\nWARNING:  mock post_analyze_hook \"mock-broken\"\nERROR:  mock broken \"mock-broken\"\nSELECT 1;\nWARNING:  mock post_analyze_hook \"mock-broken\"\nERROR:  mock broken \"mock-broken\"\nSELECT 1;\nWARNING:  mock post_analyze_hook \"mock-broken\"\nERROR:  mock broken \"mock-broken\"\n--cannot drop extension; already loaded broken version\nDROP EXTENSION timescaledb;\nWARNING:  mock post_analyze_hook \"mock-broken\"\nERROR:  mock broken \"mock-broken\"\n\\set ON_ERROR_STOP 1\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\n--can drop extension now. Since drop first command.\nDROP EXTENSION timescaledb;\nSELECT * FROM test.extension;\n  Name   | Version |   Schema   |         Description          \n---------+---------+------------+------------------------------\n plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language\n\n--broken version and update to fixed\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\nCREATE EXTENSION timescaledb VERSION 'mock-broken';\nWARNING:  mock init \"mock-broken\"\n\\set ON_ERROR_STOP 0\n--intentional broken version\nSELECT 1;\nWARNING:  mock post_analyze_hook \"mock-broken\"\nERROR:  mock broken \"mock-broken\"\n--cannot update extension; already loaded bad version\nALTER EXTENSION timescaledb UPDATE TO 'mock-5';\nERROR:  extension \"timescaledb\" cannot be updated after the old version has already been loaded\n\\set ON_ERROR_STOP 1\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\n--can update extension now.\nALTER EXTENSION timescaledb UPDATE TO 'mock-5';\nWARNING:  mock init \"mock-5\"\nSELECT 1;\nWARNING:  mock post_analyze_hook \"mock-5\"\n ?column? \n----------\n        1\n\nSELECT mock_function();\nWARNING:  mock post_analyze_hook \"mock-5\"\nWARNING:  mock function call \"mock-5\"\n mock_function \n---------------\n \n\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\nALTER EXTENSION timescaledb UPDATE TO 'mock-6';\nWARNING:  mock init \"mock-6\"\n--The mock-5->mock_6 upgrade is intentionally broken.\n--The mock_function was never changed to point to mock-6 in the update script.\n--Thus mock_function is defined incorrectly to point to the mock-5.so\n--This will now be a FATAL error.\nSELECT format($$\\! utils/test_fatal_command.sh %1$s \"SELECT mock_function()\"$$, :'TEST_DBNAME_2') as command_to_run \\gset\nWARNING:  mock post_analyze_hook \"mock-6\"\n:command_to_run\nWARNING:  mock init \"mock-6\"\nWARNING:  mock post_analyze_hook \"mock-6\"\nFATAL:  extension \"timescaledb\" version mismatch: shared library version mock-5; SQL version mock-6\nserver closed the connection unexpectedly\n\tThis probably means the server terminated abnormally\n\tbefore or while processing the request.\nconnection to server was lost\nSELECT * FROM test.extension;\nWARNING:  mock post_analyze_hook \"mock-6\"\n    Name     | Version |   Schema   |                            Description                            \n-------------+---------+------------+-------------------------------------------------------------------\n plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language\n timescaledb | mock-6  | public     | Enables scalable inserts and complex queries for time-series data\n\n--TEST: create extension when old .so already loaded\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT * FROM test.extension;\nWARNING:  mock init \"mock-1\"\nWARNING:  mock post_analyze_hook \"mock-1\"\n    Name     | Version |   Schema   |                            Description                            \n-------------+---------+------------+-------------------------------------------------------------------\n plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language\n timescaledb | mock-1  | public     | Enables scalable inserts and complex queries for time-series data\n\nDROP EXTENSION timescaledb;\nWARNING:  mock post_analyze_hook \"mock-1\"\nSELECT * FROM test.extension;\n  Name   | Version |   Schema   |         Description          \n---------+---------+------------+------------------------------\n plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language\n\n\\set ON_ERROR_STOP 0\nCREATE EXTENSION timescaledb VERSION 'mock-2';\nERROR:  extension \"timescaledb\" has already been loaded with another version\n\\set ON_ERROR_STOP 1\nSELECT * FROM test.extension;\n  Name   | Version |   Schema   |         Description          \n---------+---------+------------+------------------------------\n plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language\n\n--can create in a new session.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE EXTENSION timescaledb VERSION 'mock-2';\nWARNING:  mock init \"mock-2\"\nSELECT * FROM test.extension;\nWARNING:  mock post_analyze_hook \"mock-2\"\n    Name     | Version |   Schema   |                            Description                            \n-------------+---------+------------+-------------------------------------------------------------------\n plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language\n timescaledb | mock-2  | public     | Enables scalable inserts and complex queries for time-series data\n\n--make sure parallel workers started after a 'DISCARD ALL' work\nCREATE TABLE test (i int, j double precision);\nWARNING:  mock post_analyze_hook \"mock-2\"\nINSERT INTO test SELECT x, x+0.1 FROM generate_series(1,100) AS x;\nWARNING:  mock post_analyze_hook \"mock-2\"\nDISCARD ALL;\nWARNING:  mock post_analyze_hook \"mock-2\"\nSELECT set_config(CASE WHEN current_setting('server_version_num')::int < 160000 THEN 'force_parallel_mode' ELSE 'debug_parallel_query' END,'on', false);\nWARNING:  mock post_analyze_hook \"mock-2\"\n set_config \n------------\n on\n\nSET max_parallel_workers_per_gather = 1;\nWARNING:  mock post_analyze_hook \"mock-2\"\nSELECT count(*) FROM test;\nWARNING:  mock post_analyze_hook \"mock-2\"\nWARNING:  mock init \"mock-2\"\n count \n-------\n   100\n\nCREATE EXTENSION timescaledb_osm VERSION 'mock-1';\nWARNING:  mock post_analyze_hook \"mock-2\"\nWARNING:  mock post_analyze_hook \"mock-2\"\nWARNING:  OSM-mock-1 _PG_init\nWARNING:  got lwlock osm lock\nWARNING:  mock post_analyze_hook \"mock-2\"\nWARNING:  mock post_analyze_hook \"mock-2\"\n-- Test that OSM process utility hook works:  it should see this DROP TABLE.\nDROP TABLE test;\nWARNING:  mock post_analyze_hook \"mock-2\"\nNOTICE:  OSM-mock-1 got DROP TABLE 'test'\n-- clean up additional database\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nDROP DATABASE :\"TEST_DBNAME_2\" WITH (FORCE);\nWARNING:  mock init \"mock-2\"\nWARNING:  mock post_analyze_hook \"mock-2\"\n"
  },
  {
    "path": "test/expected/merge.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- Create target table with location and temperature\nCREATE TABLE target (\n   time        TIMESTAMPTZ       NOT NULL,\n   location    SMALLINT          NOT NULL,\n   temperature DOUBLE PRECISION  NULL,\n   to_be_dropped text\n);\nSELECT create_hypertable(\n  'target',\n  'time',\n  chunk_time_interval => INTERVAL '5 seconds');\n  create_hypertable  \n---------------------\n (1,public,target,t)\n\nINSERT INTO target\nSELECT time, location, 14 as temperature\nFROM generate_series(\n\t'2021-01-01 00:00:00',\n    '2021-01-01 00:00:09',\n    INTERVAL '5 seconds'\n  ) as time,\ngenerate_series(1,4) as location;\n-- This makes sure we have one column with attisdropped and one column\n-- with atthasmissing set to true. These two cases can cause problems\n-- with chunk dispatch execution when merging using a when-clause with\n-- inserts. Unfortunately they are hard to trigger, so this is not a\n-- definitive test.\nALTER TABLE target DROP COLUMN to_be_dropped;\nALTER TABLE target ADD COLUMN val text default 'string -';\n-- Create source table with location and temperature\nCREATE TABLE source (\n   time        TIMESTAMPTZ       NOT NULL,\n   location    SMALLINT          NOT NULL,\n   temperature DOUBLE PRECISION  NULL\n);\nSELECT create_hypertable(\n  'source',\n  'time',\n  chunk_time_interval => INTERVAL '5 seconds');\n  create_hypertable  \n---------------------\n (2,public,source,t)\n\n-- Generate data that overlaps with target table\nINSERT INTO source\nSELECT time, location, 80 as temperature\nFROM generate_series(\n\t'2021-01-01 00:00:05',\n    '2021-01-01 00:00:14',\n    INTERVAL '5 seconds'\n  ) as time,\ngenerate_series(1,4) as location;\n-- Print table/rows/num of chunks\nselect * from target order by time, location asc;\n             time             | location | temperature |   val    \n------------------------------+----------+-------------+----------\n Fri Jan 01 00:00:00 2021 PST |        1 |          14 | string -\n Fri Jan 01 00:00:00 2021 PST |        2 |          14 | string -\n Fri Jan 01 00:00:00 2021 PST |        3 |          14 | string -\n Fri Jan 01 00:00:00 2021 PST |        4 |          14 | string -\n Fri Jan 01 00:00:05 2021 PST |        1 |          14 | string -\n Fri Jan 01 00:00:05 2021 PST |        2 |          14 | string -\n Fri Jan 01 00:00:05 2021 PST |        3 |          14 | string -\n Fri Jan 01 00:00:05 2021 PST |        4 |          14 | string -\n\nselect * from source order by time, location asc;\n             time             | location | temperature \n------------------------------+----------+-------------\n Fri Jan 01 00:00:05 2021 PST |        1 |          80\n Fri Jan 01 00:00:05 2021 PST |        2 |          80\n Fri Jan 01 00:00:05 2021 PST |        3 |          80\n Fri Jan 01 00:00:05 2021 PST |        4 |          80\n Fri Jan 01 00:00:10 2021 PST |        1 |          80\n Fri Jan 01 00:00:10 2021 PST |        2 |          80\n Fri Jan 01 00:00:10 2021 PST |        3 |          80\n Fri Jan 01 00:00:10 2021 PST |        4 |          80\n\n-- CREATE normal PostgreSQL tables\nCREATE TABLE target_pg AS SELECT * FROM target;\nCREATE TABLE source_pg AS SELECT * FROM source;\n-- Merge UPDATE matched rows for normal PG tables\nMERGE INTO target_pg t\nUSING source_pg s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nUPDATE SET temperature = (t.temperature + s.temperature)/2, val = val || ' UPDATED BY MERGE';\n-- Merge UPDATE matched rows for hypertables\nMERGE INTO target t\nUSING source s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nUPDATE SET temperature = (t.temperature + s.temperature)/2, val = val || ' UPDATED BY MERGE';\n-- ensure TARGET PG table and hypertable are same\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n result \n--------\n same\n\n-- Merge DELETE matched rows for normal PG tables\nMERGE INTO target_pg t\nUSING source_pg s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nDELETE;\n-- Merge DELETE matched rows for hypertables\nMERGE INTO target t\nUSING source s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nDELETE;\n-- ensure TARGET PG table and hypertable are same\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n result \n--------\n same\n\n-- clean up tables\nDELETE FROM target_pg;\nDELETE FROM target;\nDELETE FROM source_pg;\nDELETE FROM source;\nINSERT INTO target\nSELECT time, location, 14 as temperature\nFROM generate_series(\n\t'2021-01-01 00:00:00',\n    '2021-01-01 00:00:09',\n    INTERVAL '5 seconds'\n  ) as time,\ngenerate_series(1,4) as location;\nINSERT INTO source\nSELECT time, location, 80 as temperature\nFROM generate_series(\n\t'2021-01-01 00:00:05',\n    '2021-01-01 00:00:14',\n    INTERVAL '5 seconds'\n  ) as time,\ngenerate_series(1,4) as location;\nINSERT INTO target_pg SELECT * FROM target;\nINSERT INTO source_pg SELECT * FROM source;\n-- Merge UPDATE matched rows and INSERT new row for unmatched rows for normal PG tables\nMERGE INTO target_pg t\nUSING source_pg s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nUPDATE SET temperature = (t.temperature + s.temperature)/2, val = val || ' UPDATED BY MERGE'\nWHEN NOT MATCHED THEN\nINSERT (time, location, temperature, val) VALUES (s.time, s.location, s.temperature, 'string - INSERTED BY MERGE');\n-- Merge UPDATE matched rows and INSERT new row for unmatched rows for hypertables\nMERGE INTO target t\nUSING source s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nUPDATE SET temperature = (t.temperature + s.temperature)/2, val = val || ' UPDATED BY MERGE'\nWHEN NOT MATCHED THEN\nINSERT (time, location, temperature, val) VALUES (s.time, s.location, s.temperature, 'string - INSERTED BY MERGE');\n-- ensure TARGET PG table and hypertable are same\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n result \n--------\n same\n\n-- Merge INSERT with constant literals for normal PG tables\nMERGE INTO target_pg t\nUSING source_pg s\nON t.location = 1234\nWHEN NOT MATCHED THEN\nINSERT VALUES ('2021-11-01 00:00:05'::timestamp with time zone, 5, 210, 'string - INSERTED BY MERGE');\n-- Merge INSERT with constant literals for hypertables\nMERGE INTO target t\nUSING source s\nON t.location = 1234\nWHEN NOT MATCHED THEN\nINSERT VALUES ('2021-11-01 00:00:05'::timestamp with time zone, 5, 210, 'string - INSERTED BY MERGE');\n-- ensure TARGET PG table and hypertable are same\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n result \n--------\n same\n\n-- Merge with INSERT/DELETE/UPDATE on PG tables\nMERGE INTO target_pg t\nUSING source_pg s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED AND t.location = 560076 THEN\n UPDATE SET temperature = (t.temperature + s.temperature) * 2, val = val || ' UPDATED BY MERGE'\nWHEN MATCHED AND t.location = 560083 THEN\n DELETE\nWHEN NOT MATCHED THEN\n INSERT (time, location, temperature, val) VALUES (s.time, s.location, s.temperature, 'string - INSERTED BY MERGE');\n-- Merge with INSERT/DELETE/UPDATE on hypertables\nMERGE INTO target t\nUSING source s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED AND t.location = 560076 THEN\n UPDATE SET temperature = (t.temperature + s.temperature) * 2, val = val || ' UPDATED BY MERGE'\nWHEN MATCHED AND t.location = 560083 THEN\n DELETE\nWHEN NOT MATCHED THEN\n INSERT (time, location, temperature, val) VALUES (s.time, s.location, s.temperature, 'string - INSERTED BY MERGE');\n-- ensure TARGET PG table and hypertable are same\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n result \n--------\n same\n\n-- Merge with Subqueries on PG tables\nMERGE INTO target_pg t\nUSING source_pg s\nON t.time = s.time AND t.location > (SELECT count(*) FROM source_pg)\nWHEN MATCHED AND t.temperature = 23 THEN\n UPDATE SET temperature = (SELECT count(*) FROM target_pg) * 2, val = val || ' UPDATED BY MERGE'\nWHEN MATCHED AND t.temperature = 47 THEN\n DELETE\nWHEN NOT MATCHED THEN\n INSERT (time, location, temperature, val) VALUES (s.time, s.location, s.temperature, 'SUBQUERY string - INSERTED BY MERGE');\n-- Merge with Subqueries on hypertables\nMERGE INTO target t\nUSING source s\nON t.time = s.time AND t.location > (SELECT count(*) FROM source)\nWHEN MATCHED AND t.temperature = 23 THEN\n UPDATE SET temperature = (SELECT count(*) FROM target) * 2, val = val || ' UPDATED BY MERGE'\nWHEN MATCHED AND t.temperature = 47 THEN\n DELETE\nWHEN NOT MATCHED THEN\n INSERT (time, location, temperature, val) VALUES (s.time, s.location, s.temperature, 'SUBQUERY string - INSERTED BY MERGE');\n-- ensure TARGET PG table and hypertable are same\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n result \n--------\n same\n\n-- clean up tables\nDELETE FROM target_pg;\nDELETE FROM target;\nDELETE FROM source_pg;\nDELETE FROM source;\n-- TEST with target as hypertable and source as normal PG table\nINSERT INTO target\nSELECT time, location, 14 as temperature\nFROM generate_series(\n\t'2021-01-01 00:00:00',\n    '2021-01-01 00:00:09',\n    INTERVAL '5 seconds'\n  ) as time,\ngenerate_series(1,4) as location;\nINSERT INTO source\nSELECT time, location, 80 as temperature\nFROM generate_series(\n\t'2021-01-01 00:00:05',\n    '2021-01-01 00:00:14',\n    INTERVAL '5 seconds'\n  ) as time,\ngenerate_series(1,4) as location;\nINSERT INTO target_pg SELECT * FROM target;\nINSERT INTO source_pg SELECT * FROM source;\n-- Merge UPDATE matched rows for normal PG tables\nMERGE INTO target_pg t\nUSING source_pg s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nUPDATE SET temperature = (t.temperature + s.temperature)/2, val = val || ' UPDATED BY MERGE';\n-- Merge UPDATE with target as hypertables and source as normal PG tables\nMERGE INTO target t\nUSING source_pg s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nUPDATE SET temperature = (t.temperature + s.temperature)/2, val = val || ' UPDATED BY MERGE';\n-- ensure TARGET PG table and hypertable are same\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n result \n--------\n same\n\n-- Merge DELETE matched rows for normal PG tables\nMERGE INTO target_pg t\nUSING source_pg s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nDELETE;\n-- Merge DELETE with target as hypertables and source as normal PG tables\nMERGE INTO target t\nUSING source_pg s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nDELETE;\n-- ensure TARGET PG table and hypertable are same\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n result \n--------\n same\n\n-- Merge INSERT with constant literals for normal PG tables\nMERGE INTO target_pg t\nUSING source_pg s\nON t.location = 1234\nWHEN NOT MATCHED THEN\nINSERT VALUES ('2021-11-01 00:00:05'::timestamp with time zone, 5, 210, 'string - INSERTED BY MERGE');\n-- Merge INSERT with constant literals for target as hypertables and source as normal PG tables\nMERGE INTO target t\nUSING source s\nON t.location = 1234\nWHEN NOT MATCHED THEN\nINSERT VALUES ('2021-11-01 00:00:05'::timestamp with time zone, 5, 210, 'string - INSERTED BY MERGE');\n-- ensure TARGET PG table and hypertable are same\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n result \n--------\n same\n\n-- Merge with INSERT/DELETE/UPDATE on PG tables\nMERGE INTO target_pg t\nUSING source_pg s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED AND t.temperature = 23 THEN\n UPDATE SET temperature = (t.temperature + s.temperature) * 2, val = val || ' UPDATED BY MERGE'\nWHEN MATCHED AND t.temperature = 47 THEN\n DELETE\nWHEN NOT MATCHED THEN\n INSERT (time, location, temperature, val) VALUES (s.time, s.location, s.temperature, 'string - INSERTED BY MERGE');\n-- Merge with INSERT/DELETE/UPDATE on target as hypertables and source as normal PG tables\nMERGE INTO target t\nUSING source s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED  AND t.temperature = 23 THEN\n UPDATE SET temperature = (t.temperature + s.temperature) * 2, val = val || ' UPDATED BY MERGE'\nWHEN MATCHED  AND t.temperature = 47 THEN\n DELETE\nWHEN NOT MATCHED THEN\n INSERT (time, location, temperature, val) VALUES (s.time, s.location, s.temperature, 'string - INSERTED BY MERGE');\n-- ensure TARGET PG table and hypertable are same\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n result \n--------\n same\n\nDROP TABLE target_pg CASCADE;\nDROP TABLE target CASCADE;\nDROP TABLE source_pg CASCADE;\nDROP TABLE source CASCADE;\n-- test MERGE with source being a PARTITION table\nCREATE TABLE source_pg(\n   id INT NOT NULL,\n   dev INT NOT NULL,\n   value INT,\n   CONSTRAINT cstr_source_pky PRIMARY KEY (id)\n) PARTITION BY LIST (id);\nCREATE TABLE source_1_2_3_4 PARTITION OF source_pg FOR VALUES IN (1,2,3,4);\nCREATE TABLE source_5_6_7_8 PARTITION OF source_pg FOR VALUES IN (5,6,7,8);\nINSERT INTO source_pg SELECT generate_series(1,8), 44,55;\nCREATE TABLE target (\n   ts TIMESTAMP WITH TIME ZONE NOT NULL,\n   id INT NOT NULL,\n   dev INT NOT NULL,\n   FOREIGN KEY (id)  REFERENCES source_pg(id) ON DELETE CASCADE\n);\nSELECT create_hypertable(\n   relation => 'target',\n   time_column_name => 'ts'\n);\n  create_hypertable  \n---------------------\n (3,public,target,t)\n\ninsert into target values ('2023-01-12 00:00:05'::timestamp with time zone, 1,2);\ninsert into target values ('2023-01-12 00:00:10'::timestamp with time zone, 2,2);\ninsert into target values ('2023-01-12 00:00:15'::timestamp with time zone, 3,2);\ninsert into target values ('2023-01-12 00:00:20'::timestamp with time zone, 4,2);\ninsert into target values ('2023-01-14 00:00:25'::timestamp with time zone, 5,2);\ninsert into target values ('2023-01-14 00:00:30'::timestamp with time zone, 6,2);\ninsert into target values ('2023-01-14 00:00:35'::timestamp with time zone, 7,2);\ninsert into target values ('2023-01-14 00:00:40'::timestamp with time zone, 8,2);\nCREATE TABLE target_pg AS SELECT * FROM target;\n-- Merge UPDATE matched rows for normal PG tables\nMERGE INTO target_pg t\nUSING source_pg s\nON t.id = s.id\nWHEN MATCHED THEN\nUPDATE SET dev = (t.dev + s.dev)/2;\n-- Merge UPDATE matched rows for hypertables\nMERGE INTO target t\nUSING source_pg s\nON t.id = s.id\nWHEN MATCHED THEN\nUPDATE SET dev = (t.dev + s.dev)/2;\n-- ensure TARGET PG table and hypertable are same\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n result \n--------\n same\n\n-- Merge DELETE matched rows for normal PG tables\nMERGE INTO target_pg t\nUSING source_pg s\nON t.id = s.id\nWHEN MATCHED THEN\nDELETE;\n-- Merge DELETE matched rows for hypertables\nMERGE INTO target t\nUSING source_pg s\nON t.id = s.id\nWHEN MATCHED THEN\nDELETE;\n-- ensure TARGET PG table and hypertable are same\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n result \n--------\n same\n\n-- clean up tables\nDROP TABLE target_pg CASCADE;\nDROP TABLE target CASCADE;\nDROP TABLE source_pg CASCADE;\n-- test MERGE with hypertables with time and space partitions\nCREATE TABLE target (\n     filler_1 int,\n     filler_2 int,\n     filler_3 int,\n     time timestamptz NOT NULL,\n     device_id int,\n     device_id_peer int,\n     v0 int,\n     v1 float,\n     v2 float,\n     v3 float\n );\nSELECT create_hypertable ('target', 'time', 'device_id', 5);\n  create_hypertable  \n---------------------\n (4,public,target,t)\n\nSELECT add_dimension('target', 'device_id_peer', 5);\n           add_dimension            \n------------------------------------\n (6,public,target,device_id_peer,t)\n\nSELECT add_dimension('target', 'v2', 5);\n     add_dimension      \n------------------------\n (7,public,target,v2,t)\n\nINSERT INTO target (time, device_id, device_id_peer, v0, v1, v2, v3)\n  SELECT time,\n    device_id,\n    0,\n    device_id + 1,\n    device_id + 2,\n    device_id + 0.5,\n    NULL\nFROM generate_series('2000-01-01 0:00:00+0'::timestamptz, '2000-01-05 23:55:00+0', '20m') gtime (time),\n    generate_series(1, 2, 1) gdevice (device_id);\nCREATE TABLE source (\n         filler_1 int,\n         filler_2 int,\n         filler_3 int,\n         time timestamptz NOT NULL,\n         device_id int\n  );\nSELECT create_hypertable ('source', 'time', 'device_id', 3);\n  create_hypertable  \n---------------------\n (5,public,source,t)\n\nINSERT INTO source (time, device_id, filler_2, filler_3, filler_1)\n  SELECT time,\n    device_id,\n    device_id + 134,\n    device_id + 209,\n    device_id + 0.50127\nFROM generate_series('2000-01-01 0:00:00+0'::timestamptz, '2000-01-05 23:55:00+0', '20m') gtime (time),\n    generate_series(1, 5, 1) gdevice (device_id);\n-- create PG tables to compare PG target and hypertable target tables\nCREATE table target_pg as SELECT * FROM target;\nCREATE table source_pg as SELECT * FROM source;\n-- Merge UDPATE matched rows for normal PG tables\nMERGE INTO target_pg t\nUSING source_pg s\nON t.time = s.time AND t.device_id = s.device_id\nWHEN MATCHED THEN\nUPDATE SET filler_2 = s.filler_1 + 100;\n-- Merge UDPATE matched rows for space partitioned hypertables\nMERGE INTO target t\nUSING source s\nON t.time = s.time AND t.device_id = s.device_id\nWHEN MATCHED THEN\nUPDATE SET filler_2 = s.filler_1 + 100;\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n result \n--------\n same\n\n-- Merge DELETE matched rows for normal PG tables\nMERGE INTO target_pg t\n       USING source_pg s\n       ON t.time = s.time AND t.device_id = s.device_id\n       WHEN MATCHED THEN\n       DELETE;\n-- Merge DELETE matched rows for space partitioned hypertables\nMERGE INTO target t\n       USING source s\n       ON t.time = s.time AND t.device_id = s.device_id\n       WHEN MATCHED THEN\n       DELETE;\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n result \n--------\n same\n\n-- Merge INSERT matched rows for normal PG tables\nMERGE INTO target_pg t\n              USING source_pg s\n              ON t.time = s.time AND t.device_id = s.device_id\n              WHEN NOT MATCHED THEN\n              INSERT (filler_1, filler_2, filler_3, time, device_id, device_id_peer, v0, v1, v2, v3) VALUES\n                     (s.filler_1, s.filler_2, s.filler_3, s.time, s.device_id, s.device_id + 10, 1,2,3,4);\n-- Merge INSERT matched rows for space partitioned hypertables\nMERGE INTO target t\n              USING source s\n              ON t.time = s.time AND t.device_id = s.device_id\n              WHEN NOT MATCHED THEN\n              INSERT (filler_1, filler_2, filler_3, time, device_id, device_id_peer, v0, v1, v2, v3) VALUES\n                     (s.filler_1, s.filler_2, s.filler_3, s.time, s.device_id, s.device_id + 10, 1,2,3,4);\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n result \n--------\n same\n\n-- Merge with INSERT/DELETE/UPDATE on PG tables\nMERGE INTO target_pg t\n    USING source_pg s\n        ON t.time = s.time AND t.device_id = s.device_id\n              WHEN MATCHED AND t.device_id_peer = 2 THEN\n                  UPDATE SET filler_2 = s.filler_1 + s.filler_2 + s.filler_3 + 100\n              WHEN MATCHED AND t.device_id_peer = 7 THEN\n                  DELETE\n              WHEN NOT MATCHED THEN\n                  INSERT (filler_1, filler_2, filler_3, time, device_id, device_id_peer, v0, v1, v2, v3) VALUES\n                         (s.filler_1, s.filler_2, s.filler_3, s.time, s.device_id, s.device_id + 10, 1,2,3,4);\n-- Merge with INSERT/DELETE/UPDATE on space partitioned hypertables\nMERGE INTO target t\n    USING source s\n        ON t.time = s.time AND t.device_id = s.device_id\n              WHEN MATCHED AND t.device_id_peer = 2 THEN\n                  UPDATE SET filler_2 = s.filler_1 + s.filler_2 + s.filler_3 + 100\n              WHEN MATCHED AND t.device_id_peer = 7 THEN\n                  DELETE\n              WHEN NOT MATCHED THEN\n                  INSERT (filler_1, filler_2, filler_3, time, device_id, device_id_peer, v0, v1, v2, v3) VALUES\n                         (s.filler_1, s.filler_2, s.filler_3, s.time, s.device_id, s.device_id + 10, 1,2,3,4);\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n result \n--------\n same\n\n-- clean up tables\nDROP TABLE target_pg CASCADE;\nDROP TABLE target CASCADE;\nDROP TABLE source_pg CASCADE;\nDROP TABLE source CASCADE;\n-- TEST with parition column place after similar data type column\nCREATE TABLE target (\n     filler_1 int,\n     filler_2 int,\n     filler_3 int,\n     time timestamptz NOT NULL,\n     device_id int,\n     device_id_peer int,\n     v0 int,\n     v1 float,\n     v2 float,\n     v3 float,\n     partition_column TIMESTAMPTZ NOT NULL\n );\nSELECT create_hypertable ('target', 'partition_column');\n  create_hypertable  \n---------------------\n (6,public,target,t)\n\nINSERT INTO target (time, device_id, device_id_peer, v0, v1, v2, v3, partition_column)\n  SELECT time,\n    device_id,\n    0,\n    device_id + 1,\n    device_id + 2,\n    device_id + 0.5,\n    NULL,\n    time + interval '10m'\nFROM generate_series('2000-01-01 0:00:00+0'::timestamptz, '2000-01-05 23:55:00+0', '20m') gtime (time),\n    generate_series(1, 2, 1) gdevice (device_id);\nCREATE TABLE source (\n         filler_1 int,\n         filler_2 int,\n         filler_3 int,\n         time timestamptz NOT NULL,\n         device_id int\n  );\nSELECT create_hypertable ('source', 'time', 'device_id', 3);\n  create_hypertable  \n---------------------\n (7,public,source,t)\n\nINSERT INTO source (time, device_id, filler_2, filler_3, filler_1)\n  SELECT time,\n    device_id,\n    device_id + 134,\n    device_id + 209,\n    device_id + 0.50127\nFROM generate_series('2000-01-01 0:00:00+0'::timestamptz, '2000-01-05 23:55:00+0', '20m') gtime (time),\n    generate_series(1, 5, 1) gdevice (device_id);\n-- create PG tables to compare PG target and hypertable target tables\nCREATE table target_pg as SELECT * FROM target;\nMERGE INTO target_pg t\nUSING source s\nON t.time = s.time AND t.device_id = s.device_id\nWHEN NOT MATCHED THEN\nINSERT (time, device_id, device_id_peer, v0, v1, v2, v3, partition_column) VALUES\n('2010-01-06 05:30:00+05:30', 23, 2, 11, 22, 33, 44, '2023-01-06 05:33:00+05:30');\nMERGE INTO target t\nUSING source s\nON t.time = s.time AND t.device_id = s.device_id\nWHEN NOT MATCHED THEN\nINSERT (time, device_id, device_id_peer, v0, v1, v2, v3, partition_column) VALUES\n('2010-01-06 05:30:00+05:30', 23, 2, 11, 22, 33, 44, '2023-01-06 05:33:00+05:30');\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n result \n--------\n same\n\nMERGE INTO target_pg t\nUSING source s\nON t.time = s.time AND t.device_id = s.device_id\nWHEN  MATCHED THEN\nDELETE;\nMERGE INTO target t\nUSING source s\nON t.time = s.time AND t.device_id = s.device_id\nWHEN  MATCHED THEN\nDELETE;\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n result \n--------\n same\n\nMERGE INTO target_pg t\n              USING source s\n              ON t.time = s.time AND t.device_id = s.device_id\n              WHEN NOT MATCHED THEN\n              INSERT (filler_1, filler_2, filler_3, time, device_id, device_id_peer, v0, v1, v2, v3) VALUES\n                     (s.filler_1, s.filler_2, s.filler_3, s.time, s.device_id, s.device_id + 10, 1,2,3,4);\n-- time dimension column is NULL, this will report an null constraint violation error\n\\set ON_ERROR_STOP 0\nMERGE INTO target t\n              USING source s\n              ON t.time = s.time AND t.device_id = s.device_id\n              WHEN NOT MATCHED THEN\n              INSERT (filler_1, filler_2, filler_3, time, device_id, device_id_peer, v0, v1, v2, v3) VALUES\n                     (s.filler_1, s.filler_2, s.filler_3, s.time, s.device_id, s.device_id + 10, 1,2,3,4);\nERROR:  NULL value in column \"partition_column\" violates not-null constraint\n\\set ON_ERROR_STOP 1\nDROP TABLE target CASCADE;\nDROP TABLE target_pg CASCADE;\nDROP TABLE source CASCADE;\n-- TEST with target table have CHECK constraints\nCREATE TABLE target (\n   time        TIMESTAMPTZ       NOT NULL,\n   location    SMALLINT          NOT NULL,\n   temperature DOUBLE PRECISION  NULL CHECK (temperature > 10),\n   val text default 'string -'\n);\nSELECT create_hypertable(\n  'target',\n  'time',\n  chunk_time_interval => INTERVAL '5 seconds');\n  create_hypertable  \n---------------------\n (8,public,target,t)\n\nINSERT INTO target\nSELECT time, location, 14 as temperature\nFROM generate_series(\n\t'2021-01-01 00:00:00',\n    '2021-01-01 00:00:09',\n    INTERVAL '5 seconds'\n  ) as time,\ngenerate_series(1,4) as location;\n-- Create source table with location and temperature\nCREATE TABLE source (\n   time        TIMESTAMPTZ       NOT NULL,\n   location    SMALLINT          NOT NULL,\n   temperature DOUBLE PRECISION  NULL\n);\n-- Generate data that overlaps with target table\nINSERT INTO source\nSELECT time, location, 80 as temperature\nFROM generate_series(\n\t'2021-01-01 00:00:05',\n    '2021-01-01 00:00:14',\n    INTERVAL '5 seconds'\n  ) as time,\ngenerate_series(1,4) as location;\n-- CREATE normal PostgreSQL tables\nCREATE TABLE target_pg AS SELECT * FROM target;\n-- Merge UPDATE/DELETE with DO NOTHING on pg tables\nMERGE INTO target_pg t\nUSING source s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nDO NOTHING\nWHEN NOT MATCHED THEN\nDO NOTHING;\n-- Merge UPDATE/DELETE with DO NOTHING on hypertable\nMERGE INTO target t\nUSING source s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nDO NOTHING\nWHEN NOT MATCHED THEN\nDO NOTHING;\n-- Error cases for Merge\n\\set ON_ERROR_STOP 0\n-- Merge UPDATE should fail with check constraint violation\nMERGE INTO target_pg t\nUSING source s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nUPDATE SET temperature = 8, val = val || ' UPDATED BY MERGE';\n-- Merge UPDATE should fail with check constraint violation\nMERGE INTO target t\nUSING source s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nUPDATE SET temperature = 8, val = val || ' UPDATED BY MERGE';\nERROR:  new row for relation \"_hyper_8_24_chunk\" violates check constraint \"target_temperature_check\"\n-- Merge error with unreachable WHEN clause on pg tables\nMERGE INTO target_pg t\nUSING source s\nON t.time = s.time AND t.location != s.location\nWHEN MATCHED THEN\nUPDATE SET temperature = 8, val = val || ' UPDATED BY MERGE'\nWHEN MATCHED AND t.time < now() THEN\nDELETE\nWHEN NOT MATCHED THEN\nDO NOTHING;\nERROR:  unreachable WHEN clause specified after unconditional WHEN clause\n-- Merge error with unreachable WHEN clause on hypertable\nMERGE INTO target t\nUSING source s\nON t.time = s.time AND t.location != s.location\nWHEN MATCHED THEN\nUPDATE SET temperature = 8, val = val || ' UPDATED BY MERGE'\nWHEN MATCHED AND t.time < now() THEN\nDELETE\nWHEN NOT MATCHED THEN\nDO NOTHING;\nERROR:  unreachable WHEN clause specified after unconditional WHEN clause\n-- Merge error with unknown action in MERGE WHEN MATCHED clause on pg tables\nMERGE INTO target_pg t\nUSING source s\nON t.time = s.time AND t.location != s.location\nWHEN MATCHED THEN\nSELECT 1;\nERROR:  syntax error at or near \"SELECT\" at character 105\n-- Merge error with unknown action in MERGE WHEN MATCHED clause on hypertable\nMERGE INTO target t\nUSING source s\nON t.time = s.time AND t.location != s.location\nWHEN MATCHED THEN\nSELECT 1;\nERROR:  syntax error at or near \"SELECT\" at character 102\n-- Merge error cannot affect row a second time on pg tables\nMERGE INTO target_pg t\nUSING source s\nON  t.location = s.location\nWHEN MATCHED THEN\nUPDATE SET temperature = 28, val = val || ' UPDATED BY MERGE';\nERROR:  MERGE command cannot affect row a second time\n-- Merge error cannot affect row a second time on hypertable\nMERGE INTO target t\nUSING source s\nON  t.location = s.location\nWHEN MATCHED THEN\nUPDATE SET temperature = 28, val = val || ' UPDATED BY MERGE';\nERROR:  MERGE command cannot affect row a second time\n\\set ON_ERROR_STOP 1\nDROP TABLE target CASCADE;\nDROP TABLE target_pg CASCADE;\nDROP TABLE source CASCADE;\n-- TEST for PERMISSIONS\nCREATE USER priv_user;\nCREATE USER non_priv_user;\nCREATE TABLE target (\n    value DOUBLE PRECISION NOT NULL,\n    time TIMESTAMPTZ NOT NULL\n);\nSELECT table_name FROM create_hypertable(\n                            'target'::regclass,\n                            'time'::name, chunk_time_interval=>interval '8 hours',\n                            create_default_indexes=> false);\n table_name \n------------\n target\n\nSELECT '2022-10-10 14:33:44.1234+05:30' as start_date \\gset\nINSERT INTO target (value, time)\n  SELECT 1,t from generate_series(:'start_date'::timestamptz, :'start_date'::timestamptz + interval '1 day', '5m') t cross join\n    generate_series(1,3) s;\nCREATE TABLE source (\n        time TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL,\n        value DOUBLE PRECISION NOT NULL\n    );\nSELECT table_name FROM create_hypertable(\n                                'source'::regclass,\n                                'time'::name, chunk_time_interval=>interval '6 hours',\n                                create_default_indexes=> false);\n table_name \n------------\n source\n\nALTER TABLE target OWNER TO priv_user;\nALTER TABLE source OWNER TO priv_user;\nGRANT SELECT ON source TO non_priv_user;\nSET SESSION AUTHORIZATION non_priv_user;\n\\set ON_ERROR_STOP 0\n-- non_priv_user does not have UPDATE privilege on target table\nMERGE INTO target\nUSING source\nON target.time = source.time\nWHEN MATCHED THEN\n\tUPDATE SET value = 0;\nERROR:  permission denied for table target\n-- non_priv_user does not have DELETE privilege on target table\nMERGE INTO target\nUSING source\nON target.time = source.time\nWHEN MATCHED THEN\n\tDELETE;\nERROR:  permission denied for table target\n-- non_priv_user does not have INSERT privilege on target table\nMERGE INTO target\nUSING source\nON target.time = source.time\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (10, '2023-01-15 00:00:10'::timestamp with time zone);\nERROR:  permission denied for table target\n\\set ON_ERROR_STOP 1\nRESET SESSION AUTHORIZATION;\nDROP TABLE target;\nDROP TABLE source;\nDROP USER priv_user;\nDROP USER non_priv_user;\n"
  },
  {
    "path": "test/expected/metadata.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_uuid() RETURNS UUID\n    AS :MODULE_PATHNAME, 'ts_test_uuid' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_exported_uuid() RETURNS UUID\n    AS :MODULE_PATHNAME, 'ts_test_exported_uuid' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_install_timestamp() RETURNS TIMESTAMPTZ\n    AS :MODULE_PATHNAME, 'ts_test_install_timestamp' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nINSERT INTO _timescaledb_catalog.metadata (key, value, include_in_telemetry) SELECT 'metadata_test', 'FOO', TRUE;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n-- uuid and install_timestamp should already be in the table before we generate\nSELECT COUNT(*) from _timescaledb_catalog.metadata;\n count \n-------\n     4\n\nSELECT _timescaledb_internal.test_uuid() as uuid_1 \\gset\nSELECT _timescaledb_internal.test_exported_uuid() as uuid_ex_1 \\gset\nSELECT _timescaledb_internal.test_install_timestamp() as timestamp_1 \\gset\n-- Check that there is exactly 1 UUID row\nSELECT COUNT(*) from _timescaledb_catalog.metadata where key='uuid';\n count \n-------\n     1\n\n-- Check that exported_uuid and timestamp are also generated\nSELECT COUNT(*) from _timescaledb_catalog.metadata where key='exported_uuid';\n count \n-------\n     1\n\nSELECT COUNT(*) from _timescaledb_catalog.metadata where key='install_timestamp';\n count \n-------\n     1\n\n-- Make sure that the UUID is idempotent\nSELECT _timescaledb_internal.test_uuid() = :'uuid_1' as uuids_equal;\n uuids_equal \n-------------\n t\n\nSELECT _timescaledb_internal.test_uuid() = :'uuid_1' as uuids_equal;\n uuids_equal \n-------------\n t\n\n-- Also make sure install_time and exported_uuid are idempotent\nSELECT _timescaledb_internal.test_exported_uuid() = :'uuid_ex_1' as exported_uuids_equal;\n exported_uuids_equal \n----------------------\n t\n\nSELECT _timescaledb_internal.test_exported_uuid() = :'uuid_ex_1' as exported_uuids_equal;\n exported_uuids_equal \n----------------------\n t\n\nSELECT _timescaledb_internal.test_install_timestamp() = :'timestamp_1' as timestamps_equal;\n timestamps_equal \n------------------\n t\n\nSELECT _timescaledb_internal.test_install_timestamp() = :'timestamp_1' as timestamps_equal;\n timestamps_equal \n------------------\n t\n\n-- Now make sure that only the exported_uuid is exported on pg_dump\n\\c postgres :ROLE_SUPERUSER\n\\setenv PGOPTIONS '--client-min-messages=warning'\n\\! utils/pg_dump_aux_dump.sh dump/instmeta.sql\nALTER DATABASE :TEST_DBNAME SET timescaledb.restoring='on';\n-- Redirect to /dev/null to suppress NOTICE\n\\! utils/pg_dump_aux_restore.sh dump/instmeta.sql\nALTER DATABASE :TEST_DBNAME SET timescaledb.restoring='off';\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n-- Should have all 3 row, because pg_dump includes the insertion of uuid and timestamp.\nSELECT COUNT(*) FROM _timescaledb_catalog.metadata;\n count \n-------\n     5\n\n-- Verify that this is the old exported_uuid\nSELECT _timescaledb_internal.test_exported_uuid() = :'uuid_ex_1' as exported_uuids_equal;\n exported_uuids_equal \n----------------------\n t\n\n-- Verify that the uuid is new\nSELECT _timescaledb_internal.test_uuid() = :'uuid_1' as exported_uuids_diff;\n exported_uuids_diff \n---------------------\n f\n\n-- Verify that the install_timestamp got restored\nSELECT _timescaledb_internal.test_install_timestamp() = :'timestamp_1' as timestamps_equal;\n timestamps_equal \n------------------\n t\n\nSELECT * FROM _timescaledb_catalog.metadata WHERE key = 'metadata_test';\n      key      | value | include_in_telemetry \n---------------+-------+----------------------\n metadata_test | FOO   | t\n\n-- check metadata version matches expected value\nSELECT x.extversion = m.value AS \"version match\"\nFROM pg_extension x\nJOIN _timescaledb_catalog.metadata m ON m.key='timescaledb_version'\nWHERE x.extname='timescaledb';\n version match \n---------------\n t\n\n-- test version check in post_restore\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nUPDATE _timescaledb_catalog.metadata SET value = '1.2.3' WHERE key = 'timescaledb_version';\n\\set ON_ERROR_STOP 0\n-- set verbosity to sqlstate to suppress version dependant error message\n\\set VERBOSITY sqlstate\nSELECT timescaledb_post_restore();\nERROR:  P0001\n\\set ON_ERROR_STOP 1\nUPDATE _timescaledb_catalog.metadata m SET value = x.extversion FROM pg_extension x WHERE m.key = 'timescaledb_version' AND x.extname='timescaledb';\nSELECT timescaledb_post_restore();\n timescaledb_post_restore \n--------------------------\n t\n\n"
  },
  {
    "path": "test/expected/multi_transaction_index.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE index_test(id serial, time timestamptz, device integer, temp float);\nSELECT * FROM test.show_columns('index_test');\n Column |           Type           | NotNull \n--------+--------------------------+---------\n id     | integer                  | t\n time   | timestamp with time zone | f\n device | integer                  | f\n temp   | double precision         | f\n\n-- Test that we can handle difference in attnos across hypertable and\n-- chunks by dropping the ID column\nALTER TABLE index_test DROP COLUMN id;\nSELECT * FROM test.show_columns('index_test');\n Column |           Type           | NotNull \n--------+--------------------------+---------\n time   | timestamp with time zone | f\n device | integer                  | f\n temp   | double precision         | f\n\n-- No pre-existing UNIQUE index, so partitioning on two columns should work\nSELECT create_hypertable('index_test', 'time', 'device', 2);\n    create_hypertable    \n-------------------------\n (1,public,index_test,t)\n\nINSERT INTO index_test VALUES ('2017-01-20T09:00:01', 1, 17.5);\n\\set ON_ERROR_STOP 0\n-- cannot create a UNIQUE index with transaction_per_chunk\nCREATE UNIQUE INDEX index_test_time_device_idx ON index_test (time) WITH (timescaledb.transaction_per_chunk);\nERROR:  cannot use timescaledb.transaction_per_chunk with UNIQUE or PRIMARY KEY\nCREATE UNIQUE INDEX index_test_time_device_idx ON index_test (time, device) WITH(timescaledb.transaction_per_chunk);\nERROR:  cannot use timescaledb.transaction_per_chunk with UNIQUE or PRIMARY KEY\n\\set ON_ERROR_STOP 1\nCREATE INDEX index_test_time_device_idx ON index_test (time, device) WITH (timescaledb.transaction_per_chunk);\n-- Regular index need not cover all partitioning columns\nCREATE INDEX ON index_test (time, temp) WITH (timescaledb.transaction_per_chunk);\n-- Create another chunk\nINSERT INTO index_test VALUES ('2017-04-20T09:00:01', 1, 17.5);\n-- New index should have been recursed to chunks\nSELECT * FROM test.show_indexes('index_test');\n           Index            |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------+---------------+------+--------+---------+-----------+------------\n index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n index_test_time_device_idx | {time,device} |      | f      | f       | f         | \n index_test_time_idx        | {time}        |      | f      | f       | f         | \n index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk') ORDER BY 1,2;\n                 Table                  |                               Index                               |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------+-------------------------------------------------------------------+---------------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_index_test_time_idx        | {time}        |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_index_test_time_device_idx | {time,device} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_index_test_time_idx        | {time}        |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_index_test_time_device_idx | {time,device} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n\nALTER INDEX index_test_time_idx RENAME TO index_test_time_idx2;\n-- Metadata and index should have changed name\nSELECT * FROM test.show_indexes('index_test');\n           Index            |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------+---------------+------+--------+---------+-----------+------------\n index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n index_test_time_device_idx | {time,device} |      | f      | f       | f         | \n index_test_time_idx2       | {time}        |      | f      | f       | f         | \n index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk') ORDER BY 1,2;\n                 Table                  |                               Index                               |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------+-------------------------------------------------------------------+---------------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_index_test_time_idx2       | {time}        |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_index_test_time_device_idx | {time,device} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_index_test_time_idx2       | {time}        |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_index_test_time_device_idx | {time,device} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n\nDROP INDEX index_test_time_idx2;\nDROP INDEX index_test_time_device_idx;\n-- Index should have been dropped\nSELECT * FROM test.show_indexes('index_test');\n           Index            |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------+---------------+------+--------+---------+-----------+------------\n index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n                 Table                  |                               Index                               |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------+-------------------------------------------------------------------+---------------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n\n-- Create index with long name to see how this is handled on chunks\nCREATE INDEX a_hypertable_index_with_a_very_very_long_name_that_truncates ON index_test (time ASC, temp DESC) WITH (timescaledb.transaction_per_chunk);\nCREATE INDEX a_hypertable_index_with_a_very_very_long_name_that_truncates_2 ON index_test (time DESC, temp ASC) WITH (timescaledb.transaction_per_chunk);\nSELECT * FROM test.show_indexes('index_test');\n                             Index                              |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------------------------------+---------------+------+--------+---------+-----------+------------\n a_hypertable_index_with_a_very_very_long_name_that_truncates   | {time,temp}   |      | f      | f       | f         | \n a_hypertable_index_with_a_very_very_long_name_that_truncates_2 | {time,temp}   |      | f      | f       | f         | \n index_test_device_time_idx                                     | {device,time} |      | f      | f       | f         | \n index_test_time_temp_idx                                       | {time,temp}   |      | f      | f       | f         | \n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n                 Table                  |                                         Index                                         |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------+---------------------------------------------------------------------------------------+---------------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_index_test_device_time_idx                     | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_index_test_time_temp_idx                       | {time,temp}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_a_hypertable_index_with_a_very_very_long_name_ | {time,temp}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_a_hypertable_index_with_a_very_very_long_nam_1 | {time,temp}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_index_test_device_time_idx                     | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_index_test_time_temp_idx                       | {time,temp}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_a_hypertable_index_with_a_very_very_long_name_ | {time,temp}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_a_hypertable_index_with_a_very_very_long_nam_1 | {time,temp}   |      | f      | f       | f         | \n\nDROP INDEX a_hypertable_index_with_a_very_very_long_name_that_truncates;\nDROP INDEX a_hypertable_index_with_a_very_very_long_name_that_truncates_2;\nSELECT * FROM test.show_indexes('index_test');\n           Index            |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------+---------------+------+--------+---------+-----------+------------\n index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n                 Table                  |                               Index                               |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------+-------------------------------------------------------------------+---------------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n\nSELECT * FROM test.show_indexes('index_test');\n           Index            |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------+---------------+------+--------+---------+-----------+------------\n index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n                 Table                  |                               Index                               |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------+-------------------------------------------------------------------+---------------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n\n-- Add constraint index\nALTER TABLE index_test ADD UNIQUE (time, device);\nSELECT * FROM test.show_indexes('index_test');\n           Index            |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------+---------------+------+--------+---------+-----------+------------\n index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n index_test_time_device_key | {time,device} |      | t      | f       | f         | \n index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n                 Table                  |                               Index                               |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------+-------------------------------------------------------------------+---------------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal.\"1_1_index_test_time_device_key\"            | {time,device} |      | t      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_index_test_device_time_idx | {device,time} |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_index_test_time_temp_idx   | {time,temp}   |      | f      | f       | f         | \n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal.\"2_2_index_test_time_device_key\"            | {time,device} |      | t      | f       | f         | \n\n-- Constraints are added to chunk_constraint table.\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\n chunk_id | dimension_slice_id |        constraint_name         | hypertable_constraint_name \n----------+--------------------+--------------------------------+----------------------------\n        1 |                  1 | constraint_1                   | \n        1 |                  2 | constraint_2                   | \n        2 |                  3 | constraint_3                   | \n        2 |                  2 | constraint_2                   | \n        1 |                    | 1_1_index_test_time_device_key | index_test_time_device_key\n        2 |                    | 2_2_index_test_time_device_key | index_test_time_device_key\n\nDROP TABLE index_test;\n-- Test that indexes are planned correctly\nCREATE TABLE index_expr_test(id serial, time timestamptz, temp float, meta int);\nselect create_hypertable('index_expr_test', 'time');\n      create_hypertable       \n------------------------------\n (2,public,index_expr_test,t)\n\n-- Screw up the attribute numbers\nALTER TABLE index_expr_test DROP COLUMN id;\nCREATE INDEX ON index_expr_test (meta) WITH (timescaledb.transaction_per_chunk);\nINSERT INTO index_expr_test VALUES ('2017-01-20T09:00:01', 17.5, 1);\nINSERT INTO index_expr_test VALUES ('2017-01-20T09:00:01', 17.5, 2);\nSET enable_seqscan TO false;\nSET enable_bitmapscan TO false;\nEXPLAIN (verbose, buffers off, costs off)\nSELECT * FROM index_expr_test WHERE meta = 1;\n--- QUERY PLAN ---\n Index Scan using _hyper_2_3_chunk_index_expr_test_meta_idx on _timescaledb_internal._hyper_2_3_chunk\n   Output: _hyper_2_3_chunk.\"time\", _hyper_2_3_chunk.temp, _hyper_2_3_chunk.meta\n   Index Cond: (_hyper_2_3_chunk.meta = 1)\n\nSELECT * FROM index_expr_test WHERE meta = 1;\n             time             | temp | meta \n------------------------------+------+------\n Fri Jan 20 09:00:01 2017 PST | 17.5 |    1\n\nSET enable_seqscan TO default;\nSET enable_bitmapscan TO default;\n\\set ON_ERROR_STOP 0\n-- cannot create a transaction_per_chunk index within a transaction block\nBEGIN;\nCREATE INDEX ON index_expr_test (temp) WITH (timescaledb.transaction_per_chunk);\nERROR:  CREATE INDEX ... WITH (timescaledb.transaction_per_chunk) cannot run inside a transaction block\nROLLBACK;\n\\set ON_ERROR_STOP 1\nDROP TABLE index_expr_test CASCADE;\nCREATE TABLE partial_index_test(time INTEGER);\nSELECT create_hypertable('partial_index_test', 'time', chunk_time_interval => 1, create_default_indexes => false);\n        create_hypertable        \n---------------------------------\n (3,public,partial_index_test,t)\n\n-- create 3 chunks\nINSERT INTO partial_index_test(time) SELECT generate_series(0, 2);\nselect * from partial_index_test order by 1;\n time \n------\n    0\n    1\n    2\n\n-- create indexes on only 1 of the chunks\nCREATE INDEX ON partial_index_test (time) WITH (timescaledb.transaction_per_chunk, timescaledb.max_chunks='1');\nSELECT * FROM test.show_indexes('partial_index_test');\n            Index            | Columns | Expr | Unique | Primary | Exclusion | Tablespace \n-----------------------------+---------+------+--------+---------+-----------+------------\n partial_index_test_time_idx | {time}  |      | f      | f       | f         | \n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n                 Table                  |                               Index                                | Columns | Expr | Unique | Primary | Exclusion | Tablespace \n----------------------------------------+--------------------------------------------------------------------+---------+------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_3_4_chunk | _timescaledb_internal._hyper_3_4_chunk_partial_index_test_time_idx | {time}  |      | f      | f       | f         | \n\n-- regerssion test for bug fixed by PR #1008.\n-- this caused an assertion failure when a MergeAppend node contained unsorted children\nSET enable_bitmapscan TO false;\nEXPLAIN (verbose, buffers off, costs off) SELECT * FROM partial_index_test WHERE time < 2 ORDER BY time LIMIT 2;\n--- QUERY PLAN ---\n Limit\n   Output: partial_index_test.\"time\"\n   ->  Custom Scan (ChunkAppend) on public.partial_index_test\n         Output: partial_index_test.\"time\"\n         Order: partial_index_test.\"time\"\n         Startup Exclusion: false\n         Runtime Exclusion: false\n         ->  Index Only Scan using _hyper_3_4_chunk_partial_index_test_time_idx on _timescaledb_internal._hyper_3_4_chunk\n               Output: _hyper_3_4_chunk.\"time\"\n         ->  Sort\n               Output: _hyper_3_5_chunk.\"time\"\n               Sort Key: _hyper_3_5_chunk.\"time\"\n               ->  Seq Scan on _timescaledb_internal._hyper_3_5_chunk\n                     Output: _hyper_3_5_chunk.\"time\"\n\nSELECT * FROM partial_index_test WHERE time < 2 ORDER BY time LIMIT 2;\n time \n------\n    0\n    1\n\n-- we can drop the partially created index\nDROP INDEX partial_index_test_time_idx;\nSELECT * FROM test.show_indexes('partial_index_test');\n Index | Columns | Expr | Unique | Primary | Exclusion | Tablespace \n-------+---------+------+--------+---------+-----------+------------\n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n Table | Index | Columns | Expr | Unique | Primary | Exclusion | Tablespace \n-------+-------+---------+------+--------+---------+-----------+------------\n\nEXPLAIN (verbose, buffers off, costs off) SELECT * FROM partial_index_test WHERE time < 2 ORDER BY time LIMIT 2;\n--- QUERY PLAN ---\n Limit\n   Output: partial_index_test.\"time\"\n   ->  Sort\n         Output: partial_index_test.\"time\"\n         Sort Key: partial_index_test.\"time\"\n         ->  Append\n               ->  Seq Scan on _timescaledb_internal._hyper_3_4_chunk\n                     Output: _hyper_3_4_chunk.\"time\"\n               ->  Seq Scan on _timescaledb_internal._hyper_3_5_chunk\n                     Output: _hyper_3_5_chunk.\"time\"\n\nSELECT * FROM partial_index_test WHERE time < 2 ORDER BY time LIMIT 2;\n time \n------\n    0\n    1\n\nSET enable_seqscan TO true;\nSET enable_bitmapscan TO true;\n\\c  :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n\\set ON_ERROR_STOP 0\nCREATE INDEX ON partial_index_test (time) WITH (timescaledb.transaction_per_chunk, timescaledb.max_chunks='1');\nERROR:  must be owner of hypertable \"partial_index_test\"\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "test/expected/net.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_http_parsing(int) RETURNS VOID\n    AS :MODULE_PATHNAME, 'ts_test_http_parsing' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_http_parsing_full() RETURNS VOID\n    AS :MODULE_PATHNAME, 'ts_test_http_parsing_full' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_http_request_build() RETURNS VOID\n    AS :MODULE_PATHNAME, 'ts_test_http_request_build' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_conn() RETURNS VOID\n    AS :MODULE_PATHNAME, 'ts_test_conn' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT _timescaledb_internal.test_http_parsing(10000);\n test_http_parsing \n-------------------\n \n\nSELECT _timescaledb_internal.test_http_parsing_full();\n test_http_parsing_full \n------------------------\n \n\nSELECT _timescaledb_internal.test_http_request_build();\n test_http_request_build \n-------------------------\n \n\nSELECT _timescaledb_internal.test_conn();\n test_conn \n-----------\n \n\n"
  },
  {
    "path": "test/expected/null_exclusion-15.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\ncreate table metrics(ts timestamp, id int, value float);\nselect create_hypertable('metrics', 'ts');\nWARNING:  column type \"timestamp without time zone\" used for \"ts\" does not follow best practices\n  create_hypertable   \n----------------------\n (1,public,metrics,t)\n\ninsert into metrics values ('2022-02-02 02:02:02', 2, 2.),\n    ('2023-03-03 03:03:03', 3, 3.);\nanalyze metrics;\n-- non-const condition\nexplain (analyze, buffers off, costs off, summary off, timing off)\nselect * from metrics\nwhere ts >= (select max(ts) from metrics);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics (actual rows=1.00 loops=1)\n   Chunks excluded during runtime: 1\n   InitPlan 2 (returns $1)\n     ->  Result (actual rows=1.00 loops=1)\n           InitPlan 1 (returns $0)\n             ->  Limit (actual rows=1.00 loops=1)\n                   ->  Custom Scan (ChunkAppend) on metrics metrics_1 (actual rows=1.00 loops=1)\n                         Order: metrics_1.ts DESC\n                         ->  Index Only Scan using _hyper_1_2_chunk_metrics_ts_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (actual rows=1.00 loops=1)\n                               Index Cond: (ts IS NOT NULL)\n                         ->  Index Only Scan using _hyper_1_1_chunk_metrics_ts_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (never executed)\n                               Index Cond: (ts IS NOT NULL)\n   ->  Seq Scan on _hyper_1_1_chunk (never executed)\n         Filter: (ts >= $1)\n   ->  Seq Scan on _hyper_1_2_chunk (actual rows=1.00 loops=1)\n         Filter: (ts >= $1)\n\n-- two non-const conditions\nexplain (analyze, buffers off, costs off, summary off, timing off)\nselect * from metrics\nwhere ts >= (select max(ts) from metrics)\n    and id = 1;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics (actual rows=0.00 loops=1)\n   Chunks excluded during runtime: 1\n   InitPlan 2 (returns $1)\n     ->  Result (actual rows=1.00 loops=1)\n           InitPlan 1 (returns $0)\n             ->  Limit (actual rows=1.00 loops=1)\n                   ->  Custom Scan (ChunkAppend) on metrics metrics_1 (actual rows=1.00 loops=1)\n                         Order: metrics_1.ts DESC\n                         ->  Index Only Scan using _hyper_1_2_chunk_metrics_ts_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (actual rows=1.00 loops=1)\n                               Index Cond: (ts IS NOT NULL)\n                         ->  Index Only Scan using _hyper_1_1_chunk_metrics_ts_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (never executed)\n                               Index Cond: (ts IS NOT NULL)\n   ->  Seq Scan on _hyper_1_1_chunk (never executed)\n         Filter: ((ts >= $1) AND (id = 1))\n   ->  Seq Scan on _hyper_1_2_chunk (actual rows=0.00 loops=1)\n         Filter: ((ts >= $1) AND (id = 1))\n         Rows Removed by Filter: 1\n\n-- condition that becomes const null after evaluating the param\nexplain (analyze, buffers off, costs off, summary off, timing off)\nselect * from metrics\nwhere ts >= (select max(ts) from metrics where id = -1);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics (actual rows=0.00 loops=1)\n   Chunks excluded during runtime: 2\n   InitPlan 2 (returns $1)\n     ->  Result (actual rows=1.00 loops=1)\n           InitPlan 1 (returns $0)\n             ->  Limit (actual rows=0.00 loops=1)\n                   ->  Custom Scan (ChunkAppend) on metrics metrics_1 (actual rows=0.00 loops=1)\n                         Order: metrics_1.ts DESC\n                         ->  Index Scan using _hyper_1_2_chunk_metrics_ts_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (actual rows=0.00 loops=1)\n                               Index Cond: (ts IS NOT NULL)\n                               Filter: (id = '-1'::integer)\n                               Rows Removed by Filter: 1\n                         ->  Index Scan using _hyper_1_1_chunk_metrics_ts_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (actual rows=0.00 loops=1)\n                               Index Cond: (ts IS NOT NULL)\n                               Filter: (id = '-1'::integer)\n                               Rows Removed by Filter: 1\n   ->  Seq Scan on _hyper_1_1_chunk (never executed)\n         Filter: (ts >= $1)\n   ->  Seq Scan on _hyper_1_2_chunk (never executed)\n         Filter: (ts >= $1)\n\n-- const null condition and some other condition\nexplain (analyze, buffers off, costs off, summary off, timing off)\nselect * from metrics\nwhere ts >= (select max(ts) from metrics where id = -1)\n    and id = 1;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics (actual rows=0.00 loops=1)\n   Chunks excluded during runtime: 2\n   InitPlan 2 (returns $1)\n     ->  Result (actual rows=1.00 loops=1)\n           InitPlan 1 (returns $0)\n             ->  Limit (actual rows=0.00 loops=1)\n                   ->  Custom Scan (ChunkAppend) on metrics metrics_1 (actual rows=0.00 loops=1)\n                         Order: metrics_1.ts DESC\n                         ->  Index Scan using _hyper_1_2_chunk_metrics_ts_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (actual rows=0.00 loops=1)\n                               Index Cond: (ts IS NOT NULL)\n                               Filter: (id = '-1'::integer)\n                               Rows Removed by Filter: 1\n                         ->  Index Scan using _hyper_1_1_chunk_metrics_ts_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (actual rows=0.00 loops=1)\n                               Index Cond: (ts IS NOT NULL)\n                               Filter: (id = '-1'::integer)\n                               Rows Removed by Filter: 1\n   ->  Seq Scan on _hyper_1_1_chunk (never executed)\n         Filter: ((ts >= $1) AND (id = 1))\n   ->  Seq Scan on _hyper_1_2_chunk (never executed)\n         Filter: ((ts >= $1) AND (id = 1))\n\n"
  },
  {
    "path": "test/expected/null_exclusion-16.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\ncreate table metrics(ts timestamp, id int, value float);\nselect create_hypertable('metrics', 'ts');\nWARNING:  column type \"timestamp without time zone\" used for \"ts\" does not follow best practices\n  create_hypertable   \n----------------------\n (1,public,metrics,t)\n\ninsert into metrics values ('2022-02-02 02:02:02', 2, 2.),\n    ('2023-03-03 03:03:03', 3, 3.);\nanalyze metrics;\n-- non-const condition\nexplain (analyze, buffers off, costs off, summary off, timing off)\nselect * from metrics\nwhere ts >= (select max(ts) from metrics);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics (actual rows=1.00 loops=1)\n   Chunks excluded during runtime: 1\n   InitPlan 2 (returns $1)\n     ->  Result (actual rows=1.00 loops=1)\n           InitPlan 1 (returns $0)\n             ->  Limit (actual rows=1.00 loops=1)\n                   ->  Custom Scan (ChunkAppend) on metrics metrics_1 (actual rows=1.00 loops=1)\n                         Order: metrics_1.ts DESC\n                         ->  Index Only Scan using _hyper_1_2_chunk_metrics_ts_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (actual rows=1.00 loops=1)\n                               Index Cond: (ts IS NOT NULL)\n                         ->  Index Only Scan using _hyper_1_1_chunk_metrics_ts_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (never executed)\n                               Index Cond: (ts IS NOT NULL)\n   ->  Seq Scan on _hyper_1_1_chunk (never executed)\n         Filter: (ts >= $1)\n   ->  Seq Scan on _hyper_1_2_chunk (actual rows=1.00 loops=1)\n         Filter: (ts >= $1)\n\n-- two non-const conditions\nexplain (analyze, buffers off, costs off, summary off, timing off)\nselect * from metrics\nwhere ts >= (select max(ts) from metrics)\n    and id = 1;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics (actual rows=0.00 loops=1)\n   Chunks excluded during runtime: 1\n   InitPlan 2 (returns $1)\n     ->  Result (actual rows=1.00 loops=1)\n           InitPlan 1 (returns $0)\n             ->  Limit (actual rows=1.00 loops=1)\n                   ->  Custom Scan (ChunkAppend) on metrics metrics_1 (actual rows=1.00 loops=1)\n                         Order: metrics_1.ts DESC\n                         ->  Index Only Scan using _hyper_1_2_chunk_metrics_ts_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (actual rows=1.00 loops=1)\n                               Index Cond: (ts IS NOT NULL)\n                         ->  Index Only Scan using _hyper_1_1_chunk_metrics_ts_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (never executed)\n                               Index Cond: (ts IS NOT NULL)\n   ->  Seq Scan on _hyper_1_1_chunk (never executed)\n         Filter: ((ts >= $1) AND (id = 1))\n   ->  Seq Scan on _hyper_1_2_chunk (actual rows=0.00 loops=1)\n         Filter: ((ts >= $1) AND (id = 1))\n         Rows Removed by Filter: 1\n\n-- condition that becomes const null after evaluating the param\nexplain (analyze, buffers off, costs off, summary off, timing off)\nselect * from metrics\nwhere ts >= (select max(ts) from metrics where id = -1);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics (actual rows=0.00 loops=1)\n   Chunks excluded during runtime: 2\n   InitPlan 2 (returns $1)\n     ->  Result (actual rows=1.00 loops=1)\n           InitPlan 1 (returns $0)\n             ->  Limit (actual rows=0.00 loops=1)\n                   ->  Custom Scan (ChunkAppend) on metrics metrics_1 (actual rows=0.00 loops=1)\n                         Order: metrics_1.ts DESC\n                         ->  Index Scan using _hyper_1_2_chunk_metrics_ts_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (actual rows=0.00 loops=1)\n                               Index Cond: (ts IS NOT NULL)\n                               Filter: (id = '-1'::integer)\n                               Rows Removed by Filter: 1\n                         ->  Index Scan using _hyper_1_1_chunk_metrics_ts_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (actual rows=0.00 loops=1)\n                               Index Cond: (ts IS NOT NULL)\n                               Filter: (id = '-1'::integer)\n                               Rows Removed by Filter: 1\n   ->  Seq Scan on _hyper_1_1_chunk (never executed)\n         Filter: (ts >= $1)\n   ->  Seq Scan on _hyper_1_2_chunk (never executed)\n         Filter: (ts >= $1)\n\n-- const null condition and some other condition\nexplain (analyze, buffers off, costs off, summary off, timing off)\nselect * from metrics\nwhere ts >= (select max(ts) from metrics where id = -1)\n    and id = 1;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics (actual rows=0.00 loops=1)\n   Chunks excluded during runtime: 2\n   InitPlan 2 (returns $1)\n     ->  Result (actual rows=1.00 loops=1)\n           InitPlan 1 (returns $0)\n             ->  Limit (actual rows=0.00 loops=1)\n                   ->  Custom Scan (ChunkAppend) on metrics metrics_1 (actual rows=0.00 loops=1)\n                         Order: metrics_1.ts DESC\n                         ->  Index Scan using _hyper_1_2_chunk_metrics_ts_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (actual rows=0.00 loops=1)\n                               Index Cond: (ts IS NOT NULL)\n                               Filter: (id = '-1'::integer)\n                               Rows Removed by Filter: 1\n                         ->  Index Scan using _hyper_1_1_chunk_metrics_ts_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (actual rows=0.00 loops=1)\n                               Index Cond: (ts IS NOT NULL)\n                               Filter: (id = '-1'::integer)\n                               Rows Removed by Filter: 1\n   ->  Seq Scan on _hyper_1_1_chunk (never executed)\n         Filter: ((ts >= $1) AND (id = 1))\n   ->  Seq Scan on _hyper_1_2_chunk (never executed)\n         Filter: ((ts >= $1) AND (id = 1))\n\n"
  },
  {
    "path": "test/expected/null_exclusion-17.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\ncreate table metrics(ts timestamp, id int, value float);\nselect create_hypertable('metrics', 'ts');\nWARNING:  column type \"timestamp without time zone\" used for \"ts\" does not follow best practices\n  create_hypertable   \n----------------------\n (1,public,metrics,t)\n\ninsert into metrics values ('2022-02-02 02:02:02', 2, 2.),\n    ('2023-03-03 03:03:03', 3, 3.);\nanalyze metrics;\n-- non-const condition\nexplain (analyze, buffers off, costs off, summary off, timing off)\nselect * from metrics\nwhere ts >= (select max(ts) from metrics);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics (actual rows=1.00 loops=1)\n   Chunks excluded during runtime: 1\n   InitPlan 2\n     ->  Result (actual rows=1.00 loops=1)\n           InitPlan 1\n             ->  Limit (actual rows=1.00 loops=1)\n                   ->  Custom Scan (ChunkAppend) on metrics metrics_1 (actual rows=1.00 loops=1)\n                         Order: metrics_1.ts DESC\n                         ->  Index Only Scan using _hyper_1_2_chunk_metrics_ts_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (actual rows=1.00 loops=1)\n                         ->  Index Only Scan using _hyper_1_1_chunk_metrics_ts_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (never executed)\n   ->  Seq Scan on _hyper_1_1_chunk (never executed)\n         Filter: (ts >= (InitPlan 2).col1)\n   ->  Seq Scan on _hyper_1_2_chunk (actual rows=1.00 loops=1)\n         Filter: (ts >= (InitPlan 2).col1)\n\n-- two non-const conditions\nexplain (analyze, buffers off, costs off, summary off, timing off)\nselect * from metrics\nwhere ts >= (select max(ts) from metrics)\n    and id = 1;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics (actual rows=0.00 loops=1)\n   Chunks excluded during runtime: 1\n   InitPlan 2\n     ->  Result (actual rows=1.00 loops=1)\n           InitPlan 1\n             ->  Limit (actual rows=1.00 loops=1)\n                   ->  Custom Scan (ChunkAppend) on metrics metrics_1 (actual rows=1.00 loops=1)\n                         Order: metrics_1.ts DESC\n                         ->  Index Only Scan using _hyper_1_2_chunk_metrics_ts_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (actual rows=1.00 loops=1)\n                         ->  Index Only Scan using _hyper_1_1_chunk_metrics_ts_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (never executed)\n   ->  Seq Scan on _hyper_1_1_chunk (never executed)\n         Filter: ((ts >= (InitPlan 2).col1) AND (id = 1))\n   ->  Seq Scan on _hyper_1_2_chunk (actual rows=0.00 loops=1)\n         Filter: ((ts >= (InitPlan 2).col1) AND (id = 1))\n         Rows Removed by Filter: 1\n\n-- condition that becomes const null after evaluating the param\nexplain (analyze, buffers off, costs off, summary off, timing off)\nselect * from metrics\nwhere ts >= (select max(ts) from metrics where id = -1);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics (actual rows=0.00 loops=1)\n   Chunks excluded during runtime: 2\n   InitPlan 2\n     ->  Result (actual rows=1.00 loops=1)\n           InitPlan 1\n             ->  Limit (actual rows=0.00 loops=1)\n                   ->  Custom Scan (ChunkAppend) on metrics metrics_1 (actual rows=0.00 loops=1)\n                         Order: metrics_1.ts DESC\n                         ->  Index Scan using _hyper_1_2_chunk_metrics_ts_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (actual rows=0.00 loops=1)\n                               Filter: (id = '-1'::integer)\n                               Rows Removed by Filter: 1\n                         ->  Index Scan using _hyper_1_1_chunk_metrics_ts_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (actual rows=0.00 loops=1)\n                               Filter: (id = '-1'::integer)\n                               Rows Removed by Filter: 1\n   ->  Seq Scan on _hyper_1_1_chunk (never executed)\n         Filter: (ts >= (InitPlan 2).col1)\n   ->  Seq Scan on _hyper_1_2_chunk (never executed)\n         Filter: (ts >= (InitPlan 2).col1)\n\n-- const null condition and some other condition\nexplain (analyze, buffers off, costs off, summary off, timing off)\nselect * from metrics\nwhere ts >= (select max(ts) from metrics where id = -1)\n    and id = 1;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics (actual rows=0.00 loops=1)\n   Chunks excluded during runtime: 2\n   InitPlan 2\n     ->  Result (actual rows=1.00 loops=1)\n           InitPlan 1\n             ->  Limit (actual rows=0.00 loops=1)\n                   ->  Custom Scan (ChunkAppend) on metrics metrics_1 (actual rows=0.00 loops=1)\n                         Order: metrics_1.ts DESC\n                         ->  Index Scan using _hyper_1_2_chunk_metrics_ts_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (actual rows=0.00 loops=1)\n                               Filter: (id = '-1'::integer)\n                               Rows Removed by Filter: 1\n                         ->  Index Scan using _hyper_1_1_chunk_metrics_ts_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (actual rows=0.00 loops=1)\n                               Filter: (id = '-1'::integer)\n                               Rows Removed by Filter: 1\n   ->  Seq Scan on _hyper_1_1_chunk (never executed)\n         Filter: ((ts >= (InitPlan 2).col1) AND (id = 1))\n   ->  Seq Scan on _hyper_1_2_chunk (never executed)\n         Filter: ((ts >= (InitPlan 2).col1) AND (id = 1))\n\n"
  },
  {
    "path": "test/expected/null_exclusion-18.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\ncreate table metrics(ts timestamp, id int, value float);\nselect create_hypertable('metrics', 'ts');\nWARNING:  column type \"timestamp without time zone\" used for \"ts\" does not follow best practices\n  create_hypertable   \n----------------------\n (1,public,metrics,t)\n\ninsert into metrics values ('2022-02-02 02:02:02', 2, 2.),\n    ('2023-03-03 03:03:03', 3, 3.);\nanalyze metrics;\n-- non-const condition\nexplain (analyze, buffers off, costs off, summary off, timing off)\nselect * from metrics\nwhere ts >= (select max(ts) from metrics);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics (actual rows=1.00 loops=1)\n   Chunks excluded during runtime: 1\n   InitPlan 2\n     ->  Result (actual rows=1.00 loops=1)\n           InitPlan 1\n             ->  Limit (actual rows=1.00 loops=1)\n                   ->  Custom Scan (ChunkAppend) on metrics metrics_1 (actual rows=1.00 loops=1)\n                         Order: metrics_1.ts DESC\n                         ->  Index Only Scan using _hyper_1_2_chunk_metrics_ts_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (actual rows=1.00 loops=1)\n                         ->  Index Only Scan using _hyper_1_1_chunk_metrics_ts_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (never executed)\n   ->  Seq Scan on _hyper_1_1_chunk (never executed)\n         Filter: (ts >= (InitPlan 2).col1)\n   ->  Seq Scan on _hyper_1_2_chunk (actual rows=1.00 loops=1)\n         Filter: (ts >= (InitPlan 2).col1)\n\n-- two non-const conditions\nexplain (analyze, buffers off, costs off, summary off, timing off)\nselect * from metrics\nwhere ts >= (select max(ts) from metrics)\n    and id = 1;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics (actual rows=0.00 loops=1)\n   Chunks excluded during runtime: 1\n   InitPlan 2\n     ->  Result (actual rows=1.00 loops=1)\n           InitPlan 1\n             ->  Limit (actual rows=1.00 loops=1)\n                   ->  Custom Scan (ChunkAppend) on metrics metrics_1 (actual rows=1.00 loops=1)\n                         Order: metrics_1.ts DESC\n                         ->  Index Only Scan using _hyper_1_2_chunk_metrics_ts_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (actual rows=1.00 loops=1)\n                         ->  Index Only Scan using _hyper_1_1_chunk_metrics_ts_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (never executed)\n   ->  Seq Scan on _hyper_1_1_chunk (never executed)\n         Filter: ((ts >= (InitPlan 2).col1) AND (id = 1))\n   ->  Seq Scan on _hyper_1_2_chunk (actual rows=0.00 loops=1)\n         Filter: ((ts >= (InitPlan 2).col1) AND (id = 1))\n         Rows Removed by Filter: 1\n\n-- condition that becomes const null after evaluating the param\nexplain (analyze, buffers off, costs off, summary off, timing off)\nselect * from metrics\nwhere ts >= (select max(ts) from metrics where id = -1);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics (actual rows=0.00 loops=1)\n   Chunks excluded during runtime: 2\n   InitPlan 2\n     ->  Result (actual rows=1.00 loops=1)\n           InitPlan 1\n             ->  Limit (actual rows=0.00 loops=1)\n                   ->  Custom Scan (ChunkAppend) on metrics metrics_1 (actual rows=0.00 loops=1)\n                         Order: metrics_1.ts DESC\n                         ->  Index Scan using _hyper_1_2_chunk_metrics_ts_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (actual rows=0.00 loops=1)\n                               Filter: (id = '-1'::integer)\n                               Rows Removed by Filter: 1\n                         ->  Index Scan using _hyper_1_1_chunk_metrics_ts_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (actual rows=0.00 loops=1)\n                               Filter: (id = '-1'::integer)\n                               Rows Removed by Filter: 1\n   ->  Seq Scan on _hyper_1_1_chunk (never executed)\n         Filter: (ts >= (InitPlan 2).col1)\n   ->  Seq Scan on _hyper_1_2_chunk (never executed)\n         Filter: (ts >= (InitPlan 2).col1)\n\n-- const null condition and some other condition\nexplain (analyze, buffers off, costs off, summary off, timing off)\nselect * from metrics\nwhere ts >= (select max(ts) from metrics where id = -1)\n    and id = 1;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics (actual rows=0.00 loops=1)\n   Chunks excluded during runtime: 2\n   InitPlan 2\n     ->  Result (actual rows=1.00 loops=1)\n           InitPlan 1\n             ->  Limit (actual rows=0.00 loops=1)\n                   ->  Custom Scan (ChunkAppend) on metrics metrics_1 (actual rows=0.00 loops=1)\n                         Order: metrics_1.ts DESC\n                         ->  Index Scan using _hyper_1_2_chunk_metrics_ts_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (actual rows=0.00 loops=1)\n                               Filter: (id = '-1'::integer)\n                               Rows Removed by Filter: 1\n                         ->  Index Scan using _hyper_1_1_chunk_metrics_ts_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (actual rows=0.00 loops=1)\n                               Filter: (id = '-1'::integer)\n                               Rows Removed by Filter: 1\n   ->  Seq Scan on _hyper_1_1_chunk (never executed)\n         Filter: ((ts >= (InitPlan 2).col1) AND (id = 1))\n   ->  Seq Scan on _hyper_1_2_chunk (never executed)\n         Filter: ((ts >= (InitPlan 2).col1) AND (id = 1))\n\n"
  },
  {
    "path": "test/expected/parallel-15.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n--parallel queries require big-ish tables so collect them all here\n--so that we need to generate queries only once.\n-- output with analyze is not stable because it depends on worker assignment\n\\set PREFIX 'EXPLAIN (buffers off, costs off)'\n\\set CHUNK1 _timescaledb_internal._hyper_1_1_chunk\n\\set CHUNK2 _timescaledb_internal._hyper_1_2_chunk\nCREATE TABLE test (i int, j double precision, ts timestamp);\nSELECT create_hypertable('test','i',chunk_time_interval:=500000);\nWARNING:  column type \"timestamp without time zone\" used for \"ts\" does not follow best practices\n create_hypertable \n-------------------\n (1,public,test,t)\n\nINSERT INTO test SELECT x, x+0.1, _timescaledb_functions.to_timestamp(x*1000)  FROM generate_series(0,1000000-1,10) AS x;\nANALYZE test;\nALTER TABLE :CHUNK1 SET (parallel_workers=2);\nALTER TABLE :CHUNK2 SET (parallel_workers=2);\nSET work_mem TO '50MB';\nSELECT set_config(CASE WHEN current_setting('server_version_num')::int < 160000 THEN 'force_parallel_mode' ELSE 'debug_parallel_query' END,'on', false);\n set_config \n------------\n on\n\nSET max_parallel_workers_per_gather = 4;\nSET parallel_setup_cost TO 0;\nEXPLAIN (buffers off, costs off) SELECT first(i, j) FROM \"test\";\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT first(i, j) FROM \"test\";\n first \n-------\n     0\n\nEXPLAIN (buffers off, costs off) SELECT last(i, j) FROM \"test\";\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT last(i, j) FROM \"test\";\n  last  \n--------\n 999990\n\nEXPLAIN (buffers off, costs off) SELECT time_bucket('1 second', ts) sec, last(i, j)\nFROM \"test\"\nGROUP BY sec\nORDER BY sec\nLIMIT 5;\n--- QUERY PLAN ---\n Limit\n   ->  Finalize GroupAggregate\n         Group Key: (time_bucket('@ 1 sec'::interval, test.ts))\n         ->  Gather Merge\n               Workers Planned: 2\n               ->  Partial GroupAggregate\n                     Group Key: (time_bucket('@ 1 sec'::interval, test.ts))\n                     ->  Sort\n                           Sort Key: (time_bucket('@ 1 sec'::interval, test.ts))\n                           ->  Result\n                                 ->  Parallel Append\n                                       ->  Parallel Seq Scan on _hyper_1_1_chunk\n                                       ->  Parallel Seq Scan on _hyper_1_2_chunk\n\n-- test single copy parallel plan with parallel chunk append\n:PREFIX SELECT time_bucket('1 second', ts) sec, last(i, j)\nFROM \"test\"\nWHERE length(version()) > 0\nGROUP BY sec\nORDER BY sec\nLIMIT 5;\n--- QUERY PLAN ---\n Limit\n   ->  Finalize GroupAggregate\n         Group Key: (time_bucket('@ 1 sec'::interval, test.ts))\n         ->  Gather Merge\n               Workers Planned: 2\n               ->  Partial GroupAggregate\n                     Group Key: (time_bucket('@ 1 sec'::interval, test.ts))\n                     ->  Sort\n                           Sort Key: (time_bucket('@ 1 sec'::interval, test.ts))\n                           ->  Result\n                                 ->  Result\n                                       One-Time Filter: (length(version()) > 0)\n                                       ->  Parallel Custom Scan (ChunkAppend) on test\n                                             Chunks excluded during startup: 0\n                                             ->  Result\n                                                   One-Time Filter: (length(version()) > 0)\n                                                   ->  Parallel Seq Scan on _hyper_1_1_chunk\n                                             ->  Result\n                                                   One-Time Filter: (length(version()) > 0)\n                                                   ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT time_bucket('1 second', ts) sec, last(i, j)\nFROM \"test\"\nGROUP BY sec\nORDER BY sec\nLIMIT 5;\n           sec            | last \n--------------------------+------\n Wed Dec 31 16:00:00 1969 |  990\n Wed Dec 31 16:00:01 1969 | 1990\n Wed Dec 31 16:00:02 1969 | 2990\n Wed Dec 31 16:00:03 1969 | 3990\n Wed Dec 31 16:00:04 1969 | 4990\n\n--test variants of histogram\nEXPLAIN (buffers off, costs off) SELECT histogram(i, 1, 1000000, 2) FROM \"test\";\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT histogram(i, 1, 1000000, 2) FROM \"test\";\n     histogram     \n-------------------\n {1,50000,49999,0}\n\nEXPLAIN (buffers off, costs off) SELECT histogram(i, 1,1000001,10) FROM \"test\";\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT histogram(i, 1, 1000001, 10) FROM \"test\";\n                            histogram                             \n------------------------------------------------------------------\n {1,10000,10000,10000,10000,10000,10000,10000,10000,10000,9999,0}\n\nEXPLAIN (buffers off, costs off) SELECT histogram(i, 0,100000,5) FROM \"test\";\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT histogram(i, 0, 100000, 5) FROM \"test\";\n             histogram              \n------------------------------------\n {0,2000,2000,2000,2000,2000,90000}\n\nEXPLAIN (buffers off, costs off) SELECT histogram(i, 10,100000,5) FROM \"test\";\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT histogram(i, 10, 100000, 5) FROM \"test\";\n             histogram              \n------------------------------------\n {1,2000,2000,2000,2000,1999,90000}\n\nEXPLAIN (buffers off, costs off) SELECT histogram(NULL, 10,100000,5) FROM \"test\" WHERE  i = coalesce(-1,j);\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                           Filter: ((i)::double precision = '-1'::double precision)\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n                           Filter: ((i)::double precision = '-1'::double precision)\n\nSELECT histogram(NULL, 10,100000,5) FROM \"test\" WHERE  i = coalesce(-1,j);\n histogram \n-----------\n \n\n-- test parallel ChunkAppend\n:PREFIX SELECT i FROM \"test\" WHERE length(version()) > 0;\n--- QUERY PLAN ---\n Gather\n   Workers Planned: 1\n   Single Copy: true\n   ->  Result\n         One-Time Filter: (length(version()) > 0)\n         ->  Custom Scan (ChunkAppend) on test\n               Chunks excluded during startup: 0\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Seq Scan on _hyper_1_1_chunk\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Seq Scan on _hyper_1_2_chunk\n\n:PREFIX SELECT count(*) FROM \"test\" WHERE i > 1 AND length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_1_chunk\n                                       Filter: (i > 1)\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT count(*) FROM \"test\" WHERE i > 1 AND length(version()) > 0;\n count \n-------\n 99999\n\n-- test parallel ChunkAppend with only work done in the parallel workers\nSET parallel_leader_participation = off;\nSELECT count(*) FROM \"test\" WHERE i > 1 AND length(version()) > 0;\n count \n-------\n 99999\n\nRESET parallel_leader_participation;\n-- Test parallel chunk append is used (index scan is disabled to trigger a parallel chunk append)\nSET parallel_tuple_cost = 0;\nSET enable_indexscan = OFF;\n:PREFIX SELECT * FROM (SELECT * FROM \"test\" WHERE length(version()) > 0 ORDER BY I LIMIT 10) AS t1 LEFT JOIN (SELECT * FROM \"test\" WHERE i < 500000 ORDER BY I LIMIT 10) AS t2 ON (t1.i = t2.i) ORDER BY t1.i, t2.i;\n--- QUERY PLAN ---\n Sort\n   Sort Key: test.i, _hyper_1_1_chunk_1.i\n   ->  Merge Left Join\n         Merge Cond: (test.i = _hyper_1_1_chunk_1.i)\n         ->  Limit\n               ->  Gather Merge\n                     Workers Planned: 2\n                     ->  Sort\n                           Sort Key: test.i\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Custom Scan (ChunkAppend) on test\n                                       Chunks excluded during startup: 0\n                                       ->  Result\n                                             One-Time Filter: (length(version()) > 0)\n                                             ->  Parallel Seq Scan on _hyper_1_1_chunk\n                                       ->  Result\n                                             One-Time Filter: (length(version()) > 0)\n                                             ->  Parallel Seq Scan on _hyper_1_2_chunk\n         ->  Materialize\n               ->  Limit\n                     ->  Gather Merge\n                           Workers Planned: 2\n                           ->  Sort\n                                 Sort Key: _hyper_1_1_chunk_1.i\n                                 ->  Parallel Seq Scan on _hyper_1_1_chunk _hyper_1_1_chunk_1\n\nSELECT * FROM (SELECT * FROM \"test\" WHERE length(version()) > 0 ORDER BY I LIMIT 10) AS t1 LEFT JOIN (SELECT * FROM \"test\" WHERE i < 500000 ORDER BY I LIMIT 10) AS t2 ON (t1.i = t2.i) ORDER BY t1.i, t2.i;\n i  |  j   |             ts              | i  |  j   |             ts              \n----+------+-----------------------------+----+------+-----------------------------\n  0 |  0.1 | Wed Dec 31 16:00:00 1969    |  0 |  0.1 | Wed Dec 31 16:00:00 1969\n 10 | 10.1 | Wed Dec 31 16:00:00.01 1969 | 10 | 10.1 | Wed Dec 31 16:00:00.01 1969\n 20 | 20.1 | Wed Dec 31 16:00:00.02 1969 | 20 | 20.1 | Wed Dec 31 16:00:00.02 1969\n 30 | 30.1 | Wed Dec 31 16:00:00.03 1969 | 30 | 30.1 | Wed Dec 31 16:00:00.03 1969\n 40 | 40.1 | Wed Dec 31 16:00:00.04 1969 | 40 | 40.1 | Wed Dec 31 16:00:00.04 1969\n 50 | 50.1 | Wed Dec 31 16:00:00.05 1969 | 50 | 50.1 | Wed Dec 31 16:00:00.05 1969\n 60 | 60.1 | Wed Dec 31 16:00:00.06 1969 | 60 | 60.1 | Wed Dec 31 16:00:00.06 1969\n 70 | 70.1 | Wed Dec 31 16:00:00.07 1969 | 70 | 70.1 | Wed Dec 31 16:00:00.07 1969\n 80 | 80.1 | Wed Dec 31 16:00:00.08 1969 | 80 | 80.1 | Wed Dec 31 16:00:00.08 1969\n 90 | 90.1 | Wed Dec 31 16:00:00.09 1969 | 90 | 90.1 | Wed Dec 31 16:00:00.09 1969\n\nSET enable_indexscan = ON;\n-- Test normal chunk append can be used in a parallel worker\n:PREFIX SELECT * FROM (SELECT * FROM \"test\" WHERE i >= 999000 ORDER BY i) AS t1 JOIN (SELECT * FROM \"test\" WHERE i >= 400000 ORDER BY i) AS t2 ON (TRUE) ORDER BY t1.i, t2.i LIMIT 10;\n--- QUERY PLAN ---\n Gather\n   Workers Planned: 1\n   Single Copy: true\n   ->  Limit\n         ->  Incremental Sort\n               Sort Key: _hyper_1_2_chunk.i, test.i\n               Presorted Key: _hyper_1_2_chunk.i\n               ->  Nested Loop\n                     ->  Index Scan Backward using _hyper_1_2_chunk_test_i_idx on _hyper_1_2_chunk\n                           Index Cond: (i >= 999000)\n                     ->  Materialize\n                           ->  Custom Scan (ChunkAppend) on test\n                                 Order: test.i\n                                 ->  Index Scan Backward using _hyper_1_1_chunk_test_i_idx on _hyper_1_1_chunk\n                                       Index Cond: (i >= 400000)\n                                 ->  Index Scan Backward using _hyper_1_2_chunk_test_i_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1\n\nSELECT * FROM (SELECT * FROM \"test\" WHERE i >= 999000 ORDER BY i) AS t1 JOIN (SELECT * FROM \"test\" WHERE i >= 400000 ORDER BY i) AS t2 ON (TRUE) ORDER BY t1.i, t2.i LIMIT 10;\n   i    |    j     |            ts            |   i    |    j     |             ts              \n--------+----------+--------------------------+--------+----------+-----------------------------\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400000 | 400000.1 | Wed Dec 31 16:06:40 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400010 | 400010.1 | Wed Dec 31 16:06:40.01 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400020 | 400020.1 | Wed Dec 31 16:06:40.02 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400030 | 400030.1 | Wed Dec 31 16:06:40.03 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400040 | 400040.1 | Wed Dec 31 16:06:40.04 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400050 | 400050.1 | Wed Dec 31 16:06:40.05 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400060 | 400060.1 | Wed Dec 31 16:06:40.06 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400070 | 400070.1 | Wed Dec 31 16:06:40.07 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400080 | 400080.1 | Wed Dec 31 16:06:40.08 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400090 | 400090.1 | Wed Dec 31 16:06:40.09 1969\n\n-- Test parallel ChunkAppend reinit\nSET enable_material = off;\nSET min_parallel_table_scan_size = 0;\nSET min_parallel_index_scan_size = 0;\nSET enable_hashjoin = 'off';\nSET enable_nestloop = 'off';\nCREATE TABLE sensor_data(\n      time timestamptz NOT NULL,\n      sensor_id integer NOT NULL);\nSELECT FROM create_hypertable(relation=>'sensor_data', time_column_name=> 'time');\n--\n\n-- Sensors 1 and 2\nINSERT INTO sensor_data\nSELECT time, sensor_id\nFROM\ngenerate_series('2000-01-01 00:00:30', '2022-01-01 00:00:30', INTERVAL '3 months') AS g1(time),\ngenerate_series(1, 2, 1) AS g2(sensor_id)\nORDER BY time;\n-- Sensor 100\nINSERT INTO sensor_data\nSELECT time, 100 as sensor_id\nFROM\ngenerate_series('2000-01-01 00:00:30', '2022-01-01 00:00:30', INTERVAL '1 year') AS g1(time)\nORDER BY time;\n:PREFIX SELECT * FROM sensor_data AS s1 JOIN sensor_data AS s2 ON (TRUE) WHERE s1.time > '2020-01-01 00:00:30'::text::timestamptz AND s2.time > '2020-01-01 00:00:30' AND s2.time < '2021-01-01 00:00:30' AND s1.sensor_id > 50 ORDER BY s2.time, s1.time, s1.sensor_id, s2.sensor_id;\n--- QUERY PLAN ---\n Sort\n   Sort Key: s2.\"time\", s1.\"time\", s1.sensor_id, s2.sensor_id\n   ->  Nested Loop\n         ->  Custom Scan (ChunkAppend) on sensor_data s2\n               Order: s2.\"time\"\n               ->  Index Scan Backward using _hyper_2_83_chunk_sensor_data_time_idx on _hyper_2_83_chunk s2_1\n                     Index Cond: ((\"time\" > 'Wed Jan 01 00:00:30 2020 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 01 00:00:30 2021 PST'::timestamp with time zone))\n               ->  Index Scan Backward using _hyper_2_84_chunk_sensor_data_time_idx on _hyper_2_84_chunk s2_2\n               ->  Index Scan Backward using _hyper_2_85_chunk_sensor_data_time_idx on _hyper_2_85_chunk s2_3\n               ->  Index Scan Backward using _hyper_2_86_chunk_sensor_data_time_idx on _hyper_2_86_chunk s2_4\n               ->  Index Scan Backward using _hyper_2_87_chunk_sensor_data_time_idx on _hyper_2_87_chunk s2_5\n                     Index Cond: ((\"time\" > 'Wed Jan 01 00:00:30 2020 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 01 00:00:30 2021 PST'::timestamp with time zone))\n         ->  Gather\n               Workers Planned: 4\n               ->  Parallel Custom Scan (ChunkAppend) on sensor_data s1\n                     Chunks excluded during startup: 80\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_83_chunk s1_1\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_83_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_84_chunk s1_2\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_84_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_85_chunk s1_3\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_85_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_86_chunk s1_4\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_86_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_87_chunk s1_5\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_87_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_88_chunk s1_6\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_88_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_89_chunk s1_7\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_89_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_90_chunk s1_8\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_90_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_91_chunk s1_9\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_91_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n\n-- Check query result\nSELECT * FROM sensor_data AS s1 JOIN sensor_data AS s2 ON (TRUE) WHERE s1.time > '2020-01-01 00:00:30'::text::timestamptz AND s2.time > '2020-01-01 00:00:30' AND s2.time < '2021-01-01 00:00:30' AND s1.sensor_id > 50 ORDER BY s2.time, s1.time, s1.sensor_id, s2.sensor_id;\n             time             | sensor_id |             time             | sensor_id \n------------------------------+-----------+------------------------------+-----------\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         2\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         2\n Fri Jan 01 00:00:30 2021 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         2\n\n-- Ensure the same result is produced if only the parallel workers have to produce them (i.e., the pstate is reinitialized properly)\nSET parallel_leader_participation = off;\nSELECT * FROM sensor_data AS s1 JOIN sensor_data AS s2 ON (TRUE) WHERE s1.time > '2020-01-01 00:00:30'::text::timestamptz AND s2.time > '2020-01-01 00:00:30' AND s2.time < '2021-01-01 00:00:30' AND s1.sensor_id > 50 ORDER BY s2.time, s1.time, s1.sensor_id, s2.sensor_id;\n             time             | sensor_id |             time             | sensor_id \n------------------------------+-----------+------------------------------+-----------\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         2\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         2\n Fri Jan 01 00:00:30 2021 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         2\n\nRESET parallel_leader_participation;\n-- Ensure the same query result is produced by a sequencial query\nSET max_parallel_workers_per_gather TO 0;\nSELECT set_config(CASE WHEN current_setting('server_version_num')::int < 160000 THEN 'force_parallel_mode' ELSE 'debug_parallel_query' END,'off', false);\n set_config \n------------\n off\n\nSELECT * FROM sensor_data AS s1 JOIN sensor_data AS s2 ON (TRUE) WHERE s1.time > '2020-01-01 00:00:30'::text::timestamptz AND s2.time > '2020-01-01 00:00:30' AND s2.time < '2021-01-01 00:00:30' AND s1.sensor_id > 50 ORDER BY s2.time, s1.time, s1.sensor_id, s2.sensor_id;\n             time             | sensor_id |             time             | sensor_id \n------------------------------+-----------+------------------------------+-----------\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         2\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         2\n Fri Jan 01 00:00:30 2021 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         2\n\nRESET enable_material;\nRESET min_parallel_table_scan_size;\nRESET min_parallel_index_scan_size;\nRESET enable_hashjoin;\nRESET enable_nestloop;\nRESET parallel_tuple_cost;\nSELECT set_config(CASE WHEN current_setting('server_version_num')::int < 160000 THEN 'force_parallel_mode' ELSE 'debug_parallel_query' END,'on', false);\n set_config \n------------\n on\n\n-- test worker assignment\n-- first chunk should have 1 worker and second chunk should have 2\nSET max_parallel_workers_per_gather TO 2;\n:PREFIX SELECT count(*) FROM \"test\" WHERE i >= 400000 AND length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Index Only Scan using _hyper_1_1_chunk_test_i_idx on _hyper_1_1_chunk\n                                       Index Cond: (i >= 400000)\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT count(*) FROM \"test\" WHERE i >= 400000 AND length(version()) > 0;\n count \n-------\n 60000\n\n-- test worker assignment\n-- first chunk should have 2 worker and second chunk should have 1\n:PREFIX SELECT count(*) FROM \"test\" WHERE i < 600000 AND length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Index Only Scan using _hyper_1_2_chunk_test_i_idx on _hyper_1_2_chunk\n                                       Index Cond: (i < 600000)\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_1_chunk\n\nSELECT count(*) FROM \"test\" WHERE i < 600000 AND length(version()) > 0;\n count \n-------\n 60000\n\n-- test ChunkAppend with # workers < # childs\nSET max_parallel_workers_per_gather TO 1;\n:PREFIX SELECT count(*) FROM \"test\" WHERE length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 1\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_1_chunk\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT count(*) FROM \"test\" WHERE length(version()) > 0;\n count  \n--------\n 100000\n\n-- test ChunkAppend with # workers > # childs\nSET max_parallel_workers_per_gather TO 2;\n:PREFIX SELECT count(*) FROM \"test\" WHERE i >= 500000 AND length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT count(*) FROM \"test\" WHERE i >= 500000 AND length(version()) > 0;\n count \n-------\n 50000\n\nRESET max_parallel_workers_per_gather;\n-- test partial and non-partial plans\n-- these will not be parallel on PG < 11\nALTER TABLE :CHUNK1 SET (parallel_workers=0);\nALTER TABLE :CHUNK2 SET (parallel_workers=2);\n:PREFIX SELECT count(*) FROM \"test\" WHERE i > 400000 AND length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Index Only Scan using _hyper_1_1_chunk_test_i_idx on _hyper_1_1_chunk\n                                       Index Cond: (i > 400000)\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nALTER TABLE :CHUNK1 SET (parallel_workers=2);\nALTER TABLE :CHUNK2 SET (parallel_workers=0);\n:PREFIX SELECT count(*) FROM \"test\" WHERE i < 600000 AND length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Index Only Scan using _hyper_1_2_chunk_test_i_idx on _hyper_1_2_chunk\n                                       Index Cond: (i < 600000)\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_1_chunk\n\nALTER TABLE :CHUNK1 RESET (parallel_workers);\nALTER TABLE :CHUNK2 RESET (parallel_workers);\n-- now() is not marked parallel safe in PostgreSQL < 12 so using now()\n-- in a query will prevent parallelism but CURRENT_TIMESTAMP and\n-- transaction_timestamp() are marked parallel safe\n:PREFIX SELECT i FROM \"test\" WHERE ts < CURRENT_TIMESTAMP;\n--- QUERY PLAN ---\n Gather\n   Workers Planned: 1\n   Single Copy: true\n   ->  Custom Scan (ChunkAppend) on test\n         Chunks excluded during startup: 0\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (ts < CURRENT_TIMESTAMP)\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (ts < CURRENT_TIMESTAMP)\n\n:PREFIX SELECT i FROM \"test\" WHERE ts < transaction_timestamp();\n--- QUERY PLAN ---\n Gather\n   Workers Planned: 1\n   Single Copy: true\n   ->  Custom Scan (ChunkAppend) on test\n         Chunks excluded during startup: 0\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (ts < transaction_timestamp())\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (ts < transaction_timestamp())\n\n-- this won't be parallel query because now() is parallel restricted in PG < 12\n:PREFIX SELECT i FROM \"test\" WHERE ts < now();\n--- QUERY PLAN ---\n Gather\n   Workers Planned: 1\n   Single Copy: true\n   ->  Custom Scan (ChunkAppend) on test\n         Chunks excluded during startup: 0\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (ts < now())\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (ts < now())\n\n"
  },
  {
    "path": "test/expected/parallel-16.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n--parallel queries require big-ish tables so collect them all here\n--so that we need to generate queries only once.\n-- output with analyze is not stable because it depends on worker assignment\n\\set PREFIX 'EXPLAIN (buffers off, costs off)'\n\\set CHUNK1 _timescaledb_internal._hyper_1_1_chunk\n\\set CHUNK2 _timescaledb_internal._hyper_1_2_chunk\nCREATE TABLE test (i int, j double precision, ts timestamp);\nSELECT create_hypertable('test','i',chunk_time_interval:=500000);\nWARNING:  column type \"timestamp without time zone\" used for \"ts\" does not follow best practices\n create_hypertable \n-------------------\n (1,public,test,t)\n\nINSERT INTO test SELECT x, x+0.1, _timescaledb_functions.to_timestamp(x*1000)  FROM generate_series(0,1000000-1,10) AS x;\nANALYZE test;\nALTER TABLE :CHUNK1 SET (parallel_workers=2);\nALTER TABLE :CHUNK2 SET (parallel_workers=2);\nSET work_mem TO '50MB';\nSELECT set_config(CASE WHEN current_setting('server_version_num')::int < 160000 THEN 'force_parallel_mode' ELSE 'debug_parallel_query' END,'on', false);\n set_config \n------------\n on\n\nSET max_parallel_workers_per_gather = 4;\nSET parallel_setup_cost TO 0;\nEXPLAIN (buffers off, costs off) SELECT first(i, j) FROM \"test\";\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT first(i, j) FROM \"test\";\n first \n-------\n     0\n\nEXPLAIN (buffers off, costs off) SELECT last(i, j) FROM \"test\";\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT last(i, j) FROM \"test\";\n  last  \n--------\n 999990\n\nEXPLAIN (buffers off, costs off) SELECT time_bucket('1 second', ts) sec, last(i, j)\nFROM \"test\"\nGROUP BY sec\nORDER BY sec\nLIMIT 5;\n--- QUERY PLAN ---\n Limit\n   ->  Finalize GroupAggregate\n         Group Key: (time_bucket('@ 1 sec'::interval, test.ts))\n         ->  Gather Merge\n               Workers Planned: 2\n               ->  Partial GroupAggregate\n                     Group Key: (time_bucket('@ 1 sec'::interval, test.ts))\n                     ->  Sort\n                           Sort Key: (time_bucket('@ 1 sec'::interval, test.ts))\n                           ->  Result\n                                 ->  Parallel Append\n                                       ->  Parallel Seq Scan on _hyper_1_1_chunk\n                                       ->  Parallel Seq Scan on _hyper_1_2_chunk\n\n-- test single copy parallel plan with parallel chunk append\n:PREFIX SELECT time_bucket('1 second', ts) sec, last(i, j)\nFROM \"test\"\nWHERE length(version()) > 0\nGROUP BY sec\nORDER BY sec\nLIMIT 5;\n--- QUERY PLAN ---\n Limit\n   ->  Finalize GroupAggregate\n         Group Key: (time_bucket('@ 1 sec'::interval, test.ts))\n         ->  Gather Merge\n               Workers Planned: 2\n               ->  Partial GroupAggregate\n                     Group Key: (time_bucket('@ 1 sec'::interval, test.ts))\n                     ->  Sort\n                           Sort Key: (time_bucket('@ 1 sec'::interval, test.ts))\n                           ->  Result\n                                 ->  Result\n                                       One-Time Filter: (length(version()) > 0)\n                                       ->  Parallel Custom Scan (ChunkAppend) on test\n                                             Chunks excluded during startup: 0\n                                             ->  Result\n                                                   One-Time Filter: (length(version()) > 0)\n                                                   ->  Parallel Seq Scan on _hyper_1_1_chunk\n                                             ->  Result\n                                                   One-Time Filter: (length(version()) > 0)\n                                                   ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT time_bucket('1 second', ts) sec, last(i, j)\nFROM \"test\"\nGROUP BY sec\nORDER BY sec\nLIMIT 5;\n           sec            | last \n--------------------------+------\n Wed Dec 31 16:00:00 1969 |  990\n Wed Dec 31 16:00:01 1969 | 1990\n Wed Dec 31 16:00:02 1969 | 2990\n Wed Dec 31 16:00:03 1969 | 3990\n Wed Dec 31 16:00:04 1969 | 4990\n\n--test variants of histogram\nEXPLAIN (buffers off, costs off) SELECT histogram(i, 1, 1000000, 2) FROM \"test\";\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT histogram(i, 1, 1000000, 2) FROM \"test\";\n     histogram     \n-------------------\n {1,50000,49999,0}\n\nEXPLAIN (buffers off, costs off) SELECT histogram(i, 1,1000001,10) FROM \"test\";\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT histogram(i, 1, 1000001, 10) FROM \"test\";\n                            histogram                             \n------------------------------------------------------------------\n {1,10000,10000,10000,10000,10000,10000,10000,10000,10000,9999,0}\n\nEXPLAIN (buffers off, costs off) SELECT histogram(i, 0,100000,5) FROM \"test\";\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT histogram(i, 0, 100000, 5) FROM \"test\";\n             histogram              \n------------------------------------\n {0,2000,2000,2000,2000,2000,90000}\n\nEXPLAIN (buffers off, costs off) SELECT histogram(i, 10,100000,5) FROM \"test\";\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT histogram(i, 10, 100000, 5) FROM \"test\";\n             histogram              \n------------------------------------\n {1,2000,2000,2000,2000,1999,90000}\n\nEXPLAIN (buffers off, costs off) SELECT histogram(NULL, 10,100000,5) FROM \"test\" WHERE  i = coalesce(-1,j);\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                           Filter: ((i)::double precision = '-1'::double precision)\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n                           Filter: ((i)::double precision = '-1'::double precision)\n\nSELECT histogram(NULL, 10,100000,5) FROM \"test\" WHERE  i = coalesce(-1,j);\n histogram \n-----------\n \n\n-- test parallel ChunkAppend\n:PREFIX SELECT i FROM \"test\" WHERE length(version()) > 0;\n--- QUERY PLAN ---\n Gather\n   Workers Planned: 1\n   Single Copy: true\n   ->  Result\n         One-Time Filter: (length(version()) > 0)\n         ->  Custom Scan (ChunkAppend) on test\n               Chunks excluded during startup: 0\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Seq Scan on _hyper_1_1_chunk\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Seq Scan on _hyper_1_2_chunk\n\n:PREFIX SELECT count(*) FROM \"test\" WHERE i > 1 AND length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_1_chunk\n                                       Filter: (i > 1)\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT count(*) FROM \"test\" WHERE i > 1 AND length(version()) > 0;\n count \n-------\n 99999\n\n-- test parallel ChunkAppend with only work done in the parallel workers\nSET parallel_leader_participation = off;\nSELECT count(*) FROM \"test\" WHERE i > 1 AND length(version()) > 0;\n count \n-------\n 99999\n\nRESET parallel_leader_participation;\n-- Test parallel chunk append is used (index scan is disabled to trigger a parallel chunk append)\nSET parallel_tuple_cost = 0;\nSET enable_indexscan = OFF;\n:PREFIX SELECT * FROM (SELECT * FROM \"test\" WHERE length(version()) > 0 ORDER BY I LIMIT 10) AS t1 LEFT JOIN (SELECT * FROM \"test\" WHERE i < 500000 ORDER BY I LIMIT 10) AS t2 ON (t1.i = t2.i) ORDER BY t1.i, t2.i;\n--- QUERY PLAN ---\n Incremental Sort\n   Sort Key: test.i, _hyper_1_1_chunk_1.i\n   Presorted Key: test.i\n   ->  Merge Left Join\n         Merge Cond: (test.i = _hyper_1_1_chunk_1.i)\n         ->  Limit\n               ->  Gather Merge\n                     Workers Planned: 2\n                     ->  Sort\n                           Sort Key: test.i\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Custom Scan (ChunkAppend) on test\n                                       Chunks excluded during startup: 0\n                                       ->  Result\n                                             One-Time Filter: (length(version()) > 0)\n                                             ->  Parallel Seq Scan on _hyper_1_1_chunk\n                                       ->  Result\n                                             One-Time Filter: (length(version()) > 0)\n                                             ->  Parallel Seq Scan on _hyper_1_2_chunk\n         ->  Materialize\n               ->  Limit\n                     ->  Gather Merge\n                           Workers Planned: 2\n                           ->  Sort\n                                 Sort Key: _hyper_1_1_chunk_1.i\n                                 ->  Parallel Seq Scan on _hyper_1_1_chunk _hyper_1_1_chunk_1\n\nSELECT * FROM (SELECT * FROM \"test\" WHERE length(version()) > 0 ORDER BY I LIMIT 10) AS t1 LEFT JOIN (SELECT * FROM \"test\" WHERE i < 500000 ORDER BY I LIMIT 10) AS t2 ON (t1.i = t2.i) ORDER BY t1.i, t2.i;\n i  |  j   |             ts              | i  |  j   |             ts              \n----+------+-----------------------------+----+------+-----------------------------\n  0 |  0.1 | Wed Dec 31 16:00:00 1969    |  0 |  0.1 | Wed Dec 31 16:00:00 1969\n 10 | 10.1 | Wed Dec 31 16:00:00.01 1969 | 10 | 10.1 | Wed Dec 31 16:00:00.01 1969\n 20 | 20.1 | Wed Dec 31 16:00:00.02 1969 | 20 | 20.1 | Wed Dec 31 16:00:00.02 1969\n 30 | 30.1 | Wed Dec 31 16:00:00.03 1969 | 30 | 30.1 | Wed Dec 31 16:00:00.03 1969\n 40 | 40.1 | Wed Dec 31 16:00:00.04 1969 | 40 | 40.1 | Wed Dec 31 16:00:00.04 1969\n 50 | 50.1 | Wed Dec 31 16:00:00.05 1969 | 50 | 50.1 | Wed Dec 31 16:00:00.05 1969\n 60 | 60.1 | Wed Dec 31 16:00:00.06 1969 | 60 | 60.1 | Wed Dec 31 16:00:00.06 1969\n 70 | 70.1 | Wed Dec 31 16:00:00.07 1969 | 70 | 70.1 | Wed Dec 31 16:00:00.07 1969\n 80 | 80.1 | Wed Dec 31 16:00:00.08 1969 | 80 | 80.1 | Wed Dec 31 16:00:00.08 1969\n 90 | 90.1 | Wed Dec 31 16:00:00.09 1969 | 90 | 90.1 | Wed Dec 31 16:00:00.09 1969\n\nSET enable_indexscan = ON;\n-- Test normal chunk append can be used in a parallel worker\n:PREFIX SELECT * FROM (SELECT * FROM \"test\" WHERE i >= 999000 ORDER BY i) AS t1 JOIN (SELECT * FROM \"test\" WHERE i >= 400000 ORDER BY i) AS t2 ON (TRUE) ORDER BY t1.i, t2.i LIMIT 10;\n--- QUERY PLAN ---\n Gather\n   Workers Planned: 1\n   Single Copy: true\n   ->  Limit\n         ->  Incremental Sort\n               Sort Key: _hyper_1_2_chunk.i, test.i\n               Presorted Key: _hyper_1_2_chunk.i\n               ->  Nested Loop\n                     ->  Index Scan Backward using _hyper_1_2_chunk_test_i_idx on _hyper_1_2_chunk\n                           Index Cond: (i >= 999000)\n                     ->  Materialize\n                           ->  Custom Scan (ChunkAppend) on test\n                                 Order: test.i\n                                 ->  Index Scan Backward using _hyper_1_1_chunk_test_i_idx on _hyper_1_1_chunk\n                                       Index Cond: (i >= 400000)\n                                 ->  Index Scan Backward using _hyper_1_2_chunk_test_i_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1\n\nSELECT * FROM (SELECT * FROM \"test\" WHERE i >= 999000 ORDER BY i) AS t1 JOIN (SELECT * FROM \"test\" WHERE i >= 400000 ORDER BY i) AS t2 ON (TRUE) ORDER BY t1.i, t2.i LIMIT 10;\n   i    |    j     |            ts            |   i    |    j     |             ts              \n--------+----------+--------------------------+--------+----------+-----------------------------\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400000 | 400000.1 | Wed Dec 31 16:06:40 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400010 | 400010.1 | Wed Dec 31 16:06:40.01 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400020 | 400020.1 | Wed Dec 31 16:06:40.02 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400030 | 400030.1 | Wed Dec 31 16:06:40.03 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400040 | 400040.1 | Wed Dec 31 16:06:40.04 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400050 | 400050.1 | Wed Dec 31 16:06:40.05 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400060 | 400060.1 | Wed Dec 31 16:06:40.06 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400070 | 400070.1 | Wed Dec 31 16:06:40.07 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400080 | 400080.1 | Wed Dec 31 16:06:40.08 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400090 | 400090.1 | Wed Dec 31 16:06:40.09 1969\n\n-- Test parallel ChunkAppend reinit\nSET enable_material = off;\nSET min_parallel_table_scan_size = 0;\nSET min_parallel_index_scan_size = 0;\nSET enable_hashjoin = 'off';\nSET enable_nestloop = 'off';\nCREATE TABLE sensor_data(\n      time timestamptz NOT NULL,\n      sensor_id integer NOT NULL);\nSELECT FROM create_hypertable(relation=>'sensor_data', time_column_name=> 'time');\n--\n\n-- Sensors 1 and 2\nINSERT INTO sensor_data\nSELECT time, sensor_id\nFROM\ngenerate_series('2000-01-01 00:00:30', '2022-01-01 00:00:30', INTERVAL '3 months') AS g1(time),\ngenerate_series(1, 2, 1) AS g2(sensor_id)\nORDER BY time;\n-- Sensor 100\nINSERT INTO sensor_data\nSELECT time, 100 as sensor_id\nFROM\ngenerate_series('2000-01-01 00:00:30', '2022-01-01 00:00:30', INTERVAL '1 year') AS g1(time)\nORDER BY time;\n:PREFIX SELECT * FROM sensor_data AS s1 JOIN sensor_data AS s2 ON (TRUE) WHERE s1.time > '2020-01-01 00:00:30'::text::timestamptz AND s2.time > '2020-01-01 00:00:30' AND s2.time < '2021-01-01 00:00:30' AND s1.sensor_id > 50 ORDER BY s2.time, s1.time, s1.sensor_id, s2.sensor_id;\n--- QUERY PLAN ---\n Incremental Sort\n   Sort Key: s2.\"time\", s1.\"time\", s1.sensor_id, s2.sensor_id\n   Presorted Key: s2.\"time\"\n   ->  Nested Loop\n         ->  Custom Scan (ChunkAppend) on sensor_data s2\n               Order: s2.\"time\"\n               ->  Index Scan Backward using _hyper_2_83_chunk_sensor_data_time_idx on _hyper_2_83_chunk s2_1\n                     Index Cond: ((\"time\" > 'Wed Jan 01 00:00:30 2020 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 01 00:00:30 2021 PST'::timestamp with time zone))\n               ->  Index Scan Backward using _hyper_2_84_chunk_sensor_data_time_idx on _hyper_2_84_chunk s2_2\n               ->  Index Scan Backward using _hyper_2_85_chunk_sensor_data_time_idx on _hyper_2_85_chunk s2_3\n               ->  Index Scan Backward using _hyper_2_86_chunk_sensor_data_time_idx on _hyper_2_86_chunk s2_4\n               ->  Index Scan Backward using _hyper_2_87_chunk_sensor_data_time_idx on _hyper_2_87_chunk s2_5\n                     Index Cond: ((\"time\" > 'Wed Jan 01 00:00:30 2020 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 01 00:00:30 2021 PST'::timestamp with time zone))\n         ->  Gather\n               Workers Planned: 4\n               ->  Parallel Custom Scan (ChunkAppend) on sensor_data s1\n                     Chunks excluded during startup: 80\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_83_chunk s1_1\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_83_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_84_chunk s1_2\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_84_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_85_chunk s1_3\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_85_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_86_chunk s1_4\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_86_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_87_chunk s1_5\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_87_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_88_chunk s1_6\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_88_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_89_chunk s1_7\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_89_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_90_chunk s1_8\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_90_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_91_chunk s1_9\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_91_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n\n-- Check query result\nSELECT * FROM sensor_data AS s1 JOIN sensor_data AS s2 ON (TRUE) WHERE s1.time > '2020-01-01 00:00:30'::text::timestamptz AND s2.time > '2020-01-01 00:00:30' AND s2.time < '2021-01-01 00:00:30' AND s1.sensor_id > 50 ORDER BY s2.time, s1.time, s1.sensor_id, s2.sensor_id;\n             time             | sensor_id |             time             | sensor_id \n------------------------------+-----------+------------------------------+-----------\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         2\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         2\n Fri Jan 01 00:00:30 2021 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         2\n\n-- Ensure the same result is produced if only the parallel workers have to produce them (i.e., the pstate is reinitialized properly)\nSET parallel_leader_participation = off;\nSELECT * FROM sensor_data AS s1 JOIN sensor_data AS s2 ON (TRUE) WHERE s1.time > '2020-01-01 00:00:30'::text::timestamptz AND s2.time > '2020-01-01 00:00:30' AND s2.time < '2021-01-01 00:00:30' AND s1.sensor_id > 50 ORDER BY s2.time, s1.time, s1.sensor_id, s2.sensor_id;\n             time             | sensor_id |             time             | sensor_id \n------------------------------+-----------+------------------------------+-----------\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         2\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         2\n Fri Jan 01 00:00:30 2021 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         2\n\nRESET parallel_leader_participation;\n-- Ensure the same query result is produced by a sequencial query\nSET max_parallel_workers_per_gather TO 0;\nSELECT set_config(CASE WHEN current_setting('server_version_num')::int < 160000 THEN 'force_parallel_mode' ELSE 'debug_parallel_query' END,'off', false);\n set_config \n------------\n off\n\nSELECT * FROM sensor_data AS s1 JOIN sensor_data AS s2 ON (TRUE) WHERE s1.time > '2020-01-01 00:00:30'::text::timestamptz AND s2.time > '2020-01-01 00:00:30' AND s2.time < '2021-01-01 00:00:30' AND s1.sensor_id > 50 ORDER BY s2.time, s1.time, s1.sensor_id, s2.sensor_id;\n             time             | sensor_id |             time             | sensor_id \n------------------------------+-----------+------------------------------+-----------\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         2\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         2\n Fri Jan 01 00:00:30 2021 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         2\n\nRESET enable_material;\nRESET min_parallel_table_scan_size;\nRESET min_parallel_index_scan_size;\nRESET enable_hashjoin;\nRESET enable_nestloop;\nRESET parallel_tuple_cost;\nSELECT set_config(CASE WHEN current_setting('server_version_num')::int < 160000 THEN 'force_parallel_mode' ELSE 'debug_parallel_query' END,'on', false);\n set_config \n------------\n on\n\n-- test worker assignment\n-- first chunk should have 1 worker and second chunk should have 2\nSET max_parallel_workers_per_gather TO 2;\n:PREFIX SELECT count(*) FROM \"test\" WHERE i >= 400000 AND length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Index Only Scan using _hyper_1_1_chunk_test_i_idx on _hyper_1_1_chunk\n                                       Index Cond: (i >= 400000)\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT count(*) FROM \"test\" WHERE i >= 400000 AND length(version()) > 0;\n count \n-------\n 60000\n\n-- test worker assignment\n-- first chunk should have 2 worker and second chunk should have 1\n:PREFIX SELECT count(*) FROM \"test\" WHERE i < 600000 AND length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Index Only Scan using _hyper_1_2_chunk_test_i_idx on _hyper_1_2_chunk\n                                       Index Cond: (i < 600000)\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_1_chunk\n\nSELECT count(*) FROM \"test\" WHERE i < 600000 AND length(version()) > 0;\n count \n-------\n 60000\n\n-- test ChunkAppend with # workers < # childs\nSET max_parallel_workers_per_gather TO 1;\n:PREFIX SELECT count(*) FROM \"test\" WHERE length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 1\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_1_chunk\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT count(*) FROM \"test\" WHERE length(version()) > 0;\n count  \n--------\n 100000\n\n-- test ChunkAppend with # workers > # childs\nSET max_parallel_workers_per_gather TO 2;\n:PREFIX SELECT count(*) FROM \"test\" WHERE i >= 500000 AND length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT count(*) FROM \"test\" WHERE i >= 500000 AND length(version()) > 0;\n count \n-------\n 50000\n\nRESET max_parallel_workers_per_gather;\n-- test partial and non-partial plans\n-- these will not be parallel on PG < 11\nALTER TABLE :CHUNK1 SET (parallel_workers=0);\nALTER TABLE :CHUNK2 SET (parallel_workers=2);\n:PREFIX SELECT count(*) FROM \"test\" WHERE i > 400000 AND length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Index Only Scan using _hyper_1_1_chunk_test_i_idx on _hyper_1_1_chunk\n                                       Index Cond: (i > 400000)\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nALTER TABLE :CHUNK1 SET (parallel_workers=2);\nALTER TABLE :CHUNK2 SET (parallel_workers=0);\n:PREFIX SELECT count(*) FROM \"test\" WHERE i < 600000 AND length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Index Only Scan using _hyper_1_2_chunk_test_i_idx on _hyper_1_2_chunk\n                                       Index Cond: (i < 600000)\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_1_chunk\n\nALTER TABLE :CHUNK1 RESET (parallel_workers);\nALTER TABLE :CHUNK2 RESET (parallel_workers);\n-- now() is not marked parallel safe in PostgreSQL < 12 so using now()\n-- in a query will prevent parallelism but CURRENT_TIMESTAMP and\n-- transaction_timestamp() are marked parallel safe\n:PREFIX SELECT i FROM \"test\" WHERE ts < CURRENT_TIMESTAMP;\n--- QUERY PLAN ---\n Gather\n   Workers Planned: 1\n   Single Copy: true\n   ->  Custom Scan (ChunkAppend) on test\n         Chunks excluded during startup: 0\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (ts < CURRENT_TIMESTAMP)\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (ts < CURRENT_TIMESTAMP)\n\n:PREFIX SELECT i FROM \"test\" WHERE ts < transaction_timestamp();\n--- QUERY PLAN ---\n Gather\n   Workers Planned: 1\n   Single Copy: true\n   ->  Custom Scan (ChunkAppend) on test\n         Chunks excluded during startup: 0\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (ts < transaction_timestamp())\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (ts < transaction_timestamp())\n\n-- this won't be parallel query because now() is parallel restricted in PG < 12\n:PREFIX SELECT i FROM \"test\" WHERE ts < now();\n--- QUERY PLAN ---\n Gather\n   Workers Planned: 1\n   Single Copy: true\n   ->  Custom Scan (ChunkAppend) on test\n         Chunks excluded during startup: 0\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (ts < now())\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (ts < now())\n\n"
  },
  {
    "path": "test/expected/parallel-17.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n--parallel queries require big-ish tables so collect them all here\n--so that we need to generate queries only once.\n-- output with analyze is not stable because it depends on worker assignment\n\\set PREFIX 'EXPLAIN (buffers off, costs off)'\n\\set CHUNK1 _timescaledb_internal._hyper_1_1_chunk\n\\set CHUNK2 _timescaledb_internal._hyper_1_2_chunk\nCREATE TABLE test (i int, j double precision, ts timestamp);\nSELECT create_hypertable('test','i',chunk_time_interval:=500000);\nWARNING:  column type \"timestamp without time zone\" used for \"ts\" does not follow best practices\n create_hypertable \n-------------------\n (1,public,test,t)\n\nINSERT INTO test SELECT x, x+0.1, _timescaledb_functions.to_timestamp(x*1000)  FROM generate_series(0,1000000-1,10) AS x;\nANALYZE test;\nALTER TABLE :CHUNK1 SET (parallel_workers=2);\nALTER TABLE :CHUNK2 SET (parallel_workers=2);\nSET work_mem TO '50MB';\nSELECT set_config(CASE WHEN current_setting('server_version_num')::int < 160000 THEN 'force_parallel_mode' ELSE 'debug_parallel_query' END,'on', false);\n set_config \n------------\n on\n\nSET max_parallel_workers_per_gather = 4;\nSET parallel_setup_cost TO 0;\nEXPLAIN (buffers off, costs off) SELECT first(i, j) FROM \"test\";\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT first(i, j) FROM \"test\";\n first \n-------\n     0\n\nEXPLAIN (buffers off, costs off) SELECT last(i, j) FROM \"test\";\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT last(i, j) FROM \"test\";\n  last  \n--------\n 999990\n\nEXPLAIN (buffers off, costs off) SELECT time_bucket('1 second', ts) sec, last(i, j)\nFROM \"test\"\nGROUP BY sec\nORDER BY sec\nLIMIT 5;\n--- QUERY PLAN ---\n Limit\n   ->  Finalize GroupAggregate\n         Group Key: (time_bucket('@ 1 sec'::interval, test.ts))\n         ->  Gather Merge\n               Workers Planned: 2\n               ->  Partial GroupAggregate\n                     Group Key: (time_bucket('@ 1 sec'::interval, test.ts))\n                     ->  Sort\n                           Sort Key: (time_bucket('@ 1 sec'::interval, test.ts))\n                           ->  Result\n                                 ->  Parallel Append\n                                       ->  Parallel Seq Scan on _hyper_1_1_chunk\n                                       ->  Parallel Seq Scan on _hyper_1_2_chunk\n\n-- test single copy parallel plan with parallel chunk append\n:PREFIX SELECT time_bucket('1 second', ts) sec, last(i, j)\nFROM \"test\"\nWHERE length(version()) > 0\nGROUP BY sec\nORDER BY sec\nLIMIT 5;\n--- QUERY PLAN ---\n Limit\n   ->  Finalize GroupAggregate\n         Group Key: (time_bucket('@ 1 sec'::interval, test.ts))\n         ->  Gather Merge\n               Workers Planned: 2\n               ->  Partial GroupAggregate\n                     Group Key: (time_bucket('@ 1 sec'::interval, test.ts))\n                     ->  Sort\n                           Sort Key: (time_bucket('@ 1 sec'::interval, test.ts))\n                           ->  Result\n                                 ->  Result\n                                       One-Time Filter: (length(version()) > 0)\n                                       ->  Parallel Custom Scan (ChunkAppend) on test\n                                             Chunks excluded during startup: 0\n                                             ->  Result\n                                                   One-Time Filter: (length(version()) > 0)\n                                                   ->  Parallel Seq Scan on _hyper_1_1_chunk\n                                             ->  Result\n                                                   One-Time Filter: (length(version()) > 0)\n                                                   ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT time_bucket('1 second', ts) sec, last(i, j)\nFROM \"test\"\nGROUP BY sec\nORDER BY sec\nLIMIT 5;\n           sec            | last \n--------------------------+------\n Wed Dec 31 16:00:00 1969 |  990\n Wed Dec 31 16:00:01 1969 | 1990\n Wed Dec 31 16:00:02 1969 | 2990\n Wed Dec 31 16:00:03 1969 | 3990\n Wed Dec 31 16:00:04 1969 | 4990\n\n--test variants of histogram\nEXPLAIN (buffers off, costs off) SELECT histogram(i, 1, 1000000, 2) FROM \"test\";\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT histogram(i, 1, 1000000, 2) FROM \"test\";\n     histogram     \n-------------------\n {1,50000,49999,0}\n\nEXPLAIN (buffers off, costs off) SELECT histogram(i, 1,1000001,10) FROM \"test\";\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT histogram(i, 1, 1000001, 10) FROM \"test\";\n                            histogram                             \n------------------------------------------------------------------\n {1,10000,10000,10000,10000,10000,10000,10000,10000,10000,9999,0}\n\nEXPLAIN (buffers off, costs off) SELECT histogram(i, 0,100000,5) FROM \"test\";\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT histogram(i, 0, 100000, 5) FROM \"test\";\n             histogram              \n------------------------------------\n {0,2000,2000,2000,2000,2000,90000}\n\nEXPLAIN (buffers off, costs off) SELECT histogram(i, 10,100000,5) FROM \"test\";\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT histogram(i, 10, 100000, 5) FROM \"test\";\n             histogram              \n------------------------------------\n {1,2000,2000,2000,2000,1999,90000}\n\nEXPLAIN (buffers off, costs off) SELECT histogram(NULL, 10,100000,5) FROM \"test\" WHERE  i = coalesce(-1,j);\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                           Filter: ((i)::double precision = '-1'::double precision)\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n                           Filter: ((i)::double precision = '-1'::double precision)\n\nSELECT histogram(NULL, 10,100000,5) FROM \"test\" WHERE  i = coalesce(-1,j);\n histogram \n-----------\n \n\n-- test parallel ChunkAppend\n:PREFIX SELECT i FROM \"test\" WHERE length(version()) > 0;\n--- QUERY PLAN ---\n Gather\n   Workers Planned: 1\n   Single Copy: true\n   ->  Result\n         One-Time Filter: (length(version()) > 0)\n         ->  Custom Scan (ChunkAppend) on test\n               Chunks excluded during startup: 0\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Seq Scan on _hyper_1_1_chunk\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Seq Scan on _hyper_1_2_chunk\n\n:PREFIX SELECT count(*) FROM \"test\" WHERE i > 1 AND length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_1_chunk\n                                       Filter: (i > 1)\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT count(*) FROM \"test\" WHERE i > 1 AND length(version()) > 0;\n count \n-------\n 99999\n\n-- test parallel ChunkAppend with only work done in the parallel workers\nSET parallel_leader_participation = off;\nSELECT count(*) FROM \"test\" WHERE i > 1 AND length(version()) > 0;\n count \n-------\n 99999\n\nRESET parallel_leader_participation;\n-- Test parallel chunk append is used (index scan is disabled to trigger a parallel chunk append)\nSET parallel_tuple_cost = 0;\nSET enable_indexscan = OFF;\n:PREFIX SELECT * FROM (SELECT * FROM \"test\" WHERE length(version()) > 0 ORDER BY I LIMIT 10) AS t1 LEFT JOIN (SELECT * FROM \"test\" WHERE i < 500000 ORDER BY I LIMIT 10) AS t2 ON (t1.i = t2.i) ORDER BY t1.i, t2.i;\n--- QUERY PLAN ---\n Incremental Sort\n   Sort Key: test.i, _hyper_1_1_chunk_1.i\n   Presorted Key: test.i\n   ->  Merge Left Join\n         Merge Cond: (test.i = _hyper_1_1_chunk_1.i)\n         ->  Limit\n               ->  Gather Merge\n                     Workers Planned: 2\n                     ->  Sort\n                           Sort Key: test.i\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Custom Scan (ChunkAppend) on test\n                                       Chunks excluded during startup: 0\n                                       ->  Result\n                                             One-Time Filter: (length(version()) > 0)\n                                             ->  Parallel Seq Scan on _hyper_1_1_chunk\n                                       ->  Result\n                                             One-Time Filter: (length(version()) > 0)\n                                             ->  Parallel Seq Scan on _hyper_1_2_chunk\n         ->  Materialize\n               ->  Limit\n                     ->  Gather Merge\n                           Workers Planned: 2\n                           ->  Sort\n                                 Sort Key: _hyper_1_1_chunk_1.i\n                                 ->  Parallel Seq Scan on _hyper_1_1_chunk _hyper_1_1_chunk_1\n\nSELECT * FROM (SELECT * FROM \"test\" WHERE length(version()) > 0 ORDER BY I LIMIT 10) AS t1 LEFT JOIN (SELECT * FROM \"test\" WHERE i < 500000 ORDER BY I LIMIT 10) AS t2 ON (t1.i = t2.i) ORDER BY t1.i, t2.i;\n i  |  j   |             ts              | i  |  j   |             ts              \n----+------+-----------------------------+----+------+-----------------------------\n  0 |  0.1 | Wed Dec 31 16:00:00 1969    |  0 |  0.1 | Wed Dec 31 16:00:00 1969\n 10 | 10.1 | Wed Dec 31 16:00:00.01 1969 | 10 | 10.1 | Wed Dec 31 16:00:00.01 1969\n 20 | 20.1 | Wed Dec 31 16:00:00.02 1969 | 20 | 20.1 | Wed Dec 31 16:00:00.02 1969\n 30 | 30.1 | Wed Dec 31 16:00:00.03 1969 | 30 | 30.1 | Wed Dec 31 16:00:00.03 1969\n 40 | 40.1 | Wed Dec 31 16:00:00.04 1969 | 40 | 40.1 | Wed Dec 31 16:00:00.04 1969\n 50 | 50.1 | Wed Dec 31 16:00:00.05 1969 | 50 | 50.1 | Wed Dec 31 16:00:00.05 1969\n 60 | 60.1 | Wed Dec 31 16:00:00.06 1969 | 60 | 60.1 | Wed Dec 31 16:00:00.06 1969\n 70 | 70.1 | Wed Dec 31 16:00:00.07 1969 | 70 | 70.1 | Wed Dec 31 16:00:00.07 1969\n 80 | 80.1 | Wed Dec 31 16:00:00.08 1969 | 80 | 80.1 | Wed Dec 31 16:00:00.08 1969\n 90 | 90.1 | Wed Dec 31 16:00:00.09 1969 | 90 | 90.1 | Wed Dec 31 16:00:00.09 1969\n\nSET enable_indexscan = ON;\n-- Test normal chunk append can be used in a parallel worker\n:PREFIX SELECT * FROM (SELECT * FROM \"test\" WHERE i >= 999000 ORDER BY i) AS t1 JOIN (SELECT * FROM \"test\" WHERE i >= 400000 ORDER BY i) AS t2 ON (TRUE) ORDER BY t1.i, t2.i LIMIT 10;\n--- QUERY PLAN ---\n Gather\n   Workers Planned: 1\n   Single Copy: true\n   ->  Limit\n         ->  Incremental Sort\n               Sort Key: _hyper_1_2_chunk.i, test.i\n               Presorted Key: _hyper_1_2_chunk.i\n               ->  Nested Loop\n                     ->  Index Scan Backward using _hyper_1_2_chunk_test_i_idx on _hyper_1_2_chunk\n                           Index Cond: (i >= 999000)\n                     ->  Materialize\n                           ->  Custom Scan (ChunkAppend) on test\n                                 Order: test.i\n                                 ->  Index Scan Backward using _hyper_1_1_chunk_test_i_idx on _hyper_1_1_chunk\n                                       Index Cond: (i >= 400000)\n                                 ->  Index Scan Backward using _hyper_1_2_chunk_test_i_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1\n\nSELECT * FROM (SELECT * FROM \"test\" WHERE i >= 999000 ORDER BY i) AS t1 JOIN (SELECT * FROM \"test\" WHERE i >= 400000 ORDER BY i) AS t2 ON (TRUE) ORDER BY t1.i, t2.i LIMIT 10;\n   i    |    j     |            ts            |   i    |    j     |             ts              \n--------+----------+--------------------------+--------+----------+-----------------------------\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400000 | 400000.1 | Wed Dec 31 16:06:40 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400010 | 400010.1 | Wed Dec 31 16:06:40.01 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400020 | 400020.1 | Wed Dec 31 16:06:40.02 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400030 | 400030.1 | Wed Dec 31 16:06:40.03 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400040 | 400040.1 | Wed Dec 31 16:06:40.04 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400050 | 400050.1 | Wed Dec 31 16:06:40.05 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400060 | 400060.1 | Wed Dec 31 16:06:40.06 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400070 | 400070.1 | Wed Dec 31 16:06:40.07 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400080 | 400080.1 | Wed Dec 31 16:06:40.08 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400090 | 400090.1 | Wed Dec 31 16:06:40.09 1969\n\n-- Test parallel ChunkAppend reinit\nSET enable_material = off;\nSET min_parallel_table_scan_size = 0;\nSET min_parallel_index_scan_size = 0;\nSET enable_hashjoin = 'off';\nSET enable_nestloop = 'off';\nCREATE TABLE sensor_data(\n      time timestamptz NOT NULL,\n      sensor_id integer NOT NULL);\nSELECT FROM create_hypertable(relation=>'sensor_data', time_column_name=> 'time');\n--\n\n-- Sensors 1 and 2\nINSERT INTO sensor_data\nSELECT time, sensor_id\nFROM\ngenerate_series('2000-01-01 00:00:30', '2022-01-01 00:00:30', INTERVAL '3 months') AS g1(time),\ngenerate_series(1, 2, 1) AS g2(sensor_id)\nORDER BY time;\n-- Sensor 100\nINSERT INTO sensor_data\nSELECT time, 100 as sensor_id\nFROM\ngenerate_series('2000-01-01 00:00:30', '2022-01-01 00:00:30', INTERVAL '1 year') AS g1(time)\nORDER BY time;\n:PREFIX SELECT * FROM sensor_data AS s1 JOIN sensor_data AS s2 ON (TRUE) WHERE s1.time > '2020-01-01 00:00:30'::text::timestamptz AND s2.time > '2020-01-01 00:00:30' AND s2.time < '2021-01-01 00:00:30' AND s1.sensor_id > 50 ORDER BY s2.time, s1.time, s1.sensor_id, s2.sensor_id;\n--- QUERY PLAN ---\n Incremental Sort\n   Sort Key: s2.\"time\", s1.\"time\", s1.sensor_id, s2.sensor_id\n   Presorted Key: s2.\"time\"\n   ->  Nested Loop\n         ->  Custom Scan (ChunkAppend) on sensor_data s2\n               Order: s2.\"time\"\n               ->  Index Scan Backward using _hyper_2_83_chunk_sensor_data_time_idx on _hyper_2_83_chunk s2_1\n                     Index Cond: ((\"time\" > 'Wed Jan 01 00:00:30 2020 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 01 00:00:30 2021 PST'::timestamp with time zone))\n               ->  Index Scan Backward using _hyper_2_84_chunk_sensor_data_time_idx on _hyper_2_84_chunk s2_2\n               ->  Index Scan Backward using _hyper_2_85_chunk_sensor_data_time_idx on _hyper_2_85_chunk s2_3\n               ->  Index Scan Backward using _hyper_2_86_chunk_sensor_data_time_idx on _hyper_2_86_chunk s2_4\n               ->  Index Scan Backward using _hyper_2_87_chunk_sensor_data_time_idx on _hyper_2_87_chunk s2_5\n                     Index Cond: ((\"time\" > 'Wed Jan 01 00:00:30 2020 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 01 00:00:30 2021 PST'::timestamp with time zone))\n         ->  Gather\n               Workers Planned: 4\n               ->  Parallel Custom Scan (ChunkAppend) on sensor_data s1\n                     Chunks excluded during startup: 80\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_83_chunk s1_1\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_83_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_84_chunk s1_2\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_84_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_85_chunk s1_3\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_85_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_86_chunk s1_4\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_86_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_87_chunk s1_5\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_87_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_88_chunk s1_6\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_88_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_89_chunk s1_7\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_89_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_90_chunk s1_8\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_90_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     ->  Parallel Bitmap Heap Scan on _hyper_2_91_chunk s1_9\n                           Recheck Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                           Filter: (sensor_id > 50)\n                           ->  Bitmap Index Scan on _hyper_2_91_chunk_sensor_data_time_idx\n                                 Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n\n-- Check query result\nSELECT * FROM sensor_data AS s1 JOIN sensor_data AS s2 ON (TRUE) WHERE s1.time > '2020-01-01 00:00:30'::text::timestamptz AND s2.time > '2020-01-01 00:00:30' AND s2.time < '2021-01-01 00:00:30' AND s1.sensor_id > 50 ORDER BY s2.time, s1.time, s1.sensor_id, s2.sensor_id;\n             time             | sensor_id |             time             | sensor_id \n------------------------------+-----------+------------------------------+-----------\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         2\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         2\n Fri Jan 01 00:00:30 2021 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         2\n\n-- Ensure the same result is produced if only the parallel workers have to produce them (i.e., the pstate is reinitialized properly)\nSET parallel_leader_participation = off;\nSELECT * FROM sensor_data AS s1 JOIN sensor_data AS s2 ON (TRUE) WHERE s1.time > '2020-01-01 00:00:30'::text::timestamptz AND s2.time > '2020-01-01 00:00:30' AND s2.time < '2021-01-01 00:00:30' AND s1.sensor_id > 50 ORDER BY s2.time, s1.time, s1.sensor_id, s2.sensor_id;\n             time             | sensor_id |             time             | sensor_id \n------------------------------+-----------+------------------------------+-----------\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         2\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         2\n Fri Jan 01 00:00:30 2021 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         2\n\nRESET parallel_leader_participation;\n-- Ensure the same query result is produced by a sequencial query\nSET max_parallel_workers_per_gather TO 0;\nSELECT set_config(CASE WHEN current_setting('server_version_num')::int < 160000 THEN 'force_parallel_mode' ELSE 'debug_parallel_query' END,'off', false);\n set_config \n------------\n off\n\nSELECT * FROM sensor_data AS s1 JOIN sensor_data AS s2 ON (TRUE) WHERE s1.time > '2020-01-01 00:00:30'::text::timestamptz AND s2.time > '2020-01-01 00:00:30' AND s2.time < '2021-01-01 00:00:30' AND s1.sensor_id > 50 ORDER BY s2.time, s1.time, s1.sensor_id, s2.sensor_id;\n             time             | sensor_id |             time             | sensor_id \n------------------------------+-----------+------------------------------+-----------\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         2\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         2\n Fri Jan 01 00:00:30 2021 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         2\n\nRESET enable_material;\nRESET min_parallel_table_scan_size;\nRESET min_parallel_index_scan_size;\nRESET enable_hashjoin;\nRESET enable_nestloop;\nRESET parallel_tuple_cost;\nSELECT set_config(CASE WHEN current_setting('server_version_num')::int < 160000 THEN 'force_parallel_mode' ELSE 'debug_parallel_query' END,'on', false);\n set_config \n------------\n on\n\n-- test worker assignment\n-- first chunk should have 1 worker and second chunk should have 2\nSET max_parallel_workers_per_gather TO 2;\n:PREFIX SELECT count(*) FROM \"test\" WHERE i >= 400000 AND length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Index Only Scan using _hyper_1_1_chunk_test_i_idx on _hyper_1_1_chunk\n                                       Index Cond: (i >= 400000)\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT count(*) FROM \"test\" WHERE i >= 400000 AND length(version()) > 0;\n count \n-------\n 60000\n\n-- test worker assignment\n-- first chunk should have 2 worker and second chunk should have 1\n:PREFIX SELECT count(*) FROM \"test\" WHERE i < 600000 AND length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Index Only Scan using _hyper_1_2_chunk_test_i_idx on _hyper_1_2_chunk\n                                       Index Cond: (i < 600000)\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_1_chunk\n\nSELECT count(*) FROM \"test\" WHERE i < 600000 AND length(version()) > 0;\n count \n-------\n 60000\n\n-- test ChunkAppend with # workers < # childs\nSET max_parallel_workers_per_gather TO 1;\n:PREFIX SELECT count(*) FROM \"test\" WHERE length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 1\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_1_chunk\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT count(*) FROM \"test\" WHERE length(version()) > 0;\n count  \n--------\n 100000\n\n-- test ChunkAppend with # workers > # childs\nSET max_parallel_workers_per_gather TO 2;\n:PREFIX SELECT count(*) FROM \"test\" WHERE i >= 500000 AND length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT count(*) FROM \"test\" WHERE i >= 500000 AND length(version()) > 0;\n count \n-------\n 50000\n\nRESET max_parallel_workers_per_gather;\n-- test partial and non-partial plans\n-- these will not be parallel on PG < 11\nALTER TABLE :CHUNK1 SET (parallel_workers=0);\nALTER TABLE :CHUNK2 SET (parallel_workers=2);\n:PREFIX SELECT count(*) FROM \"test\" WHERE i > 400000 AND length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Index Only Scan using _hyper_1_1_chunk_test_i_idx on _hyper_1_1_chunk\n                                       Index Cond: (i > 400000)\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nALTER TABLE :CHUNK1 SET (parallel_workers=2);\nALTER TABLE :CHUNK2 SET (parallel_workers=0);\n:PREFIX SELECT count(*) FROM \"test\" WHERE i < 600000 AND length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Index Only Scan using _hyper_1_2_chunk_test_i_idx on _hyper_1_2_chunk\n                                       Index Cond: (i < 600000)\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_1_chunk\n\nALTER TABLE :CHUNK1 RESET (parallel_workers);\nALTER TABLE :CHUNK2 RESET (parallel_workers);\n-- now() is not marked parallel safe in PostgreSQL < 12 so using now()\n-- in a query will prevent parallelism but CURRENT_TIMESTAMP and\n-- transaction_timestamp() are marked parallel safe\n:PREFIX SELECT i FROM \"test\" WHERE ts < CURRENT_TIMESTAMP;\n--- QUERY PLAN ---\n Gather\n   Workers Planned: 1\n   Single Copy: true\n   ->  Custom Scan (ChunkAppend) on test\n         Chunks excluded during startup: 0\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (ts < CURRENT_TIMESTAMP)\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (ts < CURRENT_TIMESTAMP)\n\n:PREFIX SELECT i FROM \"test\" WHERE ts < transaction_timestamp();\n--- QUERY PLAN ---\n Gather\n   Workers Planned: 1\n   Single Copy: true\n   ->  Custom Scan (ChunkAppend) on test\n         Chunks excluded during startup: 0\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (ts < transaction_timestamp())\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (ts < transaction_timestamp())\n\n-- this won't be parallel query because now() is parallel restricted in PG < 12\n:PREFIX SELECT i FROM \"test\" WHERE ts < now();\n--- QUERY PLAN ---\n Gather\n   Workers Planned: 1\n   Single Copy: true\n   ->  Custom Scan (ChunkAppend) on test\n         Chunks excluded during startup: 0\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (ts < now())\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (ts < now())\n\n"
  },
  {
    "path": "test/expected/parallel-18.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n--parallel queries require big-ish tables so collect them all here\n--so that we need to generate queries only once.\n-- output with analyze is not stable because it depends on worker assignment\n\\set PREFIX 'EXPLAIN (buffers off, costs off)'\n\\set CHUNK1 _timescaledb_internal._hyper_1_1_chunk\n\\set CHUNK2 _timescaledb_internal._hyper_1_2_chunk\nCREATE TABLE test (i int, j double precision, ts timestamp);\nSELECT create_hypertable('test','i',chunk_time_interval:=500000);\nWARNING:  column type \"timestamp without time zone\" used for \"ts\" does not follow best practices\n create_hypertable \n-------------------\n (1,public,test,t)\n\nINSERT INTO test SELECT x, x+0.1, _timescaledb_functions.to_timestamp(x*1000)  FROM generate_series(0,1000000-1,10) AS x;\nANALYZE test;\nALTER TABLE :CHUNK1 SET (parallel_workers=2);\nALTER TABLE :CHUNK2 SET (parallel_workers=2);\nSET work_mem TO '50MB';\nSELECT set_config(CASE WHEN current_setting('server_version_num')::int < 160000 THEN 'force_parallel_mode' ELSE 'debug_parallel_query' END,'on', false);\n set_config \n------------\n on\n\nSET max_parallel_workers_per_gather = 4;\nSET parallel_setup_cost TO 0;\nEXPLAIN (buffers off, costs off) SELECT first(i, j) FROM \"test\";\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT first(i, j) FROM \"test\";\n first \n-------\n     0\n\nEXPLAIN (buffers off, costs off) SELECT last(i, j) FROM \"test\";\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT last(i, j) FROM \"test\";\n  last  \n--------\n 999990\n\nEXPLAIN (buffers off, costs off) SELECT time_bucket('1 second', ts) sec, last(i, j)\nFROM \"test\"\nGROUP BY sec\nORDER BY sec\nLIMIT 5;\n--- QUERY PLAN ---\n Limit\n   ->  GroupAggregate\n         Group Key: (time_bucket('@ 1 sec'::interval, test.ts))\n         ->  Gather Merge\n               Workers Planned: 2\n               ->  Sort\n                     Sort Key: (time_bucket('@ 1 sec'::interval, test.ts))\n                     ->  Result\n                           ->  Parallel Append\n                                 ->  Parallel Seq Scan on _hyper_1_1_chunk\n                                 ->  Parallel Seq Scan on _hyper_1_2_chunk\n\n-- test single copy parallel plan with parallel chunk append\n:PREFIX SELECT time_bucket('1 second', ts) sec, last(i, j)\nFROM \"test\"\nWHERE length(version()) > 0\nGROUP BY sec\nORDER BY sec\nLIMIT 5;\n--- QUERY PLAN ---\n Limit\n   ->  GroupAggregate\n         Group Key: (time_bucket('@ 1 sec'::interval, test.ts))\n         ->  Gather Merge\n               Workers Planned: 2\n               ->  Sort\n                     Sort Key: (time_bucket('@ 1 sec'::interval, test.ts))\n                     ->  Result\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Custom Scan (ChunkAppend) on test\n                                       Chunks excluded during startup: 0\n                                       ->  Result\n                                             One-Time Filter: (length(version()) > 0)\n                                             ->  Parallel Seq Scan on _hyper_1_1_chunk\n                                       ->  Result\n                                             One-Time Filter: (length(version()) > 0)\n                                             ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT time_bucket('1 second', ts) sec, last(i, j)\nFROM \"test\"\nGROUP BY sec\nORDER BY sec\nLIMIT 5;\n           sec            | last \n--------------------------+------\n Wed Dec 31 16:00:00 1969 |  990\n Wed Dec 31 16:00:01 1969 | 1990\n Wed Dec 31 16:00:02 1969 | 2990\n Wed Dec 31 16:00:03 1969 | 3990\n Wed Dec 31 16:00:04 1969 | 4990\n\n--test variants of histogram\nEXPLAIN (buffers off, costs off) SELECT histogram(i, 1, 1000000, 2) FROM \"test\";\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT histogram(i, 1, 1000000, 2) FROM \"test\";\n     histogram     \n-------------------\n {1,50000,49999,0}\n\nEXPLAIN (buffers off, costs off) SELECT histogram(i, 1,1000001,10) FROM \"test\";\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT histogram(i, 1, 1000001, 10) FROM \"test\";\n                            histogram                             \n------------------------------------------------------------------\n {1,10000,10000,10000,10000,10000,10000,10000,10000,10000,9999,0}\n\nEXPLAIN (buffers off, costs off) SELECT histogram(i, 0,100000,5) FROM \"test\";\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT histogram(i, 0, 100000, 5) FROM \"test\";\n             histogram              \n------------------------------------\n {0,2000,2000,2000,2000,2000,90000}\n\nEXPLAIN (buffers off, costs off) SELECT histogram(i, 10,100000,5) FROM \"test\";\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT histogram(i, 10, 100000, 5) FROM \"test\";\n             histogram              \n------------------------------------\n {1,2000,2000,2000,2000,1999,90000}\n\nEXPLAIN (buffers off, costs off) SELECT histogram(NULL, 10,100000,5) FROM \"test\" WHERE  i = coalesce(-1,j);\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_1_1_chunk\n                           Filter: ((i)::double precision = '-1'::double precision)\n                     ->  Parallel Seq Scan on _hyper_1_2_chunk\n                           Filter: ((i)::double precision = '-1'::double precision)\n\nSELECT histogram(NULL, 10,100000,5) FROM \"test\" WHERE  i = coalesce(-1,j);\n histogram \n-----------\n \n\n-- test parallel ChunkAppend\n:PREFIX SELECT i FROM \"test\" WHERE length(version()) > 0;\n--- QUERY PLAN ---\n Gather\n   Workers Planned: 1\n   Single Copy: true\n   ->  Result\n         One-Time Filter: (length(version()) > 0)\n         ->  Custom Scan (ChunkAppend) on test\n               Chunks excluded during startup: 0\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Seq Scan on _hyper_1_1_chunk\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Seq Scan on _hyper_1_2_chunk\n\n:PREFIX SELECT count(*) FROM \"test\" WHERE i > 1 AND length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_1_chunk\n                                       Filter: (i > 1)\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT count(*) FROM \"test\" WHERE i > 1 AND length(version()) > 0;\n count \n-------\n 99999\n\n-- test parallel ChunkAppend with only work done in the parallel workers\nSET parallel_leader_participation = off;\nSELECT count(*) FROM \"test\" WHERE i > 1 AND length(version()) > 0;\n count \n-------\n 99999\n\nRESET parallel_leader_participation;\n-- Test parallel chunk append is used (index scan is disabled to trigger a parallel chunk append)\nSET parallel_tuple_cost = 0;\nSET enable_indexscan = OFF;\n:PREFIX SELECT * FROM (SELECT * FROM \"test\" WHERE length(version()) > 0 ORDER BY I LIMIT 10) AS t1 LEFT JOIN (SELECT * FROM \"test\" WHERE i < 500000 ORDER BY I LIMIT 10) AS t2 ON (t1.i = t2.i) ORDER BY t1.i, t2.i;\n--- QUERY PLAN ---\n Incremental Sort\n   Sort Key: test.i, _hyper_1_1_chunk_1.i\n   Presorted Key: test.i\n   ->  Merge Left Join\n         Merge Cond: (test.i = _hyper_1_1_chunk_1.i)\n         ->  Limit\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Custom Scan (ChunkAppend) on test\n                           Order: test.i\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Index Scan Backward using _hyper_1_1_chunk_test_i_idx on _hyper_1_1_chunk\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Index Scan Backward using _hyper_1_2_chunk_test_i_idx on _hyper_1_2_chunk\n         ->  Materialize\n               ->  Limit\n                     ->  Gather Merge\n                           Workers Planned: 2\n                           ->  Sort\n                                 Sort Key: _hyper_1_1_chunk_1.i\n                                 ->  Parallel Seq Scan on _hyper_1_1_chunk _hyper_1_1_chunk_1\n\nSELECT * FROM (SELECT * FROM \"test\" WHERE length(version()) > 0 ORDER BY I LIMIT 10) AS t1 LEFT JOIN (SELECT * FROM \"test\" WHERE i < 500000 ORDER BY I LIMIT 10) AS t2 ON (t1.i = t2.i) ORDER BY t1.i, t2.i;\n i  |  j   |             ts              | i  |  j   |             ts              \n----+------+-----------------------------+----+------+-----------------------------\n  0 |  0.1 | Wed Dec 31 16:00:00 1969    |  0 |  0.1 | Wed Dec 31 16:00:00 1969\n 10 | 10.1 | Wed Dec 31 16:00:00.01 1969 | 10 | 10.1 | Wed Dec 31 16:00:00.01 1969\n 20 | 20.1 | Wed Dec 31 16:00:00.02 1969 | 20 | 20.1 | Wed Dec 31 16:00:00.02 1969\n 30 | 30.1 | Wed Dec 31 16:00:00.03 1969 | 30 | 30.1 | Wed Dec 31 16:00:00.03 1969\n 40 | 40.1 | Wed Dec 31 16:00:00.04 1969 | 40 | 40.1 | Wed Dec 31 16:00:00.04 1969\n 50 | 50.1 | Wed Dec 31 16:00:00.05 1969 | 50 | 50.1 | Wed Dec 31 16:00:00.05 1969\n 60 | 60.1 | Wed Dec 31 16:00:00.06 1969 | 60 | 60.1 | Wed Dec 31 16:00:00.06 1969\n 70 | 70.1 | Wed Dec 31 16:00:00.07 1969 | 70 | 70.1 | Wed Dec 31 16:00:00.07 1969\n 80 | 80.1 | Wed Dec 31 16:00:00.08 1969 | 80 | 80.1 | Wed Dec 31 16:00:00.08 1969\n 90 | 90.1 | Wed Dec 31 16:00:00.09 1969 | 90 | 90.1 | Wed Dec 31 16:00:00.09 1969\n\nSET enable_indexscan = ON;\n-- Test normal chunk append can be used in a parallel worker\n:PREFIX SELECT * FROM (SELECT * FROM \"test\" WHERE i >= 999000 ORDER BY i) AS t1 JOIN (SELECT * FROM \"test\" WHERE i >= 400000 ORDER BY i) AS t2 ON (TRUE) ORDER BY t1.i, t2.i LIMIT 10;\n--- QUERY PLAN ---\n Gather\n   Workers Planned: 1\n   Single Copy: true\n   ->  Limit\n         ->  Incremental Sort\n               Sort Key: _hyper_1_2_chunk.i, test.i\n               Presorted Key: _hyper_1_2_chunk.i\n               ->  Nested Loop\n                     ->  Index Scan Backward using _hyper_1_2_chunk_test_i_idx on _hyper_1_2_chunk\n                           Index Cond: (i >= 999000)\n                     ->  Materialize\n                           ->  Custom Scan (ChunkAppend) on test\n                                 Order: test.i\n                                 ->  Index Scan Backward using _hyper_1_1_chunk_test_i_idx on _hyper_1_1_chunk\n                                       Index Cond: (i >= 400000)\n                                 ->  Index Scan Backward using _hyper_1_2_chunk_test_i_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1\n\nSELECT * FROM (SELECT * FROM \"test\" WHERE i >= 999000 ORDER BY i) AS t1 JOIN (SELECT * FROM \"test\" WHERE i >= 400000 ORDER BY i) AS t2 ON (TRUE) ORDER BY t1.i, t2.i LIMIT 10;\n   i    |    j     |            ts            |   i    |    j     |             ts              \n--------+----------+--------------------------+--------+----------+-----------------------------\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400000 | 400000.1 | Wed Dec 31 16:06:40 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400010 | 400010.1 | Wed Dec 31 16:06:40.01 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400020 | 400020.1 | Wed Dec 31 16:06:40.02 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400030 | 400030.1 | Wed Dec 31 16:06:40.03 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400040 | 400040.1 | Wed Dec 31 16:06:40.04 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400050 | 400050.1 | Wed Dec 31 16:06:40.05 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400060 | 400060.1 | Wed Dec 31 16:06:40.06 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400070 | 400070.1 | Wed Dec 31 16:06:40.07 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400080 | 400080.1 | Wed Dec 31 16:06:40.08 1969\n 999000 | 999000.1 | Wed Dec 31 16:16:39 1969 | 400090 | 400090.1 | Wed Dec 31 16:06:40.09 1969\n\n-- Test parallel ChunkAppend reinit\nSET enable_material = off;\nSET min_parallel_table_scan_size = 0;\nSET min_parallel_index_scan_size = 0;\nSET enable_hashjoin = 'off';\nSET enable_nestloop = 'off';\nCREATE TABLE sensor_data(\n      time timestamptz NOT NULL,\n      sensor_id integer NOT NULL);\nSELECT FROM create_hypertable(relation=>'sensor_data', time_column_name=> 'time');\n--\n\n-- Sensors 1 and 2\nINSERT INTO sensor_data\nSELECT time, sensor_id\nFROM\ngenerate_series('2000-01-01 00:00:30', '2022-01-01 00:00:30', INTERVAL '3 months') AS g1(time),\ngenerate_series(1, 2, 1) AS g2(sensor_id)\nORDER BY time;\n-- Sensor 100\nINSERT INTO sensor_data\nSELECT time, 100 as sensor_id\nFROM\ngenerate_series('2000-01-01 00:00:30', '2022-01-01 00:00:30', INTERVAL '1 year') AS g1(time)\nORDER BY time;\n:PREFIX SELECT * FROM sensor_data AS s1 JOIN sensor_data AS s2 ON (TRUE) WHERE s1.time > '2020-01-01 00:00:30'::text::timestamptz AND s2.time > '2020-01-01 00:00:30' AND s2.time < '2021-01-01 00:00:30' AND s1.sensor_id > 50 ORDER BY s2.time, s1.time, s1.sensor_id, s2.sensor_id;\n--- QUERY PLAN ---\n Sort\n   Sort Key: s2.\"time\", s1.\"time\", s1.sensor_id, s2.sensor_id\n   ->  Nested Loop\n         ->  Custom Scan (ChunkAppend) on sensor_data s1\n               Chunks excluded during startup: 80\n               ->  Index Scan using _hyper_2_83_chunk_sensor_data_time_idx on _hyper_2_83_chunk s1_1\n                     Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     Filter: (sensor_id > 50)\n               ->  Index Scan using _hyper_2_84_chunk_sensor_data_time_idx on _hyper_2_84_chunk s1_2\n                     Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     Filter: (sensor_id > 50)\n               ->  Index Scan using _hyper_2_85_chunk_sensor_data_time_idx on _hyper_2_85_chunk s1_3\n                     Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     Filter: (sensor_id > 50)\n               ->  Index Scan using _hyper_2_86_chunk_sensor_data_time_idx on _hyper_2_86_chunk s1_4\n                     Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     Filter: (sensor_id > 50)\n               ->  Index Scan using _hyper_2_87_chunk_sensor_data_time_idx on _hyper_2_87_chunk s1_5\n                     Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     Filter: (sensor_id > 50)\n               ->  Index Scan using _hyper_2_88_chunk_sensor_data_time_idx on _hyper_2_88_chunk s1_6\n                     Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     Filter: (sensor_id > 50)\n               ->  Index Scan using _hyper_2_89_chunk_sensor_data_time_idx on _hyper_2_89_chunk s1_7\n                     Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     Filter: (sensor_id > 50)\n               ->  Index Scan using _hyper_2_90_chunk_sensor_data_time_idx on _hyper_2_90_chunk s1_8\n                     Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     Filter: (sensor_id > 50)\n               ->  Index Scan using _hyper_2_91_chunk_sensor_data_time_idx on _hyper_2_91_chunk s1_9\n                     Index Cond: (\"time\" > ('2020-01-01 00:00:30'::cstring)::timestamp with time zone)\n                     Filter: (sensor_id > 50)\n         ->  Gather\n               Workers Planned: 3\n               ->  Parallel Append\n                     ->  Parallel Index Scan Backward using _hyper_2_83_chunk_sensor_data_time_idx on _hyper_2_83_chunk s2_1\n                           Index Cond: ((\"time\" > 'Wed Jan 01 00:00:30 2020 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 01 00:00:30 2021 PST'::timestamp with time zone))\n                     ->  Parallel Index Scan Backward using _hyper_2_87_chunk_sensor_data_time_idx on _hyper_2_87_chunk s2_5\n                           Index Cond: ((\"time\" > 'Wed Jan 01 00:00:30 2020 PST'::timestamp with time zone) AND (\"time\" < 'Fri Jan 01 00:00:30 2021 PST'::timestamp with time zone))\n                     ->  Parallel Seq Scan on _hyper_2_84_chunk s2_2\n                     ->  Parallel Seq Scan on _hyper_2_85_chunk s2_3\n                     ->  Parallel Seq Scan on _hyper_2_86_chunk s2_4\n\n-- Check query result\nSELECT * FROM sensor_data AS s1 JOIN sensor_data AS s2 ON (TRUE) WHERE s1.time > '2020-01-01 00:00:30'::text::timestamptz AND s2.time > '2020-01-01 00:00:30' AND s2.time < '2021-01-01 00:00:30' AND s1.sensor_id > 50 ORDER BY s2.time, s1.time, s1.sensor_id, s2.sensor_id;\n             time             | sensor_id |             time             | sensor_id \n------------------------------+-----------+------------------------------+-----------\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         2\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         2\n Fri Jan 01 00:00:30 2021 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         2\n\n-- Ensure the same result is produced if only the parallel workers have to produce them (i.e., the pstate is reinitialized properly)\nSET parallel_leader_participation = off;\nSELECT * FROM sensor_data AS s1 JOIN sensor_data AS s2 ON (TRUE) WHERE s1.time > '2020-01-01 00:00:30'::text::timestamptz AND s2.time > '2020-01-01 00:00:30' AND s2.time < '2021-01-01 00:00:30' AND s1.sensor_id > 50 ORDER BY s2.time, s1.time, s1.sensor_id, s2.sensor_id;\n             time             | sensor_id |             time             | sensor_id \n------------------------------+-----------+------------------------------+-----------\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         2\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         2\n Fri Jan 01 00:00:30 2021 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         2\n\nRESET parallel_leader_participation;\n-- Ensure the same query result is produced by a sequencial query\nSET max_parallel_workers_per_gather TO 0;\nSELECT set_config(CASE WHEN current_setting('server_version_num')::int < 160000 THEN 'force_parallel_mode' ELSE 'debug_parallel_query' END,'off', false);\n set_config \n------------\n off\n\nSELECT * FROM sensor_data AS s1 JOIN sensor_data AS s2 ON (TRUE) WHERE s1.time > '2020-01-01 00:00:30'::text::timestamptz AND s2.time > '2020-01-01 00:00:30' AND s2.time < '2021-01-01 00:00:30' AND s1.sensor_id > 50 ORDER BY s2.time, s1.time, s1.sensor_id, s2.sensor_id;\n             time             | sensor_id |             time             | sensor_id \n------------------------------+-----------+------------------------------+-----------\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Apr 01 00:00:30 2020 PDT |         2\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Wed Jul 01 00:00:30 2020 PDT |         2\n Fri Jan 01 00:00:30 2021 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         1\n Fri Jan 01 00:00:30 2021 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         2\n Sat Jan 01 00:00:30 2022 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         1\n Sat Jan 01 00:00:30 2022 PST |       100 | Thu Oct 01 00:00:30 2020 PDT |         2\n\nRESET enable_material;\nRESET min_parallel_table_scan_size;\nRESET min_parallel_index_scan_size;\nRESET enable_hashjoin;\nRESET enable_nestloop;\nRESET parallel_tuple_cost;\nSELECT set_config(CASE WHEN current_setting('server_version_num')::int < 160000 THEN 'force_parallel_mode' ELSE 'debug_parallel_query' END,'on', false);\n set_config \n------------\n on\n\n-- test worker assignment\n-- first chunk should have 1 worker and second chunk should have 2\nSET max_parallel_workers_per_gather TO 2;\n:PREFIX SELECT count(*) FROM \"test\" WHERE i >= 400000 AND length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Index Only Scan using _hyper_1_1_chunk_test_i_idx on _hyper_1_1_chunk\n                                       Index Cond: (i >= 400000)\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT count(*) FROM \"test\" WHERE i >= 400000 AND length(version()) > 0;\n count \n-------\n 60000\n\n-- test worker assignment\n-- first chunk should have 2 worker and second chunk should have 1\n:PREFIX SELECT count(*) FROM \"test\" WHERE i < 600000 AND length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Index Only Scan using _hyper_1_2_chunk_test_i_idx on _hyper_1_2_chunk\n                                       Index Cond: (i < 600000)\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_1_chunk\n\nSELECT count(*) FROM \"test\" WHERE i < 600000 AND length(version()) > 0;\n count \n-------\n 60000\n\n-- test ChunkAppend with # workers < # childs\nSET max_parallel_workers_per_gather TO 1;\n:PREFIX SELECT count(*) FROM \"test\" WHERE length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 1\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_1_chunk\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT count(*) FROM \"test\" WHERE length(version()) > 0;\n count  \n--------\n 100000\n\n-- test ChunkAppend with # workers > # childs\nSET max_parallel_workers_per_gather TO 2;\n:PREFIX SELECT count(*) FROM \"test\" WHERE i >= 500000 AND length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nSELECT count(*) FROM \"test\" WHERE i >= 500000 AND length(version()) > 0;\n count \n-------\n 50000\n\nRESET max_parallel_workers_per_gather;\n-- test partial and non-partial plans\n-- these will not be parallel on PG < 11\nALTER TABLE :CHUNK1 SET (parallel_workers=0);\nALTER TABLE :CHUNK2 SET (parallel_workers=2);\n:PREFIX SELECT count(*) FROM \"test\" WHERE i > 400000 AND length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Index Only Scan using _hyper_1_1_chunk_test_i_idx on _hyper_1_1_chunk\n                                       Index Cond: (i > 400000)\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_2_chunk\n\nALTER TABLE :CHUNK1 SET (parallel_workers=2);\nALTER TABLE :CHUNK2 SET (parallel_workers=0);\n:PREFIX SELECT count(*) FROM \"test\" WHERE i < 600000 AND length(version()) > 0;\n--- QUERY PLAN ---\n Finalize Aggregate\n   ->  Gather\n         Workers Planned: 2\n         ->  Partial Aggregate\n               ->  Result\n                     One-Time Filter: (length(version()) > 0)\n                     ->  Parallel Custom Scan (ChunkAppend) on test\n                           Chunks excluded during startup: 0\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Index Only Scan using _hyper_1_2_chunk_test_i_idx on _hyper_1_2_chunk\n                                       Index Cond: (i < 600000)\n                           ->  Result\n                                 One-Time Filter: (length(version()) > 0)\n                                 ->  Parallel Seq Scan on _hyper_1_1_chunk\n\nALTER TABLE :CHUNK1 RESET (parallel_workers);\nALTER TABLE :CHUNK2 RESET (parallel_workers);\n-- now() is not marked parallel safe in PostgreSQL < 12 so using now()\n-- in a query will prevent parallelism but CURRENT_TIMESTAMP and\n-- transaction_timestamp() are marked parallel safe\n:PREFIX SELECT i FROM \"test\" WHERE ts < CURRENT_TIMESTAMP;\n--- QUERY PLAN ---\n Gather\n   Workers Planned: 1\n   Single Copy: true\n   ->  Custom Scan (ChunkAppend) on test\n         Chunks excluded during startup: 0\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (ts < CURRENT_TIMESTAMP)\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (ts < CURRENT_TIMESTAMP)\n\n:PREFIX SELECT i FROM \"test\" WHERE ts < transaction_timestamp();\n--- QUERY PLAN ---\n Gather\n   Workers Planned: 1\n   Single Copy: true\n   ->  Custom Scan (ChunkAppend) on test\n         Chunks excluded during startup: 0\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (ts < transaction_timestamp())\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (ts < transaction_timestamp())\n\n-- this won't be parallel query because now() is parallel restricted in PG < 12\n:PREFIX SELECT i FROM \"test\" WHERE ts < now();\n--- QUERY PLAN ---\n Gather\n   Workers Planned: 1\n   Single Copy: true\n   ->  Custom Scan (ChunkAppend) on test\n         Chunks excluded during startup: 0\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (ts < now())\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (ts < now())\n\n"
  },
  {
    "path": "test/expected/partition.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE part_legacy(time timestamptz, temp float, device int);\nSELECT create_hypertable('part_legacy', 'time', 'device', 2, partitioning_func => '_timescaledb_functions.get_partition_for_key');\n    create_hypertable     \n--------------------------\n (1,public,part_legacy,t)\n\n-- Show legacy partitioning function is used\nSELECT * FROM _timescaledb_catalog.dimension;\n id | hypertable_id | column_name |       column_type        | aligned | num_slices | partitioning_func_schema |   partitioning_func   | interval_length | compress_interval_length | integer_now_func_schema | integer_now_func \n----+---------------+-------------+--------------------------+---------+------------+--------------------------+-----------------------+-----------------+--------------------------+-------------------------+------------------\n  1 |             1 | time        | timestamp with time zone | t       |            |                          |                       |    604800000000 |                          |                         | \n  2 |             1 | device      | integer                  | f       |          2 | _timescaledb_functions   | get_partition_for_key |                 |                          |                         | \n\nINSERT INTO part_legacy VALUES ('2017-03-22T09:18:23', 23.4, 1);\nINSERT INTO part_legacy VALUES ('2017-03-22T09:18:23', 23.4, 76);\nVACUUM part_legacy;\n-- Show two chunks and CHECK constraint with cast\nSELECT * FROM test.show_constraintsp('_timescaledb_internal._hyper_1_%_chunk');\n                 Table                  |  Constraint  | Type | Columns  | Index |                                                                      Expr                                                                      | Deferrable | Deferred | Validated \n----------------------------------------+--------------+------+----------+-------+------------------------------------------------------------------------------------------------------------------------------------------------+------------+----------+-----------\n _timescaledb_internal._hyper_1_1_chunk | constraint_1 | c    | {time}   | -     | ((\"time\" >= 'Wed Mar 15 17:00:00 2017 PDT'::timestamp with time zone) AND (\"time\" < 'Wed Mar 22 17:00:00 2017 PDT'::timestamp with time zone)) | f          | f        | t\n _timescaledb_internal._hyper_1_1_chunk | constraint_2 | c    | {device} | -     | (_timescaledb_functions.get_partition_for_key(device) >= 1073741823)                                                                           | f          | f        | t\n _timescaledb_internal._hyper_1_2_chunk | constraint_1 | c    | {time}   | -     | ((\"time\" >= 'Wed Mar 15 17:00:00 2017 PDT'::timestamp with time zone) AND (\"time\" < 'Wed Mar 22 17:00:00 2017 PDT'::timestamp with time zone)) | f          | f        | t\n _timescaledb_internal._hyper_1_2_chunk | constraint_3 | c    | {device} | -     | (_timescaledb_functions.get_partition_for_key(device) < 1073741823)                                                                            | f          | f        | t\n\n-- Make sure constraint exclusion works on device column\nBEGIN;\n-- For plan stability between versions\nSET LOCAL enable_bitmapscan = false;\nSET LOCAL enable_indexscan = false;\nEXPLAIN (verbose, buffers off, costs off)\nSELECT * FROM part_legacy WHERE device = 1;\n--- QUERY PLAN ---\n Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n   Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.temp, _hyper_1_1_chunk.device\n   Filter: (_hyper_1_1_chunk.device = 1)\n\nCOMMIT;\nCREATE TABLE part_new(time timestamptz, temp float, device int);\nSELECT create_hypertable('part_new', 'time', 'device', 2);\n   create_hypertable   \n-----------------------\n (2,public,part_new,t)\n\nSELECT * FROM _timescaledb_catalog.dimension;\n id | hypertable_id | column_name |       column_type        | aligned | num_slices | partitioning_func_schema |   partitioning_func   | interval_length | compress_interval_length | integer_now_func_schema | integer_now_func \n----+---------------+-------------+--------------------------+---------+------------+--------------------------+-----------------------+-----------------+--------------------------+-------------------------+------------------\n  1 |             1 | time        | timestamp with time zone | t       |            |                          |                       |    604800000000 |                          |                         | \n  2 |             1 | device      | integer                  | f       |          2 | _timescaledb_functions   | get_partition_for_key |                 |                          |                         | \n  3 |             2 | time        | timestamp with time zone | t       |            |                          |                       |    604800000000 |                          |                         | \n  4 |             2 | device      | integer                  | f       |          2 | _timescaledb_functions   | get_partition_hash    |                 |                          |                         | \n\nINSERT INTO part_new VALUES ('2017-03-22T09:18:23', 23.4, 1);\nINSERT INTO part_new VALUES ('2017-03-22T09:18:23', 23.4, 2);\nVACUUM part_new;\n-- Show two chunks and CHECK constraint without cast\nSELECT * FROM test.show_constraintsp('_timescaledb_internal._hyper_2_%_chunk');\n                 Table                  |  Constraint  | Type | Columns  | Index |                                                                      Expr                                                                      | Deferrable | Deferred | Validated \n----------------------------------------+--------------+------+----------+-------+------------------------------------------------------------------------------------------------------------------------------------------------+------------+----------+-----------\n _timescaledb_internal._hyper_2_3_chunk | constraint_4 | c    | {time}   | -     | ((\"time\" >= 'Wed Mar 15 17:00:00 2017 PDT'::timestamp with time zone) AND (\"time\" < 'Wed Mar 22 17:00:00 2017 PDT'::timestamp with time zone)) | f          | f        | t\n _timescaledb_internal._hyper_2_3_chunk | constraint_5 | c    | {device} | -     | (_timescaledb_functions.get_partition_hash(device) < 1073741823)                                                                               | f          | f        | t\n _timescaledb_internal._hyper_2_4_chunk | constraint_4 | c    | {time}   | -     | ((\"time\" >= 'Wed Mar 15 17:00:00 2017 PDT'::timestamp with time zone) AND (\"time\" < 'Wed Mar 22 17:00:00 2017 PDT'::timestamp with time zone)) | f          | f        | t\n _timescaledb_internal._hyper_2_4_chunk | constraint_6 | c    | {device} | -     | (_timescaledb_functions.get_partition_hash(device) >= 1073741823)                                                                              | f          | f        | t\n\n-- Make sure constraint exclusion works on device column\nBEGIN;\n-- For plan stability between versions\nSET LOCAL enable_bitmapscan = false;\nSET LOCAL enable_indexscan = false;\nEXPLAIN (verbose, buffers off, costs off)\nSELECT * FROM part_new WHERE device = 1;\n--- QUERY PLAN ---\n Seq Scan on _timescaledb_internal._hyper_2_3_chunk\n   Output: _hyper_2_3_chunk.\"time\", _hyper_2_3_chunk.temp, _hyper_2_3_chunk.device\n   Filter: (_hyper_2_3_chunk.device = 1)\n\nCOMMIT;\nCREATE TABLE part_new_convert1(time timestamptz, temp float8, device int);\nSELECT create_hypertable('part_new_convert1', 'time', 'temp', 2);\n       create_hypertable        \n--------------------------------\n (3,public,part_new_convert1,t)\n\nINSERT INTO part_new_convert1 VALUES ('2017-03-22T09:18:23', 1.0, 2);\n\\set ON_ERROR_STOP 0\n-- Changing the type of a hash-partitioned column should not be supported\nALTER TABLE part_new_convert1 ALTER COLUMN temp TYPE numeric;\nERROR:  cannot change the type of a hash-partitioned column\n\\set ON_ERROR_STOP 1\n-- Should be able to change if not hash partitioned though\nALTER TABLE part_new_convert1 ALTER COLUMN time TYPE timestamp;\nSELECT * FROM test.show_columnsp('_timescaledb_internal._hyper_3_%_chunk');\n                Relation                | Kind | Column |         Column type         | NotNull \n----------------------------------------+------+--------+-----------------------------+---------\n _timescaledb_internal._hyper_3_5_chunk | r    | time   | timestamp without time zone | t\n _timescaledb_internal._hyper_3_5_chunk | r    | temp   | double precision            | f\n _timescaledb_internal._hyper_3_5_chunk | r    | device | integer                     | f\n\nCREATE TABLE part_add_dim(time timestamptz, temp float8, device int, location int);\nSELECT create_hypertable('part_add_dim', 'time', 'temp', 2);\n     create_hypertable     \n---------------------------\n (4,public,part_add_dim,t)\n\n\\set ON_ERROR_STOP 0\nSELECT add_dimension('part_add_dim', 'location', 2, partitioning_func => 'bad_func');\nERROR:  function \"bad_func\" does not exist at character 74\n\\set ON_ERROR_STOP 1\nSELECT add_dimension('part_add_dim', 'location', 2, partitioning_func => '_timescaledb_functions.get_partition_for_key');\n           add_dimension            \n------------------------------------\n (9,public,part_add_dim,location,t)\n\nSELECT * FROM _timescaledb_catalog.dimension;\n id | hypertable_id | column_name |         column_type         | aligned | num_slices | partitioning_func_schema |   partitioning_func   | interval_length | compress_interval_length | integer_now_func_schema | integer_now_func \n----+---------------+-------------+-----------------------------+---------+------------+--------------------------+-----------------------+-----------------+--------------------------+-------------------------+------------------\n  1 |             1 | time        | timestamp with time zone    | t       |            |                          |                       |    604800000000 |                          |                         | \n  2 |             1 | device      | integer                     | f       |          2 | _timescaledb_functions   | get_partition_for_key |                 |                          |                         | \n  3 |             2 | time        | timestamp with time zone    | t       |            |                          |                       |    604800000000 |                          |                         | \n  4 |             2 | device      | integer                     | f       |          2 | _timescaledb_functions   | get_partition_hash    |                 |                          |                         | \n  6 |             3 | temp        | double precision            | f       |          2 | _timescaledb_functions   | get_partition_hash    |                 |                          |                         | \n  5 |             3 | time        | timestamp without time zone | t       |            |                          |                       |    604800000000 |                          |                         | \n  7 |             4 | time        | timestamp with time zone    | t       |            |                          |                       |    604800000000 |                          |                         | \n  8 |             4 | temp        | double precision            | f       |          2 | _timescaledb_functions   | get_partition_hash    |                 |                          |                         | \n  9 |             4 | location    | integer                     | f       |          2 | _timescaledb_functions   | get_partition_for_key |                 |                          |                         | \n\n-- Test that we support custom SQL-based partitioning functions and\n-- that our native partitioning function handles function expressions\n-- as argument\nCREATE OR REPLACE FUNCTION custom_partfunc(source anyelement)\n    RETURNS INTEGER LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nDECLARE\n    retval INTEGER;\nBEGIN\n    retval = _timescaledb_functions.get_partition_hash(substring(source::text FROM '[A-za-z0-9 ]+'));\n    RAISE NOTICE 'hash value for % is %', source, retval;\n    RETURN retval;\nEND\n$BODY$;\nCREATE TABLE part_custom_func(time timestamptz, temp float8, device text);\nSELECT create_hypertable('part_custom_func', 'time', 'device', 2, partitioning_func => 'custom_partfunc');\n       create_hypertable       \n-------------------------------\n (5,public,part_custom_func,t)\n\nSELECT _timescaledb_functions.get_partition_hash(substring('dev1' FROM '[A-za-z0-9 ]+'));\n get_partition_hash \n--------------------\n         1129986420\n\nSELECT _timescaledb_functions.get_partition_hash('dev1'::text);\n get_partition_hash \n--------------------\n         1129986420\n\nSELECT _timescaledb_functions.get_partition_hash('dev7'::text);\n get_partition_hash \n--------------------\n          449729092\n\nINSERT INTO part_custom_func VALUES ('2017-03-22T09:18:23', 23.4, 'dev1'),\n                                    ('2017-03-22T09:18:23', 23.4, 'dev7');\nNOTICE:  hash value for dev1 is 1129986420\nNOTICE:  hash value for dev1 is 1129986420\nNOTICE:  hash value for dev7 is 449729092\nNOTICE:  hash value for dev7 is 449729092\nSELECT * FROM test.show_subtables('part_custom_func');\n                 Child                  | Tablespace \n----------------------------------------+------------\n _timescaledb_internal._hyper_5_6_chunk | \n _timescaledb_internal._hyper_5_7_chunk | \n\n-- This first test is slightly trivial, but segfaulted in old versions\nCREATE TYPE simpl AS (val1 int4);\nCREATE OR REPLACE FUNCTION simpl_type_hash(ANYELEMENT) RETURNS int4 AS $$\n    SELECT $1.val1;\n$$ LANGUAGE SQL IMMUTABLE;\nCREATE TABLE simpl_partition (\"timestamp\" TIMESTAMPTZ, object simpl);\nSELECT create_hypertable(\n    'simpl_partition',\n    'timestamp',\n    'object',\n    1000,\n    chunk_time_interval => interval '1 day',\n    partitioning_func=>'simpl_type_hash');\n      create_hypertable       \n------------------------------\n (6,public,simpl_partition,t)\n\nINSERT INTO simpl_partition VALUES ('2017-03-22T09:18:23', ROW(1)::simpl);\nSELECT * from simpl_partition;\n          timestamp           | object \n------------------------------+--------\n Wed Mar 22 09:18:23 2017 PDT | (1)\n\n-- Also test that the fix works when we have more chunks than allowed at once\nSET timescaledb.max_open_chunks_per_insert=1;\nINSERT INTO simpl_partition VALUES\n    ('2017-03-22T10:18:23', ROW(0)::simpl),\n    ('2017-03-22T10:18:23', ROW(1)::simpl),\n    ('2017-03-22T10:18:23', ROW(2)::simpl),\n    ('2017-03-22T10:18:23', ROW(3)::simpl),\n    ('2017-03-22T10:18:23', ROW(4)::simpl),\n    ('2017-03-22T10:18:23', ROW(5)::simpl);\nSET timescaledb.max_open_chunks_per_insert=default;\nSELECT * from simpl_partition;\n          timestamp           | object \n------------------------------+--------\n Wed Mar 22 09:18:23 2017 PDT | (1)\n Wed Mar 22 10:18:23 2017 PDT | (0)\n Wed Mar 22 10:18:23 2017 PDT | (1)\n Wed Mar 22 10:18:23 2017 PDT | (2)\n Wed Mar 22 10:18:23 2017 PDT | (3)\n Wed Mar 22 10:18:23 2017 PDT | (4)\n Wed Mar 22 10:18:23 2017 PDT | (5)\n\n-- Test that index creation is handled correctly.\nCREATE TABLE hyper_with_index(time timestamptz, temp float, device int);\nCREATE UNIQUE INDEX temp_index ON hyper_with_index(temp);\n\\set ON_ERROR_STOP 0\nSELECT create_hypertable('hyper_with_index', 'time');\nERROR:  cannot create a unique index without the column \"time\" (used in partitioning)\nSELECT create_hypertable('hyper_with_index', 'time', 'device', 2);\nERROR:  cannot create a unique index without the column \"time\" (used in partitioning)\nSELECT create_hypertable('hyper_with_index', 'time', 'temp', 2);\nERROR:  cannot create a unique index without the column \"time\" (used in partitioning)\n\\set ON_ERROR_STOP 1\nDROP INDEX temp_index;\nCREATE UNIQUE INDEX time_index ON hyper_with_index(time);\n\\set ON_ERROR_STOP 0\n-- should error because device not in index\nSELECT create_hypertable('hyper_with_index', 'time', 'device', 4);\nERROR:  cannot create a unique index without the column \"device\" (used in partitioning)\n\\set ON_ERROR_STOP 1\nSELECT create_hypertable('hyper_with_index', 'time');\n       create_hypertable        \n--------------------------------\n (11,public,hyper_with_index,t)\n\n-- make sure user created index is used.\n-- not using \\d or \\d+ because output syntax differs\n-- between postgres 9 and postgres 10.\nSELECT indexname FROM pg_indexes WHERE tablename = 'hyper_with_index';\n indexname  \n------------\n time_index\n\n\\set ON_ERROR_STOP 0\nSELECT add_dimension('hyper_with_index', 'device', 4);\nERROR:  cannot create a unique index without the column \"device\" (used in partitioning)\n\\set ON_ERROR_STOP 1\nDROP INDEX time_index;\nCREATE UNIQUE INDEX time_space_index ON hyper_with_index(time, device);\nSELECT add_dimension('hyper_with_index', 'device', 4);\n             add_dimension             \n---------------------------------------\n (23,public,hyper_with_index,device,t)\n\nCREATE TABLE hyper_with_primary(time TIMESTAMPTZ PRIMARY KEY, temp float, device int);\n\\set ON_ERROR_STOP 0\nSELECT create_hypertable('hyper_with_primary', 'time', 'device', 4);\nERROR:  cannot create a unique index without the column \"device\" (used in partitioning)\n\\set ON_ERROR_STOP 1\nSELECT create_hypertable('hyper_with_primary', 'time');\n        create_hypertable         \n----------------------------------\n (13,public,hyper_with_primary,t)\n\n\\set ON_ERROR_STOP 0\nSELECT add_dimension('hyper_with_primary', 'device', 4);\nERROR:  cannot create a unique index without the column \"device\" (used in partitioning)\n\\set ON_ERROR_STOP 1\n-- NON-unique indexes can still be created\nCREATE INDEX temp_index ON hyper_with_index(temp);\n-- Make sure custom composite types are supported as dimensions\nCREATE TYPE TUPLE as (val1 int4, val2 int4);\nCREATE FUNCTION tuple_hash(value ANYELEMENT) RETURNS INT4\nLANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nBEGIN\n    RAISE NOTICE 'custom hash value is: %', value.val1+value.val2;\n    RETURN value.val1+value.val2;\nEND\n$BODY$;\nCREATE TABLE part_custom_dim (time TIMESTAMPTZ, combo TUPLE, device TEXT);\nSELECT create_hypertable('part_custom_dim', 'time', 'combo', 4, partitioning_func=>'tuple_hash');\n       create_hypertable       \n-------------------------------\n (14,public,part_custom_dim,t)\n\nINSERT INTO part_custom_dim(time, combo) VALUES (now(), (1,2));\nNOTICE:  custom hash value is: 3\nNOTICE:  custom hash value is: 3\nDROP TABLE part_custom_dim;\n-- Now make sure that renaming partitioning_func_schema will get updated properly\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA IF NOT EXISTS my_partitioning_schema;\nCREATE FUNCTION my_partitioning_schema.tuple_hash(value ANYELEMENT) RETURNS INT4\nLANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nBEGIN\n    RAISE NOTICE 'custom hash value is: %', value.val1+value.val2;\n    RETURN value.val1+value.val2;\nEND\n$BODY$;\nCREATE TABLE part_custom_dim (time TIMESTAMPTZ, combo TUPLE, device TEXT);\nSELECT create_hypertable('part_custom_dim', 'time', 'combo', 4, partitioning_func=>'my_partitioning_schema.tuple_hash');\n       create_hypertable       \n-------------------------------\n (15,public,part_custom_dim,t)\n\nINSERT INTO part_custom_dim(time, combo) VALUES (now(), (1,2));\nNOTICE:  custom hash value is: 3\nNOTICE:  custom hash value is: 3\nALTER SCHEMA my_partitioning_schema RENAME TO new_partitioning_schema;\n-- Inserts should work even after we rename the schema\nINSERT INTO part_custom_dim(time, combo) VALUES (now(), (3,4));\nNOTICE:  custom hash value is: 7\nNOTICE:  custom hash value is: 7\n-- Test partitioning function on an open (time) dimension\nCREATE OR REPLACE FUNCTION time_partfunc(unixtime float8)\n    RETURNS TIMESTAMPTZ LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nDECLARE\n    retval TIMESTAMPTZ;\nBEGIN\n\n    retval := to_timestamp(unixtime);\n    RAISE NOTICE 'time value for % is %', unixtime, timezone('UTC', retval);\n    RETURN retval;\nEND\n$BODY$;\nCREATE OR REPLACE FUNCTION time_partfunc_bad_parameters(unixtime float8, extra text)\n    RETURNS TIMESTAMPTZ LANGUAGE SQL IMMUTABLE AS\n$BODY$\n    SELECT to_timestamp(unixtime);\n$BODY$;\nCREATE OR REPLACE FUNCTION time_partfunc_bad_return_type(unixtime float8)\n    RETURNS FLOAT8 LANGUAGE SQL IMMUTABLE AS\n$BODY$\n    SELECT unixtime;\n$BODY$;\nCREATE TABLE part_time_func(time float8, temp float8, device text);\n\\set ON_ERROR_STOP 0\n-- Should fail due to invalid time column\nSELECT create_hypertable('part_time_func', 'time');\nERROR:  invalid type for dimension \"time\"\n-- Should fail due to bad signature of time partitioning function\nSELECT create_hypertable('part_time_func', 'time', time_partitioning_func => 'time_partfunc_bad_parameters');\nERROR:  invalid partitioning function\nSELECT create_hypertable('part_time_func', 'time', time_partitioning_func => 'time_partfunc_bad_return_type');\nERROR:  invalid partitioning function\n\\set ON_ERROR_STOP 1\n-- Should work with time partitioning function that returns a valid time type\nSELECT create_hypertable('part_time_func', 'time', time_partitioning_func => 'time_partfunc');\n      create_hypertable       \n------------------------------\n (16,public,part_time_func,t)\n\nINSERT INTO part_time_func VALUES (1530214157.134, 23.4, 'dev1'),\n                                  (1533214157.8734, 22.3, 'dev7');\nNOTICE:  time value for 1530214157.134 is Thu Jun 28 19:29:17.134 2018\nNOTICE:  time value for 1530214157.134 is Thu Jun 28 19:29:17.134 2018\nNOTICE:  time value for 1530214157.134 is Thu Jun 28 19:29:17.134 2018\nNOTICE:  time value for 1530214157.134 is Thu Jun 28 19:29:17.134 2018\nNOTICE:  time value for 1533214157.8734 is Thu Aug 02 12:49:17.8734 2018\nNOTICE:  time value for 1533214157.8734 is Thu Aug 02 12:49:17.8734 2018\nNOTICE:  time value for 1533214157.8734 is Thu Aug 02 12:49:17.8734 2018\nNOTICE:  time value for 1533214157.8734 is Thu Aug 02 12:49:17.8734 2018\nSELECT time, temp, device FROM part_time_func;\n      time       | temp | device \n-----------------+------+--------\n  1530214157.134 | 23.4 | dev1\n 1533214157.8734 | 22.3 | dev7\n\nSELECT time_partfunc(time) at time zone 'UTC', temp, device FROM part_time_func;\nNOTICE:  time value for 1530214157.134 is Thu Jun 28 19:29:17.134 2018\nNOTICE:  time value for 1533214157.8734 is Thu Aug 02 12:49:17.8734 2018\n           timezone            | temp | device \n-------------------------------+------+--------\n Thu Jun 28 19:29:17.134 2018  | 23.4 | dev1\n Thu Aug 02 12:49:17.8734 2018 | 22.3 | dev7\n\nSELECT * FROM test.show_subtables('part_time_func');\n                  Child                   | Tablespace \n------------------------------------------+------------\n _timescaledb_internal._hyper_16_11_chunk | \n _timescaledb_internal._hyper_16_12_chunk | \n\nSELECT (test.show_constraints(\"Child\")).*\nFROM test.show_subtables('part_time_func');\n  Constraint   | Type | Columns | Index |                                                                                     Expr                                                                                     | Deferrable | Deferred | Validated \n---------------+------+---------+-------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+----------+-----------\n constraint_18 | c    | {time}  | -     | ((time_partfunc(\"time\") >= 'Wed Jun 27 17:00:00 2018 PDT'::timestamp with time zone) AND (time_partfunc(\"time\") < 'Wed Jul 04 17:00:00 2018 PDT'::timestamp with time zone)) | f          | f        | t\n constraint_19 | c    | {time}  | -     | ((time_partfunc(\"time\") >= 'Wed Aug 01 17:00:00 2018 PDT'::timestamp with time zone) AND (time_partfunc(\"time\") < 'Wed Aug 08 17:00:00 2018 PDT'::timestamp with time zone)) | f          | f        | t\n\nSELECT (test.show_indexes(\"Child\")).*\nFROM test.show_subtables('part_time_func');\n                              Index                               | Columns |        Expr         | Unique | Primary | Exclusion | Tablespace \n------------------------------------------------------------------+---------+---------------------+--------+---------+-----------+------------\n _timescaledb_internal._hyper_16_11_chunk_part_time_func_expr_idx | {expr}  | time_partfunc(expr) | f      | f       | f         | \n _timescaledb_internal._hyper_16_12_chunk_part_time_func_expr_idx | {expr}  | time_partfunc(expr) | f      | f       | f         | \n\n-- Check that constraint exclusion works with time partitioning\n-- function (scan only one chunk)\n-- No exclusion\nEXPLAIN (verbose, buffers off, costs off)\nSELECT * FROM part_time_func;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on _timescaledb_internal._hyper_16_11_chunk\n         Output: _hyper_16_11_chunk.\"time\", _hyper_16_11_chunk.temp, _hyper_16_11_chunk.device\n   ->  Seq Scan on _timescaledb_internal._hyper_16_12_chunk\n         Output: _hyper_16_12_chunk.\"time\", _hyper_16_12_chunk.temp, _hyper_16_12_chunk.device\n\n-- Exclude using the function on time\nEXPLAIN (verbose, buffers off, costs off)\nSELECT * FROM part_time_func WHERE time_partfunc(time) < '2018-07-01';\n--- QUERY PLAN ---\n Index Scan using _hyper_16_11_chunk_part_time_func_expr_idx on _timescaledb_internal._hyper_16_11_chunk\n   Output: _hyper_16_11_chunk.\"time\", _hyper_16_11_chunk.temp, _hyper_16_11_chunk.device\n   Index Cond: (time_partfunc(_hyper_16_11_chunk.\"time\") < 'Sun Jul 01 00:00:00 2018 PDT'::timestamp with time zone)\n\n-- Exclude using the same date but as a UNIX timestamp. Won't do an\n-- index scan since the index is on the time function expression\nEXPLAIN (verbose, buffers off, costs off)\nSELECT * FROM part_time_func WHERE time < 1530403200.0;\nNOTICE:  time value for 1530403200 is Sun Jul 01 00:00:00 2018\n--- QUERY PLAN ---\n Seq Scan on _timescaledb_internal._hyper_16_11_chunk\n   Output: _hyper_16_11_chunk.\"time\", _hyper_16_11_chunk.temp, _hyper_16_11_chunk.device\n   Filter: (_hyper_16_11_chunk.\"time\" < '1530403200'::double precision)\n\n-- Check that inserts will fail if we use a time partitioning function\n-- that returns NULL\nCREATE OR REPLACE FUNCTION time_partfunc_null_ret(unixtime float8)\n    RETURNS TIMESTAMPTZ LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nBEGIN\n    RETURN NULL;\nEND\n$BODY$;\nCREATE TABLE part_time_func_null_ret(time float8, temp float8, device text);\nSELECT create_hypertable('part_time_func_null_ret', 'time', time_partitioning_func => 'time_partfunc_null_ret');\n           create_hypertable           \n---------------------------------------\n (17,public,part_time_func_null_ret,t)\n\n\\set ON_ERROR_STOP 0\nINSERT INTO part_time_func_null_ret VALUES (1530214157.134, 23.4, 'dev1'),\n                                           (1533214157.8734, 22.3, 'dev7');\nERROR:  partitioning function \"public.time_partfunc_null_ret\" returned NULL\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "test/expected/partition_coercion.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- Test partition chunk exclusion with cross-type comparisons\n-- wrong result: text column + name literal\nCREATE TABLE hash_text(time timestamptz NOT NULL, device text);\nSELECT create_hypertable('hash_text', 'time');\n   create_hypertable    \n------------------------\n (1,public,hash_text,t)\n\nSELECT add_dimension('hash_text', 'device', number_partitions => 3);\n         add_dimension         \n-------------------------------\n (2,public,hash_text,device,t)\n\nINSERT INTO hash_text VALUES ('2000-01-01', 'abc');\nSELECT count(*) FROM hash_text WHERE device = 'abc'::name;\n count \n-------\n     1\n\nDROP TABLE hash_text;\n-- x86_64 wrong result: int4 time column + large int8 literal + range query\n-- 4294967196::int8 truncates to -100 as signed int4\n-- Chunk exclusion uses time < -100, excluding all positive-time chunks\nCREATE FUNCTION time_part_int4(val int4) RETURNS int4 AS $$ SELECT val $$ LANGUAGE SQL IMMUTABLE;\nCREATE TABLE time_int4(time int4 NOT NULL, v int);\nSELECT create_hypertable('time_int4', 'time', chunk_time_interval => 100, time_partitioning_func => 'time_part_int4');\n   create_hypertable    \n------------------------\n (2,public,time_int4,t)\n\nINSERT INTO time_int4 VALUES (100, 1), (200, 2);\n-- Both rows satisfy time < 4294967196, but bug truncates to time < -100\nSELECT count(*) FROM time_int4 WHERE time < 4294967196::int8;\n count \n-------\n     2\n\nDROP TABLE time_int4;\nDROP FUNCTION time_part_int4;\n-- i386 crash: int8 time column + int4 literal + custom partitioning\n-- On i386: SEGFAULT (DatumGetInt64 dereferences byval int4 as pointer)\n-- On x86_64: works by coincidence (both int4 and int8 are byval)\nCREATE FUNCTION time_part_int8(val int8) RETURNS int8 AS $$ SELECT val $$ LANGUAGE SQL IMMUTABLE;\nCREATE TABLE time_int8(time int8 NOT NULL, v int);\nSELECT create_hypertable('time_int8', 'time', chunk_time_interval => 10, time_partitioning_func => 'time_part_int8');\n   create_hypertable    \n------------------------\n (3,public,time_int8,t)\n\nINSERT INTO time_int8 VALUES (1, 1), (11, 2), (21, 3);\nSELECT count(*) FROM time_int8 WHERE time = 1::int4;\n count \n-------\n     1\n\nDROP TABLE time_int8;\nDROP FUNCTION time_part_int8;\n-- Exact type match: text column + text literal (no coercion needed)\nCREATE TABLE hash_text_exact(time timestamptz NOT NULL, device text);\nSELECT create_hypertable('hash_text_exact', 'time');\n      create_hypertable       \n------------------------------\n (4,public,hash_text_exact,t)\n\nSELECT add_dimension('hash_text_exact', 'device', number_partitions => 3);\n            add_dimension            \n-------------------------------------\n (6,public,hash_text_exact,device,t)\n\nINSERT INTO hash_text_exact VALUES ('2000-01-01', 'abc');\nSELECT count(*) FROM hash_text_exact WHERE device = 'abc'::text;\n count \n-------\n     1\n\nDROP TABLE hash_text_exact;\n-- Binary compatible types: text column + varchar literal\n-- PostgreSQL coerces varchar to text at parse time\nCREATE TABLE hash_text_varchar(time timestamptz NOT NULL, device text);\nSELECT create_hypertable('hash_text_varchar', 'time');\n       create_hypertable        \n--------------------------------\n (5,public,hash_text_varchar,t)\n\nSELECT add_dimension('hash_text_varchar', 'device', number_partitions => 3);\n             add_dimension             \n---------------------------------------\n (8,public,hash_text_varchar,device,t)\n\nINSERT INTO hash_text_varchar VALUES ('2000-01-01', 'abc');\nSELECT count(*) FROM hash_text_varchar WHERE device = 'abc'::varchar;\n count \n-------\n     1\n\nDROP TABLE hash_text_varchar;\n-- Array coercion: text column + name[] array (ScalarArrayOpExpr)\n-- Test both ANY (OR) and ALL (AND) semantics\nCREATE TABLE hash_text_array(time timestamptz NOT NULL, device text);\nSELECT create_hypertable('hash_text_array', 'time');\n      create_hypertable       \n------------------------------\n (6,public,hash_text_array,t)\n\nSELECT add_dimension('hash_text_array', 'device', number_partitions => 3);\n            add_dimension             \n--------------------------------------\n (10,public,hash_text_array,device,t)\n\nINSERT INTO hash_text_array VALUES ('2000-01-01', 'abc'), ('2000-01-01', 'def'), ('2000-01-01', 'ghi');\n-- OR: match any element\nSELECT count(*) FROM hash_text_array WHERE device = ANY(ARRAY['abc', 'def']::name[]);\n count \n-------\n     2\n\n-- AND: match all elements (logically empty for different values, but exercises code path)\nSELECT count(*) FROM hash_text_array WHERE device = ALL(ARRAY['abc', 'def']::name[]);\n count \n-------\n     0\n\n-- AND: single element (equivalent to =)\nSELECT count(*) FROM hash_text_array WHERE device = ALL(ARRAY['abc']::name[]);\n count \n-------\n     1\n\nDROP TABLE hash_text_array;\n-- Time dimension with SAOP + type coercion (int4 column, int8 array)\n-- Note: open (time/range) dimensions can't use SAOP with multiple OR values\n-- for chunk exclusion, but AND with single effective bound works.\n-- These tests verify correct results and that AND cases use chunk exclusion.\nCREATE FUNCTION time_part_int4_saop(val int4) RETURNS int4 AS $$ SELECT val $$ LANGUAGE SQL IMMUTABLE;\nCREATE TABLE time_int4_saop(time int4 NOT NULL, v int);\nSELECT create_hypertable('time_int4_saop', 'time', chunk_time_interval => 100, time_partitioning_func => 'time_part_int4_saop');\n      create_hypertable      \n-----------------------------\n (7,public,time_int4_saop,t)\n\nINSERT INTO time_int4_saop VALUES (50, 1), (150, 2), (250, 3);\n-- AND: time < ALL(array) means time < min(array) = 100\n-- Single effective bound, chunk exclusion should work\nSELECT count(*) FROM time_int4_saop WHERE time < ALL(ARRAY[100, 200]::int8[]);\n count \n-------\n     1\n\n-- AND: time > ALL(array) means time > max(array) = 200\nSELECT count(*) FROM time_int4_saop WHERE time > ALL(ARRAY[100, 200]::int8[]);\n count \n-------\n     1\n\n-- OR cases: chunk exclusion not used (multiple OR values rejected),\n-- but results must still be correct\nSELECT count(*) FROM time_int4_saop WHERE time < ANY(ARRAY[100, 200]::int8[]);\n count \n-------\n     2\n\nSELECT count(*) FROM time_int4_saop WHERE time > ANY(ARRAY[100, 200]::int8[]);\n count \n-------\n     2\n\nDROP TABLE time_int4_saop;\nDROP FUNCTION time_part_int4_saop;\n-- Prepared statement with varchar parameter, text column\n-- Custom plan: coercion at plan time, chunk exclusion works\n-- Generic plan: no chunk exclusion (param unknown), but correct result\nCREATE TABLE hash_prep(time timestamptz NOT NULL, device text);\nSELECT create_hypertable('hash_prep', 'time');\n   create_hypertable    \n------------------------\n (8,public,hash_prep,t)\n\nSELECT add_dimension('hash_prep', 'device', number_partitions => 3);\n         add_dimension          \n--------------------------------\n (13,public,hash_prep,device,t)\n\nINSERT INTO hash_prep VALUES ('2000-01-01', 'abc'), ('2000-01-01', 'def');\nPREPARE hash_q(varchar) AS SELECT count(*) FROM hash_prep WHERE device = $1;\nSET plan_cache_mode = force_custom_plan;\nEXECUTE hash_q('abc');\n count \n-------\n     1\n\nEXECUTE hash_q('def');\n count \n-------\n     1\n\nSET plan_cache_mode = force_generic_plan;\nEXECUTE hash_q('abc');\n count \n-------\n     1\n\nEXECUTE hash_q('def');\n count \n-------\n     1\n\nRESET plan_cache_mode;\nDEALLOCATE hash_q;\nDROP TABLE hash_prep;\n-- Multiple ANDed restrictions on closed dimension\n-- Use IN + = to exercise intersection: IN creates list, = intersects with it\nCREATE TABLE hash_multi(time timestamptz NOT NULL, device text);\nSELECT create_hypertable('hash_multi', 'time');\n    create_hypertable    \n-------------------------\n (9,public,hash_multi,t)\n\nSELECT add_dimension('hash_multi', 'device', number_partitions => 3);\n          add_dimension          \n---------------------------------\n (15,public,hash_multi,device,t)\n\nINSERT INTO hash_multi VALUES ('2000-01-01', 'abc'), ('2000-01-01', 'def');\n-- IN creates partition list [abc,def], then = intersects with [abc] => [abc]\nSELECT count(*) FROM hash_multi WHERE device IN ('abc', 'def') AND device = 'abc';\n count \n-------\n     1\n\nDROP TABLE hash_multi;\n-- NULL array elements: exercises branch when iterating arrays with NULLs\n-- The NULL elements should be skipped, non-NULL elements should work\nCREATE TABLE hash_null_array(time timestamptz NOT NULL, device text);\nSELECT create_hypertable('hash_null_array', 'time');\n       create_hypertable       \n-------------------------------\n (10,public,hash_null_array,t)\n\nSELECT add_dimension('hash_null_array', 'device', number_partitions => 3);\n            add_dimension             \n--------------------------------------\n (17,public,hash_null_array,device,t)\n\nINSERT INTO hash_null_array VALUES ('2000-01-01', 'abc'), ('2000-01-01', 'def');\n-- Array with NULL elements (type coercion path)\nSELECT count(*) FROM hash_null_array WHERE device = ANY(ARRAY['abc', NULL, 'def']::name[]);\n count \n-------\n     2\n\n-- Array with NULL elements (no type coercion path)\nSELECT count(*) FROM hash_null_array WHERE device = ANY(ARRAY['abc', NULL, 'def']::text[]);\n count \n-------\n     2\n\nDROP TABLE hash_null_array;\n"
  },
  {
    "path": "test/expected/partitioned_hypertable.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- Test declarative partitioning for hypertables\n-- Enable declarative partitioning for all subsequent tests\nSET timescaledb.enable_partitioned_hypertables = true;\n-- Basic hypertable creation with TIMESTAMPTZ\nCREATE TABLE metrics(\n    time TIMESTAMP WITH TIME ZONE,\n    device TEXT,\n    value FLOAT\n) WITH (timescaledb.hypertable, timescaledb.partition_column='time');\n-- Create with TIMESTAMP\nCREATE TABLE metrics_ts(\n    time TIMESTAMP NOT NULL,\n    device TEXT,\n    value FLOAT\n) WITH (timescaledb.hypertable, timescaledb.partition_column='time');\n-- Create with DATE\nCREATE TABLE metrics_date(\n    time DATE NOT NULL,\n    device TEXT,\n    value FLOAT\n) WITH (timescaledb.hypertable, timescaledb.partition_column='time');\n-- Create with int\nCREATE TABLE metrics_int(\n    time INT NOT NULL,\n    device TEXT,\n    value FLOAT\n) WITH (timescaledb.hypertable, timescaledb.partition_column='time');\n-- Create with custom chunk_time_interval\nCREATE TABLE metrics_custom_interval(\n    time TIMESTAMPTZ NOT NULL,\n    device TEXT,\n    value FLOAT\n) WITH (timescaledb.hypertable, timescaledb.partition_column='time', timescaledb.chunk_interval='30 days');\n-- Verify hypertables are actually created and partitioned\nSELECT hypertable_name FROM timescaledb_information.hypertables\nWHERE hypertable_name IN ('metrics', 'metrics_ts', 'metrics_date', 'metrics_int', 'metrics_custom_interval')\nORDER BY hypertable_name;\n     hypertable_name     \n-------------------------\n metrics\n metrics_custom_interval\n metrics_date\n metrics_int\n metrics_ts\n\nSELECT DISTINCT(relkind) = 'p' FROM pg_class\nWHERE relname IN ('metrics', 'metrics_ts', 'metrics_date', 'metrics_int', 'metrics_custom_interval');\n ?column? \n----------\n t\n\n\\set ON_ERROR_STOP 0\n-- Try to create with invalid partition column type\nCREATE TABLE invalid(time TEXT, value FLOAT) WITH (timescaledb.hypertable, timescaledb.partition_column='time');\nERROR:  invalid type for dimension \"time\"\nCREATE TABLE invalid(time TEXT, value FLOAT) WITH (timescaledb.hypertable);\nERROR:  partition column could not be determined\n\\set ON_ERROR_STOP 1\nDROP TABLE IF EXISTS metrics_ts;\nDROP TABLE IF EXISTS metrics_date;\nDROP TABLE IF EXISTS metrics_int;\nDROP TABLE IF EXISTS metrics_custom_interval;\nDROP TABLE IF EXISTS invalid;\nNOTICE:  table \"invalid\" does not exist, skipping\n-- Test PARTITION BY syntax\nCREATE TABLE metrics_partition_by(\n    time TIMESTAMPTZ NOT NULL,\n    device TEXT,\n    value FLOAT\n) PARTITION BY RANGE (time) WITH (timescaledb.hypertable);\n\\set ON_ERROR_STOP 0\nCREATE TABLE part_col_specified(time TIMESTAMPTZ, device TEXT) PARTITION BY RANGE (time) WITH (timescaledb.hypertable, timescaledb.partition_column='time');\nERROR:  cannot specify both PARTITION BY and timescaledb.partition_column\nCREATE TABLE multiple_part_key(time TIMESTAMPTZ, time2 TIMESTAMP, device TEXT) PARTITION BY RANGE (time, time2) WITH (timescaledb.hypertable, timescaledb.partition_column='time');\nERROR:  only single column partitioning is supported for partitioned hypertables\nCREATE TABLE bad_strategy(time TIMESTAMPTZ, device TEXT) PARTITION BY LIST (time) WITH (timescaledb.hypertable);\nERROR:  only RANGE partitioning is supported for partitioned hypertables\n\\set ON_ERROR_STOP 1\nDROP TABLE IF EXISTS metrics_partition_by;\n-- Insert Operations and Chunk Creation\nINSERT INTO metrics VALUES\n  ('2025-01-15 00:00:00+00', 'device1', 11.0),\n  ('2025-02-15 00:00:00+00', 'device2', 12.0),\n  ('2025-03-15 00:00:00+00', 'device3', 13.0);\nSELECT count(*) FROM show_chunks('metrics');\n count \n-------\n     3\n\nSELECT count(*) FROM metrics;\n count \n-------\n     3\n\nSELECT * FROM metrics ORDER BY time;\n             time             | device  | value \n------------------------------+---------+-------\n Tue Jan 14 16:00:00 2025 PST | device1 |    11\n Fri Feb 14 16:00:00 2025 PST | device2 |    12\n Fri Mar 14 17:00:00 2025 PDT | device3 |    13\n\n-- Verify chunk was created and attached as partition\nSELECT count(*) FROM show_chunks('metrics');\n count \n-------\n     3\n\nSELECT child.relname AS chunk, parent.relname AS hypertable\nFROM pg_inherits\nJOIN pg_class child ON inhrelid = child.oid\nJOIN pg_class parent ON inhparent = parent.oid\nWHERE parent.relname = 'metrics'\nLIMIT 1;\n      chunk       | hypertable \n------------------+------------\n _hyper_1_1_chunk | metrics\n\n-- Insert with CHECK constraint\nALTER TABLE metrics ADD CONSTRAINT valcheck CHECK (value >= 0);\n-- Try inserting into existing and new chunk to violate CHECK constraint\n\\set ON_ERROR_STOP 0\nINSERT INTO metrics VALUES ('2025-03-15 00:00:00+00', 'device1', -10.0);\nERROR:  new row for relation \"_hyper_1_3_chunk\" violates check constraint \"valcheck\"\nINSERT INTO metrics VALUES ('2025-04-15 00:00:00+00', 'device1', -10.0);\nERROR:  new row for relation \"_hyper_1_4_chunk\" violates check constraint \"valcheck\"\n\\set ON_ERROR_STOP 1\n-- SELECT with WHERE on time (partition pruning)\nEXPLAIN (COSTS OFF)\nSELECT * FROM metrics WHERE time >= '2025-01-01' AND time < '2025-02-01';\n--- QUERY PLAN ---\n Seq Scan on _hyper_1_1_chunk metrics\n   Filter: ((\"time\" >= 'Wed Jan 01 00:00:00 2025 PST'::timestamp with time zone) AND (\"time\" < 'Sat Feb 01 00:00:00 2025 PST'::timestamp with time zone))\n\n-- FOREIGN KEY from hypertable to regular table\nCREATE TABLE ref_table(id INT PRIMARY KEY, name TEXT);\nINSERT INTO ref_table VALUES (1, 'ref1'), (2, 'ref2');\nCREATE TABLE fk_table(\n  time TIMESTAMPTZ NOT NULL,\n  ref_id INT REFERENCES ref_table(id),\n  value FLOAT\n) WITH (timescaledb.hypertable, timescaledb.partition_column='time');\nINSERT INTO fk_table VALUES ('2025-11-01', 1, 10.0);\n\\set ON_ERROR_STOP 0\nINSERT INTO fk_table VALUES ('2025-11-01', 999, 20.0);\nERROR:  insert or update on table \"_hyper_8_5_chunk\" violates foreign key constraint \"fk_table_ref_id_fkey\"\n\\set ON_ERROR_STOP 1\nDROP TABLE ref_table CASCADE;\nNOTICE:  drop cascades to constraint fk_table_ref_id_fkey on table fk_table\nDROP TABLE fk_table CASCADE;\n-- FOREIGN KEY from hypertable to hypertable\nCREATE TABLE ref_ht(\n    time TIMESTAMPTZ NOT NULL ,\n    id INT,\n    CONSTRAINT ref_ht_pkey PRIMARY KEY (time, id)\n) WITH (timescaledb.hypertable, timescaledb.partition_column='time');\nINSERT INTO ref_ht VALUES\n  ('2025-06-15 00:00:00+00', 1),\n  ('2025-07-15 00:00:00+00', 2);\nCREATE TABLE fk_ht(\n    time TIMESTAMPTZ NOT NULL,\n    ref_time TIMESTAMPTZ,\n    ref_id INT,\n    value FLOAT,\n    FOREIGN KEY (ref_time, ref_id) REFERENCES ref_ht(time, id)\n) WITH (timescaledb.hypertable, timescaledb.partition_column='time');\nINSERT INTO fk_ht VALUES\n    ('2025-08-15 00:00:00+00', '2025-06-15 00:00:00+00', 1, 31.0);\n\\set ON_ERROR_STOP 0\nINSERT INTO fk_ht VALUES\n    ('2025-08-15 00:00:00+00', '2025-01-01 00:00:00+00', 999, 32.0);\nERROR:  insert or update on table \"_hyper_10_8_chunk\" violates foreign key constraint \"fk_ht_ref_time_ref_id_fkey\"\n\\set ON_ERROR_STOP 1\nDROP TABLE ref_ht CASCADE;\nNOTICE:  drop cascades to constraint fk_ht_ref_time_ref_id_fkey on table fk_ht\nDROP TABLE fk_ht CASCADE;\n-- Test if foreign keys to hypertables not using declarative partitioning are still disallowed\nSET timescaledb.enable_partitioned_hypertables = false;\nCREATE TABLE ref_ht(\n    time TIMESTAMPTZ NOT NULL ,\n    id INT,\n    CONSTRAINT ref_ht_pkey PRIMARY KEY (time, id)\n) WITH (timescaledb.hypertable, timescaledb.partition_column='time');\nINSERT INTO ref_ht VALUES\n  ('2025-06-15 00:00:00+00', 1),\n  ('2025-07-15 00:00:00+00', 2);\nSET timescaledb.enable_partitioned_hypertables = true;\n\\set ON_ERROR_STOP 0\nCREATE TABLE fk_ht(\n    time TIMESTAMPTZ NOT NULL,\n    ref_time TIMESTAMPTZ,\n    ref_id INT,\n    value FLOAT,\n    FOREIGN KEY (ref_time, ref_id) REFERENCES ref_ht(time, id)\n) WITH (timescaledb.hypertable, timescaledb.partition_column='time');\nERROR:  hypertables cannot be used as foreign key references of hypertables\n\\set ON_ERROR_STOP 1\nDROP TABLE ref_ht CASCADE;\nDROP TABLE IF EXISTS fk_ht CASCADE;\nNOTICE:  table \"fk_ht\" does not exist, skipping\n-- Test partition wise joins\nCREATE TABLE metrics_pwj(\n    time TIMESTAMPTZ NOT NULL,\n    device TEXT,\n    value FLOAT\n) WITH (timescaledb.hypertable, timescaledb.partition_column='time');\nINSERT INTO metrics_pwj VALUES\n  ('2025-01-15 00:00:00+00', 'device1', 11.0),\n  ('2025-02-15 00:00:00+00', 'device2', 12.0),\n  ('2025-03-15 00:00:00+00', 'device3', 13.0);\nSET enable_partitionwise_join = true;\nEXPLAIN (COSTS OFF)\nSELECT m1.device, m2.device\nFROM metrics AS m1\nJOIN metrics_pwj AS m2 ON m1.time = m2.time;\n--- QUERY PLAN ---\n Append\n   ->  Nested Loop\n         Join Filter: (m1_1.\"time\" = m2_1.\"time\")\n         ->  Seq Scan on _hyper_1_1_chunk m1_1\n         ->  Seq Scan on _hyper_13_11_chunk m2_1\n   ->  Nested Loop\n         Join Filter: (m1_2.\"time\" = m2_2.\"time\")\n         ->  Seq Scan on _hyper_1_2_chunk m1_2\n         ->  Seq Scan on _hyper_13_12_chunk m2_2\n   ->  Nested Loop\n         Join Filter: (m1_3.\"time\" = m2_3.\"time\")\n         ->  Seq Scan on _hyper_1_3_chunk m1_3\n         ->  Seq Scan on _hyper_13_13_chunk m2_3\n\nSET enable_partitionwise_join = false;\n-- Transaction - ROLLBACK\nBEGIN;\nINSERT INTO metrics VALUES ('2024-12-20', 'rollback_test', 42.0);\nSELECT count(*)=1 FROM metrics WHERE device = 'rollback_test';\n ?column? \n----------\n t\n\nROLLBACK;\nSELECT count(*)=0 FROM metrics WHERE device = 'rollback_test';\n ?column? \n----------\n t\n\n-- Reset GUC\nSET timescaledb.enable_partitioned_hypertables = false;\n-- Cleanup\nDROP TABLE IF EXISTS metrics CASCADE;\nDROP TABLE IF EXISTS metrics_pwj CASCADE;\n"
  },
  {
    "path": "test/expected/partitioning.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- Should expect an error when creating a hypertable from a partition\n\\set ON_ERROR_STOP 0\nCREATE TABLE partitioned_ht_create(time timestamptz, temp float, device int) PARTITION BY RANGE (time);\nSELECT create_hypertable('partitioned_ht_create', 'time');\nERROR:  table \"partitioned_ht_create\" is already partitioned\n\\set ON_ERROR_STOP 1\n-- Should expect an error when attaching a hypertable to a partition\n\\set ON_ERROR_STOP 0\nCREATE TABLE partitioned_attachment_vanilla(time timestamptz, temp float, device int) PARTITION BY RANGE (time);\nCREATE TABLE attachment_hypertable(time timestamptz, temp float, device int);\nSELECT create_hypertable('attachment_hypertable', 'time');\n         create_hypertable          \n------------------------------------\n (1,public,attachment_hypertable,t)\n\nALTER TABLE partitioned_attachment_vanilla ATTACH PARTITION attachment_hypertable FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');\nERROR:  hypertables do not support native postgres partitioning\n\\set ON_ERROR_STOP 1\n-- Should not expect an error when attaching a normal table to a partition\nCREATE TABLE partitioned_vanilla(time timestamptz, temp float, device int) PARTITION BY RANGE (time);\nCREATE TABLE attachment_vanilla(time timestamptz, temp float, device int);\nALTER TABLE partitioned_vanilla ATTACH PARTITION attachment_vanilla FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');\n"
  },
  {
    "path": "test/expected/partitionwise-15.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\set PREFIX 'EXPLAIN (VERBOSE, BUFFERS OFF, COSTS OFF)'\n-- Create a two dimensional hypertable\nCREATE TABLE hyper (time timestamptz, device int, temp float);\nSELECT * FROM create_hypertable('hyper', 'time', 'device', 2);\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             1 | public      | hyper      | t\n\n-- Create a similar PostgreSQL partitioned table\nCREATE TABLE pg2dim (time timestamptz, device int, temp float) PARTITION BY HASH (device);\nCREATE TABLE pg2dim_h1 PARTITION OF pg2dim FOR VALUES WITH (MODULUS 2, REMAINDER 0) PARTITION BY RANGE(time);\nCREATE TABLE pg2dim_h2 PARTITION OF pg2dim FOR VALUES WITH (MODULUS 2, REMAINDER 1) PARTITION BY RANGE(time);\nCREATE TABLE pg2dim_h1_t1 PARTITION OF pg2dim_h1 FOR VALUES FROM ('2018-01-01 00:00') TO ('2018-09-01 00:00');\nCREATE TABLE pg2dim_h1_t2 PARTITION OF pg2dim_h1 FOR VALUES FROM ('2018-09-01 00:00') TO ('2018-12-01 00:00');\nCREATE TABLE pg2dim_h2_t1 PARTITION OF pg2dim_h2 FOR VALUES FROM ('2018-01-01 00:00') TO ('2018-09-01 00:00');\nCREATE TABLE pg2dim_h2_t2 PARTITION OF pg2dim_h2 FOR VALUES FROM ('2018-09-01 00:00') TO ('2018-12-01 00:00');\n-- Create a 1-dimensional partitioned table for comparison\nCREATE TABLE pg1dim (time timestamptz, device int, temp float) PARTITION BY HASH (device);\nCREATE TABLE pg1dim_h1 PARTITION OF pg1dim FOR VALUES WITH (MODULUS 2, REMAINDER 0);\nCREATE TABLE pg1dim_h2 PARTITION OF pg1dim FOR VALUES WITH (MODULUS 2, REMAINDER 1);\nINSERT INTO hyper VALUES\n       ('2018-02-19 13:01', 1, 2.3),\n       ('2018-02-19 13:02', 3, 3.1),\n       ('2018-10-19 13:01', 1, 7.6),\n       ('2018-10-19 13:02', 3, 9.0);\nINSERT INTO pg2dim VALUES\n       ('2018-02-19 13:01', 1, 2.3),\n       ('2018-02-19 13:02', 3, 3.1),\n       ('2018-10-19 13:01', 1, 7.6),\n       ('2018-10-19 13:02', 3, 9.0);\nINSERT INTO pg1dim VALUES\n       ('2018-02-19 13:01', 1, 2.3),\n       ('2018-02-19 13:02', 3, 3.1),\n       ('2018-10-19 13:01', 1, 7.6),\n       ('2018-10-19 13:02', 3, 9.0);\nSELECT * FROM test.show_subtables('hyper');\n                 Child                  | Tablespace \n----------------------------------------+------------\n _timescaledb_internal._hyper_1_1_chunk | \n _timescaledb_internal._hyper_1_2_chunk | \n _timescaledb_internal._hyper_1_3_chunk | \n _timescaledb_internal._hyper_1_4_chunk | \n\nSELECT * FROM pg2dim_h1_t1;\n             time             | device | temp \n------------------------------+--------+------\n Mon Feb 19 13:01:00 2018 PST |      1 |  2.3\n\nSELECT * FROM pg2dim_h1_t2;\n             time             | device | temp \n------------------------------+--------+------\n Fri Oct 19 13:01:00 2018 PDT |      1 |  7.6\n\nSELECT * FROM pg2dim_h2_t1;\n             time             | device | temp \n------------------------------+--------+------\n Mon Feb 19 13:02:00 2018 PST |      3 |  3.1\n\nSELECT * FROM pg2dim_h2_t2;\n             time             | device | temp \n------------------------------+--------+------\n Fri Oct 19 13:02:00 2018 PDT |      3 |    9\n\n-- Compare partitionwise aggreate enabled/disabled. First run queries\n-- on PG partitioned tables for reference.\n-- All partition keys covered by GROUP BY\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT device, avg(temp)\nFROM pg1dim\nGROUP BY 1\nORDER BY 1;\n--- QUERY PLAN ---\n Sort\n   Output: pg1dim.device, (avg(pg1dim.temp))\n   Sort Key: pg1dim.device\n   ->  HashAggregate\n         Output: pg1dim.device, avg(pg1dim.temp)\n         Group Key: pg1dim.device\n         ->  Append\n               ->  Seq Scan on public.pg1dim_h1 pg1dim_1\n                     Output: pg1dim_1.device, pg1dim_1.temp\n               ->  Seq Scan on public.pg1dim_h2 pg1dim_2\n                     Output: pg1dim_2.device, pg1dim_2.temp\n\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT device, avg(temp)\nFROM pg1dim\nGROUP BY 1\nORDER BY 1;\n--- QUERY PLAN ---\n Sort\n   Output: pg1dim.device, (avg(pg1dim.temp))\n   Sort Key: pg1dim.device\n   ->  Append\n         ->  HashAggregate\n               Output: pg1dim.device, avg(pg1dim.temp)\n               Group Key: pg1dim.device\n               ->  Seq Scan on public.pg1dim_h1 pg1dim\n                     Output: pg1dim.device, pg1dim.temp\n         ->  HashAggregate\n               Output: pg1dim_1.device, avg(pg1dim_1.temp)\n               Group Key: pg1dim_1.device\n               ->  Seq Scan on public.pg1dim_h2 pg1dim_1\n                     Output: pg1dim_1.device, pg1dim_1.temp\n\n-- All partition keys not covered by GROUP BY (partial partitionwise)\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT device, avg(temp)\nFROM pg2dim\nGROUP BY 1\nORDER BY 1;\n--- QUERY PLAN ---\n Sort\n   Output: pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: pg2dim.device\n   ->  HashAggregate\n         Output: pg2dim.device, avg(pg2dim.temp)\n         Group Key: pg2dim.device\n         ->  Append\n               ->  Seq Scan on public.pg2dim_h1_t1 pg2dim_1\n                     Output: pg2dim_1.device, pg2dim_1.temp\n               ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_2\n                     Output: pg2dim_2.device, pg2dim_2.temp\n               ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_3\n                     Output: pg2dim_3.device, pg2dim_3.temp\n               ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_4\n                     Output: pg2dim_4.device, pg2dim_4.temp\n\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT device, avg(temp)\nFROM pg2dim\nGROUP BY 1\nORDER BY 1;\n--- QUERY PLAN ---\n Sort\n   Output: pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: pg2dim.device\n   ->  Finalize HashAggregate\n         Output: pg2dim.device, avg(pg2dim.temp)\n         Group Key: pg2dim.device\n         ->  Append\n               ->  Partial HashAggregate\n                     Output: pg2dim.device, PARTIAL avg(pg2dim.temp)\n                     Group Key: pg2dim.device\n                     ->  Seq Scan on public.pg2dim_h1_t1 pg2dim\n                           Output: pg2dim.device, pg2dim.temp\n               ->  Partial HashAggregate\n                     Output: pg2dim_1.device, PARTIAL avg(pg2dim_1.temp)\n                     Group Key: pg2dim_1.device\n                     ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_1\n                           Output: pg2dim_1.device, pg2dim_1.temp\n               ->  Partial HashAggregate\n                     Output: pg2dim_2.device, PARTIAL avg(pg2dim_2.temp)\n                     Group Key: pg2dim_2.device\n                     ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_2\n                           Output: pg2dim_2.device, pg2dim_2.temp\n               ->  Partial HashAggregate\n                     Output: pg2dim_3.device, PARTIAL avg(pg2dim_3.temp)\n                     Group Key: pg2dim_3.device\n                     ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_3\n                           Output: pg2dim_3.device, pg2dim_3.temp\n\n-- All partition keys covered by GROUP BY (full partitionwise)\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM pg2dim\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: pg2dim.\"time\", pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: pg2dim.\"time\", pg2dim.device\n   ->  HashAggregate\n         Output: pg2dim.\"time\", pg2dim.device, avg(pg2dim.temp)\n         Group Key: pg2dim.\"time\", pg2dim.device\n         ->  Append\n               ->  Seq Scan on public.pg2dim_h1_t1 pg2dim_1\n                     Output: pg2dim_1.\"time\", pg2dim_1.device, pg2dim_1.temp\n               ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_2\n                     Output: pg2dim_2.\"time\", pg2dim_2.device, pg2dim_2.temp\n               ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_3\n                     Output: pg2dim_3.\"time\", pg2dim_3.device, pg2dim_3.temp\n               ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_4\n                     Output: pg2dim_4.\"time\", pg2dim_4.device, pg2dim_4.temp\n\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM pg2dim\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: pg2dim.\"time\", pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: pg2dim.\"time\", pg2dim.device\n   ->  Append\n         ->  HashAggregate\n               Output: pg2dim.\"time\", pg2dim.device, avg(pg2dim.temp)\n               Group Key: pg2dim.\"time\", pg2dim.device\n               ->  Seq Scan on public.pg2dim_h1_t1 pg2dim\n                     Output: pg2dim.\"time\", pg2dim.device, pg2dim.temp\n         ->  HashAggregate\n               Output: pg2dim_1.\"time\", pg2dim_1.device, avg(pg2dim_1.temp)\n               Group Key: pg2dim_1.\"time\", pg2dim_1.device\n               ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_1\n                     Output: pg2dim_1.\"time\", pg2dim_1.device, pg2dim_1.temp\n         ->  HashAggregate\n               Output: pg2dim_2.\"time\", pg2dim_2.device, avg(pg2dim_2.temp)\n               Group Key: pg2dim_2.\"time\", pg2dim_2.device\n               ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_2\n                     Output: pg2dim_2.\"time\", pg2dim_2.device, pg2dim_2.temp\n         ->  HashAggregate\n               Output: pg2dim_3.\"time\", pg2dim_3.device, avg(pg2dim_3.temp)\n               Group Key: pg2dim_3.\"time\", pg2dim_3.device\n               ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_3\n                     Output: pg2dim_3.\"time\", pg2dim_3.device, pg2dim_3.temp\n\n-- All partition keys not covered by GROUP BY because of date_trunc\n-- expression on time (partial partitionwise)\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT date_trunc('month', time), device, avg(temp)\nFROM pg2dim\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device\n   ->  HashAggregate\n         Output: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device, avg(pg2dim.temp)\n         Group Key: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device\n         ->  Append\n               ->  Seq Scan on public.pg2dim_h1_t1 pg2dim_1\n                     Output: date_trunc('month'::text, pg2dim_1.\"time\"), pg2dim_1.device, pg2dim_1.temp\n               ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_2\n                     Output: date_trunc('month'::text, pg2dim_2.\"time\"), pg2dim_2.device, pg2dim_2.temp\n               ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_3\n                     Output: date_trunc('month'::text, pg2dim_3.\"time\"), pg2dim_3.device, pg2dim_3.temp\n               ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_4\n                     Output: date_trunc('month'::text, pg2dim_4.\"time\"), pg2dim_4.device, pg2dim_4.temp\n\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT date_trunc('month', time), device, avg(temp)\nFROM pg2dim\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device\n   ->  Finalize HashAggregate\n         Output: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device, avg(pg2dim.temp)\n         Group Key: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device\n         ->  Append\n               ->  Partial HashAggregate\n                     Output: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device, PARTIAL avg(pg2dim.temp)\n                     Group Key: date_trunc('month'::text, pg2dim.\"time\"), pg2dim.device\n                     ->  Seq Scan on public.pg2dim_h1_t1 pg2dim\n                           Output: date_trunc('month'::text, pg2dim.\"time\"), pg2dim.device, pg2dim.temp\n               ->  Partial HashAggregate\n                     Output: (date_trunc('month'::text, pg2dim_1.\"time\")), pg2dim_1.device, PARTIAL avg(pg2dim_1.temp)\n                     Group Key: date_trunc('month'::text, pg2dim_1.\"time\"), pg2dim_1.device\n                     ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_1\n                           Output: date_trunc('month'::text, pg2dim_1.\"time\"), pg2dim_1.device, pg2dim_1.temp\n               ->  Partial HashAggregate\n                     Output: (date_trunc('month'::text, pg2dim_2.\"time\")), pg2dim_2.device, PARTIAL avg(pg2dim_2.temp)\n                     Group Key: date_trunc('month'::text, pg2dim_2.\"time\"), pg2dim_2.device\n                     ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_2\n                           Output: date_trunc('month'::text, pg2dim_2.\"time\"), pg2dim_2.device, pg2dim_2.temp\n               ->  Partial HashAggregate\n                     Output: (date_trunc('month'::text, pg2dim_3.\"time\")), pg2dim_3.device, PARTIAL avg(pg2dim_3.temp)\n                     Group Key: date_trunc('month'::text, pg2dim_3.\"time\"), pg2dim_3.device\n                     ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_3\n                           Output: date_trunc('month'::text, pg2dim_3.\"time\"), pg2dim_3.device, pg2dim_3.temp\n\n-- Now run on hypertable\n-- All partition keys not covered by GROUP BY (partial partitionwise)\nSET timescaledb.enable_chunkwise_aggregation = 'off';\n:PREFIX\nSELECT device, avg(temp)\nFROM hyper\nGROUP BY 1\nORDER BY 1;\n--- QUERY PLAN ---\n Sort\n   Output: hyper.device, (avg(hyper.temp))\n   Sort Key: hyper.device\n   ->  HashAggregate\n         Output: hyper.device, avg(hyper.temp)\n         Group Key: hyper.device\n         ->  Append\n               ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                     Output: _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                     Output: _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                     Output: _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                     Output: _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\nSET timescaledb.enable_chunkwise_aggregation = 'on';\n:PREFIX\nSELECT device, avg(temp)\nFROM hyper\nGROUP BY 1\nORDER BY 1;\n--- QUERY PLAN ---\n Sort\n   Output: hyper.device, (avg(hyper.temp))\n   Sort Key: hyper.device\n   ->  HashAggregate\n         Output: hyper.device, avg(hyper.temp)\n         Group Key: hyper.device\n         ->  Append\n               ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                     Output: _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                     Output: _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                     Output: _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                     Output: _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\n-- All partition keys covered (full partitionwise)\nSET timescaledb.enable_chunkwise_aggregation = 'off';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: hyper.\"time\", hyper.device, (avg(hyper.temp))\n   Sort Key: hyper.\"time\", hyper.device\n   ->  HashAggregate\n         Output: hyper.\"time\", hyper.device, avg(hyper.temp)\n         Group Key: hyper.\"time\", hyper.device\n         ->  Append\n               ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                     Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                     Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                     Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                     Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\nSET timescaledb.enable_chunkwise_aggregation = 'on';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: hyper.\"time\", hyper.device, (avg(hyper.temp))\n   Sort Key: hyper.\"time\", hyper.device\n   ->  HashAggregate\n         Output: hyper.\"time\", hyper.device, avg(hyper.temp)\n         Group Key: hyper.\"time\", hyper.device\n         ->  Append\n               ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                     Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                     Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                     Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                     Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\n-- Partial aggregation since date_trunc(time) is not a partition key\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT date_trunc('month', time), device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: (date_trunc('month'::text, hyper.\"time\")), hyper.device, (avg(hyper.temp))\n   Sort Key: (date_trunc('month'::text, hyper.\"time\")), hyper.device\n   ->  HashAggregate\n         Output: (date_trunc('month'::text, hyper.\"time\")), hyper.device, avg(hyper.temp)\n         Group Key: date_trunc('month'::text, hyper.\"time\"), hyper.device\n         ->  Result\n               Output: date_trunc('month'::text, hyper.\"time\"), hyper.device, hyper.temp\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                           Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                           Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                           Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                           Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\n-- Partial aggregation pushdown is currently not supported for this query by\n-- the TSDB pushdown code since a projection is used in the path.\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT date_trunc('month', time), device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: (date_trunc('month'::text, hyper.\"time\")), hyper.device, (avg(hyper.temp))\n   Sort Key: (date_trunc('month'::text, hyper.\"time\")), hyper.device\n   ->  HashAggregate\n         Output: (date_trunc('month'::text, hyper.\"time\")), hyper.device, avg(hyper.temp)\n         Group Key: date_trunc('month'::text, hyper.\"time\"), hyper.device\n         ->  Result\n               Output: date_trunc('month'::text, hyper.\"time\"), hyper.device, hyper.temp\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                           Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                           Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                           Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                           Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\n-- Also test time_bucket\nSET timescaledb.enable_chunkwise_aggregation = 'off';\n:PREFIX\nSELECT time_bucket('1 month', time), device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: (time_bucket('@ 1 mon'::interval, hyper.\"time\")), hyper.device, (avg(hyper.temp))\n   Sort Key: (time_bucket('@ 1 mon'::interval, hyper.\"time\")), hyper.device\n   ->  HashAggregate\n         Output: (time_bucket('@ 1 mon'::interval, hyper.\"time\")), hyper.device, avg(hyper.temp)\n         Group Key: time_bucket('@ 1 mon'::interval, hyper.\"time\"), hyper.device\n         ->  Result\n               Output: time_bucket('@ 1 mon'::interval, hyper.\"time\"), hyper.device, hyper.temp\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                           Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                           Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                           Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                           Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\nSET timescaledb.enable_chunkwise_aggregation = 'on';\n:PREFIX\nSELECT time_bucket('1 month', time), device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: (time_bucket('@ 1 mon'::interval, hyper.\"time\")), hyper.device, (avg(hyper.temp))\n   Sort Key: (time_bucket('@ 1 mon'::interval, hyper.\"time\")), hyper.device\n   ->  HashAggregate\n         Output: (time_bucket('@ 1 mon'::interval, hyper.\"time\")), hyper.device, avg(hyper.temp)\n         Group Key: time_bucket('@ 1 mon'::interval, hyper.\"time\"), hyper.device\n         ->  Result\n               Output: time_bucket('@ 1 mon'::interval, hyper.\"time\"), hyper.device, hyper.temp\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                           Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                           Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                           Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                           Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\n-- Test partitionwise joins, mostly to see that we do not break\n-- anything\nCREATE TABLE hyper_meta (time timestamptz, device int, info text);\nSELECT * FROM create_hypertable('hyper_meta', 'time', 'device', 2);\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             2 | public      | hyper_meta | t\n\nINSERT INTO hyper_meta VALUES\n       ('2018-02-19 13:01', 1, 'device_1'),\n       ('2018-02-19 13:02', 3, 'device_3');\nSET enable_partitionwise_join = 'off';\n:PREFIX\nSELECT h.time, h.device, h.temp, hm.info\nFROM hyper h, hyper_meta hm\nWHERE h.device = hm.device;\n--- QUERY PLAN ---\n Merge Join\n   Output: h.\"time\", h.device, h.temp, hm.info\n   Merge Cond: (hm.device = h.device)\n   ->  Merge Append\n         Sort Key: hm.device\n         ->  Index Scan using _hyper_2_5_chunk_hyper_meta_device_time_idx on _timescaledb_internal._hyper_2_5_chunk hm_1\n               Output: hm_1.info, hm_1.device\n         ->  Index Scan using _hyper_2_6_chunk_hyper_meta_device_time_idx on _timescaledb_internal._hyper_2_6_chunk hm_2\n               Output: hm_2.info, hm_2.device\n   ->  Materialize\n         Output: h.\"time\", h.device, h.temp\n         ->  Merge Append\n               Sort Key: h.device\n               ->  Index Scan using _hyper_1_1_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_1_chunk h_1\n                     Output: h_1.\"time\", h_1.device, h_1.temp\n               ->  Index Scan using _hyper_1_2_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_2_chunk h_2\n                     Output: h_2.\"time\", h_2.device, h_2.temp\n               ->  Index Scan using _hyper_1_3_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_3_chunk h_3\n                     Output: h_3.\"time\", h_3.device, h_3.temp\n               ->  Index Scan using _hyper_1_4_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_4_chunk h_4\n                     Output: h_4.\"time\", h_4.device, h_4.temp\n\n:PREFIX\nSELECT pg2.time, pg2.device, pg2.temp, pg1.temp\nFROM pg2dim pg2, pg1dim pg1\nWHERE pg2.device = pg1.device;\n--- QUERY PLAN ---\n Merge Join\n   Output: pg2.\"time\", pg2.device, pg2.temp, pg1.temp\n   Merge Cond: (pg1.device = pg2.device)\n   ->  Sort\n         Output: pg1.temp, pg1.device\n         Sort Key: pg1.device\n         ->  Append\n               ->  Seq Scan on public.pg1dim_h1 pg1_1\n                     Output: pg1_1.temp, pg1_1.device\n               ->  Seq Scan on public.pg1dim_h2 pg1_2\n                     Output: pg1_2.temp, pg1_2.device\n   ->  Sort\n         Output: pg2.\"time\", pg2.device, pg2.temp\n         Sort Key: pg2.device\n         ->  Append\n               ->  Seq Scan on public.pg2dim_h1_t1 pg2_1\n                     Output: pg2_1.\"time\", pg2_1.device, pg2_1.temp\n               ->  Seq Scan on public.pg2dim_h1_t2 pg2_2\n                     Output: pg2_2.\"time\", pg2_2.device, pg2_2.temp\n               ->  Seq Scan on public.pg2dim_h2_t1 pg2_3\n                     Output: pg2_3.\"time\", pg2_3.device, pg2_3.temp\n               ->  Seq Scan on public.pg2dim_h2_t2 pg2_4\n                     Output: pg2_4.\"time\", pg2_4.device, pg2_4.temp\n\nSET enable_partitionwise_join = 'on';\n:PREFIX\nSELECT h.time, h.device, h.temp, hm.info\nFROM hyper h, hyper_meta hm\nWHERE h.device = hm.device;\n--- QUERY PLAN ---\n Merge Join\n   Output: h.\"time\", h.device, h.temp, hm.info\n   Merge Cond: (hm.device = h.device)\n   ->  Merge Append\n         Sort Key: hm.device\n         ->  Index Scan using _hyper_2_5_chunk_hyper_meta_device_time_idx on _timescaledb_internal._hyper_2_5_chunk hm_1\n               Output: hm_1.info, hm_1.device\n         ->  Index Scan using _hyper_2_6_chunk_hyper_meta_device_time_idx on _timescaledb_internal._hyper_2_6_chunk hm_2\n               Output: hm_2.info, hm_2.device\n   ->  Materialize\n         Output: h.\"time\", h.device, h.temp\n         ->  Merge Append\n               Sort Key: h.device\n               ->  Index Scan using _hyper_1_1_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_1_chunk h_1\n                     Output: h_1.\"time\", h_1.device, h_1.temp\n               ->  Index Scan using _hyper_1_2_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_2_chunk h_2\n                     Output: h_2.\"time\", h_2.device, h_2.temp\n               ->  Index Scan using _hyper_1_3_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_3_chunk h_3\n                     Output: h_3.\"time\", h_3.device, h_3.temp\n               ->  Index Scan using _hyper_1_4_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_4_chunk h_4\n                     Output: h_4.\"time\", h_4.device, h_4.temp\n\n:PREFIX\nSELECT pg2.time, pg2.device, pg2.temp, pg1.temp\nFROM pg2dim pg2, pg1dim pg1\nWHERE pg2.device = pg1.device;\n--- QUERY PLAN ---\n Append\n   ->  Merge Join\n         Output: pg2_2.\"time\", pg2_2.device, pg2_2.temp, pg1_1.temp\n         Merge Cond: (pg1_1.device = pg2_2.device)\n         ->  Sort\n               Output: pg1_1.temp, pg1_1.device\n               Sort Key: pg1_1.device\n               ->  Seq Scan on public.pg1dim_h1 pg1_1\n                     Output: pg1_1.temp, pg1_1.device\n         ->  Sort\n               Output: pg2_2.\"time\", pg2_2.device, pg2_2.temp\n               Sort Key: pg2_2.device\n               ->  Append\n                     ->  Seq Scan on public.pg2dim_h1_t1 pg2_2\n                           Output: pg2_2.\"time\", pg2_2.device, pg2_2.temp\n                     ->  Seq Scan on public.pg2dim_h1_t2 pg2_3\n                           Output: pg2_3.\"time\", pg2_3.device, pg2_3.temp\n   ->  Merge Join\n         Output: pg2_5.\"time\", pg2_5.device, pg2_5.temp, pg1_2.temp\n         Merge Cond: (pg1_2.device = pg2_5.device)\n         ->  Sort\n               Output: pg1_2.temp, pg1_2.device\n               Sort Key: pg1_2.device\n               ->  Seq Scan on public.pg1dim_h2 pg1_2\n                     Output: pg1_2.temp, pg1_2.device\n         ->  Sort\n               Output: pg2_5.\"time\", pg2_5.device, pg2_5.temp\n               Sort Key: pg2_5.device\n               ->  Append\n                     ->  Seq Scan on public.pg2dim_h2_t1 pg2_5\n                           Output: pg2_5.\"time\", pg2_5.device, pg2_5.temp\n                     ->  Seq Scan on public.pg2dim_h2_t2 pg2_6\n                           Output: pg2_6.\"time\", pg2_6.device, pg2_6.temp\n\n-- Test hypertable with time partitioning function\nCREATE OR REPLACE FUNCTION time_func(unixtime float8)\n    RETURNS TIMESTAMPTZ LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nDECLARE\n    retval TIMESTAMPTZ;\nBEGIN\n    retval := to_timestamp(unixtime);\n    RETURN retval;\nEND\n$BODY$;\nCREATE TABLE hyper_timepart (time float8, device int, temp float);\nSELECT * FROM create_hypertable('hyper_timepart', 'time', 'device', 2, time_partitioning_func => 'time_func');\n hypertable_id | schema_name |   table_name   | created \n---------------+-------------+----------------+---------\n             3 | public      | hyper_timepart | t\n\n-- Planner won't pick push-down aggs on table with time function\n-- unless a certain amount of data\nSELECT setseed(1);\n setseed \n---------\n \n\nINSERT INTO hyper_timepart\nSELECT x, ceil(random() * 8), random() * 20\nFROM generate_series(0,5000-1) AS x;\n-- All partition keys covered (full partitionwise)\nSET timescaledb.enable_chunkwise_aggregation = 'off';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n--- QUERY PLAN ---\n Limit\n   Output: hyper_timepart.\"time\", hyper_timepart.device, (avg(hyper_timepart.temp))\n   ->  Sort\n         Output: hyper_timepart.\"time\", hyper_timepart.device, (avg(hyper_timepart.temp))\n         Sort Key: hyper_timepart.\"time\", hyper_timepart.device\n         ->  HashAggregate\n               Output: hyper_timepart.\"time\", hyper_timepart.device, avg(hyper_timepart.temp)\n               Group Key: hyper_timepart.\"time\", hyper_timepart.device\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_3_7_chunk\n                           Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_3_8_chunk\n                           Output: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp\n\n:PREFIX\nSELECT time_func(time), device, avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n--- QUERY PLAN ---\n Limit\n   Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, (avg(hyper_timepart.temp))\n   ->  GroupAggregate\n         Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, avg(hyper_timepart.temp)\n         Group Key: (time_func(hyper_timepart.\"time\")), hyper_timepart.device\n         ->  Incremental Sort\n               Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, hyper_timepart.temp\n               Sort Key: (time_func(hyper_timepart.\"time\")), hyper_timepart.device\n               Presorted Key: (time_func(hyper_timepart.\"time\"))\n               ->  Result\n                     Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, hyper_timepart.temp\n                     ->  Merge Append\n                           Sort Key: (time_func(hyper_timepart.\"time\"))\n                           ->  Index Scan Backward using _hyper_3_7_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_7_chunk\n                                 Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp, time_func(_hyper_3_7_chunk.\"time\")\n                           ->  Index Scan Backward using _hyper_3_8_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_8_chunk\n                                 Output: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp, time_func(_hyper_3_8_chunk.\"time\")\n\n-- Grouping on original time column should be pushed-down\nSET timescaledb.enable_chunkwise_aggregation = 'on';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n--- QUERY PLAN ---\n Limit\n   Output: hyper_timepart.\"time\", hyper_timepart.device, (avg(hyper_timepart.temp))\n   ->  Sort\n         Output: hyper_timepart.\"time\", hyper_timepart.device, (avg(hyper_timepart.temp))\n         Sort Key: hyper_timepart.\"time\", hyper_timepart.device\n         ->  HashAggregate\n               Output: hyper_timepart.\"time\", hyper_timepart.device, avg(hyper_timepart.temp)\n               Group Key: hyper_timepart.\"time\", hyper_timepart.device\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_3_7_chunk\n                           Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_3_8_chunk\n                           Output: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp\n\n-- Applying the time partitioning function should also allow push-down\n-- on open dimensions\n:PREFIX\nSELECT time_func(time), device, avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n--- QUERY PLAN ---\n Limit\n   Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, (avg(hyper_timepart.temp))\n   ->  GroupAggregate\n         Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, avg(hyper_timepart.temp)\n         Group Key: (time_func(hyper_timepart.\"time\")), hyper_timepart.device\n         ->  Incremental Sort\n               Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, hyper_timepart.temp\n               Sort Key: (time_func(hyper_timepart.\"time\")), hyper_timepart.device\n               Presorted Key: (time_func(hyper_timepart.\"time\"))\n               ->  Result\n                     Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, hyper_timepart.temp\n                     ->  Merge Append\n                           Sort Key: (time_func(hyper_timepart.\"time\"))\n                           ->  Index Scan Backward using _hyper_3_7_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_7_chunk\n                                 Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp, time_func(_hyper_3_7_chunk.\"time\")\n                           ->  Index Scan Backward using _hyper_3_8_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_8_chunk\n                                 Output: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp, time_func(_hyper_3_8_chunk.\"time\")\n\n-- Partial aggregation pushdown is currently not supported for this query by\n-- the TSDB pushdown code since a projection is used in the path.\n:PREFIX\nSELECT time_func(time), _timescaledb_functions.get_partition_hash(device), avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n--- QUERY PLAN ---\n Limit\n   Output: (time_func(hyper_timepart.\"time\")), (_timescaledb_functions.get_partition_hash(hyper_timepart.device)), (avg(hyper_timepart.temp))\n   ->  GroupAggregate\n         Output: (time_func(hyper_timepart.\"time\")), (_timescaledb_functions.get_partition_hash(hyper_timepart.device)), avg(hyper_timepart.temp)\n         Group Key: (time_func(hyper_timepart.\"time\")), (_timescaledb_functions.get_partition_hash(hyper_timepart.device))\n         ->  Incremental Sort\n               Output: (time_func(hyper_timepart.\"time\")), (_timescaledb_functions.get_partition_hash(hyper_timepart.device)), hyper_timepart.temp\n               Sort Key: (time_func(hyper_timepart.\"time\")), (_timescaledb_functions.get_partition_hash(hyper_timepart.device))\n               Presorted Key: (time_func(hyper_timepart.\"time\"))\n               ->  Result\n                     Output: (time_func(hyper_timepart.\"time\")), _timescaledb_functions.get_partition_hash(hyper_timepart.device), hyper_timepart.temp\n                     ->  Merge Append\n                           Sort Key: (time_func(hyper_timepart.\"time\"))\n                           ->  Index Scan Backward using _hyper_3_7_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_7_chunk\n                                 Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp, time_func(_hyper_3_7_chunk.\"time\")\n                           ->  Index Scan Backward using _hyper_3_8_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_8_chunk\n                                 Output: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp, time_func(_hyper_3_8_chunk.\"time\")\n\n-- Test removal of redundant group key optimization in PG16\n-- All lower versions include the redundant key on device column\n:PREFIX\nSELECT device, avg(temp)\nFROM hyper_timepart\nWHERE device = 1\nGROUP BY 1\nLIMIT 10;\n--- QUERY PLAN ---\n Limit\n   Output: _hyper_3_8_chunk.device, (avg(_hyper_3_8_chunk.temp))\n   ->  GroupAggregate\n         Output: _hyper_3_8_chunk.device, avg(_hyper_3_8_chunk.temp)\n         Group Key: _hyper_3_8_chunk.device\n         ->  Index Scan using _hyper_3_8_chunk_hyper_timepart_device_expr_idx on _timescaledb_internal._hyper_3_8_chunk\n               Output: _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp\n               Index Cond: (_hyper_3_8_chunk.device = 1)\n\n"
  },
  {
    "path": "test/expected/partitionwise-16.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\set PREFIX 'EXPLAIN (VERBOSE, BUFFERS OFF, COSTS OFF)'\n-- Create a two dimensional hypertable\nCREATE TABLE hyper (time timestamptz, device int, temp float);\nSELECT * FROM create_hypertable('hyper', 'time', 'device', 2);\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             1 | public      | hyper      | t\n\n-- Create a similar PostgreSQL partitioned table\nCREATE TABLE pg2dim (time timestamptz, device int, temp float) PARTITION BY HASH (device);\nCREATE TABLE pg2dim_h1 PARTITION OF pg2dim FOR VALUES WITH (MODULUS 2, REMAINDER 0) PARTITION BY RANGE(time);\nCREATE TABLE pg2dim_h2 PARTITION OF pg2dim FOR VALUES WITH (MODULUS 2, REMAINDER 1) PARTITION BY RANGE(time);\nCREATE TABLE pg2dim_h1_t1 PARTITION OF pg2dim_h1 FOR VALUES FROM ('2018-01-01 00:00') TO ('2018-09-01 00:00');\nCREATE TABLE pg2dim_h1_t2 PARTITION OF pg2dim_h1 FOR VALUES FROM ('2018-09-01 00:00') TO ('2018-12-01 00:00');\nCREATE TABLE pg2dim_h2_t1 PARTITION OF pg2dim_h2 FOR VALUES FROM ('2018-01-01 00:00') TO ('2018-09-01 00:00');\nCREATE TABLE pg2dim_h2_t2 PARTITION OF pg2dim_h2 FOR VALUES FROM ('2018-09-01 00:00') TO ('2018-12-01 00:00');\n-- Create a 1-dimensional partitioned table for comparison\nCREATE TABLE pg1dim (time timestamptz, device int, temp float) PARTITION BY HASH (device);\nCREATE TABLE pg1dim_h1 PARTITION OF pg1dim FOR VALUES WITH (MODULUS 2, REMAINDER 0);\nCREATE TABLE pg1dim_h2 PARTITION OF pg1dim FOR VALUES WITH (MODULUS 2, REMAINDER 1);\nINSERT INTO hyper VALUES\n       ('2018-02-19 13:01', 1, 2.3),\n       ('2018-02-19 13:02', 3, 3.1),\n       ('2018-10-19 13:01', 1, 7.6),\n       ('2018-10-19 13:02', 3, 9.0);\nINSERT INTO pg2dim VALUES\n       ('2018-02-19 13:01', 1, 2.3),\n       ('2018-02-19 13:02', 3, 3.1),\n       ('2018-10-19 13:01', 1, 7.6),\n       ('2018-10-19 13:02', 3, 9.0);\nINSERT INTO pg1dim VALUES\n       ('2018-02-19 13:01', 1, 2.3),\n       ('2018-02-19 13:02', 3, 3.1),\n       ('2018-10-19 13:01', 1, 7.6),\n       ('2018-10-19 13:02', 3, 9.0);\nSELECT * FROM test.show_subtables('hyper');\n                 Child                  | Tablespace \n----------------------------------------+------------\n _timescaledb_internal._hyper_1_1_chunk | \n _timescaledb_internal._hyper_1_2_chunk | \n _timescaledb_internal._hyper_1_3_chunk | \n _timescaledb_internal._hyper_1_4_chunk | \n\nSELECT * FROM pg2dim_h1_t1;\n             time             | device | temp \n------------------------------+--------+------\n Mon Feb 19 13:01:00 2018 PST |      1 |  2.3\n\nSELECT * FROM pg2dim_h1_t2;\n             time             | device | temp \n------------------------------+--------+------\n Fri Oct 19 13:01:00 2018 PDT |      1 |  7.6\n\nSELECT * FROM pg2dim_h2_t1;\n             time             | device | temp \n------------------------------+--------+------\n Mon Feb 19 13:02:00 2018 PST |      3 |  3.1\n\nSELECT * FROM pg2dim_h2_t2;\n             time             | device | temp \n------------------------------+--------+------\n Fri Oct 19 13:02:00 2018 PDT |      3 |    9\n\n-- Compare partitionwise aggreate enabled/disabled. First run queries\n-- on PG partitioned tables for reference.\n-- All partition keys covered by GROUP BY\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT device, avg(temp)\nFROM pg1dim\nGROUP BY 1\nORDER BY 1;\n--- QUERY PLAN ---\n Sort\n   Output: pg1dim.device, (avg(pg1dim.temp))\n   Sort Key: pg1dim.device\n   ->  HashAggregate\n         Output: pg1dim.device, avg(pg1dim.temp)\n         Group Key: pg1dim.device\n         ->  Append\n               ->  Seq Scan on public.pg1dim_h1 pg1dim_1\n                     Output: pg1dim_1.device, pg1dim_1.temp\n               ->  Seq Scan on public.pg1dim_h2 pg1dim_2\n                     Output: pg1dim_2.device, pg1dim_2.temp\n\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT device, avg(temp)\nFROM pg1dim\nGROUP BY 1\nORDER BY 1;\n--- QUERY PLAN ---\n Sort\n   Output: pg1dim.device, (avg(pg1dim.temp))\n   Sort Key: pg1dim.device\n   ->  Append\n         ->  HashAggregate\n               Output: pg1dim.device, avg(pg1dim.temp)\n               Group Key: pg1dim.device\n               ->  Seq Scan on public.pg1dim_h1 pg1dim\n                     Output: pg1dim.device, pg1dim.temp\n         ->  HashAggregate\n               Output: pg1dim_1.device, avg(pg1dim_1.temp)\n               Group Key: pg1dim_1.device\n               ->  Seq Scan on public.pg1dim_h2 pg1dim_1\n                     Output: pg1dim_1.device, pg1dim_1.temp\n\n-- All partition keys not covered by GROUP BY (partial partitionwise)\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT device, avg(temp)\nFROM pg2dim\nGROUP BY 1\nORDER BY 1;\n--- QUERY PLAN ---\n Sort\n   Output: pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: pg2dim.device\n   ->  HashAggregate\n         Output: pg2dim.device, avg(pg2dim.temp)\n         Group Key: pg2dim.device\n         ->  Append\n               ->  Seq Scan on public.pg2dim_h1_t1 pg2dim_1\n                     Output: pg2dim_1.device, pg2dim_1.temp\n               ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_2\n                     Output: pg2dim_2.device, pg2dim_2.temp\n               ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_3\n                     Output: pg2dim_3.device, pg2dim_3.temp\n               ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_4\n                     Output: pg2dim_4.device, pg2dim_4.temp\n\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT device, avg(temp)\nFROM pg2dim\nGROUP BY 1\nORDER BY 1;\n--- QUERY PLAN ---\n Sort\n   Output: pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: pg2dim.device\n   ->  Finalize HashAggregate\n         Output: pg2dim.device, avg(pg2dim.temp)\n         Group Key: pg2dim.device\n         ->  Append\n               ->  Partial HashAggregate\n                     Output: pg2dim.device, PARTIAL avg(pg2dim.temp)\n                     Group Key: pg2dim.device\n                     ->  Seq Scan on public.pg2dim_h1_t1 pg2dim\n                           Output: pg2dim.device, pg2dim.temp\n               ->  Partial HashAggregate\n                     Output: pg2dim_1.device, PARTIAL avg(pg2dim_1.temp)\n                     Group Key: pg2dim_1.device\n                     ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_1\n                           Output: pg2dim_1.device, pg2dim_1.temp\n               ->  Partial HashAggregate\n                     Output: pg2dim_2.device, PARTIAL avg(pg2dim_2.temp)\n                     Group Key: pg2dim_2.device\n                     ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_2\n                           Output: pg2dim_2.device, pg2dim_2.temp\n               ->  Partial HashAggregate\n                     Output: pg2dim_3.device, PARTIAL avg(pg2dim_3.temp)\n                     Group Key: pg2dim_3.device\n                     ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_3\n                           Output: pg2dim_3.device, pg2dim_3.temp\n\n-- All partition keys covered by GROUP BY (full partitionwise)\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM pg2dim\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: pg2dim.\"time\", pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: pg2dim.\"time\", pg2dim.device\n   ->  HashAggregate\n         Output: pg2dim.\"time\", pg2dim.device, avg(pg2dim.temp)\n         Group Key: pg2dim.\"time\", pg2dim.device\n         ->  Append\n               ->  Seq Scan on public.pg2dim_h1_t1 pg2dim_1\n                     Output: pg2dim_1.\"time\", pg2dim_1.device, pg2dim_1.temp\n               ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_2\n                     Output: pg2dim_2.\"time\", pg2dim_2.device, pg2dim_2.temp\n               ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_3\n                     Output: pg2dim_3.\"time\", pg2dim_3.device, pg2dim_3.temp\n               ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_4\n                     Output: pg2dim_4.\"time\", pg2dim_4.device, pg2dim_4.temp\n\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM pg2dim\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: pg2dim.\"time\", pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: pg2dim.\"time\", pg2dim.device\n   ->  Append\n         ->  HashAggregate\n               Output: pg2dim.\"time\", pg2dim.device, avg(pg2dim.temp)\n               Group Key: pg2dim.\"time\", pg2dim.device\n               ->  Seq Scan on public.pg2dim_h1_t1 pg2dim\n                     Output: pg2dim.\"time\", pg2dim.device, pg2dim.temp\n         ->  HashAggregate\n               Output: pg2dim_1.\"time\", pg2dim_1.device, avg(pg2dim_1.temp)\n               Group Key: pg2dim_1.\"time\", pg2dim_1.device\n               ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_1\n                     Output: pg2dim_1.\"time\", pg2dim_1.device, pg2dim_1.temp\n         ->  HashAggregate\n               Output: pg2dim_2.\"time\", pg2dim_2.device, avg(pg2dim_2.temp)\n               Group Key: pg2dim_2.\"time\", pg2dim_2.device\n               ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_2\n                     Output: pg2dim_2.\"time\", pg2dim_2.device, pg2dim_2.temp\n         ->  HashAggregate\n               Output: pg2dim_3.\"time\", pg2dim_3.device, avg(pg2dim_3.temp)\n               Group Key: pg2dim_3.\"time\", pg2dim_3.device\n               ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_3\n                     Output: pg2dim_3.\"time\", pg2dim_3.device, pg2dim_3.temp\n\n-- All partition keys not covered by GROUP BY because of date_trunc\n-- expression on time (partial partitionwise)\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT date_trunc('month', time), device, avg(temp)\nFROM pg2dim\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device\n   ->  HashAggregate\n         Output: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device, avg(pg2dim.temp)\n         Group Key: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device\n         ->  Append\n               ->  Seq Scan on public.pg2dim_h1_t1 pg2dim_1\n                     Output: date_trunc('month'::text, pg2dim_1.\"time\"), pg2dim_1.device, pg2dim_1.temp\n               ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_2\n                     Output: date_trunc('month'::text, pg2dim_2.\"time\"), pg2dim_2.device, pg2dim_2.temp\n               ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_3\n                     Output: date_trunc('month'::text, pg2dim_3.\"time\"), pg2dim_3.device, pg2dim_3.temp\n               ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_4\n                     Output: date_trunc('month'::text, pg2dim_4.\"time\"), pg2dim_4.device, pg2dim_4.temp\n\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT date_trunc('month', time), device, avg(temp)\nFROM pg2dim\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device\n   ->  Finalize HashAggregate\n         Output: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device, avg(pg2dim.temp)\n         Group Key: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device\n         ->  Append\n               ->  Partial HashAggregate\n                     Output: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device, PARTIAL avg(pg2dim.temp)\n                     Group Key: date_trunc('month'::text, pg2dim.\"time\"), pg2dim.device\n                     ->  Seq Scan on public.pg2dim_h1_t1 pg2dim\n                           Output: date_trunc('month'::text, pg2dim.\"time\"), pg2dim.device, pg2dim.temp\n               ->  Partial HashAggregate\n                     Output: (date_trunc('month'::text, pg2dim_1.\"time\")), pg2dim_1.device, PARTIAL avg(pg2dim_1.temp)\n                     Group Key: date_trunc('month'::text, pg2dim_1.\"time\"), pg2dim_1.device\n                     ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_1\n                           Output: date_trunc('month'::text, pg2dim_1.\"time\"), pg2dim_1.device, pg2dim_1.temp\n               ->  Partial HashAggregate\n                     Output: (date_trunc('month'::text, pg2dim_2.\"time\")), pg2dim_2.device, PARTIAL avg(pg2dim_2.temp)\n                     Group Key: date_trunc('month'::text, pg2dim_2.\"time\"), pg2dim_2.device\n                     ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_2\n                           Output: date_trunc('month'::text, pg2dim_2.\"time\"), pg2dim_2.device, pg2dim_2.temp\n               ->  Partial HashAggregate\n                     Output: (date_trunc('month'::text, pg2dim_3.\"time\")), pg2dim_3.device, PARTIAL avg(pg2dim_3.temp)\n                     Group Key: date_trunc('month'::text, pg2dim_3.\"time\"), pg2dim_3.device\n                     ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_3\n                           Output: date_trunc('month'::text, pg2dim_3.\"time\"), pg2dim_3.device, pg2dim_3.temp\n\n-- Now run on hypertable\n-- All partition keys not covered by GROUP BY (partial partitionwise)\nSET timescaledb.enable_chunkwise_aggregation = 'off';\n:PREFIX\nSELECT device, avg(temp)\nFROM hyper\nGROUP BY 1\nORDER BY 1;\n--- QUERY PLAN ---\n Sort\n   Output: hyper.device, (avg(hyper.temp))\n   Sort Key: hyper.device\n   ->  HashAggregate\n         Output: hyper.device, avg(hyper.temp)\n         Group Key: hyper.device\n         ->  Append\n               ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                     Output: _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                     Output: _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                     Output: _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                     Output: _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\nSET timescaledb.enable_chunkwise_aggregation = 'on';\n:PREFIX\nSELECT device, avg(temp)\nFROM hyper\nGROUP BY 1\nORDER BY 1;\n--- QUERY PLAN ---\n Sort\n   Output: hyper.device, (avg(hyper.temp))\n   Sort Key: hyper.device\n   ->  HashAggregate\n         Output: hyper.device, avg(hyper.temp)\n         Group Key: hyper.device\n         ->  Append\n               ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                     Output: _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                     Output: _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                     Output: _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                     Output: _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\n-- All partition keys covered (full partitionwise)\nSET timescaledb.enable_chunkwise_aggregation = 'off';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: hyper.\"time\", hyper.device, (avg(hyper.temp))\n   Sort Key: hyper.\"time\", hyper.device\n   ->  HashAggregate\n         Output: hyper.\"time\", hyper.device, avg(hyper.temp)\n         Group Key: hyper.\"time\", hyper.device\n         ->  Append\n               ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                     Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                     Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                     Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                     Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\nSET timescaledb.enable_chunkwise_aggregation = 'on';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: hyper.\"time\", hyper.device, (avg(hyper.temp))\n   Sort Key: hyper.\"time\", hyper.device\n   ->  HashAggregate\n         Output: hyper.\"time\", hyper.device, avg(hyper.temp)\n         Group Key: hyper.\"time\", hyper.device\n         ->  Append\n               ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                     Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                     Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                     Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                     Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\n-- Partial aggregation since date_trunc(time) is not a partition key\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT date_trunc('month', time), device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: (date_trunc('month'::text, hyper.\"time\")), hyper.device, (avg(hyper.temp))\n   Sort Key: (date_trunc('month'::text, hyper.\"time\")), hyper.device\n   ->  HashAggregate\n         Output: (date_trunc('month'::text, hyper.\"time\")), hyper.device, avg(hyper.temp)\n         Group Key: date_trunc('month'::text, hyper.\"time\"), hyper.device\n         ->  Result\n               Output: date_trunc('month'::text, hyper.\"time\"), hyper.device, hyper.temp\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                           Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                           Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                           Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                           Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\n-- Partial aggregation pushdown is currently not supported for this query by\n-- the TSDB pushdown code since a projection is used in the path.\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT date_trunc('month', time), device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: (date_trunc('month'::text, hyper.\"time\")), hyper.device, (avg(hyper.temp))\n   Sort Key: (date_trunc('month'::text, hyper.\"time\")), hyper.device\n   ->  HashAggregate\n         Output: (date_trunc('month'::text, hyper.\"time\")), hyper.device, avg(hyper.temp)\n         Group Key: date_trunc('month'::text, hyper.\"time\"), hyper.device\n         ->  Result\n               Output: date_trunc('month'::text, hyper.\"time\"), hyper.device, hyper.temp\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                           Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                           Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                           Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                           Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\n-- Also test time_bucket\nSET timescaledb.enable_chunkwise_aggregation = 'off';\n:PREFIX\nSELECT time_bucket('1 month', time), device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: (time_bucket('@ 1 mon'::interval, hyper.\"time\")), hyper.device, (avg(hyper.temp))\n   Sort Key: (time_bucket('@ 1 mon'::interval, hyper.\"time\")), hyper.device\n   ->  HashAggregate\n         Output: (time_bucket('@ 1 mon'::interval, hyper.\"time\")), hyper.device, avg(hyper.temp)\n         Group Key: time_bucket('@ 1 mon'::interval, hyper.\"time\"), hyper.device\n         ->  Result\n               Output: time_bucket('@ 1 mon'::interval, hyper.\"time\"), hyper.device, hyper.temp\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                           Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                           Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                           Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                           Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\nSET timescaledb.enable_chunkwise_aggregation = 'on';\n:PREFIX\nSELECT time_bucket('1 month', time), device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: (time_bucket('@ 1 mon'::interval, hyper.\"time\")), hyper.device, (avg(hyper.temp))\n   Sort Key: (time_bucket('@ 1 mon'::interval, hyper.\"time\")), hyper.device\n   ->  HashAggregate\n         Output: (time_bucket('@ 1 mon'::interval, hyper.\"time\")), hyper.device, avg(hyper.temp)\n         Group Key: time_bucket('@ 1 mon'::interval, hyper.\"time\"), hyper.device\n         ->  Result\n               Output: time_bucket('@ 1 mon'::interval, hyper.\"time\"), hyper.device, hyper.temp\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                           Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                           Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                           Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                           Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\n-- Test partitionwise joins, mostly to see that we do not break\n-- anything\nCREATE TABLE hyper_meta (time timestamptz, device int, info text);\nSELECT * FROM create_hypertable('hyper_meta', 'time', 'device', 2);\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             2 | public      | hyper_meta | t\n\nINSERT INTO hyper_meta VALUES\n       ('2018-02-19 13:01', 1, 'device_1'),\n       ('2018-02-19 13:02', 3, 'device_3');\nSET enable_partitionwise_join = 'off';\n:PREFIX\nSELECT h.time, h.device, h.temp, hm.info\nFROM hyper h, hyper_meta hm\nWHERE h.device = hm.device;\n--- QUERY PLAN ---\n Merge Join\n   Output: h.\"time\", h.device, h.temp, hm.info\n   Merge Cond: (hm.device = h.device)\n   ->  Merge Append\n         Sort Key: hm.device\n         ->  Index Scan using _hyper_2_5_chunk_hyper_meta_device_time_idx on _timescaledb_internal._hyper_2_5_chunk hm_1\n               Output: hm_1.info, hm_1.device\n         ->  Index Scan using _hyper_2_6_chunk_hyper_meta_device_time_idx on _timescaledb_internal._hyper_2_6_chunk hm_2\n               Output: hm_2.info, hm_2.device\n   ->  Materialize\n         Output: h.\"time\", h.device, h.temp\n         ->  Merge Append\n               Sort Key: h.device\n               ->  Index Scan using _hyper_1_1_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_1_chunk h_1\n                     Output: h_1.\"time\", h_1.device, h_1.temp\n               ->  Index Scan using _hyper_1_2_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_2_chunk h_2\n                     Output: h_2.\"time\", h_2.device, h_2.temp\n               ->  Index Scan using _hyper_1_3_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_3_chunk h_3\n                     Output: h_3.\"time\", h_3.device, h_3.temp\n               ->  Index Scan using _hyper_1_4_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_4_chunk h_4\n                     Output: h_4.\"time\", h_4.device, h_4.temp\n\n:PREFIX\nSELECT pg2.time, pg2.device, pg2.temp, pg1.temp\nFROM pg2dim pg2, pg1dim pg1\nWHERE pg2.device = pg1.device;\n--- QUERY PLAN ---\n Merge Join\n   Output: pg2.\"time\", pg2.device, pg2.temp, pg1.temp\n   Merge Cond: (pg1.device = pg2.device)\n   ->  Sort\n         Output: pg1.temp, pg1.device\n         Sort Key: pg1.device\n         ->  Append\n               ->  Seq Scan on public.pg1dim_h1 pg1_1\n                     Output: pg1_1.temp, pg1_1.device\n               ->  Seq Scan on public.pg1dim_h2 pg1_2\n                     Output: pg1_2.temp, pg1_2.device\n   ->  Sort\n         Output: pg2.\"time\", pg2.device, pg2.temp\n         Sort Key: pg2.device\n         ->  Append\n               ->  Seq Scan on public.pg2dim_h1_t1 pg2_1\n                     Output: pg2_1.\"time\", pg2_1.device, pg2_1.temp\n               ->  Seq Scan on public.pg2dim_h1_t2 pg2_2\n                     Output: pg2_2.\"time\", pg2_2.device, pg2_2.temp\n               ->  Seq Scan on public.pg2dim_h2_t1 pg2_3\n                     Output: pg2_3.\"time\", pg2_3.device, pg2_3.temp\n               ->  Seq Scan on public.pg2dim_h2_t2 pg2_4\n                     Output: pg2_4.\"time\", pg2_4.device, pg2_4.temp\n\nSET enable_partitionwise_join = 'on';\n:PREFIX\nSELECT h.time, h.device, h.temp, hm.info\nFROM hyper h, hyper_meta hm\nWHERE h.device = hm.device;\n--- QUERY PLAN ---\n Merge Join\n   Output: h.\"time\", h.device, h.temp, hm.info\n   Merge Cond: (hm.device = h.device)\n   ->  Merge Append\n         Sort Key: hm.device\n         ->  Index Scan using _hyper_2_5_chunk_hyper_meta_device_time_idx on _timescaledb_internal._hyper_2_5_chunk hm_1\n               Output: hm_1.info, hm_1.device\n         ->  Index Scan using _hyper_2_6_chunk_hyper_meta_device_time_idx on _timescaledb_internal._hyper_2_6_chunk hm_2\n               Output: hm_2.info, hm_2.device\n   ->  Materialize\n         Output: h.\"time\", h.device, h.temp\n         ->  Merge Append\n               Sort Key: h.device\n               ->  Index Scan using _hyper_1_1_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_1_chunk h_1\n                     Output: h_1.\"time\", h_1.device, h_1.temp\n               ->  Index Scan using _hyper_1_2_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_2_chunk h_2\n                     Output: h_2.\"time\", h_2.device, h_2.temp\n               ->  Index Scan using _hyper_1_3_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_3_chunk h_3\n                     Output: h_3.\"time\", h_3.device, h_3.temp\n               ->  Index Scan using _hyper_1_4_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_4_chunk h_4\n                     Output: h_4.\"time\", h_4.device, h_4.temp\n\n:PREFIX\nSELECT pg2.time, pg2.device, pg2.temp, pg1.temp\nFROM pg2dim pg2, pg1dim pg1\nWHERE pg2.device = pg1.device;\n--- QUERY PLAN ---\n Append\n   ->  Merge Join\n         Output: pg2_2.\"time\", pg2_2.device, pg2_2.temp, pg1_1.temp\n         Merge Cond: (pg1_1.device = pg2_2.device)\n         ->  Sort\n               Output: pg1_1.temp, pg1_1.device\n               Sort Key: pg1_1.device\n               ->  Seq Scan on public.pg1dim_h1 pg1_1\n                     Output: pg1_1.temp, pg1_1.device\n         ->  Sort\n               Output: pg2_2.\"time\", pg2_2.device, pg2_2.temp\n               Sort Key: pg2_2.device\n               ->  Append\n                     ->  Seq Scan on public.pg2dim_h1_t1 pg2_2\n                           Output: pg2_2.\"time\", pg2_2.device, pg2_2.temp\n                     ->  Seq Scan on public.pg2dim_h1_t2 pg2_3\n                           Output: pg2_3.\"time\", pg2_3.device, pg2_3.temp\n   ->  Merge Join\n         Output: pg2_5.\"time\", pg2_5.device, pg2_5.temp, pg1_2.temp\n         Merge Cond: (pg1_2.device = pg2_5.device)\n         ->  Sort\n               Output: pg1_2.temp, pg1_2.device\n               Sort Key: pg1_2.device\n               ->  Seq Scan on public.pg1dim_h2 pg1_2\n                     Output: pg1_2.temp, pg1_2.device\n         ->  Sort\n               Output: pg2_5.\"time\", pg2_5.device, pg2_5.temp\n               Sort Key: pg2_5.device\n               ->  Append\n                     ->  Seq Scan on public.pg2dim_h2_t1 pg2_5\n                           Output: pg2_5.\"time\", pg2_5.device, pg2_5.temp\n                     ->  Seq Scan on public.pg2dim_h2_t2 pg2_6\n                           Output: pg2_6.\"time\", pg2_6.device, pg2_6.temp\n\n-- Test hypertable with time partitioning function\nCREATE OR REPLACE FUNCTION time_func(unixtime float8)\n    RETURNS TIMESTAMPTZ LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nDECLARE\n    retval TIMESTAMPTZ;\nBEGIN\n    retval := to_timestamp(unixtime);\n    RETURN retval;\nEND\n$BODY$;\nCREATE TABLE hyper_timepart (time float8, device int, temp float);\nSELECT * FROM create_hypertable('hyper_timepart', 'time', 'device', 2, time_partitioning_func => 'time_func');\n hypertable_id | schema_name |   table_name   | created \n---------------+-------------+----------------+---------\n             3 | public      | hyper_timepart | t\n\n-- Planner won't pick push-down aggs on table with time function\n-- unless a certain amount of data\nSELECT setseed(1);\n setseed \n---------\n \n\nINSERT INTO hyper_timepart\nSELECT x, ceil(random() * 8), random() * 20\nFROM generate_series(0,5000-1) AS x;\n-- All partition keys covered (full partitionwise)\nSET timescaledb.enable_chunkwise_aggregation = 'off';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n--- QUERY PLAN ---\n Limit\n   Output: hyper_timepart.\"time\", hyper_timepart.device, (avg(hyper_timepart.temp))\n   ->  Sort\n         Output: hyper_timepart.\"time\", hyper_timepart.device, (avg(hyper_timepart.temp))\n         Sort Key: hyper_timepart.\"time\", hyper_timepart.device\n         ->  HashAggregate\n               Output: hyper_timepart.\"time\", hyper_timepart.device, avg(hyper_timepart.temp)\n               Group Key: hyper_timepart.\"time\", hyper_timepart.device\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_3_7_chunk\n                           Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_3_8_chunk\n                           Output: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp\n\n:PREFIX\nSELECT time_func(time), device, avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n--- QUERY PLAN ---\n Limit\n   Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, (avg(hyper_timepart.temp))\n   ->  GroupAggregate\n         Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, avg(hyper_timepart.temp)\n         Group Key: (time_func(hyper_timepart.\"time\")), hyper_timepart.device\n         ->  Incremental Sort\n               Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, hyper_timepart.temp\n               Sort Key: (time_func(hyper_timepart.\"time\")), hyper_timepart.device\n               Presorted Key: (time_func(hyper_timepart.\"time\"))\n               ->  Result\n                     Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, hyper_timepart.temp\n                     ->  Merge Append\n                           Sort Key: (time_func(hyper_timepart.\"time\"))\n                           ->  Index Scan Backward using _hyper_3_7_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_7_chunk\n                                 Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp, time_func(_hyper_3_7_chunk.\"time\")\n                           ->  Index Scan Backward using _hyper_3_8_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_8_chunk\n                                 Output: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp, time_func(_hyper_3_8_chunk.\"time\")\n\n-- Grouping on original time column should be pushed-down\nSET timescaledb.enable_chunkwise_aggregation = 'on';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n--- QUERY PLAN ---\n Limit\n   Output: hyper_timepart.\"time\", hyper_timepart.device, (avg(hyper_timepart.temp))\n   ->  Sort\n         Output: hyper_timepart.\"time\", hyper_timepart.device, (avg(hyper_timepart.temp))\n         Sort Key: hyper_timepart.\"time\", hyper_timepart.device\n         ->  HashAggregate\n               Output: hyper_timepart.\"time\", hyper_timepart.device, avg(hyper_timepart.temp)\n               Group Key: hyper_timepart.\"time\", hyper_timepart.device\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_3_7_chunk\n                           Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_3_8_chunk\n                           Output: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp\n\n-- Applying the time partitioning function should also allow push-down\n-- on open dimensions\n:PREFIX\nSELECT time_func(time), device, avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n--- QUERY PLAN ---\n Limit\n   Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, (avg(hyper_timepart.temp))\n   ->  GroupAggregate\n         Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, avg(hyper_timepart.temp)\n         Group Key: (time_func(hyper_timepart.\"time\")), hyper_timepart.device\n         ->  Incremental Sort\n               Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, hyper_timepart.temp\n               Sort Key: (time_func(hyper_timepart.\"time\")), hyper_timepart.device\n               Presorted Key: (time_func(hyper_timepart.\"time\"))\n               ->  Result\n                     Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, hyper_timepart.temp\n                     ->  Merge Append\n                           Sort Key: (time_func(hyper_timepart.\"time\"))\n                           ->  Index Scan Backward using _hyper_3_7_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_7_chunk\n                                 Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp, time_func(_hyper_3_7_chunk.\"time\")\n                           ->  Index Scan Backward using _hyper_3_8_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_8_chunk\n                                 Output: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp, time_func(_hyper_3_8_chunk.\"time\")\n\n-- Partial aggregation pushdown is currently not supported for this query by\n-- the TSDB pushdown code since a projection is used in the path.\n:PREFIX\nSELECT time_func(time), _timescaledb_functions.get_partition_hash(device), avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n--- QUERY PLAN ---\n Limit\n   Output: (time_func(hyper_timepart.\"time\")), (_timescaledb_functions.get_partition_hash(hyper_timepart.device)), (avg(hyper_timepart.temp))\n   ->  GroupAggregate\n         Output: (time_func(hyper_timepart.\"time\")), (_timescaledb_functions.get_partition_hash(hyper_timepart.device)), avg(hyper_timepart.temp)\n         Group Key: (time_func(hyper_timepart.\"time\")), (_timescaledb_functions.get_partition_hash(hyper_timepart.device))\n         ->  Incremental Sort\n               Output: (time_func(hyper_timepart.\"time\")), (_timescaledb_functions.get_partition_hash(hyper_timepart.device)), hyper_timepart.temp\n               Sort Key: (time_func(hyper_timepart.\"time\")), (_timescaledb_functions.get_partition_hash(hyper_timepart.device))\n               Presorted Key: (time_func(hyper_timepart.\"time\"))\n               ->  Result\n                     Output: (time_func(hyper_timepart.\"time\")), _timescaledb_functions.get_partition_hash(hyper_timepart.device), hyper_timepart.temp\n                     ->  Merge Append\n                           Sort Key: (time_func(hyper_timepart.\"time\"))\n                           ->  Index Scan Backward using _hyper_3_7_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_7_chunk\n                                 Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp, time_func(_hyper_3_7_chunk.\"time\")\n                           ->  Index Scan Backward using _hyper_3_8_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_8_chunk\n                                 Output: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp, time_func(_hyper_3_8_chunk.\"time\")\n\n-- Test removal of redundant group key optimization in PG16\n-- All lower versions include the redundant key on device column\n:PREFIX\nSELECT device, avg(temp)\nFROM hyper_timepart\nWHERE device = 1\nGROUP BY 1\nLIMIT 10;\n--- QUERY PLAN ---\n Limit\n   Output: _hyper_3_8_chunk.device, (avg(_hyper_3_8_chunk.temp))\n   ->  GroupAggregate\n         Output: _hyper_3_8_chunk.device, avg(_hyper_3_8_chunk.temp)\n         ->  Index Scan using _hyper_3_8_chunk_hyper_timepart_device_expr_idx on _timescaledb_internal._hyper_3_8_chunk\n               Output: _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp\n               Index Cond: (_hyper_3_8_chunk.device = 1)\n\n"
  },
  {
    "path": "test/expected/partitionwise-17.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\set PREFIX 'EXPLAIN (VERBOSE, BUFFERS OFF, COSTS OFF)'\n-- Create a two dimensional hypertable\nCREATE TABLE hyper (time timestamptz, device int, temp float);\nSELECT * FROM create_hypertable('hyper', 'time', 'device', 2);\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             1 | public      | hyper      | t\n\n-- Create a similar PostgreSQL partitioned table\nCREATE TABLE pg2dim (time timestamptz, device int, temp float) PARTITION BY HASH (device);\nCREATE TABLE pg2dim_h1 PARTITION OF pg2dim FOR VALUES WITH (MODULUS 2, REMAINDER 0) PARTITION BY RANGE(time);\nCREATE TABLE pg2dim_h2 PARTITION OF pg2dim FOR VALUES WITH (MODULUS 2, REMAINDER 1) PARTITION BY RANGE(time);\nCREATE TABLE pg2dim_h1_t1 PARTITION OF pg2dim_h1 FOR VALUES FROM ('2018-01-01 00:00') TO ('2018-09-01 00:00');\nCREATE TABLE pg2dim_h1_t2 PARTITION OF pg2dim_h1 FOR VALUES FROM ('2018-09-01 00:00') TO ('2018-12-01 00:00');\nCREATE TABLE pg2dim_h2_t1 PARTITION OF pg2dim_h2 FOR VALUES FROM ('2018-01-01 00:00') TO ('2018-09-01 00:00');\nCREATE TABLE pg2dim_h2_t2 PARTITION OF pg2dim_h2 FOR VALUES FROM ('2018-09-01 00:00') TO ('2018-12-01 00:00');\n-- Create a 1-dimensional partitioned table for comparison\nCREATE TABLE pg1dim (time timestamptz, device int, temp float) PARTITION BY HASH (device);\nCREATE TABLE pg1dim_h1 PARTITION OF pg1dim FOR VALUES WITH (MODULUS 2, REMAINDER 0);\nCREATE TABLE pg1dim_h2 PARTITION OF pg1dim FOR VALUES WITH (MODULUS 2, REMAINDER 1);\nINSERT INTO hyper VALUES\n       ('2018-02-19 13:01', 1, 2.3),\n       ('2018-02-19 13:02', 3, 3.1),\n       ('2018-10-19 13:01', 1, 7.6),\n       ('2018-10-19 13:02', 3, 9.0);\nINSERT INTO pg2dim VALUES\n       ('2018-02-19 13:01', 1, 2.3),\n       ('2018-02-19 13:02', 3, 3.1),\n       ('2018-10-19 13:01', 1, 7.6),\n       ('2018-10-19 13:02', 3, 9.0);\nINSERT INTO pg1dim VALUES\n       ('2018-02-19 13:01', 1, 2.3),\n       ('2018-02-19 13:02', 3, 3.1),\n       ('2018-10-19 13:01', 1, 7.6),\n       ('2018-10-19 13:02', 3, 9.0);\nSELECT * FROM test.show_subtables('hyper');\n                 Child                  | Tablespace \n----------------------------------------+------------\n _timescaledb_internal._hyper_1_1_chunk | \n _timescaledb_internal._hyper_1_2_chunk | \n _timescaledb_internal._hyper_1_3_chunk | \n _timescaledb_internal._hyper_1_4_chunk | \n\nSELECT * FROM pg2dim_h1_t1;\n             time             | device | temp \n------------------------------+--------+------\n Mon Feb 19 13:01:00 2018 PST |      1 |  2.3\n\nSELECT * FROM pg2dim_h1_t2;\n             time             | device | temp \n------------------------------+--------+------\n Fri Oct 19 13:01:00 2018 PDT |      1 |  7.6\n\nSELECT * FROM pg2dim_h2_t1;\n             time             | device | temp \n------------------------------+--------+------\n Mon Feb 19 13:02:00 2018 PST |      3 |  3.1\n\nSELECT * FROM pg2dim_h2_t2;\n             time             | device | temp \n------------------------------+--------+------\n Fri Oct 19 13:02:00 2018 PDT |      3 |    9\n\n-- Compare partitionwise aggreate enabled/disabled. First run queries\n-- on PG partitioned tables for reference.\n-- All partition keys covered by GROUP BY\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT device, avg(temp)\nFROM pg1dim\nGROUP BY 1\nORDER BY 1;\n--- QUERY PLAN ---\n Sort\n   Output: pg1dim.device, (avg(pg1dim.temp))\n   Sort Key: pg1dim.device\n   ->  HashAggregate\n         Output: pg1dim.device, avg(pg1dim.temp)\n         Group Key: pg1dim.device\n         ->  Append\n               ->  Seq Scan on public.pg1dim_h1 pg1dim_1\n                     Output: pg1dim_1.device, pg1dim_1.temp\n               ->  Seq Scan on public.pg1dim_h2 pg1dim_2\n                     Output: pg1dim_2.device, pg1dim_2.temp\n\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT device, avg(temp)\nFROM pg1dim\nGROUP BY 1\nORDER BY 1;\n--- QUERY PLAN ---\n Sort\n   Output: pg1dim.device, (avg(pg1dim.temp))\n   Sort Key: pg1dim.device\n   ->  Append\n         ->  HashAggregate\n               Output: pg1dim.device, avg(pg1dim.temp)\n               Group Key: pg1dim.device\n               ->  Seq Scan on public.pg1dim_h1 pg1dim\n                     Output: pg1dim.device, pg1dim.temp\n         ->  HashAggregate\n               Output: pg1dim_1.device, avg(pg1dim_1.temp)\n               Group Key: pg1dim_1.device\n               ->  Seq Scan on public.pg1dim_h2 pg1dim_1\n                     Output: pg1dim_1.device, pg1dim_1.temp\n\n-- All partition keys not covered by GROUP BY (partial partitionwise)\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT device, avg(temp)\nFROM pg2dim\nGROUP BY 1\nORDER BY 1;\n--- QUERY PLAN ---\n Sort\n   Output: pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: pg2dim.device\n   ->  HashAggregate\n         Output: pg2dim.device, avg(pg2dim.temp)\n         Group Key: pg2dim.device\n         ->  Append\n               ->  Seq Scan on public.pg2dim_h1_t1 pg2dim_1\n                     Output: pg2dim_1.device, pg2dim_1.temp\n               ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_2\n                     Output: pg2dim_2.device, pg2dim_2.temp\n               ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_3\n                     Output: pg2dim_3.device, pg2dim_3.temp\n               ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_4\n                     Output: pg2dim_4.device, pg2dim_4.temp\n\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT device, avg(temp)\nFROM pg2dim\nGROUP BY 1\nORDER BY 1;\n--- QUERY PLAN ---\n Sort\n   Output: pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: pg2dim.device\n   ->  Finalize HashAggregate\n         Output: pg2dim.device, avg(pg2dim.temp)\n         Group Key: pg2dim.device\n         ->  Append\n               ->  Partial HashAggregate\n                     Output: pg2dim.device, PARTIAL avg(pg2dim.temp)\n                     Group Key: pg2dim.device\n                     ->  Seq Scan on public.pg2dim_h1_t1 pg2dim\n                           Output: pg2dim.device, pg2dim.temp\n               ->  Partial HashAggregate\n                     Output: pg2dim_1.device, PARTIAL avg(pg2dim_1.temp)\n                     Group Key: pg2dim_1.device\n                     ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_1\n                           Output: pg2dim_1.device, pg2dim_1.temp\n               ->  Partial HashAggregate\n                     Output: pg2dim_2.device, PARTIAL avg(pg2dim_2.temp)\n                     Group Key: pg2dim_2.device\n                     ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_2\n                           Output: pg2dim_2.device, pg2dim_2.temp\n               ->  Partial HashAggregate\n                     Output: pg2dim_3.device, PARTIAL avg(pg2dim_3.temp)\n                     Group Key: pg2dim_3.device\n                     ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_3\n                           Output: pg2dim_3.device, pg2dim_3.temp\n\n-- All partition keys covered by GROUP BY (full partitionwise)\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM pg2dim\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: pg2dim.\"time\", pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: pg2dim.\"time\", pg2dim.device\n   ->  HashAggregate\n         Output: pg2dim.\"time\", pg2dim.device, avg(pg2dim.temp)\n         Group Key: pg2dim.\"time\", pg2dim.device\n         ->  Append\n               ->  Seq Scan on public.pg2dim_h1_t1 pg2dim_1\n                     Output: pg2dim_1.\"time\", pg2dim_1.device, pg2dim_1.temp\n               ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_2\n                     Output: pg2dim_2.\"time\", pg2dim_2.device, pg2dim_2.temp\n               ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_3\n                     Output: pg2dim_3.\"time\", pg2dim_3.device, pg2dim_3.temp\n               ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_4\n                     Output: pg2dim_4.\"time\", pg2dim_4.device, pg2dim_4.temp\n\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM pg2dim\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: pg2dim.\"time\", pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: pg2dim.\"time\", pg2dim.device\n   ->  Append\n         ->  HashAggregate\n               Output: pg2dim.\"time\", pg2dim.device, avg(pg2dim.temp)\n               Group Key: pg2dim.\"time\", pg2dim.device\n               ->  Seq Scan on public.pg2dim_h1_t1 pg2dim\n                     Output: pg2dim.\"time\", pg2dim.device, pg2dim.temp\n         ->  HashAggregate\n               Output: pg2dim_1.\"time\", pg2dim_1.device, avg(pg2dim_1.temp)\n               Group Key: pg2dim_1.\"time\", pg2dim_1.device\n               ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_1\n                     Output: pg2dim_1.\"time\", pg2dim_1.device, pg2dim_1.temp\n         ->  HashAggregate\n               Output: pg2dim_2.\"time\", pg2dim_2.device, avg(pg2dim_2.temp)\n               Group Key: pg2dim_2.\"time\", pg2dim_2.device\n               ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_2\n                     Output: pg2dim_2.\"time\", pg2dim_2.device, pg2dim_2.temp\n         ->  HashAggregate\n               Output: pg2dim_3.\"time\", pg2dim_3.device, avg(pg2dim_3.temp)\n               Group Key: pg2dim_3.\"time\", pg2dim_3.device\n               ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_3\n                     Output: pg2dim_3.\"time\", pg2dim_3.device, pg2dim_3.temp\n\n-- All partition keys not covered by GROUP BY because of date_trunc\n-- expression on time (partial partitionwise)\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT date_trunc('month', time), device, avg(temp)\nFROM pg2dim\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device\n   ->  HashAggregate\n         Output: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device, avg(pg2dim.temp)\n         Group Key: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device\n         ->  Append\n               ->  Seq Scan on public.pg2dim_h1_t1 pg2dim_1\n                     Output: date_trunc('month'::text, pg2dim_1.\"time\"), pg2dim_1.device, pg2dim_1.temp\n               ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_2\n                     Output: date_trunc('month'::text, pg2dim_2.\"time\"), pg2dim_2.device, pg2dim_2.temp\n               ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_3\n                     Output: date_trunc('month'::text, pg2dim_3.\"time\"), pg2dim_3.device, pg2dim_3.temp\n               ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_4\n                     Output: date_trunc('month'::text, pg2dim_4.\"time\"), pg2dim_4.device, pg2dim_4.temp\n\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT date_trunc('month', time), device, avg(temp)\nFROM pg2dim\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device\n   ->  Finalize HashAggregate\n         Output: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device, avg(pg2dim.temp)\n         Group Key: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device\n         ->  Append\n               ->  Partial HashAggregate\n                     Output: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device, PARTIAL avg(pg2dim.temp)\n                     Group Key: date_trunc('month'::text, pg2dim.\"time\"), pg2dim.device\n                     ->  Seq Scan on public.pg2dim_h1_t1 pg2dim\n                           Output: date_trunc('month'::text, pg2dim.\"time\"), pg2dim.device, pg2dim.temp\n               ->  Partial HashAggregate\n                     Output: (date_trunc('month'::text, pg2dim_1.\"time\")), pg2dim_1.device, PARTIAL avg(pg2dim_1.temp)\n                     Group Key: date_trunc('month'::text, pg2dim_1.\"time\"), pg2dim_1.device\n                     ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_1\n                           Output: date_trunc('month'::text, pg2dim_1.\"time\"), pg2dim_1.device, pg2dim_1.temp\n               ->  Partial HashAggregate\n                     Output: (date_trunc('month'::text, pg2dim_2.\"time\")), pg2dim_2.device, PARTIAL avg(pg2dim_2.temp)\n                     Group Key: date_trunc('month'::text, pg2dim_2.\"time\"), pg2dim_2.device\n                     ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_2\n                           Output: date_trunc('month'::text, pg2dim_2.\"time\"), pg2dim_2.device, pg2dim_2.temp\n               ->  Partial HashAggregate\n                     Output: (date_trunc('month'::text, pg2dim_3.\"time\")), pg2dim_3.device, PARTIAL avg(pg2dim_3.temp)\n                     Group Key: date_trunc('month'::text, pg2dim_3.\"time\"), pg2dim_3.device\n                     ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_3\n                           Output: date_trunc('month'::text, pg2dim_3.\"time\"), pg2dim_3.device, pg2dim_3.temp\n\n-- Now run on hypertable\n-- All partition keys not covered by GROUP BY (partial partitionwise)\nSET timescaledb.enable_chunkwise_aggregation = 'off';\n:PREFIX\nSELECT device, avg(temp)\nFROM hyper\nGROUP BY 1\nORDER BY 1;\n--- QUERY PLAN ---\n Sort\n   Output: hyper.device, (avg(hyper.temp))\n   Sort Key: hyper.device\n   ->  HashAggregate\n         Output: hyper.device, avg(hyper.temp)\n         Group Key: hyper.device\n         ->  Append\n               ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                     Output: _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                     Output: _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                     Output: _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                     Output: _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\nSET timescaledb.enable_chunkwise_aggregation = 'on';\n:PREFIX\nSELECT device, avg(temp)\nFROM hyper\nGROUP BY 1\nORDER BY 1;\n--- QUERY PLAN ---\n Sort\n   Output: hyper.device, (avg(hyper.temp))\n   Sort Key: hyper.device\n   ->  HashAggregate\n         Output: hyper.device, avg(hyper.temp)\n         Group Key: hyper.device\n         ->  Append\n               ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                     Output: _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                     Output: _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                     Output: _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                     Output: _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\n-- All partition keys covered (full partitionwise)\nSET timescaledb.enable_chunkwise_aggregation = 'off';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: hyper.\"time\", hyper.device, (avg(hyper.temp))\n   Sort Key: hyper.\"time\", hyper.device\n   ->  HashAggregate\n         Output: hyper.\"time\", hyper.device, avg(hyper.temp)\n         Group Key: hyper.\"time\", hyper.device\n         ->  Append\n               ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                     Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                     Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                     Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                     Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\nSET timescaledb.enable_chunkwise_aggregation = 'on';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: hyper.\"time\", hyper.device, (avg(hyper.temp))\n   Sort Key: hyper.\"time\", hyper.device\n   ->  HashAggregate\n         Output: hyper.\"time\", hyper.device, avg(hyper.temp)\n         Group Key: hyper.\"time\", hyper.device\n         ->  Append\n               ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                     Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                     Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                     Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                     Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\n-- Partial aggregation since date_trunc(time) is not a partition key\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT date_trunc('month', time), device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: (date_trunc('month'::text, hyper.\"time\")), hyper.device, (avg(hyper.temp))\n   Sort Key: (date_trunc('month'::text, hyper.\"time\")), hyper.device\n   ->  HashAggregate\n         Output: (date_trunc('month'::text, hyper.\"time\")), hyper.device, avg(hyper.temp)\n         Group Key: date_trunc('month'::text, hyper.\"time\"), hyper.device\n         ->  Result\n               Output: date_trunc('month'::text, hyper.\"time\"), hyper.device, hyper.temp\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                           Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                           Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                           Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                           Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\n-- Partial aggregation pushdown is currently not supported for this query by\n-- the TSDB pushdown code since a projection is used in the path.\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT date_trunc('month', time), device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: (date_trunc('month'::text, hyper.\"time\")), hyper.device, (avg(hyper.temp))\n   Sort Key: (date_trunc('month'::text, hyper.\"time\")), hyper.device\n   ->  HashAggregate\n         Output: (date_trunc('month'::text, hyper.\"time\")), hyper.device, avg(hyper.temp)\n         Group Key: date_trunc('month'::text, hyper.\"time\"), hyper.device\n         ->  Result\n               Output: date_trunc('month'::text, hyper.\"time\"), hyper.device, hyper.temp\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                           Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                           Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                           Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                           Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\n-- Also test time_bucket\nSET timescaledb.enable_chunkwise_aggregation = 'off';\n:PREFIX\nSELECT time_bucket('1 month', time), device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: (time_bucket('@ 1 mon'::interval, hyper.\"time\")), hyper.device, (avg(hyper.temp))\n   Sort Key: (time_bucket('@ 1 mon'::interval, hyper.\"time\")), hyper.device\n   ->  HashAggregate\n         Output: (time_bucket('@ 1 mon'::interval, hyper.\"time\")), hyper.device, avg(hyper.temp)\n         Group Key: time_bucket('@ 1 mon'::interval, hyper.\"time\"), hyper.device\n         ->  Result\n               Output: time_bucket('@ 1 mon'::interval, hyper.\"time\"), hyper.device, hyper.temp\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                           Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                           Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                           Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                           Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\nSET timescaledb.enable_chunkwise_aggregation = 'on';\n:PREFIX\nSELECT time_bucket('1 month', time), device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: (time_bucket('@ 1 mon'::interval, hyper.\"time\")), hyper.device, (avg(hyper.temp))\n   Sort Key: (time_bucket('@ 1 mon'::interval, hyper.\"time\")), hyper.device\n   ->  HashAggregate\n         Output: (time_bucket('@ 1 mon'::interval, hyper.\"time\")), hyper.device, avg(hyper.temp)\n         Group Key: time_bucket('@ 1 mon'::interval, hyper.\"time\"), hyper.device\n         ->  Result\n               Output: time_bucket('@ 1 mon'::interval, hyper.\"time\"), hyper.device, hyper.temp\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                           Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                           Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                           Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                           Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\n-- Test partitionwise joins, mostly to see that we do not break\n-- anything\nCREATE TABLE hyper_meta (time timestamptz, device int, info text);\nSELECT * FROM create_hypertable('hyper_meta', 'time', 'device', 2);\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             2 | public      | hyper_meta | t\n\nINSERT INTO hyper_meta VALUES\n       ('2018-02-19 13:01', 1, 'device_1'),\n       ('2018-02-19 13:02', 3, 'device_3');\nSET enable_partitionwise_join = 'off';\n:PREFIX\nSELECT h.time, h.device, h.temp, hm.info\nFROM hyper h, hyper_meta hm\nWHERE h.device = hm.device;\n--- QUERY PLAN ---\n Merge Join\n   Output: h.\"time\", h.device, h.temp, hm.info\n   Merge Cond: (hm.device = h.device)\n   ->  Merge Append\n         Sort Key: hm.device\n         ->  Index Scan using _hyper_2_5_chunk_hyper_meta_device_time_idx on _timescaledb_internal._hyper_2_5_chunk hm_1\n               Output: hm_1.info, hm_1.device\n         ->  Index Scan using _hyper_2_6_chunk_hyper_meta_device_time_idx on _timescaledb_internal._hyper_2_6_chunk hm_2\n               Output: hm_2.info, hm_2.device\n   ->  Materialize\n         Output: h.\"time\", h.device, h.temp\n         ->  Merge Append\n               Sort Key: h.device\n               ->  Index Scan using _hyper_1_1_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_1_chunk h_1\n                     Output: h_1.\"time\", h_1.device, h_1.temp\n               ->  Index Scan using _hyper_1_2_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_2_chunk h_2\n                     Output: h_2.\"time\", h_2.device, h_2.temp\n               ->  Index Scan using _hyper_1_3_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_3_chunk h_3\n                     Output: h_3.\"time\", h_3.device, h_3.temp\n               ->  Index Scan using _hyper_1_4_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_4_chunk h_4\n                     Output: h_4.\"time\", h_4.device, h_4.temp\n\n:PREFIX\nSELECT pg2.time, pg2.device, pg2.temp, pg1.temp\nFROM pg2dim pg2, pg1dim pg1\nWHERE pg2.device = pg1.device;\n--- QUERY PLAN ---\n Merge Join\n   Output: pg2.\"time\", pg2.device, pg2.temp, pg1.temp\n   Merge Cond: (pg1.device = pg2.device)\n   ->  Sort\n         Output: pg1.temp, pg1.device\n         Sort Key: pg1.device\n         ->  Append\n               ->  Seq Scan on public.pg1dim_h1 pg1_1\n                     Output: pg1_1.temp, pg1_1.device\n               ->  Seq Scan on public.pg1dim_h2 pg1_2\n                     Output: pg1_2.temp, pg1_2.device\n   ->  Sort\n         Output: pg2.\"time\", pg2.device, pg2.temp\n         Sort Key: pg2.device\n         ->  Append\n               ->  Seq Scan on public.pg2dim_h1_t1 pg2_1\n                     Output: pg2_1.\"time\", pg2_1.device, pg2_1.temp\n               ->  Seq Scan on public.pg2dim_h1_t2 pg2_2\n                     Output: pg2_2.\"time\", pg2_2.device, pg2_2.temp\n               ->  Seq Scan on public.pg2dim_h2_t1 pg2_3\n                     Output: pg2_3.\"time\", pg2_3.device, pg2_3.temp\n               ->  Seq Scan on public.pg2dim_h2_t2 pg2_4\n                     Output: pg2_4.\"time\", pg2_4.device, pg2_4.temp\n\nSET enable_partitionwise_join = 'on';\n:PREFIX\nSELECT h.time, h.device, h.temp, hm.info\nFROM hyper h, hyper_meta hm\nWHERE h.device = hm.device;\n--- QUERY PLAN ---\n Merge Join\n   Output: h.\"time\", h.device, h.temp, hm.info\n   Merge Cond: (hm.device = h.device)\n   ->  Merge Append\n         Sort Key: hm.device\n         ->  Index Scan using _hyper_2_5_chunk_hyper_meta_device_time_idx on _timescaledb_internal._hyper_2_5_chunk hm_1\n               Output: hm_1.info, hm_1.device\n         ->  Index Scan using _hyper_2_6_chunk_hyper_meta_device_time_idx on _timescaledb_internal._hyper_2_6_chunk hm_2\n               Output: hm_2.info, hm_2.device\n   ->  Materialize\n         Output: h.\"time\", h.device, h.temp\n         ->  Merge Append\n               Sort Key: h.device\n               ->  Index Scan using _hyper_1_1_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_1_chunk h_1\n                     Output: h_1.\"time\", h_1.device, h_1.temp\n               ->  Index Scan using _hyper_1_2_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_2_chunk h_2\n                     Output: h_2.\"time\", h_2.device, h_2.temp\n               ->  Index Scan using _hyper_1_3_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_3_chunk h_3\n                     Output: h_3.\"time\", h_3.device, h_3.temp\n               ->  Index Scan using _hyper_1_4_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_4_chunk h_4\n                     Output: h_4.\"time\", h_4.device, h_4.temp\n\n:PREFIX\nSELECT pg2.time, pg2.device, pg2.temp, pg1.temp\nFROM pg2dim pg2, pg1dim pg1\nWHERE pg2.device = pg1.device;\n--- QUERY PLAN ---\n Append\n   ->  Merge Join\n         Output: pg2_2.\"time\", pg2_2.device, pg2_2.temp, pg1_1.temp\n         Merge Cond: (pg1_1.device = pg2_2.device)\n         ->  Sort\n               Output: pg1_1.temp, pg1_1.device\n               Sort Key: pg1_1.device\n               ->  Seq Scan on public.pg1dim_h1 pg1_1\n                     Output: pg1_1.temp, pg1_1.device\n         ->  Sort\n               Output: pg2_2.\"time\", pg2_2.device, pg2_2.temp\n               Sort Key: pg2_2.device\n               ->  Append\n                     ->  Seq Scan on public.pg2dim_h1_t1 pg2_2\n                           Output: pg2_2.\"time\", pg2_2.device, pg2_2.temp\n                     ->  Seq Scan on public.pg2dim_h1_t2 pg2_3\n                           Output: pg2_3.\"time\", pg2_3.device, pg2_3.temp\n   ->  Merge Join\n         Output: pg2_5.\"time\", pg2_5.device, pg2_5.temp, pg1_2.temp\n         Merge Cond: (pg1_2.device = pg2_5.device)\n         ->  Sort\n               Output: pg1_2.temp, pg1_2.device\n               Sort Key: pg1_2.device\n               ->  Seq Scan on public.pg1dim_h2 pg1_2\n                     Output: pg1_2.temp, pg1_2.device\n         ->  Sort\n               Output: pg2_5.\"time\", pg2_5.device, pg2_5.temp\n               Sort Key: pg2_5.device\n               ->  Append\n                     ->  Seq Scan on public.pg2dim_h2_t1 pg2_5\n                           Output: pg2_5.\"time\", pg2_5.device, pg2_5.temp\n                     ->  Seq Scan on public.pg2dim_h2_t2 pg2_6\n                           Output: pg2_6.\"time\", pg2_6.device, pg2_6.temp\n\n-- Test hypertable with time partitioning function\nCREATE OR REPLACE FUNCTION time_func(unixtime float8)\n    RETURNS TIMESTAMPTZ LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nDECLARE\n    retval TIMESTAMPTZ;\nBEGIN\n    retval := to_timestamp(unixtime);\n    RETURN retval;\nEND\n$BODY$;\nCREATE TABLE hyper_timepart (time float8, device int, temp float);\nSELECT * FROM create_hypertable('hyper_timepart', 'time', 'device', 2, time_partitioning_func => 'time_func');\n hypertable_id | schema_name |   table_name   | created \n---------------+-------------+----------------+---------\n             3 | public      | hyper_timepart | t\n\n-- Planner won't pick push-down aggs on table with time function\n-- unless a certain amount of data\nSELECT setseed(1);\n setseed \n---------\n \n\nINSERT INTO hyper_timepart\nSELECT x, ceil(random() * 8), random() * 20\nFROM generate_series(0,5000-1) AS x;\n-- All partition keys covered (full partitionwise)\nSET timescaledb.enable_chunkwise_aggregation = 'off';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n--- QUERY PLAN ---\n Limit\n   Output: hyper_timepart.\"time\", hyper_timepart.device, (avg(hyper_timepart.temp))\n   ->  Sort\n         Output: hyper_timepart.\"time\", hyper_timepart.device, (avg(hyper_timepart.temp))\n         Sort Key: hyper_timepart.\"time\", hyper_timepart.device\n         ->  HashAggregate\n               Output: hyper_timepart.\"time\", hyper_timepart.device, avg(hyper_timepart.temp)\n               Group Key: hyper_timepart.\"time\", hyper_timepart.device\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_3_7_chunk\n                           Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_3_8_chunk\n                           Output: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp\n\n:PREFIX\nSELECT time_func(time), device, avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n--- QUERY PLAN ---\n Limit\n   Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, (avg(hyper_timepart.temp))\n   ->  GroupAggregate\n         Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, avg(hyper_timepart.temp)\n         Group Key: (time_func(hyper_timepart.\"time\")), hyper_timepart.device\n         ->  Incremental Sort\n               Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, hyper_timepart.temp\n               Sort Key: (time_func(hyper_timepart.\"time\")), hyper_timepart.device\n               Presorted Key: (time_func(hyper_timepart.\"time\"))\n               ->  Result\n                     Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, hyper_timepart.temp\n                     ->  Merge Append\n                           Sort Key: (time_func(hyper_timepart.\"time\"))\n                           ->  Index Scan Backward using _hyper_3_7_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_7_chunk\n                                 Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp, time_func(_hyper_3_7_chunk.\"time\")\n                           ->  Index Scan Backward using _hyper_3_8_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_8_chunk\n                                 Output: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp, time_func(_hyper_3_8_chunk.\"time\")\n\n-- Grouping on original time column should be pushed-down\nSET timescaledb.enable_chunkwise_aggregation = 'on';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n--- QUERY PLAN ---\n Limit\n   Output: hyper_timepart.\"time\", hyper_timepart.device, (avg(hyper_timepart.temp))\n   ->  Sort\n         Output: hyper_timepart.\"time\", hyper_timepart.device, (avg(hyper_timepart.temp))\n         Sort Key: hyper_timepart.\"time\", hyper_timepart.device\n         ->  HashAggregate\n               Output: hyper_timepart.\"time\", hyper_timepart.device, avg(hyper_timepart.temp)\n               Group Key: hyper_timepart.\"time\", hyper_timepart.device\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_3_7_chunk\n                           Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_3_8_chunk\n                           Output: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp\n\n-- Applying the time partitioning function should also allow push-down\n-- on open dimensions\n:PREFIX\nSELECT time_func(time), device, avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n--- QUERY PLAN ---\n Limit\n   Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, (avg(hyper_timepart.temp))\n   ->  GroupAggregate\n         Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, avg(hyper_timepart.temp)\n         Group Key: (time_func(hyper_timepart.\"time\")), hyper_timepart.device\n         ->  Incremental Sort\n               Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, hyper_timepart.temp\n               Sort Key: (time_func(hyper_timepart.\"time\")), hyper_timepart.device\n               Presorted Key: (time_func(hyper_timepart.\"time\"))\n               ->  Result\n                     Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, hyper_timepart.temp\n                     ->  Merge Append\n                           Sort Key: (time_func(hyper_timepart.\"time\"))\n                           ->  Index Scan Backward using _hyper_3_7_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_7_chunk\n                                 Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp, time_func(_hyper_3_7_chunk.\"time\")\n                           ->  Index Scan Backward using _hyper_3_8_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_8_chunk\n                                 Output: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp, time_func(_hyper_3_8_chunk.\"time\")\n\n-- Partial aggregation pushdown is currently not supported for this query by\n-- the TSDB pushdown code since a projection is used in the path.\n:PREFIX\nSELECT time_func(time), _timescaledb_functions.get_partition_hash(device), avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n--- QUERY PLAN ---\n Limit\n   Output: (time_func(hyper_timepart.\"time\")), (_timescaledb_functions.get_partition_hash(hyper_timepart.device)), (avg(hyper_timepart.temp))\n   ->  GroupAggregate\n         Output: (time_func(hyper_timepart.\"time\")), (_timescaledb_functions.get_partition_hash(hyper_timepart.device)), avg(hyper_timepart.temp)\n         Group Key: (time_func(hyper_timepart.\"time\")), (_timescaledb_functions.get_partition_hash(hyper_timepart.device))\n         ->  Incremental Sort\n               Output: (time_func(hyper_timepart.\"time\")), (_timescaledb_functions.get_partition_hash(hyper_timepart.device)), hyper_timepart.temp\n               Sort Key: (time_func(hyper_timepart.\"time\")), (_timescaledb_functions.get_partition_hash(hyper_timepart.device))\n               Presorted Key: (time_func(hyper_timepart.\"time\"))\n               ->  Result\n                     Output: (time_func(hyper_timepart.\"time\")), _timescaledb_functions.get_partition_hash(hyper_timepart.device), hyper_timepart.temp\n                     ->  Merge Append\n                           Sort Key: (time_func(hyper_timepart.\"time\"))\n                           ->  Index Scan Backward using _hyper_3_7_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_7_chunk\n                                 Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp, time_func(_hyper_3_7_chunk.\"time\")\n                           ->  Index Scan Backward using _hyper_3_8_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_8_chunk\n                                 Output: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp, time_func(_hyper_3_8_chunk.\"time\")\n\n-- Test removal of redundant group key optimization in PG16\n-- All lower versions include the redundant key on device column\n:PREFIX\nSELECT device, avg(temp)\nFROM hyper_timepart\nWHERE device = 1\nGROUP BY 1\nLIMIT 10;\n--- QUERY PLAN ---\n Limit\n   Output: _hyper_3_8_chunk.device, (avg(_hyper_3_8_chunk.temp))\n   ->  GroupAggregate\n         Output: _hyper_3_8_chunk.device, avg(_hyper_3_8_chunk.temp)\n         ->  Index Scan using _hyper_3_8_chunk_hyper_timepart_device_expr_idx on _timescaledb_internal._hyper_3_8_chunk\n               Output: _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp\n               Index Cond: (_hyper_3_8_chunk.device = 1)\n\n"
  },
  {
    "path": "test/expected/partitionwise-18.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\set PREFIX 'EXPLAIN (VERBOSE, BUFFERS OFF, COSTS OFF)'\n-- Create a two dimensional hypertable\nCREATE TABLE hyper (time timestamptz, device int, temp float);\nSELECT * FROM create_hypertable('hyper', 'time', 'device', 2);\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             1 | public      | hyper      | t\n\n-- Create a similar PostgreSQL partitioned table\nCREATE TABLE pg2dim (time timestamptz, device int, temp float) PARTITION BY HASH (device);\nCREATE TABLE pg2dim_h1 PARTITION OF pg2dim FOR VALUES WITH (MODULUS 2, REMAINDER 0) PARTITION BY RANGE(time);\nCREATE TABLE pg2dim_h2 PARTITION OF pg2dim FOR VALUES WITH (MODULUS 2, REMAINDER 1) PARTITION BY RANGE(time);\nCREATE TABLE pg2dim_h1_t1 PARTITION OF pg2dim_h1 FOR VALUES FROM ('2018-01-01 00:00') TO ('2018-09-01 00:00');\nCREATE TABLE pg2dim_h1_t2 PARTITION OF pg2dim_h1 FOR VALUES FROM ('2018-09-01 00:00') TO ('2018-12-01 00:00');\nCREATE TABLE pg2dim_h2_t1 PARTITION OF pg2dim_h2 FOR VALUES FROM ('2018-01-01 00:00') TO ('2018-09-01 00:00');\nCREATE TABLE pg2dim_h2_t2 PARTITION OF pg2dim_h2 FOR VALUES FROM ('2018-09-01 00:00') TO ('2018-12-01 00:00');\n-- Create a 1-dimensional partitioned table for comparison\nCREATE TABLE pg1dim (time timestamptz, device int, temp float) PARTITION BY HASH (device);\nCREATE TABLE pg1dim_h1 PARTITION OF pg1dim FOR VALUES WITH (MODULUS 2, REMAINDER 0);\nCREATE TABLE pg1dim_h2 PARTITION OF pg1dim FOR VALUES WITH (MODULUS 2, REMAINDER 1);\nINSERT INTO hyper VALUES\n       ('2018-02-19 13:01', 1, 2.3),\n       ('2018-02-19 13:02', 3, 3.1),\n       ('2018-10-19 13:01', 1, 7.6),\n       ('2018-10-19 13:02', 3, 9.0);\nINSERT INTO pg2dim VALUES\n       ('2018-02-19 13:01', 1, 2.3),\n       ('2018-02-19 13:02', 3, 3.1),\n       ('2018-10-19 13:01', 1, 7.6),\n       ('2018-10-19 13:02', 3, 9.0);\nINSERT INTO pg1dim VALUES\n       ('2018-02-19 13:01', 1, 2.3),\n       ('2018-02-19 13:02', 3, 3.1),\n       ('2018-10-19 13:01', 1, 7.6),\n       ('2018-10-19 13:02', 3, 9.0);\nSELECT * FROM test.show_subtables('hyper');\n                 Child                  | Tablespace \n----------------------------------------+------------\n _timescaledb_internal._hyper_1_1_chunk | \n _timescaledb_internal._hyper_1_2_chunk | \n _timescaledb_internal._hyper_1_3_chunk | \n _timescaledb_internal._hyper_1_4_chunk | \n\nSELECT * FROM pg2dim_h1_t1;\n             time             | device | temp \n------------------------------+--------+------\n Mon Feb 19 13:01:00 2018 PST |      1 |  2.3\n\nSELECT * FROM pg2dim_h1_t2;\n             time             | device | temp \n------------------------------+--------+------\n Fri Oct 19 13:01:00 2018 PDT |      1 |  7.6\n\nSELECT * FROM pg2dim_h2_t1;\n             time             | device | temp \n------------------------------+--------+------\n Mon Feb 19 13:02:00 2018 PST |      3 |  3.1\n\nSELECT * FROM pg2dim_h2_t2;\n             time             | device | temp \n------------------------------+--------+------\n Fri Oct 19 13:02:00 2018 PDT |      3 |    9\n\n-- Compare partitionwise aggreate enabled/disabled. First run queries\n-- on PG partitioned tables for reference.\n-- All partition keys covered by GROUP BY\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT device, avg(temp)\nFROM pg1dim\nGROUP BY 1\nORDER BY 1;\n--- QUERY PLAN ---\n Sort\n   Output: pg1dim.device, (avg(pg1dim.temp))\n   Sort Key: pg1dim.device\n   ->  HashAggregate\n         Output: pg1dim.device, avg(pg1dim.temp)\n         Group Key: pg1dim.device\n         ->  Append\n               ->  Seq Scan on public.pg1dim_h1 pg1dim_1\n                     Output: pg1dim_1.device, pg1dim_1.temp\n               ->  Seq Scan on public.pg1dim_h2 pg1dim_2\n                     Output: pg1dim_2.device, pg1dim_2.temp\n\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT device, avg(temp)\nFROM pg1dim\nGROUP BY 1\nORDER BY 1;\n--- QUERY PLAN ---\n Sort\n   Output: pg1dim.device, (avg(pg1dim.temp))\n   Sort Key: pg1dim.device\n   ->  Append\n         ->  HashAggregate\n               Output: pg1dim.device, avg(pg1dim.temp)\n               Group Key: pg1dim.device\n               ->  Seq Scan on public.pg1dim_h1 pg1dim\n                     Output: pg1dim.device, pg1dim.temp\n         ->  HashAggregate\n               Output: pg1dim_1.device, avg(pg1dim_1.temp)\n               Group Key: pg1dim_1.device\n               ->  Seq Scan on public.pg1dim_h2 pg1dim_1\n                     Output: pg1dim_1.device, pg1dim_1.temp\n\n-- All partition keys not covered by GROUP BY (partial partitionwise)\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT device, avg(temp)\nFROM pg2dim\nGROUP BY 1\nORDER BY 1;\n--- QUERY PLAN ---\n Sort\n   Output: pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: pg2dim.device\n   ->  HashAggregate\n         Output: pg2dim.device, avg(pg2dim.temp)\n         Group Key: pg2dim.device\n         ->  Append\n               ->  Seq Scan on public.pg2dim_h1_t1 pg2dim_1\n                     Output: pg2dim_1.device, pg2dim_1.temp\n               ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_2\n                     Output: pg2dim_2.device, pg2dim_2.temp\n               ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_3\n                     Output: pg2dim_3.device, pg2dim_3.temp\n               ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_4\n                     Output: pg2dim_4.device, pg2dim_4.temp\n\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT device, avg(temp)\nFROM pg2dim\nGROUP BY 1\nORDER BY 1;\n--- QUERY PLAN ---\n Sort\n   Output: pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: pg2dim.device\n   ->  Finalize HashAggregate\n         Output: pg2dim.device, avg(pg2dim.temp)\n         Group Key: pg2dim.device\n         ->  Append\n               ->  Partial HashAggregate\n                     Output: pg2dim.device, PARTIAL avg(pg2dim.temp)\n                     Group Key: pg2dim.device\n                     ->  Seq Scan on public.pg2dim_h1_t1 pg2dim\n                           Output: pg2dim.device, pg2dim.temp\n               ->  Partial HashAggregate\n                     Output: pg2dim_1.device, PARTIAL avg(pg2dim_1.temp)\n                     Group Key: pg2dim_1.device\n                     ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_1\n                           Output: pg2dim_1.device, pg2dim_1.temp\n               ->  Partial HashAggregate\n                     Output: pg2dim_2.device, PARTIAL avg(pg2dim_2.temp)\n                     Group Key: pg2dim_2.device\n                     ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_2\n                           Output: pg2dim_2.device, pg2dim_2.temp\n               ->  Partial HashAggregate\n                     Output: pg2dim_3.device, PARTIAL avg(pg2dim_3.temp)\n                     Group Key: pg2dim_3.device\n                     ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_3\n                           Output: pg2dim_3.device, pg2dim_3.temp\n\n-- All partition keys covered by GROUP BY (full partitionwise)\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM pg2dim\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: pg2dim.\"time\", pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: pg2dim.\"time\", pg2dim.device\n   ->  HashAggregate\n         Output: pg2dim.\"time\", pg2dim.device, avg(pg2dim.temp)\n         Group Key: pg2dim.\"time\", pg2dim.device\n         ->  Append\n               ->  Seq Scan on public.pg2dim_h1_t1 pg2dim_1\n                     Output: pg2dim_1.\"time\", pg2dim_1.device, pg2dim_1.temp\n               ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_2\n                     Output: pg2dim_2.\"time\", pg2dim_2.device, pg2dim_2.temp\n               ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_3\n                     Output: pg2dim_3.\"time\", pg2dim_3.device, pg2dim_3.temp\n               ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_4\n                     Output: pg2dim_4.\"time\", pg2dim_4.device, pg2dim_4.temp\n\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM pg2dim\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: pg2dim.\"time\", pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: pg2dim.\"time\", pg2dim.device\n   ->  Append\n         ->  HashAggregate\n               Output: pg2dim.\"time\", pg2dim.device, avg(pg2dim.temp)\n               Group Key: pg2dim.\"time\", pg2dim.device\n               ->  Seq Scan on public.pg2dim_h1_t1 pg2dim\n                     Output: pg2dim.\"time\", pg2dim.device, pg2dim.temp\n         ->  HashAggregate\n               Output: pg2dim_1.\"time\", pg2dim_1.device, avg(pg2dim_1.temp)\n               Group Key: pg2dim_1.\"time\", pg2dim_1.device\n               ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_1\n                     Output: pg2dim_1.\"time\", pg2dim_1.device, pg2dim_1.temp\n         ->  HashAggregate\n               Output: pg2dim_2.\"time\", pg2dim_2.device, avg(pg2dim_2.temp)\n               Group Key: pg2dim_2.\"time\", pg2dim_2.device\n               ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_2\n                     Output: pg2dim_2.\"time\", pg2dim_2.device, pg2dim_2.temp\n         ->  HashAggregate\n               Output: pg2dim_3.\"time\", pg2dim_3.device, avg(pg2dim_3.temp)\n               Group Key: pg2dim_3.\"time\", pg2dim_3.device\n               ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_3\n                     Output: pg2dim_3.\"time\", pg2dim_3.device, pg2dim_3.temp\n\n-- All partition keys not covered by GROUP BY because of date_trunc\n-- expression on time (partial partitionwise)\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT date_trunc('month', time), device, avg(temp)\nFROM pg2dim\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device\n   ->  HashAggregate\n         Output: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device, avg(pg2dim.temp)\n         Group Key: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device\n         ->  Append\n               ->  Seq Scan on public.pg2dim_h1_t1 pg2dim_1\n                     Output: date_trunc('month'::text, pg2dim_1.\"time\"), pg2dim_1.device, pg2dim_1.temp\n               ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_2\n                     Output: date_trunc('month'::text, pg2dim_2.\"time\"), pg2dim_2.device, pg2dim_2.temp\n               ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_3\n                     Output: date_trunc('month'::text, pg2dim_3.\"time\"), pg2dim_3.device, pg2dim_3.temp\n               ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_4\n                     Output: date_trunc('month'::text, pg2dim_4.\"time\"), pg2dim_4.device, pg2dim_4.temp\n\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT date_trunc('month', time), device, avg(temp)\nFROM pg2dim\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device\n   ->  Finalize HashAggregate\n         Output: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device, avg(pg2dim.temp)\n         Group Key: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device\n         ->  Append\n               ->  Partial HashAggregate\n                     Output: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device, PARTIAL avg(pg2dim.temp)\n                     Group Key: date_trunc('month'::text, pg2dim.\"time\"), pg2dim.device\n                     ->  Seq Scan on public.pg2dim_h1_t1 pg2dim\n                           Output: date_trunc('month'::text, pg2dim.\"time\"), pg2dim.device, pg2dim.temp\n               ->  Partial HashAggregate\n                     Output: (date_trunc('month'::text, pg2dim_1.\"time\")), pg2dim_1.device, PARTIAL avg(pg2dim_1.temp)\n                     Group Key: date_trunc('month'::text, pg2dim_1.\"time\"), pg2dim_1.device\n                     ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_1\n                           Output: date_trunc('month'::text, pg2dim_1.\"time\"), pg2dim_1.device, pg2dim_1.temp\n               ->  Partial HashAggregate\n                     Output: (date_trunc('month'::text, pg2dim_2.\"time\")), pg2dim_2.device, PARTIAL avg(pg2dim_2.temp)\n                     Group Key: date_trunc('month'::text, pg2dim_2.\"time\"), pg2dim_2.device\n                     ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_2\n                           Output: date_trunc('month'::text, pg2dim_2.\"time\"), pg2dim_2.device, pg2dim_2.temp\n               ->  Partial HashAggregate\n                     Output: (date_trunc('month'::text, pg2dim_3.\"time\")), pg2dim_3.device, PARTIAL avg(pg2dim_3.temp)\n                     Group Key: date_trunc('month'::text, pg2dim_3.\"time\"), pg2dim_3.device\n                     ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_3\n                           Output: date_trunc('month'::text, pg2dim_3.\"time\"), pg2dim_3.device, pg2dim_3.temp\n\n-- Now run on hypertable\n-- All partition keys not covered by GROUP BY (partial partitionwise)\nSET timescaledb.enable_chunkwise_aggregation = 'off';\n:PREFIX\nSELECT device, avg(temp)\nFROM hyper\nGROUP BY 1\nORDER BY 1;\n--- QUERY PLAN ---\n Sort\n   Output: hyper.device, (avg(hyper.temp))\n   Sort Key: hyper.device\n   ->  HashAggregate\n         Output: hyper.device, avg(hyper.temp)\n         Group Key: hyper.device\n         ->  Append\n               ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                     Output: _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                     Output: _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                     Output: _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                     Output: _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\nSET timescaledb.enable_chunkwise_aggregation = 'on';\n:PREFIX\nSELECT device, avg(temp)\nFROM hyper\nGROUP BY 1\nORDER BY 1;\n--- QUERY PLAN ---\n Sort\n   Output: hyper.device, (avg(hyper.temp))\n   Sort Key: hyper.device\n   ->  HashAggregate\n         Output: hyper.device, avg(hyper.temp)\n         Group Key: hyper.device\n         ->  Append\n               ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                     Output: _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                     Output: _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                     Output: _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                     Output: _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\n-- All partition keys covered (full partitionwise)\nSET timescaledb.enable_chunkwise_aggregation = 'off';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: hyper.\"time\", hyper.device, (avg(hyper.temp))\n   Sort Key: hyper.\"time\", hyper.device\n   ->  HashAggregate\n         Output: hyper.\"time\", hyper.device, avg(hyper.temp)\n         Group Key: hyper.\"time\", hyper.device\n         ->  Append\n               ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                     Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                     Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                     Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                     Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\nSET timescaledb.enable_chunkwise_aggregation = 'on';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: hyper.\"time\", hyper.device, (avg(hyper.temp))\n   Sort Key: hyper.\"time\", hyper.device\n   ->  HashAggregate\n         Output: hyper.\"time\", hyper.device, avg(hyper.temp)\n         Group Key: hyper.\"time\", hyper.device\n         ->  Append\n               ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                     Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                     Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                     Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                     Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\n-- Partial aggregation since date_trunc(time) is not a partition key\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT date_trunc('month', time), device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: (date_trunc('month'::text, hyper.\"time\")), hyper.device, (avg(hyper.temp))\n   Sort Key: (date_trunc('month'::text, hyper.\"time\")), hyper.device\n   ->  HashAggregate\n         Output: (date_trunc('month'::text, hyper.\"time\")), hyper.device, avg(hyper.temp)\n         Group Key: date_trunc('month'::text, hyper.\"time\"), hyper.device\n         ->  Result\n               Output: date_trunc('month'::text, hyper.\"time\"), hyper.device, hyper.temp\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                           Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                           Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                           Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                           Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\n-- Partial aggregation pushdown is currently not supported for this query by\n-- the TSDB pushdown code since a projection is used in the path.\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT date_trunc('month', time), device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: (date_trunc('month'::text, hyper.\"time\")), hyper.device, (avg(hyper.temp))\n   Sort Key: (date_trunc('month'::text, hyper.\"time\")), hyper.device\n   ->  HashAggregate\n         Output: (date_trunc('month'::text, hyper.\"time\")), hyper.device, avg(hyper.temp)\n         Group Key: date_trunc('month'::text, hyper.\"time\"), hyper.device\n         ->  Result\n               Output: date_trunc('month'::text, hyper.\"time\"), hyper.device, hyper.temp\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                           Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                           Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                           Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                           Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\n-- Also test time_bucket\nSET timescaledb.enable_chunkwise_aggregation = 'off';\n:PREFIX\nSELECT time_bucket('1 month', time), device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: (time_bucket('@ 1 mon'::interval, hyper.\"time\")), hyper.device, (avg(hyper.temp))\n   Sort Key: (time_bucket('@ 1 mon'::interval, hyper.\"time\")), hyper.device\n   ->  HashAggregate\n         Output: (time_bucket('@ 1 mon'::interval, hyper.\"time\")), hyper.device, avg(hyper.temp)\n         Group Key: time_bucket('@ 1 mon'::interval, hyper.\"time\"), hyper.device\n         ->  Result\n               Output: time_bucket('@ 1 mon'::interval, hyper.\"time\"), hyper.device, hyper.temp\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                           Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                           Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                           Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                           Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\nSET timescaledb.enable_chunkwise_aggregation = 'on';\n:PREFIX\nSELECT time_bucket('1 month', time), device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n--- QUERY PLAN ---\n Sort\n   Output: (time_bucket('@ 1 mon'::interval, hyper.\"time\")), hyper.device, (avg(hyper.temp))\n   Sort Key: (time_bucket('@ 1 mon'::interval, hyper.\"time\")), hyper.device\n   ->  HashAggregate\n         Output: (time_bucket('@ 1 mon'::interval, hyper.\"time\")), hyper.device, avg(hyper.temp)\n         Group Key: time_bucket('@ 1 mon'::interval, hyper.\"time\"), hyper.device\n         ->  Result\n               Output: time_bucket('@ 1 mon'::interval, hyper.\"time\"), hyper.device, hyper.temp\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                           Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                           Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                           Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                           Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n\n-- Test partitionwise joins, mostly to see that we do not break\n-- anything\nCREATE TABLE hyper_meta (time timestamptz, device int, info text);\nSELECT * FROM create_hypertable('hyper_meta', 'time', 'device', 2);\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             2 | public      | hyper_meta | t\n\nINSERT INTO hyper_meta VALUES\n       ('2018-02-19 13:01', 1, 'device_1'),\n       ('2018-02-19 13:02', 3, 'device_3');\nSET enable_partitionwise_join = 'off';\n:PREFIX\nSELECT h.time, h.device, h.temp, hm.info\nFROM hyper h, hyper_meta hm\nWHERE h.device = hm.device;\n--- QUERY PLAN ---\n Merge Join\n   Output: h.\"time\", h.device, h.temp, hm.info\n   Merge Cond: (hm.device = h.device)\n   ->  Merge Append\n         Sort Key: hm.device\n         ->  Index Scan using _hyper_2_5_chunk_hyper_meta_device_time_idx on _timescaledb_internal._hyper_2_5_chunk hm_1\n               Output: hm_1.info, hm_1.device\n         ->  Index Scan using _hyper_2_6_chunk_hyper_meta_device_time_idx on _timescaledb_internal._hyper_2_6_chunk hm_2\n               Output: hm_2.info, hm_2.device\n   ->  Materialize\n         Output: h.\"time\", h.device, h.temp\n         ->  Merge Append\n               Sort Key: h.device\n               ->  Index Scan using _hyper_1_1_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_1_chunk h_1\n                     Output: h_1.\"time\", h_1.device, h_1.temp\n               ->  Index Scan using _hyper_1_2_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_2_chunk h_2\n                     Output: h_2.\"time\", h_2.device, h_2.temp\n               ->  Index Scan using _hyper_1_3_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_3_chunk h_3\n                     Output: h_3.\"time\", h_3.device, h_3.temp\n               ->  Index Scan using _hyper_1_4_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_4_chunk h_4\n                     Output: h_4.\"time\", h_4.device, h_4.temp\n\n:PREFIX\nSELECT pg2.time, pg2.device, pg2.temp, pg1.temp\nFROM pg2dim pg2, pg1dim pg1\nWHERE pg2.device = pg1.device;\n--- QUERY PLAN ---\n Merge Join\n   Output: pg2.\"time\", pg2.device, pg2.temp, pg1.temp\n   Merge Cond: (pg1.device = pg2.device)\n   ->  Sort\n         Output: pg1.temp, pg1.device\n         Sort Key: pg1.device\n         ->  Append\n               ->  Seq Scan on public.pg1dim_h1 pg1_1\n                     Output: pg1_1.temp, pg1_1.device\n               ->  Seq Scan on public.pg1dim_h2 pg1_2\n                     Output: pg1_2.temp, pg1_2.device\n   ->  Sort\n         Output: pg2.\"time\", pg2.device, pg2.temp\n         Sort Key: pg2.device\n         ->  Append\n               ->  Seq Scan on public.pg2dim_h1_t1 pg2_1\n                     Output: pg2_1.\"time\", pg2_1.device, pg2_1.temp\n               ->  Seq Scan on public.pg2dim_h1_t2 pg2_2\n                     Output: pg2_2.\"time\", pg2_2.device, pg2_2.temp\n               ->  Seq Scan on public.pg2dim_h2_t1 pg2_3\n                     Output: pg2_3.\"time\", pg2_3.device, pg2_3.temp\n               ->  Seq Scan on public.pg2dim_h2_t2 pg2_4\n                     Output: pg2_4.\"time\", pg2_4.device, pg2_4.temp\n\nSET enable_partitionwise_join = 'on';\n:PREFIX\nSELECT h.time, h.device, h.temp, hm.info\nFROM hyper h, hyper_meta hm\nWHERE h.device = hm.device;\n--- QUERY PLAN ---\n Merge Join\n   Output: h.\"time\", h.device, h.temp, hm.info\n   Merge Cond: (hm.device = h.device)\n   ->  Merge Append\n         Sort Key: hm.device\n         ->  Index Scan using _hyper_2_5_chunk_hyper_meta_device_time_idx on _timescaledb_internal._hyper_2_5_chunk hm_1\n               Output: hm_1.info, hm_1.device\n         ->  Index Scan using _hyper_2_6_chunk_hyper_meta_device_time_idx on _timescaledb_internal._hyper_2_6_chunk hm_2\n               Output: hm_2.info, hm_2.device\n   ->  Materialize\n         Output: h.\"time\", h.device, h.temp\n         ->  Merge Append\n               Sort Key: h.device\n               ->  Index Scan using _hyper_1_1_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_1_chunk h_1\n                     Output: h_1.\"time\", h_1.device, h_1.temp\n               ->  Index Scan using _hyper_1_2_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_2_chunk h_2\n                     Output: h_2.\"time\", h_2.device, h_2.temp\n               ->  Index Scan using _hyper_1_3_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_3_chunk h_3\n                     Output: h_3.\"time\", h_3.device, h_3.temp\n               ->  Index Scan using _hyper_1_4_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_4_chunk h_4\n                     Output: h_4.\"time\", h_4.device, h_4.temp\n\n:PREFIX\nSELECT pg2.time, pg2.device, pg2.temp, pg1.temp\nFROM pg2dim pg2, pg1dim pg1\nWHERE pg2.device = pg1.device;\n--- QUERY PLAN ---\n Append\n   ->  Merge Join\n         Output: pg2_2.\"time\", pg2_2.device, pg2_2.temp, pg1_1.temp\n         Merge Cond: (pg1_1.device = pg2_2.device)\n         ->  Sort\n               Output: pg1_1.temp, pg1_1.device\n               Sort Key: pg1_1.device\n               ->  Seq Scan on public.pg1dim_h1 pg1_1\n                     Output: pg1_1.temp, pg1_1.device\n         ->  Sort\n               Output: pg2_2.\"time\", pg2_2.device, pg2_2.temp\n               Sort Key: pg2_2.device\n               ->  Append\n                     ->  Seq Scan on public.pg2dim_h1_t1 pg2_2\n                           Output: pg2_2.\"time\", pg2_2.device, pg2_2.temp\n                     ->  Seq Scan on public.pg2dim_h1_t2 pg2_3\n                           Output: pg2_3.\"time\", pg2_3.device, pg2_3.temp\n   ->  Merge Join\n         Output: pg2_5.\"time\", pg2_5.device, pg2_5.temp, pg1_2.temp\n         Merge Cond: (pg1_2.device = pg2_5.device)\n         ->  Sort\n               Output: pg1_2.temp, pg1_2.device\n               Sort Key: pg1_2.device\n               ->  Seq Scan on public.pg1dim_h2 pg1_2\n                     Output: pg1_2.temp, pg1_2.device\n         ->  Sort\n               Output: pg2_5.\"time\", pg2_5.device, pg2_5.temp\n               Sort Key: pg2_5.device\n               ->  Append\n                     ->  Seq Scan on public.pg2dim_h2_t1 pg2_5\n                           Output: pg2_5.\"time\", pg2_5.device, pg2_5.temp\n                     ->  Seq Scan on public.pg2dim_h2_t2 pg2_6\n                           Output: pg2_6.\"time\", pg2_6.device, pg2_6.temp\n\n-- Test hypertable with time partitioning function\nCREATE OR REPLACE FUNCTION time_func(unixtime float8)\n    RETURNS TIMESTAMPTZ LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nDECLARE\n    retval TIMESTAMPTZ;\nBEGIN\n    retval := to_timestamp(unixtime);\n    RETURN retval;\nEND\n$BODY$;\nCREATE TABLE hyper_timepart (time float8, device int, temp float);\nSELECT * FROM create_hypertable('hyper_timepart', 'time', 'device', 2, time_partitioning_func => 'time_func');\n hypertable_id | schema_name |   table_name   | created \n---------------+-------------+----------------+---------\n             3 | public      | hyper_timepart | t\n\n-- Planner won't pick push-down aggs on table with time function\n-- unless a certain amount of data\nSELECT setseed(1);\n setseed \n---------\n \n\nINSERT INTO hyper_timepart\nSELECT x, ceil(random() * 8), random() * 20\nFROM generate_series(0,5000-1) AS x;\n-- All partition keys covered (full partitionwise)\nSET timescaledb.enable_chunkwise_aggregation = 'off';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n--- QUERY PLAN ---\n Limit\n   Output: hyper_timepart.\"time\", hyper_timepart.device, (avg(hyper_timepart.temp))\n   ->  Sort\n         Output: hyper_timepart.\"time\", hyper_timepart.device, (avg(hyper_timepart.temp))\n         Sort Key: hyper_timepart.\"time\", hyper_timepart.device\n         ->  HashAggregate\n               Output: hyper_timepart.\"time\", hyper_timepart.device, avg(hyper_timepart.temp)\n               Group Key: hyper_timepart.\"time\", hyper_timepart.device\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_3_7_chunk\n                           Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_3_8_chunk\n                           Output: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp\n\n:PREFIX\nSELECT time_func(time), device, avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n--- QUERY PLAN ---\n Limit\n   Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, (avg(hyper_timepart.temp))\n   ->  GroupAggregate\n         Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, avg(hyper_timepart.temp)\n         Group Key: (time_func(hyper_timepart.\"time\")), hyper_timepart.device\n         ->  Incremental Sort\n               Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, hyper_timepart.temp\n               Sort Key: (time_func(hyper_timepart.\"time\")), hyper_timepart.device\n               Presorted Key: (time_func(hyper_timepart.\"time\"))\n               ->  Result\n                     Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, hyper_timepart.temp\n                     ->  Merge Append\n                           Sort Key: (time_func(hyper_timepart.\"time\"))\n                           ->  Index Scan Backward using _hyper_3_7_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_7_chunk\n                                 Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp, time_func(_hyper_3_7_chunk.\"time\")\n                           ->  Index Scan Backward using _hyper_3_8_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_8_chunk\n                                 Output: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp, time_func(_hyper_3_8_chunk.\"time\")\n\n-- Grouping on original time column should be pushed-down\nSET timescaledb.enable_chunkwise_aggregation = 'on';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n--- QUERY PLAN ---\n Limit\n   Output: hyper_timepart.\"time\", hyper_timepart.device, (avg(hyper_timepart.temp))\n   ->  Sort\n         Output: hyper_timepart.\"time\", hyper_timepart.device, (avg(hyper_timepart.temp))\n         Sort Key: hyper_timepart.\"time\", hyper_timepart.device\n         ->  HashAggregate\n               Output: hyper_timepart.\"time\", hyper_timepart.device, avg(hyper_timepart.temp)\n               Group Key: hyper_timepart.\"time\", hyper_timepart.device\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_3_7_chunk\n                           Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_3_8_chunk\n                           Output: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp\n\n-- Applying the time partitioning function should also allow push-down\n-- on open dimensions\n:PREFIX\nSELECT time_func(time), device, avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n--- QUERY PLAN ---\n Limit\n   Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, (avg(hyper_timepart.temp))\n   ->  GroupAggregate\n         Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, avg(hyper_timepart.temp)\n         Group Key: (time_func(hyper_timepart.\"time\")), hyper_timepart.device\n         ->  Incremental Sort\n               Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, hyper_timepart.temp\n               Sort Key: (time_func(hyper_timepart.\"time\")), hyper_timepart.device\n               Presorted Key: (time_func(hyper_timepart.\"time\"))\n               ->  Result\n                     Output: (time_func(hyper_timepart.\"time\")), hyper_timepart.device, hyper_timepart.temp\n                     ->  Merge Append\n                           Sort Key: (time_func(hyper_timepart.\"time\"))\n                           ->  Index Scan Backward using _hyper_3_7_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_7_chunk\n                                 Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp, time_func(_hyper_3_7_chunk.\"time\")\n                           ->  Index Scan Backward using _hyper_3_8_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_8_chunk\n                                 Output: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp, time_func(_hyper_3_8_chunk.\"time\")\n\n-- Partial aggregation pushdown is currently not supported for this query by\n-- the TSDB pushdown code since a projection is used in the path.\n:PREFIX\nSELECT time_func(time), _timescaledb_functions.get_partition_hash(device), avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n--- QUERY PLAN ---\n Limit\n   Output: (time_func(hyper_timepart.\"time\")), (_timescaledb_functions.get_partition_hash(hyper_timepart.device)), (avg(hyper_timepart.temp))\n   ->  GroupAggregate\n         Output: (time_func(hyper_timepart.\"time\")), (_timescaledb_functions.get_partition_hash(hyper_timepart.device)), avg(hyper_timepart.temp)\n         Group Key: (time_func(hyper_timepart.\"time\")), (_timescaledb_functions.get_partition_hash(hyper_timepart.device))\n         ->  Incremental Sort\n               Output: (time_func(hyper_timepart.\"time\")), (_timescaledb_functions.get_partition_hash(hyper_timepart.device)), hyper_timepart.temp\n               Sort Key: (time_func(hyper_timepart.\"time\")), (_timescaledb_functions.get_partition_hash(hyper_timepart.device))\n               Presorted Key: (time_func(hyper_timepart.\"time\"))\n               ->  Result\n                     Output: (time_func(hyper_timepart.\"time\")), _timescaledb_functions.get_partition_hash(hyper_timepart.device), hyper_timepart.temp\n                     ->  Merge Append\n                           Sort Key: (time_func(hyper_timepart.\"time\"))\n                           ->  Index Scan Backward using _hyper_3_7_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_7_chunk\n                                 Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp, time_func(_hyper_3_7_chunk.\"time\")\n                           ->  Index Scan Backward using _hyper_3_8_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_8_chunk\n                                 Output: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp, time_func(_hyper_3_8_chunk.\"time\")\n\n-- Test removal of redundant group key optimization in PG16\n-- All lower versions include the redundant key on device column\n:PREFIX\nSELECT device, avg(temp)\nFROM hyper_timepart\nWHERE device = 1\nGROUP BY 1\nLIMIT 10;\n--- QUERY PLAN ---\n Limit\n   Output: _hyper_3_8_chunk.device, (avg(_hyper_3_8_chunk.temp))\n   ->  GroupAggregate\n         Output: _hyper_3_8_chunk.device, avg(_hyper_3_8_chunk.temp)\n         ->  Index Scan using _hyper_3_8_chunk_hyper_timepart_device_expr_idx on _timescaledb_internal._hyper_3_8_chunk\n               Output: _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp\n               Index Cond: (_hyper_3_8_chunk.device = 1)\n\n"
  },
  {
    "path": "test/expected/partitionwise.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\set PREFIX 'EXPLAIN (VERBOSE, COSTS OFF)'\n-- Create a two dimensional hypertable\nCREATE TABLE hyper (time timestamptz, device int, temp float);\nSELECT * FROM create_hypertable('hyper', 'time', 'device', 2);\nNOTICE:  adding not-null constraint to column \"time\"\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             1 | public      | hyper      | t\n(1 row)\n\n-- Create a similar PostgreSQL partitioned table\nCREATE TABLE pg2dim (time timestamptz, device int, temp float) PARTITION BY HASH (device);\nCREATE TABLE pg2dim_h1 PARTITION OF pg2dim FOR VALUES WITH (MODULUS 2, REMAINDER 0) PARTITION BY RANGE(time);\nCREATE TABLE pg2dim_h2 PARTITION OF pg2dim FOR VALUES WITH (MODULUS 2, REMAINDER 1) PARTITION BY RANGE(time);\nCREATE TABLE pg2dim_h1_t1 PARTITION OF pg2dim_h1 FOR VALUES FROM ('2018-01-01 00:00') TO ('2018-09-01 00:00');\nCREATE TABLE pg2dim_h1_t2 PARTITION OF pg2dim_h1 FOR VALUES FROM ('2018-09-01 00:00') TO ('2018-12-01 00:00');\nCREATE TABLE pg2dim_h2_t1 PARTITION OF pg2dim_h2 FOR VALUES FROM ('2018-01-01 00:00') TO ('2018-09-01 00:00');\nCREATE TABLE pg2dim_h2_t2 PARTITION OF pg2dim_h2 FOR VALUES FROM ('2018-09-01 00:00') TO ('2018-12-01 00:00');\n-- Create a 1-dimensional partitioned table for comparison\nCREATE TABLE pg1dim (time timestamptz, device int, temp float) PARTITION BY HASH (device);\nCREATE TABLE pg1dim_h1 PARTITION OF pg1dim FOR VALUES WITH (MODULUS 2, REMAINDER 0);\nCREATE TABLE pg1dim_h2 PARTITION OF pg1dim FOR VALUES WITH (MODULUS 2, REMAINDER 1);\nINSERT INTO hyper VALUES\n       ('2018-02-19 13:01', 1, 2.3),\n       ('2018-02-19 13:02', 3, 3.1),\n       ('2018-10-19 13:01', 1, 7.6),\n       ('2018-10-19 13:02', 3, 9.0);\nINSERT INTO pg2dim VALUES\n       ('2018-02-19 13:01', 1, 2.3),\n       ('2018-02-19 13:02', 3, 3.1),\n       ('2018-10-19 13:01', 1, 7.6),\n       ('2018-10-19 13:02', 3, 9.0);\nINSERT INTO pg1dim VALUES\n       ('2018-02-19 13:01', 1, 2.3),\n       ('2018-02-19 13:02', 3, 3.1),\n       ('2018-10-19 13:01', 1, 7.6),\n       ('2018-10-19 13:02', 3, 9.0);\nSELECT * FROM test.show_subtables('hyper');\n                 Child                  | Tablespace \n----------------------------------------+------------\n _timescaledb_internal._hyper_1_1_chunk | \n _timescaledb_internal._hyper_1_2_chunk | \n _timescaledb_internal._hyper_1_3_chunk | \n _timescaledb_internal._hyper_1_4_chunk | \n(4 rows)\n\nSELECT * FROM pg2dim_h1_t1;\n             time             | device | temp \n------------------------------+--------+------\n Mon Feb 19 13:01:00 2018 PST |      1 |  2.3\n(1 row)\n\nSELECT * FROM pg2dim_h1_t2;\n             time             | device | temp \n------------------------------+--------+------\n Fri Oct 19 13:01:00 2018 PDT |      1 |  7.6\n(1 row)\n\nSELECT * FROM pg2dim_h2_t1;\n             time             | device | temp \n------------------------------+--------+------\n Mon Feb 19 13:02:00 2018 PST |      3 |  3.1\n(1 row)\n\nSELECT * FROM pg2dim_h2_t2;\n             time             | device | temp \n------------------------------+--------+------\n Fri Oct 19 13:02:00 2018 PDT |      3 |    9\n(1 row)\n\n-- Compare partitionwise aggreate enabled/disabled. First run queries\n-- on PG partitioned tables for reference.\n-- All partition keys covered by GROUP BY\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT device, avg(temp)\nFROM pg1dim\nGROUP BY 1\nORDER BY 1;\n                         QUERY PLAN                         \n------------------------------------------------------------\n Sort\n   Output: pg1dim.device, (avg(pg1dim.temp))\n   Sort Key: pg1dim.device\n   ->  HashAggregate\n         Output: pg1dim.device, avg(pg1dim.temp)\n         Group Key: pg1dim.device\n         ->  Append\n               ->  Seq Scan on public.pg1dim_h1 pg1dim_1\n                     Output: pg1dim_1.device, pg1dim_1.temp\n               ->  Seq Scan on public.pg1dim_h2 pg1dim_2\n                     Output: pg1dim_2.device, pg1dim_2.temp\n(11 rows)\n\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT device, avg(temp)\nFROM pg1dim\nGROUP BY 1\nORDER BY 1;\n                         QUERY PLAN                         \n------------------------------------------------------------\n Sort\n   Output: pg1dim.device, (avg(pg1dim.temp))\n   Sort Key: pg1dim.device\n   ->  Append\n         ->  HashAggregate\n               Output: pg1dim.device, avg(pg1dim.temp)\n               Group Key: pg1dim.device\n               ->  Seq Scan on public.pg1dim_h1 pg1dim\n                     Output: pg1dim.device, pg1dim.temp\n         ->  HashAggregate\n               Output: pg1dim_1.device, avg(pg1dim_1.temp)\n               Group Key: pg1dim_1.device\n               ->  Seq Scan on public.pg1dim_h2 pg1dim_1\n                     Output: pg1dim_1.device, pg1dim_1.temp\n(14 rows)\n\n-- All partition keys not covered by GROUP BY (partial partitionwise)\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT device, avg(temp)\nFROM pg2dim\nGROUP BY 1\nORDER BY 1;\n                         QUERY PLAN                         \n------------------------------------------------------------\n Sort\n   Output: pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: pg2dim.device\n   ->  HashAggregate\n         Output: pg2dim.device, avg(pg2dim.temp)\n         Group Key: pg2dim.device\n         ->  Append\n               ->  Seq Scan on public.pg2dim_h1_t1 pg2dim_1\n                     Output: pg2dim_1.device, pg2dim_1.temp\n               ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_2\n                     Output: pg2dim_2.device, pg2dim_2.temp\n               ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_3\n                     Output: pg2dim_3.device, pg2dim_3.temp\n               ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_4\n                     Output: pg2dim_4.device, pg2dim_4.temp\n(15 rows)\n\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT device, avg(temp)\nFROM pg2dim\nGROUP BY 1\nORDER BY 1;\n                               QUERY PLAN                                \n-------------------------------------------------------------------------\n Sort\n   Output: pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: pg2dim.device\n   ->  Finalize HashAggregate\n         Output: pg2dim.device, avg(pg2dim.temp)\n         Group Key: pg2dim.device\n         ->  Append\n               ->  Partial HashAggregate\n                     Output: pg2dim.device, PARTIAL avg(pg2dim.temp)\n                     Group Key: pg2dim.device\n                     ->  Seq Scan on public.pg2dim_h1_t1 pg2dim\n                           Output: pg2dim.device, pg2dim.temp\n               ->  Partial HashAggregate\n                     Output: pg2dim_1.device, PARTIAL avg(pg2dim_1.temp)\n                     Group Key: pg2dim_1.device\n                     ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_1\n                           Output: pg2dim_1.device, pg2dim_1.temp\n               ->  Partial HashAggregate\n                     Output: pg2dim_2.device, PARTIAL avg(pg2dim_2.temp)\n                     Group Key: pg2dim_2.device\n                     ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_2\n                           Output: pg2dim_2.device, pg2dim_2.temp\n               ->  Partial HashAggregate\n                     Output: pg2dim_3.device, PARTIAL avg(pg2dim_3.temp)\n                     Group Key: pg2dim_3.device\n                     ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_3\n                           Output: pg2dim_3.device, pg2dim_3.temp\n(27 rows)\n\n-- All partition keys covered by GROUP BY (full partitionwise)\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM pg2dim\nGROUP BY 1, 2\nORDER BY 1, 2;\n                                 QUERY PLAN                                  \n-----------------------------------------------------------------------------\n Sort\n   Output: pg2dim.\"time\", pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: pg2dim.\"time\", pg2dim.device\n   ->  HashAggregate\n         Output: pg2dim.\"time\", pg2dim.device, avg(pg2dim.temp)\n         Group Key: pg2dim.\"time\", pg2dim.device\n         ->  Append\n               ->  Seq Scan on public.pg2dim_h1_t1 pg2dim_1\n                     Output: pg2dim_1.\"time\", pg2dim_1.device, pg2dim_1.temp\n               ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_2\n                     Output: pg2dim_2.\"time\", pg2dim_2.device, pg2dim_2.temp\n               ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_3\n                     Output: pg2dim_3.\"time\", pg2dim_3.device, pg2dim_3.temp\n               ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_4\n                     Output: pg2dim_4.\"time\", pg2dim_4.device, pg2dim_4.temp\n(15 rows)\n\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM pg2dim\nGROUP BY 1, 2\nORDER BY 1, 2;\n                                 QUERY PLAN                                  \n-----------------------------------------------------------------------------\n Sort\n   Output: pg2dim.\"time\", pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: pg2dim.\"time\", pg2dim.device\n   ->  Append\n         ->  HashAggregate\n               Output: pg2dim.\"time\", pg2dim.device, avg(pg2dim.temp)\n               Group Key: pg2dim.\"time\", pg2dim.device\n               ->  Seq Scan on public.pg2dim_h1_t1 pg2dim\n                     Output: pg2dim.\"time\", pg2dim.device, pg2dim.temp\n         ->  HashAggregate\n               Output: pg2dim_1.\"time\", pg2dim_1.device, avg(pg2dim_1.temp)\n               Group Key: pg2dim_1.\"time\", pg2dim_1.device\n               ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_1\n                     Output: pg2dim_1.\"time\", pg2dim_1.device, pg2dim_1.temp\n         ->  HashAggregate\n               Output: pg2dim_2.\"time\", pg2dim_2.device, avg(pg2dim_2.temp)\n               Group Key: pg2dim_2.\"time\", pg2dim_2.device\n               ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_2\n                     Output: pg2dim_2.\"time\", pg2dim_2.device, pg2dim_2.temp\n         ->  HashAggregate\n               Output: pg2dim_3.\"time\", pg2dim_3.device, avg(pg2dim_3.temp)\n               Group Key: pg2dim_3.\"time\", pg2dim_3.device\n               ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_3\n                     Output: pg2dim_3.\"time\", pg2dim_3.device, pg2dim_3.temp\n(24 rows)\n\n-- All partition keys not covered by GROUP BY because of date_trunc\n-- expression on time (partial partitionwise)\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT date_trunc('month', time), device, avg(temp)\nFROM pg2dim\nGROUP BY 1, 2\nORDER BY 1, 2;\n                                               QUERY PLAN                                               \n--------------------------------------------------------------------------------------------------------\n Sort\n   Output: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device\n   ->  HashAggregate\n         Output: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device, avg(pg2dim.temp)\n         Group Key: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device\n         ->  Append\n               ->  Seq Scan on public.pg2dim_h1_t1 pg2dim_1\n                     Output: date_trunc('month'::text, pg2dim_1.\"time\"), pg2dim_1.device, pg2dim_1.temp\n               ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_2\n                     Output: date_trunc('month'::text, pg2dim_2.\"time\"), pg2dim_2.device, pg2dim_2.temp\n               ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_3\n                     Output: date_trunc('month'::text, pg2dim_3.\"time\"), pg2dim_3.device, pg2dim_3.temp\n               ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_4\n                     Output: date_trunc('month'::text, pg2dim_4.\"time\"), pg2dim_4.device, pg2dim_4.temp\n(15 rows)\n\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT date_trunc('month', time), device, avg(temp)\nFROM pg2dim\nGROUP BY 1, 2\nORDER BY 1, 2;\n                                                      QUERY PLAN                                                       \n-----------------------------------------------------------------------------------------------------------------------\n Sort\n   Output: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device, (avg(pg2dim.temp))\n   Sort Key: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device\n   ->  Finalize HashAggregate\n         Output: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device, avg(pg2dim.temp)\n         Group Key: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device\n         ->  Append\n               ->  Partial HashAggregate\n                     Output: (date_trunc('month'::text, pg2dim.\"time\")), pg2dim.device, PARTIAL avg(pg2dim.temp)\n                     Group Key: date_trunc('month'::text, pg2dim.\"time\"), pg2dim.device\n                     ->  Seq Scan on public.pg2dim_h1_t1 pg2dim\n                           Output: date_trunc('month'::text, pg2dim.\"time\"), pg2dim.device, pg2dim.temp\n               ->  Partial HashAggregate\n                     Output: (date_trunc('month'::text, pg2dim_1.\"time\")), pg2dim_1.device, PARTIAL avg(pg2dim_1.temp)\n                     Group Key: date_trunc('month'::text, pg2dim_1.\"time\"), pg2dim_1.device\n                     ->  Seq Scan on public.pg2dim_h1_t2 pg2dim_1\n                           Output: date_trunc('month'::text, pg2dim_1.\"time\"), pg2dim_1.device, pg2dim_1.temp\n               ->  Partial HashAggregate\n                     Output: (date_trunc('month'::text, pg2dim_2.\"time\")), pg2dim_2.device, PARTIAL avg(pg2dim_2.temp)\n                     Group Key: date_trunc('month'::text, pg2dim_2.\"time\"), pg2dim_2.device\n                     ->  Seq Scan on public.pg2dim_h2_t1 pg2dim_2\n                           Output: date_trunc('month'::text, pg2dim_2.\"time\"), pg2dim_2.device, pg2dim_2.temp\n               ->  Partial HashAggregate\n                     Output: (date_trunc('month'::text, pg2dim_3.\"time\")), pg2dim_3.device, PARTIAL avg(pg2dim_3.temp)\n                     Group Key: date_trunc('month'::text, pg2dim_3.\"time\"), pg2dim_3.device\n                     ->  Seq Scan on public.pg2dim_h2_t2 pg2dim_3\n                           Output: date_trunc('month'::text, pg2dim_3.\"time\"), pg2dim_3.device, pg2dim_3.temp\n(27 rows)\n\n-- Now run on hypertable\n-- All partition keys not covered by GROUP BY (partial partitionwise)\nSET timescaledb.enable_chunkwise_aggregation = 'off';\n:PREFIX\nSELECT device, avg(temp)\nFROM hyper\nGROUP BY 1\nORDER BY 1;\n                                 QUERY PLAN                                 \n----------------------------------------------------------------------------\n Sort\n   Output: _hyper_1_1_chunk.device, (avg(_hyper_1_1_chunk.temp))\n   Sort Key: _hyper_1_1_chunk.device\n   ->  HashAggregate\n         Output: _hyper_1_1_chunk.device, avg(_hyper_1_1_chunk.temp)\n         Group Key: _hyper_1_1_chunk.device\n         ->  Append\n               ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                     Output: _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                     Output: _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                     Output: _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                     Output: _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n(15 rows)\n\nSET timescaledb.enable_chunkwise_aggregation = 'on';\n:PREFIX\nSELECT device, avg(temp)\nFROM hyper\nGROUP BY 1\nORDER BY 1;\n                                       QUERY PLAN                                        \n-----------------------------------------------------------------------------------------\n Sort\n   Output: _hyper_1_1_chunk.device, (avg(_hyper_1_1_chunk.temp))\n   Sort Key: _hyper_1_1_chunk.device\n   ->  Finalize HashAggregate\n         Output: _hyper_1_1_chunk.device, avg(_hyper_1_1_chunk.temp)\n         Group Key: _hyper_1_1_chunk.device\n         ->  Append\n               ->  Partial HashAggregate\n                     Output: _hyper_1_1_chunk.device, PARTIAL avg(_hyper_1_1_chunk.temp)\n                     Group Key: _hyper_1_1_chunk.device\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                           Output: _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n               ->  Partial HashAggregate\n                     Output: _hyper_1_2_chunk.device, PARTIAL avg(_hyper_1_2_chunk.temp)\n                     Group Key: _hyper_1_2_chunk.device\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                           Output: _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n               ->  Partial HashAggregate\n                     Output: _hyper_1_3_chunk.device, PARTIAL avg(_hyper_1_3_chunk.temp)\n                     Group Key: _hyper_1_3_chunk.device\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                           Output: _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n               ->  Partial HashAggregate\n                     Output: _hyper_1_4_chunk.device, PARTIAL avg(_hyper_1_4_chunk.temp)\n                     Group Key: _hyper_1_4_chunk.device\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                           Output: _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n(27 rows)\n\n-- All partition keys covered (full partitionwise)\nSET timescaledb.enable_chunkwise_aggregation = 'off';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n                                             QUERY PLAN                                              \n-----------------------------------------------------------------------------------------------------\n Sort\n   Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, (avg(_hyper_1_1_chunk.temp))\n   Sort Key: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device\n   ->  HashAggregate\n         Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, avg(_hyper_1_1_chunk.temp)\n         Group Key: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device\n         ->  Append\n               ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                     Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                     Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                     Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n               ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                     Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n(15 rows)\n\nSET timescaledb.enable_chunkwise_aggregation = 'on';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n                                                               QUERY PLAN                                                                \n-----------------------------------------------------------------------------------------------------------------------------------------\n Finalize GroupAggregate\n   Output: hyper.\"time\", hyper.device, avg(hyper.temp)\n   Group Key: hyper.\"time\", hyper.device\n   ->  Sort\n         Output: hyper.\"time\", hyper.device, (PARTIAL avg(hyper.temp))\n         Sort Key: hyper.\"time\", hyper.device\n         ->  Custom Scan (ChunkAppend) on public.hyper\n               Output: hyper.\"time\", hyper.device, (PARTIAL avg(hyper.temp))\n               Order: hyper.\"time\"\n               Startup Exclusion: false\n               Runtime Exclusion: false\n               ->  Merge Append\n                     Sort Key: _hyper_1_1_chunk.\"time\"\n                     ->  Partial GroupAggregate\n                           Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, PARTIAL avg(_hyper_1_1_chunk.temp)\n                           Group Key: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device\n                           ->  Sort\n                                 Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n                                 Sort Key: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device\n                                 ->  Index Scan Backward using _hyper_1_1_chunk_hyper_time_idx on _timescaledb_internal._hyper_1_1_chunk\n                                       Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n                     ->  Partial GroupAggregate\n                           Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, PARTIAL avg(_hyper_1_2_chunk.temp)\n                           Group Key: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device\n                           ->  Sort\n                                 Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n                                 Sort Key: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device\n                                 ->  Index Scan Backward using _hyper_1_2_chunk_hyper_time_idx on _timescaledb_internal._hyper_1_2_chunk\n                                       Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n               ->  Merge Append\n                     Sort Key: _hyper_1_3_chunk.\"time\"\n                     ->  Partial GroupAggregate\n                           Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, PARTIAL avg(_hyper_1_3_chunk.temp)\n                           Group Key: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device\n                           ->  Sort\n                                 Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n                                 Sort Key: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device\n                                 ->  Index Scan Backward using _hyper_1_3_chunk_hyper_time_idx on _timescaledb_internal._hyper_1_3_chunk\n                                       Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n                     ->  Partial GroupAggregate\n                           Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, PARTIAL avg(_hyper_1_4_chunk.temp)\n                           Group Key: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device\n                           ->  Sort\n                                 Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n                                 Sort Key: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device\n                                 ->  Index Scan Backward using _hyper_1_4_chunk_hyper_time_idx on _timescaledb_internal._hyper_1_4_chunk\n                                       Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n(47 rows)\n\n-- Partial aggregation since date_trunc(time) is not a partition key\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT date_trunc('month', time), device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n                                                        QUERY PLAN                                                         \n---------------------------------------------------------------------------------------------------------------------------\n Sort\n   Output: (date_trunc('month'::text, _hyper_1_1_chunk.\"time\")), _hyper_1_1_chunk.device, (avg(_hyper_1_1_chunk.temp))\n   Sort Key: (date_trunc('month'::text, _hyper_1_1_chunk.\"time\")), _hyper_1_1_chunk.device\n   ->  HashAggregate\n         Output: (date_trunc('month'::text, _hyper_1_1_chunk.\"time\")), _hyper_1_1_chunk.device, avg(_hyper_1_1_chunk.temp)\n         Group Key: date_trunc('month'::text, _hyper_1_1_chunk.\"time\"), _hyper_1_1_chunk.device\n         ->  Result\n               Output: date_trunc('month'::text, _hyper_1_1_chunk.\"time\"), _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                           Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                           Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                           Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                           Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n(17 rows)\n\n-- Partial aggregation pushdown is currently not supported for this query by\n-- the TSDB pushdown code since a projection is used in the path.\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT date_trunc('month', time), device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n                                                        QUERY PLAN                                                         \n---------------------------------------------------------------------------------------------------------------------------\n Sort\n   Output: (date_trunc('month'::text, _hyper_1_1_chunk.\"time\")), _hyper_1_1_chunk.device, (avg(_hyper_1_1_chunk.temp))\n   Sort Key: (date_trunc('month'::text, _hyper_1_1_chunk.\"time\")), _hyper_1_1_chunk.device\n   ->  HashAggregate\n         Output: (date_trunc('month'::text, _hyper_1_1_chunk.\"time\")), _hyper_1_1_chunk.device, avg(_hyper_1_1_chunk.temp)\n         Group Key: date_trunc('month'::text, _hyper_1_1_chunk.\"time\"), _hyper_1_1_chunk.device\n         ->  Result\n               Output: date_trunc('month'::text, _hyper_1_1_chunk.\"time\"), _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                           Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                           Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                           Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                           Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n(17 rows)\n\n-- Also test time_bucket\nSET timescaledb.enable_chunkwise_aggregation = 'off';\n:PREFIX\nSELECT time_bucket('1 month', time), device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n                                                            QUERY PLAN                                                            \n----------------------------------------------------------------------------------------------------------------------------------\n Sort\n   Output: (time_bucket('@ 1 mon'::interval, _hyper_1_1_chunk.\"time\")), _hyper_1_1_chunk.device, (avg(_hyper_1_1_chunk.temp))\n   Sort Key: (time_bucket('@ 1 mon'::interval, _hyper_1_1_chunk.\"time\")), _hyper_1_1_chunk.device\n   ->  HashAggregate\n         Output: (time_bucket('@ 1 mon'::interval, _hyper_1_1_chunk.\"time\")), _hyper_1_1_chunk.device, avg(_hyper_1_1_chunk.temp)\n         Group Key: time_bucket('@ 1 mon'::interval, _hyper_1_1_chunk.\"time\"), _hyper_1_1_chunk.device\n         ->  Result\n               Output: time_bucket('@ 1 mon'::interval, _hyper_1_1_chunk.\"time\"), _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                           Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                           Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                           Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                           Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n(17 rows)\n\nSET timescaledb.enable_chunkwise_aggregation = 'on';\n:PREFIX\nSELECT time_bucket('1 month', time), device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n                                                            QUERY PLAN                                                            \n----------------------------------------------------------------------------------------------------------------------------------\n Sort\n   Output: (time_bucket('@ 1 mon'::interval, _hyper_1_1_chunk.\"time\")), _hyper_1_1_chunk.device, (avg(_hyper_1_1_chunk.temp))\n   Sort Key: (time_bucket('@ 1 mon'::interval, _hyper_1_1_chunk.\"time\")), _hyper_1_1_chunk.device\n   ->  HashAggregate\n         Output: (time_bucket('@ 1 mon'::interval, _hyper_1_1_chunk.\"time\")), _hyper_1_1_chunk.device, avg(_hyper_1_1_chunk.temp)\n         Group Key: time_bucket('@ 1 mon'::interval, _hyper_1_1_chunk.\"time\"), _hyper_1_1_chunk.device\n         ->  Result\n               Output: time_bucket('@ 1 mon'::interval, _hyper_1_1_chunk.\"time\"), _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                           Output: _hyper_1_1_chunk.\"time\", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                           Output: _hyper_1_2_chunk.\"time\", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                           Output: _hyper_1_3_chunk.\"time\", _hyper_1_3_chunk.device, _hyper_1_3_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                           Output: _hyper_1_4_chunk.\"time\", _hyper_1_4_chunk.device, _hyper_1_4_chunk.temp\n(17 rows)\n\n-- Test partitionwise joins, mostly to see that we do not break\n-- anything\nCREATE TABLE hyper_meta (time timestamptz, device int, info text);\nSELECT * FROM create_hypertable('hyper_meta', 'time', 'device', 2);\nNOTICE:  adding not-null constraint to column \"time\"\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             2 | public      | hyper_meta | t\n(1 row)\n\nINSERT INTO hyper_meta VALUES\n       ('2018-02-19 13:01', 1, 'device_1'),\n       ('2018-02-19 13:02', 3, 'device_3');\nSET enable_partitionwise_join = 'off';\n:PREFIX\nSELECT h.time, h.device, h.temp, hm.info\nFROM hyper h, hyper_meta hm\nWHERE h.device = hm.device;\n                                                       QUERY PLAN                                                        \n-------------------------------------------------------------------------------------------------------------------------\n Merge Join\n   Output: h_1.\"time\", h_1.device, h_1.temp, hm_1.info\n   Merge Cond: (hm_1.device = h_1.device)\n   ->  Merge Append\n         Sort Key: hm_1.device\n         ->  Index Scan using _hyper_2_5_chunk_hyper_meta_device_time_idx on _timescaledb_internal._hyper_2_5_chunk hm_1\n               Output: hm_1.info, hm_1.device\n         ->  Index Scan using _hyper_2_6_chunk_hyper_meta_device_time_idx on _timescaledb_internal._hyper_2_6_chunk hm_2\n               Output: hm_2.info, hm_2.device\n   ->  Materialize\n         Output: h_1.\"time\", h_1.device, h_1.temp\n         ->  Merge Append\n               Sort Key: h_1.device\n               ->  Index Scan using _hyper_1_1_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_1_chunk h_1\n                     Output: h_1.\"time\", h_1.device, h_1.temp\n               ->  Index Scan using _hyper_1_2_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_2_chunk h_2\n                     Output: h_2.\"time\", h_2.device, h_2.temp\n               ->  Index Scan using _hyper_1_3_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_3_chunk h_3\n                     Output: h_3.\"time\", h_3.device, h_3.temp\n               ->  Index Scan using _hyper_1_4_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_4_chunk h_4\n                     Output: h_4.\"time\", h_4.device, h_4.temp\n(21 rows)\n\n:PREFIX\nSELECT pg2.time, pg2.device, pg2.temp, pg1.temp\nFROM pg2dim pg2, pg1dim pg1\nWHERE pg2.device = pg1.device;\n                             QUERY PLAN                             \n--------------------------------------------------------------------\n Merge Join\n   Output: pg2.\"time\", pg2.device, pg2.temp, pg1.temp\n   Merge Cond: (pg1.device = pg2.device)\n   ->  Sort\n         Output: pg1.temp, pg1.device\n         Sort Key: pg1.device\n         ->  Append\n               ->  Seq Scan on public.pg1dim_h1 pg1_1\n                     Output: pg1_1.temp, pg1_1.device\n               ->  Seq Scan on public.pg1dim_h2 pg1_2\n                     Output: pg1_2.temp, pg1_2.device\n   ->  Sort\n         Output: pg2.\"time\", pg2.device, pg2.temp\n         Sort Key: pg2.device\n         ->  Append\n               ->  Seq Scan on public.pg2dim_h1_t1 pg2_1\n                     Output: pg2_1.\"time\", pg2_1.device, pg2_1.temp\n               ->  Seq Scan on public.pg2dim_h1_t2 pg2_2\n                     Output: pg2_2.\"time\", pg2_2.device, pg2_2.temp\n               ->  Seq Scan on public.pg2dim_h2_t1 pg2_3\n                     Output: pg2_3.\"time\", pg2_3.device, pg2_3.temp\n               ->  Seq Scan on public.pg2dim_h2_t2 pg2_4\n                     Output: pg2_4.\"time\", pg2_4.device, pg2_4.temp\n(23 rows)\n\nSET enable_partitionwise_join = 'on';\n:PREFIX\nSELECT h.time, h.device, h.temp, hm.info\nFROM hyper h, hyper_meta hm\nWHERE h.device = hm.device;\n                                                       QUERY PLAN                                                        \n-------------------------------------------------------------------------------------------------------------------------\n Merge Join\n   Output: h_1.\"time\", h_1.device, h_1.temp, hm_1.info\n   Merge Cond: (hm_1.device = h_1.device)\n   ->  Merge Append\n         Sort Key: hm_1.device\n         ->  Index Scan using _hyper_2_5_chunk_hyper_meta_device_time_idx on _timescaledb_internal._hyper_2_5_chunk hm_1\n               Output: hm_1.info, hm_1.device\n         ->  Index Scan using _hyper_2_6_chunk_hyper_meta_device_time_idx on _timescaledb_internal._hyper_2_6_chunk hm_2\n               Output: hm_2.info, hm_2.device\n   ->  Materialize\n         Output: h_1.\"time\", h_1.device, h_1.temp\n         ->  Merge Append\n               Sort Key: h_1.device\n               ->  Index Scan using _hyper_1_1_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_1_chunk h_1\n                     Output: h_1.\"time\", h_1.device, h_1.temp\n               ->  Index Scan using _hyper_1_2_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_2_chunk h_2\n                     Output: h_2.\"time\", h_2.device, h_2.temp\n               ->  Index Scan using _hyper_1_3_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_3_chunk h_3\n                     Output: h_3.\"time\", h_3.device, h_3.temp\n               ->  Index Scan using _hyper_1_4_chunk_hyper_device_time_idx on _timescaledb_internal._hyper_1_4_chunk h_4\n                     Output: h_4.\"time\", h_4.device, h_4.temp\n(21 rows)\n\n:PREFIX\nSELECT pg2.time, pg2.device, pg2.temp, pg1.temp\nFROM pg2dim pg2, pg1dim pg1\nWHERE pg2.device = pg1.device;\n                                QUERY PLAN                                \n--------------------------------------------------------------------------\n Append\n   ->  Merge Join\n         Output: pg2_2.\"time\", pg2_2.device, pg2_2.temp, pg1_1.temp\n         Merge Cond: (pg1_1.device = pg2_2.device)\n         ->  Sort\n               Output: pg1_1.temp, pg1_1.device\n               Sort Key: pg1_1.device\n               ->  Seq Scan on public.pg1dim_h1 pg1_1\n                     Output: pg1_1.temp, pg1_1.device\n         ->  Sort\n               Output: pg2_2.\"time\", pg2_2.device, pg2_2.temp\n               Sort Key: pg2_2.device\n               ->  Append\n                     ->  Seq Scan on public.pg2dim_h1_t1 pg2_2\n                           Output: pg2_2.\"time\", pg2_2.device, pg2_2.temp\n                     ->  Seq Scan on public.pg2dim_h1_t2 pg2_3\n                           Output: pg2_3.\"time\", pg2_3.device, pg2_3.temp\n   ->  Merge Join\n         Output: pg2_5.\"time\", pg2_5.device, pg2_5.temp, pg1_2.temp\n         Merge Cond: (pg1_2.device = pg2_5.device)\n         ->  Sort\n               Output: pg1_2.temp, pg1_2.device\n               Sort Key: pg1_2.device\n               ->  Seq Scan on public.pg1dim_h2 pg1_2\n                     Output: pg1_2.temp, pg1_2.device\n         ->  Sort\n               Output: pg2_5.\"time\", pg2_5.device, pg2_5.temp\n               Sort Key: pg2_5.device\n               ->  Append\n                     ->  Seq Scan on public.pg2dim_h2_t1 pg2_5\n                           Output: pg2_5.\"time\", pg2_5.device, pg2_5.temp\n                     ->  Seq Scan on public.pg2dim_h2_t2 pg2_6\n                           Output: pg2_6.\"time\", pg2_6.device, pg2_6.temp\n(33 rows)\n\n-- Test hypertable with time partitioning function\nCREATE OR REPLACE FUNCTION time_func(unixtime float8)\n    RETURNS TIMESTAMPTZ LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nDECLARE\n    retval TIMESTAMPTZ;\nBEGIN\n    retval := to_timestamp(unixtime);\n    RETURN retval;\nEND\n$BODY$;\nCREATE TABLE hyper_timepart (time float8, device int, temp float);\nSELECT * FROM create_hypertable('hyper_timepart', 'time', 'device', 2, time_partitioning_func => 'time_func');\nNOTICE:  adding not-null constraint to column \"time\"\n hypertable_id | schema_name |   table_name   | created \n---------------+-------------+----------------+---------\n             3 | public      | hyper_timepart | t\n(1 row)\n\n-- Planner won't pick push-down aggs on table with time function\n-- unless a certain amount of data\nSELECT setseed(1);\n setseed \n---------\n \n(1 row)\n\nINSERT INTO hyper_timepart\nSELECT x, ceil(random() * 8), random() * 20\nFROM generate_series(0,5000-1) AS x;\n-- All partition keys covered (full partitionwise)\nSET timescaledb.enable_chunkwise_aggregation = 'off';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n                                                QUERY PLAN                                                 \n-----------------------------------------------------------------------------------------------------------\n Limit\n   Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, (avg(_hyper_3_7_chunk.temp))\n   ->  Sort\n         Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, (avg(_hyper_3_7_chunk.temp))\n         Sort Key: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device\n         ->  HashAggregate\n               Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, avg(_hyper_3_7_chunk.temp)\n               Group Key: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_3_7_chunk\n                           Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp\n                     ->  Seq Scan on _timescaledb_internal._hyper_3_8_chunk\n                           Output: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp\n(13 rows)\n\n:PREFIX\nSELECT time_func(time), device, avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n                                                                     QUERY PLAN                                                                      \n-----------------------------------------------------------------------------------------------------------------------------------------------------\n Limit\n   Output: (time_func(_hyper_3_7_chunk.\"time\")), _hyper_3_7_chunk.device, (avg(_hyper_3_7_chunk.temp))\n   ->  GroupAggregate\n         Output: (time_func(_hyper_3_7_chunk.\"time\")), _hyper_3_7_chunk.device, avg(_hyper_3_7_chunk.temp)\n         Group Key: (time_func(_hyper_3_7_chunk.\"time\")), _hyper_3_7_chunk.device\n         ->  Incremental Sort\n               Output: (time_func(_hyper_3_7_chunk.\"time\")), _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp\n               Sort Key: (time_func(_hyper_3_7_chunk.\"time\")), _hyper_3_7_chunk.device\n               Presorted Key: (time_func(_hyper_3_7_chunk.\"time\"))\n               ->  Result\n                     Output: (time_func(_hyper_3_7_chunk.\"time\")), _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp\n                     ->  Merge Append\n                           Sort Key: (time_func(_hyper_3_7_chunk.\"time\"))\n                           ->  Index Scan Backward using _hyper_3_7_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_7_chunk\n                                 Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp, time_func(_hyper_3_7_chunk.\"time\")\n                           ->  Index Scan Backward using _hyper_3_8_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_8_chunk\n                                 Output: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp, time_func(_hyper_3_8_chunk.\"time\")\n(17 rows)\n\n-- Grouping on original time column should be pushed-down\nSET timescaledb.enable_chunkwise_aggregation = 'on';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n                                                       QUERY PLAN                                                       \n------------------------------------------------------------------------------------------------------------------------\n Limit\n   Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, (avg(_hyper_3_7_chunk.temp))\n   ->  Sort\n         Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, (avg(_hyper_3_7_chunk.temp))\n         Sort Key: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device\n         ->  Finalize HashAggregate\n               Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, avg(_hyper_3_7_chunk.temp)\n               Group Key: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device\n               ->  Append\n                     ->  Partial HashAggregate\n                           Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, PARTIAL avg(_hyper_3_7_chunk.temp)\n                           Group Key: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_3_7_chunk\n                                 Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp\n                     ->  Partial HashAggregate\n                           Output: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device, PARTIAL avg(_hyper_3_8_chunk.temp)\n                           Group Key: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device\n                           ->  Seq Scan on _timescaledb_internal._hyper_3_8_chunk\n                                 Output: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp\n(19 rows)\n\n-- Applying the time partitioning function should also allow push-down\n-- on open dimensions\n:PREFIX\nSELECT time_func(time), device, avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n                                                                     QUERY PLAN                                                                      \n-----------------------------------------------------------------------------------------------------------------------------------------------------\n Limit\n   Output: (time_func(_hyper_3_7_chunk.\"time\")), _hyper_3_7_chunk.device, (avg(_hyper_3_7_chunk.temp))\n   ->  GroupAggregate\n         Output: (time_func(_hyper_3_7_chunk.\"time\")), _hyper_3_7_chunk.device, avg(_hyper_3_7_chunk.temp)\n         Group Key: (time_func(_hyper_3_7_chunk.\"time\")), _hyper_3_7_chunk.device\n         ->  Incremental Sort\n               Output: (time_func(_hyper_3_7_chunk.\"time\")), _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp\n               Sort Key: (time_func(_hyper_3_7_chunk.\"time\")), _hyper_3_7_chunk.device\n               Presorted Key: (time_func(_hyper_3_7_chunk.\"time\"))\n               ->  Result\n                     Output: (time_func(_hyper_3_7_chunk.\"time\")), _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp\n                     ->  Merge Append\n                           Sort Key: (time_func(_hyper_3_7_chunk.\"time\"))\n                           ->  Index Scan Backward using _hyper_3_7_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_7_chunk\n                                 Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp, time_func(_hyper_3_7_chunk.\"time\")\n                           ->  Index Scan Backward using _hyper_3_8_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_8_chunk\n                                 Output: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp, time_func(_hyper_3_8_chunk.\"time\")\n(17 rows)\n\n-- Partial aggregation pushdown is currently not supported for this query by\n-- the TSDB pushdown code since a projection is used in the path.\n:PREFIX\nSELECT time_func(time), _timescaledb_functions.get_partition_hash(device), avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n                                                                         QUERY PLAN                                                                          \n-------------------------------------------------------------------------------------------------------------------------------------------------------------\n Limit\n   Output: (time_func(_hyper_3_7_chunk.\"time\")), (_timescaledb_functions.get_partition_hash(_hyper_3_7_chunk.device)), (avg(_hyper_3_7_chunk.temp))\n   ->  GroupAggregate\n         Output: (time_func(_hyper_3_7_chunk.\"time\")), (_timescaledb_functions.get_partition_hash(_hyper_3_7_chunk.device)), avg(_hyper_3_7_chunk.temp)\n         Group Key: (time_func(_hyper_3_7_chunk.\"time\")), (_timescaledb_functions.get_partition_hash(_hyper_3_7_chunk.device))\n         ->  Incremental Sort\n               Output: (time_func(_hyper_3_7_chunk.\"time\")), (_timescaledb_functions.get_partition_hash(_hyper_3_7_chunk.device)), _hyper_3_7_chunk.temp\n               Sort Key: (time_func(_hyper_3_7_chunk.\"time\")), (_timescaledb_functions.get_partition_hash(_hyper_3_7_chunk.device))\n               Presorted Key: (time_func(_hyper_3_7_chunk.\"time\"))\n               ->  Result\n                     Output: (time_func(_hyper_3_7_chunk.\"time\")), _timescaledb_functions.get_partition_hash(_hyper_3_7_chunk.device), _hyper_3_7_chunk.temp\n                     ->  Merge Append\n                           Sort Key: (time_func(_hyper_3_7_chunk.\"time\"))\n                           ->  Index Scan Backward using _hyper_3_7_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_7_chunk\n                                 Output: _hyper_3_7_chunk.\"time\", _hyper_3_7_chunk.device, _hyper_3_7_chunk.temp, time_func(_hyper_3_7_chunk.\"time\")\n                           ->  Index Scan Backward using _hyper_3_8_chunk_hyper_timepart_expr_idx on _timescaledb_internal._hyper_3_8_chunk\n                                 Output: _hyper_3_8_chunk.\"time\", _hyper_3_8_chunk.device, _hyper_3_8_chunk.temp, time_func(_hyper_3_8_chunk.\"time\")\n(17 rows)\n\n"
  },
  {
    "path": "test/expected/pg_dump.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\set TEST_DBNAME_EXTRA :TEST_DBNAME _extra\n\\o /dev/null\n\\ir include/insert_two_partitions.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE PUBLIC.\"two_Partitions\" (\n  \"timeCustom\" BIGINT NOT NULL,\n  device_id TEXT NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL,\n  series_bool BOOLEAN NULL\n);\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (device_id, \"timeCustom\" DESC NULLS LAST) WHERE device_id IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_0) WHERE series_0 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_1)  WHERE series_1 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_2) WHERE series_2 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_bool) WHERE series_bool IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, device_id);\nSELECT * FROM create_hypertable('\"public\".\"two_Partitions\"'::regclass, 'timeCustom'::name, 'device_id'::name, associated_schema_name=>'_timescaledb_internal'::text, number_partitions => 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\\set QUIET off\nBEGIN;\n\\COPY public.\"two_Partitions\" FROM 'data/ds1_dev1_1.tsv' NULL AS '';\nCOMMIT;\nINSERT INTO public.\"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257987600000000000, 'dev1', 1.5, 1),\n(1257987600000000000, 'dev1', 1.5, 2),\n(1257894000000000000, 'dev2', 1.5, 1),\n(1257894002000000000, 'dev1', 2.5, 3);\nINSERT INTO \"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257894000000000000, 'dev2', 1.5, 2);\n\\set QUIET on\n\\o\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION bgw_wait(database TEXT, timeout INT, raise_error BOOLEAN DEFAULT TRUE)\nRETURNS VOID\nAS :MODULE_PATHNAME, 'ts_bgw_wait'\nLANGUAGE C VOLATILE;\nCREATE SCHEMA test_schema AUTHORIZATION :ROLE_DEFAULT_PERM_USER;\n\\c :TEST_DBNAME\nALTER TABLE PUBLIC.\"two_Partitions\" SET SCHEMA \"test_schema\";\n-- Test that we can restore constraints\nALTER TABLE \"test_schema\".\"two_Partitions\"\nADD CONSTRAINT timeCustom_device_id_series_2_key\nUNIQUE (\"timeCustom\", device_id, series_2);\n-- Test that we can restore triggers\nCREATE OR REPLACE FUNCTION test_trigger()\n    RETURNS TRIGGER LANGUAGE PLPGSQL AS\n$BODY$\nBEGIN\n    RETURN NEW;\nEND\n$BODY$;\n-- Test that a custom chunk sizing function is restored\nCREATE OR REPLACE FUNCTION custom_calculate_chunk_interval(\n        dimension_id INTEGER,\n        dimension_coord BIGINT,\n        chunk_target_size BIGINT\n)\n    RETURNS BIGINT LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\nBEGIN\n    RETURN -1;\nEND\n$BODY$;\nSELECT * FROM set_adaptive_chunking('\"test_schema\".\"two_Partitions\"', '1 MB', 'custom_calculate_chunk_interval');\nWARNING:  target chunk size for adaptive chunking is less than 10 MB\n        chunk_sizing_func        | chunk_target_size \n---------------------------------+-------------------\n custom_calculate_chunk_interval |           1048576\n\n-- Chunk sizing func set\nSELECT * FROM _timescaledb_catalog.hypertable;\n id | schema_name |   table_name   | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |     chunk_sizing_func_name      | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------+----------------+------------------------+-------------------------+----------------+--------------------------+---------------------------------+-------------------+-------------------+--------------------------+--------\n  1 | test_schema | two_Partitions | _timescaledb_internal  | _hyper_1                |              2 | public                   | custom_calculate_chunk_interval |           1048576 |                 0 |                          |      0\n\nSELECT proname, pronamespace, pronargs\nFROM pg_proc WHERE proname = 'custom_calculate_chunk_interval';\n             proname             | pronamespace | pronargs \n---------------------------------+--------------+----------\n custom_calculate_chunk_interval |         2200 |        3\n\nCREATE TRIGGER restore_trigger BEFORE INSERT ON \"test_schema\".\"two_Partitions\"\nFOR EACH ROW EXECUTE FUNCTION test_trigger();\n-- Save the number of dependent objects so we can make sure we have the same number later\nSELECT count(*) as num_dependent_objects\n  FROM pg_depend\n WHERE refclassid = 'pg_extension'::regclass\n     AND refobjid = (SELECT oid FROM pg_extension WHERE extname = 'timescaledb')\n\\gset\nSELECT * FROM test.show_columns('\"test_schema\".\"two_Partitions\"');\n   Column    |       Type       | NotNull \n-------------+------------------+---------\n timeCustom  | bigint           | t\n device_id   | text             | t\n series_0    | double precision | f\n series_1    | double precision | f\n series_2    | double precision | f\n series_bool | boolean          | f\n\nSELECT * FROM test.show_columns('_timescaledb_internal._hyper_1_1_chunk');\n   Column    |       Type       | NotNull \n-------------+------------------+---------\n timeCustom  | bigint           | t\n device_id   | text             | t\n series_0    | double precision | f\n series_1    | double precision | f\n series_2    | double precision | f\n series_bool | boolean          | f\n\nSELECT * FROM test.show_indexes('\"test_schema\".\"two_Partitions\"');\n                          Index                          |             Columns             | Expr | Unique | Primary | Exclusion | Tablespace \n---------------------------------------------------------+---------------------------------+------+--------+---------+-----------+------------\n test_schema.timecustom_device_id_series_2_key           | {timeCustom,device_id,series_2} |      | t      | f       | f         | \n test_schema.\"two_Partitions_device_id_timeCustom_idx\"   | {device_id,timeCustom}          |      | f      | f       | f         | \n test_schema.\"two_Partitions_timeCustom_device_id_idx\"   | {timeCustom,device_id}          |      | f      | f       | f         | \n test_schema.\"two_Partitions_timeCustom_idx\"             | {timeCustom}                    |      | f      | f       | f         | \n test_schema.\"two_Partitions_timeCustom_series_0_idx\"    | {timeCustom,series_0}           |      | f      | f       | f         | \n test_schema.\"two_Partitions_timeCustom_series_1_idx\"    | {timeCustom,series_1}           |      | f      | f       | f         | \n test_schema.\"two_Partitions_timeCustom_series_2_idx\"    | {timeCustom,series_2}           |      | f      | f       | f         | \n test_schema.\"two_Partitions_timeCustom_series_bool_idx\" | {timeCustom,series_bool}        |      | f      | f       | f         | \n\nSELECT * FROM test.show_indexes('_timescaledb_internal._hyper_1_1_chunk');\n                                       Index                                        |             Columns             | Expr | Unique | Primary | Exclusion | Tablespace \n------------------------------------------------------------------------------------+---------------------------------+------+--------+---------+-----------+------------\n _timescaledb_internal.\"1_1_timecustom_device_id_series_2_key\"                      | {timeCustom,device_id,series_2} |      | t      | f       | f         | \n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_device_id_timeCustom_idx\"   | {device_id,timeCustom}          |      | f      | f       | f         | \n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_device_id_idx\"   | {timeCustom,device_id}          |      | f      | f       | f         | \n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_idx\"             | {timeCustom}                    |      | f      | f       | f         | \n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_0_idx\"    | {timeCustom,series_0}           |      | f      | f       | f         | \n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx\"    | {timeCustom,series_1}           |      | f      | f       | f         | \n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_2_idx\"    | {timeCustom,series_2}           |      | f      | f       | f         | \n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_bool_idx\" | {timeCustom,series_bool}        |      | f      | f       | f         | \n\nSELECT * FROM test.show_constraints('\"test_schema\".\"two_Partitions\"');\n            Constraint             | Type |             Columns             |                     Index                     | Expr | Deferrable | Deferred | Validated \n-----------------------------------+------+---------------------------------+-----------------------------------------------+------+------------+----------+-----------\n timecustom_device_id_series_2_key | u    | {timeCustom,device_id,series_2} | test_schema.timecustom_device_id_series_2_key |      | f          | f        | t\n\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_1_1_chunk');\n              Constraint               | Type |             Columns             |                             Index                             |                                                 Expr                                                 | Deferrable | Deferred | Validated \n---------------------------------------+------+---------------------------------+---------------------------------------------------------------+------------------------------------------------------------------------------------------------------+------------+----------+-----------\n 1_1_timecustom_device_id_series_2_key | u    | {timeCustom,device_id,series_2} | _timescaledb_internal.\"1_1_timecustom_device_id_series_2_key\" |                                                                                                      | f          | f        | t\n constraint_1                          | c    | {timeCustom}                    | -                                                             | ((\"timeCustom\" >= '1257892416000000000'::bigint) AND (\"timeCustom\" < '1257895008000000000'::bigint)) | f          | f        | t\n constraint_2                          | c    | {device_id}                     | -                                                             | (_timescaledb_functions.get_partition_hash(device_id) >= 1073741823)                                 | f          | f        | t\n\nSELECT * FROM test.show_triggers('\"test_schema\".\"two_Partitions\"');\n     Trigger     | Type |   Function   \n-----------------+------+--------------\n restore_trigger |    7 | test_trigger\n\nSELECT * FROM test.show_triggers('_timescaledb_internal._hyper_1_1_chunk');\n     Trigger     | Type |   Function   \n-----------------+------+--------------\n restore_trigger |    7 | test_trigger\n\nSELECT * FROM \"test_schema\".\"two_Partitions\" ORDER BY \"timeCustom\", device_id, series_0, series_1;\n     timeCustom      | device_id | series_0 | series_1 | series_2 | series_bool \n---------------------+-----------+----------+----------+----------+-------------\n 1257894000000000000 | dev1      |      1.5 |        1 |        2 | t\n 1257894000000000000 | dev1      |      1.5 |        2 |          | \n 1257894000000000000 | dev2      |      1.5 |        1 |          | \n 1257894000000000000 | dev2      |      1.5 |        2 |          | \n 1257894000000001000 | dev1      |      2.5 |        3 |          | \n 1257894001000000000 | dev1      |      3.5 |        4 |          | \n 1257894002000000000 | dev1      |      2.5 |        3 |          | \n 1257894002000000000 | dev1      |      5.5 |        6 |          | t\n 1257894002000000000 | dev1      |      5.5 |        7 |          | f\n 1257897600000000000 | dev1      |      4.5 |        5 |          | f\n 1257987600000000000 | dev1      |      1.5 |        1 |          | \n 1257987600000000000 | dev1      |      1.5 |        2 |          | \n\nSELECT * FROM _timescaledb_internal._hyper_1_1_chunk ORDER BY \"timeCustom\", device_id, series_0, series_1;\n     timeCustom      | device_id | series_0 | series_1 | series_2 | series_bool \n---------------------+-----------+----------+----------+----------+-------------\n 1257894000000000000 | dev1      |      1.5 |        1 |        2 | t\n 1257894000000000000 | dev1      |      1.5 |        2 |          | \n 1257894000000001000 | dev1      |      2.5 |        3 |          | \n 1257894001000000000 | dev1      |      3.5 |        4 |          | \n 1257894002000000000 | dev1      |      2.5 |        3 |          | \n 1257894002000000000 | dev1      |      5.5 |        6 |          | t\n 1257894002000000000 | dev1      |      5.5 |        7 |          | f\n\nSELECT * FROM _timescaledb_internal._hyper_1_2_chunk ORDER BY \"timeCustom\", device_id, series_0, series_1;\n     timeCustom      | device_id | series_0 | series_1 | series_2 | series_bool \n---------------------+-----------+----------+----------+----------+-------------\n 1257897600000000000 | dev1      |      4.5 |        5 |          | f\n\n-- Show all constraints\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\n chunk_id | dimension_slice_id |            constraint_name            |    hypertable_constraint_name     \n----------+--------------------+---------------------------------------+-----------------------------------\n        1 |                  1 | constraint_1                          | \n        1 |                  2 | constraint_2                          | \n        2 |                  3 | constraint_3                          | \n        2 |                  2 | constraint_2                          | \n        3 |                  4 | constraint_4                          | \n        3 |                  2 | constraint_2                          | \n        4 |                  1 | constraint_1                          | \n        4 |                  5 | constraint_5                          | \n        1 |                    | 1_1_timecustom_device_id_series_2_key | timecustom_device_id_series_2_key\n        2 |                    | 2_2_timecustom_device_id_series_2_key | timecustom_device_id_series_2_key\n        3 |                    | 3_3_timecustom_device_id_series_2_key | timecustom_device_id_series_2_key\n        4 |                    | 4_4_timecustom_device_id_series_2_key | timecustom_device_id_series_2_key\n\nINSERT INTO _timescaledb_catalog.metadata VALUES ('exported_uuid', 'original_uuid', true);\nINSERT INTO _timescaledb_catalog.metadata VALUES ('metadata_test', 'FOO', false);\n\\c postgres :ROLE_SUPERUSER\n-- We shell out to a script in order to grab the correct hostname from the\n-- environmental variables that originally called this psql command. Sadly\n-- vars passed to psql do not work in \\! commands so we can't do it that way.\n\\! utils/pg_dump_aux_dump.sh dump/pg_dump.sql\n\\c :TEST_DBNAME\nSET client_min_messages = ERROR;\nCREATE EXTENSION timescaledb CASCADE;\n--create a exported uuid before restoring (mocks telemetry running before restore)\nINSERT INTO _timescaledb_catalog.metadata VALUES ('exported_uuid', 'new_db_uuid', true);\n-- disable background jobs\nUPDATE _timescaledb_catalog.bgw_job SET scheduled = false;\nRESET client_min_messages;\nSELECT timescaledb_pre_restore();\n timescaledb_pre_restore \n-------------------------\n t\n\nSHOW timescaledb.restoring;\n timescaledb.restoring \n-----------------------\n on\n\n-- reconnect and check GUC value in new session\n\\c\nSHOW timescaledb.restoring;\n timescaledb.restoring \n-----------------------\n on\n\n\\! utils/pg_dump_aux_restore.sh dump/pg_dump.sql\n-- Now run our post-restore function.\nSELECT timescaledb_post_restore();\n timescaledb_post_restore \n--------------------------\n t\n\nSHOW timescaledb.restoring;\n timescaledb.restoring \n-----------------------\n off\n\n-- timescaledb_post_restore restarts background worker so we have to stop them\n-- to make sure they dont interfere with this database being used as template below\nSELECT _timescaledb_functions.stop_background_workers();\n stop_background_workers \n-------------------------\n t\n\n--should be same as count above\nSELECT count(*) = :num_dependent_objects as dependent_objects_match\n  FROM pg_depend\n WHERE refclassid = 'pg_extension'::regclass\n     AND refobjid = (SELECT oid FROM pg_extension WHERE extname = 'timescaledb');\n dependent_objects_match \n-------------------------\n t\n\n--we should have the original uuid from the backed up db set as the exported_uuid\nSELECT value = 'original_uuid' FROM _timescaledb_catalog.metadata  WHERE key='exported_uuid';\n ?column? \n----------\n t\n\nSELECT count(*) = 1 FROM _timescaledb_catalog.metadata WHERE key LIKE 'exported%';\n ?column? \n----------\n t\n\n--we should have the original value of metadata_test\nSELECT * FROM _timescaledb_catalog.metadata  WHERE key='metadata_test';\n      key      | value | include_in_telemetry \n---------------+-------+----------------------\n metadata_test | FOO   | f\n\n--main table and chunk schemas should be the same\nSELECT * FROM test.show_columns('\"test_schema\".\"two_Partitions\"');\n   Column    |       Type       | NotNull \n-------------+------------------+---------\n timeCustom  | bigint           | t\n device_id   | text             | t\n series_0    | double precision | f\n series_1    | double precision | f\n series_2    | double precision | f\n series_bool | boolean          | f\n\nSELECT * FROM test.show_columns('_timescaledb_internal._hyper_1_1_chunk');\n   Column    |       Type       | NotNull \n-------------+------------------+---------\n timeCustom  | bigint           | t\n device_id   | text             | t\n series_0    | double precision | f\n series_1    | double precision | f\n series_2    | double precision | f\n series_bool | boolean          | f\n\nSELECT * FROM test.show_indexes('\"test_schema\".\"two_Partitions\"');\n                          Index                          |             Columns             | Expr | Unique | Primary | Exclusion | Tablespace \n---------------------------------------------------------+---------------------------------+------+--------+---------+-----------+------------\n test_schema.timecustom_device_id_series_2_key           | {timeCustom,device_id,series_2} |      | t      | f       | f         | \n test_schema.\"two_Partitions_device_id_timeCustom_idx\"   | {device_id,timeCustom}          |      | f      | f       | f         | \n test_schema.\"two_Partitions_timeCustom_device_id_idx\"   | {timeCustom,device_id}          |      | f      | f       | f         | \n test_schema.\"two_Partitions_timeCustom_idx\"             | {timeCustom}                    |      | f      | f       | f         | \n test_schema.\"two_Partitions_timeCustom_series_0_idx\"    | {timeCustom,series_0}           |      | f      | f       | f         | \n test_schema.\"two_Partitions_timeCustom_series_1_idx\"    | {timeCustom,series_1}           |      | f      | f       | f         | \n test_schema.\"two_Partitions_timeCustom_series_2_idx\"    | {timeCustom,series_2}           |      | f      | f       | f         | \n test_schema.\"two_Partitions_timeCustom_series_bool_idx\" | {timeCustom,series_bool}        |      | f      | f       | f         | \n\nSELECT * FROM test.show_indexes('_timescaledb_internal._hyper_1_1_chunk');\n                                       Index                                        |             Columns             | Expr | Unique | Primary | Exclusion | Tablespace \n------------------------------------------------------------------------------------+---------------------------------+------+--------+---------+-----------+------------\n _timescaledb_internal.\"1_1_timecustom_device_id_series_2_key\"                      | {timeCustom,device_id,series_2} |      | t      | f       | f         | \n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_device_id_timeCustom_idx\"   | {device_id,timeCustom}          |      | f      | f       | f         | \n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_device_id_idx\"   | {timeCustom,device_id}          |      | f      | f       | f         | \n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_idx\"             | {timeCustom}                    |      | f      | f       | f         | \n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_0_idx\"    | {timeCustom,series_0}           |      | f      | f       | f         | \n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx\"    | {timeCustom,series_1}           |      | f      | f       | f         | \n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_2_idx\"    | {timeCustom,series_2}           |      | f      | f       | f         | \n _timescaledb_internal.\"_hyper_1_1_chunk_two_Partitions_timeCustom_series_bool_idx\" | {timeCustom,series_bool}        |      | f      | f       | f         | \n\nSELECT * FROM test.show_constraints('\"test_schema\".\"two_Partitions\"');\n            Constraint             | Type |             Columns             |                     Index                     | Expr | Deferrable | Deferred | Validated \n-----------------------------------+------+---------------------------------+-----------------------------------------------+------+------------+----------+-----------\n timecustom_device_id_series_2_key | u    | {timeCustom,device_id,series_2} | test_schema.timecustom_device_id_series_2_key |      | f          | f        | t\n\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_1_1_chunk');\n              Constraint               | Type |             Columns             |                             Index                             |                                                 Expr                                                 | Deferrable | Deferred | Validated \n---------------------------------------+------+---------------------------------+---------------------------------------------------------------+------------------------------------------------------------------------------------------------------+------------+----------+-----------\n 1_1_timecustom_device_id_series_2_key | u    | {timeCustom,device_id,series_2} | _timescaledb_internal.\"1_1_timecustom_device_id_series_2_key\" |                                                                                                      | f          | f        | t\n constraint_1                          | c    | {timeCustom}                    | -                                                             | ((\"timeCustom\" >= '1257892416000000000'::bigint) AND (\"timeCustom\" < '1257895008000000000'::bigint)) | f          | f        | t\n constraint_2                          | c    | {device_id}                     | -                                                             | (_timescaledb_functions.get_partition_hash(device_id) >= 1073741823)                                 | f          | f        | t\n\nSELECT * FROM test.show_triggers('\"test_schema\".\"two_Partitions\"');\n     Trigger     | Type |   Function   \n-----------------+------+--------------\n restore_trigger |    7 | test_trigger\n\nSELECT * FROM test.show_triggers('_timescaledb_internal._hyper_1_1_chunk');\n     Trigger     | Type |   Function   \n-----------------+------+--------------\n restore_trigger |    7 | test_trigger\n\n--data should be the same\nSELECT * FROM \"test_schema\".\"two_Partitions\" ORDER BY \"timeCustom\", device_id, series_0, series_1;\n     timeCustom      | device_id | series_0 | series_1 | series_2 | series_bool \n---------------------+-----------+----------+----------+----------+-------------\n 1257894000000000000 | dev1      |      1.5 |        1 |        2 | t\n 1257894000000000000 | dev1      |      1.5 |        2 |          | \n 1257894000000000000 | dev2      |      1.5 |        1 |          | \n 1257894000000000000 | dev2      |      1.5 |        2 |          | \n 1257894000000001000 | dev1      |      2.5 |        3 |          | \n 1257894001000000000 | dev1      |      3.5 |        4 |          | \n 1257894002000000000 | dev1      |      2.5 |        3 |          | \n 1257894002000000000 | dev1      |      5.5 |        6 |          | t\n 1257894002000000000 | dev1      |      5.5 |        7 |          | f\n 1257897600000000000 | dev1      |      4.5 |        5 |          | f\n 1257987600000000000 | dev1      |      1.5 |        1 |          | \n 1257987600000000000 | dev1      |      1.5 |        2 |          | \n\nSELECT * FROM _timescaledb_internal._hyper_1_1_chunk ORDER BY \"timeCustom\", device_id, series_0, series_1;\n     timeCustom      | device_id | series_0 | series_1 | series_2 | series_bool \n---------------------+-----------+----------+----------+----------+-------------\n 1257894000000000000 | dev1      |      1.5 |        1 |        2 | t\n 1257894000000000000 | dev1      |      1.5 |        2 |          | \n 1257894000000001000 | dev1      |      2.5 |        3 |          | \n 1257894001000000000 | dev1      |      3.5 |        4 |          | \n 1257894002000000000 | dev1      |      2.5 |        3 |          | \n 1257894002000000000 | dev1      |      5.5 |        6 |          | t\n 1257894002000000000 | dev1      |      5.5 |        7 |          | f\n\nSELECT * FROM _timescaledb_internal._hyper_1_2_chunk ORDER BY \"timeCustom\", device_id, series_0, series_1;\n     timeCustom      | device_id | series_0 | series_1 | series_2 | series_bool \n---------------------+-----------+----------+----------+----------+-------------\n 1257897600000000000 | dev1      |      4.5 |        5 |          | f\n\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\n chunk_id | dimension_slice_id |            constraint_name            |    hypertable_constraint_name     \n----------+--------------------+---------------------------------------+-----------------------------------\n        1 |                  1 | constraint_1                          | \n        1 |                  2 | constraint_2                          | \n        2 |                  3 | constraint_3                          | \n        2 |                  2 | constraint_2                          | \n        3 |                  4 | constraint_4                          | \n        3 |                  2 | constraint_2                          | \n        4 |                  1 | constraint_1                          | \n        4 |                  5 | constraint_5                          | \n        1 |                    | 1_1_timecustom_device_id_series_2_key | timecustom_device_id_series_2_key\n        2 |                    | 2_2_timecustom_device_id_series_2_key | timecustom_device_id_series_2_key\n        3 |                    | 3_3_timecustom_device_id_series_2_key | timecustom_device_id_series_2_key\n        4 |                    | 4_4_timecustom_device_id_series_2_key | timecustom_device_id_series_2_key\n\n--Chunk sizing function should have been restored\nSELECT * FROM _timescaledb_catalog.hypertable;\n id | schema_name |   table_name   | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |     chunk_sizing_func_name      | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------+----------------+------------------------+-------------------------+----------------+--------------------------+---------------------------------+-------------------+-------------------+--------------------------+--------\n  1 | test_schema | two_Partitions | _timescaledb_internal  | _hyper_1                |              2 | public                   | custom_calculate_chunk_interval |           1048576 |                 0 |                          |      0\n\nSELECT proname, pronamespace, pronargs\nFROM pg_proc WHERE proname = 'custom_calculate_chunk_interval';\n             proname             | pronamespace | pronargs \n---------------------------------+--------------+----------\n custom_calculate_chunk_interval |         2200 |        3\n\n--check simple ddl still works\nALTER TABLE \"test_schema\".\"two_Partitions\" ADD COLUMN series_3 integer;\nINSERT INTO \"test_schema\".\"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1, series_3) VALUES\n(1357894000000000000, 'dev5', 1.5, 2, 4);\nSELECT * FROM ONLY \"test_schema\".\"two_Partitions\";\n timeCustom | device_id | series_0 | series_1 | series_2 | series_bool | series_3 \n------------+-----------+----------+----------+----------+-------------+----------\n\n--query for the extension tables/sequences that will not be dumped by pg_dump (should\n--be empty except for views and explicitly excluded tables)\nSELECT objid::regclass\nFROM pg_catalog.pg_depend\nWHERE   refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass AND\n        refobjid = (select oid from pg_extension where extname='timescaledb') AND\n        deptype = 'e' AND\n        classid='pg_catalog.pg_class'::pg_catalog.regclass\n        AND objid NOT IN (select unnest(extconfig) from pg_extension where extname='timescaledb')\n        ORDER BY objid::regclass::text COLLATE \"C\";\n                          objid                          \n---------------------------------------------------------\n _timescaledb_cache.cache_inval_bgw_job\n _timescaledb_cache.cache_inval_extension\n _timescaledb_cache.cache_inval_hypertable\n _timescaledb_catalog.chunk_rewrite\n _timescaledb_catalog.compression_algorithm\n _timescaledb_catalog.tablespace_id_seq\n _timescaledb_catalog.telemetry_event\n _timescaledb_config.bgw_job\n _timescaledb_internal.bgw_job_stat\n _timescaledb_internal.bgw_job_stat_history\n _timescaledb_internal.bgw_job_stat_history_id_seq\n _timescaledb_internal.bgw_policy_chunk_stats\n _timescaledb_internal.compressed_chunk_stats\n _timescaledb_internal.hypertable_chunk_local_size\n timescaledb_experimental.policies\n timescaledb_information.chunk_columnstore_settings\n timescaledb_information.chunk_compression_settings\n timescaledb_information.chunks\n timescaledb_information.compression_settings\n timescaledb_information.continuous_aggregates\n timescaledb_information.dimensions\n timescaledb_information.hypertable_columnstore_settings\n timescaledb_information.hypertable_compression_settings\n timescaledb_information.hypertables\n timescaledb_information.job_errors\n timescaledb_information.job_history\n timescaledb_information.job_stats\n timescaledb_information.jobs\n\n-- Make sure we can't run our restoring functions as a normal perm user as that would disable functionality for the whole db\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n-- Hides error messages in cases where error messages differ between Postgres versions\ncreate or replace function get_sqlstate(in_text TEXT) RETURNS TEXT AS\n$$\nBEGIN\n    BEGIN\n        EXECUTE in_text;\n    EXCEPTION WHEN others THEN GET STACKED DIAGNOSTICS in_text = RETURNED_SQLSTATE;\n    END;\n    RETURN in_text;\nEND;\n$$\nLANGUAGE PLPGSQL;\nSELECT get_sqlstate('SELECT timescaledb_pre_restore()');\n get_sqlstate \n--------------\n 42501\n\nSELECT get_sqlstate('SELECT timescaledb_post_restore()');\n get_sqlstate \n--------------\n 42501\n\ndrop function get_sqlstate(TEXT);\n-- Check that the extension can be copied from an existing database\n-- without explicitly installing it. Stop background workers since we\n-- cannot have any backends connected to the database when cloning it.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT timescaledb_pre_restore();\n timescaledb_pre_restore \n-------------------------\n t\n\nSELECT bgw_wait(:'TEST_DBNAME', 60, FALSE);\n bgw_wait \n----------\n \n\n-- Force other sessions connected to the TEST_DBNAME to be finished\n\\c postgres :ROLE_SUPERUSER\nREVOKE CONNECT ON DATABASE :TEST_DBNAME FROM public;\nALTER DATABASE :TEST_DBNAME allow_connections = off;\nSET client_min_messages TO ERROR;\nSELECT COUNT(pg_catalog.pg_terminate_backend(pid))>=0\nFROM pg_stat_activity\nWHERE datname = ':TEST_DBNAME';\n ?column? \n----------\n t\n\nRESET client_min_messages;\nCREATE DATABASE :TEST_DBNAME_EXTRA WITH TEMPLATE :TEST_DBNAME;\nALTER DATABASE :TEST_DBNAME allow_connections = on;\nGRANT CONNECT ON DATABASE :TEST_DBNAME TO public;\n-- Connect to the database and do some basic stuff to check that the\n-- extension works.\n\\c :TEST_DBNAME_EXTRA :ROLE_DEFAULT_PERM_USER\nCREATE TABLE test_tz(time timestamptz not null, temp float8, device text);\nSELECT create_hypertable('test_tz', 'time', 'device', 2);\n  create_hypertable   \n----------------------\n (2,public,test_tz,t)\n\nSELECT id, schema_name, table_name FROM _timescaledb_catalog.hypertable;\n id | schema_name |   table_name   \n----+-------------+----------------\n  1 | test_schema | two_Partitions\n  2 | public      | test_tz\n\nINSERT INTO test_tz VALUES('Mon Mar 20 09:17:00.936242 2017', 23.4, 'dev1');\nINSERT INTO test_tz VALUES('Mon Mar 20 09:27:00.936242 2017', 22, 'dev2');\nINSERT INTO test_tz VALUES('Mon Mar 20 09:28:00.936242 2017', 21.2, 'dev1');\nINSERT INTO test_tz VALUES('Mon Mar 20 09:37:00.936242 2017', 30, 'dev3');\nSELECT * FROM test_tz ORDER BY time;\n                time                 | temp | device \n-------------------------------------+------+--------\n Mon Mar 20 09:17:00.936242 2017 PDT | 23.4 | dev1\n Mon Mar 20 09:27:00.936242 2017 PDT |   22 | dev2\n Mon Mar 20 09:28:00.936242 2017 PDT | 21.2 | dev1\n Mon Mar 20 09:37:00.936242 2017 PDT |   30 | dev3\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- make sure nobody is using it\nSET client_min_messages TO ERROR;\nREVOKE CONNECT ON DATABASE :TEST_DBNAME_EXTRA FROM public;\nSELECT count(pg_terminate_backend(pg_stat_activity.pid)) AS TERMINATED\nFROM pg_stat_activity\nWHERE pg_stat_activity.datname = :'TEST_DBNAME_EXTRA'\nAND pg_stat_activity.pid <> pg_backend_pid() \\gset\nRESET client_min_messages;\nDROP DATABASE :TEST_DBNAME_EXTRA WITH (FORCE);\n"
  },
  {
    "path": "test/expected/pg_dump_unprivileged.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c template1 :ROLE_SUPERUSER\nSET client_min_messages TO ERROR;\nCREATE EXTENSION IF NOT EXISTS timescaledb;\nRESET client_min_messages;\nCREATE USER dump_unprivileged CREATEDB;\n\\c template1 dump_unprivileged\nCREATE database dump_unprivileged;\n\\! utils/pg_dump_unprivileged.sh\nDatabase dumped successfully\n\\c dump_unprivileged :ROLE_SUPERUSER\nDROP EXTENSION timescaledb;\nGRANT ALL ON DATABASE dump_unprivileged TO dump_unprivileged;\n\\c dump_unprivileged dump_unprivileged\n-- Create the timescale extension and table as underprivileged user\nCREATE EXTENSION timescaledb;\nCREATE TABLE t1 (a int);\n-- pg_dump currently fails when dumped\n\\! utils/pg_dump_unprivileged.sh\nDatabase dumped successfully\n\\c template1 :ROLE_SUPERUSER\nDROP EXTENSION timescaledb;\nDROP DATABASE dump_unprivileged WITH (FORCE);\nDROP USER dump_unprivileged;\n"
  },
  {
    "path": "test/expected/pg_join.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- this test suite is based on the postgres join tests\n--\n-- the tests have been adjusted to work with hypertables\n-- statements that would generate an error have been commented out\n-- because errors don't play nicely with psql output redirection\n-- plan output has been disabled, because we are not interested in\n-- the actual plans produced but in the correctness of the results\n-- we need superuser because some of the tests modify statistics\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\\set TEST_BASE_NAME join\nSELECT format('include/%s_load.sql', :'TEST_BASE_NAME') as \"TEST_LOAD_NAME\",\n       format('include/%s_query.sql', :'TEST_BASE_NAME') as \"TEST_QUERY_NAME\",\n       format('%s/results/%s_results_optimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_OPTIMIZED\",\n       format('%s/results/%s_results_unoptimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_UNOPTIMIZED\"\n\\gset\nSELECT format('\\! diff -u --label \"Unoptimized results\" --label \"Optimized results\" %s %s', :'TEST_RESULTS_UNOPTIMIZED', :'TEST_RESULTS_OPTIMIZED') as \"DIFF_CMD\"\n\\gset\nset client_min_messages to warning;\n\\ir :TEST_LOAD_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- these table definitions have been adjusted from\n-- table defintions of the postgres test suite\nCREATE TABLE INT2_TBL(f1 int2, ts timestamptz NOT NULL DEFAULT '2000-01-01');\nSELECT table_name FROM create_hypertable('int2_tbl','ts');\n table_name \n------------\n int2_tbl\n\nINSERT INTO INT2_TBL(f1) VALUES ('0   ');\nINSERT INTO INT2_TBL(f1) VALUES ('  1234 ');\nINSERT INTO INT2_TBL(f1) VALUES ('    -1234');\n-- largest and smallest values\nINSERT INTO INT2_TBL(f1) VALUES ('32767');\nINSERT INTO INT2_TBL(f1) VALUES ('-32767');\nCREATE TABLE INT4_TBL(f1 int4, ts timestamptz NOT NULL DEFAULT '2000-01-01');\nSELECT table_name FROM create_hypertable('int4_tbl','ts');\n table_name \n------------\n int4_tbl\n\nINSERT INTO INT4_TBL(f1) VALUES ('   0  ');\nINSERT INTO INT4_TBL(f1) VALUES ('123456     ');\nINSERT INTO INT4_TBL(f1) VALUES ('    -123456');\n-- largest and smallest values\nINSERT INTO INT4_TBL(f1) VALUES ('2147483647');\nINSERT INTO INT4_TBL(f1) VALUES ('-2147483647');\nCREATE TABLE INT8_TBL(q1 int8, q2 int8, ts timestamptz NOT NULL DEFAULT '2000-01-01');\nSELECT table_name FROM create_hypertable('int8_tbl','ts');\n table_name \n------------\n int8_tbl\n\nINSERT INTO INT8_TBL VALUES('  123   ','  456');\nINSERT INTO INT8_TBL VALUES('123   ','4567890123456789');\nINSERT INTO INT8_TBL VALUES('4567890123456789','123');\nINSERT INTO INT8_TBL VALUES(+4567890123456789,'4567890123456789');\nINSERT INTO INT8_TBL VALUES('+4567890123456789','-4567890123456789');\nCREATE TABLE FLOAT8_TBL(f1 float8, ts timestamptz NOT NULL DEFAULT '2000-01-01');\nSELECT table_name FROM create_hypertable('float8_tbl','ts');\n table_name \n------------\n float8_tbl\n\nINSERT INTO FLOAT8_TBL(f1) VALUES ('    0.0   ');\nINSERT INTO FLOAT8_TBL(f1) VALUES ('1004.30  ');\nINSERT INTO FLOAT8_TBL(f1) VALUES ('   -34.84');\nINSERT INTO FLOAT8_TBL(f1) VALUES ('1.2345678901234e+200');\nINSERT INTO FLOAT8_TBL(f1) VALUES ('1.2345678901234e-200');\nCREATE TABLE TEXT_TBL (f1 text, ts timestamptz NOT NULL DEFAULT '2000-01-01');\nSELECT table_name FROM create_hypertable('text_tbl','ts');\n table_name \n------------\n text_tbl\n\nINSERT INTO TEXT_TBL VALUES ('doh!');\nINSERT INTO TEXT_TBL VALUES ('hi de ho neighbor');\nCREATE TABLE a (aa TEXT);\nCREATE TABLE b (bb TEXT) INHERITS (a);\nCREATE TABLE c (cc TEXT) INHERITS (a);\nCREATE TABLE d (dd TEXT) INHERITS (b,c,a);\nINSERT INTO a(aa) VALUES('aaa');\nINSERT INTO a(aa) VALUES('aaaa');\nINSERT INTO a(aa) VALUES('aaaaa');\nINSERT INTO a(aa) VALUES('aaaaaa');\nINSERT INTO a(aa) VALUES('aaaaaaa');\nINSERT INTO a(aa) VALUES('aaaaaaaa');\nINSERT INTO b(aa) VALUES('bbb');\nINSERT INTO b(aa) VALUES('bbbb');\nINSERT INTO b(aa) VALUES('bbbbb');\nINSERT INTO b(aa) VALUES('bbbbbb');\nINSERT INTO b(aa) VALUES('bbbbbbb');\nINSERT INTO b(aa) VALUES('bbbbbbbb');\nINSERT INTO c(aa) VALUES('ccc');\nINSERT INTO c(aa) VALUES('cccc');\nINSERT INTO c(aa) VALUES('ccccc');\nINSERT INTO c(aa) VALUES('cccccc');\nINSERT INTO c(aa) VALUES('ccccccc');\nINSERT INTO c(aa) VALUES('cccccccc');\nINSERT INTO d(aa) VALUES('ddd');\nINSERT INTO d(aa) VALUES('dddd');\nINSERT INTO d(aa) VALUES('ddddd');\nINSERT INTO d(aa) VALUES('dddddd');\nINSERT INTO d(aa) VALUES('ddddddd');\nINSERT INTO d(aa) VALUES('dddddddd');\nCREATE TABLE onek (\n  unique1   int4,\n  unique2   int4,\n  two     int4,\n  four    int4,\n  ten     int4,\n  twenty    int4,\n  hundred   int4,\n  thousand  int4,\n  twothousand int4,\n  fivethous int4,\n  tenthous  int4,\n  odd     int4,\n  even    int4,\n  stringu1  name,\n  stringu2  name,\n  string4   name\n);\nSELECT table_name FROM create_hypertable('onek','unique2',chunk_time_interval:=1000);\n table_name \n------------\n onek\n\n\\copy onek FROM 'data/onek.data'\nCREATE TABLE tenk1 (\n  unique1   int4,\n  unique2   int4,\n  two     int4,\n  four    int4,\n  ten     int4,\n  twenty    int4,\n  hundred   int4,\n  thousand  int4,\n  twothousand int4,\n  fivethous int4,\n  tenthous  int4,\n  odd     int4,\n  even    int4,\n  stringu1  name,\n  stringu2  name,\n  string4   name\n);\nSELECT table_name FROM create_hypertable('tenk1','unique2',chunk_time_interval:=1000);\n table_name \n------------\n tenk1\n\n\\copy tenk1 FROM 'data/tenk.data'\nCREATE TABLE tenk2 (\n  unique1   int4,\n  unique2   int4,\n  two     int4,\n  four    int4,\n  ten     int4,\n  twenty    int4,\n  hundred   int4,\n  thousand  int4,\n  twothousand int4,\n  fivethous int4,\n  tenthous  int4,\n  odd     int4,\n  even    int4,\n  stringu1  name,\n  stringu2  name,\n  string4   name\n);\nSELECT table_name FROM create_hypertable('tenk2','unique2',chunk_time_interval:=1000);\n table_name \n------------\n tenk2\n\nINSERT INTO tenk2 SELECT * FROM tenk1;\n\\set PREFIX ''\n\\set ECHO errors\n--- Unoptimized results\n+++ Optimized results\n@@ -1,6 +1,6 @@\n              setting              | value \n ----------------------------------+-------\n- timescaledb.enable_optimizations | off\n+ timescaledb.enable_optimizations | on\n \n  table_name \n"
  },
  {
    "path": "test/expected/plain.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- Tests for plain PostgreSQL commands to ensure that they work while\n-- the TimescaleDB extension is loaded. This is a mix of statements\n-- added mostly as regression checks when bugs are discovered and\n-- fixed.\nCREATE TABLE regular_table(time timestamp, temp float8, tag text, color integer);\n-- Renaming indexes should work\nCREATE INDEX time_color_idx ON regular_table(time, color);\nALTER INDEX time_color_idx RENAME TO time_color_idx2;\nALTER TABLE regular_table ALTER COLUMN color TYPE bigint;\nSELECT * FROM test.show_columns('regular_table');\n Column |            Type             | NotNull \n--------+-----------------------------+---------\n time   | timestamp without time zone | f\n temp   | double precision            | f\n tag    | text                        | f\n color  | bigint                      | f\n\nSELECT * FROM test.show_indexes('regular_table');\n      Index      |   Columns    | Expr | Unique | Primary | Exclusion | Tablespace \n-----------------+--------------+------+--------+---------+-----------+------------\n time_color_idx2 | {time,color} |      | f      | f       | f         | \n\n-- Renaming types should work\nCREATE TYPE rainbow AS ENUM ('red', 'orange', 'yellow', 'green', 'blue', 'purple');\nALTER TYPE rainbow RENAME TO colors;\n\\dT+\n                                           List of data types\n Schema |  Name  | Internal name | Size | Elements |       Owner       | Access privileges | Description \n--------+--------+---------------+------+----------+-------------------+-------------------+-------------\n public | colors | colors        | 4    | red     +| default_perm_user |                   | \n        |        |               |      | orange  +|                   |                   | \n        |        |               |      | yellow  +|                   |                   | \n        |        |               |      | green   +|                   |                   | \n        |        |               |      | blue    +|                   |                   | \n        |        |               |      | purple   |                   |                   | \n\nREINDEX TABLE regular_table;\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nREINDEX SCHEMA public;\n-- Not only simple statements should work\nCREATE TABLE a (aa TEXT);\nCREATE TABLE z (b TEXT, PRIMARY KEY(aa, b)) inherits (a);\n"
  },
  {
    "path": "test/expected/plan_expand_hypertable-15.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\set PREFIX 'EXPLAIN (buffers off, costs off) '\n\\ir include/plan_expand_hypertable_load.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n--single time dimension\nCREATE TABLE hyper (\"time_broken\" bigint NOT NULL, \"value\" integer);\nALTER TABLE hyper\nDROP COLUMN time_broken,\nADD COLUMN time BIGINT;\nSELECT create_hypertable('hyper', 'time',  chunk_time_interval => 10);\n create_hypertable  \n--------------------\n (1,public,hyper,t)\n\nINSERT INTO hyper SELECT g, g FROM generate_series(0,1000) g;\n--insert a point with INT_MAX_64\nINSERT INTO hyper (time, value) SELECT 9223372036854775807::bigint, 0;\n--time and space\nCREATE TABLE hyper_w_space (\"time_broken\" bigint NOT NULL, \"device_id\" text, \"value\" integer);\nALTER TABLE hyper_w_space\nDROP COLUMN time_broken,\nADD COLUMN time BIGINT;\nSELECT create_hypertable('hyper_w_space', 'time', 'device_id', 4, chunk_time_interval => 10);\n     create_hypertable      \n----------------------------\n (2,public,hyper_w_space,t)\n\nINSERT INTO hyper_w_space (time, device_id, value) SELECT g, 'dev' || g, g FROM generate_series(0,30) g;\nCREATE VIEW hyper_w_space_view AS (SELECT * FROM hyper_w_space);\n--with timestamp and space\nCREATE TABLE tag (id serial PRIMARY KEY, name text);\nCREATE TABLE hyper_ts (\"time_broken\" timestamptz NOT NULL, \"device_id\" text, tag_id INT REFERENCES tag(id), \"value\" integer);\nALTER TABLE hyper_ts\nDROP COLUMN time_broken,\nADD COLUMN time TIMESTAMPTZ;\nSELECT create_hypertable('hyper_ts', 'time', 'device_id', 2, chunk_time_interval => '10 seconds'::interval);\n   create_hypertable   \n-----------------------\n (3,public,hyper_ts,t)\n\nINSERT INTO tag(name) SELECT 'tag'||g FROM generate_series(0,10) g;\nINSERT INTO hyper_ts (time, device_id, tag_id, value) SELECT to_timestamp(g), 'dev' || g, (random() /10)+1, g FROM generate_series(0,30) g;\n--one in the future\nINSERT INTO hyper_ts (time, device_id, tag_id, value)  VALUES ('2100-01-01 02:03:04 PST', 'dev101', 1, 0);\n--time partitioning function\nCREATE OR REPLACE FUNCTION unix_to_timestamp(unixtime float8)\n    RETURNS TIMESTAMPTZ LANGUAGE SQL IMMUTABLE PARALLEL SAFE STRICT AS\n$BODY$\n    SELECT to_timestamp(unixtime);\n$BODY$;\nCREATE TABLE hyper_timefunc (\"time\" float8 NOT NULL, \"device_id\" text, \"value\" integer);\nSELECT create_hypertable('hyper_timefunc', 'time', 'device_id', 4, chunk_time_interval => 10, time_partitioning_func => 'unix_to_timestamp');\npsql:include/plan_expand_hypertable_load.sql:57: WARNING:  unexpected interval: smaller than one second\n      create_hypertable      \n-----------------------------\n (4,public,hyper_timefunc,t)\n\nINSERT INTO hyper_timefunc (time, device_id, value) SELECT g, 'dev' || g, g FROM generate_series(0,30) g;\nCREATE TABLE metrics_timestamp(time timestamp);\nSELECT create_hypertable('metrics_timestamp','time');\npsql:include/plan_expand_hypertable_load.sql:62: WARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n       create_hypertable        \n--------------------------------\n (5,public,metrics_timestamp,t)\n\nINSERT INTO metrics_timestamp SELECT generate_series('2000-01-01'::timestamp,'2000-02-01'::timestamp,'1d'::interval);\nCREATE TABLE metrics_timestamptz(time timestamptz, device_id int);\nSELECT create_hypertable('metrics_timestamptz','time');\n        create_hypertable         \n----------------------------------\n (6,public,metrics_timestamptz,t)\n\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval), 1;\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval), 2;\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval), 3;\n--create a second table to test joins with\nCREATE TABLE metrics_timestamptz_2 (LIKE metrics_timestamptz);\nSELECT create_hypertable('metrics_timestamptz_2','time');\n         create_hypertable          \n------------------------------------\n (7,public,metrics_timestamptz_2,t)\n\nINSERT INTO metrics_timestamptz_2\nSELECT * FROM metrics_timestamptz;\nINSERT INTO metrics_timestamptz_2 VALUES ('2000-12-01'::timestamptz, 3);\nCREATE TABLE metrics_date(time date);\nSELECT create_hypertable('metrics_date','time');\n     create_hypertable     \n---------------------------\n (8,public,metrics_date,t)\n\nINSERT INTO metrics_date SELECT generate_series('2000-01-01'::date,'2000-02-01'::date,'1d'::interval);\nANALYZE hyper;\nANALYZE hyper_w_space;\nANALYZE tag;\nANALYZE hyper_ts;\nANALYZE hyper_timefunc;\n-- create normal table for JOIN tests\nCREATE TABLE regular_timestamptz(time timestamptz);\nINSERT INTO regular_timestamptz SELECT generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval);\n\\ir include/plan_expand_hypertable_query.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n--we want to see how our logic excludes chunks\n--and not how much work constraint_exclusion does\nSET constraint_exclusion = 'off';\n\\qecho test upper bounds\ntest upper bounds\n:PREFIX SELECT * FROM hyper WHERE time < 10 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_1_chunk.value\n   ->  Seq Scan on _hyper_1_1_chunk\n\n:PREFIX SELECT * FROM hyper WHERE time < 11 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (\"time\" < 11)\n\n:PREFIX SELECT * FROM hyper WHERE time = 10 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_2_chunk.value\n   ->  Seq Scan on _hyper_1_2_chunk\n         Filter: (\"time\" = 10)\n\n:PREFIX SELECT * FROM hyper WHERE 10 >= time ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (10 >= \"time\")\n\n\\qecho test lower bounds\ntest lower bounds\n:PREFIX SELECT * FROM hyper WHERE time >= 10 and time < 20 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_2_chunk.value\n   ->  Seq Scan on _hyper_1_2_chunk\n\n:PREFIX SELECT * FROM hyper WHERE 10 < time and 20 >= time ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: ((10 < \"time\") AND (20 >= \"time\"))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((10 < \"time\") AND (20 >= \"time\"))\n\n:PREFIX SELECT * FROM hyper WHERE time >= 9 and time < 20 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: ((\"time\" >= 9) AND (\"time\" < 20))\n         ->  Seq Scan on _hyper_1_2_chunk\n\n:PREFIX SELECT * FROM hyper WHERE time > 9 and time < 20 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_2_chunk.value\n   ->  Seq Scan on _hyper_1_2_chunk\n\n\\qecho test empty result\ntest empty result\n:PREFIX SELECT * FROM hyper WHERE time < 0;\n--- QUERY PLAN ---\n Result\n   One-Time Filter: false\n\n\\qecho test expression evaluation\ntest expression evaluation\n:PREFIX SELECT * FROM hyper WHERE time < (5*2)::smallint;\n--- QUERY PLAN ---\n Seq Scan on _hyper_1_1_chunk\n\n\\qecho test logic at INT64_MAX\ntest logic at INT64_MAX\n:PREFIX SELECT * FROM hyper WHERE time = 9223372036854775807::bigint ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_102_chunk.value\n   ->  Seq Scan on _hyper_1_102_chunk\n         Filter: (\"time\" = '9223372036854775807'::bigint)\n\n:PREFIX SELECT * FROM hyper WHERE time = 9223372036854775806::bigint ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_102_chunk.value\n   ->  Seq Scan on _hyper_1_102_chunk\n         Filter: (\"time\" = '9223372036854775806'::bigint)\n\n:PREFIX SELECT * FROM hyper WHERE time >= 9223372036854775807::bigint ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_102_chunk.value\n   ->  Seq Scan on _hyper_1_102_chunk\n         Filter: (\"time\" >= '9223372036854775807'::bigint)\n\n:PREFIX SELECT * FROM hyper WHERE time > 9223372036854775807::bigint ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: value\n   ->  Result\n         One-Time Filter: false\n\n:PREFIX SELECT * FROM hyper WHERE time > 9223372036854775806::bigint ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_102_chunk.value\n   ->  Seq Scan on _hyper_1_102_chunk\n         Filter: (\"time\" > '9223372036854775806'::bigint)\n\n\\qecho cte\ncte\n:PREFIX WITH cte AS(\n  SELECT * FROM hyper WHERE time < 10\n)\nSELECT * FROM cte ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_1_chunk.value\n   ->  Seq Scan on _hyper_1_1_chunk\n\n\\qecho subquery\nsubquery\n:PREFIX SELECT 0 = ANY (SELECT value FROM hyper WHERE time < 10);\n--- QUERY PLAN ---\n Result\n   SubPlan 1\n     ->  Seq Scan on _hyper_1_1_chunk\n\n\\qecho no space constraint\nno space constraint\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (\"time\" < 10)\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (\"time\" < 10)\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (\"time\" < 10)\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (\"time\" < 10)\n\n\\qecho valid space constraint\nvalid space constraint\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 and device_id = 'dev5' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND (device_id = 'dev5'::text))\n\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 and 'dev5' = device_id ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND ('dev5'::text = device_id))\n\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 and 'dev'||(2+3) = device_id ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND ('dev5'::text = device_id))\n\n\\qecho only space constraint\nonly space constraint\n:PREFIX SELECT * FROM hyper_w_space WHERE 'dev5' = device_id ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ('dev5'::text = device_id)\n         ->  Seq Scan on _hyper_2_109_chunk\n               Filter: ('dev5'::text = device_id)\n         ->  Seq Scan on _hyper_2_111_chunk\n               Filter: ('dev5'::text = device_id)\n\n\\qecho unhandled space constraint\nunhandled space constraint\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 and device_id > 'dev5' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: ((\"time\" < 10) AND (device_id > 'dev5'::text))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: ((\"time\" < 10) AND (device_id > 'dev5'::text))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: ((\"time\" < 10) AND (device_id > 'dev5'::text))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ((\"time\" < 10) AND (device_id > 'dev5'::text))\n\n\\qecho use of OR - does not filter chunks\nuse of OR - does not filter chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND (device_id = 'dev5' or device_id = 'dev6') ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: ((\"time\" < 10) AND ((device_id = 'dev5'::text) OR (device_id = 'dev6'::text)))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: ((\"time\" < 10) AND ((device_id = 'dev5'::text) OR (device_id = 'dev6'::text)))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: ((\"time\" < 10) AND ((device_id = 'dev5'::text) OR (device_id = 'dev6'::text)))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ((\"time\" < 10) AND ((device_id = 'dev5'::text) OR (device_id = 'dev6'::text)))\n\n\\qecho cte\ncte\n:PREFIX WITH cte AS(\n   SELECT * FROM hyper_w_space WHERE time < 10 and device_id = 'dev5'\n)\nSELECT * FROM cte ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND (device_id = 'dev5'::text))\n\n\\qecho subquery\nsubquery\n:PREFIX SELECT 0 = ANY (SELECT value FROM hyper_w_space WHERE time < 10 and device_id = 'dev5');\n--- QUERY PLAN ---\n Result\n   SubPlan 1\n     ->  Seq Scan on _hyper_2_106_chunk\n           Filter: ((\"time\" < 10) AND (device_id = 'dev5'::text))\n\n\\qecho view\nview\n:PREFIX SELECT * FROM hyper_w_space_view WHERE time < 10 and device_id = 'dev5' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND (device_id = 'dev5'::text))\n\n\\qecho IN statement - simple\nIN statement - simple\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id IN ('dev5') ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND (device_id = 'dev5'::text))\n\n\\qecho IN statement - two chunks\nIN statement - two chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id IN ('dev5','dev6') ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: ((\"time\" < 10) AND (device_id = ANY ('{dev5,dev6}'::text[])))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ((\"time\" < 10) AND (device_id = ANY ('{dev5,dev6}'::text[])))\n\n\\qecho IN statement - one chunk\nIN statement - one chunk\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id IN ('dev4','dev5') ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND (device_id = ANY ('{dev4,dev5}'::text[])))\n\n\\qecho NOT IN - does not filter chunks\nNOT IN - does not filter chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id NOT IN ('dev5','dev6') ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: ((\"time\" < 10) AND (device_id <> ALL ('{dev5,dev6}'::text[])))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: ((\"time\" < 10) AND (device_id <> ALL ('{dev5,dev6}'::text[])))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: ((\"time\" < 10) AND (device_id <> ALL ('{dev5,dev6}'::text[])))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ((\"time\" < 10) AND (device_id <> ALL ('{dev5,dev6}'::text[])))\n\n\\qecho IN statement with subquery - does not filter chunks\nIN statement with subquery - does not filter chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id IN (SELECT 'dev5'::text) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND (device_id = 'dev5'::text))\n\n\\qecho ANY\nANY\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id = ANY(ARRAY['dev5','dev6']) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: ((\"time\" < 10) AND (device_id = ANY ('{dev5,dev6}'::text[])))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ((\"time\" < 10) AND (device_id = ANY ('{dev5,dev6}'::text[])))\n\n\\qecho ANY with intersection\nANY with intersection\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id = ANY(ARRAY['dev5','dev6']) AND device_id = ANY(ARRAY['dev6','dev7']) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_105_chunk.value\n   ->  Seq Scan on _hyper_2_105_chunk\n         Filter: ((\"time\" < 10) AND (device_id = ANY ('{dev5,dev6}'::text[])) AND (device_id = ANY ('{dev6,dev7}'::text[])))\n\n\\qecho ANY without intersection shouldnt scan any chunks\nANY without intersection shouldnt scan any chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id = ANY(ARRAY['dev5','dev6']) AND device_id = ANY(ARRAY['dev8','dev9']) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: value\n   ->  Result\n         One-Time Filter: false\n\n\\qecho ANY/IN/ALL only works for equals operator\nANY/IN/ALL only works for equals operator\n:PREFIX SELECT * FROM hyper_w_space WHERE device_id < ANY(ARRAY['dev5','dev6']) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_107_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_108_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_109_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_110_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_111_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_112_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_113_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_114_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_115_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n\n\\qecho ALL with equals and different values shouldnt scan any chunks\nALL with equals and different values shouldnt scan any chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE device_id = ALL(ARRAY['dev5','dev6']) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: value\n   ->  Result\n         One-Time Filter: false\n\n\\qecho Multi AND\nMulti AND\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND time < 100 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: ((\"time\" < 10) AND (\"time\" < 100))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: ((\"time\" < 10) AND (\"time\" < 100))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: ((\"time\" < 10) AND (\"time\" < 100))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ((\"time\" < 10) AND (\"time\" < 100))\n\n\\qecho Time dimension doesnt filter chunks when using non-equality IN/ANY with multiple arguments\nTime dimension doesnt filter chunks when using non-equality IN/ANY with multiple arguments\n:PREFIX SELECT * FROM hyper_w_space WHERE time < ANY(ARRAY[1,2]) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_107_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_108_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_109_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_110_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_111_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_112_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_113_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_114_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_115_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n\n\\qecho Time dimension chunk exclusion with IN/ANY equality uses bounding range\nTime dimension chunk exclusion with IN/ANY equality uses bounding range\n:PREFIX SELECT * FROM hyper WHERE time IN (5, 15) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n\n:PREFIX SELECT * FROM hyper WHERE time = ANY(ARRAY[5, 15, 25]) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (\"time\" = ANY ('{5,15,25}'::integer[]))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (\"time\" = ANY ('{5,15,25}'::integer[]))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: (\"time\" = ANY ('{5,15,25}'::integer[]))\n\n:PREFIX SELECT * FROM hyper WHERE time = ANY(ARRAY[25, 15, 5]) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (\"time\" = ANY ('{25,15,5}'::integer[]))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (\"time\" = ANY ('{25,15,5}'::integer[]))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: (\"time\" = ANY ('{25,15,5}'::integer[]))\n\n:PREFIX SELECT * FROM hyper_w_space WHERE time IN (5, 15) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_107_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_108_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_109_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_110_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time IN ('2000-01-05'::timestamp, '2000-01-15'::timestamp) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Index Cond: (\"time\" = ANY ('{\"Wed Jan 05 00:00:00 2000\",\"Sat Jan 15 00:00:00 2000\"}'::timestamp without time zone[]))\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" = ANY ('{\"Wed Jan 05 00:00:00 2000\",\"Sat Jan 15 00:00:00 2000\"}'::timestamp without time zone[]))\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Index Cond: (\"time\" = ANY ('{\"Wed Jan 05 00:00:00 2000\",\"Sat Jan 15 00:00:00 2000\"}'::timestamp without time zone[]))\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time IN ('2000-01-05'::timestamptz, '2000-01-15'::timestamptz) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Index Cond: (\"time\" = ANY ('{\"Wed Jan 05 00:00:00 2000 PST\",\"Sat Jan 15 00:00:00 2000 PST\"}'::timestamp with time zone[]))\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" = ANY ('{\"Wed Jan 05 00:00:00 2000 PST\",\"Sat Jan 15 00:00:00 2000 PST\"}'::timestamp with time zone[]))\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Index Cond: (\"time\" = ANY ('{\"Wed Jan 05 00:00:00 2000 PST\",\"Sat Jan 15 00:00:00 2000 PST\"}'::timestamp with time zone[]))\n\n:PREFIX SELECT * FROM metrics_date WHERE time IN ('2000-01-05'::date, '2000-01-15'::date) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date\n   Order: metrics_date.\"time\"\n   ->  Index Only Scan Backward using _hyper_8_171_chunk_metrics_date_time_idx on _hyper_8_171_chunk\n         Index Cond: (\"time\" = ANY ('{01-05-2000,01-15-2000}'::date[]))\n   ->  Index Only Scan Backward using _hyper_8_172_chunk_metrics_date_time_idx on _hyper_8_172_chunk\n         Index Cond: (\"time\" = ANY ('{01-05-2000,01-15-2000}'::date[]))\n   ->  Index Only Scan Backward using _hyper_8_173_chunk_metrics_date_time_idx on _hyper_8_173_chunk\n         Index Cond: (\"time\" = ANY ('{01-05-2000,01-15-2000}'::date[]))\n\n\\qecho cross-type IN/ANY: timestamp to timestamptz column does not use bounding range (stable cast)\ncross-type IN/ANY: timestamp to timestamptz column does not use bounding range (stable cast)\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time IN ('2000-01-05'::timestamp, '2000-01-15'::timestamp) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 3\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Index Cond: (\"time\" = ANY (ARRAY[('Wed Jan 05 00:00:00 2000'::timestamp without time zone)::timestamp with time zone, ('Sat Jan 15 00:00:00 2000'::timestamp without time zone)::timestamp with time zone]))\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Index Cond: (\"time\" = ANY (ARRAY[('Wed Jan 05 00:00:00 2000'::timestamp without time zone)::timestamp with time zone, ('Sat Jan 15 00:00:00 2000'::timestamp without time zone)::timestamp with time zone]))\n\n\\qecho Time dimension chunk filtering works for ANY with single argument\nTime dimension chunk filtering works for ANY with single argument\n:PREFIX SELECT * FROM hyper_w_space WHERE time < ANY(ARRAY[1]) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (\"time\" < ANY ('{1}'::integer[]))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (\"time\" < ANY ('{1}'::integer[]))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (\"time\" < ANY ('{1}'::integer[]))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (\"time\" < ANY ('{1}'::integer[]))\n\n\\qecho Time dimension chunk filtering works for ALL with single argument\nTime dimension chunk filtering works for ALL with single argument\n:PREFIX SELECT * FROM hyper_w_space WHERE time < ALL(ARRAY[1]) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (\"time\" < ALL ('{1}'::integer[]))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (\"time\" < ALL ('{1}'::integer[]))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (\"time\" < ALL ('{1}'::integer[]))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (\"time\" < ALL ('{1}'::integer[]))\n\n\\qecho Time dimension chunk filtering works for ALL with multiple arguments\nTime dimension chunk filtering works for ALL with multiple arguments\n:PREFIX SELECT * FROM hyper_w_space WHERE time < ALL(ARRAY[1,10,20,30]) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (\"time\" < ALL ('{1,10,20,30}'::integer[]))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (\"time\" < ALL ('{1,10,20,30}'::integer[]))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (\"time\" < ALL ('{1,10,20,30}'::integer[]))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (\"time\" < ALL ('{1,10,20,30}'::integer[]))\n\n\\qecho AND intersection using IN and EQUALS\nAND intersection using IN and EQUALS\n:PREFIX SELECT * FROM hyper_w_space WHERE device_id IN ('dev1','dev2') AND device_id = 'dev1' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: ((device_id = ANY ('{dev1,dev2}'::text[])) AND (device_id = 'dev1'::text))\n         ->  Seq Scan on _hyper_2_110_chunk\n               Filter: ((device_id = ANY ('{dev1,dev2}'::text[])) AND (device_id = 'dev1'::text))\n         ->  Seq Scan on _hyper_2_114_chunk\n               Filter: ((device_id = ANY ('{dev1,dev2}'::text[])) AND (device_id = 'dev1'::text))\n\n\\qecho AND with no intersection using IN and EQUALS\nAND with no intersection using IN and EQUALS\n:PREFIX SELECT * FROM hyper_w_space WHERE device_id IN ('dev1','dev2') AND device_id = 'dev3' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: value\n   ->  Result\n         One-Time Filter: false\n\n\\qecho timestamps\ntimestamps\n\\qecho these should work since they are immutable functions\nthese should work since they are immutable functions\n:PREFIX SELECT * FROM hyper_ts WHERE time < 'Wed Dec 31 16:00:10 1969 PST'::timestamptz ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Append\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_3_117_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM hyper_ts WHERE time < to_timestamp(10) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Append\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_3_117_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM hyper_ts WHERE time < 'Wed Dec 31 16:00:10 1969'::timestamp AT TIME ZONE 'PST' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Append\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_3_117_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM hyper_ts WHERE time < to_timestamp(10) and device_id = 'dev1' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_3_116_chunk.value\n   ->  Seq Scan on _hyper_3_116_chunk\n         Filter: ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text))\n\n\\qecho these should not work since uses stable functions;\nthese should not work since uses stable functions;\n:PREFIX SELECT * FROM hyper_ts WHERE time < 'Wed Dec 31 16:00:10 1969'::timestamp ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Custom Scan (ChunkAppend) on hyper_ts\n         Chunks excluded during startup: 6\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969'::timestamp without time zone)\n         ->  Seq Scan on _hyper_3_117_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969'::timestamp without time zone)\n\n:PREFIX SELECT * FROM hyper_ts WHERE time < ('Wed Dec 31 16:00:10 1969'::timestamp::timestamptz) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Custom Scan (ChunkAppend) on hyper_ts\n         Chunks excluded during startup: 6\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: (\"time\" < ('Wed Dec 31 16:00:10 1969'::timestamp without time zone)::timestamp with time zone)\n         ->  Seq Scan on _hyper_3_117_chunk\n               Filter: (\"time\" < ('Wed Dec 31 16:00:10 1969'::timestamp without time zone)::timestamp with time zone)\n\n:PREFIX SELECT * FROM hyper_ts WHERE NOW() < time ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Custom Scan (ChunkAppend) on hyper_ts\n         Chunks excluded during startup: 7\n         ->  Seq Scan on _hyper_3_123_chunk\n               Filter: (now() < \"time\")\n\n\\qecho joins\njoins\n:PREFIX SELECT * FROM hyper_ts WHERE tag_id IN (SELECT id FROM tag WHERE tag.id=1) and time < to_timestamp(10) and device_id = 'dev1' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_3_116_chunk.value\n   ->  Nested Loop Semi Join\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text) AND (tag_id = 1))\n         ->  Seq Scan on tag\n               Filter: (id = 1)\n\n:PREFIX SELECT * FROM hyper_ts WHERE tag_id IN (SELECT id FROM tag WHERE tag.id=1) or (time < to_timestamp(10) and device_id = 'dev1') ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Custom Scan (ChunkAppend) on hyper_ts\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: ((hashed SubPlan 1) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n               SubPlan 1\n                 ->  Seq Scan on tag\n                       Filter: (id = 1)\n         ->  Seq Scan on _hyper_3_117_chunk\n               Filter: ((hashed SubPlan 1) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n         ->  Seq Scan on _hyper_3_118_chunk\n               Filter: ((hashed SubPlan 1) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n         ->  Seq Scan on _hyper_3_119_chunk\n               Filter: ((hashed SubPlan 1) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n         ->  Seq Scan on _hyper_3_120_chunk\n               Filter: ((hashed SubPlan 1) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n         ->  Seq Scan on _hyper_3_121_chunk\n               Filter: ((hashed SubPlan 1) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n         ->  Seq Scan on _hyper_3_122_chunk\n               Filter: ((hashed SubPlan 1) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n         ->  Seq Scan on _hyper_3_123_chunk\n               Filter: ((hashed SubPlan 1) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n\n:PREFIX SELECT * FROM hyper_ts WHERE tag_id IN (SELECT id FROM tag WHERE tag.name='tag1') and time < to_timestamp(10) and device_id = 'dev1' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_3_116_chunk.value\n   ->  Nested Loop\n         Join Filter: (_hyper_3_116_chunk.tag_id = tag.id)\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text))\n         ->  Seq Scan on tag\n               Filter: (name = 'tag1'::text)\n\n:PREFIX SELECT * FROM hyper_ts JOIN tag on (hyper_ts.tag_id = tag.id ) WHERE time < to_timestamp(10) and device_id = 'dev1' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_3_116_chunk.value\n   ->  Merge Join\n         Merge Cond: (tag.id = _hyper_3_116_chunk.tag_id)\n         ->  Index Scan using tag_pkey on tag\n         ->  Sort\n               Sort Key: _hyper_3_116_chunk.tag_id\n               ->  Seq Scan on _hyper_3_116_chunk\n                     Filter: ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text))\n\n:PREFIX SELECT * FROM hyper_ts JOIN tag on (hyper_ts.tag_id = tag.id ) WHERE tag.name = 'tag1' and time < to_timestamp(10) and device_id = 'dev1' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_3_116_chunk.value\n   ->  Nested Loop\n         Join Filter: (_hyper_3_116_chunk.tag_id = tag.id)\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text))\n         ->  Seq Scan on tag\n               Filter: (name = 'tag1'::text)\n\n\\qecho test constraint exclusion for constraints in ON clause of JOINs\ntest constraint exclusion for constraints in ON clause of JOINs\n\\qecho should exclude chunks on m1 and propagate qual to m2 because of INNER JOIN\nshould exclude chunks on m1 and propagate qual to m2 because of INNER JOIN\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho should exclude chunks on m2 and propagate qual to m1 because of INNER JOIN\nshould exclude chunks on m2 and propagate qual to m1 because of INNER JOIN\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho must not exclude on m1\nmust not exclude on m1\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Left Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   Join Filter: (m1.\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n\\qecho should exclude chunks on m2\nshould exclude chunks on m2\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Left Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho should exclude chunks on m1\nshould exclude chunks on m1\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: m1.\"time\"\n   ->  Merge Right Join\n         Merge Cond: (m1.\"time\" = m2.\"time\")\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n               Order: m1.\"time\"\n               ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Materialize\n               ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n                     Order: m2.\"time\"\n                     ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n                     ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n                     ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n                     ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n\\qecho must not exclude chunks on m2\nmust not exclude chunks on m2\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time < '2000-01-10' ORDER BY m1.time, m2.time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: m1.\"time\", m2.\"time\"\n   ->  Merge Left Join\n         Merge Cond: (m2.\"time\" = m1.\"time\")\n         Join Filter: (m2.\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n         ->  Materialize\n               ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n                     Order: m1.\"time\"\n                     ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n                     ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n                     ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n                     ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n                     ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n\n\\qecho time_bucket exclusion\ntime_bucket exclusion\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time) < 10::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_1_chunk.\"time\"\n   ->  Seq Scan on _hyper_1_1_chunk\n         Filter: (time_bucket('10'::bigint, \"time\") < '10'::bigint)\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time) < 11::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '11'::bigint)\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '11'::bigint)\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((\"time\" < '21'::bigint) AND (time_bucket('10'::bigint, \"time\") < '11'::bigint))\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time) <= 10::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") <= '10'::bigint)\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") <= '10'::bigint)\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((\"time\" <= '20'::bigint) AND (time_bucket('10'::bigint, \"time\") <= '10'::bigint))\n\n:PREFIX SELECT * FROM hyper WHERE 10::bigint > time_bucket(10, time) ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_1_chunk.\"time\"\n   ->  Seq Scan on _hyper_1_1_chunk\n         Filter: ('10'::bigint > time_bucket('10'::bigint, \"time\"))\n\n:PREFIX SELECT * FROM hyper WHERE 11::bigint > time_bucket(10, time) ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: ('11'::bigint > time_bucket('10'::bigint, \"time\"))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: ('11'::bigint > time_bucket('10'::bigint, \"time\"))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((\"time\" < '21'::bigint) AND ('11'::bigint > time_bucket('10'::bigint, \"time\")))\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time, 5) < 10::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (time_bucket('10'::bigint, \"time\", '5'::bigint) < '10'::bigint)\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (time_bucket('10'::bigint, \"time\", '5'::bigint) < '10'::bigint)\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time, 5) < 11::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (time_bucket('10'::bigint, \"time\", '5'::bigint) < '11'::bigint)\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (time_bucket('10'::bigint, \"time\", '5'::bigint) < '11'::bigint)\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((\"time\" < '21'::bigint) AND (time_bucket('10'::bigint, \"time\", '5'::bigint) < '11'::bigint))\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time, 5) <= 10::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (time_bucket('10'::bigint, \"time\", '5'::bigint) <= '10'::bigint)\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (time_bucket('10'::bigint, \"time\", '5'::bigint) <= '10'::bigint)\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((\"time\" <= '20'::bigint) AND (time_bucket('10'::bigint, \"time\", '5'::bigint) <= '10'::bigint))\n\n:PREFIX SELECT * FROM hyper WHERE 10::bigint > time_bucket(10, time, 5) ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: ('10'::bigint > time_bucket('10'::bigint, \"time\", '5'::bigint))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: ('10'::bigint > time_bucket('10'::bigint, \"time\", '5'::bigint))\n\n:PREFIX SELECT * FROM hyper WHERE 11::bigint > time_bucket(10, time, 5) ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: ('11'::bigint > time_bucket('10'::bigint, \"time\", '5'::bigint))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: ('11'::bigint > time_bucket('10'::bigint, \"time\", '5'::bigint))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((\"time\" < '21'::bigint) AND ('11'::bigint > time_bucket('10'::bigint, \"time\", '5'::bigint)))\n\n\\qecho timestamp time_bucket exclusion\ntimestamp time_bucket exclusion\nSELECT count(DISTINCT tableoid) FROM metrics_timestamp;\n count \n-------\n     5\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time) < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") < 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") < 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time) <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") <= 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") <= 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time) > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time) >= '2000-01-15' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Index Cond: (\"time\" >= 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") >= 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") >= 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") >= 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'3d'::interval) < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) < 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) < 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'3d'::interval) <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) <= 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) <= 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'3d'::interval) > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'3d'::interval) >= '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Index Cond: (\"time\" >= 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) >= 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) >= 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'2000-01-10'::timestamp) < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) < 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) < 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'2000-01-10'::timestamp) <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) <= 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) <= 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'2000-01-10'::timestamp) > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'2000-01-10'::timestamp) >= '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Index Cond: (\"time\" >= 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) >= 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) >= 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n\n\\qecho timestamptz time_bucket exclusion\ntimestamptz time_bucket exclusion\nSELECT count(DISTINCT tableoid) FROM metrics_timestamptz;\n count \n-------\n     5\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time) < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time) <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time) > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time) >= '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'3d'::interval) < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'3d'::interval) <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'3d'::interval) > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'3d'::interval) >= '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'2000-01-10'::timestamptz) < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'2000-01-10'::timestamptz) <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'2000-01-10'::timestamptz) > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'2000-01-10'::timestamptz) >= '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'Europe/Berlin') < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'Europe/Berlin') <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'Europe/Berlin') > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'Europe/Berlin') >= '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho test overflow behaviour of time_bucket exclusion\ntest overflow behaviour of time_bucket exclusion\n:PREFIX SELECT * FROM hyper WHERE time > 950 AND time_bucket(10, time) < '9223372036854775807'::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_96_chunk\n               Filter: ((\"time\" > 950) AND (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint))\n         ->  Seq Scan on _hyper_1_97_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint)\n         ->  Seq Scan on _hyper_1_98_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint)\n         ->  Seq Scan on _hyper_1_99_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint)\n         ->  Seq Scan on _hyper_1_100_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint)\n         ->  Seq Scan on _hyper_1_101_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint)\n         ->  Seq Scan on _hyper_1_102_chunk\n               Filter: ((\"time\" > 950) AND (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint))\n\n\\qecho test timestamp upper boundary\ntest timestamp upper boundary\n\\qecho there should be no transformation if we are out of the supported (TimescaleDB-specific) range\nthere should be no transformation if we are out of the supported (TimescaleDB-specific) range\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('1d',time) < '294276-01-01'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n\n\\qecho transformation would be out of range\ntransformation would be out of range\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('1000d',time) < '294276-01-01'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n\n\\qecho test timestamptz upper boundary\ntest timestamptz upper boundary\n\\qecho there should be no transformation if we are out of the supported (TimescaleDB-specific) range\nthere should be no transformation if we are out of the supported (TimescaleDB-specific) range\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('1d',time) < '294276-01-01'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n\n\\qecho transformation would be out of range\ntransformation would be out of range\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('1000d',time) < '294276-01-01'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n\n\\qecho time_bucket exclusion with run-time constants\ntime_bucket exclusion with run-time constants\n-- These queries have a stable time_bucket expression, because the text::interval conversion is stable.\nPREPARE P1(text    , text) AS SELECT * FROM metrics_timestamptz WHERE time_bucket($1::interval, time) > $2::timestamptz ORDER BY time;\nPREPARE P2(text    , text) AS SELECT * FROM metrics_timestamp   WHERE time_bucket($1::interval, time) > $2::timestamptz ORDER BY time;\nPREPARE P3(text    , text) AS SELECT * FROM metrics_timestamptz WHERE time_bucket($1::interval, time) > $2::timestamp   ORDER BY time;\nPREPARE P4(text    , text) AS SELECT * FROM metrics_timestamp   WHERE time_bucket($1::interval, time) > $2::timestamp   ORDER BY time;\n-- These queries have an immutable time_bucket expression, because the parameter is passed as interval, and no conversion is involved.\nPREPARE P5(interval, text) AS SELECT * FROM metrics_timestamptz WHERE time_bucket($1::interval, time) > $2::timestamptz ORDER BY time;\nPREPARE P6(interval, text) AS SELECT * FROM metrics_timestamp   WHERE time_bucket($1::interval, time) > $2::timestamptz ORDER BY time;\nPREPARE P7(interval, text) AS SELECT * FROM metrics_timestamptz WHERE time_bucket($1::interval, time) > $2::timestamp   ORDER BY time;\nPREPARE P8(interval, text) AS SELECT * FROM metrics_timestamp   WHERE time_bucket($1::interval, time) > $2::timestamp   ORDER BY time;\nSET plan_cache_mode TO 'force_custom_plan';\n:PREFIX EXECUTE P1('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P2('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P3('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P4('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P5('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P6('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P7('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P8('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P1('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n\n:PREFIX EXECUTE P2('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n\n:PREFIX EXECUTE P3('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n\n:PREFIX EXECUTE P4('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n\n:PREFIX EXECUTE P5('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n\n:PREFIX EXECUTE P6('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n\n:PREFIX EXECUTE P7('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n\n:PREFIX EXECUTE P8('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n\nSET plan_cache_mode TO 'force_generic_plan';\n:PREFIX EXECUTE P1('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P2('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P3('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P4('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P5('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P6('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P7('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P8('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P1('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n\n:PREFIX EXECUTE P2('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n\n:PREFIX EXECUTE P3('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n\n:PREFIX EXECUTE P4('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n\n:PREFIX EXECUTE P5('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n\n:PREFIX EXECUTE P6('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n\n:PREFIX EXECUTE P7('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n\n:PREFIX EXECUTE P8('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n\nRESET plan_cache_mode;\nDEALLOCATE P1;\nDEALLOCATE P2;\nDEALLOCATE P3;\nDEALLOCATE P4;\nDEALLOCATE P5;\nDEALLOCATE P6;\nDEALLOCATE P7;\nDEALLOCATE P8;\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time) > 10 AND time_bucket(10, time) < 100 ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: ((\"time\" > '10'::bigint) AND (\"time\" < '100'::bigint) AND (time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_4_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_5_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_6_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_7_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_8_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_9_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_10_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time) > 10 AND time_bucket(10, time) < 20 ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_2_chunk.\"time\"\n   ->  Seq Scan on _hyper_1_2_chunk\n         Filter: ((\"time\" > '10'::bigint) AND (\"time\" < '20'::bigint) AND (time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 20))\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(1, time) > 11 AND time_bucket(1, time) < 19 ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_2_chunk.\"time\"\n   ->  Seq Scan on _hyper_1_2_chunk\n         Filter: ((\"time\" > '11'::bigint) AND (\"time\" < '19'::bigint) AND (time_bucket('1'::bigint, \"time\") > 11) AND (time_bucket('1'::bigint, \"time\") < 19))\n\n:PREFIX SELECT * FROM hyper WHERE 10 < time_bucket(10, time) AND 20 > time_bucket(10,time) ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_2_chunk.\"time\"\n   ->  Seq Scan on _hyper_1_2_chunk\n         Filter: ((\"time\" > '10'::bigint) AND (\"time\" < '20'::bigint) AND (10 < time_bucket('10'::bigint, \"time\")) AND (20 > time_bucket('10'::bigint, \"time\")))\n\n\\qecho time_bucket exclusion with date\ntime_bucket exclusion with date\n:PREFIX SELECT * FROM metrics_date WHERE time_bucket('1d',time) < '2000-01-03' ORDER BY time;\n--- QUERY PLAN ---\n Index Only Scan Backward using _hyper_8_171_chunk_metrics_date_time_idx on _hyper_8_171_chunk\n   Index Cond: (\"time\" < '01-03-2000'::date)\n   Filter: (time_bucket('@ 1 day'::interval, \"time\") < '01-03-2000'::date)\n\n:PREFIX SELECT * FROM metrics_date WHERE time_bucket('1d',time) >= '2000-01-03' AND time_bucket('1d',time) <= '2000-01-10' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date\n   Order: metrics_date.\"time\"\n   ->  Index Only Scan Backward using _hyper_8_171_chunk_metrics_date_time_idx on _hyper_8_171_chunk\n         Index Cond: ((\"time\" >= '01-03-2000'::date) AND (\"time\" <= '01-11-2000'::date))\n         Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= '01-03-2000'::date) AND (time_bucket('@ 1 day'::interval, \"time\") <= '01-10-2000'::date))\n   ->  Index Only Scan Backward using _hyper_8_172_chunk_metrics_date_time_idx on _hyper_8_172_chunk\n         Index Cond: ((\"time\" >= '01-03-2000'::date) AND (\"time\" <= '01-11-2000'::date))\n         Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= '01-03-2000'::date) AND (time_bucket('@ 1 day'::interval, \"time\") <= '01-10-2000'::date))\n\n\\qecho time_bucket exclusion with timestamp\ntime_bucket exclusion with timestamp\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('1d',time) < '2000-01-03' ORDER BY time;\n--- QUERY PLAN ---\n Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n   Index Cond: (\"time\" < 'Mon Jan 03 00:00:00 2000'::timestamp without time zone)\n   Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Mon Jan 03 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('1d',time) >= '2000-01-03' AND time_bucket('1d',time) <= '2000-01-10' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000'::timestamp without time zone) AND (\"time\" <= 'Tue Jan 11 00:00:00 2000'::timestamp without time zone))\n         Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000'::timestamp without time zone) AND (time_bucket('@ 1 day'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000'::timestamp without time zone))\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000'::timestamp without time zone) AND (\"time\" <= 'Tue Jan 11 00:00:00 2000'::timestamp without time zone))\n         Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000'::timestamp without time zone) AND (time_bucket('@ 1 day'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000'::timestamp without time zone))\n\n\\qecho time_bucket exclusion with timestamptz\ntime_bucket exclusion with timestamptz\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('6h',time) < '2000-01-03' ORDER BY time;\n--- QUERY PLAN ---\n Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n   Index Cond: (\"time\" < 'Mon Jan 03 06:00:00 2000 PST'::timestamp with time zone)\n   Filter: (time_bucket('@ 6 hours'::interval, \"time\") < 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('6h',time) >= '2000-01-03' AND time_bucket('6h',time) <= '2000-01-10' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" <= 'Mon Jan 10 06:00:00 2000 PST'::timestamp with time zone))\n         Filter: ((time_bucket('@ 6 hours'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 6 hours'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" <= 'Mon Jan 10 06:00:00 2000 PST'::timestamp with time zone))\n         Filter: ((time_bucket('@ 6 hours'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 6 hours'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho time_bucket exclusion with timestamptz and day interval\ntime_bucket exclusion with timestamptz and day interval\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('1d',time) < '2000-01-03' ORDER BY time;\n--- QUERY PLAN ---\n Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n   Index Cond: (\"time\" < 'Tue Jan 04 00:00:00 2000 PST'::timestamp with time zone)\n   Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('1d',time) >= '2000-01-03' AND time_bucket('1d',time) <= '2000-01-10' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" <= 'Tue Jan 11 00:00:00 2000 PST'::timestamp with time zone))\n         Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 1 day'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" <= 'Tue Jan 11 00:00:00 2000 PST'::timestamp with time zone))\n         Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 1 day'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('1d',time) >= '2000-01-03' AND time_bucket('7d',time) <= '2000-01-10' ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: metrics_timestamptz.\"time\"\n   ->  Append\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n               Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" <= 'Mon Jan 17 00:00:00 2000 PST'::timestamp with time zone))\n               Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 7 days'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Seq Scan on _hyper_6_161_chunk\n               Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 7 days'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n               Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" <= 'Mon Jan 17 00:00:00 2000 PST'::timestamp with time zone))\n               Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 7 days'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho no transformation\nno transformation\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10 + floor(random())::int, time) > 10 AND time_bucket(10 + floor(random())::int, time) < 100 AND time < 150 ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Custom Scan (ChunkAppend) on hyper\n         Chunks excluded during startup: 0\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_4_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_5_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_6_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_7_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_8_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_9_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_10_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_11_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_12_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_13_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_14_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_15_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n\n\\qecho exclude chunks based on time column with partitioning function. This\nexclude chunks based on time column with partitioning function. This\n\\qecho transparently applies the time partitioning function on the time\ntransparently applies the time partitioning function on the time\n\\qecho value to be able to exclude chunks (similar to a closed dimension).\nvalue to be able to exclude chunks (similar to a closed dimension).\n:PREFIX SELECT * FROM hyper_timefunc WHERE time < 4 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_timefunc.value\n   ->  Append\n         ->  Seq Scan on _hyper_4_124_chunk\n               Filter: (\"time\" < '4'::double precision)\n         ->  Seq Scan on _hyper_4_125_chunk\n               Filter: (\"time\" < '4'::double precision)\n         ->  Seq Scan on _hyper_4_126_chunk\n               Filter: (\"time\" < '4'::double precision)\n         ->  Seq Scan on _hyper_4_127_chunk\n               Filter: (\"time\" < '4'::double precision)\n\n\\qecho excluding based on time expression is currently unoptimized\nexcluding based on time expression is currently unoptimized\n:PREFIX SELECT * FROM hyper_timefunc WHERE unix_to_timestamp(time) < 'Wed Dec 31 16:00:04 1969 PST' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_timefunc.value\n   ->  Append\n         ->  Seq Scan on _hyper_4_124_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_125_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_126_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_127_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_128_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_129_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_130_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_131_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_132_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_133_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_134_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_135_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_136_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_137_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_138_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_139_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_140_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_141_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_142_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_143_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_144_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_145_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_146_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_147_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_148_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_149_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_150_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_151_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_152_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_153_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_154_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n\n\\qecho test qual propagation for joins\ntest qual propagation for joins\nRESET constraint_exclusion;\n\\qecho nothing to propagate\nnothing to propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1, metrics_timestamptz_2 m2 WHERE m1.time = m2.time ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Left Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time ORDER BY m1.time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: m1.\"time\"\n   ->  Merge Right Join\n         Merge Cond: (m1.\"time\" = m2.\"time\")\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n               Order: m1.\"time\"\n               ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n               ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n               ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n         ->  Materialize\n               ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n                     Order: m2.\"time\"\n                     ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n                     ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n                     ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n                     ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n\\qecho OR constraints should not propagate\nOR constraints should not propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10' OR m1.time > '2001-01-01' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Filter: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) OR (\"time\" > 'Mon Jan 01 00:00:00 2001 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Filter: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) OR (\"time\" > 'Mon Jan 01 00:00:00 2001 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n\\qecho test single constraint\ntest single constraint\n\\qecho constraint should be on both scans\nconstraint should be on both scans\n\\qecho these will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\nthese will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1, metrics_timestamptz_2 m2 WHERE m1.time = m2.time AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Left Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho test 2 constraints on single relation\ntest 2 constraints on single relation\n\\qecho these will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\nthese will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1, metrics_timestamptz_2 m2 WHERE m1.time = m2.time AND m1.time > '2000-01-01' AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Nested Loop Left Join\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Append\n         ->  Index Only Scan using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n               Index Cond: (\"time\" = m1.\"time\")\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho test 2 constraints with 1 constraint on each relation\ntest 2 constraints with 1 constraint on each relation\n\\qecho these will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\nthese will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1, metrics_timestamptz_2 m2 WHERE m1.time = m2.time AND m1.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho test constraints in ON clause of INNER JOIN\ntest constraints in ON clause of INNER JOIN\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho test constraints in ON clause of LEFT JOIN\ntest constraints in ON clause of LEFT JOIN\n\\qecho must not propagate\nmust not propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Left Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho test constraints in ON clause of RIGHT JOIN\ntest constraints in ON clause of RIGHT JOIN\n\\qecho must not propagate\nmust not propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Gather Merge\n   Workers Planned: 2\n   ->  Sort\n         Sort Key: m1.\"time\"\n         ->  Parallel Hash Left Join\n               Hash Cond: (m2.\"time\" = m1.\"time\")\n               Join Filter: ((m2.\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (m2.\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_7_165_chunk m2_1\n                     ->  Parallel Seq Scan on _hyper_7_166_chunk m2_2\n                     ->  Parallel Seq Scan on _hyper_7_167_chunk m2_3\n                     ->  Parallel Seq Scan on _hyper_7_168_chunk m2_4\n                     ->  Parallel Seq Scan on _hyper_7_169_chunk m2_5\n                     ->  Parallel Seq Scan on _hyper_7_170_chunk m2_6\n               ->  Parallel Hash\n                     ->  Parallel Append\n                           ->  Parallel Seq Scan on _hyper_6_160_chunk m1_1\n                           ->  Parallel Seq Scan on _hyper_6_161_chunk m1_2\n                           ->  Parallel Seq Scan on _hyper_6_162_chunk m1_3\n                           ->  Parallel Seq Scan on _hyper_6_163_chunk m1_4\n                           ->  Parallel Seq Scan on _hyper_6_164_chunk m1_5\n\n\\qecho test equality condition not in ON clause\ntest equality condition not in ON clause\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON true WHERE m2.time = m1.time AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho test constraints not joined on\ntest constraints not joined on\n\\qecho device_id constraint must not propagate\ndevice_id constraint must not propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON true WHERE m2.time = m1.time AND m2.time < '2000-01-10' AND m1.device_id = 1 ORDER BY m1.time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: m1.\"time\"\n   ->  Nested Loop\n         ->  Append\n               ->  Seq Scan on _hyper_6_160_chunk m1_1\n                     Filter: (device_id = 1)\n               ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n                     Filter: (device_id = 1)\n         ->  Append\n               ->  Index Only Scan using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: (\"time\" = m1.\"time\")\n               ->  Index Only Scan using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" = m1.\"time\") AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho test multiple join conditions\ntest multiple join conditions\n\\qecho device_id constraint should propagate\ndevice_id constraint should propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON true WHERE m2.time = m1.time AND m1.device_id = m2.device_id AND m2.time < '2000-01-10' AND m1.device_id = 1 ORDER BY m1.time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: m1.\"time\"\n   ->  Nested Loop\n         ->  Append\n               ->  Seq Scan on _hyper_6_160_chunk m1_1\n                     Filter: (device_id = 1)\n               ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n                     Filter: (device_id = 1)\n         ->  Append\n               ->  Index Scan using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: (\"time\" = m1.\"time\")\n                     Filter: (device_id = 1)\n               ->  Index Scan using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" = m1.\"time\") AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n                     Filter: (device_id = 1)\n\n\\qecho test join with 3 tables\ntest join with 3 tables\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time INNER JOIN metrics_timestamptz m3 ON m2.time=m3.time WHERE m1.time > '2000-01-01' AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Nested Loop\n   ->  Merge Join\n         Merge Cond: (m1.\"time\" = m2.\"time\")\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n               Order: m1.\"time\"\n               ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Materialize\n               ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n                     Order: m2.\"time\"\n                     ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                           Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n                     ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                           Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Append\n         ->  Index Only Scan using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m3_1\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m3_2\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m3_3\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m3_4\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m3_5\n               Index Cond: (\"time\" = m1.\"time\")\n\n\\qecho test non-Const constraints\ntest non-Const constraints\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10'::text::timestamptz ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         Chunks excluded during startup: 3\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               Chunks excluded during startup: 4\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n\n\\qecho test now()\ntest now()\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < now() ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         Chunks excluded during startup: 0\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: (\"time\" < now())\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < now())\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n               Index Cond: (\"time\" < now())\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n               Index Cond: (\"time\" < now())\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n               Index Cond: (\"time\" < now())\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               Chunks excluded during startup: 0\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: (\"time\" < now())\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < now())\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n                     Index Cond: (\"time\" < now())\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n                     Index Cond: (\"time\" < now())\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n                     Index Cond: (\"time\" < now())\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n                     Index Cond: (\"time\" < now())\n\n\\qecho test volatile function\ntest volatile function\n\\qecho should not propagate\nshould not propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < clock_timestamp() ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         Chunks excluded during startup: 0\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n               Filter: (\"time\" < clock_timestamp())\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m2.time < clock_timestamp() ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m2.\"time\" = m1.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n         Order: m2.\"time\"\n         Chunks excluded during startup: 0\n         ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n               Filter: (\"time\" < clock_timestamp())\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n               Order: m1.\"time\"\n               ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n               ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n               ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n\n\\qecho test JOINs with normal table\ntest JOINs with normal table\n\\qecho will not propagate because constraints are only added to hypertables\nwill not propagate because constraints are only added to hypertables\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN regular_timestamptz m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Sort\n         Sort Key: m2.\"time\"\n         ->  Seq Scan on regular_timestamptz m2\n\n\\qecho test JOINs with normal table\ntest JOINs with normal table\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN regular_timestamptz m2 ON m1.time = m2.time WHERE m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m2.\"time\" = m1.\"time\")\n   ->  Sort\n         Sort Key: m2.\"time\"\n         ->  Seq Scan on regular_timestamptz m2\n               Filter: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n               Order: m1.\"time\"\n               ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho test quals are not pushed into OUTER JOIN\ntest quals are not pushed into OUTER JOIN\nCREATE TABLE outer_join_1 (id int, name text,time timestamptz NOT NULL DEFAULT '2000-01-01');\nCREATE TABLE outer_join_2 (id int, name text,time timestamptz NOT NULL DEFAULT '2000-01-01');\nSELECT (SELECT table_name FROM create_hypertable(tbl, 'time')) FROM (VALUES ('outer_join_1'),('outer_join_2')) v(tbl);\n  table_name  \n--------------\n outer_join_1\n outer_join_2\n\nINSERT INTO outer_join_1 VALUES(1,'a'), (2,'b');\nINSERT INTO outer_join_2 VALUES(1,'a');\n:PREFIX SELECT one.id, two.name FROM outer_join_1 one LEFT OUTER JOIN outer_join_2 two ON one.id=two.id WHERE one.id=2;\n--- QUERY PLAN ---\n Nested Loop Left Join\n   Join Filter: (one.id = two.id)\n   ->  Seq Scan on _hyper_9_176_chunk one\n         Filter: (id = 2)\n   ->  Materialize\n         ->  Seq Scan on _hyper_10_177_chunk two\n               Filter: (id = 2)\n\n:PREFIX SELECT one.id, two.name FROM outer_join_2 two RIGHT OUTER JOIN outer_join_1 one ON one.id=two.id WHERE one.id=2;\n--- QUERY PLAN ---\n Nested Loop Left Join\n   Join Filter: (one.id = two.id)\n   ->  Seq Scan on _hyper_9_176_chunk one\n         Filter: (id = 2)\n   ->  Materialize\n         ->  Seq Scan on _hyper_10_177_chunk two\n               Filter: (id = 2)\n\nDROP TABLE outer_join_1;\nDROP TABLE outer_join_2;\n-- test UNION between regular table and hypertable\nSELECT time FROM regular_timestamptz UNION SELECT time FROM metrics_timestamptz ORDER BY 1;\n             time             \n------------------------------\n Sat Jan 01 00:00:00 2000 PST\n Sun Jan 02 00:00:00 2000 PST\n Mon Jan 03 00:00:00 2000 PST\n Tue Jan 04 00:00:00 2000 PST\n Wed Jan 05 00:00:00 2000 PST\n Thu Jan 06 00:00:00 2000 PST\n Fri Jan 07 00:00:00 2000 PST\n Sat Jan 08 00:00:00 2000 PST\n Sun Jan 09 00:00:00 2000 PST\n Mon Jan 10 00:00:00 2000 PST\n Tue Jan 11 00:00:00 2000 PST\n Wed Jan 12 00:00:00 2000 PST\n Thu Jan 13 00:00:00 2000 PST\n Fri Jan 14 00:00:00 2000 PST\n Sat Jan 15 00:00:00 2000 PST\n Sun Jan 16 00:00:00 2000 PST\n Mon Jan 17 00:00:00 2000 PST\n Tue Jan 18 00:00:00 2000 PST\n Wed Jan 19 00:00:00 2000 PST\n Thu Jan 20 00:00:00 2000 PST\n Fri Jan 21 00:00:00 2000 PST\n Sat Jan 22 00:00:00 2000 PST\n Sun Jan 23 00:00:00 2000 PST\n Mon Jan 24 00:00:00 2000 PST\n Tue Jan 25 00:00:00 2000 PST\n Wed Jan 26 00:00:00 2000 PST\n Thu Jan 27 00:00:00 2000 PST\n Fri Jan 28 00:00:00 2000 PST\n Sat Jan 29 00:00:00 2000 PST\n Sun Jan 30 00:00:00 2000 PST\n Mon Jan 31 00:00:00 2000 PST\n Tue Feb 01 00:00:00 2000 PST\n\n-- test UNION ALL between regular table and hypertable\nSELECT time FROM regular_timestamptz UNION ALL SELECT time FROM metrics_timestamptz ORDER BY 1;\n             time             \n------------------------------\n Sat Jan 01 00:00:00 2000 PST\n Sat Jan 01 00:00:00 2000 PST\n Sat Jan 01 00:00:00 2000 PST\n Sat Jan 01 00:00:00 2000 PST\n Sun Jan 02 00:00:00 2000 PST\n Sun Jan 02 00:00:00 2000 PST\n Sun Jan 02 00:00:00 2000 PST\n Sun Jan 02 00:00:00 2000 PST\n Mon Jan 03 00:00:00 2000 PST\n Mon Jan 03 00:00:00 2000 PST\n Mon Jan 03 00:00:00 2000 PST\n Mon Jan 03 00:00:00 2000 PST\n Tue Jan 04 00:00:00 2000 PST\n Tue Jan 04 00:00:00 2000 PST\n Tue Jan 04 00:00:00 2000 PST\n Tue Jan 04 00:00:00 2000 PST\n Wed Jan 05 00:00:00 2000 PST\n Wed Jan 05 00:00:00 2000 PST\n Wed Jan 05 00:00:00 2000 PST\n Wed Jan 05 00:00:00 2000 PST\n Thu Jan 06 00:00:00 2000 PST\n Thu Jan 06 00:00:00 2000 PST\n Thu Jan 06 00:00:00 2000 PST\n Thu Jan 06 00:00:00 2000 PST\n Fri Jan 07 00:00:00 2000 PST\n Fri Jan 07 00:00:00 2000 PST\n Fri Jan 07 00:00:00 2000 PST\n Fri Jan 07 00:00:00 2000 PST\n Sat Jan 08 00:00:00 2000 PST\n Sat Jan 08 00:00:00 2000 PST\n Sat Jan 08 00:00:00 2000 PST\n Sat Jan 08 00:00:00 2000 PST\n Sun Jan 09 00:00:00 2000 PST\n Sun Jan 09 00:00:00 2000 PST\n Sun Jan 09 00:00:00 2000 PST\n Sun Jan 09 00:00:00 2000 PST\n Mon Jan 10 00:00:00 2000 PST\n Mon Jan 10 00:00:00 2000 PST\n Mon Jan 10 00:00:00 2000 PST\n Mon Jan 10 00:00:00 2000 PST\n Tue Jan 11 00:00:00 2000 PST\n Tue Jan 11 00:00:00 2000 PST\n Tue Jan 11 00:00:00 2000 PST\n Tue Jan 11 00:00:00 2000 PST\n Wed Jan 12 00:00:00 2000 PST\n Wed Jan 12 00:00:00 2000 PST\n Wed Jan 12 00:00:00 2000 PST\n Wed Jan 12 00:00:00 2000 PST\n Thu Jan 13 00:00:00 2000 PST\n Thu Jan 13 00:00:00 2000 PST\n Thu Jan 13 00:00:00 2000 PST\n Thu Jan 13 00:00:00 2000 PST\n Fri Jan 14 00:00:00 2000 PST\n Fri Jan 14 00:00:00 2000 PST\n Fri Jan 14 00:00:00 2000 PST\n Fri Jan 14 00:00:00 2000 PST\n Sat Jan 15 00:00:00 2000 PST\n Sat Jan 15 00:00:00 2000 PST\n Sat Jan 15 00:00:00 2000 PST\n Sat Jan 15 00:00:00 2000 PST\n Sun Jan 16 00:00:00 2000 PST\n Sun Jan 16 00:00:00 2000 PST\n Sun Jan 16 00:00:00 2000 PST\n Sun Jan 16 00:00:00 2000 PST\n Mon Jan 17 00:00:00 2000 PST\n Mon Jan 17 00:00:00 2000 PST\n Mon Jan 17 00:00:00 2000 PST\n Mon Jan 17 00:00:00 2000 PST\n Tue Jan 18 00:00:00 2000 PST\n Tue Jan 18 00:00:00 2000 PST\n Tue Jan 18 00:00:00 2000 PST\n Tue Jan 18 00:00:00 2000 PST\n Wed Jan 19 00:00:00 2000 PST\n Wed Jan 19 00:00:00 2000 PST\n Wed Jan 19 00:00:00 2000 PST\n Wed Jan 19 00:00:00 2000 PST\n Thu Jan 20 00:00:00 2000 PST\n Thu Jan 20 00:00:00 2000 PST\n Thu Jan 20 00:00:00 2000 PST\n Thu Jan 20 00:00:00 2000 PST\n Fri Jan 21 00:00:00 2000 PST\n Fri Jan 21 00:00:00 2000 PST\n Fri Jan 21 00:00:00 2000 PST\n Fri Jan 21 00:00:00 2000 PST\n Sat Jan 22 00:00:00 2000 PST\n Sat Jan 22 00:00:00 2000 PST\n Sat Jan 22 00:00:00 2000 PST\n Sat Jan 22 00:00:00 2000 PST\n Sun Jan 23 00:00:00 2000 PST\n Sun Jan 23 00:00:00 2000 PST\n Sun Jan 23 00:00:00 2000 PST\n Sun Jan 23 00:00:00 2000 PST\n Mon Jan 24 00:00:00 2000 PST\n Mon Jan 24 00:00:00 2000 PST\n Mon Jan 24 00:00:00 2000 PST\n Mon Jan 24 00:00:00 2000 PST\n Tue Jan 25 00:00:00 2000 PST\n Tue Jan 25 00:00:00 2000 PST\n Tue Jan 25 00:00:00 2000 PST\n Tue Jan 25 00:00:00 2000 PST\n Wed Jan 26 00:00:00 2000 PST\n Wed Jan 26 00:00:00 2000 PST\n Wed Jan 26 00:00:00 2000 PST\n Wed Jan 26 00:00:00 2000 PST\n Thu Jan 27 00:00:00 2000 PST\n Thu Jan 27 00:00:00 2000 PST\n Thu Jan 27 00:00:00 2000 PST\n Thu Jan 27 00:00:00 2000 PST\n Fri Jan 28 00:00:00 2000 PST\n Fri Jan 28 00:00:00 2000 PST\n Fri Jan 28 00:00:00 2000 PST\n Fri Jan 28 00:00:00 2000 PST\n Sat Jan 29 00:00:00 2000 PST\n Sat Jan 29 00:00:00 2000 PST\n Sat Jan 29 00:00:00 2000 PST\n Sat Jan 29 00:00:00 2000 PST\n Sun Jan 30 00:00:00 2000 PST\n Sun Jan 30 00:00:00 2000 PST\n Sun Jan 30 00:00:00 2000 PST\n Sun Jan 30 00:00:00 2000 PST\n Mon Jan 31 00:00:00 2000 PST\n Mon Jan 31 00:00:00 2000 PST\n Mon Jan 31 00:00:00 2000 PST\n Mon Jan 31 00:00:00 2000 PST\n Tue Feb 01 00:00:00 2000 PST\n Tue Feb 01 00:00:00 2000 PST\n Tue Feb 01 00:00:00 2000 PST\n Tue Feb 01 00:00:00 2000 PST\n\n-- test nested join qual propagation\n:PREFIX\nSELECT * FROM (\nSELECT o1_m1.time FROM metrics_timestamptz o1_m1 INNER JOIN metrics_timestamptz_2 o1_m2 ON true WHERE o1_m2.time = o1_m1.time AND o1_m1.device_id = o1_m2.device_id AND o1_m2.time < '2000-01-10' AND o1_m1.device_id = 1\n) o1 FULL OUTER JOIN (\nSELECT o2_m1.time FROM metrics_timestamptz o2_m1 FULL OUTER JOIN metrics_timestamptz_2 o2_m2 ON true WHERE o2_m2.time = o2_m1.time AND o2_m1.device_id = o2_m2.device_id AND o2_m2.time > '2000-01-20' AND o2_m1.device_id = 2\n) o2 ON o1.time = o2.time ORDER BY 1,2;\n--- QUERY PLAN ---\n Sort\n   Sort Key: o1_m1.\"time\", o2_m1.\"time\"\n   ->  Hash Full Join\n         Hash Cond: (o2_m1.\"time\" = o1_m1.\"time\")\n         ->  Nested Loop\n               ->  Append\n                     ->  Index Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk o2_m2_1\n                           Index Cond: (\"time\" > 'Thu Jan 20 00:00:00 2000 PST'::timestamp with time zone)\n                           Filter: (device_id = 2)\n                     ->  Seq Scan on _hyper_7_169_chunk o2_m2_2\n                           Filter: (device_id = 2)\n                     ->  Seq Scan on _hyper_7_170_chunk o2_m2_3\n                           Filter: (device_id = 2)\n               ->  Append\n                     ->  Index Scan using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk o2_m1_1\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk o2_m1_2\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk o2_m1_3\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk o2_m1_4\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk o2_m1_5\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n         ->  Hash\n               ->  Nested Loop\n                     ->  Append\n                           ->  Seq Scan on _hyper_7_165_chunk o1_m2_1\n                                 Filter: (device_id = 1)\n                           ->  Index Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk o1_m2_2\n                                 Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n                                 Filter: (device_id = 1)\n                     ->  Append\n                           ->  Index Scan using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk o1_m1_1\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk o1_m1_2\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk o1_m1_3\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk o1_m1_4\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk o1_m1_5\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n\n:PREFIX\nSELECT * FROM (\nSELECT o1_m1.time FROM metrics_timestamptz o1_m1 INNER JOIN metrics_timestamptz_2 o1_m2 ON o1_m2.time = o1_m1.time AND o1_m1.device_id = o1_m2.device_id WHERE o1_m2.time < '2000-01-10' AND o1_m1.device_id = 1\n) o1 FULL OUTER JOIN (\nSELECT o2_m1.time FROM metrics_timestamptz o2_m1 FULL OUTER JOIN metrics_timestamptz_2 o2_m2 ON o2_m2.time = o2_m1.time AND o2_m1.device_id = o2_m2.device_id WHERE o2_m2.time > '2000-01-20' AND o2_m1.device_id = 2\n) o2 ON o1.time = o2.time ORDER BY 1,2;\n--- QUERY PLAN ---\n Sort\n   Sort Key: o1_m1.\"time\", o2_m1.\"time\"\n   ->  Hash Full Join\n         Hash Cond: (o2_m1.\"time\" = o1_m1.\"time\")\n         ->  Nested Loop\n               ->  Append\n                     ->  Index Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk o2_m2_1\n                           Index Cond: (\"time\" > 'Thu Jan 20 00:00:00 2000 PST'::timestamp with time zone)\n                           Filter: (device_id = 2)\n                     ->  Seq Scan on _hyper_7_169_chunk o2_m2_2\n                           Filter: (device_id = 2)\n                     ->  Seq Scan on _hyper_7_170_chunk o2_m2_3\n                           Filter: (device_id = 2)\n               ->  Append\n                     ->  Index Scan using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk o2_m1_1\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk o2_m1_2\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk o2_m1_3\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk o2_m1_4\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk o2_m1_5\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n         ->  Hash\n               ->  Nested Loop\n                     ->  Append\n                           ->  Seq Scan on _hyper_7_165_chunk o1_m2_1\n                                 Filter: (device_id = 1)\n                           ->  Index Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk o1_m2_2\n                                 Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n                                 Filter: (device_id = 1)\n                     ->  Append\n                           ->  Index Scan using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk o1_m1_1\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk o1_m1_2\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk o1_m1_3\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk o1_m1_4\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk o1_m1_5\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n\n\\set ECHO errors\nRESET timescaledb.enable_optimizations;\nCREATE TABLE t(time timestamptz NOT NULL);\nSELECT table_name FROM create_hypertable('t','time');\n table_name \n------------\n t\n\nINSERT INTO t VALUES ('2000-01-01'), ('2010-01-01'), ('2020-01-01');\nEXPLAIN (buffers off, costs off) SELECT * FROM t t1 INNER JOIN t t2 ON t1.time = t2.time WHERE t1.time < timestamptz '2010-01-01';\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (t1.\"time\" = t2.\"time\")\n   ->  Merge Append\n         Sort Key: t1.\"time\"\n         ->  Index Only Scan Backward using _hyper_15_182_chunk_t_time_idx on _hyper_15_182_chunk t1_1\n         ->  Index Only Scan Backward using _hyper_15_183_chunk_t_time_idx on _hyper_15_183_chunk t1_2\n               Index Cond: (\"time\" < 'Fri Jan 01 00:00:00 2010 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Merge Append\n               Sort Key: t2.\"time\"\n               ->  Index Only Scan Backward using _hyper_15_182_chunk_t_time_idx on _hyper_15_182_chunk t2_1\n               ->  Index Only Scan Backward using _hyper_15_183_chunk_t_time_idx on _hyper_15_183_chunk t2_2\n                     Index Cond: (\"time\" < 'Fri Jan 01 00:00:00 2010 PST'::timestamp with time zone)\n\nSET timescaledb.enable_qual_propagation TO false;\nEXPLAIN (buffers off, costs off) SELECT * FROM t t1 INNER JOIN t t2 ON t1.time = t2.time WHERE t1.time < timestamptz '2010-01-01';\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (t1.\"time\" = t2.\"time\")\n   ->  Merge Append\n         Sort Key: t1.\"time\"\n         ->  Index Only Scan Backward using _hyper_15_182_chunk_t_time_idx on _hyper_15_182_chunk t1_1\n         ->  Index Only Scan Backward using _hyper_15_183_chunk_t_time_idx on _hyper_15_183_chunk t1_2\n               Index Cond: (\"time\" < 'Fri Jan 01 00:00:00 2010 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Merge Append\n               Sort Key: t2.\"time\"\n               ->  Index Only Scan Backward using _hyper_15_182_chunk_t_time_idx on _hyper_15_182_chunk t2_1\n               ->  Index Only Scan Backward using _hyper_15_183_chunk_t_time_idx on _hyper_15_183_chunk t2_2\n               ->  Index Only Scan Backward using _hyper_15_184_chunk_t_time_idx on _hyper_15_184_chunk t2_3\n\nRESET timescaledb.enable_qual_propagation;\nCREATE TABLE test (a int, time timestamptz NOT NULL);\nSELECT table_name FROM create_hypertable('public.test', 'time');\n table_name \n------------\n test\n\nINSERT INTO test SELECT i, '2020-04-01'::date-10-i from generate_series(1,20) i;\nCREATE OR REPLACE FUNCTION test_f(_ts timestamptz)\nRETURNS SETOF test LANGUAGE SQL STABLE PARALLEL SAFE\nAS $f$\n   SELECT DISTINCT ON (a) * FROM test WHERE time >= _ts ORDER BY a, time DESC\n$f$;\nEXPLAIN (buffers off, costs off) SELECT * FROM test_f(now());\n--- QUERY PLAN ---\n Unique\n   ->  Sort\n         Sort Key: test.a, test.\"time\" DESC\n         ->  Custom Scan (ChunkAppend) on test\n               Chunks excluded during startup: 4\n\nEXPLAIN (buffers off, costs off) SELECT * FROM test_f(now());\n--- QUERY PLAN ---\n Unique\n   ->  Sort\n         Sort Key: test.a, test.\"time\" DESC\n         ->  Custom Scan (ChunkAppend) on test\n               Chunks excluded during startup: 4\n\nCREATE TABLE t1 (a int, b int NOT NULL);\nSELECT create_hypertable('t1', 'b', chunk_time_interval=>10);\n create_hypertable \n-------------------\n (17,public,t1,t)\n\nCREATE TABLE t2 (a int, b int NOT NULL);\nSELECT create_hypertable('t2', 'b', chunk_time_interval=>10);\n create_hypertable \n-------------------\n (18,public,t2,t)\n\nCREATE OR REPLACE FUNCTION f_t1(_a int, _b int)\n RETURNS SETOF t1\n LANGUAGE SQL\n STABLE PARALLEL SAFE\nAS $function$\n   SELECT DISTINCT ON (a) * FROM t1 WHERE a = _a and b = _b ORDER BY a, b DESC\n$function$\n;\nCREATE OR REPLACE FUNCTION f_t2(_a int, _b int) RETURNS SETOF t2 LANGUAGE sql STABLE PARALLEL SAFE\nAS $function$\n   SELECT DISTINCT ON (j.a) j.*\n   FROM\n      f_t1(_a, _b) sc,\n      t2 j\n   WHERE\n      j.b = _b AND\n      j.a = _a\n   ORDER BY j.a, j.b DESC\n$function$\n;\nCREATE OR REPLACE FUNCTION f_t1_2(_b int) RETURNS SETOF t1 LANGUAGE SQL STABLE PARALLEL SAFE\nAS $function$\n   SELECT DISTINCT ON (j.a) jt.* FROM t1 j, f_t1(j.a, _b) jt\n$function$;\nEXPLAIN (buffers off, costs off) SELECT * FROM f_t1_2(10);\n--- QUERY PLAN ---\n Subquery Scan on f_t1_2\n   ->  Unique\n         ->  Sort\n               Sort Key: j.a\n               ->  Nested Loop\n                     ->  Seq Scan on t1 j\n                     ->  Unique\n                           ->  Index Scan using t1_b_idx on t1\n                                 Index Cond: (b = 10)\n                                 Filter: (a = j.a)\n\nEXPLAIN (buffers off, costs off) SELECT * FROM f_t1_2(10) sc, f_t2(sc.a, 10);\n--- QUERY PLAN ---\n Nested Loop\n   ->  Unique\n         ->  Sort\n               Sort Key: j.a\n               ->  Nested Loop\n                     ->  Seq Scan on t1 j\n                     ->  Unique\n                           ->  Index Scan using t1_b_idx on t1\n                                 Index Cond: (b = 10)\n                                 Filter: (a = j.a)\n   ->  Unique\n         ->  Nested Loop\n               ->  Unique\n                     ->  Index Scan using t1_b_idx on t1 t1_1\n                           Index Cond: (b = 10)\n                           Filter: (a = t1.a)\n               ->  Index Scan using t2_b_idx on t2 j_1\n                     Index Cond: (b = 10)\n                     Filter: (a = t1.a)\n\nCREATE TABLE metrics_int1(time int, device text, value float) WITH (tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval=1);\nINSERT INTO metrics_int1 SELECT i, i::text, i FROM generate_series(3,7) i;\nSELECT tableoid::regclass, time FROM metrics_int1 ORDER BY time;\n                 tableoid                  | time \n-------------------------------------------+------\n _timescaledb_internal._hyper_19_189_chunk |    3\n _timescaledb_internal._hyper_19_190_chunk |    4\n _timescaledb_internal._hyper_19_191_chunk |    5\n _timescaledb_internal._hyper_19_192_chunk |    6\n _timescaledb_internal._hyper_19_193_chunk |    7\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time >= 2;\n--- QUERY PLAN ---\n Append (actual rows=5.00 loops=1)\n   ->  Seq Scan on _hyper_19_189_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_190_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_192_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_193_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time >= 1 AND time >= 2;\n--- QUERY PLAN ---\n Append (actual rows=5.00 loops=1)\n   ->  Seq Scan on _hyper_19_189_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_190_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_192_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_193_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time >= 4;\n--- QUERY PLAN ---\n Append (actual rows=4.00 loops=1)\n   ->  Seq Scan on _hyper_19_190_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_192_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_193_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time > 4 AND time < 6;\n--- QUERY PLAN ---\n Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time > 4 AND time <= 6;\n--- QUERY PLAN ---\n Append (actual rows=2.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_192_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time >= 4 AND time < 6;\n--- QUERY PLAN ---\n Append (actual rows=2.00 loops=1)\n   ->  Seq Scan on _hyper_19_190_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time >= 4 AND time <= 6;\n--- QUERY PLAN ---\n Append (actual rows=3.00 loops=1)\n   ->  Seq Scan on _hyper_19_190_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_192_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time BETWEEN 4 AND 5;\n--- QUERY PLAN ---\n Append (actual rows=2.00 loops=1)\n   ->  Seq Scan on _hyper_19_190_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time = 5;\n--- QUERY PLAN ---\n Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n\nSET TIMEZONE='UTC';\nCREATE TABLE metrics_tstz(time timestamptz, device text, value float) WITH (tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval='1day');\nINSERT INTO metrics_tstz SELECT '2000-01-01'::timestamptz + format('%s day',i)::interval, i::text, i FROM generate_series(2,6) i;\nSELECT tableoid::regclass, time FROM metrics_tstz ORDER BY time;\n                 tableoid                  |             time             \n-------------------------------------------+------------------------------\n _timescaledb_internal._hyper_20_194_chunk | Mon Jan 03 00:00:00 2000 UTC\n _timescaledb_internal._hyper_20_195_chunk | Tue Jan 04 00:00:00 2000 UTC\n _timescaledb_internal._hyper_20_196_chunk | Wed Jan 05 00:00:00 2000 UTC\n _timescaledb_internal._hyper_20_197_chunk | Thu Jan 06 00:00:00 2000 UTC\n _timescaledb_internal._hyper_20_198_chunk | Fri Jan 07 00:00:00 2000 UTC\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-02';\n--- QUERY PLAN ---\n Append (actual rows=5.00 loops=1)\n   ->  Seq Scan on _hyper_20_194_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_198_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-01' AND time >= '2000-01-02';\n--- QUERY PLAN ---\n Append (actual rows=5.00 loops=1)\n   ->  Seq Scan on _hyper_20_194_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_198_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-04';\n--- QUERY PLAN ---\n Append (actual rows=4.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_198_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time > '2000-01-04' AND time < '2000-01-06';\n--- QUERY PLAN ---\n Append (actual rows=1.00 loops=1)\n   ->  Index Scan using _hyper_20_195_chunk_metrics_tstz_time_idx on _hyper_20_195_chunk (actual rows=0.00 loops=1)\n         Index Cond: ((\"time\" > 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" < 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone))\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time > '2000-01-04' AND time <= '2000-01-06';\n--- QUERY PLAN ---\n Append (actual rows=2.00 loops=1)\n   ->  Index Scan using _hyper_20_195_chunk_metrics_tstz_time_idx on _hyper_20_195_chunk (actual rows=0.00 loops=1)\n         Index Cond: ((\"time\" > 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone))\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n   ->  Index Scan using _hyper_20_197_chunk_metrics_tstz_time_idx on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n         Index Cond: ((\"time\" > 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone))\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-04' AND time < '2000-01-06';\n--- QUERY PLAN ---\n Append (actual rows=2.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-04' AND time <= '2000-01-06';\n--- QUERY PLAN ---\n Append (actual rows=3.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n   ->  Index Scan using _hyper_20_197_chunk_metrics_tstz_time_idx on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n         Index Cond: ((\"time\" >= 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone))\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time BETWEEN '2000-01-04' AND '2000-01-05';\n--- QUERY PLAN ---\n Append (actual rows=2.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n   ->  Index Scan using _hyper_20_196_chunk_metrics_tstz_time_idx on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n         Index Cond: ((\"time\" >= 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Wed Jan 05 00:00:00 2000 UTC'::timestamp with time zone))\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time = '2000-01-05';\n--- QUERY PLAN ---\n Index Scan using _hyper_20_196_chunk_metrics_tstz_time_idx on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n   Index Cond: (\"time\" = 'Wed Jan 05 00:00:00 2000 UTC'::timestamp with time zone)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-04' AND time <= '2000-01-06' AND device = '5';\n--- QUERY PLAN ---\n Append (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=0.00 loops=1)\n         Filter: (device = '5'::text)\n         Rows Removed by Filter: 1\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=0.00 loops=1)\n         Filter: (device = '5'::text)\n         Rows Removed by Filter: 1\n   ->  Index Scan using _hyper_20_197_chunk_metrics_tstz_time_idx on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n         Index Cond: ((\"time\" >= 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone))\n         Filter: (device = '5'::text)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-04' AND time <= '2000-01-06' AND device IS NOT NULL;\n--- QUERY PLAN ---\n Append (actual rows=3.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n         Filter: (device IS NOT NULL)\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n         Filter: (device IS NOT NULL)\n   ->  Index Scan using _hyper_20_197_chunk_metrics_tstz_time_idx on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n         Index Cond: ((\"time\" >= 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone))\n         Filter: (device IS NOT NULL)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-04' AND time <= '2000-01-06' AND time < now();\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_tstz (actual rows=3.00 loops=1)\n   Chunks excluded during startup: 0\n   ->  Index Scan using _hyper_20_195_chunk_metrics_tstz_time_idx on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" < now())\n   ->  Index Scan using _hyper_20_196_chunk_metrics_tstz_time_idx on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" < now())\n   ->  Index Scan using _hyper_20_197_chunk_metrics_tstz_time_idx on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n         Index Cond: ((\"time\" >= 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" < now()))\n\nCREATE TABLE metrics_space(time timestamptz, device text, value float) WITH (tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval='1day');\nSELECT add_dimension('metrics_space', 'device', 4);\n           add_dimension            \n------------------------------------\n (25,public,metrics_space,device,t)\n\nINSERT INTO metrics_space SELECT '2000-01-01'::timestamptz + format('%s day',i)::interval, i::text, i FROM generate_series(2,6) i;\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_space WHERE time >= '2000-01-02';\n--- QUERY PLAN ---\n Append (actual rows=5.00 loops=1)\n   ->  Index Scan using _hyper_21_199_chunk_metrics_space_time_idx on _hyper_21_199_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" >= 'Sun Jan 02 00:00:00 2000 UTC'::timestamp with time zone)\n   ->  Index Scan using _hyper_21_200_chunk_metrics_space_time_idx on _hyper_21_200_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" >= 'Sun Jan 02 00:00:00 2000 UTC'::timestamp with time zone)\n   ->  Index Scan using _hyper_21_201_chunk_metrics_space_time_idx on _hyper_21_201_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" >= 'Sun Jan 02 00:00:00 2000 UTC'::timestamp with time zone)\n   ->  Index Scan using _hyper_21_202_chunk_metrics_space_time_idx on _hyper_21_202_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" >= 'Sun Jan 02 00:00:00 2000 UTC'::timestamp with time zone)\n   ->  Index Scan using _hyper_21_203_chunk_metrics_space_time_idx on _hyper_21_203_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" >= 'Sun Jan 02 00:00:00 2000 UTC'::timestamp with time zone)\n\n--TEST END--\n"
  },
  {
    "path": "test/expected/plan_expand_hypertable-16.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\set PREFIX 'EXPLAIN (buffers off, costs off) '\n\\ir include/plan_expand_hypertable_load.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n--single time dimension\nCREATE TABLE hyper (\"time_broken\" bigint NOT NULL, \"value\" integer);\nALTER TABLE hyper\nDROP COLUMN time_broken,\nADD COLUMN time BIGINT;\nSELECT create_hypertable('hyper', 'time',  chunk_time_interval => 10);\n create_hypertable  \n--------------------\n (1,public,hyper,t)\n\nINSERT INTO hyper SELECT g, g FROM generate_series(0,1000) g;\n--insert a point with INT_MAX_64\nINSERT INTO hyper (time, value) SELECT 9223372036854775807::bigint, 0;\n--time and space\nCREATE TABLE hyper_w_space (\"time_broken\" bigint NOT NULL, \"device_id\" text, \"value\" integer);\nALTER TABLE hyper_w_space\nDROP COLUMN time_broken,\nADD COLUMN time BIGINT;\nSELECT create_hypertable('hyper_w_space', 'time', 'device_id', 4, chunk_time_interval => 10);\n     create_hypertable      \n----------------------------\n (2,public,hyper_w_space,t)\n\nINSERT INTO hyper_w_space (time, device_id, value) SELECT g, 'dev' || g, g FROM generate_series(0,30) g;\nCREATE VIEW hyper_w_space_view AS (SELECT * FROM hyper_w_space);\n--with timestamp and space\nCREATE TABLE tag (id serial PRIMARY KEY, name text);\nCREATE TABLE hyper_ts (\"time_broken\" timestamptz NOT NULL, \"device_id\" text, tag_id INT REFERENCES tag(id), \"value\" integer);\nALTER TABLE hyper_ts\nDROP COLUMN time_broken,\nADD COLUMN time TIMESTAMPTZ;\nSELECT create_hypertable('hyper_ts', 'time', 'device_id', 2, chunk_time_interval => '10 seconds'::interval);\n   create_hypertable   \n-----------------------\n (3,public,hyper_ts,t)\n\nINSERT INTO tag(name) SELECT 'tag'||g FROM generate_series(0,10) g;\nINSERT INTO hyper_ts (time, device_id, tag_id, value) SELECT to_timestamp(g), 'dev' || g, (random() /10)+1, g FROM generate_series(0,30) g;\n--one in the future\nINSERT INTO hyper_ts (time, device_id, tag_id, value)  VALUES ('2100-01-01 02:03:04 PST', 'dev101', 1, 0);\n--time partitioning function\nCREATE OR REPLACE FUNCTION unix_to_timestamp(unixtime float8)\n    RETURNS TIMESTAMPTZ LANGUAGE SQL IMMUTABLE PARALLEL SAFE STRICT AS\n$BODY$\n    SELECT to_timestamp(unixtime);\n$BODY$;\nCREATE TABLE hyper_timefunc (\"time\" float8 NOT NULL, \"device_id\" text, \"value\" integer);\nSELECT create_hypertable('hyper_timefunc', 'time', 'device_id', 4, chunk_time_interval => 10, time_partitioning_func => 'unix_to_timestamp');\npsql:include/plan_expand_hypertable_load.sql:57: WARNING:  unexpected interval: smaller than one second\n      create_hypertable      \n-----------------------------\n (4,public,hyper_timefunc,t)\n\nINSERT INTO hyper_timefunc (time, device_id, value) SELECT g, 'dev' || g, g FROM generate_series(0,30) g;\nCREATE TABLE metrics_timestamp(time timestamp);\nSELECT create_hypertable('metrics_timestamp','time');\npsql:include/plan_expand_hypertable_load.sql:62: WARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n       create_hypertable        \n--------------------------------\n (5,public,metrics_timestamp,t)\n\nINSERT INTO metrics_timestamp SELECT generate_series('2000-01-01'::timestamp,'2000-02-01'::timestamp,'1d'::interval);\nCREATE TABLE metrics_timestamptz(time timestamptz, device_id int);\nSELECT create_hypertable('metrics_timestamptz','time');\n        create_hypertable         \n----------------------------------\n (6,public,metrics_timestamptz,t)\n\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval), 1;\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval), 2;\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval), 3;\n--create a second table to test joins with\nCREATE TABLE metrics_timestamptz_2 (LIKE metrics_timestamptz);\nSELECT create_hypertable('metrics_timestamptz_2','time');\n         create_hypertable          \n------------------------------------\n (7,public,metrics_timestamptz_2,t)\n\nINSERT INTO metrics_timestamptz_2\nSELECT * FROM metrics_timestamptz;\nINSERT INTO metrics_timestamptz_2 VALUES ('2000-12-01'::timestamptz, 3);\nCREATE TABLE metrics_date(time date);\nSELECT create_hypertable('metrics_date','time');\n     create_hypertable     \n---------------------------\n (8,public,metrics_date,t)\n\nINSERT INTO metrics_date SELECT generate_series('2000-01-01'::date,'2000-02-01'::date,'1d'::interval);\nANALYZE hyper;\nANALYZE hyper_w_space;\nANALYZE tag;\nANALYZE hyper_ts;\nANALYZE hyper_timefunc;\n-- create normal table for JOIN tests\nCREATE TABLE regular_timestamptz(time timestamptz);\nINSERT INTO regular_timestamptz SELECT generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval);\n\\ir include/plan_expand_hypertable_query.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n--we want to see how our logic excludes chunks\n--and not how much work constraint_exclusion does\nSET constraint_exclusion = 'off';\n\\qecho test upper bounds\ntest upper bounds\n:PREFIX SELECT * FROM hyper WHERE time < 10 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_1_chunk.value\n   ->  Seq Scan on _hyper_1_1_chunk\n\n:PREFIX SELECT * FROM hyper WHERE time < 11 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (\"time\" < 11)\n\n:PREFIX SELECT * FROM hyper WHERE time = 10 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_2_chunk.value\n   ->  Seq Scan on _hyper_1_2_chunk\n         Filter: (\"time\" = 10)\n\n:PREFIX SELECT * FROM hyper WHERE 10 >= time ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (10 >= \"time\")\n\n\\qecho test lower bounds\ntest lower bounds\n:PREFIX SELECT * FROM hyper WHERE time >= 10 and time < 20 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_2_chunk.value\n   ->  Seq Scan on _hyper_1_2_chunk\n\n:PREFIX SELECT * FROM hyper WHERE 10 < time and 20 >= time ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: ((10 < \"time\") AND (20 >= \"time\"))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((10 < \"time\") AND (20 >= \"time\"))\n\n:PREFIX SELECT * FROM hyper WHERE time >= 9 and time < 20 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: ((\"time\" >= 9) AND (\"time\" < 20))\n         ->  Seq Scan on _hyper_1_2_chunk\n\n:PREFIX SELECT * FROM hyper WHERE time > 9 and time < 20 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_2_chunk.value\n   ->  Seq Scan on _hyper_1_2_chunk\n\n\\qecho test empty result\ntest empty result\n:PREFIX SELECT * FROM hyper WHERE time < 0;\n--- QUERY PLAN ---\n Result\n   One-Time Filter: false\n\n\\qecho test expression evaluation\ntest expression evaluation\n:PREFIX SELECT * FROM hyper WHERE time < (5*2)::smallint;\n--- QUERY PLAN ---\n Seq Scan on _hyper_1_1_chunk\n\n\\qecho test logic at INT64_MAX\ntest logic at INT64_MAX\n:PREFIX SELECT * FROM hyper WHERE time = 9223372036854775807::bigint ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_102_chunk.value\n   ->  Seq Scan on _hyper_1_102_chunk\n         Filter: (\"time\" = '9223372036854775807'::bigint)\n\n:PREFIX SELECT * FROM hyper WHERE time = 9223372036854775806::bigint ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_102_chunk.value\n   ->  Seq Scan on _hyper_1_102_chunk\n         Filter: (\"time\" = '9223372036854775806'::bigint)\n\n:PREFIX SELECT * FROM hyper WHERE time >= 9223372036854775807::bigint ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_102_chunk.value\n   ->  Seq Scan on _hyper_1_102_chunk\n         Filter: (\"time\" >= '9223372036854775807'::bigint)\n\n:PREFIX SELECT * FROM hyper WHERE time > 9223372036854775807::bigint ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: value\n   ->  Result\n         One-Time Filter: false\n\n:PREFIX SELECT * FROM hyper WHERE time > 9223372036854775806::bigint ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_102_chunk.value\n   ->  Seq Scan on _hyper_1_102_chunk\n         Filter: (\"time\" > '9223372036854775806'::bigint)\n\n\\qecho cte\ncte\n:PREFIX WITH cte AS(\n  SELECT * FROM hyper WHERE time < 10\n)\nSELECT * FROM cte ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_1_chunk.value\n   ->  Seq Scan on _hyper_1_1_chunk\n\n\\qecho subquery\nsubquery\n:PREFIX SELECT 0 = ANY (SELECT value FROM hyper WHERE time < 10);\n--- QUERY PLAN ---\n Result\n   SubPlan 1\n     ->  Seq Scan on _hyper_1_1_chunk\n\n\\qecho no space constraint\nno space constraint\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (\"time\" < 10)\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (\"time\" < 10)\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (\"time\" < 10)\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (\"time\" < 10)\n\n\\qecho valid space constraint\nvalid space constraint\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 and device_id = 'dev5' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND (device_id = 'dev5'::text))\n\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 and 'dev5' = device_id ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND ('dev5'::text = device_id))\n\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 and 'dev'||(2+3) = device_id ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND ('dev5'::text = device_id))\n\n\\qecho only space constraint\nonly space constraint\n:PREFIX SELECT * FROM hyper_w_space WHERE 'dev5' = device_id ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ('dev5'::text = device_id)\n         ->  Seq Scan on _hyper_2_109_chunk\n               Filter: ('dev5'::text = device_id)\n         ->  Seq Scan on _hyper_2_111_chunk\n               Filter: ('dev5'::text = device_id)\n\n\\qecho unhandled space constraint\nunhandled space constraint\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 and device_id > 'dev5' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: ((\"time\" < 10) AND (device_id > 'dev5'::text))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: ((\"time\" < 10) AND (device_id > 'dev5'::text))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: ((\"time\" < 10) AND (device_id > 'dev5'::text))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ((\"time\" < 10) AND (device_id > 'dev5'::text))\n\n\\qecho use of OR - does not filter chunks\nuse of OR - does not filter chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND (device_id = 'dev5' or device_id = 'dev6') ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: ((\"time\" < 10) AND ((device_id = 'dev5'::text) OR (device_id = 'dev6'::text)))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: ((\"time\" < 10) AND ((device_id = 'dev5'::text) OR (device_id = 'dev6'::text)))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: ((\"time\" < 10) AND ((device_id = 'dev5'::text) OR (device_id = 'dev6'::text)))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ((\"time\" < 10) AND ((device_id = 'dev5'::text) OR (device_id = 'dev6'::text)))\n\n\\qecho cte\ncte\n:PREFIX WITH cte AS(\n   SELECT * FROM hyper_w_space WHERE time < 10 and device_id = 'dev5'\n)\nSELECT * FROM cte ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND (device_id = 'dev5'::text))\n\n\\qecho subquery\nsubquery\n:PREFIX SELECT 0 = ANY (SELECT value FROM hyper_w_space WHERE time < 10 and device_id = 'dev5');\n--- QUERY PLAN ---\n Result\n   SubPlan 1\n     ->  Seq Scan on _hyper_2_106_chunk\n           Filter: ((\"time\" < 10) AND (device_id = 'dev5'::text))\n\n\\qecho view\nview\n:PREFIX SELECT * FROM hyper_w_space_view WHERE time < 10 and device_id = 'dev5' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND (device_id = 'dev5'::text))\n\n\\qecho IN statement - simple\nIN statement - simple\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id IN ('dev5') ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND (device_id = 'dev5'::text))\n\n\\qecho IN statement - two chunks\nIN statement - two chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id IN ('dev5','dev6') ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: ((\"time\" < 10) AND (device_id = ANY ('{dev5,dev6}'::text[])))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ((\"time\" < 10) AND (device_id = ANY ('{dev5,dev6}'::text[])))\n\n\\qecho IN statement - one chunk\nIN statement - one chunk\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id IN ('dev4','dev5') ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND (device_id = ANY ('{dev4,dev5}'::text[])))\n\n\\qecho NOT IN - does not filter chunks\nNOT IN - does not filter chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id NOT IN ('dev5','dev6') ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: ((\"time\" < 10) AND (device_id <> ALL ('{dev5,dev6}'::text[])))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: ((\"time\" < 10) AND (device_id <> ALL ('{dev5,dev6}'::text[])))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: ((\"time\" < 10) AND (device_id <> ALL ('{dev5,dev6}'::text[])))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ((\"time\" < 10) AND (device_id <> ALL ('{dev5,dev6}'::text[])))\n\n\\qecho IN statement with subquery - does not filter chunks\nIN statement with subquery - does not filter chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id IN (SELECT 'dev5'::text) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND (device_id = 'dev5'::text))\n\n\\qecho ANY\nANY\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id = ANY(ARRAY['dev5','dev6']) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: ((\"time\" < 10) AND (device_id = ANY ('{dev5,dev6}'::text[])))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ((\"time\" < 10) AND (device_id = ANY ('{dev5,dev6}'::text[])))\n\n\\qecho ANY with intersection\nANY with intersection\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id = ANY(ARRAY['dev5','dev6']) AND device_id = ANY(ARRAY['dev6','dev7']) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_105_chunk.value\n   ->  Seq Scan on _hyper_2_105_chunk\n         Filter: ((\"time\" < 10) AND (device_id = ANY ('{dev5,dev6}'::text[])) AND (device_id = ANY ('{dev6,dev7}'::text[])))\n\n\\qecho ANY without intersection shouldnt scan any chunks\nANY without intersection shouldnt scan any chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id = ANY(ARRAY['dev5','dev6']) AND device_id = ANY(ARRAY['dev8','dev9']) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: value\n   ->  Result\n         One-Time Filter: false\n\n\\qecho ANY/IN/ALL only works for equals operator\nANY/IN/ALL only works for equals operator\n:PREFIX SELECT * FROM hyper_w_space WHERE device_id < ANY(ARRAY['dev5','dev6']) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_107_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_108_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_109_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_110_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_111_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_112_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_113_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_114_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_115_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n\n\\qecho ALL with equals and different values shouldnt scan any chunks\nALL with equals and different values shouldnt scan any chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE device_id = ALL(ARRAY['dev5','dev6']) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: value\n   ->  Result\n         One-Time Filter: false\n\n\\qecho Multi AND\nMulti AND\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND time < 100 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: ((\"time\" < 10) AND (\"time\" < 100))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: ((\"time\" < 10) AND (\"time\" < 100))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: ((\"time\" < 10) AND (\"time\" < 100))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ((\"time\" < 10) AND (\"time\" < 100))\n\n\\qecho Time dimension doesnt filter chunks when using non-equality IN/ANY with multiple arguments\nTime dimension doesnt filter chunks when using non-equality IN/ANY with multiple arguments\n:PREFIX SELECT * FROM hyper_w_space WHERE time < ANY(ARRAY[1,2]) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_107_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_108_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_109_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_110_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_111_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_112_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_113_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_114_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_115_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n\n\\qecho Time dimension chunk exclusion with IN/ANY equality uses bounding range\nTime dimension chunk exclusion with IN/ANY equality uses bounding range\n:PREFIX SELECT * FROM hyper WHERE time IN (5, 15) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n\n:PREFIX SELECT * FROM hyper WHERE time = ANY(ARRAY[5, 15, 25]) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (\"time\" = ANY ('{5,15,25}'::integer[]))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (\"time\" = ANY ('{5,15,25}'::integer[]))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: (\"time\" = ANY ('{5,15,25}'::integer[]))\n\n:PREFIX SELECT * FROM hyper WHERE time = ANY(ARRAY[25, 15, 5]) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (\"time\" = ANY ('{25,15,5}'::integer[]))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (\"time\" = ANY ('{25,15,5}'::integer[]))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: (\"time\" = ANY ('{25,15,5}'::integer[]))\n\n:PREFIX SELECT * FROM hyper_w_space WHERE time IN (5, 15) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_107_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_108_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_109_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_110_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time IN ('2000-01-05'::timestamp, '2000-01-15'::timestamp) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Index Cond: (\"time\" = ANY ('{\"Wed Jan 05 00:00:00 2000\",\"Sat Jan 15 00:00:00 2000\"}'::timestamp without time zone[]))\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" = ANY ('{\"Wed Jan 05 00:00:00 2000\",\"Sat Jan 15 00:00:00 2000\"}'::timestamp without time zone[]))\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Index Cond: (\"time\" = ANY ('{\"Wed Jan 05 00:00:00 2000\",\"Sat Jan 15 00:00:00 2000\"}'::timestamp without time zone[]))\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time IN ('2000-01-05'::timestamptz, '2000-01-15'::timestamptz) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Index Cond: (\"time\" = ANY ('{\"Wed Jan 05 00:00:00 2000 PST\",\"Sat Jan 15 00:00:00 2000 PST\"}'::timestamp with time zone[]))\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" = ANY ('{\"Wed Jan 05 00:00:00 2000 PST\",\"Sat Jan 15 00:00:00 2000 PST\"}'::timestamp with time zone[]))\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Index Cond: (\"time\" = ANY ('{\"Wed Jan 05 00:00:00 2000 PST\",\"Sat Jan 15 00:00:00 2000 PST\"}'::timestamp with time zone[]))\n\n:PREFIX SELECT * FROM metrics_date WHERE time IN ('2000-01-05'::date, '2000-01-15'::date) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date\n   Order: metrics_date.\"time\"\n   ->  Index Only Scan Backward using _hyper_8_171_chunk_metrics_date_time_idx on _hyper_8_171_chunk\n         Index Cond: (\"time\" = ANY ('{01-05-2000,01-15-2000}'::date[]))\n   ->  Index Only Scan Backward using _hyper_8_172_chunk_metrics_date_time_idx on _hyper_8_172_chunk\n         Index Cond: (\"time\" = ANY ('{01-05-2000,01-15-2000}'::date[]))\n   ->  Index Only Scan Backward using _hyper_8_173_chunk_metrics_date_time_idx on _hyper_8_173_chunk\n         Index Cond: (\"time\" = ANY ('{01-05-2000,01-15-2000}'::date[]))\n\n\\qecho cross-type IN/ANY: timestamp to timestamptz column does not use bounding range (stable cast)\ncross-type IN/ANY: timestamp to timestamptz column does not use bounding range (stable cast)\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time IN ('2000-01-05'::timestamp, '2000-01-15'::timestamp) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 3\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Index Cond: (\"time\" = ANY (ARRAY[('Wed Jan 05 00:00:00 2000'::timestamp without time zone)::timestamp with time zone, ('Sat Jan 15 00:00:00 2000'::timestamp without time zone)::timestamp with time zone]))\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Index Cond: (\"time\" = ANY (ARRAY[('Wed Jan 05 00:00:00 2000'::timestamp without time zone)::timestamp with time zone, ('Sat Jan 15 00:00:00 2000'::timestamp without time zone)::timestamp with time zone]))\n\n\\qecho Time dimension chunk filtering works for ANY with single argument\nTime dimension chunk filtering works for ANY with single argument\n:PREFIX SELECT * FROM hyper_w_space WHERE time < ANY(ARRAY[1]) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (\"time\" < ANY ('{1}'::integer[]))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (\"time\" < ANY ('{1}'::integer[]))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (\"time\" < ANY ('{1}'::integer[]))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (\"time\" < ANY ('{1}'::integer[]))\n\n\\qecho Time dimension chunk filtering works for ALL with single argument\nTime dimension chunk filtering works for ALL with single argument\n:PREFIX SELECT * FROM hyper_w_space WHERE time < ALL(ARRAY[1]) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (\"time\" < ALL ('{1}'::integer[]))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (\"time\" < ALL ('{1}'::integer[]))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (\"time\" < ALL ('{1}'::integer[]))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (\"time\" < ALL ('{1}'::integer[]))\n\n\\qecho Time dimension chunk filtering works for ALL with multiple arguments\nTime dimension chunk filtering works for ALL with multiple arguments\n:PREFIX SELECT * FROM hyper_w_space WHERE time < ALL(ARRAY[1,10,20,30]) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (\"time\" < ALL ('{1,10,20,30}'::integer[]))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (\"time\" < ALL ('{1,10,20,30}'::integer[]))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (\"time\" < ALL ('{1,10,20,30}'::integer[]))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (\"time\" < ALL ('{1,10,20,30}'::integer[]))\n\n\\qecho AND intersection using IN and EQUALS\nAND intersection using IN and EQUALS\n:PREFIX SELECT * FROM hyper_w_space WHERE device_id IN ('dev1','dev2') AND device_id = 'dev1' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: ((device_id = ANY ('{dev1,dev2}'::text[])) AND (device_id = 'dev1'::text))\n         ->  Seq Scan on _hyper_2_110_chunk\n               Filter: ((device_id = ANY ('{dev1,dev2}'::text[])) AND (device_id = 'dev1'::text))\n         ->  Seq Scan on _hyper_2_114_chunk\n               Filter: ((device_id = ANY ('{dev1,dev2}'::text[])) AND (device_id = 'dev1'::text))\n\n\\qecho AND with no intersection using IN and EQUALS\nAND with no intersection using IN and EQUALS\n:PREFIX SELECT * FROM hyper_w_space WHERE device_id IN ('dev1','dev2') AND device_id = 'dev3' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: value\n   ->  Result\n         One-Time Filter: false\n\n\\qecho timestamps\ntimestamps\n\\qecho these should work since they are immutable functions\nthese should work since they are immutable functions\n:PREFIX SELECT * FROM hyper_ts WHERE time < 'Wed Dec 31 16:00:10 1969 PST'::timestamptz ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Append\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_3_117_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM hyper_ts WHERE time < to_timestamp(10) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Append\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_3_117_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM hyper_ts WHERE time < 'Wed Dec 31 16:00:10 1969'::timestamp AT TIME ZONE 'PST' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Append\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_3_117_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM hyper_ts WHERE time < to_timestamp(10) and device_id = 'dev1' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_3_116_chunk.value\n   ->  Seq Scan on _hyper_3_116_chunk\n         Filter: ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text))\n\n\\qecho these should not work since uses stable functions;\nthese should not work since uses stable functions;\n:PREFIX SELECT * FROM hyper_ts WHERE time < 'Wed Dec 31 16:00:10 1969'::timestamp ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Custom Scan (ChunkAppend) on hyper_ts\n         Chunks excluded during startup: 6\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969'::timestamp without time zone)\n         ->  Seq Scan on _hyper_3_117_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969'::timestamp without time zone)\n\n:PREFIX SELECT * FROM hyper_ts WHERE time < ('Wed Dec 31 16:00:10 1969'::timestamp::timestamptz) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Custom Scan (ChunkAppend) on hyper_ts\n         Chunks excluded during startup: 6\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: (\"time\" < ('Wed Dec 31 16:00:10 1969'::timestamp without time zone)::timestamp with time zone)\n         ->  Seq Scan on _hyper_3_117_chunk\n               Filter: (\"time\" < ('Wed Dec 31 16:00:10 1969'::timestamp without time zone)::timestamp with time zone)\n\n:PREFIX SELECT * FROM hyper_ts WHERE NOW() < time ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Custom Scan (ChunkAppend) on hyper_ts\n         Chunks excluded during startup: 7\n         ->  Seq Scan on _hyper_3_123_chunk\n               Filter: (now() < \"time\")\n\n\\qecho joins\njoins\n:PREFIX SELECT * FROM hyper_ts WHERE tag_id IN (SELECT id FROM tag WHERE tag.id=1) and time < to_timestamp(10) and device_id = 'dev1' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_3_116_chunk.value\n   ->  Nested Loop Semi Join\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text) AND (tag_id = 1))\n         ->  Seq Scan on tag\n               Filter: (id = 1)\n\n:PREFIX SELECT * FROM hyper_ts WHERE tag_id IN (SELECT id FROM tag WHERE tag.id=1) or (time < to_timestamp(10) and device_id = 'dev1') ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Custom Scan (ChunkAppend) on hyper_ts\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: ((hashed SubPlan 1) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n               SubPlan 1\n                 ->  Seq Scan on tag\n                       Filter: (id = 1)\n         ->  Seq Scan on _hyper_3_117_chunk\n               Filter: ((hashed SubPlan 1) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n         ->  Seq Scan on _hyper_3_118_chunk\n               Filter: ((hashed SubPlan 1) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n         ->  Seq Scan on _hyper_3_119_chunk\n               Filter: ((hashed SubPlan 1) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n         ->  Seq Scan on _hyper_3_120_chunk\n               Filter: ((hashed SubPlan 1) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n         ->  Seq Scan on _hyper_3_121_chunk\n               Filter: ((hashed SubPlan 1) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n         ->  Seq Scan on _hyper_3_122_chunk\n               Filter: ((hashed SubPlan 1) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n         ->  Seq Scan on _hyper_3_123_chunk\n               Filter: ((hashed SubPlan 1) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n\n:PREFIX SELECT * FROM hyper_ts WHERE tag_id IN (SELECT id FROM tag WHERE tag.name='tag1') and time < to_timestamp(10) and device_id = 'dev1' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_3_116_chunk.value\n   ->  Nested Loop\n         Join Filter: (_hyper_3_116_chunk.tag_id = tag.id)\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text))\n         ->  Seq Scan on tag\n               Filter: (name = 'tag1'::text)\n\n:PREFIX SELECT * FROM hyper_ts JOIN tag on (hyper_ts.tag_id = tag.id ) WHERE time < to_timestamp(10) and device_id = 'dev1' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_3_116_chunk.value\n   ->  Merge Join\n         Merge Cond: (tag.id = _hyper_3_116_chunk.tag_id)\n         ->  Index Scan using tag_pkey on tag\n         ->  Sort\n               Sort Key: _hyper_3_116_chunk.tag_id\n               ->  Seq Scan on _hyper_3_116_chunk\n                     Filter: ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text))\n\n:PREFIX SELECT * FROM hyper_ts JOIN tag on (hyper_ts.tag_id = tag.id ) WHERE tag.name = 'tag1' and time < to_timestamp(10) and device_id = 'dev1' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_3_116_chunk.value\n   ->  Nested Loop\n         Join Filter: (tag.id = _hyper_3_116_chunk.tag_id)\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text))\n         ->  Seq Scan on tag\n               Filter: (name = 'tag1'::text)\n\n\\qecho test constraint exclusion for constraints in ON clause of JOINs\ntest constraint exclusion for constraints in ON clause of JOINs\n\\qecho should exclude chunks on m1 and propagate qual to m2 because of INNER JOIN\nshould exclude chunks on m1 and propagate qual to m2 because of INNER JOIN\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho should exclude chunks on m2 and propagate qual to m1 because of INNER JOIN\nshould exclude chunks on m2 and propagate qual to m1 because of INNER JOIN\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho must not exclude on m1\nmust not exclude on m1\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Left Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   Join Filter: (m1.\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n\\qecho should exclude chunks on m2\nshould exclude chunks on m2\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Left Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho should exclude chunks on m1\nshould exclude chunks on m1\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: m1.\"time\"\n   ->  Merge Right Join\n         Merge Cond: (m1.\"time\" = m2.\"time\")\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n               Order: m1.\"time\"\n               ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Materialize\n               ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n                     Order: m2.\"time\"\n                     ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n                     ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n                     ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n                     ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n\\qecho must not exclude chunks on m2\nmust not exclude chunks on m2\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time < '2000-01-10' ORDER BY m1.time, m2.time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: m1.\"time\", m2.\"time\"\n   ->  Merge Left Join\n         Merge Cond: (m2.\"time\" = m1.\"time\")\n         Join Filter: (m2.\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n         ->  Materialize\n               ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n                     Order: m1.\"time\"\n                     ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n                     ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n                     ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n                     ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n                     ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n\n\\qecho time_bucket exclusion\ntime_bucket exclusion\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time) < 10::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_1_chunk.\"time\"\n   ->  Seq Scan on _hyper_1_1_chunk\n         Filter: (time_bucket('10'::bigint, \"time\") < '10'::bigint)\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time) < 11::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '11'::bigint)\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '11'::bigint)\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((\"time\" < '21'::bigint) AND (time_bucket('10'::bigint, \"time\") < '11'::bigint))\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time) <= 10::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") <= '10'::bigint)\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") <= '10'::bigint)\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((\"time\" <= '20'::bigint) AND (time_bucket('10'::bigint, \"time\") <= '10'::bigint))\n\n:PREFIX SELECT * FROM hyper WHERE 10::bigint > time_bucket(10, time) ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_1_chunk.\"time\"\n   ->  Seq Scan on _hyper_1_1_chunk\n         Filter: ('10'::bigint > time_bucket('10'::bigint, \"time\"))\n\n:PREFIX SELECT * FROM hyper WHERE 11::bigint > time_bucket(10, time) ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: ('11'::bigint > time_bucket('10'::bigint, \"time\"))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: ('11'::bigint > time_bucket('10'::bigint, \"time\"))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((\"time\" < '21'::bigint) AND ('11'::bigint > time_bucket('10'::bigint, \"time\")))\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time, 5) < 10::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (time_bucket('10'::bigint, \"time\", '5'::bigint) < '10'::bigint)\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (time_bucket('10'::bigint, \"time\", '5'::bigint) < '10'::bigint)\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time, 5) < 11::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (time_bucket('10'::bigint, \"time\", '5'::bigint) < '11'::bigint)\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (time_bucket('10'::bigint, \"time\", '5'::bigint) < '11'::bigint)\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((\"time\" < '21'::bigint) AND (time_bucket('10'::bigint, \"time\", '5'::bigint) < '11'::bigint))\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time, 5) <= 10::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (time_bucket('10'::bigint, \"time\", '5'::bigint) <= '10'::bigint)\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (time_bucket('10'::bigint, \"time\", '5'::bigint) <= '10'::bigint)\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((\"time\" <= '20'::bigint) AND (time_bucket('10'::bigint, \"time\", '5'::bigint) <= '10'::bigint))\n\n:PREFIX SELECT * FROM hyper WHERE 10::bigint > time_bucket(10, time, 5) ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: ('10'::bigint > time_bucket('10'::bigint, \"time\", '5'::bigint))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: ('10'::bigint > time_bucket('10'::bigint, \"time\", '5'::bigint))\n\n:PREFIX SELECT * FROM hyper WHERE 11::bigint > time_bucket(10, time, 5) ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: ('11'::bigint > time_bucket('10'::bigint, \"time\", '5'::bigint))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: ('11'::bigint > time_bucket('10'::bigint, \"time\", '5'::bigint))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((\"time\" < '21'::bigint) AND ('11'::bigint > time_bucket('10'::bigint, \"time\", '5'::bigint)))\n\n\\qecho timestamp time_bucket exclusion\ntimestamp time_bucket exclusion\nSELECT count(DISTINCT tableoid) FROM metrics_timestamp;\n count \n-------\n     5\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time) < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") < 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") < 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time) <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") <= 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") <= 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time) > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time) >= '2000-01-15' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Index Cond: (\"time\" >= 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") >= 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") >= 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") >= 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'3d'::interval) < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) < 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) < 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'3d'::interval) <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) <= 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) <= 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'3d'::interval) > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'3d'::interval) >= '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Index Cond: (\"time\" >= 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) >= 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) >= 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'2000-01-10'::timestamp) < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) < 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) < 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'2000-01-10'::timestamp) <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) <= 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) <= 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'2000-01-10'::timestamp) > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'2000-01-10'::timestamp) >= '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Index Cond: (\"time\" >= 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) >= 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) >= 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n\n\\qecho timestamptz time_bucket exclusion\ntimestamptz time_bucket exclusion\nSELECT count(DISTINCT tableoid) FROM metrics_timestamptz;\n count \n-------\n     5\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time) < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time) <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time) > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time) >= '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'3d'::interval) < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'3d'::interval) <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'3d'::interval) > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'3d'::interval) >= '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'2000-01-10'::timestamptz) < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'2000-01-10'::timestamptz) <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'2000-01-10'::timestamptz) > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'2000-01-10'::timestamptz) >= '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'Europe/Berlin') < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'Europe/Berlin') <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'Europe/Berlin') > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'Europe/Berlin') >= '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho test overflow behaviour of time_bucket exclusion\ntest overflow behaviour of time_bucket exclusion\n:PREFIX SELECT * FROM hyper WHERE time > 950 AND time_bucket(10, time) < '9223372036854775807'::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_96_chunk\n               Filter: ((\"time\" > 950) AND (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint))\n         ->  Seq Scan on _hyper_1_97_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint)\n         ->  Seq Scan on _hyper_1_98_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint)\n         ->  Seq Scan on _hyper_1_99_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint)\n         ->  Seq Scan on _hyper_1_100_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint)\n         ->  Seq Scan on _hyper_1_101_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint)\n         ->  Seq Scan on _hyper_1_102_chunk\n               Filter: ((\"time\" > 950) AND (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint))\n\n\\qecho test timestamp upper boundary\ntest timestamp upper boundary\n\\qecho there should be no transformation if we are out of the supported (TimescaleDB-specific) range\nthere should be no transformation if we are out of the supported (TimescaleDB-specific) range\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('1d',time) < '294276-01-01'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n\n\\qecho transformation would be out of range\ntransformation would be out of range\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('1000d',time) < '294276-01-01'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n\n\\qecho test timestamptz upper boundary\ntest timestamptz upper boundary\n\\qecho there should be no transformation if we are out of the supported (TimescaleDB-specific) range\nthere should be no transformation if we are out of the supported (TimescaleDB-specific) range\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('1d',time) < '294276-01-01'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n\n\\qecho transformation would be out of range\ntransformation would be out of range\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('1000d',time) < '294276-01-01'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n\n\\qecho time_bucket exclusion with run-time constants\ntime_bucket exclusion with run-time constants\n-- These queries have a stable time_bucket expression, because the text::interval conversion is stable.\nPREPARE P1(text    , text) AS SELECT * FROM metrics_timestamptz WHERE time_bucket($1::interval, time) > $2::timestamptz ORDER BY time;\nPREPARE P2(text    , text) AS SELECT * FROM metrics_timestamp   WHERE time_bucket($1::interval, time) > $2::timestamptz ORDER BY time;\nPREPARE P3(text    , text) AS SELECT * FROM metrics_timestamptz WHERE time_bucket($1::interval, time) > $2::timestamp   ORDER BY time;\nPREPARE P4(text    , text) AS SELECT * FROM metrics_timestamp   WHERE time_bucket($1::interval, time) > $2::timestamp   ORDER BY time;\n-- These queries have an immutable time_bucket expression, because the parameter is passed as interval, and no conversion is involved.\nPREPARE P5(interval, text) AS SELECT * FROM metrics_timestamptz WHERE time_bucket($1::interval, time) > $2::timestamptz ORDER BY time;\nPREPARE P6(interval, text) AS SELECT * FROM metrics_timestamp   WHERE time_bucket($1::interval, time) > $2::timestamptz ORDER BY time;\nPREPARE P7(interval, text) AS SELECT * FROM metrics_timestamptz WHERE time_bucket($1::interval, time) > $2::timestamp   ORDER BY time;\nPREPARE P8(interval, text) AS SELECT * FROM metrics_timestamp   WHERE time_bucket($1::interval, time) > $2::timestamp   ORDER BY time;\nSET plan_cache_mode TO 'force_custom_plan';\n:PREFIX EXECUTE P1('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P2('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P3('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P4('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P5('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P6('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P7('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P8('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P1('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n\n:PREFIX EXECUTE P2('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n\n:PREFIX EXECUTE P3('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n\n:PREFIX EXECUTE P4('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n\n:PREFIX EXECUTE P5('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n\n:PREFIX EXECUTE P6('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n\n:PREFIX EXECUTE P7('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n\n:PREFIX EXECUTE P8('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n\nSET plan_cache_mode TO 'force_generic_plan';\n:PREFIX EXECUTE P1('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P2('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P3('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P4('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P5('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P6('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P7('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P8('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P1('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n\n:PREFIX EXECUTE P2('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n\n:PREFIX EXECUTE P3('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n\n:PREFIX EXECUTE P4('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n\n:PREFIX EXECUTE P5('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n\n:PREFIX EXECUTE P6('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n\n:PREFIX EXECUTE P7('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n\n:PREFIX EXECUTE P8('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n\nRESET plan_cache_mode;\nDEALLOCATE P1;\nDEALLOCATE P2;\nDEALLOCATE P3;\nDEALLOCATE P4;\nDEALLOCATE P5;\nDEALLOCATE P6;\nDEALLOCATE P7;\nDEALLOCATE P8;\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time) > 10 AND time_bucket(10, time) < 100 ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: ((\"time\" > '10'::bigint) AND (\"time\" < '100'::bigint) AND (time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_4_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_5_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_6_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_7_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_8_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_9_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_10_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time) > 10 AND time_bucket(10, time) < 20 ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_2_chunk.\"time\"\n   ->  Seq Scan on _hyper_1_2_chunk\n         Filter: ((\"time\" > '10'::bigint) AND (\"time\" < '20'::bigint) AND (time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 20))\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(1, time) > 11 AND time_bucket(1, time) < 19 ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_2_chunk.\"time\"\n   ->  Seq Scan on _hyper_1_2_chunk\n         Filter: ((\"time\" > '11'::bigint) AND (\"time\" < '19'::bigint) AND (time_bucket('1'::bigint, \"time\") > 11) AND (time_bucket('1'::bigint, \"time\") < 19))\n\n:PREFIX SELECT * FROM hyper WHERE 10 < time_bucket(10, time) AND 20 > time_bucket(10,time) ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_2_chunk.\"time\"\n   ->  Seq Scan on _hyper_1_2_chunk\n         Filter: ((\"time\" > '10'::bigint) AND (\"time\" < '20'::bigint) AND (10 < time_bucket('10'::bigint, \"time\")) AND (20 > time_bucket('10'::bigint, \"time\")))\n\n\\qecho time_bucket exclusion with date\ntime_bucket exclusion with date\n:PREFIX SELECT * FROM metrics_date WHERE time_bucket('1d',time) < '2000-01-03' ORDER BY time;\n--- QUERY PLAN ---\n Index Only Scan Backward using _hyper_8_171_chunk_metrics_date_time_idx on _hyper_8_171_chunk\n   Index Cond: (\"time\" < '01-03-2000'::date)\n   Filter: (time_bucket('@ 1 day'::interval, \"time\") < '01-03-2000'::date)\n\n:PREFIX SELECT * FROM metrics_date WHERE time_bucket('1d',time) >= '2000-01-03' AND time_bucket('1d',time) <= '2000-01-10' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date\n   Order: metrics_date.\"time\"\n   ->  Index Only Scan Backward using _hyper_8_171_chunk_metrics_date_time_idx on _hyper_8_171_chunk\n         Index Cond: ((\"time\" >= '01-03-2000'::date) AND (\"time\" <= '01-11-2000'::date))\n         Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= '01-03-2000'::date) AND (time_bucket('@ 1 day'::interval, \"time\") <= '01-10-2000'::date))\n   ->  Index Only Scan Backward using _hyper_8_172_chunk_metrics_date_time_idx on _hyper_8_172_chunk\n         Index Cond: ((\"time\" >= '01-03-2000'::date) AND (\"time\" <= '01-11-2000'::date))\n         Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= '01-03-2000'::date) AND (time_bucket('@ 1 day'::interval, \"time\") <= '01-10-2000'::date))\n\n\\qecho time_bucket exclusion with timestamp\ntime_bucket exclusion with timestamp\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('1d',time) < '2000-01-03' ORDER BY time;\n--- QUERY PLAN ---\n Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n   Index Cond: (\"time\" < 'Mon Jan 03 00:00:00 2000'::timestamp without time zone)\n   Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Mon Jan 03 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('1d',time) >= '2000-01-03' AND time_bucket('1d',time) <= '2000-01-10' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000'::timestamp without time zone) AND (\"time\" <= 'Tue Jan 11 00:00:00 2000'::timestamp without time zone))\n         Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000'::timestamp without time zone) AND (time_bucket('@ 1 day'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000'::timestamp without time zone))\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000'::timestamp without time zone) AND (\"time\" <= 'Tue Jan 11 00:00:00 2000'::timestamp without time zone))\n         Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000'::timestamp without time zone) AND (time_bucket('@ 1 day'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000'::timestamp without time zone))\n\n\\qecho time_bucket exclusion with timestamptz\ntime_bucket exclusion with timestamptz\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('6h',time) < '2000-01-03' ORDER BY time;\n--- QUERY PLAN ---\n Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n   Index Cond: (\"time\" < 'Mon Jan 03 06:00:00 2000 PST'::timestamp with time zone)\n   Filter: (time_bucket('@ 6 hours'::interval, \"time\") < 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('6h',time) >= '2000-01-03' AND time_bucket('6h',time) <= '2000-01-10' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" <= 'Mon Jan 10 06:00:00 2000 PST'::timestamp with time zone))\n         Filter: ((time_bucket('@ 6 hours'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 6 hours'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" <= 'Mon Jan 10 06:00:00 2000 PST'::timestamp with time zone))\n         Filter: ((time_bucket('@ 6 hours'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 6 hours'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho time_bucket exclusion with timestamptz and day interval\ntime_bucket exclusion with timestamptz and day interval\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('1d',time) < '2000-01-03' ORDER BY time;\n--- QUERY PLAN ---\n Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n   Index Cond: (\"time\" < 'Tue Jan 04 00:00:00 2000 PST'::timestamp with time zone)\n   Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('1d',time) >= '2000-01-03' AND time_bucket('1d',time) <= '2000-01-10' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" <= 'Tue Jan 11 00:00:00 2000 PST'::timestamp with time zone))\n         Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 1 day'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" <= 'Tue Jan 11 00:00:00 2000 PST'::timestamp with time zone))\n         Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 1 day'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('1d',time) >= '2000-01-03' AND time_bucket('7d',time) <= '2000-01-10' ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: metrics_timestamptz.\"time\"\n   ->  Append\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n               Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" <= 'Mon Jan 17 00:00:00 2000 PST'::timestamp with time zone))\n               Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 7 days'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Seq Scan on _hyper_6_161_chunk\n               Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 7 days'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n               Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" <= 'Mon Jan 17 00:00:00 2000 PST'::timestamp with time zone))\n               Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 7 days'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho no transformation\nno transformation\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10 + floor(random())::int, time) > 10 AND time_bucket(10 + floor(random())::int, time) < 100 AND time < 150 ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Custom Scan (ChunkAppend) on hyper\n         Chunks excluded during startup: 0\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_4_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_5_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_6_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_7_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_8_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_9_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_10_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_11_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_12_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_13_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_14_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_15_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n\n\\qecho exclude chunks based on time column with partitioning function. This\nexclude chunks based on time column with partitioning function. This\n\\qecho transparently applies the time partitioning function on the time\ntransparently applies the time partitioning function on the time\n\\qecho value to be able to exclude chunks (similar to a closed dimension).\nvalue to be able to exclude chunks (similar to a closed dimension).\n:PREFIX SELECT * FROM hyper_timefunc WHERE time < 4 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_timefunc.value\n   ->  Append\n         ->  Seq Scan on _hyper_4_124_chunk\n               Filter: (\"time\" < '4'::double precision)\n         ->  Seq Scan on _hyper_4_125_chunk\n               Filter: (\"time\" < '4'::double precision)\n         ->  Seq Scan on _hyper_4_126_chunk\n               Filter: (\"time\" < '4'::double precision)\n         ->  Seq Scan on _hyper_4_127_chunk\n               Filter: (\"time\" < '4'::double precision)\n\n\\qecho excluding based on time expression is currently unoptimized\nexcluding based on time expression is currently unoptimized\n:PREFIX SELECT * FROM hyper_timefunc WHERE unix_to_timestamp(time) < 'Wed Dec 31 16:00:04 1969 PST' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_timefunc.value\n   ->  Append\n         ->  Seq Scan on _hyper_4_124_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_125_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_126_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_127_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_128_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_129_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_130_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_131_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_132_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_133_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_134_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_135_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_136_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_137_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_138_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_139_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_140_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_141_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_142_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_143_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_144_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_145_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_146_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_147_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_148_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_149_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_150_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_151_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_152_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_153_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_154_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n\n\\qecho test qual propagation for joins\ntest qual propagation for joins\nRESET constraint_exclusion;\n\\qecho nothing to propagate\nnothing to propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1, metrics_timestamptz_2 m2 WHERE m1.time = m2.time ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Left Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time ORDER BY m1.time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: m1.\"time\"\n   ->  Merge Right Join\n         Merge Cond: (m1.\"time\" = m2.\"time\")\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n               Order: m1.\"time\"\n               ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n               ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n               ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n         ->  Materialize\n               ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n                     Order: m2.\"time\"\n                     ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n                     ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n                     ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n                     ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n\\qecho OR constraints should not propagate\nOR constraints should not propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10' OR m1.time > '2001-01-01' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Filter: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) OR (\"time\" > 'Mon Jan 01 00:00:00 2001 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Filter: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) OR (\"time\" > 'Mon Jan 01 00:00:00 2001 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n\\qecho test single constraint\ntest single constraint\n\\qecho constraint should be on both scans\nconstraint should be on both scans\n\\qecho these will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\nthese will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1, metrics_timestamptz_2 m2 WHERE m1.time = m2.time AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Left Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho test 2 constraints on single relation\ntest 2 constraints on single relation\n\\qecho these will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\nthese will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1, metrics_timestamptz_2 m2 WHERE m1.time = m2.time AND m1.time > '2000-01-01' AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Nested Loop Left Join\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Append\n         ->  Index Only Scan using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n               Index Cond: (\"time\" = m1.\"time\")\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho test 2 constraints with 1 constraint on each relation\ntest 2 constraints with 1 constraint on each relation\n\\qecho these will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\nthese will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1, metrics_timestamptz_2 m2 WHERE m1.time = m2.time AND m1.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho test constraints in ON clause of INNER JOIN\ntest constraints in ON clause of INNER JOIN\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho test constraints in ON clause of LEFT JOIN\ntest constraints in ON clause of LEFT JOIN\n\\qecho must not propagate\nmust not propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Left Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho test constraints in ON clause of RIGHT JOIN\ntest constraints in ON clause of RIGHT JOIN\n\\qecho must not propagate\nmust not propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Gather Merge\n   Workers Planned: 2\n   ->  Sort\n         Sort Key: m1.\"time\"\n         ->  Parallel Hash Left Join\n               Hash Cond: (m2.\"time\" = m1.\"time\")\n               Join Filter: ((m2.\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (m2.\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_7_165_chunk m2_1\n                     ->  Parallel Seq Scan on _hyper_7_166_chunk m2_2\n                     ->  Parallel Seq Scan on _hyper_7_167_chunk m2_3\n                     ->  Parallel Seq Scan on _hyper_7_168_chunk m2_4\n                     ->  Parallel Seq Scan on _hyper_7_169_chunk m2_5\n                     ->  Parallel Seq Scan on _hyper_7_170_chunk m2_6\n               ->  Parallel Hash\n                     ->  Parallel Append\n                           ->  Parallel Seq Scan on _hyper_6_160_chunk m1_1\n                           ->  Parallel Seq Scan on _hyper_6_161_chunk m1_2\n                           ->  Parallel Seq Scan on _hyper_6_162_chunk m1_3\n                           ->  Parallel Seq Scan on _hyper_6_163_chunk m1_4\n                           ->  Parallel Seq Scan on _hyper_6_164_chunk m1_5\n\n\\qecho test equality condition not in ON clause\ntest equality condition not in ON clause\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON true WHERE m2.time = m1.time AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho test constraints not joined on\ntest constraints not joined on\n\\qecho device_id constraint must not propagate\ndevice_id constraint must not propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON true WHERE m2.time = m1.time AND m2.time < '2000-01-10' AND m1.device_id = 1 ORDER BY m1.time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: m1.\"time\"\n   ->  Nested Loop\n         ->  Append\n               ->  Seq Scan on _hyper_6_160_chunk m1_1\n                     Filter: (device_id = 1)\n               ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n                     Filter: (device_id = 1)\n         ->  Append\n               ->  Index Only Scan using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: (\"time\" = m1.\"time\")\n               ->  Index Only Scan using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" = m1.\"time\") AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho test multiple join conditions\ntest multiple join conditions\n\\qecho device_id constraint should propagate\ndevice_id constraint should propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON true WHERE m2.time = m1.time AND m1.device_id = m2.device_id AND m2.time < '2000-01-10' AND m1.device_id = 1 ORDER BY m1.time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: m1.\"time\"\n   ->  Nested Loop\n         ->  Append\n               ->  Seq Scan on _hyper_6_160_chunk m1_1\n                     Filter: (device_id = 1)\n               ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n                     Filter: (device_id = 1)\n         ->  Append\n               ->  Index Scan using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: (\"time\" = m1.\"time\")\n                     Filter: (device_id = 1)\n               ->  Index Scan using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" = m1.\"time\") AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n                     Filter: (device_id = 1)\n\n\\qecho test join with 3 tables\ntest join with 3 tables\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time INNER JOIN metrics_timestamptz m3 ON m2.time=m3.time WHERE m1.time > '2000-01-01' AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Nested Loop\n   ->  Merge Join\n         Merge Cond: (m1.\"time\" = m2.\"time\")\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n               Order: m1.\"time\"\n               ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Materialize\n               ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n                     Order: m2.\"time\"\n                     ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                           Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n                     ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                           Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Append\n         ->  Index Only Scan using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m3_1\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m3_2\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m3_3\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m3_4\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m3_5\n               Index Cond: (\"time\" = m1.\"time\")\n\n\\qecho test non-Const constraints\ntest non-Const constraints\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10'::text::timestamptz ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         Chunks excluded during startup: 3\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               Chunks excluded during startup: 4\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n\n\\qecho test now()\ntest now()\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < now() ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         Chunks excluded during startup: 0\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: (\"time\" < now())\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < now())\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n               Index Cond: (\"time\" < now())\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n               Index Cond: (\"time\" < now())\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n               Index Cond: (\"time\" < now())\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               Chunks excluded during startup: 0\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: (\"time\" < now())\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < now())\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n                     Index Cond: (\"time\" < now())\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n                     Index Cond: (\"time\" < now())\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n                     Index Cond: (\"time\" < now())\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n                     Index Cond: (\"time\" < now())\n\n\\qecho test volatile function\ntest volatile function\n\\qecho should not propagate\nshould not propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < clock_timestamp() ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         Chunks excluded during startup: 0\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n               Filter: (\"time\" < clock_timestamp())\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m2.time < clock_timestamp() ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m2.\"time\" = m1.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n         Order: m2.\"time\"\n         Chunks excluded during startup: 0\n         ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n               Filter: (\"time\" < clock_timestamp())\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n               Order: m1.\"time\"\n               ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n               ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n               ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n\n\\qecho test JOINs with normal table\ntest JOINs with normal table\n\\qecho will not propagate because constraints are only added to hypertables\nwill not propagate because constraints are only added to hypertables\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN regular_timestamptz m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Sort\n         Sort Key: m2.\"time\"\n         ->  Seq Scan on regular_timestamptz m2\n\n\\qecho test JOINs with normal table\ntest JOINs with normal table\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN regular_timestamptz m2 ON m1.time = m2.time WHERE m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m2.\"time\" = m1.\"time\")\n   ->  Sort\n         Sort Key: m2.\"time\"\n         ->  Seq Scan on regular_timestamptz m2\n               Filter: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n               Order: m1.\"time\"\n               ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho test quals are not pushed into OUTER JOIN\ntest quals are not pushed into OUTER JOIN\nCREATE TABLE outer_join_1 (id int, name text,time timestamptz NOT NULL DEFAULT '2000-01-01');\nCREATE TABLE outer_join_2 (id int, name text,time timestamptz NOT NULL DEFAULT '2000-01-01');\nSELECT (SELECT table_name FROM create_hypertable(tbl, 'time')) FROM (VALUES ('outer_join_1'),('outer_join_2')) v(tbl);\n  table_name  \n--------------\n outer_join_1\n outer_join_2\n\nINSERT INTO outer_join_1 VALUES(1,'a'), (2,'b');\nINSERT INTO outer_join_2 VALUES(1,'a');\n:PREFIX SELECT one.id, two.name FROM outer_join_1 one LEFT OUTER JOIN outer_join_2 two ON one.id=two.id WHERE one.id=2;\n--- QUERY PLAN ---\n Nested Loop Left Join\n   ->  Seq Scan on _hyper_9_176_chunk one\n         Filter: (id = 2)\n   ->  Materialize\n         ->  Seq Scan on _hyper_10_177_chunk two\n               Filter: (id = 2)\n\n:PREFIX SELECT one.id, two.name FROM outer_join_2 two RIGHT OUTER JOIN outer_join_1 one ON one.id=two.id WHERE one.id=2;\n--- QUERY PLAN ---\n Nested Loop Left Join\n   ->  Seq Scan on _hyper_9_176_chunk one\n         Filter: (id = 2)\n   ->  Materialize\n         ->  Seq Scan on _hyper_10_177_chunk two\n               Filter: (id = 2)\n\nDROP TABLE outer_join_1;\nDROP TABLE outer_join_2;\n-- test UNION between regular table and hypertable\nSELECT time FROM regular_timestamptz UNION SELECT time FROM metrics_timestamptz ORDER BY 1;\n             time             \n------------------------------\n Sat Jan 01 00:00:00 2000 PST\n Sun Jan 02 00:00:00 2000 PST\n Mon Jan 03 00:00:00 2000 PST\n Tue Jan 04 00:00:00 2000 PST\n Wed Jan 05 00:00:00 2000 PST\n Thu Jan 06 00:00:00 2000 PST\n Fri Jan 07 00:00:00 2000 PST\n Sat Jan 08 00:00:00 2000 PST\n Sun Jan 09 00:00:00 2000 PST\n Mon Jan 10 00:00:00 2000 PST\n Tue Jan 11 00:00:00 2000 PST\n Wed Jan 12 00:00:00 2000 PST\n Thu Jan 13 00:00:00 2000 PST\n Fri Jan 14 00:00:00 2000 PST\n Sat Jan 15 00:00:00 2000 PST\n Sun Jan 16 00:00:00 2000 PST\n Mon Jan 17 00:00:00 2000 PST\n Tue Jan 18 00:00:00 2000 PST\n Wed Jan 19 00:00:00 2000 PST\n Thu Jan 20 00:00:00 2000 PST\n Fri Jan 21 00:00:00 2000 PST\n Sat Jan 22 00:00:00 2000 PST\n Sun Jan 23 00:00:00 2000 PST\n Mon Jan 24 00:00:00 2000 PST\n Tue Jan 25 00:00:00 2000 PST\n Wed Jan 26 00:00:00 2000 PST\n Thu Jan 27 00:00:00 2000 PST\n Fri Jan 28 00:00:00 2000 PST\n Sat Jan 29 00:00:00 2000 PST\n Sun Jan 30 00:00:00 2000 PST\n Mon Jan 31 00:00:00 2000 PST\n Tue Feb 01 00:00:00 2000 PST\n\n-- test UNION ALL between regular table and hypertable\nSELECT time FROM regular_timestamptz UNION ALL SELECT time FROM metrics_timestamptz ORDER BY 1;\n             time             \n------------------------------\n Sat Jan 01 00:00:00 2000 PST\n Sat Jan 01 00:00:00 2000 PST\n Sat Jan 01 00:00:00 2000 PST\n Sat Jan 01 00:00:00 2000 PST\n Sun Jan 02 00:00:00 2000 PST\n Sun Jan 02 00:00:00 2000 PST\n Sun Jan 02 00:00:00 2000 PST\n Sun Jan 02 00:00:00 2000 PST\n Mon Jan 03 00:00:00 2000 PST\n Mon Jan 03 00:00:00 2000 PST\n Mon Jan 03 00:00:00 2000 PST\n Mon Jan 03 00:00:00 2000 PST\n Tue Jan 04 00:00:00 2000 PST\n Tue Jan 04 00:00:00 2000 PST\n Tue Jan 04 00:00:00 2000 PST\n Tue Jan 04 00:00:00 2000 PST\n Wed Jan 05 00:00:00 2000 PST\n Wed Jan 05 00:00:00 2000 PST\n Wed Jan 05 00:00:00 2000 PST\n Wed Jan 05 00:00:00 2000 PST\n Thu Jan 06 00:00:00 2000 PST\n Thu Jan 06 00:00:00 2000 PST\n Thu Jan 06 00:00:00 2000 PST\n Thu Jan 06 00:00:00 2000 PST\n Fri Jan 07 00:00:00 2000 PST\n Fri Jan 07 00:00:00 2000 PST\n Fri Jan 07 00:00:00 2000 PST\n Fri Jan 07 00:00:00 2000 PST\n Sat Jan 08 00:00:00 2000 PST\n Sat Jan 08 00:00:00 2000 PST\n Sat Jan 08 00:00:00 2000 PST\n Sat Jan 08 00:00:00 2000 PST\n Sun Jan 09 00:00:00 2000 PST\n Sun Jan 09 00:00:00 2000 PST\n Sun Jan 09 00:00:00 2000 PST\n Sun Jan 09 00:00:00 2000 PST\n Mon Jan 10 00:00:00 2000 PST\n Mon Jan 10 00:00:00 2000 PST\n Mon Jan 10 00:00:00 2000 PST\n Mon Jan 10 00:00:00 2000 PST\n Tue Jan 11 00:00:00 2000 PST\n Tue Jan 11 00:00:00 2000 PST\n Tue Jan 11 00:00:00 2000 PST\n Tue Jan 11 00:00:00 2000 PST\n Wed Jan 12 00:00:00 2000 PST\n Wed Jan 12 00:00:00 2000 PST\n Wed Jan 12 00:00:00 2000 PST\n Wed Jan 12 00:00:00 2000 PST\n Thu Jan 13 00:00:00 2000 PST\n Thu Jan 13 00:00:00 2000 PST\n Thu Jan 13 00:00:00 2000 PST\n Thu Jan 13 00:00:00 2000 PST\n Fri Jan 14 00:00:00 2000 PST\n Fri Jan 14 00:00:00 2000 PST\n Fri Jan 14 00:00:00 2000 PST\n Fri Jan 14 00:00:00 2000 PST\n Sat Jan 15 00:00:00 2000 PST\n Sat Jan 15 00:00:00 2000 PST\n Sat Jan 15 00:00:00 2000 PST\n Sat Jan 15 00:00:00 2000 PST\n Sun Jan 16 00:00:00 2000 PST\n Sun Jan 16 00:00:00 2000 PST\n Sun Jan 16 00:00:00 2000 PST\n Sun Jan 16 00:00:00 2000 PST\n Mon Jan 17 00:00:00 2000 PST\n Mon Jan 17 00:00:00 2000 PST\n Mon Jan 17 00:00:00 2000 PST\n Mon Jan 17 00:00:00 2000 PST\n Tue Jan 18 00:00:00 2000 PST\n Tue Jan 18 00:00:00 2000 PST\n Tue Jan 18 00:00:00 2000 PST\n Tue Jan 18 00:00:00 2000 PST\n Wed Jan 19 00:00:00 2000 PST\n Wed Jan 19 00:00:00 2000 PST\n Wed Jan 19 00:00:00 2000 PST\n Wed Jan 19 00:00:00 2000 PST\n Thu Jan 20 00:00:00 2000 PST\n Thu Jan 20 00:00:00 2000 PST\n Thu Jan 20 00:00:00 2000 PST\n Thu Jan 20 00:00:00 2000 PST\n Fri Jan 21 00:00:00 2000 PST\n Fri Jan 21 00:00:00 2000 PST\n Fri Jan 21 00:00:00 2000 PST\n Fri Jan 21 00:00:00 2000 PST\n Sat Jan 22 00:00:00 2000 PST\n Sat Jan 22 00:00:00 2000 PST\n Sat Jan 22 00:00:00 2000 PST\n Sat Jan 22 00:00:00 2000 PST\n Sun Jan 23 00:00:00 2000 PST\n Sun Jan 23 00:00:00 2000 PST\n Sun Jan 23 00:00:00 2000 PST\n Sun Jan 23 00:00:00 2000 PST\n Mon Jan 24 00:00:00 2000 PST\n Mon Jan 24 00:00:00 2000 PST\n Mon Jan 24 00:00:00 2000 PST\n Mon Jan 24 00:00:00 2000 PST\n Tue Jan 25 00:00:00 2000 PST\n Tue Jan 25 00:00:00 2000 PST\n Tue Jan 25 00:00:00 2000 PST\n Tue Jan 25 00:00:00 2000 PST\n Wed Jan 26 00:00:00 2000 PST\n Wed Jan 26 00:00:00 2000 PST\n Wed Jan 26 00:00:00 2000 PST\n Wed Jan 26 00:00:00 2000 PST\n Thu Jan 27 00:00:00 2000 PST\n Thu Jan 27 00:00:00 2000 PST\n Thu Jan 27 00:00:00 2000 PST\n Thu Jan 27 00:00:00 2000 PST\n Fri Jan 28 00:00:00 2000 PST\n Fri Jan 28 00:00:00 2000 PST\n Fri Jan 28 00:00:00 2000 PST\n Fri Jan 28 00:00:00 2000 PST\n Sat Jan 29 00:00:00 2000 PST\n Sat Jan 29 00:00:00 2000 PST\n Sat Jan 29 00:00:00 2000 PST\n Sat Jan 29 00:00:00 2000 PST\n Sun Jan 30 00:00:00 2000 PST\n Sun Jan 30 00:00:00 2000 PST\n Sun Jan 30 00:00:00 2000 PST\n Sun Jan 30 00:00:00 2000 PST\n Mon Jan 31 00:00:00 2000 PST\n Mon Jan 31 00:00:00 2000 PST\n Mon Jan 31 00:00:00 2000 PST\n Mon Jan 31 00:00:00 2000 PST\n Tue Feb 01 00:00:00 2000 PST\n Tue Feb 01 00:00:00 2000 PST\n Tue Feb 01 00:00:00 2000 PST\n Tue Feb 01 00:00:00 2000 PST\n\n-- test nested join qual propagation\n:PREFIX\nSELECT * FROM (\nSELECT o1_m1.time FROM metrics_timestamptz o1_m1 INNER JOIN metrics_timestamptz_2 o1_m2 ON true WHERE o1_m2.time = o1_m1.time AND o1_m1.device_id = o1_m2.device_id AND o1_m2.time < '2000-01-10' AND o1_m1.device_id = 1\n) o1 FULL OUTER JOIN (\nSELECT o2_m1.time FROM metrics_timestamptz o2_m1 FULL OUTER JOIN metrics_timestamptz_2 o2_m2 ON true WHERE o2_m2.time = o2_m1.time AND o2_m1.device_id = o2_m2.device_id AND o2_m2.time > '2000-01-20' AND o2_m1.device_id = 2\n) o2 ON o1.time = o2.time ORDER BY 1,2;\n--- QUERY PLAN ---\n Sort\n   Sort Key: o1_m1.\"time\", o2_m1.\"time\"\n   ->  Hash Full Join\n         Hash Cond: (o2_m1.\"time\" = o1_m1.\"time\")\n         ->  Nested Loop\n               ->  Append\n                     ->  Index Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk o2_m2_1\n                           Index Cond: (\"time\" > 'Thu Jan 20 00:00:00 2000 PST'::timestamp with time zone)\n                           Filter: (device_id = 2)\n                     ->  Seq Scan on _hyper_7_169_chunk o2_m2_2\n                           Filter: (device_id = 2)\n                     ->  Seq Scan on _hyper_7_170_chunk o2_m2_3\n                           Filter: (device_id = 2)\n               ->  Append\n                     ->  Index Scan using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk o2_m1_1\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk o2_m1_2\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk o2_m1_3\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk o2_m1_4\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk o2_m1_5\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n         ->  Hash\n               ->  Nested Loop\n                     ->  Append\n                           ->  Seq Scan on _hyper_7_165_chunk o1_m2_1\n                                 Filter: (device_id = 1)\n                           ->  Index Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk o1_m2_2\n                                 Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n                                 Filter: (device_id = 1)\n                     ->  Append\n                           ->  Index Scan using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk o1_m1_1\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk o1_m1_2\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk o1_m1_3\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk o1_m1_4\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk o1_m1_5\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n\n:PREFIX\nSELECT * FROM (\nSELECT o1_m1.time FROM metrics_timestamptz o1_m1 INNER JOIN metrics_timestamptz_2 o1_m2 ON o1_m2.time = o1_m1.time AND o1_m1.device_id = o1_m2.device_id WHERE o1_m2.time < '2000-01-10' AND o1_m1.device_id = 1\n) o1 FULL OUTER JOIN (\nSELECT o2_m1.time FROM metrics_timestamptz o2_m1 FULL OUTER JOIN metrics_timestamptz_2 o2_m2 ON o2_m2.time = o2_m1.time AND o2_m1.device_id = o2_m2.device_id WHERE o2_m2.time > '2000-01-20' AND o2_m1.device_id = 2\n) o2 ON o1.time = o2.time ORDER BY 1,2;\n--- QUERY PLAN ---\n Sort\n   Sort Key: o1_m1.\"time\", o2_m1.\"time\"\n   ->  Hash Full Join\n         Hash Cond: (o2_m1.\"time\" = o1_m1.\"time\")\n         ->  Nested Loop\n               ->  Append\n                     ->  Index Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk o2_m2_1\n                           Index Cond: (\"time\" > 'Thu Jan 20 00:00:00 2000 PST'::timestamp with time zone)\n                           Filter: (device_id = 2)\n                     ->  Seq Scan on _hyper_7_169_chunk o2_m2_2\n                           Filter: (device_id = 2)\n                     ->  Seq Scan on _hyper_7_170_chunk o2_m2_3\n                           Filter: (device_id = 2)\n               ->  Append\n                     ->  Index Scan using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk o2_m1_1\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk o2_m1_2\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk o2_m1_3\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk o2_m1_4\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk o2_m1_5\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n         ->  Hash\n               ->  Nested Loop\n                     ->  Append\n                           ->  Seq Scan on _hyper_7_165_chunk o1_m2_1\n                                 Filter: (device_id = 1)\n                           ->  Index Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk o1_m2_2\n                                 Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n                                 Filter: (device_id = 1)\n                     ->  Append\n                           ->  Index Scan using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk o1_m1_1\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk o1_m1_2\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk o1_m1_3\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk o1_m1_4\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk o1_m1_5\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n\n\\set ECHO errors\nRESET timescaledb.enable_optimizations;\nCREATE TABLE t(time timestamptz NOT NULL);\nSELECT table_name FROM create_hypertable('t','time');\n table_name \n------------\n t\n\nINSERT INTO t VALUES ('2000-01-01'), ('2010-01-01'), ('2020-01-01');\nEXPLAIN (buffers off, costs off) SELECT * FROM t t1 INNER JOIN t t2 ON t1.time = t2.time WHERE t1.time < timestamptz '2010-01-01';\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (t1.\"time\" = t2.\"time\")\n   ->  Merge Append\n         Sort Key: t1.\"time\"\n         ->  Index Only Scan Backward using _hyper_15_182_chunk_t_time_idx on _hyper_15_182_chunk t1_1\n         ->  Index Only Scan Backward using _hyper_15_183_chunk_t_time_idx on _hyper_15_183_chunk t1_2\n               Index Cond: (\"time\" < 'Fri Jan 01 00:00:00 2010 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Merge Append\n               Sort Key: t2.\"time\"\n               ->  Index Only Scan Backward using _hyper_15_182_chunk_t_time_idx on _hyper_15_182_chunk t2_1\n               ->  Index Only Scan Backward using _hyper_15_183_chunk_t_time_idx on _hyper_15_183_chunk t2_2\n                     Index Cond: (\"time\" < 'Fri Jan 01 00:00:00 2010 PST'::timestamp with time zone)\n\nSET timescaledb.enable_qual_propagation TO false;\nEXPLAIN (buffers off, costs off) SELECT * FROM t t1 INNER JOIN t t2 ON t1.time = t2.time WHERE t1.time < timestamptz '2010-01-01';\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (t1.\"time\" = t2.\"time\")\n   ->  Merge Append\n         Sort Key: t1.\"time\"\n         ->  Index Only Scan Backward using _hyper_15_182_chunk_t_time_idx on _hyper_15_182_chunk t1_1\n         ->  Index Only Scan Backward using _hyper_15_183_chunk_t_time_idx on _hyper_15_183_chunk t1_2\n               Index Cond: (\"time\" < 'Fri Jan 01 00:00:00 2010 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Merge Append\n               Sort Key: t2.\"time\"\n               ->  Index Only Scan Backward using _hyper_15_182_chunk_t_time_idx on _hyper_15_182_chunk t2_1\n               ->  Index Only Scan Backward using _hyper_15_183_chunk_t_time_idx on _hyper_15_183_chunk t2_2\n               ->  Index Only Scan Backward using _hyper_15_184_chunk_t_time_idx on _hyper_15_184_chunk t2_3\n\nRESET timescaledb.enable_qual_propagation;\nCREATE TABLE test (a int, time timestamptz NOT NULL);\nSELECT table_name FROM create_hypertable('public.test', 'time');\n table_name \n------------\n test\n\nINSERT INTO test SELECT i, '2020-04-01'::date-10-i from generate_series(1,20) i;\nCREATE OR REPLACE FUNCTION test_f(_ts timestamptz)\nRETURNS SETOF test LANGUAGE SQL STABLE PARALLEL SAFE\nAS $f$\n   SELECT DISTINCT ON (a) * FROM test WHERE time >= _ts ORDER BY a, time DESC\n$f$;\nEXPLAIN (buffers off, costs off) SELECT * FROM test_f(now());\n--- QUERY PLAN ---\n Unique\n   ->  Sort\n         Sort Key: test.a, test.\"time\" DESC\n         ->  Custom Scan (ChunkAppend) on test\n               Chunks excluded during startup: 4\n\nEXPLAIN (buffers off, costs off) SELECT * FROM test_f(now());\n--- QUERY PLAN ---\n Unique\n   ->  Sort\n         Sort Key: test.a, test.\"time\" DESC\n         ->  Custom Scan (ChunkAppend) on test\n               Chunks excluded during startup: 4\n\nCREATE TABLE t1 (a int, b int NOT NULL);\nSELECT create_hypertable('t1', 'b', chunk_time_interval=>10);\n create_hypertable \n-------------------\n (17,public,t1,t)\n\nCREATE TABLE t2 (a int, b int NOT NULL);\nSELECT create_hypertable('t2', 'b', chunk_time_interval=>10);\n create_hypertable \n-------------------\n (18,public,t2,t)\n\nCREATE OR REPLACE FUNCTION f_t1(_a int, _b int)\n RETURNS SETOF t1\n LANGUAGE SQL\n STABLE PARALLEL SAFE\nAS $function$\n   SELECT DISTINCT ON (a) * FROM t1 WHERE a = _a and b = _b ORDER BY a, b DESC\n$function$\n;\nCREATE OR REPLACE FUNCTION f_t2(_a int, _b int) RETURNS SETOF t2 LANGUAGE sql STABLE PARALLEL SAFE\nAS $function$\n   SELECT DISTINCT ON (j.a) j.*\n   FROM\n      f_t1(_a, _b) sc,\n      t2 j\n   WHERE\n      j.b = _b AND\n      j.a = _a\n   ORDER BY j.a, j.b DESC\n$function$\n;\nCREATE OR REPLACE FUNCTION f_t1_2(_b int) RETURNS SETOF t1 LANGUAGE SQL STABLE PARALLEL SAFE\nAS $function$\n   SELECT DISTINCT ON (j.a) jt.* FROM t1 j, f_t1(j.a, _b) jt\n$function$;\nEXPLAIN (buffers off, costs off) SELECT * FROM f_t1_2(10);\n--- QUERY PLAN ---\n Subquery Scan on f_t1_2\n   ->  Unique\n         ->  Sort\n               Sort Key: j.a\n               ->  Nested Loop\n                     ->  Seq Scan on t1 j\n                     ->  Limit\n                           ->  Index Scan using t1_b_idx on t1\n                                 Index Cond: (b = 10)\n                                 Filter: (a = j.a)\n\nEXPLAIN (buffers off, costs off) SELECT * FROM f_t1_2(10) sc, f_t2(sc.a, 10);\n--- QUERY PLAN ---\n Nested Loop\n   ->  Unique\n         ->  Sort\n               Sort Key: j.a\n               ->  Nested Loop\n                     ->  Seq Scan on t1 j\n                     ->  Limit\n                           ->  Index Scan using t1_b_idx on t1\n                                 Index Cond: (b = 10)\n                                 Filter: (a = j.a)\n   ->  Limit\n         ->  Nested Loop\n               ->  Limit\n                     ->  Index Scan using t1_b_idx on t1 t1_1\n                           Index Cond: (b = 10)\n                           Filter: (a = t1.a)\n               ->  Index Scan using t2_b_idx on t2 j_1\n                     Index Cond: (b = 10)\n                     Filter: (a = t1.a)\n\nCREATE TABLE metrics_int1(time int, device text, value float) WITH (tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval=1);\nINSERT INTO metrics_int1 SELECT i, i::text, i FROM generate_series(3,7) i;\nSELECT tableoid::regclass, time FROM metrics_int1 ORDER BY time;\n                 tableoid                  | time \n-------------------------------------------+------\n _timescaledb_internal._hyper_19_189_chunk |    3\n _timescaledb_internal._hyper_19_190_chunk |    4\n _timescaledb_internal._hyper_19_191_chunk |    5\n _timescaledb_internal._hyper_19_192_chunk |    6\n _timescaledb_internal._hyper_19_193_chunk |    7\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time >= 2;\n--- QUERY PLAN ---\n Append (actual rows=5.00 loops=1)\n   ->  Seq Scan on _hyper_19_189_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_190_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_192_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_193_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time >= 1 AND time >= 2;\n--- QUERY PLAN ---\n Append (actual rows=5.00 loops=1)\n   ->  Seq Scan on _hyper_19_189_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_190_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_192_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_193_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time >= 4;\n--- QUERY PLAN ---\n Append (actual rows=4.00 loops=1)\n   ->  Seq Scan on _hyper_19_190_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_192_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_193_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time > 4 AND time < 6;\n--- QUERY PLAN ---\n Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time > 4 AND time <= 6;\n--- QUERY PLAN ---\n Append (actual rows=2.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_192_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time >= 4 AND time < 6;\n--- QUERY PLAN ---\n Append (actual rows=2.00 loops=1)\n   ->  Seq Scan on _hyper_19_190_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time >= 4 AND time <= 6;\n--- QUERY PLAN ---\n Append (actual rows=3.00 loops=1)\n   ->  Seq Scan on _hyper_19_190_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_192_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time BETWEEN 4 AND 5;\n--- QUERY PLAN ---\n Append (actual rows=2.00 loops=1)\n   ->  Seq Scan on _hyper_19_190_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time = 5;\n--- QUERY PLAN ---\n Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n\nSET TIMEZONE='UTC';\nCREATE TABLE metrics_tstz(time timestamptz, device text, value float) WITH (tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval='1day');\nINSERT INTO metrics_tstz SELECT '2000-01-01'::timestamptz + format('%s day',i)::interval, i::text, i FROM generate_series(2,6) i;\nSELECT tableoid::regclass, time FROM metrics_tstz ORDER BY time;\n                 tableoid                  |             time             \n-------------------------------------------+------------------------------\n _timescaledb_internal._hyper_20_194_chunk | Mon Jan 03 00:00:00 2000 UTC\n _timescaledb_internal._hyper_20_195_chunk | Tue Jan 04 00:00:00 2000 UTC\n _timescaledb_internal._hyper_20_196_chunk | Wed Jan 05 00:00:00 2000 UTC\n _timescaledb_internal._hyper_20_197_chunk | Thu Jan 06 00:00:00 2000 UTC\n _timescaledb_internal._hyper_20_198_chunk | Fri Jan 07 00:00:00 2000 UTC\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-02';\n--- QUERY PLAN ---\n Append (actual rows=5.00 loops=1)\n   ->  Seq Scan on _hyper_20_194_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_198_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-01' AND time >= '2000-01-02';\n--- QUERY PLAN ---\n Append (actual rows=5.00 loops=1)\n   ->  Seq Scan on _hyper_20_194_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_198_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-04';\n--- QUERY PLAN ---\n Append (actual rows=4.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_198_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time > '2000-01-04' AND time < '2000-01-06';\n--- QUERY PLAN ---\n Append (actual rows=1.00 loops=1)\n   ->  Index Scan using _hyper_20_195_chunk_metrics_tstz_time_idx on _hyper_20_195_chunk (actual rows=0.00 loops=1)\n         Index Cond: ((\"time\" > 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" < 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone))\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time > '2000-01-04' AND time <= '2000-01-06';\n--- QUERY PLAN ---\n Append (actual rows=2.00 loops=1)\n   ->  Index Scan using _hyper_20_195_chunk_metrics_tstz_time_idx on _hyper_20_195_chunk (actual rows=0.00 loops=1)\n         Index Cond: ((\"time\" > 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone))\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n   ->  Index Scan using _hyper_20_197_chunk_metrics_tstz_time_idx on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n         Index Cond: ((\"time\" > 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone))\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-04' AND time < '2000-01-06';\n--- QUERY PLAN ---\n Append (actual rows=2.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-04' AND time <= '2000-01-06';\n--- QUERY PLAN ---\n Append (actual rows=3.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n   ->  Index Scan using _hyper_20_197_chunk_metrics_tstz_time_idx on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n         Index Cond: ((\"time\" >= 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone))\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time BETWEEN '2000-01-04' AND '2000-01-05';\n--- QUERY PLAN ---\n Append (actual rows=2.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n   ->  Index Scan using _hyper_20_196_chunk_metrics_tstz_time_idx on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n         Index Cond: ((\"time\" >= 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Wed Jan 05 00:00:00 2000 UTC'::timestamp with time zone))\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time = '2000-01-05';\n--- QUERY PLAN ---\n Index Scan using _hyper_20_196_chunk_metrics_tstz_time_idx on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n   Index Cond: (\"time\" = 'Wed Jan 05 00:00:00 2000 UTC'::timestamp with time zone)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-04' AND time <= '2000-01-06' AND device = '5';\n--- QUERY PLAN ---\n Append (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=0.00 loops=1)\n         Filter: (device = '5'::text)\n         Rows Removed by Filter: 1\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=0.00 loops=1)\n         Filter: (device = '5'::text)\n         Rows Removed by Filter: 1\n   ->  Index Scan using _hyper_20_197_chunk_metrics_tstz_time_idx on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n         Index Cond: ((\"time\" >= 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone))\n         Filter: (device = '5'::text)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-04' AND time <= '2000-01-06' AND device IS NOT NULL;\n--- QUERY PLAN ---\n Append (actual rows=3.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n         Filter: (device IS NOT NULL)\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n         Filter: (device IS NOT NULL)\n   ->  Index Scan using _hyper_20_197_chunk_metrics_tstz_time_idx on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n         Index Cond: ((\"time\" >= 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone))\n         Filter: (device IS NOT NULL)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-04' AND time <= '2000-01-06' AND time < now();\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_tstz (actual rows=3.00 loops=1)\n   Chunks excluded during startup: 0\n   ->  Index Scan using _hyper_20_195_chunk_metrics_tstz_time_idx on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" < now())\n   ->  Index Scan using _hyper_20_196_chunk_metrics_tstz_time_idx on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" < now())\n   ->  Index Scan using _hyper_20_197_chunk_metrics_tstz_time_idx on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n         Index Cond: ((\"time\" >= 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" < now()))\n\nCREATE TABLE metrics_space(time timestamptz, device text, value float) WITH (tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval='1day');\nSELECT add_dimension('metrics_space', 'device', 4);\n           add_dimension            \n------------------------------------\n (25,public,metrics_space,device,t)\n\nINSERT INTO metrics_space SELECT '2000-01-01'::timestamptz + format('%s day',i)::interval, i::text, i FROM generate_series(2,6) i;\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_space WHERE time >= '2000-01-02';\n--- QUERY PLAN ---\n Append (actual rows=5.00 loops=1)\n   ->  Index Scan using _hyper_21_199_chunk_metrics_space_time_idx on _hyper_21_199_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" >= 'Sun Jan 02 00:00:00 2000 UTC'::timestamp with time zone)\n   ->  Index Scan using _hyper_21_200_chunk_metrics_space_time_idx on _hyper_21_200_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" >= 'Sun Jan 02 00:00:00 2000 UTC'::timestamp with time zone)\n   ->  Index Scan using _hyper_21_201_chunk_metrics_space_time_idx on _hyper_21_201_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" >= 'Sun Jan 02 00:00:00 2000 UTC'::timestamp with time zone)\n   ->  Index Scan using _hyper_21_202_chunk_metrics_space_time_idx on _hyper_21_202_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" >= 'Sun Jan 02 00:00:00 2000 UTC'::timestamp with time zone)\n   ->  Index Scan using _hyper_21_203_chunk_metrics_space_time_idx on _hyper_21_203_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" >= 'Sun Jan 02 00:00:00 2000 UTC'::timestamp with time zone)\n\n--TEST END--\n"
  },
  {
    "path": "test/expected/plan_expand_hypertable-17.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\set PREFIX 'EXPLAIN (buffers off, costs off) '\n\\ir include/plan_expand_hypertable_load.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n--single time dimension\nCREATE TABLE hyper (\"time_broken\" bigint NOT NULL, \"value\" integer);\nALTER TABLE hyper\nDROP COLUMN time_broken,\nADD COLUMN time BIGINT;\nSELECT create_hypertable('hyper', 'time',  chunk_time_interval => 10);\n create_hypertable  \n--------------------\n (1,public,hyper,t)\n\nINSERT INTO hyper SELECT g, g FROM generate_series(0,1000) g;\n--insert a point with INT_MAX_64\nINSERT INTO hyper (time, value) SELECT 9223372036854775807::bigint, 0;\n--time and space\nCREATE TABLE hyper_w_space (\"time_broken\" bigint NOT NULL, \"device_id\" text, \"value\" integer);\nALTER TABLE hyper_w_space\nDROP COLUMN time_broken,\nADD COLUMN time BIGINT;\nSELECT create_hypertable('hyper_w_space', 'time', 'device_id', 4, chunk_time_interval => 10);\n     create_hypertable      \n----------------------------\n (2,public,hyper_w_space,t)\n\nINSERT INTO hyper_w_space (time, device_id, value) SELECT g, 'dev' || g, g FROM generate_series(0,30) g;\nCREATE VIEW hyper_w_space_view AS (SELECT * FROM hyper_w_space);\n--with timestamp and space\nCREATE TABLE tag (id serial PRIMARY KEY, name text);\nCREATE TABLE hyper_ts (\"time_broken\" timestamptz NOT NULL, \"device_id\" text, tag_id INT REFERENCES tag(id), \"value\" integer);\nALTER TABLE hyper_ts\nDROP COLUMN time_broken,\nADD COLUMN time TIMESTAMPTZ;\nSELECT create_hypertable('hyper_ts', 'time', 'device_id', 2, chunk_time_interval => '10 seconds'::interval);\n   create_hypertable   \n-----------------------\n (3,public,hyper_ts,t)\n\nINSERT INTO tag(name) SELECT 'tag'||g FROM generate_series(0,10) g;\nINSERT INTO hyper_ts (time, device_id, tag_id, value) SELECT to_timestamp(g), 'dev' || g, (random() /10)+1, g FROM generate_series(0,30) g;\n--one in the future\nINSERT INTO hyper_ts (time, device_id, tag_id, value)  VALUES ('2100-01-01 02:03:04 PST', 'dev101', 1, 0);\n--time partitioning function\nCREATE OR REPLACE FUNCTION unix_to_timestamp(unixtime float8)\n    RETURNS TIMESTAMPTZ LANGUAGE SQL IMMUTABLE PARALLEL SAFE STRICT AS\n$BODY$\n    SELECT to_timestamp(unixtime);\n$BODY$;\nCREATE TABLE hyper_timefunc (\"time\" float8 NOT NULL, \"device_id\" text, \"value\" integer);\nSELECT create_hypertable('hyper_timefunc', 'time', 'device_id', 4, chunk_time_interval => 10, time_partitioning_func => 'unix_to_timestamp');\npsql:include/plan_expand_hypertable_load.sql:57: WARNING:  unexpected interval: smaller than one second\n      create_hypertable      \n-----------------------------\n (4,public,hyper_timefunc,t)\n\nINSERT INTO hyper_timefunc (time, device_id, value) SELECT g, 'dev' || g, g FROM generate_series(0,30) g;\nCREATE TABLE metrics_timestamp(time timestamp);\nSELECT create_hypertable('metrics_timestamp','time');\npsql:include/plan_expand_hypertable_load.sql:62: WARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n       create_hypertable        \n--------------------------------\n (5,public,metrics_timestamp,t)\n\nINSERT INTO metrics_timestamp SELECT generate_series('2000-01-01'::timestamp,'2000-02-01'::timestamp,'1d'::interval);\nCREATE TABLE metrics_timestamptz(time timestamptz, device_id int);\nSELECT create_hypertable('metrics_timestamptz','time');\n        create_hypertable         \n----------------------------------\n (6,public,metrics_timestamptz,t)\n\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval), 1;\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval), 2;\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval), 3;\n--create a second table to test joins with\nCREATE TABLE metrics_timestamptz_2 (LIKE metrics_timestamptz);\nSELECT create_hypertable('metrics_timestamptz_2','time');\n         create_hypertable          \n------------------------------------\n (7,public,metrics_timestamptz_2,t)\n\nINSERT INTO metrics_timestamptz_2\nSELECT * FROM metrics_timestamptz;\nINSERT INTO metrics_timestamptz_2 VALUES ('2000-12-01'::timestamptz, 3);\nCREATE TABLE metrics_date(time date);\nSELECT create_hypertable('metrics_date','time');\n     create_hypertable     \n---------------------------\n (8,public,metrics_date,t)\n\nINSERT INTO metrics_date SELECT generate_series('2000-01-01'::date,'2000-02-01'::date,'1d'::interval);\nANALYZE hyper;\nANALYZE hyper_w_space;\nANALYZE tag;\nANALYZE hyper_ts;\nANALYZE hyper_timefunc;\n-- create normal table for JOIN tests\nCREATE TABLE regular_timestamptz(time timestamptz);\nINSERT INTO regular_timestamptz SELECT generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval);\n\\ir include/plan_expand_hypertable_query.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n--we want to see how our logic excludes chunks\n--and not how much work constraint_exclusion does\nSET constraint_exclusion = 'off';\n\\qecho test upper bounds\ntest upper bounds\n:PREFIX SELECT * FROM hyper WHERE time < 10 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_1_chunk.value\n   ->  Seq Scan on _hyper_1_1_chunk\n\n:PREFIX SELECT * FROM hyper WHERE time < 11 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (\"time\" < 11)\n\n:PREFIX SELECT * FROM hyper WHERE time = 10 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_2_chunk.value\n   ->  Seq Scan on _hyper_1_2_chunk\n         Filter: (\"time\" = 10)\n\n:PREFIX SELECT * FROM hyper WHERE 10 >= time ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (10 >= \"time\")\n\n\\qecho test lower bounds\ntest lower bounds\n:PREFIX SELECT * FROM hyper WHERE time >= 10 and time < 20 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_2_chunk.value\n   ->  Seq Scan on _hyper_1_2_chunk\n\n:PREFIX SELECT * FROM hyper WHERE 10 < time and 20 >= time ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: ((10 < \"time\") AND (20 >= \"time\"))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((10 < \"time\") AND (20 >= \"time\"))\n\n:PREFIX SELECT * FROM hyper WHERE time >= 9 and time < 20 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: ((\"time\" >= 9) AND (\"time\" < 20))\n         ->  Seq Scan on _hyper_1_2_chunk\n\n:PREFIX SELECT * FROM hyper WHERE time > 9 and time < 20 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_2_chunk.value\n   ->  Seq Scan on _hyper_1_2_chunk\n\n\\qecho test empty result\ntest empty result\n:PREFIX SELECT * FROM hyper WHERE time < 0;\n--- QUERY PLAN ---\n Result\n   One-Time Filter: false\n\n\\qecho test expression evaluation\ntest expression evaluation\n:PREFIX SELECT * FROM hyper WHERE time < (5*2)::smallint;\n--- QUERY PLAN ---\n Seq Scan on _hyper_1_1_chunk\n\n\\qecho test logic at INT64_MAX\ntest logic at INT64_MAX\n:PREFIX SELECT * FROM hyper WHERE time = 9223372036854775807::bigint ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_102_chunk.value\n   ->  Seq Scan on _hyper_1_102_chunk\n         Filter: (\"time\" = '9223372036854775807'::bigint)\n\n:PREFIX SELECT * FROM hyper WHERE time = 9223372036854775806::bigint ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_102_chunk.value\n   ->  Seq Scan on _hyper_1_102_chunk\n         Filter: (\"time\" = '9223372036854775806'::bigint)\n\n:PREFIX SELECT * FROM hyper WHERE time >= 9223372036854775807::bigint ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_102_chunk.value\n   ->  Seq Scan on _hyper_1_102_chunk\n         Filter: (\"time\" >= '9223372036854775807'::bigint)\n\n:PREFIX SELECT * FROM hyper WHERE time > 9223372036854775807::bigint ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: value\n   ->  Result\n         One-Time Filter: false\n\n:PREFIX SELECT * FROM hyper WHERE time > 9223372036854775806::bigint ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_102_chunk.value\n   ->  Seq Scan on _hyper_1_102_chunk\n         Filter: (\"time\" > '9223372036854775806'::bigint)\n\n\\qecho cte\ncte\n:PREFIX WITH cte AS(\n  SELECT * FROM hyper WHERE time < 10\n)\nSELECT * FROM cte ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_1_chunk.value\n   ->  Seq Scan on _hyper_1_1_chunk\n\n\\qecho subquery\nsubquery\n:PREFIX SELECT 0 = ANY (SELECT value FROM hyper WHERE time < 10);\n--- QUERY PLAN ---\n Result\n   SubPlan 1\n     ->  Seq Scan on _hyper_1_1_chunk\n\n\\qecho no space constraint\nno space constraint\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (\"time\" < 10)\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (\"time\" < 10)\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (\"time\" < 10)\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (\"time\" < 10)\n\n\\qecho valid space constraint\nvalid space constraint\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 and device_id = 'dev5' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND (device_id = 'dev5'::text))\n\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 and 'dev5' = device_id ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND ('dev5'::text = device_id))\n\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 and 'dev'||(2+3) = device_id ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND ('dev5'::text = device_id))\n\n\\qecho only space constraint\nonly space constraint\n:PREFIX SELECT * FROM hyper_w_space WHERE 'dev5' = device_id ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ('dev5'::text = device_id)\n         ->  Seq Scan on _hyper_2_109_chunk\n               Filter: ('dev5'::text = device_id)\n         ->  Seq Scan on _hyper_2_111_chunk\n               Filter: ('dev5'::text = device_id)\n\n\\qecho unhandled space constraint\nunhandled space constraint\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 and device_id > 'dev5' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: ((\"time\" < 10) AND (device_id > 'dev5'::text))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: ((\"time\" < 10) AND (device_id > 'dev5'::text))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: ((\"time\" < 10) AND (device_id > 'dev5'::text))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ((\"time\" < 10) AND (device_id > 'dev5'::text))\n\n\\qecho use of OR - does not filter chunks\nuse of OR - does not filter chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND (device_id = 'dev5' or device_id = 'dev6') ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: ((\"time\" < 10) AND ((device_id = 'dev5'::text) OR (device_id = 'dev6'::text)))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: ((\"time\" < 10) AND ((device_id = 'dev5'::text) OR (device_id = 'dev6'::text)))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: ((\"time\" < 10) AND ((device_id = 'dev5'::text) OR (device_id = 'dev6'::text)))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ((\"time\" < 10) AND ((device_id = 'dev5'::text) OR (device_id = 'dev6'::text)))\n\n\\qecho cte\ncte\n:PREFIX WITH cte AS(\n   SELECT * FROM hyper_w_space WHERE time < 10 and device_id = 'dev5'\n)\nSELECT * FROM cte ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND (device_id = 'dev5'::text))\n\n\\qecho subquery\nsubquery\n:PREFIX SELECT 0 = ANY (SELECT value FROM hyper_w_space WHERE time < 10 and device_id = 'dev5');\n--- QUERY PLAN ---\n Result\n   SubPlan 1\n     ->  Seq Scan on _hyper_2_106_chunk\n           Filter: ((\"time\" < 10) AND (device_id = 'dev5'::text))\n\n\\qecho view\nview\n:PREFIX SELECT * FROM hyper_w_space_view WHERE time < 10 and device_id = 'dev5' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND (device_id = 'dev5'::text))\n\n\\qecho IN statement - simple\nIN statement - simple\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id IN ('dev5') ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND (device_id = 'dev5'::text))\n\n\\qecho IN statement - two chunks\nIN statement - two chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id IN ('dev5','dev6') ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: ((\"time\" < 10) AND (device_id = ANY ('{dev5,dev6}'::text[])))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ((\"time\" < 10) AND (device_id = ANY ('{dev5,dev6}'::text[])))\n\n\\qecho IN statement - one chunk\nIN statement - one chunk\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id IN ('dev4','dev5') ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND (device_id = ANY ('{dev4,dev5}'::text[])))\n\n\\qecho NOT IN - does not filter chunks\nNOT IN - does not filter chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id NOT IN ('dev5','dev6') ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: ((\"time\" < 10) AND (device_id <> ALL ('{dev5,dev6}'::text[])))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: ((\"time\" < 10) AND (device_id <> ALL ('{dev5,dev6}'::text[])))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: ((\"time\" < 10) AND (device_id <> ALL ('{dev5,dev6}'::text[])))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ((\"time\" < 10) AND (device_id <> ALL ('{dev5,dev6}'::text[])))\n\n\\qecho IN statement with subquery - does not filter chunks\nIN statement with subquery - does not filter chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id IN (SELECT 'dev5'::text) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND (device_id = 'dev5'::text))\n\n\\qecho ANY\nANY\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id = ANY(ARRAY['dev5','dev6']) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: ((\"time\" < 10) AND (device_id = ANY ('{dev5,dev6}'::text[])))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ((\"time\" < 10) AND (device_id = ANY ('{dev5,dev6}'::text[])))\n\n\\qecho ANY with intersection\nANY with intersection\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id = ANY(ARRAY['dev5','dev6']) AND device_id = ANY(ARRAY['dev6','dev7']) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_105_chunk.value\n   ->  Seq Scan on _hyper_2_105_chunk\n         Filter: ((\"time\" < 10) AND (device_id = ANY ('{dev5,dev6}'::text[])) AND (device_id = ANY ('{dev6,dev7}'::text[])))\n\n\\qecho ANY without intersection shouldnt scan any chunks\nANY without intersection shouldnt scan any chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id = ANY(ARRAY['dev5','dev6']) AND device_id = ANY(ARRAY['dev8','dev9']) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: value\n   ->  Result\n         One-Time Filter: false\n\n\\qecho ANY/IN/ALL only works for equals operator\nANY/IN/ALL only works for equals operator\n:PREFIX SELECT * FROM hyper_w_space WHERE device_id < ANY(ARRAY['dev5','dev6']) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_107_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_108_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_109_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_110_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_111_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_112_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_113_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_114_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_115_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n\n\\qecho ALL with equals and different values shouldnt scan any chunks\nALL with equals and different values shouldnt scan any chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE device_id = ALL(ARRAY['dev5','dev6']) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: value\n   ->  Result\n         One-Time Filter: false\n\n\\qecho Multi AND\nMulti AND\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND time < 100 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: ((\"time\" < 10) AND (\"time\" < 100))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: ((\"time\" < 10) AND (\"time\" < 100))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: ((\"time\" < 10) AND (\"time\" < 100))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ((\"time\" < 10) AND (\"time\" < 100))\n\n\\qecho Time dimension doesnt filter chunks when using non-equality IN/ANY with multiple arguments\nTime dimension doesnt filter chunks when using non-equality IN/ANY with multiple arguments\n:PREFIX SELECT * FROM hyper_w_space WHERE time < ANY(ARRAY[1,2]) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_107_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_108_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_109_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_110_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_111_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_112_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_113_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_114_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_115_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n\n\\qecho Time dimension chunk exclusion with IN/ANY equality uses bounding range\nTime dimension chunk exclusion with IN/ANY equality uses bounding range\n:PREFIX SELECT * FROM hyper WHERE time IN (5, 15) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n\n:PREFIX SELECT * FROM hyper WHERE time = ANY(ARRAY[5, 15, 25]) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (\"time\" = ANY ('{5,15,25}'::integer[]))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (\"time\" = ANY ('{5,15,25}'::integer[]))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: (\"time\" = ANY ('{5,15,25}'::integer[]))\n\n:PREFIX SELECT * FROM hyper WHERE time = ANY(ARRAY[25, 15, 5]) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (\"time\" = ANY ('{25,15,5}'::integer[]))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (\"time\" = ANY ('{25,15,5}'::integer[]))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: (\"time\" = ANY ('{25,15,5}'::integer[]))\n\n:PREFIX SELECT * FROM hyper_w_space WHERE time IN (5, 15) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_107_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_108_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_109_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_110_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time IN ('2000-01-05'::timestamp, '2000-01-15'::timestamp) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Index Cond: (\"time\" = ANY ('{\"Wed Jan 05 00:00:00 2000\",\"Sat Jan 15 00:00:00 2000\"}'::timestamp without time zone[]))\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" = ANY ('{\"Wed Jan 05 00:00:00 2000\",\"Sat Jan 15 00:00:00 2000\"}'::timestamp without time zone[]))\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Index Cond: (\"time\" = ANY ('{\"Wed Jan 05 00:00:00 2000\",\"Sat Jan 15 00:00:00 2000\"}'::timestamp without time zone[]))\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time IN ('2000-01-05'::timestamptz, '2000-01-15'::timestamptz) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Index Cond: (\"time\" = ANY ('{\"Wed Jan 05 00:00:00 2000 PST\",\"Sat Jan 15 00:00:00 2000 PST\"}'::timestamp with time zone[]))\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" = ANY ('{\"Wed Jan 05 00:00:00 2000 PST\",\"Sat Jan 15 00:00:00 2000 PST\"}'::timestamp with time zone[]))\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Index Cond: (\"time\" = ANY ('{\"Wed Jan 05 00:00:00 2000 PST\",\"Sat Jan 15 00:00:00 2000 PST\"}'::timestamp with time zone[]))\n\n:PREFIX SELECT * FROM metrics_date WHERE time IN ('2000-01-05'::date, '2000-01-15'::date) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date\n   Order: metrics_date.\"time\"\n   ->  Index Only Scan Backward using _hyper_8_171_chunk_metrics_date_time_idx on _hyper_8_171_chunk\n         Index Cond: (\"time\" = ANY ('{01-05-2000,01-15-2000}'::date[]))\n   ->  Index Only Scan Backward using _hyper_8_172_chunk_metrics_date_time_idx on _hyper_8_172_chunk\n         Index Cond: (\"time\" = ANY ('{01-05-2000,01-15-2000}'::date[]))\n   ->  Index Only Scan Backward using _hyper_8_173_chunk_metrics_date_time_idx on _hyper_8_173_chunk\n         Index Cond: (\"time\" = ANY ('{01-05-2000,01-15-2000}'::date[]))\n\n\\qecho cross-type IN/ANY: timestamp to timestamptz column does not use bounding range (stable cast)\ncross-type IN/ANY: timestamp to timestamptz column does not use bounding range (stable cast)\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time IN ('2000-01-05'::timestamp, '2000-01-15'::timestamp) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 3\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Index Cond: (\"time\" = ANY (ARRAY[('Wed Jan 05 00:00:00 2000'::timestamp without time zone)::timestamp with time zone, ('Sat Jan 15 00:00:00 2000'::timestamp without time zone)::timestamp with time zone]))\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Index Cond: (\"time\" = ANY (ARRAY[('Wed Jan 05 00:00:00 2000'::timestamp without time zone)::timestamp with time zone, ('Sat Jan 15 00:00:00 2000'::timestamp without time zone)::timestamp with time zone]))\n\n\\qecho Time dimension chunk filtering works for ANY with single argument\nTime dimension chunk filtering works for ANY with single argument\n:PREFIX SELECT * FROM hyper_w_space WHERE time < ANY(ARRAY[1]) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (\"time\" < ANY ('{1}'::integer[]))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (\"time\" < ANY ('{1}'::integer[]))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (\"time\" < ANY ('{1}'::integer[]))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (\"time\" < ANY ('{1}'::integer[]))\n\n\\qecho Time dimension chunk filtering works for ALL with single argument\nTime dimension chunk filtering works for ALL with single argument\n:PREFIX SELECT * FROM hyper_w_space WHERE time < ALL(ARRAY[1]) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (\"time\" < ALL ('{1}'::integer[]))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (\"time\" < ALL ('{1}'::integer[]))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (\"time\" < ALL ('{1}'::integer[]))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (\"time\" < ALL ('{1}'::integer[]))\n\n\\qecho Time dimension chunk filtering works for ALL with multiple arguments\nTime dimension chunk filtering works for ALL with multiple arguments\n:PREFIX SELECT * FROM hyper_w_space WHERE time < ALL(ARRAY[1,10,20,30]) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (\"time\" < ALL ('{1,10,20,30}'::integer[]))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (\"time\" < ALL ('{1,10,20,30}'::integer[]))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (\"time\" < ALL ('{1,10,20,30}'::integer[]))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (\"time\" < ALL ('{1,10,20,30}'::integer[]))\n\n\\qecho AND intersection using IN and EQUALS\nAND intersection using IN and EQUALS\n:PREFIX SELECT * FROM hyper_w_space WHERE device_id IN ('dev1','dev2') AND device_id = 'dev1' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: ((device_id = ANY ('{dev1,dev2}'::text[])) AND (device_id = 'dev1'::text))\n         ->  Seq Scan on _hyper_2_110_chunk\n               Filter: ((device_id = ANY ('{dev1,dev2}'::text[])) AND (device_id = 'dev1'::text))\n         ->  Seq Scan on _hyper_2_114_chunk\n               Filter: ((device_id = ANY ('{dev1,dev2}'::text[])) AND (device_id = 'dev1'::text))\n\n\\qecho AND with no intersection using IN and EQUALS\nAND with no intersection using IN and EQUALS\n:PREFIX SELECT * FROM hyper_w_space WHERE device_id IN ('dev1','dev2') AND device_id = 'dev3' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: value\n   ->  Result\n         One-Time Filter: false\n\n\\qecho timestamps\ntimestamps\n\\qecho these should work since they are immutable functions\nthese should work since they are immutable functions\n:PREFIX SELECT * FROM hyper_ts WHERE time < 'Wed Dec 31 16:00:10 1969 PST'::timestamptz ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Append\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_3_117_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM hyper_ts WHERE time < to_timestamp(10) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Append\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_3_117_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM hyper_ts WHERE time < 'Wed Dec 31 16:00:10 1969'::timestamp AT TIME ZONE 'PST' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Append\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_3_117_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM hyper_ts WHERE time < to_timestamp(10) and device_id = 'dev1' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_3_116_chunk.value\n   ->  Seq Scan on _hyper_3_116_chunk\n         Filter: ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text))\n\n\\qecho these should not work since uses stable functions;\nthese should not work since uses stable functions;\n:PREFIX SELECT * FROM hyper_ts WHERE time < 'Wed Dec 31 16:00:10 1969'::timestamp ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Custom Scan (ChunkAppend) on hyper_ts\n         Chunks excluded during startup: 6\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969'::timestamp without time zone)\n         ->  Seq Scan on _hyper_3_117_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969'::timestamp without time zone)\n\n:PREFIX SELECT * FROM hyper_ts WHERE time < ('Wed Dec 31 16:00:10 1969'::timestamp::timestamptz) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Custom Scan (ChunkAppend) on hyper_ts\n         Chunks excluded during startup: 6\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: (\"time\" < ('Wed Dec 31 16:00:10 1969'::timestamp without time zone)::timestamp with time zone)\n         ->  Seq Scan on _hyper_3_117_chunk\n               Filter: (\"time\" < ('Wed Dec 31 16:00:10 1969'::timestamp without time zone)::timestamp with time zone)\n\n:PREFIX SELECT * FROM hyper_ts WHERE NOW() < time ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Custom Scan (ChunkAppend) on hyper_ts\n         Chunks excluded during startup: 7\n         ->  Seq Scan on _hyper_3_123_chunk\n               Filter: (now() < \"time\")\n\n\\qecho joins\njoins\n:PREFIX SELECT * FROM hyper_ts WHERE tag_id IN (SELECT id FROM tag WHERE tag.id=1) and time < to_timestamp(10) and device_id = 'dev1' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_3_116_chunk.value\n   ->  Nested Loop Semi Join\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text) AND (tag_id = 1))\n         ->  Seq Scan on tag\n               Filter: (id = 1)\n\n:PREFIX SELECT * FROM hyper_ts WHERE tag_id IN (SELECT id FROM tag WHERE tag.id=1) or (time < to_timestamp(10) and device_id = 'dev1') ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Custom Scan (ChunkAppend) on hyper_ts\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: ((ANY (tag_id = (hashed SubPlan 1).col1)) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n               SubPlan 1\n                 ->  Seq Scan on tag\n                       Filter: (id = 1)\n         ->  Seq Scan on _hyper_3_117_chunk\n               Filter: ((ANY (tag_id = (hashed SubPlan 1).col1)) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n         ->  Seq Scan on _hyper_3_118_chunk\n               Filter: ((ANY (tag_id = (hashed SubPlan 1).col1)) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n         ->  Seq Scan on _hyper_3_119_chunk\n               Filter: ((ANY (tag_id = (hashed SubPlan 1).col1)) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n         ->  Seq Scan on _hyper_3_120_chunk\n               Filter: ((ANY (tag_id = (hashed SubPlan 1).col1)) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n         ->  Seq Scan on _hyper_3_121_chunk\n               Filter: ((ANY (tag_id = (hashed SubPlan 1).col1)) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n         ->  Seq Scan on _hyper_3_122_chunk\n               Filter: ((ANY (tag_id = (hashed SubPlan 1).col1)) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n         ->  Seq Scan on _hyper_3_123_chunk\n               Filter: ((ANY (tag_id = (hashed SubPlan 1).col1)) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n\n:PREFIX SELECT * FROM hyper_ts WHERE tag_id IN (SELECT id FROM tag WHERE tag.name='tag1') and time < to_timestamp(10) and device_id = 'dev1' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_3_116_chunk.value\n   ->  Nested Loop\n         Join Filter: (_hyper_3_116_chunk.tag_id = tag.id)\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text))\n         ->  Seq Scan on tag\n               Filter: (name = 'tag1'::text)\n\n:PREFIX SELECT * FROM hyper_ts JOIN tag on (hyper_ts.tag_id = tag.id ) WHERE time < to_timestamp(10) and device_id = 'dev1' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_3_116_chunk.value\n   ->  Merge Join\n         Merge Cond: (tag.id = _hyper_3_116_chunk.tag_id)\n         ->  Index Scan using tag_pkey on tag\n         ->  Sort\n               Sort Key: _hyper_3_116_chunk.tag_id\n               ->  Seq Scan on _hyper_3_116_chunk\n                     Filter: ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text))\n\n:PREFIX SELECT * FROM hyper_ts JOIN tag on (hyper_ts.tag_id = tag.id ) WHERE tag.name = 'tag1' and time < to_timestamp(10) and device_id = 'dev1' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_3_116_chunk.value\n   ->  Nested Loop\n         Join Filter: (tag.id = _hyper_3_116_chunk.tag_id)\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text))\n         ->  Seq Scan on tag\n               Filter: (name = 'tag1'::text)\n\n\\qecho test constraint exclusion for constraints in ON clause of JOINs\ntest constraint exclusion for constraints in ON clause of JOINs\n\\qecho should exclude chunks on m1 and propagate qual to m2 because of INNER JOIN\nshould exclude chunks on m1 and propagate qual to m2 because of INNER JOIN\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho should exclude chunks on m2 and propagate qual to m1 because of INNER JOIN\nshould exclude chunks on m2 and propagate qual to m1 because of INNER JOIN\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho must not exclude on m1\nmust not exclude on m1\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Left Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   Join Filter: (m1.\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n\\qecho should exclude chunks on m2\nshould exclude chunks on m2\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Left Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho should exclude chunks on m1\nshould exclude chunks on m1\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: m1.\"time\"\n   ->  Merge Right Join\n         Merge Cond: (m1.\"time\" = m2.\"time\")\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n               Order: m1.\"time\"\n               ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Materialize\n               ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n                     Order: m2.\"time\"\n                     ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n                     ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n                     ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n                     ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n\\qecho must not exclude chunks on m2\nmust not exclude chunks on m2\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time < '2000-01-10' ORDER BY m1.time, m2.time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: m1.\"time\", m2.\"time\"\n   ->  Merge Left Join\n         Merge Cond: (m2.\"time\" = m1.\"time\")\n         Join Filter: (m2.\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n         ->  Materialize\n               ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n                     Order: m1.\"time\"\n                     ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n                     ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n                     ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n                     ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n                     ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n\n\\qecho time_bucket exclusion\ntime_bucket exclusion\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time) < 10::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_1_chunk.\"time\"\n   ->  Seq Scan on _hyper_1_1_chunk\n         Filter: (time_bucket('10'::bigint, \"time\") < '10'::bigint)\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time) < 11::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '11'::bigint)\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '11'::bigint)\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((\"time\" < '21'::bigint) AND (time_bucket('10'::bigint, \"time\") < '11'::bigint))\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time) <= 10::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") <= '10'::bigint)\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") <= '10'::bigint)\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((\"time\" <= '20'::bigint) AND (time_bucket('10'::bigint, \"time\") <= '10'::bigint))\n\n:PREFIX SELECT * FROM hyper WHERE 10::bigint > time_bucket(10, time) ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_1_chunk.\"time\"\n   ->  Seq Scan on _hyper_1_1_chunk\n         Filter: ('10'::bigint > time_bucket('10'::bigint, \"time\"))\n\n:PREFIX SELECT * FROM hyper WHERE 11::bigint > time_bucket(10, time) ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: ('11'::bigint > time_bucket('10'::bigint, \"time\"))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: ('11'::bigint > time_bucket('10'::bigint, \"time\"))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((\"time\" < '21'::bigint) AND ('11'::bigint > time_bucket('10'::bigint, \"time\")))\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time, 5) < 10::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (time_bucket('10'::bigint, \"time\", '5'::bigint) < '10'::bigint)\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (time_bucket('10'::bigint, \"time\", '5'::bigint) < '10'::bigint)\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time, 5) < 11::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (time_bucket('10'::bigint, \"time\", '5'::bigint) < '11'::bigint)\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (time_bucket('10'::bigint, \"time\", '5'::bigint) < '11'::bigint)\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((\"time\" < '21'::bigint) AND (time_bucket('10'::bigint, \"time\", '5'::bigint) < '11'::bigint))\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time, 5) <= 10::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (time_bucket('10'::bigint, \"time\", '5'::bigint) <= '10'::bigint)\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (time_bucket('10'::bigint, \"time\", '5'::bigint) <= '10'::bigint)\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((\"time\" <= '20'::bigint) AND (time_bucket('10'::bigint, \"time\", '5'::bigint) <= '10'::bigint))\n\n:PREFIX SELECT * FROM hyper WHERE 10::bigint > time_bucket(10, time, 5) ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: ('10'::bigint > time_bucket('10'::bigint, \"time\", '5'::bigint))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: ('10'::bigint > time_bucket('10'::bigint, \"time\", '5'::bigint))\n\n:PREFIX SELECT * FROM hyper WHERE 11::bigint > time_bucket(10, time, 5) ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: ('11'::bigint > time_bucket('10'::bigint, \"time\", '5'::bigint))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: ('11'::bigint > time_bucket('10'::bigint, \"time\", '5'::bigint))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((\"time\" < '21'::bigint) AND ('11'::bigint > time_bucket('10'::bigint, \"time\", '5'::bigint)))\n\n\\qecho timestamp time_bucket exclusion\ntimestamp time_bucket exclusion\nSELECT count(DISTINCT tableoid) FROM metrics_timestamp;\n count \n-------\n     5\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time) < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") < 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") < 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time) <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") <= 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") <= 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time) > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time) >= '2000-01-15' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Index Cond: (\"time\" >= 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") >= 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") >= 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") >= 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'3d'::interval) < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) < 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) < 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'3d'::interval) <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) <= 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) <= 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'3d'::interval) > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'3d'::interval) >= '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Index Cond: (\"time\" >= 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) >= 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) >= 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'2000-01-10'::timestamp) < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) < 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) < 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'2000-01-10'::timestamp) <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) <= 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) <= 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'2000-01-10'::timestamp) > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'2000-01-10'::timestamp) >= '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Index Cond: (\"time\" >= 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) >= 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) >= 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n\n\\qecho timestamptz time_bucket exclusion\ntimestamptz time_bucket exclusion\nSELECT count(DISTINCT tableoid) FROM metrics_timestamptz;\n count \n-------\n     5\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time) < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time) <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time) > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time) >= '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'3d'::interval) < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'3d'::interval) <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'3d'::interval) > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'3d'::interval) >= '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'2000-01-10'::timestamptz) < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'2000-01-10'::timestamptz) <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'2000-01-10'::timestamptz) > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'2000-01-10'::timestamptz) >= '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'Europe/Berlin') < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'Europe/Berlin') <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'Europe/Berlin') > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'Europe/Berlin') >= '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho test overflow behaviour of time_bucket exclusion\ntest overflow behaviour of time_bucket exclusion\n:PREFIX SELECT * FROM hyper WHERE time > 950 AND time_bucket(10, time) < '9223372036854775807'::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_96_chunk\n               Filter: ((\"time\" > 950) AND (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint))\n         ->  Seq Scan on _hyper_1_97_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint)\n         ->  Seq Scan on _hyper_1_98_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint)\n         ->  Seq Scan on _hyper_1_99_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint)\n         ->  Seq Scan on _hyper_1_100_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint)\n         ->  Seq Scan on _hyper_1_101_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint)\n         ->  Seq Scan on _hyper_1_102_chunk\n               Filter: ((\"time\" > 950) AND (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint))\n\n\\qecho test timestamp upper boundary\ntest timestamp upper boundary\n\\qecho there should be no transformation if we are out of the supported (TimescaleDB-specific) range\nthere should be no transformation if we are out of the supported (TimescaleDB-specific) range\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('1d',time) < '294276-01-01'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n\n\\qecho transformation would be out of range\ntransformation would be out of range\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('1000d',time) < '294276-01-01'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n\n\\qecho test timestamptz upper boundary\ntest timestamptz upper boundary\n\\qecho there should be no transformation if we are out of the supported (TimescaleDB-specific) range\nthere should be no transformation if we are out of the supported (TimescaleDB-specific) range\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('1d',time) < '294276-01-01'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n\n\\qecho transformation would be out of range\ntransformation would be out of range\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('1000d',time) < '294276-01-01'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n\n\\qecho time_bucket exclusion with run-time constants\ntime_bucket exclusion with run-time constants\n-- These queries have a stable time_bucket expression, because the text::interval conversion is stable.\nPREPARE P1(text    , text) AS SELECT * FROM metrics_timestamptz WHERE time_bucket($1::interval, time) > $2::timestamptz ORDER BY time;\nPREPARE P2(text    , text) AS SELECT * FROM metrics_timestamp   WHERE time_bucket($1::interval, time) > $2::timestamptz ORDER BY time;\nPREPARE P3(text    , text) AS SELECT * FROM metrics_timestamptz WHERE time_bucket($1::interval, time) > $2::timestamp   ORDER BY time;\nPREPARE P4(text    , text) AS SELECT * FROM metrics_timestamp   WHERE time_bucket($1::interval, time) > $2::timestamp   ORDER BY time;\n-- These queries have an immutable time_bucket expression, because the parameter is passed as interval, and no conversion is involved.\nPREPARE P5(interval, text) AS SELECT * FROM metrics_timestamptz WHERE time_bucket($1::interval, time) > $2::timestamptz ORDER BY time;\nPREPARE P6(interval, text) AS SELECT * FROM metrics_timestamp   WHERE time_bucket($1::interval, time) > $2::timestamptz ORDER BY time;\nPREPARE P7(interval, text) AS SELECT * FROM metrics_timestamptz WHERE time_bucket($1::interval, time) > $2::timestamp   ORDER BY time;\nPREPARE P8(interval, text) AS SELECT * FROM metrics_timestamp   WHERE time_bucket($1::interval, time) > $2::timestamp   ORDER BY time;\nSET plan_cache_mode TO 'force_custom_plan';\n:PREFIX EXECUTE P1('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P2('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P3('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P4('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P5('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P6('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P7('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P8('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P1('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n\n:PREFIX EXECUTE P2('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n\n:PREFIX EXECUTE P3('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n\n:PREFIX EXECUTE P4('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n\n:PREFIX EXECUTE P5('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n\n:PREFIX EXECUTE P6('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n\n:PREFIX EXECUTE P7('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n\n:PREFIX EXECUTE P8('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n\nSET plan_cache_mode TO 'force_generic_plan';\n:PREFIX EXECUTE P1('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P2('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P3('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P4('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P5('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P6('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P7('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P8('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P1('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n\n:PREFIX EXECUTE P2('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n\n:PREFIX EXECUTE P3('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n\n:PREFIX EXECUTE P4('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n\n:PREFIX EXECUTE P5('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n\n:PREFIX EXECUTE P6('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n\n:PREFIX EXECUTE P7('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n\n:PREFIX EXECUTE P8('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n\nRESET plan_cache_mode;\nDEALLOCATE P1;\nDEALLOCATE P2;\nDEALLOCATE P3;\nDEALLOCATE P4;\nDEALLOCATE P5;\nDEALLOCATE P6;\nDEALLOCATE P7;\nDEALLOCATE P8;\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time) > 10 AND time_bucket(10, time) < 100 ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: ((\"time\" > '10'::bigint) AND (\"time\" < '100'::bigint) AND (time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_4_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_5_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_6_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_7_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_8_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_9_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_10_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time) > 10 AND time_bucket(10, time) < 20 ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_2_chunk.\"time\"\n   ->  Seq Scan on _hyper_1_2_chunk\n         Filter: ((\"time\" > '10'::bigint) AND (\"time\" < '20'::bigint) AND (time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 20))\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(1, time) > 11 AND time_bucket(1, time) < 19 ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_2_chunk.\"time\"\n   ->  Seq Scan on _hyper_1_2_chunk\n         Filter: ((\"time\" > '11'::bigint) AND (\"time\" < '19'::bigint) AND (time_bucket('1'::bigint, \"time\") > 11) AND (time_bucket('1'::bigint, \"time\") < 19))\n\n:PREFIX SELECT * FROM hyper WHERE 10 < time_bucket(10, time) AND 20 > time_bucket(10,time) ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_2_chunk.\"time\"\n   ->  Seq Scan on _hyper_1_2_chunk\n         Filter: ((\"time\" > '10'::bigint) AND (\"time\" < '20'::bigint) AND (10 < time_bucket('10'::bigint, \"time\")) AND (20 > time_bucket('10'::bigint, \"time\")))\n\n\\qecho time_bucket exclusion with date\ntime_bucket exclusion with date\n:PREFIX SELECT * FROM metrics_date WHERE time_bucket('1d',time) < '2000-01-03' ORDER BY time;\n--- QUERY PLAN ---\n Index Only Scan Backward using _hyper_8_171_chunk_metrics_date_time_idx on _hyper_8_171_chunk\n   Index Cond: (\"time\" < '01-03-2000'::date)\n   Filter: (time_bucket('@ 1 day'::interval, \"time\") < '01-03-2000'::date)\n\n:PREFIX SELECT * FROM metrics_date WHERE time_bucket('1d',time) >= '2000-01-03' AND time_bucket('1d',time) <= '2000-01-10' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date\n   Order: metrics_date.\"time\"\n   ->  Index Only Scan Backward using _hyper_8_171_chunk_metrics_date_time_idx on _hyper_8_171_chunk\n         Index Cond: ((\"time\" >= '01-03-2000'::date) AND (\"time\" <= '01-11-2000'::date))\n         Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= '01-03-2000'::date) AND (time_bucket('@ 1 day'::interval, \"time\") <= '01-10-2000'::date))\n   ->  Index Only Scan Backward using _hyper_8_172_chunk_metrics_date_time_idx on _hyper_8_172_chunk\n         Index Cond: ((\"time\" >= '01-03-2000'::date) AND (\"time\" <= '01-11-2000'::date))\n         Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= '01-03-2000'::date) AND (time_bucket('@ 1 day'::interval, \"time\") <= '01-10-2000'::date))\n\n\\qecho time_bucket exclusion with timestamp\ntime_bucket exclusion with timestamp\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('1d',time) < '2000-01-03' ORDER BY time;\n--- QUERY PLAN ---\n Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n   Index Cond: (\"time\" < 'Mon Jan 03 00:00:00 2000'::timestamp without time zone)\n   Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Mon Jan 03 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('1d',time) >= '2000-01-03' AND time_bucket('1d',time) <= '2000-01-10' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000'::timestamp without time zone) AND (\"time\" <= 'Tue Jan 11 00:00:00 2000'::timestamp without time zone))\n         Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000'::timestamp without time zone) AND (time_bucket('@ 1 day'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000'::timestamp without time zone))\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000'::timestamp without time zone) AND (\"time\" <= 'Tue Jan 11 00:00:00 2000'::timestamp without time zone))\n         Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000'::timestamp without time zone) AND (time_bucket('@ 1 day'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000'::timestamp without time zone))\n\n\\qecho time_bucket exclusion with timestamptz\ntime_bucket exclusion with timestamptz\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('6h',time) < '2000-01-03' ORDER BY time;\n--- QUERY PLAN ---\n Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n   Index Cond: (\"time\" < 'Mon Jan 03 06:00:00 2000 PST'::timestamp with time zone)\n   Filter: (time_bucket('@ 6 hours'::interval, \"time\") < 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('6h',time) >= '2000-01-03' AND time_bucket('6h',time) <= '2000-01-10' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" <= 'Mon Jan 10 06:00:00 2000 PST'::timestamp with time zone))\n         Filter: ((time_bucket('@ 6 hours'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 6 hours'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" <= 'Mon Jan 10 06:00:00 2000 PST'::timestamp with time zone))\n         Filter: ((time_bucket('@ 6 hours'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 6 hours'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho time_bucket exclusion with timestamptz and day interval\ntime_bucket exclusion with timestamptz and day interval\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('1d',time) < '2000-01-03' ORDER BY time;\n--- QUERY PLAN ---\n Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n   Index Cond: (\"time\" < 'Tue Jan 04 00:00:00 2000 PST'::timestamp with time zone)\n   Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('1d',time) >= '2000-01-03' AND time_bucket('1d',time) <= '2000-01-10' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" <= 'Tue Jan 11 00:00:00 2000 PST'::timestamp with time zone))\n         Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 1 day'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" <= 'Tue Jan 11 00:00:00 2000 PST'::timestamp with time zone))\n         Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 1 day'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('1d',time) >= '2000-01-03' AND time_bucket('7d',time) <= '2000-01-10' ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: metrics_timestamptz.\"time\"\n   ->  Append\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n               Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" <= 'Mon Jan 17 00:00:00 2000 PST'::timestamp with time zone))\n               Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 7 days'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Seq Scan on _hyper_6_161_chunk\n               Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 7 days'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n               Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" <= 'Mon Jan 17 00:00:00 2000 PST'::timestamp with time zone))\n               Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 7 days'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho no transformation\nno transformation\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10 + floor(random())::int, time) > 10 AND time_bucket(10 + floor(random())::int, time) < 100 AND time < 150 ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Custom Scan (ChunkAppend) on hyper\n         Chunks excluded during startup: 0\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_4_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_5_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_6_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_7_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_8_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_9_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_10_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_11_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_12_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_13_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_14_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_15_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n\n\\qecho exclude chunks based on time column with partitioning function. This\nexclude chunks based on time column with partitioning function. This\n\\qecho transparently applies the time partitioning function on the time\ntransparently applies the time partitioning function on the time\n\\qecho value to be able to exclude chunks (similar to a closed dimension).\nvalue to be able to exclude chunks (similar to a closed dimension).\n:PREFIX SELECT * FROM hyper_timefunc WHERE time < 4 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_timefunc.value\n   ->  Append\n         ->  Seq Scan on _hyper_4_124_chunk\n               Filter: (\"time\" < '4'::double precision)\n         ->  Seq Scan on _hyper_4_125_chunk\n               Filter: (\"time\" < '4'::double precision)\n         ->  Seq Scan on _hyper_4_126_chunk\n               Filter: (\"time\" < '4'::double precision)\n         ->  Seq Scan on _hyper_4_127_chunk\n               Filter: (\"time\" < '4'::double precision)\n\n\\qecho excluding based on time expression is currently unoptimized\nexcluding based on time expression is currently unoptimized\n:PREFIX SELECT * FROM hyper_timefunc WHERE unix_to_timestamp(time) < 'Wed Dec 31 16:00:04 1969 PST' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_timefunc.value\n   ->  Append\n         ->  Seq Scan on _hyper_4_124_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_125_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_126_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_127_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_128_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_129_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_130_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_131_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_132_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_133_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_134_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_135_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_136_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_137_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_138_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_139_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_140_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_141_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_142_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_143_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_144_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_145_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_146_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_147_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_148_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_149_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_150_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_151_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_152_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_153_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_154_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n\n\\qecho test qual propagation for joins\ntest qual propagation for joins\nRESET constraint_exclusion;\n\\qecho nothing to propagate\nnothing to propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1, metrics_timestamptz_2 m2 WHERE m1.time = m2.time ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Left Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time ORDER BY m1.time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: m1.\"time\"\n   ->  Merge Right Join\n         Merge Cond: (m1.\"time\" = m2.\"time\")\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n               Order: m1.\"time\"\n               ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n               ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n               ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n         ->  Materialize\n               ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n                     Order: m2.\"time\"\n                     ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n                     ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n                     ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n                     ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n\\qecho OR constraints should not propagate\nOR constraints should not propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10' OR m1.time > '2001-01-01' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Filter: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) OR (\"time\" > 'Mon Jan 01 00:00:00 2001 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Filter: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) OR (\"time\" > 'Mon Jan 01 00:00:00 2001 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n\\qecho test single constraint\ntest single constraint\n\\qecho constraint should be on both scans\nconstraint should be on both scans\n\\qecho these will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\nthese will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1, metrics_timestamptz_2 m2 WHERE m1.time = m2.time AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Left Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho test 2 constraints on single relation\ntest 2 constraints on single relation\n\\qecho these will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\nthese will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1, metrics_timestamptz_2 m2 WHERE m1.time = m2.time AND m1.time > '2000-01-01' AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Nested Loop Left Join\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Append\n         ->  Index Only Scan using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n               Index Cond: (\"time\" = m1.\"time\")\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho test 2 constraints with 1 constraint on each relation\ntest 2 constraints with 1 constraint on each relation\n\\qecho these will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\nthese will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1, metrics_timestamptz_2 m2 WHERE m1.time = m2.time AND m1.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho test constraints in ON clause of INNER JOIN\ntest constraints in ON clause of INNER JOIN\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho test constraints in ON clause of LEFT JOIN\ntest constraints in ON clause of LEFT JOIN\n\\qecho must not propagate\nmust not propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Left Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho test constraints in ON clause of RIGHT JOIN\ntest constraints in ON clause of RIGHT JOIN\n\\qecho must not propagate\nmust not propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Gather Merge\n   Workers Planned: 2\n   ->  Sort\n         Sort Key: m1.\"time\"\n         ->  Parallel Hash Left Join\n               Hash Cond: (m2.\"time\" = m1.\"time\")\n               Join Filter: ((m2.\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (m2.\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_7_165_chunk m2_1\n                     ->  Parallel Seq Scan on _hyper_7_166_chunk m2_2\n                     ->  Parallel Seq Scan on _hyper_7_167_chunk m2_3\n                     ->  Parallel Seq Scan on _hyper_7_168_chunk m2_4\n                     ->  Parallel Seq Scan on _hyper_7_169_chunk m2_5\n                     ->  Parallel Seq Scan on _hyper_7_170_chunk m2_6\n               ->  Parallel Hash\n                     ->  Parallel Append\n                           ->  Parallel Seq Scan on _hyper_6_160_chunk m1_1\n                           ->  Parallel Seq Scan on _hyper_6_161_chunk m1_2\n                           ->  Parallel Seq Scan on _hyper_6_162_chunk m1_3\n                           ->  Parallel Seq Scan on _hyper_6_163_chunk m1_4\n                           ->  Parallel Seq Scan on _hyper_6_164_chunk m1_5\n\n\\qecho test equality condition not in ON clause\ntest equality condition not in ON clause\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON true WHERE m2.time = m1.time AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho test constraints not joined on\ntest constraints not joined on\n\\qecho device_id constraint must not propagate\ndevice_id constraint must not propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON true WHERE m2.time = m1.time AND m2.time < '2000-01-10' AND m1.device_id = 1 ORDER BY m1.time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: m1.\"time\"\n   ->  Nested Loop\n         ->  Append\n               ->  Seq Scan on _hyper_6_160_chunk m1_1\n                     Filter: (device_id = 1)\n               ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n                     Filter: (device_id = 1)\n         ->  Append\n               ->  Index Only Scan using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: (\"time\" = m1.\"time\")\n               ->  Index Only Scan using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" = m1.\"time\") AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho test multiple join conditions\ntest multiple join conditions\n\\qecho device_id constraint should propagate\ndevice_id constraint should propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON true WHERE m2.time = m1.time AND m1.device_id = m2.device_id AND m2.time < '2000-01-10' AND m1.device_id = 1 ORDER BY m1.time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: m1.\"time\"\n   ->  Nested Loop\n         ->  Append\n               ->  Seq Scan on _hyper_6_160_chunk m1_1\n                     Filter: (device_id = 1)\n               ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n                     Filter: (device_id = 1)\n         ->  Append\n               ->  Index Scan using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: (\"time\" = m1.\"time\")\n                     Filter: (device_id = 1)\n               ->  Index Scan using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" = m1.\"time\") AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n                     Filter: (device_id = 1)\n\n\\qecho test join with 3 tables\ntest join with 3 tables\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time INNER JOIN metrics_timestamptz m3 ON m2.time=m3.time WHERE m1.time > '2000-01-01' AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Nested Loop\n   ->  Merge Join\n         Merge Cond: (m1.\"time\" = m2.\"time\")\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n               Order: m1.\"time\"\n               ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Materialize\n               ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n                     Order: m2.\"time\"\n                     ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                           Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n                     ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                           Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Append\n         ->  Index Only Scan using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m3_1\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m3_2\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m3_3\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m3_4\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m3_5\n               Index Cond: (\"time\" = m1.\"time\")\n\n\\qecho test non-Const constraints\ntest non-Const constraints\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10'::text::timestamptz ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         Chunks excluded during startup: 3\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               Chunks excluded during startup: 4\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n\n\\qecho test now()\ntest now()\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < now() ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         Chunks excluded during startup: 0\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: (\"time\" < now())\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < now())\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n               Index Cond: (\"time\" < now())\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n               Index Cond: (\"time\" < now())\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n               Index Cond: (\"time\" < now())\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               Chunks excluded during startup: 0\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: (\"time\" < now())\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < now())\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n                     Index Cond: (\"time\" < now())\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n                     Index Cond: (\"time\" < now())\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n                     Index Cond: (\"time\" < now())\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n                     Index Cond: (\"time\" < now())\n\n\\qecho test volatile function\ntest volatile function\n\\qecho should not propagate\nshould not propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < clock_timestamp() ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         Chunks excluded during startup: 0\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n               Filter: (\"time\" < clock_timestamp())\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m2.time < clock_timestamp() ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m2.\"time\" = m1.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n         Order: m2.\"time\"\n         Chunks excluded during startup: 0\n         ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n               Filter: (\"time\" < clock_timestamp())\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n               Order: m1.\"time\"\n               ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n               ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n               ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n\n\\qecho test JOINs with normal table\ntest JOINs with normal table\n\\qecho will not propagate because constraints are only added to hypertables\nwill not propagate because constraints are only added to hypertables\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN regular_timestamptz m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Sort\n         Sort Key: m2.\"time\"\n         ->  Seq Scan on regular_timestamptz m2\n\n\\qecho test JOINs with normal table\ntest JOINs with normal table\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN regular_timestamptz m2 ON m1.time = m2.time WHERE m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m2.\"time\" = m1.\"time\")\n   ->  Sort\n         Sort Key: m2.\"time\"\n         ->  Seq Scan on regular_timestamptz m2\n               Filter: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n               Order: m1.\"time\"\n               ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho test quals are not pushed into OUTER JOIN\ntest quals are not pushed into OUTER JOIN\nCREATE TABLE outer_join_1 (id int, name text,time timestamptz NOT NULL DEFAULT '2000-01-01');\nCREATE TABLE outer_join_2 (id int, name text,time timestamptz NOT NULL DEFAULT '2000-01-01');\nSELECT (SELECT table_name FROM create_hypertable(tbl, 'time')) FROM (VALUES ('outer_join_1'),('outer_join_2')) v(tbl);\n  table_name  \n--------------\n outer_join_1\n outer_join_2\n\nINSERT INTO outer_join_1 VALUES(1,'a'), (2,'b');\nINSERT INTO outer_join_2 VALUES(1,'a');\n:PREFIX SELECT one.id, two.name FROM outer_join_1 one LEFT OUTER JOIN outer_join_2 two ON one.id=two.id WHERE one.id=2;\n--- QUERY PLAN ---\n Nested Loop Left Join\n   ->  Seq Scan on _hyper_9_176_chunk one\n         Filter: (id = 2)\n   ->  Materialize\n         ->  Seq Scan on _hyper_10_177_chunk two\n               Filter: (id = 2)\n\n:PREFIX SELECT one.id, two.name FROM outer_join_2 two RIGHT OUTER JOIN outer_join_1 one ON one.id=two.id WHERE one.id=2;\n--- QUERY PLAN ---\n Nested Loop Left Join\n   ->  Seq Scan on _hyper_9_176_chunk one\n         Filter: (id = 2)\n   ->  Materialize\n         ->  Seq Scan on _hyper_10_177_chunk two\n               Filter: (id = 2)\n\nDROP TABLE outer_join_1;\nDROP TABLE outer_join_2;\n-- test UNION between regular table and hypertable\nSELECT time FROM regular_timestamptz UNION SELECT time FROM metrics_timestamptz ORDER BY 1;\n             time             \n------------------------------\n Sat Jan 01 00:00:00 2000 PST\n Sun Jan 02 00:00:00 2000 PST\n Mon Jan 03 00:00:00 2000 PST\n Tue Jan 04 00:00:00 2000 PST\n Wed Jan 05 00:00:00 2000 PST\n Thu Jan 06 00:00:00 2000 PST\n Fri Jan 07 00:00:00 2000 PST\n Sat Jan 08 00:00:00 2000 PST\n Sun Jan 09 00:00:00 2000 PST\n Mon Jan 10 00:00:00 2000 PST\n Tue Jan 11 00:00:00 2000 PST\n Wed Jan 12 00:00:00 2000 PST\n Thu Jan 13 00:00:00 2000 PST\n Fri Jan 14 00:00:00 2000 PST\n Sat Jan 15 00:00:00 2000 PST\n Sun Jan 16 00:00:00 2000 PST\n Mon Jan 17 00:00:00 2000 PST\n Tue Jan 18 00:00:00 2000 PST\n Wed Jan 19 00:00:00 2000 PST\n Thu Jan 20 00:00:00 2000 PST\n Fri Jan 21 00:00:00 2000 PST\n Sat Jan 22 00:00:00 2000 PST\n Sun Jan 23 00:00:00 2000 PST\n Mon Jan 24 00:00:00 2000 PST\n Tue Jan 25 00:00:00 2000 PST\n Wed Jan 26 00:00:00 2000 PST\n Thu Jan 27 00:00:00 2000 PST\n Fri Jan 28 00:00:00 2000 PST\n Sat Jan 29 00:00:00 2000 PST\n Sun Jan 30 00:00:00 2000 PST\n Mon Jan 31 00:00:00 2000 PST\n Tue Feb 01 00:00:00 2000 PST\n\n-- test UNION ALL between regular table and hypertable\nSELECT time FROM regular_timestamptz UNION ALL SELECT time FROM metrics_timestamptz ORDER BY 1;\n             time             \n------------------------------\n Sat Jan 01 00:00:00 2000 PST\n Sat Jan 01 00:00:00 2000 PST\n Sat Jan 01 00:00:00 2000 PST\n Sat Jan 01 00:00:00 2000 PST\n Sun Jan 02 00:00:00 2000 PST\n Sun Jan 02 00:00:00 2000 PST\n Sun Jan 02 00:00:00 2000 PST\n Sun Jan 02 00:00:00 2000 PST\n Mon Jan 03 00:00:00 2000 PST\n Mon Jan 03 00:00:00 2000 PST\n Mon Jan 03 00:00:00 2000 PST\n Mon Jan 03 00:00:00 2000 PST\n Tue Jan 04 00:00:00 2000 PST\n Tue Jan 04 00:00:00 2000 PST\n Tue Jan 04 00:00:00 2000 PST\n Tue Jan 04 00:00:00 2000 PST\n Wed Jan 05 00:00:00 2000 PST\n Wed Jan 05 00:00:00 2000 PST\n Wed Jan 05 00:00:00 2000 PST\n Wed Jan 05 00:00:00 2000 PST\n Thu Jan 06 00:00:00 2000 PST\n Thu Jan 06 00:00:00 2000 PST\n Thu Jan 06 00:00:00 2000 PST\n Thu Jan 06 00:00:00 2000 PST\n Fri Jan 07 00:00:00 2000 PST\n Fri Jan 07 00:00:00 2000 PST\n Fri Jan 07 00:00:00 2000 PST\n Fri Jan 07 00:00:00 2000 PST\n Sat Jan 08 00:00:00 2000 PST\n Sat Jan 08 00:00:00 2000 PST\n Sat Jan 08 00:00:00 2000 PST\n Sat Jan 08 00:00:00 2000 PST\n Sun Jan 09 00:00:00 2000 PST\n Sun Jan 09 00:00:00 2000 PST\n Sun Jan 09 00:00:00 2000 PST\n Sun Jan 09 00:00:00 2000 PST\n Mon Jan 10 00:00:00 2000 PST\n Mon Jan 10 00:00:00 2000 PST\n Mon Jan 10 00:00:00 2000 PST\n Mon Jan 10 00:00:00 2000 PST\n Tue Jan 11 00:00:00 2000 PST\n Tue Jan 11 00:00:00 2000 PST\n Tue Jan 11 00:00:00 2000 PST\n Tue Jan 11 00:00:00 2000 PST\n Wed Jan 12 00:00:00 2000 PST\n Wed Jan 12 00:00:00 2000 PST\n Wed Jan 12 00:00:00 2000 PST\n Wed Jan 12 00:00:00 2000 PST\n Thu Jan 13 00:00:00 2000 PST\n Thu Jan 13 00:00:00 2000 PST\n Thu Jan 13 00:00:00 2000 PST\n Thu Jan 13 00:00:00 2000 PST\n Fri Jan 14 00:00:00 2000 PST\n Fri Jan 14 00:00:00 2000 PST\n Fri Jan 14 00:00:00 2000 PST\n Fri Jan 14 00:00:00 2000 PST\n Sat Jan 15 00:00:00 2000 PST\n Sat Jan 15 00:00:00 2000 PST\n Sat Jan 15 00:00:00 2000 PST\n Sat Jan 15 00:00:00 2000 PST\n Sun Jan 16 00:00:00 2000 PST\n Sun Jan 16 00:00:00 2000 PST\n Sun Jan 16 00:00:00 2000 PST\n Sun Jan 16 00:00:00 2000 PST\n Mon Jan 17 00:00:00 2000 PST\n Mon Jan 17 00:00:00 2000 PST\n Mon Jan 17 00:00:00 2000 PST\n Mon Jan 17 00:00:00 2000 PST\n Tue Jan 18 00:00:00 2000 PST\n Tue Jan 18 00:00:00 2000 PST\n Tue Jan 18 00:00:00 2000 PST\n Tue Jan 18 00:00:00 2000 PST\n Wed Jan 19 00:00:00 2000 PST\n Wed Jan 19 00:00:00 2000 PST\n Wed Jan 19 00:00:00 2000 PST\n Wed Jan 19 00:00:00 2000 PST\n Thu Jan 20 00:00:00 2000 PST\n Thu Jan 20 00:00:00 2000 PST\n Thu Jan 20 00:00:00 2000 PST\n Thu Jan 20 00:00:00 2000 PST\n Fri Jan 21 00:00:00 2000 PST\n Fri Jan 21 00:00:00 2000 PST\n Fri Jan 21 00:00:00 2000 PST\n Fri Jan 21 00:00:00 2000 PST\n Sat Jan 22 00:00:00 2000 PST\n Sat Jan 22 00:00:00 2000 PST\n Sat Jan 22 00:00:00 2000 PST\n Sat Jan 22 00:00:00 2000 PST\n Sun Jan 23 00:00:00 2000 PST\n Sun Jan 23 00:00:00 2000 PST\n Sun Jan 23 00:00:00 2000 PST\n Sun Jan 23 00:00:00 2000 PST\n Mon Jan 24 00:00:00 2000 PST\n Mon Jan 24 00:00:00 2000 PST\n Mon Jan 24 00:00:00 2000 PST\n Mon Jan 24 00:00:00 2000 PST\n Tue Jan 25 00:00:00 2000 PST\n Tue Jan 25 00:00:00 2000 PST\n Tue Jan 25 00:00:00 2000 PST\n Tue Jan 25 00:00:00 2000 PST\n Wed Jan 26 00:00:00 2000 PST\n Wed Jan 26 00:00:00 2000 PST\n Wed Jan 26 00:00:00 2000 PST\n Wed Jan 26 00:00:00 2000 PST\n Thu Jan 27 00:00:00 2000 PST\n Thu Jan 27 00:00:00 2000 PST\n Thu Jan 27 00:00:00 2000 PST\n Thu Jan 27 00:00:00 2000 PST\n Fri Jan 28 00:00:00 2000 PST\n Fri Jan 28 00:00:00 2000 PST\n Fri Jan 28 00:00:00 2000 PST\n Fri Jan 28 00:00:00 2000 PST\n Sat Jan 29 00:00:00 2000 PST\n Sat Jan 29 00:00:00 2000 PST\n Sat Jan 29 00:00:00 2000 PST\n Sat Jan 29 00:00:00 2000 PST\n Sun Jan 30 00:00:00 2000 PST\n Sun Jan 30 00:00:00 2000 PST\n Sun Jan 30 00:00:00 2000 PST\n Sun Jan 30 00:00:00 2000 PST\n Mon Jan 31 00:00:00 2000 PST\n Mon Jan 31 00:00:00 2000 PST\n Mon Jan 31 00:00:00 2000 PST\n Mon Jan 31 00:00:00 2000 PST\n Tue Feb 01 00:00:00 2000 PST\n Tue Feb 01 00:00:00 2000 PST\n Tue Feb 01 00:00:00 2000 PST\n Tue Feb 01 00:00:00 2000 PST\n\n-- test nested join qual propagation\n:PREFIX\nSELECT * FROM (\nSELECT o1_m1.time FROM metrics_timestamptz o1_m1 INNER JOIN metrics_timestamptz_2 o1_m2 ON true WHERE o1_m2.time = o1_m1.time AND o1_m1.device_id = o1_m2.device_id AND o1_m2.time < '2000-01-10' AND o1_m1.device_id = 1\n) o1 FULL OUTER JOIN (\nSELECT o2_m1.time FROM metrics_timestamptz o2_m1 FULL OUTER JOIN metrics_timestamptz_2 o2_m2 ON true WHERE o2_m2.time = o2_m1.time AND o2_m1.device_id = o2_m2.device_id AND o2_m2.time > '2000-01-20' AND o2_m1.device_id = 2\n) o2 ON o1.time = o2.time ORDER BY 1,2;\n--- QUERY PLAN ---\n Sort\n   Sort Key: o1_m1.\"time\", o2_m1.\"time\"\n   ->  Hash Full Join\n         Hash Cond: (o2_m1.\"time\" = o1_m1.\"time\")\n         ->  Nested Loop\n               ->  Append\n                     ->  Index Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk o2_m2_1\n                           Index Cond: (\"time\" > 'Thu Jan 20 00:00:00 2000 PST'::timestamp with time zone)\n                           Filter: (device_id = 2)\n                     ->  Seq Scan on _hyper_7_169_chunk o2_m2_2\n                           Filter: (device_id = 2)\n                     ->  Seq Scan on _hyper_7_170_chunk o2_m2_3\n                           Filter: (device_id = 2)\n               ->  Append\n                     ->  Index Scan using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk o2_m1_1\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk o2_m1_2\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk o2_m1_3\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk o2_m1_4\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk o2_m1_5\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n         ->  Hash\n               ->  Nested Loop\n                     ->  Append\n                           ->  Seq Scan on _hyper_7_165_chunk o1_m2_1\n                                 Filter: (device_id = 1)\n                           ->  Index Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk o1_m2_2\n                                 Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n                                 Filter: (device_id = 1)\n                     ->  Append\n                           ->  Index Scan using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk o1_m1_1\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk o1_m1_2\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk o1_m1_3\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk o1_m1_4\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk o1_m1_5\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n\n:PREFIX\nSELECT * FROM (\nSELECT o1_m1.time FROM metrics_timestamptz o1_m1 INNER JOIN metrics_timestamptz_2 o1_m2 ON o1_m2.time = o1_m1.time AND o1_m1.device_id = o1_m2.device_id WHERE o1_m2.time < '2000-01-10' AND o1_m1.device_id = 1\n) o1 FULL OUTER JOIN (\nSELECT o2_m1.time FROM metrics_timestamptz o2_m1 FULL OUTER JOIN metrics_timestamptz_2 o2_m2 ON o2_m2.time = o2_m1.time AND o2_m1.device_id = o2_m2.device_id WHERE o2_m2.time > '2000-01-20' AND o2_m1.device_id = 2\n) o2 ON o1.time = o2.time ORDER BY 1,2;\n--- QUERY PLAN ---\n Sort\n   Sort Key: o1_m1.\"time\", o2_m1.\"time\"\n   ->  Hash Full Join\n         Hash Cond: (o2_m1.\"time\" = o1_m1.\"time\")\n         ->  Nested Loop\n               ->  Append\n                     ->  Index Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk o2_m2_1\n                           Index Cond: (\"time\" > 'Thu Jan 20 00:00:00 2000 PST'::timestamp with time zone)\n                           Filter: (device_id = 2)\n                     ->  Seq Scan on _hyper_7_169_chunk o2_m2_2\n                           Filter: (device_id = 2)\n                     ->  Seq Scan on _hyper_7_170_chunk o2_m2_3\n                           Filter: (device_id = 2)\n               ->  Append\n                     ->  Index Scan using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk o2_m1_1\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk o2_m1_2\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk o2_m1_3\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk o2_m1_4\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk o2_m1_5\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n         ->  Hash\n               ->  Nested Loop\n                     ->  Append\n                           ->  Seq Scan on _hyper_7_165_chunk o1_m2_1\n                                 Filter: (device_id = 1)\n                           ->  Index Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk o1_m2_2\n                                 Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n                                 Filter: (device_id = 1)\n                     ->  Append\n                           ->  Index Scan using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk o1_m1_1\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk o1_m1_2\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk o1_m1_3\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk o1_m1_4\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk o1_m1_5\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n\n\\set ECHO errors\nRESET timescaledb.enable_optimizations;\nCREATE TABLE t(time timestamptz NOT NULL);\nSELECT table_name FROM create_hypertable('t','time');\n table_name \n------------\n t\n\nINSERT INTO t VALUES ('2000-01-01'), ('2010-01-01'), ('2020-01-01');\nEXPLAIN (buffers off, costs off) SELECT * FROM t t1 INNER JOIN t t2 ON t1.time = t2.time WHERE t1.time < timestamptz '2010-01-01';\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (t1.\"time\" = t2.\"time\")\n   ->  Merge Append\n         Sort Key: t1.\"time\"\n         ->  Index Only Scan Backward using _hyper_15_182_chunk_t_time_idx on _hyper_15_182_chunk t1_1\n         ->  Index Only Scan Backward using _hyper_15_183_chunk_t_time_idx on _hyper_15_183_chunk t1_2\n               Index Cond: (\"time\" < 'Fri Jan 01 00:00:00 2010 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Merge Append\n               Sort Key: t2.\"time\"\n               ->  Index Only Scan Backward using _hyper_15_182_chunk_t_time_idx on _hyper_15_182_chunk t2_1\n               ->  Index Only Scan Backward using _hyper_15_183_chunk_t_time_idx on _hyper_15_183_chunk t2_2\n                     Index Cond: (\"time\" < 'Fri Jan 01 00:00:00 2010 PST'::timestamp with time zone)\n\nSET timescaledb.enable_qual_propagation TO false;\nEXPLAIN (buffers off, costs off) SELECT * FROM t t1 INNER JOIN t t2 ON t1.time = t2.time WHERE t1.time < timestamptz '2010-01-01';\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (t1.\"time\" = t2.\"time\")\n   ->  Merge Append\n         Sort Key: t1.\"time\"\n         ->  Index Only Scan Backward using _hyper_15_182_chunk_t_time_idx on _hyper_15_182_chunk t1_1\n         ->  Index Only Scan Backward using _hyper_15_183_chunk_t_time_idx on _hyper_15_183_chunk t1_2\n               Index Cond: (\"time\" < 'Fri Jan 01 00:00:00 2010 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Merge Append\n               Sort Key: t2.\"time\"\n               ->  Index Only Scan Backward using _hyper_15_182_chunk_t_time_idx on _hyper_15_182_chunk t2_1\n               ->  Index Only Scan Backward using _hyper_15_183_chunk_t_time_idx on _hyper_15_183_chunk t2_2\n               ->  Index Only Scan Backward using _hyper_15_184_chunk_t_time_idx on _hyper_15_184_chunk t2_3\n\nRESET timescaledb.enable_qual_propagation;\nCREATE TABLE test (a int, time timestamptz NOT NULL);\nSELECT table_name FROM create_hypertable('public.test', 'time');\n table_name \n------------\n test\n\nINSERT INTO test SELECT i, '2020-04-01'::date-10-i from generate_series(1,20) i;\nCREATE OR REPLACE FUNCTION test_f(_ts timestamptz)\nRETURNS SETOF test LANGUAGE SQL STABLE PARALLEL SAFE\nAS $f$\n   SELECT DISTINCT ON (a) * FROM test WHERE time >= _ts ORDER BY a, time DESC\n$f$;\nEXPLAIN (buffers off, costs off) SELECT * FROM test_f(now());\n--- QUERY PLAN ---\n Unique\n   ->  Sort\n         Sort Key: test.a, test.\"time\" DESC\n         ->  Custom Scan (ChunkAppend) on test\n               Chunks excluded during startup: 4\n\nEXPLAIN (buffers off, costs off) SELECT * FROM test_f(now());\n--- QUERY PLAN ---\n Unique\n   ->  Sort\n         Sort Key: test.a, test.\"time\" DESC\n         ->  Custom Scan (ChunkAppend) on test\n               Chunks excluded during startup: 4\n\nCREATE TABLE t1 (a int, b int NOT NULL);\nSELECT create_hypertable('t1', 'b', chunk_time_interval=>10);\n create_hypertable \n-------------------\n (17,public,t1,t)\n\nCREATE TABLE t2 (a int, b int NOT NULL);\nSELECT create_hypertable('t2', 'b', chunk_time_interval=>10);\n create_hypertable \n-------------------\n (18,public,t2,t)\n\nCREATE OR REPLACE FUNCTION f_t1(_a int, _b int)\n RETURNS SETOF t1\n LANGUAGE SQL\n STABLE PARALLEL SAFE\nAS $function$\n   SELECT DISTINCT ON (a) * FROM t1 WHERE a = _a and b = _b ORDER BY a, b DESC\n$function$\n;\nCREATE OR REPLACE FUNCTION f_t2(_a int, _b int) RETURNS SETOF t2 LANGUAGE sql STABLE PARALLEL SAFE\nAS $function$\n   SELECT DISTINCT ON (j.a) j.*\n   FROM\n      f_t1(_a, _b) sc,\n      t2 j\n   WHERE\n      j.b = _b AND\n      j.a = _a\n   ORDER BY j.a, j.b DESC\n$function$\n;\nCREATE OR REPLACE FUNCTION f_t1_2(_b int) RETURNS SETOF t1 LANGUAGE SQL STABLE PARALLEL SAFE\nAS $function$\n   SELECT DISTINCT ON (j.a) jt.* FROM t1 j, f_t1(j.a, _b) jt\n$function$;\nEXPLAIN (buffers off, costs off) SELECT * FROM f_t1_2(10);\n--- QUERY PLAN ---\n Subquery Scan on f_t1_2\n   ->  Unique\n         ->  Sort\n               Sort Key: j.a\n               ->  Nested Loop\n                     ->  Seq Scan on t1 j\n                     ->  Limit\n                           ->  Index Scan using t1_b_idx on t1\n                                 Index Cond: (b = 10)\n                                 Filter: (a = j.a)\n\nEXPLAIN (buffers off, costs off) SELECT * FROM f_t1_2(10) sc, f_t2(sc.a, 10);\n--- QUERY PLAN ---\n Nested Loop\n   ->  Unique\n         ->  Sort\n               Sort Key: j.a\n               ->  Nested Loop\n                     ->  Seq Scan on t1 j\n                     ->  Limit\n                           ->  Index Scan using t1_b_idx on t1\n                                 Index Cond: (b = 10)\n                                 Filter: (a = j.a)\n   ->  Limit\n         ->  Nested Loop\n               ->  Limit\n                     ->  Index Scan using t1_b_idx on t1 t1_1\n                           Index Cond: (b = 10)\n                           Filter: (a = t1.a)\n               ->  Index Scan using t2_b_idx on t2 j_1\n                     Index Cond: (b = 10)\n                     Filter: (a = t1.a)\n\nCREATE TABLE metrics_int1(time int, device text, value float) WITH (tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval=1);\nINSERT INTO metrics_int1 SELECT i, i::text, i FROM generate_series(3,7) i;\nSELECT tableoid::regclass, time FROM metrics_int1 ORDER BY time;\n                 tableoid                  | time \n-------------------------------------------+------\n _timescaledb_internal._hyper_19_189_chunk |    3\n _timescaledb_internal._hyper_19_190_chunk |    4\n _timescaledb_internal._hyper_19_191_chunk |    5\n _timescaledb_internal._hyper_19_192_chunk |    6\n _timescaledb_internal._hyper_19_193_chunk |    7\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time >= 2;\n--- QUERY PLAN ---\n Append (actual rows=5.00 loops=1)\n   ->  Seq Scan on _hyper_19_189_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_190_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_192_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_193_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time >= 1 AND time >= 2;\n--- QUERY PLAN ---\n Append (actual rows=5.00 loops=1)\n   ->  Seq Scan on _hyper_19_189_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_190_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_192_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_193_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time >= 4;\n--- QUERY PLAN ---\n Append (actual rows=4.00 loops=1)\n   ->  Seq Scan on _hyper_19_190_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_192_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_193_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time > 4 AND time < 6;\n--- QUERY PLAN ---\n Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time > 4 AND time <= 6;\n--- QUERY PLAN ---\n Append (actual rows=2.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_192_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time >= 4 AND time < 6;\n--- QUERY PLAN ---\n Append (actual rows=2.00 loops=1)\n   ->  Seq Scan on _hyper_19_190_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time >= 4 AND time <= 6;\n--- QUERY PLAN ---\n Append (actual rows=3.00 loops=1)\n   ->  Seq Scan on _hyper_19_190_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_192_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time BETWEEN 4 AND 5;\n--- QUERY PLAN ---\n Append (actual rows=2.00 loops=1)\n   ->  Seq Scan on _hyper_19_190_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time = 5;\n--- QUERY PLAN ---\n Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n\nSET TIMEZONE='UTC';\nCREATE TABLE metrics_tstz(time timestamptz, device text, value float) WITH (tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval='1day');\nINSERT INTO metrics_tstz SELECT '2000-01-01'::timestamptz + format('%s day',i)::interval, i::text, i FROM generate_series(2,6) i;\nSELECT tableoid::regclass, time FROM metrics_tstz ORDER BY time;\n                 tableoid                  |             time             \n-------------------------------------------+------------------------------\n _timescaledb_internal._hyper_20_194_chunk | Mon Jan 03 00:00:00 2000 UTC\n _timescaledb_internal._hyper_20_195_chunk | Tue Jan 04 00:00:00 2000 UTC\n _timescaledb_internal._hyper_20_196_chunk | Wed Jan 05 00:00:00 2000 UTC\n _timescaledb_internal._hyper_20_197_chunk | Thu Jan 06 00:00:00 2000 UTC\n _timescaledb_internal._hyper_20_198_chunk | Fri Jan 07 00:00:00 2000 UTC\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-02';\n--- QUERY PLAN ---\n Append (actual rows=5.00 loops=1)\n   ->  Seq Scan on _hyper_20_194_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_198_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-01' AND time >= '2000-01-02';\n--- QUERY PLAN ---\n Append (actual rows=5.00 loops=1)\n   ->  Seq Scan on _hyper_20_194_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_198_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-04';\n--- QUERY PLAN ---\n Append (actual rows=4.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_198_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time > '2000-01-04' AND time < '2000-01-06';\n--- QUERY PLAN ---\n Append (actual rows=1.00 loops=1)\n   ->  Index Scan using _hyper_20_195_chunk_metrics_tstz_time_idx on _hyper_20_195_chunk (actual rows=0.00 loops=1)\n         Index Cond: ((\"time\" > 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" < 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone))\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time > '2000-01-04' AND time <= '2000-01-06';\n--- QUERY PLAN ---\n Append (actual rows=2.00 loops=1)\n   ->  Index Scan using _hyper_20_195_chunk_metrics_tstz_time_idx on _hyper_20_195_chunk (actual rows=0.00 loops=1)\n         Index Cond: ((\"time\" > 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone))\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n   ->  Index Scan using _hyper_20_197_chunk_metrics_tstz_time_idx on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n         Index Cond: ((\"time\" > 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone))\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-04' AND time < '2000-01-06';\n--- QUERY PLAN ---\n Append (actual rows=2.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-04' AND time <= '2000-01-06';\n--- QUERY PLAN ---\n Append (actual rows=3.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n   ->  Index Scan using _hyper_20_197_chunk_metrics_tstz_time_idx on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n         Index Cond: ((\"time\" >= 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone))\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time BETWEEN '2000-01-04' AND '2000-01-05';\n--- QUERY PLAN ---\n Append (actual rows=2.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n   ->  Index Scan using _hyper_20_196_chunk_metrics_tstz_time_idx on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n         Index Cond: ((\"time\" >= 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Wed Jan 05 00:00:00 2000 UTC'::timestamp with time zone))\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time = '2000-01-05';\n--- QUERY PLAN ---\n Index Scan using _hyper_20_196_chunk_metrics_tstz_time_idx on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n   Index Cond: (\"time\" = 'Wed Jan 05 00:00:00 2000 UTC'::timestamp with time zone)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-04' AND time <= '2000-01-06' AND device = '5';\n--- QUERY PLAN ---\n Append (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=0.00 loops=1)\n         Filter: (device = '5'::text)\n         Rows Removed by Filter: 1\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=0.00 loops=1)\n         Filter: (device = '5'::text)\n         Rows Removed by Filter: 1\n   ->  Index Scan using _hyper_20_197_chunk_metrics_tstz_time_idx on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n         Index Cond: ((\"time\" >= 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone))\n         Filter: (device = '5'::text)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-04' AND time <= '2000-01-06' AND device IS NOT NULL;\n--- QUERY PLAN ---\n Append (actual rows=3.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n         Filter: (device IS NOT NULL)\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n         Filter: (device IS NOT NULL)\n   ->  Index Scan using _hyper_20_197_chunk_metrics_tstz_time_idx on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n         Index Cond: ((\"time\" >= 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone))\n         Filter: (device IS NOT NULL)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-04' AND time <= '2000-01-06' AND time < now();\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_tstz (actual rows=3.00 loops=1)\n   Chunks excluded during startup: 0\n   ->  Index Scan using _hyper_20_195_chunk_metrics_tstz_time_idx on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" < now())\n   ->  Index Scan using _hyper_20_196_chunk_metrics_tstz_time_idx on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" < now())\n   ->  Index Scan using _hyper_20_197_chunk_metrics_tstz_time_idx on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n         Index Cond: ((\"time\" >= 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" < now()))\n\nCREATE TABLE metrics_space(time timestamptz, device text, value float) WITH (tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval='1day');\nSELECT add_dimension('metrics_space', 'device', 4);\n           add_dimension            \n------------------------------------\n (25,public,metrics_space,device,t)\n\nINSERT INTO metrics_space SELECT '2000-01-01'::timestamptz + format('%s day',i)::interval, i::text, i FROM generate_series(2,6) i;\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_space WHERE time >= '2000-01-02';\n--- QUERY PLAN ---\n Append (actual rows=5.00 loops=1)\n   ->  Index Scan using _hyper_21_199_chunk_metrics_space_time_idx on _hyper_21_199_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" >= 'Sun Jan 02 00:00:00 2000 UTC'::timestamp with time zone)\n   ->  Index Scan using _hyper_21_200_chunk_metrics_space_time_idx on _hyper_21_200_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" >= 'Sun Jan 02 00:00:00 2000 UTC'::timestamp with time zone)\n   ->  Index Scan using _hyper_21_201_chunk_metrics_space_time_idx on _hyper_21_201_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" >= 'Sun Jan 02 00:00:00 2000 UTC'::timestamp with time zone)\n   ->  Index Scan using _hyper_21_202_chunk_metrics_space_time_idx on _hyper_21_202_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" >= 'Sun Jan 02 00:00:00 2000 UTC'::timestamp with time zone)\n   ->  Index Scan using _hyper_21_203_chunk_metrics_space_time_idx on _hyper_21_203_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" >= 'Sun Jan 02 00:00:00 2000 UTC'::timestamp with time zone)\n\n--TEST END--\n"
  },
  {
    "path": "test/expected/plan_expand_hypertable-18.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\set PREFIX 'EXPLAIN (buffers off, costs off) '\n\\ir include/plan_expand_hypertable_load.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n--single time dimension\nCREATE TABLE hyper (\"time_broken\" bigint NOT NULL, \"value\" integer);\nALTER TABLE hyper\nDROP COLUMN time_broken,\nADD COLUMN time BIGINT;\nSELECT create_hypertable('hyper', 'time',  chunk_time_interval => 10);\n create_hypertable  \n--------------------\n (1,public,hyper,t)\n\nINSERT INTO hyper SELECT g, g FROM generate_series(0,1000) g;\n--insert a point with INT_MAX_64\nINSERT INTO hyper (time, value) SELECT 9223372036854775807::bigint, 0;\n--time and space\nCREATE TABLE hyper_w_space (\"time_broken\" bigint NOT NULL, \"device_id\" text, \"value\" integer);\nALTER TABLE hyper_w_space\nDROP COLUMN time_broken,\nADD COLUMN time BIGINT;\nSELECT create_hypertable('hyper_w_space', 'time', 'device_id', 4, chunk_time_interval => 10);\n     create_hypertable      \n----------------------------\n (2,public,hyper_w_space,t)\n\nINSERT INTO hyper_w_space (time, device_id, value) SELECT g, 'dev' || g, g FROM generate_series(0,30) g;\nCREATE VIEW hyper_w_space_view AS (SELECT * FROM hyper_w_space);\n--with timestamp and space\nCREATE TABLE tag (id serial PRIMARY KEY, name text);\nCREATE TABLE hyper_ts (\"time_broken\" timestamptz NOT NULL, \"device_id\" text, tag_id INT REFERENCES tag(id), \"value\" integer);\nALTER TABLE hyper_ts\nDROP COLUMN time_broken,\nADD COLUMN time TIMESTAMPTZ;\nSELECT create_hypertable('hyper_ts', 'time', 'device_id', 2, chunk_time_interval => '10 seconds'::interval);\n   create_hypertable   \n-----------------------\n (3,public,hyper_ts,t)\n\nINSERT INTO tag(name) SELECT 'tag'||g FROM generate_series(0,10) g;\nINSERT INTO hyper_ts (time, device_id, tag_id, value) SELECT to_timestamp(g), 'dev' || g, (random() /10)+1, g FROM generate_series(0,30) g;\n--one in the future\nINSERT INTO hyper_ts (time, device_id, tag_id, value)  VALUES ('2100-01-01 02:03:04 PST', 'dev101', 1, 0);\n--time partitioning function\nCREATE OR REPLACE FUNCTION unix_to_timestamp(unixtime float8)\n    RETURNS TIMESTAMPTZ LANGUAGE SQL IMMUTABLE PARALLEL SAFE STRICT AS\n$BODY$\n    SELECT to_timestamp(unixtime);\n$BODY$;\nCREATE TABLE hyper_timefunc (\"time\" float8 NOT NULL, \"device_id\" text, \"value\" integer);\nSELECT create_hypertable('hyper_timefunc', 'time', 'device_id', 4, chunk_time_interval => 10, time_partitioning_func => 'unix_to_timestamp');\npsql:include/plan_expand_hypertable_load.sql:57: WARNING:  unexpected interval: smaller than one second\n      create_hypertable      \n-----------------------------\n (4,public,hyper_timefunc,t)\n\nINSERT INTO hyper_timefunc (time, device_id, value) SELECT g, 'dev' || g, g FROM generate_series(0,30) g;\nCREATE TABLE metrics_timestamp(time timestamp);\nSELECT create_hypertable('metrics_timestamp','time');\npsql:include/plan_expand_hypertable_load.sql:62: WARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n       create_hypertable        \n--------------------------------\n (5,public,metrics_timestamp,t)\n\nINSERT INTO metrics_timestamp SELECT generate_series('2000-01-01'::timestamp,'2000-02-01'::timestamp,'1d'::interval);\nCREATE TABLE metrics_timestamptz(time timestamptz, device_id int);\nSELECT create_hypertable('metrics_timestamptz','time');\n        create_hypertable         \n----------------------------------\n (6,public,metrics_timestamptz,t)\n\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval), 1;\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval), 2;\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval), 3;\n--create a second table to test joins with\nCREATE TABLE metrics_timestamptz_2 (LIKE metrics_timestamptz);\nSELECT create_hypertable('metrics_timestamptz_2','time');\n         create_hypertable          \n------------------------------------\n (7,public,metrics_timestamptz_2,t)\n\nINSERT INTO metrics_timestamptz_2\nSELECT * FROM metrics_timestamptz;\nINSERT INTO metrics_timestamptz_2 VALUES ('2000-12-01'::timestamptz, 3);\nCREATE TABLE metrics_date(time date);\nSELECT create_hypertable('metrics_date','time');\n     create_hypertable     \n---------------------------\n (8,public,metrics_date,t)\n\nINSERT INTO metrics_date SELECT generate_series('2000-01-01'::date,'2000-02-01'::date,'1d'::interval);\nANALYZE hyper;\nANALYZE hyper_w_space;\nANALYZE tag;\nANALYZE hyper_ts;\nANALYZE hyper_timefunc;\n-- create normal table for JOIN tests\nCREATE TABLE regular_timestamptz(time timestamptz);\nINSERT INTO regular_timestamptz SELECT generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval);\n\\ir include/plan_expand_hypertable_query.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n--we want to see how our logic excludes chunks\n--and not how much work constraint_exclusion does\nSET constraint_exclusion = 'off';\n\\qecho test upper bounds\ntest upper bounds\n:PREFIX SELECT * FROM hyper WHERE time < 10 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_1_chunk.value\n   ->  Seq Scan on _hyper_1_1_chunk\n\n:PREFIX SELECT * FROM hyper WHERE time < 11 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (\"time\" < 11)\n\n:PREFIX SELECT * FROM hyper WHERE time = 10 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_2_chunk.value\n   ->  Seq Scan on _hyper_1_2_chunk\n         Filter: (\"time\" = 10)\n\n:PREFIX SELECT * FROM hyper WHERE 10 >= time ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (10 >= \"time\")\n\n\\qecho test lower bounds\ntest lower bounds\n:PREFIX SELECT * FROM hyper WHERE time >= 10 and time < 20 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_2_chunk.value\n   ->  Seq Scan on _hyper_1_2_chunk\n\n:PREFIX SELECT * FROM hyper WHERE 10 < time and 20 >= time ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: ((10 < \"time\") AND (20 >= \"time\"))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((10 < \"time\") AND (20 >= \"time\"))\n\n:PREFIX SELECT * FROM hyper WHERE time >= 9 and time < 20 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: ((\"time\" >= 9) AND (\"time\" < 20))\n         ->  Seq Scan on _hyper_1_2_chunk\n\n:PREFIX SELECT * FROM hyper WHERE time > 9 and time < 20 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_2_chunk.value\n   ->  Seq Scan on _hyper_1_2_chunk\n\n\\qecho test empty result\ntest empty result\n:PREFIX SELECT * FROM hyper WHERE time < 0;\n--- QUERY PLAN ---\n Result\n   One-Time Filter: false\n\n\\qecho test expression evaluation\ntest expression evaluation\n:PREFIX SELECT * FROM hyper WHERE time < (5*2)::smallint;\n--- QUERY PLAN ---\n Seq Scan on _hyper_1_1_chunk\n\n\\qecho test logic at INT64_MAX\ntest logic at INT64_MAX\n:PREFIX SELECT * FROM hyper WHERE time = 9223372036854775807::bigint ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_102_chunk.value\n   ->  Seq Scan on _hyper_1_102_chunk\n         Filter: (\"time\" = '9223372036854775807'::bigint)\n\n:PREFIX SELECT * FROM hyper WHERE time = 9223372036854775806::bigint ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_102_chunk.value\n   ->  Seq Scan on _hyper_1_102_chunk\n         Filter: (\"time\" = '9223372036854775806'::bigint)\n\n:PREFIX SELECT * FROM hyper WHERE time >= 9223372036854775807::bigint ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_102_chunk.value\n   ->  Seq Scan on _hyper_1_102_chunk\n         Filter: (\"time\" >= '9223372036854775807'::bigint)\n\n:PREFIX SELECT * FROM hyper WHERE time > 9223372036854775807::bigint ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: value\n   ->  Result\n         One-Time Filter: false\n\n:PREFIX SELECT * FROM hyper WHERE time > 9223372036854775806::bigint ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_102_chunk.value\n   ->  Seq Scan on _hyper_1_102_chunk\n         Filter: (\"time\" > '9223372036854775806'::bigint)\n\n\\qecho cte\ncte\n:PREFIX WITH cte AS(\n  SELECT * FROM hyper WHERE time < 10\n)\nSELECT * FROM cte ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_1_chunk.value\n   ->  Seq Scan on _hyper_1_1_chunk\n\n\\qecho subquery\nsubquery\n:PREFIX SELECT 0 = ANY (SELECT value FROM hyper WHERE time < 10);\n--- QUERY PLAN ---\n Result\n   SubPlan 1\n     ->  Seq Scan on _hyper_1_1_chunk\n\n\\qecho no space constraint\nno space constraint\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (\"time\" < 10)\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (\"time\" < 10)\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (\"time\" < 10)\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (\"time\" < 10)\n\n\\qecho valid space constraint\nvalid space constraint\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 and device_id = 'dev5' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND (device_id = 'dev5'::text))\n\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 and 'dev5' = device_id ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND ('dev5'::text = device_id))\n\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 and 'dev'||(2+3) = device_id ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND ('dev5'::text = device_id))\n\n\\qecho only space constraint\nonly space constraint\n:PREFIX SELECT * FROM hyper_w_space WHERE 'dev5' = device_id ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ('dev5'::text = device_id)\n         ->  Seq Scan on _hyper_2_109_chunk\n               Filter: ('dev5'::text = device_id)\n         ->  Seq Scan on _hyper_2_111_chunk\n               Filter: ('dev5'::text = device_id)\n\n\\qecho unhandled space constraint\nunhandled space constraint\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 and device_id > 'dev5' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: ((\"time\" < 10) AND (device_id > 'dev5'::text))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: ((\"time\" < 10) AND (device_id > 'dev5'::text))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: ((\"time\" < 10) AND (device_id > 'dev5'::text))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ((\"time\" < 10) AND (device_id > 'dev5'::text))\n\n\\qecho use of OR - does not filter chunks\nuse of OR - does not filter chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND (device_id = 'dev5' or device_id = 'dev6') ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: ((\"time\" < 10) AND ((device_id = 'dev5'::text) OR (device_id = 'dev6'::text)))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: ((\"time\" < 10) AND ((device_id = 'dev5'::text) OR (device_id = 'dev6'::text)))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: ((\"time\" < 10) AND ((device_id = 'dev5'::text) OR (device_id = 'dev6'::text)))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ((\"time\" < 10) AND ((device_id = 'dev5'::text) OR (device_id = 'dev6'::text)))\n\n\\qecho cte\ncte\n:PREFIX WITH cte AS(\n   SELECT * FROM hyper_w_space WHERE time < 10 and device_id = 'dev5'\n)\nSELECT * FROM cte ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND (device_id = 'dev5'::text))\n\n\\qecho subquery\nsubquery\n:PREFIX SELECT 0 = ANY (SELECT value FROM hyper_w_space WHERE time < 10 and device_id = 'dev5');\n--- QUERY PLAN ---\n Result\n   SubPlan 1\n     ->  Seq Scan on _hyper_2_106_chunk\n           Filter: ((\"time\" < 10) AND (device_id = 'dev5'::text))\n\n\\qecho view\nview\n:PREFIX SELECT * FROM hyper_w_space_view WHERE time < 10 and device_id = 'dev5' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND (device_id = 'dev5'::text))\n\n\\qecho IN statement - simple\nIN statement - simple\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id IN ('dev5') ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND (device_id = 'dev5'::text))\n\n\\qecho IN statement - two chunks\nIN statement - two chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id IN ('dev5','dev6') ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: ((\"time\" < 10) AND (device_id = ANY ('{dev5,dev6}'::text[])))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ((\"time\" < 10) AND (device_id = ANY ('{dev5,dev6}'::text[])))\n\n\\qecho IN statement - one chunk\nIN statement - one chunk\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id IN ('dev4','dev5') ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND (device_id = ANY ('{dev4,dev5}'::text[])))\n\n\\qecho NOT IN - does not filter chunks\nNOT IN - does not filter chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id NOT IN ('dev5','dev6') ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: ((\"time\" < 10) AND (device_id <> ALL ('{dev5,dev6}'::text[])))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: ((\"time\" < 10) AND (device_id <> ALL ('{dev5,dev6}'::text[])))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: ((\"time\" < 10) AND (device_id <> ALL ('{dev5,dev6}'::text[])))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ((\"time\" < 10) AND (device_id <> ALL ('{dev5,dev6}'::text[])))\n\n\\qecho IN statement with subquery - does not filter chunks\nIN statement with subquery - does not filter chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id IN (SELECT 'dev5'::text) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_106_chunk.value\n   ->  Seq Scan on _hyper_2_106_chunk\n         Filter: ((\"time\" < 10) AND (device_id = 'dev5'::text))\n\n\\qecho ANY\nANY\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id = ANY(ARRAY['dev5','dev6']) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: ((\"time\" < 10) AND (device_id = ANY ('{dev5,dev6}'::text[])))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ((\"time\" < 10) AND (device_id = ANY ('{dev5,dev6}'::text[])))\n\n\\qecho ANY with intersection\nANY with intersection\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id = ANY(ARRAY['dev5','dev6']) AND device_id = ANY(ARRAY['dev6','dev7']) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_2_105_chunk.value\n   ->  Seq Scan on _hyper_2_105_chunk\n         Filter: ((\"time\" < 10) AND (device_id = ANY ('{dev5,dev6}'::text[])) AND (device_id = ANY ('{dev6,dev7}'::text[])))\n\n\\qecho ANY without intersection shouldnt scan any chunks\nANY without intersection shouldnt scan any chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id = ANY(ARRAY['dev5','dev6']) AND device_id = ANY(ARRAY['dev8','dev9']) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: value\n   ->  Result\n         One-Time Filter: false\n\n\\qecho ANY/IN/ALL only works for equals operator\nANY/IN/ALL only works for equals operator\n:PREFIX SELECT * FROM hyper_w_space WHERE device_id < ANY(ARRAY['dev5','dev6']) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_107_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_108_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_109_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_110_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_111_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_112_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_113_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_114_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n         ->  Seq Scan on _hyper_2_115_chunk\n               Filter: (device_id < ANY ('{dev5,dev6}'::text[]))\n\n\\qecho ALL with equals and different values shouldnt scan any chunks\nALL with equals and different values shouldnt scan any chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE device_id = ALL(ARRAY['dev5','dev6']) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: value\n   ->  Result\n         One-Time Filter: false\n\n\\qecho Multi AND\nMulti AND\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND time < 100 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: ((\"time\" < 10) AND (\"time\" < 100))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: ((\"time\" < 10) AND (\"time\" < 100))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: ((\"time\" < 10) AND (\"time\" < 100))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: ((\"time\" < 10) AND (\"time\" < 100))\n\n\\qecho Time dimension doesnt filter chunks when using non-equality IN/ANY with multiple arguments\nTime dimension doesnt filter chunks when using non-equality IN/ANY with multiple arguments\n:PREFIX SELECT * FROM hyper_w_space WHERE time < ANY(ARRAY[1,2]) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_107_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_108_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_109_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_110_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_111_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_112_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_113_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_114_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n         ->  Seq Scan on _hyper_2_115_chunk\n               Filter: (\"time\" < ANY ('{1,2}'::integer[]))\n\n\\qecho Time dimension chunk exclusion with IN/ANY equality uses bounding range\nTime dimension chunk exclusion with IN/ANY equality uses bounding range\n:PREFIX SELECT * FROM hyper WHERE time IN (5, 15) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n\n:PREFIX SELECT * FROM hyper WHERE time = ANY(ARRAY[5, 15, 25]) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (\"time\" = ANY ('{5,15,25}'::integer[]))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (\"time\" = ANY ('{5,15,25}'::integer[]))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: (\"time\" = ANY ('{5,15,25}'::integer[]))\n\n:PREFIX SELECT * FROM hyper WHERE time = ANY(ARRAY[25, 15, 5]) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.value\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (\"time\" = ANY ('{25,15,5}'::integer[]))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (\"time\" = ANY ('{25,15,5}'::integer[]))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: (\"time\" = ANY ('{25,15,5}'::integer[]))\n\n:PREFIX SELECT * FROM hyper_w_space WHERE time IN (5, 15) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_107_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_108_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_109_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n         ->  Seq Scan on _hyper_2_110_chunk\n               Filter: (\"time\" = ANY ('{5,15}'::bigint[]))\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time IN ('2000-01-05'::timestamp, '2000-01-15'::timestamp) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Index Cond: (\"time\" = ANY ('{\"Wed Jan 05 00:00:00 2000\",\"Sat Jan 15 00:00:00 2000\"}'::timestamp without time zone[]))\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" = ANY ('{\"Wed Jan 05 00:00:00 2000\",\"Sat Jan 15 00:00:00 2000\"}'::timestamp without time zone[]))\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Index Cond: (\"time\" = ANY ('{\"Wed Jan 05 00:00:00 2000\",\"Sat Jan 15 00:00:00 2000\"}'::timestamp without time zone[]))\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time IN ('2000-01-05'::timestamptz, '2000-01-15'::timestamptz) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Index Cond: (\"time\" = ANY ('{\"Wed Jan 05 00:00:00 2000 PST\",\"Sat Jan 15 00:00:00 2000 PST\"}'::timestamp with time zone[]))\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" = ANY ('{\"Wed Jan 05 00:00:00 2000 PST\",\"Sat Jan 15 00:00:00 2000 PST\"}'::timestamp with time zone[]))\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Index Cond: (\"time\" = ANY ('{\"Wed Jan 05 00:00:00 2000 PST\",\"Sat Jan 15 00:00:00 2000 PST\"}'::timestamp with time zone[]))\n\n:PREFIX SELECT * FROM metrics_date WHERE time IN ('2000-01-05'::date, '2000-01-15'::date) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date\n   Order: metrics_date.\"time\"\n   ->  Index Only Scan Backward using _hyper_8_171_chunk_metrics_date_time_idx on _hyper_8_171_chunk\n         Index Cond: (\"time\" = ANY ('{01-05-2000,01-15-2000}'::date[]))\n   ->  Index Only Scan Backward using _hyper_8_172_chunk_metrics_date_time_idx on _hyper_8_172_chunk\n         Index Cond: (\"time\" = ANY ('{01-05-2000,01-15-2000}'::date[]))\n   ->  Index Only Scan Backward using _hyper_8_173_chunk_metrics_date_time_idx on _hyper_8_173_chunk\n         Index Cond: (\"time\" = ANY ('{01-05-2000,01-15-2000}'::date[]))\n\n\\qecho cross-type IN/ANY: timestamp to timestamptz column does not use bounding range (stable cast)\ncross-type IN/ANY: timestamp to timestamptz column does not use bounding range (stable cast)\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time IN ('2000-01-05'::timestamp, '2000-01-15'::timestamp) ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 3\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Index Cond: (\"time\" = ANY (ARRAY[('Wed Jan 05 00:00:00 2000'::timestamp without time zone)::timestamp with time zone, ('Sat Jan 15 00:00:00 2000'::timestamp without time zone)::timestamp with time zone]))\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Index Cond: (\"time\" = ANY (ARRAY[('Wed Jan 05 00:00:00 2000'::timestamp without time zone)::timestamp with time zone, ('Sat Jan 15 00:00:00 2000'::timestamp without time zone)::timestamp with time zone]))\n\n\\qecho Time dimension chunk filtering works for ANY with single argument\nTime dimension chunk filtering works for ANY with single argument\n:PREFIX SELECT * FROM hyper_w_space WHERE time < ANY(ARRAY[1]) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (\"time\" < ANY ('{1}'::integer[]))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (\"time\" < ANY ('{1}'::integer[]))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (\"time\" < ANY ('{1}'::integer[]))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (\"time\" < ANY ('{1}'::integer[]))\n\n\\qecho Time dimension chunk filtering works for ALL with single argument\nTime dimension chunk filtering works for ALL with single argument\n:PREFIX SELECT * FROM hyper_w_space WHERE time < ALL(ARRAY[1]) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (\"time\" < ALL ('{1}'::integer[]))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (\"time\" < ALL ('{1}'::integer[]))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (\"time\" < ALL ('{1}'::integer[]))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (\"time\" < ALL ('{1}'::integer[]))\n\n\\qecho Time dimension chunk filtering works for ALL with multiple arguments\nTime dimension chunk filtering works for ALL with multiple arguments\n:PREFIX SELECT * FROM hyper_w_space WHERE time < ALL(ARRAY[1,10,20,30]) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: (\"time\" < ALL ('{1,10,20,30}'::integer[]))\n         ->  Seq Scan on _hyper_2_104_chunk\n               Filter: (\"time\" < ALL ('{1,10,20,30}'::integer[]))\n         ->  Seq Scan on _hyper_2_105_chunk\n               Filter: (\"time\" < ALL ('{1,10,20,30}'::integer[]))\n         ->  Seq Scan on _hyper_2_106_chunk\n               Filter: (\"time\" < ALL ('{1,10,20,30}'::integer[]))\n\n\\qecho AND intersection using IN and EQUALS\nAND intersection using IN and EQUALS\n:PREFIX SELECT * FROM hyper_w_space WHERE device_id IN ('dev1','dev2') AND device_id = 'dev1' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_w_space.value\n   ->  Append\n         ->  Seq Scan on _hyper_2_103_chunk\n               Filter: ((device_id = ANY ('{dev1,dev2}'::text[])) AND (device_id = 'dev1'::text))\n         ->  Seq Scan on _hyper_2_110_chunk\n               Filter: ((device_id = ANY ('{dev1,dev2}'::text[])) AND (device_id = 'dev1'::text))\n         ->  Seq Scan on _hyper_2_114_chunk\n               Filter: ((device_id = ANY ('{dev1,dev2}'::text[])) AND (device_id = 'dev1'::text))\n\n\\qecho AND with no intersection using IN and EQUALS\nAND with no intersection using IN and EQUALS\n:PREFIX SELECT * FROM hyper_w_space WHERE device_id IN ('dev1','dev2') AND device_id = 'dev3' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: value\n   ->  Result\n         One-Time Filter: false\n\n\\qecho timestamps\ntimestamps\n\\qecho these should work since they are immutable functions\nthese should work since they are immutable functions\n:PREFIX SELECT * FROM hyper_ts WHERE time < 'Wed Dec 31 16:00:10 1969 PST'::timestamptz ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Append\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_3_117_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM hyper_ts WHERE time < to_timestamp(10) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Append\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_3_117_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM hyper_ts WHERE time < 'Wed Dec 31 16:00:10 1969'::timestamp AT TIME ZONE 'PST' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Append\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_3_117_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM hyper_ts WHERE time < to_timestamp(10) and device_id = 'dev1' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_3_116_chunk.value\n   ->  Seq Scan on _hyper_3_116_chunk\n         Filter: ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text))\n\n\\qecho these should not work since uses stable functions;\nthese should not work since uses stable functions;\n:PREFIX SELECT * FROM hyper_ts WHERE time < 'Wed Dec 31 16:00:10 1969'::timestamp ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Custom Scan (ChunkAppend) on hyper_ts\n         Chunks excluded during startup: 6\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969'::timestamp without time zone)\n         ->  Seq Scan on _hyper_3_117_chunk\n               Filter: (\"time\" < 'Wed Dec 31 16:00:10 1969'::timestamp without time zone)\n\n:PREFIX SELECT * FROM hyper_ts WHERE time < ('Wed Dec 31 16:00:10 1969'::timestamp::timestamptz) ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Custom Scan (ChunkAppend) on hyper_ts\n         Chunks excluded during startup: 6\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: (\"time\" < ('Wed Dec 31 16:00:10 1969'::timestamp without time zone)::timestamp with time zone)\n         ->  Seq Scan on _hyper_3_117_chunk\n               Filter: (\"time\" < ('Wed Dec 31 16:00:10 1969'::timestamp without time zone)::timestamp with time zone)\n\n:PREFIX SELECT * FROM hyper_ts WHERE NOW() < time ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Custom Scan (ChunkAppend) on hyper_ts\n         Chunks excluded during startup: 7\n         ->  Seq Scan on _hyper_3_123_chunk\n               Filter: (now() < \"time\")\n\n\\qecho joins\njoins\n:PREFIX SELECT * FROM hyper_ts WHERE tag_id IN (SELECT id FROM tag WHERE tag.id=1) and time < to_timestamp(10) and device_id = 'dev1' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_3_116_chunk.value\n   ->  Nested Loop Semi Join\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text) AND (tag_id = 1))\n         ->  Seq Scan on tag\n               Filter: (id = 1)\n\n:PREFIX SELECT * FROM hyper_ts WHERE tag_id IN (SELECT id FROM tag WHERE tag.id=1) or (time < to_timestamp(10) and device_id = 'dev1') ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_ts.value\n   ->  Custom Scan (ChunkAppend) on hyper_ts\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: ((ANY (tag_id = (hashed SubPlan 1).col1)) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n               SubPlan 1\n                 ->  Seq Scan on tag\n                       Filter: (id = 1)\n         ->  Seq Scan on _hyper_3_117_chunk\n               Filter: ((ANY (tag_id = (hashed SubPlan 1).col1)) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n         ->  Seq Scan on _hyper_3_118_chunk\n               Filter: ((ANY (tag_id = (hashed SubPlan 1).col1)) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n         ->  Seq Scan on _hyper_3_119_chunk\n               Filter: ((ANY (tag_id = (hashed SubPlan 1).col1)) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n         ->  Seq Scan on _hyper_3_120_chunk\n               Filter: ((ANY (tag_id = (hashed SubPlan 1).col1)) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n         ->  Seq Scan on _hyper_3_121_chunk\n               Filter: ((ANY (tag_id = (hashed SubPlan 1).col1)) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n         ->  Seq Scan on _hyper_3_122_chunk\n               Filter: ((ANY (tag_id = (hashed SubPlan 1).col1)) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n         ->  Seq Scan on _hyper_3_123_chunk\n               Filter: ((ANY (tag_id = (hashed SubPlan 1).col1)) OR ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text)))\n\n:PREFIX SELECT * FROM hyper_ts WHERE tag_id IN (SELECT id FROM tag WHERE tag.name='tag1') and time < to_timestamp(10) and device_id = 'dev1' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_3_116_chunk.value\n   ->  Nested Loop\n         Join Filter: (_hyper_3_116_chunk.tag_id = tag.id)\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text))\n         ->  Seq Scan on tag\n               Filter: (name = 'tag1'::text)\n\n:PREFIX SELECT * FROM hyper_ts JOIN tag on (hyper_ts.tag_id = tag.id ) WHERE time < to_timestamp(10) and device_id = 'dev1' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_3_116_chunk.value\n   ->  Merge Join\n         Merge Cond: (tag.id = _hyper_3_116_chunk.tag_id)\n         ->  Index Scan using tag_pkey on tag\n         ->  Sort\n               Sort Key: _hyper_3_116_chunk.tag_id\n               ->  Seq Scan on _hyper_3_116_chunk\n                     Filter: ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text))\n\n:PREFIX SELECT * FROM hyper_ts JOIN tag on (hyper_ts.tag_id = tag.id ) WHERE tag.name = 'tag1' and time < to_timestamp(10) and device_id = 'dev1' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_3_116_chunk.value\n   ->  Nested Loop\n         Join Filter: (tag.id = _hyper_3_116_chunk.tag_id)\n         ->  Seq Scan on _hyper_3_116_chunk\n               Filter: ((\"time\" < 'Wed Dec 31 16:00:10 1969 PST'::timestamp with time zone) AND (device_id = 'dev1'::text))\n         ->  Seq Scan on tag\n               Filter: (name = 'tag1'::text)\n\n\\qecho test constraint exclusion for constraints in ON clause of JOINs\ntest constraint exclusion for constraints in ON clause of JOINs\n\\qecho should exclude chunks on m1 and propagate qual to m2 because of INNER JOIN\nshould exclude chunks on m1 and propagate qual to m2 because of INNER JOIN\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho should exclude chunks on m2 and propagate qual to m1 because of INNER JOIN\nshould exclude chunks on m2 and propagate qual to m1 because of INNER JOIN\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho must not exclude on m1\nmust not exclude on m1\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Left Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   Join Filter: (m1.\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n\\qecho should exclude chunks on m2\nshould exclude chunks on m2\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Left Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho should exclude chunks on m1\nshould exclude chunks on m1\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: m1.\"time\"\n   ->  Merge Right Join\n         Merge Cond: (m1.\"time\" = m2.\"time\")\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n               Order: m1.\"time\"\n               ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Materialize\n               ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n                     Order: m2.\"time\"\n                     ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n                     ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n                     ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n                     ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n\\qecho must not exclude chunks on m2\nmust not exclude chunks on m2\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time < '2000-01-10' ORDER BY m1.time, m2.time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: m1.\"time\", m2.\"time\"\n   ->  Merge Left Join\n         Merge Cond: (m2.\"time\" = m1.\"time\")\n         Join Filter: (m2.\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n         ->  Materialize\n               ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n                     Order: m1.\"time\"\n                     ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n                     ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n                     ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n                     ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n                     ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n\n\\qecho time_bucket exclusion\ntime_bucket exclusion\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time) < 10::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_1_chunk.\"time\"\n   ->  Seq Scan on _hyper_1_1_chunk\n         Filter: (time_bucket('10'::bigint, \"time\") < '10'::bigint)\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time) < 11::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '11'::bigint)\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '11'::bigint)\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((\"time\" < '21'::bigint) AND (time_bucket('10'::bigint, \"time\") < '11'::bigint))\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time) <= 10::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") <= '10'::bigint)\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") <= '10'::bigint)\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((\"time\" <= '20'::bigint) AND (time_bucket('10'::bigint, \"time\") <= '10'::bigint))\n\n:PREFIX SELECT * FROM hyper WHERE 10::bigint > time_bucket(10, time) ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_1_chunk.\"time\"\n   ->  Seq Scan on _hyper_1_1_chunk\n         Filter: ('10'::bigint > time_bucket('10'::bigint, \"time\"))\n\n:PREFIX SELECT * FROM hyper WHERE 11::bigint > time_bucket(10, time) ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: ('11'::bigint > time_bucket('10'::bigint, \"time\"))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: ('11'::bigint > time_bucket('10'::bigint, \"time\"))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((\"time\" < '21'::bigint) AND ('11'::bigint > time_bucket('10'::bigint, \"time\")))\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time, 5) < 10::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (time_bucket('10'::bigint, \"time\", '5'::bigint) < '10'::bigint)\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (time_bucket('10'::bigint, \"time\", '5'::bigint) < '10'::bigint)\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time, 5) < 11::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (time_bucket('10'::bigint, \"time\", '5'::bigint) < '11'::bigint)\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (time_bucket('10'::bigint, \"time\", '5'::bigint) < '11'::bigint)\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((\"time\" < '21'::bigint) AND (time_bucket('10'::bigint, \"time\", '5'::bigint) < '11'::bigint))\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time, 5) <= 10::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: (time_bucket('10'::bigint, \"time\", '5'::bigint) <= '10'::bigint)\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: (time_bucket('10'::bigint, \"time\", '5'::bigint) <= '10'::bigint)\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((\"time\" <= '20'::bigint) AND (time_bucket('10'::bigint, \"time\", '5'::bigint) <= '10'::bigint))\n\n:PREFIX SELECT * FROM hyper WHERE 10::bigint > time_bucket(10, time, 5) ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: ('10'::bigint > time_bucket('10'::bigint, \"time\", '5'::bigint))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: ('10'::bigint > time_bucket('10'::bigint, \"time\", '5'::bigint))\n\n:PREFIX SELECT * FROM hyper WHERE 11::bigint > time_bucket(10, time, 5) ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: ('11'::bigint > time_bucket('10'::bigint, \"time\", '5'::bigint))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: ('11'::bigint > time_bucket('10'::bigint, \"time\", '5'::bigint))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((\"time\" < '21'::bigint) AND ('11'::bigint > time_bucket('10'::bigint, \"time\", '5'::bigint)))\n\n\\qecho timestamp time_bucket exclusion\ntimestamp time_bucket exclusion\nSELECT count(DISTINCT tableoid) FROM metrics_timestamp;\n count \n-------\n     5\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time) < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") < 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") < 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time) <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") <= 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") <= 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time) > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time) >= '2000-01-15' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Index Cond: (\"time\" >= 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") >= 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") >= 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") >= 'Sat Jan 15 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'3d'::interval) < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) < 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) < 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'3d'::interval) <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) <= 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) <= 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'3d'::interval) > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'3d'::interval) >= '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Index Cond: (\"time\" >= 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) >= 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) >= 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'2000-01-10'::timestamp) < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) < 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) < 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'2000-01-10'::timestamp) <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) <= 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) <= 'Wed Jan 05 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'2000-01-10'::timestamp) > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) > 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'2000-01-10'::timestamp) >= '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Index Cond: (\"time\" >= 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) >= 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000'::timestamp without time zone) >= 'Tue Jan 25 00:00:00 2000'::timestamp without time zone)\n\n\\qecho timestamptz time_bucket exclusion\ntimestamptz time_bucket exclusion\nSELECT count(DISTINCT tableoid) FROM metrics_timestamptz;\n count \n-------\n     5\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time) < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time) <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time) > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time) >= '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\") >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'3d'::interval) < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'3d'::interval) <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'3d'::interval) > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'3d'::interval) >= '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", '@ 3 days'::interval) >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'2000-01-10'::timestamptz) < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'2000-01-10'::timestamptz) <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'2000-01-10'::timestamptz) > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'2000-01-10'::timestamptz) >= '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'Europe/Berlin') < '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" < 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) < 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'Europe/Berlin') <= '2000-01-05' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: (\"time\" <= 'Wed Jan 12 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) <= 'Wed Jan 05 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'Europe/Berlin') > '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) > 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'Europe/Berlin') >= '2000-01-25' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Index Cond: (\"time\" >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 7 days'::interval, \"time\", 'Europe/Berlin'::text, NULL::timestamp with time zone, NULL::interval) >= 'Tue Jan 25 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho test overflow behaviour of time_bucket exclusion\ntest overflow behaviour of time_bucket exclusion\n:PREFIX SELECT * FROM hyper WHERE time > 950 AND time_bucket(10, time) < '9223372036854775807'::bigint ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_96_chunk\n               Filter: ((\"time\" > 950) AND (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint))\n         ->  Seq Scan on _hyper_1_97_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint)\n         ->  Seq Scan on _hyper_1_98_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint)\n         ->  Seq Scan on _hyper_1_99_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint)\n         ->  Seq Scan on _hyper_1_100_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint)\n         ->  Seq Scan on _hyper_1_101_chunk\n               Filter: (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint)\n         ->  Seq Scan on _hyper_1_102_chunk\n               Filter: ((\"time\" > 950) AND (time_bucket('10'::bigint, \"time\") < '9223372036854775807'::bigint))\n\n\\qecho test timestamp upper boundary\ntest timestamp upper boundary\n\\qecho there should be no transformation if we are out of the supported (TimescaleDB-specific) range\nthere should be no transformation if we are out of the supported (TimescaleDB-specific) range\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('1d',time) < '294276-01-01'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n\n\\qecho transformation would be out of range\ntransformation would be out of range\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('1000d',time) < '294276-01-01'::timestamp ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276'::timestamp without time zone)\n\n\\qecho test timestamptz upper boundary\ntest timestamptz upper boundary\n\\qecho there should be no transformation if we are out of the supported (TimescaleDB-specific) range\nthere should be no transformation if we are out of the supported (TimescaleDB-specific) range\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('1d',time) < '294276-01-01'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n\n\\qecho transformation would be out of range\ntransformation would be out of range\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('1000d',time) < '294276-01-01'::timestamptz ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 1000 days'::interval, \"time\") < 'Sat Jan 01 00:00:00 294276 PST'::timestamp with time zone)\n\n\\qecho time_bucket exclusion with run-time constants\ntime_bucket exclusion with run-time constants\n-- These queries have a stable time_bucket expression, because the text::interval conversion is stable.\nPREPARE P1(text    , text) AS SELECT * FROM metrics_timestamptz WHERE time_bucket($1::interval, time) > $2::timestamptz ORDER BY time;\nPREPARE P2(text    , text) AS SELECT * FROM metrics_timestamp   WHERE time_bucket($1::interval, time) > $2::timestamptz ORDER BY time;\nPREPARE P3(text    , text) AS SELECT * FROM metrics_timestamptz WHERE time_bucket($1::interval, time) > $2::timestamp   ORDER BY time;\nPREPARE P4(text    , text) AS SELECT * FROM metrics_timestamp   WHERE time_bucket($1::interval, time) > $2::timestamp   ORDER BY time;\n-- These queries have an immutable time_bucket expression, because the parameter is passed as interval, and no conversion is involved.\nPREPARE P5(interval, text) AS SELECT * FROM metrics_timestamptz WHERE time_bucket($1::interval, time) > $2::timestamptz ORDER BY time;\nPREPARE P6(interval, text) AS SELECT * FROM metrics_timestamp   WHERE time_bucket($1::interval, time) > $2::timestamptz ORDER BY time;\nPREPARE P7(interval, text) AS SELECT * FROM metrics_timestamptz WHERE time_bucket($1::interval, time) > $2::timestamp   ORDER BY time;\nPREPARE P8(interval, text) AS SELECT * FROM metrics_timestamp   WHERE time_bucket($1::interval, time) > $2::timestamp   ORDER BY time;\nSET plan_cache_mode TO 'force_custom_plan';\n:PREFIX EXECUTE P1('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P2('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P3('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P4('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P5('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P6('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P7('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P8('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P1('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n\n:PREFIX EXECUTE P2('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n\n:PREFIX EXECUTE P3('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n\n:PREFIX EXECUTE P4('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket(('60 mins'::cstring)::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n\n:PREFIX EXECUTE P5('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n\n:PREFIX EXECUTE P6('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp with time zone)\n\n:PREFIX EXECUTE P7('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n\n:PREFIX EXECUTE P8('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket('@ 1 hour'::interval, \"time\") > ('2000-01-01 UTC'::cstring)::timestamp without time zone)\n\nSET plan_cache_mode TO 'force_generic_plan';\n:PREFIX EXECUTE P1('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P2('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P3('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P4('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P5('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P6('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P7('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P8('60 mins', '2024-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 5\n\n:PREFIX EXECUTE P1('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n\n:PREFIX EXECUTE P2('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp with time zone)\n\n:PREFIX EXECUTE P3('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n\n:PREFIX EXECUTE P4('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket(($1)::interval, \"time\") > ($2)::timestamp without time zone)\n\n:PREFIX EXECUTE P5('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n\n:PREFIX EXECUTE P6('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp with time zone)\n\n:PREFIX EXECUTE P7('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n\n:PREFIX EXECUTE P8('60 mins', '2000-01-01 UTC');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   Chunks excluded during startup: 0\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_157_chunk_metrics_timestamp_time_idx on _hyper_5_157_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_158_chunk_metrics_timestamp_time_idx on _hyper_5_158_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n   ->  Index Only Scan Backward using _hyper_5_159_chunk_metrics_timestamp_time_idx on _hyper_5_159_chunk\n         Filter: (time_bucket($1, \"time\") > ($2)::timestamp without time zone)\n\nRESET plan_cache_mode;\nDEALLOCATE P1;\nDEALLOCATE P2;\nDEALLOCATE P3;\nDEALLOCATE P4;\nDEALLOCATE P5;\nDEALLOCATE P6;\nDEALLOCATE P7;\nDEALLOCATE P8;\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time) > 10 AND time_bucket(10, time) < 100 ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Append\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: ((\"time\" > '10'::bigint) AND (\"time\" < '100'::bigint) AND (time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_4_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_5_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_6_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_7_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_8_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_9_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_10_chunk\n               Filter: ((time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 100))\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time) > 10 AND time_bucket(10, time) < 20 ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_2_chunk.\"time\"\n   ->  Seq Scan on _hyper_1_2_chunk\n         Filter: ((\"time\" > '10'::bigint) AND (\"time\" < '20'::bigint) AND (time_bucket('10'::bigint, \"time\") > 10) AND (time_bucket('10'::bigint, \"time\") < 20))\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(1, time) > 11 AND time_bucket(1, time) < 19 ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_2_chunk.\"time\"\n   ->  Seq Scan on _hyper_1_2_chunk\n         Filter: ((\"time\" > '11'::bigint) AND (\"time\" < '19'::bigint) AND (time_bucket('1'::bigint, \"time\") > 11) AND (time_bucket('1'::bigint, \"time\") < 19))\n\n:PREFIX SELECT * FROM hyper WHERE 10 < time_bucket(10, time) AND 20 > time_bucket(10,time) ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_2_chunk.\"time\"\n   ->  Seq Scan on _hyper_1_2_chunk\n         Filter: ((\"time\" > '10'::bigint) AND (\"time\" < '20'::bigint) AND (10 < time_bucket('10'::bigint, \"time\")) AND (20 > time_bucket('10'::bigint, \"time\")))\n\n\\qecho time_bucket exclusion with date\ntime_bucket exclusion with date\n:PREFIX SELECT * FROM metrics_date WHERE time_bucket('1d',time) < '2000-01-03' ORDER BY time;\n--- QUERY PLAN ---\n Index Only Scan Backward using _hyper_8_171_chunk_metrics_date_time_idx on _hyper_8_171_chunk\n   Index Cond: (\"time\" < '01-03-2000'::date)\n   Filter: (time_bucket('@ 1 day'::interval, \"time\") < '01-03-2000'::date)\n\n:PREFIX SELECT * FROM metrics_date WHERE time_bucket('1d',time) >= '2000-01-03' AND time_bucket('1d',time) <= '2000-01-10' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_date\n   Order: metrics_date.\"time\"\n   ->  Index Only Scan Backward using _hyper_8_171_chunk_metrics_date_time_idx on _hyper_8_171_chunk\n         Index Cond: ((\"time\" >= '01-03-2000'::date) AND (\"time\" <= '01-11-2000'::date))\n         Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= '01-03-2000'::date) AND (time_bucket('@ 1 day'::interval, \"time\") <= '01-10-2000'::date))\n   ->  Index Only Scan Backward using _hyper_8_172_chunk_metrics_date_time_idx on _hyper_8_172_chunk\n         Index Cond: ((\"time\" >= '01-03-2000'::date) AND (\"time\" <= '01-11-2000'::date))\n         Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= '01-03-2000'::date) AND (time_bucket('@ 1 day'::interval, \"time\") <= '01-10-2000'::date))\n\n\\qecho time_bucket exclusion with timestamp\ntime_bucket exclusion with timestamp\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('1d',time) < '2000-01-03' ORDER BY time;\n--- QUERY PLAN ---\n Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n   Index Cond: (\"time\" < 'Mon Jan 03 00:00:00 2000'::timestamp without time zone)\n   Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Mon Jan 03 00:00:00 2000'::timestamp without time zone)\n\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('1d',time) >= '2000-01-03' AND time_bucket('1d',time) <= '2000-01-10' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamp\n   Order: metrics_timestamp.\"time\"\n   ->  Index Only Scan Backward using _hyper_5_155_chunk_metrics_timestamp_time_idx on _hyper_5_155_chunk\n         Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000'::timestamp without time zone) AND (\"time\" <= 'Tue Jan 11 00:00:00 2000'::timestamp without time zone))\n         Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000'::timestamp without time zone) AND (time_bucket('@ 1 day'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000'::timestamp without time zone))\n   ->  Index Only Scan Backward using _hyper_5_156_chunk_metrics_timestamp_time_idx on _hyper_5_156_chunk\n         Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000'::timestamp without time zone) AND (\"time\" <= 'Tue Jan 11 00:00:00 2000'::timestamp without time zone))\n         Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000'::timestamp without time zone) AND (time_bucket('@ 1 day'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000'::timestamp without time zone))\n\n\\qecho time_bucket exclusion with timestamptz\ntime_bucket exclusion with timestamptz\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('6h',time) < '2000-01-03' ORDER BY time;\n--- QUERY PLAN ---\n Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n   Index Cond: (\"time\" < 'Mon Jan 03 06:00:00 2000 PST'::timestamp with time zone)\n   Filter: (time_bucket('@ 6 hours'::interval, \"time\") < 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('6h',time) >= '2000-01-03' AND time_bucket('6h',time) <= '2000-01-10' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" <= 'Mon Jan 10 06:00:00 2000 PST'::timestamp with time zone))\n         Filter: ((time_bucket('@ 6 hours'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 6 hours'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" <= 'Mon Jan 10 06:00:00 2000 PST'::timestamp with time zone))\n         Filter: ((time_bucket('@ 6 hours'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 6 hours'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho time_bucket exclusion with timestamptz and day interval\ntime_bucket exclusion with timestamptz and day interval\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('1d',time) < '2000-01-03' ORDER BY time;\n--- QUERY PLAN ---\n Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n   Index Cond: (\"time\" < 'Tue Jan 04 00:00:00 2000 PST'::timestamp with time zone)\n   Filter: (time_bucket('@ 1 day'::interval, \"time\") < 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('1d',time) >= '2000-01-03' AND time_bucket('1d',time) <= '2000-01-10' ORDER BY time;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_timestamptz\n   Order: metrics_timestamptz.\"time\"\n   ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n         Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" <= 'Tue Jan 11 00:00:00 2000 PST'::timestamp with time zone))\n         Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 1 day'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk\n         Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" <= 'Tue Jan 11 00:00:00 2000 PST'::timestamp with time zone))\n         Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 1 day'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('1d',time) >= '2000-01-03' AND time_bucket('7d',time) <= '2000-01-10' ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: metrics_timestamptz.\"time\"\n   ->  Append\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk\n               Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" <= 'Mon Jan 17 00:00:00 2000 PST'::timestamp with time zone))\n               Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 7 days'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Seq Scan on _hyper_6_161_chunk\n               Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 7 days'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk\n               Index Cond: ((\"time\" >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" <= 'Mon Jan 17 00:00:00 2000 PST'::timestamp with time zone))\n               Filter: ((time_bucket('@ 1 day'::interval, \"time\") >= 'Mon Jan 03 00:00:00 2000 PST'::timestamp with time zone) AND (time_bucket('@ 7 days'::interval, \"time\") <= 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho no transformation\nno transformation\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10 + floor(random())::int, time) > 10 AND time_bucket(10 + floor(random())::int, time) < 100 AND time < 150 ORDER BY time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper.\"time\"\n   ->  Custom Scan (ChunkAppend) on hyper\n         Chunks excluded during startup: 0\n         ->  Seq Scan on _hyper_1_1_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_2_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_3_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_4_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_5_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_6_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_7_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_8_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_9_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_10_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_11_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_12_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_13_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_14_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n         ->  Seq Scan on _hyper_1_15_chunk\n               Filter: ((time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") > 10) AND (time_bucket(((10 + (floor(random()))::integer))::bigint, \"time\") < 100))\n\n\\qecho exclude chunks based on time column with partitioning function. This\nexclude chunks based on time column with partitioning function. This\n\\qecho transparently applies the time partitioning function on the time\ntransparently applies the time partitioning function on the time\n\\qecho value to be able to exclude chunks (similar to a closed dimension).\nvalue to be able to exclude chunks (similar to a closed dimension).\n:PREFIX SELECT * FROM hyper_timefunc WHERE time < 4 ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_timefunc.value\n   ->  Append\n         ->  Seq Scan on _hyper_4_124_chunk\n               Filter: (\"time\" < '4'::double precision)\n         ->  Seq Scan on _hyper_4_125_chunk\n               Filter: (\"time\" < '4'::double precision)\n         ->  Seq Scan on _hyper_4_126_chunk\n               Filter: (\"time\" < '4'::double precision)\n         ->  Seq Scan on _hyper_4_127_chunk\n               Filter: (\"time\" < '4'::double precision)\n\n\\qecho excluding based on time expression is currently unoptimized\nexcluding based on time expression is currently unoptimized\n:PREFIX SELECT * FROM hyper_timefunc WHERE unix_to_timestamp(time) < 'Wed Dec 31 16:00:04 1969 PST' ORDER BY value;\n--- QUERY PLAN ---\n Sort\n   Sort Key: hyper_timefunc.value\n   ->  Append\n         ->  Seq Scan on _hyper_4_124_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_125_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_126_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_127_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_128_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_129_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_130_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_131_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_132_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_133_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_134_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_135_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_136_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_137_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_138_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_139_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_140_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_141_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_142_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_143_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_144_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_145_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_146_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_147_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_148_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_149_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_150_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_151_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_152_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_153_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n         ->  Seq Scan on _hyper_4_154_chunk\n               Filter: (to_timestamp(\"time\") < 'Wed Dec 31 16:00:04 1969 PST'::timestamp with time zone)\n\n\\qecho test qual propagation for joins\ntest qual propagation for joins\nRESET constraint_exclusion;\n\\qecho nothing to propagate\nnothing to propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1, metrics_timestamptz_2 m2 WHERE m1.time = m2.time ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Left Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time ORDER BY m1.time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: m1.\"time\"\n   ->  Merge Right Join\n         Merge Cond: (m1.\"time\" = m2.\"time\")\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n               Order: m1.\"time\"\n               ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n               ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n               ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n         ->  Materialize\n               ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n                     Order: m2.\"time\"\n                     ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n                     ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n                     ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n                     ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n\\qecho OR constraints should not propagate\nOR constraints should not propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10' OR m1.time > '2001-01-01' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Filter: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) OR (\"time\" > 'Mon Jan 01 00:00:00 2001 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Filter: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) OR (\"time\" > 'Mon Jan 01 00:00:00 2001 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n\\qecho test single constraint\ntest single constraint\n\\qecho constraint should be on both scans\nconstraint should be on both scans\n\\qecho these will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\nthese will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1, metrics_timestamptz_2 m2 WHERE m1.time = m2.time AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Left Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho test 2 constraints on single relation\ntest 2 constraints on single relation\n\\qecho these will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\nthese will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1, metrics_timestamptz_2 m2 WHERE m1.time = m2.time AND m1.time > '2000-01-01' AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Nested Loop Left Join\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Append\n         ->  Index Only Scan using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n               Index Cond: (\"time\" = m1.\"time\")\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho test 2 constraints with 1 constraint on each relation\ntest 2 constraints with 1 constraint on each relation\n\\qecho these will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\nthese will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1, metrics_timestamptz_2 m2 WHERE m1.time = m2.time AND m1.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho test constraints in ON clause of INNER JOIN\ntest constraints in ON clause of INNER JOIN\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho test constraints in ON clause of LEFT JOIN\ntest constraints in ON clause of LEFT JOIN\n\\qecho must not propagate\nmust not propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Left Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho test constraints in ON clause of RIGHT JOIN\ntest constraints in ON clause of RIGHT JOIN\n\\qecho must not propagate\nmust not propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Gather Merge\n   Workers Planned: 2\n   ->  Sort\n         Sort Key: m1.\"time\"\n         ->  Parallel Hash Left Join\n               Hash Cond: (m2.\"time\" = m1.\"time\")\n               Join Filter: ((m2.\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (m2.\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Parallel Append\n                     ->  Parallel Seq Scan on _hyper_7_165_chunk m2_1\n                     ->  Parallel Seq Scan on _hyper_7_166_chunk m2_2\n                     ->  Parallel Seq Scan on _hyper_7_167_chunk m2_3\n                     ->  Parallel Seq Scan on _hyper_7_168_chunk m2_4\n                     ->  Parallel Seq Scan on _hyper_7_169_chunk m2_5\n                     ->  Parallel Seq Scan on _hyper_7_170_chunk m2_6\n               ->  Parallel Hash\n                     ->  Parallel Append\n                           ->  Parallel Seq Scan on _hyper_6_160_chunk m1_1\n                           ->  Parallel Seq Scan on _hyper_6_161_chunk m1_2\n                           ->  Parallel Seq Scan on _hyper_6_162_chunk m1_3\n                           ->  Parallel Seq Scan on _hyper_6_163_chunk m1_4\n                           ->  Parallel Seq Scan on _hyper_6_164_chunk m1_5\n\n\\qecho test equality condition not in ON clause\ntest equality condition not in ON clause\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON true WHERE m2.time = m1.time AND m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho test constraints not joined on\ntest constraints not joined on\n\\qecho device_id constraint must not propagate\ndevice_id constraint must not propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON true WHERE m2.time = m1.time AND m2.time < '2000-01-10' AND m1.device_id = 1 ORDER BY m1.time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: m1.\"time\"\n   ->  Nested Loop\n         ->  Append\n               ->  Seq Scan on _hyper_6_160_chunk m1_1\n                     Filter: (device_id = 1)\n               ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n                     Filter: (device_id = 1)\n         ->  Append\n               ->  Index Only Scan using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: (\"time\" = m1.\"time\")\n               ->  Index Only Scan using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" = m1.\"time\") AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n\n\\qecho test multiple join conditions\ntest multiple join conditions\n\\qecho device_id constraint should propagate\ndevice_id constraint should propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON true WHERE m2.time = m1.time AND m1.device_id = m2.device_id AND m2.time < '2000-01-10' AND m1.device_id = 1 ORDER BY m1.time;\n--- QUERY PLAN ---\n Sort\n   Sort Key: m1.\"time\"\n   ->  Nested Loop\n         ->  Append\n               ->  Seq Scan on _hyper_6_160_chunk m1_1\n                     Filter: (device_id = 1)\n               ->  Index Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n                     Filter: (device_id = 1)\n         ->  Append\n               ->  Index Scan using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: (\"time\" = m1.\"time\")\n                     Filter: (device_id = 1)\n               ->  Index Scan using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: ((\"time\" = m1.\"time\") AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n                     Filter: (device_id = 1)\n\n\\qecho test join with 3 tables\ntest join with 3 tables\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time INNER JOIN metrics_timestamptz m3 ON m2.time=m3.time WHERE m1.time > '2000-01-01' AND m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Nested Loop\n   ->  Merge Join\n         Merge Cond: (m1.\"time\" = m2.\"time\")\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n               Order: m1.\"time\"\n               ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n               ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n                     Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n         ->  Materialize\n               ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n                     Order: m2.\"time\"\n                     ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                           Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n                     ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                           Index Cond: ((\"time\" > 'Sat Jan 01 00:00:00 2000 PST'::timestamp with time zone) AND (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone))\n   ->  Append\n         ->  Index Only Scan using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m3_1\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m3_2\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m3_3\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m3_4\n               Index Cond: (\"time\" = m1.\"time\")\n         ->  Index Only Scan using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m3_5\n               Index Cond: (\"time\" = m1.\"time\")\n\n\\qecho test non-Const constraints\ntest non-Const constraints\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10'::text::timestamptz ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         Chunks excluded during startup: 3\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               Chunks excluded during startup: 4\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < ('2000-01-10'::cstring)::timestamp with time zone)\n\n\\qecho test now()\ntest now()\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < now() ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         Chunks excluded during startup: 0\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Index Cond: (\"time\" < now())\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < now())\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n               Index Cond: (\"time\" < now())\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n               Index Cond: (\"time\" < now())\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n               Index Cond: (\"time\" < now())\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               Chunks excluded during startup: 0\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n                     Index Cond: (\"time\" < now())\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n                     Index Cond: (\"time\" < now())\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n                     Index Cond: (\"time\" < now())\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n                     Index Cond: (\"time\" < now())\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n                     Index Cond: (\"time\" < now())\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n                     Index Cond: (\"time\" < now())\n\n\\qecho test volatile function\ntest volatile function\n\\qecho should not propagate\nshould not propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < clock_timestamp() ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         Chunks excluded during startup: 0\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n               Filter: (\"time\" < clock_timestamp())\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n               Order: m2.\"time\"\n               ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m2.time < clock_timestamp() ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m2.\"time\" = m1.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz_2 m2\n         Order: m2.\"time\"\n         Chunks excluded during startup: 0\n         ->  Index Only Scan Backward using _hyper_7_165_chunk_metrics_timestamptz_2_time_idx on _hyper_7_165_chunk m2_1\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk m2_2\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_7_167_chunk_metrics_timestamptz_2_time_idx on _hyper_7_167_chunk m2_3\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk m2_4\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_7_169_chunk_metrics_timestamptz_2_time_idx on _hyper_7_169_chunk m2_5\n               Filter: (\"time\" < clock_timestamp())\n         ->  Index Only Scan Backward using _hyper_7_170_chunk_metrics_timestamptz_2_time_idx on _hyper_7_170_chunk m2_6\n               Filter: (\"time\" < clock_timestamp())\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n               Order: m1.\"time\"\n               ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               ->  Index Only Scan Backward using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk m1_3\n               ->  Index Only Scan Backward using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk m1_4\n               ->  Index Only Scan Backward using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk m1_5\n\n\\qecho test JOINs with normal table\ntest JOINs with normal table\n\\qecho will not propagate because constraints are only added to hypertables\nwill not propagate because constraints are only added to hypertables\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN regular_timestamptz m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m1.\"time\" = m2.\"time\")\n   ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n         Order: m1.\"time\"\n         ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n         ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n               Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Sort\n         Sort Key: m2.\"time\"\n         ->  Seq Scan on regular_timestamptz m2\n\n\\qecho test JOINs with normal table\ntest JOINs with normal table\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN regular_timestamptz m2 ON m1.time = m2.time WHERE m2.time < '2000-01-10' ORDER BY m1.time;\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (m2.\"time\" = m1.\"time\")\n   ->  Sort\n         Sort Key: m2.\"time\"\n         ->  Seq Scan on regular_timestamptz m2\n               Filter: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on metrics_timestamptz m1\n               Order: m1.\"time\"\n               ->  Index Only Scan Backward using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk m1_1\n               ->  Index Only Scan Backward using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk m1_2\n                     Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n\n\\qecho test quals are not pushed into OUTER JOIN\ntest quals are not pushed into OUTER JOIN\nCREATE TABLE outer_join_1 (id int, name text,time timestamptz NOT NULL DEFAULT '2000-01-01');\nCREATE TABLE outer_join_2 (id int, name text,time timestamptz NOT NULL DEFAULT '2000-01-01');\nSELECT (SELECT table_name FROM create_hypertable(tbl, 'time')) FROM (VALUES ('outer_join_1'),('outer_join_2')) v(tbl);\n  table_name  \n--------------\n outer_join_1\n outer_join_2\n\nINSERT INTO outer_join_1 VALUES(1,'a'), (2,'b');\nINSERT INTO outer_join_2 VALUES(1,'a');\n:PREFIX SELECT one.id, two.name FROM outer_join_1 one LEFT OUTER JOIN outer_join_2 two ON one.id=two.id WHERE one.id=2;\n--- QUERY PLAN ---\n Nested Loop Left Join\n   ->  Seq Scan on _hyper_9_176_chunk one\n         Filter: (id = 2)\n   ->  Materialize\n         ->  Seq Scan on _hyper_10_177_chunk two\n               Filter: (id = 2)\n\n:PREFIX SELECT one.id, two.name FROM outer_join_2 two RIGHT OUTER JOIN outer_join_1 one ON one.id=two.id WHERE one.id=2;\n--- QUERY PLAN ---\n Nested Loop Left Join\n   ->  Seq Scan on _hyper_9_176_chunk one\n         Filter: (id = 2)\n   ->  Materialize\n         ->  Seq Scan on _hyper_10_177_chunk two\n               Filter: (id = 2)\n\nDROP TABLE outer_join_1;\nDROP TABLE outer_join_2;\n-- test UNION between regular table and hypertable\nSELECT time FROM regular_timestamptz UNION SELECT time FROM metrics_timestamptz ORDER BY 1;\n             time             \n------------------------------\n Sat Jan 01 00:00:00 2000 PST\n Sun Jan 02 00:00:00 2000 PST\n Mon Jan 03 00:00:00 2000 PST\n Tue Jan 04 00:00:00 2000 PST\n Wed Jan 05 00:00:00 2000 PST\n Thu Jan 06 00:00:00 2000 PST\n Fri Jan 07 00:00:00 2000 PST\n Sat Jan 08 00:00:00 2000 PST\n Sun Jan 09 00:00:00 2000 PST\n Mon Jan 10 00:00:00 2000 PST\n Tue Jan 11 00:00:00 2000 PST\n Wed Jan 12 00:00:00 2000 PST\n Thu Jan 13 00:00:00 2000 PST\n Fri Jan 14 00:00:00 2000 PST\n Sat Jan 15 00:00:00 2000 PST\n Sun Jan 16 00:00:00 2000 PST\n Mon Jan 17 00:00:00 2000 PST\n Tue Jan 18 00:00:00 2000 PST\n Wed Jan 19 00:00:00 2000 PST\n Thu Jan 20 00:00:00 2000 PST\n Fri Jan 21 00:00:00 2000 PST\n Sat Jan 22 00:00:00 2000 PST\n Sun Jan 23 00:00:00 2000 PST\n Mon Jan 24 00:00:00 2000 PST\n Tue Jan 25 00:00:00 2000 PST\n Wed Jan 26 00:00:00 2000 PST\n Thu Jan 27 00:00:00 2000 PST\n Fri Jan 28 00:00:00 2000 PST\n Sat Jan 29 00:00:00 2000 PST\n Sun Jan 30 00:00:00 2000 PST\n Mon Jan 31 00:00:00 2000 PST\n Tue Feb 01 00:00:00 2000 PST\n\n-- test UNION ALL between regular table and hypertable\nSELECT time FROM regular_timestamptz UNION ALL SELECT time FROM metrics_timestamptz ORDER BY 1;\n             time             \n------------------------------\n Sat Jan 01 00:00:00 2000 PST\n Sat Jan 01 00:00:00 2000 PST\n Sat Jan 01 00:00:00 2000 PST\n Sat Jan 01 00:00:00 2000 PST\n Sun Jan 02 00:00:00 2000 PST\n Sun Jan 02 00:00:00 2000 PST\n Sun Jan 02 00:00:00 2000 PST\n Sun Jan 02 00:00:00 2000 PST\n Mon Jan 03 00:00:00 2000 PST\n Mon Jan 03 00:00:00 2000 PST\n Mon Jan 03 00:00:00 2000 PST\n Mon Jan 03 00:00:00 2000 PST\n Tue Jan 04 00:00:00 2000 PST\n Tue Jan 04 00:00:00 2000 PST\n Tue Jan 04 00:00:00 2000 PST\n Tue Jan 04 00:00:00 2000 PST\n Wed Jan 05 00:00:00 2000 PST\n Wed Jan 05 00:00:00 2000 PST\n Wed Jan 05 00:00:00 2000 PST\n Wed Jan 05 00:00:00 2000 PST\n Thu Jan 06 00:00:00 2000 PST\n Thu Jan 06 00:00:00 2000 PST\n Thu Jan 06 00:00:00 2000 PST\n Thu Jan 06 00:00:00 2000 PST\n Fri Jan 07 00:00:00 2000 PST\n Fri Jan 07 00:00:00 2000 PST\n Fri Jan 07 00:00:00 2000 PST\n Fri Jan 07 00:00:00 2000 PST\n Sat Jan 08 00:00:00 2000 PST\n Sat Jan 08 00:00:00 2000 PST\n Sat Jan 08 00:00:00 2000 PST\n Sat Jan 08 00:00:00 2000 PST\n Sun Jan 09 00:00:00 2000 PST\n Sun Jan 09 00:00:00 2000 PST\n Sun Jan 09 00:00:00 2000 PST\n Sun Jan 09 00:00:00 2000 PST\n Mon Jan 10 00:00:00 2000 PST\n Mon Jan 10 00:00:00 2000 PST\n Mon Jan 10 00:00:00 2000 PST\n Mon Jan 10 00:00:00 2000 PST\n Tue Jan 11 00:00:00 2000 PST\n Tue Jan 11 00:00:00 2000 PST\n Tue Jan 11 00:00:00 2000 PST\n Tue Jan 11 00:00:00 2000 PST\n Wed Jan 12 00:00:00 2000 PST\n Wed Jan 12 00:00:00 2000 PST\n Wed Jan 12 00:00:00 2000 PST\n Wed Jan 12 00:00:00 2000 PST\n Thu Jan 13 00:00:00 2000 PST\n Thu Jan 13 00:00:00 2000 PST\n Thu Jan 13 00:00:00 2000 PST\n Thu Jan 13 00:00:00 2000 PST\n Fri Jan 14 00:00:00 2000 PST\n Fri Jan 14 00:00:00 2000 PST\n Fri Jan 14 00:00:00 2000 PST\n Fri Jan 14 00:00:00 2000 PST\n Sat Jan 15 00:00:00 2000 PST\n Sat Jan 15 00:00:00 2000 PST\n Sat Jan 15 00:00:00 2000 PST\n Sat Jan 15 00:00:00 2000 PST\n Sun Jan 16 00:00:00 2000 PST\n Sun Jan 16 00:00:00 2000 PST\n Sun Jan 16 00:00:00 2000 PST\n Sun Jan 16 00:00:00 2000 PST\n Mon Jan 17 00:00:00 2000 PST\n Mon Jan 17 00:00:00 2000 PST\n Mon Jan 17 00:00:00 2000 PST\n Mon Jan 17 00:00:00 2000 PST\n Tue Jan 18 00:00:00 2000 PST\n Tue Jan 18 00:00:00 2000 PST\n Tue Jan 18 00:00:00 2000 PST\n Tue Jan 18 00:00:00 2000 PST\n Wed Jan 19 00:00:00 2000 PST\n Wed Jan 19 00:00:00 2000 PST\n Wed Jan 19 00:00:00 2000 PST\n Wed Jan 19 00:00:00 2000 PST\n Thu Jan 20 00:00:00 2000 PST\n Thu Jan 20 00:00:00 2000 PST\n Thu Jan 20 00:00:00 2000 PST\n Thu Jan 20 00:00:00 2000 PST\n Fri Jan 21 00:00:00 2000 PST\n Fri Jan 21 00:00:00 2000 PST\n Fri Jan 21 00:00:00 2000 PST\n Fri Jan 21 00:00:00 2000 PST\n Sat Jan 22 00:00:00 2000 PST\n Sat Jan 22 00:00:00 2000 PST\n Sat Jan 22 00:00:00 2000 PST\n Sat Jan 22 00:00:00 2000 PST\n Sun Jan 23 00:00:00 2000 PST\n Sun Jan 23 00:00:00 2000 PST\n Sun Jan 23 00:00:00 2000 PST\n Sun Jan 23 00:00:00 2000 PST\n Mon Jan 24 00:00:00 2000 PST\n Mon Jan 24 00:00:00 2000 PST\n Mon Jan 24 00:00:00 2000 PST\n Mon Jan 24 00:00:00 2000 PST\n Tue Jan 25 00:00:00 2000 PST\n Tue Jan 25 00:00:00 2000 PST\n Tue Jan 25 00:00:00 2000 PST\n Tue Jan 25 00:00:00 2000 PST\n Wed Jan 26 00:00:00 2000 PST\n Wed Jan 26 00:00:00 2000 PST\n Wed Jan 26 00:00:00 2000 PST\n Wed Jan 26 00:00:00 2000 PST\n Thu Jan 27 00:00:00 2000 PST\n Thu Jan 27 00:00:00 2000 PST\n Thu Jan 27 00:00:00 2000 PST\n Thu Jan 27 00:00:00 2000 PST\n Fri Jan 28 00:00:00 2000 PST\n Fri Jan 28 00:00:00 2000 PST\n Fri Jan 28 00:00:00 2000 PST\n Fri Jan 28 00:00:00 2000 PST\n Sat Jan 29 00:00:00 2000 PST\n Sat Jan 29 00:00:00 2000 PST\n Sat Jan 29 00:00:00 2000 PST\n Sat Jan 29 00:00:00 2000 PST\n Sun Jan 30 00:00:00 2000 PST\n Sun Jan 30 00:00:00 2000 PST\n Sun Jan 30 00:00:00 2000 PST\n Sun Jan 30 00:00:00 2000 PST\n Mon Jan 31 00:00:00 2000 PST\n Mon Jan 31 00:00:00 2000 PST\n Mon Jan 31 00:00:00 2000 PST\n Mon Jan 31 00:00:00 2000 PST\n Tue Feb 01 00:00:00 2000 PST\n Tue Feb 01 00:00:00 2000 PST\n Tue Feb 01 00:00:00 2000 PST\n Tue Feb 01 00:00:00 2000 PST\n\n-- test nested join qual propagation\n:PREFIX\nSELECT * FROM (\nSELECT o1_m1.time FROM metrics_timestamptz o1_m1 INNER JOIN metrics_timestamptz_2 o1_m2 ON true WHERE o1_m2.time = o1_m1.time AND o1_m1.device_id = o1_m2.device_id AND o1_m2.time < '2000-01-10' AND o1_m1.device_id = 1\n) o1 FULL OUTER JOIN (\nSELECT o2_m1.time FROM metrics_timestamptz o2_m1 FULL OUTER JOIN metrics_timestamptz_2 o2_m2 ON true WHERE o2_m2.time = o2_m1.time AND o2_m1.device_id = o2_m2.device_id AND o2_m2.time > '2000-01-20' AND o2_m1.device_id = 2\n) o2 ON o1.time = o2.time ORDER BY 1,2;\n--- QUERY PLAN ---\n Sort\n   Sort Key: o1_m1.\"time\", o2_m1.\"time\"\n   ->  Hash Full Join\n         Hash Cond: (o2_m1.\"time\" = o1_m1.\"time\")\n         ->  Nested Loop\n               ->  Append\n                     ->  Index Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk o2_m2_1\n                           Index Cond: (\"time\" > 'Thu Jan 20 00:00:00 2000 PST'::timestamp with time zone)\n                           Filter: (device_id = 2)\n                     ->  Seq Scan on _hyper_7_169_chunk o2_m2_2\n                           Filter: (device_id = 2)\n                     ->  Seq Scan on _hyper_7_170_chunk o2_m2_3\n                           Filter: (device_id = 2)\n               ->  Append\n                     ->  Index Scan using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk o2_m1_1\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk o2_m1_2\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk o2_m1_3\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk o2_m1_4\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk o2_m1_5\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n         ->  Hash\n               ->  Nested Loop\n                     ->  Append\n                           ->  Seq Scan on _hyper_7_165_chunk o1_m2_1\n                                 Filter: (device_id = 1)\n                           ->  Index Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk o1_m2_2\n                                 Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n                                 Filter: (device_id = 1)\n                     ->  Append\n                           ->  Index Scan using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk o1_m1_1\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk o1_m1_2\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk o1_m1_3\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk o1_m1_4\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk o1_m1_5\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n\n:PREFIX\nSELECT * FROM (\nSELECT o1_m1.time FROM metrics_timestamptz o1_m1 INNER JOIN metrics_timestamptz_2 o1_m2 ON o1_m2.time = o1_m1.time AND o1_m1.device_id = o1_m2.device_id WHERE o1_m2.time < '2000-01-10' AND o1_m1.device_id = 1\n) o1 FULL OUTER JOIN (\nSELECT o2_m1.time FROM metrics_timestamptz o2_m1 FULL OUTER JOIN metrics_timestamptz_2 o2_m2 ON o2_m2.time = o2_m1.time AND o2_m1.device_id = o2_m2.device_id WHERE o2_m2.time > '2000-01-20' AND o2_m1.device_id = 2\n) o2 ON o1.time = o2.time ORDER BY 1,2;\n--- QUERY PLAN ---\n Sort\n   Sort Key: o1_m1.\"time\", o2_m1.\"time\"\n   ->  Hash Full Join\n         Hash Cond: (o2_m1.\"time\" = o1_m1.\"time\")\n         ->  Nested Loop\n               ->  Append\n                     ->  Index Scan Backward using _hyper_7_168_chunk_metrics_timestamptz_2_time_idx on _hyper_7_168_chunk o2_m2_1\n                           Index Cond: (\"time\" > 'Thu Jan 20 00:00:00 2000 PST'::timestamp with time zone)\n                           Filter: (device_id = 2)\n                     ->  Seq Scan on _hyper_7_169_chunk o2_m2_2\n                           Filter: (device_id = 2)\n                     ->  Seq Scan on _hyper_7_170_chunk o2_m2_3\n                           Filter: (device_id = 2)\n               ->  Append\n                     ->  Index Scan using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk o2_m1_1\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk o2_m1_2\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk o2_m1_3\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk o2_m1_4\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n                     ->  Index Scan using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk o2_m1_5\n                           Index Cond: (\"time\" = o2_m2.\"time\")\n                           Filter: (device_id = 2)\n         ->  Hash\n               ->  Nested Loop\n                     ->  Append\n                           ->  Seq Scan on _hyper_7_165_chunk o1_m2_1\n                                 Filter: (device_id = 1)\n                           ->  Index Scan Backward using _hyper_7_166_chunk_metrics_timestamptz_2_time_idx on _hyper_7_166_chunk o1_m2_2\n                                 Index Cond: (\"time\" < 'Mon Jan 10 00:00:00 2000 PST'::timestamp with time zone)\n                                 Filter: (device_id = 1)\n                     ->  Append\n                           ->  Index Scan using _hyper_6_160_chunk_metrics_timestamptz_time_idx on _hyper_6_160_chunk o1_m1_1\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_161_chunk_metrics_timestamptz_time_idx on _hyper_6_161_chunk o1_m1_2\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_162_chunk_metrics_timestamptz_time_idx on _hyper_6_162_chunk o1_m1_3\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_163_chunk_metrics_timestamptz_time_idx on _hyper_6_163_chunk o1_m1_4\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n                           ->  Index Scan using _hyper_6_164_chunk_metrics_timestamptz_time_idx on _hyper_6_164_chunk o1_m1_5\n                                 Index Cond: (\"time\" = o1_m2.\"time\")\n                                 Filter: (device_id = 1)\n\n\\set ECHO errors\nRESET timescaledb.enable_optimizations;\nCREATE TABLE t(time timestamptz NOT NULL);\nSELECT table_name FROM create_hypertable('t','time');\n table_name \n------------\n t\n\nINSERT INTO t VALUES ('2000-01-01'), ('2010-01-01'), ('2020-01-01');\nEXPLAIN (buffers off, costs off) SELECT * FROM t t1 INNER JOIN t t2 ON t1.time = t2.time WHERE t1.time < timestamptz '2010-01-01';\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (t1.\"time\" = t2.\"time\")\n   ->  Merge Append\n         Sort Key: t1.\"time\"\n         ->  Index Only Scan Backward using _hyper_15_182_chunk_t_time_idx on _hyper_15_182_chunk t1_1\n         ->  Index Only Scan Backward using _hyper_15_183_chunk_t_time_idx on _hyper_15_183_chunk t1_2\n               Index Cond: (\"time\" < 'Fri Jan 01 00:00:00 2010 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Merge Append\n               Sort Key: t2.\"time\"\n               ->  Index Only Scan Backward using _hyper_15_182_chunk_t_time_idx on _hyper_15_182_chunk t2_1\n               ->  Index Only Scan Backward using _hyper_15_183_chunk_t_time_idx on _hyper_15_183_chunk t2_2\n                     Index Cond: (\"time\" < 'Fri Jan 01 00:00:00 2010 PST'::timestamp with time zone)\n\nSET timescaledb.enable_qual_propagation TO false;\nEXPLAIN (buffers off, costs off) SELECT * FROM t t1 INNER JOIN t t2 ON t1.time = t2.time WHERE t1.time < timestamptz '2010-01-01';\n--- QUERY PLAN ---\n Merge Join\n   Merge Cond: (t1.\"time\" = t2.\"time\")\n   ->  Merge Append\n         Sort Key: t1.\"time\"\n         ->  Index Only Scan Backward using _hyper_15_182_chunk_t_time_idx on _hyper_15_182_chunk t1_1\n         ->  Index Only Scan Backward using _hyper_15_183_chunk_t_time_idx on _hyper_15_183_chunk t1_2\n               Index Cond: (\"time\" < 'Fri Jan 01 00:00:00 2010 PST'::timestamp with time zone)\n   ->  Materialize\n         ->  Merge Append\n               Sort Key: t2.\"time\"\n               ->  Index Only Scan Backward using _hyper_15_182_chunk_t_time_idx on _hyper_15_182_chunk t2_1\n               ->  Index Only Scan Backward using _hyper_15_183_chunk_t_time_idx on _hyper_15_183_chunk t2_2\n               ->  Index Only Scan Backward using _hyper_15_184_chunk_t_time_idx on _hyper_15_184_chunk t2_3\n\nRESET timescaledb.enable_qual_propagation;\nCREATE TABLE test (a int, time timestamptz NOT NULL);\nSELECT table_name FROM create_hypertable('public.test', 'time');\n table_name \n------------\n test\n\nINSERT INTO test SELECT i, '2020-04-01'::date-10-i from generate_series(1,20) i;\nCREATE OR REPLACE FUNCTION test_f(_ts timestamptz)\nRETURNS SETOF test LANGUAGE SQL STABLE PARALLEL SAFE\nAS $f$\n   SELECT DISTINCT ON (a) * FROM test WHERE time >= _ts ORDER BY a, time DESC\n$f$;\nEXPLAIN (buffers off, costs off) SELECT * FROM test_f(now());\n--- QUERY PLAN ---\n Unique\n   ->  Sort\n         Sort Key: test.a, test.\"time\" DESC\n         ->  Custom Scan (ChunkAppend) on test\n               Chunks excluded during startup: 4\n\nEXPLAIN (buffers off, costs off) SELECT * FROM test_f(now());\n--- QUERY PLAN ---\n Unique\n   ->  Sort\n         Sort Key: test.a, test.\"time\" DESC\n         ->  Custom Scan (ChunkAppend) on test\n               Chunks excluded during startup: 4\n\nCREATE TABLE t1 (a int, b int NOT NULL);\nSELECT create_hypertable('t1', 'b', chunk_time_interval=>10);\n create_hypertable \n-------------------\n (17,public,t1,t)\n\nCREATE TABLE t2 (a int, b int NOT NULL);\nSELECT create_hypertable('t2', 'b', chunk_time_interval=>10);\n create_hypertable \n-------------------\n (18,public,t2,t)\n\nCREATE OR REPLACE FUNCTION f_t1(_a int, _b int)\n RETURNS SETOF t1\n LANGUAGE SQL\n STABLE PARALLEL SAFE\nAS $function$\n   SELECT DISTINCT ON (a) * FROM t1 WHERE a = _a and b = _b ORDER BY a, b DESC\n$function$\n;\nCREATE OR REPLACE FUNCTION f_t2(_a int, _b int) RETURNS SETOF t2 LANGUAGE sql STABLE PARALLEL SAFE\nAS $function$\n   SELECT DISTINCT ON (j.a) j.*\n   FROM\n      f_t1(_a, _b) sc,\n      t2 j\n   WHERE\n      j.b = _b AND\n      j.a = _a\n   ORDER BY j.a, j.b DESC\n$function$\n;\nCREATE OR REPLACE FUNCTION f_t1_2(_b int) RETURNS SETOF t1 LANGUAGE SQL STABLE PARALLEL SAFE\nAS $function$\n   SELECT DISTINCT ON (j.a) jt.* FROM t1 j, f_t1(j.a, _b) jt\n$function$;\nEXPLAIN (buffers off, costs off) SELECT * FROM f_t1_2(10);\n--- QUERY PLAN ---\n Subquery Scan on f_t1_2\n   ->  Unique\n         ->  Sort\n               Sort Key: j.a\n               ->  Nested Loop\n                     ->  Seq Scan on t1 j\n                     ->  Limit\n                           ->  Index Scan using t1_b_idx on t1\n                                 Index Cond: (b = 10)\n                                 Filter: (a = j.a)\n\nEXPLAIN (buffers off, costs off) SELECT * FROM f_t1_2(10) sc, f_t2(sc.a, 10);\n--- QUERY PLAN ---\n Nested Loop\n   ->  Unique\n         ->  Sort\n               Sort Key: j.a\n               ->  Nested Loop\n                     ->  Seq Scan on t1 j\n                     ->  Limit\n                           ->  Index Scan using t1_b_idx on t1\n                                 Index Cond: (b = 10)\n                                 Filter: (a = j.a)\n   ->  Limit\n         ->  Nested Loop\n               ->  Limit\n                     ->  Index Scan using t1_b_idx on t1 t1_1\n                           Index Cond: (b = 10)\n                           Filter: (a = t1.a)\n               ->  Index Scan using t2_b_idx on t2 j_1\n                     Index Cond: (b = 10)\n                     Filter: (a = t1.a)\n\nCREATE TABLE metrics_int1(time int, device text, value float) WITH (tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval=1);\nINSERT INTO metrics_int1 SELECT i, i::text, i FROM generate_series(3,7) i;\nSELECT tableoid::regclass, time FROM metrics_int1 ORDER BY time;\n                 tableoid                  | time \n-------------------------------------------+------\n _timescaledb_internal._hyper_19_189_chunk |    3\n _timescaledb_internal._hyper_19_190_chunk |    4\n _timescaledb_internal._hyper_19_191_chunk |    5\n _timescaledb_internal._hyper_19_192_chunk |    6\n _timescaledb_internal._hyper_19_193_chunk |    7\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time >= 2;\n--- QUERY PLAN ---\n Append (actual rows=5.00 loops=1)\n   ->  Seq Scan on _hyper_19_189_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_190_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_192_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_193_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time >= 1 AND time >= 2;\n--- QUERY PLAN ---\n Append (actual rows=5.00 loops=1)\n   ->  Seq Scan on _hyper_19_189_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_190_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_192_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_193_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time >= 4;\n--- QUERY PLAN ---\n Append (actual rows=4.00 loops=1)\n   ->  Seq Scan on _hyper_19_190_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_192_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_193_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time > 4 AND time < 6;\n--- QUERY PLAN ---\n Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time > 4 AND time <= 6;\n--- QUERY PLAN ---\n Append (actual rows=2.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_192_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time >= 4 AND time < 6;\n--- QUERY PLAN ---\n Append (actual rows=2.00 loops=1)\n   ->  Seq Scan on _hyper_19_190_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time >= 4 AND time <= 6;\n--- QUERY PLAN ---\n Append (actual rows=3.00 loops=1)\n   ->  Seq Scan on _hyper_19_190_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_192_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time BETWEEN 4 AND 5;\n--- QUERY PLAN ---\n Append (actual rows=2.00 loops=1)\n   ->  Seq Scan on _hyper_19_190_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_int1 WHERE time = 5;\n--- QUERY PLAN ---\n Seq Scan on _hyper_19_191_chunk (actual rows=1.00 loops=1)\n\nSET TIMEZONE='UTC';\nCREATE TABLE metrics_tstz(time timestamptz, device text, value float) WITH (tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval='1day');\nINSERT INTO metrics_tstz SELECT '2000-01-01'::timestamptz + format('%s day',i)::interval, i::text, i FROM generate_series(2,6) i;\nSELECT tableoid::regclass, time FROM metrics_tstz ORDER BY time;\n                 tableoid                  |             time             \n-------------------------------------------+------------------------------\n _timescaledb_internal._hyper_20_194_chunk | Mon Jan 03 00:00:00 2000 UTC\n _timescaledb_internal._hyper_20_195_chunk | Tue Jan 04 00:00:00 2000 UTC\n _timescaledb_internal._hyper_20_196_chunk | Wed Jan 05 00:00:00 2000 UTC\n _timescaledb_internal._hyper_20_197_chunk | Thu Jan 06 00:00:00 2000 UTC\n _timescaledb_internal._hyper_20_198_chunk | Fri Jan 07 00:00:00 2000 UTC\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-02';\n--- QUERY PLAN ---\n Append (actual rows=5.00 loops=1)\n   ->  Seq Scan on _hyper_20_194_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_198_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-01' AND time >= '2000-01-02';\n--- QUERY PLAN ---\n Append (actual rows=5.00 loops=1)\n   ->  Seq Scan on _hyper_20_194_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_198_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-04';\n--- QUERY PLAN ---\n Append (actual rows=4.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_198_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time > '2000-01-04' AND time < '2000-01-06';\n--- QUERY PLAN ---\n Append (actual rows=1.00 loops=1)\n   ->  Index Scan using _hyper_20_195_chunk_metrics_tstz_time_idx on _hyper_20_195_chunk (actual rows=0.00 loops=1)\n         Index Cond: ((\"time\" > 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" < 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone))\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time > '2000-01-04' AND time <= '2000-01-06';\n--- QUERY PLAN ---\n Append (actual rows=2.00 loops=1)\n   ->  Index Scan using _hyper_20_195_chunk_metrics_tstz_time_idx on _hyper_20_195_chunk (actual rows=0.00 loops=1)\n         Index Cond: ((\"time\" > 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone))\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n   ->  Index Scan using _hyper_20_197_chunk_metrics_tstz_time_idx on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n         Index Cond: ((\"time\" > 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone))\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-04' AND time < '2000-01-06';\n--- QUERY PLAN ---\n Append (actual rows=2.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-04' AND time <= '2000-01-06';\n--- QUERY PLAN ---\n Append (actual rows=3.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n   ->  Index Scan using _hyper_20_197_chunk_metrics_tstz_time_idx on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n         Index Cond: ((\"time\" >= 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone))\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time BETWEEN '2000-01-04' AND '2000-01-05';\n--- QUERY PLAN ---\n Append (actual rows=2.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n   ->  Index Scan using _hyper_20_196_chunk_metrics_tstz_time_idx on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n         Index Cond: ((\"time\" >= 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Wed Jan 05 00:00:00 2000 UTC'::timestamp with time zone))\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time = '2000-01-05';\n--- QUERY PLAN ---\n Index Scan using _hyper_20_196_chunk_metrics_tstz_time_idx on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n   Index Cond: (\"time\" = 'Wed Jan 05 00:00:00 2000 UTC'::timestamp with time zone)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-04' AND time <= '2000-01-06' AND device = '5';\n--- QUERY PLAN ---\n Append (actual rows=1.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=0.00 loops=1)\n         Filter: (device = '5'::text)\n         Rows Removed by Filter: 1\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=0.00 loops=1)\n         Filter: (device = '5'::text)\n         Rows Removed by Filter: 1\n   ->  Index Scan using _hyper_20_197_chunk_metrics_tstz_time_idx on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n         Index Cond: ((\"time\" >= 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone))\n         Filter: (device = '5'::text)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-04' AND time <= '2000-01-06' AND device IS NOT NULL;\n--- QUERY PLAN ---\n Append (actual rows=3.00 loops=1)\n   ->  Seq Scan on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n         Filter: (device IS NOT NULL)\n   ->  Seq Scan on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n         Filter: (device IS NOT NULL)\n   ->  Index Scan using _hyper_20_197_chunk_metrics_tstz_time_idx on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n         Index Cond: ((\"time\" >= 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone))\n         Filter: (device IS NOT NULL)\n\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_tstz WHERE time >= '2000-01-04' AND time <= '2000-01-06' AND time < now();\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on metrics_tstz (actual rows=3.00 loops=1)\n   Chunks excluded during startup: 0\n   ->  Index Scan using _hyper_20_195_chunk_metrics_tstz_time_idx on _hyper_20_195_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" < now())\n   ->  Index Scan using _hyper_20_196_chunk_metrics_tstz_time_idx on _hyper_20_196_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" < now())\n   ->  Index Scan using _hyper_20_197_chunk_metrics_tstz_time_idx on _hyper_20_197_chunk (actual rows=1.00 loops=1)\n         Index Cond: ((\"time\" >= 'Tue Jan 04 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" <= 'Thu Jan 06 00:00:00 2000 UTC'::timestamp with time zone) AND (\"time\" < now()))\n\nCREATE TABLE metrics_space(time timestamptz, device text, value float) WITH (tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval='1day');\nSELECT add_dimension('metrics_space', 'device', 4);\n           add_dimension            \n------------------------------------\n (25,public,metrics_space,device,t)\n\nINSERT INTO metrics_space SELECT '2000-01-01'::timestamptz + format('%s day',i)::interval, i::text, i FROM generate_series(2,6) i;\nEXPLAIN (buffers off, costs off, timing off, summary off, analyze) SELECT * FROM metrics_space WHERE time >= '2000-01-02';\n--- QUERY PLAN ---\n Append (actual rows=5.00 loops=1)\n   ->  Index Scan using _hyper_21_199_chunk_metrics_space_time_idx on _hyper_21_199_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" >= 'Sun Jan 02 00:00:00 2000 UTC'::timestamp with time zone)\n   ->  Index Scan using _hyper_21_200_chunk_metrics_space_time_idx on _hyper_21_200_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" >= 'Sun Jan 02 00:00:00 2000 UTC'::timestamp with time zone)\n   ->  Index Scan using _hyper_21_201_chunk_metrics_space_time_idx on _hyper_21_201_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" >= 'Sun Jan 02 00:00:00 2000 UTC'::timestamp with time zone)\n   ->  Index Scan using _hyper_21_202_chunk_metrics_space_time_idx on _hyper_21_202_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" >= 'Sun Jan 02 00:00:00 2000 UTC'::timestamp with time zone)\n   ->  Index Scan using _hyper_21_203_chunk_metrics_space_time_idx on _hyper_21_203_chunk (actual rows=1.00 loops=1)\n         Index Cond: (\"time\" >= 'Sun Jan 02 00:00:00 2000 UTC'::timestamp with time zone)\n\n--TEST END--\n"
  },
  {
    "path": "test/expected/plan_hashagg-15.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nSET max_parallel_workers_per_gather TO 0;\n\\set PREFIX 'EXPLAIN (buffers off, costs off) '\n\\ir include/plan_hashagg_load.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE metric (id SERIAL PRIMARY KEY, value INT);\nCREATE TABLE hyper(time TIMESTAMP NOT NULL, time_int BIGINT, time_broken DATE, metricid int, value double precision);\nCREATE TABLE regular(time TIMESTAMP NOT NULL, time_int BIGINT, time_date DATE, metricid int, value double precision);\nSELECT create_hypertable('hyper', 'time', chunk_time_interval => interval '20 day', create_default_indexes=>FALSE);\npsql:include/plan_hashagg_load.sql:9: WARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n create_hypertable  \n--------------------\n (1,public,hyper,t)\n\nALTER TABLE hyper\nDROP COLUMN time_broken,\nADD COLUMN time_date DATE;\nINSERT INTO metric(value) SELECT random()*100 FROM generate_series(0,10);\nINSERT INTO hyper SELECT t,  EXTRACT(EPOCH FROM t), (EXTRACT(EPOCH FROM t)::int % 10)+1, 1.0, t::date FROM generate_series('2001-01-01', '2001-01-10', INTERVAL '1 second') t;\nINSERT INTO regular(time, time_int, time_date, metricid, value)\n  SELECT t,  EXTRACT(EPOCH FROM t), t::date, (EXTRACT(EPOCH FROM t)::int % 10) + 1, 1.0 FROM generate_series('2001-01-01', '2001-01-02', INTERVAL '1 second') t;\n--test some queries before analyze;\nEXPLAIN (buffers off, costs off) SELECT time_bucket('1 minute', time) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n Sort\n   Sort Key: (time_bucket('@ 1 min'::interval, _hyper_1_1_chunk.\"time\")) DESC\n   ->  HashAggregate\n         Group Key: time_bucket('@ 1 min'::interval, _hyper_1_1_chunk.\"time\")\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\nEXPLAIN (buffers off, costs off) SELECT date_trunc('minute', time) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n Sort\n   Sort Key: (date_trunc('minute'::text, _hyper_1_1_chunk.\"time\")) DESC\n   ->  HashAggregate\n         Group Key: date_trunc('minute'::text, _hyper_1_1_chunk.\"time\")\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n-- Test partitioning function on an open (time) dimension\nCREATE OR REPLACE FUNCTION unix_to_timestamp(unixtime float8)\n    RETURNS TIMESTAMPTZ LANGUAGE SQL IMMUTABLE AS\n$BODY$\n    SELECT to_timestamp(unixtime);\n$BODY$;\nCREATE TABLE hyper_timefunc(time float8 NOT NULL, metricid int, VALUE double precision, time_date DATE);\nSELECT create_hypertable('hyper_timefunc', 'time', chunk_time_interval => interval '20 day', create_default_indexes=>FALSE, time_partitioning_func => 'unix_to_timestamp');\n      create_hypertable      \n-----------------------------\n (2,public,hyper_timefunc,t)\n\nINSERT INTO hyper_timefunc SELECT time_int, metricid, VALUE, time_date FROM hyper;\nANALYZE metric;\nANALYZE hyper;\nANALYZE regular;\nANALYZE hyper_timefunc;\n\\ir include/plan_hashagg_query.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n:PREFIX SELECT time_bucket('1 minute', time) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('@ 1 min'::interval, _hyper_1_1_chunk.\"time\"))\n   ->  Sort\n         Sort Key: (time_bucket('@ 1 min'::interval, _hyper_1_1_chunk.\"time\")) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n:PREFIX SELECT time_bucket('1 hour', time) AS MetricMinuteTs, metricid, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs, metricid\nORDER BY MetricMinuteTs DESC, metricid;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('@ 1 hour'::interval, _hyper_1_1_chunk.\"time\")), _hyper_1_1_chunk.metricid\n   ->  Sort\n         Sort Key: (time_bucket('@ 1 hour'::interval, _hyper_1_1_chunk.\"time\")) DESC, _hyper_1_1_chunk.metricid\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n--should be too many groups will not hashaggregate\n:PREFIX SELECT time_bucket('1 second', time) AS MetricMinuteTs, metricid, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs, metricid\nORDER BY MetricMinuteTs DESC, metricid;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('@ 1 sec'::interval, _hyper_1_1_chunk.\"time\")), _hyper_1_1_chunk.metricid\n   ->  Sort\n         Sort Key: (time_bucket('@ 1 sec'::interval, _hyper_1_1_chunk.\"time\")) DESC, _hyper_1_1_chunk.metricid\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n:PREFIX SELECT time_bucket('1 minute', time, INTERVAL '30 seconds') AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('@ 1 min'::interval, _hyper_1_1_chunk.\"time\", '@ 30 secs'::interval))\n   ->  Sort\n         Sort Key: (time_bucket('@ 1 min'::interval, _hyper_1_1_chunk.\"time\", '@ 30 secs'::interval)) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n:PREFIX SELECT time_bucket(60, time_int) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('60'::bigint, _hyper_1_1_chunk.time_int))\n   ->  Sort\n         Sort Key: (time_bucket('60'::bigint, _hyper_1_1_chunk.time_int)) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n:PREFIX SELECT time_bucket(60, time_int, 10) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('60'::bigint, _hyper_1_1_chunk.time_int, '10'::bigint))\n   ->  Sort\n         Sort Key: (time_bucket('60'::bigint, _hyper_1_1_chunk.time_int, '10'::bigint)) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n:PREFIX SELECT time_bucket('1 day', time_date) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n Sort\n   Sort Key: (time_bucket('@ 1 day'::interval, _hyper_1_1_chunk.time_date)) DESC\n   ->  HashAggregate\n         Group Key: time_bucket('@ 1 day'::interval, _hyper_1_1_chunk.time_date)\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n:PREFIX SELECT date_trunc('minute', time) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (date_trunc('minute'::text, _hyper_1_1_chunk.\"time\"))\n   ->  Sort\n         Sort Key: (date_trunc('minute'::text, _hyper_1_1_chunk.\"time\")) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n\\set ON_ERROR_STOP 0\n--can't optimize invalid time unit\n:PREFIX SELECT date_trunc('invalid', time) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (date_trunc('invalid'::text, _hyper_1_1_chunk.\"time\"))\n   ->  Sort\n         Sort Key: (date_trunc('invalid'::text, _hyper_1_1_chunk.\"time\")) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n\\set ON_ERROR_STOP 1\n:PREFIX SELECT date_trunc('day', time_date) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n Sort\n   Sort Key: (date_trunc('day'::text, (_hyper_1_1_chunk.time_date)::timestamp with time zone)) DESC\n   ->  HashAggregate\n         Group Key: date_trunc('day'::text, (_hyper_1_1_chunk.time_date)::timestamp with time zone)\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n--joins\n--with hypertable, optimize\n:PREFIX SELECT time_bucket(3600, time_int, 10) AS MetricMinuteTs, metric.value, AVG(hyper.value) as avg\nFROM hyper\nJOIN metric ON (hyper.metricid = metric.id)\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs, metric.id\nORDER BY MetricMinuteTs DESC, metric.id;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('3600'::bigint, _hyper_1_1_chunk.time_int, '10'::bigint)), metric.id\n   ->  Sort\n         Sort Key: (time_bucket('3600'::bigint, _hyper_1_1_chunk.time_int, '10'::bigint)) DESC, metric.id\n         ->  Hash Join\n               Hash Cond: (_hyper_1_1_chunk.metricid = metric.id)\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n               ->  Hash\n                     ->  Seq Scan on metric\n\n--no hypertable involved, no optimization\n:PREFIX SELECT time_bucket(3600, time_int, 10) AS MetricMinuteTs, metric.value, AVG(regular.value) as avg\nFROM regular\nJOIN metric ON (regular.metricid = metric.id)\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs, metric.id\nORDER BY MetricMinuteTs DESC, metric.id;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('3600'::bigint, regular.time_int, '10'::bigint)), metric.id\n   ->  Sort\n         Sort Key: (time_bucket('3600'::bigint, regular.time_int, '10'::bigint)) DESC, metric.id\n         ->  Nested Loop\n               Join Filter: (regular.metricid = metric.id)\n               ->  Seq Scan on regular\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n               ->  Seq Scan on metric\n\n-- Try with time partitioning function. Currently not optimized for hash aggregates\n:PREFIX SELECT time_bucket('1 minute', unix_to_timestamp(time)) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper_timefunc\nWHERE unix_to_timestamp(time) >= '2001-01-04T00:00:00' AND unix_to_timestamp(time) <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('@ 1 min'::interval, to_timestamp(_hyper_2_2_chunk.\"time\")))\n   ->  Sort\n         Sort Key: (time_bucket('@ 1 min'::interval, to_timestamp(_hyper_2_2_chunk.\"time\"))) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_2_2_chunk\n                     Filter: ((to_timestamp(\"time\") >= 'Thu Jan 04 00:00:00 2001 PST'::timestamp with time zone) AND (to_timestamp(\"time\") <= 'Fri Jan 05 01:00:00 2001 PST'::timestamp with time zone))\n\n\\set ECHO none\npsql:include/plan_hashagg_query.sql:60: ERROR:  unit \"invalid\" not recognized for type timestamp without time zone\npsql:include/plan_hashagg_query.sql:60: ERROR:  unit \"invalid\" not recognized for type timestamp without time zone\n"
  },
  {
    "path": "test/expected/plan_hashagg-16.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nSET max_parallel_workers_per_gather TO 0;\n\\set PREFIX 'EXPLAIN (buffers off, costs off) '\n\\ir include/plan_hashagg_load.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE metric (id SERIAL PRIMARY KEY, value INT);\nCREATE TABLE hyper(time TIMESTAMP NOT NULL, time_int BIGINT, time_broken DATE, metricid int, value double precision);\nCREATE TABLE regular(time TIMESTAMP NOT NULL, time_int BIGINT, time_date DATE, metricid int, value double precision);\nSELECT create_hypertable('hyper', 'time', chunk_time_interval => interval '20 day', create_default_indexes=>FALSE);\npsql:include/plan_hashagg_load.sql:9: WARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n create_hypertable  \n--------------------\n (1,public,hyper,t)\n\nALTER TABLE hyper\nDROP COLUMN time_broken,\nADD COLUMN time_date DATE;\nINSERT INTO metric(value) SELECT random()*100 FROM generate_series(0,10);\nINSERT INTO hyper SELECT t,  EXTRACT(EPOCH FROM t), (EXTRACT(EPOCH FROM t)::int % 10)+1, 1.0, t::date FROM generate_series('2001-01-01', '2001-01-10', INTERVAL '1 second') t;\nINSERT INTO regular(time, time_int, time_date, metricid, value)\n  SELECT t,  EXTRACT(EPOCH FROM t), t::date, (EXTRACT(EPOCH FROM t)::int % 10) + 1, 1.0 FROM generate_series('2001-01-01', '2001-01-02', INTERVAL '1 second') t;\n--test some queries before analyze;\nEXPLAIN (buffers off, costs off) SELECT time_bucket('1 minute', time) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n Sort\n   Sort Key: (time_bucket('@ 1 min'::interval, _hyper_1_1_chunk.\"time\")) DESC\n   ->  HashAggregate\n         Group Key: time_bucket('@ 1 min'::interval, _hyper_1_1_chunk.\"time\")\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\nEXPLAIN (buffers off, costs off) SELECT date_trunc('minute', time) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n Sort\n   Sort Key: (date_trunc('minute'::text, _hyper_1_1_chunk.\"time\")) DESC\n   ->  HashAggregate\n         Group Key: date_trunc('minute'::text, _hyper_1_1_chunk.\"time\")\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n-- Test partitioning function on an open (time) dimension\nCREATE OR REPLACE FUNCTION unix_to_timestamp(unixtime float8)\n    RETURNS TIMESTAMPTZ LANGUAGE SQL IMMUTABLE AS\n$BODY$\n    SELECT to_timestamp(unixtime);\n$BODY$;\nCREATE TABLE hyper_timefunc(time float8 NOT NULL, metricid int, VALUE double precision, time_date DATE);\nSELECT create_hypertable('hyper_timefunc', 'time', chunk_time_interval => interval '20 day', create_default_indexes=>FALSE, time_partitioning_func => 'unix_to_timestamp');\n      create_hypertable      \n-----------------------------\n (2,public,hyper_timefunc,t)\n\nINSERT INTO hyper_timefunc SELECT time_int, metricid, VALUE, time_date FROM hyper;\nANALYZE metric;\nANALYZE hyper;\nANALYZE regular;\nANALYZE hyper_timefunc;\n\\ir include/plan_hashagg_query.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n:PREFIX SELECT time_bucket('1 minute', time) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('@ 1 min'::interval, _hyper_1_1_chunk.\"time\"))\n   ->  Sort\n         Sort Key: (time_bucket('@ 1 min'::interval, _hyper_1_1_chunk.\"time\")) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n:PREFIX SELECT time_bucket('1 hour', time) AS MetricMinuteTs, metricid, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs, metricid\nORDER BY MetricMinuteTs DESC, metricid;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('@ 1 hour'::interval, _hyper_1_1_chunk.\"time\")), _hyper_1_1_chunk.metricid\n   ->  Sort\n         Sort Key: (time_bucket('@ 1 hour'::interval, _hyper_1_1_chunk.\"time\")) DESC, _hyper_1_1_chunk.metricid\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n--should be too many groups will not hashaggregate\n:PREFIX SELECT time_bucket('1 second', time) AS MetricMinuteTs, metricid, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs, metricid\nORDER BY MetricMinuteTs DESC, metricid;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('@ 1 sec'::interval, _hyper_1_1_chunk.\"time\")), _hyper_1_1_chunk.metricid\n   ->  Sort\n         Sort Key: (time_bucket('@ 1 sec'::interval, _hyper_1_1_chunk.\"time\")) DESC, _hyper_1_1_chunk.metricid\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n:PREFIX SELECT time_bucket('1 minute', time, INTERVAL '30 seconds') AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('@ 1 min'::interval, _hyper_1_1_chunk.\"time\", '@ 30 secs'::interval))\n   ->  Sort\n         Sort Key: (time_bucket('@ 1 min'::interval, _hyper_1_1_chunk.\"time\", '@ 30 secs'::interval)) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n:PREFIX SELECT time_bucket(60, time_int) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('60'::bigint, _hyper_1_1_chunk.time_int))\n   ->  Sort\n         Sort Key: (time_bucket('60'::bigint, _hyper_1_1_chunk.time_int)) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n:PREFIX SELECT time_bucket(60, time_int, 10) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('60'::bigint, _hyper_1_1_chunk.time_int, '10'::bigint))\n   ->  Sort\n         Sort Key: (time_bucket('60'::bigint, _hyper_1_1_chunk.time_int, '10'::bigint)) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n:PREFIX SELECT time_bucket('1 day', time_date) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n Sort\n   Sort Key: (time_bucket('@ 1 day'::interval, _hyper_1_1_chunk.time_date)) DESC\n   ->  HashAggregate\n         Group Key: time_bucket('@ 1 day'::interval, _hyper_1_1_chunk.time_date)\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n:PREFIX SELECT date_trunc('minute', time) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (date_trunc('minute'::text, _hyper_1_1_chunk.\"time\"))\n   ->  Sort\n         Sort Key: (date_trunc('minute'::text, _hyper_1_1_chunk.\"time\")) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n\\set ON_ERROR_STOP 0\n--can't optimize invalid time unit\n:PREFIX SELECT date_trunc('invalid', time) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (date_trunc('invalid'::text, _hyper_1_1_chunk.\"time\"))\n   ->  Sort\n         Sort Key: (date_trunc('invalid'::text, _hyper_1_1_chunk.\"time\")) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n\\set ON_ERROR_STOP 1\n:PREFIX SELECT date_trunc('day', time_date) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n Sort\n   Sort Key: (date_trunc('day'::text, (_hyper_1_1_chunk.time_date)::timestamp with time zone)) DESC\n   ->  HashAggregate\n         Group Key: date_trunc('day'::text, (_hyper_1_1_chunk.time_date)::timestamp with time zone)\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n--joins\n--with hypertable, optimize\n:PREFIX SELECT time_bucket(3600, time_int, 10) AS MetricMinuteTs, metric.value, AVG(hyper.value) as avg\nFROM hyper\nJOIN metric ON (hyper.metricid = metric.id)\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs, metric.id\nORDER BY MetricMinuteTs DESC, metric.id;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('3600'::bigint, _hyper_1_1_chunk.time_int, '10'::bigint)), metric.id\n   ->  Sort\n         Sort Key: (time_bucket('3600'::bigint, _hyper_1_1_chunk.time_int, '10'::bigint)) DESC, metric.id\n         ->  Hash Join\n               Hash Cond: (_hyper_1_1_chunk.metricid = metric.id)\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n               ->  Hash\n                     ->  Seq Scan on metric\n\n--no hypertable involved, no optimization\n:PREFIX SELECT time_bucket(3600, time_int, 10) AS MetricMinuteTs, metric.value, AVG(regular.value) as avg\nFROM regular\nJOIN metric ON (regular.metricid = metric.id)\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs, metric.id\nORDER BY MetricMinuteTs DESC, metric.id;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('3600'::bigint, regular.time_int, '10'::bigint)), metric.id\n   ->  Sort\n         Sort Key: (time_bucket('3600'::bigint, regular.time_int, '10'::bigint)) DESC, metric.id\n         ->  Nested Loop\n               Join Filter: (metric.id = regular.metricid)\n               ->  Seq Scan on regular\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n               ->  Seq Scan on metric\n\n-- Try with time partitioning function. Currently not optimized for hash aggregates\n:PREFIX SELECT time_bucket('1 minute', unix_to_timestamp(time)) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper_timefunc\nWHERE unix_to_timestamp(time) >= '2001-01-04T00:00:00' AND unix_to_timestamp(time) <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('@ 1 min'::interval, to_timestamp(_hyper_2_2_chunk.\"time\")))\n   ->  Sort\n         Sort Key: (time_bucket('@ 1 min'::interval, to_timestamp(_hyper_2_2_chunk.\"time\"))) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_2_2_chunk\n                     Filter: ((to_timestamp(\"time\") >= 'Thu Jan 04 00:00:00 2001 PST'::timestamp with time zone) AND (to_timestamp(\"time\") <= 'Fri Jan 05 01:00:00 2001 PST'::timestamp with time zone))\n\n\\set ECHO none\npsql:include/plan_hashagg_query.sql:60: ERROR:  unit \"invalid\" not recognized for type timestamp without time zone\npsql:include/plan_hashagg_query.sql:60: ERROR:  unit \"invalid\" not recognized for type timestamp without time zone\n"
  },
  {
    "path": "test/expected/plan_hashagg-17.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nSET max_parallel_workers_per_gather TO 0;\n\\set PREFIX 'EXPLAIN (buffers off, costs off) '\n\\ir include/plan_hashagg_load.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE metric (id SERIAL PRIMARY KEY, value INT);\nCREATE TABLE hyper(time TIMESTAMP NOT NULL, time_int BIGINT, time_broken DATE, metricid int, value double precision);\nCREATE TABLE regular(time TIMESTAMP NOT NULL, time_int BIGINT, time_date DATE, metricid int, value double precision);\nSELECT create_hypertable('hyper', 'time', chunk_time_interval => interval '20 day', create_default_indexes=>FALSE);\npsql:include/plan_hashagg_load.sql:9: WARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n create_hypertable  \n--------------------\n (1,public,hyper,t)\n\nALTER TABLE hyper\nDROP COLUMN time_broken,\nADD COLUMN time_date DATE;\nINSERT INTO metric(value) SELECT random()*100 FROM generate_series(0,10);\nINSERT INTO hyper SELECT t,  EXTRACT(EPOCH FROM t), (EXTRACT(EPOCH FROM t)::int % 10)+1, 1.0, t::date FROM generate_series('2001-01-01', '2001-01-10', INTERVAL '1 second') t;\nINSERT INTO regular(time, time_int, time_date, metricid, value)\n  SELECT t,  EXTRACT(EPOCH FROM t), t::date, (EXTRACT(EPOCH FROM t)::int % 10) + 1, 1.0 FROM generate_series('2001-01-01', '2001-01-02', INTERVAL '1 second') t;\n--test some queries before analyze;\nEXPLAIN (buffers off, costs off) SELECT time_bucket('1 minute', time) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n Sort\n   Sort Key: (time_bucket('@ 1 min'::interval, _hyper_1_1_chunk.\"time\")) DESC\n   ->  HashAggregate\n         Group Key: time_bucket('@ 1 min'::interval, _hyper_1_1_chunk.\"time\")\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\nEXPLAIN (buffers off, costs off) SELECT date_trunc('minute', time) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n Sort\n   Sort Key: (date_trunc('minute'::text, _hyper_1_1_chunk.\"time\")) DESC\n   ->  HashAggregate\n         Group Key: date_trunc('minute'::text, _hyper_1_1_chunk.\"time\")\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n-- Test partitioning function on an open (time) dimension\nCREATE OR REPLACE FUNCTION unix_to_timestamp(unixtime float8)\n    RETURNS TIMESTAMPTZ LANGUAGE SQL IMMUTABLE AS\n$BODY$\n    SELECT to_timestamp(unixtime);\n$BODY$;\nCREATE TABLE hyper_timefunc(time float8 NOT NULL, metricid int, VALUE double precision, time_date DATE);\nSELECT create_hypertable('hyper_timefunc', 'time', chunk_time_interval => interval '20 day', create_default_indexes=>FALSE, time_partitioning_func => 'unix_to_timestamp');\n      create_hypertable      \n-----------------------------\n (2,public,hyper_timefunc,t)\n\nINSERT INTO hyper_timefunc SELECT time_int, metricid, VALUE, time_date FROM hyper;\nANALYZE metric;\nANALYZE hyper;\nANALYZE regular;\nANALYZE hyper_timefunc;\n\\ir include/plan_hashagg_query.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n:PREFIX SELECT time_bucket('1 minute', time) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('@ 1 min'::interval, _hyper_1_1_chunk.\"time\"))\n   ->  Sort\n         Sort Key: (time_bucket('@ 1 min'::interval, _hyper_1_1_chunk.\"time\")) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n:PREFIX SELECT time_bucket('1 hour', time) AS MetricMinuteTs, metricid, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs, metricid\nORDER BY MetricMinuteTs DESC, metricid;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('@ 1 hour'::interval, _hyper_1_1_chunk.\"time\")), _hyper_1_1_chunk.metricid\n   ->  Sort\n         Sort Key: (time_bucket('@ 1 hour'::interval, _hyper_1_1_chunk.\"time\")) DESC, _hyper_1_1_chunk.metricid\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n--should be too many groups will not hashaggregate\n:PREFIX SELECT time_bucket('1 second', time) AS MetricMinuteTs, metricid, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs, metricid\nORDER BY MetricMinuteTs DESC, metricid;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('@ 1 sec'::interval, _hyper_1_1_chunk.\"time\")), _hyper_1_1_chunk.metricid\n   ->  Sort\n         Sort Key: (time_bucket('@ 1 sec'::interval, _hyper_1_1_chunk.\"time\")) DESC, _hyper_1_1_chunk.metricid\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n:PREFIX SELECT time_bucket('1 minute', time, INTERVAL '30 seconds') AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('@ 1 min'::interval, _hyper_1_1_chunk.\"time\", '@ 30 secs'::interval))\n   ->  Sort\n         Sort Key: (time_bucket('@ 1 min'::interval, _hyper_1_1_chunk.\"time\", '@ 30 secs'::interval)) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n:PREFIX SELECT time_bucket(60, time_int) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('60'::bigint, _hyper_1_1_chunk.time_int))\n   ->  Sort\n         Sort Key: (time_bucket('60'::bigint, _hyper_1_1_chunk.time_int)) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n:PREFIX SELECT time_bucket(60, time_int, 10) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('60'::bigint, _hyper_1_1_chunk.time_int, '10'::bigint))\n   ->  Sort\n         Sort Key: (time_bucket('60'::bigint, _hyper_1_1_chunk.time_int, '10'::bigint)) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n:PREFIX SELECT time_bucket('1 day', time_date) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n Sort\n   Sort Key: (time_bucket('@ 1 day'::interval, _hyper_1_1_chunk.time_date)) DESC\n   ->  HashAggregate\n         Group Key: time_bucket('@ 1 day'::interval, _hyper_1_1_chunk.time_date)\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n:PREFIX SELECT date_trunc('minute', time) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (date_trunc('minute'::text, _hyper_1_1_chunk.\"time\"))\n   ->  Sort\n         Sort Key: (date_trunc('minute'::text, _hyper_1_1_chunk.\"time\")) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n\\set ON_ERROR_STOP 0\n--can't optimize invalid time unit\n:PREFIX SELECT date_trunc('invalid', time) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (date_trunc('invalid'::text, _hyper_1_1_chunk.\"time\"))\n   ->  Sort\n         Sort Key: (date_trunc('invalid'::text, _hyper_1_1_chunk.\"time\")) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n\\set ON_ERROR_STOP 1\n:PREFIX SELECT date_trunc('day', time_date) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n Sort\n   Sort Key: (date_trunc('day'::text, (_hyper_1_1_chunk.time_date)::timestamp with time zone)) DESC\n   ->  HashAggregate\n         Group Key: date_trunc('day'::text, (_hyper_1_1_chunk.time_date)::timestamp with time zone)\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n--joins\n--with hypertable, optimize\n:PREFIX SELECT time_bucket(3600, time_int, 10) AS MetricMinuteTs, metric.value, AVG(hyper.value) as avg\nFROM hyper\nJOIN metric ON (hyper.metricid = metric.id)\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs, metric.id\nORDER BY MetricMinuteTs DESC, metric.id;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('3600'::bigint, _hyper_1_1_chunk.time_int, '10'::bigint)), metric.id\n   ->  Sort\n         Sort Key: (time_bucket('3600'::bigint, _hyper_1_1_chunk.time_int, '10'::bigint)) DESC, metric.id\n         ->  Hash Join\n               Hash Cond: (_hyper_1_1_chunk.metricid = metric.id)\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n               ->  Hash\n                     ->  Seq Scan on metric\n\n--no hypertable involved, no optimization\n:PREFIX SELECT time_bucket(3600, time_int, 10) AS MetricMinuteTs, metric.value, AVG(regular.value) as avg\nFROM regular\nJOIN metric ON (regular.metricid = metric.id)\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs, metric.id\nORDER BY MetricMinuteTs DESC, metric.id;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('3600'::bigint, regular.time_int, '10'::bigint)), metric.id\n   ->  Sort\n         Sort Key: (time_bucket('3600'::bigint, regular.time_int, '10'::bigint)) DESC, metric.id\n         ->  Nested Loop\n               Join Filter: (metric.id = regular.metricid)\n               ->  Seq Scan on regular\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n               ->  Seq Scan on metric\n\n-- Try with time partitioning function. Currently not optimized for hash aggregates\n:PREFIX SELECT time_bucket('1 minute', unix_to_timestamp(time)) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper_timefunc\nWHERE unix_to_timestamp(time) >= '2001-01-04T00:00:00' AND unix_to_timestamp(time) <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('@ 1 min'::interval, to_timestamp(_hyper_2_2_chunk.\"time\")))\n   ->  Sort\n         Sort Key: (time_bucket('@ 1 min'::interval, to_timestamp(_hyper_2_2_chunk.\"time\"))) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_2_2_chunk\n                     Filter: ((to_timestamp(\"time\") >= 'Thu Jan 04 00:00:00 2001 PST'::timestamp with time zone) AND (to_timestamp(\"time\") <= 'Fri Jan 05 01:00:00 2001 PST'::timestamp with time zone))\n\n\\set ECHO none\npsql:include/plan_hashagg_query.sql:60: ERROR:  unit \"invalid\" not recognized for type timestamp without time zone\npsql:include/plan_hashagg_query.sql:60: ERROR:  unit \"invalid\" not recognized for type timestamp without time zone\n"
  },
  {
    "path": "test/expected/plan_hashagg-18.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nSET max_parallel_workers_per_gather TO 0;\n\\set PREFIX 'EXPLAIN (buffers off, costs off) '\n\\ir include/plan_hashagg_load.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE metric (id SERIAL PRIMARY KEY, value INT);\nCREATE TABLE hyper(time TIMESTAMP NOT NULL, time_int BIGINT, time_broken DATE, metricid int, value double precision);\nCREATE TABLE regular(time TIMESTAMP NOT NULL, time_int BIGINT, time_date DATE, metricid int, value double precision);\nSELECT create_hypertable('hyper', 'time', chunk_time_interval => interval '20 day', create_default_indexes=>FALSE);\npsql:include/plan_hashagg_load.sql:9: WARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n create_hypertable  \n--------------------\n (1,public,hyper,t)\n\nALTER TABLE hyper\nDROP COLUMN time_broken,\nADD COLUMN time_date DATE;\nINSERT INTO metric(value) SELECT random()*100 FROM generate_series(0,10);\nINSERT INTO hyper SELECT t,  EXTRACT(EPOCH FROM t), (EXTRACT(EPOCH FROM t)::int % 10)+1, 1.0, t::date FROM generate_series('2001-01-01', '2001-01-10', INTERVAL '1 second') t;\nINSERT INTO regular(time, time_int, time_date, metricid, value)\n  SELECT t,  EXTRACT(EPOCH FROM t), t::date, (EXTRACT(EPOCH FROM t)::int % 10) + 1, 1.0 FROM generate_series('2001-01-01', '2001-01-02', INTERVAL '1 second') t;\n--test some queries before analyze;\nEXPLAIN (buffers off, costs off) SELECT time_bucket('1 minute', time) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n Sort\n   Sort Key: (time_bucket('@ 1 min'::interval, _hyper_1_1_chunk.\"time\")) DESC\n   ->  HashAggregate\n         Group Key: time_bucket('@ 1 min'::interval, _hyper_1_1_chunk.\"time\")\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\nEXPLAIN (buffers off, costs off) SELECT date_trunc('minute', time) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n Sort\n   Sort Key: (date_trunc('minute'::text, _hyper_1_1_chunk.\"time\")) DESC\n   ->  HashAggregate\n         Group Key: date_trunc('minute'::text, _hyper_1_1_chunk.\"time\")\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n-- Test partitioning function on an open (time) dimension\nCREATE OR REPLACE FUNCTION unix_to_timestamp(unixtime float8)\n    RETURNS TIMESTAMPTZ LANGUAGE SQL IMMUTABLE AS\n$BODY$\n    SELECT to_timestamp(unixtime);\n$BODY$;\nCREATE TABLE hyper_timefunc(time float8 NOT NULL, metricid int, VALUE double precision, time_date DATE);\nSELECT create_hypertable('hyper_timefunc', 'time', chunk_time_interval => interval '20 day', create_default_indexes=>FALSE, time_partitioning_func => 'unix_to_timestamp');\n      create_hypertable      \n-----------------------------\n (2,public,hyper_timefunc,t)\n\nINSERT INTO hyper_timefunc SELECT time_int, metricid, VALUE, time_date FROM hyper;\nANALYZE metric;\nANALYZE hyper;\nANALYZE regular;\nANALYZE hyper_timefunc;\n\\ir include/plan_hashagg_query.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n:PREFIX SELECT time_bucket('1 minute', time) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('@ 1 min'::interval, _hyper_1_1_chunk.\"time\"))\n   ->  Sort\n         Sort Key: (time_bucket('@ 1 min'::interval, _hyper_1_1_chunk.\"time\")) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n:PREFIX SELECT time_bucket('1 hour', time) AS MetricMinuteTs, metricid, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs, metricid\nORDER BY MetricMinuteTs DESC, metricid;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('@ 1 hour'::interval, _hyper_1_1_chunk.\"time\")), _hyper_1_1_chunk.metricid\n   ->  Sort\n         Sort Key: (time_bucket('@ 1 hour'::interval, _hyper_1_1_chunk.\"time\")) DESC, _hyper_1_1_chunk.metricid\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n--should be too many groups will not hashaggregate\n:PREFIX SELECT time_bucket('1 second', time) AS MetricMinuteTs, metricid, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs, metricid\nORDER BY MetricMinuteTs DESC, metricid;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('@ 1 sec'::interval, _hyper_1_1_chunk.\"time\")), _hyper_1_1_chunk.metricid\n   ->  Sort\n         Sort Key: (time_bucket('@ 1 sec'::interval, _hyper_1_1_chunk.\"time\")) DESC, _hyper_1_1_chunk.metricid\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n:PREFIX SELECT time_bucket('1 minute', time, INTERVAL '30 seconds') AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('@ 1 min'::interval, _hyper_1_1_chunk.\"time\", '@ 30 secs'::interval))\n   ->  Sort\n         Sort Key: (time_bucket('@ 1 min'::interval, _hyper_1_1_chunk.\"time\", '@ 30 secs'::interval)) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n:PREFIX SELECT time_bucket(60, time_int) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('60'::bigint, _hyper_1_1_chunk.time_int))\n   ->  Sort\n         Sort Key: (time_bucket('60'::bigint, _hyper_1_1_chunk.time_int)) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n:PREFIX SELECT time_bucket(60, time_int, 10) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('60'::bigint, _hyper_1_1_chunk.time_int, '10'::bigint))\n   ->  Sort\n         Sort Key: (time_bucket('60'::bigint, _hyper_1_1_chunk.time_int, '10'::bigint)) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n:PREFIX SELECT time_bucket('1 day', time_date) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n Sort\n   Sort Key: (time_bucket('@ 1 day'::interval, _hyper_1_1_chunk.time_date)) DESC\n   ->  HashAggregate\n         Group Key: time_bucket('@ 1 day'::interval, _hyper_1_1_chunk.time_date)\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n:PREFIX SELECT date_trunc('minute', time) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (date_trunc('minute'::text, _hyper_1_1_chunk.\"time\"))\n   ->  Sort\n         Sort Key: (date_trunc('minute'::text, _hyper_1_1_chunk.\"time\")) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n\\set ON_ERROR_STOP 0\n--can't optimize invalid time unit\n:PREFIX SELECT date_trunc('invalid', time) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (date_trunc('invalid'::text, _hyper_1_1_chunk.\"time\"))\n   ->  Sort\n         Sort Key: (date_trunc('invalid'::text, _hyper_1_1_chunk.\"time\")) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n\\set ON_ERROR_STOP 1\n:PREFIX SELECT date_trunc('day', time_date) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n Sort\n   Sort Key: (date_trunc('day'::text, (_hyper_1_1_chunk.time_date)::timestamp with time zone)) DESC\n   ->  HashAggregate\n         Group Key: date_trunc('day'::text, (_hyper_1_1_chunk.time_date)::timestamp with time zone)\n         ->  Result\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n\n--joins\n--with hypertable, optimize\n:PREFIX SELECT time_bucket(3600, time_int, 10) AS MetricMinuteTs, metric.value, AVG(hyper.value) as avg\nFROM hyper\nJOIN metric ON (hyper.metricid = metric.id)\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs, metric.id\nORDER BY MetricMinuteTs DESC, metric.id;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('3600'::bigint, _hyper_1_1_chunk.time_int, '10'::bigint)), metric.id\n   ->  Sort\n         Sort Key: (time_bucket('3600'::bigint, _hyper_1_1_chunk.time_int, '10'::bigint)) DESC, metric.id\n         ->  Hash Join\n               Hash Cond: (_hyper_1_1_chunk.metricid = metric.id)\n               ->  Seq Scan on _hyper_1_1_chunk\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n               ->  Hash\n                     ->  Seq Scan on metric\n\n--no hypertable involved, no optimization\n:PREFIX SELECT time_bucket(3600, time_int, 10) AS MetricMinuteTs, metric.value, AVG(regular.value) as avg\nFROM regular\nJOIN metric ON (regular.metricid = metric.id)\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs, metric.id\nORDER BY MetricMinuteTs DESC, metric.id;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('3600'::bigint, regular.time_int, '10'::bigint)), metric.id\n   ->  Sort\n         Sort Key: (time_bucket('3600'::bigint, regular.time_int, '10'::bigint)) DESC, metric.id\n         ->  Nested Loop\n               Join Filter: (metric.id = regular.metricid)\n               ->  Seq Scan on regular\n                     Filter: ((\"time\" >= 'Thu Jan 04 00:00:00 2001'::timestamp without time zone) AND (\"time\" <= 'Fri Jan 05 01:00:00 2001'::timestamp without time zone))\n               ->  Seq Scan on metric\n\n-- Try with time partitioning function. Currently not optimized for hash aggregates\n:PREFIX SELECT time_bucket('1 minute', unix_to_timestamp(time)) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper_timefunc\nWHERE unix_to_timestamp(time) >= '2001-01-04T00:00:00' AND unix_to_timestamp(time) <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: (time_bucket('@ 1 min'::interval, to_timestamp(_hyper_2_2_chunk.\"time\")))\n   ->  Sort\n         Sort Key: (time_bucket('@ 1 min'::interval, to_timestamp(_hyper_2_2_chunk.\"time\"))) DESC\n         ->  Result\n               ->  Seq Scan on _hyper_2_2_chunk\n                     Filter: ((to_timestamp(\"time\") >= 'Thu Jan 04 00:00:00 2001 PST'::timestamp with time zone) AND (to_timestamp(\"time\") <= 'Fri Jan 05 01:00:00 2001 PST'::timestamp with time zone))\n\n\\set ECHO none\npsql:include/plan_hashagg_query.sql:60: ERROR:  unit \"invalid\" not recognized for type timestamp without time zone\npsql:include/plan_hashagg_query.sql:60: ERROR:  unit \"invalid\" not recognized for type timestamp without time zone\n"
  },
  {
    "path": "test/expected/plan_hypertable_inline.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- test hypertable classification when query is in an inlineable function\n\\set PREFIX 'EXPLAIN (buffers off, costs off)'\nCREATE TABLE test (a int, b bigint NOT NULL);\nSELECT create_hypertable('public.test', 'b', chunk_time_interval=>10);\n create_hypertable \n-------------------\n (1,public,test,t)\n\nINSERT INTO test SELECT i, i FROM generate_series(1, 20) i;\nCREATE OR REPLACE FUNCTION test_f(_ts bigint)\nRETURNS SETOF test LANGUAGE SQL STABLE\nas $f$\n   SELECT DISTINCT ON (a) * FROM test WHERE b >= _ts AND b <= _ts + 2\n$f$;\n-- plans must be the same in both cases\n-- specifically, the first plan should not contain the parent hypertable\n-- as that is a sign the pruning was not done successfully\n:PREFIX SELECT * FROM test_f(5);\n--- QUERY PLAN ---\n Unique\n   ->  Sort\n         Sort Key: _hyper_1_1_chunk.a\n         ->  Index Scan using _hyper_1_1_chunk_test_b_idx on _hyper_1_1_chunk\n               Index Cond: ((b >= '5'::bigint) AND (b <= '7'::bigint))\n\n:PREFIX SELECT DISTINCT ON (a) * FROM test WHERE b >= 5 AND b <= 5 + 2;\n--- QUERY PLAN ---\n Unique\n   ->  Sort\n         Sort Key: _hyper_1_1_chunk.a\n         ->  Index Scan using _hyper_1_1_chunk_test_b_idx on _hyper_1_1_chunk\n               Index Cond: ((b >= 5) AND (b <= 7))\n\n-- test with FOR UPDATE\nCREATE OR REPLACE FUNCTION test_f(_ts bigint)\nRETURNS SETOF test LANGUAGE SQL STABLE\nas $f$\n   SELECT * FROM test WHERE b >= _ts AND b <= _ts + 2 FOR UPDATE\n$f$;\n-- pruning should not be done by TimescaleDb in this case\n-- specifically, the parent hypertable must exist in the output plan\n:PREFIX SELECT * FROM test_f(5);\n--- QUERY PLAN ---\n Subquery Scan on test_f\n   ->  LockRows\n         ->  Append\n               ->  Seq Scan on test test_1\n                     Filter: ((b >= '5'::bigint) AND (b <= '7'::bigint))\n               ->  Index Scan using _hyper_1_1_chunk_test_b_idx on _hyper_1_1_chunk test_2\n                     Index Cond: ((b >= '5'::bigint) AND (b <= '7'::bigint))\n\n:PREFIX SELECT * FROM test WHERE b >= 5 AND b <= 5 + 2 FOR UPDATE;\n--- QUERY PLAN ---\n LockRows\n   ->  Index Scan using _hyper_1_1_chunk_test_b_idx on _hyper_1_1_chunk\n         Index Cond: ((b >= 5) AND (b <= 7))\n\n-- test with CTE\n-- these cases are just to make sure we're everything is alright with\n-- the way we identify hypertables to prune chunks - we abuse ctename\n-- for this purpose. So double-check if we're not breaking plans\n-- with CTEs here.\nCREATE OR REPLACE FUNCTION test_f(_ts bigint)\nRETURNS SETOF test LANGUAGE SQL STABLE\nas $f$\n   WITH ct AS MATERIALIZED (\n      SELECT DISTINCT ON (a) * FROM test WHERE b >= _ts AND b <= _ts + 2\n   )\n   SELECT * FROM ct\n$f$;\n:PREFIX SELECT * FROM test_f(5);\n--- QUERY PLAN ---\n CTE Scan on ct\n   CTE ct\n     ->  Unique\n           ->  Sort\n                 Sort Key: _hyper_1_1_chunk.a\n                 ->  Index Scan using _hyper_1_1_chunk_test_b_idx on _hyper_1_1_chunk\n                       Index Cond: ((b >= '5'::bigint) AND (b <= '7'::bigint))\n\n:PREFIX\nWITH ct AS MATERIALIZED (\n   SELECT DISTINCT ON (a) * FROM test WHERE b >= 5 AND b <= 5 + 2\n)\nSELECT * FROM ct;\n--- QUERY PLAN ---\n CTE Scan on ct\n   CTE ct\n     ->  Unique\n           ->  Sort\n                 Sort Key: _hyper_1_1_chunk.a\n                 ->  Index Scan using _hyper_1_1_chunk_test_b_idx on _hyper_1_1_chunk\n                       Index Cond: ((b >= 5) AND (b <= 7))\n\n-- CTE within CTE\n:PREFIX\nWITH ct AS MATERIALIZED (\n   SELECT * FROM test_f(5)\n)\nSELECT * FROM ct;\n--- QUERY PLAN ---\n CTE Scan on ct\n   CTE ct\n     ->  CTE Scan on ct ct_1\n           CTE ct\n             ->  Unique\n                   ->  Sort\n                         Sort Key: _hyper_1_1_chunk.a\n                         ->  Index Scan using _hyper_1_1_chunk_test_b_idx on _hyper_1_1_chunk\n                               Index Cond: ((b >= '5'::bigint) AND (b <= '7'::bigint))\n\n-- CTE within NO MATERIALIZED CTE\n:PREFIX\nWITH ct AS NOT MATERIALIZED (\n   SELECT * FROM test_f(5)\n)\nSELECT * FROM ct;\n--- QUERY PLAN ---\n CTE Scan on ct\n   CTE ct\n     ->  Unique\n           ->  Sort\n                 Sort Key: _hyper_1_1_chunk.a\n                 ->  Index Scan using _hyper_1_1_chunk_test_b_idx on _hyper_1_1_chunk\n                       Index Cond: ((b >= '5'::bigint) AND (b <= '7'::bigint))\n\n"
  },
  {
    "path": "test/expected/plan_ordered_append-15.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- we run these with analyze to confirm that nodes that are not\n-- needed to fulfill the limit are not executed\n-- unfortunately this doesn't work on PostgreSQL 9.6 which lacks\n-- the ability to turn off analyze timing summary so we run\n-- them without ANALYZE on PostgreSQL 9.6, but since LATERAL plans\n-- are different across versions we need version specific output\n-- here anyway.\n\\set TEST_BASE_NAME plan_ordered_append\nSELECT format('include/%s_load.sql', :'TEST_BASE_NAME') as \"TEST_LOAD_NAME\",\n       format('include/%s_query.sql', :'TEST_BASE_NAME') as \"TEST_QUERY_NAME\",\n       format('%s/results/%s_results_optimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_OPTIMIZED\",\n       format('%s/results/%s_results_unoptimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_UNOPTIMIZED\"\n\\gset\nSELECT format('\\! diff -u --label \"Unoptimized result\" --label \"Optimized result\" %s %s', :'TEST_RESULTS_UNOPTIMIZED', :'TEST_RESULTS_OPTIMIZED') as \"DIFF_CMD\"\n\\gset\n\\set PREFIX 'EXPLAIN (analyze, buffers off, costs off, timing off, summary off)'\n\\set PREFIX_NO_ANALYZE 'EXPLAIN (buffers off, costs off)'\n\\ir :TEST_LOAD_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- create a now() function for repeatable testing that always returns\n-- the same timestamp. It needs to be marked STABLE\nCREATE OR REPLACE FUNCTION now_s()\nRETURNS timestamptz LANGUAGE PLPGSQL STABLE AS\n$BODY$\nBEGIN\n    RETURN '2000-01-08T0:00:00+0'::timestamptz;\nEND;\n$BODY$;\nCREATE TABLE devices(device_id INT PRIMARY KEY, name TEXT);\nINSERT INTO devices VALUES\n(1,'Device 1'),\n(2,'Device 2'),\n(3,'Device 3');\n-- create a second table where we create chunks in reverse order\nCREATE TABLE ordered_append_reverse(time timestamptz NOT NULL, device_id INT, value float);\nSELECT create_hypertable('ordered_append_reverse','time');\n          create_hypertable          \n-------------------------------------\n (1,public,ordered_append_reverse,t)\n\nINSERT INTO ordered_append_reverse SELECT generate_series('2000-01-18'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 0.5;\n-- table where dimension column is last column\nCREATE TABLE IF NOT EXISTS dimension_last(\n    id INT8 NOT NULL,\n    device_id INT NOT NULL,\n    name TEXT NOT NULL,\n    time timestamptz NOT NULL\n);\nSELECT create_hypertable('dimension_last', 'time', chunk_time_interval => interval '1day', if_not_exists => True);\n      create_hypertable      \n-----------------------------\n (2,public,dimension_last,t)\n\n-- table with only dimension column\nCREATE TABLE IF NOT EXISTS dimension_only(\n    time timestamptz NOT NULL\n);\nSELECT create_hypertable('dimension_only', 'time', chunk_time_interval => interval '1day', if_not_exists => True);\n      create_hypertable      \n-----------------------------\n (3,public,dimension_only,t)\n\nINSERT INTO dimension_last SELECT 1,1,'Device 1',generate_series('2000-01-01 0:00:00+0'::timestamptz,'2000-01-04 23:59:00+0'::timestamptz,'1m'::interval);\nINSERT INTO dimension_only VALUES\n('2000-01-01'),\n('2000-01-03'),\n('2000-01-05'),\n('2000-01-07');\nANALYZE devices;\nANALYZE ordered_append_reverse;\nANALYZE dimension_last;\nANALYZE dimension_only;\n-- create hypertable with indexes not on all chunks\nCREATE TABLE ht_missing_indexes(time timestamptz NOT NULL, device_id int, value float);\nSELECT create_hypertable('ht_missing_indexes','time');\n        create_hypertable        \n---------------------------------\n (4,public,ht_missing_indexes,t)\n\nINSERT INTO ht_missing_indexes SELECT generate_series('2000-01-01'::timestamptz,'2000-01-18'::timestamptz,'1m'::interval), 1, 0.5;\nINSERT INTO ht_missing_indexes SELECT generate_series('2000-01-01'::timestamptz,'2000-01-18'::timestamptz,'1m'::interval), 2, 1.5;\nINSERT INTO ht_missing_indexes SELECT generate_series('2000-01-01'::timestamptz,'2000-01-18'::timestamptz,'1m'::interval), 3, 2.5;\n-- drop index from 2nd chunk of ht_missing_indexes\nSELECT format('%I.%I',i.schemaname,i.indexname) AS \"INDEX_NAME\"\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable ht ON c.hypertable_id = ht.id\nINNER JOIN pg_indexes i ON i.schemaname = c.schema_name AND i.tablename=c.table_name\nWHERE ht.table_name = 'ht_missing_indexes'\nORDER BY c.id LIMIT 1 OFFSET 1 \\gset\nDROP INDEX :INDEX_NAME;\nANALYZE ht_missing_indexes;\n-- create hypertable with with dropped columns\nCREATE TABLE ht_dropped_columns(c1 int, c2 int, c3 int, c4 int, c5 int, time timestamptz NOT NULL, device_id int, value float);\nSELECT create_hypertable('ht_dropped_columns','time');\n        create_hypertable        \n---------------------------------\n (5,public,ht_dropped_columns,t)\n\nALTER TABLE ht_dropped_columns DROP COLUMN c1;\nINSERT INTO ht_dropped_columns(time,device_id,value) SELECT generate_series('2000-01-01'::timestamptz,'2000-01-02'::timestamptz,'1m'::interval), 1, 0.5;\nALTER TABLE ht_dropped_columns DROP COLUMN c2;\nINSERT INTO ht_dropped_columns(time,device_id,value) SELECT generate_series('2000-01-08'::timestamptz,'2000-01-09'::timestamptz,'1m'::interval), 1, 0.5;\nALTER TABLE ht_dropped_columns DROP COLUMN c3;\nINSERT INTO ht_dropped_columns(time,device_id,value) SELECT generate_series('2000-01-15'::timestamptz,'2000-01-16'::timestamptz,'1m'::interval), 1, 0.5;\nALTER TABLE ht_dropped_columns DROP COLUMN c4;\nINSERT INTO ht_dropped_columns(time,device_id,value) SELECT generate_series('2000-01-22'::timestamptz,'2000-01-23'::timestamptz,'1m'::interval), 1, 0.5;\nALTER TABLE ht_dropped_columns DROP COLUMN c5;\nINSERT INTO ht_dropped_columns(time,device_id,value) SELECT generate_series('2000-01-29'::timestamptz,'2000-01-30'::timestamptz,'1m'::interval), 1, 0.5;\nANALYZE ht_dropped_columns;\nCREATE TABLE space2(time timestamptz NOT NULL, device_id int NOT NULL, tag_id int NOT NULL, value float);\nSELECT create_hypertable('space2','time','device_id',number_partitions:=3);\n  create_hypertable  \n---------------------\n (6,public,space2,t)\n\nSELECT add_dimension('space2','tag_id',number_partitions:=3);\n       add_dimension        \n----------------------------\n (8,public,space2,tag_id,t)\n\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 1, 1.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 1, 2.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 3, 1, 3.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 2, 1.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 2, 2.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 3, 2, 3.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 3, 1.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 3, 2.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 3, 3, 3.5;\nANALYZE space2;\nCREATE TABLE space3(time timestamptz NOT NULL, x int NOT NULL, y int NOT NULL, z int NOT NULL, value float);\nSELECT create_hypertable('space3','time','x',number_partitions:=2);\n  create_hypertable  \n---------------------\n (7,public,space3,t)\n\nSELECT add_dimension('space3','y',number_partitions:=2);\n     add_dimension      \n------------------------\n (11,public,space3,y,t)\n\nSELECT add_dimension('space3','z',number_partitions:=2);\n     add_dimension      \n------------------------\n (12,public,space3,z,t)\n\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 1, 1, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 1, 2, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 2, 1, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 2, 2, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 1, 1, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 1, 2, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 2, 1, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 2, 2, 1.5;\nANALYZE space3;\nCREATE TABLE sortopt_test(time timestamptz NOT NULL, device TEXT);\nSELECT create_hypertable('sortopt_test','time',create_default_indexes:=false);\n     create_hypertable     \n---------------------------\n (8,public,sortopt_test,t)\n\n-- since alpine does not support locales we cant test collations in our ci\n-- CREATE COLLATION IF NOT EXISTS en_US(LOCALE='en_US.utf8');\n-- CREATE INDEX time_device_utf8 ON sortopt_test(time, device COLLATE \"en_US\");\nCREATE INDEX time_device_nullsfirst ON sortopt_test(time, device NULLS FIRST);\nCREATE INDEX time_device_nullslast ON sortopt_test(time, device DESC NULLS LAST);\nINSERT INTO sortopt_test SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 'Device 1';\nANALYZE sortopt_test;\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- print chunks ordered by time to ensure ordering we want\nSELECT\n  ht.table_name AS hypertable,\n  c.table_name AS chunk,\n  ds.range_start\nFROM\n  _timescaledb_catalog.chunk c\n  INNER JOIN LATERAL(SELECT * FROM _timescaledb_catalog.chunk_constraint cc WHERE c.id = cc.chunk_id ORDER BY cc.dimension_slice_id LIMIT 1) cc ON true\n  INNER JOIN _timescaledb_catalog.dimension_slice ds ON ds.id=cc.dimension_slice_id\n  INNER JOIN _timescaledb_catalog.dimension d ON ds.dimension_id = d.id\n  INNER JOIN _timescaledb_catalog.hypertable ht ON d.hypertable_id = ht.id\nORDER BY ht.table_name, range_start, chunk;\n       hypertable       |       chunk       |     range_start      \n------------------------+-------------------+----------------------\n dimension_last         | _hyper_2_4_chunk  |      946684800000000\n dimension_last         | _hyper_2_5_chunk  |      946771200000000\n dimension_last         | _hyper_2_6_chunk  |      946857600000000\n dimension_last         | _hyper_2_7_chunk  |      946944000000000\n dimension_only         | _hyper_3_8_chunk  |      946684800000000\n dimension_only         | _hyper_3_9_chunk  |      946857600000000\n dimension_only         | _hyper_3_10_chunk |      947030400000000\n dimension_only         | _hyper_3_11_chunk |      947203200000000\n ht_dropped_columns     | _hyper_5_15_chunk |      946512000000000\n ht_dropped_columns     | _hyper_5_16_chunk |      947116800000000\n ht_dropped_columns     | _hyper_5_17_chunk |      947721600000000\n ht_dropped_columns     | _hyper_5_18_chunk |      948326400000000\n ht_dropped_columns     | _hyper_5_19_chunk |      948931200000000\n ht_missing_indexes     | _hyper_4_12_chunk |      946512000000000\n ht_missing_indexes     | _hyper_4_13_chunk |      947116800000000\n ht_missing_indexes     | _hyper_4_14_chunk |      947721600000000\n ordered_append_reverse | _hyper_1_3_chunk  |      946512000000000\n ordered_append_reverse | _hyper_1_2_chunk  |      947116800000000\n ordered_append_reverse | _hyper_1_1_chunk  |      947721600000000\n sortopt_test           | _hyper_8_55_chunk |      946512000000000\n sortopt_test           | _hyper_8_54_chunk |      947116800000000\n space2                 | _hyper_6_21_chunk | -9223372036854775808\n space2                 | _hyper_6_23_chunk | -9223372036854775808\n space2                 | _hyper_6_25_chunk | -9223372036854775808\n space2                 | _hyper_6_27_chunk | -9223372036854775808\n space2                 | _hyper_6_33_chunk | -9223372036854775808\n space2                 | _hyper_6_29_chunk |      946512000000000\n space2                 | _hyper_6_31_chunk |      946512000000000\n space2                 | _hyper_6_35_chunk |      946512000000000\n space2                 | _hyper_6_37_chunk |      946512000000000\n space2                 | _hyper_6_20_chunk |      947116800000000\n space2                 | _hyper_6_22_chunk |      947116800000000\n space2                 | _hyper_6_24_chunk |      947116800000000\n space2                 | _hyper_6_26_chunk |      947116800000000\n space2                 | _hyper_6_28_chunk |      947116800000000\n space2                 | _hyper_6_30_chunk |      947116800000000\n space2                 | _hyper_6_32_chunk |      947116800000000\n space2                 | _hyper_6_34_chunk |      947116800000000\n space2                 | _hyper_6_36_chunk |      947116800000000\n space3                 | _hyper_7_39_chunk | -9223372036854775808\n space3                 | _hyper_7_41_chunk | -9223372036854775808\n space3                 | _hyper_7_43_chunk | -9223372036854775808\n space3                 | _hyper_7_45_chunk | -9223372036854775808\n space3                 | _hyper_7_47_chunk | -9223372036854775808\n space3                 | _hyper_7_49_chunk | -9223372036854775808\n space3                 | _hyper_7_51_chunk | -9223372036854775808\n space3                 | _hyper_7_53_chunk |      946512000000000\n space3                 | _hyper_7_38_chunk |      947116800000000\n space3                 | _hyper_7_40_chunk |      947116800000000\n space3                 | _hyper_7_42_chunk |      947116800000000\n space3                 | _hyper_7_44_chunk |      947116800000000\n space3                 | _hyper_7_46_chunk |      947116800000000\n space3                 | _hyper_7_48_chunk |      947116800000000\n space3                 | _hyper_7_50_chunk |      947116800000000\n space3                 | _hyper_7_52_chunk |      947116800000000\n\n-- test ASC for reverse ordered chunks\n:PREFIX SELECT\n  time, device_id, value\nFROM ordered_append_reverse\nORDER BY time ASC LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on ordered_append_reverse (actual rows=1.00 loops=1)\n         Order: ordered_append_reverse.\"time\"\n         ->  Index Scan Backward using _hyper_1_3_chunk_ordered_append_reverse_time_idx on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         ->  Index Scan Backward using _hyper_1_2_chunk_ordered_append_reverse_time_idx on _hyper_1_2_chunk (never executed)\n         ->  Index Scan Backward using _hyper_1_1_chunk_ordered_append_reverse_time_idx on _hyper_1_1_chunk (never executed)\n\n-- test DESC for reverse ordered chunks\n:PREFIX SELECT\n  time, device_id, value\nFROM ordered_append_reverse\nORDER BY time DESC LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on ordered_append_reverse (actual rows=1.00 loops=1)\n         Order: ordered_append_reverse.\"time\" DESC\n         ->  Index Scan using _hyper_1_1_chunk_ordered_append_reverse_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n         ->  Index Scan using _hyper_1_2_chunk_ordered_append_reverse_time_idx on _hyper_1_2_chunk (never executed)\n         ->  Index Scan using _hyper_1_3_chunk_ordered_append_reverse_time_idx on _hyper_1_3_chunk (never executed)\n\n-- test query with ORDER BY time_bucket, device_id\n-- must not use ordered append\n:PREFIX SELECT\n  time_bucket('1d',time), device_id, name\nFROM dimension_last\nORDER BY time_bucket('1d',time), device_id LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Sort (actual rows=1.00 loops=1)\n         Sort Key: (time_bucket('@ 1 day'::interval, dimension_last.\"time\")), dimension_last.device_id\n         Sort Method: top-N heapsort \n         ->  Result (actual rows=5760.00 loops=1)\n               ->  Append (actual rows=5760.00 loops=1)\n                     ->  Seq Scan on _hyper_2_4_chunk (actual rows=1440.00 loops=1)\n                     ->  Seq Scan on _hyper_2_5_chunk (actual rows=1440.00 loops=1)\n                     ->  Seq Scan on _hyper_2_6_chunk (actual rows=1440.00 loops=1)\n                     ->  Seq Scan on _hyper_2_7_chunk (actual rows=1440.00 loops=1)\n\n-- test query with ORDER BY date_trunc, device_id\n-- must not use ordered append\n:PREFIX SELECT\n  date_trunc('day',time), device_id, name\nFROM dimension_last\nORDER BY 1,2 LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Sort (actual rows=1.00 loops=1)\n         Sort Key: (date_trunc('day'::text, dimension_last.\"time\")), dimension_last.device_id\n         Sort Method: top-N heapsort \n         ->  Result (actual rows=5760.00 loops=1)\n               ->  Append (actual rows=5760.00 loops=1)\n                     ->  Seq Scan on _hyper_2_4_chunk (actual rows=1440.00 loops=1)\n                     ->  Seq Scan on _hyper_2_5_chunk (actual rows=1440.00 loops=1)\n                     ->  Seq Scan on _hyper_2_6_chunk (actual rows=1440.00 loops=1)\n                     ->  Seq Scan on _hyper_2_7_chunk (actual rows=1440.00 loops=1)\n\n-- test with table with only dimension column\n:PREFIX SELECT * FROM dimension_only ORDER BY time DESC LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on dimension_only (actual rows=1.00 loops=1)\n         Order: dimension_only.\"time\" DESC\n         ->  Index Only Scan using _hyper_3_11_chunk_dimension_only_time_idx on _hyper_3_11_chunk (actual rows=1.00 loops=1)\n         ->  Index Only Scan using _hyper_3_10_chunk_dimension_only_time_idx on _hyper_3_10_chunk (never executed)\n         ->  Index Only Scan using _hyper_3_9_chunk_dimension_only_time_idx on _hyper_3_9_chunk (never executed)\n         ->  Index Only Scan using _hyper_3_8_chunk_dimension_only_time_idx on _hyper_3_8_chunk (never executed)\n\n-- test LEFT JOIN against hypertable\n:PREFIX_NO_ANALYZE SELECT *\nFROM dimension_last\nLEFT JOIN dimension_only USING (time)\nORDER BY dimension_last.time DESC\nLIMIT 2;\n--- QUERY PLAN ---\n Limit\n   ->  Nested Loop Left Join\n         Join Filter: (dimension_last.\"time\" = dimension_only.\"time\")\n         ->  Custom Scan (ChunkAppend) on dimension_last\n               Order: dimension_last.\"time\" DESC\n               ->  Index Scan using _hyper_2_7_chunk_dimension_last_time_idx on _hyper_2_7_chunk\n               ->  Index Scan using _hyper_2_6_chunk_dimension_last_time_idx on _hyper_2_6_chunk\n               ->  Index Scan using _hyper_2_5_chunk_dimension_last_time_idx on _hyper_2_5_chunk\n               ->  Index Scan using _hyper_2_4_chunk_dimension_last_time_idx on _hyper_2_4_chunk\n         ->  Materialize\n               ->  Append\n                     ->  Seq Scan on _hyper_3_11_chunk\n                     ->  Seq Scan on _hyper_3_10_chunk\n                     ->  Seq Scan on _hyper_3_9_chunk\n                     ->  Seq Scan on _hyper_3_8_chunk\n\n-- test INNER JOIN against non-hypertable\n:PREFIX_NO_ANALYZE SELECT *\nFROM dimension_last\nINNER JOIN dimension_only USING (time)\nORDER BY dimension_last.time DESC\nLIMIT 2;\n--- QUERY PLAN ---\n Limit\n   ->  Nested Loop\n         ->  Custom Scan (ChunkAppend) on dimension_only\n               Order: dimension_only.\"time\" DESC\n               ->  Index Only Scan using _hyper_3_11_chunk_dimension_only_time_idx on _hyper_3_11_chunk\n               ->  Index Only Scan using _hyper_3_10_chunk_dimension_only_time_idx on _hyper_3_10_chunk\n               ->  Index Only Scan using _hyper_3_9_chunk_dimension_only_time_idx on _hyper_3_9_chunk\n               ->  Index Only Scan using _hyper_3_8_chunk_dimension_only_time_idx on _hyper_3_8_chunk\n         ->  Append\n               ->  Index Scan using _hyper_2_7_chunk_dimension_last_time_idx on _hyper_2_7_chunk\n                     Index Cond: (\"time\" = dimension_only.\"time\")\n               ->  Index Scan using _hyper_2_6_chunk_dimension_last_time_idx on _hyper_2_6_chunk\n                     Index Cond: (\"time\" = dimension_only.\"time\")\n               ->  Index Scan using _hyper_2_5_chunk_dimension_last_time_idx on _hyper_2_5_chunk\n                     Index Cond: (\"time\" = dimension_only.\"time\")\n               ->  Index Scan using _hyper_2_4_chunk_dimension_last_time_idx on _hyper_2_4_chunk\n                     Index Cond: (\"time\" = dimension_only.\"time\")\n\n-- test join against non-hypertable\n:PREFIX SELECT *\nFROM dimension_last\nINNER JOIN devices USING(device_id)\nORDER BY dimension_last.time DESC\nLIMIT 2;\n--- QUERY PLAN ---\n Limit (actual rows=2.00 loops=1)\n   ->  Nested Loop (actual rows=2.00 loops=1)\n         Join Filter: (dimension_last.device_id = devices.device_id)\n         ->  Custom Scan (ChunkAppend) on dimension_last (actual rows=2.00 loops=1)\n               Order: dimension_last.\"time\" DESC\n               ->  Index Scan using _hyper_2_7_chunk_dimension_last_time_idx on _hyper_2_7_chunk (actual rows=2.00 loops=1)\n               ->  Index Scan using _hyper_2_6_chunk_dimension_last_time_idx on _hyper_2_6_chunk (never executed)\n               ->  Index Scan using _hyper_2_5_chunk_dimension_last_time_idx on _hyper_2_5_chunk (never executed)\n               ->  Index Scan using _hyper_2_4_chunk_dimension_last_time_idx on _hyper_2_4_chunk (never executed)\n         ->  Materialize (actual rows=1.00 loops=2)\n               ->  Seq Scan on devices (actual rows=1.00 loops=1)\n\n-- test hypertable with index missing on one chunk\n:PREFIX SELECT\n  time, device_id, value\nFROM ht_missing_indexes\nORDER BY time ASC LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on ht_missing_indexes (actual rows=1.00 loops=1)\n         Order: ht_missing_indexes.\"time\"\n         ->  Index Scan Backward using _hyper_4_12_chunk_ht_missing_indexes_time_idx on _hyper_4_12_chunk (actual rows=1.00 loops=1)\n         ->  Sort (never executed)\n               Sort Key: _hyper_4_13_chunk.\"time\"\n               ->  Seq Scan on _hyper_4_13_chunk (never executed)\n         ->  Index Scan Backward using _hyper_4_14_chunk_ht_missing_indexes_time_idx on _hyper_4_14_chunk (never executed)\n\n-- test hypertable with index missing on one chunk\n-- and no data\n:PREFIX SELECT\n  time, device_id, value\nFROM ht_missing_indexes\nWHERE device_id = 2\nORDER BY time DESC LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on ht_missing_indexes (actual rows=1.00 loops=1)\n         Order: ht_missing_indexes.\"time\" DESC\n         ->  Index Scan using _hyper_4_14_chunk_ht_missing_indexes_time_idx on _hyper_4_14_chunk (actual rows=1.00 loops=1)\n               Filter: (device_id = 2)\n               Rows Removed by Filter: 1\n         ->  Sort (never executed)\n               Sort Key: _hyper_4_13_chunk.\"time\" DESC\n               ->  Seq Scan on _hyper_4_13_chunk (never executed)\n                     Filter: (device_id = 2)\n         ->  Index Scan using _hyper_4_12_chunk_ht_missing_indexes_time_idx on _hyper_4_12_chunk (never executed)\n               Filter: (device_id = 2)\n\n-- test hypertable with index missing on one chunk\n-- and no data\n:PREFIX SELECT\n  time, device_id, value\nFROM ht_missing_indexes\nWHERE time > '2000-01-07'\nORDER BY time LIMIT 10;\n--- QUERY PLAN ---\n Limit (actual rows=10.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on ht_missing_indexes (actual rows=10.00 loops=1)\n         Order: ht_missing_indexes.\"time\"\n         ->  Sort (actual rows=10.00 loops=1)\n               Sort Key: _hyper_4_13_chunk.\"time\"\n               Sort Method: top-N heapsort \n               ->  Seq Scan on _hyper_4_13_chunk (actual rows=24477.00 loops=1)\n                     Filter: (\"time\" > 'Fri Jan 07 00:00:00 2000 PST'::timestamp with time zone)\n                     Rows Removed by Filter: 5763\n         ->  Index Scan Backward using _hyper_4_14_chunk_ht_missing_indexes_time_idx on _hyper_4_14_chunk (never executed)\n\n-- test hypertable with dropped columns\n:PREFIX SELECT\n  time, device_id, value\nFROM ht_dropped_columns\nORDER BY time ASC LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on ht_dropped_columns (actual rows=1.00 loops=1)\n         Order: ht_dropped_columns.\"time\"\n         ->  Index Scan Backward using _hyper_5_15_chunk_ht_dropped_columns_time_idx on _hyper_5_15_chunk (actual rows=1.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_16_chunk_ht_dropped_columns_time_idx on _hyper_5_16_chunk (never executed)\n         ->  Index Scan Backward using _hyper_5_17_chunk_ht_dropped_columns_time_idx on _hyper_5_17_chunk (never executed)\n         ->  Index Scan Backward using _hyper_5_18_chunk_ht_dropped_columns_time_idx on _hyper_5_18_chunk (never executed)\n         ->  Index Scan Backward using _hyper_5_19_chunk_ht_dropped_columns_time_idx on _hyper_5_19_chunk (never executed)\n\n-- test hypertable with dropped columns\n:PREFIX SELECT\n  time, device_id, value\nFROM ht_dropped_columns\nWHERE device_id = 1\nORDER BY time DESC;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on ht_dropped_columns (actual rows=7205.00 loops=1)\n   Order: ht_dropped_columns.\"time\" DESC\n   ->  Index Scan using _hyper_5_19_chunk_ht_dropped_columns_time_idx on _hyper_5_19_chunk (actual rows=1441.00 loops=1)\n         Filter: (device_id = 1)\n   ->  Index Scan using _hyper_5_18_chunk_ht_dropped_columns_time_idx on _hyper_5_18_chunk (actual rows=1441.00 loops=1)\n         Filter: (device_id = 1)\n   ->  Index Scan using _hyper_5_17_chunk_ht_dropped_columns_time_idx on _hyper_5_17_chunk (actual rows=1441.00 loops=1)\n         Filter: (device_id = 1)\n   ->  Index Scan using _hyper_5_16_chunk_ht_dropped_columns_time_idx on _hyper_5_16_chunk (actual rows=1441.00 loops=1)\n         Filter: (device_id = 1)\n   ->  Index Scan using _hyper_5_15_chunk_ht_dropped_columns_time_idx on _hyper_5_15_chunk (actual rows=1441.00 loops=1)\n         Filter: (device_id = 1)\n\n-- test hypertable with 2 space dimensions\n:PREFIX SELECT\n  time, device_id, value\nFROM space2\nORDER BY time DESC;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on space2 (actual rows=116649.00 loops=1)\n   Order: space2.\"time\" DESC\n   ->  Merge Append (actual rows=56169.00 loops=1)\n         Sort Key: space2.\"time\" DESC\n         ->  Index Scan using _hyper_6_36_chunk_space2_time_idx on _hyper_6_36_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_34_chunk_space2_time_idx on _hyper_6_34_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_32_chunk_space2_time_idx on _hyper_6_32_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_30_chunk_space2_time_idx on _hyper_6_30_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_28_chunk_space2_time_idx on _hyper_6_28_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_26_chunk_space2_time_idx on _hyper_6_26_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_24_chunk_space2_time_idx on _hyper_6_24_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_22_chunk_space2_time_idx on _hyper_6_22_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_20_chunk_space2_time_idx on _hyper_6_20_chunk (actual rows=6241.00 loops=1)\n   ->  Merge Append (actual rows=60480.00 loops=1)\n         Sort Key: space2.\"time\" DESC\n         ->  Index Scan using _hyper_6_37_chunk_space2_time_idx on _hyper_6_37_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_35_chunk_space2_time_idx on _hyper_6_35_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_33_chunk_space2_time_idx on _hyper_6_33_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_31_chunk_space2_time_idx on _hyper_6_31_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_29_chunk_space2_time_idx on _hyper_6_29_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_27_chunk_space2_time_idx on _hyper_6_27_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_25_chunk_space2_time_idx on _hyper_6_25_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_23_chunk_space2_time_idx on _hyper_6_23_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_21_chunk_space2_time_idx on _hyper_6_21_chunk (actual rows=6720.00 loops=1)\n\n-- test hypertable with 3 space dimensions\n:PREFIX SELECT\n  time\nFROM space3\nORDER BY time DESC;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on space3 (actual rows=103688.00 loops=1)\n   Order: space3.\"time\" DESC\n   ->  Merge Append (actual rows=49928.00 loops=1)\n         Sort Key: space3.\"time\" DESC\n         ->  Index Only Scan using _hyper_7_52_chunk_space3_time_idx on _hyper_7_52_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_50_chunk_space3_time_idx on _hyper_7_50_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_48_chunk_space3_time_idx on _hyper_7_48_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_46_chunk_space3_time_idx on _hyper_7_46_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_44_chunk_space3_time_idx on _hyper_7_44_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_42_chunk_space3_time_idx on _hyper_7_42_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_40_chunk_space3_time_idx on _hyper_7_40_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_38_chunk_space3_time_idx on _hyper_7_38_chunk (actual rows=6241.00 loops=1)\n   ->  Merge Append (actual rows=53760.00 loops=1)\n         Sort Key: space3.\"time\" DESC\n         ->  Index Only Scan using _hyper_7_53_chunk_space3_time_idx on _hyper_7_53_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_51_chunk_space3_time_idx on _hyper_7_51_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_49_chunk_space3_time_idx on _hyper_7_49_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_47_chunk_space3_time_idx on _hyper_7_47_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_45_chunk_space3_time_idx on _hyper_7_45_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_43_chunk_space3_time_idx on _hyper_7_43_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_41_chunk_space3_time_idx on _hyper_7_41_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_39_chunk_space3_time_idx on _hyper_7_39_chunk (actual rows=6720.00 loops=1)\n\n-- test COLLATION\n-- cant be tested in our ci because alpine doesnt support locales\n-- :PREFIX SELECT * FROM sortopt_test ORDER BY time, device COLLATE \"en_US.utf8\";\n-- test NULLS FIRST\n:PREFIX SELECT * FROM sortopt_test ORDER BY time, device NULLS FIRST;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on sortopt_test (actual rows=12961.00 loops=1)\n   Order: sortopt_test.\"time\", sortopt_test.device NULLS FIRST\n   ->  Index Only Scan using _hyper_8_55_chunk_time_device_nullsfirst on _hyper_8_55_chunk (actual rows=6720.00 loops=1)\n   ->  Index Only Scan using _hyper_8_54_chunk_time_device_nullsfirst on _hyper_8_54_chunk (actual rows=6241.00 loops=1)\n\n-- test NULLS LAST\n:PREFIX SELECT * FROM sortopt_test ORDER BY time, device DESC NULLS LAST;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on sortopt_test (actual rows=12961.00 loops=1)\n   Order: sortopt_test.\"time\", sortopt_test.device DESC NULLS LAST\n   ->  Index Only Scan using _hyper_8_55_chunk_time_device_nullslast on _hyper_8_55_chunk (actual rows=6720.00 loops=1)\n   ->  Index Only Scan using _hyper_8_54_chunk_time_device_nullslast on _hyper_8_54_chunk (actual rows=6241.00 loops=1)\n\n--generate the results into two different files\n\\set ECHO errors\n"
  },
  {
    "path": "test/expected/plan_ordered_append-16.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- we run these with analyze to confirm that nodes that are not\n-- needed to fulfill the limit are not executed\n-- unfortunately this doesn't work on PostgreSQL 9.6 which lacks\n-- the ability to turn off analyze timing summary so we run\n-- them without ANALYZE on PostgreSQL 9.6, but since LATERAL plans\n-- are different across versions we need version specific output\n-- here anyway.\n\\set TEST_BASE_NAME plan_ordered_append\nSELECT format('include/%s_load.sql', :'TEST_BASE_NAME') as \"TEST_LOAD_NAME\",\n       format('include/%s_query.sql', :'TEST_BASE_NAME') as \"TEST_QUERY_NAME\",\n       format('%s/results/%s_results_optimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_OPTIMIZED\",\n       format('%s/results/%s_results_unoptimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_UNOPTIMIZED\"\n\\gset\nSELECT format('\\! diff -u --label \"Unoptimized result\" --label \"Optimized result\" %s %s', :'TEST_RESULTS_UNOPTIMIZED', :'TEST_RESULTS_OPTIMIZED') as \"DIFF_CMD\"\n\\gset\n\\set PREFIX 'EXPLAIN (analyze, buffers off, costs off, timing off, summary off)'\n\\set PREFIX_NO_ANALYZE 'EXPLAIN (buffers off, costs off)'\n\\ir :TEST_LOAD_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- create a now() function for repeatable testing that always returns\n-- the same timestamp. It needs to be marked STABLE\nCREATE OR REPLACE FUNCTION now_s()\nRETURNS timestamptz LANGUAGE PLPGSQL STABLE AS\n$BODY$\nBEGIN\n    RETURN '2000-01-08T0:00:00+0'::timestamptz;\nEND;\n$BODY$;\nCREATE TABLE devices(device_id INT PRIMARY KEY, name TEXT);\nINSERT INTO devices VALUES\n(1,'Device 1'),\n(2,'Device 2'),\n(3,'Device 3');\n-- create a second table where we create chunks in reverse order\nCREATE TABLE ordered_append_reverse(time timestamptz NOT NULL, device_id INT, value float);\nSELECT create_hypertable('ordered_append_reverse','time');\n          create_hypertable          \n-------------------------------------\n (1,public,ordered_append_reverse,t)\n\nINSERT INTO ordered_append_reverse SELECT generate_series('2000-01-18'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 0.5;\n-- table where dimension column is last column\nCREATE TABLE IF NOT EXISTS dimension_last(\n    id INT8 NOT NULL,\n    device_id INT NOT NULL,\n    name TEXT NOT NULL,\n    time timestamptz NOT NULL\n);\nSELECT create_hypertable('dimension_last', 'time', chunk_time_interval => interval '1day', if_not_exists => True);\n      create_hypertable      \n-----------------------------\n (2,public,dimension_last,t)\n\n-- table with only dimension column\nCREATE TABLE IF NOT EXISTS dimension_only(\n    time timestamptz NOT NULL\n);\nSELECT create_hypertable('dimension_only', 'time', chunk_time_interval => interval '1day', if_not_exists => True);\n      create_hypertable      \n-----------------------------\n (3,public,dimension_only,t)\n\nINSERT INTO dimension_last SELECT 1,1,'Device 1',generate_series('2000-01-01 0:00:00+0'::timestamptz,'2000-01-04 23:59:00+0'::timestamptz,'1m'::interval);\nINSERT INTO dimension_only VALUES\n('2000-01-01'),\n('2000-01-03'),\n('2000-01-05'),\n('2000-01-07');\nANALYZE devices;\nANALYZE ordered_append_reverse;\nANALYZE dimension_last;\nANALYZE dimension_only;\n-- create hypertable with indexes not on all chunks\nCREATE TABLE ht_missing_indexes(time timestamptz NOT NULL, device_id int, value float);\nSELECT create_hypertable('ht_missing_indexes','time');\n        create_hypertable        \n---------------------------------\n (4,public,ht_missing_indexes,t)\n\nINSERT INTO ht_missing_indexes SELECT generate_series('2000-01-01'::timestamptz,'2000-01-18'::timestamptz,'1m'::interval), 1, 0.5;\nINSERT INTO ht_missing_indexes SELECT generate_series('2000-01-01'::timestamptz,'2000-01-18'::timestamptz,'1m'::interval), 2, 1.5;\nINSERT INTO ht_missing_indexes SELECT generate_series('2000-01-01'::timestamptz,'2000-01-18'::timestamptz,'1m'::interval), 3, 2.5;\n-- drop index from 2nd chunk of ht_missing_indexes\nSELECT format('%I.%I',i.schemaname,i.indexname) AS \"INDEX_NAME\"\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable ht ON c.hypertable_id = ht.id\nINNER JOIN pg_indexes i ON i.schemaname = c.schema_name AND i.tablename=c.table_name\nWHERE ht.table_name = 'ht_missing_indexes'\nORDER BY c.id LIMIT 1 OFFSET 1 \\gset\nDROP INDEX :INDEX_NAME;\nANALYZE ht_missing_indexes;\n-- create hypertable with with dropped columns\nCREATE TABLE ht_dropped_columns(c1 int, c2 int, c3 int, c4 int, c5 int, time timestamptz NOT NULL, device_id int, value float);\nSELECT create_hypertable('ht_dropped_columns','time');\n        create_hypertable        \n---------------------------------\n (5,public,ht_dropped_columns,t)\n\nALTER TABLE ht_dropped_columns DROP COLUMN c1;\nINSERT INTO ht_dropped_columns(time,device_id,value) SELECT generate_series('2000-01-01'::timestamptz,'2000-01-02'::timestamptz,'1m'::interval), 1, 0.5;\nALTER TABLE ht_dropped_columns DROP COLUMN c2;\nINSERT INTO ht_dropped_columns(time,device_id,value) SELECT generate_series('2000-01-08'::timestamptz,'2000-01-09'::timestamptz,'1m'::interval), 1, 0.5;\nALTER TABLE ht_dropped_columns DROP COLUMN c3;\nINSERT INTO ht_dropped_columns(time,device_id,value) SELECT generate_series('2000-01-15'::timestamptz,'2000-01-16'::timestamptz,'1m'::interval), 1, 0.5;\nALTER TABLE ht_dropped_columns DROP COLUMN c4;\nINSERT INTO ht_dropped_columns(time,device_id,value) SELECT generate_series('2000-01-22'::timestamptz,'2000-01-23'::timestamptz,'1m'::interval), 1, 0.5;\nALTER TABLE ht_dropped_columns DROP COLUMN c5;\nINSERT INTO ht_dropped_columns(time,device_id,value) SELECT generate_series('2000-01-29'::timestamptz,'2000-01-30'::timestamptz,'1m'::interval), 1, 0.5;\nANALYZE ht_dropped_columns;\nCREATE TABLE space2(time timestamptz NOT NULL, device_id int NOT NULL, tag_id int NOT NULL, value float);\nSELECT create_hypertable('space2','time','device_id',number_partitions:=3);\n  create_hypertable  \n---------------------\n (6,public,space2,t)\n\nSELECT add_dimension('space2','tag_id',number_partitions:=3);\n       add_dimension        \n----------------------------\n (8,public,space2,tag_id,t)\n\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 1, 1.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 1, 2.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 3, 1, 3.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 2, 1.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 2, 2.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 3, 2, 3.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 3, 1.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 3, 2.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 3, 3, 3.5;\nANALYZE space2;\nCREATE TABLE space3(time timestamptz NOT NULL, x int NOT NULL, y int NOT NULL, z int NOT NULL, value float);\nSELECT create_hypertable('space3','time','x',number_partitions:=2);\n  create_hypertable  \n---------------------\n (7,public,space3,t)\n\nSELECT add_dimension('space3','y',number_partitions:=2);\n     add_dimension      \n------------------------\n (11,public,space3,y,t)\n\nSELECT add_dimension('space3','z',number_partitions:=2);\n     add_dimension      \n------------------------\n (12,public,space3,z,t)\n\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 1, 1, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 1, 2, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 2, 1, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 2, 2, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 1, 1, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 1, 2, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 2, 1, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 2, 2, 1.5;\nANALYZE space3;\nCREATE TABLE sortopt_test(time timestamptz NOT NULL, device TEXT);\nSELECT create_hypertable('sortopt_test','time',create_default_indexes:=false);\n     create_hypertable     \n---------------------------\n (8,public,sortopt_test,t)\n\n-- since alpine does not support locales we cant test collations in our ci\n-- CREATE COLLATION IF NOT EXISTS en_US(LOCALE='en_US.utf8');\n-- CREATE INDEX time_device_utf8 ON sortopt_test(time, device COLLATE \"en_US\");\nCREATE INDEX time_device_nullsfirst ON sortopt_test(time, device NULLS FIRST);\nCREATE INDEX time_device_nullslast ON sortopt_test(time, device DESC NULLS LAST);\nINSERT INTO sortopt_test SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 'Device 1';\nANALYZE sortopt_test;\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- print chunks ordered by time to ensure ordering we want\nSELECT\n  ht.table_name AS hypertable,\n  c.table_name AS chunk,\n  ds.range_start\nFROM\n  _timescaledb_catalog.chunk c\n  INNER JOIN LATERAL(SELECT * FROM _timescaledb_catalog.chunk_constraint cc WHERE c.id = cc.chunk_id ORDER BY cc.dimension_slice_id LIMIT 1) cc ON true\n  INNER JOIN _timescaledb_catalog.dimension_slice ds ON ds.id=cc.dimension_slice_id\n  INNER JOIN _timescaledb_catalog.dimension d ON ds.dimension_id = d.id\n  INNER JOIN _timescaledb_catalog.hypertable ht ON d.hypertable_id = ht.id\nORDER BY ht.table_name, range_start, chunk;\n       hypertable       |       chunk       |     range_start      \n------------------------+-------------------+----------------------\n dimension_last         | _hyper_2_4_chunk  |      946684800000000\n dimension_last         | _hyper_2_5_chunk  |      946771200000000\n dimension_last         | _hyper_2_6_chunk  |      946857600000000\n dimension_last         | _hyper_2_7_chunk  |      946944000000000\n dimension_only         | _hyper_3_8_chunk  |      946684800000000\n dimension_only         | _hyper_3_9_chunk  |      946857600000000\n dimension_only         | _hyper_3_10_chunk |      947030400000000\n dimension_only         | _hyper_3_11_chunk |      947203200000000\n ht_dropped_columns     | _hyper_5_15_chunk |      946512000000000\n ht_dropped_columns     | _hyper_5_16_chunk |      947116800000000\n ht_dropped_columns     | _hyper_5_17_chunk |      947721600000000\n ht_dropped_columns     | _hyper_5_18_chunk |      948326400000000\n ht_dropped_columns     | _hyper_5_19_chunk |      948931200000000\n ht_missing_indexes     | _hyper_4_12_chunk |      946512000000000\n ht_missing_indexes     | _hyper_4_13_chunk |      947116800000000\n ht_missing_indexes     | _hyper_4_14_chunk |      947721600000000\n ordered_append_reverse | _hyper_1_3_chunk  |      946512000000000\n ordered_append_reverse | _hyper_1_2_chunk  |      947116800000000\n ordered_append_reverse | _hyper_1_1_chunk  |      947721600000000\n sortopt_test           | _hyper_8_55_chunk |      946512000000000\n sortopt_test           | _hyper_8_54_chunk |      947116800000000\n space2                 | _hyper_6_21_chunk | -9223372036854775808\n space2                 | _hyper_6_23_chunk | -9223372036854775808\n space2                 | _hyper_6_25_chunk | -9223372036854775808\n space2                 | _hyper_6_27_chunk | -9223372036854775808\n space2                 | _hyper_6_33_chunk | -9223372036854775808\n space2                 | _hyper_6_29_chunk |      946512000000000\n space2                 | _hyper_6_31_chunk |      946512000000000\n space2                 | _hyper_6_35_chunk |      946512000000000\n space2                 | _hyper_6_37_chunk |      946512000000000\n space2                 | _hyper_6_20_chunk |      947116800000000\n space2                 | _hyper_6_22_chunk |      947116800000000\n space2                 | _hyper_6_24_chunk |      947116800000000\n space2                 | _hyper_6_26_chunk |      947116800000000\n space2                 | _hyper_6_28_chunk |      947116800000000\n space2                 | _hyper_6_30_chunk |      947116800000000\n space2                 | _hyper_6_32_chunk |      947116800000000\n space2                 | _hyper_6_34_chunk |      947116800000000\n space2                 | _hyper_6_36_chunk |      947116800000000\n space3                 | _hyper_7_39_chunk | -9223372036854775808\n space3                 | _hyper_7_41_chunk | -9223372036854775808\n space3                 | _hyper_7_43_chunk | -9223372036854775808\n space3                 | _hyper_7_45_chunk | -9223372036854775808\n space3                 | _hyper_7_47_chunk | -9223372036854775808\n space3                 | _hyper_7_49_chunk | -9223372036854775808\n space3                 | _hyper_7_51_chunk | -9223372036854775808\n space3                 | _hyper_7_53_chunk |      946512000000000\n space3                 | _hyper_7_38_chunk |      947116800000000\n space3                 | _hyper_7_40_chunk |      947116800000000\n space3                 | _hyper_7_42_chunk |      947116800000000\n space3                 | _hyper_7_44_chunk |      947116800000000\n space3                 | _hyper_7_46_chunk |      947116800000000\n space3                 | _hyper_7_48_chunk |      947116800000000\n space3                 | _hyper_7_50_chunk |      947116800000000\n space3                 | _hyper_7_52_chunk |      947116800000000\n\n-- test ASC for reverse ordered chunks\n:PREFIX SELECT\n  time, device_id, value\nFROM ordered_append_reverse\nORDER BY time ASC LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on ordered_append_reverse (actual rows=1.00 loops=1)\n         Order: ordered_append_reverse.\"time\"\n         ->  Index Scan Backward using _hyper_1_3_chunk_ordered_append_reverse_time_idx on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         ->  Index Scan Backward using _hyper_1_2_chunk_ordered_append_reverse_time_idx on _hyper_1_2_chunk (never executed)\n         ->  Index Scan Backward using _hyper_1_1_chunk_ordered_append_reverse_time_idx on _hyper_1_1_chunk (never executed)\n\n-- test DESC for reverse ordered chunks\n:PREFIX SELECT\n  time, device_id, value\nFROM ordered_append_reverse\nORDER BY time DESC LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on ordered_append_reverse (actual rows=1.00 loops=1)\n         Order: ordered_append_reverse.\"time\" DESC\n         ->  Index Scan using _hyper_1_1_chunk_ordered_append_reverse_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n         ->  Index Scan using _hyper_1_2_chunk_ordered_append_reverse_time_idx on _hyper_1_2_chunk (never executed)\n         ->  Index Scan using _hyper_1_3_chunk_ordered_append_reverse_time_idx on _hyper_1_3_chunk (never executed)\n\n-- test query with ORDER BY time_bucket, device_id\n-- must not use ordered append\n:PREFIX SELECT\n  time_bucket('1d',time), device_id, name\nFROM dimension_last\nORDER BY time_bucket('1d',time), device_id LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Sort (actual rows=1.00 loops=1)\n         Sort Key: (time_bucket('@ 1 day'::interval, dimension_last.\"time\")), dimension_last.device_id\n         Sort Method: top-N heapsort \n         ->  Result (actual rows=5760.00 loops=1)\n               ->  Append (actual rows=5760.00 loops=1)\n                     ->  Seq Scan on _hyper_2_4_chunk (actual rows=1440.00 loops=1)\n                     ->  Seq Scan on _hyper_2_5_chunk (actual rows=1440.00 loops=1)\n                     ->  Seq Scan on _hyper_2_6_chunk (actual rows=1440.00 loops=1)\n                     ->  Seq Scan on _hyper_2_7_chunk (actual rows=1440.00 loops=1)\n\n-- test query with ORDER BY date_trunc, device_id\n-- must not use ordered append\n:PREFIX SELECT\n  date_trunc('day',time), device_id, name\nFROM dimension_last\nORDER BY 1,2 LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Sort (actual rows=1.00 loops=1)\n         Sort Key: (date_trunc('day'::text, dimension_last.\"time\")), dimension_last.device_id\n         Sort Method: top-N heapsort \n         ->  Result (actual rows=5760.00 loops=1)\n               ->  Append (actual rows=5760.00 loops=1)\n                     ->  Seq Scan on _hyper_2_4_chunk (actual rows=1440.00 loops=1)\n                     ->  Seq Scan on _hyper_2_5_chunk (actual rows=1440.00 loops=1)\n                     ->  Seq Scan on _hyper_2_6_chunk (actual rows=1440.00 loops=1)\n                     ->  Seq Scan on _hyper_2_7_chunk (actual rows=1440.00 loops=1)\n\n-- test with table with only dimension column\n:PREFIX SELECT * FROM dimension_only ORDER BY time DESC LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on dimension_only (actual rows=1.00 loops=1)\n         Order: dimension_only.\"time\" DESC\n         ->  Index Only Scan using _hyper_3_11_chunk_dimension_only_time_idx on _hyper_3_11_chunk (actual rows=1.00 loops=1)\n         ->  Index Only Scan using _hyper_3_10_chunk_dimension_only_time_idx on _hyper_3_10_chunk (never executed)\n         ->  Index Only Scan using _hyper_3_9_chunk_dimension_only_time_idx on _hyper_3_9_chunk (never executed)\n         ->  Index Only Scan using _hyper_3_8_chunk_dimension_only_time_idx on _hyper_3_8_chunk (never executed)\n\n-- test LEFT JOIN against hypertable\n:PREFIX_NO_ANALYZE SELECT *\nFROM dimension_last\nLEFT JOIN dimension_only USING (time)\nORDER BY dimension_last.time DESC\nLIMIT 2;\n--- QUERY PLAN ---\n Limit\n   ->  Nested Loop Left Join\n         Join Filter: (dimension_last.\"time\" = dimension_only.\"time\")\n         ->  Custom Scan (ChunkAppend) on dimension_last\n               Order: dimension_last.\"time\" DESC\n               ->  Index Scan using _hyper_2_7_chunk_dimension_last_time_idx on _hyper_2_7_chunk\n               ->  Index Scan using _hyper_2_6_chunk_dimension_last_time_idx on _hyper_2_6_chunk\n               ->  Index Scan using _hyper_2_5_chunk_dimension_last_time_idx on _hyper_2_5_chunk\n               ->  Index Scan using _hyper_2_4_chunk_dimension_last_time_idx on _hyper_2_4_chunk\n         ->  Materialize\n               ->  Append\n                     ->  Seq Scan on _hyper_3_11_chunk\n                     ->  Seq Scan on _hyper_3_10_chunk\n                     ->  Seq Scan on _hyper_3_9_chunk\n                     ->  Seq Scan on _hyper_3_8_chunk\n\n-- test INNER JOIN against non-hypertable\n:PREFIX_NO_ANALYZE SELECT *\nFROM dimension_last\nINNER JOIN dimension_only USING (time)\nORDER BY dimension_last.time DESC\nLIMIT 2;\n--- QUERY PLAN ---\n Limit\n   ->  Nested Loop\n         ->  Custom Scan (ChunkAppend) on dimension_only\n               Order: dimension_only.\"time\" DESC\n               ->  Index Only Scan using _hyper_3_11_chunk_dimension_only_time_idx on _hyper_3_11_chunk\n               ->  Index Only Scan using _hyper_3_10_chunk_dimension_only_time_idx on _hyper_3_10_chunk\n               ->  Index Only Scan using _hyper_3_9_chunk_dimension_only_time_idx on _hyper_3_9_chunk\n               ->  Index Only Scan using _hyper_3_8_chunk_dimension_only_time_idx on _hyper_3_8_chunk\n         ->  Append\n               ->  Index Scan using _hyper_2_7_chunk_dimension_last_time_idx on _hyper_2_7_chunk\n                     Index Cond: (\"time\" = dimension_only.\"time\")\n               ->  Index Scan using _hyper_2_6_chunk_dimension_last_time_idx on _hyper_2_6_chunk\n                     Index Cond: (\"time\" = dimension_only.\"time\")\n               ->  Index Scan using _hyper_2_5_chunk_dimension_last_time_idx on _hyper_2_5_chunk\n                     Index Cond: (\"time\" = dimension_only.\"time\")\n               ->  Index Scan using _hyper_2_4_chunk_dimension_last_time_idx on _hyper_2_4_chunk\n                     Index Cond: (\"time\" = dimension_only.\"time\")\n\n-- test join against non-hypertable\n:PREFIX SELECT *\nFROM dimension_last\nINNER JOIN devices USING(device_id)\nORDER BY dimension_last.time DESC\nLIMIT 2;\n--- QUERY PLAN ---\n Limit (actual rows=2.00 loops=1)\n   ->  Nested Loop (actual rows=2.00 loops=1)\n         Join Filter: (devices.device_id = dimension_last.device_id)\n         ->  Custom Scan (ChunkAppend) on dimension_last (actual rows=2.00 loops=1)\n               Order: dimension_last.\"time\" DESC\n               ->  Index Scan using _hyper_2_7_chunk_dimension_last_time_idx on _hyper_2_7_chunk (actual rows=2.00 loops=1)\n               ->  Index Scan using _hyper_2_6_chunk_dimension_last_time_idx on _hyper_2_6_chunk (never executed)\n               ->  Index Scan using _hyper_2_5_chunk_dimension_last_time_idx on _hyper_2_5_chunk (never executed)\n               ->  Index Scan using _hyper_2_4_chunk_dimension_last_time_idx on _hyper_2_4_chunk (never executed)\n         ->  Materialize (actual rows=1.00 loops=2)\n               ->  Seq Scan on devices (actual rows=1.00 loops=1)\n\n-- test hypertable with index missing on one chunk\n:PREFIX SELECT\n  time, device_id, value\nFROM ht_missing_indexes\nORDER BY time ASC LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on ht_missing_indexes (actual rows=1.00 loops=1)\n         Order: ht_missing_indexes.\"time\"\n         ->  Index Scan Backward using _hyper_4_12_chunk_ht_missing_indexes_time_idx on _hyper_4_12_chunk (actual rows=1.00 loops=1)\n         ->  Sort (never executed)\n               Sort Key: _hyper_4_13_chunk.\"time\"\n               ->  Seq Scan on _hyper_4_13_chunk (never executed)\n         ->  Index Scan Backward using _hyper_4_14_chunk_ht_missing_indexes_time_idx on _hyper_4_14_chunk (never executed)\n\n-- test hypertable with index missing on one chunk\n-- and no data\n:PREFIX SELECT\n  time, device_id, value\nFROM ht_missing_indexes\nWHERE device_id = 2\nORDER BY time DESC LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on ht_missing_indexes (actual rows=1.00 loops=1)\n         Order: ht_missing_indexes.\"time\" DESC\n         ->  Index Scan using _hyper_4_14_chunk_ht_missing_indexes_time_idx on _hyper_4_14_chunk (actual rows=1.00 loops=1)\n               Filter: (device_id = 2)\n               Rows Removed by Filter: 1\n         ->  Sort (never executed)\n               Sort Key: _hyper_4_13_chunk.\"time\" DESC\n               ->  Seq Scan on _hyper_4_13_chunk (never executed)\n                     Filter: (device_id = 2)\n         ->  Index Scan using _hyper_4_12_chunk_ht_missing_indexes_time_idx on _hyper_4_12_chunk (never executed)\n               Filter: (device_id = 2)\n\n-- test hypertable with index missing on one chunk\n-- and no data\n:PREFIX SELECT\n  time, device_id, value\nFROM ht_missing_indexes\nWHERE time > '2000-01-07'\nORDER BY time LIMIT 10;\n--- QUERY PLAN ---\n Limit (actual rows=10.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on ht_missing_indexes (actual rows=10.00 loops=1)\n         Order: ht_missing_indexes.\"time\"\n         ->  Sort (actual rows=10.00 loops=1)\n               Sort Key: _hyper_4_13_chunk.\"time\"\n               Sort Method: top-N heapsort \n               ->  Seq Scan on _hyper_4_13_chunk (actual rows=24477.00 loops=1)\n                     Filter: (\"time\" > 'Fri Jan 07 00:00:00 2000 PST'::timestamp with time zone)\n                     Rows Removed by Filter: 5763\n         ->  Index Scan Backward using _hyper_4_14_chunk_ht_missing_indexes_time_idx on _hyper_4_14_chunk (never executed)\n\n-- test hypertable with dropped columns\n:PREFIX SELECT\n  time, device_id, value\nFROM ht_dropped_columns\nORDER BY time ASC LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on ht_dropped_columns (actual rows=1.00 loops=1)\n         Order: ht_dropped_columns.\"time\"\n         ->  Index Scan Backward using _hyper_5_15_chunk_ht_dropped_columns_time_idx on _hyper_5_15_chunk (actual rows=1.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_16_chunk_ht_dropped_columns_time_idx on _hyper_5_16_chunk (never executed)\n         ->  Index Scan Backward using _hyper_5_17_chunk_ht_dropped_columns_time_idx on _hyper_5_17_chunk (never executed)\n         ->  Index Scan Backward using _hyper_5_18_chunk_ht_dropped_columns_time_idx on _hyper_5_18_chunk (never executed)\n         ->  Index Scan Backward using _hyper_5_19_chunk_ht_dropped_columns_time_idx on _hyper_5_19_chunk (never executed)\n\n-- test hypertable with dropped columns\n:PREFIX SELECT\n  time, device_id, value\nFROM ht_dropped_columns\nWHERE device_id = 1\nORDER BY time DESC;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on ht_dropped_columns (actual rows=7205.00 loops=1)\n   Order: ht_dropped_columns.\"time\" DESC\n   ->  Index Scan using _hyper_5_19_chunk_ht_dropped_columns_time_idx on _hyper_5_19_chunk (actual rows=1441.00 loops=1)\n         Filter: (device_id = 1)\n   ->  Index Scan using _hyper_5_18_chunk_ht_dropped_columns_time_idx on _hyper_5_18_chunk (actual rows=1441.00 loops=1)\n         Filter: (device_id = 1)\n   ->  Index Scan using _hyper_5_17_chunk_ht_dropped_columns_time_idx on _hyper_5_17_chunk (actual rows=1441.00 loops=1)\n         Filter: (device_id = 1)\n   ->  Index Scan using _hyper_5_16_chunk_ht_dropped_columns_time_idx on _hyper_5_16_chunk (actual rows=1441.00 loops=1)\n         Filter: (device_id = 1)\n   ->  Index Scan using _hyper_5_15_chunk_ht_dropped_columns_time_idx on _hyper_5_15_chunk (actual rows=1441.00 loops=1)\n         Filter: (device_id = 1)\n\n-- test hypertable with 2 space dimensions\n:PREFIX SELECT\n  time, device_id, value\nFROM space2\nORDER BY time DESC;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on space2 (actual rows=116649.00 loops=1)\n   Order: space2.\"time\" DESC\n   ->  Merge Append (actual rows=56169.00 loops=1)\n         Sort Key: space2.\"time\" DESC\n         ->  Index Scan using _hyper_6_36_chunk_space2_time_idx on _hyper_6_36_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_34_chunk_space2_time_idx on _hyper_6_34_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_32_chunk_space2_time_idx on _hyper_6_32_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_30_chunk_space2_time_idx on _hyper_6_30_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_28_chunk_space2_time_idx on _hyper_6_28_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_26_chunk_space2_time_idx on _hyper_6_26_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_24_chunk_space2_time_idx on _hyper_6_24_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_22_chunk_space2_time_idx on _hyper_6_22_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_20_chunk_space2_time_idx on _hyper_6_20_chunk (actual rows=6241.00 loops=1)\n   ->  Merge Append (actual rows=60480.00 loops=1)\n         Sort Key: space2.\"time\" DESC\n         ->  Index Scan using _hyper_6_37_chunk_space2_time_idx on _hyper_6_37_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_35_chunk_space2_time_idx on _hyper_6_35_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_33_chunk_space2_time_idx on _hyper_6_33_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_31_chunk_space2_time_idx on _hyper_6_31_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_29_chunk_space2_time_idx on _hyper_6_29_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_27_chunk_space2_time_idx on _hyper_6_27_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_25_chunk_space2_time_idx on _hyper_6_25_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_23_chunk_space2_time_idx on _hyper_6_23_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_21_chunk_space2_time_idx on _hyper_6_21_chunk (actual rows=6720.00 loops=1)\n\n-- test hypertable with 3 space dimensions\n:PREFIX SELECT\n  time\nFROM space3\nORDER BY time DESC;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on space3 (actual rows=103688.00 loops=1)\n   Order: space3.\"time\" DESC\n   ->  Merge Append (actual rows=49928.00 loops=1)\n         Sort Key: space3.\"time\" DESC\n         ->  Index Only Scan using _hyper_7_52_chunk_space3_time_idx on _hyper_7_52_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_50_chunk_space3_time_idx on _hyper_7_50_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_48_chunk_space3_time_idx on _hyper_7_48_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_46_chunk_space3_time_idx on _hyper_7_46_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_44_chunk_space3_time_idx on _hyper_7_44_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_42_chunk_space3_time_idx on _hyper_7_42_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_40_chunk_space3_time_idx on _hyper_7_40_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_38_chunk_space3_time_idx on _hyper_7_38_chunk (actual rows=6241.00 loops=1)\n   ->  Merge Append (actual rows=53760.00 loops=1)\n         Sort Key: space3.\"time\" DESC\n         ->  Index Only Scan using _hyper_7_53_chunk_space3_time_idx on _hyper_7_53_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_51_chunk_space3_time_idx on _hyper_7_51_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_49_chunk_space3_time_idx on _hyper_7_49_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_47_chunk_space3_time_idx on _hyper_7_47_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_45_chunk_space3_time_idx on _hyper_7_45_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_43_chunk_space3_time_idx on _hyper_7_43_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_41_chunk_space3_time_idx on _hyper_7_41_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_39_chunk_space3_time_idx on _hyper_7_39_chunk (actual rows=6720.00 loops=1)\n\n-- test COLLATION\n-- cant be tested in our ci because alpine doesnt support locales\n-- :PREFIX SELECT * FROM sortopt_test ORDER BY time, device COLLATE \"en_US.utf8\";\n-- test NULLS FIRST\n:PREFIX SELECT * FROM sortopt_test ORDER BY time, device NULLS FIRST;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on sortopt_test (actual rows=12961.00 loops=1)\n   Order: sortopt_test.\"time\", sortopt_test.device NULLS FIRST\n   ->  Index Only Scan using _hyper_8_55_chunk_time_device_nullsfirst on _hyper_8_55_chunk (actual rows=6720.00 loops=1)\n   ->  Index Only Scan using _hyper_8_54_chunk_time_device_nullsfirst on _hyper_8_54_chunk (actual rows=6241.00 loops=1)\n\n-- test NULLS LAST\n:PREFIX SELECT * FROM sortopt_test ORDER BY time, device DESC NULLS LAST;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on sortopt_test (actual rows=12961.00 loops=1)\n   Order: sortopt_test.\"time\", sortopt_test.device DESC NULLS LAST\n   ->  Index Only Scan using _hyper_8_55_chunk_time_device_nullslast on _hyper_8_55_chunk (actual rows=6720.00 loops=1)\n   ->  Index Only Scan using _hyper_8_54_chunk_time_device_nullslast on _hyper_8_54_chunk (actual rows=6241.00 loops=1)\n\n--generate the results into two different files\n\\set ECHO errors\n"
  },
  {
    "path": "test/expected/plan_ordered_append-17.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- we run these with analyze to confirm that nodes that are not\n-- needed to fulfill the limit are not executed\n-- unfortunately this doesn't work on PostgreSQL 9.6 which lacks\n-- the ability to turn off analyze timing summary so we run\n-- them without ANALYZE on PostgreSQL 9.6, but since LATERAL plans\n-- are different across versions we need version specific output\n-- here anyway.\n\\set TEST_BASE_NAME plan_ordered_append\nSELECT format('include/%s_load.sql', :'TEST_BASE_NAME') as \"TEST_LOAD_NAME\",\n       format('include/%s_query.sql', :'TEST_BASE_NAME') as \"TEST_QUERY_NAME\",\n       format('%s/results/%s_results_optimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_OPTIMIZED\",\n       format('%s/results/%s_results_unoptimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_UNOPTIMIZED\"\n\\gset\nSELECT format('\\! diff -u --label \"Unoptimized result\" --label \"Optimized result\" %s %s', :'TEST_RESULTS_UNOPTIMIZED', :'TEST_RESULTS_OPTIMIZED') as \"DIFF_CMD\"\n\\gset\n\\set PREFIX 'EXPLAIN (analyze, buffers off, costs off, timing off, summary off)'\n\\set PREFIX_NO_ANALYZE 'EXPLAIN (buffers off, costs off)'\n\\ir :TEST_LOAD_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- create a now() function for repeatable testing that always returns\n-- the same timestamp. It needs to be marked STABLE\nCREATE OR REPLACE FUNCTION now_s()\nRETURNS timestamptz LANGUAGE PLPGSQL STABLE AS\n$BODY$\nBEGIN\n    RETURN '2000-01-08T0:00:00+0'::timestamptz;\nEND;\n$BODY$;\nCREATE TABLE devices(device_id INT PRIMARY KEY, name TEXT);\nINSERT INTO devices VALUES\n(1,'Device 1'),\n(2,'Device 2'),\n(3,'Device 3');\n-- create a second table where we create chunks in reverse order\nCREATE TABLE ordered_append_reverse(time timestamptz NOT NULL, device_id INT, value float);\nSELECT create_hypertable('ordered_append_reverse','time');\n          create_hypertable          \n-------------------------------------\n (1,public,ordered_append_reverse,t)\n\nINSERT INTO ordered_append_reverse SELECT generate_series('2000-01-18'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 0.5;\n-- table where dimension column is last column\nCREATE TABLE IF NOT EXISTS dimension_last(\n    id INT8 NOT NULL,\n    device_id INT NOT NULL,\n    name TEXT NOT NULL,\n    time timestamptz NOT NULL\n);\nSELECT create_hypertable('dimension_last', 'time', chunk_time_interval => interval '1day', if_not_exists => True);\n      create_hypertable      \n-----------------------------\n (2,public,dimension_last,t)\n\n-- table with only dimension column\nCREATE TABLE IF NOT EXISTS dimension_only(\n    time timestamptz NOT NULL\n);\nSELECT create_hypertable('dimension_only', 'time', chunk_time_interval => interval '1day', if_not_exists => True);\n      create_hypertable      \n-----------------------------\n (3,public,dimension_only,t)\n\nINSERT INTO dimension_last SELECT 1,1,'Device 1',generate_series('2000-01-01 0:00:00+0'::timestamptz,'2000-01-04 23:59:00+0'::timestamptz,'1m'::interval);\nINSERT INTO dimension_only VALUES\n('2000-01-01'),\n('2000-01-03'),\n('2000-01-05'),\n('2000-01-07');\nANALYZE devices;\nANALYZE ordered_append_reverse;\nANALYZE dimension_last;\nANALYZE dimension_only;\n-- create hypertable with indexes not on all chunks\nCREATE TABLE ht_missing_indexes(time timestamptz NOT NULL, device_id int, value float);\nSELECT create_hypertable('ht_missing_indexes','time');\n        create_hypertable        \n---------------------------------\n (4,public,ht_missing_indexes,t)\n\nINSERT INTO ht_missing_indexes SELECT generate_series('2000-01-01'::timestamptz,'2000-01-18'::timestamptz,'1m'::interval), 1, 0.5;\nINSERT INTO ht_missing_indexes SELECT generate_series('2000-01-01'::timestamptz,'2000-01-18'::timestamptz,'1m'::interval), 2, 1.5;\nINSERT INTO ht_missing_indexes SELECT generate_series('2000-01-01'::timestamptz,'2000-01-18'::timestamptz,'1m'::interval), 3, 2.5;\n-- drop index from 2nd chunk of ht_missing_indexes\nSELECT format('%I.%I',i.schemaname,i.indexname) AS \"INDEX_NAME\"\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable ht ON c.hypertable_id = ht.id\nINNER JOIN pg_indexes i ON i.schemaname = c.schema_name AND i.tablename=c.table_name\nWHERE ht.table_name = 'ht_missing_indexes'\nORDER BY c.id LIMIT 1 OFFSET 1 \\gset\nDROP INDEX :INDEX_NAME;\nANALYZE ht_missing_indexes;\n-- create hypertable with with dropped columns\nCREATE TABLE ht_dropped_columns(c1 int, c2 int, c3 int, c4 int, c5 int, time timestamptz NOT NULL, device_id int, value float);\nSELECT create_hypertable('ht_dropped_columns','time');\n        create_hypertable        \n---------------------------------\n (5,public,ht_dropped_columns,t)\n\nALTER TABLE ht_dropped_columns DROP COLUMN c1;\nINSERT INTO ht_dropped_columns(time,device_id,value) SELECT generate_series('2000-01-01'::timestamptz,'2000-01-02'::timestamptz,'1m'::interval), 1, 0.5;\nALTER TABLE ht_dropped_columns DROP COLUMN c2;\nINSERT INTO ht_dropped_columns(time,device_id,value) SELECT generate_series('2000-01-08'::timestamptz,'2000-01-09'::timestamptz,'1m'::interval), 1, 0.5;\nALTER TABLE ht_dropped_columns DROP COLUMN c3;\nINSERT INTO ht_dropped_columns(time,device_id,value) SELECT generate_series('2000-01-15'::timestamptz,'2000-01-16'::timestamptz,'1m'::interval), 1, 0.5;\nALTER TABLE ht_dropped_columns DROP COLUMN c4;\nINSERT INTO ht_dropped_columns(time,device_id,value) SELECT generate_series('2000-01-22'::timestamptz,'2000-01-23'::timestamptz,'1m'::interval), 1, 0.5;\nALTER TABLE ht_dropped_columns DROP COLUMN c5;\nINSERT INTO ht_dropped_columns(time,device_id,value) SELECT generate_series('2000-01-29'::timestamptz,'2000-01-30'::timestamptz,'1m'::interval), 1, 0.5;\nANALYZE ht_dropped_columns;\nCREATE TABLE space2(time timestamptz NOT NULL, device_id int NOT NULL, tag_id int NOT NULL, value float);\nSELECT create_hypertable('space2','time','device_id',number_partitions:=3);\n  create_hypertable  \n---------------------\n (6,public,space2,t)\n\nSELECT add_dimension('space2','tag_id',number_partitions:=3);\n       add_dimension        \n----------------------------\n (8,public,space2,tag_id,t)\n\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 1, 1.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 1, 2.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 3, 1, 3.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 2, 1.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 2, 2.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 3, 2, 3.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 3, 1.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 3, 2.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 3, 3, 3.5;\nANALYZE space2;\nCREATE TABLE space3(time timestamptz NOT NULL, x int NOT NULL, y int NOT NULL, z int NOT NULL, value float);\nSELECT create_hypertable('space3','time','x',number_partitions:=2);\n  create_hypertable  \n---------------------\n (7,public,space3,t)\n\nSELECT add_dimension('space3','y',number_partitions:=2);\n     add_dimension      \n------------------------\n (11,public,space3,y,t)\n\nSELECT add_dimension('space3','z',number_partitions:=2);\n     add_dimension      \n------------------------\n (12,public,space3,z,t)\n\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 1, 1, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 1, 2, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 2, 1, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 2, 2, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 1, 1, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 1, 2, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 2, 1, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 2, 2, 1.5;\nANALYZE space3;\nCREATE TABLE sortopt_test(time timestamptz NOT NULL, device TEXT);\nSELECT create_hypertable('sortopt_test','time',create_default_indexes:=false);\n     create_hypertable     \n---------------------------\n (8,public,sortopt_test,t)\n\n-- since alpine does not support locales we cant test collations in our ci\n-- CREATE COLLATION IF NOT EXISTS en_US(LOCALE='en_US.utf8');\n-- CREATE INDEX time_device_utf8 ON sortopt_test(time, device COLLATE \"en_US\");\nCREATE INDEX time_device_nullsfirst ON sortopt_test(time, device NULLS FIRST);\nCREATE INDEX time_device_nullslast ON sortopt_test(time, device DESC NULLS LAST);\nINSERT INTO sortopt_test SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 'Device 1';\nANALYZE sortopt_test;\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- print chunks ordered by time to ensure ordering we want\nSELECT\n  ht.table_name AS hypertable,\n  c.table_name AS chunk,\n  ds.range_start\nFROM\n  _timescaledb_catalog.chunk c\n  INNER JOIN LATERAL(SELECT * FROM _timescaledb_catalog.chunk_constraint cc WHERE c.id = cc.chunk_id ORDER BY cc.dimension_slice_id LIMIT 1) cc ON true\n  INNER JOIN _timescaledb_catalog.dimension_slice ds ON ds.id=cc.dimension_slice_id\n  INNER JOIN _timescaledb_catalog.dimension d ON ds.dimension_id = d.id\n  INNER JOIN _timescaledb_catalog.hypertable ht ON d.hypertable_id = ht.id\nORDER BY ht.table_name, range_start, chunk;\n       hypertable       |       chunk       |     range_start      \n------------------------+-------------------+----------------------\n dimension_last         | _hyper_2_4_chunk  |      946684800000000\n dimension_last         | _hyper_2_5_chunk  |      946771200000000\n dimension_last         | _hyper_2_6_chunk  |      946857600000000\n dimension_last         | _hyper_2_7_chunk  |      946944000000000\n dimension_only         | _hyper_3_8_chunk  |      946684800000000\n dimension_only         | _hyper_3_9_chunk  |      946857600000000\n dimension_only         | _hyper_3_10_chunk |      947030400000000\n dimension_only         | _hyper_3_11_chunk |      947203200000000\n ht_dropped_columns     | _hyper_5_15_chunk |      946512000000000\n ht_dropped_columns     | _hyper_5_16_chunk |      947116800000000\n ht_dropped_columns     | _hyper_5_17_chunk |      947721600000000\n ht_dropped_columns     | _hyper_5_18_chunk |      948326400000000\n ht_dropped_columns     | _hyper_5_19_chunk |      948931200000000\n ht_missing_indexes     | _hyper_4_12_chunk |      946512000000000\n ht_missing_indexes     | _hyper_4_13_chunk |      947116800000000\n ht_missing_indexes     | _hyper_4_14_chunk |      947721600000000\n ordered_append_reverse | _hyper_1_3_chunk  |      946512000000000\n ordered_append_reverse | _hyper_1_2_chunk  |      947116800000000\n ordered_append_reverse | _hyper_1_1_chunk  |      947721600000000\n sortopt_test           | _hyper_8_55_chunk |      946512000000000\n sortopt_test           | _hyper_8_54_chunk |      947116800000000\n space2                 | _hyper_6_21_chunk | -9223372036854775808\n space2                 | _hyper_6_23_chunk | -9223372036854775808\n space2                 | _hyper_6_25_chunk | -9223372036854775808\n space2                 | _hyper_6_27_chunk | -9223372036854775808\n space2                 | _hyper_6_33_chunk | -9223372036854775808\n space2                 | _hyper_6_29_chunk |      946512000000000\n space2                 | _hyper_6_31_chunk |      946512000000000\n space2                 | _hyper_6_35_chunk |      946512000000000\n space2                 | _hyper_6_37_chunk |      946512000000000\n space2                 | _hyper_6_20_chunk |      947116800000000\n space2                 | _hyper_6_22_chunk |      947116800000000\n space2                 | _hyper_6_24_chunk |      947116800000000\n space2                 | _hyper_6_26_chunk |      947116800000000\n space2                 | _hyper_6_28_chunk |      947116800000000\n space2                 | _hyper_6_30_chunk |      947116800000000\n space2                 | _hyper_6_32_chunk |      947116800000000\n space2                 | _hyper_6_34_chunk |      947116800000000\n space2                 | _hyper_6_36_chunk |      947116800000000\n space3                 | _hyper_7_39_chunk | -9223372036854775808\n space3                 | _hyper_7_41_chunk | -9223372036854775808\n space3                 | _hyper_7_43_chunk | -9223372036854775808\n space3                 | _hyper_7_45_chunk | -9223372036854775808\n space3                 | _hyper_7_47_chunk | -9223372036854775808\n space3                 | _hyper_7_49_chunk | -9223372036854775808\n space3                 | _hyper_7_51_chunk | -9223372036854775808\n space3                 | _hyper_7_53_chunk |      946512000000000\n space3                 | _hyper_7_38_chunk |      947116800000000\n space3                 | _hyper_7_40_chunk |      947116800000000\n space3                 | _hyper_7_42_chunk |      947116800000000\n space3                 | _hyper_7_44_chunk |      947116800000000\n space3                 | _hyper_7_46_chunk |      947116800000000\n space3                 | _hyper_7_48_chunk |      947116800000000\n space3                 | _hyper_7_50_chunk |      947116800000000\n space3                 | _hyper_7_52_chunk |      947116800000000\n\n-- test ASC for reverse ordered chunks\n:PREFIX SELECT\n  time, device_id, value\nFROM ordered_append_reverse\nORDER BY time ASC LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on ordered_append_reverse (actual rows=1.00 loops=1)\n         Order: ordered_append_reverse.\"time\"\n         ->  Index Scan Backward using _hyper_1_3_chunk_ordered_append_reverse_time_idx on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         ->  Index Scan Backward using _hyper_1_2_chunk_ordered_append_reverse_time_idx on _hyper_1_2_chunk (never executed)\n         ->  Index Scan Backward using _hyper_1_1_chunk_ordered_append_reverse_time_idx on _hyper_1_1_chunk (never executed)\n\n-- test DESC for reverse ordered chunks\n:PREFIX SELECT\n  time, device_id, value\nFROM ordered_append_reverse\nORDER BY time DESC LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on ordered_append_reverse (actual rows=1.00 loops=1)\n         Order: ordered_append_reverse.\"time\" DESC\n         ->  Index Scan using _hyper_1_1_chunk_ordered_append_reverse_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n         ->  Index Scan using _hyper_1_2_chunk_ordered_append_reverse_time_idx on _hyper_1_2_chunk (never executed)\n         ->  Index Scan using _hyper_1_3_chunk_ordered_append_reverse_time_idx on _hyper_1_3_chunk (never executed)\n\n-- test query with ORDER BY time_bucket, device_id\n-- must not use ordered append\n:PREFIX SELECT\n  time_bucket('1d',time), device_id, name\nFROM dimension_last\nORDER BY time_bucket('1d',time), device_id LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Sort (actual rows=1.00 loops=1)\n         Sort Key: (time_bucket('@ 1 day'::interval, dimension_last.\"time\")), dimension_last.device_id\n         Sort Method: top-N heapsort \n         ->  Result (actual rows=5760.00 loops=1)\n               ->  Append (actual rows=5760.00 loops=1)\n                     ->  Seq Scan on _hyper_2_4_chunk (actual rows=1440.00 loops=1)\n                     ->  Seq Scan on _hyper_2_5_chunk (actual rows=1440.00 loops=1)\n                     ->  Seq Scan on _hyper_2_6_chunk (actual rows=1440.00 loops=1)\n                     ->  Seq Scan on _hyper_2_7_chunk (actual rows=1440.00 loops=1)\n\n-- test query with ORDER BY date_trunc, device_id\n-- must not use ordered append\n:PREFIX SELECT\n  date_trunc('day',time), device_id, name\nFROM dimension_last\nORDER BY 1,2 LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Sort (actual rows=1.00 loops=1)\n         Sort Key: (date_trunc('day'::text, dimension_last.\"time\")), dimension_last.device_id\n         Sort Method: top-N heapsort \n         ->  Result (actual rows=5760.00 loops=1)\n               ->  Append (actual rows=5760.00 loops=1)\n                     ->  Seq Scan on _hyper_2_4_chunk (actual rows=1440.00 loops=1)\n                     ->  Seq Scan on _hyper_2_5_chunk (actual rows=1440.00 loops=1)\n                     ->  Seq Scan on _hyper_2_6_chunk (actual rows=1440.00 loops=1)\n                     ->  Seq Scan on _hyper_2_7_chunk (actual rows=1440.00 loops=1)\n\n-- test with table with only dimension column\n:PREFIX SELECT * FROM dimension_only ORDER BY time DESC LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on dimension_only (actual rows=1.00 loops=1)\n         Order: dimension_only.\"time\" DESC\n         ->  Index Only Scan using _hyper_3_11_chunk_dimension_only_time_idx on _hyper_3_11_chunk (actual rows=1.00 loops=1)\n         ->  Index Only Scan using _hyper_3_10_chunk_dimension_only_time_idx on _hyper_3_10_chunk (never executed)\n         ->  Index Only Scan using _hyper_3_9_chunk_dimension_only_time_idx on _hyper_3_9_chunk (never executed)\n         ->  Index Only Scan using _hyper_3_8_chunk_dimension_only_time_idx on _hyper_3_8_chunk (never executed)\n\n-- test LEFT JOIN against hypertable\n:PREFIX_NO_ANALYZE SELECT *\nFROM dimension_last\nLEFT JOIN dimension_only USING (time)\nORDER BY dimension_last.time DESC\nLIMIT 2;\n--- QUERY PLAN ---\n Limit\n   ->  Nested Loop Left Join\n         Join Filter: (dimension_last.\"time\" = dimension_only.\"time\")\n         ->  Custom Scan (ChunkAppend) on dimension_last\n               Order: dimension_last.\"time\" DESC\n               ->  Index Scan using _hyper_2_7_chunk_dimension_last_time_idx on _hyper_2_7_chunk\n               ->  Index Scan using _hyper_2_6_chunk_dimension_last_time_idx on _hyper_2_6_chunk\n               ->  Index Scan using _hyper_2_5_chunk_dimension_last_time_idx on _hyper_2_5_chunk\n               ->  Index Scan using _hyper_2_4_chunk_dimension_last_time_idx on _hyper_2_4_chunk\n         ->  Materialize\n               ->  Append\n                     ->  Seq Scan on _hyper_3_11_chunk\n                     ->  Seq Scan on _hyper_3_10_chunk\n                     ->  Seq Scan on _hyper_3_9_chunk\n                     ->  Seq Scan on _hyper_3_8_chunk\n\n-- test INNER JOIN against non-hypertable\n:PREFIX_NO_ANALYZE SELECT *\nFROM dimension_last\nINNER JOIN dimension_only USING (time)\nORDER BY dimension_last.time DESC\nLIMIT 2;\n--- QUERY PLAN ---\n Limit\n   ->  Nested Loop\n         ->  Custom Scan (ChunkAppend) on dimension_only\n               Order: dimension_only.\"time\" DESC\n               ->  Index Only Scan using _hyper_3_11_chunk_dimension_only_time_idx on _hyper_3_11_chunk\n               ->  Index Only Scan using _hyper_3_10_chunk_dimension_only_time_idx on _hyper_3_10_chunk\n               ->  Index Only Scan using _hyper_3_9_chunk_dimension_only_time_idx on _hyper_3_9_chunk\n               ->  Index Only Scan using _hyper_3_8_chunk_dimension_only_time_idx on _hyper_3_8_chunk\n         ->  Append\n               ->  Index Scan using _hyper_2_7_chunk_dimension_last_time_idx on _hyper_2_7_chunk\n                     Index Cond: (\"time\" = dimension_only.\"time\")\n               ->  Index Scan using _hyper_2_6_chunk_dimension_last_time_idx on _hyper_2_6_chunk\n                     Index Cond: (\"time\" = dimension_only.\"time\")\n               ->  Index Scan using _hyper_2_5_chunk_dimension_last_time_idx on _hyper_2_5_chunk\n                     Index Cond: (\"time\" = dimension_only.\"time\")\n               ->  Index Scan using _hyper_2_4_chunk_dimension_last_time_idx on _hyper_2_4_chunk\n                     Index Cond: (\"time\" = dimension_only.\"time\")\n\n-- test join against non-hypertable\n:PREFIX SELECT *\nFROM dimension_last\nINNER JOIN devices USING(device_id)\nORDER BY dimension_last.time DESC\nLIMIT 2;\n--- QUERY PLAN ---\n Limit (actual rows=2.00 loops=1)\n   ->  Nested Loop (actual rows=2.00 loops=1)\n         Join Filter: (devices.device_id = dimension_last.device_id)\n         ->  Custom Scan (ChunkAppend) on dimension_last (actual rows=2.00 loops=1)\n               Order: dimension_last.\"time\" DESC\n               ->  Index Scan using _hyper_2_7_chunk_dimension_last_time_idx on _hyper_2_7_chunk (actual rows=2.00 loops=1)\n               ->  Index Scan using _hyper_2_6_chunk_dimension_last_time_idx on _hyper_2_6_chunk (never executed)\n               ->  Index Scan using _hyper_2_5_chunk_dimension_last_time_idx on _hyper_2_5_chunk (never executed)\n               ->  Index Scan using _hyper_2_4_chunk_dimension_last_time_idx on _hyper_2_4_chunk (never executed)\n         ->  Materialize (actual rows=1.00 loops=2)\n               ->  Seq Scan on devices (actual rows=1.00 loops=1)\n\n-- test hypertable with index missing on one chunk\n:PREFIX SELECT\n  time, device_id, value\nFROM ht_missing_indexes\nORDER BY time ASC LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on ht_missing_indexes (actual rows=1.00 loops=1)\n         Order: ht_missing_indexes.\"time\"\n         ->  Index Scan Backward using _hyper_4_12_chunk_ht_missing_indexes_time_idx on _hyper_4_12_chunk (actual rows=1.00 loops=1)\n         ->  Sort (never executed)\n               Sort Key: _hyper_4_13_chunk.\"time\"\n               ->  Seq Scan on _hyper_4_13_chunk (never executed)\n         ->  Index Scan Backward using _hyper_4_14_chunk_ht_missing_indexes_time_idx on _hyper_4_14_chunk (never executed)\n\n-- test hypertable with index missing on one chunk\n-- and no data\n:PREFIX SELECT\n  time, device_id, value\nFROM ht_missing_indexes\nWHERE device_id = 2\nORDER BY time DESC LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on ht_missing_indexes (actual rows=1.00 loops=1)\n         Order: ht_missing_indexes.\"time\" DESC\n         ->  Index Scan using _hyper_4_14_chunk_ht_missing_indexes_time_idx on _hyper_4_14_chunk (actual rows=1.00 loops=1)\n               Filter: (device_id = 2)\n               Rows Removed by Filter: 1\n         ->  Sort (never executed)\n               Sort Key: _hyper_4_13_chunk.\"time\" DESC\n               ->  Seq Scan on _hyper_4_13_chunk (never executed)\n                     Filter: (device_id = 2)\n         ->  Index Scan using _hyper_4_12_chunk_ht_missing_indexes_time_idx on _hyper_4_12_chunk (never executed)\n               Filter: (device_id = 2)\n\n-- test hypertable with index missing on one chunk\n-- and no data\n:PREFIX SELECT\n  time, device_id, value\nFROM ht_missing_indexes\nWHERE time > '2000-01-07'\nORDER BY time LIMIT 10;\n--- QUERY PLAN ---\n Limit (actual rows=10.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on ht_missing_indexes (actual rows=10.00 loops=1)\n         Order: ht_missing_indexes.\"time\"\n         ->  Sort (actual rows=10.00 loops=1)\n               Sort Key: _hyper_4_13_chunk.\"time\"\n               Sort Method: top-N heapsort \n               ->  Seq Scan on _hyper_4_13_chunk (actual rows=24477.00 loops=1)\n                     Filter: (\"time\" > 'Fri Jan 07 00:00:00 2000 PST'::timestamp with time zone)\n                     Rows Removed by Filter: 5763\n         ->  Index Scan Backward using _hyper_4_14_chunk_ht_missing_indexes_time_idx on _hyper_4_14_chunk (never executed)\n\n-- test hypertable with dropped columns\n:PREFIX SELECT\n  time, device_id, value\nFROM ht_dropped_columns\nORDER BY time ASC LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on ht_dropped_columns (actual rows=1.00 loops=1)\n         Order: ht_dropped_columns.\"time\"\n         ->  Index Scan Backward using _hyper_5_15_chunk_ht_dropped_columns_time_idx on _hyper_5_15_chunk (actual rows=1.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_16_chunk_ht_dropped_columns_time_idx on _hyper_5_16_chunk (never executed)\n         ->  Index Scan Backward using _hyper_5_17_chunk_ht_dropped_columns_time_idx on _hyper_5_17_chunk (never executed)\n         ->  Index Scan Backward using _hyper_5_18_chunk_ht_dropped_columns_time_idx on _hyper_5_18_chunk (never executed)\n         ->  Index Scan Backward using _hyper_5_19_chunk_ht_dropped_columns_time_idx on _hyper_5_19_chunk (never executed)\n\n-- test hypertable with dropped columns\n:PREFIX SELECT\n  time, device_id, value\nFROM ht_dropped_columns\nWHERE device_id = 1\nORDER BY time DESC;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on ht_dropped_columns (actual rows=7205.00 loops=1)\n   Order: ht_dropped_columns.\"time\" DESC\n   ->  Index Scan using _hyper_5_19_chunk_ht_dropped_columns_time_idx on _hyper_5_19_chunk (actual rows=1441.00 loops=1)\n         Filter: (device_id = 1)\n   ->  Index Scan using _hyper_5_18_chunk_ht_dropped_columns_time_idx on _hyper_5_18_chunk (actual rows=1441.00 loops=1)\n         Filter: (device_id = 1)\n   ->  Index Scan using _hyper_5_17_chunk_ht_dropped_columns_time_idx on _hyper_5_17_chunk (actual rows=1441.00 loops=1)\n         Filter: (device_id = 1)\n   ->  Index Scan using _hyper_5_16_chunk_ht_dropped_columns_time_idx on _hyper_5_16_chunk (actual rows=1441.00 loops=1)\n         Filter: (device_id = 1)\n   ->  Index Scan using _hyper_5_15_chunk_ht_dropped_columns_time_idx on _hyper_5_15_chunk (actual rows=1441.00 loops=1)\n         Filter: (device_id = 1)\n\n-- test hypertable with 2 space dimensions\n:PREFIX SELECT\n  time, device_id, value\nFROM space2\nORDER BY time DESC;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on space2 (actual rows=116649.00 loops=1)\n   Order: space2.\"time\" DESC\n   ->  Merge Append (actual rows=56169.00 loops=1)\n         Sort Key: space2.\"time\" DESC\n         ->  Index Scan using _hyper_6_36_chunk_space2_time_idx on _hyper_6_36_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_34_chunk_space2_time_idx on _hyper_6_34_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_32_chunk_space2_time_idx on _hyper_6_32_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_30_chunk_space2_time_idx on _hyper_6_30_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_28_chunk_space2_time_idx on _hyper_6_28_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_26_chunk_space2_time_idx on _hyper_6_26_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_24_chunk_space2_time_idx on _hyper_6_24_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_22_chunk_space2_time_idx on _hyper_6_22_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_20_chunk_space2_time_idx on _hyper_6_20_chunk (actual rows=6241.00 loops=1)\n   ->  Merge Append (actual rows=60480.00 loops=1)\n         Sort Key: space2.\"time\" DESC\n         ->  Index Scan using _hyper_6_37_chunk_space2_time_idx on _hyper_6_37_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_35_chunk_space2_time_idx on _hyper_6_35_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_33_chunk_space2_time_idx on _hyper_6_33_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_31_chunk_space2_time_idx on _hyper_6_31_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_29_chunk_space2_time_idx on _hyper_6_29_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_27_chunk_space2_time_idx on _hyper_6_27_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_25_chunk_space2_time_idx on _hyper_6_25_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_23_chunk_space2_time_idx on _hyper_6_23_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_21_chunk_space2_time_idx on _hyper_6_21_chunk (actual rows=6720.00 loops=1)\n\n-- test hypertable with 3 space dimensions\n:PREFIX SELECT\n  time\nFROM space3\nORDER BY time DESC;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on space3 (actual rows=103688.00 loops=1)\n   Order: space3.\"time\" DESC\n   ->  Merge Append (actual rows=49928.00 loops=1)\n         Sort Key: space3.\"time\" DESC\n         ->  Index Only Scan using _hyper_7_52_chunk_space3_time_idx on _hyper_7_52_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_50_chunk_space3_time_idx on _hyper_7_50_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_48_chunk_space3_time_idx on _hyper_7_48_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_46_chunk_space3_time_idx on _hyper_7_46_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_44_chunk_space3_time_idx on _hyper_7_44_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_42_chunk_space3_time_idx on _hyper_7_42_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_40_chunk_space3_time_idx on _hyper_7_40_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_38_chunk_space3_time_idx on _hyper_7_38_chunk (actual rows=6241.00 loops=1)\n   ->  Merge Append (actual rows=53760.00 loops=1)\n         Sort Key: space3.\"time\" DESC\n         ->  Index Only Scan using _hyper_7_53_chunk_space3_time_idx on _hyper_7_53_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_51_chunk_space3_time_idx on _hyper_7_51_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_49_chunk_space3_time_idx on _hyper_7_49_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_47_chunk_space3_time_idx on _hyper_7_47_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_45_chunk_space3_time_idx on _hyper_7_45_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_43_chunk_space3_time_idx on _hyper_7_43_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_41_chunk_space3_time_idx on _hyper_7_41_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_39_chunk_space3_time_idx on _hyper_7_39_chunk (actual rows=6720.00 loops=1)\n\n-- test COLLATION\n-- cant be tested in our ci because alpine doesnt support locales\n-- :PREFIX SELECT * FROM sortopt_test ORDER BY time, device COLLATE \"en_US.utf8\";\n-- test NULLS FIRST\n:PREFIX SELECT * FROM sortopt_test ORDER BY time, device NULLS FIRST;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on sortopt_test (actual rows=12961.00 loops=1)\n   Order: sortopt_test.\"time\", sortopt_test.device NULLS FIRST\n   ->  Index Only Scan using _hyper_8_55_chunk_time_device_nullsfirst on _hyper_8_55_chunk (actual rows=6720.00 loops=1)\n   ->  Index Only Scan using _hyper_8_54_chunk_time_device_nullsfirst on _hyper_8_54_chunk (actual rows=6241.00 loops=1)\n\n-- test NULLS LAST\n:PREFIX SELECT * FROM sortopt_test ORDER BY time, device DESC NULLS LAST;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on sortopt_test (actual rows=12961.00 loops=1)\n   Order: sortopt_test.\"time\", sortopt_test.device DESC NULLS LAST\n   ->  Index Only Scan using _hyper_8_55_chunk_time_device_nullslast on _hyper_8_55_chunk (actual rows=6720.00 loops=1)\n   ->  Index Only Scan using _hyper_8_54_chunk_time_device_nullslast on _hyper_8_54_chunk (actual rows=6241.00 loops=1)\n\n--generate the results into two different files\n\\set ECHO errors\n"
  },
  {
    "path": "test/expected/plan_ordered_append-18.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- we run these with analyze to confirm that nodes that are not\n-- needed to fulfill the limit are not executed\n-- unfortunately this doesn't work on PostgreSQL 9.6 which lacks\n-- the ability to turn off analyze timing summary so we run\n-- them without ANALYZE on PostgreSQL 9.6, but since LATERAL plans\n-- are different across versions we need version specific output\n-- here anyway.\n\\set TEST_BASE_NAME plan_ordered_append\nSELECT format('include/%s_load.sql', :'TEST_BASE_NAME') as \"TEST_LOAD_NAME\",\n       format('include/%s_query.sql', :'TEST_BASE_NAME') as \"TEST_QUERY_NAME\",\n       format('%s/results/%s_results_optimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_OPTIMIZED\",\n       format('%s/results/%s_results_unoptimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_UNOPTIMIZED\"\n\\gset\nSELECT format('\\! diff -u --label \"Unoptimized result\" --label \"Optimized result\" %s %s', :'TEST_RESULTS_UNOPTIMIZED', :'TEST_RESULTS_OPTIMIZED') as \"DIFF_CMD\"\n\\gset\n\\set PREFIX 'EXPLAIN (analyze, buffers off, costs off, timing off, summary off)'\n\\set PREFIX_NO_ANALYZE 'EXPLAIN (buffers off, costs off)'\n\\ir :TEST_LOAD_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- create a now() function for repeatable testing that always returns\n-- the same timestamp. It needs to be marked STABLE\nCREATE OR REPLACE FUNCTION now_s()\nRETURNS timestamptz LANGUAGE PLPGSQL STABLE AS\n$BODY$\nBEGIN\n    RETURN '2000-01-08T0:00:00+0'::timestamptz;\nEND;\n$BODY$;\nCREATE TABLE devices(device_id INT PRIMARY KEY, name TEXT);\nINSERT INTO devices VALUES\n(1,'Device 1'),\n(2,'Device 2'),\n(3,'Device 3');\n-- create a second table where we create chunks in reverse order\nCREATE TABLE ordered_append_reverse(time timestamptz NOT NULL, device_id INT, value float);\nSELECT create_hypertable('ordered_append_reverse','time');\n          create_hypertable          \n-------------------------------------\n (1,public,ordered_append_reverse,t)\n\nINSERT INTO ordered_append_reverse SELECT generate_series('2000-01-18'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 0.5;\n-- table where dimension column is last column\nCREATE TABLE IF NOT EXISTS dimension_last(\n    id INT8 NOT NULL,\n    device_id INT NOT NULL,\n    name TEXT NOT NULL,\n    time timestamptz NOT NULL\n);\nSELECT create_hypertable('dimension_last', 'time', chunk_time_interval => interval '1day', if_not_exists => True);\n      create_hypertable      \n-----------------------------\n (2,public,dimension_last,t)\n\n-- table with only dimension column\nCREATE TABLE IF NOT EXISTS dimension_only(\n    time timestamptz NOT NULL\n);\nSELECT create_hypertable('dimension_only', 'time', chunk_time_interval => interval '1day', if_not_exists => True);\n      create_hypertable      \n-----------------------------\n (3,public,dimension_only,t)\n\nINSERT INTO dimension_last SELECT 1,1,'Device 1',generate_series('2000-01-01 0:00:00+0'::timestamptz,'2000-01-04 23:59:00+0'::timestamptz,'1m'::interval);\nINSERT INTO dimension_only VALUES\n('2000-01-01'),\n('2000-01-03'),\n('2000-01-05'),\n('2000-01-07');\nANALYZE devices;\nANALYZE ordered_append_reverse;\nANALYZE dimension_last;\nANALYZE dimension_only;\n-- create hypertable with indexes not on all chunks\nCREATE TABLE ht_missing_indexes(time timestamptz NOT NULL, device_id int, value float);\nSELECT create_hypertable('ht_missing_indexes','time');\n        create_hypertable        \n---------------------------------\n (4,public,ht_missing_indexes,t)\n\nINSERT INTO ht_missing_indexes SELECT generate_series('2000-01-01'::timestamptz,'2000-01-18'::timestamptz,'1m'::interval), 1, 0.5;\nINSERT INTO ht_missing_indexes SELECT generate_series('2000-01-01'::timestamptz,'2000-01-18'::timestamptz,'1m'::interval), 2, 1.5;\nINSERT INTO ht_missing_indexes SELECT generate_series('2000-01-01'::timestamptz,'2000-01-18'::timestamptz,'1m'::interval), 3, 2.5;\n-- drop index from 2nd chunk of ht_missing_indexes\nSELECT format('%I.%I',i.schemaname,i.indexname) AS \"INDEX_NAME\"\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable ht ON c.hypertable_id = ht.id\nINNER JOIN pg_indexes i ON i.schemaname = c.schema_name AND i.tablename=c.table_name\nWHERE ht.table_name = 'ht_missing_indexes'\nORDER BY c.id LIMIT 1 OFFSET 1 \\gset\nDROP INDEX :INDEX_NAME;\nANALYZE ht_missing_indexes;\n-- create hypertable with with dropped columns\nCREATE TABLE ht_dropped_columns(c1 int, c2 int, c3 int, c4 int, c5 int, time timestamptz NOT NULL, device_id int, value float);\nSELECT create_hypertable('ht_dropped_columns','time');\n        create_hypertable        \n---------------------------------\n (5,public,ht_dropped_columns,t)\n\nALTER TABLE ht_dropped_columns DROP COLUMN c1;\nINSERT INTO ht_dropped_columns(time,device_id,value) SELECT generate_series('2000-01-01'::timestamptz,'2000-01-02'::timestamptz,'1m'::interval), 1, 0.5;\nALTER TABLE ht_dropped_columns DROP COLUMN c2;\nINSERT INTO ht_dropped_columns(time,device_id,value) SELECT generate_series('2000-01-08'::timestamptz,'2000-01-09'::timestamptz,'1m'::interval), 1, 0.5;\nALTER TABLE ht_dropped_columns DROP COLUMN c3;\nINSERT INTO ht_dropped_columns(time,device_id,value) SELECT generate_series('2000-01-15'::timestamptz,'2000-01-16'::timestamptz,'1m'::interval), 1, 0.5;\nALTER TABLE ht_dropped_columns DROP COLUMN c4;\nINSERT INTO ht_dropped_columns(time,device_id,value) SELECT generate_series('2000-01-22'::timestamptz,'2000-01-23'::timestamptz,'1m'::interval), 1, 0.5;\nALTER TABLE ht_dropped_columns DROP COLUMN c5;\nINSERT INTO ht_dropped_columns(time,device_id,value) SELECT generate_series('2000-01-29'::timestamptz,'2000-01-30'::timestamptz,'1m'::interval), 1, 0.5;\nANALYZE ht_dropped_columns;\nCREATE TABLE space2(time timestamptz NOT NULL, device_id int NOT NULL, tag_id int NOT NULL, value float);\nSELECT create_hypertable('space2','time','device_id',number_partitions:=3);\n  create_hypertable  \n---------------------\n (6,public,space2,t)\n\nSELECT add_dimension('space2','tag_id',number_partitions:=3);\n       add_dimension        \n----------------------------\n (8,public,space2,tag_id,t)\n\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 1, 1.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 1, 2.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 3, 1, 3.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 2, 1.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 2, 2.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 3, 2, 3.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 3, 1.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 3, 2.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 3, 3, 3.5;\nANALYZE space2;\nCREATE TABLE space3(time timestamptz NOT NULL, x int NOT NULL, y int NOT NULL, z int NOT NULL, value float);\nSELECT create_hypertable('space3','time','x',number_partitions:=2);\n  create_hypertable  \n---------------------\n (7,public,space3,t)\n\nSELECT add_dimension('space3','y',number_partitions:=2);\n     add_dimension      \n------------------------\n (11,public,space3,y,t)\n\nSELECT add_dimension('space3','z',number_partitions:=2);\n     add_dimension      \n------------------------\n (12,public,space3,z,t)\n\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 1, 1, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 1, 2, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 2, 1, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 2, 2, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 1, 1, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 1, 2, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 2, 1, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 2, 2, 1.5;\nANALYZE space3;\nCREATE TABLE sortopt_test(time timestamptz NOT NULL, device TEXT);\nSELECT create_hypertable('sortopt_test','time',create_default_indexes:=false);\n     create_hypertable     \n---------------------------\n (8,public,sortopt_test,t)\n\n-- since alpine does not support locales we cant test collations in our ci\n-- CREATE COLLATION IF NOT EXISTS en_US(LOCALE='en_US.utf8');\n-- CREATE INDEX time_device_utf8 ON sortopt_test(time, device COLLATE \"en_US\");\nCREATE INDEX time_device_nullsfirst ON sortopt_test(time, device NULLS FIRST);\nCREATE INDEX time_device_nullslast ON sortopt_test(time, device DESC NULLS LAST);\nINSERT INTO sortopt_test SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 'Device 1';\nANALYZE sortopt_test;\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- print chunks ordered by time to ensure ordering we want\nSELECT\n  ht.table_name AS hypertable,\n  c.table_name AS chunk,\n  ds.range_start\nFROM\n  _timescaledb_catalog.chunk c\n  INNER JOIN LATERAL(SELECT * FROM _timescaledb_catalog.chunk_constraint cc WHERE c.id = cc.chunk_id ORDER BY cc.dimension_slice_id LIMIT 1) cc ON true\n  INNER JOIN _timescaledb_catalog.dimension_slice ds ON ds.id=cc.dimension_slice_id\n  INNER JOIN _timescaledb_catalog.dimension d ON ds.dimension_id = d.id\n  INNER JOIN _timescaledb_catalog.hypertable ht ON d.hypertable_id = ht.id\nORDER BY ht.table_name, range_start, chunk;\n       hypertable       |       chunk       |     range_start      \n------------------------+-------------------+----------------------\n dimension_last         | _hyper_2_4_chunk  |      946684800000000\n dimension_last         | _hyper_2_5_chunk  |      946771200000000\n dimension_last         | _hyper_2_6_chunk  |      946857600000000\n dimension_last         | _hyper_2_7_chunk  |      946944000000000\n dimension_only         | _hyper_3_8_chunk  |      946684800000000\n dimension_only         | _hyper_3_9_chunk  |      946857600000000\n dimension_only         | _hyper_3_10_chunk |      947030400000000\n dimension_only         | _hyper_3_11_chunk |      947203200000000\n ht_dropped_columns     | _hyper_5_15_chunk |      946512000000000\n ht_dropped_columns     | _hyper_5_16_chunk |      947116800000000\n ht_dropped_columns     | _hyper_5_17_chunk |      947721600000000\n ht_dropped_columns     | _hyper_5_18_chunk |      948326400000000\n ht_dropped_columns     | _hyper_5_19_chunk |      948931200000000\n ht_missing_indexes     | _hyper_4_12_chunk |      946512000000000\n ht_missing_indexes     | _hyper_4_13_chunk |      947116800000000\n ht_missing_indexes     | _hyper_4_14_chunk |      947721600000000\n ordered_append_reverse | _hyper_1_3_chunk  |      946512000000000\n ordered_append_reverse | _hyper_1_2_chunk  |      947116800000000\n ordered_append_reverse | _hyper_1_1_chunk  |      947721600000000\n sortopt_test           | _hyper_8_55_chunk |      946512000000000\n sortopt_test           | _hyper_8_54_chunk |      947116800000000\n space2                 | _hyper_6_21_chunk | -9223372036854775808\n space2                 | _hyper_6_23_chunk | -9223372036854775808\n space2                 | _hyper_6_25_chunk | -9223372036854775808\n space2                 | _hyper_6_27_chunk | -9223372036854775808\n space2                 | _hyper_6_33_chunk | -9223372036854775808\n space2                 | _hyper_6_29_chunk |      946512000000000\n space2                 | _hyper_6_31_chunk |      946512000000000\n space2                 | _hyper_6_35_chunk |      946512000000000\n space2                 | _hyper_6_37_chunk |      946512000000000\n space2                 | _hyper_6_20_chunk |      947116800000000\n space2                 | _hyper_6_22_chunk |      947116800000000\n space2                 | _hyper_6_24_chunk |      947116800000000\n space2                 | _hyper_6_26_chunk |      947116800000000\n space2                 | _hyper_6_28_chunk |      947116800000000\n space2                 | _hyper_6_30_chunk |      947116800000000\n space2                 | _hyper_6_32_chunk |      947116800000000\n space2                 | _hyper_6_34_chunk |      947116800000000\n space2                 | _hyper_6_36_chunk |      947116800000000\n space3                 | _hyper_7_39_chunk | -9223372036854775808\n space3                 | _hyper_7_41_chunk | -9223372036854775808\n space3                 | _hyper_7_43_chunk | -9223372036854775808\n space3                 | _hyper_7_45_chunk | -9223372036854775808\n space3                 | _hyper_7_47_chunk | -9223372036854775808\n space3                 | _hyper_7_49_chunk | -9223372036854775808\n space3                 | _hyper_7_51_chunk | -9223372036854775808\n space3                 | _hyper_7_53_chunk |      946512000000000\n space3                 | _hyper_7_38_chunk |      947116800000000\n space3                 | _hyper_7_40_chunk |      947116800000000\n space3                 | _hyper_7_42_chunk |      947116800000000\n space3                 | _hyper_7_44_chunk |      947116800000000\n space3                 | _hyper_7_46_chunk |      947116800000000\n space3                 | _hyper_7_48_chunk |      947116800000000\n space3                 | _hyper_7_50_chunk |      947116800000000\n space3                 | _hyper_7_52_chunk |      947116800000000\n\n-- test ASC for reverse ordered chunks\n:PREFIX SELECT\n  time, device_id, value\nFROM ordered_append_reverse\nORDER BY time ASC LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on ordered_append_reverse (actual rows=1.00 loops=1)\n         Order: ordered_append_reverse.\"time\"\n         ->  Index Scan Backward using _hyper_1_3_chunk_ordered_append_reverse_time_idx on _hyper_1_3_chunk (actual rows=1.00 loops=1)\n         ->  Index Scan Backward using _hyper_1_2_chunk_ordered_append_reverse_time_idx on _hyper_1_2_chunk (never executed)\n         ->  Index Scan Backward using _hyper_1_1_chunk_ordered_append_reverse_time_idx on _hyper_1_1_chunk (never executed)\n\n-- test DESC for reverse ordered chunks\n:PREFIX SELECT\n  time, device_id, value\nFROM ordered_append_reverse\nORDER BY time DESC LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on ordered_append_reverse (actual rows=1.00 loops=1)\n         Order: ordered_append_reverse.\"time\" DESC\n         ->  Index Scan using _hyper_1_1_chunk_ordered_append_reverse_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)\n         ->  Index Scan using _hyper_1_2_chunk_ordered_append_reverse_time_idx on _hyper_1_2_chunk (never executed)\n         ->  Index Scan using _hyper_1_3_chunk_ordered_append_reverse_time_idx on _hyper_1_3_chunk (never executed)\n\n-- test query with ORDER BY time_bucket, device_id\n-- must not use ordered append\n:PREFIX SELECT\n  time_bucket('1d',time), device_id, name\nFROM dimension_last\nORDER BY time_bucket('1d',time), device_id LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Sort (actual rows=1.00 loops=1)\n         Sort Key: (time_bucket('@ 1 day'::interval, dimension_last.\"time\")), dimension_last.device_id\n         Sort Method: top-N heapsort \n         ->  Result (actual rows=5760.00 loops=1)\n               ->  Append (actual rows=5760.00 loops=1)\n                     ->  Seq Scan on _hyper_2_4_chunk (actual rows=1440.00 loops=1)\n                     ->  Seq Scan on _hyper_2_5_chunk (actual rows=1440.00 loops=1)\n                     ->  Seq Scan on _hyper_2_6_chunk (actual rows=1440.00 loops=1)\n                     ->  Seq Scan on _hyper_2_7_chunk (actual rows=1440.00 loops=1)\n\n-- test query with ORDER BY date_trunc, device_id\n-- must not use ordered append\n:PREFIX SELECT\n  date_trunc('day',time), device_id, name\nFROM dimension_last\nORDER BY 1,2 LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Sort (actual rows=1.00 loops=1)\n         Sort Key: (date_trunc('day'::text, dimension_last.\"time\")), dimension_last.device_id\n         Sort Method: top-N heapsort \n         ->  Result (actual rows=5760.00 loops=1)\n               ->  Append (actual rows=5760.00 loops=1)\n                     ->  Seq Scan on _hyper_2_4_chunk (actual rows=1440.00 loops=1)\n                     ->  Seq Scan on _hyper_2_5_chunk (actual rows=1440.00 loops=1)\n                     ->  Seq Scan on _hyper_2_6_chunk (actual rows=1440.00 loops=1)\n                     ->  Seq Scan on _hyper_2_7_chunk (actual rows=1440.00 loops=1)\n\n-- test with table with only dimension column\n:PREFIX SELECT * FROM dimension_only ORDER BY time DESC LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on dimension_only (actual rows=1.00 loops=1)\n         Order: dimension_only.\"time\" DESC\n         ->  Index Only Scan using _hyper_3_11_chunk_dimension_only_time_idx on _hyper_3_11_chunk (actual rows=1.00 loops=1)\n         ->  Index Only Scan using _hyper_3_10_chunk_dimension_only_time_idx on _hyper_3_10_chunk (never executed)\n         ->  Index Only Scan using _hyper_3_9_chunk_dimension_only_time_idx on _hyper_3_9_chunk (never executed)\n         ->  Index Only Scan using _hyper_3_8_chunk_dimension_only_time_idx on _hyper_3_8_chunk (never executed)\n\n-- test LEFT JOIN against hypertable\n:PREFIX_NO_ANALYZE SELECT *\nFROM dimension_last\nLEFT JOIN dimension_only USING (time)\nORDER BY dimension_last.time DESC\nLIMIT 2;\n--- QUERY PLAN ---\n Limit\n   ->  Nested Loop Left Join\n         Join Filter: (dimension_last.\"time\" = dimension_only.\"time\")\n         ->  Custom Scan (ChunkAppend) on dimension_last\n               Order: dimension_last.\"time\" DESC\n               ->  Index Scan using _hyper_2_7_chunk_dimension_last_time_idx on _hyper_2_7_chunk\n               ->  Index Scan using _hyper_2_6_chunk_dimension_last_time_idx on _hyper_2_6_chunk\n               ->  Index Scan using _hyper_2_5_chunk_dimension_last_time_idx on _hyper_2_5_chunk\n               ->  Index Scan using _hyper_2_4_chunk_dimension_last_time_idx on _hyper_2_4_chunk\n         ->  Materialize\n               ->  Append\n                     ->  Seq Scan on _hyper_3_11_chunk\n                     ->  Seq Scan on _hyper_3_10_chunk\n                     ->  Seq Scan on _hyper_3_9_chunk\n                     ->  Seq Scan on _hyper_3_8_chunk\n\n-- test INNER JOIN against non-hypertable\n:PREFIX_NO_ANALYZE SELECT *\nFROM dimension_last\nINNER JOIN dimension_only USING (time)\nORDER BY dimension_last.time DESC\nLIMIT 2;\n--- QUERY PLAN ---\n Limit\n   ->  Nested Loop\n         ->  Custom Scan (ChunkAppend) on dimension_only\n               Order: dimension_only.\"time\" DESC\n               ->  Index Only Scan using _hyper_3_11_chunk_dimension_only_time_idx on _hyper_3_11_chunk\n               ->  Index Only Scan using _hyper_3_10_chunk_dimension_only_time_idx on _hyper_3_10_chunk\n               ->  Index Only Scan using _hyper_3_9_chunk_dimension_only_time_idx on _hyper_3_9_chunk\n               ->  Index Only Scan using _hyper_3_8_chunk_dimension_only_time_idx on _hyper_3_8_chunk\n         ->  Append\n               ->  Index Scan using _hyper_2_7_chunk_dimension_last_time_idx on _hyper_2_7_chunk\n                     Index Cond: (\"time\" = dimension_only.\"time\")\n               ->  Index Scan using _hyper_2_6_chunk_dimension_last_time_idx on _hyper_2_6_chunk\n                     Index Cond: (\"time\" = dimension_only.\"time\")\n               ->  Index Scan using _hyper_2_5_chunk_dimension_last_time_idx on _hyper_2_5_chunk\n                     Index Cond: (\"time\" = dimension_only.\"time\")\n               ->  Index Scan using _hyper_2_4_chunk_dimension_last_time_idx on _hyper_2_4_chunk\n                     Index Cond: (\"time\" = dimension_only.\"time\")\n\n-- test join against non-hypertable\n:PREFIX SELECT *\nFROM dimension_last\nINNER JOIN devices USING(device_id)\nORDER BY dimension_last.time DESC\nLIMIT 2;\n--- QUERY PLAN ---\n Limit (actual rows=2.00 loops=1)\n   ->  Nested Loop (actual rows=2.00 loops=1)\n         Join Filter: (devices.device_id = dimension_last.device_id)\n         ->  Custom Scan (ChunkAppend) on dimension_last (actual rows=2.00 loops=1)\n               Order: dimension_last.\"time\" DESC\n               ->  Index Scan using _hyper_2_7_chunk_dimension_last_time_idx on _hyper_2_7_chunk (actual rows=2.00 loops=1)\n               ->  Index Scan using _hyper_2_6_chunk_dimension_last_time_idx on _hyper_2_6_chunk (never executed)\n               ->  Index Scan using _hyper_2_5_chunk_dimension_last_time_idx on _hyper_2_5_chunk (never executed)\n               ->  Index Scan using _hyper_2_4_chunk_dimension_last_time_idx on _hyper_2_4_chunk (never executed)\n         ->  Materialize (actual rows=1.00 loops=2)\n               ->  Seq Scan on devices (actual rows=1.00 loops=1)\n\n-- test hypertable with index missing on one chunk\n:PREFIX SELECT\n  time, device_id, value\nFROM ht_missing_indexes\nORDER BY time ASC LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on ht_missing_indexes (actual rows=1.00 loops=1)\n         Order: ht_missing_indexes.\"time\"\n         ->  Index Scan Backward using _hyper_4_12_chunk_ht_missing_indexes_time_idx on _hyper_4_12_chunk (actual rows=1.00 loops=1)\n         ->  Sort (never executed)\n               Sort Key: _hyper_4_13_chunk.\"time\"\n               ->  Seq Scan on _hyper_4_13_chunk (never executed)\n         ->  Index Scan Backward using _hyper_4_14_chunk_ht_missing_indexes_time_idx on _hyper_4_14_chunk (never executed)\n\n-- test hypertable with index missing on one chunk\n-- and no data\n:PREFIX SELECT\n  time, device_id, value\nFROM ht_missing_indexes\nWHERE device_id = 2\nORDER BY time DESC LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on ht_missing_indexes (actual rows=1.00 loops=1)\n         Order: ht_missing_indexes.\"time\" DESC\n         ->  Index Scan using _hyper_4_14_chunk_ht_missing_indexes_time_idx on _hyper_4_14_chunk (actual rows=1.00 loops=1)\n               Filter: (device_id = 2)\n               Rows Removed by Filter: 1\n         ->  Sort (never executed)\n               Sort Key: _hyper_4_13_chunk.\"time\" DESC\n               ->  Seq Scan on _hyper_4_13_chunk (never executed)\n                     Filter: (device_id = 2)\n         ->  Index Scan using _hyper_4_12_chunk_ht_missing_indexes_time_idx on _hyper_4_12_chunk (never executed)\n               Filter: (device_id = 2)\n\n-- test hypertable with index missing on one chunk\n-- and no data\n:PREFIX SELECT\n  time, device_id, value\nFROM ht_missing_indexes\nWHERE time > '2000-01-07'\nORDER BY time LIMIT 10;\n--- QUERY PLAN ---\n Limit (actual rows=10.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on ht_missing_indexes (actual rows=10.00 loops=1)\n         Order: ht_missing_indexes.\"time\"\n         ->  Sort (actual rows=10.00 loops=1)\n               Sort Key: _hyper_4_13_chunk.\"time\"\n               Sort Method: top-N heapsort \n               ->  Seq Scan on _hyper_4_13_chunk (actual rows=24477.00 loops=1)\n                     Filter: (\"time\" > 'Fri Jan 07 00:00:00 2000 PST'::timestamp with time zone)\n                     Rows Removed by Filter: 5763\n         ->  Index Scan Backward using _hyper_4_14_chunk_ht_missing_indexes_time_idx on _hyper_4_14_chunk (never executed)\n\n-- test hypertable with dropped columns\n:PREFIX SELECT\n  time, device_id, value\nFROM ht_dropped_columns\nORDER BY time ASC LIMIT 1;\n--- QUERY PLAN ---\n Limit (actual rows=1.00 loops=1)\n   ->  Custom Scan (ChunkAppend) on ht_dropped_columns (actual rows=1.00 loops=1)\n         Order: ht_dropped_columns.\"time\"\n         ->  Index Scan Backward using _hyper_5_15_chunk_ht_dropped_columns_time_idx on _hyper_5_15_chunk (actual rows=1.00 loops=1)\n         ->  Index Scan Backward using _hyper_5_16_chunk_ht_dropped_columns_time_idx on _hyper_5_16_chunk (never executed)\n         ->  Index Scan Backward using _hyper_5_17_chunk_ht_dropped_columns_time_idx on _hyper_5_17_chunk (never executed)\n         ->  Index Scan Backward using _hyper_5_18_chunk_ht_dropped_columns_time_idx on _hyper_5_18_chunk (never executed)\n         ->  Index Scan Backward using _hyper_5_19_chunk_ht_dropped_columns_time_idx on _hyper_5_19_chunk (never executed)\n\n-- test hypertable with dropped columns\n:PREFIX SELECT\n  time, device_id, value\nFROM ht_dropped_columns\nWHERE device_id = 1\nORDER BY time DESC;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on ht_dropped_columns (actual rows=7205.00 loops=1)\n   Order: ht_dropped_columns.\"time\" DESC\n   ->  Index Scan using _hyper_5_19_chunk_ht_dropped_columns_time_idx on _hyper_5_19_chunk (actual rows=1441.00 loops=1)\n         Filter: (device_id = 1)\n   ->  Index Scan using _hyper_5_18_chunk_ht_dropped_columns_time_idx on _hyper_5_18_chunk (actual rows=1441.00 loops=1)\n         Filter: (device_id = 1)\n   ->  Index Scan using _hyper_5_17_chunk_ht_dropped_columns_time_idx on _hyper_5_17_chunk (actual rows=1441.00 loops=1)\n         Filter: (device_id = 1)\n   ->  Index Scan using _hyper_5_16_chunk_ht_dropped_columns_time_idx on _hyper_5_16_chunk (actual rows=1441.00 loops=1)\n         Filter: (device_id = 1)\n   ->  Index Scan using _hyper_5_15_chunk_ht_dropped_columns_time_idx on _hyper_5_15_chunk (actual rows=1441.00 loops=1)\n         Filter: (device_id = 1)\n\n-- test hypertable with 2 space dimensions\n:PREFIX SELECT\n  time, device_id, value\nFROM space2\nORDER BY time DESC;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on space2 (actual rows=116649.00 loops=1)\n   Order: space2.\"time\" DESC\n   ->  Merge Append (actual rows=56169.00 loops=1)\n         Sort Key: space2.\"time\" DESC\n         ->  Index Scan using _hyper_6_36_chunk_space2_time_idx on _hyper_6_36_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_34_chunk_space2_time_idx on _hyper_6_34_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_32_chunk_space2_time_idx on _hyper_6_32_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_30_chunk_space2_time_idx on _hyper_6_30_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_28_chunk_space2_time_idx on _hyper_6_28_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_26_chunk_space2_time_idx on _hyper_6_26_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_24_chunk_space2_time_idx on _hyper_6_24_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_22_chunk_space2_time_idx on _hyper_6_22_chunk (actual rows=6241.00 loops=1)\n         ->  Index Scan using _hyper_6_20_chunk_space2_time_idx on _hyper_6_20_chunk (actual rows=6241.00 loops=1)\n   ->  Merge Append (actual rows=60480.00 loops=1)\n         Sort Key: space2.\"time\" DESC\n         ->  Index Scan using _hyper_6_37_chunk_space2_time_idx on _hyper_6_37_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_35_chunk_space2_time_idx on _hyper_6_35_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_33_chunk_space2_time_idx on _hyper_6_33_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_31_chunk_space2_time_idx on _hyper_6_31_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_29_chunk_space2_time_idx on _hyper_6_29_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_27_chunk_space2_time_idx on _hyper_6_27_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_25_chunk_space2_time_idx on _hyper_6_25_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_23_chunk_space2_time_idx on _hyper_6_23_chunk (actual rows=6720.00 loops=1)\n         ->  Index Scan using _hyper_6_21_chunk_space2_time_idx on _hyper_6_21_chunk (actual rows=6720.00 loops=1)\n\n-- test hypertable with 3 space dimensions\n:PREFIX SELECT\n  time\nFROM space3\nORDER BY time DESC;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on space3 (actual rows=103688.00 loops=1)\n   Order: space3.\"time\" DESC\n   ->  Merge Append (actual rows=49928.00 loops=1)\n         Sort Key: space3.\"time\" DESC\n         ->  Index Only Scan using _hyper_7_52_chunk_space3_time_idx on _hyper_7_52_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_50_chunk_space3_time_idx on _hyper_7_50_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_48_chunk_space3_time_idx on _hyper_7_48_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_46_chunk_space3_time_idx on _hyper_7_46_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_44_chunk_space3_time_idx on _hyper_7_44_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_42_chunk_space3_time_idx on _hyper_7_42_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_40_chunk_space3_time_idx on _hyper_7_40_chunk (actual rows=6241.00 loops=1)\n         ->  Index Only Scan using _hyper_7_38_chunk_space3_time_idx on _hyper_7_38_chunk (actual rows=6241.00 loops=1)\n   ->  Merge Append (actual rows=53760.00 loops=1)\n         Sort Key: space3.\"time\" DESC\n         ->  Index Only Scan using _hyper_7_53_chunk_space3_time_idx on _hyper_7_53_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_51_chunk_space3_time_idx on _hyper_7_51_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_49_chunk_space3_time_idx on _hyper_7_49_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_47_chunk_space3_time_idx on _hyper_7_47_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_45_chunk_space3_time_idx on _hyper_7_45_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_43_chunk_space3_time_idx on _hyper_7_43_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_41_chunk_space3_time_idx on _hyper_7_41_chunk (actual rows=6720.00 loops=1)\n         ->  Index Only Scan using _hyper_7_39_chunk_space3_time_idx on _hyper_7_39_chunk (actual rows=6720.00 loops=1)\n\n-- test COLLATION\n-- cant be tested in our ci because alpine doesnt support locales\n-- :PREFIX SELECT * FROM sortopt_test ORDER BY time, device COLLATE \"en_US.utf8\";\n-- test NULLS FIRST\n:PREFIX SELECT * FROM sortopt_test ORDER BY time, device NULLS FIRST;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on sortopt_test (actual rows=12961.00 loops=1)\n   Order: sortopt_test.\"time\", sortopt_test.device NULLS FIRST\n   ->  Index Only Scan using _hyper_8_55_chunk_time_device_nullsfirst on _hyper_8_55_chunk (actual rows=6720.00 loops=1)\n   ->  Index Only Scan using _hyper_8_54_chunk_time_device_nullsfirst on _hyper_8_54_chunk (actual rows=6241.00 loops=1)\n\n-- test NULLS LAST\n:PREFIX SELECT * FROM sortopt_test ORDER BY time, device DESC NULLS LAST;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on sortopt_test (actual rows=12961.00 loops=1)\n   Order: sortopt_test.\"time\", sortopt_test.device DESC NULLS LAST\n   ->  Index Only Scan using _hyper_8_55_chunk_time_device_nullslast on _hyper_8_55_chunk (actual rows=6720.00 loops=1)\n   ->  Index Only Scan using _hyper_8_54_chunk_time_device_nullslast on _hyper_8_54_chunk (actual rows=6241.00 loops=1)\n\n--generate the results into two different files\n\\set ECHO errors\n"
  },
  {
    "path": "test/expected/query.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\set TEST_BASE_NAME query\nSELECT format('include/%s_load.sql', :'TEST_BASE_NAME') as \"TEST_LOAD_NAME\",\n       format('include/%s_query.sql', :'TEST_BASE_NAME') as \"TEST_QUERY_NAME\",\n       format('%s/results/%s_results_optimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_OPTIMIZED\",\n       format('%s/results/%s_results_unoptimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_UNOPTIMIZED\"\n\\gset\nSELECT format('\\! diff -u  --label \"Unoptimized result\" --label \"Optimized result\" %s %s', :'TEST_RESULTS_UNOPTIMIZED', :'TEST_RESULTS_OPTIMIZED') as \"DIFF_CMD\"\n\\gset\n\\set PREFIX 'EXPLAIN (buffers OFF, costs OFF)'\n\\ir :TEST_LOAD_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE PUBLIC.hyper_1 (\n  time TIMESTAMP NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL\n);\nCREATE INDEX \"time_plain\" ON PUBLIC.hyper_1 (time DESC, series_0);\nSELECT * FROM create_hypertable('\"public\".\"hyper_1\"'::regclass, 'time'::name, number_partitions => 1, create_default_indexes=>false);\npsql:include/query_load.sql:13: WARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             1 | public      | hyper_1    | t\n\nINSERT INTO hyper_1 SELECT to_timestamp(ser), ser, ser+10000, sqrt(ser::numeric) FROM generate_series(0,10000) ser;\nINSERT INTO hyper_1 SELECT to_timestamp(ser), ser, ser+10000, sqrt(ser::numeric) FROM generate_series(10001,20000) ser;\nCREATE TABLE PUBLIC.hyper_1_tz (\n  time TIMESTAMPTZ NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL\n);\nCREATE INDEX \"time_plain_tz\" ON PUBLIC.hyper_1_tz (time DESC, series_0);\nSELECT * FROM create_hypertable('\"public\".\"hyper_1_tz\"'::regclass, 'time'::name, number_partitions => 1, create_default_indexes=>false);\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             2 | public      | hyper_1_tz | t\n\nINSERT INTO hyper_1_tz SELECT to_timestamp(ser), ser, ser+10000, sqrt(ser::numeric) FROM generate_series(0,10000) ser;\nINSERT INTO hyper_1_tz SELECT to_timestamp(ser), ser, ser+10000, sqrt(ser::numeric) FROM generate_series(10001,20000) ser;\nCREATE TABLE PUBLIC.hyper_1_int (\n  time int NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL\n);\nCREATE INDEX \"time_plain_int\" ON PUBLIC.hyper_1_int (time DESC, series_0);\nSELECT * FROM create_hypertable('\"public\".\"hyper_1_int\"'::regclass, 'time'::name, number_partitions => 1, chunk_time_interval=>10000, create_default_indexes=>FALSE);\n hypertable_id | schema_name | table_name  | created \n---------------+-------------+-------------+---------\n             3 | public      | hyper_1_int | t\n\nINSERT INTO hyper_1_int SELECT ser, ser, ser+10000, sqrt(ser::numeric) FROM generate_series(0,10000) ser;\nINSERT INTO hyper_1_int SELECT ser, ser, ser+10000, sqrt(ser::numeric) FROM generate_series(10001,20000) ser;\nCREATE TABLE PUBLIC.hyper_1_date (\n  time date NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL\n);\nCREATE INDEX \"time_plain_date\" ON PUBLIC.hyper_1_date (time DESC, series_0);\nSELECT * FROM create_hypertable('\"public\".\"hyper_1_date\"'::regclass, 'time'::name, number_partitions => 1, chunk_time_interval=>86400000000, create_default_indexes=>FALSE);\n hypertable_id | schema_name |  table_name  | created \n---------------+-------------+--------------+---------\n             4 | public      | hyper_1_date | t\n\nINSERT INTO hyper_1_date SELECT to_timestamp(ser)::date, ser, ser+10000, sqrt(ser::numeric) FROM generate_series(0,10000) ser;\nINSERT INTO hyper_1_date SELECT to_timestamp(ser)::date, ser, ser+10000, sqrt(ser::numeric) FROM generate_series(10001,20000) ser;\n--below needed to create enough unique dates to trigger an index scan\nINSERT INTO hyper_1_date SELECT to_timestamp(ser*100)::date, ser, ser+10000, sqrt(ser::numeric) FROM generate_series(10001,20000) ser;\nCREATE TABLE PUBLIC.plain_table (\n  time TIMESTAMPTZ NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL\n);\nCREATE INDEX \"time_plain_plain_table\" ON PUBLIC.plain_table (time DESC, series_0);\nINSERT INTO plain_table SELECT to_timestamp(ser), ser, ser+10000, sqrt(ser::numeric) FROM generate_series(0,10000) ser;\nINSERT INTO plain_table SELECT to_timestamp(ser), ser, ser+10000, sqrt(ser::numeric) FROM generate_series(10001,20000) ser;\n-- Table with a time partitioning function\nCREATE TABLE PUBLIC.hyper_timefunc (\n  time float8 NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL\n);\nCREATE OR REPLACE FUNCTION unix_to_timestamp(unixtime float8)\n    RETURNS TIMESTAMPTZ LANGUAGE SQL IMMUTABLE AS\n$BODY$\n    SELECT to_timestamp(unixtime);\n$BODY$;\nCREATE INDEX \"time_plain_timefunc\" ON PUBLIC.hyper_timefunc (to_timestamp(time) DESC, series_0);\nSELECT * FROM create_hypertable('\"public\".\"hyper_timefunc\"'::regclass, 'time'::name, number_partitions => 1, create_default_indexes=>false, time_partitioning_func => 'unix_to_timestamp');\n hypertable_id | schema_name |   table_name   | created \n---------------+-------------+----------------+---------\n             5 | public      | hyper_timefunc | t\n\nINSERT INTO hyper_timefunc SELECT ser, ser, ser+10000, sqrt(ser::numeric) FROM generate_series(0,10000) ser;\nINSERT INTO hyper_timefunc SELECT ser, ser, ser+10000, sqrt(ser::numeric) FROM generate_series(10001,20000) ser;\nANALYZE plain_table;\nANALYZE hyper_timefunc;\nANALYZE hyper_1;\nANALYZE hyper_1_tz;\nANALYZE hyper_1_int;\nANALYZE hyper_1_date;\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nSHOW timescaledb.enable_optimizations;\n timescaledb.enable_optimizations \n----------------------------------\n on\n\n--non-aggregates use MergeAppend in both optimized and non-optimized\n:PREFIX SELECT * FROM hyper_1 ORDER BY \"time\" DESC limit 2;\n--- QUERY PLAN ---\n Limit\n   ->  Index Scan using _hyper_1_1_chunk_time_plain on _hyper_1_1_chunk\n\n:PREFIX SELECT * FROM hyper_timefunc ORDER BY unix_to_timestamp(\"time\") DESC limit 2;\n--- QUERY PLAN ---\n Limit\n   ->  Index Scan using _hyper_5_19_chunk_time_plain_timefunc on _hyper_5_19_chunk\n\n--Aggregates use MergeAppend only in optimized\n:PREFIX SELECT date_trunc('minute', time) t, avg(series_0), min(series_1), avg(series_2) FROM hyper_1 GROUP BY t ORDER BY t DESC limit 2;\n--- QUERY PLAN ---\n Limit\n   ->  GroupAggregate\n         Group Key: (date_trunc('minute'::text, _hyper_1_1_chunk.\"time\"))\n         ->  Result\n               ->  Index Scan using _hyper_1_1_chunk_time_plain on _hyper_1_1_chunk\n\n:PREFIX SELECT date_trunc('minute', time) t, avg(series_0), min(series_1), avg(series_2) FROM hyper_1_date GROUP BY t ORDER BY t DESC limit 2;\n--- QUERY PLAN ---\n Limit\n   ->  GroupAggregate\n         Group Key: (date_trunc('minute'::text, (hyper_1_date.\"time\")::timestamp with time zone))\n         ->  Result\n               ->  Merge Append\n                     Sort Key: (date_trunc('minute'::text, (hyper_1_date.\"time\")::timestamp with time zone)) DESC\n                     ->  Index Scan using _hyper_4_6_chunk_time_plain_date on _hyper_4_6_chunk\n                     ->  Index Scan using _hyper_4_7_chunk_time_plain_date on _hyper_4_7_chunk\n                     ->  Index Scan using _hyper_4_8_chunk_time_plain_date on _hyper_4_8_chunk\n                     ->  Index Scan using _hyper_4_9_chunk_time_plain_date on _hyper_4_9_chunk\n                     ->  Index Scan using _hyper_4_10_chunk_time_plain_date on _hyper_4_10_chunk\n                     ->  Index Scan using _hyper_4_11_chunk_time_plain_date on _hyper_4_11_chunk\n                     ->  Index Scan using _hyper_4_12_chunk_time_plain_date on _hyper_4_12_chunk\n                     ->  Index Scan using _hyper_4_13_chunk_time_plain_date on _hyper_4_13_chunk\n                     ->  Index Scan using _hyper_4_14_chunk_time_plain_date on _hyper_4_14_chunk\n                     ->  Index Scan using _hyper_4_15_chunk_time_plain_date on _hyper_4_15_chunk\n                     ->  Index Scan using _hyper_4_16_chunk_time_plain_date on _hyper_4_16_chunk\n                     ->  Index Scan using _hyper_4_17_chunk_time_plain_date on _hyper_4_17_chunk\n                     ->  Index Scan using _hyper_4_18_chunk_time_plain_date on _hyper_4_18_chunk\n\n--the minute and second results should be diff\n:PREFIX SELECT date_trunc('minute', time) t, avg(series_0), min(series_1), avg(series_2) FROM hyper_1 GROUP BY t ORDER BY t DESC limit 2;\n--- QUERY PLAN ---\n Limit\n   ->  GroupAggregate\n         Group Key: (date_trunc('minute'::text, _hyper_1_1_chunk.\"time\"))\n         ->  Result\n               ->  Index Scan using _hyper_1_1_chunk_time_plain on _hyper_1_1_chunk\n\n:PREFIX SELECT date_trunc('second', time) t, avg(series_0), min(series_1), avg(series_2) FROM hyper_1 GROUP BY t ORDER BY t DESC limit 2;\n--- QUERY PLAN ---\n Limit\n   ->  GroupAggregate\n         Group Key: (date_trunc('second'::text, _hyper_1_1_chunk.\"time\"))\n         ->  Result\n               ->  Index Scan using _hyper_1_1_chunk_time_plain on _hyper_1_1_chunk\n\n--test that when index on time used by constraint, still works correctly\n:PREFIX\nSELECT date_trunc('minute', time) t, avg(series_0), min(series_1), avg(series_2)\nFROM hyper_1\nWHERE time < to_timestamp(900)\nGROUP BY t\nORDER BY t DESC\nLIMIT 2;\n--- QUERY PLAN ---\n Limit\n   ->  GroupAggregate\n         Group Key: (date_trunc('minute'::text, hyper_1.\"time\"))\n         ->  Result\n               ->  Custom Scan (ChunkAppend) on hyper_1\n                     Order: date_trunc('minute'::text, hyper_1.\"time\") DESC\n                     Chunks excluded during startup: 0\n                     ->  Index Scan using _hyper_1_1_chunk_time_plain on _hyper_1_1_chunk\n                           Index Cond: (\"time\" < 'Wed Dec 31 16:15:00 1969 PST'::timestamp with time zone)\n\n--test on table with time partitioning function. Currently not\n--optimized to use index for ordering since the index is an expression\n--on time (e.g., timefunc(time)), and we currently don't handle that\n--case.\n:PREFIX\nSELECT date_trunc('minute', to_timestamp(time)) t, avg(series_0), min(series_1), avg(series_2)\nFROM hyper_timefunc\nWHERE to_timestamp(time) < to_timestamp(900)\nGROUP BY t\nORDER BY t DESC\nLIMIT 2;\n--- QUERY PLAN ---\n Limit\n   ->  Sort\n         Sort Key: (date_trunc('minute'::text, to_timestamp(_hyper_5_19_chunk.\"time\"))) DESC\n         ->  HashAggregate\n               Group Key: date_trunc('minute'::text, to_timestamp(_hyper_5_19_chunk.\"time\"))\n               ->  Result\n                     ->  Index Scan using _hyper_5_19_chunk_time_plain_timefunc on _hyper_5_19_chunk\n                           Index Cond: (to_timestamp(\"time\") < 'Wed Dec 31 16:15:00 1969 PST'::timestamp with time zone)\n\nBEGIN;\n  --test that still works with an expression index on data_trunc.\n  DROP INDEX \"time_plain\";\n  CREATE INDEX \"time_trunc\" ON PUBLIC.hyper_1 (date_trunc('minute', time));\n  ANALYZE hyper_1;\n  :PREFIX SELECT date_trunc('minute', time) t, avg(series_0), min(series_1), avg(series_2) FROM hyper_1 GROUP BY t ORDER BY t DESC limit 2;\n--- QUERY PLAN ---\n Limit\n   ->  GroupAggregate\n         Group Key: (date_trunc('minute'::text, _hyper_1_1_chunk.\"time\"))\n         ->  Result\n               ->  Index Scan Backward using _hyper_1_1_chunk_time_trunc on _hyper_1_1_chunk\n\n  --test that works with both indexes\n  CREATE INDEX \"time_plain\" ON PUBLIC.hyper_1 (time DESC, series_0);\n  ANALYZE hyper_1;\n  :PREFIX SELECT date_trunc('minute', time) t, avg(series_0), min(series_1), avg(series_2) FROM hyper_1 GROUP BY t ORDER BY t DESC limit 2;\n--- QUERY PLAN ---\n Limit\n   ->  GroupAggregate\n         Group Key: (date_trunc('minute'::text, _hyper_1_1_chunk.\"time\"))\n         ->  Result\n               ->  Index Scan Backward using _hyper_1_1_chunk_time_trunc on _hyper_1_1_chunk\n\n  :PREFIX SELECT time_bucket('1 minute', time) t, avg(series_0), min(series_1), trunc(avg(series_2)::numeric, 5)\n  FROM hyper_1 GROUP BY t ORDER BY t DESC limit 2;\n--- QUERY PLAN ---\n Limit\n   ->  GroupAggregate\n         Group Key: (time_bucket('@ 1 min'::interval, _hyper_1_1_chunk.\"time\"))\n         ->  Result\n               ->  Index Scan using _hyper_1_1_chunk_time_plain on _hyper_1_1_chunk\n\n  :PREFIX SELECT time_bucket('1 minute', time, INTERVAL '30 seconds') t, avg(series_0), min(series_1), trunc(avg(series_2)::numeric,5)\n  FROM hyper_1 GROUP BY t ORDER BY t DESC limit 2;\n--- QUERY PLAN ---\n Limit\n   ->  GroupAggregate\n         Group Key: (time_bucket('@ 1 min'::interval, _hyper_1_1_chunk.\"time\", '@ 30 secs'::interval))\n         ->  Result\n               ->  Index Scan using _hyper_1_1_chunk_time_plain on _hyper_1_1_chunk\n\n  :PREFIX SELECT time_bucket('1 minute', time - INTERVAL '30 seconds') t, avg(series_0), min(series_1), trunc(avg(series_2)::numeric,5)\n  FROM hyper_1 GROUP BY t ORDER BY t DESC limit 2;\n--- QUERY PLAN ---\n Limit\n   ->  GroupAggregate\n         Group Key: (time_bucket('@ 1 min'::interval, (_hyper_1_1_chunk.\"time\" - '@ 30 secs'::interval)))\n         ->  Result\n               ->  Index Scan using _hyper_1_1_chunk_time_plain on _hyper_1_1_chunk\n\n  :PREFIX SELECT time_bucket('1 minute', time - INTERVAL '30 seconds') + INTERVAL '30 seconds' t, avg(series_0), min(series_1), trunc(avg(series_2)::numeric,5)\n  FROM hyper_1 GROUP BY t ORDER BY t DESC limit 2;\n--- QUERY PLAN ---\n Limit\n   ->  GroupAggregate\n         Group Key: ((time_bucket('@ 1 min'::interval, (_hyper_1_1_chunk.\"time\" - '@ 30 secs'::interval)) + '@ 30 secs'::interval))\n         ->  Result\n               ->  Index Scan using _hyper_1_1_chunk_time_plain on _hyper_1_1_chunk\n\n  :PREFIX SELECT time_bucket('1 minute', time) t, avg(series_0), min(series_1), avg(series_2)\n  FROM hyper_1_tz GROUP BY t ORDER BY t DESC limit 2;\n--- QUERY PLAN ---\n Limit\n   ->  GroupAggregate\n         Group Key: (time_bucket('@ 1 min'::interval, _hyper_2_2_chunk.\"time\"))\n         ->  Result\n               ->  Index Scan using _hyper_2_2_chunk_time_plain_tz on _hyper_2_2_chunk\n\n  :PREFIX SELECT time_bucket('1 minute', time::timestamp) t, avg(series_0), min(series_1), avg(series_2)\n  FROM hyper_1_tz GROUP BY t ORDER BY t DESC limit 2;\n--- QUERY PLAN ---\n Limit\n   ->  GroupAggregate\n         Group Key: (time_bucket('@ 1 min'::interval, (_hyper_2_2_chunk.\"time\")::timestamp without time zone))\n         ->  Result\n               ->  Index Scan using _hyper_2_2_chunk_time_plain_tz on _hyper_2_2_chunk\n\n  :PREFIX SELECT time_bucket(10, time) t, avg(series_0), min(series_1), avg(series_2)\n  FROM hyper_1_int GROUP BY t ORDER BY t DESC limit 2;\n--- QUERY PLAN ---\n Limit\n   ->  GroupAggregate\n         Group Key: (time_bucket(10, hyper_1_int.\"time\"))\n         ->  Result\n               ->  Custom Scan (ChunkAppend) on hyper_1_int\n                     Order: time_bucket(10, hyper_1_int.\"time\") DESC\n                     ->  Index Scan using _hyper_3_5_chunk_time_plain_int on _hyper_3_5_chunk\n                     ->  Index Scan using _hyper_3_4_chunk_time_plain_int on _hyper_3_4_chunk\n                     ->  Index Scan using _hyper_3_3_chunk_time_plain_int on _hyper_3_3_chunk\n\n  :PREFIX SELECT time_bucket(10, time, 2) t, avg(series_0), min(series_1), avg(series_2)\n  FROM hyper_1_int GROUP BY t ORDER BY t DESC limit 2;\n--- QUERY PLAN ---\n Limit\n   ->  GroupAggregate\n         Group Key: (time_bucket(10, hyper_1_int.\"time\", 2))\n         ->  Result\n               ->  Custom Scan (ChunkAppend) on hyper_1_int\n                     Order: time_bucket(10, hyper_1_int.\"time\", 2) DESC\n                     ->  Index Scan using _hyper_3_5_chunk_time_plain_int on _hyper_3_5_chunk\n                     ->  Index Scan using _hyper_3_4_chunk_time_plain_int on _hyper_3_4_chunk\n                     ->  Index Scan using _hyper_3_3_chunk_time_plain_int on _hyper_3_3_chunk\n\nROLLBACK;\n-- sort order optimization should not be applied to non-hypertables\n:PREFIX\nSELECT date_trunc('minute', time) t, avg(series_0), min(series_1), avg(series_2)\nFROM plain_table\nWHERE time < to_timestamp(900)\nGROUP BY t\nORDER BY t DESC\nLIMIT 2;\n--- QUERY PLAN ---\n Limit\n   ->  Sort\n         Sort Key: (date_trunc('minute'::text, \"time\")) DESC\n         ->  HashAggregate\n               Group Key: date_trunc('minute'::text, \"time\")\n               ->  Index Scan using time_plain_plain_table on plain_table\n                     Index Cond: (\"time\" < 'Wed Dec 31 16:15:00 1969 PST'::timestamp with time zone)\n\n--generate the results into two different files\n\\set ECHO errors\n--- Unoptimized result\n+++ Optimized result\n@@ -1,6 +1,6 @@\n  timescaledb.enable_optimizations \n ----------------------------------\n- off\n+ on\n \n            time           | series_0 | series_1 |     series_2     \n ?column? \n----------\n Done\n\n"
  },
  {
    "path": "test/expected/relocate_extension.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- Set this variable to avoid using a hard-coded path each time query\n-- results are compared\n\\set QUERY_RESULT_TEST_EQUAL_RELPATH 'include/query_result_test_equal.sql'\n\\c postgres :ROLE_SUPERUSER\nDROP DATABASE :TEST_DBNAME WITH (FORCE);\nCREATE DATABASE :TEST_DBNAME;\n\\c :TEST_DBNAME\nCREATE SCHEMA \"testSchema0\";\nSET client_min_messages=error;\nCREATE EXTENSION IF NOT EXISTS timescaledb SCHEMA \"testSchema0\";\nRESET client_min_messages;\nCREATE TABLE test_ts(time timestamp, temp float8, device text);\nCREATE TABLE test_tz(time timestamptz, temp float8, device text);\nCREATE TABLE test_dt(time date, temp float8, device text);\nSELECT \"testSchema0\".create_hypertable('test_ts', 'time', 'device', 2);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n  create_hypertable   \n----------------------\n (1,public,test_ts,t)\n\nSELECT \"testSchema0\".create_hypertable('test_tz', 'time', 'device', 2);\n  create_hypertable   \n----------------------\n (2,public,test_tz,t)\n\nSELECT \"testSchema0\".create_hypertable('test_dt', 'time', 'device', 2);\n  create_hypertable   \n----------------------\n (3,public,test_dt,t)\n\nSELECT * FROM _timescaledb_catalog.hypertable;\n id | schema_name | table_name | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------+------------+------------------------+-------------------------+----------------+--------------------------+--------------------------+-------------------+-------------------+--------------------------+--------\n  1 | public      | test_ts    | _timescaledb_internal  | _hyper_1                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n  2 | public      | test_tz    | _timescaledb_internal  | _hyper_2                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n  3 | public      | test_dt    | _timescaledb_internal  | _hyper_3                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n\nINSERT INTO test_ts VALUES('Mon Mar 20 09:17:00.936242 2017', 23.4, 'dev1');\nINSERT INTO test_ts VALUES('Mon Mar 20 09:27:00.936242 2017', 22, 'dev2');\nINSERT INTO test_ts VALUES('Mon Mar 20 09:28:00.936242 2017', 21.2, 'dev1');\nINSERT INTO test_ts VALUES('Mon Mar 20 09:37:00.936242 2017', 30, 'dev3');\nSELECT * FROM test_ts ORDER BY time;\n              time               | temp | device \n---------------------------------+------+--------\n Mon Mar 20 09:17:00.936242 2017 | 23.4 | dev1\n Mon Mar 20 09:27:00.936242 2017 |   22 | dev2\n Mon Mar 20 09:28:00.936242 2017 | 21.2 | dev1\n Mon Mar 20 09:37:00.936242 2017 |   30 | dev3\n\nINSERT INTO test_tz VALUES('Mon Mar 20 09:17:00.936242 2017', 23.4, 'dev1');\nINSERT INTO test_tz VALUES('Mon Mar 20 09:27:00.936242 2017', 22, 'dev2');\nINSERT INTO test_tz VALUES('Mon Mar 20 09:28:00.936242 2017', 21.2, 'dev1');\nINSERT INTO test_tz VALUES('Mon Mar 20 09:37:00.936242 2017', 30, 'dev3');\nSELECT * FROM test_tz ORDER BY time;\n                time                 | temp | device \n-------------------------------------+------+--------\n Mon Mar 20 09:17:00.936242 2017 PDT | 23.4 | dev1\n Mon Mar 20 09:27:00.936242 2017 PDT |   22 | dev2\n Mon Mar 20 09:28:00.936242 2017 PDT | 21.2 | dev1\n Mon Mar 20 09:37:00.936242 2017 PDT |   30 | dev3\n\nINSERT INTO test_dt VALUES('Mon Mar 20 09:17:00.936242 2017', 23.4, 'dev1');\nINSERT INTO test_dt VALUES('Mon Mar 21 09:27:00.936242 2017', 22, 'dev2');\nINSERT INTO test_dt VALUES('Mon Mar 22 09:28:00.936242 2017', 21.2, 'dev1');\nINSERT INTO test_dt VALUES('Mon Mar 23 09:37:00.936242 2017', 30, 'dev3');\nSELECT * FROM test_dt ORDER BY time;\n    time    | temp | device \n------------+------+--------\n 03-20-2017 | 23.4 | dev1\n 03-21-2017 |   22 | dev2\n 03-22-2017 | 21.2 | dev1\n 03-23-2017 |   30 | dev3\n\n-- testing time_bucket START\nSELECT AVG(temp) AS avg_tmp, \"testSchema0\".time_bucket('5 minutes', time, INTERVAL '1 minutes') AS ten_min FROM test_ts GROUP BY ten_min ORDER BY avg_tmp;\n avg_tmp |         ten_min          \n---------+--------------------------\n    21.6 | Mon Mar 20 09:26:00 2017\n    23.4 | Mon Mar 20 09:16:00 2017\n      30 | Mon Mar 20 09:36:00 2017\n\nSELECT AVG(temp) AS avg_tmp, \"testSchema0\".time_bucket('5 minutes', time, INTERVAL '1 minutes') AS ten_min FROM test_tz GROUP BY ten_min ORDER BY avg_tmp;\n avg_tmp |           ten_min            \n---------+------------------------------\n    21.6 | Mon Mar 20 09:26:00 2017 PDT\n    23.4 | Mon Mar 20 09:16:00 2017 PDT\n      30 | Mon Mar 20 09:36:00 2017 PDT\n\nSELECT AVG(temp) AS avg_tmp, \"testSchema0\".time_bucket('1 day', time, INTERVAL '-0.5 day') AS ten_min FROM test_dt GROUP BY ten_min ORDER BY avg_tmp;\n avg_tmp |  ten_min   \n---------+------------\n    21.2 | 03-21-2017\n      22 | 03-20-2017\n    23.4 | 03-19-2017\n      30 | 03-22-2017\n\n-- testing time_bucket END\n-- testing drop_chunks START\n-- show_chunks and drop_chunks output should be the same\n\\set QUERY1 'SELECT \"testSchema0\".show_chunks(older_than => \\'2017-03-01\\'::timestamp, relation => \\'test_ts\\')::REGCLASS::TEXT'\n\\set QUERY2 'SELECT \"testSchema0\".drop_chunks(\\'test_ts\\', \\'2017-03-01\\'::timestamp)::TEXT'\n\\set ECHO errors\n Different Rows | Total Rows from Query 1 | Total Rows from Query 2 \n----------------+-------------------------+-------------------------\n              0 |                       0 |                       0\n\nSELECT * FROM test_ts ORDER BY time;\n              time               | temp | device \n---------------------------------+------+--------\n Mon Mar 20 09:17:00.936242 2017 | 23.4 | dev1\n Mon Mar 20 09:27:00.936242 2017 |   22 | dev2\n Mon Mar 20 09:28:00.936242 2017 | 21.2 | dev1\n Mon Mar 20 09:37:00.936242 2017 |   30 | dev3\n\n\\set QUERY1 'SELECT \"testSchema0\".show_chunks(older_than => interval \\'1 minutes\\', relation => \\'test_tz\\')::REGCLASS::TEXT'\n\\set QUERY2 'SELECT \"testSchema0\".drop_chunks(\\'test_tz\\', interval \\'1 minutes\\')::TEXT'\n\\set ECHO errors\n Different Rows | Total Rows from Query 1 | Total Rows from Query 2 \n----------------+-------------------------+-------------------------\n              0 |                       2 |                       2\n\nSELECT * FROM test_tz ORDER BY time;\n time | temp | device \n------+------+--------\n\n\\set QUERY1 'SELECT \"testSchema0\".show_chunks(older_than => interval \\'1 minutes\\', relation => \\'test_dt\\')::REGCLASS::TEXT'\n\\set QUERY2 'SELECT \"testSchema0\".drop_chunks(\\'test_dt\\', interval \\'1 minutes\\')::TEXT'\n\\set ECHO errors\n Different Rows | Total Rows from Query 1 | Total Rows from Query 2 \n----------------+-------------------------+-------------------------\n              0 |                       3 |                       3\n\nSELECT * FROM test_dt ORDER BY time;\n time | temp | device \n------+------+--------\n\n-- testing drop_chunks END\n-- testing hypertable_detailed_size START\nSELECT * FROM \"testSchema0\".hypertable_detailed_size('test_ts');\n table_bytes | index_bytes | toast_bytes | total_bytes | node_name \n-------------+-------------+-------------+-------------+-----------\n       16384 |       81920 |       24576 |      122880 | \n\n-- testing hypertable_detailed_size END\nSELECT * FROM \"testSchema0\".hypertable_index_size('test_ts_time_idx');\n hypertable_index_size \n-----------------------\n                 40960\n\nSELECT * FROM \"testSchema0\".hypertable_index_size('test_ts_device_time_idx');\n hypertable_index_size \n-----------------------\n                 40960\n\nCREATE SCHEMA \"testSchema\";\n\\set ON_ERROR_STOP 0\nALTER EXTENSION timescaledb SET SCHEMA \"testSchema\";\nERROR:  extension \"timescaledb\" does not support SET SCHEMA\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "test/expected/reloptions.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE reloptions_test(time integer, temp float8, color integer)\nWITH (fillfactor=75, autovacuum_vacuum_threshold=100);\nSELECT create_hypertable('reloptions_test', 'time', chunk_time_interval => 3);\n      create_hypertable       \n------------------------------\n (1,public,reloptions_test,t)\n\nINSERT INTO reloptions_test VALUES (4, 24.3, 1), (9, 13.3, 2);\n-- Show that reloptions are inherited by chunks\nSELECT relname, reloptions FROM pg_class\nWHERE relname ~ '^_hyper.*' AND relkind = 'r';\n     relname      |                   reloptions                    \n------------------+-------------------------------------------------\n _hyper_1_1_chunk | {fillfactor=75,autovacuum_vacuum_threshold=100}\n _hyper_1_2_chunk | {fillfactor=75,autovacuum_vacuum_threshold=100}\n\n-- Alter reloptions. We support multiple options for the ALTER TABLE\nALTER TABLE reloptions_test SET (fillfactor=80, parallel_workers=8);\nALTER TABLE reloptions_test SET (fillfactor=80), SET (parallel_workers=8);\nSELECT relname, reloptions FROM pg_class\nWHERE relname ~ '^_hyper.*' AND relkind = 'r';\n     relname      |                             reloptions                             \n------------------+--------------------------------------------------------------------\n _hyper_1_1_chunk | {autovacuum_vacuum_threshold=100,fillfactor=80,parallel_workers=8}\n _hyper_1_2_chunk | {autovacuum_vacuum_threshold=100,fillfactor=80,parallel_workers=8}\n\nALTER TABLE reloptions_test RESET (fillfactor);\nSELECT relname, reloptions FROM pg_class\nWHERE relname ~ '^_hyper.*' AND relkind = 'r';\n     relname      |                      reloptions                      \n------------------+------------------------------------------------------\n _hyper_1_1_chunk | {autovacuum_vacuum_threshold=100,parallel_workers=8}\n _hyper_1_2_chunk | {autovacuum_vacuum_threshold=100,parallel_workers=8}\n\n-- Test reloptions on a regular table\nCREATE TABLE reloptions_test2(time integer, temp float8, color integer);\nALTER TABLE reloptions_test2 SET (fillfactor=80, parallel_workers=8);\nALTER TABLE reloptions_test2 SET (fillfactor=80), SET (parallel_workers=8);\nDROP TABLE reloptions_test2;\n"
  },
  {
    "path": "test/expected/repair.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- We are testing different repair functions here to make sure that\n-- they work as expected.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\\set TMP_USER :TEST_DBNAME _wizard\nCREATE USER :TMP_USER;\nCREATE USER \"Random L User\";\nCREATE TABLE test_table_1(time timestamptz not null, temp float);\nSELECT create_hypertable('test_table_1', by_range('time', '1 day'::interval));\n create_hypertable \n-------------------\n (1,t)\n\nINSERT INTO test_table_1(time,temp)\nSELECT time, 100 * random()\n  FROM generate_series(\n           '2000-01-01'::timestamptz,\n           '2000-01-05'::timestamptz,\n           '1min'::interval\n       ) time;\nCREATE TABLE test_table_2(time timestamptz not null, temp float);\nSELECT create_hypertable('test_table_2', by_range('time', '1 day'::interval));\n create_hypertable \n-------------------\n (2,t)\n\nINSERT INTO test_table_2(time,temp)\nSELECT time, 100 * random()\n  FROM generate_series(\n           '2000-01-01'::timestamptz,\n           '2000-01-05'::timestamptz,\n           '1min'::interval\n       ) time;\nGRANT ALL ON test_table_1 TO :TMP_USER;\nGRANT ALL ON test_table_2 TO :TMP_USER;\nGRANT SELECT, INSERT ON test_table_1 TO \"Random L User\";\nGRANT INSERT ON test_table_2 TO \"Random L User\";\n-- Break the relacl of the table by deleting users\nDELETE FROM pg_authid WHERE rolname IN (:'TMP_USER', 'Random L User');\nCREATE TABLE saved (LIKE pg_class);\nINSERT INTO saved SELECT * FROM pg_class;\nCALL _timescaledb_functions.repair_relation_acls();\n-- The only thing we should see here are the relations we broke and\n-- the privileges we added for that user. No other relations should be\n-- touched.\nWITH\n   lhs AS (SELECT oid, aclexplode(relacl) FROM pg_class),\n   rhs AS (SELECT oid, aclexplode(relacl) FROM saved)\nSELECT rhs.oid::regclass\nFROM lhs FULL OUTER JOIN rhs ON row(lhs) = row(rhs)\nWHERE lhs.oid IS NULL AND rhs.oid IS NOT NULL\nGROUP BY rhs.oid;\n                   oid                   \n-----------------------------------------\n test_table_1\n _timescaledb_internal._hyper_1_1_chunk\n _timescaledb_internal._hyper_1_2_chunk\n _timescaledb_internal._hyper_1_3_chunk\n _timescaledb_internal._hyper_1_4_chunk\n _timescaledb_internal._hyper_1_5_chunk\n test_table_2\n _timescaledb_internal._hyper_2_6_chunk\n _timescaledb_internal._hyper_2_7_chunk\n _timescaledb_internal._hyper_2_8_chunk\n _timescaledb_internal._hyper_2_9_chunk\n _timescaledb_internal._hyper_2_10_chunk\n\nDROP TABLE saved;\nDROP TABLE test_table_1;\nDROP TABLE test_table_2;\n"
  },
  {
    "path": "test/expected/rowsecurity-15.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n--\n-- Test of Row-level security feature\n--\n-- Clean up in case a prior regression run failed\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\\set ON_ERROR_STOP 0\n\\set VERBOSITY default\nSET timescaledb.enable_constraint_exclusion TO off;\n-- Suppress NOTICE messages when users/groups don't exist\nSET client_min_messages TO 'warning';\nDROP USER IF EXISTS regress_rls_alice;\nDROP USER IF EXISTS regress_rls_bob;\nDROP USER IF EXISTS regress_rls_carol;\nDROP USER IF EXISTS regress_rls_dave;\nDROP USER IF EXISTS regress_rls_exempt_user;\nDROP ROLE IF EXISTS regress_rls_group1;\nDROP ROLE IF EXISTS regress_rls_group2;\nDROP SCHEMA IF EXISTS regress_rls_schema CASCADE;\nRESET client_min_messages;\n-- initial setup\nCREATE USER regress_rls_alice NOLOGIN;\nCREATE USER regress_rls_bob NOLOGIN;\nCREATE USER regress_rls_carol NOLOGIN;\nCREATE USER regress_rls_dave NOLOGIN;\nCREATE USER regress_rls_exempt_user BYPASSRLS NOLOGIN;\nCREATE ROLE regress_rls_group1 NOLOGIN;\nCREATE ROLE regress_rls_group2 NOLOGIN;\nGRANT regress_rls_group1 TO regress_rls_bob;\nGRANT regress_rls_group2 TO regress_rls_carol;\nCREATE SCHEMA regress_rls_schema;\nGRANT ALL ON SCHEMA regress_rls_schema to public;\nSET search_path = regress_rls_schema;\n-- setup of malicious function\nCREATE OR REPLACE FUNCTION f_leak(text) RETURNS bool\n    COST 0.0000001 LANGUAGE plpgsql\n    AS 'BEGIN RAISE NOTICE ''f_leak => %'', $1; RETURN true; END';\nGRANT EXECUTE ON FUNCTION f_leak(text) TO public;\n-- BASIC Row-Level Security Scenario\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE uaccount (\n    pguser      name primary key,\n    seclv       int\n);\nGRANT SELECT ON uaccount TO public;\nINSERT INTO uaccount VALUES\n    ('regress_rls_alice', 99),\n    ('regress_rls_bob', 1),\n    ('regress_rls_carol', 2),\n    ('regress_rls_dave', 3);\nCREATE TABLE category (\n    cid        int primary key,\n    cname      text\n);\nGRANT ALL ON category TO public;\nINSERT INTO category VALUES\n    (11, 'novel'),\n    (22, 'science fiction'),\n    (33, 'technology'),\n    (44, 'manga');\nCREATE TABLE document (\n    did         int primary key,\n    cid         int references category(cid),\n    dlevel      int not null,\n    dauthor     name,\n    dtitle      text\n);\nGRANT ALL ON document TO public;\nSELECT public.create_hypertable('document', 'did', chunk_time_interval=>2);\n         create_hypertable         \n-----------------------------------\n (1,regress_rls_schema,document,t)\n\nINSERT INTO document VALUES\n    ( 1, 11, 1, 'regress_rls_bob', 'my first novel'),\n    ( 2, 11, 2, 'regress_rls_bob', 'my second novel'),\n    ( 3, 22, 2, 'regress_rls_bob', 'my science fiction'),\n    ( 4, 44, 1, 'regress_rls_bob', 'my first manga'),\n    ( 5, 44, 2, 'regress_rls_bob', 'my second manga'),\n    ( 6, 22, 1, 'regress_rls_carol', 'great science fiction'),\n    ( 7, 33, 2, 'regress_rls_carol', 'great technology book'),\n    ( 8, 44, 1, 'regress_rls_carol', 'great manga'),\n    ( 9, 22, 1, 'regress_rls_dave', 'awesome science fiction'),\n    (10, 33, 2, 'regress_rls_dave', 'awesome technology book');\nALTER TABLE document ENABLE ROW LEVEL SECURITY;\n-- user's security level must be higher than or equal to document's\nCREATE POLICY p1 ON document AS PERMISSIVE\n    USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));\n-- try to create a policy of bogus type\nCREATE POLICY p1 ON document AS UGLY\n    USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));\nERROR:  unrecognized row security option \"ugly\"\nLINE 1: CREATE POLICY p1 ON document AS UGLY\n                                        ^\nHINT:  Only PERMISSIVE or RESTRICTIVE policies are supported currently.\n-- but Dave isn't allowed to anything at cid 50 or above\n-- this is to make sure that we sort the policies by name first\n-- when applying WITH CHECK, a later INSERT by Dave should fail due\n-- to p1r first\nCREATE POLICY p2r ON document AS RESTRICTIVE TO regress_rls_dave\n    USING (cid <> 44 AND cid < 50);\n-- and Dave isn't allowed to see manga documents\nCREATE POLICY p1r ON document AS RESTRICTIVE TO regress_rls_dave\n    USING (cid <> 44);\n\\dp\n                                                                  Access privileges\n       Schema       |   Name   | Type  |              Access privileges              | Column privileges |                  Policies                  \n--------------------+----------+-------+---------------------------------------------+-------------------+--------------------------------------------\n regress_rls_schema | category | table | regress_rls_alice=arwdDxt/regress_rls_alice+|                   | \n                    |          |       | =arwdDxt/regress_rls_alice                  |                   | \n regress_rls_schema | document | table | regress_rls_alice=arwdDxt/regress_rls_alice+|                   | p1:                                       +\n                    |          |       | =arwdDxt/regress_rls_alice                  |                   |   (u): (dlevel <= ( SELECT uaccount.seclv +\n                    |          |       |                                             |                   |    FROM uaccount                          +\n                    |          |       |                                             |                   |   WHERE (uaccount.pguser = CURRENT_USER)))+\n                    |          |       |                                             |                   | p2r (RESTRICTIVE):                        +\n                    |          |       |                                             |                   |   (u): ((cid <> 44) AND (cid < 50))       +\n                    |          |       |                                             |                   |   to: regress_rls_dave                    +\n                    |          |       |                                             |                   | p1r (RESTRICTIVE):                        +\n                    |          |       |                                             |                   |   (u): (cid <> 44)                        +\n                    |          |       |                                             |                   |   to: regress_rls_dave\n regress_rls_schema | uaccount | table | regress_rls_alice=arwdDxt/regress_rls_alice+|                   | \n                    |          |       | =r/regress_rls_alice                        |                   | \n\n\\d document\n        Table \"regress_rls_schema.document\"\n Column  |  Type   | Collation | Nullable | Default \n---------+---------+-----------+----------+---------\n did     | integer |           | not null | \n cid     | integer |           |          | \n dlevel  | integer |           | not null | \n dauthor | name    |           |          | \n dtitle  | text    |           |          | \nIndexes:\n    \"document_pkey\" PRIMARY KEY, btree (did)\nForeign-key constraints:\n    \"document_cid_fkey\" FOREIGN KEY (cid) REFERENCES category(cid)\nPolicies:\n    POLICY \"p1\"\n      USING ((dlevel <= ( SELECT uaccount.seclv\n   FROM uaccount\n  WHERE (uaccount.pguser = CURRENT_USER))))\n    POLICY \"p1r\" AS RESTRICTIVE\n      TO regress_rls_dave\n      USING ((cid <> 44))\n    POLICY \"p2r\" AS RESTRICTIVE\n      TO regress_rls_dave\n      USING (((cid <> 44) AND (cid < 50)))\nNumber of child tables: 6 (Use \\d+ to list them.)\n\nSELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename = 'document' ORDER BY policyname;\n     schemaname     | tablename | policyname | permissive  |       roles        | cmd |                    qual                    | with_check \n--------------------+-----------+------------+-------------+--------------------+-----+--------------------------------------------+------------\n regress_rls_schema | document  | p1         | PERMISSIVE  | {public}           | ALL | (dlevel <= ( SELECT uaccount.seclv        +| \n                    |           |            |             |                    |     |    FROM uaccount                          +| \n                    |           |            |             |                    |     |   WHERE (uaccount.pguser = CURRENT_USER))) | \n regress_rls_schema | document  | p1r        | RESTRICTIVE | {regress_rls_dave} | ALL | (cid <> 44)                                | \n regress_rls_schema | document  | p2r        | RESTRICTIVE | {regress_rls_dave} | ALL | ((cid <> 44) AND (cid < 50))               | \n\n-- viewpoint from regress_rls_bob\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO ON;\nSELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my first manga\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great manga\nNOTICE:  f_leak => awesome science fiction\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n\nSELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my first manga\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great manga\nNOTICE:  f_leak => awesome science fiction\n cid | did | dlevel |      dauthor      |         dtitle          |      cname      \n-----+-----+--------+-------------------+-------------------------+-----------------\n  11 |   1 |      1 | regress_rls_bob   | my first novel          | novel\n  44 |   4 |      1 | regress_rls_bob   | my first manga          | manga\n  22 |   6 |      1 | regress_rls_carol | great science fiction   | science fiction\n  44 |   8 |      1 | regress_rls_carol | great manga             | manga\n  22 |   9 |      1 | regress_rls_dave  | awesome science fiction | science fiction\n\n-- try a sampled version\nSELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0)\n  WHERE f_leak(dtitle) ORDER BY did;\n did | cid | dlevel | dauthor | dtitle \n-----+-----+--------+---------+--------\n\n-- viewpoint from regress_rls_carol\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science fiction\nNOTICE:  f_leak => my first manga\nNOTICE:  f_leak => my second manga\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great manga\nNOTICE:  f_leak => awesome science fiction\nNOTICE:  f_leak => awesome technology book\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   5 |  44 |      2 | regress_rls_bob   | my second manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n\nSELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science fiction\nNOTICE:  f_leak => my first manga\nNOTICE:  f_leak => my second manga\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great manga\nNOTICE:  f_leak => awesome science fiction\nNOTICE:  f_leak => awesome technology book\n cid | did | dlevel |      dauthor      |         dtitle          |      cname      \n-----+-----+--------+-------------------+-------------------------+-----------------\n  11 |   1 |      1 | regress_rls_bob   | my first novel          | novel\n  11 |   2 |      2 | regress_rls_bob   | my second novel         | novel\n  22 |   3 |      2 | regress_rls_bob   | my science fiction      | science fiction\n  44 |   4 |      1 | regress_rls_bob   | my first manga          | manga\n  44 |   5 |      2 | regress_rls_bob   | my second manga         | manga\n  22 |   6 |      1 | regress_rls_carol | great science fiction   | science fiction\n  33 |   7 |      2 | regress_rls_carol | great technology book   | technology\n  44 |   8 |      1 | regress_rls_carol | great manga             | manga\n  22 |   9 |      1 | regress_rls_dave  | awesome science fiction | science fiction\n  33 |  10 |      2 | regress_rls_dave  | awesome technology book | technology\n\n-- try a sampled version\nSELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0)\n  WHERE f_leak(dtitle) ORDER BY did;\n did | cid | dlevel | dauthor | dtitle \n-----+-----+--------+---------+--------\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on document\n   Chunks excluded during startup: 0\n   InitPlan 1 (returns $0)\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on document document_1\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_1_chunk document_2\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_2_chunk document_3\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_3_chunk document_4\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_4_chunk document_5\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_5_chunk document_6\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_6_chunk document_7\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Hash Join\n   Hash Cond: (document.cid = category.cid)\n   InitPlan 1 (returns $0)\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Custom Scan (ChunkAppend) on document\n         Chunks excluded during startup: 0\n         ->  Seq Scan on document document_1\n               Filter: ((dlevel <= $0) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_1_chunk document_2\n               Filter: ((dlevel <= $0) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_2_chunk document_3\n               Filter: ((dlevel <= $0) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_3_chunk document_4\n               Filter: ((dlevel <= $0) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_4_chunk document_5\n               Filter: ((dlevel <= $0) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_5_chunk document_6\n               Filter: ((dlevel <= $0) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_6_chunk document_7\n               Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Hash\n         ->  Seq Scan on category\n\n-- viewpoint from regress_rls_dave\nSET SESSION AUTHORIZATION regress_rls_dave;\nSELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science fiction\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => awesome science fiction\nNOTICE:  f_leak => awesome technology book\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n\nSELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science fiction\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => awesome science fiction\nNOTICE:  f_leak => awesome technology book\n cid | did | dlevel |      dauthor      |         dtitle          |      cname      \n-----+-----+--------+-------------------+-------------------------+-----------------\n  11 |   1 |      1 | regress_rls_bob   | my first novel          | novel\n  11 |   2 |      2 | regress_rls_bob   | my second novel         | novel\n  22 |   3 |      2 | regress_rls_bob   | my science fiction      | science fiction\n  22 |   6 |      1 | regress_rls_carol | great science fiction   | science fiction\n  33 |   7 |      2 | regress_rls_carol | great technology book   | technology\n  22 |   9 |      1 | regress_rls_dave  | awesome science fiction | science fiction\n  33 |  10 |      2 | regress_rls_dave  | awesome technology book | technology\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on document\n   Chunks excluded during startup: 0\n   InitPlan 1 (returns $0)\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on document document_1\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_1_chunk document_2\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_2_chunk document_3\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_3_chunk document_4\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_4_chunk document_5\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_5_chunk document_6\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_6_chunk document_7\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Hash Join\n   Hash Cond: (category.cid = document.cid)\n   InitPlan 1 (returns $0)\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on category\n   ->  Hash\n         ->  Custom Scan (ChunkAppend) on document\n               Chunks excluded during startup: 0\n               ->  Seq Scan on document document_1\n                     Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n               ->  Seq Scan on _hyper_1_1_chunk document_2\n                     Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n               ->  Seq Scan on _hyper_1_2_chunk document_3\n                     Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n               ->  Seq Scan on _hyper_1_3_chunk document_4\n                     Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n               ->  Seq Scan on _hyper_1_4_chunk document_5\n                     Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n               ->  Seq Scan on _hyper_1_5_chunk document_6\n                     Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n               ->  Seq Scan on _hyper_1_6_chunk document_7\n                     Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n\n-- 44 would technically fail for both p2r and p1r, but we should get an error\n-- back from p1r for this because it sorts first\nINSERT INTO document VALUES (100, 44, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail\nERROR:  new row violates row-level security policy \"p1r\" for table \"document\"\n-- Just to see a p2r error\nINSERT INTO document VALUES (100, 55, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail\nERROR:  new row violates row-level security policy \"p2r\" for table \"document\"\n-- only owner can change policies\nALTER POLICY p1 ON document USING (true);    --fail\nERROR:  must be owner of table document\nDROP POLICY p1 ON document;                  --fail\nERROR:  must be owner of relation document\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER POLICY p1 ON document USING (dauthor = current_user);\n-- viewpoint from regress_rls_bob again\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science fiction\nNOTICE:  f_leak => my first manga\nNOTICE:  f_leak => my second manga\n did | cid | dlevel |     dauthor     |       dtitle       \n-----+-----+--------+-----------------+--------------------\n   1 |  11 |      1 | regress_rls_bob | my first novel\n   2 |  11 |      2 | regress_rls_bob | my second novel\n   3 |  22 |      2 | regress_rls_bob | my science fiction\n   4 |  44 |      1 | regress_rls_bob | my first manga\n   5 |  44 |      2 | regress_rls_bob | my second manga\n\nSELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science fiction\nNOTICE:  f_leak => my first manga\nNOTICE:  f_leak => my second manga\n cid | did | dlevel |     dauthor     |       dtitle       |      cname      \n-----+-----+--------+-----------------+--------------------+-----------------\n  11 |   1 |      1 | regress_rls_bob | my first novel     | novel\n  11 |   2 |      2 | regress_rls_bob | my second novel    | novel\n  22 |   3 |      2 | regress_rls_bob | my science fiction | science fiction\n  44 |   4 |      1 | regress_rls_bob | my first manga     | manga\n  44 |   5 |      2 | regress_rls_bob | my second manga    | manga\n\n-- viewpoint from rls_regres_carol again\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great manga\n did | cid | dlevel |      dauthor      |        dtitle         \n-----+-----+--------+-------------------+-----------------------\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n\nSELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did;\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great manga\n cid | did | dlevel |      dauthor      |        dtitle         |      cname      \n-----+-----+--------+-------------------+-----------------------+-----------------\n  22 |   6 |      1 | regress_rls_carol | great science fiction | science fiction\n  33 |   7 |      2 | regress_rls_carol | great technology book | technology\n  44 |   8 |      1 | regress_rls_carol | great manga           | manga\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on document\n   Chunks excluded during startup: 0\n   ->  Seq Scan on document document_1\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_1_chunk document_2\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_2_chunk document_3\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_3_chunk document_4\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_4_chunk document_5\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_5_chunk document_6\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_6_chunk document_7\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Nested Loop\n   ->  Custom Scan (ChunkAppend) on document\n         Chunks excluded during startup: 0\n         ->  Seq Scan on document document_1\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_1_chunk document_2\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_2_chunk document_3\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_3_chunk document_4\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_4_chunk document_5\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_5_chunk document_6\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_6_chunk document_7\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Index Scan using category_pkey on category\n         Index Cond: (cid = document.cid)\n\n-- interaction of FK/PK constraints\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE POLICY p2 ON category\n    USING (CASE WHEN current_user = 'regress_rls_bob' THEN cid IN (11, 33)\n           WHEN current_user = 'regress_rls_carol' THEN cid IN (22, 44)\n           ELSE false END);\nALTER TABLE category ENABLE ROW LEVEL SECURITY;\n-- cannot delete PK referenced by invisible FK\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid ORDER BY d.did, c.cid;\n did | cid | dlevel |     dauthor     |       dtitle       | cid |   cname    \n-----+-----+--------+-----------------+--------------------+-----+------------\n   1 |  11 |      1 | regress_rls_bob | my first novel     |  11 | novel\n   2 |  11 |      2 | regress_rls_bob | my second novel    |  11 | novel\n   3 |  22 |      2 | regress_rls_bob | my science fiction |     | \n   4 |  44 |      1 | regress_rls_bob | my first manga     |     | \n   5 |  44 |      2 | regress_rls_bob | my second manga    |     | \n     |     |        |                 |                    |  33 | technology\n\n\\set VERBOSITY sqlstate\nDELETE FROM category WHERE cid = 33;    -- fails with FK violation\nERROR:  23503\n\\set VERBOSITY default\n-- can insert FK referencing invisible PK\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid ORDER BY d.did, c.cid;\n did | cid | dlevel |      dauthor      |        dtitle         | cid |      cname      \n-----+-----+--------+-------------------+-----------------------+-----+-----------------\n   6 |  22 |      1 | regress_rls_carol | great science fiction |  22 | science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book |     | \n   8 |  44 |      1 | regress_rls_carol | great manga           |  44 | manga\n\nINSERT INTO document VALUES (11, 33, 1, current_user, 'hoge');\n-- UNIQUE or PRIMARY KEY constraint violation DOES reveal presence of row\nSET SESSION AUTHORIZATION regress_rls_bob;\nINSERT INTO document VALUES (8, 44, 1, 'regress_rls_bob', 'my third manga'); -- Must fail with unique violation, revealing presence of did we can't see\nERROR:  duplicate key value violates unique constraint \"5_10_document_pkey\"\nDETAIL:  Key (did)=(8) already exists.\nSELECT * FROM document WHERE did = 8; -- and confirm we can't see it\n did | cid | dlevel | dauthor | dtitle \n-----+-----+--------+---------+--------\n\n-- RLS policies are checked before constraints\nINSERT INTO document VALUES (8, 44, 1, 'regress_rls_carol', 'my third manga'); -- Should fail with RLS check violation, not duplicate key violation\nERROR:  new row violates row-level security policy for table \"document\"\nUPDATE document SET did = 8, dauthor = 'regress_rls_carol' WHERE did = 5; -- Should fail with RLS check violation, not duplicate key violation\nERROR:  new row violates row-level security policy for table \"document\"\n-- database superuser does bypass RLS policy when enabled\nRESET SESSION AUTHORIZATION;\nSET row_security TO ON;\nSELECT * FROM document;\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   5 |  44 |      2 | regress_rls_bob   | my second manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n  11 |  33 |      1 | regress_rls_carol | hoge\n\nSELECT * FROM category;\n cid |      cname      \n-----+-----------------\n  11 | novel\n  22 | science fiction\n  33 | technology\n  44 | manga\n\n-- database superuser does bypass RLS policy when disabled\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nSELECT * FROM document;\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   5 |  44 |      2 | regress_rls_bob   | my second manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n  11 |  33 |      1 | regress_rls_carol | hoge\n\nSELECT * FROM category;\n cid |      cname      \n-----+-----------------\n  11 | novel\n  22 | science fiction\n  33 | technology\n  44 | manga\n\n-- database non-superuser with bypass privilege can bypass RLS policy when disabled\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO OFF;\nSELECT * FROM document;\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   5 |  44 |      2 | regress_rls_bob   | my second manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n  11 |  33 |      1 | regress_rls_carol | hoge\n\nSELECT * FROM category;\n cid |      cname      \n-----+-----------------\n  11 | novel\n  22 | science fiction\n  33 | technology\n  44 | manga\n\n-- RLS policy does not apply to table owner when RLS enabled.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO ON;\nSELECT * FROM document;\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   5 |  44 |      2 | regress_rls_bob   | my second manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n  11 |  33 |      1 | regress_rls_carol | hoge\n\nSELECT * FROM category;\n cid |      cname      \n-----+-----------------\n  11 | novel\n  22 | science fiction\n  33 | technology\n  44 | manga\n\n-- RLS policy does not apply to table owner when RLS disabled.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO OFF;\nSELECT * FROM document;\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   5 |  44 |      2 | regress_rls_bob   | my second manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n  11 |  33 |      1 | regress_rls_carol | hoge\n\nSELECT * FROM category;\n cid |      cname      \n-----+-----------------\n  11 | novel\n  22 | science fiction\n  33 | technology\n  44 | manga\n\n--\n-- Table inheritance and RLS policy\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO ON;\nCREATE TABLE t1 (a int, junk1 text, b text);\nALTER TABLE t1 DROP COLUMN junk1;    -- just a disturbing factor\nGRANT ALL ON t1 TO public;\nCOPY t1 FROM stdin;\nCREATE TABLE t2 (c float) INHERITS (t1);\nGRANT ALL ON t2 TO public;\nCOPY t2 FROM stdin;\nCREATE TABLE t3 (c text, b text, a int);\nALTER TABLE t3 INHERIT t1;\nGRANT ALL ON t3 TO public;\nCOPY t3(a,b,c) FROM stdin;\nCREATE POLICY p1 ON t1 FOR ALL TO PUBLIC USING (a % 2 = 0); -- be even number\nCREATE POLICY p2 ON t2 FOR ALL TO PUBLIC USING (a % 2 = 1); -- be odd number\nALTER TABLE t1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE t2 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM t1;\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n 2 | bcd\n 4 | def\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: ((a % 2) = 0)\n   ->  Seq Scan on t2 t1_2\n         Filter: ((a % 2) = 0)\n   ->  Seq Scan on t3 t1_3\n         Filter: ((a % 2) = 0)\n\nSELECT * FROM t1 WHERE f_leak(b);\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => def\nNOTICE:  f_leak => yyy\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n 2 | bcd\n 4 | def\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on t2 t1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on t3 t1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\n-- reference to system column\nSELECT ctid, * FROM t1;\n ctid  | a |  b  \n-------+---+-----\n (0,2) | 2 | bbb\n (0,4) | 4 | dad\n (0,2) | 2 | bcd\n (0,4) | 4 | def\n (0,2) | 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT *, t1 FROM t1;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: ((a % 2) = 0)\n   ->  Seq Scan on t2 t1_2\n         Filter: ((a % 2) = 0)\n   ->  Seq Scan on t3 t1_3\n         Filter: ((a % 2) = 0)\n\n-- reference to whole-row reference\nSELECT *, t1 FROM t1;\n a |  b  |   t1    \n---+-----+---------\n 2 | bbb | (2,bbb)\n 4 | dad | (4,dad)\n 2 | bcd | (2,bcd)\n 4 | def | (4,def)\n 2 | yyy | (2,yyy)\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT *, t1 FROM t1;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: ((a % 2) = 0)\n   ->  Seq Scan on t2 t1_2\n         Filter: ((a % 2) = 0)\n   ->  Seq Scan on t3 t1_3\n         Filter: ((a % 2) = 0)\n\n-- for share/update lock\nSELECT * FROM t1 FOR SHARE;\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n 2 | bcd\n 4 | def\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 FOR SHARE;\n--- QUERY PLAN ---\n LockRows\n   ->  Append\n         ->  Seq Scan on t1 t1_1\n               Filter: ((a % 2) = 0)\n         ->  Seq Scan on t2 t1_2\n               Filter: ((a % 2) = 0)\n         ->  Seq Scan on t3 t1_3\n               Filter: ((a % 2) = 0)\n\nSELECT * FROM t1 WHERE f_leak(b) FOR SHARE;\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => def\nNOTICE:  f_leak => yyy\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n 2 | bcd\n 4 | def\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;\n--- QUERY PLAN ---\n LockRows\n   ->  Append\n         ->  Seq Scan on t1 t1_1\n               Filter: (((a % 2) = 0) AND f_leak(b))\n         ->  Seq Scan on t2 t1_2\n               Filter: (((a % 2) = 0) AND f_leak(b))\n         ->  Seq Scan on t3 t1_3\n               Filter: (((a % 2) = 0) AND f_leak(b))\n\n-- union all query\nSELECT a, b, ctid FROM t2 UNION ALL SELECT a, b, ctid FROM t3;\n a |  b  | ctid  \n---+-----+-------\n 1 | abc | (0,1)\n 3 | cde | (0,3)\n 1 | xxx | (0,1)\n 2 | yyy | (0,2)\n 3 | zzz | (0,3)\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT a, b, ctid FROM t2 UNION ALL SELECT a, b, ctid FROM t3;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t2\n         Filter: ((a % 2) = 1)\n   ->  Seq Scan on t3\n\n-- superuser is allowed to bypass RLS checks\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nSELECT * FROM t1 WHERE f_leak(b);\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => ccc\nNOTICE:  f_leak => dad\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => def\nNOTICE:  f_leak => xxx\nNOTICE:  f_leak => yyy\nNOTICE:  f_leak => zzz\n a |  b  \n---+-----\n 1 | aba\n 2 | bbb\n 3 | ccc\n 4 | dad\n 1 | abc\n 2 | bcd\n 3 | cde\n 4 | def\n 1 | xxx\n 2 | yyy\n 3 | zzz\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: f_leak(b)\n   ->  Seq Scan on t2 t1_2\n         Filter: f_leak(b)\n   ->  Seq Scan on t3 t1_3\n         Filter: f_leak(b)\n\n-- non-superuser with bypass privilege can bypass RLS policy when disabled\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO OFF;\nSELECT * FROM t1 WHERE f_leak(b);\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => ccc\nNOTICE:  f_leak => dad\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => def\nNOTICE:  f_leak => xxx\nNOTICE:  f_leak => yyy\nNOTICE:  f_leak => zzz\n a |  b  \n---+-----\n 1 | aba\n 2 | bbb\n 3 | ccc\n 4 | dad\n 1 | abc\n 2 | bcd\n 3 | cde\n 4 | def\n 1 | xxx\n 2 | yyy\n 3 | zzz\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: f_leak(b)\n   ->  Seq Scan on t2 t1_2\n         Filter: f_leak(b)\n   ->  Seq Scan on t3 t1_3\n         Filter: f_leak(b)\n\n--\n-- Hyper Tables\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE hyper_document (\n    did         int,\n    cid         int,\n    dlevel      int not null,\n    dauthor     name,\n    dtitle      text\n);\nGRANT ALL ON hyper_document TO public;\nSELECT public.create_hypertable('hyper_document', 'did', chunk_time_interval=>2);\n            create_hypertable            \n-----------------------------------------\n (2,regress_rls_schema,hyper_document,t)\n\nINSERT INTO hyper_document VALUES\n    ( 1, 11, 1, 'regress_rls_bob', 'my first novel'),\n    ( 2, 11, 2, 'regress_rls_bob', 'my second novel'),\n    ( 3, 99, 2, 'regress_rls_bob', 'my science textbook'),\n    ( 4, 55, 1, 'regress_rls_bob', 'my first satire'),\n    ( 5, 99, 2, 'regress_rls_bob', 'my history book'),\n    ( 6, 11, 1, 'regress_rls_carol', 'great science fiction'),\n    ( 7, 99, 2, 'regress_rls_carol', 'great technology book'),\n    ( 8, 55, 2, 'regress_rls_carol', 'great satire'),\n    ( 9, 11, 1, 'regress_rls_dave', 'awesome science fiction'),\n    (10, 99, 2, 'regress_rls_dave', 'awesome technology book');\nALTER TABLE hyper_document ENABLE ROW LEVEL SECURITY;\nGRANT ALL ON _timescaledb_internal._hyper_2_9_chunk TO public;\n-- Create policy on parent\n-- user's security level must be higher than or equal to document's\nCREATE POLICY pp1 ON hyper_document AS PERMISSIVE\n    USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));\n-- Dave is only allowed to see cid < 55\nCREATE POLICY pp1r ON hyper_document AS RESTRICTIVE TO regress_rls_dave\n    USING (cid < 55);\n\\d+ hyper_document\n                         Table \"regress_rls_schema.hyper_document\"\n Column  |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description \n---------+---------+-----------+----------+---------+----------+--------------+-------------\n did     | integer |           | not null |         | plain    |              | \n cid     | integer |           |          |         | plain    |              | \n dlevel  | integer |           | not null |         | plain    |              | \n dauthor | name    |           |          |         | plain    |              | \n dtitle  | text    |           |          |         | extended |              | \nIndexes:\n    \"hyper_document_did_idx\" btree (did DESC)\nPolicies:\n    POLICY \"pp1\"\n      USING ((dlevel <= ( SELECT uaccount.seclv\n   FROM uaccount\n  WHERE (uaccount.pguser = CURRENT_USER))))\n    POLICY \"pp1r\" AS RESTRICTIVE\n      TO regress_rls_dave\n      USING ((cid < 55))\nChild tables: _timescaledb_internal._hyper_2_10_chunk,\n              _timescaledb_internal._hyper_2_11_chunk,\n              _timescaledb_internal._hyper_2_12_chunk,\n              _timescaledb_internal._hyper_2_13_chunk,\n              _timescaledb_internal._hyper_2_14_chunk,\n              _timescaledb_internal._hyper_2_9_chunk\n\nSELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename like '%hyper_document%' ORDER BY policyname;\n     schemaname     |   tablename    | policyname | permissive  |       roles        | cmd |                    qual                    | with_check \n--------------------+----------------+------------+-------------+--------------------+-----+--------------------------------------------+------------\n regress_rls_schema | hyper_document | pp1        | PERMISSIVE  | {public}           | ALL | (dlevel <= ( SELECT uaccount.seclv        +| \n                    |                |            |             |                    |     |    FROM uaccount                          +| \n                    |                |            |             |                    |     |   WHERE (uaccount.pguser = CURRENT_USER))) | \n regress_rls_schema | hyper_document | pp1r       | RESTRICTIVE | {regress_rls_dave} | ALL | (cid < 55)                                 | \n\n-- viewpoint from regress_rls_bob\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO ON;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my first satire\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => awesome science fiction\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   4 |  55 |      1 | regress_rls_bob   | my first satire\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on hyper_document\n   Chunks excluded during startup: 0\n   InitPlan 1 (returns $0)\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on hyper_document hyper_document_1\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_9_chunk hyper_document_2\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_10_chunk hyper_document_3\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_11_chunk hyper_document_4\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_12_chunk hyper_document_5\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_13_chunk hyper_document_6\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_14_chunk hyper_document_7\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n\n-- viewpoint from regress_rls_carol\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science textbook\nNOTICE:  f_leak => my first satire\nNOTICE:  f_leak => my history book\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great satire\nNOTICE:  f_leak => awesome science fiction\nNOTICE:  f_leak => awesome technology book\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  99 |      2 | regress_rls_bob   | my science textbook\n   4 |  55 |      1 | regress_rls_bob   | my first satire\n   5 |  99 |      2 | regress_rls_bob   | my history book\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   7 |  99 |      2 | regress_rls_carol | great technology book\n   8 |  55 |      2 | regress_rls_carol | great satire\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  99 |      2 | regress_rls_dave  | awesome technology book\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on hyper_document\n   Chunks excluded during startup: 0\n   InitPlan 1 (returns $0)\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on hyper_document hyper_document_1\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_9_chunk hyper_document_2\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_10_chunk hyper_document_3\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_11_chunk hyper_document_4\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_12_chunk hyper_document_5\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_13_chunk hyper_document_6\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_14_chunk hyper_document_7\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n\n-- viewpoint from regress_rls_dave\nSET SESSION AUTHORIZATION regress_rls_dave;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => awesome science fiction\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on hyper_document\n   Chunks excluded during startup: 0\n   InitPlan 1 (returns $0)\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on hyper_document hyper_document_1\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_9_chunk hyper_document_2\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_10_chunk hyper_document_3\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_11_chunk hyper_document_4\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_12_chunk hyper_document_5\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_13_chunk hyper_document_6\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_14_chunk hyper_document_7\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n\n-- pp1 ERROR\nINSERT INTO hyper_document VALUES (1, 11, 5, 'regress_rls_dave', 'testing pp1'); -- fail\nERROR:  new row violates row-level security policy for table \"hyper_document\"\n-- pp1r ERROR\nINSERT INTO hyper_document VALUES (1, 99, 1, 'regress_rls_dave', 'testing pp1r'); -- fail\nERROR:  new row violates row-level security policy \"pp1r\" for table \"hyper_document\"\n-- Show that RLS policy does not apply for direct inserts to children\n-- This should fail with RLS POLICY pp1r violation.\nINSERT INTO hyper_document VALUES (1, 55, 1, 'regress_rls_dave', 'testing RLS with hypertables'); -- fail\nERROR:  new row violates row-level security policy \"pp1r\" for table \"hyper_document\"\n-- But this should succeed.\nINSERT INTO _timescaledb_internal._hyper_2_9_chunk VALUES (1, 55, 1, 'regress_rls_dave', 'testing RLS with hypertables'); -- success\n-- We still cannot see the row using the parent\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did, cid;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => awesome science fiction\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n\n-- But we can if we look directly\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk WHERE f_leak(dtitle) ORDER BY did, cid;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => testing RLS with hypertables\n did | cid | dlevel |     dauthor      |            dtitle            \n-----+-----+--------+------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob  | my first novel\n   1 |  55 |      1 | regress_rls_dave | testing RLS with hypertables\n\n-- Turn on RLS and create policy on child to show RLS is checked before constraints\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER TABLE _timescaledb_internal._hyper_2_9_chunk ENABLE ROW LEVEL SECURITY;\nCREATE POLICY pp3 ON _timescaledb_internal._hyper_2_9_chunk AS RESTRICTIVE\n    USING (cid < 55);\n-- This should fail with RLS violation now.\nSET SESSION AUTHORIZATION regress_rls_dave;\nINSERT INTO _timescaledb_internal._hyper_2_9_chunk VALUES (1, 55, 1, 'regress_rls_dave', 'testing RLS with hypertables - round 2'); -- fail\nERROR:  new row violates row-level security policy for table \"_hyper_2_9_chunk\"\n-- And now we cannot see directly into the partition either, due to RLS\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk WHERE f_leak(dtitle) ORDER BY did, cid;\n did | cid | dlevel | dauthor | dtitle \n-----+-----+--------+---------+--------\n\n-- The parent looks same as before\n-- viewpoint from regress_rls_dave\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did, cid;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => awesome science fiction\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on hyper_document\n   Chunks excluded during startup: 0\n   InitPlan 1 (returns $0)\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on hyper_document hyper_document_1\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_9_chunk hyper_document_2\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_10_chunk hyper_document_3\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_11_chunk hyper_document_4\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_12_chunk hyper_document_5\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_13_chunk hyper_document_6\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_14_chunk hyper_document_7\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n\n-- viewpoint from regress_rls_carol\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did, cid;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => testing RLS with hypertables\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science textbook\nNOTICE:  f_leak => my first satire\nNOTICE:  f_leak => my history book\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great satire\nNOTICE:  f_leak => awesome science fiction\nNOTICE:  f_leak => awesome technology book\n did | cid | dlevel |      dauthor      |            dtitle            \n-----+-----+--------+-------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   1 |  55 |      1 | regress_rls_dave  | testing RLS with hypertables\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  99 |      2 | regress_rls_bob   | my science textbook\n   4 |  55 |      1 | regress_rls_bob   | my first satire\n   5 |  99 |      2 | regress_rls_bob   | my history book\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   7 |  99 |      2 | regress_rls_carol | great technology book\n   8 |  55 |      2 | regress_rls_carol | great satire\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  99 |      2 | regress_rls_dave  | awesome technology book\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on hyper_document\n   Chunks excluded during startup: 0\n   InitPlan 1 (returns $0)\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on hyper_document hyper_document_1\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_9_chunk hyper_document_2\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_10_chunk hyper_document_3\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_11_chunk hyper_document_4\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_12_chunk hyper_document_5\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_13_chunk hyper_document_6\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_14_chunk hyper_document_7\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n\n-- only owner can change policies\nALTER POLICY pp1 ON hyper_document USING (true);    --fail\nERROR:  must be owner of table hyper_document\nDROP POLICY pp1 ON hyper_document;                  --fail\nERROR:  must be owner of relation hyper_document\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER POLICY pp1 ON hyper_document USING (dauthor = current_user);\n-- viewpoint from regress_rls_bob again\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did, cid;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science textbook\nNOTICE:  f_leak => my first satire\nNOTICE:  f_leak => my history book\n did | cid | dlevel |     dauthor     |       dtitle        \n-----+-----+--------+-----------------+---------------------\n   1 |  11 |      1 | regress_rls_bob | my first novel\n   2 |  11 |      2 | regress_rls_bob | my second novel\n   3 |  99 |      2 | regress_rls_bob | my science textbook\n   4 |  55 |      1 | regress_rls_bob | my first satire\n   5 |  99 |      2 | regress_rls_bob | my history book\n\n-- viewpoint from rls_regres_carol again\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did, cid;\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great satire\n did | cid | dlevel |      dauthor      |        dtitle         \n-----+-----+--------+-------------------+-----------------------\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   7 |  99 |      2 | regress_rls_carol | great technology book\n   8 |  55 |      2 | regress_rls_carol | great satire\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on hyper_document\n   Chunks excluded during startup: 0\n   ->  Seq Scan on hyper_document hyper_document_1\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_9_chunk hyper_document_2\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_10_chunk hyper_document_3\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_11_chunk hyper_document_4\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_12_chunk hyper_document_5\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_13_chunk hyper_document_6\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_14_chunk hyper_document_7\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n\n-- database superuser does bypass RLS policy when enabled\nRESET SESSION AUTHORIZATION;\nSET row_security TO ON;\nSELECT * FROM hyper_document ORDER BY did, cid;\n did | cid | dlevel |      dauthor      |            dtitle            \n-----+-----+--------+-------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   1 |  55 |      1 | regress_rls_dave  | testing RLS with hypertables\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  99 |      2 | regress_rls_bob   | my science textbook\n   4 |  55 |      1 | regress_rls_bob   | my first satire\n   5 |  99 |      2 | regress_rls_bob   | my history book\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   7 |  99 |      2 | regress_rls_carol | great technology book\n   8 |  55 |      2 | regress_rls_carol | great satire\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  99 |      2 | regress_rls_dave  | awesome technology book\n\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk ORDER BY did, cid;\n did | cid | dlevel |     dauthor      |            dtitle            \n-----+-----+--------+------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob  | my first novel\n   1 |  55 |      1 | regress_rls_dave | testing RLS with hypertables\n\n-- database non-superuser with bypass privilege can bypass RLS policy when disabled\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO OFF;\nSELECT * FROM hyper_document ORDER BY did, cid;\n did | cid | dlevel |      dauthor      |            dtitle            \n-----+-----+--------+-------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   1 |  55 |      1 | regress_rls_dave  | testing RLS with hypertables\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  99 |      2 | regress_rls_bob   | my science textbook\n   4 |  55 |      1 | regress_rls_bob   | my first satire\n   5 |  99 |      2 | regress_rls_bob   | my history book\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   7 |  99 |      2 | regress_rls_carol | great technology book\n   8 |  55 |      2 | regress_rls_carol | great satire\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  99 |      2 | regress_rls_dave  | awesome technology book\n\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk ORDER BY did, cid;\n did | cid | dlevel |     dauthor      |            dtitle            \n-----+-----+--------+------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob  | my first novel\n   1 |  55 |      1 | regress_rls_dave | testing RLS with hypertables\n\n-- RLS policy does not apply to table owner when RLS enabled.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO ON;\nSELECT * FROM hyper_document ORDER BY did, cid;\n did | cid | dlevel |      dauthor      |            dtitle            \n-----+-----+--------+-------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   1 |  55 |      1 | regress_rls_dave  | testing RLS with hypertables\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  99 |      2 | regress_rls_bob   | my science textbook\n   4 |  55 |      1 | regress_rls_bob   | my first satire\n   5 |  99 |      2 | regress_rls_bob   | my history book\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   7 |  99 |      2 | regress_rls_carol | great technology book\n   8 |  55 |      2 | regress_rls_carol | great satire\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  99 |      2 | regress_rls_dave  | awesome technology book\n\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk ORDER BY did, cid;\n did | cid | dlevel |     dauthor      |            dtitle            \n-----+-----+--------+------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob  | my first novel\n   1 |  55 |      1 | regress_rls_dave | testing RLS with hypertables\n\n-- When RLS disabled, other users get ERROR.\nSET SESSION AUTHORIZATION regress_rls_dave;\nSET row_security TO OFF;\nSELECT * FROM hyper_document ORDER BY did, cid;\nERROR:  query would be affected by row-level security policy for table \"hyper_document\"\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk ORDER BY did, cid;\nERROR:  query would be affected by row-level security policy for table \"_hyper_2_9_chunk\"\n-- Check behavior with a policy that uses a SubPlan not an InitPlan.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO ON;\nCREATE POLICY pp3 ON hyper_document AS RESTRICTIVE\n    USING ((SELECT dlevel <= seclv FROM uaccount WHERE pguser = current_user));\nSET SESSION AUTHORIZATION regress_rls_carol;\nINSERT INTO hyper_document VALUES (100, 11, 5, 'regress_rls_carol', 'testing pp3'); -- fail\nERROR:  new row violates row-level security policy \"pp3\" for table \"hyper_document\"\n----- Dependencies -----\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO ON;\nCREATE TABLE dependee (x integer, y integer);\nSELECT public.create_hypertable('dependee', 'x', chunk_time_interval=>2);\n         create_hypertable         \n-----------------------------------\n (3,regress_rls_schema,dependee,t)\n\nCREATE TABLE dependent (x integer, y integer);\nSELECT public.create_hypertable('dependent', 'x', chunk_time_interval=>2);\n         create_hypertable          \n------------------------------------\n (4,regress_rls_schema,dependent,t)\n\nCREATE POLICY d1 ON dependent FOR ALL\n    TO PUBLIC\n    USING (x = (SELECT d.x FROM dependee d WHERE d.y = y));\nDROP TABLE dependee; -- Should fail without CASCADE due to dependency on row security qual?\nERROR:  cannot drop table dependee because other objects depend on it\nDETAIL:  policy d1 on table dependent depends on table dependee\nHINT:  Use DROP ... CASCADE to drop the dependent objects too.\nDROP TABLE dependee CASCADE;\nNOTICE:  drop cascades to policy d1 on table dependent\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM dependent; -- After drop, should be unqualified\n--- QUERY PLAN ---\n Seq Scan on dependent\n\n-----   RECURSION    ----\n--\n-- Simple recursion\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE rec1 (x integer, y integer);\nSELECT public.create_hypertable('rec1', 'x', chunk_time_interval=>2);\n       create_hypertable       \n-------------------------------\n (5,regress_rls_schema,rec1,t)\n\nCREATE POLICY r1 ON rec1 USING (x = (SELECT r.x FROM rec1 r WHERE y = r.y));\nALTER TABLE rec1 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rec1; -- fail, direct recursion\nERROR:  infinite recursion detected in policy for relation \"rec1\"\n--\n-- Mutual recursion\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE rec2 (a integer, b integer);\nSELECT public.create_hypertable('rec2', 'x', chunk_time_interval=>2);\nERROR:  column \"x\" does not exist\nALTER POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2 WHERE b = y));\nCREATE POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1 WHERE y = b));\nALTER TABLE rec2 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rec1;    -- fail, mutual recursion\nERROR:  infinite recursion detected in policy for relation \"rec1\"\n--\n-- Mutual recursion via views\n--\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE VIEW rec1v AS SELECT * FROM rec1;\nCREATE VIEW rec2v AS SELECT * FROM rec2;\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2v WHERE b = y));\nALTER POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b));\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rec1;    -- fail, mutual recursion via views\nERROR:  infinite recursion detected in policy for relation \"rec1\"\n--\n-- Mutual recursion via .s.b views\n--\nSET SESSION AUTHORIZATION regress_rls_bob;\n\\set VERBOSITY terse \\\\ -- suppress cascade details\nDROP VIEW rec1v, rec2v CASCADE;\nNOTICE:  drop cascades to 2 other objects\n\\set VERBOSITY default\nCREATE VIEW rec1v WITH (security_barrier) AS SELECT * FROM rec1;\nCREATE VIEW rec2v WITH (security_barrier) AS SELECT * FROM rec2;\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2v WHERE b = y));\nCREATE POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b));\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rec1;    -- fail, mutual recursion via s.b. views\nERROR:  infinite recursion detected in policy for relation \"rec1\"\n--\n-- recursive RLS and VIEWs in policy\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE s1 (a int, b text);\nSELECT public.create_hypertable('s1', 'a', chunk_time_interval=>2);\n      create_hypertable      \n-----------------------------\n (6,regress_rls_schema,s1,t)\n\nINSERT INTO s1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x);\nCREATE TABLE s2 (x int, y text);\nSELECT public.create_hypertable('s2', 'x', chunk_time_interval=>2);\n      create_hypertable      \n-----------------------------\n (7,regress_rls_schema,s2,t)\n\nINSERT INTO s2 (SELECT x, md5(x::text) FROM generate_series(-6,6) x);\nGRANT SELECT ON s1, s2 TO regress_rls_bob;\nCREATE POLICY p1 ON s1 USING (a in (select x from s2 where y like '%2f%'));\nCREATE POLICY p2 ON s2 USING (x in (select a from s1 where b like '%22%'));\nCREATE POLICY p3 ON s1 FOR INSERT WITH CHECK (a = (SELECT a FROM s1));\nALTER TABLE s1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE s2 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE VIEW v2 AS SELECT * FROM s2 WHERE y like '%af%';\nSELECT * FROM s1 WHERE f_leak(b); -- fail (infinite recursion)\nERROR:  infinite recursion detected in policy for relation \"s1\"\nINSERT INTO s1 VALUES (1, 'foo'); -- fail (infinite recursion)\nERROR:  infinite recursion detected in policy for relation \"s1\"\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP POLICY p3 on s1;\nALTER POLICY p2 ON s2 USING (x % 2 = 0);\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM s1 WHERE f_leak(b);\t-- OK\nNOTICE:  f_leak => c81e728d9d4c2f636f067f89cc14862c\nNOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c\n a |                b                 \n---+----------------------------------\n 2 | c81e728d9d4c2f636f067f89cc14862c\n 4 | a87ff679a2f3e71d9181a67b7542122c\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM only s1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Seq Scan on s1\n   Filter: ((hashed SubPlan 1) AND f_leak(b))\n   SubPlan 1\n     ->  Append\n           ->  Seq Scan on s2 s2_1\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_27_chunk s2_2\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_28_chunk s2_3\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_29_chunk s2_4\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_30_chunk s2_5\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_31_chunk s2_6\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_32_chunk s2_7\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_33_chunk s2_8\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER POLICY p1 ON s1 USING (a in (select x from v2)); -- using VIEW in RLS policy\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM s1 WHERE f_leak(b);\t-- OK\nNOTICE:  f_leak => 0267aaf632e87a63288a08331f22c7c3\nNOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc\n a  |                b                 \n----+----------------------------------\n -4 | 0267aaf632e87a63288a08331f22c7c3\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM s1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on s1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on s1 s1_1\n         Filter: ((hashed SubPlan 1) AND f_leak(b))\n         SubPlan 1\n           ->  Append\n                 ->  Seq Scan on s2 s2_1\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_27_chunk s2_2\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_28_chunk s2_3\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_29_chunk s2_4\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_30_chunk s2_5\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_31_chunk s2_6\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_32_chunk s2_7\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_33_chunk s2_8\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n   ->  Seq Scan on _hyper_6_16_chunk s1_2\n         Filter: ((hashed SubPlan 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_17_chunk s1_3\n         Filter: ((hashed SubPlan 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_18_chunk s1_4\n         Filter: ((hashed SubPlan 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_19_chunk s1_5\n         Filter: ((hashed SubPlan 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_20_chunk s1_6\n         Filter: ((hashed SubPlan 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_21_chunk s1_7\n         Filter: ((hashed SubPlan 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_22_chunk s1_8\n         Filter: ((hashed SubPlan 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_23_chunk s1_9\n         Filter: ((hashed SubPlan 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_24_chunk s1_10\n         Filter: ((hashed SubPlan 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_25_chunk s1_11\n         Filter: ((hashed SubPlan 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_26_chunk s1_12\n         Filter: ((hashed SubPlan 1) AND f_leak(b))\n\nSELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%';\n xx | x  |                y                 \n----+----+----------------------------------\n -6 | -6 | 596a3d04481816330f07e4f97510c28f\n -4 | -4 | 0267aaf632e87a63288a08331f22c7c3\n  2 |  2 | c81e728d9d4c2f636f067f89cc14862c\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%';\n--- QUERY PLAN ---\n Result\n   ->  Append\n         ->  Seq Scan on s2 s2_1\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_27_chunk s2_2\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_28_chunk s2_3\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_29_chunk s2_4\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_30_chunk s2_5\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_31_chunk s2_6\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_32_chunk s2_7\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_33_chunk s2_8\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n   SubPlan 2\n     ->  Limit\n           ->  Result\n                 ->  Custom Scan (ChunkAppend) on s1\n                       ->  Seq Scan on s1 s1_1\n                             Filter: (hashed SubPlan 1)\n                             SubPlan 1\n                               ->  Append\n                                     ->  Seq Scan on s2 s2_10\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_27_chunk s2_11\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_28_chunk s2_12\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_29_chunk s2_13\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_30_chunk s2_14\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_31_chunk s2_15\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_32_chunk s2_16\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_33_chunk s2_17\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                       ->  Seq Scan on _hyper_6_16_chunk s1_2\n                             Filter: (hashed SubPlan 1)\n                       ->  Seq Scan on _hyper_6_17_chunk s1_3\n                             Filter: (hashed SubPlan 1)\n                       ->  Seq Scan on _hyper_6_18_chunk s1_4\n                             Filter: (hashed SubPlan 1)\n                       ->  Seq Scan on _hyper_6_19_chunk s1_5\n                             Filter: (hashed SubPlan 1)\n                       ->  Seq Scan on _hyper_6_20_chunk s1_6\n                             Filter: (hashed SubPlan 1)\n                       ->  Seq Scan on _hyper_6_21_chunk s1_7\n                             Filter: (hashed SubPlan 1)\n                       ->  Seq Scan on _hyper_6_22_chunk s1_8\n                             Filter: (hashed SubPlan 1)\n                       ->  Seq Scan on _hyper_6_23_chunk s1_9\n                             Filter: (hashed SubPlan 1)\n                       ->  Seq Scan on _hyper_6_24_chunk s1_10\n                             Filter: (hashed SubPlan 1)\n                       ->  Seq Scan on _hyper_6_25_chunk s1_11\n                             Filter: (hashed SubPlan 1)\n                       ->  Seq Scan on _hyper_6_26_chunk s1_12\n                             Filter: (hashed SubPlan 1)\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER POLICY p2 ON s2 USING (x in (select a from s1 where b like '%d2%'));\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM s1 WHERE f_leak(b);\t-- fail (infinite recursion via view)\nERROR:  infinite recursion detected in policy for relation \"s1\"\n-- prepared statement with regress_rls_alice privilege\nPREPARE p1(int) AS SELECT * FROM t1 WHERE a <= $1;\nEXECUTE p1(2);\n a |  b  \n---+-----\n 2 | bbb\n 2 | bcd\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE p1(2);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: ((a <= 2) AND ((a % 2) = 0))\n   ->  Seq Scan on t2 t1_2\n         Filter: ((a <= 2) AND ((a % 2) = 0))\n   ->  Seq Scan on t3 t1_3\n         Filter: ((a <= 2) AND ((a % 2) = 0))\n\n-- superuser is allowed to bypass RLS checks\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nSELECT * FROM t1 WHERE f_leak(b);\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => ccc\nNOTICE:  f_leak => dad\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => def\nNOTICE:  f_leak => xxx\nNOTICE:  f_leak => yyy\nNOTICE:  f_leak => zzz\n a |  b  \n---+-----\n 1 | aba\n 2 | bbb\n 3 | ccc\n 4 | dad\n 1 | abc\n 2 | bcd\n 3 | cde\n 4 | def\n 1 | xxx\n 2 | yyy\n 3 | zzz\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: f_leak(b)\n   ->  Seq Scan on t2 t1_2\n         Filter: f_leak(b)\n   ->  Seq Scan on t3 t1_3\n         Filter: f_leak(b)\n\n-- plan cache should be invalidated\nEXECUTE p1(2);\n a |  b  \n---+-----\n 1 | aba\n 2 | bbb\n 1 | abc\n 2 | bcd\n 1 | xxx\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE p1(2);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: (a <= 2)\n   ->  Seq Scan on t2 t1_2\n         Filter: (a <= 2)\n   ->  Seq Scan on t3 t1_3\n         Filter: (a <= 2)\n\nPREPARE p2(int) AS SELECT * FROM t1 WHERE a = $1;\nEXECUTE p2(2);\n a |  b  \n---+-----\n 2 | bbb\n 2 | bcd\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE p2(2);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: (a = 2)\n   ->  Seq Scan on t2 t1_2\n         Filter: (a = 2)\n   ->  Seq Scan on t3 t1_3\n         Filter: (a = 2)\n\n-- also, case when privilege switch from superuser\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO ON;\nEXECUTE p2(2);\n a |  b  \n---+-----\n 2 | bbb\n 2 | bcd\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE p2(2);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: ((a = 2) AND ((a % 2) = 0))\n   ->  Seq Scan on t2 t1_2\n         Filter: ((a = 2) AND ((a % 2) = 0))\n   ->  Seq Scan on t3 t1_3\n         Filter: ((a = 2) AND ((a % 2) = 0))\n\n--\n-- UPDATE / DELETE and Row-level security\n--\nSET SESSION AUTHORIZATION regress_rls_bob;\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t1 SET b = b || b WHERE f_leak(b);\n--- QUERY PLAN ---\n Update on t1\n   Update on t1 t1_1\n   Update on t2 t1_2\n   Update on t3 t1_3\n   ->  Result\n         ->  Append\n               ->  Seq Scan on t1 t1_1\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t2 t1_2\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t3 t1_3\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nUPDATE t1 SET b = b || b WHERE f_leak(b);\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => def\nNOTICE:  f_leak => yyy\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b);\n--- QUERY PLAN ---\n Update on t1\n   ->  Seq Scan on t1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nUPDATE only t1 SET b = b || '_updt' WHERE f_leak(b);\nNOTICE:  f_leak => bbbbbb\nNOTICE:  f_leak => daddad\n-- returning clause with system column\nUPDATE only t1 SET b = b WHERE f_leak(b) RETURNING ctid, *, t1;\nNOTICE:  f_leak => bbbbbb_updt\nNOTICE:  f_leak => daddad_updt\n  ctid  | a |      b      |       t1        \n--------+---+-------------+-----------------\n (0,9)  | 2 | bbbbbb_updt | (2,bbbbbb_updt)\n (0,10) | 4 | daddad_updt | (4,daddad_updt)\n\nUPDATE t1 SET b = b WHERE f_leak(b) RETURNING *;\nNOTICE:  f_leak => bbbbbb_updt\nNOTICE:  f_leak => daddad_updt\nNOTICE:  f_leak => bcdbcd\nNOTICE:  f_leak => defdef\nNOTICE:  f_leak => yyyyyy\n a |      b      \n---+-------------\n 2 | bbbbbb_updt\n 4 | daddad_updt\n 2 | bcdbcd\n 4 | defdef\n 2 | yyyyyy\n\nUPDATE t1 SET b = b WHERE f_leak(b) RETURNING ctid, *, t1;\nNOTICE:  f_leak => bbbbbb_updt\nNOTICE:  f_leak => daddad_updt\nNOTICE:  f_leak => bcdbcd\nNOTICE:  f_leak => defdef\nNOTICE:  f_leak => yyyyyy\n  ctid  | a |      b      |       t1        \n--------+---+-------------+-----------------\n (0,13) | 2 | bbbbbb_updt | (2,bbbbbb_updt)\n (0,14) | 4 | daddad_updt | (4,daddad_updt)\n (0,9)  | 2 | bcdbcd      | (2,bcdbcd)\n (0,10) | 4 | defdef      | (4,defdef)\n (0,6)  | 2 | yyyyyy      | (2,yyyyyy)\n\n-- updates with from clause\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t2 SET b=t2.b FROM t3\nWHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b);\n--- QUERY PLAN ---\n Update on t2\n   ->  Nested Loop\n         ->  Seq Scan on t2\n               Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))\n         ->  Seq Scan on t3\n               Filter: ((a = 2) AND f_leak(b))\n\nUPDATE t2 SET b=t2.b FROM t3\nWHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b);\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => yyyyyy\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t1 SET b=t1.b FROM t2\nWHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);\n--- QUERY PLAN ---\n Update on t1\n   Update on t1 t1_1\n   Update on t2 t1_2\n   Update on t3 t1_3\n   ->  Nested Loop\n         ->  Seq Scan on t2\n               Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))\n         ->  Append\n               ->  Seq Scan on t1 t1_1\n                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t2 t1_2\n                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t3 t1_3\n                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))\n\nUPDATE t1 SET b=t1.b FROM t2\nWHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);\nNOTICE:  f_leak => cde\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t2 SET b=t2.b FROM t1\nWHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);\n--- QUERY PLAN ---\n Update on t2\n   ->  Nested Loop\n         ->  Seq Scan on t2\n               Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))\n         ->  Append\n               ->  Seq Scan on t1 t1_1\n                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t2 t1_2\n                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t3 t1_3\n                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))\n\nUPDATE t2 SET b=t2.b FROM t1\nWHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);\nNOTICE:  f_leak => cde\n-- updates with from clause self join\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2\nWHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b\nAND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2;\n--- QUERY PLAN ---\n Update on t2 t2_1\n   ->  Nested Loop\n         Join Filter: (t2_1.b = t2_2.b)\n         ->  Seq Scan on t2 t2_1\n               Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))\n         ->  Seq Scan on t2 t2_2\n               Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))\n\nUPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2\nWHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b\nAND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2;\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => cde\n a |  b  |  c  | a |  b  |  c  |    t2_1     |    t2_2     \n---+-----+-----+---+-----+-----+-------------+-------------\n 3 | cde | 3.3 | 3 | cde | 3.3 | (3,cde,3.3) | (3,cde,3.3)\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2\nWHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b\nAND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;\n--- QUERY PLAN ---\n Update on t1 t1_1\n   Update on t1 t1_1_1\n   Update on t2 t1_1_2\n   Update on t3 t1_1_3\n   ->  Nested Loop\n         Join Filter: (t1_1.b = t1_2.b)\n         ->  Append\n               ->  Seq Scan on t1 t1_1_1\n                     Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t2 t1_1_2\n                     Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t3 t1_1_3\n                     Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))\n         ->  Materialize\n               ->  Append\n                     ->  Seq Scan on t1 t1_2_1\n                           Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))\n                     ->  Seq Scan on t2 t1_2_2\n                           Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))\n                     ->  Seq Scan on t3 t1_2_3\n                           Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))\n\nUPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2\nWHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b\nAND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;\nNOTICE:  f_leak => daddad_updt\nNOTICE:  f_leak => daddad_updt\nNOTICE:  f_leak => defdef\nNOTICE:  f_leak => defdef\n a |      b      | a |      b      |      t1_1       |      t1_2       \n---+-------------+---+-------------+-----------------+-----------------\n 4 | daddad_updt | 4 | daddad_updt | (4,daddad_updt) | (4,daddad_updt)\n 4 | defdef      | 4 | defdef      | (4,defdef)      | (4,defdef)\n\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nSELECT * FROM t1 ORDER BY a,b;\n a |      b      \n---+-------------\n 1 | aba\n 1 | abc\n 1 | xxx\n 2 | bbbbbb_updt\n 2 | bcdbcd\n 2 | yyyyyy\n 3 | ccc\n 3 | cde\n 3 | zzz\n 4 | daddad_updt\n 4 | defdef\n\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO ON;\nEXPLAIN (BUFFERS OFF, COSTS OFF) DELETE FROM only t1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Delete on t1\n   ->  Seq Scan on t1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) DELETE FROM t1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Delete on t1\n   Delete on t1 t1_1\n   Delete on t2 t1_2\n   Delete on t3 t1_3\n   ->  Append\n         ->  Seq Scan on t1 t1_1\n               Filter: (((a % 2) = 0) AND f_leak(b))\n         ->  Seq Scan on t2 t1_2\n               Filter: (((a % 2) = 0) AND f_leak(b))\n         ->  Seq Scan on t3 t1_3\n               Filter: (((a % 2) = 0) AND f_leak(b))\n\nDELETE FROM only t1 WHERE f_leak(b) RETURNING ctid, *, t1;\nNOTICE:  f_leak => bbbbbb_updt\nNOTICE:  f_leak => daddad_updt\n  ctid  | a |      b      |       t1        \n--------+---+-------------+-----------------\n (0,13) | 2 | bbbbbb_updt | (2,bbbbbb_updt)\n (0,15) | 4 | daddad_updt | (4,daddad_updt)\n\nDELETE FROM t1 WHERE f_leak(b) RETURNING ctid, *, t1;\nNOTICE:  f_leak => bcdbcd\nNOTICE:  f_leak => defdef\nNOTICE:  f_leak => yyyyyy\n  ctid  | a |   b    |     t1     \n--------+---+--------+------------\n (0,9)  | 2 | bcdbcd | (2,bcdbcd)\n (0,13) | 4 | defdef | (4,defdef)\n (0,6)  | 2 | yyyyyy | (2,yyyyyy)\n\n--\n-- S.b. view on top of Row-level security\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE b1 (a int, b text);\nSELECT public.create_hypertable('b1', 'a', chunk_time_interval=>2);\n      create_hypertable      \n-----------------------------\n (8,regress_rls_schema,b1,t)\n\nINSERT INTO b1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x);\nCREATE POLICY p1 ON b1 USING (a % 2 = 0);\nALTER TABLE b1 ENABLE ROW LEVEL SECURITY;\nGRANT ALL ON b1 TO regress_rls_bob;\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE VIEW bv1 WITH (security_barrier) AS SELECT * FROM b1 WHERE a > 0 WITH CHECK OPTION;\nGRANT ALL ON bv1 TO regress_rls_carol;\nSET SESSION AUTHORIZATION regress_rls_carol;\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM bv1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Subquery Scan on bv1\n   Filter: f_leak(bv1.b)\n   ->  Append\n         ->  Seq Scan on b1 b1_1\n               Filter: ((a > 0) AND ((a % 2) = 0))\n         ->  Index Scan using _hyper_8_39_chunk_b1_a_idx on _hyper_8_39_chunk b1_2\n               Index Cond: (a > 0)\n               Filter: ((a % 2) = 0)\n         ->  Index Scan using _hyper_8_40_chunk_b1_a_idx on _hyper_8_40_chunk b1_3\n               Index Cond: (a > 0)\n               Filter: ((a % 2) = 0)\n         ->  Index Scan using _hyper_8_41_chunk_b1_a_idx on _hyper_8_41_chunk b1_4\n               Index Cond: (a > 0)\n               Filter: ((a % 2) = 0)\n         ->  Index Scan using _hyper_8_42_chunk_b1_a_idx on _hyper_8_42_chunk b1_5\n               Index Cond: (a > 0)\n               Filter: ((a % 2) = 0)\n         ->  Index Scan using _hyper_8_43_chunk_b1_a_idx on _hyper_8_43_chunk b1_6\n               Index Cond: (a > 0)\n               Filter: ((a % 2) = 0)\n         ->  Index Scan using _hyper_8_44_chunk_b1_a_idx on _hyper_8_44_chunk b1_7\n               Index Cond: (a > 0)\n               Filter: ((a % 2) = 0)\n\nSELECT * FROM bv1 WHERE f_leak(b);\nNOTICE:  f_leak => c81e728d9d4c2f636f067f89cc14862c\nNOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c\nNOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc\nNOTICE:  f_leak => c9f0f895fb98ab9159f51fd0297e236d\nNOTICE:  f_leak => d3d9446802a44259755d38e6d163e820\n a  |                b                 \n----+----------------------------------\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 10 | d3d9446802a44259755d38e6d163e820\n\nINSERT INTO bv1 VALUES (-1, 'xxx'); -- should fail view WCO\nERROR:  new row violates row-level security policy for table \"b1\"\nINSERT INTO bv1 VALUES (11, 'xxx'); -- should fail RLS check\nERROR:  new row violates row-level security policy for table \"b1\"\nINSERT INTO bv1 VALUES (12, 'xxx'); -- ok\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Update on b1\n         Update on _hyper_8_41_chunk b1_1\n         ->  Result\n               ->  Custom Scan (ChunkAppend) on b1\n                     Chunks excluded during startup: 0\n                     ->  Index Scan using _hyper_8_41_chunk_b1_a_idx on _hyper_8_41_chunk b1_1\n                           Index Cond: ((a > 0) AND (a = 4))\n                           Filter: (((a % 2) = 0) AND f_leak(b))\n\nUPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b);\nNOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c\nEXPLAIN (BUFFERS OFF, COSTS OFF) DELETE FROM bv1 WHERE a = 6 AND f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Delete on b1\n         Delete on _hyper_8_42_chunk b1_1\n         ->  Custom Scan (ChunkAppend) on b1\n               Chunks excluded during startup: 0\n               ->  Index Scan using _hyper_8_42_chunk_b1_a_idx on _hyper_8_42_chunk b1_1\n                     Index Cond: ((a > 0) AND (a = 6))\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nDELETE FROM bv1 WHERE a = 6 AND f_leak(b);\nNOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc\nSET SESSION AUTHORIZATION regress_rls_alice;\nSELECT * FROM b1;\n  a  |                b                 \n-----+----------------------------------\n -10 | 1b0fd9efa5279c4203b7c70233f86dbf\n  -9 | 252e691406782824eec43d7eadc3d256\n  -8 | a8d2ec85eaf98407310b72eb73dda247\n  -7 | 74687a12d3915d3c4d83f1af7b3683d5\n  -6 | 596a3d04481816330f07e4f97510c28f\n  -5 | 47c1b025fa18ea96c33fbb6718688c0f\n  -4 | 0267aaf632e87a63288a08331f22c7c3\n  -3 | b3149ecea4628efd23d2f86e5a723472\n  -2 | 5d7b9adcbe1c629ec722529dd12e5129\n  -1 | 6bb61e3b7bce0931da574d19d1d82c88\n   0 | cfcd208495d565ef66e7dff9f98764da\n   1 | c4ca4238a0b923820dcc509a6f75849b\n   2 | c81e728d9d4c2f636f067f89cc14862c\n   3 | eccbc87e4b5ce2fe28308fd9f2a7baf3\n   5 | e4da3b7fbbce2345d7772b0674a318d5\n   4 | yyy\n   7 | 8f14e45fceea167a5a36dedd4bea2543\n   8 | c9f0f895fb98ab9159f51fd0297e236d\n   9 | 45c48cce2e2d7fbdea1afc51c7c6ad26\n  10 | d3d9446802a44259755d38e6d163e820\n  12 | xxx\n\n--\n-- INSERT ... ON CONFLICT DO UPDATE and Row-level security\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP POLICY p1 ON document;\nDROP POLICY p1r ON document;\nCREATE POLICY p1 ON document FOR SELECT USING (true);\nCREATE POLICY p2 ON document FOR INSERT WITH CHECK (dauthor = current_user);\nCREATE POLICY p3 ON document FOR UPDATE\n  USING (cid = (SELECT cid from category WHERE cname = 'novel'))\n  WITH CHECK (dauthor = current_user);\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Exists...\nSELECT * FROM document WHERE did = 2;\n did | cid | dlevel |     dauthor     |     dtitle      \n-----+-----+--------+-----------------+-----------------\n   2 |  11 |      2 | regress_rls_bob | my second novel\n\n-- ...so violates actual WITH CHECK OPTION within UPDATE (not INSERT, since\n-- alternative UPDATE path happens to be taken):\nINSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_carol', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, dauthor = EXCLUDED.dauthor;\nERROR:  new row violates row-level security policy for table \"document\"\n-- Violates USING qual for UPDATE policy p3.\n--\n-- UPDATE path is taken, but UPDATE fails purely because *existing* row to be\n-- updated is not a \"novel\"/cid 11 (row is not leaked, even though we have\n-- SELECT privileges sufficient to see the row in this instance):\nINSERT INTO document VALUES (33, 22, 1, 'regress_rls_bob', 'okay science fiction'); -- preparation for next statement\nINSERT INTO document VALUES (33, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'Some novel, replaces sci-fi') -- takes UPDATE path\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle;\nERROR:  new row violates row-level security policy (USING expression) for table \"document\"\n-- Fine (we UPDATE, since INSERT WCOs and UPDATE security barrier quals + WCOs\n-- not violated):\nINSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;\n did | cid | dlevel |     dauthor     |     dtitle     \n-----+-----+--------+-----------------+----------------\n   2 |  11 |      2 | regress_rls_bob | my first novel\n\n-- Fine (we INSERT, so \"cid = 33\" (\"technology\") isn't evaluated):\nINSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *;\n did | cid | dlevel |     dauthor     |        dtitle         \n-----+-----+--------+-----------------+-----------------------\n  78 |  11 |      1 | regress_rls_bob | some technology novel\n\n-- Fine (same query, but we UPDATE, so \"cid = 33\", (\"technology\") is not the\n-- case in respect of *existing* tuple):\nINSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *;\n did | cid | dlevel |     dauthor     |        dtitle         \n-----+-----+--------+-----------------+-----------------------\n  78 |  33 |      1 | regress_rls_bob | some technology novel\n\n-- Same query a third time, but now fails due to existing tuple finally not\n-- passing quals:\nINSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *;\nERROR:  new row violates row-level security policy (USING expression) for table \"document\"\n-- Don't fail just because INSERT doesn't satisfy WITH CHECK option that\n-- originated as a barrier/USING() qual from the UPDATE.  Note that the UPDATE\n-- path *isn't* taken, and so UPDATE-related policy does not apply:\nINSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;\n did | cid | dlevel |     dauthor     |              dtitle              \n-----+-----+--------+-----------------+----------------------------------\n  79 |  33 |      1 | regress_rls_bob | technology book, can only insert\n\n-- But this time, the same statement fails, because the UPDATE path is taken,\n-- and updating the row just inserted falls afoul of security barrier qual\n-- (enforced as WCO) -- what we might have updated target tuple to is\n-- irrelevant, in fact.\nINSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;\nERROR:  new row violates row-level security policy (USING expression) for table \"document\"\n-- Test default USING qual enforced as WCO\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP POLICY p1 ON document;\nDROP POLICY p2 ON document;\nDROP POLICY p3 ON document;\nCREATE POLICY p3_with_default ON document FOR UPDATE\n  USING (cid = (SELECT cid from category WHERE cname = 'novel'));\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Just because WCO-style enforcement of USING quals occurs with\n-- existing/target tuple does not mean that the implementation can be allowed\n-- to fail to also enforce this qual against the final tuple appended to\n-- relation (since in the absence of an explicit WCO, this is also interpreted\n-- as an UPDATE/ALL WCO in general).\n--\n-- UPDATE path is taken here (fails due to existing tuple).  Note that this is\n-- not reported as a \"USING expression\", because it's an RLS UPDATE check that originated as\n-- a USING qual for the purposes of RLS in general, as opposed to an explicit\n-- USING qual that is ordinarily a security barrier.  We leave it up to the\n-- UPDATE to make this fail:\nINSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;\nERROR:  new row violates row-level security policy for table \"document\"\n-- UPDATE path is taken here.  Existing tuple passes, since it's cid\n-- corresponds to \"novel\", but default USING qual is enforced against\n-- post-UPDATE tuple too (as always when updating with a policy that lacks an\n-- explicit WCO), and so this fails:\nINSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET cid = EXCLUDED.cid, dtitle = EXCLUDED.dtitle RETURNING *;\nERROR:  new row violates row-level security policy for table \"document\"\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP POLICY p3_with_default ON document;\n--\n-- Test ALL policies with ON CONFLICT DO UPDATE (much the same as existing UPDATE\n-- tests)\n--\nCREATE POLICY p3_with_all ON document FOR ALL\n  USING (cid = (SELECT cid from category WHERE cname = 'novel'))\n  WITH CHECK (dauthor = current_user);\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Fails, since ALL WCO is enforced in insert path:\nINSERT INTO document VALUES (80, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_carol', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33;\nERROR:  new row violates row-level security policy for table \"document\"\n-- Fails, since ALL policy USING qual is enforced (existing, target tuple is in\n-- violation, since it has the \"manga\" cid):\nINSERT INTO document VALUES (4, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle;\nERROR:  new row violates row-level security policy (USING expression) for table \"document\"\n-- Fails, since ALL WCO are enforced:\nINSERT INTO document VALUES (1, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET dauthor = 'regress_rls_carol';\nERROR:  new row violates row-level security policy for table \"document\"\n--\n-- ROLE/GROUP\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE z1 (a int, b text);\nSELECT public.create_hypertable('z1', 'a', chunk_time_interval=>2);\n      create_hypertable      \n-----------------------------\n (9,regress_rls_schema,z1,t)\n\nCREATE TABLE z2 (a int, b text);\nSELECT public.create_hypertable('z2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (10,regress_rls_schema,z2,t)\n\nGRANT SELECT ON z1,z2 TO regress_rls_group1, regress_rls_group2,\n    regress_rls_bob, regress_rls_carol;\nINSERT INTO z1 VALUES\n    (1, 'aba'),\n    (2, 'bbb'),\n    (3, 'ccc'),\n    (4, 'dad');\nCREATE POLICY p1 ON z1 TO regress_rls_group1 USING (a % 2 = 0);\nCREATE POLICY p2 ON z1 TO regress_rls_group2 USING (a % 2 = 1);\nALTER TABLE z1 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM z1 WHERE f_leak(b);\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nPREPARE plancache_test AS SELECT * FROM z1 WHERE f_leak(b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nPREPARE plancache_test2 AS WITH q AS MATERIALIZED (SELECT * FROM z1 WHERE f_leak(b)) SELECT * FROM q,z2;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test2;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nPREPARE plancache_test4 AS WITH q AS (SELECT * FROM z1 WHERE f_leak(b)) SELECT * FROM q,z2;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test4;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nPREPARE plancache_test6 AS WITH q AS NOT MATERIALIZED (SELECT * FROM z1 WHERE f_leak(b)) SELECT * FROM q,z2;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test6;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nPREPARE plancache_test3 AS WITH q AS MATERIALIZED (SELECT * FROM z2) SELECT * FROM q,z1 WHERE f_leak(z1.b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test3;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Seq Scan on z2\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nPREPARE plancache_test5 AS WITH q AS (SELECT * FROM z2) SELECT * FROM q,z1 WHERE f_leak(z1.b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test5;\n--- QUERY PLAN ---\n Nested Loop\n   ->  Seq Scan on z2\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nPREPARE plancache_test7 AS WITH q AS NOT MATERIALIZED (SELECT * FROM z2) SELECT * FROM q,z1 WHERE f_leak(z1.b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test7;\n--- QUERY PLAN ---\n Nested Loop\n   ->  Seq Scan on z2\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nSET ROLE regress_rls_group1;\nSELECT * FROM z1 WHERE f_leak(b);\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test2;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test4;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test3;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Seq Scan on z2\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test5;\n--- QUERY PLAN ---\n Nested Loop\n   ->  Seq Scan on z2\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM z1 WHERE f_leak(b);\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => ccc\n a |  b  \n---+-----\n 1 | aba\n 3 | ccc\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 1) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 1) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test2;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test4;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test3;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Seq Scan on z2\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test5;\n--- QUERY PLAN ---\n Nested Loop\n   ->  Seq Scan on z2\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n\nSET ROLE regress_rls_group2;\nSELECT * FROM z1 WHERE f_leak(b);\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => ccc\n a |  b  \n---+-----\n 1 | aba\n 3 | ccc\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 1) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 1) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test2;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test4;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test3;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Seq Scan on z2\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test5;\n--- QUERY PLAN ---\n Nested Loop\n   ->  Seq Scan on z2\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n\n--\n-- Views should follow policy for view owner.\n--\n-- View and Table owner are the same.\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b);\nGRANT SELECT ON rls_view TO regress_rls_bob;\n-- Query as role that is not owner of view or table.  Should return all records.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rls_view;\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => ccc\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 1 | aba\n 2 | bbb\n 3 | ccc\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: f_leak(b)\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: f_leak(b)\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: f_leak(b)\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: f_leak(b)\n\n-- Query as view/table owner.  Should return all records.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSELECT * FROM rls_view;\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => ccc\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 1 | aba\n 2 | bbb\n 3 | ccc\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: f_leak(b)\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: f_leak(b)\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: f_leak(b)\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: f_leak(b)\n\nDROP VIEW rls_view;\n-- View and Table owners are different.\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b);\nGRANT SELECT ON rls_view TO regress_rls_alice;\n-- Query as role that is not owner of view but is owner of table.\n-- Should return records based on view owner policies.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSELECT * FROM rls_view;\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\n-- Query as role that is not owner of table but is owner of view.\n-- Should return records based on view owner policies.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rls_view;\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\n-- Query as role that is not the owner of the table or view without permissions.\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM rls_view; --fail - permission denied.\nERROR:  permission denied for view rls_view\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.\nERROR:  permission denied for view rls_view\n-- Query as role that is not the owner of the table or view with permissions.\nSET SESSION AUTHORIZATION regress_rls_bob;\nGRANT SELECT ON rls_view TO regress_rls_carol;\nSELECT * FROM rls_view;\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nSET SESSION AUTHORIZATION regress_rls_bob;\nDROP VIEW rls_view;\n--\n-- Command specific\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE x1 (a int, b text, c text);\nSELECT public.create_hypertable('x1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (11,regress_rls_schema,x1,t)\n\nGRANT ALL ON x1 TO PUBLIC;\nINSERT INTO x1 VALUES\n    (1, 'abc', 'regress_rls_bob'),\n    (2, 'bcd', 'regress_rls_bob'),\n    (3, 'cde', 'regress_rls_carol'),\n    (4, 'def', 'regress_rls_carol'),\n    (5, 'efg', 'regress_rls_bob'),\n    (6, 'fgh', 'regress_rls_bob'),\n    (7, 'fgh', 'regress_rls_carol'),\n    (8, 'fgh', 'regress_rls_carol');\nCREATE POLICY p0 ON x1 FOR ALL USING (c = current_user);\nCREATE POLICY p1 ON x1 FOR SELECT USING (a % 2 = 0);\nCREATE POLICY p2 ON x1 FOR INSERT WITH CHECK (a % 2 = 1);\nCREATE POLICY p3 ON x1 FOR UPDATE USING (a % 2 = 0);\nCREATE POLICY p4 ON x1 FOR DELETE USING (a < 8);\nALTER TABLE x1 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM x1 WHERE f_leak(b) ORDER BY a ASC;\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => def\nNOTICE:  f_leak => efg\nNOTICE:  f_leak => fgh\nNOTICE:  f_leak => fgh\n a |  b  |         c         \n---+-----+-------------------\n 1 | abc | regress_rls_bob\n 2 | bcd | regress_rls_bob\n 4 | def | regress_rls_carol\n 5 | efg | regress_rls_bob\n 6 | fgh | regress_rls_bob\n 8 | fgh | regress_rls_carol\n\nUPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *;\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => def\nNOTICE:  f_leak => efg\nNOTICE:  f_leak => fgh\nNOTICE:  f_leak => fgh\n a |    b     |         c         \n---+----------+-------------------\n 1 | abc_updt | regress_rls_bob\n 2 | bcd_updt | regress_rls_bob\n 4 | def_updt | regress_rls_carol\n 5 | efg_updt | regress_rls_bob\n 6 | fgh_updt | regress_rls_bob\n 8 | fgh_updt | regress_rls_carol\n\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM x1 WHERE f_leak(b) ORDER BY a ASC;\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => bcd_updt\nNOTICE:  f_leak => def_updt\nNOTICE:  f_leak => fgh\nNOTICE:  f_leak => fgh_updt\nNOTICE:  f_leak => fgh_updt\n a |    b     |         c         \n---+----------+-------------------\n 2 | bcd_updt | regress_rls_bob\n 3 | cde      | regress_rls_carol\n 4 | def_updt | regress_rls_carol\n 6 | fgh_updt | regress_rls_bob\n 7 | fgh      | regress_rls_carol\n 8 | fgh_updt | regress_rls_carol\n\nUPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *;\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => bcd_updt\nNOTICE:  f_leak => def_updt\nNOTICE:  f_leak => fgh\nNOTICE:  f_leak => fgh_updt\nNOTICE:  f_leak => fgh_updt\n a |       b       |         c         \n---+---------------+-------------------\n 3 | cde_updt      | regress_rls_carol\n 2 | bcd_updt_updt | regress_rls_bob\n 4 | def_updt_updt | regress_rls_carol\n 7 | fgh_updt      | regress_rls_carol\n 6 | fgh_updt_updt | regress_rls_bob\n 8 | fgh_updt_updt | regress_rls_carol\n\nDELETE FROM x1 WHERE f_leak(b) RETURNING *;\nNOTICE:  f_leak => cde_updt\nNOTICE:  f_leak => bcd_updt_updt\nNOTICE:  f_leak => def_updt_updt\nNOTICE:  f_leak => fgh_updt\nNOTICE:  f_leak => fgh_updt_updt\nNOTICE:  f_leak => fgh_updt_updt\n a |       b       |         c         \n---+---------------+-------------------\n 3 | cde_updt      | regress_rls_carol\n 2 | bcd_updt_updt | regress_rls_bob\n 4 | def_updt_updt | regress_rls_carol\n 7 | fgh_updt      | regress_rls_carol\n 6 | fgh_updt_updt | regress_rls_bob\n 8 | fgh_updt_updt | regress_rls_carol\n\n--\n-- Duplicate Policy Names\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE y1 (a int, b text);\nSELECT public.create_hypertable('y1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (12,regress_rls_schema,y1,t)\n\nINSERT INTO y1 VALUES(1,2);\nCREATE TABLE y2 (a int, b text);\nSELECT public.create_hypertable('y2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (13,regress_rls_schema,y2,t)\n\nGRANT ALL ON y1, y2 TO regress_rls_bob;\nCREATE POLICY p1 ON y1 FOR ALL USING (a % 2 = 0);\nCREATE POLICY p2 ON y1 FOR SELECT USING (a > 2);\nCREATE POLICY p1 ON y1 FOR SELECT USING (a % 2 = 1);  --fail\nERROR:  policy \"p1\" for table \"y1\" already exists\nCREATE POLICY p1 ON y2 FOR ALL USING (a % 2 = 0);  --OK\nALTER TABLE y1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE y2 ENABLE ROW LEVEL SECURITY;\n--\n-- Expression structure with SBV\n--\n-- Create view as table owner.  RLS should NOT be applied.\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE VIEW rls_sbv WITH (security_barrier) AS\n    SELECT * FROM y1 WHERE f_leak(b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on y1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on y1 y1_1\n         Filter: (f_leak(b) AND (a = 1))\n   ->  Index Scan using _hyper_12_57_chunk_y1_a_idx on _hyper_12_57_chunk y1_2\n         Index Cond: (a = 1)\n         Filter: f_leak(b)\n\nDROP VIEW rls_sbv;\n-- Create view as role that does not own table.  RLS should be applied.\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE VIEW rls_sbv WITH (security_barrier) AS\n    SELECT * FROM y1 WHERE f_leak(b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on y1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on y1 y1_1\n         Filter: ((a = 1) AND ((a > 2) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Index Scan using _hyper_12_57_chunk_y1_a_idx on _hyper_12_57_chunk y1_2\n         Index Cond: (a = 1)\n         Filter: (((a > 2) OR ((a % 2) = 0)) AND f_leak(b))\n\nDROP VIEW rls_sbv;\n--\n-- Expression structure\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nINSERT INTO y2 (SELECT x, md5(x::text) FROM generate_series(0,20) x);\nCREATE POLICY p2 ON y2 USING (a % 3 = 0);\nCREATE POLICY p3 ON y2 USING (a % 4 = 0);\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM y2 WHERE f_leak(b);\nNOTICE:  f_leak => cfcd208495d565ef66e7dff9f98764da\nNOTICE:  f_leak => c81e728d9d4c2f636f067f89cc14862c\nNOTICE:  f_leak => eccbc87e4b5ce2fe28308fd9f2a7baf3\nNOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c\nNOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc\nNOTICE:  f_leak => c9f0f895fb98ab9159f51fd0297e236d\nNOTICE:  f_leak => 45c48cce2e2d7fbdea1afc51c7c6ad26\nNOTICE:  f_leak => d3d9446802a44259755d38e6d163e820\nNOTICE:  f_leak => c20ad4d76fe97759aa27a0c99bff6710\nNOTICE:  f_leak => aab3238922bcc25a6f606eb525ffdc56\nNOTICE:  f_leak => 9bf31c7ff062936a96d3c8bd1f8f2ff3\nNOTICE:  f_leak => c74d97b01eae257e44aa9d5bade97baf\nNOTICE:  f_leak => 6f4922f45568161a8cdf4ad2299f6d23\nNOTICE:  f_leak => 98f13708210194c475687be6106a3b84\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  3 | eccbc87e4b5ce2fe28308fd9f2a7baf3\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n  9 | 45c48cce2e2d7fbdea1afc51c7c6ad26\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM y2 WHERE f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on y2\n   Chunks excluded during startup: 0\n   ->  Seq Scan on y2 y2_1\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_58_chunk y2_2\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_59_chunk y2_3\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_60_chunk y2_4\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_61_chunk y2_5\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_62_chunk y2_6\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_63_chunk y2_7\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_64_chunk y2_8\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_65_chunk y2_9\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_66_chunk y2_10\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_67_chunk y2_11\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_68_chunk y2_12\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n\n--\n-- Qual push-down of leaky functions, when not referring to table\n--\nSELECT * FROM y2 WHERE f_leak('abc');\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  3 | eccbc87e4b5ce2fe28308fd9f2a7baf3\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n  9 | 45c48cce2e2d7fbdea1afc51c7c6ad26\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM y2 WHERE f_leak('abc');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on y2\n   Chunks excluded during startup: 0\n   ->  Seq Scan on y2 y2_1\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_58_chunk y2_2\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_59_chunk y2_3\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_60_chunk y2_4\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_61_chunk y2_5\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_62_chunk y2_6\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_63_chunk y2_7\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_64_chunk y2_8\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_65_chunk y2_9\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_66_chunk y2_10\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_67_chunk y2_11\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_68_chunk y2_12\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n\nCREATE TABLE test_qual_pushdown (\n    abc text\n);\nINSERT INTO test_qual_pushdown VALUES ('abc'),('def');\nSELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc);\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => def\n a | b | abc \n---+---+-----\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc);\n--- QUERY PLAN ---\n Hash Join\n   Hash Cond: (y2.b = test_qual_pushdown.abc)\n   ->  Append\n         ->  Seq Scan on y2 y2_1\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_58_chunk y2_2\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_59_chunk y2_3\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_60_chunk y2_4\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_61_chunk y2_5\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_62_chunk y2_6\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_63_chunk y2_7\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_64_chunk y2_8\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_65_chunk y2_9\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_66_chunk y2_10\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_67_chunk y2_11\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_68_chunk y2_12\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n   ->  Hash\n         ->  Seq Scan on test_qual_pushdown\n               Filter: f_leak(abc)\n\nSELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b);\nNOTICE:  f_leak => cfcd208495d565ef66e7dff9f98764da\nNOTICE:  f_leak => c81e728d9d4c2f636f067f89cc14862c\nNOTICE:  f_leak => eccbc87e4b5ce2fe28308fd9f2a7baf3\nNOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c\nNOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc\nNOTICE:  f_leak => c9f0f895fb98ab9159f51fd0297e236d\nNOTICE:  f_leak => 45c48cce2e2d7fbdea1afc51c7c6ad26\nNOTICE:  f_leak => d3d9446802a44259755d38e6d163e820\nNOTICE:  f_leak => c20ad4d76fe97759aa27a0c99bff6710\nNOTICE:  f_leak => aab3238922bcc25a6f606eb525ffdc56\nNOTICE:  f_leak => 9bf31c7ff062936a96d3c8bd1f8f2ff3\nNOTICE:  f_leak => c74d97b01eae257e44aa9d5bade97baf\nNOTICE:  f_leak => 6f4922f45568161a8cdf4ad2299f6d23\nNOTICE:  f_leak => 98f13708210194c475687be6106a3b84\n a | b | abc \n---+---+-----\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b);\n--- QUERY PLAN ---\n Hash Join\n   Hash Cond: (test_qual_pushdown.abc = y2.b)\n   ->  Seq Scan on test_qual_pushdown\n   ->  Hash\n         ->  Custom Scan (ChunkAppend) on y2\n               Chunks excluded during startup: 0\n               ->  Seq Scan on y2 y2_1\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_58_chunk y2_2\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_59_chunk y2_3\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_60_chunk y2_4\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_61_chunk y2_5\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_62_chunk y2_6\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_63_chunk y2_7\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_64_chunk y2_8\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_65_chunk y2_9\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_66_chunk y2_10\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_67_chunk y2_11\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_68_chunk y2_12\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n\nDROP TABLE test_qual_pushdown;\n--\n-- Plancache invalidate on user change.\n--\nRESET SESSION AUTHORIZATION;\n\\set VERBOSITY terse \\\\ -- suppress cascade details\nDROP TABLE t1 CASCADE;\nNOTICE:  drop cascades to 2 other objects\n\\set VERBOSITY default\nCREATE TABLE t1 (a integer);\nSELECT public.create_hypertable('t1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (14,regress_rls_schema,t1,t)\n\nGRANT SELECT ON t1 TO regress_rls_bob, regress_rls_carol;\nCREATE POLICY p1 ON t1 TO regress_rls_bob USING ((a % 2) = 0);\nCREATE POLICY p2 ON t1 TO regress_rls_carol USING ((a % 4) = 0);\nALTER TABLE t1 ENABLE ROW LEVEL SECURITY;\n-- Prepare as regress_rls_bob\nSET ROLE regress_rls_bob;\nPREPARE role_inval AS SELECT * FROM t1;\n-- Check plan\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE role_inval;\n--- QUERY PLAN ---\n Seq Scan on t1\n   Filter: ((a % 2) = 0)\n\n-- Change to regress_rls_carol\nSET ROLE regress_rls_carol;\n-- Check plan- should be different\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE role_inval;\n--- QUERY PLAN ---\n Seq Scan on t1\n   Filter: ((a % 4) = 0)\n\n-- Change back to regress_rls_bob\nSET ROLE regress_rls_bob;\n-- Check plan- should be back to original\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE role_inval;\n--- QUERY PLAN ---\n Seq Scan on t1\n   Filter: ((a % 2) = 0)\n\n--\n-- CTE and RLS\n--\nRESET SESSION AUTHORIZATION;\nDROP TABLE t1 CASCADE;\nCREATE TABLE t1 (a integer, b text);\nSELECT public.create_hypertable('t1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (15,regress_rls_schema,t1,t)\n\nCREATE POLICY p1 ON t1 USING (a % 2 = 0);\nALTER TABLE t1 ENABLE ROW LEVEL SECURITY;\nGRANT ALL ON t1 TO regress_rls_bob;\nINSERT INTO t1 (SELECT x, md5(x::text) FROM generate_series(0,20) x);\nSET SESSION AUTHORIZATION regress_rls_bob;\nWITH cte1 AS (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1;\nNOTICE:  f_leak => cfcd208495d565ef66e7dff9f98764da\nNOTICE:  f_leak => c81e728d9d4c2f636f067f89cc14862c\nNOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c\nNOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc\nNOTICE:  f_leak => c9f0f895fb98ab9159f51fd0297e236d\nNOTICE:  f_leak => d3d9446802a44259755d38e6d163e820\nNOTICE:  f_leak => c20ad4d76fe97759aa27a0c99bff6710\nNOTICE:  f_leak => aab3238922bcc25a6f606eb525ffdc56\nNOTICE:  f_leak => c74d97b01eae257e44aa9d5bade97baf\nNOTICE:  f_leak => 6f4922f45568161a8cdf4ad2299f6d23\nNOTICE:  f_leak => 98f13708210194c475687be6106a3b84\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) WITH cte1 AS (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1;\n--- QUERY PLAN ---\n CTE Scan on cte1\n   CTE cte1\n     ->  Custom Scan (ChunkAppend) on t1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on t1 t1_1\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_69_chunk t1_2\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_70_chunk t1_3\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_71_chunk t1_4\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_72_chunk t1_5\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_73_chunk t1_6\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_74_chunk t1_7\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_75_chunk t1_8\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_76_chunk t1_9\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_77_chunk t1_10\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_78_chunk t1_11\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_79_chunk t1_12\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n\nWITH cte1 AS (UPDATE t1 SET a = a + 1 RETURNING *) SELECT * FROM cte1; --fail\nERROR:  new row violates row-level security policy for table \"t1\"\nWITH cte1 AS (UPDATE t1 SET a = a RETURNING *) SELECT * FROM cte1; --ok\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n\nWITH cte1 AS (INSERT INTO t1 VALUES (21, 'Fail') RETURNING *) SELECT * FROM cte1; --fail\nERROR:  new row violates row-level security policy for table \"t1\"\nWITH cte1 AS (INSERT INTO t1 VALUES (20, 'Success') RETURNING *) SELECT * FROM cte1; --ok\n a  |    b    \n----+---------\n 20 | Success\n\n--\n-- Rename Policy\n--\nRESET SESSION AUTHORIZATION;\nALTER POLICY p1 ON t1 RENAME TO p1; --fail\nERROR:  policy \"p1\" for table \"t1\" already exists\nSELECT polname, relname\n    FROM pg_policy pol\n    JOIN pg_class pc ON (pc.oid = pol.polrelid)\n    WHERE relname = 't1';\n polname | relname \n---------+---------\n p1      | t1\n\nALTER POLICY p1 ON t1 RENAME TO p2; --ok\nSELECT polname, relname\n    FROM pg_policy pol\n    JOIN pg_class pc ON (pc.oid = pol.polrelid)\n    WHERE relname = 't1';\n polname | relname \n---------+---------\n p2      | t1\n\n--\n-- Check INSERT SELECT\n--\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE TABLE t2 (a integer, b text);\nSELECT public.create_hypertable('t2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (16,regress_rls_schema,t2,t)\n\nINSERT INTO t2 (SELECT * FROM t1);\nEXPLAIN (BUFFERS OFF, COSTS OFF) INSERT INTO t2 (SELECT * FROM t1);\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on t2\n         ->  Append\n               ->  Seq Scan on t1 t1_1\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_69_chunk t1_2\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_70_chunk t1_3\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_71_chunk t1_4\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_72_chunk t1_5\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_73_chunk t1_6\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_74_chunk t1_7\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_75_chunk t1_8\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_76_chunk t1_9\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_77_chunk t1_10\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_78_chunk t1_11\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_79_chunk t1_12\n                     Filter: ((a % 2) = 0)\n\nSELECT * FROM t2;\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n 20 | Success\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t2;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t2 t2_1\n   ->  Seq Scan on _hyper_16_80_chunk t2_2\n   ->  Seq Scan on _hyper_16_81_chunk t2_3\n   ->  Seq Scan on _hyper_16_82_chunk t2_4\n   ->  Seq Scan on _hyper_16_83_chunk t2_5\n   ->  Seq Scan on _hyper_16_84_chunk t2_6\n   ->  Seq Scan on _hyper_16_85_chunk t2_7\n   ->  Seq Scan on _hyper_16_86_chunk t2_8\n   ->  Seq Scan on _hyper_16_87_chunk t2_9\n   ->  Seq Scan on _hyper_16_88_chunk t2_10\n   ->  Seq Scan on _hyper_16_89_chunk t2_11\n   ->  Seq Scan on _hyper_16_90_chunk t2_12\n\nCREATE TABLE t3 AS SELECT * FROM t1;\nSELECT public.create_hypertable('t2', 'a', chunk_time_interval=>2);\nERROR:  table \"t2\" is already a hypertable\nSELECT * FROM t3;\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n 20 | Success\n\nSELECT * INTO t4 FROM t1;\nSELECT * FROM t4;\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n 20 | Success\n\n--\n-- RLS with JOIN\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE blog (id integer, author text, post text);\nSELECT public.create_hypertable('blog', 'id', chunk_time_interval=>2);\n       create_hypertable        \n--------------------------------\n (17,regress_rls_schema,blog,t)\n\nCREATE TABLE comment (blog_id integer, message text);\nSELECT public.create_hypertable('comment', 'blog_id', chunk_time_interval=>2);\n         create_hypertable         \n-----------------------------------\n (18,regress_rls_schema,comment,t)\n\nGRANT ALL ON blog, comment TO regress_rls_bob;\nCREATE POLICY blog_1 ON blog USING (id % 2 = 0);\nALTER TABLE blog ENABLE ROW LEVEL SECURITY;\nINSERT INTO blog VALUES\n    (1, 'alice', 'blog #1'),\n    (2, 'bob', 'blog #1'),\n    (3, 'alice', 'blog #2'),\n    (4, 'alice', 'blog #3'),\n    (5, 'john', 'blog #1');\nINSERT INTO comment VALUES\n    (1, 'cool blog'),\n    (1, 'fun blog'),\n    (3, 'crazy blog'),\n    (5, 'what?'),\n    (4, 'insane!'),\n    (2, 'who did it?');\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Check RLS JOIN with Non-RLS.\nSELECT id, author, message FROM blog JOIN comment ON id = blog_id;\n id | author |   message   \n----+--------+-------------\n  2 | bob    | who did it?\n  4 | alice  | insane!\n\n-- Check Non-RLS JOIN with RLS.\nSELECT id, author, message FROM comment JOIN blog ON id = blog_id;\n id | author |   message   \n----+--------+-------------\n  2 | bob    | who did it?\n  4 | alice  | insane!\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE POLICY comment_1 ON comment USING (blog_id < 4);\nALTER TABLE comment ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Check RLS JOIN RLS\nSELECT id, author, message FROM blog JOIN comment ON id = blog_id;\n id | author |   message   \n----+--------+-------------\n  2 | bob    | who did it?\n\nSELECT id, author, message FROM comment JOIN blog ON id = blog_id;\n id | author |   message   \n----+--------+-------------\n  2 | bob    | who did it?\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP TABLE blog;\nDROP TABLE comment;\n--\n-- Default Deny Policy\n--\nRESET SESSION AUTHORIZATION;\nDROP POLICY p2 ON t1;\nALTER TABLE t1 OWNER TO regress_rls_alice;\n-- Check that default deny does not apply to superuser.\nRESET SESSION AUTHORIZATION;\nSELECT * FROM t1;\n a  |                b                 \n----+----------------------------------\n  1 | c4ca4238a0b923820dcc509a6f75849b\n  0 | cfcd208495d565ef66e7dff9f98764da\n  3 | eccbc87e4b5ce2fe28308fd9f2a7baf3\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  5 | e4da3b7fbbce2345d7772b0674a318d5\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  7 | 8f14e45fceea167a5a36dedd4bea2543\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  9 | 45c48cce2e2d7fbdea1afc51c7c6ad26\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 11 | 6512bd43d9caa6e02c990b0a82652dca\n 10 | d3d9446802a44259755d38e6d163e820\n 13 | c51ce410c124a10e0db5e4b97fc2af39\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 17 | 70efdf2ec9b086079795c442636b55fb\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 19 | 1f0e3dad99908345f7439f8ffabdffc4\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n 20 | Success\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n   ->  Seq Scan on _hyper_15_69_chunk t1_2\n   ->  Seq Scan on _hyper_15_70_chunk t1_3\n   ->  Seq Scan on _hyper_15_71_chunk t1_4\n   ->  Seq Scan on _hyper_15_72_chunk t1_5\n   ->  Seq Scan on _hyper_15_73_chunk t1_6\n   ->  Seq Scan on _hyper_15_74_chunk t1_7\n   ->  Seq Scan on _hyper_15_75_chunk t1_8\n   ->  Seq Scan on _hyper_15_76_chunk t1_9\n   ->  Seq Scan on _hyper_15_77_chunk t1_10\n   ->  Seq Scan on _hyper_15_78_chunk t1_11\n   ->  Seq Scan on _hyper_15_79_chunk t1_12\n\n-- Check that default deny does not apply to table owner.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSELECT * FROM t1;\n a  |                b                 \n----+----------------------------------\n  1 | c4ca4238a0b923820dcc509a6f75849b\n  0 | cfcd208495d565ef66e7dff9f98764da\n  3 | eccbc87e4b5ce2fe28308fd9f2a7baf3\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  5 | e4da3b7fbbce2345d7772b0674a318d5\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  7 | 8f14e45fceea167a5a36dedd4bea2543\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  9 | 45c48cce2e2d7fbdea1afc51c7c6ad26\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 11 | 6512bd43d9caa6e02c990b0a82652dca\n 10 | d3d9446802a44259755d38e6d163e820\n 13 | c51ce410c124a10e0db5e4b97fc2af39\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 17 | 70efdf2ec9b086079795c442636b55fb\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 19 | 1f0e3dad99908345f7439f8ffabdffc4\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n 20 | Success\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n   ->  Seq Scan on _hyper_15_69_chunk t1_2\n   ->  Seq Scan on _hyper_15_70_chunk t1_3\n   ->  Seq Scan on _hyper_15_71_chunk t1_4\n   ->  Seq Scan on _hyper_15_72_chunk t1_5\n   ->  Seq Scan on _hyper_15_73_chunk t1_6\n   ->  Seq Scan on _hyper_15_74_chunk t1_7\n   ->  Seq Scan on _hyper_15_75_chunk t1_8\n   ->  Seq Scan on _hyper_15_76_chunk t1_9\n   ->  Seq Scan on _hyper_15_77_chunk t1_10\n   ->  Seq Scan on _hyper_15_78_chunk t1_11\n   ->  Seq Scan on _hyper_15_79_chunk t1_12\n\n-- Check that default deny applies to non-owner/non-superuser when RLS on.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO ON;\nSELECT * FROM t1;\n a | b \n---+---\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1;\n--- QUERY PLAN ---\n Result\n   One-Time Filter: false\n\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM t1;\n a | b \n---+---\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1;\n--- QUERY PLAN ---\n Result\n   One-Time Filter: false\n\n--\n-- COPY TO/FROM\n--\nRESET SESSION AUTHORIZATION;\nDROP TABLE copy_t CASCADE;\nERROR:  table \"copy_t\" does not exist\nCREATE TABLE copy_t (a integer, b text);\nSELECT public.create_hypertable('copy_t', 'a', chunk_time_interval=>2);\n        create_hypertable         \n----------------------------------\n (19,regress_rls_schema,copy_t,t)\n\nCREATE POLICY p1 ON copy_t USING (a % 2 = 0);\nALTER TABLE copy_t ENABLE ROW LEVEL SECURITY;\nGRANT ALL ON copy_t TO regress_rls_bob, regress_rls_exempt_user;\nINSERT INTO copy_t (SELECT x, md5(x::text) FROM generate_series(0,10) x);\n-- Check COPY TO as Superuser/owner.\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';\n0,cfcd208495d565ef66e7dff9f98764da\n1,c4ca4238a0b923820dcc509a6f75849b\n2,c81e728d9d4c2f636f067f89cc14862c\n3,eccbc87e4b5ce2fe28308fd9f2a7baf3\n4,a87ff679a2f3e71d9181a67b7542122c\n5,e4da3b7fbbce2345d7772b0674a318d5\n6,1679091c5a880faf6fb5e6087eb1b2dc\n7,8f14e45fceea167a5a36dedd4bea2543\n8,c9f0f895fb98ab9159f51fd0297e236d\n9,45c48cce2e2d7fbdea1afc51c7c6ad26\n10,d3d9446802a44259755d38e6d163e820\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';\n0,cfcd208495d565ef66e7dff9f98764da\n1,c4ca4238a0b923820dcc509a6f75849b\n2,c81e728d9d4c2f636f067f89cc14862c\n3,eccbc87e4b5ce2fe28308fd9f2a7baf3\n4,a87ff679a2f3e71d9181a67b7542122c\n5,e4da3b7fbbce2345d7772b0674a318d5\n6,1679091c5a880faf6fb5e6087eb1b2dc\n7,8f14e45fceea167a5a36dedd4bea2543\n8,c9f0f895fb98ab9159f51fd0297e236d\n9,45c48cce2e2d7fbdea1afc51c7c6ad26\n10,d3d9446802a44259755d38e6d163e820\n-- Check COPY TO as user with permissions.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS\nERROR:  query would be affected by row-level security policy for table \"copy_t\"\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok\n0,cfcd208495d565ef66e7dff9f98764da\n2,c81e728d9d4c2f636f067f89cc14862c\n4,a87ff679a2f3e71d9181a67b7542122c\n6,1679091c5a880faf6fb5e6087eb1b2dc\n8,c9f0f895fb98ab9159f51fd0297e236d\n10,d3d9446802a44259755d38e6d163e820\n-- Check COPY TO as user with permissions and BYPASSRLS\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok\n0,cfcd208495d565ef66e7dff9f98764da\n1,c4ca4238a0b923820dcc509a6f75849b\n2,c81e728d9d4c2f636f067f89cc14862c\n3,eccbc87e4b5ce2fe28308fd9f2a7baf3\n4,a87ff679a2f3e71d9181a67b7542122c\n5,e4da3b7fbbce2345d7772b0674a318d5\n6,1679091c5a880faf6fb5e6087eb1b2dc\n7,8f14e45fceea167a5a36dedd4bea2543\n8,c9f0f895fb98ab9159f51fd0297e236d\n9,45c48cce2e2d7fbdea1afc51c7c6ad26\n10,d3d9446802a44259755d38e6d163e820\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok\n0,cfcd208495d565ef66e7dff9f98764da\n1,c4ca4238a0b923820dcc509a6f75849b\n2,c81e728d9d4c2f636f067f89cc14862c\n3,eccbc87e4b5ce2fe28308fd9f2a7baf3\n4,a87ff679a2f3e71d9181a67b7542122c\n5,e4da3b7fbbce2345d7772b0674a318d5\n6,1679091c5a880faf6fb5e6087eb1b2dc\n7,8f14e45fceea167a5a36dedd4bea2543\n8,c9f0f895fb98ab9159f51fd0297e236d\n9,45c48cce2e2d7fbdea1afc51c7c6ad26\n10,d3d9446802a44259755d38e6d163e820\n-- Check COPY TO as user without permissions. SET row_security TO OFF;\nSET SESSION AUTHORIZATION regress_rls_carol;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS\nERROR:  query would be affected by row-level security policy for table \"copy_t\"\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - permission denied\nERROR:  permission denied for table copy_t\n-- Check COPY relation TO; keep it just one row to avoid reordering issues\nRESET SESSION AUTHORIZATION;\nSET row_security TO ON;\nCREATE TABLE copy_rel_to (a integer, b text);\nSELECT public.create_hypertable('copy_rel_to', 'a', chunk_time_interval=>2);\n           create_hypertable           \n---------------------------------------\n (20,regress_rls_schema,copy_rel_to,t)\n\nCREATE POLICY p1 ON copy_rel_to USING (a % 2 = 0);\nALTER TABLE copy_rel_to ENABLE ROW LEVEL SECURITY;\nGRANT ALL ON copy_rel_to TO regress_rls_bob, regress_rls_exempt_user;\nINSERT INTO copy_rel_to VALUES (1, md5('1'));\n-- Check COPY TO as Superuser/owner.\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ',';\n1,c4ca4238a0b923820dcc509a6f75849b\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ',';\n1,c4ca4238a0b923820dcc509a6f75849b\n-- Check COPY TO as user with permissions.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS\nERROR:  query would be affected by row-level security policy for table \"copy_rel_to\"\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --ok\n-- Check COPY TO as user with permissions and BYPASSRLS\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --ok\n1,c4ca4238a0b923820dcc509a6f75849b\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --ok\n1,c4ca4238a0b923820dcc509a6f75849b\n-- Check COPY TO as user without permissions. SET row_security TO OFF;\nSET SESSION AUTHORIZATION regress_rls_carol;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --fail - permission denied\nERROR:  query would be affected by row-level security policy for table \"copy_rel_to\"\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --fail - permission denied\nERROR:  permission denied for table copy_rel_to\n-- Check COPY FROM as Superuser/owner.\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nCOPY copy_t FROM STDIN; --ok\nSET row_security TO ON;\nCOPY copy_t FROM STDIN; --ok\n-- Check COPY FROM as user with permissions.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO OFF;\nCOPY copy_t FROM STDIN; --fail - would be affected by RLS.\nERROR:  query would be affected by row-level security policy for table \"copy_t\"\nSET row_security TO ON;\nCOPY copy_t FROM STDIN; --fail - COPY FROM not supported by RLS.\nERROR:  COPY FROM not supported with row-level security\nHINT:  Use INSERT statements instead.\n-- Check COPY FROM as user with permissions and BYPASSRLS\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO ON;\nCOPY copy_t FROM STDIN; --ok\n-- Check COPY FROM as user without permissions.\nSET SESSION AUTHORIZATION regress_rls_carol;\nSET row_security TO OFF;\nCOPY copy_t FROM STDIN; --fail - permission denied.\nERROR:  permission denied for table copy_t\nSET row_security TO ON;\nCOPY copy_t FROM STDIN; --fail - permission denied.\nERROR:  permission denied for table copy_t\nRESET SESSION AUTHORIZATION;\nDROP TABLE copy_t;\nDROP TABLE copy_rel_to CASCADE;\n-- Check WHERE CURRENT OF\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE current_check (currentid int, payload text, rlsuser text);\nSELECT public.create_hypertable('current_check', 'currentid', chunk_time_interval=>10);\n            create_hypertable            \n-----------------------------------------\n (21,regress_rls_schema,current_check,t)\n\nGRANT ALL ON current_check TO PUBLIC;\nINSERT INTO current_check VALUES\n    (1, 'abc', 'regress_rls_bob'),\n    (2, 'bcd', 'regress_rls_bob'),\n    (3, 'cde', 'regress_rls_bob'),\n    (4, 'def', 'regress_rls_bob');\nCREATE POLICY p1 ON current_check FOR SELECT USING (currentid % 2 = 0);\nCREATE POLICY p2 ON current_check FOR DELETE USING (currentid = 4 AND rlsuser = current_user);\nCREATE POLICY p3 ON current_check FOR UPDATE USING (currentid = 4) WITH CHECK (rlsuser = current_user);\nALTER TABLE current_check ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Can SELECT even rows\nSELECT * FROM current_check;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         2 | bcd     | regress_rls_bob\n         4 | def     | regress_rls_bob\n\n-- Cannot UPDATE row 2\nUPDATE current_check SET payload = payload || '_new' WHERE currentid = 2 RETURNING *;\n currentid | payload | rlsuser \n-----------+---------+---------\n\nBEGIN;\n-- WHERE CURRENT OF does not work with custom scan nodes\n-- so we have to disable chunk append here\nSET timescaledb.enable_chunk_append TO false;\nDECLARE current_check_cursor SCROLL CURSOR FOR SELECT * FROM current_check;\n-- Returns rows that can be seen according to SELECT policy, like plain SELECT\n-- above (even rows)\nFETCH ABSOLUTE 1 FROM current_check_cursor;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         2 | bcd     | regress_rls_bob\n\n-- Still cannot UPDATE row 2 through cursor\nUPDATE current_check SET payload = payload || '_new' WHERE CURRENT OF current_check_cursor RETURNING *;\n currentid | payload | rlsuser \n-----------+---------+---------\n\n-- Can update row 4 through cursor, which is the next visible row\nFETCH RELATIVE 1 FROM current_check_cursor;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         4 | def     | regress_rls_bob\n\nUPDATE current_check SET payload = payload || '_new' WHERE CURRENT OF current_check_cursor RETURNING *;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         4 | def_new | regress_rls_bob\n\nSELECT * FROM current_check;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         2 | bcd     | regress_rls_bob\n         4 | def_new | regress_rls_bob\n\n-- Plan should be a subquery TID scan\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE current_check SET payload = payload WHERE CURRENT OF current_check_cursor;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Update on current_check\n         Update on _hyper_21_104_chunk current_check_1\n         ->  Tid Scan on _hyper_21_104_chunk current_check_1\n               TID Cond: CURRENT OF current_check_cursor\n               Filter: ((currentid = 4) AND ((currentid % 2) = 0))\n\n-- Similarly can only delete row 4\nFETCH ABSOLUTE 1 FROM current_check_cursor;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         2 | bcd     | regress_rls_bob\n\nDELETE FROM current_check WHERE CURRENT OF current_check_cursor RETURNING *;\n currentid | payload | rlsuser \n-----------+---------+---------\n\nFETCH RELATIVE 1 FROM current_check_cursor;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         4 | def     | regress_rls_bob\n\nDELETE FROM current_check WHERE CURRENT OF current_check_cursor RETURNING *;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         4 | def_new | regress_rls_bob\n\nSELECT * FROM current_check;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         2 | bcd     | regress_rls_bob\n\nRESET timescaledb.enable_chunk_append;\nCOMMIT;\n--\n-- check pg_stats view filtering\n--\nSET row_security TO ON;\nSET SESSION AUTHORIZATION regress_rls_alice;\nANALYZE current_check;\n-- Stats visible\nSELECT row_security_active('current_check');\n row_security_active \n---------------------\n f\n\nSELECT attname, most_common_vals FROM pg_stats\n  WHERE tablename = 'current_check'\n  ORDER BY 1;\n  attname  | most_common_vals  \n-----------+-------------------\n currentid | \n payload   | \n rlsuser   | {regress_rls_bob}\n\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Stats not visible\nSELECT row_security_active('current_check');\n row_security_active \n---------------------\n t\n\nSELECT attname, most_common_vals FROM pg_stats\n  WHERE tablename = 'current_check'\n  ORDER BY 1;\n attname | most_common_vals \n---------+------------------\n\n--\n-- Collation support\n--\nBEGIN;\nCREATE TABLE coll_t (c) AS VALUES ('bar'::text);\nCREATE POLICY coll_p ON coll_t USING (c < ('foo'::text COLLATE \"C\"));\nALTER TABLE coll_t ENABLE ROW LEVEL SECURITY;\nGRANT SELECT ON coll_t TO regress_rls_alice;\nSELECT (string_to_array(polqual, ':'))[7] AS inputcollid FROM pg_policy WHERE polrelid = 'coll_t'::regclass;\n   inputcollid    \n------------------\n inputcollid 950 \n\nSET SESSION AUTHORIZATION regress_rls_alice;\nSELECT * FROM coll_t;\n  c  \n-----\n bar\n\nROLLBACK;\n--\n-- Shared Object Dependencies\n--\nRESET SESSION AUTHORIZATION;\nBEGIN;\nCREATE ROLE regress_rls_eve;\nCREATE ROLE regress_rls_frank;\nCREATE TABLE tbl1 (c) AS VALUES ('bar'::text);\nGRANT SELECT ON TABLE tbl1 TO regress_rls_eve;\nCREATE POLICY P ON tbl1 TO regress_rls_eve, regress_rls_frank USING (true);\nSELECT refclassid::regclass, deptype\n  FROM pg_depend\n  WHERE classid = 'pg_policy'::regclass\n  AND refobjid = 'tbl1'::regclass;\n refclassid | deptype \n------------+---------\n pg_class   | a\n\nSELECT refclassid::regclass, deptype\n  FROM pg_shdepend\n  WHERE classid = 'pg_policy'::regclass\n  AND refobjid IN ('regress_rls_eve'::regrole, 'regress_rls_frank'::regrole);\n refclassid | deptype \n------------+---------\n pg_authid  | r\n pg_authid  | r\n\nSAVEPOINT q;\nDROP ROLE regress_rls_eve; --fails due to dependency on POLICY p\nERROR:  role \"regress_rls_eve\" cannot be dropped because some objects depend on it\nDETAIL:  privileges for table tbl1\ntarget of policy p on table tbl1\nROLLBACK TO q;\nALTER POLICY p ON tbl1 TO regress_rls_frank USING (true);\nSAVEPOINT q;\nDROP ROLE regress_rls_eve; --fails due to dependency on GRANT SELECT\nERROR:  role \"regress_rls_eve\" cannot be dropped because some objects depend on it\nDETAIL:  privileges for table tbl1\nROLLBACK TO q;\nREVOKE ALL ON TABLE tbl1 FROM regress_rls_eve;\nSAVEPOINT q;\nDROP ROLE regress_rls_eve; --succeeds\nROLLBACK TO q;\nSAVEPOINT q;\nDROP ROLE regress_rls_frank; --fails due to dependency on POLICY p\nERROR:  role \"regress_rls_frank\" cannot be dropped because some objects depend on it\nDETAIL:  target of policy p on table tbl1\nROLLBACK TO q;\nDROP POLICY p ON tbl1;\nSAVEPOINT q;\nDROP ROLE regress_rls_frank; -- succeeds\nROLLBACK TO q;\nROLLBACK; -- cleanup\n--\n-- Converting table to view\n--\nBEGIN;\nCREATE TABLE t (c int);\nSELECT public.create_hypertable('t', 'c', chunk_time_interval=>2);\n      create_hypertable      \n-----------------------------\n (22,regress_rls_schema,t,t)\n\nCREATE POLICY p ON t USING (c % 2 = 1);\nALTER TABLE t ENABLE ROW LEVEL SECURITY;\nSAVEPOINT q;\nCREATE RULE \"_RETURN\" AS ON SELECT TO t DO INSTEAD\n  SELECT * FROM generate_series(1,5) t0(c); -- fails due to row level security enabled\nERROR:  hypertables do not support rules\nROLLBACK TO q;\nALTER TABLE t DISABLE ROW LEVEL SECURITY;\nSAVEPOINT q;\nCREATE RULE \"_RETURN\" AS ON SELECT TO t DO INSTEAD\n  SELECT * FROM generate_series(1,5) t0(c); -- fails due to policy p on t\nERROR:  hypertables do not support rules\nROLLBACK TO q;\nDROP POLICY p ON t;\nCREATE RULE \"_RETURN\" AS ON SELECT TO t DO INSTEAD\n  SELECT * FROM generate_series(1,5) t0(c); -- succeeds\nERROR:  hypertables do not support rules\nROLLBACK;\n--\n-- Policy expression handling\n--\nBEGIN;\nCREATE TABLE t (c) AS VALUES ('bar'::text);\nCREATE POLICY p ON t USING (max(c)); -- fails: aggregate functions are not allowed in policy expressions\nERROR:  aggregate functions are not allowed in policy expressions\nROLLBACK;\n--\n-- Non-target relations are only subject to SELECT policies\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE r1 (a int);\nSELECT public.create_hypertable('r1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (23,regress_rls_schema,r1,t)\n\nCREATE TABLE r2 (a int);\nSELECT public.create_hypertable('r2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (24,regress_rls_schema,r2,t)\n\nINSERT INTO r1 VALUES (10), (20);\nINSERT INTO r2 VALUES (10), (20);\nGRANT ALL ON r1, r2 TO regress_rls_bob;\nCREATE POLICY p1 ON r1 USING (true);\nALTER TABLE r1 ENABLE ROW LEVEL SECURITY;\nCREATE POLICY p1 ON r2 FOR SELECT USING (true);\nCREATE POLICY p2 ON r2 FOR INSERT WITH CHECK (false);\nCREATE POLICY p3 ON r2 FOR UPDATE USING (false);\nCREATE POLICY p4 ON r2 FOR DELETE USING (false);\nALTER TABLE r2 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM r1;\n a  \n----\n 10\n 20\n\nSELECT * FROM r2;\n a  \n----\n 10\n 20\n\n-- r2 is read-only\nINSERT INTO r2 VALUES (2); -- Not allowed\nERROR:  new row violates row-level security policy for table \"r2\"\n\\pset tuples_only 1\nUPDATE r2 SET a = 2 RETURNING *; -- Updates nothing\n\nDELETE FROM r2 RETURNING *; -- Deletes nothing\n\n\\pset tuples_only 0\n-- r2 can be used as a non-target relation in DML\nINSERT INTO r1 SELECT a + 1 FROM r2 RETURNING *; -- OK\n a  \n----\n 11\n 21\n\nUPDATE r1 SET a = r2.a + 2 FROM r2 WHERE r1.a = r2.a RETURNING *; -- OK\nERROR:  new row for relation \"_hyper_23_105_chunk\" violates check constraint \"constraint_105\"\nDELETE FROM r1 USING r2 WHERE r1.a = r2.a + 2 RETURNING *; -- OK\n a | a \n---+---\n\nSELECT * FROM r1;\n a  \n----\n 10\n 11\n 20\n 21\n\nSELECT * FROM r2;\n a  \n----\n 10\n 20\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP TABLE r1;\nDROP TABLE r2;\n--\n-- FORCE ROW LEVEL SECURITY applies RLS to owners too\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security = on;\nCREATE TABLE r1 (a int);\nSELECT public.create_hypertable('r1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (25,regress_rls_schema,r1,t)\n\nINSERT INTO r1 VALUES (10), (20);\nCREATE POLICY p1 ON r1 USING (false);\nALTER TABLE r1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r1 FORCE ROW LEVEL SECURITY;\n-- No error, but no rows\nTABLE r1;\n a \n---\n\n-- RLS error\nINSERT INTO r1 VALUES (1);\nERROR:  new row violates row-level security policy for table \"r1\"\n-- No error (unable to see any rows to update)\nUPDATE r1 SET a = 1;\nTABLE r1;\n a \n---\n\n-- No error (unable to see any rows to delete)\nDELETE FROM r1;\nTABLE r1;\n a \n---\n\nSET row_security = off;\n-- these all fail, would be affected by RLS\nTABLE r1;\nERROR:  query would be affected by row-level security policy for table \"r1\"\nHINT:  To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.\nUPDATE r1 SET a = 1;\nERROR:  query would be affected by row-level security policy for table \"r1\"\nHINT:  To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.\nDELETE FROM r1;\nERROR:  query would be affected by row-level security policy for table \"r1\"\nHINT:  To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.\nDROP TABLE r1;\n--\n-- FORCE ROW LEVEL SECURITY does not break RI\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security = on;\nCREATE TABLE r1 (a int PRIMARY KEY);\n-- r1 is not a hypertable since r1.a is referenced by r2\nCREATE TABLE r2 (a int REFERENCES r1);\nSELECT public.create_hypertable('r2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (26,regress_rls_schema,r2,t)\n\nINSERT INTO r1 VALUES (10), (20);\nINSERT INTO r2 VALUES (10), (20);\n-- Create policies on r2 which prevent the\n-- owner from seeing any rows, but RI should\n-- still see them.\nCREATE POLICY p1 ON r2 USING (false);\nALTER TABLE r2 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r2 FORCE ROW LEVEL SECURITY;\n-- Errors due to rows in r2\nDELETE FROM r1;\nERROR:  update or delete on table \"r1\" violates foreign key constraint \"r2_a_fkey\" on table \"r2\"\nDETAIL:  Key (a)=(10) is still referenced from table \"r2\".\n-- Reset r2 to no-RLS\nDROP POLICY p1 ON r2;\nALTER TABLE r2 NO FORCE ROW LEVEL SECURITY;\nALTER TABLE r2 DISABLE ROW LEVEL SECURITY;\n-- clean out r2 for INSERT test below\nDELETE FROM r2;\n-- Change r1 to not allow rows to be seen\nCREATE POLICY p1 ON r1 USING (false);\nALTER TABLE r1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r1 FORCE ROW LEVEL SECURITY;\n-- No rows seen\nTABLE r1;\n a \n---\n\n-- No error, RI still sees that row exists in r1\nINSERT INTO r2 VALUES (10);\nDROP TABLE r2;\nDROP TABLE r1;\n-- Ensure cascaded DELETE works\nCREATE TABLE r1 (a int PRIMARY KEY);\n-- r1 is not a hypertable since r1.a is referenced by r2\nCREATE TABLE r2 (a int REFERENCES r1 ON DELETE CASCADE);\nSELECT public.create_hypertable('r2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (27,regress_rls_schema,r2,t)\n\nINSERT INTO r1 VALUES (10), (20);\nINSERT INTO r2 VALUES (10), (20);\n-- Create policies on r2 which prevent the\n-- owner from seeing any rows, but RI should\n-- still see them.\nCREATE POLICY p1 ON r2 USING (false);\nALTER TABLE r2 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r2 FORCE ROW LEVEL SECURITY;\n-- Deletes all records from both\nDELETE FROM r1;\n-- Remove FORCE from r2\nALTER TABLE r2 NO FORCE ROW LEVEL SECURITY;\n-- As owner, we now bypass RLS\n-- verify no rows in r2 now\nTABLE r2;\n a \n---\n\nDROP TABLE r2;\nDROP TABLE r1;\n-- Ensure cascaded UPDATE works\nCREATE TABLE r1 (a int PRIMARY KEY);\n-- r1 is not a hypertable since r1.a is referenced by r2\nCREATE TABLE r2 (a int REFERENCES r1 ON UPDATE CASCADE);\nSELECT public.create_hypertable('r2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (28,regress_rls_schema,r2,t)\n\nINSERT INTO r1 VALUES (10), (20);\nINSERT INTO r2 VALUES (10), (20);\n-- Create policies on r2 which prevent the\n-- owner from seeing any rows, but RI should\n-- still see them.\nCREATE POLICY p1 ON r2 USING (false);\nALTER TABLE r2 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r2 FORCE ROW LEVEL SECURITY;\n-- Updates records in both (terse output to not print CONTEXT, which can be different).\n\\set VERBOSITY terse\nUPDATE r1 SET a = a+5;\nERROR:  new row for relation \"_hyper_28_117_chunk\" violates check constraint \"constraint_117\"\n\\set VERBOSITY default\n-- Remove FORCE from r2\nALTER TABLE r2 NO FORCE ROW LEVEL SECURITY;\n-- As owner, we now bypass RLS\n-- verify records in r2 updated\nTABLE r2;\n a  \n----\n 10\n 20\n\nDROP TABLE r2;\nDROP TABLE r1;\n--\n-- Test INSERT+RETURNING applies SELECT policies as\n-- WithCheckOptions (meaning an error is thrown)\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security = on;\nCREATE TABLE r1 (a int);\nSELECT public.create_hypertable('r1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (29,regress_rls_schema,r1,t)\n\nCREATE POLICY p1 ON r1 FOR SELECT USING (false);\nCREATE POLICY p2 ON r1 FOR INSERT WITH CHECK (true);\nALTER TABLE r1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r1 FORCE ROW LEVEL SECURITY;\n-- Works fine\nINSERT INTO r1 VALUES (10), (20);\n-- No error, but no rows\nTABLE r1;\n a \n---\n\nSET row_security = off;\n-- fail, would be affected by RLS\nTABLE r1;\nERROR:  query would be affected by row-level security policy for table \"r1\"\nHINT:  To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.\nSET row_security = on;\n-- Error\nINSERT INTO r1 VALUES (10), (20) RETURNING *;\nERROR:  new row violates row-level security policy for table \"r1\"\nDROP TABLE r1;\n--\n-- Test UPDATE+RETURNING applies SELECT policies as\n-- WithCheckOptions (meaning an error is thrown)\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security = on;\nCREATE TABLE r1 (a int PRIMARY KEY);\nSELECT public.create_hypertable('r1', 'a', chunk_time_interval=>100);\n      create_hypertable       \n------------------------------\n (30,regress_rls_schema,r1,t)\n\nCREATE POLICY p1 ON r1 FOR SELECT USING (a < 20);\nCREATE POLICY p2 ON r1 FOR UPDATE USING (a < 20) WITH CHECK (true);\nCREATE POLICY p3 ON r1 FOR INSERT WITH CHECK (true);\nINSERT INTO r1 VALUES (10);\nALTER TABLE r1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r1 FORCE ROW LEVEL SECURITY;\n-- Works fine\nUPDATE r1 SET a = 30;\n-- Show updated rows\nALTER TABLE r1 NO FORCE ROW LEVEL SECURITY;\nTABLE r1;\n a  \n----\n 30\n\n-- reset value in r1 for test with RETURNING\nUPDATE r1 SET a = 10;\n-- Verify row reset\nTABLE r1;\n a  \n----\n 10\n\nALTER TABLE r1 FORCE ROW LEVEL SECURITY;\n-- Error\nUPDATE r1 SET a = 30 RETURNING *;\nERROR:  new row violates row-level security policy for table \"r1\"\n-- UPDATE path of INSERT ... ON CONFLICT DO UPDATE should also error out\nINSERT INTO r1 VALUES (10)\n    ON CONFLICT (a) DO UPDATE SET a = 30 RETURNING *;\nERROR:  new row violates row-level security policy for table \"r1\"\n-- Should still error out without RETURNING (use of arbiter always requires\n-- SELECT permissions)\nINSERT INTO r1 VALUES (10)\n    ON CONFLICT (a) DO UPDATE SET a = 30;\nERROR:  new row violates row-level security policy for table \"r1\"\n-- ON CONFLICT ON CONSTRAINT\nINSERT INTO r1 VALUES (10)\n    ON CONFLICT ON CONSTRAINT r1_pkey DO UPDATE SET a = 30;\nERROR:  new row violates row-level security policy for table \"r1\"\nDROP TABLE r1;\n-- Check dependency handling\nRESET SESSION AUTHORIZATION;\nCREATE TABLE dep1 (c1 int);\nSELECT public.create_hypertable('dep1', 'c1', chunk_time_interval=>2);\n       create_hypertable        \n--------------------------------\n (31,regress_rls_schema,dep1,t)\n\nCREATE TABLE dep2 (c1 int);\nSELECT public.create_hypertable('dep2', 'c1', chunk_time_interval=>2);\n       create_hypertable        \n--------------------------------\n (32,regress_rls_schema,dep2,t)\n\nCREATE POLICY dep_p1 ON dep1 TO regress_rls_bob USING (c1 > (select max(dep2.c1) from dep2));\nALTER POLICY dep_p1 ON dep1 TO regress_rls_bob,regress_rls_carol;\n-- Should return one\nSELECT count(*) = 1 FROM pg_depend\n\t\t\t\t   WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1')\n\t\t\t\t\t AND refobjid = (SELECT oid FROM pg_class WHERE relname = 'dep2');\n ?column? \n----------\n t\n\nALTER POLICY dep_p1 ON dep1 USING (true);\n-- Should return one\nSELECT count(*) = 1 FROM pg_shdepend\n\t\t\t\t   WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1')\n\t\t\t\t\t AND refobjid = (SELECT oid FROM pg_authid WHERE rolname = 'regress_rls_bob');\n ?column? \n----------\n t\n\n-- Should return one\nSELECT count(*) = 1 FROM pg_shdepend\n\t\t\t\t   WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1')\n\t\t\t\t\t AND refobjid = (SELECT oid FROM pg_authid WHERE rolname = 'regress_rls_carol');\n ?column? \n----------\n t\n\n-- Should return zero\nSELECT count(*) = 0 FROM pg_depend\n\t\t\t\t   WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1')\n\t\t\t\t\t AND refobjid = (SELECT oid FROM pg_class WHERE relname = 'dep2');\n ?column? \n----------\n t\n\n-- DROP OWNED BY testing\nRESET SESSION AUTHORIZATION;\nCREATE ROLE regress_rls_dob_role1;\nCREATE ROLE regress_rls_dob_role2;\nCREATE TABLE dob_t1 (c1 int);\nSELECT public.create_hypertable('dob_t1', 'c1', chunk_time_interval=>2);\n        create_hypertable         \n----------------------------------\n (33,regress_rls_schema,dob_t1,t)\n\nCREATE TABLE dob_t2 (c1 int) PARTITION BY RANGE (c1);\nCREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1 USING (true);\nDROP OWNED BY regress_rls_dob_role1;\nDROP POLICY p1 ON dob_t1; -- should fail, already gone\nERROR:  policy \"p1\" for table \"dob_t1\" does not exist\nCREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1,regress_rls_dob_role2 USING (true);\nDROP OWNED BY regress_rls_dob_role1;\nDROP POLICY p1 ON dob_t1; -- should succeed\nCREATE POLICY p1 ON dob_t2 TO regress_rls_dob_role1,regress_rls_dob_role2 USING (true);\nDROP OWNED BY regress_rls_dob_role1;\nDROP POLICY p1 ON dob_t2; -- should succeed\nDROP USER regress_rls_dob_role1;\nDROP USER regress_rls_dob_role2;\n--\n-- Clean up objects\n--\nRESET SESSION AUTHORIZATION;\n\\set VERBOSITY terse \\\\ -- suppress cascade details\nDROP SCHEMA regress_rls_schema CASCADE;\nNOTICE:  drop cascades to 116 other objects\n\\set VERBOSITY default\nDROP USER regress_rls_alice;\nDROP USER regress_rls_bob;\nDROP USER regress_rls_carol;\nDROP USER regress_rls_dave;\nDROP USER regress_rls_exempt_user;\nDROP ROLE regress_rls_group1;\nDROP ROLE regress_rls_group2;\n-- Arrange to have a few policies left over, for testing\n-- pg_dump/pg_restore\nCREATE SCHEMA regress_rls_schema;\nCREATE TABLE rls_tbl (c1 int);\nSELECT public.create_hypertable('rls_tbl', 'c1', chunk_time_interval=>2);\n         create_hypertable         \n-----------------------------------\n (34,regress_rls_schema,rls_tbl,t)\n\nALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;\nCREATE POLICY p1 ON rls_tbl USING (c1 > 5);\nCREATE POLICY p2 ON rls_tbl FOR SELECT USING (c1 <= 3);\nCREATE POLICY p3 ON rls_tbl FOR UPDATE USING (c1 <= 3) WITH CHECK (c1 > 5);\nCREATE POLICY p4 ON rls_tbl FOR DELETE USING (c1 <= 3);\nCREATE TABLE rls_tbl_force (c1 int);\nSELECT public.create_hypertable('rls_tbl_force', 'c1', chunk_time_interval=>2);\n            create_hypertable            \n-----------------------------------------\n (35,regress_rls_schema,rls_tbl_force,t)\n\nALTER TABLE rls_tbl_force ENABLE ROW LEVEL SECURITY;\nALTER TABLE rls_tbl_force FORCE ROW LEVEL SECURITY;\nCREATE POLICY p1 ON rls_tbl_force USING (c1 = 5) WITH CHECK (c1 < 5);\nCREATE POLICY p2 ON rls_tbl_force FOR SELECT USING (c1 = 8);\nCREATE POLICY p3 ON rls_tbl_force FOR UPDATE USING (c1 = 8) WITH CHECK (c1 >= 5);\nCREATE POLICY p4 ON rls_tbl_force FOR DELETE USING (c1 = 8);\n"
  },
  {
    "path": "test/expected/rowsecurity-16.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n--\n-- Test of Row-level security feature\n--\n-- Clean up in case a prior regression run failed\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\\set ON_ERROR_STOP 0\n\\set VERBOSITY default\nSET timescaledb.enable_constraint_exclusion TO off;\n-- Suppress NOTICE messages when users/groups don't exist\nSET client_min_messages TO 'warning';\nDROP USER IF EXISTS regress_rls_alice;\nDROP USER IF EXISTS regress_rls_bob;\nDROP USER IF EXISTS regress_rls_carol;\nDROP USER IF EXISTS regress_rls_dave;\nDROP USER IF EXISTS regress_rls_exempt_user;\nDROP ROLE IF EXISTS regress_rls_group1;\nDROP ROLE IF EXISTS regress_rls_group2;\nDROP SCHEMA IF EXISTS regress_rls_schema CASCADE;\nRESET client_min_messages;\n-- initial setup\nCREATE USER regress_rls_alice NOLOGIN;\nCREATE USER regress_rls_bob NOLOGIN;\nCREATE USER regress_rls_carol NOLOGIN;\nCREATE USER regress_rls_dave NOLOGIN;\nCREATE USER regress_rls_exempt_user BYPASSRLS NOLOGIN;\nCREATE ROLE regress_rls_group1 NOLOGIN;\nCREATE ROLE regress_rls_group2 NOLOGIN;\nGRANT regress_rls_group1 TO regress_rls_bob;\nGRANT regress_rls_group2 TO regress_rls_carol;\nCREATE SCHEMA regress_rls_schema;\nGRANT ALL ON SCHEMA regress_rls_schema to public;\nSET search_path = regress_rls_schema;\n-- setup of malicious function\nCREATE OR REPLACE FUNCTION f_leak(text) RETURNS bool\n    COST 0.0000001 LANGUAGE plpgsql\n    AS 'BEGIN RAISE NOTICE ''f_leak => %'', $1; RETURN true; END';\nGRANT EXECUTE ON FUNCTION f_leak(text) TO public;\n-- BASIC Row-Level Security Scenario\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE uaccount (\n    pguser      name primary key,\n    seclv       int\n);\nGRANT SELECT ON uaccount TO public;\nINSERT INTO uaccount VALUES\n    ('regress_rls_alice', 99),\n    ('regress_rls_bob', 1),\n    ('regress_rls_carol', 2),\n    ('regress_rls_dave', 3);\nCREATE TABLE category (\n    cid        int primary key,\n    cname      text\n);\nGRANT ALL ON category TO public;\nINSERT INTO category VALUES\n    (11, 'novel'),\n    (22, 'science fiction'),\n    (33, 'technology'),\n    (44, 'manga');\nCREATE TABLE document (\n    did         int primary key,\n    cid         int references category(cid),\n    dlevel      int not null,\n    dauthor     name,\n    dtitle      text\n);\nGRANT ALL ON document TO public;\nSELECT public.create_hypertable('document', 'did', chunk_time_interval=>2);\n         create_hypertable         \n-----------------------------------\n (1,regress_rls_schema,document,t)\n\nINSERT INTO document VALUES\n    ( 1, 11, 1, 'regress_rls_bob', 'my first novel'),\n    ( 2, 11, 2, 'regress_rls_bob', 'my second novel'),\n    ( 3, 22, 2, 'regress_rls_bob', 'my science fiction'),\n    ( 4, 44, 1, 'regress_rls_bob', 'my first manga'),\n    ( 5, 44, 2, 'regress_rls_bob', 'my second manga'),\n    ( 6, 22, 1, 'regress_rls_carol', 'great science fiction'),\n    ( 7, 33, 2, 'regress_rls_carol', 'great technology book'),\n    ( 8, 44, 1, 'regress_rls_carol', 'great manga'),\n    ( 9, 22, 1, 'regress_rls_dave', 'awesome science fiction'),\n    (10, 33, 2, 'regress_rls_dave', 'awesome technology book');\nALTER TABLE document ENABLE ROW LEVEL SECURITY;\n-- user's security level must be higher than or equal to document's\nCREATE POLICY p1 ON document AS PERMISSIVE\n    USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));\n-- try to create a policy of bogus type\nCREATE POLICY p1 ON document AS UGLY\n    USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));\nERROR:  unrecognized row security option \"ugly\"\nLINE 1: CREATE POLICY p1 ON document AS UGLY\n                                        ^\nHINT:  Only PERMISSIVE or RESTRICTIVE policies are supported currently.\n-- but Dave isn't allowed to anything at cid 50 or above\n-- this is to make sure that we sort the policies by name first\n-- when applying WITH CHECK, a later INSERT by Dave should fail due\n-- to p1r first\nCREATE POLICY p2r ON document AS RESTRICTIVE TO regress_rls_dave\n    USING (cid <> 44 AND cid < 50);\n-- and Dave isn't allowed to see manga documents\nCREATE POLICY p1r ON document AS RESTRICTIVE TO regress_rls_dave\n    USING (cid <> 44);\n\\dp\n                                                                  Access privileges\n       Schema       |   Name   | Type  |              Access privileges              | Column privileges |                  Policies                  \n--------------------+----------+-------+---------------------------------------------+-------------------+--------------------------------------------\n regress_rls_schema | category | table | regress_rls_alice=arwdDxt/regress_rls_alice+|                   | \n                    |          |       | =arwdDxt/regress_rls_alice                  |                   | \n regress_rls_schema | document | table | regress_rls_alice=arwdDxt/regress_rls_alice+|                   | p1:                                       +\n                    |          |       | =arwdDxt/regress_rls_alice                  |                   |   (u): (dlevel <= ( SELECT uaccount.seclv +\n                    |          |       |                                             |                   |    FROM uaccount                          +\n                    |          |       |                                             |                   |   WHERE (uaccount.pguser = CURRENT_USER)))+\n                    |          |       |                                             |                   | p2r (RESTRICTIVE):                        +\n                    |          |       |                                             |                   |   (u): ((cid <> 44) AND (cid < 50))       +\n                    |          |       |                                             |                   |   to: regress_rls_dave                    +\n                    |          |       |                                             |                   | p1r (RESTRICTIVE):                        +\n                    |          |       |                                             |                   |   (u): (cid <> 44)                        +\n                    |          |       |                                             |                   |   to: regress_rls_dave\n regress_rls_schema | uaccount | table | regress_rls_alice=arwdDxt/regress_rls_alice+|                   | \n                    |          |       | =r/regress_rls_alice                        |                   | \n\n\\d document\n        Table \"regress_rls_schema.document\"\n Column  |  Type   | Collation | Nullable | Default \n---------+---------+-----------+----------+---------\n did     | integer |           | not null | \n cid     | integer |           |          | \n dlevel  | integer |           | not null | \n dauthor | name    |           |          | \n dtitle  | text    |           |          | \nIndexes:\n    \"document_pkey\" PRIMARY KEY, btree (did)\nForeign-key constraints:\n    \"document_cid_fkey\" FOREIGN KEY (cid) REFERENCES category(cid)\nPolicies:\n    POLICY \"p1\"\n      USING ((dlevel <= ( SELECT uaccount.seclv\n   FROM uaccount\n  WHERE (uaccount.pguser = CURRENT_USER))))\n    POLICY \"p1r\" AS RESTRICTIVE\n      TO regress_rls_dave\n      USING ((cid <> 44))\n    POLICY \"p2r\" AS RESTRICTIVE\n      TO regress_rls_dave\n      USING (((cid <> 44) AND (cid < 50)))\nNumber of child tables: 6 (Use \\d+ to list them.)\n\nSELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename = 'document' ORDER BY policyname;\n     schemaname     | tablename | policyname | permissive  |       roles        | cmd |                    qual                    | with_check \n--------------------+-----------+------------+-------------+--------------------+-----+--------------------------------------------+------------\n regress_rls_schema | document  | p1         | PERMISSIVE  | {public}           | ALL | (dlevel <= ( SELECT uaccount.seclv        +| \n                    |           |            |             |                    |     |    FROM uaccount                          +| \n                    |           |            |             |                    |     |   WHERE (uaccount.pguser = CURRENT_USER))) | \n regress_rls_schema | document  | p1r        | RESTRICTIVE | {regress_rls_dave} | ALL | (cid <> 44)                                | \n regress_rls_schema | document  | p2r        | RESTRICTIVE | {regress_rls_dave} | ALL | ((cid <> 44) AND (cid < 50))               | \n\n-- viewpoint from regress_rls_bob\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO ON;\nSELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my first manga\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great manga\nNOTICE:  f_leak => awesome science fiction\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n\nSELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my first manga\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great manga\nNOTICE:  f_leak => awesome science fiction\n cid | did | dlevel |      dauthor      |         dtitle          |      cname      \n-----+-----+--------+-------------------+-------------------------+-----------------\n  11 |   1 |      1 | regress_rls_bob   | my first novel          | novel\n  44 |   4 |      1 | regress_rls_bob   | my first manga          | manga\n  22 |   6 |      1 | regress_rls_carol | great science fiction   | science fiction\n  44 |   8 |      1 | regress_rls_carol | great manga             | manga\n  22 |   9 |      1 | regress_rls_dave  | awesome science fiction | science fiction\n\n-- try a sampled version\nSELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0)\n  WHERE f_leak(dtitle) ORDER BY did;\n did | cid | dlevel | dauthor | dtitle \n-----+-----+--------+---------+--------\n\n-- viewpoint from regress_rls_carol\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science fiction\nNOTICE:  f_leak => my first manga\nNOTICE:  f_leak => my second manga\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great manga\nNOTICE:  f_leak => awesome science fiction\nNOTICE:  f_leak => awesome technology book\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   5 |  44 |      2 | regress_rls_bob   | my second manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n\nSELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science fiction\nNOTICE:  f_leak => my first manga\nNOTICE:  f_leak => my second manga\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great manga\nNOTICE:  f_leak => awesome science fiction\nNOTICE:  f_leak => awesome technology book\n cid | did | dlevel |      dauthor      |         dtitle          |      cname      \n-----+-----+--------+-------------------+-------------------------+-----------------\n  11 |   1 |      1 | regress_rls_bob   | my first novel          | novel\n  11 |   2 |      2 | regress_rls_bob   | my second novel         | novel\n  22 |   3 |      2 | regress_rls_bob   | my science fiction      | science fiction\n  44 |   4 |      1 | regress_rls_bob   | my first manga          | manga\n  44 |   5 |      2 | regress_rls_bob   | my second manga         | manga\n  22 |   6 |      1 | regress_rls_carol | great science fiction   | science fiction\n  33 |   7 |      2 | regress_rls_carol | great technology book   | technology\n  44 |   8 |      1 | regress_rls_carol | great manga             | manga\n  22 |   9 |      1 | regress_rls_dave  | awesome science fiction | science fiction\n  33 |  10 |      2 | regress_rls_dave  | awesome technology book | technology\n\n-- try a sampled version\nSELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0)\n  WHERE f_leak(dtitle) ORDER BY did;\n did | cid | dlevel | dauthor | dtitle \n-----+-----+--------+---------+--------\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on document\n   Chunks excluded during startup: 0\n   InitPlan 1 (returns $0)\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on document document_1\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_1_chunk document_2\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_2_chunk document_3\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_3_chunk document_4\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_4_chunk document_5\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_5_chunk document_6\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_6_chunk document_7\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Hash Join\n   Hash Cond: (document.cid = category.cid)\n   InitPlan 1 (returns $0)\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Custom Scan (ChunkAppend) on document\n         Chunks excluded during startup: 0\n         ->  Seq Scan on document document_1\n               Filter: ((dlevel <= $0) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_1_chunk document_2\n               Filter: ((dlevel <= $0) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_2_chunk document_3\n               Filter: ((dlevel <= $0) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_3_chunk document_4\n               Filter: ((dlevel <= $0) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_4_chunk document_5\n               Filter: ((dlevel <= $0) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_5_chunk document_6\n               Filter: ((dlevel <= $0) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_6_chunk document_7\n               Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Hash\n         ->  Seq Scan on category\n\n-- viewpoint from regress_rls_dave\nSET SESSION AUTHORIZATION regress_rls_dave;\nSELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science fiction\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => awesome science fiction\nNOTICE:  f_leak => awesome technology book\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n\nSELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science fiction\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => awesome science fiction\nNOTICE:  f_leak => awesome technology book\n cid | did | dlevel |      dauthor      |         dtitle          |      cname      \n-----+-----+--------+-------------------+-------------------------+-----------------\n  11 |   1 |      1 | regress_rls_bob   | my first novel          | novel\n  11 |   2 |      2 | regress_rls_bob   | my second novel         | novel\n  22 |   3 |      2 | regress_rls_bob   | my science fiction      | science fiction\n  22 |   6 |      1 | regress_rls_carol | great science fiction   | science fiction\n  33 |   7 |      2 | regress_rls_carol | great technology book   | technology\n  22 |   9 |      1 | regress_rls_dave  | awesome science fiction | science fiction\n  33 |  10 |      2 | regress_rls_dave  | awesome technology book | technology\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on document\n   Chunks excluded during startup: 0\n   InitPlan 1 (returns $0)\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on document document_1\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_1_chunk document_2\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_2_chunk document_3\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_3_chunk document_4\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_4_chunk document_5\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_5_chunk document_6\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_6_chunk document_7\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Hash Join\n   Hash Cond: (category.cid = document.cid)\n   InitPlan 1 (returns $0)\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on category\n   ->  Hash\n         ->  Custom Scan (ChunkAppend) on document\n               Chunks excluded during startup: 0\n               ->  Seq Scan on document document_1\n                     Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n               ->  Seq Scan on _hyper_1_1_chunk document_2\n                     Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n               ->  Seq Scan on _hyper_1_2_chunk document_3\n                     Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n               ->  Seq Scan on _hyper_1_3_chunk document_4\n                     Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n               ->  Seq Scan on _hyper_1_4_chunk document_5\n                     Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n               ->  Seq Scan on _hyper_1_5_chunk document_6\n                     Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n               ->  Seq Scan on _hyper_1_6_chunk document_7\n                     Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))\n\n-- 44 would technically fail for both p2r and p1r, but we should get an error\n-- back from p1r for this because it sorts first\nINSERT INTO document VALUES (100, 44, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail\nERROR:  new row violates row-level security policy \"p1r\" for table \"document\"\n-- Just to see a p2r error\nINSERT INTO document VALUES (100, 55, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail\nERROR:  new row violates row-level security policy \"p2r\" for table \"document\"\n-- only owner can change policies\nALTER POLICY p1 ON document USING (true);    --fail\nERROR:  must be owner of table document\nDROP POLICY p1 ON document;                  --fail\nERROR:  must be owner of relation document\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER POLICY p1 ON document USING (dauthor = current_user);\n-- viewpoint from regress_rls_bob again\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science fiction\nNOTICE:  f_leak => my first manga\nNOTICE:  f_leak => my second manga\n did | cid | dlevel |     dauthor     |       dtitle       \n-----+-----+--------+-----------------+--------------------\n   1 |  11 |      1 | regress_rls_bob | my first novel\n   2 |  11 |      2 | regress_rls_bob | my second novel\n   3 |  22 |      2 | regress_rls_bob | my science fiction\n   4 |  44 |      1 | regress_rls_bob | my first manga\n   5 |  44 |      2 | regress_rls_bob | my second manga\n\nSELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science fiction\nNOTICE:  f_leak => my first manga\nNOTICE:  f_leak => my second manga\n cid | did | dlevel |     dauthor     |       dtitle       |      cname      \n-----+-----+--------+-----------------+--------------------+-----------------\n  11 |   1 |      1 | regress_rls_bob | my first novel     | novel\n  11 |   2 |      2 | regress_rls_bob | my second novel    | novel\n  22 |   3 |      2 | regress_rls_bob | my science fiction | science fiction\n  44 |   4 |      1 | regress_rls_bob | my first manga     | manga\n  44 |   5 |      2 | regress_rls_bob | my second manga    | manga\n\n-- viewpoint from rls_regres_carol again\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great manga\n did | cid | dlevel |      dauthor      |        dtitle         \n-----+-----+--------+-------------------+-----------------------\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n\nSELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did;\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great manga\n cid | did | dlevel |      dauthor      |        dtitle         |      cname      \n-----+-----+--------+-------------------+-----------------------+-----------------\n  22 |   6 |      1 | regress_rls_carol | great science fiction | science fiction\n  33 |   7 |      2 | regress_rls_carol | great technology book | technology\n  44 |   8 |      1 | regress_rls_carol | great manga           | manga\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on document\n   Chunks excluded during startup: 0\n   ->  Seq Scan on document document_1\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_1_chunk document_2\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_2_chunk document_3\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_3_chunk document_4\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_4_chunk document_5\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_5_chunk document_6\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_6_chunk document_7\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Nested Loop\n   ->  Custom Scan (ChunkAppend) on document\n         Chunks excluded during startup: 0\n         ->  Seq Scan on document document_1\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_1_chunk document_2\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_2_chunk document_3\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_3_chunk document_4\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_4_chunk document_5\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_5_chunk document_6\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_6_chunk document_7\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Index Scan using category_pkey on category\n         Index Cond: (cid = document.cid)\n\n-- interaction of FK/PK constraints\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE POLICY p2 ON category\n    USING (CASE WHEN current_user = 'regress_rls_bob' THEN cid IN (11, 33)\n           WHEN current_user = 'regress_rls_carol' THEN cid IN (22, 44)\n           ELSE false END);\nALTER TABLE category ENABLE ROW LEVEL SECURITY;\n-- cannot delete PK referenced by invisible FK\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid ORDER BY d.did, c.cid;\n did | cid | dlevel |     dauthor     |       dtitle       | cid |   cname    \n-----+-----+--------+-----------------+--------------------+-----+------------\n   1 |  11 |      1 | regress_rls_bob | my first novel     |  11 | novel\n   2 |  11 |      2 | regress_rls_bob | my second novel    |  11 | novel\n   3 |  22 |      2 | regress_rls_bob | my science fiction |     | \n   4 |  44 |      1 | regress_rls_bob | my first manga     |     | \n   5 |  44 |      2 | regress_rls_bob | my second manga    |     | \n     |     |        |                 |                    |  33 | technology\n\n\\set VERBOSITY sqlstate\nDELETE FROM category WHERE cid = 33;    -- fails with FK violation\nERROR:  23503\n\\set VERBOSITY default\n-- can insert FK referencing invisible PK\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid ORDER BY d.did, c.cid;\n did | cid | dlevel |      dauthor      |        dtitle         | cid |      cname      \n-----+-----+--------+-------------------+-----------------------+-----+-----------------\n   6 |  22 |      1 | regress_rls_carol | great science fiction |  22 | science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book |     | \n   8 |  44 |      1 | regress_rls_carol | great manga           |  44 | manga\n\nINSERT INTO document VALUES (11, 33, 1, current_user, 'hoge');\n-- UNIQUE or PRIMARY KEY constraint violation DOES reveal presence of row\nSET SESSION AUTHORIZATION regress_rls_bob;\nINSERT INTO document VALUES (8, 44, 1, 'regress_rls_bob', 'my third manga'); -- Must fail with unique violation, revealing presence of did we can't see\nERROR:  duplicate key value violates unique constraint \"5_10_document_pkey\"\nDETAIL:  Key (did)=(8) already exists.\nSELECT * FROM document WHERE did = 8; -- and confirm we can't see it\n did | cid | dlevel | dauthor | dtitle \n-----+-----+--------+---------+--------\n\n-- RLS policies are checked before constraints\nINSERT INTO document VALUES (8, 44, 1, 'regress_rls_carol', 'my third manga'); -- Should fail with RLS check violation, not duplicate key violation\nERROR:  new row violates row-level security policy for table \"document\"\nUPDATE document SET did = 8, dauthor = 'regress_rls_carol' WHERE did = 5; -- Should fail with RLS check violation, not duplicate key violation\nERROR:  new row violates row-level security policy for table \"document\"\n-- database superuser does bypass RLS policy when enabled\nRESET SESSION AUTHORIZATION;\nSET row_security TO ON;\nSELECT * FROM document;\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   5 |  44 |      2 | regress_rls_bob   | my second manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n  11 |  33 |      1 | regress_rls_carol | hoge\n\nSELECT * FROM category;\n cid |      cname      \n-----+-----------------\n  11 | novel\n  22 | science fiction\n  33 | technology\n  44 | manga\n\n-- database superuser does bypass RLS policy when disabled\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nSELECT * FROM document;\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   5 |  44 |      2 | regress_rls_bob   | my second manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n  11 |  33 |      1 | regress_rls_carol | hoge\n\nSELECT * FROM category;\n cid |      cname      \n-----+-----------------\n  11 | novel\n  22 | science fiction\n  33 | technology\n  44 | manga\n\n-- database non-superuser with bypass privilege can bypass RLS policy when disabled\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO OFF;\nSELECT * FROM document;\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   5 |  44 |      2 | regress_rls_bob   | my second manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n  11 |  33 |      1 | regress_rls_carol | hoge\n\nSELECT * FROM category;\n cid |      cname      \n-----+-----------------\n  11 | novel\n  22 | science fiction\n  33 | technology\n  44 | manga\n\n-- RLS policy does not apply to table owner when RLS enabled.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO ON;\nSELECT * FROM document;\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   5 |  44 |      2 | regress_rls_bob   | my second manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n  11 |  33 |      1 | regress_rls_carol | hoge\n\nSELECT * FROM category;\n cid |      cname      \n-----+-----------------\n  11 | novel\n  22 | science fiction\n  33 | technology\n  44 | manga\n\n-- RLS policy does not apply to table owner when RLS disabled.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO OFF;\nSELECT * FROM document;\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   5 |  44 |      2 | regress_rls_bob   | my second manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n  11 |  33 |      1 | regress_rls_carol | hoge\n\nSELECT * FROM category;\n cid |      cname      \n-----+-----------------\n  11 | novel\n  22 | science fiction\n  33 | technology\n  44 | manga\n\n--\n-- Table inheritance and RLS policy\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO ON;\nCREATE TABLE t1 (a int, junk1 text, b text);\nALTER TABLE t1 DROP COLUMN junk1;    -- just a disturbing factor\nGRANT ALL ON t1 TO public;\nCOPY t1 FROM stdin;\nCREATE TABLE t2 (c float) INHERITS (t1);\nGRANT ALL ON t2 TO public;\nCOPY t2 FROM stdin;\nCREATE TABLE t3 (c text, b text, a int);\nALTER TABLE t3 INHERIT t1;\nGRANT ALL ON t3 TO public;\nCOPY t3(a,b,c) FROM stdin;\nCREATE POLICY p1 ON t1 FOR ALL TO PUBLIC USING (a % 2 = 0); -- be even number\nCREATE POLICY p2 ON t2 FOR ALL TO PUBLIC USING (a % 2 = 1); -- be odd number\nALTER TABLE t1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE t2 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM t1;\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n 2 | bcd\n 4 | def\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: ((a % 2) = 0)\n   ->  Seq Scan on t2 t1_2\n         Filter: ((a % 2) = 0)\n   ->  Seq Scan on t3 t1_3\n         Filter: ((a % 2) = 0)\n\nSELECT * FROM t1 WHERE f_leak(b);\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => def\nNOTICE:  f_leak => yyy\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n 2 | bcd\n 4 | def\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on t2 t1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on t3 t1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\n-- reference to system column\nSELECT ctid, * FROM t1;\n ctid  | a |  b  \n-------+---+-----\n (0,2) | 2 | bbb\n (0,4) | 4 | dad\n (0,2) | 2 | bcd\n (0,4) | 4 | def\n (0,2) | 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT *, t1 FROM t1;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: ((a % 2) = 0)\n   ->  Seq Scan on t2 t1_2\n         Filter: ((a % 2) = 0)\n   ->  Seq Scan on t3 t1_3\n         Filter: ((a % 2) = 0)\n\n-- reference to whole-row reference\nSELECT *, t1 FROM t1;\n a |  b  |   t1    \n---+-----+---------\n 2 | bbb | (2,bbb)\n 4 | dad | (4,dad)\n 2 | bcd | (2,bcd)\n 4 | def | (4,def)\n 2 | yyy | (2,yyy)\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT *, t1 FROM t1;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: ((a % 2) = 0)\n   ->  Seq Scan on t2 t1_2\n         Filter: ((a % 2) = 0)\n   ->  Seq Scan on t3 t1_3\n         Filter: ((a % 2) = 0)\n\n-- for share/update lock\nSELECT * FROM t1 FOR SHARE;\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n 2 | bcd\n 4 | def\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 FOR SHARE;\n--- QUERY PLAN ---\n LockRows\n   ->  Append\n         ->  Seq Scan on t1 t1_1\n               Filter: ((a % 2) = 0)\n         ->  Seq Scan on t2 t1_2\n               Filter: ((a % 2) = 0)\n         ->  Seq Scan on t3 t1_3\n               Filter: ((a % 2) = 0)\n\nSELECT * FROM t1 WHERE f_leak(b) FOR SHARE;\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => def\nNOTICE:  f_leak => yyy\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n 2 | bcd\n 4 | def\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;\n--- QUERY PLAN ---\n LockRows\n   ->  Append\n         ->  Seq Scan on t1 t1_1\n               Filter: (((a % 2) = 0) AND f_leak(b))\n         ->  Seq Scan on t2 t1_2\n               Filter: (((a % 2) = 0) AND f_leak(b))\n         ->  Seq Scan on t3 t1_3\n               Filter: (((a % 2) = 0) AND f_leak(b))\n\n-- union all query\nSELECT a, b, ctid FROM t2 UNION ALL SELECT a, b, ctid FROM t3;\n a |  b  | ctid  \n---+-----+-------\n 1 | abc | (0,1)\n 3 | cde | (0,3)\n 1 | xxx | (0,1)\n 2 | yyy | (0,2)\n 3 | zzz | (0,3)\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT a, b, ctid FROM t2 UNION ALL SELECT a, b, ctid FROM t3;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t2\n         Filter: ((a % 2) = 1)\n   ->  Seq Scan on t3\n\n-- superuser is allowed to bypass RLS checks\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nSELECT * FROM t1 WHERE f_leak(b);\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => ccc\nNOTICE:  f_leak => dad\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => def\nNOTICE:  f_leak => xxx\nNOTICE:  f_leak => yyy\nNOTICE:  f_leak => zzz\n a |  b  \n---+-----\n 1 | aba\n 2 | bbb\n 3 | ccc\n 4 | dad\n 1 | abc\n 2 | bcd\n 3 | cde\n 4 | def\n 1 | xxx\n 2 | yyy\n 3 | zzz\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: f_leak(b)\n   ->  Seq Scan on t2 t1_2\n         Filter: f_leak(b)\n   ->  Seq Scan on t3 t1_3\n         Filter: f_leak(b)\n\n-- non-superuser with bypass privilege can bypass RLS policy when disabled\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO OFF;\nSELECT * FROM t1 WHERE f_leak(b);\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => ccc\nNOTICE:  f_leak => dad\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => def\nNOTICE:  f_leak => xxx\nNOTICE:  f_leak => yyy\nNOTICE:  f_leak => zzz\n a |  b  \n---+-----\n 1 | aba\n 2 | bbb\n 3 | ccc\n 4 | dad\n 1 | abc\n 2 | bcd\n 3 | cde\n 4 | def\n 1 | xxx\n 2 | yyy\n 3 | zzz\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: f_leak(b)\n   ->  Seq Scan on t2 t1_2\n         Filter: f_leak(b)\n   ->  Seq Scan on t3 t1_3\n         Filter: f_leak(b)\n\n--\n-- Hyper Tables\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE hyper_document (\n    did         int,\n    cid         int,\n    dlevel      int not null,\n    dauthor     name,\n    dtitle      text\n);\nGRANT ALL ON hyper_document TO public;\nSELECT public.create_hypertable('hyper_document', 'did', chunk_time_interval=>2);\n            create_hypertable            \n-----------------------------------------\n (2,regress_rls_schema,hyper_document,t)\n\nINSERT INTO hyper_document VALUES\n    ( 1, 11, 1, 'regress_rls_bob', 'my first novel'),\n    ( 2, 11, 2, 'regress_rls_bob', 'my second novel'),\n    ( 3, 99, 2, 'regress_rls_bob', 'my science textbook'),\n    ( 4, 55, 1, 'regress_rls_bob', 'my first satire'),\n    ( 5, 99, 2, 'regress_rls_bob', 'my history book'),\n    ( 6, 11, 1, 'regress_rls_carol', 'great science fiction'),\n    ( 7, 99, 2, 'regress_rls_carol', 'great technology book'),\n    ( 8, 55, 2, 'regress_rls_carol', 'great satire'),\n    ( 9, 11, 1, 'regress_rls_dave', 'awesome science fiction'),\n    (10, 99, 2, 'regress_rls_dave', 'awesome technology book');\nALTER TABLE hyper_document ENABLE ROW LEVEL SECURITY;\nGRANT ALL ON _timescaledb_internal._hyper_2_9_chunk TO public;\n-- Create policy on parent\n-- user's security level must be higher than or equal to document's\nCREATE POLICY pp1 ON hyper_document AS PERMISSIVE\n    USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));\n-- Dave is only allowed to see cid < 55\nCREATE POLICY pp1r ON hyper_document AS RESTRICTIVE TO regress_rls_dave\n    USING (cid < 55);\n\\d+ hyper_document\n                         Table \"regress_rls_schema.hyper_document\"\n Column  |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description \n---------+---------+-----------+----------+---------+----------+--------------+-------------\n did     | integer |           | not null |         | plain    |              | \n cid     | integer |           |          |         | plain    |              | \n dlevel  | integer |           | not null |         | plain    |              | \n dauthor | name    |           |          |         | plain    |              | \n dtitle  | text    |           |          |         | extended |              | \nIndexes:\n    \"hyper_document_did_idx\" btree (did DESC)\nPolicies:\n    POLICY \"pp1\"\n      USING ((dlevel <= ( SELECT uaccount.seclv\n   FROM uaccount\n  WHERE (uaccount.pguser = CURRENT_USER))))\n    POLICY \"pp1r\" AS RESTRICTIVE\n      TO regress_rls_dave\n      USING ((cid < 55))\nChild tables: _timescaledb_internal._hyper_2_10_chunk,\n              _timescaledb_internal._hyper_2_11_chunk,\n              _timescaledb_internal._hyper_2_12_chunk,\n              _timescaledb_internal._hyper_2_13_chunk,\n              _timescaledb_internal._hyper_2_14_chunk,\n              _timescaledb_internal._hyper_2_9_chunk\n\nSELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename like '%hyper_document%' ORDER BY policyname;\n     schemaname     |   tablename    | policyname | permissive  |       roles        | cmd |                    qual                    | with_check \n--------------------+----------------+------------+-------------+--------------------+-----+--------------------------------------------+------------\n regress_rls_schema | hyper_document | pp1        | PERMISSIVE  | {public}           | ALL | (dlevel <= ( SELECT uaccount.seclv        +| \n                    |                |            |             |                    |     |    FROM uaccount                          +| \n                    |                |            |             |                    |     |   WHERE (uaccount.pguser = CURRENT_USER))) | \n regress_rls_schema | hyper_document | pp1r       | RESTRICTIVE | {regress_rls_dave} | ALL | (cid < 55)                                 | \n\n-- viewpoint from regress_rls_bob\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO ON;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my first satire\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => awesome science fiction\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   4 |  55 |      1 | regress_rls_bob   | my first satire\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on hyper_document\n   Chunks excluded during startup: 0\n   InitPlan 1 (returns $0)\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on hyper_document hyper_document_1\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_9_chunk hyper_document_2\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_10_chunk hyper_document_3\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_11_chunk hyper_document_4\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_12_chunk hyper_document_5\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_13_chunk hyper_document_6\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_14_chunk hyper_document_7\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n\n-- viewpoint from regress_rls_carol\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science textbook\nNOTICE:  f_leak => my first satire\nNOTICE:  f_leak => my history book\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great satire\nNOTICE:  f_leak => awesome science fiction\nNOTICE:  f_leak => awesome technology book\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  99 |      2 | regress_rls_bob   | my science textbook\n   4 |  55 |      1 | regress_rls_bob   | my first satire\n   5 |  99 |      2 | regress_rls_bob   | my history book\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   7 |  99 |      2 | regress_rls_carol | great technology book\n   8 |  55 |      2 | regress_rls_carol | great satire\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  99 |      2 | regress_rls_dave  | awesome technology book\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on hyper_document\n   Chunks excluded during startup: 0\n   InitPlan 1 (returns $0)\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on hyper_document hyper_document_1\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_9_chunk hyper_document_2\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_10_chunk hyper_document_3\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_11_chunk hyper_document_4\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_12_chunk hyper_document_5\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_13_chunk hyper_document_6\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_14_chunk hyper_document_7\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n\n-- viewpoint from regress_rls_dave\nSET SESSION AUTHORIZATION regress_rls_dave;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => awesome science fiction\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on hyper_document\n   Chunks excluded during startup: 0\n   InitPlan 1 (returns $0)\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on hyper_document hyper_document_1\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_9_chunk hyper_document_2\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_10_chunk hyper_document_3\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_11_chunk hyper_document_4\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_12_chunk hyper_document_5\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_13_chunk hyper_document_6\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_14_chunk hyper_document_7\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n\n-- pp1 ERROR\nINSERT INTO hyper_document VALUES (1, 11, 5, 'regress_rls_dave', 'testing pp1'); -- fail\nERROR:  new row violates row-level security policy for table \"hyper_document\"\n-- pp1r ERROR\nINSERT INTO hyper_document VALUES (1, 99, 1, 'regress_rls_dave', 'testing pp1r'); -- fail\nERROR:  new row violates row-level security policy \"pp1r\" for table \"hyper_document\"\n-- Show that RLS policy does not apply for direct inserts to children\n-- This should fail with RLS POLICY pp1r violation.\nINSERT INTO hyper_document VALUES (1, 55, 1, 'regress_rls_dave', 'testing RLS with hypertables'); -- fail\nERROR:  new row violates row-level security policy \"pp1r\" for table \"hyper_document\"\n-- But this should succeed.\nINSERT INTO _timescaledb_internal._hyper_2_9_chunk VALUES (1, 55, 1, 'regress_rls_dave', 'testing RLS with hypertables'); -- success\n-- We still cannot see the row using the parent\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did, cid;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => awesome science fiction\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n\n-- But we can if we look directly\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk WHERE f_leak(dtitle) ORDER BY did, cid;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => testing RLS with hypertables\n did | cid | dlevel |     dauthor      |            dtitle            \n-----+-----+--------+------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob  | my first novel\n   1 |  55 |      1 | regress_rls_dave | testing RLS with hypertables\n\n-- Turn on RLS and create policy on child to show RLS is checked before constraints\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER TABLE _timescaledb_internal._hyper_2_9_chunk ENABLE ROW LEVEL SECURITY;\nCREATE POLICY pp3 ON _timescaledb_internal._hyper_2_9_chunk AS RESTRICTIVE\n    USING (cid < 55);\n-- This should fail with RLS violation now.\nSET SESSION AUTHORIZATION regress_rls_dave;\nINSERT INTO _timescaledb_internal._hyper_2_9_chunk VALUES (1, 55, 1, 'regress_rls_dave', 'testing RLS with hypertables - round 2'); -- fail\nERROR:  new row violates row-level security policy for table \"_hyper_2_9_chunk\"\n-- And now we cannot see directly into the partition either, due to RLS\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk WHERE f_leak(dtitle) ORDER BY did, cid;\n did | cid | dlevel | dauthor | dtitle \n-----+-----+--------+---------+--------\n\n-- The parent looks same as before\n-- viewpoint from regress_rls_dave\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did, cid;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => awesome science fiction\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on hyper_document\n   Chunks excluded during startup: 0\n   InitPlan 1 (returns $0)\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on hyper_document hyper_document_1\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_9_chunk hyper_document_2\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_10_chunk hyper_document_3\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_11_chunk hyper_document_4\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_12_chunk hyper_document_5\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_13_chunk hyper_document_6\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_14_chunk hyper_document_7\n         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))\n\n-- viewpoint from regress_rls_carol\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did, cid;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => testing RLS with hypertables\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science textbook\nNOTICE:  f_leak => my first satire\nNOTICE:  f_leak => my history book\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great satire\nNOTICE:  f_leak => awesome science fiction\nNOTICE:  f_leak => awesome technology book\n did | cid | dlevel |      dauthor      |            dtitle            \n-----+-----+--------+-------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   1 |  55 |      1 | regress_rls_dave  | testing RLS with hypertables\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  99 |      2 | regress_rls_bob   | my science textbook\n   4 |  55 |      1 | regress_rls_bob   | my first satire\n   5 |  99 |      2 | regress_rls_bob   | my history book\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   7 |  99 |      2 | regress_rls_carol | great technology book\n   8 |  55 |      2 | regress_rls_carol | great satire\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  99 |      2 | regress_rls_dave  | awesome technology book\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on hyper_document\n   Chunks excluded during startup: 0\n   InitPlan 1 (returns $0)\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on hyper_document hyper_document_1\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_9_chunk hyper_document_2\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_10_chunk hyper_document_3\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_11_chunk hyper_document_4\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_12_chunk hyper_document_5\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_13_chunk hyper_document_6\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_14_chunk hyper_document_7\n         Filter: ((dlevel <= $0) AND f_leak(dtitle))\n\n-- only owner can change policies\nALTER POLICY pp1 ON hyper_document USING (true);    --fail\nERROR:  must be owner of table hyper_document\nDROP POLICY pp1 ON hyper_document;                  --fail\nERROR:  must be owner of relation hyper_document\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER POLICY pp1 ON hyper_document USING (dauthor = current_user);\n-- viewpoint from regress_rls_bob again\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did, cid;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science textbook\nNOTICE:  f_leak => my first satire\nNOTICE:  f_leak => my history book\n did | cid | dlevel |     dauthor     |       dtitle        \n-----+-----+--------+-----------------+---------------------\n   1 |  11 |      1 | regress_rls_bob | my first novel\n   2 |  11 |      2 | regress_rls_bob | my second novel\n   3 |  99 |      2 | regress_rls_bob | my science textbook\n   4 |  55 |      1 | regress_rls_bob | my first satire\n   5 |  99 |      2 | regress_rls_bob | my history book\n\n-- viewpoint from rls_regres_carol again\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did, cid;\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great satire\n did | cid | dlevel |      dauthor      |        dtitle         \n-----+-----+--------+-------------------+-----------------------\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   7 |  99 |      2 | regress_rls_carol | great technology book\n   8 |  55 |      2 | regress_rls_carol | great satire\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on hyper_document\n   Chunks excluded during startup: 0\n   ->  Seq Scan on hyper_document hyper_document_1\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_9_chunk hyper_document_2\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_10_chunk hyper_document_3\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_11_chunk hyper_document_4\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_12_chunk hyper_document_5\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_13_chunk hyper_document_6\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_14_chunk hyper_document_7\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n\n-- database superuser does bypass RLS policy when enabled\nRESET SESSION AUTHORIZATION;\nSET row_security TO ON;\nSELECT * FROM hyper_document ORDER BY did, cid;\n did | cid | dlevel |      dauthor      |            dtitle            \n-----+-----+--------+-------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   1 |  55 |      1 | regress_rls_dave  | testing RLS with hypertables\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  99 |      2 | regress_rls_bob   | my science textbook\n   4 |  55 |      1 | regress_rls_bob   | my first satire\n   5 |  99 |      2 | regress_rls_bob   | my history book\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   7 |  99 |      2 | regress_rls_carol | great technology book\n   8 |  55 |      2 | regress_rls_carol | great satire\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  99 |      2 | regress_rls_dave  | awesome technology book\n\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk ORDER BY did, cid;\n did | cid | dlevel |     dauthor      |            dtitle            \n-----+-----+--------+------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob  | my first novel\n   1 |  55 |      1 | regress_rls_dave | testing RLS with hypertables\n\n-- database non-superuser with bypass privilege can bypass RLS policy when disabled\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO OFF;\nSELECT * FROM hyper_document ORDER BY did, cid;\n did | cid | dlevel |      dauthor      |            dtitle            \n-----+-----+--------+-------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   1 |  55 |      1 | regress_rls_dave  | testing RLS with hypertables\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  99 |      2 | regress_rls_bob   | my science textbook\n   4 |  55 |      1 | regress_rls_bob   | my first satire\n   5 |  99 |      2 | regress_rls_bob   | my history book\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   7 |  99 |      2 | regress_rls_carol | great technology book\n   8 |  55 |      2 | regress_rls_carol | great satire\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  99 |      2 | regress_rls_dave  | awesome technology book\n\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk ORDER BY did, cid;\n did | cid | dlevel |     dauthor      |            dtitle            \n-----+-----+--------+------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob  | my first novel\n   1 |  55 |      1 | regress_rls_dave | testing RLS with hypertables\n\n-- RLS policy does not apply to table owner when RLS enabled.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO ON;\nSELECT * FROM hyper_document ORDER BY did, cid;\n did | cid | dlevel |      dauthor      |            dtitle            \n-----+-----+--------+-------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   1 |  55 |      1 | regress_rls_dave  | testing RLS with hypertables\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  99 |      2 | regress_rls_bob   | my science textbook\n   4 |  55 |      1 | regress_rls_bob   | my first satire\n   5 |  99 |      2 | regress_rls_bob   | my history book\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   7 |  99 |      2 | regress_rls_carol | great technology book\n   8 |  55 |      2 | regress_rls_carol | great satire\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  99 |      2 | regress_rls_dave  | awesome technology book\n\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk ORDER BY did, cid;\n did | cid | dlevel |     dauthor      |            dtitle            \n-----+-----+--------+------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob  | my first novel\n   1 |  55 |      1 | regress_rls_dave | testing RLS with hypertables\n\n-- When RLS disabled, other users get ERROR.\nSET SESSION AUTHORIZATION regress_rls_dave;\nSET row_security TO OFF;\nSELECT * FROM hyper_document ORDER BY did, cid;\nERROR:  query would be affected by row-level security policy for table \"hyper_document\"\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk ORDER BY did, cid;\nERROR:  query would be affected by row-level security policy for table \"_hyper_2_9_chunk\"\n-- Check behavior with a policy that uses a SubPlan not an InitPlan.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO ON;\nCREATE POLICY pp3 ON hyper_document AS RESTRICTIVE\n    USING ((SELECT dlevel <= seclv FROM uaccount WHERE pguser = current_user));\nSET SESSION AUTHORIZATION regress_rls_carol;\nINSERT INTO hyper_document VALUES (100, 11, 5, 'regress_rls_carol', 'testing pp3'); -- fail\nERROR:  new row violates row-level security policy \"pp3\" for table \"hyper_document\"\n----- Dependencies -----\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO ON;\nCREATE TABLE dependee (x integer, y integer);\nSELECT public.create_hypertable('dependee', 'x', chunk_time_interval=>2);\n         create_hypertable         \n-----------------------------------\n (3,regress_rls_schema,dependee,t)\n\nCREATE TABLE dependent (x integer, y integer);\nSELECT public.create_hypertable('dependent', 'x', chunk_time_interval=>2);\n         create_hypertable          \n------------------------------------\n (4,regress_rls_schema,dependent,t)\n\nCREATE POLICY d1 ON dependent FOR ALL\n    TO PUBLIC\n    USING (x = (SELECT d.x FROM dependee d WHERE d.y = y));\nDROP TABLE dependee; -- Should fail without CASCADE due to dependency on row security qual?\nERROR:  cannot drop table dependee because other objects depend on it\nDETAIL:  policy d1 on table dependent depends on table dependee\nHINT:  Use DROP ... CASCADE to drop the dependent objects too.\nDROP TABLE dependee CASCADE;\nNOTICE:  drop cascades to policy d1 on table dependent\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM dependent; -- After drop, should be unqualified\n--- QUERY PLAN ---\n Seq Scan on dependent\n\n-----   RECURSION    ----\n--\n-- Simple recursion\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE rec1 (x integer, y integer);\nSELECT public.create_hypertable('rec1', 'x', chunk_time_interval=>2);\n       create_hypertable       \n-------------------------------\n (5,regress_rls_schema,rec1,t)\n\nCREATE POLICY r1 ON rec1 USING (x = (SELECT r.x FROM rec1 r WHERE y = r.y));\nALTER TABLE rec1 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rec1; -- fail, direct recursion\nERROR:  infinite recursion detected in policy for relation \"rec1\"\n--\n-- Mutual recursion\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE rec2 (a integer, b integer);\nSELECT public.create_hypertable('rec2', 'x', chunk_time_interval=>2);\nERROR:  column \"x\" does not exist\nALTER POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2 WHERE b = y));\nCREATE POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1 WHERE y = b));\nALTER TABLE rec2 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rec1;    -- fail, mutual recursion\nERROR:  infinite recursion detected in policy for relation \"rec1\"\n--\n-- Mutual recursion via views\n--\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE VIEW rec1v AS SELECT * FROM rec1;\nCREATE VIEW rec2v AS SELECT * FROM rec2;\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2v WHERE b = y));\nALTER POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b));\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rec1;    -- fail, mutual recursion via views\nERROR:  infinite recursion detected in policy for relation \"rec1\"\n--\n-- Mutual recursion via .s.b views\n--\nSET SESSION AUTHORIZATION regress_rls_bob;\n\\set VERBOSITY terse \\\\ -- suppress cascade details\nDROP VIEW rec1v, rec2v CASCADE;\nNOTICE:  drop cascades to 2 other objects\n\\set VERBOSITY default\nCREATE VIEW rec1v WITH (security_barrier) AS SELECT * FROM rec1;\nCREATE VIEW rec2v WITH (security_barrier) AS SELECT * FROM rec2;\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2v WHERE b = y));\nCREATE POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b));\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rec1;    -- fail, mutual recursion via s.b. views\nERROR:  infinite recursion detected in policy for relation \"rec1\"\n--\n-- recursive RLS and VIEWs in policy\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE s1 (a int, b text);\nSELECT public.create_hypertable('s1', 'a', chunk_time_interval=>2);\n      create_hypertable      \n-----------------------------\n (6,regress_rls_schema,s1,t)\n\nINSERT INTO s1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x);\nCREATE TABLE s2 (x int, y text);\nSELECT public.create_hypertable('s2', 'x', chunk_time_interval=>2);\n      create_hypertable      \n-----------------------------\n (7,regress_rls_schema,s2,t)\n\nINSERT INTO s2 (SELECT x, md5(x::text) FROM generate_series(-6,6) x);\nGRANT SELECT ON s1, s2 TO regress_rls_bob;\nCREATE POLICY p1 ON s1 USING (a in (select x from s2 where y like '%2f%'));\nCREATE POLICY p2 ON s2 USING (x in (select a from s1 where b like '%22%'));\nCREATE POLICY p3 ON s1 FOR INSERT WITH CHECK (a = (SELECT a FROM s1));\nALTER TABLE s1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE s2 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE VIEW v2 AS SELECT * FROM s2 WHERE y like '%af%';\nSELECT * FROM s1 WHERE f_leak(b); -- fail (infinite recursion)\nERROR:  infinite recursion detected in policy for relation \"s1\"\nINSERT INTO s1 VALUES (1, 'foo'); -- fail (infinite recursion)\nERROR:  infinite recursion detected in policy for relation \"s1\"\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP POLICY p3 on s1;\nALTER POLICY p2 ON s2 USING (x % 2 = 0);\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM s1 WHERE f_leak(b);\t-- OK\nNOTICE:  f_leak => c81e728d9d4c2f636f067f89cc14862c\nNOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c\n a |                b                 \n---+----------------------------------\n 2 | c81e728d9d4c2f636f067f89cc14862c\n 4 | a87ff679a2f3e71d9181a67b7542122c\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM only s1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Seq Scan on s1\n   Filter: ((hashed SubPlan 1) AND f_leak(b))\n   SubPlan 1\n     ->  Append\n           ->  Seq Scan on s2 s2_1\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_27_chunk s2_2\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_28_chunk s2_3\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_29_chunk s2_4\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_30_chunk s2_5\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_31_chunk s2_6\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_32_chunk s2_7\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_33_chunk s2_8\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER POLICY p1 ON s1 USING (a in (select x from v2)); -- using VIEW in RLS policy\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM s1 WHERE f_leak(b);\t-- OK\nNOTICE:  f_leak => 0267aaf632e87a63288a08331f22c7c3\nNOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc\n a  |                b                 \n----+----------------------------------\n -4 | 0267aaf632e87a63288a08331f22c7c3\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM s1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on s1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on s1 s1_1\n         Filter: ((hashed SubPlan 1) AND f_leak(b))\n         SubPlan 1\n           ->  Append\n                 ->  Seq Scan on s2 s2_1\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_27_chunk s2_2\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_28_chunk s2_3\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_29_chunk s2_4\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_30_chunk s2_5\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_31_chunk s2_6\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_32_chunk s2_7\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_33_chunk s2_8\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n   ->  Seq Scan on _hyper_6_16_chunk s1_2\n         Filter: ((hashed SubPlan 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_17_chunk s1_3\n         Filter: ((hashed SubPlan 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_18_chunk s1_4\n         Filter: ((hashed SubPlan 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_19_chunk s1_5\n         Filter: ((hashed SubPlan 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_20_chunk s1_6\n         Filter: ((hashed SubPlan 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_21_chunk s1_7\n         Filter: ((hashed SubPlan 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_22_chunk s1_8\n         Filter: ((hashed SubPlan 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_23_chunk s1_9\n         Filter: ((hashed SubPlan 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_24_chunk s1_10\n         Filter: ((hashed SubPlan 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_25_chunk s1_11\n         Filter: ((hashed SubPlan 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_26_chunk s1_12\n         Filter: ((hashed SubPlan 1) AND f_leak(b))\n\nSELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%';\n xx | x  |                y                 \n----+----+----------------------------------\n -6 | -6 | 596a3d04481816330f07e4f97510c28f\n -4 | -4 | 0267aaf632e87a63288a08331f22c7c3\n  2 |  2 | c81e728d9d4c2f636f067f89cc14862c\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%';\n--- QUERY PLAN ---\n Result\n   ->  Append\n         ->  Seq Scan on s2 s2_1\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_27_chunk s2_2\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_28_chunk s2_3\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_29_chunk s2_4\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_30_chunk s2_5\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_31_chunk s2_6\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_32_chunk s2_7\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_33_chunk s2_8\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n   SubPlan 2\n     ->  Limit\n           ->  Result\n                 ->  Custom Scan (ChunkAppend) on s1\n                       ->  Seq Scan on s1 s1_1\n                             Filter: (hashed SubPlan 1)\n                             SubPlan 1\n                               ->  Append\n                                     ->  Seq Scan on s2 s2_10\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_27_chunk s2_11\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_28_chunk s2_12\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_29_chunk s2_13\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_30_chunk s2_14\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_31_chunk s2_15\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_32_chunk s2_16\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_33_chunk s2_17\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                       ->  Seq Scan on _hyper_6_16_chunk s1_2\n                             Filter: (hashed SubPlan 1)\n                       ->  Seq Scan on _hyper_6_17_chunk s1_3\n                             Filter: (hashed SubPlan 1)\n                       ->  Seq Scan on _hyper_6_18_chunk s1_4\n                             Filter: (hashed SubPlan 1)\n                       ->  Seq Scan on _hyper_6_19_chunk s1_5\n                             Filter: (hashed SubPlan 1)\n                       ->  Seq Scan on _hyper_6_20_chunk s1_6\n                             Filter: (hashed SubPlan 1)\n                       ->  Seq Scan on _hyper_6_21_chunk s1_7\n                             Filter: (hashed SubPlan 1)\n                       ->  Seq Scan on _hyper_6_22_chunk s1_8\n                             Filter: (hashed SubPlan 1)\n                       ->  Seq Scan on _hyper_6_23_chunk s1_9\n                             Filter: (hashed SubPlan 1)\n                       ->  Seq Scan on _hyper_6_24_chunk s1_10\n                             Filter: (hashed SubPlan 1)\n                       ->  Seq Scan on _hyper_6_25_chunk s1_11\n                             Filter: (hashed SubPlan 1)\n                       ->  Seq Scan on _hyper_6_26_chunk s1_12\n                             Filter: (hashed SubPlan 1)\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER POLICY p2 ON s2 USING (x in (select a from s1 where b like '%d2%'));\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM s1 WHERE f_leak(b);\t-- fail (infinite recursion via view)\nERROR:  infinite recursion detected in policy for relation \"s1\"\n-- prepared statement with regress_rls_alice privilege\nPREPARE p1(int) AS SELECT * FROM t1 WHERE a <= $1;\nEXECUTE p1(2);\n a |  b  \n---+-----\n 2 | bbb\n 2 | bcd\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE p1(2);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: ((a <= 2) AND ((a % 2) = 0))\n   ->  Seq Scan on t2 t1_2\n         Filter: ((a <= 2) AND ((a % 2) = 0))\n   ->  Seq Scan on t3 t1_3\n         Filter: ((a <= 2) AND ((a % 2) = 0))\n\n-- superuser is allowed to bypass RLS checks\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nSELECT * FROM t1 WHERE f_leak(b);\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => ccc\nNOTICE:  f_leak => dad\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => def\nNOTICE:  f_leak => xxx\nNOTICE:  f_leak => yyy\nNOTICE:  f_leak => zzz\n a |  b  \n---+-----\n 1 | aba\n 2 | bbb\n 3 | ccc\n 4 | dad\n 1 | abc\n 2 | bcd\n 3 | cde\n 4 | def\n 1 | xxx\n 2 | yyy\n 3 | zzz\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: f_leak(b)\n   ->  Seq Scan on t2 t1_2\n         Filter: f_leak(b)\n   ->  Seq Scan on t3 t1_3\n         Filter: f_leak(b)\n\n-- plan cache should be invalidated\nEXECUTE p1(2);\n a |  b  \n---+-----\n 1 | aba\n 2 | bbb\n 1 | abc\n 2 | bcd\n 1 | xxx\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE p1(2);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: (a <= 2)\n   ->  Seq Scan on t2 t1_2\n         Filter: (a <= 2)\n   ->  Seq Scan on t3 t1_3\n         Filter: (a <= 2)\n\nPREPARE p2(int) AS SELECT * FROM t1 WHERE a = $1;\nEXECUTE p2(2);\n a |  b  \n---+-----\n 2 | bbb\n 2 | bcd\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE p2(2);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: (a = 2)\n   ->  Seq Scan on t2 t1_2\n         Filter: (a = 2)\n   ->  Seq Scan on t3 t1_3\n         Filter: (a = 2)\n\n-- also, case when privilege switch from superuser\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO ON;\nEXECUTE p2(2);\n a |  b  \n---+-----\n 2 | bbb\n 2 | bcd\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE p2(2);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: ((a = 2) AND ((a % 2) = 0))\n   ->  Seq Scan on t2 t1_2\n         Filter: ((a = 2) AND ((a % 2) = 0))\n   ->  Seq Scan on t3 t1_3\n         Filter: ((a = 2) AND ((a % 2) = 0))\n\n--\n-- UPDATE / DELETE and Row-level security\n--\nSET SESSION AUTHORIZATION regress_rls_bob;\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t1 SET b = b || b WHERE f_leak(b);\n--- QUERY PLAN ---\n Update on t1\n   Update on t1 t1_1\n   Update on t2 t1_2\n   Update on t3 t1_3\n   ->  Result\n         ->  Append\n               ->  Seq Scan on t1 t1_1\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t2 t1_2\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t3 t1_3\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nUPDATE t1 SET b = b || b WHERE f_leak(b);\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => def\nNOTICE:  f_leak => yyy\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b);\n--- QUERY PLAN ---\n Update on t1\n   ->  Seq Scan on t1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nUPDATE only t1 SET b = b || '_updt' WHERE f_leak(b);\nNOTICE:  f_leak => bbbbbb\nNOTICE:  f_leak => daddad\n-- returning clause with system column\nUPDATE only t1 SET b = b WHERE f_leak(b) RETURNING ctid, *, t1;\nNOTICE:  f_leak => bbbbbb_updt\nNOTICE:  f_leak => daddad_updt\n  ctid  | a |      b      |       t1        \n--------+---+-------------+-----------------\n (0,9)  | 2 | bbbbbb_updt | (2,bbbbbb_updt)\n (0,10) | 4 | daddad_updt | (4,daddad_updt)\n\nUPDATE t1 SET b = b WHERE f_leak(b) RETURNING *;\nNOTICE:  f_leak => bbbbbb_updt\nNOTICE:  f_leak => daddad_updt\nNOTICE:  f_leak => bcdbcd\nNOTICE:  f_leak => defdef\nNOTICE:  f_leak => yyyyyy\n a |      b      \n---+-------------\n 2 | bbbbbb_updt\n 4 | daddad_updt\n 2 | bcdbcd\n 4 | defdef\n 2 | yyyyyy\n\nUPDATE t1 SET b = b WHERE f_leak(b) RETURNING ctid, *, t1;\nNOTICE:  f_leak => bbbbbb_updt\nNOTICE:  f_leak => daddad_updt\nNOTICE:  f_leak => bcdbcd\nNOTICE:  f_leak => defdef\nNOTICE:  f_leak => yyyyyy\n  ctid  | a |      b      |       t1        \n--------+---+-------------+-----------------\n (0,13) | 2 | bbbbbb_updt | (2,bbbbbb_updt)\n (0,14) | 4 | daddad_updt | (4,daddad_updt)\n (0,9)  | 2 | bcdbcd      | (2,bcdbcd)\n (0,10) | 4 | defdef      | (4,defdef)\n (0,6)  | 2 | yyyyyy      | (2,yyyyyy)\n\n-- updates with from clause\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t2 SET b=t2.b FROM t3\nWHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b);\n--- QUERY PLAN ---\n Update on t2\n   ->  Nested Loop\n         ->  Seq Scan on t2\n               Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))\n         ->  Seq Scan on t3\n               Filter: ((a = 2) AND f_leak(b))\n\nUPDATE t2 SET b=t2.b FROM t3\nWHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b);\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => yyyyyy\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t1 SET b=t1.b FROM t2\nWHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);\n--- QUERY PLAN ---\n Update on t1\n   Update on t1 t1_1\n   Update on t2 t1_2\n   Update on t3 t1_3\n   ->  Nested Loop\n         ->  Seq Scan on t2\n               Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))\n         ->  Append\n               ->  Seq Scan on t1 t1_1\n                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t2 t1_2\n                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t3 t1_3\n                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))\n\nUPDATE t1 SET b=t1.b FROM t2\nWHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);\nNOTICE:  f_leak => cde\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t2 SET b=t2.b FROM t1\nWHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);\n--- QUERY PLAN ---\n Update on t2\n   ->  Nested Loop\n         ->  Seq Scan on t2\n               Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))\n         ->  Append\n               ->  Seq Scan on t1 t1_1\n                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t2 t1_2\n                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t3 t1_3\n                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))\n\nUPDATE t2 SET b=t2.b FROM t1\nWHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);\nNOTICE:  f_leak => cde\n-- updates with from clause self join\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2\nWHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b\nAND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2;\n--- QUERY PLAN ---\n Update on t2 t2_1\n   ->  Nested Loop\n         Join Filter: (t2_1.b = t2_2.b)\n         ->  Seq Scan on t2 t2_1\n               Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))\n         ->  Seq Scan on t2 t2_2\n               Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))\n\nUPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2\nWHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b\nAND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2;\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => cde\n a |  b  |  c  | a |  b  |  c  |    t2_1     |    t2_2     \n---+-----+-----+---+-----+-----+-------------+-------------\n 3 | cde | 3.3 | 3 | cde | 3.3 | (3,cde,3.3) | (3,cde,3.3)\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2\nWHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b\nAND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;\n--- QUERY PLAN ---\n Update on t1 t1_1\n   Update on t1 t1_1_1\n   Update on t2 t1_1_2\n   Update on t3 t1_1_3\n   ->  Nested Loop\n         Join Filter: (t1_1.b = t1_2.b)\n         ->  Append\n               ->  Seq Scan on t1 t1_1_1\n                     Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t2 t1_1_2\n                     Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t3 t1_1_3\n                     Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))\n         ->  Materialize\n               ->  Append\n                     ->  Seq Scan on t1 t1_2_1\n                           Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))\n                     ->  Seq Scan on t2 t1_2_2\n                           Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))\n                     ->  Seq Scan on t3 t1_2_3\n                           Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))\n\nUPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2\nWHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b\nAND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;\nNOTICE:  f_leak => daddad_updt\nNOTICE:  f_leak => daddad_updt\nNOTICE:  f_leak => defdef\nNOTICE:  f_leak => defdef\n a |      b      | a |      b      |      t1_1       |      t1_2       \n---+-------------+---+-------------+-----------------+-----------------\n 4 | daddad_updt | 4 | daddad_updt | (4,daddad_updt) | (4,daddad_updt)\n 4 | defdef      | 4 | defdef      | (4,defdef)      | (4,defdef)\n\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nSELECT * FROM t1 ORDER BY a,b;\n a |      b      \n---+-------------\n 1 | aba\n 1 | abc\n 1 | xxx\n 2 | bbbbbb_updt\n 2 | bcdbcd\n 2 | yyyyyy\n 3 | ccc\n 3 | cde\n 3 | zzz\n 4 | daddad_updt\n 4 | defdef\n\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO ON;\nEXPLAIN (BUFFERS OFF, COSTS OFF) DELETE FROM only t1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Delete on t1\n   ->  Seq Scan on t1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) DELETE FROM t1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Delete on t1\n   Delete on t1 t1_1\n   Delete on t2 t1_2\n   Delete on t3 t1_3\n   ->  Append\n         ->  Seq Scan on t1 t1_1\n               Filter: (((a % 2) = 0) AND f_leak(b))\n         ->  Seq Scan on t2 t1_2\n               Filter: (((a % 2) = 0) AND f_leak(b))\n         ->  Seq Scan on t3 t1_3\n               Filter: (((a % 2) = 0) AND f_leak(b))\n\nDELETE FROM only t1 WHERE f_leak(b) RETURNING ctid, *, t1;\nNOTICE:  f_leak => bbbbbb_updt\nNOTICE:  f_leak => daddad_updt\n  ctid  | a |      b      |       t1        \n--------+---+-------------+-----------------\n (0,13) | 2 | bbbbbb_updt | (2,bbbbbb_updt)\n (0,15) | 4 | daddad_updt | (4,daddad_updt)\n\nDELETE FROM t1 WHERE f_leak(b) RETURNING ctid, *, t1;\nNOTICE:  f_leak => bcdbcd\nNOTICE:  f_leak => defdef\nNOTICE:  f_leak => yyyyyy\n  ctid  | a |   b    |     t1     \n--------+---+--------+------------\n (0,9)  | 2 | bcdbcd | (2,bcdbcd)\n (0,13) | 4 | defdef | (4,defdef)\n (0,6)  | 2 | yyyyyy | (2,yyyyyy)\n\n--\n-- S.b. view on top of Row-level security\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE b1 (a int, b text);\nSELECT public.create_hypertable('b1', 'a', chunk_time_interval=>2);\n      create_hypertable      \n-----------------------------\n (8,regress_rls_schema,b1,t)\n\nINSERT INTO b1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x);\nCREATE POLICY p1 ON b1 USING (a % 2 = 0);\nALTER TABLE b1 ENABLE ROW LEVEL SECURITY;\nGRANT ALL ON b1 TO regress_rls_bob;\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE VIEW bv1 WITH (security_barrier) AS SELECT * FROM b1 WHERE a > 0 WITH CHECK OPTION;\nGRANT ALL ON bv1 TO regress_rls_carol;\nSET SESSION AUTHORIZATION regress_rls_carol;\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM bv1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Subquery Scan on bv1\n   Filter: f_leak(bv1.b)\n   ->  Append\n         ->  Seq Scan on b1 b1_1\n               Filter: ((a > 0) AND ((a % 2) = 0))\n         ->  Index Scan using _hyper_8_39_chunk_b1_a_idx on _hyper_8_39_chunk b1_2\n               Index Cond: (a > 0)\n               Filter: ((a % 2) = 0)\n         ->  Index Scan using _hyper_8_40_chunk_b1_a_idx on _hyper_8_40_chunk b1_3\n               Index Cond: (a > 0)\n               Filter: ((a % 2) = 0)\n         ->  Index Scan using _hyper_8_41_chunk_b1_a_idx on _hyper_8_41_chunk b1_4\n               Index Cond: (a > 0)\n               Filter: ((a % 2) = 0)\n         ->  Index Scan using _hyper_8_42_chunk_b1_a_idx on _hyper_8_42_chunk b1_5\n               Index Cond: (a > 0)\n               Filter: ((a % 2) = 0)\n         ->  Index Scan using _hyper_8_43_chunk_b1_a_idx on _hyper_8_43_chunk b1_6\n               Index Cond: (a > 0)\n               Filter: ((a % 2) = 0)\n         ->  Index Scan using _hyper_8_44_chunk_b1_a_idx on _hyper_8_44_chunk b1_7\n               Index Cond: (a > 0)\n               Filter: ((a % 2) = 0)\n\nSELECT * FROM bv1 WHERE f_leak(b);\nNOTICE:  f_leak => c81e728d9d4c2f636f067f89cc14862c\nNOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c\nNOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc\nNOTICE:  f_leak => c9f0f895fb98ab9159f51fd0297e236d\nNOTICE:  f_leak => d3d9446802a44259755d38e6d163e820\n a  |                b                 \n----+----------------------------------\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 10 | d3d9446802a44259755d38e6d163e820\n\nINSERT INTO bv1 VALUES (-1, 'xxx'); -- should fail view WCO\nERROR:  new row violates row-level security policy for table \"b1\"\nINSERT INTO bv1 VALUES (11, 'xxx'); -- should fail RLS check\nERROR:  new row violates row-level security policy for table \"b1\"\nINSERT INTO bv1 VALUES (12, 'xxx'); -- ok\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Update on b1\n         Update on _hyper_8_41_chunk b1_1\n         ->  Result\n               ->  Custom Scan (ChunkAppend) on b1\n                     Chunks excluded during startup: 0\n                     ->  Index Scan using _hyper_8_41_chunk_b1_a_idx on _hyper_8_41_chunk b1_1\n                           Index Cond: ((a > 0) AND (a = 4))\n                           Filter: (((a % 2) = 0) AND f_leak(b))\n\nUPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b);\nNOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c\nEXPLAIN (BUFFERS OFF, COSTS OFF) DELETE FROM bv1 WHERE a = 6 AND f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Delete on b1\n         Delete on _hyper_8_42_chunk b1_1\n         ->  Custom Scan (ChunkAppend) on b1\n               Chunks excluded during startup: 0\n               ->  Index Scan using _hyper_8_42_chunk_b1_a_idx on _hyper_8_42_chunk b1_1\n                     Index Cond: ((a > 0) AND (a = 6))\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nDELETE FROM bv1 WHERE a = 6 AND f_leak(b);\nNOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc\nSET SESSION AUTHORIZATION regress_rls_alice;\nSELECT * FROM b1;\n  a  |                b                 \n-----+----------------------------------\n -10 | 1b0fd9efa5279c4203b7c70233f86dbf\n  -9 | 252e691406782824eec43d7eadc3d256\n  -8 | a8d2ec85eaf98407310b72eb73dda247\n  -7 | 74687a12d3915d3c4d83f1af7b3683d5\n  -6 | 596a3d04481816330f07e4f97510c28f\n  -5 | 47c1b025fa18ea96c33fbb6718688c0f\n  -4 | 0267aaf632e87a63288a08331f22c7c3\n  -3 | b3149ecea4628efd23d2f86e5a723472\n  -2 | 5d7b9adcbe1c629ec722529dd12e5129\n  -1 | 6bb61e3b7bce0931da574d19d1d82c88\n   0 | cfcd208495d565ef66e7dff9f98764da\n   1 | c4ca4238a0b923820dcc509a6f75849b\n   2 | c81e728d9d4c2f636f067f89cc14862c\n   3 | eccbc87e4b5ce2fe28308fd9f2a7baf3\n   5 | e4da3b7fbbce2345d7772b0674a318d5\n   4 | yyy\n   7 | 8f14e45fceea167a5a36dedd4bea2543\n   8 | c9f0f895fb98ab9159f51fd0297e236d\n   9 | 45c48cce2e2d7fbdea1afc51c7c6ad26\n  10 | d3d9446802a44259755d38e6d163e820\n  12 | xxx\n\n--\n-- INSERT ... ON CONFLICT DO UPDATE and Row-level security\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP POLICY p1 ON document;\nDROP POLICY p1r ON document;\nCREATE POLICY p1 ON document FOR SELECT USING (true);\nCREATE POLICY p2 ON document FOR INSERT WITH CHECK (dauthor = current_user);\nCREATE POLICY p3 ON document FOR UPDATE\n  USING (cid = (SELECT cid from category WHERE cname = 'novel'))\n  WITH CHECK (dauthor = current_user);\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Exists...\nSELECT * FROM document WHERE did = 2;\n did | cid | dlevel |     dauthor     |     dtitle      \n-----+-----+--------+-----------------+-----------------\n   2 |  11 |      2 | regress_rls_bob | my second novel\n\n-- ...so violates actual WITH CHECK OPTION within UPDATE (not INSERT, since\n-- alternative UPDATE path happens to be taken):\nINSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_carol', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, dauthor = EXCLUDED.dauthor;\nERROR:  new row violates row-level security policy for table \"document\"\n-- Violates USING qual for UPDATE policy p3.\n--\n-- UPDATE path is taken, but UPDATE fails purely because *existing* row to be\n-- updated is not a \"novel\"/cid 11 (row is not leaked, even though we have\n-- SELECT privileges sufficient to see the row in this instance):\nINSERT INTO document VALUES (33, 22, 1, 'regress_rls_bob', 'okay science fiction'); -- preparation for next statement\nINSERT INTO document VALUES (33, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'Some novel, replaces sci-fi') -- takes UPDATE path\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle;\nERROR:  new row violates row-level security policy (USING expression) for table \"document\"\n-- Fine (we UPDATE, since INSERT WCOs and UPDATE security barrier quals + WCOs\n-- not violated):\nINSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;\n did | cid | dlevel |     dauthor     |     dtitle     \n-----+-----+--------+-----------------+----------------\n   2 |  11 |      2 | regress_rls_bob | my first novel\n\n-- Fine (we INSERT, so \"cid = 33\" (\"technology\") isn't evaluated):\nINSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *;\n did | cid | dlevel |     dauthor     |        dtitle         \n-----+-----+--------+-----------------+-----------------------\n  78 |  11 |      1 | regress_rls_bob | some technology novel\n\n-- Fine (same query, but we UPDATE, so \"cid = 33\", (\"technology\") is not the\n-- case in respect of *existing* tuple):\nINSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *;\n did | cid | dlevel |     dauthor     |        dtitle         \n-----+-----+--------+-----------------+-----------------------\n  78 |  33 |      1 | regress_rls_bob | some technology novel\n\n-- Same query a third time, but now fails due to existing tuple finally not\n-- passing quals:\nINSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *;\nERROR:  new row violates row-level security policy (USING expression) for table \"document\"\n-- Don't fail just because INSERT doesn't satisfy WITH CHECK option that\n-- originated as a barrier/USING() qual from the UPDATE.  Note that the UPDATE\n-- path *isn't* taken, and so UPDATE-related policy does not apply:\nINSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;\n did | cid | dlevel |     dauthor     |              dtitle              \n-----+-----+--------+-----------------+----------------------------------\n  79 |  33 |      1 | regress_rls_bob | technology book, can only insert\n\n-- But this time, the same statement fails, because the UPDATE path is taken,\n-- and updating the row just inserted falls afoul of security barrier qual\n-- (enforced as WCO) -- what we might have updated target tuple to is\n-- irrelevant, in fact.\nINSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;\nERROR:  new row violates row-level security policy (USING expression) for table \"document\"\n-- Test default USING qual enforced as WCO\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP POLICY p1 ON document;\nDROP POLICY p2 ON document;\nDROP POLICY p3 ON document;\nCREATE POLICY p3_with_default ON document FOR UPDATE\n  USING (cid = (SELECT cid from category WHERE cname = 'novel'));\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Just because WCO-style enforcement of USING quals occurs with\n-- existing/target tuple does not mean that the implementation can be allowed\n-- to fail to also enforce this qual against the final tuple appended to\n-- relation (since in the absence of an explicit WCO, this is also interpreted\n-- as an UPDATE/ALL WCO in general).\n--\n-- UPDATE path is taken here (fails due to existing tuple).  Note that this is\n-- not reported as a \"USING expression\", because it's an RLS UPDATE check that originated as\n-- a USING qual for the purposes of RLS in general, as opposed to an explicit\n-- USING qual that is ordinarily a security barrier.  We leave it up to the\n-- UPDATE to make this fail:\nINSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;\nERROR:  new row violates row-level security policy for table \"document\"\n-- UPDATE path is taken here.  Existing tuple passes, since it's cid\n-- corresponds to \"novel\", but default USING qual is enforced against\n-- post-UPDATE tuple too (as always when updating with a policy that lacks an\n-- explicit WCO), and so this fails:\nINSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET cid = EXCLUDED.cid, dtitle = EXCLUDED.dtitle RETURNING *;\nERROR:  new row violates row-level security policy for table \"document\"\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP POLICY p3_with_default ON document;\n--\n-- Test ALL policies with ON CONFLICT DO UPDATE (much the same as existing UPDATE\n-- tests)\n--\nCREATE POLICY p3_with_all ON document FOR ALL\n  USING (cid = (SELECT cid from category WHERE cname = 'novel'))\n  WITH CHECK (dauthor = current_user);\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Fails, since ALL WCO is enforced in insert path:\nINSERT INTO document VALUES (80, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_carol', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33;\nERROR:  new row violates row-level security policy for table \"document\"\n-- Fails, since ALL policy USING qual is enforced (existing, target tuple is in\n-- violation, since it has the \"manga\" cid):\nINSERT INTO document VALUES (4, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle;\nERROR:  new row violates row-level security policy (USING expression) for table \"document\"\n-- Fails, since ALL WCO are enforced:\nINSERT INTO document VALUES (1, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET dauthor = 'regress_rls_carol';\nERROR:  new row violates row-level security policy for table \"document\"\n--\n-- ROLE/GROUP\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE z1 (a int, b text);\nSELECT public.create_hypertable('z1', 'a', chunk_time_interval=>2);\n      create_hypertable      \n-----------------------------\n (9,regress_rls_schema,z1,t)\n\nCREATE TABLE z2 (a int, b text);\nSELECT public.create_hypertable('z2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (10,regress_rls_schema,z2,t)\n\nGRANT SELECT ON z1,z2 TO regress_rls_group1, regress_rls_group2,\n    regress_rls_bob, regress_rls_carol;\nINSERT INTO z1 VALUES\n    (1, 'aba'),\n    (2, 'bbb'),\n    (3, 'ccc'),\n    (4, 'dad');\nCREATE POLICY p1 ON z1 TO regress_rls_group1 USING (a % 2 = 0);\nCREATE POLICY p2 ON z1 TO regress_rls_group2 USING (a % 2 = 1);\nALTER TABLE z1 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM z1 WHERE f_leak(b);\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nPREPARE plancache_test AS SELECT * FROM z1 WHERE f_leak(b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nPREPARE plancache_test2 AS WITH q AS MATERIALIZED (SELECT * FROM z1 WHERE f_leak(b)) SELECT * FROM q,z2;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test2;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nPREPARE plancache_test4 AS WITH q AS (SELECT * FROM z1 WHERE f_leak(b)) SELECT * FROM q,z2;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test4;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nPREPARE plancache_test6 AS WITH q AS NOT MATERIALIZED (SELECT * FROM z1 WHERE f_leak(b)) SELECT * FROM q,z2;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test6;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nPREPARE plancache_test3 AS WITH q AS MATERIALIZED (SELECT * FROM z2) SELECT * FROM q,z1 WHERE f_leak(z1.b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test3;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Seq Scan on z2\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nPREPARE plancache_test5 AS WITH q AS (SELECT * FROM z2) SELECT * FROM q,z1 WHERE f_leak(z1.b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test5;\n--- QUERY PLAN ---\n Nested Loop\n   ->  Seq Scan on z2\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nPREPARE plancache_test7 AS WITH q AS NOT MATERIALIZED (SELECT * FROM z2) SELECT * FROM q,z1 WHERE f_leak(z1.b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test7;\n--- QUERY PLAN ---\n Nested Loop\n   ->  Seq Scan on z2\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nSET ROLE regress_rls_group1;\nSELECT * FROM z1 WHERE f_leak(b);\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test2;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test4;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test3;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Seq Scan on z2\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test5;\n--- QUERY PLAN ---\n Nested Loop\n   ->  Seq Scan on z2\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM z1 WHERE f_leak(b);\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => ccc\n a |  b  \n---+-----\n 1 | aba\n 3 | ccc\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 1) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 1) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test2;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test4;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test3;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Seq Scan on z2\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test5;\n--- QUERY PLAN ---\n Nested Loop\n   ->  Seq Scan on z2\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n\nSET ROLE regress_rls_group2;\nSELECT * FROM z1 WHERE f_leak(b);\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => ccc\n a |  b  \n---+-----\n 1 | aba\n 3 | ccc\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 1) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 1) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test2;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test4;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test3;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Seq Scan on z2\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test5;\n--- QUERY PLAN ---\n Nested Loop\n   ->  Seq Scan on z2\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n\n--\n-- Views should follow policy for view owner.\n--\n-- View and Table owner are the same.\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b);\nGRANT SELECT ON rls_view TO regress_rls_bob;\n-- Query as role that is not owner of view or table.  Should return all records.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rls_view;\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => ccc\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 1 | aba\n 2 | bbb\n 3 | ccc\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: f_leak(b)\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: f_leak(b)\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: f_leak(b)\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: f_leak(b)\n\n-- Query as view/table owner.  Should return all records.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSELECT * FROM rls_view;\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => ccc\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 1 | aba\n 2 | bbb\n 3 | ccc\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: f_leak(b)\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: f_leak(b)\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: f_leak(b)\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: f_leak(b)\n\nDROP VIEW rls_view;\n-- View and Table owners are different.\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b);\nGRANT SELECT ON rls_view TO regress_rls_alice;\n-- Query as role that is not owner of view but is owner of table.\n-- Should return records based on view owner policies.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSELECT * FROM rls_view;\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\n-- Query as role that is not owner of table but is owner of view.\n-- Should return records based on view owner policies.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rls_view;\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\n-- Query as role that is not the owner of the table or view without permissions.\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM rls_view; --fail - permission denied.\nERROR:  permission denied for view rls_view\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.\nERROR:  permission denied for view rls_view\n-- Query as role that is not the owner of the table or view with permissions.\nSET SESSION AUTHORIZATION regress_rls_bob;\nGRANT SELECT ON rls_view TO regress_rls_carol;\nSELECT * FROM rls_view;\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nSET SESSION AUTHORIZATION regress_rls_bob;\nDROP VIEW rls_view;\n--\n-- Command specific\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE x1 (a int, b text, c text);\nSELECT public.create_hypertable('x1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (11,regress_rls_schema,x1,t)\n\nGRANT ALL ON x1 TO PUBLIC;\nINSERT INTO x1 VALUES\n    (1, 'abc', 'regress_rls_bob'),\n    (2, 'bcd', 'regress_rls_bob'),\n    (3, 'cde', 'regress_rls_carol'),\n    (4, 'def', 'regress_rls_carol'),\n    (5, 'efg', 'regress_rls_bob'),\n    (6, 'fgh', 'regress_rls_bob'),\n    (7, 'fgh', 'regress_rls_carol'),\n    (8, 'fgh', 'regress_rls_carol');\nCREATE POLICY p0 ON x1 FOR ALL USING (c = current_user);\nCREATE POLICY p1 ON x1 FOR SELECT USING (a % 2 = 0);\nCREATE POLICY p2 ON x1 FOR INSERT WITH CHECK (a % 2 = 1);\nCREATE POLICY p3 ON x1 FOR UPDATE USING (a % 2 = 0);\nCREATE POLICY p4 ON x1 FOR DELETE USING (a < 8);\nALTER TABLE x1 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM x1 WHERE f_leak(b) ORDER BY a ASC;\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => def\nNOTICE:  f_leak => efg\nNOTICE:  f_leak => fgh\nNOTICE:  f_leak => fgh\n a |  b  |         c         \n---+-----+-------------------\n 1 | abc | regress_rls_bob\n 2 | bcd | regress_rls_bob\n 4 | def | regress_rls_carol\n 5 | efg | regress_rls_bob\n 6 | fgh | regress_rls_bob\n 8 | fgh | regress_rls_carol\n\nUPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *;\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => def\nNOTICE:  f_leak => efg\nNOTICE:  f_leak => fgh\nNOTICE:  f_leak => fgh\n a |    b     |         c         \n---+----------+-------------------\n 1 | abc_updt | regress_rls_bob\n 2 | bcd_updt | regress_rls_bob\n 4 | def_updt | regress_rls_carol\n 5 | efg_updt | regress_rls_bob\n 6 | fgh_updt | regress_rls_bob\n 8 | fgh_updt | regress_rls_carol\n\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM x1 WHERE f_leak(b) ORDER BY a ASC;\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => bcd_updt\nNOTICE:  f_leak => def_updt\nNOTICE:  f_leak => fgh\nNOTICE:  f_leak => fgh_updt\nNOTICE:  f_leak => fgh_updt\n a |    b     |         c         \n---+----------+-------------------\n 2 | bcd_updt | regress_rls_bob\n 3 | cde      | regress_rls_carol\n 4 | def_updt | regress_rls_carol\n 6 | fgh_updt | regress_rls_bob\n 7 | fgh      | regress_rls_carol\n 8 | fgh_updt | regress_rls_carol\n\nUPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *;\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => bcd_updt\nNOTICE:  f_leak => def_updt\nNOTICE:  f_leak => fgh\nNOTICE:  f_leak => fgh_updt\nNOTICE:  f_leak => fgh_updt\n a |       b       |         c         \n---+---------------+-------------------\n 3 | cde_updt      | regress_rls_carol\n 2 | bcd_updt_updt | regress_rls_bob\n 4 | def_updt_updt | regress_rls_carol\n 7 | fgh_updt      | regress_rls_carol\n 6 | fgh_updt_updt | regress_rls_bob\n 8 | fgh_updt_updt | regress_rls_carol\n\nDELETE FROM x1 WHERE f_leak(b) RETURNING *;\nNOTICE:  f_leak => cde_updt\nNOTICE:  f_leak => bcd_updt_updt\nNOTICE:  f_leak => def_updt_updt\nNOTICE:  f_leak => fgh_updt\nNOTICE:  f_leak => fgh_updt_updt\nNOTICE:  f_leak => fgh_updt_updt\n a |       b       |         c         \n---+---------------+-------------------\n 3 | cde_updt      | regress_rls_carol\n 2 | bcd_updt_updt | regress_rls_bob\n 4 | def_updt_updt | regress_rls_carol\n 7 | fgh_updt      | regress_rls_carol\n 6 | fgh_updt_updt | regress_rls_bob\n 8 | fgh_updt_updt | regress_rls_carol\n\n--\n-- Duplicate Policy Names\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE y1 (a int, b text);\nSELECT public.create_hypertable('y1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (12,regress_rls_schema,y1,t)\n\nINSERT INTO y1 VALUES(1,2);\nCREATE TABLE y2 (a int, b text);\nSELECT public.create_hypertable('y2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (13,regress_rls_schema,y2,t)\n\nGRANT ALL ON y1, y2 TO regress_rls_bob;\nCREATE POLICY p1 ON y1 FOR ALL USING (a % 2 = 0);\nCREATE POLICY p2 ON y1 FOR SELECT USING (a > 2);\nCREATE POLICY p1 ON y1 FOR SELECT USING (a % 2 = 1);  --fail\nERROR:  policy \"p1\" for table \"y1\" already exists\nCREATE POLICY p1 ON y2 FOR ALL USING (a % 2 = 0);  --OK\nALTER TABLE y1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE y2 ENABLE ROW LEVEL SECURITY;\n--\n-- Expression structure with SBV\n--\n-- Create view as table owner.  RLS should NOT be applied.\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE VIEW rls_sbv WITH (security_barrier) AS\n    SELECT * FROM y1 WHERE f_leak(b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on y1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on y1 y1_1\n         Filter: (f_leak(b) AND (a = 1))\n   ->  Index Scan using _hyper_12_57_chunk_y1_a_idx on _hyper_12_57_chunk y1_2\n         Index Cond: (a = 1)\n         Filter: f_leak(b)\n\nDROP VIEW rls_sbv;\n-- Create view as role that does not own table.  RLS should be applied.\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE VIEW rls_sbv WITH (security_barrier) AS\n    SELECT * FROM y1 WHERE f_leak(b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on y1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on y1 y1_1\n         Filter: ((a = 1) AND ((a > 2) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Index Scan using _hyper_12_57_chunk_y1_a_idx on _hyper_12_57_chunk y1_2\n         Index Cond: (a = 1)\n         Filter: (((a > 2) OR ((a % 2) = 0)) AND f_leak(b))\n\nDROP VIEW rls_sbv;\n--\n-- Expression structure\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nINSERT INTO y2 (SELECT x, md5(x::text) FROM generate_series(0,20) x);\nCREATE POLICY p2 ON y2 USING (a % 3 = 0);\nCREATE POLICY p3 ON y2 USING (a % 4 = 0);\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM y2 WHERE f_leak(b);\nNOTICE:  f_leak => cfcd208495d565ef66e7dff9f98764da\nNOTICE:  f_leak => c81e728d9d4c2f636f067f89cc14862c\nNOTICE:  f_leak => eccbc87e4b5ce2fe28308fd9f2a7baf3\nNOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c\nNOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc\nNOTICE:  f_leak => c9f0f895fb98ab9159f51fd0297e236d\nNOTICE:  f_leak => 45c48cce2e2d7fbdea1afc51c7c6ad26\nNOTICE:  f_leak => d3d9446802a44259755d38e6d163e820\nNOTICE:  f_leak => c20ad4d76fe97759aa27a0c99bff6710\nNOTICE:  f_leak => aab3238922bcc25a6f606eb525ffdc56\nNOTICE:  f_leak => 9bf31c7ff062936a96d3c8bd1f8f2ff3\nNOTICE:  f_leak => c74d97b01eae257e44aa9d5bade97baf\nNOTICE:  f_leak => 6f4922f45568161a8cdf4ad2299f6d23\nNOTICE:  f_leak => 98f13708210194c475687be6106a3b84\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  3 | eccbc87e4b5ce2fe28308fd9f2a7baf3\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n  9 | 45c48cce2e2d7fbdea1afc51c7c6ad26\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM y2 WHERE f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on y2\n   Chunks excluded during startup: 0\n   ->  Seq Scan on y2 y2_1\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_58_chunk y2_2\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_59_chunk y2_3\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_60_chunk y2_4\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_61_chunk y2_5\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_62_chunk y2_6\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_63_chunk y2_7\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_64_chunk y2_8\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_65_chunk y2_9\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_66_chunk y2_10\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_67_chunk y2_11\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_68_chunk y2_12\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n\n--\n-- Qual push-down of leaky functions, when not referring to table\n--\nSELECT * FROM y2 WHERE f_leak('abc');\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  3 | eccbc87e4b5ce2fe28308fd9f2a7baf3\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n  9 | 45c48cce2e2d7fbdea1afc51c7c6ad26\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM y2 WHERE f_leak('abc');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on y2\n   Chunks excluded during startup: 0\n   ->  Seq Scan on y2 y2_1\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_58_chunk y2_2\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_59_chunk y2_3\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_60_chunk y2_4\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_61_chunk y2_5\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_62_chunk y2_6\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_63_chunk y2_7\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_64_chunk y2_8\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_65_chunk y2_9\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_66_chunk y2_10\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_67_chunk y2_11\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_68_chunk y2_12\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n\nCREATE TABLE test_qual_pushdown (\n    abc text\n);\nINSERT INTO test_qual_pushdown VALUES ('abc'),('def');\nSELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc);\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => def\n a | b | abc \n---+---+-----\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc);\n--- QUERY PLAN ---\n Hash Join\n   Hash Cond: (y2.b = test_qual_pushdown.abc)\n   ->  Append\n         ->  Seq Scan on y2 y2_1\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_58_chunk y2_2\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_59_chunk y2_3\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_60_chunk y2_4\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_61_chunk y2_5\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_62_chunk y2_6\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_63_chunk y2_7\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_64_chunk y2_8\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_65_chunk y2_9\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_66_chunk y2_10\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_67_chunk y2_11\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_68_chunk y2_12\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n   ->  Hash\n         ->  Seq Scan on test_qual_pushdown\n               Filter: f_leak(abc)\n\nSELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b);\nNOTICE:  f_leak => cfcd208495d565ef66e7dff9f98764da\nNOTICE:  f_leak => c81e728d9d4c2f636f067f89cc14862c\nNOTICE:  f_leak => eccbc87e4b5ce2fe28308fd9f2a7baf3\nNOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c\nNOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc\nNOTICE:  f_leak => c9f0f895fb98ab9159f51fd0297e236d\nNOTICE:  f_leak => 45c48cce2e2d7fbdea1afc51c7c6ad26\nNOTICE:  f_leak => d3d9446802a44259755d38e6d163e820\nNOTICE:  f_leak => c20ad4d76fe97759aa27a0c99bff6710\nNOTICE:  f_leak => aab3238922bcc25a6f606eb525ffdc56\nNOTICE:  f_leak => 9bf31c7ff062936a96d3c8bd1f8f2ff3\nNOTICE:  f_leak => c74d97b01eae257e44aa9d5bade97baf\nNOTICE:  f_leak => 6f4922f45568161a8cdf4ad2299f6d23\nNOTICE:  f_leak => 98f13708210194c475687be6106a3b84\n a | b | abc \n---+---+-----\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b);\n--- QUERY PLAN ---\n Hash Join\n   Hash Cond: (test_qual_pushdown.abc = y2.b)\n   ->  Seq Scan on test_qual_pushdown\n   ->  Hash\n         ->  Custom Scan (ChunkAppend) on y2\n               Chunks excluded during startup: 0\n               ->  Seq Scan on y2 y2_1\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_58_chunk y2_2\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_59_chunk y2_3\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_60_chunk y2_4\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_61_chunk y2_5\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_62_chunk y2_6\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_63_chunk y2_7\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_64_chunk y2_8\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_65_chunk y2_9\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_66_chunk y2_10\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_67_chunk y2_11\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_68_chunk y2_12\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n\nDROP TABLE test_qual_pushdown;\n--\n-- Plancache invalidate on user change.\n--\nRESET SESSION AUTHORIZATION;\n\\set VERBOSITY terse \\\\ -- suppress cascade details\nDROP TABLE t1 CASCADE;\nNOTICE:  drop cascades to 2 other objects\n\\set VERBOSITY default\nCREATE TABLE t1 (a integer);\nSELECT public.create_hypertable('t1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (14,regress_rls_schema,t1,t)\n\nGRANT SELECT ON t1 TO regress_rls_bob, regress_rls_carol;\nCREATE POLICY p1 ON t1 TO regress_rls_bob USING ((a % 2) = 0);\nCREATE POLICY p2 ON t1 TO regress_rls_carol USING ((a % 4) = 0);\nALTER TABLE t1 ENABLE ROW LEVEL SECURITY;\n-- Prepare as regress_rls_bob\nSET ROLE regress_rls_bob;\nPREPARE role_inval AS SELECT * FROM t1;\n-- Check plan\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE role_inval;\n--- QUERY PLAN ---\n Seq Scan on t1\n   Filter: ((a % 2) = 0)\n\n-- Change to regress_rls_carol\nSET ROLE regress_rls_carol;\n-- Check plan- should be different\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE role_inval;\n--- QUERY PLAN ---\n Seq Scan on t1\n   Filter: ((a % 4) = 0)\n\n-- Change back to regress_rls_bob\nSET ROLE regress_rls_bob;\n-- Check plan- should be back to original\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE role_inval;\n--- QUERY PLAN ---\n Seq Scan on t1\n   Filter: ((a % 2) = 0)\n\n--\n-- CTE and RLS\n--\nRESET SESSION AUTHORIZATION;\nDROP TABLE t1 CASCADE;\nCREATE TABLE t1 (a integer, b text);\nSELECT public.create_hypertable('t1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (15,regress_rls_schema,t1,t)\n\nCREATE POLICY p1 ON t1 USING (a % 2 = 0);\nALTER TABLE t1 ENABLE ROW LEVEL SECURITY;\nGRANT ALL ON t1 TO regress_rls_bob;\nINSERT INTO t1 (SELECT x, md5(x::text) FROM generate_series(0,20) x);\nSET SESSION AUTHORIZATION regress_rls_bob;\nWITH cte1 AS (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1;\nNOTICE:  f_leak => cfcd208495d565ef66e7dff9f98764da\nNOTICE:  f_leak => c81e728d9d4c2f636f067f89cc14862c\nNOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c\nNOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc\nNOTICE:  f_leak => c9f0f895fb98ab9159f51fd0297e236d\nNOTICE:  f_leak => d3d9446802a44259755d38e6d163e820\nNOTICE:  f_leak => c20ad4d76fe97759aa27a0c99bff6710\nNOTICE:  f_leak => aab3238922bcc25a6f606eb525ffdc56\nNOTICE:  f_leak => c74d97b01eae257e44aa9d5bade97baf\nNOTICE:  f_leak => 6f4922f45568161a8cdf4ad2299f6d23\nNOTICE:  f_leak => 98f13708210194c475687be6106a3b84\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) WITH cte1 AS (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1;\n--- QUERY PLAN ---\n CTE Scan on cte1\n   CTE cte1\n     ->  Custom Scan (ChunkAppend) on t1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on t1 t1_1\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_69_chunk t1_2\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_70_chunk t1_3\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_71_chunk t1_4\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_72_chunk t1_5\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_73_chunk t1_6\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_74_chunk t1_7\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_75_chunk t1_8\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_76_chunk t1_9\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_77_chunk t1_10\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_78_chunk t1_11\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_79_chunk t1_12\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n\nWITH cte1 AS (UPDATE t1 SET a = a + 1 RETURNING *) SELECT * FROM cte1; --fail\nERROR:  new row violates row-level security policy for table \"t1\"\nWITH cte1 AS (UPDATE t1 SET a = a RETURNING *) SELECT * FROM cte1; --ok\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n\nWITH cte1 AS (INSERT INTO t1 VALUES (21, 'Fail') RETURNING *) SELECT * FROM cte1; --fail\nERROR:  new row violates row-level security policy for table \"t1\"\nWITH cte1 AS (INSERT INTO t1 VALUES (20, 'Success') RETURNING *) SELECT * FROM cte1; --ok\n a  |    b    \n----+---------\n 20 | Success\n\n--\n-- Rename Policy\n--\nRESET SESSION AUTHORIZATION;\nALTER POLICY p1 ON t1 RENAME TO p1; --fail\nERROR:  policy \"p1\" for table \"t1\" already exists\nSELECT polname, relname\n    FROM pg_policy pol\n    JOIN pg_class pc ON (pc.oid = pol.polrelid)\n    WHERE relname = 't1';\n polname | relname \n---------+---------\n p1      | t1\n\nALTER POLICY p1 ON t1 RENAME TO p2; --ok\nSELECT polname, relname\n    FROM pg_policy pol\n    JOIN pg_class pc ON (pc.oid = pol.polrelid)\n    WHERE relname = 't1';\n polname | relname \n---------+---------\n p2      | t1\n\n--\n-- Check INSERT SELECT\n--\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE TABLE t2 (a integer, b text);\nSELECT public.create_hypertable('t2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (16,regress_rls_schema,t2,t)\n\nINSERT INTO t2 (SELECT * FROM t1);\nEXPLAIN (BUFFERS OFF, COSTS OFF) INSERT INTO t2 (SELECT * FROM t1);\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on t2\n         ->  Append\n               ->  Seq Scan on t1 t1_1\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_69_chunk t1_2\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_70_chunk t1_3\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_71_chunk t1_4\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_72_chunk t1_5\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_73_chunk t1_6\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_74_chunk t1_7\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_75_chunk t1_8\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_76_chunk t1_9\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_77_chunk t1_10\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_78_chunk t1_11\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_79_chunk t1_12\n                     Filter: ((a % 2) = 0)\n\nSELECT * FROM t2;\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n 20 | Success\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t2;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t2 t2_1\n   ->  Seq Scan on _hyper_16_80_chunk t2_2\n   ->  Seq Scan on _hyper_16_81_chunk t2_3\n   ->  Seq Scan on _hyper_16_82_chunk t2_4\n   ->  Seq Scan on _hyper_16_83_chunk t2_5\n   ->  Seq Scan on _hyper_16_84_chunk t2_6\n   ->  Seq Scan on _hyper_16_85_chunk t2_7\n   ->  Seq Scan on _hyper_16_86_chunk t2_8\n   ->  Seq Scan on _hyper_16_87_chunk t2_9\n   ->  Seq Scan on _hyper_16_88_chunk t2_10\n   ->  Seq Scan on _hyper_16_89_chunk t2_11\n   ->  Seq Scan on _hyper_16_90_chunk t2_12\n\nCREATE TABLE t3 AS SELECT * FROM t1;\nSELECT public.create_hypertable('t2', 'a', chunk_time_interval=>2);\nERROR:  table \"t2\" is already a hypertable\nSELECT * FROM t3;\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n 20 | Success\n\nSELECT * INTO t4 FROM t1;\nSELECT * FROM t4;\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n 20 | Success\n\n--\n-- RLS with JOIN\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE blog (id integer, author text, post text);\nSELECT public.create_hypertable('blog', 'id', chunk_time_interval=>2);\n       create_hypertable        \n--------------------------------\n (17,regress_rls_schema,blog,t)\n\nCREATE TABLE comment (blog_id integer, message text);\nSELECT public.create_hypertable('comment', 'blog_id', chunk_time_interval=>2);\n         create_hypertable         \n-----------------------------------\n (18,regress_rls_schema,comment,t)\n\nGRANT ALL ON blog, comment TO regress_rls_bob;\nCREATE POLICY blog_1 ON blog USING (id % 2 = 0);\nALTER TABLE blog ENABLE ROW LEVEL SECURITY;\nINSERT INTO blog VALUES\n    (1, 'alice', 'blog #1'),\n    (2, 'bob', 'blog #1'),\n    (3, 'alice', 'blog #2'),\n    (4, 'alice', 'blog #3'),\n    (5, 'john', 'blog #1');\nINSERT INTO comment VALUES\n    (1, 'cool blog'),\n    (1, 'fun blog'),\n    (3, 'crazy blog'),\n    (5, 'what?'),\n    (4, 'insane!'),\n    (2, 'who did it?');\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Check RLS JOIN with Non-RLS.\nSELECT id, author, message FROM blog JOIN comment ON id = blog_id;\n id | author |   message   \n----+--------+-------------\n  2 | bob    | who did it?\n  4 | alice  | insane!\n\n-- Check Non-RLS JOIN with RLS.\nSELECT id, author, message FROM comment JOIN blog ON id = blog_id;\n id | author |   message   \n----+--------+-------------\n  2 | bob    | who did it?\n  4 | alice  | insane!\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE POLICY comment_1 ON comment USING (blog_id < 4);\nALTER TABLE comment ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Check RLS JOIN RLS\nSELECT id, author, message FROM blog JOIN comment ON id = blog_id;\n id | author |   message   \n----+--------+-------------\n  2 | bob    | who did it?\n\nSELECT id, author, message FROM comment JOIN blog ON id = blog_id;\n id | author |   message   \n----+--------+-------------\n  2 | bob    | who did it?\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP TABLE blog;\nDROP TABLE comment;\n--\n-- Default Deny Policy\n--\nRESET SESSION AUTHORIZATION;\nDROP POLICY p2 ON t1;\nALTER TABLE t1 OWNER TO regress_rls_alice;\n-- Check that default deny does not apply to superuser.\nRESET SESSION AUTHORIZATION;\nSELECT * FROM t1;\n a  |                b                 \n----+----------------------------------\n  1 | c4ca4238a0b923820dcc509a6f75849b\n  0 | cfcd208495d565ef66e7dff9f98764da\n  3 | eccbc87e4b5ce2fe28308fd9f2a7baf3\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  5 | e4da3b7fbbce2345d7772b0674a318d5\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  7 | 8f14e45fceea167a5a36dedd4bea2543\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  9 | 45c48cce2e2d7fbdea1afc51c7c6ad26\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 11 | 6512bd43d9caa6e02c990b0a82652dca\n 10 | d3d9446802a44259755d38e6d163e820\n 13 | c51ce410c124a10e0db5e4b97fc2af39\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 17 | 70efdf2ec9b086079795c442636b55fb\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 19 | 1f0e3dad99908345f7439f8ffabdffc4\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n 20 | Success\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n   ->  Seq Scan on _hyper_15_69_chunk t1_2\n   ->  Seq Scan on _hyper_15_70_chunk t1_3\n   ->  Seq Scan on _hyper_15_71_chunk t1_4\n   ->  Seq Scan on _hyper_15_72_chunk t1_5\n   ->  Seq Scan on _hyper_15_73_chunk t1_6\n   ->  Seq Scan on _hyper_15_74_chunk t1_7\n   ->  Seq Scan on _hyper_15_75_chunk t1_8\n   ->  Seq Scan on _hyper_15_76_chunk t1_9\n   ->  Seq Scan on _hyper_15_77_chunk t1_10\n   ->  Seq Scan on _hyper_15_78_chunk t1_11\n   ->  Seq Scan on _hyper_15_79_chunk t1_12\n\n-- Check that default deny does not apply to table owner.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSELECT * FROM t1;\n a  |                b                 \n----+----------------------------------\n  1 | c4ca4238a0b923820dcc509a6f75849b\n  0 | cfcd208495d565ef66e7dff9f98764da\n  3 | eccbc87e4b5ce2fe28308fd9f2a7baf3\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  5 | e4da3b7fbbce2345d7772b0674a318d5\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  7 | 8f14e45fceea167a5a36dedd4bea2543\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  9 | 45c48cce2e2d7fbdea1afc51c7c6ad26\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 11 | 6512bd43d9caa6e02c990b0a82652dca\n 10 | d3d9446802a44259755d38e6d163e820\n 13 | c51ce410c124a10e0db5e4b97fc2af39\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 17 | 70efdf2ec9b086079795c442636b55fb\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 19 | 1f0e3dad99908345f7439f8ffabdffc4\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n 20 | Success\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n   ->  Seq Scan on _hyper_15_69_chunk t1_2\n   ->  Seq Scan on _hyper_15_70_chunk t1_3\n   ->  Seq Scan on _hyper_15_71_chunk t1_4\n   ->  Seq Scan on _hyper_15_72_chunk t1_5\n   ->  Seq Scan on _hyper_15_73_chunk t1_6\n   ->  Seq Scan on _hyper_15_74_chunk t1_7\n   ->  Seq Scan on _hyper_15_75_chunk t1_8\n   ->  Seq Scan on _hyper_15_76_chunk t1_9\n   ->  Seq Scan on _hyper_15_77_chunk t1_10\n   ->  Seq Scan on _hyper_15_78_chunk t1_11\n   ->  Seq Scan on _hyper_15_79_chunk t1_12\n\n-- Check that default deny applies to non-owner/non-superuser when RLS on.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO ON;\nSELECT * FROM t1;\n a | b \n---+---\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1;\n--- QUERY PLAN ---\n Result\n   One-Time Filter: false\n\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM t1;\n a | b \n---+---\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1;\n--- QUERY PLAN ---\n Result\n   One-Time Filter: false\n\n--\n-- COPY TO/FROM\n--\nRESET SESSION AUTHORIZATION;\nDROP TABLE copy_t CASCADE;\nERROR:  table \"copy_t\" does not exist\nCREATE TABLE copy_t (a integer, b text);\nSELECT public.create_hypertable('copy_t', 'a', chunk_time_interval=>2);\n        create_hypertable         \n----------------------------------\n (19,regress_rls_schema,copy_t,t)\n\nCREATE POLICY p1 ON copy_t USING (a % 2 = 0);\nALTER TABLE copy_t ENABLE ROW LEVEL SECURITY;\nGRANT ALL ON copy_t TO regress_rls_bob, regress_rls_exempt_user;\nINSERT INTO copy_t (SELECT x, md5(x::text) FROM generate_series(0,10) x);\n-- Check COPY TO as Superuser/owner.\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';\n0,cfcd208495d565ef66e7dff9f98764da\n1,c4ca4238a0b923820dcc509a6f75849b\n2,c81e728d9d4c2f636f067f89cc14862c\n3,eccbc87e4b5ce2fe28308fd9f2a7baf3\n4,a87ff679a2f3e71d9181a67b7542122c\n5,e4da3b7fbbce2345d7772b0674a318d5\n6,1679091c5a880faf6fb5e6087eb1b2dc\n7,8f14e45fceea167a5a36dedd4bea2543\n8,c9f0f895fb98ab9159f51fd0297e236d\n9,45c48cce2e2d7fbdea1afc51c7c6ad26\n10,d3d9446802a44259755d38e6d163e820\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';\n0,cfcd208495d565ef66e7dff9f98764da\n1,c4ca4238a0b923820dcc509a6f75849b\n2,c81e728d9d4c2f636f067f89cc14862c\n3,eccbc87e4b5ce2fe28308fd9f2a7baf3\n4,a87ff679a2f3e71d9181a67b7542122c\n5,e4da3b7fbbce2345d7772b0674a318d5\n6,1679091c5a880faf6fb5e6087eb1b2dc\n7,8f14e45fceea167a5a36dedd4bea2543\n8,c9f0f895fb98ab9159f51fd0297e236d\n9,45c48cce2e2d7fbdea1afc51c7c6ad26\n10,d3d9446802a44259755d38e6d163e820\n-- Check COPY TO as user with permissions.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS\nERROR:  query would be affected by row-level security policy for table \"copy_t\"\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok\n0,cfcd208495d565ef66e7dff9f98764da\n2,c81e728d9d4c2f636f067f89cc14862c\n4,a87ff679a2f3e71d9181a67b7542122c\n6,1679091c5a880faf6fb5e6087eb1b2dc\n8,c9f0f895fb98ab9159f51fd0297e236d\n10,d3d9446802a44259755d38e6d163e820\n-- Check COPY TO as user with permissions and BYPASSRLS\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok\n0,cfcd208495d565ef66e7dff9f98764da\n1,c4ca4238a0b923820dcc509a6f75849b\n2,c81e728d9d4c2f636f067f89cc14862c\n3,eccbc87e4b5ce2fe28308fd9f2a7baf3\n4,a87ff679a2f3e71d9181a67b7542122c\n5,e4da3b7fbbce2345d7772b0674a318d5\n6,1679091c5a880faf6fb5e6087eb1b2dc\n7,8f14e45fceea167a5a36dedd4bea2543\n8,c9f0f895fb98ab9159f51fd0297e236d\n9,45c48cce2e2d7fbdea1afc51c7c6ad26\n10,d3d9446802a44259755d38e6d163e820\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok\n0,cfcd208495d565ef66e7dff9f98764da\n1,c4ca4238a0b923820dcc509a6f75849b\n2,c81e728d9d4c2f636f067f89cc14862c\n3,eccbc87e4b5ce2fe28308fd9f2a7baf3\n4,a87ff679a2f3e71d9181a67b7542122c\n5,e4da3b7fbbce2345d7772b0674a318d5\n6,1679091c5a880faf6fb5e6087eb1b2dc\n7,8f14e45fceea167a5a36dedd4bea2543\n8,c9f0f895fb98ab9159f51fd0297e236d\n9,45c48cce2e2d7fbdea1afc51c7c6ad26\n10,d3d9446802a44259755d38e6d163e820\n-- Check COPY TO as user without permissions. SET row_security TO OFF;\nSET SESSION AUTHORIZATION regress_rls_carol;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS\nERROR:  query would be affected by row-level security policy for table \"copy_t\"\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - permission denied\nERROR:  permission denied for table copy_t\n-- Check COPY relation TO; keep it just one row to avoid reordering issues\nRESET SESSION AUTHORIZATION;\nSET row_security TO ON;\nCREATE TABLE copy_rel_to (a integer, b text);\nSELECT public.create_hypertable('copy_rel_to', 'a', chunk_time_interval=>2);\n           create_hypertable           \n---------------------------------------\n (20,regress_rls_schema,copy_rel_to,t)\n\nCREATE POLICY p1 ON copy_rel_to USING (a % 2 = 0);\nALTER TABLE copy_rel_to ENABLE ROW LEVEL SECURITY;\nGRANT ALL ON copy_rel_to TO regress_rls_bob, regress_rls_exempt_user;\nINSERT INTO copy_rel_to VALUES (1, md5('1'));\n-- Check COPY TO as Superuser/owner.\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ',';\n1,c4ca4238a0b923820dcc509a6f75849b\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ',';\n1,c4ca4238a0b923820dcc509a6f75849b\n-- Check COPY TO as user with permissions.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS\nERROR:  query would be affected by row-level security policy for table \"copy_rel_to\"\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --ok\n-- Check COPY TO as user with permissions and BYPASSRLS\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --ok\n1,c4ca4238a0b923820dcc509a6f75849b\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --ok\n1,c4ca4238a0b923820dcc509a6f75849b\n-- Check COPY TO as user without permissions. SET row_security TO OFF;\nSET SESSION AUTHORIZATION regress_rls_carol;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --fail - permission denied\nERROR:  query would be affected by row-level security policy for table \"copy_rel_to\"\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --fail - permission denied\nERROR:  permission denied for table copy_rel_to\n-- Check COPY FROM as Superuser/owner.\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nCOPY copy_t FROM STDIN; --ok\nSET row_security TO ON;\nCOPY copy_t FROM STDIN; --ok\n-- Check COPY FROM as user with permissions.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO OFF;\nCOPY copy_t FROM STDIN; --fail - would be affected by RLS.\nERROR:  query would be affected by row-level security policy for table \"copy_t\"\nSET row_security TO ON;\nCOPY copy_t FROM STDIN; --fail - COPY FROM not supported by RLS.\nERROR:  COPY FROM not supported with row-level security\nHINT:  Use INSERT statements instead.\n-- Check COPY FROM as user with permissions and BYPASSRLS\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO ON;\nCOPY copy_t FROM STDIN; --ok\n-- Check COPY FROM as user without permissions.\nSET SESSION AUTHORIZATION regress_rls_carol;\nSET row_security TO OFF;\nCOPY copy_t FROM STDIN; --fail - permission denied.\nERROR:  permission denied for table copy_t\nSET row_security TO ON;\nCOPY copy_t FROM STDIN; --fail - permission denied.\nERROR:  permission denied for table copy_t\nRESET SESSION AUTHORIZATION;\nDROP TABLE copy_t;\nDROP TABLE copy_rel_to CASCADE;\n-- Check WHERE CURRENT OF\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE current_check (currentid int, payload text, rlsuser text);\nSELECT public.create_hypertable('current_check', 'currentid', chunk_time_interval=>10);\n            create_hypertable            \n-----------------------------------------\n (21,regress_rls_schema,current_check,t)\n\nGRANT ALL ON current_check TO PUBLIC;\nINSERT INTO current_check VALUES\n    (1, 'abc', 'regress_rls_bob'),\n    (2, 'bcd', 'regress_rls_bob'),\n    (3, 'cde', 'regress_rls_bob'),\n    (4, 'def', 'regress_rls_bob');\nCREATE POLICY p1 ON current_check FOR SELECT USING (currentid % 2 = 0);\nCREATE POLICY p2 ON current_check FOR DELETE USING (currentid = 4 AND rlsuser = current_user);\nCREATE POLICY p3 ON current_check FOR UPDATE USING (currentid = 4) WITH CHECK (rlsuser = current_user);\nALTER TABLE current_check ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Can SELECT even rows\nSELECT * FROM current_check;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         2 | bcd     | regress_rls_bob\n         4 | def     | regress_rls_bob\n\n-- Cannot UPDATE row 2\nUPDATE current_check SET payload = payload || '_new' WHERE currentid = 2 RETURNING *;\n currentid | payload | rlsuser \n-----------+---------+---------\n\nBEGIN;\n-- WHERE CURRENT OF does not work with custom scan nodes\n-- so we have to disable chunk append here\nSET timescaledb.enable_chunk_append TO false;\nDECLARE current_check_cursor SCROLL CURSOR FOR SELECT * FROM current_check;\n-- Returns rows that can be seen according to SELECT policy, like plain SELECT\n-- above (even rows)\nFETCH ABSOLUTE 1 FROM current_check_cursor;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         2 | bcd     | regress_rls_bob\n\n-- Still cannot UPDATE row 2 through cursor\nUPDATE current_check SET payload = payload || '_new' WHERE CURRENT OF current_check_cursor RETURNING *;\n currentid | payload | rlsuser \n-----------+---------+---------\n\n-- Can update row 4 through cursor, which is the next visible row\nFETCH RELATIVE 1 FROM current_check_cursor;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         4 | def     | regress_rls_bob\n\nUPDATE current_check SET payload = payload || '_new' WHERE CURRENT OF current_check_cursor RETURNING *;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         4 | def_new | regress_rls_bob\n\nSELECT * FROM current_check;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         2 | bcd     | regress_rls_bob\n         4 | def_new | regress_rls_bob\n\n-- Plan should be a subquery TID scan\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE current_check SET payload = payload WHERE CURRENT OF current_check_cursor;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Update on current_check\n         Update on _hyper_21_104_chunk current_check_1\n         ->  Tid Scan on _hyper_21_104_chunk current_check_1\n               TID Cond: CURRENT OF current_check_cursor\n               Filter: ((currentid = 4) AND ((currentid % 2) = 0))\n\n-- Similarly can only delete row 4\nFETCH ABSOLUTE 1 FROM current_check_cursor;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         2 | bcd     | regress_rls_bob\n\nDELETE FROM current_check WHERE CURRENT OF current_check_cursor RETURNING *;\n currentid | payload | rlsuser \n-----------+---------+---------\n\nFETCH RELATIVE 1 FROM current_check_cursor;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         4 | def     | regress_rls_bob\n\nDELETE FROM current_check WHERE CURRENT OF current_check_cursor RETURNING *;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         4 | def_new | regress_rls_bob\n\nSELECT * FROM current_check;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         2 | bcd     | regress_rls_bob\n\nRESET timescaledb.enable_chunk_append;\nCOMMIT;\n--\n-- check pg_stats view filtering\n--\nSET row_security TO ON;\nSET SESSION AUTHORIZATION regress_rls_alice;\nANALYZE current_check;\n-- Stats visible\nSELECT row_security_active('current_check');\n row_security_active \n---------------------\n f\n\nSELECT attname, most_common_vals FROM pg_stats\n  WHERE tablename = 'current_check'\n  ORDER BY 1;\n  attname  | most_common_vals  \n-----------+-------------------\n currentid | \n payload   | \n rlsuser   | {regress_rls_bob}\n\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Stats not visible\nSELECT row_security_active('current_check');\n row_security_active \n---------------------\n t\n\nSELECT attname, most_common_vals FROM pg_stats\n  WHERE tablename = 'current_check'\n  ORDER BY 1;\n attname | most_common_vals \n---------+------------------\n\n--\n-- Collation support\n--\nBEGIN;\nCREATE TABLE coll_t (c) AS VALUES ('bar'::text);\nCREATE POLICY coll_p ON coll_t USING (c < ('foo'::text COLLATE \"C\"));\nALTER TABLE coll_t ENABLE ROW LEVEL SECURITY;\nGRANT SELECT ON coll_t TO regress_rls_alice;\nSELECT (string_to_array(polqual, ':'))[7] AS inputcollid FROM pg_policy WHERE polrelid = 'coll_t'::regclass;\n   inputcollid    \n------------------\n inputcollid 950 \n\nSET SESSION AUTHORIZATION regress_rls_alice;\nSELECT * FROM coll_t;\n  c  \n-----\n bar\n\nROLLBACK;\n--\n-- Shared Object Dependencies\n--\nRESET SESSION AUTHORIZATION;\nBEGIN;\nCREATE ROLE regress_rls_eve;\nCREATE ROLE regress_rls_frank;\nCREATE TABLE tbl1 (c) AS VALUES ('bar'::text);\nGRANT SELECT ON TABLE tbl1 TO regress_rls_eve;\nCREATE POLICY P ON tbl1 TO regress_rls_eve, regress_rls_frank USING (true);\nSELECT refclassid::regclass, deptype\n  FROM pg_depend\n  WHERE classid = 'pg_policy'::regclass\n  AND refobjid = 'tbl1'::regclass;\n refclassid | deptype \n------------+---------\n pg_class   | a\n\nSELECT refclassid::regclass, deptype\n  FROM pg_shdepend\n  WHERE classid = 'pg_policy'::regclass\n  AND refobjid IN ('regress_rls_eve'::regrole, 'regress_rls_frank'::regrole);\n refclassid | deptype \n------------+---------\n pg_authid  | r\n pg_authid  | r\n\nSAVEPOINT q;\nDROP ROLE regress_rls_eve; --fails due to dependency on POLICY p\nERROR:  role \"regress_rls_eve\" cannot be dropped because some objects depend on it\nDETAIL:  privileges for table tbl1\ntarget of policy p on table tbl1\nROLLBACK TO q;\nALTER POLICY p ON tbl1 TO regress_rls_frank USING (true);\nSAVEPOINT q;\nDROP ROLE regress_rls_eve; --fails due to dependency on GRANT SELECT\nERROR:  role \"regress_rls_eve\" cannot be dropped because some objects depend on it\nDETAIL:  privileges for table tbl1\nROLLBACK TO q;\nREVOKE ALL ON TABLE tbl1 FROM regress_rls_eve;\nSAVEPOINT q;\nDROP ROLE regress_rls_eve; --succeeds\nROLLBACK TO q;\nSAVEPOINT q;\nDROP ROLE regress_rls_frank; --fails due to dependency on POLICY p\nERROR:  role \"regress_rls_frank\" cannot be dropped because some objects depend on it\nDETAIL:  target of policy p on table tbl1\nROLLBACK TO q;\nDROP POLICY p ON tbl1;\nSAVEPOINT q;\nDROP ROLE regress_rls_frank; -- succeeds\nROLLBACK TO q;\nROLLBACK; -- cleanup\n--\n-- Converting table to view\n--\nBEGIN;\nCREATE TABLE t (c int);\nSELECT public.create_hypertable('t', 'c', chunk_time_interval=>2);\n      create_hypertable      \n-----------------------------\n (22,regress_rls_schema,t,t)\n\nCREATE POLICY p ON t USING (c % 2 = 1);\nALTER TABLE t ENABLE ROW LEVEL SECURITY;\nSAVEPOINT q;\nCREATE RULE \"_RETURN\" AS ON SELECT TO t DO INSTEAD\n  SELECT * FROM generate_series(1,5) t0(c); -- fails due to row level security enabled\nERROR:  hypertables do not support rules\nROLLBACK TO q;\nALTER TABLE t DISABLE ROW LEVEL SECURITY;\nSAVEPOINT q;\nCREATE RULE \"_RETURN\" AS ON SELECT TO t DO INSTEAD\n  SELECT * FROM generate_series(1,5) t0(c); -- fails due to policy p on t\nERROR:  hypertables do not support rules\nROLLBACK TO q;\nDROP POLICY p ON t;\nCREATE RULE \"_RETURN\" AS ON SELECT TO t DO INSTEAD\n  SELECT * FROM generate_series(1,5) t0(c); -- succeeds\nERROR:  hypertables do not support rules\nROLLBACK;\n--\n-- Policy expression handling\n--\nBEGIN;\nCREATE TABLE t (c) AS VALUES ('bar'::text);\nCREATE POLICY p ON t USING (max(c)); -- fails: aggregate functions are not allowed in policy expressions\nERROR:  aggregate functions are not allowed in policy expressions\nROLLBACK;\n--\n-- Non-target relations are only subject to SELECT policies\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE r1 (a int);\nSELECT public.create_hypertable('r1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (23,regress_rls_schema,r1,t)\n\nCREATE TABLE r2 (a int);\nSELECT public.create_hypertable('r2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (24,regress_rls_schema,r2,t)\n\nINSERT INTO r1 VALUES (10), (20);\nINSERT INTO r2 VALUES (10), (20);\nGRANT ALL ON r1, r2 TO regress_rls_bob;\nCREATE POLICY p1 ON r1 USING (true);\nALTER TABLE r1 ENABLE ROW LEVEL SECURITY;\nCREATE POLICY p1 ON r2 FOR SELECT USING (true);\nCREATE POLICY p2 ON r2 FOR INSERT WITH CHECK (false);\nCREATE POLICY p3 ON r2 FOR UPDATE USING (false);\nCREATE POLICY p4 ON r2 FOR DELETE USING (false);\nALTER TABLE r2 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM r1;\n a  \n----\n 10\n 20\n\nSELECT * FROM r2;\n a  \n----\n 10\n 20\n\n-- r2 is read-only\nINSERT INTO r2 VALUES (2); -- Not allowed\nERROR:  new row violates row-level security policy for table \"r2\"\n\\pset tuples_only 1\nUPDATE r2 SET a = 2 RETURNING *; -- Updates nothing\n\nDELETE FROM r2 RETURNING *; -- Deletes nothing\n\n\\pset tuples_only 0\n-- r2 can be used as a non-target relation in DML\nINSERT INTO r1 SELECT a + 1 FROM r2 RETURNING *; -- OK\n a  \n----\n 11\n 21\n\nUPDATE r1 SET a = r2.a + 2 FROM r2 WHERE r1.a = r2.a RETURNING *; -- OK\nERROR:  new row for relation \"_hyper_23_105_chunk\" violates check constraint \"constraint_105\"\nDELETE FROM r1 USING r2 WHERE r1.a = r2.a + 2 RETURNING *; -- OK\n a | a \n---+---\n\nSELECT * FROM r1;\n a  \n----\n 10\n 11\n 20\n 21\n\nSELECT * FROM r2;\n a  \n----\n 10\n 20\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP TABLE r1;\nDROP TABLE r2;\n--\n-- FORCE ROW LEVEL SECURITY applies RLS to owners too\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security = on;\nCREATE TABLE r1 (a int);\nSELECT public.create_hypertable('r1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (25,regress_rls_schema,r1,t)\n\nINSERT INTO r1 VALUES (10), (20);\nCREATE POLICY p1 ON r1 USING (false);\nALTER TABLE r1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r1 FORCE ROW LEVEL SECURITY;\n-- No error, but no rows\nTABLE r1;\n a \n---\n\n-- RLS error\nINSERT INTO r1 VALUES (1);\nERROR:  new row violates row-level security policy for table \"r1\"\n-- No error (unable to see any rows to update)\nUPDATE r1 SET a = 1;\nTABLE r1;\n a \n---\n\n-- No error (unable to see any rows to delete)\nDELETE FROM r1;\nTABLE r1;\n a \n---\n\nSET row_security = off;\n-- these all fail, would be affected by RLS\nTABLE r1;\nERROR:  query would be affected by row-level security policy for table \"r1\"\nHINT:  To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.\nUPDATE r1 SET a = 1;\nERROR:  query would be affected by row-level security policy for table \"r1\"\nHINT:  To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.\nDELETE FROM r1;\nERROR:  query would be affected by row-level security policy for table \"r1\"\nHINT:  To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.\nDROP TABLE r1;\n--\n-- FORCE ROW LEVEL SECURITY does not break RI\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security = on;\nCREATE TABLE r1 (a int PRIMARY KEY);\n-- r1 is not a hypertable since r1.a is referenced by r2\nCREATE TABLE r2 (a int REFERENCES r1);\nSELECT public.create_hypertable('r2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (26,regress_rls_schema,r2,t)\n\nINSERT INTO r1 VALUES (10), (20);\nINSERT INTO r2 VALUES (10), (20);\n-- Create policies on r2 which prevent the\n-- owner from seeing any rows, but RI should\n-- still see them.\nCREATE POLICY p1 ON r2 USING (false);\nALTER TABLE r2 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r2 FORCE ROW LEVEL SECURITY;\n-- Errors due to rows in r2\nDELETE FROM r1;\nERROR:  update or delete on table \"r1\" violates foreign key constraint \"r2_a_fkey\" on table \"r2\"\nDETAIL:  Key (a)=(10) is still referenced from table \"r2\".\n-- Reset r2 to no-RLS\nDROP POLICY p1 ON r2;\nALTER TABLE r2 NO FORCE ROW LEVEL SECURITY;\nALTER TABLE r2 DISABLE ROW LEVEL SECURITY;\n-- clean out r2 for INSERT test below\nDELETE FROM r2;\n-- Change r1 to not allow rows to be seen\nCREATE POLICY p1 ON r1 USING (false);\nALTER TABLE r1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r1 FORCE ROW LEVEL SECURITY;\n-- No rows seen\nTABLE r1;\n a \n---\n\n-- No error, RI still sees that row exists in r1\nINSERT INTO r2 VALUES (10);\nDROP TABLE r2;\nDROP TABLE r1;\n-- Ensure cascaded DELETE works\nCREATE TABLE r1 (a int PRIMARY KEY);\n-- r1 is not a hypertable since r1.a is referenced by r2\nCREATE TABLE r2 (a int REFERENCES r1 ON DELETE CASCADE);\nSELECT public.create_hypertable('r2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (27,regress_rls_schema,r2,t)\n\nINSERT INTO r1 VALUES (10), (20);\nINSERT INTO r2 VALUES (10), (20);\n-- Create policies on r2 which prevent the\n-- owner from seeing any rows, but RI should\n-- still see them.\nCREATE POLICY p1 ON r2 USING (false);\nALTER TABLE r2 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r2 FORCE ROW LEVEL SECURITY;\n-- Deletes all records from both\nDELETE FROM r1;\n-- Remove FORCE from r2\nALTER TABLE r2 NO FORCE ROW LEVEL SECURITY;\n-- As owner, we now bypass RLS\n-- verify no rows in r2 now\nTABLE r2;\n a \n---\n\nDROP TABLE r2;\nDROP TABLE r1;\n-- Ensure cascaded UPDATE works\nCREATE TABLE r1 (a int PRIMARY KEY);\n-- r1 is not a hypertable since r1.a is referenced by r2\nCREATE TABLE r2 (a int REFERENCES r1 ON UPDATE CASCADE);\nSELECT public.create_hypertable('r2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (28,regress_rls_schema,r2,t)\n\nINSERT INTO r1 VALUES (10), (20);\nINSERT INTO r2 VALUES (10), (20);\n-- Create policies on r2 which prevent the\n-- owner from seeing any rows, but RI should\n-- still see them.\nCREATE POLICY p1 ON r2 USING (false);\nALTER TABLE r2 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r2 FORCE ROW LEVEL SECURITY;\n-- Updates records in both (terse output to not print CONTEXT, which can be different).\n\\set VERBOSITY terse\nUPDATE r1 SET a = a+5;\nERROR:  new row for relation \"_hyper_28_117_chunk\" violates check constraint \"constraint_117\"\n\\set VERBOSITY default\n-- Remove FORCE from r2\nALTER TABLE r2 NO FORCE ROW LEVEL SECURITY;\n-- As owner, we now bypass RLS\n-- verify records in r2 updated\nTABLE r2;\n a  \n----\n 10\n 20\n\nDROP TABLE r2;\nDROP TABLE r1;\n--\n-- Test INSERT+RETURNING applies SELECT policies as\n-- WithCheckOptions (meaning an error is thrown)\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security = on;\nCREATE TABLE r1 (a int);\nSELECT public.create_hypertable('r1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (29,regress_rls_schema,r1,t)\n\nCREATE POLICY p1 ON r1 FOR SELECT USING (false);\nCREATE POLICY p2 ON r1 FOR INSERT WITH CHECK (true);\nALTER TABLE r1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r1 FORCE ROW LEVEL SECURITY;\n-- Works fine\nINSERT INTO r1 VALUES (10), (20);\n-- No error, but no rows\nTABLE r1;\n a \n---\n\nSET row_security = off;\n-- fail, would be affected by RLS\nTABLE r1;\nERROR:  query would be affected by row-level security policy for table \"r1\"\nHINT:  To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.\nSET row_security = on;\n-- Error\nINSERT INTO r1 VALUES (10), (20) RETURNING *;\nERROR:  new row violates row-level security policy for table \"r1\"\nDROP TABLE r1;\n--\n-- Test UPDATE+RETURNING applies SELECT policies as\n-- WithCheckOptions (meaning an error is thrown)\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security = on;\nCREATE TABLE r1 (a int PRIMARY KEY);\nSELECT public.create_hypertable('r1', 'a', chunk_time_interval=>100);\n      create_hypertable       \n------------------------------\n (30,regress_rls_schema,r1,t)\n\nCREATE POLICY p1 ON r1 FOR SELECT USING (a < 20);\nCREATE POLICY p2 ON r1 FOR UPDATE USING (a < 20) WITH CHECK (true);\nCREATE POLICY p3 ON r1 FOR INSERT WITH CHECK (true);\nINSERT INTO r1 VALUES (10);\nALTER TABLE r1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r1 FORCE ROW LEVEL SECURITY;\n-- Works fine\nUPDATE r1 SET a = 30;\n-- Show updated rows\nALTER TABLE r1 NO FORCE ROW LEVEL SECURITY;\nTABLE r1;\n a  \n----\n 30\n\n-- reset value in r1 for test with RETURNING\nUPDATE r1 SET a = 10;\n-- Verify row reset\nTABLE r1;\n a  \n----\n 10\n\nALTER TABLE r1 FORCE ROW LEVEL SECURITY;\n-- Error\nUPDATE r1 SET a = 30 RETURNING *;\nERROR:  new row violates row-level security policy for table \"r1\"\n-- UPDATE path of INSERT ... ON CONFLICT DO UPDATE should also error out\nINSERT INTO r1 VALUES (10)\n    ON CONFLICT (a) DO UPDATE SET a = 30 RETURNING *;\nERROR:  new row violates row-level security policy for table \"r1\"\n-- Should still error out without RETURNING (use of arbiter always requires\n-- SELECT permissions)\nINSERT INTO r1 VALUES (10)\n    ON CONFLICT (a) DO UPDATE SET a = 30;\nERROR:  new row violates row-level security policy for table \"r1\"\n-- ON CONFLICT ON CONSTRAINT\nINSERT INTO r1 VALUES (10)\n    ON CONFLICT ON CONSTRAINT r1_pkey DO UPDATE SET a = 30;\nERROR:  new row violates row-level security policy for table \"r1\"\nDROP TABLE r1;\n-- Check dependency handling\nRESET SESSION AUTHORIZATION;\nCREATE TABLE dep1 (c1 int);\nSELECT public.create_hypertable('dep1', 'c1', chunk_time_interval=>2);\n       create_hypertable        \n--------------------------------\n (31,regress_rls_schema,dep1,t)\n\nCREATE TABLE dep2 (c1 int);\nSELECT public.create_hypertable('dep2', 'c1', chunk_time_interval=>2);\n       create_hypertable        \n--------------------------------\n (32,regress_rls_schema,dep2,t)\n\nCREATE POLICY dep_p1 ON dep1 TO regress_rls_bob USING (c1 > (select max(dep2.c1) from dep2));\nALTER POLICY dep_p1 ON dep1 TO regress_rls_bob,regress_rls_carol;\n-- Should return one\nSELECT count(*) = 1 FROM pg_depend\n\t\t\t\t   WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1')\n\t\t\t\t\t AND refobjid = (SELECT oid FROM pg_class WHERE relname = 'dep2');\n ?column? \n----------\n t\n\nALTER POLICY dep_p1 ON dep1 USING (true);\n-- Should return one\nSELECT count(*) = 1 FROM pg_shdepend\n\t\t\t\t   WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1')\n\t\t\t\t\t AND refobjid = (SELECT oid FROM pg_authid WHERE rolname = 'regress_rls_bob');\n ?column? \n----------\n t\n\n-- Should return one\nSELECT count(*) = 1 FROM pg_shdepend\n\t\t\t\t   WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1')\n\t\t\t\t\t AND refobjid = (SELECT oid FROM pg_authid WHERE rolname = 'regress_rls_carol');\n ?column? \n----------\n t\n\n-- Should return zero\nSELECT count(*) = 0 FROM pg_depend\n\t\t\t\t   WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1')\n\t\t\t\t\t AND refobjid = (SELECT oid FROM pg_class WHERE relname = 'dep2');\n ?column? \n----------\n t\n\n-- DROP OWNED BY testing\nRESET SESSION AUTHORIZATION;\nCREATE ROLE regress_rls_dob_role1;\nCREATE ROLE regress_rls_dob_role2;\nCREATE TABLE dob_t1 (c1 int);\nSELECT public.create_hypertable('dob_t1', 'c1', chunk_time_interval=>2);\n        create_hypertable         \n----------------------------------\n (33,regress_rls_schema,dob_t1,t)\n\nCREATE TABLE dob_t2 (c1 int) PARTITION BY RANGE (c1);\nCREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1 USING (true);\nDROP OWNED BY regress_rls_dob_role1;\nDROP POLICY p1 ON dob_t1; -- should fail, already gone\nERROR:  policy \"p1\" for table \"dob_t1\" does not exist\nCREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1,regress_rls_dob_role2 USING (true);\nDROP OWNED BY regress_rls_dob_role1;\nDROP POLICY p1 ON dob_t1; -- should succeed\nCREATE POLICY p1 ON dob_t2 TO regress_rls_dob_role1,regress_rls_dob_role2 USING (true);\nDROP OWNED BY regress_rls_dob_role1;\nDROP POLICY p1 ON dob_t2; -- should succeed\nDROP USER regress_rls_dob_role1;\nDROP USER regress_rls_dob_role2;\n--\n-- Clean up objects\n--\nRESET SESSION AUTHORIZATION;\n\\set VERBOSITY terse \\\\ -- suppress cascade details\nDROP SCHEMA regress_rls_schema CASCADE;\nNOTICE:  drop cascades to 116 other objects\n\\set VERBOSITY default\nDROP USER regress_rls_alice;\nDROP USER regress_rls_bob;\nDROP USER regress_rls_carol;\nDROP USER regress_rls_dave;\nDROP USER regress_rls_exempt_user;\nDROP ROLE regress_rls_group1;\nDROP ROLE regress_rls_group2;\n-- Arrange to have a few policies left over, for testing\n-- pg_dump/pg_restore\nCREATE SCHEMA regress_rls_schema;\nCREATE TABLE rls_tbl (c1 int);\nSELECT public.create_hypertable('rls_tbl', 'c1', chunk_time_interval=>2);\n         create_hypertable         \n-----------------------------------\n (34,regress_rls_schema,rls_tbl,t)\n\nALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;\nCREATE POLICY p1 ON rls_tbl USING (c1 > 5);\nCREATE POLICY p2 ON rls_tbl FOR SELECT USING (c1 <= 3);\nCREATE POLICY p3 ON rls_tbl FOR UPDATE USING (c1 <= 3) WITH CHECK (c1 > 5);\nCREATE POLICY p4 ON rls_tbl FOR DELETE USING (c1 <= 3);\nCREATE TABLE rls_tbl_force (c1 int);\nSELECT public.create_hypertable('rls_tbl_force', 'c1', chunk_time_interval=>2);\n            create_hypertable            \n-----------------------------------------\n (35,regress_rls_schema,rls_tbl_force,t)\n\nALTER TABLE rls_tbl_force ENABLE ROW LEVEL SECURITY;\nALTER TABLE rls_tbl_force FORCE ROW LEVEL SECURITY;\nCREATE POLICY p1 ON rls_tbl_force USING (c1 = 5) WITH CHECK (c1 < 5);\nCREATE POLICY p2 ON rls_tbl_force FOR SELECT USING (c1 = 8);\nCREATE POLICY p3 ON rls_tbl_force FOR UPDATE USING (c1 = 8) WITH CHECK (c1 >= 5);\nCREATE POLICY p4 ON rls_tbl_force FOR DELETE USING (c1 = 8);\n"
  },
  {
    "path": "test/expected/rowsecurity-17.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n--\n-- Test of Row-level security feature\n--\n-- Clean up in case a prior regression run failed\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\\set ON_ERROR_STOP 0\n\\set VERBOSITY default\nSET timescaledb.enable_constraint_exclusion TO off;\n-- Suppress NOTICE messages when users/groups don't exist\nSET client_min_messages TO 'warning';\nDROP USER IF EXISTS regress_rls_alice;\nDROP USER IF EXISTS regress_rls_bob;\nDROP USER IF EXISTS regress_rls_carol;\nDROP USER IF EXISTS regress_rls_dave;\nDROP USER IF EXISTS regress_rls_exempt_user;\nDROP ROLE IF EXISTS regress_rls_group1;\nDROP ROLE IF EXISTS regress_rls_group2;\nDROP SCHEMA IF EXISTS regress_rls_schema CASCADE;\nRESET client_min_messages;\n-- initial setup\nCREATE USER regress_rls_alice NOLOGIN;\nCREATE USER regress_rls_bob NOLOGIN;\nCREATE USER regress_rls_carol NOLOGIN;\nCREATE USER regress_rls_dave NOLOGIN;\nCREATE USER regress_rls_exempt_user BYPASSRLS NOLOGIN;\nCREATE ROLE regress_rls_group1 NOLOGIN;\nCREATE ROLE regress_rls_group2 NOLOGIN;\nGRANT regress_rls_group1 TO regress_rls_bob;\nGRANT regress_rls_group2 TO regress_rls_carol;\nCREATE SCHEMA regress_rls_schema;\nGRANT ALL ON SCHEMA regress_rls_schema to public;\nSET search_path = regress_rls_schema;\n-- setup of malicious function\nCREATE OR REPLACE FUNCTION f_leak(text) RETURNS bool\n    COST 0.0000001 LANGUAGE plpgsql\n    AS 'BEGIN RAISE NOTICE ''f_leak => %'', $1; RETURN true; END';\nGRANT EXECUTE ON FUNCTION f_leak(text) TO public;\n-- BASIC Row-Level Security Scenario\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE uaccount (\n    pguser      name primary key,\n    seclv       int\n);\nGRANT SELECT ON uaccount TO public;\nINSERT INTO uaccount VALUES\n    ('regress_rls_alice', 99),\n    ('regress_rls_bob', 1),\n    ('regress_rls_carol', 2),\n    ('regress_rls_dave', 3);\nCREATE TABLE category (\n    cid        int primary key,\n    cname      text\n);\nGRANT ALL ON category TO public;\nINSERT INTO category VALUES\n    (11, 'novel'),\n    (22, 'science fiction'),\n    (33, 'technology'),\n    (44, 'manga');\nCREATE TABLE document (\n    did         int primary key,\n    cid         int references category(cid),\n    dlevel      int not null,\n    dauthor     name,\n    dtitle      text\n);\nGRANT ALL ON document TO public;\nSELECT public.create_hypertable('document', 'did', chunk_time_interval=>2);\n         create_hypertable         \n-----------------------------------\n (1,regress_rls_schema,document,t)\n\nINSERT INTO document VALUES\n    ( 1, 11, 1, 'regress_rls_bob', 'my first novel'),\n    ( 2, 11, 2, 'regress_rls_bob', 'my second novel'),\n    ( 3, 22, 2, 'regress_rls_bob', 'my science fiction'),\n    ( 4, 44, 1, 'regress_rls_bob', 'my first manga'),\n    ( 5, 44, 2, 'regress_rls_bob', 'my second manga'),\n    ( 6, 22, 1, 'regress_rls_carol', 'great science fiction'),\n    ( 7, 33, 2, 'regress_rls_carol', 'great technology book'),\n    ( 8, 44, 1, 'regress_rls_carol', 'great manga'),\n    ( 9, 22, 1, 'regress_rls_dave', 'awesome science fiction'),\n    (10, 33, 2, 'regress_rls_dave', 'awesome technology book');\nALTER TABLE document ENABLE ROW LEVEL SECURITY;\n-- user's security level must be higher than or equal to document's\nCREATE POLICY p1 ON document AS PERMISSIVE\n    USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));\n-- try to create a policy of bogus type\nCREATE POLICY p1 ON document AS UGLY\n    USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));\nERROR:  unrecognized row security option \"ugly\"\nLINE 1: CREATE POLICY p1 ON document AS UGLY\n                                        ^\nHINT:  Only PERMISSIVE or RESTRICTIVE policies are supported currently.\n-- but Dave isn't allowed to anything at cid 50 or above\n-- this is to make sure that we sort the policies by name first\n-- when applying WITH CHECK, a later INSERT by Dave should fail due\n-- to p1r first\nCREATE POLICY p2r ON document AS RESTRICTIVE TO regress_rls_dave\n    USING (cid <> 44 AND cid < 50);\n-- and Dave isn't allowed to see manga documents\nCREATE POLICY p1r ON document AS RESTRICTIVE TO regress_rls_dave\n    USING (cid <> 44);\n\\dp\n                                                                   Access privileges\n       Schema       |   Name   | Type  |              Access privileges               | Column privileges |                  Policies                  \n--------------------+----------+-------+----------------------------------------------+-------------------+--------------------------------------------\n regress_rls_schema | category | table | regress_rls_alice=arwdDxtm/regress_rls_alice+|                   | \n                    |          |       | =arwdDxtm/regress_rls_alice                  |                   | \n regress_rls_schema | document | table | regress_rls_alice=arwdDxtm/regress_rls_alice+|                   | p1:                                       +\n                    |          |       | =arwdDxtm/regress_rls_alice                  |                   |   (u): (dlevel <= ( SELECT uaccount.seclv +\n                    |          |       |                                              |                   |    FROM uaccount                          +\n                    |          |       |                                              |                   |   WHERE (uaccount.pguser = CURRENT_USER)))+\n                    |          |       |                                              |                   | p2r (RESTRICTIVE):                        +\n                    |          |       |                                              |                   |   (u): ((cid <> 44) AND (cid < 50))       +\n                    |          |       |                                              |                   |   to: regress_rls_dave                    +\n                    |          |       |                                              |                   | p1r (RESTRICTIVE):                        +\n                    |          |       |                                              |                   |   (u): (cid <> 44)                        +\n                    |          |       |                                              |                   |   to: regress_rls_dave\n regress_rls_schema | uaccount | table | regress_rls_alice=arwdDxtm/regress_rls_alice+|                   | \n                    |          |       | =r/regress_rls_alice                         |                   | \n\n\\d document\n        Table \"regress_rls_schema.document\"\n Column  |  Type   | Collation | Nullable | Default \n---------+---------+-----------+----------+---------\n did     | integer |           | not null | \n cid     | integer |           |          | \n dlevel  | integer |           | not null | \n dauthor | name    |           |          | \n dtitle  | text    |           |          | \nIndexes:\n    \"document_pkey\" PRIMARY KEY, btree (did)\nForeign-key constraints:\n    \"document_cid_fkey\" FOREIGN KEY (cid) REFERENCES category(cid)\nPolicies:\n    POLICY \"p1\"\n      USING ((dlevel <= ( SELECT uaccount.seclv\n   FROM uaccount\n  WHERE (uaccount.pguser = CURRENT_USER))))\n    POLICY \"p1r\" AS RESTRICTIVE\n      TO regress_rls_dave\n      USING ((cid <> 44))\n    POLICY \"p2r\" AS RESTRICTIVE\n      TO regress_rls_dave\n      USING (((cid <> 44) AND (cid < 50)))\nNumber of child tables: 6 (Use \\d+ to list them.)\n\nSELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename = 'document' ORDER BY policyname;\n     schemaname     | tablename | policyname | permissive  |       roles        | cmd |                    qual                    | with_check \n--------------------+-----------+------------+-------------+--------------------+-----+--------------------------------------------+------------\n regress_rls_schema | document  | p1         | PERMISSIVE  | {public}           | ALL | (dlevel <= ( SELECT uaccount.seclv        +| \n                    |           |            |             |                    |     |    FROM uaccount                          +| \n                    |           |            |             |                    |     |   WHERE (uaccount.pguser = CURRENT_USER))) | \n regress_rls_schema | document  | p1r        | RESTRICTIVE | {regress_rls_dave} | ALL | (cid <> 44)                                | \n regress_rls_schema | document  | p2r        | RESTRICTIVE | {regress_rls_dave} | ALL | ((cid <> 44) AND (cid < 50))               | \n\n-- viewpoint from regress_rls_bob\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO ON;\nSELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my first manga\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great manga\nNOTICE:  f_leak => awesome science fiction\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n\nSELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my first manga\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great manga\nNOTICE:  f_leak => awesome science fiction\n cid | did | dlevel |      dauthor      |         dtitle          |      cname      \n-----+-----+--------+-------------------+-------------------------+-----------------\n  11 |   1 |      1 | regress_rls_bob   | my first novel          | novel\n  44 |   4 |      1 | regress_rls_bob   | my first manga          | manga\n  22 |   6 |      1 | regress_rls_carol | great science fiction   | science fiction\n  44 |   8 |      1 | regress_rls_carol | great manga             | manga\n  22 |   9 |      1 | regress_rls_dave  | awesome science fiction | science fiction\n\n-- try a sampled version\nSELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0)\n  WHERE f_leak(dtitle) ORDER BY did;\n did | cid | dlevel | dauthor | dtitle \n-----+-----+--------+---------+--------\n\n-- viewpoint from regress_rls_carol\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science fiction\nNOTICE:  f_leak => my first manga\nNOTICE:  f_leak => my second manga\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great manga\nNOTICE:  f_leak => awesome science fiction\nNOTICE:  f_leak => awesome technology book\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   5 |  44 |      2 | regress_rls_bob   | my second manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n\nSELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science fiction\nNOTICE:  f_leak => my first manga\nNOTICE:  f_leak => my second manga\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great manga\nNOTICE:  f_leak => awesome science fiction\nNOTICE:  f_leak => awesome technology book\n cid | did | dlevel |      dauthor      |         dtitle          |      cname      \n-----+-----+--------+-------------------+-------------------------+-----------------\n  11 |   1 |      1 | regress_rls_bob   | my first novel          | novel\n  11 |   2 |      2 | regress_rls_bob   | my second novel         | novel\n  22 |   3 |      2 | regress_rls_bob   | my science fiction      | science fiction\n  44 |   4 |      1 | regress_rls_bob   | my first manga          | manga\n  44 |   5 |      2 | regress_rls_bob   | my second manga         | manga\n  22 |   6 |      1 | regress_rls_carol | great science fiction   | science fiction\n  33 |   7 |      2 | regress_rls_carol | great technology book   | technology\n  44 |   8 |      1 | regress_rls_carol | great manga             | manga\n  22 |   9 |      1 | regress_rls_dave  | awesome science fiction | science fiction\n  33 |  10 |      2 | regress_rls_dave  | awesome technology book | technology\n\n-- try a sampled version\nSELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0)\n  WHERE f_leak(dtitle) ORDER BY did;\n did | cid | dlevel | dauthor | dtitle \n-----+-----+--------+---------+--------\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on document\n   Chunks excluded during startup: 0\n   InitPlan 1\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on document document_1\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_1_chunk document_2\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_2_chunk document_3\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_3_chunk document_4\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_4_chunk document_5\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_5_chunk document_6\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_6_chunk document_7\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Hash Join\n   Hash Cond: (document.cid = category.cid)\n   InitPlan 1\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Custom Scan (ChunkAppend) on document\n         Chunks excluded during startup: 0\n         ->  Seq Scan on document document_1\n               Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_1_chunk document_2\n               Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_2_chunk document_3\n               Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_3_chunk document_4\n               Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_4_chunk document_5\n               Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_5_chunk document_6\n               Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_6_chunk document_7\n               Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Hash\n         ->  Seq Scan on category\n\n-- viewpoint from regress_rls_dave\nSET SESSION AUTHORIZATION regress_rls_dave;\nSELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science fiction\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => awesome science fiction\nNOTICE:  f_leak => awesome technology book\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n\nSELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science fiction\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => awesome science fiction\nNOTICE:  f_leak => awesome technology book\n cid | did | dlevel |      dauthor      |         dtitle          |      cname      \n-----+-----+--------+-------------------+-------------------------+-----------------\n  11 |   1 |      1 | regress_rls_bob   | my first novel          | novel\n  11 |   2 |      2 | regress_rls_bob   | my second novel         | novel\n  22 |   3 |      2 | regress_rls_bob   | my science fiction      | science fiction\n  22 |   6 |      1 | regress_rls_carol | great science fiction   | science fiction\n  33 |   7 |      2 | regress_rls_carol | great technology book   | technology\n  22 |   9 |      1 | regress_rls_dave  | awesome science fiction | science fiction\n  33 |  10 |      2 | regress_rls_dave  | awesome technology book | technology\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on document\n   Chunks excluded during startup: 0\n   InitPlan 1\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on document document_1\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_1_chunk document_2\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_2_chunk document_3\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_3_chunk document_4\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_4_chunk document_5\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_5_chunk document_6\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_6_chunk document_7\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Hash Join\n   Hash Cond: (category.cid = document.cid)\n   InitPlan 1\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on category\n   ->  Hash\n         ->  Custom Scan (ChunkAppend) on document\n               Chunks excluded during startup: 0\n               ->  Seq Scan on document document_1\n                     Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n               ->  Seq Scan on _hyper_1_1_chunk document_2\n                     Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n               ->  Seq Scan on _hyper_1_2_chunk document_3\n                     Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n               ->  Seq Scan on _hyper_1_3_chunk document_4\n                     Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n               ->  Seq Scan on _hyper_1_4_chunk document_5\n                     Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n               ->  Seq Scan on _hyper_1_5_chunk document_6\n                     Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n               ->  Seq Scan on _hyper_1_6_chunk document_7\n                     Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n\n-- 44 would technically fail for both p2r and p1r, but we should get an error\n-- back from p1r for this because it sorts first\nINSERT INTO document VALUES (100, 44, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail\nERROR:  new row violates row-level security policy \"p1r\" for table \"document\"\n-- Just to see a p2r error\nINSERT INTO document VALUES (100, 55, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail\nERROR:  new row violates row-level security policy \"p2r\" for table \"document\"\n-- only owner can change policies\nALTER POLICY p1 ON document USING (true);    --fail\nERROR:  must be owner of table document\nDROP POLICY p1 ON document;                  --fail\nERROR:  must be owner of relation document\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER POLICY p1 ON document USING (dauthor = current_user);\n-- viewpoint from regress_rls_bob again\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science fiction\nNOTICE:  f_leak => my first manga\nNOTICE:  f_leak => my second manga\n did | cid | dlevel |     dauthor     |       dtitle       \n-----+-----+--------+-----------------+--------------------\n   1 |  11 |      1 | regress_rls_bob | my first novel\n   2 |  11 |      2 | regress_rls_bob | my second novel\n   3 |  22 |      2 | regress_rls_bob | my science fiction\n   4 |  44 |      1 | regress_rls_bob | my first manga\n   5 |  44 |      2 | regress_rls_bob | my second manga\n\nSELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science fiction\nNOTICE:  f_leak => my first manga\nNOTICE:  f_leak => my second manga\n cid | did | dlevel |     dauthor     |       dtitle       |      cname      \n-----+-----+--------+-----------------+--------------------+-----------------\n  11 |   1 |      1 | regress_rls_bob | my first novel     | novel\n  11 |   2 |      2 | regress_rls_bob | my second novel    | novel\n  22 |   3 |      2 | regress_rls_bob | my science fiction | science fiction\n  44 |   4 |      1 | regress_rls_bob | my first manga     | manga\n  44 |   5 |      2 | regress_rls_bob | my second manga    | manga\n\n-- viewpoint from rls_regres_carol again\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great manga\n did | cid | dlevel |      dauthor      |        dtitle         \n-----+-----+--------+-------------------+-----------------------\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n\nSELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did;\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great manga\n cid | did | dlevel |      dauthor      |        dtitle         |      cname      \n-----+-----+--------+-------------------+-----------------------+-----------------\n  22 |   6 |      1 | regress_rls_carol | great science fiction | science fiction\n  33 |   7 |      2 | regress_rls_carol | great technology book | technology\n  44 |   8 |      1 | regress_rls_carol | great manga           | manga\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on document\n   Chunks excluded during startup: 0\n   ->  Seq Scan on document document_1\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_1_chunk document_2\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_2_chunk document_3\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_3_chunk document_4\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_4_chunk document_5\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_5_chunk document_6\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_6_chunk document_7\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Nested Loop\n   ->  Custom Scan (ChunkAppend) on document\n         Chunks excluded during startup: 0\n         ->  Seq Scan on document document_1\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_1_chunk document_2\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_2_chunk document_3\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_3_chunk document_4\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_4_chunk document_5\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_5_chunk document_6\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_6_chunk document_7\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Index Scan using category_pkey on category\n         Index Cond: (cid = document.cid)\n\n-- interaction of FK/PK constraints\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE POLICY p2 ON category\n    USING (CASE WHEN current_user = 'regress_rls_bob' THEN cid IN (11, 33)\n           WHEN current_user = 'regress_rls_carol' THEN cid IN (22, 44)\n           ELSE false END);\nALTER TABLE category ENABLE ROW LEVEL SECURITY;\n-- cannot delete PK referenced by invisible FK\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid ORDER BY d.did, c.cid;\n did | cid | dlevel |     dauthor     |       dtitle       | cid |   cname    \n-----+-----+--------+-----------------+--------------------+-----+------------\n   1 |  11 |      1 | regress_rls_bob | my first novel     |  11 | novel\n   2 |  11 |      2 | regress_rls_bob | my second novel    |  11 | novel\n   3 |  22 |      2 | regress_rls_bob | my science fiction |     | \n   4 |  44 |      1 | regress_rls_bob | my first manga     |     | \n   5 |  44 |      2 | regress_rls_bob | my second manga    |     | \n     |     |        |                 |                    |  33 | technology\n\n\\set VERBOSITY sqlstate\nDELETE FROM category WHERE cid = 33;    -- fails with FK violation\nERROR:  23503\n\\set VERBOSITY default\n-- can insert FK referencing invisible PK\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid ORDER BY d.did, c.cid;\n did | cid | dlevel |      dauthor      |        dtitle         | cid |      cname      \n-----+-----+--------+-------------------+-----------------------+-----+-----------------\n   6 |  22 |      1 | regress_rls_carol | great science fiction |  22 | science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book |     | \n   8 |  44 |      1 | regress_rls_carol | great manga           |  44 | manga\n\nINSERT INTO document VALUES (11, 33, 1, current_user, 'hoge');\n-- UNIQUE or PRIMARY KEY constraint violation DOES reveal presence of row\nSET SESSION AUTHORIZATION regress_rls_bob;\nINSERT INTO document VALUES (8, 44, 1, 'regress_rls_bob', 'my third manga'); -- Must fail with unique violation, revealing presence of did we can't see\nERROR:  duplicate key value violates unique constraint \"5_10_document_pkey\"\nDETAIL:  Key (did)=(8) already exists.\nSELECT * FROM document WHERE did = 8; -- and confirm we can't see it\n did | cid | dlevel | dauthor | dtitle \n-----+-----+--------+---------+--------\n\n-- RLS policies are checked before constraints\nINSERT INTO document VALUES (8, 44, 1, 'regress_rls_carol', 'my third manga'); -- Should fail with RLS check violation, not duplicate key violation\nERROR:  new row violates row-level security policy for table \"document\"\nUPDATE document SET did = 8, dauthor = 'regress_rls_carol' WHERE did = 5; -- Should fail with RLS check violation, not duplicate key violation\nERROR:  new row violates row-level security policy for table \"document\"\n-- database superuser does bypass RLS policy when enabled\nRESET SESSION AUTHORIZATION;\nSET row_security TO ON;\nSELECT * FROM document;\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   5 |  44 |      2 | regress_rls_bob   | my second manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n  11 |  33 |      1 | regress_rls_carol | hoge\n\nSELECT * FROM category;\n cid |      cname      \n-----+-----------------\n  11 | novel\n  22 | science fiction\n  33 | technology\n  44 | manga\n\n-- database superuser does bypass RLS policy when disabled\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nSELECT * FROM document;\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   5 |  44 |      2 | regress_rls_bob   | my second manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n  11 |  33 |      1 | regress_rls_carol | hoge\n\nSELECT * FROM category;\n cid |      cname      \n-----+-----------------\n  11 | novel\n  22 | science fiction\n  33 | technology\n  44 | manga\n\n-- database non-superuser with bypass privilege can bypass RLS policy when disabled\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO OFF;\nSELECT * FROM document;\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   5 |  44 |      2 | regress_rls_bob   | my second manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n  11 |  33 |      1 | regress_rls_carol | hoge\n\nSELECT * FROM category;\n cid |      cname      \n-----+-----------------\n  11 | novel\n  22 | science fiction\n  33 | technology\n  44 | manga\n\n-- RLS policy does not apply to table owner when RLS enabled.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO ON;\nSELECT * FROM document;\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   5 |  44 |      2 | regress_rls_bob   | my second manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n  11 |  33 |      1 | regress_rls_carol | hoge\n\nSELECT * FROM category;\n cid |      cname      \n-----+-----------------\n  11 | novel\n  22 | science fiction\n  33 | technology\n  44 | manga\n\n-- RLS policy does not apply to table owner when RLS disabled.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO OFF;\nSELECT * FROM document;\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   5 |  44 |      2 | regress_rls_bob   | my second manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n  11 |  33 |      1 | regress_rls_carol | hoge\n\nSELECT * FROM category;\n cid |      cname      \n-----+-----------------\n  11 | novel\n  22 | science fiction\n  33 | technology\n  44 | manga\n\n--\n-- Table inheritance and RLS policy\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO ON;\nCREATE TABLE t1 (a int, junk1 text, b text);\nALTER TABLE t1 DROP COLUMN junk1;    -- just a disturbing factor\nGRANT ALL ON t1 TO public;\nCOPY t1 FROM stdin;\nCREATE TABLE t2 (c float) INHERITS (t1);\nGRANT ALL ON t2 TO public;\nCOPY t2 FROM stdin;\nCREATE TABLE t3 (c text, b text, a int);\nALTER TABLE t3 INHERIT t1;\nGRANT ALL ON t3 TO public;\nCOPY t3(a,b,c) FROM stdin;\nCREATE POLICY p1 ON t1 FOR ALL TO PUBLIC USING (a % 2 = 0); -- be even number\nCREATE POLICY p2 ON t2 FOR ALL TO PUBLIC USING (a % 2 = 1); -- be odd number\nALTER TABLE t1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE t2 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM t1;\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n 2 | bcd\n 4 | def\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: ((a % 2) = 0)\n   ->  Seq Scan on t2 t1_2\n         Filter: ((a % 2) = 0)\n   ->  Seq Scan on t3 t1_3\n         Filter: ((a % 2) = 0)\n\nSELECT * FROM t1 WHERE f_leak(b);\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => def\nNOTICE:  f_leak => yyy\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n 2 | bcd\n 4 | def\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on t2 t1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on t3 t1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\n-- reference to system column\nSELECT ctid, * FROM t1;\n ctid  | a |  b  \n-------+---+-----\n (0,2) | 2 | bbb\n (0,4) | 4 | dad\n (0,2) | 2 | bcd\n (0,4) | 4 | def\n (0,2) | 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT *, t1 FROM t1;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: ((a % 2) = 0)\n   ->  Seq Scan on t2 t1_2\n         Filter: ((a % 2) = 0)\n   ->  Seq Scan on t3 t1_3\n         Filter: ((a % 2) = 0)\n\n-- reference to whole-row reference\nSELECT *, t1 FROM t1;\n a |  b  |   t1    \n---+-----+---------\n 2 | bbb | (2,bbb)\n 4 | dad | (4,dad)\n 2 | bcd | (2,bcd)\n 4 | def | (4,def)\n 2 | yyy | (2,yyy)\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT *, t1 FROM t1;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: ((a % 2) = 0)\n   ->  Seq Scan on t2 t1_2\n         Filter: ((a % 2) = 0)\n   ->  Seq Scan on t3 t1_3\n         Filter: ((a % 2) = 0)\n\n-- for share/update lock\nSELECT * FROM t1 FOR SHARE;\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n 2 | bcd\n 4 | def\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 FOR SHARE;\n--- QUERY PLAN ---\n LockRows\n   ->  Append\n         ->  Seq Scan on t1 t1_1\n               Filter: ((a % 2) = 0)\n         ->  Seq Scan on t2 t1_2\n               Filter: ((a % 2) = 0)\n         ->  Seq Scan on t3 t1_3\n               Filter: ((a % 2) = 0)\n\nSELECT * FROM t1 WHERE f_leak(b) FOR SHARE;\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => def\nNOTICE:  f_leak => yyy\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n 2 | bcd\n 4 | def\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;\n--- QUERY PLAN ---\n LockRows\n   ->  Append\n         ->  Seq Scan on t1 t1_1\n               Filter: (((a % 2) = 0) AND f_leak(b))\n         ->  Seq Scan on t2 t1_2\n               Filter: (((a % 2) = 0) AND f_leak(b))\n         ->  Seq Scan on t3 t1_3\n               Filter: (((a % 2) = 0) AND f_leak(b))\n\n-- union all query\nSELECT a, b, ctid FROM t2 UNION ALL SELECT a, b, ctid FROM t3;\n a |  b  | ctid  \n---+-----+-------\n 1 | abc | (0,1)\n 3 | cde | (0,3)\n 1 | xxx | (0,1)\n 2 | yyy | (0,2)\n 3 | zzz | (0,3)\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT a, b, ctid FROM t2 UNION ALL SELECT a, b, ctid FROM t3;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t2\n         Filter: ((a % 2) = 1)\n   ->  Seq Scan on t3\n\n-- superuser is allowed to bypass RLS checks\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nSELECT * FROM t1 WHERE f_leak(b);\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => ccc\nNOTICE:  f_leak => dad\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => def\nNOTICE:  f_leak => xxx\nNOTICE:  f_leak => yyy\nNOTICE:  f_leak => zzz\n a |  b  \n---+-----\n 1 | aba\n 2 | bbb\n 3 | ccc\n 4 | dad\n 1 | abc\n 2 | bcd\n 3 | cde\n 4 | def\n 1 | xxx\n 2 | yyy\n 3 | zzz\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: f_leak(b)\n   ->  Seq Scan on t2 t1_2\n         Filter: f_leak(b)\n   ->  Seq Scan on t3 t1_3\n         Filter: f_leak(b)\n\n-- non-superuser with bypass privilege can bypass RLS policy when disabled\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO OFF;\nSELECT * FROM t1 WHERE f_leak(b);\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => ccc\nNOTICE:  f_leak => dad\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => def\nNOTICE:  f_leak => xxx\nNOTICE:  f_leak => yyy\nNOTICE:  f_leak => zzz\n a |  b  \n---+-----\n 1 | aba\n 2 | bbb\n 3 | ccc\n 4 | dad\n 1 | abc\n 2 | bcd\n 3 | cde\n 4 | def\n 1 | xxx\n 2 | yyy\n 3 | zzz\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: f_leak(b)\n   ->  Seq Scan on t2 t1_2\n         Filter: f_leak(b)\n   ->  Seq Scan on t3 t1_3\n         Filter: f_leak(b)\n\n--\n-- Hyper Tables\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE hyper_document (\n    did         int,\n    cid         int,\n    dlevel      int not null,\n    dauthor     name,\n    dtitle      text\n);\nGRANT ALL ON hyper_document TO public;\nSELECT public.create_hypertable('hyper_document', 'did', chunk_time_interval=>2);\n            create_hypertable            \n-----------------------------------------\n (2,regress_rls_schema,hyper_document,t)\n\nINSERT INTO hyper_document VALUES\n    ( 1, 11, 1, 'regress_rls_bob', 'my first novel'),\n    ( 2, 11, 2, 'regress_rls_bob', 'my second novel'),\n    ( 3, 99, 2, 'regress_rls_bob', 'my science textbook'),\n    ( 4, 55, 1, 'regress_rls_bob', 'my first satire'),\n    ( 5, 99, 2, 'regress_rls_bob', 'my history book'),\n    ( 6, 11, 1, 'regress_rls_carol', 'great science fiction'),\n    ( 7, 99, 2, 'regress_rls_carol', 'great technology book'),\n    ( 8, 55, 2, 'regress_rls_carol', 'great satire'),\n    ( 9, 11, 1, 'regress_rls_dave', 'awesome science fiction'),\n    (10, 99, 2, 'regress_rls_dave', 'awesome technology book');\nALTER TABLE hyper_document ENABLE ROW LEVEL SECURITY;\nGRANT ALL ON _timescaledb_internal._hyper_2_9_chunk TO public;\n-- Create policy on parent\n-- user's security level must be higher than or equal to document's\nCREATE POLICY pp1 ON hyper_document AS PERMISSIVE\n    USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));\n-- Dave is only allowed to see cid < 55\nCREATE POLICY pp1r ON hyper_document AS RESTRICTIVE TO regress_rls_dave\n    USING (cid < 55);\n\\d+ hyper_document\n                         Table \"regress_rls_schema.hyper_document\"\n Column  |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description \n---------+---------+-----------+----------+---------+----------+--------------+-------------\n did     | integer |           | not null |         | plain    |              | \n cid     | integer |           |          |         | plain    |              | \n dlevel  | integer |           | not null |         | plain    |              | \n dauthor | name    |           |          |         | plain    |              | \n dtitle  | text    |           |          |         | extended |              | \nIndexes:\n    \"hyper_document_did_idx\" btree (did DESC)\nPolicies:\n    POLICY \"pp1\"\n      USING ((dlevel <= ( SELECT uaccount.seclv\n   FROM uaccount\n  WHERE (uaccount.pguser = CURRENT_USER))))\n    POLICY \"pp1r\" AS RESTRICTIVE\n      TO regress_rls_dave\n      USING ((cid < 55))\nChild tables: _timescaledb_internal._hyper_2_10_chunk,\n              _timescaledb_internal._hyper_2_11_chunk,\n              _timescaledb_internal._hyper_2_12_chunk,\n              _timescaledb_internal._hyper_2_13_chunk,\n              _timescaledb_internal._hyper_2_14_chunk,\n              _timescaledb_internal._hyper_2_9_chunk\n\nSELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename like '%hyper_document%' ORDER BY policyname;\n     schemaname     |   tablename    | policyname | permissive  |       roles        | cmd |                    qual                    | with_check \n--------------------+----------------+------------+-------------+--------------------+-----+--------------------------------------------+------------\n regress_rls_schema | hyper_document | pp1        | PERMISSIVE  | {public}           | ALL | (dlevel <= ( SELECT uaccount.seclv        +| \n                    |                |            |             |                    |     |    FROM uaccount                          +| \n                    |                |            |             |                    |     |   WHERE (uaccount.pguser = CURRENT_USER))) | \n regress_rls_schema | hyper_document | pp1r       | RESTRICTIVE | {regress_rls_dave} | ALL | (cid < 55)                                 | \n\n-- viewpoint from regress_rls_bob\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO ON;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my first satire\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => awesome science fiction\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   4 |  55 |      1 | regress_rls_bob   | my first satire\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on hyper_document\n   Chunks excluded during startup: 0\n   InitPlan 1\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on hyper_document hyper_document_1\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_9_chunk hyper_document_2\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_10_chunk hyper_document_3\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_11_chunk hyper_document_4\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_12_chunk hyper_document_5\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_13_chunk hyper_document_6\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_14_chunk hyper_document_7\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n\n-- viewpoint from regress_rls_carol\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science textbook\nNOTICE:  f_leak => my first satire\nNOTICE:  f_leak => my history book\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great satire\nNOTICE:  f_leak => awesome science fiction\nNOTICE:  f_leak => awesome technology book\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  99 |      2 | regress_rls_bob   | my science textbook\n   4 |  55 |      1 | regress_rls_bob   | my first satire\n   5 |  99 |      2 | regress_rls_bob   | my history book\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   7 |  99 |      2 | regress_rls_carol | great technology book\n   8 |  55 |      2 | regress_rls_carol | great satire\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  99 |      2 | regress_rls_dave  | awesome technology book\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on hyper_document\n   Chunks excluded during startup: 0\n   InitPlan 1\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on hyper_document hyper_document_1\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_9_chunk hyper_document_2\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_10_chunk hyper_document_3\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_11_chunk hyper_document_4\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_12_chunk hyper_document_5\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_13_chunk hyper_document_6\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_14_chunk hyper_document_7\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n\n-- viewpoint from regress_rls_dave\nSET SESSION AUTHORIZATION regress_rls_dave;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => awesome science fiction\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on hyper_document\n   Chunks excluded during startup: 0\n   InitPlan 1\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on hyper_document hyper_document_1\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_9_chunk hyper_document_2\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_10_chunk hyper_document_3\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_11_chunk hyper_document_4\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_12_chunk hyper_document_5\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_13_chunk hyper_document_6\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_14_chunk hyper_document_7\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n\n-- pp1 ERROR\nINSERT INTO hyper_document VALUES (1, 11, 5, 'regress_rls_dave', 'testing pp1'); -- fail\nERROR:  new row violates row-level security policy for table \"hyper_document\"\n-- pp1r ERROR\nINSERT INTO hyper_document VALUES (1, 99, 1, 'regress_rls_dave', 'testing pp1r'); -- fail\nERROR:  new row violates row-level security policy \"pp1r\" for table \"hyper_document\"\n-- Show that RLS policy does not apply for direct inserts to children\n-- This should fail with RLS POLICY pp1r violation.\nINSERT INTO hyper_document VALUES (1, 55, 1, 'regress_rls_dave', 'testing RLS with hypertables'); -- fail\nERROR:  new row violates row-level security policy \"pp1r\" for table \"hyper_document\"\n-- But this should succeed.\nINSERT INTO _timescaledb_internal._hyper_2_9_chunk VALUES (1, 55, 1, 'regress_rls_dave', 'testing RLS with hypertables'); -- success\n-- We still cannot see the row using the parent\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did, cid;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => awesome science fiction\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n\n-- But we can if we look directly\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk WHERE f_leak(dtitle) ORDER BY did, cid;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => testing RLS with hypertables\n did | cid | dlevel |     dauthor      |            dtitle            \n-----+-----+--------+------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob  | my first novel\n   1 |  55 |      1 | regress_rls_dave | testing RLS with hypertables\n\n-- Turn on RLS and create policy on child to show RLS is checked before constraints\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER TABLE _timescaledb_internal._hyper_2_9_chunk ENABLE ROW LEVEL SECURITY;\nCREATE POLICY pp3 ON _timescaledb_internal._hyper_2_9_chunk AS RESTRICTIVE\n    USING (cid < 55);\n-- This should fail with RLS violation now.\nSET SESSION AUTHORIZATION regress_rls_dave;\nINSERT INTO _timescaledb_internal._hyper_2_9_chunk VALUES (1, 55, 1, 'regress_rls_dave', 'testing RLS with hypertables - round 2'); -- fail\nERROR:  new row violates row-level security policy for table \"_hyper_2_9_chunk\"\n-- And now we cannot see directly into the partition either, due to RLS\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk WHERE f_leak(dtitle) ORDER BY did, cid;\n did | cid | dlevel | dauthor | dtitle \n-----+-----+--------+---------+--------\n\n-- The parent looks same as before\n-- viewpoint from regress_rls_dave\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did, cid;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => awesome science fiction\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on hyper_document\n   Chunks excluded during startup: 0\n   InitPlan 1\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on hyper_document hyper_document_1\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_9_chunk hyper_document_2\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_10_chunk hyper_document_3\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_11_chunk hyper_document_4\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_12_chunk hyper_document_5\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_13_chunk hyper_document_6\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_14_chunk hyper_document_7\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n\n-- viewpoint from regress_rls_carol\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did, cid;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => testing RLS with hypertables\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science textbook\nNOTICE:  f_leak => my first satire\nNOTICE:  f_leak => my history book\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great satire\nNOTICE:  f_leak => awesome science fiction\nNOTICE:  f_leak => awesome technology book\n did | cid | dlevel |      dauthor      |            dtitle            \n-----+-----+--------+-------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   1 |  55 |      1 | regress_rls_dave  | testing RLS with hypertables\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  99 |      2 | regress_rls_bob   | my science textbook\n   4 |  55 |      1 | regress_rls_bob   | my first satire\n   5 |  99 |      2 | regress_rls_bob   | my history book\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   7 |  99 |      2 | regress_rls_carol | great technology book\n   8 |  55 |      2 | regress_rls_carol | great satire\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  99 |      2 | regress_rls_dave  | awesome technology book\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on hyper_document\n   Chunks excluded during startup: 0\n   InitPlan 1\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on hyper_document hyper_document_1\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_9_chunk hyper_document_2\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_10_chunk hyper_document_3\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_11_chunk hyper_document_4\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_12_chunk hyper_document_5\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_13_chunk hyper_document_6\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_14_chunk hyper_document_7\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n\n-- only owner can change policies\nALTER POLICY pp1 ON hyper_document USING (true);    --fail\nERROR:  must be owner of table hyper_document\nDROP POLICY pp1 ON hyper_document;                  --fail\nERROR:  must be owner of relation hyper_document\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER POLICY pp1 ON hyper_document USING (dauthor = current_user);\n-- viewpoint from regress_rls_bob again\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did, cid;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science textbook\nNOTICE:  f_leak => my first satire\nNOTICE:  f_leak => my history book\n did | cid | dlevel |     dauthor     |       dtitle        \n-----+-----+--------+-----------------+---------------------\n   1 |  11 |      1 | regress_rls_bob | my first novel\n   2 |  11 |      2 | regress_rls_bob | my second novel\n   3 |  99 |      2 | regress_rls_bob | my science textbook\n   4 |  55 |      1 | regress_rls_bob | my first satire\n   5 |  99 |      2 | regress_rls_bob | my history book\n\n-- viewpoint from rls_regres_carol again\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did, cid;\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great satire\n did | cid | dlevel |      dauthor      |        dtitle         \n-----+-----+--------+-------------------+-----------------------\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   7 |  99 |      2 | regress_rls_carol | great technology book\n   8 |  55 |      2 | regress_rls_carol | great satire\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on hyper_document\n   Chunks excluded during startup: 0\n   ->  Seq Scan on hyper_document hyper_document_1\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_9_chunk hyper_document_2\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_10_chunk hyper_document_3\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_11_chunk hyper_document_4\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_12_chunk hyper_document_5\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_13_chunk hyper_document_6\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_14_chunk hyper_document_7\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n\n-- database superuser does bypass RLS policy when enabled\nRESET SESSION AUTHORIZATION;\nSET row_security TO ON;\nSELECT * FROM hyper_document ORDER BY did, cid;\n did | cid | dlevel |      dauthor      |            dtitle            \n-----+-----+--------+-------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   1 |  55 |      1 | regress_rls_dave  | testing RLS with hypertables\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  99 |      2 | regress_rls_bob   | my science textbook\n   4 |  55 |      1 | regress_rls_bob   | my first satire\n   5 |  99 |      2 | regress_rls_bob   | my history book\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   7 |  99 |      2 | regress_rls_carol | great technology book\n   8 |  55 |      2 | regress_rls_carol | great satire\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  99 |      2 | regress_rls_dave  | awesome technology book\n\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk ORDER BY did, cid;\n did | cid | dlevel |     dauthor      |            dtitle            \n-----+-----+--------+------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob  | my first novel\n   1 |  55 |      1 | regress_rls_dave | testing RLS with hypertables\n\n-- database non-superuser with bypass privilege can bypass RLS policy when disabled\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO OFF;\nSELECT * FROM hyper_document ORDER BY did, cid;\n did | cid | dlevel |      dauthor      |            dtitle            \n-----+-----+--------+-------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   1 |  55 |      1 | regress_rls_dave  | testing RLS with hypertables\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  99 |      2 | regress_rls_bob   | my science textbook\n   4 |  55 |      1 | regress_rls_bob   | my first satire\n   5 |  99 |      2 | regress_rls_bob   | my history book\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   7 |  99 |      2 | regress_rls_carol | great technology book\n   8 |  55 |      2 | regress_rls_carol | great satire\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  99 |      2 | regress_rls_dave  | awesome technology book\n\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk ORDER BY did, cid;\n did | cid | dlevel |     dauthor      |            dtitle            \n-----+-----+--------+------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob  | my first novel\n   1 |  55 |      1 | regress_rls_dave | testing RLS with hypertables\n\n-- RLS policy does not apply to table owner when RLS enabled.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO ON;\nSELECT * FROM hyper_document ORDER BY did, cid;\n did | cid | dlevel |      dauthor      |            dtitle            \n-----+-----+--------+-------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   1 |  55 |      1 | regress_rls_dave  | testing RLS with hypertables\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  99 |      2 | regress_rls_bob   | my science textbook\n   4 |  55 |      1 | regress_rls_bob   | my first satire\n   5 |  99 |      2 | regress_rls_bob   | my history book\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   7 |  99 |      2 | regress_rls_carol | great technology book\n   8 |  55 |      2 | regress_rls_carol | great satire\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  99 |      2 | regress_rls_dave  | awesome technology book\n\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk ORDER BY did, cid;\n did | cid | dlevel |     dauthor      |            dtitle            \n-----+-----+--------+------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob  | my first novel\n   1 |  55 |      1 | regress_rls_dave | testing RLS with hypertables\n\n-- When RLS disabled, other users get ERROR.\nSET SESSION AUTHORIZATION regress_rls_dave;\nSET row_security TO OFF;\nSELECT * FROM hyper_document ORDER BY did, cid;\nERROR:  query would be affected by row-level security policy for table \"hyper_document\"\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk ORDER BY did, cid;\nERROR:  query would be affected by row-level security policy for table \"_hyper_2_9_chunk\"\n-- Check behavior with a policy that uses a SubPlan not an InitPlan.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO ON;\nCREATE POLICY pp3 ON hyper_document AS RESTRICTIVE\n    USING ((SELECT dlevel <= seclv FROM uaccount WHERE pguser = current_user));\nSET SESSION AUTHORIZATION regress_rls_carol;\nINSERT INTO hyper_document VALUES (100, 11, 5, 'regress_rls_carol', 'testing pp3'); -- fail\nERROR:  new row violates row-level security policy \"pp3\" for table \"hyper_document\"\n----- Dependencies -----\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO ON;\nCREATE TABLE dependee (x integer, y integer);\nSELECT public.create_hypertable('dependee', 'x', chunk_time_interval=>2);\n         create_hypertable         \n-----------------------------------\n (3,regress_rls_schema,dependee,t)\n\nCREATE TABLE dependent (x integer, y integer);\nSELECT public.create_hypertable('dependent', 'x', chunk_time_interval=>2);\n         create_hypertable          \n------------------------------------\n (4,regress_rls_schema,dependent,t)\n\nCREATE POLICY d1 ON dependent FOR ALL\n    TO PUBLIC\n    USING (x = (SELECT d.x FROM dependee d WHERE d.y = y));\nDROP TABLE dependee; -- Should fail without CASCADE due to dependency on row security qual?\nERROR:  cannot drop table dependee because other objects depend on it\nDETAIL:  policy d1 on table dependent depends on table dependee\nHINT:  Use DROP ... CASCADE to drop the dependent objects too.\nDROP TABLE dependee CASCADE;\nNOTICE:  drop cascades to policy d1 on table dependent\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM dependent; -- After drop, should be unqualified\n--- QUERY PLAN ---\n Seq Scan on dependent\n\n-----   RECURSION    ----\n--\n-- Simple recursion\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE rec1 (x integer, y integer);\nSELECT public.create_hypertable('rec1', 'x', chunk_time_interval=>2);\n       create_hypertable       \n-------------------------------\n (5,regress_rls_schema,rec1,t)\n\nCREATE POLICY r1 ON rec1 USING (x = (SELECT r.x FROM rec1 r WHERE y = r.y));\nALTER TABLE rec1 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rec1; -- fail, direct recursion\nERROR:  infinite recursion detected in policy for relation \"rec1\"\n--\n-- Mutual recursion\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE rec2 (a integer, b integer);\nSELECT public.create_hypertable('rec2', 'x', chunk_time_interval=>2);\nERROR:  column \"x\" does not exist\nALTER POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2 WHERE b = y));\nCREATE POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1 WHERE y = b));\nALTER TABLE rec2 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rec1;    -- fail, mutual recursion\nERROR:  infinite recursion detected in policy for relation \"rec1\"\n--\n-- Mutual recursion via views\n--\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE VIEW rec1v AS SELECT * FROM rec1;\nCREATE VIEW rec2v AS SELECT * FROM rec2;\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2v WHERE b = y));\nALTER POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b));\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rec1;    -- fail, mutual recursion via views\nERROR:  infinite recursion detected in policy for relation \"rec1\"\n--\n-- Mutual recursion via .s.b views\n--\nSET SESSION AUTHORIZATION regress_rls_bob;\n\\set VERBOSITY terse \\\\ -- suppress cascade details\nDROP VIEW rec1v, rec2v CASCADE;\nNOTICE:  drop cascades to 2 other objects\n\\set VERBOSITY default\nCREATE VIEW rec1v WITH (security_barrier) AS SELECT * FROM rec1;\nCREATE VIEW rec2v WITH (security_barrier) AS SELECT * FROM rec2;\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2v WHERE b = y));\nCREATE POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b));\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rec1;    -- fail, mutual recursion via s.b. views\nERROR:  infinite recursion detected in policy for relation \"rec1\"\n--\n-- recursive RLS and VIEWs in policy\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE s1 (a int, b text);\nSELECT public.create_hypertable('s1', 'a', chunk_time_interval=>2);\n      create_hypertable      \n-----------------------------\n (6,regress_rls_schema,s1,t)\n\nINSERT INTO s1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x);\nCREATE TABLE s2 (x int, y text);\nSELECT public.create_hypertable('s2', 'x', chunk_time_interval=>2);\n      create_hypertable      \n-----------------------------\n (7,regress_rls_schema,s2,t)\n\nINSERT INTO s2 (SELECT x, md5(x::text) FROM generate_series(-6,6) x);\nGRANT SELECT ON s1, s2 TO regress_rls_bob;\nCREATE POLICY p1 ON s1 USING (a in (select x from s2 where y like '%2f%'));\nCREATE POLICY p2 ON s2 USING (x in (select a from s1 where b like '%22%'));\nCREATE POLICY p3 ON s1 FOR INSERT WITH CHECK (a = (SELECT a FROM s1));\nALTER TABLE s1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE s2 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE VIEW v2 AS SELECT * FROM s2 WHERE y like '%af%';\nSELECT * FROM s1 WHERE f_leak(b); -- fail (infinite recursion)\nERROR:  infinite recursion detected in policy for relation \"s1\"\nINSERT INTO s1 VALUES (1, 'foo'); -- fail (infinite recursion)\nERROR:  infinite recursion detected in policy for relation \"s1\"\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP POLICY p3 on s1;\nALTER POLICY p2 ON s2 USING (x % 2 = 0);\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM s1 WHERE f_leak(b);\t-- OK\nNOTICE:  f_leak => c81e728d9d4c2f636f067f89cc14862c\nNOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c\n a |                b                 \n---+----------------------------------\n 2 | c81e728d9d4c2f636f067f89cc14862c\n 4 | a87ff679a2f3e71d9181a67b7542122c\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM only s1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Seq Scan on s1\n   Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))\n   SubPlan 1\n     ->  Append\n           ->  Seq Scan on s2 s2_1\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_27_chunk s2_2\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_28_chunk s2_3\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_29_chunk s2_4\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_30_chunk s2_5\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_31_chunk s2_6\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_32_chunk s2_7\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_33_chunk s2_8\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER POLICY p1 ON s1 USING (a in (select x from v2)); -- using VIEW in RLS policy\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM s1 WHERE f_leak(b);\t-- OK\nNOTICE:  f_leak => 0267aaf632e87a63288a08331f22c7c3\nNOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc\n a  |                b                 \n----+----------------------------------\n -4 | 0267aaf632e87a63288a08331f22c7c3\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM s1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on s1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on s1 s1_1\n         Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))\n         SubPlan 1\n           ->  Append\n                 ->  Seq Scan on s2 s2_1\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_27_chunk s2_2\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_28_chunk s2_3\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_29_chunk s2_4\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_30_chunk s2_5\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_31_chunk s2_6\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_32_chunk s2_7\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_33_chunk s2_8\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n   ->  Seq Scan on _hyper_6_16_chunk s1_2\n         Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_17_chunk s1_3\n         Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_18_chunk s1_4\n         Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_19_chunk s1_5\n         Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_20_chunk s1_6\n         Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_21_chunk s1_7\n         Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_22_chunk s1_8\n         Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_23_chunk s1_9\n         Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_24_chunk s1_10\n         Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_25_chunk s1_11\n         Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_26_chunk s1_12\n         Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))\n\nSELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%';\n xx | x  |                y                 \n----+----+----------------------------------\n -6 | -6 | 596a3d04481816330f07e4f97510c28f\n -4 | -4 | 0267aaf632e87a63288a08331f22c7c3\n  2 |  2 | c81e728d9d4c2f636f067f89cc14862c\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%';\n--- QUERY PLAN ---\n Result\n   ->  Append\n         ->  Seq Scan on s2 s2_1\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_27_chunk s2_2\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_28_chunk s2_3\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_29_chunk s2_4\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_30_chunk s2_5\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_31_chunk s2_6\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_32_chunk s2_7\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_33_chunk s2_8\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n   SubPlan 2\n     ->  Limit\n           ->  Result\n                 ->  Custom Scan (ChunkAppend) on s1\n                       ->  Seq Scan on s1 s1_1\n                             Filter: (ANY (a = (hashed SubPlan 1).col1))\n                             SubPlan 1\n                               ->  Append\n                                     ->  Seq Scan on s2 s2_10\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_27_chunk s2_11\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_28_chunk s2_12\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_29_chunk s2_13\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_30_chunk s2_14\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_31_chunk s2_15\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_32_chunk s2_16\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_33_chunk s2_17\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                       ->  Seq Scan on _hyper_6_16_chunk s1_2\n                             Filter: (ANY (a = (hashed SubPlan 1).col1))\n                       ->  Seq Scan on _hyper_6_17_chunk s1_3\n                             Filter: (ANY (a = (hashed SubPlan 1).col1))\n                       ->  Seq Scan on _hyper_6_18_chunk s1_4\n                             Filter: (ANY (a = (hashed SubPlan 1).col1))\n                       ->  Seq Scan on _hyper_6_19_chunk s1_5\n                             Filter: (ANY (a = (hashed SubPlan 1).col1))\n                       ->  Seq Scan on _hyper_6_20_chunk s1_6\n                             Filter: (ANY (a = (hashed SubPlan 1).col1))\n                       ->  Seq Scan on _hyper_6_21_chunk s1_7\n                             Filter: (ANY (a = (hashed SubPlan 1).col1))\n                       ->  Seq Scan on _hyper_6_22_chunk s1_8\n                             Filter: (ANY (a = (hashed SubPlan 1).col1))\n                       ->  Seq Scan on _hyper_6_23_chunk s1_9\n                             Filter: (ANY (a = (hashed SubPlan 1).col1))\n                       ->  Seq Scan on _hyper_6_24_chunk s1_10\n                             Filter: (ANY (a = (hashed SubPlan 1).col1))\n                       ->  Seq Scan on _hyper_6_25_chunk s1_11\n                             Filter: (ANY (a = (hashed SubPlan 1).col1))\n                       ->  Seq Scan on _hyper_6_26_chunk s1_12\n                             Filter: (ANY (a = (hashed SubPlan 1).col1))\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER POLICY p2 ON s2 USING (x in (select a from s1 where b like '%d2%'));\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM s1 WHERE f_leak(b);\t-- fail (infinite recursion via view)\nERROR:  infinite recursion detected in policy for relation \"s1\"\n-- prepared statement with regress_rls_alice privilege\nPREPARE p1(int) AS SELECT * FROM t1 WHERE a <= $1;\nEXECUTE p1(2);\n a |  b  \n---+-----\n 2 | bbb\n 2 | bcd\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE p1(2);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: ((a <= 2) AND ((a % 2) = 0))\n   ->  Seq Scan on t2 t1_2\n         Filter: ((a <= 2) AND ((a % 2) = 0))\n   ->  Seq Scan on t3 t1_3\n         Filter: ((a <= 2) AND ((a % 2) = 0))\n\n-- superuser is allowed to bypass RLS checks\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nSELECT * FROM t1 WHERE f_leak(b);\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => ccc\nNOTICE:  f_leak => dad\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => def\nNOTICE:  f_leak => xxx\nNOTICE:  f_leak => yyy\nNOTICE:  f_leak => zzz\n a |  b  \n---+-----\n 1 | aba\n 2 | bbb\n 3 | ccc\n 4 | dad\n 1 | abc\n 2 | bcd\n 3 | cde\n 4 | def\n 1 | xxx\n 2 | yyy\n 3 | zzz\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: f_leak(b)\n   ->  Seq Scan on t2 t1_2\n         Filter: f_leak(b)\n   ->  Seq Scan on t3 t1_3\n         Filter: f_leak(b)\n\n-- plan cache should be invalidated\nEXECUTE p1(2);\n a |  b  \n---+-----\n 1 | aba\n 2 | bbb\n 1 | abc\n 2 | bcd\n 1 | xxx\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE p1(2);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: (a <= 2)\n   ->  Seq Scan on t2 t1_2\n         Filter: (a <= 2)\n   ->  Seq Scan on t3 t1_3\n         Filter: (a <= 2)\n\nPREPARE p2(int) AS SELECT * FROM t1 WHERE a = $1;\nEXECUTE p2(2);\n a |  b  \n---+-----\n 2 | bbb\n 2 | bcd\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE p2(2);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: (a = 2)\n   ->  Seq Scan on t2 t1_2\n         Filter: (a = 2)\n   ->  Seq Scan on t3 t1_3\n         Filter: (a = 2)\n\n-- also, case when privilege switch from superuser\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO ON;\nEXECUTE p2(2);\n a |  b  \n---+-----\n 2 | bbb\n 2 | bcd\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE p2(2);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: ((a = 2) AND ((a % 2) = 0))\n   ->  Seq Scan on t2 t1_2\n         Filter: ((a = 2) AND ((a % 2) = 0))\n   ->  Seq Scan on t3 t1_3\n         Filter: ((a = 2) AND ((a % 2) = 0))\n\n--\n-- UPDATE / DELETE and Row-level security\n--\nSET SESSION AUTHORIZATION regress_rls_bob;\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t1 SET b = b || b WHERE f_leak(b);\n--- QUERY PLAN ---\n Update on t1\n   Update on t1 t1_1\n   Update on t2 t1_2\n   Update on t3 t1_3\n   ->  Result\n         ->  Append\n               ->  Seq Scan on t1 t1_1\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t2 t1_2\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t3 t1_3\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nUPDATE t1 SET b = b || b WHERE f_leak(b);\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => def\nNOTICE:  f_leak => yyy\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b);\n--- QUERY PLAN ---\n Update on t1\n   ->  Seq Scan on t1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nUPDATE only t1 SET b = b || '_updt' WHERE f_leak(b);\nNOTICE:  f_leak => bbbbbb\nNOTICE:  f_leak => daddad\n-- returning clause with system column\nUPDATE only t1 SET b = b WHERE f_leak(b) RETURNING ctid, *, t1;\nNOTICE:  f_leak => bbbbbb_updt\nNOTICE:  f_leak => daddad_updt\n  ctid  | a |      b      |       t1        \n--------+---+-------------+-----------------\n (0,9)  | 2 | bbbbbb_updt | (2,bbbbbb_updt)\n (0,10) | 4 | daddad_updt | (4,daddad_updt)\n\nUPDATE t1 SET b = b WHERE f_leak(b) RETURNING *;\nNOTICE:  f_leak => bbbbbb_updt\nNOTICE:  f_leak => daddad_updt\nNOTICE:  f_leak => bcdbcd\nNOTICE:  f_leak => defdef\nNOTICE:  f_leak => yyyyyy\n a |      b      \n---+-------------\n 2 | bbbbbb_updt\n 4 | daddad_updt\n 2 | bcdbcd\n 4 | defdef\n 2 | yyyyyy\n\nUPDATE t1 SET b = b WHERE f_leak(b) RETURNING ctid, *, t1;\nNOTICE:  f_leak => bbbbbb_updt\nNOTICE:  f_leak => daddad_updt\nNOTICE:  f_leak => bcdbcd\nNOTICE:  f_leak => defdef\nNOTICE:  f_leak => yyyyyy\n  ctid  | a |      b      |       t1        \n--------+---+-------------+-----------------\n (0,13) | 2 | bbbbbb_updt | (2,bbbbbb_updt)\n (0,14) | 4 | daddad_updt | (4,daddad_updt)\n (0,9)  | 2 | bcdbcd      | (2,bcdbcd)\n (0,10) | 4 | defdef      | (4,defdef)\n (0,6)  | 2 | yyyyyy      | (2,yyyyyy)\n\n-- updates with from clause\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t2 SET b=t2.b FROM t3\nWHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b);\n--- QUERY PLAN ---\n Update on t2\n   ->  Nested Loop\n         ->  Seq Scan on t2\n               Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))\n         ->  Seq Scan on t3\n               Filter: ((a = 2) AND f_leak(b))\n\nUPDATE t2 SET b=t2.b FROM t3\nWHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b);\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => yyyyyy\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t1 SET b=t1.b FROM t2\nWHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);\n--- QUERY PLAN ---\n Update on t1\n   Update on t1 t1_1\n   Update on t2 t1_2\n   Update on t3 t1_3\n   ->  Nested Loop\n         ->  Seq Scan on t2\n               Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))\n         ->  Append\n               ->  Seq Scan on t1 t1_1\n                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t2 t1_2\n                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t3 t1_3\n                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))\n\nUPDATE t1 SET b=t1.b FROM t2\nWHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);\nNOTICE:  f_leak => cde\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t2 SET b=t2.b FROM t1\nWHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);\n--- QUERY PLAN ---\n Update on t2\n   ->  Nested Loop\n         ->  Seq Scan on t2\n               Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))\n         ->  Append\n               ->  Seq Scan on t1 t1_1\n                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t2 t1_2\n                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t3 t1_3\n                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))\n\nUPDATE t2 SET b=t2.b FROM t1\nWHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);\nNOTICE:  f_leak => cde\n-- updates with from clause self join\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2\nWHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b\nAND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2;\n--- QUERY PLAN ---\n Update on t2 t2_1\n   ->  Nested Loop\n         Join Filter: (t2_1.b = t2_2.b)\n         ->  Seq Scan on t2 t2_1\n               Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))\n         ->  Seq Scan on t2 t2_2\n               Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))\n\nUPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2\nWHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b\nAND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2;\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => cde\n a |  b  |  c  | a |  b  |  c  |    t2_1     |    t2_2     \n---+-----+-----+---+-----+-----+-------------+-------------\n 3 | cde | 3.3 | 3 | cde | 3.3 | (3,cde,3.3) | (3,cde,3.3)\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2\nWHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b\nAND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;\n--- QUERY PLAN ---\n Update on t1 t1_1\n   Update on t1 t1_1_1\n   Update on t2 t1_1_2\n   Update on t3 t1_1_3\n   ->  Nested Loop\n         Join Filter: (t1_1.b = t1_2.b)\n         ->  Append\n               ->  Seq Scan on t1 t1_1_1\n                     Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t2 t1_1_2\n                     Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t3 t1_1_3\n                     Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))\n         ->  Materialize\n               ->  Append\n                     ->  Seq Scan on t1 t1_2_1\n                           Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))\n                     ->  Seq Scan on t2 t1_2_2\n                           Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))\n                     ->  Seq Scan on t3 t1_2_3\n                           Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))\n\nUPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2\nWHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b\nAND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;\nNOTICE:  f_leak => daddad_updt\nNOTICE:  f_leak => daddad_updt\nNOTICE:  f_leak => defdef\nNOTICE:  f_leak => defdef\n a |      b      | a |      b      |      t1_1       |      t1_2       \n---+-------------+---+-------------+-----------------+-----------------\n 4 | daddad_updt | 4 | daddad_updt | (4,daddad_updt) | (4,daddad_updt)\n 4 | defdef      | 4 | defdef      | (4,defdef)      | (4,defdef)\n\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nSELECT * FROM t1 ORDER BY a,b;\n a |      b      \n---+-------------\n 1 | aba\n 1 | abc\n 1 | xxx\n 2 | bbbbbb_updt\n 2 | bcdbcd\n 2 | yyyyyy\n 3 | ccc\n 3 | cde\n 3 | zzz\n 4 | daddad_updt\n 4 | defdef\n\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO ON;\nEXPLAIN (BUFFERS OFF, COSTS OFF) DELETE FROM only t1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Delete on t1\n   ->  Seq Scan on t1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) DELETE FROM t1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Delete on t1\n   Delete on t1 t1_1\n   Delete on t2 t1_2\n   Delete on t3 t1_3\n   ->  Append\n         ->  Seq Scan on t1 t1_1\n               Filter: (((a % 2) = 0) AND f_leak(b))\n         ->  Seq Scan on t2 t1_2\n               Filter: (((a % 2) = 0) AND f_leak(b))\n         ->  Seq Scan on t3 t1_3\n               Filter: (((a % 2) = 0) AND f_leak(b))\n\nDELETE FROM only t1 WHERE f_leak(b) RETURNING ctid, *, t1;\nNOTICE:  f_leak => bbbbbb_updt\nNOTICE:  f_leak => daddad_updt\n  ctid  | a |      b      |       t1        \n--------+---+-------------+-----------------\n (0,13) | 2 | bbbbbb_updt | (2,bbbbbb_updt)\n (0,15) | 4 | daddad_updt | (4,daddad_updt)\n\nDELETE FROM t1 WHERE f_leak(b) RETURNING ctid, *, t1;\nNOTICE:  f_leak => bcdbcd\nNOTICE:  f_leak => defdef\nNOTICE:  f_leak => yyyyyy\n  ctid  | a |   b    |     t1     \n--------+---+--------+------------\n (0,9)  | 2 | bcdbcd | (2,bcdbcd)\n (0,13) | 4 | defdef | (4,defdef)\n (0,6)  | 2 | yyyyyy | (2,yyyyyy)\n\n--\n-- S.b. view on top of Row-level security\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE b1 (a int, b text);\nSELECT public.create_hypertable('b1', 'a', chunk_time_interval=>2);\n      create_hypertable      \n-----------------------------\n (8,regress_rls_schema,b1,t)\n\nINSERT INTO b1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x);\nCREATE POLICY p1 ON b1 USING (a % 2 = 0);\nALTER TABLE b1 ENABLE ROW LEVEL SECURITY;\nGRANT ALL ON b1 TO regress_rls_bob;\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE VIEW bv1 WITH (security_barrier) AS SELECT * FROM b1 WHERE a > 0 WITH CHECK OPTION;\nGRANT ALL ON bv1 TO regress_rls_carol;\nSET SESSION AUTHORIZATION regress_rls_carol;\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM bv1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Subquery Scan on bv1\n   Filter: f_leak(bv1.b)\n   ->  Append\n         ->  Seq Scan on b1 b1_1\n               Filter: ((a > 0) AND ((a % 2) = 0))\n         ->  Index Scan using _hyper_8_39_chunk_b1_a_idx on _hyper_8_39_chunk b1_2\n               Index Cond: (a > 0)\n               Filter: ((a % 2) = 0)\n         ->  Index Scan using _hyper_8_40_chunk_b1_a_idx on _hyper_8_40_chunk b1_3\n               Index Cond: (a > 0)\n               Filter: ((a % 2) = 0)\n         ->  Index Scan using _hyper_8_41_chunk_b1_a_idx on _hyper_8_41_chunk b1_4\n               Index Cond: (a > 0)\n               Filter: ((a % 2) = 0)\n         ->  Index Scan using _hyper_8_42_chunk_b1_a_idx on _hyper_8_42_chunk b1_5\n               Index Cond: (a > 0)\n               Filter: ((a % 2) = 0)\n         ->  Index Scan using _hyper_8_43_chunk_b1_a_idx on _hyper_8_43_chunk b1_6\n               Index Cond: (a > 0)\n               Filter: ((a % 2) = 0)\n         ->  Index Scan using _hyper_8_44_chunk_b1_a_idx on _hyper_8_44_chunk b1_7\n               Index Cond: (a > 0)\n               Filter: ((a % 2) = 0)\n\nSELECT * FROM bv1 WHERE f_leak(b);\nNOTICE:  f_leak => c81e728d9d4c2f636f067f89cc14862c\nNOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c\nNOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc\nNOTICE:  f_leak => c9f0f895fb98ab9159f51fd0297e236d\nNOTICE:  f_leak => d3d9446802a44259755d38e6d163e820\n a  |                b                 \n----+----------------------------------\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 10 | d3d9446802a44259755d38e6d163e820\n\nINSERT INTO bv1 VALUES (-1, 'xxx'); -- should fail view WCO\nERROR:  new row violates row-level security policy for table \"b1\"\nINSERT INTO bv1 VALUES (11, 'xxx'); -- should fail RLS check\nERROR:  new row violates row-level security policy for table \"b1\"\nINSERT INTO bv1 VALUES (12, 'xxx'); -- ok\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Update on b1\n         Update on _hyper_8_41_chunk b1_1\n         ->  Result\n               ->  Custom Scan (ChunkAppend) on b1\n                     Chunks excluded during startup: 0\n                     ->  Index Scan using _hyper_8_41_chunk_b1_a_idx on _hyper_8_41_chunk b1_1\n                           Index Cond: ((a > 0) AND (a = 4))\n                           Filter: (((a % 2) = 0) AND f_leak(b))\n\nUPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b);\nNOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c\nEXPLAIN (BUFFERS OFF, COSTS OFF) DELETE FROM bv1 WHERE a = 6 AND f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Delete on b1\n         Delete on _hyper_8_42_chunk b1_1\n         ->  Custom Scan (ChunkAppend) on b1\n               Chunks excluded during startup: 0\n               ->  Index Scan using _hyper_8_42_chunk_b1_a_idx on _hyper_8_42_chunk b1_1\n                     Index Cond: ((a > 0) AND (a = 6))\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nDELETE FROM bv1 WHERE a = 6 AND f_leak(b);\nNOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc\nSET SESSION AUTHORIZATION regress_rls_alice;\nSELECT * FROM b1;\n  a  |                b                 \n-----+----------------------------------\n -10 | 1b0fd9efa5279c4203b7c70233f86dbf\n  -9 | 252e691406782824eec43d7eadc3d256\n  -8 | a8d2ec85eaf98407310b72eb73dda247\n  -7 | 74687a12d3915d3c4d83f1af7b3683d5\n  -6 | 596a3d04481816330f07e4f97510c28f\n  -5 | 47c1b025fa18ea96c33fbb6718688c0f\n  -4 | 0267aaf632e87a63288a08331f22c7c3\n  -3 | b3149ecea4628efd23d2f86e5a723472\n  -2 | 5d7b9adcbe1c629ec722529dd12e5129\n  -1 | 6bb61e3b7bce0931da574d19d1d82c88\n   0 | cfcd208495d565ef66e7dff9f98764da\n   1 | c4ca4238a0b923820dcc509a6f75849b\n   2 | c81e728d9d4c2f636f067f89cc14862c\n   3 | eccbc87e4b5ce2fe28308fd9f2a7baf3\n   5 | e4da3b7fbbce2345d7772b0674a318d5\n   4 | yyy\n   7 | 8f14e45fceea167a5a36dedd4bea2543\n   8 | c9f0f895fb98ab9159f51fd0297e236d\n   9 | 45c48cce2e2d7fbdea1afc51c7c6ad26\n  10 | d3d9446802a44259755d38e6d163e820\n  12 | xxx\n\n--\n-- INSERT ... ON CONFLICT DO UPDATE and Row-level security\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP POLICY p1 ON document;\nDROP POLICY p1r ON document;\nCREATE POLICY p1 ON document FOR SELECT USING (true);\nCREATE POLICY p2 ON document FOR INSERT WITH CHECK (dauthor = current_user);\nCREATE POLICY p3 ON document FOR UPDATE\n  USING (cid = (SELECT cid from category WHERE cname = 'novel'))\n  WITH CHECK (dauthor = current_user);\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Exists...\nSELECT * FROM document WHERE did = 2;\n did | cid | dlevel |     dauthor     |     dtitle      \n-----+-----+--------+-----------------+-----------------\n   2 |  11 |      2 | regress_rls_bob | my second novel\n\n-- ...so violates actual WITH CHECK OPTION within UPDATE (not INSERT, since\n-- alternative UPDATE path happens to be taken):\nINSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_carol', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, dauthor = EXCLUDED.dauthor;\nERROR:  new row violates row-level security policy for table \"document\"\n-- Violates USING qual for UPDATE policy p3.\n--\n-- UPDATE path is taken, but UPDATE fails purely because *existing* row to be\n-- updated is not a \"novel\"/cid 11 (row is not leaked, even though we have\n-- SELECT privileges sufficient to see the row in this instance):\nINSERT INTO document VALUES (33, 22, 1, 'regress_rls_bob', 'okay science fiction'); -- preparation for next statement\nINSERT INTO document VALUES (33, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'Some novel, replaces sci-fi') -- takes UPDATE path\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle;\nERROR:  new row violates row-level security policy (USING expression) for table \"document\"\n-- Fine (we UPDATE, since INSERT WCOs and UPDATE security barrier quals + WCOs\n-- not violated):\nINSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;\n did | cid | dlevel |     dauthor     |     dtitle     \n-----+-----+--------+-----------------+----------------\n   2 |  11 |      2 | regress_rls_bob | my first novel\n\n-- Fine (we INSERT, so \"cid = 33\" (\"technology\") isn't evaluated):\nINSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *;\n did | cid | dlevel |     dauthor     |        dtitle         \n-----+-----+--------+-----------------+-----------------------\n  78 |  11 |      1 | regress_rls_bob | some technology novel\n\n-- Fine (same query, but we UPDATE, so \"cid = 33\", (\"technology\") is not the\n-- case in respect of *existing* tuple):\nINSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *;\n did | cid | dlevel |     dauthor     |        dtitle         \n-----+-----+--------+-----------------+-----------------------\n  78 |  33 |      1 | regress_rls_bob | some technology novel\n\n-- Same query a third time, but now fails due to existing tuple finally not\n-- passing quals:\nINSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *;\nERROR:  new row violates row-level security policy (USING expression) for table \"document\"\n-- Don't fail just because INSERT doesn't satisfy WITH CHECK option that\n-- originated as a barrier/USING() qual from the UPDATE.  Note that the UPDATE\n-- path *isn't* taken, and so UPDATE-related policy does not apply:\nINSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;\n did | cid | dlevel |     dauthor     |              dtitle              \n-----+-----+--------+-----------------+----------------------------------\n  79 |  33 |      1 | regress_rls_bob | technology book, can only insert\n\n-- But this time, the same statement fails, because the UPDATE path is taken,\n-- and updating the row just inserted falls afoul of security barrier qual\n-- (enforced as WCO) -- what we might have updated target tuple to is\n-- irrelevant, in fact.\nINSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;\nERROR:  new row violates row-level security policy (USING expression) for table \"document\"\n-- Test default USING qual enforced as WCO\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP POLICY p1 ON document;\nDROP POLICY p2 ON document;\nDROP POLICY p3 ON document;\nCREATE POLICY p3_with_default ON document FOR UPDATE\n  USING (cid = (SELECT cid from category WHERE cname = 'novel'));\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Just because WCO-style enforcement of USING quals occurs with\n-- existing/target tuple does not mean that the implementation can be allowed\n-- to fail to also enforce this qual against the final tuple appended to\n-- relation (since in the absence of an explicit WCO, this is also interpreted\n-- as an UPDATE/ALL WCO in general).\n--\n-- UPDATE path is taken here (fails due to existing tuple).  Note that this is\n-- not reported as a \"USING expression\", because it's an RLS UPDATE check that originated as\n-- a USING qual for the purposes of RLS in general, as opposed to an explicit\n-- USING qual that is ordinarily a security barrier.  We leave it up to the\n-- UPDATE to make this fail:\nINSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;\nERROR:  new row violates row-level security policy for table \"document\"\n-- UPDATE path is taken here.  Existing tuple passes, since it's cid\n-- corresponds to \"novel\", but default USING qual is enforced against\n-- post-UPDATE tuple too (as always when updating with a policy that lacks an\n-- explicit WCO), and so this fails:\nINSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET cid = EXCLUDED.cid, dtitle = EXCLUDED.dtitle RETURNING *;\nERROR:  new row violates row-level security policy for table \"document\"\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP POLICY p3_with_default ON document;\n--\n-- Test ALL policies with ON CONFLICT DO UPDATE (much the same as existing UPDATE\n-- tests)\n--\nCREATE POLICY p3_with_all ON document FOR ALL\n  USING (cid = (SELECT cid from category WHERE cname = 'novel'))\n  WITH CHECK (dauthor = current_user);\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Fails, since ALL WCO is enforced in insert path:\nINSERT INTO document VALUES (80, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_carol', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33;\nERROR:  new row violates row-level security policy for table \"document\"\n-- Fails, since ALL policy USING qual is enforced (existing, target tuple is in\n-- violation, since it has the \"manga\" cid):\nINSERT INTO document VALUES (4, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle;\nERROR:  new row violates row-level security policy (USING expression) for table \"document\"\n-- Fails, since ALL WCO are enforced:\nINSERT INTO document VALUES (1, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET dauthor = 'regress_rls_carol';\nERROR:  new row violates row-level security policy for table \"document\"\n--\n-- ROLE/GROUP\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE z1 (a int, b text);\nSELECT public.create_hypertable('z1', 'a', chunk_time_interval=>2);\n      create_hypertable      \n-----------------------------\n (9,regress_rls_schema,z1,t)\n\nCREATE TABLE z2 (a int, b text);\nSELECT public.create_hypertable('z2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (10,regress_rls_schema,z2,t)\n\nGRANT SELECT ON z1,z2 TO regress_rls_group1, regress_rls_group2,\n    regress_rls_bob, regress_rls_carol;\nINSERT INTO z1 VALUES\n    (1, 'aba'),\n    (2, 'bbb'),\n    (3, 'ccc'),\n    (4, 'dad');\nCREATE POLICY p1 ON z1 TO regress_rls_group1 USING (a % 2 = 0);\nCREATE POLICY p2 ON z1 TO regress_rls_group2 USING (a % 2 = 1);\nALTER TABLE z1 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM z1 WHERE f_leak(b);\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nPREPARE plancache_test AS SELECT * FROM z1 WHERE f_leak(b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nPREPARE plancache_test2 AS WITH q AS MATERIALIZED (SELECT * FROM z1 WHERE f_leak(b)) SELECT * FROM q,z2;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test2;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nPREPARE plancache_test4 AS WITH q AS (SELECT * FROM z1 WHERE f_leak(b)) SELECT * FROM q,z2;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test4;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nPREPARE plancache_test6 AS WITH q AS NOT MATERIALIZED (SELECT * FROM z1 WHERE f_leak(b)) SELECT * FROM q,z2;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test6;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nPREPARE plancache_test3 AS WITH q AS MATERIALIZED (SELECT * FROM z2) SELECT * FROM q,z1 WHERE f_leak(z1.b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test3;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Seq Scan on z2\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nPREPARE plancache_test5 AS WITH q AS (SELECT * FROM z2) SELECT * FROM q,z1 WHERE f_leak(z1.b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test5;\n--- QUERY PLAN ---\n Nested Loop\n   ->  Seq Scan on z2\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nPREPARE plancache_test7 AS WITH q AS NOT MATERIALIZED (SELECT * FROM z2) SELECT * FROM q,z1 WHERE f_leak(z1.b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test7;\n--- QUERY PLAN ---\n Nested Loop\n   ->  Seq Scan on z2\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nSET ROLE regress_rls_group1;\nSELECT * FROM z1 WHERE f_leak(b);\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test2;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test4;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test3;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Seq Scan on z2\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test5;\n--- QUERY PLAN ---\n Nested Loop\n   ->  Seq Scan on z2\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM z1 WHERE f_leak(b);\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => ccc\n a |  b  \n---+-----\n 1 | aba\n 3 | ccc\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 1) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 1) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test2;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test4;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test3;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Seq Scan on z2\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test5;\n--- QUERY PLAN ---\n Nested Loop\n   ->  Seq Scan on z2\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n\nSET ROLE regress_rls_group2;\nSELECT * FROM z1 WHERE f_leak(b);\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => ccc\n a |  b  \n---+-----\n 1 | aba\n 3 | ccc\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 1) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 1) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test2;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test4;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test3;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Seq Scan on z2\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test5;\n--- QUERY PLAN ---\n Nested Loop\n   ->  Seq Scan on z2\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n\n--\n-- Views should follow policy for view owner.\n--\n-- View and Table owner are the same.\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b);\nGRANT SELECT ON rls_view TO regress_rls_bob;\n-- Query as role that is not owner of view or table.  Should return all records.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rls_view;\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => ccc\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 1 | aba\n 2 | bbb\n 3 | ccc\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: f_leak(b)\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: f_leak(b)\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: f_leak(b)\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: f_leak(b)\n\n-- Query as view/table owner.  Should return all records.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSELECT * FROM rls_view;\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => ccc\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 1 | aba\n 2 | bbb\n 3 | ccc\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: f_leak(b)\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: f_leak(b)\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: f_leak(b)\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: f_leak(b)\n\nDROP VIEW rls_view;\n-- View and Table owners are different.\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b);\nGRANT SELECT ON rls_view TO regress_rls_alice;\n-- Query as role that is not owner of view but is owner of table.\n-- Should return records based on view owner policies.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSELECT * FROM rls_view;\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\n-- Query as role that is not owner of table but is owner of view.\n-- Should return records based on view owner policies.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rls_view;\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\n-- Query as role that is not the owner of the table or view without permissions.\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM rls_view; --fail - permission denied.\nERROR:  permission denied for view rls_view\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.\nERROR:  permission denied for view rls_view\n-- Query as role that is not the owner of the table or view with permissions.\nSET SESSION AUTHORIZATION regress_rls_bob;\nGRANT SELECT ON rls_view TO regress_rls_carol;\nSELECT * FROM rls_view;\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nSET SESSION AUTHORIZATION regress_rls_bob;\nDROP VIEW rls_view;\n--\n-- Command specific\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE x1 (a int, b text, c text);\nSELECT public.create_hypertable('x1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (11,regress_rls_schema,x1,t)\n\nGRANT ALL ON x1 TO PUBLIC;\nINSERT INTO x1 VALUES\n    (1, 'abc', 'regress_rls_bob'),\n    (2, 'bcd', 'regress_rls_bob'),\n    (3, 'cde', 'regress_rls_carol'),\n    (4, 'def', 'regress_rls_carol'),\n    (5, 'efg', 'regress_rls_bob'),\n    (6, 'fgh', 'regress_rls_bob'),\n    (7, 'fgh', 'regress_rls_carol'),\n    (8, 'fgh', 'regress_rls_carol');\nCREATE POLICY p0 ON x1 FOR ALL USING (c = current_user);\nCREATE POLICY p1 ON x1 FOR SELECT USING (a % 2 = 0);\nCREATE POLICY p2 ON x1 FOR INSERT WITH CHECK (a % 2 = 1);\nCREATE POLICY p3 ON x1 FOR UPDATE USING (a % 2 = 0);\nCREATE POLICY p4 ON x1 FOR DELETE USING (a < 8);\nALTER TABLE x1 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM x1 WHERE f_leak(b) ORDER BY a ASC;\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => def\nNOTICE:  f_leak => efg\nNOTICE:  f_leak => fgh\nNOTICE:  f_leak => fgh\n a |  b  |         c         \n---+-----+-------------------\n 1 | abc | regress_rls_bob\n 2 | bcd | regress_rls_bob\n 4 | def | regress_rls_carol\n 5 | efg | regress_rls_bob\n 6 | fgh | regress_rls_bob\n 8 | fgh | regress_rls_carol\n\nUPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *;\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => def\nNOTICE:  f_leak => efg\nNOTICE:  f_leak => fgh\nNOTICE:  f_leak => fgh\n a |    b     |         c         \n---+----------+-------------------\n 1 | abc_updt | regress_rls_bob\n 2 | bcd_updt | regress_rls_bob\n 4 | def_updt | regress_rls_carol\n 5 | efg_updt | regress_rls_bob\n 6 | fgh_updt | regress_rls_bob\n 8 | fgh_updt | regress_rls_carol\n\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM x1 WHERE f_leak(b) ORDER BY a ASC;\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => bcd_updt\nNOTICE:  f_leak => def_updt\nNOTICE:  f_leak => fgh\nNOTICE:  f_leak => fgh_updt\nNOTICE:  f_leak => fgh_updt\n a |    b     |         c         \n---+----------+-------------------\n 2 | bcd_updt | regress_rls_bob\n 3 | cde      | regress_rls_carol\n 4 | def_updt | regress_rls_carol\n 6 | fgh_updt | regress_rls_bob\n 7 | fgh      | regress_rls_carol\n 8 | fgh_updt | regress_rls_carol\n\nUPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *;\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => bcd_updt\nNOTICE:  f_leak => def_updt\nNOTICE:  f_leak => fgh\nNOTICE:  f_leak => fgh_updt\nNOTICE:  f_leak => fgh_updt\n a |       b       |         c         \n---+---------------+-------------------\n 3 | cde_updt      | regress_rls_carol\n 2 | bcd_updt_updt | regress_rls_bob\n 4 | def_updt_updt | regress_rls_carol\n 7 | fgh_updt      | regress_rls_carol\n 6 | fgh_updt_updt | regress_rls_bob\n 8 | fgh_updt_updt | regress_rls_carol\n\nDELETE FROM x1 WHERE f_leak(b) RETURNING *;\nNOTICE:  f_leak => cde_updt\nNOTICE:  f_leak => bcd_updt_updt\nNOTICE:  f_leak => def_updt_updt\nNOTICE:  f_leak => fgh_updt\nNOTICE:  f_leak => fgh_updt_updt\nNOTICE:  f_leak => fgh_updt_updt\n a |       b       |         c         \n---+---------------+-------------------\n 3 | cde_updt      | regress_rls_carol\n 2 | bcd_updt_updt | regress_rls_bob\n 4 | def_updt_updt | regress_rls_carol\n 7 | fgh_updt      | regress_rls_carol\n 6 | fgh_updt_updt | regress_rls_bob\n 8 | fgh_updt_updt | regress_rls_carol\n\n--\n-- Duplicate Policy Names\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE y1 (a int, b text);\nSELECT public.create_hypertable('y1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (12,regress_rls_schema,y1,t)\n\nINSERT INTO y1 VALUES(1,2);\nCREATE TABLE y2 (a int, b text);\nSELECT public.create_hypertable('y2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (13,regress_rls_schema,y2,t)\n\nGRANT ALL ON y1, y2 TO regress_rls_bob;\nCREATE POLICY p1 ON y1 FOR ALL USING (a % 2 = 0);\nCREATE POLICY p2 ON y1 FOR SELECT USING (a > 2);\nCREATE POLICY p1 ON y1 FOR SELECT USING (a % 2 = 1);  --fail\nERROR:  policy \"p1\" for table \"y1\" already exists\nCREATE POLICY p1 ON y2 FOR ALL USING (a % 2 = 0);  --OK\nALTER TABLE y1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE y2 ENABLE ROW LEVEL SECURITY;\n--\n-- Expression structure with SBV\n--\n-- Create view as table owner.  RLS should NOT be applied.\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE VIEW rls_sbv WITH (security_barrier) AS\n    SELECT * FROM y1 WHERE f_leak(b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on y1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on y1 y1_1\n         Filter: (f_leak(b) AND (a = 1))\n   ->  Index Scan using _hyper_12_57_chunk_y1_a_idx on _hyper_12_57_chunk y1_2\n         Index Cond: (a = 1)\n         Filter: f_leak(b)\n\nDROP VIEW rls_sbv;\n-- Create view as role that does not own table.  RLS should be applied.\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE VIEW rls_sbv WITH (security_barrier) AS\n    SELECT * FROM y1 WHERE f_leak(b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on y1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on y1 y1_1\n         Filter: ((a = 1) AND ((a > 2) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Index Scan using _hyper_12_57_chunk_y1_a_idx on _hyper_12_57_chunk y1_2\n         Index Cond: (a = 1)\n         Filter: (((a > 2) OR ((a % 2) = 0)) AND f_leak(b))\n\nDROP VIEW rls_sbv;\n--\n-- Expression structure\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nINSERT INTO y2 (SELECT x, md5(x::text) FROM generate_series(0,20) x);\nCREATE POLICY p2 ON y2 USING (a % 3 = 0);\nCREATE POLICY p3 ON y2 USING (a % 4 = 0);\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM y2 WHERE f_leak(b);\nNOTICE:  f_leak => cfcd208495d565ef66e7dff9f98764da\nNOTICE:  f_leak => c81e728d9d4c2f636f067f89cc14862c\nNOTICE:  f_leak => eccbc87e4b5ce2fe28308fd9f2a7baf3\nNOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c\nNOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc\nNOTICE:  f_leak => c9f0f895fb98ab9159f51fd0297e236d\nNOTICE:  f_leak => 45c48cce2e2d7fbdea1afc51c7c6ad26\nNOTICE:  f_leak => d3d9446802a44259755d38e6d163e820\nNOTICE:  f_leak => c20ad4d76fe97759aa27a0c99bff6710\nNOTICE:  f_leak => aab3238922bcc25a6f606eb525ffdc56\nNOTICE:  f_leak => 9bf31c7ff062936a96d3c8bd1f8f2ff3\nNOTICE:  f_leak => c74d97b01eae257e44aa9d5bade97baf\nNOTICE:  f_leak => 6f4922f45568161a8cdf4ad2299f6d23\nNOTICE:  f_leak => 98f13708210194c475687be6106a3b84\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  3 | eccbc87e4b5ce2fe28308fd9f2a7baf3\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n  9 | 45c48cce2e2d7fbdea1afc51c7c6ad26\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM y2 WHERE f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on y2\n   Chunks excluded during startup: 0\n   ->  Seq Scan on y2 y2_1\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_58_chunk y2_2\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_59_chunk y2_3\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_60_chunk y2_4\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_61_chunk y2_5\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_62_chunk y2_6\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_63_chunk y2_7\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_64_chunk y2_8\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_65_chunk y2_9\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_66_chunk y2_10\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_67_chunk y2_11\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_68_chunk y2_12\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n\n--\n-- Qual push-down of leaky functions, when not referring to table\n--\nSELECT * FROM y2 WHERE f_leak('abc');\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  3 | eccbc87e4b5ce2fe28308fd9f2a7baf3\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n  9 | 45c48cce2e2d7fbdea1afc51c7c6ad26\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM y2 WHERE f_leak('abc');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on y2\n   Chunks excluded during startup: 0\n   ->  Seq Scan on y2 y2_1\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_58_chunk y2_2\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_59_chunk y2_3\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_60_chunk y2_4\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_61_chunk y2_5\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_62_chunk y2_6\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_63_chunk y2_7\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_64_chunk y2_8\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_65_chunk y2_9\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_66_chunk y2_10\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_67_chunk y2_11\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_68_chunk y2_12\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n\nCREATE TABLE test_qual_pushdown (\n    abc text\n);\nINSERT INTO test_qual_pushdown VALUES ('abc'),('def');\nSELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc);\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => def\n a | b | abc \n---+---+-----\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc);\n--- QUERY PLAN ---\n Hash Join\n   Hash Cond: (y2.b = test_qual_pushdown.abc)\n   ->  Append\n         ->  Seq Scan on y2 y2_1\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_58_chunk y2_2\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_59_chunk y2_3\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_60_chunk y2_4\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_61_chunk y2_5\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_62_chunk y2_6\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_63_chunk y2_7\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_64_chunk y2_8\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_65_chunk y2_9\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_66_chunk y2_10\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_67_chunk y2_11\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_68_chunk y2_12\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n   ->  Hash\n         ->  Seq Scan on test_qual_pushdown\n               Filter: f_leak(abc)\n\nSELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b);\nNOTICE:  f_leak => cfcd208495d565ef66e7dff9f98764da\nNOTICE:  f_leak => c81e728d9d4c2f636f067f89cc14862c\nNOTICE:  f_leak => eccbc87e4b5ce2fe28308fd9f2a7baf3\nNOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c\nNOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc\nNOTICE:  f_leak => c9f0f895fb98ab9159f51fd0297e236d\nNOTICE:  f_leak => 45c48cce2e2d7fbdea1afc51c7c6ad26\nNOTICE:  f_leak => d3d9446802a44259755d38e6d163e820\nNOTICE:  f_leak => c20ad4d76fe97759aa27a0c99bff6710\nNOTICE:  f_leak => aab3238922bcc25a6f606eb525ffdc56\nNOTICE:  f_leak => 9bf31c7ff062936a96d3c8bd1f8f2ff3\nNOTICE:  f_leak => c74d97b01eae257e44aa9d5bade97baf\nNOTICE:  f_leak => 6f4922f45568161a8cdf4ad2299f6d23\nNOTICE:  f_leak => 98f13708210194c475687be6106a3b84\n a | b | abc \n---+---+-----\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b);\n--- QUERY PLAN ---\n Hash Join\n   Hash Cond: (test_qual_pushdown.abc = y2.b)\n   ->  Seq Scan on test_qual_pushdown\n   ->  Hash\n         ->  Custom Scan (ChunkAppend) on y2\n               Chunks excluded during startup: 0\n               ->  Seq Scan on y2 y2_1\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_58_chunk y2_2\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_59_chunk y2_3\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_60_chunk y2_4\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_61_chunk y2_5\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_62_chunk y2_6\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_63_chunk y2_7\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_64_chunk y2_8\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_65_chunk y2_9\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_66_chunk y2_10\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_67_chunk y2_11\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_68_chunk y2_12\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n\nDROP TABLE test_qual_pushdown;\n--\n-- Plancache invalidate on user change.\n--\nRESET SESSION AUTHORIZATION;\n\\set VERBOSITY terse \\\\ -- suppress cascade details\nDROP TABLE t1 CASCADE;\nNOTICE:  drop cascades to 2 other objects\n\\set VERBOSITY default\nCREATE TABLE t1 (a integer);\nSELECT public.create_hypertable('t1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (14,regress_rls_schema,t1,t)\n\nGRANT SELECT ON t1 TO regress_rls_bob, regress_rls_carol;\nCREATE POLICY p1 ON t1 TO regress_rls_bob USING ((a % 2) = 0);\nCREATE POLICY p2 ON t1 TO regress_rls_carol USING ((a % 4) = 0);\nALTER TABLE t1 ENABLE ROW LEVEL SECURITY;\n-- Prepare as regress_rls_bob\nSET ROLE regress_rls_bob;\nPREPARE role_inval AS SELECT * FROM t1;\n-- Check plan\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE role_inval;\n--- QUERY PLAN ---\n Seq Scan on t1\n   Filter: ((a % 2) = 0)\n\n-- Change to regress_rls_carol\nSET ROLE regress_rls_carol;\n-- Check plan- should be different\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE role_inval;\n--- QUERY PLAN ---\n Seq Scan on t1\n   Filter: ((a % 4) = 0)\n\n-- Change back to regress_rls_bob\nSET ROLE regress_rls_bob;\n-- Check plan- should be back to original\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE role_inval;\n--- QUERY PLAN ---\n Seq Scan on t1\n   Filter: ((a % 2) = 0)\n\n--\n-- CTE and RLS\n--\nRESET SESSION AUTHORIZATION;\nDROP TABLE t1 CASCADE;\nCREATE TABLE t1 (a integer, b text);\nSELECT public.create_hypertable('t1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (15,regress_rls_schema,t1,t)\n\nCREATE POLICY p1 ON t1 USING (a % 2 = 0);\nALTER TABLE t1 ENABLE ROW LEVEL SECURITY;\nGRANT ALL ON t1 TO regress_rls_bob;\nINSERT INTO t1 (SELECT x, md5(x::text) FROM generate_series(0,20) x);\nSET SESSION AUTHORIZATION regress_rls_bob;\nWITH cte1 AS (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1;\nNOTICE:  f_leak => cfcd208495d565ef66e7dff9f98764da\nNOTICE:  f_leak => c81e728d9d4c2f636f067f89cc14862c\nNOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c\nNOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc\nNOTICE:  f_leak => c9f0f895fb98ab9159f51fd0297e236d\nNOTICE:  f_leak => d3d9446802a44259755d38e6d163e820\nNOTICE:  f_leak => c20ad4d76fe97759aa27a0c99bff6710\nNOTICE:  f_leak => aab3238922bcc25a6f606eb525ffdc56\nNOTICE:  f_leak => c74d97b01eae257e44aa9d5bade97baf\nNOTICE:  f_leak => 6f4922f45568161a8cdf4ad2299f6d23\nNOTICE:  f_leak => 98f13708210194c475687be6106a3b84\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) WITH cte1 AS (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1;\n--- QUERY PLAN ---\n CTE Scan on cte1\n   CTE cte1\n     ->  Custom Scan (ChunkAppend) on t1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on t1 t1_1\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_69_chunk t1_2\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_70_chunk t1_3\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_71_chunk t1_4\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_72_chunk t1_5\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_73_chunk t1_6\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_74_chunk t1_7\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_75_chunk t1_8\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_76_chunk t1_9\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_77_chunk t1_10\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_78_chunk t1_11\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_79_chunk t1_12\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n\nWITH cte1 AS (UPDATE t1 SET a = a + 1 RETURNING *) SELECT * FROM cte1; --fail\nERROR:  new row violates row-level security policy for table \"t1\"\nWITH cte1 AS (UPDATE t1 SET a = a RETURNING *) SELECT * FROM cte1; --ok\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n\nWITH cte1 AS (INSERT INTO t1 VALUES (21, 'Fail') RETURNING *) SELECT * FROM cte1; --fail\nERROR:  new row violates row-level security policy for table \"t1\"\nWITH cte1 AS (INSERT INTO t1 VALUES (20, 'Success') RETURNING *) SELECT * FROM cte1; --ok\n a  |    b    \n----+---------\n 20 | Success\n\n--\n-- Rename Policy\n--\nRESET SESSION AUTHORIZATION;\nALTER POLICY p1 ON t1 RENAME TO p1; --fail\nERROR:  policy \"p1\" for table \"t1\" already exists\nSELECT polname, relname\n    FROM pg_policy pol\n    JOIN pg_class pc ON (pc.oid = pol.polrelid)\n    WHERE relname = 't1';\n polname | relname \n---------+---------\n p1      | t1\n\nALTER POLICY p1 ON t1 RENAME TO p2; --ok\nSELECT polname, relname\n    FROM pg_policy pol\n    JOIN pg_class pc ON (pc.oid = pol.polrelid)\n    WHERE relname = 't1';\n polname | relname \n---------+---------\n p2      | t1\n\n--\n-- Check INSERT SELECT\n--\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE TABLE t2 (a integer, b text);\nSELECT public.create_hypertable('t2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (16,regress_rls_schema,t2,t)\n\nINSERT INTO t2 (SELECT * FROM t1);\nEXPLAIN (BUFFERS OFF, COSTS OFF) INSERT INTO t2 (SELECT * FROM t1);\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on t2\n         ->  Append\n               ->  Seq Scan on t1 t1_1\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_69_chunk t1_2\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_70_chunk t1_3\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_71_chunk t1_4\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_72_chunk t1_5\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_73_chunk t1_6\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_74_chunk t1_7\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_75_chunk t1_8\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_76_chunk t1_9\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_77_chunk t1_10\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_78_chunk t1_11\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_79_chunk t1_12\n                     Filter: ((a % 2) = 0)\n\nSELECT * FROM t2;\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n 20 | Success\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t2;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t2 t2_1\n   ->  Seq Scan on _hyper_16_80_chunk t2_2\n   ->  Seq Scan on _hyper_16_81_chunk t2_3\n   ->  Seq Scan on _hyper_16_82_chunk t2_4\n   ->  Seq Scan on _hyper_16_83_chunk t2_5\n   ->  Seq Scan on _hyper_16_84_chunk t2_6\n   ->  Seq Scan on _hyper_16_85_chunk t2_7\n   ->  Seq Scan on _hyper_16_86_chunk t2_8\n   ->  Seq Scan on _hyper_16_87_chunk t2_9\n   ->  Seq Scan on _hyper_16_88_chunk t2_10\n   ->  Seq Scan on _hyper_16_89_chunk t2_11\n   ->  Seq Scan on _hyper_16_90_chunk t2_12\n\nCREATE TABLE t3 AS SELECT * FROM t1;\nSELECT public.create_hypertable('t2', 'a', chunk_time_interval=>2);\nERROR:  table \"t2\" is already a hypertable\nSELECT * FROM t3;\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n 20 | Success\n\nSELECT * INTO t4 FROM t1;\nSELECT * FROM t4;\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n 20 | Success\n\n--\n-- RLS with JOIN\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE blog (id integer, author text, post text);\nSELECT public.create_hypertable('blog', 'id', chunk_time_interval=>2);\n       create_hypertable        \n--------------------------------\n (17,regress_rls_schema,blog,t)\n\nCREATE TABLE comment (blog_id integer, message text);\nSELECT public.create_hypertable('comment', 'blog_id', chunk_time_interval=>2);\n         create_hypertable         \n-----------------------------------\n (18,regress_rls_schema,comment,t)\n\nGRANT ALL ON blog, comment TO regress_rls_bob;\nCREATE POLICY blog_1 ON blog USING (id % 2 = 0);\nALTER TABLE blog ENABLE ROW LEVEL SECURITY;\nINSERT INTO blog VALUES\n    (1, 'alice', 'blog #1'),\n    (2, 'bob', 'blog #1'),\n    (3, 'alice', 'blog #2'),\n    (4, 'alice', 'blog #3'),\n    (5, 'john', 'blog #1');\nINSERT INTO comment VALUES\n    (1, 'cool blog'),\n    (1, 'fun blog'),\n    (3, 'crazy blog'),\n    (5, 'what?'),\n    (4, 'insane!'),\n    (2, 'who did it?');\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Check RLS JOIN with Non-RLS.\nSELECT id, author, message FROM blog JOIN comment ON id = blog_id;\n id | author |   message   \n----+--------+-------------\n  2 | bob    | who did it?\n  4 | alice  | insane!\n\n-- Check Non-RLS JOIN with RLS.\nSELECT id, author, message FROM comment JOIN blog ON id = blog_id;\n id | author |   message   \n----+--------+-------------\n  2 | bob    | who did it?\n  4 | alice  | insane!\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE POLICY comment_1 ON comment USING (blog_id < 4);\nALTER TABLE comment ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Check RLS JOIN RLS\nSELECT id, author, message FROM blog JOIN comment ON id = blog_id;\n id | author |   message   \n----+--------+-------------\n  2 | bob    | who did it?\n\nSELECT id, author, message FROM comment JOIN blog ON id = blog_id;\n id | author |   message   \n----+--------+-------------\n  2 | bob    | who did it?\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP TABLE blog;\nDROP TABLE comment;\n--\n-- Default Deny Policy\n--\nRESET SESSION AUTHORIZATION;\nDROP POLICY p2 ON t1;\nALTER TABLE t1 OWNER TO regress_rls_alice;\n-- Check that default deny does not apply to superuser.\nRESET SESSION AUTHORIZATION;\nSELECT * FROM t1;\n a  |                b                 \n----+----------------------------------\n  1 | c4ca4238a0b923820dcc509a6f75849b\n  0 | cfcd208495d565ef66e7dff9f98764da\n  3 | eccbc87e4b5ce2fe28308fd9f2a7baf3\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  5 | e4da3b7fbbce2345d7772b0674a318d5\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  7 | 8f14e45fceea167a5a36dedd4bea2543\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  9 | 45c48cce2e2d7fbdea1afc51c7c6ad26\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 11 | 6512bd43d9caa6e02c990b0a82652dca\n 10 | d3d9446802a44259755d38e6d163e820\n 13 | c51ce410c124a10e0db5e4b97fc2af39\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 17 | 70efdf2ec9b086079795c442636b55fb\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 19 | 1f0e3dad99908345f7439f8ffabdffc4\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n 20 | Success\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n   ->  Seq Scan on _hyper_15_69_chunk t1_2\n   ->  Seq Scan on _hyper_15_70_chunk t1_3\n   ->  Seq Scan on _hyper_15_71_chunk t1_4\n   ->  Seq Scan on _hyper_15_72_chunk t1_5\n   ->  Seq Scan on _hyper_15_73_chunk t1_6\n   ->  Seq Scan on _hyper_15_74_chunk t1_7\n   ->  Seq Scan on _hyper_15_75_chunk t1_8\n   ->  Seq Scan on _hyper_15_76_chunk t1_9\n   ->  Seq Scan on _hyper_15_77_chunk t1_10\n   ->  Seq Scan on _hyper_15_78_chunk t1_11\n   ->  Seq Scan on _hyper_15_79_chunk t1_12\n\n-- Check that default deny does not apply to table owner.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSELECT * FROM t1;\n a  |                b                 \n----+----------------------------------\n  1 | c4ca4238a0b923820dcc509a6f75849b\n  0 | cfcd208495d565ef66e7dff9f98764da\n  3 | eccbc87e4b5ce2fe28308fd9f2a7baf3\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  5 | e4da3b7fbbce2345d7772b0674a318d5\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  7 | 8f14e45fceea167a5a36dedd4bea2543\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  9 | 45c48cce2e2d7fbdea1afc51c7c6ad26\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 11 | 6512bd43d9caa6e02c990b0a82652dca\n 10 | d3d9446802a44259755d38e6d163e820\n 13 | c51ce410c124a10e0db5e4b97fc2af39\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 17 | 70efdf2ec9b086079795c442636b55fb\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 19 | 1f0e3dad99908345f7439f8ffabdffc4\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n 20 | Success\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n   ->  Seq Scan on _hyper_15_69_chunk t1_2\n   ->  Seq Scan on _hyper_15_70_chunk t1_3\n   ->  Seq Scan on _hyper_15_71_chunk t1_4\n   ->  Seq Scan on _hyper_15_72_chunk t1_5\n   ->  Seq Scan on _hyper_15_73_chunk t1_6\n   ->  Seq Scan on _hyper_15_74_chunk t1_7\n   ->  Seq Scan on _hyper_15_75_chunk t1_8\n   ->  Seq Scan on _hyper_15_76_chunk t1_9\n   ->  Seq Scan on _hyper_15_77_chunk t1_10\n   ->  Seq Scan on _hyper_15_78_chunk t1_11\n   ->  Seq Scan on _hyper_15_79_chunk t1_12\n\n-- Check that default deny applies to non-owner/non-superuser when RLS on.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO ON;\nSELECT * FROM t1;\n a | b \n---+---\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1;\n--- QUERY PLAN ---\n Result\n   One-Time Filter: false\n\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM t1;\n a | b \n---+---\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1;\n--- QUERY PLAN ---\n Result\n   One-Time Filter: false\n\n--\n-- COPY TO/FROM\n--\nRESET SESSION AUTHORIZATION;\nDROP TABLE copy_t CASCADE;\nERROR:  table \"copy_t\" does not exist\nCREATE TABLE copy_t (a integer, b text);\nSELECT public.create_hypertable('copy_t', 'a', chunk_time_interval=>2);\n        create_hypertable         \n----------------------------------\n (19,regress_rls_schema,copy_t,t)\n\nCREATE POLICY p1 ON copy_t USING (a % 2 = 0);\nALTER TABLE copy_t ENABLE ROW LEVEL SECURITY;\nGRANT ALL ON copy_t TO regress_rls_bob, regress_rls_exempt_user;\nINSERT INTO copy_t (SELECT x, md5(x::text) FROM generate_series(0,10) x);\n-- Check COPY TO as Superuser/owner.\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';\n0,cfcd208495d565ef66e7dff9f98764da\n1,c4ca4238a0b923820dcc509a6f75849b\n2,c81e728d9d4c2f636f067f89cc14862c\n3,eccbc87e4b5ce2fe28308fd9f2a7baf3\n4,a87ff679a2f3e71d9181a67b7542122c\n5,e4da3b7fbbce2345d7772b0674a318d5\n6,1679091c5a880faf6fb5e6087eb1b2dc\n7,8f14e45fceea167a5a36dedd4bea2543\n8,c9f0f895fb98ab9159f51fd0297e236d\n9,45c48cce2e2d7fbdea1afc51c7c6ad26\n10,d3d9446802a44259755d38e6d163e820\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';\n0,cfcd208495d565ef66e7dff9f98764da\n1,c4ca4238a0b923820dcc509a6f75849b\n2,c81e728d9d4c2f636f067f89cc14862c\n3,eccbc87e4b5ce2fe28308fd9f2a7baf3\n4,a87ff679a2f3e71d9181a67b7542122c\n5,e4da3b7fbbce2345d7772b0674a318d5\n6,1679091c5a880faf6fb5e6087eb1b2dc\n7,8f14e45fceea167a5a36dedd4bea2543\n8,c9f0f895fb98ab9159f51fd0297e236d\n9,45c48cce2e2d7fbdea1afc51c7c6ad26\n10,d3d9446802a44259755d38e6d163e820\n-- Check COPY TO as user with permissions.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS\nERROR:  query would be affected by row-level security policy for table \"copy_t\"\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok\n0,cfcd208495d565ef66e7dff9f98764da\n2,c81e728d9d4c2f636f067f89cc14862c\n4,a87ff679a2f3e71d9181a67b7542122c\n6,1679091c5a880faf6fb5e6087eb1b2dc\n8,c9f0f895fb98ab9159f51fd0297e236d\n10,d3d9446802a44259755d38e6d163e820\n-- Check COPY TO as user with permissions and BYPASSRLS\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok\n0,cfcd208495d565ef66e7dff9f98764da\n1,c4ca4238a0b923820dcc509a6f75849b\n2,c81e728d9d4c2f636f067f89cc14862c\n3,eccbc87e4b5ce2fe28308fd9f2a7baf3\n4,a87ff679a2f3e71d9181a67b7542122c\n5,e4da3b7fbbce2345d7772b0674a318d5\n6,1679091c5a880faf6fb5e6087eb1b2dc\n7,8f14e45fceea167a5a36dedd4bea2543\n8,c9f0f895fb98ab9159f51fd0297e236d\n9,45c48cce2e2d7fbdea1afc51c7c6ad26\n10,d3d9446802a44259755d38e6d163e820\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok\n0,cfcd208495d565ef66e7dff9f98764da\n1,c4ca4238a0b923820dcc509a6f75849b\n2,c81e728d9d4c2f636f067f89cc14862c\n3,eccbc87e4b5ce2fe28308fd9f2a7baf3\n4,a87ff679a2f3e71d9181a67b7542122c\n5,e4da3b7fbbce2345d7772b0674a318d5\n6,1679091c5a880faf6fb5e6087eb1b2dc\n7,8f14e45fceea167a5a36dedd4bea2543\n8,c9f0f895fb98ab9159f51fd0297e236d\n9,45c48cce2e2d7fbdea1afc51c7c6ad26\n10,d3d9446802a44259755d38e6d163e820\n-- Check COPY TO as user without permissions. SET row_security TO OFF;\nSET SESSION AUTHORIZATION regress_rls_carol;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS\nERROR:  query would be affected by row-level security policy for table \"copy_t\"\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - permission denied\nERROR:  permission denied for table copy_t\n-- Check COPY relation TO; keep it just one row to avoid reordering issues\nRESET SESSION AUTHORIZATION;\nSET row_security TO ON;\nCREATE TABLE copy_rel_to (a integer, b text);\nSELECT public.create_hypertable('copy_rel_to', 'a', chunk_time_interval=>2);\n           create_hypertable           \n---------------------------------------\n (20,regress_rls_schema,copy_rel_to,t)\n\nCREATE POLICY p1 ON copy_rel_to USING (a % 2 = 0);\nALTER TABLE copy_rel_to ENABLE ROW LEVEL SECURITY;\nGRANT ALL ON copy_rel_to TO regress_rls_bob, regress_rls_exempt_user;\nINSERT INTO copy_rel_to VALUES (1, md5('1'));\n-- Check COPY TO as Superuser/owner.\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ',';\n1,c4ca4238a0b923820dcc509a6f75849b\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ',';\n1,c4ca4238a0b923820dcc509a6f75849b\n-- Check COPY TO as user with permissions.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS\nERROR:  query would be affected by row-level security policy for table \"copy_rel_to\"\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --ok\n-- Check COPY TO as user with permissions and BYPASSRLS\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --ok\n1,c4ca4238a0b923820dcc509a6f75849b\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --ok\n1,c4ca4238a0b923820dcc509a6f75849b\n-- Check COPY TO as user without permissions. SET row_security TO OFF;\nSET SESSION AUTHORIZATION regress_rls_carol;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --fail - permission denied\nERROR:  query would be affected by row-level security policy for table \"copy_rel_to\"\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --fail - permission denied\nERROR:  permission denied for table copy_rel_to\n-- Check COPY FROM as Superuser/owner.\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nCOPY copy_t FROM STDIN; --ok\nSET row_security TO ON;\nCOPY copy_t FROM STDIN; --ok\n-- Check COPY FROM as user with permissions.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO OFF;\nCOPY copy_t FROM STDIN; --fail - would be affected by RLS.\nERROR:  query would be affected by row-level security policy for table \"copy_t\"\nSET row_security TO ON;\nCOPY copy_t FROM STDIN; --fail - COPY FROM not supported by RLS.\nERROR:  COPY FROM not supported with row-level security\nHINT:  Use INSERT statements instead.\n-- Check COPY FROM as user with permissions and BYPASSRLS\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO ON;\nCOPY copy_t FROM STDIN; --ok\n-- Check COPY FROM as user without permissions.\nSET SESSION AUTHORIZATION regress_rls_carol;\nSET row_security TO OFF;\nCOPY copy_t FROM STDIN; --fail - permission denied.\nERROR:  permission denied for table copy_t\nSET row_security TO ON;\nCOPY copy_t FROM STDIN; --fail - permission denied.\nERROR:  permission denied for table copy_t\nRESET SESSION AUTHORIZATION;\nDROP TABLE copy_t;\nDROP TABLE copy_rel_to CASCADE;\n-- Check WHERE CURRENT OF\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE current_check (currentid int, payload text, rlsuser text);\nSELECT public.create_hypertable('current_check', 'currentid', chunk_time_interval=>10);\n            create_hypertable            \n-----------------------------------------\n (21,regress_rls_schema,current_check,t)\n\nGRANT ALL ON current_check TO PUBLIC;\nINSERT INTO current_check VALUES\n    (1, 'abc', 'regress_rls_bob'),\n    (2, 'bcd', 'regress_rls_bob'),\n    (3, 'cde', 'regress_rls_bob'),\n    (4, 'def', 'regress_rls_bob');\nCREATE POLICY p1 ON current_check FOR SELECT USING (currentid % 2 = 0);\nCREATE POLICY p2 ON current_check FOR DELETE USING (currentid = 4 AND rlsuser = current_user);\nCREATE POLICY p3 ON current_check FOR UPDATE USING (currentid = 4) WITH CHECK (rlsuser = current_user);\nALTER TABLE current_check ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Can SELECT even rows\nSELECT * FROM current_check;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         2 | bcd     | regress_rls_bob\n         4 | def     | regress_rls_bob\n\n-- Cannot UPDATE row 2\nUPDATE current_check SET payload = payload || '_new' WHERE currentid = 2 RETURNING *;\n currentid | payload | rlsuser \n-----------+---------+---------\n\nBEGIN;\n-- WHERE CURRENT OF does not work with custom scan nodes\n-- so we have to disable chunk append here\nSET timescaledb.enable_chunk_append TO false;\nDECLARE current_check_cursor SCROLL CURSOR FOR SELECT * FROM current_check;\n-- Returns rows that can be seen according to SELECT policy, like plain SELECT\n-- above (even rows)\nFETCH ABSOLUTE 1 FROM current_check_cursor;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         2 | bcd     | regress_rls_bob\n\n-- Still cannot UPDATE row 2 through cursor\nUPDATE current_check SET payload = payload || '_new' WHERE CURRENT OF current_check_cursor RETURNING *;\n currentid | payload | rlsuser \n-----------+---------+---------\n\n-- Can update row 4 through cursor, which is the next visible row\nFETCH RELATIVE 1 FROM current_check_cursor;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         4 | def     | regress_rls_bob\n\nUPDATE current_check SET payload = payload || '_new' WHERE CURRENT OF current_check_cursor RETURNING *;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         4 | def_new | regress_rls_bob\n\nSELECT * FROM current_check;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         2 | bcd     | regress_rls_bob\n         4 | def_new | regress_rls_bob\n\n-- Plan should be a subquery TID scan\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE current_check SET payload = payload WHERE CURRENT OF current_check_cursor;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Update on current_check\n         Update on _hyper_21_104_chunk current_check_1\n         ->  Tid Scan on _hyper_21_104_chunk current_check_1\n               TID Cond: CURRENT OF current_check_cursor\n               Filter: ((currentid = 4) AND ((currentid % 2) = 0))\n\n-- Similarly can only delete row 4\nFETCH ABSOLUTE 1 FROM current_check_cursor;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         2 | bcd     | regress_rls_bob\n\nDELETE FROM current_check WHERE CURRENT OF current_check_cursor RETURNING *;\n currentid | payload | rlsuser \n-----------+---------+---------\n\nFETCH RELATIVE 1 FROM current_check_cursor;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         4 | def     | regress_rls_bob\n\nDELETE FROM current_check WHERE CURRENT OF current_check_cursor RETURNING *;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         4 | def_new | regress_rls_bob\n\nSELECT * FROM current_check;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         2 | bcd     | regress_rls_bob\n\nRESET timescaledb.enable_chunk_append;\nCOMMIT;\n--\n-- check pg_stats view filtering\n--\nSET row_security TO ON;\nSET SESSION AUTHORIZATION regress_rls_alice;\nANALYZE current_check;\n-- Stats visible\nSELECT row_security_active('current_check');\n row_security_active \n---------------------\n f\n\nSELECT attname, most_common_vals FROM pg_stats\n  WHERE tablename = 'current_check'\n  ORDER BY 1;\n  attname  | most_common_vals  \n-----------+-------------------\n currentid | \n payload   | \n rlsuser   | {regress_rls_bob}\n\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Stats not visible\nSELECT row_security_active('current_check');\n row_security_active \n---------------------\n t\n\nSELECT attname, most_common_vals FROM pg_stats\n  WHERE tablename = 'current_check'\n  ORDER BY 1;\n attname | most_common_vals \n---------+------------------\n\n--\n-- Collation support\n--\nBEGIN;\nCREATE TABLE coll_t (c) AS VALUES ('bar'::text);\nCREATE POLICY coll_p ON coll_t USING (c < ('foo'::text COLLATE \"C\"));\nALTER TABLE coll_t ENABLE ROW LEVEL SECURITY;\nGRANT SELECT ON coll_t TO regress_rls_alice;\nSELECT (string_to_array(polqual, ':'))[7] AS inputcollid FROM pg_policy WHERE polrelid = 'coll_t'::regclass;\n   inputcollid    \n------------------\n inputcollid 950 \n\nSET SESSION AUTHORIZATION regress_rls_alice;\nSELECT * FROM coll_t;\n  c  \n-----\n bar\n\nROLLBACK;\n--\n-- Shared Object Dependencies\n--\nRESET SESSION AUTHORIZATION;\nBEGIN;\nCREATE ROLE regress_rls_eve;\nCREATE ROLE regress_rls_frank;\nCREATE TABLE tbl1 (c) AS VALUES ('bar'::text);\nGRANT SELECT ON TABLE tbl1 TO regress_rls_eve;\nCREATE POLICY P ON tbl1 TO regress_rls_eve, regress_rls_frank USING (true);\nSELECT refclassid::regclass, deptype\n  FROM pg_depend\n  WHERE classid = 'pg_policy'::regclass\n  AND refobjid = 'tbl1'::regclass;\n refclassid | deptype \n------------+---------\n pg_class   | a\n\nSELECT refclassid::regclass, deptype\n  FROM pg_shdepend\n  WHERE classid = 'pg_policy'::regclass\n  AND refobjid IN ('regress_rls_eve'::regrole, 'regress_rls_frank'::regrole);\n refclassid | deptype \n------------+---------\n pg_authid  | r\n pg_authid  | r\n\nSAVEPOINT q;\nDROP ROLE regress_rls_eve; --fails due to dependency on POLICY p\nERROR:  role \"regress_rls_eve\" cannot be dropped because some objects depend on it\nDETAIL:  privileges for table tbl1\ntarget of policy p on table tbl1\nROLLBACK TO q;\nALTER POLICY p ON tbl1 TO regress_rls_frank USING (true);\nSAVEPOINT q;\nDROP ROLE regress_rls_eve; --fails due to dependency on GRANT SELECT\nERROR:  role \"regress_rls_eve\" cannot be dropped because some objects depend on it\nDETAIL:  privileges for table tbl1\nROLLBACK TO q;\nREVOKE ALL ON TABLE tbl1 FROM regress_rls_eve;\nSAVEPOINT q;\nDROP ROLE regress_rls_eve; --succeeds\nROLLBACK TO q;\nSAVEPOINT q;\nDROP ROLE regress_rls_frank; --fails due to dependency on POLICY p\nERROR:  role \"regress_rls_frank\" cannot be dropped because some objects depend on it\nDETAIL:  target of policy p on table tbl1\nROLLBACK TO q;\nDROP POLICY p ON tbl1;\nSAVEPOINT q;\nDROP ROLE regress_rls_frank; -- succeeds\nROLLBACK TO q;\nROLLBACK; -- cleanup\n--\n-- Converting table to view\n--\nBEGIN;\nCREATE TABLE t (c int);\nSELECT public.create_hypertable('t', 'c', chunk_time_interval=>2);\n      create_hypertable      \n-----------------------------\n (22,regress_rls_schema,t,t)\n\nCREATE POLICY p ON t USING (c % 2 = 1);\nALTER TABLE t ENABLE ROW LEVEL SECURITY;\nSAVEPOINT q;\nCREATE RULE \"_RETURN\" AS ON SELECT TO t DO INSTEAD\n  SELECT * FROM generate_series(1,5) t0(c); -- fails due to row level security enabled\nERROR:  hypertables do not support rules\nROLLBACK TO q;\nALTER TABLE t DISABLE ROW LEVEL SECURITY;\nSAVEPOINT q;\nCREATE RULE \"_RETURN\" AS ON SELECT TO t DO INSTEAD\n  SELECT * FROM generate_series(1,5) t0(c); -- fails due to policy p on t\nERROR:  hypertables do not support rules\nROLLBACK TO q;\nDROP POLICY p ON t;\nCREATE RULE \"_RETURN\" AS ON SELECT TO t DO INSTEAD\n  SELECT * FROM generate_series(1,5) t0(c); -- succeeds\nERROR:  hypertables do not support rules\nROLLBACK;\n--\n-- Policy expression handling\n--\nBEGIN;\nCREATE TABLE t (c) AS VALUES ('bar'::text);\nCREATE POLICY p ON t USING (max(c)); -- fails: aggregate functions are not allowed in policy expressions\nERROR:  aggregate functions are not allowed in policy expressions\nROLLBACK;\n--\n-- Non-target relations are only subject to SELECT policies\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE r1 (a int);\nSELECT public.create_hypertable('r1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (23,regress_rls_schema,r1,t)\n\nCREATE TABLE r2 (a int);\nSELECT public.create_hypertable('r2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (24,regress_rls_schema,r2,t)\n\nINSERT INTO r1 VALUES (10), (20);\nINSERT INTO r2 VALUES (10), (20);\nGRANT ALL ON r1, r2 TO regress_rls_bob;\nCREATE POLICY p1 ON r1 USING (true);\nALTER TABLE r1 ENABLE ROW LEVEL SECURITY;\nCREATE POLICY p1 ON r2 FOR SELECT USING (true);\nCREATE POLICY p2 ON r2 FOR INSERT WITH CHECK (false);\nCREATE POLICY p3 ON r2 FOR UPDATE USING (false);\nCREATE POLICY p4 ON r2 FOR DELETE USING (false);\nALTER TABLE r2 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM r1;\n a  \n----\n 10\n 20\n\nSELECT * FROM r2;\n a  \n----\n 10\n 20\n\n-- r2 is read-only\nINSERT INTO r2 VALUES (2); -- Not allowed\nERROR:  new row violates row-level security policy for table \"r2\"\n\\pset tuples_only 1\nUPDATE r2 SET a = 2 RETURNING *; -- Updates nothing\n\nDELETE FROM r2 RETURNING *; -- Deletes nothing\n\n\\pset tuples_only 0\n-- r2 can be used as a non-target relation in DML\nINSERT INTO r1 SELECT a + 1 FROM r2 RETURNING *; -- OK\n a  \n----\n 11\n 21\n\nUPDATE r1 SET a = r2.a + 2 FROM r2 WHERE r1.a = r2.a RETURNING *; -- OK\nERROR:  new row for relation \"_hyper_23_105_chunk\" violates check constraint \"constraint_105\"\nDELETE FROM r1 USING r2 WHERE r1.a = r2.a + 2 RETURNING *; -- OK\n a | a \n---+---\n\nSELECT * FROM r1;\n a  \n----\n 10\n 11\n 20\n 21\n\nSELECT * FROM r2;\n a  \n----\n 10\n 20\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP TABLE r1;\nDROP TABLE r2;\n--\n-- FORCE ROW LEVEL SECURITY applies RLS to owners too\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security = on;\nCREATE TABLE r1 (a int);\nSELECT public.create_hypertable('r1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (25,regress_rls_schema,r1,t)\n\nINSERT INTO r1 VALUES (10), (20);\nCREATE POLICY p1 ON r1 USING (false);\nALTER TABLE r1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r1 FORCE ROW LEVEL SECURITY;\n-- No error, but no rows\nTABLE r1;\n a \n---\n\n-- RLS error\nINSERT INTO r1 VALUES (1);\nERROR:  new row violates row-level security policy for table \"r1\"\n-- No error (unable to see any rows to update)\nUPDATE r1 SET a = 1;\nTABLE r1;\n a \n---\n\n-- No error (unable to see any rows to delete)\nDELETE FROM r1;\nTABLE r1;\n a \n---\n\nSET row_security = off;\n-- these all fail, would be affected by RLS\nTABLE r1;\nERROR:  query would be affected by row-level security policy for table \"r1\"\nHINT:  To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.\nUPDATE r1 SET a = 1;\nERROR:  query would be affected by row-level security policy for table \"r1\"\nHINT:  To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.\nDELETE FROM r1;\nERROR:  query would be affected by row-level security policy for table \"r1\"\nHINT:  To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.\nDROP TABLE r1;\n--\n-- FORCE ROW LEVEL SECURITY does not break RI\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security = on;\nCREATE TABLE r1 (a int PRIMARY KEY);\n-- r1 is not a hypertable since r1.a is referenced by r2\nCREATE TABLE r2 (a int REFERENCES r1);\nSELECT public.create_hypertable('r2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (26,regress_rls_schema,r2,t)\n\nINSERT INTO r1 VALUES (10), (20);\nINSERT INTO r2 VALUES (10), (20);\n-- Create policies on r2 which prevent the\n-- owner from seeing any rows, but RI should\n-- still see them.\nCREATE POLICY p1 ON r2 USING (false);\nALTER TABLE r2 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r2 FORCE ROW LEVEL SECURITY;\n-- Errors due to rows in r2\nDELETE FROM r1;\nERROR:  update or delete on table \"r1\" violates foreign key constraint \"r2_a_fkey\" on table \"r2\"\nDETAIL:  Key (a)=(10) is still referenced from table \"r2\".\n-- Reset r2 to no-RLS\nDROP POLICY p1 ON r2;\nALTER TABLE r2 NO FORCE ROW LEVEL SECURITY;\nALTER TABLE r2 DISABLE ROW LEVEL SECURITY;\n-- clean out r2 for INSERT test below\nDELETE FROM r2;\n-- Change r1 to not allow rows to be seen\nCREATE POLICY p1 ON r1 USING (false);\nALTER TABLE r1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r1 FORCE ROW LEVEL SECURITY;\n-- No rows seen\nTABLE r1;\n a \n---\n\n-- No error, RI still sees that row exists in r1\nINSERT INTO r2 VALUES (10);\nDROP TABLE r2;\nDROP TABLE r1;\n-- Ensure cascaded DELETE works\nCREATE TABLE r1 (a int PRIMARY KEY);\n-- r1 is not a hypertable since r1.a is referenced by r2\nCREATE TABLE r2 (a int REFERENCES r1 ON DELETE CASCADE);\nSELECT public.create_hypertable('r2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (27,regress_rls_schema,r2,t)\n\nINSERT INTO r1 VALUES (10), (20);\nINSERT INTO r2 VALUES (10), (20);\n-- Create policies on r2 which prevent the\n-- owner from seeing any rows, but RI should\n-- still see them.\nCREATE POLICY p1 ON r2 USING (false);\nALTER TABLE r2 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r2 FORCE ROW LEVEL SECURITY;\n-- Deletes all records from both\nDELETE FROM r1;\n-- Remove FORCE from r2\nALTER TABLE r2 NO FORCE ROW LEVEL SECURITY;\n-- As owner, we now bypass RLS\n-- verify no rows in r2 now\nTABLE r2;\n a \n---\n\nDROP TABLE r2;\nDROP TABLE r1;\n-- Ensure cascaded UPDATE works\nCREATE TABLE r1 (a int PRIMARY KEY);\n-- r1 is not a hypertable since r1.a is referenced by r2\nCREATE TABLE r2 (a int REFERENCES r1 ON UPDATE CASCADE);\nSELECT public.create_hypertable('r2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (28,regress_rls_schema,r2,t)\n\nINSERT INTO r1 VALUES (10), (20);\nINSERT INTO r2 VALUES (10), (20);\n-- Create policies on r2 which prevent the\n-- owner from seeing any rows, but RI should\n-- still see them.\nCREATE POLICY p1 ON r2 USING (false);\nALTER TABLE r2 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r2 FORCE ROW LEVEL SECURITY;\n-- Updates records in both (terse output to not print CONTEXT, which can be different).\n\\set VERBOSITY terse\nUPDATE r1 SET a = a+5;\nERROR:  new row for relation \"_hyper_28_117_chunk\" violates check constraint \"constraint_117\"\n\\set VERBOSITY default\n-- Remove FORCE from r2\nALTER TABLE r2 NO FORCE ROW LEVEL SECURITY;\n-- As owner, we now bypass RLS\n-- verify records in r2 updated\nTABLE r2;\n a  \n----\n 10\n 20\n\nDROP TABLE r2;\nDROP TABLE r1;\n--\n-- Test INSERT+RETURNING applies SELECT policies as\n-- WithCheckOptions (meaning an error is thrown)\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security = on;\nCREATE TABLE r1 (a int);\nSELECT public.create_hypertable('r1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (29,regress_rls_schema,r1,t)\n\nCREATE POLICY p1 ON r1 FOR SELECT USING (false);\nCREATE POLICY p2 ON r1 FOR INSERT WITH CHECK (true);\nALTER TABLE r1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r1 FORCE ROW LEVEL SECURITY;\n-- Works fine\nINSERT INTO r1 VALUES (10), (20);\n-- No error, but no rows\nTABLE r1;\n a \n---\n\nSET row_security = off;\n-- fail, would be affected by RLS\nTABLE r1;\nERROR:  query would be affected by row-level security policy for table \"r1\"\nHINT:  To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.\nSET row_security = on;\n-- Error\nINSERT INTO r1 VALUES (10), (20) RETURNING *;\nERROR:  new row violates row-level security policy for table \"r1\"\nDROP TABLE r1;\n--\n-- Test UPDATE+RETURNING applies SELECT policies as\n-- WithCheckOptions (meaning an error is thrown)\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security = on;\nCREATE TABLE r1 (a int PRIMARY KEY);\nSELECT public.create_hypertable('r1', 'a', chunk_time_interval=>100);\n      create_hypertable       \n------------------------------\n (30,regress_rls_schema,r1,t)\n\nCREATE POLICY p1 ON r1 FOR SELECT USING (a < 20);\nCREATE POLICY p2 ON r1 FOR UPDATE USING (a < 20) WITH CHECK (true);\nCREATE POLICY p3 ON r1 FOR INSERT WITH CHECK (true);\nINSERT INTO r1 VALUES (10);\nALTER TABLE r1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r1 FORCE ROW LEVEL SECURITY;\n-- Works fine\nUPDATE r1 SET a = 30;\n-- Show updated rows\nALTER TABLE r1 NO FORCE ROW LEVEL SECURITY;\nTABLE r1;\n a  \n----\n 30\n\n-- reset value in r1 for test with RETURNING\nUPDATE r1 SET a = 10;\n-- Verify row reset\nTABLE r1;\n a  \n----\n 10\n\nALTER TABLE r1 FORCE ROW LEVEL SECURITY;\n-- Error\nUPDATE r1 SET a = 30 RETURNING *;\nERROR:  new row violates row-level security policy for table \"r1\"\n-- UPDATE path of INSERT ... ON CONFLICT DO UPDATE should also error out\nINSERT INTO r1 VALUES (10)\n    ON CONFLICT (a) DO UPDATE SET a = 30 RETURNING *;\nERROR:  new row violates row-level security policy for table \"r1\"\n-- Should still error out without RETURNING (use of arbiter always requires\n-- SELECT permissions)\nINSERT INTO r1 VALUES (10)\n    ON CONFLICT (a) DO UPDATE SET a = 30;\nERROR:  new row violates row-level security policy for table \"r1\"\n-- ON CONFLICT ON CONSTRAINT\nINSERT INTO r1 VALUES (10)\n    ON CONFLICT ON CONSTRAINT r1_pkey DO UPDATE SET a = 30;\nERROR:  new row violates row-level security policy for table \"r1\"\nDROP TABLE r1;\n-- Check dependency handling\nRESET SESSION AUTHORIZATION;\nCREATE TABLE dep1 (c1 int);\nSELECT public.create_hypertable('dep1', 'c1', chunk_time_interval=>2);\n       create_hypertable        \n--------------------------------\n (31,regress_rls_schema,dep1,t)\n\nCREATE TABLE dep2 (c1 int);\nSELECT public.create_hypertable('dep2', 'c1', chunk_time_interval=>2);\n       create_hypertable        \n--------------------------------\n (32,regress_rls_schema,dep2,t)\n\nCREATE POLICY dep_p1 ON dep1 TO regress_rls_bob USING (c1 > (select max(dep2.c1) from dep2));\nALTER POLICY dep_p1 ON dep1 TO regress_rls_bob,regress_rls_carol;\n-- Should return one\nSELECT count(*) = 1 FROM pg_depend\n\t\t\t\t   WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1')\n\t\t\t\t\t AND refobjid = (SELECT oid FROM pg_class WHERE relname = 'dep2');\n ?column? \n----------\n t\n\nALTER POLICY dep_p1 ON dep1 USING (true);\n-- Should return one\nSELECT count(*) = 1 FROM pg_shdepend\n\t\t\t\t   WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1')\n\t\t\t\t\t AND refobjid = (SELECT oid FROM pg_authid WHERE rolname = 'regress_rls_bob');\n ?column? \n----------\n t\n\n-- Should return one\nSELECT count(*) = 1 FROM pg_shdepend\n\t\t\t\t   WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1')\n\t\t\t\t\t AND refobjid = (SELECT oid FROM pg_authid WHERE rolname = 'regress_rls_carol');\n ?column? \n----------\n t\n\n-- Should return zero\nSELECT count(*) = 0 FROM pg_depend\n\t\t\t\t   WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1')\n\t\t\t\t\t AND refobjid = (SELECT oid FROM pg_class WHERE relname = 'dep2');\n ?column? \n----------\n t\n\n-- DROP OWNED BY testing\nRESET SESSION AUTHORIZATION;\nCREATE ROLE regress_rls_dob_role1;\nCREATE ROLE regress_rls_dob_role2;\nCREATE TABLE dob_t1 (c1 int);\nSELECT public.create_hypertable('dob_t1', 'c1', chunk_time_interval=>2);\n        create_hypertable         \n----------------------------------\n (33,regress_rls_schema,dob_t1,t)\n\nCREATE TABLE dob_t2 (c1 int) PARTITION BY RANGE (c1);\nCREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1 USING (true);\nDROP OWNED BY regress_rls_dob_role1;\nDROP POLICY p1 ON dob_t1; -- should fail, already gone\nERROR:  policy \"p1\" for table \"dob_t1\" does not exist\nCREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1,regress_rls_dob_role2 USING (true);\nDROP OWNED BY regress_rls_dob_role1;\nDROP POLICY p1 ON dob_t1; -- should succeed\nCREATE POLICY p1 ON dob_t2 TO regress_rls_dob_role1,regress_rls_dob_role2 USING (true);\nDROP OWNED BY regress_rls_dob_role1;\nDROP POLICY p1 ON dob_t2; -- should succeed\nDROP USER regress_rls_dob_role1;\nDROP USER regress_rls_dob_role2;\n--\n-- Clean up objects\n--\nRESET SESSION AUTHORIZATION;\n\\set VERBOSITY terse \\\\ -- suppress cascade details\nDROP SCHEMA regress_rls_schema CASCADE;\nNOTICE:  drop cascades to 116 other objects\n\\set VERBOSITY default\nDROP USER regress_rls_alice;\nDROP USER regress_rls_bob;\nDROP USER regress_rls_carol;\nDROP USER regress_rls_dave;\nDROP USER regress_rls_exempt_user;\nDROP ROLE regress_rls_group1;\nDROP ROLE regress_rls_group2;\n-- Arrange to have a few policies left over, for testing\n-- pg_dump/pg_restore\nCREATE SCHEMA regress_rls_schema;\nCREATE TABLE rls_tbl (c1 int);\nSELECT public.create_hypertable('rls_tbl', 'c1', chunk_time_interval=>2);\n         create_hypertable         \n-----------------------------------\n (34,regress_rls_schema,rls_tbl,t)\n\nALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;\nCREATE POLICY p1 ON rls_tbl USING (c1 > 5);\nCREATE POLICY p2 ON rls_tbl FOR SELECT USING (c1 <= 3);\nCREATE POLICY p3 ON rls_tbl FOR UPDATE USING (c1 <= 3) WITH CHECK (c1 > 5);\nCREATE POLICY p4 ON rls_tbl FOR DELETE USING (c1 <= 3);\nCREATE TABLE rls_tbl_force (c1 int);\nSELECT public.create_hypertable('rls_tbl_force', 'c1', chunk_time_interval=>2);\n            create_hypertable            \n-----------------------------------------\n (35,regress_rls_schema,rls_tbl_force,t)\n\nALTER TABLE rls_tbl_force ENABLE ROW LEVEL SECURITY;\nALTER TABLE rls_tbl_force FORCE ROW LEVEL SECURITY;\nCREATE POLICY p1 ON rls_tbl_force USING (c1 = 5) WITH CHECK (c1 < 5);\nCREATE POLICY p2 ON rls_tbl_force FOR SELECT USING (c1 = 8);\nCREATE POLICY p3 ON rls_tbl_force FOR UPDATE USING (c1 = 8) WITH CHECK (c1 >= 5);\nCREATE POLICY p4 ON rls_tbl_force FOR DELETE USING (c1 = 8);\n"
  },
  {
    "path": "test/expected/rowsecurity-18.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n--\n-- Test of Row-level security feature\n--\n-- Clean up in case a prior regression run failed\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\\set ON_ERROR_STOP 0\n\\set VERBOSITY default\nSET timescaledb.enable_constraint_exclusion TO off;\n-- Suppress NOTICE messages when users/groups don't exist\nSET client_min_messages TO 'warning';\nDROP USER IF EXISTS regress_rls_alice;\nDROP USER IF EXISTS regress_rls_bob;\nDROP USER IF EXISTS regress_rls_carol;\nDROP USER IF EXISTS regress_rls_dave;\nDROP USER IF EXISTS regress_rls_exempt_user;\nDROP ROLE IF EXISTS regress_rls_group1;\nDROP ROLE IF EXISTS regress_rls_group2;\nDROP SCHEMA IF EXISTS regress_rls_schema CASCADE;\nRESET client_min_messages;\n-- initial setup\nCREATE USER regress_rls_alice NOLOGIN;\nCREATE USER regress_rls_bob NOLOGIN;\nCREATE USER regress_rls_carol NOLOGIN;\nCREATE USER regress_rls_dave NOLOGIN;\nCREATE USER regress_rls_exempt_user BYPASSRLS NOLOGIN;\nCREATE ROLE regress_rls_group1 NOLOGIN;\nCREATE ROLE regress_rls_group2 NOLOGIN;\nGRANT regress_rls_group1 TO regress_rls_bob;\nGRANT regress_rls_group2 TO regress_rls_carol;\nCREATE SCHEMA regress_rls_schema;\nGRANT ALL ON SCHEMA regress_rls_schema to public;\nSET search_path = regress_rls_schema;\n-- setup of malicious function\nCREATE OR REPLACE FUNCTION f_leak(text) RETURNS bool\n    COST 0.0000001 LANGUAGE plpgsql\n    AS 'BEGIN RAISE NOTICE ''f_leak => %'', $1; RETURN true; END';\nGRANT EXECUTE ON FUNCTION f_leak(text) TO public;\n-- BASIC Row-Level Security Scenario\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE uaccount (\n    pguser      name primary key,\n    seclv       int\n);\nGRANT SELECT ON uaccount TO public;\nINSERT INTO uaccount VALUES\n    ('regress_rls_alice', 99),\n    ('regress_rls_bob', 1),\n    ('regress_rls_carol', 2),\n    ('regress_rls_dave', 3);\nCREATE TABLE category (\n    cid        int primary key,\n    cname      text\n);\nGRANT ALL ON category TO public;\nINSERT INTO category VALUES\n    (11, 'novel'),\n    (22, 'science fiction'),\n    (33, 'technology'),\n    (44, 'manga');\nCREATE TABLE document (\n    did         int primary key,\n    cid         int references category(cid),\n    dlevel      int not null,\n    dauthor     name,\n    dtitle      text\n);\nGRANT ALL ON document TO public;\nSELECT public.create_hypertable('document', 'did', chunk_time_interval=>2);\n         create_hypertable         \n-----------------------------------\n (1,regress_rls_schema,document,t)\n\nINSERT INTO document VALUES\n    ( 1, 11, 1, 'regress_rls_bob', 'my first novel'),\n    ( 2, 11, 2, 'regress_rls_bob', 'my second novel'),\n    ( 3, 22, 2, 'regress_rls_bob', 'my science fiction'),\n    ( 4, 44, 1, 'regress_rls_bob', 'my first manga'),\n    ( 5, 44, 2, 'regress_rls_bob', 'my second manga'),\n    ( 6, 22, 1, 'regress_rls_carol', 'great science fiction'),\n    ( 7, 33, 2, 'regress_rls_carol', 'great technology book'),\n    ( 8, 44, 1, 'regress_rls_carol', 'great manga'),\n    ( 9, 22, 1, 'regress_rls_dave', 'awesome science fiction'),\n    (10, 33, 2, 'regress_rls_dave', 'awesome technology book');\nALTER TABLE document ENABLE ROW LEVEL SECURITY;\n-- user's security level must be higher than or equal to document's\nCREATE POLICY p1 ON document AS PERMISSIVE\n    USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));\n-- try to create a policy of bogus type\nCREATE POLICY p1 ON document AS UGLY\n    USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));\nERROR:  unrecognized row security option \"ugly\"\nLINE 1: CREATE POLICY p1 ON document AS UGLY\n                                        ^\nHINT:  Only PERMISSIVE or RESTRICTIVE policies are supported currently.\n-- but Dave isn't allowed to anything at cid 50 or above\n-- this is to make sure that we sort the policies by name first\n-- when applying WITH CHECK, a later INSERT by Dave should fail due\n-- to p1r first\nCREATE POLICY p2r ON document AS RESTRICTIVE TO regress_rls_dave\n    USING (cid <> 44 AND cid < 50);\n-- and Dave isn't allowed to see manga documents\nCREATE POLICY p1r ON document AS RESTRICTIVE TO regress_rls_dave\n    USING (cid <> 44);\n\\dp\n                                                                   Access privileges\n       Schema       |   Name   | Type  |              Access privileges               | Column privileges |                  Policies                  \n--------------------+----------+-------+----------------------------------------------+-------------------+--------------------------------------------\n regress_rls_schema | category | table | regress_rls_alice=arwdDxtm/regress_rls_alice+|                   | \n                    |          |       | =arwdDxtm/regress_rls_alice                  |                   | \n regress_rls_schema | document | table | regress_rls_alice=arwdDxtm/regress_rls_alice+|                   | p1:                                       +\n                    |          |       | =arwdDxtm/regress_rls_alice                  |                   |   (u): (dlevel <= ( SELECT uaccount.seclv +\n                    |          |       |                                              |                   |    FROM uaccount                          +\n                    |          |       |                                              |                   |   WHERE (uaccount.pguser = CURRENT_USER)))+\n                    |          |       |                                              |                   | p2r (RESTRICTIVE):                        +\n                    |          |       |                                              |                   |   (u): ((cid <> 44) AND (cid < 50))       +\n                    |          |       |                                              |                   |   to: regress_rls_dave                    +\n                    |          |       |                                              |                   | p1r (RESTRICTIVE):                        +\n                    |          |       |                                              |                   |   (u): (cid <> 44)                        +\n                    |          |       |                                              |                   |   to: regress_rls_dave\n regress_rls_schema | uaccount | table | regress_rls_alice=arwdDxtm/regress_rls_alice+|                   | \n                    |          |       | =r/regress_rls_alice                         |                   | \n\n\\d document\n        Table \"regress_rls_schema.document\"\n Column  |  Type   | Collation | Nullable | Default \n---------+---------+-----------+----------+---------\n did     | integer |           | not null | \n cid     | integer |           |          | \n dlevel  | integer |           | not null | \n dauthor | name    |           |          | \n dtitle  | text    |           |          | \nIndexes:\n    \"document_pkey\" PRIMARY KEY, btree (did)\nForeign-key constraints:\n    \"document_cid_fkey\" FOREIGN KEY (cid) REFERENCES category(cid)\nPolicies:\n    POLICY \"p1\"\n      USING ((dlevel <= ( SELECT uaccount.seclv\n   FROM uaccount\n  WHERE (uaccount.pguser = CURRENT_USER))))\n    POLICY \"p1r\" AS RESTRICTIVE\n      TO regress_rls_dave\n      USING ((cid <> 44))\n    POLICY \"p2r\" AS RESTRICTIVE\n      TO regress_rls_dave\n      USING (((cid <> 44) AND (cid < 50)))\nNumber of child tables: 6 (Use \\d+ to list them.)\n\nSELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename = 'document' ORDER BY policyname;\n     schemaname     | tablename | policyname | permissive  |       roles        | cmd |                    qual                    | with_check \n--------------------+-----------+------------+-------------+--------------------+-----+--------------------------------------------+------------\n regress_rls_schema | document  | p1         | PERMISSIVE  | {public}           | ALL | (dlevel <= ( SELECT uaccount.seclv        +| \n                    |           |            |             |                    |     |    FROM uaccount                          +| \n                    |           |            |             |                    |     |   WHERE (uaccount.pguser = CURRENT_USER))) | \n regress_rls_schema | document  | p1r        | RESTRICTIVE | {regress_rls_dave} | ALL | (cid <> 44)                                | \n regress_rls_schema | document  | p2r        | RESTRICTIVE | {regress_rls_dave} | ALL | ((cid <> 44) AND (cid < 50))               | \n\n-- viewpoint from regress_rls_bob\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO ON;\nSELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my first manga\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great manga\nNOTICE:  f_leak => awesome science fiction\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n\nSELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my first manga\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great manga\nNOTICE:  f_leak => awesome science fiction\n cid | did | dlevel |      dauthor      |         dtitle          |      cname      \n-----+-----+--------+-------------------+-------------------------+-----------------\n  11 |   1 |      1 | regress_rls_bob   | my first novel          | novel\n  44 |   4 |      1 | regress_rls_bob   | my first manga          | manga\n  22 |   6 |      1 | regress_rls_carol | great science fiction   | science fiction\n  44 |   8 |      1 | regress_rls_carol | great manga             | manga\n  22 |   9 |      1 | regress_rls_dave  | awesome science fiction | science fiction\n\n-- try a sampled version\nSELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0)\n  WHERE f_leak(dtitle) ORDER BY did;\n did | cid | dlevel | dauthor | dtitle \n-----+-----+--------+---------+--------\n\n-- viewpoint from regress_rls_carol\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science fiction\nNOTICE:  f_leak => my first manga\nNOTICE:  f_leak => my second manga\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great manga\nNOTICE:  f_leak => awesome science fiction\nNOTICE:  f_leak => awesome technology book\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   5 |  44 |      2 | regress_rls_bob   | my second manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n\nSELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science fiction\nNOTICE:  f_leak => my first manga\nNOTICE:  f_leak => my second manga\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great manga\nNOTICE:  f_leak => awesome science fiction\nNOTICE:  f_leak => awesome technology book\n cid | did | dlevel |      dauthor      |         dtitle          |      cname      \n-----+-----+--------+-------------------+-------------------------+-----------------\n  11 |   1 |      1 | regress_rls_bob   | my first novel          | novel\n  11 |   2 |      2 | regress_rls_bob   | my second novel         | novel\n  22 |   3 |      2 | regress_rls_bob   | my science fiction      | science fiction\n  44 |   4 |      1 | regress_rls_bob   | my first manga          | manga\n  44 |   5 |      2 | regress_rls_bob   | my second manga         | manga\n  22 |   6 |      1 | regress_rls_carol | great science fiction   | science fiction\n  33 |   7 |      2 | regress_rls_carol | great technology book   | technology\n  44 |   8 |      1 | regress_rls_carol | great manga             | manga\n  22 |   9 |      1 | regress_rls_dave  | awesome science fiction | science fiction\n  33 |  10 |      2 | regress_rls_dave  | awesome technology book | technology\n\n-- try a sampled version\nSELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0)\n  WHERE f_leak(dtitle) ORDER BY did;\n did | cid | dlevel | dauthor | dtitle \n-----+-----+--------+---------+--------\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on document\n   Chunks excluded during startup: 0\n   InitPlan 1\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on document document_1\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_1_chunk document_2\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_2_chunk document_3\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_3_chunk document_4\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_4_chunk document_5\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_5_chunk document_6\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_6_chunk document_7\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Hash Join\n   Hash Cond: (document.cid = category.cid)\n   InitPlan 1\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Custom Scan (ChunkAppend) on document\n         Chunks excluded during startup: 0\n         ->  Seq Scan on document document_1\n               Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_1_chunk document_2\n               Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_2_chunk document_3\n               Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_3_chunk document_4\n               Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_4_chunk document_5\n               Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_5_chunk document_6\n               Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_6_chunk document_7\n               Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Hash\n         ->  Seq Scan on category\n\n-- viewpoint from regress_rls_dave\nSET SESSION AUTHORIZATION regress_rls_dave;\nSELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science fiction\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => awesome science fiction\nNOTICE:  f_leak => awesome technology book\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n\nSELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science fiction\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => awesome science fiction\nNOTICE:  f_leak => awesome technology book\n cid | did | dlevel |      dauthor      |         dtitle          |      cname      \n-----+-----+--------+-------------------+-------------------------+-----------------\n  11 |   1 |      1 | regress_rls_bob   | my first novel          | novel\n  11 |   2 |      2 | regress_rls_bob   | my second novel         | novel\n  22 |   3 |      2 | regress_rls_bob   | my science fiction      | science fiction\n  22 |   6 |      1 | regress_rls_carol | great science fiction   | science fiction\n  33 |   7 |      2 | regress_rls_carol | great technology book   | technology\n  22 |   9 |      1 | regress_rls_dave  | awesome science fiction | science fiction\n  33 |  10 |      2 | regress_rls_dave  | awesome technology book | technology\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on document\n   Chunks excluded during startup: 0\n   InitPlan 1\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on document document_1\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_1_chunk document_2\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_2_chunk document_3\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_3_chunk document_4\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_4_chunk document_5\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_5_chunk document_6\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_6_chunk document_7\n         Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Nested Loop\n   InitPlan 1\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Custom Scan (ChunkAppend) on document\n         Chunks excluded during startup: 0\n         ->  Seq Scan on document document_1\n               Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_1_chunk document_2\n               Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_2_chunk document_3\n               Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_3_chunk document_4\n               Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_4_chunk document_5\n               Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_5_chunk document_6\n               Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_6_chunk document_7\n               Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Index Scan using category_pkey on category\n         Index Cond: (cid = document.cid)\n\n-- 44 would technically fail for both p2r and p1r, but we should get an error\n-- back from p1r for this because it sorts first\nINSERT INTO document VALUES (100, 44, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail\nERROR:  new row violates row-level security policy \"p1r\" for table \"document\"\n-- Just to see a p2r error\nINSERT INTO document VALUES (100, 55, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail\nERROR:  new row violates row-level security policy \"p2r\" for table \"document\"\n-- only owner can change policies\nALTER POLICY p1 ON document USING (true);    --fail\nERROR:  must be owner of table document\nDROP POLICY p1 ON document;                  --fail\nERROR:  must be owner of relation document\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER POLICY p1 ON document USING (dauthor = current_user);\n-- viewpoint from regress_rls_bob again\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science fiction\nNOTICE:  f_leak => my first manga\nNOTICE:  f_leak => my second manga\n did | cid | dlevel |     dauthor     |       dtitle       \n-----+-----+--------+-----------------+--------------------\n   1 |  11 |      1 | regress_rls_bob | my first novel\n   2 |  11 |      2 | regress_rls_bob | my second novel\n   3 |  22 |      2 | regress_rls_bob | my science fiction\n   4 |  44 |      1 | regress_rls_bob | my first manga\n   5 |  44 |      2 | regress_rls_bob | my second manga\n\nSELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science fiction\nNOTICE:  f_leak => my first manga\nNOTICE:  f_leak => my second manga\n cid | did | dlevel |     dauthor     |       dtitle       |      cname      \n-----+-----+--------+-----------------+--------------------+-----------------\n  11 |   1 |      1 | regress_rls_bob | my first novel     | novel\n  11 |   2 |      2 | regress_rls_bob | my second novel    | novel\n  22 |   3 |      2 | regress_rls_bob | my science fiction | science fiction\n  44 |   4 |      1 | regress_rls_bob | my first manga     | manga\n  44 |   5 |      2 | regress_rls_bob | my second manga    | manga\n\n-- viewpoint from rls_regres_carol again\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great manga\n did | cid | dlevel |      dauthor      |        dtitle         \n-----+-----+--------+-------------------+-----------------------\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n\nSELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did;\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great manga\n cid | did | dlevel |      dauthor      |        dtitle         |      cname      \n-----+-----+--------+-------------------+-----------------------+-----------------\n  22 |   6 |      1 | regress_rls_carol | great science fiction | science fiction\n  33 |   7 |      2 | regress_rls_carol | great technology book | technology\n  44 |   8 |      1 | regress_rls_carol | great manga           | manga\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on document\n   Chunks excluded during startup: 0\n   ->  Seq Scan on document document_1\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_1_chunk document_2\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_2_chunk document_3\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_3_chunk document_4\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_4_chunk document_5\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_5_chunk document_6\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_1_6_chunk document_7\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Nested Loop\n   ->  Custom Scan (ChunkAppend) on document\n         Chunks excluded during startup: 0\n         ->  Seq Scan on document document_1\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_1_chunk document_2\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_2_chunk document_3\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_3_chunk document_4\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_4_chunk document_5\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_5_chunk document_6\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n         ->  Seq Scan on _hyper_1_6_chunk document_7\n               Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Index Scan using category_pkey on category\n         Index Cond: (cid = document.cid)\n\n-- interaction of FK/PK constraints\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE POLICY p2 ON category\n    USING (CASE WHEN current_user = 'regress_rls_bob' THEN cid IN (11, 33)\n           WHEN current_user = 'regress_rls_carol' THEN cid IN (22, 44)\n           ELSE false END);\nALTER TABLE category ENABLE ROW LEVEL SECURITY;\n-- cannot delete PK referenced by invisible FK\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid ORDER BY d.did, c.cid;\n did | cid | dlevel |     dauthor     |       dtitle       | cid |   cname    \n-----+-----+--------+-----------------+--------------------+-----+------------\n   1 |  11 |      1 | regress_rls_bob | my first novel     |  11 | novel\n   2 |  11 |      2 | regress_rls_bob | my second novel    |  11 | novel\n   3 |  22 |      2 | regress_rls_bob | my science fiction |     | \n   4 |  44 |      1 | regress_rls_bob | my first manga     |     | \n   5 |  44 |      2 | regress_rls_bob | my second manga    |     | \n     |     |        |                 |                    |  33 | technology\n\n\\set VERBOSITY sqlstate\nDELETE FROM category WHERE cid = 33;    -- fails with FK violation\nERROR:  23503\n\\set VERBOSITY default\n-- can insert FK referencing invisible PK\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid ORDER BY d.did, c.cid;\n did | cid | dlevel |      dauthor      |        dtitle         | cid |      cname      \n-----+-----+--------+-------------------+-----------------------+-----+-----------------\n   6 |  22 |      1 | regress_rls_carol | great science fiction |  22 | science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book |     | \n   8 |  44 |      1 | regress_rls_carol | great manga           |  44 | manga\n\nINSERT INTO document VALUES (11, 33, 1, current_user, 'hoge');\n-- UNIQUE or PRIMARY KEY constraint violation DOES reveal presence of row\nSET SESSION AUTHORIZATION regress_rls_bob;\nINSERT INTO document VALUES (8, 44, 1, 'regress_rls_bob', 'my third manga'); -- Must fail with unique violation, revealing presence of did we can't see\nERROR:  duplicate key value violates unique constraint \"5_10_document_pkey\"\nDETAIL:  Key (did)=(8) already exists.\nSELECT * FROM document WHERE did = 8; -- and confirm we can't see it\n did | cid | dlevel | dauthor | dtitle \n-----+-----+--------+---------+--------\n\n-- RLS policies are checked before constraints\nINSERT INTO document VALUES (8, 44, 1, 'regress_rls_carol', 'my third manga'); -- Should fail with RLS check violation, not duplicate key violation\nERROR:  new row violates row-level security policy for table \"document\"\nUPDATE document SET did = 8, dauthor = 'regress_rls_carol' WHERE did = 5; -- Should fail with RLS check violation, not duplicate key violation\nERROR:  new row violates row-level security policy for table \"document\"\n-- database superuser does bypass RLS policy when enabled\nRESET SESSION AUTHORIZATION;\nSET row_security TO ON;\nSELECT * FROM document;\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   5 |  44 |      2 | regress_rls_bob   | my second manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n  11 |  33 |      1 | regress_rls_carol | hoge\n\nSELECT * FROM category;\n cid |      cname      \n-----+-----------------\n  11 | novel\n  22 | science fiction\n  33 | technology\n  44 | manga\n\n-- database superuser does bypass RLS policy when disabled\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nSELECT * FROM document;\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   5 |  44 |      2 | regress_rls_bob   | my second manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n  11 |  33 |      1 | regress_rls_carol | hoge\n\nSELECT * FROM category;\n cid |      cname      \n-----+-----------------\n  11 | novel\n  22 | science fiction\n  33 | technology\n  44 | manga\n\n-- database non-superuser with bypass privilege can bypass RLS policy when disabled\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO OFF;\nSELECT * FROM document;\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   5 |  44 |      2 | regress_rls_bob   | my second manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n  11 |  33 |      1 | regress_rls_carol | hoge\n\nSELECT * FROM category;\n cid |      cname      \n-----+-----------------\n  11 | novel\n  22 | science fiction\n  33 | technology\n  44 | manga\n\n-- RLS policy does not apply to table owner when RLS enabled.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO ON;\nSELECT * FROM document;\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   5 |  44 |      2 | regress_rls_bob   | my second manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n  11 |  33 |      1 | regress_rls_carol | hoge\n\nSELECT * FROM category;\n cid |      cname      \n-----+-----------------\n  11 | novel\n  22 | science fiction\n  33 | technology\n  44 | manga\n\n-- RLS policy does not apply to table owner when RLS disabled.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO OFF;\nSELECT * FROM document;\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  22 |      2 | regress_rls_bob   | my science fiction\n   4 |  44 |      1 | regress_rls_bob   | my first manga\n   5 |  44 |      2 | regress_rls_bob   | my second manga\n   6 |  22 |      1 | regress_rls_carol | great science fiction\n   7 |  33 |      2 | regress_rls_carol | great technology book\n   8 |  44 |      1 | regress_rls_carol | great manga\n   9 |  22 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  33 |      2 | regress_rls_dave  | awesome technology book\n  11 |  33 |      1 | regress_rls_carol | hoge\n\nSELECT * FROM category;\n cid |      cname      \n-----+-----------------\n  11 | novel\n  22 | science fiction\n  33 | technology\n  44 | manga\n\n--\n-- Table inheritance and RLS policy\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO ON;\nCREATE TABLE t1 (a int, junk1 text, b text);\nALTER TABLE t1 DROP COLUMN junk1;    -- just a disturbing factor\nGRANT ALL ON t1 TO public;\nCOPY t1 FROM stdin;\nCREATE TABLE t2 (c float) INHERITS (t1);\nGRANT ALL ON t2 TO public;\nCOPY t2 FROM stdin;\nCREATE TABLE t3 (c text, b text, a int);\nALTER TABLE t3 INHERIT t1;\nGRANT ALL ON t3 TO public;\nCOPY t3(a,b,c) FROM stdin;\nCREATE POLICY p1 ON t1 FOR ALL TO PUBLIC USING (a % 2 = 0); -- be even number\nCREATE POLICY p2 ON t2 FOR ALL TO PUBLIC USING (a % 2 = 1); -- be odd number\nALTER TABLE t1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE t2 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM t1;\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n 2 | bcd\n 4 | def\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: ((a % 2) = 0)\n   ->  Seq Scan on t2 t1_2\n         Filter: ((a % 2) = 0)\n   ->  Seq Scan on t3 t1_3\n         Filter: ((a % 2) = 0)\n\nSELECT * FROM t1 WHERE f_leak(b);\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => def\nNOTICE:  f_leak => yyy\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n 2 | bcd\n 4 | def\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on t2 t1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on t3 t1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\n-- reference to system column\nSELECT ctid, * FROM t1;\n ctid  | a |  b  \n-------+---+-----\n (0,2) | 2 | bbb\n (0,4) | 4 | dad\n (0,2) | 2 | bcd\n (0,4) | 4 | def\n (0,2) | 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT *, t1 FROM t1;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: ((a % 2) = 0)\n   ->  Seq Scan on t2 t1_2\n         Filter: ((a % 2) = 0)\n   ->  Seq Scan on t3 t1_3\n         Filter: ((a % 2) = 0)\n\n-- reference to whole-row reference\nSELECT *, t1 FROM t1;\n a |  b  |   t1    \n---+-----+---------\n 2 | bbb | (2,bbb)\n 4 | dad | (4,dad)\n 2 | bcd | (2,bcd)\n 4 | def | (4,def)\n 2 | yyy | (2,yyy)\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT *, t1 FROM t1;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: ((a % 2) = 0)\n   ->  Seq Scan on t2 t1_2\n         Filter: ((a % 2) = 0)\n   ->  Seq Scan on t3 t1_3\n         Filter: ((a % 2) = 0)\n\n-- for share/update lock\nSELECT * FROM t1 FOR SHARE;\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n 2 | bcd\n 4 | def\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 FOR SHARE;\n--- QUERY PLAN ---\n LockRows\n   ->  Append\n         ->  Seq Scan on t1 t1_1\n               Filter: ((a % 2) = 0)\n         ->  Seq Scan on t2 t1_2\n               Filter: ((a % 2) = 0)\n         ->  Seq Scan on t3 t1_3\n               Filter: ((a % 2) = 0)\n\nSELECT * FROM t1 WHERE f_leak(b) FOR SHARE;\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => def\nNOTICE:  f_leak => yyy\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n 2 | bcd\n 4 | def\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;\n--- QUERY PLAN ---\n LockRows\n   ->  Append\n         ->  Seq Scan on t1 t1_1\n               Filter: (((a % 2) = 0) AND f_leak(b))\n         ->  Seq Scan on t2 t1_2\n               Filter: (((a % 2) = 0) AND f_leak(b))\n         ->  Seq Scan on t3 t1_3\n               Filter: (((a % 2) = 0) AND f_leak(b))\n\n-- union all query\nSELECT a, b, ctid FROM t2 UNION ALL SELECT a, b, ctid FROM t3;\n a |  b  | ctid  \n---+-----+-------\n 1 | abc | (0,1)\n 3 | cde | (0,3)\n 1 | xxx | (0,1)\n 2 | yyy | (0,2)\n 3 | zzz | (0,3)\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT a, b, ctid FROM t2 UNION ALL SELECT a, b, ctid FROM t3;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t2\n         Filter: ((a % 2) = 1)\n   ->  Seq Scan on t3\n\n-- superuser is allowed to bypass RLS checks\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nSELECT * FROM t1 WHERE f_leak(b);\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => ccc\nNOTICE:  f_leak => dad\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => def\nNOTICE:  f_leak => xxx\nNOTICE:  f_leak => yyy\nNOTICE:  f_leak => zzz\n a |  b  \n---+-----\n 1 | aba\n 2 | bbb\n 3 | ccc\n 4 | dad\n 1 | abc\n 2 | bcd\n 3 | cde\n 4 | def\n 1 | xxx\n 2 | yyy\n 3 | zzz\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: f_leak(b)\n   ->  Seq Scan on t2 t1_2\n         Filter: f_leak(b)\n   ->  Seq Scan on t3 t1_3\n         Filter: f_leak(b)\n\n-- non-superuser with bypass privilege can bypass RLS policy when disabled\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO OFF;\nSELECT * FROM t1 WHERE f_leak(b);\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => ccc\nNOTICE:  f_leak => dad\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => def\nNOTICE:  f_leak => xxx\nNOTICE:  f_leak => yyy\nNOTICE:  f_leak => zzz\n a |  b  \n---+-----\n 1 | aba\n 2 | bbb\n 3 | ccc\n 4 | dad\n 1 | abc\n 2 | bcd\n 3 | cde\n 4 | def\n 1 | xxx\n 2 | yyy\n 3 | zzz\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: f_leak(b)\n   ->  Seq Scan on t2 t1_2\n         Filter: f_leak(b)\n   ->  Seq Scan on t3 t1_3\n         Filter: f_leak(b)\n\n--\n-- Hyper Tables\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE hyper_document (\n    did         int,\n    cid         int,\n    dlevel      int not null,\n    dauthor     name,\n    dtitle      text\n);\nGRANT ALL ON hyper_document TO public;\nSELECT public.create_hypertable('hyper_document', 'did', chunk_time_interval=>2);\n            create_hypertable            \n-----------------------------------------\n (2,regress_rls_schema,hyper_document,t)\n\nINSERT INTO hyper_document VALUES\n    ( 1, 11, 1, 'regress_rls_bob', 'my first novel'),\n    ( 2, 11, 2, 'regress_rls_bob', 'my second novel'),\n    ( 3, 99, 2, 'regress_rls_bob', 'my science textbook'),\n    ( 4, 55, 1, 'regress_rls_bob', 'my first satire'),\n    ( 5, 99, 2, 'regress_rls_bob', 'my history book'),\n    ( 6, 11, 1, 'regress_rls_carol', 'great science fiction'),\n    ( 7, 99, 2, 'regress_rls_carol', 'great technology book'),\n    ( 8, 55, 2, 'regress_rls_carol', 'great satire'),\n    ( 9, 11, 1, 'regress_rls_dave', 'awesome science fiction'),\n    (10, 99, 2, 'regress_rls_dave', 'awesome technology book');\nALTER TABLE hyper_document ENABLE ROW LEVEL SECURITY;\nGRANT ALL ON _timescaledb_internal._hyper_2_9_chunk TO public;\n-- Create policy on parent\n-- user's security level must be higher than or equal to document's\nCREATE POLICY pp1 ON hyper_document AS PERMISSIVE\n    USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));\n-- Dave is only allowed to see cid < 55\nCREATE POLICY pp1r ON hyper_document AS RESTRICTIVE TO regress_rls_dave\n    USING (cid < 55);\n\\d+ hyper_document\n                         Table \"regress_rls_schema.hyper_document\"\n Column  |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description \n---------+---------+-----------+----------+---------+----------+--------------+-------------\n did     | integer |           | not null |         | plain    |              | \n cid     | integer |           |          |         | plain    |              | \n dlevel  | integer |           | not null |         | plain    |              | \n dauthor | name    |           |          |         | plain    |              | \n dtitle  | text    |           |          |         | extended |              | \nIndexes:\n    \"hyper_document_did_idx\" btree (did DESC)\nPolicies:\n    POLICY \"pp1\"\n      USING ((dlevel <= ( SELECT uaccount.seclv\n   FROM uaccount\n  WHERE (uaccount.pguser = CURRENT_USER))))\n    POLICY \"pp1r\" AS RESTRICTIVE\n      TO regress_rls_dave\n      USING ((cid < 55))\nNot-null constraints:\n    \"hyper_document_did_not_null\" NOT NULL \"did\"\n    \"hyper_document_dlevel_not_null\" NOT NULL \"dlevel\"\nChild tables: _timescaledb_internal._hyper_2_10_chunk,\n              _timescaledb_internal._hyper_2_11_chunk,\n              _timescaledb_internal._hyper_2_12_chunk,\n              _timescaledb_internal._hyper_2_13_chunk,\n              _timescaledb_internal._hyper_2_14_chunk,\n              _timescaledb_internal._hyper_2_9_chunk\n\nSELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename like '%hyper_document%' ORDER BY policyname;\n     schemaname     |   tablename    | policyname | permissive  |       roles        | cmd |                    qual                    | with_check \n--------------------+----------------+------------+-------------+--------------------+-----+--------------------------------------------+------------\n regress_rls_schema | hyper_document | pp1        | PERMISSIVE  | {public}           | ALL | (dlevel <= ( SELECT uaccount.seclv        +| \n                    |                |            |             |                    |     |    FROM uaccount                          +| \n                    |                |            |             |                    |     |   WHERE (uaccount.pguser = CURRENT_USER))) | \n regress_rls_schema | hyper_document | pp1r       | RESTRICTIVE | {regress_rls_dave} | ALL | (cid < 55)                                 | \n\n-- viewpoint from regress_rls_bob\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO ON;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my first satire\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => awesome science fiction\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   4 |  55 |      1 | regress_rls_bob   | my first satire\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on hyper_document\n   Chunks excluded during startup: 0\n   InitPlan 1\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on hyper_document hyper_document_1\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_9_chunk hyper_document_2\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_10_chunk hyper_document_3\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_11_chunk hyper_document_4\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_12_chunk hyper_document_5\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_13_chunk hyper_document_6\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_14_chunk hyper_document_7\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n\n-- viewpoint from regress_rls_carol\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science textbook\nNOTICE:  f_leak => my first satire\nNOTICE:  f_leak => my history book\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great satire\nNOTICE:  f_leak => awesome science fiction\nNOTICE:  f_leak => awesome technology book\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  99 |      2 | regress_rls_bob   | my science textbook\n   4 |  55 |      1 | regress_rls_bob   | my first satire\n   5 |  99 |      2 | regress_rls_bob   | my history book\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   7 |  99 |      2 | regress_rls_carol | great technology book\n   8 |  55 |      2 | regress_rls_carol | great satire\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  99 |      2 | regress_rls_dave  | awesome technology book\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on hyper_document\n   Chunks excluded during startup: 0\n   InitPlan 1\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on hyper_document hyper_document_1\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_9_chunk hyper_document_2\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_10_chunk hyper_document_3\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_11_chunk hyper_document_4\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_12_chunk hyper_document_5\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_13_chunk hyper_document_6\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_14_chunk hyper_document_7\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n\n-- viewpoint from regress_rls_dave\nSET SESSION AUTHORIZATION regress_rls_dave;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => awesome science fiction\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on hyper_document\n   Chunks excluded during startup: 0\n   InitPlan 1\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on hyper_document hyper_document_1\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_9_chunk hyper_document_2\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_10_chunk hyper_document_3\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_11_chunk hyper_document_4\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_12_chunk hyper_document_5\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_13_chunk hyper_document_6\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_14_chunk hyper_document_7\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n\n-- pp1 ERROR\nINSERT INTO hyper_document VALUES (1, 11, 5, 'regress_rls_dave', 'testing pp1'); -- fail\nERROR:  new row violates row-level security policy for table \"hyper_document\"\n-- pp1r ERROR\nINSERT INTO hyper_document VALUES (1, 99, 1, 'regress_rls_dave', 'testing pp1r'); -- fail\nERROR:  new row violates row-level security policy \"pp1r\" for table \"hyper_document\"\n-- Show that RLS policy does not apply for direct inserts to children\n-- This should fail with RLS POLICY pp1r violation.\nINSERT INTO hyper_document VALUES (1, 55, 1, 'regress_rls_dave', 'testing RLS with hypertables'); -- fail\nERROR:  new row violates row-level security policy \"pp1r\" for table \"hyper_document\"\n-- But this should succeed.\nINSERT INTO _timescaledb_internal._hyper_2_9_chunk VALUES (1, 55, 1, 'regress_rls_dave', 'testing RLS with hypertables'); -- success\n-- We still cannot see the row using the parent\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did, cid;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => awesome science fiction\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n\n-- But we can if we look directly\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk WHERE f_leak(dtitle) ORDER BY did, cid;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => testing RLS with hypertables\n did | cid | dlevel |     dauthor      |            dtitle            \n-----+-----+--------+------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob  | my first novel\n   1 |  55 |      1 | regress_rls_dave | testing RLS with hypertables\n\n-- Turn on RLS and create policy on child to show RLS is checked before constraints\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER TABLE _timescaledb_internal._hyper_2_9_chunk ENABLE ROW LEVEL SECURITY;\nCREATE POLICY pp3 ON _timescaledb_internal._hyper_2_9_chunk AS RESTRICTIVE\n    USING (cid < 55);\n-- This should fail with RLS violation now.\nSET SESSION AUTHORIZATION regress_rls_dave;\nINSERT INTO _timescaledb_internal._hyper_2_9_chunk VALUES (1, 55, 1, 'regress_rls_dave', 'testing RLS with hypertables - round 2'); -- fail\nERROR:  new row violates row-level security policy for table \"_hyper_2_9_chunk\"\n-- And now we cannot see directly into the partition either, due to RLS\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk WHERE f_leak(dtitle) ORDER BY did, cid;\n did | cid | dlevel | dauthor | dtitle \n-----+-----+--------+---------+--------\n\n-- The parent looks same as before\n-- viewpoint from regress_rls_dave\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did, cid;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => awesome science fiction\n did | cid | dlevel |      dauthor      |         dtitle          \n-----+-----+--------+-------------------+-------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on hyper_document\n   Chunks excluded during startup: 0\n   InitPlan 1\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on hyper_document hyper_document_1\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_9_chunk hyper_document_2\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_10_chunk hyper_document_3\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_11_chunk hyper_document_4\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_12_chunk hyper_document_5\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_13_chunk hyper_document_6\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_14_chunk hyper_document_7\n         Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n\n-- viewpoint from regress_rls_carol\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did, cid;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => testing RLS with hypertables\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science textbook\nNOTICE:  f_leak => my first satire\nNOTICE:  f_leak => my history book\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great satire\nNOTICE:  f_leak => awesome science fiction\nNOTICE:  f_leak => awesome technology book\n did | cid | dlevel |      dauthor      |            dtitle            \n-----+-----+--------+-------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   1 |  55 |      1 | regress_rls_dave  | testing RLS with hypertables\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  99 |      2 | regress_rls_bob   | my science textbook\n   4 |  55 |      1 | regress_rls_bob   | my first satire\n   5 |  99 |      2 | regress_rls_bob   | my history book\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   7 |  99 |      2 | regress_rls_carol | great technology book\n   8 |  55 |      2 | regress_rls_carol | great satire\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  99 |      2 | regress_rls_dave  | awesome technology book\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on hyper_document\n   Chunks excluded during startup: 0\n   InitPlan 1\n     ->  Index Scan using uaccount_pkey on uaccount\n           Index Cond: (pguser = CURRENT_USER)\n   ->  Seq Scan on hyper_document hyper_document_1\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_9_chunk hyper_document_2\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_10_chunk hyper_document_3\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_11_chunk hyper_document_4\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_12_chunk hyper_document_5\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_13_chunk hyper_document_6\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_14_chunk hyper_document_7\n         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))\n\n-- only owner can change policies\nALTER POLICY pp1 ON hyper_document USING (true);    --fail\nERROR:  must be owner of table hyper_document\nDROP POLICY pp1 ON hyper_document;                  --fail\nERROR:  must be owner of relation hyper_document\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER POLICY pp1 ON hyper_document USING (dauthor = current_user);\n-- viewpoint from regress_rls_bob again\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did, cid;\nNOTICE:  f_leak => my first novel\nNOTICE:  f_leak => my second novel\nNOTICE:  f_leak => my science textbook\nNOTICE:  f_leak => my first satire\nNOTICE:  f_leak => my history book\n did | cid | dlevel |     dauthor     |       dtitle        \n-----+-----+--------+-----------------+---------------------\n   1 |  11 |      1 | regress_rls_bob | my first novel\n   2 |  11 |      2 | regress_rls_bob | my second novel\n   3 |  99 |      2 | regress_rls_bob | my science textbook\n   4 |  55 |      1 | regress_rls_bob | my first satire\n   5 |  99 |      2 | regress_rls_bob | my history book\n\n-- viewpoint from rls_regres_carol again\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did, cid;\nNOTICE:  f_leak => great science fiction\nNOTICE:  f_leak => great technology book\nNOTICE:  f_leak => great satire\n did | cid | dlevel |      dauthor      |        dtitle         \n-----+-----+--------+-------------------+-----------------------\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   7 |  99 |      2 | regress_rls_carol | great technology book\n   8 |  55 |      2 | regress_rls_carol | great satire\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on hyper_document\n   Chunks excluded during startup: 0\n   ->  Seq Scan on hyper_document hyper_document_1\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_9_chunk hyper_document_2\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_10_chunk hyper_document_3\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_11_chunk hyper_document_4\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_12_chunk hyper_document_5\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_13_chunk hyper_document_6\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n   ->  Seq Scan on _hyper_2_14_chunk hyper_document_7\n         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))\n\n-- database superuser does bypass RLS policy when enabled\nRESET SESSION AUTHORIZATION;\nSET row_security TO ON;\nSELECT * FROM hyper_document ORDER BY did, cid;\n did | cid | dlevel |      dauthor      |            dtitle            \n-----+-----+--------+-------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   1 |  55 |      1 | regress_rls_dave  | testing RLS with hypertables\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  99 |      2 | regress_rls_bob   | my science textbook\n   4 |  55 |      1 | regress_rls_bob   | my first satire\n   5 |  99 |      2 | regress_rls_bob   | my history book\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   7 |  99 |      2 | regress_rls_carol | great technology book\n   8 |  55 |      2 | regress_rls_carol | great satire\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  99 |      2 | regress_rls_dave  | awesome technology book\n\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk ORDER BY did, cid;\n did | cid | dlevel |     dauthor      |            dtitle            \n-----+-----+--------+------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob  | my first novel\n   1 |  55 |      1 | regress_rls_dave | testing RLS with hypertables\n\n-- database non-superuser with bypass privilege can bypass RLS policy when disabled\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO OFF;\nSELECT * FROM hyper_document ORDER BY did, cid;\n did | cid | dlevel |      dauthor      |            dtitle            \n-----+-----+--------+-------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   1 |  55 |      1 | regress_rls_dave  | testing RLS with hypertables\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  99 |      2 | regress_rls_bob   | my science textbook\n   4 |  55 |      1 | regress_rls_bob   | my first satire\n   5 |  99 |      2 | regress_rls_bob   | my history book\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   7 |  99 |      2 | regress_rls_carol | great technology book\n   8 |  55 |      2 | regress_rls_carol | great satire\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  99 |      2 | regress_rls_dave  | awesome technology book\n\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk ORDER BY did, cid;\n did | cid | dlevel |     dauthor      |            dtitle            \n-----+-----+--------+------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob  | my first novel\n   1 |  55 |      1 | regress_rls_dave | testing RLS with hypertables\n\n-- RLS policy does not apply to table owner when RLS enabled.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO ON;\nSELECT * FROM hyper_document ORDER BY did, cid;\n did | cid | dlevel |      dauthor      |            dtitle            \n-----+-----+--------+-------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob   | my first novel\n   1 |  55 |      1 | regress_rls_dave  | testing RLS with hypertables\n   2 |  11 |      2 | regress_rls_bob   | my second novel\n   3 |  99 |      2 | regress_rls_bob   | my science textbook\n   4 |  55 |      1 | regress_rls_bob   | my first satire\n   5 |  99 |      2 | regress_rls_bob   | my history book\n   6 |  11 |      1 | regress_rls_carol | great science fiction\n   7 |  99 |      2 | regress_rls_carol | great technology book\n   8 |  55 |      2 | regress_rls_carol | great satire\n   9 |  11 |      1 | regress_rls_dave  | awesome science fiction\n  10 |  99 |      2 | regress_rls_dave  | awesome technology book\n\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk ORDER BY did, cid;\n did | cid | dlevel |     dauthor      |            dtitle            \n-----+-----+--------+------------------+------------------------------\n   1 |  11 |      1 | regress_rls_bob  | my first novel\n   1 |  55 |      1 | regress_rls_dave | testing RLS with hypertables\n\n-- When RLS disabled, other users get ERROR.\nSET SESSION AUTHORIZATION regress_rls_dave;\nSET row_security TO OFF;\nSELECT * FROM hyper_document ORDER BY did, cid;\nERROR:  query would be affected by row-level security policy for table \"hyper_document\"\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk ORDER BY did, cid;\nERROR:  query would be affected by row-level security policy for table \"_hyper_2_9_chunk\"\n-- Check behavior with a policy that uses a SubPlan not an InitPlan.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO ON;\nCREATE POLICY pp3 ON hyper_document AS RESTRICTIVE\n    USING ((SELECT dlevel <= seclv FROM uaccount WHERE pguser = current_user));\nSET SESSION AUTHORIZATION regress_rls_carol;\nINSERT INTO hyper_document VALUES (100, 11, 5, 'regress_rls_carol', 'testing pp3'); -- fail\nERROR:  new row violates row-level security policy \"pp3\" for table \"hyper_document\"\n----- Dependencies -----\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO ON;\nCREATE TABLE dependee (x integer, y integer);\nSELECT public.create_hypertable('dependee', 'x', chunk_time_interval=>2);\n         create_hypertable         \n-----------------------------------\n (3,regress_rls_schema,dependee,t)\n\nCREATE TABLE dependent (x integer, y integer);\nSELECT public.create_hypertable('dependent', 'x', chunk_time_interval=>2);\n         create_hypertable          \n------------------------------------\n (4,regress_rls_schema,dependent,t)\n\nCREATE POLICY d1 ON dependent FOR ALL\n    TO PUBLIC\n    USING (x = (SELECT d.x FROM dependee d WHERE d.y = y));\nDROP TABLE dependee; -- Should fail without CASCADE due to dependency on row security qual?\nERROR:  cannot drop table dependee because other objects depend on it\nDETAIL:  policy d1 on table dependent depends on table dependee\nHINT:  Use DROP ... CASCADE to drop the dependent objects too.\nDROP TABLE dependee CASCADE;\nNOTICE:  drop cascades to policy d1 on table dependent\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM dependent; -- After drop, should be unqualified\n--- QUERY PLAN ---\n Seq Scan on dependent\n\n-----   RECURSION    ----\n--\n-- Simple recursion\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE rec1 (x integer, y integer);\nSELECT public.create_hypertable('rec1', 'x', chunk_time_interval=>2);\n       create_hypertable       \n-------------------------------\n (5,regress_rls_schema,rec1,t)\n\nCREATE POLICY r1 ON rec1 USING (x = (SELECT r.x FROM rec1 r WHERE y = r.y));\nALTER TABLE rec1 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rec1; -- fail, direct recursion\nERROR:  infinite recursion detected in policy for relation \"rec1\"\n--\n-- Mutual recursion\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE rec2 (a integer, b integer);\nSELECT public.create_hypertable('rec2', 'x', chunk_time_interval=>2);\nERROR:  column \"x\" does not exist\nALTER POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2 WHERE b = y));\nCREATE POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1 WHERE y = b));\nALTER TABLE rec2 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rec1;    -- fail, mutual recursion\nERROR:  infinite recursion detected in policy for relation \"rec1\"\n--\n-- Mutual recursion via views\n--\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE VIEW rec1v AS SELECT * FROM rec1;\nCREATE VIEW rec2v AS SELECT * FROM rec2;\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2v WHERE b = y));\nALTER POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b));\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rec1;    -- fail, mutual recursion via views\nERROR:  infinite recursion detected in policy for relation \"rec1\"\n--\n-- Mutual recursion via .s.b views\n--\nSET SESSION AUTHORIZATION regress_rls_bob;\n\\set VERBOSITY terse \\\\ -- suppress cascade details\nDROP VIEW rec1v, rec2v CASCADE;\nNOTICE:  drop cascades to 2 other objects\n\\set VERBOSITY default\nCREATE VIEW rec1v WITH (security_barrier) AS SELECT * FROM rec1;\nCREATE VIEW rec2v WITH (security_barrier) AS SELECT * FROM rec2;\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2v WHERE b = y));\nCREATE POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b));\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rec1;    -- fail, mutual recursion via s.b. views\nERROR:  infinite recursion detected in policy for relation \"rec1\"\n--\n-- recursive RLS and VIEWs in policy\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE s1 (a int, b text);\nSELECT public.create_hypertable('s1', 'a', chunk_time_interval=>2);\n      create_hypertable      \n-----------------------------\n (6,regress_rls_schema,s1,t)\n\nINSERT INTO s1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x);\nCREATE TABLE s2 (x int, y text);\nSELECT public.create_hypertable('s2', 'x', chunk_time_interval=>2);\n      create_hypertable      \n-----------------------------\n (7,regress_rls_schema,s2,t)\n\nINSERT INTO s2 (SELECT x, md5(x::text) FROM generate_series(-6,6) x);\nGRANT SELECT ON s1, s2 TO regress_rls_bob;\nCREATE POLICY p1 ON s1 USING (a in (select x from s2 where y like '%2f%'));\nCREATE POLICY p2 ON s2 USING (x in (select a from s1 where b like '%22%'));\nCREATE POLICY p3 ON s1 FOR INSERT WITH CHECK (a = (SELECT a FROM s1));\nALTER TABLE s1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE s2 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE VIEW v2 AS SELECT * FROM s2 WHERE y like '%af%';\nSELECT * FROM s1 WHERE f_leak(b); -- fail (infinite recursion)\nERROR:  infinite recursion detected in policy for relation \"s1\"\nINSERT INTO s1 VALUES (1, 'foo'); -- fail (infinite recursion)\nERROR:  infinite recursion detected in policy for relation \"s1\"\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP POLICY p3 on s1;\nALTER POLICY p2 ON s2 USING (x % 2 = 0);\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM s1 WHERE f_leak(b);\t-- OK\nNOTICE:  f_leak => c81e728d9d4c2f636f067f89cc14862c\nNOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c\n a |                b                 \n---+----------------------------------\n 2 | c81e728d9d4c2f636f067f89cc14862c\n 4 | a87ff679a2f3e71d9181a67b7542122c\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM only s1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Seq Scan on s1\n   Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))\n   SubPlan 1\n     ->  Append\n           ->  Seq Scan on s2 s2_1\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_27_chunk s2_2\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_28_chunk s2_3\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_29_chunk s2_4\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_30_chunk s2_5\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_31_chunk s2_6\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_32_chunk s2_7\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n           ->  Seq Scan on _hyper_7_33_chunk s2_8\n                 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER POLICY p1 ON s1 USING (a in (select x from v2)); -- using VIEW in RLS policy\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM s1 WHERE f_leak(b);\t-- OK\nNOTICE:  f_leak => 0267aaf632e87a63288a08331f22c7c3\nNOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc\n a  |                b                 \n----+----------------------------------\n -4 | 0267aaf632e87a63288a08331f22c7c3\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM s1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on s1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on s1 s1_1\n         Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))\n         SubPlan 1\n           ->  Append\n                 ->  Seq Scan on s2 s2_1\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_27_chunk s2_2\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_28_chunk s2_3\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_29_chunk s2_4\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_30_chunk s2_5\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_31_chunk s2_6\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_32_chunk s2_7\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                 ->  Seq Scan on _hyper_7_33_chunk s2_8\n                       Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n   ->  Seq Scan on _hyper_6_16_chunk s1_2\n         Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_17_chunk s1_3\n         Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_18_chunk s1_4\n         Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_19_chunk s1_5\n         Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_20_chunk s1_6\n         Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_21_chunk s1_7\n         Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_22_chunk s1_8\n         Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_23_chunk s1_9\n         Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_24_chunk s1_10\n         Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_25_chunk s1_11\n         Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))\n   ->  Seq Scan on _hyper_6_26_chunk s1_12\n         Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))\n\nSELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%';\n xx | x  |                y                 \n----+----+----------------------------------\n -6 | -6 | 596a3d04481816330f07e4f97510c28f\n -4 | -4 | 0267aaf632e87a63288a08331f22c7c3\n  2 |  2 | c81e728d9d4c2f636f067f89cc14862c\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%';\n--- QUERY PLAN ---\n Result\n   ->  Append\n         ->  Seq Scan on s2 s2_1\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_27_chunk s2_2\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_28_chunk s2_3\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_29_chunk s2_4\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_30_chunk s2_5\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_31_chunk s2_6\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_32_chunk s2_7\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n         ->  Seq Scan on _hyper_7_33_chunk s2_8\n               Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))\n   SubPlan 2\n     ->  Limit\n           ->  Result\n                 ->  Custom Scan (ChunkAppend) on s1\n                       ->  Seq Scan on s1 s1_1\n                             Filter: (ANY (a = (hashed SubPlan 1).col1))\n                             SubPlan 1\n                               ->  Append\n                                     ->  Seq Scan on s2 s2_10\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_27_chunk s2_11\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_28_chunk s2_12\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_29_chunk s2_13\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_30_chunk s2_14\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_31_chunk s2_15\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_32_chunk s2_16\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                                     ->  Seq Scan on _hyper_7_33_chunk s2_17\n                                           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))\n                       ->  Seq Scan on _hyper_6_16_chunk s1_2\n                             Filter: (ANY (a = (hashed SubPlan 1).col1))\n                       ->  Seq Scan on _hyper_6_17_chunk s1_3\n                             Filter: (ANY (a = (hashed SubPlan 1).col1))\n                       ->  Seq Scan on _hyper_6_18_chunk s1_4\n                             Filter: (ANY (a = (hashed SubPlan 1).col1))\n                       ->  Seq Scan on _hyper_6_19_chunk s1_5\n                             Filter: (ANY (a = (hashed SubPlan 1).col1))\n                       ->  Seq Scan on _hyper_6_20_chunk s1_6\n                             Filter: (ANY (a = (hashed SubPlan 1).col1))\n                       ->  Seq Scan on _hyper_6_21_chunk s1_7\n                             Filter: (ANY (a = (hashed SubPlan 1).col1))\n                       ->  Seq Scan on _hyper_6_22_chunk s1_8\n                             Filter: (ANY (a = (hashed SubPlan 1).col1))\n                       ->  Seq Scan on _hyper_6_23_chunk s1_9\n                             Filter: (ANY (a = (hashed SubPlan 1).col1))\n                       ->  Seq Scan on _hyper_6_24_chunk s1_10\n                             Filter: (ANY (a = (hashed SubPlan 1).col1))\n                       ->  Seq Scan on _hyper_6_25_chunk s1_11\n                             Filter: (ANY (a = (hashed SubPlan 1).col1))\n                       ->  Seq Scan on _hyper_6_26_chunk s1_12\n                             Filter: (ANY (a = (hashed SubPlan 1).col1))\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER POLICY p2 ON s2 USING (x in (select a from s1 where b like '%d2%'));\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM s1 WHERE f_leak(b);\t-- fail (infinite recursion via view)\nERROR:  infinite recursion detected in policy for relation \"s1\"\n-- prepared statement with regress_rls_alice privilege\nPREPARE p1(int) AS SELECT * FROM t1 WHERE a <= $1;\nEXECUTE p1(2);\n a |  b  \n---+-----\n 2 | bbb\n 2 | bcd\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE p1(2);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: ((a <= 2) AND ((a % 2) = 0))\n   ->  Seq Scan on t2 t1_2\n         Filter: ((a <= 2) AND ((a % 2) = 0))\n   ->  Seq Scan on t3 t1_3\n         Filter: ((a <= 2) AND ((a % 2) = 0))\n\n-- superuser is allowed to bypass RLS checks\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nSELECT * FROM t1 WHERE f_leak(b);\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => ccc\nNOTICE:  f_leak => dad\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => def\nNOTICE:  f_leak => xxx\nNOTICE:  f_leak => yyy\nNOTICE:  f_leak => zzz\n a |  b  \n---+-----\n 1 | aba\n 2 | bbb\n 3 | ccc\n 4 | dad\n 1 | abc\n 2 | bcd\n 3 | cde\n 4 | def\n 1 | xxx\n 2 | yyy\n 3 | zzz\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: f_leak(b)\n   ->  Seq Scan on t2 t1_2\n         Filter: f_leak(b)\n   ->  Seq Scan on t3 t1_3\n         Filter: f_leak(b)\n\n-- plan cache should be invalidated\nEXECUTE p1(2);\n a |  b  \n---+-----\n 1 | aba\n 2 | bbb\n 1 | abc\n 2 | bcd\n 1 | xxx\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE p1(2);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: (a <= 2)\n   ->  Seq Scan on t2 t1_2\n         Filter: (a <= 2)\n   ->  Seq Scan on t3 t1_3\n         Filter: (a <= 2)\n\nPREPARE p2(int) AS SELECT * FROM t1 WHERE a = $1;\nEXECUTE p2(2);\n a |  b  \n---+-----\n 2 | bbb\n 2 | bcd\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE p2(2);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: (a = 2)\n   ->  Seq Scan on t2 t1_2\n         Filter: (a = 2)\n   ->  Seq Scan on t3 t1_3\n         Filter: (a = 2)\n\n-- also, case when privilege switch from superuser\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO ON;\nEXECUTE p2(2);\n a |  b  \n---+-----\n 2 | bbb\n 2 | bcd\n 2 | yyy\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE p2(2);\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n         Filter: ((a = 2) AND ((a % 2) = 0))\n   ->  Seq Scan on t2 t1_2\n         Filter: ((a = 2) AND ((a % 2) = 0))\n   ->  Seq Scan on t3 t1_3\n         Filter: ((a = 2) AND ((a % 2) = 0))\n\n--\n-- UPDATE / DELETE and Row-level security\n--\nSET SESSION AUTHORIZATION regress_rls_bob;\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t1 SET b = b || b WHERE f_leak(b);\n--- QUERY PLAN ---\n Update on t1\n   Update on t1 t1_1\n   Update on t2 t1_2\n   Update on t3 t1_3\n   ->  Result\n         ->  Append\n               ->  Seq Scan on t1 t1_1\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t2 t1_2\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t3 t1_3\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nUPDATE t1 SET b = b || b WHERE f_leak(b);\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => def\nNOTICE:  f_leak => yyy\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b);\n--- QUERY PLAN ---\n Update on t1\n   ->  Seq Scan on t1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nUPDATE only t1 SET b = b || '_updt' WHERE f_leak(b);\nNOTICE:  f_leak => bbbbbb\nNOTICE:  f_leak => daddad\n-- returning clause with system column\nUPDATE only t1 SET b = b WHERE f_leak(b) RETURNING ctid, *, t1;\nNOTICE:  f_leak => bbbbbb_updt\nNOTICE:  f_leak => daddad_updt\n  ctid  | a |      b      |       t1        \n--------+---+-------------+-----------------\n (0,9)  | 2 | bbbbbb_updt | (2,bbbbbb_updt)\n (0,10) | 4 | daddad_updt | (4,daddad_updt)\n\nUPDATE t1 SET b = b WHERE f_leak(b) RETURNING *;\nNOTICE:  f_leak => bbbbbb_updt\nNOTICE:  f_leak => daddad_updt\nNOTICE:  f_leak => bcdbcd\nNOTICE:  f_leak => defdef\nNOTICE:  f_leak => yyyyyy\n a |      b      \n---+-------------\n 2 | bbbbbb_updt\n 4 | daddad_updt\n 2 | bcdbcd\n 4 | defdef\n 2 | yyyyyy\n\nUPDATE t1 SET b = b WHERE f_leak(b) RETURNING ctid, *, t1;\nNOTICE:  f_leak => bbbbbb_updt\nNOTICE:  f_leak => daddad_updt\nNOTICE:  f_leak => bcdbcd\nNOTICE:  f_leak => defdef\nNOTICE:  f_leak => yyyyyy\n  ctid  | a |      b      |       t1        \n--------+---+-------------+-----------------\n (0,13) | 2 | bbbbbb_updt | (2,bbbbbb_updt)\n (0,14) | 4 | daddad_updt | (4,daddad_updt)\n (0,9)  | 2 | bcdbcd      | (2,bcdbcd)\n (0,10) | 4 | defdef      | (4,defdef)\n (0,6)  | 2 | yyyyyy      | (2,yyyyyy)\n\n-- updates with from clause\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t2 SET b=t2.b FROM t3\nWHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b);\n--- QUERY PLAN ---\n Update on t2\n   ->  Nested Loop\n         ->  Seq Scan on t2\n               Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))\n         ->  Seq Scan on t3\n               Filter: ((a = 2) AND f_leak(b))\n\nUPDATE t2 SET b=t2.b FROM t3\nWHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b);\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => yyyyyy\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t1 SET b=t1.b FROM t2\nWHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);\n--- QUERY PLAN ---\n Update on t1\n   Update on t1 t1_1\n   Update on t2 t1_2\n   Update on t3 t1_3\n   ->  Nested Loop\n         ->  Seq Scan on t2\n               Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))\n         ->  Append\n               ->  Seq Scan on t1 t1_1\n                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t2 t1_2\n                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t3 t1_3\n                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))\n\nUPDATE t1 SET b=t1.b FROM t2\nWHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);\nNOTICE:  f_leak => cde\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t2 SET b=t2.b FROM t1\nWHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);\n--- QUERY PLAN ---\n Update on t2\n   ->  Nested Loop\n         ->  Seq Scan on t2\n               Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))\n         ->  Append\n               ->  Seq Scan on t1 t1_1\n                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t2 t1_2\n                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t3 t1_3\n                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))\n\nUPDATE t2 SET b=t2.b FROM t1\nWHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);\nNOTICE:  f_leak => cde\n-- updates with from clause self join\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2\nWHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b\nAND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2;\n--- QUERY PLAN ---\n Update on t2 t2_1\n   ->  Nested Loop\n         Join Filter: (t2_1.b = t2_2.b)\n         ->  Seq Scan on t2 t2_1\n               Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))\n         ->  Seq Scan on t2 t2_2\n               Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))\n\nUPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2\nWHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b\nAND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2;\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => cde\n a |  b  |  c  | a |  b  |  c  |    t2_1     |    t2_2     \n---+-----+-----+---+-----+-----+-------------+-------------\n 3 | cde | 3.3 | 3 | cde | 3.3 | (3,cde,3.3) | (3,cde,3.3)\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2\nWHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b\nAND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;\n--- QUERY PLAN ---\n Update on t1 t1_1\n   Update on t1 t1_1_1\n   Update on t2 t1_1_2\n   Update on t3 t1_1_3\n   ->  Nested Loop\n         Join Filter: (t1_1.b = t1_2.b)\n         ->  Append\n               ->  Seq Scan on t1 t1_1_1\n                     Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t2 t1_1_2\n                     Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on t3 t1_1_3\n                     Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))\n         ->  Materialize\n               ->  Append\n                     ->  Seq Scan on t1 t1_2_1\n                           Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))\n                     ->  Seq Scan on t2 t1_2_2\n                           Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))\n                     ->  Seq Scan on t3 t1_2_3\n                           Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))\n\nUPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2\nWHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b\nAND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;\nNOTICE:  f_leak => daddad_updt\nNOTICE:  f_leak => daddad_updt\nNOTICE:  f_leak => defdef\nNOTICE:  f_leak => defdef\n a |      b      | a |      b      |      t1_1       |      t1_2       \n---+-------------+---+-------------+-----------------+-----------------\n 4 | daddad_updt | 4 | daddad_updt | (4,daddad_updt) | (4,daddad_updt)\n 4 | defdef      | 4 | defdef      | (4,defdef)      | (4,defdef)\n\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nSELECT * FROM t1 ORDER BY a,b;\n a |      b      \n---+-------------\n 1 | aba\n 1 | abc\n 1 | xxx\n 2 | bbbbbb_updt\n 2 | bcdbcd\n 2 | yyyyyy\n 3 | ccc\n 3 | cde\n 3 | zzz\n 4 | daddad_updt\n 4 | defdef\n\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO ON;\nEXPLAIN (BUFFERS OFF, COSTS OFF) DELETE FROM only t1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Delete on t1\n   ->  Seq Scan on t1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) DELETE FROM t1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Delete on t1\n   Delete on t1 t1_1\n   Delete on t2 t1_2\n   Delete on t3 t1_3\n   ->  Append\n         ->  Seq Scan on t1 t1_1\n               Filter: (((a % 2) = 0) AND f_leak(b))\n         ->  Seq Scan on t2 t1_2\n               Filter: (((a % 2) = 0) AND f_leak(b))\n         ->  Seq Scan on t3 t1_3\n               Filter: (((a % 2) = 0) AND f_leak(b))\n\nDELETE FROM only t1 WHERE f_leak(b) RETURNING ctid, *, t1;\nNOTICE:  f_leak => bbbbbb_updt\nNOTICE:  f_leak => daddad_updt\n  ctid  | a |      b      |       t1        \n--------+---+-------------+-----------------\n (0,13) | 2 | bbbbbb_updt | (2,bbbbbb_updt)\n (0,15) | 4 | daddad_updt | (4,daddad_updt)\n\nDELETE FROM t1 WHERE f_leak(b) RETURNING ctid, *, t1;\nNOTICE:  f_leak => bcdbcd\nNOTICE:  f_leak => defdef\nNOTICE:  f_leak => yyyyyy\n  ctid  | a |   b    |     t1     \n--------+---+--------+------------\n (0,9)  | 2 | bcdbcd | (2,bcdbcd)\n (0,13) | 4 | defdef | (4,defdef)\n (0,6)  | 2 | yyyyyy | (2,yyyyyy)\n\n--\n-- S.b. view on top of Row-level security\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE b1 (a int, b text);\nSELECT public.create_hypertable('b1', 'a', chunk_time_interval=>2);\n      create_hypertable      \n-----------------------------\n (8,regress_rls_schema,b1,t)\n\nINSERT INTO b1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x);\nCREATE POLICY p1 ON b1 USING (a % 2 = 0);\nALTER TABLE b1 ENABLE ROW LEVEL SECURITY;\nGRANT ALL ON b1 TO regress_rls_bob;\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE VIEW bv1 WITH (security_barrier) AS SELECT * FROM b1 WHERE a > 0 WITH CHECK OPTION;\nGRANT ALL ON bv1 TO regress_rls_carol;\nSET SESSION AUTHORIZATION regress_rls_carol;\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM bv1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Subquery Scan on bv1\n   Filter: f_leak(bv1.b)\n   ->  Append\n         ->  Seq Scan on b1 b1_1\n               Filter: ((a > 0) AND ((a % 2) = 0))\n         ->  Index Scan using _hyper_8_39_chunk_b1_a_idx on _hyper_8_39_chunk b1_2\n               Index Cond: (a > 0)\n               Filter: ((a % 2) = 0)\n         ->  Index Scan using _hyper_8_40_chunk_b1_a_idx on _hyper_8_40_chunk b1_3\n               Index Cond: (a > 0)\n               Filter: ((a % 2) = 0)\n         ->  Index Scan using _hyper_8_41_chunk_b1_a_idx on _hyper_8_41_chunk b1_4\n               Index Cond: (a > 0)\n               Filter: ((a % 2) = 0)\n         ->  Index Scan using _hyper_8_42_chunk_b1_a_idx on _hyper_8_42_chunk b1_5\n               Index Cond: (a > 0)\n               Filter: ((a % 2) = 0)\n         ->  Index Scan using _hyper_8_43_chunk_b1_a_idx on _hyper_8_43_chunk b1_6\n               Index Cond: (a > 0)\n               Filter: ((a % 2) = 0)\n         ->  Index Scan using _hyper_8_44_chunk_b1_a_idx on _hyper_8_44_chunk b1_7\n               Index Cond: (a > 0)\n               Filter: ((a % 2) = 0)\n\nSELECT * FROM bv1 WHERE f_leak(b);\nNOTICE:  f_leak => c81e728d9d4c2f636f067f89cc14862c\nNOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c\nNOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc\nNOTICE:  f_leak => c9f0f895fb98ab9159f51fd0297e236d\nNOTICE:  f_leak => d3d9446802a44259755d38e6d163e820\n a  |                b                 \n----+----------------------------------\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 10 | d3d9446802a44259755d38e6d163e820\n\nINSERT INTO bv1 VALUES (-1, 'xxx'); -- should fail view WCO\nERROR:  new row violates row-level security policy for table \"b1\"\nINSERT INTO bv1 VALUES (11, 'xxx'); -- should fail RLS check\nERROR:  new row violates row-level security policy for table \"b1\"\nINSERT INTO bv1 VALUES (12, 'xxx'); -- ok\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Update on b1\n         Update on _hyper_8_41_chunk b1_1\n         ->  Result\n               ->  Custom Scan (ChunkAppend) on b1\n                     Chunks excluded during startup: 0\n                     ->  Index Scan using _hyper_8_41_chunk_b1_a_idx on _hyper_8_41_chunk b1_1\n                           Index Cond: ((a > 0) AND (a = 4))\n                           Filter: (((a % 2) = 0) AND f_leak(b))\n\nUPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b);\nNOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c\nEXPLAIN (BUFFERS OFF, COSTS OFF) DELETE FROM bv1 WHERE a = 6 AND f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Delete on b1\n         Delete on _hyper_8_42_chunk b1_1\n         ->  Custom Scan (ChunkAppend) on b1\n               Chunks excluded during startup: 0\n               ->  Index Scan using _hyper_8_42_chunk_b1_a_idx on _hyper_8_42_chunk b1_1\n                     Index Cond: ((a > 0) AND (a = 6))\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nDELETE FROM bv1 WHERE a = 6 AND f_leak(b);\nNOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc\nSET SESSION AUTHORIZATION regress_rls_alice;\nSELECT * FROM b1;\n  a  |                b                 \n-----+----------------------------------\n -10 | 1b0fd9efa5279c4203b7c70233f86dbf\n  -9 | 252e691406782824eec43d7eadc3d256\n  -8 | a8d2ec85eaf98407310b72eb73dda247\n  -7 | 74687a12d3915d3c4d83f1af7b3683d5\n  -6 | 596a3d04481816330f07e4f97510c28f\n  -5 | 47c1b025fa18ea96c33fbb6718688c0f\n  -4 | 0267aaf632e87a63288a08331f22c7c3\n  -3 | b3149ecea4628efd23d2f86e5a723472\n  -2 | 5d7b9adcbe1c629ec722529dd12e5129\n  -1 | 6bb61e3b7bce0931da574d19d1d82c88\n   0 | cfcd208495d565ef66e7dff9f98764da\n   1 | c4ca4238a0b923820dcc509a6f75849b\n   2 | c81e728d9d4c2f636f067f89cc14862c\n   3 | eccbc87e4b5ce2fe28308fd9f2a7baf3\n   5 | e4da3b7fbbce2345d7772b0674a318d5\n   4 | yyy\n   7 | 8f14e45fceea167a5a36dedd4bea2543\n   8 | c9f0f895fb98ab9159f51fd0297e236d\n   9 | 45c48cce2e2d7fbdea1afc51c7c6ad26\n  10 | d3d9446802a44259755d38e6d163e820\n  12 | xxx\n\n--\n-- INSERT ... ON CONFLICT DO UPDATE and Row-level security\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP POLICY p1 ON document;\nDROP POLICY p1r ON document;\nCREATE POLICY p1 ON document FOR SELECT USING (true);\nCREATE POLICY p2 ON document FOR INSERT WITH CHECK (dauthor = current_user);\nCREATE POLICY p3 ON document FOR UPDATE\n  USING (cid = (SELECT cid from category WHERE cname = 'novel'))\n  WITH CHECK (dauthor = current_user);\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Exists...\nSELECT * FROM document WHERE did = 2;\n did | cid | dlevel |     dauthor     |     dtitle      \n-----+-----+--------+-----------------+-----------------\n   2 |  11 |      2 | regress_rls_bob | my second novel\n\n-- ...so violates actual WITH CHECK OPTION within UPDATE (not INSERT, since\n-- alternative UPDATE path happens to be taken):\nINSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_carol', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, dauthor = EXCLUDED.dauthor;\nERROR:  new row violates row-level security policy for table \"document\"\n-- Violates USING qual for UPDATE policy p3.\n--\n-- UPDATE path is taken, but UPDATE fails purely because *existing* row to be\n-- updated is not a \"novel\"/cid 11 (row is not leaked, even though we have\n-- SELECT privileges sufficient to see the row in this instance):\nINSERT INTO document VALUES (33, 22, 1, 'regress_rls_bob', 'okay science fiction'); -- preparation for next statement\nINSERT INTO document VALUES (33, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'Some novel, replaces sci-fi') -- takes UPDATE path\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle;\nERROR:  new row violates row-level security policy (USING expression) for table \"document\"\n-- Fine (we UPDATE, since INSERT WCOs and UPDATE security barrier quals + WCOs\n-- not violated):\nINSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;\n did | cid | dlevel |     dauthor     |     dtitle     \n-----+-----+--------+-----------------+----------------\n   2 |  11 |      2 | regress_rls_bob | my first novel\n\n-- Fine (we INSERT, so \"cid = 33\" (\"technology\") isn't evaluated):\nINSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *;\n did | cid | dlevel |     dauthor     |        dtitle         \n-----+-----+--------+-----------------+-----------------------\n  78 |  11 |      1 | regress_rls_bob | some technology novel\n\n-- Fine (same query, but we UPDATE, so \"cid = 33\", (\"technology\") is not the\n-- case in respect of *existing* tuple):\nINSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *;\n did | cid | dlevel |     dauthor     |        dtitle         \n-----+-----+--------+-----------------+-----------------------\n  78 |  33 |      1 | regress_rls_bob | some technology novel\n\n-- Same query a third time, but now fails due to existing tuple finally not\n-- passing quals:\nINSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *;\nERROR:  new row violates row-level security policy (USING expression) for table \"document\"\n-- Don't fail just because INSERT doesn't satisfy WITH CHECK option that\n-- originated as a barrier/USING() qual from the UPDATE.  Note that the UPDATE\n-- path *isn't* taken, and so UPDATE-related policy does not apply:\nINSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;\n did | cid | dlevel |     dauthor     |              dtitle              \n-----+-----+--------+-----------------+----------------------------------\n  79 |  33 |      1 | regress_rls_bob | technology book, can only insert\n\n-- But this time, the same statement fails, because the UPDATE path is taken,\n-- and updating the row just inserted falls afoul of security barrier qual\n-- (enforced as WCO) -- what we might have updated target tuple to is\n-- irrelevant, in fact.\nINSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;\nERROR:  new row violates row-level security policy (USING expression) for table \"document\"\n-- Test default USING qual enforced as WCO\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP POLICY p1 ON document;\nDROP POLICY p2 ON document;\nDROP POLICY p3 ON document;\nCREATE POLICY p3_with_default ON document FOR UPDATE\n  USING (cid = (SELECT cid from category WHERE cname = 'novel'));\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Just because WCO-style enforcement of USING quals occurs with\n-- existing/target tuple does not mean that the implementation can be allowed\n-- to fail to also enforce this qual against the final tuple appended to\n-- relation (since in the absence of an explicit WCO, this is also interpreted\n-- as an UPDATE/ALL WCO in general).\n--\n-- UPDATE path is taken here (fails due to existing tuple).  Note that this is\n-- not reported as a \"USING expression\", because it's an RLS UPDATE check that originated as\n-- a USING qual for the purposes of RLS in general, as opposed to an explicit\n-- USING qual that is ordinarily a security barrier.  We leave it up to the\n-- UPDATE to make this fail:\nINSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;\nERROR:  new row violates row-level security policy for table \"document\"\n-- UPDATE path is taken here.  Existing tuple passes, since it's cid\n-- corresponds to \"novel\", but default USING qual is enforced against\n-- post-UPDATE tuple too (as always when updating with a policy that lacks an\n-- explicit WCO), and so this fails:\nINSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET cid = EXCLUDED.cid, dtitle = EXCLUDED.dtitle RETURNING *;\nERROR:  new row violates row-level security policy for table \"document\"\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP POLICY p3_with_default ON document;\n--\n-- Test ALL policies with ON CONFLICT DO UPDATE (much the same as existing UPDATE\n-- tests)\n--\nCREATE POLICY p3_with_all ON document FOR ALL\n  USING (cid = (SELECT cid from category WHERE cname = 'novel'))\n  WITH CHECK (dauthor = current_user);\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Fails, since ALL WCO is enforced in insert path:\nINSERT INTO document VALUES (80, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_carol', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33;\nERROR:  new row violates row-level security policy for table \"document\"\n-- Fails, since ALL policy USING qual is enforced (existing, target tuple is in\n-- violation, since it has the \"manga\" cid):\nINSERT INTO document VALUES (4, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle;\nERROR:  new row violates row-level security policy (USING expression) for table \"document\"\n-- Fails, since ALL WCO are enforced:\nINSERT INTO document VALUES (1, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET dauthor = 'regress_rls_carol';\nERROR:  new row violates row-level security policy for table \"document\"\n--\n-- ROLE/GROUP\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE z1 (a int, b text);\nSELECT public.create_hypertable('z1', 'a', chunk_time_interval=>2);\n      create_hypertable      \n-----------------------------\n (9,regress_rls_schema,z1,t)\n\nCREATE TABLE z2 (a int, b text);\nSELECT public.create_hypertable('z2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (10,regress_rls_schema,z2,t)\n\nGRANT SELECT ON z1,z2 TO regress_rls_group1, regress_rls_group2,\n    regress_rls_bob, regress_rls_carol;\nINSERT INTO z1 VALUES\n    (1, 'aba'),\n    (2, 'bbb'),\n    (3, 'ccc'),\n    (4, 'dad');\nCREATE POLICY p1 ON z1 TO regress_rls_group1 USING (a % 2 = 0);\nCREATE POLICY p2 ON z1 TO regress_rls_group2 USING (a % 2 = 1);\nALTER TABLE z1 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM z1 WHERE f_leak(b);\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nPREPARE plancache_test AS SELECT * FROM z1 WHERE f_leak(b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nPREPARE plancache_test2 AS WITH q AS MATERIALIZED (SELECT * FROM z1 WHERE f_leak(b)) SELECT * FROM q,z2;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test2;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nPREPARE plancache_test4 AS WITH q AS (SELECT * FROM z1 WHERE f_leak(b)) SELECT * FROM q,z2;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test4;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nPREPARE plancache_test6 AS WITH q AS NOT MATERIALIZED (SELECT * FROM z1 WHERE f_leak(b)) SELECT * FROM q,z2;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test6;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nPREPARE plancache_test3 AS WITH q AS MATERIALIZED (SELECT * FROM z2) SELECT * FROM q,z1 WHERE f_leak(z1.b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test3;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Seq Scan on z2\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nPREPARE plancache_test5 AS WITH q AS (SELECT * FROM z2) SELECT * FROM q,z1 WHERE f_leak(z1.b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test5;\n--- QUERY PLAN ---\n Nested Loop\n   ->  Seq Scan on z2\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nPREPARE plancache_test7 AS WITH q AS NOT MATERIALIZED (SELECT * FROM z2) SELECT * FROM q,z1 WHERE f_leak(z1.b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test7;\n--- QUERY PLAN ---\n Nested Loop\n   ->  Seq Scan on z2\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nSET ROLE regress_rls_group1;\nSELECT * FROM z1 WHERE f_leak(b);\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test2;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test4;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test3;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Seq Scan on z2\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test5;\n--- QUERY PLAN ---\n Nested Loop\n   ->  Seq Scan on z2\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 0) AND f_leak(b))\n\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM z1 WHERE f_leak(b);\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => ccc\n a |  b  \n---+-----\n 1 | aba\n 3 | ccc\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 1) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 1) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test2;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test4;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test3;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Seq Scan on z2\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test5;\n--- QUERY PLAN ---\n Nested Loop\n   ->  Seq Scan on z2\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n\nSET ROLE regress_rls_group2;\nSELECT * FROM z1 WHERE f_leak(b);\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => ccc\n a |  b  \n---+-----\n 1 | aba\n 3 | ccc\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 1) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 1) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test2;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test4;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Custom Scan (ChunkAppend) on z1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on z1 z1_1\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_49_chunk z1_2\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_50_chunk z1_3\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n           ->  Seq Scan on _hyper_9_51_chunk z1_4\n                 Filter: (((a % 2) = 1) AND f_leak(b))\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Seq Scan on z2\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test3;\n--- QUERY PLAN ---\n Nested Loop\n   CTE q\n     ->  Seq Scan on z2\n   ->  CTE Scan on q\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test5;\n--- QUERY PLAN ---\n Nested Loop\n   ->  Seq Scan on z2\n   ->  Materialize\n         ->  Custom Scan (ChunkAppend) on z1\n               Chunks excluded during startup: 0\n               ->  Seq Scan on z1 z1_1\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_49_chunk z1_2\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_50_chunk z1_3\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n               ->  Seq Scan on _hyper_9_51_chunk z1_4\n                     Filter: (((a % 2) = 1) AND f_leak(b))\n\n--\n-- Views should follow policy for view owner.\n--\n-- View and Table owner are the same.\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b);\nGRANT SELECT ON rls_view TO regress_rls_bob;\n-- Query as role that is not owner of view or table.  Should return all records.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rls_view;\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => ccc\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 1 | aba\n 2 | bbb\n 3 | ccc\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: f_leak(b)\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: f_leak(b)\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: f_leak(b)\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: f_leak(b)\n\n-- Query as view/table owner.  Should return all records.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSELECT * FROM rls_view;\nNOTICE:  f_leak => aba\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => ccc\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 1 | aba\n 2 | bbb\n 3 | ccc\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: f_leak(b)\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: f_leak(b)\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: f_leak(b)\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: f_leak(b)\n\nDROP VIEW rls_view;\n-- View and Table owners are different.\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b);\nGRANT SELECT ON rls_view TO regress_rls_alice;\n-- Query as role that is not owner of view but is owner of table.\n-- Should return records based on view owner policies.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSELECT * FROM rls_view;\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\n-- Query as role that is not owner of table but is owner of view.\n-- Should return records based on view owner policies.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rls_view;\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\n-- Query as role that is not the owner of the table or view without permissions.\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM rls_view; --fail - permission denied.\nERROR:  permission denied for view rls_view\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.\nERROR:  permission denied for view rls_view\n-- Query as role that is not the owner of the table or view with permissions.\nSET SESSION AUTHORIZATION regress_rls_bob;\nGRANT SELECT ON rls_view TO regress_rls_carol;\nSELECT * FROM rls_view;\nNOTICE:  f_leak => bbb\nNOTICE:  f_leak => dad\n a |  b  \n---+-----\n 2 | bbb\n 4 | dad\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view;\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on z1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on z1 z1_1\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_49_chunk z1_2\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_50_chunk z1_3\n         Filter: (((a % 2) = 0) AND f_leak(b))\n   ->  Seq Scan on _hyper_9_51_chunk z1_4\n         Filter: (((a % 2) = 0) AND f_leak(b))\n\nSET SESSION AUTHORIZATION regress_rls_bob;\nDROP VIEW rls_view;\n--\n-- Command specific\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE x1 (a int, b text, c text);\nSELECT public.create_hypertable('x1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (11,regress_rls_schema,x1,t)\n\nGRANT ALL ON x1 TO PUBLIC;\nINSERT INTO x1 VALUES\n    (1, 'abc', 'regress_rls_bob'),\n    (2, 'bcd', 'regress_rls_bob'),\n    (3, 'cde', 'regress_rls_carol'),\n    (4, 'def', 'regress_rls_carol'),\n    (5, 'efg', 'regress_rls_bob'),\n    (6, 'fgh', 'regress_rls_bob'),\n    (7, 'fgh', 'regress_rls_carol'),\n    (8, 'fgh', 'regress_rls_carol');\nCREATE POLICY p0 ON x1 FOR ALL USING (c = current_user);\nCREATE POLICY p1 ON x1 FOR SELECT USING (a % 2 = 0);\nCREATE POLICY p2 ON x1 FOR INSERT WITH CHECK (a % 2 = 1);\nCREATE POLICY p3 ON x1 FOR UPDATE USING (a % 2 = 0);\nCREATE POLICY p4 ON x1 FOR DELETE USING (a < 8);\nALTER TABLE x1 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM x1 WHERE f_leak(b) ORDER BY a ASC;\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => def\nNOTICE:  f_leak => efg\nNOTICE:  f_leak => fgh\nNOTICE:  f_leak => fgh\n a |  b  |         c         \n---+-----+-------------------\n 1 | abc | regress_rls_bob\n 2 | bcd | regress_rls_bob\n 4 | def | regress_rls_carol\n 5 | efg | regress_rls_bob\n 6 | fgh | regress_rls_bob\n 8 | fgh | regress_rls_carol\n\nUPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *;\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => bcd\nNOTICE:  f_leak => def\nNOTICE:  f_leak => efg\nNOTICE:  f_leak => fgh\nNOTICE:  f_leak => fgh\n a |    b     |         c         \n---+----------+-------------------\n 1 | abc_updt | regress_rls_bob\n 2 | bcd_updt | regress_rls_bob\n 4 | def_updt | regress_rls_carol\n 5 | efg_updt | regress_rls_bob\n 6 | fgh_updt | regress_rls_bob\n 8 | fgh_updt | regress_rls_carol\n\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM x1 WHERE f_leak(b) ORDER BY a ASC;\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => bcd_updt\nNOTICE:  f_leak => def_updt\nNOTICE:  f_leak => fgh\nNOTICE:  f_leak => fgh_updt\nNOTICE:  f_leak => fgh_updt\n a |    b     |         c         \n---+----------+-------------------\n 2 | bcd_updt | regress_rls_bob\n 3 | cde      | regress_rls_carol\n 4 | def_updt | regress_rls_carol\n 6 | fgh_updt | regress_rls_bob\n 7 | fgh      | regress_rls_carol\n 8 | fgh_updt | regress_rls_carol\n\nUPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *;\nNOTICE:  f_leak => cde\nNOTICE:  f_leak => bcd_updt\nNOTICE:  f_leak => def_updt\nNOTICE:  f_leak => fgh\nNOTICE:  f_leak => fgh_updt\nNOTICE:  f_leak => fgh_updt\n a |       b       |         c         \n---+---------------+-------------------\n 3 | cde_updt      | regress_rls_carol\n 2 | bcd_updt_updt | regress_rls_bob\n 4 | def_updt_updt | regress_rls_carol\n 7 | fgh_updt      | regress_rls_carol\n 6 | fgh_updt_updt | regress_rls_bob\n 8 | fgh_updt_updt | regress_rls_carol\n\nDELETE FROM x1 WHERE f_leak(b) RETURNING *;\nNOTICE:  f_leak => cde_updt\nNOTICE:  f_leak => bcd_updt_updt\nNOTICE:  f_leak => def_updt_updt\nNOTICE:  f_leak => fgh_updt\nNOTICE:  f_leak => fgh_updt_updt\nNOTICE:  f_leak => fgh_updt_updt\n a |       b       |         c         \n---+---------------+-------------------\n 3 | cde_updt      | regress_rls_carol\n 2 | bcd_updt_updt | regress_rls_bob\n 4 | def_updt_updt | regress_rls_carol\n 7 | fgh_updt      | regress_rls_carol\n 6 | fgh_updt_updt | regress_rls_bob\n 8 | fgh_updt_updt | regress_rls_carol\n\n--\n-- Duplicate Policy Names\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE y1 (a int, b text);\nSELECT public.create_hypertable('y1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (12,regress_rls_schema,y1,t)\n\nINSERT INTO y1 VALUES(1,2);\nCREATE TABLE y2 (a int, b text);\nSELECT public.create_hypertable('y2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (13,regress_rls_schema,y2,t)\n\nGRANT ALL ON y1, y2 TO regress_rls_bob;\nCREATE POLICY p1 ON y1 FOR ALL USING (a % 2 = 0);\nCREATE POLICY p2 ON y1 FOR SELECT USING (a > 2);\nCREATE POLICY p1 ON y1 FOR SELECT USING (a % 2 = 1);  --fail\nERROR:  policy \"p1\" for table \"y1\" already exists\nCREATE POLICY p1 ON y2 FOR ALL USING (a % 2 = 0);  --OK\nALTER TABLE y1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE y2 ENABLE ROW LEVEL SECURITY;\n--\n-- Expression structure with SBV\n--\n-- Create view as table owner.  RLS should NOT be applied.\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE VIEW rls_sbv WITH (security_barrier) AS\n    SELECT * FROM y1 WHERE f_leak(b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on y1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on y1 y1_1\n         Filter: (f_leak(b) AND (a = 1))\n   ->  Index Scan using _hyper_12_57_chunk_y1_a_idx on _hyper_12_57_chunk y1_2\n         Index Cond: (a = 1)\n         Filter: f_leak(b)\n\nDROP VIEW rls_sbv;\n-- Create view as role that does not own table.  RLS should be applied.\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE VIEW rls_sbv WITH (security_barrier) AS\n    SELECT * FROM y1 WHERE f_leak(b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on y1\n   Chunks excluded during startup: 0\n   ->  Seq Scan on y1 y1_1\n         Filter: ((a = 1) AND ((a > 2) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Index Scan using _hyper_12_57_chunk_y1_a_idx on _hyper_12_57_chunk y1_2\n         Index Cond: (a = 1)\n         Filter: (((a > 2) OR ((a % 2) = 0)) AND f_leak(b))\n\nDROP VIEW rls_sbv;\n--\n-- Expression structure\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nINSERT INTO y2 (SELECT x, md5(x::text) FROM generate_series(0,20) x);\nCREATE POLICY p2 ON y2 USING (a % 3 = 0);\nCREATE POLICY p3 ON y2 USING (a % 4 = 0);\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM y2 WHERE f_leak(b);\nNOTICE:  f_leak => cfcd208495d565ef66e7dff9f98764da\nNOTICE:  f_leak => c81e728d9d4c2f636f067f89cc14862c\nNOTICE:  f_leak => eccbc87e4b5ce2fe28308fd9f2a7baf3\nNOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c\nNOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc\nNOTICE:  f_leak => c9f0f895fb98ab9159f51fd0297e236d\nNOTICE:  f_leak => 45c48cce2e2d7fbdea1afc51c7c6ad26\nNOTICE:  f_leak => d3d9446802a44259755d38e6d163e820\nNOTICE:  f_leak => c20ad4d76fe97759aa27a0c99bff6710\nNOTICE:  f_leak => aab3238922bcc25a6f606eb525ffdc56\nNOTICE:  f_leak => 9bf31c7ff062936a96d3c8bd1f8f2ff3\nNOTICE:  f_leak => c74d97b01eae257e44aa9d5bade97baf\nNOTICE:  f_leak => 6f4922f45568161a8cdf4ad2299f6d23\nNOTICE:  f_leak => 98f13708210194c475687be6106a3b84\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  3 | eccbc87e4b5ce2fe28308fd9f2a7baf3\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n  9 | 45c48cce2e2d7fbdea1afc51c7c6ad26\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM y2 WHERE f_leak(b);\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on y2\n   Chunks excluded during startup: 0\n   ->  Seq Scan on y2 y2_1\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_58_chunk y2_2\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_59_chunk y2_3\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_60_chunk y2_4\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_61_chunk y2_5\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_62_chunk y2_6\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_63_chunk y2_7\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_64_chunk y2_8\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_65_chunk y2_9\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_66_chunk y2_10\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_67_chunk y2_11\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n   ->  Seq Scan on _hyper_13_68_chunk y2_12\n         Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n\n--\n-- Qual push-down of leaky functions, when not referring to table\n--\nSELECT * FROM y2 WHERE f_leak('abc');\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => abc\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  3 | eccbc87e4b5ce2fe28308fd9f2a7baf3\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n  9 | 45c48cce2e2d7fbdea1afc51c7c6ad26\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM y2 WHERE f_leak('abc');\n--- QUERY PLAN ---\n Custom Scan (ChunkAppend) on y2\n   Chunks excluded during startup: 0\n   ->  Seq Scan on y2 y2_1\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_58_chunk y2_2\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_59_chunk y2_3\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_60_chunk y2_4\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_61_chunk y2_5\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_62_chunk y2_6\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_63_chunk y2_7\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_64_chunk y2_8\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_65_chunk y2_9\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_66_chunk y2_10\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_67_chunk y2_11\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n   ->  Seq Scan on _hyper_13_68_chunk y2_12\n         Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))\n\nCREATE TABLE test_qual_pushdown (\n    abc text\n);\nINSERT INTO test_qual_pushdown VALUES ('abc'),('def');\nSELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc);\nNOTICE:  f_leak => abc\nNOTICE:  f_leak => def\n a | b | abc \n---+---+-----\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc);\n--- QUERY PLAN ---\n Hash Join\n   Hash Cond: (y2.b = test_qual_pushdown.abc)\n   ->  Append\n         ->  Seq Scan on y2 y2_1\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_58_chunk y2_2\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_59_chunk y2_3\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_60_chunk y2_4\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_61_chunk y2_5\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_62_chunk y2_6\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_63_chunk y2_7\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_64_chunk y2_8\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_65_chunk y2_9\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_66_chunk y2_10\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_67_chunk y2_11\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n         ->  Seq Scan on _hyper_13_68_chunk y2_12\n               Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))\n   ->  Hash\n         ->  Seq Scan on test_qual_pushdown\n               Filter: f_leak(abc)\n\nSELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b);\nNOTICE:  f_leak => cfcd208495d565ef66e7dff9f98764da\nNOTICE:  f_leak => c81e728d9d4c2f636f067f89cc14862c\nNOTICE:  f_leak => eccbc87e4b5ce2fe28308fd9f2a7baf3\nNOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c\nNOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc\nNOTICE:  f_leak => c9f0f895fb98ab9159f51fd0297e236d\nNOTICE:  f_leak => 45c48cce2e2d7fbdea1afc51c7c6ad26\nNOTICE:  f_leak => d3d9446802a44259755d38e6d163e820\nNOTICE:  f_leak => c20ad4d76fe97759aa27a0c99bff6710\nNOTICE:  f_leak => aab3238922bcc25a6f606eb525ffdc56\nNOTICE:  f_leak => 9bf31c7ff062936a96d3c8bd1f8f2ff3\nNOTICE:  f_leak => c74d97b01eae257e44aa9d5bade97baf\nNOTICE:  f_leak => 6f4922f45568161a8cdf4ad2299f6d23\nNOTICE:  f_leak => 98f13708210194c475687be6106a3b84\n a | b | abc \n---+---+-----\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b);\n--- QUERY PLAN ---\n Hash Join\n   Hash Cond: (test_qual_pushdown.abc = y2.b)\n   ->  Seq Scan on test_qual_pushdown\n   ->  Hash\n         ->  Custom Scan (ChunkAppend) on y2\n               Chunks excluded during startup: 0\n               ->  Seq Scan on y2 y2_1\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_58_chunk y2_2\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_59_chunk y2_3\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_60_chunk y2_4\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_61_chunk y2_5\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_62_chunk y2_6\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_63_chunk y2_7\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_64_chunk y2_8\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_65_chunk y2_9\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_66_chunk y2_10\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_67_chunk y2_11\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n               ->  Seq Scan on _hyper_13_68_chunk y2_12\n                     Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))\n\nDROP TABLE test_qual_pushdown;\n--\n-- Plancache invalidate on user change.\n--\nRESET SESSION AUTHORIZATION;\n\\set VERBOSITY terse \\\\ -- suppress cascade details\nDROP TABLE t1 CASCADE;\nNOTICE:  drop cascades to 2 other objects\n\\set VERBOSITY default\nCREATE TABLE t1 (a integer);\nSELECT public.create_hypertable('t1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (14,regress_rls_schema,t1,t)\n\nGRANT SELECT ON t1 TO regress_rls_bob, regress_rls_carol;\nCREATE POLICY p1 ON t1 TO regress_rls_bob USING ((a % 2) = 0);\nCREATE POLICY p2 ON t1 TO regress_rls_carol USING ((a % 4) = 0);\nALTER TABLE t1 ENABLE ROW LEVEL SECURITY;\n-- Prepare as regress_rls_bob\nSET ROLE regress_rls_bob;\nPREPARE role_inval AS SELECT * FROM t1;\n-- Check plan\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE role_inval;\n--- QUERY PLAN ---\n Seq Scan on t1\n   Filter: ((a % 2) = 0)\n\n-- Change to regress_rls_carol\nSET ROLE regress_rls_carol;\n-- Check plan- should be different\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE role_inval;\n--- QUERY PLAN ---\n Seq Scan on t1\n   Filter: ((a % 4) = 0)\n\n-- Change back to regress_rls_bob\nSET ROLE regress_rls_bob;\n-- Check plan- should be back to original\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE role_inval;\n--- QUERY PLAN ---\n Seq Scan on t1\n   Filter: ((a % 2) = 0)\n\n--\n-- CTE and RLS\n--\nRESET SESSION AUTHORIZATION;\nDROP TABLE t1 CASCADE;\nCREATE TABLE t1 (a integer, b text);\nSELECT public.create_hypertable('t1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (15,regress_rls_schema,t1,t)\n\nCREATE POLICY p1 ON t1 USING (a % 2 = 0);\nALTER TABLE t1 ENABLE ROW LEVEL SECURITY;\nGRANT ALL ON t1 TO regress_rls_bob;\nINSERT INTO t1 (SELECT x, md5(x::text) FROM generate_series(0,20) x);\nSET SESSION AUTHORIZATION regress_rls_bob;\nWITH cte1 AS (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1;\nNOTICE:  f_leak => cfcd208495d565ef66e7dff9f98764da\nNOTICE:  f_leak => c81e728d9d4c2f636f067f89cc14862c\nNOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c\nNOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc\nNOTICE:  f_leak => c9f0f895fb98ab9159f51fd0297e236d\nNOTICE:  f_leak => d3d9446802a44259755d38e6d163e820\nNOTICE:  f_leak => c20ad4d76fe97759aa27a0c99bff6710\nNOTICE:  f_leak => aab3238922bcc25a6f606eb525ffdc56\nNOTICE:  f_leak => c74d97b01eae257e44aa9d5bade97baf\nNOTICE:  f_leak => 6f4922f45568161a8cdf4ad2299f6d23\nNOTICE:  f_leak => 98f13708210194c475687be6106a3b84\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) WITH cte1 AS (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1;\n--- QUERY PLAN ---\n CTE Scan on cte1\n   CTE cte1\n     ->  Custom Scan (ChunkAppend) on t1\n           Chunks excluded during startup: 0\n           ->  Seq Scan on t1 t1_1\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_69_chunk t1_2\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_70_chunk t1_3\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_71_chunk t1_4\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_72_chunk t1_5\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_73_chunk t1_6\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_74_chunk t1_7\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_75_chunk t1_8\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_76_chunk t1_9\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_77_chunk t1_10\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_78_chunk t1_11\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n           ->  Seq Scan on _hyper_15_79_chunk t1_12\n                 Filter: (((a % 2) = 0) AND f_leak(b))\n\nWITH cte1 AS (UPDATE t1 SET a = a + 1 RETURNING *) SELECT * FROM cte1; --fail\nERROR:  new row violates row-level security policy for table \"t1\"\nWITH cte1 AS (UPDATE t1 SET a = a RETURNING *) SELECT * FROM cte1; --ok\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n\nWITH cte1 AS (INSERT INTO t1 VALUES (21, 'Fail') RETURNING *) SELECT * FROM cte1; --fail\nERROR:  new row violates row-level security policy for table \"t1\"\nWITH cte1 AS (INSERT INTO t1 VALUES (20, 'Success') RETURNING *) SELECT * FROM cte1; --ok\n a  |    b    \n----+---------\n 20 | Success\n\n--\n-- Rename Policy\n--\nRESET SESSION AUTHORIZATION;\nALTER POLICY p1 ON t1 RENAME TO p1; --fail\nERROR:  policy \"p1\" for table \"t1\" already exists\nSELECT polname, relname\n    FROM pg_policy pol\n    JOIN pg_class pc ON (pc.oid = pol.polrelid)\n    WHERE relname = 't1';\n polname | relname \n---------+---------\n p1      | t1\n\nALTER POLICY p1 ON t1 RENAME TO p2; --ok\nSELECT polname, relname\n    FROM pg_policy pol\n    JOIN pg_class pc ON (pc.oid = pol.polrelid)\n    WHERE relname = 't1';\n polname | relname \n---------+---------\n p2      | t1\n\n--\n-- Check INSERT SELECT\n--\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE TABLE t2 (a integer, b text);\nSELECT public.create_hypertable('t2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (16,regress_rls_schema,t2,t)\n\nINSERT INTO t2 (SELECT * FROM t1);\nEXPLAIN (BUFFERS OFF, COSTS OFF) INSERT INTO t2 (SELECT * FROM t1);\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Insert on t2\n         ->  Append\n               ->  Seq Scan on t1 t1_1\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_69_chunk t1_2\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_70_chunk t1_3\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_71_chunk t1_4\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_72_chunk t1_5\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_73_chunk t1_6\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_74_chunk t1_7\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_75_chunk t1_8\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_76_chunk t1_9\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_77_chunk t1_10\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_78_chunk t1_11\n                     Filter: ((a % 2) = 0)\n               ->  Seq Scan on _hyper_15_79_chunk t1_12\n                     Filter: ((a % 2) = 0)\n\nSELECT * FROM t2;\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n 20 | Success\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t2;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t2 t2_1\n   ->  Seq Scan on _hyper_16_80_chunk t2_2\n   ->  Seq Scan on _hyper_16_81_chunk t2_3\n   ->  Seq Scan on _hyper_16_82_chunk t2_4\n   ->  Seq Scan on _hyper_16_83_chunk t2_5\n   ->  Seq Scan on _hyper_16_84_chunk t2_6\n   ->  Seq Scan on _hyper_16_85_chunk t2_7\n   ->  Seq Scan on _hyper_16_86_chunk t2_8\n   ->  Seq Scan on _hyper_16_87_chunk t2_9\n   ->  Seq Scan on _hyper_16_88_chunk t2_10\n   ->  Seq Scan on _hyper_16_89_chunk t2_11\n   ->  Seq Scan on _hyper_16_90_chunk t2_12\n\nCREATE TABLE t3 AS SELECT * FROM t1;\nSELECT public.create_hypertable('t2', 'a', chunk_time_interval=>2);\nERROR:  table \"t2\" is already a hypertable\nSELECT * FROM t3;\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n 20 | Success\n\nSELECT * INTO t4 FROM t1;\nSELECT * FROM t4;\n a  |                b                 \n----+----------------------------------\n  0 | cfcd208495d565ef66e7dff9f98764da\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 10 | d3d9446802a44259755d38e6d163e820\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n 20 | Success\n\n--\n-- RLS with JOIN\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE blog (id integer, author text, post text);\nSELECT public.create_hypertable('blog', 'id', chunk_time_interval=>2);\n       create_hypertable        \n--------------------------------\n (17,regress_rls_schema,blog,t)\n\nCREATE TABLE comment (blog_id integer, message text);\nSELECT public.create_hypertable('comment', 'blog_id', chunk_time_interval=>2);\n         create_hypertable         \n-----------------------------------\n (18,regress_rls_schema,comment,t)\n\nGRANT ALL ON blog, comment TO regress_rls_bob;\nCREATE POLICY blog_1 ON blog USING (id % 2 = 0);\nALTER TABLE blog ENABLE ROW LEVEL SECURITY;\nINSERT INTO blog VALUES\n    (1, 'alice', 'blog #1'),\n    (2, 'bob', 'blog #1'),\n    (3, 'alice', 'blog #2'),\n    (4, 'alice', 'blog #3'),\n    (5, 'john', 'blog #1');\nINSERT INTO comment VALUES\n    (1, 'cool blog'),\n    (1, 'fun blog'),\n    (3, 'crazy blog'),\n    (5, 'what?'),\n    (4, 'insane!'),\n    (2, 'who did it?');\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Check RLS JOIN with Non-RLS.\nSELECT id, author, message FROM blog JOIN comment ON id = blog_id;\n id | author |   message   \n----+--------+-------------\n  2 | bob    | who did it?\n  4 | alice  | insane!\n\n-- Check Non-RLS JOIN with RLS.\nSELECT id, author, message FROM comment JOIN blog ON id = blog_id;\n id | author |   message   \n----+--------+-------------\n  2 | bob    | who did it?\n  4 | alice  | insane!\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE POLICY comment_1 ON comment USING (blog_id < 4);\nALTER TABLE comment ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Check RLS JOIN RLS\nSELECT id, author, message FROM blog JOIN comment ON id = blog_id;\n id | author |   message   \n----+--------+-------------\n  2 | bob    | who did it?\n\nSELECT id, author, message FROM comment JOIN blog ON id = blog_id;\n id | author |   message   \n----+--------+-------------\n  2 | bob    | who did it?\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP TABLE blog;\nDROP TABLE comment;\n--\n-- Default Deny Policy\n--\nRESET SESSION AUTHORIZATION;\nDROP POLICY p2 ON t1;\nALTER TABLE t1 OWNER TO regress_rls_alice;\n-- Check that default deny does not apply to superuser.\nRESET SESSION AUTHORIZATION;\nSELECT * FROM t1;\n a  |                b                 \n----+----------------------------------\n  1 | c4ca4238a0b923820dcc509a6f75849b\n  0 | cfcd208495d565ef66e7dff9f98764da\n  3 | eccbc87e4b5ce2fe28308fd9f2a7baf3\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  5 | e4da3b7fbbce2345d7772b0674a318d5\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  7 | 8f14e45fceea167a5a36dedd4bea2543\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  9 | 45c48cce2e2d7fbdea1afc51c7c6ad26\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 11 | 6512bd43d9caa6e02c990b0a82652dca\n 10 | d3d9446802a44259755d38e6d163e820\n 13 | c51ce410c124a10e0db5e4b97fc2af39\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 17 | 70efdf2ec9b086079795c442636b55fb\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 19 | 1f0e3dad99908345f7439f8ffabdffc4\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n 20 | Success\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n   ->  Seq Scan on _hyper_15_69_chunk t1_2\n   ->  Seq Scan on _hyper_15_70_chunk t1_3\n   ->  Seq Scan on _hyper_15_71_chunk t1_4\n   ->  Seq Scan on _hyper_15_72_chunk t1_5\n   ->  Seq Scan on _hyper_15_73_chunk t1_6\n   ->  Seq Scan on _hyper_15_74_chunk t1_7\n   ->  Seq Scan on _hyper_15_75_chunk t1_8\n   ->  Seq Scan on _hyper_15_76_chunk t1_9\n   ->  Seq Scan on _hyper_15_77_chunk t1_10\n   ->  Seq Scan on _hyper_15_78_chunk t1_11\n   ->  Seq Scan on _hyper_15_79_chunk t1_12\n\n-- Check that default deny does not apply to table owner.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSELECT * FROM t1;\n a  |                b                 \n----+----------------------------------\n  1 | c4ca4238a0b923820dcc509a6f75849b\n  0 | cfcd208495d565ef66e7dff9f98764da\n  3 | eccbc87e4b5ce2fe28308fd9f2a7baf3\n  2 | c81e728d9d4c2f636f067f89cc14862c\n  5 | e4da3b7fbbce2345d7772b0674a318d5\n  4 | a87ff679a2f3e71d9181a67b7542122c\n  7 | 8f14e45fceea167a5a36dedd4bea2543\n  6 | 1679091c5a880faf6fb5e6087eb1b2dc\n  9 | 45c48cce2e2d7fbdea1afc51c7c6ad26\n  8 | c9f0f895fb98ab9159f51fd0297e236d\n 11 | 6512bd43d9caa6e02c990b0a82652dca\n 10 | d3d9446802a44259755d38e6d163e820\n 13 | c51ce410c124a10e0db5e4b97fc2af39\n 12 | c20ad4d76fe97759aa27a0c99bff6710\n 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3\n 14 | aab3238922bcc25a6f606eb525ffdc56\n 17 | 70efdf2ec9b086079795c442636b55fb\n 16 | c74d97b01eae257e44aa9d5bade97baf\n 19 | 1f0e3dad99908345f7439f8ffabdffc4\n 18 | 6f4922f45568161a8cdf4ad2299f6d23\n 20 | 98f13708210194c475687be6106a3b84\n 20 | Success\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on t1 t1_1\n   ->  Seq Scan on _hyper_15_69_chunk t1_2\n   ->  Seq Scan on _hyper_15_70_chunk t1_3\n   ->  Seq Scan on _hyper_15_71_chunk t1_4\n   ->  Seq Scan on _hyper_15_72_chunk t1_5\n   ->  Seq Scan on _hyper_15_73_chunk t1_6\n   ->  Seq Scan on _hyper_15_74_chunk t1_7\n   ->  Seq Scan on _hyper_15_75_chunk t1_8\n   ->  Seq Scan on _hyper_15_76_chunk t1_9\n   ->  Seq Scan on _hyper_15_77_chunk t1_10\n   ->  Seq Scan on _hyper_15_78_chunk t1_11\n   ->  Seq Scan on _hyper_15_79_chunk t1_12\n\n-- Check that default deny applies to non-owner/non-superuser when RLS on.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO ON;\nSELECT * FROM t1;\n a | b \n---+---\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1;\n--- QUERY PLAN ---\n Result\n   One-Time Filter: false\n\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM t1;\n a | b \n---+---\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1;\n--- QUERY PLAN ---\n Result\n   One-Time Filter: false\n\n--\n-- COPY TO/FROM\n--\nRESET SESSION AUTHORIZATION;\nDROP TABLE copy_t CASCADE;\nERROR:  table \"copy_t\" does not exist\nCREATE TABLE copy_t (a integer, b text);\nSELECT public.create_hypertable('copy_t', 'a', chunk_time_interval=>2);\n        create_hypertable         \n----------------------------------\n (19,regress_rls_schema,copy_t,t)\n\nCREATE POLICY p1 ON copy_t USING (a % 2 = 0);\nALTER TABLE copy_t ENABLE ROW LEVEL SECURITY;\nGRANT ALL ON copy_t TO regress_rls_bob, regress_rls_exempt_user;\nINSERT INTO copy_t (SELECT x, md5(x::text) FROM generate_series(0,10) x);\n-- Check COPY TO as Superuser/owner.\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';\n0,cfcd208495d565ef66e7dff9f98764da\n1,c4ca4238a0b923820dcc509a6f75849b\n2,c81e728d9d4c2f636f067f89cc14862c\n3,eccbc87e4b5ce2fe28308fd9f2a7baf3\n4,a87ff679a2f3e71d9181a67b7542122c\n5,e4da3b7fbbce2345d7772b0674a318d5\n6,1679091c5a880faf6fb5e6087eb1b2dc\n7,8f14e45fceea167a5a36dedd4bea2543\n8,c9f0f895fb98ab9159f51fd0297e236d\n9,45c48cce2e2d7fbdea1afc51c7c6ad26\n10,d3d9446802a44259755d38e6d163e820\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';\n0,cfcd208495d565ef66e7dff9f98764da\n1,c4ca4238a0b923820dcc509a6f75849b\n2,c81e728d9d4c2f636f067f89cc14862c\n3,eccbc87e4b5ce2fe28308fd9f2a7baf3\n4,a87ff679a2f3e71d9181a67b7542122c\n5,e4da3b7fbbce2345d7772b0674a318d5\n6,1679091c5a880faf6fb5e6087eb1b2dc\n7,8f14e45fceea167a5a36dedd4bea2543\n8,c9f0f895fb98ab9159f51fd0297e236d\n9,45c48cce2e2d7fbdea1afc51c7c6ad26\n10,d3d9446802a44259755d38e6d163e820\n-- Check COPY TO as user with permissions.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS\nERROR:  query would be affected by row-level security policy for table \"copy_t\"\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok\n0,cfcd208495d565ef66e7dff9f98764da\n2,c81e728d9d4c2f636f067f89cc14862c\n4,a87ff679a2f3e71d9181a67b7542122c\n6,1679091c5a880faf6fb5e6087eb1b2dc\n8,c9f0f895fb98ab9159f51fd0297e236d\n10,d3d9446802a44259755d38e6d163e820\n-- Check COPY TO as user with permissions and BYPASSRLS\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok\n0,cfcd208495d565ef66e7dff9f98764da\n1,c4ca4238a0b923820dcc509a6f75849b\n2,c81e728d9d4c2f636f067f89cc14862c\n3,eccbc87e4b5ce2fe28308fd9f2a7baf3\n4,a87ff679a2f3e71d9181a67b7542122c\n5,e4da3b7fbbce2345d7772b0674a318d5\n6,1679091c5a880faf6fb5e6087eb1b2dc\n7,8f14e45fceea167a5a36dedd4bea2543\n8,c9f0f895fb98ab9159f51fd0297e236d\n9,45c48cce2e2d7fbdea1afc51c7c6ad26\n10,d3d9446802a44259755d38e6d163e820\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok\n0,cfcd208495d565ef66e7dff9f98764da\n1,c4ca4238a0b923820dcc509a6f75849b\n2,c81e728d9d4c2f636f067f89cc14862c\n3,eccbc87e4b5ce2fe28308fd9f2a7baf3\n4,a87ff679a2f3e71d9181a67b7542122c\n5,e4da3b7fbbce2345d7772b0674a318d5\n6,1679091c5a880faf6fb5e6087eb1b2dc\n7,8f14e45fceea167a5a36dedd4bea2543\n8,c9f0f895fb98ab9159f51fd0297e236d\n9,45c48cce2e2d7fbdea1afc51c7c6ad26\n10,d3d9446802a44259755d38e6d163e820\n-- Check COPY TO as user without permissions. SET row_security TO OFF;\nSET SESSION AUTHORIZATION regress_rls_carol;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS\nERROR:  query would be affected by row-level security policy for table \"copy_t\"\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - permission denied\nERROR:  permission denied for table copy_t\n-- Check COPY relation TO; keep it just one row to avoid reordering issues\nRESET SESSION AUTHORIZATION;\nSET row_security TO ON;\nCREATE TABLE copy_rel_to (a integer, b text);\nSELECT public.create_hypertable('copy_rel_to', 'a', chunk_time_interval=>2);\n           create_hypertable           \n---------------------------------------\n (20,regress_rls_schema,copy_rel_to,t)\n\nCREATE POLICY p1 ON copy_rel_to USING (a % 2 = 0);\nALTER TABLE copy_rel_to ENABLE ROW LEVEL SECURITY;\nGRANT ALL ON copy_rel_to TO regress_rls_bob, regress_rls_exempt_user;\nINSERT INTO copy_rel_to VALUES (1, md5('1'));\n-- Check COPY TO as Superuser/owner.\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ',';\n1,c4ca4238a0b923820dcc509a6f75849b\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ',';\n1,c4ca4238a0b923820dcc509a6f75849b\n-- Check COPY TO as user with permissions.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS\nERROR:  query would be affected by row-level security policy for table \"copy_rel_to\"\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --ok\n-- Check COPY TO as user with permissions and BYPASSRLS\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --ok\n1,c4ca4238a0b923820dcc509a6f75849b\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --ok\n1,c4ca4238a0b923820dcc509a6f75849b\n-- Check COPY TO as user without permissions. SET row_security TO OFF;\nSET SESSION AUTHORIZATION regress_rls_carol;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --fail - permission denied\nERROR:  query would be affected by row-level security policy for table \"copy_rel_to\"\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --fail - permission denied\nERROR:  permission denied for table copy_rel_to\n-- Check COPY FROM as Superuser/owner.\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nCOPY copy_t FROM STDIN; --ok\nSET row_security TO ON;\nCOPY copy_t FROM STDIN; --ok\n-- Check COPY FROM as user with permissions.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO OFF;\nCOPY copy_t FROM STDIN; --fail - would be affected by RLS.\nERROR:  query would be affected by row-level security policy for table \"copy_t\"\nSET row_security TO ON;\nCOPY copy_t FROM STDIN; --fail - COPY FROM not supported by RLS.\nERROR:  COPY FROM not supported with row-level security\nHINT:  Use INSERT statements instead.\n-- Check COPY FROM as user with permissions and BYPASSRLS\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO ON;\nCOPY copy_t FROM STDIN; --ok\n-- Check COPY FROM as user without permissions.\nSET SESSION AUTHORIZATION regress_rls_carol;\nSET row_security TO OFF;\nCOPY copy_t FROM STDIN; --fail - permission denied.\nERROR:  permission denied for table copy_t\nSET row_security TO ON;\nCOPY copy_t FROM STDIN; --fail - permission denied.\nERROR:  permission denied for table copy_t\nRESET SESSION AUTHORIZATION;\nDROP TABLE copy_t;\nDROP TABLE copy_rel_to CASCADE;\n-- Check WHERE CURRENT OF\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE current_check (currentid int, payload text, rlsuser text);\nSELECT public.create_hypertable('current_check', 'currentid', chunk_time_interval=>10);\n            create_hypertable            \n-----------------------------------------\n (21,regress_rls_schema,current_check,t)\n\nGRANT ALL ON current_check TO PUBLIC;\nINSERT INTO current_check VALUES\n    (1, 'abc', 'regress_rls_bob'),\n    (2, 'bcd', 'regress_rls_bob'),\n    (3, 'cde', 'regress_rls_bob'),\n    (4, 'def', 'regress_rls_bob');\nCREATE POLICY p1 ON current_check FOR SELECT USING (currentid % 2 = 0);\nCREATE POLICY p2 ON current_check FOR DELETE USING (currentid = 4 AND rlsuser = current_user);\nCREATE POLICY p3 ON current_check FOR UPDATE USING (currentid = 4) WITH CHECK (rlsuser = current_user);\nALTER TABLE current_check ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Can SELECT even rows\nSELECT * FROM current_check;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         2 | bcd     | regress_rls_bob\n         4 | def     | regress_rls_bob\n\n-- Cannot UPDATE row 2\nUPDATE current_check SET payload = payload || '_new' WHERE currentid = 2 RETURNING *;\n currentid | payload | rlsuser \n-----------+---------+---------\n\nBEGIN;\n-- WHERE CURRENT OF does not work with custom scan nodes\n-- so we have to disable chunk append here\nSET timescaledb.enable_chunk_append TO false;\nDECLARE current_check_cursor SCROLL CURSOR FOR SELECT * FROM current_check;\n-- Returns rows that can be seen according to SELECT policy, like plain SELECT\n-- above (even rows)\nFETCH ABSOLUTE 1 FROM current_check_cursor;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         2 | bcd     | regress_rls_bob\n\n-- Still cannot UPDATE row 2 through cursor\nUPDATE current_check SET payload = payload || '_new' WHERE CURRENT OF current_check_cursor RETURNING *;\n currentid | payload | rlsuser \n-----------+---------+---------\n\n-- Can update row 4 through cursor, which is the next visible row\nFETCH RELATIVE 1 FROM current_check_cursor;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         4 | def     | regress_rls_bob\n\nUPDATE current_check SET payload = payload || '_new' WHERE CURRENT OF current_check_cursor RETURNING *;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         4 | def_new | regress_rls_bob\n\nSELECT * FROM current_check;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         2 | bcd     | regress_rls_bob\n         4 | def_new | regress_rls_bob\n\n-- Plan should be a subquery TID scan\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE current_check SET payload = payload WHERE CURRENT OF current_check_cursor;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Update on current_check\n         Update on _hyper_21_104_chunk current_check_1\n         ->  Tid Scan on _hyper_21_104_chunk current_check_1\n               TID Cond: CURRENT OF current_check_cursor\n               Filter: ((currentid = 4) AND ((currentid % 2) = 0))\n\n-- Similarly can only delete row 4\nFETCH ABSOLUTE 1 FROM current_check_cursor;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         2 | bcd     | regress_rls_bob\n\nDELETE FROM current_check WHERE CURRENT OF current_check_cursor RETURNING *;\n currentid | payload | rlsuser \n-----------+---------+---------\n\nFETCH RELATIVE 1 FROM current_check_cursor;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         4 | def     | regress_rls_bob\n\nDELETE FROM current_check WHERE CURRENT OF current_check_cursor RETURNING *;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         4 | def_new | regress_rls_bob\n\nSELECT * FROM current_check;\n currentid | payload |     rlsuser     \n-----------+---------+-----------------\n         2 | bcd     | regress_rls_bob\n\nRESET timescaledb.enable_chunk_append;\nCOMMIT;\n--\n-- check pg_stats view filtering\n--\nSET row_security TO ON;\nSET SESSION AUTHORIZATION regress_rls_alice;\nANALYZE current_check;\n-- Stats visible\nSELECT row_security_active('current_check');\n row_security_active \n---------------------\n f\n\nSELECT attname, most_common_vals FROM pg_stats\n  WHERE tablename = 'current_check'\n  ORDER BY 1;\n  attname  | most_common_vals  \n-----------+-------------------\n currentid | \n payload   | \n rlsuser   | {regress_rls_bob}\n\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Stats not visible\nSELECT row_security_active('current_check');\n row_security_active \n---------------------\n t\n\nSELECT attname, most_common_vals FROM pg_stats\n  WHERE tablename = 'current_check'\n  ORDER BY 1;\n attname | most_common_vals \n---------+------------------\n\n--\n-- Collation support\n--\nBEGIN;\nCREATE TABLE coll_t (c) AS VALUES ('bar'::text);\nCREATE POLICY coll_p ON coll_t USING (c < ('foo'::text COLLATE \"C\"));\nALTER TABLE coll_t ENABLE ROW LEVEL SECURITY;\nGRANT SELECT ON coll_t TO regress_rls_alice;\nSELECT (string_to_array(polqual, ':'))[7] AS inputcollid FROM pg_policy WHERE polrelid = 'coll_t'::regclass;\n   inputcollid    \n------------------\n inputcollid 950 \n\nSET SESSION AUTHORIZATION regress_rls_alice;\nSELECT * FROM coll_t;\n  c  \n-----\n bar\n\nROLLBACK;\n--\n-- Shared Object Dependencies\n--\nRESET SESSION AUTHORIZATION;\nBEGIN;\nCREATE ROLE regress_rls_eve;\nCREATE ROLE regress_rls_frank;\nCREATE TABLE tbl1 (c) AS VALUES ('bar'::text);\nGRANT SELECT ON TABLE tbl1 TO regress_rls_eve;\nCREATE POLICY P ON tbl1 TO regress_rls_eve, regress_rls_frank USING (true);\nSELECT refclassid::regclass, deptype\n  FROM pg_depend\n  WHERE classid = 'pg_policy'::regclass\n  AND refobjid = 'tbl1'::regclass;\n refclassid | deptype \n------------+---------\n pg_class   | a\n\nSELECT refclassid::regclass, deptype\n  FROM pg_shdepend\n  WHERE classid = 'pg_policy'::regclass\n  AND refobjid IN ('regress_rls_eve'::regrole, 'regress_rls_frank'::regrole);\n refclassid | deptype \n------------+---------\n pg_authid  | r\n pg_authid  | r\n\nSAVEPOINT q;\nDROP ROLE regress_rls_eve; --fails due to dependency on POLICY p\nERROR:  role \"regress_rls_eve\" cannot be dropped because some objects depend on it\nDETAIL:  privileges for table tbl1\ntarget of policy p on table tbl1\nROLLBACK TO q;\nALTER POLICY p ON tbl1 TO regress_rls_frank USING (true);\nSAVEPOINT q;\nDROP ROLE regress_rls_eve; --fails due to dependency on GRANT SELECT\nERROR:  role \"regress_rls_eve\" cannot be dropped because some objects depend on it\nDETAIL:  privileges for table tbl1\nROLLBACK TO q;\nREVOKE ALL ON TABLE tbl1 FROM regress_rls_eve;\nSAVEPOINT q;\nDROP ROLE regress_rls_eve; --succeeds\nROLLBACK TO q;\nSAVEPOINT q;\nDROP ROLE regress_rls_frank; --fails due to dependency on POLICY p\nERROR:  role \"regress_rls_frank\" cannot be dropped because some objects depend on it\nDETAIL:  target of policy p on table tbl1\nROLLBACK TO q;\nDROP POLICY p ON tbl1;\nSAVEPOINT q;\nDROP ROLE regress_rls_frank; -- succeeds\nROLLBACK TO q;\nROLLBACK; -- cleanup\n--\n-- Converting table to view\n--\nBEGIN;\nCREATE TABLE t (c int);\nSELECT public.create_hypertable('t', 'c', chunk_time_interval=>2);\n      create_hypertable      \n-----------------------------\n (22,regress_rls_schema,t,t)\n\nCREATE POLICY p ON t USING (c % 2 = 1);\nALTER TABLE t ENABLE ROW LEVEL SECURITY;\nSAVEPOINT q;\nCREATE RULE \"_RETURN\" AS ON SELECT TO t DO INSTEAD\n  SELECT * FROM generate_series(1,5) t0(c); -- fails due to row level security enabled\nERROR:  hypertables do not support rules\nROLLBACK TO q;\nALTER TABLE t DISABLE ROW LEVEL SECURITY;\nSAVEPOINT q;\nCREATE RULE \"_RETURN\" AS ON SELECT TO t DO INSTEAD\n  SELECT * FROM generate_series(1,5) t0(c); -- fails due to policy p on t\nERROR:  hypertables do not support rules\nROLLBACK TO q;\nDROP POLICY p ON t;\nCREATE RULE \"_RETURN\" AS ON SELECT TO t DO INSTEAD\n  SELECT * FROM generate_series(1,5) t0(c); -- succeeds\nERROR:  hypertables do not support rules\nROLLBACK;\n--\n-- Policy expression handling\n--\nBEGIN;\nCREATE TABLE t (c) AS VALUES ('bar'::text);\nCREATE POLICY p ON t USING (max(c)); -- fails: aggregate functions are not allowed in policy expressions\nERROR:  aggregate functions are not allowed in policy expressions\nROLLBACK;\n--\n-- Non-target relations are only subject to SELECT policies\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE r1 (a int);\nSELECT public.create_hypertable('r1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (23,regress_rls_schema,r1,t)\n\nCREATE TABLE r2 (a int);\nSELECT public.create_hypertable('r2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (24,regress_rls_schema,r2,t)\n\nINSERT INTO r1 VALUES (10), (20);\nINSERT INTO r2 VALUES (10), (20);\nGRANT ALL ON r1, r2 TO regress_rls_bob;\nCREATE POLICY p1 ON r1 USING (true);\nALTER TABLE r1 ENABLE ROW LEVEL SECURITY;\nCREATE POLICY p1 ON r2 FOR SELECT USING (true);\nCREATE POLICY p2 ON r2 FOR INSERT WITH CHECK (false);\nCREATE POLICY p3 ON r2 FOR UPDATE USING (false);\nCREATE POLICY p4 ON r2 FOR DELETE USING (false);\nALTER TABLE r2 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM r1;\n a  \n----\n 10\n 20\n\nSELECT * FROM r2;\n a  \n----\n 10\n 20\n\n-- r2 is read-only\nINSERT INTO r2 VALUES (2); -- Not allowed\nERROR:  new row violates row-level security policy for table \"r2\"\n\\pset tuples_only 1\nUPDATE r2 SET a = 2 RETURNING *; -- Updates nothing\n\nDELETE FROM r2 RETURNING *; -- Deletes nothing\n\n\\pset tuples_only 0\n-- r2 can be used as a non-target relation in DML\nINSERT INTO r1 SELECT a + 1 FROM r2 RETURNING *; -- OK\n a  \n----\n 11\n 21\n\nUPDATE r1 SET a = r2.a + 2 FROM r2 WHERE r1.a = r2.a RETURNING *; -- OK\nERROR:  new row for relation \"_hyper_23_105_chunk\" violates check constraint \"constraint_105\"\nDELETE FROM r1 USING r2 WHERE r1.a = r2.a + 2 RETURNING *; -- OK\n a | a \n---+---\n\nSELECT * FROM r1;\n a  \n----\n 10\n 11\n 20\n 21\n\nSELECT * FROM r2;\n a  \n----\n 10\n 20\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP TABLE r1;\nDROP TABLE r2;\n--\n-- FORCE ROW LEVEL SECURITY applies RLS to owners too\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security = on;\nCREATE TABLE r1 (a int);\nSELECT public.create_hypertable('r1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (25,regress_rls_schema,r1,t)\n\nINSERT INTO r1 VALUES (10), (20);\nCREATE POLICY p1 ON r1 USING (false);\nALTER TABLE r1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r1 FORCE ROW LEVEL SECURITY;\n-- No error, but no rows\nTABLE r1;\n a \n---\n\n-- RLS error\nINSERT INTO r1 VALUES (1);\nERROR:  new row violates row-level security policy for table \"r1\"\n-- No error (unable to see any rows to update)\nUPDATE r1 SET a = 1;\nTABLE r1;\n a \n---\n\n-- No error (unable to see any rows to delete)\nDELETE FROM r1;\nTABLE r1;\n a \n---\n\nSET row_security = off;\n-- these all fail, would be affected by RLS\nTABLE r1;\nERROR:  query would be affected by row-level security policy for table \"r1\"\nHINT:  To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.\nUPDATE r1 SET a = 1;\nERROR:  query would be affected by row-level security policy for table \"r1\"\nHINT:  To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.\nDELETE FROM r1;\nERROR:  query would be affected by row-level security policy for table \"r1\"\nHINT:  To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.\nDROP TABLE r1;\n--\n-- FORCE ROW LEVEL SECURITY does not break RI\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security = on;\nCREATE TABLE r1 (a int PRIMARY KEY);\n-- r1 is not a hypertable since r1.a is referenced by r2\nCREATE TABLE r2 (a int REFERENCES r1);\nSELECT public.create_hypertable('r2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (26,regress_rls_schema,r2,t)\n\nINSERT INTO r1 VALUES (10), (20);\nINSERT INTO r2 VALUES (10), (20);\n-- Create policies on r2 which prevent the\n-- owner from seeing any rows, but RI should\n-- still see them.\nCREATE POLICY p1 ON r2 USING (false);\nALTER TABLE r2 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r2 FORCE ROW LEVEL SECURITY;\n-- Errors due to rows in r2\nDELETE FROM r1;\nERROR:  update or delete on table \"r1\" violates foreign key constraint \"r2_a_fkey\" on table \"r2\"\nDETAIL:  Key (a)=(10) is still referenced from table \"r2\".\n-- Reset r2 to no-RLS\nDROP POLICY p1 ON r2;\nALTER TABLE r2 NO FORCE ROW LEVEL SECURITY;\nALTER TABLE r2 DISABLE ROW LEVEL SECURITY;\n-- clean out r2 for INSERT test below\nDELETE FROM r2;\n-- Change r1 to not allow rows to be seen\nCREATE POLICY p1 ON r1 USING (false);\nALTER TABLE r1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r1 FORCE ROW LEVEL SECURITY;\n-- No rows seen\nTABLE r1;\n a \n---\n\n-- No error, RI still sees that row exists in r1\nINSERT INTO r2 VALUES (10);\nDROP TABLE r2;\nDROP TABLE r1;\n-- Ensure cascaded DELETE works\nCREATE TABLE r1 (a int PRIMARY KEY);\n-- r1 is not a hypertable since r1.a is referenced by r2\nCREATE TABLE r2 (a int REFERENCES r1 ON DELETE CASCADE);\nSELECT public.create_hypertable('r2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (27,regress_rls_schema,r2,t)\n\nINSERT INTO r1 VALUES (10), (20);\nINSERT INTO r2 VALUES (10), (20);\n-- Create policies on r2 which prevent the\n-- owner from seeing any rows, but RI should\n-- still see them.\nCREATE POLICY p1 ON r2 USING (false);\nALTER TABLE r2 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r2 FORCE ROW LEVEL SECURITY;\n-- Deletes all records from both\nDELETE FROM r1;\n-- Remove FORCE from r2\nALTER TABLE r2 NO FORCE ROW LEVEL SECURITY;\n-- As owner, we now bypass RLS\n-- verify no rows in r2 now\nTABLE r2;\n a \n---\n\nDROP TABLE r2;\nDROP TABLE r1;\n-- Ensure cascaded UPDATE works\nCREATE TABLE r1 (a int PRIMARY KEY);\n-- r1 is not a hypertable since r1.a is referenced by r2\nCREATE TABLE r2 (a int REFERENCES r1 ON UPDATE CASCADE);\nSELECT public.create_hypertable('r2', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (28,regress_rls_schema,r2,t)\n\nINSERT INTO r1 VALUES (10), (20);\nINSERT INTO r2 VALUES (10), (20);\n-- Create policies on r2 which prevent the\n-- owner from seeing any rows, but RI should\n-- still see them.\nCREATE POLICY p1 ON r2 USING (false);\nALTER TABLE r2 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r2 FORCE ROW LEVEL SECURITY;\n-- Updates records in both (terse output to not print CONTEXT, which can be different).\n\\set VERBOSITY terse\nUPDATE r1 SET a = a+5;\nERROR:  new row for relation \"_hyper_28_117_chunk\" violates check constraint \"constraint_117\"\n\\set VERBOSITY default\n-- Remove FORCE from r2\nALTER TABLE r2 NO FORCE ROW LEVEL SECURITY;\n-- As owner, we now bypass RLS\n-- verify records in r2 updated\nTABLE r2;\n a  \n----\n 10\n 20\n\nDROP TABLE r2;\nDROP TABLE r1;\n--\n-- Test INSERT+RETURNING applies SELECT policies as\n-- WithCheckOptions (meaning an error is thrown)\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security = on;\nCREATE TABLE r1 (a int);\nSELECT public.create_hypertable('r1', 'a', chunk_time_interval=>2);\n      create_hypertable       \n------------------------------\n (29,regress_rls_schema,r1,t)\n\nCREATE POLICY p1 ON r1 FOR SELECT USING (false);\nCREATE POLICY p2 ON r1 FOR INSERT WITH CHECK (true);\nALTER TABLE r1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r1 FORCE ROW LEVEL SECURITY;\n-- Works fine\nINSERT INTO r1 VALUES (10), (20);\n-- No error, but no rows\nTABLE r1;\n a \n---\n\nSET row_security = off;\n-- fail, would be affected by RLS\nTABLE r1;\nERROR:  query would be affected by row-level security policy for table \"r1\"\nHINT:  To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.\nSET row_security = on;\n-- Error\nINSERT INTO r1 VALUES (10), (20) RETURNING *;\nERROR:  new row violates row-level security policy for table \"r1\"\nDROP TABLE r1;\n--\n-- Test UPDATE+RETURNING applies SELECT policies as\n-- WithCheckOptions (meaning an error is thrown)\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security = on;\nCREATE TABLE r1 (a int PRIMARY KEY);\nSELECT public.create_hypertable('r1', 'a', chunk_time_interval=>100);\n      create_hypertable       \n------------------------------\n (30,regress_rls_schema,r1,t)\n\nCREATE POLICY p1 ON r1 FOR SELECT USING (a < 20);\nCREATE POLICY p2 ON r1 FOR UPDATE USING (a < 20) WITH CHECK (true);\nCREATE POLICY p3 ON r1 FOR INSERT WITH CHECK (true);\nINSERT INTO r1 VALUES (10);\nALTER TABLE r1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r1 FORCE ROW LEVEL SECURITY;\n-- Works fine\nUPDATE r1 SET a = 30;\n-- Show updated rows\nALTER TABLE r1 NO FORCE ROW LEVEL SECURITY;\nTABLE r1;\n a  \n----\n 30\n\n-- reset value in r1 for test with RETURNING\nUPDATE r1 SET a = 10;\n-- Verify row reset\nTABLE r1;\n a  \n----\n 10\n\nALTER TABLE r1 FORCE ROW LEVEL SECURITY;\n-- Error\nUPDATE r1 SET a = 30 RETURNING *;\nERROR:  new row violates row-level security policy for table \"r1\"\n-- UPDATE path of INSERT ... ON CONFLICT DO UPDATE should also error out\nINSERT INTO r1 VALUES (10)\n    ON CONFLICT (a) DO UPDATE SET a = 30 RETURNING *;\nERROR:  new row violates row-level security policy for table \"r1\"\n-- Should still error out without RETURNING (use of arbiter always requires\n-- SELECT permissions)\nINSERT INTO r1 VALUES (10)\n    ON CONFLICT (a) DO UPDATE SET a = 30;\nERROR:  new row violates row-level security policy for table \"r1\"\n-- ON CONFLICT ON CONSTRAINT\nINSERT INTO r1 VALUES (10)\n    ON CONFLICT ON CONSTRAINT r1_pkey DO UPDATE SET a = 30;\nERROR:  new row violates row-level security policy for table \"r1\"\nDROP TABLE r1;\n-- Check dependency handling\nRESET SESSION AUTHORIZATION;\nCREATE TABLE dep1 (c1 int);\nSELECT public.create_hypertable('dep1', 'c1', chunk_time_interval=>2);\n       create_hypertable        \n--------------------------------\n (31,regress_rls_schema,dep1,t)\n\nCREATE TABLE dep2 (c1 int);\nSELECT public.create_hypertable('dep2', 'c1', chunk_time_interval=>2);\n       create_hypertable        \n--------------------------------\n (32,regress_rls_schema,dep2,t)\n\nCREATE POLICY dep_p1 ON dep1 TO regress_rls_bob USING (c1 > (select max(dep2.c1) from dep2));\nALTER POLICY dep_p1 ON dep1 TO regress_rls_bob,regress_rls_carol;\n-- Should return one\nSELECT count(*) = 1 FROM pg_depend\n\t\t\t\t   WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1')\n\t\t\t\t\t AND refobjid = (SELECT oid FROM pg_class WHERE relname = 'dep2');\n ?column? \n----------\n t\n\nALTER POLICY dep_p1 ON dep1 USING (true);\n-- Should return one\nSELECT count(*) = 1 FROM pg_shdepend\n\t\t\t\t   WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1')\n\t\t\t\t\t AND refobjid = (SELECT oid FROM pg_authid WHERE rolname = 'regress_rls_bob');\n ?column? \n----------\n t\n\n-- Should return one\nSELECT count(*) = 1 FROM pg_shdepend\n\t\t\t\t   WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1')\n\t\t\t\t\t AND refobjid = (SELECT oid FROM pg_authid WHERE rolname = 'regress_rls_carol');\n ?column? \n----------\n t\n\n-- Should return zero\nSELECT count(*) = 0 FROM pg_depend\n\t\t\t\t   WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1')\n\t\t\t\t\t AND refobjid = (SELECT oid FROM pg_class WHERE relname = 'dep2');\n ?column? \n----------\n t\n\n-- DROP OWNED BY testing\nRESET SESSION AUTHORIZATION;\nCREATE ROLE regress_rls_dob_role1;\nCREATE ROLE regress_rls_dob_role2;\nCREATE TABLE dob_t1 (c1 int);\nSELECT public.create_hypertable('dob_t1', 'c1', chunk_time_interval=>2);\n        create_hypertable         \n----------------------------------\n (33,regress_rls_schema,dob_t1,t)\n\nCREATE TABLE dob_t2 (c1 int) PARTITION BY RANGE (c1);\nCREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1 USING (true);\nDROP OWNED BY regress_rls_dob_role1;\nDROP POLICY p1 ON dob_t1; -- should fail, already gone\nERROR:  policy \"p1\" for table \"dob_t1\" does not exist\nCREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1,regress_rls_dob_role2 USING (true);\nDROP OWNED BY regress_rls_dob_role1;\nDROP POLICY p1 ON dob_t1; -- should succeed\nCREATE POLICY p1 ON dob_t2 TO regress_rls_dob_role1,regress_rls_dob_role2 USING (true);\nDROP OWNED BY regress_rls_dob_role1;\nDROP POLICY p1 ON dob_t2; -- should succeed\nDROP USER regress_rls_dob_role1;\nDROP USER regress_rls_dob_role2;\n--\n-- Clean up objects\n--\nRESET SESSION AUTHORIZATION;\n\\set VERBOSITY terse \\\\ -- suppress cascade details\nDROP SCHEMA regress_rls_schema CASCADE;\nNOTICE:  drop cascades to 116 other objects\n\\set VERBOSITY default\nDROP USER regress_rls_alice;\nDROP USER regress_rls_bob;\nDROP USER regress_rls_carol;\nDROP USER regress_rls_dave;\nDROP USER regress_rls_exempt_user;\nDROP ROLE regress_rls_group1;\nDROP ROLE regress_rls_group2;\n-- Arrange to have a few policies left over, for testing\n-- pg_dump/pg_restore\nCREATE SCHEMA regress_rls_schema;\nCREATE TABLE rls_tbl (c1 int);\nSELECT public.create_hypertable('rls_tbl', 'c1', chunk_time_interval=>2);\n         create_hypertable         \n-----------------------------------\n (34,regress_rls_schema,rls_tbl,t)\n\nALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;\nCREATE POLICY p1 ON rls_tbl USING (c1 > 5);\nCREATE POLICY p2 ON rls_tbl FOR SELECT USING (c1 <= 3);\nCREATE POLICY p3 ON rls_tbl FOR UPDATE USING (c1 <= 3) WITH CHECK (c1 > 5);\nCREATE POLICY p4 ON rls_tbl FOR DELETE USING (c1 <= 3);\nCREATE TABLE rls_tbl_force (c1 int);\nSELECT public.create_hypertable('rls_tbl_force', 'c1', chunk_time_interval=>2);\n            create_hypertable            \n-----------------------------------------\n (35,regress_rls_schema,rls_tbl_force,t)\n\nALTER TABLE rls_tbl_force ENABLE ROW LEVEL SECURITY;\nALTER TABLE rls_tbl_force FORCE ROW LEVEL SECURITY;\nCREATE POLICY p1 ON rls_tbl_force USING (c1 = 5) WITH CHECK (c1 < 5);\nCREATE POLICY p2 ON rls_tbl_force FOR SELECT USING (c1 = 8);\nCREATE POLICY p3 ON rls_tbl_force FOR UPDATE USING (c1 = 8) WITH CHECK (c1 >= 5);\nCREATE POLICY p4 ON rls_tbl_force FOR DELETE USING (c1 = 8);\n"
  },
  {
    "path": "test/expected/size_utils.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\ir include/insert_two_partitions.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE PUBLIC.\"two_Partitions\" (\n  \"timeCustom\" BIGINT NOT NULL,\n  device_id TEXT NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL,\n  series_bool BOOLEAN NULL\n);\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (device_id, \"timeCustom\" DESC NULLS LAST) WHERE device_id IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_0) WHERE series_0 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_1)  WHERE series_1 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_2) WHERE series_2 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_bool) WHERE series_bool IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, device_id);\nSELECT * FROM create_hypertable('\"public\".\"two_Partitions\"'::regclass, 'timeCustom'::name, 'device_id'::name, associated_schema_name=>'_timescaledb_internal'::text, number_partitions => 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name |   table_name   | created \n---------------+-------------+----------------+---------\n             1 | public      | two_Partitions | t\n\n\\set QUIET off\nBEGIN;\nBEGIN\n\\COPY public.\"two_Partitions\" FROM 'data/ds1_dev1_1.tsv' NULL AS '';\nCOPY 7\nCOMMIT;\nCOMMIT\nINSERT INTO public.\"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257987600000000000, 'dev1', 1.5, 1),\n(1257987600000000000, 'dev1', 1.5, 2),\n(1257894000000000000, 'dev2', 1.5, 1),\n(1257894002000000000, 'dev1', 2.5, 3);\nINSERT 0 4\nINSERT INTO \"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257894000000000000, 'dev2', 1.5, 2);\nINSERT 0 1\n\\set QUIET on\nSELECT * FROM hypertable_detailed_size('\"public\".\"two_Partitions\"');\n table_bytes | index_bytes | toast_bytes | total_bytes | node_name \n-------------+-------------+-------------+-------------+-----------\n       32768 |      475136 |       40960 |      548864 | \n\nSELECT * FROM hypertable_index_size('\"public\".\"two_Partitions_device_id_timeCustom_idx\"');\n hypertable_index_size \n-----------------------\n                 73728\n\nSELECT * FROM hypertable_index_size('\"public\".\"two_Partitions_timeCustom_device_id_idx\"');\n hypertable_index_size \n-----------------------\n                 73728\n\nSELECT * FROM hypertable_index_size('\"public\".\"two_Partitions_timeCustom_idx\"');\n hypertable_index_size \n-----------------------\n                 73728\n\nSELECT * FROM hypertable_index_size('\"public\".\"two_Partitions_timeCustom_series_0_idx\"');\n hypertable_index_size \n-----------------------\n                 73728\n\nSELECT * FROM hypertable_index_size('\"public\".\"two_Partitions_timeCustom_series_1_idx\"');\n hypertable_index_size \n-----------------------\n                 73728\n\nSELECT * FROM hypertable_index_size('\"public\".\"two_Partitions_timeCustom_series_2_idx\"');\n hypertable_index_size \n-----------------------\n                 49152\n\nSELECT * FROM hypertable_index_size('\"public\".\"two_Partitions_timeCustom_series_bool_idx\"');\n hypertable_index_size \n-----------------------\n                 57344\n\nSELECT * FROM chunks_detailed_size('\"public\".\"two_Partitions\"') order by chunk_name;\n     chunk_schema      |    chunk_name    | table_bytes | index_bytes | toast_bytes | total_bytes | node_name \n-----------------------+------------------+-------------+-------------+-------------+-------------+-----------\n _timescaledb_internal | _hyper_1_1_chunk |        8192 |      114688 |        8192 |      131072 | \n _timescaledb_internal | _hyper_1_2_chunk |        8192 |      106496 |        8192 |      122880 | \n _timescaledb_internal | _hyper_1_3_chunk |        8192 |       98304 |        8192 |      114688 | \n _timescaledb_internal | _hyper_1_4_chunk |        8192 |       98304 |        8192 |      114688 | \n\nCREATE TABLE timestamp_partitioned(time TIMESTAMP, value TEXT);\nSELECT * FROM create_hypertable('timestamp_partitioned', 'time', 'value', 2);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n hypertable_id | schema_name |      table_name       | created \n---------------+-------------+-----------------------+---------\n             2 | public      | timestamp_partitioned | t\n\nINSERT INTO timestamp_partitioned VALUES('2004-10-19 10:23:54', '10');\nINSERT INTO timestamp_partitioned VALUES('2004-12-19 10:23:54', '30');\nSELECT * FROM chunks_detailed_size('timestamp_partitioned') order by chunk_name;\n     chunk_schema      |    chunk_name    | table_bytes | index_bytes | toast_bytes | total_bytes | node_name \n-----------------------+------------------+-------------+-------------+-------------+-------------+-----------\n _timescaledb_internal | _hyper_2_5_chunk |        8192 |       32768 |        8192 |       49152 | \n _timescaledb_internal | _hyper_2_6_chunk |        8192 |       32768 |        8192 |       49152 | \n\nCREATE TABLE timestamp_partitioned_2(time TIMESTAMP, value CHAR(9));\nSELECT * FROM create_hypertable('timestamp_partitioned_2', 'time', 'value', 2);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n hypertable_id | schema_name |       table_name        | created \n---------------+-------------+-------------------------+---------\n             3 | public      | timestamp_partitioned_2 | t\n\nINSERT INTO timestamp_partitioned_2 VALUES('2004-10-19 10:23:54', '10');\nINSERT INTO timestamp_partitioned_2 VALUES('2004-12-19 10:23:54', '30');\nSELECT * FROM chunks_detailed_size('timestamp_partitioned_2') order by chunk_name;\n     chunk_schema      |    chunk_name    | table_bytes | index_bytes | toast_bytes | total_bytes | node_name \n-----------------------+------------------+-------------+-------------+-------------+-------------+-----------\n _timescaledb_internal | _hyper_3_7_chunk |        8192 |       32768 |           0 |       40960 | \n _timescaledb_internal | _hyper_3_8_chunk |        8192 |       32768 |           0 |       40960 | \n\nCREATE TABLE toast_test(time TIMESTAMP, value TEXT);\n-- Set storage type to EXTERNAL to prevent PostgreSQL from compressing my\n-- easily compressable string and instead store it with TOAST\nALTER TABLE toast_test ALTER COLUMN value SET STORAGE EXTERNAL;\nSELECT * FROM create_hypertable('toast_test', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             4 | public      | toast_test | t\n\nINSERT INTO toast_test VALUES('2004-10-19 10:23:54', $$\nthis must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k.\n$$);\nSELECT * FROM chunks_detailed_size('toast_test');\n     chunk_schema      |    chunk_name    | table_bytes | index_bytes | toast_bytes | total_bytes | node_name \n-----------------------+------------------+-------------+-------------+-------------+-------------+-----------\n _timescaledb_internal | _hyper_4_9_chunk |        8192 |       16384 |       24576 |       49152 | \n\n--\n-- Tests for approximate_row_count()\n--\n-- Regular table\n--\nCREATE TABLE approx_count(time TIMESTAMP, value int);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:01', 1);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:02', 2);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:03', 3);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:04', 4);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:05', 5);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:06', 6);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:07', 7);\nSELECT * FROM approximate_row_count('approx_count');\n approximate_row_count \n-----------------------\n                     0\n\nANALYZE approx_count;\nSELECT count(*) FROM approx_count;\n count \n-------\n     7\n\nSELECT * FROM approximate_row_count('approx_count');\n approximate_row_count \n-----------------------\n                     7\n\nDROP TABLE approx_count;\n-- Regular table with basic inheritance\n--\nCREATE TABLE approx_count(id int);\nINSERT INTO approx_count VALUES(1);\nSELECT count(*) FROM approx_count;\n count \n-------\n     1\n\nSELECT * FROM approximate_row_count('approx_count');\n approximate_row_count \n-----------------------\n                     0\n\nANALYZE approx_count;\nSELECT * FROM approximate_row_count('approx_count');\n approximate_row_count \n-----------------------\n                     1\n\nCREATE TABLE approx_count_child(id2 int) INHERITS (approx_count);\nINSERT INTO approx_count_child VALUES(0);\nSELECT count(*) FROM approx_count;\n count \n-------\n     2\n\nSELECT * FROM approximate_row_count('approx_count');\n approximate_row_count \n-----------------------\n                     1\n\nANALYZE approx_count_child;\nSELECT * FROM approximate_row_count('approx_count');\n approximate_row_count \n-----------------------\n                     2\n\nDROP TABLE approx_count CASCADE;\nNOTICE:  drop cascades to table approx_count_child\n-- Regular table with nested inheritance\n--\nCREATE TABLE approx_count(id int);\nCREATE TABLE approx_count_a(id2 int) INHERITS (approx_count);\nCREATE TABLE approx_count_b(id3 int) INHERITS (approx_count_a);\nCREATE TABLE approx_count_c(id4 int) INHERITS (approx_count_b);\nINSERT INTO approx_count_a VALUES(0);\nINSERT INTO approx_count_b VALUES(1);\nINSERT INTO approx_count_c VALUES(2);\nINSERT INTO approx_count VALUES(3);\nSELECT * FROM approximate_row_count('approx_count');\n approximate_row_count \n-----------------------\n                     0\n\nANALYZE approx_count_a;\nANALYZE approx_count_b;\nANALYZE approx_count_c;\nANALYZE approx_count;\nSELECT count(*) FROM approx_count;\n count \n-------\n     4\n\nSELECT * FROM approximate_row_count('approx_count');\n approximate_row_count \n-----------------------\n                     4\n\nSELECT count(*) FROM approx_count_a;\n count \n-------\n     3\n\nSELECT * FROM approximate_row_count('approx_count_a');\n approximate_row_count \n-----------------------\n                     3\n\nSELECT count(*) FROM approx_count_b;\n count \n-------\n     2\n\nSELECT * FROM approximate_row_count('approx_count_b');\n approximate_row_count \n-----------------------\n                     2\n\nSELECT count(*) FROM approx_count_c;\n count \n-------\n     1\n\nSELECT * FROM approximate_row_count('approx_count_c');\n approximate_row_count \n-----------------------\n                     1\n\nDROP TABLE approx_count CASCADE;\nNOTICE:  drop cascades to 3 other objects\n-- table with declarative partitioning\n--\nCREATE TABLE approx_count_dp(time TIMESTAMP, value int) PARTITION BY RANGE(time);\nCREATE TABLE approx_count_dp0 PARTITION OF approx_count_dp\nFOR VALUES FROM ('2004-01-01 00:00:00') TO ('2005-01-01 00:00:00');\nCREATE TABLE approx_count_dp1 PARTITION OF approx_count_dp\nFOR VALUES FROM ('2005-01-01 00:00:00') TO ('2006-01-01 00:00:00');\nCREATE TABLE approx_count_dp2 PARTITION OF approx_count_dp\nFOR VALUES FROM ('2006-01-01 00:00:00') TO ('2007-01-01 00:00:00');\nINSERT INTO approx_count_dp VALUES('2004-01-01 10:00:00', 1);\nINSERT INTO approx_count_dp VALUES('2004-01-01 11:00:00', 1);\nINSERT INTO approx_count_dp VALUES('2004-01-01 12:00:01', 1);\nINSERT INTO approx_count_dp VALUES('2005-01-01 10:00:00', 1);\nINSERT INTO approx_count_dp VALUES('2005-01-01 11:00:00', 1);\nINSERT INTO approx_count_dp VALUES('2005-01-01 12:00:01', 1);\nINSERT INTO approx_count_dp VALUES('2006-01-01 10:00:00', 1);\nINSERT INTO approx_count_dp VALUES('2006-01-01 11:00:00', 1);\nINSERT INTO approx_count_dp VALUES('2006-01-01 12:00:01', 1);\nSELECT count(*) FROM approx_count_dp;\n count \n-------\n     9\n\nSELECT count(*) FROM approx_count_dp0;\n count \n-------\n     3\n\nSELECT count(*) FROM approx_count_dp1;\n count \n-------\n     3\n\nSELECT count(*) FROM approx_count_dp2;\n count \n-------\n     3\n\nSELECT * FROM approximate_row_count('approx_count_dp');\n approximate_row_count \n-----------------------\n                     0\n\nANALYZE approx_count_dp;\nSELECT * FROM approximate_row_count('approx_count_dp');\n approximate_row_count \n-----------------------\n                     9\n\nSELECT * FROM approximate_row_count('approx_count_dp0');\n approximate_row_count \n-----------------------\n                     3\n\nSELECT * FROM approximate_row_count('approx_count_dp1');\n approximate_row_count \n-----------------------\n                     3\n\nSELECT * FROM approximate_row_count('approx_count_dp2');\n approximate_row_count \n-----------------------\n                     3\n\nCREATE TABLE approx_count_dp_nested(time TIMESTAMP, device_id int, value int) PARTITION BY RANGE(time);\nCREATE TABLE approx_count_dp_nested_0 PARTITION OF approx_count_dp_nested FOR VALUES FROM ('2004-01-01 00:00:00') TO ('2005-01-01 00:00:00') PARTITION BY RANGE (device_id);\nCREATE TABLE approx_count_dp_nested_0_0 PARTITION OF approx_count_dp_nested_0 FOR VALUES FROM (0) TO (10);\nCREATE TABLE approx_count_dp_nested_0_1 PARTITION OF approx_count_dp_nested_0 FOR VALUES FROM (10) TO (20);\nCREATE TABLE approx_count_dp_nested_1 PARTITION OF approx_count_dp_nested FOR VALUES FROM ('2005-01-01 00:00:00') TO ('2006-01-01 00:00:00') PARTITION BY RANGE (device_id);\nCREATE TABLE approx_count_dp_nested_1_0 PARTITION OF approx_count_dp_nested_1 FOR VALUES FROM (0) TO (10);\nCREATE TABLE approx_count_dp_nested_1_1 PARTITION OF approx_count_dp_nested_1 FOR VALUES FROM (10) TO (20);\nINSERT INTO approx_count_dp_nested VALUES('2004-01-01 10:00:00', 1, 1);\nINSERT INTO approx_count_dp_nested VALUES('2004-01-01 10:00:00', 2, 1);\nINSERT INTO approx_count_dp_nested VALUES('2004-01-01 10:00:00', 3, 1);\nINSERT INTO approx_count_dp_nested VALUES('2004-01-01 10:00:00', 11, 1);\nINSERT INTO approx_count_dp_nested VALUES('2004-01-01 10:00:00', 12, 1);\nINSERT INTO approx_count_dp_nested VALUES('2004-01-01 10:00:00', 13, 1);\nINSERT INTO approx_count_dp_nested VALUES('2005-01-01 10:00:00', 1, 1);\nINSERT INTO approx_count_dp_nested VALUES('2005-01-01 10:00:00', 2, 1);\nINSERT INTO approx_count_dp_nested VALUES('2005-01-01 10:00:00', 3, 1);\nINSERT INTO approx_count_dp_nested VALUES('2005-01-01 10:00:00', 11, 1);\nINSERT INTO approx_count_dp_nested VALUES('2005-01-01 10:00:00', 12, 1);\nINSERT INTO approx_count_dp_nested VALUES('2005-01-01 10:00:00', 13, 1);\nSELECT * FROM approximate_row_count('approx_count_dp_nested');\n approximate_row_count \n-----------------------\n                     0\n\nANALYZE approx_count_dp_nested;\nSELECT\n  (SELECT count(*) FROM approx_count_dp_nested) AS dp_nested,\n  (SELECT count(*) FROM approx_count_dp_nested_0) AS dp_nested_0,\n  (SELECT count(*) FROM approx_count_dp_nested_0_0) AS dp_nested_0_0,\n  (SELECT count(*) FROM approx_count_dp_nested_0_1) AS dp_nested_0_1,\n  (SELECT count(*) FROM approx_count_dp_nested_1) AS dp_nested_1,\n  (SELECT count(*) FROM approx_count_dp_nested_1_0) AS dp_nested_1_0,\n  (SELECT count(*) FROM approx_count_dp_nested_1_1) AS dp_nested_1_1\nUNION ALL\nSELECT\n  approximate_row_count('approx_count_dp_nested'),\n  approximate_row_count('approx_count_dp_nested_0'),\n  approximate_row_count('approx_count_dp_nested_0_0'),\n  approximate_row_count('approx_count_dp_nested_0_1'),\n  approximate_row_count('approx_count_dp_nested_1'),\n  approximate_row_count('approx_count_dp_nested_1_0'),\n  approximate_row_count('approx_count_dp_nested_1_1');\n dp_nested | dp_nested_0 | dp_nested_0_0 | dp_nested_0_1 | dp_nested_1 | dp_nested_1_0 | dp_nested_1_1 \n-----------+-------------+---------------+---------------+-------------+---------------+---------------\n        12 |           6 |             3 |             3 |           6 |             3 |             3\n        12 |           6 |             3 |             3 |           6 |             3 |             3\n\n-- Hypertable\n--\nCREATE TABLE approx_count(time TIMESTAMP, value int);\nSELECT * FROM create_hypertable('approx_count', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n hypertable_id | schema_name |  table_name  | created \n---------------+-------------+--------------+---------\n             5 | public      | approx_count | t\n\nINSERT INTO approx_count VALUES('2004-01-01 10:00:01', 1);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:02', 2);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:03', 3);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:04', 4);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:05', 5);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:06', 6);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:07', 7);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:08', 8);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:09', 9);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:10', 10);\nSELECT count(*) FROM approx_count;\n count \n-------\n    10\n\nSELECT * FROM approximate_row_count('approx_count');\n approximate_row_count \n-----------------------\n                     0\n\nANALYZE approx_count;\nSELECT * FROM approximate_row_count('approx_count');\n approximate_row_count \n-----------------------\n                    10\n\n\\set ON_ERROR_STOP 0\nSELECT * FROM approximate_row_count('unexisting');\nERROR:  relation \"unexisting\" does not exist at character 37\nSELECT * FROM approximate_row_count();\nERROR:  function approximate_row_count() does not exist at character 15\nSELECT * FROM approximate_row_count(NULL);\n approximate_row_count \n-----------------------\n                      \n\n\\set ON_ERROR_STOP 1\n-- Test size functions with invalid or non-existing OID\nSELECT * FROM hypertable_size(0);\n hypertable_size \n-----------------\n                \n\nSELECT * FROM hypertable_detailed_size(0) ORDER BY node_name;\n table_bytes | index_bytes | toast_bytes | total_bytes | node_name \n-------------+-------------+-------------+-------------+-----------\n\nSELECT * FROM chunks_detailed_size(0) ORDER BY node_name;\n chunk_schema | chunk_name | table_bytes | index_bytes | toast_bytes | total_bytes | node_name \n--------------+------------+-------------+-------------+-------------+-------------+-----------\n\nSELECT * FROM hypertable_compression_stats(0) ORDER BY node_name;\n total_chunks | number_compressed_chunks | before_compression_table_bytes | before_compression_index_bytes | before_compression_toast_bytes | before_compression_total_bytes | after_compression_table_bytes | after_compression_index_bytes | after_compression_toast_bytes | after_compression_total_bytes | node_name \n--------------+--------------------------+--------------------------------+--------------------------------+--------------------------------+--------------------------------+-------------------------------+-------------------------------+-------------------------------+-------------------------------+-----------\n\nSELECT * FROM chunk_compression_stats(0) ORDER BY node_name;\n chunk_schema | chunk_name | compression_status | before_compression_table_bytes | before_compression_index_bytes | before_compression_toast_bytes | before_compression_total_bytes | after_compression_table_bytes | after_compression_index_bytes | after_compression_toast_bytes | after_compression_total_bytes | node_name \n--------------+------------+--------------------+--------------------------------+--------------------------------+--------------------------------+--------------------------------+-------------------------------+-------------------------------+-------------------------------+-------------------------------+-----------\n\nSELECT * FROM hypertable_index_size(0);\n hypertable_index_size \n-----------------------\n                      \n\nSELECT * FROM _timescaledb_functions.relation_size(0);\n total_size | heap_size | index_size | toast_size \n------------+-----------+------------+------------\n            |           |            |           \n\nSELECT * FROM hypertable_size(1);\n hypertable_size \n-----------------\n                \n\nSELECT * FROM hypertable_detailed_size(1) ORDER BY node_name;\n table_bytes | index_bytes | toast_bytes | total_bytes | node_name \n-------------+-------------+-------------+-------------+-----------\n\nSELECT * FROM chunks_detailed_size(1) ORDER BY node_name;\n chunk_schema | chunk_name | table_bytes | index_bytes | toast_bytes | total_bytes | node_name \n--------------+------------+-------------+-------------+-------------+-------------+-----------\n\nSELECT * FROM hypertable_compression_stats(1) ORDER BY node_name;\n total_chunks | number_compressed_chunks | before_compression_table_bytes | before_compression_index_bytes | before_compression_toast_bytes | before_compression_total_bytes | after_compression_table_bytes | after_compression_index_bytes | after_compression_toast_bytes | after_compression_total_bytes | node_name \n--------------+--------------------------+--------------------------------+--------------------------------+--------------------------------+--------------------------------+-------------------------------+-------------------------------+-------------------------------+-------------------------------+-----------\n\nSELECT * FROM chunk_compression_stats(1) ORDER BY node_name;\n chunk_schema | chunk_name | compression_status | before_compression_table_bytes | before_compression_index_bytes | before_compression_toast_bytes | before_compression_total_bytes | after_compression_table_bytes | after_compression_index_bytes | after_compression_toast_bytes | after_compression_total_bytes | node_name \n--------------+------------+--------------------+--------------------------------+--------------------------------+--------------------------------+--------------------------------+-------------------------------+-------------------------------+-------------------------------+-------------------------------+-----------\n\nSELECT * FROM hypertable_index_size(1);\n hypertable_index_size \n-----------------------\n                      \n\nSELECT * FROM _timescaledb_functions.relation_size(1);\n total_size | heap_size | index_size | toast_size \n------------+-----------+------------+------------\n          0 |         0 |          0 |          0\n\n-- Test size functions with NULL input\nSELECT * FROM hypertable_size(NULL);\n hypertable_size \n-----------------\n                \n\nSELECT * FROM hypertable_detailed_size(NULL) ORDER BY node_name;\n table_bytes | index_bytes | toast_bytes | total_bytes | node_name \n-------------+-------------+-------------+-------------+-----------\n\nSELECT * FROM chunks_detailed_size(NULL) ORDER BY node_name;\n chunk_schema | chunk_name | table_bytes | index_bytes | toast_bytes | total_bytes | node_name \n--------------+------------+-------------+-------------+-------------+-------------+-----------\n\nSELECT * FROM hypertable_compression_stats(NULL) ORDER BY node_name;\n total_chunks | number_compressed_chunks | before_compression_table_bytes | before_compression_index_bytes | before_compression_toast_bytes | before_compression_total_bytes | after_compression_table_bytes | after_compression_index_bytes | after_compression_toast_bytes | after_compression_total_bytes | node_name \n--------------+--------------------------+--------------------------------+--------------------------------+--------------------------------+--------------------------------+-------------------------------+-------------------------------+-------------------------------+-------------------------------+-----------\n\nSELECT * FROM chunk_compression_stats(NULL) ORDER BY node_name;\n chunk_schema | chunk_name | compression_status | before_compression_table_bytes | before_compression_index_bytes | before_compression_toast_bytes | before_compression_total_bytes | after_compression_table_bytes | after_compression_index_bytes | after_compression_toast_bytes | after_compression_total_bytes | node_name \n--------------+------------+--------------------+--------------------------------+--------------------------------+--------------------------------+--------------------------------+-------------------------------+-------------------------------+-------------------------------+-------------------------------+-----------\n\nSELECT * FROM hypertable_index_size(NULL);\n hypertable_index_size \n-----------------------\n                      \n\nSELECT * FROM _timescaledb_functions.relation_size(NULL);\n total_size | heap_size | index_size | toast_size \n------------+-----------+------------+------------\n            |           |            |           \n\n-- Test approximate size functions with invalid input\nSELECT * FROM hypertable_approximate_size(0);\n hypertable_approximate_size \n-----------------------------\n                            \n\nSELECT * FROM hypertable_approximate_detailed_size(0);\n table_bytes | index_bytes | toast_bytes | total_bytes \n-------------+-------------+-------------+-------------\n             |             |             |            \n\nSELECT * FROM _timescaledb_functions.relation_approximate_size(0);\n total_size | heap_size | index_size | toast_size \n------------+-----------+------------+------------\n            |           |            |           \n\nSELECT * FROM hypertable_approximate_size(NULL);\n hypertable_approximate_size \n-----------------------------\n                            \n\nSELECT * FROM hypertable_approximate_detailed_size(NULL);\n table_bytes | index_bytes | toast_bytes | total_bytes \n-------------+-------------+-------------+-------------\n             |             |             |            \n\nSELECT * FROM _timescaledb_functions.relation_approximate_size(NULL);\n total_size | heap_size | index_size | toast_size \n------------+-----------+------------+------------\n\n-- Test size on view, sequence and composite type\nCREATE VIEW view1 as SELECT 1;\nSELECT * FROM _timescaledb_functions.relation_approximate_size('view1');\n total_size | heap_size | index_size | toast_size \n------------+-----------+------------+------------\n          0 |         0 |          0 |          0\n\nSELECT * FROM _timescaledb_functions.relation_size('view1');\n total_size | heap_size | index_size | toast_size \n------------+-----------+------------+------------\n          0 |         0 |          0 |          0\n\nCREATE SEQUENCE test_id_seq\n    INCREMENT 1\n    START 1    MINVALUE 1\n    MAXVALUE 9223372036854775807\n    CACHE 1;\nSELECT * FROM _timescaledb_functions.relation_approximate_size('test_id_seq');\n total_size | heap_size | index_size | toast_size \n------------+-----------+------------+------------\n       8192 |      8192 |          0 |          0\n\nSELECT * FROM _timescaledb_functions.relation_size('test_id_seq');\n total_size | heap_size | index_size | toast_size \n------------+-----------+------------+------------\n       8192 |      8192 |          0 |          0\n\nCREATE TYPE test_type AS (time timestamp, temp float);\nSELECT * FROM _timescaledb_functions.relation_approximate_size('test_type');\n total_size | heap_size | index_size | toast_size \n------------+-----------+------------+------------\n          0 |         0 |          0 |          0\n\nSELECT * FROM _timescaledb_functions.relation_size('test_type');\n total_size | heap_size | index_size | toast_size \n------------+-----------+------------+------------\n          0 |         0 |          0 |          0\n\n-- Test size functions on regular table\nCREATE TABLE hypersize(time timestamptz, device int);\nCREATE INDEX hypersize_time_idx ON hypersize (time);\n\\set ON_ERROR_STOP 0\n\\set VERBOSITY default\n\\set SHOW_CONTEXT never\nSELECT pg_relation_size('hypersize'), pg_table_size('hypersize'), pg_indexes_size('hypersize'), pg_total_relation_size('hypersize'), pg_relation_size('hypersize_time_idx');\n pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size | pg_relation_size \n------------------+---------------+-----------------+------------------------+------------------\n                0 |             0 |            8192 |                   8192 |             8192\n\nSELECT * FROM _timescaledb_functions.relation_size('hypersize');\n total_size | heap_size | index_size | toast_size \n------------+-----------+------------+------------\n       8192 |         0 |       8192 |          0\n\nSELECT * FROM hypertable_size('hypersize');\n hypertable_size \n-----------------\n                \n\nSELECT * FROM hypertable_detailed_size('hypersize') ORDER BY node_name;\n table_bytes | index_bytes | toast_bytes | total_bytes | node_name \n-------------+-------------+-------------+-------------+-----------\n\nSELECT * FROM chunks_detailed_size('hypersize') ORDER BY node_name;\n chunk_schema | chunk_name | table_bytes | index_bytes | toast_bytes | total_bytes | node_name \n--------------+------------+-------------+-------------+-------------+-------------+-----------\n\nSELECT * FROM hypertable_compression_stats('hypersize') ORDER BY node_name;\n total_chunks | number_compressed_chunks | before_compression_table_bytes | before_compression_index_bytes | before_compression_toast_bytes | before_compression_total_bytes | after_compression_table_bytes | after_compression_index_bytes | after_compression_toast_bytes | after_compression_total_bytes | node_name \n--------------+--------------------------+--------------------------------+--------------------------------+--------------------------------+--------------------------------+-------------------------------+-------------------------------+-------------------------------+-------------------------------+-----------\n\nSELECT * FROM chunk_compression_stats('hypersize') ORDER BY node_name;\n chunk_schema | chunk_name | compression_status | before_compression_table_bytes | before_compression_index_bytes | before_compression_toast_bytes | before_compression_total_bytes | after_compression_table_bytes | after_compression_index_bytes | after_compression_toast_bytes | after_compression_total_bytes | node_name \n--------------+------------+--------------------+--------------------------------+--------------------------------+--------------------------------+--------------------------------+-------------------------------+-------------------------------+-------------------------------+-------------------------------+-----------\n\nSELECT * FROM hypertable_index_size('hypersize_time_idx');\n hypertable_index_size \n-----------------------\n                  8192\n\nSELECT * FROM _timescaledb_functions.relation_approximate_size('hypersize');\n total_size | heap_size | index_size | toast_size \n------------+-----------+------------+------------\n       8192 |         0 |       8192 |          0\n\nSELECT * FROM hypertable_approximate_size('hypersize');\nERROR:  \"hypersize\" is not a hypertable or a continuous aggregate\nHINT:  The operation is only possible on a hypertable or continuous aggregate.\nSELECT * FROM hypertable_approximate_detailed_size('hypersize');\nERROR:  \"hypersize\" is not a hypertable or a continuous aggregate\nHINT:  The operation is only possible on a hypertable or continuous aggregate.\n\\set VERBOSITY terse\n\\set ON_ERROR_STOP 1\n-- Test size functions on empty hypertable\nSELECT * FROM create_hypertable('hypersize', 'time');\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             6 | public      | hypersize  | t\n\nSELECT pg_relation_size('hypersize'), pg_table_size('hypersize'), pg_indexes_size('hypersize'), pg_total_relation_size('hypersize'), pg_relation_size('hypersize_time_idx');\n pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size | pg_relation_size \n------------------+---------------+-----------------+------------------------+------------------\n                0 |             0 |            8192 |                   8192 |             8192\n\nSELECT * FROM _timescaledb_functions.relation_size('hypersize');\n total_size | heap_size | index_size | toast_size \n------------+-----------+------------+------------\n       8192 |         0 |       8192 |          0\n\nSELECT * FROM hypertable_size('hypersize');\n hypertable_size \n-----------------\n            8192\n\nSELECT * FROM hypertable_detailed_size('hypersize') ORDER BY node_name;\n table_bytes | index_bytes | toast_bytes | total_bytes | node_name \n-------------+-------------+-------------+-------------+-----------\n           0 |        8192 |           0 |        8192 | \n\nSELECT * FROM chunks_detailed_size('hypersize') ORDER BY node_name;\n chunk_schema | chunk_name | table_bytes | index_bytes | toast_bytes | total_bytes | node_name \n--------------+------------+-------------+-------------+-------------+-------------+-----------\n\nSELECT * FROM hypertable_compression_stats('hypersize') ORDER BY node_name;\n total_chunks | number_compressed_chunks | before_compression_table_bytes | before_compression_index_bytes | before_compression_toast_bytes | before_compression_total_bytes | after_compression_table_bytes | after_compression_index_bytes | after_compression_toast_bytes | after_compression_total_bytes | node_name \n--------------+--------------------------+--------------------------------+--------------------------------+--------------------------------+--------------------------------+-------------------------------+-------------------------------+-------------------------------+-------------------------------+-----------\n\nSELECT * FROM chunk_compression_stats('hypersize') ORDER BY node_name;\n chunk_schema | chunk_name | compression_status | before_compression_table_bytes | before_compression_index_bytes | before_compression_toast_bytes | before_compression_total_bytes | after_compression_table_bytes | after_compression_index_bytes | after_compression_toast_bytes | after_compression_total_bytes | node_name \n--------------+------------+--------------------+--------------------------------+--------------------------------+--------------------------------+--------------------------------+-------------------------------+-------------------------------+-------------------------------+-------------------------------+-----------\n\nSELECT * FROM hypertable_index_size('hypersize_time_idx');\n hypertable_index_size \n-----------------------\n                  8192\n\nSELECT * FROM _timescaledb_functions.relation_approximate_size('hypersize');\n total_size | heap_size | index_size | toast_size \n------------+-----------+------------+------------\n       8192 |         0 |       8192 |          0\n\nSELECT * FROM hypertable_approximate_size('hypersize');\n hypertable_approximate_size \n-----------------------------\n                        8192\n\nSELECT * FROM hypertable_approximate_detailed_size('hypersize');\n table_bytes | index_bytes | toast_bytes | total_bytes \n-------------+-------------+-------------+-------------\n           0 |        8192 |           0 |        8192\n\n-- Test size functions on non-empty hypertable\nINSERT INTO hypersize VALUES('2021-02-25', 1);\nSELECT pg_relation_size('hypersize'), pg_table_size('hypersize'), pg_indexes_size('hypersize'), pg_total_relation_size('hypersize'), pg_relation_size('hypersize_time_idx');\n pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size | pg_relation_size \n------------------+---------------+-----------------+------------------------+------------------\n                0 |             0 |            8192 |                   8192 |             8192\n\nSELECT pg_relation_size(ch), pg_table_size(ch), pg_indexes_size(ch), pg_total_relation_size(ch)\nFROM show_chunks('hypersize') ch\nORDER BY ch;\n pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size \n------------------+---------------+-----------------+------------------------\n             8192 |          8192 |           16384 |                  24576\n\nSELECT * FROM show_chunks('hypersize') ch JOIN LATERAL _timescaledb_functions.relation_size(ch) ON true;\n                   ch                    | total_size | heap_size | index_size | toast_size \n-----------------------------------------+------------+-----------+------------+------------\n _timescaledb_internal._hyper_6_11_chunk |      24576 |      8192 |      16384 |          0\n\nSELECT * FROM hypertable_size('hypersize');\n hypertable_size \n-----------------\n           32768\n\nSELECT * FROM hypertable_detailed_size('hypersize') ORDER BY node_name;\n table_bytes | index_bytes | toast_bytes | total_bytes | node_name \n-------------+-------------+-------------+-------------+-----------\n        8192 |       24576 |           0 |       32768 | \n\nSELECT * FROM chunks_detailed_size('hypersize') ORDER BY node_name;\n     chunk_schema      |    chunk_name     | table_bytes | index_bytes | toast_bytes | total_bytes | node_name \n-----------------------+-------------------+-------------+-------------+-------------+-------------+-----------\n _timescaledb_internal | _hyper_6_11_chunk |        8192 |       16384 |           0 |       24576 | \n\nSELECT * FROM hypertable_compression_stats('hypersize') ORDER BY node_name;\n total_chunks | number_compressed_chunks | before_compression_table_bytes | before_compression_index_bytes | before_compression_toast_bytes | before_compression_total_bytes | after_compression_table_bytes | after_compression_index_bytes | after_compression_toast_bytes | after_compression_total_bytes | node_name \n--------------+--------------------------+--------------------------------+--------------------------------+--------------------------------+--------------------------------+-------------------------------+-------------------------------+-------------------------------+-------------------------------+-----------\n\nSELECT * FROM chunk_compression_stats('hypersize') ORDER BY node_name;\n chunk_schema | chunk_name | compression_status | before_compression_table_bytes | before_compression_index_bytes | before_compression_toast_bytes | before_compression_total_bytes | after_compression_table_bytes | after_compression_index_bytes | after_compression_toast_bytes | after_compression_total_bytes | node_name \n--------------+------------+--------------------+--------------------------------+--------------------------------+--------------------------------+--------------------------------+-------------------------------+-------------------------------+-------------------------------+-------------------------------+-----------\n\nSELECT * FROM hypertable_index_size('hypersize_time_idx');\n hypertable_index_size \n-----------------------\n                 24576\n\nSELECT * FROM _timescaledb_functions.relation_approximate_size('hypersize');\n total_size | heap_size | index_size | toast_size \n------------+-----------+------------+------------\n       8192 |         0 |       8192 |          0\n\nSELECT * FROM hypertable_approximate_size('hypersize');\n hypertable_approximate_size \n-----------------------------\n                       32768\n\nSELECT * FROM hypertable_approximate_detailed_size('hypersize');\n table_bytes | index_bytes | toast_bytes | total_bytes \n-------------+-------------+-------------+-------------\n        8192 |       24576 |           0 |       32768\n\n-- Test approx size functions with toast entries\nSELECT * FROM _timescaledb_functions.relation_approximate_size('toast_test');\n total_size | heap_size | index_size | toast_size \n------------+-----------+------------+------------\n      16384 |         0 |       8192 |       8192\n\nSELECT * FROM hypertable_approximate_size('toast_test');\n hypertable_approximate_size \n-----------------------------\n                       65536\n\nSELECT * FROM hypertable_approximate_detailed_size('toast_test');\n table_bytes | index_bytes | toast_bytes | total_bytes \n-------------+-------------+-------------+-------------\n        8192 |       24576 |       32768 |       65536\n\n-- Test approx size function against a regular table\n\\set ON_ERROR_STOP 0\nCREATE TABLE regular(time TIMESTAMP, value TEXT);\nSELECT * FROM hypertable_approximate_size('regular');\nERROR:  \"regular\" is not a hypertable or a continuous aggregate\n\\set ON_ERROR_STOP 1\n-- Test approx size functions with dropped chunks\nCREATE TABLE drop_chunks_table(time BIGINT NOT NULL, data INTEGER);\nSELECT hypertable_id AS drop_chunks_table_id\n    FROM create_hypertable('drop_chunks_table', 'time', chunk_time_interval => 10) \\gset\nINSERT INTO drop_chunks_table SELECT i, i FROM generate_series(0, 29) AS i;\nSELECT * FROM hypertable_approximate_size('drop_chunks_table');\n hypertable_approximate_size \n-----------------------------\n                       81920\n\nSELECT drop_chunks('drop_chunks_table', older_than => 19);\n               drop_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_7_12_chunk\n\nSELECT * FROM hypertable_approximate_size('drop_chunks_table');\n hypertable_approximate_size \n-----------------------------\n                       57344\n\n-- github issue #4857\n-- below procedure should not crash\nSET client_min_messages = ERROR;\ndo\n$$\nDECLARE\n  o INT;\nBEGIN\n  FOR c IN 1..20 LOOP\n    ANALYZE;\n  END LOOP;\nEND;\n$$;\nRESET client_min_messages;\n"
  },
  {
    "path": "test/expected/sort_optimization.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\set PREFIX 'EXPLAIN (BUFFERS OFF, COSTS OFF) '\nCREATE TABLE order_test(time int NOT NULL, device_id int, value float);\nCREATE INDEX ON order_test(time,device_id);\nCREATE INDEX ON order_test(device_id,time);\nSELECT create_hypertable('order_test','time',chunk_time_interval:=1000);\n    create_hypertable    \n-------------------------\n (1,public,order_test,t)\n\nINSERT INTO order_test SELECT 0,10,0.5;\nINSERT INTO order_test SELECT 1,9,0.5;\nINSERT INTO order_test SELECT 2,8,0.5;\n-- we want to see here that index scans are possible for the chosen expressions\n-- so we disable seqscan so we dont need to worry about other factors which would\n-- make PostgreSQL prefer seqscan over index scan\nSET enable_seqscan TO off;\n-- test sort optimization with single member order by\nSELECT time_bucket(10,time),device_id,value FROM order_test ORDER BY 1;\n time_bucket | device_id | value \n-------------+-----------+-------\n           0 |        10 |   0.5\n           0 |         9 |   0.5\n           0 |         8 |   0.5\n\n-- should use index scan\n:PREFIX SELECT time_bucket(10,time),device_id,value FROM order_test ORDER BY 1;\n--- QUERY PLAN ---\n Result\n   ->  Index Scan Backward using _hyper_1_1_chunk_order_test_time_idx on _hyper_1_1_chunk\n\n-- test sort optimization with ordering by multiple columns and time_bucket not last\nSELECT time_bucket(10,time),device_id,value FROM order_test ORDER BY 1,2;\n time_bucket | device_id | value \n-------------+-----------+-------\n           0 |         8 |   0.5\n           0 |         9 |   0.5\n           0 |        10 |   0.5\n\nSET enable_seqscan TO default;\n-- must not use index scan\n:PREFIX SELECT time_bucket(10,time),device_id,value FROM order_test ORDER BY 1,2;\n--- QUERY PLAN ---\n Sort\n   Sort Key: (time_bucket(10, _hyper_1_1_chunk.\"time\")), _hyper_1_1_chunk.device_id\n   ->  Result\n         ->  Seq Scan on _hyper_1_1_chunk\n\nSET enable_seqscan TO off;\n-- test sort optimization with ordering by multiple columns and time_bucket as last member\nSELECT time_bucket(10,time),device_id,value FROM order_test ORDER BY 2,1;\n time_bucket | device_id | value \n-------------+-----------+-------\n           0 |         8 |   0.5\n           0 |         9 |   0.5\n           0 |        10 |   0.5\n\n-- should use index scan\n:PREFIX SELECT time_bucket(10,time),device_id,value FROM order_test ORDER BY 2,1;\n--- QUERY PLAN ---\n Result\n   ->  Index Scan using _hyper_1_1_chunk_order_test_device_id_time_idx on _hyper_1_1_chunk\n\n-- test sort optimization with interval calculation with non-fixed interval\n-- #7097\nCREATE TABLE i7097_1(time timestamptz NOT NULL, quantity float, \"isText\" boolean);\nCREATE TABLE i7097_2(time timestamptz NOT NULL, quantity float, \"isText\" boolean);\nCREATE INDEX ON i7097_1(time) WHERE \"isText\" IS NULL;\nCREATE INDEX ON i7097_2(time) WHERE \"isText\" IS NULL;\nSELECT table_name FROM create_hypertable('i7097_1', 'time', create_default_indexes => false);\n table_name \n------------\n i7097_1\n\nSELECT table_name FROM create_hypertable('i7097_2', 'time', create_default_indexes => false);\n table_name \n------------\n i7097_2\n\nINSERT INTO i7097_1(time, quantity)\nSELECT time, round((random() * (100-3) + 3)::NUMERIC) AS quantity\nFROM generate_series('2023-01-01T00:00:00+01:00', '2023-05-01T00:00:00+01:00', interval 'PT10M') AS t(time);\nINSERT INTO i7097_2(time, quantity)\nSELECT time, round((random() * (100-3) + 3)::NUMERIC) AS quantity\nFROM generate_series('2023-01-01T00:00:00+01:00', '2023-05-01T00:00:00+01:00', interval 'PT10M') AS t(time);\nVACUUM ANALYZE i7097_1, i7097_2;\nSET TIME ZONE 'Europe/Paris';\nWITH\n\"cte1\" AS (SELECT time + interval 'P1Y' AS time, avg(quantity) AS quantity FROM i7097_1 WHERE time >= '2024-03-31T00:00:00+01:00'::timestamptz - interval 'P1Y' AND time < '2024-03-31T23:59:59+02:00'::timestamptz + (- interval 'P1Y') AND \"isText\" IS NULL GROUP BY 1 ORDER BY 1 ASC),\n\"cte2\" AS (SELECT time + interval 'P1Y' AS time, avg(quantity) AS quantity FROM i7097_2 WHERE time >= '2024-03-31T00:00:00+01:00'::timestamptz - interval 'P1Y' AND time < '2024-03-31T23:59:59+02:00'::timestamptz + (- interval 'P1Y') AND \"isText\" IS NULL GROUP BY 1 ORDER BY 1 ASC)\nSELECT count(*) FROM (SELECT time, cte1.quantity + cte2.quantity FROM cte1 FULL OUTER JOIN cte2 USING (time)) j;\n count \n-------\n   138\n\n-- github issue 9214\n-- test off-by one error in sort optimization\nCREATE TABLE i9214(time timestamptz NOT NULL, machine_id INT NOT NULL, name TEXT NOT NULL, value FLOAT NOT NULL) WITH (tsdb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nINSERT INTO i9214\nVALUES\n('2026-01-30 10:00:00+00', 1, 'tag1', 10.5),\n('2026-01-30 10:00:00+00', 1, 'tag2', 20.5),\n('2026-01-30 10:01:00+00', 1, 'tag1', 11.0),\n('2026-01-30 10:01:00+00', 1, 'tag2', 21.0);\nWITH rule1 AS (\n  SELECT date_trunc('minute', time) AS time, machine_id FROM i9214 WHERE machine_id = 1 AND name = 'tag1' AND value > 5\n), row_numbered AS (\n  SELECT time, machine_id, row_number() OVER (ORDER BY time) AS seqnum FROM rule1\n)\nSELECT min(time) AS start_time, machine_id, count(*) AS duration_minutes\nFROM row_numbered\nGROUP BY machine_id, (time - (seqnum * interval '1 minute'))\nORDER BY min(time);\n          start_time          | machine_id | duration_minutes \n------------------------------+------------+------------------\n Fri Jan 30 11:00:00 2026 CET |          1 |                2\n\nWITH rule1 AS (\n\tSELECT date_trunc('minute', time) AS time, machine_id FROM i9214 WHERE machine_id = 1 AND name = 'tag1' AND value > 5\n), rule2 AS (\n  SELECT date_trunc('minute', time) AS time, machine_id FROM i9214 WHERE machine_id = 1 AND name = 'tag2' AND value > 5\n), joined_rules AS (\n  SELECT r1.time, r1.machine_id FROM rule1 r1 INNER JOIN rule2 r2 USING(time,machine_id)\n), row_numbered AS (\n  SELECT time, machine_id, row_number() OVER (ORDER BY time) AS seqnum FROM joined_rules\n)\nSELECT min(time) AS start_time, machine_id, count(*) AS duration_minutes\nFROM row_numbered\nGROUP BY machine_id, (time - (seqnum * interval '1 minute'))\nORDER BY min(time);\n          start_time          | machine_id | duration_minutes \n------------------------------+------------+------------------\n Fri Jan 30 11:00:00 2026 CET |          1 |                2\n\n"
  },
  {
    "path": "test/expected/sql_query.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\o /dev/null\n\\ir include/insert_two_partitions.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE PUBLIC.\"two_Partitions\" (\n  \"timeCustom\" BIGINT NOT NULL,\n  device_id TEXT NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL,\n  series_bool BOOLEAN NULL\n);\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (device_id, \"timeCustom\" DESC NULLS LAST) WHERE device_id IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_0) WHERE series_0 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_1)  WHERE series_1 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_2) WHERE series_2 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_bool) WHERE series_bool IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, device_id);\nSELECT * FROM create_hypertable('\"public\".\"two_Partitions\"'::regclass, 'timeCustom'::name, 'device_id'::name, associated_schema_name=>'_timescaledb_internal'::text, number_partitions => 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\\set QUIET off\nBEGIN;\n\\COPY public.\"two_Partitions\" FROM 'data/ds1_dev1_1.tsv' NULL AS '';\nCOMMIT;\nINSERT INTO public.\"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257987600000000000, 'dev1', 1.5, 1),\n(1257987600000000000, 'dev1', 1.5, 2),\n(1257894000000000000, 'dev2', 1.5, 1),\n(1257894002000000000, 'dev1', 2.5, 3);\nINSERT INTO \"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257894000000000000, 'dev2', 1.5, 2);\n\\set QUIET on\n\\o\nSELECT * FROM PUBLIC.\"two_Partitions\";\n     timeCustom      | device_id | series_0 | series_1 | series_2 | series_bool \n---------------------+-----------+----------+----------+----------+-------------\n 1257894000000000000 | dev1      |      1.5 |        1 |        2 | t\n 1257894000000000000 | dev1      |      1.5 |        2 |          | \n 1257894000000001000 | dev1      |      2.5 |        3 |          | \n 1257894001000000000 | dev1      |      3.5 |        4 |          | \n 1257894002000000000 | dev1      |      5.5 |        6 |          | t\n 1257894002000000000 | dev1      |      5.5 |        7 |          | f\n 1257894002000000000 | dev1      |      2.5 |        3 |          | \n 1257897600000000000 | dev1      |      4.5 |        5 |          | f\n 1257987600000000000 | dev1      |      1.5 |        1 |          | \n 1257987600000000000 | dev1      |      1.5 |        2 |          | \n 1257894000000000000 | dev2      |      1.5 |        1 |          | \n 1257894000000000000 | dev2      |      1.5 |        2 |          | \n\nEXPLAIN (verbose ON, buffers off, costs off) SELECT * FROM PUBLIC.\"two_Partitions\";\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n         Output: _hyper_1_1_chunk.\"timeCustom\", _hyper_1_1_chunk.device_id, _hyper_1_1_chunk.series_0, _hyper_1_1_chunk.series_1, _hyper_1_1_chunk.series_2, _hyper_1_1_chunk.series_bool\n   ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n         Output: _hyper_1_2_chunk.\"timeCustom\", _hyper_1_2_chunk.device_id, _hyper_1_2_chunk.series_0, _hyper_1_2_chunk.series_1, _hyper_1_2_chunk.series_2, _hyper_1_2_chunk.series_bool\n   ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n         Output: _hyper_1_3_chunk.\"timeCustom\", _hyper_1_3_chunk.device_id, _hyper_1_3_chunk.series_0, _hyper_1_3_chunk.series_1, _hyper_1_3_chunk.series_2, _hyper_1_3_chunk.series_bool\n   ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n         Output: _hyper_1_4_chunk.\"timeCustom\", _hyper_1_4_chunk.device_id, _hyper_1_4_chunk.series_0, _hyper_1_4_chunk.series_1, _hyper_1_4_chunk.series_2, _hyper_1_4_chunk.series_bool\n\n\\echo \"The following queries should NOT scan two_Partitions._hyper_1_1_chunk\"\n\"The following queries should NOT scan two_Partitions._hyper_1_1_chunk\"\nEXPLAIN (verbose ON, buffers off, costs off) SELECT * FROM PUBLIC.\"two_Partitions\" WHERE device_id = 'dev2';\n--- QUERY PLAN ---\n Index Scan using \"_hyper_1_4_chunk_two_Partitions_device_id_timeCustom_idx\" on _timescaledb_internal._hyper_1_4_chunk\n   Output: _hyper_1_4_chunk.\"timeCustom\", _hyper_1_4_chunk.device_id, _hyper_1_4_chunk.series_0, _hyper_1_4_chunk.series_1, _hyper_1_4_chunk.series_2, _hyper_1_4_chunk.series_bool\n   Index Cond: (_hyper_1_4_chunk.device_id = 'dev2'::text)\n\nEXPLAIN (verbose ON, buffers off, costs off) SELECT * FROM PUBLIC.\"two_Partitions\" WHERE device_id = 'dev'||'2';\n--- QUERY PLAN ---\n Index Scan using \"_hyper_1_4_chunk_two_Partitions_device_id_timeCustom_idx\" on _timescaledb_internal._hyper_1_4_chunk\n   Output: _hyper_1_4_chunk.\"timeCustom\", _hyper_1_4_chunk.device_id, _hyper_1_4_chunk.series_0, _hyper_1_4_chunk.series_1, _hyper_1_4_chunk.series_2, _hyper_1_4_chunk.series_bool\n   Index Cond: (_hyper_1_4_chunk.device_id = 'dev2'::text)\n\nEXPLAIN (verbose ON, buffers off, costs off) SELECT * FROM PUBLIC.\"two_Partitions\" WHERE 'dev'||'2' = device_id;\n--- QUERY PLAN ---\n Index Scan using \"_hyper_1_4_chunk_two_Partitions_device_id_timeCustom_idx\" on _timescaledb_internal._hyper_1_4_chunk\n   Output: _hyper_1_4_chunk.\"timeCustom\", _hyper_1_4_chunk.device_id, _hyper_1_4_chunk.series_0, _hyper_1_4_chunk.series_1, _hyper_1_4_chunk.series_2, _hyper_1_4_chunk.series_bool\n   Index Cond: (_hyper_1_4_chunk.device_id = 'dev2'::text)\n\n--test integer partition key\nCREATE TABLE \"int_part\"(time timestamp, object_id int, temp float);\nSELECT create_hypertable('\"int_part\"', 'time', 'object_id', 2);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n   create_hypertable   \n-----------------------\n (2,public,int_part,t)\n\nINSERT INTO \"int_part\" VALUES('2017-01-20T09:00:01', 1, 22.5);\nINSERT INTO \"int_part\" VALUES('2017-01-20T09:00:01', 2, 22.5);\n--check that there are two chunks\nSELECT * FROM test.show_subtables('int_part');\n                 Child                  | Tablespace \n----------------------------------------+------------\n _timescaledb_internal._hyper_2_5_chunk | \n _timescaledb_internal._hyper_2_6_chunk | \n\nSELECT * FROM \"int_part\" WHERE object_id = 1;\n           time           | object_id | temp \n--------------------------+-----------+------\n Fri Jan 20 09:00:01 2017 |         1 | 22.5\n\n--check that queries with IN/ANY/= work for the \"time\" column\nSELECT * FROM \"int_part\" WHERE time IN (NULL);\n time | object_id | temp \n------+-----------+------\n\nSELECT * FROM \"int_part\" WHERE time = ANY (NULL);\n time | object_id | temp \n------+-----------+------\n\nSELECT * FROM \"int_part\" WHERE time = NULL;\n time | object_id | temp \n------+-----------+------\n\n--make sure this touches only one partititon\nEXPLAIN (verbose ON, buffers off, costs off) SELECT * FROM \"int_part\" WHERE object_id = 1;\n--- QUERY PLAN ---\n Index Scan using _hyper_2_5_chunk_int_part_object_id_time_idx on _timescaledb_internal._hyper_2_5_chunk\n   Output: _hyper_2_5_chunk.\"time\", _hyper_2_5_chunk.object_id, _hyper_2_5_chunk.temp\n   Index Cond: (_hyper_2_5_chunk.object_id = 1)\n\n--Need to verify space partitions are currently pruned in this query\n--EXPLAIN (verbose ON, buffers off, costs off) SELECT * FROM \"two_Partitions\" WHERE device_id IN ('dev2', 'dev21');\n\\echo \"The following shows non-aggregated queries with time desc using merge append\"\n\"The following shows non-aggregated queries with time desc using merge append\"\nEXPLAIN (verbose ON, buffers off, costs off)SELECT * FROM PUBLIC.\"two_Partitions\" ORDER BY \"timeCustom\" DESC NULLS LAST limit 2;\n--- QUERY PLAN ---\n Limit\n   Output: \"two_Partitions\".\"timeCustom\", \"two_Partitions\".device_id, \"two_Partitions\".series_0, \"two_Partitions\".series_1, \"two_Partitions\".series_2, \"two_Partitions\".series_bool\n   ->  Custom Scan (ChunkAppend) on public.\"two_Partitions\"\n         Output: \"two_Partitions\".\"timeCustom\", \"two_Partitions\".device_id, \"two_Partitions\".series_0, \"two_Partitions\".series_1, \"two_Partitions\".series_2, \"two_Partitions\".series_bool\n         Order: \"two_Partitions\".\"timeCustom\" DESC NULLS LAST\n         Startup Exclusion: false\n         Runtime Exclusion: false\n         ->  Index Scan using \"_hyper_1_3_chunk_two_Partitions_timeCustom_device_id_idx\" on _timescaledb_internal._hyper_1_3_chunk\n               Output: _hyper_1_3_chunk.\"timeCustom\", _hyper_1_3_chunk.device_id, _hyper_1_3_chunk.series_0, _hyper_1_3_chunk.series_1, _hyper_1_3_chunk.series_2, _hyper_1_3_chunk.series_bool\n         ->  Index Scan using \"_hyper_1_2_chunk_two_Partitions_timeCustom_device_id_idx\" on _timescaledb_internal._hyper_1_2_chunk\n               Output: _hyper_1_2_chunk.\"timeCustom\", _hyper_1_2_chunk.device_id, _hyper_1_2_chunk.series_0, _hyper_1_2_chunk.series_1, _hyper_1_2_chunk.series_2, _hyper_1_2_chunk.series_bool\n         ->  Merge Append\n               Sort Key: \"two_Partitions\".\"timeCustom\" DESC NULLS LAST\n               ->  Index Scan using \"_hyper_1_4_chunk_two_Partitions_timeCustom_device_id_idx\" on _timescaledb_internal._hyper_1_4_chunk\n                     Output: _hyper_1_4_chunk.\"timeCustom\", _hyper_1_4_chunk.device_id, _hyper_1_4_chunk.series_0, _hyper_1_4_chunk.series_1, _hyper_1_4_chunk.series_2, _hyper_1_4_chunk.series_bool\n               ->  Index Scan using \"_hyper_1_1_chunk_two_Partitions_timeCustom_device_id_idx\" on _timescaledb_internal._hyper_1_1_chunk\n                     Output: _hyper_1_1_chunk.\"timeCustom\", _hyper_1_1_chunk.device_id, _hyper_1_1_chunk.series_0, _hyper_1_1_chunk.series_1, _hyper_1_1_chunk.series_2, _hyper_1_1_chunk.series_bool\n\n--shows that more specific indexes are used if the WHERE clauses \"match\", uses the series_1 index here.\nEXPLAIN (verbose ON, buffers off, costs off)SELECT * FROM PUBLIC.\"two_Partitions\" WHERE series_1 IS NOT NULL ORDER BY \"timeCustom\" DESC NULLS LAST limit 2;\n--- QUERY PLAN ---\n Limit\n   Output: \"two_Partitions\".\"timeCustom\", \"two_Partitions\".device_id, \"two_Partitions\".series_0, \"two_Partitions\".series_1, \"two_Partitions\".series_2, \"two_Partitions\".series_bool\n   ->  Custom Scan (ChunkAppend) on public.\"two_Partitions\"\n         Output: \"two_Partitions\".\"timeCustom\", \"two_Partitions\".device_id, \"two_Partitions\".series_0, \"two_Partitions\".series_1, \"two_Partitions\".series_2, \"two_Partitions\".series_bool\n         Order: \"two_Partitions\".\"timeCustom\" DESC NULLS LAST\n         Startup Exclusion: false\n         Runtime Exclusion: false\n         ->  Index Scan using \"_hyper_1_3_chunk_two_Partitions_timeCustom_series_1_idx\" on _timescaledb_internal._hyper_1_3_chunk\n               Output: _hyper_1_3_chunk.\"timeCustom\", _hyper_1_3_chunk.device_id, _hyper_1_3_chunk.series_0, _hyper_1_3_chunk.series_1, _hyper_1_3_chunk.series_2, _hyper_1_3_chunk.series_bool\n         ->  Index Scan using \"_hyper_1_2_chunk_two_Partitions_timeCustom_series_1_idx\" on _timescaledb_internal._hyper_1_2_chunk\n               Output: _hyper_1_2_chunk.\"timeCustom\", _hyper_1_2_chunk.device_id, _hyper_1_2_chunk.series_0, _hyper_1_2_chunk.series_1, _hyper_1_2_chunk.series_2, _hyper_1_2_chunk.series_bool\n         ->  Merge Append\n               Sort Key: \"two_Partitions\".\"timeCustom\" DESC NULLS LAST\n               ->  Index Scan using \"_hyper_1_4_chunk_two_Partitions_timeCustom_series_1_idx\" on _timescaledb_internal._hyper_1_4_chunk\n                     Output: _hyper_1_4_chunk.\"timeCustom\", _hyper_1_4_chunk.device_id, _hyper_1_4_chunk.series_0, _hyper_1_4_chunk.series_1, _hyper_1_4_chunk.series_2, _hyper_1_4_chunk.series_bool\n               ->  Index Scan using \"_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx\" on _timescaledb_internal._hyper_1_1_chunk\n                     Output: _hyper_1_1_chunk.\"timeCustom\", _hyper_1_1_chunk.device_id, _hyper_1_1_chunk.series_0, _hyper_1_1_chunk.series_1, _hyper_1_1_chunk.series_2, _hyper_1_1_chunk.series_bool\n\n--here the \"match\" is implication series_1 > 1 => series_1 IS NOT NULL\nEXPLAIN (verbose ON, buffers off, costs off)SELECT * FROM PUBLIC.\"two_Partitions\" WHERE series_1 > 1 ORDER BY \"timeCustom\" DESC NULLS LAST limit 2;\n--- QUERY PLAN ---\n Limit\n   Output: \"two_Partitions\".\"timeCustom\", \"two_Partitions\".device_id, \"two_Partitions\".series_0, \"two_Partitions\".series_1, \"two_Partitions\".series_2, \"two_Partitions\".series_bool\n   ->  Custom Scan (ChunkAppend) on public.\"two_Partitions\"\n         Output: \"two_Partitions\".\"timeCustom\", \"two_Partitions\".device_id, \"two_Partitions\".series_0, \"two_Partitions\".series_1, \"two_Partitions\".series_2, \"two_Partitions\".series_bool\n         Order: \"two_Partitions\".\"timeCustom\" DESC NULLS LAST\n         Startup Exclusion: false\n         Runtime Exclusion: false\n         ->  Index Scan using \"_hyper_1_3_chunk_two_Partitions_timeCustom_series_1_idx\" on _timescaledb_internal._hyper_1_3_chunk\n               Output: _hyper_1_3_chunk.\"timeCustom\", _hyper_1_3_chunk.device_id, _hyper_1_3_chunk.series_0, _hyper_1_3_chunk.series_1, _hyper_1_3_chunk.series_2, _hyper_1_3_chunk.series_bool\n               Index Cond: (_hyper_1_3_chunk.series_1 > '1'::double precision)\n         ->  Index Scan using \"_hyper_1_2_chunk_two_Partitions_timeCustom_series_1_idx\" on _timescaledb_internal._hyper_1_2_chunk\n               Output: _hyper_1_2_chunk.\"timeCustom\", _hyper_1_2_chunk.device_id, _hyper_1_2_chunk.series_0, _hyper_1_2_chunk.series_1, _hyper_1_2_chunk.series_2, _hyper_1_2_chunk.series_bool\n               Index Cond: (_hyper_1_2_chunk.series_1 > '1'::double precision)\n         ->  Merge Append\n               Sort Key: \"two_Partitions\".\"timeCustom\" DESC NULLS LAST\n               ->  Index Scan using \"_hyper_1_4_chunk_two_Partitions_timeCustom_series_1_idx\" on _timescaledb_internal._hyper_1_4_chunk\n                     Output: _hyper_1_4_chunk.\"timeCustom\", _hyper_1_4_chunk.device_id, _hyper_1_4_chunk.series_0, _hyper_1_4_chunk.series_1, _hyper_1_4_chunk.series_2, _hyper_1_4_chunk.series_bool\n                     Index Cond: (_hyper_1_4_chunk.series_1 > '1'::double precision)\n               ->  Index Scan using \"_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx\" on _timescaledb_internal._hyper_1_1_chunk\n                     Output: _hyper_1_1_chunk.\"timeCustom\", _hyper_1_1_chunk.device_id, _hyper_1_1_chunk.series_0, _hyper_1_1_chunk.series_1, _hyper_1_1_chunk.series_2, _hyper_1_1_chunk.series_bool\n                     Index Cond: (_hyper_1_1_chunk.series_1 > '1'::double precision)\n\n--note that without time transform things work too\nEXPLAIN (verbose ON, buffers off, costs off)SELECT \"timeCustom\" t, min(series_0) FROM PUBLIC.\"two_Partitions\" GROUP BY t ORDER BY t DESC NULLS LAST limit 2;\n--- QUERY PLAN ---\n Limit\n   Output: \"two_Partitions\".\"timeCustom\", (min(\"two_Partitions\".series_0))\n   ->  GroupAggregate\n         Output: \"two_Partitions\".\"timeCustom\", min(\"two_Partitions\".series_0)\n         Group Key: \"two_Partitions\".\"timeCustom\"\n         ->  Custom Scan (ChunkAppend) on public.\"two_Partitions\"\n               Output: \"two_Partitions\".\"timeCustom\", \"two_Partitions\".series_0\n               Order: \"two_Partitions\".\"timeCustom\" DESC NULLS LAST\n               Startup Exclusion: false\n               Runtime Exclusion: false\n               ->  Index Scan using \"_hyper_1_3_chunk_two_Partitions_timeCustom_device_id_idx\" on _timescaledb_internal._hyper_1_3_chunk\n                     Output: _hyper_1_3_chunk.\"timeCustom\", _hyper_1_3_chunk.series_0\n               ->  Index Scan using \"_hyper_1_2_chunk_two_Partitions_timeCustom_device_id_idx\" on _timescaledb_internal._hyper_1_2_chunk\n                     Output: _hyper_1_2_chunk.\"timeCustom\", _hyper_1_2_chunk.series_0\n               ->  Merge Append\n                     Sort Key: \"two_Partitions\".\"timeCustom\" DESC NULLS LAST\n                     ->  Index Scan using \"_hyper_1_4_chunk_two_Partitions_timeCustom_device_id_idx\" on _timescaledb_internal._hyper_1_4_chunk\n                           Output: _hyper_1_4_chunk.\"timeCustom\", _hyper_1_4_chunk.series_0\n                     ->  Index Scan using \"_hyper_1_1_chunk_two_Partitions_timeCustom_device_id_idx\" on _timescaledb_internal._hyper_1_1_chunk\n                           Output: _hyper_1_1_chunk.\"timeCustom\", _hyper_1_1_chunk.series_0\n\n--The query should still use the index on timeCustom, even though the GROUP BY/ORDER BY is on the transformed time 't'.\n--However, current query plans show that it does not.\nEXPLAIN (verbose ON, buffers off, costs off)SELECT \"timeCustom\"/10 t, min(series_0) FROM PUBLIC.\"two_Partitions\" GROUP BY t ORDER BY t DESC NULLS LAST limit 2;\n--- QUERY PLAN ---\n Limit\n   Output: ((\"two_Partitions\".\"timeCustom\" / 10)), (min(\"two_Partitions\".series_0))\n   ->  Sort\n         Output: ((\"two_Partitions\".\"timeCustom\" / 10)), (min(\"two_Partitions\".series_0))\n         Sort Key: ((\"two_Partitions\".\"timeCustom\" / 10)) DESC NULLS LAST\n         ->  HashAggregate\n               Output: ((\"two_Partitions\".\"timeCustom\" / 10)), min(\"two_Partitions\".series_0)\n               Group Key: (\"two_Partitions\".\"timeCustom\" / 10)\n               ->  Result\n                     Output: (\"two_Partitions\".\"timeCustom\" / 10), \"two_Partitions\".series_0\n                     ->  Append\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                                 Output: _hyper_1_1_chunk.\"timeCustom\", _hyper_1_1_chunk.series_0\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                                 Output: _hyper_1_2_chunk.\"timeCustom\", _hyper_1_2_chunk.series_0\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                                 Output: _hyper_1_3_chunk.\"timeCustom\", _hyper_1_3_chunk.series_0\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                                 Output: _hyper_1_4_chunk.\"timeCustom\", _hyper_1_4_chunk.series_0\n\nEXPLAIN (verbose ON, buffers off, costs off)SELECT \"timeCustom\"%10 t, min(series_0) FROM PUBLIC.\"two_Partitions\" GROUP BY t ORDER BY t DESC NULLS LAST limit 2;\n--- QUERY PLAN ---\n Limit\n   Output: ((\"two_Partitions\".\"timeCustom\" % '10'::bigint)), (min(\"two_Partitions\".series_0))\n   ->  Sort\n         Output: ((\"two_Partitions\".\"timeCustom\" % '10'::bigint)), (min(\"two_Partitions\".series_0))\n         Sort Key: ((\"two_Partitions\".\"timeCustom\" % '10'::bigint)) DESC NULLS LAST\n         ->  HashAggregate\n               Output: ((\"two_Partitions\".\"timeCustom\" % '10'::bigint)), min(\"two_Partitions\".series_0)\n               Group Key: (\"two_Partitions\".\"timeCustom\" % '10'::bigint)\n               ->  Result\n                     Output: (\"two_Partitions\".\"timeCustom\" % '10'::bigint), \"two_Partitions\".series_0\n                     ->  Append\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                                 Output: _hyper_1_1_chunk.\"timeCustom\", _hyper_1_1_chunk.series_0\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                                 Output: _hyper_1_2_chunk.\"timeCustom\", _hyper_1_2_chunk.series_0\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_3_chunk\n                                 Output: _hyper_1_3_chunk.\"timeCustom\", _hyper_1_3_chunk.series_0\n                           ->  Seq Scan on _timescaledb_internal._hyper_1_4_chunk\n                                 Output: _hyper_1_4_chunk.\"timeCustom\", _hyper_1_4_chunk.series_0\n\n"
  },
  {
    "path": "test/expected/symbol_conflict.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- Test for symbol conflicts between the loader module and the\n-- versioned extension module.\n-- This test fails on, e.g. Linux, unless compiled with -fvisibility=hidden\nCREATE OR REPLACE FUNCTION hello_loader() RETURNS TEXT\nAS 'timescaledb', 'loader_hello' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;\nSELECT hello_loader();\n   hello_loader    \n-------------------\n hello from loader\n\nCREATE OR REPLACE FUNCTION hello_timescaledb() RETURNS TEXT\nAS :MODULE_PATHNAME, 'timescaledb_hello' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;\n-- This calls an internal function with a conflicting name in the loader\nSELECT hello_loader();\n   hello_loader    \n-------------------\n hello from loader\n\n-- This calls the identically named internal function in the versioned extension\nSELECT hello_timescaledb();\n   hello_timescaledb    \n------------------------\n hello from timescaledb\n\n"
  },
  {
    "path": "test/expected/tableam.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- Test support for setting table access method on hypertables\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- create a new access method that reuses the heap handler\nCREATE ACCESS METHOD testam TYPE TABLE HANDLER heap_tableam_handler;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nCREATE TABLE testam (time timestamptz, device int, temp float) USING testam;\nSELECT create_hypertable('testam', 'time', 'device', 2);\n  create_hypertable  \n---------------------\n (1,public,testam,t)\n\n-- show that the hypertable is using the 'testam' table access method\nSELECT amname AS hypertable_amname\nFROM pg_class cl, pg_am am\nWHERE cl.oid = 'testam'::regclass\nAND cl.relam = am.oid;\n hypertable_amname \n-------------------\n testam\n\n-- insert data to create a chunk\nINSERT INTO testam VALUES('2020-01-22:11:30', 1, 29.3);\n-- make sure the table access method for a chunk is the same as the\n-- hypertable root\nSELECT amname AS chunk_amname\nFROM pg_class cl, pg_am am, show_chunks('testam') ch\nWHERE cl.oid = ch\nAND cl.relam = am.oid;\n chunk_amname \n--------------\n testam\n\n"
  },
  {
    "path": "test/expected/tableam_alter.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- Test support for setting table access method on hypertables using\n-- ALTER TABLE. It should propagate to the chunks.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE ACCESS METHOD testam TYPE TABLE HANDLER heap_tableam_handler;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nCREATE VIEW chunk_info AS\nSELECT hypertable_name AS hypertable,\n       chunk_name AS chunk,\n       amname\n  FROM timescaledb_information.chunks ch\n  JOIN pg_class cl ON (format('%I.%I', ch.chunk_schema, ch.chunk_name)::regclass = cl.oid)\n  JOIN pg_am am ON (am.oid = cl.relam);\nCREATE TABLE test_table (time timestamptz not null, device int, temp float);\nSELECT create_hypertable('test_table', by_range('time'));\n create_hypertable \n-------------------\n (1,t)\n\nINSERT INTO test_table\nSELECT ts, 10 * random(), 100 * random()\nFROM generate_series('2001-01-01'::timestamp, '2001-02-01', '1d'::interval) as x(ts);\nSELECT cl.relname, amname\n  FROM pg_class cl JOIN pg_am am ON cl.relam = am.oid\n WHERE cl.relname = 'test_table';\n  relname   | amname \n------------+--------\n test_table | heap\n\nSELECT * FROM chunk_info WHERE hypertable = 'test_table';\n hypertable |      chunk       | amname \n------------+------------------+--------\n test_table | _hyper_1_1_chunk | heap\n test_table | _hyper_1_2_chunk | heap\n test_table | _hyper_1_3_chunk | heap\n test_table | _hyper_1_4_chunk | heap\n test_table | _hyper_1_5_chunk | heap\n test_table | _hyper_1_6_chunk | heap\n\n-- Test setting the access method together with other options. This\n-- should not generate an error.\nALTER TABLE test_table\n      SET ACCESS METHOD testam,\n      SET (autovacuum_vacuum_threshold = 100);\n-- Create more chunks. These will use the new access method, but the\n-- old chunks will use the old access method.\nINSERT INTO test_table\nSELECT ts, 10 * random(), 100 * random()\nFROM generate_series('2001-02-01'::timestamp, '2001-03-01', '1d'::interval) as x(ts);\nSELECT cl.relname, amname\n  FROM pg_class cl JOIN pg_am am ON cl.relam = am.oid\n WHERE cl.relname = 'test_table';\n  relname   | amname \n------------+--------\n test_table | testam\n\nSELECT * FROM chunk_info WHERE hypertable = 'test_table';\n hypertable |       chunk       | amname \n------------+-------------------+--------\n test_table | _hyper_1_1_chunk  | heap\n test_table | _hyper_1_2_chunk  | heap\n test_table | _hyper_1_3_chunk  | heap\n test_table | _hyper_1_4_chunk  | heap\n test_table | _hyper_1_5_chunk  | heap\n test_table | _hyper_1_6_chunk  | heap\n test_table | _hyper_1_7_chunk  | testam\n test_table | _hyper_1_8_chunk  | testam\n test_table | _hyper_1_9_chunk  | testam\n test_table | _hyper_1_10_chunk | testam\n\n"
  },
  {
    "path": "test/expected/tableam_alter_defaults.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- Test support for setting table access method on hypertables using\n-- ALTER TABLE for version 17 and later. This is in addition to the\n-- tests in tableam_alter.sql.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE ACCESS METHOD testam TYPE TABLE HANDLER heap_tableam_handler;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nCREATE VIEW chunk_info AS\nSELECT hypertable_name AS hypertable,\n       chunk_name AS chunk,\n       amname\n  FROM timescaledb_information.chunks ch\n  JOIN pg_class cl ON (format('%I.%I', ch.chunk_schema, ch.chunk_name)::regclass = cl.oid)\n  JOIN pg_am am ON (am.oid = cl.relam);\nCREATE TABLE test_table (time timestamptz not null, device int, temp float);\nSELECT cl.relname, amname\n  FROM pg_class cl JOIN pg_am am ON cl.relam = am.oid\n WHERE cl.relname = 'test_table';\n  relname   | amname \n------------+--------\n test_table | heap\n\n-- Check that setting default access method of a normal table works.\nALTER TABLE test_table SET ACCESS METHOD DEFAULT;\n-- Check that changing the access method and then changing it back\n-- works.\nALTER TABLE test_table SET ACCESS METHOD testam;\nALTER TABLE test_table SET ACCESS METHOD DEFAULT;\n-- Check that setting default access method of a hypertable works.\nSELECT create_hypertable('test_table', by_range('time'));\n create_hypertable \n-------------------\n (1,t)\n\nALTER TABLE test_table SET ACCESS METHOD DEFAULT;\nSELECT cl.relname, amname\n  FROM pg_class cl JOIN pg_am am ON cl.relam = am.oid\n WHERE cl.relname = 'test_table';\n  relname   | amname \n------------+--------\n test_table | heap\n\n-- Test setting the access method together with other options. This\n-- should not generate an error.\nALTER TABLE test_table\n      SET ACCESS METHOD testam,\n      SET (autovacuum_vacuum_threshold = 100);\n-- Add some rows to generate a chunk. This should get the access\n-- method of the hypertable.\nINSERT INTO test_table\nSELECT ts, 10 * random(), 100 * random()\nFROM generate_series('2001-01-01'::timestamp, '2001-01-14', '1d'::interval) as x(ts);\nSELECT * FROM chunk_info WHERE hypertable = 'test_table';\n hypertable |      chunk       | amname \n------------+------------------+--------\n test_table | _hyper_1_1_chunk | testam\n test_table | _hyper_1_2_chunk | testam\n test_table | _hyper_1_3_chunk | testam\n\n-- Setting it to the default method after we have set it to a test\n-- access method should work fine also on a hypertable.\nSELECT cl.relname, amname\n  FROM pg_class cl JOIN pg_am am ON cl.relam = am.oid\n WHERE cl.relname = 'test_table';\n  relname   | amname \n------------+--------\n test_table | testam\n\nALTER TABLE test_table SET ACCESS METHOD DEFAULT;\nSELECT cl.relname, amname\n  FROM pg_class cl JOIN pg_am am ON cl.relam = am.oid\n WHERE cl.relname = 'test_table';\n  relname   | amname \n------------+--------\n test_table | heap\n\nSELECT chunk FROM show_chunks('test_table') t(chunk) limit 1 \\gset\nALTER TABLE :chunk SET ACCESS METHOD DEFAULT;\nSELECT * FROM chunk_info WHERE hypertable = 'test_table';\n hypertable |      chunk       | amname \n------------+------------------+--------\n test_table | _hyper_1_1_chunk | heap\n test_table | _hyper_1_2_chunk | testam\n test_table | _hyper_1_3_chunk | testam\n\n"
  },
  {
    "path": "test/expected/tablespace.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\set ON_ERROR_STOP 0\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE VIEW hypertable_tablespaces AS\nSELECT cls.relname AS hypertable,\n       (SELECT spcname FROM pg_tablespace WHERE oid = reltablespace) AS tablespace\n  FROM _timescaledb_catalog.hypertable,\n  LATERAL (SELECT * FROM pg_class WHERE oid = format('%I.%I', schema_name, table_name)::regclass) AS cls\n  ORDER BY hypertable, tablespace;\nGRANT SELECT ON hypertable_tablespaces TO PUBLIC;\n--Test hypertable with tablespace. Tablespaces are cluster-wide, so we\n--attach the test name as prefix to allow tests to be executed in\n--parallel.\nCREATE TABLESPACE tablespace1 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE1_PATH;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n--assigning a tablespace via the main table should work\nCREATE TABLE tspace_2dim(time timestamp, temp float, device text) TABLESPACE tablespace1;\nSELECT create_hypertable('tspace_2dim', 'time', 'device', 2);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n    create_hypertable     \n--------------------------\n (1,public,tspace_2dim,t)\n\nINSERT INTO tspace_2dim VALUES ('2017-01-20T09:00:01', 24.3, 'blue');\n-- Tablespace for tspace_2dim should be set\nSELECT * FROM hypertable_tablespaces WHERE hypertable = 'tspace_2dim';\n hypertable  | tablespace  \n-------------+-------------\n tspace_2dim | tablespace1\n\nSELECT show_tablespaces('tspace_2dim');\n show_tablespaces \n------------------\n tablespace1\n\n--verify that the table chunk has the correct tablespace\nSELECT relname, spcname FROM pg_class c\nINNER JOIN pg_tablespace t ON (c.reltablespace = t.oid)\nINNER JOIN _timescaledb_catalog.chunk ch ON (ch.table_name = c.relname);\n     relname      |   spcname   \n------------------+-------------\n _hyper_1_1_chunk | tablespace1\n\n--check some error conditions\nSELECT attach_tablespace(NULL,NULL);\nERROR:  invalid tablespace name\nSELECT attach_tablespace('tablespace2', NULL);\nERROR:  invalid hypertable\nSELECT attach_tablespace(NULL, 'tspace_2dim');\nERROR:  invalid tablespace name\nSELECT attach_tablespace('none_existing_tablespace', 'tspace_2dim');\nERROR:  tablespace \"none_existing_tablespace\" does not exist\nSELECT attach_tablespace('tablespace2', 'none_existing_table');\nERROR:  relation \"none_existing_table\" does not exist at character 41\nSELECT detach_tablespace(NULL);\nERROR:  invalid tablespace name\nSELECT detach_tablespaces(NULL);\nERROR:  invalid argument\nSELECT show_tablespaces(NULL);\n show_tablespaces \n------------------\n\n--attach another tablespace without first creating it --> should generate error\nSELECT attach_tablespace('tablespace2', 'tspace_2dim');\nERROR:  tablespace \"tablespace2\" does not exist\n--attach the same tablespace twice to same table should also generate error\nSELECT attach_tablespace('tablespace1', 'tspace_2dim');\nERROR:  tablespace \"tablespace1\" is already attached to hypertable \"tspace_2dim\"\n--no error if if_not_attached is given\nSELECT attach_tablespace('tablespace1', 'tspace_2dim', if_not_attached => true);\nNOTICE:  tablespace \"tablespace1\" is already attached to hypertable \"tspace_2dim\", skipping\n attach_tablespace \n-------------------\n \n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n--Tablespaces are cluster-wide, so we attach the test name as prefix\n--to allow tests to be executed in parallel.\nCREATE TABLESPACE tablespace2 OWNER :ROLE_DEFAULT_PERM_USER_2 LOCATION :TEST_TABLESPACE2_PATH;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n--attach without permissions on the table should fail\nSELECT attach_tablespace('tablespace2', 'tspace_2dim');\nERROR:  must be owner of hypertable \"tspace_2dim\"\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n--attach without permissions on the tablespace should also fail\nSELECT attach_tablespace('tablespace2', 'tspace_2dim');\nERROR:  permission denied for tablespace \"tablespace2\" by table owner \"default_perm_user\"\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nGRANT :ROLE_DEFAULT_PERM_USER_2 TO :ROLE_DEFAULT_PERM_USER;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n--should work with permissions on both the table and the tablespace\nSELECT attach_tablespace('tablespace2', 'tspace_2dim');\n attach_tablespace \n-------------------\n \n\nSELECT * FROM _timescaledb_catalog.tablespace;\n id | hypertable_id | tablespace_name \n----+---------------+-----------------\n  1 |             1 | tablespace1\n  2 |             1 | tablespace2\n\nSELECT * FROM show_tablespaces('tspace_2dim');\n show_tablespaces \n------------------\n tablespace1\n tablespace2\n\n--insert into another chunk\nINSERT INTO tspace_2dim VALUES ('2017-01-20T09:00:01', 24.3, 'brown');\nSELECT * FROM test.show_subtables('tspace_2dim');\n                 Child                  | Tablespace  \n----------------------------------------+-------------\n _timescaledb_internal._hyper_1_1_chunk | tablespace1\n _timescaledb_internal._hyper_1_2_chunk | tablespace2\n\n--indexes should inherit the tablespace of their chunk\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n                 Table                  |                               Index                                |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace  \n----------------------------------------+--------------------------------------------------------------------+---------------+------+--------+---------+-----------+-------------\n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_tspace_2dim_time_idx        | {time}        |      | f      | f       | f         | tablespace1\n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_tspace_2dim_device_time_idx | {device,time} |      | f      | f       | f         | tablespace1\n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_tspace_2dim_time_idx        | {time}        |      | f      | f       | f         | tablespace1\n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_tspace_2dim_device_time_idx | {device,time} |      | f      | f       | f         | tablespace1\n\n\\x\nSELECT * FROM timescaledb_information.hypertables\nORDER BY hypertable_schema, hypertable_name;\n-[ RECORD 1 ]----------+----------------------------\nhypertable_schema      | public\nhypertable_name        | tspace_2dim\nowner                  | default_perm_user\nnum_dimensions         | 2\nnum_chunks             | 2\ncompression_enabled    | f\ntablespaces            | {tablespace1,tablespace2}\nprimary_dimension      | time\nprimary_dimension_type | timestamp without time zone\n\nSELECT hypertable_schema,\n       hypertable_name,\n       chunk_schema,\n       chunk_name,\n       chunk_tablespace\nFROM timescaledb_information.chunks\nORDER BY chunk_name;\n-[ RECORD 1 ]-----+----------------------\nhypertable_schema | public\nhypertable_name   | tspace_2dim\nchunk_schema      | _timescaledb_internal\nchunk_name        | _hyper_1_1_chunk\nchunk_tablespace  | tablespace1\n-[ RECORD 2 ]-----+----------------------\nhypertable_schema | public\nhypertable_name   | tspace_2dim\nchunk_schema      | _timescaledb_internal\nchunk_name        | _hyper_1_2_chunk\nchunk_tablespace  | tablespace2\n\n\\x\n--\nSET ROLE :ROLE_DEFAULT_PERM_USER_2;\nCREATE TABLE tspace_1dim(time timestamp, temp float, device text);\nSELECT create_hypertable('tspace_1dim', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n    create_hypertable     \n--------------------------\n (2,public,tspace_1dim,t)\n\n--user doesn't have permission on tablespace1 --> error\nSELECT attach_tablespace('tablespace1', 'tspace_1dim');\nERROR:  permission denied for tablespace \"tablespace1\" by table owner \"default_perm_user_2\"\n--grant permission to tablespace1\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nGRANT CREATE ON TABLESPACE tablespace1 TO :ROLE_DEFAULT_PERM_USER_2;\nSET ROLE :ROLE_DEFAULT_PERM_USER_2;\n--should work fine now. Test SELECT INTO utility statements to ensure\n--internal alter table function call works with event triggers.\nSELECT true INTO attached FROM attach_tablespace('tablespace1', 'tspace_1dim');\nSELECT attach_tablespace('tablespace2', 'tspace_1dim');\n attach_tablespace \n-------------------\n \n\n-- Tablespace for tspace_1dim should be set and attached\nSELECT * FROM hypertable_tablespaces WHERE hypertable = 'tspace_1dim';\n hypertable  | tablespace  \n-------------+-------------\n tspace_1dim | tablespace1\n\nSELECT show_tablespaces('tspace_1dim');\n show_tablespaces \n------------------\n tablespace1\n tablespace2\n\n--trying to revoke permissions while attached should fail\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nREVOKE CREATE ON TABLESPACE tablespace1 FROM :ROLE_DEFAULT_PERM_USER_2;\nERROR:  cannot revoke privilege while tablespace \"tablespace1\" is attached to hypertable \"tspace_1dim\"\nREVOKE ALL ON TABLESPACE tablespace1 FROM :ROLE_DEFAULT_PERM_USER_2;\nERROR:  cannot revoke privilege while tablespace \"tablespace1\" is attached to hypertable \"tspace_1dim\"\nSET ROLE :ROLE_DEFAULT_PERM_USER_2;\nSELECT * FROM _timescaledb_catalog.tablespace;\n id | hypertable_id | tablespace_name \n----+---------------+-----------------\n  1 |             1 | tablespace1\n  2 |             1 | tablespace2\n  3 |             2 | tablespace1\n  4 |             2 | tablespace2\n\nINSERT INTO tspace_1dim VALUES ('2017-01-20T09:00:01', 24.3, 'blue');\nINSERT INTO tspace_1dim VALUES ('2017-03-20T09:00:01', 24.3, 'brown');\nSELECT * FROM test.show_subtablesp('tspace_%');\n   Parent    |                 Child                  | Tablespace  \n-------------+----------------------------------------+-------------\n tspace_2dim | _timescaledb_internal._hyper_1_1_chunk | tablespace1\n tspace_2dim | _timescaledb_internal._hyper_1_2_chunk | tablespace2\n tspace_1dim | _timescaledb_internal._hyper_2_3_chunk | tablespace1\n tspace_1dim | _timescaledb_internal._hyper_2_4_chunk | tablespace2\n\n--indexes should inherit the tablespace of their chunk, unless the\n--parent index has a tablespace set, in which case the chunks'\n--corresponding indexes are pinned to the parent index's\n--tablespace. The parent index can have a tablespace set in two cases:\n--(1) if explicitly set in CREATE INDEX, or (2) if the main table was\n--created with a tablespace, because then default indexes will be\n--created in that tablespace too.\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n                 Table                  |                               Index                                |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace  \n----------------------------------------+--------------------------------------------------------------------+---------------+------+--------+---------+-----------+-------------\n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_tspace_2dim_time_idx        | {time}        |      | f      | f       | f         | tablespace1\n _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_tspace_2dim_device_time_idx | {device,time} |      | f      | f       | f         | tablespace1\n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_tspace_2dim_time_idx        | {time}        |      | f      | f       | f         | tablespace1\n _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_tspace_2dim_device_time_idx | {device,time} |      | f      | f       | f         | tablespace1\n _timescaledb_internal._hyper_2_3_chunk | _timescaledb_internal._hyper_2_3_chunk_tspace_1dim_time_idx        | {time}        |      | f      | f       | f         | tablespace2\n _timescaledb_internal._hyper_2_4_chunk | _timescaledb_internal._hyper_2_4_chunk_tspace_1dim_time_idx        | {time}        |      | f      | f       | f         | tablespace1\n\n--detach tablespace1 from tspace_2dim should fail due to lack of permissions\nSELECT detach_tablespace('tablespace1', 'tspace_2dim');\nERROR:  must be owner of hypertable \"tspace_2dim\"\n--detach tablespace1 from all tables. Should only detach from\n--'tspace_1dim' (1 tablespace) due to lack of permissions\nSELECT * FROM hypertable_tablespaces;\n hypertable  | tablespace  \n-------------+-------------\n tspace_1dim | tablespace1\n tspace_2dim | tablespace1\n\nSELECT * INTO detached FROM detach_tablespace('tablespace1');\nNOTICE:  tablespace \"tablespace1\" remains attached to 1 hypertable(s) due to lack of permissions\nSELECT * FROM detached;\n detach_tablespace \n-------------------\n                 1\n\nSELECT * FROM _timescaledb_catalog.tablespace;\n id | hypertable_id | tablespace_name \n----+---------------+-----------------\n  1 |             1 | tablespace1\n  2 |             1 | tablespace2\n  4 |             2 | tablespace2\n\nSELECT * FROM show_tablespaces('tspace_1dim');\n show_tablespaces \n------------------\n tablespace2\n\nSELECT * FROM show_tablespaces('tspace_2dim');\n show_tablespaces \n------------------\n tablespace1\n tablespace2\n\nSELECT * FROM hypertable_tablespaces;\n hypertable  | tablespace  \n-------------+-------------\n tspace_1dim | \n tspace_2dim | tablespace1\n\n--it should now be possible to revoke permissions on tablespace1\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nREVOKE CREATE ON TABLESPACE tablespace1 FROM :ROLE_DEFAULT_PERM_USER_2;\nSET ROLE :ROLE_DEFAULT_PERM_USER_2;\n--detach the other tablespace\nSELECT detach_tablespace('tablespace2', 'tspace_1dim');\n detach_tablespace \n-------------------\n                 1\n\nSELECT * FROM _timescaledb_catalog.tablespace;\n id | hypertable_id | tablespace_name \n----+---------------+-----------------\n  1 |             1 | tablespace1\n  2 |             1 | tablespace2\n\nSELECT * FROM show_tablespaces('tspace_1dim');\n show_tablespaces \n------------------\n\nSELECT * FROM show_tablespaces('tspace_2dim');\n show_tablespaces \n------------------\n tablespace1\n tablespace2\n\nSELECT * FROM hypertable_tablespaces;\n hypertable  | tablespace  \n-------------+-------------\n tspace_1dim | \n tspace_2dim | tablespace1\n\n--detaching tablespace2 from a table without permissions should fail\nSELECT detach_tablespace('tablespace2', 'tspace_2dim');\nERROR:  must be owner of hypertable \"tspace_2dim\"\nSELECT detach_tablespaces('tspace_2dim');\nERROR:  must be owner of hypertable \"tspace_2dim\"\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- PERM_USER_2 owns tablespace2, and PERM_USER owns the table\n-- 'tspace_2dim', which has tablespace2 attached. Revoking PERM_USER_2\n-- FROM PERM_USER should therefore fail\nREVOKE :ROLE_DEFAULT_PERM_USER_2 FROM :ROLE_DEFAULT_PERM_USER;\nERROR:  cannot revoke privilege while tablespace \"tablespace2\" is attached to hypertable \"tspace_2dim\"\nSET ROLE :ROLE_DEFAULT_PERM_USER_2;\n--set other user should make detach work\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT * INTO detached_all FROM detach_tablespaces('tspace_2dim');\nSELECT * FROM detached_all;\n detach_tablespaces \n--------------------\n                  2\n\nSELECT * FROM _timescaledb_catalog.tablespace;\n id | hypertable_id | tablespace_name \n----+---------------+-----------------\n\nSELECT * FROM show_tablespaces('tspace_1dim');\n show_tablespaces \n------------------\n\nSELECT * FROM show_tablespaces('tspace_2dim');\n show_tablespaces \n------------------\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- It should now be possible to revoke PERM_USER_2 from PERM_USER\n-- since tablespace2 is no longer attched to tspace_2dim\nREVOKE :ROLE_DEFAULT_PERM_USER_2 FROM :ROLE_DEFAULT_PERM_USER;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--detaching twice should fail\nSELECT detach_tablespace('tablespace2', 'tspace_2dim');\nERROR:  tablespace \"tablespace2\" is not attached to hypertable \"tspace_2dim\"\n--adding if_attached should only generate notice\nSELECT detach_tablespace('tablespace2', 'tspace_2dim', if_attached => true);\nNOTICE:  tablespace \"tablespace2\" is not attached to hypertable \"tspace_2dim\", skipping\n detach_tablespace \n-------------------\n                 0\n\n--attach tablespaces again to verify that tablespaces are cleaned up\n--when tables are dropped\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT attach_tablespace('tablespace2', 'tspace_1dim');\n attach_tablespace \n-------------------\n \n\nSELECT attach_tablespace('tablespace1', 'tspace_2dim');\n attach_tablespace \n-------------------\n \n\nSELECT * FROM _timescaledb_catalog.tablespace;\n id | hypertable_id | tablespace_name \n----+---------------+-----------------\n  5 |             2 | tablespace2\n  6 |             1 | tablespace1\n\nDROP TABLE tspace_1dim;\nSELECT * FROM _timescaledb_catalog.tablespace;\n id | hypertable_id | tablespace_name \n----+---------------+-----------------\n  6 |             1 | tablespace1\n\nDROP TABLE tspace_2dim;\nSELECT * FROM _timescaledb_catalog.tablespace;\n id | hypertable_id | tablespace_name \n----+---------------+-----------------\n\n-- Create two tables and attach multiple tablespaces to them. Verify\n-- that dropping a tablespace from multiple tables work as expected.\nCREATE TABLE tbl_1(time timestamp, temp float, device text);\nSELECT create_hypertable('tbl_1', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n create_hypertable  \n--------------------\n (3,public,tbl_1,t)\n\nCREATE TABLE tbl_2(time timestamp, temp float, device text);\nSELECT create_hypertable('tbl_2', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n create_hypertable  \n--------------------\n (4,public,tbl_2,t)\n\nCREATE TABLE tbl_3(time timestamp, temp float, device text);\nSELECT create_hypertable('tbl_3', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n create_hypertable  \n--------------------\n (5,public,tbl_3,t)\n\nSELECT * FROM hypertable_tablespaces;\n hypertable | tablespace \n------------+------------\n tbl_1      | \n tbl_2      | \n tbl_3      | \n\nSELECT * FROM show_tablespaces('tbl_1');\n show_tablespaces \n------------------\n\nSELECT * FROM show_tablespaces('tbl_2');\n show_tablespaces \n------------------\n\nSELECT * FROM show_tablespaces('tbl_3');\n show_tablespaces \n------------------\n\nSELECT attach_tablespace('tablespace1', 'tbl_1');\n attach_tablespace \n-------------------\n \n\nSELECT attach_tablespace('tablespace2', 'tbl_1');\n attach_tablespace \n-------------------\n \n\nSELECT attach_tablespace('tablespace2', 'tbl_2');\n attach_tablespace \n-------------------\n \n\nSELECT attach_tablespace('tablespace2', 'tbl_3');\n attach_tablespace \n-------------------\n \n\nSELECT * FROM hypertable_tablespaces;\n hypertable | tablespace  \n------------+-------------\n tbl_1      | tablespace1\n tbl_2      | tablespace2\n tbl_3      | tablespace2\n\nSELECT * FROM show_tablespaces('tbl_1');\n show_tablespaces \n------------------\n tablespace1\n tablespace2\n\nSELECT * FROM show_tablespaces('tbl_2');\n show_tablespaces \n------------------\n tablespace2\n\nSELECT * FROM show_tablespaces('tbl_3');\n show_tablespaces \n------------------\n tablespace2\n\nSELECT detach_tablespace('tablespace2');\n detach_tablespace \n-------------------\n                 3\n\nSELECT * FROM hypertable_tablespaces;\n hypertable | tablespace  \n------------+-------------\n tbl_1      | tablespace1\n tbl_2      | \n tbl_3      | \n\nSELECT * FROM show_tablespaces('tbl_1');\n show_tablespaces \n------------------\n tablespace1\n\nSELECT * FROM show_tablespaces('tbl_2');\n show_tablespaces \n------------------\n\nSELECT * FROM show_tablespaces('tbl_3');\n show_tablespaces \n------------------\n\nDROP TABLE tbl_1;\nDROP TABLE tbl_2;\nDROP TABLE tbl_3;\n-- verify that one cannot DROP a tablespace while it is attached to a\n-- hypertable\nCREATE TABLE tbl_1(time timestamp, temp float, device text);\nSELECT create_hypertable('tbl_1', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n create_hypertable  \n--------------------\n (6,public,tbl_1,t)\n\nSELECT attach_tablespace('tablespace1', 'tbl_1');\n attach_tablespace \n-------------------\n \n\nSELECT * FROM show_tablespaces('tbl_1');\n show_tablespaces \n------------------\n tablespace1\n\nDROP TABLESPACE tablespace1;\nERROR:  tablespace \"tablespace1\" is still attached to 1 hypertables\n--after detaching we should now be able to drop the tablespace\nSELECT detach_tablespace('tablespace1', 'tbl_1');\n detach_tablespace \n-------------------\n                 1\n\nDROP TABLESPACE tablespace1;\nDROP TABLESPACE tablespace2;\n"
  },
  {
    "path": "test/expected/telemetry.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_status(int) RETURNS JSONB\n    AS :MODULE_PATHNAME, 'ts_test_status' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_status_ssl(int) RETURNS JSONB\n    AS :MODULE_PATHNAME, 'ts_test_status_ssl' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_status_mock(text) RETURNS JSONB\n    AS :MODULE_PATHNAME, 'ts_test_status_mock' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_validate_server_version(response text)\n    RETURNS TEXT\n    AS :MODULE_PATHNAME, 'ts_test_validate_server_version' LANGUAGE C IMMUTABLE PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_telemetry_main_conn(text, text)\nRETURNS BOOLEAN AS :MODULE_PATHNAME, 'ts_test_telemetry_main_conn' LANGUAGE C IMMUTABLE PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_telemetry(host text = NULL, servname text = NULL, port int = NULL) RETURNS JSONB AS :MODULE_PATHNAME, 'ts_test_telemetry' LANGUAGE C IMMUTABLE PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_privacy() RETURNS BOOLEAN\n    AS :MODULE_PATHNAME, 'ts_test_privacy' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION test_check_version_response(response text)\n       RETURNS VOID\n       AS :MODULE_PATHNAME, 'ts_test_check_version_response'\n       LANGUAGE C\n       IMMUTABLE STRICT PARALLEL SAFE;\nINSERT INTO _timescaledb_catalog.metadata VALUES ('foo','bar',TRUE);\nINSERT INTO _timescaledb_catalog.metadata VALUES ('bar','baz',FALSE);\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT _timescaledb_internal.test_status_ssl(200);\n test_status_ssl \n-----------------\n {\"status\": 200}\n\nSELECT _timescaledb_internal.test_status_ssl(201);\n test_status_ssl \n-----------------\n {\"status\": 201}\n\n\\set ON_ERROR_STOP 0\nSELECT _timescaledb_internal.test_status_ssl(304);\nERROR:  endpoint sent back unexpected HTTP status: 304\nSELECT _timescaledb_internal.test_status_ssl(400);\nERROR:  endpoint sent back unexpected HTTP status: 400\nSELECT _timescaledb_internal.test_status_ssl(401);\nERROR:  endpoint sent back unexpected HTTP status: 401\nSELECT _timescaledb_internal.test_status_ssl(404);\nERROR:  endpoint sent back unexpected HTTP status: 404\nSELECT _timescaledb_internal.test_status_ssl(500);\nERROR:  endpoint sent back unexpected HTTP status: 500\nSELECT _timescaledb_internal.test_status_ssl(503);\nERROR:  endpoint sent back unexpected HTTP status: 503\n\\set ON_ERROR_STOP 1\n-- This function runs the test 5 times, because each time the internal C function is choosing a random length to send from the server on each socket read. We hit many cases this way.\nCREATE OR REPLACE FUNCTION mocker(TEXT) RETURNS SETOF TEXT AS\n$BODY$\nDECLARE\nr TEXT;\nBEGIN\nFOR i in 1..5 LOOP\nSELECT _timescaledb_internal.test_status_mock($1) INTO r;\nRETURN NEXT r;\nEND LOOP;\nRETURN;\nEND\n$BODY$\nLANGUAGE 'plpgsql';\nselect * from mocker(\n        E'HTTP/1.1 200 OK\\r\\n'\n        'Content-Type: application/json; charset=utf-8\\r\\n'\n        'Date: Thu, 12 Jul 2018 18:33:04 GMT\\r\\n'\n        'ETag: W/\\\"e-upYEWCL+q6R/++2nWHz5b76hBgo\\\"\\r\\n'\n        'Server: nginx\\r\\n'\n        'Vary: Accept-Encoding\\r\\n'\n        'Content-Length: 14\\r\\n'\n        'Connection: Close\\r\\n\\r\\n'\n        '{\\\"status\\\":200}');\n     mocker      \n-----------------\n {\"status\": 200}\n {\"status\": 200}\n {\"status\": 200}\n {\"status\": 200}\n {\"status\": 200}\n\nselect * from mocker(\n        E'HTTP/1.1 201 OK\\r\\n'\n        'Content-Type: application/json; charset=utf-8\\r\\n'\n        'Vary: Accept-Encoding\\r\\n'\n        'Content-Length: 14\\r\\n'\n        'Connection: Close\\r\\n\\r\\n'\n        '{\\\"status\\\":201}');\n     mocker      \n-----------------\n {\"status\": 201}\n {\"status\": 201}\n {\"status\": 201}\n {\"status\": 201}\n {\"status\": 201}\n\n\\set ON_ERROR_STOP 0\n\\set test_string 'HTTP/1.1 404 Not Found\\r\\nContent-Length: 14\\r\\nConnection: Close\\r\\n\\r\\n{\\\"status\\\":404}';\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nERROR:  endpoint sent back unexpected HTTP status: 404\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nERROR:  endpoint sent back unexpected HTTP status: 404\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nERROR:  endpoint sent back unexpected HTTP status: 404\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nERROR:  endpoint sent back unexpected HTTP status: 404\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nERROR:  endpoint sent back unexpected HTTP status: 404\n\\set test_string 'Content-Length: 14\\r\\nConnection: Close\\r\\n\\r\\n{\\\"status\\\":404}';\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nERROR:  could not parse HTTP response\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nERROR:  could not parse HTTP response\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nERROR:  could not parse HTTP response\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nERROR:  could not parse HTTP response\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nERROR:  could not parse HTTP response\n\\set test_string 'HTTP/1.1 404 Not Found\\r\\nContent-Length: 14\\r\\nConnection: Close\\r\\n{\\\"status\\\":404}';\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nERROR:  could not parse HTTP response\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nERROR:  could not parse HTTP response\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nERROR:  could not parse HTTP response\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nERROR:  could not parse HTTP response\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nERROR:  could not parse HTTP response\n\\set ON_ERROR_STOP 1\n-- Test parsing version response\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"status\": \"200\", \"current_timescaledb_version\": \"10.1.0\"}');\n test_validate_server_version \n------------------------------\n 10.1.0\n\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"10.1\"}');\n test_validate_server_version \n------------------------------\n 10.1\n\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"10\"}');\n test_validate_server_version \n------------------------------\n 10\n\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"9.2.0\"}');\n test_validate_server_version \n------------------------------\n 9.2.0\n\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"9.1.2\"}');\n test_validate_server_version \n------------------------------\n 9.1.2\n\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"1.0.0\"}');\n test_validate_server_version \n------------------------------\n 1.0.0\n\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"1.0.0-rc1\"}');\n test_validate_server_version \n------------------------------\n 1.0.0-rc1\n\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"1.0.0-rc2\"}');\n test_validate_server_version \n------------------------------\n 1.0.0-rc2\n\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"1.0.0-rc1\"}');\n test_validate_server_version \n------------------------------\n 1.0.0-rc1\n\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"1.0.0-alpha\"}');\n test_validate_server_version \n------------------------------\n 1.0.0-alpha\n\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"123456789\"}');\n test_validate_server_version \n------------------------------\n 123456789\n\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"!@#$%\"}');\n test_validate_server_version \n------------------------------\n \n\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"\"}');\n test_validate_server_version \n------------------------------\n \n\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \" 10 \"}');\n test_validate_server_version \n------------------------------\n \n\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"a\"}');\n test_validate_server_version \n------------------------------\n a\n\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"a.b.c\"}');\n test_validate_server_version \n------------------------------\n a.b.c\n\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"10.1.1a\"}');\n test_validate_server_version \n------------------------------\n 10.1.1a\n\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"10.1.1+rc1\"}');\n test_validate_server_version \n------------------------------\n \n\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"10.1.1.1\"}');\n test_validate_server_version \n------------------------------\n 10.1.1.1\n\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"1.0.0-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz\"}');\n test_validate_server_version \n------------------------------\n \n\n----------------------------------------------------------------\n-- Test well-formed response and valid versions\nSELECT test_check_version_response('{\"current_timescaledb_version\": \"1.6.1\", \"is_up_to_date\": true}');\nNOTICE:  the \"timescaledb\" extension is up-to-date\n test_check_version_response \n-----------------------------\n \n\nSELECT test_check_version_response('{\"current_timescaledb_version\": \"1.6.1\", \"is_up_to_date\": false}');\n test_check_version_response \n-----------------------------\n \n\nSELECT test_check_version_response('{\"current_timescaledb_version\": \"10.1\", \"is_up_to_date\": false}');\n test_check_version_response \n-----------------------------\n \n\nSELECT test_check_version_response('{\"current_timescaledb_version\": \"10.1.1-rc1\", \"is_up_to_date\": false}');\n test_check_version_response \n-----------------------------\n \n\n----------------------------------------------------------------\n-- Test well-formed response but invalid versions\nSELECT test_check_version_response('{\"current_timescaledb_version\": \"1.0.0-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz\", \"is_up_to_date\": false}');\nNOTICE:  server did not return a valid TimescaleDB version: version string is too long\n test_check_version_response \n-----------------------------\n \n\nSELECT test_check_version_response('{\"current_timescaledb_version\": \"10.1.1+rc1\", \"is_up_to_date\": false}');\nNOTICE:  server did not return a valid TimescaleDB version: version string has invalid characters\n test_check_version_response \n-----------------------------\n \n\nSELECT test_check_version_response('{\"current_timescaledb_version\": \"@10.1.1\", \"is_up_to_date\": false}');\nNOTICE:  server did not return a valid TimescaleDB version: version string has invalid characters\n test_check_version_response \n-----------------------------\n \n\nSELECT test_check_version_response('{\"current_timescaledb_version\": \"10.1.1@\", \"is_up_to_date\": false}');\nNOTICE:  server did not return a valid TimescaleDB version: version string has invalid characters\n test_check_version_response \n-----------------------------\n \n\n----------------------------------------------------------------\n-- Test malformed responses\n\\set ON_ERROR_STOP 0\n-- Empty response\nSELECT test_check_version_response('{}');\nERROR:  malformed telemetry response body\n-- Field \"is_up_to_date\" missing\nSELECT test_check_version_response('{\"current_timescaledb_version\": \"1.6.1\"}');\nERROR:  malformed telemetry response body\n-- Field \"current_timescaledb_version\" is missing\nSELECT test_check_version_response('{\"is_up_to_date\": false}');\nERROR:  malformed telemetry response body\n\\set ON_ERROR_STOP 1\nSET timescaledb.last_tune_time = '2024-01-01 00:00:00+00';\nSET timescaledb.last_tune_version = '1.2.3';\nSET timescaledb.telemetry_level=basic;\n-- Connect to a bogus host and path to test error handling in telemetry_main()\nSELECT _timescaledb_internal.test_telemetry_main_conn('noservice.timescale.com', 'path');\nNOTICE:  telemetry could not connect to \"noservice.timescale.com\"\n test_telemetry_main_conn \n--------------------------\n f\n\n-- Test telemetry report contents\nSET timescaledb.telemetry_level=basic;\nSELECT * FROM jsonb_object_keys(get_telemetry_report()) AS key\nWHERE key != 'os_name_pretty';\n                key                 \n------------------------------------\n db_uuid\n license\n os_name\n relations\n os_release\n os_version\n data_volume\n db_metadata\n replication\n build_os_name\n access_methods\n functions_used\n install_method\n installed_time\n last_tuned_time\n build_os_version\n exported_db_uuid\n instance_metadata\n stats_by_job_type\n telemetry_version\n build_architecture\n last_tuned_version\n postgresql_version\n related_extensions\n db_telemetry_events\n timescaledb_version\n errors_by_sqlerrcode\n num_reorder_policies\n num_retention_policies\n num_compression_policies\n num_user_defined_actions\n num_reorder_policies_fixed\n build_architecture_bit_size\n num_continuous_aggs_policies\n num_retention_policies_fixed\n num_compression_policies_fixed\n num_user_defined_actions_fixed\n num_continuous_aggs_policies_fixed\n\nCREATE MATERIALIZED VIEW telemetry_report AS\nSELECT t FROM get_telemetry_report() t;\n-- check telemetry picks up flagged content from metadata\nSELECT t -> 'db_metadata' FROM telemetry_report;\n    ?column?    \n----------------\n {\"foo\": \"bar\"}\n\n-- check timescaledb_telemetry.cloud\nSELECT t -> 'instance_metadata' FROM telemetry_report;\n    ?column?     \n-----------------\n {\"cloud\": \"ci\"}\n\n-- Check access methods\nSELECT t->'access_methods' ? 'btree',\n       t->'access_methods' ? 'heap',\n       CAST(t->'access_methods'->'btree'->'pages' AS int) > 0,\n       CAST(t->'access_methods'->'btree'->'instances' AS int) > 0\n  FROM telemetry_report;\n ?column? | ?column? | ?column? | ?column? \n----------+----------+----------+----------\n t        | t        | t        | t\n\nWITH t AS (\n\t SELECT t -> 'relations' AS rels\n\t FROM telemetry_report\n)\nSELECT rels -> 'hypertables' -> 'num_relations' AS num_hypertables,\n\t   rels -> 'continuous_aggregates' -> 'num_relations' AS num_caggs\nFROM t;\n num_hypertables | num_caggs \n-----------------+-----------\n 0               | 0\n\nCREATE TABLE device_readings (\n      observation_time  TIMESTAMPTZ       NOT NULL\n);\nSELECT table_name FROM create_hypertable('device_readings', 'observation_time');\n   table_name    \n-----------------\n device_readings\n\nREFRESH MATERIALIZED VIEW telemetry_report;\nWITH t AS (\n\t SELECT t -> 'relations' AS rels\n\t FROM telemetry_report\n)\nSELECT rels -> 'hypertables' -> 'num_relations' AS num_hypertables,\n\t   rels -> 'continuous_aggregates' -> 'num_relations' AS num_caggs\nFROM t;\n num_hypertables | num_caggs \n-----------------+-----------\n 1               | 0\n\nset datestyle to iso;\n-- check that installed_time formatting in telemetry report does not depend on local date settings\nSELECT t -> 'installed_time' AS installed_time FROM telemetry_report\n\\gset\nset datestyle to sql;\nSELECT t-> 'installed_time' AS installed_time2 FROM telemetry_report\n\\gset\nSELECT :'installed_time' = :'installed_time2' AS equal, length(:'installed_time'), length(:'installed_time2');\n equal | length | length \n-------+--------+--------\n t     |     24 |     24\n\nRESET datestyle;\n-- test function call telemetry\nCREATE FUNCTION not_visible_in_telemetry() RETURNS INT AS $$\n    SELECT 1;\n$$ LANGUAGE SQL;\n-- drain old function call telemetry so we have fixed out put;\nSELECT FROM get_telemetry_report();\n--\n\n-- call some arbirary functions\nSELECT 1 + 1, not_visible_in_telemetry(), 1 + 1, abs(-1), not_visible_in_telemetry()\nWHERE 1 + 1 = 2;\n ?column? | not_visible_in_telemetry | ?column? | abs | not_visible_in_telemetry \n----------+--------------------------+----------+-----+--------------------------\n        2 |                        1 |        2 |   1 |                        1\n\n-- call some aggregates\nSELECT min(not_visible_in_telemetry()), sum(not_visible_in_telemetry());\n min | sum \n-----+-----\n   1 |   1\n\n-- check that we can record from a prepared statement\nPREPARE record_from_prepared AS SELECT 1 - 1;\n-- execute 10 times to make sure turning it into generic plan works\nEXECUTE record_from_prepared;\n ?column? \n----------\n        0\n\nEXECUTE record_from_prepared;\n ?column? \n----------\n        0\n\nEXECUTE record_from_prepared;\n ?column? \n----------\n        0\n\nEXECUTE record_from_prepared;\n ?column? \n----------\n        0\n\nEXECUTE record_from_prepared;\n ?column? \n----------\n        0\n\nEXECUTE record_from_prepared;\n ?column? \n----------\n        0\n\nEXECUTE record_from_prepared;\n ?column? \n----------\n        0\n\nEXECUTE record_from_prepared;\n ?column? \n----------\n        0\n\nEXECUTE record_from_prepared;\n ?column? \n----------\n        0\n\nEXECUTE record_from_prepared;\n ?column? \n----------\n        0\n\nDEALLOCATE record_from_prepared;\nSELECT get_telemetry_report()->'functions_used';\n                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         ?column?                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          \n---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n {\"pg_catalog.count()\": 1, \"pg_catalog.sum(bigint)\": 4, \"pg_catalog.abs(integer)\": 1, \"pg_catalog.max(integer)\": 2, \"pg_catalog.min(integer)\": 1, \"pg_catalog.sum(integer)\": 1, \"pg_catalog.int8(numeric)\": 4, \"pg_catalog.sum(interval)\": 2, \"pg_catalog.current_database()\": 1, \"public.get_telemetry_report()\": 1, \"pg_catalog.text(pg_catalog.name)\": 1, \"pg_catalog.int4eq(integer,integer)\": 3, \"pg_catalog.int4mi(integer,integer)\": 11, \"pg_catalog.int4pl(integer,integer)\": 3, \"pg_catalog.concat(pg_catalog.\\\"any\\\")\": 3, \"pg_catalog.pg_get_userbyid(pg_catalog.oid)\": 1, \"pg_catalog.nameeq(pg_catalog.name,pg_catalog.name)\": 2, \"pg_catalog.texteq(pg_catalog.text,pg_catalog.text)\": 1, \"pg_catalog.nameregexeq(pg_catalog.name,pg_catalog.text)\": 1, \"pg_catalog.textregexeq(pg_catalog.text,pg_catalog.text)\": 1, \"pg_catalog.jsonb_object_agg(pg_catalog.\\\"any\\\",pg_catalog.\\\"any\\\")\": 1, \"pg_catalog.jsonb_object_field(pg_catalog.jsonb,pg_catalog.text)\": 16, \"pg_catalog.jsonb_object_field_text(pg_catalog.jsonb,pg_catalog.text)\": 15, \"pg_catalog.pg_has_role(pg_catalog.name,pg_catalog.oid,pg_catalog.text)\": 1, \"pg_catalog.pg_has_role(pg_catalog.name,pg_catalog.name,pg_catalog.text)\": 1}\n\n-- check the report again to see if resetting works\nSELECT get_telemetry_report()->'functions_used';\n                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   ?column?                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   \n----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n {\"pg_catalog.count()\": 1, \"pg_catalog.sum(bigint)\": 4, \"pg_catalog.max(integer)\": 2, \"pg_catalog.int8(numeric)\": 4, \"pg_catalog.sum(interval)\": 2, \"pg_catalog.current_database()\": 1, \"public.get_telemetry_report()\": 1, \"pg_catalog.text(pg_catalog.name)\": 1, \"pg_catalog.int4eq(integer,integer)\": 2, \"pg_catalog.concat(pg_catalog.\\\"any\\\")\": 3, \"pg_catalog.pg_get_userbyid(pg_catalog.oid)\": 1, \"pg_catalog.nameeq(pg_catalog.name,pg_catalog.name)\": 2, \"pg_catalog.texteq(pg_catalog.text,pg_catalog.text)\": 1, \"pg_catalog.nameregexeq(pg_catalog.name,pg_catalog.text)\": 1, \"pg_catalog.textregexeq(pg_catalog.text,pg_catalog.text)\": 1, \"pg_catalog.jsonb_object_agg(pg_catalog.\\\"any\\\",pg_catalog.\\\"any\\\")\": 1, \"pg_catalog.jsonb_object_field(pg_catalog.jsonb,pg_catalog.text)\": 16, \"pg_catalog.jsonb_object_field_text(pg_catalog.jsonb,pg_catalog.text)\": 15, \"pg_catalog.pg_has_role(pg_catalog.name,pg_catalog.oid,pg_catalog.text)\": 1, \"pg_catalog.pg_has_role(pg_catalog.name,pg_catalog.name,pg_catalog.text)\": 1}\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE _timescaledb_catalog.metadata;\nSET timescaledb.telemetry_level=off;\n-- returns false which means telemetry got canceled\nSELECT * FROM _timescaledb_internal.test_privacy();\n test_privacy \n--------------\n f\n\nRESET timescaledb.telemetry_level;\n-- returns false which means telemetry got canceled\nSELECT * FROM _timescaledb_internal.test_privacy();\n test_privacy \n--------------\n f\n\n-- To make sure nothing was sent, we check the UUID table to make sure no exported UUID row was created\nSELECT key from _timescaledb_catalog.metadata;\n key \n-----\n\n\\set ON_ERROR_STOP 0\n-- test that the telemetry gathering code doesn't break nonexistent statements\nEXECUTE noexistent_statement;\nERROR:  prepared statement \"noexistent_statement\" does not exist\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- Insert some data into the telemetry event table\nINSERT INTO _timescaledb_catalog.telemetry_event(tag, body) VALUES\n    ('ummagumma', '{\"title\": \"Careful with that Axe Eugene!\"}'),\n    ('kaboom', '{\"title\": \"Where is that kaboom?\"}');\n-- Check that it is present in the telemetry report\nSELECT * FROM jsonb_to_recordset(get_telemetry_report()->'db_telemetry_events') AS x(tag name, body text);\n    tag    |                    body                    \n-----------+--------------------------------------------\n ummagumma | {\"title\": \"Careful with that Axe Eugene!\"}\n kaboom    | {\"title\": \"Where is that kaboom?\"}\n\n"
  },
  {
    "path": "test/expected/test_tss_callbacks.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION test.setup_tss_hook_v0() RETURNS VOID\n    AS :MODULE_PATHNAME, 'ts_setup_tss_hook_v0' LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION test.setup_tss_hook_v1() RETURNS VOID\n    AS :MODULE_PATHNAME, 'ts_setup_tss_hook_v1' LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION test.teardown_tss_hook_v1() RETURNS VOID\n    AS :MODULE_PATHNAME, 'ts_teardown_tss_hook_v1' LANGUAGE C VOLATILE;\nSELECT test.setup_tss_hook_v1();\n setup_tss_hook_v1 \n-------------------\n \n\nCREATE TABLE copy_test (\n    \"time\" timestamptz NOT NULL,\n    \"value\" double precision NOT NULL\n);\nSELECT create_hypertable('copy_test', 'time');\n   create_hypertable    \n------------------------\n (1,public,copy_test,t)\n\n-- We should se a mock message\nCOPY copy_test FROM STDIN DELIMITER ',';\nNOTICE:  test_tss_callbacks (mock): query=COPY copy_test FROM STDIN DELIMITER ',';, len=39, id=0, rows=2\nSELECT test.teardown_tss_hook_v1();\n teardown_tss_hook_v1 \n----------------------\n \n\n-- Without the hook registered we cannot see the mock message\nCOPY copy_test FROM STDIN DELIMITER ',';\n-- Test for mismatch version\nSELECT test.setup_tss_hook_v0();\n setup_tss_hook_v0 \n-------------------\n \n\n-- Warning because the mismatch versions\nCOPY copy_test FROM STDIN DELIMITER ',';\nWARNING:  version mismatch between timescaledb and ts_stat_statements callbacks\nWARNING:  version mismatch between timescaledb and ts_stat_statements callbacks\n"
  },
  {
    "path": "test/expected/test_utils.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION test.condition() RETURNS VOID\n    AS :MODULE_PATHNAME, 'ts_test_utils_condition' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION test.int64_eq() RETURNS VOID\n    AS :MODULE_PATHNAME, 'ts_test_utils_int64_eq' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION test.ptr_eq() RETURNS VOID\n    AS :MODULE_PATHNAME, 'ts_test_utils_ptr_eq' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION test.double_eq() RETURNS VOID\n    AS :MODULE_PATHNAME, 'ts_test_utils_double_eq' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n-- We're testing that the test utils work and generate errors on\n-- failing conditions\n\\set ON_ERROR_STOP 0\nSELECT test.condition();\nWARNING:  TestFailure in ts_test_utils_condition() at line:43\nERROR:  TestFailure | (true_value == false_value)\nSELECT test.int64_eq();\nWARNING:  TestFailure in ts_test_utils_int64_eq() at line:53\nERROR:  TestFailure | (big == small) [32532978 == 3242234]\nSELECT test.ptr_eq();\nWARNING:  TestFailure in ts_test_utils_ptr_eq() at line:67\nERROR:  TestFailure | (true_ptr == false_ptr)\nSELECT test.double_eq();\nWARNING:  TestFailure in ts_test_utils_double_eq() at line:78\nERROR:  TestFailure | (big_double == small_double) [923423478.324200 == 324.300000]\n\\set ON_ERROR_STOP 1\n-- Test debug points\n--\n\\set ECHO all\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- debug point already enabled\nSELECT debug_waitpoint_enable('test_debug_point');\n debug_waitpoint_enable \n------------------------\n \n\n\\set ON_ERROR_STOP 0\nSELECT debug_waitpoint_enable('test_debug_point');\nERROR:  debug point \"test_debug_point\" already enabled\n\\set ON_ERROR_STOP 1\nSELECT debug_waitpoint_release('test_debug_point');\n debug_waitpoint_release \n-------------------------\n \n\n-- debug point not enabled\n\\set ON_ERROR_STOP 0\nSELECT debug_waitpoint_release('test_debug_point');\nWARNING:  you don't own a lock of type ExclusiveLock\nERROR:  cannot release debug point \"test_debug_point\"\n\\set ON_ERROR_STOP 1\n-- error injections\n--\nCREATE OR REPLACE FUNCTION test_error_injection(TEXT)\nRETURNS VOID\nAS :MODULE_PATHNAME, 'ts_test_error_injection'\nLANGUAGE C VOLATILE STRICT;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT test_error_injection('test_error');\n test_error_injection \n----------------------\n \n\nSELECT debug_waitpoint_enable('test_error');\n debug_waitpoint_enable \n------------------------\n \n\n\\set ON_ERROR_STOP 0\nSELECT test_error_injection('test_error');\nERROR:  error injected at debug point 'test_error'\n\\set ON_ERROR_STOP 1\nSELECT debug_waitpoint_release('test_error');\n debug_waitpoint_release \n-------------------------\n \n\nSELECT test_error_injection('test_error');\n test_error_injection \n----------------------\n \n\n-- Test Scanner\nRESET ROLE;\nCREATE OR REPLACE FUNCTION test.scanner() RETURNS VOID\n    AS :MODULE_PATHNAME, 'ts_test_scanner' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n-- Create two chunks to scan in the test\nCREATE TABLE hyper (time timestamptz, temp float);\nSELECT create_hypertable('hyper', 'time');\n create_hypertable  \n--------------------\n (1,public,hyper,t)\n\nINSERT INTO hyper VALUES ('2021-01-01', 1.0), ('2022-01-01', 2.0);\nSELECT test.scanner();\nNOTICE:  1. Scan: \"_timescaledb_internal._hyper_1_1_chunk\"\nNOTICE:  1. Scan: \"_timescaledb_internal._hyper_1_2_chunk\"\nNOTICE:  2. Scan with filter: \"_timescaledb_internal._hyper_1_1_chunk\"\nNOTICE:  3. ReScan: \"_timescaledb_internal._hyper_1_2_chunk\"\nNOTICE:  4. IndexScan: \"_timescaledb_internal._hyper_1_2_chunk\"\n scanner \n---------\n \n\n-- Test errdata_to_jsonb\nRESET ROLE;\nCREATE OR REPLACE FUNCTION test.errdata_to_jsonb() RETURNS JSONB\nAS :MODULE_PATHNAME, 'ts_test_errdata_to_jsonb' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nSELECT test.errdata_to_jsonb();\n                                                                                                                                                                                                                                                                                                                         errdata_to_jsonb                                                                                                                                                                                                                                                                                                                          \n-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n {\"hint\": \"test error hint\", \"detail\": \"test error detail\", \"domain\": \"test error domain\", \"lineno\": 123, \"context\": \"test error context\", \"message\": \"test error message\", \"filename\": \"test error filename\", \"funcname\": \"test error function\", \"proc_name\": \"proc_name\", \"detail_log\": \"test error detail log\", \"sqlerrcode\": \"22023\", \"table_name\": \"test error table\", \"column_name\": \"test error column\", \"proc_schema\": \"proc_schema\", \"schema_name\": \"test error schema\", \"datatype_name\": \"test error datatype\", \"internalquery\": \"test error internal query\", \"context_domain\": \"test error context domain\", \"constraint_name\": \"test error constraint\"}\n\n"
  },
  {
    "path": "test/expected/timestamp-15.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- Utility function for grouping/slotting time with a given interval.\nCREATE OR REPLACE FUNCTION date_group(\n    field           timestamp,\n    group_interval  interval\n)\n    RETURNS timestamp LANGUAGE SQL STABLE AS\n$BODY$\n    SELECT to_timestamp((EXTRACT(EPOCH from $1)::int /\n        EXTRACT(EPOCH from group_interval)::int) *\n        EXTRACT(EPOCH from group_interval)::int)::timestamp;\n$BODY$;\nCREATE TABLE PUBLIC.\"testNs\" (\n  \"timeCustom\" TIMESTAMP NOT NULL,\n  device_id TEXT NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL,\n  series_bool BOOLEAN NULL\n);\nCREATE INDEX ON PUBLIC.\"testNs\" (device_id, \"timeCustom\" DESC NULLS LAST) WHERE device_id IS NOT NULL;\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA \"testNs\" AUTHORIZATION :ROLE_DEFAULT_PERM_USER;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT * FROM create_hypertable('\"public\".\"testNs\"', 'timeCustom', 'device_id', 2, associated_schema_name=>'testNs' );\nWARNING:  column type \"timestamp without time zone\" used for \"timeCustom\" does not follow best practices\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             1 | public      | testNs     | t\n\n\\c :TEST_DBNAME\nINSERT INTO PUBLIC.\"testNs\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n('2009-11-12T01:00:00+00:00', 'dev1', 1.5, 1),\n('2009-11-12T01:00:00+00:00', 'dev1', 1.5, 2),\n('2009-11-10T23:00:02+00:00', 'dev1', 2.5, 3);\nINSERT INTO PUBLIC.\"testNs\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n('2009-11-10T23:00:00+00:00', 'dev2', 1.5, 1),\n('2009-11-10T23:00:00+00:00', 'dev2', 1.5, 2);\nSELECT * FROM PUBLIC.\"testNs\";\n        timeCustom        | device_id | series_0 | series_1 | series_2 | series_bool \n--------------------------+-----------+----------+----------+----------+-------------\n Thu Nov 12 01:00:00 2009 | dev1      |      1.5 |        1 |          | \n Thu Nov 12 01:00:00 2009 | dev1      |      1.5 |        2 |          | \n Tue Nov 10 23:00:02 2009 | dev1      |      2.5 |        3 |          | \n Tue Nov 10 23:00:00 2009 | dev2      |      1.5 |        1 |          | \n Tue Nov 10 23:00:00 2009 | dev2      |      1.5 |        2 |          | \n\nSET client_min_messages = WARNING;\n\\echo 'The next 2 queries will differ in output between UTC and EST since the mod is on the 100th hour UTC'\nThe next 2 queries will differ in output between UTC and EST since the mod is on the 100th hour UTC\nSET timezone = 'UTC';\nSELECT date_group(\"timeCustom\", '100 days') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC;\n           time           | sum \n--------------------------+-----\n Sun Sep 13 00:00:00 2009 | 8.5\n\nSET timezone = 'EST';\nSELECT date_group(\"timeCustom\", '100 days') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC;\n           time           | sum \n--------------------------+-----\n Sat Sep 12 19:00:00 2009 | 8.5\n\n\\echo 'The rest of the queries will be the same in output between UTC and EST'\nThe rest of the queries will be the same in output between UTC and EST\nSET timezone = 'UTC';\nSELECT date_group(\"timeCustom\", '1 day') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC;\n           time           | sum \n--------------------------+-----\n Tue Nov 10 00:00:00 2009 | 5.5\n Thu Nov 12 00:00:00 2009 |   3\n\nSET timezone = 'EST';\nSELECT date_group(\"timeCustom\", '1 day') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC;\n           time           | sum \n--------------------------+-----\n Mon Nov 09 19:00:00 2009 | 5.5\n Wed Nov 11 19:00:00 2009 |   3\n\nSET timezone = 'UTC';\nSELECT *\nFROM PUBLIC.\"testNs\"\nWHERE \"timeCustom\" >= TIMESTAMP '2009-11-10T23:00:00'\nAND \"timeCustom\" < TIMESTAMP '2009-11-12T01:00:00' ORDER BY \"timeCustom\" DESC, device_id, series_1;\n        timeCustom        | device_id | series_0 | series_1 | series_2 | series_bool \n--------------------------+-----------+----------+----------+----------+-------------\n Tue Nov 10 23:00:02 2009 | dev1      |      2.5 |        3 |          | \n Tue Nov 10 23:00:00 2009 | dev2      |      1.5 |        1 |          | \n Tue Nov 10 23:00:00 2009 | dev2      |      1.5 |        2 |          | \n\nSET timezone = 'EST';\nSELECT *\nFROM PUBLIC.\"testNs\"\nWHERE \"timeCustom\" >= TIMESTAMP '2009-11-10T23:00:00'\nAND \"timeCustom\" < TIMESTAMP '2009-11-12T01:00:00' ORDER BY \"timeCustom\" DESC, device_id, series_1;\n        timeCustom        | device_id | series_0 | series_1 | series_2 | series_bool \n--------------------------+-----------+----------+----------+----------+-------------\n Tue Nov 10 23:00:02 2009 | dev1      |      2.5 |        3 |          | \n Tue Nov 10 23:00:00 2009 | dev2      |      1.5 |        1 |          | \n Tue Nov 10 23:00:00 2009 | dev2      |      1.5 |        2 |          | \n\nSET timezone = 'UTC';\nSELECT date_group(\"timeCustom\", '1 day') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC LIMIT 2;\n           time           | sum \n--------------------------+-----\n Tue Nov 10 00:00:00 2009 | 5.5\n Thu Nov 12 00:00:00 2009 |   3\n\nSET timezone = 'EST';\nSELECT date_group(\"timeCustom\", '1 day') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC LIMIT 2;\n           time           | sum \n--------------------------+-----\n Mon Nov 09 19:00:00 2009 | 5.5\n Wed Nov 11 19:00:00 2009 |   3\n\n------------------------------------\n-- Test time conversion functions --\n------------------------------------\n\\set ON_ERROR_STOP 0\nSET timezone = 'UTC';\n-- Conversion to timestamp using Postgres built-in function taking\n-- double. Gives inaccurate result on Postgres <= 9.6.2. Accurate on\n-- Postgres >= 9.6.3.\nSELECT to_timestamp(1486480176.236538);\n            to_timestamp             \n-------------------------------------\n Tue Feb 07 15:09:36.236538 2017 UTC\n\n-- extension-specific version taking microsecond UNIX timestamp\nSELECT _timescaledb_functions.to_timestamp(1486480176236538);\n            to_timestamp             \n-------------------------------------\n Tue Feb 07 15:09:36.236538 2017 UTC\n\n-- Should be the inverse of the statement above.\nSELECT _timescaledb_functions.to_unix_microseconds('2017-02-07 15:09:36.236538+00');\n to_unix_microseconds \n----------------------\n     1486480176236538\n\n-- For timestamps, BIGINT MAX represents +Infinity and BIGINT MIN\n-- -Infinity. We keep this notion for UNIX epoch time:\nSELECT _timescaledb_functions.to_unix_microseconds('+infinity');\nERROR:  invalid input syntax for type timestamp with time zone: \"+infinity\" at character 52\nSELECT _timescaledb_functions.to_timestamp(9223372036854775807);\n to_timestamp \n--------------\n infinity\n\nSELECT _timescaledb_functions.to_unix_microseconds('-infinity');\n to_unix_microseconds \n----------------------\n -9223372036854775808\n\nSELECT _timescaledb_functions.to_timestamp(-9223372036854775808);\n to_timestamp \n--------------\n -infinity\n\n-- In UNIX microseconds, the largest bigint value below infinity\n-- (BIGINT MAX) is smaller than internal date upper bound and should\n-- therefore be OK. Further, converting to the internal postgres epoch\n-- cannot overflow a 64-bit INTEGER since the postgres epoch is at a\n-- later date compared to the UNIX epoch, and is therefore represented\n-- by a smaller number\nSELECT _timescaledb_functions.to_timestamp(9223372036854775806);\n             to_timestamp              \n---------------------------------------\n Sun Jan 10 04:00:54.775806 294247 UTC\n\n-- Julian day zero is -210866803200000000 microseconds from UNIX epoch\nSELECT _timescaledb_functions.to_timestamp(-210866803200000000);\n          to_timestamp           \n---------------------------------\n Mon Nov 24 00:00:00 4714 UTC BC\n\n\\set VERBOSITY default\n-- Going beyond Julian day zero should give out-of-range error\nSELECT _timescaledb_functions.to_timestamp(-210866803200000001);\nERROR:  timestamp out of range\n-- Lower bound on date (should return the Julian day zero UNIX timestamp above)\nSELECT _timescaledb_functions.to_unix_microseconds('4714-11-24 00:00:00+00 BC');\n to_unix_microseconds \n----------------------\n  -210866803200000000\n\n-- Going beyond lower bound on date should return out-of-range\nSELECT _timescaledb_functions.to_unix_microseconds('4714-11-23 23:59:59.999999+00 BC');\nERROR:  timestamp out of range: \"4714-11-23 23:59:59.999999+00 BC\"\nLINE 1: ...ELECT _timescaledb_functions.to_unix_microseconds('4714-11-2...\n                                                             ^\n-- The upper bound for Postgres TIMESTAMPTZ\nSELECT timestamp '294276-12-31 23:59:59.999999+00';\n             timestamp             \n-----------------------------------\n Sun Dec 31 23:59:59.999999 294276\n\n-- Going beyond the upper bound, should fail\nSELECT timestamp '294276-12-31 23:59:59.999999+00' + interval '1 us';\nERROR:  timestamp out of range\n-- Cannot represent the upper bound timestamp with a UNIX microsecond timestamp\n-- since the Postgres epoch is at a later date than the UNIX epoch.\nSELECT _timescaledb_functions.to_unix_microseconds('294276-12-31 23:59:59.999999+00');\nERROR:  timestamp out of range\n-- Subtracting the difference between the two epochs (10957 days) should bring\n-- us within range.\nSELECT timestamp '294276-12-31 23:59:59.999999+00' - interval '10957 days';\n             ?column?              \n-----------------------------------\n Fri Jan 01 23:59:59.999999 294247\n\nSELECT _timescaledb_functions.to_unix_microseconds('294247-01-01 23:59:59.999999');\n to_unix_microseconds \n----------------------\n  9223371331199999999\n\n-- Adding one microsecond should take us out-of-range again\nSELECT timestamp '294247-01-01 23:59:59.999999' + interval '1 us';\n          ?column?          \n----------------------------\n Sat Jan 02 00:00:00 294247\n\nSELECT _timescaledb_functions.to_unix_microseconds(timestamp '294247-01-01 23:59:59.999999' + interval '1 us');\nERROR:  timestamp out of range\n--no time_bucketing of dates not by integer # of days\nSELECT time_bucket('1 hour', DATE '2012-01-01');\nERROR:  interval must not have sub-day precision\nSELECT time_bucket('25 hour', DATE '2012-01-01');\nERROR:  interval must be a multiple of a day\n\\set ON_ERROR_STOP 1\nSELECT time_bucket(INTERVAL '1 day', TIMESTAMP '2011-01-02 01:01:01');\n       time_bucket        \n--------------------------\n Sun Jan 02 00:00:00 2011\n\nSELECT time, time_bucket(INTERVAL '2 day ', time)\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-01 01:01:01',\n    TIMESTAMP '2011-01-02 01:01:01',\n    TIMESTAMP '2011-01-03 01:01:01',\n    TIMESTAMP '2011-01-04 01:01:01'\n    ]) AS time;\n           time           |       time_bucket        \n--------------------------+--------------------------\n Sat Jan 01 01:01:01 2011 | Sat Jan 01 00:00:00 2011\n Sun Jan 02 01:01:01 2011 | Sat Jan 01 00:00:00 2011\n Mon Jan 03 01:01:01 2011 | Mon Jan 03 00:00:00 2011\n Tue Jan 04 01:01:01 2011 | Mon Jan 03 00:00:00 2011\n\nSELECT int_def, time_bucket(int_def,TIMESTAMP '2011-01-02 01:01:01.111')\nFROM unnest(ARRAY[\n    INTERVAL '1 millisecond',\n    INTERVAL '1 second',\n    INTERVAL '1 minute',\n    INTERVAL '1 hour',\n    INTERVAL '1 day',\n    INTERVAL '2 millisecond',\n    INTERVAL '2 second',\n    INTERVAL '2 minute',\n    INTERVAL '2 hour',\n    INTERVAL '2 day'\n    ]) AS int_def;\n   int_def    |         time_bucket          \n--------------+------------------------------\n @ 0.001 secs | Sun Jan 02 01:01:01.111 2011\n @ 1 sec      | Sun Jan 02 01:01:01 2011\n @ 1 min      | Sun Jan 02 01:01:00 2011\n @ 1 hour     | Sun Jan 02 01:00:00 2011\n @ 1 day      | Sun Jan 02 00:00:00 2011\n @ 0.002 secs | Sun Jan 02 01:01:01.11 2011\n @ 2 secs     | Sun Jan 02 01:01:00 2011\n @ 2 mins     | Sun Jan 02 01:00:00 2011\n @ 2 hours    | Sun Jan 02 00:00:00 2011\n @ 2 days     | Sat Jan 01 00:00:00 2011\n\n\\set ON_ERROR_STOP 0\nSELECT time_bucket(INTERVAL '1 year 1d',TIMESTAMP '2011-01-02 01:01:01.111');\nERROR:  month intervals cannot have day or time component\nSELECT time_bucket(INTERVAL '1 month 1 minute',TIMESTAMP '2011-01-02 01:01:01.111');\nERROR:  month intervals cannot have day or time component\n\\set ON_ERROR_STOP 1\nSELECT time, time_bucket(INTERVAL '5 minute', time)\nFROM unnest(ARRAY[\n    TIMESTAMP '1970-01-01 00:59:59.999999',\n    TIMESTAMP '1970-01-01 01:01:00',\n    TIMESTAMP '1970-01-01 01:04:59.999999',\n    TIMESTAMP '1970-01-01 01:05:00'\n    ]) AS time;\n              time               |       time_bucket        \n---------------------------------+--------------------------\n Thu Jan 01 00:59:59.999999 1970 | Thu Jan 01 00:55:00 1970\n Thu Jan 01 01:01:00 1970        | Thu Jan 01 01:00:00 1970\n Thu Jan 01 01:04:59.999999 1970 | Thu Jan 01 01:00:00 1970\n Thu Jan 01 01:05:00 1970        | Thu Jan 01 01:05:00 1970\n\nSELECT time, time_bucket(INTERVAL '5 minute', time)\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-02 01:04:59.999999',\n    TIMESTAMP '2011-01-02 01:05:00',\n    TIMESTAMP '2011-01-02 01:09:59.999999',\n    TIMESTAMP '2011-01-02 01:10:00'\n    ]) AS time;\n              time               |       time_bucket        \n---------------------------------+--------------------------\n Sun Jan 02 01:04:59.999999 2011 | Sun Jan 02 01:00:00 2011\n Sun Jan 02 01:05:00 2011        | Sun Jan 02 01:05:00 2011\n Sun Jan 02 01:09:59.999999 2011 | Sun Jan 02 01:05:00 2011\n Sun Jan 02 01:10:00 2011        | Sun Jan 02 01:10:00 2011\n\n--offset with interval\nSELECT time, time_bucket(INTERVAL '5 minute', time ,  INTERVAL '2 minutes')\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-02 01:01:59.999999',\n    TIMESTAMP '2011-01-02 01:02:00',\n    TIMESTAMP '2011-01-02 01:06:59.999999',\n    TIMESTAMP '2011-01-02 01:07:00'\n    ]) AS time;\n              time               |       time_bucket        \n---------------------------------+--------------------------\n Sun Jan 02 01:01:59.999999 2011 | Sun Jan 02 00:57:00 2011\n Sun Jan 02 01:02:00 2011        | Sun Jan 02 01:02:00 2011\n Sun Jan 02 01:06:59.999999 2011 | Sun Jan 02 01:02:00 2011\n Sun Jan 02 01:07:00 2011        | Sun Jan 02 01:07:00 2011\n\nSELECT time, time_bucket(INTERVAL '5 minute', time , - INTERVAL '2 minutes')\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-02 01:02:59.999999',\n    TIMESTAMP '2011-01-02 01:03:00',\n    TIMESTAMP '2011-01-02 01:07:59.999999',\n    TIMESTAMP '2011-01-02 01:08:00'\n    ]) AS time;\n              time               |       time_bucket        \n---------------------------------+--------------------------\n Sun Jan 02 01:02:59.999999 2011 | Sun Jan 02 00:58:00 2011\n Sun Jan 02 01:03:00 2011        | Sun Jan 02 01:03:00 2011\n Sun Jan 02 01:07:59.999999 2011 | Sun Jan 02 01:03:00 2011\n Sun Jan 02 01:08:00 2011        | Sun Jan 02 01:08:00 2011\n\n--offset with infinity\n-- timestamp\nSELECT time, time_bucket(INTERVAL '1 week', time, INTERVAL '1 day')\nFROM unnest(ARRAY[\n    timestamp '-Infinity',\n    timestamp 'Infinity'\n    ]) AS time;\n   time    | time_bucket \n-----------+-------------\n -infinity | -infinity\n infinity  | infinity\n\n-- timestamptz\nSELECT time, time_bucket(INTERVAL '1 week', time, INTERVAL '1 day')\nFROM unnest(ARRAY[\n    timestamp with time zone '-Infinity',\n    timestamp with time zone 'Infinity'\n    ]) AS time;\n   time    | time_bucket \n-----------+-------------\n -infinity | -infinity\n infinity  | infinity\n\n-- Date\nSELECT date, time_bucket(INTERVAL '1 week', date, INTERVAL '1 day')\nFROM unnest(ARRAY[\n    date '-Infinity',\n    date 'Infinity'\n    ]) AS date;\n   date    | time_bucket \n-----------+-------------\n -infinity | -infinity\n infinity  | infinity\n\n--example to align with an origin\nSELECT time, time_bucket(INTERVAL '5 minute', time - (TIMESTAMP '2011-01-02 00:02:00' - TIMESTAMP 'epoch')) +  (TIMESTAMP '2011-01-02 00:02:00'-TIMESTAMP 'epoch')\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-02 01:01:59.999999',\n    TIMESTAMP '2011-01-02 01:02:00',\n    TIMESTAMP '2011-01-02 01:06:59.999999',\n    TIMESTAMP '2011-01-02 01:07:00'\n    ]) AS time;\n              time               |         ?column?         \n---------------------------------+--------------------------\n Sun Jan 02 01:01:59.999999 2011 | Sun Jan 02 00:57:00 2011\n Sun Jan 02 01:02:00 2011        | Sun Jan 02 01:02:00 2011\n Sun Jan 02 01:06:59.999999 2011 | Sun Jan 02 01:02:00 2011\n Sun Jan 02 01:07:00 2011        | Sun Jan 02 01:07:00 2011\n\n--rounding version\nSELECT time, time_bucket(INTERVAL '5 minute', time , - INTERVAL '2.5 minutes') + INTERVAL '2 minutes 30 seconds'\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-02 01:05:01',\n    TIMESTAMP '2011-01-02 01:07:29',\n    TIMESTAMP '2011-01-02 01:02:30',\n    TIMESTAMP '2011-01-02 01:07:30',\n    TIMESTAMP '2011-01-02 01:02:29'\n    ]) AS time;\n           time           |         ?column?         \n--------------------------+--------------------------\n Sun Jan 02 01:05:01 2011 | Sun Jan 02 01:05:00 2011\n Sun Jan 02 01:07:29 2011 | Sun Jan 02 01:05:00 2011\n Sun Jan 02 01:02:30 2011 | Sun Jan 02 01:05:00 2011\n Sun Jan 02 01:07:30 2011 | Sun Jan 02 01:10:00 2011\n Sun Jan 02 01:02:29 2011 | Sun Jan 02 01:00:00 2011\n\n--time_bucket with timezone should mimick date_trunc\nSET timezone TO 'UTC';\nSELECT time, time_bucket(INTERVAL '1 hour', time), date_trunc('hour', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+02'\n    ]) AS time;\n             time             |         time_bucket          |          date_trunc          \n------------------------------+------------------------------+------------------------------\n Sun Jan 02 01:01:01 2011 UTC | Sun Jan 02 01:00:00 2011 UTC | Sun Jan 02 01:00:00 2011 UTC\n Sun Jan 02 00:01:01 2011 UTC | Sun Jan 02 00:00:00 2011 UTC | Sun Jan 02 00:00:00 2011 UTC\n Sat Jan 01 23:01:01 2011 UTC | Sat Jan 01 23:00:00 2011 UTC | Sat Jan 01 23:00:00 2011 UTC\n\nSELECT time, time_bucket(INTERVAL '1 day', time), date_trunc('day', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+02'\n    ]) AS time;\n             time             |         time_bucket          |          date_trunc          \n------------------------------+------------------------------+------------------------------\n Sun Jan 02 01:01:01 2011 UTC | Sun Jan 02 00:00:00 2011 UTC | Sun Jan 02 00:00:00 2011 UTC\n Sun Jan 02 00:01:01 2011 UTC | Sun Jan 02 00:00:00 2011 UTC | Sun Jan 02 00:00:00 2011 UTC\n Sat Jan 01 23:01:01 2011 UTC | Sat Jan 01 00:00:00 2011 UTC | Sat Jan 01 00:00:00 2011 UTC\n\n--what happens with a local tz\nSET timezone TO 'America/New_York';\nSELECT time, time_bucket(INTERVAL '1 hour', time), date_trunc('hour', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+02'\n    ]) AS time;\n             time             |         time_bucket          |          date_trunc          \n------------------------------+------------------------------+------------------------------\n Sun Jan 02 01:01:01 2011 EST | Sun Jan 02 01:00:00 2011 EST | Sun Jan 02 01:00:00 2011 EST\n Sat Jan 01 19:01:01 2011 EST | Sat Jan 01 19:00:00 2011 EST | Sat Jan 01 19:00:00 2011 EST\n Sat Jan 01 18:01:01 2011 EST | Sat Jan 01 18:00:00 2011 EST | Sat Jan 01 18:00:00 2011 EST\n\n--Note the timestamp tz input is aligned with UTC day /not/ local day. different than date_trunc.\nSELECT time, time_bucket(INTERVAL '1 day', time), date_trunc('day', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-03 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-04 01:01:01+02'\n    ]) AS time;\n             time             |         time_bucket          |          date_trunc          \n------------------------------+------------------------------+------------------------------\n Sun Jan 02 01:01:01 2011 EST | Sat Jan 01 19:00:00 2011 EST | Sun Jan 02 00:00:00 2011 EST\n Sun Jan 02 19:01:01 2011 EST | Sun Jan 02 19:00:00 2011 EST | Sun Jan 02 00:00:00 2011 EST\n Mon Jan 03 18:01:01 2011 EST | Sun Jan 02 19:00:00 2011 EST | Mon Jan 03 00:00:00 2011 EST\n\n--can force local bucketing with simple cast.\nSELECT time, time_bucket(INTERVAL '1 day', time::timestamp), date_trunc('day', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-03 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-04 01:01:01+02'\n    ]) AS time;\n             time             |       time_bucket        |          date_trunc          \n------------------------------+--------------------------+------------------------------\n Sun Jan 02 01:01:01 2011 EST | Sun Jan 02 00:00:00 2011 | Sun Jan 02 00:00:00 2011 EST\n Sun Jan 02 19:01:01 2011 EST | Sun Jan 02 00:00:00 2011 | Sun Jan 02 00:00:00 2011 EST\n Mon Jan 03 18:01:01 2011 EST | Mon Jan 03 00:00:00 2011 | Mon Jan 03 00:00:00 2011 EST\n\n--can also use interval to correct\nSELECT time, time_bucket(INTERVAL '1 day', time, -INTERVAL '19 hours'), date_trunc('day', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-03 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-04 01:01:01+02'\n    ]) AS time;\n             time             |         time_bucket          |          date_trunc          \n------------------------------+------------------------------+------------------------------\n Sun Jan 02 01:01:01 2011 EST | Sun Jan 02 00:00:00 2011 EST | Sun Jan 02 00:00:00 2011 EST\n Sun Jan 02 19:01:01 2011 EST | Sun Jan 02 00:00:00 2011 EST | Sun Jan 02 00:00:00 2011 EST\n Mon Jan 03 18:01:01 2011 EST | Mon Jan 03 00:00:00 2011 EST | Mon Jan 03 00:00:00 2011 EST\n\n--dst: same local hour bucketed as two different hours.\nSELECT time, time_bucket(INTERVAL '1 hour', time), date_trunc('hour', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2017-11-05 12:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 13:05:00+07'\n    ]) AS time;\n             time             |         time_bucket          |          date_trunc          \n------------------------------+------------------------------+------------------------------\n Sun Nov 05 01:05:00 2017 EDT | Sun Nov 05 01:00:00 2017 EDT | Sun Nov 05 01:00:00 2017 EDT\n Sun Nov 05 01:05:00 2017 EST | Sun Nov 05 01:00:00 2017 EST | Sun Nov 05 01:00:00 2017 EST\n\n--local alignment changes when bucketing by UTC across dst boundary\nSELECT time, time_bucket(INTERVAL '2 hour', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2017-11-05 10:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 12:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 13:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 15:05:00+07'\n    ]) AS time;\n             time             |         time_bucket          \n------------------------------+------------------------------\n Sat Nov 04 23:05:00 2017 EDT | Sat Nov 04 22:00:00 2017 EDT\n Sun Nov 05 01:05:00 2017 EDT | Sun Nov 05 00:00:00 2017 EDT\n Sun Nov 05 01:05:00 2017 EST | Sun Nov 05 01:00:00 2017 EST\n Sun Nov 05 03:05:00 2017 EST | Sun Nov 05 03:00:00 2017 EST\n\n--local alignment is preserved when bucketing by local time across DST boundary.\nSELECT time, time_bucket(INTERVAL '2 hour', time::timestamp)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2017-11-05 10:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 12:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 13:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 15:05:00+07'\n    ]) AS time;\n             time             |       time_bucket        \n------------------------------+--------------------------\n Sat Nov 04 23:05:00 2017 EDT | Sat Nov 04 22:00:00 2017\n Sun Nov 05 01:05:00 2017 EDT | Sun Nov 05 00:00:00 2017\n Sun Nov 05 01:05:00 2017 EST | Sun Nov 05 00:00:00 2017\n Sun Nov 05 03:05:00 2017 EST | Sun Nov 05 02:00:00 2017\n\n-- GitHub issue #7059: time_bucket with timezone + offset across DST boundary\n-- Asia/Amman: clocks skip from 00:00 to 01:00 on 2021-03-26\n-- Input: 01:00+03 = 22:00 UTC → Result: 22:15 UTC = 01:15 local (00:00 + 15min offset)\nSELECT time_bucket('1 day', '2021-03-26 01:00:00+03'::timestamptz,\n    timezone := 'Asia/Amman', \"offset\" := '15 minutes'::interval);\n         time_bucket          \n------------------------------\n Wed Mar 24 18:15:00 2021 EDT\n\n-- GitHub issue #8851: time_bucket with negative offset during DST fall-back\n-- Europe/Berlin: clocks repeat 02:00-02:59 on 2025-10-26\n-- Input: 02:00+02 = 00:00 UTC → Result: 23:59:45 UTC = 01:59:45 local (02:00 - 15s offset)\nSELECT time_bucket('30 seconds', '2025-10-26 02:00:00+02'::timestamptz,\n    timezone := 'Europe/Berlin', \"offset\" := '-15 seconds'::interval);\n         time_bucket          \n------------------------------\n Sat Oct 25 19:59:45 2025 EDT\n\n-- Additional DST edge cases for coverage of DST direction × offset sign combinations\n-- Spring-forward + negative offset\n-- Input: 01:30+03 = 22:30 UTC → Result: 22:45 UTC = 01:45 local (01:00 + 45min = 02:00 - 15min)\nSELECT time_bucket('1 hour', '2021-03-26 01:30:00+03'::timestamptz,\n    timezone := 'Asia/Amman', \"offset\" := '-15 minutes'::interval);\n         time_bucket          \n------------------------------\n Thu Mar 25 17:45:00 2021 EDT\n\n-- Fall-back + positive offset\n-- Input: 02:30+01 = 01:30 UTC → Result: 01:15 UTC = 02:15 local (02:00 + 15min offset)\nSELECT time_bucket('1 hour', '2025-10-26 02:30:00+01'::timestamptz,\n    timezone := 'Europe/Berlin', \"offset\" := '15 minutes'::interval);\n         time_bucket          \n------------------------------\n Sat Oct 25 21:15:00 2025 EDT\n\n-- Input exactly at DST spring-forward transition\n-- Input: 22:00 UTC = 00:00 local (the moment clocks jump to 01:00)\n-- Result: 22:15 UTC = 01:15 local (01:00 + 15min offset)\nSELECT time_bucket('1 hour', '2021-03-25 22:00:00+00'::timestamptz,\n    timezone := 'Asia/Amman', \"offset\" := '15 minutes'::interval);\n         time_bucket          \n------------------------------\n Thu Mar 25 17:15:00 2021 EDT\n\n-- Input exactly at DST fall-back transition\n-- Input: 01:00 UTC = 03:00 CEST (the moment clocks go back to 02:00 CET)\n-- Result: 23:15 UTC = 01:15 local (01:00 + 15min offset, but in CET now)\nSELECT time_bucket('1 hour', '2025-10-26 01:00:00+00'::timestamptz,\n    timezone := 'Europe/Berlin', \"offset\" := '15 minutes'::interval);\n         time_bucket          \n------------------------------\n Sat Oct 25 20:15:00 2025 EDT\n\n-- Offset larger than bucket size (1h offset with 30min bucket)\n-- Input: 01:30+03 = 22:30 UTC → Result: 22:30 UTC = 01:30 local (01:00 + 30min = 00:30 + 1h)\nSELECT time_bucket('30 minutes', '2021-03-26 01:30:00+03'::timestamptz,\n    timezone := 'Asia/Amman', \"offset\" := '1 hour'::interval);\n         time_bucket          \n------------------------------\n Thu Mar 25 18:30:00 2021 EDT\n\n-- GitHub issue #9136: time_bucket with origin during DST fall-back\n-- When origin is in standard time but timestamp is in daylight time,\n-- the bucket could incorrectly start AFTER the timestamp.\n-- America/New_York: clocks go back at 02:00 EDT on 2024-11-03\n-- Input: 01:30-04 (EDT) = 05:30 UTC; origin in EST\n-- Result should have bucket start <= timestamp (bucket in EDT, not EST)\nSELECT time_bucket('1 hour', '2024-11-03 01:30:00-04'::timestamptz,\n    'America/New_York', '2000-01-01 00:00:00 America/New_York'::timestamptz) as bucket,\n    '2024-11-03 01:30:00-04'::timestamptz < time_bucket('1 hour',\n        '2024-11-03 01:30:00-04'::timestamptz, 'America/New_York',\n        '2000-01-01 00:00:00 America/New_York'::timestamptz) as ts_before_bucket;\n            bucket            | ts_before_bucket \n------------------------------+------------------\n Sun Nov 03 01:00:00 2024 EDT | f\n\nSELECT time,\n    time_bucket(10::smallint, time) AS time_bucket_smallint,\n    time_bucket(10::int, time) AS time_bucket_int,\n    time_bucket(10::bigint, time) AS time_bucket_bigint\nFROM unnest(ARRAY[\n     '-11',\n     '-10',\n      '-9',\n      '-1',\n       '0',\n       '1',\n      '99',\n     '100',\n     '109',\n     '110'\n    ]::smallint[]) AS time;\n time | time_bucket_smallint | time_bucket_int | time_bucket_bigint \n------+----------------------+-----------------+--------------------\n  -11 |                  -20 |             -20 |                -20\n  -10 |                  -10 |             -10 |                -10\n   -9 |                  -10 |             -10 |                -10\n   -1 |                  -10 |             -10 |                -10\n    0 |                    0 |               0 |                  0\n    1 |                    0 |               0 |                  0\n   99 |                   90 |              90 |                 90\n  100 |                  100 |             100 |                100\n  109 |                  100 |             100 |                100\n  110 |                  110 |             110 |                110\n\nSELECT time,\n    time_bucket(10::smallint, time, 2::smallint) AS time_bucket_smallint,\n    time_bucket(10::int, time, 2::int) AS time_bucket_int,\n    time_bucket(10::bigint, time, 2::bigint) AS time_bucket_bigint\nFROM unnest(ARRAY[\n      '-9',\n      '-8',\n      '-7',\n       '1',\n       '2',\n       '3',\n     '101',\n     '102',\n     '111',\n     '112'\n    ]::smallint[]) AS time;\n time | time_bucket_smallint | time_bucket_int | time_bucket_bigint \n------+----------------------+-----------------+--------------------\n   -9 |                  -18 |             -18 |                -18\n   -8 |                   -8 |              -8 |                 -8\n   -7 |                   -8 |              -8 |                 -8\n    1 |                   -8 |              -8 |                 -8\n    2 |                    2 |               2 |                  2\n    3 |                    2 |               2 |                  2\n  101 |                   92 |              92 |                 92\n  102 |                  102 |             102 |                102\n  111 |                  102 |             102 |                102\n  112 |                  112 |             112 |                112\n\nSELECT time,\n    time_bucket(10::smallint, time, -2::smallint) AS time_bucket_smallint,\n    time_bucket(10::int, time, -2::int) AS time_bucket_int,\n    time_bucket(10::bigint, time, -2::bigint) AS time_bucket_bigint\nFROM unnest(ARRAY[\n    '-13',\n    '-12',\n    '-11',\n     '-3',\n     '-2',\n     '-1',\n     '97',\n     '98',\n    '107',\n    '108'\n    ]::smallint[]) AS time;\n time | time_bucket_smallint | time_bucket_int | time_bucket_bigint \n------+----------------------+-----------------+--------------------\n  -13 |                  -22 |             -22 |                -22\n  -12 |                  -12 |             -12 |                -12\n  -11 |                  -12 |             -12 |                -12\n   -3 |                  -12 |             -12 |                -12\n   -2 |                   -2 |              -2 |                 -2\n   -1 |                   -2 |              -2 |                 -2\n   97 |                   88 |              88 |                 88\n   98 |                   98 |              98 |                 98\n  107 |                   98 |              98 |                 98\n  108 |                  108 |             108 |                108\n\n\\set ON_ERROR_STOP 0\nSELECT time_bucket(10::smallint, '-32768'::smallint);\nERROR:  timestamp out of range\nSELECT time_bucket(10::smallint, '-32761'::smallint);\nERROR:  timestamp out of range\nselect time_bucket(10::smallint, '-32768'::smallint, 1000::smallint);\nERROR:  timestamp out of range\nselect time_bucket(10::smallint, '-32768'::smallint, '32767'::smallint);\nERROR:  timestamp out of range\nselect time_bucket(10::smallint, '32767'::smallint, '-32768'::smallint);\nERROR:  timestamp out of range\n\\set ON_ERROR_STOP 1\nSELECT time, time_bucket(10::smallint, time)\nFROM unnest(ARRAY[\n    '-32760',\n    '-32759',\n    '32767'\n    ]::smallint[]) AS time;\n  time  | time_bucket \n--------+-------------\n -32760 |      -32760\n -32759 |      -32760\n  32767 |       32760\n\n\\set ON_ERROR_STOP 0\nSELECT time_bucket(10::int, '-2147483648'::int);\nERROR:  timestamp out of range\nSELECT time_bucket(10::int, '-2147483641'::int);\nERROR:  timestamp out of range\nSELECT time_bucket(1000::int, '-2147483000'::int, 1::int);\nERROR:  timestamp out of range\nSELECT time_bucket(1000::int, '-2147483648'::int, '2147483647'::int);\nERROR:  timestamp out of range\nSELECT time_bucket(1000::int, '2147483647'::int, '-2147483648'::int);\nERROR:  timestamp out of range\n\\set ON_ERROR_STOP 1\nSELECT time, time_bucket(10::int, time)\nFROM unnest(ARRAY[\n    '-2147483640',\n    '-2147483639',\n    '2147483647'\n    ]::int[]) AS time;\n    time     | time_bucket \n-------------+-------------\n -2147483640 | -2147483640\n -2147483639 | -2147483640\n  2147483647 |  2147483640\n\n\\set ON_ERROR_STOP 0\nSELECT time_bucket(10::bigint, '-9223372036854775808'::bigint);\nERROR:  timestamp out of range\nSELECT time_bucket(10::bigint, '-9223372036854775801'::bigint);\nERROR:  timestamp out of range\nSELECT time_bucket(1000::bigint, '-9223372036854775000'::bigint, 1::bigint);\nERROR:  timestamp out of range\nSELECT time_bucket(1000::bigint, '-9223372036854775808'::bigint, '9223372036854775807'::bigint);\nERROR:  timestamp out of range\nSELECT time_bucket(1000::bigint, '9223372036854775807'::bigint, '-9223372036854775808'::bigint);\nERROR:  timestamp out of range\n\\set ON_ERROR_STOP 1\nSELECT time, time_bucket(10::bigint, time)\nFROM unnest(ARRAY[\n    '-9223372036854775800',\n    '-9223372036854775799',\n    '9223372036854775807'\n    ]::bigint[]) AS time;\n         time         |     time_bucket      \n----------------------+----------------------\n -9223372036854775800 | -9223372036854775800\n -9223372036854775799 | -9223372036854775800\n  9223372036854775807 |  9223372036854775800\n\nSELECT time, time_bucket(INTERVAL '1 day', time::date)\nFROM unnest(ARRAY[\n    date '2017-11-05',\n    date '2017-11-06'\n    ]) AS time;\n    time    | time_bucket \n------------+-------------\n 11-05-2017 | 11-05-2017\n 11-06-2017 | 11-06-2017\n\nSELECT time, time_bucket(INTERVAL '4 day', time::date)\nFROM unnest(ARRAY[\n    date '2017-11-04',\n    date '2017-11-05',\n    date '2017-11-08',\n    date '2017-11-09'\n    ]) AS time;\n    time    | time_bucket \n------------+-------------\n 11-04-2017 | 11-01-2017\n 11-05-2017 | 11-05-2017\n 11-08-2017 | 11-05-2017\n 11-09-2017 | 11-09-2017\n\nSELECT time, time_bucket(INTERVAL '4 day', time::date, INTERVAL '2 day')\nFROM unnest(ARRAY[\n    date '2017-11-06',\n    date '2017-11-07',\n    date '2017-11-10',\n    date '2017-11-11'\n    ]) AS time;\n    time    | time_bucket \n------------+-------------\n 11-06-2017 | 11-03-2017\n 11-07-2017 | 11-07-2017\n 11-10-2017 | 11-07-2017\n 11-11-2017 | 11-11-2017\n\n-- 2019-09-24 is a Monday, and we want to ensure that time_bucket returns the week starting with a Monday as date_trunc does,\n-- Rather than a Saturday which is the date of the PostgreSQL epoch\nSELECT time, time_bucket(INTERVAL '1 week', time::date)\nFROM unnest(ARRAY[\n    date '2018-09-16',\n    date '2018-09-17',\n    date '2018-09-23',\n    date '2018-09-24'\n    ]) AS time;\n    time    | time_bucket \n------------+-------------\n 09-16-2018 | 09-10-2018\n 09-17-2018 | 09-17-2018\n 09-23-2018 | 09-17-2018\n 09-24-2018 | 09-24-2018\n\nSELECT time, time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp without time zone '2018-09-16',\n    timestamp without time zone '2018-09-17',\n    timestamp without time zone '2018-09-23',\n    timestamp without time zone '2018-09-24'\n    ]) AS time;\n           time           |       time_bucket        \n--------------------------+--------------------------\n Sun Sep 16 00:00:00 2018 | Mon Sep 10 00:00:00 2018\n Mon Sep 17 00:00:00 2018 | Mon Sep 17 00:00:00 2018\n Sun Sep 23 00:00:00 2018 | Mon Sep 17 00:00:00 2018\n Mon Sep 24 00:00:00 2018 | Mon Sep 24 00:00:00 2018\n\nSELECT time, time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp with time zone '2018-09-16',\n    timestamp with time zone '2018-09-17',\n    timestamp with time zone '2018-09-23',\n    timestamp with time zone '2018-09-24'\n    ]) AS time;\n             time             |         time_bucket          \n------------------------------+------------------------------\n Sun Sep 16 00:00:00 2018 EDT | Sun Sep 09 20:00:00 2018 EDT\n Mon Sep 17 00:00:00 2018 EDT | Sun Sep 16 20:00:00 2018 EDT\n Sun Sep 23 00:00:00 2018 EDT | Sun Sep 16 20:00:00 2018 EDT\n Mon Sep 24 00:00:00 2018 EDT | Sun Sep 23 20:00:00 2018 EDT\n\nSELECT time, time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp with time zone '-Infinity',\n    timestamp with time zone 'Infinity'\n    ]) AS time;\n   time    | time_bucket \n-----------+-------------\n -infinity | -infinity\n infinity  | infinity\n\nSELECT time, time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp without time zone '-Infinity',\n    timestamp without time zone 'Infinity'\n    ]) AS time;\n   time    | time_bucket \n-----------+-------------\n -infinity | -infinity\n infinity  | infinity\n\nSELECT time, time_bucket(INTERVAL '1 week', time), date_trunc('week', time) = time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp without time zone '4714-11-24 01:01:01.0 BC',\n    timestamp without time zone '294276-12-31 23:59:59.9999'\n    ]) AS time;\n              time               |         time_bucket         | ?column? \n---------------------------------+-----------------------------+----------\n Mon Nov 24 01:01:01 4714 BC     | Mon Nov 24 00:00:00 4714 BC | t\n Sun Dec 31 23:59:59.9999 294276 | Mon Dec 25 00:00:00 294276  | t\n\n--1000 years later weeks still align.\nSELECT time, time_bucket(INTERVAL '1 week', time), date_trunc('week', time) = time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp without time zone '3018-09-14',\n    timestamp without time zone '3018-09-20',\n    timestamp without time zone '3018-09-21',\n    timestamp without time zone '3018-09-22'\n    ]) AS time;\n           time           |       time_bucket        | ?column? \n--------------------------+--------------------------+----------\n Mon Sep 14 00:00:00 3018 | Mon Sep 14 00:00:00 3018 | t\n Sun Sep 20 00:00:00 3018 | Mon Sep 14 00:00:00 3018 | t\n Mon Sep 21 00:00:00 3018 | Mon Sep 21 00:00:00 3018 | t\n Tue Sep 22 00:00:00 3018 | Mon Sep 21 00:00:00 3018 | t\n\n--weeks align for timestamptz as well if cast to local time, (but not if done at UTC).\nSELECT time, date_trunc('week', time) = time_bucket(INTERVAL '1 week', time),  date_trunc('week', time) = time_bucket(INTERVAL '1 week', time::timestamp)\nFROM unnest(ARRAY[\n    timestamp with time zone '3018-09-14',\n    timestamp with time zone '3018-09-20',\n    timestamp with time zone '3018-09-21',\n    timestamp with time zone '3018-09-22'\n    ]) AS time;\n             time             | ?column? | ?column? \n------------------------------+----------+----------\n Mon Sep 14 00:00:00 3018 EDT | f        | t\n Sun Sep 20 00:00:00 3018 EDT | f        | t\n Mon Sep 21 00:00:00 3018 EDT | f        | t\n Tue Sep 22 00:00:00 3018 EDT | f        | t\n\n--check functions with origin\n--note that the default origin is at 0 UTC, using origin parameter it is easy to provide a EDT origin point\n\\x\nSELECT time, time_bucket(INTERVAL '1 week', time) no_epoch,\n             time_bucket(INTERVAL '1 week', time::timestamp) no_epoch_local,\n             time_bucket(INTERVAL '1 week', time) = time_bucket(INTERVAL '1 week', time, timestamptz '2000-01-03 00:00:00+0') always_true,\n             time_bucket(INTERVAL '1 week', time, timestamptz '2000-01-01 00:00:00+0') pg_epoch,\n             time_bucket(INTERVAL '1 week', time, timestamptz 'epoch') unix_epoch,\n             time_bucket(INTERVAL '1 week', time, timestamptz '3018-09-13') custom_1,\n             time_bucket(INTERVAL '1 week', time, timestamptz '3018-09-14') custom_2\nFROM unnest(ARRAY[\n    timestamp with time zone '2000-01-01 00:00:00+0'- interval '1 second',\n    timestamp with time zone '2000-01-01 00:00:00+0',\n    timestamp with time zone '2000-01-03 00:00:00+0'- interval '1 second',\n    timestamp with time zone '2000-01-03 00:00:00+0',\n    timestamp with time zone '2000-01-01',\n    timestamp with time zone '2000-01-02',\n    timestamp with time zone '2000-01-03',\n    timestamp with time zone '3018-09-12',\n    timestamp with time zone '3018-09-13',\n    timestamp with time zone '3018-09-14',\n    timestamp with time zone '3018-09-15'\n    ]) AS time;\n-[ RECORD 1 ]--+-----------------------------\ntime           | Fri Dec 31 18:59:59 1999 EST\nno_epoch       | Sun Dec 26 19:00:00 1999 EST\nno_epoch_local | Mon Dec 27 00:00:00 1999\nalways_true    | t\npg_epoch       | Fri Dec 24 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Dec 25 23:00:00 1999 EST\ncustom_2       | Sun Dec 26 23:00:00 1999 EST\n-[ RECORD 2 ]--+-----------------------------\ntime           | Fri Dec 31 19:00:00 1999 EST\nno_epoch       | Sun Dec 26 19:00:00 1999 EST\nno_epoch_local | Mon Dec 27 00:00:00 1999\nalways_true    | t\npg_epoch       | Fri Dec 31 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Dec 25 23:00:00 1999 EST\ncustom_2       | Sun Dec 26 23:00:00 1999 EST\n-[ RECORD 3 ]--+-----------------------------\ntime           | Sun Jan 02 18:59:59 2000 EST\nno_epoch       | Sun Dec 26 19:00:00 1999 EST\nno_epoch_local | Mon Dec 27 00:00:00 1999\nalways_true    | t\npg_epoch       | Fri Dec 31 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Jan 01 23:00:00 2000 EST\ncustom_2       | Sun Dec 26 23:00:00 1999 EST\n-[ RECORD 4 ]--+-----------------------------\ntime           | Sun Jan 02 19:00:00 2000 EST\nno_epoch       | Sun Jan 02 19:00:00 2000 EST\nno_epoch_local | Mon Dec 27 00:00:00 1999\nalways_true    | t\npg_epoch       | Fri Dec 31 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Jan 01 23:00:00 2000 EST\ncustom_2       | Sun Dec 26 23:00:00 1999 EST\n-[ RECORD 5 ]--+-----------------------------\ntime           | Sat Jan 01 00:00:00 2000 EST\nno_epoch       | Sun Dec 26 19:00:00 1999 EST\nno_epoch_local | Mon Dec 27 00:00:00 1999\nalways_true    | t\npg_epoch       | Fri Dec 31 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Dec 25 23:00:00 1999 EST\ncustom_2       | Sun Dec 26 23:00:00 1999 EST\n-[ RECORD 6 ]--+-----------------------------\ntime           | Sun Jan 02 00:00:00 2000 EST\nno_epoch       | Sun Dec 26 19:00:00 1999 EST\nno_epoch_local | Mon Dec 27 00:00:00 1999\nalways_true    | t\npg_epoch       | Fri Dec 31 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Jan 01 23:00:00 2000 EST\ncustom_2       | Sun Dec 26 23:00:00 1999 EST\n-[ RECORD 7 ]--+-----------------------------\ntime           | Mon Jan 03 00:00:00 2000 EST\nno_epoch       | Sun Jan 02 19:00:00 2000 EST\nno_epoch_local | Mon Jan 03 00:00:00 2000\nalways_true    | t\npg_epoch       | Fri Dec 31 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Jan 01 23:00:00 2000 EST\ncustom_2       | Sun Jan 02 23:00:00 2000 EST\n-[ RECORD 8 ]--+-----------------------------\ntime           | Sat Sep 12 00:00:00 3018 EDT\nno_epoch       | Sun Sep 06 20:00:00 3018 EDT\nno_epoch_local | Mon Sep 07 00:00:00 3018\nalways_true    | t\npg_epoch       | Fri Sep 11 20:00:00 3018 EDT\nunix_epoch     | Wed Sep 09 20:00:00 3018 EDT\ncustom_1       | Sun Sep 06 00:00:00 3018 EDT\ncustom_2       | Mon Sep 07 00:00:00 3018 EDT\n-[ RECORD 9 ]--+-----------------------------\ntime           | Sun Sep 13 00:00:00 3018 EDT\nno_epoch       | Sun Sep 06 20:00:00 3018 EDT\nno_epoch_local | Mon Sep 07 00:00:00 3018\nalways_true    | t\npg_epoch       | Fri Sep 11 20:00:00 3018 EDT\nunix_epoch     | Wed Sep 09 20:00:00 3018 EDT\ncustom_1       | Sun Sep 13 00:00:00 3018 EDT\ncustom_2       | Mon Sep 07 00:00:00 3018 EDT\n-[ RECORD 10 ]-+-----------------------------\ntime           | Mon Sep 14 00:00:00 3018 EDT\nno_epoch       | Sun Sep 13 20:00:00 3018 EDT\nno_epoch_local | Mon Sep 14 00:00:00 3018\nalways_true    | t\npg_epoch       | Fri Sep 11 20:00:00 3018 EDT\nunix_epoch     | Wed Sep 09 20:00:00 3018 EDT\ncustom_1       | Sun Sep 13 00:00:00 3018 EDT\ncustom_2       | Mon Sep 14 00:00:00 3018 EDT\n-[ RECORD 11 ]-+-----------------------------\ntime           | Tue Sep 15 00:00:00 3018 EDT\nno_epoch       | Sun Sep 13 20:00:00 3018 EDT\nno_epoch_local | Mon Sep 14 00:00:00 3018\nalways_true    | t\npg_epoch       | Fri Sep 11 20:00:00 3018 EDT\nunix_epoch     | Wed Sep 09 20:00:00 3018 EDT\ncustom_1       | Sun Sep 13 00:00:00 3018 EDT\ncustom_2       | Mon Sep 14 00:00:00 3018 EDT\n\nSELECT time, time_bucket(INTERVAL '1 week', time) no_epoch,\n             time_bucket(INTERVAL '1 week', time) = time_bucket(INTERVAL '1 week', time, timestamp '2000-01-03 00:00:00') always_true,\n             time_bucket(INTERVAL '1 week', time, timestamp '2000-01-01 00:00:00+0') pg_epoch,\n             time_bucket(INTERVAL '1 week', time, timestamp 'epoch') unix_epoch,\n             time_bucket(INTERVAL '1 week', time, timestamp '3018-09-13') custom_1,\n             time_bucket(INTERVAL '1 week', time, timestamp '3018-09-14') custom_2\nFROM unnest(ARRAY[\n    timestamp without time zone '2000-01-01 00:00:00'- interval '1 second',\n    timestamp without time zone '2000-01-01 00:00:00',\n    timestamp without time zone '2000-01-03 00:00:00'- interval '1 second',\n    timestamp without time zone '2000-01-03 00:00:00',\n    timestamp without time zone '2000-01-01',\n    timestamp without time zone '2000-01-02',\n    timestamp without time zone '2000-01-03',\n    timestamp without time zone '3018-09-12',\n    timestamp without time zone '3018-09-13',\n    timestamp without time zone '3018-09-14',\n    timestamp without time zone '3018-09-15'\n    ]) AS time;\n-[ RECORD 1 ]-------------------------\ntime        | Fri Dec 31 23:59:59 1999\nno_epoch    | Mon Dec 27 00:00:00 1999\nalways_true | t\npg_epoch    | Sat Dec 25 00:00:00 1999\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Dec 26 00:00:00 1999\ncustom_2    | Mon Dec 27 00:00:00 1999\n-[ RECORD 2 ]-------------------------\ntime        | Sat Jan 01 00:00:00 2000\nno_epoch    | Mon Dec 27 00:00:00 1999\nalways_true | t\npg_epoch    | Sat Jan 01 00:00:00 2000\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Dec 26 00:00:00 1999\ncustom_2    | Mon Dec 27 00:00:00 1999\n-[ RECORD 3 ]-------------------------\ntime        | Sun Jan 02 23:59:59 2000\nno_epoch    | Mon Dec 27 00:00:00 1999\nalways_true | t\npg_epoch    | Sat Jan 01 00:00:00 2000\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Jan 02 00:00:00 2000\ncustom_2    | Mon Dec 27 00:00:00 1999\n-[ RECORD 4 ]-------------------------\ntime        | Mon Jan 03 00:00:00 2000\nno_epoch    | Mon Jan 03 00:00:00 2000\nalways_true | t\npg_epoch    | Sat Jan 01 00:00:00 2000\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Jan 02 00:00:00 2000\ncustom_2    | Mon Jan 03 00:00:00 2000\n-[ RECORD 5 ]-------------------------\ntime        | Sat Jan 01 00:00:00 2000\nno_epoch    | Mon Dec 27 00:00:00 1999\nalways_true | t\npg_epoch    | Sat Jan 01 00:00:00 2000\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Dec 26 00:00:00 1999\ncustom_2    | Mon Dec 27 00:00:00 1999\n-[ RECORD 6 ]-------------------------\ntime        | Sun Jan 02 00:00:00 2000\nno_epoch    | Mon Dec 27 00:00:00 1999\nalways_true | t\npg_epoch    | Sat Jan 01 00:00:00 2000\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Jan 02 00:00:00 2000\ncustom_2    | Mon Dec 27 00:00:00 1999\n-[ RECORD 7 ]-------------------------\ntime        | Mon Jan 03 00:00:00 2000\nno_epoch    | Mon Jan 03 00:00:00 2000\nalways_true | t\npg_epoch    | Sat Jan 01 00:00:00 2000\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Jan 02 00:00:00 2000\ncustom_2    | Mon Jan 03 00:00:00 2000\n-[ RECORD 8 ]-------------------------\ntime        | Sat Sep 12 00:00:00 3018\nno_epoch    | Mon Sep 07 00:00:00 3018\nalways_true | t\npg_epoch    | Sat Sep 12 00:00:00 3018\nunix_epoch  | Thu Sep 10 00:00:00 3018\ncustom_1    | Sun Sep 06 00:00:00 3018\ncustom_2    | Mon Sep 07 00:00:00 3018\n-[ RECORD 9 ]-------------------------\ntime        | Sun Sep 13 00:00:00 3018\nno_epoch    | Mon Sep 07 00:00:00 3018\nalways_true | t\npg_epoch    | Sat Sep 12 00:00:00 3018\nunix_epoch  | Thu Sep 10 00:00:00 3018\ncustom_1    | Sun Sep 13 00:00:00 3018\ncustom_2    | Mon Sep 07 00:00:00 3018\n-[ RECORD 10 ]------------------------\ntime        | Mon Sep 14 00:00:00 3018\nno_epoch    | Mon Sep 14 00:00:00 3018\nalways_true | t\npg_epoch    | Sat Sep 12 00:00:00 3018\nunix_epoch  | Thu Sep 10 00:00:00 3018\ncustom_1    | Sun Sep 13 00:00:00 3018\ncustom_2    | Mon Sep 14 00:00:00 3018\n-[ RECORD 11 ]------------------------\ntime        | Tue Sep 15 00:00:00 3018\nno_epoch    | Mon Sep 14 00:00:00 3018\nalways_true | t\npg_epoch    | Sat Sep 12 00:00:00 3018\nunix_epoch  | Thu Sep 10 00:00:00 3018\ncustom_1    | Sun Sep 13 00:00:00 3018\ncustom_2    | Mon Sep 14 00:00:00 3018\n\nSELECT time, time_bucket(INTERVAL '1 week', time) no_epoch,\n             time_bucket(INTERVAL '1 week', time) = time_bucket(INTERVAL '1 week', time, date '2000-01-03') always_true,\n             time_bucket(INTERVAL '1 week', time, date '2000-01-01') pg_epoch,\n             time_bucket(INTERVAL '1 week', time, (timestamp 'epoch')::date) unix_epoch,\n             time_bucket(INTERVAL '1 week', time, date '3018-09-13') custom_1,\n             time_bucket(INTERVAL '1 week', time, date '3018-09-14') custom_2\nFROM unnest(ARRAY[\n    date '1999-12-31',\n    date '2000-01-01',\n    date '2000-01-02',\n    date '2000-01-03',\n    date '3018-09-12',\n    date '3018-09-13',\n    date '3018-09-14',\n    date '3018-09-15'\n    ]) AS time;\n-[ RECORD 1 ]-----------\ntime        | 12-31-1999\nno_epoch    | 12-27-1999\nalways_true | t\npg_epoch    | 12-25-1999\nunix_epoch  | 12-30-1999\ncustom_1    | 12-26-1999\ncustom_2    | 12-27-1999\n-[ RECORD 2 ]-----------\ntime        | 01-01-2000\nno_epoch    | 12-27-1999\nalways_true | t\npg_epoch    | 01-01-2000\nunix_epoch  | 12-30-1999\ncustom_1    | 12-26-1999\ncustom_2    | 12-27-1999\n-[ RECORD 3 ]-----------\ntime        | 01-02-2000\nno_epoch    | 12-27-1999\nalways_true | t\npg_epoch    | 01-01-2000\nunix_epoch  | 12-30-1999\ncustom_1    | 01-02-2000\ncustom_2    | 12-27-1999\n-[ RECORD 4 ]-----------\ntime        | 01-03-2000\nno_epoch    | 01-03-2000\nalways_true | t\npg_epoch    | 01-01-2000\nunix_epoch  | 12-30-1999\ncustom_1    | 01-02-2000\ncustom_2    | 01-03-2000\n-[ RECORD 5 ]-----------\ntime        | 09-12-3018\nno_epoch    | 09-07-3018\nalways_true | t\npg_epoch    | 09-12-3018\nunix_epoch  | 09-10-3018\ncustom_1    | 09-06-3018\ncustom_2    | 09-07-3018\n-[ RECORD 6 ]-----------\ntime        | 09-13-3018\nno_epoch    | 09-07-3018\nalways_true | t\npg_epoch    | 09-12-3018\nunix_epoch  | 09-10-3018\ncustom_1    | 09-13-3018\ncustom_2    | 09-07-3018\n-[ RECORD 7 ]-----------\ntime        | 09-14-3018\nno_epoch    | 09-14-3018\nalways_true | t\npg_epoch    | 09-12-3018\nunix_epoch  | 09-10-3018\ncustom_1    | 09-13-3018\ncustom_2    | 09-14-3018\n-[ RECORD 8 ]-----------\ntime        | 09-15-3018\nno_epoch    | 09-14-3018\nalways_true | t\npg_epoch    | 09-12-3018\nunix_epoch  | 09-10-3018\ncustom_1    | 09-13-3018\ncustom_2    | 09-14-3018\n\n\\x\n--really old origin works if date around that time\nSELECT time, time_bucket(INTERVAL '1 week', time, timestamp without time zone '4710-11-24 01:01:01.0 BC')\nFROM unnest(ARRAY[\n    timestamp without time zone '4710-11-24 01:01:01.0 BC',\n    timestamp without time zone '4710-11-25 01:01:01.0 BC',\n    timestamp without time zone '2001-01-01',\n    timestamp without time zone '3001-01-01'\n    ]) AS time;\n            time             |         time_bucket         \n-----------------------------+-----------------------------\n Sat Nov 24 01:01:01 4710 BC | Sat Nov 24 01:01:01 4710 BC\n Sun Nov 25 01:01:01 4710 BC | Sat Nov 24 01:01:01 4710 BC\n Mon Jan 01 00:00:00 2001    | Sat Dec 30 01:01:01 2000\n Thu Jan 01 00:00:00 3001    | Sat Dec 27 01:01:01 3000\n\nSELECT time, time_bucket(INTERVAL '1 week', time, timestamp without time zone '294270-12-30 23:59:59.9999')\nFROM unnest(ARRAY[\n    timestamp without time zone '294270-12-29 23:59:59.9999',\n    timestamp without time zone '294270-12-30 23:59:59.9999',\n    timestamp without time zone '294270-12-31 23:59:59.9999',\n    timestamp without time zone '2001-01-01',\n    timestamp without time zone '3001-01-01'\n    ]) AS time;\n              time               |           time_bucket           \n---------------------------------+---------------------------------\n Thu Dec 29 23:59:59.9999 294270 | Fri Dec 23 23:59:59.9999 294270\n Fri Dec 30 23:59:59.9999 294270 | Fri Dec 30 23:59:59.9999 294270\n Sat Dec 31 23:59:59.9999 294270 | Fri Dec 30 23:59:59.9999 294270\n Mon Jan 01 00:00:00 2001        | Fri Dec 29 23:59:59.9999 2000\n Thu Jan 01 00:00:00 3001        | Fri Dec 26 23:59:59.9999 3000\n\n\\set ON_ERROR_STOP 0\n--really old origin + very new data + long period errors\nSELECT time, time_bucket(INTERVAL '100000 day', time, timestamp without time zone '4710-11-24 01:01:01.0 BC')\nFROM unnest(ARRAY[\n    timestamp without time zone '294270-12-31 23:59:59.9999'\n    ]) AS time;\nERROR:  timestamp out of range\nSELECT time, time_bucket(INTERVAL '100000 day', time, timestamp with time zone '4710-11-25 01:01:01.0 BC')\nFROM unnest(ARRAY[\n    timestamp with time zone '294270-12-30 23:59:59.9999'\n    ]) AS time;\nERROR:  timestamp out of range\n--really high origin + old data + long period errors out\nSELECT time, time_bucket(INTERVAL '10000000 day', time, timestamp without time zone '294270-12-31 23:59:59.9999')\nFROM unnest(ARRAY[\n    timestamp without time zone '4710-11-24 01:01:01.0 BC'\n    ]) AS time;\nERROR:  timestamp out of range\nSELECT time, time_bucket(INTERVAL '10000000 day', time, timestamp with time zone '294270-12-31 23:59:59.9999')\nFROM unnest(ARRAY[\n    timestamp with time zone '4710-11-24 01:01:01.0 BC'\n    ]) AS time;\nERROR:  timestamp out of range\n\\set ON_ERROR_STOP 1\n-------------------------------------------\n--- Test time_bucket with month periods ---\n-------------------------------------------\nSET datestyle TO ISO;\nSELECT\n  time::date,\n  time_bucket('1 month', time::date) AS \"1m\",\n  time_bucket('2 month', time::date) AS \"2m\",\n  time_bucket('3 month', time::date) AS \"3m\",\n  time_bucket('1 month', time::date, '2000-02-01'::date) AS \"1m origin\",\n  time_bucket('2 month', time::date, '2000-02-01'::date) AS \"2m origin\",\n  time_bucket('3 month', time::date, '2000-02-01'::date) AS \"3m origin\"\nFROM generate_series('1990-01-03'::date,'1990-06-03'::date,'1month'::interval) time;\n    time    |     1m     |     2m     |     3m     | 1m origin  | 2m origin  | 3m origin  \n------------+------------+------------+------------+------------+------------+------------\n 1990-01-03 | 1990-01-01 | 1990-01-01 | 1990-01-01 | 1990-01-01 | 1989-12-01 | 1989-11-01\n 1990-02-03 | 1990-02-01 | 1990-01-01 | 1990-01-01 | 1990-02-01 | 1990-02-01 | 1990-02-01\n 1990-03-03 | 1990-03-01 | 1990-03-01 | 1990-01-01 | 1990-03-01 | 1990-02-01 | 1990-02-01\n 1990-04-03 | 1990-04-01 | 1990-03-01 | 1990-04-01 | 1990-04-01 | 1990-04-01 | 1990-02-01\n 1990-05-03 | 1990-05-01 | 1990-05-01 | 1990-04-01 | 1990-05-01 | 1990-04-01 | 1990-05-01\n 1990-06-03 | 1990-06-01 | 1990-05-01 | 1990-04-01 | 1990-06-01 | 1990-06-01 | 1990-05-01\n\nSELECT\n  time,\n  time_bucket('1 month', time) AS \"1m\",\n  time_bucket('2 month', time) AS \"2m\",\n  time_bucket('3 month', time) AS \"3m\",\n  time_bucket('1 month', time, '2000-02-01'::timestamp) AS \"1m origin\",\n  time_bucket('2 month', time, '2000-02-01'::timestamp) AS \"2m origin\",\n  time_bucket('3 month', time, '2000-02-01'::timestamp) AS \"3m origin\"\nFROM generate_series('1990-01-03'::timestamp,'1990-06-03'::timestamp,'1month'::interval) time;\n        time         |         1m          |         2m          |         3m          |      1m origin      |      2m origin      |      3m origin      \n---------------------+---------------------+---------------------+---------------------+---------------------+---------------------+---------------------\n 1990-01-03 00:00:00 | 1990-01-01 00:00:00 | 1990-01-01 00:00:00 | 1990-01-01 00:00:00 | 1990-01-01 00:00:00 | 1989-12-01 00:00:00 | 1989-11-01 00:00:00\n 1990-02-03 00:00:00 | 1990-02-01 00:00:00 | 1990-01-01 00:00:00 | 1990-01-01 00:00:00 | 1990-02-01 00:00:00 | 1990-02-01 00:00:00 | 1990-02-01 00:00:00\n 1990-03-03 00:00:00 | 1990-03-01 00:00:00 | 1990-03-01 00:00:00 | 1990-01-01 00:00:00 | 1990-03-01 00:00:00 | 1990-02-01 00:00:00 | 1990-02-01 00:00:00\n 1990-04-03 00:00:00 | 1990-04-01 00:00:00 | 1990-03-01 00:00:00 | 1990-04-01 00:00:00 | 1990-04-01 00:00:00 | 1990-04-01 00:00:00 | 1990-02-01 00:00:00\n 1990-05-03 00:00:00 | 1990-05-01 00:00:00 | 1990-05-01 00:00:00 | 1990-04-01 00:00:00 | 1990-05-01 00:00:00 | 1990-04-01 00:00:00 | 1990-05-01 00:00:00\n 1990-06-03 00:00:00 | 1990-06-01 00:00:00 | 1990-05-01 00:00:00 | 1990-04-01 00:00:00 | 1990-06-01 00:00:00 | 1990-06-01 00:00:00 | 1990-05-01 00:00:00\n\nSELECT\n  time,\n  time_bucket('1 month', time) AS \"1m\",\n  time_bucket('2 month', time) AS \"2m\",\n  time_bucket('3 month', time) AS \"3m\",\n  time_bucket('1 month', time, '2000-02-01'::timestamptz) AS \"1m origin\",\n  time_bucket('2 month', time, '2000-02-01'::timestamptz) AS \"2m origin\",\n  time_bucket('3 month', time, '2000-02-01'::timestamptz) AS \"3m origin\"\nFROM generate_series('1990-01-03'::timestamptz,'1990-06-03'::timestamptz,'1month'::interval) time;\n          time          |           1m           |           2m           |           3m           |       1m origin        |       2m origin        |       3m origin        \n------------------------+------------------------+------------------------+------------------------+------------------------+------------------------+------------------------\n 1990-01-03 00:00:00-05 | 1989-12-31 19:00:00-05 | 1989-12-31 19:00:00-05 | 1989-12-31 19:00:00-05 | 1989-12-31 19:00:00-05 | 1989-11-30 19:00:00-05 | 1989-10-31 19:00:00-05\n 1990-02-03 00:00:00-05 | 1990-01-31 19:00:00-05 | 1989-12-31 19:00:00-05 | 1989-12-31 19:00:00-05 | 1990-01-31 19:00:00-05 | 1990-01-31 19:00:00-05 | 1990-01-31 19:00:00-05\n 1990-03-03 00:00:00-05 | 1990-02-28 19:00:00-05 | 1990-02-28 19:00:00-05 | 1989-12-31 19:00:00-05 | 1990-02-28 19:00:00-05 | 1990-01-31 19:00:00-05 | 1990-01-31 19:00:00-05\n 1990-04-03 00:00:00-04 | 1990-03-31 19:00:00-05 | 1990-02-28 19:00:00-05 | 1990-03-31 19:00:00-05 | 1990-03-31 19:00:00-05 | 1990-03-31 19:00:00-05 | 1990-01-31 19:00:00-05\n 1990-05-03 00:00:00-04 | 1990-04-30 20:00:00-04 | 1990-04-30 20:00:00-04 | 1990-03-31 19:00:00-05 | 1990-04-30 20:00:00-04 | 1990-03-31 19:00:00-05 | 1990-04-30 20:00:00-04\n 1990-06-03 00:00:00-04 | 1990-05-31 20:00:00-04 | 1990-04-30 20:00:00-04 | 1990-03-31 19:00:00-05 | 1990-05-31 20:00:00-04 | 1990-05-31 20:00:00-04 | 1990-04-30 20:00:00-04\n\n---------------------------------------\n--- Test time_bucket with timezones ---\n---------------------------------------\n-- test NULL args\nSELECT\ntime_bucket(NULL::interval,now(),'Europe/Berlin'),\ntime_bucket('1day',NULL::timestamptz,'Europe/Berlin'),\ntime_bucket('1day',now(),NULL::text),\ntime_bucket('1day',timestamptz '2020-02-03','Europe/Berlin',NULL),\ntime_bucket('1day',timestamptz '2020-02-03','Europe/Berlin','2020-04-01',NULL),\ntime_bucket('1day',timestamptz '2020-02-03','Europe/Berlin',NULL,NULL),\ntime_bucket('1day',timestamptz '2020-02-03','Europe/Berlin',\"offset\":=NULL::interval),\ntime_bucket('1day',timestamptz '2020-02-03','Europe/Berlin',origin:=NULL::timestamptz);\n time_bucket | time_bucket | time_bucket |      time_bucket       |      time_bucket       |      time_bucket       |      time_bucket       |      time_bucket       \n-------------+-------------+-------------+------------------------+------------------------+------------------------+------------------------+------------------------\n             |             |             | 2020-02-02 18:00:00-05 | 2020-02-03 00:00:00-05 | 2020-02-02 18:00:00-05 | 2020-02-02 18:00:00-05 | 2020-02-02 18:00:00-05\n\nSET datestyle TO ISO;\nSELECT\n  time_bucket('1day', ts) AS \"UTC\",\n  time_bucket('1day', ts, 'Europe/Berlin') AS \"Berlin\",\n  time_bucket('1day', ts, 'Europe/London') AS \"London\",\n  time_bucket('1day', ts, 'America/New_York') AS \"New York\",\n  time_bucket('1day', ts, 'PST') AS \"PST\",\n  time_bucket('1day', ts, current_setting('timezone')) AS \"current\"\nFROM generate_series('1999-12-31 17:00'::timestamptz,'2000-01-02 3:00'::timestamptz, '1hour'::interval) ts;\n          UTC           |         Berlin         |         London         |        New York        |          PST           |        current         \n------------------------+------------------------+------------------------+------------------------+------------------------+------------------------\n 1999-12-30 19:00:00-05 | 1999-12-30 18:00:00-05 | 1999-12-30 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-30 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-30 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-31 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-31 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-31 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 2000-01-01 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-02 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-02 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-02 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-02 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-02 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-02 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-02 00:00:00-05 | 2000-01-02 03:00:00-05 | 2000-01-02 00:00:00-05\n\nSELECT\n  time_bucket('1month', ts) AS \"UTC\",\n  time_bucket('1month', ts, 'Europe/Berlin') AS \"Berlin\",\n  time_bucket('1month', ts, 'America/New_York') AS \"New York\",\n  time_bucket('1month', ts, current_setting('timezone')) AS \"current\",\n  time_bucket('2month', ts, current_setting('timezone')) AS \"2m\",\n  time_bucket('2month', ts, current_setting('timezone'), '2000-02-01'::timestamp) AS \"2m origin\",\n  time_bucket('2month', ts, current_setting('timezone'), \"offset\":='14 day'::interval) AS \"2m offset\",\n  time_bucket('2month', ts, current_setting('timezone'), '2000-02-01'::timestamp, '7 day'::interval) AS \"2m offset + origin\"\nFROM generate_series('1999-12-01'::timestamptz,'2000-09-01'::timestamptz, '9 day'::interval) ts;\n          UTC           |         Berlin         |        New York        |        current         |           2m           |       2m origin        |       2m offset        |   2m offset + origin   \n------------------------+------------------------+------------------------+------------------------+------------------------+------------------------+------------------------+------------------------\n 1999-11-30 19:00:00-05 | 1999-11-30 18:00:00-05 | 1999-12-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-15 00:00:00-05 | 1999-10-08 00:00:00-04\n 1999-11-30 19:00:00-05 | 1999-11-30 18:00:00-05 | 1999-12-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 1999-11-30 19:00:00-05 | 1999-11-30 18:00:00-05 | 1999-12-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 1999-11-30 19:00:00-05 | 1999-11-30 18:00:00-05 | 1999-12-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 2000-01-31 19:00:00-05 | 2000-01-31 18:00:00-05 | 2000-02-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 2000-01-31 19:00:00-05 | 2000-01-31 18:00:00-05 | 2000-02-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-01-31 19:00:00-05 | 2000-01-31 18:00:00-05 | 2000-02-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-01-31 19:00:00-05 | 2000-01-31 18:00:00-05 | 2000-02-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-02-29 19:00:00-05 | 2000-02-29 18:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-02-29 19:00:00-05 | 2000-02-29 18:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-02-29 19:00:00-05 | 2000-02-29 18:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-03-31 19:00:00-05 | 2000-03-31 17:00:00-05 | 2000-04-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-03-31 19:00:00-05 | 2000-03-31 17:00:00-05 | 2000-04-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-04-08 00:00:00-04\n 2000-03-31 19:00:00-05 | 2000-03-31 17:00:00-05 | 2000-04-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-04-08 00:00:00-04\n 2000-04-30 20:00:00-04 | 2000-04-30 18:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-04-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-04-08 00:00:00-04\n 2000-04-30 20:00:00-04 | 2000-04-30 18:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-04-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-04-08 00:00:00-04\n 2000-04-30 20:00:00-04 | 2000-04-30 18:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-04-01 00:00:00-05 | 2000-05-15 00:00:00-04 | 2000-04-08 00:00:00-04\n 2000-04-30 20:00:00-04 | 2000-04-30 18:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-04-01 00:00:00-05 | 2000-05-15 00:00:00-04 | 2000-04-08 00:00:00-04\n 2000-05-31 20:00:00-04 | 2000-05-31 18:00:00-04 | 2000-06-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-15 00:00:00-04 | 2000-04-08 00:00:00-04\n 2000-05-31 20:00:00-04 | 2000-05-31 18:00:00-04 | 2000-06-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-15 00:00:00-04 | 2000-06-08 00:00:00-04\n 2000-05-31 20:00:00-04 | 2000-05-31 18:00:00-04 | 2000-06-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-15 00:00:00-04 | 2000-06-08 00:00:00-04\n 2000-06-30 20:00:00-04 | 2000-06-30 18:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-15 00:00:00-04 | 2000-06-08 00:00:00-04\n 2000-06-30 20:00:00-04 | 2000-06-30 18:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-15 00:00:00-04 | 2000-06-08 00:00:00-04\n 2000-06-30 20:00:00-04 | 2000-06-30 18:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-07-15 00:00:00-04 | 2000-06-08 00:00:00-04\n 2000-06-30 20:00:00-04 | 2000-06-30 18:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-07-15 00:00:00-04 | 2000-06-08 00:00:00-04\n 2000-07-31 20:00:00-04 | 2000-07-31 18:00:00-04 | 2000-08-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-15 00:00:00-04 | 2000-08-08 00:00:00-04\n 2000-07-31 20:00:00-04 | 2000-07-31 18:00:00-04 | 2000-08-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-15 00:00:00-04 | 2000-08-08 00:00:00-04\n 2000-07-31 20:00:00-04 | 2000-07-31 18:00:00-04 | 2000-08-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-15 00:00:00-04 | 2000-08-08 00:00:00-04\n\nRESET datestyle;\n-------------------------------------\n--- Test time input functions --\n-------------------------------------\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION test.interval_to_internal(coltype REGTYPE, value ANYELEMENT = NULL::BIGINT) RETURNS BIGINT\nAS :MODULE_PATHNAME, 'ts_dimension_interval_to_internal_test' LANGUAGE C VOLATILE;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT test.interval_to_internal('TIMESTAMP'::regtype, INTERVAL '1 day');\n interval_to_internal \n----------------------\n          86400000000\n\nSELECT test.interval_to_internal('TIMESTAMP'::regtype, 86400000000);\n interval_to_internal \n----------------------\n          86400000000\n\n---should give warning\nSELECT test.interval_to_internal('TIMESTAMP'::regtype, 86400);\nWARNING:  unexpected interval: smaller than one second\nHINT:  The interval is specified in microseconds.\n interval_to_internal \n----------------------\n                86400\n\nSELECT test.interval_to_internal('TIMESTAMP'::regtype);\n interval_to_internal \n----------------------\n         604800000000\n\nSELECT test.interval_to_internal('BIGINT'::regtype, 2147483649::bigint);\n interval_to_internal \n----------------------\n           2147483649\n\n-- Default interval for integer is supported as part of\n-- hypertable generalization\nSELECT test.interval_to_internal('INT'::regtype);\n interval_to_internal \n----------------------\n               100000\n\nSELECT test.interval_to_internal('SMALLINT'::regtype);\n interval_to_internal \n----------------------\n                10000\n\nSELECT test.interval_to_internal('BIGINT'::regtype);\n interval_to_internal \n----------------------\n              1000000\n\nSELECT test.interval_to_internal('TIMESTAMPTZ'::regtype);\n interval_to_internal \n----------------------\n         604800000000\n\nSELECT test.interval_to_internal('TIMESTAMP'::regtype);\n interval_to_internal \n----------------------\n         604800000000\n\nSELECT test.interval_to_internal('DATE'::regtype);\n interval_to_internal \n----------------------\n         604800000000\n\n\\set VERBOSITY terse\n\\set ON_ERROR_STOP 0\nSELECT test.interval_to_internal('INT'::regtype, 2147483649::bigint);\nERROR:  invalid interval: must be between 1 and 2147483647\nSELECT test.interval_to_internal('SMALLINT'::regtype, 32768::bigint);\nERROR:  invalid interval: must be between 1 and 32767\nSELECT test.interval_to_internal('TEXT'::regtype, 32768::bigint);\nERROR:  invalid type for dimension \"testcol\"\nSELECT test.interval_to_internal('INT'::regtype, INTERVAL '1 day');\nERROR:  invalid interval type for integer dimension\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "test/expected/timestamp-16.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- Utility function for grouping/slotting time with a given interval.\nCREATE OR REPLACE FUNCTION date_group(\n    field           timestamp,\n    group_interval  interval\n)\n    RETURNS timestamp LANGUAGE SQL STABLE AS\n$BODY$\n    SELECT to_timestamp((EXTRACT(EPOCH from $1)::int /\n        EXTRACT(EPOCH from group_interval)::int) *\n        EXTRACT(EPOCH from group_interval)::int)::timestamp;\n$BODY$;\nCREATE TABLE PUBLIC.\"testNs\" (\n  \"timeCustom\" TIMESTAMP NOT NULL,\n  device_id TEXT NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL,\n  series_bool BOOLEAN NULL\n);\nCREATE INDEX ON PUBLIC.\"testNs\" (device_id, \"timeCustom\" DESC NULLS LAST) WHERE device_id IS NOT NULL;\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA \"testNs\" AUTHORIZATION :ROLE_DEFAULT_PERM_USER;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT * FROM create_hypertable('\"public\".\"testNs\"', 'timeCustom', 'device_id', 2, associated_schema_name=>'testNs' );\nWARNING:  column type \"timestamp without time zone\" used for \"timeCustom\" does not follow best practices\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             1 | public      | testNs     | t\n\n\\c :TEST_DBNAME\nINSERT INTO PUBLIC.\"testNs\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n('2009-11-12T01:00:00+00:00', 'dev1', 1.5, 1),\n('2009-11-12T01:00:00+00:00', 'dev1', 1.5, 2),\n('2009-11-10T23:00:02+00:00', 'dev1', 2.5, 3);\nINSERT INTO PUBLIC.\"testNs\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n('2009-11-10T23:00:00+00:00', 'dev2', 1.5, 1),\n('2009-11-10T23:00:00+00:00', 'dev2', 1.5, 2);\nSELECT * FROM PUBLIC.\"testNs\";\n        timeCustom        | device_id | series_0 | series_1 | series_2 | series_bool \n--------------------------+-----------+----------+----------+----------+-------------\n Thu Nov 12 01:00:00 2009 | dev1      |      1.5 |        1 |          | \n Thu Nov 12 01:00:00 2009 | dev1      |      1.5 |        2 |          | \n Tue Nov 10 23:00:02 2009 | dev1      |      2.5 |        3 |          | \n Tue Nov 10 23:00:00 2009 | dev2      |      1.5 |        1 |          | \n Tue Nov 10 23:00:00 2009 | dev2      |      1.5 |        2 |          | \n\nSET client_min_messages = WARNING;\n\\echo 'The next 2 queries will differ in output between UTC and EST since the mod is on the 100th hour UTC'\nThe next 2 queries will differ in output between UTC and EST since the mod is on the 100th hour UTC\nSET timezone = 'UTC';\nSELECT date_group(\"timeCustom\", '100 days') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC;\n           time           | sum \n--------------------------+-----\n Sun Sep 13 00:00:00 2009 | 8.5\n\nSET timezone = 'EST';\nSELECT date_group(\"timeCustom\", '100 days') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC;\n           time           | sum \n--------------------------+-----\n Sat Sep 12 19:00:00 2009 | 8.5\n\n\\echo 'The rest of the queries will be the same in output between UTC and EST'\nThe rest of the queries will be the same in output between UTC and EST\nSET timezone = 'UTC';\nSELECT date_group(\"timeCustom\", '1 day') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC;\n           time           | sum \n--------------------------+-----\n Tue Nov 10 00:00:00 2009 | 5.5\n Thu Nov 12 00:00:00 2009 |   3\n\nSET timezone = 'EST';\nSELECT date_group(\"timeCustom\", '1 day') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC;\n           time           | sum \n--------------------------+-----\n Mon Nov 09 19:00:00 2009 | 5.5\n Wed Nov 11 19:00:00 2009 |   3\n\nSET timezone = 'UTC';\nSELECT *\nFROM PUBLIC.\"testNs\"\nWHERE \"timeCustom\" >= TIMESTAMP '2009-11-10T23:00:00'\nAND \"timeCustom\" < TIMESTAMP '2009-11-12T01:00:00' ORDER BY \"timeCustom\" DESC, device_id, series_1;\n        timeCustom        | device_id | series_0 | series_1 | series_2 | series_bool \n--------------------------+-----------+----------+----------+----------+-------------\n Tue Nov 10 23:00:02 2009 | dev1      |      2.5 |        3 |          | \n Tue Nov 10 23:00:00 2009 | dev2      |      1.5 |        1 |          | \n Tue Nov 10 23:00:00 2009 | dev2      |      1.5 |        2 |          | \n\nSET timezone = 'EST';\nSELECT *\nFROM PUBLIC.\"testNs\"\nWHERE \"timeCustom\" >= TIMESTAMP '2009-11-10T23:00:00'\nAND \"timeCustom\" < TIMESTAMP '2009-11-12T01:00:00' ORDER BY \"timeCustom\" DESC, device_id, series_1;\n        timeCustom        | device_id | series_0 | series_1 | series_2 | series_bool \n--------------------------+-----------+----------+----------+----------+-------------\n Tue Nov 10 23:00:02 2009 | dev1      |      2.5 |        3 |          | \n Tue Nov 10 23:00:00 2009 | dev2      |      1.5 |        1 |          | \n Tue Nov 10 23:00:00 2009 | dev2      |      1.5 |        2 |          | \n\nSET timezone = 'UTC';\nSELECT date_group(\"timeCustom\", '1 day') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC LIMIT 2;\n           time           | sum \n--------------------------+-----\n Tue Nov 10 00:00:00 2009 | 5.5\n Thu Nov 12 00:00:00 2009 |   3\n\nSET timezone = 'EST';\nSELECT date_group(\"timeCustom\", '1 day') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC LIMIT 2;\n           time           | sum \n--------------------------+-----\n Mon Nov 09 19:00:00 2009 | 5.5\n Wed Nov 11 19:00:00 2009 |   3\n\n------------------------------------\n-- Test time conversion functions --\n------------------------------------\n\\set ON_ERROR_STOP 0\nSET timezone = 'UTC';\n-- Conversion to timestamp using Postgres built-in function taking\n-- double. Gives inaccurate result on Postgres <= 9.6.2. Accurate on\n-- Postgres >= 9.6.3.\nSELECT to_timestamp(1486480176.236538);\n            to_timestamp             \n-------------------------------------\n Tue Feb 07 15:09:36.236538 2017 UTC\n\n-- extension-specific version taking microsecond UNIX timestamp\nSELECT _timescaledb_functions.to_timestamp(1486480176236538);\n            to_timestamp             \n-------------------------------------\n Tue Feb 07 15:09:36.236538 2017 UTC\n\n-- Should be the inverse of the statement above.\nSELECT _timescaledb_functions.to_unix_microseconds('2017-02-07 15:09:36.236538+00');\n to_unix_microseconds \n----------------------\n     1486480176236538\n\n-- For timestamps, BIGINT MAX represents +Infinity and BIGINT MIN\n-- -Infinity. We keep this notion for UNIX epoch time:\nSELECT _timescaledb_functions.to_unix_microseconds('+infinity');\n to_unix_microseconds \n----------------------\n  9223372036854775807\n\nSELECT _timescaledb_functions.to_timestamp(9223372036854775807);\n to_timestamp \n--------------\n infinity\n\nSELECT _timescaledb_functions.to_unix_microseconds('-infinity');\n to_unix_microseconds \n----------------------\n -9223372036854775808\n\nSELECT _timescaledb_functions.to_timestamp(-9223372036854775808);\n to_timestamp \n--------------\n -infinity\n\n-- In UNIX microseconds, the largest bigint value below infinity\n-- (BIGINT MAX) is smaller than internal date upper bound and should\n-- therefore be OK. Further, converting to the internal postgres epoch\n-- cannot overflow a 64-bit INTEGER since the postgres epoch is at a\n-- later date compared to the UNIX epoch, and is therefore represented\n-- by a smaller number\nSELECT _timescaledb_functions.to_timestamp(9223372036854775806);\n             to_timestamp              \n---------------------------------------\n Sun Jan 10 04:00:54.775806 294247 UTC\n\n-- Julian day zero is -210866803200000000 microseconds from UNIX epoch\nSELECT _timescaledb_functions.to_timestamp(-210866803200000000);\n          to_timestamp           \n---------------------------------\n Mon Nov 24 00:00:00 4714 UTC BC\n\n\\set VERBOSITY default\n-- Going beyond Julian day zero should give out-of-range error\nSELECT _timescaledb_functions.to_timestamp(-210866803200000001);\nERROR:  timestamp out of range\n-- Lower bound on date (should return the Julian day zero UNIX timestamp above)\nSELECT _timescaledb_functions.to_unix_microseconds('4714-11-24 00:00:00+00 BC');\n to_unix_microseconds \n----------------------\n  -210866803200000000\n\n-- Going beyond lower bound on date should return out-of-range\nSELECT _timescaledb_functions.to_unix_microseconds('4714-11-23 23:59:59.999999+00 BC');\nERROR:  timestamp out of range: \"4714-11-23 23:59:59.999999+00 BC\"\nLINE 1: ...ELECT _timescaledb_functions.to_unix_microseconds('4714-11-2...\n                                                             ^\n-- The upper bound for Postgres TIMESTAMPTZ\nSELECT timestamp '294276-12-31 23:59:59.999999+00';\n             timestamp             \n-----------------------------------\n Sun Dec 31 23:59:59.999999 294276\n\n-- Going beyond the upper bound, should fail\nSELECT timestamp '294276-12-31 23:59:59.999999+00' + interval '1 us';\nERROR:  timestamp out of range\n-- Cannot represent the upper bound timestamp with a UNIX microsecond timestamp\n-- since the Postgres epoch is at a later date than the UNIX epoch.\nSELECT _timescaledb_functions.to_unix_microseconds('294276-12-31 23:59:59.999999+00');\nERROR:  timestamp out of range\n-- Subtracting the difference between the two epochs (10957 days) should bring\n-- us within range.\nSELECT timestamp '294276-12-31 23:59:59.999999+00' - interval '10957 days';\n             ?column?              \n-----------------------------------\n Fri Jan 01 23:59:59.999999 294247\n\nSELECT _timescaledb_functions.to_unix_microseconds('294247-01-01 23:59:59.999999');\n to_unix_microseconds \n----------------------\n  9223371331199999999\n\n-- Adding one microsecond should take us out-of-range again\nSELECT timestamp '294247-01-01 23:59:59.999999' + interval '1 us';\n          ?column?          \n----------------------------\n Sat Jan 02 00:00:00 294247\n\nSELECT _timescaledb_functions.to_unix_microseconds(timestamp '294247-01-01 23:59:59.999999' + interval '1 us');\nERROR:  timestamp out of range\n--no time_bucketing of dates not by integer # of days\nSELECT time_bucket('1 hour', DATE '2012-01-01');\nERROR:  interval must not have sub-day precision\nSELECT time_bucket('25 hour', DATE '2012-01-01');\nERROR:  interval must be a multiple of a day\n\\set ON_ERROR_STOP 1\nSELECT time_bucket(INTERVAL '1 day', TIMESTAMP '2011-01-02 01:01:01');\n       time_bucket        \n--------------------------\n Sun Jan 02 00:00:00 2011\n\nSELECT time, time_bucket(INTERVAL '2 day ', time)\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-01 01:01:01',\n    TIMESTAMP '2011-01-02 01:01:01',\n    TIMESTAMP '2011-01-03 01:01:01',\n    TIMESTAMP '2011-01-04 01:01:01'\n    ]) AS time;\n           time           |       time_bucket        \n--------------------------+--------------------------\n Sat Jan 01 01:01:01 2011 | Sat Jan 01 00:00:00 2011\n Sun Jan 02 01:01:01 2011 | Sat Jan 01 00:00:00 2011\n Mon Jan 03 01:01:01 2011 | Mon Jan 03 00:00:00 2011\n Tue Jan 04 01:01:01 2011 | Mon Jan 03 00:00:00 2011\n\nSELECT int_def, time_bucket(int_def,TIMESTAMP '2011-01-02 01:01:01.111')\nFROM unnest(ARRAY[\n    INTERVAL '1 millisecond',\n    INTERVAL '1 second',\n    INTERVAL '1 minute',\n    INTERVAL '1 hour',\n    INTERVAL '1 day',\n    INTERVAL '2 millisecond',\n    INTERVAL '2 second',\n    INTERVAL '2 minute',\n    INTERVAL '2 hour',\n    INTERVAL '2 day'\n    ]) AS int_def;\n   int_def    |         time_bucket          \n--------------+------------------------------\n @ 0.001 secs | Sun Jan 02 01:01:01.111 2011\n @ 1 sec      | Sun Jan 02 01:01:01 2011\n @ 1 min      | Sun Jan 02 01:01:00 2011\n @ 1 hour     | Sun Jan 02 01:00:00 2011\n @ 1 day      | Sun Jan 02 00:00:00 2011\n @ 0.002 secs | Sun Jan 02 01:01:01.11 2011\n @ 2 secs     | Sun Jan 02 01:01:00 2011\n @ 2 mins     | Sun Jan 02 01:00:00 2011\n @ 2 hours    | Sun Jan 02 00:00:00 2011\n @ 2 days     | Sat Jan 01 00:00:00 2011\n\n\\set ON_ERROR_STOP 0\nSELECT time_bucket(INTERVAL '1 year 1d',TIMESTAMP '2011-01-02 01:01:01.111');\nERROR:  month intervals cannot have day or time component\nSELECT time_bucket(INTERVAL '1 month 1 minute',TIMESTAMP '2011-01-02 01:01:01.111');\nERROR:  month intervals cannot have day or time component\n\\set ON_ERROR_STOP 1\nSELECT time, time_bucket(INTERVAL '5 minute', time)\nFROM unnest(ARRAY[\n    TIMESTAMP '1970-01-01 00:59:59.999999',\n    TIMESTAMP '1970-01-01 01:01:00',\n    TIMESTAMP '1970-01-01 01:04:59.999999',\n    TIMESTAMP '1970-01-01 01:05:00'\n    ]) AS time;\n              time               |       time_bucket        \n---------------------------------+--------------------------\n Thu Jan 01 00:59:59.999999 1970 | Thu Jan 01 00:55:00 1970\n Thu Jan 01 01:01:00 1970        | Thu Jan 01 01:00:00 1970\n Thu Jan 01 01:04:59.999999 1970 | Thu Jan 01 01:00:00 1970\n Thu Jan 01 01:05:00 1970        | Thu Jan 01 01:05:00 1970\n\nSELECT time, time_bucket(INTERVAL '5 minute', time)\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-02 01:04:59.999999',\n    TIMESTAMP '2011-01-02 01:05:00',\n    TIMESTAMP '2011-01-02 01:09:59.999999',\n    TIMESTAMP '2011-01-02 01:10:00'\n    ]) AS time;\n              time               |       time_bucket        \n---------------------------------+--------------------------\n Sun Jan 02 01:04:59.999999 2011 | Sun Jan 02 01:00:00 2011\n Sun Jan 02 01:05:00 2011        | Sun Jan 02 01:05:00 2011\n Sun Jan 02 01:09:59.999999 2011 | Sun Jan 02 01:05:00 2011\n Sun Jan 02 01:10:00 2011        | Sun Jan 02 01:10:00 2011\n\n--offset with interval\nSELECT time, time_bucket(INTERVAL '5 minute', time ,  INTERVAL '2 minutes')\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-02 01:01:59.999999',\n    TIMESTAMP '2011-01-02 01:02:00',\n    TIMESTAMP '2011-01-02 01:06:59.999999',\n    TIMESTAMP '2011-01-02 01:07:00'\n    ]) AS time;\n              time               |       time_bucket        \n---------------------------------+--------------------------\n Sun Jan 02 01:01:59.999999 2011 | Sun Jan 02 00:57:00 2011\n Sun Jan 02 01:02:00 2011        | Sun Jan 02 01:02:00 2011\n Sun Jan 02 01:06:59.999999 2011 | Sun Jan 02 01:02:00 2011\n Sun Jan 02 01:07:00 2011        | Sun Jan 02 01:07:00 2011\n\nSELECT time, time_bucket(INTERVAL '5 minute', time , - INTERVAL '2 minutes')\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-02 01:02:59.999999',\n    TIMESTAMP '2011-01-02 01:03:00',\n    TIMESTAMP '2011-01-02 01:07:59.999999',\n    TIMESTAMP '2011-01-02 01:08:00'\n    ]) AS time;\n              time               |       time_bucket        \n---------------------------------+--------------------------\n Sun Jan 02 01:02:59.999999 2011 | Sun Jan 02 00:58:00 2011\n Sun Jan 02 01:03:00 2011        | Sun Jan 02 01:03:00 2011\n Sun Jan 02 01:07:59.999999 2011 | Sun Jan 02 01:03:00 2011\n Sun Jan 02 01:08:00 2011        | Sun Jan 02 01:08:00 2011\n\n--offset with infinity\n-- timestamp\nSELECT time, time_bucket(INTERVAL '1 week', time, INTERVAL '1 day')\nFROM unnest(ARRAY[\n    timestamp '-Infinity',\n    timestamp 'Infinity'\n    ]) AS time;\n   time    | time_bucket \n-----------+-------------\n -infinity | -infinity\n infinity  | infinity\n\n-- timestamptz\nSELECT time, time_bucket(INTERVAL '1 week', time, INTERVAL '1 day')\nFROM unnest(ARRAY[\n    timestamp with time zone '-Infinity',\n    timestamp with time zone 'Infinity'\n    ]) AS time;\n   time    | time_bucket \n-----------+-------------\n -infinity | -infinity\n infinity  | infinity\n\n-- Date\nSELECT date, time_bucket(INTERVAL '1 week', date, INTERVAL '1 day')\nFROM unnest(ARRAY[\n    date '-Infinity',\n    date 'Infinity'\n    ]) AS date;\n   date    | time_bucket \n-----------+-------------\n -infinity | -infinity\n infinity  | infinity\n\n--example to align with an origin\nSELECT time, time_bucket(INTERVAL '5 minute', time - (TIMESTAMP '2011-01-02 00:02:00' - TIMESTAMP 'epoch')) +  (TIMESTAMP '2011-01-02 00:02:00'-TIMESTAMP 'epoch')\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-02 01:01:59.999999',\n    TIMESTAMP '2011-01-02 01:02:00',\n    TIMESTAMP '2011-01-02 01:06:59.999999',\n    TIMESTAMP '2011-01-02 01:07:00'\n    ]) AS time;\n              time               |         ?column?         \n---------------------------------+--------------------------\n Sun Jan 02 01:01:59.999999 2011 | Sun Jan 02 00:57:00 2011\n Sun Jan 02 01:02:00 2011        | Sun Jan 02 01:02:00 2011\n Sun Jan 02 01:06:59.999999 2011 | Sun Jan 02 01:02:00 2011\n Sun Jan 02 01:07:00 2011        | Sun Jan 02 01:07:00 2011\n\n--rounding version\nSELECT time, time_bucket(INTERVAL '5 minute', time , - INTERVAL '2.5 minutes') + INTERVAL '2 minutes 30 seconds'\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-02 01:05:01',\n    TIMESTAMP '2011-01-02 01:07:29',\n    TIMESTAMP '2011-01-02 01:02:30',\n    TIMESTAMP '2011-01-02 01:07:30',\n    TIMESTAMP '2011-01-02 01:02:29'\n    ]) AS time;\n           time           |         ?column?         \n--------------------------+--------------------------\n Sun Jan 02 01:05:01 2011 | Sun Jan 02 01:05:00 2011\n Sun Jan 02 01:07:29 2011 | Sun Jan 02 01:05:00 2011\n Sun Jan 02 01:02:30 2011 | Sun Jan 02 01:05:00 2011\n Sun Jan 02 01:07:30 2011 | Sun Jan 02 01:10:00 2011\n Sun Jan 02 01:02:29 2011 | Sun Jan 02 01:00:00 2011\n\n--time_bucket with timezone should mimick date_trunc\nSET timezone TO 'UTC';\nSELECT time, time_bucket(INTERVAL '1 hour', time), date_trunc('hour', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+02'\n    ]) AS time;\n             time             |         time_bucket          |          date_trunc          \n------------------------------+------------------------------+------------------------------\n Sun Jan 02 01:01:01 2011 UTC | Sun Jan 02 01:00:00 2011 UTC | Sun Jan 02 01:00:00 2011 UTC\n Sun Jan 02 00:01:01 2011 UTC | Sun Jan 02 00:00:00 2011 UTC | Sun Jan 02 00:00:00 2011 UTC\n Sat Jan 01 23:01:01 2011 UTC | Sat Jan 01 23:00:00 2011 UTC | Sat Jan 01 23:00:00 2011 UTC\n\nSELECT time, time_bucket(INTERVAL '1 day', time), date_trunc('day', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+02'\n    ]) AS time;\n             time             |         time_bucket          |          date_trunc          \n------------------------------+------------------------------+------------------------------\n Sun Jan 02 01:01:01 2011 UTC | Sun Jan 02 00:00:00 2011 UTC | Sun Jan 02 00:00:00 2011 UTC\n Sun Jan 02 00:01:01 2011 UTC | Sun Jan 02 00:00:00 2011 UTC | Sun Jan 02 00:00:00 2011 UTC\n Sat Jan 01 23:01:01 2011 UTC | Sat Jan 01 00:00:00 2011 UTC | Sat Jan 01 00:00:00 2011 UTC\n\n--what happens with a local tz\nSET timezone TO 'America/New_York';\nSELECT time, time_bucket(INTERVAL '1 hour', time), date_trunc('hour', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+02'\n    ]) AS time;\n             time             |         time_bucket          |          date_trunc          \n------------------------------+------------------------------+------------------------------\n Sun Jan 02 01:01:01 2011 EST | Sun Jan 02 01:00:00 2011 EST | Sun Jan 02 01:00:00 2011 EST\n Sat Jan 01 19:01:01 2011 EST | Sat Jan 01 19:00:00 2011 EST | Sat Jan 01 19:00:00 2011 EST\n Sat Jan 01 18:01:01 2011 EST | Sat Jan 01 18:00:00 2011 EST | Sat Jan 01 18:00:00 2011 EST\n\n--Note the timestamp tz input is aligned with UTC day /not/ local day. different than date_trunc.\nSELECT time, time_bucket(INTERVAL '1 day', time), date_trunc('day', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-03 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-04 01:01:01+02'\n    ]) AS time;\n             time             |         time_bucket          |          date_trunc          \n------------------------------+------------------------------+------------------------------\n Sun Jan 02 01:01:01 2011 EST | Sat Jan 01 19:00:00 2011 EST | Sun Jan 02 00:00:00 2011 EST\n Sun Jan 02 19:01:01 2011 EST | Sun Jan 02 19:00:00 2011 EST | Sun Jan 02 00:00:00 2011 EST\n Mon Jan 03 18:01:01 2011 EST | Sun Jan 02 19:00:00 2011 EST | Mon Jan 03 00:00:00 2011 EST\n\n--can force local bucketing with simple cast.\nSELECT time, time_bucket(INTERVAL '1 day', time::timestamp), date_trunc('day', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-03 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-04 01:01:01+02'\n    ]) AS time;\n             time             |       time_bucket        |          date_trunc          \n------------------------------+--------------------------+------------------------------\n Sun Jan 02 01:01:01 2011 EST | Sun Jan 02 00:00:00 2011 | Sun Jan 02 00:00:00 2011 EST\n Sun Jan 02 19:01:01 2011 EST | Sun Jan 02 00:00:00 2011 | Sun Jan 02 00:00:00 2011 EST\n Mon Jan 03 18:01:01 2011 EST | Mon Jan 03 00:00:00 2011 | Mon Jan 03 00:00:00 2011 EST\n\n--can also use interval to correct\nSELECT time, time_bucket(INTERVAL '1 day', time, -INTERVAL '19 hours'), date_trunc('day', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-03 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-04 01:01:01+02'\n    ]) AS time;\n             time             |         time_bucket          |          date_trunc          \n------------------------------+------------------------------+------------------------------\n Sun Jan 02 01:01:01 2011 EST | Sun Jan 02 00:00:00 2011 EST | Sun Jan 02 00:00:00 2011 EST\n Sun Jan 02 19:01:01 2011 EST | Sun Jan 02 00:00:00 2011 EST | Sun Jan 02 00:00:00 2011 EST\n Mon Jan 03 18:01:01 2011 EST | Mon Jan 03 00:00:00 2011 EST | Mon Jan 03 00:00:00 2011 EST\n\n--dst: same local hour bucketed as two different hours.\nSELECT time, time_bucket(INTERVAL '1 hour', time), date_trunc('hour', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2017-11-05 12:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 13:05:00+07'\n    ]) AS time;\n             time             |         time_bucket          |          date_trunc          \n------------------------------+------------------------------+------------------------------\n Sun Nov 05 01:05:00 2017 EDT | Sun Nov 05 01:00:00 2017 EDT | Sun Nov 05 01:00:00 2017 EDT\n Sun Nov 05 01:05:00 2017 EST | Sun Nov 05 01:00:00 2017 EST | Sun Nov 05 01:00:00 2017 EST\n\n--local alignment changes when bucketing by UTC across dst boundary\nSELECT time, time_bucket(INTERVAL '2 hour', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2017-11-05 10:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 12:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 13:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 15:05:00+07'\n    ]) AS time;\n             time             |         time_bucket          \n------------------------------+------------------------------\n Sat Nov 04 23:05:00 2017 EDT | Sat Nov 04 22:00:00 2017 EDT\n Sun Nov 05 01:05:00 2017 EDT | Sun Nov 05 00:00:00 2017 EDT\n Sun Nov 05 01:05:00 2017 EST | Sun Nov 05 01:00:00 2017 EST\n Sun Nov 05 03:05:00 2017 EST | Sun Nov 05 03:00:00 2017 EST\n\n--local alignment is preserved when bucketing by local time across DST boundary.\nSELECT time, time_bucket(INTERVAL '2 hour', time::timestamp)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2017-11-05 10:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 12:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 13:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 15:05:00+07'\n    ]) AS time;\n             time             |       time_bucket        \n------------------------------+--------------------------\n Sat Nov 04 23:05:00 2017 EDT | Sat Nov 04 22:00:00 2017\n Sun Nov 05 01:05:00 2017 EDT | Sun Nov 05 00:00:00 2017\n Sun Nov 05 01:05:00 2017 EST | Sun Nov 05 00:00:00 2017\n Sun Nov 05 03:05:00 2017 EST | Sun Nov 05 02:00:00 2017\n\n-- GitHub issue #7059: time_bucket with timezone + offset across DST boundary\n-- Asia/Amman: clocks skip from 00:00 to 01:00 on 2021-03-26\n-- Input: 01:00+03 = 22:00 UTC → Result: 22:15 UTC = 01:15 local (00:00 + 15min offset)\nSELECT time_bucket('1 day', '2021-03-26 01:00:00+03'::timestamptz,\n    timezone := 'Asia/Amman', \"offset\" := '15 minutes'::interval);\n         time_bucket          \n------------------------------\n Wed Mar 24 18:15:00 2021 EDT\n\n-- GitHub issue #8851: time_bucket with negative offset during DST fall-back\n-- Europe/Berlin: clocks repeat 02:00-02:59 on 2025-10-26\n-- Input: 02:00+02 = 00:00 UTC → Result: 23:59:45 UTC = 01:59:45 local (02:00 - 15s offset)\nSELECT time_bucket('30 seconds', '2025-10-26 02:00:00+02'::timestamptz,\n    timezone := 'Europe/Berlin', \"offset\" := '-15 seconds'::interval);\n         time_bucket          \n------------------------------\n Sat Oct 25 19:59:45 2025 EDT\n\n-- Additional DST edge cases for coverage of DST direction × offset sign combinations\n-- Spring-forward + negative offset\n-- Input: 01:30+03 = 22:30 UTC → Result: 22:45 UTC = 01:45 local (01:00 + 45min = 02:00 - 15min)\nSELECT time_bucket('1 hour', '2021-03-26 01:30:00+03'::timestamptz,\n    timezone := 'Asia/Amman', \"offset\" := '-15 minutes'::interval);\n         time_bucket          \n------------------------------\n Thu Mar 25 17:45:00 2021 EDT\n\n-- Fall-back + positive offset\n-- Input: 02:30+01 = 01:30 UTC → Result: 01:15 UTC = 02:15 local (02:00 + 15min offset)\nSELECT time_bucket('1 hour', '2025-10-26 02:30:00+01'::timestamptz,\n    timezone := 'Europe/Berlin', \"offset\" := '15 minutes'::interval);\n         time_bucket          \n------------------------------\n Sat Oct 25 21:15:00 2025 EDT\n\n-- Input exactly at DST spring-forward transition\n-- Input: 22:00 UTC = 00:00 local (the moment clocks jump to 01:00)\n-- Result: 22:15 UTC = 01:15 local (01:00 + 15min offset)\nSELECT time_bucket('1 hour', '2021-03-25 22:00:00+00'::timestamptz,\n    timezone := 'Asia/Amman', \"offset\" := '15 minutes'::interval);\n         time_bucket          \n------------------------------\n Thu Mar 25 17:15:00 2021 EDT\n\n-- Input exactly at DST fall-back transition\n-- Input: 01:00 UTC = 03:00 CEST (the moment clocks go back to 02:00 CET)\n-- Result: 23:15 UTC = 01:15 local (01:00 + 15min offset, but in CET now)\nSELECT time_bucket('1 hour', '2025-10-26 01:00:00+00'::timestamptz,\n    timezone := 'Europe/Berlin', \"offset\" := '15 minutes'::interval);\n         time_bucket          \n------------------------------\n Sat Oct 25 20:15:00 2025 EDT\n\n-- Offset larger than bucket size (1h offset with 30min bucket)\n-- Input: 01:30+03 = 22:30 UTC → Result: 22:30 UTC = 01:30 local (01:00 + 30min = 00:30 + 1h)\nSELECT time_bucket('30 minutes', '2021-03-26 01:30:00+03'::timestamptz,\n    timezone := 'Asia/Amman', \"offset\" := '1 hour'::interval);\n         time_bucket          \n------------------------------\n Thu Mar 25 18:30:00 2021 EDT\n\n-- GitHub issue #9136: time_bucket with origin during DST fall-back\n-- When origin is in standard time but timestamp is in daylight time,\n-- the bucket could incorrectly start AFTER the timestamp.\n-- America/New_York: clocks go back at 02:00 EDT on 2024-11-03\n-- Input: 01:30-04 (EDT) = 05:30 UTC; origin in EST\n-- Result should have bucket start <= timestamp (bucket in EDT, not EST)\nSELECT time_bucket('1 hour', '2024-11-03 01:30:00-04'::timestamptz,\n    'America/New_York', '2000-01-01 00:00:00 America/New_York'::timestamptz) as bucket,\n    '2024-11-03 01:30:00-04'::timestamptz < time_bucket('1 hour',\n        '2024-11-03 01:30:00-04'::timestamptz, 'America/New_York',\n        '2000-01-01 00:00:00 America/New_York'::timestamptz) as ts_before_bucket;\n            bucket            | ts_before_bucket \n------------------------------+------------------\n Sun Nov 03 01:00:00 2024 EDT | f\n\nSELECT time,\n    time_bucket(10::smallint, time) AS time_bucket_smallint,\n    time_bucket(10::int, time) AS time_bucket_int,\n    time_bucket(10::bigint, time) AS time_bucket_bigint\nFROM unnest(ARRAY[\n     '-11',\n     '-10',\n      '-9',\n      '-1',\n       '0',\n       '1',\n      '99',\n     '100',\n     '109',\n     '110'\n    ]::smallint[]) AS time;\n time | time_bucket_smallint | time_bucket_int | time_bucket_bigint \n------+----------------------+-----------------+--------------------\n  -11 |                  -20 |             -20 |                -20\n  -10 |                  -10 |             -10 |                -10\n   -9 |                  -10 |             -10 |                -10\n   -1 |                  -10 |             -10 |                -10\n    0 |                    0 |               0 |                  0\n    1 |                    0 |               0 |                  0\n   99 |                   90 |              90 |                 90\n  100 |                  100 |             100 |                100\n  109 |                  100 |             100 |                100\n  110 |                  110 |             110 |                110\n\nSELECT time,\n    time_bucket(10::smallint, time, 2::smallint) AS time_bucket_smallint,\n    time_bucket(10::int, time, 2::int) AS time_bucket_int,\n    time_bucket(10::bigint, time, 2::bigint) AS time_bucket_bigint\nFROM unnest(ARRAY[\n      '-9',\n      '-8',\n      '-7',\n       '1',\n       '2',\n       '3',\n     '101',\n     '102',\n     '111',\n     '112'\n    ]::smallint[]) AS time;\n time | time_bucket_smallint | time_bucket_int | time_bucket_bigint \n------+----------------------+-----------------+--------------------\n   -9 |                  -18 |             -18 |                -18\n   -8 |                   -8 |              -8 |                 -8\n   -7 |                   -8 |              -8 |                 -8\n    1 |                   -8 |              -8 |                 -8\n    2 |                    2 |               2 |                  2\n    3 |                    2 |               2 |                  2\n  101 |                   92 |              92 |                 92\n  102 |                  102 |             102 |                102\n  111 |                  102 |             102 |                102\n  112 |                  112 |             112 |                112\n\nSELECT time,\n    time_bucket(10::smallint, time, -2::smallint) AS time_bucket_smallint,\n    time_bucket(10::int, time, -2::int) AS time_bucket_int,\n    time_bucket(10::bigint, time, -2::bigint) AS time_bucket_bigint\nFROM unnest(ARRAY[\n    '-13',\n    '-12',\n    '-11',\n     '-3',\n     '-2',\n     '-1',\n     '97',\n     '98',\n    '107',\n    '108'\n    ]::smallint[]) AS time;\n time | time_bucket_smallint | time_bucket_int | time_bucket_bigint \n------+----------------------+-----------------+--------------------\n  -13 |                  -22 |             -22 |                -22\n  -12 |                  -12 |             -12 |                -12\n  -11 |                  -12 |             -12 |                -12\n   -3 |                  -12 |             -12 |                -12\n   -2 |                   -2 |              -2 |                 -2\n   -1 |                   -2 |              -2 |                 -2\n   97 |                   88 |              88 |                 88\n   98 |                   98 |              98 |                 98\n  107 |                   98 |              98 |                 98\n  108 |                  108 |             108 |                108\n\n\\set ON_ERROR_STOP 0\nSELECT time_bucket(10::smallint, '-32768'::smallint);\nERROR:  timestamp out of range\nSELECT time_bucket(10::smallint, '-32761'::smallint);\nERROR:  timestamp out of range\nselect time_bucket(10::smallint, '-32768'::smallint, 1000::smallint);\nERROR:  timestamp out of range\nselect time_bucket(10::smallint, '-32768'::smallint, '32767'::smallint);\nERROR:  timestamp out of range\nselect time_bucket(10::smallint, '32767'::smallint, '-32768'::smallint);\nERROR:  timestamp out of range\n\\set ON_ERROR_STOP 1\nSELECT time, time_bucket(10::smallint, time)\nFROM unnest(ARRAY[\n    '-32760',\n    '-32759',\n    '32767'\n    ]::smallint[]) AS time;\n  time  | time_bucket \n--------+-------------\n -32760 |      -32760\n -32759 |      -32760\n  32767 |       32760\n\n\\set ON_ERROR_STOP 0\nSELECT time_bucket(10::int, '-2147483648'::int);\nERROR:  timestamp out of range\nSELECT time_bucket(10::int, '-2147483641'::int);\nERROR:  timestamp out of range\nSELECT time_bucket(1000::int, '-2147483000'::int, 1::int);\nERROR:  timestamp out of range\nSELECT time_bucket(1000::int, '-2147483648'::int, '2147483647'::int);\nERROR:  timestamp out of range\nSELECT time_bucket(1000::int, '2147483647'::int, '-2147483648'::int);\nERROR:  timestamp out of range\n\\set ON_ERROR_STOP 1\nSELECT time, time_bucket(10::int, time)\nFROM unnest(ARRAY[\n    '-2147483640',\n    '-2147483639',\n    '2147483647'\n    ]::int[]) AS time;\n    time     | time_bucket \n-------------+-------------\n -2147483640 | -2147483640\n -2147483639 | -2147483640\n  2147483647 |  2147483640\n\n\\set ON_ERROR_STOP 0\nSELECT time_bucket(10::bigint, '-9223372036854775808'::bigint);\nERROR:  timestamp out of range\nSELECT time_bucket(10::bigint, '-9223372036854775801'::bigint);\nERROR:  timestamp out of range\nSELECT time_bucket(1000::bigint, '-9223372036854775000'::bigint, 1::bigint);\nERROR:  timestamp out of range\nSELECT time_bucket(1000::bigint, '-9223372036854775808'::bigint, '9223372036854775807'::bigint);\nERROR:  timestamp out of range\nSELECT time_bucket(1000::bigint, '9223372036854775807'::bigint, '-9223372036854775808'::bigint);\nERROR:  timestamp out of range\n\\set ON_ERROR_STOP 1\nSELECT time, time_bucket(10::bigint, time)\nFROM unnest(ARRAY[\n    '-9223372036854775800',\n    '-9223372036854775799',\n    '9223372036854775807'\n    ]::bigint[]) AS time;\n         time         |     time_bucket      \n----------------------+----------------------\n -9223372036854775800 | -9223372036854775800\n -9223372036854775799 | -9223372036854775800\n  9223372036854775807 |  9223372036854775800\n\nSELECT time, time_bucket(INTERVAL '1 day', time::date)\nFROM unnest(ARRAY[\n    date '2017-11-05',\n    date '2017-11-06'\n    ]) AS time;\n    time    | time_bucket \n------------+-------------\n 11-05-2017 | 11-05-2017\n 11-06-2017 | 11-06-2017\n\nSELECT time, time_bucket(INTERVAL '4 day', time::date)\nFROM unnest(ARRAY[\n    date '2017-11-04',\n    date '2017-11-05',\n    date '2017-11-08',\n    date '2017-11-09'\n    ]) AS time;\n    time    | time_bucket \n------------+-------------\n 11-04-2017 | 11-01-2017\n 11-05-2017 | 11-05-2017\n 11-08-2017 | 11-05-2017\n 11-09-2017 | 11-09-2017\n\nSELECT time, time_bucket(INTERVAL '4 day', time::date, INTERVAL '2 day')\nFROM unnest(ARRAY[\n    date '2017-11-06',\n    date '2017-11-07',\n    date '2017-11-10',\n    date '2017-11-11'\n    ]) AS time;\n    time    | time_bucket \n------------+-------------\n 11-06-2017 | 11-03-2017\n 11-07-2017 | 11-07-2017\n 11-10-2017 | 11-07-2017\n 11-11-2017 | 11-11-2017\n\n-- 2019-09-24 is a Monday, and we want to ensure that time_bucket returns the week starting with a Monday as date_trunc does,\n-- Rather than a Saturday which is the date of the PostgreSQL epoch\nSELECT time, time_bucket(INTERVAL '1 week', time::date)\nFROM unnest(ARRAY[\n    date '2018-09-16',\n    date '2018-09-17',\n    date '2018-09-23',\n    date '2018-09-24'\n    ]) AS time;\n    time    | time_bucket \n------------+-------------\n 09-16-2018 | 09-10-2018\n 09-17-2018 | 09-17-2018\n 09-23-2018 | 09-17-2018\n 09-24-2018 | 09-24-2018\n\nSELECT time, time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp without time zone '2018-09-16',\n    timestamp without time zone '2018-09-17',\n    timestamp without time zone '2018-09-23',\n    timestamp without time zone '2018-09-24'\n    ]) AS time;\n           time           |       time_bucket        \n--------------------------+--------------------------\n Sun Sep 16 00:00:00 2018 | Mon Sep 10 00:00:00 2018\n Mon Sep 17 00:00:00 2018 | Mon Sep 17 00:00:00 2018\n Sun Sep 23 00:00:00 2018 | Mon Sep 17 00:00:00 2018\n Mon Sep 24 00:00:00 2018 | Mon Sep 24 00:00:00 2018\n\nSELECT time, time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp with time zone '2018-09-16',\n    timestamp with time zone '2018-09-17',\n    timestamp with time zone '2018-09-23',\n    timestamp with time zone '2018-09-24'\n    ]) AS time;\n             time             |         time_bucket          \n------------------------------+------------------------------\n Sun Sep 16 00:00:00 2018 EDT | Sun Sep 09 20:00:00 2018 EDT\n Mon Sep 17 00:00:00 2018 EDT | Sun Sep 16 20:00:00 2018 EDT\n Sun Sep 23 00:00:00 2018 EDT | Sun Sep 16 20:00:00 2018 EDT\n Mon Sep 24 00:00:00 2018 EDT | Sun Sep 23 20:00:00 2018 EDT\n\nSELECT time, time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp with time zone '-Infinity',\n    timestamp with time zone 'Infinity'\n    ]) AS time;\n   time    | time_bucket \n-----------+-------------\n -infinity | -infinity\n infinity  | infinity\n\nSELECT time, time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp without time zone '-Infinity',\n    timestamp without time zone 'Infinity'\n    ]) AS time;\n   time    | time_bucket \n-----------+-------------\n -infinity | -infinity\n infinity  | infinity\n\nSELECT time, time_bucket(INTERVAL '1 week', time), date_trunc('week', time) = time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp without time zone '4714-11-24 01:01:01.0 BC',\n    timestamp without time zone '294276-12-31 23:59:59.9999'\n    ]) AS time;\n              time               |         time_bucket         | ?column? \n---------------------------------+-----------------------------+----------\n Mon Nov 24 01:01:01 4714 BC     | Mon Nov 24 00:00:00 4714 BC | t\n Sun Dec 31 23:59:59.9999 294276 | Mon Dec 25 00:00:00 294276  | t\n\n--1000 years later weeks still align.\nSELECT time, time_bucket(INTERVAL '1 week', time), date_trunc('week', time) = time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp without time zone '3018-09-14',\n    timestamp without time zone '3018-09-20',\n    timestamp without time zone '3018-09-21',\n    timestamp without time zone '3018-09-22'\n    ]) AS time;\n           time           |       time_bucket        | ?column? \n--------------------------+--------------------------+----------\n Mon Sep 14 00:00:00 3018 | Mon Sep 14 00:00:00 3018 | t\n Sun Sep 20 00:00:00 3018 | Mon Sep 14 00:00:00 3018 | t\n Mon Sep 21 00:00:00 3018 | Mon Sep 21 00:00:00 3018 | t\n Tue Sep 22 00:00:00 3018 | Mon Sep 21 00:00:00 3018 | t\n\n--weeks align for timestamptz as well if cast to local time, (but not if done at UTC).\nSELECT time, date_trunc('week', time) = time_bucket(INTERVAL '1 week', time),  date_trunc('week', time) = time_bucket(INTERVAL '1 week', time::timestamp)\nFROM unnest(ARRAY[\n    timestamp with time zone '3018-09-14',\n    timestamp with time zone '3018-09-20',\n    timestamp with time zone '3018-09-21',\n    timestamp with time zone '3018-09-22'\n    ]) AS time;\n             time             | ?column? | ?column? \n------------------------------+----------+----------\n Mon Sep 14 00:00:00 3018 EDT | f        | t\n Sun Sep 20 00:00:00 3018 EDT | f        | t\n Mon Sep 21 00:00:00 3018 EDT | f        | t\n Tue Sep 22 00:00:00 3018 EDT | f        | t\n\n--check functions with origin\n--note that the default origin is at 0 UTC, using origin parameter it is easy to provide a EDT origin point\n\\x\nSELECT time, time_bucket(INTERVAL '1 week', time) no_epoch,\n             time_bucket(INTERVAL '1 week', time::timestamp) no_epoch_local,\n             time_bucket(INTERVAL '1 week', time) = time_bucket(INTERVAL '1 week', time, timestamptz '2000-01-03 00:00:00+0') always_true,\n             time_bucket(INTERVAL '1 week', time, timestamptz '2000-01-01 00:00:00+0') pg_epoch,\n             time_bucket(INTERVAL '1 week', time, timestamptz 'epoch') unix_epoch,\n             time_bucket(INTERVAL '1 week', time, timestamptz '3018-09-13') custom_1,\n             time_bucket(INTERVAL '1 week', time, timestamptz '3018-09-14') custom_2\nFROM unnest(ARRAY[\n    timestamp with time zone '2000-01-01 00:00:00+0'- interval '1 second',\n    timestamp with time zone '2000-01-01 00:00:00+0',\n    timestamp with time zone '2000-01-03 00:00:00+0'- interval '1 second',\n    timestamp with time zone '2000-01-03 00:00:00+0',\n    timestamp with time zone '2000-01-01',\n    timestamp with time zone '2000-01-02',\n    timestamp with time zone '2000-01-03',\n    timestamp with time zone '3018-09-12',\n    timestamp with time zone '3018-09-13',\n    timestamp with time zone '3018-09-14',\n    timestamp with time zone '3018-09-15'\n    ]) AS time;\n-[ RECORD 1 ]--+-----------------------------\ntime           | Fri Dec 31 18:59:59 1999 EST\nno_epoch       | Sun Dec 26 19:00:00 1999 EST\nno_epoch_local | Mon Dec 27 00:00:00 1999\nalways_true    | t\npg_epoch       | Fri Dec 24 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Dec 25 23:00:00 1999 EST\ncustom_2       | Sun Dec 26 23:00:00 1999 EST\n-[ RECORD 2 ]--+-----------------------------\ntime           | Fri Dec 31 19:00:00 1999 EST\nno_epoch       | Sun Dec 26 19:00:00 1999 EST\nno_epoch_local | Mon Dec 27 00:00:00 1999\nalways_true    | t\npg_epoch       | Fri Dec 31 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Dec 25 23:00:00 1999 EST\ncustom_2       | Sun Dec 26 23:00:00 1999 EST\n-[ RECORD 3 ]--+-----------------------------\ntime           | Sun Jan 02 18:59:59 2000 EST\nno_epoch       | Sun Dec 26 19:00:00 1999 EST\nno_epoch_local | Mon Dec 27 00:00:00 1999\nalways_true    | t\npg_epoch       | Fri Dec 31 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Jan 01 23:00:00 2000 EST\ncustom_2       | Sun Dec 26 23:00:00 1999 EST\n-[ RECORD 4 ]--+-----------------------------\ntime           | Sun Jan 02 19:00:00 2000 EST\nno_epoch       | Sun Jan 02 19:00:00 2000 EST\nno_epoch_local | Mon Dec 27 00:00:00 1999\nalways_true    | t\npg_epoch       | Fri Dec 31 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Jan 01 23:00:00 2000 EST\ncustom_2       | Sun Dec 26 23:00:00 1999 EST\n-[ RECORD 5 ]--+-----------------------------\ntime           | Sat Jan 01 00:00:00 2000 EST\nno_epoch       | Sun Dec 26 19:00:00 1999 EST\nno_epoch_local | Mon Dec 27 00:00:00 1999\nalways_true    | t\npg_epoch       | Fri Dec 31 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Dec 25 23:00:00 1999 EST\ncustom_2       | Sun Dec 26 23:00:00 1999 EST\n-[ RECORD 6 ]--+-----------------------------\ntime           | Sun Jan 02 00:00:00 2000 EST\nno_epoch       | Sun Dec 26 19:00:00 1999 EST\nno_epoch_local | Mon Dec 27 00:00:00 1999\nalways_true    | t\npg_epoch       | Fri Dec 31 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Jan 01 23:00:00 2000 EST\ncustom_2       | Sun Dec 26 23:00:00 1999 EST\n-[ RECORD 7 ]--+-----------------------------\ntime           | Mon Jan 03 00:00:00 2000 EST\nno_epoch       | Sun Jan 02 19:00:00 2000 EST\nno_epoch_local | Mon Jan 03 00:00:00 2000\nalways_true    | t\npg_epoch       | Fri Dec 31 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Jan 01 23:00:00 2000 EST\ncustom_2       | Sun Jan 02 23:00:00 2000 EST\n-[ RECORD 8 ]--+-----------------------------\ntime           | Sat Sep 12 00:00:00 3018 EDT\nno_epoch       | Sun Sep 06 20:00:00 3018 EDT\nno_epoch_local | Mon Sep 07 00:00:00 3018\nalways_true    | t\npg_epoch       | Fri Sep 11 20:00:00 3018 EDT\nunix_epoch     | Wed Sep 09 20:00:00 3018 EDT\ncustom_1       | Sun Sep 06 00:00:00 3018 EDT\ncustom_2       | Mon Sep 07 00:00:00 3018 EDT\n-[ RECORD 9 ]--+-----------------------------\ntime           | Sun Sep 13 00:00:00 3018 EDT\nno_epoch       | Sun Sep 06 20:00:00 3018 EDT\nno_epoch_local | Mon Sep 07 00:00:00 3018\nalways_true    | t\npg_epoch       | Fri Sep 11 20:00:00 3018 EDT\nunix_epoch     | Wed Sep 09 20:00:00 3018 EDT\ncustom_1       | Sun Sep 13 00:00:00 3018 EDT\ncustom_2       | Mon Sep 07 00:00:00 3018 EDT\n-[ RECORD 10 ]-+-----------------------------\ntime           | Mon Sep 14 00:00:00 3018 EDT\nno_epoch       | Sun Sep 13 20:00:00 3018 EDT\nno_epoch_local | Mon Sep 14 00:00:00 3018\nalways_true    | t\npg_epoch       | Fri Sep 11 20:00:00 3018 EDT\nunix_epoch     | Wed Sep 09 20:00:00 3018 EDT\ncustom_1       | Sun Sep 13 00:00:00 3018 EDT\ncustom_2       | Mon Sep 14 00:00:00 3018 EDT\n-[ RECORD 11 ]-+-----------------------------\ntime           | Tue Sep 15 00:00:00 3018 EDT\nno_epoch       | Sun Sep 13 20:00:00 3018 EDT\nno_epoch_local | Mon Sep 14 00:00:00 3018\nalways_true    | t\npg_epoch       | Fri Sep 11 20:00:00 3018 EDT\nunix_epoch     | Wed Sep 09 20:00:00 3018 EDT\ncustom_1       | Sun Sep 13 00:00:00 3018 EDT\ncustom_2       | Mon Sep 14 00:00:00 3018 EDT\n\nSELECT time, time_bucket(INTERVAL '1 week', time) no_epoch,\n             time_bucket(INTERVAL '1 week', time) = time_bucket(INTERVAL '1 week', time, timestamp '2000-01-03 00:00:00') always_true,\n             time_bucket(INTERVAL '1 week', time, timestamp '2000-01-01 00:00:00+0') pg_epoch,\n             time_bucket(INTERVAL '1 week', time, timestamp 'epoch') unix_epoch,\n             time_bucket(INTERVAL '1 week', time, timestamp '3018-09-13') custom_1,\n             time_bucket(INTERVAL '1 week', time, timestamp '3018-09-14') custom_2\nFROM unnest(ARRAY[\n    timestamp without time zone '2000-01-01 00:00:00'- interval '1 second',\n    timestamp without time zone '2000-01-01 00:00:00',\n    timestamp without time zone '2000-01-03 00:00:00'- interval '1 second',\n    timestamp without time zone '2000-01-03 00:00:00',\n    timestamp without time zone '2000-01-01',\n    timestamp without time zone '2000-01-02',\n    timestamp without time zone '2000-01-03',\n    timestamp without time zone '3018-09-12',\n    timestamp without time zone '3018-09-13',\n    timestamp without time zone '3018-09-14',\n    timestamp without time zone '3018-09-15'\n    ]) AS time;\n-[ RECORD 1 ]-------------------------\ntime        | Fri Dec 31 23:59:59 1999\nno_epoch    | Mon Dec 27 00:00:00 1999\nalways_true | t\npg_epoch    | Sat Dec 25 00:00:00 1999\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Dec 26 00:00:00 1999\ncustom_2    | Mon Dec 27 00:00:00 1999\n-[ RECORD 2 ]-------------------------\ntime        | Sat Jan 01 00:00:00 2000\nno_epoch    | Mon Dec 27 00:00:00 1999\nalways_true | t\npg_epoch    | Sat Jan 01 00:00:00 2000\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Dec 26 00:00:00 1999\ncustom_2    | Mon Dec 27 00:00:00 1999\n-[ RECORD 3 ]-------------------------\ntime        | Sun Jan 02 23:59:59 2000\nno_epoch    | Mon Dec 27 00:00:00 1999\nalways_true | t\npg_epoch    | Sat Jan 01 00:00:00 2000\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Jan 02 00:00:00 2000\ncustom_2    | Mon Dec 27 00:00:00 1999\n-[ RECORD 4 ]-------------------------\ntime        | Mon Jan 03 00:00:00 2000\nno_epoch    | Mon Jan 03 00:00:00 2000\nalways_true | t\npg_epoch    | Sat Jan 01 00:00:00 2000\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Jan 02 00:00:00 2000\ncustom_2    | Mon Jan 03 00:00:00 2000\n-[ RECORD 5 ]-------------------------\ntime        | Sat Jan 01 00:00:00 2000\nno_epoch    | Mon Dec 27 00:00:00 1999\nalways_true | t\npg_epoch    | Sat Jan 01 00:00:00 2000\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Dec 26 00:00:00 1999\ncustom_2    | Mon Dec 27 00:00:00 1999\n-[ RECORD 6 ]-------------------------\ntime        | Sun Jan 02 00:00:00 2000\nno_epoch    | Mon Dec 27 00:00:00 1999\nalways_true | t\npg_epoch    | Sat Jan 01 00:00:00 2000\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Jan 02 00:00:00 2000\ncustom_2    | Mon Dec 27 00:00:00 1999\n-[ RECORD 7 ]-------------------------\ntime        | Mon Jan 03 00:00:00 2000\nno_epoch    | Mon Jan 03 00:00:00 2000\nalways_true | t\npg_epoch    | Sat Jan 01 00:00:00 2000\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Jan 02 00:00:00 2000\ncustom_2    | Mon Jan 03 00:00:00 2000\n-[ RECORD 8 ]-------------------------\ntime        | Sat Sep 12 00:00:00 3018\nno_epoch    | Mon Sep 07 00:00:00 3018\nalways_true | t\npg_epoch    | Sat Sep 12 00:00:00 3018\nunix_epoch  | Thu Sep 10 00:00:00 3018\ncustom_1    | Sun Sep 06 00:00:00 3018\ncustom_2    | Mon Sep 07 00:00:00 3018\n-[ RECORD 9 ]-------------------------\ntime        | Sun Sep 13 00:00:00 3018\nno_epoch    | Mon Sep 07 00:00:00 3018\nalways_true | t\npg_epoch    | Sat Sep 12 00:00:00 3018\nunix_epoch  | Thu Sep 10 00:00:00 3018\ncustom_1    | Sun Sep 13 00:00:00 3018\ncustom_2    | Mon Sep 07 00:00:00 3018\n-[ RECORD 10 ]------------------------\ntime        | Mon Sep 14 00:00:00 3018\nno_epoch    | Mon Sep 14 00:00:00 3018\nalways_true | t\npg_epoch    | Sat Sep 12 00:00:00 3018\nunix_epoch  | Thu Sep 10 00:00:00 3018\ncustom_1    | Sun Sep 13 00:00:00 3018\ncustom_2    | Mon Sep 14 00:00:00 3018\n-[ RECORD 11 ]------------------------\ntime        | Tue Sep 15 00:00:00 3018\nno_epoch    | Mon Sep 14 00:00:00 3018\nalways_true | t\npg_epoch    | Sat Sep 12 00:00:00 3018\nunix_epoch  | Thu Sep 10 00:00:00 3018\ncustom_1    | Sun Sep 13 00:00:00 3018\ncustom_2    | Mon Sep 14 00:00:00 3018\n\nSELECT time, time_bucket(INTERVAL '1 week', time) no_epoch,\n             time_bucket(INTERVAL '1 week', time) = time_bucket(INTERVAL '1 week', time, date '2000-01-03') always_true,\n             time_bucket(INTERVAL '1 week', time, date '2000-01-01') pg_epoch,\n             time_bucket(INTERVAL '1 week', time, (timestamp 'epoch')::date) unix_epoch,\n             time_bucket(INTERVAL '1 week', time, date '3018-09-13') custom_1,\n             time_bucket(INTERVAL '1 week', time, date '3018-09-14') custom_2\nFROM unnest(ARRAY[\n    date '1999-12-31',\n    date '2000-01-01',\n    date '2000-01-02',\n    date '2000-01-03',\n    date '3018-09-12',\n    date '3018-09-13',\n    date '3018-09-14',\n    date '3018-09-15'\n    ]) AS time;\n-[ RECORD 1 ]-----------\ntime        | 12-31-1999\nno_epoch    | 12-27-1999\nalways_true | t\npg_epoch    | 12-25-1999\nunix_epoch  | 12-30-1999\ncustom_1    | 12-26-1999\ncustom_2    | 12-27-1999\n-[ RECORD 2 ]-----------\ntime        | 01-01-2000\nno_epoch    | 12-27-1999\nalways_true | t\npg_epoch    | 01-01-2000\nunix_epoch  | 12-30-1999\ncustom_1    | 12-26-1999\ncustom_2    | 12-27-1999\n-[ RECORD 3 ]-----------\ntime        | 01-02-2000\nno_epoch    | 12-27-1999\nalways_true | t\npg_epoch    | 01-01-2000\nunix_epoch  | 12-30-1999\ncustom_1    | 01-02-2000\ncustom_2    | 12-27-1999\n-[ RECORD 4 ]-----------\ntime        | 01-03-2000\nno_epoch    | 01-03-2000\nalways_true | t\npg_epoch    | 01-01-2000\nunix_epoch  | 12-30-1999\ncustom_1    | 01-02-2000\ncustom_2    | 01-03-2000\n-[ RECORD 5 ]-----------\ntime        | 09-12-3018\nno_epoch    | 09-07-3018\nalways_true | t\npg_epoch    | 09-12-3018\nunix_epoch  | 09-10-3018\ncustom_1    | 09-06-3018\ncustom_2    | 09-07-3018\n-[ RECORD 6 ]-----------\ntime        | 09-13-3018\nno_epoch    | 09-07-3018\nalways_true | t\npg_epoch    | 09-12-3018\nunix_epoch  | 09-10-3018\ncustom_1    | 09-13-3018\ncustom_2    | 09-07-3018\n-[ RECORD 7 ]-----------\ntime        | 09-14-3018\nno_epoch    | 09-14-3018\nalways_true | t\npg_epoch    | 09-12-3018\nunix_epoch  | 09-10-3018\ncustom_1    | 09-13-3018\ncustom_2    | 09-14-3018\n-[ RECORD 8 ]-----------\ntime        | 09-15-3018\nno_epoch    | 09-14-3018\nalways_true | t\npg_epoch    | 09-12-3018\nunix_epoch  | 09-10-3018\ncustom_1    | 09-13-3018\ncustom_2    | 09-14-3018\n\n\\x\n--really old origin works if date around that time\nSELECT time, time_bucket(INTERVAL '1 week', time, timestamp without time zone '4710-11-24 01:01:01.0 BC')\nFROM unnest(ARRAY[\n    timestamp without time zone '4710-11-24 01:01:01.0 BC',\n    timestamp without time zone '4710-11-25 01:01:01.0 BC',\n    timestamp without time zone '2001-01-01',\n    timestamp without time zone '3001-01-01'\n    ]) AS time;\n            time             |         time_bucket         \n-----------------------------+-----------------------------\n Sat Nov 24 01:01:01 4710 BC | Sat Nov 24 01:01:01 4710 BC\n Sun Nov 25 01:01:01 4710 BC | Sat Nov 24 01:01:01 4710 BC\n Mon Jan 01 00:00:00 2001    | Sat Dec 30 01:01:01 2000\n Thu Jan 01 00:00:00 3001    | Sat Dec 27 01:01:01 3000\n\nSELECT time, time_bucket(INTERVAL '1 week', time, timestamp without time zone '294270-12-30 23:59:59.9999')\nFROM unnest(ARRAY[\n    timestamp without time zone '294270-12-29 23:59:59.9999',\n    timestamp without time zone '294270-12-30 23:59:59.9999',\n    timestamp without time zone '294270-12-31 23:59:59.9999',\n    timestamp without time zone '2001-01-01',\n    timestamp without time zone '3001-01-01'\n    ]) AS time;\n              time               |           time_bucket           \n---------------------------------+---------------------------------\n Thu Dec 29 23:59:59.9999 294270 | Fri Dec 23 23:59:59.9999 294270\n Fri Dec 30 23:59:59.9999 294270 | Fri Dec 30 23:59:59.9999 294270\n Sat Dec 31 23:59:59.9999 294270 | Fri Dec 30 23:59:59.9999 294270\n Mon Jan 01 00:00:00 2001        | Fri Dec 29 23:59:59.9999 2000\n Thu Jan 01 00:00:00 3001        | Fri Dec 26 23:59:59.9999 3000\n\n\\set ON_ERROR_STOP 0\n--really old origin + very new data + long period errors\nSELECT time, time_bucket(INTERVAL '100000 day', time, timestamp without time zone '4710-11-24 01:01:01.0 BC')\nFROM unnest(ARRAY[\n    timestamp without time zone '294270-12-31 23:59:59.9999'\n    ]) AS time;\nERROR:  timestamp out of range\nSELECT time, time_bucket(INTERVAL '100000 day', time, timestamp with time zone '4710-11-25 01:01:01.0 BC')\nFROM unnest(ARRAY[\n    timestamp with time zone '294270-12-30 23:59:59.9999'\n    ]) AS time;\nERROR:  timestamp out of range\n--really high origin + old data + long period errors out\nSELECT time, time_bucket(INTERVAL '10000000 day', time, timestamp without time zone '294270-12-31 23:59:59.9999')\nFROM unnest(ARRAY[\n    timestamp without time zone '4710-11-24 01:01:01.0 BC'\n    ]) AS time;\nERROR:  timestamp out of range\nSELECT time, time_bucket(INTERVAL '10000000 day', time, timestamp with time zone '294270-12-31 23:59:59.9999')\nFROM unnest(ARRAY[\n    timestamp with time zone '4710-11-24 01:01:01.0 BC'\n    ]) AS time;\nERROR:  timestamp out of range\n\\set ON_ERROR_STOP 1\n-------------------------------------------\n--- Test time_bucket with month periods ---\n-------------------------------------------\nSET datestyle TO ISO;\nSELECT\n  time::date,\n  time_bucket('1 month', time::date) AS \"1m\",\n  time_bucket('2 month', time::date) AS \"2m\",\n  time_bucket('3 month', time::date) AS \"3m\",\n  time_bucket('1 month', time::date, '2000-02-01'::date) AS \"1m origin\",\n  time_bucket('2 month', time::date, '2000-02-01'::date) AS \"2m origin\",\n  time_bucket('3 month', time::date, '2000-02-01'::date) AS \"3m origin\"\nFROM generate_series('1990-01-03'::date,'1990-06-03'::date,'1month'::interval) time;\n    time    |     1m     |     2m     |     3m     | 1m origin  | 2m origin  | 3m origin  \n------------+------------+------------+------------+------------+------------+------------\n 1990-01-03 | 1990-01-01 | 1990-01-01 | 1990-01-01 | 1990-01-01 | 1989-12-01 | 1989-11-01\n 1990-02-03 | 1990-02-01 | 1990-01-01 | 1990-01-01 | 1990-02-01 | 1990-02-01 | 1990-02-01\n 1990-03-03 | 1990-03-01 | 1990-03-01 | 1990-01-01 | 1990-03-01 | 1990-02-01 | 1990-02-01\n 1990-04-03 | 1990-04-01 | 1990-03-01 | 1990-04-01 | 1990-04-01 | 1990-04-01 | 1990-02-01\n 1990-05-03 | 1990-05-01 | 1990-05-01 | 1990-04-01 | 1990-05-01 | 1990-04-01 | 1990-05-01\n 1990-06-03 | 1990-06-01 | 1990-05-01 | 1990-04-01 | 1990-06-01 | 1990-06-01 | 1990-05-01\n\nSELECT\n  time,\n  time_bucket('1 month', time) AS \"1m\",\n  time_bucket('2 month', time) AS \"2m\",\n  time_bucket('3 month', time) AS \"3m\",\n  time_bucket('1 month', time, '2000-02-01'::timestamp) AS \"1m origin\",\n  time_bucket('2 month', time, '2000-02-01'::timestamp) AS \"2m origin\",\n  time_bucket('3 month', time, '2000-02-01'::timestamp) AS \"3m origin\"\nFROM generate_series('1990-01-03'::timestamp,'1990-06-03'::timestamp,'1month'::interval) time;\n        time         |         1m          |         2m          |         3m          |      1m origin      |      2m origin      |      3m origin      \n---------------------+---------------------+---------------------+---------------------+---------------------+---------------------+---------------------\n 1990-01-03 00:00:00 | 1990-01-01 00:00:00 | 1990-01-01 00:00:00 | 1990-01-01 00:00:00 | 1990-01-01 00:00:00 | 1989-12-01 00:00:00 | 1989-11-01 00:00:00\n 1990-02-03 00:00:00 | 1990-02-01 00:00:00 | 1990-01-01 00:00:00 | 1990-01-01 00:00:00 | 1990-02-01 00:00:00 | 1990-02-01 00:00:00 | 1990-02-01 00:00:00\n 1990-03-03 00:00:00 | 1990-03-01 00:00:00 | 1990-03-01 00:00:00 | 1990-01-01 00:00:00 | 1990-03-01 00:00:00 | 1990-02-01 00:00:00 | 1990-02-01 00:00:00\n 1990-04-03 00:00:00 | 1990-04-01 00:00:00 | 1990-03-01 00:00:00 | 1990-04-01 00:00:00 | 1990-04-01 00:00:00 | 1990-04-01 00:00:00 | 1990-02-01 00:00:00\n 1990-05-03 00:00:00 | 1990-05-01 00:00:00 | 1990-05-01 00:00:00 | 1990-04-01 00:00:00 | 1990-05-01 00:00:00 | 1990-04-01 00:00:00 | 1990-05-01 00:00:00\n 1990-06-03 00:00:00 | 1990-06-01 00:00:00 | 1990-05-01 00:00:00 | 1990-04-01 00:00:00 | 1990-06-01 00:00:00 | 1990-06-01 00:00:00 | 1990-05-01 00:00:00\n\nSELECT\n  time,\n  time_bucket('1 month', time) AS \"1m\",\n  time_bucket('2 month', time) AS \"2m\",\n  time_bucket('3 month', time) AS \"3m\",\n  time_bucket('1 month', time, '2000-02-01'::timestamptz) AS \"1m origin\",\n  time_bucket('2 month', time, '2000-02-01'::timestamptz) AS \"2m origin\",\n  time_bucket('3 month', time, '2000-02-01'::timestamptz) AS \"3m origin\"\nFROM generate_series('1990-01-03'::timestamptz,'1990-06-03'::timestamptz,'1month'::interval) time;\n          time          |           1m           |           2m           |           3m           |       1m origin        |       2m origin        |       3m origin        \n------------------------+------------------------+------------------------+------------------------+------------------------+------------------------+------------------------\n 1990-01-03 00:00:00-05 | 1989-12-31 19:00:00-05 | 1989-12-31 19:00:00-05 | 1989-12-31 19:00:00-05 | 1989-12-31 19:00:00-05 | 1989-11-30 19:00:00-05 | 1989-10-31 19:00:00-05\n 1990-02-03 00:00:00-05 | 1990-01-31 19:00:00-05 | 1989-12-31 19:00:00-05 | 1989-12-31 19:00:00-05 | 1990-01-31 19:00:00-05 | 1990-01-31 19:00:00-05 | 1990-01-31 19:00:00-05\n 1990-03-03 00:00:00-05 | 1990-02-28 19:00:00-05 | 1990-02-28 19:00:00-05 | 1989-12-31 19:00:00-05 | 1990-02-28 19:00:00-05 | 1990-01-31 19:00:00-05 | 1990-01-31 19:00:00-05\n 1990-04-03 00:00:00-04 | 1990-03-31 19:00:00-05 | 1990-02-28 19:00:00-05 | 1990-03-31 19:00:00-05 | 1990-03-31 19:00:00-05 | 1990-03-31 19:00:00-05 | 1990-01-31 19:00:00-05\n 1990-05-03 00:00:00-04 | 1990-04-30 20:00:00-04 | 1990-04-30 20:00:00-04 | 1990-03-31 19:00:00-05 | 1990-04-30 20:00:00-04 | 1990-03-31 19:00:00-05 | 1990-04-30 20:00:00-04\n 1990-06-03 00:00:00-04 | 1990-05-31 20:00:00-04 | 1990-04-30 20:00:00-04 | 1990-03-31 19:00:00-05 | 1990-05-31 20:00:00-04 | 1990-05-31 20:00:00-04 | 1990-04-30 20:00:00-04\n\n---------------------------------------\n--- Test time_bucket with timezones ---\n---------------------------------------\n-- test NULL args\nSELECT\ntime_bucket(NULL::interval,now(),'Europe/Berlin'),\ntime_bucket('1day',NULL::timestamptz,'Europe/Berlin'),\ntime_bucket('1day',now(),NULL::text),\ntime_bucket('1day',timestamptz '2020-02-03','Europe/Berlin',NULL),\ntime_bucket('1day',timestamptz '2020-02-03','Europe/Berlin','2020-04-01',NULL),\ntime_bucket('1day',timestamptz '2020-02-03','Europe/Berlin',NULL,NULL),\ntime_bucket('1day',timestamptz '2020-02-03','Europe/Berlin',\"offset\":=NULL::interval),\ntime_bucket('1day',timestamptz '2020-02-03','Europe/Berlin',origin:=NULL::timestamptz);\n time_bucket | time_bucket | time_bucket |      time_bucket       |      time_bucket       |      time_bucket       |      time_bucket       |      time_bucket       \n-------------+-------------+-------------+------------------------+------------------------+------------------------+------------------------+------------------------\n             |             |             | 2020-02-02 18:00:00-05 | 2020-02-03 00:00:00-05 | 2020-02-02 18:00:00-05 | 2020-02-02 18:00:00-05 | 2020-02-02 18:00:00-05\n\nSET datestyle TO ISO;\nSELECT\n  time_bucket('1day', ts) AS \"UTC\",\n  time_bucket('1day', ts, 'Europe/Berlin') AS \"Berlin\",\n  time_bucket('1day', ts, 'Europe/London') AS \"London\",\n  time_bucket('1day', ts, 'America/New_York') AS \"New York\",\n  time_bucket('1day', ts, 'PST') AS \"PST\",\n  time_bucket('1day', ts, current_setting('timezone')) AS \"current\"\nFROM generate_series('1999-12-31 17:00'::timestamptz,'2000-01-02 3:00'::timestamptz, '1hour'::interval) ts;\n          UTC           |         Berlin         |         London         |        New York        |          PST           |        current         \n------------------------+------------------------+------------------------+------------------------+------------------------+------------------------\n 1999-12-30 19:00:00-05 | 1999-12-30 18:00:00-05 | 1999-12-30 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-30 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-30 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-31 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-31 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-31 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 2000-01-01 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-02 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-02 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-02 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-02 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-02 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-02 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-02 00:00:00-05 | 2000-01-02 03:00:00-05 | 2000-01-02 00:00:00-05\n\nSELECT\n  time_bucket('1month', ts) AS \"UTC\",\n  time_bucket('1month', ts, 'Europe/Berlin') AS \"Berlin\",\n  time_bucket('1month', ts, 'America/New_York') AS \"New York\",\n  time_bucket('1month', ts, current_setting('timezone')) AS \"current\",\n  time_bucket('2month', ts, current_setting('timezone')) AS \"2m\",\n  time_bucket('2month', ts, current_setting('timezone'), '2000-02-01'::timestamp) AS \"2m origin\",\n  time_bucket('2month', ts, current_setting('timezone'), \"offset\":='14 day'::interval) AS \"2m offset\",\n  time_bucket('2month', ts, current_setting('timezone'), '2000-02-01'::timestamp, '7 day'::interval) AS \"2m offset + origin\"\nFROM generate_series('1999-12-01'::timestamptz,'2000-09-01'::timestamptz, '9 day'::interval) ts;\n          UTC           |         Berlin         |        New York        |        current         |           2m           |       2m origin        |       2m offset        |   2m offset + origin   \n------------------------+------------------------+------------------------+------------------------+------------------------+------------------------+------------------------+------------------------\n 1999-11-30 19:00:00-05 | 1999-11-30 18:00:00-05 | 1999-12-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-15 00:00:00-05 | 1999-10-08 00:00:00-04\n 1999-11-30 19:00:00-05 | 1999-11-30 18:00:00-05 | 1999-12-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 1999-11-30 19:00:00-05 | 1999-11-30 18:00:00-05 | 1999-12-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 1999-11-30 19:00:00-05 | 1999-11-30 18:00:00-05 | 1999-12-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 2000-01-31 19:00:00-05 | 2000-01-31 18:00:00-05 | 2000-02-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 2000-01-31 19:00:00-05 | 2000-01-31 18:00:00-05 | 2000-02-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-01-31 19:00:00-05 | 2000-01-31 18:00:00-05 | 2000-02-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-01-31 19:00:00-05 | 2000-01-31 18:00:00-05 | 2000-02-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-02-29 19:00:00-05 | 2000-02-29 18:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-02-29 19:00:00-05 | 2000-02-29 18:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-02-29 19:00:00-05 | 2000-02-29 18:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-03-31 19:00:00-05 | 2000-03-31 17:00:00-05 | 2000-04-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-03-31 19:00:00-05 | 2000-03-31 17:00:00-05 | 2000-04-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-04-08 00:00:00-04\n 2000-03-31 19:00:00-05 | 2000-03-31 17:00:00-05 | 2000-04-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-04-08 00:00:00-04\n 2000-04-30 20:00:00-04 | 2000-04-30 18:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-04-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-04-08 00:00:00-04\n 2000-04-30 20:00:00-04 | 2000-04-30 18:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-04-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-04-08 00:00:00-04\n 2000-04-30 20:00:00-04 | 2000-04-30 18:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-04-01 00:00:00-05 | 2000-05-15 00:00:00-04 | 2000-04-08 00:00:00-04\n 2000-04-30 20:00:00-04 | 2000-04-30 18:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-04-01 00:00:00-05 | 2000-05-15 00:00:00-04 | 2000-04-08 00:00:00-04\n 2000-05-31 20:00:00-04 | 2000-05-31 18:00:00-04 | 2000-06-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-15 00:00:00-04 | 2000-04-08 00:00:00-04\n 2000-05-31 20:00:00-04 | 2000-05-31 18:00:00-04 | 2000-06-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-15 00:00:00-04 | 2000-06-08 00:00:00-04\n 2000-05-31 20:00:00-04 | 2000-05-31 18:00:00-04 | 2000-06-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-15 00:00:00-04 | 2000-06-08 00:00:00-04\n 2000-06-30 20:00:00-04 | 2000-06-30 18:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-15 00:00:00-04 | 2000-06-08 00:00:00-04\n 2000-06-30 20:00:00-04 | 2000-06-30 18:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-15 00:00:00-04 | 2000-06-08 00:00:00-04\n 2000-06-30 20:00:00-04 | 2000-06-30 18:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-07-15 00:00:00-04 | 2000-06-08 00:00:00-04\n 2000-06-30 20:00:00-04 | 2000-06-30 18:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-07-15 00:00:00-04 | 2000-06-08 00:00:00-04\n 2000-07-31 20:00:00-04 | 2000-07-31 18:00:00-04 | 2000-08-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-15 00:00:00-04 | 2000-08-08 00:00:00-04\n 2000-07-31 20:00:00-04 | 2000-07-31 18:00:00-04 | 2000-08-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-15 00:00:00-04 | 2000-08-08 00:00:00-04\n 2000-07-31 20:00:00-04 | 2000-07-31 18:00:00-04 | 2000-08-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-15 00:00:00-04 | 2000-08-08 00:00:00-04\n\nRESET datestyle;\n-------------------------------------\n--- Test time input functions --\n-------------------------------------\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION test.interval_to_internal(coltype REGTYPE, value ANYELEMENT = NULL::BIGINT) RETURNS BIGINT\nAS :MODULE_PATHNAME, 'ts_dimension_interval_to_internal_test' LANGUAGE C VOLATILE;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT test.interval_to_internal('TIMESTAMP'::regtype, INTERVAL '1 day');\n interval_to_internal \n----------------------\n          86400000000\n\nSELECT test.interval_to_internal('TIMESTAMP'::regtype, 86400000000);\n interval_to_internal \n----------------------\n          86400000000\n\n---should give warning\nSELECT test.interval_to_internal('TIMESTAMP'::regtype, 86400);\nWARNING:  unexpected interval: smaller than one second\nHINT:  The interval is specified in microseconds.\n interval_to_internal \n----------------------\n                86400\n\nSELECT test.interval_to_internal('TIMESTAMP'::regtype);\n interval_to_internal \n----------------------\n         604800000000\n\nSELECT test.interval_to_internal('BIGINT'::regtype, 2147483649::bigint);\n interval_to_internal \n----------------------\n           2147483649\n\n-- Default interval for integer is supported as part of\n-- hypertable generalization\nSELECT test.interval_to_internal('INT'::regtype);\n interval_to_internal \n----------------------\n               100000\n\nSELECT test.interval_to_internal('SMALLINT'::regtype);\n interval_to_internal \n----------------------\n                10000\n\nSELECT test.interval_to_internal('BIGINT'::regtype);\n interval_to_internal \n----------------------\n              1000000\n\nSELECT test.interval_to_internal('TIMESTAMPTZ'::regtype);\n interval_to_internal \n----------------------\n         604800000000\n\nSELECT test.interval_to_internal('TIMESTAMP'::regtype);\n interval_to_internal \n----------------------\n         604800000000\n\nSELECT test.interval_to_internal('DATE'::regtype);\n interval_to_internal \n----------------------\n         604800000000\n\n\\set VERBOSITY terse\n\\set ON_ERROR_STOP 0\nSELECT test.interval_to_internal('INT'::regtype, 2147483649::bigint);\nERROR:  invalid interval: must be between 1 and 2147483647\nSELECT test.interval_to_internal('SMALLINT'::regtype, 32768::bigint);\nERROR:  invalid interval: must be between 1 and 32767\nSELECT test.interval_to_internal('TEXT'::regtype, 32768::bigint);\nERROR:  invalid type for dimension \"testcol\"\nSELECT test.interval_to_internal('INT'::regtype, INTERVAL '1 day');\nERROR:  invalid interval type for integer dimension\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "test/expected/timestamp-17.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- Utility function for grouping/slotting time with a given interval.\nCREATE OR REPLACE FUNCTION date_group(\n    field           timestamp,\n    group_interval  interval\n)\n    RETURNS timestamp LANGUAGE SQL STABLE AS\n$BODY$\n    SELECT to_timestamp((EXTRACT(EPOCH from $1)::int /\n        EXTRACT(EPOCH from group_interval)::int) *\n        EXTRACT(EPOCH from group_interval)::int)::timestamp;\n$BODY$;\nCREATE TABLE PUBLIC.\"testNs\" (\n  \"timeCustom\" TIMESTAMP NOT NULL,\n  device_id TEXT NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL,\n  series_bool BOOLEAN NULL\n);\nCREATE INDEX ON PUBLIC.\"testNs\" (device_id, \"timeCustom\" DESC NULLS LAST) WHERE device_id IS NOT NULL;\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA \"testNs\" AUTHORIZATION :ROLE_DEFAULT_PERM_USER;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT * FROM create_hypertable('\"public\".\"testNs\"', 'timeCustom', 'device_id', 2, associated_schema_name=>'testNs' );\nWARNING:  column type \"timestamp without time zone\" used for \"timeCustom\" does not follow best practices\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             1 | public      | testNs     | t\n\n\\c :TEST_DBNAME\nINSERT INTO PUBLIC.\"testNs\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n('2009-11-12T01:00:00+00:00', 'dev1', 1.5, 1),\n('2009-11-12T01:00:00+00:00', 'dev1', 1.5, 2),\n('2009-11-10T23:00:02+00:00', 'dev1', 2.5, 3);\nINSERT INTO PUBLIC.\"testNs\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n('2009-11-10T23:00:00+00:00', 'dev2', 1.5, 1),\n('2009-11-10T23:00:00+00:00', 'dev2', 1.5, 2);\nSELECT * FROM PUBLIC.\"testNs\";\n        timeCustom        | device_id | series_0 | series_1 | series_2 | series_bool \n--------------------------+-----------+----------+----------+----------+-------------\n Thu Nov 12 01:00:00 2009 | dev1      |      1.5 |        1 |          | \n Thu Nov 12 01:00:00 2009 | dev1      |      1.5 |        2 |          | \n Tue Nov 10 23:00:02 2009 | dev1      |      2.5 |        3 |          | \n Tue Nov 10 23:00:00 2009 | dev2      |      1.5 |        1 |          | \n Tue Nov 10 23:00:00 2009 | dev2      |      1.5 |        2 |          | \n\nSET client_min_messages = WARNING;\n\\echo 'The next 2 queries will differ in output between UTC and EST since the mod is on the 100th hour UTC'\nThe next 2 queries will differ in output between UTC and EST since the mod is on the 100th hour UTC\nSET timezone = 'UTC';\nSELECT date_group(\"timeCustom\", '100 days') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC;\n           time           | sum \n--------------------------+-----\n Sun Sep 13 00:00:00 2009 | 8.5\n\nSET timezone = 'EST';\nSELECT date_group(\"timeCustom\", '100 days') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC;\n           time           | sum \n--------------------------+-----\n Sat Sep 12 19:00:00 2009 | 8.5\n\n\\echo 'The rest of the queries will be the same in output between UTC and EST'\nThe rest of the queries will be the same in output between UTC and EST\nSET timezone = 'UTC';\nSELECT date_group(\"timeCustom\", '1 day') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC;\n           time           | sum \n--------------------------+-----\n Tue Nov 10 00:00:00 2009 | 5.5\n Thu Nov 12 00:00:00 2009 |   3\n\nSET timezone = 'EST';\nSELECT date_group(\"timeCustom\", '1 day') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC;\n           time           | sum \n--------------------------+-----\n Mon Nov 09 19:00:00 2009 | 5.5\n Wed Nov 11 19:00:00 2009 |   3\n\nSET timezone = 'UTC';\nSELECT *\nFROM PUBLIC.\"testNs\"\nWHERE \"timeCustom\" >= TIMESTAMP '2009-11-10T23:00:00'\nAND \"timeCustom\" < TIMESTAMP '2009-11-12T01:00:00' ORDER BY \"timeCustom\" DESC, device_id, series_1;\n        timeCustom        | device_id | series_0 | series_1 | series_2 | series_bool \n--------------------------+-----------+----------+----------+----------+-------------\n Tue Nov 10 23:00:02 2009 | dev1      |      2.5 |        3 |          | \n Tue Nov 10 23:00:00 2009 | dev2      |      1.5 |        1 |          | \n Tue Nov 10 23:00:00 2009 | dev2      |      1.5 |        2 |          | \n\nSET timezone = 'EST';\nSELECT *\nFROM PUBLIC.\"testNs\"\nWHERE \"timeCustom\" >= TIMESTAMP '2009-11-10T23:00:00'\nAND \"timeCustom\" < TIMESTAMP '2009-11-12T01:00:00' ORDER BY \"timeCustom\" DESC, device_id, series_1;\n        timeCustom        | device_id | series_0 | series_1 | series_2 | series_bool \n--------------------------+-----------+----------+----------+----------+-------------\n Tue Nov 10 23:00:02 2009 | dev1      |      2.5 |        3 |          | \n Tue Nov 10 23:00:00 2009 | dev2      |      1.5 |        1 |          | \n Tue Nov 10 23:00:00 2009 | dev2      |      1.5 |        2 |          | \n\nSET timezone = 'UTC';\nSELECT date_group(\"timeCustom\", '1 day') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC LIMIT 2;\n           time           | sum \n--------------------------+-----\n Tue Nov 10 00:00:00 2009 | 5.5\n Thu Nov 12 00:00:00 2009 |   3\n\nSET timezone = 'EST';\nSELECT date_group(\"timeCustom\", '1 day') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC LIMIT 2;\n           time           | sum \n--------------------------+-----\n Mon Nov 09 19:00:00 2009 | 5.5\n Wed Nov 11 19:00:00 2009 |   3\n\n------------------------------------\n-- Test time conversion functions --\n------------------------------------\n\\set ON_ERROR_STOP 0\nSET timezone = 'UTC';\n-- Conversion to timestamp using Postgres built-in function taking\n-- double. Gives inaccurate result on Postgres <= 9.6.2. Accurate on\n-- Postgres >= 9.6.3.\nSELECT to_timestamp(1486480176.236538);\n            to_timestamp             \n-------------------------------------\n Tue Feb 07 15:09:36.236538 2017 UTC\n\n-- extension-specific version taking microsecond UNIX timestamp\nSELECT _timescaledb_functions.to_timestamp(1486480176236538);\n            to_timestamp             \n-------------------------------------\n Tue Feb 07 15:09:36.236538 2017 UTC\n\n-- Should be the inverse of the statement above.\nSELECT _timescaledb_functions.to_unix_microseconds('2017-02-07 15:09:36.236538+00');\n to_unix_microseconds \n----------------------\n     1486480176236538\n\n-- For timestamps, BIGINT MAX represents +Infinity and BIGINT MIN\n-- -Infinity. We keep this notion for UNIX epoch time:\nSELECT _timescaledb_functions.to_unix_microseconds('+infinity');\n to_unix_microseconds \n----------------------\n  9223372036854775807\n\nSELECT _timescaledb_functions.to_timestamp(9223372036854775807);\n to_timestamp \n--------------\n infinity\n\nSELECT _timescaledb_functions.to_unix_microseconds('-infinity');\n to_unix_microseconds \n----------------------\n -9223372036854775808\n\nSELECT _timescaledb_functions.to_timestamp(-9223372036854775808);\n to_timestamp \n--------------\n -infinity\n\n-- In UNIX microseconds, the largest bigint value below infinity\n-- (BIGINT MAX) is smaller than internal date upper bound and should\n-- therefore be OK. Further, converting to the internal postgres epoch\n-- cannot overflow a 64-bit INTEGER since the postgres epoch is at a\n-- later date compared to the UNIX epoch, and is therefore represented\n-- by a smaller number\nSELECT _timescaledb_functions.to_timestamp(9223372036854775806);\n             to_timestamp              \n---------------------------------------\n Sun Jan 10 04:00:54.775806 294247 UTC\n\n-- Julian day zero is -210866803200000000 microseconds from UNIX epoch\nSELECT _timescaledb_functions.to_timestamp(-210866803200000000);\n          to_timestamp           \n---------------------------------\n Mon Nov 24 00:00:00 4714 UTC BC\n\n\\set VERBOSITY default\n-- Going beyond Julian day zero should give out-of-range error\nSELECT _timescaledb_functions.to_timestamp(-210866803200000001);\nERROR:  timestamp out of range\n-- Lower bound on date (should return the Julian day zero UNIX timestamp above)\nSELECT _timescaledb_functions.to_unix_microseconds('4714-11-24 00:00:00+00 BC');\n to_unix_microseconds \n----------------------\n  -210866803200000000\n\n-- Going beyond lower bound on date should return out-of-range\nSELECT _timescaledb_functions.to_unix_microseconds('4714-11-23 23:59:59.999999+00 BC');\nERROR:  timestamp out of range: \"4714-11-23 23:59:59.999999+00 BC\"\nLINE 1: ...ELECT _timescaledb_functions.to_unix_microseconds('4714-11-2...\n                                                             ^\n-- The upper bound for Postgres TIMESTAMPTZ\nSELECT timestamp '294276-12-31 23:59:59.999999+00';\n             timestamp             \n-----------------------------------\n Sun Dec 31 23:59:59.999999 294276\n\n-- Going beyond the upper bound, should fail\nSELECT timestamp '294276-12-31 23:59:59.999999+00' + interval '1 us';\nERROR:  timestamp out of range\n-- Cannot represent the upper bound timestamp with a UNIX microsecond timestamp\n-- since the Postgres epoch is at a later date than the UNIX epoch.\nSELECT _timescaledb_functions.to_unix_microseconds('294276-12-31 23:59:59.999999+00');\nERROR:  timestamp out of range\n-- Subtracting the difference between the two epochs (10957 days) should bring\n-- us within range.\nSELECT timestamp '294276-12-31 23:59:59.999999+00' - interval '10957 days';\n             ?column?              \n-----------------------------------\n Fri Jan 01 23:59:59.999999 294247\n\nSELECT _timescaledb_functions.to_unix_microseconds('294247-01-01 23:59:59.999999');\n to_unix_microseconds \n----------------------\n  9223371331199999999\n\n-- Adding one microsecond should take us out-of-range again\nSELECT timestamp '294247-01-01 23:59:59.999999' + interval '1 us';\n          ?column?          \n----------------------------\n Sat Jan 02 00:00:00 294247\n\nSELECT _timescaledb_functions.to_unix_microseconds(timestamp '294247-01-01 23:59:59.999999' + interval '1 us');\nERROR:  timestamp out of range\n--no time_bucketing of dates not by integer # of days\nSELECT time_bucket('1 hour', DATE '2012-01-01');\nERROR:  interval must not have sub-day precision\nSELECT time_bucket('25 hour', DATE '2012-01-01');\nERROR:  interval must be a multiple of a day\n\\set ON_ERROR_STOP 1\nSELECT time_bucket(INTERVAL '1 day', TIMESTAMP '2011-01-02 01:01:01');\n       time_bucket        \n--------------------------\n Sun Jan 02 00:00:00 2011\n\nSELECT time, time_bucket(INTERVAL '2 day ', time)\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-01 01:01:01',\n    TIMESTAMP '2011-01-02 01:01:01',\n    TIMESTAMP '2011-01-03 01:01:01',\n    TIMESTAMP '2011-01-04 01:01:01'\n    ]) AS time;\n           time           |       time_bucket        \n--------------------------+--------------------------\n Sat Jan 01 01:01:01 2011 | Sat Jan 01 00:00:00 2011\n Sun Jan 02 01:01:01 2011 | Sat Jan 01 00:00:00 2011\n Mon Jan 03 01:01:01 2011 | Mon Jan 03 00:00:00 2011\n Tue Jan 04 01:01:01 2011 | Mon Jan 03 00:00:00 2011\n\nSELECT int_def, time_bucket(int_def,TIMESTAMP '2011-01-02 01:01:01.111')\nFROM unnest(ARRAY[\n    INTERVAL '1 millisecond',\n    INTERVAL '1 second',\n    INTERVAL '1 minute',\n    INTERVAL '1 hour',\n    INTERVAL '1 day',\n    INTERVAL '2 millisecond',\n    INTERVAL '2 second',\n    INTERVAL '2 minute',\n    INTERVAL '2 hour',\n    INTERVAL '2 day'\n    ]) AS int_def;\n   int_def    |         time_bucket          \n--------------+------------------------------\n @ 0.001 secs | Sun Jan 02 01:01:01.111 2011\n @ 1 sec      | Sun Jan 02 01:01:01 2011\n @ 1 min      | Sun Jan 02 01:01:00 2011\n @ 1 hour     | Sun Jan 02 01:00:00 2011\n @ 1 day      | Sun Jan 02 00:00:00 2011\n @ 0.002 secs | Sun Jan 02 01:01:01.11 2011\n @ 2 secs     | Sun Jan 02 01:01:00 2011\n @ 2 mins     | Sun Jan 02 01:00:00 2011\n @ 2 hours    | Sun Jan 02 00:00:00 2011\n @ 2 days     | Sat Jan 01 00:00:00 2011\n\n\\set ON_ERROR_STOP 0\nSELECT time_bucket(INTERVAL '1 year 1d',TIMESTAMP '2011-01-02 01:01:01.111');\nERROR:  month intervals cannot have day or time component\nSELECT time_bucket(INTERVAL '1 month 1 minute',TIMESTAMP '2011-01-02 01:01:01.111');\nERROR:  month intervals cannot have day or time component\n\\set ON_ERROR_STOP 1\nSELECT time, time_bucket(INTERVAL '5 minute', time)\nFROM unnest(ARRAY[\n    TIMESTAMP '1970-01-01 00:59:59.999999',\n    TIMESTAMP '1970-01-01 01:01:00',\n    TIMESTAMP '1970-01-01 01:04:59.999999',\n    TIMESTAMP '1970-01-01 01:05:00'\n    ]) AS time;\n              time               |       time_bucket        \n---------------------------------+--------------------------\n Thu Jan 01 00:59:59.999999 1970 | Thu Jan 01 00:55:00 1970\n Thu Jan 01 01:01:00 1970        | Thu Jan 01 01:00:00 1970\n Thu Jan 01 01:04:59.999999 1970 | Thu Jan 01 01:00:00 1970\n Thu Jan 01 01:05:00 1970        | Thu Jan 01 01:05:00 1970\n\nSELECT time, time_bucket(INTERVAL '5 minute', time)\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-02 01:04:59.999999',\n    TIMESTAMP '2011-01-02 01:05:00',\n    TIMESTAMP '2011-01-02 01:09:59.999999',\n    TIMESTAMP '2011-01-02 01:10:00'\n    ]) AS time;\n              time               |       time_bucket        \n---------------------------------+--------------------------\n Sun Jan 02 01:04:59.999999 2011 | Sun Jan 02 01:00:00 2011\n Sun Jan 02 01:05:00 2011        | Sun Jan 02 01:05:00 2011\n Sun Jan 02 01:09:59.999999 2011 | Sun Jan 02 01:05:00 2011\n Sun Jan 02 01:10:00 2011        | Sun Jan 02 01:10:00 2011\n\n--offset with interval\nSELECT time, time_bucket(INTERVAL '5 minute', time ,  INTERVAL '2 minutes')\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-02 01:01:59.999999',\n    TIMESTAMP '2011-01-02 01:02:00',\n    TIMESTAMP '2011-01-02 01:06:59.999999',\n    TIMESTAMP '2011-01-02 01:07:00'\n    ]) AS time;\n              time               |       time_bucket        \n---------------------------------+--------------------------\n Sun Jan 02 01:01:59.999999 2011 | Sun Jan 02 00:57:00 2011\n Sun Jan 02 01:02:00 2011        | Sun Jan 02 01:02:00 2011\n Sun Jan 02 01:06:59.999999 2011 | Sun Jan 02 01:02:00 2011\n Sun Jan 02 01:07:00 2011        | Sun Jan 02 01:07:00 2011\n\nSELECT time, time_bucket(INTERVAL '5 minute', time , - INTERVAL '2 minutes')\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-02 01:02:59.999999',\n    TIMESTAMP '2011-01-02 01:03:00',\n    TIMESTAMP '2011-01-02 01:07:59.999999',\n    TIMESTAMP '2011-01-02 01:08:00'\n    ]) AS time;\n              time               |       time_bucket        \n---------------------------------+--------------------------\n Sun Jan 02 01:02:59.999999 2011 | Sun Jan 02 00:58:00 2011\n Sun Jan 02 01:03:00 2011        | Sun Jan 02 01:03:00 2011\n Sun Jan 02 01:07:59.999999 2011 | Sun Jan 02 01:03:00 2011\n Sun Jan 02 01:08:00 2011        | Sun Jan 02 01:08:00 2011\n\n--offset with infinity\n-- timestamp\nSELECT time, time_bucket(INTERVAL '1 week', time, INTERVAL '1 day')\nFROM unnest(ARRAY[\n    timestamp '-Infinity',\n    timestamp 'Infinity'\n    ]) AS time;\n   time    | time_bucket \n-----------+-------------\n -infinity | -infinity\n infinity  | infinity\n\n-- timestamptz\nSELECT time, time_bucket(INTERVAL '1 week', time, INTERVAL '1 day')\nFROM unnest(ARRAY[\n    timestamp with time zone '-Infinity',\n    timestamp with time zone 'Infinity'\n    ]) AS time;\n   time    | time_bucket \n-----------+-------------\n -infinity | -infinity\n infinity  | infinity\n\n-- Date\nSELECT date, time_bucket(INTERVAL '1 week', date, INTERVAL '1 day')\nFROM unnest(ARRAY[\n    date '-Infinity',\n    date 'Infinity'\n    ]) AS date;\n   date    | time_bucket \n-----------+-------------\n -infinity | -infinity\n infinity  | infinity\n\n--example to align with an origin\nSELECT time, time_bucket(INTERVAL '5 minute', time - (TIMESTAMP '2011-01-02 00:02:00' - TIMESTAMP 'epoch')) +  (TIMESTAMP '2011-01-02 00:02:00'-TIMESTAMP 'epoch')\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-02 01:01:59.999999',\n    TIMESTAMP '2011-01-02 01:02:00',\n    TIMESTAMP '2011-01-02 01:06:59.999999',\n    TIMESTAMP '2011-01-02 01:07:00'\n    ]) AS time;\n              time               |         ?column?         \n---------------------------------+--------------------------\n Sun Jan 02 01:01:59.999999 2011 | Sun Jan 02 00:57:00 2011\n Sun Jan 02 01:02:00 2011        | Sun Jan 02 01:02:00 2011\n Sun Jan 02 01:06:59.999999 2011 | Sun Jan 02 01:02:00 2011\n Sun Jan 02 01:07:00 2011        | Sun Jan 02 01:07:00 2011\n\n--rounding version\nSELECT time, time_bucket(INTERVAL '5 minute', time , - INTERVAL '2.5 minutes') + INTERVAL '2 minutes 30 seconds'\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-02 01:05:01',\n    TIMESTAMP '2011-01-02 01:07:29',\n    TIMESTAMP '2011-01-02 01:02:30',\n    TIMESTAMP '2011-01-02 01:07:30',\n    TIMESTAMP '2011-01-02 01:02:29'\n    ]) AS time;\n           time           |         ?column?         \n--------------------------+--------------------------\n Sun Jan 02 01:05:01 2011 | Sun Jan 02 01:05:00 2011\n Sun Jan 02 01:07:29 2011 | Sun Jan 02 01:05:00 2011\n Sun Jan 02 01:02:30 2011 | Sun Jan 02 01:05:00 2011\n Sun Jan 02 01:07:30 2011 | Sun Jan 02 01:10:00 2011\n Sun Jan 02 01:02:29 2011 | Sun Jan 02 01:00:00 2011\n\n--time_bucket with timezone should mimick date_trunc\nSET timezone TO 'UTC';\nSELECT time, time_bucket(INTERVAL '1 hour', time), date_trunc('hour', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+02'\n    ]) AS time;\n             time             |         time_bucket          |          date_trunc          \n------------------------------+------------------------------+------------------------------\n Sun Jan 02 01:01:01 2011 UTC | Sun Jan 02 01:00:00 2011 UTC | Sun Jan 02 01:00:00 2011 UTC\n Sun Jan 02 00:01:01 2011 UTC | Sun Jan 02 00:00:00 2011 UTC | Sun Jan 02 00:00:00 2011 UTC\n Sat Jan 01 23:01:01 2011 UTC | Sat Jan 01 23:00:00 2011 UTC | Sat Jan 01 23:00:00 2011 UTC\n\nSELECT time, time_bucket(INTERVAL '1 day', time), date_trunc('day', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+02'\n    ]) AS time;\n             time             |         time_bucket          |          date_trunc          \n------------------------------+------------------------------+------------------------------\n Sun Jan 02 01:01:01 2011 UTC | Sun Jan 02 00:00:00 2011 UTC | Sun Jan 02 00:00:00 2011 UTC\n Sun Jan 02 00:01:01 2011 UTC | Sun Jan 02 00:00:00 2011 UTC | Sun Jan 02 00:00:00 2011 UTC\n Sat Jan 01 23:01:01 2011 UTC | Sat Jan 01 00:00:00 2011 UTC | Sat Jan 01 00:00:00 2011 UTC\n\n--what happens with a local tz\nSET timezone TO 'America/New_York';\nSELECT time, time_bucket(INTERVAL '1 hour', time), date_trunc('hour', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+02'\n    ]) AS time;\n             time             |         time_bucket          |          date_trunc          \n------------------------------+------------------------------+------------------------------\n Sun Jan 02 01:01:01 2011 EST | Sun Jan 02 01:00:00 2011 EST | Sun Jan 02 01:00:00 2011 EST\n Sat Jan 01 19:01:01 2011 EST | Sat Jan 01 19:00:00 2011 EST | Sat Jan 01 19:00:00 2011 EST\n Sat Jan 01 18:01:01 2011 EST | Sat Jan 01 18:00:00 2011 EST | Sat Jan 01 18:00:00 2011 EST\n\n--Note the timestamp tz input is aligned with UTC day /not/ local day. different than date_trunc.\nSELECT time, time_bucket(INTERVAL '1 day', time), date_trunc('day', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-03 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-04 01:01:01+02'\n    ]) AS time;\n             time             |         time_bucket          |          date_trunc          \n------------------------------+------------------------------+------------------------------\n Sun Jan 02 01:01:01 2011 EST | Sat Jan 01 19:00:00 2011 EST | Sun Jan 02 00:00:00 2011 EST\n Sun Jan 02 19:01:01 2011 EST | Sun Jan 02 19:00:00 2011 EST | Sun Jan 02 00:00:00 2011 EST\n Mon Jan 03 18:01:01 2011 EST | Sun Jan 02 19:00:00 2011 EST | Mon Jan 03 00:00:00 2011 EST\n\n--can force local bucketing with simple cast.\nSELECT time, time_bucket(INTERVAL '1 day', time::timestamp), date_trunc('day', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-03 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-04 01:01:01+02'\n    ]) AS time;\n             time             |       time_bucket        |          date_trunc          \n------------------------------+--------------------------+------------------------------\n Sun Jan 02 01:01:01 2011 EST | Sun Jan 02 00:00:00 2011 | Sun Jan 02 00:00:00 2011 EST\n Sun Jan 02 19:01:01 2011 EST | Sun Jan 02 00:00:00 2011 | Sun Jan 02 00:00:00 2011 EST\n Mon Jan 03 18:01:01 2011 EST | Mon Jan 03 00:00:00 2011 | Mon Jan 03 00:00:00 2011 EST\n\n--can also use interval to correct\nSELECT time, time_bucket(INTERVAL '1 day', time, -INTERVAL '19 hours'), date_trunc('day', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-03 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-04 01:01:01+02'\n    ]) AS time;\n             time             |         time_bucket          |          date_trunc          \n------------------------------+------------------------------+------------------------------\n Sun Jan 02 01:01:01 2011 EST | Sun Jan 02 00:00:00 2011 EST | Sun Jan 02 00:00:00 2011 EST\n Sun Jan 02 19:01:01 2011 EST | Sun Jan 02 00:00:00 2011 EST | Sun Jan 02 00:00:00 2011 EST\n Mon Jan 03 18:01:01 2011 EST | Mon Jan 03 00:00:00 2011 EST | Mon Jan 03 00:00:00 2011 EST\n\n--dst: same local hour bucketed as two different hours.\nSELECT time, time_bucket(INTERVAL '1 hour', time), date_trunc('hour', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2017-11-05 12:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 13:05:00+07'\n    ]) AS time;\n             time             |         time_bucket          |          date_trunc          \n------------------------------+------------------------------+------------------------------\n Sun Nov 05 01:05:00 2017 EDT | Sun Nov 05 01:00:00 2017 EDT | Sun Nov 05 01:00:00 2017 EDT\n Sun Nov 05 01:05:00 2017 EST | Sun Nov 05 01:00:00 2017 EST | Sun Nov 05 01:00:00 2017 EST\n\n--local alignment changes when bucketing by UTC across dst boundary\nSELECT time, time_bucket(INTERVAL '2 hour', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2017-11-05 10:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 12:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 13:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 15:05:00+07'\n    ]) AS time;\n             time             |         time_bucket          \n------------------------------+------------------------------\n Sat Nov 04 23:05:00 2017 EDT | Sat Nov 04 22:00:00 2017 EDT\n Sun Nov 05 01:05:00 2017 EDT | Sun Nov 05 00:00:00 2017 EDT\n Sun Nov 05 01:05:00 2017 EST | Sun Nov 05 01:00:00 2017 EST\n Sun Nov 05 03:05:00 2017 EST | Sun Nov 05 03:00:00 2017 EST\n\n--local alignment is preserved when bucketing by local time across DST boundary.\nSELECT time, time_bucket(INTERVAL '2 hour', time::timestamp)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2017-11-05 10:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 12:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 13:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 15:05:00+07'\n    ]) AS time;\n             time             |       time_bucket        \n------------------------------+--------------------------\n Sat Nov 04 23:05:00 2017 EDT | Sat Nov 04 22:00:00 2017\n Sun Nov 05 01:05:00 2017 EDT | Sun Nov 05 00:00:00 2017\n Sun Nov 05 01:05:00 2017 EST | Sun Nov 05 00:00:00 2017\n Sun Nov 05 03:05:00 2017 EST | Sun Nov 05 02:00:00 2017\n\n-- GitHub issue #7059: time_bucket with timezone + offset across DST boundary\n-- Asia/Amman: clocks skip from 00:00 to 01:00 on 2021-03-26\n-- Input: 01:00+03 = 22:00 UTC → Result: 22:15 UTC = 01:15 local (00:00 + 15min offset)\nSELECT time_bucket('1 day', '2021-03-26 01:00:00+03'::timestamptz,\n    timezone := 'Asia/Amman', \"offset\" := '15 minutes'::interval);\n         time_bucket          \n------------------------------\n Wed Mar 24 18:15:00 2021 EDT\n\n-- GitHub issue #8851: time_bucket with negative offset during DST fall-back\n-- Europe/Berlin: clocks repeat 02:00-02:59 on 2025-10-26\n-- Input: 02:00+02 = 00:00 UTC → Result: 23:59:45 UTC = 01:59:45 local (02:00 - 15s offset)\nSELECT time_bucket('30 seconds', '2025-10-26 02:00:00+02'::timestamptz,\n    timezone := 'Europe/Berlin', \"offset\" := '-15 seconds'::interval);\n         time_bucket          \n------------------------------\n Sat Oct 25 19:59:45 2025 EDT\n\n-- Additional DST edge cases for coverage of DST direction × offset sign combinations\n-- Spring-forward + negative offset\n-- Input: 01:30+03 = 22:30 UTC → Result: 22:45 UTC = 01:45 local (01:00 + 45min = 02:00 - 15min)\nSELECT time_bucket('1 hour', '2021-03-26 01:30:00+03'::timestamptz,\n    timezone := 'Asia/Amman', \"offset\" := '-15 minutes'::interval);\n         time_bucket          \n------------------------------\n Thu Mar 25 17:45:00 2021 EDT\n\n-- Fall-back + positive offset\n-- Input: 02:30+01 = 01:30 UTC → Result: 01:15 UTC = 02:15 local (02:00 + 15min offset)\nSELECT time_bucket('1 hour', '2025-10-26 02:30:00+01'::timestamptz,\n    timezone := 'Europe/Berlin', \"offset\" := '15 minutes'::interval);\n         time_bucket          \n------------------------------\n Sat Oct 25 21:15:00 2025 EDT\n\n-- Input exactly at DST spring-forward transition\n-- Input: 22:00 UTC = 00:00 local (the moment clocks jump to 01:00)\n-- Result: 22:15 UTC = 01:15 local (01:00 + 15min offset)\nSELECT time_bucket('1 hour', '2021-03-25 22:00:00+00'::timestamptz,\n    timezone := 'Asia/Amman', \"offset\" := '15 minutes'::interval);\n         time_bucket          \n------------------------------\n Thu Mar 25 17:15:00 2021 EDT\n\n-- Input exactly at DST fall-back transition\n-- Input: 01:00 UTC = 03:00 CEST (the moment clocks go back to 02:00 CET)\n-- Result: 23:15 UTC = 01:15 local (01:00 + 15min offset, but in CET now)\nSELECT time_bucket('1 hour', '2025-10-26 01:00:00+00'::timestamptz,\n    timezone := 'Europe/Berlin', \"offset\" := '15 minutes'::interval);\n         time_bucket          \n------------------------------\n Sat Oct 25 20:15:00 2025 EDT\n\n-- Offset larger than bucket size (1h offset with 30min bucket)\n-- Input: 01:30+03 = 22:30 UTC → Result: 22:30 UTC = 01:30 local (01:00 + 30min = 00:30 + 1h)\nSELECT time_bucket('30 minutes', '2021-03-26 01:30:00+03'::timestamptz,\n    timezone := 'Asia/Amman', \"offset\" := '1 hour'::interval);\n         time_bucket          \n------------------------------\n Thu Mar 25 18:30:00 2021 EDT\n\n-- GitHub issue #9136: time_bucket with origin during DST fall-back\n-- When origin is in standard time but timestamp is in daylight time,\n-- the bucket could incorrectly start AFTER the timestamp.\n-- America/New_York: clocks go back at 02:00 EDT on 2024-11-03\n-- Input: 01:30-04 (EDT) = 05:30 UTC; origin in EST\n-- Result should have bucket start <= timestamp (bucket in EDT, not EST)\nSELECT time_bucket('1 hour', '2024-11-03 01:30:00-04'::timestamptz,\n    'America/New_York', '2000-01-01 00:00:00 America/New_York'::timestamptz) as bucket,\n    '2024-11-03 01:30:00-04'::timestamptz < time_bucket('1 hour',\n        '2024-11-03 01:30:00-04'::timestamptz, 'America/New_York',\n        '2000-01-01 00:00:00 America/New_York'::timestamptz) as ts_before_bucket;\n            bucket            | ts_before_bucket \n------------------------------+------------------\n Sun Nov 03 01:00:00 2024 EDT | f\n\nSELECT time,\n    time_bucket(10::smallint, time) AS time_bucket_smallint,\n    time_bucket(10::int, time) AS time_bucket_int,\n    time_bucket(10::bigint, time) AS time_bucket_bigint\nFROM unnest(ARRAY[\n     '-11',\n     '-10',\n      '-9',\n      '-1',\n       '0',\n       '1',\n      '99',\n     '100',\n     '109',\n     '110'\n    ]::smallint[]) AS time;\n time | time_bucket_smallint | time_bucket_int | time_bucket_bigint \n------+----------------------+-----------------+--------------------\n  -11 |                  -20 |             -20 |                -20\n  -10 |                  -10 |             -10 |                -10\n   -9 |                  -10 |             -10 |                -10\n   -1 |                  -10 |             -10 |                -10\n    0 |                    0 |               0 |                  0\n    1 |                    0 |               0 |                  0\n   99 |                   90 |              90 |                 90\n  100 |                  100 |             100 |                100\n  109 |                  100 |             100 |                100\n  110 |                  110 |             110 |                110\n\nSELECT time,\n    time_bucket(10::smallint, time, 2::smallint) AS time_bucket_smallint,\n    time_bucket(10::int, time, 2::int) AS time_bucket_int,\n    time_bucket(10::bigint, time, 2::bigint) AS time_bucket_bigint\nFROM unnest(ARRAY[\n      '-9',\n      '-8',\n      '-7',\n       '1',\n       '2',\n       '3',\n     '101',\n     '102',\n     '111',\n     '112'\n    ]::smallint[]) AS time;\n time | time_bucket_smallint | time_bucket_int | time_bucket_bigint \n------+----------------------+-----------------+--------------------\n   -9 |                  -18 |             -18 |                -18\n   -8 |                   -8 |              -8 |                 -8\n   -7 |                   -8 |              -8 |                 -8\n    1 |                   -8 |              -8 |                 -8\n    2 |                    2 |               2 |                  2\n    3 |                    2 |               2 |                  2\n  101 |                   92 |              92 |                 92\n  102 |                  102 |             102 |                102\n  111 |                  102 |             102 |                102\n  112 |                  112 |             112 |                112\n\nSELECT time,\n    time_bucket(10::smallint, time, -2::smallint) AS time_bucket_smallint,\n    time_bucket(10::int, time, -2::int) AS time_bucket_int,\n    time_bucket(10::bigint, time, -2::bigint) AS time_bucket_bigint\nFROM unnest(ARRAY[\n    '-13',\n    '-12',\n    '-11',\n     '-3',\n     '-2',\n     '-1',\n     '97',\n     '98',\n    '107',\n    '108'\n    ]::smallint[]) AS time;\n time | time_bucket_smallint | time_bucket_int | time_bucket_bigint \n------+----------------------+-----------------+--------------------\n  -13 |                  -22 |             -22 |                -22\n  -12 |                  -12 |             -12 |                -12\n  -11 |                  -12 |             -12 |                -12\n   -3 |                  -12 |             -12 |                -12\n   -2 |                   -2 |              -2 |                 -2\n   -1 |                   -2 |              -2 |                 -2\n   97 |                   88 |              88 |                 88\n   98 |                   98 |              98 |                 98\n  107 |                   98 |              98 |                 98\n  108 |                  108 |             108 |                108\n\n\\set ON_ERROR_STOP 0\nSELECT time_bucket(10::smallint, '-32768'::smallint);\nERROR:  timestamp out of range\nSELECT time_bucket(10::smallint, '-32761'::smallint);\nERROR:  timestamp out of range\nselect time_bucket(10::smallint, '-32768'::smallint, 1000::smallint);\nERROR:  timestamp out of range\nselect time_bucket(10::smallint, '-32768'::smallint, '32767'::smallint);\nERROR:  timestamp out of range\nselect time_bucket(10::smallint, '32767'::smallint, '-32768'::smallint);\nERROR:  timestamp out of range\n\\set ON_ERROR_STOP 1\nSELECT time, time_bucket(10::smallint, time)\nFROM unnest(ARRAY[\n    '-32760',\n    '-32759',\n    '32767'\n    ]::smallint[]) AS time;\n  time  | time_bucket \n--------+-------------\n -32760 |      -32760\n -32759 |      -32760\n  32767 |       32760\n\n\\set ON_ERROR_STOP 0\nSELECT time_bucket(10::int, '-2147483648'::int);\nERROR:  timestamp out of range\nSELECT time_bucket(10::int, '-2147483641'::int);\nERROR:  timestamp out of range\nSELECT time_bucket(1000::int, '-2147483000'::int, 1::int);\nERROR:  timestamp out of range\nSELECT time_bucket(1000::int, '-2147483648'::int, '2147483647'::int);\nERROR:  timestamp out of range\nSELECT time_bucket(1000::int, '2147483647'::int, '-2147483648'::int);\nERROR:  timestamp out of range\n\\set ON_ERROR_STOP 1\nSELECT time, time_bucket(10::int, time)\nFROM unnest(ARRAY[\n    '-2147483640',\n    '-2147483639',\n    '2147483647'\n    ]::int[]) AS time;\n    time     | time_bucket \n-------------+-------------\n -2147483640 | -2147483640\n -2147483639 | -2147483640\n  2147483647 |  2147483640\n\n\\set ON_ERROR_STOP 0\nSELECT time_bucket(10::bigint, '-9223372036854775808'::bigint);\nERROR:  timestamp out of range\nSELECT time_bucket(10::bigint, '-9223372036854775801'::bigint);\nERROR:  timestamp out of range\nSELECT time_bucket(1000::bigint, '-9223372036854775000'::bigint, 1::bigint);\nERROR:  timestamp out of range\nSELECT time_bucket(1000::bigint, '-9223372036854775808'::bigint, '9223372036854775807'::bigint);\nERROR:  timestamp out of range\nSELECT time_bucket(1000::bigint, '9223372036854775807'::bigint, '-9223372036854775808'::bigint);\nERROR:  timestamp out of range\n\\set ON_ERROR_STOP 1\nSELECT time, time_bucket(10::bigint, time)\nFROM unnest(ARRAY[\n    '-9223372036854775800',\n    '-9223372036854775799',\n    '9223372036854775807'\n    ]::bigint[]) AS time;\n         time         |     time_bucket      \n----------------------+----------------------\n -9223372036854775800 | -9223372036854775800\n -9223372036854775799 | -9223372036854775800\n  9223372036854775807 |  9223372036854775800\n\nSELECT time, time_bucket(INTERVAL '1 day', time::date)\nFROM unnest(ARRAY[\n    date '2017-11-05',\n    date '2017-11-06'\n    ]) AS time;\n    time    | time_bucket \n------------+-------------\n 11-05-2017 | 11-05-2017\n 11-06-2017 | 11-06-2017\n\nSELECT time, time_bucket(INTERVAL '4 day', time::date)\nFROM unnest(ARRAY[\n    date '2017-11-04',\n    date '2017-11-05',\n    date '2017-11-08',\n    date '2017-11-09'\n    ]) AS time;\n    time    | time_bucket \n------------+-------------\n 11-04-2017 | 11-01-2017\n 11-05-2017 | 11-05-2017\n 11-08-2017 | 11-05-2017\n 11-09-2017 | 11-09-2017\n\nSELECT time, time_bucket(INTERVAL '4 day', time::date, INTERVAL '2 day')\nFROM unnest(ARRAY[\n    date '2017-11-06',\n    date '2017-11-07',\n    date '2017-11-10',\n    date '2017-11-11'\n    ]) AS time;\n    time    | time_bucket \n------------+-------------\n 11-06-2017 | 11-03-2017\n 11-07-2017 | 11-07-2017\n 11-10-2017 | 11-07-2017\n 11-11-2017 | 11-11-2017\n\n-- 2019-09-24 is a Monday, and we want to ensure that time_bucket returns the week starting with a Monday as date_trunc does,\n-- Rather than a Saturday which is the date of the PostgreSQL epoch\nSELECT time, time_bucket(INTERVAL '1 week', time::date)\nFROM unnest(ARRAY[\n    date '2018-09-16',\n    date '2018-09-17',\n    date '2018-09-23',\n    date '2018-09-24'\n    ]) AS time;\n    time    | time_bucket \n------------+-------------\n 09-16-2018 | 09-10-2018\n 09-17-2018 | 09-17-2018\n 09-23-2018 | 09-17-2018\n 09-24-2018 | 09-24-2018\n\nSELECT time, time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp without time zone '2018-09-16',\n    timestamp without time zone '2018-09-17',\n    timestamp without time zone '2018-09-23',\n    timestamp without time zone '2018-09-24'\n    ]) AS time;\n           time           |       time_bucket        \n--------------------------+--------------------------\n Sun Sep 16 00:00:00 2018 | Mon Sep 10 00:00:00 2018\n Mon Sep 17 00:00:00 2018 | Mon Sep 17 00:00:00 2018\n Sun Sep 23 00:00:00 2018 | Mon Sep 17 00:00:00 2018\n Mon Sep 24 00:00:00 2018 | Mon Sep 24 00:00:00 2018\n\nSELECT time, time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp with time zone '2018-09-16',\n    timestamp with time zone '2018-09-17',\n    timestamp with time zone '2018-09-23',\n    timestamp with time zone '2018-09-24'\n    ]) AS time;\n             time             |         time_bucket          \n------------------------------+------------------------------\n Sun Sep 16 00:00:00 2018 EDT | Sun Sep 09 20:00:00 2018 EDT\n Mon Sep 17 00:00:00 2018 EDT | Sun Sep 16 20:00:00 2018 EDT\n Sun Sep 23 00:00:00 2018 EDT | Sun Sep 16 20:00:00 2018 EDT\n Mon Sep 24 00:00:00 2018 EDT | Sun Sep 23 20:00:00 2018 EDT\n\nSELECT time, time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp with time zone '-Infinity',\n    timestamp with time zone 'Infinity'\n    ]) AS time;\n   time    | time_bucket \n-----------+-------------\n -infinity | -infinity\n infinity  | infinity\n\nSELECT time, time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp without time zone '-Infinity',\n    timestamp without time zone 'Infinity'\n    ]) AS time;\n   time    | time_bucket \n-----------+-------------\n -infinity | -infinity\n infinity  | infinity\n\nSELECT time, time_bucket(INTERVAL '1 week', time), date_trunc('week', time) = time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp without time zone '4714-11-24 01:01:01.0 BC',\n    timestamp without time zone '294276-12-31 23:59:59.9999'\n    ]) AS time;\n              time               |         time_bucket         | ?column? \n---------------------------------+-----------------------------+----------\n Mon Nov 24 01:01:01 4714 BC     | Mon Nov 24 00:00:00 4714 BC | t\n Sun Dec 31 23:59:59.9999 294276 | Mon Dec 25 00:00:00 294276  | t\n\n--1000 years later weeks still align.\nSELECT time, time_bucket(INTERVAL '1 week', time), date_trunc('week', time) = time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp without time zone '3018-09-14',\n    timestamp without time zone '3018-09-20',\n    timestamp without time zone '3018-09-21',\n    timestamp without time zone '3018-09-22'\n    ]) AS time;\n           time           |       time_bucket        | ?column? \n--------------------------+--------------------------+----------\n Mon Sep 14 00:00:00 3018 | Mon Sep 14 00:00:00 3018 | t\n Sun Sep 20 00:00:00 3018 | Mon Sep 14 00:00:00 3018 | t\n Mon Sep 21 00:00:00 3018 | Mon Sep 21 00:00:00 3018 | t\n Tue Sep 22 00:00:00 3018 | Mon Sep 21 00:00:00 3018 | t\n\n--weeks align for timestamptz as well if cast to local time, (but not if done at UTC).\nSELECT time, date_trunc('week', time) = time_bucket(INTERVAL '1 week', time),  date_trunc('week', time) = time_bucket(INTERVAL '1 week', time::timestamp)\nFROM unnest(ARRAY[\n    timestamp with time zone '3018-09-14',\n    timestamp with time zone '3018-09-20',\n    timestamp with time zone '3018-09-21',\n    timestamp with time zone '3018-09-22'\n    ]) AS time;\n             time             | ?column? | ?column? \n------------------------------+----------+----------\n Mon Sep 14 00:00:00 3018 EDT | f        | t\n Sun Sep 20 00:00:00 3018 EDT | f        | t\n Mon Sep 21 00:00:00 3018 EDT | f        | t\n Tue Sep 22 00:00:00 3018 EDT | f        | t\n\n--check functions with origin\n--note that the default origin is at 0 UTC, using origin parameter it is easy to provide a EDT origin point\n\\x\nSELECT time, time_bucket(INTERVAL '1 week', time) no_epoch,\n             time_bucket(INTERVAL '1 week', time::timestamp) no_epoch_local,\n             time_bucket(INTERVAL '1 week', time) = time_bucket(INTERVAL '1 week', time, timestamptz '2000-01-03 00:00:00+0') always_true,\n             time_bucket(INTERVAL '1 week', time, timestamptz '2000-01-01 00:00:00+0') pg_epoch,\n             time_bucket(INTERVAL '1 week', time, timestamptz 'epoch') unix_epoch,\n             time_bucket(INTERVAL '1 week', time, timestamptz '3018-09-13') custom_1,\n             time_bucket(INTERVAL '1 week', time, timestamptz '3018-09-14') custom_2\nFROM unnest(ARRAY[\n    timestamp with time zone '2000-01-01 00:00:00+0'- interval '1 second',\n    timestamp with time zone '2000-01-01 00:00:00+0',\n    timestamp with time zone '2000-01-03 00:00:00+0'- interval '1 second',\n    timestamp with time zone '2000-01-03 00:00:00+0',\n    timestamp with time zone '2000-01-01',\n    timestamp with time zone '2000-01-02',\n    timestamp with time zone '2000-01-03',\n    timestamp with time zone '3018-09-12',\n    timestamp with time zone '3018-09-13',\n    timestamp with time zone '3018-09-14',\n    timestamp with time zone '3018-09-15'\n    ]) AS time;\n-[ RECORD 1 ]--+-----------------------------\ntime           | Fri Dec 31 18:59:59 1999 EST\nno_epoch       | Sun Dec 26 19:00:00 1999 EST\nno_epoch_local | Mon Dec 27 00:00:00 1999\nalways_true    | t\npg_epoch       | Fri Dec 24 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Dec 25 23:00:00 1999 EST\ncustom_2       | Sun Dec 26 23:00:00 1999 EST\n-[ RECORD 2 ]--+-----------------------------\ntime           | Fri Dec 31 19:00:00 1999 EST\nno_epoch       | Sun Dec 26 19:00:00 1999 EST\nno_epoch_local | Mon Dec 27 00:00:00 1999\nalways_true    | t\npg_epoch       | Fri Dec 31 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Dec 25 23:00:00 1999 EST\ncustom_2       | Sun Dec 26 23:00:00 1999 EST\n-[ RECORD 3 ]--+-----------------------------\ntime           | Sun Jan 02 18:59:59 2000 EST\nno_epoch       | Sun Dec 26 19:00:00 1999 EST\nno_epoch_local | Mon Dec 27 00:00:00 1999\nalways_true    | t\npg_epoch       | Fri Dec 31 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Jan 01 23:00:00 2000 EST\ncustom_2       | Sun Dec 26 23:00:00 1999 EST\n-[ RECORD 4 ]--+-----------------------------\ntime           | Sun Jan 02 19:00:00 2000 EST\nno_epoch       | Sun Jan 02 19:00:00 2000 EST\nno_epoch_local | Mon Dec 27 00:00:00 1999\nalways_true    | t\npg_epoch       | Fri Dec 31 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Jan 01 23:00:00 2000 EST\ncustom_2       | Sun Dec 26 23:00:00 1999 EST\n-[ RECORD 5 ]--+-----------------------------\ntime           | Sat Jan 01 00:00:00 2000 EST\nno_epoch       | Sun Dec 26 19:00:00 1999 EST\nno_epoch_local | Mon Dec 27 00:00:00 1999\nalways_true    | t\npg_epoch       | Fri Dec 31 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Dec 25 23:00:00 1999 EST\ncustom_2       | Sun Dec 26 23:00:00 1999 EST\n-[ RECORD 6 ]--+-----------------------------\ntime           | Sun Jan 02 00:00:00 2000 EST\nno_epoch       | Sun Dec 26 19:00:00 1999 EST\nno_epoch_local | Mon Dec 27 00:00:00 1999\nalways_true    | t\npg_epoch       | Fri Dec 31 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Jan 01 23:00:00 2000 EST\ncustom_2       | Sun Dec 26 23:00:00 1999 EST\n-[ RECORD 7 ]--+-----------------------------\ntime           | Mon Jan 03 00:00:00 2000 EST\nno_epoch       | Sun Jan 02 19:00:00 2000 EST\nno_epoch_local | Mon Jan 03 00:00:00 2000\nalways_true    | t\npg_epoch       | Fri Dec 31 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Jan 01 23:00:00 2000 EST\ncustom_2       | Sun Jan 02 23:00:00 2000 EST\n-[ RECORD 8 ]--+-----------------------------\ntime           | Sat Sep 12 00:00:00 3018 EDT\nno_epoch       | Sun Sep 06 20:00:00 3018 EDT\nno_epoch_local | Mon Sep 07 00:00:00 3018\nalways_true    | t\npg_epoch       | Fri Sep 11 20:00:00 3018 EDT\nunix_epoch     | Wed Sep 09 20:00:00 3018 EDT\ncustom_1       | Sun Sep 06 00:00:00 3018 EDT\ncustom_2       | Mon Sep 07 00:00:00 3018 EDT\n-[ RECORD 9 ]--+-----------------------------\ntime           | Sun Sep 13 00:00:00 3018 EDT\nno_epoch       | Sun Sep 06 20:00:00 3018 EDT\nno_epoch_local | Mon Sep 07 00:00:00 3018\nalways_true    | t\npg_epoch       | Fri Sep 11 20:00:00 3018 EDT\nunix_epoch     | Wed Sep 09 20:00:00 3018 EDT\ncustom_1       | Sun Sep 13 00:00:00 3018 EDT\ncustom_2       | Mon Sep 07 00:00:00 3018 EDT\n-[ RECORD 10 ]-+-----------------------------\ntime           | Mon Sep 14 00:00:00 3018 EDT\nno_epoch       | Sun Sep 13 20:00:00 3018 EDT\nno_epoch_local | Mon Sep 14 00:00:00 3018\nalways_true    | t\npg_epoch       | Fri Sep 11 20:00:00 3018 EDT\nunix_epoch     | Wed Sep 09 20:00:00 3018 EDT\ncustom_1       | Sun Sep 13 00:00:00 3018 EDT\ncustom_2       | Mon Sep 14 00:00:00 3018 EDT\n-[ RECORD 11 ]-+-----------------------------\ntime           | Tue Sep 15 00:00:00 3018 EDT\nno_epoch       | Sun Sep 13 20:00:00 3018 EDT\nno_epoch_local | Mon Sep 14 00:00:00 3018\nalways_true    | t\npg_epoch       | Fri Sep 11 20:00:00 3018 EDT\nunix_epoch     | Wed Sep 09 20:00:00 3018 EDT\ncustom_1       | Sun Sep 13 00:00:00 3018 EDT\ncustom_2       | Mon Sep 14 00:00:00 3018 EDT\n\nSELECT time, time_bucket(INTERVAL '1 week', time) no_epoch,\n             time_bucket(INTERVAL '1 week', time) = time_bucket(INTERVAL '1 week', time, timestamp '2000-01-03 00:00:00') always_true,\n             time_bucket(INTERVAL '1 week', time, timestamp '2000-01-01 00:00:00+0') pg_epoch,\n             time_bucket(INTERVAL '1 week', time, timestamp 'epoch') unix_epoch,\n             time_bucket(INTERVAL '1 week', time, timestamp '3018-09-13') custom_1,\n             time_bucket(INTERVAL '1 week', time, timestamp '3018-09-14') custom_2\nFROM unnest(ARRAY[\n    timestamp without time zone '2000-01-01 00:00:00'- interval '1 second',\n    timestamp without time zone '2000-01-01 00:00:00',\n    timestamp without time zone '2000-01-03 00:00:00'- interval '1 second',\n    timestamp without time zone '2000-01-03 00:00:00',\n    timestamp without time zone '2000-01-01',\n    timestamp without time zone '2000-01-02',\n    timestamp without time zone '2000-01-03',\n    timestamp without time zone '3018-09-12',\n    timestamp without time zone '3018-09-13',\n    timestamp without time zone '3018-09-14',\n    timestamp without time zone '3018-09-15'\n    ]) AS time;\n-[ RECORD 1 ]-------------------------\ntime        | Fri Dec 31 23:59:59 1999\nno_epoch    | Mon Dec 27 00:00:00 1999\nalways_true | t\npg_epoch    | Sat Dec 25 00:00:00 1999\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Dec 26 00:00:00 1999\ncustom_2    | Mon Dec 27 00:00:00 1999\n-[ RECORD 2 ]-------------------------\ntime        | Sat Jan 01 00:00:00 2000\nno_epoch    | Mon Dec 27 00:00:00 1999\nalways_true | t\npg_epoch    | Sat Jan 01 00:00:00 2000\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Dec 26 00:00:00 1999\ncustom_2    | Mon Dec 27 00:00:00 1999\n-[ RECORD 3 ]-------------------------\ntime        | Sun Jan 02 23:59:59 2000\nno_epoch    | Mon Dec 27 00:00:00 1999\nalways_true | t\npg_epoch    | Sat Jan 01 00:00:00 2000\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Jan 02 00:00:00 2000\ncustom_2    | Mon Dec 27 00:00:00 1999\n-[ RECORD 4 ]-------------------------\ntime        | Mon Jan 03 00:00:00 2000\nno_epoch    | Mon Jan 03 00:00:00 2000\nalways_true | t\npg_epoch    | Sat Jan 01 00:00:00 2000\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Jan 02 00:00:00 2000\ncustom_2    | Mon Jan 03 00:00:00 2000\n-[ RECORD 5 ]-------------------------\ntime        | Sat Jan 01 00:00:00 2000\nno_epoch    | Mon Dec 27 00:00:00 1999\nalways_true | t\npg_epoch    | Sat Jan 01 00:00:00 2000\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Dec 26 00:00:00 1999\ncustom_2    | Mon Dec 27 00:00:00 1999\n-[ RECORD 6 ]-------------------------\ntime        | Sun Jan 02 00:00:00 2000\nno_epoch    | Mon Dec 27 00:00:00 1999\nalways_true | t\npg_epoch    | Sat Jan 01 00:00:00 2000\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Jan 02 00:00:00 2000\ncustom_2    | Mon Dec 27 00:00:00 1999\n-[ RECORD 7 ]-------------------------\ntime        | Mon Jan 03 00:00:00 2000\nno_epoch    | Mon Jan 03 00:00:00 2000\nalways_true | t\npg_epoch    | Sat Jan 01 00:00:00 2000\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Jan 02 00:00:00 2000\ncustom_2    | Mon Jan 03 00:00:00 2000\n-[ RECORD 8 ]-------------------------\ntime        | Sat Sep 12 00:00:00 3018\nno_epoch    | Mon Sep 07 00:00:00 3018\nalways_true | t\npg_epoch    | Sat Sep 12 00:00:00 3018\nunix_epoch  | Thu Sep 10 00:00:00 3018\ncustom_1    | Sun Sep 06 00:00:00 3018\ncustom_2    | Mon Sep 07 00:00:00 3018\n-[ RECORD 9 ]-------------------------\ntime        | Sun Sep 13 00:00:00 3018\nno_epoch    | Mon Sep 07 00:00:00 3018\nalways_true | t\npg_epoch    | Sat Sep 12 00:00:00 3018\nunix_epoch  | Thu Sep 10 00:00:00 3018\ncustom_1    | Sun Sep 13 00:00:00 3018\ncustom_2    | Mon Sep 07 00:00:00 3018\n-[ RECORD 10 ]------------------------\ntime        | Mon Sep 14 00:00:00 3018\nno_epoch    | Mon Sep 14 00:00:00 3018\nalways_true | t\npg_epoch    | Sat Sep 12 00:00:00 3018\nunix_epoch  | Thu Sep 10 00:00:00 3018\ncustom_1    | Sun Sep 13 00:00:00 3018\ncustom_2    | Mon Sep 14 00:00:00 3018\n-[ RECORD 11 ]------------------------\ntime        | Tue Sep 15 00:00:00 3018\nno_epoch    | Mon Sep 14 00:00:00 3018\nalways_true | t\npg_epoch    | Sat Sep 12 00:00:00 3018\nunix_epoch  | Thu Sep 10 00:00:00 3018\ncustom_1    | Sun Sep 13 00:00:00 3018\ncustom_2    | Mon Sep 14 00:00:00 3018\n\nSELECT time, time_bucket(INTERVAL '1 week', time) no_epoch,\n             time_bucket(INTERVAL '1 week', time) = time_bucket(INTERVAL '1 week', time, date '2000-01-03') always_true,\n             time_bucket(INTERVAL '1 week', time, date '2000-01-01') pg_epoch,\n             time_bucket(INTERVAL '1 week', time, (timestamp 'epoch')::date) unix_epoch,\n             time_bucket(INTERVAL '1 week', time, date '3018-09-13') custom_1,\n             time_bucket(INTERVAL '1 week', time, date '3018-09-14') custom_2\nFROM unnest(ARRAY[\n    date '1999-12-31',\n    date '2000-01-01',\n    date '2000-01-02',\n    date '2000-01-03',\n    date '3018-09-12',\n    date '3018-09-13',\n    date '3018-09-14',\n    date '3018-09-15'\n    ]) AS time;\n-[ RECORD 1 ]-----------\ntime        | 12-31-1999\nno_epoch    | 12-27-1999\nalways_true | t\npg_epoch    | 12-25-1999\nunix_epoch  | 12-30-1999\ncustom_1    | 12-26-1999\ncustom_2    | 12-27-1999\n-[ RECORD 2 ]-----------\ntime        | 01-01-2000\nno_epoch    | 12-27-1999\nalways_true | t\npg_epoch    | 01-01-2000\nunix_epoch  | 12-30-1999\ncustom_1    | 12-26-1999\ncustom_2    | 12-27-1999\n-[ RECORD 3 ]-----------\ntime        | 01-02-2000\nno_epoch    | 12-27-1999\nalways_true | t\npg_epoch    | 01-01-2000\nunix_epoch  | 12-30-1999\ncustom_1    | 01-02-2000\ncustom_2    | 12-27-1999\n-[ RECORD 4 ]-----------\ntime        | 01-03-2000\nno_epoch    | 01-03-2000\nalways_true | t\npg_epoch    | 01-01-2000\nunix_epoch  | 12-30-1999\ncustom_1    | 01-02-2000\ncustom_2    | 01-03-2000\n-[ RECORD 5 ]-----------\ntime        | 09-12-3018\nno_epoch    | 09-07-3018\nalways_true | t\npg_epoch    | 09-12-3018\nunix_epoch  | 09-10-3018\ncustom_1    | 09-06-3018\ncustom_2    | 09-07-3018\n-[ RECORD 6 ]-----------\ntime        | 09-13-3018\nno_epoch    | 09-07-3018\nalways_true | t\npg_epoch    | 09-12-3018\nunix_epoch  | 09-10-3018\ncustom_1    | 09-13-3018\ncustom_2    | 09-07-3018\n-[ RECORD 7 ]-----------\ntime        | 09-14-3018\nno_epoch    | 09-14-3018\nalways_true | t\npg_epoch    | 09-12-3018\nunix_epoch  | 09-10-3018\ncustom_1    | 09-13-3018\ncustom_2    | 09-14-3018\n-[ RECORD 8 ]-----------\ntime        | 09-15-3018\nno_epoch    | 09-14-3018\nalways_true | t\npg_epoch    | 09-12-3018\nunix_epoch  | 09-10-3018\ncustom_1    | 09-13-3018\ncustom_2    | 09-14-3018\n\n\\x\n--really old origin works if date around that time\nSELECT time, time_bucket(INTERVAL '1 week', time, timestamp without time zone '4710-11-24 01:01:01.0 BC')\nFROM unnest(ARRAY[\n    timestamp without time zone '4710-11-24 01:01:01.0 BC',\n    timestamp without time zone '4710-11-25 01:01:01.0 BC',\n    timestamp without time zone '2001-01-01',\n    timestamp without time zone '3001-01-01'\n    ]) AS time;\n            time             |         time_bucket         \n-----------------------------+-----------------------------\n Sat Nov 24 01:01:01 4710 BC | Sat Nov 24 01:01:01 4710 BC\n Sun Nov 25 01:01:01 4710 BC | Sat Nov 24 01:01:01 4710 BC\n Mon Jan 01 00:00:00 2001    | Sat Dec 30 01:01:01 2000\n Thu Jan 01 00:00:00 3001    | Sat Dec 27 01:01:01 3000\n\nSELECT time, time_bucket(INTERVAL '1 week', time, timestamp without time zone '294270-12-30 23:59:59.9999')\nFROM unnest(ARRAY[\n    timestamp without time zone '294270-12-29 23:59:59.9999',\n    timestamp without time zone '294270-12-30 23:59:59.9999',\n    timestamp without time zone '294270-12-31 23:59:59.9999',\n    timestamp without time zone '2001-01-01',\n    timestamp without time zone '3001-01-01'\n    ]) AS time;\n              time               |           time_bucket           \n---------------------------------+---------------------------------\n Thu Dec 29 23:59:59.9999 294270 | Fri Dec 23 23:59:59.9999 294270\n Fri Dec 30 23:59:59.9999 294270 | Fri Dec 30 23:59:59.9999 294270\n Sat Dec 31 23:59:59.9999 294270 | Fri Dec 30 23:59:59.9999 294270\n Mon Jan 01 00:00:00 2001        | Fri Dec 29 23:59:59.9999 2000\n Thu Jan 01 00:00:00 3001        | Fri Dec 26 23:59:59.9999 3000\n\n\\set ON_ERROR_STOP 0\n--really old origin + very new data + long period errors\nSELECT time, time_bucket(INTERVAL '100000 day', time, timestamp without time zone '4710-11-24 01:01:01.0 BC')\nFROM unnest(ARRAY[\n    timestamp without time zone '294270-12-31 23:59:59.9999'\n    ]) AS time;\nERROR:  timestamp out of range\nSELECT time, time_bucket(INTERVAL '100000 day', time, timestamp with time zone '4710-11-25 01:01:01.0 BC')\nFROM unnest(ARRAY[\n    timestamp with time zone '294270-12-30 23:59:59.9999'\n    ]) AS time;\nERROR:  timestamp out of range\n--really high origin + old data + long period errors out\nSELECT time, time_bucket(INTERVAL '10000000 day', time, timestamp without time zone '294270-12-31 23:59:59.9999')\nFROM unnest(ARRAY[\n    timestamp without time zone '4710-11-24 01:01:01.0 BC'\n    ]) AS time;\nERROR:  timestamp out of range\nSELECT time, time_bucket(INTERVAL '10000000 day', time, timestamp with time zone '294270-12-31 23:59:59.9999')\nFROM unnest(ARRAY[\n    timestamp with time zone '4710-11-24 01:01:01.0 BC'\n    ]) AS time;\nERROR:  timestamp out of range\n\\set ON_ERROR_STOP 1\n-------------------------------------------\n--- Test time_bucket with month periods ---\n-------------------------------------------\nSET datestyle TO ISO;\nSELECT\n  time::date,\n  time_bucket('1 month', time::date) AS \"1m\",\n  time_bucket('2 month', time::date) AS \"2m\",\n  time_bucket('3 month', time::date) AS \"3m\",\n  time_bucket('1 month', time::date, '2000-02-01'::date) AS \"1m origin\",\n  time_bucket('2 month', time::date, '2000-02-01'::date) AS \"2m origin\",\n  time_bucket('3 month', time::date, '2000-02-01'::date) AS \"3m origin\"\nFROM generate_series('1990-01-03'::date,'1990-06-03'::date,'1month'::interval) time;\n    time    |     1m     |     2m     |     3m     | 1m origin  | 2m origin  | 3m origin  \n------------+------------+------------+------------+------------+------------+------------\n 1990-01-03 | 1990-01-01 | 1990-01-01 | 1990-01-01 | 1990-01-01 | 1989-12-01 | 1989-11-01\n 1990-02-03 | 1990-02-01 | 1990-01-01 | 1990-01-01 | 1990-02-01 | 1990-02-01 | 1990-02-01\n 1990-03-03 | 1990-03-01 | 1990-03-01 | 1990-01-01 | 1990-03-01 | 1990-02-01 | 1990-02-01\n 1990-04-03 | 1990-04-01 | 1990-03-01 | 1990-04-01 | 1990-04-01 | 1990-04-01 | 1990-02-01\n 1990-05-03 | 1990-05-01 | 1990-05-01 | 1990-04-01 | 1990-05-01 | 1990-04-01 | 1990-05-01\n 1990-06-03 | 1990-06-01 | 1990-05-01 | 1990-04-01 | 1990-06-01 | 1990-06-01 | 1990-05-01\n\nSELECT\n  time,\n  time_bucket('1 month', time) AS \"1m\",\n  time_bucket('2 month', time) AS \"2m\",\n  time_bucket('3 month', time) AS \"3m\",\n  time_bucket('1 month', time, '2000-02-01'::timestamp) AS \"1m origin\",\n  time_bucket('2 month', time, '2000-02-01'::timestamp) AS \"2m origin\",\n  time_bucket('3 month', time, '2000-02-01'::timestamp) AS \"3m origin\"\nFROM generate_series('1990-01-03'::timestamp,'1990-06-03'::timestamp,'1month'::interval) time;\n        time         |         1m          |         2m          |         3m          |      1m origin      |      2m origin      |      3m origin      \n---------------------+---------------------+---------------------+---------------------+---------------------+---------------------+---------------------\n 1990-01-03 00:00:00 | 1990-01-01 00:00:00 | 1990-01-01 00:00:00 | 1990-01-01 00:00:00 | 1990-01-01 00:00:00 | 1989-12-01 00:00:00 | 1989-11-01 00:00:00\n 1990-02-03 00:00:00 | 1990-02-01 00:00:00 | 1990-01-01 00:00:00 | 1990-01-01 00:00:00 | 1990-02-01 00:00:00 | 1990-02-01 00:00:00 | 1990-02-01 00:00:00\n 1990-03-03 00:00:00 | 1990-03-01 00:00:00 | 1990-03-01 00:00:00 | 1990-01-01 00:00:00 | 1990-03-01 00:00:00 | 1990-02-01 00:00:00 | 1990-02-01 00:00:00\n 1990-04-03 00:00:00 | 1990-04-01 00:00:00 | 1990-03-01 00:00:00 | 1990-04-01 00:00:00 | 1990-04-01 00:00:00 | 1990-04-01 00:00:00 | 1990-02-01 00:00:00\n 1990-05-03 00:00:00 | 1990-05-01 00:00:00 | 1990-05-01 00:00:00 | 1990-04-01 00:00:00 | 1990-05-01 00:00:00 | 1990-04-01 00:00:00 | 1990-05-01 00:00:00\n 1990-06-03 00:00:00 | 1990-06-01 00:00:00 | 1990-05-01 00:00:00 | 1990-04-01 00:00:00 | 1990-06-01 00:00:00 | 1990-06-01 00:00:00 | 1990-05-01 00:00:00\n\nSELECT\n  time,\n  time_bucket('1 month', time) AS \"1m\",\n  time_bucket('2 month', time) AS \"2m\",\n  time_bucket('3 month', time) AS \"3m\",\n  time_bucket('1 month', time, '2000-02-01'::timestamptz) AS \"1m origin\",\n  time_bucket('2 month', time, '2000-02-01'::timestamptz) AS \"2m origin\",\n  time_bucket('3 month', time, '2000-02-01'::timestamptz) AS \"3m origin\"\nFROM generate_series('1990-01-03'::timestamptz,'1990-06-03'::timestamptz,'1month'::interval) time;\n          time          |           1m           |           2m           |           3m           |       1m origin        |       2m origin        |       3m origin        \n------------------------+------------------------+------------------------+------------------------+------------------------+------------------------+------------------------\n 1990-01-03 00:00:00-05 | 1989-12-31 19:00:00-05 | 1989-12-31 19:00:00-05 | 1989-12-31 19:00:00-05 | 1989-12-31 19:00:00-05 | 1989-11-30 19:00:00-05 | 1989-10-31 19:00:00-05\n 1990-02-03 00:00:00-05 | 1990-01-31 19:00:00-05 | 1989-12-31 19:00:00-05 | 1989-12-31 19:00:00-05 | 1990-01-31 19:00:00-05 | 1990-01-31 19:00:00-05 | 1990-01-31 19:00:00-05\n 1990-03-03 00:00:00-05 | 1990-02-28 19:00:00-05 | 1990-02-28 19:00:00-05 | 1989-12-31 19:00:00-05 | 1990-02-28 19:00:00-05 | 1990-01-31 19:00:00-05 | 1990-01-31 19:00:00-05\n 1990-04-03 00:00:00-04 | 1990-03-31 19:00:00-05 | 1990-02-28 19:00:00-05 | 1990-03-31 19:00:00-05 | 1990-03-31 19:00:00-05 | 1990-03-31 19:00:00-05 | 1990-01-31 19:00:00-05\n 1990-05-03 00:00:00-04 | 1990-04-30 20:00:00-04 | 1990-04-30 20:00:00-04 | 1990-03-31 19:00:00-05 | 1990-04-30 20:00:00-04 | 1990-03-31 19:00:00-05 | 1990-04-30 20:00:00-04\n 1990-06-03 00:00:00-04 | 1990-05-31 20:00:00-04 | 1990-04-30 20:00:00-04 | 1990-03-31 19:00:00-05 | 1990-05-31 20:00:00-04 | 1990-05-31 20:00:00-04 | 1990-04-30 20:00:00-04\n\n---------------------------------------\n--- Test time_bucket with timezones ---\n---------------------------------------\n-- test NULL args\nSELECT\ntime_bucket(NULL::interval,now(),'Europe/Berlin'),\ntime_bucket('1day',NULL::timestamptz,'Europe/Berlin'),\ntime_bucket('1day',now(),NULL::text),\ntime_bucket('1day',timestamptz '2020-02-03','Europe/Berlin',NULL),\ntime_bucket('1day',timestamptz '2020-02-03','Europe/Berlin','2020-04-01',NULL),\ntime_bucket('1day',timestamptz '2020-02-03','Europe/Berlin',NULL,NULL),\ntime_bucket('1day',timestamptz '2020-02-03','Europe/Berlin',\"offset\":=NULL::interval),\ntime_bucket('1day',timestamptz '2020-02-03','Europe/Berlin',origin:=NULL::timestamptz);\n time_bucket | time_bucket | time_bucket |      time_bucket       |      time_bucket       |      time_bucket       |      time_bucket       |      time_bucket       \n-------------+-------------+-------------+------------------------+------------------------+------------------------+------------------------+------------------------\n             |             |             | 2020-02-02 18:00:00-05 | 2020-02-03 00:00:00-05 | 2020-02-02 18:00:00-05 | 2020-02-02 18:00:00-05 | 2020-02-02 18:00:00-05\n\nSET datestyle TO ISO;\nSELECT\n  time_bucket('1day', ts) AS \"UTC\",\n  time_bucket('1day', ts, 'Europe/Berlin') AS \"Berlin\",\n  time_bucket('1day', ts, 'Europe/London') AS \"London\",\n  time_bucket('1day', ts, 'America/New_York') AS \"New York\",\n  time_bucket('1day', ts, 'PST') AS \"PST\",\n  time_bucket('1day', ts, current_setting('timezone')) AS \"current\"\nFROM generate_series('1999-12-31 17:00'::timestamptz,'2000-01-02 3:00'::timestamptz, '1hour'::interval) ts;\n          UTC           |         Berlin         |         London         |        New York        |          PST           |        current         \n------------------------+------------------------+------------------------+------------------------+------------------------+------------------------\n 1999-12-30 19:00:00-05 | 1999-12-30 18:00:00-05 | 1999-12-30 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-30 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-30 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-31 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-31 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-31 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 2000-01-01 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-02 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-02 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-02 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-02 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-02 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-02 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-02 00:00:00-05 | 2000-01-02 03:00:00-05 | 2000-01-02 00:00:00-05\n\nSELECT\n  time_bucket('1month', ts) AS \"UTC\",\n  time_bucket('1month', ts, 'Europe/Berlin') AS \"Berlin\",\n  time_bucket('1month', ts, 'America/New_York') AS \"New York\",\n  time_bucket('1month', ts, current_setting('timezone')) AS \"current\",\n  time_bucket('2month', ts, current_setting('timezone')) AS \"2m\",\n  time_bucket('2month', ts, current_setting('timezone'), '2000-02-01'::timestamp) AS \"2m origin\",\n  time_bucket('2month', ts, current_setting('timezone'), \"offset\":='14 day'::interval) AS \"2m offset\",\n  time_bucket('2month', ts, current_setting('timezone'), '2000-02-01'::timestamp, '7 day'::interval) AS \"2m offset + origin\"\nFROM generate_series('1999-12-01'::timestamptz,'2000-09-01'::timestamptz, '9 day'::interval) ts;\n          UTC           |         Berlin         |        New York        |        current         |           2m           |       2m origin        |       2m offset        |   2m offset + origin   \n------------------------+------------------------+------------------------+------------------------+------------------------+------------------------+------------------------+------------------------\n 1999-11-30 19:00:00-05 | 1999-11-30 18:00:00-05 | 1999-12-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-15 00:00:00-05 | 1999-10-08 00:00:00-04\n 1999-11-30 19:00:00-05 | 1999-11-30 18:00:00-05 | 1999-12-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 1999-11-30 19:00:00-05 | 1999-11-30 18:00:00-05 | 1999-12-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 1999-11-30 19:00:00-05 | 1999-11-30 18:00:00-05 | 1999-12-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 2000-01-31 19:00:00-05 | 2000-01-31 18:00:00-05 | 2000-02-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 2000-01-31 19:00:00-05 | 2000-01-31 18:00:00-05 | 2000-02-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-01-31 19:00:00-05 | 2000-01-31 18:00:00-05 | 2000-02-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-01-31 19:00:00-05 | 2000-01-31 18:00:00-05 | 2000-02-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-02-29 19:00:00-05 | 2000-02-29 18:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-02-29 19:00:00-05 | 2000-02-29 18:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-02-29 19:00:00-05 | 2000-02-29 18:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-03-31 19:00:00-05 | 2000-03-31 17:00:00-05 | 2000-04-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-03-31 19:00:00-05 | 2000-03-31 17:00:00-05 | 2000-04-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-04-08 00:00:00-04\n 2000-03-31 19:00:00-05 | 2000-03-31 17:00:00-05 | 2000-04-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-04-08 00:00:00-04\n 2000-04-30 20:00:00-04 | 2000-04-30 18:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-04-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-04-08 00:00:00-04\n 2000-04-30 20:00:00-04 | 2000-04-30 18:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-04-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-04-08 00:00:00-04\n 2000-04-30 20:00:00-04 | 2000-04-30 18:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-04-01 00:00:00-05 | 2000-05-15 00:00:00-04 | 2000-04-08 00:00:00-04\n 2000-04-30 20:00:00-04 | 2000-04-30 18:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-04-01 00:00:00-05 | 2000-05-15 00:00:00-04 | 2000-04-08 00:00:00-04\n 2000-05-31 20:00:00-04 | 2000-05-31 18:00:00-04 | 2000-06-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-15 00:00:00-04 | 2000-04-08 00:00:00-04\n 2000-05-31 20:00:00-04 | 2000-05-31 18:00:00-04 | 2000-06-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-15 00:00:00-04 | 2000-06-08 00:00:00-04\n 2000-05-31 20:00:00-04 | 2000-05-31 18:00:00-04 | 2000-06-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-15 00:00:00-04 | 2000-06-08 00:00:00-04\n 2000-06-30 20:00:00-04 | 2000-06-30 18:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-15 00:00:00-04 | 2000-06-08 00:00:00-04\n 2000-06-30 20:00:00-04 | 2000-06-30 18:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-15 00:00:00-04 | 2000-06-08 00:00:00-04\n 2000-06-30 20:00:00-04 | 2000-06-30 18:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-07-15 00:00:00-04 | 2000-06-08 00:00:00-04\n 2000-06-30 20:00:00-04 | 2000-06-30 18:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-07-15 00:00:00-04 | 2000-06-08 00:00:00-04\n 2000-07-31 20:00:00-04 | 2000-07-31 18:00:00-04 | 2000-08-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-15 00:00:00-04 | 2000-08-08 00:00:00-04\n 2000-07-31 20:00:00-04 | 2000-07-31 18:00:00-04 | 2000-08-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-15 00:00:00-04 | 2000-08-08 00:00:00-04\n 2000-07-31 20:00:00-04 | 2000-07-31 18:00:00-04 | 2000-08-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-15 00:00:00-04 | 2000-08-08 00:00:00-04\n\nRESET datestyle;\n-------------------------------------\n--- Test time input functions --\n-------------------------------------\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION test.interval_to_internal(coltype REGTYPE, value ANYELEMENT = NULL::BIGINT) RETURNS BIGINT\nAS :MODULE_PATHNAME, 'ts_dimension_interval_to_internal_test' LANGUAGE C VOLATILE;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT test.interval_to_internal('TIMESTAMP'::regtype, INTERVAL '1 day');\n interval_to_internal \n----------------------\n          86400000000\n\nSELECT test.interval_to_internal('TIMESTAMP'::regtype, 86400000000);\n interval_to_internal \n----------------------\n          86400000000\n\n---should give warning\nSELECT test.interval_to_internal('TIMESTAMP'::regtype, 86400);\nWARNING:  unexpected interval: smaller than one second\nHINT:  The interval is specified in microseconds.\n interval_to_internal \n----------------------\n                86400\n\nSELECT test.interval_to_internal('TIMESTAMP'::regtype);\n interval_to_internal \n----------------------\n         604800000000\n\nSELECT test.interval_to_internal('BIGINT'::regtype, 2147483649::bigint);\n interval_to_internal \n----------------------\n           2147483649\n\n-- Default interval for integer is supported as part of\n-- hypertable generalization\nSELECT test.interval_to_internal('INT'::regtype);\n interval_to_internal \n----------------------\n               100000\n\nSELECT test.interval_to_internal('SMALLINT'::regtype);\n interval_to_internal \n----------------------\n                10000\n\nSELECT test.interval_to_internal('BIGINT'::regtype);\n interval_to_internal \n----------------------\n              1000000\n\nSELECT test.interval_to_internal('TIMESTAMPTZ'::regtype);\n interval_to_internal \n----------------------\n         604800000000\n\nSELECT test.interval_to_internal('TIMESTAMP'::regtype);\n interval_to_internal \n----------------------\n         604800000000\n\nSELECT test.interval_to_internal('DATE'::regtype);\n interval_to_internal \n----------------------\n         604800000000\n\n\\set VERBOSITY terse\n\\set ON_ERROR_STOP 0\nSELECT test.interval_to_internal('INT'::regtype, 2147483649::bigint);\nERROR:  invalid interval: must be between 1 and 2147483647\nSELECT test.interval_to_internal('SMALLINT'::regtype, 32768::bigint);\nERROR:  invalid interval: must be between 1 and 32767\nSELECT test.interval_to_internal('TEXT'::regtype, 32768::bigint);\nERROR:  invalid type for dimension \"testcol\"\nSELECT test.interval_to_internal('INT'::regtype, INTERVAL '1 day');\nERROR:  invalid interval type for integer dimension\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "test/expected/timestamp-18.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- Utility function for grouping/slotting time with a given interval.\nCREATE OR REPLACE FUNCTION date_group(\n    field           timestamp,\n    group_interval  interval\n)\n    RETURNS timestamp LANGUAGE SQL STABLE AS\n$BODY$\n    SELECT to_timestamp((EXTRACT(EPOCH from $1)::int /\n        EXTRACT(EPOCH from group_interval)::int) *\n        EXTRACT(EPOCH from group_interval)::int)::timestamp;\n$BODY$;\nCREATE TABLE PUBLIC.\"testNs\" (\n  \"timeCustom\" TIMESTAMP NOT NULL,\n  device_id TEXT NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL,\n  series_bool BOOLEAN NULL\n);\nCREATE INDEX ON PUBLIC.\"testNs\" (device_id, \"timeCustom\" DESC NULLS LAST) WHERE device_id IS NOT NULL;\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA \"testNs\" AUTHORIZATION :ROLE_DEFAULT_PERM_USER;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT * FROM create_hypertable('\"public\".\"testNs\"', 'timeCustom', 'device_id', 2, associated_schema_name=>'testNs' );\nWARNING:  column type \"timestamp without time zone\" used for \"timeCustom\" does not follow best practices\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             1 | public      | testNs     | t\n\n\\c :TEST_DBNAME\nINSERT INTO PUBLIC.\"testNs\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n('2009-11-12T01:00:00+00:00', 'dev1', 1.5, 1),\n('2009-11-12T01:00:00+00:00', 'dev1', 1.5, 2),\n('2009-11-10T23:00:02+00:00', 'dev1', 2.5, 3);\nINSERT INTO PUBLIC.\"testNs\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n('2009-11-10T23:00:00+00:00', 'dev2', 1.5, 1),\n('2009-11-10T23:00:00+00:00', 'dev2', 1.5, 2);\nSELECT * FROM PUBLIC.\"testNs\";\n        timeCustom        | device_id | series_0 | series_1 | series_2 | series_bool \n--------------------------+-----------+----------+----------+----------+-------------\n Thu Nov 12 01:00:00 2009 | dev1      |      1.5 |        1 |          | \n Thu Nov 12 01:00:00 2009 | dev1      |      1.5 |        2 |          | \n Tue Nov 10 23:00:02 2009 | dev1      |      2.5 |        3 |          | \n Tue Nov 10 23:00:00 2009 | dev2      |      1.5 |        1 |          | \n Tue Nov 10 23:00:00 2009 | dev2      |      1.5 |        2 |          | \n\nSET client_min_messages = WARNING;\n\\echo 'The next 2 queries will differ in output between UTC and EST since the mod is on the 100th hour UTC'\nThe next 2 queries will differ in output between UTC and EST since the mod is on the 100th hour UTC\nSET timezone = 'UTC';\nSELECT date_group(\"timeCustom\", '100 days') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC;\n           time           | sum \n--------------------------+-----\n Sun Sep 13 00:00:00 2009 | 8.5\n\nSET timezone = 'EST';\nSELECT date_group(\"timeCustom\", '100 days') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC;\n           time           | sum \n--------------------------+-----\n Sat Sep 12 19:00:00 2009 | 8.5\n\n\\echo 'The rest of the queries will be the same in output between UTC and EST'\nThe rest of the queries will be the same in output between UTC and EST\nSET timezone = 'UTC';\nSELECT date_group(\"timeCustom\", '1 day') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC;\n           time           | sum \n--------------------------+-----\n Tue Nov 10 00:00:00 2009 | 5.5\n Thu Nov 12 00:00:00 2009 |   3\n\nSET timezone = 'EST';\nSELECT date_group(\"timeCustom\", '1 day') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC;\n           time           | sum \n--------------------------+-----\n Mon Nov 09 19:00:00 2009 | 5.5\n Wed Nov 11 19:00:00 2009 |   3\n\nSET timezone = 'UTC';\nSELECT *\nFROM PUBLIC.\"testNs\"\nWHERE \"timeCustom\" >= TIMESTAMP '2009-11-10T23:00:00'\nAND \"timeCustom\" < TIMESTAMP '2009-11-12T01:00:00' ORDER BY \"timeCustom\" DESC, device_id, series_1;\n        timeCustom        | device_id | series_0 | series_1 | series_2 | series_bool \n--------------------------+-----------+----------+----------+----------+-------------\n Tue Nov 10 23:00:02 2009 | dev1      |      2.5 |        3 |          | \n Tue Nov 10 23:00:00 2009 | dev2      |      1.5 |        1 |          | \n Tue Nov 10 23:00:00 2009 | dev2      |      1.5 |        2 |          | \n\nSET timezone = 'EST';\nSELECT *\nFROM PUBLIC.\"testNs\"\nWHERE \"timeCustom\" >= TIMESTAMP '2009-11-10T23:00:00'\nAND \"timeCustom\" < TIMESTAMP '2009-11-12T01:00:00' ORDER BY \"timeCustom\" DESC, device_id, series_1;\n        timeCustom        | device_id | series_0 | series_1 | series_2 | series_bool \n--------------------------+-----------+----------+----------+----------+-------------\n Tue Nov 10 23:00:02 2009 | dev1      |      2.5 |        3 |          | \n Tue Nov 10 23:00:00 2009 | dev2      |      1.5 |        1 |          | \n Tue Nov 10 23:00:00 2009 | dev2      |      1.5 |        2 |          | \n\nSET timezone = 'UTC';\nSELECT date_group(\"timeCustom\", '1 day') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC LIMIT 2;\n           time           | sum \n--------------------------+-----\n Tue Nov 10 00:00:00 2009 | 5.5\n Thu Nov 12 00:00:00 2009 |   3\n\nSET timezone = 'EST';\nSELECT date_group(\"timeCustom\", '1 day') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC LIMIT 2;\n           time           | sum \n--------------------------+-----\n Mon Nov 09 19:00:00 2009 | 5.5\n Wed Nov 11 19:00:00 2009 |   3\n\n------------------------------------\n-- Test time conversion functions --\n------------------------------------\n\\set ON_ERROR_STOP 0\nSET timezone = 'UTC';\n-- Conversion to timestamp using Postgres built-in function taking\n-- double. Gives inaccurate result on Postgres <= 9.6.2. Accurate on\n-- Postgres >= 9.6.3.\nSELECT to_timestamp(1486480176.236538);\n            to_timestamp             \n-------------------------------------\n Tue Feb 07 15:09:36.236538 2017 UTC\n\n-- extension-specific version taking microsecond UNIX timestamp\nSELECT _timescaledb_functions.to_timestamp(1486480176236538);\n            to_timestamp             \n-------------------------------------\n Tue Feb 07 15:09:36.236538 2017 UTC\n\n-- Should be the inverse of the statement above.\nSELECT _timescaledb_functions.to_unix_microseconds('2017-02-07 15:09:36.236538+00');\n to_unix_microseconds \n----------------------\n     1486480176236538\n\n-- For timestamps, BIGINT MAX represents +Infinity and BIGINT MIN\n-- -Infinity. We keep this notion for UNIX epoch time:\nSELECT _timescaledb_functions.to_unix_microseconds('+infinity');\n to_unix_microseconds \n----------------------\n  9223372036854775807\n\nSELECT _timescaledb_functions.to_timestamp(9223372036854775807);\n to_timestamp \n--------------\n infinity\n\nSELECT _timescaledb_functions.to_unix_microseconds('-infinity');\n to_unix_microseconds \n----------------------\n -9223372036854775808\n\nSELECT _timescaledb_functions.to_timestamp(-9223372036854775808);\n to_timestamp \n--------------\n -infinity\n\n-- In UNIX microseconds, the largest bigint value below infinity\n-- (BIGINT MAX) is smaller than internal date upper bound and should\n-- therefore be OK. Further, converting to the internal postgres epoch\n-- cannot overflow a 64-bit INTEGER since the postgres epoch is at a\n-- later date compared to the UNIX epoch, and is therefore represented\n-- by a smaller number\nSELECT _timescaledb_functions.to_timestamp(9223372036854775806);\n             to_timestamp              \n---------------------------------------\n Sun Jan 10 04:00:54.775806 294247 UTC\n\n-- Julian day zero is -210866803200000000 microseconds from UNIX epoch\nSELECT _timescaledb_functions.to_timestamp(-210866803200000000);\n          to_timestamp           \n---------------------------------\n Mon Nov 24 00:00:00 4714 UTC BC\n\n\\set VERBOSITY default\n-- Going beyond Julian day zero should give out-of-range error\nSELECT _timescaledb_functions.to_timestamp(-210866803200000001);\nERROR:  timestamp out of range\n-- Lower bound on date (should return the Julian day zero UNIX timestamp above)\nSELECT _timescaledb_functions.to_unix_microseconds('4714-11-24 00:00:00+00 BC');\n to_unix_microseconds \n----------------------\n  -210866803200000000\n\n-- Going beyond lower bound on date should return out-of-range\nSELECT _timescaledb_functions.to_unix_microseconds('4714-11-23 23:59:59.999999+00 BC');\nERROR:  timestamp out of range: \"4714-11-23 23:59:59.999999+00 BC\"\nLINE 1: ...ELECT _timescaledb_functions.to_unix_microseconds('4714-11-2...\n                                                             ^\n-- The upper bound for Postgres TIMESTAMPTZ\nSELECT timestamp '294276-12-31 23:59:59.999999+00';\n             timestamp             \n-----------------------------------\n Sun Dec 31 23:59:59.999999 294276\n\n-- Going beyond the upper bound, should fail\nSELECT timestamp '294276-12-31 23:59:59.999999+00' + interval '1 us';\nERROR:  timestamp out of range\n-- Cannot represent the upper bound timestamp with a UNIX microsecond timestamp\n-- since the Postgres epoch is at a later date than the UNIX epoch.\nSELECT _timescaledb_functions.to_unix_microseconds('294276-12-31 23:59:59.999999+00');\nERROR:  timestamp out of range\n-- Subtracting the difference between the two epochs (10957 days) should bring\n-- us within range.\nSELECT timestamp '294276-12-31 23:59:59.999999+00' - interval '10957 days';\n             ?column?              \n-----------------------------------\n Fri Jan 01 23:59:59.999999 294247\n\nSELECT _timescaledb_functions.to_unix_microseconds('294247-01-01 23:59:59.999999');\n to_unix_microseconds \n----------------------\n  9223371331199999999\n\n-- Adding one microsecond should take us out-of-range again\nSELECT timestamp '294247-01-01 23:59:59.999999' + interval '1 us';\n          ?column?          \n----------------------------\n Sat Jan 02 00:00:00 294247\n\nSELECT _timescaledb_functions.to_unix_microseconds(timestamp '294247-01-01 23:59:59.999999' + interval '1 us');\nERROR:  timestamp out of range\n--no time_bucketing of dates not by integer # of days\nSELECT time_bucket('1 hour', DATE '2012-01-01');\nERROR:  interval must not have sub-day precision\nSELECT time_bucket('25 hour', DATE '2012-01-01');\nERROR:  interval must be a multiple of a day\n\\set ON_ERROR_STOP 1\nSELECT time_bucket(INTERVAL '1 day', TIMESTAMP '2011-01-02 01:01:01');\n       time_bucket        \n--------------------------\n Sun Jan 02 00:00:00 2011\n\nSELECT time, time_bucket(INTERVAL '2 day ', time)\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-01 01:01:01',\n    TIMESTAMP '2011-01-02 01:01:01',\n    TIMESTAMP '2011-01-03 01:01:01',\n    TIMESTAMP '2011-01-04 01:01:01'\n    ]) AS time;\n           time           |       time_bucket        \n--------------------------+--------------------------\n Sat Jan 01 01:01:01 2011 | Sat Jan 01 00:00:00 2011\n Sun Jan 02 01:01:01 2011 | Sat Jan 01 00:00:00 2011\n Mon Jan 03 01:01:01 2011 | Mon Jan 03 00:00:00 2011\n Tue Jan 04 01:01:01 2011 | Mon Jan 03 00:00:00 2011\n\nSELECT int_def, time_bucket(int_def,TIMESTAMP '2011-01-02 01:01:01.111')\nFROM unnest(ARRAY[\n    INTERVAL '1 millisecond',\n    INTERVAL '1 second',\n    INTERVAL '1 minute',\n    INTERVAL '1 hour',\n    INTERVAL '1 day',\n    INTERVAL '2 millisecond',\n    INTERVAL '2 second',\n    INTERVAL '2 minute',\n    INTERVAL '2 hour',\n    INTERVAL '2 day'\n    ]) AS int_def;\n   int_def    |         time_bucket          \n--------------+------------------------------\n @ 0.001 secs | Sun Jan 02 01:01:01.111 2011\n @ 1 sec      | Sun Jan 02 01:01:01 2011\n @ 1 min      | Sun Jan 02 01:01:00 2011\n @ 1 hour     | Sun Jan 02 01:00:00 2011\n @ 1 day      | Sun Jan 02 00:00:00 2011\n @ 0.002 secs | Sun Jan 02 01:01:01.11 2011\n @ 2 secs     | Sun Jan 02 01:01:00 2011\n @ 2 mins     | Sun Jan 02 01:00:00 2011\n @ 2 hours    | Sun Jan 02 00:00:00 2011\n @ 2 days     | Sat Jan 01 00:00:00 2011\n\n\\set ON_ERROR_STOP 0\nSELECT time_bucket(INTERVAL '1 year 1d',TIMESTAMP '2011-01-02 01:01:01.111');\nERROR:  month intervals cannot have day or time component\nSELECT time_bucket(INTERVAL '1 month 1 minute',TIMESTAMP '2011-01-02 01:01:01.111');\nERROR:  month intervals cannot have day or time component\n\\set ON_ERROR_STOP 1\nSELECT time, time_bucket(INTERVAL '5 minute', time)\nFROM unnest(ARRAY[\n    TIMESTAMP '1970-01-01 00:59:59.999999',\n    TIMESTAMP '1970-01-01 01:01:00',\n    TIMESTAMP '1970-01-01 01:04:59.999999',\n    TIMESTAMP '1970-01-01 01:05:00'\n    ]) AS time;\n              time               |       time_bucket        \n---------------------------------+--------------------------\n Thu Jan 01 00:59:59.999999 1970 | Thu Jan 01 00:55:00 1970\n Thu Jan 01 01:01:00 1970        | Thu Jan 01 01:00:00 1970\n Thu Jan 01 01:04:59.999999 1970 | Thu Jan 01 01:00:00 1970\n Thu Jan 01 01:05:00 1970        | Thu Jan 01 01:05:00 1970\n\nSELECT time, time_bucket(INTERVAL '5 minute', time)\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-02 01:04:59.999999',\n    TIMESTAMP '2011-01-02 01:05:00',\n    TIMESTAMP '2011-01-02 01:09:59.999999',\n    TIMESTAMP '2011-01-02 01:10:00'\n    ]) AS time;\n              time               |       time_bucket        \n---------------------------------+--------------------------\n Sun Jan 02 01:04:59.999999 2011 | Sun Jan 02 01:00:00 2011\n Sun Jan 02 01:05:00 2011        | Sun Jan 02 01:05:00 2011\n Sun Jan 02 01:09:59.999999 2011 | Sun Jan 02 01:05:00 2011\n Sun Jan 02 01:10:00 2011        | Sun Jan 02 01:10:00 2011\n\n--offset with interval\nSELECT time, time_bucket(INTERVAL '5 minute', time ,  INTERVAL '2 minutes')\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-02 01:01:59.999999',\n    TIMESTAMP '2011-01-02 01:02:00',\n    TIMESTAMP '2011-01-02 01:06:59.999999',\n    TIMESTAMP '2011-01-02 01:07:00'\n    ]) AS time;\n              time               |       time_bucket        \n---------------------------------+--------------------------\n Sun Jan 02 01:01:59.999999 2011 | Sun Jan 02 00:57:00 2011\n Sun Jan 02 01:02:00 2011        | Sun Jan 02 01:02:00 2011\n Sun Jan 02 01:06:59.999999 2011 | Sun Jan 02 01:02:00 2011\n Sun Jan 02 01:07:00 2011        | Sun Jan 02 01:07:00 2011\n\nSELECT time, time_bucket(INTERVAL '5 minute', time , - INTERVAL '2 minutes')\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-02 01:02:59.999999',\n    TIMESTAMP '2011-01-02 01:03:00',\n    TIMESTAMP '2011-01-02 01:07:59.999999',\n    TIMESTAMP '2011-01-02 01:08:00'\n    ]) AS time;\n              time               |       time_bucket        \n---------------------------------+--------------------------\n Sun Jan 02 01:02:59.999999 2011 | Sun Jan 02 00:58:00 2011\n Sun Jan 02 01:03:00 2011        | Sun Jan 02 01:03:00 2011\n Sun Jan 02 01:07:59.999999 2011 | Sun Jan 02 01:03:00 2011\n Sun Jan 02 01:08:00 2011        | Sun Jan 02 01:08:00 2011\n\n--offset with infinity\n-- timestamp\nSELECT time, time_bucket(INTERVAL '1 week', time, INTERVAL '1 day')\nFROM unnest(ARRAY[\n    timestamp '-Infinity',\n    timestamp 'Infinity'\n    ]) AS time;\n   time    | time_bucket \n-----------+-------------\n -infinity | -infinity\n infinity  | infinity\n\n-- timestamptz\nSELECT time, time_bucket(INTERVAL '1 week', time, INTERVAL '1 day')\nFROM unnest(ARRAY[\n    timestamp with time zone '-Infinity',\n    timestamp with time zone 'Infinity'\n    ]) AS time;\n   time    | time_bucket \n-----------+-------------\n -infinity | -infinity\n infinity  | infinity\n\n-- Date\nSELECT date, time_bucket(INTERVAL '1 week', date, INTERVAL '1 day')\nFROM unnest(ARRAY[\n    date '-Infinity',\n    date 'Infinity'\n    ]) AS date;\n   date    | time_bucket \n-----------+-------------\n -infinity | -infinity\n infinity  | infinity\n\n--example to align with an origin\nSELECT time, time_bucket(INTERVAL '5 minute', time - (TIMESTAMP '2011-01-02 00:02:00' - TIMESTAMP 'epoch')) +  (TIMESTAMP '2011-01-02 00:02:00'-TIMESTAMP 'epoch')\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-02 01:01:59.999999',\n    TIMESTAMP '2011-01-02 01:02:00',\n    TIMESTAMP '2011-01-02 01:06:59.999999',\n    TIMESTAMP '2011-01-02 01:07:00'\n    ]) AS time;\n              time               |         ?column?         \n---------------------------------+--------------------------\n Sun Jan 02 01:01:59.999999 2011 | Sun Jan 02 00:57:00 2011\n Sun Jan 02 01:02:00 2011        | Sun Jan 02 01:02:00 2011\n Sun Jan 02 01:06:59.999999 2011 | Sun Jan 02 01:02:00 2011\n Sun Jan 02 01:07:00 2011        | Sun Jan 02 01:07:00 2011\n\n--rounding version\nSELECT time, time_bucket(INTERVAL '5 minute', time , - INTERVAL '2.5 minutes') + INTERVAL '2 minutes 30 seconds'\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-02 01:05:01',\n    TIMESTAMP '2011-01-02 01:07:29',\n    TIMESTAMP '2011-01-02 01:02:30',\n    TIMESTAMP '2011-01-02 01:07:30',\n    TIMESTAMP '2011-01-02 01:02:29'\n    ]) AS time;\n           time           |         ?column?         \n--------------------------+--------------------------\n Sun Jan 02 01:05:01 2011 | Sun Jan 02 01:05:00 2011\n Sun Jan 02 01:07:29 2011 | Sun Jan 02 01:05:00 2011\n Sun Jan 02 01:02:30 2011 | Sun Jan 02 01:05:00 2011\n Sun Jan 02 01:07:30 2011 | Sun Jan 02 01:10:00 2011\n Sun Jan 02 01:02:29 2011 | Sun Jan 02 01:00:00 2011\n\n--time_bucket with timezone should mimick date_trunc\nSET timezone TO 'UTC';\nSELECT time, time_bucket(INTERVAL '1 hour', time), date_trunc('hour', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+02'\n    ]) AS time;\n             time             |         time_bucket          |          date_trunc          \n------------------------------+------------------------------+------------------------------\n Sun Jan 02 01:01:01 2011 UTC | Sun Jan 02 01:00:00 2011 UTC | Sun Jan 02 01:00:00 2011 UTC\n Sun Jan 02 00:01:01 2011 UTC | Sun Jan 02 00:00:00 2011 UTC | Sun Jan 02 00:00:00 2011 UTC\n Sat Jan 01 23:01:01 2011 UTC | Sat Jan 01 23:00:00 2011 UTC | Sat Jan 01 23:00:00 2011 UTC\n\nSELECT time, time_bucket(INTERVAL '1 day', time), date_trunc('day', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+02'\n    ]) AS time;\n             time             |         time_bucket          |          date_trunc          \n------------------------------+------------------------------+------------------------------\n Sun Jan 02 01:01:01 2011 UTC | Sun Jan 02 00:00:00 2011 UTC | Sun Jan 02 00:00:00 2011 UTC\n Sun Jan 02 00:01:01 2011 UTC | Sun Jan 02 00:00:00 2011 UTC | Sun Jan 02 00:00:00 2011 UTC\n Sat Jan 01 23:01:01 2011 UTC | Sat Jan 01 00:00:00 2011 UTC | Sat Jan 01 00:00:00 2011 UTC\n\n--what happens with a local tz\nSET timezone TO 'America/New_York';\nSELECT time, time_bucket(INTERVAL '1 hour', time), date_trunc('hour', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+02'\n    ]) AS time;\n             time             |         time_bucket          |          date_trunc          \n------------------------------+------------------------------+------------------------------\n Sun Jan 02 01:01:01 2011 EST | Sun Jan 02 01:00:00 2011 EST | Sun Jan 02 01:00:00 2011 EST\n Sat Jan 01 19:01:01 2011 EST | Sat Jan 01 19:00:00 2011 EST | Sat Jan 01 19:00:00 2011 EST\n Sat Jan 01 18:01:01 2011 EST | Sat Jan 01 18:00:00 2011 EST | Sat Jan 01 18:00:00 2011 EST\n\n--Note the timestamp tz input is aligned with UTC day /not/ local day. different than date_trunc.\nSELECT time, time_bucket(INTERVAL '1 day', time), date_trunc('day', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-03 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-04 01:01:01+02'\n    ]) AS time;\n             time             |         time_bucket          |          date_trunc          \n------------------------------+------------------------------+------------------------------\n Sun Jan 02 01:01:01 2011 EST | Sat Jan 01 19:00:00 2011 EST | Sun Jan 02 00:00:00 2011 EST\n Sun Jan 02 19:01:01 2011 EST | Sun Jan 02 19:00:00 2011 EST | Sun Jan 02 00:00:00 2011 EST\n Mon Jan 03 18:01:01 2011 EST | Sun Jan 02 19:00:00 2011 EST | Mon Jan 03 00:00:00 2011 EST\n\n--can force local bucketing with simple cast.\nSELECT time, time_bucket(INTERVAL '1 day', time::timestamp), date_trunc('day', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-03 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-04 01:01:01+02'\n    ]) AS time;\n             time             |       time_bucket        |          date_trunc          \n------------------------------+--------------------------+------------------------------\n Sun Jan 02 01:01:01 2011 EST | Sun Jan 02 00:00:00 2011 | Sun Jan 02 00:00:00 2011 EST\n Sun Jan 02 19:01:01 2011 EST | Sun Jan 02 00:00:00 2011 | Sun Jan 02 00:00:00 2011 EST\n Mon Jan 03 18:01:01 2011 EST | Mon Jan 03 00:00:00 2011 | Mon Jan 03 00:00:00 2011 EST\n\n--can also use interval to correct\nSELECT time, time_bucket(INTERVAL '1 day', time, -INTERVAL '19 hours'), date_trunc('day', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-03 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-04 01:01:01+02'\n    ]) AS time;\n             time             |         time_bucket          |          date_trunc          \n------------------------------+------------------------------+------------------------------\n Sun Jan 02 01:01:01 2011 EST | Sun Jan 02 00:00:00 2011 EST | Sun Jan 02 00:00:00 2011 EST\n Sun Jan 02 19:01:01 2011 EST | Sun Jan 02 00:00:00 2011 EST | Sun Jan 02 00:00:00 2011 EST\n Mon Jan 03 18:01:01 2011 EST | Mon Jan 03 00:00:00 2011 EST | Mon Jan 03 00:00:00 2011 EST\n\n--dst: same local hour bucketed as two different hours.\nSELECT time, time_bucket(INTERVAL '1 hour', time), date_trunc('hour', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2017-11-05 12:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 13:05:00+07'\n    ]) AS time;\n             time             |         time_bucket          |          date_trunc          \n------------------------------+------------------------------+------------------------------\n Sun Nov 05 01:05:00 2017 EDT | Sun Nov 05 01:00:00 2017 EDT | Sun Nov 05 01:00:00 2017 EDT\n Sun Nov 05 01:05:00 2017 EST | Sun Nov 05 01:00:00 2017 EST | Sun Nov 05 01:00:00 2017 EST\n\n--local alignment changes when bucketing by UTC across dst boundary\nSELECT time, time_bucket(INTERVAL '2 hour', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2017-11-05 10:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 12:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 13:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 15:05:00+07'\n    ]) AS time;\n             time             |         time_bucket          \n------------------------------+------------------------------\n Sat Nov 04 23:05:00 2017 EDT | Sat Nov 04 22:00:00 2017 EDT\n Sun Nov 05 01:05:00 2017 EDT | Sun Nov 05 00:00:00 2017 EDT\n Sun Nov 05 01:05:00 2017 EST | Sun Nov 05 01:00:00 2017 EST\n Sun Nov 05 03:05:00 2017 EST | Sun Nov 05 03:00:00 2017 EST\n\n--local alignment is preserved when bucketing by local time across DST boundary.\nSELECT time, time_bucket(INTERVAL '2 hour', time::timestamp)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2017-11-05 10:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 12:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 13:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 15:05:00+07'\n    ]) AS time;\n             time             |       time_bucket        \n------------------------------+--------------------------\n Sat Nov 04 23:05:00 2017 EDT | Sat Nov 04 22:00:00 2017\n Sun Nov 05 01:05:00 2017 EDT | Sun Nov 05 00:00:00 2017\n Sun Nov 05 01:05:00 2017 EST | Sun Nov 05 00:00:00 2017\n Sun Nov 05 03:05:00 2017 EST | Sun Nov 05 02:00:00 2017\n\n-- GitHub issue #7059: time_bucket with timezone + offset across DST boundary\n-- Asia/Amman: clocks skip from 00:00 to 01:00 on 2021-03-26\n-- Input: 01:00+03 = 22:00 UTC → Result: 22:15 UTC = 01:15 local (00:00 + 15min offset)\nSELECT time_bucket('1 day', '2021-03-26 01:00:00+03'::timestamptz,\n    timezone := 'Asia/Amman', \"offset\" := '15 minutes'::interval);\n         time_bucket          \n------------------------------\n Wed Mar 24 18:15:00 2021 EDT\n\n-- GitHub issue #8851: time_bucket with negative offset during DST fall-back\n-- Europe/Berlin: clocks repeat 02:00-02:59 on 2025-10-26\n-- Input: 02:00+02 = 00:00 UTC → Result: 23:59:45 UTC = 01:59:45 local (02:00 - 15s offset)\nSELECT time_bucket('30 seconds', '2025-10-26 02:00:00+02'::timestamptz,\n    timezone := 'Europe/Berlin', \"offset\" := '-15 seconds'::interval);\n         time_bucket          \n------------------------------\n Sat Oct 25 19:59:45 2025 EDT\n\n-- Additional DST edge cases for coverage of DST direction × offset sign combinations\n-- Spring-forward + negative offset\n-- Input: 01:30+03 = 22:30 UTC → Result: 22:45 UTC = 01:45 local (01:00 + 45min = 02:00 - 15min)\nSELECT time_bucket('1 hour', '2021-03-26 01:30:00+03'::timestamptz,\n    timezone := 'Asia/Amman', \"offset\" := '-15 minutes'::interval);\n         time_bucket          \n------------------------------\n Thu Mar 25 17:45:00 2021 EDT\n\n-- Fall-back + positive offset\n-- Input: 02:30+01 = 01:30 UTC → Result: 01:15 UTC = 02:15 local (02:00 + 15min offset)\nSELECT time_bucket('1 hour', '2025-10-26 02:30:00+01'::timestamptz,\n    timezone := 'Europe/Berlin', \"offset\" := '15 minutes'::interval);\n         time_bucket          \n------------------------------\n Sat Oct 25 21:15:00 2025 EDT\n\n-- Input exactly at DST spring-forward transition\n-- Input: 22:00 UTC = 00:00 local (the moment clocks jump to 01:00)\n-- Result: 22:15 UTC = 01:15 local (01:00 + 15min offset)\nSELECT time_bucket('1 hour', '2021-03-25 22:00:00+00'::timestamptz,\n    timezone := 'Asia/Amman', \"offset\" := '15 minutes'::interval);\n         time_bucket          \n------------------------------\n Thu Mar 25 17:15:00 2021 EDT\n\n-- Input exactly at DST fall-back transition\n-- Input: 01:00 UTC = 03:00 CEST (the moment clocks go back to 02:00 CET)\n-- Result: 23:15 UTC = 01:15 local (01:00 + 15min offset, but in CET now)\nSELECT time_bucket('1 hour', '2025-10-26 01:00:00+00'::timestamptz,\n    timezone := 'Europe/Berlin', \"offset\" := '15 minutes'::interval);\n         time_bucket          \n------------------------------\n Sat Oct 25 20:15:00 2025 EDT\n\n-- Offset larger than bucket size (1h offset with 30min bucket)\n-- Input: 01:30+03 = 22:30 UTC → Result: 22:30 UTC = 01:30 local (01:00 + 30min = 00:30 + 1h)\nSELECT time_bucket('30 minutes', '2021-03-26 01:30:00+03'::timestamptz,\n    timezone := 'Asia/Amman', \"offset\" := '1 hour'::interval);\n         time_bucket          \n------------------------------\n Thu Mar 25 18:30:00 2021 EDT\n\n-- GitHub issue #9136: time_bucket with origin during DST fall-back\n-- When origin is in standard time but timestamp is in daylight time,\n-- the bucket could incorrectly start AFTER the timestamp.\n-- America/New_York: clocks go back at 02:00 EDT on 2024-11-03\n-- Input: 01:30-04 (EDT) = 05:30 UTC; origin in EST\n-- Result should have bucket start <= timestamp (bucket in EDT, not EST)\nSELECT time_bucket('1 hour', '2024-11-03 01:30:00-04'::timestamptz,\n    'America/New_York', '2000-01-01 00:00:00 America/New_York'::timestamptz) as bucket,\n    '2024-11-03 01:30:00-04'::timestamptz < time_bucket('1 hour',\n        '2024-11-03 01:30:00-04'::timestamptz, 'America/New_York',\n        '2000-01-01 00:00:00 America/New_York'::timestamptz) as ts_before_bucket;\n            bucket            | ts_before_bucket \n------------------------------+------------------\n Sun Nov 03 01:00:00 2024 EDT | f\n\nSELECT time,\n    time_bucket(10::smallint, time) AS time_bucket_smallint,\n    time_bucket(10::int, time) AS time_bucket_int,\n    time_bucket(10::bigint, time) AS time_bucket_bigint\nFROM unnest(ARRAY[\n     '-11',\n     '-10',\n      '-9',\n      '-1',\n       '0',\n       '1',\n      '99',\n     '100',\n     '109',\n     '110'\n    ]::smallint[]) AS time;\n time | time_bucket_smallint | time_bucket_int | time_bucket_bigint \n------+----------------------+-----------------+--------------------\n  -11 |                  -20 |             -20 |                -20\n  -10 |                  -10 |             -10 |                -10\n   -9 |                  -10 |             -10 |                -10\n   -1 |                  -10 |             -10 |                -10\n    0 |                    0 |               0 |                  0\n    1 |                    0 |               0 |                  0\n   99 |                   90 |              90 |                 90\n  100 |                  100 |             100 |                100\n  109 |                  100 |             100 |                100\n  110 |                  110 |             110 |                110\n\nSELECT time,\n    time_bucket(10::smallint, time, 2::smallint) AS time_bucket_smallint,\n    time_bucket(10::int, time, 2::int) AS time_bucket_int,\n    time_bucket(10::bigint, time, 2::bigint) AS time_bucket_bigint\nFROM unnest(ARRAY[\n      '-9',\n      '-8',\n      '-7',\n       '1',\n       '2',\n       '3',\n     '101',\n     '102',\n     '111',\n     '112'\n    ]::smallint[]) AS time;\n time | time_bucket_smallint | time_bucket_int | time_bucket_bigint \n------+----------------------+-----------------+--------------------\n   -9 |                  -18 |             -18 |                -18\n   -8 |                   -8 |              -8 |                 -8\n   -7 |                   -8 |              -8 |                 -8\n    1 |                   -8 |              -8 |                 -8\n    2 |                    2 |               2 |                  2\n    3 |                    2 |               2 |                  2\n  101 |                   92 |              92 |                 92\n  102 |                  102 |             102 |                102\n  111 |                  102 |             102 |                102\n  112 |                  112 |             112 |                112\n\nSELECT time,\n    time_bucket(10::smallint, time, -2::smallint) AS time_bucket_smallint,\n    time_bucket(10::int, time, -2::int) AS time_bucket_int,\n    time_bucket(10::bigint, time, -2::bigint) AS time_bucket_bigint\nFROM unnest(ARRAY[\n    '-13',\n    '-12',\n    '-11',\n     '-3',\n     '-2',\n     '-1',\n     '97',\n     '98',\n    '107',\n    '108'\n    ]::smallint[]) AS time;\n time | time_bucket_smallint | time_bucket_int | time_bucket_bigint \n------+----------------------+-----------------+--------------------\n  -13 |                  -22 |             -22 |                -22\n  -12 |                  -12 |             -12 |                -12\n  -11 |                  -12 |             -12 |                -12\n   -3 |                  -12 |             -12 |                -12\n   -2 |                   -2 |              -2 |                 -2\n   -1 |                   -2 |              -2 |                 -2\n   97 |                   88 |              88 |                 88\n   98 |                   98 |              98 |                 98\n  107 |                   98 |              98 |                 98\n  108 |                  108 |             108 |                108\n\n\\set ON_ERROR_STOP 0\nSELECT time_bucket(10::smallint, '-32768'::smallint);\nERROR:  timestamp out of range\nSELECT time_bucket(10::smallint, '-32761'::smallint);\nERROR:  timestamp out of range\nselect time_bucket(10::smallint, '-32768'::smallint, 1000::smallint);\nERROR:  timestamp out of range\nselect time_bucket(10::smallint, '-32768'::smallint, '32767'::smallint);\nERROR:  timestamp out of range\nselect time_bucket(10::smallint, '32767'::smallint, '-32768'::smallint);\nERROR:  timestamp out of range\n\\set ON_ERROR_STOP 1\nSELECT time, time_bucket(10::smallint, time)\nFROM unnest(ARRAY[\n    '-32760',\n    '-32759',\n    '32767'\n    ]::smallint[]) AS time;\n  time  | time_bucket \n--------+-------------\n -32760 |      -32760\n -32759 |      -32760\n  32767 |       32760\n\n\\set ON_ERROR_STOP 0\nSELECT time_bucket(10::int, '-2147483648'::int);\nERROR:  timestamp out of range\nSELECT time_bucket(10::int, '-2147483641'::int);\nERROR:  timestamp out of range\nSELECT time_bucket(1000::int, '-2147483000'::int, 1::int);\nERROR:  timestamp out of range\nSELECT time_bucket(1000::int, '-2147483648'::int, '2147483647'::int);\nERROR:  timestamp out of range\nSELECT time_bucket(1000::int, '2147483647'::int, '-2147483648'::int);\nERROR:  timestamp out of range\n\\set ON_ERROR_STOP 1\nSELECT time, time_bucket(10::int, time)\nFROM unnest(ARRAY[\n    '-2147483640',\n    '-2147483639',\n    '2147483647'\n    ]::int[]) AS time;\n    time     | time_bucket \n-------------+-------------\n -2147483640 | -2147483640\n -2147483639 | -2147483640\n  2147483647 |  2147483640\n\n\\set ON_ERROR_STOP 0\nSELECT time_bucket(10::bigint, '-9223372036854775808'::bigint);\nERROR:  timestamp out of range\nSELECT time_bucket(10::bigint, '-9223372036854775801'::bigint);\nERROR:  timestamp out of range\nSELECT time_bucket(1000::bigint, '-9223372036854775000'::bigint, 1::bigint);\nERROR:  timestamp out of range\nSELECT time_bucket(1000::bigint, '-9223372036854775808'::bigint, '9223372036854775807'::bigint);\nERROR:  timestamp out of range\nSELECT time_bucket(1000::bigint, '9223372036854775807'::bigint, '-9223372036854775808'::bigint);\nERROR:  timestamp out of range\n\\set ON_ERROR_STOP 1\nSELECT time, time_bucket(10::bigint, time)\nFROM unnest(ARRAY[\n    '-9223372036854775800',\n    '-9223372036854775799',\n    '9223372036854775807'\n    ]::bigint[]) AS time;\n         time         |     time_bucket      \n----------------------+----------------------\n -9223372036854775800 | -9223372036854775800\n -9223372036854775799 | -9223372036854775800\n  9223372036854775807 |  9223372036854775800\n\nSELECT time, time_bucket(INTERVAL '1 day', time::date)\nFROM unnest(ARRAY[\n    date '2017-11-05',\n    date '2017-11-06'\n    ]) AS time;\n    time    | time_bucket \n------------+-------------\n 11-05-2017 | 11-05-2017\n 11-06-2017 | 11-06-2017\n\nSELECT time, time_bucket(INTERVAL '4 day', time::date)\nFROM unnest(ARRAY[\n    date '2017-11-04',\n    date '2017-11-05',\n    date '2017-11-08',\n    date '2017-11-09'\n    ]) AS time;\n    time    | time_bucket \n------------+-------------\n 11-04-2017 | 11-01-2017\n 11-05-2017 | 11-05-2017\n 11-08-2017 | 11-05-2017\n 11-09-2017 | 11-09-2017\n\nSELECT time, time_bucket(INTERVAL '4 day', time::date, INTERVAL '2 day')\nFROM unnest(ARRAY[\n    date '2017-11-06',\n    date '2017-11-07',\n    date '2017-11-10',\n    date '2017-11-11'\n    ]) AS time;\n    time    | time_bucket \n------------+-------------\n 11-06-2017 | 11-03-2017\n 11-07-2017 | 11-07-2017\n 11-10-2017 | 11-07-2017\n 11-11-2017 | 11-11-2017\n\n-- 2019-09-24 is a Monday, and we want to ensure that time_bucket returns the week starting with a Monday as date_trunc does,\n-- Rather than a Saturday which is the date of the PostgreSQL epoch\nSELECT time, time_bucket(INTERVAL '1 week', time::date)\nFROM unnest(ARRAY[\n    date '2018-09-16',\n    date '2018-09-17',\n    date '2018-09-23',\n    date '2018-09-24'\n    ]) AS time;\n    time    | time_bucket \n------------+-------------\n 09-16-2018 | 09-10-2018\n 09-17-2018 | 09-17-2018\n 09-23-2018 | 09-17-2018\n 09-24-2018 | 09-24-2018\n\nSELECT time, time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp without time zone '2018-09-16',\n    timestamp without time zone '2018-09-17',\n    timestamp without time zone '2018-09-23',\n    timestamp without time zone '2018-09-24'\n    ]) AS time;\n           time           |       time_bucket        \n--------------------------+--------------------------\n Sun Sep 16 00:00:00 2018 | Mon Sep 10 00:00:00 2018\n Mon Sep 17 00:00:00 2018 | Mon Sep 17 00:00:00 2018\n Sun Sep 23 00:00:00 2018 | Mon Sep 17 00:00:00 2018\n Mon Sep 24 00:00:00 2018 | Mon Sep 24 00:00:00 2018\n\nSELECT time, time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp with time zone '2018-09-16',\n    timestamp with time zone '2018-09-17',\n    timestamp with time zone '2018-09-23',\n    timestamp with time zone '2018-09-24'\n    ]) AS time;\n             time             |         time_bucket          \n------------------------------+------------------------------\n Sun Sep 16 00:00:00 2018 EDT | Sun Sep 09 20:00:00 2018 EDT\n Mon Sep 17 00:00:00 2018 EDT | Sun Sep 16 20:00:00 2018 EDT\n Sun Sep 23 00:00:00 2018 EDT | Sun Sep 16 20:00:00 2018 EDT\n Mon Sep 24 00:00:00 2018 EDT | Sun Sep 23 20:00:00 2018 EDT\n\nSELECT time, time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp with time zone '-Infinity',\n    timestamp with time zone 'Infinity'\n    ]) AS time;\n   time    | time_bucket \n-----------+-------------\n -infinity | -infinity\n infinity  | infinity\n\nSELECT time, time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp without time zone '-Infinity',\n    timestamp without time zone 'Infinity'\n    ]) AS time;\n   time    | time_bucket \n-----------+-------------\n -infinity | -infinity\n infinity  | infinity\n\nSELECT time, time_bucket(INTERVAL '1 week', time), date_trunc('week', time) = time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp without time zone '4714-11-24 01:01:01.0 BC',\n    timestamp without time zone '294276-12-31 23:59:59.9999'\n    ]) AS time;\n              time               |         time_bucket         | ?column? \n---------------------------------+-----------------------------+----------\n Mon Nov 24 01:01:01 4714 BC     | Mon Nov 24 00:00:00 4714 BC | t\n Sun Dec 31 23:59:59.9999 294276 | Mon Dec 25 00:00:00 294276  | t\n\n--1000 years later weeks still align.\nSELECT time, time_bucket(INTERVAL '1 week', time), date_trunc('week', time) = time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp without time zone '3018-09-14',\n    timestamp without time zone '3018-09-20',\n    timestamp without time zone '3018-09-21',\n    timestamp without time zone '3018-09-22'\n    ]) AS time;\n           time           |       time_bucket        | ?column? \n--------------------------+--------------------------+----------\n Mon Sep 14 00:00:00 3018 | Mon Sep 14 00:00:00 3018 | t\n Sun Sep 20 00:00:00 3018 | Mon Sep 14 00:00:00 3018 | t\n Mon Sep 21 00:00:00 3018 | Mon Sep 21 00:00:00 3018 | t\n Tue Sep 22 00:00:00 3018 | Mon Sep 21 00:00:00 3018 | t\n\n--weeks align for timestamptz as well if cast to local time, (but not if done at UTC).\nSELECT time, date_trunc('week', time) = time_bucket(INTERVAL '1 week', time),  date_trunc('week', time) = time_bucket(INTERVAL '1 week', time::timestamp)\nFROM unnest(ARRAY[\n    timestamp with time zone '3018-09-14',\n    timestamp with time zone '3018-09-20',\n    timestamp with time zone '3018-09-21',\n    timestamp with time zone '3018-09-22'\n    ]) AS time;\n             time             | ?column? | ?column? \n------------------------------+----------+----------\n Mon Sep 14 00:00:00 3018 EDT | f        | t\n Sun Sep 20 00:00:00 3018 EDT | f        | t\n Mon Sep 21 00:00:00 3018 EDT | f        | t\n Tue Sep 22 00:00:00 3018 EDT | f        | t\n\n--check functions with origin\n--note that the default origin is at 0 UTC, using origin parameter it is easy to provide a EDT origin point\n\\x\nSELECT time, time_bucket(INTERVAL '1 week', time) no_epoch,\n             time_bucket(INTERVAL '1 week', time::timestamp) no_epoch_local,\n             time_bucket(INTERVAL '1 week', time) = time_bucket(INTERVAL '1 week', time, timestamptz '2000-01-03 00:00:00+0') always_true,\n             time_bucket(INTERVAL '1 week', time, timestamptz '2000-01-01 00:00:00+0') pg_epoch,\n             time_bucket(INTERVAL '1 week', time, timestamptz 'epoch') unix_epoch,\n             time_bucket(INTERVAL '1 week', time, timestamptz '3018-09-13') custom_1,\n             time_bucket(INTERVAL '1 week', time, timestamptz '3018-09-14') custom_2\nFROM unnest(ARRAY[\n    timestamp with time zone '2000-01-01 00:00:00+0'- interval '1 second',\n    timestamp with time zone '2000-01-01 00:00:00+0',\n    timestamp with time zone '2000-01-03 00:00:00+0'- interval '1 second',\n    timestamp with time zone '2000-01-03 00:00:00+0',\n    timestamp with time zone '2000-01-01',\n    timestamp with time zone '2000-01-02',\n    timestamp with time zone '2000-01-03',\n    timestamp with time zone '3018-09-12',\n    timestamp with time zone '3018-09-13',\n    timestamp with time zone '3018-09-14',\n    timestamp with time zone '3018-09-15'\n    ]) AS time;\n-[ RECORD 1 ]--+-----------------------------\ntime           | Fri Dec 31 18:59:59 1999 EST\nno_epoch       | Sun Dec 26 19:00:00 1999 EST\nno_epoch_local | Mon Dec 27 00:00:00 1999\nalways_true    | t\npg_epoch       | Fri Dec 24 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Dec 25 23:00:00 1999 EST\ncustom_2       | Sun Dec 26 23:00:00 1999 EST\n-[ RECORD 2 ]--+-----------------------------\ntime           | Fri Dec 31 19:00:00 1999 EST\nno_epoch       | Sun Dec 26 19:00:00 1999 EST\nno_epoch_local | Mon Dec 27 00:00:00 1999\nalways_true    | t\npg_epoch       | Fri Dec 31 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Dec 25 23:00:00 1999 EST\ncustom_2       | Sun Dec 26 23:00:00 1999 EST\n-[ RECORD 3 ]--+-----------------------------\ntime           | Sun Jan 02 18:59:59 2000 EST\nno_epoch       | Sun Dec 26 19:00:00 1999 EST\nno_epoch_local | Mon Dec 27 00:00:00 1999\nalways_true    | t\npg_epoch       | Fri Dec 31 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Jan 01 23:00:00 2000 EST\ncustom_2       | Sun Dec 26 23:00:00 1999 EST\n-[ RECORD 4 ]--+-----------------------------\ntime           | Sun Jan 02 19:00:00 2000 EST\nno_epoch       | Sun Jan 02 19:00:00 2000 EST\nno_epoch_local | Mon Dec 27 00:00:00 1999\nalways_true    | t\npg_epoch       | Fri Dec 31 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Jan 01 23:00:00 2000 EST\ncustom_2       | Sun Dec 26 23:00:00 1999 EST\n-[ RECORD 5 ]--+-----------------------------\ntime           | Sat Jan 01 00:00:00 2000 EST\nno_epoch       | Sun Dec 26 19:00:00 1999 EST\nno_epoch_local | Mon Dec 27 00:00:00 1999\nalways_true    | t\npg_epoch       | Fri Dec 31 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Dec 25 23:00:00 1999 EST\ncustom_2       | Sun Dec 26 23:00:00 1999 EST\n-[ RECORD 6 ]--+-----------------------------\ntime           | Sun Jan 02 00:00:00 2000 EST\nno_epoch       | Sun Dec 26 19:00:00 1999 EST\nno_epoch_local | Mon Dec 27 00:00:00 1999\nalways_true    | t\npg_epoch       | Fri Dec 31 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Jan 01 23:00:00 2000 EST\ncustom_2       | Sun Dec 26 23:00:00 1999 EST\n-[ RECORD 7 ]--+-----------------------------\ntime           | Mon Jan 03 00:00:00 2000 EST\nno_epoch       | Sun Jan 02 19:00:00 2000 EST\nno_epoch_local | Mon Jan 03 00:00:00 2000\nalways_true    | t\npg_epoch       | Fri Dec 31 19:00:00 1999 EST\nunix_epoch     | Wed Dec 29 19:00:00 1999 EST\ncustom_1       | Sat Jan 01 23:00:00 2000 EST\ncustom_2       | Sun Jan 02 23:00:00 2000 EST\n-[ RECORD 8 ]--+-----------------------------\ntime           | Sat Sep 12 00:00:00 3018 EDT\nno_epoch       | Sun Sep 06 20:00:00 3018 EDT\nno_epoch_local | Mon Sep 07 00:00:00 3018\nalways_true    | t\npg_epoch       | Fri Sep 11 20:00:00 3018 EDT\nunix_epoch     | Wed Sep 09 20:00:00 3018 EDT\ncustom_1       | Sun Sep 06 00:00:00 3018 EDT\ncustom_2       | Mon Sep 07 00:00:00 3018 EDT\n-[ RECORD 9 ]--+-----------------------------\ntime           | Sun Sep 13 00:00:00 3018 EDT\nno_epoch       | Sun Sep 06 20:00:00 3018 EDT\nno_epoch_local | Mon Sep 07 00:00:00 3018\nalways_true    | t\npg_epoch       | Fri Sep 11 20:00:00 3018 EDT\nunix_epoch     | Wed Sep 09 20:00:00 3018 EDT\ncustom_1       | Sun Sep 13 00:00:00 3018 EDT\ncustom_2       | Mon Sep 07 00:00:00 3018 EDT\n-[ RECORD 10 ]-+-----------------------------\ntime           | Mon Sep 14 00:00:00 3018 EDT\nno_epoch       | Sun Sep 13 20:00:00 3018 EDT\nno_epoch_local | Mon Sep 14 00:00:00 3018\nalways_true    | t\npg_epoch       | Fri Sep 11 20:00:00 3018 EDT\nunix_epoch     | Wed Sep 09 20:00:00 3018 EDT\ncustom_1       | Sun Sep 13 00:00:00 3018 EDT\ncustom_2       | Mon Sep 14 00:00:00 3018 EDT\n-[ RECORD 11 ]-+-----------------------------\ntime           | Tue Sep 15 00:00:00 3018 EDT\nno_epoch       | Sun Sep 13 20:00:00 3018 EDT\nno_epoch_local | Mon Sep 14 00:00:00 3018\nalways_true    | t\npg_epoch       | Fri Sep 11 20:00:00 3018 EDT\nunix_epoch     | Wed Sep 09 20:00:00 3018 EDT\ncustom_1       | Sun Sep 13 00:00:00 3018 EDT\ncustom_2       | Mon Sep 14 00:00:00 3018 EDT\n\nSELECT time, time_bucket(INTERVAL '1 week', time) no_epoch,\n             time_bucket(INTERVAL '1 week', time) = time_bucket(INTERVAL '1 week', time, timestamp '2000-01-03 00:00:00') always_true,\n             time_bucket(INTERVAL '1 week', time, timestamp '2000-01-01 00:00:00+0') pg_epoch,\n             time_bucket(INTERVAL '1 week', time, timestamp 'epoch') unix_epoch,\n             time_bucket(INTERVAL '1 week', time, timestamp '3018-09-13') custom_1,\n             time_bucket(INTERVAL '1 week', time, timestamp '3018-09-14') custom_2\nFROM unnest(ARRAY[\n    timestamp without time zone '2000-01-01 00:00:00'- interval '1 second',\n    timestamp without time zone '2000-01-01 00:00:00',\n    timestamp without time zone '2000-01-03 00:00:00'- interval '1 second',\n    timestamp without time zone '2000-01-03 00:00:00',\n    timestamp without time zone '2000-01-01',\n    timestamp without time zone '2000-01-02',\n    timestamp without time zone '2000-01-03',\n    timestamp without time zone '3018-09-12',\n    timestamp without time zone '3018-09-13',\n    timestamp without time zone '3018-09-14',\n    timestamp without time zone '3018-09-15'\n    ]) AS time;\n-[ RECORD 1 ]-------------------------\ntime        | Fri Dec 31 23:59:59 1999\nno_epoch    | Mon Dec 27 00:00:00 1999\nalways_true | t\npg_epoch    | Sat Dec 25 00:00:00 1999\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Dec 26 00:00:00 1999\ncustom_2    | Mon Dec 27 00:00:00 1999\n-[ RECORD 2 ]-------------------------\ntime        | Sat Jan 01 00:00:00 2000\nno_epoch    | Mon Dec 27 00:00:00 1999\nalways_true | t\npg_epoch    | Sat Jan 01 00:00:00 2000\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Dec 26 00:00:00 1999\ncustom_2    | Mon Dec 27 00:00:00 1999\n-[ RECORD 3 ]-------------------------\ntime        | Sun Jan 02 23:59:59 2000\nno_epoch    | Mon Dec 27 00:00:00 1999\nalways_true | t\npg_epoch    | Sat Jan 01 00:00:00 2000\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Jan 02 00:00:00 2000\ncustom_2    | Mon Dec 27 00:00:00 1999\n-[ RECORD 4 ]-------------------------\ntime        | Mon Jan 03 00:00:00 2000\nno_epoch    | Mon Jan 03 00:00:00 2000\nalways_true | t\npg_epoch    | Sat Jan 01 00:00:00 2000\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Jan 02 00:00:00 2000\ncustom_2    | Mon Jan 03 00:00:00 2000\n-[ RECORD 5 ]-------------------------\ntime        | Sat Jan 01 00:00:00 2000\nno_epoch    | Mon Dec 27 00:00:00 1999\nalways_true | t\npg_epoch    | Sat Jan 01 00:00:00 2000\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Dec 26 00:00:00 1999\ncustom_2    | Mon Dec 27 00:00:00 1999\n-[ RECORD 6 ]-------------------------\ntime        | Sun Jan 02 00:00:00 2000\nno_epoch    | Mon Dec 27 00:00:00 1999\nalways_true | t\npg_epoch    | Sat Jan 01 00:00:00 2000\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Jan 02 00:00:00 2000\ncustom_2    | Mon Dec 27 00:00:00 1999\n-[ RECORD 7 ]-------------------------\ntime        | Mon Jan 03 00:00:00 2000\nno_epoch    | Mon Jan 03 00:00:00 2000\nalways_true | t\npg_epoch    | Sat Jan 01 00:00:00 2000\nunix_epoch  | Thu Dec 30 00:00:00 1999\ncustom_1    | Sun Jan 02 00:00:00 2000\ncustom_2    | Mon Jan 03 00:00:00 2000\n-[ RECORD 8 ]-------------------------\ntime        | Sat Sep 12 00:00:00 3018\nno_epoch    | Mon Sep 07 00:00:00 3018\nalways_true | t\npg_epoch    | Sat Sep 12 00:00:00 3018\nunix_epoch  | Thu Sep 10 00:00:00 3018\ncustom_1    | Sun Sep 06 00:00:00 3018\ncustom_2    | Mon Sep 07 00:00:00 3018\n-[ RECORD 9 ]-------------------------\ntime        | Sun Sep 13 00:00:00 3018\nno_epoch    | Mon Sep 07 00:00:00 3018\nalways_true | t\npg_epoch    | Sat Sep 12 00:00:00 3018\nunix_epoch  | Thu Sep 10 00:00:00 3018\ncustom_1    | Sun Sep 13 00:00:00 3018\ncustom_2    | Mon Sep 07 00:00:00 3018\n-[ RECORD 10 ]------------------------\ntime        | Mon Sep 14 00:00:00 3018\nno_epoch    | Mon Sep 14 00:00:00 3018\nalways_true | t\npg_epoch    | Sat Sep 12 00:00:00 3018\nunix_epoch  | Thu Sep 10 00:00:00 3018\ncustom_1    | Sun Sep 13 00:00:00 3018\ncustom_2    | Mon Sep 14 00:00:00 3018\n-[ RECORD 11 ]------------------------\ntime        | Tue Sep 15 00:00:00 3018\nno_epoch    | Mon Sep 14 00:00:00 3018\nalways_true | t\npg_epoch    | Sat Sep 12 00:00:00 3018\nunix_epoch  | Thu Sep 10 00:00:00 3018\ncustom_1    | Sun Sep 13 00:00:00 3018\ncustom_2    | Mon Sep 14 00:00:00 3018\n\nSELECT time, time_bucket(INTERVAL '1 week', time) no_epoch,\n             time_bucket(INTERVAL '1 week', time) = time_bucket(INTERVAL '1 week', time, date '2000-01-03') always_true,\n             time_bucket(INTERVAL '1 week', time, date '2000-01-01') pg_epoch,\n             time_bucket(INTERVAL '1 week', time, (timestamp 'epoch')::date) unix_epoch,\n             time_bucket(INTERVAL '1 week', time, date '3018-09-13') custom_1,\n             time_bucket(INTERVAL '1 week', time, date '3018-09-14') custom_2\nFROM unnest(ARRAY[\n    date '1999-12-31',\n    date '2000-01-01',\n    date '2000-01-02',\n    date '2000-01-03',\n    date '3018-09-12',\n    date '3018-09-13',\n    date '3018-09-14',\n    date '3018-09-15'\n    ]) AS time;\n-[ RECORD 1 ]-----------\ntime        | 12-31-1999\nno_epoch    | 12-27-1999\nalways_true | t\npg_epoch    | 12-25-1999\nunix_epoch  | 12-30-1999\ncustom_1    | 12-26-1999\ncustom_2    | 12-27-1999\n-[ RECORD 2 ]-----------\ntime        | 01-01-2000\nno_epoch    | 12-27-1999\nalways_true | t\npg_epoch    | 01-01-2000\nunix_epoch  | 12-30-1999\ncustom_1    | 12-26-1999\ncustom_2    | 12-27-1999\n-[ RECORD 3 ]-----------\ntime        | 01-02-2000\nno_epoch    | 12-27-1999\nalways_true | t\npg_epoch    | 01-01-2000\nunix_epoch  | 12-30-1999\ncustom_1    | 01-02-2000\ncustom_2    | 12-27-1999\n-[ RECORD 4 ]-----------\ntime        | 01-03-2000\nno_epoch    | 01-03-2000\nalways_true | t\npg_epoch    | 01-01-2000\nunix_epoch  | 12-30-1999\ncustom_1    | 01-02-2000\ncustom_2    | 01-03-2000\n-[ RECORD 5 ]-----------\ntime        | 09-12-3018\nno_epoch    | 09-07-3018\nalways_true | t\npg_epoch    | 09-12-3018\nunix_epoch  | 09-10-3018\ncustom_1    | 09-06-3018\ncustom_2    | 09-07-3018\n-[ RECORD 6 ]-----------\ntime        | 09-13-3018\nno_epoch    | 09-07-3018\nalways_true | t\npg_epoch    | 09-12-3018\nunix_epoch  | 09-10-3018\ncustom_1    | 09-13-3018\ncustom_2    | 09-07-3018\n-[ RECORD 7 ]-----------\ntime        | 09-14-3018\nno_epoch    | 09-14-3018\nalways_true | t\npg_epoch    | 09-12-3018\nunix_epoch  | 09-10-3018\ncustom_1    | 09-13-3018\ncustom_2    | 09-14-3018\n-[ RECORD 8 ]-----------\ntime        | 09-15-3018\nno_epoch    | 09-14-3018\nalways_true | t\npg_epoch    | 09-12-3018\nunix_epoch  | 09-10-3018\ncustom_1    | 09-13-3018\ncustom_2    | 09-14-3018\n\n\\x\n--really old origin works if date around that time\nSELECT time, time_bucket(INTERVAL '1 week', time, timestamp without time zone '4710-11-24 01:01:01.0 BC')\nFROM unnest(ARRAY[\n    timestamp without time zone '4710-11-24 01:01:01.0 BC',\n    timestamp without time zone '4710-11-25 01:01:01.0 BC',\n    timestamp without time zone '2001-01-01',\n    timestamp without time zone '3001-01-01'\n    ]) AS time;\n            time             |         time_bucket         \n-----------------------------+-----------------------------\n Sat Nov 24 01:01:01 4710 BC | Sat Nov 24 01:01:01 4710 BC\n Sun Nov 25 01:01:01 4710 BC | Sat Nov 24 01:01:01 4710 BC\n Mon Jan 01 00:00:00 2001    | Sat Dec 30 01:01:01 2000\n Thu Jan 01 00:00:00 3001    | Sat Dec 27 01:01:01 3000\n\nSELECT time, time_bucket(INTERVAL '1 week', time, timestamp without time zone '294270-12-30 23:59:59.9999')\nFROM unnest(ARRAY[\n    timestamp without time zone '294270-12-29 23:59:59.9999',\n    timestamp without time zone '294270-12-30 23:59:59.9999',\n    timestamp without time zone '294270-12-31 23:59:59.9999',\n    timestamp without time zone '2001-01-01',\n    timestamp without time zone '3001-01-01'\n    ]) AS time;\n              time               |           time_bucket           \n---------------------------------+---------------------------------\n Thu Dec 29 23:59:59.9999 294270 | Fri Dec 23 23:59:59.9999 294270\n Fri Dec 30 23:59:59.9999 294270 | Fri Dec 30 23:59:59.9999 294270\n Sat Dec 31 23:59:59.9999 294270 | Fri Dec 30 23:59:59.9999 294270\n Mon Jan 01 00:00:00 2001        | Fri Dec 29 23:59:59.9999 2000\n Thu Jan 01 00:00:00 3001        | Fri Dec 26 23:59:59.9999 3000\n\n\\set ON_ERROR_STOP 0\n--really old origin + very new data + long period errors\nSELECT time, time_bucket(INTERVAL '100000 day', time, timestamp without time zone '4710-11-24 01:01:01.0 BC')\nFROM unnest(ARRAY[\n    timestamp without time zone '294270-12-31 23:59:59.9999'\n    ]) AS time;\nERROR:  timestamp out of range\nSELECT time, time_bucket(INTERVAL '100000 day', time, timestamp with time zone '4710-11-25 01:01:01.0 BC')\nFROM unnest(ARRAY[\n    timestamp with time zone '294270-12-30 23:59:59.9999'\n    ]) AS time;\nERROR:  timestamp out of range\n--really high origin + old data + long period errors out\nSELECT time, time_bucket(INTERVAL '10000000 day', time, timestamp without time zone '294270-12-31 23:59:59.9999')\nFROM unnest(ARRAY[\n    timestamp without time zone '4710-11-24 01:01:01.0 BC'\n    ]) AS time;\nERROR:  timestamp out of range\nSELECT time, time_bucket(INTERVAL '10000000 day', time, timestamp with time zone '294270-12-31 23:59:59.9999')\nFROM unnest(ARRAY[\n    timestamp with time zone '4710-11-24 01:01:01.0 BC'\n    ]) AS time;\nERROR:  timestamp out of range\n\\set ON_ERROR_STOP 1\n-------------------------------------------\n--- Test time_bucket with month periods ---\n-------------------------------------------\nSET datestyle TO ISO;\nSELECT\n  time::date,\n  time_bucket('1 month', time::date) AS \"1m\",\n  time_bucket('2 month', time::date) AS \"2m\",\n  time_bucket('3 month', time::date) AS \"3m\",\n  time_bucket('1 month', time::date, '2000-02-01'::date) AS \"1m origin\",\n  time_bucket('2 month', time::date, '2000-02-01'::date) AS \"2m origin\",\n  time_bucket('3 month', time::date, '2000-02-01'::date) AS \"3m origin\"\nFROM generate_series('1990-01-03'::date,'1990-06-03'::date,'1month'::interval) time;\n    time    |     1m     |     2m     |     3m     | 1m origin  | 2m origin  | 3m origin  \n------------+------------+------------+------------+------------+------------+------------\n 1990-01-03 | 1990-01-01 | 1990-01-01 | 1990-01-01 | 1990-01-01 | 1989-12-01 | 1989-11-01\n 1990-02-03 | 1990-02-01 | 1990-01-01 | 1990-01-01 | 1990-02-01 | 1990-02-01 | 1990-02-01\n 1990-03-03 | 1990-03-01 | 1990-03-01 | 1990-01-01 | 1990-03-01 | 1990-02-01 | 1990-02-01\n 1990-04-03 | 1990-04-01 | 1990-03-01 | 1990-04-01 | 1990-04-01 | 1990-04-01 | 1990-02-01\n 1990-05-03 | 1990-05-01 | 1990-05-01 | 1990-04-01 | 1990-05-01 | 1990-04-01 | 1990-05-01\n 1990-06-03 | 1990-06-01 | 1990-05-01 | 1990-04-01 | 1990-06-01 | 1990-06-01 | 1990-05-01\n\nSELECT\n  time,\n  time_bucket('1 month', time) AS \"1m\",\n  time_bucket('2 month', time) AS \"2m\",\n  time_bucket('3 month', time) AS \"3m\",\n  time_bucket('1 month', time, '2000-02-01'::timestamp) AS \"1m origin\",\n  time_bucket('2 month', time, '2000-02-01'::timestamp) AS \"2m origin\",\n  time_bucket('3 month', time, '2000-02-01'::timestamp) AS \"3m origin\"\nFROM generate_series('1990-01-03'::timestamp,'1990-06-03'::timestamp,'1month'::interval) time;\n        time         |         1m          |         2m          |         3m          |      1m origin      |      2m origin      |      3m origin      \n---------------------+---------------------+---------------------+---------------------+---------------------+---------------------+---------------------\n 1990-01-03 00:00:00 | 1990-01-01 00:00:00 | 1990-01-01 00:00:00 | 1990-01-01 00:00:00 | 1990-01-01 00:00:00 | 1989-12-01 00:00:00 | 1989-11-01 00:00:00\n 1990-02-03 00:00:00 | 1990-02-01 00:00:00 | 1990-01-01 00:00:00 | 1990-01-01 00:00:00 | 1990-02-01 00:00:00 | 1990-02-01 00:00:00 | 1990-02-01 00:00:00\n 1990-03-03 00:00:00 | 1990-03-01 00:00:00 | 1990-03-01 00:00:00 | 1990-01-01 00:00:00 | 1990-03-01 00:00:00 | 1990-02-01 00:00:00 | 1990-02-01 00:00:00\n 1990-04-03 00:00:00 | 1990-04-01 00:00:00 | 1990-03-01 00:00:00 | 1990-04-01 00:00:00 | 1990-04-01 00:00:00 | 1990-04-01 00:00:00 | 1990-02-01 00:00:00\n 1990-05-03 00:00:00 | 1990-05-01 00:00:00 | 1990-05-01 00:00:00 | 1990-04-01 00:00:00 | 1990-05-01 00:00:00 | 1990-04-01 00:00:00 | 1990-05-01 00:00:00\n 1990-06-03 00:00:00 | 1990-06-01 00:00:00 | 1990-05-01 00:00:00 | 1990-04-01 00:00:00 | 1990-06-01 00:00:00 | 1990-06-01 00:00:00 | 1990-05-01 00:00:00\n\nSELECT\n  time,\n  time_bucket('1 month', time) AS \"1m\",\n  time_bucket('2 month', time) AS \"2m\",\n  time_bucket('3 month', time) AS \"3m\",\n  time_bucket('1 month', time, '2000-02-01'::timestamptz) AS \"1m origin\",\n  time_bucket('2 month', time, '2000-02-01'::timestamptz) AS \"2m origin\",\n  time_bucket('3 month', time, '2000-02-01'::timestamptz) AS \"3m origin\"\nFROM generate_series('1990-01-03'::timestamptz,'1990-06-03'::timestamptz,'1month'::interval) time;\n          time          |           1m           |           2m           |           3m           |       1m origin        |       2m origin        |       3m origin        \n------------------------+------------------------+------------------------+------------------------+------------------------+------------------------+------------------------\n 1990-01-03 00:00:00-05 | 1989-12-31 19:00:00-05 | 1989-12-31 19:00:00-05 | 1989-12-31 19:00:00-05 | 1989-12-31 19:00:00-05 | 1989-11-30 19:00:00-05 | 1989-10-31 19:00:00-05\n 1990-02-03 00:00:00-05 | 1990-01-31 19:00:00-05 | 1989-12-31 19:00:00-05 | 1989-12-31 19:00:00-05 | 1990-01-31 19:00:00-05 | 1990-01-31 19:00:00-05 | 1990-01-31 19:00:00-05\n 1990-03-03 00:00:00-05 | 1990-02-28 19:00:00-05 | 1990-02-28 19:00:00-05 | 1989-12-31 19:00:00-05 | 1990-02-28 19:00:00-05 | 1990-01-31 19:00:00-05 | 1990-01-31 19:00:00-05\n 1990-04-03 00:00:00-04 | 1990-03-31 19:00:00-05 | 1990-02-28 19:00:00-05 | 1990-03-31 19:00:00-05 | 1990-03-31 19:00:00-05 | 1990-03-31 19:00:00-05 | 1990-01-31 19:00:00-05\n 1990-05-03 00:00:00-04 | 1990-04-30 20:00:00-04 | 1990-04-30 20:00:00-04 | 1990-03-31 19:00:00-05 | 1990-04-30 20:00:00-04 | 1990-03-31 19:00:00-05 | 1990-04-30 20:00:00-04\n 1990-06-03 00:00:00-04 | 1990-05-31 20:00:00-04 | 1990-04-30 20:00:00-04 | 1990-03-31 19:00:00-05 | 1990-05-31 20:00:00-04 | 1990-05-31 20:00:00-04 | 1990-04-30 20:00:00-04\n\n---------------------------------------\n--- Test time_bucket with timezones ---\n---------------------------------------\n-- test NULL args\nSELECT\ntime_bucket(NULL::interval,now(),'Europe/Berlin'),\ntime_bucket('1day',NULL::timestamptz,'Europe/Berlin'),\ntime_bucket('1day',now(),NULL::text),\ntime_bucket('1day',timestamptz '2020-02-03','Europe/Berlin',NULL),\ntime_bucket('1day',timestamptz '2020-02-03','Europe/Berlin','2020-04-01',NULL),\ntime_bucket('1day',timestamptz '2020-02-03','Europe/Berlin',NULL,NULL),\ntime_bucket('1day',timestamptz '2020-02-03','Europe/Berlin',\"offset\":=NULL::interval),\ntime_bucket('1day',timestamptz '2020-02-03','Europe/Berlin',origin:=NULL::timestamptz);\n time_bucket | time_bucket | time_bucket |      time_bucket       |      time_bucket       |      time_bucket       |      time_bucket       |      time_bucket       \n-------------+-------------+-------------+------------------------+------------------------+------------------------+------------------------+------------------------\n             |             |             | 2020-02-02 18:00:00-05 | 2020-02-03 00:00:00-05 | 2020-02-02 18:00:00-05 | 2020-02-02 18:00:00-05 | 2020-02-02 18:00:00-05\n\nSET datestyle TO ISO;\nSELECT\n  time_bucket('1day', ts) AS \"UTC\",\n  time_bucket('1day', ts, 'Europe/Berlin') AS \"Berlin\",\n  time_bucket('1day', ts, 'Europe/London') AS \"London\",\n  time_bucket('1day', ts, 'America/New_York') AS \"New York\",\n  time_bucket('1day', ts, 'PST') AS \"PST\",\n  time_bucket('1day', ts, current_setting('timezone')) AS \"current\"\nFROM generate_series('1999-12-31 17:00'::timestamptz,'2000-01-02 3:00'::timestamptz, '1hour'::interval) ts;\n          UTC           |         Berlin         |         London         |        New York        |          PST           |        current         \n------------------------+------------------------+------------------------+------------------------+------------------------+------------------------\n 1999-12-30 19:00:00-05 | 1999-12-30 18:00:00-05 | 1999-12-30 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-30 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-30 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-31 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-31 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-31 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 1999-12-31 19:00:00-05 | 2000-01-01 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-02 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-02 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-02 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-02 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-02 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-02 00:00:00-05\n 2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-02 00:00:00-05 | 2000-01-02 03:00:00-05 | 2000-01-02 00:00:00-05\n\nSELECT\n  time_bucket('1month', ts) AS \"UTC\",\n  time_bucket('1month', ts, 'Europe/Berlin') AS \"Berlin\",\n  time_bucket('1month', ts, 'America/New_York') AS \"New York\",\n  time_bucket('1month', ts, current_setting('timezone')) AS \"current\",\n  time_bucket('2month', ts, current_setting('timezone')) AS \"2m\",\n  time_bucket('2month', ts, current_setting('timezone'), '2000-02-01'::timestamp) AS \"2m origin\",\n  time_bucket('2month', ts, current_setting('timezone'), \"offset\":='14 day'::interval) AS \"2m offset\",\n  time_bucket('2month', ts, current_setting('timezone'), '2000-02-01'::timestamp, '7 day'::interval) AS \"2m offset + origin\"\nFROM generate_series('1999-12-01'::timestamptz,'2000-09-01'::timestamptz, '9 day'::interval) ts;\n          UTC           |         Berlin         |        New York        |        current         |           2m           |       2m origin        |       2m offset        |   2m offset + origin   \n------------------------+------------------------+------------------------+------------------------+------------------------+------------------------+------------------------+------------------------\n 1999-11-30 19:00:00-05 | 1999-11-30 18:00:00-05 | 1999-12-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-15 00:00:00-05 | 1999-10-08 00:00:00-04\n 1999-11-30 19:00:00-05 | 1999-11-30 18:00:00-05 | 1999-12-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 1999-11-30 19:00:00-05 | 1999-11-30 18:00:00-05 | 1999-12-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 1999-11-30 19:00:00-05 | 1999-11-30 18:00:00-05 | 1999-12-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 2000-01-31 19:00:00-05 | 2000-01-31 18:00:00-05 | 2000-02-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 1999-12-08 00:00:00-05\n 2000-01-31 19:00:00-05 | 2000-01-31 18:00:00-05 | 2000-02-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-01-31 19:00:00-05 | 2000-01-31 18:00:00-05 | 2000-02-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-01-31 19:00:00-05 | 2000-01-31 18:00:00-05 | 2000-02-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-02-29 19:00:00-05 | 2000-02-29 18:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-02-29 19:00:00-05 | 2000-02-29 18:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-02-29 19:00:00-05 | 2000-02-29 18:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-03-31 19:00:00-05 | 2000-03-31 17:00:00-05 | 2000-04-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-02-08 00:00:00-05\n 2000-03-31 19:00:00-05 | 2000-03-31 17:00:00-05 | 2000-04-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-04-08 00:00:00-04\n 2000-03-31 19:00:00-05 | 2000-03-31 17:00:00-05 | 2000-04-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-04-08 00:00:00-04\n 2000-04-30 20:00:00-04 | 2000-04-30 18:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-04-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-04-08 00:00:00-04\n 2000-04-30 20:00:00-04 | 2000-04-30 18:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-04-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-04-08 00:00:00-04\n 2000-04-30 20:00:00-04 | 2000-04-30 18:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-04-01 00:00:00-05 | 2000-05-15 00:00:00-04 | 2000-04-08 00:00:00-04\n 2000-04-30 20:00:00-04 | 2000-04-30 18:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-04-01 00:00:00-05 | 2000-05-15 00:00:00-04 | 2000-04-08 00:00:00-04\n 2000-05-31 20:00:00-04 | 2000-05-31 18:00:00-04 | 2000-06-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-15 00:00:00-04 | 2000-04-08 00:00:00-04\n 2000-05-31 20:00:00-04 | 2000-05-31 18:00:00-04 | 2000-06-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-15 00:00:00-04 | 2000-06-08 00:00:00-04\n 2000-05-31 20:00:00-04 | 2000-05-31 18:00:00-04 | 2000-06-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-15 00:00:00-04 | 2000-06-08 00:00:00-04\n 2000-06-30 20:00:00-04 | 2000-06-30 18:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-15 00:00:00-04 | 2000-06-08 00:00:00-04\n 2000-06-30 20:00:00-04 | 2000-06-30 18:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-15 00:00:00-04 | 2000-06-08 00:00:00-04\n 2000-06-30 20:00:00-04 | 2000-06-30 18:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-07-15 00:00:00-04 | 2000-06-08 00:00:00-04\n 2000-06-30 20:00:00-04 | 2000-06-30 18:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-07-15 00:00:00-04 | 2000-06-08 00:00:00-04\n 2000-07-31 20:00:00-04 | 2000-07-31 18:00:00-04 | 2000-08-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-15 00:00:00-04 | 2000-08-08 00:00:00-04\n 2000-07-31 20:00:00-04 | 2000-07-31 18:00:00-04 | 2000-08-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-15 00:00:00-04 | 2000-08-08 00:00:00-04\n 2000-07-31 20:00:00-04 | 2000-07-31 18:00:00-04 | 2000-08-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-15 00:00:00-04 | 2000-08-08 00:00:00-04\n\nRESET datestyle;\n-------------------------------------\n--- Test time input functions --\n-------------------------------------\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION test.interval_to_internal(coltype REGTYPE, value ANYELEMENT = NULL::BIGINT) RETURNS BIGINT\nAS :MODULE_PATHNAME, 'ts_dimension_interval_to_internal_test' LANGUAGE C VOLATILE;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT test.interval_to_internal('TIMESTAMP'::regtype, INTERVAL '1 day');\n interval_to_internal \n----------------------\n          86400000000\n\nSELECT test.interval_to_internal('TIMESTAMP'::regtype, 86400000000);\n interval_to_internal \n----------------------\n          86400000000\n\n---should give warning\nSELECT test.interval_to_internal('TIMESTAMP'::regtype, 86400);\nWARNING:  unexpected interval: smaller than one second\nHINT:  The interval is specified in microseconds.\n interval_to_internal \n----------------------\n                86400\n\nSELECT test.interval_to_internal('TIMESTAMP'::regtype);\n interval_to_internal \n----------------------\n         604800000000\n\nSELECT test.interval_to_internal('BIGINT'::regtype, 2147483649::bigint);\n interval_to_internal \n----------------------\n           2147483649\n\n-- Default interval for integer is supported as part of\n-- hypertable generalization\nSELECT test.interval_to_internal('INT'::regtype);\n interval_to_internal \n----------------------\n               100000\n\nSELECT test.interval_to_internal('SMALLINT'::regtype);\n interval_to_internal \n----------------------\n                10000\n\nSELECT test.interval_to_internal('BIGINT'::regtype);\n interval_to_internal \n----------------------\n              1000000\n\nSELECT test.interval_to_internal('TIMESTAMPTZ'::regtype);\n interval_to_internal \n----------------------\n         604800000000\n\nSELECT test.interval_to_internal('TIMESTAMP'::regtype);\n interval_to_internal \n----------------------\n         604800000000\n\nSELECT test.interval_to_internal('DATE'::regtype);\n interval_to_internal \n----------------------\n         604800000000\n\n\\set VERBOSITY terse\n\\set ON_ERROR_STOP 0\nSELECT test.interval_to_internal('INT'::regtype, 2147483649::bigint);\nERROR:  invalid interval: must be between 1 and 2147483647\nSELECT test.interval_to_internal('SMALLINT'::regtype, 32768::bigint);\nERROR:  invalid interval: must be between 1 and 32767\nSELECT test.interval_to_internal('TEXT'::regtype, 32768::bigint);\nERROR:  invalid type for dimension \"testcol\"\nSELECT test.interval_to_internal('INT'::regtype, INTERVAL '1 day');\nERROR:  invalid interval type for integer dimension\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "test/expected/triggers.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE hyper (\n  time BIGINT NOT NULL,\n  device_id TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1\n);\nCREATE OR REPLACE FUNCTION test_trigger()\n    RETURNS TRIGGER LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n    cnt INTEGER;\nBEGIN\n    SELECT count(*) INTO cnt FROM hyper;\n    RAISE WARNING 'FIRING trigger when: % level: % op: % cnt: % trigger_name %',\n        tg_when, tg_level, tg_op, cnt, tg_name;\n\n    IF TG_OP = 'DELETE' THEN\n        RETURN OLD;\n    END IF;\n    RETURN NEW;\nEND\n$BODY$;\n-- row triggers: BEFORE\nCREATE TRIGGER _0_test_trigger_insert\n    BEFORE INSERT ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER _0_test_trigger_update\n    BEFORE UPDATE ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER _0_test_trigger_delete\n    BEFORE delete ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER z_test_trigger_all\n    BEFORE INSERT OR UPDATE OR DELETE ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\n-- row triggers: AFTER\nCREATE TRIGGER _0_test_trigger_insert_after\n    AFTER INSERT ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER _0_test_trigger_insert_after_when_dev1\n    AFTER INSERT ON hyper\n    FOR EACH ROW\n    WHEN (NEW.device_id = 'dev1')\n    EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER _0_test_trigger_update_after\n    AFTER UPDATE ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER _0_test_trigger_delete_after\n    AFTER delete ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER z_test_trigger_all_after\n    AFTER INSERT OR UPDATE OR DELETE ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\n-- statement triggers: BEFORE\nCREATE TRIGGER _0_test_trigger_insert_s_before\n    BEFORE INSERT ON hyper\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER _0_test_trigger_update_s_before\n    BEFORE UPDATE ON hyper\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER _0_test_trigger_delete_s_before\n    BEFORE DELETE ON hyper\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\n-- statement triggers: AFTER\nCREATE TRIGGER _0_test_trigger_insert_s_after\n    AFTER INSERT ON hyper\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER _0_test_trigger_update_s_after\n    AFTER UPDATE ON hyper\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER _0_test_trigger_delete_s_after\n    AFTER DELETE ON hyper\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\n-- CONSTRAINT TRIGGER\nCREATE CONSTRAINT TRIGGER _0_test_trigger_constraint_insert\n  AFTER INSERT ON hyper FOR EACH ROW EXECUTE FUNCTION test_trigger();\nCREATE CONSTRAINT TRIGGER _0_test_trigger_constraint_update\n  AFTER UPDATE ON hyper FOR EACH ROW EXECUTE FUNCTION test_trigger();\nCREATE CONSTRAINT TRIGGER _0_test_trigger_constraint_delete\n  AFTER DELETE ON hyper FOR EACH ROW EXECUTE FUNCTION test_trigger();\nSELECT * FROM create_hypertable('hyper', 'time', chunk_time_interval => 10);\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             1 | public      | hyper      | t\n\n--test triggers before create_hypertable\nINSERT INTO hyper(time, device_id,sensor_1) VALUES\n(1257987600000000000, 'dev1', 1);\nWARNING:  FIRING trigger when: BEFORE level: STATEMENT op: INSERT cnt: 0 trigger_name _0_test_trigger_insert_s_before\nWARNING:  FIRING trigger when: BEFORE level: ROW op: INSERT cnt: 0 trigger_name _0_test_trigger_insert\nWARNING:  FIRING trigger when: BEFORE level: ROW op: INSERT cnt: 0 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 1 trigger_name _0_test_trigger_constraint_insert\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 1 trigger_name _0_test_trigger_insert_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 1 trigger_name _0_test_trigger_insert_after_when_dev1\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 1 trigger_name z_test_trigger_all_after\nWARNING:  FIRING trigger when: AFTER level: STATEMENT op: INSERT cnt: 1 trigger_name _0_test_trigger_insert_s_after\nINSERT INTO hyper(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 1), (1257987800000000000, 'dev2', 1);\nWARNING:  FIRING trigger when: BEFORE level: STATEMENT op: INSERT cnt: 1 trigger_name _0_test_trigger_insert_s_before\nWARNING:  FIRING trigger when: BEFORE level: ROW op: INSERT cnt: 1 trigger_name _0_test_trigger_insert\nWARNING:  FIRING trigger when: BEFORE level: ROW op: INSERT cnt: 1 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: BEFORE level: ROW op: INSERT cnt: 2 trigger_name _0_test_trigger_insert\nWARNING:  FIRING trigger when: BEFORE level: ROW op: INSERT cnt: 2 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 3 trigger_name _0_test_trigger_constraint_insert\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 3 trigger_name _0_test_trigger_insert_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 3 trigger_name z_test_trigger_all_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 3 trigger_name _0_test_trigger_constraint_insert\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 3 trigger_name _0_test_trigger_insert_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 3 trigger_name z_test_trigger_all_after\nWARNING:  FIRING trigger when: AFTER level: STATEMENT op: INSERT cnt: 3 trigger_name _0_test_trigger_insert_s_after\nUPDATE hyper SET sensor_1 = 2;\nWARNING:  FIRING trigger when: BEFORE level: STATEMENT op: UPDATE cnt: 3 trigger_name _0_test_trigger_update_s_before\nWARNING:  FIRING trigger when: BEFORE level: ROW op: UPDATE cnt: 3 trigger_name _0_test_trigger_update\nWARNING:  FIRING trigger when: BEFORE level: ROW op: UPDATE cnt: 3 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: BEFORE level: ROW op: UPDATE cnt: 3 trigger_name _0_test_trigger_update\nWARNING:  FIRING trigger when: BEFORE level: ROW op: UPDATE cnt: 3 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: BEFORE level: ROW op: UPDATE cnt: 3 trigger_name _0_test_trigger_update\nWARNING:  FIRING trigger when: BEFORE level: ROW op: UPDATE cnt: 3 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: AFTER level: ROW op: UPDATE cnt: 3 trigger_name _0_test_trigger_constraint_update\nWARNING:  FIRING trigger when: AFTER level: ROW op: UPDATE cnt: 3 trigger_name _0_test_trigger_update_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: UPDATE cnt: 3 trigger_name z_test_trigger_all_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: UPDATE cnt: 3 trigger_name _0_test_trigger_constraint_update\nWARNING:  FIRING trigger when: AFTER level: ROW op: UPDATE cnt: 3 trigger_name _0_test_trigger_update_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: UPDATE cnt: 3 trigger_name z_test_trigger_all_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: UPDATE cnt: 3 trigger_name _0_test_trigger_constraint_update\nWARNING:  FIRING trigger when: AFTER level: ROW op: UPDATE cnt: 3 trigger_name _0_test_trigger_update_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: UPDATE cnt: 3 trigger_name z_test_trigger_all_after\nWARNING:  FIRING trigger when: AFTER level: STATEMENT op: UPDATE cnt: 3 trigger_name _0_test_trigger_update_s_after\nDELETE FROM hyper;\nWARNING:  FIRING trigger when: BEFORE level: STATEMENT op: DELETE cnt: 3 trigger_name _0_test_trigger_delete_s_before\nWARNING:  FIRING trigger when: BEFORE level: ROW op: DELETE cnt: 3 trigger_name _0_test_trigger_delete\nWARNING:  FIRING trigger when: BEFORE level: ROW op: DELETE cnt: 3 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: BEFORE level: ROW op: DELETE cnt: 2 trigger_name _0_test_trigger_delete\nWARNING:  FIRING trigger when: BEFORE level: ROW op: DELETE cnt: 2 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: BEFORE level: ROW op: DELETE cnt: 1 trigger_name _0_test_trigger_delete\nWARNING:  FIRING trigger when: BEFORE level: ROW op: DELETE cnt: 1 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: AFTER level: ROW op: DELETE cnt: 0 trigger_name _0_test_trigger_constraint_delete\nWARNING:  FIRING trigger when: AFTER level: ROW op: DELETE cnt: 0 trigger_name _0_test_trigger_delete_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: DELETE cnt: 0 trigger_name z_test_trigger_all_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: DELETE cnt: 0 trigger_name _0_test_trigger_constraint_delete\nWARNING:  FIRING trigger when: AFTER level: ROW op: DELETE cnt: 0 trigger_name _0_test_trigger_delete_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: DELETE cnt: 0 trigger_name z_test_trigger_all_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: DELETE cnt: 0 trigger_name _0_test_trigger_constraint_delete\nWARNING:  FIRING trigger when: AFTER level: ROW op: DELETE cnt: 0 trigger_name _0_test_trigger_delete_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: DELETE cnt: 0 trigger_name z_test_trigger_all_after\nWARNING:  FIRING trigger when: AFTER level: STATEMENT op: DELETE cnt: 0 trigger_name _0_test_trigger_delete_s_after\n--test drop trigger\nDROP TRIGGER _0_test_trigger_insert ON hyper;\nDROP TRIGGER _0_test_trigger_insert_s_before ON hyper;\nDROP TRIGGER _0_test_trigger_insert_after ON hyper;\nDROP TRIGGER _0_test_trigger_insert_s_after ON hyper;\nINSERT INTO hyper(time, device_id,sensor_1) VALUES\n(1257987600000000000, 'dev1', 1);\nWARNING:  FIRING trigger when: BEFORE level: ROW op: INSERT cnt: 0 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 1 trigger_name _0_test_trigger_constraint_insert\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 1 trigger_name _0_test_trigger_insert_after_when_dev1\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 1 trigger_name z_test_trigger_all_after\nINSERT INTO hyper(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 1), (1257987800000000000, 'dev2', 1);\nWARNING:  FIRING trigger when: BEFORE level: ROW op: INSERT cnt: 1 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: BEFORE level: ROW op: INSERT cnt: 2 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 3 trigger_name _0_test_trigger_constraint_insert\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 3 trigger_name z_test_trigger_all_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 3 trigger_name _0_test_trigger_constraint_insert\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 3 trigger_name z_test_trigger_all_after\nDROP TRIGGER _0_test_trigger_update ON hyper;\nDROP TRIGGER _0_test_trigger_update_s_before ON hyper;\nDROP TRIGGER _0_test_trigger_update_after ON hyper;\nDROP TRIGGER _0_test_trigger_update_s_after ON hyper;\nUPDATE hyper SET sensor_1 = 2;\nWARNING:  FIRING trigger when: BEFORE level: ROW op: UPDATE cnt: 3 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: BEFORE level: ROW op: UPDATE cnt: 3 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: BEFORE level: ROW op: UPDATE cnt: 3 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: AFTER level: ROW op: UPDATE cnt: 3 trigger_name _0_test_trigger_constraint_update\nWARNING:  FIRING trigger when: AFTER level: ROW op: UPDATE cnt: 3 trigger_name z_test_trigger_all_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: UPDATE cnt: 3 trigger_name _0_test_trigger_constraint_update\nWARNING:  FIRING trigger when: AFTER level: ROW op: UPDATE cnt: 3 trigger_name z_test_trigger_all_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: UPDATE cnt: 3 trigger_name _0_test_trigger_constraint_update\nWARNING:  FIRING trigger when: AFTER level: ROW op: UPDATE cnt: 3 trigger_name z_test_trigger_all_after\nDROP TRIGGER _0_test_trigger_delete ON hyper;\nDROP TRIGGER _0_test_trigger_delete_s_before ON hyper;\nDROP TRIGGER _0_test_trigger_delete_after ON hyper;\nDROP TRIGGER _0_test_trigger_delete_s_after ON hyper;\nDELETE FROM hyper;\nWARNING:  FIRING trigger when: BEFORE level: ROW op: DELETE cnt: 3 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: BEFORE level: ROW op: DELETE cnt: 2 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: BEFORE level: ROW op: DELETE cnt: 1 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: AFTER level: ROW op: DELETE cnt: 0 trigger_name _0_test_trigger_constraint_delete\nWARNING:  FIRING trigger when: AFTER level: ROW op: DELETE cnt: 0 trigger_name z_test_trigger_all_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: DELETE cnt: 0 trigger_name _0_test_trigger_constraint_delete\nWARNING:  FIRING trigger when: AFTER level: ROW op: DELETE cnt: 0 trigger_name z_test_trigger_all_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: DELETE cnt: 0 trigger_name _0_test_trigger_constraint_delete\nWARNING:  FIRING trigger when: AFTER level: ROW op: DELETE cnt: 0 trigger_name z_test_trigger_all_after\nDROP TRIGGER z_test_trigger_all ON hyper;\nDROP TRIGGER z_test_trigger_all_after ON hyper;\n--test create trigger on hypertable\n-- row triggers: BEFORE\nCREATE TRIGGER _0_test_trigger_insert\n    BEFORE INSERT ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER _0_test_trigger_update\n    BEFORE UPDATE ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER _0_test_trigger_delete\n    BEFORE delete ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER z_test_trigger_all\n    BEFORE INSERT OR UPDATE OR DELETE ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\n-- row triggers: AFTER\nCREATE TRIGGER _0_test_trigger_insert_after\n    AFTER INSERT ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER _0_test_trigger_update_after\n    AFTER UPDATE ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER _0_test_trigger_delete_after\n    AFTER delete ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER z_test_trigger_all_after\n    AFTER INSERT OR UPDATE OR DELETE ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\n-- statement triggers: BEFORE\nCREATE TRIGGER _0_test_trigger_insert_s_before\n    BEFORE INSERT ON hyper\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER _0_test_trigger_update_s_before\n    BEFORE UPDATE ON hyper\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER _0_test_trigger_delete_s_before\n    BEFORE DELETE ON hyper\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\n-- statement triggers: AFTER\nCREATE TRIGGER _0_test_trigger_insert_s_after\n    AFTER INSERT ON hyper\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER _0_test_trigger_update_s_after\n    AFTER UPDATE ON hyper\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER _0_test_trigger_delete_s_after\n    AFTER DELETE ON hyper\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\nINSERT INTO hyper(time, device_id,sensor_1) VALUES\n(1257987600000000000, 'dev1', 1);\nWARNING:  FIRING trigger when: BEFORE level: STATEMENT op: INSERT cnt: 0 trigger_name _0_test_trigger_insert_s_before\nWARNING:  FIRING trigger when: BEFORE level: ROW op: INSERT cnt: 0 trigger_name _0_test_trigger_insert\nWARNING:  FIRING trigger when: BEFORE level: ROW op: INSERT cnt: 0 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 1 trigger_name _0_test_trigger_constraint_insert\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 1 trigger_name _0_test_trigger_insert_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 1 trigger_name _0_test_trigger_insert_after_when_dev1\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 1 trigger_name z_test_trigger_all_after\nWARNING:  FIRING trigger when: AFTER level: STATEMENT op: INSERT cnt: 1 trigger_name _0_test_trigger_insert_s_after\nINSERT INTO hyper(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 1), (1257987800000000000, 'dev2', 1);\nWARNING:  FIRING trigger when: BEFORE level: STATEMENT op: INSERT cnt: 1 trigger_name _0_test_trigger_insert_s_before\nWARNING:  FIRING trigger when: BEFORE level: ROW op: INSERT cnt: 1 trigger_name _0_test_trigger_insert\nWARNING:  FIRING trigger when: BEFORE level: ROW op: INSERT cnt: 1 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: BEFORE level: ROW op: INSERT cnt: 2 trigger_name _0_test_trigger_insert\nWARNING:  FIRING trigger when: BEFORE level: ROW op: INSERT cnt: 2 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 3 trigger_name _0_test_trigger_constraint_insert\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 3 trigger_name _0_test_trigger_insert_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 3 trigger_name z_test_trigger_all_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 3 trigger_name _0_test_trigger_constraint_insert\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 3 trigger_name _0_test_trigger_insert_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: INSERT cnt: 3 trigger_name z_test_trigger_all_after\nWARNING:  FIRING trigger when: AFTER level: STATEMENT op: INSERT cnt: 3 trigger_name _0_test_trigger_insert_s_after\nUPDATE hyper SET sensor_1 = 2;\nWARNING:  FIRING trigger when: BEFORE level: STATEMENT op: UPDATE cnt: 3 trigger_name _0_test_trigger_update_s_before\nWARNING:  FIRING trigger when: BEFORE level: ROW op: UPDATE cnt: 3 trigger_name _0_test_trigger_update\nWARNING:  FIRING trigger when: BEFORE level: ROW op: UPDATE cnt: 3 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: BEFORE level: ROW op: UPDATE cnt: 3 trigger_name _0_test_trigger_update\nWARNING:  FIRING trigger when: BEFORE level: ROW op: UPDATE cnt: 3 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: BEFORE level: ROW op: UPDATE cnt: 3 trigger_name _0_test_trigger_update\nWARNING:  FIRING trigger when: BEFORE level: ROW op: UPDATE cnt: 3 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: AFTER level: ROW op: UPDATE cnt: 3 trigger_name _0_test_trigger_constraint_update\nWARNING:  FIRING trigger when: AFTER level: ROW op: UPDATE cnt: 3 trigger_name _0_test_trigger_update_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: UPDATE cnt: 3 trigger_name z_test_trigger_all_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: UPDATE cnt: 3 trigger_name _0_test_trigger_constraint_update\nWARNING:  FIRING trigger when: AFTER level: ROW op: UPDATE cnt: 3 trigger_name _0_test_trigger_update_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: UPDATE cnt: 3 trigger_name z_test_trigger_all_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: UPDATE cnt: 3 trigger_name _0_test_trigger_constraint_update\nWARNING:  FIRING trigger when: AFTER level: ROW op: UPDATE cnt: 3 trigger_name _0_test_trigger_update_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: UPDATE cnt: 3 trigger_name z_test_trigger_all_after\nWARNING:  FIRING trigger when: AFTER level: STATEMENT op: UPDATE cnt: 3 trigger_name _0_test_trigger_update_s_after\nDELETE FROM hyper;\nWARNING:  FIRING trigger when: BEFORE level: STATEMENT op: DELETE cnt: 3 trigger_name _0_test_trigger_delete_s_before\nWARNING:  FIRING trigger when: BEFORE level: ROW op: DELETE cnt: 3 trigger_name _0_test_trigger_delete\nWARNING:  FIRING trigger when: BEFORE level: ROW op: DELETE cnt: 3 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: BEFORE level: ROW op: DELETE cnt: 2 trigger_name _0_test_trigger_delete\nWARNING:  FIRING trigger when: BEFORE level: ROW op: DELETE cnt: 2 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: BEFORE level: ROW op: DELETE cnt: 1 trigger_name _0_test_trigger_delete\nWARNING:  FIRING trigger when: BEFORE level: ROW op: DELETE cnt: 1 trigger_name z_test_trigger_all\nWARNING:  FIRING trigger when: AFTER level: ROW op: DELETE cnt: 0 trigger_name _0_test_trigger_constraint_delete\nWARNING:  FIRING trigger when: AFTER level: ROW op: DELETE cnt: 0 trigger_name _0_test_trigger_delete_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: DELETE cnt: 0 trigger_name z_test_trigger_all_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: DELETE cnt: 0 trigger_name _0_test_trigger_constraint_delete\nWARNING:  FIRING trigger when: AFTER level: ROW op: DELETE cnt: 0 trigger_name _0_test_trigger_delete_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: DELETE cnt: 0 trigger_name z_test_trigger_all_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: DELETE cnt: 0 trigger_name _0_test_trigger_constraint_delete\nWARNING:  FIRING trigger when: AFTER level: ROW op: DELETE cnt: 0 trigger_name _0_test_trigger_delete_after\nWARNING:  FIRING trigger when: AFTER level: ROW op: DELETE cnt: 0 trigger_name z_test_trigger_all_after\nWARNING:  FIRING trigger when: AFTER level: STATEMENT op: DELETE cnt: 0 trigger_name _0_test_trigger_delete_s_after\nCREATE TABLE vehicles (\n  vehicle_id INTEGER PRIMARY KEY,\n  vin_number CHAR(17),\n  last_checkup TIMESTAMP\n);\nCREATE TABLE color (\n  color_id INTEGER PRIMARY KEY,\n  notes text\n);\nCREATE TABLE location (\n  time TIMESTAMP NOT NULL,\n  vehicle_id INTEGER REFERENCES vehicles (vehicle_id),\n  color_id INTEGER, --no reference since gonna populate a hypertable\n  latitude FLOAT,\n  longitude FLOAT\n);\nCREATE OR REPLACE FUNCTION create_vehicle_trigger_fn()\n    RETURNS TRIGGER LANGUAGE PLPGSQL AS\n$BODY$\nBEGIN\n    INSERT INTO vehicles VALUES(NEW.vehicle_id, NULL, NULL) ON CONFLICT DO NOTHING;\n    RETURN NEW;\nEND\n$BODY$;\nCREATE OR REPLACE FUNCTION create_color_trigger_fn()\n    RETURNS TRIGGER LANGUAGE PLPGSQL AS\n$BODY$\nBEGIN\n    --test subtxns within triggers\n    BEGIN\n        INSERT INTO color VALUES(NEW.color_id, 'n/a');\n    EXCEPTION WHEN unique_violation THEN\n            -- Nothing to do, just continue\n    END;\n    RETURN NEW;\nEND\n$BODY$;\nCREATE TRIGGER create_color_trigger\n    BEFORE INSERT OR UPDATE ON location\n    FOR EACH ROW EXECUTE FUNCTION create_color_trigger_fn();\nSELECT create_hypertable('location', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n   create_hypertable   \n-----------------------\n (2,public,location,t)\n\n--make color also a hypertable\nSELECT create_hypertable('color', 'color_id', chunk_time_interval=>10);\n create_hypertable  \n--------------------\n (3,public,color,t)\n\n-- Test that we can create and use triggers with another user\nGRANT TRIGGER, INSERT, SELECT, UPDATE ON location TO :ROLE_DEFAULT_PERM_USER_2;\nGRANT SELECT, INSERT, UPDATE ON color TO :ROLE_DEFAULT_PERM_USER_2;\nGRANT SELECT, INSERT, UPDATE ON vehicles TO :ROLE_DEFAULT_PERM_USER_2;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2;\nCREATE TRIGGER create_vehicle_trigger\n    BEFORE INSERT OR UPDATE ON location\n    FOR EACH ROW EXECUTE FUNCTION create_vehicle_trigger_fn();\nINSERT INTO location VALUES('2017-01-01 01:02:03', 1, 1, 40.7493226,-73.9771259);\nINSERT INTO location VALUES('2017-01-01 01:02:04', 1, 20, 24.7493226,-73.9771259);\nINSERT INTO location VALUES('2017-01-01 01:02:03', 23, 1, 40.7493226,-73.9771269);\nINSERT INTO location VALUES('2017-01-01 01:02:03', 53, 20, 40.7493226,-73.9771269);\nUPDATE location SET vehicle_id = 52 WHERE vehicle_id = 53;\nSELECT * FROM location;\n           time           | vehicle_id | color_id |  latitude  |  longitude  \n--------------------------+------------+----------+------------+-------------\n Sun Jan 01 01:02:03 2017 |          1 |        1 | 40.7493226 | -73.9771259\n Sun Jan 01 01:02:04 2017 |          1 |       20 | 24.7493226 | -73.9771259\n Sun Jan 01 01:02:03 2017 |         23 |        1 | 40.7493226 | -73.9771269\n Sun Jan 01 01:02:03 2017 |         52 |       20 | 40.7493226 | -73.9771269\n\nSELECT * FROM vehicles;\n vehicle_id | vin_number | last_checkup \n------------+------------+--------------\n          1 |            | \n         23 |            | \n         53 |            | \n         52 |            | \n\nSELECT * FROM color;\n color_id | notes \n----------+-------\n        1 | n/a\n       20 | n/a\n\n-- switch back to default user to run some dropping tests\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER;\n\\set ON_ERROR_STOP 0\n-- test that disable trigger is disallowed\nALTER TABLE location DISABLE TRIGGER create_vehicle_trigger;\nERROR:  hypertables do not support  enabling or disabling triggers.\n\\set ON_ERROR_STOP 1\n-- test that drop trigger works\nDROP TRIGGER create_color_trigger ON location;\nDROP TRIGGER create_vehicle_trigger ON location;\n-- test that drop trigger doesn't cause leftovers that mean that dropping chunks or hypertables no longer works\nSELECT count(1) FROM pg_depend d WHERE d.classid = 'pg_trigger'::regclass AND NOT EXISTS (SELECT 1 FROM pg_trigger WHERE oid = d.objid);\n count \n-------\n     0\n\nDROP TABLE location;\n-- test triggers with transition tables\n-- test creating hypertable from table with triggers with transition tables\nCREATE TABLE transition_test(time timestamptz NOT NULL);\nCREATE TRIGGER t1_stmt AFTER INSERT ON transition_test REFERENCING NEW TABLE AS new_trans FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER t1_row AFTER INSERT ON transition_test REFERENCING NEW TABLE AS new_trans FOR EACH ROW EXECUTE FUNCTION test_trigger();\n-- We do not support ROW triggers with transition tables, so we need\n-- to remove it to be able to create the hypertable.\n\\set ON_ERROR_STOP 0\nSELECT create_hypertable('transition_test','time');\nERROR:  ROW triggers with transition tables are not supported on hypertables\n\\set ON_ERROR_STOP 1\nDROP TRIGGER t1_row ON transition_test;\nSELECT create_hypertable('transition_test','time');\n      create_hypertable       \n------------------------------\n (4,public,transition_test,t)\n\n-- Insert some rows to create a chunk\nINSERT INTO transition_test values ('2020-01-10');\nWARNING:  FIRING trigger when: AFTER level: STATEMENT op: INSERT cnt: 0 trigger_name t1_stmt\nSELECT chunk FROM show_chunks('transition_test') tbl(chunk) limit 1 \\gset\n-- test creating trigger with transition tables on existing hypertable\nCREATE TRIGGER t3 AFTER UPDATE ON transition_test REFERENCING NEW TABLE AS new_trans OLD TABLE AS old_trans FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER t4 AFTER DELETE ON transition_test REFERENCING OLD TABLE AS old_trans FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\nINSERT INTO transition_test values ('2020-01-11');\nWARNING:  FIRING trigger when: AFTER level: STATEMENT op: INSERT cnt: 0 trigger_name t1_stmt\nCOPY transition_test FROM STDIN;\nWARNING:  FIRING trigger when: AFTER level: STATEMENT op: INSERT cnt: 0 trigger_name t1_stmt\nUPDATE transition_test SET time = '2020-01-12' WHERE time = '2020-01-11';\nWARNING:  FIRING trigger when: AFTER level: STATEMENT op: UPDATE cnt: 0 trigger_name t3\nDELETE FROM transition_test WHERE time = '2020-01-12';\nWARNING:  FIRING trigger when: AFTER level: STATEMENT op: DELETE cnt: 0 trigger_name t4\n\\set ON_ERROR_STOP 0\nCREATE TRIGGER t3 AFTER UPDATE ON :chunk REFERENCING NEW TABLE AS new_trans OLD TABLE AS old_trans FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\nERROR:  triggers with transition tables are not supported on hypertable chunks\nCREATE TRIGGER t4 AFTER DELETE ON :chunk REFERENCING OLD TABLE AS old_trans FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\nERROR:  triggers with transition tables are not supported on hypertable chunks\nCREATE TRIGGER t5 AFTER INSERT ON transition_test REFERENCING NEW TABLE AS new_trans FOR EACH ROW EXECUTE FUNCTION test_trigger();\nERROR:  ROW triggers with transition tables are not supported on hypertables\nCREATE TRIGGER t6 AFTER UPDATE ON transition_test REFERENCING NEW TABLE AS new_trans OLD TABLE AS old_trans FOR EACH ROW EXECUTE FUNCTION test_trigger();\nERROR:  ROW triggers with transition tables are not supported on hypertables\nCREATE TRIGGER t7 AFTER DELETE ON transition_test REFERENCING OLD TABLE AS old_trans FOR EACH ROW EXECUTE FUNCTION test_trigger();\nERROR:  ROW triggers with transition tables are not supported on hypertables\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "test/expected/truncate.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\o /dev/null\n\\ir include/insert_two_partitions.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE PUBLIC.\"two_Partitions\" (\n  \"timeCustom\" BIGINT NOT NULL,\n  device_id TEXT NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL,\n  series_bool BOOLEAN NULL\n);\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (device_id, \"timeCustom\" DESC NULLS LAST) WHERE device_id IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_0) WHERE series_0 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_1)  WHERE series_1 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_2) WHERE series_2 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_bool) WHERE series_bool IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, device_id);\nSELECT * FROM create_hypertable('\"public\".\"two_Partitions\"'::regclass, 'timeCustom'::name, 'device_id'::name, associated_schema_name=>'_timescaledb_internal'::text, number_partitions => 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\\set QUIET off\nBEGIN;\n\\COPY public.\"two_Partitions\" FROM 'data/ds1_dev1_1.tsv' NULL AS '';\nCOMMIT;\nINSERT INTO public.\"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257987600000000000, 'dev1', 1.5, 1),\n(1257987600000000000, 'dev1', 1.5, 2),\n(1257894000000000000, 'dev2', 1.5, 1),\n(1257894002000000000, 'dev1', 2.5, 3);\nINSERT INTO \"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257894000000000000, 'dev2', 1.5, 2);\n\\set QUIET on\n\\o\nSELECT * FROM _timescaledb_catalog.hypertable;\n id | schema_name |   table_name   | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------+----------------+------------------------+-------------------------+----------------+--------------------------+--------------------------+-------------------+-------------------+--------------------------+--------\n  1 | public      | two_Partitions | _timescaledb_internal  | _hyper_1                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n id | hypertable_id |      schema_name      |    table_name    | compressed_chunk_id | status | osm_chunk \n----+---------------+-----------------------+------------------+---------------------+--------+-----------\n  1 |             1 | _timescaledb_internal | _hyper_1_1_chunk |                     |      0 | f\n  2 |             1 | _timescaledb_internal | _hyper_1_2_chunk |                     |      0 | f\n  3 |             1 | _timescaledb_internal | _hyper_1_3_chunk |                     |      0 | f\n  4 |             1 | _timescaledb_internal | _hyper_1_4_chunk |                     |      0 | f\n\nSELECT * FROM test.show_subtables('\"two_Partitions\"');\n                 Child                  | Tablespace \n----------------------------------------+------------\n _timescaledb_internal._hyper_1_1_chunk | \n _timescaledb_internal._hyper_1_2_chunk | \n _timescaledb_internal._hyper_1_3_chunk | \n _timescaledb_internal._hyper_1_4_chunk | \n\nSELECT * FROM \"two_Partitions\";\n     timeCustom      | device_id | series_0 | series_1 | series_2 | series_bool \n---------------------+-----------+----------+----------+----------+-------------\n 1257894000000000000 | dev1      |      1.5 |        1 |        2 | t\n 1257894000000000000 | dev1      |      1.5 |        2 |          | \n 1257894000000001000 | dev1      |      2.5 |        3 |          | \n 1257894001000000000 | dev1      |      3.5 |        4 |          | \n 1257894002000000000 | dev1      |      5.5 |        6 |          | t\n 1257894002000000000 | dev1      |      5.5 |        7 |          | f\n 1257894002000000000 | dev1      |      2.5 |        3 |          | \n 1257897600000000000 | dev1      |      4.5 |        5 |          | f\n 1257987600000000000 | dev1      |      1.5 |        1 |          | \n 1257987600000000000 | dev1      |      1.5 |        2 |          | \n 1257894000000000000 | dev2      |      1.5 |        1 |          | \n 1257894000000000000 | dev2      |      1.5 |        2 |          | \n\nSET client_min_messages = WARNING;\nTRUNCATE \"two_Partitions\";\nSELECT * FROM _timescaledb_catalog.hypertable;\n id | schema_name |   table_name   | associated_schema_name | associated_table_prefix | num_dimensions | chunk_sizing_func_schema |  chunk_sizing_func_name  | chunk_target_size | compression_state | compressed_hypertable_id | status \n----+-------------+----------------+------------------------+-------------------------+----------------+--------------------------+--------------------------+-------------------+-------------------+--------------------------+--------\n  1 | public      | two_Partitions | _timescaledb_internal  | _hyper_1                |              2 | _timescaledb_functions   | calculate_chunk_interval |                 0 |                 0 |                          |      0\n\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n id | hypertable_id | schema_name | table_name | compressed_chunk_id | status | osm_chunk \n----+---------------+-------------+------------+---------------------+--------+-----------\n\n-- should be empty\nSELECT * FROM test.show_subtables('\"two_Partitions\"');\n Child | Tablespace \n-------+------------\n\nSELECT * FROM \"two_Partitions\";\n timeCustom | device_id | series_0 | series_1 | series_2 | series_bool \n------------+-----------+----------+----------+----------+-------------\n\nINSERT INTO public.\"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257987600000000000, 'dev1', 1.5, 1),\n(1257987600000000000, 'dev1', 1.5, 2),\n(1257894000000000000, 'dev2', 1.5, 1),\n(1257894002000000000, 'dev1', 2.5, 3);\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n id | hypertable_id |      schema_name      |    table_name    | compressed_chunk_id | status | osm_chunk \n----+---------------+-----------------------+------------------+---------------------+--------+-----------\n  5 |             1 | _timescaledb_internal | _hyper_1_5_chunk |                     |      0 | f\n  6 |             1 | _timescaledb_internal | _hyper_1_6_chunk |                     |      0 | f\n  7 |             1 | _timescaledb_internal | _hyper_1_7_chunk |                     |      0 | f\n\nCREATE VIEW dependent_view AS SELECT * FROM _timescaledb_internal._hyper_1_5_chunk;\nCREATE OR REPLACE FUNCTION test_trigger()\n    RETURNS TRIGGER LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n    cnt INTEGER;\nBEGIN\n    RAISE WARNING 'FIRING trigger when: % level: % op: % cnt: % trigger_name %',\n          tg_when, tg_level, tg_op, cnt, tg_name;\n    IF TG_OP = 'DELETE' THEN\n        RETURN OLD;\n    END IF;\n    RETURN NEW;\nEND\n$BODY$;\n-- test truncate on a chunk\nCREATE TRIGGER _test_truncate_before\n    BEFORE TRUNCATE ON _timescaledb_internal._hyper_1_5_chunk\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER _test_truncate_after\n    AFTER TRUNCATE ON _timescaledb_internal._hyper_1_5_chunk\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\n\\set ON_ERROR_STOP 0\nTRUNCATE \"two_Partitions\";\nWARNING:  FIRING trigger when: BEFORE level: STATEMENT op: TRUNCATE cnt: <NULL> trigger_name _test_truncate_before\nWARNING:  FIRING trigger when: AFTER level: STATEMENT op: TRUNCATE cnt: <NULL> trigger_name _test_truncate_after\nERROR:  cannot drop table _timescaledb_internal._hyper_1_5_chunk because other objects depend on it\n-- cannot TRUNCATE ONLY a hypertable\nTRUNCATE ONLY \"two_Partitions\" CASCADE;\nERROR:  cannot truncate only a hypertable\n\\set ON_ERROR_STOP 1\n-- create a regular table to make sure we can truncate it in the same call\nCREATE TABLE truncate_normal (color int);\nINSERT INTO truncate_normal VALUES (1);\nSELECT * FROM truncate_normal;\n color \n-------\n     1\n\n-- fix for bug #3580\n\\set ON_ERROR_STOP 0\nTRUNCATE nonexistentrelation;\nERROR:  relation \"nonexistentrelation\" does not exist\n\\set ON_ERROR_STOP 1\nCREATE TABLE truncate_nested (color int);\nINSERT INTO truncate_nested VALUES (2);\nSELECT * FROM truncate_normal, truncate_nested;\n color | color \n-------+-------\n     1 |     2\n\nCREATE FUNCTION fn_truncate_nested()\nRETURNS trigger LANGUAGE plpgsql\nAS $$\nBEGIN\n    TRUNCATE truncate_nested;\n    RETURN NEW;\nEND;\n$$;\nCREATE TRIGGER tg_truncate_nested\n    BEFORE TRUNCATE ON truncate_normal\n    FOR EACH STATEMENT EXECUTE FUNCTION fn_truncate_nested();\nTRUNCATE truncate_normal;\nSELECT * FROM truncate_normal, truncate_nested;\n color | color \n-------+-------\n\nINSERT INTO truncate_normal VALUES (3);\nINSERT INTO truncate_nested VALUES (4);\nSELECT * FROM truncate_normal, truncate_nested;\n color | color \n-------+-------\n     3 |     4\n\nTRUNCATE truncate_normal;\nSELECT * FROM truncate_normal, truncate_nested;\n color | color \n-------+-------\n\nINSERT INTO truncate_normal VALUES (5);\nINSERT INTO truncate_nested VALUES (6);\nSELECT * FROM truncate_normal, truncate_nested;\n color | color \n-------+-------\n     5 |     6\n\nSELECT * FROM test.show_subtables('\"two_Partitions\"');\n                 Child                  | Tablespace \n----------------------------------------+------------\n _timescaledb_internal._hyper_1_5_chunk | \n _timescaledb_internal._hyper_1_6_chunk | \n _timescaledb_internal._hyper_1_7_chunk | \n\nTRUNCATE \"two_Partitions\", truncate_normal CASCADE;\nWARNING:  FIRING trigger when: BEFORE level: STATEMENT op: TRUNCATE cnt: <NULL> trigger_name _test_truncate_before\nWARNING:  FIRING trigger when: AFTER level: STATEMENT op: TRUNCATE cnt: <NULL> trigger_name _test_truncate_after\n-- should be empty\nSELECT * FROM test.show_subtables('\"two_Partitions\"');\n Child | Tablespace \n-------+------------\n\nSELECT * FROM \"two_Partitions\";\n timeCustom | device_id | series_0 | series_1 | series_2 | series_bool \n------------+-----------+----------+----------+----------+-------------\n\nSELECT * FROM truncate_normal, truncate_nested;\n color | color \n-------+-------\n\n-- test TRUNCATE can be performed by a user\n-- with TRUNCATE privilege who is not table owner\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE ROLE owner WITH LOGIN;\nCREATE ROLE truncator WITH LOGIN;\nCREATE DATABASE test_trunc_ht OWNER owner;\n\\c test_trunc_ht :ROLE_SUPERUSER\nSET client_min_messages = ERROR;\nCREATE EXTENSION timescaledb;\nRESET client_min_messages;\n\\c test_trunc_ht owner\nCREATE TABLE test_hypertable (time TIMESTAMP WITHOUT TIME ZONE NOT NULL, value DOUBLE PRECISION);\nSELECT create_hypertable('test_hypertable', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n      create_hypertable       \n------------------------------\n (1,public,test_hypertable,t)\n\n-- fail since we don't have TRUNCATE privileges yet\n\\set ON_ERROR_STOP 0\n\\c test_trunc_ht truncator\nTRUNCATE TABLE test_hypertable;\nERROR:  permission denied for table test_hypertable\n\\set ON_ERROR_STOP 1\n\\c test_trunc_ht owner\nGRANT TRUNCATE ON test_hypertable TO truncator;\n-- now succeed after privilege was granted\n\\c test_trunc_ht truncator;\nTRUNCATE TABLE test_hypertable;\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- set client_min_messages to ERROR to suppress warnings about orphaned files\nSET client_min_messages TO ERROR;\nDROP DATABASE test_trunc_ht WITH (FORCE);\nDROP ROLE owner;\nDROP ROLE truncator;\n"
  },
  {
    "path": "test/expected/trusted_extension.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE DATABASE trusted_test;\nGRANT CREATE ON DATABASE trusted_test TO :ROLE_1;\n\\c trusted_test :ROLE_READ_ONLY\n\\set ON_ERROR_STOP 0\nCREATE EXTENSION timescaledb;\nERROR:  permission denied to create extension \"timescaledb\"\n\\set ON_ERROR_STOP 1\n\\c trusted_test :ROLE_1\n-- user shouldn't have superuser privilege\nSELECT rolsuper FROM pg_roles WHERE rolname=user;\n rolsuper \n----------\n f\n\nSET client_min_messages TO ERROR;\nCREATE EXTENSION timescaledb;\nRESET client_min_messages;\nCREATE TABLE t(time timestamptz);\nSELECT create_hypertable('t','time');\n create_hypertable \n-------------------\n (1,public,t,t)\n\nINSERT INTO t VALUES ('2000-01-01'), ('2001-01-01');\nSELECT * FROM t ORDER BY 1;\n             time             \n------------------------------\n Sat Jan 01 00:00:00 2000 PST\n Mon Jan 01 00:00:00 2001 PST\n\nSELECT * FROM timescaledb_information.hypertables;\n hypertable_schema | hypertable_name |    owner    | num_dimensions | num_chunks | compression_enabled | tablespaces | primary_dimension |  primary_dimension_type  \n-------------------+-----------------+-------------+----------------+------------+---------------------+-------------+-------------------+--------------------------\n public            | t               | test_role_1 |              1 |          2 | f                   |             | time              | timestamp with time zone\n\nSELECT * FROM test.relation WHERE schema = '_timescaledb_internal' AND name LIKE '\\_hyper%';\n        schema         |       name       | type  |    owner    \n-----------------------+------------------+-------+-------------\n _timescaledb_internal | _hyper_1_1_chunk | table | test_role_1\n _timescaledb_internal | _hyper_1_2_chunk | table | test_role_1\n\nDROP EXTENSION timescaledb CASCADE;\nNOTICE:  drop cascades to 2 other objects\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nDROP DATABASE trusted_test WITH (FORCE);\n"
  },
  {
    "path": "test/expected/ts_merge-15.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\\set ON_ERROR_STOP 0\n\\set VERBOSITY default\nSET client_min_messages TO error;\n\\set TEST_BASE_NAME ts_merge\nSELECT format('include/%s_load.sql', :'TEST_BASE_NAME') AS \"TEST_LOAD_NAME\",\n    format('include/%s_load_ht.sql', :'TEST_BASE_NAME') AS \"TEST_LOAD_HT_NAME\",\n    format('include/%s_query.sql', :'TEST_BASE_NAME') AS \"TEST_QUERY_NAME\",\n    format('%s/results/%s_ht_results.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') AS \"TEST_RESULTS_WITH_HYPERTABLE\",\n    format('%s/results/%s_results.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') AS \"TEST_RESULTS_WITH_NO_HYPERTABLE\" \\gset\nSELECT format('\\! diff -u --label \"Base pg table results\" --label \"Hypertable results\" %s %s', :'TEST_RESULTS_WITH_HYPERTABLE', :'TEST_RESULTS_WITH_NO_HYPERTABLE') AS \"DIFF_CMD\" \\gset\n\\ir :TEST_LOAD_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE USER regress_merge_privs;\nCREATE USER regress_merge_no_privs;\nDROP TABLE IF EXISTS target;\nDROP TABLE IF EXISTS source;\nCREATE TABLE target (tid integer, balance integer)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE source (sid integer, delta integer) -- no index\n  WITH (autovacuum_enabled=off);\nINSERT INTO target VALUES (1, 10);\nINSERT INTO target VALUES (2, 20);\nINSERT INTO target VALUES (3, 30);\nSELECT t.ctid is not null as matched, t.*, s.* FROM source s FULL OUTER JOIN target t ON s.sid = t.tid ORDER BY t.tid, s.sid;\n matched | tid | balance | sid | delta \n---------+-----+---------+-----+-------\n t       |   1 |      10 |     |      \n t       |   2 |      20 |     |      \n t       |   3 |      30 |     |      \n\nALTER TABLE target OWNER TO regress_merge_privs;\nALTER TABLE source OWNER TO regress_merge_privs;\nCREATE TABLE target2 (tid integer, balance integer)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE source2 (sid integer, delta integer)\n  WITH (autovacuum_enabled=off);\nALTER TABLE target2 OWNER TO regress_merge_no_privs;\nALTER TABLE source2 OWNER TO regress_merge_no_privs;\nGRANT INSERT ON target TO regress_merge_no_privs;\nGRANT CREATE ON SCHEMA public TO regress_merge_privs;\nSET SESSION AUTHORIZATION regress_merge_privs;\nCREATE TABLE sq_target (tid integer NOT NULL, balance integer)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE sq_source (delta integer, sid integer, balance integer DEFAULT 0)\n  WITH (autovacuum_enabled=off);\nINSERT INTO sq_target(tid, balance) VALUES (1,100), (2,200), (3,300);\nINSERT INTO sq_source(sid, delta) VALUES (1,10), (2,20), (4,40);\n-- conditional WHEN clause\nCREATE TABLE wq_target (tid integer not null, balance integer DEFAULT -1)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE wq_source (balance integer, sid integer)\n  WITH (autovacuum_enabled=off);\nINSERT INTO wq_source (sid, balance) VALUES (1, 100);\nCREATE TABLE cj_target (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE cj_source1 (sid1 integer, scat integer, delta integer)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE cj_source2 (sid2 integer, sval text)\n  WITH (autovacuum_enabled=off);\nINSERT INTO cj_source1 VALUES (1, 10, 100);\nINSERT INTO cj_source1 VALUES (1, 20, 200);\nINSERT INTO cj_source1 VALUES (2, 20, 300);\nINSERT INTO cj_source1 VALUES (3, 10, 400);\nINSERT INTO cj_source2 VALUES (1, 'initial source2');\nINSERT INTO cj_source2 VALUES (2, 'initial source2');\nINSERT INTO cj_source2 VALUES (3, 'initial source2');\nCREATE TABLE fs_target (a int, b int, c text)\n  WITH (autovacuum_enabled=off);\n-- run tests on normal table\n\\o :TEST_RESULTS_WITH_NO_HYPERTABLE\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n--\n-- Errors\n--\nMERGE INTO target t RANDOMWORD\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:12: ERROR:  syntax error at or near \"RANDOMWORD\"\nLINE 1: MERGE INTO target t RANDOMWORD\n                            ^\n-- MATCHED/INSERT error\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:18: ERROR:  syntax error at or near \"INSERT\"\nLINE 5:  INSERT DEFAULT VALUES;\n         ^\n-- incorrectly specifying INTO target\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT INTO target DEFAULT VALUES;\npsql:include/ts_merge_query.sql:24: ERROR:  syntax error at or near \"INTO\"\nLINE 5:  INSERT INTO target DEFAULT VALUES;\n                ^\n-- Multiple VALUES clause\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (1,1), (2,2);\npsql:include/ts_merge_query.sql:30: ERROR:  syntax error at or near \",\"\nLINE 5:  INSERT VALUES (1,1), (2,2);\n                            ^\n-- SELECT query for INSERT\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT SELECT (1, 1);\npsql:include/ts_merge_query.sql:36: ERROR:  syntax error at or near \"SELECT\"\nLINE 5:  INSERT SELECT (1, 1);\n                ^\n-- NOT MATCHED/UPDATE\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:42: ERROR:  syntax error at or near \"UPDATE\"\nLINE 5:  UPDATE SET balance = 0;\n         ^\n-- UPDATE tablename\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE target SET balance = 0;\npsql:include/ts_merge_query.sql:48: ERROR:  syntax error at or near \"target\"\nLINE 5:  UPDATE target SET balance = 0;\n                ^\n-- source and target names the same\nMERGE INTO target\nUSING target\nON tid = tid\nWHEN MATCHED THEN DO NOTHING;\npsql:include/ts_merge_query.sql:53: ERROR:  name \"target\" specified more than once\nDETAIL:  The name is used both as MERGE target table and data source.\n-- used in a CTE\nWITH foo AS (\n  MERGE INTO target USING source ON (true)\n  WHEN MATCHED THEN DELETE\n) SELECT * FROM foo;\npsql:include/ts_merge_query.sql:58: ERROR:  MERGE not supported in WITH query\nLINE 1: WITH foo AS (\n             ^\n-- used in COPY\nCOPY (\n  MERGE INTO target USING source ON (true)\n  WHEN MATCHED THEN DELETE\n) TO stdout;\npsql:include/ts_merge_query.sql:63: ERROR:  MERGE not supported in COPY\n-- unsupported relation types\n-- view\nCREATE VIEW tv AS SELECT * FROM target;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:72: ERROR:  cannot execute MERGE on relation \"tv\"\nDETAIL:  This operation is not supported for views.\nDROP VIEW tv;\n-- materialized view\nCREATE MATERIALIZED VIEW mv AS SELECT * FROM target;\nMERGE INTO mv t\nUSING source s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:81: ERROR:  cannot execute MERGE on relation \"mv\"\nDETAIL:  This operation is not supported for materialized views.\nDROP MATERIALIZED VIEW mv;\n-- permissions\nMERGE INTO target\nUSING source2\nON target.tid = source2.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:90: ERROR:  permission denied for table source2\nGRANT INSERT ON target TO regress_merge_no_privs;\nSET SESSION AUTHORIZATION regress_merge_no_privs;\nMERGE INTO target\nUSING source2\nON target.tid = source2.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:99: ERROR:  permission denied for table target\nGRANT UPDATE ON target2 TO regress_merge_privs;\nSET SESSION AUTHORIZATION regress_merge_privs;\nMERGE INTO target2\nUSING source\nON target2.tid = source.sid\nWHEN MATCHED THEN\n\tDELETE;\npsql:include/ts_merge_query.sql:108: ERROR:  permission denied for table target2\nMERGE INTO target2\nUSING source\nON target2.tid = source.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:114: ERROR:  permission denied for table target2\n-- check if the target can be accessed from source relation subquery; we should\n-- not be able to do so\nMERGE INTO target t\nUSING (SELECT * FROM source WHERE t.tid > sid) s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:122: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 2: USING (SELECT * FROM source WHERE t.tid > sid) s\n                                          ^\nHINT:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\n--\n-- initial tests\n--\n-- zero rows in source has no effect\nMERGE INTO target\nUSING source\nON target.tid = source.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\nROLLBACK;\n-- insert some non-matching source rows to work from\nINSERT INTO source VALUES (4, 40);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tDO NOTHING;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (5, 50);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- index plans\nINSERT INTO target SELECT generate_series(1000,2500), 0;\nALTER TABLE target ADD PRIMARY KEY (tid);\nANALYZE target;\nDELETE FROM target WHERE tid > 100;\nANALYZE target;\n-- insert some matching source rows to work from\nINSERT INTO source VALUES (2, 5);\nINSERT INTO source VALUES (3, 20);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n-- equivalent of an UPDATE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- equivalent of a DELETE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDO NOTHING;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, NULL);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- duplicate source row causes multiple target row update ERROR\nINSERT INTO source VALUES (2, 5);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:241: ERROR:  MERGE command cannot affect row a second time\nHINT:  Ensure that not more than one source row matches any one target row.\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\npsql:include/ts_merge_query.sql:249: ERROR:  MERGE command cannot affect row a second time\nHINT:  Ensure that not more than one source row matches any one target row.\nROLLBACK;\n-- remove duplicate MATCHED data from source data\nDELETE FROM source WHERE sid = 2;\nINSERT INTO source VALUES (2, 5);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n-- duplicate source row on INSERT should fail because of target_pkey\nINSERT INTO source VALUES (4, 40);\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n  INSERT VALUES (4, NULL);\npsql:include/ts_merge_query.sql:265: ERROR:  duplicate key value violates unique constraint \"target_pkey\"\nDETAIL:  Key (tid)=(4) already exists.\nSELECT * FROM target ORDER BY tid;\npsql:include/ts_merge_query.sql:266: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\n-- remove duplicate NOT MATCHED data from source data\nDELETE FROM source WHERE sid = 4;\nINSERT INTO source VALUES (4, 40);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n-- remove constraints\nalter table target drop CONSTRAINT target_pkey;\nalter table target alter column tid drop not null;\n-- multiple actions\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, 4)\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- should be equivalent\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, 4);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- column references\n-- do a simple equivalent of an UPDATE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance + s.delta;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- do a simple equivalent of an INSERT SELECT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- and again with duplicate source rows\nINSERT INTO source VALUES (5, 50);\nINSERT INTO source VALUES (5, 50);\n-- do a simple equivalent of an INSERT SELECT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n  INSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- removing duplicate source rows\nDELETE FROM source WHERE sid = 5;\n-- and again with explicitly identified column list\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- and again with a subtle error: referring to non-existent target row for NOT MATCHED\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (t.tid, s.delta);\npsql:include/ts_merge_query.sql:356: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 5:  INSERT (tid, balance) VALUES (t.tid, s.delta);\n                                       ^\nHINT:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\n-- and again with a constant ON clause\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON (SELECT true)\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (t.tid, s.delta);\npsql:include/ts_merge_query.sql:364: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 5:  INSERT (tid, balance) VALUES (t.tid, s.delta);\n                                       ^\nHINT:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\nSELECT * FROM target ORDER BY tid;\npsql:include/ts_merge_query.sql:365: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\n-- now the classic UPSERT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance + s.delta\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- this time with a FALSE condition\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND FALSE THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\n-- this time with an actual condition which returns false\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance <> 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\nBEGIN;\n-- and now with a condition which returns true\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\nROLLBACK;\n-- conditions in the NOT MATCHED clause can only refer to source columns\nBEGIN;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND t.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\npsql:include/ts_merge_query.sql:408: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 3: WHEN NOT MATCHED AND t.balance = 100 THEN\n                             ^\nHINT:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\nSELECT * FROM wq_target;\npsql:include/ts_merge_query.sql:409: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\n-- conditions in MATCHED clause can refer to both source and target\nSELECT * FROM wq_source;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND s.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n-- check if AND works\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 AND s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 AND s.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n-- check if OR works\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 OR s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 199 OR s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n-- check source-side whole-row references\nBEGIN;\nMERGE INTO wq_target t\nUSING wq_source s ON (t.tid = s.sid)\nWHEN matched and t = s or t.tid = s.sid THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nROLLBACK;\n-- check if subqueries work in the conditions?\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance > (SELECT max(balance) FROM target) THEN\n\tUPDATE SET balance = t.balance + s.balance;\n-- check if we can access system columns in the conditions\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.xmin = t.xmax THEN\n\tUPDATE SET balance = t.balance + s.balance;\npsql:include/ts_merge_query.sql:477: ERROR:  cannot use system column \"xmin\" in MERGE WHEN condition\nLINE 3: WHEN MATCHED AND t.xmin = t.xmax THEN\n                         ^\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.tableoid >= 0 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nDROP TABLE wq_target CASCADE;\nDROP TABLE wq_source;\n-- test triggers\ncreate or replace function merge_trigfunc () returns trigger\nlanguage plpgsql as\n$$\nDECLARE\n\tline text;\nBEGIN\n\tSELECT INTO line format('%s %s %s trigger%s',\n\t\tTG_WHEN, TG_OP, TG_LEVEL, CASE\n\t\tWHEN TG_OP = 'INSERT' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s', NEW)\n\t\tWHEN TG_OP = 'UPDATE' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s -> %s', OLD, NEW)\n\t\tWHEN TG_OP = 'DELETE' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s', OLD)\n\t\tEND);\n\n\tRAISE NOTICE '%', line;\n\tIF (TG_WHEN = 'BEFORE' AND TG_LEVEL = 'ROW') THEN\n\t\tIF (TG_OP = 'DELETE') THEN\n\t\t\tRETURN OLD;\n\t\tELSE\n\t\t\tRETURN NEW;\n\t\tEND IF;\n\tELSE\n\t\tRETURN NULL;\n\tEND IF;\nEND;\n$$;\nCREATE TRIGGER merge_bsi BEFORE INSERT ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bsu BEFORE UPDATE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bsd BEFORE DELETE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asi AFTER INSERT ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asu AFTER UPDATE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asd AFTER DELETE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bri BEFORE INSERT ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bru BEFORE UPDATE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_brd BEFORE DELETE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_ari AFTER INSERT ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_aru AFTER UPDATE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_ard AFTER DELETE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\n-- now the classic UPSERT, with a DELETE\nBEGIN;\nUPDATE target SET balance = 0 WHERE tid = 3;\n--EXPLAIN (ANALYZE ON, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF)\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND t.balance > s.delta THEN\n\tUPDATE SET balance = t.balance - s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- Test behavior of triggers that turn UPDATE/DELETE into no-ops\ncreate or replace function skip_merge_op() returns trigger\nlanguage plpgsql as\n$$\nBEGIN\n\tRETURN NULL;\nEND;\n$$;\nSELECT * FROM target full outer join source on (sid = tid);\ncreate trigger merge_skip BEFORE INSERT OR UPDATE or DELETE\n  ON target FOR EACH ROW EXECUTE FUNCTION skip_merge_op();\nDO $$\nDECLARE\n  result integer;\nBEGIN\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND s.sid = 3 THEN UPDATE SET balance = t.balance + s.delta\nWHEN MATCHED THEN DELETE\nWHEN NOT MATCHED THEN INSERT VALUES (sid, delta);\nIF FOUND THEN\n  RAISE NOTICE 'Found';\nELSE\n  RAISE NOTICE 'Not found';\nEND IF;\nGET DIAGNOSTICS result := ROW_COUNT;\nRAISE NOTICE 'ROW_COUNT = %', result;\nEND;\n$$;\nSELECT * FROM target FULL OUTER JOIN source ON (sid = tid);\nDROP TRIGGER merge_skip ON target;\nDROP FUNCTION skip_merge_op();\n-- test from PL/pgSQL\n-- make sure MERGE INTO isn't interpreted to mean returning variables like SELECT INTO\nBEGIN;\nDO LANGUAGE plpgsql $$\nBEGIN\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND t.balance > s.delta THEN\n\tUPDATE SET balance = t.balance - s.delta;\nEND;\n$$;\nROLLBACK;\n--source constants\nBEGIN;\nMERGE INTO target t\nUSING (SELECT 9 AS sid, 57 AS delta) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n--source query\nBEGIN;\nMERGE INTO target t\nUSING (SELECT sid, delta FROM source WHERE delta > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING (SELECT sid, delta as newname FROM source WHERE delta > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.newname);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n--self-merge\nBEGIN;\nMERGE INTO target t1\nUSING target t2\nON t1.tid = t2.tid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t1.balance + t2.balance\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (t2.tid, t2.balance);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING (SELECT tid as sid, balance as delta FROM target WHERE balance > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING\n(SELECT sid, max(delta) AS delta\n FROM source\n GROUP BY sid\n HAVING count(*) = 1\n ORDER BY sid ASC) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- plpgsql parameters and results\nBEGIN;\nCREATE FUNCTION merge_func (p_id integer, p_bal integer)\nRETURNS INTEGER\nLANGUAGE plpgsql\nAS $$\nDECLARE\n result integer;\nBEGIN\nMERGE INTO target t\nUSING (SELECT p_id AS sid) AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance - p_bal;\nIF FOUND THEN\n\tGET DIAGNOSTICS result := ROW_COUNT;\nEND IF;\nRETURN result;\nEND;\n$$;\nSELECT merge_func(3, 4);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- PREPARE\nBEGIN;\nprepare foom as merge into target t using (select 1 as sid) s on (t.tid = s.sid) when matched then update set balance = 1;\nexecute foom;\nROLLBACK;\nBEGIN;\nPREPARE foom2 (integer, integer) AS\nMERGE INTO target t\nUSING (SELECT 1) s\nON t.tid = $1\nWHEN MATCHED THEN\nUPDATE SET balance = $2;\n--EXPLAIN (ANALYZE ON, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF)\nexecute foom2 (1, 1);\nROLLBACK;\n-- subqueries in source relation\nBEGIN;\nMERGE INTO sq_target t\nUSING (SELECT * FROM sq_source) s\nON tid = sid\nWHEN MATCHED AND t.balance > delta THEN\n\tUPDATE SET balance = t.balance + delta;\nSELECT * FROM sq_target ORDER BY tid;\nROLLBACK;\n-- try a view\nCREATE VIEW v AS SELECT * FROM sq_source WHERE sid < 2;\nBEGIN;\nMERGE INTO sq_target\nUSING v\nON tid = sid\nWHEN MATCHED THEN\n    UPDATE SET balance = v.balance + delta;\nSELECT * FROM sq_target ORDER BY tid;\nROLLBACK;\n-- ambiguous reference to a column\nBEGIN;\nMERGE INTO sq_target\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\npsql:include/ts_merge_query.sql:732: ERROR:  column reference \"balance\" is ambiguous\nLINE 5:     UPDATE SET balance = balance + delta\n                                 ^\nROLLBACK;\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\nSELECT * FROM sq_target;\nROLLBACK;\n-- CTEs\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nWITH targq AS (\n\tSELECT * FROM v\n)\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\nROLLBACK;\n-- RETURNING\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE\nRETURNING *;\npsql:include/ts_merge_query.sql:778: ERROR:  syntax error at or near \"RETURNING\"\nLINE 10: RETURNING *;\n         ^\nROLLBACK;\n-- PG17-specific tests for views, returning and merge_action. These throw syntax errors for previous versions of Postgres.\n-- However, since the error is the same for both hypertables and regular tables, this test should still pass for previous versions.\n-- RETURNING\nINSERT INTO source(sid, delta) VALUES(1, 40), (5, 50);\nBEGIN;\nMERGE INTO target t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta);\nSELECT * from target;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 1 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta)\nRETURNING merge_action(), t.*;\npsql:include/ts_merge_query.sql:811: ERROR:  syntax error at or near \"RETURNING\"\nLINE 10: RETURNING merge_action(), t.*;\n         ^\nROLLBACK;\n-- Views\nCREATE VIEW tv AS SELECT * FROM target;\nBEGIN;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta);\npsql:include/ts_merge_query.sql:826: ERROR:  cannot execute MERGE on relation \"tv\"\nDETAIL:  This operation is not supported for views.\nSELECT * from tv;\npsql:include/ts_merge_query.sql:827: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nSELECT * from target; -- should also update the underlying table\npsql:include/ts_merge_query.sql:828: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\nBEGIN;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta)\nRETURNING merge_action(), t.*;\npsql:include/ts_merge_query.sql:841: ERROR:  syntax error at or near \"RETURNING\"\nLINE 10: RETURNING merge_action(), t.*;\n         ^\nROLLBACK;\nDROP VIEW tv;\nDELETE FROM source where sid in (1, 5);\n-- EXPLAIN\nCREATE TABLE ex_mtarget (a int, b int)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE ex_msource (a int, b int)\n  WITH (autovacuum_enabled=off);\nINSERT INTO ex_mtarget SELECT i, i*10 FROM generate_series(1,100,2) i;\nINSERT INTO ex_msource SELECT i, i*10 FROM generate_series(1,100,1) i;\nCREATE FUNCTION explain_merge(query text) RETURNS SETOF text\nLANGUAGE plpgsql AS\n$$\nDECLARE ln text;\nBEGIN\n    FOR ln IN\n        EXECUTE 'explain (analyze, timing off, summary off, buffers off, costs off) ' ||\n\t\t  query\n    LOOP\n        ln := regexp_replace(ln, '(Memory( Usage)?|Buckets|Batches): \\S*',  '\\1: xxx', 'g');\n        RETURN NEXT ln;\n    END LOOP;\nEND;\n$$;\n-- only updates\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED THEN\n\tUPDATE SET b = t.b + 1');\n-- only updates to selected tuples\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1');\n-- updates + deletes\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1\nWHEN MATCHED AND t.a >= 10 AND t.a <= 20 THEN\n\tDELETE');\n-- only inserts\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN NOT MATCHED AND s.a < 10 THEN\n\tINSERT VALUES (a, b)');\n-- all three\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1\nWHEN MATCHED AND t.a >= 30 AND t.a <= 40 THEN\n\tDELETE\nWHEN NOT MATCHED AND s.a < 20 THEN\n\tINSERT VALUES (a, b)');\n-- nothing\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a AND t.a < -1000\nWHEN MATCHED AND t.a < 10 THEN\n\tDO NOTHING');\nDROP TABLE ex_msource, ex_mtarget;\nDROP FUNCTION explain_merge(text);\n-- Subqueries\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED THEN\n    UPDATE SET balance = (SELECT count(*) FROM sq_target);\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND (SELECT count(*) > 0 FROM sq_target) THEN\n    UPDATE SET balance = 42;\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid AND (SELECT count(*) > 0 FROM sq_target)\nWHEN MATCHED THEN\n    UPDATE SET balance = 42;\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\n--  Test RETURNING with subqueries\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid AND (SELECT count(*) > 0 FROM sq_target)\nWHEN MATCHED THEN\n    UPDATE SET balance = 42\nRETURNING *;\npsql:include/ts_merge_query.sql:952: ERROR:  syntax error at or near \"RETURNING\"\nLINE 6: RETURNING *;\n        ^\nSELECT * FROM sq_target WHERE tid = 1;\npsql:include/ts_merge_query.sql:953: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\nDROP TABLE sq_target CASCADE;\nDROP TABLE sq_source CASCADE;\nCREATE TABLE pa_target (tid integer, balance float, val text)\n\tPARTITION BY LIST (tid);\nCREATE TABLE part1 PARTITION OF pa_target FOR VALUES IN (1,4)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part2 PARTITION OF pa_target FOR VALUES IN (2,5,6)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part3 PARTITION OF pa_target FOR VALUES IN (3,8,9)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part4 PARTITION OF pa_target DEFAULT\n  WITH (autovacuum_enabled=off);\nCREATE TABLE pa_source (sid integer, delta float);\n-- insert many rows to the source table\nINSERT INTO pa_source SELECT id, id * 10  FROM generate_series(1,14) AS id;\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT id, id * 100, 'initial' FROM generate_series(1,14,2) AS id;\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- same with a constant qual\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid AND tid = 1\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- try updating the partition key column\nBEGIN;\nCREATE FUNCTION merge_func() RETURNS integer LANGUAGE plpgsql AS $$\nDECLARE\n  result integer;\nBEGIN\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nIF FOUND THEN\n  GET DIAGNOSTICS result := ROW_COUNT;\nEND IF;\nRETURN result;\nEND;\n$$;\nSELECT merge_func();\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\nDROP TABLE pa_target CASCADE;\n-- The target table is partitioned in the same way, but this time by attaching\n-- partitions which have columns in different order, dropped columns etc.\nCREATE TABLE pa_target (tid integer, balance float, val text)\n\tPARTITION BY LIST (tid);\nCREATE TABLE part1 (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part2 (balance float, tid integer, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part3 (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part4 (extraid text, tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nALTER TABLE part4 DROP COLUMN extraid;\nALTER TABLE pa_target ATTACH PARTITION part1 FOR VALUES IN (1,4);\nALTER TABLE pa_target ATTACH PARTITION part2 FOR VALUES IN (2,5,6);\nALTER TABLE pa_target ATTACH PARTITION part3 FOR VALUES IN (3,8,9);\nALTER TABLE pa_target ATTACH PARTITION part4 DEFAULT;\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT id, id * 100, 'initial' FROM generate_series(1,14,2) AS id;\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- same with a constant qual\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid AND tid IN (1, 5)\n  WHEN MATCHED AND tid % 5 = 0 THEN DELETE\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- try updating the partition key column\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\nDROP TABLE pa_source;\nDROP TABLE pa_target CASCADE;\n-- Sub-partitioning\nCREATE TABLE pa_target (logts timestamp, tid integer, balance float, val text)\n\tPARTITION BY RANGE (logts);\nCREATE TABLE part_m01 PARTITION OF pa_target\n\tFOR VALUES FROM ('2017-01-01') TO ('2017-02-01')\n\tPARTITION BY LIST (tid);\nCREATE TABLE part_m01_odd PARTITION OF part_m01\n\tFOR VALUES IN (1,3,5,7,9) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m01_even PARTITION OF part_m01\n\tFOR VALUES IN (2,4,6,8) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m02 PARTITION OF pa_target\n\tFOR VALUES FROM ('2017-02-01') TO ('2017-03-01')\n\tPARTITION BY LIST (tid);\nCREATE TABLE part_m02_odd PARTITION OF part_m02\n\tFOR VALUES IN (1,3,5,7,9) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m02_even PARTITION OF part_m02\n\tFOR VALUES IN (2,4,6,8) WITH (autovacuum_enabled=off);\nCREATE TABLE pa_source (sid integer, delta float)\n  WITH (autovacuum_enabled=off);\n-- insert many rows to the source table\nINSERT INTO pa_source SELECT id, id * 10  FROM generate_series(1,14) AS id;\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT '2017-01-31', id, id * 100, 'initial' FROM generate_series(1,9,3) AS id;\nINSERT INTO pa_target SELECT '2017-02-28', id, id * 100, 'initial' FROM generate_series(2,9,3) AS id;\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING (SELECT '2017-01-15' AS slogts, * FROM pa_source WHERE sid < 10) s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (slogts::timestamp, sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\nDROP TABLE pa_source;\nDROP TABLE pa_target CASCADE;\n-- some complex joins on the source side\n-- source relation is an unaliased join\nMERGE INTO cj_target t\nUSING cj_source1 s1\n\tINNER JOIN cj_source2 s2 ON sid1 = sid2\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid1, delta, sval);\n-- try accessing columns from either side of the source join\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2 AND scat = 20\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid2, delta, sval)\nWHEN MATCHED THEN\n\tDELETE;\n-- some simple expressions in INSERT targetlist\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid2, delta + scat, sval)\nWHEN MATCHED THEN\n\tUPDATE SET val = val || ' updated by merge';\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2 AND scat = 20\nON t.tid = sid1\nWHEN MATCHED THEN\n\tUPDATE SET val = val || ' ' || delta::text;\nSELECT * FROM cj_target ORDER BY tid;\nALTER TABLE cj_source1 RENAME COLUMN sid1 TO sid;\nALTER TABLE cj_source2 RENAME COLUMN sid2 TO sid;\nTRUNCATE cj_target;\nMERGE INTO cj_target t\nUSING cj_source1 s1\n\tINNER JOIN cj_source2 s2 ON s1.sid = s2.sid\nON t.tid = s1.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s2.sid, delta, sval);\nDROP TABLE cj_source2, cj_source1;\nDROP TABLE cj_target CASCADE;\n-- Function scans\nMERGE INTO fs_target t\nUSING generate_series(1,100,1) AS id\nON t.a = id\nWHEN MATCHED THEN\n\tUPDATE SET b = b + id\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (id, -1);\nMERGE INTO fs_target t\nUSING generate_series(1,100,2) AS id\nON t.a = id\nWHEN MATCHED THEN\n\tUPDATE SET b = b + id, c = 'updated '|| id.*::text\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (id, -1, 'inserted ' || id.*::text);\nSELECT count(*) FROM fs_target;\nDROP TABLE fs_target CASCADE;\n-- SERIALIZABLE test\n-- handled in isolation tests\n-- Inheritance-based partitioning\nCREATE TABLE measurement (\n    city_id         int not null,\n    logdate         date not null,\n    peaktemp        int,\n    unitsales       int\n) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2006m02 (\n    CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )\n) INHERITS (measurement) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2006m03 (\n    CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )\n) INHERITS (measurement) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2007m01 (\n    filler          text,\n    peaktemp        int,\n    logdate         date not null,\n    city_id         int not null,\n    unitsales       int\n    CHECK ( logdate >= DATE '2007-01-01' AND logdate < DATE '2007-02-01')\n) WITH (autovacuum_enabled=off);\nALTER TABLE measurement_y2007m01 DROP COLUMN filler;\nALTER TABLE measurement_y2007m01 INHERIT measurement;\nINSERT INTO measurement VALUES (0, '2005-07-21', 5, 15);\nCREATE OR REPLACE FUNCTION measurement_insert_trigger()\nRETURNS TRIGGER AS $$\nBEGIN\n    IF ( NEW.logdate >= DATE '2006-02-01' AND\n         NEW.logdate < DATE '2006-03-01' ) THEN\n        INSERT INTO measurement_y2006m02 VALUES (NEW.*);\n    ELSIF ( NEW.logdate >= DATE '2006-03-01' AND\n            NEW.logdate < DATE '2006-04-01' ) THEN\n        INSERT INTO measurement_y2006m03 VALUES (NEW.*);\n    ELSIF ( NEW.logdate >= DATE '2007-01-01' AND\n            NEW.logdate < DATE '2007-02-01' ) THEN\n        INSERT INTO measurement_y2007m01 (city_id, logdate, peaktemp, unitsales)\n            VALUES (NEW.*);\n    ELSE\n        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';\n    END IF;\n    RETURN NULL;\nEND;\n$$ LANGUAGE plpgsql ;\nCREATE TRIGGER insert_measurement_trigger\n    BEFORE INSERT ON measurement\n    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();\nINSERT INTO measurement VALUES (1, '2006-02-10', 35, 10);\nINSERT INTO measurement VALUES (1, '2006-02-16', 45, 20);\nINSERT INTO measurement VALUES (1, '2006-03-17', 25, 10);\nINSERT INTO measurement VALUES (1, '2006-03-27', 15, 40);\nINSERT INTO measurement VALUES (1, '2007-01-15', 10, 10);\nINSERT INTO measurement VALUES (1, '2007-01-17', 10, 10);\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate;\nCREATE TABLE new_measurement (LIKE measurement) WITH (autovacuum_enabled=off);\nINSERT INTO new_measurement VALUES (0, '2005-07-21', 25, 20);\nINSERT INTO new_measurement VALUES (1, '2006-03-01', 20, 10);\nINSERT INTO new_measurement VALUES (1, '2006-02-16', 50, 10);\nINSERT INTO new_measurement VALUES (2, '2006-02-10', 20, 20);\nINSERT INTO new_measurement VALUES (1, '2006-03-27', NULL, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-17', NULL, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-15', 5, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-16', 10, 10);\nBEGIN;\nMERGE INTO ONLY measurement m\n USING new_measurement nm ON\n      (m.city_id = nm.city_id and m.logdate=nm.logdate)\nWHEN MATCHED AND nm.peaktemp IS NULL THEN DELETE\nWHEN MATCHED THEN UPDATE\n     SET peaktemp = greatest(m.peaktemp, nm.peaktemp),\n        unitsales = m.unitsales + coalesce(nm.unitsales, 0)\nWHEN NOT MATCHED THEN INSERT\n     (city_id, logdate, peaktemp, unitsales)\n   VALUES (city_id, logdate, peaktemp, unitsales);\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate, peaktemp;\nROLLBACK;\nMERGE into measurement m\n USING new_measurement nm ON\n      (m.city_id = nm.city_id and m.logdate=nm.logdate)\nWHEN MATCHED AND nm.peaktemp IS NULL THEN DELETE\nWHEN MATCHED THEN UPDATE\n     SET peaktemp = greatest(m.peaktemp, nm.peaktemp),\n        unitsales = m.unitsales + coalesce(nm.unitsales, 0)\nWHEN NOT MATCHED THEN INSERT\n     (city_id, logdate, peaktemp, unitsales)\n   VALUES (city_id, logdate, peaktemp, unitsales);\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate;\nBEGIN;\nMERGE INTO new_measurement nm\n USING ONLY measurement m ON\n      (nm.city_id = m.city_id and nm.logdate=m.logdate)\nWHEN MATCHED THEN DELETE;\nSELECT * FROM new_measurement ORDER BY city_id, logdate;\nROLLBACK;\nMERGE INTO new_measurement nm\n USING measurement m ON\n      (nm.city_id = m.city_id and nm.logdate=m.logdate)\nWHEN MATCHED THEN DELETE;\nSELECT * FROM new_measurement ORDER BY city_id, logdate;\nDROP TABLE measurement, new_measurement CASCADE;\nDROP FUNCTION measurement_insert_trigger();\nRESET SESSION AUTHORIZATION;\nDROP TABLE target CASCADE;\nDROP TABLE target2 CASCADE;\nDROP TABLE source, source2;\nDROP FUNCTION merge_trigfunc();\nREVOKE CREATE ON SCHEMA public FROM regress_merge_privs;\nDROP USER regress_merge_privs;\nDROP USER regress_merge_no_privs;\n\\o\n\\ir :TEST_LOAD_HT_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE USER regress_merge_privs;\nCREATE USER regress_merge_no_privs;\nDROP TABLE IF EXISTS target;\nDROP TABLE IF EXISTS source;\nCREATE TABLE target (tid integer, balance integer)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('target', 'tid', chunk_time_interval => 3);\n  create_hypertable  \n---------------------\n (1,public,target,t)\n\nCREATE TABLE source (sid integer, delta integer) -- no index\n  WITH (autovacuum_enabled=off);\nINSERT INTO target VALUES (1, 10);\nINSERT INTO target VALUES (2, 20);\nINSERT INTO target VALUES (3, 30);\nSELECT t.ctid is not null as matched, t.*, s.* FROM source s FULL OUTER JOIN target t ON s.sid = t.tid ORDER BY t.tid, s.sid;\n matched | tid | balance | sid | delta \n---------+-----+---------+-----+-------\n t       |   1 |      10 |     |      \n t       |   2 |      20 |     |      \n t       |   3 |      30 |     |      \n\nALTER TABLE target OWNER TO regress_merge_privs;\nALTER TABLE source OWNER TO regress_merge_privs;\nCREATE TABLE target2 (tid integer, balance integer)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('target2', 'tid', chunk_time_interval => 3);\n  create_hypertable   \n----------------------\n (2,public,target2,t)\n\nCREATE TABLE source2 (sid integer, delta integer)\n  WITH (autovacuum_enabled=off);\nALTER TABLE target2 OWNER TO regress_merge_no_privs;\nALTER TABLE source2 OWNER TO regress_merge_no_privs;\nGRANT INSERT ON target TO regress_merge_no_privs;\nGRANT CREATE ON SCHEMA public TO regress_merge_privs;\nSET SESSION AUTHORIZATION regress_merge_privs;\nCREATE TABLE sq_target (tid integer NOT NULL, balance integer)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('sq_target', 'tid', chunk_time_interval => 3);\n   create_hypertable    \n------------------------\n (3,public,sq_target,t)\n\nCREATE TABLE sq_source (delta integer, sid integer, balance integer DEFAULT 0)\n  WITH (autovacuum_enabled=off);\nINSERT INTO sq_target(tid, balance) VALUES (1,100), (2,200), (3,300);\nINSERT INTO sq_source(sid, delta) VALUES (1,10), (2,20), (4,40);\n-- conditional WHEN clause\nCREATE TABLE wq_target (tid integer not null, balance integer DEFAULT -1)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('wq_target', 'tid', chunk_time_interval => 3);\n   create_hypertable    \n------------------------\n (4,public,wq_target,t)\n\nCREATE TABLE wq_source (balance integer, sid integer)\n  WITH (autovacuum_enabled=off);\nINSERT INTO wq_source (sid, balance) VALUES (1, 100);\n-- some complex joins on the source side\nCREATE TABLE cj_target (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('cj_target', 'tid', chunk_time_interval => 3);\n   create_hypertable    \n------------------------\n (5,public,cj_target,t)\n\nCREATE TABLE cj_source1 (sid1 integer, scat integer, delta integer)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE cj_source2 (sid2 integer, sval text)\n  WITH (autovacuum_enabled=off);\nINSERT INTO cj_source1 VALUES (1, 10, 100);\nINSERT INTO cj_source1 VALUES (1, 20, 200);\nINSERT INTO cj_source1 VALUES (2, 20, 300);\nINSERT INTO cj_source1 VALUES (3, 10, 400);\nINSERT INTO cj_source2 VALUES (1, 'initial source2');\nINSERT INTO cj_source2 VALUES (2, 'initial source2');\nINSERT INTO cj_source2 VALUES (3, 'initial source2');\nCREATE TABLE fs_target (a int, b int, c text)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('fs_target', 'a', chunk_time_interval => 3);\n   create_hypertable    \n------------------------\n (6,public,fs_target,t)\n\n-- run tests on hypertable\n\\o :TEST_RESULTS_WITH_HYPERTABLE\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n--\n-- Errors\n--\nMERGE INTO target t RANDOMWORD\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:12: ERROR:  syntax error at or near \"RANDOMWORD\"\nLINE 1: MERGE INTO target t RANDOMWORD\n                            ^\n-- MATCHED/INSERT error\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:18: ERROR:  syntax error at or near \"INSERT\"\nLINE 5:  INSERT DEFAULT VALUES;\n         ^\n-- incorrectly specifying INTO target\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT INTO target DEFAULT VALUES;\npsql:include/ts_merge_query.sql:24: ERROR:  syntax error at or near \"INTO\"\nLINE 5:  INSERT INTO target DEFAULT VALUES;\n                ^\n-- Multiple VALUES clause\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (1,1), (2,2);\npsql:include/ts_merge_query.sql:30: ERROR:  syntax error at or near \",\"\nLINE 5:  INSERT VALUES (1,1), (2,2);\n                            ^\n-- SELECT query for INSERT\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT SELECT (1, 1);\npsql:include/ts_merge_query.sql:36: ERROR:  syntax error at or near \"SELECT\"\nLINE 5:  INSERT SELECT (1, 1);\n                ^\n-- NOT MATCHED/UPDATE\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:42: ERROR:  syntax error at or near \"UPDATE\"\nLINE 5:  UPDATE SET balance = 0;\n         ^\n-- UPDATE tablename\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE target SET balance = 0;\npsql:include/ts_merge_query.sql:48: ERROR:  syntax error at or near \"target\"\nLINE 5:  UPDATE target SET balance = 0;\n                ^\n-- source and target names the same\nMERGE INTO target\nUSING target\nON tid = tid\nWHEN MATCHED THEN DO NOTHING;\npsql:include/ts_merge_query.sql:53: ERROR:  name \"target\" specified more than once\nDETAIL:  The name is used both as MERGE target table and data source.\n-- used in a CTE\nWITH foo AS (\n  MERGE INTO target USING source ON (true)\n  WHEN MATCHED THEN DELETE\n) SELECT * FROM foo;\npsql:include/ts_merge_query.sql:58: ERROR:  MERGE not supported in WITH query\nLINE 1: WITH foo AS (\n             ^\n-- used in COPY\nCOPY (\n  MERGE INTO target USING source ON (true)\n  WHEN MATCHED THEN DELETE\n) TO stdout;\npsql:include/ts_merge_query.sql:63: ERROR:  MERGE not supported in COPY\n-- unsupported relation types\n-- view\nCREATE VIEW tv AS SELECT * FROM target;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:72: ERROR:  cannot execute MERGE on relation \"tv\"\nDETAIL:  This operation is not supported for views.\nDROP VIEW tv;\n-- materialized view\nCREATE MATERIALIZED VIEW mv AS SELECT * FROM target;\nMERGE INTO mv t\nUSING source s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:81: ERROR:  cannot execute MERGE on relation \"mv\"\nDETAIL:  This operation is not supported for materialized views.\nDROP MATERIALIZED VIEW mv;\n-- permissions\nMERGE INTO target\nUSING source2\nON target.tid = source2.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:90: ERROR:  permission denied for table source2\nGRANT INSERT ON target TO regress_merge_no_privs;\nSET SESSION AUTHORIZATION regress_merge_no_privs;\nMERGE INTO target\nUSING source2\nON target.tid = source2.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:99: ERROR:  permission denied for table target\nGRANT UPDATE ON target2 TO regress_merge_privs;\nSET SESSION AUTHORIZATION regress_merge_privs;\nMERGE INTO target2\nUSING source\nON target2.tid = source.sid\nWHEN MATCHED THEN\n\tDELETE;\npsql:include/ts_merge_query.sql:108: ERROR:  permission denied for table target2\nMERGE INTO target2\nUSING source\nON target2.tid = source.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:114: ERROR:  permission denied for table target2\n-- check if the target can be accessed from source relation subquery; we should\n-- not be able to do so\nMERGE INTO target t\nUSING (SELECT * FROM source WHERE t.tid > sid) s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:122: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 2: USING (SELECT * FROM source WHERE t.tid > sid) s\n                                          ^\nHINT:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\n--\n-- initial tests\n--\n-- zero rows in source has no effect\nMERGE INTO target\nUSING source\nON target.tid = source.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\nROLLBACK;\n-- insert some non-matching source rows to work from\nINSERT INTO source VALUES (4, 40);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tDO NOTHING;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (5, 50);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- index plans\nINSERT INTO target SELECT generate_series(1000,2500), 0;\nALTER TABLE target ADD PRIMARY KEY (tid);\nANALYZE target;\nDELETE FROM target WHERE tid > 100;\nANALYZE target;\n-- insert some matching source rows to work from\nINSERT INTO source VALUES (2, 5);\nINSERT INTO source VALUES (3, 20);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n-- equivalent of an UPDATE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- equivalent of a DELETE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDO NOTHING;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, NULL);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- duplicate source row causes multiple target row update ERROR\nINSERT INTO source VALUES (2, 5);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:241: ERROR:  MERGE command cannot affect row a second time\nHINT:  Ensure that not more than one source row matches any one target row.\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\npsql:include/ts_merge_query.sql:249: ERROR:  MERGE command cannot affect row a second time\nHINT:  Ensure that not more than one source row matches any one target row.\nROLLBACK;\n-- remove duplicate MATCHED data from source data\nDELETE FROM source WHERE sid = 2;\nINSERT INTO source VALUES (2, 5);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n-- duplicate source row on INSERT should fail because of target_pkey\nINSERT INTO source VALUES (4, 40);\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n  INSERT VALUES (4, NULL);\npsql:include/ts_merge_query.sql:265: ERROR:  duplicate key value violates unique constraint \"2_2_target_pkey\"\nDETAIL:  Key (tid)=(4) already exists.\nSELECT * FROM target ORDER BY tid;\npsql:include/ts_merge_query.sql:266: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\n-- remove duplicate NOT MATCHED data from source data\nDELETE FROM source WHERE sid = 4;\nINSERT INTO source VALUES (4, 40);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n-- remove constraints\nalter table target drop CONSTRAINT target_pkey;\nalter table target alter column tid drop not null;\npsql:include/ts_merge_query.sql:277: ERROR:  cannot drop not-null constraint from a time-partitioned column\n-- multiple actions\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, 4)\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- should be equivalent\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, 4);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- column references\n-- do a simple equivalent of an UPDATE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance + s.delta;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- do a simple equivalent of an INSERT SELECT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- and again with duplicate source rows\nINSERT INTO source VALUES (5, 50);\nINSERT INTO source VALUES (5, 50);\n-- do a simple equivalent of an INSERT SELECT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n  INSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- removing duplicate source rows\nDELETE FROM source WHERE sid = 5;\n-- and again with explicitly identified column list\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- and again with a subtle error: referring to non-existent target row for NOT MATCHED\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (t.tid, s.delta);\npsql:include/ts_merge_query.sql:356: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 5:  INSERT (tid, balance) VALUES (t.tid, s.delta);\n                                       ^\nHINT:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\n-- and again with a constant ON clause\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON (SELECT true)\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (t.tid, s.delta);\npsql:include/ts_merge_query.sql:364: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 5:  INSERT (tid, balance) VALUES (t.tid, s.delta);\n                                       ^\nHINT:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\nSELECT * FROM target ORDER BY tid;\npsql:include/ts_merge_query.sql:365: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\n-- now the classic UPSERT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance + s.delta\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- this time with a FALSE condition\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND FALSE THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\n-- this time with an actual condition which returns false\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance <> 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\nBEGIN;\n-- and now with a condition which returns true\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\nROLLBACK;\n-- conditions in the NOT MATCHED clause can only refer to source columns\nBEGIN;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND t.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\npsql:include/ts_merge_query.sql:408: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 3: WHEN NOT MATCHED AND t.balance = 100 THEN\n                             ^\nHINT:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\nSELECT * FROM wq_target;\npsql:include/ts_merge_query.sql:409: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\n-- conditions in MATCHED clause can refer to both source and target\nSELECT * FROM wq_source;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND s.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n-- check if AND works\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 AND s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 AND s.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n-- check if OR works\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 OR s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 199 OR s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n-- check source-side whole-row references\nBEGIN;\nMERGE INTO wq_target t\nUSING wq_source s ON (t.tid = s.sid)\nWHEN matched and t = s or t.tid = s.sid THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nROLLBACK;\n-- check if subqueries work in the conditions?\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance > (SELECT max(balance) FROM target) THEN\n\tUPDATE SET balance = t.balance + s.balance;\n-- check if we can access system columns in the conditions\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.xmin = t.xmax THEN\n\tUPDATE SET balance = t.balance + s.balance;\npsql:include/ts_merge_query.sql:477: ERROR:  cannot use system column \"xmin\" in MERGE WHEN condition\nLINE 3: WHEN MATCHED AND t.xmin = t.xmax THEN\n                         ^\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.tableoid >= 0 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nDROP TABLE wq_target CASCADE;\nDROP TABLE wq_source;\n-- test triggers\ncreate or replace function merge_trigfunc () returns trigger\nlanguage plpgsql as\n$$\nDECLARE\n\tline text;\nBEGIN\n\tSELECT INTO line format('%s %s %s trigger%s',\n\t\tTG_WHEN, TG_OP, TG_LEVEL, CASE\n\t\tWHEN TG_OP = 'INSERT' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s', NEW)\n\t\tWHEN TG_OP = 'UPDATE' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s -> %s', OLD, NEW)\n\t\tWHEN TG_OP = 'DELETE' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s', OLD)\n\t\tEND);\n\n\tRAISE NOTICE '%', line;\n\tIF (TG_WHEN = 'BEFORE' AND TG_LEVEL = 'ROW') THEN\n\t\tIF (TG_OP = 'DELETE') THEN\n\t\t\tRETURN OLD;\n\t\tELSE\n\t\t\tRETURN NEW;\n\t\tEND IF;\n\tELSE\n\t\tRETURN NULL;\n\tEND IF;\nEND;\n$$;\nCREATE TRIGGER merge_bsi BEFORE INSERT ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bsu BEFORE UPDATE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bsd BEFORE DELETE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asi AFTER INSERT ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asu AFTER UPDATE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asd AFTER DELETE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bri BEFORE INSERT ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bru BEFORE UPDATE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_brd BEFORE DELETE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_ari AFTER INSERT ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_aru AFTER UPDATE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_ard AFTER DELETE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\n-- now the classic UPSERT, with a DELETE\nBEGIN;\nUPDATE target SET balance = 0 WHERE tid = 3;\n--EXPLAIN (ANALYZE ON, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF)\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND t.balance > s.delta THEN\n\tUPDATE SET balance = t.balance - s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- Test behavior of triggers that turn UPDATE/DELETE into no-ops\ncreate or replace function skip_merge_op() returns trigger\nlanguage plpgsql as\n$$\nBEGIN\n\tRETURN NULL;\nEND;\n$$;\nSELECT * FROM target full outer join source on (sid = tid);\ncreate trigger merge_skip BEFORE INSERT OR UPDATE or DELETE\n  ON target FOR EACH ROW EXECUTE FUNCTION skip_merge_op();\nDO $$\nDECLARE\n  result integer;\nBEGIN\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND s.sid = 3 THEN UPDATE SET balance = t.balance + s.delta\nWHEN MATCHED THEN DELETE\nWHEN NOT MATCHED THEN INSERT VALUES (sid, delta);\nIF FOUND THEN\n  RAISE NOTICE 'Found';\nELSE\n  RAISE NOTICE 'Not found';\nEND IF;\nGET DIAGNOSTICS result := ROW_COUNT;\nRAISE NOTICE 'ROW_COUNT = %', result;\nEND;\n$$;\nSELECT * FROM target FULL OUTER JOIN source ON (sid = tid);\nDROP TRIGGER merge_skip ON target;\nDROP FUNCTION skip_merge_op();\n-- test from PL/pgSQL\n-- make sure MERGE INTO isn't interpreted to mean returning variables like SELECT INTO\nBEGIN;\nDO LANGUAGE plpgsql $$\nBEGIN\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND t.balance > s.delta THEN\n\tUPDATE SET balance = t.balance - s.delta;\nEND;\n$$;\nROLLBACK;\n--source constants\nBEGIN;\nMERGE INTO target t\nUSING (SELECT 9 AS sid, 57 AS delta) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n--source query\nBEGIN;\nMERGE INTO target t\nUSING (SELECT sid, delta FROM source WHERE delta > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING (SELECT sid, delta as newname FROM source WHERE delta > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.newname);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n--self-merge\nBEGIN;\nMERGE INTO target t1\nUSING target t2\nON t1.tid = t2.tid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t1.balance + t2.balance\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (t2.tid, t2.balance);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING (SELECT tid as sid, balance as delta FROM target WHERE balance > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING\n(SELECT sid, max(delta) AS delta\n FROM source\n GROUP BY sid\n HAVING count(*) = 1\n ORDER BY sid ASC) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- plpgsql parameters and results\nBEGIN;\nCREATE FUNCTION merge_func (p_id integer, p_bal integer)\nRETURNS INTEGER\nLANGUAGE plpgsql\nAS $$\nDECLARE\n result integer;\nBEGIN\nMERGE INTO target t\nUSING (SELECT p_id AS sid) AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance - p_bal;\nIF FOUND THEN\n\tGET DIAGNOSTICS result := ROW_COUNT;\nEND IF;\nRETURN result;\nEND;\n$$;\nSELECT merge_func(3, 4);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- PREPARE\nBEGIN;\nprepare foom as merge into target t using (select 1 as sid) s on (t.tid = s.sid) when matched then update set balance = 1;\npsql:include/ts_merge_query.sql:685: ERROR:  prepared statement \"foom\" already exists\nexecute foom;\npsql:include/ts_merge_query.sql:686: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\nBEGIN;\nPREPARE foom2 (integer, integer) AS\nMERGE INTO target t\nUSING (SELECT 1) s\nON t.tid = $1\nWHEN MATCHED THEN\nUPDATE SET balance = $2;\npsql:include/ts_merge_query.sql:695: ERROR:  prepared statement \"foom2\" already exists\n--EXPLAIN (ANALYZE ON, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF)\nexecute foom2 (1, 1);\npsql:include/ts_merge_query.sql:697: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\n-- subqueries in source relation\nBEGIN;\nMERGE INTO sq_target t\nUSING (SELECT * FROM sq_source) s\nON tid = sid\nWHEN MATCHED AND t.balance > delta THEN\n\tUPDATE SET balance = t.balance + delta;\nSELECT * FROM sq_target ORDER BY tid;\nROLLBACK;\n-- try a view\nCREATE VIEW v AS SELECT * FROM sq_source WHERE sid < 2;\nBEGIN;\nMERGE INTO sq_target\nUSING v\nON tid = sid\nWHEN MATCHED THEN\n    UPDATE SET balance = v.balance + delta;\nSELECT * FROM sq_target ORDER BY tid;\nROLLBACK;\n-- ambiguous reference to a column\nBEGIN;\nMERGE INTO sq_target\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\npsql:include/ts_merge_query.sql:732: ERROR:  column reference \"balance\" is ambiguous\nLINE 5:     UPDATE SET balance = balance + delta\n                                 ^\nROLLBACK;\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\nSELECT * FROM sq_target;\nROLLBACK;\n-- CTEs\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nWITH targq AS (\n\tSELECT * FROM v\n)\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\nROLLBACK;\n-- RETURNING\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE\nRETURNING *;\npsql:include/ts_merge_query.sql:778: ERROR:  syntax error at or near \"RETURNING\"\nLINE 10: RETURNING *;\n         ^\nROLLBACK;\n-- PG17-specific tests for views, returning and merge_action. These throw syntax errors for previous versions of Postgres.\n-- However, since the error is the same for both hypertables and regular tables, this test should still pass for previous versions.\n-- RETURNING\nINSERT INTO source(sid, delta) VALUES(1, 40), (5, 50);\nBEGIN;\nMERGE INTO target t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta);\nSELECT * from target;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 1 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta)\nRETURNING merge_action(), t.*;\npsql:include/ts_merge_query.sql:811: ERROR:  syntax error at or near \"RETURNING\"\nLINE 10: RETURNING merge_action(), t.*;\n         ^\nROLLBACK;\n-- Views\nCREATE VIEW tv AS SELECT * FROM target;\nBEGIN;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta);\npsql:include/ts_merge_query.sql:826: ERROR:  cannot execute MERGE on relation \"tv\"\nDETAIL:  This operation is not supported for views.\nSELECT * from tv;\npsql:include/ts_merge_query.sql:827: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nSELECT * from target; -- should also update the underlying table\npsql:include/ts_merge_query.sql:828: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\nBEGIN;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta)\nRETURNING merge_action(), t.*;\npsql:include/ts_merge_query.sql:841: ERROR:  syntax error at or near \"RETURNING\"\nLINE 10: RETURNING merge_action(), t.*;\n         ^\nROLLBACK;\nDROP VIEW tv;\nDELETE FROM source where sid in (1, 5);\n-- EXPLAIN\nCREATE TABLE ex_mtarget (a int, b int)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE ex_msource (a int, b int)\n  WITH (autovacuum_enabled=off);\nINSERT INTO ex_mtarget SELECT i, i*10 FROM generate_series(1,100,2) i;\nINSERT INTO ex_msource SELECT i, i*10 FROM generate_series(1,100,1) i;\nCREATE FUNCTION explain_merge(query text) RETURNS SETOF text\nLANGUAGE plpgsql AS\n$$\nDECLARE ln text;\nBEGIN\n    FOR ln IN\n        EXECUTE 'explain (analyze, timing off, summary off, buffers off, costs off) ' ||\n\t\t  query\n    LOOP\n        ln := regexp_replace(ln, '(Memory( Usage)?|Buckets|Batches): \\S*',  '\\1: xxx', 'g');\n        RETURN NEXT ln;\n    END LOOP;\nEND;\n$$;\n-- only updates\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED THEN\n\tUPDATE SET b = t.b + 1');\n-- only updates to selected tuples\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1');\n-- updates + deletes\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1\nWHEN MATCHED AND t.a >= 10 AND t.a <= 20 THEN\n\tDELETE');\n-- only inserts\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN NOT MATCHED AND s.a < 10 THEN\n\tINSERT VALUES (a, b)');\n-- all three\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1\nWHEN MATCHED AND t.a >= 30 AND t.a <= 40 THEN\n\tDELETE\nWHEN NOT MATCHED AND s.a < 20 THEN\n\tINSERT VALUES (a, b)');\n-- nothing\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a AND t.a < -1000\nWHEN MATCHED AND t.a < 10 THEN\n\tDO NOTHING');\nDROP TABLE ex_msource, ex_mtarget;\nDROP FUNCTION explain_merge(text);\n-- Subqueries\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED THEN\n    UPDATE SET balance = (SELECT count(*) FROM sq_target);\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND (SELECT count(*) > 0 FROM sq_target) THEN\n    UPDATE SET balance = 42;\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid AND (SELECT count(*) > 0 FROM sq_target)\nWHEN MATCHED THEN\n    UPDATE SET balance = 42;\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\n--  Test RETURNING with subqueries\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid AND (SELECT count(*) > 0 FROM sq_target)\nWHEN MATCHED THEN\n    UPDATE SET balance = 42\nRETURNING *;\npsql:include/ts_merge_query.sql:952: ERROR:  syntax error at or near \"RETURNING\"\nLINE 6: RETURNING *;\n        ^\nSELECT * FROM sq_target WHERE tid = 1;\npsql:include/ts_merge_query.sql:953: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\nDROP TABLE sq_target CASCADE;\nDROP TABLE sq_source CASCADE;\nCREATE TABLE pa_target (tid integer, balance float, val text)\n\tPARTITION BY LIST (tid);\nCREATE TABLE part1 PARTITION OF pa_target FOR VALUES IN (1,4)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part2 PARTITION OF pa_target FOR VALUES IN (2,5,6)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part3 PARTITION OF pa_target FOR VALUES IN (3,8,9)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part4 PARTITION OF pa_target DEFAULT\n  WITH (autovacuum_enabled=off);\nCREATE TABLE pa_source (sid integer, delta float);\n-- insert many rows to the source table\nINSERT INTO pa_source SELECT id, id * 10  FROM generate_series(1,14) AS id;\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT id, id * 100, 'initial' FROM generate_series(1,14,2) AS id;\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- same with a constant qual\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid AND tid = 1\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- try updating the partition key column\nBEGIN;\nCREATE FUNCTION merge_func() RETURNS integer LANGUAGE plpgsql AS $$\nDECLARE\n  result integer;\nBEGIN\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nIF FOUND THEN\n  GET DIAGNOSTICS result := ROW_COUNT;\nEND IF;\nRETURN result;\nEND;\n$$;\nSELECT merge_func();\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\nDROP TABLE pa_target CASCADE;\n-- The target table is partitioned in the same way, but this time by attaching\n-- partitions which have columns in different order, dropped columns etc.\nCREATE TABLE pa_target (tid integer, balance float, val text)\n\tPARTITION BY LIST (tid);\nCREATE TABLE part1 (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part2 (balance float, tid integer, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part3 (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part4 (extraid text, tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nALTER TABLE part4 DROP COLUMN extraid;\nALTER TABLE pa_target ATTACH PARTITION part1 FOR VALUES IN (1,4);\nALTER TABLE pa_target ATTACH PARTITION part2 FOR VALUES IN (2,5,6);\nALTER TABLE pa_target ATTACH PARTITION part3 FOR VALUES IN (3,8,9);\nALTER TABLE pa_target ATTACH PARTITION part4 DEFAULT;\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT id, id * 100, 'initial' FROM generate_series(1,14,2) AS id;\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- same with a constant qual\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid AND tid IN (1, 5)\n  WHEN MATCHED AND tid % 5 = 0 THEN DELETE\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- try updating the partition key column\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\nDROP TABLE pa_source;\nDROP TABLE pa_target CASCADE;\n-- Sub-partitioning\nCREATE TABLE pa_target (logts timestamp, tid integer, balance float, val text)\n\tPARTITION BY RANGE (logts);\nCREATE TABLE part_m01 PARTITION OF pa_target\n\tFOR VALUES FROM ('2017-01-01') TO ('2017-02-01')\n\tPARTITION BY LIST (tid);\nCREATE TABLE part_m01_odd PARTITION OF part_m01\n\tFOR VALUES IN (1,3,5,7,9) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m01_even PARTITION OF part_m01\n\tFOR VALUES IN (2,4,6,8) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m02 PARTITION OF pa_target\n\tFOR VALUES FROM ('2017-02-01') TO ('2017-03-01')\n\tPARTITION BY LIST (tid);\nCREATE TABLE part_m02_odd PARTITION OF part_m02\n\tFOR VALUES IN (1,3,5,7,9) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m02_even PARTITION OF part_m02\n\tFOR VALUES IN (2,4,6,8) WITH (autovacuum_enabled=off);\nCREATE TABLE pa_source (sid integer, delta float)\n  WITH (autovacuum_enabled=off);\n-- insert many rows to the source table\nINSERT INTO pa_source SELECT id, id * 10  FROM generate_series(1,14) AS id;\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT '2017-01-31', id, id * 100, 'initial' FROM generate_series(1,9,3) AS id;\nINSERT INTO pa_target SELECT '2017-02-28', id, id * 100, 'initial' FROM generate_series(2,9,3) AS id;\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING (SELECT '2017-01-15' AS slogts, * FROM pa_source WHERE sid < 10) s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (slogts::timestamp, sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\nDROP TABLE pa_source;\nDROP TABLE pa_target CASCADE;\n-- some complex joins on the source side\n-- source relation is an unaliased join\nMERGE INTO cj_target t\nUSING cj_source1 s1\n\tINNER JOIN cj_source2 s2 ON sid1 = sid2\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid1, delta, sval);\n-- try accessing columns from either side of the source join\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2 AND scat = 20\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid2, delta, sval)\nWHEN MATCHED THEN\n\tDELETE;\n-- some simple expressions in INSERT targetlist\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid2, delta + scat, sval)\nWHEN MATCHED THEN\n\tUPDATE SET val = val || ' updated by merge';\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2 AND scat = 20\nON t.tid = sid1\nWHEN MATCHED THEN\n\tUPDATE SET val = val || ' ' || delta::text;\nSELECT * FROM cj_target ORDER BY tid;\nALTER TABLE cj_source1 RENAME COLUMN sid1 TO sid;\nALTER TABLE cj_source2 RENAME COLUMN sid2 TO sid;\nTRUNCATE cj_target;\nMERGE INTO cj_target t\nUSING cj_source1 s1\n\tINNER JOIN cj_source2 s2 ON s1.sid = s2.sid\nON t.tid = s1.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s2.sid, delta, sval);\nDROP TABLE cj_source2, cj_source1;\nDROP TABLE cj_target CASCADE;\n-- Function scans\nMERGE INTO fs_target t\nUSING generate_series(1,100,1) AS id\nON t.a = id\nWHEN MATCHED THEN\n\tUPDATE SET b = b + id\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (id, -1);\nMERGE INTO fs_target t\nUSING generate_series(1,100,2) AS id\nON t.a = id\nWHEN MATCHED THEN\n\tUPDATE SET b = b + id, c = 'updated '|| id.*::text\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (id, -1, 'inserted ' || id.*::text);\nSELECT count(*) FROM fs_target;\nDROP TABLE fs_target CASCADE;\n-- SERIALIZABLE test\n-- handled in isolation tests\n-- Inheritance-based partitioning\nCREATE TABLE measurement (\n    city_id         int not null,\n    logdate         date not null,\n    peaktemp        int,\n    unitsales       int\n) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2006m02 (\n    CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )\n) INHERITS (measurement) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2006m03 (\n    CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )\n) INHERITS (measurement) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2007m01 (\n    filler          text,\n    peaktemp        int,\n    logdate         date not null,\n    city_id         int not null,\n    unitsales       int\n    CHECK ( logdate >= DATE '2007-01-01' AND logdate < DATE '2007-02-01')\n) WITH (autovacuum_enabled=off);\nALTER TABLE measurement_y2007m01 DROP COLUMN filler;\nALTER TABLE measurement_y2007m01 INHERIT measurement;\nINSERT INTO measurement VALUES (0, '2005-07-21', 5, 15);\nCREATE OR REPLACE FUNCTION measurement_insert_trigger()\nRETURNS TRIGGER AS $$\nBEGIN\n    IF ( NEW.logdate >= DATE '2006-02-01' AND\n         NEW.logdate < DATE '2006-03-01' ) THEN\n        INSERT INTO measurement_y2006m02 VALUES (NEW.*);\n    ELSIF ( NEW.logdate >= DATE '2006-03-01' AND\n            NEW.logdate < DATE '2006-04-01' ) THEN\n        INSERT INTO measurement_y2006m03 VALUES (NEW.*);\n    ELSIF ( NEW.logdate >= DATE '2007-01-01' AND\n            NEW.logdate < DATE '2007-02-01' ) THEN\n        INSERT INTO measurement_y2007m01 (city_id, logdate, peaktemp, unitsales)\n            VALUES (NEW.*);\n    ELSE\n        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';\n    END IF;\n    RETURN NULL;\nEND;\n$$ LANGUAGE plpgsql ;\nCREATE TRIGGER insert_measurement_trigger\n    BEFORE INSERT ON measurement\n    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();\nINSERT INTO measurement VALUES (1, '2006-02-10', 35, 10);\nINSERT INTO measurement VALUES (1, '2006-02-16', 45, 20);\nINSERT INTO measurement VALUES (1, '2006-03-17', 25, 10);\nINSERT INTO measurement VALUES (1, '2006-03-27', 15, 40);\nINSERT INTO measurement VALUES (1, '2007-01-15', 10, 10);\nINSERT INTO measurement VALUES (1, '2007-01-17', 10, 10);\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate;\nCREATE TABLE new_measurement (LIKE measurement) WITH (autovacuum_enabled=off);\nINSERT INTO new_measurement VALUES (0, '2005-07-21', 25, 20);\nINSERT INTO new_measurement VALUES (1, '2006-03-01', 20, 10);\nINSERT INTO new_measurement VALUES (1, '2006-02-16', 50, 10);\nINSERT INTO new_measurement VALUES (2, '2006-02-10', 20, 20);\nINSERT INTO new_measurement VALUES (1, '2006-03-27', NULL, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-17', NULL, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-15', 5, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-16', 10, 10);\nBEGIN;\nMERGE INTO ONLY measurement m\n USING new_measurement nm ON\n      (m.city_id = nm.city_id and m.logdate=nm.logdate)\nWHEN MATCHED AND nm.peaktemp IS NULL THEN DELETE\nWHEN MATCHED THEN UPDATE\n     SET peaktemp = greatest(m.peaktemp, nm.peaktemp),\n        unitsales = m.unitsales + coalesce(nm.unitsales, 0)\nWHEN NOT MATCHED THEN INSERT\n     (city_id, logdate, peaktemp, unitsales)\n   VALUES (city_id, logdate, peaktemp, unitsales);\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate, peaktemp;\nROLLBACK;\nMERGE into measurement m\n USING new_measurement nm ON\n      (m.city_id = nm.city_id and m.logdate=nm.logdate)\nWHEN MATCHED AND nm.peaktemp IS NULL THEN DELETE\nWHEN MATCHED THEN UPDATE\n     SET peaktemp = greatest(m.peaktemp, nm.peaktemp),\n        unitsales = m.unitsales + coalesce(nm.unitsales, 0)\nWHEN NOT MATCHED THEN INSERT\n     (city_id, logdate, peaktemp, unitsales)\n   VALUES (city_id, logdate, peaktemp, unitsales);\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate;\nBEGIN;\nMERGE INTO new_measurement nm\n USING ONLY measurement m ON\n      (nm.city_id = m.city_id and nm.logdate=m.logdate)\nWHEN MATCHED THEN DELETE;\nSELECT * FROM new_measurement ORDER BY city_id, logdate;\nROLLBACK;\nMERGE INTO new_measurement nm\n USING measurement m ON\n      (nm.city_id = m.city_id and nm.logdate=m.logdate)\nWHEN MATCHED THEN DELETE;\nSELECT * FROM new_measurement ORDER BY city_id, logdate;\nDROP TABLE measurement, new_measurement CASCADE;\nDROP FUNCTION measurement_insert_trigger();\nRESET SESSION AUTHORIZATION;\nDROP TABLE target CASCADE;\nDROP TABLE target2 CASCADE;\nDROP TABLE source, source2;\nDROP FUNCTION merge_trigfunc();\nREVOKE CREATE ON SCHEMA public FROM regress_merge_privs;\nDROP USER regress_merge_privs;\nDROP USER regress_merge_no_privs;\n\\o\n:DIFF_CMD\n"
  },
  {
    "path": "test/expected/ts_merge-16.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\\set ON_ERROR_STOP 0\n\\set VERBOSITY default\nSET client_min_messages TO error;\n\\set TEST_BASE_NAME ts_merge\nSELECT format('include/%s_load.sql', :'TEST_BASE_NAME') AS \"TEST_LOAD_NAME\",\n    format('include/%s_load_ht.sql', :'TEST_BASE_NAME') AS \"TEST_LOAD_HT_NAME\",\n    format('include/%s_query.sql', :'TEST_BASE_NAME') AS \"TEST_QUERY_NAME\",\n    format('%s/results/%s_ht_results.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') AS \"TEST_RESULTS_WITH_HYPERTABLE\",\n    format('%s/results/%s_results.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') AS \"TEST_RESULTS_WITH_NO_HYPERTABLE\" \\gset\nSELECT format('\\! diff -u --label \"Base pg table results\" --label \"Hypertable results\" %s %s', :'TEST_RESULTS_WITH_HYPERTABLE', :'TEST_RESULTS_WITH_NO_HYPERTABLE') AS \"DIFF_CMD\" \\gset\n\\ir :TEST_LOAD_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE USER regress_merge_privs;\nCREATE USER regress_merge_no_privs;\nDROP TABLE IF EXISTS target;\nDROP TABLE IF EXISTS source;\nCREATE TABLE target (tid integer, balance integer)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE source (sid integer, delta integer) -- no index\n  WITH (autovacuum_enabled=off);\nINSERT INTO target VALUES (1, 10);\nINSERT INTO target VALUES (2, 20);\nINSERT INTO target VALUES (3, 30);\nSELECT t.ctid is not null as matched, t.*, s.* FROM source s FULL OUTER JOIN target t ON s.sid = t.tid ORDER BY t.tid, s.sid;\n matched | tid | balance | sid | delta \n---------+-----+---------+-----+-------\n t       |   1 |      10 |     |      \n t       |   2 |      20 |     |      \n t       |   3 |      30 |     |      \n\nALTER TABLE target OWNER TO regress_merge_privs;\nALTER TABLE source OWNER TO regress_merge_privs;\nCREATE TABLE target2 (tid integer, balance integer)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE source2 (sid integer, delta integer)\n  WITH (autovacuum_enabled=off);\nALTER TABLE target2 OWNER TO regress_merge_no_privs;\nALTER TABLE source2 OWNER TO regress_merge_no_privs;\nGRANT INSERT ON target TO regress_merge_no_privs;\nGRANT CREATE ON SCHEMA public TO regress_merge_privs;\nSET SESSION AUTHORIZATION regress_merge_privs;\nCREATE TABLE sq_target (tid integer NOT NULL, balance integer)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE sq_source (delta integer, sid integer, balance integer DEFAULT 0)\n  WITH (autovacuum_enabled=off);\nINSERT INTO sq_target(tid, balance) VALUES (1,100), (2,200), (3,300);\nINSERT INTO sq_source(sid, delta) VALUES (1,10), (2,20), (4,40);\n-- conditional WHEN clause\nCREATE TABLE wq_target (tid integer not null, balance integer DEFAULT -1)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE wq_source (balance integer, sid integer)\n  WITH (autovacuum_enabled=off);\nINSERT INTO wq_source (sid, balance) VALUES (1, 100);\nCREATE TABLE cj_target (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE cj_source1 (sid1 integer, scat integer, delta integer)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE cj_source2 (sid2 integer, sval text)\n  WITH (autovacuum_enabled=off);\nINSERT INTO cj_source1 VALUES (1, 10, 100);\nINSERT INTO cj_source1 VALUES (1, 20, 200);\nINSERT INTO cj_source1 VALUES (2, 20, 300);\nINSERT INTO cj_source1 VALUES (3, 10, 400);\nINSERT INTO cj_source2 VALUES (1, 'initial source2');\nINSERT INTO cj_source2 VALUES (2, 'initial source2');\nINSERT INTO cj_source2 VALUES (3, 'initial source2');\nCREATE TABLE fs_target (a int, b int, c text)\n  WITH (autovacuum_enabled=off);\n-- run tests on normal table\n\\o :TEST_RESULTS_WITH_NO_HYPERTABLE\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n--\n-- Errors\n--\nMERGE INTO target t RANDOMWORD\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:12: ERROR:  syntax error at or near \"RANDOMWORD\"\nLINE 1: MERGE INTO target t RANDOMWORD\n                            ^\n-- MATCHED/INSERT error\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:18: ERROR:  syntax error at or near \"INSERT\"\nLINE 5:  INSERT DEFAULT VALUES;\n         ^\n-- incorrectly specifying INTO target\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT INTO target DEFAULT VALUES;\npsql:include/ts_merge_query.sql:24: ERROR:  syntax error at or near \"INTO\"\nLINE 5:  INSERT INTO target DEFAULT VALUES;\n                ^\n-- Multiple VALUES clause\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (1,1), (2,2);\npsql:include/ts_merge_query.sql:30: ERROR:  syntax error at or near \",\"\nLINE 5:  INSERT VALUES (1,1), (2,2);\n                            ^\n-- SELECT query for INSERT\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT SELECT (1, 1);\npsql:include/ts_merge_query.sql:36: ERROR:  syntax error at or near \"SELECT\"\nLINE 5:  INSERT SELECT (1, 1);\n                ^\n-- NOT MATCHED/UPDATE\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:42: ERROR:  syntax error at or near \"UPDATE\"\nLINE 5:  UPDATE SET balance = 0;\n         ^\n-- UPDATE tablename\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE target SET balance = 0;\npsql:include/ts_merge_query.sql:48: ERROR:  syntax error at or near \"target\"\nLINE 5:  UPDATE target SET balance = 0;\n                ^\n-- source and target names the same\nMERGE INTO target\nUSING target\nON tid = tid\nWHEN MATCHED THEN DO NOTHING;\npsql:include/ts_merge_query.sql:53: ERROR:  name \"target\" specified more than once\nDETAIL:  The name is used both as MERGE target table and data source.\n-- used in a CTE\nWITH foo AS (\n  MERGE INTO target USING source ON (true)\n  WHEN MATCHED THEN DELETE\n) SELECT * FROM foo;\npsql:include/ts_merge_query.sql:58: ERROR:  MERGE not supported in WITH query\nLINE 1: WITH foo AS (\n             ^\n-- used in COPY\nCOPY (\n  MERGE INTO target USING source ON (true)\n  WHEN MATCHED THEN DELETE\n) TO stdout;\npsql:include/ts_merge_query.sql:63: ERROR:  MERGE not supported in COPY\n-- unsupported relation types\n-- view\nCREATE VIEW tv AS SELECT * FROM target;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:72: ERROR:  cannot execute MERGE on relation \"tv\"\nDETAIL:  This operation is not supported for views.\nDROP VIEW tv;\n-- materialized view\nCREATE MATERIALIZED VIEW mv AS SELECT * FROM target;\nMERGE INTO mv t\nUSING source s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:81: ERROR:  cannot execute MERGE on relation \"mv\"\nDETAIL:  This operation is not supported for materialized views.\nDROP MATERIALIZED VIEW mv;\n-- permissions\nMERGE INTO target\nUSING source2\nON target.tid = source2.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:90: ERROR:  permission denied for table source2\nGRANT INSERT ON target TO regress_merge_no_privs;\nSET SESSION AUTHORIZATION regress_merge_no_privs;\nMERGE INTO target\nUSING source2\nON target.tid = source2.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:99: ERROR:  permission denied for table target\nGRANT UPDATE ON target2 TO regress_merge_privs;\nSET SESSION AUTHORIZATION regress_merge_privs;\nMERGE INTO target2\nUSING source\nON target2.tid = source.sid\nWHEN MATCHED THEN\n\tDELETE;\npsql:include/ts_merge_query.sql:108: ERROR:  permission denied for table target2\nMERGE INTO target2\nUSING source\nON target2.tid = source.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:114: ERROR:  permission denied for table target2\n-- check if the target can be accessed from source relation subquery; we should\n-- not be able to do so\nMERGE INTO target t\nUSING (SELECT * FROM source WHERE t.tid > sid) s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:122: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 2: USING (SELECT * FROM source WHERE t.tid > sid) s\n                                          ^\nDETAIL:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\n--\n-- initial tests\n--\n-- zero rows in source has no effect\nMERGE INTO target\nUSING source\nON target.tid = source.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\nROLLBACK;\n-- insert some non-matching source rows to work from\nINSERT INTO source VALUES (4, 40);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tDO NOTHING;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (5, 50);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- index plans\nINSERT INTO target SELECT generate_series(1000,2500), 0;\nALTER TABLE target ADD PRIMARY KEY (tid);\nANALYZE target;\nDELETE FROM target WHERE tid > 100;\nANALYZE target;\n-- insert some matching source rows to work from\nINSERT INTO source VALUES (2, 5);\nINSERT INTO source VALUES (3, 20);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n-- equivalent of an UPDATE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- equivalent of a DELETE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDO NOTHING;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, NULL);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- duplicate source row causes multiple target row update ERROR\nINSERT INTO source VALUES (2, 5);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:241: ERROR:  MERGE command cannot affect row a second time\nHINT:  Ensure that not more than one source row matches any one target row.\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\npsql:include/ts_merge_query.sql:249: ERROR:  MERGE command cannot affect row a second time\nHINT:  Ensure that not more than one source row matches any one target row.\nROLLBACK;\n-- remove duplicate MATCHED data from source data\nDELETE FROM source WHERE sid = 2;\nINSERT INTO source VALUES (2, 5);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n-- duplicate source row on INSERT should fail because of target_pkey\nINSERT INTO source VALUES (4, 40);\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n  INSERT VALUES (4, NULL);\npsql:include/ts_merge_query.sql:265: ERROR:  duplicate key value violates unique constraint \"target_pkey\"\nDETAIL:  Key (tid)=(4) already exists.\nSELECT * FROM target ORDER BY tid;\npsql:include/ts_merge_query.sql:266: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\n-- remove duplicate NOT MATCHED data from source data\nDELETE FROM source WHERE sid = 4;\nINSERT INTO source VALUES (4, 40);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n-- remove constraints\nalter table target drop CONSTRAINT target_pkey;\nalter table target alter column tid drop not null;\n-- multiple actions\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, 4)\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- should be equivalent\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, 4);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- column references\n-- do a simple equivalent of an UPDATE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance + s.delta;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- do a simple equivalent of an INSERT SELECT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- and again with duplicate source rows\nINSERT INTO source VALUES (5, 50);\nINSERT INTO source VALUES (5, 50);\n-- do a simple equivalent of an INSERT SELECT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n  INSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- removing duplicate source rows\nDELETE FROM source WHERE sid = 5;\n-- and again with explicitly identified column list\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- and again with a subtle error: referring to non-existent target row for NOT MATCHED\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (t.tid, s.delta);\npsql:include/ts_merge_query.sql:356: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 5:  INSERT (tid, balance) VALUES (t.tid, s.delta);\n                                       ^\nDETAIL:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\n-- and again with a constant ON clause\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON (SELECT true)\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (t.tid, s.delta);\npsql:include/ts_merge_query.sql:364: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 5:  INSERT (tid, balance) VALUES (t.tid, s.delta);\n                                       ^\nDETAIL:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\nSELECT * FROM target ORDER BY tid;\npsql:include/ts_merge_query.sql:365: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\n-- now the classic UPSERT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance + s.delta\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- this time with a FALSE condition\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND FALSE THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\n-- this time with an actual condition which returns false\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance <> 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\nBEGIN;\n-- and now with a condition which returns true\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\nROLLBACK;\n-- conditions in the NOT MATCHED clause can only refer to source columns\nBEGIN;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND t.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\npsql:include/ts_merge_query.sql:408: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 3: WHEN NOT MATCHED AND t.balance = 100 THEN\n                             ^\nDETAIL:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\nSELECT * FROM wq_target;\npsql:include/ts_merge_query.sql:409: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\n-- conditions in MATCHED clause can refer to both source and target\nSELECT * FROM wq_source;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND s.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n-- check if AND works\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 AND s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 AND s.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n-- check if OR works\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 OR s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 199 OR s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n-- check source-side whole-row references\nBEGIN;\nMERGE INTO wq_target t\nUSING wq_source s ON (t.tid = s.sid)\nWHEN matched and t = s or t.tid = s.sid THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nROLLBACK;\n-- check if subqueries work in the conditions?\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance > (SELECT max(balance) FROM target) THEN\n\tUPDATE SET balance = t.balance + s.balance;\n-- check if we can access system columns in the conditions\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.xmin = t.xmax THEN\n\tUPDATE SET balance = t.balance + s.balance;\npsql:include/ts_merge_query.sql:477: ERROR:  cannot use system column \"xmin\" in MERGE WHEN condition\nLINE 3: WHEN MATCHED AND t.xmin = t.xmax THEN\n                         ^\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.tableoid >= 0 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nDROP TABLE wq_target CASCADE;\nDROP TABLE wq_source;\n-- test triggers\ncreate or replace function merge_trigfunc () returns trigger\nlanguage plpgsql as\n$$\nDECLARE\n\tline text;\nBEGIN\n\tSELECT INTO line format('%s %s %s trigger%s',\n\t\tTG_WHEN, TG_OP, TG_LEVEL, CASE\n\t\tWHEN TG_OP = 'INSERT' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s', NEW)\n\t\tWHEN TG_OP = 'UPDATE' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s -> %s', OLD, NEW)\n\t\tWHEN TG_OP = 'DELETE' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s', OLD)\n\t\tEND);\n\n\tRAISE NOTICE '%', line;\n\tIF (TG_WHEN = 'BEFORE' AND TG_LEVEL = 'ROW') THEN\n\t\tIF (TG_OP = 'DELETE') THEN\n\t\t\tRETURN OLD;\n\t\tELSE\n\t\t\tRETURN NEW;\n\t\tEND IF;\n\tELSE\n\t\tRETURN NULL;\n\tEND IF;\nEND;\n$$;\nCREATE TRIGGER merge_bsi BEFORE INSERT ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bsu BEFORE UPDATE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bsd BEFORE DELETE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asi AFTER INSERT ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asu AFTER UPDATE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asd AFTER DELETE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bri BEFORE INSERT ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bru BEFORE UPDATE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_brd BEFORE DELETE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_ari AFTER INSERT ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_aru AFTER UPDATE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_ard AFTER DELETE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\n-- now the classic UPSERT, with a DELETE\nBEGIN;\nUPDATE target SET balance = 0 WHERE tid = 3;\n--EXPLAIN (ANALYZE ON, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF)\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND t.balance > s.delta THEN\n\tUPDATE SET balance = t.balance - s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- Test behavior of triggers that turn UPDATE/DELETE into no-ops\ncreate or replace function skip_merge_op() returns trigger\nlanguage plpgsql as\n$$\nBEGIN\n\tRETURN NULL;\nEND;\n$$;\nSELECT * FROM target full outer join source on (sid = tid);\ncreate trigger merge_skip BEFORE INSERT OR UPDATE or DELETE\n  ON target FOR EACH ROW EXECUTE FUNCTION skip_merge_op();\nDO $$\nDECLARE\n  result integer;\nBEGIN\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND s.sid = 3 THEN UPDATE SET balance = t.balance + s.delta\nWHEN MATCHED THEN DELETE\nWHEN NOT MATCHED THEN INSERT VALUES (sid, delta);\nIF FOUND THEN\n  RAISE NOTICE 'Found';\nELSE\n  RAISE NOTICE 'Not found';\nEND IF;\nGET DIAGNOSTICS result := ROW_COUNT;\nRAISE NOTICE 'ROW_COUNT = %', result;\nEND;\n$$;\nSELECT * FROM target FULL OUTER JOIN source ON (sid = tid);\nDROP TRIGGER merge_skip ON target;\nDROP FUNCTION skip_merge_op();\n-- test from PL/pgSQL\n-- make sure MERGE INTO isn't interpreted to mean returning variables like SELECT INTO\nBEGIN;\nDO LANGUAGE plpgsql $$\nBEGIN\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND t.balance > s.delta THEN\n\tUPDATE SET balance = t.balance - s.delta;\nEND;\n$$;\nROLLBACK;\n--source constants\nBEGIN;\nMERGE INTO target t\nUSING (SELECT 9 AS sid, 57 AS delta) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n--source query\nBEGIN;\nMERGE INTO target t\nUSING (SELECT sid, delta FROM source WHERE delta > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING (SELECT sid, delta as newname FROM source WHERE delta > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.newname);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n--self-merge\nBEGIN;\nMERGE INTO target t1\nUSING target t2\nON t1.tid = t2.tid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t1.balance + t2.balance\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (t2.tid, t2.balance);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING (SELECT tid as sid, balance as delta FROM target WHERE balance > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING\n(SELECT sid, max(delta) AS delta\n FROM source\n GROUP BY sid\n HAVING count(*) = 1\n ORDER BY sid ASC) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- plpgsql parameters and results\nBEGIN;\nCREATE FUNCTION merge_func (p_id integer, p_bal integer)\nRETURNS INTEGER\nLANGUAGE plpgsql\nAS $$\nDECLARE\n result integer;\nBEGIN\nMERGE INTO target t\nUSING (SELECT p_id AS sid) AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance - p_bal;\nIF FOUND THEN\n\tGET DIAGNOSTICS result := ROW_COUNT;\nEND IF;\nRETURN result;\nEND;\n$$;\nSELECT merge_func(3, 4);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- PREPARE\nBEGIN;\nprepare foom as merge into target t using (select 1 as sid) s on (t.tid = s.sid) when matched then update set balance = 1;\nexecute foom;\nROLLBACK;\nBEGIN;\nPREPARE foom2 (integer, integer) AS\nMERGE INTO target t\nUSING (SELECT 1) s\nON t.tid = $1\nWHEN MATCHED THEN\nUPDATE SET balance = $2;\n--EXPLAIN (ANALYZE ON, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF)\nexecute foom2 (1, 1);\nROLLBACK;\n-- subqueries in source relation\nBEGIN;\nMERGE INTO sq_target t\nUSING (SELECT * FROM sq_source) s\nON tid = sid\nWHEN MATCHED AND t.balance > delta THEN\n\tUPDATE SET balance = t.balance + delta;\nSELECT * FROM sq_target ORDER BY tid;\nROLLBACK;\n-- try a view\nCREATE VIEW v AS SELECT * FROM sq_source WHERE sid < 2;\nBEGIN;\nMERGE INTO sq_target\nUSING v\nON tid = sid\nWHEN MATCHED THEN\n    UPDATE SET balance = v.balance + delta;\nSELECT * FROM sq_target ORDER BY tid;\nROLLBACK;\n-- ambiguous reference to a column\nBEGIN;\nMERGE INTO sq_target\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\npsql:include/ts_merge_query.sql:732: ERROR:  column reference \"balance\" is ambiguous\nLINE 5:     UPDATE SET balance = balance + delta\n                                 ^\nROLLBACK;\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\nSELECT * FROM sq_target;\nROLLBACK;\n-- CTEs\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nWITH targq AS (\n\tSELECT * FROM v\n)\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\nROLLBACK;\n-- RETURNING\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE\nRETURNING *;\npsql:include/ts_merge_query.sql:778: ERROR:  syntax error at or near \"RETURNING\"\nLINE 10: RETURNING *;\n         ^\nROLLBACK;\n-- PG17-specific tests for views, returning and merge_action. These throw syntax errors for previous versions of Postgres.\n-- However, since the error is the same for both hypertables and regular tables, this test should still pass for previous versions.\n-- RETURNING\nINSERT INTO source(sid, delta) VALUES(1, 40), (5, 50);\nBEGIN;\nMERGE INTO target t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta);\nSELECT * from target;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 1 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta)\nRETURNING merge_action(), t.*;\npsql:include/ts_merge_query.sql:811: ERROR:  syntax error at or near \"RETURNING\"\nLINE 10: RETURNING merge_action(), t.*;\n         ^\nROLLBACK;\n-- Views\nCREATE VIEW tv AS SELECT * FROM target;\nBEGIN;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta);\npsql:include/ts_merge_query.sql:826: ERROR:  cannot execute MERGE on relation \"tv\"\nDETAIL:  This operation is not supported for views.\nSELECT * from tv;\npsql:include/ts_merge_query.sql:827: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nSELECT * from target; -- should also update the underlying table\npsql:include/ts_merge_query.sql:828: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\nBEGIN;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta)\nRETURNING merge_action(), t.*;\npsql:include/ts_merge_query.sql:841: ERROR:  syntax error at or near \"RETURNING\"\nLINE 10: RETURNING merge_action(), t.*;\n         ^\nROLLBACK;\nDROP VIEW tv;\nDELETE FROM source where sid in (1, 5);\n-- EXPLAIN\nCREATE TABLE ex_mtarget (a int, b int)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE ex_msource (a int, b int)\n  WITH (autovacuum_enabled=off);\nINSERT INTO ex_mtarget SELECT i, i*10 FROM generate_series(1,100,2) i;\nINSERT INTO ex_msource SELECT i, i*10 FROM generate_series(1,100,1) i;\nCREATE FUNCTION explain_merge(query text) RETURNS SETOF text\nLANGUAGE plpgsql AS\n$$\nDECLARE ln text;\nBEGIN\n    FOR ln IN\n        EXECUTE 'explain (analyze, timing off, summary off, buffers off, costs off) ' ||\n\t\t  query\n    LOOP\n        ln := regexp_replace(ln, '(Memory( Usage)?|Buckets|Batches): \\S*',  '\\1: xxx', 'g');\n        RETURN NEXT ln;\n    END LOOP;\nEND;\n$$;\n-- only updates\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED THEN\n\tUPDATE SET b = t.b + 1');\n-- only updates to selected tuples\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1');\n-- updates + deletes\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1\nWHEN MATCHED AND t.a >= 10 AND t.a <= 20 THEN\n\tDELETE');\n-- only inserts\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN NOT MATCHED AND s.a < 10 THEN\n\tINSERT VALUES (a, b)');\n-- all three\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1\nWHEN MATCHED AND t.a >= 30 AND t.a <= 40 THEN\n\tDELETE\nWHEN NOT MATCHED AND s.a < 20 THEN\n\tINSERT VALUES (a, b)');\n-- nothing\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a AND t.a < -1000\nWHEN MATCHED AND t.a < 10 THEN\n\tDO NOTHING');\nDROP TABLE ex_msource, ex_mtarget;\nDROP FUNCTION explain_merge(text);\n-- Subqueries\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED THEN\n    UPDATE SET balance = (SELECT count(*) FROM sq_target);\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND (SELECT count(*) > 0 FROM sq_target) THEN\n    UPDATE SET balance = 42;\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid AND (SELECT count(*) > 0 FROM sq_target)\nWHEN MATCHED THEN\n    UPDATE SET balance = 42;\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\n--  Test RETURNING with subqueries\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid AND (SELECT count(*) > 0 FROM sq_target)\nWHEN MATCHED THEN\n    UPDATE SET balance = 42\nRETURNING *;\npsql:include/ts_merge_query.sql:952: ERROR:  syntax error at or near \"RETURNING\"\nLINE 6: RETURNING *;\n        ^\nSELECT * FROM sq_target WHERE tid = 1;\npsql:include/ts_merge_query.sql:953: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\nDROP TABLE sq_target CASCADE;\nDROP TABLE sq_source CASCADE;\nCREATE TABLE pa_target (tid integer, balance float, val text)\n\tPARTITION BY LIST (tid);\nCREATE TABLE part1 PARTITION OF pa_target FOR VALUES IN (1,4)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part2 PARTITION OF pa_target FOR VALUES IN (2,5,6)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part3 PARTITION OF pa_target FOR VALUES IN (3,8,9)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part4 PARTITION OF pa_target DEFAULT\n  WITH (autovacuum_enabled=off);\nCREATE TABLE pa_source (sid integer, delta float);\n-- insert many rows to the source table\nINSERT INTO pa_source SELECT id, id * 10  FROM generate_series(1,14) AS id;\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT id, id * 100, 'initial' FROM generate_series(1,14,2) AS id;\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- same with a constant qual\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid AND tid = 1\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- try updating the partition key column\nBEGIN;\nCREATE FUNCTION merge_func() RETURNS integer LANGUAGE plpgsql AS $$\nDECLARE\n  result integer;\nBEGIN\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nIF FOUND THEN\n  GET DIAGNOSTICS result := ROW_COUNT;\nEND IF;\nRETURN result;\nEND;\n$$;\nSELECT merge_func();\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\nDROP TABLE pa_target CASCADE;\n-- The target table is partitioned in the same way, but this time by attaching\n-- partitions which have columns in different order, dropped columns etc.\nCREATE TABLE pa_target (tid integer, balance float, val text)\n\tPARTITION BY LIST (tid);\nCREATE TABLE part1 (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part2 (balance float, tid integer, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part3 (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part4 (extraid text, tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nALTER TABLE part4 DROP COLUMN extraid;\nALTER TABLE pa_target ATTACH PARTITION part1 FOR VALUES IN (1,4);\nALTER TABLE pa_target ATTACH PARTITION part2 FOR VALUES IN (2,5,6);\nALTER TABLE pa_target ATTACH PARTITION part3 FOR VALUES IN (3,8,9);\nALTER TABLE pa_target ATTACH PARTITION part4 DEFAULT;\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT id, id * 100, 'initial' FROM generate_series(1,14,2) AS id;\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- same with a constant qual\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid AND tid IN (1, 5)\n  WHEN MATCHED AND tid % 5 = 0 THEN DELETE\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- try updating the partition key column\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\nDROP TABLE pa_source;\nDROP TABLE pa_target CASCADE;\n-- Sub-partitioning\nCREATE TABLE pa_target (logts timestamp, tid integer, balance float, val text)\n\tPARTITION BY RANGE (logts);\nCREATE TABLE part_m01 PARTITION OF pa_target\n\tFOR VALUES FROM ('2017-01-01') TO ('2017-02-01')\n\tPARTITION BY LIST (tid);\nCREATE TABLE part_m01_odd PARTITION OF part_m01\n\tFOR VALUES IN (1,3,5,7,9) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m01_even PARTITION OF part_m01\n\tFOR VALUES IN (2,4,6,8) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m02 PARTITION OF pa_target\n\tFOR VALUES FROM ('2017-02-01') TO ('2017-03-01')\n\tPARTITION BY LIST (tid);\nCREATE TABLE part_m02_odd PARTITION OF part_m02\n\tFOR VALUES IN (1,3,5,7,9) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m02_even PARTITION OF part_m02\n\tFOR VALUES IN (2,4,6,8) WITH (autovacuum_enabled=off);\nCREATE TABLE pa_source (sid integer, delta float)\n  WITH (autovacuum_enabled=off);\n-- insert many rows to the source table\nINSERT INTO pa_source SELECT id, id * 10  FROM generate_series(1,14) AS id;\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT '2017-01-31', id, id * 100, 'initial' FROM generate_series(1,9,3) AS id;\nINSERT INTO pa_target SELECT '2017-02-28', id, id * 100, 'initial' FROM generate_series(2,9,3) AS id;\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING (SELECT '2017-01-15' AS slogts, * FROM pa_source WHERE sid < 10) s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (slogts::timestamp, sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\nDROP TABLE pa_source;\nDROP TABLE pa_target CASCADE;\n-- some complex joins on the source side\n-- source relation is an unaliased join\nMERGE INTO cj_target t\nUSING cj_source1 s1\n\tINNER JOIN cj_source2 s2 ON sid1 = sid2\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid1, delta, sval);\n-- try accessing columns from either side of the source join\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2 AND scat = 20\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid2, delta, sval)\nWHEN MATCHED THEN\n\tDELETE;\n-- some simple expressions in INSERT targetlist\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid2, delta + scat, sval)\nWHEN MATCHED THEN\n\tUPDATE SET val = val || ' updated by merge';\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2 AND scat = 20\nON t.tid = sid1\nWHEN MATCHED THEN\n\tUPDATE SET val = val || ' ' || delta::text;\nSELECT * FROM cj_target ORDER BY tid;\nALTER TABLE cj_source1 RENAME COLUMN sid1 TO sid;\nALTER TABLE cj_source2 RENAME COLUMN sid2 TO sid;\nTRUNCATE cj_target;\nMERGE INTO cj_target t\nUSING cj_source1 s1\n\tINNER JOIN cj_source2 s2 ON s1.sid = s2.sid\nON t.tid = s1.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s2.sid, delta, sval);\nDROP TABLE cj_source2, cj_source1;\nDROP TABLE cj_target CASCADE;\n-- Function scans\nMERGE INTO fs_target t\nUSING generate_series(1,100,1) AS id\nON t.a = id\nWHEN MATCHED THEN\n\tUPDATE SET b = b + id\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (id, -1);\nMERGE INTO fs_target t\nUSING generate_series(1,100,2) AS id\nON t.a = id\nWHEN MATCHED THEN\n\tUPDATE SET b = b + id, c = 'updated '|| id.*::text\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (id, -1, 'inserted ' || id.*::text);\nSELECT count(*) FROM fs_target;\nDROP TABLE fs_target CASCADE;\n-- SERIALIZABLE test\n-- handled in isolation tests\n-- Inheritance-based partitioning\nCREATE TABLE measurement (\n    city_id         int not null,\n    logdate         date not null,\n    peaktemp        int,\n    unitsales       int\n) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2006m02 (\n    CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )\n) INHERITS (measurement) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2006m03 (\n    CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )\n) INHERITS (measurement) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2007m01 (\n    filler          text,\n    peaktemp        int,\n    logdate         date not null,\n    city_id         int not null,\n    unitsales       int\n    CHECK ( logdate >= DATE '2007-01-01' AND logdate < DATE '2007-02-01')\n) WITH (autovacuum_enabled=off);\nALTER TABLE measurement_y2007m01 DROP COLUMN filler;\nALTER TABLE measurement_y2007m01 INHERIT measurement;\nINSERT INTO measurement VALUES (0, '2005-07-21', 5, 15);\nCREATE OR REPLACE FUNCTION measurement_insert_trigger()\nRETURNS TRIGGER AS $$\nBEGIN\n    IF ( NEW.logdate >= DATE '2006-02-01' AND\n         NEW.logdate < DATE '2006-03-01' ) THEN\n        INSERT INTO measurement_y2006m02 VALUES (NEW.*);\n    ELSIF ( NEW.logdate >= DATE '2006-03-01' AND\n            NEW.logdate < DATE '2006-04-01' ) THEN\n        INSERT INTO measurement_y2006m03 VALUES (NEW.*);\n    ELSIF ( NEW.logdate >= DATE '2007-01-01' AND\n            NEW.logdate < DATE '2007-02-01' ) THEN\n        INSERT INTO measurement_y2007m01 (city_id, logdate, peaktemp, unitsales)\n            VALUES (NEW.*);\n    ELSE\n        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';\n    END IF;\n    RETURN NULL;\nEND;\n$$ LANGUAGE plpgsql ;\nCREATE TRIGGER insert_measurement_trigger\n    BEFORE INSERT ON measurement\n    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();\nINSERT INTO measurement VALUES (1, '2006-02-10', 35, 10);\nINSERT INTO measurement VALUES (1, '2006-02-16', 45, 20);\nINSERT INTO measurement VALUES (1, '2006-03-17', 25, 10);\nINSERT INTO measurement VALUES (1, '2006-03-27', 15, 40);\nINSERT INTO measurement VALUES (1, '2007-01-15', 10, 10);\nINSERT INTO measurement VALUES (1, '2007-01-17', 10, 10);\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate;\nCREATE TABLE new_measurement (LIKE measurement) WITH (autovacuum_enabled=off);\nINSERT INTO new_measurement VALUES (0, '2005-07-21', 25, 20);\nINSERT INTO new_measurement VALUES (1, '2006-03-01', 20, 10);\nINSERT INTO new_measurement VALUES (1, '2006-02-16', 50, 10);\nINSERT INTO new_measurement VALUES (2, '2006-02-10', 20, 20);\nINSERT INTO new_measurement VALUES (1, '2006-03-27', NULL, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-17', NULL, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-15', 5, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-16', 10, 10);\nBEGIN;\nMERGE INTO ONLY measurement m\n USING new_measurement nm ON\n      (m.city_id = nm.city_id and m.logdate=nm.logdate)\nWHEN MATCHED AND nm.peaktemp IS NULL THEN DELETE\nWHEN MATCHED THEN UPDATE\n     SET peaktemp = greatest(m.peaktemp, nm.peaktemp),\n        unitsales = m.unitsales + coalesce(nm.unitsales, 0)\nWHEN NOT MATCHED THEN INSERT\n     (city_id, logdate, peaktemp, unitsales)\n   VALUES (city_id, logdate, peaktemp, unitsales);\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate, peaktemp;\nROLLBACK;\nMERGE into measurement m\n USING new_measurement nm ON\n      (m.city_id = nm.city_id and m.logdate=nm.logdate)\nWHEN MATCHED AND nm.peaktemp IS NULL THEN DELETE\nWHEN MATCHED THEN UPDATE\n     SET peaktemp = greatest(m.peaktemp, nm.peaktemp),\n        unitsales = m.unitsales + coalesce(nm.unitsales, 0)\nWHEN NOT MATCHED THEN INSERT\n     (city_id, logdate, peaktemp, unitsales)\n   VALUES (city_id, logdate, peaktemp, unitsales);\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate;\nBEGIN;\nMERGE INTO new_measurement nm\n USING ONLY measurement m ON\n      (nm.city_id = m.city_id and nm.logdate=m.logdate)\nWHEN MATCHED THEN DELETE;\nSELECT * FROM new_measurement ORDER BY city_id, logdate;\nROLLBACK;\nMERGE INTO new_measurement nm\n USING measurement m ON\n      (nm.city_id = m.city_id and nm.logdate=m.logdate)\nWHEN MATCHED THEN DELETE;\nSELECT * FROM new_measurement ORDER BY city_id, logdate;\nDROP TABLE measurement, new_measurement CASCADE;\nDROP FUNCTION measurement_insert_trigger();\nRESET SESSION AUTHORIZATION;\nDROP TABLE target CASCADE;\nDROP TABLE target2 CASCADE;\nDROP TABLE source, source2;\nDROP FUNCTION merge_trigfunc();\nREVOKE CREATE ON SCHEMA public FROM regress_merge_privs;\nDROP USER regress_merge_privs;\nDROP USER regress_merge_no_privs;\n\\o\n\\ir :TEST_LOAD_HT_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE USER regress_merge_privs;\nCREATE USER regress_merge_no_privs;\nDROP TABLE IF EXISTS target;\nDROP TABLE IF EXISTS source;\nCREATE TABLE target (tid integer, balance integer)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('target', 'tid', chunk_time_interval => 3);\n  create_hypertable  \n---------------------\n (1,public,target,t)\n\nCREATE TABLE source (sid integer, delta integer) -- no index\n  WITH (autovacuum_enabled=off);\nINSERT INTO target VALUES (1, 10);\nINSERT INTO target VALUES (2, 20);\nINSERT INTO target VALUES (3, 30);\nSELECT t.ctid is not null as matched, t.*, s.* FROM source s FULL OUTER JOIN target t ON s.sid = t.tid ORDER BY t.tid, s.sid;\n matched | tid | balance | sid | delta \n---------+-----+---------+-----+-------\n t       |   1 |      10 |     |      \n t       |   2 |      20 |     |      \n t       |   3 |      30 |     |      \n\nALTER TABLE target OWNER TO regress_merge_privs;\nALTER TABLE source OWNER TO regress_merge_privs;\nCREATE TABLE target2 (tid integer, balance integer)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('target2', 'tid', chunk_time_interval => 3);\n  create_hypertable   \n----------------------\n (2,public,target2,t)\n\nCREATE TABLE source2 (sid integer, delta integer)\n  WITH (autovacuum_enabled=off);\nALTER TABLE target2 OWNER TO regress_merge_no_privs;\nALTER TABLE source2 OWNER TO regress_merge_no_privs;\nGRANT INSERT ON target TO regress_merge_no_privs;\nGRANT CREATE ON SCHEMA public TO regress_merge_privs;\nSET SESSION AUTHORIZATION regress_merge_privs;\nCREATE TABLE sq_target (tid integer NOT NULL, balance integer)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('sq_target', 'tid', chunk_time_interval => 3);\n   create_hypertable    \n------------------------\n (3,public,sq_target,t)\n\nCREATE TABLE sq_source (delta integer, sid integer, balance integer DEFAULT 0)\n  WITH (autovacuum_enabled=off);\nINSERT INTO sq_target(tid, balance) VALUES (1,100), (2,200), (3,300);\nINSERT INTO sq_source(sid, delta) VALUES (1,10), (2,20), (4,40);\n-- conditional WHEN clause\nCREATE TABLE wq_target (tid integer not null, balance integer DEFAULT -1)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('wq_target', 'tid', chunk_time_interval => 3);\n   create_hypertable    \n------------------------\n (4,public,wq_target,t)\n\nCREATE TABLE wq_source (balance integer, sid integer)\n  WITH (autovacuum_enabled=off);\nINSERT INTO wq_source (sid, balance) VALUES (1, 100);\n-- some complex joins on the source side\nCREATE TABLE cj_target (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('cj_target', 'tid', chunk_time_interval => 3);\n   create_hypertable    \n------------------------\n (5,public,cj_target,t)\n\nCREATE TABLE cj_source1 (sid1 integer, scat integer, delta integer)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE cj_source2 (sid2 integer, sval text)\n  WITH (autovacuum_enabled=off);\nINSERT INTO cj_source1 VALUES (1, 10, 100);\nINSERT INTO cj_source1 VALUES (1, 20, 200);\nINSERT INTO cj_source1 VALUES (2, 20, 300);\nINSERT INTO cj_source1 VALUES (3, 10, 400);\nINSERT INTO cj_source2 VALUES (1, 'initial source2');\nINSERT INTO cj_source2 VALUES (2, 'initial source2');\nINSERT INTO cj_source2 VALUES (3, 'initial source2');\nCREATE TABLE fs_target (a int, b int, c text)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('fs_target', 'a', chunk_time_interval => 3);\n   create_hypertable    \n------------------------\n (6,public,fs_target,t)\n\n-- run tests on hypertable\n\\o :TEST_RESULTS_WITH_HYPERTABLE\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n--\n-- Errors\n--\nMERGE INTO target t RANDOMWORD\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:12: ERROR:  syntax error at or near \"RANDOMWORD\"\nLINE 1: MERGE INTO target t RANDOMWORD\n                            ^\n-- MATCHED/INSERT error\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:18: ERROR:  syntax error at or near \"INSERT\"\nLINE 5:  INSERT DEFAULT VALUES;\n         ^\n-- incorrectly specifying INTO target\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT INTO target DEFAULT VALUES;\npsql:include/ts_merge_query.sql:24: ERROR:  syntax error at or near \"INTO\"\nLINE 5:  INSERT INTO target DEFAULT VALUES;\n                ^\n-- Multiple VALUES clause\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (1,1), (2,2);\npsql:include/ts_merge_query.sql:30: ERROR:  syntax error at or near \",\"\nLINE 5:  INSERT VALUES (1,1), (2,2);\n                            ^\n-- SELECT query for INSERT\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT SELECT (1, 1);\npsql:include/ts_merge_query.sql:36: ERROR:  syntax error at or near \"SELECT\"\nLINE 5:  INSERT SELECT (1, 1);\n                ^\n-- NOT MATCHED/UPDATE\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:42: ERROR:  syntax error at or near \"UPDATE\"\nLINE 5:  UPDATE SET balance = 0;\n         ^\n-- UPDATE tablename\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE target SET balance = 0;\npsql:include/ts_merge_query.sql:48: ERROR:  syntax error at or near \"target\"\nLINE 5:  UPDATE target SET balance = 0;\n                ^\n-- source and target names the same\nMERGE INTO target\nUSING target\nON tid = tid\nWHEN MATCHED THEN DO NOTHING;\npsql:include/ts_merge_query.sql:53: ERROR:  name \"target\" specified more than once\nDETAIL:  The name is used both as MERGE target table and data source.\n-- used in a CTE\nWITH foo AS (\n  MERGE INTO target USING source ON (true)\n  WHEN MATCHED THEN DELETE\n) SELECT * FROM foo;\npsql:include/ts_merge_query.sql:58: ERROR:  MERGE not supported in WITH query\nLINE 1: WITH foo AS (\n             ^\n-- used in COPY\nCOPY (\n  MERGE INTO target USING source ON (true)\n  WHEN MATCHED THEN DELETE\n) TO stdout;\npsql:include/ts_merge_query.sql:63: ERROR:  MERGE not supported in COPY\n-- unsupported relation types\n-- view\nCREATE VIEW tv AS SELECT * FROM target;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:72: ERROR:  cannot execute MERGE on relation \"tv\"\nDETAIL:  This operation is not supported for views.\nDROP VIEW tv;\n-- materialized view\nCREATE MATERIALIZED VIEW mv AS SELECT * FROM target;\nMERGE INTO mv t\nUSING source s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:81: ERROR:  cannot execute MERGE on relation \"mv\"\nDETAIL:  This operation is not supported for materialized views.\nDROP MATERIALIZED VIEW mv;\n-- permissions\nMERGE INTO target\nUSING source2\nON target.tid = source2.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:90: ERROR:  permission denied for table source2\nGRANT INSERT ON target TO regress_merge_no_privs;\nSET SESSION AUTHORIZATION regress_merge_no_privs;\nMERGE INTO target\nUSING source2\nON target.tid = source2.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:99: ERROR:  permission denied for table target\nGRANT UPDATE ON target2 TO regress_merge_privs;\nSET SESSION AUTHORIZATION regress_merge_privs;\nMERGE INTO target2\nUSING source\nON target2.tid = source.sid\nWHEN MATCHED THEN\n\tDELETE;\npsql:include/ts_merge_query.sql:108: ERROR:  permission denied for table target2\nMERGE INTO target2\nUSING source\nON target2.tid = source.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:114: ERROR:  permission denied for table target2\n-- check if the target can be accessed from source relation subquery; we should\n-- not be able to do so\nMERGE INTO target t\nUSING (SELECT * FROM source WHERE t.tid > sid) s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:122: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 2: USING (SELECT * FROM source WHERE t.tid > sid) s\n                                          ^\nDETAIL:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\n--\n-- initial tests\n--\n-- zero rows in source has no effect\nMERGE INTO target\nUSING source\nON target.tid = source.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\nROLLBACK;\n-- insert some non-matching source rows to work from\nINSERT INTO source VALUES (4, 40);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tDO NOTHING;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (5, 50);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- index plans\nINSERT INTO target SELECT generate_series(1000,2500), 0;\nALTER TABLE target ADD PRIMARY KEY (tid);\nANALYZE target;\nDELETE FROM target WHERE tid > 100;\nANALYZE target;\n-- insert some matching source rows to work from\nINSERT INTO source VALUES (2, 5);\nINSERT INTO source VALUES (3, 20);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n-- equivalent of an UPDATE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- equivalent of a DELETE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDO NOTHING;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, NULL);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- duplicate source row causes multiple target row update ERROR\nINSERT INTO source VALUES (2, 5);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:241: ERROR:  MERGE command cannot affect row a second time\nHINT:  Ensure that not more than one source row matches any one target row.\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\npsql:include/ts_merge_query.sql:249: ERROR:  MERGE command cannot affect row a second time\nHINT:  Ensure that not more than one source row matches any one target row.\nROLLBACK;\n-- remove duplicate MATCHED data from source data\nDELETE FROM source WHERE sid = 2;\nINSERT INTO source VALUES (2, 5);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n-- duplicate source row on INSERT should fail because of target_pkey\nINSERT INTO source VALUES (4, 40);\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n  INSERT VALUES (4, NULL);\npsql:include/ts_merge_query.sql:265: ERROR:  duplicate key value violates unique constraint \"2_2_target_pkey\"\nDETAIL:  Key (tid)=(4) already exists.\nSELECT * FROM target ORDER BY tid;\npsql:include/ts_merge_query.sql:266: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\n-- remove duplicate NOT MATCHED data from source data\nDELETE FROM source WHERE sid = 4;\nINSERT INTO source VALUES (4, 40);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n-- remove constraints\nalter table target drop CONSTRAINT target_pkey;\nalter table target alter column tid drop not null;\npsql:include/ts_merge_query.sql:277: ERROR:  cannot drop not-null constraint from a time-partitioned column\n-- multiple actions\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, 4)\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- should be equivalent\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, 4);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- column references\n-- do a simple equivalent of an UPDATE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance + s.delta;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- do a simple equivalent of an INSERT SELECT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- and again with duplicate source rows\nINSERT INTO source VALUES (5, 50);\nINSERT INTO source VALUES (5, 50);\n-- do a simple equivalent of an INSERT SELECT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n  INSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- removing duplicate source rows\nDELETE FROM source WHERE sid = 5;\n-- and again with explicitly identified column list\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- and again with a subtle error: referring to non-existent target row for NOT MATCHED\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (t.tid, s.delta);\npsql:include/ts_merge_query.sql:356: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 5:  INSERT (tid, balance) VALUES (t.tid, s.delta);\n                                       ^\nDETAIL:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\n-- and again with a constant ON clause\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON (SELECT true)\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (t.tid, s.delta);\npsql:include/ts_merge_query.sql:364: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 5:  INSERT (tid, balance) VALUES (t.tid, s.delta);\n                                       ^\nDETAIL:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\nSELECT * FROM target ORDER BY tid;\npsql:include/ts_merge_query.sql:365: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\n-- now the classic UPSERT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance + s.delta\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- this time with a FALSE condition\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND FALSE THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\n-- this time with an actual condition which returns false\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance <> 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\nBEGIN;\n-- and now with a condition which returns true\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\nROLLBACK;\n-- conditions in the NOT MATCHED clause can only refer to source columns\nBEGIN;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND t.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\npsql:include/ts_merge_query.sql:408: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 3: WHEN NOT MATCHED AND t.balance = 100 THEN\n                             ^\nDETAIL:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\nSELECT * FROM wq_target;\npsql:include/ts_merge_query.sql:409: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\n-- conditions in MATCHED clause can refer to both source and target\nSELECT * FROM wq_source;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND s.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n-- check if AND works\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 AND s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 AND s.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n-- check if OR works\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 OR s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 199 OR s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n-- check source-side whole-row references\nBEGIN;\nMERGE INTO wq_target t\nUSING wq_source s ON (t.tid = s.sid)\nWHEN matched and t = s or t.tid = s.sid THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nROLLBACK;\n-- check if subqueries work in the conditions?\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance > (SELECT max(balance) FROM target) THEN\n\tUPDATE SET balance = t.balance + s.balance;\n-- check if we can access system columns in the conditions\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.xmin = t.xmax THEN\n\tUPDATE SET balance = t.balance + s.balance;\npsql:include/ts_merge_query.sql:477: ERROR:  cannot use system column \"xmin\" in MERGE WHEN condition\nLINE 3: WHEN MATCHED AND t.xmin = t.xmax THEN\n                         ^\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.tableoid >= 0 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nDROP TABLE wq_target CASCADE;\nDROP TABLE wq_source;\n-- test triggers\ncreate or replace function merge_trigfunc () returns trigger\nlanguage plpgsql as\n$$\nDECLARE\n\tline text;\nBEGIN\n\tSELECT INTO line format('%s %s %s trigger%s',\n\t\tTG_WHEN, TG_OP, TG_LEVEL, CASE\n\t\tWHEN TG_OP = 'INSERT' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s', NEW)\n\t\tWHEN TG_OP = 'UPDATE' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s -> %s', OLD, NEW)\n\t\tWHEN TG_OP = 'DELETE' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s', OLD)\n\t\tEND);\n\n\tRAISE NOTICE '%', line;\n\tIF (TG_WHEN = 'BEFORE' AND TG_LEVEL = 'ROW') THEN\n\t\tIF (TG_OP = 'DELETE') THEN\n\t\t\tRETURN OLD;\n\t\tELSE\n\t\t\tRETURN NEW;\n\t\tEND IF;\n\tELSE\n\t\tRETURN NULL;\n\tEND IF;\nEND;\n$$;\nCREATE TRIGGER merge_bsi BEFORE INSERT ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bsu BEFORE UPDATE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bsd BEFORE DELETE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asi AFTER INSERT ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asu AFTER UPDATE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asd AFTER DELETE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bri BEFORE INSERT ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bru BEFORE UPDATE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_brd BEFORE DELETE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_ari AFTER INSERT ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_aru AFTER UPDATE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_ard AFTER DELETE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\n-- now the classic UPSERT, with a DELETE\nBEGIN;\nUPDATE target SET balance = 0 WHERE tid = 3;\n--EXPLAIN (ANALYZE ON, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF)\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND t.balance > s.delta THEN\n\tUPDATE SET balance = t.balance - s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- Test behavior of triggers that turn UPDATE/DELETE into no-ops\ncreate or replace function skip_merge_op() returns trigger\nlanguage plpgsql as\n$$\nBEGIN\n\tRETURN NULL;\nEND;\n$$;\nSELECT * FROM target full outer join source on (sid = tid);\ncreate trigger merge_skip BEFORE INSERT OR UPDATE or DELETE\n  ON target FOR EACH ROW EXECUTE FUNCTION skip_merge_op();\nDO $$\nDECLARE\n  result integer;\nBEGIN\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND s.sid = 3 THEN UPDATE SET balance = t.balance + s.delta\nWHEN MATCHED THEN DELETE\nWHEN NOT MATCHED THEN INSERT VALUES (sid, delta);\nIF FOUND THEN\n  RAISE NOTICE 'Found';\nELSE\n  RAISE NOTICE 'Not found';\nEND IF;\nGET DIAGNOSTICS result := ROW_COUNT;\nRAISE NOTICE 'ROW_COUNT = %', result;\nEND;\n$$;\nSELECT * FROM target FULL OUTER JOIN source ON (sid = tid);\nDROP TRIGGER merge_skip ON target;\nDROP FUNCTION skip_merge_op();\n-- test from PL/pgSQL\n-- make sure MERGE INTO isn't interpreted to mean returning variables like SELECT INTO\nBEGIN;\nDO LANGUAGE plpgsql $$\nBEGIN\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND t.balance > s.delta THEN\n\tUPDATE SET balance = t.balance - s.delta;\nEND;\n$$;\nROLLBACK;\n--source constants\nBEGIN;\nMERGE INTO target t\nUSING (SELECT 9 AS sid, 57 AS delta) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n--source query\nBEGIN;\nMERGE INTO target t\nUSING (SELECT sid, delta FROM source WHERE delta > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING (SELECT sid, delta as newname FROM source WHERE delta > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.newname);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n--self-merge\nBEGIN;\nMERGE INTO target t1\nUSING target t2\nON t1.tid = t2.tid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t1.balance + t2.balance\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (t2.tid, t2.balance);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING (SELECT tid as sid, balance as delta FROM target WHERE balance > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING\n(SELECT sid, max(delta) AS delta\n FROM source\n GROUP BY sid\n HAVING count(*) = 1\n ORDER BY sid ASC) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- plpgsql parameters and results\nBEGIN;\nCREATE FUNCTION merge_func (p_id integer, p_bal integer)\nRETURNS INTEGER\nLANGUAGE plpgsql\nAS $$\nDECLARE\n result integer;\nBEGIN\nMERGE INTO target t\nUSING (SELECT p_id AS sid) AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance - p_bal;\nIF FOUND THEN\n\tGET DIAGNOSTICS result := ROW_COUNT;\nEND IF;\nRETURN result;\nEND;\n$$;\nSELECT merge_func(3, 4);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- PREPARE\nBEGIN;\nprepare foom as merge into target t using (select 1 as sid) s on (t.tid = s.sid) when matched then update set balance = 1;\npsql:include/ts_merge_query.sql:685: ERROR:  prepared statement \"foom\" already exists\nexecute foom;\npsql:include/ts_merge_query.sql:686: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\nBEGIN;\nPREPARE foom2 (integer, integer) AS\nMERGE INTO target t\nUSING (SELECT 1) s\nON t.tid = $1\nWHEN MATCHED THEN\nUPDATE SET balance = $2;\npsql:include/ts_merge_query.sql:695: ERROR:  prepared statement \"foom2\" already exists\n--EXPLAIN (ANALYZE ON, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF)\nexecute foom2 (1, 1);\npsql:include/ts_merge_query.sql:697: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\n-- subqueries in source relation\nBEGIN;\nMERGE INTO sq_target t\nUSING (SELECT * FROM sq_source) s\nON tid = sid\nWHEN MATCHED AND t.balance > delta THEN\n\tUPDATE SET balance = t.balance + delta;\nSELECT * FROM sq_target ORDER BY tid;\nROLLBACK;\n-- try a view\nCREATE VIEW v AS SELECT * FROM sq_source WHERE sid < 2;\nBEGIN;\nMERGE INTO sq_target\nUSING v\nON tid = sid\nWHEN MATCHED THEN\n    UPDATE SET balance = v.balance + delta;\nSELECT * FROM sq_target ORDER BY tid;\nROLLBACK;\n-- ambiguous reference to a column\nBEGIN;\nMERGE INTO sq_target\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\npsql:include/ts_merge_query.sql:732: ERROR:  column reference \"balance\" is ambiguous\nLINE 5:     UPDATE SET balance = balance + delta\n                                 ^\nROLLBACK;\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\nSELECT * FROM sq_target;\nROLLBACK;\n-- CTEs\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nWITH targq AS (\n\tSELECT * FROM v\n)\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\nROLLBACK;\n-- RETURNING\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE\nRETURNING *;\npsql:include/ts_merge_query.sql:778: ERROR:  syntax error at or near \"RETURNING\"\nLINE 10: RETURNING *;\n         ^\nROLLBACK;\n-- PG17-specific tests for views, returning and merge_action. These throw syntax errors for previous versions of Postgres.\n-- However, since the error is the same for both hypertables and regular tables, this test should still pass for previous versions.\n-- RETURNING\nINSERT INTO source(sid, delta) VALUES(1, 40), (5, 50);\nBEGIN;\nMERGE INTO target t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta);\nSELECT * from target;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 1 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta)\nRETURNING merge_action(), t.*;\npsql:include/ts_merge_query.sql:811: ERROR:  syntax error at or near \"RETURNING\"\nLINE 10: RETURNING merge_action(), t.*;\n         ^\nROLLBACK;\n-- Views\nCREATE VIEW tv AS SELECT * FROM target;\nBEGIN;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta);\npsql:include/ts_merge_query.sql:826: ERROR:  cannot execute MERGE on relation \"tv\"\nDETAIL:  This operation is not supported for views.\nSELECT * from tv;\npsql:include/ts_merge_query.sql:827: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nSELECT * from target; -- should also update the underlying table\npsql:include/ts_merge_query.sql:828: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\nBEGIN;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta)\nRETURNING merge_action(), t.*;\npsql:include/ts_merge_query.sql:841: ERROR:  syntax error at or near \"RETURNING\"\nLINE 10: RETURNING merge_action(), t.*;\n         ^\nROLLBACK;\nDROP VIEW tv;\nDELETE FROM source where sid in (1, 5);\n-- EXPLAIN\nCREATE TABLE ex_mtarget (a int, b int)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE ex_msource (a int, b int)\n  WITH (autovacuum_enabled=off);\nINSERT INTO ex_mtarget SELECT i, i*10 FROM generate_series(1,100,2) i;\nINSERT INTO ex_msource SELECT i, i*10 FROM generate_series(1,100,1) i;\nCREATE FUNCTION explain_merge(query text) RETURNS SETOF text\nLANGUAGE plpgsql AS\n$$\nDECLARE ln text;\nBEGIN\n    FOR ln IN\n        EXECUTE 'explain (analyze, timing off, summary off, buffers off, costs off) ' ||\n\t\t  query\n    LOOP\n        ln := regexp_replace(ln, '(Memory( Usage)?|Buckets|Batches): \\S*',  '\\1: xxx', 'g');\n        RETURN NEXT ln;\n    END LOOP;\nEND;\n$$;\n-- only updates\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED THEN\n\tUPDATE SET b = t.b + 1');\n-- only updates to selected tuples\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1');\n-- updates + deletes\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1\nWHEN MATCHED AND t.a >= 10 AND t.a <= 20 THEN\n\tDELETE');\n-- only inserts\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN NOT MATCHED AND s.a < 10 THEN\n\tINSERT VALUES (a, b)');\n-- all three\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1\nWHEN MATCHED AND t.a >= 30 AND t.a <= 40 THEN\n\tDELETE\nWHEN NOT MATCHED AND s.a < 20 THEN\n\tINSERT VALUES (a, b)');\n-- nothing\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a AND t.a < -1000\nWHEN MATCHED AND t.a < 10 THEN\n\tDO NOTHING');\nDROP TABLE ex_msource, ex_mtarget;\nDROP FUNCTION explain_merge(text);\n-- Subqueries\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED THEN\n    UPDATE SET balance = (SELECT count(*) FROM sq_target);\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND (SELECT count(*) > 0 FROM sq_target) THEN\n    UPDATE SET balance = 42;\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid AND (SELECT count(*) > 0 FROM sq_target)\nWHEN MATCHED THEN\n    UPDATE SET balance = 42;\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\n--  Test RETURNING with subqueries\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid AND (SELECT count(*) > 0 FROM sq_target)\nWHEN MATCHED THEN\n    UPDATE SET balance = 42\nRETURNING *;\npsql:include/ts_merge_query.sql:952: ERROR:  syntax error at or near \"RETURNING\"\nLINE 6: RETURNING *;\n        ^\nSELECT * FROM sq_target WHERE tid = 1;\npsql:include/ts_merge_query.sql:953: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\nDROP TABLE sq_target CASCADE;\nDROP TABLE sq_source CASCADE;\nCREATE TABLE pa_target (tid integer, balance float, val text)\n\tPARTITION BY LIST (tid);\nCREATE TABLE part1 PARTITION OF pa_target FOR VALUES IN (1,4)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part2 PARTITION OF pa_target FOR VALUES IN (2,5,6)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part3 PARTITION OF pa_target FOR VALUES IN (3,8,9)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part4 PARTITION OF pa_target DEFAULT\n  WITH (autovacuum_enabled=off);\nCREATE TABLE pa_source (sid integer, delta float);\n-- insert many rows to the source table\nINSERT INTO pa_source SELECT id, id * 10  FROM generate_series(1,14) AS id;\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT id, id * 100, 'initial' FROM generate_series(1,14,2) AS id;\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- same with a constant qual\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid AND tid = 1\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- try updating the partition key column\nBEGIN;\nCREATE FUNCTION merge_func() RETURNS integer LANGUAGE plpgsql AS $$\nDECLARE\n  result integer;\nBEGIN\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nIF FOUND THEN\n  GET DIAGNOSTICS result := ROW_COUNT;\nEND IF;\nRETURN result;\nEND;\n$$;\nSELECT merge_func();\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\nDROP TABLE pa_target CASCADE;\n-- The target table is partitioned in the same way, but this time by attaching\n-- partitions which have columns in different order, dropped columns etc.\nCREATE TABLE pa_target (tid integer, balance float, val text)\n\tPARTITION BY LIST (tid);\nCREATE TABLE part1 (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part2 (balance float, tid integer, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part3 (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part4 (extraid text, tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nALTER TABLE part4 DROP COLUMN extraid;\nALTER TABLE pa_target ATTACH PARTITION part1 FOR VALUES IN (1,4);\nALTER TABLE pa_target ATTACH PARTITION part2 FOR VALUES IN (2,5,6);\nALTER TABLE pa_target ATTACH PARTITION part3 FOR VALUES IN (3,8,9);\nALTER TABLE pa_target ATTACH PARTITION part4 DEFAULT;\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT id, id * 100, 'initial' FROM generate_series(1,14,2) AS id;\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- same with a constant qual\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid AND tid IN (1, 5)\n  WHEN MATCHED AND tid % 5 = 0 THEN DELETE\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- try updating the partition key column\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\nDROP TABLE pa_source;\nDROP TABLE pa_target CASCADE;\n-- Sub-partitioning\nCREATE TABLE pa_target (logts timestamp, tid integer, balance float, val text)\n\tPARTITION BY RANGE (logts);\nCREATE TABLE part_m01 PARTITION OF pa_target\n\tFOR VALUES FROM ('2017-01-01') TO ('2017-02-01')\n\tPARTITION BY LIST (tid);\nCREATE TABLE part_m01_odd PARTITION OF part_m01\n\tFOR VALUES IN (1,3,5,7,9) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m01_even PARTITION OF part_m01\n\tFOR VALUES IN (2,4,6,8) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m02 PARTITION OF pa_target\n\tFOR VALUES FROM ('2017-02-01') TO ('2017-03-01')\n\tPARTITION BY LIST (tid);\nCREATE TABLE part_m02_odd PARTITION OF part_m02\n\tFOR VALUES IN (1,3,5,7,9) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m02_even PARTITION OF part_m02\n\tFOR VALUES IN (2,4,6,8) WITH (autovacuum_enabled=off);\nCREATE TABLE pa_source (sid integer, delta float)\n  WITH (autovacuum_enabled=off);\n-- insert many rows to the source table\nINSERT INTO pa_source SELECT id, id * 10  FROM generate_series(1,14) AS id;\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT '2017-01-31', id, id * 100, 'initial' FROM generate_series(1,9,3) AS id;\nINSERT INTO pa_target SELECT '2017-02-28', id, id * 100, 'initial' FROM generate_series(2,9,3) AS id;\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING (SELECT '2017-01-15' AS slogts, * FROM pa_source WHERE sid < 10) s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (slogts::timestamp, sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\nDROP TABLE pa_source;\nDROP TABLE pa_target CASCADE;\n-- some complex joins on the source side\n-- source relation is an unaliased join\nMERGE INTO cj_target t\nUSING cj_source1 s1\n\tINNER JOIN cj_source2 s2 ON sid1 = sid2\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid1, delta, sval);\n-- try accessing columns from either side of the source join\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2 AND scat = 20\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid2, delta, sval)\nWHEN MATCHED THEN\n\tDELETE;\n-- some simple expressions in INSERT targetlist\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid2, delta + scat, sval)\nWHEN MATCHED THEN\n\tUPDATE SET val = val || ' updated by merge';\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2 AND scat = 20\nON t.tid = sid1\nWHEN MATCHED THEN\n\tUPDATE SET val = val || ' ' || delta::text;\nSELECT * FROM cj_target ORDER BY tid;\nALTER TABLE cj_source1 RENAME COLUMN sid1 TO sid;\nALTER TABLE cj_source2 RENAME COLUMN sid2 TO sid;\nTRUNCATE cj_target;\nMERGE INTO cj_target t\nUSING cj_source1 s1\n\tINNER JOIN cj_source2 s2 ON s1.sid = s2.sid\nON t.tid = s1.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s2.sid, delta, sval);\nDROP TABLE cj_source2, cj_source1;\nDROP TABLE cj_target CASCADE;\n-- Function scans\nMERGE INTO fs_target t\nUSING generate_series(1,100,1) AS id\nON t.a = id\nWHEN MATCHED THEN\n\tUPDATE SET b = b + id\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (id, -1);\nMERGE INTO fs_target t\nUSING generate_series(1,100,2) AS id\nON t.a = id\nWHEN MATCHED THEN\n\tUPDATE SET b = b + id, c = 'updated '|| id.*::text\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (id, -1, 'inserted ' || id.*::text);\nSELECT count(*) FROM fs_target;\nDROP TABLE fs_target CASCADE;\n-- SERIALIZABLE test\n-- handled in isolation tests\n-- Inheritance-based partitioning\nCREATE TABLE measurement (\n    city_id         int not null,\n    logdate         date not null,\n    peaktemp        int,\n    unitsales       int\n) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2006m02 (\n    CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )\n) INHERITS (measurement) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2006m03 (\n    CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )\n) INHERITS (measurement) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2007m01 (\n    filler          text,\n    peaktemp        int,\n    logdate         date not null,\n    city_id         int not null,\n    unitsales       int\n    CHECK ( logdate >= DATE '2007-01-01' AND logdate < DATE '2007-02-01')\n) WITH (autovacuum_enabled=off);\nALTER TABLE measurement_y2007m01 DROP COLUMN filler;\nALTER TABLE measurement_y2007m01 INHERIT measurement;\nINSERT INTO measurement VALUES (0, '2005-07-21', 5, 15);\nCREATE OR REPLACE FUNCTION measurement_insert_trigger()\nRETURNS TRIGGER AS $$\nBEGIN\n    IF ( NEW.logdate >= DATE '2006-02-01' AND\n         NEW.logdate < DATE '2006-03-01' ) THEN\n        INSERT INTO measurement_y2006m02 VALUES (NEW.*);\n    ELSIF ( NEW.logdate >= DATE '2006-03-01' AND\n            NEW.logdate < DATE '2006-04-01' ) THEN\n        INSERT INTO measurement_y2006m03 VALUES (NEW.*);\n    ELSIF ( NEW.logdate >= DATE '2007-01-01' AND\n            NEW.logdate < DATE '2007-02-01' ) THEN\n        INSERT INTO measurement_y2007m01 (city_id, logdate, peaktemp, unitsales)\n            VALUES (NEW.*);\n    ELSE\n        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';\n    END IF;\n    RETURN NULL;\nEND;\n$$ LANGUAGE plpgsql ;\nCREATE TRIGGER insert_measurement_trigger\n    BEFORE INSERT ON measurement\n    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();\nINSERT INTO measurement VALUES (1, '2006-02-10', 35, 10);\nINSERT INTO measurement VALUES (1, '2006-02-16', 45, 20);\nINSERT INTO measurement VALUES (1, '2006-03-17', 25, 10);\nINSERT INTO measurement VALUES (1, '2006-03-27', 15, 40);\nINSERT INTO measurement VALUES (1, '2007-01-15', 10, 10);\nINSERT INTO measurement VALUES (1, '2007-01-17', 10, 10);\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate;\nCREATE TABLE new_measurement (LIKE measurement) WITH (autovacuum_enabled=off);\nINSERT INTO new_measurement VALUES (0, '2005-07-21', 25, 20);\nINSERT INTO new_measurement VALUES (1, '2006-03-01', 20, 10);\nINSERT INTO new_measurement VALUES (1, '2006-02-16', 50, 10);\nINSERT INTO new_measurement VALUES (2, '2006-02-10', 20, 20);\nINSERT INTO new_measurement VALUES (1, '2006-03-27', NULL, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-17', NULL, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-15', 5, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-16', 10, 10);\nBEGIN;\nMERGE INTO ONLY measurement m\n USING new_measurement nm ON\n      (m.city_id = nm.city_id and m.logdate=nm.logdate)\nWHEN MATCHED AND nm.peaktemp IS NULL THEN DELETE\nWHEN MATCHED THEN UPDATE\n     SET peaktemp = greatest(m.peaktemp, nm.peaktemp),\n        unitsales = m.unitsales + coalesce(nm.unitsales, 0)\nWHEN NOT MATCHED THEN INSERT\n     (city_id, logdate, peaktemp, unitsales)\n   VALUES (city_id, logdate, peaktemp, unitsales);\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate, peaktemp;\nROLLBACK;\nMERGE into measurement m\n USING new_measurement nm ON\n      (m.city_id = nm.city_id and m.logdate=nm.logdate)\nWHEN MATCHED AND nm.peaktemp IS NULL THEN DELETE\nWHEN MATCHED THEN UPDATE\n     SET peaktemp = greatest(m.peaktemp, nm.peaktemp),\n        unitsales = m.unitsales + coalesce(nm.unitsales, 0)\nWHEN NOT MATCHED THEN INSERT\n     (city_id, logdate, peaktemp, unitsales)\n   VALUES (city_id, logdate, peaktemp, unitsales);\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate;\nBEGIN;\nMERGE INTO new_measurement nm\n USING ONLY measurement m ON\n      (nm.city_id = m.city_id and nm.logdate=m.logdate)\nWHEN MATCHED THEN DELETE;\nSELECT * FROM new_measurement ORDER BY city_id, logdate;\nROLLBACK;\nMERGE INTO new_measurement nm\n USING measurement m ON\n      (nm.city_id = m.city_id and nm.logdate=m.logdate)\nWHEN MATCHED THEN DELETE;\nSELECT * FROM new_measurement ORDER BY city_id, logdate;\nDROP TABLE measurement, new_measurement CASCADE;\nDROP FUNCTION measurement_insert_trigger();\nRESET SESSION AUTHORIZATION;\nDROP TABLE target CASCADE;\nDROP TABLE target2 CASCADE;\nDROP TABLE source, source2;\nDROP FUNCTION merge_trigfunc();\nREVOKE CREATE ON SCHEMA public FROM regress_merge_privs;\nDROP USER regress_merge_privs;\nDROP USER regress_merge_no_privs;\n\\o\n:DIFF_CMD\n"
  },
  {
    "path": "test/expected/ts_merge-17.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\\set ON_ERROR_STOP 0\n\\set VERBOSITY default\nSET client_min_messages TO error;\n\\set TEST_BASE_NAME ts_merge\nSELECT format('include/%s_load.sql', :'TEST_BASE_NAME') AS \"TEST_LOAD_NAME\",\n    format('include/%s_load_ht.sql', :'TEST_BASE_NAME') AS \"TEST_LOAD_HT_NAME\",\n    format('include/%s_query.sql', :'TEST_BASE_NAME') AS \"TEST_QUERY_NAME\",\n    format('%s/results/%s_ht_results.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') AS \"TEST_RESULTS_WITH_HYPERTABLE\",\n    format('%s/results/%s_results.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') AS \"TEST_RESULTS_WITH_NO_HYPERTABLE\" \\gset\nSELECT format('\\! diff -u --label \"Base pg table results\" --label \"Hypertable results\" %s %s', :'TEST_RESULTS_WITH_HYPERTABLE', :'TEST_RESULTS_WITH_NO_HYPERTABLE') AS \"DIFF_CMD\" \\gset\n\\ir :TEST_LOAD_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE USER regress_merge_privs;\nCREATE USER regress_merge_no_privs;\nDROP TABLE IF EXISTS target;\nDROP TABLE IF EXISTS source;\nCREATE TABLE target (tid integer, balance integer)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE source (sid integer, delta integer) -- no index\n  WITH (autovacuum_enabled=off);\nINSERT INTO target VALUES (1, 10);\nINSERT INTO target VALUES (2, 20);\nINSERT INTO target VALUES (3, 30);\nSELECT t.ctid is not null as matched, t.*, s.* FROM source s FULL OUTER JOIN target t ON s.sid = t.tid ORDER BY t.tid, s.sid;\n matched | tid | balance | sid | delta \n---------+-----+---------+-----+-------\n t       |   1 |      10 |     |      \n t       |   2 |      20 |     |      \n t       |   3 |      30 |     |      \n\nALTER TABLE target OWNER TO regress_merge_privs;\nALTER TABLE source OWNER TO regress_merge_privs;\nCREATE TABLE target2 (tid integer, balance integer)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE source2 (sid integer, delta integer)\n  WITH (autovacuum_enabled=off);\nALTER TABLE target2 OWNER TO regress_merge_no_privs;\nALTER TABLE source2 OWNER TO regress_merge_no_privs;\nGRANT INSERT ON target TO regress_merge_no_privs;\nGRANT CREATE ON SCHEMA public TO regress_merge_privs;\nSET SESSION AUTHORIZATION regress_merge_privs;\nCREATE TABLE sq_target (tid integer NOT NULL, balance integer)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE sq_source (delta integer, sid integer, balance integer DEFAULT 0)\n  WITH (autovacuum_enabled=off);\nINSERT INTO sq_target(tid, balance) VALUES (1,100), (2,200), (3,300);\nINSERT INTO sq_source(sid, delta) VALUES (1,10), (2,20), (4,40);\n-- conditional WHEN clause\nCREATE TABLE wq_target (tid integer not null, balance integer DEFAULT -1)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE wq_source (balance integer, sid integer)\n  WITH (autovacuum_enabled=off);\nINSERT INTO wq_source (sid, balance) VALUES (1, 100);\nCREATE TABLE cj_target (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE cj_source1 (sid1 integer, scat integer, delta integer)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE cj_source2 (sid2 integer, sval text)\n  WITH (autovacuum_enabled=off);\nINSERT INTO cj_source1 VALUES (1, 10, 100);\nINSERT INTO cj_source1 VALUES (1, 20, 200);\nINSERT INTO cj_source1 VALUES (2, 20, 300);\nINSERT INTO cj_source1 VALUES (3, 10, 400);\nINSERT INTO cj_source2 VALUES (1, 'initial source2');\nINSERT INTO cj_source2 VALUES (2, 'initial source2');\nINSERT INTO cj_source2 VALUES (3, 'initial source2');\nCREATE TABLE fs_target (a int, b int, c text)\n  WITH (autovacuum_enabled=off);\n-- run tests on normal table\n\\o :TEST_RESULTS_WITH_NO_HYPERTABLE\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n--\n-- Errors\n--\nMERGE INTO target t RANDOMWORD\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:12: ERROR:  syntax error at or near \"RANDOMWORD\"\nLINE 1: MERGE INTO target t RANDOMWORD\n                            ^\n-- MATCHED/INSERT error\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:18: ERROR:  syntax error at or near \"INSERT\"\nLINE 5:  INSERT DEFAULT VALUES;\n         ^\n-- incorrectly specifying INTO target\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT INTO target DEFAULT VALUES;\npsql:include/ts_merge_query.sql:24: ERROR:  syntax error at or near \"INTO\"\nLINE 5:  INSERT INTO target DEFAULT VALUES;\n                ^\n-- Multiple VALUES clause\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (1,1), (2,2);\npsql:include/ts_merge_query.sql:30: ERROR:  syntax error at or near \",\"\nLINE 5:  INSERT VALUES (1,1), (2,2);\n                            ^\n-- SELECT query for INSERT\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT SELECT (1, 1);\npsql:include/ts_merge_query.sql:36: ERROR:  syntax error at or near \"SELECT\"\nLINE 5:  INSERT SELECT (1, 1);\n                ^\n-- NOT MATCHED/UPDATE\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:42: ERROR:  syntax error at or near \"UPDATE\"\nLINE 5:  UPDATE SET balance = 0;\n         ^\n-- UPDATE tablename\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE target SET balance = 0;\npsql:include/ts_merge_query.sql:48: ERROR:  syntax error at or near \"target\"\nLINE 5:  UPDATE target SET balance = 0;\n                ^\n-- source and target names the same\nMERGE INTO target\nUSING target\nON tid = tid\nWHEN MATCHED THEN DO NOTHING;\npsql:include/ts_merge_query.sql:53: ERROR:  name \"target\" specified more than once\nDETAIL:  The name is used both as MERGE target table and data source.\n-- used in a CTE\nWITH foo AS (\n  MERGE INTO target USING source ON (true)\n  WHEN MATCHED THEN DELETE\n) SELECT * FROM foo;\npsql:include/ts_merge_query.sql:58: ERROR:  WITH query \"foo\" does not have a RETURNING clause\nLINE 4: ) SELECT * FROM foo;\n                        ^\n-- used in COPY\nCOPY (\n  MERGE INTO target USING source ON (true)\n  WHEN MATCHED THEN DELETE\n) TO stdout;\npsql:include/ts_merge_query.sql:63: ERROR:  COPY query must have a RETURNING clause\n-- unsupported relation types\n-- view\nCREATE VIEW tv AS SELECT * FROM target;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\nDROP VIEW tv;\n-- materialized view\nCREATE MATERIALIZED VIEW mv AS SELECT * FROM target;\nMERGE INTO mv t\nUSING source s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:81: ERROR:  cannot execute MERGE on relation \"mv\"\nDETAIL:  This operation is not supported for materialized views.\nDROP MATERIALIZED VIEW mv;\n-- permissions\nMERGE INTO target\nUSING source2\nON target.tid = source2.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:90: ERROR:  permission denied for table source2\nGRANT INSERT ON target TO regress_merge_no_privs;\nSET SESSION AUTHORIZATION regress_merge_no_privs;\nMERGE INTO target\nUSING source2\nON target.tid = source2.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:99: ERROR:  permission denied for table target\nGRANT UPDATE ON target2 TO regress_merge_privs;\nSET SESSION AUTHORIZATION regress_merge_privs;\nMERGE INTO target2\nUSING source\nON target2.tid = source.sid\nWHEN MATCHED THEN\n\tDELETE;\npsql:include/ts_merge_query.sql:108: ERROR:  permission denied for table target2\nMERGE INTO target2\nUSING source\nON target2.tid = source.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:114: ERROR:  permission denied for table target2\n-- check if the target can be accessed from source relation subquery; we should\n-- not be able to do so\nMERGE INTO target t\nUSING (SELECT * FROM source WHERE t.tid > sid) s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:122: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 2: USING (SELECT * FROM source WHERE t.tid > sid) s\n                                          ^\nDETAIL:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\n--\n-- initial tests\n--\n-- zero rows in source has no effect\nMERGE INTO target\nUSING source\nON target.tid = source.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\nROLLBACK;\n-- insert some non-matching source rows to work from\nINSERT INTO source VALUES (4, 40);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tDO NOTHING;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (5, 50);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- index plans\nINSERT INTO target SELECT generate_series(1000,2500), 0;\nALTER TABLE target ADD PRIMARY KEY (tid);\nANALYZE target;\nDELETE FROM target WHERE tid > 100;\nANALYZE target;\n-- insert some matching source rows to work from\nINSERT INTO source VALUES (2, 5);\nINSERT INTO source VALUES (3, 20);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n-- equivalent of an UPDATE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- equivalent of a DELETE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDO NOTHING;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, NULL);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- duplicate source row causes multiple target row update ERROR\nINSERT INTO source VALUES (2, 5);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:241: ERROR:  MERGE command cannot affect row a second time\nHINT:  Ensure that not more than one source row matches any one target row.\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\npsql:include/ts_merge_query.sql:249: ERROR:  MERGE command cannot affect row a second time\nHINT:  Ensure that not more than one source row matches any one target row.\nROLLBACK;\n-- remove duplicate MATCHED data from source data\nDELETE FROM source WHERE sid = 2;\nINSERT INTO source VALUES (2, 5);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n-- duplicate source row on INSERT should fail because of target_pkey\nINSERT INTO source VALUES (4, 40);\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n  INSERT VALUES (4, NULL);\npsql:include/ts_merge_query.sql:265: ERROR:  duplicate key value violates unique constraint \"target_pkey\"\nDETAIL:  Key (tid)=(4) already exists.\nSELECT * FROM target ORDER BY tid;\npsql:include/ts_merge_query.sql:266: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\n-- remove duplicate NOT MATCHED data from source data\nDELETE FROM source WHERE sid = 4;\nINSERT INTO source VALUES (4, 40);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n-- remove constraints\nalter table target drop CONSTRAINT target_pkey;\nalter table target alter column tid drop not null;\n-- multiple actions\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, 4)\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- should be equivalent\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, 4);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- column references\n-- do a simple equivalent of an UPDATE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance + s.delta;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- do a simple equivalent of an INSERT SELECT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- and again with duplicate source rows\nINSERT INTO source VALUES (5, 50);\nINSERT INTO source VALUES (5, 50);\n-- do a simple equivalent of an INSERT SELECT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n  INSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- removing duplicate source rows\nDELETE FROM source WHERE sid = 5;\n-- and again with explicitly identified column list\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- and again with a subtle error: referring to non-existent target row for NOT MATCHED\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (t.tid, s.delta);\npsql:include/ts_merge_query.sql:356: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 5:  INSERT (tid, balance) VALUES (t.tid, s.delta);\n                                       ^\nDETAIL:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\n-- and again with a constant ON clause\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON (SELECT true)\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (t.tid, s.delta);\npsql:include/ts_merge_query.sql:364: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 5:  INSERT (tid, balance) VALUES (t.tid, s.delta);\n                                       ^\nDETAIL:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\nSELECT * FROM target ORDER BY tid;\npsql:include/ts_merge_query.sql:365: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\n-- now the classic UPSERT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance + s.delta\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- this time with a FALSE condition\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND FALSE THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\n-- this time with an actual condition which returns false\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance <> 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\nBEGIN;\n-- and now with a condition which returns true\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\nROLLBACK;\n-- conditions in the NOT MATCHED clause can only refer to source columns\nBEGIN;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND t.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\npsql:include/ts_merge_query.sql:408: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 3: WHEN NOT MATCHED AND t.balance = 100 THEN\n                             ^\nDETAIL:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\nSELECT * FROM wq_target;\npsql:include/ts_merge_query.sql:409: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\n-- conditions in MATCHED clause can refer to both source and target\nSELECT * FROM wq_source;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND s.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n-- check if AND works\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 AND s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 AND s.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n-- check if OR works\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 OR s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 199 OR s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n-- check source-side whole-row references\nBEGIN;\nMERGE INTO wq_target t\nUSING wq_source s ON (t.tid = s.sid)\nWHEN matched and t = s or t.tid = s.sid THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nROLLBACK;\n-- check if subqueries work in the conditions?\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance > (SELECT max(balance) FROM target) THEN\n\tUPDATE SET balance = t.balance + s.balance;\n-- check if we can access system columns in the conditions\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.xmin = t.xmax THEN\n\tUPDATE SET balance = t.balance + s.balance;\npsql:include/ts_merge_query.sql:477: ERROR:  cannot use system column \"xmin\" in MERGE WHEN condition\nLINE 3: WHEN MATCHED AND t.xmin = t.xmax THEN\n                         ^\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.tableoid >= 0 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nDROP TABLE wq_target CASCADE;\nDROP TABLE wq_source;\n-- test triggers\ncreate or replace function merge_trigfunc () returns trigger\nlanguage plpgsql as\n$$\nDECLARE\n\tline text;\nBEGIN\n\tSELECT INTO line format('%s %s %s trigger%s',\n\t\tTG_WHEN, TG_OP, TG_LEVEL, CASE\n\t\tWHEN TG_OP = 'INSERT' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s', NEW)\n\t\tWHEN TG_OP = 'UPDATE' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s -> %s', OLD, NEW)\n\t\tWHEN TG_OP = 'DELETE' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s', OLD)\n\t\tEND);\n\n\tRAISE NOTICE '%', line;\n\tIF (TG_WHEN = 'BEFORE' AND TG_LEVEL = 'ROW') THEN\n\t\tIF (TG_OP = 'DELETE') THEN\n\t\t\tRETURN OLD;\n\t\tELSE\n\t\t\tRETURN NEW;\n\t\tEND IF;\n\tELSE\n\t\tRETURN NULL;\n\tEND IF;\nEND;\n$$;\nCREATE TRIGGER merge_bsi BEFORE INSERT ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bsu BEFORE UPDATE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bsd BEFORE DELETE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asi AFTER INSERT ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asu AFTER UPDATE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asd AFTER DELETE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bri BEFORE INSERT ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bru BEFORE UPDATE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_brd BEFORE DELETE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_ari AFTER INSERT ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_aru AFTER UPDATE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_ard AFTER DELETE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\n-- now the classic UPSERT, with a DELETE\nBEGIN;\nUPDATE target SET balance = 0 WHERE tid = 3;\n--EXPLAIN (ANALYZE ON, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF)\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND t.balance > s.delta THEN\n\tUPDATE SET balance = t.balance - s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- Test behavior of triggers that turn UPDATE/DELETE into no-ops\ncreate or replace function skip_merge_op() returns trigger\nlanguage plpgsql as\n$$\nBEGIN\n\tRETURN NULL;\nEND;\n$$;\nSELECT * FROM target full outer join source on (sid = tid);\ncreate trigger merge_skip BEFORE INSERT OR UPDATE or DELETE\n  ON target FOR EACH ROW EXECUTE FUNCTION skip_merge_op();\nDO $$\nDECLARE\n  result integer;\nBEGIN\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND s.sid = 3 THEN UPDATE SET balance = t.balance + s.delta\nWHEN MATCHED THEN DELETE\nWHEN NOT MATCHED THEN INSERT VALUES (sid, delta);\nIF FOUND THEN\n  RAISE NOTICE 'Found';\nELSE\n  RAISE NOTICE 'Not found';\nEND IF;\nGET DIAGNOSTICS result := ROW_COUNT;\nRAISE NOTICE 'ROW_COUNT = %', result;\nEND;\n$$;\nSELECT * FROM target FULL OUTER JOIN source ON (sid = tid);\nDROP TRIGGER merge_skip ON target;\nDROP FUNCTION skip_merge_op();\n-- test from PL/pgSQL\n-- make sure MERGE INTO isn't interpreted to mean returning variables like SELECT INTO\nBEGIN;\nDO LANGUAGE plpgsql $$\nBEGIN\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND t.balance > s.delta THEN\n\tUPDATE SET balance = t.balance - s.delta;\nEND;\n$$;\nROLLBACK;\n--source constants\nBEGIN;\nMERGE INTO target t\nUSING (SELECT 9 AS sid, 57 AS delta) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n--source query\nBEGIN;\nMERGE INTO target t\nUSING (SELECT sid, delta FROM source WHERE delta > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING (SELECT sid, delta as newname FROM source WHERE delta > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.newname);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n--self-merge\nBEGIN;\nMERGE INTO target t1\nUSING target t2\nON t1.tid = t2.tid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t1.balance + t2.balance\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (t2.tid, t2.balance);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING (SELECT tid as sid, balance as delta FROM target WHERE balance > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING\n(SELECT sid, max(delta) AS delta\n FROM source\n GROUP BY sid\n HAVING count(*) = 1\n ORDER BY sid ASC) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- plpgsql parameters and results\nBEGIN;\nCREATE FUNCTION merge_func (p_id integer, p_bal integer)\nRETURNS INTEGER\nLANGUAGE plpgsql\nAS $$\nDECLARE\n result integer;\nBEGIN\nMERGE INTO target t\nUSING (SELECT p_id AS sid) AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance - p_bal;\nIF FOUND THEN\n\tGET DIAGNOSTICS result := ROW_COUNT;\nEND IF;\nRETURN result;\nEND;\n$$;\nSELECT merge_func(3, 4);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- PREPARE\nBEGIN;\nprepare foom as merge into target t using (select 1 as sid) s on (t.tid = s.sid) when matched then update set balance = 1;\nexecute foom;\nROLLBACK;\nBEGIN;\nPREPARE foom2 (integer, integer) AS\nMERGE INTO target t\nUSING (SELECT 1) s\nON t.tid = $1\nWHEN MATCHED THEN\nUPDATE SET balance = $2;\n--EXPLAIN (ANALYZE ON, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF)\nexecute foom2 (1, 1);\nROLLBACK;\n-- subqueries in source relation\nBEGIN;\nMERGE INTO sq_target t\nUSING (SELECT * FROM sq_source) s\nON tid = sid\nWHEN MATCHED AND t.balance > delta THEN\n\tUPDATE SET balance = t.balance + delta;\nSELECT * FROM sq_target ORDER BY tid;\nROLLBACK;\n-- try a view\nCREATE VIEW v AS SELECT * FROM sq_source WHERE sid < 2;\nBEGIN;\nMERGE INTO sq_target\nUSING v\nON tid = sid\nWHEN MATCHED THEN\n    UPDATE SET balance = v.balance + delta;\nSELECT * FROM sq_target ORDER BY tid;\nROLLBACK;\n-- ambiguous reference to a column\nBEGIN;\nMERGE INTO sq_target\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\npsql:include/ts_merge_query.sql:732: ERROR:  column reference \"balance\" is ambiguous\nLINE 5:     UPDATE SET balance = balance + delta\n                                 ^\nROLLBACK;\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\nSELECT * FROM sq_target;\nROLLBACK;\n-- CTEs\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nWITH targq AS (\n\tSELECT * FROM v\n)\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\nROLLBACK;\n-- RETURNING\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE\nRETURNING *;\nROLLBACK;\n-- PG17-specific tests for views, returning and merge_action. These throw syntax errors for previous versions of Postgres.\n-- However, since the error is the same for both hypertables and regular tables, this test should still pass for previous versions.\n-- RETURNING\nINSERT INTO source(sid, delta) VALUES(1, 40), (5, 50);\nBEGIN;\nMERGE INTO target t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta);\nSELECT * from target;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 1 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta)\nRETURNING merge_action(), t.*;\nROLLBACK;\n-- Views\nCREATE VIEW tv AS SELECT * FROM target;\nBEGIN;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta);\nSELECT * from tv;\nSELECT * from target; -- should also update the underlying table\nROLLBACK;\nBEGIN;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta)\nRETURNING merge_action(), t.*;\nROLLBACK;\nDROP VIEW tv;\nDELETE FROM source where sid in (1, 5);\n-- EXPLAIN\nCREATE TABLE ex_mtarget (a int, b int)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE ex_msource (a int, b int)\n  WITH (autovacuum_enabled=off);\nINSERT INTO ex_mtarget SELECT i, i*10 FROM generate_series(1,100,2) i;\nINSERT INTO ex_msource SELECT i, i*10 FROM generate_series(1,100,1) i;\nCREATE FUNCTION explain_merge(query text) RETURNS SETOF text\nLANGUAGE plpgsql AS\n$$\nDECLARE ln text;\nBEGIN\n    FOR ln IN\n        EXECUTE 'explain (analyze, timing off, summary off, buffers off, costs off) ' ||\n\t\t  query\n    LOOP\n        ln := regexp_replace(ln, '(Memory( Usage)?|Buckets|Batches): \\S*',  '\\1: xxx', 'g');\n        RETURN NEXT ln;\n    END LOOP;\nEND;\n$$;\n-- only updates\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED THEN\n\tUPDATE SET b = t.b + 1');\n-- only updates to selected tuples\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1');\n-- updates + deletes\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1\nWHEN MATCHED AND t.a >= 10 AND t.a <= 20 THEN\n\tDELETE');\n-- only inserts\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN NOT MATCHED AND s.a < 10 THEN\n\tINSERT VALUES (a, b)');\n-- all three\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1\nWHEN MATCHED AND t.a >= 30 AND t.a <= 40 THEN\n\tDELETE\nWHEN NOT MATCHED AND s.a < 20 THEN\n\tINSERT VALUES (a, b)');\n-- nothing\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a AND t.a < -1000\nWHEN MATCHED AND t.a < 10 THEN\n\tDO NOTHING');\nDROP TABLE ex_msource, ex_mtarget;\nDROP FUNCTION explain_merge(text);\n-- Subqueries\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED THEN\n    UPDATE SET balance = (SELECT count(*) FROM sq_target);\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND (SELECT count(*) > 0 FROM sq_target) THEN\n    UPDATE SET balance = 42;\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid AND (SELECT count(*) > 0 FROM sq_target)\nWHEN MATCHED THEN\n    UPDATE SET balance = 42;\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\n--  Test RETURNING with subqueries\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid AND (SELECT count(*) > 0 FROM sq_target)\nWHEN MATCHED THEN\n    UPDATE SET balance = 42\nRETURNING *;\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\nDROP TABLE sq_target CASCADE;\nDROP TABLE sq_source CASCADE;\nCREATE TABLE pa_target (tid integer, balance float, val text)\n\tPARTITION BY LIST (tid);\nCREATE TABLE part1 PARTITION OF pa_target FOR VALUES IN (1,4)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part2 PARTITION OF pa_target FOR VALUES IN (2,5,6)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part3 PARTITION OF pa_target FOR VALUES IN (3,8,9)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part4 PARTITION OF pa_target DEFAULT\n  WITH (autovacuum_enabled=off);\nCREATE TABLE pa_source (sid integer, delta float);\n-- insert many rows to the source table\nINSERT INTO pa_source SELECT id, id * 10  FROM generate_series(1,14) AS id;\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT id, id * 100, 'initial' FROM generate_series(1,14,2) AS id;\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- same with a constant qual\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid AND tid = 1\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- try updating the partition key column\nBEGIN;\nCREATE FUNCTION merge_func() RETURNS integer LANGUAGE plpgsql AS $$\nDECLARE\n  result integer;\nBEGIN\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nIF FOUND THEN\n  GET DIAGNOSTICS result := ROW_COUNT;\nEND IF;\nRETURN result;\nEND;\n$$;\nSELECT merge_func();\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\nDROP TABLE pa_target CASCADE;\n-- The target table is partitioned in the same way, but this time by attaching\n-- partitions which have columns in different order, dropped columns etc.\nCREATE TABLE pa_target (tid integer, balance float, val text)\n\tPARTITION BY LIST (tid);\nCREATE TABLE part1 (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part2 (balance float, tid integer, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part3 (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part4 (extraid text, tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nALTER TABLE part4 DROP COLUMN extraid;\nALTER TABLE pa_target ATTACH PARTITION part1 FOR VALUES IN (1,4);\nALTER TABLE pa_target ATTACH PARTITION part2 FOR VALUES IN (2,5,6);\nALTER TABLE pa_target ATTACH PARTITION part3 FOR VALUES IN (3,8,9);\nALTER TABLE pa_target ATTACH PARTITION part4 DEFAULT;\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT id, id * 100, 'initial' FROM generate_series(1,14,2) AS id;\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- same with a constant qual\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid AND tid IN (1, 5)\n  WHEN MATCHED AND tid % 5 = 0 THEN DELETE\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- try updating the partition key column\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\nDROP TABLE pa_source;\nDROP TABLE pa_target CASCADE;\n-- Sub-partitioning\nCREATE TABLE pa_target (logts timestamp, tid integer, balance float, val text)\n\tPARTITION BY RANGE (logts);\nCREATE TABLE part_m01 PARTITION OF pa_target\n\tFOR VALUES FROM ('2017-01-01') TO ('2017-02-01')\n\tPARTITION BY LIST (tid);\nCREATE TABLE part_m01_odd PARTITION OF part_m01\n\tFOR VALUES IN (1,3,5,7,9) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m01_even PARTITION OF part_m01\n\tFOR VALUES IN (2,4,6,8) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m02 PARTITION OF pa_target\n\tFOR VALUES FROM ('2017-02-01') TO ('2017-03-01')\n\tPARTITION BY LIST (tid);\nCREATE TABLE part_m02_odd PARTITION OF part_m02\n\tFOR VALUES IN (1,3,5,7,9) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m02_even PARTITION OF part_m02\n\tFOR VALUES IN (2,4,6,8) WITH (autovacuum_enabled=off);\nCREATE TABLE pa_source (sid integer, delta float)\n  WITH (autovacuum_enabled=off);\n-- insert many rows to the source table\nINSERT INTO pa_source SELECT id, id * 10  FROM generate_series(1,14) AS id;\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT '2017-01-31', id, id * 100, 'initial' FROM generate_series(1,9,3) AS id;\nINSERT INTO pa_target SELECT '2017-02-28', id, id * 100, 'initial' FROM generate_series(2,9,3) AS id;\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING (SELECT '2017-01-15' AS slogts, * FROM pa_source WHERE sid < 10) s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (slogts::timestamp, sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\nDROP TABLE pa_source;\nDROP TABLE pa_target CASCADE;\n-- some complex joins on the source side\n-- source relation is an unaliased join\nMERGE INTO cj_target t\nUSING cj_source1 s1\n\tINNER JOIN cj_source2 s2 ON sid1 = sid2\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid1, delta, sval);\n-- try accessing columns from either side of the source join\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2 AND scat = 20\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid2, delta, sval)\nWHEN MATCHED THEN\n\tDELETE;\n-- some simple expressions in INSERT targetlist\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid2, delta + scat, sval)\nWHEN MATCHED THEN\n\tUPDATE SET val = val || ' updated by merge';\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2 AND scat = 20\nON t.tid = sid1\nWHEN MATCHED THEN\n\tUPDATE SET val = val || ' ' || delta::text;\nSELECT * FROM cj_target ORDER BY tid;\nALTER TABLE cj_source1 RENAME COLUMN sid1 TO sid;\nALTER TABLE cj_source2 RENAME COLUMN sid2 TO sid;\nTRUNCATE cj_target;\nMERGE INTO cj_target t\nUSING cj_source1 s1\n\tINNER JOIN cj_source2 s2 ON s1.sid = s2.sid\nON t.tid = s1.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s2.sid, delta, sval);\nDROP TABLE cj_source2, cj_source1;\nDROP TABLE cj_target CASCADE;\n-- Function scans\nMERGE INTO fs_target t\nUSING generate_series(1,100,1) AS id\nON t.a = id\nWHEN MATCHED THEN\n\tUPDATE SET b = b + id\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (id, -1);\nMERGE INTO fs_target t\nUSING generate_series(1,100,2) AS id\nON t.a = id\nWHEN MATCHED THEN\n\tUPDATE SET b = b + id, c = 'updated '|| id.*::text\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (id, -1, 'inserted ' || id.*::text);\nSELECT count(*) FROM fs_target;\nDROP TABLE fs_target CASCADE;\n-- SERIALIZABLE test\n-- handled in isolation tests\n-- Inheritance-based partitioning\nCREATE TABLE measurement (\n    city_id         int not null,\n    logdate         date not null,\n    peaktemp        int,\n    unitsales       int\n) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2006m02 (\n    CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )\n) INHERITS (measurement) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2006m03 (\n    CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )\n) INHERITS (measurement) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2007m01 (\n    filler          text,\n    peaktemp        int,\n    logdate         date not null,\n    city_id         int not null,\n    unitsales       int\n    CHECK ( logdate >= DATE '2007-01-01' AND logdate < DATE '2007-02-01')\n) WITH (autovacuum_enabled=off);\nALTER TABLE measurement_y2007m01 DROP COLUMN filler;\nALTER TABLE measurement_y2007m01 INHERIT measurement;\nINSERT INTO measurement VALUES (0, '2005-07-21', 5, 15);\nCREATE OR REPLACE FUNCTION measurement_insert_trigger()\nRETURNS TRIGGER AS $$\nBEGIN\n    IF ( NEW.logdate >= DATE '2006-02-01' AND\n         NEW.logdate < DATE '2006-03-01' ) THEN\n        INSERT INTO measurement_y2006m02 VALUES (NEW.*);\n    ELSIF ( NEW.logdate >= DATE '2006-03-01' AND\n            NEW.logdate < DATE '2006-04-01' ) THEN\n        INSERT INTO measurement_y2006m03 VALUES (NEW.*);\n    ELSIF ( NEW.logdate >= DATE '2007-01-01' AND\n            NEW.logdate < DATE '2007-02-01' ) THEN\n        INSERT INTO measurement_y2007m01 (city_id, logdate, peaktemp, unitsales)\n            VALUES (NEW.*);\n    ELSE\n        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';\n    END IF;\n    RETURN NULL;\nEND;\n$$ LANGUAGE plpgsql ;\nCREATE TRIGGER insert_measurement_trigger\n    BEFORE INSERT ON measurement\n    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();\nINSERT INTO measurement VALUES (1, '2006-02-10', 35, 10);\nINSERT INTO measurement VALUES (1, '2006-02-16', 45, 20);\nINSERT INTO measurement VALUES (1, '2006-03-17', 25, 10);\nINSERT INTO measurement VALUES (1, '2006-03-27', 15, 40);\nINSERT INTO measurement VALUES (1, '2007-01-15', 10, 10);\nINSERT INTO measurement VALUES (1, '2007-01-17', 10, 10);\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate;\nCREATE TABLE new_measurement (LIKE measurement) WITH (autovacuum_enabled=off);\nINSERT INTO new_measurement VALUES (0, '2005-07-21', 25, 20);\nINSERT INTO new_measurement VALUES (1, '2006-03-01', 20, 10);\nINSERT INTO new_measurement VALUES (1, '2006-02-16', 50, 10);\nINSERT INTO new_measurement VALUES (2, '2006-02-10', 20, 20);\nINSERT INTO new_measurement VALUES (1, '2006-03-27', NULL, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-17', NULL, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-15', 5, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-16', 10, 10);\nBEGIN;\nMERGE INTO ONLY measurement m\n USING new_measurement nm ON\n      (m.city_id = nm.city_id and m.logdate=nm.logdate)\nWHEN MATCHED AND nm.peaktemp IS NULL THEN DELETE\nWHEN MATCHED THEN UPDATE\n     SET peaktemp = greatest(m.peaktemp, nm.peaktemp),\n        unitsales = m.unitsales + coalesce(nm.unitsales, 0)\nWHEN NOT MATCHED THEN INSERT\n     (city_id, logdate, peaktemp, unitsales)\n   VALUES (city_id, logdate, peaktemp, unitsales);\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate, peaktemp;\nROLLBACK;\nMERGE into measurement m\n USING new_measurement nm ON\n      (m.city_id = nm.city_id and m.logdate=nm.logdate)\nWHEN MATCHED AND nm.peaktemp IS NULL THEN DELETE\nWHEN MATCHED THEN UPDATE\n     SET peaktemp = greatest(m.peaktemp, nm.peaktemp),\n        unitsales = m.unitsales + coalesce(nm.unitsales, 0)\nWHEN NOT MATCHED THEN INSERT\n     (city_id, logdate, peaktemp, unitsales)\n   VALUES (city_id, logdate, peaktemp, unitsales);\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate;\nBEGIN;\nMERGE INTO new_measurement nm\n USING ONLY measurement m ON\n      (nm.city_id = m.city_id and nm.logdate=m.logdate)\nWHEN MATCHED THEN DELETE;\nSELECT * FROM new_measurement ORDER BY city_id, logdate;\nROLLBACK;\nMERGE INTO new_measurement nm\n USING measurement m ON\n      (nm.city_id = m.city_id and nm.logdate=m.logdate)\nWHEN MATCHED THEN DELETE;\nSELECT * FROM new_measurement ORDER BY city_id, logdate;\nDROP TABLE measurement, new_measurement CASCADE;\nDROP FUNCTION measurement_insert_trigger();\nRESET SESSION AUTHORIZATION;\nDROP TABLE target CASCADE;\nDROP TABLE target2 CASCADE;\nDROP TABLE source, source2;\nDROP FUNCTION merge_trigfunc();\nREVOKE CREATE ON SCHEMA public FROM regress_merge_privs;\nDROP USER regress_merge_privs;\nDROP USER regress_merge_no_privs;\n\\o\n\\ir :TEST_LOAD_HT_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE USER regress_merge_privs;\nCREATE USER regress_merge_no_privs;\nDROP TABLE IF EXISTS target;\nDROP TABLE IF EXISTS source;\nCREATE TABLE target (tid integer, balance integer)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('target', 'tid', chunk_time_interval => 3);\n  create_hypertable  \n---------------------\n (1,public,target,t)\n\nCREATE TABLE source (sid integer, delta integer) -- no index\n  WITH (autovacuum_enabled=off);\nINSERT INTO target VALUES (1, 10);\nINSERT INTO target VALUES (2, 20);\nINSERT INTO target VALUES (3, 30);\nSELECT t.ctid is not null as matched, t.*, s.* FROM source s FULL OUTER JOIN target t ON s.sid = t.tid ORDER BY t.tid, s.sid;\n matched | tid | balance | sid | delta \n---------+-----+---------+-----+-------\n t       |   1 |      10 |     |      \n t       |   2 |      20 |     |      \n t       |   3 |      30 |     |      \n\nALTER TABLE target OWNER TO regress_merge_privs;\nALTER TABLE source OWNER TO regress_merge_privs;\nCREATE TABLE target2 (tid integer, balance integer)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('target2', 'tid', chunk_time_interval => 3);\n  create_hypertable   \n----------------------\n (2,public,target2,t)\n\nCREATE TABLE source2 (sid integer, delta integer)\n  WITH (autovacuum_enabled=off);\nALTER TABLE target2 OWNER TO regress_merge_no_privs;\nALTER TABLE source2 OWNER TO regress_merge_no_privs;\nGRANT INSERT ON target TO regress_merge_no_privs;\nGRANT CREATE ON SCHEMA public TO regress_merge_privs;\nSET SESSION AUTHORIZATION regress_merge_privs;\nCREATE TABLE sq_target (tid integer NOT NULL, balance integer)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('sq_target', 'tid', chunk_time_interval => 3);\n   create_hypertable    \n------------------------\n (3,public,sq_target,t)\n\nCREATE TABLE sq_source (delta integer, sid integer, balance integer DEFAULT 0)\n  WITH (autovacuum_enabled=off);\nINSERT INTO sq_target(tid, balance) VALUES (1,100), (2,200), (3,300);\nINSERT INTO sq_source(sid, delta) VALUES (1,10), (2,20), (4,40);\n-- conditional WHEN clause\nCREATE TABLE wq_target (tid integer not null, balance integer DEFAULT -1)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('wq_target', 'tid', chunk_time_interval => 3);\n   create_hypertable    \n------------------------\n (4,public,wq_target,t)\n\nCREATE TABLE wq_source (balance integer, sid integer)\n  WITH (autovacuum_enabled=off);\nINSERT INTO wq_source (sid, balance) VALUES (1, 100);\n-- some complex joins on the source side\nCREATE TABLE cj_target (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('cj_target', 'tid', chunk_time_interval => 3);\n   create_hypertable    \n------------------------\n (5,public,cj_target,t)\n\nCREATE TABLE cj_source1 (sid1 integer, scat integer, delta integer)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE cj_source2 (sid2 integer, sval text)\n  WITH (autovacuum_enabled=off);\nINSERT INTO cj_source1 VALUES (1, 10, 100);\nINSERT INTO cj_source1 VALUES (1, 20, 200);\nINSERT INTO cj_source1 VALUES (2, 20, 300);\nINSERT INTO cj_source1 VALUES (3, 10, 400);\nINSERT INTO cj_source2 VALUES (1, 'initial source2');\nINSERT INTO cj_source2 VALUES (2, 'initial source2');\nINSERT INTO cj_source2 VALUES (3, 'initial source2');\nCREATE TABLE fs_target (a int, b int, c text)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('fs_target', 'a', chunk_time_interval => 3);\n   create_hypertable    \n------------------------\n (6,public,fs_target,t)\n\n-- run tests on hypertable\n\\o :TEST_RESULTS_WITH_HYPERTABLE\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n--\n-- Errors\n--\nMERGE INTO target t RANDOMWORD\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:12: ERROR:  syntax error at or near \"RANDOMWORD\"\nLINE 1: MERGE INTO target t RANDOMWORD\n                            ^\n-- MATCHED/INSERT error\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:18: ERROR:  syntax error at or near \"INSERT\"\nLINE 5:  INSERT DEFAULT VALUES;\n         ^\n-- incorrectly specifying INTO target\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT INTO target DEFAULT VALUES;\npsql:include/ts_merge_query.sql:24: ERROR:  syntax error at or near \"INTO\"\nLINE 5:  INSERT INTO target DEFAULT VALUES;\n                ^\n-- Multiple VALUES clause\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (1,1), (2,2);\npsql:include/ts_merge_query.sql:30: ERROR:  syntax error at or near \",\"\nLINE 5:  INSERT VALUES (1,1), (2,2);\n                            ^\n-- SELECT query for INSERT\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT SELECT (1, 1);\npsql:include/ts_merge_query.sql:36: ERROR:  syntax error at or near \"SELECT\"\nLINE 5:  INSERT SELECT (1, 1);\n                ^\n-- NOT MATCHED/UPDATE\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:42: ERROR:  syntax error at or near \"UPDATE\"\nLINE 5:  UPDATE SET balance = 0;\n         ^\n-- UPDATE tablename\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE target SET balance = 0;\npsql:include/ts_merge_query.sql:48: ERROR:  syntax error at or near \"target\"\nLINE 5:  UPDATE target SET balance = 0;\n                ^\n-- source and target names the same\nMERGE INTO target\nUSING target\nON tid = tid\nWHEN MATCHED THEN DO NOTHING;\npsql:include/ts_merge_query.sql:53: ERROR:  name \"target\" specified more than once\nDETAIL:  The name is used both as MERGE target table and data source.\n-- used in a CTE\nWITH foo AS (\n  MERGE INTO target USING source ON (true)\n  WHEN MATCHED THEN DELETE\n) SELECT * FROM foo;\npsql:include/ts_merge_query.sql:58: ERROR:  WITH query \"foo\" does not have a RETURNING clause\nLINE 4: ) SELECT * FROM foo;\n                        ^\n-- used in COPY\nCOPY (\n  MERGE INTO target USING source ON (true)\n  WHEN MATCHED THEN DELETE\n) TO stdout;\npsql:include/ts_merge_query.sql:63: ERROR:  COPY query must have a RETURNING clause\n-- unsupported relation types\n-- view\nCREATE VIEW tv AS SELECT * FROM target;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\nDROP VIEW tv;\n-- materialized view\nCREATE MATERIALIZED VIEW mv AS SELECT * FROM target;\nMERGE INTO mv t\nUSING source s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:81: ERROR:  cannot execute MERGE on relation \"mv\"\nDETAIL:  This operation is not supported for materialized views.\nDROP MATERIALIZED VIEW mv;\n-- permissions\nMERGE INTO target\nUSING source2\nON target.tid = source2.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:90: ERROR:  permission denied for table source2\nGRANT INSERT ON target TO regress_merge_no_privs;\nSET SESSION AUTHORIZATION regress_merge_no_privs;\nMERGE INTO target\nUSING source2\nON target.tid = source2.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:99: ERROR:  permission denied for table target\nGRANT UPDATE ON target2 TO regress_merge_privs;\nSET SESSION AUTHORIZATION regress_merge_privs;\nMERGE INTO target2\nUSING source\nON target2.tid = source.sid\nWHEN MATCHED THEN\n\tDELETE;\npsql:include/ts_merge_query.sql:108: ERROR:  permission denied for table target2\nMERGE INTO target2\nUSING source\nON target2.tid = source.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:114: ERROR:  permission denied for table target2\n-- check if the target can be accessed from source relation subquery; we should\n-- not be able to do so\nMERGE INTO target t\nUSING (SELECT * FROM source WHERE t.tid > sid) s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:122: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 2: USING (SELECT * FROM source WHERE t.tid > sid) s\n                                          ^\nDETAIL:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\n--\n-- initial tests\n--\n-- zero rows in source has no effect\nMERGE INTO target\nUSING source\nON target.tid = source.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\nROLLBACK;\n-- insert some non-matching source rows to work from\nINSERT INTO source VALUES (4, 40);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tDO NOTHING;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (5, 50);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- index plans\nINSERT INTO target SELECT generate_series(1000,2500), 0;\nALTER TABLE target ADD PRIMARY KEY (tid);\nANALYZE target;\nDELETE FROM target WHERE tid > 100;\nANALYZE target;\n-- insert some matching source rows to work from\nINSERT INTO source VALUES (2, 5);\nINSERT INTO source VALUES (3, 20);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n-- equivalent of an UPDATE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- equivalent of a DELETE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDO NOTHING;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, NULL);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- duplicate source row causes multiple target row update ERROR\nINSERT INTO source VALUES (2, 5);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:241: ERROR:  MERGE command cannot affect row a second time\nHINT:  Ensure that not more than one source row matches any one target row.\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\npsql:include/ts_merge_query.sql:249: ERROR:  MERGE command cannot affect row a second time\nHINT:  Ensure that not more than one source row matches any one target row.\nROLLBACK;\n-- remove duplicate MATCHED data from source data\nDELETE FROM source WHERE sid = 2;\nINSERT INTO source VALUES (2, 5);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n-- duplicate source row on INSERT should fail because of target_pkey\nINSERT INTO source VALUES (4, 40);\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n  INSERT VALUES (4, NULL);\npsql:include/ts_merge_query.sql:265: ERROR:  duplicate key value violates unique constraint \"2_2_target_pkey\"\nDETAIL:  Key (tid)=(4) already exists.\nSELECT * FROM target ORDER BY tid;\npsql:include/ts_merge_query.sql:266: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\n-- remove duplicate NOT MATCHED data from source data\nDELETE FROM source WHERE sid = 4;\nINSERT INTO source VALUES (4, 40);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n-- remove constraints\nalter table target drop CONSTRAINT target_pkey;\nalter table target alter column tid drop not null;\npsql:include/ts_merge_query.sql:277: ERROR:  cannot drop not-null constraint from a time-partitioned column\n-- multiple actions\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, 4)\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- should be equivalent\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, 4);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- column references\n-- do a simple equivalent of an UPDATE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance + s.delta;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- do a simple equivalent of an INSERT SELECT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- and again with duplicate source rows\nINSERT INTO source VALUES (5, 50);\nINSERT INTO source VALUES (5, 50);\n-- do a simple equivalent of an INSERT SELECT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n  INSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- removing duplicate source rows\nDELETE FROM source WHERE sid = 5;\n-- and again with explicitly identified column list\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- and again with a subtle error: referring to non-existent target row for NOT MATCHED\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (t.tid, s.delta);\npsql:include/ts_merge_query.sql:356: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 5:  INSERT (tid, balance) VALUES (t.tid, s.delta);\n                                       ^\nDETAIL:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\n-- and again with a constant ON clause\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON (SELECT true)\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (t.tid, s.delta);\npsql:include/ts_merge_query.sql:364: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 5:  INSERT (tid, balance) VALUES (t.tid, s.delta);\n                                       ^\nDETAIL:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\nSELECT * FROM target ORDER BY tid;\npsql:include/ts_merge_query.sql:365: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\n-- now the classic UPSERT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance + s.delta\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- this time with a FALSE condition\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND FALSE THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\n-- this time with an actual condition which returns false\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance <> 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\nBEGIN;\n-- and now with a condition which returns true\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\nROLLBACK;\n-- conditions in the NOT MATCHED clause can only refer to source columns\nBEGIN;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND t.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\npsql:include/ts_merge_query.sql:408: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 3: WHEN NOT MATCHED AND t.balance = 100 THEN\n                             ^\nDETAIL:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\nSELECT * FROM wq_target;\npsql:include/ts_merge_query.sql:409: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\n-- conditions in MATCHED clause can refer to both source and target\nSELECT * FROM wq_source;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND s.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n-- check if AND works\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 AND s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 AND s.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n-- check if OR works\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 OR s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 199 OR s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n-- check source-side whole-row references\nBEGIN;\nMERGE INTO wq_target t\nUSING wq_source s ON (t.tid = s.sid)\nWHEN matched and t = s or t.tid = s.sid THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nROLLBACK;\n-- check if subqueries work in the conditions?\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance > (SELECT max(balance) FROM target) THEN\n\tUPDATE SET balance = t.balance + s.balance;\n-- check if we can access system columns in the conditions\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.xmin = t.xmax THEN\n\tUPDATE SET balance = t.balance + s.balance;\npsql:include/ts_merge_query.sql:477: ERROR:  cannot use system column \"xmin\" in MERGE WHEN condition\nLINE 3: WHEN MATCHED AND t.xmin = t.xmax THEN\n                         ^\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.tableoid >= 0 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nDROP TABLE wq_target CASCADE;\nDROP TABLE wq_source;\n-- test triggers\ncreate or replace function merge_trigfunc () returns trigger\nlanguage plpgsql as\n$$\nDECLARE\n\tline text;\nBEGIN\n\tSELECT INTO line format('%s %s %s trigger%s',\n\t\tTG_WHEN, TG_OP, TG_LEVEL, CASE\n\t\tWHEN TG_OP = 'INSERT' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s', NEW)\n\t\tWHEN TG_OP = 'UPDATE' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s -> %s', OLD, NEW)\n\t\tWHEN TG_OP = 'DELETE' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s', OLD)\n\t\tEND);\n\n\tRAISE NOTICE '%', line;\n\tIF (TG_WHEN = 'BEFORE' AND TG_LEVEL = 'ROW') THEN\n\t\tIF (TG_OP = 'DELETE') THEN\n\t\t\tRETURN OLD;\n\t\tELSE\n\t\t\tRETURN NEW;\n\t\tEND IF;\n\tELSE\n\t\tRETURN NULL;\n\tEND IF;\nEND;\n$$;\nCREATE TRIGGER merge_bsi BEFORE INSERT ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bsu BEFORE UPDATE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bsd BEFORE DELETE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asi AFTER INSERT ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asu AFTER UPDATE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asd AFTER DELETE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bri BEFORE INSERT ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bru BEFORE UPDATE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_brd BEFORE DELETE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_ari AFTER INSERT ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_aru AFTER UPDATE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_ard AFTER DELETE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\n-- now the classic UPSERT, with a DELETE\nBEGIN;\nUPDATE target SET balance = 0 WHERE tid = 3;\n--EXPLAIN (ANALYZE ON, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF)\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND t.balance > s.delta THEN\n\tUPDATE SET balance = t.balance - s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- Test behavior of triggers that turn UPDATE/DELETE into no-ops\ncreate or replace function skip_merge_op() returns trigger\nlanguage plpgsql as\n$$\nBEGIN\n\tRETURN NULL;\nEND;\n$$;\nSELECT * FROM target full outer join source on (sid = tid);\ncreate trigger merge_skip BEFORE INSERT OR UPDATE or DELETE\n  ON target FOR EACH ROW EXECUTE FUNCTION skip_merge_op();\nDO $$\nDECLARE\n  result integer;\nBEGIN\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND s.sid = 3 THEN UPDATE SET balance = t.balance + s.delta\nWHEN MATCHED THEN DELETE\nWHEN NOT MATCHED THEN INSERT VALUES (sid, delta);\nIF FOUND THEN\n  RAISE NOTICE 'Found';\nELSE\n  RAISE NOTICE 'Not found';\nEND IF;\nGET DIAGNOSTICS result := ROW_COUNT;\nRAISE NOTICE 'ROW_COUNT = %', result;\nEND;\n$$;\nSELECT * FROM target FULL OUTER JOIN source ON (sid = tid);\nDROP TRIGGER merge_skip ON target;\nDROP FUNCTION skip_merge_op();\n-- test from PL/pgSQL\n-- make sure MERGE INTO isn't interpreted to mean returning variables like SELECT INTO\nBEGIN;\nDO LANGUAGE plpgsql $$\nBEGIN\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND t.balance > s.delta THEN\n\tUPDATE SET balance = t.balance - s.delta;\nEND;\n$$;\nROLLBACK;\n--source constants\nBEGIN;\nMERGE INTO target t\nUSING (SELECT 9 AS sid, 57 AS delta) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n--source query\nBEGIN;\nMERGE INTO target t\nUSING (SELECT sid, delta FROM source WHERE delta > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING (SELECT sid, delta as newname FROM source WHERE delta > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.newname);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n--self-merge\nBEGIN;\nMERGE INTO target t1\nUSING target t2\nON t1.tid = t2.tid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t1.balance + t2.balance\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (t2.tid, t2.balance);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING (SELECT tid as sid, balance as delta FROM target WHERE balance > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING\n(SELECT sid, max(delta) AS delta\n FROM source\n GROUP BY sid\n HAVING count(*) = 1\n ORDER BY sid ASC) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- plpgsql parameters and results\nBEGIN;\nCREATE FUNCTION merge_func (p_id integer, p_bal integer)\nRETURNS INTEGER\nLANGUAGE plpgsql\nAS $$\nDECLARE\n result integer;\nBEGIN\nMERGE INTO target t\nUSING (SELECT p_id AS sid) AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance - p_bal;\nIF FOUND THEN\n\tGET DIAGNOSTICS result := ROW_COUNT;\nEND IF;\nRETURN result;\nEND;\n$$;\nSELECT merge_func(3, 4);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- PREPARE\nBEGIN;\nprepare foom as merge into target t using (select 1 as sid) s on (t.tid = s.sid) when matched then update set balance = 1;\npsql:include/ts_merge_query.sql:685: ERROR:  prepared statement \"foom\" already exists\nexecute foom;\npsql:include/ts_merge_query.sql:686: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\nBEGIN;\nPREPARE foom2 (integer, integer) AS\nMERGE INTO target t\nUSING (SELECT 1) s\nON t.tid = $1\nWHEN MATCHED THEN\nUPDATE SET balance = $2;\npsql:include/ts_merge_query.sql:695: ERROR:  prepared statement \"foom2\" already exists\n--EXPLAIN (ANALYZE ON, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF)\nexecute foom2 (1, 1);\npsql:include/ts_merge_query.sql:697: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\n-- subqueries in source relation\nBEGIN;\nMERGE INTO sq_target t\nUSING (SELECT * FROM sq_source) s\nON tid = sid\nWHEN MATCHED AND t.balance > delta THEN\n\tUPDATE SET balance = t.balance + delta;\nSELECT * FROM sq_target ORDER BY tid;\nROLLBACK;\n-- try a view\nCREATE VIEW v AS SELECT * FROM sq_source WHERE sid < 2;\nBEGIN;\nMERGE INTO sq_target\nUSING v\nON tid = sid\nWHEN MATCHED THEN\n    UPDATE SET balance = v.balance + delta;\nSELECT * FROM sq_target ORDER BY tid;\nROLLBACK;\n-- ambiguous reference to a column\nBEGIN;\nMERGE INTO sq_target\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\npsql:include/ts_merge_query.sql:732: ERROR:  column reference \"balance\" is ambiguous\nLINE 5:     UPDATE SET balance = balance + delta\n                                 ^\nROLLBACK;\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\nSELECT * FROM sq_target;\nROLLBACK;\n-- CTEs\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nWITH targq AS (\n\tSELECT * FROM v\n)\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\nROLLBACK;\n-- RETURNING\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE\nRETURNING *;\nROLLBACK;\n-- PG17-specific tests for views, returning and merge_action. These throw syntax errors for previous versions of Postgres.\n-- However, since the error is the same for both hypertables and regular tables, this test should still pass for previous versions.\n-- RETURNING\nINSERT INTO source(sid, delta) VALUES(1, 40), (5, 50);\nBEGIN;\nMERGE INTO target t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta);\nSELECT * from target;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 1 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta)\nRETURNING merge_action(), t.*;\nROLLBACK;\n-- Views\nCREATE VIEW tv AS SELECT * FROM target;\nBEGIN;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta);\nSELECT * from tv;\nSELECT * from target; -- should also update the underlying table\nROLLBACK;\nBEGIN;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta)\nRETURNING merge_action(), t.*;\nROLLBACK;\nDROP VIEW tv;\nDELETE FROM source where sid in (1, 5);\n-- EXPLAIN\nCREATE TABLE ex_mtarget (a int, b int)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE ex_msource (a int, b int)\n  WITH (autovacuum_enabled=off);\nINSERT INTO ex_mtarget SELECT i, i*10 FROM generate_series(1,100,2) i;\nINSERT INTO ex_msource SELECT i, i*10 FROM generate_series(1,100,1) i;\nCREATE FUNCTION explain_merge(query text) RETURNS SETOF text\nLANGUAGE plpgsql AS\n$$\nDECLARE ln text;\nBEGIN\n    FOR ln IN\n        EXECUTE 'explain (analyze, timing off, summary off, buffers off, costs off) ' ||\n\t\t  query\n    LOOP\n        ln := regexp_replace(ln, '(Memory( Usage)?|Buckets|Batches): \\S*',  '\\1: xxx', 'g');\n        RETURN NEXT ln;\n    END LOOP;\nEND;\n$$;\n-- only updates\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED THEN\n\tUPDATE SET b = t.b + 1');\n-- only updates to selected tuples\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1');\n-- updates + deletes\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1\nWHEN MATCHED AND t.a >= 10 AND t.a <= 20 THEN\n\tDELETE');\n-- only inserts\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN NOT MATCHED AND s.a < 10 THEN\n\tINSERT VALUES (a, b)');\n-- all three\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1\nWHEN MATCHED AND t.a >= 30 AND t.a <= 40 THEN\n\tDELETE\nWHEN NOT MATCHED AND s.a < 20 THEN\n\tINSERT VALUES (a, b)');\n-- nothing\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a AND t.a < -1000\nWHEN MATCHED AND t.a < 10 THEN\n\tDO NOTHING');\nDROP TABLE ex_msource, ex_mtarget;\nDROP FUNCTION explain_merge(text);\n-- Subqueries\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED THEN\n    UPDATE SET balance = (SELECT count(*) FROM sq_target);\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND (SELECT count(*) > 0 FROM sq_target) THEN\n    UPDATE SET balance = 42;\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid AND (SELECT count(*) > 0 FROM sq_target)\nWHEN MATCHED THEN\n    UPDATE SET balance = 42;\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\n--  Test RETURNING with subqueries\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid AND (SELECT count(*) > 0 FROM sq_target)\nWHEN MATCHED THEN\n    UPDATE SET balance = 42\nRETURNING *;\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\nDROP TABLE sq_target CASCADE;\nDROP TABLE sq_source CASCADE;\nCREATE TABLE pa_target (tid integer, balance float, val text)\n\tPARTITION BY LIST (tid);\nCREATE TABLE part1 PARTITION OF pa_target FOR VALUES IN (1,4)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part2 PARTITION OF pa_target FOR VALUES IN (2,5,6)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part3 PARTITION OF pa_target FOR VALUES IN (3,8,9)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part4 PARTITION OF pa_target DEFAULT\n  WITH (autovacuum_enabled=off);\nCREATE TABLE pa_source (sid integer, delta float);\n-- insert many rows to the source table\nINSERT INTO pa_source SELECT id, id * 10  FROM generate_series(1,14) AS id;\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT id, id * 100, 'initial' FROM generate_series(1,14,2) AS id;\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- same with a constant qual\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid AND tid = 1\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- try updating the partition key column\nBEGIN;\nCREATE FUNCTION merge_func() RETURNS integer LANGUAGE plpgsql AS $$\nDECLARE\n  result integer;\nBEGIN\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nIF FOUND THEN\n  GET DIAGNOSTICS result := ROW_COUNT;\nEND IF;\nRETURN result;\nEND;\n$$;\nSELECT merge_func();\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\nDROP TABLE pa_target CASCADE;\n-- The target table is partitioned in the same way, but this time by attaching\n-- partitions which have columns in different order, dropped columns etc.\nCREATE TABLE pa_target (tid integer, balance float, val text)\n\tPARTITION BY LIST (tid);\nCREATE TABLE part1 (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part2 (balance float, tid integer, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part3 (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part4 (extraid text, tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nALTER TABLE part4 DROP COLUMN extraid;\nALTER TABLE pa_target ATTACH PARTITION part1 FOR VALUES IN (1,4);\nALTER TABLE pa_target ATTACH PARTITION part2 FOR VALUES IN (2,5,6);\nALTER TABLE pa_target ATTACH PARTITION part3 FOR VALUES IN (3,8,9);\nALTER TABLE pa_target ATTACH PARTITION part4 DEFAULT;\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT id, id * 100, 'initial' FROM generate_series(1,14,2) AS id;\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- same with a constant qual\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid AND tid IN (1, 5)\n  WHEN MATCHED AND tid % 5 = 0 THEN DELETE\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- try updating the partition key column\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\nDROP TABLE pa_source;\nDROP TABLE pa_target CASCADE;\n-- Sub-partitioning\nCREATE TABLE pa_target (logts timestamp, tid integer, balance float, val text)\n\tPARTITION BY RANGE (logts);\nCREATE TABLE part_m01 PARTITION OF pa_target\n\tFOR VALUES FROM ('2017-01-01') TO ('2017-02-01')\n\tPARTITION BY LIST (tid);\nCREATE TABLE part_m01_odd PARTITION OF part_m01\n\tFOR VALUES IN (1,3,5,7,9) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m01_even PARTITION OF part_m01\n\tFOR VALUES IN (2,4,6,8) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m02 PARTITION OF pa_target\n\tFOR VALUES FROM ('2017-02-01') TO ('2017-03-01')\n\tPARTITION BY LIST (tid);\nCREATE TABLE part_m02_odd PARTITION OF part_m02\n\tFOR VALUES IN (1,3,5,7,9) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m02_even PARTITION OF part_m02\n\tFOR VALUES IN (2,4,6,8) WITH (autovacuum_enabled=off);\nCREATE TABLE pa_source (sid integer, delta float)\n  WITH (autovacuum_enabled=off);\n-- insert many rows to the source table\nINSERT INTO pa_source SELECT id, id * 10  FROM generate_series(1,14) AS id;\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT '2017-01-31', id, id * 100, 'initial' FROM generate_series(1,9,3) AS id;\nINSERT INTO pa_target SELECT '2017-02-28', id, id * 100, 'initial' FROM generate_series(2,9,3) AS id;\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING (SELECT '2017-01-15' AS slogts, * FROM pa_source WHERE sid < 10) s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (slogts::timestamp, sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\nDROP TABLE pa_source;\nDROP TABLE pa_target CASCADE;\n-- some complex joins on the source side\n-- source relation is an unaliased join\nMERGE INTO cj_target t\nUSING cj_source1 s1\n\tINNER JOIN cj_source2 s2 ON sid1 = sid2\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid1, delta, sval);\n-- try accessing columns from either side of the source join\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2 AND scat = 20\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid2, delta, sval)\nWHEN MATCHED THEN\n\tDELETE;\n-- some simple expressions in INSERT targetlist\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid2, delta + scat, sval)\nWHEN MATCHED THEN\n\tUPDATE SET val = val || ' updated by merge';\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2 AND scat = 20\nON t.tid = sid1\nWHEN MATCHED THEN\n\tUPDATE SET val = val || ' ' || delta::text;\nSELECT * FROM cj_target ORDER BY tid;\nALTER TABLE cj_source1 RENAME COLUMN sid1 TO sid;\nALTER TABLE cj_source2 RENAME COLUMN sid2 TO sid;\nTRUNCATE cj_target;\nMERGE INTO cj_target t\nUSING cj_source1 s1\n\tINNER JOIN cj_source2 s2 ON s1.sid = s2.sid\nON t.tid = s1.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s2.sid, delta, sval);\nDROP TABLE cj_source2, cj_source1;\nDROP TABLE cj_target CASCADE;\n-- Function scans\nMERGE INTO fs_target t\nUSING generate_series(1,100,1) AS id\nON t.a = id\nWHEN MATCHED THEN\n\tUPDATE SET b = b + id\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (id, -1);\nMERGE INTO fs_target t\nUSING generate_series(1,100,2) AS id\nON t.a = id\nWHEN MATCHED THEN\n\tUPDATE SET b = b + id, c = 'updated '|| id.*::text\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (id, -1, 'inserted ' || id.*::text);\nSELECT count(*) FROM fs_target;\nDROP TABLE fs_target CASCADE;\n-- SERIALIZABLE test\n-- handled in isolation tests\n-- Inheritance-based partitioning\nCREATE TABLE measurement (\n    city_id         int not null,\n    logdate         date not null,\n    peaktemp        int,\n    unitsales       int\n) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2006m02 (\n    CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )\n) INHERITS (measurement) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2006m03 (\n    CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )\n) INHERITS (measurement) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2007m01 (\n    filler          text,\n    peaktemp        int,\n    logdate         date not null,\n    city_id         int not null,\n    unitsales       int\n    CHECK ( logdate >= DATE '2007-01-01' AND logdate < DATE '2007-02-01')\n) WITH (autovacuum_enabled=off);\nALTER TABLE measurement_y2007m01 DROP COLUMN filler;\nALTER TABLE measurement_y2007m01 INHERIT measurement;\nINSERT INTO measurement VALUES (0, '2005-07-21', 5, 15);\nCREATE OR REPLACE FUNCTION measurement_insert_trigger()\nRETURNS TRIGGER AS $$\nBEGIN\n    IF ( NEW.logdate >= DATE '2006-02-01' AND\n         NEW.logdate < DATE '2006-03-01' ) THEN\n        INSERT INTO measurement_y2006m02 VALUES (NEW.*);\n    ELSIF ( NEW.logdate >= DATE '2006-03-01' AND\n            NEW.logdate < DATE '2006-04-01' ) THEN\n        INSERT INTO measurement_y2006m03 VALUES (NEW.*);\n    ELSIF ( NEW.logdate >= DATE '2007-01-01' AND\n            NEW.logdate < DATE '2007-02-01' ) THEN\n        INSERT INTO measurement_y2007m01 (city_id, logdate, peaktemp, unitsales)\n            VALUES (NEW.*);\n    ELSE\n        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';\n    END IF;\n    RETURN NULL;\nEND;\n$$ LANGUAGE plpgsql ;\nCREATE TRIGGER insert_measurement_trigger\n    BEFORE INSERT ON measurement\n    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();\nINSERT INTO measurement VALUES (1, '2006-02-10', 35, 10);\nINSERT INTO measurement VALUES (1, '2006-02-16', 45, 20);\nINSERT INTO measurement VALUES (1, '2006-03-17', 25, 10);\nINSERT INTO measurement VALUES (1, '2006-03-27', 15, 40);\nINSERT INTO measurement VALUES (1, '2007-01-15', 10, 10);\nINSERT INTO measurement VALUES (1, '2007-01-17', 10, 10);\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate;\nCREATE TABLE new_measurement (LIKE measurement) WITH (autovacuum_enabled=off);\nINSERT INTO new_measurement VALUES (0, '2005-07-21', 25, 20);\nINSERT INTO new_measurement VALUES (1, '2006-03-01', 20, 10);\nINSERT INTO new_measurement VALUES (1, '2006-02-16', 50, 10);\nINSERT INTO new_measurement VALUES (2, '2006-02-10', 20, 20);\nINSERT INTO new_measurement VALUES (1, '2006-03-27', NULL, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-17', NULL, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-15', 5, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-16', 10, 10);\nBEGIN;\nMERGE INTO ONLY measurement m\n USING new_measurement nm ON\n      (m.city_id = nm.city_id and m.logdate=nm.logdate)\nWHEN MATCHED AND nm.peaktemp IS NULL THEN DELETE\nWHEN MATCHED THEN UPDATE\n     SET peaktemp = greatest(m.peaktemp, nm.peaktemp),\n        unitsales = m.unitsales + coalesce(nm.unitsales, 0)\nWHEN NOT MATCHED THEN INSERT\n     (city_id, logdate, peaktemp, unitsales)\n   VALUES (city_id, logdate, peaktemp, unitsales);\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate, peaktemp;\nROLLBACK;\nMERGE into measurement m\n USING new_measurement nm ON\n      (m.city_id = nm.city_id and m.logdate=nm.logdate)\nWHEN MATCHED AND nm.peaktemp IS NULL THEN DELETE\nWHEN MATCHED THEN UPDATE\n     SET peaktemp = greatest(m.peaktemp, nm.peaktemp),\n        unitsales = m.unitsales + coalesce(nm.unitsales, 0)\nWHEN NOT MATCHED THEN INSERT\n     (city_id, logdate, peaktemp, unitsales)\n   VALUES (city_id, logdate, peaktemp, unitsales);\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate;\nBEGIN;\nMERGE INTO new_measurement nm\n USING ONLY measurement m ON\n      (nm.city_id = m.city_id and nm.logdate=m.logdate)\nWHEN MATCHED THEN DELETE;\nSELECT * FROM new_measurement ORDER BY city_id, logdate;\nROLLBACK;\nMERGE INTO new_measurement nm\n USING measurement m ON\n      (nm.city_id = m.city_id and nm.logdate=m.logdate)\nWHEN MATCHED THEN DELETE;\nSELECT * FROM new_measurement ORDER BY city_id, logdate;\nDROP TABLE measurement, new_measurement CASCADE;\nDROP FUNCTION measurement_insert_trigger();\nRESET SESSION AUTHORIZATION;\nDROP TABLE target CASCADE;\nDROP TABLE target2 CASCADE;\nDROP TABLE source, source2;\nDROP FUNCTION merge_trigfunc();\nREVOKE CREATE ON SCHEMA public FROM regress_merge_privs;\nDROP USER regress_merge_privs;\nDROP USER regress_merge_no_privs;\n\\o\n:DIFF_CMD\n"
  },
  {
    "path": "test/expected/ts_merge-18.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\\set ON_ERROR_STOP 0\n\\set VERBOSITY default\nSET client_min_messages TO error;\n\\set TEST_BASE_NAME ts_merge\nSELECT format('include/%s_load.sql', :'TEST_BASE_NAME') AS \"TEST_LOAD_NAME\",\n    format('include/%s_load_ht.sql', :'TEST_BASE_NAME') AS \"TEST_LOAD_HT_NAME\",\n    format('include/%s_query.sql', :'TEST_BASE_NAME') AS \"TEST_QUERY_NAME\",\n    format('%s/results/%s_ht_results.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') AS \"TEST_RESULTS_WITH_HYPERTABLE\",\n    format('%s/results/%s_results.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') AS \"TEST_RESULTS_WITH_NO_HYPERTABLE\" \\gset\nSELECT format('\\! diff -u --label \"Base pg table results\" --label \"Hypertable results\" %s %s', :'TEST_RESULTS_WITH_HYPERTABLE', :'TEST_RESULTS_WITH_NO_HYPERTABLE') AS \"DIFF_CMD\" \\gset\n\\ir :TEST_LOAD_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE USER regress_merge_privs;\nCREATE USER regress_merge_no_privs;\nDROP TABLE IF EXISTS target;\nDROP TABLE IF EXISTS source;\nCREATE TABLE target (tid integer, balance integer)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE source (sid integer, delta integer) -- no index\n  WITH (autovacuum_enabled=off);\nINSERT INTO target VALUES (1, 10);\nINSERT INTO target VALUES (2, 20);\nINSERT INTO target VALUES (3, 30);\nSELECT t.ctid is not null as matched, t.*, s.* FROM source s FULL OUTER JOIN target t ON s.sid = t.tid ORDER BY t.tid, s.sid;\n matched | tid | balance | sid | delta \n---------+-----+---------+-----+-------\n t       |   1 |      10 |     |      \n t       |   2 |      20 |     |      \n t       |   3 |      30 |     |      \n\nALTER TABLE target OWNER TO regress_merge_privs;\nALTER TABLE source OWNER TO regress_merge_privs;\nCREATE TABLE target2 (tid integer, balance integer)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE source2 (sid integer, delta integer)\n  WITH (autovacuum_enabled=off);\nALTER TABLE target2 OWNER TO regress_merge_no_privs;\nALTER TABLE source2 OWNER TO regress_merge_no_privs;\nGRANT INSERT ON target TO regress_merge_no_privs;\nGRANT CREATE ON SCHEMA public TO regress_merge_privs;\nSET SESSION AUTHORIZATION regress_merge_privs;\nCREATE TABLE sq_target (tid integer NOT NULL, balance integer)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE sq_source (delta integer, sid integer, balance integer DEFAULT 0)\n  WITH (autovacuum_enabled=off);\nINSERT INTO sq_target(tid, balance) VALUES (1,100), (2,200), (3,300);\nINSERT INTO sq_source(sid, delta) VALUES (1,10), (2,20), (4,40);\n-- conditional WHEN clause\nCREATE TABLE wq_target (tid integer not null, balance integer DEFAULT -1)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE wq_source (balance integer, sid integer)\n  WITH (autovacuum_enabled=off);\nINSERT INTO wq_source (sid, balance) VALUES (1, 100);\nCREATE TABLE cj_target (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE cj_source1 (sid1 integer, scat integer, delta integer)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE cj_source2 (sid2 integer, sval text)\n  WITH (autovacuum_enabled=off);\nINSERT INTO cj_source1 VALUES (1, 10, 100);\nINSERT INTO cj_source1 VALUES (1, 20, 200);\nINSERT INTO cj_source1 VALUES (2, 20, 300);\nINSERT INTO cj_source1 VALUES (3, 10, 400);\nINSERT INTO cj_source2 VALUES (1, 'initial source2');\nINSERT INTO cj_source2 VALUES (2, 'initial source2');\nINSERT INTO cj_source2 VALUES (3, 'initial source2');\nCREATE TABLE fs_target (a int, b int, c text)\n  WITH (autovacuum_enabled=off);\n-- run tests on normal table\n\\o :TEST_RESULTS_WITH_NO_HYPERTABLE\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n--\n-- Errors\n--\nMERGE INTO target t RANDOMWORD\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:12: ERROR:  syntax error at or near \"RANDOMWORD\"\nLINE 1: MERGE INTO target t RANDOMWORD\n                            ^\n-- MATCHED/INSERT error\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:18: ERROR:  syntax error at or near \"INSERT\"\nLINE 5:  INSERT DEFAULT VALUES;\n         ^\n-- incorrectly specifying INTO target\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT INTO target DEFAULT VALUES;\npsql:include/ts_merge_query.sql:24: ERROR:  syntax error at or near \"INTO\"\nLINE 5:  INSERT INTO target DEFAULT VALUES;\n                ^\n-- Multiple VALUES clause\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (1,1), (2,2);\npsql:include/ts_merge_query.sql:30: ERROR:  syntax error at or near \",\"\nLINE 5:  INSERT VALUES (1,1), (2,2);\n                            ^\n-- SELECT query for INSERT\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT SELECT (1, 1);\npsql:include/ts_merge_query.sql:36: ERROR:  syntax error at or near \"SELECT\"\nLINE 5:  INSERT SELECT (1, 1);\n                ^\n-- NOT MATCHED/UPDATE\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:42: ERROR:  syntax error at or near \"UPDATE\"\nLINE 5:  UPDATE SET balance = 0;\n         ^\n-- UPDATE tablename\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE target SET balance = 0;\npsql:include/ts_merge_query.sql:48: ERROR:  syntax error at or near \"target\"\nLINE 5:  UPDATE target SET balance = 0;\n                ^\n-- source and target names the same\nMERGE INTO target\nUSING target\nON tid = tid\nWHEN MATCHED THEN DO NOTHING;\npsql:include/ts_merge_query.sql:53: ERROR:  name \"target\" specified more than once\nDETAIL:  The name is used both as MERGE target table and data source.\n-- used in a CTE\nWITH foo AS (\n  MERGE INTO target USING source ON (true)\n  WHEN MATCHED THEN DELETE\n) SELECT * FROM foo;\npsql:include/ts_merge_query.sql:58: ERROR:  WITH query \"foo\" does not have a RETURNING clause\nLINE 4: ) SELECT * FROM foo;\n                        ^\n-- used in COPY\nCOPY (\n  MERGE INTO target USING source ON (true)\n  WHEN MATCHED THEN DELETE\n) TO stdout;\npsql:include/ts_merge_query.sql:63: ERROR:  COPY query must have a RETURNING clause\n-- unsupported relation types\n-- view\nCREATE VIEW tv AS SELECT * FROM target;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\nDROP VIEW tv;\n-- materialized view\nCREATE MATERIALIZED VIEW mv AS SELECT * FROM target;\nMERGE INTO mv t\nUSING source s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:81: ERROR:  cannot execute MERGE on relation \"mv\"\nDETAIL:  This operation is not supported for materialized views.\nDROP MATERIALIZED VIEW mv;\n-- permissions\nMERGE INTO target\nUSING source2\nON target.tid = source2.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:90: ERROR:  permission denied for table source2\nGRANT INSERT ON target TO regress_merge_no_privs;\nSET SESSION AUTHORIZATION regress_merge_no_privs;\nMERGE INTO target\nUSING source2\nON target.tid = source2.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:99: ERROR:  permission denied for table target\nGRANT UPDATE ON target2 TO regress_merge_privs;\nSET SESSION AUTHORIZATION regress_merge_privs;\nMERGE INTO target2\nUSING source\nON target2.tid = source.sid\nWHEN MATCHED THEN\n\tDELETE;\npsql:include/ts_merge_query.sql:108: ERROR:  permission denied for table target2\nMERGE INTO target2\nUSING source\nON target2.tid = source.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:114: ERROR:  permission denied for table target2\n-- check if the target can be accessed from source relation subquery; we should\n-- not be able to do so\nMERGE INTO target t\nUSING (SELECT * FROM source WHERE t.tid > sid) s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:122: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 2: USING (SELECT * FROM source WHERE t.tid > sid) s\n                                          ^\nDETAIL:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\n--\n-- initial tests\n--\n-- zero rows in source has no effect\nMERGE INTO target\nUSING source\nON target.tid = source.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\nROLLBACK;\n-- insert some non-matching source rows to work from\nINSERT INTO source VALUES (4, 40);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tDO NOTHING;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (5, 50);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- index plans\nINSERT INTO target SELECT generate_series(1000,2500), 0;\nALTER TABLE target ADD PRIMARY KEY (tid);\nANALYZE target;\nDELETE FROM target WHERE tid > 100;\nANALYZE target;\n-- insert some matching source rows to work from\nINSERT INTO source VALUES (2, 5);\nINSERT INTO source VALUES (3, 20);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n-- equivalent of an UPDATE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- equivalent of a DELETE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDO NOTHING;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, NULL);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- duplicate source row causes multiple target row update ERROR\nINSERT INTO source VALUES (2, 5);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:241: ERROR:  MERGE command cannot affect row a second time\nHINT:  Ensure that not more than one source row matches any one target row.\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\npsql:include/ts_merge_query.sql:249: ERROR:  MERGE command cannot affect row a second time\nHINT:  Ensure that not more than one source row matches any one target row.\nROLLBACK;\n-- remove duplicate MATCHED data from source data\nDELETE FROM source WHERE sid = 2;\nINSERT INTO source VALUES (2, 5);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n-- duplicate source row on INSERT should fail because of target_pkey\nINSERT INTO source VALUES (4, 40);\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n  INSERT VALUES (4, NULL);\npsql:include/ts_merge_query.sql:265: ERROR:  duplicate key value violates unique constraint \"target_pkey\"\nDETAIL:  Key (tid)=(4) already exists.\nSELECT * FROM target ORDER BY tid;\npsql:include/ts_merge_query.sql:266: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\n-- remove duplicate NOT MATCHED data from source data\nDELETE FROM source WHERE sid = 4;\nINSERT INTO source VALUES (4, 40);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n-- remove constraints\nalter table target drop CONSTRAINT target_pkey;\nalter table target alter column tid drop not null;\n-- multiple actions\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, 4)\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- should be equivalent\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, 4);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- column references\n-- do a simple equivalent of an UPDATE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance + s.delta;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- do a simple equivalent of an INSERT SELECT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- and again with duplicate source rows\nINSERT INTO source VALUES (5, 50);\nINSERT INTO source VALUES (5, 50);\n-- do a simple equivalent of an INSERT SELECT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n  INSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- removing duplicate source rows\nDELETE FROM source WHERE sid = 5;\n-- and again with explicitly identified column list\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- and again with a subtle error: referring to non-existent target row for NOT MATCHED\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (t.tid, s.delta);\npsql:include/ts_merge_query.sql:356: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 5:  INSERT (tid, balance) VALUES (t.tid, s.delta);\n                                       ^\nDETAIL:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\n-- and again with a constant ON clause\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON (SELECT true)\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (t.tid, s.delta);\npsql:include/ts_merge_query.sql:364: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 5:  INSERT (tid, balance) VALUES (t.tid, s.delta);\n                                       ^\nDETAIL:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\nSELECT * FROM target ORDER BY tid;\npsql:include/ts_merge_query.sql:365: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\n-- now the classic UPSERT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance + s.delta\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- this time with a FALSE condition\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND FALSE THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\n-- this time with an actual condition which returns false\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance <> 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\nBEGIN;\n-- and now with a condition which returns true\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\nROLLBACK;\n-- conditions in the NOT MATCHED clause can only refer to source columns\nBEGIN;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND t.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\npsql:include/ts_merge_query.sql:408: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 3: WHEN NOT MATCHED AND t.balance = 100 THEN\n                             ^\nDETAIL:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\nSELECT * FROM wq_target;\npsql:include/ts_merge_query.sql:409: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\n-- conditions in MATCHED clause can refer to both source and target\nSELECT * FROM wq_source;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND s.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n-- check if AND works\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 AND s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 AND s.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n-- check if OR works\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 OR s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 199 OR s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n-- check source-side whole-row references\nBEGIN;\nMERGE INTO wq_target t\nUSING wq_source s ON (t.tid = s.sid)\nWHEN matched and t = s or t.tid = s.sid THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nROLLBACK;\n-- check if subqueries work in the conditions?\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance > (SELECT max(balance) FROM target) THEN\n\tUPDATE SET balance = t.balance + s.balance;\n-- check if we can access system columns in the conditions\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.xmin = t.xmax THEN\n\tUPDATE SET balance = t.balance + s.balance;\npsql:include/ts_merge_query.sql:477: ERROR:  cannot use system column \"xmin\" in MERGE WHEN condition\nLINE 3: WHEN MATCHED AND t.xmin = t.xmax THEN\n                         ^\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.tableoid >= 0 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nDROP TABLE wq_target CASCADE;\nDROP TABLE wq_source;\n-- test triggers\ncreate or replace function merge_trigfunc () returns trigger\nlanguage plpgsql as\n$$\nDECLARE\n\tline text;\nBEGIN\n\tSELECT INTO line format('%s %s %s trigger%s',\n\t\tTG_WHEN, TG_OP, TG_LEVEL, CASE\n\t\tWHEN TG_OP = 'INSERT' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s', NEW)\n\t\tWHEN TG_OP = 'UPDATE' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s -> %s', OLD, NEW)\n\t\tWHEN TG_OP = 'DELETE' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s', OLD)\n\t\tEND);\n\n\tRAISE NOTICE '%', line;\n\tIF (TG_WHEN = 'BEFORE' AND TG_LEVEL = 'ROW') THEN\n\t\tIF (TG_OP = 'DELETE') THEN\n\t\t\tRETURN OLD;\n\t\tELSE\n\t\t\tRETURN NEW;\n\t\tEND IF;\n\tELSE\n\t\tRETURN NULL;\n\tEND IF;\nEND;\n$$;\nCREATE TRIGGER merge_bsi BEFORE INSERT ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bsu BEFORE UPDATE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bsd BEFORE DELETE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asi AFTER INSERT ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asu AFTER UPDATE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asd AFTER DELETE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bri BEFORE INSERT ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bru BEFORE UPDATE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_brd BEFORE DELETE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_ari AFTER INSERT ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_aru AFTER UPDATE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_ard AFTER DELETE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\n-- now the classic UPSERT, with a DELETE\nBEGIN;\nUPDATE target SET balance = 0 WHERE tid = 3;\n--EXPLAIN (ANALYZE ON, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF)\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND t.balance > s.delta THEN\n\tUPDATE SET balance = t.balance - s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- Test behavior of triggers that turn UPDATE/DELETE into no-ops\ncreate or replace function skip_merge_op() returns trigger\nlanguage plpgsql as\n$$\nBEGIN\n\tRETURN NULL;\nEND;\n$$;\nSELECT * FROM target full outer join source on (sid = tid);\ncreate trigger merge_skip BEFORE INSERT OR UPDATE or DELETE\n  ON target FOR EACH ROW EXECUTE FUNCTION skip_merge_op();\nDO $$\nDECLARE\n  result integer;\nBEGIN\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND s.sid = 3 THEN UPDATE SET balance = t.balance + s.delta\nWHEN MATCHED THEN DELETE\nWHEN NOT MATCHED THEN INSERT VALUES (sid, delta);\nIF FOUND THEN\n  RAISE NOTICE 'Found';\nELSE\n  RAISE NOTICE 'Not found';\nEND IF;\nGET DIAGNOSTICS result := ROW_COUNT;\nRAISE NOTICE 'ROW_COUNT = %', result;\nEND;\n$$;\nSELECT * FROM target FULL OUTER JOIN source ON (sid = tid);\nDROP TRIGGER merge_skip ON target;\nDROP FUNCTION skip_merge_op();\n-- test from PL/pgSQL\n-- make sure MERGE INTO isn't interpreted to mean returning variables like SELECT INTO\nBEGIN;\nDO LANGUAGE plpgsql $$\nBEGIN\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND t.balance > s.delta THEN\n\tUPDATE SET balance = t.balance - s.delta;\nEND;\n$$;\nROLLBACK;\n--source constants\nBEGIN;\nMERGE INTO target t\nUSING (SELECT 9 AS sid, 57 AS delta) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n--source query\nBEGIN;\nMERGE INTO target t\nUSING (SELECT sid, delta FROM source WHERE delta > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING (SELECT sid, delta as newname FROM source WHERE delta > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.newname);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n--self-merge\nBEGIN;\nMERGE INTO target t1\nUSING target t2\nON t1.tid = t2.tid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t1.balance + t2.balance\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (t2.tid, t2.balance);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING (SELECT tid as sid, balance as delta FROM target WHERE balance > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING\n(SELECT sid, max(delta) AS delta\n FROM source\n GROUP BY sid\n HAVING count(*) = 1\n ORDER BY sid ASC) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- plpgsql parameters and results\nBEGIN;\nCREATE FUNCTION merge_func (p_id integer, p_bal integer)\nRETURNS INTEGER\nLANGUAGE plpgsql\nAS $$\nDECLARE\n result integer;\nBEGIN\nMERGE INTO target t\nUSING (SELECT p_id AS sid) AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance - p_bal;\nIF FOUND THEN\n\tGET DIAGNOSTICS result := ROW_COUNT;\nEND IF;\nRETURN result;\nEND;\n$$;\nSELECT merge_func(3, 4);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- PREPARE\nBEGIN;\nprepare foom as merge into target t using (select 1 as sid) s on (t.tid = s.sid) when matched then update set balance = 1;\nexecute foom;\nROLLBACK;\nBEGIN;\nPREPARE foom2 (integer, integer) AS\nMERGE INTO target t\nUSING (SELECT 1) s\nON t.tid = $1\nWHEN MATCHED THEN\nUPDATE SET balance = $2;\n--EXPLAIN (ANALYZE ON, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF)\nexecute foom2 (1, 1);\nROLLBACK;\n-- subqueries in source relation\nBEGIN;\nMERGE INTO sq_target t\nUSING (SELECT * FROM sq_source) s\nON tid = sid\nWHEN MATCHED AND t.balance > delta THEN\n\tUPDATE SET balance = t.balance + delta;\nSELECT * FROM sq_target ORDER BY tid;\nROLLBACK;\n-- try a view\nCREATE VIEW v AS SELECT * FROM sq_source WHERE sid < 2;\nBEGIN;\nMERGE INTO sq_target\nUSING v\nON tid = sid\nWHEN MATCHED THEN\n    UPDATE SET balance = v.balance + delta;\nSELECT * FROM sq_target ORDER BY tid;\nROLLBACK;\n-- ambiguous reference to a column\nBEGIN;\nMERGE INTO sq_target\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\npsql:include/ts_merge_query.sql:732: ERROR:  column reference \"balance\" is ambiguous\nLINE 5:     UPDATE SET balance = balance + delta\n                                 ^\nROLLBACK;\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\nSELECT * FROM sq_target;\nROLLBACK;\n-- CTEs\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nWITH targq AS (\n\tSELECT * FROM v\n)\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\nROLLBACK;\n-- RETURNING\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE\nRETURNING *;\nROLLBACK;\n-- PG17-specific tests for views, returning and merge_action. These throw syntax errors for previous versions of Postgres.\n-- However, since the error is the same for both hypertables and regular tables, this test should still pass for previous versions.\n-- RETURNING\nINSERT INTO source(sid, delta) VALUES(1, 40), (5, 50);\nBEGIN;\nMERGE INTO target t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta);\nSELECT * from target;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 1 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta)\nRETURNING merge_action(), t.*;\nROLLBACK;\n-- Views\nCREATE VIEW tv AS SELECT * FROM target;\nBEGIN;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta);\nSELECT * from tv;\nSELECT * from target; -- should also update the underlying table\nROLLBACK;\nBEGIN;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta)\nRETURNING merge_action(), t.*;\nROLLBACK;\nDROP VIEW tv;\nDELETE FROM source where sid in (1, 5);\n-- EXPLAIN\nCREATE TABLE ex_mtarget (a int, b int)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE ex_msource (a int, b int)\n  WITH (autovacuum_enabled=off);\nINSERT INTO ex_mtarget SELECT i, i*10 FROM generate_series(1,100,2) i;\nINSERT INTO ex_msource SELECT i, i*10 FROM generate_series(1,100,1) i;\nCREATE FUNCTION explain_merge(query text) RETURNS SETOF text\nLANGUAGE plpgsql AS\n$$\nDECLARE ln text;\nBEGIN\n    FOR ln IN\n        EXECUTE 'explain (analyze, timing off, summary off, buffers off, costs off) ' ||\n\t\t  query\n    LOOP\n        ln := regexp_replace(ln, '(Memory( Usage)?|Buckets|Batches): \\S*',  '\\1: xxx', 'g');\n        RETURN NEXT ln;\n    END LOOP;\nEND;\n$$;\n-- only updates\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED THEN\n\tUPDATE SET b = t.b + 1');\n-- only updates to selected tuples\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1');\n-- updates + deletes\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1\nWHEN MATCHED AND t.a >= 10 AND t.a <= 20 THEN\n\tDELETE');\n-- only inserts\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN NOT MATCHED AND s.a < 10 THEN\n\tINSERT VALUES (a, b)');\n-- all three\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1\nWHEN MATCHED AND t.a >= 30 AND t.a <= 40 THEN\n\tDELETE\nWHEN NOT MATCHED AND s.a < 20 THEN\n\tINSERT VALUES (a, b)');\n-- nothing\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a AND t.a < -1000\nWHEN MATCHED AND t.a < 10 THEN\n\tDO NOTHING');\nDROP TABLE ex_msource, ex_mtarget;\nDROP FUNCTION explain_merge(text);\n-- Subqueries\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED THEN\n    UPDATE SET balance = (SELECT count(*) FROM sq_target);\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND (SELECT count(*) > 0 FROM sq_target) THEN\n    UPDATE SET balance = 42;\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid AND (SELECT count(*) > 0 FROM sq_target)\nWHEN MATCHED THEN\n    UPDATE SET balance = 42;\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\n--  Test RETURNING with subqueries\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid AND (SELECT count(*) > 0 FROM sq_target)\nWHEN MATCHED THEN\n    UPDATE SET balance = 42\nRETURNING *;\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\nDROP TABLE sq_target CASCADE;\nDROP TABLE sq_source CASCADE;\nCREATE TABLE pa_target (tid integer, balance float, val text)\n\tPARTITION BY LIST (tid);\nCREATE TABLE part1 PARTITION OF pa_target FOR VALUES IN (1,4)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part2 PARTITION OF pa_target FOR VALUES IN (2,5,6)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part3 PARTITION OF pa_target FOR VALUES IN (3,8,9)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part4 PARTITION OF pa_target DEFAULT\n  WITH (autovacuum_enabled=off);\nCREATE TABLE pa_source (sid integer, delta float);\n-- insert many rows to the source table\nINSERT INTO pa_source SELECT id, id * 10  FROM generate_series(1,14) AS id;\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT id, id * 100, 'initial' FROM generate_series(1,14,2) AS id;\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- same with a constant qual\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid AND tid = 1\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- try updating the partition key column\nBEGIN;\nCREATE FUNCTION merge_func() RETURNS integer LANGUAGE plpgsql AS $$\nDECLARE\n  result integer;\nBEGIN\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nIF FOUND THEN\n  GET DIAGNOSTICS result := ROW_COUNT;\nEND IF;\nRETURN result;\nEND;\n$$;\nSELECT merge_func();\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\nDROP TABLE pa_target CASCADE;\n-- The target table is partitioned in the same way, but this time by attaching\n-- partitions which have columns in different order, dropped columns etc.\nCREATE TABLE pa_target (tid integer, balance float, val text)\n\tPARTITION BY LIST (tid);\nCREATE TABLE part1 (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part2 (balance float, tid integer, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part3 (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part4 (extraid text, tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nALTER TABLE part4 DROP COLUMN extraid;\nALTER TABLE pa_target ATTACH PARTITION part1 FOR VALUES IN (1,4);\nALTER TABLE pa_target ATTACH PARTITION part2 FOR VALUES IN (2,5,6);\nALTER TABLE pa_target ATTACH PARTITION part3 FOR VALUES IN (3,8,9);\nALTER TABLE pa_target ATTACH PARTITION part4 DEFAULT;\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT id, id * 100, 'initial' FROM generate_series(1,14,2) AS id;\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- same with a constant qual\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid AND tid IN (1, 5)\n  WHEN MATCHED AND tid % 5 = 0 THEN DELETE\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- try updating the partition key column\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\nDROP TABLE pa_source;\nDROP TABLE pa_target CASCADE;\n-- Sub-partitioning\nCREATE TABLE pa_target (logts timestamp, tid integer, balance float, val text)\n\tPARTITION BY RANGE (logts);\nCREATE TABLE part_m01 PARTITION OF pa_target\n\tFOR VALUES FROM ('2017-01-01') TO ('2017-02-01')\n\tPARTITION BY LIST (tid);\nCREATE TABLE part_m01_odd PARTITION OF part_m01\n\tFOR VALUES IN (1,3,5,7,9) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m01_even PARTITION OF part_m01\n\tFOR VALUES IN (2,4,6,8) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m02 PARTITION OF pa_target\n\tFOR VALUES FROM ('2017-02-01') TO ('2017-03-01')\n\tPARTITION BY LIST (tid);\nCREATE TABLE part_m02_odd PARTITION OF part_m02\n\tFOR VALUES IN (1,3,5,7,9) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m02_even PARTITION OF part_m02\n\tFOR VALUES IN (2,4,6,8) WITH (autovacuum_enabled=off);\nCREATE TABLE pa_source (sid integer, delta float)\n  WITH (autovacuum_enabled=off);\n-- insert many rows to the source table\nINSERT INTO pa_source SELECT id, id * 10  FROM generate_series(1,14) AS id;\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT '2017-01-31', id, id * 100, 'initial' FROM generate_series(1,9,3) AS id;\nINSERT INTO pa_target SELECT '2017-02-28', id, id * 100, 'initial' FROM generate_series(2,9,3) AS id;\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING (SELECT '2017-01-15' AS slogts, * FROM pa_source WHERE sid < 10) s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (slogts::timestamp, sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\nDROP TABLE pa_source;\nDROP TABLE pa_target CASCADE;\n-- some complex joins on the source side\n-- source relation is an unaliased join\nMERGE INTO cj_target t\nUSING cj_source1 s1\n\tINNER JOIN cj_source2 s2 ON sid1 = sid2\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid1, delta, sval);\n-- try accessing columns from either side of the source join\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2 AND scat = 20\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid2, delta, sval)\nWHEN MATCHED THEN\n\tDELETE;\n-- some simple expressions in INSERT targetlist\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid2, delta + scat, sval)\nWHEN MATCHED THEN\n\tUPDATE SET val = val || ' updated by merge';\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2 AND scat = 20\nON t.tid = sid1\nWHEN MATCHED THEN\n\tUPDATE SET val = val || ' ' || delta::text;\nSELECT * FROM cj_target ORDER BY tid;\nALTER TABLE cj_source1 RENAME COLUMN sid1 TO sid;\nALTER TABLE cj_source2 RENAME COLUMN sid2 TO sid;\nTRUNCATE cj_target;\nMERGE INTO cj_target t\nUSING cj_source1 s1\n\tINNER JOIN cj_source2 s2 ON s1.sid = s2.sid\nON t.tid = s1.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s2.sid, delta, sval);\nDROP TABLE cj_source2, cj_source1;\nDROP TABLE cj_target CASCADE;\n-- Function scans\nMERGE INTO fs_target t\nUSING generate_series(1,100,1) AS id\nON t.a = id\nWHEN MATCHED THEN\n\tUPDATE SET b = b + id\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (id, -1);\nMERGE INTO fs_target t\nUSING generate_series(1,100,2) AS id\nON t.a = id\nWHEN MATCHED THEN\n\tUPDATE SET b = b + id, c = 'updated '|| id.*::text\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (id, -1, 'inserted ' || id.*::text);\nSELECT count(*) FROM fs_target;\nDROP TABLE fs_target CASCADE;\n-- SERIALIZABLE test\n-- handled in isolation tests\n-- Inheritance-based partitioning\nCREATE TABLE measurement (\n    city_id         int not null,\n    logdate         date not null,\n    peaktemp        int,\n    unitsales       int\n) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2006m02 (\n    CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )\n) INHERITS (measurement) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2006m03 (\n    CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )\n) INHERITS (measurement) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2007m01 (\n    filler          text,\n    peaktemp        int,\n    logdate         date not null,\n    city_id         int not null,\n    unitsales       int\n    CHECK ( logdate >= DATE '2007-01-01' AND logdate < DATE '2007-02-01')\n) WITH (autovacuum_enabled=off);\nALTER TABLE measurement_y2007m01 DROP COLUMN filler;\nALTER TABLE measurement_y2007m01 INHERIT measurement;\nINSERT INTO measurement VALUES (0, '2005-07-21', 5, 15);\nCREATE OR REPLACE FUNCTION measurement_insert_trigger()\nRETURNS TRIGGER AS $$\nBEGIN\n    IF ( NEW.logdate >= DATE '2006-02-01' AND\n         NEW.logdate < DATE '2006-03-01' ) THEN\n        INSERT INTO measurement_y2006m02 VALUES (NEW.*);\n    ELSIF ( NEW.logdate >= DATE '2006-03-01' AND\n            NEW.logdate < DATE '2006-04-01' ) THEN\n        INSERT INTO measurement_y2006m03 VALUES (NEW.*);\n    ELSIF ( NEW.logdate >= DATE '2007-01-01' AND\n            NEW.logdate < DATE '2007-02-01' ) THEN\n        INSERT INTO measurement_y2007m01 (city_id, logdate, peaktemp, unitsales)\n            VALUES (NEW.*);\n    ELSE\n        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';\n    END IF;\n    RETURN NULL;\nEND;\n$$ LANGUAGE plpgsql ;\nCREATE TRIGGER insert_measurement_trigger\n    BEFORE INSERT ON measurement\n    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();\nINSERT INTO measurement VALUES (1, '2006-02-10', 35, 10);\nINSERT INTO measurement VALUES (1, '2006-02-16', 45, 20);\nINSERT INTO measurement VALUES (1, '2006-03-17', 25, 10);\nINSERT INTO measurement VALUES (1, '2006-03-27', 15, 40);\nINSERT INTO measurement VALUES (1, '2007-01-15', 10, 10);\nINSERT INTO measurement VALUES (1, '2007-01-17', 10, 10);\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate;\nCREATE TABLE new_measurement (LIKE measurement) WITH (autovacuum_enabled=off);\nINSERT INTO new_measurement VALUES (0, '2005-07-21', 25, 20);\nINSERT INTO new_measurement VALUES (1, '2006-03-01', 20, 10);\nINSERT INTO new_measurement VALUES (1, '2006-02-16', 50, 10);\nINSERT INTO new_measurement VALUES (2, '2006-02-10', 20, 20);\nINSERT INTO new_measurement VALUES (1, '2006-03-27', NULL, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-17', NULL, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-15', 5, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-16', 10, 10);\nBEGIN;\nMERGE INTO ONLY measurement m\n USING new_measurement nm ON\n      (m.city_id = nm.city_id and m.logdate=nm.logdate)\nWHEN MATCHED AND nm.peaktemp IS NULL THEN DELETE\nWHEN MATCHED THEN UPDATE\n     SET peaktemp = greatest(m.peaktemp, nm.peaktemp),\n        unitsales = m.unitsales + coalesce(nm.unitsales, 0)\nWHEN NOT MATCHED THEN INSERT\n     (city_id, logdate, peaktemp, unitsales)\n   VALUES (city_id, logdate, peaktemp, unitsales);\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate, peaktemp;\nROLLBACK;\nMERGE into measurement m\n USING new_measurement nm ON\n      (m.city_id = nm.city_id and m.logdate=nm.logdate)\nWHEN MATCHED AND nm.peaktemp IS NULL THEN DELETE\nWHEN MATCHED THEN UPDATE\n     SET peaktemp = greatest(m.peaktemp, nm.peaktemp),\n        unitsales = m.unitsales + coalesce(nm.unitsales, 0)\nWHEN NOT MATCHED THEN INSERT\n     (city_id, logdate, peaktemp, unitsales)\n   VALUES (city_id, logdate, peaktemp, unitsales);\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate;\nBEGIN;\nMERGE INTO new_measurement nm\n USING ONLY measurement m ON\n      (nm.city_id = m.city_id and nm.logdate=m.logdate)\nWHEN MATCHED THEN DELETE;\nSELECT * FROM new_measurement ORDER BY city_id, logdate;\nROLLBACK;\nMERGE INTO new_measurement nm\n USING measurement m ON\n      (nm.city_id = m.city_id and nm.logdate=m.logdate)\nWHEN MATCHED THEN DELETE;\nSELECT * FROM new_measurement ORDER BY city_id, logdate;\nDROP TABLE measurement, new_measurement CASCADE;\nDROP FUNCTION measurement_insert_trigger();\nRESET SESSION AUTHORIZATION;\nDROP TABLE target CASCADE;\nDROP TABLE target2 CASCADE;\nDROP TABLE source, source2;\nDROP FUNCTION merge_trigfunc();\nREVOKE CREATE ON SCHEMA public FROM regress_merge_privs;\nDROP USER regress_merge_privs;\nDROP USER regress_merge_no_privs;\n\\o\n\\ir :TEST_LOAD_HT_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE USER regress_merge_privs;\nCREATE USER regress_merge_no_privs;\nDROP TABLE IF EXISTS target;\nDROP TABLE IF EXISTS source;\nCREATE TABLE target (tid integer, balance integer)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('target', 'tid', chunk_time_interval => 3);\n  create_hypertable  \n---------------------\n (1,public,target,t)\n\nCREATE TABLE source (sid integer, delta integer) -- no index\n  WITH (autovacuum_enabled=off);\nINSERT INTO target VALUES (1, 10);\nINSERT INTO target VALUES (2, 20);\nINSERT INTO target VALUES (3, 30);\nSELECT t.ctid is not null as matched, t.*, s.* FROM source s FULL OUTER JOIN target t ON s.sid = t.tid ORDER BY t.tid, s.sid;\n matched | tid | balance | sid | delta \n---------+-----+---------+-----+-------\n t       |   1 |      10 |     |      \n t       |   2 |      20 |     |      \n t       |   3 |      30 |     |      \n\nALTER TABLE target OWNER TO regress_merge_privs;\nALTER TABLE source OWNER TO regress_merge_privs;\nCREATE TABLE target2 (tid integer, balance integer)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('target2', 'tid', chunk_time_interval => 3);\n  create_hypertable   \n----------------------\n (2,public,target2,t)\n\nCREATE TABLE source2 (sid integer, delta integer)\n  WITH (autovacuum_enabled=off);\nALTER TABLE target2 OWNER TO regress_merge_no_privs;\nALTER TABLE source2 OWNER TO regress_merge_no_privs;\nGRANT INSERT ON target TO regress_merge_no_privs;\nGRANT CREATE ON SCHEMA public TO regress_merge_privs;\nSET SESSION AUTHORIZATION regress_merge_privs;\nCREATE TABLE sq_target (tid integer NOT NULL, balance integer)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('sq_target', 'tid', chunk_time_interval => 3);\n   create_hypertable    \n------------------------\n (3,public,sq_target,t)\n\nCREATE TABLE sq_source (delta integer, sid integer, balance integer DEFAULT 0)\n  WITH (autovacuum_enabled=off);\nINSERT INTO sq_target(tid, balance) VALUES (1,100), (2,200), (3,300);\nINSERT INTO sq_source(sid, delta) VALUES (1,10), (2,20), (4,40);\n-- conditional WHEN clause\nCREATE TABLE wq_target (tid integer not null, balance integer DEFAULT -1)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('wq_target', 'tid', chunk_time_interval => 3);\n   create_hypertable    \n------------------------\n (4,public,wq_target,t)\n\nCREATE TABLE wq_source (balance integer, sid integer)\n  WITH (autovacuum_enabled=off);\nINSERT INTO wq_source (sid, balance) VALUES (1, 100);\n-- some complex joins on the source side\nCREATE TABLE cj_target (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('cj_target', 'tid', chunk_time_interval => 3);\n   create_hypertable    \n------------------------\n (5,public,cj_target,t)\n\nCREATE TABLE cj_source1 (sid1 integer, scat integer, delta integer)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE cj_source2 (sid2 integer, sval text)\n  WITH (autovacuum_enabled=off);\nINSERT INTO cj_source1 VALUES (1, 10, 100);\nINSERT INTO cj_source1 VALUES (1, 20, 200);\nINSERT INTO cj_source1 VALUES (2, 20, 300);\nINSERT INTO cj_source1 VALUES (3, 10, 400);\nINSERT INTO cj_source2 VALUES (1, 'initial source2');\nINSERT INTO cj_source2 VALUES (2, 'initial source2');\nINSERT INTO cj_source2 VALUES (3, 'initial source2');\nCREATE TABLE fs_target (a int, b int, c text)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('fs_target', 'a', chunk_time_interval => 3);\n   create_hypertable    \n------------------------\n (6,public,fs_target,t)\n\n-- run tests on hypertable\n\\o :TEST_RESULTS_WITH_HYPERTABLE\n\\ir :TEST_QUERY_NAME\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n--\n-- Errors\n--\nMERGE INTO target t RANDOMWORD\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:12: ERROR:  syntax error at or near \"RANDOMWORD\"\nLINE 1: MERGE INTO target t RANDOMWORD\n                            ^\n-- MATCHED/INSERT error\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:18: ERROR:  syntax error at or near \"INSERT\"\nLINE 5:  INSERT DEFAULT VALUES;\n         ^\n-- incorrectly specifying INTO target\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT INTO target DEFAULT VALUES;\npsql:include/ts_merge_query.sql:24: ERROR:  syntax error at or near \"INTO\"\nLINE 5:  INSERT INTO target DEFAULT VALUES;\n                ^\n-- Multiple VALUES clause\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (1,1), (2,2);\npsql:include/ts_merge_query.sql:30: ERROR:  syntax error at or near \",\"\nLINE 5:  INSERT VALUES (1,1), (2,2);\n                            ^\n-- SELECT query for INSERT\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT SELECT (1, 1);\npsql:include/ts_merge_query.sql:36: ERROR:  syntax error at or near \"SELECT\"\nLINE 5:  INSERT SELECT (1, 1);\n                ^\n-- NOT MATCHED/UPDATE\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:42: ERROR:  syntax error at or near \"UPDATE\"\nLINE 5:  UPDATE SET balance = 0;\n         ^\n-- UPDATE tablename\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE target SET balance = 0;\npsql:include/ts_merge_query.sql:48: ERROR:  syntax error at or near \"target\"\nLINE 5:  UPDATE target SET balance = 0;\n                ^\n-- source and target names the same\nMERGE INTO target\nUSING target\nON tid = tid\nWHEN MATCHED THEN DO NOTHING;\npsql:include/ts_merge_query.sql:53: ERROR:  name \"target\" specified more than once\nDETAIL:  The name is used both as MERGE target table and data source.\n-- used in a CTE\nWITH foo AS (\n  MERGE INTO target USING source ON (true)\n  WHEN MATCHED THEN DELETE\n) SELECT * FROM foo;\npsql:include/ts_merge_query.sql:58: ERROR:  WITH query \"foo\" does not have a RETURNING clause\nLINE 4: ) SELECT * FROM foo;\n                        ^\n-- used in COPY\nCOPY (\n  MERGE INTO target USING source ON (true)\n  WHEN MATCHED THEN DELETE\n) TO stdout;\npsql:include/ts_merge_query.sql:63: ERROR:  COPY query must have a RETURNING clause\n-- unsupported relation types\n-- view\nCREATE VIEW tv AS SELECT * FROM target;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\nDROP VIEW tv;\n-- materialized view\nCREATE MATERIALIZED VIEW mv AS SELECT * FROM target;\nMERGE INTO mv t\nUSING source s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:81: ERROR:  cannot execute MERGE on relation \"mv\"\nDETAIL:  This operation is not supported for materialized views.\nDROP MATERIALIZED VIEW mv;\n-- permissions\nMERGE INTO target\nUSING source2\nON target.tid = source2.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:90: ERROR:  permission denied for table source2\nGRANT INSERT ON target TO regress_merge_no_privs;\nSET SESSION AUTHORIZATION regress_merge_no_privs;\nMERGE INTO target\nUSING source2\nON target.tid = source2.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:99: ERROR:  permission denied for table target\nGRANT UPDATE ON target2 TO regress_merge_privs;\nSET SESSION AUTHORIZATION regress_merge_privs;\nMERGE INTO target2\nUSING source\nON target2.tid = source.sid\nWHEN MATCHED THEN\n\tDELETE;\npsql:include/ts_merge_query.sql:108: ERROR:  permission denied for table target2\nMERGE INTO target2\nUSING source\nON target2.tid = source.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:114: ERROR:  permission denied for table target2\n-- check if the target can be accessed from source relation subquery; we should\n-- not be able to do so\nMERGE INTO target t\nUSING (SELECT * FROM source WHERE t.tid > sid) s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\npsql:include/ts_merge_query.sql:122: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 2: USING (SELECT * FROM source WHERE t.tid > sid) s\n                                          ^\nDETAIL:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\n--\n-- initial tests\n--\n-- zero rows in source has no effect\nMERGE INTO target\nUSING source\nON target.tid = source.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\nROLLBACK;\n-- insert some non-matching source rows to work from\nINSERT INTO source VALUES (4, 40);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tDO NOTHING;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (5, 50);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- index plans\nINSERT INTO target SELECT generate_series(1000,2500), 0;\nALTER TABLE target ADD PRIMARY KEY (tid);\nANALYZE target;\nDELETE FROM target WHERE tid > 100;\nANALYZE target;\n-- insert some matching source rows to work from\nINSERT INTO source VALUES (2, 5);\nINSERT INTO source VALUES (3, 20);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n-- equivalent of an UPDATE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- equivalent of a DELETE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDO NOTHING;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, NULL);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- duplicate source row causes multiple target row update ERROR\nINSERT INTO source VALUES (2, 5);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\npsql:include/ts_merge_query.sql:241: ERROR:  MERGE command cannot affect row a second time\nHINT:  Ensure that not more than one source row matches any one target row.\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\npsql:include/ts_merge_query.sql:249: ERROR:  MERGE command cannot affect row a second time\nHINT:  Ensure that not more than one source row matches any one target row.\nROLLBACK;\n-- remove duplicate MATCHED data from source data\nDELETE FROM source WHERE sid = 2;\nINSERT INTO source VALUES (2, 5);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n-- duplicate source row on INSERT should fail because of target_pkey\nINSERT INTO source VALUES (4, 40);\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n  INSERT VALUES (4, NULL);\npsql:include/ts_merge_query.sql:265: ERROR:  duplicate key value violates unique constraint \"2_2_target_pkey\"\nDETAIL:  Key (tid)=(4) already exists.\nSELECT * FROM target ORDER BY tid;\npsql:include/ts_merge_query.sql:266: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\n-- remove duplicate NOT MATCHED data from source data\nDELETE FROM source WHERE sid = 4;\nINSERT INTO source VALUES (4, 40);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n-- remove constraints\nalter table target drop CONSTRAINT target_pkey;\nalter table target alter column tid drop not null;\npsql:include/ts_merge_query.sql:277: ERROR:  cannot drop not-null constraint from a time-partitioned column\n-- multiple actions\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, 4)\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- should be equivalent\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, 4);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- column references\n-- do a simple equivalent of an UPDATE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance + s.delta;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- do a simple equivalent of an INSERT SELECT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- and again with duplicate source rows\nINSERT INTO source VALUES (5, 50);\nINSERT INTO source VALUES (5, 50);\n-- do a simple equivalent of an INSERT SELECT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n  INSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- removing duplicate source rows\nDELETE FROM source WHERE sid = 5;\n-- and again with explicitly identified column list\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- and again with a subtle error: referring to non-existent target row for NOT MATCHED\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (t.tid, s.delta);\npsql:include/ts_merge_query.sql:356: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 5:  INSERT (tid, balance) VALUES (t.tid, s.delta);\n                                       ^\nDETAIL:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\n-- and again with a constant ON clause\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON (SELECT true)\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (t.tid, s.delta);\npsql:include/ts_merge_query.sql:364: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 5:  INSERT (tid, balance) VALUES (t.tid, s.delta);\n                                       ^\nDETAIL:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\nSELECT * FROM target ORDER BY tid;\npsql:include/ts_merge_query.sql:365: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\n-- now the classic UPSERT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance + s.delta\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- this time with a FALSE condition\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND FALSE THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\n-- this time with an actual condition which returns false\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance <> 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\nBEGIN;\n-- and now with a condition which returns true\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\nROLLBACK;\n-- conditions in the NOT MATCHED clause can only refer to source columns\nBEGIN;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND t.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\npsql:include/ts_merge_query.sql:408: ERROR:  invalid reference to FROM-clause entry for table \"t\"\nLINE 3: WHEN NOT MATCHED AND t.balance = 100 THEN\n                             ^\nDETAIL:  There is an entry for table \"t\", but it cannot be referenced from this part of the query.\nSELECT * FROM wq_target;\npsql:include/ts_merge_query.sql:409: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\n-- conditions in MATCHED clause can refer to both source and target\nSELECT * FROM wq_source;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND s.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n-- check if AND works\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 AND s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 AND s.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n-- check if OR works\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 OR s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 199 OR s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n-- check source-side whole-row references\nBEGIN;\nMERGE INTO wq_target t\nUSING wq_source s ON (t.tid = s.sid)\nWHEN matched and t = s or t.tid = s.sid THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nROLLBACK;\n-- check if subqueries work in the conditions?\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance > (SELECT max(balance) FROM target) THEN\n\tUPDATE SET balance = t.balance + s.balance;\n-- check if we can access system columns in the conditions\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.xmin = t.xmax THEN\n\tUPDATE SET balance = t.balance + s.balance;\npsql:include/ts_merge_query.sql:477: ERROR:  cannot use system column \"xmin\" in MERGE WHEN condition\nLINE 3: WHEN MATCHED AND t.xmin = t.xmax THEN\n                         ^\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.tableoid >= 0 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nDROP TABLE wq_target CASCADE;\nDROP TABLE wq_source;\n-- test triggers\ncreate or replace function merge_trigfunc () returns trigger\nlanguage plpgsql as\n$$\nDECLARE\n\tline text;\nBEGIN\n\tSELECT INTO line format('%s %s %s trigger%s',\n\t\tTG_WHEN, TG_OP, TG_LEVEL, CASE\n\t\tWHEN TG_OP = 'INSERT' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s', NEW)\n\t\tWHEN TG_OP = 'UPDATE' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s -> %s', OLD, NEW)\n\t\tWHEN TG_OP = 'DELETE' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s', OLD)\n\t\tEND);\n\n\tRAISE NOTICE '%', line;\n\tIF (TG_WHEN = 'BEFORE' AND TG_LEVEL = 'ROW') THEN\n\t\tIF (TG_OP = 'DELETE') THEN\n\t\t\tRETURN OLD;\n\t\tELSE\n\t\t\tRETURN NEW;\n\t\tEND IF;\n\tELSE\n\t\tRETURN NULL;\n\tEND IF;\nEND;\n$$;\nCREATE TRIGGER merge_bsi BEFORE INSERT ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bsu BEFORE UPDATE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bsd BEFORE DELETE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asi AFTER INSERT ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asu AFTER UPDATE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asd AFTER DELETE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bri BEFORE INSERT ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bru BEFORE UPDATE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_brd BEFORE DELETE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_ari AFTER INSERT ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_aru AFTER UPDATE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_ard AFTER DELETE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\n-- now the classic UPSERT, with a DELETE\nBEGIN;\nUPDATE target SET balance = 0 WHERE tid = 3;\n--EXPLAIN (ANALYZE ON, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF)\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND t.balance > s.delta THEN\n\tUPDATE SET balance = t.balance - s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- Test behavior of triggers that turn UPDATE/DELETE into no-ops\ncreate or replace function skip_merge_op() returns trigger\nlanguage plpgsql as\n$$\nBEGIN\n\tRETURN NULL;\nEND;\n$$;\nSELECT * FROM target full outer join source on (sid = tid);\ncreate trigger merge_skip BEFORE INSERT OR UPDATE or DELETE\n  ON target FOR EACH ROW EXECUTE FUNCTION skip_merge_op();\nDO $$\nDECLARE\n  result integer;\nBEGIN\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND s.sid = 3 THEN UPDATE SET balance = t.balance + s.delta\nWHEN MATCHED THEN DELETE\nWHEN NOT MATCHED THEN INSERT VALUES (sid, delta);\nIF FOUND THEN\n  RAISE NOTICE 'Found';\nELSE\n  RAISE NOTICE 'Not found';\nEND IF;\nGET DIAGNOSTICS result := ROW_COUNT;\nRAISE NOTICE 'ROW_COUNT = %', result;\nEND;\n$$;\nSELECT * FROM target FULL OUTER JOIN source ON (sid = tid);\nDROP TRIGGER merge_skip ON target;\nDROP FUNCTION skip_merge_op();\n-- test from PL/pgSQL\n-- make sure MERGE INTO isn't interpreted to mean returning variables like SELECT INTO\nBEGIN;\nDO LANGUAGE plpgsql $$\nBEGIN\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND t.balance > s.delta THEN\n\tUPDATE SET balance = t.balance - s.delta;\nEND;\n$$;\nROLLBACK;\n--source constants\nBEGIN;\nMERGE INTO target t\nUSING (SELECT 9 AS sid, 57 AS delta) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n--source query\nBEGIN;\nMERGE INTO target t\nUSING (SELECT sid, delta FROM source WHERE delta > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING (SELECT sid, delta as newname FROM source WHERE delta > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.newname);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n--self-merge\nBEGIN;\nMERGE INTO target t1\nUSING target t2\nON t1.tid = t2.tid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t1.balance + t2.balance\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (t2.tid, t2.balance);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING (SELECT tid as sid, balance as delta FROM target WHERE balance > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING\n(SELECT sid, max(delta) AS delta\n FROM source\n GROUP BY sid\n HAVING count(*) = 1\n ORDER BY sid ASC) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- plpgsql parameters and results\nBEGIN;\nCREATE FUNCTION merge_func (p_id integer, p_bal integer)\nRETURNS INTEGER\nLANGUAGE plpgsql\nAS $$\nDECLARE\n result integer;\nBEGIN\nMERGE INTO target t\nUSING (SELECT p_id AS sid) AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance - p_bal;\nIF FOUND THEN\n\tGET DIAGNOSTICS result := ROW_COUNT;\nEND IF;\nRETURN result;\nEND;\n$$;\nSELECT merge_func(3, 4);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n-- PREPARE\nBEGIN;\nprepare foom as merge into target t using (select 1 as sid) s on (t.tid = s.sid) when matched then update set balance = 1;\npsql:include/ts_merge_query.sql:685: ERROR:  prepared statement \"foom\" already exists\nexecute foom;\npsql:include/ts_merge_query.sql:686: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\nBEGIN;\nPREPARE foom2 (integer, integer) AS\nMERGE INTO target t\nUSING (SELECT 1) s\nON t.tid = $1\nWHEN MATCHED THEN\nUPDATE SET balance = $2;\npsql:include/ts_merge_query.sql:695: ERROR:  prepared statement \"foom2\" already exists\n--EXPLAIN (ANALYZE ON, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF)\nexecute foom2 (1, 1);\npsql:include/ts_merge_query.sql:697: ERROR:  current transaction is aborted, commands ignored until end of transaction block\nROLLBACK;\n-- subqueries in source relation\nBEGIN;\nMERGE INTO sq_target t\nUSING (SELECT * FROM sq_source) s\nON tid = sid\nWHEN MATCHED AND t.balance > delta THEN\n\tUPDATE SET balance = t.balance + delta;\nSELECT * FROM sq_target ORDER BY tid;\nROLLBACK;\n-- try a view\nCREATE VIEW v AS SELECT * FROM sq_source WHERE sid < 2;\nBEGIN;\nMERGE INTO sq_target\nUSING v\nON tid = sid\nWHEN MATCHED THEN\n    UPDATE SET balance = v.balance + delta;\nSELECT * FROM sq_target ORDER BY tid;\nROLLBACK;\n-- ambiguous reference to a column\nBEGIN;\nMERGE INTO sq_target\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\npsql:include/ts_merge_query.sql:732: ERROR:  column reference \"balance\" is ambiguous\nLINE 5:     UPDATE SET balance = balance + delta\n                                 ^\nROLLBACK;\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\nSELECT * FROM sq_target;\nROLLBACK;\n-- CTEs\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nWITH targq AS (\n\tSELECT * FROM v\n)\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\nROLLBACK;\n-- RETURNING\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE\nRETURNING *;\nROLLBACK;\n-- PG17-specific tests for views, returning and merge_action. These throw syntax errors for previous versions of Postgres.\n-- However, since the error is the same for both hypertables and regular tables, this test should still pass for previous versions.\n-- RETURNING\nINSERT INTO source(sid, delta) VALUES(1, 40), (5, 50);\nBEGIN;\nMERGE INTO target t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta);\nSELECT * from target;\nROLLBACK;\nBEGIN;\nMERGE INTO target t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 1 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta)\nRETURNING merge_action(), t.*;\nROLLBACK;\n-- Views\nCREATE VIEW tv AS SELECT * FROM target;\nBEGIN;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta);\nSELECT * from tv;\nSELECT * from target; -- should also update the underlying table\nROLLBACK;\nBEGIN;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta)\nRETURNING merge_action(), t.*;\nROLLBACK;\nDROP VIEW tv;\nDELETE FROM source where sid in (1, 5);\n-- EXPLAIN\nCREATE TABLE ex_mtarget (a int, b int)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE ex_msource (a int, b int)\n  WITH (autovacuum_enabled=off);\nINSERT INTO ex_mtarget SELECT i, i*10 FROM generate_series(1,100,2) i;\nINSERT INTO ex_msource SELECT i, i*10 FROM generate_series(1,100,1) i;\nCREATE FUNCTION explain_merge(query text) RETURNS SETOF text\nLANGUAGE plpgsql AS\n$$\nDECLARE ln text;\nBEGIN\n    FOR ln IN\n        EXECUTE 'explain (analyze, timing off, summary off, buffers off, costs off) ' ||\n\t\t  query\n    LOOP\n        ln := regexp_replace(ln, '(Memory( Usage)?|Buckets|Batches): \\S*',  '\\1: xxx', 'g');\n        RETURN NEXT ln;\n    END LOOP;\nEND;\n$$;\n-- only updates\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED THEN\n\tUPDATE SET b = t.b + 1');\n-- only updates to selected tuples\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1');\n-- updates + deletes\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1\nWHEN MATCHED AND t.a >= 10 AND t.a <= 20 THEN\n\tDELETE');\n-- only inserts\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN NOT MATCHED AND s.a < 10 THEN\n\tINSERT VALUES (a, b)');\n-- all three\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1\nWHEN MATCHED AND t.a >= 30 AND t.a <= 40 THEN\n\tDELETE\nWHEN NOT MATCHED AND s.a < 20 THEN\n\tINSERT VALUES (a, b)');\n-- nothing\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a AND t.a < -1000\nWHEN MATCHED AND t.a < 10 THEN\n\tDO NOTHING');\nDROP TABLE ex_msource, ex_mtarget;\nDROP FUNCTION explain_merge(text);\n-- Subqueries\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED THEN\n    UPDATE SET balance = (SELECT count(*) FROM sq_target);\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND (SELECT count(*) > 0 FROM sq_target) THEN\n    UPDATE SET balance = 42;\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid AND (SELECT count(*) > 0 FROM sq_target)\nWHEN MATCHED THEN\n    UPDATE SET balance = 42;\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\n--  Test RETURNING with subqueries\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid AND (SELECT count(*) > 0 FROM sq_target)\nWHEN MATCHED THEN\n    UPDATE SET balance = 42\nRETURNING *;\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\nDROP TABLE sq_target CASCADE;\nDROP TABLE sq_source CASCADE;\nCREATE TABLE pa_target (tid integer, balance float, val text)\n\tPARTITION BY LIST (tid);\nCREATE TABLE part1 PARTITION OF pa_target FOR VALUES IN (1,4)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part2 PARTITION OF pa_target FOR VALUES IN (2,5,6)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part3 PARTITION OF pa_target FOR VALUES IN (3,8,9)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part4 PARTITION OF pa_target DEFAULT\n  WITH (autovacuum_enabled=off);\nCREATE TABLE pa_source (sid integer, delta float);\n-- insert many rows to the source table\nINSERT INTO pa_source SELECT id, id * 10  FROM generate_series(1,14) AS id;\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT id, id * 100, 'initial' FROM generate_series(1,14,2) AS id;\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- same with a constant qual\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid AND tid = 1\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- try updating the partition key column\nBEGIN;\nCREATE FUNCTION merge_func() RETURNS integer LANGUAGE plpgsql AS $$\nDECLARE\n  result integer;\nBEGIN\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nIF FOUND THEN\n  GET DIAGNOSTICS result := ROW_COUNT;\nEND IF;\nRETURN result;\nEND;\n$$;\nSELECT merge_func();\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\nDROP TABLE pa_target CASCADE;\n-- The target table is partitioned in the same way, but this time by attaching\n-- partitions which have columns in different order, dropped columns etc.\nCREATE TABLE pa_target (tid integer, balance float, val text)\n\tPARTITION BY LIST (tid);\nCREATE TABLE part1 (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part2 (balance float, tid integer, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part3 (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part4 (extraid text, tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nALTER TABLE part4 DROP COLUMN extraid;\nALTER TABLE pa_target ATTACH PARTITION part1 FOR VALUES IN (1,4);\nALTER TABLE pa_target ATTACH PARTITION part2 FOR VALUES IN (2,5,6);\nALTER TABLE pa_target ATTACH PARTITION part3 FOR VALUES IN (3,8,9);\nALTER TABLE pa_target ATTACH PARTITION part4 DEFAULT;\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT id, id * 100, 'initial' FROM generate_series(1,14,2) AS id;\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- same with a constant qual\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid AND tid IN (1, 5)\n  WHEN MATCHED AND tid % 5 = 0 THEN DELETE\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n-- try updating the partition key column\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\nDROP TABLE pa_source;\nDROP TABLE pa_target CASCADE;\n-- Sub-partitioning\nCREATE TABLE pa_target (logts timestamp, tid integer, balance float, val text)\n\tPARTITION BY RANGE (logts);\nCREATE TABLE part_m01 PARTITION OF pa_target\n\tFOR VALUES FROM ('2017-01-01') TO ('2017-02-01')\n\tPARTITION BY LIST (tid);\nCREATE TABLE part_m01_odd PARTITION OF part_m01\n\tFOR VALUES IN (1,3,5,7,9) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m01_even PARTITION OF part_m01\n\tFOR VALUES IN (2,4,6,8) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m02 PARTITION OF pa_target\n\tFOR VALUES FROM ('2017-02-01') TO ('2017-03-01')\n\tPARTITION BY LIST (tid);\nCREATE TABLE part_m02_odd PARTITION OF part_m02\n\tFOR VALUES IN (1,3,5,7,9) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m02_even PARTITION OF part_m02\n\tFOR VALUES IN (2,4,6,8) WITH (autovacuum_enabled=off);\nCREATE TABLE pa_source (sid integer, delta float)\n  WITH (autovacuum_enabled=off);\n-- insert many rows to the source table\nINSERT INTO pa_source SELECT id, id * 10  FROM generate_series(1,14) AS id;\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT '2017-01-31', id, id * 100, 'initial' FROM generate_series(1,9,3) AS id;\nINSERT INTO pa_target SELECT '2017-02-28', id, id * 100, 'initial' FROM generate_series(2,9,3) AS id;\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING (SELECT '2017-01-15' AS slogts, * FROM pa_source WHERE sid < 10) s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (slogts::timestamp, sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\nDROP TABLE pa_source;\nDROP TABLE pa_target CASCADE;\n-- some complex joins on the source side\n-- source relation is an unaliased join\nMERGE INTO cj_target t\nUSING cj_source1 s1\n\tINNER JOIN cj_source2 s2 ON sid1 = sid2\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid1, delta, sval);\n-- try accessing columns from either side of the source join\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2 AND scat = 20\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid2, delta, sval)\nWHEN MATCHED THEN\n\tDELETE;\n-- some simple expressions in INSERT targetlist\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid2, delta + scat, sval)\nWHEN MATCHED THEN\n\tUPDATE SET val = val || ' updated by merge';\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2 AND scat = 20\nON t.tid = sid1\nWHEN MATCHED THEN\n\tUPDATE SET val = val || ' ' || delta::text;\nSELECT * FROM cj_target ORDER BY tid;\nALTER TABLE cj_source1 RENAME COLUMN sid1 TO sid;\nALTER TABLE cj_source2 RENAME COLUMN sid2 TO sid;\nTRUNCATE cj_target;\nMERGE INTO cj_target t\nUSING cj_source1 s1\n\tINNER JOIN cj_source2 s2 ON s1.sid = s2.sid\nON t.tid = s1.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s2.sid, delta, sval);\nDROP TABLE cj_source2, cj_source1;\nDROP TABLE cj_target CASCADE;\n-- Function scans\nMERGE INTO fs_target t\nUSING generate_series(1,100,1) AS id\nON t.a = id\nWHEN MATCHED THEN\n\tUPDATE SET b = b + id\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (id, -1);\nMERGE INTO fs_target t\nUSING generate_series(1,100,2) AS id\nON t.a = id\nWHEN MATCHED THEN\n\tUPDATE SET b = b + id, c = 'updated '|| id.*::text\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (id, -1, 'inserted ' || id.*::text);\nSELECT count(*) FROM fs_target;\nDROP TABLE fs_target CASCADE;\n-- SERIALIZABLE test\n-- handled in isolation tests\n-- Inheritance-based partitioning\nCREATE TABLE measurement (\n    city_id         int not null,\n    logdate         date not null,\n    peaktemp        int,\n    unitsales       int\n) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2006m02 (\n    CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )\n) INHERITS (measurement) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2006m03 (\n    CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )\n) INHERITS (measurement) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2007m01 (\n    filler          text,\n    peaktemp        int,\n    logdate         date not null,\n    city_id         int not null,\n    unitsales       int\n    CHECK ( logdate >= DATE '2007-01-01' AND logdate < DATE '2007-02-01')\n) WITH (autovacuum_enabled=off);\nALTER TABLE measurement_y2007m01 DROP COLUMN filler;\nALTER TABLE measurement_y2007m01 INHERIT measurement;\nINSERT INTO measurement VALUES (0, '2005-07-21', 5, 15);\nCREATE OR REPLACE FUNCTION measurement_insert_trigger()\nRETURNS TRIGGER AS $$\nBEGIN\n    IF ( NEW.logdate >= DATE '2006-02-01' AND\n         NEW.logdate < DATE '2006-03-01' ) THEN\n        INSERT INTO measurement_y2006m02 VALUES (NEW.*);\n    ELSIF ( NEW.logdate >= DATE '2006-03-01' AND\n            NEW.logdate < DATE '2006-04-01' ) THEN\n        INSERT INTO measurement_y2006m03 VALUES (NEW.*);\n    ELSIF ( NEW.logdate >= DATE '2007-01-01' AND\n            NEW.logdate < DATE '2007-02-01' ) THEN\n        INSERT INTO measurement_y2007m01 (city_id, logdate, peaktemp, unitsales)\n            VALUES (NEW.*);\n    ELSE\n        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';\n    END IF;\n    RETURN NULL;\nEND;\n$$ LANGUAGE plpgsql ;\nCREATE TRIGGER insert_measurement_trigger\n    BEFORE INSERT ON measurement\n    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();\nINSERT INTO measurement VALUES (1, '2006-02-10', 35, 10);\nINSERT INTO measurement VALUES (1, '2006-02-16', 45, 20);\nINSERT INTO measurement VALUES (1, '2006-03-17', 25, 10);\nINSERT INTO measurement VALUES (1, '2006-03-27', 15, 40);\nINSERT INTO measurement VALUES (1, '2007-01-15', 10, 10);\nINSERT INTO measurement VALUES (1, '2007-01-17', 10, 10);\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate;\nCREATE TABLE new_measurement (LIKE measurement) WITH (autovacuum_enabled=off);\nINSERT INTO new_measurement VALUES (0, '2005-07-21', 25, 20);\nINSERT INTO new_measurement VALUES (1, '2006-03-01', 20, 10);\nINSERT INTO new_measurement VALUES (1, '2006-02-16', 50, 10);\nINSERT INTO new_measurement VALUES (2, '2006-02-10', 20, 20);\nINSERT INTO new_measurement VALUES (1, '2006-03-27', NULL, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-17', NULL, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-15', 5, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-16', 10, 10);\nBEGIN;\nMERGE INTO ONLY measurement m\n USING new_measurement nm ON\n      (m.city_id = nm.city_id and m.logdate=nm.logdate)\nWHEN MATCHED AND nm.peaktemp IS NULL THEN DELETE\nWHEN MATCHED THEN UPDATE\n     SET peaktemp = greatest(m.peaktemp, nm.peaktemp),\n        unitsales = m.unitsales + coalesce(nm.unitsales, 0)\nWHEN NOT MATCHED THEN INSERT\n     (city_id, logdate, peaktemp, unitsales)\n   VALUES (city_id, logdate, peaktemp, unitsales);\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate, peaktemp;\nROLLBACK;\nMERGE into measurement m\n USING new_measurement nm ON\n      (m.city_id = nm.city_id and m.logdate=nm.logdate)\nWHEN MATCHED AND nm.peaktemp IS NULL THEN DELETE\nWHEN MATCHED THEN UPDATE\n     SET peaktemp = greatest(m.peaktemp, nm.peaktemp),\n        unitsales = m.unitsales + coalesce(nm.unitsales, 0)\nWHEN NOT MATCHED THEN INSERT\n     (city_id, logdate, peaktemp, unitsales)\n   VALUES (city_id, logdate, peaktemp, unitsales);\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate;\nBEGIN;\nMERGE INTO new_measurement nm\n USING ONLY measurement m ON\n      (nm.city_id = m.city_id and nm.logdate=m.logdate)\nWHEN MATCHED THEN DELETE;\nSELECT * FROM new_measurement ORDER BY city_id, logdate;\nROLLBACK;\nMERGE INTO new_measurement nm\n USING measurement m ON\n      (nm.city_id = m.city_id and nm.logdate=m.logdate)\nWHEN MATCHED THEN DELETE;\nSELECT * FROM new_measurement ORDER BY city_id, logdate;\nDROP TABLE measurement, new_measurement CASCADE;\nDROP FUNCTION measurement_insert_trigger();\nRESET SESSION AUTHORIZATION;\nDROP TABLE target CASCADE;\nDROP TABLE target2 CASCADE;\nDROP TABLE source, source2;\nDROP FUNCTION merge_trigfunc();\nREVOKE CREATE ON SCHEMA public FROM regress_merge_privs;\nDROP USER regress_merge_privs;\nDROP USER regress_merge_no_privs;\n\\o\n:DIFF_CMD\n"
  },
  {
    "path": "test/expected/update.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\o /dev/null\n\\ir include/insert_single.sql\n-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE PUBLIC.\"one_Partition\" (\n  \"timeCustom\" BIGINT NOT NULL,\n  device_id TEXT NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL,\n  series_bool BOOLEAN NULL\n);\nCREATE INDEX ON PUBLIC.\"one_Partition\" (device_id, \"timeCustom\" DESC NULLS LAST) WHERE device_id IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"one_Partition\" (\"timeCustom\" DESC NULLS LAST, series_0) WHERE series_0 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"one_Partition\" (\"timeCustom\" DESC NULLS LAST, series_1)  WHERE series_1 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"one_Partition\" (\"timeCustom\" DESC NULLS LAST, series_2) WHERE series_2 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"one_Partition\" (\"timeCustom\" DESC NULLS LAST, series_bool) WHERE series_bool IS NOT NULL;\n\\c :DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA \"one_Partition\" AUTHORIZATION :ROLE_DEFAULT_PERM_USER;\n\\c :DBNAME :ROLE_DEFAULT_PERM_USER;\nSELECT * FROM create_hypertable('\"public\".\"one_Partition\"', 'timeCustom', associated_schema_name=>'one_Partition', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n--output command tags\n\\set QUIET off\nBEGIN;\n\\COPY \"one_Partition\" FROM 'data/ds1_dev1_1.tsv' NULL AS '';\nCOMMIT;\nINSERT INTO \"one_Partition\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257987600000000000, 'dev1', 1.5, 1),\n(1257987600000000000, 'dev1', 1.5, 2),\n(1257894000000000000, 'dev2', 1.5, 1),\n(1257894002000000000, 'dev1', 2.5, 3);\nINSERT INTO \"one_Partition\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257894000000000000, 'dev2', 1.5, 2);\n\\set QUIET on\n\\o\n-- Make sure UPDATE isn't optimized if it includes Append plans\n-- Need to turn of nestloop to make append appear the same on PG96 and PG10\nset enable_nestloop = 'off';\nCREATE OR REPLACE FUNCTION series_val()\nRETURNS integer LANGUAGE PLPGSQL STABLE AS\n$BODY$\nBEGIN\n    RETURN 5;\nEND;\n$BODY$;\n-- ConstraintAwareAppend applied for SELECT\nEXPLAIN (buffers off, costs off)\nSELECT FROM \"one_Partition\"\nWHERE series_1 IN (SELECT series_1 FROM \"one_Partition\" WHERE series_1 > series_val());\n--- QUERY PLAN ---\n Hash Join\n   Hash Cond: (\"one_Partition\".series_1 = \"one_Partition_1\".series_1)\n   ->  Custom Scan (ChunkAppend) on \"one_Partition\"\n         Chunks excluded during startup: 0\n         ->  Index Only Scan using \"_hyper_1_1_chunk_one_Partition_timeCustom_series_1_idx\" on _hyper_1_1_chunk\n               Index Cond: (series_1 > (series_val())::double precision)\n         ->  Index Only Scan using \"_hyper_1_2_chunk_one_Partition_timeCustom_series_1_idx\" on _hyper_1_2_chunk\n               Index Cond: (series_1 > (series_val())::double precision)\n         ->  Index Only Scan using \"_hyper_1_3_chunk_one_Partition_timeCustom_series_1_idx\" on _hyper_1_3_chunk\n               Index Cond: (series_1 > (series_val())::double precision)\n   ->  Hash\n         ->  HashAggregate\n               Group Key: \"one_Partition_1\".series_1\n               ->  Custom Scan (ChunkAppend) on \"one_Partition\" \"one_Partition_1\"\n                     Chunks excluded during startup: 0\n                     ->  Index Only Scan using \"_hyper_1_1_chunk_one_Partition_timeCustom_series_1_idx\" on _hyper_1_1_chunk _hyper_1_1_chunk_1\n                           Index Cond: (series_1 > (series_val())::double precision)\n                     ->  Index Only Scan using \"_hyper_1_2_chunk_one_Partition_timeCustom_series_1_idx\" on _hyper_1_2_chunk _hyper_1_2_chunk_1\n                           Index Cond: (series_1 > (series_val())::double precision)\n                     ->  Index Only Scan using \"_hyper_1_3_chunk_one_Partition_timeCustom_series_1_idx\" on _hyper_1_3_chunk _hyper_1_3_chunk_1\n                           Index Cond: (series_1 > (series_val())::double precision)\n\n-- ConstraintAwareAppend NOT applied for UPDATE\nEXPLAIN (buffers off, costs off)\nUPDATE \"one_Partition\"\nSET series_1 = 8\nWHERE series_1 IN (SELECT series_1 FROM \"one_Partition\" WHERE series_1 > series_val());\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Update on \"one_Partition\"\n         Update on _hyper_1_1_chunk \"one_Partition_2\"\n         Update on _hyper_1_2_chunk \"one_Partition_3\"\n         Update on _hyper_1_3_chunk \"one_Partition_4\"\n         ->  Hash Join\n               Hash Cond: (\"one_Partition\".series_1 = \"one_Partition_1\".series_1)\n               ->  Append\n                     ->  Seq Scan on _hyper_1_1_chunk \"one_Partition_2\"\n                     ->  Seq Scan on _hyper_1_2_chunk \"one_Partition_3\"\n                     ->  Seq Scan on _hyper_1_3_chunk \"one_Partition_4\"\n               ->  Hash\n                     ->  HashAggregate\n                           Group Key: \"one_Partition_1\".series_1\n                           ->  Append\n                                 ->  Index Scan using \"_hyper_1_1_chunk_one_Partition_timeCustom_series_1_idx\" on _hyper_1_1_chunk \"one_Partition_5\"\n                                       Index Cond: (series_1 > (series_val())::double precision)\n                                 ->  Index Scan using \"_hyper_1_2_chunk_one_Partition_timeCustom_series_1_idx\" on _hyper_1_2_chunk \"one_Partition_6\"\n                                       Index Cond: (series_1 > (series_val())::double precision)\n                                 ->  Index Scan using \"_hyper_1_3_chunk_one_Partition_timeCustom_series_1_idx\" on _hyper_1_3_chunk \"one_Partition_7\"\n                                       Index Cond: (series_1 > (series_val())::double precision)\n\nSELECT * FROM \"one_Partition\" ORDER BY \"timeCustom\", device_id, series_0, series_1, series_2;\n     timeCustom      | device_id | series_0 | series_1 | series_2 | series_bool \n---------------------+-----------+----------+----------+----------+-------------\n 1257894000000000000 | dev1      |      1.5 |        1 |        2 | t\n 1257894000000000000 | dev1      |      1.5 |        2 |          | \n 1257894000000000000 | dev2      |      1.5 |        1 |          | \n 1257894000000000000 | dev2      |      1.5 |        2 |          | \n 1257894000000001000 | dev1      |      2.5 |        3 |          | \n 1257894001000000000 | dev1      |      3.5 |        4 |          | \n 1257894002000000000 | dev1      |      2.5 |        3 |          | \n 1257894002000000000 | dev1      |      5.5 |        6 |          | t\n 1257894002000000000 | dev1      |      5.5 |        7 |          | f\n 1257897600000000000 | dev1      |      4.5 |        5 |          | f\n 1257987600000000000 | dev1      |      1.5 |        1 |          | \n 1257987600000000000 | dev1      |      1.5 |        2 |          | \n\nUPDATE \"one_Partition\"\nSET series_1 = 8\nWHERE series_1 IN (SELECT series_1 FROM \"one_Partition\" WHERE series_1 > series_val());\nSELECT * FROM \"one_Partition\" ORDER BY \"timeCustom\", device_id, series_0, series_1, series_2;\n     timeCustom      | device_id | series_0 | series_1 | series_2 | series_bool \n---------------------+-----------+----------+----------+----------+-------------\n 1257894000000000000 | dev1      |      1.5 |        1 |        2 | t\n 1257894000000000000 | dev1      |      1.5 |        2 |          | \n 1257894000000000000 | dev2      |      1.5 |        1 |          | \n 1257894000000000000 | dev2      |      1.5 |        2 |          | \n 1257894000000001000 | dev1      |      2.5 |        3 |          | \n 1257894001000000000 | dev1      |      3.5 |        4 |          | \n 1257894002000000000 | dev1      |      2.5 |        3 |          | \n 1257894002000000000 | dev1      |      5.5 |        8 |          | f\n 1257894002000000000 | dev1      |      5.5 |        8 |          | t\n 1257897600000000000 | dev1      |      4.5 |        5 |          | f\n 1257987600000000000 | dev1      |      1.5 |        1 |          | \n 1257987600000000000 | dev1      |      1.5 |        2 |          | \n\nUPDATE \"one_Partition\" SET series_1 = 47;\nUPDATE \"one_Partition\" SET series_bool = true;\nSELECT * FROM \"one_Partition\" ORDER BY \"timeCustom\", device_id, series_0, series_1, series_2;\n     timeCustom      | device_id | series_0 | series_1 | series_2 | series_bool \n---------------------+-----------+----------+----------+----------+-------------\n 1257894000000000000 | dev1      |      1.5 |       47 |        2 | t\n 1257894000000000000 | dev1      |      1.5 |       47 |          | t\n 1257894000000000000 | dev2      |      1.5 |       47 |          | t\n 1257894000000000000 | dev2      |      1.5 |       47 |          | t\n 1257894000000001000 | dev1      |      2.5 |       47 |          | t\n 1257894001000000000 | dev1      |      3.5 |       47 |          | t\n 1257894002000000000 | dev1      |      2.5 |       47 |          | t\n 1257894002000000000 | dev1      |      5.5 |       47 |          | t\n 1257894002000000000 | dev1      |      5.5 |       47 |          | t\n 1257897600000000000 | dev1      |      4.5 |       47 |          | t\n 1257987600000000000 | dev1      |      1.5 |       47 |          | t\n 1257987600000000000 | dev1      |      1.5 |       47 |          | t\n\n-- test update on chunks directly\nCREATE TABLE direct_update(time timestamptz) WITH (tsdb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nINSERT INTO direct_update VALUES ('2020-01-01');\nSELECT show_chunks('direct_update') AS \"CHUNK\" \\gset\n--should have ModifyHyperable node\nEXPLAIN (costs off, timing off, summary off) UPDATE :CHUNK SET time = time + INTERVAL '1 minute';\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Update on _hyper_2_4_chunk\n         ->  Seq Scan on _hyper_2_4_chunk\n\nEXPLAIN (costs off, timing off, summary off) UPDATE ONLY :CHUNK SET time = time + INTERVAL '1 minute';\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable)\n   ->  Update on _hyper_2_4_chunk\n         ->  Seq Scan on _hyper_2_4_chunk\n\n-- correct time range should succeed\nUPDATE :CHUNK SET time = time + INTERVAL '1 minute' RETURNING *;\n             time             \n------------------------------\n Wed Jan 01 00:01:00 2020 PST\n\nUPDATE ONLY :CHUNK SET time = time + INTERVAL '1 minute' RETURNING *;\n             time             \n------------------------------\n Wed Jan 01 00:02:00 2020 PST\n\n-- crossing chunk boundary should fail\n\\set ON_ERROR_STOP 0\nUPDATE :CHUNK SET time = time + INTERVAL '1 month' RETURNING *;\nERROR:  new row for relation \"_hyper_2_4_chunk\" violates check constraint \"constraint_4\"\nUPDATE ONLY :CHUNK SET time = time + INTERVAL '1 month' RETURNING *;\nERROR:  new row for relation \"_hyper_2_4_chunk\" violates check constraint \"constraint_4\"\n\\set ON_ERROR_STOP 1\n-- github issue #6790\n-- test UPDATE with WHERE EXISTS on hypertable\nCREATE TABLE i6790_update(time timestamptz NOT NULL, device int, value float) WITH (tsdb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nINSERT INTO i6790_update SELECT t, 1, 0.1 FROM generate_series('2026-01-01'::timestamptz, '2026-01-03'::timestamptz, interval '12 hours') t;\n-- UPDATE with simple EXISTS - creates gating Result node(s) wrapping ChunkAppend\nUPDATE i6790_update SET value = 0.2 WHERE EXISTS (SELECT 1);\nSELECT count(*) FROM i6790_update WHERE value = 0.2;\n count \n-------\n     5\n\n-- UPDATE with correlated EXISTS\nUPDATE i6790_update SET value = 0.3 WHERE EXISTS (SELECT 1 FROM i6790_update g WHERE g.device = i6790_update.device);\nSELECT count(*) FROM i6790_update WHERE value = 0.3;\n count \n-------\n     5\n\n"
  },
  {
    "path": "test/expected/upsert.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE upsert_test(time timestamp PRIMARY KEY, temp float, color text);\nSELECT create_hypertable('upsert_test', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n    create_hypertable     \n--------------------------\n (1,public,upsert_test,t)\n\nINSERT INTO upsert_test VALUES ('2017-01-20T09:00:01', 22.5, 'yellow') RETURNING *;\n           time           | temp | color  \n--------------------------+------+--------\n Fri Jan 20 09:00:01 2017 | 22.5 | yellow\n\nINSERT INTO upsert_test VALUES ('2017-01-20T09:00:01', 23.8, 'yellow') ON CONFLICT (time)\nDO UPDATE SET temp = 23.8 RETURNING *;\n           time           | temp | color  \n--------------------------+------+--------\n Fri Jan 20 09:00:01 2017 | 23.8 | yellow\n\nINSERT INTO upsert_test VALUES ('2017-01-20T09:00:01', 78.4, 'yellow') ON CONFLICT DO NOTHING;\nSELECT * FROM upsert_test;\n           time           | temp | color  \n--------------------------+------+--------\n Fri Jan 20 09:00:01 2017 | 23.8 | yellow\n\n-- Test 'Tuples Inserted' and 'Conflicting Tuples' values in EXPLAIN ANALYZE\nEXPLAIN (VERBOSE, ANALYZE, BUFFERS FALSE, COSTS FALSE, TIMING FALSE, SUMMARY FALSE)\n  INSERT INTO upsert_test VALUES\n    ('2017-01-20T09:00:01', 28.5, 'blue'),\n    ('2017-01-20T09:00:01', 21.9, 'red'),\n    ('2017-01-20T10:00:01', 2.4, 'pink') ON CONFLICT DO NOTHING;\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)\n   ->  Insert on public.upsert_test (actual rows=0.00 loops=1)\n         Conflict Resolution: NOTHING\n         Tuples Inserted: 1\n         Conflicting Tuples: 2\n         ->  Values Scan on \"*VALUES*\" (actual rows=3.00 loops=1)\n               Output: \"*VALUES*\".column1, \"*VALUES*\".column2, \"*VALUES*\".column3\n\n-- Test ON CONFLICT ON CONSTRAINT\nINSERT INTO upsert_test VALUES ('2017-01-20T09:00:01', 12.3, 'yellow') ON CONFLICT ON CONSTRAINT upsert_test_pkey\nDO UPDATE SET temp = 12.3 RETURNING time, temp, color;\n           time           | temp | color  \n--------------------------+------+--------\n Fri Jan 20 09:00:01 2017 | 12.3 | yellow\n\n-- Test that update generates error on conflicts\n\\set ON_ERROR_STOP 0\nINSERT INTO upsert_test VALUES ('2017-01-21T09:00:01', 22.5, 'yellow') RETURNING *;\n           time           | temp | color  \n--------------------------+------+--------\n Sat Jan 21 09:00:01 2017 | 22.5 | yellow\n\nUPDATE upsert_test SET time = '2017-01-20T09:00:01';\nERROR:  duplicate key value violates unique constraint \"1_1_upsert_test_pkey\"\n\\set ON_ERROR_STOP 1\n-- Test with UNIQUE index on multiple columns instead of PRIMARY KEY constraint\nCREATE TABLE upsert_test_unique(time timestamp, temp float, color text);\nSELECT create_hypertable('upsert_test_unique', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n        create_hypertable        \n---------------------------------\n (2,public,upsert_test_unique,t)\n\nCREATE UNIQUE INDEX time_color_idx ON upsert_test_unique (time, color);\nINSERT INTO upsert_test_unique VALUES ('2017-01-20T09:00:01', 22.5, 'yellow') RETURNING *;\n           time           | temp | color  \n--------------------------+------+--------\n Fri Jan 20 09:00:01 2017 | 22.5 | yellow\n\nINSERT INTO upsert_test_unique VALUES ('2017-01-20T09:00:01', 21.2, 'brown');\nSELECT * FROM upsert_test_unique ORDER BY time, color DESC;\n           time           | temp | color  \n--------------------------+------+--------\n Fri Jan 20 09:00:01 2017 | 22.5 | yellow\n Fri Jan 20 09:00:01 2017 | 21.2 | brown\n\nINSERT INTO upsert_test_unique VALUES ('2017-01-20T09:00:01', 31.8, 'yellow') ON CONFLICT (time, color)\nDO UPDATE SET temp = 31.8;\nINSERT INTO upsert_test_unique VALUES ('2017-01-20T09:00:01', 54.3, 'yellow') ON CONFLICT DO NOTHING;\nSELECT * FROM upsert_test_unique ORDER BY time, color DESC;\n           time           | temp | color  \n--------------------------+------+--------\n Fri Jan 20 09:00:01 2017 | 31.8 | yellow\n Fri Jan 20 09:00:01 2017 | 21.2 | brown\n\n-- Test with multiple UNIQUE indexes\nCREATE TABLE upsert_test_multi_unique(time timestamp, temp float, color text);\nSELECT create_hypertable('upsert_test_multi_unique', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n           create_hypertable           \n---------------------------------------\n (3,public,upsert_test_multi_unique,t)\n\nALTER TABLE upsert_test_multi_unique ADD CONSTRAINT multi_time_temp UNIQUE (time, temp);\nCREATE UNIQUE INDEX multi_time_color_idx ON upsert_test_multi_unique (time, color);\nINSERT INTO upsert_test_multi_unique VALUES ('2017-01-20T09:00:01', 25.9, 'yellow');\nINSERT INTO upsert_test_multi_unique VALUES ('2017-01-21T09:00:01', 25.9, 'yellow');\nINSERT INTO upsert_test_multi_unique VALUES ('2017-01-20T09:00:01', 23.5, 'brown');\nINSERT INTO upsert_test_multi_unique VALUES ('2017-01-20T09:00:01', 25.9, 'purple') ON CONFLICT DO NOTHING;\nSELECT * FROM upsert_test_multi_unique ORDER BY time, color DESC;\n           time           | temp | color  \n--------------------------+------+--------\n Fri Jan 20 09:00:01 2017 | 25.9 | yellow\n Fri Jan 20 09:00:01 2017 | 23.5 | brown\n Sat Jan 21 09:00:01 2017 | 25.9 | yellow\n\nINSERT INTO upsert_test_multi_unique VALUES ('2017-01-20T09:00:01', 25.9, 'blue') ON CONFLICT (time, temp)\nDO UPDATE SET color = 'blue';\nINSERT INTO upsert_test_multi_unique VALUES ('2017-01-20T09:00:01', 23.5, 'orange') ON CONFLICT ON CONSTRAINT multi_time_temp\nDO UPDATE SET color = excluded.color;\nSELECT * FROM upsert_test_multi_unique ORDER BY time, color DESC;\n           time           | temp | color  \n--------------------------+------+--------\n Fri Jan 20 09:00:01 2017 | 23.5 | orange\n Fri Jan 20 09:00:01 2017 | 25.9 | blue\n Sat Jan 21 09:00:01 2017 | 25.9 | yellow\n\nINSERT INTO upsert_test_multi_unique VALUES ('2017-01-21T09:00:01', 45.7, 'yellow') ON CONFLICT (time, color)\nDO UPDATE SET temp = 45.7;\nSELECT * FROM upsert_test_multi_unique ORDER BY time, color DESC;\n           time           | temp | color  \n--------------------------+------+--------\n Fri Jan 20 09:00:01 2017 | 23.5 | orange\n Fri Jan 20 09:00:01 2017 | 25.9 | blue\n Sat Jan 21 09:00:01 2017 | 45.7 | yellow\n\n\\set ON_ERROR_STOP 0\n-- Here the constraint in the ON CONFLICT clause is not the one that is\n-- actually violated by the INSERT, so it should still fail.\nINSERT INTO upsert_test_multi_unique VALUES ('2017-01-20T09:00:01', 23.5, 'purple') ON CONFLICT (time, color)\nDO UPDATE set temp = 23.5;\nERROR:  duplicate key value violates unique constraint \"3_2_multi_time_temp\"\nINSERT INTO upsert_test_multi_unique VALUES ('2017-01-20T09:00:01', 22.5, 'orange') ON CONFLICT ON CONSTRAINT multi_time_temp\nDO UPDATE set color = 'orange';\nERROR:  duplicate key value violates unique constraint \"_hyper_3_3_chunk_multi_time_color_idx\"\n\\set ON_ERROR_STOP 1\nCREATE TABLE upsert_test_space(time timestamp, device_id_1 char(20), to_drop int, temp float, color text);\n--drop two columns; create one.\nALTER TABLE upsert_test_space DROP to_drop;\nALTER TABLE upsert_test_space DROP device_id_1, ADD device_id char(20);\nALTER TABLE upsert_test_space ADD CONSTRAINT time_space_constraint UNIQUE (time, device_id);\nSELECT create_hypertable('upsert_test_space', 'time', 'device_id', 2, partitioning_func=>'_timescaledb_functions.get_partition_for_key'::regproc);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n       create_hypertable        \n--------------------------------\n (4,public,upsert_test_space,t)\n\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev1', 25.9, 'yellow') RETURNING *;\n           time           | temp | color  |      device_id       \n--------------------------+------+--------+----------------------\n Fri Jan 20 09:00:01 2017 | 25.9 | yellow | dev1                \n\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev2', 25.9, 'yellow');\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev1', 23.5, 'green') ON CONFLICT (time, device_id)\nDO UPDATE SET color = excluded.color;\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev1', 23.5, 'orange') ON CONFLICT ON CONSTRAINT time_space_constraint\nDO UPDATE SET color = excluded.color;\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev2', 23.5, 'orange3') ON CONFLICT (time, device_id)\nDO UPDATE SET color = excluded.color||' (originally '|| upsert_test_space.color ||')' RETURNING *;\n           time           | temp |            color            |      device_id       \n--------------------------+------+-----------------------------+----------------------\n Fri Jan 20 09:00:01 2017 | 25.9 | orange3 (originally yellow) | dev2                \n\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev3', 23.5, 'orange3.1') ON CONFLICT (time, device_id)\nDO UPDATE SET color = excluded.color||' (originally '|| upsert_test_space.color ||')' RETURNING *;\n           time           | temp |   color   |      device_id       \n--------------------------+------+-----------+----------------------\n Fri Jan 20 09:00:01 2017 | 23.5 | orange3.1 | dev3                \n\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev2', 23.5, 'orange4') ON CONFLICT (time, device_id)\nDO NOTHING RETURNING *;\n time | temp | color | device_id \n------+------+-------+-----------\n\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev4', 23.5, 'orange5') ON CONFLICT (time, device_id)\nDO NOTHING RETURNING *;\n           time           | temp |  color  |      device_id       \n--------------------------+------+---------+----------------------\n Fri Jan 20 09:00:01 2017 | 23.5 | orange5 | dev4                \n\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev5', 23.5, 'orange5') ON CONFLICT (time, device_id)\nDO NOTHING RETURNING *;\n           time           | temp |  color  |      device_id       \n--------------------------+------+---------+----------------------\n Fri Jan 20 09:00:01 2017 | 23.5 | orange5 | dev5                \n\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev5', 23.5, 'orange6') ON CONFLICT ON CONSTRAINT time_space_constraint\nDO NOTHING RETURNING *;\n time | temp | color | device_id \n------+------+-------+-----------\n\n--restore a column with the same name as a previously deleted one;\nALTER TABLE upsert_test_space ADD device_id_1 char(20);\nINSERT INTO upsert_test_space (time, device_id, temp, color, device_id_1) VALUES ('2017-01-20T09:00:01', 'dev4', 23.5, 'orange5.1', 'dev-id-1') ON CONFLICT (time, device_id)\nDO UPDATE SET color = excluded.color||' (originally '|| upsert_test_space.color ||')' RETURNING *;\n           time           | temp |             color              |      device_id       | device_id_1 \n--------------------------+------+--------------------------------+----------------------+-------------\n Fri Jan 20 09:00:01 2017 | 23.5 | orange5.1 (originally orange5) | dev4                 | \n\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev5', 23.5, 'orange6') ON CONFLICT (time, device_id)\nDO UPDATE SET color = excluded.color WHERE upsert_test_space.temp < 20 RETURNING *;\n time | temp | color | device_id | device_id_1 \n------+------+-------+-----------+-------------\n\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev5', 23.5, 'orange7') ON CONFLICT (time, device_id)\nDO UPDATE SET color = excluded.color WHERE excluded.temp < 20 RETURNING *;\n time | temp | color | device_id | device_id_1 \n------+------+-------+-----------+-------------\n\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev5', 3.5, 'orange7') ON CONFLICT (time, device_id)\nDO UPDATE SET color = excluded.color, temp=excluded.temp WHERE excluded.temp < 20 RETURNING *;\n           time           | temp |  color  |      device_id       | device_id_1 \n--------------------------+------+---------+----------------------+-------------\n Fri Jan 20 09:00:01 2017 |  3.5 | orange7 | dev5                 | \n\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev5', 43.5, 'orange8') ON CONFLICT (time, device_id)\nDO UPDATE SET color = excluded.color WHERE upsert_test_space.temp < 20 RETURNING *;\n           time           | temp |  color  |      device_id       | device_id_1 \n--------------------------+------+---------+----------------------+-------------\n Fri Jan 20 09:00:01 2017 |  3.5 | orange8 | dev5                 | \n\nINSERT INTO upsert_test_space (time, device_id, temp, color, device_id_1) VALUES ('2017-01-20T09:00:01', 'dev5', 43.5, 'orange8', 'device-id-1-new') ON CONFLICT (time, device_id)\nDO UPDATE SET device_id_1 = excluded.device_id_1 RETURNING *;\n           time           | temp |  color  |      device_id       |     device_id_1      \n--------------------------+------+---------+----------------------+----------------------\n Fri Jan 20 09:00:01 2017 |  3.5 | orange8 | dev5                 | device-id-1-new     \n\nINSERT INTO upsert_test_space (time, device_id, temp, color, device_id_1) VALUES ('2017-01-20T09:00:01', 'dev5', 43.5, 'orange8', 'device-id-1-new') ON CONFLICT (time, device_id)\nDO UPDATE SET device_id_1 = 'device-id-1-new-2', color = 'orange9'  RETURNING *;\n           time           | temp |  color  |      device_id       |     device_id_1      \n--------------------------+------+---------+----------------------+----------------------\n Fri Jan 20 09:00:01 2017 |  3.5 | orange9 | dev5                 | device-id-1-new-2   \n\nSELECT * FROM upsert_test_space;\n           time           | temp |             color              |      device_id       |     device_id_1      \n--------------------------+------+--------------------------------+----------------------+----------------------\n Fri Jan 20 09:00:01 2017 | 25.9 | orange                         | dev1                 | \n Fri Jan 20 09:00:01 2017 | 25.9 | orange3 (originally yellow)    | dev2                 | \n Fri Jan 20 09:00:01 2017 | 23.5 | orange3.1                      | dev3                 | \n Fri Jan 20 09:00:01 2017 | 23.5 | orange5.1 (originally orange5) | dev4                 | \n Fri Jan 20 09:00:01 2017 |  3.5 | orange9                        | dev5                 | device-id-1-new-2   \n\nALTER TABLE upsert_test_space DROP device_id_1, ADD device_id_2 char(20);\nINSERT INTO upsert_test_space (time, device_id, temp, color, device_id_2) VALUES ('2017-01-20T09:00:01', 'dev5', 43.5, 'orange8', 'device-id-2')\nON CONFLICT (time, device_id)\nDO UPDATE SET device_id_2 = 'device-id-2-new', color = 'orange10' RETURNING *;\n           time           | temp |  color   |      device_id       |     device_id_2      \n--------------------------+------+----------+----------------------+----------------------\n Fri Jan 20 09:00:01 2017 |  3.5 | orange10 | dev5                 | device-id-2-new     \n\n--test inserting to to a chunk already in the chunk dispatch cache again.\nINSERT INTO upsert_test_space as current (time, device_id, temp, color, device_id_2) VALUES ('2017-01-20T09:00:01', 'dev5', 43.5, 'orange8', 'device-id-2'),\n('2018-01-20T09:00:01', 'dev5', 43.5, 'orange8', 'device-id-2'),\n('2017-01-20T09:00:01', 'dev3', 43.5, 'orange7', 'device-id-2'),\n('2018-01-21T09:00:01', 'dev5', 43.5, 'orange9', 'device-id-2')\nON CONFLICT (time, device_id)\nDO UPDATE SET device_id_2 = coalesce(excluded.device_id_2,current.device_id_2), color = coalesce(excluded.color,current.color) RETURNING *;\n           time           | temp |  color  |      device_id       |     device_id_2      \n--------------------------+------+---------+----------------------+----------------------\n Fri Jan 20 09:00:01 2017 |  3.5 | orange8 | dev5                 | device-id-2         \n Sat Jan 20 09:00:01 2018 | 43.5 | orange8 | dev5                 | device-id-2         \n Fri Jan 20 09:00:01 2017 | 23.5 | orange7 | dev3                 | device-id-2         \n Sun Jan 21 09:00:01 2018 | 43.5 | orange9 | dev5                 | device-id-2         \n\nWITH CTE AS (\n    INSERT INTO upsert_test_multi_unique\n    VALUES ('2017-01-20T09:00:01', 25.9, 'purple')\n    ON CONFLICT DO NOTHING\n    RETURNING *\n) SELECT 1;\n ?column? \n----------\n        1\n\nWITH CTE AS (\n    INSERT INTO upsert_test_multi_unique\n    VALUES ('2017-01-20T09:00:01', 25.9, 'purple'),\n    ('2017-01-20T09:00:01', 29.9, 'purple1')\n    ON CONFLICT DO NOTHING\n    RETURNING *\n) SELECT * FROM CTE;\n           time           | temp |  color  \n--------------------------+------+---------\n Fri Jan 20 09:00:01 2017 | 29.9 | purple1\n\nWITH CTE AS (\n    INSERT INTO upsert_test_multi_unique\n    VALUES ('2017-01-20T09:00:01', 25.9, 'blue')\n    ON CONFLICT (time, temp) DO UPDATE SET color = 'blue'\n    RETURNING *\n)\nSELECT * FROM CTE;\n           time           | temp | color \n--------------------------+------+-------\n Fri Jan 20 09:00:01 2017 | 25.9 | blue\n\n--test error conditions when an index is dropped on a chunk\nDROP INDEX _timescaledb_internal._hyper_3_3_chunk_multi_time_color_idx;\n--everything is ok if not used as an arbiter index\nINSERT INTO upsert_test_multi_unique\nVALUES ('2017-01-20T09:00:01', 25.9, 'purple')\nON CONFLICT DO NOTHING\nRETURNING *;\n time | temp | color \n------+------+-------\n\n--errors out if used as an arbiter index\n\\set ON_ERROR_STOP 0\nINSERT INTO upsert_test_multi_unique\nVALUES ('2017-01-20T09:00:01', 25.9, 'purple')\nON CONFLICT (time, color) DO NOTHING\nRETURNING *;\nERROR:  could not find arbiter index for hypertable index \"multi_time_color_idx\" on chunk \"_hyper_3_3_chunk\"\n\\set ON_ERROR_STOP 1\n--create table with one chunk that has a tup_conv_map and one that does not\n--to ensure this, create a chunk before altering the table this chunk will not have a tup_conv_map\nCREATE TABLE upsert_test_diffchunk(time timestamp, device_id char(20), to_drop int, temp float, color text);\nSELECT create_hypertable('upsert_test_diffchunk', 'time', chunk_time_interval=> interval '1 month');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n         create_hypertable          \n------------------------------------\n (5,public,upsert_test_diffchunk,t)\n\nCREATE UNIQUE INDEX time_device_idx ON upsert_test_diffchunk (time, device_id);\n--this is the chunk with no tup_conv_map\nINSERT INTO upsert_test_diffchunk (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev1', 25.9, 'yellow') RETURNING *;\n           time           |      device_id       | to_drop | temp | color  \n--------------------------+----------------------+---------+------+--------\n Fri Jan 20 09:00:01 2017 | dev1                 |         | 25.9 | yellow\n\nINSERT INTO upsert_test_diffchunk (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev2', 25.9, 'yellow') RETURNING *;\n           time           |      device_id       | to_drop | temp | color  \n--------------------------+----------------------+---------+------+--------\n Fri Jan 20 09:00:01 2017 | dev2                 |         | 25.9 | yellow\n\n--alter the table\nALTER TABLE upsert_test_diffchunk DROP to_drop;\nALTER TABLE upsert_test_diffchunk ADD device_id_2 char(20);\n--new chunk that does have a tup conv map\nINSERT INTO upsert_test_diffchunk (time, device_id, temp, color) VALUES ('2019-01-20T09:00:01', 'dev1', 23.5, 'orange') ;\nINSERT INTO upsert_test_diffchunk (time, device_id, temp, color) VALUES ('2019-01-20T09:00:01', 'dev2', 23.5, 'orange') ;\nselect * from upsert_test_diffchunk order by time, device_id;\n           time           |      device_id       | temp | color  | device_id_2 \n--------------------------+----------------------+------+--------+-------------\n Fri Jan 20 09:00:01 2017 | dev1                 | 25.9 | yellow | \n Fri Jan 20 09:00:01 2017 | dev2                 | 25.9 | yellow | \n Sun Jan 20 09:00:01 2019 | dev1                 | 23.5 | orange | \n Sun Jan 20 09:00:01 2019 | dev2                 | 23.5 | orange | \n\n--make sure current works\nINSERT INTO upsert_test_diffchunk as current (time, device_id, temp, color, device_id_2) VALUES\n('2019-01-20T09:00:01', 'dev1', 43.5, 'orange2', 'device-id-2'),\n('2017-01-20T09:00:01', 'dev1', 43.5, 'yellow2', 'device-id-2'),\n('2019-01-20T09:00:01', 'dev2', 43.5, 'orange2', 'device-id-2')\nON CONFLICT (time, device_id)\nDO UPDATE SET\ndevice_id_2 = coalesce(excluded.device_id_2,current.device_id_2),\ntemp = coalesce(excluded.temp,current.temp) ,\ncolor = coalesce(excluded.color,current.color);\nselect * from upsert_test_diffchunk order by time, device_id;\n           time           |      device_id       | temp |  color  |     device_id_2      \n--------------------------+----------------------+------+---------+----------------------\n Fri Jan 20 09:00:01 2017 | dev1                 | 43.5 | yellow2 | device-id-2         \n Fri Jan 20 09:00:01 2017 | dev2                 | 25.9 | yellow  | \n Sun Jan 20 09:00:01 2019 | dev1                 | 43.5 | orange2 | device-id-2         \n Sun Jan 20 09:00:01 2019 | dev2                 | 43.5 | orange2 | device-id-2         \n\n--arbiter index tests\nCREATE TABLE upsert_test_arbiter(time timestamp, to_drop int);\nSELECT create_hypertable('upsert_test_arbiter', 'time', chunk_time_interval=> interval '1 month');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n        create_hypertable         \n----------------------------------\n (6,public,upsert_test_arbiter,t)\n\n--this is the chunk with no tup_conv_map\nINSERT INTO upsert_test_arbiter (time, to_drop) VALUES ('2017-01-20T09:00:01', 1) RETURNING *;\n           time           | to_drop \n--------------------------+---------\n Fri Jan 20 09:00:01 2017 |       1\n\nINSERT INTO upsert_test_arbiter (time, to_drop) VALUES ('2017-01-21T09:00:01', 2) RETURNING *;\n           time           | to_drop \n--------------------------+---------\n Sat Jan 21 09:00:01 2017 |       2\n\nINSERT INTO upsert_test_arbiter (time, to_drop) VALUES ('2017-03-20T09:00:01', 3) RETURNING *;\n           time           | to_drop \n--------------------------+---------\n Mon Mar 20 09:00:01 2017 |       3\n\n--alter the table\nALTER TABLE upsert_test_arbiter DROP to_drop;\nALTER TABLE upsert_test_arbiter ADD device_id char(20) DEFAULT 'dev1';\nCREATE UNIQUE INDEX arbiter_time_device_idx ON upsert_test_arbiter (time, device_id);\nINSERT INTO upsert_test_arbiter as current (time, device_id) VALUES\n    ('2018-01-21T09:00:01', 'dev1'),\n    ('2017-01-20T09:00:01', 'dev1'),\n    ('2017-01-21T09:00:01', 'dev2'),\n    ('2018-01-21T09:00:01', 'dev2')\n ON CONFLICT (time, device_id) DO UPDATE SET device_id = coalesce(excluded.device_id,current.device_id)\nRETURNING *;\n           time           |      device_id       \n--------------------------+----------------------\n Sun Jan 21 09:00:01 2018 | dev1                \n Fri Jan 20 09:00:01 2017 | dev1                \n Sat Jan 21 09:00:01 2017 | dev2                \n Sun Jan 21 09:00:01 2018 | dev2                \n\nwith cte as (\nINSERT INTO upsert_test_arbiter (time, device_id) VALUES\n    ('2017-01-21T09:00:01', 'dev2'),\n    ('2018-01-21T09:00:01', 'dev2')\n ON CONFLICT (time, device_id) DO UPDATE SET device_id = 'dev3'\nRETURNING *)\nselect * from cte;\n           time           |      device_id       \n--------------------------+----------------------\n Sat Jan 21 09:00:01 2017 | dev3                \n Sun Jan 21 09:00:01 2018 | dev3                \n\n-- test ON CONFLICT with prepared statements\nCREATE TABLE prepared_test(time timestamptz PRIMARY KEY, value float CHECK(value > 0));\nSELECT create_hypertable('prepared_test','time');\n     create_hypertable      \n----------------------------\n (7,public,prepared_test,t)\n\nCREATE TABLE source_data(time timestamptz PRIMARY KEY, value float);\nINSERT INTO source_data VALUES('2000-01-01',0.5), ('2001-01-01',0.5);\n-- at some point PostgreSQL will turn the plan into a generic plan\n-- so we execute the prepared statement 10 times\n-- check that an error in the prepared statement does not lead to the plan becoming unusable\nPREPARE prep_insert_select AS INSERT INTO prepared_test select * from source_data ON CONFLICT (time) DO UPDATE SET value = EXCLUDED.value;\nEXECUTE prep_insert_select;\nEXECUTE prep_insert_select;\nEXECUTE prep_insert_select;\nEXECUTE prep_insert_select;\nEXECUTE prep_insert_select;\nEXECUTE prep_insert_select;\nEXECUTE prep_insert_select;\nEXECUTE prep_insert_select;\nEXECUTE prep_insert_select;\nEXECUTE prep_insert_select;\n--this insert will create an invalid tuple in source_data\n--so that future calls to prep_insert_select will fail\nINSERT INTO source_data VALUES('2000-01-02',-0.5);\n\\set ON_ERROR_STOP 0\nEXECUTE prep_insert_select;\nERROR:  new row for relation \"_hyper_7_11_chunk\" violates check constraint \"prepared_test_value_check\"\nEXECUTE prep_insert_select;\nERROR:  new row for relation \"_hyper_7_11_chunk\" violates check constraint \"prepared_test_value_check\"\n\\set ON_ERROR_STOP 1\nDELETE FROM source_data WHERE value <= 0;\nEXECUTE prep_insert_select;\nPREPARE prep_insert AS INSERT INTO prepared_test VALUES('2000-01-01',0.5) ON CONFLICT (time) DO UPDATE SET value = EXCLUDED.value;\n-- at some point PostgreSQL will turn the plan into a generic plan\n-- so we execute the prepared statement 10 times\nEXECUTE prep_insert;\nEXECUTE prep_insert;\nEXECUTE prep_insert;\nEXECUTE prep_insert;\nEXECUTE prep_insert;\nEXECUTE prep_insert;\nEXECUTE prep_insert;\nEXECUTE prep_insert;\nEXECUTE prep_insert;\nEXECUTE prep_insert;\nSELECT * FROM prepared_test;\n             time             | value \n------------------------------+-------\n Sat Jan 01 00:00:00 2000 PST |   0.5\n Mon Jan 01 00:00:00 2001 PST |   0.5\n\nDELETE FROM prepared_test;\n-- test ON CONFLICT with functions\nCREATE OR REPLACE FUNCTION test_upsert(t timestamptz, v float) RETURNS VOID AS $sql$\nBEGIN\nINSERT INTO prepared_test VALUES(t,v) ON CONFLICT (time) DO UPDATE SET value = EXCLUDED.value;\nEND;\n$sql$ LANGUAGE PLPGSQL;\n-- at some point PostgreSQL will turn the plan into a generic plan\n-- so we execute the function 10 times\nSELECT counter,test_upsert('2000-01-01',0.5) FROM generate_series(1,10) AS g(counter);\n counter | test_upsert \n---------+-------------\n       1 | \n       2 | \n       3 | \n       4 | \n       5 | \n       6 | \n       7 | \n       8 | \n       9 | \n      10 | \n\nSELECT * FROM prepared_test;\n             time             | value \n------------------------------+-------\n Sat Jan 01 00:00:00 2000 PST |   0.5\n\nDELETE FROM prepared_test;\n-- at some point PostgreSQL will turn the plan into a generic plan\n-- so we execute the function 10 times\nSELECT counter,test_upsert('2000-01-01',0.5) FROM generate_series(1,10) AS g(counter);\n counter | test_upsert \n---------+-------------\n       1 | \n       2 | \n       3 | \n       4 | \n       5 | \n       6 | \n       7 | \n       8 | \n       9 | \n      10 | \n\nSELECT * FROM prepared_test;\n             time             | value \n------------------------------+-------\n Sat Jan 01 00:00:00 2000 PST |   0.5\n\nDELETE FROM prepared_test;\n-- run it again to ensure INSERT path is still working as well\nSELECT counter,test_upsert('2000-01-01',0.5) FROM generate_series(1,10) AS g(counter);\n counter | test_upsert \n---------+-------------\n       1 | \n       2 | \n       3 | \n       4 | \n       5 | \n       6 | \n       7 | \n       8 | \n       9 | \n      10 | \n\nSELECT * FROM prepared_test;\n             time             | value \n------------------------------+-------\n Sat Jan 01 00:00:00 2000 PST |   0.5\n\nDELETE FROM prepared_test;\n-- test ON CONFLICT with functions\nCREATE OR REPLACE FUNCTION test_upsert2(t timestamptz, v float) RETURNS VOID AS $sql$\nBEGIN\nINSERT INTO prepared_test VALUES(t,v) ON CONFLICT (time) DO UPDATE SET value = prepared_test.value + 1.0;\nEND;\n$sql$ LANGUAGE PLPGSQL;\n-- at some point PostgreSQL will turn the plan into a generic plan\n-- so we execute the function 10 times\nSELECT counter,test_upsert2('2000-01-01',1.0) FROM generate_series(1,10) AS g(counter);\n counter | test_upsert2 \n---------+--------------\n       1 | \n       2 | \n       3 | \n       4 | \n       5 | \n       6 | \n       7 | \n       8 | \n       9 | \n      10 | \n\nSELECT * FROM prepared_test;\n             time             | value \n------------------------------+-------\n Sat Jan 01 00:00:00 2000 PST |    10\n\n"
  },
  {
    "path": "test/expected/util.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\\set ECHO errors\n                item                \n------------------------------------\n db_util_wizard=a/db_util_wizard\n db_util_wizard=ar/db_util_wizard\n db_util_wizard=a*/db_util_wizard\n db_util_wizard=a*r*/db_util_wizard\n [NULL]\n [NULL]\n [NULL]\n [NULL]\n =a*r*/db_util_wizard\n db_util_wizard=a*r*/0\n\nSELECT _timescaledb_functions.align_to_bucket('5 minutes'::interval, rng::tstzrange) FROM data;\n                         align_to_bucket                         \n-----------------------------------------------------------------\n [\"Fri Apr 25 02:10:00 2025 PDT\",\"Fri Apr 25 02:15:00 2025 PDT\")\n [\"Fri Apr 25 02:10:00 2025 PDT\",\"Fri Apr 25 02:20:00 2025 PDT\")\n [\"Fri Apr 25 02:10:00 2025 PDT\",\"Fri Apr 25 02:20:00 2025 PDT\")\n\n\\set ON_ERROR_STOP 0\nSELECT _timescaledb_functions.align_to_bucket(null, null);\nERROR:  could not determine polymorphic type because input has type unknown\nSELECT _timescaledb_functions.align_to_bucket(null::interval, null::tstzrange);\n align_to_bucket \n-----------------\n [NULL]\n\nSELECT _timescaledb_functions.align_to_bucket(\n       null::interval,\n       '[\"2025-04-25 11:10:00+02\",\"2025-04-25 11:14:00+02\"]'::tstzrange\n);\n align_to_bucket \n-----------------\n [NULL]\n\n\\set ON_ERROR_STOP 1\nSELECT typ,\n       _timescaledb_functions.get_internal_time_min(typ),\n       _timescaledb_functions.get_internal_time_max(typ)\n  FROM (VALUES\n    ('bigint'::regtype), ('int'::regtype), ('smallint'::regtype),\n    ('timestamp'::regtype), ('timestamptz'::regtype), ('date'::regtype),\n    (null::regtype)\n  ) t(typ);\n             typ             | get_internal_time_min | get_internal_time_max \n-----------------------------+-----------------------+-----------------------\n bigint                      |  -9223372036854775808 |   9223372036854775807\n integer                     |           -2147483648 |            2147483647\n smallint                    |                -32768 |                 32767\n timestamp without time zone |   -210866803200000000 |   9223371331199999999\n timestamp with time zone    |   -210866803200000000 |   9223371331199999999\n date                        |   -210866803200000000 |   9223371331199999999\n [NULL]                      |                [NULL] |                [NULL]\n\n\\set ON_ERROR_STOP 0\nSELECT _timescaledb_functions.get_internal_time_min(0);\nERROR:  unsupported time type \"-\"\nSELECT _timescaledb_functions.get_internal_time_max(0);\nERROR:  unsupported time type \"-\"\n\\set ON_ERROR_STOP 1\nWITH\n  tstzranges AS (\n    SELECT vid,\n           rng::tstzrange,\n           lower(rng::tstzrange) AS lower_ts,\n           upper(rng::tstzrange) AS upper_ts\n      FROM data\n  ),\n  usecranges AS (\n    SELECT vid,\n           _timescaledb_functions.to_unix_microseconds(lower_ts) AS lower_usec,\n           _timescaledb_functions.to_unix_microseconds(upper_ts) AS upper_usec\n      FROM tstzranges\n  )\nSELECT _timescaledb_functions.make_multirange_from_internal_time(rng, lower_usec, upper_usec),\n       _timescaledb_functions.make_range_from_internal_time(rng, lower_ts, upper_ts)\n  FROM tstzranges join usecranges using (vid);\n                make_multirange_from_internal_time                 |                  make_range_from_internal_time                  \n-------------------------------------------------------------------+-----------------------------------------------------------------\n {[\"Fri Apr 25 02:10:00 2025 PDT\",\"Fri Apr 25 02:14:00 2025 PDT\")} | [\"Fri Apr 25 02:10:00 2025 PDT\",\"Fri Apr 25 02:14:00 2025 PDT\")\n {[\"Fri Apr 25 02:10:00 2025 PDT\",\"Fri Apr 25 02:17:00 2025 PDT\")} | [\"Fri Apr 25 02:10:00 2025 PDT\",\"Fri Apr 25 02:17:00 2025 PDT\")\n {[\"Fri Apr 25 02:10:00 2025 PDT\",\"Fri Apr 25 02:20:00 2025 PDT\")} | [\"Fri Apr 25 02:10:00 2025 PDT\",\"Fri Apr 25 02:20:00 2025 PDT\")\n\nWITH\n  tsranges AS (\n    SELECT vid,\n           rng::tsrange,\n           lower(rng::tsrange) AS lower_ts,\n           upper(rng::tsrange) AS upper_ts\n      FROM data\n  ),\n  usecranges AS (\n    SELECT vid,\n           _timescaledb_functions.to_unix_microseconds(lower_ts) AS lower_usec,\n           _timescaledb_functions.to_unix_microseconds(upper_ts) AS upper_usec\n      FROM tsranges\n  )\nSELECT _timescaledb_functions.make_multirange_from_internal_time(rng, lower_usec, upper_usec),\n       _timescaledb_functions.make_range_from_internal_time(rng, lower_ts, upper_ts)\n  FROM tsranges join usecranges using (vid);\n            make_multirange_from_internal_time             |              make_range_from_internal_time              \n-----------------------------------------------------------+---------------------------------------------------------\n {[\"Fri Apr 25 18:10:00 2025\",\"Fri Apr 25 18:14:00 2025\")} | [\"Fri Apr 25 11:10:00 2025\",\"Fri Apr 25 11:14:00 2025\")\n {[\"Fri Apr 25 18:10:00 2025\",\"Fri Apr 25 18:17:00 2025\")} | [\"Fri Apr 25 11:10:00 2025\",\"Fri Apr 25 11:17:00 2025\")\n {[\"Fri Apr 25 18:10:00 2025\",\"Fri Apr 25 18:20:00 2025\")} | [\"Fri Apr 25 11:10:00 2025\",\"Fri Apr 25 11:20:00 2025\")\n\n"
  },
  {
    "path": "test/expected/uuid.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n--\n--\n-- Test \"time\" partitioning on UUIDv7\n--\n--\nCREATE TABLE uuid_events(id uuid primary key, device int, temp float);\n\\set ON_ERROR_STOP 0\n-- Test invalid interval type\nSELECT create_hypertable('uuid_events', 'id', chunk_time_interval => true);\nERROR:  invalid interval type for uuid dimension\n\\set ON_ERROR_STOP 1\nSELECT create_hypertable('uuid_events', 'id', chunk_time_interval => interval '1 day');\n    create_hypertable     \n--------------------------\n (1,public,uuid_events,t)\n\nSELECT time_interval\nFROM timescaledb_information.dimensions\nWHERE hypertable_name = 'uuid_events';\n time_interval \n---------------\n @ 1 day\n\n--\n-- Test that inserting boundary values generates the right constraints\n-- on chunks.\n--\n-- First value with min time: 00000000-0000-7000-8000-000000000000\nBEGIN;\nINSERT INTO uuid_events VALUES ('00000000-0000-7000-8000-000000000000', 1, 1.0);\nSELECT (test.show_constraints(ch)).* from show_chunks('uuid_events') ch;\n      Constraint      | Type | Columns |                    Index                     |                                                      Expr                                                      | Deferrable | Deferred | Validated \n----------------------+------+---------+----------------------------------------------+----------------------------------------------------------------------------------------------------------------+------------+----------+-----------\n 1_1_uuid_events_pkey | p    | {id}    | _timescaledb_internal.\"1_1_uuid_events_pkey\" |                                                                                                                | f          | f        | t\n constraint_1         | c    | {id}    | -                                            | ((id >= '00000000-0000-0000-0000-000000000000'::uuid) AND (id < '00000526-5c00-0000-0000-000000000000'::uuid)) | f          | f        | t\n\nSELECT uuid_timestamp(id), device, temp\nFROM uuid_events ORDER BY id;\n        uuid_timestamp        | device | temp \n------------------------------+--------+------\n Wed Dec 31 16:00:00 1969 PST |      1 |    1\n\n-- Update v7 UUID to a v4 UUID that doesn't violate the chunk's range\n-- constraint. Currently we don't prevent this \"loophole\".\nUPDATE uuid_events SET id = '00000000-0001-4000-8000-000000000000'\nWHERE id = '00000000-0000-7000-8000-000000000000';\nSELECT uuid_timestamp(id), device, temp\nFROM uuid_events ORDER BY id;\n uuid_timestamp | device | temp \n----------------+--------+------\n                |      1 |    1\n\n-- Update v7 UUID to a v4 that violates the chunk constraint:\n\\set ON_ERROR_STOP 0\nUPDATE uuid_events SET id = 'ffff0000-0000-4000-8000-000000000000'\nWHERE id = '00000000-0001-4000-8000-000000000000';\nERROR:  new row for relation \"_hyper_1_1_chunk\" violates check constraint \"constraint_1\"\n\\set ON_ERROR_STOP 1\nROLLBACK;\n-- Last value with min time: 00000000-0000-7fff-bfff-ffffffffffff\nBEGIN;\nINSERT INTO uuid_events VALUES ('00000000-0000-7fff-bfff-ffffffffffff', 1, 1.0);\nSELECT (test.show_constraints(ch)).* from show_chunks('uuid_events') ch;\n      Constraint      | Type | Columns |                    Index                     |                                                      Expr                                                      | Deferrable | Deferred | Validated \n----------------------+------+---------+----------------------------------------------+----------------------------------------------------------------------------------------------------------------+------------+----------+-----------\n 2_2_uuid_events_pkey | p    | {id}    | _timescaledb_internal.\"2_2_uuid_events_pkey\" |                                                                                                                | f          | f        | t\n constraint_2         | c    | {id}    | -                                            | ((id >= '00000000-0000-0000-0000-000000000000'::uuid) AND (id < '00000526-5c00-0000-0000-000000000000'::uuid)) | f          | f        | t\n\nROLLBACK;\n-- First value with max time: ffffffff-ffff-7000-8000-000000000000\nBEGIN;\nINSERT INTO uuid_events VALUES ('ffffffff-ffff-7000-8000-000000000000', 1, 1.0);\nSELECT (test.show_constraints(ch)).* from show_chunks('uuid_events') ch;\n      Constraint      | Type | Columns |                    Index                     |                         Expr                         | Deferrable | Deferred | Validated \n----------------------+------+---------+----------------------------------------------+------------------------------------------------------+------------+----------+-----------\n 3_3_uuid_events_pkey | p    | {id}    | _timescaledb_internal.\"3_3_uuid_events_pkey\" |                                                      | f          | f        | t\n constraint_3         | c    | {id}    | -                                            | (id >= 'fffffed0-3000-0000-0000-000000000000'::uuid) | f          | f        | t\n\nROLLBACK;\n-- (Max time with min value) + 1\nBEGIN;\nINSERT INTO uuid_events VALUES ('ffffffff-ffff-7000-8000-000000000001', 1, 1.0);\nSELECT (test.show_constraints(ch)).* from show_chunks('uuid_events') ch;\n      Constraint      | Type | Columns |                    Index                     |                         Expr                         | Deferrable | Deferred | Validated \n----------------------+------+---------+----------------------------------------------+------------------------------------------------------+------------+----------+-----------\n 4_4_uuid_events_pkey | p    | {id}    | _timescaledb_internal.\"4_4_uuid_events_pkey\" |                                                      | f          | f        | t\n constraint_4         | c    | {id}    | -                                            | (id >= 'fffffed0-3000-0000-0000-000000000000'::uuid) | f          | f        | t\n\nROLLBACK;\n-- Last value with max time: ffffffff-ffff-7fff-bfff-ffffffffffff\nBEGIN;\nINSERT INTO uuid_events VALUES ('ffffffff-ffff-7fff-bfff-ffffffffffff', 1, 1.0);\nSELECT (test.show_constraints(ch)).* from show_chunks('uuid_events') ch;\n      Constraint      | Type | Columns |                    Index                     |                         Expr                         | Deferrable | Deferred | Validated \n----------------------+------+---------+----------------------------------------------+------------------------------------------------------+------------+----------+-----------\n 5_5_uuid_events_pkey | p    | {id}    | _timescaledb_internal.\"5_5_uuid_events_pkey\" |                                                      | f          | f        | t\n constraint_5         | c    | {id}    | -                                            | (id >= 'fffffed0-3000-0000-0000-000000000000'::uuid) | f          | f        | t\n\nROLLBACK;\n--\n-- It is possible to generate UUIDs like follows, but the random\n-- generator used doesn't respect setseed() so used constant UUIDs for\n-- determinism.\n--\n-- (_timescaledb_functions.uuid_v7_from_timestamptz('2025-01-01 01:00 PST'), 1, 1.0),\n-- (_timescaledb_functions.uuid_v7_from_timestamptz('2025-01-01 02:00 PST'), 2, 2.0),\n-- (_timescaledb_functions.uuid_v7_from_timestamptz('2025-01-02 01:00 PST'), 3, 3.0),\n-- (_timescaledb_functions.uuid_v7_from_timestamptz('2025-01-02 02:00 PST'), 4, 4.0),\n-- (_timescaledb_functions.uuid_v7_from_timestamptz('2025-01-03 03:00 PST'), 5, 5.0),\n-- (_timescaledb_functions.uuid_v7_from_timestamptz('2025-01-03 10:00 PST'), 6, 6.0);\n--\nINSERT INTO uuid_events VALUES\n       ('0194214e-cd00-7000-a9a7-63f1416dab45', 2, 2.0),\n       ('01942117-de80-7000-8121-f12b2b69dd96', 1, 1.0),\n       ('0194263e-3a80-7000-8f40-82c987b1bc1f', 3, 3.0),\n       ('01942675-2900-7000-8db1-a98694b18785', 4, 4.0),\n       ('01942bd2-7380-7000-9bc4-5f97443907b8', 5, 5.0),\n       ('01942d52-f900-7000-866e-07d6404d53c1', 6, 6.0);\nSELECT * FROM show_chunks('uuid_events');\n              show_chunks               \n----------------------------------------\n _timescaledb_internal._hyper_1_6_chunk\n _timescaledb_internal._hyper_1_7_chunk\n _timescaledb_internal._hyper_1_8_chunk\n\nSELECT (test.show_constraints(ch)).* from show_chunks('uuid_events') ch;\n      Constraint      | Type | Columns |                    Index                     |                                                      Expr                                                      | Deferrable | Deferred | Validated \n----------------------+------+---------+----------------------------------------------+----------------------------------------------------------------------------------------------------------------+------------+----------+-----------\n 6_6_uuid_events_pkey | p    | {id}    | _timescaledb_internal.\"6_6_uuid_events_pkey\" |                                                                                                                | f          | f        | t\n constraint_6         | c    | {id}    | -                                            | ((id >= '01941f29-7c00-0000-0000-000000000000'::uuid) AND (id < '0194244f-d800-0000-0000-000000000000'::uuid)) | f          | f        | t\n 7_7_uuid_events_pkey | p    | {id}    | _timescaledb_internal.\"7_7_uuid_events_pkey\" |                                                                                                                | f          | f        | t\n constraint_7         | c    | {id}    | -                                            | ((id >= '0194244f-d800-0000-0000-000000000000'::uuid) AND (id < '01942976-3400-0000-0000-000000000000'::uuid)) | f          | f        | t\n 8_8_uuid_events_pkey | p    | {id}    | _timescaledb_internal.\"8_8_uuid_events_pkey\" |                                                                                                                | f          | f        | t\n constraint_8         | c    | {id}    | -                                            | ((id >= '01942976-3400-0000-0000-000000000000'::uuid) AND (id < '01942e9c-9000-0000-0000-000000000000'::uuid)) | f          | f        | t\n\nSELECT id, device, temp FROM uuid_events;\n                  id                  | device | temp \n--------------------------------------+--------+------\n 0194214e-cd00-7000-a9a7-63f1416dab45 |      2 |    2\n 01942117-de80-7000-8121-f12b2b69dd96 |      1 |    1\n 0194263e-3a80-7000-8f40-82c987b1bc1f |      3 |    3\n 01942675-2900-7000-8db1-a98694b18785 |      4 |    4\n 01942bd2-7380-7000-9bc4-5f97443907b8 |      5 |    5\n 01942d52-f900-7000-866e-07d6404d53c1 |      6 |    6\n\nSELECT uuid_timestamp(id), device, temp\nFROM uuid_events;\n        uuid_timestamp        | device | temp \n------------------------------+--------+------\n Wed Jan 01 02:00:00 2025 PST |      2 |    2\n Wed Jan 01 01:00:00 2025 PST |      1 |    1\n Thu Jan 02 01:00:00 2025 PST |      3 |    3\n Thu Jan 02 02:00:00 2025 PST |      4 |    4\n Fri Jan 03 03:00:00 2025 PST |      5 |    5\n Fri Jan 03 10:00:00 2025 PST |      6 |    6\n\nSELECT uuid_timestamp(id), device, temp\nFROM uuid_events ORDER BY id;\n        uuid_timestamp        | device | temp \n------------------------------+--------+------\n Wed Jan 01 01:00:00 2025 PST |      1 |    1\n Wed Jan 01 02:00:00 2025 PST |      2 |    2\n Thu Jan 02 01:00:00 2025 PST |      3 |    3\n Thu Jan 02 02:00:00 2025 PST |      4 |    4\n Fri Jan 03 03:00:00 2025 PST |      5 |    5\n Fri Jan 03 10:00:00 2025 PST |      6 |    6\n\nSELECT\n    _timescaledb_functions.to_timestamp(range_start) AS range_start,\n    _timescaledb_functions.to_timestamp(range_end) AS range_end\nFROM _timescaledb_catalog.dimension_slice ds\nJOIN _timescaledb_catalog.dimension d ON (ds.dimension_id = d.id)\nJOIN _timescaledb_catalog.hypertable h ON (d.hypertable_id = h.id)\nWHERE h.table_name = 'uuid_events';\n         range_start          |          range_end           \n------------------------------+------------------------------\n Tue Dec 31 16:00:00 2024 PST | Wed Jan 01 16:00:00 2025 PST\n Wed Jan 01 16:00:00 2025 PST | Thu Jan 02 16:00:00 2025 PST\n Thu Jan 02 16:00:00 2025 PST | Fri Jan 03 16:00:00 2025 PST\n\nSELECT\n    _timescaledb_functions.to_timestamp(range_start) AS chunk_range_start,\n    _timescaledb_functions.to_timestamp(range_end) AS chunk_range_end\nFROM _timescaledb_catalog.dimension_slice ds\nJOIN _timescaledb_catalog.dimension d ON (ds.dimension_id = d.id)\nJOIN _timescaledb_catalog.hypertable h ON (d.hypertable_id = h.id)\nWHERE h.table_name = 'uuid_events'\nLIMIT 1 OFFSET 1 \\gset\n-- Test that chunk exclusion on uuidv7 column works\nSELECT :'chunk_range_start',  to_uuidv7_boundary(:'chunk_range_start');\n           ?column?           |          to_uuidv7_boundary          \n------------------------------+--------------------------------------\n Wed Jan 01 16:00:00 2025 PST | 0194244f-d800-7000-8000-000000000000\n\n-- Exclude all but one chunk\nEXPLAIN (verbose, buffers off, costs off, timing off)\nSELECT uuid_timestamp(id), device, temp\nFROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_start');\n--- QUERY PLAN ---\n Result\n   Output: uuid_timestamp(_hyper_1_6_chunk.id), _hyper_1_6_chunk.device, _hyper_1_6_chunk.temp\n   ->  Seq Scan on _timescaledb_internal._hyper_1_6_chunk\n         Output: _hyper_1_6_chunk.id, _hyper_1_6_chunk.device, _hyper_1_6_chunk.temp\n\nSELECT uuid_timestamp(id), device, temp\nFROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_start') ORDER BY id;\n        uuid_timestamp        | device | temp \n------------------------------+--------+------\n Wed Jan 01 01:00:00 2025 PST |      1 |    1\n Wed Jan 01 02:00:00 2025 PST |      2 |    2\n\n-- Exclude only one chunk. Add ordering (DESC)\nEXPLAIN (verbose, buffers off, costs off, timing off)\nSELECT uuid_timestamp(id), device, temp\nFROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')\nORDER BY id DESC;\n--- QUERY PLAN ---\n Result\n   Output: uuid_timestamp(uuid_events.id), uuid_events.device, uuid_events.temp, uuid_events.id\n   ->  Custom Scan (ChunkAppend) on public.uuid_events\n         Output: uuid_events.id, uuid_events.device, uuid_events.temp\n         Order: uuid_events.id DESC\n         Startup Exclusion: false\n         Runtime Exclusion: false\n         ->  Index Scan Backward using \"7_7_uuid_events_pkey\" on _timescaledb_internal._hyper_1_7_chunk\n               Output: _hyper_1_7_chunk.id, _hyper_1_7_chunk.device, _hyper_1_7_chunk.temp\n         ->  Index Scan Backward using \"6_6_uuid_events_pkey\" on _timescaledb_internal._hyper_1_6_chunk\n               Output: _hyper_1_6_chunk.id, _hyper_1_6_chunk.device, _hyper_1_6_chunk.temp\n\nSELECT uuid_timestamp(id), device, temp\nFROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')\nORDER BY id DESC;\n        uuid_timestamp        | device | temp \n------------------------------+--------+------\n Thu Jan 02 02:00:00 2025 PST |      4 |    4\n Thu Jan 02 01:00:00 2025 PST |      3 |    3\n Wed Jan 01 02:00:00 2025 PST |      2 |    2\n Wed Jan 01 01:00:00 2025 PST |      1 |    1\n\nSELECT time_bucket('1 day', id) AS day, avg(temp)\nFROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')\nGROUP BY id ORDER BY id DESC;\n             day              | avg \n------------------------------+-----\n Wed Jan 01 16:00:00 2025 PST |   4\n Wed Jan 01 16:00:00 2025 PST |   3\n Tue Dec 31 16:00:00 2024 PST |   2\n Tue Dec 31 16:00:00 2024 PST |   1\n\nSELECT time_bucket('1 day', uuid_timestamp(id)) AS day, avg(temp)\nFROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')\nGROUP BY id ORDER BY id DESC;\n             day              | avg \n------------------------------+-----\n Wed Jan 01 16:00:00 2025 PST |   4\n Wed Jan 01 16:00:00 2025 PST |   3\n Tue Dec 31 16:00:00 2024 PST |   2\n Tue Dec 31 16:00:00 2024 PST |   1\n\n-- Bucket with offset\nSELECT time_bucket('1 day', id, \"offset\" => '1 week') AS day, avg(temp)\nFROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')\nGROUP BY id ORDER BY id DESC;\n             day              | avg \n------------------------------+-----\n Wed Jan 01 16:00:00 2025 PST |   4\n Wed Jan 01 16:00:00 2025 PST |   3\n Tue Dec 31 16:00:00 2024 PST |   2\n Tue Dec 31 16:00:00 2024 PST |   1\n\n-- Bucket with origin\nSELECT time_bucket('1 day', id, timestamptz '2000-01-01 00:00') AS day, avg(temp)\nFROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')\nGROUP BY id ORDER BY id DESC;\n             day              | avg \n------------------------------+-----\n Thu Jan 02 00:00:00 2025 PST |   4\n Thu Jan 02 00:00:00 2025 PST |   3\n Wed Jan 01 00:00:00 2025 PST |   2\n Wed Jan 01 00:00:00 2025 PST |   1\n\n-- Bucket with time zone\nSELECT time_bucket('1 day', id, 'Europe/Stockholm') at time zone 'Europe/Stockholm' as day, avg(temp)\nFROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')\nGROUP BY id ORDER BY id DESC;\n           day            | avg \n--------------------------+-----\n Thu Jan 02 00:00:00 2025 |   4\n Thu Jan 02 00:00:00 2025 |   3\n Wed Jan 01 00:00:00 2025 |   2\n Wed Jan 01 00:00:00 2025 |   1\n\n-- Test NULL arguments\nSELECT time_bucket('1 day', id, 'Europe/Stockholm'::text, NULL::timestamptz) AS day, avg(temp)\nFROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')\nGROUP BY id ORDER BY id DESC;\n             day              | avg \n------------------------------+-----\n Wed Jan 01 15:00:00 2025 PST |   4\n Wed Jan 01 15:00:00 2025 PST |   3\n Tue Dec 31 15:00:00 2024 PST |   2\n Tue Dec 31 15:00:00 2024 PST |   1\n\nSELECT time_bucket('1 day', id, 'Europe/Stockholm'::text, '2000-01-01 00:00'::timestamptz, NULL::interval) AS day, avg(temp)\nFROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')\nGROUP BY id ORDER BY id DESC;\n             day              | avg \n------------------------------+-----\n Thu Jan 02 00:00:00 2025 PST |   4\n Thu Jan 02 00:00:00 2025 PST |   3\n Wed Jan 01 00:00:00 2025 PST |   2\n Wed Jan 01 00:00:00 2025 PST |   1\n\n-- Test UUID time_bucket in WHERE clause. Note that there is currently no chunk\n-- exclusion when using time_bucket() in the WHERE clause qual. This requires\n-- special handling of the time_bucket() transform optimizatios for different\n-- operators.\nEXPLAIN (COSTS OFF) SELECT time_bucket('1 day', id) AS day, avg(temp)\nFROM uuid_events WHERE time_bucket('1 day', id) >= :'chunk_range_end'\nGROUP BY id ORDER BY id DESC;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _hyper_1_8_chunk.id DESC\n   ->  HashAggregate\n         Group Key: _hyper_1_8_chunk.id\n         ->  Seq Scan on _hyper_1_8_chunk\n               Filter: (time_bucket('@ 1 day'::interval, id) >= 'Thu Jan 02 16:00:00 2025 PST'::timestamp with time zone)\n\nSELECT time_bucket('1 day', id) AS day, avg(temp)\nFROM uuid_events WHERE time_bucket('1 day', id) >= :'chunk_range_end'\nGROUP BY id ORDER BY id DESC;\n             day              | avg \n------------------------------+-----\n Thu Jan 02 16:00:00 2025 PST |   6\n Thu Jan 02 16:00:00 2025 PST |   5\n\nEXPLAIN (COSTS OFF) SELECT time_bucket('1 day', id) AS day, avg(temp)\nFROM uuid_events WHERE time_bucket('1 day', id) > :'chunk_range_start'\nGROUP BY id ORDER BY id DESC;\n--- QUERY PLAN ---\n GroupAggregate\n   Group Key: uuid_events.id\n   ->  Custom Scan (ChunkAppend) on uuid_events\n         Order: uuid_events.id DESC\n         ->  Index Scan Backward using \"8_8_uuid_events_pkey\" on _hyper_1_8_chunk\n               Filter: (time_bucket('@ 1 day'::interval, id) > 'Wed Jan 01 16:00:00 2025 PST'::timestamp with time zone)\n         ->  Index Scan Backward using \"7_7_uuid_events_pkey\" on _hyper_1_7_chunk\n               Index Cond: (id > '0194244f-d800-7000-8000-000000000000'::uuid)\n               Filter: (time_bucket('@ 1 day'::interval, id) > 'Wed Jan 01 16:00:00 2025 PST'::timestamp with time zone)\n\nSELECT time_bucket('1 day', id) AS day, avg(temp)\nFROM uuid_events WHERE time_bucket('1 day', id) > :'chunk_range_start'\nGROUP BY id ORDER BY id DESC;\n             day              | avg \n------------------------------+-----\n Thu Jan 02 16:00:00 2025 PST |   6\n Thu Jan 02 16:00:00 2025 PST |   5\n\nEXPLAIN (COSTS OFF) SELECT time_bucket('1 day', id) AS day, avg(temp)\nFROM uuid_events WHERE time_bucket('1 day', id) < :'chunk_range_end'\nGROUP BY id ORDER BY id DESC;\n--- QUERY PLAN ---\n Sort\n   Sort Key: uuid_events.id DESC\n   ->  HashAggregate\n         Group Key: uuid_events.id\n         ->  Append\n               ->  Seq Scan on _hyper_1_7_chunk\n                     Filter: (time_bucket('@ 1 day'::interval, id) < 'Thu Jan 02 16:00:00 2025 PST'::timestamp with time zone)\n               ->  Seq Scan on _hyper_1_6_chunk\n                     Filter: (time_bucket('@ 1 day'::interval, id) < 'Thu Jan 02 16:00:00 2025 PST'::timestamp with time zone)\n\nSELECT time_bucket('1 day', id) AS day, avg(temp)\nFROM uuid_events WHERE time_bucket('1 day', id) < :'chunk_range_end'\nGROUP BY id ORDER BY id DESC;\n             day              | avg \n------------------------------+-----\n Wed Jan 01 16:00:00 2025 PST |   4\n Wed Jan 01 16:00:00 2025 PST |   3\n Tue Dec 31 16:00:00 2024 PST |   2\n Tue Dec 31 16:00:00 2024 PST |   1\n\nEXPLAIN (COSTS OFF) SELECT time_bucket('1 day', id) AS day, avg(temp)\nFROM uuid_events WHERE time_bucket('1 day', id) <= :'chunk_range_start'\nGROUP BY id ORDER BY id DESC;\n--- QUERY PLAN ---\n Sort\n   Sort Key: uuid_events.id DESC\n   ->  HashAggregate\n         Group Key: uuid_events.id\n         ->  Append\n               ->  Index Scan Backward using \"8_8_uuid_events_pkey\" on _hyper_1_8_chunk\n                     Index Cond: (id <= '01942976-3400-7000-8000-000000000000'::uuid)\n                     Filter: (time_bucket('@ 1 day'::interval, id) <= 'Wed Jan 01 16:00:00 2025 PST'::timestamp with time zone)\n               ->  Seq Scan on _hyper_1_7_chunk\n                     Filter: (time_bucket('@ 1 day'::interval, id) <= 'Wed Jan 01 16:00:00 2025 PST'::timestamp with time zone)\n               ->  Seq Scan on _hyper_1_6_chunk\n                     Filter: (time_bucket('@ 1 day'::interval, id) <= 'Wed Jan 01 16:00:00 2025 PST'::timestamp with time zone)\n\nSELECT time_bucket('1 day', id) AS day, avg(temp)\nFROM uuid_events WHERE time_bucket('1 day', id) <= :'chunk_range_start'\nGROUP BY id ORDER BY id DESC;\n             day              | avg \n------------------------------+-----\n Wed Jan 01 16:00:00 2025 PST |   4\n Wed Jan 01 16:00:00 2025 PST |   3\n Tue Dec 31 16:00:00 2024 PST |   2\n Tue Dec 31 16:00:00 2024 PST |   1\n\n-- Test time_bucket on non-v7 UUID\n\\set ON_ERROR_STOP 0\nSELECT time_bucket('1 day', 'ffff0000-0000-4000-8000-000000000000'::uuid);\nERROR:  not a version 7 UUID: ffff0000-0000-4000-8000-000000000000\n\\set ON_ERROR_STOP 1\nCREATE VIEW chunk_ranges AS\nSELECT\n  chunk_name,\n  range_start,\n  range_end\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'uuid_events';\nSELECT * FROM chunk_ranges;\n    chunk_name    |         range_start          |          range_end           \n------------------+------------------------------+------------------------------\n _hyper_1_6_chunk | Tue Dec 31 16:00:00 2024 PST | Wed Jan 01 16:00:00 2025 PST\n _hyper_1_7_chunk | Wed Jan 01 16:00:00 2025 PST | Thu Jan 02 16:00:00 2025 PST\n _hyper_1_8_chunk | Thu Jan 02 16:00:00 2025 PST | Fri Jan 03 16:00:00 2025 PST\n\nSELECT show_chunks('uuid_events', older_than => INTERVAL '1 day');\n              show_chunks               \n----------------------------------------\n _timescaledb_internal._hyper_1_6_chunk\n _timescaledb_internal._hyper_1_7_chunk\n _timescaledb_internal._hyper_1_8_chunk\n\nSELECT show_chunks('uuid_events', older_than => '2025-01-02');\n              show_chunks               \n----------------------------------------\n _timescaledb_internal._hyper_1_6_chunk\n\nSELECT show_chunks('uuid_events', newer_than => '2025-01-02');\n              show_chunks               \n----------------------------------------\n _timescaledb_internal._hyper_1_8_chunk\n\nSELECT drop_chunks('uuid_events', older_than => '2025-01-02');\n              drop_chunks               \n----------------------------------------\n _timescaledb_internal._hyper_1_6_chunk\n\nSELECT show_chunks('uuid_events');\n              show_chunks               \n----------------------------------------\n _timescaledb_internal._hyper_1_7_chunk\n _timescaledb_internal._hyper_1_8_chunk\n\n-- Insert non-v7 UUIDs\n\\set ON_ERROR_STOP 0\nINSERT INTO uuid_events SELECT 'a8961135-cd89-4c4b-aa05-79df642407dd', 5, 5.0;\nERROR:  a8961135-cd89-4c4b-aa05-79df642407dd is not a version 7 UUID\n\\set ON_ERROR_STOP 1\n-- Insert as v7 UUID and later change to non-v7 to show effect on show_chunks()\n-- and drop_chunks()\nINSERT INTO uuid_events SELECT 'a8961135-cd89-7000-aa05-79df642407dd', 5, 5.0;\nSELECT * FROM chunk_ranges;\n    chunk_name    |         range_start          |          range_end           \n------------------+------------------------------+------------------------------\n _hyper_1_7_chunk | Wed Jan 01 16:00:00 2025 PST | Thu Jan 02 16:00:00 2025 PST\n _hyper_1_8_chunk | Thu Jan 02 16:00:00 2025 PST | Fri Jan 03 16:00:00 2025 PST\n _hyper_1_9_chunk | Sun Nov 26 16:00:00 7843 PST | Mon Nov 27 16:00:00 7843 PST\n\nUPDATE uuid_events\nSET id = 'a8961135-cd89-4c4b-aa05-79df642407dd'\nWHERE id = 'a8961135-cd89-7000-aa05-79df642407dd';\nSELECT show_chunks('uuid_events', newer_than => '2025-01-02');\n              show_chunks               \n----------------------------------------\n _timescaledb_internal._hyper_1_8_chunk\n _timescaledb_internal._hyper_1_9_chunk\n\nSELECT drop_chunks('uuid_events', newer_than => '2025-01-02');\n              drop_chunks               \n----------------------------------------\n _timescaledb_internal._hyper_1_8_chunk\n _timescaledb_internal._hyper_1_9_chunk\n\nSELECT * FROM chunk_ranges;\n    chunk_name    |         range_start          |          range_end           \n------------------+------------------------------+------------------------------\n _hyper_1_7_chunk | Wed Jan 01 16:00:00 2025 PST | Thu Jan 02 16:00:00 2025 PST\n\nINSERT INTO uuid_events SELECT to_uuidv7(now()), 6, 6.0;\nSELECT show_chunks('uuid_events', newer_than => INTERVAL '2 months');\n               show_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_1_10_chunk\n\nSELECT drop_chunks('uuid_events', newer_than => INTERVAL '2 months');\n               drop_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_1_10_chunk\n\nSELECT show_chunks('uuid_events', newer_than => INTERVAL '2 months');\n show_chunks \n-------------\n\nDROP TABLE uuid_events;\nBEGIN;\n-- Test UUID partition when using CREATE TABLE ... WITH\nCREATE TABLE IF NOT EXISTS events (\n     event_id UUID NOT NULL,\n     entity_id VARCHAR(100) NOT NULL,\n     ts TIMESTAMPTZ NOT NULL,\n     event_type VARCHAR(100) NOT NULL,\n     metadata JSONB,\n     PRIMARY KEY (event_id)\n)\nWITH (\n     tsdb.hypertable,\n     tsdb.partition_column='event_id',\n     tsdb.chunk_interval='2 hours'\n);\n-- Verify that the chunk time interval is two hours\nSELECT ((interval_length/1000000)/60)/60 AS hours\nFROM _timescaledb_catalog.dimension d JOIN _timescaledb_catalog.hypertable h ON (h.id = d.hypertable_id)\nWHERE h.table_name='events';\n hours \n-------\n     2\n\nROLLBACK;\n-- Test a different interval\nBEGIN;\nCREATE TABLE IF NOT EXISTS events (\n     event_id UUID NOT NULL,\n     entity_id VARCHAR(100) NOT NULL,\n     ts TIMESTAMPTZ NOT NULL,\n     event_type VARCHAR(100) NOT NULL,\n     metadata JSONB,\n     PRIMARY KEY (event_id)\n)\nWITH (\n     tsdb.hypertable,\n     tsdb.partition_column='event_id',\n     tsdb.chunk_interval='2 months'\n);\n-- Verify that the chunk time interval is two hours\nSELECT (((interval_length/1000000)/60)/60)/24 AS days\nFROM _timescaledb_catalog.dimension d JOIN _timescaledb_catalog.hypertable h ON (h.id = d.hypertable_id)\nWHERE h.table_name='events';\n days \n------\n   60\n\nROLLBACK;\n-- Verify same behavior without CREATE TABLE WITH:\nBEGIN;\nCREATE TABLE IF NOT EXISTS events (\n     event_id UUID NOT NULL,\n     entity_id VARCHAR(100) NOT NULL,\n     ts TIMESTAMPTZ NOT NULL,\n     event_type VARCHAR(100) NOT NULL,\n     metadata JSONB,\n     PRIMARY KEY (event_id)\n);\nSELECT create_hypertable('events', 'event_id', chunk_time_interval => interval '2 hours');\nWARNING:  column type \"character varying\" used for \"entity_id\" does not follow best practices\nWARNING:  column type \"character varying\" used for \"event_type\" does not follow best practices\n  create_hypertable  \n---------------------\n (4,public,events,t)\n\n-- Verify that the chunk time interval is two hours\nSELECT ((interval_length/1000000)/60)/60 AS hours\nFROM _timescaledb_catalog.dimension d JOIN _timescaledb_catalog.hypertable h ON (h.id = d.hypertable_id)\nWHERE h.table_name='events';\n hours \n-------\n     2\n\nROLLBACK;\nBEGIN;\nCREATE TABLE IF NOT EXISTS events (\n     event_id UUID NOT NULL,\n     entity_id VARCHAR(100) NOT NULL,\n     ts TIMESTAMPTZ NOT NULL,\n     event_type VARCHAR(100) NOT NULL,\n     metadata JSONB,\n     PRIMARY KEY (event_id)\n);\nSELECT create_hypertable('events', 'event_id', chunk_time_interval => interval '2 months');\nWARNING:  column type \"character varying\" used for \"entity_id\" does not follow best practices\nWARNING:  column type \"character varying\" used for \"event_type\" does not follow best practices\n  create_hypertable  \n---------------------\n (5,public,events,t)\n\n-- Verify that the chunk time interval is two hours\nSELECT (((interval_length/1000000)/60)/60)/24 AS days\nFROM _timescaledb_catalog.dimension d JOIN _timescaledb_catalog.hypertable h ON (h.id = d.hypertable_id)\nWHERE h.table_name='events';\n days \n------\n   60\n\nROLLBACK;\n"
  },
  {
    "path": "test/expected/vacuum.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\nCREATE TABLE vacuum_test(time timestamp, temp float);\n-- create hypertable with three chunks\nSELECT create_hypertable('vacuum_test', 'time', chunk_time_interval => 2628000000000, create_default_indexes => false);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n    create_hypertable     \n--------------------------\n (1,public,vacuum_test,t)\n\nINSERT INTO vacuum_test VALUES ('2017-01-20T16:00:01', 17.5),\n                               ('2017-01-21T16:00:01', 19.1),\n                               ('2017-04-20T16:00:01', 89.5),\n                               ('2017-04-21T16:00:01', 17.1),\n                               ('2017-06-20T16:00:01', 18.5),\n                               ('2017-06-21T16:00:01', 11.0);\n-- no stats\nSELECT tablename, attname, histogram_bounds, n_distinct FROM pg_stats\nWHERE schemaname = '_timescaledb_internal' AND tablename LIKE '_hyper_%_chunk'\nORDER BY tablename, attname, array_to_string(histogram_bounds, ',');\n tablename | attname | histogram_bounds | n_distinct \n-----------+---------+------------------+------------\n\nSELECT tablename, attname, histogram_bounds, n_distinct FROM pg_stats\nWHERE schemaname = 'public' AND tablename LIKE 'vacuum_test'\nORDER BY tablename, attname, array_to_string(histogram_bounds, ',');\n tablename | attname | histogram_bounds | n_distinct \n-----------+---------+------------------+------------\n\nVACUUM ANALYZE vacuum_test;\n-- stats should exist for all three chunks\nSELECT tablename, attname, histogram_bounds, n_distinct FROM pg_stats\nWHERE schemaname = '_timescaledb_internal' AND tablename LIKE '_hyper_%_chunk'\nORDER BY tablename, attname, array_to_string(histogram_bounds, ',');\n    tablename     | attname |                    histogram_bounds                     | n_distinct \n------------------+---------+---------------------------------------------------------+------------\n _hyper_1_1_chunk | temp    | {17.5,19.1}                                             |         -1\n _hyper_1_1_chunk | time    | {\"Fri Jan 20 16:00:01 2017\",\"Sat Jan 21 16:00:01 2017\"} |         -1\n _hyper_1_2_chunk | temp    | {17.1,89.5}                                             |         -1\n _hyper_1_2_chunk | time    | {\"Thu Apr 20 16:00:01 2017\",\"Fri Apr 21 16:00:01 2017\"} |         -1\n _hyper_1_3_chunk | temp    | {11,18.5}                                               |         -1\n _hyper_1_3_chunk | time    | {\"Tue Jun 20 16:00:01 2017\",\"Wed Jun 21 16:00:01 2017\"} |         -1\n\n-- stats should exist on parent hypertable\nSELECT tablename, attname, histogram_bounds, n_distinct FROM pg_stats\nWHERE schemaname = 'public' AND tablename LIKE 'vacuum_test'\nORDER BY tablename, attname, array_to_string(histogram_bounds, ',');\n  tablename  | attname |                                                                          histogram_bounds                                                                           | n_distinct \n-------------+---------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------\n vacuum_test | temp    | {11,17.1,17.5,18.5,19.1,89.5}                                                                                                                                       |         -1\n vacuum_test | time    | {\"Fri Jan 20 16:00:01 2017\",\"Sat Jan 21 16:00:01 2017\",\"Thu Apr 20 16:00:01 2017\",\"Fri Apr 21 16:00:01 2017\",\"Tue Jun 20 16:00:01 2017\",\"Wed Jun 21 16:00:01 2017\"} |         -1\n\nDROP TABLE vacuum_test;\n--test plain analyze (no_vacuum)\nCREATE TABLE analyze_test(time timestamp, temp float);\nSELECT create_hypertable('analyze_test', 'time', chunk_time_interval => 2628000000000, create_default_indexes => false);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n     create_hypertable     \n---------------------------\n (2,public,analyze_test,t)\n\nINSERT INTO analyze_test VALUES ('2017-01-20T16:00:01', 17.5),\n                               ('2017-01-21T16:00:01', 19.1),\n                               ('2017-04-20T16:00:01', 89.5),\n                               ('2017-04-21T16:00:01', 17.1),\n                               ('2017-06-20T16:00:01', 18.5),\n                               ('2017-06-21T16:00:01', 11.0);\n-- no stats\nSELECT tablename, attname, histogram_bounds, n_distinct FROM pg_stats\nWHERE schemaname = '_timescaledb_internal' AND tablename LIKE '_hyper_%_chunk'\nORDER BY tablename, attname, array_to_string(histogram_bounds, ',');\n tablename | attname | histogram_bounds | n_distinct \n-----------+---------+------------------+------------\n\nSELECT tablename, attname, histogram_bounds, n_distinct FROM pg_stats\nWHERE schemaname = 'public' AND tablename LIKE 'analyze_test'\nORDER BY tablename, attname, array_to_string(histogram_bounds, ',');\n tablename | attname | histogram_bounds | n_distinct \n-----------+---------+------------------+------------\n\nANALYZE analyze_test;\n-- stats should exist for all three chunks\nSELECT tablename, attname, histogram_bounds, n_distinct FROM pg_stats\nWHERE schemaname = '_timescaledb_internal' AND tablename LIKE '_hyper_%_chunk'\nORDER BY tablename, attname, array_to_string(histogram_bounds, ',');\n    tablename     | attname |                    histogram_bounds                     | n_distinct \n------------------+---------+---------------------------------------------------------+------------\n _hyper_2_4_chunk | temp    | {17.5,19.1}                                             |         -1\n _hyper_2_4_chunk | time    | {\"Fri Jan 20 16:00:01 2017\",\"Sat Jan 21 16:00:01 2017\"} |         -1\n _hyper_2_5_chunk | temp    | {17.1,89.5}                                             |         -1\n _hyper_2_5_chunk | time    | {\"Thu Apr 20 16:00:01 2017\",\"Fri Apr 21 16:00:01 2017\"} |         -1\n _hyper_2_6_chunk | temp    | {11,18.5}                                               |         -1\n _hyper_2_6_chunk | time    | {\"Tue Jun 20 16:00:01 2017\",\"Wed Jun 21 16:00:01 2017\"} |         -1\n\n-- stats should exist on parent hypertable\nSELECT tablename, attname, histogram_bounds, n_distinct FROM pg_stats\nWHERE schemaname = 'public' AND tablename LIKE 'analyze_test'\nORDER BY tablename, attname, array_to_string(histogram_bounds, ',');\n  tablename   | attname |                                                                          histogram_bounds                                                                           | n_distinct \n--------------+---------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------\n analyze_test | temp    | {11,17.1,17.5,18.5,19.1,89.5}                                                                                                                                       |         -1\n analyze_test | time    | {\"Fri Jan 20 16:00:01 2017\",\"Sat Jan 21 16:00:01 2017\",\"Thu Apr 20 16:00:01 2017\",\"Fri Apr 21 16:00:01 2017\",\"Tue Jun 20 16:00:01 2017\",\"Wed Jun 21 16:00:01 2017\"} |         -1\n\nDROP TABLE analyze_test;\n-- Run vacuum on a normal (non-hypertable) table\nCREATE TABLE vacuum_norm(time timestamp, temp float);\nINSERT INTO vacuum_norm VALUES ('2017-01-20T09:00:01', 17.5),\n                               ('2017-01-21T09:00:01', 19.1),\n                               ('2017-04-20T09:00:01', 89.5),\n                               ('2017-04-21T09:00:01', 17.1),\n                               ('2017-06-20T09:00:01', 18.5),\n                               ('2017-06-21T09:00:01', 11.0);\nVACUUM ANALYZE vacuum_norm;\nDROP TABLE vacuum_norm;\n--Similar to normal vacuum tests, but PG11 introduced ability to vacuum multiple tables at once, we make sure that works for hypertables as well.\nCREATE TABLE vacuum_test(time timestamp, temp float);\n-- create hypertable with three chunks\nSELECT create_hypertable('vacuum_test', 'time', chunk_time_interval => 2628000000000, create_default_indexes => false);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n    create_hypertable     \n--------------------------\n (3,public,vacuum_test,t)\n\nINSERT INTO vacuum_test VALUES ('2017-01-20T16:00:01', 17.5),\n                               ('2017-01-21T16:00:01', 19.1),\n                               ('2017-04-20T16:00:01', 89.5),\n                               ('2017-04-21T16:00:01', 17.1),\n                               ('2017-06-20T16:00:01', 18.5),\n                               ('2017-06-21T16:00:01', 11.0);\nCREATE TABLE analyze_test(time timestamp, temp float);\nSELECT create_hypertable('analyze_test', 'time', chunk_time_interval => 2628000000000, create_default_indexes => false);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n     create_hypertable     \n---------------------------\n (4,public,analyze_test,t)\n\nINSERT INTO analyze_test VALUES ('2017-01-20T16:00:01', 17.5),\n                               ('2017-01-21T16:00:01', 19.1),\n                               ('2017-04-20T16:00:01', 89.5),\n                               ('2017-04-21T16:00:01', 17.1),\n                               ('2017-06-20T16:00:01', 18.5),\n                               ('2017-06-21T16:00:01', 11.0);\nCREATE TABLE vacuum_norm(time timestamp, temp float);\nINSERT INTO vacuum_norm VALUES ('2017-01-20T09:00:01', 17.5),\n                               ('2017-01-21T09:00:01', 19.1),\n                               ('2017-04-20T09:00:01', 89.5),\n                               ('2017-04-21T09:00:01', 17.1),\n                               ('2017-06-20T09:00:01', 18.5),\n                               ('2017-06-21T09:00:01', 11.0);\n-- no stats\nSELECT tablename, attname, histogram_bounds, n_distinct FROM pg_stats\nWHERE schemaname = '_timescaledb_internal' AND tablename LIKE '_hyper_%_chunk'\nORDER BY tablename, attname, array_to_string(histogram_bounds, ',');\n tablename | attname | histogram_bounds | n_distinct \n-----------+---------+------------------+------------\n\nSELECT tablename, attname, histogram_bounds, n_distinct FROM pg_stats\nWHERE schemaname = 'public'\nORDER BY tablename, attname, array_to_string(histogram_bounds, ',');\n tablename | attname | histogram_bounds | n_distinct \n-----------+---------+------------------+------------\n\nVACUUM ANALYZE vacuum_norm, vacuum_test, analyze_test;\n-- stats should exist for all 6 chunks\nSELECT tablename, attname, histogram_bounds, n_distinct FROM pg_stats\nWHERE schemaname = '_timescaledb_internal' AND tablename LIKE '_hyper_%_chunk'\nORDER BY tablename, attname, array_to_string(histogram_bounds, ',');\n     tablename     | attname |                    histogram_bounds                     | n_distinct \n-------------------+---------+---------------------------------------------------------+------------\n _hyper_3_7_chunk  | temp    | {17.5,19.1}                                             |         -1\n _hyper_3_7_chunk  | time    | {\"Fri Jan 20 16:00:01 2017\",\"Sat Jan 21 16:00:01 2017\"} |         -1\n _hyper_3_8_chunk  | temp    | {17.1,89.5}                                             |         -1\n _hyper_3_8_chunk  | time    | {\"Thu Apr 20 16:00:01 2017\",\"Fri Apr 21 16:00:01 2017\"} |         -1\n _hyper_3_9_chunk  | temp    | {11,18.5}                                               |         -1\n _hyper_3_9_chunk  | time    | {\"Tue Jun 20 16:00:01 2017\",\"Wed Jun 21 16:00:01 2017\"} |         -1\n _hyper_4_10_chunk | temp    | {17.5,19.1}                                             |         -1\n _hyper_4_10_chunk | time    | {\"Fri Jan 20 16:00:01 2017\",\"Sat Jan 21 16:00:01 2017\"} |         -1\n _hyper_4_11_chunk | temp    | {17.1,89.5}                                             |         -1\n _hyper_4_11_chunk | time    | {\"Thu Apr 20 16:00:01 2017\",\"Fri Apr 21 16:00:01 2017\"} |         -1\n _hyper_4_12_chunk | temp    | {11,18.5}                                               |         -1\n _hyper_4_12_chunk | time    | {\"Tue Jun 20 16:00:01 2017\",\"Wed Jun 21 16:00:01 2017\"} |         -1\n\n-- stats should exist on parent hypertable and normal table\nSELECT tablename, attname, histogram_bounds, n_distinct FROM pg_stats\nWHERE schemaname = 'public'\nORDER BY tablename, attname, array_to_string(histogram_bounds, ',');\n  tablename   | attname |                                                                          histogram_bounds                                                                           | n_distinct \n--------------+---------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------\n analyze_test | temp    | {11,17.1,17.5,18.5,19.1,89.5}                                                                                                                                       |         -1\n analyze_test | time    | {\"Fri Jan 20 16:00:01 2017\",\"Sat Jan 21 16:00:01 2017\",\"Thu Apr 20 16:00:01 2017\",\"Fri Apr 21 16:00:01 2017\",\"Tue Jun 20 16:00:01 2017\",\"Wed Jun 21 16:00:01 2017\"} |         -1\n vacuum_norm  | temp    | {11,17.1,17.5,18.5,19.1,89.5}                                                                                                                                       |         -1\n vacuum_norm  | time    | {\"Fri Jan 20 09:00:01 2017\",\"Sat Jan 21 09:00:01 2017\",\"Thu Apr 20 09:00:01 2017\",\"Fri Apr 21 09:00:01 2017\",\"Tue Jun 20 09:00:01 2017\",\"Wed Jun 21 09:00:01 2017\"} |         -1\n vacuum_test  | temp    | {11,17.1,17.5,18.5,19.1,89.5}                                                                                                                                       |         -1\n vacuum_test  | time    | {\"Fri Jan 20 16:00:01 2017\",\"Sat Jan 21 16:00:01 2017\",\"Thu Apr 20 16:00:01 2017\",\"Fri Apr 21 16:00:01 2017\",\"Tue Jun 20 16:00:01 2017\",\"Wed Jun 21 16:00:01 2017\"} |         -1\n\n"
  },
  {
    "path": "test/expected/vacuum_parallel.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- PG13 introduced parallel VACUUM functionality. It gets invoked when a table\n-- has two or more indexes on it. Read up more at\n-- https://www.postgresql.org/docs/13/sql-vacuum.html#PARALLEL\nCREATE TABLE vacuum_test(time timestamp NOT NULL, temp1 float, temp2 int);\n-- create hypertable\n-- we create chunks in public schema cause otherwise we would need\n-- elevated privileges to create indexes directly\nSELECT create_hypertable('vacuum_test', 'time', create_default_indexes => false, associated_schema_name => 'public');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n    create_hypertable     \n--------------------------\n (1,public,vacuum_test,t)\n\n-- parallel vacuum needs the index size to be larger than min_parallel_index_scan_size to kick in\nSET min_parallel_index_scan_size TO 0;\nINSERT INTO vacuum_test SELECT TIMESTAMP 'epoch' + (i * INTERVAL '4h'),\n                i, i+1 FROM generate_series(1, 100) as T(i);\n-- create indexes on the temp columns\n-- we create indexes manually because otherwise vacuum verbose output\n-- would be different between 13.2 and 13.3+\n-- 13.2 would try to vacuum the parent table index too while 13.3+ wouldn't\nCREATE INDEX ON _hyper_1_1_chunk(time);\nCREATE INDEX ON _hyper_1_1_chunk(temp1);\nCREATE INDEX ON _hyper_1_1_chunk(temp2);\nCREATE INDEX ON _hyper_1_2_chunk(time);\nCREATE INDEX ON _hyper_1_2_chunk(temp1);\nCREATE INDEX ON _hyper_1_2_chunk(temp2);\nCREATE INDEX ON _hyper_1_3_chunk(time);\nCREATE INDEX ON _hyper_1_3_chunk(temp1);\nCREATE INDEX ON _hyper_1_3_chunk(temp2);\n-- INSERT only will not trigger vacuum on indexes for PG13.3+\nUPDATE vacuum_test SET time = time + '1s'::interval, temp1 = random(), temp2 = random();\n-- we should see two parallel workers for each chunk\nVACUUM (PARALLEL 3) vacuum_test;\nDROP TABLE vacuum_test;\n"
  },
  {
    "path": "test/expected/version.out",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n-- Test that get_os_info returns 3 x text\nselect pg_typeof(sysname) AS sysname_type,pg_typeof(version) AS version_type,pg_typeof(release) AS release_type from _timescaledb_functions.get_os_info();\n sysname_type | version_type | release_type \n--------------+--------------+--------------\n text         | text         | text\n\n"
  },
  {
    "path": "test/isolation/CMakeLists.txt",
    "content": "add_subdirectory(specs)\n"
  },
  {
    "path": "test/isolation/expected/concurrent_add_dimension.out",
    "content": "unused step name: s1_wp_enable\nunused step name: s1_wp_release\nParsed test spec with 3 sessions\n\nstarting permutation: s3_wp_enable s1_add_dimension s2_add_dimension s3_wp_release s3_query\nstep s3_wp_enable: SELECT debug_waitpoint_enable('add_dimension_ht_lock');\ndebug_waitpoint_enable\n----------------------\n                      \n\nstep s1_add_dimension: SELECT column_name FROM add_dimension('dim_test', 'device', 2); <waiting ...>\nstep s2_add_dimension: SELECT column_name FROM add_dimension('dim_test', 'device', 1); <waiting ...>\nstep s3_wp_release: SELECT debug_waitpoint_release('add_dimension_ht_lock');\ndebug_waitpoint_release\n-----------------------\n                       \n\nstep s1_add_dimension: <... completed>\ncolumn_name\n-----------\ndevice     \n\nstep s2_add_dimension: <... completed>\nERROR:  column \"device\" is already a dimension\nstep s3_query: \n\tSELECT count(*)\n\tFROM _timescaledb_catalog.chunk c\n\tINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\n\tINNER JOIN _timescaledb_catalog.dimension td ON (h.id = td.hypertable_id)\n\tINNER JOIN _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = td.id)\n\tINNER JOIN _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id AND cc.chunk_id = c.id)\n\tWHERE h.table_name = 'dim_test';\n\ncount\n-----\n    2\n\n\nstarting permutation: s3_chunk_wp_enable s1_create_chunk s2_add_dimension2 s3_chunk_wp_release s3_query\nstep s3_chunk_wp_enable: SELECT debug_waitpoint_enable('chunk_create_for_point');\ndebug_waitpoint_enable\n----------------------\n                      \n\nstep s1_create_chunk: INSERT INTO dim_test VALUES ('2004-10-20 00:00:00+00', 1, 2); <waiting ...>\nstep s2_add_dimension2: SELECT column_name FROM add_dimension('dim_test', 'device2', 1); <waiting ...>\nstep s3_chunk_wp_release: SELECT debug_waitpoint_release('chunk_create_for_point');\ndebug_waitpoint_release\n-----------------------\n                       \n\nstep s1_create_chunk: <... completed>\nstep s2_add_dimension2: <... completed>\ncolumn_name\n-----------\ndevice2    \n\nstep s3_query: \n\tSELECT count(*)\n\tFROM _timescaledb_catalog.chunk c\n\tINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\n\tINNER JOIN _timescaledb_catalog.dimension td ON (h.id = td.hypertable_id)\n\tINNER JOIN _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = td.id)\n\tINNER JOIN _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id AND cc.chunk_id = c.id)\n\tWHERE h.table_name = 'dim_test';\n\ncount\n-----\n    4\n\n"
  },
  {
    "path": "test/isolation/expected/concurrent_query_and_drop_chunks.out",
    "content": "Parsed test spec with 4 sessions\n\nstarting permutation: s2_query s1_wp_enable s2_query s1_drop_chunks s1_wp_release s2_show_num_chunks\nstep s2_query: SELECT * FROM measurements ORDER BY 1;\ntime                        |device|temp\n----------------------------+------+----\nFri Jan 03 10:30:00 2020 PST|     1|   1\nSun Jan 03 10:30:00 2021 PST|     2|   2\n\nstep s1_wp_enable: SELECT debug_waitpoint_enable('hypertable_expansion_before_lock_chunk');\ndebug_waitpoint_enable\n----------------------\n                      \n\nstep s2_query: SELECT * FROM measurements ORDER BY 1; <waiting ...>\nstep s1_drop_chunks: SELECT count(*) FROM drop_chunks('measurements', TIMESTAMPTZ '2020-03-01');\ncount\n-----\n    1\n\nstep s1_wp_release: SELECT debug_waitpoint_release('hypertable_expansion_before_lock_chunk');\ndebug_waitpoint_release\n-----------------------\n                       \n\nstep s2_query: <... completed>\ntime                        |device|temp\n----------------------------+------+----\nSun Jan 03 10:30:00 2021 PST|     2|   2\n\nstep s2_show_num_chunks: SELECT count(*) FROM show_chunks('measurements') ORDER BY 1;\ncount\n-----\n    1\n\n\nstarting permutation: s3_wp_enable s4_hypertable_size s3_drop_chunks s3_wp_release\nstep s3_wp_enable: SELECT debug_waitpoint_enable('relation_size_before_lock');\ndebug_waitpoint_enable\n----------------------\n                      \n\nstep s4_hypertable_size: SELECT count(*) FROM hypertable_size('measurements'); <waiting ...>\nstep s3_drop_chunks: SELECT count(*) FROM drop_chunks('measurements', TIMESTAMPTZ '2020-03-01');\ncount\n-----\n    1\n\nstep s3_wp_release: SELECT debug_waitpoint_release('relation_size_before_lock');\ndebug_waitpoint_release\n-----------------------\n                       \n\nstep s4_hypertable_size: <... completed>\ncount\n-----\n    1\n\n"
  },
  {
    "path": "test/isolation/expected/deadlock_dropchunks_select.out",
    "content": "Parsed test spec with 2 sessions\n\nstarting permutation: s1a s1b s2a s2b\nstep s1a: SELECT count (*) FROM drop_chunks('dt',  '2018-12-25 00:00'::timestamptz);\ncount\n-----\n   24\n\nstep s1b: COMMIT;\nstep s2a: SELECT typ, loc, mtim FROM DT , SL , ST WHERE SL.lid = DT.lid AND ST.sid = DT.sid AND mtim >= '2018-12-01 03:00:00+00' AND mtim <= '2018-12-01 04:00:00+00' AND typ = 'T1' ;\ntyp|loc|mtim\n---+---+----\n\nstep s2b: COMMIT;\n\nstarting permutation: s1a s2a s1b s2b\nstep s1a: SELECT count (*) FROM drop_chunks('dt',  '2018-12-25 00:00'::timestamptz);\ncount\n-----\n   24\n\nstep s2a: SELECT typ, loc, mtim FROM DT , SL , ST WHERE SL.lid = DT.lid AND ST.sid = DT.sid AND mtim >= '2018-12-01 03:00:00+00' AND mtim <= '2018-12-01 04:00:00+00' AND typ = 'T1' ; <waiting ...>\nstep s1b: COMMIT;\nstep s2a: <... completed>\ntyp|loc|mtim\n---+---+----\n\nstep s2b: COMMIT;\n\nstarting permutation: s1a s2a s2b s1b\nstep s1a: SELECT count (*) FROM drop_chunks('dt',  '2018-12-25 00:00'::timestamptz);\ncount\n-----\n   24\n\nstep s2a: SELECT typ, loc, mtim FROM DT , SL , ST WHERE SL.lid = DT.lid AND ST.sid = DT.sid AND mtim >= '2018-12-01 03:00:00+00' AND mtim <= '2018-12-01 04:00:00+00' AND typ = 'T1' ; <waiting ...>\nstep s2a: <... completed>\nERROR:  canceling statement due to lock timeout\nstep s2b: COMMIT;\nstep s1b: COMMIT;\n\nstarting permutation: s2a s1a s1b s2b\nstep s2a: SELECT typ, loc, mtim FROM DT , SL , ST WHERE SL.lid = DT.lid AND ST.sid = DT.sid AND mtim >= '2018-12-01 03:00:00+00' AND mtim <= '2018-12-01 04:00:00+00' AND typ = 'T1' ;\ntyp|loc|mtim\n---+---+----\n\nstep s1a: SELECT count (*) FROM drop_chunks('dt',  '2018-12-25 00:00'::timestamptz); <waiting ...>\nstep s1a: <... completed>\nERROR:  canceling statement due to lock timeout\nstep s1b: COMMIT;\nstep s2b: COMMIT;\n\nstarting permutation: s2a s1a s2b s1b\nstep s2a: SELECT typ, loc, mtim FROM DT , SL , ST WHERE SL.lid = DT.lid AND ST.sid = DT.sid AND mtim >= '2018-12-01 03:00:00+00' AND mtim <= '2018-12-01 04:00:00+00' AND typ = 'T1' ;\ntyp|loc|mtim\n---+---+----\n\nstep s1a: SELECT count (*) FROM drop_chunks('dt',  '2018-12-25 00:00'::timestamptz); <waiting ...>\nstep s2b: COMMIT;\nstep s1a: <... completed>\ncount\n-----\n   24\n\nstep s1b: COMMIT;\n\nstarting permutation: s2a s2b s1a s1b\nstep s2a: SELECT typ, loc, mtim FROM DT , SL , ST WHERE SL.lid = DT.lid AND ST.sid = DT.sid AND mtim >= '2018-12-01 03:00:00+00' AND mtim <= '2018-12-01 04:00:00+00' AND typ = 'T1' ;\ntyp|loc|mtim\n---+---+----\n\nstep s2b: COMMIT;\nstep s1a: SELECT count (*) FROM drop_chunks('dt',  '2018-12-25 00:00'::timestamptz);\ncount\n-----\n   24\n\nstep s1b: COMMIT;\n"
  },
  {
    "path": "test/isolation/expected/dropchunks_race.out",
    "content": "Parsed test spec with 5 sessions\n\nstarting permutation: s3_chunks_found_wait s1_drop_chunks s2_drop_chunks s3_chunks_found_release s3_show_missing_slices s3_show_num_chunks s3_show_data\nstep s3_chunks_found_wait: SELECT debug_waitpoint_enable('drop_chunks_chunks_found');\ndebug_waitpoint_enable\n----------------------\n                      \n\nstep s1_drop_chunks: SELECT count(*) FROM drop_chunks('dropchunks_race_t1', TIMESTAMPTZ '2020-03-01'); <waiting ...>\nstep s2_drop_chunks: SELECT count(*) FROM drop_chunks('dropchunks_race_t1', TIMESTAMPTZ '2020-03-01'); <waiting ...>\nstep s3_chunks_found_release: SELECT debug_waitpoint_release('drop_chunks_chunks_found');\ndebug_waitpoint_release\n-----------------------\n                       \n\nstep s1_drop_chunks: <... completed>\ncount\n-----\n    1\n\nstep s2_drop_chunks: <... completed>\ncount\n-----\n    0\n\nstep s3_show_missing_slices: SELECT count(*) FROM _timescaledb_catalog.chunk_constraint WHERE dimension_slice_id NOT IN (SELECT id FROM _timescaledb_catalog.dimension_slice);\ncount\n-----\n    0\n\nstep s3_show_num_chunks: SELECT count(*) FROM show_chunks('dropchunks_race_t1') ORDER BY 1;\ncount\n-----\n    0\n\nstep s3_show_data: SELECT * FROM dropchunks_race_t1 ORDER BY 1;\ntime|device|temp\n----+------+----\n\n\nstarting permutation: s4_chunks_dropped_wait s1_drop_chunks s5_insert_new_chunk s4_chunks_dropped_release s3_show_missing_slices s3_show_num_chunks s3_show_data\nstep s4_chunks_dropped_wait: SELECT debug_waitpoint_enable('drop_chunks_end');\ndebug_waitpoint_enable\n----------------------\n                      \n\nstep s1_drop_chunks: SELECT count(*) FROM drop_chunks('dropchunks_race_t1', TIMESTAMPTZ '2020-03-01'); <waiting ...>\nstep s5_insert_new_chunk: INSERT INTO dropchunks_race_t1 VALUES ('2020-03-01 10:30', 1, 2.2); <waiting ...>\nstep s4_chunks_dropped_release: SELECT debug_waitpoint_release('drop_chunks_end');\ndebug_waitpoint_release\n-----------------------\n                       \n\nstep s1_drop_chunks: <... completed>\ncount\n-----\n    1\n\nstep s5_insert_new_chunk: <... completed>\nstep s3_show_missing_slices: SELECT count(*) FROM _timescaledb_catalog.chunk_constraint WHERE dimension_slice_id NOT IN (SELECT id FROM _timescaledb_catalog.dimension_slice);\ncount\n-----\n    0\n\nstep s3_show_num_chunks: SELECT count(*) FROM show_chunks('dropchunks_race_t1') ORDER BY 1;\ncount\n-----\n    1\n\nstep s3_show_data: SELECT * FROM dropchunks_race_t1 ORDER BY 1;\ntime                        |device|temp\n----------------------------+------+----\nSun Mar 01 10:30:00 2020 PST|     1| 2.2\n\n\nstarting permutation: s4_chunks_dropped_wait s1_drop_chunks s5_insert_old_chunk s4_chunks_dropped_release s3_show_missing_slices s3_show_num_chunks s3_show_data\nstep s4_chunks_dropped_wait: SELECT debug_waitpoint_enable('drop_chunks_end');\ndebug_waitpoint_enable\n----------------------\n                      \n\nstep s1_drop_chunks: SELECT count(*) FROM drop_chunks('dropchunks_race_t1', TIMESTAMPTZ '2020-03-01'); <waiting ...>\nstep s5_insert_old_chunk: INSERT INTO dropchunks_race_t1 VALUES ('2020-01-02 10:31', 1, 1.1); <waiting ...>\nstep s4_chunks_dropped_release: SELECT debug_waitpoint_release('drop_chunks_end');\ndebug_waitpoint_release\n-----------------------\n                       \n\nstep s1_drop_chunks: <... completed>\ncount\n-----\n    1\n\nstep s5_insert_old_chunk: <... completed>\nstep s3_show_missing_slices: SELECT count(*) FROM _timescaledb_catalog.chunk_constraint WHERE dimension_slice_id NOT IN (SELECT id FROM _timescaledb_catalog.dimension_slice);\ncount\n-----\n    0\n\nstep s3_show_num_chunks: SELECT count(*) FROM show_chunks('dropchunks_race_t1') ORDER BY 1;\ncount\n-----\n    1\n\nstep s3_show_data: SELECT * FROM dropchunks_race_t1 ORDER BY 1;\ntime                        |device|temp\n----------------------------+------+----\nThu Jan 02 10:31:00 2020 PST|     1| 1.1\n\n"
  },
  {
    "path": "test/isolation/expected/insert_dropchunks_race.out",
    "content": "Parsed test spec with 2 sessions\n\nstarting permutation: s1a s2a s1b s2b s1c\nstep s1a: INSERT INTO insert_dropchunks_race_t1 VALUES ('2020-01-03 10:30', 3, 33.4);\nstep s2a: SELECT COUNT(*) FROM drop_chunks('insert_dropchunks_race_t1', TIMESTAMPTZ '2020-03-01'); <waiting ...>\nstep s1b: COMMIT;\nstep s2a: <... completed>\ncount\n-----\n    2\n\nstep s2b: COMMIT;\nstep s1c: SELECT COUNT(*) FROM _timescaledb_catalog.chunk_constraint LEFT JOIN _timescaledb_catalog.dimension_slice sl ON dimension_slice_id = sl.id WHERE sl.id IS NULL;\ncount\n-----\n    0\n\n"
  },
  {
    "path": "test/isolation/expected/isolation_nop.out",
    "content": "Parsed test spec with 1 sessions\n\nstarting permutation: s1a\ntable_name     \n---------------\nts_cluster_test\n\nstep s1a: SELECT pg_sleep(0);\npg_sleep\n--------\n        \n\n"
  },
  {
    "path": "test/isolation/expected/multi_transaction_indexing.out",
    "content": "Parsed test spec with 8 sessions\n\nstarting permutation: CI I1 Ic Bc P Sc\nstep CI: CREATE INDEX test_index ON ts_index_test(location) WITH (timescaledb.transaction_per_chunk, timescaledb.barrier_table='barrier'); <waiting ...>\nstep I1: INSERT INTO ts_index_test VALUES (31, 6.4, 1);\nstep Ic: COMMIT;\nstep Bc: ROLLBACK;\nstep CI: <... completed>\nstep P: SELECT * FROM hypertable_index_size('test_index');\nhypertable_index_size\n---------------------\n                73728\n\nstep Sc: COMMIT;\n\nstarting permutation: I1 CI Bc Ic P Sc\nstep I1: INSERT INTO ts_index_test VALUES (31, 6.4, 1);\nstep CI: CREATE INDEX test_index ON ts_index_test(location) WITH (timescaledb.transaction_per_chunk, timescaledb.barrier_table='barrier'); <waiting ...>\nstep Bc: ROLLBACK;\nstep Ic: COMMIT;\nstep CI: <... completed>\nstep P: SELECT * FROM hypertable_index_size('test_index');\nhypertable_index_size\n---------------------\n                73728\n\nstep Sc: COMMIT;\n\nstarting permutation: S1 CI Bc Sc P Ic\nstep S1: SELECT * FROM ts_index_test;\ntime|temp|location\n----+----+--------\n   1|23.4|       1\n  11|21.3|       2\n  21|19.5|       3\n\nstep CI: CREATE INDEX test_index ON ts_index_test(location) WITH (timescaledb.transaction_per_chunk, timescaledb.barrier_table='barrier'); <waiting ...>\nstep Bc: ROLLBACK;\nstep CI: <... completed>\nstep Sc: COMMIT;\nstep P: SELECT * FROM hypertable_index_size('test_index');\nhypertable_index_size\n---------------------\n                57344\n\nstep Ic: COMMIT;\n\nstarting permutation: F WPE CI DI Bc WPR P Ic Sc\nstep F: SET client_min_messages TO 'error';\nstep WPE: SELECT debug_waitpoint_enable('process_index_start_indexing_done');\ndebug_waitpoint_enable\n----------------------\n                      \n\nstep CI: CREATE INDEX test_index ON ts_index_test(location) WITH (timescaledb.transaction_per_chunk, timescaledb.barrier_table='barrier'); <waiting ...>\nstep DI: DROP INDEX test_index; <waiting ...>\nstep Bc: ROLLBACK;\nstep DI: <... completed>\nstep WPR: SELECT debug_waitpoint_release('process_index_start_indexing_done');\ndebug_waitpoint_release\n-----------------------\n                       \n\nstep CI: <... completed>\nstep P: SELECT * FROM hypertable_index_size('test_index');\nERROR:  relation \"test_index\" does not exist\nstep Ic: COMMIT;\nstep Sc: COMMIT;\n\nstarting permutation: CI RI Bc P Ic Sc\nstep CI: CREATE INDEX test_index ON ts_index_test(location) WITH (timescaledb.transaction_per_chunk, timescaledb.barrier_table='barrier'); <waiting ...>\nstep RI: ALTER TABLE test_index RENAME COLUMN location TO height; <waiting ...>\nstep Bc: ROLLBACK;\nstep CI: <... completed>\nstep RI: <... completed>\nstep P: SELECT * FROM hypertable_index_size('test_index');\nhypertable_index_size\n---------------------\n                57344\n\nstep Ic: COMMIT;\nstep Sc: COMMIT;\n"
  },
  {
    "path": "test/isolation/expected/read_committed_insert.out",
    "content": "Parsed test spec with 2 sessions\n\nstarting permutation: s1a s1c s2a s2b\ntable_name     \n---------------\nts_cluster_test\n\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090001', 23.4, 1);\nstep s1c: COMMIT;\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090002', 0.72, 1);\nstep s2b: COMMIT;\n\nstarting permutation: s1a s2a s1c s2b\ntable_name     \n---------------\nts_cluster_test\n\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090001', 23.4, 1);\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090002', 0.72, 1); <waiting ...>\nstep s1c: COMMIT;\nstep s2a: <... completed>\nstep s2b: COMMIT;\n\nstarting permutation: s1a s2a s2b s1c\ntable_name     \n---------------\nts_cluster_test\n\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090001', 23.4, 1);\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090002', 0.72, 1); <waiting ...>\nstep s2a: <... completed>\nERROR:  canceling statement due to lock timeout\nstep s2b: COMMIT;\nstep s1c: COMMIT;\n\nstarting permutation: s2a s1a s1c s2b\ntable_name     \n---------------\nts_cluster_test\n\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090002', 0.72, 1);\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090001', 23.4, 1); <waiting ...>\nstep s1a: <... completed>\nERROR:  canceling statement due to lock timeout\nstep s1c: COMMIT;\nstep s2b: COMMIT;\n\nstarting permutation: s2a s1a s2b s1c\ntable_name     \n---------------\nts_cluster_test\n\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090002', 0.72, 1);\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090001', 23.4, 1); <waiting ...>\nstep s2b: COMMIT;\nstep s1a: <... completed>\nstep s1c: COMMIT;\n\nstarting permutation: s2a s2b s1a s1c\ntable_name     \n---------------\nts_cluster_test\n\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090002', 0.72, 1);\nstep s2b: COMMIT;\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090001', 23.4, 1);\nstep s1c: COMMIT;\n"
  },
  {
    "path": "test/isolation/expected/read_uncommitted_insert.out",
    "content": "Parsed test spec with 2 sessions\n\nstarting permutation: s1a s1c s2a s2b\ntable_name     \n---------------\nts_cluster_test\n\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090001', 23.4, 1);\nstep s1c: COMMIT;\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090002', 0.72, 1);\nstep s2b: COMMIT;\n\nstarting permutation: s1a s2a s1c s2b\ntable_name     \n---------------\nts_cluster_test\n\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090001', 23.4, 1);\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090002', 0.72, 1); <waiting ...>\nstep s1c: COMMIT;\nstep s2a: <... completed>\nstep s2b: COMMIT;\n\nstarting permutation: s1a s2a s2b s1c\ntable_name     \n---------------\nts_cluster_test\n\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090001', 23.4, 1);\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090002', 0.72, 1); <waiting ...>\nstep s2a: <... completed>\nERROR:  canceling statement due to lock timeout\nstep s2b: COMMIT;\nstep s1c: COMMIT;\n\nstarting permutation: s2a s1a s1c s2b\ntable_name     \n---------------\nts_cluster_test\n\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090002', 0.72, 1);\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090001', 23.4, 1); <waiting ...>\nstep s1a: <... completed>\nERROR:  canceling statement due to lock timeout\nstep s1c: COMMIT;\nstep s2b: COMMIT;\n\nstarting permutation: s2a s1a s2b s1c\ntable_name     \n---------------\nts_cluster_test\n\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090002', 0.72, 1);\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090001', 23.4, 1); <waiting ...>\nstep s2b: COMMIT;\nstep s1a: <... completed>\nstep s1c: COMMIT;\n\nstarting permutation: s2a s2b s1a s1c\ntable_name     \n---------------\nts_cluster_test\n\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090002', 0.72, 1);\nstep s2b: COMMIT;\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090001', 23.4, 1);\nstep s1c: COMMIT;\n"
  },
  {
    "path": "test/isolation/expected/repeatable_read_insert.out",
    "content": "Parsed test spec with 2 sessions\n\nstarting permutation: s1a s1c s2a s2b\ntable_name     \n---------------\nts_cluster_test\n\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090001', 23.4, 1);\nstep s1c: COMMIT;\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090002', 0.72, 1);\nstep s2b: COMMIT;\n\nstarting permutation: s1a s2a s1c s2b\ntable_name     \n---------------\nts_cluster_test\n\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090001', 23.4, 1);\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090002', 0.72, 1); <waiting ...>\nstep s1c: COMMIT;\nstep s2a: <... completed>\nstep s2b: COMMIT;\n\nstarting permutation: s1a s2a s2b s1c\ntable_name     \n---------------\nts_cluster_test\n\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090001', 23.4, 1);\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090002', 0.72, 1); <waiting ...>\nstep s2a: <... completed>\nERROR:  canceling statement due to lock timeout\nstep s2b: COMMIT;\nstep s1c: COMMIT;\n\nstarting permutation: s2a s1a s1c s2b\ntable_name     \n---------------\nts_cluster_test\n\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090002', 0.72, 1);\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090001', 23.4, 1); <waiting ...>\nstep s1a: <... completed>\nERROR:  canceling statement due to lock timeout\nstep s1c: COMMIT;\nstep s2b: COMMIT;\n\nstarting permutation: s2a s1a s2b s1c\ntable_name     \n---------------\nts_cluster_test\n\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090002', 0.72, 1);\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090001', 23.4, 1); <waiting ...>\nstep s2b: COMMIT;\nstep s1a: <... completed>\nstep s1c: COMMIT;\n\nstarting permutation: s2a s2b s1a s1c\ntable_name     \n---------------\nts_cluster_test\n\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090002', 0.72, 1);\nstep s2b: COMMIT;\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T090001', 23.4, 1);\nstep s1c: COMMIT;\n"
  },
  {
    "path": "test/isolation/expected/serializable_insert.out",
    "content": "Parsed test spec with 2 sessions\n\nstarting permutation: s1a s1c s2a s2c\nschema_name|table_name     \n-----------+---------------\npublic     |ts_cluster_test\n\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:01', 23.4, 1);\nstep s1c: COMMIT;\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:02', 0.72, 1);\nstep s2c: COMMIT;\n\nstarting permutation: s1a s2a s1c s2c\nschema_name|table_name     \n-----------+---------------\npublic     |ts_cluster_test\n\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:01', 23.4, 1);\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:02', 0.72, 1); <waiting ...>\nstep s1c: COMMIT;\nstep s2a: <... completed>\nstep s2c: COMMIT;\n\nstarting permutation: s1a s2a s2c s1c\nschema_name|table_name     \n-----------+---------------\npublic     |ts_cluster_test\n\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:01', 23.4, 1);\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:02', 0.72, 1); <waiting ...>\nstep s2a: <... completed>\nERROR:  canceling statement due to lock timeout\nstep s2c: COMMIT;\nstep s1c: COMMIT;\n\nstarting permutation: s2a s1a s1c s2c\nschema_name|table_name     \n-----------+---------------\npublic     |ts_cluster_test\n\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:02', 0.72, 1);\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:01', 23.4, 1); <waiting ...>\nstep s1a: <... completed>\nERROR:  canceling statement due to lock timeout\nstep s1c: COMMIT;\nstep s2c: COMMIT;\n\nstarting permutation: s2a s1a s2c s1c\nschema_name|table_name     \n-----------+---------------\npublic     |ts_cluster_test\n\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:02', 0.72, 1);\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:01', 23.4, 1); <waiting ...>\nstep s2c: COMMIT;\nstep s1a: <... completed>\nstep s1c: COMMIT;\n\nstarting permutation: s2a s2c s1a s1c\nschema_name|table_name     \n-----------+---------------\npublic     |ts_cluster_test\n\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:02', 0.72, 1);\nstep s2c: COMMIT;\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:01', 23.4, 1);\nstep s1c: COMMIT;\n"
  },
  {
    "path": "test/isolation/expected/serializable_insert_rollback.out",
    "content": "Parsed test spec with 2 sessions\n\nstarting permutation: s1a s1c s2a s2c\nschema_name|table_name     \n-----------+---------------\npublic     |ts_cluster_test\n\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:01', 23.4, 1);\nstep s1c: ROLLBACK;\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:02', 0.72, 1);\nstep s2c: COMMIT;\n\nstarting permutation: s1a s2a s1c s2c\nschema_name|table_name     \n-----------+---------------\npublic     |ts_cluster_test\n\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:01', 23.4, 1);\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:02', 0.72, 1); <waiting ...>\nstep s1c: ROLLBACK;\nstep s2a: <... completed>\nstep s2c: COMMIT;\n\nstarting permutation: s1a s2a s2c s1c\nschema_name|table_name     \n-----------+---------------\npublic     |ts_cluster_test\n\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:01', 23.4, 1);\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:02', 0.72, 1); <waiting ...>\nstep s2a: <... completed>\nERROR:  canceling statement due to lock timeout\nstep s2c: COMMIT;\nstep s1c: ROLLBACK;\n\nstarting permutation: s2a s1a s1c s2c\nschema_name|table_name     \n-----------+---------------\npublic     |ts_cluster_test\n\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:02', 0.72, 1);\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:01', 23.4, 1); <waiting ...>\nstep s1a: <... completed>\nERROR:  canceling statement due to lock timeout\nstep s1c: ROLLBACK;\nstep s2c: COMMIT;\n\nstarting permutation: s2a s1a s2c s1c\nschema_name|table_name     \n-----------+---------------\npublic     |ts_cluster_test\n\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:02', 0.72, 1);\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:01', 23.4, 1); <waiting ...>\nstep s2c: COMMIT;\nstep s1a: <... completed>\nstep s1c: ROLLBACK;\n\nstarting permutation: s2a s2c s1a s1c\nschema_name|table_name     \n-----------+---------------\npublic     |ts_cluster_test\n\nstep s2a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:02', 0.72, 1);\nstep s2c: COMMIT;\nstep s1a: INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:01', 23.4, 1);\nstep s1c: ROLLBACK;\n"
  },
  {
    "path": "test/isolation/specs/CMakeLists.txt",
    "content": "set(TEST_FILES\n    deadlock_dropchunks_select.spec\n    insert_dropchunks_race.spec\n    isolation_nop.spec\n    read_committed_insert.spec\n    read_uncommitted_insert.spec\n    repeatable_read_insert.spec\n    serializable_insert_rollback.spec\n    serializable_insert.spec)\n\nfile(REMOVE ${ISOLATION_TEST_SCHEDULE})\n\nset(TEST_TEMPLATES)\n\nif(CMAKE_BUILD_TYPE MATCHES Debug)\n  list(APPEND TEST_FILES concurrent_add_dimension.spec\n       concurrent_query_and_drop_chunks.spec dropchunks_race.spec\n       multi_transaction_indexing.spec)\nendif(CMAKE_BUILD_TYPE MATCHES Debug)\n\nforeach(TEST_FILE ${TEST_FILES})\n  string(REGEX REPLACE \"(.+)\\.spec\" \"\\\\1\" TESTS_TO_RUN ${TEST_FILE})\n  file(APPEND ${ISOLATION_TEST_SCHEDULE} \"test: ${TESTS_TO_RUN}\\n\")\nendforeach(TEST_FILE)\n"
  },
  {
    "path": "test/isolation/specs/concurrent_add_dimension.spec",
    "content": "# This file and its contents are licensed under the Apache License 2.0.\n# Please see the included NOTICE for copyright information and\n# LICENSE-APACHE for a copy of the license.\n\nsetup {\n  DROP TABLE IF EXISTS dim_test;\n  CREATE TABLE dim_test(time TIMESTAMPTZ, device int, device2 int);\n  SELECT table_name FROM create_hypertable('dim_test', 'time', chunk_time_interval => INTERVAL '1 day');\n  INSERT INTO dim_test VALUES ('2004-10-10 00:00:00+00', 1, 1);\n}\n\nteardown {\n  DROP TABLE dim_test;\n}\n\nsession \"s1\"\nstep \"s1_wp_enable\"        { SELECT debug_waitpoint_enable('add_dimension_ht_lock'); }\nstep \"s1_wp_release\"       { SELECT debug_waitpoint_release('add_dimension_ht_lock'); }\nstep \"s1_add_dimension\"\t   { SELECT column_name FROM add_dimension('dim_test', 'device', 2); }\nstep \"s1_create_chunk\"     { INSERT INTO dim_test VALUES ('2004-10-20 00:00:00+00', 1, 2); }\n\nsession \"s2\"\nstep \"s2_add_dimension\"\t   { SELECT column_name FROM add_dimension('dim_test', 'device', 1); }\nstep \"s2_add_dimension2\"   { SELECT column_name FROM add_dimension('dim_test', 'device2', 1); }\n\nsession \"s3\"\nstep \"s3_wp_enable\"        { SELECT debug_waitpoint_enable('add_dimension_ht_lock'); }\nstep \"s3_wp_release\"       { SELECT debug_waitpoint_release('add_dimension_ht_lock'); }\nstep \"s3_chunk_wp_enable\"  { SELECT debug_waitpoint_enable('chunk_create_for_point'); }\nstep \"s3_chunk_wp_release\" { SELECT debug_waitpoint_release('chunk_create_for_point'); }\n\nstep \"s3_query\"            {\n\tSELECT count(*)\n\tFROM _timescaledb_catalog.chunk c\n\tINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\n\tINNER JOIN _timescaledb_catalog.dimension td ON (h.id = td.hypertable_id)\n\tINNER JOIN _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = td.id)\n\tINNER JOIN _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id AND cc.chunk_id = c.id)\n\tWHERE h.table_name = 'dim_test';\n}\n\n# Test concurrent add_dimension() call with existing data\n#\npermutation \"s3_wp_enable\" \"s1_add_dimension\" \"s2_add_dimension\" \"s3_wp_release\" \"s3_query\"\n\n# Test concurrent chunk creation during add_dimension() call\n#\npermutation \"s3_chunk_wp_enable\" \"s1_create_chunk\" \"s2_add_dimension2\" \"s3_chunk_wp_release\" \"s3_query\"\n"
  },
  {
    "path": "test/isolation/specs/concurrent_query_and_drop_chunks.spec",
    "content": "# This file and its contents are licensed under the Apache License 2.0.\n# Please see the included NOTICE for copyright information and\n# LICENSE-APACHE for a copy of the license.\n\nsetup {\n  DROP TABLE IF EXISTS measurements;\n  CREATE TABLE measurements (time timestamptz, device int, temp float);\n  SELECT create_hypertable('measurements', 'time', 'device', 2);\n  INSERT INTO measurements VALUES ('2020-01-03 10:30', 1, 1.0), ('2021-01-03 10:30', 2, 2.0);\n}\n\nteardown {\n  DROP TABLE measurements;\n}\n\n#\n# Test concurrent querying and drop chunks.\n#\n\nsession \"s1\"\nstep \"s1_wp_enable\" { SELECT debug_waitpoint_enable('hypertable_expansion_before_lock_chunk'); }\nstep \"s1_wp_release\" { SELECT debug_waitpoint_release('hypertable_expansion_before_lock_chunk'); }\nstep \"s1_drop_chunks\" { SELECT count(*) FROM drop_chunks('measurements', TIMESTAMPTZ '2020-03-01'); }\n\nsession \"s2\"\nstep \"s2_show_num_chunks\"  { SELECT count(*) FROM show_chunks('measurements') ORDER BY 1; }\nstep \"s2_query\"  { SELECT * FROM measurements ORDER BY 1; }\n\nsession \"s3\"\nstep \"s3_wp_enable\" { SELECT debug_waitpoint_enable('relation_size_before_lock'); }\nstep \"s3_wp_release\" { SELECT debug_waitpoint_release('relation_size_before_lock'); }\nstep \"s3_drop_chunks\" { SELECT count(*) FROM drop_chunks('measurements', TIMESTAMPTZ '2020-03-01'); }\n\nsession \"s4\"\nstep \"s4_hypertable_size\"  { SELECT count(*) FROM hypertable_size('measurements'); }\n\n# The wait point happens after chunks have been found for table\n# expansion, but before the chunks are locked. Because one chunk\n# will dropped before the lock is acqurired, the chunk should\n# also be ignored.\npermutation \"s2_query\" \"s1_wp_enable\" \"s2_query\" \"s1_drop_chunks\" \"s1_wp_release\" \"s2_show_num_chunks\"\n\n# The wait point happens before the relation_size get the lock\n# for the relation and one chunk will be dropped in another session\n# don't leading to race conditions\npermutation \"s3_wp_enable\" \"s4_hypertable_size\" \"s3_drop_chunks\" \"s3_wp_release\"\n"
  },
  {
    "path": "test/isolation/specs/deadlock_dropchunks_select.spec",
    "content": "# This file and its contents are licensed under the Apache License 2.0.\n# Please see the included NOTICE for copyright information and\n# LICENSE-APACHE for a copy of the license.\n\n##github issue 865 deadlock between select and drop chunks\n\nsetup\n{\n CREATE TABLE ST ( sid int PRIMARY KEY, typ text) ;\n CREATE TABLE SL ( lid int PRIMARY KEY, loc text) ;\n CREATE TABLE DT ( sid int REFERENCES ST(sid), lid int REFERENCES SL(lid), mtim timestamp with time zone ) ;\n SELECT create_hypertable('DT', 'mtim', chunk_time_interval => interval '1 day');\n INSERT INTO SL  VALUES (1, 'LA');\n INSERT INTO ST  VALUES (1, 'T1');\n INSERT INTO DT (sid,lid,mtim)\n SELECT 1,1, generate_series( '2018-12-01 00:00'::timestamp, '2018-12-31 12:00','1 minute') ;\n}\n\nteardown {\n    DROP TABLE DT;\n    DROP table ST;\n    DROP table SL;\n}\n\nsession \"s1\"\nsetup\t{\n    BEGIN;\n    SET TRANSACTION ISOLATION LEVEL READ COMMITTED;\n    SET LOCAL lock_timeout = '5000ms';\n    SET LOCAL deadlock_timeout = '10ms';\n}\nstep \"s1a\"\t{ SELECT count (*) FROM drop_chunks('dt',  '2018-12-25 00:00'::timestamptz); }\nstep \"s1b\"\t{ COMMIT; }\n\nsession \"s2\"\nsetup\t{\n    BEGIN;\n    SET TRANSACTION ISOLATION LEVEL READ COMMITTED;\n    SET LOCAL lock_timeout = '5000ms';\n    SET LOCAL deadlock_timeout = '10ms';\n}\nstep \"s2a\"\t{ SELECT typ, loc, mtim FROM DT , SL , ST WHERE SL.lid = DT.lid AND ST.sid = DT.sid AND mtim >= '2018-12-01 03:00:00+00' AND mtim <= '2018-12-01 04:00:00+00' AND typ = 'T1' ; }\nstep \"s2b\"\t{ COMMIT; }\n\n"
  },
  {
    "path": "test/isolation/specs/dropchunks_race.spec",
    "content": "# This file and its contents are licensed under the Apache License 2.0.\n# Please see the included NOTICE for copyright information and\n# LICENSE-APACHE for a copy of the license.\n\nsetup {\n  DROP TABLE IF EXISTS dropchunks_race_t1;\n  CREATE TABLE dropchunks_race_t1 (time timestamptz, device int, temp float);\n  SELECT create_hypertable('dropchunks_race_t1', 'time', 'device', 2);\n  INSERT INTO dropchunks_race_t1 VALUES ('2020-01-03 10:30', 1, 32.2);\n}\n\nteardown {\n  DROP TABLE dropchunks_race_t1;\n}\n\nsession \"s1\"\nstep \"s1_drop_chunks\"\t{ SELECT count(*) FROM drop_chunks('dropchunks_race_t1', TIMESTAMPTZ '2020-03-01'); }\n\nsession \"s2\"\nstep \"s2_drop_chunks\"\t{ SELECT count(*) FROM drop_chunks('dropchunks_race_t1', TIMESTAMPTZ '2020-03-01'); }\n\nsession \"s3\"\nstep \"s3_chunks_found_wait\"           { SELECT debug_waitpoint_enable('drop_chunks_chunks_found'); }\nstep \"s3_chunks_found_release\"      { SELECT debug_waitpoint_release('drop_chunks_chunks_found'); }\nstep \"s3_show_missing_slices\"\t{ SELECT count(*) FROM _timescaledb_catalog.chunk_constraint WHERE dimension_slice_id NOT IN (SELECT id FROM _timescaledb_catalog.dimension_slice); }\nstep \"s3_show_num_chunks\"  { SELECT count(*) FROM show_chunks('dropchunks_race_t1') ORDER BY 1; }\nstep \"s3_show_data\"  { SELECT * FROM dropchunks_race_t1 ORDER BY 1; }\n\nsession \"s4\"\nstep \"s4_chunks_dropped_wait\"         { SELECT debug_waitpoint_enable('drop_chunks_end'); }\nstep \"s4_chunks_dropped_release\"      { SELECT debug_waitpoint_release('drop_chunks_end'); }\n\nsession \"s5\"\nstep \"s5_insert_old_chunk\" { INSERT INTO dropchunks_race_t1 VALUES ('2020-01-02 10:31', 1, 1.1); }\nstep \"s5_insert_new_chunk\" { INSERT INTO dropchunks_race_t1 VALUES ('2020-03-01 10:30', 1, 2.2); }\n\n# Test race between two drop_chunks processes.\npermutation \"s3_chunks_found_wait\" \"s1_drop_chunks\" \"s2_drop_chunks\" \"s3_chunks_found_release\" \"s3_show_missing_slices\" \"s3_show_num_chunks\" \"s3_show_data\"\n\n# Test race between drop_chunks and an insert into a new chunk. The\n# new chunk will share a slice with the chunk that is about to be\n# dropped. The shared slice must persist after drop_chunks completes,\n# or otherwise the new chunk will lack one slice.\npermutation \"s4_chunks_dropped_wait\" \"s1_drop_chunks\" \"s5_insert_new_chunk\" \"s4_chunks_dropped_release\" \"s3_show_missing_slices\" \"s3_show_num_chunks\" \"s3_show_data\"\n\n# Test race between drop_chunks and an insert into the chunk being\n# concurrently dropped. The chunk and slices should be recreated.\npermutation \"s4_chunks_dropped_wait\" \"s1_drop_chunks\" \"s5_insert_old_chunk\" \"s4_chunks_dropped_release\" \"s3_show_missing_slices\" \"s3_show_num_chunks\" \"s3_show_data\"\n"
  },
  {
    "path": "test/isolation/specs/insert_dropchunks_race.spec",
    "content": "# This file and its contents are licensed under the Apache License 2.0.\n# Please see the included NOTICE for copyright information and\n# LICENSE-APACHE for a copy of the license.\n\n# Race condition between insert and drop_chunks\n#\n# If an insert need to create a new chunk, it will look for existing\n# dimension slices to see if any need to be added: if slices already\n# exist, they do not need to be re-constructed and constraints can be\n# added that reference these slices. If chunks are dropped, there is a\n# cleanup of unreferenced dimension slices which can possibly remove\n# unreferenced dimension slices if transactions creating new chunks do\n# not lock the dimension slices for read.\n#\n# This isolation test check that a concurrent insert and drop_chunks\n# do not accidentally create a broken state by adding chunk\n# constraints that reference non-existing dimension slices.\n\nsetup {\n  DROP TABLE IF EXISTS insert_dropchunks_race_t1;\n  CREATE TABLE insert_dropchunks_race_t1 (time timestamptz, device int, temp float);\n  SELECT create_hypertable('insert_dropchunks_race_t1', 'time', 'device', 2);\n  INSERT INTO insert_dropchunks_race_t1 VALUES ('2020-01-03 10:30', 1, 32.2);\n}\n\nteardown {\n  DROP TABLE insert_dropchunks_race_t1;\n}\n\nsession \"s1\"\nsetup\t\t{ BEGIN; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; }\nstep \"s1a\"\t{ INSERT INTO insert_dropchunks_race_t1 VALUES ('2020-01-03 10:30', 3, 33.4); }\nstep \"s1b\" \t{ COMMIT; }\nstep \"s1c\" \t{ SELECT COUNT(*) FROM _timescaledb_catalog.chunk_constraint LEFT JOIN _timescaledb_catalog.dimension_slice sl ON dimension_slice_id = sl.id WHERE sl.id IS NULL; }\n\nsession \"s2\"\nsetup\t        { BEGIN; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; }\nstep \"s2a\"\t{ SELECT COUNT(*) FROM drop_chunks('insert_dropchunks_race_t1', TIMESTAMPTZ '2020-03-01'); }\nstep \"s2b\"\t{ COMMIT; }\n\npermutation \"s1a\" \"s2a\" \"s1b\" \"s2b\" \"s1c\"\n"
  },
  {
    "path": "test/isolation/specs/isolation_nop.spec",
    "content": "# This file and its contents are licensed under the Apache License 2.0.\n# Please see the included NOTICE for copyright information and\n# LICENSE-APACHE for a copy of the license.\n\nsetup{\n    CREATE TABLE ts_cluster_test(time timestamptz, temp float, location int);\n    SELECT table_name from create_hypertable('ts_cluster_test', 'time', chunk_time_interval => interval '1 day');\n}\n\nteardown {\n    DROP TABLE ts_cluster_test;\n}\n\nsession \"s1\"\nstep \"s1a\"\t{ SELECT pg_sleep(0); }\n"
  },
  {
    "path": "test/isolation/specs/multi_transaction_indexing.spec",
    "content": "# This file and its contents are licensed under the Apache License 2.0.\n# Please see the included NOTICE for copyright information and\n# LICENSE-APACHE for a copy of the license.\n\nsetup {\n    CREATE TABLE ts_index_test(time int, temp float, location int);\n    SELECT create_hypertable('ts_index_test', 'time', chunk_time_interval => 10, create_default_indexes => false);\n    INSERT INTO ts_index_test VALUES (1, 23.4, 1),\n        (11, 21.3, 2),\n        (21, 19.5, 3);\n\n    CREATE TABLE barrier(i INTEGER);\n}\n\nteardown {\n    DROP TABLE ts_index_test;\n    DROP TABLE barrier;\n}\n\nsession \"Waitpoints\"\nstep \"WPE\"      { SELECT debug_waitpoint_enable('process_index_start_indexing_done'); }\nstep \"WPR\"      { SELECT debug_waitpoint_release('process_index_start_indexing_done'); }\n\nsession \"CREATE INDEX\"\nstep \"F\" { SET client_min_messages TO 'error'; }\nstep \"CI\"\t{ CREATE INDEX test_index ON ts_index_test(location) WITH (timescaledb.transaction_per_chunk, timescaledb.barrier_table='barrier'); }\n\nsession \"RELEASE BARRIER\"\nsetup\t\t{ BEGIN; SET LOCAL lock_timeout = '500ms'; SET LOCAL deadlock_timeout = '10ms'; LOCK TABLE barrier;}\nstep \"Bc\"   { ROLLBACK; }\n\nsession \"SELECT\"\nsetup\t\t{ BEGIN; SET LOCAL lock_timeout = '500ms'; SET LOCAL deadlock_timeout = '10ms'; }\nstep \"S1\"\t{ SELECT * FROM ts_index_test; }\nstep \"Sc\"\t{ COMMIT; }\n\nsession \"INSERT CHUNK\"\nsetup\t\t{ BEGIN; SET LOCAL lock_timeout = '500ms'; SET LOCAL deadlock_timeout = '10ms'; }\nstep \"I1\"\t{ INSERT INTO ts_index_test VALUES (31, 6.4, 1); }\nstep \"Ic\"\t{ COMMIT; }\n\nsession \"DROP INDEX\"\nstep \"DI\"\t{ DROP INDEX test_index; }\n\nsession \"RENAME COLUMN\"\nstep \"RI\"\t{ ALTER TABLE test_index RENAME COLUMN location TO height; }\n\nsession \"COUNT INDEXES\"\nstep \"P\"    {  SELECT * FROM hypertable_index_size('test_index'); }\n\n# we need to COMMIT every transaction started in setup regardless of whether we use them\n# inserts work between chunks\npermutation \"CI\" \"I1\" \"Ic\" \"Bc\" \"P\" \"Sc\"\n\n# create blocks on insert\npermutation \"I1\" \"CI\" \"Bc\" \"Ic\" \"P\" \"Sc\"\n\n# create blocks on select\npermutation \"S1\" \"CI\" \"Bc\" \"Sc\" \"P\" \"Ic\"\n\n# drop works (the error message outputs an OID, remove the \"F\" to see the error)\npermutation \"F\" \"WPE\" \"CI\" \"DI\" \"Bc\" \"WPR\" \"P\" \"Ic\" \"Sc\"\n\n# rename should block\npermutation \"CI\" \"RI\" \"Bc\" \"P\" \"Ic\" \"Sc\"\n\n# Ideally we would declare these functions in setup, and use them to check that the actual index\n# exist on the relevant chunks in these tests. Unfortunately, in older versions of postgres\n# (IIRC until 10.4) there was an arbitrary limit that each SQL statement in an isolation test could\n# be no longer than 1024 characters. Instead, we currently use the number of bytes indexs take\n# as a proxy. Once we deprecate the old version, or add some other way to get index info we should\n# switch to this.\n#\n#   -- functions from testsupport.sql duplicated here becasue we cannot include sql files\n#    CREATE OR REPLACE FUNCTION show_columns(rel regclass)\n#    RETURNS TABLE(\"Column\" name,\n#                \"Type\" text,\n#                \"Nullable\" boolean) LANGUAGE SQL STABLE AS\n#    $BODY$\n#        SELECT a.attname,\n#        format_type(t.oid, t.typtypmod),\n#        a.attnotnull\n#        FROM pg_attribute a, pg_type t\n#        WHERE a.attrelid = rel\n#        AND a.atttypid = t.oid\n#        AND a.attnum >= 0\n#        ORDER BY a.attnum;\n#    $BODY$;\n#\n#    CREATE OR REPLACE FUNCTION show_indexesp(pattern text)\n#    RETURNS TABLE(\"Table\" regclass,\n#                \"Index\" regclass,\n#                \"Columns\" name[],\n#                \"Expr\" text,\n#                \"Unique\" boolean,\n#                \"Primary\" boolean,\n#                \"Exclusion\" boolean,\n#                \"Tablespace\" name) LANGUAGE PLPGSQL STABLE AS\n#    $BODY$\n#    DECLARE\n#        schema_name name = split_part(pattern, '.', 1);\n#        table_name name = split_part(pattern, '.', 2);\n#    BEGIN\n#        IF schema_name = '' OR table_name = '' THEN\n#        schema_name := current_schema();\n#        table_name := pattern;\n#        END IF;\n#\n#        RETURN QUERY\n#        SELECT c.oid::regclass,\n#        i.indexrelid::regclass,\n#        array(SELECT \"Column\" FROM show_columns(i.indexrelid)),\n#        pg_get_expr(i.indexprs, c.oid, true),\n#        i.indisunique,\n#        i.indisprimary,\n#        i.indisexclusion,\n#        (SELECT t.spcname FROM pg_class cc, pg_tablespace t WHERE cc.oid = i.indexrelid AND t.oid = cc.reltablespace)\n#        FROM pg_class c, pg_index i\n#        WHERE format('%I.%I', c.relnamespace::regnamespace::name, c.relname) LIKE format('%I.%s', schema_name, table_name)\n#        AND c.oid = i.indrelid\n#        ORDER BY c.oid, i.indexrelid;\n#    END\n#    $BODY$;\n"
  },
  {
    "path": "test/isolation/specs/read_committed_insert.spec",
    "content": "# This file and its contents are licensed under the Apache License 2.0.\n# Please see the included NOTICE for copyright information and\n# LICENSE-APACHE for a copy of the license.\n\nsetup\n{\n CREATE TABLE ts_cluster_test(time timestamptz, temp float, location int);\n SELECT table_name from create_hypertable('ts_cluster_test', 'time', chunk_time_interval => interval '1 day');\n \n}\n\nteardown { DROP TABLE ts_cluster_test; }\n\nsession \"s1\"\nsetup\t{ BEGIN; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SET LOCAL lock_timeout = '500ms'; SET LOCAL deadlock_timeout = '10ms'; }\nstep \"s1a\"\t{ INSERT INTO ts_cluster_test VALUES ('2017-01-20T090001', 23.4, 1); }\nstep \"s1c\"\t{ COMMIT; }\n\nsession \"s2\"\nsetup\t{ BEGIN; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SET LOCAL lock_timeout = '500ms'; SET LOCAL deadlock_timeout = '10ms'; }\nstep \"s2a\"\t{ INSERT INTO ts_cluster_test VALUES ('2017-01-20T090002', 0.72, 1); }\nstep \"s2b\"\t{ COMMIT; }\n\n"
  },
  {
    "path": "test/isolation/specs/read_uncommitted_insert.spec",
    "content": "# This file and its contents are licensed under the Apache License 2.0.\n# Please see the included NOTICE for copyright information and\n# LICENSE-APACHE for a copy of the license.\n\nsetup\n{\n CREATE TABLE ts_cluster_test(time timestamptz, temp float, location int);\n SELECT table_name from create_hypertable('ts_cluster_test', 'time', chunk_time_interval => interval '1 day');\n}\n\nteardown { DROP TABLE ts_cluster_test; }\n\nsession \"s1\"\nsetup\t{ BEGIN; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCAL lock_timeout = '500ms'; SET LOCAL deadlock_timeout = '10ms'; }\nstep \"s1a\"\t{ INSERT INTO ts_cluster_test VALUES ('2017-01-20T090001', 23.4, 1); }\nstep \"s1c\"\t{ COMMIT; }\n\nsession \"s2\"\nsetup\t{ BEGIN; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET LOCAL lock_timeout = '500ms'; SET LOCAL deadlock_timeout = '10ms'; }\nstep \"s2a\"\t{ INSERT INTO ts_cluster_test VALUES ('2017-01-20T090002', 0.72, 1); }\nstep \"s2b\"\t{ COMMIT; }\n\n"
  },
  {
    "path": "test/isolation/specs/repeatable_read_insert.spec",
    "content": "# This file and its contents are licensed under the Apache License 2.0.\n# Please see the included NOTICE for copyright information and\n# LICENSE-APACHE for a copy of the license.\n\nsetup\n{\n CREATE TABLE ts_cluster_test(time timestamptz, temp float, location int);\n SELECT table_name from create_hypertable('ts_cluster_test', 'time', chunk_time_interval => interval '1 day');\n}\n\nteardown { DROP TABLE ts_cluster_test; }\n\nsession \"s1\"\nsetup\t{ BEGIN; SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; SET LOCAL lock_timeout = '500ms'; SET LOCAL deadlock_timeout = '10ms'; }\nstep \"s1a\"\t{ INSERT INTO ts_cluster_test VALUES ('2017-01-20T090001', 23.4, 1); }\nstep \"s1c\"\t{ COMMIT; }\n\nsession \"s2\"\nsetup\t{ BEGIN; SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; SET LOCAL lock_timeout = '500ms'; SET LOCAL deadlock_timeout = '10ms'; }\nstep \"s2a\"\t{ INSERT INTO ts_cluster_test VALUES ('2017-01-20T090002', 0.72, 1); }\nstep \"s2b\"\t{ COMMIT; }\n\n"
  },
  {
    "path": "test/isolation/specs/serializable_insert.spec",
    "content": "# This file and its contents are licensed under the Apache License 2.0.\n# Please see the included NOTICE for copyright information and\n# LICENSE-APACHE for a copy of the license.\n\nsetup\n{\n CREATE TABLE ts_cluster_test(time timestamptz, temp float, location int);\n SELECT schema_name, table_name FROM create_hypertable('ts_cluster_test', 'time', chunk_time_interval => interval '1 day');\n}\n\nteardown { DROP TABLE ts_cluster_test; }\n\nsession \"s1\"\nsetup\t    { BEGIN; SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; SET LOCAL lock_timeout = '500ms'; SET LOCAL deadlock_timeout = '10ms'; }\nstep \"s1a\"\t{ INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:01', 23.4, 1); }\nstep \"s1c\"\t{ COMMIT; }\n\nsession \"s2\"\nsetup\t    { BEGIN; SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; SET LOCAL lock_timeout = '500ms'; SET LOCAL deadlock_timeout = '10ms'; }\nstep \"s2a\"\t{ INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:02', 0.72, 1); }\nstep \"s2c\"\t{ COMMIT; }\n"
  },
  {
    "path": "test/isolation/specs/serializable_insert_rollback.spec",
    "content": "# This file and its contents are licensed under the Apache License 2.0.\n# Please see the included NOTICE for copyright information and\n# LICENSE-APACHE for a copy of the license.\n\nsetup\n{\n CREATE TABLE ts_cluster_test(time timestamptz, temp float, location int);\n SELECT schema_name, table_name FROM create_hypertable('ts_cluster_test', 'time', chunk_time_interval => interval '1 day');\n}\n\nteardown { DROP TABLE ts_cluster_test; }\n\nsession \"s1\"\nsetup\t    { BEGIN; SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; SET LOCAL lock_timeout = '500ms'; SET LOCAL deadlock_timeout = '10ms'; }\nstep \"s1a\"\t{ INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:01', 23.4, 1); }\nstep \"s1c\"\t{ ROLLBACK; }\n\nsession \"s2\"\nsetup\t    { BEGIN; SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; SET LOCAL lock_timeout = '500ms'; SET LOCAL deadlock_timeout = '10ms'; }\nstep \"s2a\"\t{ INSERT INTO ts_cluster_test VALUES ('2017-01-20T09:00:02', 0.72, 1); }\nstep \"s2c\"\t{ COMMIT; }\n"
  },
  {
    "path": "test/perl/CMakeLists.txt",
    "content": "set(PERL_FILES TimescaleNode.pm)\n\n# Check if PostgreSQL was compiled with --enable-tap-tests\nif(TAP_CHECKS AND EXISTS \"${PG_PKGLIBDIR}/pgxs/src/test/perl\")\n  install(FILES ${PERL_FILES} DESTINATION \"${PG_PKGLIBDIR}/pgxs/src/test/perl\")\nendif()\n"
  },
  {
    "path": "test/perl/README.md",
    "content": "# Perl-based TAP tests\n\n`test/perl/` contains shared infrastructure that's used by Perl-based tests\nacross the source tree\n\nThe tests are invoked via perl's `prove` command. By default every\ntest in the t/ subdirectory is run. Individual test(s) can be run\ninstead by passing something like `PROVE_TESTS=\"t/001_testname.pl\nt/002_othertestname.pl\"` to make.\n\nYou should prefer to write tests using `pg_regress`, or\nisolation tester specs, if possible.\n\nNote that all tests and test tools should have perltidy run on them\nusing perltidy, for example:\n\n```\nperltidy --profile=$TS_SRC_DIR/.perltidyrc /path/to/taptest\n```\n\n## Writing tests\n\nTests are written using Perl's `Test::More` with some PostgreSQL-specific\ninfrastructure from `src/test/perl` providing node management, support for\ninvoking `psql` to run queries and get results, etc. You should read the\ndocumentation for `Test::More` before trying to write tests.\n\nThe PG specific infrastructure has been extended via the `TimescaleNode`\nclass in this directory to add timescale specific configuration parameters\nand some often used helper functions.\n\nTest scripts in the t/ subdirectory of a suite are executed in alphabetical\norder.\n\nEach test script should begin with:\n\n```\n    use strict;\n    use warnings;\n    use TimescaleNode;\n    use TestLib;\n    # Replace with the number of tests to execute:\n    use Test::More tests => 1;\n```\n\nthen it will generally need to set up one or more nodes, run commands\nagainst them and evaluate the results. For example:\n\n```\n    my $node = get_new_ts_node('access_node');\n    $node->init;\n    $node->start;\n\n    my $ret = $node->safe_psql('postgres', 'SELECT 1');\n    is($ret, '1', 'SELECT 1 returns 1');\n\n    $node->stop('fast');\n```\n\n`Test::More::like` entails use of the qr// operator.  Avoid Perl 5.8.8 bug\n#39185 by not using the \"$\" regular expression metacharacter in qr// when also\nusing the \"/m\" modifier.  Instead of \"$\", use \"\\n\" or \"(?=\\n|\\z)\".\n\nRead the `Test::More` documentation for more on how to write tests:\n\n```\n    perldoc Test::More\n```\n\nFor available PostgreSQL-specific test methods and some example tests read the\nperldoc for the test modules, e.g.:\n\n```\n    cd $COMMUNITY_PG_SRCS\n    perldoc src/test/perl/PostgresNode.pm\n```\n\n## Required Perl\n\nTests must run on perl `5.8.0` and newer. `perlbrew` is a good way to obtain such\na Perl; see http://perlbrew.pl .\n\nJust install and\n\n```\n    perlbrew --force install 5.8.0\n    perlbrew use 5.8.0\n    perlbrew install-cpanm\n    cpanm install IPC::Run\n```\n\nthen re-run configure to ensure the correct Perl is used when running\ntests. To verify that the right Perl was found:\n\n```\n    grep ^PERL= config.log\n```\n"
  },
  {
    "path": "test/perl/TimescaleNode.pm",
    "content": "# This file and its contents are licensed under the Timescale License.\n# Please see the included NOTICE for copyright information and\n# LICENSE-TIMESCALE for a copy of the license.\n\n# This class extends PostgresNode with Timescale-specific\n# routines for setup.\n\npackage TimescaleNode;\n\n# Using linebreaks here to prevent perltidy from performing vertical alignment.\n# This functionality has changed in recent perltidy versions (e.g., 2021 10 29)\n# and would restrict the versions of perltidy that can be used to format the\n# sources.\n\nuse parent PostgreSQL::Test::Cluster;\nuse PostgreSQL::Test::Utils qw(slurp_file);\n\nuse strict;\nuse warnings;\n\nuse Carp 'verbose';\n$SIG{__DIE__} = \\&Carp::confess;\n\nsub create\n{\n\tmy ($class, $name, %kwargs) = @_;\n\tmy $self = $class->new($name);\n\t$self->init(%kwargs);\n\t$self->start(%kwargs);\n\t$self->safe_psql('postgres', 'CREATE EXTENSION timescaledb');\n\treturn $self;\n}\n\n# initialize the data directory and add TS specific parameters\nsub init\n{\n\tmy ($self, %kwargs) = @_;\n\n\t$self->SUPER::init(%kwargs);\n\t# append into postgresql.conf from Timescale\n\t# template config file\n\t$self->append_conf('postgresql.conf',\n\t\tslurp_file(\"$ENV{'CONFDIR'}/postgresql.conf\"));\n\t$self->append_conf('postgresql.conf', 'datestyle=ISO');\n}\n\n# helper function to check output from PSQL for a query\nsub psql_is\n{\n\tmy ($self, $db, $query, $expected_stdout, $testname) = @_;\n\tmy ($psql_rc, $psql_out, $psql_err) = $self->SUPER::psql($db, $query);\n\tPostgreSQL::Test::Cluster::ok(!$psql_rc, \"$testname: err_code check\");\n\tPostgreSQL::Test::Cluster::is($psql_err, '',\n\t\t\"$testname: error_msg check\");\n\tPostgreSQL::Test::Cluster::is($psql_out, $expected_stdout,\n\t\t\"$testname: psql output check\");\n}\n\n# remove leading and trailing whitespace\nsub strip\n{\n\tmy ($str) = @_;\n\t$str =~ s/^\\s+|\\s+$//g;\n\treturn $str;\n}\n\nsub safe_psql\n{\n\tmy ($self, $db, $query) = @_;\n\tmy $psql_out = $self->SUPER::safe_psql($db, $query);\n\treturn strip($psql_out);\n}\n\n1;\n"
  },
  {
    "path": "test/pg_prove.sh",
    "content": "#!/usr/bin/env bash\n\n# Wrapper around perl prove utility to control running of TAP tests\n#\n# The following control variable is supported:\n#\n# PROVE_TESTS  only run TAP tests from this list\n# e.g make provecheck PROVE_TESTS=\"t/foo.pl t/bar.pl\"\n#\n# Note that you can also use regular expressions to run multiple\n# taps tests matching the pattern:\n#\n# e.g make provecheck PROVE_TESTS=\"t/*chunk*\"\n#\n\nPROVE_TESTS=${PROVE_TESTS:-}\nPROVE=${PROVE:-prove}\n\necho \"SKIPS: ${SKIPS}\"\n\n# If PROVE_TESTS is specified then run those subset of TAP tests even if\n# TESTS is also specified\nif [ -z \"$PROVE_TESTS\" ] && [ -z \"${SKIPS}\" ]\nthen\n    # Exit early if we are running with TESTS=expr\n    if [ -n \"$TESTS\" ]\n    then\n        exit 0\n    fi\n    FINAL_TESTS=$(ls -1 t/*.pl 2>/dev/null)\nelif [ -z \"$PROVE_TESTS\" ] && [ -n \"${SKIPS}\" ]\nthen\n    ALL_TESTS=$(ls -1 t/*.pl 2>/dev/null)\n    FILTERED_TESTS=\"\"\n    # disable path expansion to make SKIPS='*' work\n    set -f\n\n    # to support wildcards in SKIPS we match the SKIPS\n    # list against the actual list of tests\n    for test_name in ${ALL_TESTS}; do\n      for test_pattern in ${SKIPS}; do\n        # shellcheck disable=SC2053\n        # We do want to match globs in $test_pattern here.\n        if [[ $test_name == t/${test_pattern}.pl ]]; then\n          continue 2\n        fi\n      done\n      FILTERED_TESTS=\"${FILTERED_TESTS}\\n${test_name}\"\n    done\n    FINAL_TESTS=$(echo -e \"${FILTERED_TESTS}\" | tr '\\n' ' ' | sed -e 's/^ *//')\n\nelse\n    FINAL_TESTS=$PROVE_TESTS\nfi\n\nif [ -z \"$FINAL_TESTS\" ]\nthen\n\techo \"No TAP tests to run for the current configuration, skipping...\"\n\texit 0;\nfi\nPG_VERSION_MAJOR=${PG_VERSION_MAJOR} ${PROVE} \\\n    -I \"${SRC_DIR}/src/test/perl\" \\\n    -I \"${CM_SRC_DIR}/test/perl\" \\\n    -I \"${PG_LIBDIR}/pgxs/src/test/perl\" \\\n    -I \"${PG_PKGLIBDIR}/pgxs/src/test/perl\" \\\n    -I \"${PG_LIBDIR}/postgresql/pgxs/src/test/perl\" \\\n    $FINAL_TESTS\n"
  },
  {
    "path": "test/pg_regress.sh",
    "content": "#!/usr/bin/env bash\n\n# shellcheck disable=SC2053\n\n# Wrapper around pg_regress and pg_isolation_regress to be able to control the schedule with environment variables\n#\n# The following control variables are supported:\n#\n# TESTS     only run tests from this list\n# IGNORES   failure of tests in this list will not lead to test failure\n# SKIPS     tests from this list are not run\n#\n# In TESTS you may use wildcards to match multiple test names\n# TESTS=\"compression*\" will match all tests whose name starts with compression\n# TESTS=\"*compression*\" will match all tests that have compression anywhere in the name\n# Wildcard matching also applies to version specific tests so compression-13\n# would also be matched by those patterns.\n\nCURRENT_DIR=$(dirname $0)\nEXE_DIR=${EXE_DIR:-${CURRENT_DIR}}\nPG_REGRESS=${PG_REGRESS:-pg_regress}\nPG_REGRESS_DIFF_OPTS=-u\nTEST_SCHEDULE=${TEST_SCHEDULE:-}\nTEMP_SCHEDULE=${CURRENT_DIR}/temp_schedule\nSCHEDULE=\nTESTS=${TESTS:-}\nIGNORES=${IGNORES:-}\nSKIPS=${SKIPS:-}\n# PG_BINDIR is passed from CMake via environment\nPSQL=\"${PSQL:-${PG_BINDIR}/psql} -X\" # Prevent any .psqlrc files from being executed during the tests\nPG_VERSION_MAJOR=$(${PSQL} --version | awk '{split($3,v,\"[.a-z]\"); print v[1]}')\n\n# check if test matches any of the patterns in a list\n# $1 list of patterns or test names\n# $2 test name\n# we use == intentionally and not =~ because the pattern syntax differs between\n# those two and == allows for simpler patterns. With == the pattern to match\n# all bgw tests would be \"*bgw*\" while with =~ it would be \".*bgw.*\"\nmatches() {\n  for pattern in $1; do\n    if [[ $2 == $pattern ]]; then\n      return 0\n    fi\n  done\n  return 1\n}\n\nif [[ -z ${TEST_SCHEDULE} ]];  then\n  echo \"No test schedule supplied please set TEST_SCHEDULE\"\n  exit 1;\nfi\n\n# PG16 removed the `ignore` feature from `pg_regress`\n# so as an workaround if we have any IGNORES entry then\n# we merge it together with SKIPS and cleanup the IGNORES\n# https://github.com/postgres/postgres/commit/bd8d453e9b5f8b632a400a9e796fc041aed76d82\nif [[ ${PG_VERSION_MAJOR} -ge 16 ]]; then\n  if [[ -n ${IGNORES} ]]; then\n    if [[ -n ${SKIPS} ]]; then\n      SKIPS=\"${SKIPS} ${IGNORES}\"\n    else\n      SKIPS=\"${IGNORES}\"\n    fi\n    IGNORES=\"\"\n  fi\nfi\n\necho \"TESTS ${TESTS}\"\nif [[ ${PG_VERSION_MAJOR} -lt 16 ]]; then\n  echo \"IGNORES ${IGNORES}\"\nfi\necho \"SKIPS ${SKIPS}\"\n\nif [[ -z ${TESTS} ]] && [[ -z ${SKIPS} ]] && [[ -z ${IGNORES} ]]; then\n  # no filter variables set\n  # nothing to do here and we can use the cmake generated schedule\n\n  SCHEDULE=${TEST_SCHEDULE}\n\nelif [[ -z ${TESTS} && ( -n ${SKIPS} || -n ${IGNORES} ) ]]; then\n  # If we only have IGNORES or SKIPS we can use the cmake created schedule\n  # and just prepend ignore lines for the tests whose result should be\n  # ignored and strip out the skipped tests. This will allow us to retain\n  # the parallel groupings from the supplied schedule.\n\n  echo > ${TEMP_SCHEDULE}\n\n  ALL_TESTS=$(grep -a '^test: ' ${TEST_SCHEDULE} | sed -e 's!^test: !!' |tr '\\n' ' ')\n\n  # to support wildcards in IGNORES we match the IGNORES\n  # list against the actual list of tests\n  if [[ -n ${IGNORES} ]]; then\n    for test_pattern in ${IGNORES}; do\n      for test_name in ${ALL_TESTS}; do\n        if [[ -n ${test_name} ]] && [[ $test_name == $test_pattern ]]; then\n          echo \"ignore: ${test_name}\" >> ${TEMP_SCHEDULE}\n        fi\n      done\n      for test_name in ${SKIPS}\n      do\n        if [[ -n ${test_name} && ${test_name} == ${test_pattern} ]]\n        then\n            echo \"The ignored pattern '${test_name}' matches the skipped pattern '${test_pattern}'. This is not allowed.\"\n            exit 1\n        fi\n      done\n    done\n  fi\n\n  cat ${TEST_SCHEDULE} >> ${TEMP_SCHEDULE}\n\n  # to support wildcards in SKIPS we match the SKIPS\n  # list against the actual list of tests\n  if [[ -n ${SKIPS} ]]; then\n    for test_pattern in ${SKIPS}; do\n      for test_name in ${ALL_TESTS}; do\n        if [[ $test_name == $test_pattern ]]; then\n          sed -e \"s!^test:\\s*${test_name}\\s*\\$!!\" -i.backup ${TEMP_SCHEDULE}\n          sed -e \"s!\\b${test_name}\\b!!\" -i.backup ${TEMP_SCHEDULE}\n        fi\n      done\n    done\n  fi\n\n  SCHEDULE=${TEMP_SCHEDULE}\n\nelse\n  # TESTS was specified so we need to create a new schedule based on that\n\n  ALL_TESTS=$(grep -a '^test: ' ${TEST_SCHEDULE} | sed -e 's!^test: !!' |tr '\\n' ' ')\n\n  if [[ -z \"${TESTS}\" ]]; then\n    TESTS=${ALL_TESTS}\n  fi\n\n  # build new test list in current_tests removing entries in SKIPS and\n  # validating against schedule as TESTS might contain tests from\n  # multiple suites and not apply to current run\n  current_tests=\"\"\n  for test_pattern in ${TESTS}; do\n    for test_name in ${ALL_TESTS}; do\n      if ! matches \"${SKIPS}\" \"${test_name}\"; then\n        if [[ $test_name == $test_pattern ]]; then\n          current_tests=\"${current_tests} ${test_name}\"\n        elif [[ $test_name =~ ^${test_pattern}-[1-9][0-9]$ ]]; then\n          current_tests=\"${current_tests} ${test_name}\"\n        fi\n      fi\n    done\n  done\n\n  # if none of the tests survived filtering we can exit early\n  if [[ -z \"${current_tests}\" ]]; then\n    exit 0\n  fi\n\n  current_tests=$(echo \"${current_tests}\" | tr ' ' '\\n' | sort)\n\n  TESTS=${current_tests}\n\n  echo > ${TEMP_SCHEDULE}\n\n  # to support wildcards in IGNORES we match the IGNORES\n  # list against the actual list of tests\n  for test_pattern in ${IGNORES}; do\n    for test_name in ${ALL_TESTS}; do\n      if ! matches \"${SKIPS}\" \"${test_name}\"; then\n        if [[ $test_name == $test_pattern ]]; then\n          echo \"ignore: ${test_name}\" >> ${TEMP_SCHEDULE}\n        fi\n      fi\n    done\n  done\n\n  for t in ${TESTS}; do\n      echo \"test: ${t}\" >> ${TEMP_SCHEDULE}\n  done\n\n  SCHEDULE=${TEMP_SCHEDULE}\n\nfi\n\nfunction cleanup() {\n  rm -rf ${EXE_DIR}/sql/dump\n  rm -rf ${TEST_TABLESPACE1_PREFIX}\n  rm -rf ${TEST_TABLESPACE2_PREFIX}\n  rm -rf ${TEST_TABLESPACE3_PREFIX}\n  rm -f ${TEMP_SCHEDULE}\n  cat <<EOF | ${PSQL} -U ${USER} -h ${TEST_PGHOST} -p ${TEST_PGPORT} -d template1 >/dev/null 2>&1\n    DROP TABLESPACE IF EXISTS tablespace1;\n    DROP TABLESPACE IF EXISTS tablespace2;\n    DROP TABLESPACE IF EXISTS tablespace3;\nEOF\n  rm -rf ${TEST_OUTPUT_DIR}/.pg_init\n}\n\ntrap cleanup EXIT\n\n# Generating a prefix directory for all test tablespaces. This should\n# be used to build a full path for the tablespace. Note that we\n# terminate the prefix with the directory separator so that we can\n# easily generate paths independent of the OS.\n#\n# This mktemp line will work on both OSX and GNU systems\nTEST_TABLESPACE1_PREFIX=${TEST_TABLESPACE1_PREFIX:-$(mktemp -d 2>/dev/null || mktemp -d -t 'timescaledb_regress')/}\nTEST_TABLESPACE2_PREFIX=${TEST_TABLESPACE2_PREFIX:-$(mktemp -d 2>/dev/null || mktemp -d -t 'timescaledb_regress')/}\nTEST_TABLESPACE3_PREFIX=${TEST_TABLESPACE3_PREFIX:-$(mktemp -d 2>/dev/null || mktemp -d -t 'timescaledb_regress')/}\n\n# Creating some defaults for transitioning tests to use the prefix.\nTEST_TABLESPACE1_PATH=${TEST_TABLESPACE1_PATH:-${TEST_TABLESPACE1_PREFIX}_default}\nTEST_TABLESPACE2_PATH=${TEST_TABLESPACE2_PATH:-${TEST_TABLESPACE2_PREFIX}_default}\nTEST_TABLESPACE3_PATH=${TEST_TABLESPACE3_PATH:-${TEST_TABLESPACE3_PREFIX}_default}\nmkdir -p $TEST_TABLESPACE1_PATH $TEST_TABLESPACE2_PATH $TEST_TABLESPACE3_PATH\n\nexport TEST_TABLESPACE1_PREFIX TEST_TABLESPACE2_PREFIX TEST_TABLESPACE3_PREFIX\nexport TEST_TABLESPACE1_PATH TEST_TABLESPACE2_PATH TEST_TABLESPACE3_PATH\n\nrm -rf ${TEST_OUTPUT_DIR}/.pg_init\nmkdir -p ${EXE_DIR}/sql/dump\n\nexport PG_REGRESS_DIFF_OPTS\n\n# If so configured, we run the tests with faketime utility to change the current\n# time. This helps catch the mistakes with using the current time in test\n# references. We can't do this for isolation tests because this breaks the\n# waiting mechanism in isolation tester.\nif [[ \"${PG_REGRESS_USE_FAKETIME}\" == \"1\" ]]\nthen\n    PG_REGRESS_FAKETIME=\"${FAKETIME}\"\nfi\n\nPG_REGRESS_OPTS=\"${PG_REGRESS_OPTS}  --schedule=${SCHEDULE}\"\n${PG_REGRESS_FAKETIME} ${PG_REGRESS} \"$@\" ${PG_REGRESS_OPTS}\n"
  },
  {
    "path": "test/pgpass.conf.in",
    "content": "# Only TEST_ROLE_2 should have password in passfile\n# TEST_ROLE_3 needs to rely on, e.g., user mappings in the DB\n*:*:*:@TEST_ROLE_2@:@TEST_ROLE_2_PASS@\n"
  },
  {
    "path": "test/pgtest/CMakeLists.txt",
    "content": "set(PG_REGRESS_DIR\n    ${PG_SOURCE_DIR}/src/test/regress\n    CACHE PATH \"Path to PostgreSQL's regress directory\")\n\n# input and output directory got removed in PG15\nset(PGTEST_DIRS ${PG_REGRESS_DIR}/data ${PG_REGRESS_DIR}/sql\n                ${PG_REGRESS_DIR}/expected)\nif(EXISTS ${PG_REGRESS_DIR}/input AND EXISTS ${PG_REGRESS_DIR}/output)\n  list(APPEND PGTEST_DIRS ${PG_REGRESS_DIR}/input ${PG_REGRESS_DIR}/output)\nendif()\n\n# Copy the input and output files from PostgreSQL's test suite. The test suite\n# generates some SQL scripts and output files from template source files and\n# require directories to be colocated\nfile(COPY ${PGTEST_DIRS} DESTINATION ${CMAKE_CURRENT_BINARY_DIR})\n\nfile(READ ${PG_REGRESS_DIR}/parallel_schedule PG_TEST_SCHEDULE)\n\n# create directory for tablespace test\nfile(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/testtablespace)\n\n# Tests to ignore\nset(PG_IGNORE_TESTS\n    amutils\n    database\n    event_trigger\n    jsonb_jsonpath\n    opr_sanity\n    sanity_check\n    type_sanity\n    create_am\n    # Ignoring because it spawns different number of workers in different\n    # versions.\n    select_parallel\n    psql)\n\n# Modify the test schedule to ignore some tests\nforeach(IGNORE_TEST ${PG_IGNORE_TESTS})\n  # ignored schedules was removed in PG16\n  # https://github.com/postgres/postgres/commit/bd8d453e9b5f8b632a400a9e796fc041aed76d82\n  if(${PG_VERSION_MAJOR} LESS \"16\")\n    string(CONCAT PG_TEST_SCHEDULE \"ignore: ${IGNORE_TEST}\\n\"\n                  ${PG_TEST_SCHEDULE})\n  else()\n    # remove the ignored test from the schedule\n    string(REPLACE \"test: ${IGNORE_TEST}\\n\" \"\" PG_TEST_SCHEDULE\n                   \"${PG_TEST_SCHEDULE}\")\n    string(REPLACE \" ${IGNORE_TEST} \" \" \" PG_TEST_SCHEDULE\n                   \"${PG_TEST_SCHEDULE}\")\n    string(REPLACE \" ${IGNORE_TEST}\\n\" \"\\n\" PG_TEST_SCHEDULE\n                   \"${PG_TEST_SCHEDULE}\")\n  endif()\nendforeach(IGNORE_TEST)\n\n# Write the final test schedule\nfile(WRITE ${CMAKE_CURRENT_BINARY_DIR}/schedule ${PG_TEST_SCHEDULE})\n\n# Need --dlpath set to PostgreSQL's test directory so that the tests can load\n# libraries there\nset(PG_REGRESS_OPTS_PGTEST\n    --schedule=${CMAKE_CURRENT_BINARY_DIR}/schedule\n    --load-extension=timescaledb --dlpath=${PG_REGRESS_DIR})\n\nadd_custom_target(\n  pginstallcheck\n  COMMAND ${PG_REGRESS} ${PG_REGRESS_OPTS_BASE} ${PG_REGRESS_OPTS_PGTEST}\n          ${PG_REGRESS_OPTS_TEMP_INSTANCE_PGTEST}\n  USES_TERMINAL)\n\nadd_custom_target(\n  pginstallchecklocal\n  COMMAND ${PG_REGRESS} ${PG_REGRESS_OPTS_BASE} ${PG_REGRESS_OPTS_PGTEST}\n          ${PG_REGRESS_OPTS_LOCAL_INSTANCE}\n  USES_TERMINAL)\n"
  },
  {
    "path": "test/pgtest/README.md",
    "content": "# PostgreSQL tests for TimescaleDB\n\nThe CMake configuration within this directory makes it possible to run\nthe standard PostgreSQL test suite with the TimescaleDB extension\nloaded. This is useful to ensure that TimescaleDBs modifications\nplanner and DDL hooks are compatible with standard PostgreSQL.\n\n## Running\n\nThe configuration within adds a new CMake target, `pginstallcheck`,\nthat allows running the PostgreSQL test suite using a modified test\nschedule. The target requires access to the PostgreSQL source code,\nwhich can be configured via the `PG_SOURCE_DIR` CMake variable. The\nsource tree needs to be compiled, at least the `src/test/regress`\ndirectory. If the path to a PostgreSQL source tree is not\nauto-detected, this variable can be set manually to point to the right\nlocation.\n\n```\n# In top-level directory of a TimescaleDB source tree\n$ mkdir build && cd build\n$ cmake -DPG_SOURCE_DIR=<path/to/pg/source> ..\n```\n\nOnce CMake is correctly configured, run:\n\n```\n$ make pginstallcheck\n```\n"
  },
  {
    "path": "test/pgtest.conf.in",
    "content": "# postgresql.conf settings for PostgreSQL test suite\nshared_preload_libraries=timescaledb\n@TELEMETRY_DEFAULT_SETTING@\n"
  },
  {
    "path": "test/postgres-asan-instrumentation-PG18GE.patch",
    "content": "diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c\nindex 8f7cf531fbc..2f7b1cebbe6 100644\n--- a/src/backend/utils/misc/stack_depth.c\n+++ b/src/backend/utils/misc/stack_depth.c\n@@ -108,6 +108,12 @@ check_stack_depth(void)\n bool\n stack_is_too_deep(void)\n {\n+       /*\n+        * Pointer arithmetics to determine stack depth doesn't work under\n+        * AddressSanitizer.\n+        */\n+       return false;\n+\n        char            stack_top_loc;\n        ssize_t         stack_depth;\n\ndiff --git a/src/include/utils/memdebug.h b/src/include/utils/memdebug.h\nindex e88b4c6e8e..4ccbbf0146 100644\n--- a/src/include/utils/memdebug.h\n+++ b/src/include/utils/memdebug.h\n@@ -19,6 +19,31 @@\n \n #ifdef USE_VALGRIND\n #include <valgrind/memcheck.h>\n+\n+#elif __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)\n+\n+#include <sanitizer/asan_interface.h>\n+\n+#define VALGRIND_MAKE_MEM_DEFINED(addr, size) \\\n+ ASAN_UNPOISON_MEMORY_REGION(addr, size)\n+\n+#define VALGRIND_MAKE_MEM_NOACCESS(addr, size) \\\n+ ASAN_POISON_MEMORY_REGION(addr, size)\n+\n+#define VALGRIND_MAKE_MEM_UNDEFINED(addr, size) \\\n+ ASAN_UNPOISON_MEMORY_REGION(addr, size)\n+\n+#define VALGRIND_MEMPOOL_ALLOC(context, addr, size) \\\n+ ASAN_UNPOISON_MEMORY_REGION(addr, size)\n+\n+#define VALGRIND_MEMPOOL_FREE(context, addr) \\\n+ ASAN_POISON_MEMORY_REGION(addr, 1 /* Length unknown, poison first byte. */)\n+\n+#define VALGRIND_CHECK_MEM_IS_DEFINED(addr, size) do {} while (0)\n+#define VALGRIND_CREATE_MEMPOOL(context, redzones, zeroed) do {} while (0)\n+#define VALGRIND_DESTROY_MEMPOOL(context) do {} while (0)\n+#define VALGRIND_MEMPOOL_CHANGE(context, optr, nptr, size) do {} while (0)\n+\n #else\n #define VALGRIND_CHECK_MEM_IS_DEFINED(addr, size)\t\t\tdo {} while (0)\n #define VALGRIND_CREATE_MEMPOOL(context, redzones, zeroed)\tdo {} while (0)\n"
  },
  {
    "path": "test/postgres-asan-instrumentation.patch",
    "content": "diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c\nindex 2c50575b37..11b6c688c7 100644\n--- a/src/backend/tcop/postgres.c\n+++ b/src/backend/tcop/postgres.c\n@@ -3492,6 +4476,12 @@ check_stack_depth(void)\n bool\n stack_is_too_deep(void)\n {\n+\t/*\n+\t * Pointer arithmetics to determine stack depth doesn't work under\n+\t * AddressSanitizer.\n+\t */\n+\treturn false;\n+\n \tchar\t\tstack_top_loc;\n \tlong\t\tstack_depth;\n \ndiff --git a/src/include/utils/memdebug.h b/src/include/utils/memdebug.h\nindex e88b4c6e8e..4ccbbf0146 100644\n--- a/src/include/utils/memdebug.h\n+++ b/src/include/utils/memdebug.h\n@@ -19,6 +19,31 @@\n \n #ifdef USE_VALGRIND\n #include <valgrind/memcheck.h>\n+\n+#elif __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)\n+\n+#include <sanitizer/asan_interface.h>\n+\n+#define VALGRIND_MAKE_MEM_DEFINED(addr, size) \\\n+ ASAN_UNPOISON_MEMORY_REGION(addr, size)\n+\n+#define VALGRIND_MAKE_MEM_NOACCESS(addr, size) \\\n+ ASAN_POISON_MEMORY_REGION(addr, size)\n+\n+#define VALGRIND_MAKE_MEM_UNDEFINED(addr, size) \\\n+ ASAN_UNPOISON_MEMORY_REGION(addr, size)\n+\n+#define VALGRIND_MEMPOOL_ALLOC(context, addr, size) \\\n+ ASAN_UNPOISON_MEMORY_REGION(addr, size)\n+\n+#define VALGRIND_MEMPOOL_FREE(context, addr) \\\n+ ASAN_POISON_MEMORY_REGION(addr, 1 /* Length unknown, poison first byte. */)\n+\n+#define VALGRIND_CHECK_MEM_IS_DEFINED(addr, size) do {} while (0)\n+#define VALGRIND_CREATE_MEMPOOL(context, redzones, zeroed) do {} while (0)\n+#define VALGRIND_DESTROY_MEMPOOL(context) do {} while (0)\n+#define VALGRIND_MEMPOOL_CHANGE(context, optr, nptr, size) do {} while (0)\n+\n #else\n #define VALGRIND_CHECK_MEM_IS_DEFINED(addr, size)\t\t\tdo {} while (0)\n #define VALGRIND_CREATE_MEMPOOL(context, redzones, zeroed)\tdo {} while (0)\n"
  },
  {
    "path": "test/postgresql.conf.in",
    "content": "# NOTE: any changes here require changes to tsl/test/postgresql.conf. Its prefix\n# must be the same as this file.\n\nautovacuum=true\ndatestyle='Postgres, MDY'\nlog_destination='jsonlog,stderr'\nlog_directory='@TEST_PG_LOG_DIRECTORY@'\nlog_filename='postmaster.log'\nlog_line_prefix='%m: %u [%p] %d '\nlogging_collector=true\nmax_worker_processes=24\nrandom_page_cost=1.0\nshared_preload_libraries=timescaledb\ntimescaledb.last_tuned='1971-02-03 04:05:06.789012 -0300'\ntimescaledb.last_tuned_version='0.0.1'\ntimescaledb.passfile='@TEST_PASSFILE@'\ntimescaledb_telemetry.cloud='ci'\ntimezone='US/Pacific'\n\n# Set extra_float_digits=0 to retain the pre PG12 rounding behaviour\n# of floating point numbers, which are needed to make our tests work.\nextra_float_digits=0\n\n@TELEMETRY_DEFAULT_SETTING@\n\ntimescaledb.license='apache'\ntimescaledb.enable_compression_ratio_warnings=false\n\n# Some tests use logical entries in WAL\nwal_level = logical\n"
  },
  {
    "path": "test/runner.sh",
    "content": "#!/usr/bin/env bash\n\nset -u\nset -e\nCURRENT_DIR=$(dirname $0)\nEXE_DIR=${EXE_DIR:-${CURRENT_DIR}}\nPG_REGRESS_PSQL=$1\nPSQL=${PSQL:-$PG_REGRESS_PSQL}\nPSQL=\"${PSQL} -X\" # Prevent any .psqlrc files from being executed during the tests\nTEST_PGUSER=${TEST_PGUSER:-postgres}\nTEST_INPUT_DIR=${TEST_INPUT_DIR:-${EXE_DIR}}\nTEST_OUTPUT_DIR=${TEST_OUTPUT_DIR:-${EXE_DIR}}\nTEST_SUPPORT_FILE=${CURRENT_DIR}/sql/utils/testsupport.sql\nTEST_SUPPORT_FILE_INIT=${CURRENT_DIR}/sql/utils/testsupport_init.sql\nTEST_TIMEOUT=${TEST_TIMEOUT:-120}\n\n# PGAPPNAME will be 'pg_regress/test' so we cut off the prefix\n# to get the name of the test\nCURRENT_TEST=${PGAPPNAME##pg_regress/}\n\n# Since different PG version tests cannot run in parallel in the same instance,\n# we remove the trailing version suffix to get a good symbol that can be\n# used as identifier as well.\nTEST_DBNAME=\"db_${CURRENT_TEST%%-[0-9][0-9]}\"\n\n# Read the extension version from version.config\nread -r VERSION < ${CURRENT_DIR}/../version.config\nEXT_VERSION=${VERSION##version = }\n\n# on macos check if proper timeout is available\nif [ \"$(uname)\" == \"Darwin\" ]; then\n  if ! command -v gtimeout >/dev/null 2>&1\n    then\n      TIMEOUT_CMD=\"\"\n    else\n      TIMEOUT_CMD=\"gtimeout -v ${TEST_TIMEOUT}s\"\n  fi\nelse\n  TIMEOUT_CMD=\"timeout -v ${TEST_TIMEOUT}s\"\nfi\n\n#docker doesn't set user\nUSER=${USER:-$(whoami)}\n\nTEST_SPINWAIT_ITERS=${TEST_SPINWAIT_ITERS:-1000}\n\nTEST_ROLE_SUPERUSER=${TEST_ROLE_SUPERUSER:-super_user}\nTEST_ROLE_DEFAULT_PERM_USER=${TEST_ROLE_DEFAULT_PERM_USER:-default_perm_user}\nTEST_ROLE_DEFAULT_PERM_USER_2=${TEST_ROLE_DEFAULT_PERM_USER_2:-default_perm_user_2}\n\n# Users for clustering. These users have password auth enabled in pg_hba.conf\nTEST_ROLE_1=${TEST_ROLE_1:-test_role_1}\nTEST_ROLE_2=${TEST_ROLE_2:-test_role_2}\nTEST_ROLE_2_PASS=${TEST_ROLE_2_PASS:-pass}\nTEST_ROLE_3=${TEST_ROLE_3:-test_role_3}\nTEST_ROLE_3_PASS=${TEST_ROLE_3_PASS:-pass}\nTEST_ROLE_4=${TEST_ROLE_4:-test_role_4}\nTEST_ROLE_4_PASS=${TEST_ROLE_4_PASS:-pass}\nTEST_ROLE_READ_ONLY=${TEST_ROLE_READ_ONLY:-test_role_read_only}\n\nshift\n\n# Drop test database and make it less verbose in case of dropping a\n# distributed database.\nfunction cleanup {\n  cat <<EOF | ${PSQL} \"$@\" -U $TEST_ROLE_SUPERUSER -d postgres -v ECHO=none >/dev/null 2>&1\n    SET client_min_messages=ERROR;\n    DROP DATABASE \"${TEST_DBNAME}\";\nEOF\n}\n\ntrap cleanup EXIT\n\n# setup clusterwide settings on first run\n# we use mkdir here because it is an atomic operation unlike existence of a lockfile\n# where creating and checking are 2 separate operations\nif mkdir ${TEST_OUTPUT_DIR}/.pg_init 2>/dev/null; then\n  ${PSQL} \"$@\" -U ${USER} -d template1 -v ECHO=none >/dev/null 2>&1 <<EOF\n    SET client_min_messages=ERROR;\n\n    DO \\$\\$\n      BEGIN\n        IF current_setting('server_version_num')::int >= 150000 THEN\n          GRANT CREATE ON SCHEMA public TO ${TEST_PGUSER};\n          GRANT CREATE ON SCHEMA public TO ${TEST_ROLE_DEFAULT_PERM_USER};\n          GRANT CREATE ON SCHEMA public TO ${TEST_ROLE_DEFAULT_PERM_USER_2};\n          GRANT CREATE ON SCHEMA public TO ${TEST_ROLE_1};\n          GRANT CREATE ON SCHEMA public TO ${TEST_ROLE_2};\n          GRANT CREATE ON SCHEMA public TO ${TEST_ROLE_3};\n          GRANT CREATE ON SCHEMA public TO ${TEST_ROLE_4};\n        END IF;\n      END\n    \\$\\$ LANGUAGE PLPGSQL;\n\n    ALTER USER ${TEST_ROLE_SUPERUSER} WITH SUPERUSER;\n    ALTER USER ${TEST_ROLE_1} WITH CREATEDB CREATEROLE;\n    ALTER USER ${TEST_ROLE_2} WITH CREATEDB PASSWORD '${TEST_ROLE_2_PASS}';\n    ALTER USER ${TEST_ROLE_3} WITH CREATEDB PASSWORD '${TEST_ROLE_3_PASS}';\n    ALTER USER ${TEST_ROLE_4} WITH CREATEDB PASSWORD '${TEST_ROLE_4_PASS}';\nEOF\n  ${PSQL} \"$@\" -U $TEST_ROLE_SUPERUSER -d template1 \\\n      -v ECHO=none \\\n      -v MODULE_PATHNAME=\"'timescaledb-${EXT_VERSION}'\" \\\n      -v TSL_MODULE_PATHNAME=\"'timescaledb-tsl-${EXT_VERSION}'\" \\\n      -v TEST_SPINWAIT_ITERS=${TEST_SPINWAIT_ITERS} \\\n      -f ${TEST_SUPPORT_FILE} >/dev/null 2>&1\n\n  ${PSQL} \"$@\" -U ${USER} -d postgres -v ECHO=none -c \"ALTER USER ${TEST_ROLE_SUPERUSER} WITH SUPERUSER;\" >/dev/null\n  touch ${TEST_OUTPUT_DIR}/.pg_init/done\nfi\n\n# we need to wait for cluster setup to finish cause with parallel schedule\n# multiple instances will be running and mkdir will only succeed on the first runner\nwhile [ ! -f ${TEST_OUTPUT_DIR}/.pg_init/done ]; do sleep 0.2; done\n\ncd ${EXE_DIR}/sql\n\n# create database and install timescaledb\n${PSQL} \"$@\" -U $TEST_ROLE_SUPERUSER -d postgres -v ECHO=none -c \"CREATE DATABASE \\\"${TEST_DBNAME}\\\";\"\n${PSQL} \"$@\" -U $TEST_ROLE_SUPERUSER -d ${TEST_DBNAME} -v ECHO=none -c \"SET client_min_messages=error; CREATE EXTENSION timescaledb;\"\n${PSQL} \"$@\" -U $TEST_ROLE_SUPERUSER -d ${TEST_DBNAME} \\\n    -v ECHO=none \\\n    -v MODULE_PATHNAME=\"'timescaledb-${EXT_VERSION}'\" \\\n    -v TSL_MODULE_PATHNAME=\"'timescaledb-tsl-${EXT_VERSION}'\" \\\n    -v TEST_SPINWAIT_ITERS=${TEST_SPINWAIT_ITERS} \\\n    -f ${TEST_SUPPORT_FILE_INIT} >/dev/null 2>&1\nexport TEST_DBNAME\n\n# we strip out any output between <exclude_from_test></exclude_from_test>\n# and the part about memory usage in EXPLAIN ANALYZE output of Sort nodes\n# also ignore the Postgres rehashing catalog debug messages from 'src/backend/utils/cache/catcache.c'\n${TIMEOUT_CMD} ${PSQL} -U ${TEST_PGUSER} \\\n     -v ON_ERROR_STOP=1 \\\n     -v VERBOSITY=terse \\\n     -v ECHO=all \\\n     -v TEST_DBNAME=\"${TEST_DBNAME}\" \\\n     -v TEST_TABLESPACE1_PREFIX=${TEST_TABLESPACE1_PREFIX} \\\n     -v TEST_TABLESPACE2_PREFIX=${TEST_TABLESPACE2_PREFIX} \\\n     -v TEST_TABLESPACE3_PREFIX=${TEST_TABLESPACE3_PREFIX} \\\n     -v TEST_TABLESPACE1_PATH=\\'${TEST_TABLESPACE1_PATH}\\' \\\n     -v TEST_TABLESPACE2_PATH=\\'${TEST_TABLESPACE2_PATH}\\' \\\n     -v TEST_TABLESPACE3_PATH=\\'${TEST_TABLESPACE3_PATH}\\' \\\n     -v TEST_INPUT_DIR=${TEST_INPUT_DIR} \\\n     -v TEST_OUTPUT_DIR=${TEST_OUTPUT_DIR} \\\n     -v TEST_SPINWAIT_ITERS=${TEST_SPINWAIT_ITERS} \\\n     -v ROLE_SUPERUSER=${TEST_ROLE_SUPERUSER} \\\n     -v ROLE_DEFAULT_PERM_USER=${TEST_ROLE_DEFAULT_PERM_USER} \\\n     -v ROLE_DEFAULT_PERM_USER_2=${TEST_ROLE_DEFAULT_PERM_USER_2} \\\n     -v ROLE_1=${TEST_ROLE_1} \\\n     -v ROLE_2=${TEST_ROLE_2} \\\n     -v ROLE_3=${TEST_ROLE_3} \\\n     -v ROLE_4=${TEST_ROLE_4} \\\n     -v ROLE_READ_ONLY=${TEST_ROLE_READ_ONLY} \\\n     -v ROLE_2_PASS=${TEST_ROLE_2_PASS} \\\n     -v ROLE_3_PASS=${TEST_ROLE_3_PASS} \\\n     -v ROLE_4_PASS=${TEST_ROLE_4_PASS} \\\n     -v MODULE_PATHNAME=\"'timescaledb-${EXT_VERSION}'\" \\\n     -v TSL_MODULE_PATHNAME=\"'timescaledb-tsl-${EXT_VERSION}'\" \\\n     -v TEST_SUPPORT_FILE=${TEST_SUPPORT_FILE} \\\n     -v TEST_SUPPORT_FILE_INIT=${TEST_SUPPORT_FILE_INIT} \\\n     \"$@\" -d ${TEST_DBNAME} 2>&1 | ${CURRENT_DIR}/runner_cleanup_output.sh\n"
  },
  {
    "path": "test/runner_cleanup_output.sh",
    "content": "#!/usr/bin/env bash\n\nset -u\nset -e\n\nRUNNER=${1:-\"\"}\n\nsed  -E -e '/<exclude_from_test>/,/<\\/exclude_from_test>/d' \\\n     -e 's! Disk: [0-9]+kB!!' \\\n     -e 's! Memory: [0-9]+kB!!' \\\n     -e 's! Memory Usage: [0-9]+kB!!' \\\n     -e 's! Average  Peak Memory: [0-9]+kB!!' \\\n     -e 's!ERROR:  permission denied for materialized view!ERROR:  permission denied for view!' \\\n     -e 's!\"*_ts_meta_v2_bl[0-9A-Za-z]+_([_0-9A-Za-z]+)\"*!regress-test-bloom_\\1!g' \\\n\t -e 's/(actual rows=[0-9]+) /\\1.00 /' \\\n\t -e '/^ ?\\([0-9]+ row[s]?\\)$/d' \\\n\t -e '/ +QUERY PLAN +/{N;s/ +QUERY PLAN +\\n-+/--- QUERY PLAN ---/;}' \\\n     -e '/Disabled: true/d' \\\n     -e '/Heap Fetches: [0-9]+/d' \\\n     -e '/Buckets: [0-9]\\+/d' \\\n     -e '/Index Searches: [0-9]+/d' \\\n     -e '/Storage: Memory  Maximum Storage: [0-9]+kB/d' \\\n     -e '/Window: /d' \\\n     -e '/Batches: [0-9]+/d' \\\n     -e '/found [0-9]+ removable, [0-9]+ nonremovable row versions in [0-9]+ pages/d' | \\\ngrep -av 'DEBUG:  rehashing catalog cache id' | \\\ngrep -av 'DEBUG:  compacted fsync request queue from' | \\\ngrep -av 'DEBUG:  creating and filling new WAL file' | \\\ngrep -av 'DEBUG:  done creating and filling new WAL file' | \\\ngrep -av 'DEBUG:  flushed relation because a checkpoint occurred concurrently' | \\\ngrep -av 'NOTICE:  cancelling the background worker for job' | \\\nif [ \"${RUNNER}\" = \"shared\" ]; then \\\n    sed -e 's!_[0-9]\\{1,\\}_[0-9]\\{1,\\}_chunk!_X_X_chunk!g'; \\\nelse \\\n    cat; \\\nfi | \\\nif [ \"${RUNNER}\" = \"isolation\" ]; then \\\n    sed -e 's!_[0-9]\\{1,\\}_[0-9]\\{1,\\}_chunk!_X_X_chunk!g' \\\n        -e 's!hypertable_[0-9]\\{1,\\}!hypertable_X!g' \\\n        -e 's!constraint_[0-9]\\{1,\\}!constraint_X!g' \\\n        -e 's!with OID [0-9]\\{1,\\}!with OID X!g'; \\\nelse \\\n    cat; \\\nfi\n"
  },
  {
    "path": "test/runner_isolation.sh",
    "content": "#!/usr/bin/env bash\n#\n# Wrapper for the PostgreSQL isolationtest runner. It replaces\n# the chunks IDs in the output of the tests by _X_X_. So, even\n# if the IDs change, the tests will not fail.\n##############################################################\n\nset -e\nset -u\nCURRENT_DIR=$(dirname $0)\n\nISOLATIONTEST=$1\nshift\n\n# Note that removing the chunk numbers is not enough. The chunk numbers also\n# influence the alignment of the EXPLAIN output, so not only we have to replace\n# them, we also have to remove the \"----\"s and the trailing spaces. The aligned\n# output format in isolation tester is hardcoded, we cannot change it. Moreover,\n# the chunk numbers influence the names of indexes if they are long enough to be\n# truncated, so the only way to get a stable explain output is to run such a test\n# in a separate database.\n$ISOLATIONTEST \"$@\" | ${CURRENT_DIR}/runner_cleanup_output.sh \"isolation\"\n"
  },
  {
    "path": "test/runner_shared.sh",
    "content": "#!/usr/bin/env bash\n\nset -u\nset -e\nCURRENT_DIR=$(dirname $0)\nEXE_DIR=${EXE_DIR:-${CURRENT_DIR}}\nPG_REGRESS_PSQL=$1\nPSQL=${PSQL:-$PG_REGRESS_PSQL}\nPSQL=\"${PSQL} -X\" # Prevent any .psqlrc files from being executed during the tests\nTEST_PGUSER=${TEST_PGUSER:-postgres}\nTEST_INPUT_DIR=${TEST_INPUT_DIR:-${EXE_DIR}}\nTEST_OUTPUT_DIR=${TEST_OUTPUT_DIR:-${EXE_DIR}}\nTEST_SUPPORT_FILE=${CURRENT_DIR}/sql/utils/testsupport.sql\nTEST_SUPPORT_FILE_INIT=${CURRENT_DIR}/sql/utils/testsupport_init.sql\nTEST_TIMEOUT=${TEST_TIMEOUT:-120}\n\n# Read the extension version from version.config\nread -r VERSION < ${CURRENT_DIR}/../version.config\nEXT_VERSION=${VERSION##version = }\n\n# PGAPPNAME will be 'pg_regress/test' so we cut off the prefix\n# to get the name of the test\nTEST_BASE_NAME=${PGAPPNAME##pg_regress/}\n\n# if this is a versioned test our name will have version as suffix\n# so we cut off suffix to get base name\nif [[ ${TEST_BASE_NAME} == *-1[0-9] ]]; then\n  TEST_BASE_NAME=${TEST_BASE_NAME%???}\nfi\n\n# on macos check if proper timeout is available\nif [ \"$(uname)\" == \"Darwin\" ]; then\n  if ! command -v gtimeout >/dev/null 2>&1\n    then\n      TIMEOUT_CMD=\"\"\n    else\n      TIMEOUT_CMD=\"gtimeout -v ${TEST_TIMEOUT}s\"\n  fi\nelse\n  TIMEOUT_CMD=\"timeout -v ${TEST_TIMEOUT}s\"\nfi\n\n#docker doesn't set user\nUSER=${USER:-$(whoami)}\n\nTEST_ROLE_SUPERUSER=${TEST_ROLE_SUPERUSER:-super_user}\nTEST_ROLE_DEFAULT_PERM_USER=${TEST_ROLE_DEFAULT_PERM_USER:-default_perm_user}\nTEST_ROLE_DEFAULT_PERM_USER_2=${TEST_ROLE_DEFAULT_PERM_USER_2:-default_perm_user_2}\n\nshift\n\n# setup clusterwide settings on first run\n# we use mkdir here because it is an atomic operation unlike existence of a lockfile\n# where creating and checking are 2 separate operations\nif mkdir ${TEST_OUTPUT_DIR}/.pg_init 2>/dev/null; then\n  ${PSQL} \"$@\" -U ${USER} -d postgres -v ECHO=none -c \"ALTER USER ${TEST_ROLE_SUPERUSER} WITH SUPERUSER;\" >/dev/null\n  ${PSQL} -U ${USER} \\\n     -v MODULE_PATHNAME=\"'timescaledb-${EXT_VERSION}'\" \\\n     -v ROLE_DEFAULT_PERM_USER=${TEST_ROLE_DEFAULT_PERM_USER} \\\n     -v ROLE_DEFAULT_PERM_USER_2=${TEST_ROLE_DEFAULT_PERM_USER_2} \\\n     -v ROLE_SUPERUSER=${TEST_ROLE_SUPERUSER} \\\n     -v TEST_BASE_NAME=${TEST_BASE_NAME} \\\n     -v TEST_DBNAME=\"${TEST_DBNAME}\" \\\n     -v TEST_INPUT_DIR=${TEST_INPUT_DIR} \\\n     -v TEST_OUTPUT_DIR=${TEST_OUTPUT_DIR} \\\n     -v TEST_SUPPORT_FILE=${TEST_SUPPORT_FILE} \\\n     -v TEST_SUPPORT_FILE_INIT=${TEST_SUPPORT_FILE_INIT} \\\n     -v TSL_MODULE_PATHNAME=\"'timescaledb-tsl-${EXT_VERSION}'\" \\\n     \"$@\" -d ${TEST_DBNAME} < ${TEST_INPUT_DIR}/shared/sql/include/shared_setup.sql >/dev/null\n  touch ${TEST_OUTPUT_DIR}/.pg_init/done\nfi\n\n# we need to wait for cluster setup to finish cause with parallel schedule\n# multiple instances will be running and mkdir will only succeed on the first runner\nwhile [ ! -f ${TEST_OUTPUT_DIR}/.pg_init/done ]; do sleep 0.2; done\n\ncd ${EXE_DIR}/sql\n\n# we strip out any output between <exclude_from_test></exclude_from_test>\n# and the part about memory usage in EXPLAIN ANALYZE output of Sort nodes\n# also ignore the Postgres rehashing catalog debug messages from 'src/backend/utils/cache/catcache.c'\n${TIMEOUT_CMD} ${PSQL} -U ${TEST_PGUSER} \\\n     -v ON_ERROR_STOP=1 \\\n     -v VERBOSITY=terse \\\n     -v ECHO=all \\\n     -v TEST_DBNAME=\"${TEST_DBNAME}\" \\\n     -v TEST_BASE_NAME=${TEST_BASE_NAME} \\\n     -v TEST_INPUT_DIR=${TEST_INPUT_DIR} \\\n     -v TEST_OUTPUT_DIR=${TEST_OUTPUT_DIR} \\\n     -v ROLE_SUPERUSER=${TEST_ROLE_SUPERUSER} \\\n     -v ROLE_DEFAULT_PERM_USER=${TEST_ROLE_DEFAULT_PERM_USER} \\\n     -v ROLE_DEFAULT_PERM_USER_2=${TEST_ROLE_DEFAULT_PERM_USER_2} \\\n     -v MODULE_PATHNAME=\"'timescaledb-${EXT_VERSION}'\" \\\n     -v TSL_MODULE_PATHNAME=\"'timescaledb-tsl-${EXT_VERSION}'\" \\\n     \"$@\" -d ${TEST_DBNAME} 2>&1 | ${CURRENT_DIR}/runner_cleanup_output.sh \"shared\"\n"
  },
  {
    "path": "test/sql/.gitignore",
    "content": "/agg_bookends-*.sql\n/alternate_users-*.sql\n/append-*.sql\n/cluster-*.sql\n/drop_owned-*.sql\n/grant_hypertable-*.sql\n/histogram_test-*.sql\n/insert-*.sql\n/insert_many-*.sql\n/null_exclusion-*.sql\n/parallel-*.sql\n/partitioning-*.sql\n/partitionwise-*.sql\n/plan_expand_hypertable-*.sql\n/plan_hashagg-*.sql\n/plan_hashagg_optimized-*.sql\n/plan_hypertable_cache-*.sql\n/plan_ordered_append-*.sql\n/rowsecurity-*.sql\n/timestamp-*.sql\n/ts_merge-*.sql\n/loader-*.sql\n"
  },
  {
    "path": "test/sql/CMakeLists.txt",
    "content": "include(GenerateTestSchedule)\n\nset(TEST_FILES\n    alter.sql\n    alternate_users.sql\n    baserel_cache.sql\n    catalog_corruption.sql\n    chunk_adaptive.sql\n    chunk_publication.sql\n    chunk_utils.sql\n    chunks.sql\n    cluster.sql\n    constraint.sql\n    copy.sql\n    copy_where.sql\n    create_chunks.sql\n    create_hypertable.sql\n    create_table.sql\n    create_table_with.sql\n    cursor.sql\n    ddl.sql\n    ddl_errors.sql\n    ddl_extra.sql\n    debug_utils.sql\n    delete.sql\n    drop_extension.sql\n    drop_hypertable.sql\n    drop_rename_hypertable.sql\n    drop_schema.sql\n    dump_meta.sql\n    extension_scripts.sql\n    generated_as_identity.sql\n    hash.sql\n    index.sql\n    information_views.sql\n    insert_many.sql\n    insert_returning.sql\n    insert_single.sql\n    lateral.sql\n    merge.sql\n    partition.sql\n    partition_coercion.sql\n    partitioning.sql\n    pg_dump_unprivileged.sql\n    pg_join.sql\n    plain.sql\n    plan_hypertable_inline.sql\n    query.sql\n    relocate_extension.sql\n    reloptions.sql\n    repair.sql\n    size_utils.sql\n    sort_optimization.sql\n    sql_query.sql\n    tableam.sql\n    tableam_alter.sql\n    tablespace.sql\n    triggers.sql\n    truncate.sql\n    trusted_extension.sql\n    update.sql\n    upsert.sql\n    util.sql\n    uuid.sql\n    vacuum.sql\n    vacuum_parallel.sql\n    version.sql\n    license.sql)\n\nset(TEST_TEMPLATES\n    agg_bookends.sql.in\n    append.sql.in\n    drop_owned.sql.in\n    grant_hypertable.sql.in\n    histogram_test.sql.in\n    insert.sql.in\n    null_exclusion.sql.in\n    plan_hashagg.sql.in\n    rowsecurity.sql.in\n    parallel.sql.in\n    partitionwise.sql.in\n    plan_expand_hypertable.sql.in\n    plan_ordered_append.sql.in\n    timestamp.sql.in\n    ts_merge.sql.in)\n\n# Loader test must distinguish between Apache and TSL builds so we parametrize\n# this here\nset(LOADER_TEST_FILE loader-${TEST_LICENSE_SUFFIX})\nconfigure_file(loader.sql.in\n               ${CMAKE_CURRENT_SOURCE_DIR}/${LOADER_TEST_FILE}.sql)\n\n# tests that fail or are unreliable when run in parallel bgw tests need to run\n# first otherwise they are flaky\nset(SOLO_TESTS\n    alter\n    alternate_users\n    bgw_launcher\n    chunk_utils\n    index\n    pg_dump_unprivileged\n    tablespace\n    telemetry\n    net)\n\nlist(APPEND SOLO_TESTS ${LOADER_TEST_FILE})\n\nif(CMAKE_BUILD_TYPE MATCHES Debug)\n  list(\n    APPEND\n    TEST_FILES\n    bgw_launcher.sql\n    c_unit_tests.sql\n    copy_memory_usage.sql\n    metadata.sql\n    multi_transaction_index.sql\n    net.sql\n    partitioned_hypertable.sql\n    pg_dump.sql\n    symbol_conflict.sql\n    test_tss_callbacks.sql\n    test_utils.sql\n    ${LOADER_TEST_FILE}.sql)\n  if(USE_TELEMETRY)\n    list(APPEND TEST_FILES telemetry.sql)\n  endif()\nendif(CMAKE_BUILD_TYPE MATCHES Debug)\n\nif((${PG_VERSION_MAJOR} GREATER_EQUAL \"17\"))\n  list(APPEND TEST_FILES tableam_alter_defaults.sql)\nendif()\n\nif((${PG_VERSION_MAJOR} GREATER_EQUAL \"18\"))\n  list(APPEND TEST_FILES insert_returning_old_new.sql)\nendif()\n\n# only test custom type if we are in 64-bit architecture\nif(\"${CMAKE_SIZEOF_VOID_P}\" STREQUAL \"8\")\n  list(APPEND TEST_FILES custom_type.sql)\nendif()\n\n# Regression tests that vary with PostgreSQL version. Generated test files are\n# put in the original source directory since all tests must be in the same\n# directory. These files are updated when the template is edited, but not when\n# the output file is deleted. If the output is deleted either recreate it\n# manually, or rerun cmake on the root dir.\nforeach(TEMPLATE_FILE ${TEST_TEMPLATES})\n  string(LENGTH ${TEMPLATE_FILE} TEMPLATE_NAME_LEN)\n  math(EXPR TEMPLATE_NAME_LEN ${TEMPLATE_NAME_LEN}-7)\n  string(SUBSTRING ${TEMPLATE_FILE} 0 ${TEMPLATE_NAME_LEN} TEMPLATE)\n  set(TEST_FILE ${TEMPLATE}-${TEST_VERSION_SUFFIX}.sql)\n  configure_file(${TEMPLATE_FILE} ${CMAKE_CURRENT_SOURCE_DIR}/${TEST_FILE}\n                 @ONLY)\n  list(APPEND TEST_FILES ${TEST_FILE})\nendforeach(TEMPLATE_FILE)\n\nif(NOT TEST_GROUP_SIZE)\n  set(PARALLEL_GROUP_SIZE 20)\nelse()\n  set(PARALLEL_GROUP_SIZE ${TEST_GROUP_SIZE})\nendif()\n\n# Generate a test schedule for each configuration.\ngenerate_test_schedule(\n  ${TEST_SCHEDULE}\n  TEST_FILES\n  ${TEST_FILES}\n  SOLO\n  ${SOLO_TESTS}\n  GROUP_SIZE\n  ${PARALLEL_GROUP_SIZE})\n\nadd_subdirectory(loader)\n"
  },
  {
    "path": "test/sql/agg_bookends.sql.in",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\set TEST_BASE_NAME agg_bookends\nSELECT format('include/%s_load.sql', :'TEST_BASE_NAME') as \"TEST_LOAD_NAME\",\n       format('include/%s_query.sql', :'TEST_BASE_NAME') as \"TEST_QUERY_NAME\",\n       format('%s/results/%s_results_optimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_OPTIMIZED\",\n       format('%s/results/%s_results_unoptimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_UNOPTIMIZED\"\n\\gset\nSELECT format('\\! diff -u  --label \"Unoptimized result\" --label \"Optimized result\" %s %s', :'TEST_RESULTS_UNOPTIMIZED', :'TEST_RESULTS_OPTIMIZED') as \"DIFF_CMD\"\n\\gset\n\n\\set PREFIX 'EXPLAIN (analyze, buffers off, costs off, timing off, summary off)'\n\\ir :TEST_LOAD_NAME\n\\ir :TEST_QUERY_NAME\n\n-- we want test results as part of the output too to make sure we produce correct output\n\\set PREFIX ''\n\\ir :TEST_QUERY_NAME\n\n-- diff results with optimizations disabled and enabled\n\\o :TEST_RESULTS_UNOPTIMIZED\nSET timescaledb.enable_optimizations TO false;\n\\ir :TEST_QUERY_NAME\n\\o\n\n\\o :TEST_RESULTS_OPTIMIZED\nSET timescaledb.enable_optimizations TO true;\n\\ir :TEST_QUERY_NAME\n\\o\n\n:DIFF_CMD\n\n-- Test partial aggregation\nCREATE TABLE partial_aggregation (time timestamptz NOT NULL, quantity numeric, longvalue text);\nSELECT schema_name, table_name, created FROM create_hypertable('partial_aggregation', 'time');\n\nINSERT INTO partial_aggregation VALUES('2018-01-20T09:00:43', NULL, NULL);\nINSERT INTO partial_aggregation VALUES('2018-01-20T09:00:44', NULL, NULL);\nINSERT INTO partial_aggregation VALUES('2019-01-20T09:00:43', 1, 'hello');\nINSERT INTO partial_aggregation VALUES('2019-01-20T09:00:44', 2, 'world');\nINSERT INTO partial_aggregation VALUES('2020-01-20T09:00:43', 3.1, 'some1');\nINSERT INTO partial_aggregation VALUES('2020-01-20T09:00:44', 3.2, 'more1');\nINSERT INTO partial_aggregation VALUES('2021-01-20T09:00:43', 3.3, 'some2');\nINSERT INTO partial_aggregation VALUES('2021-01-20T09:00:44', 3.4, 'more2');\nINSERT INTO partial_aggregation VALUES('2022-01-20T09:00:43', 4, 'word1');\nINSERT INTO partial_aggregation VALUES('2022-01-20T09:00:44', 5, 'word2');\nINSERT INTO partial_aggregation VALUES('2023-01-20T09:00:43', 6, 'word3');\nINSERT INTO partial_aggregation VALUES('2023-01-20T09:00:44', 7, 'word4');\n\n-- Use enable_partitionwise_aggregate to create partial aggregates per chunk\nSET enable_partitionwise_aggregate = ON;\n\nSELECT\n    format('SELECT %3$s, %1$s FROM partial_aggregation WHERE %2$s GROUP BY %3$s ORDER BY 1, 2;',\n            function, condition, grouping)\nFROM\n    unnest(array[\n            'first(time, quantity), last(time, quantity)',\n            'last(longvalue, quantity)',\n            'last(quantity, longvalue)',\n            'last(quantity, time)',\n            'last(time, longvalue)']) AS function,\n    unnest(array[\n            'true',\n            $$time < '2021-01-01'$$,\n            'quantity is null',\n            'quantity is not null',\n            'quantity >= 4']) AS condition,\n    unnest(array[\n            '777::text' /* dummy grouping column */,\n            'longvalue',\n            'quantity',\n            $$time_bucket('1 year', time)$$,\n            $$time_bucket('3 year', time)$$]) AS grouping\n\\gexec\n\nSET enable_partitionwise_aggregate = OFF;\n\n"
  },
  {
    "path": "test/sql/alter.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Set this variable to avoid using a hard-coded path each time query\n-- results are compared\n\\set QUERY_RESULT_TEST_EQUAL_RELPATH 'include/query_result_test_equal.sql'\n\n-- DROP a table's column before making it a hypertable\nCREATE TABLE alter_before(id serial, time timestamp, temp float, colorid integer, notes text, notes_2 text);\nALTER TABLE alter_before DROP COLUMN id;\nALTER TABLE alter_before ALTER COLUMN temp SET (n_distinct = 10);\nALTER TABLE alter_before ALTER COLUMN colorid SET (n_distinct = 11);\nALTER TABLE alter_before ALTER COLUMN colorid RESET (n_distinct);\nALTER TABLE alter_before ALTER COLUMN temp SET STATISTICS 100;\nALTER TABLE alter_before ALTER COLUMN notes SET STORAGE EXTERNAL;\n\nSELECT create_hypertable('alter_before', 'time', chunk_time_interval => 2628000000000);\n\n-- Test error hint for invalid timescaledb options on ALTER TABLE\n\\set ON_ERROR_STOP 0\n-- Invalid timescaledb option should show hint with valid options\n\\set VERBOSITY default\nALTER TABLE alter_before SET (tsdb.invalid_option = true);\nALTER TABLE alter_before SET (timescaledb.nonexistent = false);\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n\nINSERT INTO alter_before VALUES ('2017-03-22T09:18:22', 23.5, 1);\n\nSELECT * FROM alter_before;\n\n-- Show that deleted column is marked as dropped and that attnums are\n-- now different for the root table and the chunk\n-- PG17 made attstattarget NULLABLE and changed the default from -1 to NULL\nSELECT c.relname, a.attname, a.attnum, a.attoptions, CASE WHEN a.attstattarget = -1 OR (a.attisdropped AND a.attstattarget = 0) THEN NULL ELSE a.attstattarget END attstattarget, a.attstorage FROM pg_attribute a, pg_class c\nWHERE a.attrelid = c.oid\nAND (c.relname LIKE '_hyper_1%_chunk' OR c.relname = 'alter_before')\nAND a.attnum > 0\nORDER BY c.relname, a.attnum;\n\n-- DROP a table's column after making it a hypertable and having data\nCREATE TABLE alter_after(id serial, time timestamp, temp float, colorid integer, notes text, notes_2 text);\nSELECT create_hypertable('alter_after', 'time', chunk_time_interval => 2628000000000);\n\n-- Create first chunk\nINSERT INTO alter_after (time, temp, colorid) VALUES ('2017-03-22T09:18:22', 23.5, 1);\n\nALTER TABLE alter_after DROP COLUMN id;\nALTER TABLE alter_after ALTER COLUMN temp SET (n_distinct = 10);\nALTER TABLE alter_after ALTER COLUMN colorid SET (n_distinct = 11);\nALTER TABLE alter_after ALTER COLUMN colorid RESET (n_distinct);\nALTER TABLE alter_after ALTER COLUMN colorid SET STATISTICS 101;\nALTER TABLE alter_after ALTER COLUMN notes_2 SET STORAGE EXTERNAL;\n\n-- Creating new chunks after dropping a column should work just fine\nINSERT INTO alter_after VALUES ('2017-03-22T09:18:23', 21.5, 1),\n                               ('2017-05-22T09:18:22', 36.2, 2),\n                               ('2017-05-22T09:18:23', 15.2, 2);\n\n-- Make sure tuple conversion also works with COPY\n\\COPY alter_after FROM 'data/alter.tsv' NULL AS '';\n\n-- Data should look OK\nSELECT * FROM alter_after;\n\n-- Show that attnums are different for chunks created after DROP\n-- column\nSELECT c.relname, a.attname, a.attnum FROM pg_attribute a, pg_class c\nWHERE a.attrelid = c.oid\nAND (c.relname LIKE '_hyper_2%_chunk' OR c.relname = 'alter_after')\nAND a.attnum > 0\nORDER BY c.relname, a.attnum;\n\n-- Add an ID column again\nALTER TABLE alter_after ADD COLUMN id serial;\n\nINSERT INTO alter_after (time, temp, colorid) VALUES ('2017-08-22T09:19:14', 12.5, 3);\n\n--test thing that we are allowed to do on chunks\nALTER TABLE  _timescaledb_internal._hyper_2_3_chunk ALTER COLUMN temp RESET (n_distinct);\nALTER TABLE  _timescaledb_internal._hyper_2_4_chunk ALTER COLUMN temp SET (n_distinct = 20);\nALTER TABLE  _timescaledb_internal._hyper_2_4_chunk ALTER COLUMN temp SET STATISTICS 201;\nALTER TABLE  _timescaledb_internal._hyper_2_4_chunk ALTER COLUMN notes SET STORAGE EXTERNAL;\n\n-- PG17 made attstattarget NULLABLE and changed the default from -1 to NULL\nSELECT c.relname, a.attname, a.attnum, a.attoptions, CASE WHEN a.attstattarget = -1 OR (a.attisdropped AND a.attstattarget = 0) THEN NULL ELSE a.attstattarget END attstattarget, a.attstorage FROM pg_attribute a, pg_class c\nWHERE a.attrelid = c.oid\nAND (c.relname LIKE '_hyper_2%_chunk' OR c.relname = 'alter_after')\nAND a.attnum > 0\nORDER BY c.relname, a.attnum;\n\nSELECT * FROM alter_after;\n\n-- test setting reloptions\nALTER TABLE  _timescaledb_internal._hyper_2_3_chunk SET (parallel_workers=2);\nALTER TABLE  _timescaledb_internal._hyper_2_4_chunk SET (parallel_workers=4);\nALTER TABLE  _timescaledb_internal._hyper_2_4_chunk RESET (parallel_workers);\n\nSELECT relname, reloptions FROM pg_class WHERE relname IN ('_hyper_2_3_chunk','_hyper_2_4_chunk');\n\n-- Need superuser to ALTER chunks in _timescaledb_internal schema\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk WHERE id = 2;\n\n-- Rename chunk\nALTER TABLE _timescaledb_internal._hyper_2_2_chunk RENAME TO new_chunk_name;\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk WHERE id = 2;\n\n-- Set schema\nALTER TABLE _timescaledb_internal.new_chunk_name SET SCHEMA public;\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk WHERE id = 2;\n\n-- Test that we cannot rename chunk columns\n\\set ON_ERROR_STOP 0\nALTER TABLE public.new_chunk_name RENAME COLUMN time TO newtime;\n\\set ON_ERROR_STOP 1\n\n-- Test that we can set tablespace of a hypertable\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET client_min_messages = ERROR;\nDROP TABLESPACE IF EXISTS tablespace1;\nDROP TABLESPACE IF EXISTS tablespace2;\nSET client_min_messages = NOTICE;\n--test hypertable with tables space\nCREATE TABLESPACE tablespace1 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE1_PATH;\nCREATE TABLESPACE tablespace2 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE2_PATH;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n\n-- Test that we can directly change chunk tablespace\nALTER TABLE public.new_chunk_name SET TABLESPACE tablespace1;\nSELECT tablespace FROM pg_tables WHERE tablename = 'new_chunk_name';\n\n-- drop all tables to make checking the tests below easier\nDROP TABLE alter_before;\nDROP TABLE alter_after;\n-- should return 0 rows\nSELECT tablename, tablespace FROM pg_tables\nWHERE tablename = 'hyper_in_space' OR tablename LIKE '\\_hyper\\__\\__\\_chunk' ORDER BY tablename;\n\nCREATE TABLE hyper_in_space(time bigint, temp float, device int);\nSELECT create_hypertable('hyper_in_space', 'time', 'device', 4, chunk_time_interval=>1);\n\nINSERT INTO hyper_in_space(time, temp, device) VALUES (1, 20, 1);\nINSERT INTO hyper_in_space(time, temp, device) VALUES (3, 21, 2);\nINSERT INTO hyper_in_space(time, temp, device) VALUES (5, 23, 1);\n\nSELECT tablename FROM pg_tables WHERE tablespace = 'tablespace1' ORDER BY tablename;\n\nSET default_tablespace = tablespace1;\n\n-- should be inserted in tablespace1 which is now default\nINSERT INTO hyper_in_space(time, temp, device) VALUES (11, 24, 3);\nSELECT tablename, tablespace FROM pg_tables\nWHERE tablename = 'hyper_in_space' OR tablename LIKE '\\_hyper\\__\\__\\_chunk' ORDER BY tablename;\n\nSET default_tablespace TO DEFAULT;\n\nALTER TABLE hyper_in_space SET TABLESPACE tablespace1;\nSELECT tablename FROM pg_tables WHERE tablespace = 'tablespace1' ORDER BY tablename;\n\n-- should be inserted in an existing chunk in the new tablespace,\n-- no new chunks\nINSERT INTO hyper_in_space(time, temp, device) VALUES (5, 27, 1);\n\n-- the new chunk should be create in the new tablespace\nINSERT INTO hyper_in_space(time, temp, device) VALUES (8, 24, 2);\nSELECT tablename, tablespace FROM pg_tables\nWHERE tablename = 'hyper_in_space' OR tablename LIKE '\\_hyper\\__\\__\\_chunk' ORDER BY tablename;\n\n-- should not fail (unlike attach_tablespace)\nALTER TABLE hyper_in_space SET TABLESPACE tablespace1;\n\n\\set ON_ERROR_STOP 0\n-- not an empty tablespace\nDROP TABLESPACE tablespace1;\n\\set ON_ERROR_STOP 1\n\n-- show_chunks and drop_chunks output should be the same\n\\set QUERY1 'SELECT show_chunks(\\'hyper_in_space\\', 22)::NAME'\n\\set QUERY2 'SELECT drop_chunks(\\'hyper_in_space\\', 22)::NAME'\n\\set ECHO errors\n\\ir :QUERY_RESULT_TEST_EQUAL_RELPATH\n\\set ECHO all\nSELECT tablename, tablespace FROM pg_tables WHERE tablespace = 'tablespace1' ORDER BY tablename;\n\n\\set ON_ERROR_STOP 0\n-- should not be able to drop tablespace if a hypertable depends on it\n-- even when there are no chunks\nDROP TABLESPACE tablespace1;\n\\set ON_ERROR_STOP 1\n\nDROP TABLE hyper_in_space;\n\nCREATE TABLE hyper_in_space(time bigint, temp float, device int) TABLESPACE tablespace1;\nSELECT create_hypertable('hyper_in_space', 'time', 'device', 4, chunk_time_interval=>1);\n\nINSERT INTO hyper_in_space(time, temp, device) VALUES (1, 20, 1);\nINSERT INTO hyper_in_space(time, temp, device) VALUES (3, 21, 2);\nINSERT INTO hyper_in_space(time, temp, device) VALUES (5, 23, 1);\n\nSELECT tablename, tablespace FROM pg_tables\nWHERE tablename = 'hyper_in_space' OR tablename ~ '_hyper_\\d+_\\d+_chunk' ORDER BY tablename;\n\nSELECT attach_tablespace('tablespace2', 'hyper_in_space');\n\n\\set ON_ERROR_STOP 0\n-- should fail as >1 tablespaces are attached\nALTER TABLE hyper_in_space SET TABLESPACE tablespace1;\n\\set ON_ERROR_STOP 1\n\nSELECT detach_tablespace('tablespace2', 'hyper_in_space');\n\nSELECT * FROM _timescaledb_catalog.tablespace;\n\n-- make sure when using ALTER TABLE, table spaces are not accumulated\n-- as in case of attach_tablespace\n-- should have one result\nSELECT * FROM _timescaledb_catalog.tablespace;\nALTER TABLE hyper_in_space SET TABLESPACE tablespace2;\n-- should have one result\nSELECT * FROM _timescaledb_catalog.tablespace;\nALTER TABLE hyper_in_space SET TABLESPACE tablespace1;\n-- should have one result, (same as the first in the block)\nSELECT * FROM _timescaledb_catalog.tablespace;\n\nSELECT tablename, tablespace FROM pg_tables\nWHERE tablename = 'hyper_in_space' OR tablename ~ '_hyper_\\d+_\\d+_chunk' ORDER BY tablename;\n-- attach tb2 <-> ALTER SET tb1 <-> detach tb1 should work\nSELECT detach_tablespace('tablespace1', 'hyper_in_space');\nINSERT INTO hyper_in_space(time, temp, device) VALUES (5, 23, 1);\nINSERT INTO hyper_in_space(time, temp, device) VALUES (7, 23, 1);\n\n-- Since we have detached tablespace1 the new chunk should not be\n-- placed there.\nSELECT tablename, tablespace FROM pg_tables\nWHERE tablename = 'hyper_in_space' OR tablename ~ '_hyper_\\d+_\\d+_chunk' ORDER BY tablename;\nSELECT * FROM _timescaledb_catalog.tablespace;\n\n-- tablespace functions should handle the default tablespace just as they do others\nSELECT attach_tablespace('pg_default', 'hyper_in_space');\nSELECT attach_tablespace('tablespace2', 'hyper_in_space');\n\nSELECT tablename, tablespace FROM pg_tables\nWHERE tablename = 'hyper_in_space' OR tablename ~ '_hyper_\\d+_\\d+_chunk' ORDER BY tablename;\nSELECT * FROM _timescaledb_catalog.tablespace;\n\nINSERT INTO hyper_in_space(time, temp, device) VALUES (12, 22, 1);\nINSERT INTO hyper_in_space(time, temp, device) VALUES (13, 23, 3);\n\nSELECT tablename, tablespace FROM pg_tables\nWHERE tablename = 'hyper_in_space' OR tablename ~ '_hyper_\\d+_\\d+_chunk' ORDER BY tablename;\n\n\nSELECT detach_tablespace('pg_default', 'hyper_in_space');\n\nALTER TABLE hyper_in_space SET TABLESPACE pg_default;\n\nSELECT tablename, tablespace FROM pg_tables\nWHERE tablename = 'hyper_in_space' OR tablename ~ '_hyper_\\d+_\\d+_chunk' ORDER BY tablename;\n\nSELECT detach_tablespace('pg_default', 'hyper_in_space');\n\nDROP TABLE hyper_in_space;\n\n-- test altering tablespace on index, issue #903\nCREATE TABLE series(\n  time timestamptz not null,\n  device int,\n  value float,\n  CONSTRAINT series_pk PRIMARY KEY (time, device) USING INDEX TABLESPACE tablespace1);\nSELECT create_hypertable('series', 'time', create_default_indexes => FALSE);\n\nINSERT INTO series VALUES ('2019-04-21 10:12', 1, 1.01);\n\nCREATE INDEX series_value ON series (value, time) TABLESPACE tablespace2;\n\nSELECT schemaname, tablename, indexname, tablespace\nFROM pg_indexes\nWHERE indexname LIKE '%series%'\nORDER BY indexname;\n\nALTER INDEX series_pk SET TABLESPACE tablespace2;\n\nCREATE INDEX ON series (time) TABLESPACE tablespace1;\n\nALTER INDEX series_value SET TABLESPACE pg_default;\n\nINSERT INTO series VALUES ('2019-04-29 10:12', 2, 1.31);\n\nSELECT schemaname, tablename, indexname, tablespace\nFROM pg_indexes\nWHERE indexname LIKE '%series%'\nORDER BY indexname;\n\nDROP TABLE series;\n\nDROP TABLESPACE tablespace1;\nDROP TABLESPACE tablespace2;\n\n-- Make sure we handle ALTER SCHEMA RENAME for hypertable schemas\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\nCREATE SCHEMA IF NOT EXISTS original_name;\nCREATE TABLE original_name.my_table (\n  date timestamp with time zone NOT NULL,\n  quantity double precision\n);\nSELECT create_hypertable('original_name.my_table','date');\n\nINSERT INTO original_name.my_table (date, quantity) VALUES ('2018-07-04T21:00:00+00:00', 8);\n\nALTER SCHEMA original_name RENAME TO new_name;\n\nDROP TABLE new_name.my_table;\nDROP SCHEMA new_name;\n\n-- Now make sure schema is renamed for multiple hypertables, but not hypertables not in the schema\nCREATE SCHEMA IF NOT EXISTS original_name;\nCREATE TABLE original_name.my_table (\n  date timestamp with time zone NOT NULL,\n  quantity double precision\n);\nCREATE TABLE original_name.my_table2 (\n  date timestamp with time zone NOT NULL,\n  quantity double precision\n);\nCREATE TABLE regular_table (\n  date timestamp with time zone NOT NULL,\n  quantity double precision\n);\nSELECT create_hypertable('original_name.my_table','date');\nSELECT create_hypertable('original_name.my_table2','date');\nSELECT create_hypertable('regular_table','date');\n\nINSERT INTO original_name.my_table (date, quantity) VALUES ('2018-07-04T21:00:00+00:00', 8);\nINSERT INTO original_name.my_table2 (date, quantity) VALUES ('2018-07-04T21:00:00+00:00', 8);\nINSERT INTO regular_table (date, quantity) VALUES ('2018-07-04T21:00:00+00:00', 8);\n\nALTER SCHEMA original_name RENAME TO new_name;\n\nDROP TABLE new_name.my_table;\nDROP TABLE new_name.my_table2;\nDROP TABLE regular_table;\nDROP SCHEMA new_name;\n\n-- These tables should also drop when we drop the whole schema\nCREATE SCHEMA IF NOT EXISTS original_name;\nCREATE TABLE original_name.my_table (\n  date timestamp with time zone NOT NULL,\n  quantity double precision\n);\nCREATE TABLE original_name.my_table2 (\n  date timestamp with time zone NOT NULL,\n  quantity double precision\n);\nSELECT create_hypertable('original_name.my_table','date');\nSELECT create_hypertable('original_name.my_table2','date');\n\nINSERT INTO original_name.my_table (date, quantity) VALUES ('2018-07-04T21:00:00+00:00', 8);\nINSERT INTO original_name.my_table2 (date, quantity) VALUES ('2018-07-04T21:00:00+00:00', 8);\n\nALTER SCHEMA original_name RENAME TO new_name;\n\nDROP SCHEMA new_name CASCADE;\nSELECT * FROM test.relation WHERE schema = 'new_name';\n\n-- Make sure we can't rename internal schemas\n\\set ON_ERROR_STOP 0\nALTER SCHEMA _timescaledb_internal RENAME TO my_new_schema_name;\nALTER SCHEMA _timescaledb_catalog RENAME TO my_new_schema_name;\nALTER SCHEMA _timescaledb_cache RENAME TO my_new_schema_name;\n\\set ON_ERROR_STOP 1\n\n-- Make sure we can rename associated schemas\nCREATE TABLE my_table (\n  date timestamp with time zone NOT NULL,\n  quantity double precision\n);\n\nSELECT create_hypertable('my_table','date', associated_schema_name => 'my_associated_schema');\nINSERT INTO my_table (date, quantity) VALUES ('2018-07-04T21:00:00+00:00', 8);\n\nALTER SCHEMA my_associated_schema RENAME TO new_associated_schema;\nINSERT INTO my_table (date, quantity) VALUES ('2018-08-10T23:00:00+00:00', 20);\n-- Make sure the schema name is changed in both catalog tables\nSELECT * from _timescaledb_catalog.hypertable;\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk from _timescaledb_catalog.chunk;\n\nDROP TABLE my_table;\n\n-- test renaming unique constraints/indexes\nCREATE TABLE t_hypertable ( id INTEGER NOT NULL, time TIMESTAMPTZ NOT NULL, value FLOAT NOT NULL CHECK (value > 0), UNIQUE(id, time));\nSELECT create_hypertable('t_hypertable', 'time');\n\nINSERT INTO t_hypertable AS h VALUES ( 1, '2020-01-01 00:00:00', 3.2) ON CONFLICT (id, time) DO UPDATE SET value = h.value + EXCLUDED.value;\nINSERT INTO t_hypertable AS h VALUES ( 1, '2021-01-01 00:00:00', 3.2) ON CONFLICT (id, time) DO UPDATE SET value = h.value + EXCLUDED.value;\n\nBEGIN;\nALTER INDEX t_hypertable_id_time_key RENAME TO t_new_constraint;\n\n-- chunk_constraint should have updated constraint names\nSELECT hypertable_constraint_name, constraint_name from _timescaledb_catalog.chunk_constraint WHERE hypertable_constraint_name = 't_new_constraint' ORDER BY 1,2;\n\nINSERT INTO t_hypertable AS h VALUES ( 1, '2020-01-01 00:01:00', 3.2) ON CONFLICT (id, time) DO UPDATE SET value = h.value + EXCLUDED.value;\nROLLBACK;\n\nBEGIN;\nALTER TABLE t_hypertable RENAME CONSTRAINT t_hypertable_id_time_key TO t_new_constraint;\n\n-- chunk_constraint should have updated constraint names\nSELECT hypertable_constraint_name, constraint_name from _timescaledb_catalog.chunk_constraint WHERE hypertable_constraint_name = 't_new_constraint' ORDER BY 1,2;\n\nINSERT INTO t_hypertable AS h VALUES ( 1, '2020-01-01 00:01:00', 3.2) ON CONFLICT (id, time) DO UPDATE SET value = h.value + EXCLUDED.value;\nROLLBACK;\n\n-- predicate reconstruction when attnos are different in hypertable and chunk\n\nCREATE TABLE p_hypertable (a integer not null, b integer, c integer);\nSELECT create_hypertable('p_hypertable', 'a', chunk_time_interval => int '3');\n\nBEGIN;\nALTER TABLE p_hypertable DROP COLUMN b, ADD COLUMN d boolean;\nCREATE INDEX idx_ht ON p_hypertable(a, c) WHERE d = FALSE;\nEND;\nINSERT INTO p_hypertable(a, c, d) VALUES (1, 1, FALSE);\n\n\\d _timescaledb_internal._hyper_14_28_chunk\n\nDROP TABLE p_hypertable;\n\n-- check none of our hooks interact badly with normal alter view handling\nCREATE VIEW v1 AS SELECT random();\n\\set ON_ERROR_STOP 0\n-- should error with unrecognized parameter\nALTER VIEW v1 SET (autovacuum_enabled = false);\n\\set ON_ERROR_STOP 1\n\n-- issue 4474\n-- test hypertable with non-default statistics target\n-- and chunk creation triggered by non-owner\nCREATE ROLE role_4474;\nCREATE TABLE i4474(time timestamptz NOT NULL);\nSELECT table_name FROM public.create_hypertable( 'i4474', 'time');\nGRANT SELECT, INSERT on i4474 TO role_4474;\n\n-- create chunk as owner\nINSERT INTO i4474 SELECT '2020-01-01';\n\n-- set statistics\nALTER TABLE i4474 ALTER COLUMN time SET statistics 10;\n\n-- create chunk as non-owner\nSET ROLE role_4474;\nINSERT INTO i4474 SELECT '2021-01-01';\nRESET ROLE;\nDROP TABLE i4474 CASCADE;\nDROP ROLE role_4474;\n\n-- verify that setting replica identity works and chunks inherit the\n-- root table's setting\nCREATE TABLE replid(time timestamptz, value int);\nSELECT create_hypertable('replid', 'time', chunk_time_interval => interval '1 day', create_default_indexes => false);\n\n-- replica identity set to default\nSELECT relreplident FROM pg_class WHERE relname = 'replid';\n\nINSERT INTO replid VALUES ('2023-01-01', 1);\n-- the new chunk should have the same replica identity setting\nSELECT relname, relreplident FROM show_chunks('replid') ch INNER JOIN pg_class c ON (ch = c.oid) ORDER BY relname;\n\n-- test change to replica identity full\nALTER TABLE replid REPLICA IDENTITY FULL;\nSELECT relname, relreplident FROM pg_class WHERE relname = 'replid' ORDER BY relname;\n-- the chunk's setting should also change to FULL\nSELECT relname, relreplident FROM show_chunks('replid') ch INNER JOIN pg_class c ON (ch = c.oid) ORDER BY relname;\n\n-- change to replica identity index\nCREATE UNIQUE INDEX time_key ON replid (time);\nALTER TABLE replid REPLICA IDENTITY USING INDEX time_key;\n\nSELECT relname, relreplident FROM pg_class WHERE relname = 'replid' ORDER BY relname;\nSELECT relname, relreplident FROM show_chunks('replid') ch INNER JOIN pg_class c ON (ch = c.oid) ORDER BY relname;\nSELECT indexrelid::regclass::text AS index_name\nFROM show_chunks('replid') chid\nINNER JOIN pg_index i ON (i.indrelid = chid) AND indisreplident=true\nORDER BY index_name;\n\nINSERT INTO replid VALUES ('2023-01-02', 2);\n\n-- the new chunk will also have replica identity \"index\"\nSELECT relname, relreplident FROM show_chunks('replid') ch INNER JOIN pg_class c ON (ch = c.oid) ORDER BY relname;\nSELECT indexrelid::regclass::text AS index_name\nFROM show_chunks('replid') chid\nINNER JOIN pg_index i ON (i.indrelid = chid) AND indisreplident=true\nORDER BY index_name;\n\n-- drop the replica identity index and create a new chunk. The new\n-- chunk should have replica identity \"NOTHING\" since this is the\n-- behavior of replica identity index when the index is dropped.\nDROP INDEX time_key;\nINSERT INTO replid VALUES ('2023-01-03', 3);\n\n-- no indexes left\nSELECT relname, relreplident FROM show_chunks('replid') ch INNER JOIN pg_class c ON (ch = c.oid) ORDER BY relname;\nSELECT indexrelid::regclass::text AS index_name\nFROM show_chunks('replid') chid\nINNER JOIN pg_index i ON (i.indrelid = chid) AND indisreplident=true\nORDER BY index_name;\n\n-- recreate the unique index after drop and insert to create a new chunk.\n-- This is a regression test for a bug where rd_replidindex was stale\n-- after relcache invalidation from chunk index creation, leading to\n-- \"could not open relation with OID 0\" error.\nCREATE UNIQUE INDEX time_key ON replid (time);\nINSERT INTO replid VALUES ('2023-01-04', 4);\n\nSELECT relname, relreplident FROM show_chunks('replid') ch INNER JOIN pg_class c ON (ch = c.oid) ORDER BY relname;\n\n-- Alter replica identity directly on a chunk is not supported\nSELECT ch AS chunk_name FROM show_chunks('replid') ch ORDER BY chunk_name LIMIT 1 \\gset\n\\set ON_ERROR_STOP 0\nALTER TABLE :chunk_name REPLICA IDENTITY FULL;\n\\set ON_ERROR_STOP 1\nSELECT relname, relreplident FROM show_chunks('replid') ch INNER JOIN pg_class c ON (ch = c.oid) ORDER BY relname;\n\n-- test implicit constraints gh issue #9132\nCREATE TABLE i9132(time timestamptz) WITH (tsdb.hypertable);\nINSERT INTO i9132 VALUES ('2024-01-01'), ('2024-02-02');\nALTER TABLE i9132 ADD COLUMN id serial, ADD CONSTRAINT implicit_pk PRIMARY KEY (id, time);\n\n"
  },
  {
    "path": "test/sql/alternate_users.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\ir include/insert_single.sql\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\n-- make sure tablespace1 exists\n-- since there is no CREATE TABLESPACE IF EXISTS we drop with if exists and recreate\nSET client_min_messages TO error;\nDROP TABLESPACE IF EXISTS tablespace1;\nRESET client_min_messages;\nCREATE TABLESPACE tablespace1 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE1_PATH;\n\n--needed for ddl ops:\nCREATE SCHEMA IF NOT EXISTS \"customSchema\" AUTHORIZATION :ROLE_DEFAULT_PERM_USER_2;\n\n--needed for ROLE_DEFAULT_PERM_USER_2 to write to the 'one_Partition' schema which\n--is owned by ROLE_DEFAULT_PERM_USER\nGRANT CREATE ON SCHEMA \"one_Partition\" TO :ROLE_DEFAULT_PERM_USER_2;\n\n--test creating and using schema as non-superuser\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\nSELECT * FROM test.relation WHERE schema='public' ORDER BY schema, name;\n\n\\set ON_ERROR_STOP 0\nSELECT * FROM \"one_Partition\";\nSELECT set_chunk_time_interval('\"one_Partition\"', 1::bigint);\nselect add_dimension('\"one_Partition\"', 'device_id', 2);\nselect attach_tablespace('tablespace1', '\"one_Partition\"');\n\\set ON_ERROR_STOP 1\n\nCREATE TABLE \"1dim\"(time timestamp, temp float);\nSELECT create_hypertable('\"1dim\"', 'time');\nINSERT INTO \"1dim\" VALUES('2017-01-20T09:00:01', 22.5);\nINSERT INTO \"1dim\" VALUES('2017-01-20T09:00:21', 21.2);\nINSERT INTO \"1dim\" VALUES('2017-01-20T09:00:47', 25.1);\nSELECT * FROM \"1dim\";\n\n\n\\ir include/ddl_ops_1.sql\n\\ir include/ddl_ops_2.sql\n\n--test proper denials for all security definer functions:\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE TABLE plain_table_su (time timestamp, temp float);\nCREATE TABLE hypertable_su (time timestamp, temp float);\nSELECT create_hypertable('hypertable_su', 'time');\nCREATE INDEX \"ind_1\" ON hypertable_su (time);\nINSERT INTO hypertable_su VALUES('2017-01-20T09:00:01', 22.5);\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n--all of the following should produce errors\n\\set ON_ERROR_STOP 0\nSELECT create_hypertable('plain_table_su', 'time');\nCREATE INDEX ON plain_table_su (time, temp);\nCREATE INDEX ON hypertable_su (time, temp);\nDROP INDEX \"ind_1\";\nALTER INDEX \"ind_1\" RENAME TO \"ind_2\";\n\\set ON_ERROR_STOP 1\n\n--test that I can't do anything to a non-owned hypertable.\n\\set ON_ERROR_STOP 0\nCREATE INDEX ON hypertable_su (time, temp);\nSELECT * FROM hypertable_su;\nINSERT INTO hypertable_su VALUES('2017-01-20T09:00:01', 22.5);\nALTER TABLE hypertable_su ADD COLUMN val2 integer;\n\\set ON_ERROR_STOP 1\n\n--grant read permissions\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nGRANT SELECT ON hypertable_su TO :ROLE_DEFAULT_PERM_USER_2;\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\nSELECT * FROM hypertable_su;\n\\set ON_ERROR_STOP 0\nCREATE INDEX ON hypertable_su (time, temp);\nINSERT INTO hypertable_su VALUES('2017-01-20T09:00:01', 22.5);\nALTER TABLE hypertable_su ADD COLUMN val2 integer;\n\\set ON_ERROR_STOP 1\n\n--grant read, insert permissions\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nGRANT SELECT, INSERT ON hypertable_su TO :ROLE_DEFAULT_PERM_USER_2;\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\nINSERT INTO hypertable_su VALUES('2017-01-20T09:00:01', 22.5);\nSELECT * FROM hypertable_su;\n\\set ON_ERROR_STOP 0\nCREATE INDEX ON hypertable_su (time, temp);\nALTER TABLE hypertable_su ADD COLUMN val2 integer;\n\\set ON_ERROR_STOP 1\n\n--change owner\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nALTER TABLE hypertable_su OWNER TO :ROLE_DEFAULT_PERM_USER_2;\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\nINSERT INTO hypertable_su VALUES('2017-01-20T09:00:01', 22.5);\nSELECT * FROM hypertable_su;\nCREATE INDEX ON hypertable_su (time, temp);\nALTER TABLE hypertable_su ADD COLUMN val2 integer;\n"
  },
  {
    "path": "test/sql/append.sql.in",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\set TEST_BASE_NAME append\nSELECT format('include/%s_load.sql', :'TEST_BASE_NAME') as \"TEST_LOAD_NAME\",\n       format('include/%s_query.sql', :'TEST_BASE_NAME') as \"TEST_QUERY_NAME\",\n       format('%s/results/%s_results_optimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_OPTIMIZED\",\n       format('%s/results/%s_results_unoptimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_UNOPTIMIZED\"\n\\gset\nSELECT format('\\! diff -u --label \"Unoptimized results\" --label \"Optimized results\" %s %s', :'TEST_RESULTS_UNOPTIMIZED', :'TEST_RESULTS_OPTIMIZED') as \"DIFF_CMD\"\n\\gset\n\nSET timescaledb.enable_now_constify TO false;\n\n-- disable memoize node to avoid flaky results\nSET enable_memoize TO 'off';\n\n-- disable index only scans to avoid some flaky results\nSET enable_indexonlyscan TO FALSE;\n\n\\set PREFIX 'EXPLAIN (analyze, buffers off, costs off, timing off, summary off)'\n\n\\ir :TEST_LOAD_NAME\n\\ir :TEST_QUERY_NAME\n\n--generate the results into two different files\n\\set ECHO errors\nSET client_min_messages TO error;\n\n\\set PREFIX ''\n\n-- get results with optimizations disabled\n\\o :TEST_RESULTS_UNOPTIMIZED\nSET timescaledb.enable_optimizations TO false;\n\\ir :TEST_QUERY_NAME\n\\o\n\n-- get query results with all optimizations\n\\o :TEST_RESULTS_OPTIMIZED\nSET timescaledb.enable_optimizations TO true;\n\\ir :TEST_QUERY_NAME\n\\o\n\n:DIFF_CMD\n\n-- get query results with constraint aware append\n\\o :TEST_RESULTS_OPTIMIZED\nSET timescaledb.enable_chunk_append TO false;\n\\ir :TEST_QUERY_NAME\n\\o\n\n:DIFF_CMD\n\n"
  },
  {
    "path": "test/sql/baserel_cache.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Test that the baserel cache is not clobbered if there's an error\n-- in a SQL function.\nCREATE TABLE valid_ids\n(\n  id UUID PRIMARY KEY\n);\n\nCREATE FUNCTION DEFAULT_UUID(TEXT DEFAULT '') RETURNS UUID AS $$\n  BEGIN\n    RETURN COALESCE($1, '')::UUID;\n  EXCEPTION WHEN invalid_text_representation THEN\n    RETURN '00000000-0000-0000-0000-000000000000';\n  END;\n$$ LANGUAGE PLPGSQL IMMUTABLE;\n\nCREATE FUNCTION KNOWN_ID(UUID, TEXT) RETURNS UUID AS $$\n  SELECT COALESCE(\n    (SELECT id FROM valid_ids WHERE id = $1),\n    DEFAULT_UUID()\n  );\n$$ LANGUAGE SQL;\n\nSELECT KNOWN_ID(NULL, ''), KNOWN_ID(NULL, '');\n"
  },
  {
    "path": "test/sql/bgw_launcher.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\set TEST_DBNAME_2 :TEST_DBNAME _2\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\n-- start bgw since they are stopped for tests by default\nSELECT _timescaledb_functions.start_background_workers();\n\nCREATE DATABASE :TEST_DBNAME_2;\n\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\n\n\\ir include/bgw_launcher_utils.sql\n\n-- When we've connected to test db 2, we should be able to see the cluster launcher\n-- and the scheduler for test db in pg_stat_activity\n-- but test db 2 shouldn't have a scheduler because ext not created yet\nSELECT wait_worker_counts(1,1,0,0);\n\n-- Now create the extension in test db 2\nSET client_min_messages = ERROR;\nCREATE EXTENSION timescaledb CASCADE;\nRESET client_min_messages;\nSELECT wait_worker_counts(1,1,1,0);\n\nDROP DATABASE :TEST_DBNAME WITH (FORCE);\n\n-- Now the db_scheduler for test db should have disappeared\nSELECT wait_worker_counts(1,0,1,0);\n\n-- Now let's restart the scheduler in test db 2 and make sure our backend_start changed\nSELECT backend_start as orig_backend_start\nFROM pg_stat_activity\nWHERE backend_type = 'TimescaleDB Background Worker Scheduler'\nAND datname = :'TEST_DBNAME_2' \\gset\n-- We'll do this in a txn so that we can see that the worker locks on our txn before continuing\nBEGIN;\nSELECT _timescaledb_functions.restart_background_workers();\nSELECT wait_worker_counts(1,0,1,0);\n\nSELECT (backend_start > :'orig_backend_start'::timestamptz) backend_start_changed,\n(wait_event = 'virtualxid') wait_event_changed\nFROM pg_stat_activity\nWHERE backend_type = 'TimescaleDB Background Worker Scheduler'\nAND datname = :'TEST_DBNAME_2';\nCOMMIT;\n\nSELECT wait_worker_counts(1,0,1,0);\nSELECT (wait_event IS DISTINCT FROM 'virtualxid') wait_event_changed\nFROM pg_stat_activity\nWHERE backend_type = 'TimescaleDB Background Worker Scheduler'\nAND datname = :'TEST_DBNAME_2';\n\n-- Test stop\nSELECT _timescaledb_functions.stop_background_workers();\nSELECT wait_worker_counts(1,0,0,0);\n\n-- Make sure it doesn't break if we stop twice in a row\nSELECT _timescaledb_functions.stop_background_workers();\nSELECT wait_worker_counts(1,0,0,0);\n\n-- test start\nSELECT _timescaledb_functions.start_background_workers();\nSELECT wait_worker_counts(1,0,1,0);\n\n-- make sure start is idempotent\nSELECT backend_start as orig_backend_start\nFROM pg_stat_activity\nWHERE backend_type = 'TimescaleDB Background Worker Scheduler'\nAND datname = :'TEST_DBNAME_2' \\gset\n\n-- Since we're doing idempotency tests, we're also going to exercise our queue and start 20 times\nSELECT _timescaledb_functions.start_background_workers() as start_background_workers, * FROM generate_series(1,20);\n-- Here we're waiting to see if something shows up in pg_stat_activity,\n--  so we have to condition our loop in the opposite way. We'll only wait\n--  half a second in total as well so that tests don't take too long.\nCREATE FUNCTION wait_equals(TIMESTAMPTZ, TEXT) RETURNS BOOLEAN LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\nr BOOLEAN;\nBEGIN\nFOR i in 1..5\nLOOP\nSELECT (backend_start = $1::timestamptz) backend_start_unchanged\nFROM pg_stat_activity\nWHERE backend_type = 'TimescaleDB Background Worker Scheduler'\nAND datname = $2 into r;\nif(r) THEN\n  PERFORM pg_sleep(0.1);\n  PERFORM pg_stat_clear_snapshot();\nELSE\n  RETURN FALSE;\nEND IF;\nEND LOOP;\nRETURN TRUE;\nEND\n$BODY$;\nselect wait_equals(:'orig_backend_start', :'TEST_DBNAME_2');\n\n-- Make sure restart starts a worker even if it is stopped\nSELECT _timescaledb_functions.stop_background_workers();\nSELECT wait_worker_counts(1,0,0,0);\nSELECT _timescaledb_functions.restart_background_workers();\nSELECT wait_worker_counts(1,0,1,0);\n\n-- Make sure drop extension statement restarts the worker and on rollback it keeps running\n-- Now let's restart the scheduler and make sure our backend_start changed\nSELECT backend_start as orig_backend_start\nFROM pg_stat_activity\nWHERE backend_type = 'TimescaleDB Background Worker Scheduler'\nAND datname = :'TEST_DBNAME_2' \\gset\n\nBEGIN;\nDROP EXTENSION timescaledb;\nSELECT wait_worker_counts(1,0,1,0);\nROLLBACK;\n\nCREATE FUNCTION wait_greater(TIMESTAMPTZ,TEXT) RETURNS BOOLEAN LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\nr BOOLEAN;\nBEGIN\nFOR i in 1..10\nLOOP\nSELECT (backend_start > $1::timestamptz) backend_start_changed\nFROM pg_stat_activity\nWHERE backend_type = 'TimescaleDB Background Worker Scheduler'\nAND datname = $2 into r;\nif(NOT r) THEN\n  PERFORM pg_sleep(0.1);\n  PERFORM pg_stat_clear_snapshot();\nELSE\n  RETURN TRUE;\nEND IF;\nEND LOOP;\nRETURN FALSE;\nEND\n$BODY$;\nSELECT wait_greater(:'orig_backend_start',:'TEST_DBNAME_2');\n\n-- Make sure canceling the launcher backend causes a restart of schedulers\nSELECT backend_start as orig_backend_start\nFROM pg_stat_activity\nWHERE backend_type = 'TimescaleDB Background Worker Scheduler'\nAND datname = :'TEST_DBNAME_2' \\gset\n\nSELECT pg_cancel_backend(pid) FROM pg_stat_activity WHERE backend_type = 'TimescaleDB Background Worker Launcher';\n\nSELECT wait_worker_counts(1,0,1,0);\n\nSELECT wait_greater(:'orig_backend_start', :'TEST_DBNAME_2');\n\n-- Make sure running pre_restore function stops background workers\nSELECT timescaledb_pre_restore();\nSELECT wait_worker_counts(1,0,0,0);\n-- Make sure a restart with restoring on first starts the background worker\nBEGIN;\nSELECT _timescaledb_functions.restart_background_workers();\nSELECT wait_worker_counts(1,0,1,0);\nCOMMIT;\n-- Then the worker dies when it sees that restoring is on after the txn commits\nSELECT wait_worker_counts(1,0,0,0);\n\n--And post_restore starts them\nBEGIN;\nSELECT timescaledb_post_restore();\nSELECT wait_worker_counts(1,0,1,0);\nCOMMIT;\n-- And they stay started\nSELECT wait_worker_counts(1,0,1,0);\n\n-- Make sure dropping the extension means that the scheduler is stopped\nBEGIN;\nDROP EXTENSION timescaledb;\nCOMMIT;\nSELECT wait_worker_counts(1,0,0,0);\n\n-- Test that background workers are stopped with DROP OWNED\nALTER ROLE :ROLE_DEFAULT_PERM_USER WITH SUPERUSER;\n\\c :TEST_DBNAME_2 :ROLE_DEFAULT_PERM_USER\nSET client_min_messages = ERROR;\nCREATE EXTENSION timescaledb CASCADE;\nRESET client_min_messages;\n-- Make sure there is 1 launcher and 1 bgw in test db 2\nSELECT wait_worker_counts(launcher_ct=>1, scheduler1_ct=> 0, scheduler2_ct=>1, template1_ct=>0);\n-- drop a non-owner of the extension results in no change to worker counts\nDROP OWNED BY :ROLE_DEFAULT_PERM_USER_2;\nSELECT wait_worker_counts(launcher_ct=>1, scheduler1_ct=> 0, scheduler2_ct=>1, template1_ct=>0);\n-- drop of owner of extension results in extension drop and a stop to the bgw\nDROP OWNED BY :ROLE_DEFAULT_PERM_USER;\n-- The worker in test db 2 is dead. Note that 0s are respected\nSELECT wait_worker_counts(launcher_ct=>1, scheduler1_ct=>0, scheduler2_ct=>0, template1_ct=>0);\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\nALTER ROLE :ROLE_DEFAULT_PERM_USER WITH NOSUPERUSER;\n\n-- Connect to the template1 database\n\\c template1\n\\ir include/bgw_launcher_utils.sql\nBEGIN;\n-- Then create extension there in a txn and make sure we see a scheduler start\n\nSET client_min_messages = ERROR;\nCREATE EXTENSION timescaledb CASCADE;\nRESET client_min_messages;\nSELECT wait_worker_counts(1,0,0,1);\nCOMMIT;\n-- End our transaction and it should immediately exit because it's a template database.\nSELECT wait_worker_counts(1,0,0,0);\n\n\\c :TEST_DBNAME_2\n-- Now try creating a DB from a template with the extension already installed.\n-- Make sure we see a scheduler start.\nCREATE DATABASE :TEST_DBNAME;\nSELECT wait_worker_counts(1,1,0,0);\nDROP DATABASE :TEST_DBNAME WITH (FORCE);\n-- Now make sure that there's no race between create database and create extension.\n-- Although to be honest, this race probably wouldn't manifest in this test.\n\\c template1\nDROP EXTENSION timescaledb;\n\\c :TEST_DBNAME_2\nCREATE DATABASE :TEST_DBNAME;\n\\c :TEST_DBNAME\nSET client_min_messages = ERROR;\nCREATE EXTENSION timescaledb;\nRESET client_min_messages;\n\\c :TEST_DBNAME_2\nSELECT wait_worker_counts(1,1,0,0);\n\n-- test rename database\nCREATE DATABASE db_rename_test;\n\n\\c db_rename_test :ROLE_SUPERUSER\nSET client_min_messages=error;\nCREATE EXTENSION timescaledb;\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\nSELECT wait_for_bgw_scheduler('db_rename_test');\nALTER DATABASE db_rename_test RENAME TO db_rename_test2;\nDROP DATABASE db_rename_test2 WITH (FORCE);\n\n-- test create database with timescaledb database as template\nSELECT wait_for_bgw_scheduler(:'TEST_DBNAME');\nCREATE DATABASE db_from_template WITH TEMPLATE :TEST_DBNAME;\nSELECT wait_for_bgw_scheduler(:'TEST_DBNAME');\nDROP DATABASE db_from_template WITH (FORCE);\n\n-- test alter database set tablespace\nSET client_min_messages TO error;\nDROP TABLESPACE IF EXISTS tablespace1;\nRESET client_min_messages;\nCREATE TABLESPACE tablespace1 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE1_PATH;\n\n-- Stop background worker before we change the tablespace of the database (otherwise, the database might be used)\nSELECT wait_for_bgw_scheduler(:'TEST_DBNAME');\n\n-- Connect to TEST_DBNAME (_timescaledb_functions.stop_background_workers() is not available in TEST_DBNAME_2)\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT _timescaledb_functions.stop_background_workers();\nSELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE backend_type = 'TimescaleDB Background Worker Launcher';\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\n-- make sure nobody is using it\nREVOKE CONNECT ON DATABASE :TEST_DBNAME FROM public;\nCALL kill_database_backends(:'TEST_DBNAME');\n\n-- Change tablespace\nALTER DATABASE :TEST_DBNAME SET TABLESPACE tablespace1;\n\n-- tear down test and clean up additional database\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\nSELECT _timescaledb_functions.stop_background_workers() \\gset\nREVOKE CONNECT ON DATABASE :TEST_DBNAME_2 FROM public;\nCALL kill_database_backends(:'TEST_DBNAME_2');\nSELECT * FROM pg_stat_activity WHERE datname = :'TEST_DBNAME_2';\n\nDROP DATABASE :TEST_DBNAME_2 WITH (force);\n\n-- Clean up the template database, removing our test utilities etc\n\\c template1\n\\ir include/bgw_launcher_utils_cleanup.sql\n"
  },
  {
    "path": "test/sql/c_unit_tests.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION test.time_to_internal_conversion() RETURNS VOID\nAS :MODULE_PATHNAME, 'ts_test_time_to_internal_conversion' LANGUAGE C VOLATILE;\n\nCREATE OR REPLACE FUNCTION test.interval_to_internal_conversion() RETURNS VOID\nAS :MODULE_PATHNAME, 'ts_test_interval_to_internal_conversion' LANGUAGE C VOLATILE;\n\nCREATE OR REPLACE FUNCTION test.adts() RETURNS VOID\nAS :MODULE_PATHNAME, 'ts_test_adts' LANGUAGE C VOLATILE;\n\nCREATE OR REPLACE FUNCTION test.time_utils() RETURNS VOID\nAS :MODULE_PATHNAME, 'ts_test_time_utils' LANGUAGE C;\n\nCREATE OR REPLACE FUNCTION test.bmslist_utils() RETURNS VOID\nAS :MODULE_PATHNAME, 'ts_test_bmslist_utils' LANGUAGE C;\n\nCREATE OR REPLACE FUNCTION test.jsonb_utils() RETURNS VOID\nAS :MODULE_PATHNAME, 'ts_test_jsonb_utils' LANGUAGE C;\n\nCREATE OR REPLACE FUNCTION test.compression_settings() RETURNS VOID\nAS :MODULE_PATHNAME, 'ts_test_compression_settings' LANGUAGE C;\n\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n\nSELECT test.time_to_internal_conversion();\nSELECT test.interval_to_internal_conversion();\nSELECT test.adts();\nSELECT test.time_utils();\nSELECT test.bmslist_utils();\nSELECT test.jsonb_utils();\nSELECT test.compression_settings();\n\n"
  },
  {
    "path": "test/sql/catalog_corruption.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\n--- Test handling of missing dimension slices\nCREATE TABLE dim_test(time TIMESTAMPTZ, device int);\nSELECT create_hypertable('dim_test', 'time', chunk_time_interval => INTERVAL '1 day');\n\n-- Create two chunks\nINSERT INTO dim_test values('2000-01-01 00:00:00', 1);\nINSERT INTO dim_test values('2020-01-01 00:00:00', 1);\n\nSELECT id AS dim_slice_id FROM _timescaledb_catalog.dimension_slice\n  ORDER BY id DESC LIMIT 1\n  \\gset\n\n-- Delete the dimension slice for the second chunk\nDELETE FROM _timescaledb_catalog.chunk_constraint WHERE dimension_slice_id = :dim_slice_id;\n\n\\set ON_ERROR_STOP 0\n\n-- Select data\nSELECT * FROM dim_test;\n\n-- Select data using ordered append\nSELECT * FROM dim_test ORDER BY time;\n\n\\set ON_ERROR_STOP 1\n\nDROP TABLE dim_test;\n\n"
  },
  {
    "path": "test/sql/chunk_adaptive.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- test error handling _timescaledb_functions.calculate_chunk_interval\n\\set ON_ERROR_STOP 0\nSELECT _timescaledb_functions.calculate_chunk_interval(0,0,-0);\nSELECT _timescaledb_functions.calculate_chunk_interval(1,0,-1);\n\\set ON_ERROR_STOP 1\n\n-- Valid chunk sizing function for testing\nCREATE OR REPLACE FUNCTION calculate_chunk_interval(\n        dimension_id INTEGER,\n        dimension_coord BIGINT,\n        chunk_target_size BIGINT\n)\n    RETURNS BIGINT LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\nBEGIN\n    RETURN -1;\nEND\n$BODY$;\n\n-- Chunk sizing function with bad signature\nCREATE OR REPLACE FUNCTION bad_calculate_chunk_interval(\n        dimension_id INTEGER\n)\n    RETURNS BIGINT LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\nBEGIN\n    RETURN -1;\nEND\n$BODY$;\n\n-- Set a fixed memory cache size to make tests determinstic\n-- (independent of available machine memory)\nSELECT * FROM test.set_memory_cache_size('2GB');\n\n-- test NULL handling\n\\set ON_ERROR_STOP 0\nSELECT * FROM set_adaptive_chunking(NULL,NULL);\n\\set ON_ERROR_STOP 1\n\nCREATE TABLE test_adaptive(time timestamptz, temp float, location int);\n\n\\set ON_ERROR_STOP 0\n-- Bad signature of sizing func should fail\nSELECT create_hypertable('test_adaptive', 'time',\n                         chunk_target_size => '1MB',\n                         chunk_sizing_func => 'bad_calculate_chunk_interval');\n\\set ON_ERROR_STOP 1\n\n-- Setting sizing func with correct signature should work\nSELECT create_hypertable('test_adaptive', 'time',\n                         chunk_target_size => '1MB',\n                         chunk_sizing_func => 'calculate_chunk_interval');\n\nDROP TABLE test_adaptive;\nCREATE TABLE test_adaptive(time timestamptz, temp float, location int);\n\n-- Size but no explicit func should use default func\nSELECT create_hypertable('test_adaptive', 'time',\n                         chunk_target_size => '1MB',\n                         create_default_indexes => true);\nSELECT table_name, chunk_sizing_func_schema, chunk_sizing_func_name, chunk_target_size\nFROM _timescaledb_catalog.hypertable;\n\n-- Check that adaptive chunking sets a 1 day default chunk time\n-- interval => 86400000000 microseconds\nSELECT * FROM _timescaledb_catalog.dimension;\n\n-- Change the target size\nSELECT * FROM set_adaptive_chunking('test_adaptive', '2MB');\nSELECT table_name, chunk_sizing_func_schema, chunk_sizing_func_name, chunk_target_size\nFROM _timescaledb_catalog.hypertable;\n\n\\set ON_ERROR_STOP 0\n-- Setting NULL func should fail\nSELECT * FROM set_adaptive_chunking('test_adaptive', '1MB', NULL);\n\\set ON_ERROR_STOP 1\n\n-- Setting NULL size disables adaptive chunking\nSELECT * FROM set_adaptive_chunking('test_adaptive', NULL);\nSELECT table_name, chunk_sizing_func_schema, chunk_sizing_func_name, chunk_target_size\nFROM _timescaledb_catalog.hypertable;\n\nSELECT * FROM set_adaptive_chunking('test_adaptive', '1MB');\n\n-- Setting size to 'off' should also disable\nSELECT * FROM set_adaptive_chunking('test_adaptive', 'off');\nSELECT table_name, chunk_sizing_func_schema, chunk_sizing_func_name, chunk_target_size\nFROM _timescaledb_catalog.hypertable;\n\n-- Setting 0 size should also disable\nSELECT * FROM set_adaptive_chunking('test_adaptive', '0MB');\nSELECT table_name, chunk_sizing_func_schema, chunk_sizing_func_name, chunk_target_size\nFROM _timescaledb_catalog.hypertable;\n\nSELECT * FROM set_adaptive_chunking('test_adaptive', '1MB');\n\n-- No warning about small target size if > 10MB\nSELECT * FROM set_adaptive_chunking('test_adaptive', '11MB');\n\n-- Setting size to 'estimate' should also estimate size\nSELECT * FROM set_adaptive_chunking('test_adaptive', 'estimate');\nSELECT table_name, chunk_sizing_func_schema, chunk_sizing_func_name, chunk_target_size\nFROM _timescaledb_catalog.hypertable;\n\n-- Use a lower memory setting to test that the calculated chunk_target_size is reduced\nSELECT * FROM test.set_memory_cache_size('512MB');\nSELECT * FROM set_adaptive_chunking('test_adaptive', 'estimate');\nSELECT table_name, chunk_sizing_func_schema, chunk_sizing_func_name, chunk_target_size\nFROM _timescaledb_catalog.hypertable;\n\n-- Reset memory settings\nSELECT * FROM test.set_memory_cache_size('2GB');\n\n-- Set a reasonable test value\nSELECT * FROM set_adaptive_chunking('test_adaptive', '1MB');\n\n-- Show the interval length before and after adaptation\nSELECT id, hypertable_id, interval_length FROM _timescaledb_catalog.dimension;\n\n-- Generate data to create chunks. We use the hash of the time value\n-- to get determinstic location IDs so that we always spread these\n-- values the same way across space partitions\nINSERT INTO test_adaptive\nSELECT time, random() * 35, _timescaledb_functions.get_partition_hash(time) FROM\ngenerate_series('2017-03-07T18:18:03+00'::timestamptz - interval '175 days',\n                '2017-03-07T18:18:03+00'::timestamptz,\n                '2 minutes') as time;\n\nSELECT chunk_name, primary_dimension, range_start, range_end\nFROM  timescaledb_information.chunks\nWHERE hypertable_name = 'test_adaptive' ORDER BY chunk_name;\n\n-- Do same thing without an index on the time column. This affects\n-- both the calculation of fill-factor of the chunk and its size\nCREATE TABLE test_adaptive_no_index(time timestamptz, temp float, location int);\n\n-- Size but no explicit func should use default func\n-- No default indexes should warn and use heap scan for min and max\nSELECT create_hypertable('test_adaptive_no_index', 'time',\n                         chunk_target_size => '1MB',\n                         create_default_indexes => false);\nSELECT id, hypertable_id, interval_length FROM _timescaledb_catalog.dimension;\n\nINSERT INTO test_adaptive_no_index\nSELECT time, random() * 35, _timescaledb_functions.get_partition_hash(time) FROM\ngenerate_series('2017-03-07T18:18:03+00'::timestamptz - interval '175 days',\n                '2017-03-07T18:18:03+00'::timestamptz,\n                '2 minutes') as time;\nSELECT chunk_name, primary_dimension, range_start, range_end\nFROM  timescaledb_information.chunks\nWHERE hypertable_name = 'test_adaptive_no_index' ORDER BY chunk_name;\n\n-- Test added to check that the correct index (i.e. time index) is being used\n-- to find the min and max. Previously a bug selected the first index listed,\n-- which in this case is location rather than time and therefore could return\n-- the wrong min and max if items at the start and end of the index did not have\n-- the correct min and max timestamps.\n--\n-- In this test, we create chunks with a lot of locations with only one reading\n-- that is at the beginning of the time frame, and then one location in the middle\n-- of the range that has two readings, one that is the same as the others and one\n-- that is larger. The algorithm should use these two readings for min & max; however,\n-- if it's broken (as it was before), it would choose just the reading that is common\n-- to all the locations.\nCREATE TABLE test_adaptive_correct_index(time timestamptz, temp float, location int);\nSELECT create_hypertable('test_adaptive_correct_index', 'time',\n                         chunk_target_size => '100MB',\n                         chunk_time_interval => 86400000000,\n                         create_default_indexes => false);\nCREATE INDEX ON test_adaptive_correct_index(location);\nCREATE INDEX ON test_adaptive_correct_index(time DESC);\n\n-- First chunk\nINSERT INTO test_adaptive_correct_index\nSELECT '2018-01-01T00:00:00+00'::timestamptz, val, val + 1 FROM\ngenerate_series(1, 1000) as val;\nINSERT INTO test_adaptive_correct_index\nSELECT time, 0.0, '1500' FROM\ngenerate_series('2018-01-01T00:00:00+00'::timestamptz,\n                '2018-01-01T20:00:00+00'::timestamptz,\n                '10 hours') as time;\nINSERT INTO test_adaptive_correct_index\nSELECT '2018-01-01T00:00:00+00'::timestamptz, val, val + 1 FROM\ngenerate_series(2001, 3000) as val;\n\n-- Second chunk\nINSERT INTO test_adaptive_correct_index\nSELECT '2018-01-02T00:00:00+00'::timestamptz, val, val + 1 FROM\ngenerate_series(1, 1000) as val;\nINSERT INTO test_adaptive_correct_index\nSELECT time, 0.0, '1500' FROM\ngenerate_series('2018-01-02T00:00:00+00'::timestamptz,\n                '2018-01-02T20:00:00+00'::timestamptz,\n                '10 hours') as time;\nINSERT INTO test_adaptive_correct_index\nSELECT '2018-01-02T00:00:00+00'::timestamptz, val, val + 1 FROM\ngenerate_series(2001, 3000) as val;\n\n-- Third chunk\nINSERT INTO test_adaptive_correct_index\nSELECT '2018-01-03T00:00:00+00'::timestamptz, val, val + 1 FROM\ngenerate_series(1, 1000) as val;\nINSERT INTO test_adaptive_correct_index\nSELECT time, 0.0, '1500' FROM\ngenerate_series('2018-01-03T00:00:00+00'::timestamptz,\n                '2018-01-03T20:00:00+00'::timestamptz,\n                '10 hours') as time;\nINSERT INTO test_adaptive_correct_index\nSELECT '2018-01-03T00:00:00+00'::timestamptz, val, val + 1 FROM\ngenerate_series(2001, 3000) as val;\n\n-- This should be the start of the fourth chunk\nINSERT INTO test_adaptive_correct_index\nSELECT '2018-01-04T00:00:00+00'::timestamptz, val, val + 1 FROM\ngenerate_series(1, 1000) as val;\nINSERT INTO test_adaptive_correct_index\nSELECT time, 0.0, '1500' FROM\ngenerate_series('2018-01-04T00:00:00+00'::timestamptz,\n                '2018-01-04T20:00:00+00'::timestamptz,\n                '10 hours') as time;\nINSERT INTO test_adaptive_correct_index\nSELECT '2018-01-04T00:00:00+00'::timestamptz, val, val + 1 FROM\ngenerate_series(2001, 3000) as val;\n\n-- If working correctly, this goes in the 4th chunk, otherwise its a separate 5th chunk\nINSERT INTO test_adaptive_correct_index\nSELECT '2018-01-05T00:00:00+00'::timestamptz, val, val + 1 FROM\ngenerate_series(1, 1000) as val;\nINSERT INTO test_adaptive_correct_index\nSELECT time, 0.0, '1500' FROM\ngenerate_series('2018-01-05T00:00:00+00'::timestamptz,\n                '2018-01-05T20:00:00+00'::timestamptz,\n                '10 hours') as time;\nINSERT INTO test_adaptive_correct_index\nSELECT '2018-01-05T00:00:00+00'::timestamptz, val, val + 1 FROM\ngenerate_series(2001, 3000) as val;\n\n-- This should show 4 chunks, rather than 5\nSELECT count(*)\nFROM  timescaledb_information.chunks\nWHERE hypertable_name = 'test_adaptive_correct_index';\n-- The interval_length should no longer be 86400000000 for our hypertable, so 3rd column so be true.\n-- Note: the exact interval_length is non-deterministic, so we can't use its actual value for tests\nSELECT id, hypertable_id, interval_length > 86400000000 FROM _timescaledb_catalog.dimension;\n\n-- Drop because it's size and estimated chunk_interval is non-deterministic so\n-- we don't want to make other tests flaky.\nDROP TABLE test_adaptive_correct_index;\n\n-- Test with space partitioning. This might affect the estimation\n-- since there are more chunks in the same time interval and space\n-- chunks might be unevenly filled.\nCREATE TABLE test_adaptive_space(time timestamptz, temp float, location int);\nSELECT create_hypertable('test_adaptive_space', 'time', 'location', 2,\n                         chunk_target_size => '1MB',\n                         create_default_indexes => true);\n\nSELECT id, hypertable_id, interval_length FROM _timescaledb_catalog.dimension;\n\nINSERT INTO test_adaptive_space\nSELECT time, random() * 35, _timescaledb_functions.get_partition_hash(time) FROM\ngenerate_series('2017-03-07T18:18:03+00'::timestamptz - interval '175 days',\n                '2017-03-07T18:18:03+00'::timestamptz,\n                '2 minutes') as time;\n\n\\x\nSELECT chunk_name, range_start, range_end\nFROM  timescaledb_information.chunks\nWHERE hypertable_name = 'test_adaptive_space' ORDER BY chunk_name;\nSELECT *\nFROM  timescaledb_information.dimensions\nWHERE hypertable_name = 'test_adaptive_space' ORDER BY dimension_number;\n\\x\nSELECT *\nFROM chunks_detailed_size('test_adaptive_space') ORDER BY chunk_name;\nSELECT id, hypertable_id, interval_length FROM _timescaledb_catalog.dimension;\n\n-- A previous version stopped working as soon as hypertable_id stopped being\n-- equal to dimension_id (i.e., there was a hypertable with more than 1 dimension).\n-- This test comes after test_adaptive_space, which has 2 dimensions, and makes\n-- sure that it still works.\nCREATE TABLE test_adaptive_after_multiple_dims(time timestamptz, temp float, location int);\nSELECT create_hypertable('test_adaptive_after_multiple_dims', 'time',\n                         chunk_target_size => '100MB',\n                         create_default_indexes => true);\nINSERT INTO test_adaptive_after_multiple_dims VALUES('2018-01-01T00:00:00+00'::timestamptz, 0.0, 5);\n\n\\c  :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n\\set ON_ERROR_STOP 0\nSELECT * FROM set_adaptive_chunking('test_adaptive', '2MB');\n\\set ON_ERROR_STOP 1\n\n\\c  :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n-- Now make sure renaming schema gets propagated to the func_schema\nDROP TABLE test_adaptive;\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA IF NOT EXISTS my_chunk_func_schema;\n\nCREATE OR REPLACE FUNCTION my_chunk_func_schema.calculate_chunk_interval(\n        dimension_id INTEGER,\n        dimension_coord BIGINT,\n        chunk_target_size BIGINT\n)\n    RETURNS BIGINT LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\nBEGIN\n    RETURN 2;\nEND\n$BODY$;\n\nCREATE TABLE test_adaptive(time timestamptz, temp float, location int);\nSELECT create_hypertable('test_adaptive', 'time',\n                         chunk_target_size => '1MB',\n                         chunk_sizing_func => 'my_chunk_func_schema.calculate_chunk_interval');\n\n\nALTER SCHEMA my_chunk_func_schema RENAME TO new_chunk_func_schema;\nINSERT INTO test_adaptive VALUES (now(), 1.0, 1);\n"
  },
  {
    "path": "test/sql/chunk_publication.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Test automatic addition of chunks to publications\n-- Publications require superuser privileges\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\nSET client_min_messages = WARNING;\nSET timescaledb.enable_chunk_auto_publication = true;\n\n-- Test 1: Basic single publication\nCREATE TABLE test_hypertable (time timestamptz NOT NULL, device_id int, value float, extra text);\nSELECT create_hypertable('test_hypertable', 'time', chunk_time_interval => interval '1 day');\n\n-- Insert to create first chunk\nINSERT INTO test_hypertable VALUES ('2024-01-01 00:00:00+00', 1, 1.0, 'data1');\n\n-- Create publication and add hypertable\nCREATE PUBLICATION test_pub FOR TABLE test_hypertable;\n\n-- Verify initial state (1 chunk)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub' ORDER BY schemaname, tablename;\n\n-- Insert to create 2 more chunks (total 3 chunks)\nINSERT INTO test_hypertable VALUES ('2024-01-02 00:00:00+00', 2, 2.0, 'data2');\nINSERT INTO test_hypertable VALUES ('2024-01-03 00:00:00+00', 3, 3.0, 'data3');\n\n-- Verify state (3 chunks)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub' ORDER BY schemaname, tablename;\n\n-- Insert to create 5 more chunks (total 8 chunks)\nINSERT INTO test_hypertable VALUES ('2024-01-04 00:00:00+00', 4, 4.0, 'data4');\nINSERT INTO test_hypertable VALUES ('2024-01-05 00:00:00+00', 5, 5.0, 'data5');\nINSERT INTO test_hypertable VALUES ('2024-01-06 00:00:00+00', 6, 6.0, 'data6');\nINSERT INTO test_hypertable VALUES ('2024-01-07 00:00:00+00', 7, 7.0, 'data7');\nINSERT INTO test_hypertable VALUES ('2024-01-08 00:00:00+00', 8, 8.0, 'data8');\n\n-- Verify final state (8 chunks)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub' ORDER BY schemaname, tablename;\n\n-- Verify chunk removal via DROP TABLE\nSELECT chunk_schema || '.' || chunk_name as \"CHUNK_TO_DROP\"\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'test_hypertable'\nORDER BY chunk_schema, chunk_name LIMIT 1 \\gset\n\n-- Verify chunk removal via DROP TABLE\nDROP TABLE :CHUNK_TO_DROP;\n\n-- Verify chunk was removed from publication (7 chunks remaining)\nSELECT chunk_schema, chunk_name\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'test_hypertable'\nORDER BY chunk_schema, chunk_name;\n\nSELECT schemaname, tablename, attnames, rowfilter\nFROM pg_publication_tables\nWHERE pubname = 'test_pub'\nORDER BY schemaname, tablename;\n\n-- Verify chunk removal via drop_chunks()\nSELECT drop_chunks('test_hypertable', older_than => '2024-01-07 00:00:00+00'::timestamptz);\n\n-- Verify dropped chunks were removed from publication (2 chunks remaining: 2024-01-07 and 2024-01-08)\nSELECT chunk_schema, chunk_name\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'test_hypertable'\nORDER BY chunk_schema, chunk_name;\n\nSELECT schemaname, tablename, attnames, rowfilter\nFROM pg_publication_tables\nWHERE pubname = 'test_pub'\nORDER BY schemaname, tablename;\n\n-- Verify chunk removal via TRUNCATE\nTRUNCATE TABLE test_hypertable;\n\n-- Verify all chunks were removed from publication (0 chunks remaining)\nSELECT chunk_schema, chunk_name\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'test_hypertable'\nORDER BY chunk_schema, chunk_name;\n\nSELECT schemaname, tablename, attnames, rowfilter\nFROM pg_publication_tables\nWHERE pubname = 'test_pub'\nORDER BY schemaname, tablename;\n\n-- Cleanup\nDROP PUBLICATION test_pub CASCADE;\nDROP TABLE test_hypertable CASCADE;\n\n-- Test 2: Multiple publications\nCREATE TABLE test_hypertable (time timestamptz NOT NULL, device_id int, value float, extra text);\nSELECT create_hypertable('test_hypertable', 'time', chunk_time_interval => interval '1 day');\n\n-- Insert to create first chunk\nINSERT INTO test_hypertable VALUES ('2024-01-01 00:00:00+00', 1, 1.0, 'data1');\n\n-- Create pub1 and add hypertable\nCREATE PUBLICATION test_pub1 FOR TABLE test_hypertable;\n\n-- Verify (1 chunk in pub1)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub1' ORDER BY schemaname, tablename;\n\n-- Insert to create 2 more chunks (total 3 chunks)\nINSERT INTO test_hypertable VALUES ('2024-01-02 00:00:00+00', 2, 2.0, 'data2');\nINSERT INTO test_hypertable VALUES ('2024-01-03 00:00:00+00', 3, 3.0, 'data3');\n\n-- Verify (3 chunks in pub1)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub1' ORDER BY schemaname, tablename;\n\n-- Create pub2 and add hypertable\nCREATE PUBLICATION test_pub2 FOR TABLE test_hypertable;\n\n-- Verify (3 chunks in pub1, 3 chunks in pub2)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub1' ORDER BY schemaname, tablename;\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub2' ORDER BY schemaname, tablename;\n\n-- Insert to create 5 more chunks (total 8 chunks)\nINSERT INTO test_hypertable VALUES ('2024-01-04 00:00:00+00', 4, 4.0, 'data4');\nINSERT INTO test_hypertable VALUES ('2024-01-05 00:00:00+00', 5, 5.0, 'data5');\nINSERT INTO test_hypertable VALUES ('2024-01-06 00:00:00+00', 6, 6.0, 'data6');\nINSERT INTO test_hypertable VALUES ('2024-01-07 00:00:00+00', 7, 7.0, 'data7');\nINSERT INTO test_hypertable VALUES ('2024-01-08 00:00:00+00', 8, 8.0, 'data8');\n\n-- Verify (8 chunks in pub1, 8 chunks in pub2)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub1' ORDER BY schemaname, tablename;\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub2' ORDER BY schemaname, tablename;\n\n-- Cleanup\nDROP PUBLICATION test_pub1 CASCADE;\nDROP PUBLICATION test_pub2 CASCADE;\nDROP TABLE test_hypertable CASCADE;\n\n-- Test 3: Row filtering (WHERE clause with multiple conditions)\nCREATE TABLE test_hypertable (time timestamptz NOT NULL, device_id int, value float, extra text);\nSELECT create_hypertable('test_hypertable', 'time', chunk_time_interval => interval '1 day');\n\n-- Insert to create first chunk\nINSERT INTO test_hypertable VALUES ('2024-01-01 00:00:00+00', 1, 1.0, 'data1');\n\n-- Create publication with row filter (multiple conditions)\nCREATE PUBLICATION test_pub_row_filter FOR TABLE test_hypertable WHERE (device_id > 10 AND value > 1000);\n\n-- Verify initial state (1 chunk with row filter)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub_row_filter' ORDER BY schemaname, tablename;\n\n-- Insert to create 2 more chunks (total 3 chunks)\nINSERT INTO test_hypertable VALUES ('2024-01-02 00:00:00+00', 2, 2.0, 'data2');\nINSERT INTO test_hypertable VALUES ('2024-01-03 00:00:00+00', 3, 3.0, 'data3');\n\n-- Verify state (3 chunks with row filters)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub_row_filter' ORDER BY schemaname, tablename;\n\n-- Insert to create 5 more chunks (total 8 chunks)\nINSERT INTO test_hypertable VALUES ('2024-01-04 00:00:00+00', 4, 4.0, 'data4');\nINSERT INTO test_hypertable VALUES ('2024-01-05 00:00:00+00', 5, 5.0, 'data5');\nINSERT INTO test_hypertable VALUES ('2024-01-06 00:00:00+00', 6, 6.0, 'data6');\nINSERT INTO test_hypertable VALUES ('2024-01-07 00:00:00+00', 7, 7.0, 'data7');\nINSERT INTO test_hypertable VALUES ('2024-01-08 00:00:00+00', 8, 8.0, 'data8');\n\n-- Verify final state (8 chunks with row filters)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub_row_filter' ORDER BY schemaname, tablename;\n\n-- Cleanup\nDROP PUBLICATION test_pub_row_filter CASCADE;\nDROP TABLE test_hypertable CASCADE;\n\n-- Test 4: Column filtering\nCREATE TABLE test_hypertable (time timestamptz NOT NULL, device_id int, value float, extra text);\nSELECT create_hypertable('test_hypertable', 'time', chunk_time_interval => interval '1 day');\n\n-- Insert to create first chunk\nINSERT INTO test_hypertable VALUES ('2024-01-01 00:00:00+00', 1, 1.0, 'data1');\n\n-- Create publication with column filter\nCREATE PUBLICATION test_pub_col_filter FOR TABLE test_hypertable (time, device_id);\n\n-- Verify initial state (1 chunk with column filter)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub_col_filter' ORDER BY schemaname, tablename;\n\n-- Insert to create 2 more chunks (total 3 chunks)\nINSERT INTO test_hypertable VALUES ('2024-01-02 00:00:00+00', 2, 2.0, 'data2');\nINSERT INTO test_hypertable VALUES ('2024-01-03 00:00:00+00', 3, 3.0, 'data3');\n\n-- Verify state (3 chunks with column filters)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub_col_filter' ORDER BY schemaname, tablename;\n\n-- Insert to create 5 more chunks (total 8 chunks)\nINSERT INTO test_hypertable VALUES ('2024-01-04 00:00:00+00', 4, 4.0, 'data4');\nINSERT INTO test_hypertable VALUES ('2024-01-05 00:00:00+00', 5, 5.0, 'data5');\nINSERT INTO test_hypertable VALUES ('2024-01-06 00:00:00+00', 6, 6.0, 'data6');\nINSERT INTO test_hypertable VALUES ('2024-01-07 00:00:00+00', 7, 7.0, 'data7');\nINSERT INTO test_hypertable VALUES ('2024-01-08 00:00:00+00', 8, 8.0, 'data8');\n\n-- Verify final state (8 chunks with column filters)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub_col_filter' ORDER BY schemaname, tablename;\n\n-- Cleanup\nDROP PUBLICATION test_pub_col_filter CASCADE;\nDROP TABLE test_hypertable CASCADE;\n\n-- Test 5: Combined row + column filtering\nCREATE TABLE test_hypertable (time timestamptz NOT NULL, device_id int, value float, extra text);\nSELECT create_hypertable('test_hypertable', 'time', chunk_time_interval => interval '1 day');\n\n-- Insert to create first chunk\nINSERT INTO test_hypertable VALUES ('2024-01-01 00:00:00+00', 1, 1.0, 'data1');\n\n-- Create publication with both row and column filters\nCREATE PUBLICATION test_pub_combined FOR TABLE test_hypertable (time, device_id) WHERE (device_id > 10);\n\n-- Verify initial state (1 chunk with both filters)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub_combined' ORDER BY schemaname, tablename;\n\n-- Insert to create 2 more chunks (total 3 chunks)\nINSERT INTO test_hypertable VALUES ('2024-01-02 00:00:00+00', 2, 2.0, 'data2');\nINSERT INTO test_hypertable VALUES ('2024-01-03 00:00:00+00', 3, 3.0, 'data3');\n\n-- Verify state (3 chunks with both filters)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub_combined' ORDER BY schemaname, tablename;\n\n-- Insert to create 5 more chunks (total 8 chunks)\nINSERT INTO test_hypertable VALUES ('2024-01-04 00:00:00+00', 4, 4.0, 'data4');\nINSERT INTO test_hypertable VALUES ('2024-01-05 00:00:00+00', 5, 5.0, 'data5');\nINSERT INTO test_hypertable VALUES ('2024-01-06 00:00:00+00', 6, 6.0, 'data6');\nINSERT INTO test_hypertable VALUES ('2024-01-07 00:00:00+00', 7, 7.0, 'data7');\nINSERT INTO test_hypertable VALUES ('2024-01-08 00:00:00+00', 8, 8.0, 'data8');\n\n-- Verify final state (8 chunks with both filters)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub_combined' ORDER BY schemaname, tablename;\n\n-- Cleanup\nDROP PUBLICATION test_pub_combined CASCADE;\nDROP TABLE test_hypertable CASCADE;\n\n-- Test 6: FOR ALL TABLES publication\nCREATE TABLE test_hypertable (time timestamptz NOT NULL, device_id int, value float, extra text);\nSELECT create_hypertable('test_hypertable', 'time', chunk_time_interval => interval '1 day');\n\n-- Insert to create first chunk\nINSERT INTO test_hypertable VALUES ('2024-01-01 00:00:00+00', 1, 1.0, 'data1');\n\n-- Create FOR ALL TABLES publication\nCREATE PUBLICATION test_pub_all_tables FOR ALL TABLES;\n\n-- Verify initial state (1 chunk)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub_all_tables' AND tablename LIKE '%test_hypertable%' OR tablename LIKE '_hyper_%' ORDER BY schemaname, tablename;\n\n-- Insert to create 2 more chunks (total 3 chunks)\nINSERT INTO test_hypertable VALUES ('2024-01-02 00:00:00+00', 2, 2.0, 'data2');\nINSERT INTO test_hypertable VALUES ('2024-01-03 00:00:00+00', 3, 3.0, 'data3');\n\n-- Verify state (3 chunks)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub_all_tables' AND tablename LIKE '%test_hypertable%' OR tablename LIKE '_hyper_%' ORDER BY schemaname, tablename;\n\n-- Insert to create 5 more chunks (total 8 chunks)\nINSERT INTO test_hypertable VALUES ('2024-01-04 00:00:00+00', 4, 4.0, 'data4');\nINSERT INTO test_hypertable VALUES ('2024-01-05 00:00:00+00', 5, 5.0, 'data5');\nINSERT INTO test_hypertable VALUES ('2024-01-06 00:00:00+00', 6, 6.0, 'data6');\nINSERT INTO test_hypertable VALUES ('2024-01-07 00:00:00+00', 7, 7.0, 'data7');\nINSERT INTO test_hypertable VALUES ('2024-01-08 00:00:00+00', 8, 8.0, 'data8');\n\n-- Verify final state (8 chunks)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub_all_tables' AND tablename LIKE '%test_hypertable%' OR tablename LIKE '_hyper_%' ORDER BY schemaname, tablename;\n\n-- Cleanup\nDROP PUBLICATION test_pub_all_tables CASCADE;\nDROP TABLE test_hypertable CASCADE;\n\n-- Test 7: Edge case - Hypertable not in any publication\nCREATE TABLE test_hypertable (time timestamptz NOT NULL, device_id int, value float, extra text);\nSELECT create_hypertable('test_hypertable', 'time', chunk_time_interval => interval '1 day');\n\n-- Insert to create 8 chunks without any publication\nINSERT INTO test_hypertable VALUES ('2024-01-01 00:00:00+00', 1, 1.0, 'data1');\nINSERT INTO test_hypertable VALUES ('2024-01-02 00:00:00+00', 2, 2.0, 'data2');\nINSERT INTO test_hypertable VALUES ('2024-01-03 00:00:00+00', 3, 3.0, 'data3');\nINSERT INTO test_hypertable VALUES ('2024-01-04 00:00:00+00', 4, 4.0, 'data4');\nINSERT INTO test_hypertable VALUES ('2024-01-05 00:00:00+00', 5, 5.0, 'data5');\nINSERT INTO test_hypertable VALUES ('2024-01-06 00:00:00+00', 6, 6.0, 'data6');\nINSERT INTO test_hypertable VALUES ('2024-01-07 00:00:00+00', 7, 7.0, 'data7');\nINSERT INTO test_hypertable VALUES ('2024-01-08 00:00:00+00', 8, 8.0, 'data8');\n\n-- Verify chunks were created successfully\nSELECT COUNT(*) as chunks_created FROM timescaledb_information.chunks\nWHERE hypertable_name = 'test_hypertable';\n\n-- Cleanup\nDROP TABLE test_hypertable CASCADE;\n\n-- Test 8: Edge case - Publication dropped before chunk creation\nCREATE TABLE test_hypertable (time timestamptz NOT NULL, device_id int, value float, extra text);\nSELECT create_hypertable('test_hypertable', 'time', chunk_time_interval => interval '1 day');\n\n-- Insert to create first chunk\nINSERT INTO test_hypertable VALUES ('2024-01-01 00:00:00+00', 1, 1.0, 'data1');\n\n-- Create publication and add hypertable\nCREATE PUBLICATION test_pub FOR TABLE test_hypertable;\n\n-- Verify (1 chunk in publication)\nSELECT schemaname, tablename, attnames, rowfilter FROM pg_publication_tables WHERE pubname = 'test_pub' ORDER BY schemaname, tablename;\n\n-- Drop the publication\nDROP PUBLICATION test_pub;\n\n-- Insert to create 2 more chunks (total 3 chunks)\n-- Should succeed with WARNING, not error\nINSERT INTO test_hypertable VALUES ('2024-01-02 00:00:00+00', 2, 2.0, 'data2');\nINSERT INTO test_hypertable VALUES ('2024-01-03 00:00:00+00', 3, 3.0, 'data3');\n\n-- Verify chunks were created successfully despite missing publication\nSELECT COUNT(*) as chunks_after_pub_drop FROM timescaledb_information.chunks\nWHERE hypertable_name = 'test_hypertable';\n\n-- Cleanup\nDROP TABLE test_hypertable CASCADE;\n\n-- Test 9: GUC control of chunk publication\nCREATE TABLE test_hypertable (time timestamptz NOT NULL, device_id int, value float, extra text);\nSELECT create_hypertable('test_hypertable', 'time', chunk_time_interval => interval '1 day');\n\n-- Insert to create first chunk\nINSERT INTO test_hypertable VALUES ('2024-01-01 00:00:00+00', 1, 1.0, 'data1');\n\n-- Create publication\nCREATE PUBLICATION test_pub_guc FOR TABLE test_hypertable;\n\n-- Verify initial state (1 chunk)\nSELECT schemaname, tablename FROM pg_publication_tables WHERE pubname = 'test_pub_guc' ORDER BY schemaname, tablename;\n\n-- Test Part 1: GUC enabled - chunks should be added to publication automatically\n-- Insert to create a new chunk - should be added to publication automatically\nINSERT INTO test_hypertable VALUES ('2024-01-02 00:00:00+00', 2, 2.0, 'data2');\n\n-- Verify (2 chunks in publication)\nSELECT schemaname, tablename FROM pg_publication_tables WHERE pubname = 'test_pub_guc' ORDER BY schemaname, tablename;\n\n-- Test Part 2: Disable the GUC and create another chunk\nSET timescaledb.enable_chunk_auto_publication = false;\n\n-- Insert to create a new chunk - should NOT be added to publication\nINSERT INTO test_hypertable VALUES ('2024-01-03 00:00:00+00', 3, 3.0, 'data3');\n\n-- Verify (still 2 chunks in publication, chunk 3 should not be there)\nSELECT schemaname, tablename FROM pg_publication_tables WHERE pubname = 'test_pub_guc' ORDER BY schemaname, tablename;\n\n-- Verify that chunk 3 exists but is not in the publication\nSELECT chunk_schema, chunk_name FROM timescaledb_information.chunks WHERE hypertable_name = 'test_hypertable';\n\n-- Test Part 3: Re-enable the GUC and create another chunk\nSET timescaledb.enable_chunk_auto_publication = true;\n\n-- Insert to create a new chunk - should be added to publication again\nINSERT INTO test_hypertable VALUES ('2024-01-04 00:00:00+00', 4, 4.0, 'data4');\n\n-- Verify (3 chunks in publication: chunk 1, 2, and 4; chunk 3 still missing)\nSELECT schemaname, tablename FROM pg_publication_tables WHERE pubname = 'test_pub_guc' ORDER BY schemaname, tablename;\n\nSELECT chunk_schema, chunk_name FROM timescaledb_information.chunks WHERE hypertable_name = 'test_hypertable';\n\n-- Cleanup\nDROP PUBLICATION test_pub_guc CASCADE;\nDROP TABLE test_hypertable CASCADE;\n\nRESET client_min_messages;\n"
  },
  {
    "path": "test/sql/chunk_utils.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Set this variable to avoid using a hard-coded path each time query\n-- results are compared\n\\set QUERY_RESULT_TEST_EQUAL_RELPATH 'include/query_result_test_equal.sql'\n\nCREATE OR REPLACE FUNCTION dimension_get_time(\n    hypertable_id INT\n)\n    RETURNS _timescaledb_catalog.dimension LANGUAGE SQL STABLE AS\n$BODY$\n    SELECT *\n    FROM _timescaledb_catalog.dimension d\n    WHERE d.hypertable_id = dimension_get_time.hypertable_id AND\n          d.interval_length IS NOT NULL\n$BODY$;\n\nCREATE TABLE PUBLIC.drop_chunk_test1(time bigint, temp float8, device_id text);\nCREATE TABLE PUBLIC.drop_chunk_test2(time bigint, temp float8, device_id text);\nCREATE TABLE PUBLIC.drop_chunk_test3(time bigint, temp float8, device_id text);\nCREATE INDEX ON drop_chunk_test1(time DESC);\n\n-- show_chunks() without specifying a table is not allowed\n\\set ON_ERROR_STOP 0\nSELECT show_chunks(NULL);\n\\set ON_ERROR_STOP 1\n\nSELECT create_hypertable('public.drop_chunk_test1', 'time', chunk_time_interval => 1, create_default_indexes=>false);\nSELECT create_hypertable('public.drop_chunk_test2', 'time', chunk_time_interval => 1, create_default_indexes=>false);\nSELECT create_hypertable('public.drop_chunk_test3', 'time', chunk_time_interval => 1, create_default_indexes=>false);\n\n-- Add space dimensions to ensure chunks share dimension slices\nSELECT add_dimension('public.drop_chunk_test1', 'device_id', 2);\nSELECT add_dimension('public.drop_chunk_test2', 'device_id', 2);\nSELECT add_dimension('public.drop_chunk_test3', 'device_id', 2);\n\nSELECT c.id AS chunk_id, c.hypertable_id, c.schema_name AS chunk_schema, c.table_name AS chunk_table, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\nINNER JOIN  dimension_get_time(h.id) time_dimension ON(true)\nINNER JOIN  _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = time_dimension.id)\nINNER JOIN  _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id AND cc.chunk_id = c.id)\nWHERE h.schema_name = 'public' AND (h.table_name = 'drop_chunk_test1' OR h.table_name = 'drop_chunk_test2')\nORDER BY c.id;\n\nSELECT * FROM test.relation WHERE schema = '_timescaledb_internal' AND name LIKE '\\_hyper%';\n\nSELECT  _timescaledb_functions.get_partition_for_key('dev1'::text);\nSELECT  _timescaledb_functions.get_partition_for_key('dev7'::varchar(5));\n\nINSERT INTO PUBLIC.drop_chunk_test1 VALUES(1, 1.0, 'dev1');\nINSERT INTO PUBLIC.drop_chunk_test1 VALUES(2, 2.0, 'dev1');\nINSERT INTO PUBLIC.drop_chunk_test1 VALUES(3, 3.0, 'dev1');\nINSERT INTO PUBLIC.drop_chunk_test1 VALUES(4, 4.0, 'dev7');\nINSERT INTO PUBLIC.drop_chunk_test1 VALUES(5, 5.0, 'dev7');\nINSERT INTO PUBLIC.drop_chunk_test1 VALUES(6, 6.0, 'dev7');\n\nINSERT INTO PUBLIC.drop_chunk_test2 VALUES(1, 1.0, 'dev1');\nINSERT INTO PUBLIC.drop_chunk_test2 VALUES(2, 2.0, 'dev1');\nINSERT INTO PUBLIC.drop_chunk_test2 VALUES(3, 3.0, 'dev1');\nINSERT INTO PUBLIC.drop_chunk_test2 VALUES(4, 4.0, 'dev7');\nINSERT INTO PUBLIC.drop_chunk_test2 VALUES(5, 5.0, 'dev7');\nINSERT INTO PUBLIC.drop_chunk_test2 VALUES(6, 6.0, 'dev7');\n\nINSERT INTO PUBLIC.drop_chunk_test3 VALUES(1, 1.0, 'dev1');\nINSERT INTO PUBLIC.drop_chunk_test3 VALUES(2, 2.0, 'dev1');\nINSERT INTO PUBLIC.drop_chunk_test3 VALUES(3, 3.0, 'dev1');\nINSERT INTO PUBLIC.drop_chunk_test3 VALUES(4, 4.0, 'dev7');\nINSERT INTO PUBLIC.drop_chunk_test3 VALUES(5, 5.0, 'dev7');\nINSERT INTO PUBLIC.drop_chunk_test3 VALUES(6, 6.0, 'dev7');\n\nSELECT c.id AS chunk_id, c.hypertable_id, c.schema_name AS chunk_schema, c.table_name AS chunk_table, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\nINNER JOIN  dimension_get_time(h.id) time_dimension ON(true)\nINNER JOIN  _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = time_dimension.id)\nINNER JOIN  _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id AND cc.chunk_id = c.id)\nWHERE h.schema_name = 'public' AND (h.table_name = 'drop_chunk_test1' OR h.table_name = 'drop_chunk_test2')\nORDER BY c.id;\nSELECT * FROM test.relation WHERE schema = '_timescaledb_internal' AND name LIKE '\\_hyper%';\n\n-- next two calls of show_chunks should give same set of chunks as above when combined\nSELECT show_chunks('drop_chunk_test1');\nSELECT * FROM show_chunks('drop_chunk_test2');\n\nCREATE VIEW dependent_view AS SELECT * FROM _timescaledb_internal._hyper_1_1_chunk;\n\n\\set ON_ERROR_STOP 0\nSELECT drop_chunks('drop_chunk_test1');\nSELECT drop_chunks('drop_chunk_test1', older_than => 2);\nSELECT drop_chunks('drop_chunk_test1', older_than => NULL::interval);\nSELECT drop_chunks('drop_chunk_test1', older_than => NULL::int);\n\nDROP VIEW dependent_view;\n\n-- should error because of wrong relative order of time constraints\nSELECT show_chunks('drop_chunk_test1', older_than=>3, newer_than=>4);\n\n-- Should error because NULL was used for the table name.\nSELECT drop_chunks(NULL, older_than => 3);\n-- should error because there is no relation with that OID.\nSELECT drop_chunks(3533, older_than => 3);\n\n\\set ON_ERROR_STOP 1\n\n-- show created constraints and dimension slices for each chunk\nSELECT c.table_name, cc.constraint_name, ds.id AS dimension_slice_id, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.chunk_constraint cc ON (c.id = cc.chunk_id)\nFULL OUTER JOIN _timescaledb_catalog.dimension_slice ds ON (ds.id = cc.dimension_slice_id)\nORDER BY c.id;\nSELECT * FROM _timescaledb_catalog.dimension_slice ORDER BY id;\n\n-- Test that truncating chunks works\nSELECT count(*) FROM _timescaledb_internal._hyper_2_7_chunk;\nTRUNCATE TABLE _timescaledb_internal._hyper_2_7_chunk;\nSELECT count(*) FROM _timescaledb_internal._hyper_2_7_chunk;\n\n-- Drop one chunk \"manually\" and verify that dimension slices and\n-- constraints are cleaned up. Each chunk has two constraints and two\n-- dimension slices. Both constraints should be deleted, but only one\n-- slice should be deleted since the space-dimension slice is shared\n-- with other chunks in the same hypertable\nDROP TABLE _timescaledb_internal._hyper_2_7_chunk;\n\n-- Two constraints deleted compared to above\nSELECT c.table_name, cc.constraint_name, ds.id AS dimension_slice_id, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.chunk_constraint cc ON (c.id = cc.chunk_id)\nFULL OUTER JOIN _timescaledb_catalog.dimension_slice ds ON (ds.id = cc.dimension_slice_id)\nORDER BY c.id;\n-- Only one dimension slice deleted\nSELECT * FROM _timescaledb_catalog.dimension_slice ORDER BY id;\n\n-- We drop all chunks older than timestamp 2 in all hypertable. This\n-- is added only to avoid making the diff for this commit larger than\n-- necessary and make reviews easier.\nSELECT drop_chunks(format('%1$I.%2$I', schema_name, table_name)::regclass, older_than => 2)\n  FROM _timescaledb_catalog.hypertable;\n\nSELECT c.table_name, cc.constraint_name, ds.id AS dimension_slice_id, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.chunk_constraint cc ON (c.id = cc.chunk_id)\nFULL OUTER JOIN _timescaledb_catalog.dimension_slice ds ON (ds.id = cc.dimension_slice_id)\nORDER BY c.id;\nSELECT * FROM _timescaledb_catalog.dimension_slice ORDER BY id;\n\nSELECT c.id AS chunk_id, c.hypertable_id, c.schema_name AS chunk_schema, c.table_name AS chunk_table, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\nINNER JOIN  dimension_get_time(h.id) time_dimension ON(true)\nINNER JOIN  _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = time_dimension.id)\nINNER JOIN  _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id AND cc.chunk_id = c.id)\nWHERE h.schema_name = 'public' AND (h.table_name = 'drop_chunk_test1' OR h.table_name = 'drop_chunk_test2')\nORDER BY c.id;\n\n-- next two calls of show_chunks should give same set of chunks as above when combined\nSELECT show_chunks('drop_chunk_test1');\nSELECT * FROM show_chunks('drop_chunk_test2');\n\nSELECT * FROM test.relation WHERE schema = '_timescaledb_internal' AND name LIKE '\\_hyper%';\n-- show_chunks and drop_chunks output should be the same\n\\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test1\\', older_than => 3)::NAME'\n\\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test1\\', older_than => 3)::NAME'\n\\set ECHO errors\n\\ir  :QUERY_RESULT_TEST_EQUAL_RELPATH\n\\set ECHO all\n\nSELECT c.id AS chunk_id, c.hypertable_id, c.schema_name AS chunk_schema, c.table_name AS chunk_table, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\nINNER JOIN  dimension_get_time(h.id) time_dimension ON(true)\nINNER JOIN  _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = time_dimension.id)\nINNER JOIN  _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id AND cc.chunk_id = c.id)\nWHERE h.schema_name = 'public' AND (h.table_name = 'drop_chunk_test1' OR h.table_name = 'drop_chunk_test2')\nORDER BY c.id;\n\nSELECT * FROM test.relation WHERE schema = '_timescaledb_internal' AND name LIKE '\\_hyper%';\n\n-- next two calls of show_chunks should give same set of chunks as above when combined\nSELECT show_chunks('drop_chunk_test1');\nSELECT * FROM show_chunks('drop_chunk_test2');\n\n-- 2,147,483,647 is the largest int so this tests that BIGINTs work\n-- show_chunks and drop_chunks output should be the same\n\\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test3\\', older_than => 2147483648)::NAME'\n\\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test3\\', older_than => 2147483648)::NAME'\n\\set ECHO errors\n\\ir  :QUERY_RESULT_TEST_EQUAL_RELPATH\n\\set ECHO all\n\nSELECT c.id AS chunk_id, c.hypertable_id, c.schema_name AS chunk_schema, c.table_name AS chunk_table, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\nINNER JOIN  dimension_get_time(h.id) time_dimension ON(true)\nINNER JOIN  _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = time_dimension.id)\nINNER JOIN  _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id AND cc.chunk_id = c.id)\nWHERE h.schema_name = 'public' AND (h.table_name = 'drop_chunk_test1' OR h.table_name = 'drop_chunk_test2' OR h.table_name = 'drop_chunk_test3')\nORDER BY c.id;\n\nSELECT * FROM test.relation WHERE schema = '_timescaledb_internal' AND name LIKE '\\_hyper%';\n\\set ON_ERROR_STOP 0\n-- should error because no hypertable\nSELECT drop_chunks('drop_chunk_test4', older_than => 5);\nSELECT show_chunks('drop_chunk_test4');\nSELECT show_chunks('drop_chunk_test4', 5);\n\\set ON_ERROR_STOP 1\n\nDROP TABLE _timescaledb_internal._hyper_1_6_chunk;\n\nSELECT c.id AS chunk_id, c.hypertable_id, c.schema_name AS chunk_schema, c.table_name AS chunk_table, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\nINNER JOIN  dimension_get_time(h.id) time_dimension ON(true)\nINNER JOIN  _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = time_dimension.id)\nINNER JOIN  _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id AND cc.chunk_id = c.id)\nWHERE h.schema_name = 'public' AND (h.table_name = 'drop_chunk_test1' OR h.table_name = 'drop_chunk_test2')\nORDER BY c.id;\n\nSELECT * FROM test.relation WHERE schema = '_timescaledb_internal' AND name LIKE '\\_hyper%';\n\n-- newer_than tests\n-- show_chunks and drop_chunks output should be the same\n\\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test1\\', newer_than=>5)::NAME'\n\\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test1\\', newer_than=>5, verbose => true)::NAME'\n\\set ECHO errors\n\\ir  :QUERY_RESULT_TEST_EQUAL_RELPATH\n\\set ECHO all\n\nSELECT c.id AS chunk_id, c.hypertable_id, c.schema_name AS chunk_schema, c.table_name AS chunk_table, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\nINNER JOIN  dimension_get_time(h.id) time_dimension ON(true)\nINNER JOIN  _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = time_dimension.id)\nINNER JOIN  _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id AND cc.chunk_id = c.id)\nWHERE h.schema_name = 'public' AND (h.table_name = 'drop_chunk_test1')\nORDER BY c.id;\n\nSELECT show_chunks('drop_chunk_test1');\n\nSELECT * FROM test.relation WHERE schema = '_timescaledb_internal' AND name LIKE '_hyper%';\n\n-- show_chunks and drop_chunks output should be the same\n\\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test1\\', older_than=>4, newer_than=>3)::NAME'\n\\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test1\\', older_than=>4, newer_than=>3)::NAME'\n\\set ECHO errors\n\\ir  :QUERY_RESULT_TEST_EQUAL_RELPATH\n\\set ECHO all\n\nSELECT c.id AS chunk_id, c.hypertable_id, c.schema_name AS chunk_schema, c.table_name AS chunk_table, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\nINNER JOIN  dimension_get_time(h.id) time_dimension ON(true)\nINNER JOIN  _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = time_dimension.id)\nINNER JOIN  _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id AND cc.chunk_id = c.id)\nWHERE h.schema_name = 'public' AND (h.table_name = 'drop_chunk_test1')\nORDER BY c.id;\n\n-- the call of show_chunks should give same set of chunks as above\nSELECT show_chunks('drop_chunk_test1');\n\nSELECT c.id AS chunk_id, c.hypertable_id, c.schema_name AS chunk_schema, c.table_name AS chunk_table, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\nINNER JOIN  dimension_get_time(h.id) time_dimension ON(true)\nINNER JOIN  _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = time_dimension.id)\nINNER JOIN  _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id AND cc.chunk_id = c.id)\nWHERE h.schema_name = 'public'\nORDER BY c.id;\n\n-- We support show/drop chunks using timestamps/interval even with integer partitioning\n-- the chunk creation time gets used for these. But we need to use \"created_before, created_after\"\n-- for these\n\\set ON_ERROR_STOP 0\nSELECT show_chunks('drop_chunk_test3', older_than => now());\nSELECT show_chunks('drop_chunk_test2', older_than => now());\nSELECT show_chunks('drop_chunk_test1', newer_than => INTERVAL '15 minutes');\nSELECT show_chunks('drop_chunk_test1', older_than => now(), newer_than => INTERVAL '15 minutes');\nSELECT drop_chunks('drop_chunk_test1', older_than => now());\n-- mix of older_than/newer_than and created_after/created_before doesn't work\nSELECT show_chunks('drop_chunk_test1', older_than => now(), created_after => INTERVAL '15 minutes');\nSELECT show_chunks('drop_chunk_test1', created_before => now(), newer_than => INTERVAL '15 minutes');\n\\set ON_ERROR_STOP 1\nSELECT show_chunks('drop_chunk_test3', created_before => now() + INTERVAL '1 hour');\nSELECT show_chunks('drop_chunk_test2', created_before => now() + INTERVAL '1 hour');\nSELECT show_chunks('drop_chunk_test1', created_after => INTERVAL '15 minutes');\nSELECT show_chunks('drop_chunk_test1', created_before => now() + INTERVAL '1 hour', created_after => INTERVAL '1 hour');\nSELECT drop_chunks('drop_chunk_test1', created_before => now() + INTERVAL '1 hour');\n\nSELECT drop_chunks(format('%1$I.%2$I', schema_name, table_name)::regclass, older_than => 5, newer_than => 4)\n  FROM _timescaledb_catalog.hypertable WHERE schema_name = 'public';\n\n\n\nCREATE TABLE PUBLIC.drop_chunk_test_ts(time timestamp, temp float8, device_id text);\nSELECT create_hypertable('public.drop_chunk_test_ts', 'time', chunk_time_interval => interval '1 minute', create_default_indexes=>false);\n\nCREATE TABLE PUBLIC.drop_chunk_test_tstz(time timestamptz, temp float8, device_id text);\nSELECT create_hypertable('public.drop_chunk_test_tstz', 'time', chunk_time_interval => interval '1 minute', create_default_indexes=>false);\n\nSET timezone = '+1';\nINSERT INTO PUBLIC.drop_chunk_test_ts VALUES(now()-INTERVAL '5 minutes', 1.0, 'dev1');\nINSERT INTO PUBLIC.drop_chunk_test_ts VALUES(now()+INTERVAL '5 minutes', 1.0, 'dev1');\nINSERT INTO PUBLIC.drop_chunk_test_tstz VALUES(now()-INTERVAL '5 minutes', 1.0, 'dev1');\nINSERT INTO PUBLIC.drop_chunk_test_tstz VALUES(now()+INTERVAL '5 minutes', 1.0, 'dev1');\n\nSELECT * FROM test.show_subtables('drop_chunk_test_ts');\nSELECT * FROM test.show_subtables('drop_chunk_test_tstz');\n\nBEGIN;\n    SELECT show_chunks('drop_chunk_test_ts');\n    SELECT show_chunks('drop_chunk_test_ts', now()::timestamp-interval '1 minute');\n-- show_chunks and drop_chunks output should be the same\n    \\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test_ts\\', newer_than => interval \\'1 minute\\')::NAME'\n    \\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test_ts\\', newer_than => interval \\'1 minute\\')::NAME'\n    \\set ECHO errors\n    \\ir  :QUERY_RESULT_TEST_EQUAL_RELPATH\n    \\set ECHO all\n    \\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test_ts\\', older_than => interval \\'6 minute\\')::NAME'\n    \\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test_ts\\', older_than => interval \\'6 minute\\')::NAME'\n    \\set ECHO errors\n    \\ir  :QUERY_RESULT_TEST_EQUAL_RELPATH\n    \\set ECHO all\n\n    SELECT * FROM test.show_subtables('drop_chunk_test_ts');\n    \\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test_ts\\', older_than => interval \\'1 minute\\')::NAME'\n    \\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test_ts\\', interval \\'1 minute\\')::NAME'\n    \\set ECHO errors\n    \\ir  :QUERY_RESULT_TEST_EQUAL_RELPATH\n    \\set ECHO all\n    SELECT * FROM test.show_subtables('drop_chunk_test_ts');\n    SELECT show_chunks('drop_chunk_test_tstz');\n    SELECT show_chunks('drop_chunk_test_tstz', older_than => now() - interval '1 minute', newer_than => now() - interval '6 minute');\n    SELECT show_chunks('drop_chunk_test_tstz', newer_than => now() - interval '1 minute');\n    SELECT show_chunks('drop_chunk_test_tstz', older_than => now() - interval '1 minute');\n\n    \\set QUERY1 'SELECT show_chunks(older_than => interval \\'1 minute\\', relation => \\'drop_chunk_test_tstz\\')::NAME'\n    \\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test_tstz\\', interval \\'1 minute\\')::NAME'\n    \\set ECHO errors\n    \\ir  :QUERY_RESULT_TEST_EQUAL_RELPATH\n    \\set ECHO all\n    SELECT * FROM test.show_subtables('drop_chunk_test_tstz');\nROLLBACK;\n\nBEGIN;\n-- show_chunks and drop_chunks output should be the same\n    \\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test_ts\\', newer_than => interval \\'6 minute\\')::NAME'\n    \\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test_ts\\', newer_than => interval \\'6 minute\\')::NAME'\n    \\set ECHO errors\n    \\ir  :QUERY_RESULT_TEST_EQUAL_RELPATH\n    \\set ECHO all\n    SELECT * FROM test.show_subtables('drop_chunk_test_ts');\nROLLBACK;\n\nBEGIN;\n-- show_chunks and drop_chunks output should be the same\n    \\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test_ts\\', older_than => interval \\'1 minute\\')::NAME'\n    \\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test_ts\\', older_than => interval \\'1 minute\\')::NAME'\n    \\set ECHO errors\n    \\ir  :QUERY_RESULT_TEST_EQUAL_RELPATH\n    \\set ECHO all\n    SELECT * FROM test.show_subtables('drop_chunk_test_ts');\n    \\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test_tstz\\', older_than => interval \\'1 minute\\')::NAME'\n    \\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test_tstz\\', older_than => interval \\'1 minute\\')::NAME'\n    \\set ECHO errors\n    \\ir  :QUERY_RESULT_TEST_EQUAL_RELPATH\n    \\set ECHO all\n    SELECT * FROM test.show_subtables('drop_chunk_test_tstz');\nROLLBACK;\n\nBEGIN;\n-- show_chunks and drop_chunks output should be the same\n    \\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test_ts\\', older_than => now()::timestamp-interval \\'1 minute\\')::NAME'\n    \\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test_ts\\', older_than => now()::timestamp-interval \\'1 minute\\')::NAME'\n    \\set ECHO errors\n    \\ir  :QUERY_RESULT_TEST_EQUAL_RELPATH\n    \\set ECHO all\n    SELECT * FROM test.show_subtables('drop_chunk_test_ts');\n    \\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test_tstz\\', older_than => now()-interval \\'1 minute\\')::NAME'\n    \\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test_tstz\\', older_than => now()-interval \\'1 minute\\')::NAME'\n    \\set ECHO errors\n    \\ir  :QUERY_RESULT_TEST_EQUAL_RELPATH\n    \\set ECHO all\n    SELECT * FROM test.show_subtables('drop_chunk_test_tstz');\nROLLBACK;\n\nSELECT * FROM test.relation WHERE schema = '_timescaledb_internal' AND name LIKE '\\_hyper%';\n\n\\set ON_ERROR_STOP 0\nSELECT drop_chunks(interval '1 minute');\nSELECT drop_chunks('drop_chunk_test_ts', (now()-interval '1 minute'));\nSELECT drop_chunks('drop_chunk_test3', verbose => true);\nSELECT drop_chunks('drop_chunk_test3', interval '1 minute');\n\\set ON_ERROR_STOP 1\n\n-- Interval boundary for INTEGER type columns. It uses chunk creation\n-- time to identify the affected chunks.\nSELECT drop_chunks('drop_chunk_test3', created_after => interval '1 minute');\n\nSELECT * FROM test.relation WHERE schema = '_timescaledb_internal' AND name LIKE '\\_hyper%';\n\nCREATE TABLE PUBLIC.drop_chunk_test_date(time date, temp float8, device_id text);\nSELECT create_hypertable('public.drop_chunk_test_date', 'time', chunk_time_interval => interval '1 day', create_default_indexes=>false);\n\nSET timezone = '+100';\nINSERT INTO PUBLIC.drop_chunk_test_date VALUES(now()-INTERVAL '2 day', 1.0, 'dev1');\n\nBEGIN;\n-- show_chunks and drop_chunks output should be the same\n    \\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test_date\\', older_than => interval \\'1 day\\')::NAME'\n    \\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test_date\\', older_than => interval \\'1 day\\')::NAME'\n    \\set ECHO errors\n    \\ir  :QUERY_RESULT_TEST_EQUAL_RELPATH\n    \\set ECHO all\n    SELECT * FROM test.show_subtables('drop_chunk_test_date');\nROLLBACK;\n\nBEGIN;\n-- show_chunks and drop_chunks output should be the same\n    \\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test_date\\', older_than => (now()-interval \\'1 day\\')::date)::NAME'\n    \\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test_date\\', older_than => (now()-interval \\'1 day\\')::date)::NAME'\n    \\set ECHO errors\n    \\ir  :QUERY_RESULT_TEST_EQUAL_RELPATH\n    \\set ECHO all\n    SELECT * FROM test.show_subtables('drop_chunk_test_date');\nROLLBACK;\n\nSET timezone TO '-5';\n\nCREATE TABLE chunk_id_from_relid_test(time bigint, temp float8, device_id int);\nSELECT hypertable_id FROM create_hypertable('chunk_id_from_relid_test', 'time', chunk_time_interval => 10) \\gset\n\nINSERT INTO chunk_id_from_relid_test VALUES (0, 1.1, 0), (0, 1.3, 11), (12, 2.0, 0), (12, 0.1, 11);\n\nSELECT _timescaledb_functions.chunk_id_from_relid(tableoid) FROM chunk_id_from_relid_test;\n\nDROP TABLE chunk_id_from_relid_test;\n\nCREATE TABLE chunk_id_from_relid_test(time bigint, temp float8, device_id int);\nSELECT hypertable_id FROM  create_hypertable('chunk_id_from_relid_test',\n    'time', chunk_time_interval => 10,\n    partitioning_column => 'device_id',\n    number_partitions => 3) \\gset\n\nINSERT INTO chunk_id_from_relid_test VALUES (0, 1.1, 2), (0, 1.3, 11), (12, 2.0, 2), (12, 0.1, 11);\n\nSELECT _timescaledb_functions.chunk_id_from_relid(tableoid) FROM chunk_id_from_relid_test;\n\n\\set ON_ERROR_STOP 0\nSELECT _timescaledb_functions.chunk_id_from_relid('pg_type'::regclass);\nSELECT _timescaledb_functions.chunk_id_from_relid('chunk_id_from_relid_test'::regclass);\n\n-- test drop/show_chunks on custom partition types\nCREATE FUNCTION extract_time(a jsonb)\nRETURNS TIMESTAMPTZ\nLANGUAGE SQL\nAS $$ SELECT (a->>'time')::TIMESTAMPTZ $$ IMMUTABLE;\n\nCREATE TABLE test_weird_type(a jsonb);\nSELECT create_hypertable('test_weird_type', 'a',\n    time_partitioning_func=>'extract_time'::regproc,\n    chunk_time_interval=>'2 hours'::interval);\n\nINSERT INTO test_weird_type VALUES ('{\"time\":\"2019/06/06 1:00+0\"}'), ('{\"time\":\"2019/06/06 5:00+0\"}');\nSELECT * FROM test.show_subtables('test_weird_type');\nSELECT show_chunks('test_weird_type', older_than=>'2019/06/06 4:00+0'::TIMESTAMPTZ);\nSELECT show_chunks('test_weird_type', older_than=>'2019/06/06 10:00+0'::TIMESTAMPTZ);\n\n-- show_chunks and drop_chunks output should be the same\n\\set QUERY1 'SELECT show_chunks(\\'test_weird_type\\', older_than => \\'2019/06/06 5:00+0\\'::TIMESTAMPTZ)::NAME'\n\\set QUERY2 'SELECT drop_chunks(\\'test_weird_type\\', older_than => \\'2019/06/06 5:00+0\\'::TIMESTAMPTZ)::NAME'\n\\set ECHO errors\n\\ir  :QUERY_RESULT_TEST_EQUAL_RELPATH\n\\set ECHO all\nSELECT * FROM test.show_subtables('test_weird_type');\nSELECT show_chunks('test_weird_type', older_than=>'2019/06/06 4:00+0'::TIMESTAMPTZ);\nSELECT show_chunks('test_weird_type', older_than=>'2019/06/06 10:00+0'::TIMESTAMPTZ);\n\n-- show_chunks and drop_chunks output should be the same\n\\set QUERY1 'SELECT show_chunks(\\'test_weird_type\\', older_than => \\'2019/06/06 6:00+0\\'::TIMESTAMPTZ)::NAME'\n\\set QUERY2 'SELECT drop_chunks(\\'test_weird_type\\', older_than => \\'2019/06/06 6:00+0\\'::TIMESTAMPTZ)::NAME'\n\\set ECHO errors\n\\ir  :QUERY_RESULT_TEST_EQUAL_RELPATH\n\\set ECHO all\nSELECT * FROM test.show_subtables('test_weird_type');\nSELECT show_chunks('test_weird_type', older_than=>'2019/06/06 10:00+0'::TIMESTAMPTZ);\n\nDROP TABLE test_weird_type;\n\nCREATE FUNCTION extract_int_time(a jsonb)\nRETURNS BIGINT\nLANGUAGE SQL\nAS $$ SELECT (a->>'time')::BIGINT $$ IMMUTABLE;\n\nCREATE TABLE test_weird_type_i(a jsonb);\nSELECT create_hypertable('test_weird_type_i', 'a',\n    time_partitioning_func=>'extract_int_time'::regproc,\n    chunk_time_interval=>5);\n\nINSERT INTO test_weird_type_i VALUES ('{\"time\":\"0\"}'), ('{\"time\":\"5\"}');\nSELECT * FROM test.show_subtables('test_weird_type_i');\nSELECT show_chunks('test_weird_type_i', older_than=>5);\nSELECT show_chunks('test_weird_type_i', older_than=>10);\n\n-- show_chunks and drop_chunks output should be the same\n\\set QUERY1 'SELECT show_chunks(\\'test_weird_type_i\\', older_than=>5)::NAME'\n\\set QUERY2 'SELECT drop_chunks(\\'test_weird_type_i\\', older_than => 5)::NAME'\n\\set ECHO errors\n\\ir  :QUERY_RESULT_TEST_EQUAL_RELPATH\n\\set ECHO all\nSELECT * FROM test.show_subtables('test_weird_type_i');\nSELECT show_chunks('test_weird_type_i', older_than=>5);\nSELECT show_chunks('test_weird_type_i', older_than=>10);\n\n-- show_chunks and drop_chunks output should be the same\n\\set QUERY1 'SELECT show_chunks(\\'test_weird_type_i\\', older_than=>10)::NAME'\n\\set QUERY2 'SELECT drop_chunks(\\'test_weird_type_i\\', older_than => 10)::NAME'\n\\set ECHO errors\n\\ir  :QUERY_RESULT_TEST_EQUAL_RELPATH\n\\set ECHO all\nSELECT * FROM test.show_subtables('test_weird_type_i');\nSELECT show_chunks('test_weird_type_i', older_than=>10);\n\nDROP TABLE test_weird_type_i CASCADE;\n\\c  :TEST_DBNAME :ROLE_SUPERUSER\nALTER TABLE drop_chunk_test2 OWNER TO :ROLE_DEFAULT_PERM_USER_2;\n\n--drop chunks 3 will have a chunk we a dependent object (a view)\n--we create the dependent object now\nINSERT INTO PUBLIC.drop_chunk_test3 VALUES(1, 1.0, 'dev1');\n\nSELECT c.schema_name as chunk_schema, c.table_name as chunk_table\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\nWHERE h.schema_name = 'public' AND h.table_name = 'drop_chunk_test3'\nORDER BY c.id \\gset\n\ncreate view dependent_view as SELECT * FROM :\"chunk_schema\".:\"chunk_table\";\ncreate view dependent_view2 as SELECT * FROM :\"chunk_schema\".:\"chunk_table\";\nALTER TABLE drop_chunk_test3 OWNER TO :ROLE_DEFAULT_PERM_USER_2;\n\n\\c  :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n\\set ON_ERROR_STOP 0\nSELECT drop_chunks('drop_chunk_test1', older_than=>4, newer_than=>3);\n\n--works with modified owner tables\n-- show_chunks and drop_chunks output should be the same\n\\set QUERY1 'SELECT show_chunks(\\'drop_chunk_test2\\', older_than=>4, newer_than=>3)::NAME'\n\\set QUERY2 'SELECT drop_chunks(\\'drop_chunk_test2\\', older_than=>4, newer_than=>3)::NAME'\n\\set ECHO errors\n\\ir  :QUERY_RESULT_TEST_EQUAL_RELPATH\n\\set ECHO all\n\n\\set VERBOSITY default\n--this fails because there are dependent objects\nSELECT drop_chunks('drop_chunk_test3', older_than=>100);\n\\set VERBOSITY terse\n\n\\c  :TEST_DBNAME :ROLE_SUPERUSER\nDROP VIEW dependent_view;\nDROP VIEW dependent_view2;\n\n\\c  :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n\\set ON_ERROR_STOP 1\n\n--drop chunks from hypertable with same name in different schema\n-- order of schema in search_path matters --\n\\c :TEST_DBNAME :ROLE_SUPERUSER\ndrop table chunk_id_from_relid_test;\ndrop table drop_chunk_test1;\ndrop table drop_chunk_test2;\ndrop table drop_chunk_test3;\nCREATE SCHEMA try_schema;\nCREATE SCHEMA test1;\nCREATE SCHEMA test2;\nCREATE SCHEMA test3;\nGRANT CREATE ON SCHEMA try_schema, test1, test2, test3 TO :ROLE_DEFAULT_PERM_USER;\nGRANT USAGE ON SCHEMA try_schema, test1, test2, test3 TO :ROLE_DEFAULT_PERM_USER;\n\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nCREATE TABLE try_schema.drop_chunk_test_date(time date, temp float8, device_id text);\nSELECT create_hypertable('try_schema.drop_chunk_test_date', 'time', chunk_time_interval => interval '1 day', create_default_indexes=>false);\nINSERT INTO public.drop_chunk_test_date VALUES( '2020-01-10', 100, 'hello');\nINSERT INTO try_schema.drop_chunk_test_date VALUES( '2020-01-10', 100, 'hello');\nset search_path to try_schema, test1, test2, test3, public;\nSELECT show_chunks('public.drop_chunk_test_date', older_than=>'1 day'::interval);\nSELECT show_chunks('try_schema.drop_chunk_test_date', older_than=>'1 day'::interval);\nSELECT drop_chunks('drop_chunk_test_date', older_than=> '1 day'::interval);\n\n-- test drop chunks across two tables within the same schema\nCREATE TABLE test1.hyper1 (time bigint, temp float);\nCREATE TABLE test1.hyper2 (time bigint, temp float);\n\nSELECT create_hypertable('test1.hyper1', 'time', chunk_time_interval => 10);\nSELECT create_hypertable('test1.hyper2', 'time', chunk_time_interval => 10);\n\nINSERT INTO test1.hyper1 VALUES (10, 0.5);\nINSERT INTO test1.hyper2 VALUES (10, 0.7);\n\nSELECT show_chunks('test1.hyper1');\nSELECT show_chunks('test1.hyper2');\n\n-- test drop chunks for given table name across all schemas\nCREATE TABLE test2.hyperx (time bigint, temp float);\nCREATE TABLE test3.hyperx (time bigint, temp float);\n\nSELECT create_hypertable('test2.hyperx', 'time', chunk_time_interval => 10);\nSELECT create_hypertable('test3.hyperx', 'time', chunk_time_interval => 10);\n\nINSERT INTO test2.hyperx VALUES (10, 0.5);\nINSERT INTO test3.hyperx VALUES (10, 0.7);\n\nSELECT show_chunks('test2.hyperx');\nSELECT show_chunks('test3.hyperx');\n\n-- This will only drop from one of the tables since the one that is\n-- first in the search path will hide the other one.\nSELECT drop_chunks('hyperx', older_than => 100);\nSELECT show_chunks('test2.hyperx');\nSELECT show_chunks('test3.hyperx');\n\n-- Check CTAS behavior when internal ALTER TABLE gets fired\nCREATE TABLE PUBLIC.drop_chunk_test4(time bigint, temp float8, device_id text);\nCREATE TABLE drop_chunks_table_id AS SELECT hypertable_id\n      FROM create_hypertable('public.drop_chunk_test4', 'time', chunk_time_interval => 1);\n\n-- TEST for internal api that drops a single chunk\n-- this drops the table and removes entry from the catalog.\n-- does not affect any materialized cagg data\nINSERT INTO test1.hyper1 VALUES (20, 0.5);\nSELECT chunk_schema as \"CHSCHEMA\",  chunk_name as \"CHNAME\"\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'hyper1' and hypertable_schema = 'test1'\nORDER BY chunk_name ;\n--drop one of the chunks\nSELECT chunk_schema || '.' || chunk_name as \"CHNAME\"\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'hyper1' and hypertable_schema = 'test1'\nORDER BY chunk_name LIMIT 1\n\\gset\n\nSELECT  _timescaledb_functions.drop_chunk(:'CHNAME');\nSELECT chunk_schema as \"CHSCHEMA\",  chunk_name as \"CHNAME\"\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'hyper1' and hypertable_schema = 'test1'\nORDER BY chunk_name ;\n\n-- \"created_before/after\" can be used with time partitioning in drop/show chunks\nSELECT show_chunks('drop_chunk_test_tstz', created_before => now() - INTERVAL '1 hour');\nSELECT drop_chunks('drop_chunk_test_tstz', created_before => now() + INTERVAL '1 hour');\nSELECT show_chunks('drop_chunk_test_ts');\n-- \"created_before/after\" accept timestamptz even though partitioning col is just\n-- timestamp\nSELECT show_chunks('drop_chunk_test_ts', created_after => now() - INTERVAL '1 hour', created_before => now());\nSELECT drop_chunks('drop_chunk_test_ts', created_after => INTERVAL '1 hour', created_before => now());\n\n-- Test views on top of hypertables\nCREATE TABLE view_test (project_id INT, ts TIMESTAMPTZ NOT NULL);\nSELECT create_hypertable('view_test', by_range('ts', INTERVAL '1 day'));\n-- exactly one partition per project_id\nSELECT * FROM add_dimension('view_test', 'project_id', chunk_time_interval => 1); -- exactly one partition per project; works for *integer* types\nINSERT INTO view_test (project_id, ts)\nSELECT g % 25 + 1 AS project_id, i.ts + (g * interval '1 week') / i.total AS ts\nFROM (SELECT timestamptz '2024-01-01 00:00:00+0', 600) i(ts, total),\ngenerate_series(1, i.total) g;\n-- Create a view on top of this hypertable\nCREATE VIEW test_view_part_few AS SELECT project_id,\n    ts\n   FROM view_test\n  WHERE project_id = ANY (ARRAY[5, 10, 15]);\n-- Complicated query on a view involving a range check and a sort\nSELECT * FROM test_view_part_few WHERE ts BETWEEN '2024-01-04 00:00:00+00'AND '2024-01-05 00:00:00' ORDER BY ts LIMIT 1000;\n\n-- Test chunk_status_text function\nCREATE TABLE chunk_status_test(time timestamptz) WITH (tsdb.hypertable,tsdb.partition_column='time',tsdb.columnstore=false);\nINSERT INTO chunk_status_test VALUES ('2025-01-01'),('2025-02-01'),('2025-03-01');\nSELECT _timescaledb_functions.chunk_status_text(i) FROM generate_series(0,15) i;\n\nSELECT chunk, _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('chunk_status_test') chunk;\n\nSELECT _timescaledb_functions.chunk_status_text(NULL::int);\nSELECT _timescaledb_functions.chunk_status_text(NULL::regclass);\n\\set ON_ERROR_STOP 0\nSELECT _timescaledb_functions.chunk_status_text(-1);\nSELECT _timescaledb_functions.chunk_status_text(16);\nSELECT _timescaledb_functions.chunk_status_text(1000);\nSELECT _timescaledb_functions.chunk_status_text(0::regclass);\nSELECT _timescaledb_functions.chunk_status_text('pg_class'::regclass);\n\\set ON_ERROR_STOP 1\n\n-- Test that function exists and returns an array type\nSELECT pg_typeof(_timescaledb_functions.chunk_status_text(0));\n\n"
  },
  {
    "path": "test/sql/chunks.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\unset ECHO\n\\o /dev/null\n\\ir include/test_utils.sql\n\\o\n\\set ECHO errors\n\\set VERBOSITY default\n\n\\o /dev/null\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION _timescaledb_internal.dimension_calculate_default_range_open(\n        dimension_value   BIGINT,\n        interval_length   BIGINT,\n        dimension_type    CSTRING,\n    OUT range_start       BIGINT,\n    OUT range_end         BIGINT)\n    AS :MODULE_PATHNAME, 'ts_dimension_calculate_open_range_default' LANGUAGE C STABLE;\n\nCREATE OR REPLACE FUNCTION _timescaledb_internal.dimension_calculate_default_range_closed(\n        dimension_value   BIGINT,\n        num_slices        SMALLINT,\n    OUT range_start       BIGINT,\n    OUT range_end         BIGINT)\n    AS :MODULE_PATHNAME, 'ts_dimension_calculate_closed_range_default' LANGUAGE C STABLE;\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n--open\nSELECT assert_equal(0::bigint, actual_range_start), assert_equal(10::bigint, actual_range_end)\nFROM _timescaledb_internal.dimension_calculate_default_range_open(0,10, 'int8') AS res(actual_range_start, actual_range_end);\n\nSELECT assert_equal(0::bigint, actual_range_start), assert_equal(10::bigint, actual_range_end)\nFROM _timescaledb_internal.dimension_calculate_default_range_open(9,10, 'int4') AS res(actual_range_start, actual_range_end);\n\nSELECT assert_equal(10::bigint, actual_range_start), assert_equal(20::bigint, actual_range_end)\nFROM _timescaledb_internal.dimension_calculate_default_range_open(10,10, 'int2') AS res(actual_range_start, actual_range_end);\n\nSELECT assert_equal(-10::bigint, actual_range_start), assert_equal(0::bigint, actual_range_end)\nFROM _timescaledb_internal.dimension_calculate_default_range_open(-1,10, 'int8') AS res(actual_range_start, actual_range_end);\n\nSELECT assert_equal(-10::bigint, actual_range_start), assert_equal(0::bigint, actual_range_end)\nFROM _timescaledb_internal.dimension_calculate_default_range_open(-10,10, 'int4') AS res(actual_range_start, actual_range_end);\n\nSELECT assert_equal(-20::bigint, actual_range_start), assert_equal(-10::bigint, actual_range_end)\nFROM _timescaledb_internal.dimension_calculate_default_range_open(-11,10, 'int2') AS res(actual_range_start, actual_range_end);\n\n--test that the ends are cut as needed to prevent overflow/undeflow.\n\n---------------\n-- BIGINT\n---------------\nSELECT assert_equal(-9223372036854775808, actual_range_start), assert_equal(-9223372036854775800::bigint, actual_range_end)\nFROM _timescaledb_internal.dimension_calculate_default_range_open(-9223372036854775808,10, 'int8') AS res(actual_range_start, actual_range_end);\n\nSELECT assert_equal(-9223372036854775808, actual_range_start), assert_equal(-9223372036854775800::bigint, actual_range_end)\nFROM _timescaledb_internal.dimension_calculate_default_range_open(-9223372036854775807,10, 'int8') AS res(actual_range_start, actual_range_end);\n\nSELECT assert_equal(9223372036854775800::bigint, actual_range_start), assert_equal(9223372036854775807::bigint, actual_range_end)\nFROM _timescaledb_internal.dimension_calculate_default_range_open(9223372036854775807,10, 'int8') AS res(actual_range_start, actual_range_end);\n\nSELECT assert_equal(9223372036854775800::bigint, actual_range_start), assert_equal(9223372036854775807::bigint, actual_range_end)\nFROM _timescaledb_internal.dimension_calculate_default_range_open(9223372036854775806,10, 'int8') AS res(actual_range_start, actual_range_end);\n\n---------------\n-- INT\n---------------\nSELECT assert_equal(-9223372036854775808, actual_range_start), assert_equal(-2147483640::bigint, actual_range_end)\nFROM _timescaledb_internal.dimension_calculate_default_range_open(-2147483648,10, 'int4') AS res(actual_range_start, actual_range_end);\n\nSELECT assert_equal(-9223372036854775808, actual_range_start), assert_equal(-2147483640::bigint, actual_range_end)\nFROM _timescaledb_internal.dimension_calculate_default_range_open(-2147483647,10, 'int4') AS res(actual_range_start, actual_range_end);\n\nSELECT assert_equal(2147483640::bigint, actual_range_start), assert_equal(9223372036854775807::bigint, actual_range_end)\nFROM _timescaledb_internal.dimension_calculate_default_range_open(2147483647,10, 'int4') AS res(actual_range_start, actual_range_end);\n\nSELECT assert_equal(2147483640::bigint, actual_range_start), assert_equal(9223372036854775807::bigint, actual_range_end)\nFROM _timescaledb_internal.dimension_calculate_default_range_open(2147483646,10, 'int4') AS res(actual_range_start, actual_range_end);\n\n---------------\n-- SMALLINT\n---------------\nSELECT assert_equal(-9223372036854775808, actual_range_start), assert_equal(-32760::bigint, actual_range_end)\nFROM _timescaledb_internal.dimension_calculate_default_range_open(-32768,10, 'int2') AS res(actual_range_start, actual_range_end);\n\nSELECT assert_equal(-9223372036854775808, actual_range_start), assert_equal(-32760::bigint, actual_range_end)\nFROM _timescaledb_internal.dimension_calculate_default_range_open(-32767,10, 'int2') AS res(actual_range_start, actual_range_end);\n\nSELECT assert_equal(32760::bigint, actual_range_start), assert_equal(9223372036854775807::bigint, actual_range_end)\nFROM _timescaledb_internal.dimension_calculate_default_range_open(32767,10, 'int2') AS res(actual_range_start, actual_range_end);\n\nSELECT assert_equal(32760::bigint, actual_range_start), assert_equal(9223372036854775807::bigint, actual_range_end)\nFROM _timescaledb_internal.dimension_calculate_default_range_open(32766,10, 'int2') AS res(actual_range_start, actual_range_end);\n\n\n--closed\nSELECT assert_equal((-9223372036854775808)::bigint, actual_range_start), assert_equal(1073741823::bigint, actual_range_end)\nFROM _timescaledb_internal.dimension_calculate_default_range_closed(0,2::smallint) AS res(actual_range_start, actual_range_end);\n\nSELECT assert_equal(1073741823::bigint, actual_range_start), assert_equal(9223372036854775807::bigint, actual_range_end)\nFROM _timescaledb_internal.dimension_calculate_default_range_closed(1073741824,2::smallint) AS res(actual_range_start, actual_range_end);\n\nSELECT assert_equal((-9223372036854775808)::bigint, actual_range_start), assert_equal(9223372036854775807::bigint, actual_range_end)\nFROM _timescaledb_internal.dimension_calculate_default_range_closed(1073741824,1::smallint) AS res(actual_range_start, actual_range_end);\n"
  },
  {
    "path": "test/sql/cluster.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE TABLE cluster_test(time timestamptz, temp float, location int);\n\nSELECT create_hypertable('cluster_test', 'time', chunk_time_interval => interval '1 day');\n\n-- Show default indexes\nSELECT * FROM test.show_indexes('cluster_test');\n\n-- Create two chunks\nINSERT INTO cluster_test VALUES ('2017-01-20T09:00:01', 23.4, 1),\n       ('2017-01-21T09:00:01', 21.3, 2);\n\n-- Run cluster\nCLUSTER VERBOSE cluster_test USING cluster_test_time_idx;\n\n-- Create a third chunk\nINSERT INTO cluster_test VALUES ('2017-01-22T09:00:01', 19.5, 3);\n\n-- Show clustered indexes\nSELECT indexrelid::regclass, indisclustered\nFROM pg_index\nWHERE indisclustered = true ORDER BY 1;\n\n-- Reorder just our table\nCLUSTER VERBOSE cluster_test;\n\n-- Show clustered indexes, including new chunk\nSELECT indexrelid::regclass, indisclustered\nFROM pg_index\nWHERE indisclustered = true ORDER BY 1;\n\n-- Reorder all tables (although will only be our test table)\nCLUSTER VERBOSE;\n\n-- Change the clustered index\nCREATE INDEX ON cluster_test (time, location);\n\nCLUSTER VERBOSE cluster_test using cluster_test_time_location_idx;\n\n-- Show updated clustered indexes\nSELECT indexrelid::regclass, indisclustered\nFROM pg_index\nWHERE indisclustered = true ORDER BY 1;\n\n--check the setting of cluster indexes on hypertables and chunks\nALTER TABLE cluster_test CLUSTER ON cluster_test_time_idx;\n\nSELECT indexrelid::regclass, indisclustered\nFROM pg_index\nWHERE indisclustered = true\nORDER BY 1,2;\n\nCLUSTER VERBOSE cluster_test;\n\nALTER TABLE cluster_test SET WITHOUT CLUSTER;\n\nSELECT indexrelid::regclass, indisclustered\nFROM pg_index\nWHERE indisclustered = true\nORDER BY 1,2;\n\n\\set ON_ERROR_STOP 0\nCLUSTER VERBOSE cluster_test;\n\\set ON_ERROR_STOP 1\n\nALTER TABLE _timescaledb_internal._hyper_1_1_chunk CLUSTER ON _hyper_1_1_chunk_cluster_test_time_idx;\n\nSELECT indexrelid::regclass, indisclustered\nFROM pg_index\nWHERE indisclustered = true\nORDER BY 1,2;\n\nCLUSTER VERBOSE _timescaledb_internal._hyper_1_1_chunk;\n\nALTER TABLE _timescaledb_internal._hyper_1_1_chunk SET WITHOUT CLUSTER;\n\nSELECT indexrelid::regclass, indisclustered\nFROM pg_index\nWHERE indisclustered = true\nORDER BY 1,2;\n\n\\set ON_ERROR_STOP 0\nCLUSTER VERBOSE _timescaledb_internal._hyper_1_1_chunk;\n\\set ON_ERROR_STOP 1\n\n-- test alter column type on hypertable with clustering\nCREATE TABLE cluster_alter(time timestamp, id text, val int);\nCREATE INDEX idstuff ON cluster_alter USING btree (id ASC NULLS LAST, time);\n\nSELECT table_name FROM create_hypertable('cluster_alter', 'time');\n\nINSERT INTO cluster_alter VALUES('2020-01-01', '123', 1);\n\nCLUSTER cluster_alter using idstuff;\n\n--attempt the alter table\nALTER TABLE cluster_alter ALTER COLUMN id TYPE int USING id::int;\n\nCLUSTER cluster_alter;\n\nCLUSTER cluster_alter using idstuff;\n"
  },
  {
    "path": "test/sql/constraint.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE TABLE hyper (\n  time BIGINT NOT NULL,\n  device_id TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1 CHECK (sensor_1 > 10)\n);\n\n\nSELECT * FROM create_hypertable('hyper', 'time', chunk_time_interval => 10);\n\n--check and not-null constraints are inherited through regular inheritance.\n\n\\set ON_ERROR_STOP 0\nINSERT INTO hyper(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 9);\n\nINSERT INTO hyper(time, device_id,sensor_1) VALUES\n(1257987700000000000, NULL, 11);\n\nALTER TABLE hyper ALTER COLUMN time DROP NOT NULL;\n\nALTER TABLE ONLY hyper ALTER COLUMN sensor_1 SET NOT NULL;\nALTER TABLE ONLY hyper ALTER COLUMN device_id DROP NOT NULL;\n\n\\set ON_ERROR_STOP 1\n\nINSERT INTO hyper(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 11);\n\nINSERT INTO hyper(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 11);\n\nALTER TABLE hyper ALTER COLUMN device_id DROP NOT NULL;\n\nINSERT INTO hyper(time, device_id,sensor_1) VALUES\n(1257987700000000000, NULL, 11);\n\n--make sure validate works\n\\set ON_ERROR_STOP 0\nALTER TABLE hyper ADD CONSTRAINT bad_check_const CHECK (sensor_1 > 100);\n\\set ON_ERROR_STOP 1\n\nALTER TABLE hyper ADD CONSTRAINT bad_check_const CHECK (sensor_1 > 100) NOT VALID;\n\n\\set ON_ERROR_STOP 0\nALTER TABLE hyper VALIDATE CONSTRAINT bad_check_const;\n\\set ON_ERROR_STOP 1\n----------------------- UNIQUE CONSTRAINTS ------------------\n\nCREATE TABLE hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name (\n  time BIGINT NOT NULL UNIQUE,\n  device_id TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1 CHECK (sensor_1 > 10)\n);\n\n\nSELECT * FROM create_hypertable('hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name', 'time', chunk_time_interval => 10);\n\nINSERT INTO hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 11);\n\nINSERT INTO hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name(time, device_id,sensor_1) VALUES\n(1257987800000000000, 'dev2', 11);\n\n\\set ON_ERROR_STOP 0\nINSERT INTO hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 11);\n\\set ON_ERROR_STOP 1\n\n-- Show constraints on main tables\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\nSELECT * FROM test.show_constraints('hyper');\nSELECT * FROM test.show_constraints('hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name');\n--should have unique constraint not just unique index\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_2_4_chunk');\n\nALTER TABLE hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name DROP CONSTRAINT hyper_unique_with_looooooooooooooooooooooooooooooooooo_time_key;\n-- The constraint should have been removed from the chunk as well\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_2_4_chunk');\n\n--uniqueness not enforced\nINSERT INTO hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev3', 11);\n\n--shouldn't be able to create constraint\n\\set ON_ERROR_STOP 0\nALTER TABLE hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name ADD CONSTRAINT hyper_unique_time_key UNIQUE (time);\n\\set ON_ERROR_STOP 1\n\nDELETE FROM hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name WHERE device_id = 'dev3';\n\n-- Try multi-alter table statement with a constraint without a name\nALTER TABLE hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\n      ADD CHECK (time > 0),\n      ADD UNIQUE (time) DEFERRABLE INITIALLY DEFERRED;\n\n\\set ON_ERROR_STOP 0\nBEGIN;\n--testing deferred checking. The following row has an error, which will not appear until the commit\nINSERT INTO hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev3', 11);\nSELECT 1;\nCOMMIT;\n\\set ON_ERROR_STOP 1\n\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\nSELECT * FROM test.show_constraints('hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name');\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_2_4_chunk');\n\nALTER TABLE  hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\nDROP CONSTRAINT hyper_unique_with_looooooooooooooooooooooooooooooooooo_time_key,\nDROP CONSTRAINT hyper_unique_with_looooooooooooooooooooooooooooooooo_time_check;\n\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\nSELECT * FROM test.show_constraints('hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name');\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_2_4_chunk');\n\nCREATE UNIQUE INDEX hyper_unique_with_looooooooooooooooooooooooooooooooo_time_idx\nON hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name (time);\n\n\\set ON_ERROR_STOP 0\n-- Try adding constraint using existing index\nALTER TABLE hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\nADD CONSTRAINT hyper_unique_with_looooooooooooooooooooooooooooooooooo_time_key UNIQUE\nUSING INDEX hyper_unique_with_looooooooooooooooooooooooooooooooo_time_idx;\n\\set ON_ERROR_STOP 1\nDROP INDEX hyper_unique_with_looooooooooooooooooooooooooooooooo_time_idx;\n\n--now can create\nALTER TABLE  hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\nADD CONSTRAINT hyper_unique_with_looooooooooooooooooooooooooooooooooo_time_key UNIQUE (time);\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_2_4_chunk');\n\n--test adding constraint with same name to different table -- should fail\n\\set ON_ERROR_STOP 0\nALTER TABLE hyper\nADD CONSTRAINT hyper_unique_with_looooooooooooooooooooooooooooooooooo_time_key UNIQUE (time);\n\\set ON_ERROR_STOP 1\n\n--uniquness violation fails\n\\set ON_ERROR_STOP 0\nINSERT INTO hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name(time, device_id,sensor_1)\nVALUES (1257987700000000000, 'dev2', 11);\n\\set ON_ERROR_STOP 1\n\n--cannot create unique constraint on non-partition column\n\\set ON_ERROR_STOP 0\nALTER TABLE hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\nADD CONSTRAINT hyper_unique_invalid UNIQUE (device_id);\n\nALTER TABLE hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\nADD COLUMN new_device_id int UNIQUE;\n\nALTER TABLE hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\nDROP COLUMN device_id,\nADD COLUMN new_device_id int UNIQUE;\n\\set ON_ERROR_STOP 1\n\n----------------------- RENAME CONSTRAINT  ------------------\n\nALTER TABLE hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\nRENAME CONSTRAINT hyper_unique_with_looooooooooooooooooooooooooooooooooo_time_key TO new_name;\n\nALTER TABLE hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name *\nRENAME CONSTRAINT new_name TO new_name2;\n\nALTER TABLE IF EXISTS hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\nRENAME CONSTRAINT  hyper_unique_with_looooooooooooooooooooooooooooo_sensor_1_check TO check_2;\n\n\nSELECT * FROM test.show_constraints('hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name');\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_2_4_chunk');\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\n\n\\set ON_ERROR_STOP 0\nALTER TABLE hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\nRENAME CONSTRAINT new_name TO new_name2;\nALTER TABLE hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\nRENAME CONSTRAINT new_name2 TO check_2;\nALTER TABLE ONLY hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\nRENAME CONSTRAINT new_name2 TO new_name;\nALTER TABLE _timescaledb_internal._hyper_2_4_chunk\nRENAME CONSTRAINT \"4_10_new_name2\" TO new_name;\n\\set ON_ERROR_STOP 1\n\n----------------------- PRIMARY KEY  ------------------\n\nCREATE TABLE hyper_pk (\n  time BIGINT NOT NULL PRIMARY KEY,\n  device_id TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1 CHECK (sensor_1 > 10)\n);\n\n\nSELECT * FROM create_hypertable('hyper_pk', 'time', chunk_time_interval => 10);\n\nINSERT INTO hyper_pk(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 11);\n\n\\set ON_ERROR_STOP 0\nINSERT INTO hyper_pk(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 11);\n\\set ON_ERROR_STOP 1\n\n--should have unique constraint not just unique index\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_3_6_chunk');\n\nALTER TABLE hyper_pk DROP CONSTRAINT hyper_pk_pkey;\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_3_6_chunk');\n\n--uniqueness not enforced\nINSERT INTO hyper_pk(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev3', 11);\n\n\n--shouldn't be able to create pk\n\\set ON_ERROR_STOP 0\nALTER TABLE hyper_pk ADD CONSTRAINT hyper_pk_pkey PRIMARY KEY (time);\n\nALTER TABLE hyper_unique_with_looooooooooooooooooooooooooooooooooooong_name\nADD COLUMN new_device_id int PRIMARY KEY;\n\\set ON_ERROR_STOP 1\n\nDELETE FROM hyper_pk WHERE device_id = 'dev3';\n\n--cannot create pk constraint on non-partition column\n\\set ON_ERROR_STOP 0\nALTER TABLE hyper_pk ADD CONSTRAINT hyper_pk_invalid PRIMARY KEY (device_id);\n\\set ON_ERROR_STOP 1\n\n--now can create\nALTER TABLE hyper_pk ADD CONSTRAINT hyper_pk_pkey PRIMARY KEY (time) DEFERRABLE INITIALLY DEFERRED;\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_3_6_chunk');\n\n--test adding constraint with same name to different table -- should fail\n\\set ON_ERROR_STOP 0\nALTER TABLE hyper ADD CONSTRAINT hyper_pk_pkey UNIQUE (time);\n\\set ON_ERROR_STOP 1\n\n--uniquness violation fails\n\\set ON_ERROR_STOP 0\nBEGIN;\n  --error here deferred until commit\n  INSERT INTO hyper_pk(time, device_id,sensor_1) VALUES\n  (1257987700000000000, 'dev2', 11);\n  SELECT 1;\nCOMMIT;\n\\set ON_ERROR_STOP 1\n\n\n----------------------- FOREIGN KEY  ------------------\n\nCREATE TABLE devices(\n    device_id TEXT NOT NULL,\n    PRIMARY KEY (device_id)\n);\n\nCREATE TABLE hyper_fk (\n  time BIGINT NOT NULL PRIMARY KEY,\n  device_id TEXT NOT NULL REFERENCES devices(device_id),\n  sensor_1 NUMERIC NULL DEFAULT 1 CHECK (sensor_1 > 10)\n);\n\n\nSELECT * FROM create_hypertable('hyper_fk', 'time', chunk_time_interval => 10);\n\n--fail fk constraint\n\\set ON_ERROR_STOP 0\nINSERT INTO hyper_fk(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 11);\n\\set ON_ERROR_STOP 1\n\nINSERT INTO devices VALUES ('dev2');\n\nINSERT INTO hyper_fk(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 11);\n\n--delete should fail\n\\set ON_ERROR_STOP 0\nDELETE FROM devices;\n\\set ON_ERROR_STOP 1\n\nALTER TABLE hyper_fk DROP CONSTRAINT hyper_fk_device_id_fkey;\n\n--should now be able to add non-fk rows\nINSERT INTO hyper_fk(time, device_id,sensor_1) VALUES\n(1257987700000000001, 'dev3', 11);\n\n--can't add fk because of dev3 row\n\\set ON_ERROR_STOP 0\nALTER TABLE hyper_fk ADD CONSTRAINT hyper_fk_device_id_fkey\nFOREIGN KEY (device_id) REFERENCES devices(device_id);\n\\set ON_ERROR_STOP 1\n\n--but can add a NOT VALID one\nALTER TABLE hyper_fk ADD CONSTRAINT hyper_fk_device_id_fkey\nFOREIGN KEY (device_id) REFERENCES devices(device_id) NOT VALID;\n\n--which will fail when validated\n\\set ON_ERROR_STOP 0\nALTER TABLE hyper_fk VALIDATE CONSTRAINT hyper_fk_device_id_fkey;\n\\set ON_ERROR_STOP 1\n\nALTER TABLE hyper_fk DROP CONSTRAINT hyper_fk_device_id_fkey;\n\nDELETE FROM hyper_fk WHERE device_id = 'dev3';\n\nALTER TABLE hyper_fk ADD CONSTRAINT hyper_fk_device_id_fkey\nFOREIGN KEY (device_id) REFERENCES devices(device_id);\n\n\\set ON_ERROR_STOP 0\nINSERT INTO hyper_fk(time, device_id,sensor_1) VALUES\n(1257987700000000002, 'dev3', 11);\n\\set ON_ERROR_STOP 1\n\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_4_8_chunk');\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\n\n--test CASCADE drop behavior\nDROP TABLE devices CASCADE;\n\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_4_8_chunk');\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\n\n--the fk went away.\nINSERT INTO hyper_fk(time, device_id,sensor_1) VALUES\n(1257987700000000002, 'dev3', 11);\n\nCREATE TABLE devices(\n    device_id TEXT NOT NULL,\n    PRIMARY KEY (device_id)\n);\n\nINSERT INTO devices VALUES ('dev2'), ('dev3');\n\nALTER TABLE hyper_fk ADD CONSTRAINT hyper_fk_device_id_fkey\nFOREIGN KEY (device_id) REFERENCES devices(device_id) DEFERRABLE INITIALLY DEFERRED;\n\n\\set ON_ERROR_STOP 0\nBEGIN;\n  --error deferred until commmit\n  INSERT INTO hyper_fk(time, device_id,sensor_1) VALUES\n  (1257987700000000003, 'dev4', 11);\n  SELECT 1;\nCOMMIT;\n\\set ON_ERROR_STOP 1\n\nALTER TABLE hyper_fk ALTER CONSTRAINT hyper_fk_device_id_fkey NOT DEFERRABLE;\n\n\\set ON_ERROR_STOP 0\nBEGIN;\n  --error detected right away\n  INSERT INTO hyper_fk(time, device_id,sensor_1) VALUES\n  (1257987700000000003, 'dev4', 11);\n  SELECT 1;\nCOMMIT;\n\\set ON_ERROR_STOP 1\n\n--this tests that there are no extra chunk_constraints left on hyper_fk\nTRUNCATE hyper_fk;\n\n----------------------- FOREIGN KEY INTO A HYPERTABLE  ------------------\n\n\n--FOREIGN KEY references into a hypertable are currently broken.\n--The referencing table will never find the corresponding row in the hypertable\n--since it will only search the parent. Thus any insert will result in an ERROR\n--Block such foreign keys or fix. (Hard to block on create table so punting for now)\nCREATE TABLE hyper_for_ref (\n  time BIGINT NOT NULL PRIMARY KEY,\n  device_id TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1 CHECK (sensor_1 > 10)\n);\n\nSELECT * FROM create_hypertable('hyper_for_ref', 'time', chunk_time_interval => 10);\n\n\\set ON_ERROR_STOP 0\nCREATE TABLE referrer (\n    time BIGINT NOT NULL REFERENCES hyper_for_ref(time)\n);\n\\set ON_ERROR_STOP 1\n\nCREATE TABLE referrer2 (\n   time BIGINT NOT NULL\n);\n\n\\set ON_ERROR_STOP 0\nALTER TABLE referrer2 ADD CONSTRAINT hyper_fk_device_id_fkey\nFOREIGN KEY (time) REFERENCES  hyper_for_ref(time);\n\\set ON_ERROR_STOP 1\n\n-- github issue 8082: FK referencing hypertable with composite unique index\n-- fails on first insert because chunk indexes are created after FK propagation\nCREATE TABLE messages_ref (\n    time_received TIMESTAMPTZ NOT NULL,\n    message_id BIGSERIAL,\n    message_type SMALLINT NOT NULL\n);\n\nSELECT create_hypertable('messages_ref', by_range('time_received'));\nCREATE UNIQUE INDEX ON messages_ref(time_received, message_id);\n\n-- Create FK referencing the hypertable BEFORE any data exists\nCREATE TABLE contents_ref (\n    content_id BIGSERIAL,\n    time_received TIMESTAMPTZ NOT NULL,\n    message_id BIGINT NOT NULL,\n    content CHAR(10),\n    FOREIGN KEY (time_received, message_id) REFERENCES messages_ref(time_received, message_id) ON DELETE CASCADE\n);\n\n-- This insert creates a new chunk. Previously it would fail with\n-- \"index for constraint not found on chunk\" because FK propagation\n-- happened before chunk indexes were created.\nINSERT INTO messages_ref (time_received, message_type) VALUES ('2025-05-05 14:56:58.000 UTC', 2);\nINSERT INTO contents_ref (message_id, time_received, content) VALUES (CURRVAL('messages_ref_message_id_seq'), '2025-05-05 14:56:58.000 UTC', 'HEJ');\n\n-- Insert into a second chunk\nINSERT INTO messages_ref (time_received, message_type) VALUES ('2025-06-05 14:57:58.000 UTC', 3);\nINSERT INTO contents_ref (message_id, time_received, content) VALUES (CURRVAL('messages_ref_message_id_seq'), '2025-06-05 14:57:58.000 UTC', 'HEJ2');\n\n-- Verify data\nSELECT message_type FROM messages_ref ORDER BY time_received;\nSELECT content FROM contents_ref ORDER BY time_received;\n\n-- Verify FK enforcement\n\\set ON_ERROR_STOP 0\nINSERT INTO contents_ref (message_id, time_received, content) VALUES (9999, '2025-05-05 14:56:58.000 UTC', 'FAIL');\n\\set ON_ERROR_STOP 1\n\n-- Verify cascade delete\nDELETE FROM messages_ref WHERE message_type = 2;\nSELECT content FROM contents_ref ORDER BY time_received;\n\nDROP TABLE contents_ref;\nDROP TABLE messages_ref;\n\n----------------------- EXCLUSION CONSTRAINT  ------------------\n\nCREATE TABLE hyper_ex (\n    time BIGINT,\n    device_id TEXT NOT NULL REFERENCES devices(device_id),\n    sensor_1 NUMERIC NULL DEFAULT 1 CHECK (sensor_1 > 10),\n    canceled boolean DEFAULT false,\n    EXCLUDE USING btree (\n        time WITH =, device_id WITH =\n    ) WHERE (not canceled)\n);\n\nSELECT * FROM create_hypertable('hyper_ex', 'time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\nINSERT INTO hyper_ex(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 11);\n\n\\set ON_ERROR_STOP 0\nINSERT INTO hyper_ex(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 12);\n\\set ON_ERROR_STOP 1\n\nALTER TABLE hyper_ex DROP CONSTRAINT hyper_ex_time_device_id_excl;\n\n--can now add\nINSERT INTO hyper_ex(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 12);\n\n--cannot add because of conflicts\n\\set ON_ERROR_STOP 0\nALTER TABLE hyper_ex ADD CONSTRAINT hyper_ex_time_device_id_excl\n    EXCLUDE USING btree (\n        time WITH =, device_id WITH =\n    ) WHERE (not canceled)\n;\n\\set ON_ERROR_STOP 1\n\nDELETE FROM hyper_ex WHERE sensor_1 = 12;\n\nALTER TABLE hyper_ex ADD CONSTRAINT hyper_ex_time_device_id_excl\n    EXCLUDE USING btree (\n        time WITH =, device_id WITH =\n    ) WHERE (not canceled) DEFERRABLE INITIALLY DEFERRED\n;\n\n\\set ON_ERROR_STOP 0\nBEGIN;\n  --error deferred til commit\n  INSERT INTO hyper_ex(time, device_id,sensor_1) VALUES\n  (1257987700000000000, 'dev2', 12);\n  SELECT 1;\nCOMMIT;\n\\set ON_ERROR_STOP 1\n\n\n--cannot add exclusion constraint without partition key.\nCREATE TABLE hyper_ex_invalid (\n    time BIGINT,\n    device_id TEXT NOT NULL REFERENCES devices(device_id),\n    sensor_1 NUMERIC NULL DEFAULT 1 CHECK (sensor_1 > 10),\n    canceled boolean DEFAULT false,\n    EXCLUDE USING btree (\n        device_id WITH =\n    ) WHERE (not canceled)\n);\n\n\\set ON_ERROR_STOP 0\nSELECT * FROM create_hypertable('hyper_ex_invalid', 'time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\\set ON_ERROR_STOP 1\n\n\n--- NO INHERIT constraints (not allowed) ----\nCREATE TABLE hyper_noinherit (\n    time BIGINT,\n    sensor_1 NUMERIC NULL DEFAULT 1 CHECK (sensor_1 > 0) NO INHERIT\n);\n\nSELECT * FROM test.show_constraints('hyper_noinherit');\n\n\\set ON_ERROR_STOP 0\nSELECT * FROM create_hypertable('hyper_noinherit', 'time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\\set ON_ERROR_STOP 1\n\nCREATE TABLE hyper_noinherit_alter (\n    time BIGINT,\n    sensor_1 NUMERIC NULL DEFAULT 1\n);\n\nSELECT * FROM create_hypertable('hyper_noinherit_alter', 'time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\n\\set ON_ERROR_STOP 0\nALTER TABLE hyper_noinherit_alter ADD CONSTRAINT check_noinherit CHECK (sensor_1 > 0) NO INHERIT;\n--  CREATE TABLE WITH DEFERRED CONSTRAINTS --\n\nCREATE TABLE hyper_unique_deferred (\n  time BIGINT UNIQUE DEFERRABLE INITIALLY DEFERRED,\n  device_id TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1 CHECK (sensor_1 > 10)\n);\n\nSELECT * FROM create_hypertable('hyper_unique_deferred', 'time', chunk_time_interval => 10);\n\nINSERT INTO hyper_unique_deferred(time, device_id,sensor_1) VALUES (1257987700000000000, 'dev2', 11);\n\n\\set ON_ERROR_STOP 0\nBEGIN;\n  --error here deferred until commit\n  INSERT INTO hyper_unique_deferred(time, device_id,sensor_1) VALUES (1257987700000000000, 'dev2', 11);\n  SELECT 1;\nCOMMIT;\n\\set ON_ERROR_STOP 1\n\n--test deferred on create table\nCREATE TABLE hyper_pk_deferred (\n  time BIGINT NOT NULL PRIMARY KEY DEFERRABLE INITIALLY DEFERRED,\n  device_id TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1 CHECK (sensor_1 > 10)\n);\n\nSELECT * FROM create_hypertable('hyper_pk_deferred', 'time', chunk_time_interval => 10);\n\nINSERT INTO hyper_pk_deferred(time, device_id,sensor_1) VALUES (1257987700000000000, 'dev2', 11);\n\n\\set ON_ERROR_STOP 0\nBEGIN;\n  --error here deferred until commit\n  INSERT INTO hyper_pk_deferred(time, device_id,sensor_1) VALUES (1257987700000000000, 'dev2', 11);\n  SELECT 1;\nCOMMIT;\n\\set ON_ERROR_STOP 1\n\n--test that deferred works on create table too\nCREATE TABLE hyper_fk_deferred (\n  time BIGINT NOT NULL PRIMARY KEY,\n  device_id TEXT NOT NULL REFERENCES devices(device_id) DEFERRABLE INITIALLY DEFERRED,\n  sensor_1 NUMERIC NULL DEFAULT 1 CHECK (sensor_1 > 10)\n);\n\nSELECT * FROM create_hypertable('hyper_fk_deferred', 'time', chunk_time_interval => 10);\n\n\\set ON_ERROR_STOP 0\nBEGIN;\n  --error deferred until commmit\n  INSERT INTO hyper_fk_deferred(time, device_id,sensor_1) VALUES (1257987700000000003, 'dev4', 11);\n  SELECT 1;\nCOMMIT;\n\\set ON_ERROR_STOP 1\n\nCREATE TABLE hyper_ex_deferred (\n    time BIGINT,\n    device_id TEXT NOT NULL REFERENCES devices(device_id),\n    sensor_1 NUMERIC NULL DEFAULT 1 CHECK (sensor_1 > 10),\n    canceled boolean DEFAULT false,\n    EXCLUDE USING btree (\n        time WITH =, device_id WITH =\n    ) WHERE (not canceled) DEFERRABLE INITIALLY DEFERRED\n);\n\nSELECT * FROM create_hypertable('hyper_ex_deferred', 'time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\nINSERT INTO hyper_ex_deferred(time, device_id,sensor_1) VALUES (1257987700000000000, 'dev2', 12);\n\n\\set ON_ERROR_STOP 0\nBEGIN;\n  --error deferred til commit\n  INSERT INTO hyper_ex_deferred(time, device_id,sensor_1) VALUES (1257987700000000000, 'dev2', 12);\n  SELECT 1;\nCOMMIT;\n\\set ON_ERROR_STOP 1\n\n-- Make sure renaming schemas won't break dropping constraints\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE TABLE hyper_unique (\n  time BIGINT NOT NULL UNIQUE,\n  device_id TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1 CHECK (sensor_1 > 10)\n);\n\nSELECT * FROM create_hypertable('hyper_unique', 'time', chunk_time_interval => 10, associated_schema_name => 'my_associated_schema');\n\nINSERT INTO hyper_unique(time, device_id,sensor_1) VALUES (1257987700000000000, 'dev2', 11);\n\nALTER SCHEMA my_associated_schema RENAME TO new_associated_schema;\n\nALTER TABLE hyper_unique DROP CONSTRAINT hyper_unique_time_key;\n\n-- test for constraint validation crash, see #1183\nCREATE TABLE test_validate(time timestamp NOT NULL, a TEXT, b TEXT);\nSELECT * FROM create_hypertable('test_validate', 'time');\nINSERT INTO test_validate values(now(), 'a', 'b');\n\nALTER TABLE test_validate\nADD COLUMN c TEXT,\nADD CONSTRAINT c_not_null CHECK (c IS NOT NULL) NOT VALID;\n\nUPDATE test_validate SET c = '';\n\nALTER TABLE test_validate\nVALIDATE CONSTRAINT c_not_null;\n\nDROP TABLE test_validate;\n\n-- test for hypertables constraints both using index tablespaces and not See #2604\nSET client_min_messages = ERROR;\nDROP TABLESPACE IF EXISTS tablespace1;\nSET client_min_messages = NOTICE;\nCREATE TABLESPACE tablespace1 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE1_PATH;\n\nCREATE TABLE fk_tbl (\nid int,\nCONSTRAINT pkfk PRIMARY KEY (id) USING INDEX TABLESPACE tablespace1);\n\nCREATE TABLE tbl (\nfk_id int,\nid int,\ntime timestamp,\nCONSTRAINT pk PRIMARY KEY (time, id) USING INDEX TABLESPACE tablespace1 DEFERRABLE INITIALLY DEFERRED);\n\nSELECT create_hypertable('tbl', 'time');\n\nALTER TABLE tbl\nADD CONSTRAINT fk_con\nFOREIGN KEY (fk_id) REFERENCES fk_tbl(id)\nON UPDATE SET NULL\nON DELETE SET NULL;\n\nINSERT INTO fk_tbl VALUES(1);\n\nINSERT INTO tbl VALUES (\n1, 1, now()\n);\n\nDROP TABLE tbl;\nDROP TABLE fk_tbl;\n\nDROP TABLESPACE IF EXISTS tablespace1;\n"
  },
  {
    "path": "test/sql/copy.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\o /dev/null\n\\ir include/insert_two_partitions.sql\n\\o\n\n--old chunks\nCOPY \"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) FROM STDIN DELIMITER ',';\n1257894000000000000,dev3,1.5,2\n\\.\n\\copy \"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) FROM STDIN DELIMITER ',';\n1257894000000000000,dev3,1.5,2\n\\.\n\n--new chunks\nCOPY \"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) FROM STDIN DELIMITER ',';\n2257894000000000000,dev3,1.5,2\n\\.\n\\copy \"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) FROM STDIN DELIMITER ',';\n2257894000000000000,dev3,1.5,2\n\\.\n\nCOPY (SELECT * FROM \"two_Partitions\" ORDER BY \"timeCustom\", device_id, series_0, series_1) TO STDOUT;\n\n\n---test hypertable with FK\nCREATE TABLE \"meta\" (\"id\" serial PRIMARY KEY);\nCREATE TABLE \"hyper\" (\n    \"meta_id\" integer NOT NULL REFERENCES meta(id),\n    \"time\" bigint NOT NULL,\n    \"value\" double precision NOT NULL\n);\nSELECT create_hypertable('hyper', 'time', chunk_time_interval => 100);\n\nINSERT INTO \"meta\" (\"id\") values (1);\n\\copy hyper (time, meta_id, value) FROM STDIN DELIMITER ',';\n1,1,1\n\\.\n\nCOPY hyper (time, meta_id, value) FROM STDIN DELIMITER ',';\n2,1,1\n\\.\n\n\\set ON_ERROR_STOP 0\n\\copy hyper (time, meta_id, value) FROM STDIN DELIMITER ',';\n1,2,1\n\\.\nCOPY hyper (time, meta_id, value) FROM STDIN DELIMITER ',';\n2,2,1\n\\.\n\\set ON_ERROR_STOP 1\n\nCOPY (SELECT * FROM hyper ORDER BY time, meta_id) TO STDOUT;\n\n--test that copy works with a low setting for max_open_chunks_per_insert\nset timescaledb.max_open_chunks_per_insert = 1;\nCREATE TABLE \"hyper2\" (\n    \"time\" bigint NOT NULL,\n    \"value\" double precision NOT NULL\n);\nSELECT create_hypertable('hyper2', 'time', chunk_time_interval => 10);\n\\copy hyper2 from data/copy_data.csv with csv header ;\n\n-- test copy with blocking trigger\nCREATE FUNCTION gt_10() RETURNS trigger AS\n$func$\nBEGIN\n    IF NEW.\"time\" < 11\n        THEN RETURN NULL;\n    END IF;\n    RETURN NEW;\nEND\n$func$ LANGUAGE plpgsql;\n\nCREATE TABLE \"trigger_test\" (\n    \"time\" bigint NOT NULL,\n    \"value\" double precision NOT NULL\n);\nSELECT create_hypertable('trigger_test', 'time', chunk_time_interval => 10);\nCREATE TRIGGER check_time BEFORE INSERT ON trigger_test\nFOR EACH ROW EXECUTE FUNCTION gt_10();\n\n\\copy trigger_test from data/copy_data.csv with csv header ;\nSELECT * FROM trigger_test ORDER BY time;\n\n-- Test that if we copy from stdin to a hypertable and violate a null\n-- constraint, it does not crash and generate an appropriate error\n-- message.\nCREATE TABLE test(a INT NOT NULL, b TIMESTAMPTZ);\nSELECT create_hypertable('test', 'b');\n\\set ON_ERROR_STOP 0\nCOPY TEST (a,b) FROM STDIN (delimiter ',', null 'N');\nN,'2020-01-01'\n\\.\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET client_min_messages TO NOTICE;\n\n-- Do a basic test of COPY with a wrong PROGRAM\nCOPY hyper FROM PROGRAM 'error';\n\\set ON_ERROR_STOP 1\n\n----------------------------------------------------------------\n-- Testing COPY TO.\n----------------------------------------------------------------\n\n-- COPY TO using a hypertable will not copy any tuples, but should\n-- show a notice.\nCOPY hyper TO STDOUT DELIMITER ',';\n\n-- COPY TO using a query should display all the tuples and not show a\n-- notice.\nCOPY (SELECT * FROM hyper) TO STDOUT DELIMITER ',';\n\n----------------------------------------------------------------\n-- Testing multi-buffer optimization.\n----------------------------------------------------------------\n\nCREATE TABLE \"hyper_copy\" (\n    \"time\" bigint NOT NULL,\n    \"value\" double precision NOT NULL\n);\n\nSELECT create_hypertable('hyper_copy', 'time', chunk_time_interval => 2);\n\n-- First copy call with default client_min_messages, to get rid of the\n-- building index \"_hyper_XXX_chunk_hyper_copy_time_idx\" on table \"_hyper_XXX_chunk\" serially\n-- messages\n\\copy hyper_copy FROM data/copy_data.csv WITH csv header;\n\nSET client_min_messages TO DEBUG1;\n\\copy hyper_copy FROM data/copy_data.csv WITH csv header;\n\nSELECT count(*) FROM hyper_copy;\n\n-- Limit number of open chunks\n\nSET timescaledb.max_open_chunks_per_insert = 1;\n\n\\copy hyper_copy FROM data/copy_data.csv WITH csv header;\n\nSELECT count(*) FROM hyper_copy;\n\n-- Before trigger disable the multi-buffer optimization\n\nCREATE OR REPLACE FUNCTION empty_test_trigger()\n    RETURNS TRIGGER LANGUAGE PLPGSQL AS\n$BODY$\nBEGIN\n    IF TG_OP = 'DELETE' THEN\n        RETURN OLD;\n    END IF;\n    RETURN NEW;\nEND\n$BODY$;\n\n-- Before trigger (CIM_SINGLE should be used)\nCREATE TRIGGER hyper_copy_trigger_insert_before\n    BEFORE INSERT ON hyper_copy\n    FOR EACH ROW EXECUTE FUNCTION empty_test_trigger();\n\n\\copy hyper_copy FROM data/copy_data.csv WITH csv header;\n\nSELECT count(*) FROM hyper_copy;\n\n-- Suppress 'DEBUG:  EventTriggerInvoke XXXX' messages\nRESET client_min_messages;\nDROP TRIGGER hyper_copy_trigger_insert_before ON hyper_copy;\nSET client_min_messages TO DEBUG1;\n\n-- After trigger (CIM_MULTI_CONDITIONAL should be used)\nCREATE TRIGGER hyper_copy_trigger_insert_after\n    AFTER INSERT ON hyper_copy\n    FOR EACH ROW EXECUTE FUNCTION empty_test_trigger();\n\n\\copy hyper_copy FROM data/copy_data.csv WITH csv header;\n\nSELECT count(*) FROM hyper_copy;\n\n-- Insert data into the chunks in random order\nCOPY hyper_copy FROM STDIN DELIMITER ',' NULL AS 'null';\n5,1\n7,1\n1,1\n0,5\n15,3\n0,7\n17,2\n20,1\n5,6\n19,1\n18,2\n17,1\n16,1\n15,1\n14,1\n13,1\n12,1\n11,1\n10,1\n11,1\n12,2\n13,2\n14,2\n15,2\n16,2\n17,2\n18,2\n19,2\n20,2\n\\.\n\nSELECT count(*) FROM hyper_copy;\n\nRESET client_min_messages;\nRESET timescaledb.max_open_chunks_per_insert;\n\n----------------------------------------------------------------\n-- Testing multi-buffer optimization\n-- (no index on destination hypertable).\n----------------------------------------------------------------\n\nCREATE TABLE \"hyper_copy_noindex\" (\n    \"time\" bigint NOT NULL,\n    \"value\" double precision NOT NULL\n);\n\nSELECT create_hypertable('hyper_copy_noindex', 'time', chunk_time_interval => 10, create_default_indexes => false);\n\n-- No trigger\n\\copy hyper_copy_noindex FROM data/copy_data.csv WITH csv header;\n\nSET client_min_messages TO DEBUG1;\n\\copy hyper_copy_noindex FROM data/copy_data.csv WITH csv header;\nRESET client_min_messages;\n\nSELECT count(*) FROM hyper_copy_noindex;\n\n-- Before trigger (CIM_SINGLE should be used)\nCREATE TRIGGER hyper_copy_trigger_insert_before\n    BEFORE INSERT ON hyper_copy_noindex\n    FOR EACH ROW EXECUTE FUNCTION empty_test_trigger();\n\\copy hyper_copy_noindex FROM data/copy_data.csv WITH csv header;\n\nSET client_min_messages TO DEBUG1;\n\\copy hyper_copy_noindex FROM data/copy_data.csv WITH csv header;\nRESET client_min_messages;\n\nSELECT count(*) FROM hyper_copy_noindex;\n\n-- After trigger (CIM_MULTI_CONDITIONAL should be used)\nDROP TRIGGER hyper_copy_trigger_insert_before ON hyper_copy_noindex;\nCREATE TRIGGER hyper_copy_trigger_insert_after\n    AFTER INSERT ON hyper_copy_noindex\n    FOR EACH ROW EXECUTE FUNCTION empty_test_trigger();\n\\copy hyper_copy_noindex FROM data/copy_data.csv WITH csv header;\n\nSET client_min_messages TO DEBUG1;\n\\copy hyper_copy_noindex FROM data/copy_data.csv WITH csv header;\nRESET client_min_messages;\n\nSELECT count(*) FROM hyper_copy_noindex;\n\n----------------------------------------------------------------\n-- Testing multi-buffer optimization\n-- (more chunks than MAX_PARTITION_BUFFERS).\n----------------------------------------------------------------\n\nCREATE TABLE \"hyper_copy_large\" (\n    \"time\" timestamp NOT NULL,\n    \"value\" double precision NOT NULL\n);\n\n-- Genate data that will create more than 32 (MAX_PARTITION_BUFFERS)\n-- chunks on the 10 second chunk_time_interval partitioned hypertable.\nINSERT INTO hyper_copy_large\nSELECT time,\nrandom() AS value\nFROM\ngenerate_series('2022-01-01', '2022-01-31', INTERVAL '1 hour') AS g1(time)\nORDER BY time;\n\nSELECT COUNT(*) FROM hyper_copy_large;\n\n-- Migrate data to chunks by using copy\nSELECT create_hypertable('hyper_copy_large', 'time',\n   chunk_time_interval => INTERVAL '1 hour', migrate_data => 'true');\n\nSELECT COUNT(*) FROM hyper_copy_large;\n\n----------------------------------------------------------------\n-- Testing multi-buffer optimization\n-- (triggers on chunks).\n----------------------------------------------------------------\n\nCREATE TABLE \"table_with_chunk_trigger\" (\n    \"time\" bigint NOT NULL,\n    \"value\" double precision NOT NULL\n);\n\n-- This trigger counts the already inserted tuples in\n-- the table table_with_chunk_trigger.\nCREATE OR REPLACE FUNCTION count_test_chunk_trigger()\n    RETURNS TRIGGER LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n    cnt INTEGER;\nBEGIN\n    SELECT count(*) FROM table_with_chunk_trigger INTO cnt;\n    RAISE WARNING 'Trigger counted % tuples in table table_with_chunk_trigger', cnt;\n    IF TG_OP = 'DELETE' THEN\n        RETURN OLD;\n    END IF;\n    RETURN NEW;\nEND\n$BODY$;\n\n-- Create hypertable and chunks\nSELECT create_hypertable('table_with_chunk_trigger', 'time', chunk_time_interval => 1);\n\n-- Insert data to create all missing chunks\n\\copy table_with_chunk_trigger from data/copy_data.csv with csv header;\nSELECT count(*) FROM table_with_chunk_trigger;\n\n-- Chunk 1: 1-2, Chunk 2: 2-3, Chunk 3: 3-4, Chunk 4: 4-5\nSELECT chunk_schema, chunk_name FROM timescaledb_information.chunks\n    WHERE hypertable_name = 'table_with_chunk_trigger' AND range_end_integer=5 \\gset\n\n-- Create before trigger on the 4th chunk\nCREATE TRIGGER table_with_chunk_trigger_before_trigger\n    BEFORE INSERT ON :chunk_schema.:chunk_name\n    FOR EACH ROW EXECUTE FUNCTION count_test_chunk_trigger();\n\n-- Insert data\n-- 25 tuples are already imported. The trigger is executed before tuples\n-- are copied into the 4th chunk. So, the trigger should report 25+3 = 28\n-- This test requires that the multi-insert buffers of the other chunks\n-- are flushed before the trigger is executed.\nSET client_min_messages TO DEBUG1;\n\\copy table_with_chunk_trigger FROM data/copy_data.csv WITH csv header;\nRESET client_min_messages;\n\nSELECT count(*) FROM table_with_chunk_trigger;\nDROP TRIGGER table_with_chunk_trigger_before_trigger ON :chunk_schema.:chunk_name;\n\n-- Create after trigger\nCREATE TRIGGER table_with_chunk_trigger_after_trigger\n    AFTER INSERT ON :chunk_schema.:chunk_name\n    FOR EACH ROW EXECUTE FUNCTION count_test_chunk_trigger();\n\n-- Insert data\n-- 50 tuples are already imported. The trigger is executed after all\n-- tuples are imported. So, the trigger should report 50+25 = 75\nSET client_min_messages TO DEBUG1;\n\\copy table_with_chunk_trigger FROM data/copy_data.csv WITH csv header;\nRESET client_min_messages;\n\nSELECT count(*) FROM table_with_chunk_trigger;\n\n-- Hypertable with after row trigger and no index\nDROP TABLE table_with_chunk_trigger;\nCREATE TABLE \"table_with_chunk_trigger\" (\n    \"time\" bigint NOT NULL,\n    \"value\" double precision NOT NULL\n);\n\n-- Create hypertable and chunks\nSELECT create_hypertable('table_with_chunk_trigger', 'time', chunk_time_interval => 1, create_default_indexes => false);\n\n-- Insert data to create all missing chunks\n\\copy table_with_chunk_trigger from data/copy_data.csv with csv header;\nSELECT count(*) FROM table_with_chunk_trigger;\n\n-- Chunk 1: 1-2, Chunk 2: 2-3, Chunk 3: 3-4, Chunk 4: 4-5\nSELECT chunk_schema, chunk_name FROM timescaledb_information.chunks\n    WHERE hypertable_name = 'table_with_chunk_trigger' AND range_end_integer=5 \\gset\n\n-- Create after trigger\nCREATE TRIGGER table_with_chunk_trigger_after_trigger\n    AFTER INSERT ON :chunk_schema.:chunk_name\n    FOR EACH ROW EXECUTE FUNCTION count_test_chunk_trigger();\n\n\\copy table_with_chunk_trigger from data/copy_data.csv with csv header;\nSELECT count(*) FROM table_with_chunk_trigger;\n\n----------------------------------------------------------------\n-- Testing multi-buffer optimization\n-- (Hypertable without before insert trigger)\n----------------------------------------------------------------\nCREATE TABLE \"table_without_bf_trigger\" (\n    \"time\" bigint NOT NULL,\n    \"value\" double precision NOT NULL\n);\n\nSELECT create_hypertable('table_without_bf_trigger', 'time', chunk_time_interval => 1);\n\n\\copy table_without_bf_trigger from data/copy_data.csv with csv header;\n\nSET client_min_messages TO DEBUG1;\n\\copy table_without_bf_trigger from data/copy_data.csv with csv header;\nRESET client_min_messages;\n\nSELECT count(*) FROM table_without_bf_trigger;\n\n-- After trigger (CIM_MULTI_CONDITIONAL should be used)\nCREATE TRIGGER table_with_chunk_trigger_after_trigger\n    AFTER INSERT ON table_without_bf_trigger\n    FOR EACH ROW EXECUTE FUNCTION empty_test_trigger();\n\nSET client_min_messages TO DEBUG1;\n\\copy table_without_bf_trigger from data/copy_data.csv with csv header;\nRESET client_min_messages;\n\nSELECT count(*) FROM table_without_bf_trigger;\n\n----------------------------------------------------------------\n-- Testing multi-buffer optimization\n-- (Chunks with different layouts)\n----------------------------------------------------------------\n-- Time is not the first attribute of the hypertable\n\nCREATE TABLE \"table_with_layout_change\" (\n    \"value1\" real NOT NULL DEFAULT 1,\n    \"value2\" smallint DEFAULT NULL,\n    \"value3\" bigint DEFAULT NULL,\n    \"time\" bigint NOT NULL,\n    \"value4\" double precision NOT NULL DEFAULT 4,\n    \"value5\" double precision NOT NULL DEFAULT 5\n);\n\nSELECT create_hypertable('table_with_layout_change', 'time', chunk_time_interval => 1);\n\n-- Chunk 1 (time = 1)\nCOPY table_with_layout_change FROM STDIN DELIMITER ',' NULL AS 'null';\n100,200,300,1,400,500\n\\.\n\nSELECT * FROM table_with_layout_change;\n\n-- Drop the first attribute\nALTER TABLE table_with_layout_change DROP COLUMN value1;\nSELECT * FROM table_with_layout_change;\n\n-- COPY into existing chunk (time = 1)\nCOPY table_with_layout_change FROM STDIN DELIMITER ',' NULL AS 'null';\n201,301,1,401,501\n\\.\n\n-- Create new chunk (time = 2)\nCOPY table_with_layout_change FROM STDIN DELIMITER ',' NULL AS 'null';\n202,302,2,402,502\n\\.\n\nSELECT * FROM table_with_layout_change ORDER BY time, value2, value3, value4, value5;\n\n-- Create new chunk (time = 2), insert in different order\nCOPY table_with_layout_change (time, value5, value4, value3, value2) FROM STDIN DELIMITER ',' NULL AS 'null';\n2,503,403,303,203\n\\.\n\nCOPY table_with_layout_change (value5, value4, value3, value2, time) FROM STDIN DELIMITER ',' NULL AS 'null';\n504,404,304,204,2\n\\.\n\nCOPY table_with_layout_change (value5, value4, value3, time, value2) FROM STDIN DELIMITER ',' NULL AS 'null';\n505,405,305,2,205\n\\.\n\nSELECT * FROM table_with_layout_change ORDER BY time, value2, value3, value4, value5;\n\n-- Drop the last attribute and add a new one\nALTER TABLE table_with_layout_change DROP COLUMN value5;\nALTER TABLE table_with_layout_change ADD COLUMN value6 double precision NOT NULL default 600;\n\nSELECT * FROM table_with_layout_change ORDER BY time, value2, value3, value4, value6;\n\n-- COPY in first chunk (time = 1)\nCOPY table_with_layout_change (time, value2, value3, value4, value6) FROM STDIN DELIMITER ',' NULL AS 'null';\n1,206,306,406,606\n\\.\n\n-- COPY in second chunk (time = 2)\nCOPY table_with_layout_change (time, value2, value3, value4, value6) FROM STDIN DELIMITER ',' NULL AS 'null';\n2,207,307,407,607\n\\.\n\n-- COPY in new chunk (time = 3)\nCOPY table_with_layout_change (time, value2, value3, value4, value6) FROM STDIN DELIMITER ',' NULL AS 'null';\n3,208,308,408,608\n\\.\n\n-- COPY in all chunks, different attribute order\nCOPY table_with_layout_change (value3, value4, time, value6, value2) FROM STDIN DELIMITER ',' NULL AS 'null';\n309,409,3,609,209\n310,410,2,610,210\n311,411,1,611,211\n\\.\n\nSELECT * FROM table_with_layout_change ORDER BY time, value2, value3, value4, value6;\n\n-- Drop first column\nALTER TABLE table_with_layout_change DROP COLUMN value2;\nSELECT * FROM table_with_layout_change ORDER BY time, value3, value4, value6;\n\n-- COPY in all exiting chunks and create a new one (time 4)\nCOPY table_with_layout_change (value3, value4, time, value6) FROM STDIN DELIMITER ',' NULL AS 'null';\n312,412,3,612\n313,413,2,613\n314,414,4,614\n315,415,1,615\n\\.\n\nSELECT * FROM table_with_layout_change ORDER BY time, value3, value4, value6;\n\n-- Drop the last two columns\nALTER TABLE table_with_layout_change DROP COLUMN value4;\nALTER TABLE table_with_layout_change DROP COLUMN value6;\n\n-- COPY in all exiting chunks and create a new one (time 5)\nCOPY table_with_layout_change (value3, time) FROM STDIN DELIMITER ',' NULL AS 'null';\n316,2\n317,1\n318,3\n319,5\n320,4\n\\.\n\nSELECT * FROM table_with_layout_change ORDER BY time, value3;\n\n-- Drop the last of the initial attributes and add a new one\nALTER TABLE table_with_layout_change DROP COLUMN value3;\nALTER TABLE table_with_layout_change ADD COLUMN value7 double precision NOT NULL default 700;\n\n-- COPY in all exiting chunks and create a new one (time 6)\nCOPY table_with_layout_change (value7, time) FROM STDIN DELIMITER ',' NULL AS 'null';\n721,2\n722,1\n723,3\n724,5\n725,6\n726,4\n\\.\n\nSELECT * FROM table_with_layout_change ORDER BY time, value7;\n\n-- verify check constraints work\nCREATE TABLE test_check(a INT, b TIMESTAMPTZ);\nALTER TABLE test_check ADD CONSTRAINT c1 CHECK (a > 7);\nSELECT table_name FROM create_hypertable('test_check', 'b');\nCOPY test_check(a,b) FROM STDIN (delimiter ',', null 'N');\n8,'2020-01-01'\n\\.\n\\set ON_ERROR_STOP 0\nCOPY test_check(a,b) FROM STDIN (delimiter ',', null 'N');\n3,'2020-01-01'\n\\.\n\\set ON_ERROR_STOP 1\n\n\n"
  },
  {
    "path": "test/sql/copy_memory_usage.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Test that transaction memory usage with COPY doesn't grow.\n-- We need memory usage in PortalContext after the completion of the query, so\n-- we'll have to log it from a trigger that runs after the query is completed.\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER;\n\ncreate table uk_price_paid(price integer, \"date\" date, postcode1 text, postcode2 text, type smallint, is_new bool, duration smallint, addr1 text, addr2 text, street text, locality text, town text, district text, country text, category smallint);\n-- Aim to about 100 partitions, the data is from 1995 to 2022.\nselect create_hypertable('uk_price_paid', 'date', chunk_time_interval => interval '90 day');\n\n-- This is where we log the memory usage.\ncreate table portal_memory_log(id serial, bytes bigint);\n\n-- Returns the amount of memory currently allocated in a given\n-- memory context. Only works for PortalContext, and doesn't work for PG 12.\ncreate or replace function ts_debug_allocated_bytes(text) returns bigint\n    as :MODULE_PATHNAME, 'ts_debug_allocated_bytes'\n    language c strict volatile;\n\n-- Log current memory usage into the log table.\ncreate function log_memory() returns trigger as $$\n    begin\n        insert into portal_memory_log\n            values (default, ts_debug_allocated_bytes('PortalContext'));\n        return new;\n    end;\n$$ language plpgsql;\n\n-- Prepare version dependent TopTransactionContext total memory usage query.\n-- Using prepared statements to avoid contaminating memory usage numbers.\n-- PG18 removed parent column so we have to use path to get TopTransactionContext child entries.\n-- https://github.com/postgres/postgres/commit/f0d11275\ncreate or replace function prepare_transaction_total_memory_usage_stmt() returns void\nlanguage plpgsql as\n$$\nbegin\n    if current_setting('server_version_num')::int < 180000 then\n        prepare total_stmt as select sum(total_bytes)\n        from pg_backend_memory_contexts\n\t\twhere parent = 'TopTransactionContext';\n    else\n        prepare total_stmt as select sum(m.total_bytes)\n        from pg_backend_memory_contexts m\n        inner join pg_backend_memory_contexts p\n            on (m.path[m.level-1] = p.path[p.level])\n        where p.name = 'TopTransactionContext';\n    end if;\nend;\n$$;\n\n-- Add a trigger that runs after completion of each INSERT/COPY and logs the\n-- current memory usage.\ncreate trigger check_update after insert on uk_price_paid\n    for each statement execute function log_memory();\n\n-- Memory leaks often happen on cache invalidation, so make sure they are\n-- invalidated often and independently (at co-prime periods).\nset timescaledb.max_open_chunks_per_insert = 2;\nset timescaledb.max_cached_chunks_per_hypertable = 3;\n\n-- Try increasingly larger data sets by concatenating the same file multiple\n-- times.\n\\copy uk_price_paid from program 'bash -c \"cat <(zcat < data/prices-10k-random-1.tsv.gz)\"';\n\\copy uk_price_paid from program 'bash -c \"cat <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz)\"';\n\\copy uk_price_paid from program 'bash -c \"cat <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz)\"';\n\\copy uk_price_paid from program 'bash -c \"cat <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz)\"';\n\\copy uk_price_paid from program 'bash -c \"cat <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz) <(zcat < data/prices-10k-random-1.tsv.gz)\"';\n\nselect count(*) from portal_memory_log;\n\n-- Check that the memory doesn't increase with file size by using linear regression.\nselect * from portal_memory_log where (\n    select regr_slope(bytes, id - 1) / regr_intercept(bytes, id - 1)::float > 0.05\n        from portal_memory_log\n);\n\n-- Test plpgsql leaks\nCREATE TABLE test_ht(tm timestamptz, val float8);\nSELECT * FROM create_hypertable('test_ht', 'tm');\n-- Use a plpgsql function to insert into the hypertable\nCREATE OR REPLACE FUNCTION to_double(_in text, INOUT _out double precision)\nLANGUAGE plpgsql IMMUTABLE parallel safe\nAS $$\nBEGIN\n    SELECT CAST(_in AS double precision) INTO _out;\nEXCEPTION WHEN others THEN\n    --do nothing: _out already carries default\nEND;\n$$;\n\n-- TopTransactionContext usage needs to remain the same after every insert\n-- There was a leak earlier in the child CurTransactionContext\n\nSELECT prepare_transaction_total_memory_usage_stmt();\nBEGIN;\nINSERT INTO test_ht VALUES ('1980-01-01 00:00:00-00', to_double('23.11', 0));\nEXECUTE total_stmt;\nINSERT INTO test_ht VALUES ('1980-02-01 00:00:00-00', to_double('24.11', 0));\nEXECUTE total_stmt;\nCOMMIT;\n"
  },
  {
    "path": "test/sql/copy_where.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n------- TEST 1: Restrictive copy from file\nCREATE TABLE \"copy_golden\" (\n    \"time\" bigint NOT NULL,\n    \"value\" double precision NOT NULL\n);\n\\COPY copy_golden (time, value) FROM data/copy_data.csv WITH CSV HEADER\nSELECT * FROM copy_golden ORDER BY TIME;\n\nCREATE TABLE \"copy_control\" (\n    \"time\" bigint NOT NULL,\n    \"value\" double precision NOT NULL\n);\n\\COPY copy_control (time, value) FROM data/copy_data.csv WITH CSV HEADER WHERE time > 10;\nSELECT * FROM copy_control ORDER BY TIME;\n\nCREATE TABLE \"copy_test\" (\n    \"time\" bigint NOT NULL,\n    \"value\" double precision NOT NULL\n);\nSELECT create_hypertable('copy_test', 'time', chunk_time_interval => 10);\n\\COPY copy_test (time, value) FROM data/copy_data.csv WITH CSV HEADER WHERE time > 10;\nSELECT * FROM copy_test ORDER BY TIME;\n\n-- Verify attempting to use subqueries fails the same as non-hypertables\n\\set ON_ERROR_STOP 0\n\\COPY copy_control (time, value) FROM data/copy_data.csv WITH CSV HEADER WHERE time IN (SELECT time FROM copy_golden);\n\\COPY copy_test (time, value) FROM data/copy_data.csv WITH CSV HEADER WHERE time IN (SELECT time FROM copy_golden);\n\\set ON_ERROR_STOP 1\n\nDROP TABLE copy_golden;\nDROP TABLE copy_control;\nDROP TABLE copy_test;\n"
  },
  {
    "path": "test/sql/create_chunks.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n--\n--  This test will create chunks in two dimenisions, time (x) and\n--  space (y), where the time dimension is aligned. The figure below\n--  shows the expected result. The chunk number in the figure\n--  indicates the creation order.\n--\n--  +\n--  +\n--  +     +-----+     +-----+\n--  +     |  2  |     |  3  |\n--  +     |     +---+-+     |\n--  +     +-----+ 5 |6+-----+\n--  +     |  1  +---+-+-----+     +---------+\n--  +     |     |   |4|  7  |     |    8    |\n--  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-\n--  0         5         10        15        20\n--\n-- Partitioning:\n--\n-- Chunk #  |  time  | space\n--    1     |   3    |   2\n--    4     |   1    |   3\n--    5     |   5    |   3\n--\n\nCREATE TABLE chunk_test(time integer, temp float8, tag integer, color integer);\n\nSELECT create_hypertable('chunk_test', 'time', 'tag', 2, chunk_time_interval => 3);\n\nINSERT INTO chunk_test VALUES (4, 24.3, 1, 1);\n\nSELECT * FROM _timescaledb_catalog.dimension_slice;\n\nINSERT INTO chunk_test VALUES (4, 24.3, 2, 1);\nINSERT INTO chunk_test VALUES (10, 24.3, 2, 1);\n\nSELECT c.table_name AS chunk_name, d.id AS dimension_id, ds.id AS slice_id, range_start, range_end FROM _timescaledb_catalog.chunk c\nLEFT JOIN _timescaledb_catalog.chunk_constraint cc ON (c.id = cc.chunk_id)\nLEFT JOIN _timescaledb_catalog.dimension_slice ds ON (ds.id = cc.dimension_slice_id)\nLEFT JOIN _timescaledb_catalog.dimension d ON (d.id = ds.dimension_id)\nLEFT JOIN _timescaledb_catalog.hypertable h ON (d.hypertable_id = h.id)\nWHERE h.schema_name = 'public' AND h.table_name = 'chunk_test'\nORDER BY c.id, d.id;\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT set_number_partitions('chunk_test', 3);\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT set_chunk_time_interval('chunk_test', 1::bigint);\n\nINSERT INTO chunk_test VALUES (8, 24.3, 11233, 1);\n\nSELECT set_chunk_time_interval('chunk_test', 5::bigint);\n\nSELECT * FROM _timescaledb_catalog.dimension;\nINSERT INTO chunk_test VALUES (7, 24.3, 79669, 1);\nINSERT INTO chunk_test VALUES (8, 24.3, 79669, 1);\nINSERT INTO chunk_test VALUES (10, 24.3, 11233, 1);\nINSERT INTO chunk_test VALUES (16, 24.3, 11233, 1);\n\nSELECT c.table_name AS chunk_name, d.id AS dimension_id, ds.id AS slice_id, range_start, range_end FROM _timescaledb_catalog.chunk c\nLEFT JOIN _timescaledb_catalog.chunk_constraint cc ON (c.id = cc.chunk_id)\nLEFT JOIN _timescaledb_catalog.dimension_slice ds ON (ds.id = cc.dimension_slice_id)\nLEFT JOIN _timescaledb_catalog.dimension d ON (d.id = ds.dimension_id)\nLEFT JOIN _timescaledb_catalog.hypertable h ON (d.hypertable_id = h.id)\nWHERE h.schema_name = 'public' AND h.table_name = 'chunk_test'\nORDER BY c.id, d.id;\n\n\n--test the edges of an open partition -- INT_64_MAX and INT_64_MIN.\nCREATE TABLE chunk_test_ends(time bigint, temp float8, tag integer, color integer);\nSELECT create_hypertable('chunk_test_ends', 'time', chunk_time_interval => 5);\n\nINSERT INTO chunk_test_ends VALUES ((-9223372036854775808)::bigint, 23.1, 11233, 1);\nINSERT INTO chunk_test_ends VALUES (9223372036854775807::bigint, 24.1, 11233, 1);\n\n--try to hit cache\nINSERT INTO chunk_test_ends VALUES (9223372036854775807::bigint, 24.2, 11233, 1);\nINSERT INTO chunk_test_ends VALUES (9223372036854775807::bigint, 24.3, 11233, 1), (9223372036854775807::bigint, 24.4, 11233, 1);\n\nINSERT INTO chunk_test_ends VALUES ((-9223372036854775808)::bigint, 23.2, 11233, 1);\nINSERT INTO chunk_test_ends VALUES ((-9223372036854775808)::bigint, 23.3, 11233, 1), ((-9223372036854775808)::bigint, 23.4, 11233, 1);\n\nSELECT * FROM chunk_test_ends ORDER BY time asc, tag, temp;\n\n--further tests of set_chunk_time_interval\nCREATE TABLE chunk_test2(time TIMESTAMPTZ, temp float8, tag integer, color integer);\nSELECT create_hypertable('chunk_test2', 'time', 'tag', 2, chunk_time_interval => 3);\nSELECT interval_length\nFROM _timescaledb_catalog.dimension d\nLEFT JOIN _timescaledb_catalog.hypertable h ON (d.hypertable_id = h.id)\nWHERE h.schema_name = 'public' AND h.table_name = 'chunk_test2'\nORDER BY d.id;\n\n-- should work since time column is non-INT\nSELECT set_chunk_time_interval('chunk_test2', INTERVAL '1 minute');\nSELECT interval_length\nFROM _timescaledb_catalog.dimension d\nLEFT JOIN _timescaledb_catalog.hypertable h ON (d.hypertable_id = h.id)\nWHERE h.schema_name = 'public' AND h.table_name = 'chunk_test2'\nORDER BY d.id;\n\n-- should still work for non-INT time columns\nSELECT set_chunk_time_interval('chunk_test2', 1000000);\nSELECT interval_length\nFROM _timescaledb_catalog.dimension d\nLEFT JOIN _timescaledb_catalog.hypertable h ON (d.hypertable_id = h.id)\nWHERE h.schema_name = 'public' AND h.table_name = 'chunk_test2'\nORDER BY d.id;\n\n\\set ON_ERROR_STOP 0\nselect set_chunk_time_interval(NULL,NULL::interval);\n-- should fail since time column is an int\nSELECT set_chunk_time_interval('chunk_test', INTERVAL '1 minute');\n-- should fail since its not a valid way to represent time\nSELECT set_chunk_time_interval('chunk_test', 'foo'::TEXT);\nSELECT set_chunk_time_interval('chunk_test', NULL::BIGINT);\nSELECT set_chunk_time_interval('chunk_test2', NULL::BIGINT);\nSELECT set_chunk_time_interval('chunk_test2', NULL::INTERVAL);\n\\set ON_ERROR_STOP 1\n\n-- Issue https://github.com/timescale/timescaledb/issues/7406\nCREATE TABLE test_ht (time TIMESTAMPTZ, v1 INTEGER);\nSELECT create_hypertable('test_ht', by_range('time', INTERVAL '1 hour'));\nCREATE TABLE test_tb (time TIMESTAMPTZ, v1 INTEGER);\n\nCREATE OR REPLACE FUNCTION test_tb_trg_insert() RETURNS TRIGGER AS $$\nBEGIN\n  INSERT INTO test_ht VALUES (NEW.time, NEW.v1);\n  RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nCREATE TRIGGER test_tb_trg_insert AFTER\nINSERT ON test_tb\nFOR EACH ROW EXECUTE FUNCTION test_tb_trg_insert();\n\n-- Creating new chunk inside a trigger called by\n-- a DDL statement should not fail.\nCREATE TABLE test_output AS\nWITH inserted AS (\n  INSERT INTO test_tb VALUES (NOW(), 1), (NOW(), 2) RETURNING *\n)\nSELECT * FROM inserted;\n\n-- Check the DEFAULT REPLICA IDENTITY of the chunks\nSELECT relname, relreplident FROM show_chunks('test_ht') ch JOIN pg_class c ON (ch = c.oid) ORDER BY relname;\n\n-- Clean up\nTRUNCATE test_ht, test_tb;\nDROP TABLE test_output;\n\n-- Change the DEFAULT REPLICA IDENTITY of the chunks\nALTER TABLE test_ht REPLICA IDENTITY FULL;\n\n-- Internally we force new chunks have the same REPLICA IDENTITY\n-- as the parent table.\nCREATE TABLE test_output AS\nWITH inserted AS (\n  INSERT INTO test_tb VALUES (NOW(), 1), (NOW(), 2) RETURNING *\n)\nSELECT * FROM inserted;\n\n-- Check current new REPLICA IDENTITY FULL in the chunks\nSELECT relname, relreplident FROM show_chunks('test_ht') ch JOIN pg_class c ON (ch = c.oid) ORDER BY relname;\n\n-- All tables should have the same number of rows\nSELECT count(*) FROM test_tb;\nSELECT count(*) FROM test_ht;\nSELECT count(*) FROM test_output;\n\n-- test ALTER TABLE SET (tsdb.chunk_interval) on a hypertable\nCREATE TABLE t_with(time timestamptz not null, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time');\n\nSELECT time_interval FROM timescaledb_information.dimensions WHERE hypertable_name = 't_with';\nALTER TABLE t_with SET (tsdb.chunk_interval = '1 hour');\nSELECT time_interval FROM timescaledb_information.dimensions WHERE hypertable_name = 't_with';\n\n\n"
  },
  {
    "path": "test/sql/create_hypertable.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\ncreate schema test_schema AUTHORIZATION :ROLE_DEFAULT_PERM_USER;\ncreate schema chunk_schema AUTHORIZATION :ROLE_DEFAULT_PERM_USER_2;\n\nSET ROLE :ROLE_DEFAULT_PERM_USER;\ncreate table test_schema.test_table(time BIGINT, temp float8, device_id text, device_type text, location text, id int, id2 int);\n\n\\set ON_ERROR_STOP 0\n-- get_create_command should fail since hypertable isn't made yet\nSELECT * FROM _timescaledb_functions.get_create_command('test_table');\n\\set ON_ERROR_STOP 1\n\nSELECT * FROM test.relation WHERE schema = 'test_schema';\n\\d _timescaledb_catalog.chunk\n\ncreate table test_schema.test_table_no_not_null(time BIGINT, device_id text);\n\n\\set ON_ERROR_STOP 0\n-- Permission denied with unprivileged role\nSET ROLE :ROLE_DEFAULT_PERM_USER_2;\nselect * from create_hypertable('test_schema.test_table_no_not_null', 'time', 'device_id', 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\n-- CREATE on schema is not enough\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nGRANT ALL ON SCHEMA test_schema TO :ROLE_DEFAULT_PERM_USER_2;\nSET ROLE :ROLE_DEFAULT_PERM_USER_2;\nselect * from create_hypertable('test_schema.test_table_no_not_null', 'time', 'device_id', 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\\set ON_ERROR_STOP 1\n\n-- Should work with when granted table owner role\nRESET ROLE;\nGRANT :ROLE_DEFAULT_PERM_USER TO :ROLE_DEFAULT_PERM_USER_2;\nSET ROLE :ROLE_DEFAULT_PERM_USER_2;\nselect * from create_hypertable('test_schema.test_table_no_not_null', 'time', 'device_id', 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\n\\set ON_ERROR_STOP 0\ninsert into test_schema.test_table_no_not_null (device_id) VALUES('foo');\n\\set ON_ERROR_STOP 1\ninsert into test_schema.test_table_no_not_null (time, device_id) VALUES(1, 'foo');\n\nRESET ROLE;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n\n\\set ON_ERROR_STOP 0\n-- No permissions on associated schema should fail\nselect * from create_hypertable('test_schema.test_table', 'time', 'device_id', 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'), associated_schema_name => 'chunk_schema');\n\\set ON_ERROR_STOP 1\n\n-- Granting permissions on chunk_schema should make things work\nRESET ROLE;\nGRANT CREATE ON SCHEMA chunk_schema TO :ROLE_DEFAULT_PERM_USER;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nselect * from create_hypertable('test_schema.test_table', 'time', 'device_id', 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'), associated_schema_name => 'chunk_schema');\n\n-- Check that the insert block trigger exists\nSELECT * FROM test.show_triggers('test_schema.test_table');\n\nSELECT * FROM _timescaledb_functions.get_create_command('test_table');\n\n--test adding one more closed dimension\nselect add_dimension('test_schema.test_table', 'location', 4);\nselect * from _timescaledb_catalog.hypertable where table_name = 'test_table';\nselect * from _timescaledb_catalog.dimension;\n\n--test that we can change the number of partitions and that 1 is allowed\nSELECT set_number_partitions('test_schema.test_table', 1, 'location');\nselect * from _timescaledb_catalog.dimension WHERE column_name = 'location';\nSELECT set_number_partitions('test_schema.test_table', 2, 'location');\nselect * from _timescaledb_catalog.dimension WHERE column_name = 'location';\n\n\\set ON_ERROR_STOP 0\n--must give an explicit dimension when there are multiple space dimensions\nSELECT set_number_partitions('test_schema.test_table', 3);\n--too few\nSELECT set_number_partitions('test_schema.test_table', 0, 'location');\n-- Too many\nSELECT set_number_partitions('test_schema.test_table', 32768, 'location');\n-- get_create_command only works on tables w/ 1 or 2 dimensions\nSELECT * FROM _timescaledb_functions.get_create_command('test_table');\n\\set ON_ERROR_STOP 1\n\n--test adding one more open dimension\nselect add_dimension('test_schema.test_table', 'id', chunk_time_interval => 1000);\nselect * from _timescaledb_catalog.hypertable where table_name = 'test_table';\nselect * from _timescaledb_catalog.dimension;\n\n-- Test add_dimension: can use interval types for TIMESTAMPTZ columns\nCREATE TABLE dim_test_time(time TIMESTAMPTZ, time2 TIMESTAMPTZ, time3 BIGINT, temp float8, device int, location int);\nSELECT create_hypertable('dim_test_time', 'time');\nSELECT add_dimension('dim_test_time', 'time2', chunk_time_interval => INTERVAL '1 day');\n\n-- Test add_dimension: only integral should work on BIGINT columns\n\\set ON_ERROR_STOP 0\nSELECT add_dimension('dim_test_time', 'time3', chunk_time_interval => INTERVAL '1 day');\n\n-- string is not a valid type\nSELECT add_dimension('dim_test_time', 'time3', chunk_time_interval => 'foo'::TEXT);\n\\set ON_ERROR_STOP 1\nSELECT add_dimension('dim_test_time', 'time3', chunk_time_interval => 500);\n\n-- Test add_dimension: integrals should work on TIMESTAMPTZ columns\nCREATE TABLE dim_test_time2(time TIMESTAMPTZ, time2 TIMESTAMPTZ, temp float8, device int, location int);\nSELECT create_hypertable('dim_test_time2', 'time');\nSELECT add_dimension('dim_test_time2', 'time2', chunk_time_interval => 500);\n\n--adding a dimension twice should not fail with 'if_not_exists'\nSELECT add_dimension('dim_test_time2', 'time2', chunk_time_interval => 500, if_not_exists => true);\n\n\\set ON_ERROR_STOP 0\n--adding on a non-hypertable\nCREATE TABLE not_hypertable(time TIMESTAMPTZ, temp float8, device int, location int);\nSELECT add_dimension('not_hypertable', 'time', chunk_time_interval => 500);\n\n--adding a non-exist column\nSELECT add_dimension('test_schema.test_table', 'nope', 2);\n\n--adding the same dimension twice should fail\nselect add_dimension('test_schema.test_table', 'location', 2);\n\n--adding dimension with both number_partitions and chunk_time_interval should fail\nselect add_dimension('test_schema.test_table', 'id2', number_partitions => 2, chunk_time_interval => 1000);\n\n\\set ON_ERROR_STOP 1\n\n-- test adding a new dimension on a non-empty table\nCREATE TABLE dim_test(time TIMESTAMPTZ, device int);\nSELECT create_hypertable('dim_test', 'time', chunk_time_interval => INTERVAL '1 day');\n\nCREATE VIEW dim_test_slices AS\nSELECT c.id AS chunk_id, c.hypertable_id, ds.dimension_id, cc.dimension_slice_id, c.schema_name AS chunk_schema, c.table_name AS chunk_table, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\nINNER JOIN _timescaledb_catalog.dimension td ON (h.id = td.hypertable_id)\nINNER JOIN _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = td.id)\nINNER JOIN _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id AND cc.chunk_id = c.id)\nWHERE h.table_name = 'dim_test'\nORDER BY c.id, ds.dimension_id;\n\nINSERT INTO dim_test VALUES ('2004-10-10 00:00:00+00', 1);\nINSERT INTO dim_test VALUES ('2004-10-20 00:00:00+00', 2);\n\nSELECT * FROM dim_test_slices;\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_5_2_chunk');\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_5_3_chunk');\n\n-- add dimension to the existing chunks by adding -inf/inf dimension slices\nSELECT add_dimension('dim_test', 'device', 2);\n\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_5_2_chunk');\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_5_3_chunk');\nSELECT * FROM dim_test_slices;\n\n-- newer chunks have proper dimension slices range\nINSERT INTO dim_test VALUES ('2004-10-30 00:00:00+00', 3);\nSELECT * FROM dim_test_slices;\n\nSELECT * FROM dim_test ORDER BY time;\n\nDROP VIEW dim_test_slices;\nDROP TABLE dim_test;\n\n-- test add_dimension() with existing data on table with space partitioning\nCREATE TABLE dim_test(time TIMESTAMPTZ, device int, data int);\nSELECT create_hypertable('dim_test', 'time', 'device', 2, chunk_time_interval => INTERVAL '1 day');\n\nCREATE VIEW dim_test_slices AS\nSELECT c.id AS chunk_id, c.hypertable_id, ds.dimension_id, cc.dimension_slice_id, c.schema_name AS chunk_schema, c.table_name AS chunk_table, ds.range_start, ds.range_end\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable h ON (c.hypertable_id = h.id)\nINNER JOIN _timescaledb_catalog.dimension td ON (h.id = td.hypertable_id)\nINNER JOIN _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = td.id)\nINNER JOIN _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id AND cc.chunk_id = c.id)\nWHERE h.table_name = 'dim_test'\nORDER BY c.id, ds.dimension_id;\n\nINSERT INTO dim_test VALUES ('2004-10-10 00:00:00+00', 1, 3);\nINSERT INTO dim_test VALUES ('2004-10-20 00:00:00+00', 2, 2);\nSELECT * FROM dim_test_slices;\n\n-- new dimension slice will cover full range on existing chunks\nSELECT add_dimension('dim_test', 'data', 1);\nSELECT * FROM dim_test_slices;\n\nINSERT INTO dim_test VALUES ('2004-10-30 00:00:00+00', 3, 1);\nSELECT * FROM dim_test_slices;\nSELECT * FROM dim_test ORDER BY time;\n\nDROP VIEW dim_test_slices;\nDROP TABLE dim_test;\n\n-- should not fail on non-empty table with 'if_not_exists' in case the dimension exists\nselect add_dimension('test_schema.test_table', 'location', 2, if_not_exists => true);\n\n--test partitioning in only time dimension\ncreate table test_schema.test_1dim(time timestamp, temp float);\nselect create_hypertable('test_schema.test_1dim', 'time');\nSELECT * FROM _timescaledb_functions.get_create_command('test_1dim');\n\nSELECT * FROM test.relation WHERE schema = 'test_schema';\n\nselect create_hypertable('test_schema.test_1dim', 'time', if_not_exists => true);\n\n-- Should error when creating again without if_not_exists set to true\n\\set ON_ERROR_STOP 0\nselect create_hypertable('test_schema.test_1dim', 'time');\n\\set ON_ERROR_STOP 1\n\n-- if_not_exist should also work with data in the hypertable\ninsert into test_schema.test_1dim VALUES ('2004-10-19 10:23:54+02', 1.0);\nselect create_hypertable('test_schema.test_1dim', 'time', if_not_exists => true);\n\n-- Should error when creating again without if_not_exists set to true\n\\set ON_ERROR_STOP 0\nselect create_hypertable('test_schema.test_1dim', 'time');\n\\set ON_ERROR_STOP 1\n\n-- Test partitioning functions\nCREATE OR REPLACE FUNCTION invalid_partfunc(source integer)\n    RETURNS INTEGER LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nBEGIN\n    RETURN NULL;\nEND\n$BODY$;\n\nCREATE OR REPLACE FUNCTION time_partfunc(source text)\n    RETURNS TIMESTAMPTZ LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nBEGIN\n    RETURN timezone('UTC', to_timestamp(source));\nEND\n$BODY$;\n\nCREATE TABLE test_schema.test_invalid_func(time timestamptz, temp float8, device text);\n\n\\set ON_ERROR_STOP 0\n-- should fail due to invalid signature\nSELECT create_hypertable('test_schema.test_invalid_func', 'time', 'device', 2, partitioning_func => 'invalid_partfunc');\n\nSELECT create_hypertable('test_schema.test_invalid_func', 'time');\n-- should also fail due to invalid signature\nSELECT add_dimension('test_schema.test_invalid_func', 'device', 2, partitioning_func => 'invalid_partfunc');\n\\set ON_ERROR_STOP 1\n\n\n-- Test open-dimension function\nCREATE TABLE test_schema.open_dim_part_func(time text, temp float8, device text, event_time text);\n\n\\set ON_ERROR_STOP 0\n-- should fail due to invalid signature\nSELECT create_hypertable('test_schema.open_dim_part_func', 'time', time_partitioning_func => 'invalid_partfunc');\n\\set ON_ERROR_STOP 1\n\nSELECT create_hypertable('test_schema.open_dim_part_func', 'time', time_partitioning_func => 'time_partfunc');\n\n\\set ON_ERROR_STOP 0\n-- should fail due to invalid signature\nSELECT add_dimension('test_schema.open_dim_part_func', 'event_time', chunk_time_interval => interval '1 day', partitioning_func => 'invalid_partfunc');\n\\set ON_ERROR_STOP 1\n\nSELECT add_dimension('test_schema.open_dim_part_func', 'event_time', chunk_time_interval => interval '1 day', partitioning_func => 'time_partfunc');\n\n--test data migration\ncreate table test_schema.test_migrate(time timestamp, temp float);\ninsert into test_schema.test_migrate VALUES ('2004-10-19 10:23:54+02', 1.0), ('2004-12-19 10:23:54+02', 2.0);\nselect * from only test_schema.test_migrate;\n\\set ON_ERROR_STOP 0\n--should fail without migrate_data => true\nselect create_hypertable('test_schema.test_migrate', 'time');\n\\set ON_ERROR_STOP 1\nselect create_hypertable('test_schema.test_migrate', 'time', migrate_data => true);\n\n--there should be two new chunks\nselect * from _timescaledb_catalog.hypertable where table_name = 'test_migrate';\nselect id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk from _timescaledb_catalog.chunk;\nselect * from test_schema.test_migrate;\n--main table should now be empty\nselect * from only test_schema.test_migrate;\nselect * from only _timescaledb_internal._hyper_10_9_chunk;\nselect * from only _timescaledb_internal._hyper_10_10_chunk;\n\ncreate table test_schema.test_migrate_empty(time timestamp, temp float);\nselect create_hypertable('test_schema.test_migrate_empty', 'time', migrate_data => true);\n\nCREATE TYPE test_type AS (time timestamp, temp float);\nCREATE TABLE test_table_of_type OF test_type;\nSELECT create_hypertable('test_table_of_type', 'time');\nINSERT INTO test_table_of_type VALUES ('2004-10-19 10:23:54+02', 1.0), ('2004-12-19 10:23:54+02', 2.0);\n\n\\set ON_ERROR_STOP 0\nDROP TYPE test_type;\n\\set ON_ERROR_STOP 1\nDROP TYPE test_type CASCADE;\n\nCREATE TABLE test_table_of_type (time timestamp, temp float);\nSELECT create_hypertable('test_table_of_type', 'time');\nINSERT INTO test_table_of_type VALUES ('2004-10-19 10:23:54+02', 1.0), ('2004-12-19 10:23:54+02', 2.0);\nCREATE TYPE test_type AS (time timestamp, temp float);\nALTER TABLE test_table_of_type OF test_type;\n\n\\set ON_ERROR_STOP 0\nDROP TYPE test_type;\n\\set ON_ERROR_STOP 1\nBEGIN;\nDROP TYPE test_type CASCADE;\nROLLBACK;\n\nALTER TABLE test_table_of_type NOT OF;\nDROP TYPE test_type;\n\n\n-- Reset GRANTS\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nREVOKE :ROLE_DEFAULT_PERM_USER FROM :ROLE_DEFAULT_PERM_USER_2;\n\n-- Test custom partitioning functions\nCREATE OR REPLACE FUNCTION partfunc_not_immutable(source anyelement)\n    RETURNS INTEGER LANGUAGE PLPGSQL AS\n$BODY$\nBEGIN\n    RETURN _timescaledb_functions.get_partition_hash(source);\nEND\n$BODY$;\n\n\nCREATE OR REPLACE FUNCTION partfunc_bad_return_type(source anyelement)\n    RETURNS BIGINT LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nBEGIN\n    RETURN _timescaledb_functions.get_partition_hash(source);\nEND\n$BODY$;\n\n\nCREATE OR REPLACE FUNCTION partfunc_bad_arg_type(source text)\n    RETURNS INTEGER LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nBEGIN\n    RETURN _timescaledb_functions.get_partition_hash(source);\nEND\n$BODY$;\n\n\nCREATE OR REPLACE FUNCTION partfunc_bad_multi_arg(source anyelement, extra_arg integer)\n    RETURNS INTEGER LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nBEGIN\n    RETURN _timescaledb_functions.get_partition_hash(source);\nEND\n$BODY$;\n\nCREATE OR REPLACE FUNCTION partfunc_valid(source anyelement)\n    RETURNS INTEGER LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nBEGIN\n    RETURN _timescaledb_functions.get_partition_hash(source);\nEND\n$BODY$;\n\ncreate table test_schema.test_partfunc(time timestamptz, temp float, device int);\n\n-- Test that create_hypertable fails due to invalid partitioning function\n\\set ON_ERROR_STOP 0\nselect create_hypertable('test_schema.test_partfunc', 'time', 'device', 2, partitioning_func => 'partfunc_not_immutable');\nselect create_hypertable('test_schema.test_partfunc', 'time', 'device', 2, partitioning_func => 'partfunc_bad_return_type');\nselect create_hypertable('test_schema.test_partfunc', 'time', 'device', 2, partitioning_func => 'partfunc_bad_arg_type');\nselect create_hypertable('test_schema.test_partfunc', 'time', 'device', 2, partitioning_func => 'partfunc_bad_multi_arg');\n\\set ON_ERROR_STOP 1\n\n-- Test that add_dimension fails due to invalid partitioning function\nselect create_hypertable('test_schema.test_partfunc', 'time');\n\n\\set ON_ERROR_STOP 0\nselect add_dimension('test_schema.test_partfunc', 'device', 2, partitioning_func => 'partfunc_not_immutable');\nselect add_dimension('test_schema.test_partfunc', 'device', 2, partitioning_func => 'partfunc_bad_return_type');\nselect add_dimension('test_schema.test_partfunc', 'device', 2, partitioning_func => 'partfunc_bad_arg_type');\nselect add_dimension('test_schema.test_partfunc', 'device', 2, partitioning_func => 'partfunc_bad_multi_arg');\n\\set ON_ERROR_STOP 1\n\n-- A valid function should work:\nselect add_dimension('test_schema.test_partfunc', 'device', 2, partitioning_func => 'partfunc_valid');\n\n-- check get_create_command produces valid command\nCREATE TABLE test_schema.test_sql_cmd(time TIMESTAMPTZ, temp FLOAT8, device_id TEXT, device_type TEXT, location TEXT, id INT, id2 INT);\nSELECT create_hypertable('test_schema.test_sql_cmd','time');\nSELECT * FROM _timescaledb_functions.get_create_command('test_sql_cmd');\nSELECT _timescaledb_functions.get_create_command('test_sql_cmd') AS create_cmd; \\gset\nDROP TABLE test_schema.test_sql_cmd CASCADE;\nCREATE TABLE test_schema.test_sql_cmd(time TIMESTAMPTZ, temp FLOAT8, device_id TEXT, device_type TEXT, location TEXT, id INT, id2 INT);\nSELECT test.execute_sql(:'create_cmd');\n\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nCREATE TABLE test_table_int(time bigint, junk int);\nSELECT hypertable_id AS \"TEST_TABLE_INT_HYPERTABLE_ID\" FROM create_hypertable('test_table_int', 'time', chunk_time_interval => 1) \\gset\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA IF NOT EXISTS my_schema;\ncreate or replace function my_schema.dummy_now2() returns BIGINT LANGUAGE SQL IMMUTABLE as  'SELECT 1::BIGINT';\ngrant execute on ALL FUNCTIONS IN SCHEMA my_schema to public;\ncreate or replace function dummy_now3() returns BIGINT LANGUAGE SQL IMMUTABLE as  'SELECT 1::BIGINT';\ngrant execute on ALL FUNCTIONS IN SCHEMA my_schema to public;\nREVOKE execute ON function dummy_now3() FROM PUBLIC;\nCREATE SCHEMA IF NOT EXISTS my_user_schema;\nGRANT ALL ON SCHEMA my_user_schema to PUBLIC;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n\n\ncreate or replace function dummy_now() returns BIGINT LANGUAGE SQL IMMUTABLE as  'SELECT 1::BIGINT';\ncreate or replace function my_user_schema.dummy_now4() returns BIGINT LANGUAGE SQL IMMUTABLE as  'SELECT 1::BIGINT';\n\n\nselect set_integer_now_func('test_table_int', 'dummy_now');\nselect * from _timescaledb_catalog.dimension WHERE hypertable_id = :TEST_TABLE_INT_HYPERTABLE_ID;\n-- show chunks works with \"created_before\" and errors out with time used in \"older_than\"\nSELECT SHOW_CHUNKS('test_table_int', older_than => 10);\nSELECT SHOW_CHUNKS('test_table_int', created_before => now());\n\\set ON_ERROR_STOP 0\nSELECT SHOW_CHUNKS('test_table_int', older_than => now());\nselect set_integer_now_func('test_table_int', 'dummy_now');\nselect set_integer_now_func('test_table_int', 'my_schema.dummy_now2', replace_if_exists => TRUE);\nselect set_integer_now_func('test_table_int', 'dummy_now3', replace_if_exists => TRUE);\n-- test invalid oid as the integer_now_func\nselect set_integer_now_func('test_table_int', 1, replace_if_exists => TRUE);\n\\set ON_ERROR_STOP\n\nselect set_integer_now_func('test_table_int', 'my_user_schema.dummy_now4', replace_if_exists => TRUE);\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nALTER SCHEMA my_user_schema RENAME TO my_new_schema;\nselect * from _timescaledb_catalog.dimension WHERE hypertable_id = :TEST_TABLE_INT_HYPERTABLE_ID;\n\n-- github issue #4650\nCREATE TABLE sample_table (\n       cpu double precision null,\n       time TIMESTAMP WITH TIME ZONE NOT NULL,\n       sensor_id INTEGER NOT NULL,\n       name varchar(100) default 'this is a default string value',\n       UNIQUE(sensor_id, time)\n);\n\nALTER TABLE sample_table DROP COLUMN name;\n\n-- below creation should not report any warnings.\nSELECT * FROM create_hypertable('sample_table', 'time');\n\n-- cleanup\nDROP TABLE sample_table CASCADE;\n\n-- github issue 4684\n-- test PARTITION BY HASH\nCREATE TABLE regular(\n   id INT NOT NULL,\n   dev INT NOT NULL,\n   value INT,\n   CONSTRAINT cstr_regular_pky PRIMARY KEY (id)\n) PARTITION BY HASH (id);\n\nDO $$\nBEGIN\n   FOR i IN 1..2\n   LOOP\n      EXECUTE format('\n         CREATE TABLE %I\n         PARTITION OF regular\n         FOR VALUES WITH (MODULUS 2, REMAINDER %s)',\n         'regular_' || i, i - 1\n      );\n   END LOOP;\nEND;\n$$;\n\nINSERT INTO regular SELECT generate_series(1,1000), 44,55;\n\nCREATE TABLE timescale (\n   ts TIMESTAMP WITH TIME ZONE NOT NULL,\n   id INT NOT NULL,\n   dev INT NOT NULL,\n   FOREIGN KEY (id)  REFERENCES regular(id) ON DELETE CASCADE\n);\n\nSELECT create_hypertable(\n   relation => 'timescale',\n   time_column_name => 'ts'\n);\n\n-- creates chunk1\nINSERT INTO timescale SELECT now(), generate_series(1,200), 43;\n-- creates chunk2\nINSERT INTO timescale SELECT now() + interval '20' day, generate_series(1,200), 43;\n-- creates chunk3\nINSERT INTO timescale SELECT now() + interval '40' day, generate_series(1,200), 43;\n\n-- show chunks\nSELECT SHOW_CHUNKS('timescale');\n\n\\set ON_ERROR_STOP 0\n-- record goes into chunk1 violating FK constraint as value 1001 is not present in regular table\nINSERT INTO timescale SELECT now(), 1001, 43;\n-- record goes into chunk2 violating FK constraint as value 1002 is not present in regular table\nINSERT INTO timescale SELECT now() + interval '20' day, 1002, 43;\n-- record goes into chunk3 violating FK constraint as value 1003 is not present in regular table\nINSERT INTO timescale SELECT now() + interval '40' day, 1003, 43;\n\\set ON_ERROR_STOP 1\n\n-- cleanup\nDROP TABLE regular cascade;\nDROP TABLE timescale cascade;\n\n-- test PARTITION BY RANGE\nCREATE TABLE regular(\n   id INT NOT NULL,\n   dev INT NOT NULL,\n   value INT,\n   CONSTRAINT cstr_regular_pky PRIMARY KEY (id)\n) PARTITION BY RANGE (id);\n\nCREATE TABLE regular_1_500 PARTITION OF regular\n    FOR VALUES FROM (1) TO (500);\n\nCREATE TABLE regular_500_1000 PARTITION OF regular\n    FOR VALUES FROM (500) TO (801);\n\nINSERT INTO regular SELECT generate_series(1,800), 44,55;\n\nCREATE TABLE timescale (\n   ts TIMESTAMP WITH TIME ZONE NOT NULL,\n   id INT NOT NULL,\n   dev INT NOT NULL,\n   FOREIGN KEY (id)  REFERENCES regular(id) ON DELETE CASCADE\n);\n\nSELECT create_hypertable(\n   relation => 'timescale',\n   time_column_name => 'ts'\n);\n\n-- creates chunk1\nINSERT INTO timescale SELECT now(), generate_series(1,200), 43;\n-- creates chunk2\nINSERT INTO timescale SELECT now() + interval '20' day, generate_series(200,400), 43;\n-- creates chunk3\nINSERT INTO timescale SELECT now() + interval '40' day, generate_series(400,600), 43;\n\n-- show chunks\nSELECT SHOW_CHUNKS('timescale');\n\n\\set ON_ERROR_STOP 0\n-- FK constraint violation as value 801 is not present in regular table\nINSERT INTO timescale SELECT now(), 801, 43;\n-- FK constraint violation as value 902 is not present in regular table\nINSERT INTO timescale SELECT now() + interval '20' day, 902, 43;\n-- FK constraint violation as value 1003 is not present in regular table\nINSERT INTO timescale SELECT now() + interval '40' day, 1003, 43;\n\\set ON_ERROR_STOP 1\n\n-- cleanup\nDROP TABLE regular cascade;\nDROP TABLE timescale cascade;\n\n-- test PARTITION BY LIST\nCREATE TABLE regular(\n   id INT NOT NULL,\n   dev INT NOT NULL,\n   value INT,\n   CONSTRAINT cstr_regular_pky PRIMARY KEY (id)\n) PARTITION BY LIST (id);\n\nCREATE TABLE regular_1_2_3_4 PARTITION OF regular FOR VALUES IN (1,2,3,4);\nCREATE TABLE regular_5_6_7_8 PARTITION OF regular FOR VALUES IN (5,6,7,8);\n\nINSERT INTO regular SELECT generate_series(1,8), 44,55;\n\nCREATE TABLE timescale (\n   ts TIMESTAMP WITH TIME ZONE NOT NULL,\n   id INT NOT NULL,\n   dev INT NOT NULL,\n   FOREIGN KEY (id)  REFERENCES regular(id) ON DELETE CASCADE\n);\n\nSELECT create_hypertable(\n   relation => 'timescale',\n   time_column_name => 'ts'\n);\n\ninsert into timescale values (now(), 1,2);\ninsert into timescale values (now(), 2,2);\ninsert into timescale values (now(), 3,2);\ninsert into timescale values (now(), 4,2);\ninsert into timescale values (now(), 5,2);\ninsert into timescale values (now(), 6,2);\ninsert into timescale values (now(), 7,2);\ninsert into timescale values (now(), 8,2);\n\n\\set ON_ERROR_STOP 0\n-- FK constraint violation as value 9 is not present in regular table\ninsert into timescale values (now(), 9,2);\n-- FK constraint violation as value 10 is not present in regular table\ninsert into timescale values (now(), 10,2);\n-- FK constraint violation as value 111 is not present in regular table\ninsert into timescale values (now(), 111,2);\n\\set ON_ERROR_STOP 1\n\n-- cleanup\nDROP TABLE regular cascade;\nDROP TABLE timescale cascade;\n\n-- github issue 4872\n-- If subplan of ChunkAppend is TidRangeScan, then SELECT on\n-- hypertable fails with error \"invalid child of chunk append: Node (26)\"\ncreate table tidrangescan_test (\n  time timestamp with time zone,\n  some_column bigint\n);\n\nselect create_hypertable('tidrangescan_test', 'time');\n\ninsert into tidrangescan_test (time, some_column) values ('2023-02-12 00:00:00+02:40', 1);\ninsert into tidrangescan_test (time, some_column) values ('2023-02-12 00:00:10+02:40', 2);\ninsert into tidrangescan_test (time, some_column) values ('2023-02-12 00:00:20+02:40', 3);\n\n-- Below query will generate plan as\n-- Custom Scan (ChunkAppend)\n--   ->  Tid Range Scan\n-- However when traversing ChunkAppend node, Tid Range Scan node is not\n-- recognised as a valid child node of ChunkAppend which causes error\n-- \"invalid child of chunk append: Node (26)\" when below query is executed\nselect * from tidrangescan_test where time > '2023-02-12 00:00:00+02:40'::timestamp with time zone - interval '5 years' and ctid < '(1,1)'::tid ORDER BY time;\n\ndrop table tidrangescan_test;\n\n\\set VERBOSITY default\nset client_min_messages = WARNING;\n-- test creating a hypertable from table referenced by a foreign key fails with\n-- error \"cannot have FOREIGN KEY constraints to hypertable\".\ncreate table test_schema.fk_parent(time timestamptz, id int, unique(time, id));\ncreate table test_schema.fk_child(\n  time timestamptz,\n  id int,\n  foreign key (time, id) references test_schema.fk_parent(time, id)\n);\nselect create_hypertable ('test_schema.fk_child', 'time');\n\\set ON_ERROR_STOP 0\nselect create_hypertable ('test_schema.fk_parent', 'time');\n\\set ON_ERROR_STOP 1\n\n-- create default indexes on chunks when migrating data\nCREATE TABLE test(time TIMESTAMPTZ, val BIGINT);\nCREATE INDEX test_val_idx ON test(val);\nINSERT INTO test VALUES('2024-01-01 00:00:00-03', 500);\nSELECT FROM create_hypertable('test', 'time', migrate_data=>TRUE);\n-- should return ALL indexes for hypertable and chunk\nSELECT * FROM test.show_indexes('test') ORDER BY 1;\nSELECT * FROM show_chunks('test') ch, LATERAL test.show_indexes(ch) ORDER BY 1, 2;\nDROP TABLE test;\n\n-- don't create default indexes on chunks when migrating data\nCREATE TABLE test(time TIMESTAMPTZ, val BIGINT);\nCREATE INDEX test_val_idx ON test(val);\nINSERT INTO test VALUES('2024-01-01 00:00:00-03', 500);\nSELECT FROM create_hypertable('test', 'time', create_default_indexes => FALSE, migrate_data=>TRUE);\n-- should NOT return default indexes for hypertable and chunk\n-- only user indexes should be returned\nSELECT * FROM test.show_indexes('test') ORDER BY 1;\nSELECT * FROM show_chunks('test') ch, LATERAL test.show_indexes(ch) ORDER BY 1, 2;\nDROP TABLE test;\n\n-- test creating a hypertable with a primary key where the partitioning column is not part of the primary key\nCREATE TABLE test_schema.partition_not_pk (id INT NOT NULL, device_id INT NOT NULL, time TIMESTAMPTZ NOT NULL, a TEXT NOT NULL, PRIMARY KEY (id));\n\\set ON_ERROR_STOP 0\nselect create_hypertable ('test_schema.partition_not_pk', 'time');\n\\set ON_ERROR_STOP 1\nDROP TABLE test_schema.partition_not_pk;\n\n-- test creating a hypertable with a composite key where the partitioning column is not part of the composite key\nCREATE TABLE test_schema.partition_not_pk (id INT NOT NULL, device_id INT NOT NULL, time TIMESTAMPTZ NOT NULL, a TEXT NOT NULL, PRIMARY KEY (id, device_id));\n\\set ON_ERROR_STOP 0\nselect create_hypertable ('test_schema.partition_not_pk', 'time');\n\\set ON_ERROR_STOP 1\nDROP TABLE test_schema.partition_not_pk;\n\n-- test hypertable is not created for a table that is a part of a publication explicitly\nSET client_min_messages = ERROR;\nCREATE TABLE test (timestamp TIMESTAMPTZ NOT NULL);\nCREATE PUBLICATION publication_test;\nALTER PUBLICATION publication_test ADD TABLE test;\n\\set ON_ERROR_STOP 0\nSELECT create_hypertable('test', 'timestamp');\n\\set ON_ERROR_STOP 1\nINSERT INTO test (timestamp) values (now());\nALTER PUBLICATION publication_test DROP TABLE test;\nDROP PUBLICATION publication_test;\nDROP TABLE test;\n\nCREATE TABLE test (timestamp TIMESTAMPTZ NOT NULL);\nCREATE PUBLICATION publication_test1;\nCREATE PUBLICATION publication_test2;\nALTER PUBLICATION publication_test1 ADD TABLE test;\nALTER PUBLICATION publication_test2 ADD TABLE test;\n\\set ON_ERROR_STOP 0\nSELECT create_hypertable('test', 'timestamp');\n\\set ON_ERROR_STOP 1\nINSERT INTO test (timestamp) values (now());\nALTER PUBLICATION publication_test1 DROP TABLE test;\nALTER PUBLICATION publication_test2 DROP TABLE test;\nDROP PUBLICATION publication_test1;\nDROP PUBLICATION publication_test2;\nDROP TABLE test;\n\n-- test hypertable is not created for a table that is a part of a publication implicitly\nCREATE PUBLICATION publication_test FOR ALL tables;\nCREATE TABLE test (timestamp TIMESTAMPTZ NOT NULL);\n\\set ON_ERROR_STOP 0\nSELECT create_hypertable('test', 'timestamp');\n\\set ON_ERROR_STOP 1\nDROP PUBLICATION publication_test;\nDROP TABLE test;\n\nCREATE TABLE test (timestamp TIMESTAMPTZ NOT NULL);\nCREATE PUBLICATION publication_test FOR ALL tables;\n\\set ON_ERROR_STOP 0\nSELECT create_hypertable('test', 'timestamp');\n\\set ON_ERROR_STOP 1\nDROP PUBLICATION publication_test;\nDROP TABLE test;\nRESET client_min_messages;\n\n-- Test default_chunk_time_interval GUC\n-- Tests that the GUC correctly sets the default chunk interval for hypertables\n-- with different time column types (timestamp, timestamptz, date) and integer types.\n\n-- Show initial state (should be NULL meaning legacy defaults)\nSHOW timescaledb.default_chunk_time_interval;\n\n-- No default_chunk_time_interval set (NULL) - uses legacy defaults\nCREATE TABLE test_no_guc_timestamptz(time TIMESTAMPTZ NOT NULL, val INT);\nSELECT create_hypertable('test_no_guc_timestamptz', 'time');\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_no_guc_timestamptz';\nDROP TABLE test_no_guc_timestamptz;\n\nCREATE TABLE test_no_guc_timestamp(time TIMESTAMP NOT NULL, val INT);\nSELECT create_hypertable('test_no_guc_timestamp', 'time');\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_no_guc_timestamp';\nDROP TABLE test_no_guc_timestamp;\n\nCREATE TABLE test_no_guc_date(time DATE NOT NULL, val INT);\nSELECT create_hypertable('test_no_guc_date', 'time');\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_no_guc_date';\nDROP TABLE test_no_guc_date;\n\nCREATE TABLE test_no_guc_uuid(time UUID NOT NULL, val INT);\nSELECT create_hypertable('test_no_guc_uuid', 'time');\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_no_guc_uuid';\nDROP TABLE test_no_guc_uuid;\n\n-- Set default_chunk_time_interval to '1 week' and create hypertables\nSET timescaledb.default_chunk_time_interval = '1 week';\nSHOW timescaledb.default_chunk_time_interval;\n\nCREATE TABLE test_guc_timestamptz(time TIMESTAMPTZ NOT NULL, val INT);\nSELECT create_hypertable('test_guc_timestamptz', 'time');\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_guc_timestamptz';\nDROP TABLE test_guc_timestamptz;\n\nCREATE TABLE test_guc_timestamp(time TIMESTAMP NOT NULL, val INT);\nSELECT create_hypertable('test_guc_timestamp', 'time');\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_guc_timestamp';\nDROP TABLE test_guc_timestamp;\n\nCREATE TABLE test_guc_date(time DATE NOT NULL, val INT);\nSELECT create_hypertable('test_guc_date', 'time');\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_guc_date';\nDROP TABLE test_guc_date;\n\nCREATE TABLE test_guc_uuid(time UUID NOT NULL, val INT);\nSELECT create_hypertable('test_guc_uuid', 'time');\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_guc_uuid';\nDROP TABLE test_guc_uuid;\n\n-- Set default_chunk_time_interval to '1 day'\nSET timescaledb.default_chunk_time_interval = '1 day';\nSHOW timescaledb.default_chunk_time_interval;\n\nCREATE TABLE test_guc_1day_timestamptz(time TIMESTAMPTZ NOT NULL, val INT);\nSELECT create_hypertable('test_guc_1day_timestamptz', 'time');\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_guc_1day_timestamptz';\nDROP TABLE test_guc_1day_timestamptz;\n\n-- Set default_chunk_time_interval to '1 month'\nSET timescaledb.default_chunk_time_interval = '1 month';\nSHOW timescaledb.default_chunk_time_interval;\n\nCREATE TABLE test_guc_1month_timestamptz(time TIMESTAMPTZ NOT NULL, val INT);\nSELECT create_hypertable('test_guc_1month_timestamptz', 'time');\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_guc_1month_timestamptz';\nDROP TABLE test_guc_1month_timestamptz;\n\n-- Integer partition types have their own defaults and do not use the GUC\nSET timescaledb.default_chunk_time_interval = '1 week';\n\nCREATE TABLE test_guc_bigint(time BIGINT NOT NULL, val INT);\nSELECT create_hypertable('test_guc_bigint', 'time');\nSELECT integer_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_guc_bigint';\nDROP TABLE test_guc_bigint;\n\nCREATE TABLE test_guc_int(time INT NOT NULL, val INT);\nSELECT create_hypertable('test_guc_int', 'time');\nSELECT integer_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_guc_int';\nDROP TABLE test_guc_int;\n\nCREATE TABLE test_guc_smallint(time SMALLINT NOT NULL, val INT);\nSELECT create_hypertable('test_guc_smallint', 'time');\nSELECT integer_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_guc_smallint';\nDROP TABLE test_guc_smallint;\n\n-- Explicit chunk_time_interval should override the GUC\nSET timescaledb.default_chunk_time_interval = '1 week';\n\nCREATE TABLE test_override_timestamptz(time TIMESTAMPTZ NOT NULL, val INT);\nSELECT create_hypertable('test_override_timestamptz', 'time', chunk_time_interval => INTERVAL '2 days');\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_override_timestamptz';\nDROP TABLE test_override_timestamptz;\n\n-- Reset GUC to NULL (legacy behavior)\nRESET timescaledb.default_chunk_time_interval;\nSHOW timescaledb.default_chunk_time_interval;\n\nCREATE TABLE test_reset_timestamptz(time TIMESTAMPTZ NOT NULL, val INT);\nSELECT create_hypertable('test_reset_timestamptz', 'time');\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_reset_timestamptz';\nDROP TABLE test_reset_timestamptz;\n\n-- Invalid interval should fail\n\\set ON_ERROR_STOP 0\nSET timescaledb.default_chunk_time_interval = 'not_an_interval';\nSET timescaledb.default_chunk_time_interval = '123abc';\n\\set ON_ERROR_STOP 1\n\n-- add_dimension does not use the GUC, keeps legacy behavior requiring explicit interval\nRESET timescaledb.default_chunk_time_interval;\nSET timescaledb.default_chunk_time_interval = '3 days';\n\nCREATE TABLE test_add_dim(time TIMESTAMPTZ NOT NULL, time2 TIMESTAMPTZ NOT NULL, val INT);\nSELECT create_hypertable('test_add_dim', 'time');\n-- First dimension uses GUC\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_add_dim';\n\n-- add_dimension without explicit interval should fail\n\\set ON_ERROR_STOP 0\nSELECT add_dimension('test_add_dim', 'time2');\n\\set ON_ERROR_STOP 1\n\n-- add_dimension with explicit interval works\nSELECT add_dimension('test_add_dim', 'time2', chunk_time_interval => INTERVAL '1 day');\nSELECT column_name, time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_add_dim'\n  ORDER BY dimension_number;\nDROP TABLE test_add_dim;\n\n-- Session-level GUC changes\nRESET timescaledb.default_chunk_time_interval;\nSET timescaledb.default_chunk_time_interval = '5 days';\n\nCREATE TABLE test_session_1(time TIMESTAMPTZ NOT NULL, val INT);\nSELECT create_hypertable('test_session_1', 'time');\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_session_1';\n\n-- Change GUC mid-session\nSET timescaledb.default_chunk_time_interval = '10 days';\n\nCREATE TABLE test_session_2(time TIMESTAMPTZ NOT NULL, val INT);\nSELECT create_hypertable('test_session_2', 'time');\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_session_2';\n\nDROP TABLE test_session_1;\nDROP TABLE test_session_2;\n\n-- Transaction-level GUC with SET LOCAL\nRESET timescaledb.default_chunk_time_interval;\nSET timescaledb.default_chunk_time_interval = '1 week';\n\nBEGIN;\nSET LOCAL timescaledb.default_chunk_time_interval = '2 weeks';\n\nCREATE TABLE test_local_guc(time TIMESTAMPTZ NOT NULL, val INT);\nSELECT create_hypertable('test_local_guc', 'time');\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_local_guc';\nCOMMIT;\n\n-- After commit, GUC should be back to session level (1 week)\nSHOW timescaledb.default_chunk_time_interval;\n\nDROP TABLE test_local_guc;\n\n-- UUID partition type with various intervals\nRESET timescaledb.default_chunk_time_interval;\n\nSET timescaledb.default_chunk_time_interval = '1 day';\nCREATE TABLE test_uuid_1day(time UUID NOT NULL, val INT);\nSELECT create_hypertable('test_uuid_1day', 'time');\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_uuid_1day';\nDROP TABLE test_uuid_1day;\n\nSET timescaledb.default_chunk_time_interval = '1 hour';\nCREATE TABLE test_uuid_1hour(time UUID NOT NULL, val INT);\nSELECT create_hypertable('test_uuid_1hour', 'time');\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_uuid_1hour';\nDROP TABLE test_uuid_1hour;\n\n-- UUID with explicit override should work\nSET timescaledb.default_chunk_time_interval = '1 week';\nCREATE TABLE test_uuid_override(time UUID NOT NULL, val INT);\nSELECT create_hypertable('test_uuid_override', 'time', chunk_time_interval => INTERVAL '12 hours');\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_uuid_override';\nDROP TABLE test_uuid_override;\n\n-- UUID with no GUC set (legacy default)\nRESET timescaledb.default_chunk_time_interval;\nCREATE TABLE test_uuid_legacy(time UUID NOT NULL, val INT);\nSELECT create_hypertable('test_uuid_legacy', 'time');\nSELECT time_interval FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 'test_uuid_legacy';\nDROP TABLE test_uuid_legacy;\n\n-- Cleanup\nRESET timescaledb.default_chunk_time_interval;\n\n-- Check that we produce the proper error code for nonexistent tables.\nselect create_hypertable(77777777, 'nonexistent');\n"
  },
  {
    "path": "test/sql/create_table.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Test that we can verify constraints on regular tables\nCREATE TABLE test_hyper_pk(time TIMESTAMPTZ PRIMARY KEY, temp FLOAT, device INT);\nCREATE TABLE test_pk(device INT PRIMARY KEY);\nCREATE TABLE test_like(LIKE test_pk);\n\nSELECT create_hypertable('test_hyper_pk', 'time');\n\n\\set ON_ERROR_STOP 0\n-- Foreign key constraints that reference hypertables are currently unsupported\nCREATE TABLE test_fk(time TIMESTAMPTZ REFERENCES test_hyper_pk(time));\n\\set ON_ERROR_STOP 1\n\nCREATE TABLE test_delete(time timestamp with time zone PRIMARY KEY, temp float);\nSELECT create_hypertable('test_delete', 'time');\nINSERT INTO test_delete VALUES('2017-01-20T09:00:01', 22.5);\nINSERT INTO test_delete VALUES('2017-01-20T09:00:21', 21.2);\nINSERT INTO test_delete VALUES('2017-01-20T09:00:47', 25.1);\nINSERT INTO test_delete VALUES('2020-01-20T09:00:47', 25.1);\nINSERT INTO test_delete VALUES('2021-01-20T09:00:47', 25.1);\nSELECT * FROM test_delete WHERE temp = 25.1 ORDER BY time;\n\nCREATE OR replace FUNCTION test_delete_row_count()\nRETURNS void AS $$\nDECLARE\n    v_cnt numeric;\nBEGIN\n    v_cnt := 0;\n\n        DELETE FROM test_delete WHERE temp = 25.1;\n        GET DIAGNOSTICS v_cnt = ROW_COUNT;\n\n        IF v_cnt != 3 THEN\n           RAISE EXCEPTION 'unexpected result';\n        END IF;\nEND;\n$$ LANGUAGE plpgsql;\n\nSELECT test_delete_row_count();\n"
  },
  {
    "path": "test/sql/create_table_with.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- our user needs permission to create schema for the schema tests\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nGRANT CREATE ON DATABASE :TEST_DBNAME TO :ROLE_DEFAULT_PERM_USER;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n\n-- create table with non-tsdb option should not be affected\nCREATE TABLE t1(time timestamptz, device text, value float) WITH (autovacuum_enabled);\nDROP TABLE t1;\n\n-- test error cases\n\\set ON_ERROR_STOP 0\n\\set VERBOSITY default\nCREATE TABLE t2(time float, device text, value float) WITH (tsdb.hypertable);\nCREATE TABLE t2(time float, device text, value float) WITH (timescaledb.hypertable);\nCREATE TABLE t2(time timestamptz, device text, value float) WITH (tsdb.hypertable,tsdb.partition_column=NULL);\nCREATE TABLE t2(time timestamptz, device text, value float) WITH (tsdb.hypertable,tsdb.partition_column='');\nCREATE TABLE t2(time timestamptz, device text, value float) WITH (tsdb.hypertable,tsdb.partition_column='foo');\nCREATE TABLE t2(time timestamptz, device text, value float) WITH (tsdb.partition_column='time');\nCREATE TABLE t2(time timestamptz, device text, value float) WITH (timescaledb.partition_column='time');\nCREATE TABLE t2(time timestamptz , device text, value float) WITH (tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval='foo');\nCREATE TABLE t2(time int2 NOT NULL, device text, value float) WITH (tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval='3 months');\nCREATE TABLE t2(time timestamptz, device text, value float) WITH (tsdb.create_default_indexes='time');\nCREATE TABLE t2(time timestamptz, device text, value float) WITH (tsdb.create_default_indexes=2);\nCREATE TABLE t2(time timestamptz, device text, value float) WITH (tsdb.create_default_indexes=-1);\nCREATE TABLE t2(time timestamptz NOT NULL, device text, value float) WITH (tsdb.hypertable,tsdb.partition_column='time',tsdb.columnstore=true);\nCREATE TABLE t2(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore,tsdb.hypertable,tsdb.partition_column='time');\n-- Test error hint for invalid timescaledb options during CREATE TABLE\nCREATE TABLE t2(time timestamptz, device text, value float) WITH (tsdb.invalid_option = true);\nCREATE TABLE t2(time timestamptz, device text, value float) WITH (timescaledb.nonexistent_param = false);\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n\n\nBEGIN;\nCREATE TABLE t3(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time');\nCREATE TABLE t4(time timestamp, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,timescaledb.partition_column='time');\nCREATE TABLE t5(time date, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time',autovacuum_enabled);\nCREATE TABLE t6(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,timescaledb.hypertable,tsdb.partition_column='time');\nCREATE TABLE t7(time timestamptz, device text, value float) WITH (timescaledb.hypertable,tsdb.partition_column='time');\n\nSELECT hypertable_name FROM timescaledb_information.hypertables ORDER BY 1;\nROLLBACK;\n\n-- IF NOT EXISTS\nBEGIN;\nCREATE TABLE IF NOT EXISTS t7(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time');\nCREATE TABLE IF NOT EXISTS t7(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time');\nCREATE TABLE IF NOT EXISTS t7(time timestamptz NOT NULL, device text, value float);\n\nSELECT hypertable_name FROM timescaledb_information.hypertables ORDER BY 1;\nROLLBACK;\n\n-- table won't be converted to hypertable unless it is in the initial CREATE TABLE\nBEGIN;\nCREATE TABLE IF NOT EXISTS t8(time timestamptz NOT NULL, device text, value float);\nCREATE TABLE IF NOT EXISTS t8(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time');\nCREATE TABLE IF NOT EXISTS t8(time timestamptz NOT NULL, device text, value float);\n\nSELECT hypertable_name FROM timescaledb_information.hypertables ORDER BY 1;\nROLLBACK;\n\n-- chunk_interval\nBEGIN;\nCREATE TABLE IF NOT EXISTS t9(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval='8weeks');\nSELECT hypertable_name, column_name, column_type, time_interval FROM timescaledb_information.dimensions;\nROLLBACK;\n\nBEGIN;\nCREATE TABLE IF NOT EXISTS t9(time timestamp NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval='23 days');\nSELECT hypertable_name, column_name, column_type, time_interval FROM timescaledb_information.dimensions;\nROLLBACK;\n\nBEGIN;\nCREATE TABLE IF NOT EXISTS t9(time date NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval='3 months');\nSELECT hypertable_name, column_name, column_type, time_interval FROM timescaledb_information.dimensions;\nROLLBACK;\n\nBEGIN;\nCREATE TABLE IF NOT EXISTS t9(time int2 NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval=12);\nSELECT hypertable_name, column_name, column_type, integer_interval FROM timescaledb_information.dimensions;\nROLLBACK;\n\nBEGIN;\nCREATE TABLE IF NOT EXISTS t9(time int4 NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval=3453);\nSELECT hypertable_name, column_name, column_type, integer_interval FROM timescaledb_information.dimensions;\nROLLBACK;\n\nBEGIN;\nCREATE TABLE IF NOT EXISTS t9(time int8 NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval=32768);\nSELECT hypertable_name, column_name, column_type, integer_interval FROM timescaledb_information.dimensions;\nROLLBACK;\n\n-- create_default_indexes\nBEGIN;\nCREATE TABLE t10(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time');\nSELECT indexrelid::regclass from pg_index where indrelid='t10'::regclass ORDER BY indexrelid::regclass::text;\nROLLBACK;\n\nBEGIN;\nCREATE TABLE t10(time timestamptz NOT NULL PRIMARY KEY, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time');\nSELECT indexrelid::regclass from pg_index where indrelid='t10'::regclass ORDER BY indexrelid::regclass::text;\nROLLBACK;\n\nBEGIN;\nCREATE TABLE t10(time timestamptz NOT NULL UNIQUE, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time');\nSELECT indexrelid::regclass from pg_index where indrelid='t10'::regclass ORDER BY indexrelid::regclass::text;\nROLLBACK;\n\nBEGIN;\nCREATE TABLE t10(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time',tsdb.create_default_indexes=true);\nSELECT indexrelid::regclass from pg_index where indrelid='t10'::regclass ORDER BY indexrelid::regclass::text;\nROLLBACK;\n\nBEGIN;\nCREATE TABLE t10(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time',tsdb.create_default_indexes=false);\nSELECT indexrelid::regclass from pg_index where indrelid='t10'::regclass ORDER BY indexrelid::regclass::text;\nROLLBACK;\n\n-- associated_schema\nBEGIN;\nCREATE TABLE t11(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time');\nSELECT associated_schema_name FROM _timescaledb_catalog.hypertable WHERE table_name = 't11';\nROLLBACK;\n\nBEGIN;\nCREATE TABLE t11(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time', tsdb.associated_schema='abc');\nSELECT associated_schema_name FROM _timescaledb_catalog.hypertable WHERE table_name = 't11';\nINSERT INTO t11 SELECT '2025-01-01', 'd1', 0.1;\nSELECT relname from pg_class where relnamespace = 'abc'::regnamespace ORDER BY 1;\nROLLBACK;\n\nBEGIN;\nCREATE SCHEMA abc2;\nCREATE TABLE t11(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time', tsdb.associated_schema='abc2');\nSELECT associated_schema_name FROM _timescaledb_catalog.hypertable WHERE table_name = 't11';\nINSERT INTO t11 SELECT '2025-01-01', 'd1', 0.1;\nSELECT relname from pg_class where relnamespace = 'abc2'::regnamespace ORDER BY 1;\nROLLBACK;\n\n-- associated_table_prefix\nBEGIN;\nCREATE TABLE t12(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time');\nSELECT associated_table_prefix FROM _timescaledb_catalog.hypertable WHERE table_name = 't12';\nROLLBACK;\n\nBEGIN;\nCREATE TABLE t12(time timestamptz NOT NULL, device text, value float) WITH (tsdb.columnstore=false,tsdb.hypertable,tsdb.partition_column='time', tsdb.associated_schema='abc', tsdb.associated_table_prefix='tbl_prefix');\nSELECT associated_table_prefix FROM _timescaledb_catalog.hypertable WHERE table_name = 't12';\nINSERT INTO t12 SELECT '2025-01-01', 'd1', 0.1;\nSELECT relname from pg_class where relnamespace = 'abc'::regnamespace ORDER BY 1;\nROLLBACK;\n\n-- default partition column\nBEGIN;\nCREATE TABLE t13(time timestamptz, device text, value float) WITH (tsdb.hypertable);\nCREATE TABLE t14(\"TiMe\" timestamptz, device text, value float) WITH (tsdb.hypertable);\nSELECT hypertable_name, column_name FROM timescaledb_information.dimensions WHERE hypertable_name IN ('t13','t14') ORDER BY 1;\nROLLBACK;\n\n-- Test default_chunk_time_interval GUC interaction with CREATE TABLE WITH\n-- Tests that the GUC correctly sets the default chunk interval when using\n-- CREATE TABLE WITH syntax instead of create_hypertable().\n\n-- GUC set to '1 week' should be used by CREATE TABLE WITH\nBEGIN;\nSET timescaledb.default_chunk_time_interval = '1 week';\nCREATE TABLE t_guc_week(time timestamptz NOT NULL, device text, value float)\n  WITH (tsdb.hypertable, tsdb.partition_column='time');\nSELECT hypertable_name, time_interval\n  FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 't_guc_week';\nROLLBACK;\n\n-- GUC set to '1 day' with different time types\nBEGIN;\nSET timescaledb.default_chunk_time_interval = '1 day';\nCREATE TABLE t_guc_timestamptz(time timestamptz NOT NULL, device text, value float)\n  WITH (tsdb.hypertable, tsdb.partition_column='time');\nCREATE TABLE t_guc_timestamp(time timestamp NOT NULL, device text, value float)\n  WITH (tsdb.hypertable, tsdb.partition_column='time');\nCREATE TABLE t_guc_date(time date NOT NULL, device text, value float)\n  WITH (tsdb.hypertable, tsdb.partition_column='time');\nSELECT hypertable_name, time_interval\n  FROM timescaledb_information.dimensions\n  WHERE hypertable_name LIKE 't_guc_%'\n  ORDER BY hypertable_name;\nROLLBACK;\n\n-- Explicit tsdb.chunk_interval should override the GUC\nBEGIN;\nSET timescaledb.default_chunk_time_interval = '1 week';\nCREATE TABLE t_guc_override(time timestamptz NOT NULL, device text, value float)\n  WITH (tsdb.hypertable, tsdb.partition_column='time', tsdb.chunk_interval='2 days');\nSELECT hypertable_name, time_interval\n  FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 't_guc_override';\nROLLBACK;\n\n-- Integer partition types have their own default and do not use the GUC\nBEGIN;\nSET timescaledb.default_chunk_time_interval = '1 week';\nCREATE TABLE t_guc_int(time int8 NOT NULL, device text, value float)\n  WITH (tsdb.hypertable, tsdb.partition_column='time');\nSELECT hypertable_name, integer_interval\n  FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 't_guc_int';\nROLLBACK;\n\n-- No GUC set (NULL) should use legacy defaults\nBEGIN;\nRESET timescaledb.default_chunk_time_interval;\nCREATE TABLE t_no_guc(time timestamptz NOT NULL, device text, value float)\n  WITH (tsdb.hypertable, tsdb.partition_column='time');\nSELECT hypertable_name, time_interval\n  FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 't_no_guc';\nROLLBACK;\n\n-- GUC with UUID partition type\nBEGIN;\nSET timescaledb.default_chunk_time_interval = '12 hours';\nCREATE TABLE t_guc_uuid(time uuid NOT NULL, device text, value float)\n  WITH (tsdb.hypertable, tsdb.partition_column='time');\nSELECT hypertable_name, time_interval\n  FROM timescaledb_information.dimensions\n  WHERE hypertable_name = 't_guc_uuid';\nROLLBACK;\n\n-- Cleanup\nRESET timescaledb.default_chunk_time_interval;\n"
  },
  {
    "path": "test/sql/cursor.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE TABLE cursor_test(time timestamptz, device_id int, temp float);\nSELECT create_hypertable('cursor_test','time');\n\nINSERT INTO cursor_test SELECT '2000-01-01',1,0.5;\nINSERT INTO cursor_test SELECT '2001-01-01',1,0.5;\nINSERT INTO cursor_test SELECT '2002-01-01',1,0.5;\n\n\\set ON_ERROR_STOP 0\nBEGIN;\nDECLARE c1 SCROLL CURSOR FOR SELECT * FROM cursor_test;\nFETCH NEXT FROM c1;\n-- this will produce an error on PG < 14 because PostgreSQL checks\n-- for the existence of a scan node with the relation id for every relation\n-- used in the update plan in the plan of the cursor.\nUPDATE cursor_test SET temp = 0.7 WHERE CURRENT OF c1;\nCOMMIT;\n\n-- test cursor with no chunks left after runtime exclusion\nBEGIN;\nDECLARE c1 SCROLL CURSOR FOR SELECT * FROM cursor_test WHERE time > now();\nUPDATE cursor_test SET temp = 0.7 WHERE CURRENT OF c1;\nCOMMIT;\n\n-- test cursor with no chunks left after planning exclusion\nBEGIN;\nDECLARE c1 SCROLL CURSOR FOR SELECT * FROM cursor_test WHERE time > '2010-01-01';\nUPDATE cursor_test SET temp = 0.7 WHERE CURRENT OF c1;\nCOMMIT;\n\\set ON_ERROR_STOP 1\n\nSET timescaledb.enable_constraint_exclusion TO off;\nBEGIN;\nDECLARE c1 SCROLL CURSOR FOR SELECT * FROM cursor_test;\nFETCH NEXT FROM c1;\nUPDATE cursor_test SET temp = 0.7 WHERE CURRENT OF c1;\nCOMMIT;\n\n"
  },
  {
    "path": "test/sql/custom_type.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\nSET client_min_messages TO WARNING;\n\nCREATE OR REPLACE FUNCTION customtype_in(cstring) RETURNS customtype AS\n'timestamptz_in'\nLANGUAGE internal IMMUTABLE STRICT;\nCREATE OR REPLACE FUNCTION customtype_out(customtype) RETURNS cstring AS\n'timestamptz_out'\nLANGUAGE internal IMMUTABLE STRICT;\nCREATE OR REPLACE FUNCTION customtype_recv(internal) RETURNS customtype AS\n'timestamptz_recv'\nLANGUAGE internal IMMUTABLE STRICT;\nCREATE OR REPLACE FUNCTION customtype_send(customtype) RETURNS bytea AS\n'timestamptz_send'\nLANGUAGE internal IMMUTABLE STRICT;\n\nSET client_min_messages TO DEFAULT;\n\nCREATE TYPE customtype (\n INPUT = customtype_in,\n OUTPUT = customtype_out,\n RECEIVE = customtype_recv,\n SEND = customtype_send,\n LIKE = TIMESTAMPTZ\n);\n\nCREATE CAST (customtype AS bigint)\nWITHOUT FUNCTION AS ASSIGNMENT;\nCREATE CAST (bigint AS customtype)\nWITHOUT FUNCTION AS IMPLICIT;\n\nCREATE CAST (customtype AS timestamptz)\nWITHOUT FUNCTION AS ASSIGNMENT;\nCREATE CAST (timestamptz AS customtype)\nWITHOUT FUNCTION AS ASSIGNMENT;\n\nCREATE OR REPLACE FUNCTION customtype_lt(customtype, customtype) RETURNS bool AS\n'timestamp_lt'\nLANGUAGE internal IMMUTABLE STRICT;\nCREATE OPERATOR < (\n\tLEFTARG = customtype,\n\tRIGHTARG = customtype,\n\tPROCEDURE = customtype_lt,\n\tCOMMUTATOR = >,\n\tNEGATOR = >=,\n\tRESTRICT = scalarltsel,\n\tJOIN = scalarltjoinsel\n);\n\nCREATE OR REPLACE FUNCTION customtype_ge(customtype, customtype) RETURNS bool AS\n'timestamp_ge'\nLANGUAGE internal IMMUTABLE STRICT;\nCREATE OPERATOR >= (\n\tLEFTARG = customtype,\n\tRIGHTARG = customtype,\n\tPROCEDURE = customtype_ge,\n\tCOMMUTATOR = <=,\n\tNEGATOR = <,\n\tRESTRICT = scalargtsel,\n\tJOIN = scalargtjoinsel\n);\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n\nCREATE TABLE customtype_test(time_custom customtype, val int);\n\\set ON_ERROR_STOP 0\n-- Using interval type for chunk time interval should fail with custom time type\nSELECT create_hypertable('customtype_test', 'time_custom', chunk_time_interval => INTERVAL '1 day', create_default_indexes=>false);\n\\set ON_ERROR_STOP 1\n\nSELECT create_hypertable('customtype_test', 'time_custom', chunk_time_interval => 10e6::bigint, create_default_indexes=>false);\n\nINSERT INTO customtype_test VALUES ('2001-01-01 01:02:03'::customtype, 10);\nINSERT INTO customtype_test VALUES ('2001-01-01 01:02:03'::customtype, 10);\nINSERT INTO customtype_test VALUES ('2001-01-01 01:02:03'::customtype, 10);\nEXPLAIN (buffers off, costs off) SELECT * FROM customtype_test;\nINSERT INTO customtype_test VALUES ('2001-01-01 01:02:23'::customtype, 11);\nEXPLAIN (buffers off, costs off) SELECT * FROM customtype_test;\n\nSELECT * FROM customtype_test;\n"
  },
  {
    "path": "test/sql/data/alter.tsv",
    "content": "2017-08-22T09:19:22\t21.4\t3\tnr1\tn2r1\n2017-08-23T09:20:17\t31.5\t2\tnr2\tn2r2\n"
  },
  {
    "path": "test/sql/data/copy_data.csv",
    "content": "generate_series,random\n1,0.951734602451324\n2,0.717823888640851\n3,0.543408489786088\n4,0.641131274402142\n5,0.12689296528697\n6,0.0126486560329795\n7,0.213605496101081\n8,0.132784110959619\n9,0.381155731156468\n10,0.284836102742702\n11,0.795640022493899\n12,0.631451691035181\n13,0.0958626130595803\n14,0.929304684977978\n15,0.524866581428796\n16,0.919249163009226\n17,0.878917074296623\n18,0.68551931809634\n19,0.594833800103515\n20,0.819584367796779\n21,0.474171321373433\n22,0.938535195309669\n23,0.333933369256556\n24,0.274582070298493\n25,0.602348630782217\n"
  },
  {
    "path": "test/sql/data/ds1_dev1_1.tsv",
    "content": "1257894000000000000\tdev1\t1.5\t1\t2\ttrue\n1257894000000000000\tdev1\t1.5\t2\t\t\n1257894000000001000\tdev1\t2.5\t3\t\t\n1257894001000000000\tdev1\t3.5\t4\t\t\n1257897600000000000\tdev1\t4.5\t5\t\tfalse\n1257894002000000000\tdev1\t5.5\t6\t\ttrue\n1257894002000000000\tdev1\t5.5\t7\t\tfalse\n"
  },
  {
    "path": "test/sql/data/onek.data",
    "content": "147\t0\t1\t3\t7\t7\t7\t47\t147\t147\t147\t14\t15\tRFAAAA\tAAAAAA\tAAAAxx\n931\t1\t1\t3\t1\t11\t1\t31\t131\t431\t931\t2\t3\tVJAAAA\tBAAAAA\tHHHHxx\n714\t2\t0\t2\t4\t14\t4\t14\t114\t214\t714\t8\t9\tMBAAAA\tCAAAAA\tOOOOxx\n711\t3\t1\t3\t1\t11\t1\t11\t111\t211\t711\t2\t3\tJBAAAA\tDAAAAA\tVVVVxx\n883\t4\t1\t3\t3\t3\t3\t83\t83\t383\t883\t6\t7\tZHAAAA\tEAAAAA\tAAAAxx\n439\t5\t1\t3\t9\t19\t9\t39\t39\t439\t439\t18\t19\tXQAAAA\tFAAAAA\tHHHHxx\n670\t6\t0\t2\t0\t10\t0\t70\t70\t170\t670\t0\t1\tUZAAAA\tGAAAAA\tOOOOxx\n543\t7\t1\t3\t3\t3\t3\t43\t143\t43\t543\t6\t7\tXUAAAA\tHAAAAA\tVVVVxx\n425\t8\t1\t1\t5\t5\t5\t25\t25\t425\t425\t10\t11\tJQAAAA\tIAAAAA\tAAAAxx\n800\t9\t0\t0\t0\t0\t0\t0\t0\t300\t800\t0\t1\tUEAAAA\tJAAAAA\tHHHHxx\n489\t10\t1\t1\t9\t9\t9\t89\t89\t489\t489\t18\t19\tVSAAAA\tKAAAAA\tOOOOxx\n494\t11\t0\t2\t4\t14\t4\t94\t94\t494\t494\t8\t9\tATAAAA\tLAAAAA\tVVVVxx\n880\t12\t0\t0\t0\t0\t0\t80\t80\t380\t880\t0\t1\tWHAAAA\tMAAAAA\tAAAAxx\n611\t13\t1\t3\t1\t11\t1\t11\t11\t111\t611\t2\t3\tNXAAAA\tNAAAAA\tHHHHxx\n226\t14\t0\t2\t6\t6\t6\t26\t26\t226\t226\t12\t13\tSIAAAA\tOAAAAA\tOOOOxx\n774\t15\t0\t2\t4\t14\t4\t74\t174\t274\t774\t8\t9\tUDAAAA\tPAAAAA\tVVVVxx\n298\t16\t0\t2\t8\t18\t8\t98\t98\t298\t298\t16\t17\tMLAAAA\tQAAAAA\tAAAAxx\n682\t17\t0\t2\t2\t2\t2\t82\t82\t182\t682\t4\t5\tGAAAAA\tRAAAAA\tHHHHxx\n864\t18\t0\t0\t4\t4\t4\t64\t64\t364\t864\t8\t9\tGHAAAA\tSAAAAA\tOOOOxx\n183\t19\t1\t3\t3\t3\t3\t83\t183\t183\t183\t6\t7\tBHAAAA\tTAAAAA\tVVVVxx\n885\t20\t1\t1\t5\t5\t5\t85\t85\t385\t885\t10\t11\tBIAAAA\tUAAAAA\tAAAAxx\n997\t21\t1\t1\t7\t17\t7\t97\t197\t497\t997\t14\t15\tJMAAAA\tVAAAAA\tHHHHxx\n966\t22\t0\t2\t6\t6\t6\t66\t166\t466\t966\t12\t13\tELAAAA\tWAAAAA\tOOOOxx\n389\t23\t1\t1\t9\t9\t9\t89\t189\t389\t389\t18\t19\tZOAAAA\tXAAAAA\tVVVVxx\n846\t24\t0\t2\t6\t6\t6\t46\t46\t346\t846\t12\t13\tOGAAAA\tYAAAAA\tAAAAxx\n206\t25\t0\t2\t6\t6\t6\t6\t6\t206\t206\t12\t13\tYHAAAA\tZAAAAA\tHHHHxx\n239\t26\t1\t3\t9\t19\t9\t39\t39\t239\t239\t18\t19\tFJAAAA\tABAAAA\tOOOOxx\n365\t27\t1\t1\t5\t5\t5\t65\t165\t365\t365\t10\t11\tBOAAAA\tBBAAAA\tVVVVxx\n204\t28\t0\t0\t4\t4\t4\t4\t4\t204\t204\t8\t9\tWHAAAA\tCBAAAA\tAAAAxx\n690\t29\t0\t2\t0\t10\t0\t90\t90\t190\t690\t0\t1\tOAAAAA\tDBAAAA\tHHHHxx\n69\t30\t1\t1\t9\t9\t9\t69\t69\t69\t69\t18\t19\tRCAAAA\tEBAAAA\tOOOOxx\n358\t31\t0\t2\t8\t18\t8\t58\t158\t358\t358\t16\t17\tUNAAAA\tFBAAAA\tVVVVxx\n269\t32\t1\t1\t9\t9\t9\t69\t69\t269\t269\t18\t19\tJKAAAA\tGBAAAA\tAAAAxx\n663\t33\t1\t3\t3\t3\t3\t63\t63\t163\t663\t6\t7\tNZAAAA\tHBAAAA\tHHHHxx\n608\t34\t0\t0\t8\t8\t8\t8\t8\t108\t608\t16\t17\tKXAAAA\tIBAAAA\tOOOOxx\n398\t35\t0\t2\t8\t18\t8\t98\t198\t398\t398\t16\t17\tIPAAAA\tJBAAAA\tVVVVxx\n330\t36\t0\t2\t0\t10\t0\t30\t130\t330\t330\t0\t1\tSMAAAA\tKBAAAA\tAAAAxx\n529\t37\t1\t1\t9\t9\t9\t29\t129\t29\t529\t18\t19\tJUAAAA\tLBAAAA\tHHHHxx\n555\t38\t1\t3\t5\t15\t5\t55\t155\t55\t555\t10\t11\tJVAAAA\tMBAAAA\tOOOOxx\n746\t39\t0\t2\t6\t6\t6\t46\t146\t246\t746\t12\t13\tSCAAAA\tNBAAAA\tVVVVxx\n558\t40\t0\t2\t8\t18\t8\t58\t158\t58\t558\t16\t17\tMVAAAA\tOBAAAA\tAAAAxx\n574\t41\t0\t2\t4\t14\t4\t74\t174\t74\t574\t8\t9\tCWAAAA\tPBAAAA\tHHHHxx\n343\t42\t1\t3\t3\t3\t3\t43\t143\t343\t343\t6\t7\tFNAAAA\tQBAAAA\tOOOOxx\n120\t43\t0\t0\t0\t0\t0\t20\t120\t120\t120\t0\t1\tQEAAAA\tRBAAAA\tVVVVxx\n461\t44\t1\t1\t1\t1\t1\t61\t61\t461\t461\t2\t3\tTRAAAA\tSBAAAA\tAAAAxx\n754\t45\t0\t2\t4\t14\t4\t54\t154\t254\t754\t8\t9\tADAAAA\tTBAAAA\tHHHHxx\n772\t46\t0\t0\t2\t12\t2\t72\t172\t272\t772\t4\t5\tSDAAAA\tUBAAAA\tOOOOxx\n749\t47\t1\t1\t9\t9\t9\t49\t149\t249\t749\t18\t19\tVCAAAA\tVBAAAA\tVVVVxx\n386\t48\t0\t2\t6\t6\t6\t86\t186\t386\t386\t12\t13\tWOAAAA\tWBAAAA\tAAAAxx\n9\t49\t1\t1\t9\t9\t9\t9\t9\t9\t9\t18\t19\tJAAAAA\tXBAAAA\tHHHHxx\n771\t50\t1\t3\t1\t11\t1\t71\t171\t271\t771\t2\t3\tRDAAAA\tYBAAAA\tOOOOxx\n470\t51\t0\t2\t0\t10\t0\t70\t70\t470\t470\t0\t1\tCSAAAA\tZBAAAA\tVVVVxx\n238\t52\t0\t2\t8\t18\t8\t38\t38\t238\t238\t16\t17\tEJAAAA\tACAAAA\tAAAAxx\n86\t53\t0\t2\t6\t6\t6\t86\t86\t86\t86\t12\t13\tIDAAAA\tBCAAAA\tHHHHxx\n56\t54\t0\t0\t6\t16\t6\t56\t56\t56\t56\t12\t13\tECAAAA\tCCAAAA\tOOOOxx\n767\t55\t1\t3\t7\t7\t7\t67\t167\t267\t767\t14\t15\tNDAAAA\tDCAAAA\tVVVVxx\n363\t56\t1\t3\t3\t3\t3\t63\t163\t363\t363\t6\t7\tZNAAAA\tECAAAA\tAAAAxx\n655\t57\t1\t3\t5\t15\t5\t55\t55\t155\t655\t10\t11\tFZAAAA\tFCAAAA\tHHHHxx\n394\t58\t0\t2\t4\t14\t4\t94\t194\t394\t394\t8\t9\tEPAAAA\tGCAAAA\tOOOOxx\n223\t59\t1\t3\t3\t3\t3\t23\t23\t223\t223\t6\t7\tPIAAAA\tHCAAAA\tVVVVxx\n946\t60\t0\t2\t6\t6\t6\t46\t146\t446\t946\t12\t13\tKKAAAA\tICAAAA\tAAAAxx\n863\t61\t1\t3\t3\t3\t3\t63\t63\t363\t863\t6\t7\tFHAAAA\tJCAAAA\tHHHHxx\n913\t62\t1\t1\t3\t13\t3\t13\t113\t413\t913\t6\t7\tDJAAAA\tKCAAAA\tOOOOxx\n737\t63\t1\t1\t7\t17\t7\t37\t137\t237\t737\t14\t15\tJCAAAA\tLCAAAA\tVVVVxx\n65\t64\t1\t1\t5\t5\t5\t65\t65\t65\t65\t10\t11\tNCAAAA\tMCAAAA\tAAAAxx\n251\t65\t1\t3\t1\t11\t1\t51\t51\t251\t251\t2\t3\tRJAAAA\tNCAAAA\tHHHHxx\n686\t66\t0\t2\t6\t6\t6\t86\t86\t186\t686\t12\t13\tKAAAAA\tOCAAAA\tOOOOxx\n971\t67\t1\t3\t1\t11\t1\t71\t171\t471\t971\t2\t3\tJLAAAA\tPCAAAA\tVVVVxx\n775\t68\t1\t3\t5\t15\t5\t75\t175\t275\t775\t10\t11\tVDAAAA\tQCAAAA\tAAAAxx\n577\t69\t1\t1\t7\t17\t7\t77\t177\t77\t577\t14\t15\tFWAAAA\tRCAAAA\tHHHHxx\n830\t70\t0\t2\t0\t10\t0\t30\t30\t330\t830\t0\t1\tYFAAAA\tSCAAAA\tOOOOxx\n787\t71\t1\t3\t7\t7\t7\t87\t187\t287\t787\t14\t15\tHEAAAA\tTCAAAA\tVVVVxx\n898\t72\t0\t2\t8\t18\t8\t98\t98\t398\t898\t16\t17\tOIAAAA\tUCAAAA\tAAAAxx\n588\t73\t0\t0\t8\t8\t8\t88\t188\t88\t588\t16\t17\tQWAAAA\tVCAAAA\tHHHHxx\n872\t74\t0\t0\t2\t12\t2\t72\t72\t372\t872\t4\t5\tOHAAAA\tWCAAAA\tOOOOxx\n397\t75\t1\t1\t7\t17\t7\t97\t197\t397\t397\t14\t15\tHPAAAA\tXCAAAA\tVVVVxx\n51\t76\t1\t3\t1\t11\t1\t51\t51\t51\t51\t2\t3\tZBAAAA\tYCAAAA\tAAAAxx\n381\t77\t1\t1\t1\t1\t1\t81\t181\t381\t381\t2\t3\tROAAAA\tZCAAAA\tHHHHxx\n632\t78\t0\t0\t2\t12\t2\t32\t32\t132\t632\t4\t5\tIYAAAA\tADAAAA\tOOOOxx\n31\t79\t1\t3\t1\t11\t1\t31\t31\t31\t31\t2\t3\tFBAAAA\tBDAAAA\tVVVVxx\n855\t80\t1\t3\t5\t15\t5\t55\t55\t355\t855\t10\t11\tXGAAAA\tCDAAAA\tAAAAxx\n699\t81\t1\t3\t9\t19\t9\t99\t99\t199\t699\t18\t19\tXAAAAA\tDDAAAA\tHHHHxx\n562\t82\t0\t2\t2\t2\t2\t62\t162\t62\t562\t4\t5\tQVAAAA\tEDAAAA\tOOOOxx\n681\t83\t1\t1\t1\t1\t1\t81\t81\t181\t681\t2\t3\tFAAAAA\tFDAAAA\tVVVVxx\n585\t84\t1\t1\t5\t5\t5\t85\t185\t85\t585\t10\t11\tNWAAAA\tGDAAAA\tAAAAxx\n35\t85\t1\t3\t5\t15\t5\t35\t35\t35\t35\t10\t11\tJBAAAA\tHDAAAA\tHHHHxx\n962\t86\t0\t2\t2\t2\t2\t62\t162\t462\t962\t4\t5\tALAAAA\tIDAAAA\tOOOOxx\n282\t87\t0\t2\t2\t2\t2\t82\t82\t282\t282\t4\t5\tWKAAAA\tJDAAAA\tVVVVxx\n254\t88\t0\t2\t4\t14\t4\t54\t54\t254\t254\t8\t9\tUJAAAA\tKDAAAA\tAAAAxx\n514\t89\t0\t2\t4\t14\t4\t14\t114\t14\t514\t8\t9\tUTAAAA\tLDAAAA\tHHHHxx\n406\t90\t0\t2\t6\t6\t6\t6\t6\t406\t406\t12\t13\tQPAAAA\tMDAAAA\tOOOOxx\n544\t91\t0\t0\t4\t4\t4\t44\t144\t44\t544\t8\t9\tYUAAAA\tNDAAAA\tVVVVxx\n704\t92\t0\t0\t4\t4\t4\t4\t104\t204\t704\t8\t9\tCBAAAA\tODAAAA\tAAAAxx\n948\t93\t0\t0\t8\t8\t8\t48\t148\t448\t948\t16\t17\tMKAAAA\tPDAAAA\tHHHHxx\n412\t94\t0\t0\t2\t12\t2\t12\t12\t412\t412\t4\t5\tWPAAAA\tQDAAAA\tOOOOxx\n200\t95\t0\t0\t0\t0\t0\t0\t0\t200\t200\t0\t1\tSHAAAA\tRDAAAA\tVVVVxx\n583\t96\t1\t3\t3\t3\t3\t83\t183\t83\t583\t6\t7\tLWAAAA\tSDAAAA\tAAAAxx\n486\t97\t0\t2\t6\t6\t6\t86\t86\t486\t486\t12\t13\tSSAAAA\tTDAAAA\tHHHHxx\n666\t98\t0\t2\t6\t6\t6\t66\t66\t166\t666\t12\t13\tQZAAAA\tUDAAAA\tOOOOxx\n436\t99\t0\t0\t6\t16\t6\t36\t36\t436\t436\t12\t13\tUQAAAA\tVDAAAA\tVVVVxx\n842\t100\t0\t2\t2\t2\t2\t42\t42\t342\t842\t4\t5\tKGAAAA\tWDAAAA\tAAAAxx\n99\t101\t1\t3\t9\t19\t9\t99\t99\t99\t99\t18\t19\tVDAAAA\tXDAAAA\tHHHHxx\n656\t102\t0\t0\t6\t16\t6\t56\t56\t156\t656\t12\t13\tGZAAAA\tYDAAAA\tOOOOxx\n673\t103\t1\t1\t3\t13\t3\t73\t73\t173\t673\t6\t7\tXZAAAA\tZDAAAA\tVVVVxx\n371\t104\t1\t3\t1\t11\t1\t71\t171\t371\t371\t2\t3\tHOAAAA\tAEAAAA\tAAAAxx\n869\t105\t1\t1\t9\t9\t9\t69\t69\t369\t869\t18\t19\tLHAAAA\tBEAAAA\tHHHHxx\n569\t106\t1\t1\t9\t9\t9\t69\t169\t69\t569\t18\t19\tXVAAAA\tCEAAAA\tOOOOxx\n616\t107\t0\t0\t6\t16\t6\t16\t16\t116\t616\t12\t13\tSXAAAA\tDEAAAA\tVVVVxx\n612\t108\t0\t0\t2\t12\t2\t12\t12\t112\t612\t4\t5\tOXAAAA\tEEAAAA\tAAAAxx\n505\t109\t1\t1\t5\t5\t5\t5\t105\t5\t505\t10\t11\tLTAAAA\tFEAAAA\tHHHHxx\n922\t110\t0\t2\t2\t2\t2\t22\t122\t422\t922\t4\t5\tMJAAAA\tGEAAAA\tOOOOxx\n221\t111\t1\t1\t1\t1\t1\t21\t21\t221\t221\t2\t3\tNIAAAA\tHEAAAA\tVVVVxx\n388\t112\t0\t0\t8\t8\t8\t88\t188\t388\t388\t16\t17\tYOAAAA\tIEAAAA\tAAAAxx\n567\t113\t1\t3\t7\t7\t7\t67\t167\t67\t567\t14\t15\tVVAAAA\tJEAAAA\tHHHHxx\n58\t114\t0\t2\t8\t18\t8\t58\t58\t58\t58\t16\t17\tGCAAAA\tKEAAAA\tOOOOxx\n316\t115\t0\t0\t6\t16\t6\t16\t116\t316\t316\t12\t13\tEMAAAA\tLEAAAA\tVVVVxx\n659\t116\t1\t3\t9\t19\t9\t59\t59\t159\t659\t18\t19\tJZAAAA\tMEAAAA\tAAAAxx\n501\t117\t1\t1\t1\t1\t1\t1\t101\t1\t501\t2\t3\tHTAAAA\tNEAAAA\tHHHHxx\n815\t118\t1\t3\t5\t15\t5\t15\t15\t315\t815\t10\t11\tJFAAAA\tOEAAAA\tOOOOxx\n638\t119\t0\t2\t8\t18\t8\t38\t38\t138\t638\t16\t17\tOYAAAA\tPEAAAA\tVVVVxx\n696\t120\t0\t0\t6\t16\t6\t96\t96\t196\t696\t12\t13\tUAAAAA\tQEAAAA\tAAAAxx\n734\t121\t0\t2\t4\t14\t4\t34\t134\t234\t734\t8\t9\tGCAAAA\tREAAAA\tHHHHxx\n237\t122\t1\t1\t7\t17\t7\t37\t37\t237\t237\t14\t15\tDJAAAA\tSEAAAA\tOOOOxx\n816\t123\t0\t0\t6\t16\t6\t16\t16\t316\t816\t12\t13\tKFAAAA\tTEAAAA\tVVVVxx\n917\t124\t1\t1\t7\t17\t7\t17\t117\t417\t917\t14\t15\tHJAAAA\tUEAAAA\tAAAAxx\n844\t125\t0\t0\t4\t4\t4\t44\t44\t344\t844\t8\t9\tMGAAAA\tVEAAAA\tHHHHxx\n657\t126\t1\t1\t7\t17\t7\t57\t57\t157\t657\t14\t15\tHZAAAA\tWEAAAA\tOOOOxx\n952\t127\t0\t0\t2\t12\t2\t52\t152\t452\t952\t4\t5\tQKAAAA\tXEAAAA\tVVVVxx\n519\t128\t1\t3\t9\t19\t9\t19\t119\t19\t519\t18\t19\tZTAAAA\tYEAAAA\tAAAAxx\n792\t129\t0\t0\t2\t12\t2\t92\t192\t292\t792\t4\t5\tMEAAAA\tZEAAAA\tHHHHxx\n275\t130\t1\t3\t5\t15\t5\t75\t75\t275\t275\t10\t11\tPKAAAA\tAFAAAA\tOOOOxx\n319\t131\t1\t3\t9\t19\t9\t19\t119\t319\t319\t18\t19\tHMAAAA\tBFAAAA\tVVVVxx\n487\t132\t1\t3\t7\t7\t7\t87\t87\t487\t487\t14\t15\tTSAAAA\tCFAAAA\tAAAAxx\n945\t133\t1\t1\t5\t5\t5\t45\t145\t445\t945\t10\t11\tJKAAAA\tDFAAAA\tHHHHxx\n584\t134\t0\t0\t4\t4\t4\t84\t184\t84\t584\t8\t9\tMWAAAA\tEFAAAA\tOOOOxx\n765\t135\t1\t1\t5\t5\t5\t65\t165\t265\t765\t10\t11\tLDAAAA\tFFAAAA\tVVVVxx\n814\t136\t0\t2\t4\t14\t4\t14\t14\t314\t814\t8\t9\tIFAAAA\tGFAAAA\tAAAAxx\n359\t137\t1\t3\t9\t19\t9\t59\t159\t359\t359\t18\t19\tVNAAAA\tHFAAAA\tHHHHxx\n548\t138\t0\t0\t8\t8\t8\t48\t148\t48\t548\t16\t17\tCVAAAA\tIFAAAA\tOOOOxx\n811\t139\t1\t3\t1\t11\t1\t11\t11\t311\t811\t2\t3\tFFAAAA\tJFAAAA\tVVVVxx\n531\t140\t1\t3\t1\t11\t1\t31\t131\t31\t531\t2\t3\tLUAAAA\tKFAAAA\tAAAAxx\n104\t141\t0\t0\t4\t4\t4\t4\t104\t104\t104\t8\t9\tAEAAAA\tLFAAAA\tHHHHxx\n33\t142\t1\t1\t3\t13\t3\t33\t33\t33\t33\t6\t7\tHBAAAA\tMFAAAA\tOOOOxx\n404\t143\t0\t0\t4\t4\t4\t4\t4\t404\t404\t8\t9\tOPAAAA\tNFAAAA\tVVVVxx\n995\t144\t1\t3\t5\t15\t5\t95\t195\t495\t995\t10\t11\tHMAAAA\tOFAAAA\tAAAAxx\n408\t145\t0\t0\t8\t8\t8\t8\t8\t408\t408\t16\t17\tSPAAAA\tPFAAAA\tHHHHxx\n93\t146\t1\t1\t3\t13\t3\t93\t93\t93\t93\t6\t7\tPDAAAA\tQFAAAA\tOOOOxx\n794\t147\t0\t2\t4\t14\t4\t94\t194\t294\t794\t8\t9\tOEAAAA\tRFAAAA\tVVVVxx\n833\t148\t1\t1\t3\t13\t3\t33\t33\t333\t833\t6\t7\tBGAAAA\tSFAAAA\tAAAAxx\n615\t149\t1\t3\t5\t15\t5\t15\t15\t115\t615\t10\t11\tRXAAAA\tTFAAAA\tHHHHxx\n333\t150\t1\t1\t3\t13\t3\t33\t133\t333\t333\t6\t7\tVMAAAA\tUFAAAA\tOOOOxx\n357\t151\t1\t1\t7\t17\t7\t57\t157\t357\t357\t14\t15\tTNAAAA\tVFAAAA\tVVVVxx\n999\t152\t1\t3\t9\t19\t9\t99\t199\t499\t999\t18\t19\tLMAAAA\tWFAAAA\tAAAAxx\n515\t153\t1\t3\t5\t15\t5\t15\t115\t15\t515\t10\t11\tVTAAAA\tXFAAAA\tHHHHxx\n685\t154\t1\t1\t5\t5\t5\t85\t85\t185\t685\t10\t11\tJAAAAA\tYFAAAA\tOOOOxx\n692\t155\t0\t0\t2\t12\t2\t92\t92\t192\t692\t4\t5\tQAAAAA\tZFAAAA\tVVVVxx\n627\t156\t1\t3\t7\t7\t7\t27\t27\t127\t627\t14\t15\tDYAAAA\tAGAAAA\tAAAAxx\n654\t157\t0\t2\t4\t14\t4\t54\t54\t154\t654\t8\t9\tEZAAAA\tBGAAAA\tHHHHxx\n115\t158\t1\t3\t5\t15\t5\t15\t115\t115\t115\t10\t11\tLEAAAA\tCGAAAA\tOOOOxx\n75\t159\t1\t3\t5\t15\t5\t75\t75\t75\t75\t10\t11\tXCAAAA\tDGAAAA\tVVVVxx\n14\t160\t0\t2\t4\t14\t4\t14\t14\t14\t14\t8\t9\tOAAAAA\tEGAAAA\tAAAAxx\n148\t161\t0\t0\t8\t8\t8\t48\t148\t148\t148\t16\t17\tSFAAAA\tFGAAAA\tHHHHxx\n201\t162\t1\t1\t1\t1\t1\t1\t1\t201\t201\t2\t3\tTHAAAA\tGGAAAA\tOOOOxx\n862\t163\t0\t2\t2\t2\t2\t62\t62\t362\t862\t4\t5\tEHAAAA\tHGAAAA\tVVVVxx\n634\t164\t0\t2\t4\t14\t4\t34\t34\t134\t634\t8\t9\tKYAAAA\tIGAAAA\tAAAAxx\n589\t165\t1\t1\t9\t9\t9\t89\t189\t89\t589\t18\t19\tRWAAAA\tJGAAAA\tHHHHxx\n142\t166\t0\t2\t2\t2\t2\t42\t142\t142\t142\t4\t5\tMFAAAA\tKGAAAA\tOOOOxx\n545\t167\t1\t1\t5\t5\t5\t45\t145\t45\t545\t10\t11\tZUAAAA\tLGAAAA\tVVVVxx\n983\t168\t1\t3\t3\t3\t3\t83\t183\t483\t983\t6\t7\tVLAAAA\tMGAAAA\tAAAAxx\n87\t169\t1\t3\t7\t7\t7\t87\t87\t87\t87\t14\t15\tJDAAAA\tNGAAAA\tHHHHxx\n335\t170\t1\t3\t5\t15\t5\t35\t135\t335\t335\t10\t11\tXMAAAA\tOGAAAA\tOOOOxx\n915\t171\t1\t3\t5\t15\t5\t15\t115\t415\t915\t10\t11\tFJAAAA\tPGAAAA\tVVVVxx\n286\t172\t0\t2\t6\t6\t6\t86\t86\t286\t286\t12\t13\tALAAAA\tQGAAAA\tAAAAxx\n361\t173\t1\t1\t1\t1\t1\t61\t161\t361\t361\t2\t3\tXNAAAA\tRGAAAA\tHHHHxx\n97\t174\t1\t1\t7\t17\t7\t97\t97\t97\t97\t14\t15\tTDAAAA\tSGAAAA\tOOOOxx\n98\t175\t0\t2\t8\t18\t8\t98\t98\t98\t98\t16\t17\tUDAAAA\tTGAAAA\tVVVVxx\n377\t176\t1\t1\t7\t17\t7\t77\t177\t377\t377\t14\t15\tNOAAAA\tUGAAAA\tAAAAxx\n525\t177\t1\t1\t5\t5\t5\t25\t125\t25\t525\t10\t11\tFUAAAA\tVGAAAA\tHHHHxx\n448\t178\t0\t0\t8\t8\t8\t48\t48\t448\t448\t16\t17\tGRAAAA\tWGAAAA\tOOOOxx\n154\t179\t0\t2\t4\t14\t4\t54\t154\t154\t154\t8\t9\tYFAAAA\tXGAAAA\tVVVVxx\n866\t180\t0\t2\t6\t6\t6\t66\t66\t366\t866\t12\t13\tIHAAAA\tYGAAAA\tAAAAxx\n741\t181\t1\t1\t1\t1\t1\t41\t141\t241\t741\t2\t3\tNCAAAA\tZGAAAA\tHHHHxx\n172\t182\t0\t0\t2\t12\t2\t72\t172\t172\t172\t4\t5\tQGAAAA\tAHAAAA\tOOOOxx\n843\t183\t1\t3\t3\t3\t3\t43\t43\t343\t843\t6\t7\tLGAAAA\tBHAAAA\tVVVVxx\n378\t184\t0\t2\t8\t18\t8\t78\t178\t378\t378\t16\t17\tOOAAAA\tCHAAAA\tAAAAxx\n804\t185\t0\t0\t4\t4\t4\t4\t4\t304\t804\t8\t9\tYEAAAA\tDHAAAA\tHHHHxx\n596\t186\t0\t0\t6\t16\t6\t96\t196\t96\t596\t12\t13\tYWAAAA\tEHAAAA\tOOOOxx\n77\t187\t1\t1\t7\t17\t7\t77\t77\t77\t77\t14\t15\tZCAAAA\tFHAAAA\tVVVVxx\n572\t188\t0\t0\t2\t12\t2\t72\t172\t72\t572\t4\t5\tAWAAAA\tGHAAAA\tAAAAxx\n444\t189\t0\t0\t4\t4\t4\t44\t44\t444\t444\t8\t9\tCRAAAA\tHHAAAA\tHHHHxx\n47\t190\t1\t3\t7\t7\t7\t47\t47\t47\t47\t14\t15\tVBAAAA\tIHAAAA\tOOOOxx\n274\t191\t0\t2\t4\t14\t4\t74\t74\t274\t274\t8\t9\tOKAAAA\tJHAAAA\tVVVVxx\n40\t192\t0\t0\t0\t0\t0\t40\t40\t40\t40\t0\t1\tOBAAAA\tKHAAAA\tAAAAxx\n339\t193\t1\t3\t9\t19\t9\t39\t139\t339\t339\t18\t19\tBNAAAA\tLHAAAA\tHHHHxx\n13\t194\t1\t1\t3\t13\t3\t13\t13\t13\t13\t6\t7\tNAAAAA\tMHAAAA\tOOOOxx\n878\t195\t0\t2\t8\t18\t8\t78\t78\t378\t878\t16\t17\tUHAAAA\tNHAAAA\tVVVVxx\n53\t196\t1\t1\t3\t13\t3\t53\t53\t53\t53\t6\t7\tBCAAAA\tOHAAAA\tAAAAxx\n939\t197\t1\t3\t9\t19\t9\t39\t139\t439\t939\t18\t19\tDKAAAA\tPHAAAA\tHHHHxx\n928\t198\t0\t0\t8\t8\t8\t28\t128\t428\t928\t16\t17\tSJAAAA\tQHAAAA\tOOOOxx\n886\t199\t0\t2\t6\t6\t6\t86\t86\t386\t886\t12\t13\tCIAAAA\tRHAAAA\tVVVVxx\n267\t200\t1\t3\t7\t7\t7\t67\t67\t267\t267\t14\t15\tHKAAAA\tSHAAAA\tAAAAxx\n105\t201\t1\t1\t5\t5\t5\t5\t105\t105\t105\t10\t11\tBEAAAA\tTHAAAA\tHHHHxx\n312\t202\t0\t0\t2\t12\t2\t12\t112\t312\t312\t4\t5\tAMAAAA\tUHAAAA\tOOOOxx\n552\t203\t0\t0\t2\t12\t2\t52\t152\t52\t552\t4\t5\tGVAAAA\tVHAAAA\tVVVVxx\n918\t204\t0\t2\t8\t18\t8\t18\t118\t418\t918\t16\t17\tIJAAAA\tWHAAAA\tAAAAxx\n114\t205\t0\t2\t4\t14\t4\t14\t114\t114\t114\t8\t9\tKEAAAA\tXHAAAA\tHHHHxx\n805\t206\t1\t1\t5\t5\t5\t5\t5\t305\t805\t10\t11\tZEAAAA\tYHAAAA\tOOOOxx\n875\t207\t1\t3\t5\t15\t5\t75\t75\t375\t875\t10\t11\tRHAAAA\tZHAAAA\tVVVVxx\n225\t208\t1\t1\t5\t5\t5\t25\t25\t225\t225\t10\t11\tRIAAAA\tAIAAAA\tAAAAxx\n495\t209\t1\t3\t5\t15\t5\t95\t95\t495\t495\t10\t11\tBTAAAA\tBIAAAA\tHHHHxx\n150\t210\t0\t2\t0\t10\t0\t50\t150\t150\t150\t0\t1\tUFAAAA\tCIAAAA\tOOOOxx\n759\t211\t1\t3\t9\t19\t9\t59\t159\t259\t759\t18\t19\tFDAAAA\tDIAAAA\tVVVVxx\n149\t212\t1\t1\t9\t9\t9\t49\t149\t149\t149\t18\t19\tTFAAAA\tEIAAAA\tAAAAxx\n480\t213\t0\t0\t0\t0\t0\t80\t80\t480\t480\t0\t1\tMSAAAA\tFIAAAA\tHHHHxx\n1\t214\t1\t1\t1\t1\t1\t1\t1\t1\t1\t2\t3\tBAAAAA\tGIAAAA\tOOOOxx\n557\t215\t1\t1\t7\t17\t7\t57\t157\t57\t557\t14\t15\tLVAAAA\tHIAAAA\tVVVVxx\n295\t216\t1\t3\t5\t15\t5\t95\t95\t295\t295\t10\t11\tJLAAAA\tIIAAAA\tAAAAxx\n854\t217\t0\t2\t4\t14\t4\t54\t54\t354\t854\t8\t9\tWGAAAA\tJIAAAA\tHHHHxx\n420\t218\t0\t0\t0\t0\t0\t20\t20\t420\t420\t0\t1\tEQAAAA\tKIAAAA\tOOOOxx\n414\t219\t0\t2\t4\t14\t4\t14\t14\t414\t414\t8\t9\tYPAAAA\tLIAAAA\tVVVVxx\n758\t220\t0\t2\t8\t18\t8\t58\t158\t258\t758\t16\t17\tEDAAAA\tMIAAAA\tAAAAxx\n879\t221\t1\t3\t9\t19\t9\t79\t79\t379\t879\t18\t19\tVHAAAA\tNIAAAA\tHHHHxx\n332\t222\t0\t0\t2\t12\t2\t32\t132\t332\t332\t4\t5\tUMAAAA\tOIAAAA\tOOOOxx\n78\t223\t0\t2\t8\t18\t8\t78\t78\t78\t78\t16\t17\tADAAAA\tPIAAAA\tVVVVxx\n851\t224\t1\t3\t1\t11\t1\t51\t51\t351\t851\t2\t3\tTGAAAA\tQIAAAA\tAAAAxx\n592\t225\t0\t0\t2\t12\t2\t92\t192\t92\t592\t4\t5\tUWAAAA\tRIAAAA\tHHHHxx\n979\t226\t1\t3\t9\t19\t9\t79\t179\t479\t979\t18\t19\tRLAAAA\tSIAAAA\tOOOOxx\n989\t227\t1\t1\t9\t9\t9\t89\t189\t489\t989\t18\t19\tBMAAAA\tTIAAAA\tVVVVxx\n752\t228\t0\t0\t2\t12\t2\t52\t152\t252\t752\t4\t5\tYCAAAA\tUIAAAA\tAAAAxx\n214\t229\t0\t2\t4\t14\t4\t14\t14\t214\t214\t8\t9\tGIAAAA\tVIAAAA\tHHHHxx\n453\t230\t1\t1\t3\t13\t3\t53\t53\t453\t453\t6\t7\tLRAAAA\tWIAAAA\tOOOOxx\n540\t231\t0\t0\t0\t0\t0\t40\t140\t40\t540\t0\t1\tUUAAAA\tXIAAAA\tVVVVxx\n597\t232\t1\t1\t7\t17\t7\t97\t197\t97\t597\t14\t15\tZWAAAA\tYIAAAA\tAAAAxx\n356\t233\t0\t0\t6\t16\t6\t56\t156\t356\t356\t12\t13\tSNAAAA\tZIAAAA\tHHHHxx\n720\t234\t0\t0\t0\t0\t0\t20\t120\t220\t720\t0\t1\tSBAAAA\tAJAAAA\tOOOOxx\n367\t235\t1\t3\t7\t7\t7\t67\t167\t367\t367\t14\t15\tDOAAAA\tBJAAAA\tVVVVxx\n762\t236\t0\t2\t2\t2\t2\t62\t162\t262\t762\t4\t5\tIDAAAA\tCJAAAA\tAAAAxx\n986\t237\t0\t2\t6\t6\t6\t86\t186\t486\t986\t12\t13\tYLAAAA\tDJAAAA\tHHHHxx\n924\t238\t0\t0\t4\t4\t4\t24\t124\t424\t924\t8\t9\tOJAAAA\tEJAAAA\tOOOOxx\n779\t239\t1\t3\t9\t19\t9\t79\t179\t279\t779\t18\t19\tZDAAAA\tFJAAAA\tVVVVxx\n684\t240\t0\t0\t4\t4\t4\t84\t84\t184\t684\t8\t9\tIAAAAA\tGJAAAA\tAAAAxx\n413\t241\t1\t1\t3\t13\t3\t13\t13\t413\t413\t6\t7\tXPAAAA\tHJAAAA\tHHHHxx\n479\t242\t1\t3\t9\t19\t9\t79\t79\t479\t479\t18\t19\tLSAAAA\tIJAAAA\tOOOOxx\n731\t243\t1\t3\t1\t11\t1\t31\t131\t231\t731\t2\t3\tDCAAAA\tJJAAAA\tVVVVxx\n409\t244\t1\t1\t9\t9\t9\t9\t9\t409\t409\t18\t19\tTPAAAA\tKJAAAA\tAAAAxx\n372\t245\t0\t0\t2\t12\t2\t72\t172\t372\t372\t4\t5\tIOAAAA\tLJAAAA\tHHHHxx\n139\t246\t1\t3\t9\t19\t9\t39\t139\t139\t139\t18\t19\tJFAAAA\tMJAAAA\tOOOOxx\n717\t247\t1\t1\t7\t17\t7\t17\t117\t217\t717\t14\t15\tPBAAAA\tNJAAAA\tVVVVxx\n539\t248\t1\t3\t9\t19\t9\t39\t139\t39\t539\t18\t19\tTUAAAA\tOJAAAA\tAAAAxx\n318\t249\t0\t2\t8\t18\t8\t18\t118\t318\t318\t16\t17\tGMAAAA\tPJAAAA\tHHHHxx\n208\t250\t0\t0\t8\t8\t8\t8\t8\t208\t208\t16\t17\tAIAAAA\tQJAAAA\tOOOOxx\n797\t251\t1\t1\t7\t17\t7\t97\t197\t297\t797\t14\t15\tREAAAA\tRJAAAA\tVVVVxx\n661\t252\t1\t1\t1\t1\t1\t61\t61\t161\t661\t2\t3\tLZAAAA\tSJAAAA\tAAAAxx\n50\t253\t0\t2\t0\t10\t0\t50\t50\t50\t50\t0\t1\tYBAAAA\tTJAAAA\tHHHHxx\n102\t254\t0\t2\t2\t2\t2\t2\t102\t102\t102\t4\t5\tYDAAAA\tUJAAAA\tOOOOxx\n484\t255\t0\t0\t4\t4\t4\t84\t84\t484\t484\t8\t9\tQSAAAA\tVJAAAA\tVVVVxx\n108\t256\t0\t0\t8\t8\t8\t8\t108\t108\t108\t16\t17\tEEAAAA\tWJAAAA\tAAAAxx\n140\t257\t0\t0\t0\t0\t0\t40\t140\t140\t140\t0\t1\tKFAAAA\tXJAAAA\tHHHHxx\n996\t258\t0\t0\t6\t16\t6\t96\t196\t496\t996\t12\t13\tIMAAAA\tYJAAAA\tOOOOxx\n687\t259\t1\t3\t7\t7\t7\t87\t87\t187\t687\t14\t15\tLAAAAA\tZJAAAA\tVVVVxx\n241\t260\t1\t1\t1\t1\t1\t41\t41\t241\t241\t2\t3\tHJAAAA\tAKAAAA\tAAAAxx\n923\t261\t1\t3\t3\t3\t3\t23\t123\t423\t923\t6\t7\tNJAAAA\tBKAAAA\tHHHHxx\n500\t262\t0\t0\t0\t0\t0\t0\t100\t0\t500\t0\t1\tGTAAAA\tCKAAAA\tOOOOxx\n536\t263\t0\t0\t6\t16\t6\t36\t136\t36\t536\t12\t13\tQUAAAA\tDKAAAA\tVVVVxx\n490\t264\t0\t2\t0\t10\t0\t90\t90\t490\t490\t0\t1\tWSAAAA\tEKAAAA\tAAAAxx\n773\t265\t1\t1\t3\t13\t3\t73\t173\t273\t773\t6\t7\tTDAAAA\tFKAAAA\tHHHHxx\n19\t266\t1\t3\t9\t19\t9\t19\t19\t19\t19\t18\t19\tTAAAAA\tGKAAAA\tOOOOxx\n534\t267\t0\t2\t4\t14\t4\t34\t134\t34\t534\t8\t9\tOUAAAA\tHKAAAA\tVVVVxx\n941\t268\t1\t1\t1\t1\t1\t41\t141\t441\t941\t2\t3\tFKAAAA\tIKAAAA\tAAAAxx\n477\t269\t1\t1\t7\t17\t7\t77\t77\t477\t477\t14\t15\tJSAAAA\tJKAAAA\tHHHHxx\n173\t270\t1\t1\t3\t13\t3\t73\t173\t173\t173\t6\t7\tRGAAAA\tKKAAAA\tOOOOxx\n113\t271\t1\t1\t3\t13\t3\t13\t113\t113\t113\t6\t7\tJEAAAA\tLKAAAA\tVVVVxx\n526\t272\t0\t2\t6\t6\t6\t26\t126\t26\t526\t12\t13\tGUAAAA\tMKAAAA\tAAAAxx\n727\t273\t1\t3\t7\t7\t7\t27\t127\t227\t727\t14\t15\tZBAAAA\tNKAAAA\tHHHHxx\n302\t274\t0\t2\t2\t2\t2\t2\t102\t302\t302\t4\t5\tQLAAAA\tOKAAAA\tOOOOxx\n789\t275\t1\t1\t9\t9\t9\t89\t189\t289\t789\t18\t19\tJEAAAA\tPKAAAA\tVVVVxx\n447\t276\t1\t3\t7\t7\t7\t47\t47\t447\t447\t14\t15\tFRAAAA\tQKAAAA\tAAAAxx\n884\t277\t0\t0\t4\t4\t4\t84\t84\t384\t884\t8\t9\tAIAAAA\tRKAAAA\tHHHHxx\n718\t278\t0\t2\t8\t18\t8\t18\t118\t218\t718\t16\t17\tQBAAAA\tSKAAAA\tOOOOxx\n818\t279\t0\t2\t8\t18\t8\t18\t18\t318\t818\t16\t17\tMFAAAA\tTKAAAA\tVVVVxx\n466\t280\t0\t2\t6\t6\t6\t66\t66\t466\t466\t12\t13\tYRAAAA\tUKAAAA\tAAAAxx\n131\t281\t1\t3\t1\t11\t1\t31\t131\t131\t131\t2\t3\tBFAAAA\tVKAAAA\tHHHHxx\n503\t282\t1\t3\t3\t3\t3\t3\t103\t3\t503\t6\t7\tJTAAAA\tWKAAAA\tOOOOxx\n364\t283\t0\t0\t4\t4\t4\t64\t164\t364\t364\t8\t9\tAOAAAA\tXKAAAA\tVVVVxx\n934\t284\t0\t2\t4\t14\t4\t34\t134\t434\t934\t8\t9\tYJAAAA\tYKAAAA\tAAAAxx\n542\t285\t0\t2\t2\t2\t2\t42\t142\t42\t542\t4\t5\tWUAAAA\tZKAAAA\tHHHHxx\n146\t286\t0\t2\t6\t6\t6\t46\t146\t146\t146\t12\t13\tQFAAAA\tALAAAA\tOOOOxx\n652\t287\t0\t0\t2\t12\t2\t52\t52\t152\t652\t4\t5\tCZAAAA\tBLAAAA\tVVVVxx\n566\t288\t0\t2\t6\t6\t6\t66\t166\t66\t566\t12\t13\tUVAAAA\tCLAAAA\tAAAAxx\n788\t289\t0\t0\t8\t8\t8\t88\t188\t288\t788\t16\t17\tIEAAAA\tDLAAAA\tHHHHxx\n168\t290\t0\t0\t8\t8\t8\t68\t168\t168\t168\t16\t17\tMGAAAA\tELAAAA\tOOOOxx\n736\t291\t0\t0\t6\t16\t6\t36\t136\t236\t736\t12\t13\tICAAAA\tFLAAAA\tVVVVxx\n795\t292\t1\t3\t5\t15\t5\t95\t195\t295\t795\t10\t11\tPEAAAA\tGLAAAA\tAAAAxx\n103\t293\t1\t3\t3\t3\t3\t3\t103\t103\t103\t6\t7\tZDAAAA\tHLAAAA\tHHHHxx\n763\t294\t1\t3\t3\t3\t3\t63\t163\t263\t763\t6\t7\tJDAAAA\tILAAAA\tOOOOxx\n256\t295\t0\t0\t6\t16\t6\t56\t56\t256\t256\t12\t13\tWJAAAA\tJLAAAA\tVVVVxx\n63\t296\t1\t3\t3\t3\t3\t63\t63\t63\t63\t6\t7\tLCAAAA\tKLAAAA\tAAAAxx\n702\t297\t0\t2\t2\t2\t2\t2\t102\t202\t702\t4\t5\tABAAAA\tLLAAAA\tHHHHxx\n390\t298\t0\t2\t0\t10\t0\t90\t190\t390\t390\t0\t1\tAPAAAA\tMLAAAA\tOOOOxx\n116\t299\t0\t0\t6\t16\t6\t16\t116\t116\t116\t12\t13\tMEAAAA\tNLAAAA\tVVVVxx\n354\t300\t0\t2\t4\t14\t4\t54\t154\t354\t354\t8\t9\tQNAAAA\tOLAAAA\tAAAAxx\n162\t301\t0\t2\t2\t2\t2\t62\t162\t162\t162\t4\t5\tGGAAAA\tPLAAAA\tHHHHxx\n71\t302\t1\t3\t1\t11\t1\t71\t71\t71\t71\t2\t3\tTCAAAA\tQLAAAA\tOOOOxx\n916\t303\t0\t0\t6\t16\t6\t16\t116\t416\t916\t12\t13\tGJAAAA\tRLAAAA\tVVVVxx\n565\t304\t1\t1\t5\t5\t5\t65\t165\t65\t565\t10\t11\tTVAAAA\tSLAAAA\tAAAAxx\n509\t305\t1\t1\t9\t9\t9\t9\t109\t9\t509\t18\t19\tPTAAAA\tTLAAAA\tHHHHxx\n20\t306\t0\t0\t0\t0\t0\t20\t20\t20\t20\t0\t1\tUAAAAA\tULAAAA\tOOOOxx\n813\t307\t1\t1\t3\t13\t3\t13\t13\t313\t813\t6\t7\tHFAAAA\tVLAAAA\tVVVVxx\n80\t308\t0\t0\t0\t0\t0\t80\t80\t80\t80\t0\t1\tCDAAAA\tWLAAAA\tAAAAxx\n400\t309\t0\t0\t0\t0\t0\t0\t0\t400\t400\t0\t1\tKPAAAA\tXLAAAA\tHHHHxx\n888\t310\t0\t0\t8\t8\t8\t88\t88\t388\t888\t16\t17\tEIAAAA\tYLAAAA\tOOOOxx\n825\t311\t1\t1\t5\t5\t5\t25\t25\t325\t825\t10\t11\tTFAAAA\tZLAAAA\tVVVVxx\n401\t312\t1\t1\t1\t1\t1\t1\t1\t401\t401\t2\t3\tLPAAAA\tAMAAAA\tAAAAxx\n158\t313\t0\t2\t8\t18\t8\t58\t158\t158\t158\t16\t17\tCGAAAA\tBMAAAA\tHHHHxx\n973\t314\t1\t1\t3\t13\t3\t73\t173\t473\t973\t6\t7\tLLAAAA\tCMAAAA\tOOOOxx\n324\t315\t0\t0\t4\t4\t4\t24\t124\t324\t324\t8\t9\tMMAAAA\tDMAAAA\tVVVVxx\n873\t316\t1\t1\t3\t13\t3\t73\t73\t373\t873\t6\t7\tPHAAAA\tEMAAAA\tAAAAxx\n676\t317\t0\t0\t6\t16\t6\t76\t76\t176\t676\t12\t13\tAAAAAA\tFMAAAA\tHHHHxx\n199\t318\t1\t3\t9\t19\t9\t99\t199\t199\t199\t18\t19\tRHAAAA\tGMAAAA\tOOOOxx\n304\t319\t0\t0\t4\t4\t4\t4\t104\t304\t304\t8\t9\tSLAAAA\tHMAAAA\tVVVVxx\n338\t320\t0\t2\t8\t18\t8\t38\t138\t338\t338\t16\t17\tANAAAA\tIMAAAA\tAAAAxx\n743\t321\t1\t3\t3\t3\t3\t43\t143\t243\t743\t6\t7\tPCAAAA\tJMAAAA\tHHHHxx\n730\t322\t0\t2\t0\t10\t0\t30\t130\t230\t730\t0\t1\tCCAAAA\tKMAAAA\tOOOOxx\n130\t323\t0\t2\t0\t10\t0\t30\t130\t130\t130\t0\t1\tAFAAAA\tLMAAAA\tVVVVxx\n224\t324\t0\t0\t4\t4\t4\t24\t24\t224\t224\t8\t9\tQIAAAA\tMMAAAA\tAAAAxx\n216\t325\t0\t0\t6\t16\t6\t16\t16\t216\t216\t12\t13\tIIAAAA\tNMAAAA\tHHHHxx\n2\t326\t0\t2\t2\t2\t2\t2\t2\t2\t2\t4\t5\tCAAAAA\tOMAAAA\tOOOOxx\n836\t327\t0\t0\t6\t16\t6\t36\t36\t336\t836\t12\t13\tEGAAAA\tPMAAAA\tVVVVxx\n443\t328\t1\t3\t3\t3\t3\t43\t43\t443\t443\t6\t7\tBRAAAA\tQMAAAA\tAAAAxx\n777\t329\t1\t1\t7\t17\t7\t77\t177\t277\t777\t14\t15\tXDAAAA\tRMAAAA\tHHHHxx\n126\t330\t0\t2\t6\t6\t6\t26\t126\t126\t126\t12\t13\tWEAAAA\tSMAAAA\tOOOOxx\n117\t331\t1\t1\t7\t17\t7\t17\t117\t117\t117\t14\t15\tNEAAAA\tTMAAAA\tVVVVxx\n633\t332\t1\t1\t3\t13\t3\t33\t33\t133\t633\t6\t7\tJYAAAA\tUMAAAA\tAAAAxx\n310\t333\t0\t2\t0\t10\t0\t10\t110\t310\t310\t0\t1\tYLAAAA\tVMAAAA\tHHHHxx\n622\t334\t0\t2\t2\t2\t2\t22\t22\t122\t622\t4\t5\tYXAAAA\tWMAAAA\tOOOOxx\n268\t335\t0\t0\t8\t8\t8\t68\t68\t268\t268\t16\t17\tIKAAAA\tXMAAAA\tVVVVxx\n384\t336\t0\t0\t4\t4\t4\t84\t184\t384\t384\t8\t9\tUOAAAA\tYMAAAA\tAAAAxx\n460\t337\t0\t0\t0\t0\t0\t60\t60\t460\t460\t0\t1\tSRAAAA\tZMAAAA\tHHHHxx\n475\t338\t1\t3\t5\t15\t5\t75\t75\t475\t475\t10\t11\tHSAAAA\tANAAAA\tOOOOxx\n624\t339\t0\t0\t4\t4\t4\t24\t24\t124\t624\t8\t9\tAYAAAA\tBNAAAA\tVVVVxx\n826\t340\t0\t2\t6\t6\t6\t26\t26\t326\t826\t12\t13\tUFAAAA\tCNAAAA\tAAAAxx\n680\t341\t0\t0\t0\t0\t0\t80\t80\t180\t680\t0\t1\tEAAAAA\tDNAAAA\tHHHHxx\n306\t342\t0\t2\t6\t6\t6\t6\t106\t306\t306\t12\t13\tULAAAA\tENAAAA\tOOOOxx\n896\t343\t0\t0\t6\t16\t6\t96\t96\t396\t896\t12\t13\tMIAAAA\tFNAAAA\tVVVVxx\n30\t344\t0\t2\t0\t10\t0\t30\t30\t30\t30\t0\t1\tEBAAAA\tGNAAAA\tAAAAxx\n576\t345\t0\t0\t6\t16\t6\t76\t176\t76\t576\t12\t13\tEWAAAA\tHNAAAA\tHHHHxx\n551\t346\t1\t3\t1\t11\t1\t51\t151\t51\t551\t2\t3\tFVAAAA\tINAAAA\tOOOOxx\n639\t347\t1\t3\t9\t19\t9\t39\t39\t139\t639\t18\t19\tPYAAAA\tJNAAAA\tVVVVxx\n975\t348\t1\t3\t5\t15\t5\t75\t175\t475\t975\t10\t11\tNLAAAA\tKNAAAA\tAAAAxx\n882\t349\t0\t2\t2\t2\t2\t82\t82\t382\t882\t4\t5\tYHAAAA\tLNAAAA\tHHHHxx\n160\t350\t0\t0\t0\t0\t0\t60\t160\t160\t160\t0\t1\tEGAAAA\tMNAAAA\tOOOOxx\n522\t351\t0\t2\t2\t2\t2\t22\t122\t22\t522\t4\t5\tCUAAAA\tNNAAAA\tVVVVxx\n620\t352\t0\t0\t0\t0\t0\t20\t20\t120\t620\t0\t1\tWXAAAA\tONAAAA\tAAAAxx\n719\t353\t1\t3\t9\t19\t9\t19\t119\t219\t719\t18\t19\tRBAAAA\tPNAAAA\tHHHHxx\n88\t354\t0\t0\t8\t8\t8\t88\t88\t88\t88\t16\t17\tKDAAAA\tQNAAAA\tOOOOxx\n614\t355\t0\t2\t4\t14\t4\t14\t14\t114\t614\t8\t9\tQXAAAA\tRNAAAA\tVVVVxx\n54\t356\t0\t2\t4\t14\t4\t54\t54\t54\t54\t8\t9\tCCAAAA\tSNAAAA\tAAAAxx\n209\t357\t1\t1\t9\t9\t9\t9\t9\t209\t209\t18\t19\tBIAAAA\tTNAAAA\tHHHHxx\n67\t358\t1\t3\t7\t7\t7\t67\t67\t67\t67\t14\t15\tPCAAAA\tUNAAAA\tOOOOxx\n809\t359\t1\t1\t9\t9\t9\t9\t9\t309\t809\t18\t19\tDFAAAA\tVNAAAA\tVVVVxx\n982\t360\t0\t2\t2\t2\t2\t82\t182\t482\t982\t4\t5\tULAAAA\tWNAAAA\tAAAAxx\n817\t361\t1\t1\t7\t17\t7\t17\t17\t317\t817\t14\t15\tLFAAAA\tXNAAAA\tHHHHxx\n187\t362\t1\t3\t7\t7\t7\t87\t187\t187\t187\t14\t15\tFHAAAA\tYNAAAA\tOOOOxx\n992\t363\t0\t0\t2\t12\t2\t92\t192\t492\t992\t4\t5\tEMAAAA\tZNAAAA\tVVVVxx\n580\t364\t0\t0\t0\t0\t0\t80\t180\t80\t580\t0\t1\tIWAAAA\tAOAAAA\tAAAAxx\n658\t365\t0\t2\t8\t18\t8\t58\t58\t158\t658\t16\t17\tIZAAAA\tBOAAAA\tHHHHxx\n222\t366\t0\t2\t2\t2\t2\t22\t22\t222\t222\t4\t5\tOIAAAA\tCOAAAA\tOOOOxx\n667\t367\t1\t3\t7\t7\t7\t67\t67\t167\t667\t14\t15\tRZAAAA\tDOAAAA\tVVVVxx\n715\t368\t1\t3\t5\t15\t5\t15\t115\t215\t715\t10\t11\tNBAAAA\tEOAAAA\tAAAAxx\n990\t369\t0\t2\t0\t10\t0\t90\t190\t490\t990\t0\t1\tCMAAAA\tFOAAAA\tHHHHxx\n22\t370\t0\t2\t2\t2\t2\t22\t22\t22\t22\t4\t5\tWAAAAA\tGOAAAA\tOOOOxx\n362\t371\t0\t2\t2\t2\t2\t62\t162\t362\t362\t4\t5\tYNAAAA\tHOAAAA\tVVVVxx\n376\t372\t0\t0\t6\t16\t6\t76\t176\t376\t376\t12\t13\tMOAAAA\tIOAAAA\tAAAAxx\n246\t373\t0\t2\t6\t6\t6\t46\t46\t246\t246\t12\t13\tMJAAAA\tJOAAAA\tHHHHxx\n300\t374\t0\t0\t0\t0\t0\t0\t100\t300\t300\t0\t1\tOLAAAA\tKOAAAA\tOOOOxx\n231\t375\t1\t3\t1\t11\t1\t31\t31\t231\t231\t2\t3\tXIAAAA\tLOAAAA\tVVVVxx\n151\t376\t1\t3\t1\t11\t1\t51\t151\t151\t151\t2\t3\tVFAAAA\tMOAAAA\tAAAAxx\n29\t377\t1\t1\t9\t9\t9\t29\t29\t29\t29\t18\t19\tDBAAAA\tNOAAAA\tHHHHxx\n297\t378\t1\t1\t7\t17\t7\t97\t97\t297\t297\t14\t15\tLLAAAA\tOOAAAA\tOOOOxx\n403\t379\t1\t3\t3\t3\t3\t3\t3\t403\t403\t6\t7\tNPAAAA\tPOAAAA\tVVVVxx\n716\t380\t0\t0\t6\t16\t6\t16\t116\t216\t716\t12\t13\tOBAAAA\tQOAAAA\tAAAAxx\n260\t381\t0\t0\t0\t0\t0\t60\t60\t260\t260\t0\t1\tAKAAAA\tROAAAA\tHHHHxx\n170\t382\t0\t2\t0\t10\t0\t70\t170\t170\t170\t0\t1\tOGAAAA\tSOAAAA\tOOOOxx\n285\t383\t1\t1\t5\t5\t5\t85\t85\t285\t285\t10\t11\tZKAAAA\tTOAAAA\tVVVVxx\n82\t384\t0\t2\t2\t2\t2\t82\t82\t82\t82\t4\t5\tEDAAAA\tUOAAAA\tAAAAxx\n958\t385\t0\t2\t8\t18\t8\t58\t158\t458\t958\t16\t17\tWKAAAA\tVOAAAA\tHHHHxx\n175\t386\t1\t3\t5\t15\t5\t75\t175\t175\t175\t10\t11\tTGAAAA\tWOAAAA\tOOOOxx\n671\t387\t1\t3\t1\t11\t1\t71\t71\t171\t671\t2\t3\tVZAAAA\tXOAAAA\tVVVVxx\n822\t388\t0\t2\t2\t2\t2\t22\t22\t322\t822\t4\t5\tQFAAAA\tYOAAAA\tAAAAxx\n573\t389\t1\t1\t3\t13\t3\t73\t173\t73\t573\t6\t7\tBWAAAA\tZOAAAA\tHHHHxx\n723\t390\t1\t3\t3\t3\t3\t23\t123\t223\t723\t6\t7\tVBAAAA\tAPAAAA\tOOOOxx\n195\t391\t1\t3\t5\t15\t5\t95\t195\t195\t195\t10\t11\tNHAAAA\tBPAAAA\tVVVVxx\n197\t392\t1\t1\t7\t17\t7\t97\t197\t197\t197\t14\t15\tPHAAAA\tCPAAAA\tAAAAxx\n755\t393\t1\t3\t5\t15\t5\t55\t155\t255\t755\t10\t11\tBDAAAA\tDPAAAA\tHHHHxx\n42\t394\t0\t2\t2\t2\t2\t42\t42\t42\t42\t4\t5\tQBAAAA\tEPAAAA\tOOOOxx\n897\t395\t1\t1\t7\t17\t7\t97\t97\t397\t897\t14\t15\tNIAAAA\tFPAAAA\tVVVVxx\n309\t396\t1\t1\t9\t9\t9\t9\t109\t309\t309\t18\t19\tXLAAAA\tGPAAAA\tAAAAxx\n724\t397\t0\t0\t4\t4\t4\t24\t124\t224\t724\t8\t9\tWBAAAA\tHPAAAA\tHHHHxx\n474\t398\t0\t2\t4\t14\t4\t74\t74\t474\t474\t8\t9\tGSAAAA\tIPAAAA\tOOOOxx\n345\t399\t1\t1\t5\t5\t5\t45\t145\t345\t345\t10\t11\tHNAAAA\tJPAAAA\tVVVVxx\n678\t400\t0\t2\t8\t18\t8\t78\t78\t178\t678\t16\t17\tCAAAAA\tKPAAAA\tAAAAxx\n757\t401\t1\t1\t7\t17\t7\t57\t157\t257\t757\t14\t15\tDDAAAA\tLPAAAA\tHHHHxx\n600\t402\t0\t0\t0\t0\t0\t0\t0\t100\t600\t0\t1\tCXAAAA\tMPAAAA\tOOOOxx\n184\t403\t0\t0\t4\t4\t4\t84\t184\t184\t184\t8\t9\tCHAAAA\tNPAAAA\tVVVVxx\n155\t404\t1\t3\t5\t15\t5\t55\t155\t155\t155\t10\t11\tZFAAAA\tOPAAAA\tAAAAxx\n136\t405\t0\t0\t6\t16\t6\t36\t136\t136\t136\t12\t13\tGFAAAA\tPPAAAA\tHHHHxx\n889\t406\t1\t1\t9\t9\t9\t89\t89\t389\t889\t18\t19\tFIAAAA\tQPAAAA\tOOOOxx\n95\t407\t1\t3\t5\t15\t5\t95\t95\t95\t95\t10\t11\tRDAAAA\tRPAAAA\tVVVVxx\n549\t408\t1\t1\t9\t9\t9\t49\t149\t49\t549\t18\t19\tDVAAAA\tSPAAAA\tAAAAxx\n81\t409\t1\t1\t1\t1\t1\t81\t81\t81\t81\t2\t3\tDDAAAA\tTPAAAA\tHHHHxx\n679\t410\t1\t3\t9\t19\t9\t79\t79\t179\t679\t18\t19\tDAAAAA\tUPAAAA\tOOOOxx\n27\t411\t1\t3\t7\t7\t7\t27\t27\t27\t27\t14\t15\tBBAAAA\tVPAAAA\tVVVVxx\n748\t412\t0\t0\t8\t8\t8\t48\t148\t248\t748\t16\t17\tUCAAAA\tWPAAAA\tAAAAxx\n107\t413\t1\t3\t7\t7\t7\t7\t107\t107\t107\t14\t15\tDEAAAA\tXPAAAA\tHHHHxx\n870\t414\t0\t2\t0\t10\t0\t70\t70\t370\t870\t0\t1\tMHAAAA\tYPAAAA\tOOOOxx\n848\t415\t0\t0\t8\t8\t8\t48\t48\t348\t848\t16\t17\tQGAAAA\tZPAAAA\tVVVVxx\n764\t416\t0\t0\t4\t4\t4\t64\t164\t264\t764\t8\t9\tKDAAAA\tAQAAAA\tAAAAxx\n535\t417\t1\t3\t5\t15\t5\t35\t135\t35\t535\t10\t11\tPUAAAA\tBQAAAA\tHHHHxx\n211\t418\t1\t3\t1\t11\t1\t11\t11\t211\t211\t2\t3\tDIAAAA\tCQAAAA\tOOOOxx\n625\t419\t1\t1\t5\t5\t5\t25\t25\t125\t625\t10\t11\tBYAAAA\tDQAAAA\tVVVVxx\n96\t420\t0\t0\t6\t16\t6\t96\t96\t96\t96\t12\t13\tSDAAAA\tEQAAAA\tAAAAxx\n828\t421\t0\t0\t8\t8\t8\t28\t28\t328\t828\t16\t17\tWFAAAA\tFQAAAA\tHHHHxx\n229\t422\t1\t1\t9\t9\t9\t29\t29\t229\t229\t18\t19\tVIAAAA\tGQAAAA\tOOOOxx\n602\t423\t0\t2\t2\t2\t2\t2\t2\t102\t602\t4\t5\tEXAAAA\tHQAAAA\tVVVVxx\n742\t424\t0\t2\t2\t2\t2\t42\t142\t242\t742\t4\t5\tOCAAAA\tIQAAAA\tAAAAxx\n451\t425\t1\t3\t1\t11\t1\t51\t51\t451\t451\t2\t3\tJRAAAA\tJQAAAA\tHHHHxx\n991\t426\t1\t3\t1\t11\t1\t91\t191\t491\t991\t2\t3\tDMAAAA\tKQAAAA\tOOOOxx\n301\t427\t1\t1\t1\t1\t1\t1\t101\t301\t301\t2\t3\tPLAAAA\tLQAAAA\tVVVVxx\n510\t428\t0\t2\t0\t10\t0\t10\t110\t10\t510\t0\t1\tQTAAAA\tMQAAAA\tAAAAxx\n299\t429\t1\t3\t9\t19\t9\t99\t99\t299\t299\t18\t19\tNLAAAA\tNQAAAA\tHHHHxx\n961\t430\t1\t1\t1\t1\t1\t61\t161\t461\t961\t2\t3\tZKAAAA\tOQAAAA\tOOOOxx\n3\t431\t1\t3\t3\t3\t3\t3\t3\t3\t3\t6\t7\tDAAAAA\tPQAAAA\tVVVVxx\n106\t432\t0\t2\t6\t6\t6\t6\t106\t106\t106\t12\t13\tCEAAAA\tQQAAAA\tAAAAxx\n591\t433\t1\t3\t1\t11\t1\t91\t191\t91\t591\t2\t3\tTWAAAA\tRQAAAA\tHHHHxx\n700\t434\t0\t0\t0\t0\t0\t0\t100\t200\t700\t0\t1\tYAAAAA\tSQAAAA\tOOOOxx\n841\t435\t1\t1\t1\t1\t1\t41\t41\t341\t841\t2\t3\tJGAAAA\tTQAAAA\tVVVVxx\n829\t436\t1\t1\t9\t9\t9\t29\t29\t329\t829\t18\t19\tXFAAAA\tUQAAAA\tAAAAxx\n508\t437\t0\t0\t8\t8\t8\t8\t108\t8\t508\t16\t17\tOTAAAA\tVQAAAA\tHHHHxx\n750\t438\t0\t2\t0\t10\t0\t50\t150\t250\t750\t0\t1\tWCAAAA\tWQAAAA\tOOOOxx\n665\t439\t1\t1\t5\t5\t5\t65\t65\t165\t665\t10\t11\tPZAAAA\tXQAAAA\tVVVVxx\n157\t440\t1\t1\t7\t17\t7\t57\t157\t157\t157\t14\t15\tBGAAAA\tYQAAAA\tAAAAxx\n694\t441\t0\t2\t4\t14\t4\t94\t94\t194\t694\t8\t9\tSAAAAA\tZQAAAA\tHHHHxx\n176\t442\t0\t0\t6\t16\t6\t76\t176\t176\t176\t12\t13\tUGAAAA\tARAAAA\tOOOOxx\n950\t443\t0\t2\t0\t10\t0\t50\t150\t450\t950\t0\t1\tOKAAAA\tBRAAAA\tVVVVxx\n970\t444\t0\t2\t0\t10\t0\t70\t170\t470\t970\t0\t1\tILAAAA\tCRAAAA\tAAAAxx\n496\t445\t0\t0\t6\t16\t6\t96\t96\t496\t496\t12\t13\tCTAAAA\tDRAAAA\tHHHHxx\n429\t446\t1\t1\t9\t9\t9\t29\t29\t429\t429\t18\t19\tNQAAAA\tERAAAA\tOOOOxx\n907\t447\t1\t3\t7\t7\t7\t7\t107\t407\t907\t14\t15\tXIAAAA\tFRAAAA\tVVVVxx\n72\t448\t0\t0\t2\t12\t2\t72\t72\t72\t72\t4\t5\tUCAAAA\tGRAAAA\tAAAAxx\n186\t449\t0\t2\t6\t6\t6\t86\t186\t186\t186\t12\t13\tEHAAAA\tHRAAAA\tHHHHxx\n713\t450\t1\t1\t3\t13\t3\t13\t113\t213\t713\t6\t7\tLBAAAA\tIRAAAA\tOOOOxx\n432\t451\t0\t0\t2\t12\t2\t32\t32\t432\t432\t4\t5\tQQAAAA\tJRAAAA\tVVVVxx\n735\t452\t1\t3\t5\t15\t5\t35\t135\t235\t735\t10\t11\tHCAAAA\tKRAAAA\tAAAAxx\n516\t453\t0\t0\t6\t16\t6\t16\t116\t16\t516\t12\t13\tWTAAAA\tLRAAAA\tHHHHxx\n964\t454\t0\t0\t4\t4\t4\t64\t164\t464\t964\t8\t9\tCLAAAA\tMRAAAA\tOOOOxx\n840\t455\t0\t0\t0\t0\t0\t40\t40\t340\t840\t0\t1\tIGAAAA\tNRAAAA\tVVVVxx\n550\t456\t0\t2\t0\t10\t0\t50\t150\t50\t550\t0\t1\tEVAAAA\tORAAAA\tAAAAxx\n360\t457\t0\t0\t0\t0\t0\t60\t160\t360\t360\t0\t1\tWNAAAA\tPRAAAA\tHHHHxx\n827\t458\t1\t3\t7\t7\t7\t27\t27\t327\t827\t14\t15\tVFAAAA\tQRAAAA\tOOOOxx\n959\t459\t1\t3\t9\t19\t9\t59\t159\t459\t959\t18\t19\tXKAAAA\tRRAAAA\tVVVVxx\n454\t460\t0\t2\t4\t14\t4\t54\t54\t454\t454\t8\t9\tMRAAAA\tSRAAAA\tAAAAxx\n819\t461\t1\t3\t9\t19\t9\t19\t19\t319\t819\t18\t19\tNFAAAA\tTRAAAA\tHHHHxx\n745\t462\t1\t1\t5\t5\t5\t45\t145\t245\t745\t10\t11\tRCAAAA\tURAAAA\tOOOOxx\n279\t463\t1\t3\t9\t19\t9\t79\t79\t279\t279\t18\t19\tTKAAAA\tVRAAAA\tVVVVxx\n426\t464\t0\t2\t6\t6\t6\t26\t26\t426\t426\t12\t13\tKQAAAA\tWRAAAA\tAAAAxx\n70\t465\t0\t2\t0\t10\t0\t70\t70\t70\t70\t0\t1\tSCAAAA\tXRAAAA\tHHHHxx\n637\t466\t1\t1\t7\t17\t7\t37\t37\t137\t637\t14\t15\tNYAAAA\tYRAAAA\tOOOOxx\n417\t467\t1\t1\t7\t17\t7\t17\t17\t417\t417\t14\t15\tBQAAAA\tZRAAAA\tVVVVxx\n586\t468\t0\t2\t6\t6\t6\t86\t186\t86\t586\t12\t13\tOWAAAA\tASAAAA\tAAAAxx\n314\t469\t0\t2\t4\t14\t4\t14\t114\t314\t314\t8\t9\tCMAAAA\tBSAAAA\tHHHHxx\n101\t470\t1\t1\t1\t1\t1\t1\t101\t101\t101\t2\t3\tXDAAAA\tCSAAAA\tOOOOxx\n205\t471\t1\t1\t5\t5\t5\t5\t5\t205\t205\t10\t11\tXHAAAA\tDSAAAA\tVVVVxx\n969\t472\t1\t1\t9\t9\t9\t69\t169\t469\t969\t18\t19\tHLAAAA\tESAAAA\tAAAAxx\n217\t473\t1\t1\t7\t17\t7\t17\t17\t217\t217\t14\t15\tJIAAAA\tFSAAAA\tHHHHxx\n281\t474\t1\t1\t1\t1\t1\t81\t81\t281\t281\t2\t3\tVKAAAA\tGSAAAA\tOOOOxx\n984\t475\t0\t0\t4\t4\t4\t84\t184\t484\t984\t8\t9\tWLAAAA\tHSAAAA\tVVVVxx\n366\t476\t0\t2\t6\t6\t6\t66\t166\t366\t366\t12\t13\tCOAAAA\tISAAAA\tAAAAxx\n483\t477\t1\t3\t3\t3\t3\t83\t83\t483\t483\t6\t7\tPSAAAA\tJSAAAA\tHHHHxx\n838\t478\t0\t2\t8\t18\t8\t38\t38\t338\t838\t16\t17\tGGAAAA\tKSAAAA\tOOOOxx\n64\t479\t0\t0\t4\t4\t4\t64\t64\t64\t64\t8\t9\tMCAAAA\tLSAAAA\tVVVVxx\n981\t480\t1\t1\t1\t1\t1\t81\t181\t481\t981\t2\t3\tTLAAAA\tMSAAAA\tAAAAxx\n538\t481\t0\t2\t8\t18\t8\t38\t138\t38\t538\t16\t17\tSUAAAA\tNSAAAA\tHHHHxx\n39\t482\t1\t3\t9\t19\t9\t39\t39\t39\t39\t18\t19\tNBAAAA\tOSAAAA\tOOOOxx\n60\t483\t0\t0\t0\t0\t0\t60\t60\t60\t60\t0\t1\tICAAAA\tPSAAAA\tVVVVxx\n874\t484\t0\t2\t4\t14\t4\t74\t74\t374\t874\t8\t9\tQHAAAA\tQSAAAA\tAAAAxx\n955\t485\t1\t3\t5\t15\t5\t55\t155\t455\t955\t10\t11\tTKAAAA\tRSAAAA\tHHHHxx\n347\t486\t1\t3\t7\t7\t7\t47\t147\t347\t347\t14\t15\tJNAAAA\tSSAAAA\tOOOOxx\n227\t487\t1\t3\t7\t7\t7\t27\t27\t227\t227\t14\t15\tTIAAAA\tTSAAAA\tVVVVxx\n44\t488\t0\t0\t4\t4\t4\t44\t44\t44\t44\t8\t9\tSBAAAA\tUSAAAA\tAAAAxx\n446\t489\t0\t2\t6\t6\t6\t46\t46\t446\t446\t12\t13\tERAAAA\tVSAAAA\tHHHHxx\n605\t490\t1\t1\t5\t5\t5\t5\t5\t105\t605\t10\t11\tHXAAAA\tWSAAAA\tOOOOxx\n570\t491\t0\t2\t0\t10\t0\t70\t170\t70\t570\t0\t1\tYVAAAA\tXSAAAA\tVVVVxx\n895\t492\t1\t3\t5\t15\t5\t95\t95\t395\t895\t10\t11\tLIAAAA\tYSAAAA\tAAAAxx\n760\t493\t0\t0\t0\t0\t0\t60\t160\t260\t760\t0\t1\tGDAAAA\tZSAAAA\tHHHHxx\n428\t494\t0\t0\t8\t8\t8\t28\t28\t428\t428\t16\t17\tMQAAAA\tATAAAA\tOOOOxx\n628\t495\t0\t0\t8\t8\t8\t28\t28\t128\t628\t16\t17\tEYAAAA\tBTAAAA\tVVVVxx\n933\t496\t1\t1\t3\t13\t3\t33\t133\t433\t933\t6\t7\tXJAAAA\tCTAAAA\tAAAAxx\n263\t497\t1\t3\t3\t3\t3\t63\t63\t263\t263\t6\t7\tDKAAAA\tDTAAAA\tHHHHxx\n729\t498\t1\t1\t9\t9\t9\t29\t129\t229\t729\t18\t19\tBCAAAA\tETAAAA\tOOOOxx\n860\t499\t0\t0\t0\t0\t0\t60\t60\t360\t860\t0\t1\tCHAAAA\tFTAAAA\tVVVVxx\n76\t500\t0\t0\t6\t16\t6\t76\t76\t76\t76\t12\t13\tYCAAAA\tGTAAAA\tAAAAxx\n293\t501\t1\t1\t3\t13\t3\t93\t93\t293\t293\t6\t7\tHLAAAA\tHTAAAA\tHHHHxx\n296\t502\t0\t0\t6\t16\t6\t96\t96\t296\t296\t12\t13\tKLAAAA\tITAAAA\tOOOOxx\n124\t503\t0\t0\t4\t4\t4\t24\t124\t124\t124\t8\t9\tUEAAAA\tJTAAAA\tVVVVxx\n568\t504\t0\t0\t8\t8\t8\t68\t168\t68\t568\t16\t17\tWVAAAA\tKTAAAA\tAAAAxx\n337\t505\t1\t1\t7\t17\t7\t37\t137\t337\t337\t14\t15\tZMAAAA\tLTAAAA\tHHHHxx\n464\t506\t0\t0\t4\t4\t4\t64\t64\t464\t464\t8\t9\tWRAAAA\tMTAAAA\tOOOOxx\n582\t507\t0\t2\t2\t2\t2\t82\t182\t82\t582\t4\t5\tKWAAAA\tNTAAAA\tVVVVxx\n207\t508\t1\t3\t7\t7\t7\t7\t7\t207\t207\t14\t15\tZHAAAA\tOTAAAA\tAAAAxx\n518\t509\t0\t2\t8\t18\t8\t18\t118\t18\t518\t16\t17\tYTAAAA\tPTAAAA\tHHHHxx\n513\t510\t1\t1\t3\t13\t3\t13\t113\t13\t513\t6\t7\tTTAAAA\tQTAAAA\tOOOOxx\n127\t511\t1\t3\t7\t7\t7\t27\t127\t127\t127\t14\t15\tXEAAAA\tRTAAAA\tVVVVxx\n396\t512\t0\t0\t6\t16\t6\t96\t196\t396\t396\t12\t13\tGPAAAA\tSTAAAA\tAAAAxx\n781\t513\t1\t1\t1\t1\t1\t81\t181\t281\t781\t2\t3\tBEAAAA\tTTAAAA\tHHHHxx\n233\t514\t1\t1\t3\t13\t3\t33\t33\t233\t233\t6\t7\tZIAAAA\tUTAAAA\tOOOOxx\n709\t515\t1\t1\t9\t9\t9\t9\t109\t209\t709\t18\t19\tHBAAAA\tVTAAAA\tVVVVxx\n325\t516\t1\t1\t5\t5\t5\t25\t125\t325\t325\t10\t11\tNMAAAA\tWTAAAA\tAAAAxx\n143\t517\t1\t3\t3\t3\t3\t43\t143\t143\t143\t6\t7\tNFAAAA\tXTAAAA\tHHHHxx\n824\t518\t0\t0\t4\t4\t4\t24\t24\t324\t824\t8\t9\tSFAAAA\tYTAAAA\tOOOOxx\n122\t519\t0\t2\t2\t2\t2\t22\t122\t122\t122\t4\t5\tSEAAAA\tZTAAAA\tVVVVxx\n10\t520\t0\t2\t0\t10\t0\t10\t10\t10\t10\t0\t1\tKAAAAA\tAUAAAA\tAAAAxx\n41\t521\t1\t1\t1\t1\t1\t41\t41\t41\t41\t2\t3\tPBAAAA\tBUAAAA\tHHHHxx\n618\t522\t0\t2\t8\t18\t8\t18\t18\t118\t618\t16\t17\tUXAAAA\tCUAAAA\tOOOOxx\n161\t523\t1\t1\t1\t1\t1\t61\t161\t161\t161\t2\t3\tFGAAAA\tDUAAAA\tVVVVxx\n801\t524\t1\t1\t1\t1\t1\t1\t1\t301\t801\t2\t3\tVEAAAA\tEUAAAA\tAAAAxx\n768\t525\t0\t0\t8\t8\t8\t68\t168\t268\t768\t16\t17\tODAAAA\tFUAAAA\tHHHHxx\n642\t526\t0\t2\t2\t2\t2\t42\t42\t142\t642\t4\t5\tSYAAAA\tGUAAAA\tOOOOxx\n803\t527\t1\t3\t3\t3\t3\t3\t3\t303\t803\t6\t7\tXEAAAA\tHUAAAA\tVVVVxx\n317\t528\t1\t1\t7\t17\t7\t17\t117\t317\t317\t14\t15\tFMAAAA\tIUAAAA\tAAAAxx\n938\t529\t0\t2\t8\t18\t8\t38\t138\t438\t938\t16\t17\tCKAAAA\tJUAAAA\tHHHHxx\n649\t530\t1\t1\t9\t9\t9\t49\t49\t149\t649\t18\t19\tZYAAAA\tKUAAAA\tOOOOxx\n738\t531\t0\t2\t8\t18\t8\t38\t138\t238\t738\t16\t17\tKCAAAA\tLUAAAA\tVVVVxx\n344\t532\t0\t0\t4\t4\t4\t44\t144\t344\t344\t8\t9\tGNAAAA\tMUAAAA\tAAAAxx\n399\t533\t1\t3\t9\t19\t9\t99\t199\t399\t399\t18\t19\tJPAAAA\tNUAAAA\tHHHHxx\n609\t534\t1\t1\t9\t9\t9\t9\t9\t109\t609\t18\t19\tLXAAAA\tOUAAAA\tOOOOxx\n677\t535\t1\t1\t7\t17\t7\t77\t77\t177\t677\t14\t15\tBAAAAA\tPUAAAA\tVVVVxx\n478\t536\t0\t2\t8\t18\t8\t78\t78\t478\t478\t16\t17\tKSAAAA\tQUAAAA\tAAAAxx\n452\t537\t0\t0\t2\t12\t2\t52\t52\t452\t452\t4\t5\tKRAAAA\tRUAAAA\tHHHHxx\n261\t538\t1\t1\t1\t1\t1\t61\t61\t261\t261\t2\t3\tBKAAAA\tSUAAAA\tOOOOxx\n449\t539\t1\t1\t9\t9\t9\t49\t49\t449\t449\t18\t19\tHRAAAA\tTUAAAA\tVVVVxx\n433\t540\t1\t1\t3\t13\t3\t33\t33\t433\t433\t6\t7\tRQAAAA\tUUAAAA\tAAAAxx\n5\t541\t1\t1\t5\t5\t5\t5\t5\t5\t5\t10\t11\tFAAAAA\tVUAAAA\tHHHHxx\n664\t542\t0\t0\t4\t4\t4\t64\t64\t164\t664\t8\t9\tOZAAAA\tWUAAAA\tOOOOxx\n887\t543\t1\t3\t7\t7\t7\t87\t87\t387\t887\t14\t15\tDIAAAA\tXUAAAA\tVVVVxx\n546\t544\t0\t2\t6\t6\t6\t46\t146\t46\t546\t12\t13\tAVAAAA\tYUAAAA\tAAAAxx\n253\t545\t1\t1\t3\t13\t3\t53\t53\t253\t253\t6\t7\tTJAAAA\tZUAAAA\tHHHHxx\n235\t546\t1\t3\t5\t15\t5\t35\t35\t235\t235\t10\t11\tBJAAAA\tAVAAAA\tOOOOxx\n258\t547\t0\t2\t8\t18\t8\t58\t58\t258\t258\t16\t17\tYJAAAA\tBVAAAA\tVVVVxx\n621\t548\t1\t1\t1\t1\t1\t21\t21\t121\t621\t2\t3\tXXAAAA\tCVAAAA\tAAAAxx\n998\t549\t0\t2\t8\t18\t8\t98\t198\t498\t998\t16\t17\tKMAAAA\tDVAAAA\tHHHHxx\n236\t550\t0\t0\t6\t16\t6\t36\t36\t236\t236\t12\t13\tCJAAAA\tEVAAAA\tOOOOxx\n537\t551\t1\t1\t7\t17\t7\t37\t137\t37\t537\t14\t15\tRUAAAA\tFVAAAA\tVVVVxx\n769\t552\t1\t1\t9\t9\t9\t69\t169\t269\t769\t18\t19\tPDAAAA\tGVAAAA\tAAAAxx\n921\t553\t1\t1\t1\t1\t1\t21\t121\t421\t921\t2\t3\tLJAAAA\tHVAAAA\tHHHHxx\n951\t554\t1\t3\t1\t11\t1\t51\t151\t451\t951\t2\t3\tPKAAAA\tIVAAAA\tOOOOxx\n240\t555\t0\t0\t0\t0\t0\t40\t40\t240\t240\t0\t1\tGJAAAA\tJVAAAA\tVVVVxx\n644\t556\t0\t0\t4\t4\t4\t44\t44\t144\t644\t8\t9\tUYAAAA\tKVAAAA\tAAAAxx\n352\t557\t0\t0\t2\t12\t2\t52\t152\t352\t352\t4\t5\tONAAAA\tLVAAAA\tHHHHxx\n613\t558\t1\t1\t3\t13\t3\t13\t13\t113\t613\t6\t7\tPXAAAA\tMVAAAA\tOOOOxx\n784\t559\t0\t0\t4\t4\t4\t84\t184\t284\t784\t8\t9\tEEAAAA\tNVAAAA\tVVVVxx\n61\t560\t1\t1\t1\t1\t1\t61\t61\t61\t61\t2\t3\tJCAAAA\tOVAAAA\tAAAAxx\n144\t561\t0\t0\t4\t4\t4\t44\t144\t144\t144\t8\t9\tOFAAAA\tPVAAAA\tHHHHxx\n94\t562\t0\t2\t4\t14\t4\t94\t94\t94\t94\t8\t9\tQDAAAA\tQVAAAA\tOOOOxx\n270\t563\t0\t2\t0\t10\t0\t70\t70\t270\t270\t0\t1\tKKAAAA\tRVAAAA\tVVVVxx\n942\t564\t0\t2\t2\t2\t2\t42\t142\t442\t942\t4\t5\tGKAAAA\tSVAAAA\tAAAAxx\n756\t565\t0\t0\t6\t16\t6\t56\t156\t256\t756\t12\t13\tCDAAAA\tTVAAAA\tHHHHxx\n321\t566\t1\t1\t1\t1\t1\t21\t121\t321\t321\t2\t3\tJMAAAA\tUVAAAA\tOOOOxx\n36\t567\t0\t0\t6\t16\t6\t36\t36\t36\t36\t12\t13\tKBAAAA\tVVAAAA\tVVVVxx\n232\t568\t0\t0\t2\t12\t2\t32\t32\t232\t232\t4\t5\tYIAAAA\tWVAAAA\tAAAAxx\n430\t569\t0\t2\t0\t10\t0\t30\t30\t430\t430\t0\t1\tOQAAAA\tXVAAAA\tHHHHxx\n177\t570\t1\t1\t7\t17\t7\t77\t177\t177\t177\t14\t15\tVGAAAA\tYVAAAA\tOOOOxx\n220\t571\t0\t0\t0\t0\t0\t20\t20\t220\t220\t0\t1\tMIAAAA\tZVAAAA\tVVVVxx\n109\t572\t1\t1\t9\t9\t9\t9\t109\t109\t109\t18\t19\tFEAAAA\tAWAAAA\tAAAAxx\n419\t573\t1\t3\t9\t19\t9\t19\t19\t419\t419\t18\t19\tDQAAAA\tBWAAAA\tHHHHxx\n135\t574\t1\t3\t5\t15\t5\t35\t135\t135\t135\t10\t11\tFFAAAA\tCWAAAA\tOOOOxx\n610\t575\t0\t2\t0\t10\t0\t10\t10\t110\t610\t0\t1\tMXAAAA\tDWAAAA\tVVVVxx\n956\t576\t0\t0\t6\t16\t6\t56\t156\t456\t956\t12\t13\tUKAAAA\tEWAAAA\tAAAAxx\n626\t577\t0\t2\t6\t6\t6\t26\t26\t126\t626\t12\t13\tCYAAAA\tFWAAAA\tHHHHxx\n375\t578\t1\t3\t5\t15\t5\t75\t175\t375\t375\t10\t11\tLOAAAA\tGWAAAA\tOOOOxx\n976\t579\t0\t0\t6\t16\t6\t76\t176\t476\t976\t12\t13\tOLAAAA\tHWAAAA\tVVVVxx\n152\t580\t0\t0\t2\t12\t2\t52\t152\t152\t152\t4\t5\tWFAAAA\tIWAAAA\tAAAAxx\n308\t581\t0\t0\t8\t8\t8\t8\t108\t308\t308\t16\t17\tWLAAAA\tJWAAAA\tHHHHxx\n445\t582\t1\t1\t5\t5\t5\t45\t45\t445\t445\t10\t11\tDRAAAA\tKWAAAA\tOOOOxx\n326\t583\t0\t2\t6\t6\t6\t26\t126\t326\t326\t12\t13\tOMAAAA\tLWAAAA\tVVVVxx\n422\t584\t0\t2\t2\t2\t2\t22\t22\t422\t422\t4\t5\tGQAAAA\tMWAAAA\tAAAAxx\n972\t585\t0\t0\t2\t12\t2\t72\t172\t472\t972\t4\t5\tKLAAAA\tNWAAAA\tHHHHxx\n45\t586\t1\t1\t5\t5\t5\t45\t45\t45\t45\t10\t11\tTBAAAA\tOWAAAA\tOOOOxx\n725\t587\t1\t1\t5\t5\t5\t25\t125\t225\t725\t10\t11\tXBAAAA\tPWAAAA\tVVVVxx\n753\t588\t1\t1\t3\t13\t3\t53\t153\t253\t753\t6\t7\tZCAAAA\tQWAAAA\tAAAAxx\n493\t589\t1\t1\t3\t13\t3\t93\t93\t493\t493\t6\t7\tZSAAAA\tRWAAAA\tHHHHxx\n601\t590\t1\t1\t1\t1\t1\t1\t1\t101\t601\t2\t3\tDXAAAA\tSWAAAA\tOOOOxx\n463\t591\t1\t3\t3\t3\t3\t63\t63\t463\t463\t6\t7\tVRAAAA\tTWAAAA\tVVVVxx\n303\t592\t1\t3\t3\t3\t3\t3\t103\t303\t303\t6\t7\tRLAAAA\tUWAAAA\tAAAAxx\n59\t593\t1\t3\t9\t19\t9\t59\t59\t59\t59\t18\t19\tHCAAAA\tVWAAAA\tHHHHxx\n595\t594\t1\t3\t5\t15\t5\t95\t195\t95\t595\t10\t11\tXWAAAA\tWWAAAA\tOOOOxx\n807\t595\t1\t3\t7\t7\t7\t7\t7\t307\t807\t14\t15\tBFAAAA\tXWAAAA\tVVVVxx\n424\t596\t0\t0\t4\t4\t4\t24\t24\t424\t424\t8\t9\tIQAAAA\tYWAAAA\tAAAAxx\n521\t597\t1\t1\t1\t1\t1\t21\t121\t21\t521\t2\t3\tBUAAAA\tZWAAAA\tHHHHxx\n341\t598\t1\t1\t1\t1\t1\t41\t141\t341\t341\t2\t3\tDNAAAA\tAXAAAA\tOOOOxx\n571\t599\t1\t3\t1\t11\t1\t71\t171\t71\t571\t2\t3\tZVAAAA\tBXAAAA\tVVVVxx\n165\t600\t1\t1\t5\t5\t5\t65\t165\t165\t165\t10\t11\tJGAAAA\tCXAAAA\tAAAAxx\n908\t601\t0\t0\t8\t8\t8\t8\t108\t408\t908\t16\t17\tYIAAAA\tDXAAAA\tHHHHxx\n351\t602\t1\t3\t1\t11\t1\t51\t151\t351\t351\t2\t3\tNNAAAA\tEXAAAA\tOOOOxx\n334\t603\t0\t2\t4\t14\t4\t34\t134\t334\t334\t8\t9\tWMAAAA\tFXAAAA\tVVVVxx\n636\t604\t0\t0\t6\t16\t6\t36\t36\t136\t636\t12\t13\tMYAAAA\tGXAAAA\tAAAAxx\n138\t605\t0\t2\t8\t18\t8\t38\t138\t138\t138\t16\t17\tIFAAAA\tHXAAAA\tHHHHxx\n438\t606\t0\t2\t8\t18\t8\t38\t38\t438\t438\t16\t17\tWQAAAA\tIXAAAA\tOOOOxx\n391\t607\t1\t3\t1\t11\t1\t91\t191\t391\t391\t2\t3\tBPAAAA\tJXAAAA\tVVVVxx\n395\t608\t1\t3\t5\t15\t5\t95\t195\t395\t395\t10\t11\tFPAAAA\tKXAAAA\tAAAAxx\n502\t609\t0\t2\t2\t2\t2\t2\t102\t2\t502\t4\t5\tITAAAA\tLXAAAA\tHHHHxx\n85\t610\t1\t1\t5\t5\t5\t85\t85\t85\t85\t10\t11\tHDAAAA\tMXAAAA\tOOOOxx\n786\t611\t0\t2\t6\t6\t6\t86\t186\t286\t786\t12\t13\tGEAAAA\tNXAAAA\tVVVVxx\n619\t612\t1\t3\t9\t19\t9\t19\t19\t119\t619\t18\t19\tVXAAAA\tOXAAAA\tAAAAxx\n440\t613\t0\t0\t0\t0\t0\t40\t40\t440\t440\t0\t1\tYQAAAA\tPXAAAA\tHHHHxx\n949\t614\t1\t1\t9\t9\t9\t49\t149\t449\t949\t18\t19\tNKAAAA\tQXAAAA\tOOOOxx\n691\t615\t1\t3\t1\t11\t1\t91\t91\t191\t691\t2\t3\tPAAAAA\tRXAAAA\tVVVVxx\n348\t616\t0\t0\t8\t8\t8\t48\t148\t348\t348\t16\t17\tKNAAAA\tSXAAAA\tAAAAxx\n506\t617\t0\t2\t6\t6\t6\t6\t106\t6\t506\t12\t13\tMTAAAA\tTXAAAA\tHHHHxx\n192\t618\t0\t0\t2\t12\t2\t92\t192\t192\t192\t4\t5\tKHAAAA\tUXAAAA\tOOOOxx\n369\t619\t1\t1\t9\t9\t9\t69\t169\t369\t369\t18\t19\tFOAAAA\tVXAAAA\tVVVVxx\n311\t620\t1\t3\t1\t11\t1\t11\t111\t311\t311\t2\t3\tZLAAAA\tWXAAAA\tAAAAxx\n273\t621\t1\t1\t3\t13\t3\t73\t73\t273\t273\t6\t7\tNKAAAA\tXXAAAA\tHHHHxx\n770\t622\t0\t2\t0\t10\t0\t70\t170\t270\t770\t0\t1\tQDAAAA\tYXAAAA\tOOOOxx\n191\t623\t1\t3\t1\t11\t1\t91\t191\t191\t191\t2\t3\tJHAAAA\tZXAAAA\tVVVVxx\n90\t624\t0\t2\t0\t10\t0\t90\t90\t90\t90\t0\t1\tMDAAAA\tAYAAAA\tAAAAxx\n163\t625\t1\t3\t3\t3\t3\t63\t163\t163\t163\t6\t7\tHGAAAA\tBYAAAA\tHHHHxx\n350\t626\t0\t2\t0\t10\t0\t50\t150\t350\t350\t0\t1\tMNAAAA\tCYAAAA\tOOOOxx\n55\t627\t1\t3\t5\t15\t5\t55\t55\t55\t55\t10\t11\tDCAAAA\tDYAAAA\tVVVVxx\n488\t628\t0\t0\t8\t8\t8\t88\t88\t488\t488\t16\t17\tUSAAAA\tEYAAAA\tAAAAxx\n215\t629\t1\t3\t5\t15\t5\t15\t15\t215\t215\t10\t11\tHIAAAA\tFYAAAA\tHHHHxx\n732\t630\t0\t0\t2\t12\t2\t32\t132\t232\t732\t4\t5\tECAAAA\tGYAAAA\tOOOOxx\n688\t631\t0\t0\t8\t8\t8\t88\t88\t188\t688\t16\t17\tMAAAAA\tHYAAAA\tVVVVxx\n520\t632\t0\t0\t0\t0\t0\t20\t120\t20\t520\t0\t1\tAUAAAA\tIYAAAA\tAAAAxx\n62\t633\t0\t2\t2\t2\t2\t62\t62\t62\t62\t4\t5\tKCAAAA\tJYAAAA\tHHHHxx\n423\t634\t1\t3\t3\t3\t3\t23\t23\t423\t423\t6\t7\tHQAAAA\tKYAAAA\tOOOOxx\n242\t635\t0\t2\t2\t2\t2\t42\t42\t242\t242\t4\t5\tIJAAAA\tLYAAAA\tVVVVxx\n193\t636\t1\t1\t3\t13\t3\t93\t193\t193\t193\t6\t7\tLHAAAA\tMYAAAA\tAAAAxx\n648\t637\t0\t0\t8\t8\t8\t48\t48\t148\t648\t16\t17\tYYAAAA\tNYAAAA\tHHHHxx\n459\t638\t1\t3\t9\t19\t9\t59\t59\t459\t459\t18\t19\tRRAAAA\tOYAAAA\tOOOOxx\n196\t639\t0\t0\t6\t16\t6\t96\t196\t196\t196\t12\t13\tOHAAAA\tPYAAAA\tVVVVxx\n476\t640\t0\t0\t6\t16\t6\t76\t76\t476\t476\t12\t13\tISAAAA\tQYAAAA\tAAAAxx\n903\t641\t1\t3\t3\t3\t3\t3\t103\t403\t903\t6\t7\tTIAAAA\tRYAAAA\tHHHHxx\n974\t642\t0\t2\t4\t14\t4\t74\t174\t474\t974\t8\t9\tMLAAAA\tSYAAAA\tOOOOxx\n603\t643\t1\t3\t3\t3\t3\t3\t3\t103\t603\t6\t7\tFXAAAA\tTYAAAA\tVVVVxx\n12\t644\t0\t0\t2\t12\t2\t12\t12\t12\t12\t4\t5\tMAAAAA\tUYAAAA\tAAAAxx\n599\t645\t1\t3\t9\t19\t9\t99\t199\t99\t599\t18\t19\tBXAAAA\tVYAAAA\tHHHHxx\n914\t646\t0\t2\t4\t14\t4\t14\t114\t414\t914\t8\t9\tEJAAAA\tWYAAAA\tOOOOxx\n7\t647\t1\t3\t7\t7\t7\t7\t7\t7\t7\t14\t15\tHAAAAA\tXYAAAA\tVVVVxx\n213\t648\t1\t1\t3\t13\t3\t13\t13\t213\t213\t6\t7\tFIAAAA\tYYAAAA\tAAAAxx\n174\t649\t0\t2\t4\t14\t4\t74\t174\t174\t174\t8\t9\tSGAAAA\tZYAAAA\tHHHHxx\n392\t650\t0\t0\t2\t12\t2\t92\t192\t392\t392\t4\t5\tCPAAAA\tAZAAAA\tOOOOxx\n674\t651\t0\t2\t4\t14\t4\t74\t74\t174\t674\t8\t9\tYZAAAA\tBZAAAA\tVVVVxx\n650\t652\t0\t2\t0\t10\t0\t50\t50\t150\t650\t0\t1\tAZAAAA\tCZAAAA\tAAAAxx\n8\t653\t0\t0\t8\t8\t8\t8\t8\t8\t8\t16\t17\tIAAAAA\tDZAAAA\tHHHHxx\n492\t654\t0\t0\t2\t12\t2\t92\t92\t492\t492\t4\t5\tYSAAAA\tEZAAAA\tOOOOxx\n322\t655\t0\t2\t2\t2\t2\t22\t122\t322\t322\t4\t5\tKMAAAA\tFZAAAA\tVVVVxx\n315\t656\t1\t3\t5\t15\t5\t15\t115\t315\t315\t10\t11\tDMAAAA\tGZAAAA\tAAAAxx\n380\t657\t0\t0\t0\t0\t0\t80\t180\t380\t380\t0\t1\tQOAAAA\tHZAAAA\tHHHHxx\n353\t658\t1\t1\t3\t13\t3\t53\t153\t353\t353\t6\t7\tPNAAAA\tIZAAAA\tOOOOxx\n892\t659\t0\t0\t2\t12\t2\t92\t92\t392\t892\t4\t5\tIIAAAA\tJZAAAA\tVVVVxx\n932\t660\t0\t0\t2\t12\t2\t32\t132\t432\t932\t4\t5\tWJAAAA\tKZAAAA\tAAAAxx\n993\t661\t1\t1\t3\t13\t3\t93\t193\t493\t993\t6\t7\tFMAAAA\tLZAAAA\tHHHHxx\n859\t662\t1\t3\t9\t19\t9\t59\t59\t359\t859\t18\t19\tBHAAAA\tMZAAAA\tOOOOxx\n806\t663\t0\t2\t6\t6\t6\t6\t6\t306\t806\t12\t13\tAFAAAA\tNZAAAA\tVVVVxx\n145\t664\t1\t1\t5\t5\t5\t45\t145\t145\t145\t10\t11\tPFAAAA\tOZAAAA\tAAAAxx\n373\t665\t1\t1\t3\t13\t3\t73\t173\t373\t373\t6\t7\tJOAAAA\tPZAAAA\tHHHHxx\n418\t666\t0\t2\t8\t18\t8\t18\t18\t418\t418\t16\t17\tCQAAAA\tQZAAAA\tOOOOxx\n865\t667\t1\t1\t5\t5\t5\t65\t65\t365\t865\t10\t11\tHHAAAA\tRZAAAA\tVVVVxx\n462\t668\t0\t2\t2\t2\t2\t62\t62\t462\t462\t4\t5\tURAAAA\tSZAAAA\tAAAAxx\n24\t669\t0\t0\t4\t4\t4\t24\t24\t24\t24\t8\t9\tYAAAAA\tTZAAAA\tHHHHxx\n920\t670\t0\t0\t0\t0\t0\t20\t120\t420\t920\t0\t1\tKJAAAA\tUZAAAA\tOOOOxx\n672\t671\t0\t0\t2\t12\t2\t72\t72\t172\t672\t4\t5\tWZAAAA\tVZAAAA\tVVVVxx\n92\t672\t0\t0\t2\t12\t2\t92\t92\t92\t92\t4\t5\tODAAAA\tWZAAAA\tAAAAxx\n721\t673\t1\t1\t1\t1\t1\t21\t121\t221\t721\t2\t3\tTBAAAA\tXZAAAA\tHHHHxx\n646\t674\t0\t2\t6\t6\t6\t46\t46\t146\t646\t12\t13\tWYAAAA\tYZAAAA\tOOOOxx\n910\t675\t0\t2\t0\t10\t0\t10\t110\t410\t910\t0\t1\tAJAAAA\tZZAAAA\tVVVVxx\n909\t676\t1\t1\t9\t9\t9\t9\t109\t409\t909\t18\t19\tZIAAAA\tAABAAA\tAAAAxx\n630\t677\t0\t2\t0\t10\t0\t30\t30\t130\t630\t0\t1\tGYAAAA\tBABAAA\tHHHHxx\n482\t678\t0\t2\t2\t2\t2\t82\t82\t482\t482\t4\t5\tOSAAAA\tCABAAA\tOOOOxx\n559\t679\t1\t3\t9\t19\t9\t59\t159\t59\t559\t18\t19\tNVAAAA\tDABAAA\tVVVVxx\n853\t680\t1\t1\t3\t13\t3\t53\t53\t353\t853\t6\t7\tVGAAAA\tEABAAA\tAAAAxx\n141\t681\t1\t1\t1\t1\t1\t41\t141\t141\t141\t2\t3\tLFAAAA\tFABAAA\tHHHHxx\n266\t682\t0\t2\t6\t6\t6\t66\t66\t266\t266\t12\t13\tGKAAAA\tGABAAA\tOOOOxx\n835\t683\t1\t3\t5\t15\t5\t35\t35\t335\t835\t10\t11\tDGAAAA\tHABAAA\tVVVVxx\n164\t684\t0\t0\t4\t4\t4\t64\t164\t164\t164\t8\t9\tIGAAAA\tIABAAA\tAAAAxx\n629\t685\t1\t1\t9\t9\t9\t29\t29\t129\t629\t18\t19\tFYAAAA\tJABAAA\tHHHHxx\n203\t686\t1\t3\t3\t3\t3\t3\t3\t203\t203\t6\t7\tVHAAAA\tKABAAA\tOOOOxx\n411\t687\t1\t3\t1\t11\t1\t11\t11\t411\t411\t2\t3\tVPAAAA\tLABAAA\tVVVVxx\n930\t688\t0\t2\t0\t10\t0\t30\t130\t430\t930\t0\t1\tUJAAAA\tMABAAA\tAAAAxx\n435\t689\t1\t3\t5\t15\t5\t35\t35\t435\t435\t10\t11\tTQAAAA\tNABAAA\tHHHHxx\n563\t690\t1\t3\t3\t3\t3\t63\t163\t63\t563\t6\t7\tRVAAAA\tOABAAA\tOOOOxx\n960\t691\t0\t0\t0\t0\t0\t60\t160\t460\t960\t0\t1\tYKAAAA\tPABAAA\tVVVVxx\n733\t692\t1\t1\t3\t13\t3\t33\t133\t233\t733\t6\t7\tFCAAAA\tQABAAA\tAAAAxx\n967\t693\t1\t3\t7\t7\t7\t67\t167\t467\t967\t14\t15\tFLAAAA\tRABAAA\tHHHHxx\n668\t694\t0\t0\t8\t8\t8\t68\t68\t168\t668\t16\t17\tSZAAAA\tSABAAA\tOOOOxx\n994\t695\t0\t2\t4\t14\t4\t94\t194\t494\t994\t8\t9\tGMAAAA\tTABAAA\tVVVVxx\n129\t696\t1\t1\t9\t9\t9\t29\t129\t129\t129\t18\t19\tZEAAAA\tUABAAA\tAAAAxx\n954\t697\t0\t2\t4\t14\t4\t54\t154\t454\t954\t8\t9\tSKAAAA\tVABAAA\tHHHHxx\n68\t698\t0\t0\t8\t8\t8\t68\t68\t68\t68\t16\t17\tQCAAAA\tWABAAA\tOOOOxx\n79\t699\t1\t3\t9\t19\t9\t79\t79\t79\t79\t18\t19\tBDAAAA\tXABAAA\tVVVVxx\n121\t700\t1\t1\t1\t1\t1\t21\t121\t121\t121\t2\t3\tREAAAA\tYABAAA\tAAAAxx\n740\t701\t0\t0\t0\t0\t0\t40\t140\t240\t740\t0\t1\tMCAAAA\tZABAAA\tHHHHxx\n902\t702\t0\t2\t2\t2\t2\t2\t102\t402\t902\t4\t5\tSIAAAA\tABBAAA\tOOOOxx\n695\t703\t1\t3\t5\t15\t5\t95\t95\t195\t695\t10\t11\tTAAAAA\tBBBAAA\tVVVVxx\n455\t704\t1\t3\t5\t15\t5\t55\t55\t455\t455\t10\t11\tNRAAAA\tCBBAAA\tAAAAxx\n89\t705\t1\t1\t9\t9\t9\t89\t89\t89\t89\t18\t19\tLDAAAA\tDBBAAA\tHHHHxx\n893\t706\t1\t1\t3\t13\t3\t93\t93\t393\t893\t6\t7\tJIAAAA\tEBBAAA\tOOOOxx\n202\t707\t0\t2\t2\t2\t2\t2\t2\t202\t202\t4\t5\tUHAAAA\tFBBAAA\tVVVVxx\n132\t708\t0\t0\t2\t12\t2\t32\t132\t132\t132\t4\t5\tCFAAAA\tGBBAAA\tAAAAxx\n782\t709\t0\t2\t2\t2\t2\t82\t182\t282\t782\t4\t5\tCEAAAA\tHBBAAA\tHHHHxx\n512\t710\t0\t0\t2\t12\t2\t12\t112\t12\t512\t4\t5\tSTAAAA\tIBBAAA\tOOOOxx\n857\t711\t1\t1\t7\t17\t7\t57\t57\t357\t857\t14\t15\tZGAAAA\tJBBAAA\tVVVVxx\n248\t712\t0\t0\t8\t8\t8\t48\t48\t248\t248\t16\t17\tOJAAAA\tKBBAAA\tAAAAxx\n858\t713\t0\t2\t8\t18\t8\t58\t58\t358\t858\t16\t17\tAHAAAA\tLBBAAA\tHHHHxx\n527\t714\t1\t3\t7\t7\t7\t27\t127\t27\t527\t14\t15\tHUAAAA\tMBBAAA\tOOOOxx\n450\t715\t0\t2\t0\t10\t0\t50\t50\t450\t450\t0\t1\tIRAAAA\tNBBAAA\tVVVVxx\n712\t716\t0\t0\t2\t12\t2\t12\t112\t212\t712\t4\t5\tKBAAAA\tOBBAAA\tAAAAxx\n153\t717\t1\t1\t3\t13\t3\t53\t153\t153\t153\t6\t7\tXFAAAA\tPBBAAA\tHHHHxx\n587\t718\t1\t3\t7\t7\t7\t87\t187\t87\t587\t14\t15\tPWAAAA\tQBBAAA\tOOOOxx\n593\t719\t1\t1\t3\t13\t3\t93\t193\t93\t593\t6\t7\tVWAAAA\tRBBAAA\tVVVVxx\n249\t720\t1\t1\t9\t9\t9\t49\t49\t249\t249\t18\t19\tPJAAAA\tSBBAAA\tAAAAxx\n128\t721\t0\t0\t8\t8\t8\t28\t128\t128\t128\t16\t17\tYEAAAA\tTBBAAA\tHHHHxx\n675\t722\t1\t3\t5\t15\t5\t75\t75\t175\t675\t10\t11\tZZAAAA\tUBBAAA\tOOOOxx\n929\t723\t1\t1\t9\t9\t9\t29\t129\t429\t929\t18\t19\tTJAAAA\tVBBAAA\tVVVVxx\n156\t724\t0\t0\t6\t16\t6\t56\t156\t156\t156\t12\t13\tAGAAAA\tWBBAAA\tAAAAxx\n415\t725\t1\t3\t5\t15\t5\t15\t15\t415\t415\t10\t11\tZPAAAA\tXBBAAA\tHHHHxx\n28\t726\t0\t0\t8\t8\t8\t28\t28\t28\t28\t16\t17\tCBAAAA\tYBBAAA\tOOOOxx\n18\t727\t0\t2\t8\t18\t8\t18\t18\t18\t18\t16\t17\tSAAAAA\tZBBAAA\tVVVVxx\n255\t728\t1\t3\t5\t15\t5\t55\t55\t255\t255\t10\t11\tVJAAAA\tACBAAA\tAAAAxx\n793\t729\t1\t1\t3\t13\t3\t93\t193\t293\t793\t6\t7\tNEAAAA\tBCBAAA\tHHHHxx\n554\t730\t0\t2\t4\t14\t4\t54\t154\t54\t554\t8\t9\tIVAAAA\tCCBAAA\tOOOOxx\n467\t731\t1\t3\t7\t7\t7\t67\t67\t467\t467\t14\t15\tZRAAAA\tDCBAAA\tVVVVxx\n410\t732\t0\t2\t0\t10\t0\t10\t10\t410\t410\t0\t1\tUPAAAA\tECBAAA\tAAAAxx\n651\t733\t1\t3\t1\t11\t1\t51\t51\t151\t651\t2\t3\tBZAAAA\tFCBAAA\tHHHHxx\n287\t734\t1\t3\t7\t7\t7\t87\t87\t287\t287\t14\t15\tBLAAAA\tGCBAAA\tOOOOxx\n640\t735\t0\t0\t0\t0\t0\t40\t40\t140\t640\t0\t1\tQYAAAA\tHCBAAA\tVVVVxx\n245\t736\t1\t1\t5\t5\t5\t45\t45\t245\t245\t10\t11\tLJAAAA\tICBAAA\tAAAAxx\n21\t737\t1\t1\t1\t1\t1\t21\t21\t21\t21\t2\t3\tVAAAAA\tJCBAAA\tHHHHxx\n83\t738\t1\t3\t3\t3\t3\t83\t83\t83\t83\t6\t7\tFDAAAA\tKCBAAA\tOOOOxx\n228\t739\t0\t0\t8\t8\t8\t28\t28\t228\t228\t16\t17\tUIAAAA\tLCBAAA\tVVVVxx\n323\t740\t1\t3\t3\t3\t3\t23\t123\t323\t323\t6\t7\tLMAAAA\tMCBAAA\tAAAAxx\n594\t741\t0\t2\t4\t14\t4\t94\t194\t94\t594\t8\t9\tWWAAAA\tNCBAAA\tHHHHxx\n528\t742\t0\t0\t8\t8\t8\t28\t128\t28\t528\t16\t17\tIUAAAA\tOCBAAA\tOOOOxx\n276\t743\t0\t0\t6\t16\t6\t76\t76\t276\t276\t12\t13\tQKAAAA\tPCBAAA\tVVVVxx\n598\t744\t0\t2\t8\t18\t8\t98\t198\t98\t598\t16\t17\tAXAAAA\tQCBAAA\tAAAAxx\n635\t745\t1\t3\t5\t15\t5\t35\t35\t135\t635\t10\t11\tLYAAAA\tRCBAAA\tHHHHxx\n868\t746\t0\t0\t8\t8\t8\t68\t68\t368\t868\t16\t17\tKHAAAA\tSCBAAA\tOOOOxx\n290\t747\t0\t2\t0\t10\t0\t90\t90\t290\t290\t0\t1\tELAAAA\tTCBAAA\tVVVVxx\n468\t748\t0\t0\t8\t8\t8\t68\t68\t468\t468\t16\t17\tASAAAA\tUCBAAA\tAAAAxx\n689\t749\t1\t1\t9\t9\t9\t89\t89\t189\t689\t18\t19\tNAAAAA\tVCBAAA\tHHHHxx\n799\t750\t1\t3\t9\t19\t9\t99\t199\t299\t799\t18\t19\tTEAAAA\tWCBAAA\tOOOOxx\n210\t751\t0\t2\t0\t10\t0\t10\t10\t210\t210\t0\t1\tCIAAAA\tXCBAAA\tVVVVxx\n346\t752\t0\t2\t6\t6\t6\t46\t146\t346\t346\t12\t13\tINAAAA\tYCBAAA\tAAAAxx\n957\t753\t1\t1\t7\t17\t7\t57\t157\t457\t957\t14\t15\tVKAAAA\tZCBAAA\tHHHHxx\n905\t754\t1\t1\t5\t5\t5\t5\t105\t405\t905\t10\t11\tVIAAAA\tADBAAA\tOOOOxx\n523\t755\t1\t3\t3\t3\t3\t23\t123\t23\t523\t6\t7\tDUAAAA\tBDBAAA\tVVVVxx\n899\t756\t1\t3\t9\t19\t9\t99\t99\t399\t899\t18\t19\tPIAAAA\tCDBAAA\tAAAAxx\n867\t757\t1\t3\t7\t7\t7\t67\t67\t367\t867\t14\t15\tJHAAAA\tDDBAAA\tHHHHxx\n11\t758\t1\t3\t1\t11\t1\t11\t11\t11\t11\t2\t3\tLAAAAA\tEDBAAA\tOOOOxx\n320\t759\t0\t0\t0\t0\t0\t20\t120\t320\t320\t0\t1\tIMAAAA\tFDBAAA\tVVVVxx\n766\t760\t0\t2\t6\t6\t6\t66\t166\t266\t766\t12\t13\tMDAAAA\tGDBAAA\tAAAAxx\n84\t761\t0\t0\t4\t4\t4\t84\t84\t84\t84\t8\t9\tGDAAAA\tHDBAAA\tHHHHxx\n507\t762\t1\t3\t7\t7\t7\t7\t107\t7\t507\t14\t15\tNTAAAA\tIDBAAA\tOOOOxx\n471\t763\t1\t3\t1\t11\t1\t71\t71\t471\t471\t2\t3\tDSAAAA\tJDBAAA\tVVVVxx\n517\t764\t1\t1\t7\t17\t7\t17\t117\t17\t517\t14\t15\tXTAAAA\tKDBAAA\tAAAAxx\n234\t765\t0\t2\t4\t14\t4\t34\t34\t234\t234\t8\t9\tAJAAAA\tLDBAAA\tHHHHxx\n988\t766\t0\t0\t8\t8\t8\t88\t188\t488\t988\t16\t17\tAMAAAA\tMDBAAA\tOOOOxx\n473\t767\t1\t1\t3\t13\t3\t73\t73\t473\t473\t6\t7\tFSAAAA\tNDBAAA\tVVVVxx\n66\t768\t0\t2\t6\t6\t6\t66\t66\t66\t66\t12\t13\tOCAAAA\tODBAAA\tAAAAxx\n530\t769\t0\t2\t0\t10\t0\t30\t130\t30\t530\t0\t1\tKUAAAA\tPDBAAA\tHHHHxx\n834\t770\t0\t2\t4\t14\t4\t34\t34\t334\t834\t8\t9\tCGAAAA\tQDBAAA\tOOOOxx\n894\t771\t0\t2\t4\t14\t4\t94\t94\t394\t894\t8\t9\tKIAAAA\tRDBAAA\tVVVVxx\n481\t772\t1\t1\t1\t1\t1\t81\t81\t481\t481\t2\t3\tNSAAAA\tSDBAAA\tAAAAxx\n280\t773\t0\t0\t0\t0\t0\t80\t80\t280\t280\t0\t1\tUKAAAA\tTDBAAA\tHHHHxx\n705\t774\t1\t1\t5\t5\t5\t5\t105\t205\t705\t10\t11\tDBAAAA\tUDBAAA\tOOOOxx\n218\t775\t0\t2\t8\t18\t8\t18\t18\t218\t218\t16\t17\tKIAAAA\tVDBAAA\tVVVVxx\n560\t776\t0\t0\t0\t0\t0\t60\t160\t60\t560\t0\t1\tOVAAAA\tWDBAAA\tAAAAxx\n123\t777\t1\t3\t3\t3\t3\t23\t123\t123\t123\t6\t7\tTEAAAA\tXDBAAA\tHHHHxx\n289\t778\t1\t1\t9\t9\t9\t89\t89\t289\t289\t18\t19\tDLAAAA\tYDBAAA\tOOOOxx\n189\t779\t1\t1\t9\t9\t9\t89\t189\t189\t189\t18\t19\tHHAAAA\tZDBAAA\tVVVVxx\n541\t780\t1\t1\t1\t1\t1\t41\t141\t41\t541\t2\t3\tVUAAAA\tAEBAAA\tAAAAxx\n876\t781\t0\t0\t6\t16\t6\t76\t76\t376\t876\t12\t13\tSHAAAA\tBEBAAA\tHHHHxx\n504\t782\t0\t0\t4\t4\t4\t4\t104\t4\t504\t8\t9\tKTAAAA\tCEBAAA\tOOOOxx\n643\t783\t1\t3\t3\t3\t3\t43\t43\t143\t643\t6\t7\tTYAAAA\tDEBAAA\tVVVVxx\n73\t784\t1\t1\t3\t13\t3\t73\t73\t73\t73\t6\t7\tVCAAAA\tEEBAAA\tAAAAxx\n465\t785\t1\t1\t5\t5\t5\t65\t65\t465\t465\t10\t11\tXRAAAA\tFEBAAA\tHHHHxx\n861\t786\t1\t1\t1\t1\t1\t61\t61\t361\t861\t2\t3\tDHAAAA\tGEBAAA\tOOOOxx\n355\t787\t1\t3\t5\t15\t5\t55\t155\t355\t355\t10\t11\tRNAAAA\tHEBAAA\tVVVVxx\n441\t788\t1\t1\t1\t1\t1\t41\t41\t441\t441\t2\t3\tZQAAAA\tIEBAAA\tAAAAxx\n219\t789\t1\t3\t9\t19\t9\t19\t19\t219\t219\t18\t19\tLIAAAA\tJEBAAA\tHHHHxx\n839\t790\t1\t3\t9\t19\t9\t39\t39\t339\t839\t18\t19\tHGAAAA\tKEBAAA\tOOOOxx\n271\t791\t1\t3\t1\t11\t1\t71\t71\t271\t271\t2\t3\tLKAAAA\tLEBAAA\tVVVVxx\n212\t792\t0\t0\t2\t12\t2\t12\t12\t212\t212\t4\t5\tEIAAAA\tMEBAAA\tAAAAxx\n904\t793\t0\t0\t4\t4\t4\t4\t104\t404\t904\t8\t9\tUIAAAA\tNEBAAA\tHHHHxx\n244\t794\t0\t0\t4\t4\t4\t44\t44\t244\t244\t8\t9\tKJAAAA\tOEBAAA\tOOOOxx\n751\t795\t1\t3\t1\t11\t1\t51\t151\t251\t751\t2\t3\tXCAAAA\tPEBAAA\tVVVVxx\n944\t796\t0\t0\t4\t4\t4\t44\t144\t444\t944\t8\t9\tIKAAAA\tQEBAAA\tAAAAxx\n305\t797\t1\t1\t5\t5\t5\t5\t105\t305\t305\t10\t11\tTLAAAA\tREBAAA\tHHHHxx\n617\t798\t1\t1\t7\t17\t7\t17\t17\t117\t617\t14\t15\tTXAAAA\tSEBAAA\tOOOOxx\n891\t799\t1\t3\t1\t11\t1\t91\t91\t391\t891\t2\t3\tHIAAAA\tTEBAAA\tVVVVxx\n653\t800\t1\t1\t3\t13\t3\t53\t53\t153\t653\t6\t7\tDZAAAA\tUEBAAA\tAAAAxx\n845\t801\t1\t1\t5\t5\t5\t45\t45\t345\t845\t10\t11\tNGAAAA\tVEBAAA\tHHHHxx\n936\t802\t0\t0\t6\t16\t6\t36\t136\t436\t936\t12\t13\tAKAAAA\tWEBAAA\tOOOOxx\n91\t803\t1\t3\t1\t11\t1\t91\t91\t91\t91\t2\t3\tNDAAAA\tXEBAAA\tVVVVxx\n442\t804\t0\t2\t2\t2\t2\t42\t42\t442\t442\t4\t5\tARAAAA\tYEBAAA\tAAAAxx\n498\t805\t0\t2\t8\t18\t8\t98\t98\t498\t498\t16\t17\tETAAAA\tZEBAAA\tHHHHxx\n987\t806\t1\t3\t7\t7\t7\t87\t187\t487\t987\t14\t15\tZLAAAA\tAFBAAA\tOOOOxx\n194\t807\t0\t2\t4\t14\t4\t94\t194\t194\t194\t8\t9\tMHAAAA\tBFBAAA\tVVVVxx\n927\t808\t1\t3\t7\t7\t7\t27\t127\t427\t927\t14\t15\tRJAAAA\tCFBAAA\tAAAAxx\n607\t809\t1\t3\t7\t7\t7\t7\t7\t107\t607\t14\t15\tJXAAAA\tDFBAAA\tHHHHxx\n119\t810\t1\t3\t9\t19\t9\t19\t119\t119\t119\t18\t19\tPEAAAA\tEFBAAA\tOOOOxx\n182\t811\t0\t2\t2\t2\t2\t82\t182\t182\t182\t4\t5\tAHAAAA\tFFBAAA\tVVVVxx\n606\t812\t0\t2\t6\t6\t6\t6\t6\t106\t606\t12\t13\tIXAAAA\tGFBAAA\tAAAAxx\n849\t813\t1\t1\t9\t9\t9\t49\t49\t349\t849\t18\t19\tRGAAAA\tHFBAAA\tHHHHxx\n34\t814\t0\t2\t4\t14\t4\t34\t34\t34\t34\t8\t9\tIBAAAA\tIFBAAA\tOOOOxx\n683\t815\t1\t3\t3\t3\t3\t83\t83\t183\t683\t6\t7\tHAAAAA\tJFBAAA\tVVVVxx\n134\t816\t0\t2\t4\t14\t4\t34\t134\t134\t134\t8\t9\tEFAAAA\tKFBAAA\tAAAAxx\n331\t817\t1\t3\t1\t11\t1\t31\t131\t331\t331\t2\t3\tTMAAAA\tLFBAAA\tHHHHxx\n808\t818\t0\t0\t8\t8\t8\t8\t8\t308\t808\t16\t17\tCFAAAA\tMFBAAA\tOOOOxx\n703\t819\t1\t3\t3\t3\t3\t3\t103\t203\t703\t6\t7\tBBAAAA\tNFBAAA\tVVVVxx\n669\t820\t1\t1\t9\t9\t9\t69\t69\t169\t669\t18\t19\tTZAAAA\tOFBAAA\tAAAAxx\n264\t821\t0\t0\t4\t4\t4\t64\t64\t264\t264\t8\t9\tEKAAAA\tPFBAAA\tHHHHxx\n277\t822\t1\t1\t7\t17\t7\t77\t77\t277\t277\t14\t15\tRKAAAA\tQFBAAA\tOOOOxx\n877\t823\t1\t1\t7\t17\t7\t77\t77\t377\t877\t14\t15\tTHAAAA\tRFBAAA\tVVVVxx\n783\t824\t1\t3\t3\t3\t3\t83\t183\t283\t783\t6\t7\tDEAAAA\tSFBAAA\tAAAAxx\n791\t825\t1\t3\t1\t11\t1\t91\t191\t291\t791\t2\t3\tLEAAAA\tTFBAAA\tHHHHxx\n171\t826\t1\t3\t1\t11\t1\t71\t171\t171\t171\t2\t3\tPGAAAA\tUFBAAA\tOOOOxx\n564\t827\t0\t0\t4\t4\t4\t64\t164\t64\t564\t8\t9\tSVAAAA\tVFBAAA\tVVVVxx\n230\t828\t0\t2\t0\t10\t0\t30\t30\t230\t230\t0\t1\tWIAAAA\tWFBAAA\tAAAAxx\n881\t829\t1\t1\t1\t1\t1\t81\t81\t381\t881\t2\t3\tXHAAAA\tXFBAAA\tHHHHxx\n890\t830\t0\t2\t0\t10\t0\t90\t90\t390\t890\t0\t1\tGIAAAA\tYFBAAA\tOOOOxx\n374\t831\t0\t2\t4\t14\t4\t74\t174\t374\t374\t8\t9\tKOAAAA\tZFBAAA\tVVVVxx\n697\t832\t1\t1\t7\t17\t7\t97\t97\t197\t697\t14\t15\tVAAAAA\tAGBAAA\tAAAAxx\n4\t833\t0\t0\t4\t4\t4\t4\t4\t4\t4\t8\t9\tEAAAAA\tBGBAAA\tHHHHxx\n385\t834\t1\t1\t5\t5\t5\t85\t185\t385\t385\t10\t11\tVOAAAA\tCGBAAA\tOOOOxx\n739\t835\t1\t3\t9\t19\t9\t39\t139\t239\t739\t18\t19\tLCAAAA\tDGBAAA\tVVVVxx\n623\t836\t1\t3\t3\t3\t3\t23\t23\t123\t623\t6\t7\tZXAAAA\tEGBAAA\tAAAAxx\n547\t837\t1\t3\t7\t7\t7\t47\t147\t47\t547\t14\t15\tBVAAAA\tFGBAAA\tHHHHxx\n532\t838\t0\t0\t2\t12\t2\t32\t132\t32\t532\t4\t5\tMUAAAA\tGGBAAA\tOOOOxx\n383\t839\t1\t3\t3\t3\t3\t83\t183\t383\t383\t6\t7\tTOAAAA\tHGBAAA\tVVVVxx\n181\t840\t1\t1\t1\t1\t1\t81\t181\t181\t181\t2\t3\tZGAAAA\tIGBAAA\tAAAAxx\n327\t841\t1\t3\t7\t7\t7\t27\t127\t327\t327\t14\t15\tPMAAAA\tJGBAAA\tHHHHxx\n701\t842\t1\t1\t1\t1\t1\t1\t101\t201\t701\t2\t3\tZAAAAA\tKGBAAA\tOOOOxx\n111\t843\t1\t3\t1\t11\t1\t11\t111\t111\t111\t2\t3\tHEAAAA\tLGBAAA\tVVVVxx\n977\t844\t1\t1\t7\t17\t7\t77\t177\t477\t977\t14\t15\tPLAAAA\tMGBAAA\tAAAAxx\n431\t845\t1\t3\t1\t11\t1\t31\t31\t431\t431\t2\t3\tPQAAAA\tNGBAAA\tHHHHxx\n456\t846\t0\t0\t6\t16\t6\t56\t56\t456\t456\t12\t13\tORAAAA\tOGBAAA\tOOOOxx\n368\t847\t0\t0\t8\t8\t8\t68\t168\t368\t368\t16\t17\tEOAAAA\tPGBAAA\tVVVVxx\n32\t848\t0\t0\t2\t12\t2\t32\t32\t32\t32\t4\t5\tGBAAAA\tQGBAAA\tAAAAxx\n125\t849\t1\t1\t5\t5\t5\t25\t125\t125\t125\t10\t11\tVEAAAA\tRGBAAA\tHHHHxx\n847\t850\t1\t3\t7\t7\t7\t47\t47\t347\t847\t14\t15\tPGAAAA\tSGBAAA\tOOOOxx\n485\t851\t1\t1\t5\t5\t5\t85\t85\t485\t485\t10\t11\tRSAAAA\tTGBAAA\tVVVVxx\n387\t852\t1\t3\t7\t7\t7\t87\t187\t387\t387\t14\t15\tXOAAAA\tUGBAAA\tAAAAxx\n288\t853\t0\t0\t8\t8\t8\t88\t88\t288\t288\t16\t17\tCLAAAA\tVGBAAA\tHHHHxx\n919\t854\t1\t3\t9\t19\t9\t19\t119\t419\t919\t18\t19\tJJAAAA\tWGBAAA\tOOOOxx\n393\t855\t1\t1\t3\t13\t3\t93\t193\t393\t393\t6\t7\tDPAAAA\tXGBAAA\tVVVVxx\n953\t856\t1\t1\t3\t13\t3\t53\t153\t453\t953\t6\t7\tRKAAAA\tYGBAAA\tAAAAxx\n798\t857\t0\t2\t8\t18\t8\t98\t198\t298\t798\t16\t17\tSEAAAA\tZGBAAA\tHHHHxx\n940\t858\t0\t0\t0\t0\t0\t40\t140\t440\t940\t0\t1\tEKAAAA\tAHBAAA\tOOOOxx\n198\t859\t0\t2\t8\t18\t8\t98\t198\t198\t198\t16\t17\tQHAAAA\tBHBAAA\tVVVVxx\n25\t860\t1\t1\t5\t5\t5\t25\t25\t25\t25\t10\t11\tZAAAAA\tCHBAAA\tAAAAxx\n190\t861\t0\t2\t0\t10\t0\t90\t190\t190\t190\t0\t1\tIHAAAA\tDHBAAA\tHHHHxx\n820\t862\t0\t0\t0\t0\t0\t20\t20\t320\t820\t0\t1\tOFAAAA\tEHBAAA\tOOOOxx\n15\t863\t1\t3\t5\t15\t5\t15\t15\t15\t15\t10\t11\tPAAAAA\tFHBAAA\tVVVVxx\n427\t864\t1\t3\t7\t7\t7\t27\t27\t427\t427\t14\t15\tLQAAAA\tGHBAAA\tAAAAxx\n349\t865\t1\t1\t9\t9\t9\t49\t149\t349\t349\t18\t19\tLNAAAA\tHHBAAA\tHHHHxx\n785\t866\t1\t1\t5\t5\t5\t85\t185\t285\t785\t10\t11\tFEAAAA\tIHBAAA\tOOOOxx\n340\t867\t0\t0\t0\t0\t0\t40\t140\t340\t340\t0\t1\tCNAAAA\tJHBAAA\tVVVVxx\n292\t868\t0\t0\t2\t12\t2\t92\t92\t292\t292\t4\t5\tGLAAAA\tKHBAAA\tAAAAxx\n17\t869\t1\t1\t7\t17\t7\t17\t17\t17\t17\t14\t15\tRAAAAA\tLHBAAA\tHHHHxx\n985\t870\t1\t1\t5\t5\t5\t85\t185\t485\t985\t10\t11\tXLAAAA\tMHBAAA\tOOOOxx\n645\t871\t1\t1\t5\t5\t5\t45\t45\t145\t645\t10\t11\tVYAAAA\tNHBAAA\tVVVVxx\n631\t872\t1\t3\t1\t11\t1\t31\t31\t131\t631\t2\t3\tHYAAAA\tOHBAAA\tAAAAxx\n761\t873\t1\t1\t1\t1\t1\t61\t161\t261\t761\t2\t3\tHDAAAA\tPHBAAA\tHHHHxx\n707\t874\t1\t3\t7\t7\t7\t7\t107\t207\t707\t14\t15\tFBAAAA\tQHBAAA\tOOOOxx\n776\t875\t0\t0\t6\t16\t6\t76\t176\t276\t776\t12\t13\tWDAAAA\tRHBAAA\tVVVVxx\n856\t876\t0\t0\t6\t16\t6\t56\t56\t356\t856\t12\t13\tYGAAAA\tSHBAAA\tAAAAxx\n978\t877\t0\t2\t8\t18\t8\t78\t178\t478\t978\t16\t17\tQLAAAA\tTHBAAA\tHHHHxx\n710\t878\t0\t2\t0\t10\t0\t10\t110\t210\t710\t0\t1\tIBAAAA\tUHBAAA\tOOOOxx\n604\t879\t0\t0\t4\t4\t4\t4\t4\t104\t604\t8\t9\tGXAAAA\tVHBAAA\tVVVVxx\n291\t880\t1\t3\t1\t11\t1\t91\t91\t291\t291\t2\t3\tFLAAAA\tWHBAAA\tAAAAxx\n747\t881\t1\t3\t7\t7\t7\t47\t147\t247\t747\t14\t15\tTCAAAA\tXHBAAA\tHHHHxx\n837\t882\t1\t1\t7\t17\t7\t37\t37\t337\t837\t14\t15\tFGAAAA\tYHBAAA\tOOOOxx\n722\t883\t0\t2\t2\t2\t2\t22\t122\t222\t722\t4\t5\tUBAAAA\tZHBAAA\tVVVVxx\n925\t884\t1\t1\t5\t5\t5\t25\t125\t425\t925\t10\t11\tPJAAAA\tAIBAAA\tAAAAxx\n49\t885\t1\t1\t9\t9\t9\t49\t49\t49\t49\t18\t19\tXBAAAA\tBIBAAA\tHHHHxx\n832\t886\t0\t0\t2\t12\t2\t32\t32\t332\t832\t4\t5\tAGAAAA\tCIBAAA\tOOOOxx\n336\t887\t0\t0\t6\t16\t6\t36\t136\t336\t336\t12\t13\tYMAAAA\tDIBAAA\tVVVVxx\n185\t888\t1\t1\t5\t5\t5\t85\t185\t185\t185\t10\t11\tDHAAAA\tEIBAAA\tAAAAxx\n434\t889\t0\t2\t4\t14\t4\t34\t34\t434\t434\t8\t9\tSQAAAA\tFIBAAA\tHHHHxx\n284\t890\t0\t0\t4\t4\t4\t84\t84\t284\t284\t8\t9\tYKAAAA\tGIBAAA\tOOOOxx\n812\t891\t0\t0\t2\t12\t2\t12\t12\t312\t812\t4\t5\tGFAAAA\tHIBAAA\tVVVVxx\n810\t892\t0\t2\t0\t10\t0\t10\t10\t310\t810\t0\t1\tEFAAAA\tIIBAAA\tAAAAxx\n252\t893\t0\t0\t2\t12\t2\t52\t52\t252\t252\t4\t5\tSJAAAA\tJIBAAA\tHHHHxx\n965\t894\t1\t1\t5\t5\t5\t65\t165\t465\t965\t10\t11\tDLAAAA\tKIBAAA\tOOOOxx\n110\t895\t0\t2\t0\t10\t0\t10\t110\t110\t110\t0\t1\tGEAAAA\tLIBAAA\tVVVVxx\n698\t896\t0\t2\t8\t18\t8\t98\t98\t198\t698\t16\t17\tWAAAAA\tMIBAAA\tAAAAxx\n283\t897\t1\t3\t3\t3\t3\t83\t83\t283\t283\t6\t7\tXKAAAA\tNIBAAA\tHHHHxx\n533\t898\t1\t1\t3\t13\t3\t33\t133\t33\t533\t6\t7\tNUAAAA\tOIBAAA\tOOOOxx\n662\t899\t0\t2\t2\t2\t2\t62\t62\t162\t662\t4\t5\tMZAAAA\tPIBAAA\tVVVVxx\n329\t900\t1\t1\t9\t9\t9\t29\t129\t329\t329\t18\t19\tRMAAAA\tQIBAAA\tAAAAxx\n250\t901\t0\t2\t0\t10\t0\t50\t50\t250\t250\t0\t1\tQJAAAA\tRIBAAA\tHHHHxx\n407\t902\t1\t3\t7\t7\t7\t7\t7\t407\t407\t14\t15\tRPAAAA\tSIBAAA\tOOOOxx\n823\t903\t1\t3\t3\t3\t3\t23\t23\t323\t823\t6\t7\tRFAAAA\tTIBAAA\tVVVVxx\n852\t904\t0\t0\t2\t12\t2\t52\t52\t352\t852\t4\t5\tUGAAAA\tUIBAAA\tAAAAxx\n871\t905\t1\t3\t1\t11\t1\t71\t71\t371\t871\t2\t3\tNHAAAA\tVIBAAA\tHHHHxx\n118\t906\t0\t2\t8\t18\t8\t18\t118\t118\t118\t16\t17\tOEAAAA\tWIBAAA\tOOOOxx\n912\t907\t0\t0\t2\t12\t2\t12\t112\t412\t912\t4\t5\tCJAAAA\tXIBAAA\tVVVVxx\n458\t908\t0\t2\t8\t18\t8\t58\t58\t458\t458\t16\t17\tQRAAAA\tYIBAAA\tAAAAxx\n926\t909\t0\t2\t6\t6\t6\t26\t126\t426\t926\t12\t13\tQJAAAA\tZIBAAA\tHHHHxx\n328\t910\t0\t0\t8\t8\t8\t28\t128\t328\t328\t16\t17\tQMAAAA\tAJBAAA\tOOOOxx\n980\t911\t0\t0\t0\t0\t0\t80\t180\t480\t980\t0\t1\tSLAAAA\tBJBAAA\tVVVVxx\n259\t912\t1\t3\t9\t19\t9\t59\t59\t259\t259\t18\t19\tZJAAAA\tCJBAAA\tAAAAxx\n900\t913\t0\t0\t0\t0\t0\t0\t100\t400\t900\t0\t1\tQIAAAA\tDJBAAA\tHHHHxx\n137\t914\t1\t1\t7\t17\t7\t37\t137\t137\t137\t14\t15\tHFAAAA\tEJBAAA\tOOOOxx\n159\t915\t1\t3\t9\t19\t9\t59\t159\t159\t159\t18\t19\tDGAAAA\tFJBAAA\tVVVVxx\n243\t916\t1\t3\t3\t3\t3\t43\t43\t243\t243\t6\t7\tJJAAAA\tGJBAAA\tAAAAxx\n472\t917\t0\t0\t2\t12\t2\t72\t72\t472\t472\t4\t5\tESAAAA\tHJBAAA\tHHHHxx\n796\t918\t0\t0\t6\t16\t6\t96\t196\t296\t796\t12\t13\tQEAAAA\tIJBAAA\tOOOOxx\n382\t919\t0\t2\t2\t2\t2\t82\t182\t382\t382\t4\t5\tSOAAAA\tJJBAAA\tVVVVxx\n911\t920\t1\t3\t1\t11\t1\t11\t111\t411\t911\t2\t3\tBJAAAA\tKJBAAA\tAAAAxx\n179\t921\t1\t3\t9\t19\t9\t79\t179\t179\t179\t18\t19\tXGAAAA\tLJBAAA\tHHHHxx\n778\t922\t0\t2\t8\t18\t8\t78\t178\t278\t778\t16\t17\tYDAAAA\tMJBAAA\tOOOOxx\n405\t923\t1\t1\t5\t5\t5\t5\t5\t405\t405\t10\t11\tPPAAAA\tNJBAAA\tVVVVxx\n265\t924\t1\t1\t5\t5\t5\t65\t65\t265\t265\t10\t11\tFKAAAA\tOJBAAA\tAAAAxx\n556\t925\t0\t0\t6\t16\t6\t56\t156\t56\t556\t12\t13\tKVAAAA\tPJBAAA\tHHHHxx\n16\t926\t0\t0\t6\t16\t6\t16\t16\t16\t16\t12\t13\tQAAAAA\tQJBAAA\tOOOOxx\n706\t927\t0\t2\t6\t6\t6\t6\t106\t206\t706\t12\t13\tEBAAAA\tRJBAAA\tVVVVxx\n497\t928\t1\t1\t7\t17\t7\t97\t97\t497\t497\t14\t15\tDTAAAA\tSJBAAA\tAAAAxx\n708\t929\t0\t0\t8\t8\t8\t8\t108\t208\t708\t16\t17\tGBAAAA\tTJBAAA\tHHHHxx\n46\t930\t0\t2\t6\t6\t6\t46\t46\t46\t46\t12\t13\tUBAAAA\tUJBAAA\tOOOOxx\n901\t931\t1\t1\t1\t1\t1\t1\t101\t401\t901\t2\t3\tRIAAAA\tVJBAAA\tVVVVxx\n416\t932\t0\t0\t6\t16\t6\t16\t16\t416\t416\t12\t13\tAQAAAA\tWJBAAA\tAAAAxx\n307\t933\t1\t3\t7\t7\t7\t7\t107\t307\t307\t14\t15\tVLAAAA\tXJBAAA\tHHHHxx\n166\t934\t0\t2\t6\t6\t6\t66\t166\t166\t166\t12\t13\tKGAAAA\tYJBAAA\tOOOOxx\n178\t935\t0\t2\t8\t18\t8\t78\t178\t178\t178\t16\t17\tWGAAAA\tZJBAAA\tVVVVxx\n499\t936\t1\t3\t9\t19\t9\t99\t99\t499\t499\t18\t19\tFTAAAA\tAKBAAA\tAAAAxx\n257\t937\t1\t1\t7\t17\t7\t57\t57\t257\t257\t14\t15\tXJAAAA\tBKBAAA\tHHHHxx\n342\t938\t0\t2\t2\t2\t2\t42\t142\t342\t342\t4\t5\tENAAAA\tCKBAAA\tOOOOxx\n850\t939\t0\t2\t0\t10\t0\t50\t50\t350\t850\t0\t1\tSGAAAA\tDKBAAA\tVVVVxx\n313\t940\t1\t1\t3\t13\t3\t13\t113\t313\t313\t6\t7\tBMAAAA\tEKBAAA\tAAAAxx\n831\t941\t1\t3\t1\t11\t1\t31\t31\t331\t831\t2\t3\tZFAAAA\tFKBAAA\tHHHHxx\n57\t942\t1\t1\t7\t17\t7\t57\t57\t57\t57\t14\t15\tFCAAAA\tGKBAAA\tOOOOxx\n37\t943\t1\t1\t7\t17\t7\t37\t37\t37\t37\t14\t15\tLBAAAA\tHKBAAA\tVVVVxx\n511\t944\t1\t3\t1\t11\t1\t11\t111\t11\t511\t2\t3\tRTAAAA\tIKBAAA\tAAAAxx\n578\t945\t0\t2\t8\t18\t8\t78\t178\t78\t578\t16\t17\tGWAAAA\tJKBAAA\tHHHHxx\n100\t946\t0\t0\t0\t0\t0\t0\t100\t100\t100\t0\t1\tWDAAAA\tKKBAAA\tOOOOxx\n935\t947\t1\t3\t5\t15\t5\t35\t135\t435\t935\t10\t11\tZJAAAA\tLKBAAA\tVVVVxx\n821\t948\t1\t1\t1\t1\t1\t21\t21\t321\t821\t2\t3\tPFAAAA\tMKBAAA\tAAAAxx\n294\t949\t0\t2\t4\t14\t4\t94\t94\t294\t294\t8\t9\tILAAAA\tNKBAAA\tHHHHxx\n575\t950\t1\t3\t5\t15\t5\t75\t175\t75\t575\t10\t11\tDWAAAA\tOKBAAA\tOOOOxx\n272\t951\t0\t0\t2\t12\t2\t72\t72\t272\t272\t4\t5\tMKAAAA\tPKBAAA\tVVVVxx\n491\t952\t1\t3\t1\t11\t1\t91\t91\t491\t491\t2\t3\tXSAAAA\tQKBAAA\tAAAAxx\n43\t953\t1\t3\t3\t3\t3\t43\t43\t43\t43\t6\t7\tRBAAAA\tRKBAAA\tHHHHxx\n167\t954\t1\t3\t7\t7\t7\t67\t167\t167\t167\t14\t15\tLGAAAA\tSKBAAA\tOOOOxx\n457\t955\t1\t1\t7\t17\t7\t57\t57\t457\t457\t14\t15\tPRAAAA\tTKBAAA\tVVVVxx\n647\t956\t1\t3\t7\t7\t7\t47\t47\t147\t647\t14\t15\tXYAAAA\tUKBAAA\tAAAAxx\n180\t957\t0\t0\t0\t0\t0\t80\t180\t180\t180\t0\t1\tYGAAAA\tVKBAAA\tHHHHxx\n48\t958\t0\t0\t8\t8\t8\t48\t48\t48\t48\t16\t17\tWBAAAA\tWKBAAA\tOOOOxx\n553\t959\t1\t1\t3\t13\t3\t53\t153\t53\t553\t6\t7\tHVAAAA\tXKBAAA\tVVVVxx\n188\t960\t0\t0\t8\t8\t8\t88\t188\t188\t188\t16\t17\tGHAAAA\tYKBAAA\tAAAAxx\n262\t961\t0\t2\t2\t2\t2\t62\t62\t262\t262\t4\t5\tCKAAAA\tZKBAAA\tHHHHxx\n728\t962\t0\t0\t8\t8\t8\t28\t128\t228\t728\t16\t17\tACAAAA\tALBAAA\tOOOOxx\n581\t963\t1\t1\t1\t1\t1\t81\t181\t81\t581\t2\t3\tJWAAAA\tBLBAAA\tVVVVxx\n937\t964\t1\t1\t7\t17\t7\t37\t137\t437\t937\t14\t15\tBKAAAA\tCLBAAA\tAAAAxx\n370\t965\t0\t2\t0\t10\t0\t70\t170\t370\t370\t0\t1\tGOAAAA\tDLBAAA\tHHHHxx\n590\t966\t0\t2\t0\t10\t0\t90\t190\t90\t590\t0\t1\tSWAAAA\tELBAAA\tOOOOxx\n421\t967\t1\t1\t1\t1\t1\t21\t21\t421\t421\t2\t3\tFQAAAA\tFLBAAA\tVVVVxx\n693\t968\t1\t1\t3\t13\t3\t93\t93\t193\t693\t6\t7\tRAAAAA\tGLBAAA\tAAAAxx\n906\t969\t0\t2\t6\t6\t6\t6\t106\t406\t906\t12\t13\tWIAAAA\tHLBAAA\tHHHHxx\n802\t970\t0\t2\t2\t2\t2\t2\t2\t302\t802\t4\t5\tWEAAAA\tILBAAA\tOOOOxx\n38\t971\t0\t2\t8\t18\t8\t38\t38\t38\t38\t16\t17\tMBAAAA\tJLBAAA\tVVVVxx\n790\t972\t0\t2\t0\t10\t0\t90\t190\t290\t790\t0\t1\tKEAAAA\tKLBAAA\tAAAAxx\n726\t973\t0\t2\t6\t6\t6\t26\t126\t226\t726\t12\t13\tYBAAAA\tLLBAAA\tHHHHxx\n23\t974\t1\t3\t3\t3\t3\t23\t23\t23\t23\t6\t7\tXAAAAA\tMLBAAA\tOOOOxx\n641\t975\t1\t1\t1\t1\t1\t41\t41\t141\t641\t2\t3\tRYAAAA\tNLBAAA\tVVVVxx\n524\t976\t0\t0\t4\t4\t4\t24\t124\t24\t524\t8\t9\tEUAAAA\tOLBAAA\tAAAAxx\n169\t977\t1\t1\t9\t9\t9\t69\t169\t169\t169\t18\t19\tNGAAAA\tPLBAAA\tHHHHxx\n6\t978\t0\t2\t6\t6\t6\t6\t6\t6\t6\t12\t13\tGAAAAA\tQLBAAA\tOOOOxx\n943\t979\t1\t3\t3\t3\t3\t43\t143\t443\t943\t6\t7\tHKAAAA\tRLBAAA\tVVVVxx\n26\t980\t0\t2\t6\t6\t6\t26\t26\t26\t26\t12\t13\tABAAAA\tSLBAAA\tAAAAxx\n469\t981\t1\t1\t9\t9\t9\t69\t69\t469\t469\t18\t19\tBSAAAA\tTLBAAA\tHHHHxx\n968\t982\t0\t0\t8\t8\t8\t68\t168\t468\t968\t16\t17\tGLAAAA\tULBAAA\tOOOOxx\n947\t983\t1\t3\t7\t7\t7\t47\t147\t447\t947\t14\t15\tLKAAAA\tVLBAAA\tVVVVxx\n133\t984\t1\t1\t3\t13\t3\t33\t133\t133\t133\t6\t7\tDFAAAA\tWLBAAA\tAAAAxx\n52\t985\t0\t0\t2\t12\t2\t52\t52\t52\t52\t4\t5\tACAAAA\tXLBAAA\tHHHHxx\n660\t986\t0\t0\t0\t0\t0\t60\t60\t160\t660\t0\t1\tKZAAAA\tYLBAAA\tOOOOxx\n780\t987\t0\t0\t0\t0\t0\t80\t180\t280\t780\t0\t1\tAEAAAA\tZLBAAA\tVVVVxx\n963\t988\t1\t3\t3\t3\t3\t63\t163\t463\t963\t6\t7\tBLAAAA\tAMBAAA\tAAAAxx\n561\t989\t1\t1\t1\t1\t1\t61\t161\t61\t561\t2\t3\tPVAAAA\tBMBAAA\tHHHHxx\n402\t990\t0\t2\t2\t2\t2\t2\t2\t402\t402\t4\t5\tMPAAAA\tCMBAAA\tOOOOxx\n437\t991\t1\t1\t7\t17\t7\t37\t37\t437\t437\t14\t15\tVQAAAA\tDMBAAA\tVVVVxx\n112\t992\t0\t0\t2\t12\t2\t12\t112\t112\t112\t4\t5\tIEAAAA\tEMBAAA\tAAAAxx\n247\t993\t1\t3\t7\t7\t7\t47\t47\t247\t247\t14\t15\tNJAAAA\tFMBAAA\tHHHHxx\n579\t994\t1\t3\t9\t19\t9\t79\t179\t79\t579\t18\t19\tHWAAAA\tGMBAAA\tOOOOxx\n379\t995\t1\t3\t9\t19\t9\t79\t179\t379\t379\t18\t19\tPOAAAA\tHMBAAA\tVVVVxx\n74\t996\t0\t2\t4\t14\t4\t74\t74\t74\t74\t8\t9\tWCAAAA\tIMBAAA\tAAAAxx\n744\t997\t0\t0\t4\t4\t4\t44\t144\t244\t744\t8\t9\tQCAAAA\tJMBAAA\tHHHHxx\n0\t998\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t1\tAAAAAA\tKMBAAA\tOOOOxx\n278\t999\t0\t2\t8\t18\t8\t78\t78\t278\t278\t16\t17\tSKAAAA\tLMBAAA\tVVVVxx\n"
  },
  {
    "path": "test/sql/data/tenk.data",
    "content": "8800\t0\t0\t0\t0\t0\t0\t800\t800\t3800\t8800\t0\t1\tMAAAAA\tAAAAAA\tAAAAxx\n1891\t1\t1\t3\t1\t11\t91\t891\t1891\t1891\t1891\t182\t183\tTUAAAA\tBAAAAA\tHHHHxx\n3420\t2\t0\t0\t0\t0\t20\t420\t1420\t3420\t3420\t40\t41\tOBAAAA\tCAAAAA\tOOOOxx\n9850\t3\t0\t2\t0\t10\t50\t850\t1850\t4850\t9850\t100\t101\tWOAAAA\tDAAAAA\tVVVVxx\n7164\t4\t0\t0\t4\t4\t64\t164\t1164\t2164\t7164\t128\t129\tOPAAAA\tEAAAAA\tAAAAxx\n8009\t5\t1\t1\t9\t9\t9\t9\t9\t3009\t8009\t18\t19\tBWAAAA\tFAAAAA\tHHHHxx\n5057\t6\t1\t1\t7\t17\t57\t57\t1057\t57\t5057\t114\t115\tNMAAAA\tGAAAAA\tOOOOxx\n6701\t7\t1\t1\t1\t1\t1\t701\t701\t1701\t6701\t2\t3\tTXAAAA\tHAAAAA\tVVVVxx\n4321\t8\t1\t1\t1\t1\t21\t321\t321\t4321\t4321\t42\t43\tFKAAAA\tIAAAAA\tAAAAxx\n3043\t9\t1\t3\t3\t3\t43\t43\t1043\t3043\t3043\t86\t87\tBNAAAA\tJAAAAA\tHHHHxx\n1314\t10\t0\t2\t4\t14\t14\t314\t1314\t1314\t1314\t28\t29\tOYAAAA\tKAAAAA\tOOOOxx\n1504\t11\t0\t0\t4\t4\t4\t504\t1504\t1504\t1504\t8\t9\tWFAAAA\tLAAAAA\tVVVVxx\n5222\t12\t0\t2\t2\t2\t22\t222\t1222\t222\t5222\t44\t45\tWSAAAA\tMAAAAA\tAAAAxx\n6243\t13\t1\t3\t3\t3\t43\t243\t243\t1243\t6243\t86\t87\tDGAAAA\tNAAAAA\tHHHHxx\n5471\t14\t1\t3\t1\t11\t71\t471\t1471\t471\t5471\t142\t143\tLCAAAA\tOAAAAA\tOOOOxx\n5006\t15\t0\t2\t6\t6\t6\t6\t1006\t6\t5006\t12\t13\tOKAAAA\tPAAAAA\tVVVVxx\n5387\t16\t1\t3\t7\t7\t87\t387\t1387\t387\t5387\t174\t175\tFZAAAA\tQAAAAA\tAAAAxx\n5785\t17\t1\t1\t5\t5\t85\t785\t1785\t785\t5785\t170\t171\tNOAAAA\tRAAAAA\tHHHHxx\n6621\t18\t1\t1\t1\t1\t21\t621\t621\t1621\t6621\t42\t43\tRUAAAA\tSAAAAA\tOOOOxx\n6969\t19\t1\t1\t9\t9\t69\t969\t969\t1969\t6969\t138\t139\tBIAAAA\tTAAAAA\tVVVVxx\n9460\t20\t0\t0\t0\t0\t60\t460\t1460\t4460\t9460\t120\t121\tWZAAAA\tUAAAAA\tAAAAxx\n59\t21\t1\t3\t9\t19\t59\t59\t59\t59\t59\t118\t119\tHCAAAA\tVAAAAA\tHHHHxx\n8020\t22\t0\t0\t0\t0\t20\t20\t20\t3020\t8020\t40\t41\tMWAAAA\tWAAAAA\tOOOOxx\n7695\t23\t1\t3\t5\t15\t95\t695\t1695\t2695\t7695\t190\t191\tZJAAAA\tXAAAAA\tVVVVxx\n3442\t24\t0\t2\t2\t2\t42\t442\t1442\t3442\t3442\t84\t85\tKCAAAA\tYAAAAA\tAAAAxx\n5119\t25\t1\t3\t9\t19\t19\t119\t1119\t119\t5119\t38\t39\tXOAAAA\tZAAAAA\tHHHHxx\n646\t26\t0\t2\t6\t6\t46\t646\t646\t646\t646\t92\t93\tWYAAAA\tABAAAA\tOOOOxx\n9605\t27\t1\t1\t5\t5\t5\t605\t1605\t4605\t9605\t10\t11\tLFAAAA\tBBAAAA\tVVVVxx\n263\t28\t1\t3\t3\t3\t63\t263\t263\t263\t263\t126\t127\tDKAAAA\tCBAAAA\tAAAAxx\n3269\t29\t1\t1\t9\t9\t69\t269\t1269\t3269\t3269\t138\t139\tTVAAAA\tDBAAAA\tHHHHxx\n1839\t30\t1\t3\t9\t19\t39\t839\t1839\t1839\t1839\t78\t79\tTSAAAA\tEBAAAA\tOOOOxx\n9144\t31\t0\t0\t4\t4\t44\t144\t1144\t4144\t9144\t88\t89\tSNAAAA\tFBAAAA\tVVVVxx\n2513\t32\t1\t1\t3\t13\t13\t513\t513\t2513\t2513\t26\t27\tRSAAAA\tGBAAAA\tAAAAxx\n8850\t33\t0\t2\t0\t10\t50\t850\t850\t3850\t8850\t100\t101\tKCAAAA\tHBAAAA\tHHHHxx\n236\t34\t0\t0\t6\t16\t36\t236\t236\t236\t236\t72\t73\tCJAAAA\tIBAAAA\tOOOOxx\n3162\t35\t0\t2\t2\t2\t62\t162\t1162\t3162\t3162\t124\t125\tQRAAAA\tJBAAAA\tVVVVxx\n4380\t36\t0\t0\t0\t0\t80\t380\t380\t4380\t4380\t160\t161\tMMAAAA\tKBAAAA\tAAAAxx\n8095\t37\t1\t3\t5\t15\t95\t95\t95\t3095\t8095\t190\t191\tJZAAAA\tLBAAAA\tHHHHxx\n209\t38\t1\t1\t9\t9\t9\t209\t209\t209\t209\t18\t19\tBIAAAA\tMBAAAA\tOOOOxx\n3055\t39\t1\t3\t5\t15\t55\t55\t1055\t3055\t3055\t110\t111\tNNAAAA\tNBAAAA\tVVVVxx\n6921\t40\t1\t1\t1\t1\t21\t921\t921\t1921\t6921\t42\t43\tFGAAAA\tOBAAAA\tAAAAxx\n7046\t41\t0\t2\t6\t6\t46\t46\t1046\t2046\t7046\t92\t93\tALAAAA\tPBAAAA\tHHHHxx\n7912\t42\t0\t0\t2\t12\t12\t912\t1912\t2912\t7912\t24\t25\tISAAAA\tQBAAAA\tOOOOxx\n7267\t43\t1\t3\t7\t7\t67\t267\t1267\t2267\t7267\t134\t135\tNTAAAA\tRBAAAA\tVVVVxx\n3599\t44\t1\t3\t9\t19\t99\t599\t1599\t3599\t3599\t198\t199\tLIAAAA\tSBAAAA\tAAAAxx\n923\t45\t1\t3\t3\t3\t23\t923\t923\t923\t923\t46\t47\tNJAAAA\tTBAAAA\tHHHHxx\n1437\t46\t1\t1\t7\t17\t37\t437\t1437\t1437\t1437\t74\t75\tHDAAAA\tUBAAAA\tOOOOxx\n6439\t47\t1\t3\t9\t19\t39\t439\t439\t1439\t6439\t78\t79\tRNAAAA\tVBAAAA\tVVVVxx\n6989\t48\t1\t1\t9\t9\t89\t989\t989\t1989\t6989\t178\t179\tVIAAAA\tWBAAAA\tAAAAxx\n8798\t49\t0\t2\t8\t18\t98\t798\t798\t3798\t8798\t196\t197\tKAAAAA\tXBAAAA\tHHHHxx\n5960\t50\t0\t0\t0\t0\t60\t960\t1960\t960\t5960\t120\t121\tGVAAAA\tYBAAAA\tOOOOxx\n5832\t51\t0\t0\t2\t12\t32\t832\t1832\t832\t5832\t64\t65\tIQAAAA\tZBAAAA\tVVVVxx\n6066\t52\t0\t2\t6\t6\t66\t66\t66\t1066\t6066\t132\t133\tIZAAAA\tACAAAA\tAAAAxx\n322\t53\t0\t2\t2\t2\t22\t322\t322\t322\t322\t44\t45\tKMAAAA\tBCAAAA\tHHHHxx\n8321\t54\t1\t1\t1\t1\t21\t321\t321\t3321\t8321\t42\t43\tBIAAAA\tCCAAAA\tOOOOxx\n734\t55\t0\t2\t4\t14\t34\t734\t734\t734\t734\t68\t69\tGCAAAA\tDCAAAA\tVVVVxx\n688\t56\t0\t0\t8\t8\t88\t688\t688\t688\t688\t176\t177\tMAAAAA\tECAAAA\tAAAAxx\n4212\t57\t0\t0\t2\t12\t12\t212\t212\t4212\t4212\t24\t25\tAGAAAA\tFCAAAA\tHHHHxx\n9653\t58\t1\t1\t3\t13\t53\t653\t1653\t4653\t9653\t106\t107\tHHAAAA\tGCAAAA\tOOOOxx\n2677\t59\t1\t1\t7\t17\t77\t677\t677\t2677\t2677\t154\t155\tZYAAAA\tHCAAAA\tVVVVxx\n5423\t60\t1\t3\t3\t3\t23\t423\t1423\t423\t5423\t46\t47\tPAAAAA\tICAAAA\tAAAAxx\n2592\t61\t0\t0\t2\t12\t92\t592\t592\t2592\t2592\t184\t185\tSVAAAA\tJCAAAA\tHHHHxx\n3233\t62\t1\t1\t3\t13\t33\t233\t1233\t3233\t3233\t66\t67\tJUAAAA\tKCAAAA\tOOOOxx\n5032\t63\t0\t0\t2\t12\t32\t32\t1032\t32\t5032\t64\t65\tOLAAAA\tLCAAAA\tVVVVxx\n2525\t64\t1\t1\t5\t5\t25\t525\t525\t2525\t2525\t50\t51\tDTAAAA\tMCAAAA\tAAAAxx\n4450\t65\t0\t2\t0\t10\t50\t450\t450\t4450\t4450\t100\t101\tEPAAAA\tNCAAAA\tHHHHxx\n5778\t66\t0\t2\t8\t18\t78\t778\t1778\t778\t5778\t156\t157\tGOAAAA\tOCAAAA\tOOOOxx\n5852\t67\t0\t0\t2\t12\t52\t852\t1852\t852\t5852\t104\t105\tCRAAAA\tPCAAAA\tVVVVxx\n5404\t68\t0\t0\t4\t4\t4\t404\t1404\t404\t5404\t8\t9\tWZAAAA\tQCAAAA\tAAAAxx\n6223\t69\t1\t3\t3\t3\t23\t223\t223\t1223\t6223\t46\t47\tJFAAAA\tRCAAAA\tHHHHxx\n6133\t70\t1\t1\t3\t13\t33\t133\t133\t1133\t6133\t66\t67\tXBAAAA\tSCAAAA\tOOOOxx\n9112\t71\t0\t0\t2\t12\t12\t112\t1112\t4112\t9112\t24\t25\tMMAAAA\tTCAAAA\tVVVVxx\n7575\t72\t1\t3\t5\t15\t75\t575\t1575\t2575\t7575\t150\t151\tJFAAAA\tUCAAAA\tAAAAxx\n7414\t73\t0\t2\t4\t14\t14\t414\t1414\t2414\t7414\t28\t29\tEZAAAA\tVCAAAA\tHHHHxx\n9741\t74\t1\t1\t1\t1\t41\t741\t1741\t4741\t9741\t82\t83\tRKAAAA\tWCAAAA\tOOOOxx\n3767\t75\t1\t3\t7\t7\t67\t767\t1767\t3767\t3767\t134\t135\tXOAAAA\tXCAAAA\tVVVVxx\n9372\t76\t0\t0\t2\t12\t72\t372\t1372\t4372\t9372\t144\t145\tMWAAAA\tYCAAAA\tAAAAxx\n8976\t77\t0\t0\t6\t16\t76\t976\t976\t3976\t8976\t152\t153\tGHAAAA\tZCAAAA\tHHHHxx\n4071\t78\t1\t3\t1\t11\t71\t71\t71\t4071\t4071\t142\t143\tPAAAAA\tADAAAA\tOOOOxx\n1311\t79\t1\t3\t1\t11\t11\t311\t1311\t1311\t1311\t22\t23\tLYAAAA\tBDAAAA\tVVVVxx\n2604\t80\t0\t0\t4\t4\t4\t604\t604\t2604\t2604\t8\t9\tEWAAAA\tCDAAAA\tAAAAxx\n8840\t81\t0\t0\t0\t0\t40\t840\t840\t3840\t8840\t80\t81\tACAAAA\tDDAAAA\tHHHHxx\n567\t82\t1\t3\t7\t7\t67\t567\t567\t567\t567\t134\t135\tVVAAAA\tEDAAAA\tOOOOxx\n5215\t83\t1\t3\t5\t15\t15\t215\t1215\t215\t5215\t30\t31\tPSAAAA\tFDAAAA\tVVVVxx\n5474\t84\t0\t2\t4\t14\t74\t474\t1474\t474\t5474\t148\t149\tOCAAAA\tGDAAAA\tAAAAxx\n3906\t85\t0\t2\t6\t6\t6\t906\t1906\t3906\t3906\t12\t13\tGUAAAA\tHDAAAA\tHHHHxx\n1769\t86\t1\t1\t9\t9\t69\t769\t1769\t1769\t1769\t138\t139\tBQAAAA\tIDAAAA\tOOOOxx\n1454\t87\t0\t2\t4\t14\t54\t454\t1454\t1454\t1454\t108\t109\tYDAAAA\tJDAAAA\tVVVVxx\n6877\t88\t1\t1\t7\t17\t77\t877\t877\t1877\t6877\t154\t155\tNEAAAA\tKDAAAA\tAAAAxx\n6501\t89\t1\t1\t1\t1\t1\t501\t501\t1501\t6501\t2\t3\tBQAAAA\tLDAAAA\tHHHHxx\n934\t90\t0\t2\t4\t14\t34\t934\t934\t934\t934\t68\t69\tYJAAAA\tMDAAAA\tOOOOxx\n4075\t91\t1\t3\t5\t15\t75\t75\t75\t4075\t4075\t150\t151\tTAAAAA\tNDAAAA\tVVVVxx\n3180\t92\t0\t0\t0\t0\t80\t180\t1180\t3180\t3180\t160\t161\tISAAAA\tODAAAA\tAAAAxx\n7787\t93\t1\t3\t7\t7\t87\t787\t1787\t2787\t7787\t174\t175\tNNAAAA\tPDAAAA\tHHHHxx\n6401\t94\t1\t1\t1\t1\t1\t401\t401\t1401\t6401\t2\t3\tFMAAAA\tQDAAAA\tOOOOxx\n4244\t95\t0\t0\t4\t4\t44\t244\t244\t4244\t4244\t88\t89\tGHAAAA\tRDAAAA\tVVVVxx\n4591\t96\t1\t3\t1\t11\t91\t591\t591\t4591\t4591\t182\t183\tPUAAAA\tSDAAAA\tAAAAxx\n4113\t97\t1\t1\t3\t13\t13\t113\t113\t4113\t4113\t26\t27\tFCAAAA\tTDAAAA\tHHHHxx\n5925\t98\t1\t1\t5\t5\t25\t925\t1925\t925\t5925\t50\t51\tXTAAAA\tUDAAAA\tOOOOxx\n1987\t99\t1\t3\t7\t7\t87\t987\t1987\t1987\t1987\t174\t175\tLYAAAA\tVDAAAA\tVVVVxx\n8248\t100\t0\t0\t8\t8\t48\t248\t248\t3248\t8248\t96\t97\tGFAAAA\tWDAAAA\tAAAAxx\n4151\t101\t1\t3\t1\t11\t51\t151\t151\t4151\t4151\t102\t103\tRDAAAA\tXDAAAA\tHHHHxx\n8670\t102\t0\t2\t0\t10\t70\t670\t670\t3670\t8670\t140\t141\tMVAAAA\tYDAAAA\tOOOOxx\n6194\t103\t0\t2\t4\t14\t94\t194\t194\t1194\t6194\t188\t189\tGEAAAA\tZDAAAA\tVVVVxx\n88\t104\t0\t0\t8\t8\t88\t88\t88\t88\t88\t176\t177\tKDAAAA\tAEAAAA\tAAAAxx\n4058\t105\t0\t2\t8\t18\t58\t58\t58\t4058\t4058\t116\t117\tCAAAAA\tBEAAAA\tHHHHxx\n2742\t106\t0\t2\t2\t2\t42\t742\t742\t2742\t2742\t84\t85\tMBAAAA\tCEAAAA\tOOOOxx\n8275\t107\t1\t3\t5\t15\t75\t275\t275\t3275\t8275\t150\t151\tHGAAAA\tDEAAAA\tVVVVxx\n4258\t108\t0\t2\t8\t18\t58\t258\t258\t4258\t4258\t116\t117\tUHAAAA\tEEAAAA\tAAAAxx\n6129\t109\t1\t1\t9\t9\t29\t129\t129\t1129\t6129\t58\t59\tTBAAAA\tFEAAAA\tHHHHxx\n7243\t110\t1\t3\t3\t3\t43\t243\t1243\t2243\t7243\t86\t87\tPSAAAA\tGEAAAA\tOOOOxx\n2392\t111\t0\t0\t2\t12\t92\t392\t392\t2392\t2392\t184\t185\tAOAAAA\tHEAAAA\tVVVVxx\n9853\t112\t1\t1\t3\t13\t53\t853\t1853\t4853\t9853\t106\t107\tZOAAAA\tIEAAAA\tAAAAxx\n6064\t113\t0\t0\t4\t4\t64\t64\t64\t1064\t6064\t128\t129\tGZAAAA\tJEAAAA\tHHHHxx\n4391\t114\t1\t3\t1\t11\t91\t391\t391\t4391\t4391\t182\t183\tXMAAAA\tKEAAAA\tOOOOxx\n726\t115\t0\t2\t6\t6\t26\t726\t726\t726\t726\t52\t53\tYBAAAA\tLEAAAA\tVVVVxx\n6957\t116\t1\t1\t7\t17\t57\t957\t957\t1957\t6957\t114\t115\tPHAAAA\tMEAAAA\tAAAAxx\n3853\t117\t1\t1\t3\t13\t53\t853\t1853\t3853\t3853\t106\t107\tFSAAAA\tNEAAAA\tHHHHxx\n4524\t118\t0\t0\t4\t4\t24\t524\t524\t4524\t4524\t48\t49\tASAAAA\tOEAAAA\tOOOOxx\n5330\t119\t0\t2\t0\t10\t30\t330\t1330\t330\t5330\t60\t61\tAXAAAA\tPEAAAA\tVVVVxx\n6671\t120\t1\t3\t1\t11\t71\t671\t671\t1671\t6671\t142\t143\tPWAAAA\tQEAAAA\tAAAAxx\n5314\t121\t0\t2\t4\t14\t14\t314\t1314\t314\t5314\t28\t29\tKWAAAA\tREAAAA\tHHHHxx\n9202\t122\t0\t2\t2\t2\t2\t202\t1202\t4202\t9202\t4\t5\tYPAAAA\tSEAAAA\tOOOOxx\n4596\t123\t0\t0\t6\t16\t96\t596\t596\t4596\t4596\t192\t193\tUUAAAA\tTEAAAA\tVVVVxx\n8951\t124\t1\t3\t1\t11\t51\t951\t951\t3951\t8951\t102\t103\tHGAAAA\tUEAAAA\tAAAAxx\n9902\t125\t0\t2\t2\t2\t2\t902\t1902\t4902\t9902\t4\t5\tWQAAAA\tVEAAAA\tHHHHxx\n1440\t126\t0\t0\t0\t0\t40\t440\t1440\t1440\t1440\t80\t81\tKDAAAA\tWEAAAA\tOOOOxx\n5339\t127\t1\t3\t9\t19\t39\t339\t1339\t339\t5339\t78\t79\tJXAAAA\tXEAAAA\tVVVVxx\n3371\t128\t1\t3\t1\t11\t71\t371\t1371\t3371\t3371\t142\t143\tRZAAAA\tYEAAAA\tAAAAxx\n4467\t129\t1\t3\t7\t7\t67\t467\t467\t4467\t4467\t134\t135\tVPAAAA\tZEAAAA\tHHHHxx\n6216\t130\t0\t0\t6\t16\t16\t216\t216\t1216\t6216\t32\t33\tCFAAAA\tAFAAAA\tOOOOxx\n5364\t131\t0\t0\t4\t4\t64\t364\t1364\t364\t5364\t128\t129\tIYAAAA\tBFAAAA\tVVVVxx\n7547\t132\t1\t3\t7\t7\t47\t547\t1547\t2547\t7547\t94\t95\tHEAAAA\tCFAAAA\tAAAAxx\n4338\t133\t0\t2\t8\t18\t38\t338\t338\t4338\t4338\t76\t77\tWKAAAA\tDFAAAA\tHHHHxx\n3481\t134\t1\t1\t1\t1\t81\t481\t1481\t3481\t3481\t162\t163\tXDAAAA\tEFAAAA\tOOOOxx\n826\t135\t0\t2\t6\t6\t26\t826\t826\t826\t826\t52\t53\tUFAAAA\tFFAAAA\tVVVVxx\n3647\t136\t1\t3\t7\t7\t47\t647\t1647\t3647\t3647\t94\t95\tHKAAAA\tGFAAAA\tAAAAxx\n3337\t137\t1\t1\t7\t17\t37\t337\t1337\t3337\t3337\t74\t75\tJYAAAA\tHFAAAA\tHHHHxx\n3591\t138\t1\t3\t1\t11\t91\t591\t1591\t3591\t3591\t182\t183\tDIAAAA\tIFAAAA\tOOOOxx\n7192\t139\t0\t0\t2\t12\t92\t192\t1192\t2192\t7192\t184\t185\tQQAAAA\tJFAAAA\tVVVVxx\n1078\t140\t0\t2\t8\t18\t78\t78\t1078\t1078\t1078\t156\t157\tMPAAAA\tKFAAAA\tAAAAxx\n1310\t141\t0\t2\t0\t10\t10\t310\t1310\t1310\t1310\t20\t21\tKYAAAA\tLFAAAA\tHHHHxx\n9642\t142\t0\t2\t2\t2\t42\t642\t1642\t4642\t9642\t84\t85\tWGAAAA\tMFAAAA\tOOOOxx\n39\t143\t1\t3\t9\t19\t39\t39\t39\t39\t39\t78\t79\tNBAAAA\tNFAAAA\tVVVVxx\n8682\t144\t0\t2\t2\t2\t82\t682\t682\t3682\t8682\t164\t165\tYVAAAA\tOFAAAA\tAAAAxx\n1794\t145\t0\t2\t4\t14\t94\t794\t1794\t1794\t1794\t188\t189\tARAAAA\tPFAAAA\tHHHHxx\n5630\t146\t0\t2\t0\t10\t30\t630\t1630\t630\t5630\t60\t61\tOIAAAA\tQFAAAA\tOOOOxx\n6748\t147\t0\t0\t8\t8\t48\t748\t748\t1748\t6748\t96\t97\tOZAAAA\tRFAAAA\tVVVVxx\n3766\t148\t0\t2\t6\t6\t66\t766\t1766\t3766\t3766\t132\t133\tWOAAAA\tSFAAAA\tAAAAxx\n6403\t149\t1\t3\t3\t3\t3\t403\t403\t1403\t6403\t6\t7\tHMAAAA\tTFAAAA\tHHHHxx\n175\t150\t1\t3\t5\t15\t75\t175\t175\t175\t175\t150\t151\tTGAAAA\tUFAAAA\tOOOOxx\n2179\t151\t1\t3\t9\t19\t79\t179\t179\t2179\t2179\t158\t159\tVFAAAA\tVFAAAA\tVVVVxx\n7897\t152\t1\t1\t7\t17\t97\t897\t1897\t2897\t7897\t194\t195\tTRAAAA\tWFAAAA\tAAAAxx\n2760\t153\t0\t0\t0\t0\t60\t760\t760\t2760\t2760\t120\t121\tECAAAA\tXFAAAA\tHHHHxx\n1675\t154\t1\t3\t5\t15\t75\t675\t1675\t1675\t1675\t150\t151\tLMAAAA\tYFAAAA\tOOOOxx\n2564\t155\t0\t0\t4\t4\t64\t564\t564\t2564\t2564\t128\t129\tQUAAAA\tZFAAAA\tVVVVxx\n157\t156\t1\t1\t7\t17\t57\t157\t157\t157\t157\t114\t115\tBGAAAA\tAGAAAA\tAAAAxx\n8779\t157\t1\t3\t9\t19\t79\t779\t779\t3779\t8779\t158\t159\tRZAAAA\tBGAAAA\tHHHHxx\n9591\t158\t1\t3\t1\t11\t91\t591\t1591\t4591\t9591\t182\t183\tXEAAAA\tCGAAAA\tOOOOxx\n8732\t159\t0\t0\t2\t12\t32\t732\t732\t3732\t8732\t64\t65\tWXAAAA\tDGAAAA\tVVVVxx\n139\t160\t1\t3\t9\t19\t39\t139\t139\t139\t139\t78\t79\tJFAAAA\tEGAAAA\tAAAAxx\n5372\t161\t0\t0\t2\t12\t72\t372\t1372\t372\t5372\t144\t145\tQYAAAA\tFGAAAA\tHHHHxx\n1278\t162\t0\t2\t8\t18\t78\t278\t1278\t1278\t1278\t156\t157\tEXAAAA\tGGAAAA\tOOOOxx\n4697\t163\t1\t1\t7\t17\t97\t697\t697\t4697\t4697\t194\t195\tRYAAAA\tHGAAAA\tVVVVxx\n8610\t164\t0\t2\t0\t10\t10\t610\t610\t3610\t8610\t20\t21\tETAAAA\tIGAAAA\tAAAAxx\n8180\t165\t0\t0\t0\t0\t80\t180\t180\t3180\t8180\t160\t161\tQCAAAA\tJGAAAA\tHHHHxx\n2399\t166\t1\t3\t9\t19\t99\t399\t399\t2399\t2399\t198\t199\tHOAAAA\tKGAAAA\tOOOOxx\n615\t167\t1\t3\t5\t15\t15\t615\t615\t615\t615\t30\t31\tRXAAAA\tLGAAAA\tVVVVxx\n7629\t168\t1\t1\t9\t9\t29\t629\t1629\t2629\t7629\t58\t59\tLHAAAA\tMGAAAA\tAAAAxx\n7628\t169\t0\t0\t8\t8\t28\t628\t1628\t2628\t7628\t56\t57\tKHAAAA\tNGAAAA\tHHHHxx\n4659\t170\t1\t3\t9\t19\t59\t659\t659\t4659\t4659\t118\t119\tFXAAAA\tOGAAAA\tOOOOxx\n5865\t171\t1\t1\t5\t5\t65\t865\t1865\t865\t5865\t130\t131\tPRAAAA\tPGAAAA\tVVVVxx\n3973\t172\t1\t1\t3\t13\t73\t973\t1973\t3973\t3973\t146\t147\tVWAAAA\tQGAAAA\tAAAAxx\n552\t173\t0\t0\t2\t12\t52\t552\t552\t552\t552\t104\t105\tGVAAAA\tRGAAAA\tHHHHxx\n708\t174\t0\t0\t8\t8\t8\t708\t708\t708\t708\t16\t17\tGBAAAA\tSGAAAA\tOOOOxx\n3550\t175\t0\t2\t0\t10\t50\t550\t1550\t3550\t3550\t100\t101\tOGAAAA\tTGAAAA\tVVVVxx\n5547\t176\t1\t3\t7\t7\t47\t547\t1547\t547\t5547\t94\t95\tJFAAAA\tUGAAAA\tAAAAxx\n489\t177\t1\t1\t9\t9\t89\t489\t489\t489\t489\t178\t179\tVSAAAA\tVGAAAA\tHHHHxx\n3794\t178\t0\t2\t4\t14\t94\t794\t1794\t3794\t3794\t188\t189\tYPAAAA\tWGAAAA\tOOOOxx\n9479\t179\t1\t3\t9\t19\t79\t479\t1479\t4479\t9479\t158\t159\tPAAAAA\tXGAAAA\tVVVVxx\n6435\t180\t1\t3\t5\t15\t35\t435\t435\t1435\t6435\t70\t71\tNNAAAA\tYGAAAA\tAAAAxx\n5120\t181\t0\t0\t0\t0\t20\t120\t1120\t120\t5120\t40\t41\tYOAAAA\tZGAAAA\tHHHHxx\n3615\t182\t1\t3\t5\t15\t15\t615\t1615\t3615\t3615\t30\t31\tBJAAAA\tAHAAAA\tOOOOxx\n8399\t183\t1\t3\t9\t19\t99\t399\t399\t3399\t8399\t198\t199\tBLAAAA\tBHAAAA\tVVVVxx\n2155\t184\t1\t3\t5\t15\t55\t155\t155\t2155\t2155\t110\t111\tXEAAAA\tCHAAAA\tAAAAxx\n6690\t185\t0\t2\t0\t10\t90\t690\t690\t1690\t6690\t180\t181\tIXAAAA\tDHAAAA\tHHHHxx\n1683\t186\t1\t3\t3\t3\t83\t683\t1683\t1683\t1683\t166\t167\tTMAAAA\tEHAAAA\tOOOOxx\n6302\t187\t0\t2\t2\t2\t2\t302\t302\t1302\t6302\t4\t5\tKIAAAA\tFHAAAA\tVVVVxx\n516\t188\t0\t0\t6\t16\t16\t516\t516\t516\t516\t32\t33\tWTAAAA\tGHAAAA\tAAAAxx\n3901\t189\t1\t1\t1\t1\t1\t901\t1901\t3901\t3901\t2\t3\tBUAAAA\tHHAAAA\tHHHHxx\n6938\t190\t0\t2\t8\t18\t38\t938\t938\t1938\t6938\t76\t77\tWGAAAA\tIHAAAA\tOOOOxx\n7484\t191\t0\t0\t4\t4\t84\t484\t1484\t2484\t7484\t168\t169\tWBAAAA\tJHAAAA\tVVVVxx\n7424\t192\t0\t0\t4\t4\t24\t424\t1424\t2424\t7424\t48\t49\tOZAAAA\tKHAAAA\tAAAAxx\n9410\t193\t0\t2\t0\t10\t10\t410\t1410\t4410\t9410\t20\t21\tYXAAAA\tLHAAAA\tHHHHxx\n1714\t194\t0\t2\t4\t14\t14\t714\t1714\t1714\t1714\t28\t29\tYNAAAA\tMHAAAA\tOOOOxx\n8278\t195\t0\t2\t8\t18\t78\t278\t278\t3278\t8278\t156\t157\tKGAAAA\tNHAAAA\tVVVVxx\n3158\t196\t0\t2\t8\t18\t58\t158\t1158\t3158\t3158\t116\t117\tMRAAAA\tOHAAAA\tAAAAxx\n2511\t197\t1\t3\t1\t11\t11\t511\t511\t2511\t2511\t22\t23\tPSAAAA\tPHAAAA\tHHHHxx\n2912\t198\t0\t0\t2\t12\t12\t912\t912\t2912\t2912\t24\t25\tAIAAAA\tQHAAAA\tOOOOxx\n2648\t199\t0\t0\t8\t8\t48\t648\t648\t2648\t2648\t96\t97\tWXAAAA\tRHAAAA\tVVVVxx\n9385\t200\t1\t1\t5\t5\t85\t385\t1385\t4385\t9385\t170\t171\tZWAAAA\tSHAAAA\tAAAAxx\n7545\t201\t1\t1\t5\t5\t45\t545\t1545\t2545\t7545\t90\t91\tFEAAAA\tTHAAAA\tHHHHxx\n8407\t202\t1\t3\t7\t7\t7\t407\t407\t3407\t8407\t14\t15\tJLAAAA\tUHAAAA\tOOOOxx\n5893\t203\t1\t1\t3\t13\t93\t893\t1893\t893\t5893\t186\t187\tRSAAAA\tVHAAAA\tVVVVxx\n7049\t204\t1\t1\t9\t9\t49\t49\t1049\t2049\t7049\t98\t99\tDLAAAA\tWHAAAA\tAAAAxx\n6812\t205\t0\t0\t2\t12\t12\t812\t812\t1812\t6812\t24\t25\tACAAAA\tXHAAAA\tHHHHxx\n3649\t206\t1\t1\t9\t9\t49\t649\t1649\t3649\t3649\t98\t99\tJKAAAA\tYHAAAA\tOOOOxx\n9275\t207\t1\t3\t5\t15\t75\t275\t1275\t4275\t9275\t150\t151\tTSAAAA\tZHAAAA\tVVVVxx\n1179\t208\t1\t3\t9\t19\t79\t179\t1179\t1179\t1179\t158\t159\tJTAAAA\tAIAAAA\tAAAAxx\n969\t209\t1\t1\t9\t9\t69\t969\t969\t969\t969\t138\t139\tHLAAAA\tBIAAAA\tHHHHxx\n7920\t210\t0\t0\t0\t0\t20\t920\t1920\t2920\t7920\t40\t41\tQSAAAA\tCIAAAA\tOOOOxx\n998\t211\t0\t2\t8\t18\t98\t998\t998\t998\t998\t196\t197\tKMAAAA\tDIAAAA\tVVVVxx\n3958\t212\t0\t2\t8\t18\t58\t958\t1958\t3958\t3958\t116\t117\tGWAAAA\tEIAAAA\tAAAAxx\n6052\t213\t0\t0\t2\t12\t52\t52\t52\t1052\t6052\t104\t105\tUYAAAA\tFIAAAA\tHHHHxx\n8791\t214\t1\t3\t1\t11\t91\t791\t791\t3791\t8791\t182\t183\tDAAAAA\tGIAAAA\tOOOOxx\n5191\t215\t1\t3\t1\t11\t91\t191\t1191\t191\t5191\t182\t183\tRRAAAA\tHIAAAA\tVVVVxx\n4267\t216\t1\t3\t7\t7\t67\t267\t267\t4267\t4267\t134\t135\tDIAAAA\tIIAAAA\tAAAAxx\n2829\t217\t1\t1\t9\t9\t29\t829\t829\t2829\t2829\t58\t59\tVEAAAA\tJIAAAA\tHHHHxx\n6396\t218\t0\t0\t6\t16\t96\t396\t396\t1396\t6396\t192\t193\tAMAAAA\tKIAAAA\tOOOOxx\n9413\t219\t1\t1\t3\t13\t13\t413\t1413\t4413\t9413\t26\t27\tBYAAAA\tLIAAAA\tVVVVxx\n614\t220\t0\t2\t4\t14\t14\t614\t614\t614\t614\t28\t29\tQXAAAA\tMIAAAA\tAAAAxx\n4660\t221\t0\t0\t0\t0\t60\t660\t660\t4660\t4660\t120\t121\tGXAAAA\tNIAAAA\tHHHHxx\n8834\t222\t0\t2\t4\t14\t34\t834\t834\t3834\t8834\t68\t69\tUBAAAA\tOIAAAA\tOOOOxx\n2767\t223\t1\t3\t7\t7\t67\t767\t767\t2767\t2767\t134\t135\tLCAAAA\tPIAAAA\tVVVVxx\n2444\t224\t0\t0\t4\t4\t44\t444\t444\t2444\t2444\t88\t89\tAQAAAA\tQIAAAA\tAAAAxx\n4129\t225\t1\t1\t9\t9\t29\t129\t129\t4129\t4129\t58\t59\tVCAAAA\tRIAAAA\tHHHHxx\n3394\t226\t0\t2\t4\t14\t94\t394\t1394\t3394\t3394\t188\t189\tOAAAAA\tSIAAAA\tOOOOxx\n2705\t227\t1\t1\t5\t5\t5\t705\t705\t2705\t2705\t10\t11\tBAAAAA\tTIAAAA\tVVVVxx\n8499\t228\t1\t3\t9\t19\t99\t499\t499\t3499\t8499\t198\t199\tXOAAAA\tUIAAAA\tAAAAxx\n8852\t229\t0\t0\t2\t12\t52\t852\t852\t3852\t8852\t104\t105\tMCAAAA\tVIAAAA\tHHHHxx\n6174\t230\t0\t2\t4\t14\t74\t174\t174\t1174\t6174\t148\t149\tMDAAAA\tWIAAAA\tOOOOxx\n750\t231\t0\t2\t0\t10\t50\t750\t750\t750\t750\t100\t101\tWCAAAA\tXIAAAA\tVVVVxx\n8164\t232\t0\t0\t4\t4\t64\t164\t164\t3164\t8164\t128\t129\tACAAAA\tYIAAAA\tAAAAxx\n4930\t233\t0\t2\t0\t10\t30\t930\t930\t4930\t4930\t60\t61\tQHAAAA\tZIAAAA\tHHHHxx\n9904\t234\t0\t0\t4\t4\t4\t904\t1904\t4904\t9904\t8\t9\tYQAAAA\tAJAAAA\tOOOOxx\n7378\t235\t0\t2\t8\t18\t78\t378\t1378\t2378\t7378\t156\t157\tUXAAAA\tBJAAAA\tVVVVxx\n2927\t236\t1\t3\t7\t7\t27\t927\t927\t2927\t2927\t54\t55\tPIAAAA\tCJAAAA\tAAAAxx\n7155\t237\t1\t3\t5\t15\t55\t155\t1155\t2155\t7155\t110\t111\tFPAAAA\tDJAAAA\tHHHHxx\n1302\t238\t0\t2\t2\t2\t2\t302\t1302\t1302\t1302\t4\t5\tCYAAAA\tEJAAAA\tOOOOxx\n5904\t239\t0\t0\t4\t4\t4\t904\t1904\t904\t5904\t8\t9\tCTAAAA\tFJAAAA\tVVVVxx\n9687\t240\t1\t3\t7\t7\t87\t687\t1687\t4687\t9687\t174\t175\tPIAAAA\tGJAAAA\tAAAAxx\n3553\t241\t1\t1\t3\t13\t53\t553\t1553\t3553\t3553\t106\t107\tRGAAAA\tHJAAAA\tHHHHxx\n4447\t242\t1\t3\t7\t7\t47\t447\t447\t4447\t4447\t94\t95\tBPAAAA\tIJAAAA\tOOOOxx\n6878\t243\t0\t2\t8\t18\t78\t878\t878\t1878\t6878\t156\t157\tOEAAAA\tJJAAAA\tVVVVxx\n9470\t244\t0\t2\t0\t10\t70\t470\t1470\t4470\t9470\t140\t141\tGAAAAA\tKJAAAA\tAAAAxx\n9735\t245\t1\t3\t5\t15\t35\t735\t1735\t4735\t9735\t70\t71\tLKAAAA\tLJAAAA\tHHHHxx\n5967\t246\t1\t3\t7\t7\t67\t967\t1967\t967\t5967\t134\t135\tNVAAAA\tMJAAAA\tOOOOxx\n6601\t247\t1\t1\t1\t1\t1\t601\t601\t1601\t6601\t2\t3\tXTAAAA\tNJAAAA\tVVVVxx\n7631\t248\t1\t3\t1\t11\t31\t631\t1631\t2631\t7631\t62\t63\tNHAAAA\tOJAAAA\tAAAAxx\n3559\t249\t1\t3\t9\t19\t59\t559\t1559\t3559\t3559\t118\t119\tXGAAAA\tPJAAAA\tHHHHxx\n2247\t250\t1\t3\t7\t7\t47\t247\t247\t2247\t2247\t94\t95\tLIAAAA\tQJAAAA\tOOOOxx\n9649\t251\t1\t1\t9\t9\t49\t649\t1649\t4649\t9649\t98\t99\tDHAAAA\tRJAAAA\tVVVVxx\n808\t252\t0\t0\t8\t8\t8\t808\t808\t808\t808\t16\t17\tCFAAAA\tSJAAAA\tAAAAxx\n240\t253\t0\t0\t0\t0\t40\t240\t240\t240\t240\t80\t81\tGJAAAA\tTJAAAA\tHHHHxx\n5031\t254\t1\t3\t1\t11\t31\t31\t1031\t31\t5031\t62\t63\tNLAAAA\tUJAAAA\tOOOOxx\n9563\t255\t1\t3\t3\t3\t63\t563\t1563\t4563\t9563\t126\t127\tVDAAAA\tVJAAAA\tVVVVxx\n5656\t256\t0\t0\t6\t16\t56\t656\t1656\t656\t5656\t112\t113\tOJAAAA\tWJAAAA\tAAAAxx\n3886\t257\t0\t2\t6\t6\t86\t886\t1886\t3886\t3886\t172\t173\tMTAAAA\tXJAAAA\tHHHHxx\n2431\t258\t1\t3\t1\t11\t31\t431\t431\t2431\t2431\t62\t63\tNPAAAA\tYJAAAA\tOOOOxx\n5560\t259\t0\t0\t0\t0\t60\t560\t1560\t560\t5560\t120\t121\tWFAAAA\tZJAAAA\tVVVVxx\n9065\t260\t1\t1\t5\t5\t65\t65\t1065\t4065\t9065\t130\t131\tRKAAAA\tAKAAAA\tAAAAxx\n8130\t261\t0\t2\t0\t10\t30\t130\t130\t3130\t8130\t60\t61\tSAAAAA\tBKAAAA\tHHHHxx\n4054\t262\t0\t2\t4\t14\t54\t54\t54\t4054\t4054\t108\t109\tYZAAAA\tCKAAAA\tOOOOxx\n873\t263\t1\t1\t3\t13\t73\t873\t873\t873\t873\t146\t147\tPHAAAA\tDKAAAA\tVVVVxx\n3092\t264\t0\t0\t2\t12\t92\t92\t1092\t3092\t3092\t184\t185\tYOAAAA\tEKAAAA\tAAAAxx\n6697\t265\t1\t1\t7\t17\t97\t697\t697\t1697\t6697\t194\t195\tPXAAAA\tFKAAAA\tHHHHxx\n2452\t266\t0\t0\t2\t12\t52\t452\t452\t2452\t2452\t104\t105\tIQAAAA\tGKAAAA\tOOOOxx\n7867\t267\t1\t3\t7\t7\t67\t867\t1867\t2867\t7867\t134\t135\tPQAAAA\tHKAAAA\tVVVVxx\n3753\t268\t1\t1\t3\t13\t53\t753\t1753\t3753\t3753\t106\t107\tJOAAAA\tIKAAAA\tAAAAxx\n7834\t269\t0\t2\t4\t14\t34\t834\t1834\t2834\t7834\t68\t69\tIPAAAA\tJKAAAA\tHHHHxx\n5846\t270\t0\t2\t6\t6\t46\t846\t1846\t846\t5846\t92\t93\tWQAAAA\tKKAAAA\tOOOOxx\n7604\t271\t0\t0\t4\t4\t4\t604\t1604\t2604\t7604\t8\t9\tMGAAAA\tLKAAAA\tVVVVxx\n3452\t272\t0\t0\t2\t12\t52\t452\t1452\t3452\t3452\t104\t105\tUCAAAA\tMKAAAA\tAAAAxx\n4788\t273\t0\t0\t8\t8\t88\t788\t788\t4788\t4788\t176\t177\tECAAAA\tNKAAAA\tHHHHxx\n8600\t274\t0\t0\t0\t0\t0\t600\t600\t3600\t8600\t0\t1\tUSAAAA\tOKAAAA\tOOOOxx\n8511\t275\t1\t3\t1\t11\t11\t511\t511\t3511\t8511\t22\t23\tJPAAAA\tPKAAAA\tVVVVxx\n4452\t276\t0\t0\t2\t12\t52\t452\t452\t4452\t4452\t104\t105\tGPAAAA\tQKAAAA\tAAAAxx\n1709\t277\t1\t1\t9\t9\t9\t709\t1709\t1709\t1709\t18\t19\tTNAAAA\tRKAAAA\tHHHHxx\n3440\t278\t0\t0\t0\t0\t40\t440\t1440\t3440\t3440\t80\t81\tICAAAA\tSKAAAA\tOOOOxx\n9188\t279\t0\t0\t8\t8\t88\t188\t1188\t4188\t9188\t176\t177\tKPAAAA\tTKAAAA\tVVVVxx\n3058\t280\t0\t2\t8\t18\t58\t58\t1058\t3058\t3058\t116\t117\tQNAAAA\tUKAAAA\tAAAAxx\n5821\t281\t1\t1\t1\t1\t21\t821\t1821\t821\t5821\t42\t43\tXPAAAA\tVKAAAA\tHHHHxx\n3428\t282\t0\t0\t8\t8\t28\t428\t1428\t3428\t3428\t56\t57\tWBAAAA\tWKAAAA\tOOOOxx\n3581\t283\t1\t1\t1\t1\t81\t581\t1581\t3581\t3581\t162\t163\tTHAAAA\tXKAAAA\tVVVVxx\n7523\t284\t1\t3\t3\t3\t23\t523\t1523\t2523\t7523\t46\t47\tJDAAAA\tYKAAAA\tAAAAxx\n3131\t285\t1\t3\t1\t11\t31\t131\t1131\t3131\t3131\t62\t63\tLQAAAA\tZKAAAA\tHHHHxx\n2404\t286\t0\t0\t4\t4\t4\t404\t404\t2404\t2404\t8\t9\tMOAAAA\tALAAAA\tOOOOxx\n5453\t287\t1\t1\t3\t13\t53\t453\t1453\t453\t5453\t106\t107\tTBAAAA\tBLAAAA\tVVVVxx\n1599\t288\t1\t3\t9\t19\t99\t599\t1599\t1599\t1599\t198\t199\tNJAAAA\tCLAAAA\tAAAAxx\n7081\t289\t1\t1\t1\t1\t81\t81\t1081\t2081\t7081\t162\t163\tJMAAAA\tDLAAAA\tHHHHxx\n1750\t290\t0\t2\t0\t10\t50\t750\t1750\t1750\t1750\t100\t101\tIPAAAA\tELAAAA\tOOOOxx\n5085\t291\t1\t1\t5\t5\t85\t85\t1085\t85\t5085\t170\t171\tPNAAAA\tFLAAAA\tVVVVxx\n9777\t292\t1\t1\t7\t17\t77\t777\t1777\t4777\t9777\t154\t155\tBMAAAA\tGLAAAA\tAAAAxx\n574\t293\t0\t2\t4\t14\t74\t574\t574\t574\t574\t148\t149\tCWAAAA\tHLAAAA\tHHHHxx\n5984\t294\t0\t0\t4\t4\t84\t984\t1984\t984\t5984\t168\t169\tEWAAAA\tILAAAA\tOOOOxx\n7039\t295\t1\t3\t9\t19\t39\t39\t1039\t2039\t7039\t78\t79\tTKAAAA\tJLAAAA\tVVVVxx\n7143\t296\t1\t3\t3\t3\t43\t143\t1143\t2143\t7143\t86\t87\tTOAAAA\tKLAAAA\tAAAAxx\n5702\t297\t0\t2\t2\t2\t2\t702\t1702\t702\t5702\t4\t5\tILAAAA\tLLAAAA\tHHHHxx\n362\t298\t0\t2\t2\t2\t62\t362\t362\t362\t362\t124\t125\tYNAAAA\tMLAAAA\tOOOOxx\n6997\t299\t1\t1\t7\t17\t97\t997\t997\t1997\t6997\t194\t195\tDJAAAA\tNLAAAA\tVVVVxx\n2529\t300\t1\t1\t9\t9\t29\t529\t529\t2529\t2529\t58\t59\tHTAAAA\tOLAAAA\tAAAAxx\n6319\t301\t1\t3\t9\t19\t19\t319\t319\t1319\t6319\t38\t39\tBJAAAA\tPLAAAA\tHHHHxx\n954\t302\t0\t2\t4\t14\t54\t954\t954\t954\t954\t108\t109\tSKAAAA\tQLAAAA\tOOOOxx\n3413\t303\t1\t1\t3\t13\t13\t413\t1413\t3413\t3413\t26\t27\tHBAAAA\tRLAAAA\tVVVVxx\n9081\t304\t1\t1\t1\t1\t81\t81\t1081\t4081\t9081\t162\t163\tHLAAAA\tSLAAAA\tAAAAxx\n5599\t305\t1\t3\t9\t19\t99\t599\t1599\t599\t5599\t198\t199\tJHAAAA\tTLAAAA\tHHHHxx\n4772\t306\t0\t0\t2\t12\t72\t772\t772\t4772\t4772\t144\t145\tOBAAAA\tULAAAA\tOOOOxx\n1124\t307\t0\t0\t4\t4\t24\t124\t1124\t1124\t1124\t48\t49\tGRAAAA\tVLAAAA\tVVVVxx\n7793\t308\t1\t1\t3\t13\t93\t793\t1793\t2793\t7793\t186\t187\tTNAAAA\tWLAAAA\tAAAAxx\n4201\t309\t1\t1\t1\t1\t1\t201\t201\t4201\t4201\t2\t3\tPFAAAA\tXLAAAA\tHHHHxx\n7015\t310\t1\t3\t5\t15\t15\t15\t1015\t2015\t7015\t30\t31\tVJAAAA\tYLAAAA\tOOOOxx\n5936\t311\t0\t0\t6\t16\t36\t936\t1936\t936\t5936\t72\t73\tIUAAAA\tZLAAAA\tVVVVxx\n4625\t312\t1\t1\t5\t5\t25\t625\t625\t4625\t4625\t50\t51\tXVAAAA\tAMAAAA\tAAAAxx\n4989\t313\t1\t1\t9\t9\t89\t989\t989\t4989\t4989\t178\t179\tXJAAAA\tBMAAAA\tHHHHxx\n4949\t314\t1\t1\t9\t9\t49\t949\t949\t4949\t4949\t98\t99\tJIAAAA\tCMAAAA\tOOOOxx\n6273\t315\t1\t1\t3\t13\t73\t273\t273\t1273\t6273\t146\t147\tHHAAAA\tDMAAAA\tVVVVxx\n4478\t316\t0\t2\t8\t18\t78\t478\t478\t4478\t4478\t156\t157\tGQAAAA\tEMAAAA\tAAAAxx\n8854\t317\t0\t2\t4\t14\t54\t854\t854\t3854\t8854\t108\t109\tOCAAAA\tFMAAAA\tHHHHxx\n2105\t318\t1\t1\t5\t5\t5\t105\t105\t2105\t2105\t10\t11\tZCAAAA\tGMAAAA\tOOOOxx\n8345\t319\t1\t1\t5\t5\t45\t345\t345\t3345\t8345\t90\t91\tZIAAAA\tHMAAAA\tVVVVxx\n1941\t320\t1\t1\t1\t1\t41\t941\t1941\t1941\t1941\t82\t83\tRWAAAA\tIMAAAA\tAAAAxx\n1765\t321\t1\t1\t5\t5\t65\t765\t1765\t1765\t1765\t130\t131\tXPAAAA\tJMAAAA\tHHHHxx\n9592\t322\t0\t0\t2\t12\t92\t592\t1592\t4592\t9592\t184\t185\tYEAAAA\tKMAAAA\tOOOOxx\n1694\t323\t0\t2\t4\t14\t94\t694\t1694\t1694\t1694\t188\t189\tENAAAA\tLMAAAA\tVVVVxx\n8940\t324\t0\t0\t0\t0\t40\t940\t940\t3940\t8940\t80\t81\tWFAAAA\tMMAAAA\tAAAAxx\n7264\t325\t0\t0\t4\t4\t64\t264\t1264\t2264\t7264\t128\t129\tKTAAAA\tNMAAAA\tHHHHxx\n4699\t326\t1\t3\t9\t19\t99\t699\t699\t4699\t4699\t198\t199\tTYAAAA\tOMAAAA\tOOOOxx\n4541\t327\t1\t1\t1\t1\t41\t541\t541\t4541\t4541\t82\t83\tRSAAAA\tPMAAAA\tVVVVxx\n5768\t328\t0\t0\t8\t8\t68\t768\t1768\t768\t5768\t136\t137\tWNAAAA\tQMAAAA\tAAAAxx\n6183\t329\t1\t3\t3\t3\t83\t183\t183\t1183\t6183\t166\t167\tVDAAAA\tRMAAAA\tHHHHxx\n7457\t330\t1\t1\t7\t17\t57\t457\t1457\t2457\t7457\t114\t115\tVAAAAA\tSMAAAA\tOOOOxx\n7317\t331\t1\t1\t7\t17\t17\t317\t1317\t2317\t7317\t34\t35\tLVAAAA\tTMAAAA\tVVVVxx\n1944\t332\t0\t0\t4\t4\t44\t944\t1944\t1944\t1944\t88\t89\tUWAAAA\tUMAAAA\tAAAAxx\n665\t333\t1\t1\t5\t5\t65\t665\t665\t665\t665\t130\t131\tPZAAAA\tVMAAAA\tHHHHxx\n5974\t334\t0\t2\t4\t14\t74\t974\t1974\t974\t5974\t148\t149\tUVAAAA\tWMAAAA\tOOOOxx\n7370\t335\t0\t2\t0\t10\t70\t370\t1370\t2370\t7370\t140\t141\tMXAAAA\tXMAAAA\tVVVVxx\n9196\t336\t0\t0\t6\t16\t96\t196\t1196\t4196\t9196\t192\t193\tSPAAAA\tYMAAAA\tAAAAxx\n6796\t337\t0\t0\t6\t16\t96\t796\t796\t1796\t6796\t192\t193\tKBAAAA\tZMAAAA\tHHHHxx\n6180\t338\t0\t0\t0\t0\t80\t180\t180\t1180\t6180\t160\t161\tSDAAAA\tANAAAA\tOOOOxx\n8557\t339\t1\t1\t7\t17\t57\t557\t557\t3557\t8557\t114\t115\tDRAAAA\tBNAAAA\tVVVVxx\n928\t340\t0\t0\t8\t8\t28\t928\t928\t928\t928\t56\t57\tSJAAAA\tCNAAAA\tAAAAxx\n6275\t341\t1\t3\t5\t15\t75\t275\t275\t1275\t6275\t150\t151\tJHAAAA\tDNAAAA\tHHHHxx\n409\t342\t1\t1\t9\t9\t9\t409\t409\t409\t409\t18\t19\tTPAAAA\tENAAAA\tOOOOxx\n6442\t343\t0\t2\t2\t2\t42\t442\t442\t1442\t6442\t84\t85\tUNAAAA\tFNAAAA\tVVVVxx\n5889\t344\t1\t1\t9\t9\t89\t889\t1889\t889\t5889\t178\t179\tNSAAAA\tGNAAAA\tAAAAxx\n5180\t345\t0\t0\t0\t0\t80\t180\t1180\t180\t5180\t160\t161\tGRAAAA\tHNAAAA\tHHHHxx\n1629\t346\t1\t1\t9\t9\t29\t629\t1629\t1629\t1629\t58\t59\tRKAAAA\tINAAAA\tOOOOxx\n6088\t347\t0\t0\t8\t8\t88\t88\t88\t1088\t6088\t176\t177\tEAAAAA\tJNAAAA\tVVVVxx\n5598\t348\t0\t2\t8\t18\t98\t598\t1598\t598\t5598\t196\t197\tIHAAAA\tKNAAAA\tAAAAxx\n1803\t349\t1\t3\t3\t3\t3\t803\t1803\t1803\t1803\t6\t7\tJRAAAA\tLNAAAA\tHHHHxx\n2330\t350\t0\t2\t0\t10\t30\t330\t330\t2330\t2330\t60\t61\tQLAAAA\tMNAAAA\tOOOOxx\n5901\t351\t1\t1\t1\t1\t1\t901\t1901\t901\t5901\t2\t3\tZSAAAA\tNNAAAA\tVVVVxx\n780\t352\t0\t0\t0\t0\t80\t780\t780\t780\t780\t160\t161\tAEAAAA\tONAAAA\tAAAAxx\n7171\t353\t1\t3\t1\t11\t71\t171\t1171\t2171\t7171\t142\t143\tVPAAAA\tPNAAAA\tHHHHxx\n8778\t354\t0\t2\t8\t18\t78\t778\t778\t3778\t8778\t156\t157\tQZAAAA\tQNAAAA\tOOOOxx\n6622\t355\t0\t2\t2\t2\t22\t622\t622\t1622\t6622\t44\t45\tSUAAAA\tRNAAAA\tVVVVxx\n9938\t356\t0\t2\t8\t18\t38\t938\t1938\t4938\t9938\t76\t77\tGSAAAA\tSNAAAA\tAAAAxx\n8254\t357\t0\t2\t4\t14\t54\t254\t254\t3254\t8254\t108\t109\tMFAAAA\tTNAAAA\tHHHHxx\n1951\t358\t1\t3\t1\t11\t51\t951\t1951\t1951\t1951\t102\t103\tBXAAAA\tUNAAAA\tOOOOxx\n1434\t359\t0\t2\t4\t14\t34\t434\t1434\t1434\t1434\t68\t69\tEDAAAA\tVNAAAA\tVVVVxx\n7539\t360\t1\t3\t9\t19\t39\t539\t1539\t2539\t7539\t78\t79\tZDAAAA\tWNAAAA\tAAAAxx\n600\t361\t0\t0\t0\t0\t0\t600\t600\t600\t600\t0\t1\tCXAAAA\tXNAAAA\tHHHHxx\n3122\t362\t0\t2\t2\t2\t22\t122\t1122\t3122\t3122\t44\t45\tCQAAAA\tYNAAAA\tOOOOxx\n5704\t363\t0\t0\t4\t4\t4\t704\t1704\t704\t5704\t8\t9\tKLAAAA\tZNAAAA\tVVVVxx\n6300\t364\t0\t0\t0\t0\t0\t300\t300\t1300\t6300\t0\t1\tIIAAAA\tAOAAAA\tAAAAxx\n4585\t365\t1\t1\t5\t5\t85\t585\t585\t4585\t4585\t170\t171\tJUAAAA\tBOAAAA\tHHHHxx\n6313\t366\t1\t1\t3\t13\t13\t313\t313\t1313\t6313\t26\t27\tVIAAAA\tCOAAAA\tOOOOxx\n3154\t367\t0\t2\t4\t14\t54\t154\t1154\t3154\t3154\t108\t109\tIRAAAA\tDOAAAA\tVVVVxx\n642\t368\t0\t2\t2\t2\t42\t642\t642\t642\t642\t84\t85\tSYAAAA\tEOAAAA\tAAAAxx\n7736\t369\t0\t0\t6\t16\t36\t736\t1736\t2736\t7736\t72\t73\tOLAAAA\tFOAAAA\tHHHHxx\n5087\t370\t1\t3\t7\t7\t87\t87\t1087\t87\t5087\t174\t175\tRNAAAA\tGOAAAA\tOOOOxx\n5708\t371\t0\t0\t8\t8\t8\t708\t1708\t708\t5708\t16\t17\tOLAAAA\tHOAAAA\tVVVVxx\n8169\t372\t1\t1\t9\t9\t69\t169\t169\t3169\t8169\t138\t139\tFCAAAA\tIOAAAA\tAAAAxx\n9768\t373\t0\t0\t8\t8\t68\t768\t1768\t4768\t9768\t136\t137\tSLAAAA\tJOAAAA\tHHHHxx\n3874\t374\t0\t2\t4\t14\t74\t874\t1874\t3874\t3874\t148\t149\tATAAAA\tKOAAAA\tOOOOxx\n6831\t375\t1\t3\t1\t11\t31\t831\t831\t1831\t6831\t62\t63\tTCAAAA\tLOAAAA\tVVVVxx\n18\t376\t0\t2\t8\t18\t18\t18\t18\t18\t18\t36\t37\tSAAAAA\tMOAAAA\tAAAAxx\n6375\t377\t1\t3\t5\t15\t75\t375\t375\t1375\t6375\t150\t151\tFLAAAA\tNOAAAA\tHHHHxx\n7106\t378\t0\t2\t6\t6\t6\t106\t1106\t2106\t7106\t12\t13\tINAAAA\tOOAAAA\tOOOOxx\n5926\t379\t0\t2\t6\t6\t26\t926\t1926\t926\t5926\t52\t53\tYTAAAA\tPOAAAA\tVVVVxx\n4956\t380\t0\t0\t6\t16\t56\t956\t956\t4956\t4956\t112\t113\tQIAAAA\tQOAAAA\tAAAAxx\n7042\t381\t0\t2\t2\t2\t42\t42\t1042\t2042\t7042\t84\t85\tWKAAAA\tROAAAA\tHHHHxx\n6043\t382\t1\t3\t3\t3\t43\t43\t43\t1043\t6043\t86\t87\tLYAAAA\tSOAAAA\tOOOOxx\n2084\t383\t0\t0\t4\t4\t84\t84\t84\t2084\t2084\t168\t169\tECAAAA\tTOAAAA\tVVVVxx\n6038\t384\t0\t2\t8\t18\t38\t38\t38\t1038\t6038\t76\t77\tGYAAAA\tUOAAAA\tAAAAxx\n7253\t385\t1\t1\t3\t13\t53\t253\t1253\t2253\t7253\t106\t107\tZSAAAA\tVOAAAA\tHHHHxx\n2061\t386\t1\t1\t1\t1\t61\t61\t61\t2061\t2061\t122\t123\tHBAAAA\tWOAAAA\tOOOOxx\n7800\t387\t0\t0\t0\t0\t0\t800\t1800\t2800\t7800\t0\t1\tAOAAAA\tXOAAAA\tVVVVxx\n4970\t388\t0\t2\t0\t10\t70\t970\t970\t4970\t4970\t140\t141\tEJAAAA\tYOAAAA\tAAAAxx\n8580\t389\t0\t0\t0\t0\t80\t580\t580\t3580\t8580\t160\t161\tASAAAA\tZOAAAA\tHHHHxx\n9173\t390\t1\t1\t3\t13\t73\t173\t1173\t4173\t9173\t146\t147\tVOAAAA\tAPAAAA\tOOOOxx\n8558\t391\t0\t2\t8\t18\t58\t558\t558\t3558\t8558\t116\t117\tERAAAA\tBPAAAA\tVVVVxx\n3897\t392\t1\t1\t7\t17\t97\t897\t1897\t3897\t3897\t194\t195\tXTAAAA\tCPAAAA\tAAAAxx\n5069\t393\t1\t1\t9\t9\t69\t69\t1069\t69\t5069\t138\t139\tZMAAAA\tDPAAAA\tHHHHxx\n2301\t394\t1\t1\t1\t1\t1\t301\t301\t2301\t2301\t2\t3\tNKAAAA\tEPAAAA\tOOOOxx\n9863\t395\t1\t3\t3\t3\t63\t863\t1863\t4863\t9863\t126\t127\tJPAAAA\tFPAAAA\tVVVVxx\n5733\t396\t1\t1\t3\t13\t33\t733\t1733\t733\t5733\t66\t67\tNMAAAA\tGPAAAA\tAAAAxx\n2338\t397\t0\t2\t8\t18\t38\t338\t338\t2338\t2338\t76\t77\tYLAAAA\tHPAAAA\tHHHHxx\n9639\t398\t1\t3\t9\t19\t39\t639\t1639\t4639\t9639\t78\t79\tTGAAAA\tIPAAAA\tOOOOxx\n1139\t399\t1\t3\t9\t19\t39\t139\t1139\t1139\t1139\t78\t79\tVRAAAA\tJPAAAA\tVVVVxx\n2293\t400\t1\t1\t3\t13\t93\t293\t293\t2293\t2293\t186\t187\tFKAAAA\tKPAAAA\tAAAAxx\n6125\t401\t1\t1\t5\t5\t25\t125\t125\t1125\t6125\t50\t51\tPBAAAA\tLPAAAA\tHHHHxx\n5374\t402\t0\t2\t4\t14\t74\t374\t1374\t374\t5374\t148\t149\tSYAAAA\tMPAAAA\tOOOOxx\n7216\t403\t0\t0\t6\t16\t16\t216\t1216\t2216\t7216\t32\t33\tORAAAA\tNPAAAA\tVVVVxx\n2285\t404\t1\t1\t5\t5\t85\t285\t285\t2285\t2285\t170\t171\tXJAAAA\tOPAAAA\tAAAAxx\n2387\t405\t1\t3\t7\t7\t87\t387\t387\t2387\t2387\t174\t175\tVNAAAA\tPPAAAA\tHHHHxx\n5015\t406\t1\t3\t5\t15\t15\t15\t1015\t15\t5015\t30\t31\tXKAAAA\tQPAAAA\tOOOOxx\n2087\t407\t1\t3\t7\t7\t87\t87\t87\t2087\t2087\t174\t175\tHCAAAA\tRPAAAA\tVVVVxx\n4938\t408\t0\t2\t8\t18\t38\t938\t938\t4938\t4938\t76\t77\tYHAAAA\tSPAAAA\tAAAAxx\n3635\t409\t1\t3\t5\t15\t35\t635\t1635\t3635\t3635\t70\t71\tVJAAAA\tTPAAAA\tHHHHxx\n7737\t410\t1\t1\t7\t17\t37\t737\t1737\t2737\t7737\t74\t75\tPLAAAA\tUPAAAA\tOOOOxx\n8056\t411\t0\t0\t6\t16\t56\t56\t56\t3056\t8056\t112\t113\tWXAAAA\tVPAAAA\tVVVVxx\n4502\t412\t0\t2\t2\t2\t2\t502\t502\t4502\t4502\t4\t5\tERAAAA\tWPAAAA\tAAAAxx\n54\t413\t0\t2\t4\t14\t54\t54\t54\t54\t54\t108\t109\tCCAAAA\tXPAAAA\tHHHHxx\n3182\t414\t0\t2\t2\t2\t82\t182\t1182\t3182\t3182\t164\t165\tKSAAAA\tYPAAAA\tOOOOxx\n3718\t415\t0\t2\t8\t18\t18\t718\t1718\t3718\t3718\t36\t37\tANAAAA\tZPAAAA\tVVVVxx\n3989\t416\t1\t1\t9\t9\t89\t989\t1989\t3989\t3989\t178\t179\tLXAAAA\tAQAAAA\tAAAAxx\n8028\t417\t0\t0\t8\t8\t28\t28\t28\t3028\t8028\t56\t57\tUWAAAA\tBQAAAA\tHHHHxx\n1426\t418\t0\t2\t6\t6\t26\t426\t1426\t1426\t1426\t52\t53\tWCAAAA\tCQAAAA\tOOOOxx\n3801\t419\t1\t1\t1\t1\t1\t801\t1801\t3801\t3801\t2\t3\tFQAAAA\tDQAAAA\tVVVVxx\n241\t420\t1\t1\t1\t1\t41\t241\t241\t241\t241\t82\t83\tHJAAAA\tEQAAAA\tAAAAxx\n8000\t421\t0\t0\t0\t0\t0\t0\t0\t3000\t8000\t0\t1\tSVAAAA\tFQAAAA\tHHHHxx\n8357\t422\t1\t1\t7\t17\t57\t357\t357\t3357\t8357\t114\t115\tLJAAAA\tGQAAAA\tOOOOxx\n7548\t423\t0\t0\t8\t8\t48\t548\t1548\t2548\t7548\t96\t97\tIEAAAA\tHQAAAA\tVVVVxx\n7307\t424\t1\t3\t7\t7\t7\t307\t1307\t2307\t7307\t14\t15\tBVAAAA\tIQAAAA\tAAAAxx\n2275\t425\t1\t3\t5\t15\t75\t275\t275\t2275\t2275\t150\t151\tNJAAAA\tJQAAAA\tHHHHxx\n2718\t426\t0\t2\t8\t18\t18\t718\t718\t2718\t2718\t36\t37\tOAAAAA\tKQAAAA\tOOOOxx\n7068\t427\t0\t0\t8\t8\t68\t68\t1068\t2068\t7068\t136\t137\tWLAAAA\tLQAAAA\tVVVVxx\n3181\t428\t1\t1\t1\t1\t81\t181\t1181\t3181\t3181\t162\t163\tJSAAAA\tMQAAAA\tAAAAxx\n749\t429\t1\t1\t9\t9\t49\t749\t749\t749\t749\t98\t99\tVCAAAA\tNQAAAA\tHHHHxx\n5195\t430\t1\t3\t5\t15\t95\t195\t1195\t195\t5195\t190\t191\tVRAAAA\tOQAAAA\tOOOOxx\n6136\t431\t0\t0\t6\t16\t36\t136\t136\t1136\t6136\t72\t73\tACAAAA\tPQAAAA\tVVVVxx\n8012\t432\t0\t0\t2\t12\t12\t12\t12\t3012\t8012\t24\t25\tEWAAAA\tQQAAAA\tAAAAxx\n3957\t433\t1\t1\t7\t17\t57\t957\t1957\t3957\t3957\t114\t115\tFWAAAA\tRQAAAA\tHHHHxx\n3083\t434\t1\t3\t3\t3\t83\t83\t1083\t3083\t3083\t166\t167\tPOAAAA\tSQAAAA\tOOOOxx\n9997\t435\t1\t1\t7\t17\t97\t997\t1997\t4997\t9997\t194\t195\tNUAAAA\tTQAAAA\tVVVVxx\n3299\t436\t1\t3\t9\t19\t99\t299\t1299\t3299\t3299\t198\t199\tXWAAAA\tUQAAAA\tAAAAxx\n846\t437\t0\t2\t6\t6\t46\t846\t846\t846\t846\t92\t93\tOGAAAA\tVQAAAA\tHHHHxx\n2985\t438\t1\t1\t5\t5\t85\t985\t985\t2985\t2985\t170\t171\tVKAAAA\tWQAAAA\tOOOOxx\n9238\t439\t0\t2\t8\t18\t38\t238\t1238\t4238\t9238\t76\t77\tIRAAAA\tXQAAAA\tVVVVxx\n1403\t440\t1\t3\t3\t3\t3\t403\t1403\t1403\t1403\t6\t7\tZBAAAA\tYQAAAA\tAAAAxx\n5563\t441\t1\t3\t3\t3\t63\t563\t1563\t563\t5563\t126\t127\tZFAAAA\tZQAAAA\tHHHHxx\n7965\t442\t1\t1\t5\t5\t65\t965\t1965\t2965\t7965\t130\t131\tJUAAAA\tARAAAA\tOOOOxx\n4512\t443\t0\t0\t2\t12\t12\t512\t512\t4512\t4512\t24\t25\tORAAAA\tBRAAAA\tVVVVxx\n9730\t444\t0\t2\t0\t10\t30\t730\t1730\t4730\t9730\t60\t61\tGKAAAA\tCRAAAA\tAAAAxx\n1129\t445\t1\t1\t9\t9\t29\t129\t1129\t1129\t1129\t58\t59\tLRAAAA\tDRAAAA\tHHHHxx\n2624\t446\t0\t0\t4\t4\t24\t624\t624\t2624\t2624\t48\t49\tYWAAAA\tERAAAA\tOOOOxx\n8178\t447\t0\t2\t8\t18\t78\t178\t178\t3178\t8178\t156\t157\tOCAAAA\tFRAAAA\tVVVVxx\n6468\t448\t0\t0\t8\t8\t68\t468\t468\t1468\t6468\t136\t137\tUOAAAA\tGRAAAA\tAAAAxx\n3027\t449\t1\t3\t7\t7\t27\t27\t1027\t3027\t3027\t54\t55\tLMAAAA\tHRAAAA\tHHHHxx\n3845\t450\t1\t1\t5\t5\t45\t845\t1845\t3845\t3845\t90\t91\tXRAAAA\tIRAAAA\tOOOOxx\n786\t451\t0\t2\t6\t6\t86\t786\t786\t786\t786\t172\t173\tGEAAAA\tJRAAAA\tVVVVxx\n4971\t452\t1\t3\t1\t11\t71\t971\t971\t4971\t4971\t142\t143\tFJAAAA\tKRAAAA\tAAAAxx\n1542\t453\t0\t2\t2\t2\t42\t542\t1542\t1542\t1542\t84\t85\tIHAAAA\tLRAAAA\tHHHHxx\n7967\t454\t1\t3\t7\t7\t67\t967\t1967\t2967\t7967\t134\t135\tLUAAAA\tMRAAAA\tOOOOxx\n443\t455\t1\t3\t3\t3\t43\t443\t443\t443\t443\t86\t87\tBRAAAA\tNRAAAA\tVVVVxx\n7318\t456\t0\t2\t8\t18\t18\t318\t1318\t2318\t7318\t36\t37\tMVAAAA\tORAAAA\tAAAAxx\n4913\t457\t1\t1\t3\t13\t13\t913\t913\t4913\t4913\t26\t27\tZGAAAA\tPRAAAA\tHHHHxx\n9466\t458\t0\t2\t6\t6\t66\t466\t1466\t4466\t9466\t132\t133\tCAAAAA\tQRAAAA\tOOOOxx\n7866\t459\t0\t2\t6\t6\t66\t866\t1866\t2866\t7866\t132\t133\tOQAAAA\tRRAAAA\tVVVVxx\n784\t460\t0\t0\t4\t4\t84\t784\t784\t784\t784\t168\t169\tEEAAAA\tSRAAAA\tAAAAxx\n9040\t461\t0\t0\t0\t0\t40\t40\t1040\t4040\t9040\t80\t81\tSJAAAA\tTRAAAA\tHHHHxx\n3954\t462\t0\t2\t4\t14\t54\t954\t1954\t3954\t3954\t108\t109\tCWAAAA\tURAAAA\tOOOOxx\n4183\t463\t1\t3\t3\t3\t83\t183\t183\t4183\t4183\t166\t167\tXEAAAA\tVRAAAA\tVVVVxx\n3608\t464\t0\t0\t8\t8\t8\t608\t1608\t3608\t3608\t16\t17\tUIAAAA\tWRAAAA\tAAAAxx\n7630\t465\t0\t2\t0\t10\t30\t630\t1630\t2630\t7630\t60\t61\tMHAAAA\tXRAAAA\tHHHHxx\n590\t466\t0\t2\t0\t10\t90\t590\t590\t590\t590\t180\t181\tSWAAAA\tYRAAAA\tOOOOxx\n3453\t467\t1\t1\t3\t13\t53\t453\t1453\t3453\t3453\t106\t107\tVCAAAA\tZRAAAA\tVVVVxx\n7757\t468\t1\t1\t7\t17\t57\t757\t1757\t2757\t7757\t114\t115\tJMAAAA\tASAAAA\tAAAAxx\n7394\t469\t0\t2\t4\t14\t94\t394\t1394\t2394\t7394\t188\t189\tKYAAAA\tBSAAAA\tHHHHxx\n396\t470\t0\t0\t6\t16\t96\t396\t396\t396\t396\t192\t193\tGPAAAA\tCSAAAA\tOOOOxx\n7873\t471\t1\t1\t3\t13\t73\t873\t1873\t2873\t7873\t146\t147\tVQAAAA\tDSAAAA\tVVVVxx\n1553\t472\t1\t1\t3\t13\t53\t553\t1553\t1553\t1553\t106\t107\tTHAAAA\tESAAAA\tAAAAxx\n598\t473\t0\t2\t8\t18\t98\t598\t598\t598\t598\t196\t197\tAXAAAA\tFSAAAA\tHHHHxx\n7191\t474\t1\t3\t1\t11\t91\t191\t1191\t2191\t7191\t182\t183\tPQAAAA\tGSAAAA\tOOOOxx\n8116\t475\t0\t0\t6\t16\t16\t116\t116\t3116\t8116\t32\t33\tEAAAAA\tHSAAAA\tVVVVxx\n2516\t476\t0\t0\t6\t16\t16\t516\t516\t2516\t2516\t32\t33\tUSAAAA\tISAAAA\tAAAAxx\n7750\t477\t0\t2\t0\t10\t50\t750\t1750\t2750\t7750\t100\t101\tCMAAAA\tJSAAAA\tHHHHxx\n6625\t478\t1\t1\t5\t5\t25\t625\t625\t1625\t6625\t50\t51\tVUAAAA\tKSAAAA\tOOOOxx\n8838\t479\t0\t2\t8\t18\t38\t838\t838\t3838\t8838\t76\t77\tYBAAAA\tLSAAAA\tVVVVxx\n4636\t480\t0\t0\t6\t16\t36\t636\t636\t4636\t4636\t72\t73\tIWAAAA\tMSAAAA\tAAAAxx\n7627\t481\t1\t3\t7\t7\t27\t627\t1627\t2627\t7627\t54\t55\tJHAAAA\tNSAAAA\tHHHHxx\n1690\t482\t0\t2\t0\t10\t90\t690\t1690\t1690\t1690\t180\t181\tANAAAA\tOSAAAA\tOOOOxx\n7071\t483\t1\t3\t1\t11\t71\t71\t1071\t2071\t7071\t142\t143\tZLAAAA\tPSAAAA\tVVVVxx\n2081\t484\t1\t1\t1\t1\t81\t81\t81\t2081\t2081\t162\t163\tBCAAAA\tQSAAAA\tAAAAxx\n7138\t485\t0\t2\t8\t18\t38\t138\t1138\t2138\t7138\t76\t77\tOOAAAA\tRSAAAA\tHHHHxx\n864\t486\t0\t0\t4\t4\t64\t864\t864\t864\t864\t128\t129\tGHAAAA\tSSAAAA\tOOOOxx\n6392\t487\t0\t0\t2\t12\t92\t392\t392\t1392\t6392\t184\t185\tWLAAAA\tTSAAAA\tVVVVxx\n7544\t488\t0\t0\t4\t4\t44\t544\t1544\t2544\t7544\t88\t89\tEEAAAA\tUSAAAA\tAAAAxx\n5438\t489\t0\t2\t8\t18\t38\t438\t1438\t438\t5438\t76\t77\tEBAAAA\tVSAAAA\tHHHHxx\n7099\t490\t1\t3\t9\t19\t99\t99\t1099\t2099\t7099\t198\t199\tBNAAAA\tWSAAAA\tOOOOxx\n5157\t491\t1\t1\t7\t17\t57\t157\t1157\t157\t5157\t114\t115\tJQAAAA\tXSAAAA\tVVVVxx\n3391\t492\t1\t3\t1\t11\t91\t391\t1391\t3391\t3391\t182\t183\tLAAAAA\tYSAAAA\tAAAAxx\n3805\t493\t1\t1\t5\t5\t5\t805\t1805\t3805\t3805\t10\t11\tJQAAAA\tZSAAAA\tHHHHxx\n2110\t494\t0\t2\t0\t10\t10\t110\t110\t2110\t2110\t20\t21\tEDAAAA\tATAAAA\tOOOOxx\n3176\t495\t0\t0\t6\t16\t76\t176\t1176\t3176\t3176\t152\t153\tESAAAA\tBTAAAA\tVVVVxx\n5918\t496\t0\t2\t8\t18\t18\t918\t1918\t918\t5918\t36\t37\tQTAAAA\tCTAAAA\tAAAAxx\n1218\t497\t0\t2\t8\t18\t18\t218\t1218\t1218\t1218\t36\t37\tWUAAAA\tDTAAAA\tHHHHxx\n6683\t498\t1\t3\t3\t3\t83\t683\t683\t1683\t6683\t166\t167\tBXAAAA\tETAAAA\tOOOOxx\n914\t499\t0\t2\t4\t14\t14\t914\t914\t914\t914\t28\t29\tEJAAAA\tFTAAAA\tVVVVxx\n4737\t500\t1\t1\t7\t17\t37\t737\t737\t4737\t4737\t74\t75\tFAAAAA\tGTAAAA\tAAAAxx\n7286\t501\t0\t2\t6\t6\t86\t286\t1286\t2286\t7286\t172\t173\tGUAAAA\tHTAAAA\tHHHHxx\n9975\t502\t1\t3\t5\t15\t75\t975\t1975\t4975\t9975\t150\t151\tRTAAAA\tITAAAA\tOOOOxx\n8030\t503\t0\t2\t0\t10\t30\t30\t30\t3030\t8030\t60\t61\tWWAAAA\tJTAAAA\tVVVVxx\n7364\t504\t0\t0\t4\t4\t64\t364\t1364\t2364\t7364\t128\t129\tGXAAAA\tKTAAAA\tAAAAxx\n1389\t505\t1\t1\t9\t9\t89\t389\t1389\t1389\t1389\t178\t179\tLBAAAA\tLTAAAA\tHHHHxx\n4025\t506\t1\t1\t5\t5\t25\t25\t25\t4025\t4025\t50\t51\tVYAAAA\tMTAAAA\tOOOOxx\n4835\t507\t1\t3\t5\t15\t35\t835\t835\t4835\t4835\t70\t71\tZDAAAA\tNTAAAA\tVVVVxx\n8045\t508\t1\t1\t5\t5\t45\t45\t45\t3045\t8045\t90\t91\tLXAAAA\tOTAAAA\tAAAAxx\n1864\t509\t0\t0\t4\t4\t64\t864\t1864\t1864\t1864\t128\t129\tSTAAAA\tPTAAAA\tHHHHxx\n3313\t510\t1\t1\t3\t13\t13\t313\t1313\t3313\t3313\t26\t27\tLXAAAA\tQTAAAA\tOOOOxx\n2384\t511\t0\t0\t4\t4\t84\t384\t384\t2384\t2384\t168\t169\tSNAAAA\tRTAAAA\tVVVVxx\n6115\t512\t1\t3\t5\t15\t15\t115\t115\t1115\t6115\t30\t31\tFBAAAA\tSTAAAA\tAAAAxx\n5705\t513\t1\t1\t5\t5\t5\t705\t1705\t705\t5705\t10\t11\tLLAAAA\tTTAAAA\tHHHHxx\n9269\t514\t1\t1\t9\t9\t69\t269\t1269\t4269\t9269\t138\t139\tNSAAAA\tUTAAAA\tOOOOxx\n3379\t515\t1\t3\t9\t19\t79\t379\t1379\t3379\t3379\t158\t159\tZZAAAA\tVTAAAA\tVVVVxx\n8205\t516\t1\t1\t5\t5\t5\t205\t205\t3205\t8205\t10\t11\tPDAAAA\tWTAAAA\tAAAAxx\n6575\t517\t1\t3\t5\t15\t75\t575\t575\t1575\t6575\t150\t151\tXSAAAA\tXTAAAA\tHHHHxx\n486\t518\t0\t2\t6\t6\t86\t486\t486\t486\t486\t172\t173\tSSAAAA\tYTAAAA\tOOOOxx\n4894\t519\t0\t2\t4\t14\t94\t894\t894\t4894\t4894\t188\t189\tGGAAAA\tZTAAAA\tVVVVxx\n3090\t520\t0\t2\t0\t10\t90\t90\t1090\t3090\t3090\t180\t181\tWOAAAA\tAUAAAA\tAAAAxx\n759\t521\t1\t3\t9\t19\t59\t759\t759\t759\t759\t118\t119\tFDAAAA\tBUAAAA\tHHHHxx\n4864\t522\t0\t0\t4\t4\t64\t864\t864\t4864\t4864\t128\t129\tCFAAAA\tCUAAAA\tOOOOxx\n4083\t523\t1\t3\t3\t3\t83\t83\t83\t4083\t4083\t166\t167\tBBAAAA\tDUAAAA\tVVVVxx\n6918\t524\t0\t2\t8\t18\t18\t918\t918\t1918\t6918\t36\t37\tCGAAAA\tEUAAAA\tAAAAxx\n8146\t525\t0\t2\t6\t6\t46\t146\t146\t3146\t8146\t92\t93\tIBAAAA\tFUAAAA\tHHHHxx\n1523\t526\t1\t3\t3\t3\t23\t523\t1523\t1523\t1523\t46\t47\tPGAAAA\tGUAAAA\tOOOOxx\n1591\t527\t1\t3\t1\t11\t91\t591\t1591\t1591\t1591\t182\t183\tFJAAAA\tHUAAAA\tVVVVxx\n3343\t528\t1\t3\t3\t3\t43\t343\t1343\t3343\t3343\t86\t87\tPYAAAA\tIUAAAA\tAAAAxx\n1391\t529\t1\t3\t1\t11\t91\t391\t1391\t1391\t1391\t182\t183\tNBAAAA\tJUAAAA\tHHHHxx\n9963\t530\t1\t3\t3\t3\t63\t963\t1963\t4963\t9963\t126\t127\tFTAAAA\tKUAAAA\tOOOOxx\n2423\t531\t1\t3\t3\t3\t23\t423\t423\t2423\t2423\t46\t47\tFPAAAA\tLUAAAA\tVVVVxx\n1822\t532\t0\t2\t2\t2\t22\t822\t1822\t1822\t1822\t44\t45\tCSAAAA\tMUAAAA\tAAAAxx\n8706\t533\t0\t2\t6\t6\t6\t706\t706\t3706\t8706\t12\t13\tWWAAAA\tNUAAAA\tHHHHxx\n3001\t534\t1\t1\t1\t1\t1\t1\t1001\t3001\t3001\t2\t3\tLLAAAA\tOUAAAA\tOOOOxx\n6707\t535\t1\t3\t7\t7\t7\t707\t707\t1707\t6707\t14\t15\tZXAAAA\tPUAAAA\tVVVVxx\n2121\t536\t1\t1\t1\t1\t21\t121\t121\t2121\t2121\t42\t43\tPDAAAA\tQUAAAA\tAAAAxx\n5814\t537\t0\t2\t4\t14\t14\t814\t1814\t814\t5814\t28\t29\tQPAAAA\tRUAAAA\tHHHHxx\n2659\t538\t1\t3\t9\t19\t59\t659\t659\t2659\t2659\t118\t119\tHYAAAA\tSUAAAA\tOOOOxx\n2016\t539\t0\t0\t6\t16\t16\t16\t16\t2016\t2016\t32\t33\tOZAAAA\tTUAAAA\tVVVVxx\n4286\t540\t0\t2\t6\t6\t86\t286\t286\t4286\t4286\t172\t173\tWIAAAA\tUUAAAA\tAAAAxx\n9205\t541\t1\t1\t5\t5\t5\t205\t1205\t4205\t9205\t10\t11\tBQAAAA\tVUAAAA\tHHHHxx\n3496\t542\t0\t0\t6\t16\t96\t496\t1496\t3496\t3496\t192\t193\tMEAAAA\tWUAAAA\tOOOOxx\n5333\t543\t1\t1\t3\t13\t33\t333\t1333\t333\t5333\t66\t67\tDXAAAA\tXUAAAA\tVVVVxx\n5571\t544\t1\t3\t1\t11\t71\t571\t1571\t571\t5571\t142\t143\tHGAAAA\tYUAAAA\tAAAAxx\n1696\t545\t0\t0\t6\t16\t96\t696\t1696\t1696\t1696\t192\t193\tGNAAAA\tZUAAAA\tHHHHxx\n4871\t546\t1\t3\t1\t11\t71\t871\t871\t4871\t4871\t142\t143\tJFAAAA\tAVAAAA\tOOOOxx\n4852\t547\t0\t0\t2\t12\t52\t852\t852\t4852\t4852\t104\t105\tQEAAAA\tBVAAAA\tVVVVxx\n8483\t548\t1\t3\t3\t3\t83\t483\t483\t3483\t8483\t166\t167\tHOAAAA\tCVAAAA\tAAAAxx\n1376\t549\t0\t0\t6\t16\t76\t376\t1376\t1376\t1376\t152\t153\tYAAAAA\tDVAAAA\tHHHHxx\n5456\t550\t0\t0\t6\t16\t56\t456\t1456\t456\t5456\t112\t113\tWBAAAA\tEVAAAA\tOOOOxx\n499\t551\t1\t3\t9\t19\t99\t499\t499\t499\t499\t198\t199\tFTAAAA\tFVAAAA\tVVVVxx\n3463\t552\t1\t3\t3\t3\t63\t463\t1463\t3463\t3463\t126\t127\tFDAAAA\tGVAAAA\tAAAAxx\n7426\t553\t0\t2\t6\t6\t26\t426\t1426\t2426\t7426\t52\t53\tQZAAAA\tHVAAAA\tHHHHxx\n5341\t554\t1\t1\t1\t1\t41\t341\t1341\t341\t5341\t82\t83\tLXAAAA\tIVAAAA\tOOOOxx\n9309\t555\t1\t1\t9\t9\t9\t309\t1309\t4309\t9309\t18\t19\tBUAAAA\tJVAAAA\tVVVVxx\n2055\t556\t1\t3\t5\t15\t55\t55\t55\t2055\t2055\t110\t111\tBBAAAA\tKVAAAA\tAAAAxx\n2199\t557\t1\t3\t9\t19\t99\t199\t199\t2199\t2199\t198\t199\tPGAAAA\tLVAAAA\tHHHHxx\n7235\t558\t1\t3\t5\t15\t35\t235\t1235\t2235\t7235\t70\t71\tHSAAAA\tMVAAAA\tOOOOxx\n8661\t559\t1\t1\t1\t1\t61\t661\t661\t3661\t8661\t122\t123\tDVAAAA\tNVAAAA\tVVVVxx\n9494\t560\t0\t2\t4\t14\t94\t494\t1494\t4494\t9494\t188\t189\tEBAAAA\tOVAAAA\tAAAAxx\n935\t561\t1\t3\t5\t15\t35\t935\t935\t935\t935\t70\t71\tZJAAAA\tPVAAAA\tHHHHxx\n7044\t562\t0\t0\t4\t4\t44\t44\t1044\t2044\t7044\t88\t89\tYKAAAA\tQVAAAA\tOOOOxx\n1974\t563\t0\t2\t4\t14\t74\t974\t1974\t1974\t1974\t148\t149\tYXAAAA\tRVAAAA\tVVVVxx\n9679\t564\t1\t3\t9\t19\t79\t679\t1679\t4679\t9679\t158\t159\tHIAAAA\tSVAAAA\tAAAAxx\n9822\t565\t0\t2\t2\t2\t22\t822\t1822\t4822\t9822\t44\t45\tUNAAAA\tTVAAAA\tHHHHxx\n4088\t566\t0\t0\t8\t8\t88\t88\t88\t4088\t4088\t176\t177\tGBAAAA\tUVAAAA\tOOOOxx\n1749\t567\t1\t1\t9\t9\t49\t749\t1749\t1749\t1749\t98\t99\tHPAAAA\tVVAAAA\tVVVVxx\n2116\t568\t0\t0\t6\t16\t16\t116\t116\t2116\t2116\t32\t33\tKDAAAA\tWVAAAA\tAAAAxx\n976\t569\t0\t0\t6\t16\t76\t976\t976\t976\t976\t152\t153\tOLAAAA\tXVAAAA\tHHHHxx\n8689\t570\t1\t1\t9\t9\t89\t689\t689\t3689\t8689\t178\t179\tFWAAAA\tYVAAAA\tOOOOxx\n2563\t571\t1\t3\t3\t3\t63\t563\t563\t2563\t2563\t126\t127\tPUAAAA\tZVAAAA\tVVVVxx\n7195\t572\t1\t3\t5\t15\t95\t195\t1195\t2195\t7195\t190\t191\tTQAAAA\tAWAAAA\tAAAAxx\n9985\t573\t1\t1\t5\t5\t85\t985\t1985\t4985\t9985\t170\t171\tBUAAAA\tBWAAAA\tHHHHxx\n7699\t574\t1\t3\t9\t19\t99\t699\t1699\t2699\t7699\t198\t199\tDKAAAA\tCWAAAA\tOOOOxx\n5311\t575\t1\t3\t1\t11\t11\t311\t1311\t311\t5311\t22\t23\tHWAAAA\tDWAAAA\tVVVVxx\n295\t576\t1\t3\t5\t15\t95\t295\t295\t295\t295\t190\t191\tJLAAAA\tEWAAAA\tAAAAxx\n8214\t577\t0\t2\t4\t14\t14\t214\t214\t3214\t8214\t28\t29\tYDAAAA\tFWAAAA\tHHHHxx\n3275\t578\t1\t3\t5\t15\t75\t275\t1275\t3275\t3275\t150\t151\tZVAAAA\tGWAAAA\tOOOOxx\n9646\t579\t0\t2\t6\t6\t46\t646\t1646\t4646\t9646\t92\t93\tAHAAAA\tHWAAAA\tVVVVxx\n1908\t580\t0\t0\t8\t8\t8\t908\t1908\t1908\t1908\t16\t17\tKVAAAA\tIWAAAA\tAAAAxx\n3858\t581\t0\t2\t8\t18\t58\t858\t1858\t3858\t3858\t116\t117\tKSAAAA\tJWAAAA\tHHHHxx\n9362\t582\t0\t2\t2\t2\t62\t362\t1362\t4362\t9362\t124\t125\tCWAAAA\tKWAAAA\tOOOOxx\n9307\t583\t1\t3\t7\t7\t7\t307\t1307\t4307\t9307\t14\t15\tZTAAAA\tLWAAAA\tVVVVxx\n6124\t584\t0\t0\t4\t4\t24\t124\t124\t1124\t6124\t48\t49\tOBAAAA\tMWAAAA\tAAAAxx\n2405\t585\t1\t1\t5\t5\t5\t405\t405\t2405\t2405\t10\t11\tNOAAAA\tNWAAAA\tHHHHxx\n8422\t586\t0\t2\t2\t2\t22\t422\t422\t3422\t8422\t44\t45\tYLAAAA\tOWAAAA\tOOOOxx\n393\t587\t1\t1\t3\t13\t93\t393\t393\t393\t393\t186\t187\tDPAAAA\tPWAAAA\tVVVVxx\n8973\t588\t1\t1\t3\t13\t73\t973\t973\t3973\t8973\t146\t147\tDHAAAA\tQWAAAA\tAAAAxx\n5171\t589\t1\t3\t1\t11\t71\t171\t1171\t171\t5171\t142\t143\tXQAAAA\tRWAAAA\tHHHHxx\n4929\t590\t1\t1\t9\t9\t29\t929\t929\t4929\t4929\t58\t59\tPHAAAA\tSWAAAA\tOOOOxx\n6935\t591\t1\t3\t5\t15\t35\t935\t935\t1935\t6935\t70\t71\tTGAAAA\tTWAAAA\tVVVVxx\n8584\t592\t0\t0\t4\t4\t84\t584\t584\t3584\t8584\t168\t169\tESAAAA\tUWAAAA\tAAAAxx\n1035\t593\t1\t3\t5\t15\t35\t35\t1035\t1035\t1035\t70\t71\tVNAAAA\tVWAAAA\tHHHHxx\n3734\t594\t0\t2\t4\t14\t34\t734\t1734\t3734\t3734\t68\t69\tQNAAAA\tWWAAAA\tOOOOxx\n1458\t595\t0\t2\t8\t18\t58\t458\t1458\t1458\t1458\t116\t117\tCEAAAA\tXWAAAA\tVVVVxx\n8746\t596\t0\t2\t6\t6\t46\t746\t746\t3746\t8746\t92\t93\tKYAAAA\tYWAAAA\tAAAAxx\n1677\t597\t1\t1\t7\t17\t77\t677\t1677\t1677\t1677\t154\t155\tNMAAAA\tZWAAAA\tHHHHxx\n8502\t598\t0\t2\t2\t2\t2\t502\t502\t3502\t8502\t4\t5\tAPAAAA\tAXAAAA\tOOOOxx\n7752\t599\t0\t0\t2\t12\t52\t752\t1752\t2752\t7752\t104\t105\tEMAAAA\tBXAAAA\tVVVVxx\n2556\t600\t0\t0\t6\t16\t56\t556\t556\t2556\t2556\t112\t113\tIUAAAA\tCXAAAA\tAAAAxx\n6426\t601\t0\t2\t6\t6\t26\t426\t426\t1426\t6426\t52\t53\tENAAAA\tDXAAAA\tHHHHxx\n8420\t602\t0\t0\t0\t0\t20\t420\t420\t3420\t8420\t40\t41\tWLAAAA\tEXAAAA\tOOOOxx\n4462\t603\t0\t2\t2\t2\t62\t462\t462\t4462\t4462\t124\t125\tQPAAAA\tFXAAAA\tVVVVxx\n1378\t604\t0\t2\t8\t18\t78\t378\t1378\t1378\t1378\t156\t157\tABAAAA\tGXAAAA\tAAAAxx\n1387\t605\t1\t3\t7\t7\t87\t387\t1387\t1387\t1387\t174\t175\tJBAAAA\tHXAAAA\tHHHHxx\n8094\t606\t0\t2\t4\t14\t94\t94\t94\t3094\t8094\t188\t189\tIZAAAA\tIXAAAA\tOOOOxx\n7247\t607\t1\t3\t7\t7\t47\t247\t1247\t2247\t7247\t94\t95\tTSAAAA\tJXAAAA\tVVVVxx\n4261\t608\t1\t1\t1\t1\t61\t261\t261\t4261\t4261\t122\t123\tXHAAAA\tKXAAAA\tAAAAxx\n5029\t609\t1\t1\t9\t9\t29\t29\t1029\t29\t5029\t58\t59\tLLAAAA\tLXAAAA\tHHHHxx\n3625\t610\t1\t1\t5\t5\t25\t625\t1625\t3625\t3625\t50\t51\tLJAAAA\tMXAAAA\tOOOOxx\n8068\t611\t0\t0\t8\t8\t68\t68\t68\t3068\t8068\t136\t137\tIYAAAA\tNXAAAA\tVVVVxx\n102\t612\t0\t2\t2\t2\t2\t102\t102\t102\t102\t4\t5\tYDAAAA\tOXAAAA\tAAAAxx\n5596\t613\t0\t0\t6\t16\t96\t596\t1596\t596\t5596\t192\t193\tGHAAAA\tPXAAAA\tHHHHxx\n5872\t614\t0\t0\t2\t12\t72\t872\t1872\t872\t5872\t144\t145\tWRAAAA\tQXAAAA\tOOOOxx\n4742\t615\t0\t2\t2\t2\t42\t742\t742\t4742\t4742\t84\t85\tKAAAAA\tRXAAAA\tVVVVxx\n2117\t616\t1\t1\t7\t17\t17\t117\t117\t2117\t2117\t34\t35\tLDAAAA\tSXAAAA\tAAAAxx\n3945\t617\t1\t1\t5\t5\t45\t945\t1945\t3945\t3945\t90\t91\tTVAAAA\tTXAAAA\tHHHHxx\n7483\t618\t1\t3\t3\t3\t83\t483\t1483\t2483\t7483\t166\t167\tVBAAAA\tUXAAAA\tOOOOxx\n4455\t619\t1\t3\t5\t15\t55\t455\t455\t4455\t4455\t110\t111\tJPAAAA\tVXAAAA\tVVVVxx\n609\t620\t1\t1\t9\t9\t9\t609\t609\t609\t609\t18\t19\tLXAAAA\tWXAAAA\tAAAAxx\n9829\t621\t1\t1\t9\t9\t29\t829\t1829\t4829\t9829\t58\t59\tBOAAAA\tXXAAAA\tHHHHxx\n4857\t622\t1\t1\t7\t17\t57\t857\t857\t4857\t4857\t114\t115\tVEAAAA\tYXAAAA\tOOOOxx\n3314\t623\t0\t2\t4\t14\t14\t314\t1314\t3314\t3314\t28\t29\tMXAAAA\tZXAAAA\tVVVVxx\n5353\t624\t1\t1\t3\t13\t53\t353\t1353\t353\t5353\t106\t107\tXXAAAA\tAYAAAA\tAAAAxx\n4909\t625\t1\t1\t9\t9\t9\t909\t909\t4909\t4909\t18\t19\tVGAAAA\tBYAAAA\tHHHHxx\n7597\t626\t1\t1\t7\t17\t97\t597\t1597\t2597\t7597\t194\t195\tFGAAAA\tCYAAAA\tOOOOxx\n2683\t627\t1\t3\t3\t3\t83\t683\t683\t2683\t2683\t166\t167\tFZAAAA\tDYAAAA\tVVVVxx\n3223\t628\t1\t3\t3\t3\t23\t223\t1223\t3223\t3223\t46\t47\tZTAAAA\tEYAAAA\tAAAAxx\n5363\t629\t1\t3\t3\t3\t63\t363\t1363\t363\t5363\t126\t127\tHYAAAA\tFYAAAA\tHHHHxx\n4578\t630\t0\t2\t8\t18\t78\t578\t578\t4578\t4578\t156\t157\tCUAAAA\tGYAAAA\tOOOOxx\n5544\t631\t0\t0\t4\t4\t44\t544\t1544\t544\t5544\t88\t89\tGFAAAA\tHYAAAA\tVVVVxx\n1589\t632\t1\t1\t9\t9\t89\t589\t1589\t1589\t1589\t178\t179\tDJAAAA\tIYAAAA\tAAAAxx\n7412\t633\t0\t0\t2\t12\t12\t412\t1412\t2412\t7412\t24\t25\tCZAAAA\tJYAAAA\tHHHHxx\n3803\t634\t1\t3\t3\t3\t3\t803\t1803\t3803\t3803\t6\t7\tHQAAAA\tKYAAAA\tOOOOxx\n6179\t635\t1\t3\t9\t19\t79\t179\t179\t1179\t6179\t158\t159\tRDAAAA\tLYAAAA\tVVVVxx\n5588\t636\t0\t0\t8\t8\t88\t588\t1588\t588\t5588\t176\t177\tYGAAAA\tMYAAAA\tAAAAxx\n2134\t637\t0\t2\t4\t14\t34\t134\t134\t2134\t2134\t68\t69\tCEAAAA\tNYAAAA\tHHHHxx\n4383\t638\t1\t3\t3\t3\t83\t383\t383\t4383\t4383\t166\t167\tPMAAAA\tOYAAAA\tOOOOxx\n6995\t639\t1\t3\t5\t15\t95\t995\t995\t1995\t6995\t190\t191\tBJAAAA\tPYAAAA\tVVVVxx\n6598\t640\t0\t2\t8\t18\t98\t598\t598\t1598\t6598\t196\t197\tUTAAAA\tQYAAAA\tAAAAxx\n8731\t641\t1\t3\t1\t11\t31\t731\t731\t3731\t8731\t62\t63\tVXAAAA\tRYAAAA\tHHHHxx\n7177\t642\t1\t1\t7\t17\t77\t177\t1177\t2177\t7177\t154\t155\tBQAAAA\tSYAAAA\tOOOOxx\n6578\t643\t0\t2\t8\t18\t78\t578\t578\t1578\t6578\t156\t157\tATAAAA\tTYAAAA\tVVVVxx\n9393\t644\t1\t1\t3\t13\t93\t393\t1393\t4393\t9393\t186\t187\tHXAAAA\tUYAAAA\tAAAAxx\n1276\t645\t0\t0\t6\t16\t76\t276\t1276\t1276\t1276\t152\t153\tCXAAAA\tVYAAAA\tHHHHxx\n8766\t646\t0\t2\t6\t6\t66\t766\t766\t3766\t8766\t132\t133\tEZAAAA\tWYAAAA\tOOOOxx\n1015\t647\t1\t3\t5\t15\t15\t15\t1015\t1015\t1015\t30\t31\tBNAAAA\tXYAAAA\tVVVVxx\n4396\t648\t0\t0\t6\t16\t96\t396\t396\t4396\t4396\t192\t193\tCNAAAA\tYYAAAA\tAAAAxx\n5564\t649\t0\t0\t4\t4\t64\t564\t1564\t564\t5564\t128\t129\tAGAAAA\tZYAAAA\tHHHHxx\n927\t650\t1\t3\t7\t7\t27\t927\t927\t927\t927\t54\t55\tRJAAAA\tAZAAAA\tOOOOxx\n3306\t651\t0\t2\t6\t6\t6\t306\t1306\t3306\t3306\t12\t13\tEXAAAA\tBZAAAA\tVVVVxx\n1615\t652\t1\t3\t5\t15\t15\t615\t1615\t1615\t1615\t30\t31\tDKAAAA\tCZAAAA\tAAAAxx\n4550\t653\t0\t2\t0\t10\t50\t550\t550\t4550\t4550\t100\t101\tATAAAA\tDZAAAA\tHHHHxx\n2468\t654\t0\t0\t8\t8\t68\t468\t468\t2468\t2468\t136\t137\tYQAAAA\tEZAAAA\tOOOOxx\n5336\t655\t0\t0\t6\t16\t36\t336\t1336\t336\t5336\t72\t73\tGXAAAA\tFZAAAA\tVVVVxx\n4471\t656\t1\t3\t1\t11\t71\t471\t471\t4471\t4471\t142\t143\tZPAAAA\tGZAAAA\tAAAAxx\n8085\t657\t1\t1\t5\t5\t85\t85\t85\t3085\t8085\t170\t171\tZYAAAA\tHZAAAA\tHHHHxx\n540\t658\t0\t0\t0\t0\t40\t540\t540\t540\t540\t80\t81\tUUAAAA\tIZAAAA\tOOOOxx\n5108\t659\t0\t0\t8\t8\t8\t108\t1108\t108\t5108\t16\t17\tMOAAAA\tJZAAAA\tVVVVxx\n8015\t660\t1\t3\t5\t15\t15\t15\t15\t3015\t8015\t30\t31\tHWAAAA\tKZAAAA\tAAAAxx\n2857\t661\t1\t1\t7\t17\t57\t857\t857\t2857\t2857\t114\t115\tXFAAAA\tLZAAAA\tHHHHxx\n9472\t662\t0\t0\t2\t12\t72\t472\t1472\t4472\t9472\t144\t145\tIAAAAA\tMZAAAA\tOOOOxx\n5666\t663\t0\t2\t6\t6\t66\t666\t1666\t666\t5666\t132\t133\tYJAAAA\tNZAAAA\tVVVVxx\n3555\t664\t1\t3\t5\t15\t55\t555\t1555\t3555\t3555\t110\t111\tTGAAAA\tOZAAAA\tAAAAxx\n378\t665\t0\t2\t8\t18\t78\t378\t378\t378\t378\t156\t157\tOOAAAA\tPZAAAA\tHHHHxx\n4466\t666\t0\t2\t6\t6\t66\t466\t466\t4466\t4466\t132\t133\tUPAAAA\tQZAAAA\tOOOOxx\n3247\t667\t1\t3\t7\t7\t47\t247\t1247\t3247\t3247\t94\t95\tXUAAAA\tRZAAAA\tVVVVxx\n6570\t668\t0\t2\t0\t10\t70\t570\t570\t1570\t6570\t140\t141\tSSAAAA\tSZAAAA\tAAAAxx\n5655\t669\t1\t3\t5\t15\t55\t655\t1655\t655\t5655\t110\t111\tNJAAAA\tTZAAAA\tHHHHxx\n917\t670\t1\t1\t7\t17\t17\t917\t917\t917\t917\t34\t35\tHJAAAA\tUZAAAA\tOOOOxx\n3637\t671\t1\t1\t7\t17\t37\t637\t1637\t3637\t3637\t74\t75\tXJAAAA\tVZAAAA\tVVVVxx\n3668\t672\t0\t0\t8\t8\t68\t668\t1668\t3668\t3668\t136\t137\tCLAAAA\tWZAAAA\tAAAAxx\n5644\t673\t0\t0\t4\t4\t44\t644\t1644\t644\t5644\t88\t89\tCJAAAA\tXZAAAA\tHHHHxx\n8286\t674\t0\t2\t6\t6\t86\t286\t286\t3286\t8286\t172\t173\tSGAAAA\tYZAAAA\tOOOOxx\n6896\t675\t0\t0\t6\t16\t96\t896\t896\t1896\t6896\t192\t193\tGFAAAA\tZZAAAA\tVVVVxx\n2870\t676\t0\t2\t0\t10\t70\t870\t870\t2870\t2870\t140\t141\tKGAAAA\tAABAAA\tAAAAxx\n8041\t677\t1\t1\t1\t1\t41\t41\t41\t3041\t8041\t82\t83\tHXAAAA\tBABAAA\tHHHHxx\n8137\t678\t1\t1\t7\t17\t37\t137\t137\t3137\t8137\t74\t75\tZAAAAA\tCABAAA\tOOOOxx\n4823\t679\t1\t3\t3\t3\t23\t823\t823\t4823\t4823\t46\t47\tNDAAAA\tDABAAA\tVVVVxx\n2438\t680\t0\t2\t8\t18\t38\t438\t438\t2438\t2438\t76\t77\tUPAAAA\tEABAAA\tAAAAxx\n6329\t681\t1\t1\t9\t9\t29\t329\t329\t1329\t6329\t58\t59\tLJAAAA\tFABAAA\tHHHHxx\n623\t682\t1\t3\t3\t3\t23\t623\t623\t623\t623\t46\t47\tZXAAAA\tGABAAA\tOOOOxx\n1360\t683\t0\t0\t0\t0\t60\t360\t1360\t1360\t1360\t120\t121\tIAAAAA\tHABAAA\tVVVVxx\n7987\t684\t1\t3\t7\t7\t87\t987\t1987\t2987\t7987\t174\t175\tFVAAAA\tIABAAA\tAAAAxx\n9788\t685\t0\t0\t8\t8\t88\t788\t1788\t4788\t9788\t176\t177\tMMAAAA\tJABAAA\tHHHHxx\n3212\t686\t0\t0\t2\t12\t12\t212\t1212\t3212\t3212\t24\t25\tOTAAAA\tKABAAA\tOOOOxx\n2725\t687\t1\t1\t5\t5\t25\t725\t725\t2725\t2725\t50\t51\tVAAAAA\tLABAAA\tVVVVxx\n7837\t688\t1\t1\t7\t17\t37\t837\t1837\t2837\t7837\t74\t75\tLPAAAA\tMABAAA\tAAAAxx\n4746\t689\t0\t2\t6\t6\t46\t746\t746\t4746\t4746\t92\t93\tOAAAAA\tNABAAA\tHHHHxx\n3986\t690\t0\t2\t6\t6\t86\t986\t1986\t3986\t3986\t172\t173\tIXAAAA\tOABAAA\tOOOOxx\n9128\t691\t0\t0\t8\t8\t28\t128\t1128\t4128\t9128\t56\t57\tCNAAAA\tPABAAA\tVVVVxx\n5044\t692\t0\t0\t4\t4\t44\t44\t1044\t44\t5044\t88\t89\tAMAAAA\tQABAAA\tAAAAxx\n8132\t693\t0\t0\t2\t12\t32\t132\t132\t3132\t8132\t64\t65\tUAAAAA\tRABAAA\tHHHHxx\n9992\t694\t0\t0\t2\t12\t92\t992\t1992\t4992\t9992\t184\t185\tIUAAAA\tSABAAA\tOOOOxx\n8468\t695\t0\t0\t8\t8\t68\t468\t468\t3468\t8468\t136\t137\tSNAAAA\tTABAAA\tVVVVxx\n6876\t696\t0\t0\t6\t16\t76\t876\t876\t1876\t6876\t152\t153\tMEAAAA\tUABAAA\tAAAAxx\n3532\t697\t0\t0\t2\t12\t32\t532\t1532\t3532\t3532\t64\t65\tWFAAAA\tVABAAA\tHHHHxx\n2140\t698\t0\t0\t0\t0\t40\t140\t140\t2140\t2140\t80\t81\tIEAAAA\tWABAAA\tOOOOxx\n2183\t699\t1\t3\t3\t3\t83\t183\t183\t2183\t2183\t166\t167\tZFAAAA\tXABAAA\tVVVVxx\n9766\t700\t0\t2\t6\t6\t66\t766\t1766\t4766\t9766\t132\t133\tQLAAAA\tYABAAA\tAAAAxx\n7943\t701\t1\t3\t3\t3\t43\t943\t1943\t2943\t7943\t86\t87\tNTAAAA\tZABAAA\tHHHHxx\n9243\t702\t1\t3\t3\t3\t43\t243\t1243\t4243\t9243\t86\t87\tNRAAAA\tABBAAA\tOOOOxx\n6241\t703\t1\t1\t1\t1\t41\t241\t241\t1241\t6241\t82\t83\tBGAAAA\tBBBAAA\tVVVVxx\n9540\t704\t0\t0\t0\t0\t40\t540\t1540\t4540\t9540\t80\t81\tYCAAAA\tCBBAAA\tAAAAxx\n7418\t705\t0\t2\t8\t18\t18\t418\t1418\t2418\t7418\t36\t37\tIZAAAA\tDBBAAA\tHHHHxx\n1603\t706\t1\t3\t3\t3\t3\t603\t1603\t1603\t1603\t6\t7\tRJAAAA\tEBBAAA\tOOOOxx\n8950\t707\t0\t2\t0\t10\t50\t950\t950\t3950\t8950\t100\t101\tGGAAAA\tFBBAAA\tVVVVxx\n6933\t708\t1\t1\t3\t13\t33\t933\t933\t1933\t6933\t66\t67\tRGAAAA\tGBBAAA\tAAAAxx\n2646\t709\t0\t2\t6\t6\t46\t646\t646\t2646\t2646\t92\t93\tUXAAAA\tHBBAAA\tHHHHxx\n3447\t710\t1\t3\t7\t7\t47\t447\t1447\t3447\t3447\t94\t95\tPCAAAA\tIBBAAA\tOOOOxx\n9957\t711\t1\t1\t7\t17\t57\t957\t1957\t4957\t9957\t114\t115\tZSAAAA\tJBBAAA\tVVVVxx\n4623\t712\t1\t3\t3\t3\t23\t623\t623\t4623\t4623\t46\t47\tVVAAAA\tKBBAAA\tAAAAxx\n9058\t713\t0\t2\t8\t18\t58\t58\t1058\t4058\t9058\t116\t117\tKKAAAA\tLBBAAA\tHHHHxx\n7361\t714\t1\t1\t1\t1\t61\t361\t1361\t2361\t7361\t122\t123\tDXAAAA\tMBBAAA\tOOOOxx\n2489\t715\t1\t1\t9\t9\t89\t489\t489\t2489\t2489\t178\t179\tTRAAAA\tNBBAAA\tVVVVxx\n7643\t716\t1\t3\t3\t3\t43\t643\t1643\t2643\t7643\t86\t87\tZHAAAA\tOBBAAA\tAAAAxx\n9166\t717\t0\t2\t6\t6\t66\t166\t1166\t4166\t9166\t132\t133\tOOAAAA\tPBBAAA\tHHHHxx\n7789\t718\t1\t1\t9\t9\t89\t789\t1789\t2789\t7789\t178\t179\tPNAAAA\tQBBAAA\tOOOOxx\n2332\t719\t0\t0\t2\t12\t32\t332\t332\t2332\t2332\t64\t65\tSLAAAA\tRBBAAA\tVVVVxx\n1832\t720\t0\t0\t2\t12\t32\t832\t1832\t1832\t1832\t64\t65\tMSAAAA\tSBBAAA\tAAAAxx\n8375\t721\t1\t3\t5\t15\t75\t375\t375\t3375\t8375\t150\t151\tDKAAAA\tTBBAAA\tHHHHxx\n948\t722\t0\t0\t8\t8\t48\t948\t948\t948\t948\t96\t97\tMKAAAA\tUBBAAA\tOOOOxx\n5613\t723\t1\t1\t3\t13\t13\t613\t1613\t613\t5613\t26\t27\tXHAAAA\tVBBAAA\tVVVVxx\n6310\t724\t0\t2\t0\t10\t10\t310\t310\t1310\t6310\t20\t21\tSIAAAA\tWBBAAA\tAAAAxx\n4254\t725\t0\t2\t4\t14\t54\t254\t254\t4254\t4254\t108\t109\tQHAAAA\tXBBAAA\tHHHHxx\n4260\t726\t0\t0\t0\t0\t60\t260\t260\t4260\t4260\t120\t121\tWHAAAA\tYBBAAA\tOOOOxx\n2060\t727\t0\t0\t0\t0\t60\t60\t60\t2060\t2060\t120\t121\tGBAAAA\tZBBAAA\tVVVVxx\n4831\t728\t1\t3\t1\t11\t31\t831\t831\t4831\t4831\t62\t63\tVDAAAA\tACBAAA\tAAAAxx\n6176\t729\t0\t0\t6\t16\t76\t176\t176\t1176\t6176\t152\t153\tODAAAA\tBCBAAA\tHHHHxx\n6688\t730\t0\t0\t8\t8\t88\t688\t688\t1688\t6688\t176\t177\tGXAAAA\tCCBAAA\tOOOOxx\n5752\t731\t0\t0\t2\t12\t52\t752\t1752\t752\t5752\t104\t105\tGNAAAA\tDCBAAA\tVVVVxx\n8714\t732\t0\t2\t4\t14\t14\t714\t714\t3714\t8714\t28\t29\tEXAAAA\tECBAAA\tAAAAxx\n6739\t733\t1\t3\t9\t19\t39\t739\t739\t1739\t6739\t78\t79\tFZAAAA\tFCBAAA\tHHHHxx\n7066\t734\t0\t2\t6\t6\t66\t66\t1066\t2066\t7066\t132\t133\tULAAAA\tGCBAAA\tOOOOxx\n7250\t735\t0\t2\t0\t10\t50\t250\t1250\t2250\t7250\t100\t101\tWSAAAA\tHCBAAA\tVVVVxx\n3161\t736\t1\t1\t1\t1\t61\t161\t1161\t3161\t3161\t122\t123\tPRAAAA\tICBAAA\tAAAAxx\n1411\t737\t1\t3\t1\t11\t11\t411\t1411\t1411\t1411\t22\t23\tHCAAAA\tJCBAAA\tHHHHxx\n9301\t738\t1\t1\t1\t1\t1\t301\t1301\t4301\t9301\t2\t3\tTTAAAA\tKCBAAA\tOOOOxx\n8324\t739\t0\t0\t4\t4\t24\t324\t324\t3324\t8324\t48\t49\tEIAAAA\tLCBAAA\tVVVVxx\n9641\t740\t1\t1\t1\t1\t41\t641\t1641\t4641\t9641\t82\t83\tVGAAAA\tMCBAAA\tAAAAxx\n7077\t741\t1\t1\t7\t17\t77\t77\t1077\t2077\t7077\t154\t155\tFMAAAA\tNCBAAA\tHHHHxx\n9888\t742\t0\t0\t8\t8\t88\t888\t1888\t4888\t9888\t176\t177\tIQAAAA\tOCBAAA\tOOOOxx\n9909\t743\t1\t1\t9\t9\t9\t909\t1909\t4909\t9909\t18\t19\tDRAAAA\tPCBAAA\tVVVVxx\n2209\t744\t1\t1\t9\t9\t9\t209\t209\t2209\t2209\t18\t19\tZGAAAA\tQCBAAA\tAAAAxx\n6904\t745\t0\t0\t4\t4\t4\t904\t904\t1904\t6904\t8\t9\tOFAAAA\tRCBAAA\tHHHHxx\n6608\t746\t0\t0\t8\t8\t8\t608\t608\t1608\t6608\t16\t17\tEUAAAA\tSCBAAA\tOOOOxx\n8400\t747\t0\t0\t0\t0\t0\t400\t400\t3400\t8400\t0\t1\tCLAAAA\tTCBAAA\tVVVVxx\n5124\t748\t0\t0\t4\t4\t24\t124\t1124\t124\t5124\t48\t49\tCPAAAA\tUCBAAA\tAAAAxx\n5484\t749\t0\t0\t4\t4\t84\t484\t1484\t484\t5484\t168\t169\tYCAAAA\tVCBAAA\tHHHHxx\n3575\t750\t1\t3\t5\t15\t75\t575\t1575\t3575\t3575\t150\t151\tNHAAAA\tWCBAAA\tOOOOxx\n9723\t751\t1\t3\t3\t3\t23\t723\t1723\t4723\t9723\t46\t47\tZJAAAA\tXCBAAA\tVVVVxx\n360\t752\t0\t0\t0\t0\t60\t360\t360\t360\t360\t120\t121\tWNAAAA\tYCBAAA\tAAAAxx\n1059\t753\t1\t3\t9\t19\t59\t59\t1059\t1059\t1059\t118\t119\tTOAAAA\tZCBAAA\tHHHHxx\n4941\t754\t1\t1\t1\t1\t41\t941\t941\t4941\t4941\t82\t83\tBIAAAA\tADBAAA\tOOOOxx\n2535\t755\t1\t3\t5\t15\t35\t535\t535\t2535\t2535\t70\t71\tNTAAAA\tBDBAAA\tVVVVxx\n4119\t756\t1\t3\t9\t19\t19\t119\t119\t4119\t4119\t38\t39\tLCAAAA\tCDBAAA\tAAAAxx\n3725\t757\t1\t1\t5\t5\t25\t725\t1725\t3725\t3725\t50\t51\tHNAAAA\tDDBAAA\tHHHHxx\n4758\t758\t0\t2\t8\t18\t58\t758\t758\t4758\t4758\t116\t117\tABAAAA\tEDBAAA\tOOOOxx\n9593\t759\t1\t1\t3\t13\t93\t593\t1593\t4593\t9593\t186\t187\tZEAAAA\tFDBAAA\tVVVVxx\n4663\t760\t1\t3\t3\t3\t63\t663\t663\t4663\t4663\t126\t127\tJXAAAA\tGDBAAA\tAAAAxx\n7734\t761\t0\t2\t4\t14\t34\t734\t1734\t2734\t7734\t68\t69\tMLAAAA\tHDBAAA\tHHHHxx\n9156\t762\t0\t0\t6\t16\t56\t156\t1156\t4156\t9156\t112\t113\tEOAAAA\tIDBAAA\tOOOOxx\n8120\t763\t0\t0\t0\t0\t20\t120\t120\t3120\t8120\t40\t41\tIAAAAA\tJDBAAA\tVVVVxx\n4385\t764\t1\t1\t5\t5\t85\t385\t385\t4385\t4385\t170\t171\tRMAAAA\tKDBAAA\tAAAAxx\n2926\t765\t0\t2\t6\t6\t26\t926\t926\t2926\t2926\t52\t53\tOIAAAA\tLDBAAA\tHHHHxx\n4186\t766\t0\t2\t6\t6\t86\t186\t186\t4186\t4186\t172\t173\tAFAAAA\tMDBAAA\tOOOOxx\n2508\t767\t0\t0\t8\t8\t8\t508\t508\t2508\t2508\t16\t17\tMSAAAA\tNDBAAA\tVVVVxx\n4012\t768\t0\t0\t2\t12\t12\t12\t12\t4012\t4012\t24\t25\tIYAAAA\tODBAAA\tAAAAxx\n6266\t769\t0\t2\t6\t6\t66\t266\t266\t1266\t6266\t132\t133\tAHAAAA\tPDBAAA\tHHHHxx\n3709\t770\t1\t1\t9\t9\t9\t709\t1709\t3709\t3709\t18\t19\tRMAAAA\tQDBAAA\tOOOOxx\n7289\t771\t1\t1\t9\t9\t89\t289\t1289\t2289\t7289\t178\t179\tJUAAAA\tRDBAAA\tVVVVxx\n8875\t772\t1\t3\t5\t15\t75\t875\t875\t3875\t8875\t150\t151\tJDAAAA\tSDBAAA\tAAAAxx\n4412\t773\t0\t0\t2\t12\t12\t412\t412\t4412\t4412\t24\t25\tSNAAAA\tTDBAAA\tHHHHxx\n3033\t774\t1\t1\t3\t13\t33\t33\t1033\t3033\t3033\t66\t67\tRMAAAA\tUDBAAA\tOOOOxx\n1645\t775\t1\t1\t5\t5\t45\t645\t1645\t1645\t1645\t90\t91\tHLAAAA\tVDBAAA\tVVVVxx\n3557\t776\t1\t1\t7\t17\t57\t557\t1557\t3557\t3557\t114\t115\tVGAAAA\tWDBAAA\tAAAAxx\n6316\t777\t0\t0\t6\t16\t16\t316\t316\t1316\t6316\t32\t33\tYIAAAA\tXDBAAA\tHHHHxx\n2054\t778\t0\t2\t4\t14\t54\t54\t54\t2054\t2054\t108\t109\tABAAAA\tYDBAAA\tOOOOxx\n7031\t779\t1\t3\t1\t11\t31\t31\t1031\t2031\t7031\t62\t63\tLKAAAA\tZDBAAA\tVVVVxx\n3405\t780\t1\t1\t5\t5\t5\t405\t1405\t3405\t3405\t10\t11\tZAAAAA\tAEBAAA\tAAAAxx\n5343\t781\t1\t3\t3\t3\t43\t343\t1343\t343\t5343\t86\t87\tNXAAAA\tBEBAAA\tHHHHxx\n5240\t782\t0\t0\t0\t0\t40\t240\t1240\t240\t5240\t80\t81\tOTAAAA\tCEBAAA\tOOOOxx\n9650\t783\t0\t2\t0\t10\t50\t650\t1650\t4650\t9650\t100\t101\tEHAAAA\tDEBAAA\tVVVVxx\n3777\t784\t1\t1\t7\t17\t77\t777\t1777\t3777\t3777\t154\t155\tHPAAAA\tEEBAAA\tAAAAxx\n9041\t785\t1\t1\t1\t1\t41\t41\t1041\t4041\t9041\t82\t83\tTJAAAA\tFEBAAA\tHHHHxx\n6923\t786\t1\t3\t3\t3\t23\t923\t923\t1923\t6923\t46\t47\tHGAAAA\tGEBAAA\tOOOOxx\n2977\t787\t1\t1\t7\t17\t77\t977\t977\t2977\t2977\t154\t155\tNKAAAA\tHEBAAA\tVVVVxx\n5500\t788\t0\t0\t0\t0\t0\t500\t1500\t500\t5500\t0\t1\tODAAAA\tIEBAAA\tAAAAxx\n1044\t789\t0\t0\t4\t4\t44\t44\t1044\t1044\t1044\t88\t89\tEOAAAA\tJEBAAA\tHHHHxx\n434\t790\t0\t2\t4\t14\t34\t434\t434\t434\t434\t68\t69\tSQAAAA\tKEBAAA\tOOOOxx\n611\t791\t1\t3\t1\t11\t11\t611\t611\t611\t611\t22\t23\tNXAAAA\tLEBAAA\tVVVVxx\n5760\t792\t0\t0\t0\t0\t60\t760\t1760\t760\t5760\t120\t121\tONAAAA\tMEBAAA\tAAAAxx\n2445\t793\t1\t1\t5\t5\t45\t445\t445\t2445\t2445\t90\t91\tBQAAAA\tNEBAAA\tHHHHxx\n7098\t794\t0\t2\t8\t18\t98\t98\t1098\t2098\t7098\t196\t197\tANAAAA\tOEBAAA\tOOOOxx\n2188\t795\t0\t0\t8\t8\t88\t188\t188\t2188\t2188\t176\t177\tEGAAAA\tPEBAAA\tVVVVxx\n4597\t796\t1\t1\t7\t17\t97\t597\t597\t4597\t4597\t194\t195\tVUAAAA\tQEBAAA\tAAAAxx\n1913\t797\t1\t1\t3\t13\t13\t913\t1913\t1913\t1913\t26\t27\tPVAAAA\tREBAAA\tHHHHxx\n8696\t798\t0\t0\t6\t16\t96\t696\t696\t3696\t8696\t192\t193\tMWAAAA\tSEBAAA\tOOOOxx\n3332\t799\t0\t0\t2\t12\t32\t332\t1332\t3332\t3332\t64\t65\tEYAAAA\tTEBAAA\tVVVVxx\n8760\t800\t0\t0\t0\t0\t60\t760\t760\t3760\t8760\t120\t121\tYYAAAA\tUEBAAA\tAAAAxx\n3215\t801\t1\t3\t5\t15\t15\t215\t1215\t3215\t3215\t30\t31\tRTAAAA\tVEBAAA\tHHHHxx\n1625\t802\t1\t1\t5\t5\t25\t625\t1625\t1625\t1625\t50\t51\tNKAAAA\tWEBAAA\tOOOOxx\n4219\t803\t1\t3\t9\t19\t19\t219\t219\t4219\t4219\t38\t39\tHGAAAA\tXEBAAA\tVVVVxx\n415\t804\t1\t3\t5\t15\t15\t415\t415\t415\t415\t30\t31\tZPAAAA\tYEBAAA\tAAAAxx\n4242\t805\t0\t2\t2\t2\t42\t242\t242\t4242\t4242\t84\t85\tEHAAAA\tZEBAAA\tHHHHxx\n8660\t806\t0\t0\t0\t0\t60\t660\t660\t3660\t8660\t120\t121\tCVAAAA\tAFBAAA\tOOOOxx\n6525\t807\t1\t1\t5\t5\t25\t525\t525\t1525\t6525\t50\t51\tZQAAAA\tBFBAAA\tVVVVxx\n2141\t808\t1\t1\t1\t1\t41\t141\t141\t2141\t2141\t82\t83\tJEAAAA\tCFBAAA\tAAAAxx\n5152\t809\t0\t0\t2\t12\t52\t152\t1152\t152\t5152\t104\t105\tEQAAAA\tDFBAAA\tHHHHxx\n8560\t810\t0\t0\t0\t0\t60\t560\t560\t3560\t8560\t120\t121\tGRAAAA\tEFBAAA\tOOOOxx\n9835\t811\t1\t3\t5\t15\t35\t835\t1835\t4835\t9835\t70\t71\tHOAAAA\tFFBAAA\tVVVVxx\n2657\t812\t1\t1\t7\t17\t57\t657\t657\t2657\t2657\t114\t115\tFYAAAA\tGFBAAA\tAAAAxx\n6085\t813\t1\t1\t5\t5\t85\t85\t85\t1085\t6085\t170\t171\tBAAAAA\tHFBAAA\tHHHHxx\n6698\t814\t0\t2\t8\t18\t98\t698\t698\t1698\t6698\t196\t197\tQXAAAA\tIFBAAA\tOOOOxx\n5421\t815\t1\t1\t1\t1\t21\t421\t1421\t421\t5421\t42\t43\tNAAAAA\tJFBAAA\tVVVVxx\n6661\t816\t1\t1\t1\t1\t61\t661\t661\t1661\t6661\t122\t123\tFWAAAA\tKFBAAA\tAAAAxx\n5645\t817\t1\t1\t5\t5\t45\t645\t1645\t645\t5645\t90\t91\tDJAAAA\tLFBAAA\tHHHHxx\n1248\t818\t0\t0\t8\t8\t48\t248\t1248\t1248\t1248\t96\t97\tAWAAAA\tMFBAAA\tOOOOxx\n5690\t819\t0\t2\t0\t10\t90\t690\t1690\t690\t5690\t180\t181\tWKAAAA\tNFBAAA\tVVVVxx\n4762\t820\t0\t2\t2\t2\t62\t762\t762\t4762\t4762\t124\t125\tEBAAAA\tOFBAAA\tAAAAxx\n1455\t821\t1\t3\t5\t15\t55\t455\t1455\t1455\t1455\t110\t111\tZDAAAA\tPFBAAA\tHHHHxx\n9846\t822\t0\t2\t6\t6\t46\t846\t1846\t4846\t9846\t92\t93\tSOAAAA\tQFBAAA\tOOOOxx\n5295\t823\t1\t3\t5\t15\t95\t295\t1295\t295\t5295\t190\t191\tRVAAAA\tRFBAAA\tVVVVxx\n2826\t824\t0\t2\t6\t6\t26\t826\t826\t2826\t2826\t52\t53\tSEAAAA\tSFBAAA\tAAAAxx\n7496\t825\t0\t0\t6\t16\t96\t496\t1496\t2496\t7496\t192\t193\tICAAAA\tTFBAAA\tHHHHxx\n3024\t826\t0\t0\t4\t4\t24\t24\t1024\t3024\t3024\t48\t49\tIMAAAA\tUFBAAA\tOOOOxx\n4945\t827\t1\t1\t5\t5\t45\t945\t945\t4945\t4945\t90\t91\tFIAAAA\tVFBAAA\tVVVVxx\n4404\t828\t0\t0\t4\t4\t4\t404\t404\t4404\t4404\t8\t9\tKNAAAA\tWFBAAA\tAAAAxx\n9302\t829\t0\t2\t2\t2\t2\t302\t1302\t4302\t9302\t4\t5\tUTAAAA\tXFBAAA\tHHHHxx\n1286\t830\t0\t2\t6\t6\t86\t286\t1286\t1286\t1286\t172\t173\tMXAAAA\tYFBAAA\tOOOOxx\n8435\t831\t1\t3\t5\t15\t35\t435\t435\t3435\t8435\t70\t71\tLMAAAA\tZFBAAA\tVVVVxx\n8969\t832\t1\t1\t9\t9\t69\t969\t969\t3969\t8969\t138\t139\tZGAAAA\tAGBAAA\tAAAAxx\n3302\t833\t0\t2\t2\t2\t2\t302\t1302\t3302\t3302\t4\t5\tAXAAAA\tBGBAAA\tHHHHxx\n9753\t834\t1\t1\t3\t13\t53\t753\t1753\t4753\t9753\t106\t107\tDLAAAA\tCGBAAA\tOOOOxx\n9374\t835\t0\t2\t4\t14\t74\t374\t1374\t4374\t9374\t148\t149\tOWAAAA\tDGBAAA\tVVVVxx\n4907\t836\t1\t3\t7\t7\t7\t907\t907\t4907\t4907\t14\t15\tTGAAAA\tEGBAAA\tAAAAxx\n1659\t837\t1\t3\t9\t19\t59\t659\t1659\t1659\t1659\t118\t119\tVLAAAA\tFGBAAA\tHHHHxx\n5095\t838\t1\t3\t5\t15\t95\t95\t1095\t95\t5095\t190\t191\tZNAAAA\tGGBAAA\tOOOOxx\n9446\t839\t0\t2\t6\t6\t46\t446\t1446\t4446\t9446\t92\t93\tIZAAAA\tHGBAAA\tVVVVxx\n8528\t840\t0\t0\t8\t8\t28\t528\t528\t3528\t8528\t56\t57\tAQAAAA\tIGBAAA\tAAAAxx\n4890\t841\t0\t2\t0\t10\t90\t890\t890\t4890\t4890\t180\t181\tCGAAAA\tJGBAAA\tHHHHxx\n1221\t842\t1\t1\t1\t1\t21\t221\t1221\t1221\t1221\t42\t43\tZUAAAA\tKGBAAA\tOOOOxx\n5583\t843\t1\t3\t3\t3\t83\t583\t1583\t583\t5583\t166\t167\tTGAAAA\tLGBAAA\tVVVVxx\n7303\t844\t1\t3\t3\t3\t3\t303\t1303\t2303\t7303\t6\t7\tXUAAAA\tMGBAAA\tAAAAxx\n406\t845\t0\t2\t6\t6\t6\t406\t406\t406\t406\t12\t13\tQPAAAA\tNGBAAA\tHHHHxx\n7542\t846\t0\t2\t2\t2\t42\t542\t1542\t2542\t7542\t84\t85\tCEAAAA\tOGBAAA\tOOOOxx\n9507\t847\t1\t3\t7\t7\t7\t507\t1507\t4507\t9507\t14\t15\tRBAAAA\tPGBAAA\tVVVVxx\n9511\t848\t1\t3\t1\t11\t11\t511\t1511\t4511\t9511\t22\t23\tVBAAAA\tQGBAAA\tAAAAxx\n1373\t849\t1\t1\t3\t13\t73\t373\t1373\t1373\t1373\t146\t147\tVAAAAA\tRGBAAA\tHHHHxx\n6556\t850\t0\t0\t6\t16\t56\t556\t556\t1556\t6556\t112\t113\tESAAAA\tSGBAAA\tOOOOxx\n4117\t851\t1\t1\t7\t17\t17\t117\t117\t4117\t4117\t34\t35\tJCAAAA\tTGBAAA\tVVVVxx\n7794\t852\t0\t2\t4\t14\t94\t794\t1794\t2794\t7794\t188\t189\tUNAAAA\tUGBAAA\tAAAAxx\n7170\t853\t0\t2\t0\t10\t70\t170\t1170\t2170\t7170\t140\t141\tUPAAAA\tVGBAAA\tHHHHxx\n5809\t854\t1\t1\t9\t9\t9\t809\t1809\t809\t5809\t18\t19\tLPAAAA\tWGBAAA\tOOOOxx\n7828\t855\t0\t0\t8\t8\t28\t828\t1828\t2828\t7828\t56\t57\tCPAAAA\tXGBAAA\tVVVVxx\n8046\t856\t0\t2\t6\t6\t46\t46\t46\t3046\t8046\t92\t93\tMXAAAA\tYGBAAA\tAAAAxx\n4833\t857\t1\t1\t3\t13\t33\t833\t833\t4833\t4833\t66\t67\tXDAAAA\tZGBAAA\tHHHHxx\n2107\t858\t1\t3\t7\t7\t7\t107\t107\t2107\t2107\t14\t15\tBDAAAA\tAHBAAA\tOOOOxx\n4276\t859\t0\t0\t6\t16\t76\t276\t276\t4276\t4276\t152\t153\tMIAAAA\tBHBAAA\tVVVVxx\n9536\t860\t0\t0\t6\t16\t36\t536\t1536\t4536\t9536\t72\t73\tUCAAAA\tCHBAAA\tAAAAxx\n5549\t861\t1\t1\t9\t9\t49\t549\t1549\t549\t5549\t98\t99\tLFAAAA\tDHBAAA\tHHHHxx\n6427\t862\t1\t3\t7\t7\t27\t427\t427\t1427\t6427\t54\t55\tFNAAAA\tEHBAAA\tOOOOxx\n1382\t863\t0\t2\t2\t2\t82\t382\t1382\t1382\t1382\t164\t165\tEBAAAA\tFHBAAA\tVVVVxx\n3256\t864\t0\t0\t6\t16\t56\t256\t1256\t3256\t3256\t112\t113\tGVAAAA\tGHBAAA\tAAAAxx\n3270\t865\t0\t2\t0\t10\t70\t270\t1270\t3270\t3270\t140\t141\tUVAAAA\tHHBAAA\tHHHHxx\n4808\t866\t0\t0\t8\t8\t8\t808\t808\t4808\t4808\t16\t17\tYCAAAA\tIHBAAA\tOOOOxx\n7938\t867\t0\t2\t8\t18\t38\t938\t1938\t2938\t7938\t76\t77\tITAAAA\tJHBAAA\tVVVVxx\n4405\t868\t1\t1\t5\t5\t5\t405\t405\t4405\t4405\t10\t11\tLNAAAA\tKHBAAA\tAAAAxx\n2264\t869\t0\t0\t4\t4\t64\t264\t264\t2264\t2264\t128\t129\tCJAAAA\tLHBAAA\tHHHHxx\n80\t870\t0\t0\t0\t0\t80\t80\t80\t80\t80\t160\t161\tCDAAAA\tMHBAAA\tOOOOxx\n320\t871\t0\t0\t0\t0\t20\t320\t320\t320\t320\t40\t41\tIMAAAA\tNHBAAA\tVVVVxx\n2383\t872\t1\t3\t3\t3\t83\t383\t383\t2383\t2383\t166\t167\tRNAAAA\tOHBAAA\tAAAAxx\n3146\t873\t0\t2\t6\t6\t46\t146\t1146\t3146\t3146\t92\t93\tARAAAA\tPHBAAA\tHHHHxx\n6911\t874\t1\t3\t1\t11\t11\t911\t911\t1911\t6911\t22\t23\tVFAAAA\tQHBAAA\tOOOOxx\n7377\t875\t1\t1\t7\t17\t77\t377\t1377\t2377\t7377\t154\t155\tTXAAAA\tRHBAAA\tVVVVxx\n9965\t876\t1\t1\t5\t5\t65\t965\t1965\t4965\t9965\t130\t131\tHTAAAA\tSHBAAA\tAAAAxx\n8361\t877\t1\t1\t1\t1\t61\t361\t361\t3361\t8361\t122\t123\tPJAAAA\tTHBAAA\tHHHHxx\n9417\t878\t1\t1\t7\t17\t17\t417\t1417\t4417\t9417\t34\t35\tFYAAAA\tUHBAAA\tOOOOxx\n2483\t879\t1\t3\t3\t3\t83\t483\t483\t2483\t2483\t166\t167\tNRAAAA\tVHBAAA\tVVVVxx\n9843\t880\t1\t3\t3\t3\t43\t843\t1843\t4843\t9843\t86\t87\tPOAAAA\tWHBAAA\tAAAAxx\n6395\t881\t1\t3\t5\t15\t95\t395\t395\t1395\t6395\t190\t191\tZLAAAA\tXHBAAA\tHHHHxx\n6444\t882\t0\t0\t4\t4\t44\t444\t444\t1444\t6444\t88\t89\tWNAAAA\tYHBAAA\tOOOOxx\n1820\t883\t0\t0\t0\t0\t20\t820\t1820\t1820\t1820\t40\t41\tASAAAA\tZHBAAA\tVVVVxx\n2768\t884\t0\t0\t8\t8\t68\t768\t768\t2768\t2768\t136\t137\tMCAAAA\tAIBAAA\tAAAAxx\n5413\t885\t1\t1\t3\t13\t13\t413\t1413\t413\t5413\t26\t27\tFAAAAA\tBIBAAA\tHHHHxx\n2923\t886\t1\t3\t3\t3\t23\t923\t923\t2923\t2923\t46\t47\tLIAAAA\tCIBAAA\tOOOOxx\n5286\t887\t0\t2\t6\t6\t86\t286\t1286\t286\t5286\t172\t173\tIVAAAA\tDIBAAA\tVVVVxx\n6126\t888\t0\t2\t6\t6\t26\t126\t126\t1126\t6126\t52\t53\tQBAAAA\tEIBAAA\tAAAAxx\n8343\t889\t1\t3\t3\t3\t43\t343\t343\t3343\t8343\t86\t87\tXIAAAA\tFIBAAA\tHHHHxx\n6010\t890\t0\t2\t0\t10\t10\t10\t10\t1010\t6010\t20\t21\tEXAAAA\tGIBAAA\tOOOOxx\n4177\t891\t1\t1\t7\t17\t77\t177\t177\t4177\t4177\t154\t155\tREAAAA\tHIBAAA\tVVVVxx\n5808\t892\t0\t0\t8\t8\t8\t808\t1808\t808\t5808\t16\t17\tKPAAAA\tIIBAAA\tAAAAxx\n4859\t893\t1\t3\t9\t19\t59\t859\t859\t4859\t4859\t118\t119\tXEAAAA\tJIBAAA\tHHHHxx\n9252\t894\t0\t0\t2\t12\t52\t252\t1252\t4252\t9252\t104\t105\tWRAAAA\tKIBAAA\tOOOOxx\n2941\t895\t1\t1\t1\t1\t41\t941\t941\t2941\t2941\t82\t83\tDJAAAA\tLIBAAA\tVVVVxx\n8693\t896\t1\t1\t3\t13\t93\t693\t693\t3693\t8693\t186\t187\tJWAAAA\tMIBAAA\tAAAAxx\n4432\t897\t0\t0\t2\t12\t32\t432\t432\t4432\t4432\t64\t65\tMOAAAA\tNIBAAA\tHHHHxx\n2371\t898\t1\t3\t1\t11\t71\t371\t371\t2371\t2371\t142\t143\tFNAAAA\tOIBAAA\tOOOOxx\n7546\t899\t0\t2\t6\t6\t46\t546\t1546\t2546\t7546\t92\t93\tGEAAAA\tPIBAAA\tVVVVxx\n1369\t900\t1\t1\t9\t9\t69\t369\t1369\t1369\t1369\t138\t139\tRAAAAA\tQIBAAA\tAAAAxx\n4687\t901\t1\t3\t7\t7\t87\t687\t687\t4687\t4687\t174\t175\tHYAAAA\tRIBAAA\tHHHHxx\n8941\t902\t1\t1\t1\t1\t41\t941\t941\t3941\t8941\t82\t83\tXFAAAA\tSIBAAA\tOOOOxx\n226\t903\t0\t2\t6\t6\t26\t226\t226\t226\t226\t52\t53\tSIAAAA\tTIBAAA\tVVVVxx\n3493\t904\t1\t1\t3\t13\t93\t493\t1493\t3493\t3493\t186\t187\tJEAAAA\tUIBAAA\tAAAAxx\n6433\t905\t1\t1\t3\t13\t33\t433\t433\t1433\t6433\t66\t67\tLNAAAA\tVIBAAA\tHHHHxx\n9189\t906\t1\t1\t9\t9\t89\t189\t1189\t4189\t9189\t178\t179\tLPAAAA\tWIBAAA\tOOOOxx\n6027\t907\t1\t3\t7\t7\t27\t27\t27\t1027\t6027\t54\t55\tVXAAAA\tXIBAAA\tVVVVxx\n4615\t908\t1\t3\t5\t15\t15\t615\t615\t4615\t4615\t30\t31\tNVAAAA\tYIBAAA\tAAAAxx\n5320\t909\t0\t0\t0\t0\t20\t320\t1320\t320\t5320\t40\t41\tQWAAAA\tZIBAAA\tHHHHxx\n7002\t910\t0\t2\t2\t2\t2\t2\t1002\t2002\t7002\t4\t5\tIJAAAA\tAJBAAA\tOOOOxx\n7367\t911\t1\t3\t7\t7\t67\t367\t1367\t2367\t7367\t134\t135\tJXAAAA\tBJBAAA\tVVVVxx\n289\t912\t1\t1\t9\t9\t89\t289\t289\t289\t289\t178\t179\tDLAAAA\tCJBAAA\tAAAAxx\n407\t913\t1\t3\t7\t7\t7\t407\t407\t407\t407\t14\t15\tRPAAAA\tDJBAAA\tHHHHxx\n504\t914\t0\t0\t4\t4\t4\t504\t504\t504\t504\t8\t9\tKTAAAA\tEJBAAA\tOOOOxx\n8301\t915\t1\t1\t1\t1\t1\t301\t301\t3301\t8301\t2\t3\tHHAAAA\tFJBAAA\tVVVVxx\n1396\t916\t0\t0\t6\t16\t96\t396\t1396\t1396\t1396\t192\t193\tSBAAAA\tGJBAAA\tAAAAxx\n4794\t917\t0\t2\t4\t14\t94\t794\t794\t4794\t4794\t188\t189\tKCAAAA\tHJBAAA\tHHHHxx\n6400\t918\t0\t0\t0\t0\t0\t400\t400\t1400\t6400\t0\t1\tEMAAAA\tIJBAAA\tOOOOxx\n1275\t919\t1\t3\t5\t15\t75\t275\t1275\t1275\t1275\t150\t151\tBXAAAA\tJJBAAA\tVVVVxx\n5797\t920\t1\t1\t7\t17\t97\t797\t1797\t797\t5797\t194\t195\tZOAAAA\tKJBAAA\tAAAAxx\n2221\t921\t1\t1\t1\t1\t21\t221\t221\t2221\t2221\t42\t43\tLHAAAA\tLJBAAA\tHHHHxx\n2504\t922\t0\t0\t4\t4\t4\t504\t504\t2504\t2504\t8\t9\tISAAAA\tMJBAAA\tOOOOxx\n2143\t923\t1\t3\t3\t3\t43\t143\t143\t2143\t2143\t86\t87\tLEAAAA\tNJBAAA\tVVVVxx\n1083\t924\t1\t3\t3\t3\t83\t83\t1083\t1083\t1083\t166\t167\tRPAAAA\tOJBAAA\tAAAAxx\n6148\t925\t0\t0\t8\t8\t48\t148\t148\t1148\t6148\t96\t97\tMCAAAA\tPJBAAA\tHHHHxx\n3612\t926\t0\t0\t2\t12\t12\t612\t1612\t3612\t3612\t24\t25\tYIAAAA\tQJBAAA\tOOOOxx\n9499\t927\t1\t3\t9\t19\t99\t499\t1499\t4499\t9499\t198\t199\tJBAAAA\tRJBAAA\tVVVVxx\n5773\t928\t1\t1\t3\t13\t73\t773\t1773\t773\t5773\t146\t147\tBOAAAA\tSJBAAA\tAAAAxx\n1014\t929\t0\t2\t4\t14\t14\t14\t1014\t1014\t1014\t28\t29\tANAAAA\tTJBAAA\tHHHHxx\n1427\t930\t1\t3\t7\t7\t27\t427\t1427\t1427\t1427\t54\t55\tXCAAAA\tUJBAAA\tOOOOxx\n6770\t931\t0\t2\t0\t10\t70\t770\t770\t1770\t6770\t140\t141\tKAAAAA\tVJBAAA\tVVVVxx\n9042\t932\t0\t2\t2\t2\t42\t42\t1042\t4042\t9042\t84\t85\tUJAAAA\tWJBAAA\tAAAAxx\n9892\t933\t0\t0\t2\t12\t92\t892\t1892\t4892\t9892\t184\t185\tMQAAAA\tXJBAAA\tHHHHxx\n1771\t934\t1\t3\t1\t11\t71\t771\t1771\t1771\t1771\t142\t143\tDQAAAA\tYJBAAA\tOOOOxx\n7392\t935\t0\t0\t2\t12\t92\t392\t1392\t2392\t7392\t184\t185\tIYAAAA\tZJBAAA\tVVVVxx\n4465\t936\t1\t1\t5\t5\t65\t465\t465\t4465\t4465\t130\t131\tTPAAAA\tAKBAAA\tAAAAxx\n278\t937\t0\t2\t8\t18\t78\t278\t278\t278\t278\t156\t157\tSKAAAA\tBKBAAA\tHHHHxx\n7776\t938\t0\t0\t6\t16\t76\t776\t1776\t2776\t7776\t152\t153\tCNAAAA\tCKBAAA\tOOOOxx\n3763\t939\t1\t3\t3\t3\t63\t763\t1763\t3763\t3763\t126\t127\tTOAAAA\tDKBAAA\tVVVVxx\n7503\t940\t1\t3\t3\t3\t3\t503\t1503\t2503\t7503\t6\t7\tPCAAAA\tEKBAAA\tAAAAxx\n3793\t941\t1\t1\t3\t13\t93\t793\t1793\t3793\t3793\t186\t187\tXPAAAA\tFKBAAA\tHHHHxx\n6510\t942\t0\t2\t0\t10\t10\t510\t510\t1510\t6510\t20\t21\tKQAAAA\tGKBAAA\tOOOOxx\n7641\t943\t1\t1\t1\t1\t41\t641\t1641\t2641\t7641\t82\t83\tXHAAAA\tHKBAAA\tVVVVxx\n3228\t944\t0\t0\t8\t8\t28\t228\t1228\t3228\t3228\t56\t57\tEUAAAA\tIKBAAA\tAAAAxx\n194\t945\t0\t2\t4\t14\t94\t194\t194\t194\t194\t188\t189\tMHAAAA\tJKBAAA\tHHHHxx\n8555\t946\t1\t3\t5\t15\t55\t555\t555\t3555\t8555\t110\t111\tBRAAAA\tKKBAAA\tOOOOxx\n4997\t947\t1\t1\t7\t17\t97\t997\t997\t4997\t4997\t194\t195\tFKAAAA\tLKBAAA\tVVVVxx\n8687\t948\t1\t3\t7\t7\t87\t687\t687\t3687\t8687\t174\t175\tDWAAAA\tMKBAAA\tAAAAxx\n6632\t949\t0\t0\t2\t12\t32\t632\t632\t1632\t6632\t64\t65\tCVAAAA\tNKBAAA\tHHHHxx\n9607\t950\t1\t3\t7\t7\t7\t607\t1607\t4607\t9607\t14\t15\tNFAAAA\tOKBAAA\tOOOOxx\n6201\t951\t1\t1\t1\t1\t1\t201\t201\t1201\t6201\t2\t3\tNEAAAA\tPKBAAA\tVVVVxx\n857\t952\t1\t1\t7\t17\t57\t857\t857\t857\t857\t114\t115\tZGAAAA\tQKBAAA\tAAAAxx\n5623\t953\t1\t3\t3\t3\t23\t623\t1623\t623\t5623\t46\t47\tHIAAAA\tRKBAAA\tHHHHxx\n5979\t954\t1\t3\t9\t19\t79\t979\t1979\t979\t5979\t158\t159\tZVAAAA\tSKBAAA\tOOOOxx\n2201\t955\t1\t1\t1\t1\t1\t201\t201\t2201\t2201\t2\t3\tRGAAAA\tTKBAAA\tVVVVxx\n3166\t956\t0\t2\t6\t6\t66\t166\t1166\t3166\t3166\t132\t133\tURAAAA\tUKBAAA\tAAAAxx\n6249\t957\t1\t1\t9\t9\t49\t249\t249\t1249\t6249\t98\t99\tJGAAAA\tVKBAAA\tHHHHxx\n3271\t958\t1\t3\t1\t11\t71\t271\t1271\t3271\t3271\t142\t143\tVVAAAA\tWKBAAA\tOOOOxx\n7777\t959\t1\t1\t7\t17\t77\t777\t1777\t2777\t7777\t154\t155\tDNAAAA\tXKBAAA\tVVVVxx\n6732\t960\t0\t0\t2\t12\t32\t732\t732\t1732\t6732\t64\t65\tYYAAAA\tYKBAAA\tAAAAxx\n6297\t961\t1\t1\t7\t17\t97\t297\t297\t1297\t6297\t194\t195\tFIAAAA\tZKBAAA\tHHHHxx\n5685\t962\t1\t1\t5\t5\t85\t685\t1685\t685\t5685\t170\t171\tRKAAAA\tALBAAA\tOOOOxx\n9931\t963\t1\t3\t1\t11\t31\t931\t1931\t4931\t9931\t62\t63\tZRAAAA\tBLBAAA\tVVVVxx\n7485\t964\t1\t1\t5\t5\t85\t485\t1485\t2485\t7485\t170\t171\tXBAAAA\tCLBAAA\tAAAAxx\n386\t965\t0\t2\t6\t6\t86\t386\t386\t386\t386\t172\t173\tWOAAAA\tDLBAAA\tHHHHxx\n8204\t966\t0\t0\t4\t4\t4\t204\t204\t3204\t8204\t8\t9\tODAAAA\tELBAAA\tOOOOxx\n3606\t967\t0\t2\t6\t6\t6\t606\t1606\t3606\t3606\t12\t13\tSIAAAA\tFLBAAA\tVVVVxx\n1692\t968\t0\t0\t2\t12\t92\t692\t1692\t1692\t1692\t184\t185\tCNAAAA\tGLBAAA\tAAAAxx\n3002\t969\t0\t2\t2\t2\t2\t2\t1002\t3002\t3002\t4\t5\tMLAAAA\tHLBAAA\tHHHHxx\n9676\t970\t0\t0\t6\t16\t76\t676\t1676\t4676\t9676\t152\t153\tEIAAAA\tILBAAA\tOOOOxx\n915\t971\t1\t3\t5\t15\t15\t915\t915\t915\t915\t30\t31\tFJAAAA\tJLBAAA\tVVVVxx\n7706\t972\t0\t2\t6\t6\t6\t706\t1706\t2706\t7706\t12\t13\tKKAAAA\tKLBAAA\tAAAAxx\n6080\t973\t0\t0\t0\t0\t80\t80\t80\t1080\t6080\t160\t161\tWZAAAA\tLLBAAA\tHHHHxx\n1860\t974\t0\t0\t0\t0\t60\t860\t1860\t1860\t1860\t120\t121\tOTAAAA\tMLBAAA\tOOOOxx\n1444\t975\t0\t0\t4\t4\t44\t444\t1444\t1444\t1444\t88\t89\tODAAAA\tNLBAAA\tVVVVxx\n7208\t976\t0\t0\t8\t8\t8\t208\t1208\t2208\t7208\t16\t17\tGRAAAA\tOLBAAA\tAAAAxx\n8554\t977\t0\t2\t4\t14\t54\t554\t554\t3554\t8554\t108\t109\tARAAAA\tPLBAAA\tHHHHxx\n2028\t978\t0\t0\t8\t8\t28\t28\t28\t2028\t2028\t56\t57\tAAAAAA\tQLBAAA\tOOOOxx\n9893\t979\t1\t1\t3\t13\t93\t893\t1893\t4893\t9893\t186\t187\tNQAAAA\tRLBAAA\tVVVVxx\n4740\t980\t0\t0\t0\t0\t40\t740\t740\t4740\t4740\t80\t81\tIAAAAA\tSLBAAA\tAAAAxx\n6186\t981\t0\t2\t6\t6\t86\t186\t186\t1186\t6186\t172\t173\tYDAAAA\tTLBAAA\tHHHHxx\n6357\t982\t1\t1\t7\t17\t57\t357\t357\t1357\t6357\t114\t115\tNKAAAA\tULBAAA\tOOOOxx\n3699\t983\t1\t3\t9\t19\t99\t699\t1699\t3699\t3699\t198\t199\tHMAAAA\tVLBAAA\tVVVVxx\n7620\t984\t0\t0\t0\t0\t20\t620\t1620\t2620\t7620\t40\t41\tCHAAAA\tWLBAAA\tAAAAxx\n921\t985\t1\t1\t1\t1\t21\t921\t921\t921\t921\t42\t43\tLJAAAA\tXLBAAA\tHHHHxx\n5506\t986\t0\t2\t6\t6\t6\t506\t1506\t506\t5506\t12\t13\tUDAAAA\tYLBAAA\tOOOOxx\n8851\t987\t1\t3\t1\t11\t51\t851\t851\t3851\t8851\t102\t103\tLCAAAA\tZLBAAA\tVVVVxx\n3205\t988\t1\t1\t5\t5\t5\t205\t1205\t3205\t3205\t10\t11\tHTAAAA\tAMBAAA\tAAAAxx\n1956\t989\t0\t0\t6\t16\t56\t956\t1956\t1956\t1956\t112\t113\tGXAAAA\tBMBAAA\tHHHHxx\n6272\t990\t0\t0\t2\t12\t72\t272\t272\t1272\t6272\t144\t145\tGHAAAA\tCMBAAA\tOOOOxx\n1509\t991\t1\t1\t9\t9\t9\t509\t1509\t1509\t1509\t18\t19\tBGAAAA\tDMBAAA\tVVVVxx\n53\t992\t1\t1\t3\t13\t53\t53\t53\t53\t53\t106\t107\tBCAAAA\tEMBAAA\tAAAAxx\n213\t993\t1\t1\t3\t13\t13\t213\t213\t213\t213\t26\t27\tFIAAAA\tFMBAAA\tHHHHxx\n4924\t994\t0\t0\t4\t4\t24\t924\t924\t4924\t4924\t48\t49\tKHAAAA\tGMBAAA\tOOOOxx\n2097\t995\t1\t1\t7\t17\t97\t97\t97\t2097\t2097\t194\t195\tRCAAAA\tHMBAAA\tVVVVxx\n4607\t996\t1\t3\t7\t7\t7\t607\t607\t4607\t4607\t14\t15\tFVAAAA\tIMBAAA\tAAAAxx\n1582\t997\t0\t2\t2\t2\t82\t582\t1582\t1582\t1582\t164\t165\tWIAAAA\tJMBAAA\tHHHHxx\n6643\t998\t1\t3\t3\t3\t43\t643\t643\t1643\t6643\t86\t87\tNVAAAA\tKMBAAA\tOOOOxx\n2238\t999\t0\t2\t8\t18\t38\t238\t238\t2238\t2238\t76\t77\tCIAAAA\tLMBAAA\tVVVVxx\n2942\t1000\t0\t2\t2\t2\t42\t942\t942\t2942\t2942\t84\t85\tEJAAAA\tMMBAAA\tAAAAxx\n1655\t1001\t1\t3\t5\t15\t55\t655\t1655\t1655\t1655\t110\t111\tRLAAAA\tNMBAAA\tHHHHxx\n3226\t1002\t0\t2\t6\t6\t26\t226\t1226\t3226\t3226\t52\t53\tCUAAAA\tOMBAAA\tOOOOxx\n4263\t1003\t1\t3\t3\t3\t63\t263\t263\t4263\t4263\t126\t127\tZHAAAA\tPMBAAA\tVVVVxx\n960\t1004\t0\t0\t0\t0\t60\t960\t960\t960\t960\t120\t121\tYKAAAA\tQMBAAA\tAAAAxx\n1213\t1005\t1\t1\t3\t13\t13\t213\t1213\t1213\t1213\t26\t27\tRUAAAA\tRMBAAA\tHHHHxx\n1845\t1006\t1\t1\t5\t5\t45\t845\t1845\t1845\t1845\t90\t91\tZSAAAA\tSMBAAA\tOOOOxx\n6944\t1007\t0\t0\t4\t4\t44\t944\t944\t1944\t6944\t88\t89\tCHAAAA\tTMBAAA\tVVVVxx\n5284\t1008\t0\t0\t4\t4\t84\t284\t1284\t284\t5284\t168\t169\tGVAAAA\tUMBAAA\tAAAAxx\n188\t1009\t0\t0\t8\t8\t88\t188\t188\t188\t188\t176\t177\tGHAAAA\tVMBAAA\tHHHHxx\n748\t1010\t0\t0\t8\t8\t48\t748\t748\t748\t748\t96\t97\tUCAAAA\tWMBAAA\tOOOOxx\n2226\t1011\t0\t2\t6\t6\t26\t226\t226\t2226\t2226\t52\t53\tQHAAAA\tXMBAAA\tVVVVxx\n7342\t1012\t0\t2\t2\t2\t42\t342\t1342\t2342\t7342\t84\t85\tKWAAAA\tYMBAAA\tAAAAxx\n6120\t1013\t0\t0\t0\t0\t20\t120\t120\t1120\t6120\t40\t41\tKBAAAA\tZMBAAA\tHHHHxx\n536\t1014\t0\t0\t6\t16\t36\t536\t536\t536\t536\t72\t73\tQUAAAA\tANBAAA\tOOOOxx\n3239\t1015\t1\t3\t9\t19\t39\t239\t1239\t3239\t3239\t78\t79\tPUAAAA\tBNBAAA\tVVVVxx\n2832\t1016\t0\t0\t2\t12\t32\t832\t832\t2832\t2832\t64\t65\tYEAAAA\tCNBAAA\tAAAAxx\n5296\t1017\t0\t0\t6\t16\t96\t296\t1296\t296\t5296\t192\t193\tSVAAAA\tDNBAAA\tHHHHxx\n5795\t1018\t1\t3\t5\t15\t95\t795\t1795\t795\t5795\t190\t191\tXOAAAA\tENBAAA\tOOOOxx\n6290\t1019\t0\t2\t0\t10\t90\t290\t290\t1290\t6290\t180\t181\tYHAAAA\tFNBAAA\tVVVVxx\n4916\t1020\t0\t0\t6\t16\t16\t916\t916\t4916\t4916\t32\t33\tCHAAAA\tGNBAAA\tAAAAxx\n8366\t1021\t0\t2\t6\t6\t66\t366\t366\t3366\t8366\t132\t133\tUJAAAA\tHNBAAA\tHHHHxx\n4248\t1022\t0\t0\t8\t8\t48\t248\t248\t4248\t4248\t96\t97\tKHAAAA\tINBAAA\tOOOOxx\n6460\t1023\t0\t0\t0\t0\t60\t460\t460\t1460\t6460\t120\t121\tMOAAAA\tJNBAAA\tVVVVxx\n9296\t1024\t0\t0\t6\t16\t96\t296\t1296\t4296\t9296\t192\t193\tOTAAAA\tKNBAAA\tAAAAxx\n3486\t1025\t0\t2\t6\t6\t86\t486\t1486\t3486\t3486\t172\t173\tCEAAAA\tLNBAAA\tHHHHxx\n5664\t1026\t0\t0\t4\t4\t64\t664\t1664\t664\t5664\t128\t129\tWJAAAA\tMNBAAA\tOOOOxx\n7624\t1027\t0\t0\t4\t4\t24\t624\t1624\t2624\t7624\t48\t49\tGHAAAA\tNNBAAA\tVVVVxx\n2790\t1028\t0\t2\t0\t10\t90\t790\t790\t2790\t2790\t180\t181\tIDAAAA\tONBAAA\tAAAAxx\n682\t1029\t0\t2\t2\t2\t82\t682\t682\t682\t682\t164\t165\tGAAAAA\tPNBAAA\tHHHHxx\n6412\t1030\t0\t0\t2\t12\t12\t412\t412\t1412\t6412\t24\t25\tQMAAAA\tQNBAAA\tOOOOxx\n6882\t1031\t0\t2\t2\t2\t82\t882\t882\t1882\t6882\t164\t165\tSEAAAA\tRNBAAA\tVVVVxx\n1332\t1032\t0\t0\t2\t12\t32\t332\t1332\t1332\t1332\t64\t65\tGZAAAA\tSNBAAA\tAAAAxx\n4911\t1033\t1\t3\t1\t11\t11\t911\t911\t4911\t4911\t22\t23\tXGAAAA\tTNBAAA\tHHHHxx\n3528\t1034\t0\t0\t8\t8\t28\t528\t1528\t3528\t3528\t56\t57\tSFAAAA\tUNBAAA\tOOOOxx\n271\t1035\t1\t3\t1\t11\t71\t271\t271\t271\t271\t142\t143\tLKAAAA\tVNBAAA\tVVVVxx\n7007\t1036\t1\t3\t7\t7\t7\t7\t1007\t2007\t7007\t14\t15\tNJAAAA\tWNBAAA\tAAAAxx\n2198\t1037\t0\t2\t8\t18\t98\t198\t198\t2198\t2198\t196\t197\tOGAAAA\tXNBAAA\tHHHHxx\n4266\t1038\t0\t2\t6\t6\t66\t266\t266\t4266\t4266\t132\t133\tCIAAAA\tYNBAAA\tOOOOxx\n9867\t1039\t1\t3\t7\t7\t67\t867\t1867\t4867\t9867\t134\t135\tNPAAAA\tZNBAAA\tVVVVxx\n7602\t1040\t0\t2\t2\t2\t2\t602\t1602\t2602\t7602\t4\t5\tKGAAAA\tAOBAAA\tAAAAxx\n7521\t1041\t1\t1\t1\t1\t21\t521\t1521\t2521\t7521\t42\t43\tHDAAAA\tBOBAAA\tHHHHxx\n7200\t1042\t0\t0\t0\t0\t0\t200\t1200\t2200\t7200\t0\t1\tYQAAAA\tCOBAAA\tOOOOxx\n4816\t1043\t0\t0\t6\t16\t16\t816\t816\t4816\t4816\t32\t33\tGDAAAA\tDOBAAA\tVVVVxx\n1669\t1044\t1\t1\t9\t9\t69\t669\t1669\t1669\t1669\t138\t139\tFMAAAA\tEOBAAA\tAAAAxx\n4764\t1045\t0\t0\t4\t4\t64\t764\t764\t4764\t4764\t128\t129\tGBAAAA\tFOBAAA\tHHHHxx\n7393\t1046\t1\t1\t3\t13\t93\t393\t1393\t2393\t7393\t186\t187\tJYAAAA\tGOBAAA\tOOOOxx\n7434\t1047\t0\t2\t4\t14\t34\t434\t1434\t2434\t7434\t68\t69\tYZAAAA\tHOBAAA\tVVVVxx\n9079\t1048\t1\t3\t9\t19\t79\t79\t1079\t4079\t9079\t158\t159\tFLAAAA\tIOBAAA\tAAAAxx\n9668\t1049\t0\t0\t8\t8\t68\t668\t1668\t4668\t9668\t136\t137\tWHAAAA\tJOBAAA\tHHHHxx\n7184\t1050\t0\t0\t4\t4\t84\t184\t1184\t2184\t7184\t168\t169\tIQAAAA\tKOBAAA\tOOOOxx\n7347\t1051\t1\t3\t7\t7\t47\t347\t1347\t2347\t7347\t94\t95\tPWAAAA\tLOBAAA\tVVVVxx\n951\t1052\t1\t3\t1\t11\t51\t951\t951\t951\t951\t102\t103\tPKAAAA\tMOBAAA\tAAAAxx\n4513\t1053\t1\t1\t3\t13\t13\t513\t513\t4513\t4513\t26\t27\tPRAAAA\tNOBAAA\tHHHHxx\n2692\t1054\t0\t0\t2\t12\t92\t692\t692\t2692\t2692\t184\t185\tOZAAAA\tOOBAAA\tOOOOxx\n9930\t1055\t0\t2\t0\t10\t30\t930\t1930\t4930\t9930\t60\t61\tYRAAAA\tPOBAAA\tVVVVxx\n4516\t1056\t0\t0\t6\t16\t16\t516\t516\t4516\t4516\t32\t33\tSRAAAA\tQOBAAA\tAAAAxx\n1592\t1057\t0\t0\t2\t12\t92\t592\t1592\t1592\t1592\t184\t185\tGJAAAA\tROBAAA\tHHHHxx\n6312\t1058\t0\t0\t2\t12\t12\t312\t312\t1312\t6312\t24\t25\tUIAAAA\tSOBAAA\tOOOOxx\n185\t1059\t1\t1\t5\t5\t85\t185\t185\t185\t185\t170\t171\tDHAAAA\tTOBAAA\tVVVVxx\n1848\t1060\t0\t0\t8\t8\t48\t848\t1848\t1848\t1848\t96\t97\tCTAAAA\tUOBAAA\tAAAAxx\n5844\t1061\t0\t0\t4\t4\t44\t844\t1844\t844\t5844\t88\t89\tUQAAAA\tVOBAAA\tHHHHxx\n1666\t1062\t0\t2\t6\t6\t66\t666\t1666\t1666\t1666\t132\t133\tCMAAAA\tWOBAAA\tOOOOxx\n5864\t1063\t0\t0\t4\t4\t64\t864\t1864\t864\t5864\t128\t129\tORAAAA\tXOBAAA\tVVVVxx\n1004\t1064\t0\t0\t4\t4\t4\t4\t1004\t1004\t1004\t8\t9\tQMAAAA\tYOBAAA\tAAAAxx\n1758\t1065\t0\t2\t8\t18\t58\t758\t1758\t1758\t1758\t116\t117\tQPAAAA\tZOBAAA\tHHHHxx\n8823\t1066\t1\t3\t3\t3\t23\t823\t823\t3823\t8823\t46\t47\tJBAAAA\tAPBAAA\tOOOOxx\n129\t1067\t1\t1\t9\t9\t29\t129\t129\t129\t129\t58\t59\tZEAAAA\tBPBAAA\tVVVVxx\n5703\t1068\t1\t3\t3\t3\t3\t703\t1703\t703\t5703\t6\t7\tJLAAAA\tCPBAAA\tAAAAxx\n3331\t1069\t1\t3\t1\t11\t31\t331\t1331\t3331\t3331\t62\t63\tDYAAAA\tDPBAAA\tHHHHxx\n5791\t1070\t1\t3\t1\t11\t91\t791\t1791\t791\t5791\t182\t183\tTOAAAA\tEPBAAA\tOOOOxx\n4421\t1071\t1\t1\t1\t1\t21\t421\t421\t4421\t4421\t42\t43\tBOAAAA\tFPBAAA\tVVVVxx\n9740\t1072\t0\t0\t0\t0\t40\t740\t1740\t4740\t9740\t80\t81\tQKAAAA\tGPBAAA\tAAAAxx\n798\t1073\t0\t2\t8\t18\t98\t798\t798\t798\t798\t196\t197\tSEAAAA\tHPBAAA\tHHHHxx\n571\t1074\t1\t3\t1\t11\t71\t571\t571\t571\t571\t142\t143\tZVAAAA\tIPBAAA\tOOOOxx\n7084\t1075\t0\t0\t4\t4\t84\t84\t1084\t2084\t7084\t168\t169\tMMAAAA\tJPBAAA\tVVVVxx\n650\t1076\t0\t2\t0\t10\t50\t650\t650\t650\t650\t100\t101\tAZAAAA\tKPBAAA\tAAAAxx\n1467\t1077\t1\t3\t7\t7\t67\t467\t1467\t1467\t1467\t134\t135\tLEAAAA\tLPBAAA\tHHHHxx\n5446\t1078\t0\t2\t6\t6\t46\t446\t1446\t446\t5446\t92\t93\tMBAAAA\tMPBAAA\tOOOOxx\n830\t1079\t0\t2\t0\t10\t30\t830\t830\t830\t830\t60\t61\tYFAAAA\tNPBAAA\tVVVVxx\n5516\t1080\t0\t0\t6\t16\t16\t516\t1516\t516\t5516\t32\t33\tEEAAAA\tOPBAAA\tAAAAxx\n8520\t1081\t0\t0\t0\t0\t20\t520\t520\t3520\t8520\t40\t41\tSPAAAA\tPPBAAA\tHHHHxx\n1152\t1082\t0\t0\t2\t12\t52\t152\t1152\t1152\t1152\t104\t105\tISAAAA\tQPBAAA\tOOOOxx\n862\t1083\t0\t2\t2\t2\t62\t862\t862\t862\t862\t124\t125\tEHAAAA\tRPBAAA\tVVVVxx\n454\t1084\t0\t2\t4\t14\t54\t454\t454\t454\t454\t108\t109\tMRAAAA\tSPBAAA\tAAAAxx\n9956\t1085\t0\t0\t6\t16\t56\t956\t1956\t4956\t9956\t112\t113\tYSAAAA\tTPBAAA\tHHHHxx\n1654\t1086\t0\t2\t4\t14\t54\t654\t1654\t1654\t1654\t108\t109\tQLAAAA\tUPBAAA\tOOOOxx\n257\t1087\t1\t1\t7\t17\t57\t257\t257\t257\t257\t114\t115\tXJAAAA\tVPBAAA\tVVVVxx\n5469\t1088\t1\t1\t9\t9\t69\t469\t1469\t469\t5469\t138\t139\tJCAAAA\tWPBAAA\tAAAAxx\n9075\t1089\t1\t3\t5\t15\t75\t75\t1075\t4075\t9075\t150\t151\tBLAAAA\tXPBAAA\tHHHHxx\n7799\t1090\t1\t3\t9\t19\t99\t799\t1799\t2799\t7799\t198\t199\tZNAAAA\tYPBAAA\tOOOOxx\n2001\t1091\t1\t1\t1\t1\t1\t1\t1\t2001\t2001\t2\t3\tZYAAAA\tZPBAAA\tVVVVxx\n9786\t1092\t0\t2\t6\t6\t86\t786\t1786\t4786\t9786\t172\t173\tKMAAAA\tAQBAAA\tAAAAxx\n7281\t1093\t1\t1\t1\t1\t81\t281\t1281\t2281\t7281\t162\t163\tBUAAAA\tBQBAAA\tHHHHxx\n5137\t1094\t1\t1\t7\t17\t37\t137\t1137\t137\t5137\t74\t75\tPPAAAA\tCQBAAA\tOOOOxx\n4053\t1095\t1\t1\t3\t13\t53\t53\t53\t4053\t4053\t106\t107\tXZAAAA\tDQBAAA\tVVVVxx\n7911\t1096\t1\t3\t1\t11\t11\t911\t1911\t2911\t7911\t22\t23\tHSAAAA\tEQBAAA\tAAAAxx\n4298\t1097\t0\t2\t8\t18\t98\t298\t298\t4298\t4298\t196\t197\tIJAAAA\tFQBAAA\tHHHHxx\n4805\t1098\t1\t1\t5\t5\t5\t805\t805\t4805\t4805\t10\t11\tVCAAAA\tGQBAAA\tOOOOxx\n9038\t1099\t0\t2\t8\t18\t38\t38\t1038\t4038\t9038\t76\t77\tQJAAAA\tHQBAAA\tVVVVxx\n8023\t1100\t1\t3\t3\t3\t23\t23\t23\t3023\t8023\t46\t47\tPWAAAA\tIQBAAA\tAAAAxx\n6595\t1101\t1\t3\t5\t15\t95\t595\t595\t1595\t6595\t190\t191\tRTAAAA\tJQBAAA\tHHHHxx\n9831\t1102\t1\t3\t1\t11\t31\t831\t1831\t4831\t9831\t62\t63\tDOAAAA\tKQBAAA\tOOOOxx\n788\t1103\t0\t0\t8\t8\t88\t788\t788\t788\t788\t176\t177\tIEAAAA\tLQBAAA\tVVVVxx\n902\t1104\t0\t2\t2\t2\t2\t902\t902\t902\t902\t4\t5\tSIAAAA\tMQBAAA\tAAAAxx\n9137\t1105\t1\t1\t7\t17\t37\t137\t1137\t4137\t9137\t74\t75\tLNAAAA\tNQBAAA\tHHHHxx\n1744\t1106\t0\t0\t4\t4\t44\t744\t1744\t1744\t1744\t88\t89\tCPAAAA\tOQBAAA\tOOOOxx\n7285\t1107\t1\t1\t5\t5\t85\t285\t1285\t2285\t7285\t170\t171\tFUAAAA\tPQBAAA\tVVVVxx\n7006\t1108\t0\t2\t6\t6\t6\t6\t1006\t2006\t7006\t12\t13\tMJAAAA\tQQBAAA\tAAAAxx\n9236\t1109\t0\t0\t6\t16\t36\t236\t1236\t4236\t9236\t72\t73\tGRAAAA\tRQBAAA\tHHHHxx\n5472\t1110\t0\t0\t2\t12\t72\t472\t1472\t472\t5472\t144\t145\tMCAAAA\tSQBAAA\tOOOOxx\n7975\t1111\t1\t3\t5\t15\t75\t975\t1975\t2975\t7975\t150\t151\tTUAAAA\tTQBAAA\tVVVVxx\n4181\t1112\t1\t1\t1\t1\t81\t181\t181\t4181\t4181\t162\t163\tVEAAAA\tUQBAAA\tAAAAxx\n7677\t1113\t1\t1\t7\t17\t77\t677\t1677\t2677\t7677\t154\t155\tHJAAAA\tVQBAAA\tHHHHxx\n35\t1114\t1\t3\t5\t15\t35\t35\t35\t35\t35\t70\t71\tJBAAAA\tWQBAAA\tOOOOxx\n6813\t1115\t1\t1\t3\t13\t13\t813\t813\t1813\t6813\t26\t27\tBCAAAA\tXQBAAA\tVVVVxx\n6618\t1116\t0\t2\t8\t18\t18\t618\t618\t1618\t6618\t36\t37\tOUAAAA\tYQBAAA\tAAAAxx\n8069\t1117\t1\t1\t9\t9\t69\t69\t69\t3069\t8069\t138\t139\tJYAAAA\tZQBAAA\tHHHHxx\n3071\t1118\t1\t3\t1\t11\t71\t71\t1071\t3071\t3071\t142\t143\tDOAAAA\tARBAAA\tOOOOxx\n4390\t1119\t0\t2\t0\t10\t90\t390\t390\t4390\t4390\t180\t181\tWMAAAA\tBRBAAA\tVVVVxx\n7764\t1120\t0\t0\t4\t4\t64\t764\t1764\t2764\t7764\t128\t129\tQMAAAA\tCRBAAA\tAAAAxx\n8163\t1121\t1\t3\t3\t3\t63\t163\t163\t3163\t8163\t126\t127\tZBAAAA\tDRBAAA\tHHHHxx\n1961\t1122\t1\t1\t1\t1\t61\t961\t1961\t1961\t1961\t122\t123\tLXAAAA\tERBAAA\tOOOOxx\n1103\t1123\t1\t3\t3\t3\t3\t103\t1103\t1103\t1103\t6\t7\tLQAAAA\tFRBAAA\tVVVVxx\n5486\t1124\t0\t2\t6\t6\t86\t486\t1486\t486\t5486\t172\t173\tADAAAA\tGRBAAA\tAAAAxx\n9513\t1125\t1\t1\t3\t13\t13\t513\t1513\t4513\t9513\t26\t27\tXBAAAA\tHRBAAA\tHHHHxx\n7311\t1126\t1\t3\t1\t11\t11\t311\t1311\t2311\t7311\t22\t23\tFVAAAA\tIRBAAA\tOOOOxx\n4144\t1127\t0\t0\t4\t4\t44\t144\t144\t4144\t4144\t88\t89\tKDAAAA\tJRBAAA\tVVVVxx\n7901\t1128\t1\t1\t1\t1\t1\t901\t1901\t2901\t7901\t2\t3\tXRAAAA\tKRBAAA\tAAAAxx\n4629\t1129\t1\t1\t9\t9\t29\t629\t629\t4629\t4629\t58\t59\tBWAAAA\tLRBAAA\tHHHHxx\n6858\t1130\t0\t2\t8\t18\t58\t858\t858\t1858\t6858\t116\t117\tUDAAAA\tMRBAAA\tOOOOxx\n125\t1131\t1\t1\t5\t5\t25\t125\t125\t125\t125\t50\t51\tVEAAAA\tNRBAAA\tVVVVxx\n3834\t1132\t0\t2\t4\t14\t34\t834\t1834\t3834\t3834\t68\t69\tMRAAAA\tORBAAA\tAAAAxx\n8155\t1133\t1\t3\t5\t15\t55\t155\t155\t3155\t8155\t110\t111\tRBAAAA\tPRBAAA\tHHHHxx\n8230\t1134\t0\t2\t0\t10\t30\t230\t230\t3230\t8230\t60\t61\tOEAAAA\tQRBAAA\tOOOOxx\n744\t1135\t0\t0\t4\t4\t44\t744\t744\t744\t744\t88\t89\tQCAAAA\tRRBAAA\tVVVVxx\n357\t1136\t1\t1\t7\t17\t57\t357\t357\t357\t357\t114\t115\tTNAAAA\tSRBAAA\tAAAAxx\n2159\t1137\t1\t3\t9\t19\t59\t159\t159\t2159\t2159\t118\t119\tBFAAAA\tTRBAAA\tHHHHxx\n8559\t1138\t1\t3\t9\t19\t59\t559\t559\t3559\t8559\t118\t119\tFRAAAA\tURBAAA\tOOOOxx\n6866\t1139\t0\t2\t6\t6\t66\t866\t866\t1866\t6866\t132\t133\tCEAAAA\tVRBAAA\tVVVVxx\n3863\t1140\t1\t3\t3\t3\t63\t863\t1863\t3863\t3863\t126\t127\tPSAAAA\tWRBAAA\tAAAAxx\n4193\t1141\t1\t1\t3\t13\t93\t193\t193\t4193\t4193\t186\t187\tHFAAAA\tXRBAAA\tHHHHxx\n3277\t1142\t1\t1\t7\t17\t77\t277\t1277\t3277\t3277\t154\t155\tBWAAAA\tYRBAAA\tOOOOxx\n5577\t1143\t1\t1\t7\t17\t77\t577\t1577\t577\t5577\t154\t155\tNGAAAA\tZRBAAA\tVVVVxx\n9503\t1144\t1\t3\t3\t3\t3\t503\t1503\t4503\t9503\t6\t7\tNBAAAA\tASBAAA\tAAAAxx\n7642\t1145\t0\t2\t2\t2\t42\t642\t1642\t2642\t7642\t84\t85\tYHAAAA\tBSBAAA\tHHHHxx\n6197\t1146\t1\t1\t7\t17\t97\t197\t197\t1197\t6197\t194\t195\tJEAAAA\tCSBAAA\tOOOOxx\n8995\t1147\t1\t3\t5\t15\t95\t995\t995\t3995\t8995\t190\t191\tZHAAAA\tDSBAAA\tVVVVxx\n440\t1148\t0\t0\t0\t0\t40\t440\t440\t440\t440\t80\t81\tYQAAAA\tESBAAA\tAAAAxx\n8418\t1149\t0\t2\t8\t18\t18\t418\t418\t3418\t8418\t36\t37\tULAAAA\tFSBAAA\tHHHHxx\n8531\t1150\t1\t3\t1\t11\t31\t531\t531\t3531\t8531\t62\t63\tDQAAAA\tGSBAAA\tOOOOxx\n3790\t1151\t0\t2\t0\t10\t90\t790\t1790\t3790\t3790\t180\t181\tUPAAAA\tHSBAAA\tVVVVxx\n7610\t1152\t0\t2\t0\t10\t10\t610\t1610\t2610\t7610\t20\t21\tSGAAAA\tISBAAA\tAAAAxx\n1252\t1153\t0\t0\t2\t12\t52\t252\t1252\t1252\t1252\t104\t105\tEWAAAA\tJSBAAA\tHHHHxx\n7559\t1154\t1\t3\t9\t19\t59\t559\t1559\t2559\t7559\t118\t119\tTEAAAA\tKSBAAA\tOOOOxx\n9945\t1155\t1\t1\t5\t5\t45\t945\t1945\t4945\t9945\t90\t91\tNSAAAA\tLSBAAA\tVVVVxx\n9023\t1156\t1\t3\t3\t3\t23\t23\t1023\t4023\t9023\t46\t47\tBJAAAA\tMSBAAA\tAAAAxx\n3516\t1157\t0\t0\t6\t16\t16\t516\t1516\t3516\t3516\t32\t33\tGFAAAA\tNSBAAA\tHHHHxx\n4671\t1158\t1\t3\t1\t11\t71\t671\t671\t4671\t4671\t142\t143\tRXAAAA\tOSBAAA\tOOOOxx\n1465\t1159\t1\t1\t5\t5\t65\t465\t1465\t1465\t1465\t130\t131\tJEAAAA\tPSBAAA\tVVVVxx\n9515\t1160\t1\t3\t5\t15\t15\t515\t1515\t4515\t9515\t30\t31\tZBAAAA\tQSBAAA\tAAAAxx\n3242\t1161\t0\t2\t2\t2\t42\t242\t1242\t3242\t3242\t84\t85\tSUAAAA\tRSBAAA\tHHHHxx\n1732\t1162\t0\t0\t2\t12\t32\t732\t1732\t1732\t1732\t64\t65\tQOAAAA\tSSBAAA\tOOOOxx\n1678\t1163\t0\t2\t8\t18\t78\t678\t1678\t1678\t1678\t156\t157\tOMAAAA\tTSBAAA\tVVVVxx\n1464\t1164\t0\t0\t4\t4\t64\t464\t1464\t1464\t1464\t128\t129\tIEAAAA\tUSBAAA\tAAAAxx\n6546\t1165\t0\t2\t6\t6\t46\t546\t546\t1546\t6546\t92\t93\tURAAAA\tVSBAAA\tHHHHxx\n4448\t1166\t0\t0\t8\t8\t48\t448\t448\t4448\t4448\t96\t97\tCPAAAA\tWSBAAA\tOOOOxx\n9847\t1167\t1\t3\t7\t7\t47\t847\t1847\t4847\t9847\t94\t95\tTOAAAA\tXSBAAA\tVVVVxx\n8264\t1168\t0\t0\t4\t4\t64\t264\t264\t3264\t8264\t128\t129\tWFAAAA\tYSBAAA\tAAAAxx\n1620\t1169\t0\t0\t0\t0\t20\t620\t1620\t1620\t1620\t40\t41\tIKAAAA\tZSBAAA\tHHHHxx\n9388\t1170\t0\t0\t8\t8\t88\t388\t1388\t4388\t9388\t176\t177\tCXAAAA\tATBAAA\tOOOOxx\n6445\t1171\t1\t1\t5\t5\t45\t445\t445\t1445\t6445\t90\t91\tXNAAAA\tBTBAAA\tVVVVxx\n4789\t1172\t1\t1\t9\t9\t89\t789\t789\t4789\t4789\t178\t179\tFCAAAA\tCTBAAA\tAAAAxx\n1562\t1173\t0\t2\t2\t2\t62\t562\t1562\t1562\t1562\t124\t125\tCIAAAA\tDTBAAA\tHHHHxx\n7305\t1174\t1\t1\t5\t5\t5\t305\t1305\t2305\t7305\t10\t11\tZUAAAA\tETBAAA\tOOOOxx\n6344\t1175\t0\t0\t4\t4\t44\t344\t344\t1344\t6344\t88\t89\tAKAAAA\tFTBAAA\tVVVVxx\n5130\t1176\t0\t2\t0\t10\t30\t130\t1130\t130\t5130\t60\t61\tIPAAAA\tGTBAAA\tAAAAxx\n3284\t1177\t0\t0\t4\t4\t84\t284\t1284\t3284\t3284\t168\t169\tIWAAAA\tHTBAAA\tHHHHxx\n6346\t1178\t0\t2\t6\t6\t46\t346\t346\t1346\t6346\t92\t93\tCKAAAA\tITBAAA\tOOOOxx\n1061\t1179\t1\t1\t1\t1\t61\t61\t1061\t1061\t1061\t122\t123\tVOAAAA\tJTBAAA\tVVVVxx\n872\t1180\t0\t0\t2\t12\t72\t872\t872\t872\t872\t144\t145\tOHAAAA\tKTBAAA\tAAAAxx\n123\t1181\t1\t3\t3\t3\t23\t123\t123\t123\t123\t46\t47\tTEAAAA\tLTBAAA\tHHHHxx\n7903\t1182\t1\t3\t3\t3\t3\t903\t1903\t2903\t7903\t6\t7\tZRAAAA\tMTBAAA\tOOOOxx\n560\t1183\t0\t0\t0\t0\t60\t560\t560\t560\t560\t120\t121\tOVAAAA\tNTBAAA\tVVVVxx\n4446\t1184\t0\t2\t6\t6\t46\t446\t446\t4446\t4446\t92\t93\tAPAAAA\tOTBAAA\tAAAAxx\n3909\t1185\t1\t1\t9\t9\t9\t909\t1909\t3909\t3909\t18\t19\tJUAAAA\tPTBAAA\tHHHHxx\n669\t1186\t1\t1\t9\t9\t69\t669\t669\t669\t669\t138\t139\tTZAAAA\tQTBAAA\tOOOOxx\n7843\t1187\t1\t3\t3\t3\t43\t843\t1843\t2843\t7843\t86\t87\tRPAAAA\tRTBAAA\tVVVVxx\n2546\t1188\t0\t2\t6\t6\t46\t546\t546\t2546\t2546\t92\t93\tYTAAAA\tSTBAAA\tAAAAxx\n6757\t1189\t1\t1\t7\t17\t57\t757\t757\t1757\t6757\t114\t115\tXZAAAA\tTTBAAA\tHHHHxx\n466\t1190\t0\t2\t6\t6\t66\t466\t466\t466\t466\t132\t133\tYRAAAA\tUTBAAA\tOOOOxx\n5556\t1191\t0\t0\t6\t16\t56\t556\t1556\t556\t5556\t112\t113\tSFAAAA\tVTBAAA\tVVVVxx\n7196\t1192\t0\t0\t6\t16\t96\t196\t1196\t2196\t7196\t192\t193\tUQAAAA\tWTBAAA\tAAAAxx\n2947\t1193\t1\t3\t7\t7\t47\t947\t947\t2947\t2947\t94\t95\tJJAAAA\tXTBAAA\tHHHHxx\n6493\t1194\t1\t1\t3\t13\t93\t493\t493\t1493\t6493\t186\t187\tTPAAAA\tYTBAAA\tOOOOxx\n7203\t1195\t1\t3\t3\t3\t3\t203\t1203\t2203\t7203\t6\t7\tBRAAAA\tZTBAAA\tVVVVxx\n3716\t1196\t0\t0\t6\t16\t16\t716\t1716\t3716\t3716\t32\t33\tYMAAAA\tAUBAAA\tAAAAxx\n8058\t1197\t0\t2\t8\t18\t58\t58\t58\t3058\t8058\t116\t117\tYXAAAA\tBUBAAA\tHHHHxx\n433\t1198\t1\t1\t3\t13\t33\t433\t433\t433\t433\t66\t67\tRQAAAA\tCUBAAA\tOOOOxx\n7649\t1199\t1\t1\t9\t9\t49\t649\t1649\t2649\t7649\t98\t99\tFIAAAA\tDUBAAA\tVVVVxx\n6966\t1200\t0\t2\t6\t6\t66\t966\t966\t1966\t6966\t132\t133\tYHAAAA\tEUBAAA\tAAAAxx\n553\t1201\t1\t1\t3\t13\t53\t553\t553\t553\t553\t106\t107\tHVAAAA\tFUBAAA\tHHHHxx\n3677\t1202\t1\t1\t7\t17\t77\t677\t1677\t3677\t3677\t154\t155\tLLAAAA\tGUBAAA\tOOOOxx\n2344\t1203\t0\t0\t4\t4\t44\t344\t344\t2344\t2344\t88\t89\tEMAAAA\tHUBAAA\tVVVVxx\n7439\t1204\t1\t3\t9\t19\t39\t439\t1439\t2439\t7439\t78\t79\tDAAAAA\tIUBAAA\tAAAAxx\n3910\t1205\t0\t2\t0\t10\t10\t910\t1910\t3910\t3910\t20\t21\tKUAAAA\tJUBAAA\tHHHHxx\n3638\t1206\t0\t2\t8\t18\t38\t638\t1638\t3638\t3638\t76\t77\tYJAAAA\tKUBAAA\tOOOOxx\n6637\t1207\t1\t1\t7\t17\t37\t637\t637\t1637\t6637\t74\t75\tHVAAAA\tLUBAAA\tVVVVxx\n4438\t1208\t0\t2\t8\t18\t38\t438\t438\t4438\t4438\t76\t77\tSOAAAA\tMUBAAA\tAAAAxx\n171\t1209\t1\t3\t1\t11\t71\t171\t171\t171\t171\t142\t143\tPGAAAA\tNUBAAA\tHHHHxx\n310\t1210\t0\t2\t0\t10\t10\t310\t310\t310\t310\t20\t21\tYLAAAA\tOUBAAA\tOOOOxx\n2714\t1211\t0\t2\t4\t14\t14\t714\t714\t2714\t2714\t28\t29\tKAAAAA\tPUBAAA\tVVVVxx\n5199\t1212\t1\t3\t9\t19\t99\t199\t1199\t199\t5199\t198\t199\tZRAAAA\tQUBAAA\tAAAAxx\n8005\t1213\t1\t1\t5\t5\t5\t5\t5\t3005\t8005\t10\t11\tXVAAAA\tRUBAAA\tHHHHxx\n3188\t1214\t0\t0\t8\t8\t88\t188\t1188\t3188\t3188\t176\t177\tQSAAAA\tSUBAAA\tOOOOxx\n1518\t1215\t0\t2\t8\t18\t18\t518\t1518\t1518\t1518\t36\t37\tKGAAAA\tTUBAAA\tVVVVxx\n6760\t1216\t0\t0\t0\t0\t60\t760\t760\t1760\t6760\t120\t121\tAAAAAA\tUUBAAA\tAAAAxx\n9373\t1217\t1\t1\t3\t13\t73\t373\t1373\t4373\t9373\t146\t147\tNWAAAA\tVUBAAA\tHHHHxx\n1938\t1218\t0\t2\t8\t18\t38\t938\t1938\t1938\t1938\t76\t77\tOWAAAA\tWUBAAA\tOOOOxx\n2865\t1219\t1\t1\t5\t5\t65\t865\t865\t2865\t2865\t130\t131\tFGAAAA\tXUBAAA\tVVVVxx\n3203\t1220\t1\t3\t3\t3\t3\t203\t1203\t3203\t3203\t6\t7\tFTAAAA\tYUBAAA\tAAAAxx\n6025\t1221\t1\t1\t5\t5\t25\t25\t25\t1025\t6025\t50\t51\tTXAAAA\tZUBAAA\tHHHHxx\n8684\t1222\t0\t0\t4\t4\t84\t684\t684\t3684\t8684\t168\t169\tAWAAAA\tAVBAAA\tOOOOxx\n7732\t1223\t0\t0\t2\t12\t32\t732\t1732\t2732\t7732\t64\t65\tKLAAAA\tBVBAAA\tVVVVxx\n3218\t1224\t0\t2\t8\t18\t18\t218\t1218\t3218\t3218\t36\t37\tUTAAAA\tCVBAAA\tAAAAxx\n525\t1225\t1\t1\t5\t5\t25\t525\t525\t525\t525\t50\t51\tFUAAAA\tDVBAAA\tHHHHxx\n601\t1226\t1\t1\t1\t1\t1\t601\t601\t601\t601\t2\t3\tDXAAAA\tEVBAAA\tOOOOxx\n6091\t1227\t1\t3\t1\t11\t91\t91\t91\t1091\t6091\t182\t183\tHAAAAA\tFVBAAA\tVVVVxx\n4498\t1228\t0\t2\t8\t18\t98\t498\t498\t4498\t4498\t196\t197\tARAAAA\tGVBAAA\tAAAAxx\n8192\t1229\t0\t0\t2\t12\t92\t192\t192\t3192\t8192\t184\t185\tCDAAAA\tHVBAAA\tHHHHxx\n8006\t1230\t0\t2\t6\t6\t6\t6\t6\t3006\t8006\t12\t13\tYVAAAA\tIVBAAA\tOOOOxx\n6157\t1231\t1\t1\t7\t17\t57\t157\t157\t1157\t6157\t114\t115\tVCAAAA\tJVBAAA\tVVVVxx\n312\t1232\t0\t0\t2\t12\t12\t312\t312\t312\t312\t24\t25\tAMAAAA\tKVBAAA\tAAAAxx\n8652\t1233\t0\t0\t2\t12\t52\t652\t652\t3652\t8652\t104\t105\tUUAAAA\tLVBAAA\tHHHHxx\n2787\t1234\t1\t3\t7\t7\t87\t787\t787\t2787\t2787\t174\t175\tFDAAAA\tMVBAAA\tOOOOxx\n1782\t1235\t0\t2\t2\t2\t82\t782\t1782\t1782\t1782\t164\t165\tOQAAAA\tNVBAAA\tVVVVxx\n23\t1236\t1\t3\t3\t3\t23\t23\t23\t23\t23\t46\t47\tXAAAAA\tOVBAAA\tAAAAxx\n1206\t1237\t0\t2\t6\t6\t6\t206\t1206\t1206\t1206\t12\t13\tKUAAAA\tPVBAAA\tHHHHxx\n1076\t1238\t0\t0\t6\t16\t76\t76\t1076\t1076\t1076\t152\t153\tKPAAAA\tQVBAAA\tOOOOxx\n5379\t1239\t1\t3\t9\t19\t79\t379\t1379\t379\t5379\t158\t159\tXYAAAA\tRVBAAA\tVVVVxx\n2047\t1240\t1\t3\t7\t7\t47\t47\t47\t2047\t2047\t94\t95\tTAAAAA\tSVBAAA\tAAAAxx\n6262\t1241\t0\t2\t2\t2\t62\t262\t262\t1262\t6262\t124\t125\tWGAAAA\tTVBAAA\tHHHHxx\n1840\t1242\t0\t0\t0\t0\t40\t840\t1840\t1840\t1840\t80\t81\tUSAAAA\tUVBAAA\tOOOOxx\n2106\t1243\t0\t2\t6\t6\t6\t106\t106\t2106\t2106\t12\t13\tADAAAA\tVVBAAA\tVVVVxx\n1307\t1244\t1\t3\t7\t7\t7\t307\t1307\t1307\t1307\t14\t15\tHYAAAA\tWVBAAA\tAAAAxx\n735\t1245\t1\t3\t5\t15\t35\t735\t735\t735\t735\t70\t71\tHCAAAA\tXVBAAA\tHHHHxx\n3657\t1246\t1\t1\t7\t17\t57\t657\t1657\t3657\t3657\t114\t115\tRKAAAA\tYVBAAA\tOOOOxx\n3006\t1247\t0\t2\t6\t6\t6\t6\t1006\t3006\t3006\t12\t13\tQLAAAA\tZVBAAA\tVVVVxx\n1538\t1248\t0\t2\t8\t18\t38\t538\t1538\t1538\t1538\t76\t77\tEHAAAA\tAWBAAA\tAAAAxx\n6098\t1249\t0\t2\t8\t18\t98\t98\t98\t1098\t6098\t196\t197\tOAAAAA\tBWBAAA\tHHHHxx\n5267\t1250\t1\t3\t7\t7\t67\t267\t1267\t267\t5267\t134\t135\tPUAAAA\tCWBAAA\tOOOOxx\n9757\t1251\t1\t1\t7\t17\t57\t757\t1757\t4757\t9757\t114\t115\tHLAAAA\tDWBAAA\tVVVVxx\n1236\t1252\t0\t0\t6\t16\t36\t236\t1236\t1236\t1236\t72\t73\tOVAAAA\tEWBAAA\tAAAAxx\n83\t1253\t1\t3\t3\t3\t83\t83\t83\t83\t83\t166\t167\tFDAAAA\tFWBAAA\tHHHHxx\n9227\t1254\t1\t3\t7\t7\t27\t227\t1227\t4227\t9227\t54\t55\tXQAAAA\tGWBAAA\tOOOOxx\n8772\t1255\t0\t0\t2\t12\t72\t772\t772\t3772\t8772\t144\t145\tKZAAAA\tHWBAAA\tVVVVxx\n8822\t1256\t0\t2\t2\t2\t22\t822\t822\t3822\t8822\t44\t45\tIBAAAA\tIWBAAA\tAAAAxx\n7167\t1257\t1\t3\t7\t7\t67\t167\t1167\t2167\t7167\t134\t135\tRPAAAA\tJWBAAA\tHHHHxx\n6909\t1258\t1\t1\t9\t9\t9\t909\t909\t1909\t6909\t18\t19\tTFAAAA\tKWBAAA\tOOOOxx\n1439\t1259\t1\t3\t9\t19\t39\t439\t1439\t1439\t1439\t78\t79\tJDAAAA\tLWBAAA\tVVVVxx\n2370\t1260\t0\t2\t0\t10\t70\t370\t370\t2370\t2370\t140\t141\tENAAAA\tMWBAAA\tAAAAxx\n4577\t1261\t1\t1\t7\t17\t77\t577\t577\t4577\t4577\t154\t155\tBUAAAA\tNWBAAA\tHHHHxx\n2575\t1262\t1\t3\t5\t15\t75\t575\t575\t2575\t2575\t150\t151\tBVAAAA\tOWBAAA\tOOOOxx\n2795\t1263\t1\t3\t5\t15\t95\t795\t795\t2795\t2795\t190\t191\tNDAAAA\tPWBAAA\tVVVVxx\n5520\t1264\t0\t0\t0\t0\t20\t520\t1520\t520\t5520\t40\t41\tIEAAAA\tQWBAAA\tAAAAxx\n382\t1265\t0\t2\t2\t2\t82\t382\t382\t382\t382\t164\t165\tSOAAAA\tRWBAAA\tHHHHxx\n6335\t1266\t1\t3\t5\t15\t35\t335\t335\t1335\t6335\t70\t71\tRJAAAA\tSWBAAA\tOOOOxx\n8430\t1267\t0\t2\t0\t10\t30\t430\t430\t3430\t8430\t60\t61\tGMAAAA\tTWBAAA\tVVVVxx\n4131\t1268\t1\t3\t1\t11\t31\t131\t131\t4131\t4131\t62\t63\tXCAAAA\tUWBAAA\tAAAAxx\n9332\t1269\t0\t0\t2\t12\t32\t332\t1332\t4332\t9332\t64\t65\tYUAAAA\tVWBAAA\tHHHHxx\n293\t1270\t1\t1\t3\t13\t93\t293\t293\t293\t293\t186\t187\tHLAAAA\tWWBAAA\tOOOOxx\n2276\t1271\t0\t0\t6\t16\t76\t276\t276\t2276\t2276\t152\t153\tOJAAAA\tXWBAAA\tVVVVxx\n5687\t1272\t1\t3\t7\t7\t87\t687\t1687\t687\t5687\t174\t175\tTKAAAA\tYWBAAA\tAAAAxx\n5862\t1273\t0\t2\t2\t2\t62\t862\t1862\t862\t5862\t124\t125\tMRAAAA\tZWBAAA\tHHHHxx\n5073\t1274\t1\t1\t3\t13\t73\t73\t1073\t73\t5073\t146\t147\tDNAAAA\tAXBAAA\tOOOOxx\n4170\t1275\t0\t2\t0\t10\t70\t170\t170\t4170\t4170\t140\t141\tKEAAAA\tBXBAAA\tVVVVxx\n5039\t1276\t1\t3\t9\t19\t39\t39\t1039\t39\t5039\t78\t79\tVLAAAA\tCXBAAA\tAAAAxx\n3294\t1277\t0\t2\t4\t14\t94\t294\t1294\t3294\t3294\t188\t189\tSWAAAA\tDXBAAA\tHHHHxx\n6015\t1278\t1\t3\t5\t15\t15\t15\t15\t1015\t6015\t30\t31\tJXAAAA\tEXBAAA\tOOOOxx\n9015\t1279\t1\t3\t5\t15\t15\t15\t1015\t4015\t9015\t30\t31\tTIAAAA\tFXBAAA\tVVVVxx\n9785\t1280\t1\t1\t5\t5\t85\t785\t1785\t4785\t9785\t170\t171\tJMAAAA\tGXBAAA\tAAAAxx\n4312\t1281\t0\t0\t2\t12\t12\t312\t312\t4312\t4312\t24\t25\tWJAAAA\tHXBAAA\tHHHHxx\n6343\t1282\t1\t3\t3\t3\t43\t343\t343\t1343\t6343\t86\t87\tZJAAAA\tIXBAAA\tOOOOxx\n2161\t1283\t1\t1\t1\t1\t61\t161\t161\t2161\t2161\t122\t123\tDFAAAA\tJXBAAA\tVVVVxx\n4490\t1284\t0\t2\t0\t10\t90\t490\t490\t4490\t4490\t180\t181\tSQAAAA\tKXBAAA\tAAAAxx\n4454\t1285\t0\t2\t4\t14\t54\t454\t454\t4454\t4454\t108\t109\tIPAAAA\tLXBAAA\tHHHHxx\n7647\t1286\t1\t3\t7\t7\t47\t647\t1647\t2647\t7647\t94\t95\tDIAAAA\tMXBAAA\tOOOOxx\n1028\t1287\t0\t0\t8\t8\t28\t28\t1028\t1028\t1028\t56\t57\tONAAAA\tNXBAAA\tVVVVxx\n2965\t1288\t1\t1\t5\t5\t65\t965\t965\t2965\t2965\t130\t131\tBKAAAA\tOXBAAA\tAAAAxx\n9900\t1289\t0\t0\t0\t0\t0\t900\t1900\t4900\t9900\t0\t1\tUQAAAA\tPXBAAA\tHHHHxx\n5509\t1290\t1\t1\t9\t9\t9\t509\t1509\t509\t5509\t18\t19\tXDAAAA\tQXBAAA\tOOOOxx\n7751\t1291\t1\t3\t1\t11\t51\t751\t1751\t2751\t7751\t102\t103\tDMAAAA\tRXBAAA\tVVVVxx\n9594\t1292\t0\t2\t4\t14\t94\t594\t1594\t4594\t9594\t188\t189\tAFAAAA\tSXBAAA\tAAAAxx\n7632\t1293\t0\t0\t2\t12\t32\t632\t1632\t2632\t7632\t64\t65\tOHAAAA\tTXBAAA\tHHHHxx\n6528\t1294\t0\t0\t8\t8\t28\t528\t528\t1528\t6528\t56\t57\tCRAAAA\tUXBAAA\tOOOOxx\n1041\t1295\t1\t1\t1\t1\t41\t41\t1041\t1041\t1041\t82\t83\tBOAAAA\tVXBAAA\tVVVVxx\n1534\t1296\t0\t2\t4\t14\t34\t534\t1534\t1534\t1534\t68\t69\tAHAAAA\tWXBAAA\tAAAAxx\n4229\t1297\t1\t1\t9\t9\t29\t229\t229\t4229\t4229\t58\t59\tRGAAAA\tXXBAAA\tHHHHxx\n84\t1298\t0\t0\t4\t4\t84\t84\t84\t84\t84\t168\t169\tGDAAAA\tYXBAAA\tOOOOxx\n2189\t1299\t1\t1\t9\t9\t89\t189\t189\t2189\t2189\t178\t179\tFGAAAA\tZXBAAA\tVVVVxx\n7566\t1300\t0\t2\t6\t6\t66\t566\t1566\t2566\t7566\t132\t133\tAFAAAA\tAYBAAA\tAAAAxx\n707\t1301\t1\t3\t7\t7\t7\t707\t707\t707\t707\t14\t15\tFBAAAA\tBYBAAA\tHHHHxx\n581\t1302\t1\t1\t1\t1\t81\t581\t581\t581\t581\t162\t163\tJWAAAA\tCYBAAA\tOOOOxx\n6753\t1303\t1\t1\t3\t13\t53\t753\t753\t1753\t6753\t106\t107\tTZAAAA\tDYBAAA\tVVVVxx\n8604\t1304\t0\t0\t4\t4\t4\t604\t604\t3604\t8604\t8\t9\tYSAAAA\tEYBAAA\tAAAAxx\n373\t1305\t1\t1\t3\t13\t73\t373\t373\t373\t373\t146\t147\tJOAAAA\tFYBAAA\tHHHHxx\n9635\t1306\t1\t3\t5\t15\t35\t635\t1635\t4635\t9635\t70\t71\tPGAAAA\tGYBAAA\tOOOOxx\n9277\t1307\t1\t1\t7\t17\t77\t277\t1277\t4277\t9277\t154\t155\tVSAAAA\tHYBAAA\tVVVVxx\n7117\t1308\t1\t1\t7\t17\t17\t117\t1117\t2117\t7117\t34\t35\tTNAAAA\tIYBAAA\tAAAAxx\n8564\t1309\t0\t0\t4\t4\t64\t564\t564\t3564\t8564\t128\t129\tKRAAAA\tJYBAAA\tHHHHxx\n1697\t1310\t1\t1\t7\t17\t97\t697\t1697\t1697\t1697\t194\t195\tHNAAAA\tKYBAAA\tOOOOxx\n7840\t1311\t0\t0\t0\t0\t40\t840\t1840\t2840\t7840\t80\t81\tOPAAAA\tLYBAAA\tVVVVxx\n3646\t1312\t0\t2\t6\t6\t46\t646\t1646\t3646\t3646\t92\t93\tGKAAAA\tMYBAAA\tAAAAxx\n368\t1313\t0\t0\t8\t8\t68\t368\t368\t368\t368\t136\t137\tEOAAAA\tNYBAAA\tHHHHxx\n4797\t1314\t1\t1\t7\t17\t97\t797\t797\t4797\t4797\t194\t195\tNCAAAA\tOYBAAA\tOOOOxx\n5300\t1315\t0\t0\t0\t0\t0\t300\t1300\t300\t5300\t0\t1\tWVAAAA\tPYBAAA\tVVVVxx\n7664\t1316\t0\t0\t4\t4\t64\t664\t1664\t2664\t7664\t128\t129\tUIAAAA\tQYBAAA\tAAAAxx\n1466\t1317\t0\t2\t6\t6\t66\t466\t1466\t1466\t1466\t132\t133\tKEAAAA\tRYBAAA\tHHHHxx\n2477\t1318\t1\t1\t7\t17\t77\t477\t477\t2477\t2477\t154\t155\tHRAAAA\tSYBAAA\tOOOOxx\n2036\t1319\t0\t0\t6\t16\t36\t36\t36\t2036\t2036\t72\t73\tIAAAAA\tTYBAAA\tVVVVxx\n3624\t1320\t0\t0\t4\t4\t24\t624\t1624\t3624\t3624\t48\t49\tKJAAAA\tUYBAAA\tAAAAxx\n5099\t1321\t1\t3\t9\t19\t99\t99\t1099\t99\t5099\t198\t199\tDOAAAA\tVYBAAA\tHHHHxx\n1308\t1322\t0\t0\t8\t8\t8\t308\t1308\t1308\t1308\t16\t17\tIYAAAA\tWYBAAA\tOOOOxx\n3704\t1323\t0\t0\t4\t4\t4\t704\t1704\t3704\t3704\t8\t9\tMMAAAA\tXYBAAA\tVVVVxx\n2451\t1324\t1\t3\t1\t11\t51\t451\t451\t2451\t2451\t102\t103\tHQAAAA\tYYBAAA\tAAAAxx\n4898\t1325\t0\t2\t8\t18\t98\t898\t898\t4898\t4898\t196\t197\tKGAAAA\tZYBAAA\tHHHHxx\n4959\t1326\t1\t3\t9\t19\t59\t959\t959\t4959\t4959\t118\t119\tTIAAAA\tAZBAAA\tOOOOxx\n5942\t1327\t0\t2\t2\t2\t42\t942\t1942\t942\t5942\t84\t85\tOUAAAA\tBZBAAA\tVVVVxx\n2425\t1328\t1\t1\t5\t5\t25\t425\t425\t2425\t2425\t50\t51\tHPAAAA\tCZBAAA\tAAAAxx\n7760\t1329\t0\t0\t0\t0\t60\t760\t1760\t2760\t7760\t120\t121\tMMAAAA\tDZBAAA\tHHHHxx\n6294\t1330\t0\t2\t4\t14\t94\t294\t294\t1294\t6294\t188\t189\tCIAAAA\tEZBAAA\tOOOOxx\n6785\t1331\t1\t1\t5\t5\t85\t785\t785\t1785\t6785\t170\t171\tZAAAAA\tFZBAAA\tVVVVxx\n3542\t1332\t0\t2\t2\t2\t42\t542\t1542\t3542\t3542\t84\t85\tGGAAAA\tGZBAAA\tAAAAxx\n1809\t1333\t1\t1\t9\t9\t9\t809\t1809\t1809\t1809\t18\t19\tPRAAAA\tHZBAAA\tHHHHxx\n130\t1334\t0\t2\t0\t10\t30\t130\t130\t130\t130\t60\t61\tAFAAAA\tIZBAAA\tOOOOxx\n8672\t1335\t0\t0\t2\t12\t72\t672\t672\t3672\t8672\t144\t145\tOVAAAA\tJZBAAA\tVVVVxx\n2125\t1336\t1\t1\t5\t5\t25\t125\t125\t2125\t2125\t50\t51\tTDAAAA\tKZBAAA\tAAAAxx\n7683\t1337\t1\t3\t3\t3\t83\t683\t1683\t2683\t7683\t166\t167\tNJAAAA\tLZBAAA\tHHHHxx\n7842\t1338\t0\t2\t2\t2\t42\t842\t1842\t2842\t7842\t84\t85\tQPAAAA\tMZBAAA\tOOOOxx\n9584\t1339\t0\t0\t4\t4\t84\t584\t1584\t4584\t9584\t168\t169\tQEAAAA\tNZBAAA\tVVVVxx\n7963\t1340\t1\t3\t3\t3\t63\t963\t1963\t2963\t7963\t126\t127\tHUAAAA\tOZBAAA\tAAAAxx\n8581\t1341\t1\t1\t1\t1\t81\t581\t581\t3581\t8581\t162\t163\tBSAAAA\tPZBAAA\tHHHHxx\n2135\t1342\t1\t3\t5\t15\t35\t135\t135\t2135\t2135\t70\t71\tDEAAAA\tQZBAAA\tOOOOxx\n7352\t1343\t0\t0\t2\t12\t52\t352\t1352\t2352\t7352\t104\t105\tUWAAAA\tRZBAAA\tVVVVxx\n5789\t1344\t1\t1\t9\t9\t89\t789\t1789\t789\t5789\t178\t179\tROAAAA\tSZBAAA\tAAAAxx\n8490\t1345\t0\t2\t0\t10\t90\t490\t490\t3490\t8490\t180\t181\tOOAAAA\tTZBAAA\tHHHHxx\n2145\t1346\t1\t1\t5\t5\t45\t145\t145\t2145\t2145\t90\t91\tNEAAAA\tUZBAAA\tOOOOxx\n7021\t1347\t1\t1\t1\t1\t21\t21\t1021\t2021\t7021\t42\t43\tBKAAAA\tVZBAAA\tVVVVxx\n3736\t1348\t0\t0\t6\t16\t36\t736\t1736\t3736\t3736\t72\t73\tSNAAAA\tWZBAAA\tAAAAxx\n7396\t1349\t0\t0\t6\t16\t96\t396\t1396\t2396\t7396\t192\t193\tMYAAAA\tXZBAAA\tHHHHxx\n6334\t1350\t0\t2\t4\t14\t34\t334\t334\t1334\t6334\t68\t69\tQJAAAA\tYZBAAA\tOOOOxx\n5461\t1351\t1\t1\t1\t1\t61\t461\t1461\t461\t5461\t122\t123\tBCAAAA\tZZBAAA\tVVVVxx\n5337\t1352\t1\t1\t7\t17\t37\t337\t1337\t337\t5337\t74\t75\tHXAAAA\tAACAAA\tAAAAxx\n7440\t1353\t0\t0\t0\t0\t40\t440\t1440\t2440\t7440\t80\t81\tEAAAAA\tBACAAA\tHHHHxx\n6879\t1354\t1\t3\t9\t19\t79\t879\t879\t1879\t6879\t158\t159\tPEAAAA\tCACAAA\tOOOOxx\n2432\t1355\t0\t0\t2\t12\t32\t432\t432\t2432\t2432\t64\t65\tOPAAAA\tDACAAA\tVVVVxx\n8529\t1356\t1\t1\t9\t9\t29\t529\t529\t3529\t8529\t58\t59\tBQAAAA\tEACAAA\tAAAAxx\n7859\t1357\t1\t3\t9\t19\t59\t859\t1859\t2859\t7859\t118\t119\tHQAAAA\tFACAAA\tHHHHxx\n15\t1358\t1\t3\t5\t15\t15\t15\t15\t15\t15\t30\t31\tPAAAAA\tGACAAA\tOOOOxx\n7475\t1359\t1\t3\t5\t15\t75\t475\t1475\t2475\t7475\t150\t151\tNBAAAA\tHACAAA\tVVVVxx\n717\t1360\t1\t1\t7\t17\t17\t717\t717\t717\t717\t34\t35\tPBAAAA\tIACAAA\tAAAAxx\n250\t1361\t0\t2\t0\t10\t50\t250\t250\t250\t250\t100\t101\tQJAAAA\tJACAAA\tHHHHxx\n4700\t1362\t0\t0\t0\t0\t0\t700\t700\t4700\t4700\t0\t1\tUYAAAA\tKACAAA\tOOOOxx\n7510\t1363\t0\t2\t0\t10\t10\t510\t1510\t2510\t7510\t20\t21\tWCAAAA\tLACAAA\tVVVVxx\n4562\t1364\t0\t2\t2\t2\t62\t562\t562\t4562\t4562\t124\t125\tMTAAAA\tMACAAA\tAAAAxx\n8075\t1365\t1\t3\t5\t15\t75\t75\t75\t3075\t8075\t150\t151\tPYAAAA\tNACAAA\tHHHHxx\n871\t1366\t1\t3\t1\t11\t71\t871\t871\t871\t871\t142\t143\tNHAAAA\tOACAAA\tOOOOxx\n7161\t1367\t1\t1\t1\t1\t61\t161\t1161\t2161\t7161\t122\t123\tLPAAAA\tPACAAA\tVVVVxx\n9109\t1368\t1\t1\t9\t9\t9\t109\t1109\t4109\t9109\t18\t19\tJMAAAA\tQACAAA\tAAAAxx\n8675\t1369\t1\t3\t5\t15\t75\t675\t675\t3675\t8675\t150\t151\tRVAAAA\tRACAAA\tHHHHxx\n1025\t1370\t1\t1\t5\t5\t25\t25\t1025\t1025\t1025\t50\t51\tLNAAAA\tSACAAA\tOOOOxx\n4065\t1371\t1\t1\t5\t5\t65\t65\t65\t4065\t4065\t130\t131\tJAAAAA\tTACAAA\tVVVVxx\n3511\t1372\t1\t3\t1\t11\t11\t511\t1511\t3511\t3511\t22\t23\tBFAAAA\tUACAAA\tAAAAxx\n9840\t1373\t0\t0\t0\t0\t40\t840\t1840\t4840\t9840\t80\t81\tMOAAAA\tVACAAA\tHHHHxx\n7495\t1374\t1\t3\t5\t15\t95\t495\t1495\t2495\t7495\t190\t191\tHCAAAA\tWACAAA\tOOOOxx\n55\t1375\t1\t3\t5\t15\t55\t55\t55\t55\t55\t110\t111\tDCAAAA\tXACAAA\tVVVVxx\n6151\t1376\t1\t3\t1\t11\t51\t151\t151\t1151\t6151\t102\t103\tPCAAAA\tYACAAA\tAAAAxx\n2512\t1377\t0\t0\t2\t12\t12\t512\t512\t2512\t2512\t24\t25\tQSAAAA\tZACAAA\tHHHHxx\n5881\t1378\t1\t1\t1\t1\t81\t881\t1881\t881\t5881\t162\t163\tFSAAAA\tABCAAA\tOOOOxx\n1442\t1379\t0\t2\t2\t2\t42\t442\t1442\t1442\t1442\t84\t85\tMDAAAA\tBBCAAA\tVVVVxx\n1270\t1380\t0\t2\t0\t10\t70\t270\t1270\t1270\t1270\t140\t141\tWWAAAA\tCBCAAA\tAAAAxx\n959\t1381\t1\t3\t9\t19\t59\t959\t959\t959\t959\t118\t119\tXKAAAA\tDBCAAA\tHHHHxx\n8251\t1382\t1\t3\t1\t11\t51\t251\t251\t3251\t8251\t102\t103\tJFAAAA\tEBCAAA\tOOOOxx\n3051\t1383\t1\t3\t1\t11\t51\t51\t1051\t3051\t3051\t102\t103\tJNAAAA\tFBCAAA\tVVVVxx\n5052\t1384\t0\t0\t2\t12\t52\t52\t1052\t52\t5052\t104\t105\tIMAAAA\tGBCAAA\tAAAAxx\n1863\t1385\t1\t3\t3\t3\t63\t863\t1863\t1863\t1863\t126\t127\tRTAAAA\tHBCAAA\tHHHHxx\n344\t1386\t0\t0\t4\t4\t44\t344\t344\t344\t344\t88\t89\tGNAAAA\tIBCAAA\tOOOOxx\n3590\t1387\t0\t2\t0\t10\t90\t590\t1590\t3590\t3590\t180\t181\tCIAAAA\tJBCAAA\tVVVVxx\n4223\t1388\t1\t3\t3\t3\t23\t223\t223\t4223\t4223\t46\t47\tLGAAAA\tKBCAAA\tAAAAxx\n2284\t1389\t0\t0\t4\t4\t84\t284\t284\t2284\t2284\t168\t169\tWJAAAA\tLBCAAA\tHHHHxx\n9425\t1390\t1\t1\t5\t5\t25\t425\t1425\t4425\t9425\t50\t51\tNYAAAA\tMBCAAA\tOOOOxx\n6221\t1391\t1\t1\t1\t1\t21\t221\t221\t1221\t6221\t42\t43\tHFAAAA\tNBCAAA\tVVVVxx\n195\t1392\t1\t3\t5\t15\t95\t195\t195\t195\t195\t190\t191\tNHAAAA\tOBCAAA\tAAAAxx\n1517\t1393\t1\t1\t7\t17\t17\t517\t1517\t1517\t1517\t34\t35\tJGAAAA\tPBCAAA\tHHHHxx\n3791\t1394\t1\t3\t1\t11\t91\t791\t1791\t3791\t3791\t182\t183\tVPAAAA\tQBCAAA\tOOOOxx\n572\t1395\t0\t0\t2\t12\t72\t572\t572\t572\t572\t144\t145\tAWAAAA\tRBCAAA\tVVVVxx\n46\t1396\t0\t2\t6\t6\t46\t46\t46\t46\t46\t92\t93\tUBAAAA\tSBCAAA\tAAAAxx\n9451\t1397\t1\t3\t1\t11\t51\t451\t1451\t4451\t9451\t102\t103\tNZAAAA\tTBCAAA\tHHHHxx\n3359\t1398\t1\t3\t9\t19\t59\t359\t1359\t3359\t3359\t118\t119\tFZAAAA\tUBCAAA\tOOOOxx\n8867\t1399\t1\t3\t7\t7\t67\t867\t867\t3867\t8867\t134\t135\tBDAAAA\tVBCAAA\tVVVVxx\n674\t1400\t0\t2\t4\t14\t74\t674\t674\t674\t674\t148\t149\tYZAAAA\tWBCAAA\tAAAAxx\n2674\t1401\t0\t2\t4\t14\t74\t674\t674\t2674\t2674\t148\t149\tWYAAAA\tXBCAAA\tHHHHxx\n6523\t1402\t1\t3\t3\t3\t23\t523\t523\t1523\t6523\t46\t47\tXQAAAA\tYBCAAA\tOOOOxx\n6210\t1403\t0\t2\t0\t10\t10\t210\t210\t1210\t6210\t20\t21\tWEAAAA\tZBCAAA\tVVVVxx\n7564\t1404\t0\t0\t4\t4\t64\t564\t1564\t2564\t7564\t128\t129\tYEAAAA\tACCAAA\tAAAAxx\n4776\t1405\t0\t0\t6\t16\t76\t776\t776\t4776\t4776\t152\t153\tSBAAAA\tBCCAAA\tHHHHxx\n2993\t1406\t1\t1\t3\t13\t93\t993\t993\t2993\t2993\t186\t187\tDLAAAA\tCCCAAA\tOOOOxx\n2969\t1407\t1\t1\t9\t9\t69\t969\t969\t2969\t2969\t138\t139\tFKAAAA\tDCCAAA\tVVVVxx\n1762\t1408\t0\t2\t2\t2\t62\t762\t1762\t1762\t1762\t124\t125\tUPAAAA\tECCAAA\tAAAAxx\n685\t1409\t1\t1\t5\t5\t85\t685\t685\t685\t685\t170\t171\tJAAAAA\tFCCAAA\tHHHHxx\n5312\t1410\t0\t0\t2\t12\t12\t312\t1312\t312\t5312\t24\t25\tIWAAAA\tGCCAAA\tOOOOxx\n3264\t1411\t0\t0\t4\t4\t64\t264\t1264\t3264\t3264\t128\t129\tOVAAAA\tHCCAAA\tVVVVxx\n7008\t1412\t0\t0\t8\t8\t8\t8\t1008\t2008\t7008\t16\t17\tOJAAAA\tICCAAA\tAAAAxx\n5167\t1413\t1\t3\t7\t7\t67\t167\t1167\t167\t5167\t134\t135\tTQAAAA\tJCCAAA\tHHHHxx\n3060\t1414\t0\t0\t0\t0\t60\t60\t1060\t3060\t3060\t120\t121\tSNAAAA\tKCCAAA\tOOOOxx\n1752\t1415\t0\t0\t2\t12\t52\t752\t1752\t1752\t1752\t104\t105\tKPAAAA\tLCCAAA\tVVVVxx\n1016\t1416\t0\t0\t6\t16\t16\t16\t1016\t1016\t1016\t32\t33\tCNAAAA\tMCCAAA\tAAAAxx\n7365\t1417\t1\t1\t5\t5\t65\t365\t1365\t2365\t7365\t130\t131\tHXAAAA\tNCCAAA\tHHHHxx\n4358\t1418\t0\t2\t8\t18\t58\t358\t358\t4358\t4358\t116\t117\tQLAAAA\tOCCAAA\tOOOOxx\n2819\t1419\t1\t3\t9\t19\t19\t819\t819\t2819\t2819\t38\t39\tLEAAAA\tPCCAAA\tVVVVxx\n6727\t1420\t1\t3\t7\t7\t27\t727\t727\t1727\t6727\t54\t55\tTYAAAA\tQCCAAA\tAAAAxx\n1459\t1421\t1\t3\t9\t19\t59\t459\t1459\t1459\t1459\t118\t119\tDEAAAA\tRCCAAA\tHHHHxx\n1708\t1422\t0\t0\t8\t8\t8\t708\t1708\t1708\t1708\t16\t17\tSNAAAA\tSCCAAA\tOOOOxx\n471\t1423\t1\t3\t1\t11\t71\t471\t471\t471\t471\t142\t143\tDSAAAA\tTCCAAA\tVVVVxx\n387\t1424\t1\t3\t7\t7\t87\t387\t387\t387\t387\t174\t175\tXOAAAA\tUCCAAA\tAAAAxx\n1166\t1425\t0\t2\t6\t6\t66\t166\t1166\t1166\t1166\t132\t133\tWSAAAA\tVCCAAA\tHHHHxx\n2400\t1426\t0\t0\t0\t0\t0\t400\t400\t2400\t2400\t0\t1\tIOAAAA\tWCCAAA\tOOOOxx\n3584\t1427\t0\t0\t4\t4\t84\t584\t1584\t3584\t3584\t168\t169\tWHAAAA\tXCCAAA\tVVVVxx\n6423\t1428\t1\t3\t3\t3\t23\t423\t423\t1423\t6423\t46\t47\tBNAAAA\tYCCAAA\tAAAAxx\n9520\t1429\t0\t0\t0\t0\t20\t520\t1520\t4520\t9520\t40\t41\tECAAAA\tZCCAAA\tHHHHxx\n8080\t1430\t0\t0\t0\t0\t80\t80\t80\t3080\t8080\t160\t161\tUYAAAA\tADCAAA\tOOOOxx\n5709\t1431\t1\t1\t9\t9\t9\t709\t1709\t709\t5709\t18\t19\tPLAAAA\tBDCAAA\tVVVVxx\n1131\t1432\t1\t3\t1\t11\t31\t131\t1131\t1131\t1131\t62\t63\tNRAAAA\tCDCAAA\tAAAAxx\n8562\t1433\t0\t2\t2\t2\t62\t562\t562\t3562\t8562\t124\t125\tIRAAAA\tDDCAAA\tHHHHxx\n5766\t1434\t0\t2\t6\t6\t66\t766\t1766\t766\t5766\t132\t133\tUNAAAA\tEDCAAA\tOOOOxx\n245\t1435\t1\t1\t5\t5\t45\t245\t245\t245\t245\t90\t91\tLJAAAA\tFDCAAA\tVVVVxx\n9869\t1436\t1\t1\t9\t9\t69\t869\t1869\t4869\t9869\t138\t139\tPPAAAA\tGDCAAA\tAAAAxx\n3533\t1437\t1\t1\t3\t13\t33\t533\t1533\t3533\t3533\t66\t67\tXFAAAA\tHDCAAA\tHHHHxx\n5109\t1438\t1\t1\t9\t9\t9\t109\t1109\t109\t5109\t18\t19\tNOAAAA\tIDCAAA\tOOOOxx\n977\t1439\t1\t1\t7\t17\t77\t977\t977\t977\t977\t154\t155\tPLAAAA\tJDCAAA\tVVVVxx\n1651\t1440\t1\t3\t1\t11\t51\t651\t1651\t1651\t1651\t102\t103\tNLAAAA\tKDCAAA\tAAAAxx\n1357\t1441\t1\t1\t7\t17\t57\t357\t1357\t1357\t1357\t114\t115\tFAAAAA\tLDCAAA\tHHHHxx\n9087\t1442\t1\t3\t7\t7\t87\t87\t1087\t4087\t9087\t174\t175\tNLAAAA\tMDCAAA\tOOOOxx\n3399\t1443\t1\t3\t9\t19\t99\t399\t1399\t3399\t3399\t198\t199\tTAAAAA\tNDCAAA\tVVVVxx\n7543\t1444\t1\t3\t3\t3\t43\t543\t1543\t2543\t7543\t86\t87\tDEAAAA\tODCAAA\tAAAAxx\n2469\t1445\t1\t1\t9\t9\t69\t469\t469\t2469\t2469\t138\t139\tZQAAAA\tPDCAAA\tHHHHxx\n8305\t1446\t1\t1\t5\t5\t5\t305\t305\t3305\t8305\t10\t11\tLHAAAA\tQDCAAA\tOOOOxx\n3265\t1447\t1\t1\t5\t5\t65\t265\t1265\t3265\t3265\t130\t131\tPVAAAA\tRDCAAA\tVVVVxx\n9977\t1448\t1\t1\t7\t17\t77\t977\t1977\t4977\t9977\t154\t155\tTTAAAA\tSDCAAA\tAAAAxx\n3961\t1449\t1\t1\t1\t1\t61\t961\t1961\t3961\t3961\t122\t123\tJWAAAA\tTDCAAA\tHHHHxx\n4952\t1450\t0\t0\t2\t12\t52\t952\t952\t4952\t4952\t104\t105\tMIAAAA\tUDCAAA\tOOOOxx\n5173\t1451\t1\t1\t3\t13\t73\t173\t1173\t173\t5173\t146\t147\tZQAAAA\tVDCAAA\tVVVVxx\n860\t1452\t0\t0\t0\t0\t60\t860\t860\t860\t860\t120\t121\tCHAAAA\tWDCAAA\tAAAAxx\n4523\t1453\t1\t3\t3\t3\t23\t523\t523\t4523\t4523\t46\t47\tZRAAAA\tXDCAAA\tHHHHxx\n2361\t1454\t1\t1\t1\t1\t61\t361\t361\t2361\t2361\t122\t123\tVMAAAA\tYDCAAA\tOOOOxx\n7877\t1455\t1\t1\t7\t17\t77\t877\t1877\t2877\t7877\t154\t155\tZQAAAA\tZDCAAA\tVVVVxx\n3422\t1456\t0\t2\t2\t2\t22\t422\t1422\t3422\t3422\t44\t45\tQBAAAA\tAECAAA\tAAAAxx\n5781\t1457\t1\t1\t1\t1\t81\t781\t1781\t781\t5781\t162\t163\tJOAAAA\tBECAAA\tHHHHxx\n4752\t1458\t0\t0\t2\t12\t52\t752\t752\t4752\t4752\t104\t105\tUAAAAA\tCECAAA\tOOOOxx\n1786\t1459\t0\t2\t6\t6\t86\t786\t1786\t1786\t1786\t172\t173\tSQAAAA\tDECAAA\tVVVVxx\n1892\t1460\t0\t0\t2\t12\t92\t892\t1892\t1892\t1892\t184\t185\tUUAAAA\tEECAAA\tAAAAxx\n6389\t1461\t1\t1\t9\t9\t89\t389\t389\t1389\t6389\t178\t179\tTLAAAA\tFECAAA\tHHHHxx\n8644\t1462\t0\t0\t4\t4\t44\t644\t644\t3644\t8644\t88\t89\tMUAAAA\tGECAAA\tOOOOxx\n9056\t1463\t0\t0\t6\t16\t56\t56\t1056\t4056\t9056\t112\t113\tIKAAAA\tHECAAA\tVVVVxx\n1423\t1464\t1\t3\t3\t3\t23\t423\t1423\t1423\t1423\t46\t47\tTCAAAA\tIECAAA\tAAAAxx\n4901\t1465\t1\t1\t1\t1\t1\t901\t901\t4901\t4901\t2\t3\tNGAAAA\tJECAAA\tHHHHxx\n3859\t1466\t1\t3\t9\t19\t59\t859\t1859\t3859\t3859\t118\t119\tLSAAAA\tKECAAA\tOOOOxx\n2324\t1467\t0\t0\t4\t4\t24\t324\t324\t2324\t2324\t48\t49\tKLAAAA\tLECAAA\tVVVVxx\n8101\t1468\t1\t1\t1\t1\t1\t101\t101\t3101\t8101\t2\t3\tPZAAAA\tMECAAA\tAAAAxx\n8016\t1469\t0\t0\t6\t16\t16\t16\t16\t3016\t8016\t32\t33\tIWAAAA\tNECAAA\tHHHHxx\n5826\t1470\t0\t2\t6\t6\t26\t826\t1826\t826\t5826\t52\t53\tCQAAAA\tOECAAA\tOOOOxx\n8266\t1471\t0\t2\t6\t6\t66\t266\t266\t3266\t8266\t132\t133\tYFAAAA\tPECAAA\tVVVVxx\n7558\t1472\t0\t2\t8\t18\t58\t558\t1558\t2558\t7558\t116\t117\tSEAAAA\tQECAAA\tAAAAxx\n6976\t1473\t0\t0\t6\t16\t76\t976\t976\t1976\t6976\t152\t153\tIIAAAA\tRECAAA\tHHHHxx\n222\t1474\t0\t2\t2\t2\t22\t222\t222\t222\t222\t44\t45\tOIAAAA\tSECAAA\tOOOOxx\n1624\t1475\t0\t0\t4\t4\t24\t624\t1624\t1624\t1624\t48\t49\tMKAAAA\tTECAAA\tVVVVxx\n1250\t1476\t0\t2\t0\t10\t50\t250\t1250\t1250\t1250\t100\t101\tCWAAAA\tUECAAA\tAAAAxx\n1621\t1477\t1\t1\t1\t1\t21\t621\t1621\t1621\t1621\t42\t43\tJKAAAA\tVECAAA\tHHHHxx\n2350\t1478\t0\t2\t0\t10\t50\t350\t350\t2350\t2350\t100\t101\tKMAAAA\tWECAAA\tOOOOxx\n5239\t1479\t1\t3\t9\t19\t39\t239\t1239\t239\t5239\t78\t79\tNTAAAA\tXECAAA\tVVVVxx\n6681\t1480\t1\t1\t1\t1\t81\t681\t681\t1681\t6681\t162\t163\tZWAAAA\tYECAAA\tAAAAxx\n4983\t1481\t1\t3\t3\t3\t83\t983\t983\t4983\t4983\t166\t167\tRJAAAA\tZECAAA\tHHHHxx\n7149\t1482\t1\t1\t9\t9\t49\t149\t1149\t2149\t7149\t98\t99\tZOAAAA\tAFCAAA\tOOOOxx\n3502\t1483\t0\t2\t2\t2\t2\t502\t1502\t3502\t3502\t4\t5\tSEAAAA\tBFCAAA\tVVVVxx\n3133\t1484\t1\t1\t3\t13\t33\t133\t1133\t3133\t3133\t66\t67\tNQAAAA\tCFCAAA\tAAAAxx\n8342\t1485\t0\t2\t2\t2\t42\t342\t342\t3342\t8342\t84\t85\tWIAAAA\tDFCAAA\tHHHHxx\n3041\t1486\t1\t1\t1\t1\t41\t41\t1041\t3041\t3041\t82\t83\tZMAAAA\tEFCAAA\tOOOOxx\n5383\t1487\t1\t3\t3\t3\t83\t383\t1383\t383\t5383\t166\t167\tBZAAAA\tFFCAAA\tVVVVxx\n3916\t1488\t0\t0\t6\t16\t16\t916\t1916\t3916\t3916\t32\t33\tQUAAAA\tGFCAAA\tAAAAxx\n1438\t1489\t0\t2\t8\t18\t38\t438\t1438\t1438\t1438\t76\t77\tIDAAAA\tHFCAAA\tHHHHxx\n9408\t1490\t0\t0\t8\t8\t8\t408\t1408\t4408\t9408\t16\t17\tWXAAAA\tIFCAAA\tOOOOxx\n5783\t1491\t1\t3\t3\t3\t83\t783\t1783\t783\t5783\t166\t167\tLOAAAA\tJFCAAA\tVVVVxx\n683\t1492\t1\t3\t3\t3\t83\t683\t683\t683\t683\t166\t167\tHAAAAA\tKFCAAA\tAAAAxx\n9381\t1493\t1\t1\t1\t1\t81\t381\t1381\t4381\t9381\t162\t163\tVWAAAA\tLFCAAA\tHHHHxx\n5676\t1494\t0\t0\t6\t16\t76\t676\t1676\t676\t5676\t152\t153\tIKAAAA\tMFCAAA\tOOOOxx\n3224\t1495\t0\t0\t4\t4\t24\t224\t1224\t3224\t3224\t48\t49\tAUAAAA\tNFCAAA\tVVVVxx\n8332\t1496\t0\t0\t2\t12\t32\t332\t332\t3332\t8332\t64\t65\tMIAAAA\tOFCAAA\tAAAAxx\n3372\t1497\t0\t0\t2\t12\t72\t372\t1372\t3372\t3372\t144\t145\tSZAAAA\tPFCAAA\tHHHHxx\n7436\t1498\t0\t0\t6\t16\t36\t436\t1436\t2436\t7436\t72\t73\tAAAAAA\tQFCAAA\tOOOOxx\n5010\t1499\t0\t2\t0\t10\t10\t10\t1010\t10\t5010\t20\t21\tSKAAAA\tRFCAAA\tVVVVxx\n7256\t1500\t0\t0\t6\t16\t56\t256\t1256\t2256\t7256\t112\t113\tCTAAAA\tSFCAAA\tAAAAxx\n961\t1501\t1\t1\t1\t1\t61\t961\t961\t961\t961\t122\t123\tZKAAAA\tTFCAAA\tHHHHxx\n4182\t1502\t0\t2\t2\t2\t82\t182\t182\t4182\t4182\t164\t165\tWEAAAA\tUFCAAA\tOOOOxx\n639\t1503\t1\t3\t9\t19\t39\t639\t639\t639\t639\t78\t79\tPYAAAA\tVFCAAA\tVVVVxx\n8836\t1504\t0\t0\t6\t16\t36\t836\t836\t3836\t8836\t72\t73\tWBAAAA\tWFCAAA\tAAAAxx\n8705\t1505\t1\t1\t5\t5\t5\t705\t705\t3705\t8705\t10\t11\tVWAAAA\tXFCAAA\tHHHHxx\n32\t1506\t0\t0\t2\t12\t32\t32\t32\t32\t32\t64\t65\tGBAAAA\tYFCAAA\tOOOOxx\n7913\t1507\t1\t1\t3\t13\t13\t913\t1913\t2913\t7913\t26\t27\tJSAAAA\tZFCAAA\tVVVVxx\n229\t1508\t1\t1\t9\t9\t29\t229\t229\t229\t229\t58\t59\tVIAAAA\tAGCAAA\tAAAAxx\n2393\t1509\t1\t1\t3\t13\t93\t393\t393\t2393\t2393\t186\t187\tBOAAAA\tBGCAAA\tHHHHxx\n2815\t1510\t1\t3\t5\t15\t15\t815\t815\t2815\t2815\t30\t31\tHEAAAA\tCGCAAA\tOOOOxx\n4858\t1511\t0\t2\t8\t18\t58\t858\t858\t4858\t4858\t116\t117\tWEAAAA\tDGCAAA\tVVVVxx\n6283\t1512\t1\t3\t3\t3\t83\t283\t283\t1283\t6283\t166\t167\tRHAAAA\tEGCAAA\tAAAAxx\n4147\t1513\t1\t3\t7\t7\t47\t147\t147\t4147\t4147\t94\t95\tNDAAAA\tFGCAAA\tHHHHxx\n6801\t1514\t1\t1\t1\t1\t1\t801\t801\t1801\t6801\t2\t3\tPBAAAA\tGGCAAA\tOOOOxx\n1011\t1515\t1\t3\t1\t11\t11\t11\t1011\t1011\t1011\t22\t23\tXMAAAA\tHGCAAA\tVVVVxx\n2527\t1516\t1\t3\t7\t7\t27\t527\t527\t2527\t2527\t54\t55\tFTAAAA\tIGCAAA\tAAAAxx\n381\t1517\t1\t1\t1\t1\t81\t381\t381\t381\t381\t162\t163\tROAAAA\tJGCAAA\tHHHHxx\n3366\t1518\t0\t2\t6\t6\t66\t366\t1366\t3366\t3366\t132\t133\tMZAAAA\tKGCAAA\tOOOOxx\n9636\t1519\t0\t0\t6\t16\t36\t636\t1636\t4636\t9636\t72\t73\tQGAAAA\tLGCAAA\tVVVVxx\n2239\t1520\t1\t3\t9\t19\t39\t239\t239\t2239\t2239\t78\t79\tDIAAAA\tMGCAAA\tAAAAxx\n5911\t1521\t1\t3\t1\t11\t11\t911\t1911\t911\t5911\t22\t23\tJTAAAA\tNGCAAA\tHHHHxx\n449\t1522\t1\t1\t9\t9\t49\t449\t449\t449\t449\t98\t99\tHRAAAA\tOGCAAA\tOOOOxx\n5118\t1523\t0\t2\t8\t18\t18\t118\t1118\t118\t5118\t36\t37\tWOAAAA\tPGCAAA\tVVVVxx\n7684\t1524\t0\t0\t4\t4\t84\t684\t1684\t2684\t7684\t168\t169\tOJAAAA\tQGCAAA\tAAAAxx\n804\t1525\t0\t0\t4\t4\t4\t804\t804\t804\t804\t8\t9\tYEAAAA\tRGCAAA\tHHHHxx\n8378\t1526\t0\t2\t8\t18\t78\t378\t378\t3378\t8378\t156\t157\tGKAAAA\tSGCAAA\tOOOOxx\n9855\t1527\t1\t3\t5\t15\t55\t855\t1855\t4855\t9855\t110\t111\tBPAAAA\tTGCAAA\tVVVVxx\n1995\t1528\t1\t3\t5\t15\t95\t995\t1995\t1995\t1995\t190\t191\tTYAAAA\tUGCAAA\tAAAAxx\n1979\t1529\t1\t3\t9\t19\t79\t979\t1979\t1979\t1979\t158\t159\tDYAAAA\tVGCAAA\tHHHHxx\n4510\t1530\t0\t2\t0\t10\t10\t510\t510\t4510\t4510\t20\t21\tMRAAAA\tWGCAAA\tOOOOxx\n3792\t1531\t0\t0\t2\t12\t92\t792\t1792\t3792\t3792\t184\t185\tWPAAAA\tXGCAAA\tVVVVxx\n3541\t1532\t1\t1\t1\t1\t41\t541\t1541\t3541\t3541\t82\t83\tFGAAAA\tYGCAAA\tAAAAxx\n8847\t1533\t1\t3\t7\t7\t47\t847\t847\t3847\t8847\t94\t95\tHCAAAA\tZGCAAA\tHHHHxx\n1336\t1534\t0\t0\t6\t16\t36\t336\t1336\t1336\t1336\t72\t73\tKZAAAA\tAHCAAA\tOOOOxx\n6780\t1535\t0\t0\t0\t0\t80\t780\t780\t1780\t6780\t160\t161\tUAAAAA\tBHCAAA\tVVVVxx\n8711\t1536\t1\t3\t1\t11\t11\t711\t711\t3711\t8711\t22\t23\tBXAAAA\tCHCAAA\tAAAAxx\n7839\t1537\t1\t3\t9\t19\t39\t839\t1839\t2839\t7839\t78\t79\tNPAAAA\tDHCAAA\tHHHHxx\n677\t1538\t1\t1\t7\t17\t77\t677\t677\t677\t677\t154\t155\tBAAAAA\tEHCAAA\tOOOOxx\n1574\t1539\t0\t2\t4\t14\t74\t574\t1574\t1574\t1574\t148\t149\tOIAAAA\tFHCAAA\tVVVVxx\n2905\t1540\t1\t1\t5\t5\t5\t905\t905\t2905\t2905\t10\t11\tTHAAAA\tGHCAAA\tAAAAxx\n1879\t1541\t1\t3\t9\t19\t79\t879\t1879\t1879\t1879\t158\t159\tHUAAAA\tHHCAAA\tHHHHxx\n7820\t1542\t0\t0\t0\t0\t20\t820\t1820\t2820\t7820\t40\t41\tUOAAAA\tIHCAAA\tOOOOxx\n4308\t1543\t0\t0\t8\t8\t8\t308\t308\t4308\t4308\t16\t17\tSJAAAA\tJHCAAA\tVVVVxx\n4474\t1544\t0\t2\t4\t14\t74\t474\t474\t4474\t4474\t148\t149\tCQAAAA\tKHCAAA\tAAAAxx\n6985\t1545\t1\t1\t5\t5\t85\t985\t985\t1985\t6985\t170\t171\tRIAAAA\tLHCAAA\tHHHHxx\n6929\t1546\t1\t1\t9\t9\t29\t929\t929\t1929\t6929\t58\t59\tNGAAAA\tMHCAAA\tOOOOxx\n777\t1547\t1\t1\t7\t17\t77\t777\t777\t777\t777\t154\t155\tXDAAAA\tNHCAAA\tVVVVxx\n8271\t1548\t1\t3\t1\t11\t71\t271\t271\t3271\t8271\t142\t143\tDGAAAA\tOHCAAA\tAAAAxx\n2389\t1549\t1\t1\t9\t9\t89\t389\t389\t2389\t2389\t178\t179\tXNAAAA\tPHCAAA\tHHHHxx\n946\t1550\t0\t2\t6\t6\t46\t946\t946\t946\t946\t92\t93\tKKAAAA\tQHCAAA\tOOOOxx\n9682\t1551\t0\t2\t2\t2\t82\t682\t1682\t4682\t9682\t164\t165\tKIAAAA\tRHCAAA\tVVVVxx\n8722\t1552\t0\t2\t2\t2\t22\t722\t722\t3722\t8722\t44\t45\tMXAAAA\tSHCAAA\tAAAAxx\n470\t1553\t0\t2\t0\t10\t70\t470\t470\t470\t470\t140\t141\tCSAAAA\tTHCAAA\tHHHHxx\n7425\t1554\t1\t1\t5\t5\t25\t425\t1425\t2425\t7425\t50\t51\tPZAAAA\tUHCAAA\tOOOOxx\n2372\t1555\t0\t0\t2\t12\t72\t372\t372\t2372\t2372\t144\t145\tGNAAAA\tVHCAAA\tVVVVxx\n508\t1556\t0\t0\t8\t8\t8\t508\t508\t508\t508\t16\t17\tOTAAAA\tWHCAAA\tAAAAxx\n163\t1557\t1\t3\t3\t3\t63\t163\t163\t163\t163\t126\t127\tHGAAAA\tXHCAAA\tHHHHxx\n6579\t1558\t1\t3\t9\t19\t79\t579\t579\t1579\t6579\t158\t159\tBTAAAA\tYHCAAA\tOOOOxx\n2355\t1559\t1\t3\t5\t15\t55\t355\t355\t2355\t2355\t110\t111\tPMAAAA\tZHCAAA\tVVVVxx\n70\t1560\t0\t2\t0\t10\t70\t70\t70\t70\t70\t140\t141\tSCAAAA\tAICAAA\tAAAAxx\n651\t1561\t1\t3\t1\t11\t51\t651\t651\t651\t651\t102\t103\tBZAAAA\tBICAAA\tHHHHxx\n4436\t1562\t0\t0\t6\t16\t36\t436\t436\t4436\t4436\t72\t73\tQOAAAA\tCICAAA\tOOOOxx\n4240\t1563\t0\t0\t0\t0\t40\t240\t240\t4240\t4240\t80\t81\tCHAAAA\tDICAAA\tVVVVxx\n2722\t1564\t0\t2\t2\t2\t22\t722\t722\t2722\t2722\t44\t45\tSAAAAA\tEICAAA\tAAAAxx\n8937\t1565\t1\t1\t7\t17\t37\t937\t937\t3937\t8937\t74\t75\tTFAAAA\tFICAAA\tHHHHxx\n8364\t1566\t0\t0\t4\t4\t64\t364\t364\t3364\t8364\t128\t129\tSJAAAA\tGICAAA\tOOOOxx\n8317\t1567\t1\t1\t7\t17\t17\t317\t317\t3317\t8317\t34\t35\tXHAAAA\tHICAAA\tVVVVxx\n8872\t1568\t0\t0\t2\t12\t72\t872\t872\t3872\t8872\t144\t145\tGDAAAA\tIICAAA\tAAAAxx\n5512\t1569\t0\t0\t2\t12\t12\t512\t1512\t512\t5512\t24\t25\tAEAAAA\tJICAAA\tHHHHxx\n6651\t1570\t1\t3\t1\t11\t51\t651\t651\t1651\t6651\t102\t103\tVVAAAA\tKICAAA\tOOOOxx\n5976\t1571\t0\t0\t6\t16\t76\t976\t1976\t976\t5976\t152\t153\tWVAAAA\tLICAAA\tVVVVxx\n3301\t1572\t1\t1\t1\t1\t1\t301\t1301\t3301\t3301\t2\t3\tZWAAAA\tMICAAA\tAAAAxx\n6784\t1573\t0\t0\t4\t4\t84\t784\t784\t1784\t6784\t168\t169\tYAAAAA\tNICAAA\tHHHHxx\n573\t1574\t1\t1\t3\t13\t73\t573\t573\t573\t573\t146\t147\tBWAAAA\tOICAAA\tOOOOxx\n3015\t1575\t1\t3\t5\t15\t15\t15\t1015\t3015\t3015\t30\t31\tZLAAAA\tPICAAA\tVVVVxx\n8245\t1576\t1\t1\t5\t5\t45\t245\t245\t3245\t8245\t90\t91\tDFAAAA\tQICAAA\tAAAAxx\n5251\t1577\t1\t3\t1\t11\t51\t251\t1251\t251\t5251\t102\t103\tZTAAAA\tRICAAA\tHHHHxx\n2281\t1578\t1\t1\t1\t1\t81\t281\t281\t2281\t2281\t162\t163\tTJAAAA\tSICAAA\tOOOOxx\n518\t1579\t0\t2\t8\t18\t18\t518\t518\t518\t518\t36\t37\tYTAAAA\tTICAAA\tVVVVxx\n9839\t1580\t1\t3\t9\t19\t39\t839\t1839\t4839\t9839\t78\t79\tLOAAAA\tUICAAA\tAAAAxx\n4526\t1581\t0\t2\t6\t6\t26\t526\t526\t4526\t4526\t52\t53\tCSAAAA\tVICAAA\tHHHHxx\n1261\t1582\t1\t1\t1\t1\t61\t261\t1261\t1261\t1261\t122\t123\tNWAAAA\tWICAAA\tOOOOxx\n4259\t1583\t1\t3\t9\t19\t59\t259\t259\t4259\t4259\t118\t119\tVHAAAA\tXICAAA\tVVVVxx\n9098\t1584\t0\t2\t8\t18\t98\t98\t1098\t4098\t9098\t196\t197\tYLAAAA\tYICAAA\tAAAAxx\n6037\t1585\t1\t1\t7\t17\t37\t37\t37\t1037\t6037\t74\t75\tFYAAAA\tZICAAA\tHHHHxx\n4284\t1586\t0\t0\t4\t4\t84\t284\t284\t4284\t4284\t168\t169\tUIAAAA\tAJCAAA\tOOOOxx\n3267\t1587\t1\t3\t7\t7\t67\t267\t1267\t3267\t3267\t134\t135\tRVAAAA\tBJCAAA\tVVVVxx\n5908\t1588\t0\t0\t8\t8\t8\t908\t1908\t908\t5908\t16\t17\tGTAAAA\tCJCAAA\tAAAAxx\n1549\t1589\t1\t1\t9\t9\t49\t549\t1549\t1549\t1549\t98\t99\tPHAAAA\tDJCAAA\tHHHHxx\n8736\t1590\t0\t0\t6\t16\t36\t736\t736\t3736\t8736\t72\t73\tAYAAAA\tEJCAAA\tOOOOxx\n2008\t1591\t0\t0\t8\t8\t8\t8\t8\t2008\t2008\t16\t17\tGZAAAA\tFJCAAA\tVVVVxx\n548\t1592\t0\t0\t8\t8\t48\t548\t548\t548\t548\t96\t97\tCVAAAA\tGJCAAA\tAAAAxx\n8846\t1593\t0\t2\t6\t6\t46\t846\t846\t3846\t8846\t92\t93\tGCAAAA\tHJCAAA\tHHHHxx\n8374\t1594\t0\t2\t4\t14\t74\t374\t374\t3374\t8374\t148\t149\tCKAAAA\tIJCAAA\tOOOOxx\n7986\t1595\t0\t2\t6\t6\t86\t986\t1986\t2986\t7986\t172\t173\tEVAAAA\tJJCAAA\tVVVVxx\n6819\t1596\t1\t3\t9\t19\t19\t819\t819\t1819\t6819\t38\t39\tHCAAAA\tKJCAAA\tAAAAxx\n4418\t1597\t0\t2\t8\t18\t18\t418\t418\t4418\t4418\t36\t37\tYNAAAA\tLJCAAA\tHHHHxx\n833\t1598\t1\t1\t3\t13\t33\t833\t833\t833\t833\t66\t67\tBGAAAA\tMJCAAA\tOOOOxx\n4416\t1599\t0\t0\t6\t16\t16\t416\t416\t4416\t4416\t32\t33\tWNAAAA\tNJCAAA\tVVVVxx\n4902\t1600\t0\t2\t2\t2\t2\t902\t902\t4902\t4902\t4\t5\tOGAAAA\tOJCAAA\tAAAAxx\n6828\t1601\t0\t0\t8\t8\t28\t828\t828\t1828\t6828\t56\t57\tQCAAAA\tPJCAAA\tHHHHxx\n1118\t1602\t0\t2\t8\t18\t18\t118\t1118\t1118\t1118\t36\t37\tARAAAA\tQJCAAA\tOOOOxx\n9993\t1603\t1\t1\t3\t13\t93\t993\t1993\t4993\t9993\t186\t187\tJUAAAA\tRJCAAA\tVVVVxx\n1430\t1604\t0\t2\t0\t10\t30\t430\t1430\t1430\t1430\t60\t61\tADAAAA\tSJCAAA\tAAAAxx\n5670\t1605\t0\t2\t0\t10\t70\t670\t1670\t670\t5670\t140\t141\tCKAAAA\tTJCAAA\tHHHHxx\n5424\t1606\t0\t0\t4\t4\t24\t424\t1424\t424\t5424\t48\t49\tQAAAAA\tUJCAAA\tOOOOxx\n5561\t1607\t1\t1\t1\t1\t61\t561\t1561\t561\t5561\t122\t123\tXFAAAA\tVJCAAA\tVVVVxx\n2027\t1608\t1\t3\t7\t7\t27\t27\t27\t2027\t2027\t54\t55\tZZAAAA\tWJCAAA\tAAAAxx\n6924\t1609\t0\t0\t4\t4\t24\t924\t924\t1924\t6924\t48\t49\tIGAAAA\tXJCAAA\tHHHHxx\n5946\t1610\t0\t2\t6\t6\t46\t946\t1946\t946\t5946\t92\t93\tSUAAAA\tYJCAAA\tOOOOxx\n4294\t1611\t0\t2\t4\t14\t94\t294\t294\t4294\t4294\t188\t189\tEJAAAA\tZJCAAA\tVVVVxx\n2936\t1612\t0\t0\t6\t16\t36\t936\t936\t2936\t2936\t72\t73\tYIAAAA\tAKCAAA\tAAAAxx\n3855\t1613\t1\t3\t5\t15\t55\t855\t1855\t3855\t3855\t110\t111\tHSAAAA\tBKCAAA\tHHHHxx\n455\t1614\t1\t3\t5\t15\t55\t455\t455\t455\t455\t110\t111\tNRAAAA\tCKCAAA\tOOOOxx\n2918\t1615\t0\t2\t8\t18\t18\t918\t918\t2918\t2918\t36\t37\tGIAAAA\tDKCAAA\tVVVVxx\n448\t1616\t0\t0\t8\t8\t48\t448\t448\t448\t448\t96\t97\tGRAAAA\tEKCAAA\tAAAAxx\n2149\t1617\t1\t1\t9\t9\t49\t149\t149\t2149\t2149\t98\t99\tREAAAA\tFKCAAA\tHHHHxx\n8890\t1618\t0\t2\t0\t10\t90\t890\t890\t3890\t8890\t180\t181\tYDAAAA\tGKCAAA\tOOOOxx\n8919\t1619\t1\t3\t9\t19\t19\t919\t919\t3919\t8919\t38\t39\tBFAAAA\tHKCAAA\tVVVVxx\n4957\t1620\t1\t1\t7\t17\t57\t957\t957\t4957\t4957\t114\t115\tRIAAAA\tIKCAAA\tAAAAxx\n4\t1621\t0\t0\t4\t4\t4\t4\t4\t4\t4\t8\t9\tEAAAAA\tJKCAAA\tHHHHxx\n4837\t1622\t1\t1\t7\t17\t37\t837\t837\t4837\t4837\t74\t75\tBEAAAA\tKKCAAA\tOOOOxx\n3976\t1623\t0\t0\t6\t16\t76\t976\t1976\t3976\t3976\t152\t153\tYWAAAA\tLKCAAA\tVVVVxx\n9459\t1624\t1\t3\t9\t19\t59\t459\t1459\t4459\t9459\t118\t119\tVZAAAA\tMKCAAA\tAAAAxx\n7097\t1625\t1\t1\t7\t17\t97\t97\t1097\t2097\t7097\t194\t195\tZMAAAA\tNKCAAA\tHHHHxx\n9226\t1626\t0\t2\t6\t6\t26\t226\t1226\t4226\t9226\t52\t53\tWQAAAA\tOKCAAA\tOOOOxx\n5803\t1627\t1\t3\t3\t3\t3\t803\t1803\t803\t5803\t6\t7\tFPAAAA\tPKCAAA\tVVVVxx\n21\t1628\t1\t1\t1\t1\t21\t21\t21\t21\t21\t42\t43\tVAAAAA\tQKCAAA\tAAAAxx\n5275\t1629\t1\t3\t5\t15\t75\t275\t1275\t275\t5275\t150\t151\tXUAAAA\tRKCAAA\tHHHHxx\n3488\t1630\t0\t0\t8\t8\t88\t488\t1488\t3488\t3488\t176\t177\tEEAAAA\tSKCAAA\tOOOOxx\n1595\t1631\t1\t3\t5\t15\t95\t595\t1595\t1595\t1595\t190\t191\tJJAAAA\tTKCAAA\tVVVVxx\n5212\t1632\t0\t0\t2\t12\t12\t212\t1212\t212\t5212\t24\t25\tMSAAAA\tUKCAAA\tAAAAxx\n6574\t1633\t0\t2\t4\t14\t74\t574\t574\t1574\t6574\t148\t149\tWSAAAA\tVKCAAA\tHHHHxx\n7524\t1634\t0\t0\t4\t4\t24\t524\t1524\t2524\t7524\t48\t49\tKDAAAA\tWKCAAA\tOOOOxx\n6100\t1635\t0\t0\t0\t0\t0\t100\t100\t1100\t6100\t0\t1\tQAAAAA\tXKCAAA\tVVVVxx\n1198\t1636\t0\t2\t8\t18\t98\t198\t1198\t1198\t1198\t196\t197\tCUAAAA\tYKCAAA\tAAAAxx\n7345\t1637\t1\t1\t5\t5\t45\t345\t1345\t2345\t7345\t90\t91\tNWAAAA\tZKCAAA\tHHHHxx\n5020\t1638\t0\t0\t0\t0\t20\t20\t1020\t20\t5020\t40\t41\tCLAAAA\tALCAAA\tOOOOxx\n6925\t1639\t1\t1\t5\t5\t25\t925\t925\t1925\t6925\t50\t51\tJGAAAA\tBLCAAA\tVVVVxx\n8915\t1640\t1\t3\t5\t15\t15\t915\t915\t3915\t8915\t30\t31\tXEAAAA\tCLCAAA\tAAAAxx\n3088\t1641\t0\t0\t8\t8\t88\t88\t1088\t3088\t3088\t176\t177\tUOAAAA\tDLCAAA\tHHHHxx\n4828\t1642\t0\t0\t8\t8\t28\t828\t828\t4828\t4828\t56\t57\tSDAAAA\tELCAAA\tOOOOxx\n7276\t1643\t0\t0\t6\t16\t76\t276\t1276\t2276\t7276\t152\t153\tWTAAAA\tFLCAAA\tVVVVxx\n299\t1644\t1\t3\t9\t19\t99\t299\t299\t299\t299\t198\t199\tNLAAAA\tGLCAAA\tAAAAxx\n76\t1645\t0\t0\t6\t16\t76\t76\t76\t76\t76\t152\t153\tYCAAAA\tHLCAAA\tHHHHxx\n8458\t1646\t0\t2\t8\t18\t58\t458\t458\t3458\t8458\t116\t117\tINAAAA\tILCAAA\tOOOOxx\n7207\t1647\t1\t3\t7\t7\t7\t207\t1207\t2207\t7207\t14\t15\tFRAAAA\tJLCAAA\tVVVVxx\n5585\t1648\t1\t1\t5\t5\t85\t585\t1585\t585\t5585\t170\t171\tVGAAAA\tKLCAAA\tAAAAxx\n3234\t1649\t0\t2\t4\t14\t34\t234\t1234\t3234\t3234\t68\t69\tKUAAAA\tLLCAAA\tHHHHxx\n8001\t1650\t1\t1\t1\t1\t1\t1\t1\t3001\t8001\t2\t3\tTVAAAA\tMLCAAA\tOOOOxx\n1319\t1651\t1\t3\t9\t19\t19\t319\t1319\t1319\t1319\t38\t39\tTYAAAA\tNLCAAA\tVVVVxx\n6342\t1652\t0\t2\t2\t2\t42\t342\t342\t1342\t6342\t84\t85\tYJAAAA\tOLCAAA\tAAAAxx\n9199\t1653\t1\t3\t9\t19\t99\t199\t1199\t4199\t9199\t198\t199\tVPAAAA\tPLCAAA\tHHHHxx\n5696\t1654\t0\t0\t6\t16\t96\t696\t1696\t696\t5696\t192\t193\tCLAAAA\tQLCAAA\tOOOOxx\n2562\t1655\t0\t2\t2\t2\t62\t562\t562\t2562\t2562\t124\t125\tOUAAAA\tRLCAAA\tVVVVxx\n4226\t1656\t0\t2\t6\t6\t26\t226\t226\t4226\t4226\t52\t53\tOGAAAA\tSLCAAA\tAAAAxx\n1184\t1657\t0\t0\t4\t4\t84\t184\t1184\t1184\t1184\t168\t169\tOTAAAA\tTLCAAA\tHHHHxx\n5807\t1658\t1\t3\t7\t7\t7\t807\t1807\t807\t5807\t14\t15\tJPAAAA\tULCAAA\tOOOOxx\n1890\t1659\t0\t2\t0\t10\t90\t890\t1890\t1890\t1890\t180\t181\tSUAAAA\tVLCAAA\tVVVVxx\n451\t1660\t1\t3\t1\t11\t51\t451\t451\t451\t451\t102\t103\tJRAAAA\tWLCAAA\tAAAAxx\n1049\t1661\t1\t1\t9\t9\t49\t49\t1049\t1049\t1049\t98\t99\tJOAAAA\tXLCAAA\tHHHHxx\n5272\t1662\t0\t0\t2\t12\t72\t272\t1272\t272\t5272\t144\t145\tUUAAAA\tYLCAAA\tOOOOxx\n4588\t1663\t0\t0\t8\t8\t88\t588\t588\t4588\t4588\t176\t177\tMUAAAA\tZLCAAA\tVVVVxx\n5213\t1664\t1\t1\t3\t13\t13\t213\t1213\t213\t5213\t26\t27\tNSAAAA\tAMCAAA\tAAAAxx\n9543\t1665\t1\t3\t3\t3\t43\t543\t1543\t4543\t9543\t86\t87\tBDAAAA\tBMCAAA\tHHHHxx\n6318\t1666\t0\t2\t8\t18\t18\t318\t318\t1318\t6318\t36\t37\tAJAAAA\tCMCAAA\tOOOOxx\n7992\t1667\t0\t0\t2\t12\t92\t992\t1992\t2992\t7992\t184\t185\tKVAAAA\tDMCAAA\tVVVVxx\n4619\t1668\t1\t3\t9\t19\t19\t619\t619\t4619\t4619\t38\t39\tRVAAAA\tEMCAAA\tAAAAxx\n7189\t1669\t1\t1\t9\t9\t89\t189\t1189\t2189\t7189\t178\t179\tNQAAAA\tFMCAAA\tHHHHxx\n2178\t1670\t0\t2\t8\t18\t78\t178\t178\t2178\t2178\t156\t157\tUFAAAA\tGMCAAA\tOOOOxx\n4928\t1671\t0\t0\t8\t8\t28\t928\t928\t4928\t4928\t56\t57\tOHAAAA\tHMCAAA\tVVVVxx\n3966\t1672\t0\t2\t6\t6\t66\t966\t1966\t3966\t3966\t132\t133\tOWAAAA\tIMCAAA\tAAAAxx\n9790\t1673\t0\t2\t0\t10\t90\t790\t1790\t4790\t9790\t180\t181\tOMAAAA\tJMCAAA\tHHHHxx\n9150\t1674\t0\t2\t0\t10\t50\t150\t1150\t4150\t9150\t100\t101\tYNAAAA\tKMCAAA\tOOOOxx\n313\t1675\t1\t1\t3\t13\t13\t313\t313\t313\t313\t26\t27\tBMAAAA\tLMCAAA\tVVVVxx\n1614\t1676\t0\t2\t4\t14\t14\t614\t1614\t1614\t1614\t28\t29\tCKAAAA\tMMCAAA\tAAAAxx\n1581\t1677\t1\t1\t1\t1\t81\t581\t1581\t1581\t1581\t162\t163\tVIAAAA\tNMCAAA\tHHHHxx\n3674\t1678\t0\t2\t4\t14\t74\t674\t1674\t3674\t3674\t148\t149\tILAAAA\tOMCAAA\tOOOOxx\n3444\t1679\t0\t0\t4\t4\t44\t444\t1444\t3444\t3444\t88\t89\tMCAAAA\tPMCAAA\tVVVVxx\n1050\t1680\t0\t2\t0\t10\t50\t50\t1050\t1050\t1050\t100\t101\tKOAAAA\tQMCAAA\tAAAAxx\n8241\t1681\t1\t1\t1\t1\t41\t241\t241\t3241\t8241\t82\t83\tZEAAAA\tRMCAAA\tHHHHxx\n3382\t1682\t0\t2\t2\t2\t82\t382\t1382\t3382\t3382\t164\t165\tCAAAAA\tSMCAAA\tOOOOxx\n7105\t1683\t1\t1\t5\t5\t5\t105\t1105\t2105\t7105\t10\t11\tHNAAAA\tTMCAAA\tVVVVxx\n2957\t1684\t1\t1\t7\t17\t57\t957\t957\t2957\t2957\t114\t115\tTJAAAA\tUMCAAA\tAAAAxx\n6162\t1685\t0\t2\t2\t2\t62\t162\t162\t1162\t6162\t124\t125\tADAAAA\tVMCAAA\tHHHHxx\n5150\t1686\t0\t2\t0\t10\t50\t150\t1150\t150\t5150\t100\t101\tCQAAAA\tWMCAAA\tOOOOxx\n2622\t1687\t0\t2\t2\t2\t22\t622\t622\t2622\t2622\t44\t45\tWWAAAA\tXMCAAA\tVVVVxx\n2240\t1688\t0\t0\t0\t0\t40\t240\t240\t2240\t2240\t80\t81\tEIAAAA\tYMCAAA\tAAAAxx\n8880\t1689\t0\t0\t0\t0\t80\t880\t880\t3880\t8880\t160\t161\tODAAAA\tZMCAAA\tHHHHxx\n9250\t1690\t0\t2\t0\t10\t50\t250\t1250\t4250\t9250\t100\t101\tURAAAA\tANCAAA\tOOOOxx\n7010\t1691\t0\t2\t0\t10\t10\t10\t1010\t2010\t7010\t20\t21\tQJAAAA\tBNCAAA\tVVVVxx\n1098\t1692\t0\t2\t8\t18\t98\t98\t1098\t1098\t1098\t196\t197\tGQAAAA\tCNCAAA\tAAAAxx\n648\t1693\t0\t0\t8\t8\t48\t648\t648\t648\t648\t96\t97\tYYAAAA\tDNCAAA\tHHHHxx\n5536\t1694\t0\t0\t6\t16\t36\t536\t1536\t536\t5536\t72\t73\tYEAAAA\tENCAAA\tOOOOxx\n7858\t1695\t0\t2\t8\t18\t58\t858\t1858\t2858\t7858\t116\t117\tGQAAAA\tFNCAAA\tVVVVxx\n7053\t1696\t1\t1\t3\t13\t53\t53\t1053\t2053\t7053\t106\t107\tHLAAAA\tGNCAAA\tAAAAxx\n8681\t1697\t1\t1\t1\t1\t81\t681\t681\t3681\t8681\t162\t163\tXVAAAA\tHNCAAA\tHHHHxx\n8832\t1698\t0\t0\t2\t12\t32\t832\t832\t3832\t8832\t64\t65\tSBAAAA\tINCAAA\tOOOOxx\n6836\t1699\t0\t0\t6\t16\t36\t836\t836\t1836\t6836\t72\t73\tYCAAAA\tJNCAAA\tVVVVxx\n4856\t1700\t0\t0\t6\t16\t56\t856\t856\t4856\t4856\t112\t113\tUEAAAA\tKNCAAA\tAAAAxx\n345\t1701\t1\t1\t5\t5\t45\t345\t345\t345\t345\t90\t91\tHNAAAA\tLNCAAA\tHHHHxx\n6559\t1702\t1\t3\t9\t19\t59\t559\t559\t1559\t6559\t118\t119\tHSAAAA\tMNCAAA\tOOOOxx\n3017\t1703\t1\t1\t7\t17\t17\t17\t1017\t3017\t3017\t34\t35\tBMAAAA\tNNCAAA\tVVVVxx\n4176\t1704\t0\t0\t6\t16\t76\t176\t176\t4176\t4176\t152\t153\tQEAAAA\tONCAAA\tAAAAxx\n2839\t1705\t1\t3\t9\t19\t39\t839\t839\t2839\t2839\t78\t79\tFFAAAA\tPNCAAA\tHHHHxx\n6065\t1706\t1\t1\t5\t5\t65\t65\t65\t1065\t6065\t130\t131\tHZAAAA\tQNCAAA\tOOOOxx\n7360\t1707\t0\t0\t0\t0\t60\t360\t1360\t2360\t7360\t120\t121\tCXAAAA\tRNCAAA\tVVVVxx\n9527\t1708\t1\t3\t7\t7\t27\t527\t1527\t4527\t9527\t54\t55\tLCAAAA\tSNCAAA\tAAAAxx\n8849\t1709\t1\t1\t9\t9\t49\t849\t849\t3849\t8849\t98\t99\tJCAAAA\tTNCAAA\tHHHHxx\n7274\t1710\t0\t2\t4\t14\t74\t274\t1274\t2274\t7274\t148\t149\tUTAAAA\tUNCAAA\tOOOOxx\n4368\t1711\t0\t0\t8\t8\t68\t368\t368\t4368\t4368\t136\t137\tAMAAAA\tVNCAAA\tVVVVxx\n2488\t1712\t0\t0\t8\t8\t88\t488\t488\t2488\t2488\t176\t177\tSRAAAA\tWNCAAA\tAAAAxx\n4674\t1713\t0\t2\t4\t14\t74\t674\t674\t4674\t4674\t148\t149\tUXAAAA\tXNCAAA\tHHHHxx\n365\t1714\t1\t1\t5\t5\t65\t365\t365\t365\t365\t130\t131\tBOAAAA\tYNCAAA\tOOOOxx\n5897\t1715\t1\t1\t7\t17\t97\t897\t1897\t897\t5897\t194\t195\tVSAAAA\tZNCAAA\tVVVVxx\n8918\t1716\t0\t2\t8\t18\t18\t918\t918\t3918\t8918\t36\t37\tAFAAAA\tAOCAAA\tAAAAxx\n1988\t1717\t0\t0\t8\t8\t88\t988\t1988\t1988\t1988\t176\t177\tMYAAAA\tBOCAAA\tHHHHxx\n1210\t1718\t0\t2\t0\t10\t10\t210\t1210\t1210\t1210\t20\t21\tOUAAAA\tCOCAAA\tOOOOxx\n2945\t1719\t1\t1\t5\t5\t45\t945\t945\t2945\t2945\t90\t91\tHJAAAA\tDOCAAA\tVVVVxx\n555\t1720\t1\t3\t5\t15\t55\t555\t555\t555\t555\t110\t111\tJVAAAA\tEOCAAA\tAAAAxx\n9615\t1721\t1\t3\t5\t15\t15\t615\t1615\t4615\t9615\t30\t31\tVFAAAA\tFOCAAA\tHHHHxx\n9939\t1722\t1\t3\t9\t19\t39\t939\t1939\t4939\t9939\t78\t79\tHSAAAA\tGOCAAA\tOOOOxx\n1216\t1723\t0\t0\t6\t16\t16\t216\t1216\t1216\t1216\t32\t33\tUUAAAA\tHOCAAA\tVVVVxx\n745\t1724\t1\t1\t5\t5\t45\t745\t745\t745\t745\t90\t91\tRCAAAA\tIOCAAA\tAAAAxx\n3326\t1725\t0\t2\t6\t6\t26\t326\t1326\t3326\t3326\t52\t53\tYXAAAA\tJOCAAA\tHHHHxx\n953\t1726\t1\t1\t3\t13\t53\t953\t953\t953\t953\t106\t107\tRKAAAA\tKOCAAA\tOOOOxx\n444\t1727\t0\t0\t4\t4\t44\t444\t444\t444\t444\t88\t89\tCRAAAA\tLOCAAA\tVVVVxx\n280\t1728\t0\t0\t0\t0\t80\t280\t280\t280\t280\t160\t161\tUKAAAA\tMOCAAA\tAAAAxx\n3707\t1729\t1\t3\t7\t7\t7\t707\t1707\t3707\t3707\t14\t15\tPMAAAA\tNOCAAA\tHHHHxx\n1351\t1730\t1\t3\t1\t11\t51\t351\t1351\t1351\t1351\t102\t103\tZZAAAA\tOOCAAA\tOOOOxx\n1280\t1731\t0\t0\t0\t0\t80\t280\t1280\t1280\t1280\t160\t161\tGXAAAA\tPOCAAA\tVVVVxx\n628\t1732\t0\t0\t8\t8\t28\t628\t628\t628\t628\t56\t57\tEYAAAA\tQOCAAA\tAAAAxx\n6198\t1733\t0\t2\t8\t18\t98\t198\t198\t1198\t6198\t196\t197\tKEAAAA\tROCAAA\tHHHHxx\n1957\t1734\t1\t1\t7\t17\t57\t957\t1957\t1957\t1957\t114\t115\tHXAAAA\tSOCAAA\tOOOOxx\n9241\t1735\t1\t1\t1\t1\t41\t241\t1241\t4241\t9241\t82\t83\tLRAAAA\tTOCAAA\tVVVVxx\n303\t1736\t1\t3\t3\t3\t3\t303\t303\t303\t303\t6\t7\tRLAAAA\tUOCAAA\tAAAAxx\n1945\t1737\t1\t1\t5\t5\t45\t945\t1945\t1945\t1945\t90\t91\tVWAAAA\tVOCAAA\tHHHHxx\n3634\t1738\t0\t2\t4\t14\t34\t634\t1634\t3634\t3634\t68\t69\tUJAAAA\tWOCAAA\tOOOOxx\n4768\t1739\t0\t0\t8\t8\t68\t768\t768\t4768\t4768\t136\t137\tKBAAAA\tXOCAAA\tVVVVxx\n9262\t1740\t0\t2\t2\t2\t62\t262\t1262\t4262\t9262\t124\t125\tGSAAAA\tYOCAAA\tAAAAxx\n2610\t1741\t0\t2\t0\t10\t10\t610\t610\t2610\t2610\t20\t21\tKWAAAA\tZOCAAA\tHHHHxx\n6640\t1742\t0\t0\t0\t0\t40\t640\t640\t1640\t6640\t80\t81\tKVAAAA\tAPCAAA\tOOOOxx\n3338\t1743\t0\t2\t8\t18\t38\t338\t1338\t3338\t3338\t76\t77\tKYAAAA\tBPCAAA\tVVVVxx\n6560\t1744\t0\t0\t0\t0\t60\t560\t560\t1560\t6560\t120\t121\tISAAAA\tCPCAAA\tAAAAxx\n5986\t1745\t0\t2\t6\t6\t86\t986\t1986\t986\t5986\t172\t173\tGWAAAA\tDPCAAA\tHHHHxx\n2970\t1746\t0\t2\t0\t10\t70\t970\t970\t2970\t2970\t140\t141\tGKAAAA\tEPCAAA\tOOOOxx\n4731\t1747\t1\t3\t1\t11\t31\t731\t731\t4731\t4731\t62\t63\tZZAAAA\tFPCAAA\tVVVVxx\n9486\t1748\t0\t2\t6\t6\t86\t486\t1486\t4486\t9486\t172\t173\tWAAAAA\tGPCAAA\tAAAAxx\n7204\t1749\t0\t0\t4\t4\t4\t204\t1204\t2204\t7204\t8\t9\tCRAAAA\tHPCAAA\tHHHHxx\n6685\t1750\t1\t1\t5\t5\t85\t685\t685\t1685\t6685\t170\t171\tDXAAAA\tIPCAAA\tOOOOxx\n6852\t1751\t0\t0\t2\t12\t52\t852\t852\t1852\t6852\t104\t105\tODAAAA\tJPCAAA\tVVVVxx\n2325\t1752\t1\t1\t5\t5\t25\t325\t325\t2325\t2325\t50\t51\tLLAAAA\tKPCAAA\tAAAAxx\n1063\t1753\t1\t3\t3\t3\t63\t63\t1063\t1063\t1063\t126\t127\tXOAAAA\tLPCAAA\tHHHHxx\n6810\t1754\t0\t2\t0\t10\t10\t810\t810\t1810\t6810\t20\t21\tYBAAAA\tMPCAAA\tOOOOxx\n7718\t1755\t0\t2\t8\t18\t18\t718\t1718\t2718\t7718\t36\t37\tWKAAAA\tNPCAAA\tVVVVxx\n1680\t1756\t0\t0\t0\t0\t80\t680\t1680\t1680\t1680\t160\t161\tQMAAAA\tOPCAAA\tAAAAxx\n7402\t1757\t0\t2\t2\t2\t2\t402\t1402\t2402\t7402\t4\t5\tSYAAAA\tPPCAAA\tHHHHxx\n4134\t1758\t0\t2\t4\t14\t34\t134\t134\t4134\t4134\t68\t69\tADAAAA\tQPCAAA\tOOOOxx\n8232\t1759\t0\t0\t2\t12\t32\t232\t232\t3232\t8232\t64\t65\tQEAAAA\tRPCAAA\tVVVVxx\n6682\t1760\t0\t2\t2\t2\t82\t682\t682\t1682\t6682\t164\t165\tAXAAAA\tSPCAAA\tAAAAxx\n7952\t1761\t0\t0\t2\t12\t52\t952\t1952\t2952\t7952\t104\t105\tWTAAAA\tTPCAAA\tHHHHxx\n5943\t1762\t1\t3\t3\t3\t43\t943\t1943\t943\t5943\t86\t87\tPUAAAA\tUPCAAA\tOOOOxx\n5394\t1763\t0\t2\t4\t14\t94\t394\t1394\t394\t5394\t188\t189\tMZAAAA\tVPCAAA\tVVVVxx\n6554\t1764\t0\t2\t4\t14\t54\t554\t554\t1554\t6554\t108\t109\tCSAAAA\tWPCAAA\tAAAAxx\n8186\t1765\t0\t2\t6\t6\t86\t186\t186\t3186\t8186\t172\t173\tWCAAAA\tXPCAAA\tHHHHxx\n199\t1766\t1\t3\t9\t19\t99\t199\t199\t199\t199\t198\t199\tRHAAAA\tYPCAAA\tOOOOxx\n3386\t1767\t0\t2\t6\t6\t86\t386\t1386\t3386\t3386\t172\t173\tGAAAAA\tZPCAAA\tVVVVxx\n8974\t1768\t0\t2\t4\t14\t74\t974\t974\t3974\t8974\t148\t149\tEHAAAA\tAQCAAA\tAAAAxx\n8140\t1769\t0\t0\t0\t0\t40\t140\t140\t3140\t8140\t80\t81\tCBAAAA\tBQCAAA\tHHHHxx\n3723\t1770\t1\t3\t3\t3\t23\t723\t1723\t3723\t3723\t46\t47\tFNAAAA\tCQCAAA\tOOOOxx\n8827\t1771\t1\t3\t7\t7\t27\t827\t827\t3827\t8827\t54\t55\tNBAAAA\tDQCAAA\tVVVVxx\n1998\t1772\t0\t2\t8\t18\t98\t998\t1998\t1998\t1998\t196\t197\tWYAAAA\tEQCAAA\tAAAAxx\n879\t1773\t1\t3\t9\t19\t79\t879\t879\t879\t879\t158\t159\tVHAAAA\tFQCAAA\tHHHHxx\n892\t1774\t0\t0\t2\t12\t92\t892\t892\t892\t892\t184\t185\tIIAAAA\tGQCAAA\tOOOOxx\n9468\t1775\t0\t0\t8\t8\t68\t468\t1468\t4468\t9468\t136\t137\tEAAAAA\tHQCAAA\tVVVVxx\n3797\t1776\t1\t1\t7\t17\t97\t797\t1797\t3797\t3797\t194\t195\tBQAAAA\tIQCAAA\tAAAAxx\n8379\t1777\t1\t3\t9\t19\t79\t379\t379\t3379\t8379\t158\t159\tHKAAAA\tJQCAAA\tHHHHxx\n2817\t1778\t1\t1\t7\t17\t17\t817\t817\t2817\t2817\t34\t35\tJEAAAA\tKQCAAA\tOOOOxx\n789\t1779\t1\t1\t9\t9\t89\t789\t789\t789\t789\t178\t179\tJEAAAA\tLQCAAA\tVVVVxx\n3871\t1780\t1\t3\t1\t11\t71\t871\t1871\t3871\t3871\t142\t143\tXSAAAA\tMQCAAA\tAAAAxx\n7931\t1781\t1\t3\t1\t11\t31\t931\t1931\t2931\t7931\t62\t63\tBTAAAA\tNQCAAA\tHHHHxx\n3636\t1782\t0\t0\t6\t16\t36\t636\t1636\t3636\t3636\t72\t73\tWJAAAA\tOQCAAA\tOOOOxx\n699\t1783\t1\t3\t9\t19\t99\t699\t699\t699\t699\t198\t199\tXAAAAA\tPQCAAA\tVVVVxx\n6850\t1784\t0\t2\t0\t10\t50\t850\t850\t1850\t6850\t100\t101\tMDAAAA\tQQCAAA\tAAAAxx\n6394\t1785\t0\t2\t4\t14\t94\t394\t394\t1394\t6394\t188\t189\tYLAAAA\tRQCAAA\tHHHHxx\n3475\t1786\t1\t3\t5\t15\t75\t475\t1475\t3475\t3475\t150\t151\tRDAAAA\tSQCAAA\tOOOOxx\n3026\t1787\t0\t2\t6\t6\t26\t26\t1026\t3026\t3026\t52\t53\tKMAAAA\tTQCAAA\tVVVVxx\n876\t1788\t0\t0\t6\t16\t76\t876\t876\t876\t876\t152\t153\tSHAAAA\tUQCAAA\tAAAAxx\n1992\t1789\t0\t0\t2\t12\t92\t992\t1992\t1992\t1992\t184\t185\tQYAAAA\tVQCAAA\tHHHHxx\n3079\t1790\t1\t3\t9\t19\t79\t79\t1079\t3079\t3079\t158\t159\tLOAAAA\tWQCAAA\tOOOOxx\n8128\t1791\t0\t0\t8\t8\t28\t128\t128\t3128\t8128\t56\t57\tQAAAAA\tXQCAAA\tVVVVxx\n8123\t1792\t1\t3\t3\t3\t23\t123\t123\t3123\t8123\t46\t47\tLAAAAA\tYQCAAA\tAAAAxx\n3285\t1793\t1\t1\t5\t5\t85\t285\t1285\t3285\t3285\t170\t171\tJWAAAA\tZQCAAA\tHHHHxx\n9315\t1794\t1\t3\t5\t15\t15\t315\t1315\t4315\t9315\t30\t31\tHUAAAA\tARCAAA\tOOOOxx\n9862\t1795\t0\t2\t2\t2\t62\t862\t1862\t4862\t9862\t124\t125\tIPAAAA\tBRCAAA\tVVVVxx\n2764\t1796\t0\t0\t4\t4\t64\t764\t764\t2764\t2764\t128\t129\tICAAAA\tCRCAAA\tAAAAxx\n3544\t1797\t0\t0\t4\t4\t44\t544\t1544\t3544\t3544\t88\t89\tIGAAAA\tDRCAAA\tHHHHxx\n7747\t1798\t1\t3\t7\t7\t47\t747\t1747\t2747\t7747\t94\t95\tZLAAAA\tERCAAA\tOOOOxx\n7725\t1799\t1\t1\t5\t5\t25\t725\t1725\t2725\t7725\t50\t51\tDLAAAA\tFRCAAA\tVVVVxx\n2449\t1800\t1\t1\t9\t9\t49\t449\t449\t2449\t2449\t98\t99\tFQAAAA\tGRCAAA\tAAAAxx\n8967\t1801\t1\t3\t7\t7\t67\t967\t967\t3967\t8967\t134\t135\tXGAAAA\tHRCAAA\tHHHHxx\n7371\t1802\t1\t3\t1\t11\t71\t371\t1371\t2371\t7371\t142\t143\tNXAAAA\tIRCAAA\tOOOOxx\n2158\t1803\t0\t2\t8\t18\t58\t158\t158\t2158\t2158\t116\t117\tAFAAAA\tJRCAAA\tVVVVxx\n5590\t1804\t0\t2\t0\t10\t90\t590\t1590\t590\t5590\t180\t181\tAHAAAA\tKRCAAA\tAAAAxx\n8072\t1805\t0\t0\t2\t12\t72\t72\t72\t3072\t8072\t144\t145\tMYAAAA\tLRCAAA\tHHHHxx\n1971\t1806\t1\t3\t1\t11\t71\t971\t1971\t1971\t1971\t142\t143\tVXAAAA\tMRCAAA\tOOOOxx\n772\t1807\t0\t0\t2\t12\t72\t772\t772\t772\t772\t144\t145\tSDAAAA\tNRCAAA\tVVVVxx\n3433\t1808\t1\t1\t3\t13\t33\t433\t1433\t3433\t3433\t66\t67\tBCAAAA\tORCAAA\tAAAAxx\n8419\t1809\t1\t3\t9\t19\t19\t419\t419\t3419\t8419\t38\t39\tVLAAAA\tPRCAAA\tHHHHxx\n1493\t1810\t1\t1\t3\t13\t93\t493\t1493\t1493\t1493\t186\t187\tLFAAAA\tQRCAAA\tOOOOxx\n2584\t1811\t0\t0\t4\t4\t84\t584\t584\t2584\t2584\t168\t169\tKVAAAA\tRRCAAA\tVVVVxx\n9502\t1812\t0\t2\t2\t2\t2\t502\t1502\t4502\t9502\t4\t5\tMBAAAA\tSRCAAA\tAAAAxx\n4673\t1813\t1\t1\t3\t13\t73\t673\t673\t4673\t4673\t146\t147\tTXAAAA\tTRCAAA\tHHHHxx\n7403\t1814\t1\t3\t3\t3\t3\t403\t1403\t2403\t7403\t6\t7\tTYAAAA\tURCAAA\tOOOOxx\n7103\t1815\t1\t3\t3\t3\t3\t103\t1103\t2103\t7103\t6\t7\tFNAAAA\tVRCAAA\tVVVVxx\n7026\t1816\t0\t2\t6\t6\t26\t26\t1026\t2026\t7026\t52\t53\tGKAAAA\tWRCAAA\tAAAAxx\n8574\t1817\t0\t2\t4\t14\t74\t574\t574\t3574\t8574\t148\t149\tURAAAA\tXRCAAA\tHHHHxx\n1366\t1818\t0\t2\t6\t6\t66\t366\t1366\t1366\t1366\t132\t133\tOAAAAA\tYRCAAA\tOOOOxx\n5787\t1819\t1\t3\t7\t7\t87\t787\t1787\t787\t5787\t174\t175\tPOAAAA\tZRCAAA\tVVVVxx\n2552\t1820\t0\t0\t2\t12\t52\t552\t552\t2552\t2552\t104\t105\tEUAAAA\tASCAAA\tAAAAxx\n4557\t1821\t1\t1\t7\t17\t57\t557\t557\t4557\t4557\t114\t115\tHTAAAA\tBSCAAA\tHHHHxx\n3237\t1822\t1\t1\t7\t17\t37\t237\t1237\t3237\t3237\t74\t75\tNUAAAA\tCSCAAA\tOOOOxx\n6901\t1823\t1\t1\t1\t1\t1\t901\t901\t1901\t6901\t2\t3\tLFAAAA\tDSCAAA\tVVVVxx\n7708\t1824\t0\t0\t8\t8\t8\t708\t1708\t2708\t7708\t16\t17\tMKAAAA\tESCAAA\tAAAAxx\n2011\t1825\t1\t3\t1\t11\t11\t11\t11\t2011\t2011\t22\t23\tJZAAAA\tFSCAAA\tHHHHxx\n9455\t1826\t1\t3\t5\t15\t55\t455\t1455\t4455\t9455\t110\t111\tRZAAAA\tGSCAAA\tOOOOxx\n5228\t1827\t0\t0\t8\t8\t28\t228\t1228\t228\t5228\t56\t57\tCTAAAA\tHSCAAA\tVVVVxx\n4043\t1828\t1\t3\t3\t3\t43\t43\t43\t4043\t4043\t86\t87\tNZAAAA\tISCAAA\tAAAAxx\n8242\t1829\t0\t2\t2\t2\t42\t242\t242\t3242\t8242\t84\t85\tAFAAAA\tJSCAAA\tHHHHxx\n6351\t1830\t1\t3\t1\t11\t51\t351\t351\t1351\t6351\t102\t103\tHKAAAA\tKSCAAA\tOOOOxx\n5899\t1831\t1\t3\t9\t19\t99\t899\t1899\t899\t5899\t198\t199\tXSAAAA\tLSCAAA\tVVVVxx\n4849\t1832\t1\t1\t9\t9\t49\t849\t849\t4849\t4849\t98\t99\tNEAAAA\tMSCAAA\tAAAAxx\n9583\t1833\t1\t3\t3\t3\t83\t583\t1583\t4583\t9583\t166\t167\tPEAAAA\tNSCAAA\tHHHHxx\n4994\t1834\t0\t2\t4\t14\t94\t994\t994\t4994\t4994\t188\t189\tCKAAAA\tOSCAAA\tOOOOxx\n9787\t1835\t1\t3\t7\t7\t87\t787\t1787\t4787\t9787\t174\t175\tLMAAAA\tPSCAAA\tVVVVxx\n243\t1836\t1\t3\t3\t3\t43\t243\t243\t243\t243\t86\t87\tJJAAAA\tQSCAAA\tAAAAxx\n3931\t1837\t1\t3\t1\t11\t31\t931\t1931\t3931\t3931\t62\t63\tFVAAAA\tRSCAAA\tHHHHxx\n5945\t1838\t1\t1\t5\t5\t45\t945\t1945\t945\t5945\t90\t91\tRUAAAA\tSSCAAA\tOOOOxx\n1325\t1839\t1\t1\t5\t5\t25\t325\t1325\t1325\t1325\t50\t51\tZYAAAA\tTSCAAA\tVVVVxx\n4142\t1840\t0\t2\t2\t2\t42\t142\t142\t4142\t4142\t84\t85\tIDAAAA\tUSCAAA\tAAAAxx\n1963\t1841\t1\t3\t3\t3\t63\t963\t1963\t1963\t1963\t126\t127\tNXAAAA\tVSCAAA\tHHHHxx\n7041\t1842\t1\t1\t1\t1\t41\t41\t1041\t2041\t7041\t82\t83\tVKAAAA\tWSCAAA\tOOOOxx\n3074\t1843\t0\t2\t4\t14\t74\t74\t1074\t3074\t3074\t148\t149\tGOAAAA\tXSCAAA\tVVVVxx\n3290\t1844\t0\t2\t0\t10\t90\t290\t1290\t3290\t3290\t180\t181\tOWAAAA\tYSCAAA\tAAAAxx\n4146\t1845\t0\t2\t6\t6\t46\t146\t146\t4146\t4146\t92\t93\tMDAAAA\tZSCAAA\tHHHHxx\n3832\t1846\t0\t0\t2\t12\t32\t832\t1832\t3832\t3832\t64\t65\tKRAAAA\tATCAAA\tOOOOxx\n2217\t1847\t1\t1\t7\t17\t17\t217\t217\t2217\t2217\t34\t35\tHHAAAA\tBTCAAA\tVVVVxx\n635\t1848\t1\t3\t5\t15\t35\t635\t635\t635\t635\t70\t71\tLYAAAA\tCTCAAA\tAAAAxx\n6967\t1849\t1\t3\t7\t7\t67\t967\t967\t1967\t6967\t134\t135\tZHAAAA\tDTCAAA\tHHHHxx\n3522\t1850\t0\t2\t2\t2\t22\t522\t1522\t3522\t3522\t44\t45\tMFAAAA\tETCAAA\tOOOOxx\n2471\t1851\t1\t3\t1\t11\t71\t471\t471\t2471\t2471\t142\t143\tBRAAAA\tFTCAAA\tVVVVxx\n4236\t1852\t0\t0\t6\t16\t36\t236\t236\t4236\t4236\t72\t73\tYGAAAA\tGTCAAA\tAAAAxx\n853\t1853\t1\t1\t3\t13\t53\t853\t853\t853\t853\t106\t107\tVGAAAA\tHTCAAA\tHHHHxx\n3754\t1854\t0\t2\t4\t14\t54\t754\t1754\t3754\t3754\t108\t109\tKOAAAA\tITCAAA\tOOOOxx\n796\t1855\t0\t0\t6\t16\t96\t796\t796\t796\t796\t192\t193\tQEAAAA\tJTCAAA\tVVVVxx\n4640\t1856\t0\t0\t0\t0\t40\t640\t640\t4640\t4640\t80\t81\tMWAAAA\tKTCAAA\tAAAAxx\n9496\t1857\t0\t0\t6\t16\t96\t496\t1496\t4496\t9496\t192\t193\tGBAAAA\tLTCAAA\tHHHHxx\n6873\t1858\t1\t1\t3\t13\t73\t873\t873\t1873\t6873\t146\t147\tJEAAAA\tMTCAAA\tOOOOxx\n4632\t1859\t0\t0\t2\t12\t32\t632\t632\t4632\t4632\t64\t65\tEWAAAA\tNTCAAA\tVVVVxx\n5758\t1860\t0\t2\t8\t18\t58\t758\t1758\t758\t5758\t116\t117\tMNAAAA\tOTCAAA\tAAAAxx\n6514\t1861\t0\t2\t4\t14\t14\t514\t514\t1514\t6514\t28\t29\tOQAAAA\tPTCAAA\tHHHHxx\n9510\t1862\t0\t2\t0\t10\t10\t510\t1510\t4510\t9510\t20\t21\tUBAAAA\tQTCAAA\tOOOOxx\n8411\t1863\t1\t3\t1\t11\t11\t411\t411\t3411\t8411\t22\t23\tNLAAAA\tRTCAAA\tVVVVxx\n7762\t1864\t0\t2\t2\t2\t62\t762\t1762\t2762\t7762\t124\t125\tOMAAAA\tSTCAAA\tAAAAxx\n2225\t1865\t1\t1\t5\t5\t25\t225\t225\t2225\t2225\t50\t51\tPHAAAA\tTTCAAA\tHHHHxx\n4373\t1866\t1\t1\t3\t13\t73\t373\t373\t4373\t4373\t146\t147\tFMAAAA\tUTCAAA\tOOOOxx\n7326\t1867\t0\t2\t6\t6\t26\t326\t1326\t2326\t7326\t52\t53\tUVAAAA\tVTCAAA\tVVVVxx\n8651\t1868\t1\t3\t1\t11\t51\t651\t651\t3651\t8651\t102\t103\tTUAAAA\tWTCAAA\tAAAAxx\n9825\t1869\t1\t1\t5\t5\t25\t825\t1825\t4825\t9825\t50\t51\tXNAAAA\tXTCAAA\tHHHHxx\n2988\t1870\t0\t0\t8\t8\t88\t988\t988\t2988\t2988\t176\t177\tYKAAAA\tYTCAAA\tOOOOxx\n8138\t1871\t0\t2\t8\t18\t38\t138\t138\t3138\t8138\t76\t77\tABAAAA\tZTCAAA\tVVVVxx\n7792\t1872\t0\t0\t2\t12\t92\t792\t1792\t2792\t7792\t184\t185\tSNAAAA\tAUCAAA\tAAAAxx\n1232\t1873\t0\t0\t2\t12\t32\t232\t1232\t1232\t1232\t64\t65\tKVAAAA\tBUCAAA\tHHHHxx\n8221\t1874\t1\t1\t1\t1\t21\t221\t221\t3221\t8221\t42\t43\tFEAAAA\tCUCAAA\tOOOOxx\n4044\t1875\t0\t0\t4\t4\t44\t44\t44\t4044\t4044\t88\t89\tOZAAAA\tDUCAAA\tVVVVxx\n1204\t1876\t0\t0\t4\t4\t4\t204\t1204\t1204\t1204\t8\t9\tIUAAAA\tEUCAAA\tAAAAxx\n5145\t1877\t1\t1\t5\t5\t45\t145\t1145\t145\t5145\t90\t91\tXPAAAA\tFUCAAA\tHHHHxx\n7791\t1878\t1\t3\t1\t11\t91\t791\t1791\t2791\t7791\t182\t183\tRNAAAA\tGUCAAA\tOOOOxx\n8270\t1879\t0\t2\t0\t10\t70\t270\t270\t3270\t8270\t140\t141\tCGAAAA\tHUCAAA\tVVVVxx\n9427\t1880\t1\t3\t7\t7\t27\t427\t1427\t4427\t9427\t54\t55\tPYAAAA\tIUCAAA\tAAAAxx\n2152\t1881\t0\t0\t2\t12\t52\t152\t152\t2152\t2152\t104\t105\tUEAAAA\tJUCAAA\tHHHHxx\n7790\t1882\t0\t2\t0\t10\t90\t790\t1790\t2790\t7790\t180\t181\tQNAAAA\tKUCAAA\tOOOOxx\n5301\t1883\t1\t1\t1\t1\t1\t301\t1301\t301\t5301\t2\t3\tXVAAAA\tLUCAAA\tVVVVxx\n626\t1884\t0\t2\t6\t6\t26\t626\t626\t626\t626\t52\t53\tCYAAAA\tMUCAAA\tAAAAxx\n260\t1885\t0\t0\t0\t0\t60\t260\t260\t260\t260\t120\t121\tAKAAAA\tNUCAAA\tHHHHxx\n4369\t1886\t1\t1\t9\t9\t69\t369\t369\t4369\t4369\t138\t139\tBMAAAA\tOUCAAA\tOOOOxx\n5457\t1887\t1\t1\t7\t17\t57\t457\t1457\t457\t5457\t114\t115\tXBAAAA\tPUCAAA\tVVVVxx\n3468\t1888\t0\t0\t8\t8\t68\t468\t1468\t3468\t3468\t136\t137\tKDAAAA\tQUCAAA\tAAAAxx\n2257\t1889\t1\t1\t7\t17\t57\t257\t257\t2257\t2257\t114\t115\tVIAAAA\tRUCAAA\tHHHHxx\n9318\t1890\t0\t2\t8\t18\t18\t318\t1318\t4318\t9318\t36\t37\tKUAAAA\tSUCAAA\tOOOOxx\n8762\t1891\t0\t2\t2\t2\t62\t762\t762\t3762\t8762\t124\t125\tAZAAAA\tTUCAAA\tVVVVxx\n9153\t1892\t1\t1\t3\t13\t53\t153\t1153\t4153\t9153\t106\t107\tBOAAAA\tUUCAAA\tAAAAxx\n9220\t1893\t0\t0\t0\t0\t20\t220\t1220\t4220\t9220\t40\t41\tQQAAAA\tVUCAAA\tHHHHxx\n8003\t1894\t1\t3\t3\t3\t3\t3\t3\t3003\t8003\t6\t7\tVVAAAA\tWUCAAA\tOOOOxx\n7257\t1895\t1\t1\t7\t17\t57\t257\t1257\t2257\t7257\t114\t115\tDTAAAA\tXUCAAA\tVVVVxx\n3930\t1896\t0\t2\t0\t10\t30\t930\t1930\t3930\t3930\t60\t61\tEVAAAA\tYUCAAA\tAAAAxx\n2976\t1897\t0\t0\t6\t16\t76\t976\t976\t2976\t2976\t152\t153\tMKAAAA\tZUCAAA\tHHHHxx\n2531\t1898\t1\t3\t1\t11\t31\t531\t531\t2531\t2531\t62\t63\tJTAAAA\tAVCAAA\tOOOOxx\n2250\t1899\t0\t2\t0\t10\t50\t250\t250\t2250\t2250\t100\t101\tOIAAAA\tBVCAAA\tVVVVxx\n8549\t1900\t1\t1\t9\t9\t49\t549\t549\t3549\t8549\t98\t99\tVQAAAA\tCVCAAA\tAAAAxx\n7197\t1901\t1\t1\t7\t17\t97\t197\t1197\t2197\t7197\t194\t195\tVQAAAA\tDVCAAA\tHHHHxx\n5916\t1902\t0\t0\t6\t16\t16\t916\t1916\t916\t5916\t32\t33\tOTAAAA\tEVCAAA\tOOOOxx\n5287\t1903\t1\t3\t7\t7\t87\t287\t1287\t287\t5287\t174\t175\tJVAAAA\tFVCAAA\tVVVVxx\n9095\t1904\t1\t3\t5\t15\t95\t95\t1095\t4095\t9095\t190\t191\tVLAAAA\tGVCAAA\tAAAAxx\n7137\t1905\t1\t1\t7\t17\t37\t137\t1137\t2137\t7137\t74\t75\tNOAAAA\tHVCAAA\tHHHHxx\n7902\t1906\t0\t2\t2\t2\t2\t902\t1902\t2902\t7902\t4\t5\tYRAAAA\tIVCAAA\tOOOOxx\n7598\t1907\t0\t2\t8\t18\t98\t598\t1598\t2598\t7598\t196\t197\tGGAAAA\tJVCAAA\tVVVVxx\n5652\t1908\t0\t0\t2\t12\t52\t652\t1652\t652\t5652\t104\t105\tKJAAAA\tKVCAAA\tAAAAxx\n2017\t1909\t1\t1\t7\t17\t17\t17\t17\t2017\t2017\t34\t35\tPZAAAA\tLVCAAA\tHHHHxx\n7255\t1910\t1\t3\t5\t15\t55\t255\t1255\t2255\t7255\t110\t111\tBTAAAA\tMVCAAA\tOOOOxx\n7999\t1911\t1\t3\t9\t19\t99\t999\t1999\t2999\t7999\t198\t199\tRVAAAA\tNVCAAA\tVVVVxx\n5388\t1912\t0\t0\t8\t8\t88\t388\t1388\t388\t5388\t176\t177\tGZAAAA\tOVCAAA\tAAAAxx\n8754\t1913\t0\t2\t4\t14\t54\t754\t754\t3754\t8754\t108\t109\tSYAAAA\tPVCAAA\tHHHHxx\n5415\t1914\t1\t3\t5\t15\t15\t415\t1415\t415\t5415\t30\t31\tHAAAAA\tQVCAAA\tOOOOxx\n8861\t1915\t1\t1\t1\t1\t61\t861\t861\t3861\t8861\t122\t123\tVCAAAA\tRVCAAA\tVVVVxx\n2874\t1916\t0\t2\t4\t14\t74\t874\t874\t2874\t2874\t148\t149\tOGAAAA\tSVCAAA\tAAAAxx\n9910\t1917\t0\t2\t0\t10\t10\t910\t1910\t4910\t9910\t20\t21\tERAAAA\tTVCAAA\tHHHHxx\n5178\t1918\t0\t2\t8\t18\t78\t178\t1178\t178\t5178\t156\t157\tERAAAA\tUVCAAA\tOOOOxx\n5698\t1919\t0\t2\t8\t18\t98\t698\t1698\t698\t5698\t196\t197\tELAAAA\tVVCAAA\tVVVVxx\n8500\t1920\t0\t0\t0\t0\t0\t500\t500\t3500\t8500\t0\t1\tYOAAAA\tWVCAAA\tAAAAxx\n1814\t1921\t0\t2\t4\t14\t14\t814\t1814\t1814\t1814\t28\t29\tURAAAA\tXVCAAA\tHHHHxx\n4968\t1922\t0\t0\t8\t8\t68\t968\t968\t4968\t4968\t136\t137\tCJAAAA\tYVCAAA\tOOOOxx\n2642\t1923\t0\t2\t2\t2\t42\t642\t642\t2642\t2642\t84\t85\tQXAAAA\tZVCAAA\tVVVVxx\n1578\t1924\t0\t2\t8\t18\t78\t578\t1578\t1578\t1578\t156\t157\tSIAAAA\tAWCAAA\tAAAAxx\n4774\t1925\t0\t2\t4\t14\t74\t774\t774\t4774\t4774\t148\t149\tQBAAAA\tBWCAAA\tHHHHxx\n7062\t1926\t0\t2\t2\t2\t62\t62\t1062\t2062\t7062\t124\t125\tQLAAAA\tCWCAAA\tOOOOxx\n5381\t1927\t1\t1\t1\t1\t81\t381\t1381\t381\t5381\t162\t163\tZYAAAA\tDWCAAA\tVVVVxx\n7985\t1928\t1\t1\t5\t5\t85\t985\t1985\t2985\t7985\t170\t171\tDVAAAA\tEWCAAA\tAAAAxx\n3850\t1929\t0\t2\t0\t10\t50\t850\t1850\t3850\t3850\t100\t101\tCSAAAA\tFWCAAA\tHHHHxx\n5624\t1930\t0\t0\t4\t4\t24\t624\t1624\t624\t5624\t48\t49\tIIAAAA\tGWCAAA\tOOOOxx\n8948\t1931\t0\t0\t8\t8\t48\t948\t948\t3948\t8948\t96\t97\tEGAAAA\tHWCAAA\tVVVVxx\n995\t1932\t1\t3\t5\t15\t95\t995\t995\t995\t995\t190\t191\tHMAAAA\tIWCAAA\tAAAAxx\n5058\t1933\t0\t2\t8\t18\t58\t58\t1058\t58\t5058\t116\t117\tOMAAAA\tJWCAAA\tHHHHxx\n9670\t1934\t0\t2\t0\t10\t70\t670\t1670\t4670\t9670\t140\t141\tYHAAAA\tKWCAAA\tOOOOxx\n3115\t1935\t1\t3\t5\t15\t15\t115\t1115\t3115\t3115\t30\t31\tVPAAAA\tLWCAAA\tVVVVxx\n4935\t1936\t1\t3\t5\t15\t35\t935\t935\t4935\t4935\t70\t71\tVHAAAA\tMWCAAA\tAAAAxx\n4735\t1937\t1\t3\t5\t15\t35\t735\t735\t4735\t4735\t70\t71\tDAAAAA\tNWCAAA\tHHHHxx\n1348\t1938\t0\t0\t8\t8\t48\t348\t1348\t1348\t1348\t96\t97\tWZAAAA\tOWCAAA\tOOOOxx\n2380\t1939\t0\t0\t0\t0\t80\t380\t380\t2380\t2380\t160\t161\tONAAAA\tPWCAAA\tVVVVxx\n4246\t1940\t0\t2\t6\t6\t46\t246\t246\t4246\t4246\t92\t93\tIHAAAA\tQWCAAA\tAAAAxx\n522\t1941\t0\t2\t2\t2\t22\t522\t522\t522\t522\t44\t45\tCUAAAA\tRWCAAA\tHHHHxx\n1701\t1942\t1\t1\t1\t1\t1\t701\t1701\t1701\t1701\t2\t3\tLNAAAA\tSWCAAA\tOOOOxx\n9709\t1943\t1\t1\t9\t9\t9\t709\t1709\t4709\t9709\t18\t19\tLJAAAA\tTWCAAA\tVVVVxx\n8829\t1944\t1\t1\t9\t9\t29\t829\t829\t3829\t8829\t58\t59\tPBAAAA\tUWCAAA\tAAAAxx\n7936\t1945\t0\t0\t6\t16\t36\t936\t1936\t2936\t7936\t72\t73\tGTAAAA\tVWCAAA\tHHHHxx\n8474\t1946\t0\t2\t4\t14\t74\t474\t474\t3474\t8474\t148\t149\tYNAAAA\tWWCAAA\tOOOOxx\n4676\t1947\t0\t0\t6\t16\t76\t676\t676\t4676\t4676\t152\t153\tWXAAAA\tXWCAAA\tVVVVxx\n6303\t1948\t1\t3\t3\t3\t3\t303\t303\t1303\t6303\t6\t7\tLIAAAA\tYWCAAA\tAAAAxx\n3485\t1949\t1\t1\t5\t5\t85\t485\t1485\t3485\t3485\t170\t171\tBEAAAA\tZWCAAA\tHHHHxx\n2695\t1950\t1\t3\t5\t15\t95\t695\t695\t2695\t2695\t190\t191\tRZAAAA\tAXCAAA\tOOOOxx\n8830\t1951\t0\t2\t0\t10\t30\t830\t830\t3830\t8830\t60\t61\tQBAAAA\tBXCAAA\tVVVVxx\n898\t1952\t0\t2\t8\t18\t98\t898\t898\t898\t898\t196\t197\tOIAAAA\tCXCAAA\tAAAAxx\n7268\t1953\t0\t0\t8\t8\t68\t268\t1268\t2268\t7268\t136\t137\tOTAAAA\tDXCAAA\tHHHHxx\n6568\t1954\t0\t0\t8\t8\t68\t568\t568\t1568\t6568\t136\t137\tQSAAAA\tEXCAAA\tOOOOxx\n9724\t1955\t0\t0\t4\t4\t24\t724\t1724\t4724\t9724\t48\t49\tAKAAAA\tFXCAAA\tVVVVxx\n3329\t1956\t1\t1\t9\t9\t29\t329\t1329\t3329\t3329\t58\t59\tBYAAAA\tGXCAAA\tAAAAxx\n9860\t1957\t0\t0\t0\t0\t60\t860\t1860\t4860\t9860\t120\t121\tGPAAAA\tHXCAAA\tHHHHxx\n6833\t1958\t1\t1\t3\t13\t33\t833\t833\t1833\t6833\t66\t67\tVCAAAA\tIXCAAA\tOOOOxx\n5956\t1959\t0\t0\t6\t16\t56\t956\t1956\t956\t5956\t112\t113\tCVAAAA\tJXCAAA\tVVVVxx\n3963\t1960\t1\t3\t3\t3\t63\t963\t1963\t3963\t3963\t126\t127\tLWAAAA\tKXCAAA\tAAAAxx\n883\t1961\t1\t3\t3\t3\t83\t883\t883\t883\t883\t166\t167\tZHAAAA\tLXCAAA\tHHHHxx\n2761\t1962\t1\t1\t1\t1\t61\t761\t761\t2761\t2761\t122\t123\tFCAAAA\tMXCAAA\tOOOOxx\n4644\t1963\t0\t0\t4\t4\t44\t644\t644\t4644\t4644\t88\t89\tQWAAAA\tNXCAAA\tVVVVxx\n1358\t1964\t0\t2\t8\t18\t58\t358\t1358\t1358\t1358\t116\t117\tGAAAAA\tOXCAAA\tAAAAxx\n2049\t1965\t1\t1\t9\t9\t49\t49\t49\t2049\t2049\t98\t99\tVAAAAA\tPXCAAA\tHHHHxx\n2193\t1966\t1\t1\t3\t13\t93\t193\t193\t2193\t2193\t186\t187\tJGAAAA\tQXCAAA\tOOOOxx\n9435\t1967\t1\t3\t5\t15\t35\t435\t1435\t4435\t9435\t70\t71\tXYAAAA\tRXCAAA\tVVVVxx\n5890\t1968\t0\t2\t0\t10\t90\t890\t1890\t890\t5890\t180\t181\tOSAAAA\tSXCAAA\tAAAAxx\n8149\t1969\t1\t1\t9\t9\t49\t149\t149\t3149\t8149\t98\t99\tLBAAAA\tTXCAAA\tHHHHxx\n423\t1970\t1\t3\t3\t3\t23\t423\t423\t423\t423\t46\t47\tHQAAAA\tUXCAAA\tOOOOxx\n7980\t1971\t0\t0\t0\t0\t80\t980\t1980\t2980\t7980\t160\t161\tYUAAAA\tVXCAAA\tVVVVxx\n9019\t1972\t1\t3\t9\t19\t19\t19\t1019\t4019\t9019\t38\t39\tXIAAAA\tWXCAAA\tAAAAxx\n1647\t1973\t1\t3\t7\t7\t47\t647\t1647\t1647\t1647\t94\t95\tJLAAAA\tXXCAAA\tHHHHxx\n9495\t1974\t1\t3\t5\t15\t95\t495\t1495\t4495\t9495\t190\t191\tFBAAAA\tYXCAAA\tOOOOxx\n3904\t1975\t0\t0\t4\t4\t4\t904\t1904\t3904\t3904\t8\t9\tEUAAAA\tZXCAAA\tVVVVxx\n5838\t1976\t0\t2\t8\t18\t38\t838\t1838\t838\t5838\t76\t77\tOQAAAA\tAYCAAA\tAAAAxx\n3866\t1977\t0\t2\t6\t6\t66\t866\t1866\t3866\t3866\t132\t133\tSSAAAA\tBYCAAA\tHHHHxx\n3093\t1978\t1\t1\t3\t13\t93\t93\t1093\t3093\t3093\t186\t187\tZOAAAA\tCYCAAA\tOOOOxx\n9666\t1979\t0\t2\t6\t6\t66\t666\t1666\t4666\t9666\t132\t133\tUHAAAA\tDYCAAA\tVVVVxx\n1246\t1980\t0\t2\t6\t6\t46\t246\t1246\t1246\t1246\t92\t93\tYVAAAA\tEYCAAA\tAAAAxx\n9759\t1981\t1\t3\t9\t19\t59\t759\t1759\t4759\t9759\t118\t119\tJLAAAA\tFYCAAA\tHHHHxx\n7174\t1982\t0\t2\t4\t14\t74\t174\t1174\t2174\t7174\t148\t149\tYPAAAA\tGYCAAA\tOOOOxx\n7678\t1983\t0\t2\t8\t18\t78\t678\t1678\t2678\t7678\t156\t157\tIJAAAA\tHYCAAA\tVVVVxx\n3004\t1984\t0\t0\t4\t4\t4\t4\t1004\t3004\t3004\t8\t9\tOLAAAA\tIYCAAA\tAAAAxx\n5607\t1985\t1\t3\t7\t7\t7\t607\t1607\t607\t5607\t14\t15\tRHAAAA\tJYCAAA\tHHHHxx\n8510\t1986\t0\t2\t0\t10\t10\t510\t510\t3510\t8510\t20\t21\tIPAAAA\tKYCAAA\tOOOOxx\n1483\t1987\t1\t3\t3\t3\t83\t483\t1483\t1483\t1483\t166\t167\tBFAAAA\tLYCAAA\tVVVVxx\n2915\t1988\t1\t3\t5\t15\t15\t915\t915\t2915\t2915\t30\t31\tDIAAAA\tMYCAAA\tAAAAxx\n1548\t1989\t0\t0\t8\t8\t48\t548\t1548\t1548\t1548\t96\t97\tOHAAAA\tNYCAAA\tHHHHxx\n5767\t1990\t1\t3\t7\t7\t67\t767\t1767\t767\t5767\t134\t135\tVNAAAA\tOYCAAA\tOOOOxx\n3214\t1991\t0\t2\t4\t14\t14\t214\t1214\t3214\t3214\t28\t29\tQTAAAA\tPYCAAA\tVVVVxx\n8663\t1992\t1\t3\t3\t3\t63\t663\t663\t3663\t8663\t126\t127\tFVAAAA\tQYCAAA\tAAAAxx\n5425\t1993\t1\t1\t5\t5\t25\t425\t1425\t425\t5425\t50\t51\tRAAAAA\tRYCAAA\tHHHHxx\n8530\t1994\t0\t2\t0\t10\t30\t530\t530\t3530\t8530\t60\t61\tCQAAAA\tSYCAAA\tOOOOxx\n821\t1995\t1\t1\t1\t1\t21\t821\t821\t821\t821\t42\t43\tPFAAAA\tTYCAAA\tVVVVxx\n8816\t1996\t0\t0\t6\t16\t16\t816\t816\t3816\t8816\t32\t33\tCBAAAA\tUYCAAA\tAAAAxx\n9367\t1997\t1\t3\t7\t7\t67\t367\t1367\t4367\t9367\t134\t135\tHWAAAA\tVYCAAA\tHHHHxx\n4138\t1998\t0\t2\t8\t18\t38\t138\t138\t4138\t4138\t76\t77\tEDAAAA\tWYCAAA\tOOOOxx\n94\t1999\t0\t2\t4\t14\t94\t94\t94\t94\t94\t188\t189\tQDAAAA\tXYCAAA\tVVVVxx\n1858\t2000\t0\t2\t8\t18\t58\t858\t1858\t1858\t1858\t116\t117\tMTAAAA\tYYCAAA\tAAAAxx\n5513\t2001\t1\t1\t3\t13\t13\t513\t1513\t513\t5513\t26\t27\tBEAAAA\tZYCAAA\tHHHHxx\n9620\t2002\t0\t0\t0\t0\t20\t620\t1620\t4620\t9620\t40\t41\tAGAAAA\tAZCAAA\tOOOOxx\n4770\t2003\t0\t2\t0\t10\t70\t770\t770\t4770\t4770\t140\t141\tMBAAAA\tBZCAAA\tVVVVxx\n5193\t2004\t1\t1\t3\t13\t93\t193\t1193\t193\t5193\t186\t187\tTRAAAA\tCZCAAA\tAAAAxx\n198\t2005\t0\t2\t8\t18\t98\t198\t198\t198\t198\t196\t197\tQHAAAA\tDZCAAA\tHHHHxx\n417\t2006\t1\t1\t7\t17\t17\t417\t417\t417\t417\t34\t35\tBQAAAA\tEZCAAA\tOOOOxx\n173\t2007\t1\t1\t3\t13\t73\t173\t173\t173\t173\t146\t147\tRGAAAA\tFZCAAA\tVVVVxx\n6248\t2008\t0\t0\t8\t8\t48\t248\t248\t1248\t6248\t96\t97\tIGAAAA\tGZCAAA\tAAAAxx\n302\t2009\t0\t2\t2\t2\t2\t302\t302\t302\t302\t4\t5\tQLAAAA\tHZCAAA\tHHHHxx\n8983\t2010\t1\t3\t3\t3\t83\t983\t983\t3983\t8983\t166\t167\tNHAAAA\tIZCAAA\tOOOOxx\n4840\t2011\t0\t0\t0\t0\t40\t840\t840\t4840\t4840\t80\t81\tEEAAAA\tJZCAAA\tVVVVxx\n2876\t2012\t0\t0\t6\t16\t76\t876\t876\t2876\t2876\t152\t153\tQGAAAA\tKZCAAA\tAAAAxx\n5841\t2013\t1\t1\t1\t1\t41\t841\t1841\t841\t5841\t82\t83\tRQAAAA\tLZCAAA\tHHHHxx\n2766\t2014\t0\t2\t6\t6\t66\t766\t766\t2766\t2766\t132\t133\tKCAAAA\tMZCAAA\tOOOOxx\n9482\t2015\t0\t2\t2\t2\t82\t482\t1482\t4482\t9482\t164\t165\tSAAAAA\tNZCAAA\tVVVVxx\n5335\t2016\t1\t3\t5\t15\t35\t335\t1335\t335\t5335\t70\t71\tFXAAAA\tOZCAAA\tAAAAxx\n1502\t2017\t0\t2\t2\t2\t2\t502\t1502\t1502\t1502\t4\t5\tUFAAAA\tPZCAAA\tHHHHxx\n9291\t2018\t1\t3\t1\t11\t91\t291\t1291\t4291\t9291\t182\t183\tJTAAAA\tQZCAAA\tOOOOxx\n8655\t2019\t1\t3\t5\t15\t55\t655\t655\t3655\t8655\t110\t111\tXUAAAA\tRZCAAA\tVVVVxx\n1687\t2020\t1\t3\t7\t7\t87\t687\t1687\t1687\t1687\t174\t175\tXMAAAA\tSZCAAA\tAAAAxx\n8171\t2021\t1\t3\t1\t11\t71\t171\t171\t3171\t8171\t142\t143\tHCAAAA\tTZCAAA\tHHHHxx\n5699\t2022\t1\t3\t9\t19\t99\t699\t1699\t699\t5699\t198\t199\tFLAAAA\tUZCAAA\tOOOOxx\n1462\t2023\t0\t2\t2\t2\t62\t462\t1462\t1462\t1462\t124\t125\tGEAAAA\tVZCAAA\tVVVVxx\n608\t2024\t0\t0\t8\t8\t8\t608\t608\t608\t608\t16\t17\tKXAAAA\tWZCAAA\tAAAAxx\n6860\t2025\t0\t0\t0\t0\t60\t860\t860\t1860\t6860\t120\t121\tWDAAAA\tXZCAAA\tHHHHxx\n6063\t2026\t1\t3\t3\t3\t63\t63\t63\t1063\t6063\t126\t127\tFZAAAA\tYZCAAA\tOOOOxx\n1422\t2027\t0\t2\t2\t2\t22\t422\t1422\t1422\t1422\t44\t45\tSCAAAA\tZZCAAA\tVVVVxx\n1932\t2028\t0\t0\t2\t12\t32\t932\t1932\t1932\t1932\t64\t65\tIWAAAA\tAADAAA\tAAAAxx\n5065\t2029\t1\t1\t5\t5\t65\t65\t1065\t65\t5065\t130\t131\tVMAAAA\tBADAAA\tHHHHxx\n432\t2030\t0\t0\t2\t12\t32\t432\t432\t432\t432\t64\t65\tQQAAAA\tCADAAA\tOOOOxx\n4680\t2031\t0\t0\t0\t0\t80\t680\t680\t4680\t4680\t160\t161\tAYAAAA\tDADAAA\tVVVVxx\n8172\t2032\t0\t0\t2\t12\t72\t172\t172\t3172\t8172\t144\t145\tICAAAA\tEADAAA\tAAAAxx\n8668\t2033\t0\t0\t8\t8\t68\t668\t668\t3668\t8668\t136\t137\tKVAAAA\tFADAAA\tHHHHxx\n256\t2034\t0\t0\t6\t16\t56\t256\t256\t256\t256\t112\t113\tWJAAAA\tGADAAA\tOOOOxx\n2500\t2035\t0\t0\t0\t0\t0\t500\t500\t2500\t2500\t0\t1\tESAAAA\tHADAAA\tVVVVxx\n274\t2036\t0\t2\t4\t14\t74\t274\t274\t274\t274\t148\t149\tOKAAAA\tIADAAA\tAAAAxx\n5907\t2037\t1\t3\t7\t7\t7\t907\t1907\t907\t5907\t14\t15\tFTAAAA\tJADAAA\tHHHHxx\n8587\t2038\t1\t3\t7\t7\t87\t587\t587\t3587\t8587\t174\t175\tHSAAAA\tKADAAA\tOOOOxx\n9942\t2039\t0\t2\t2\t2\t42\t942\t1942\t4942\t9942\t84\t85\tKSAAAA\tLADAAA\tVVVVxx\n116\t2040\t0\t0\t6\t16\t16\t116\t116\t116\t116\t32\t33\tMEAAAA\tMADAAA\tAAAAxx\n7134\t2041\t0\t2\t4\t14\t34\t134\t1134\t2134\t7134\t68\t69\tKOAAAA\tNADAAA\tHHHHxx\n9002\t2042\t0\t2\t2\t2\t2\t2\t1002\t4002\t9002\t4\t5\tGIAAAA\tOADAAA\tOOOOxx\n1209\t2043\t1\t1\t9\t9\t9\t209\t1209\t1209\t1209\t18\t19\tNUAAAA\tPADAAA\tVVVVxx\n9983\t2044\t1\t3\t3\t3\t83\t983\t1983\t4983\t9983\t166\t167\tZTAAAA\tQADAAA\tAAAAxx\n1761\t2045\t1\t1\t1\t1\t61\t761\t1761\t1761\t1761\t122\t123\tTPAAAA\tRADAAA\tHHHHxx\n7723\t2046\t1\t3\t3\t3\t23\t723\t1723\t2723\t7723\t46\t47\tBLAAAA\tSADAAA\tOOOOxx\n6518\t2047\t0\t2\t8\t18\t18\t518\t518\t1518\t6518\t36\t37\tSQAAAA\tTADAAA\tVVVVxx\n1372\t2048\t0\t0\t2\t12\t72\t372\t1372\t1372\t1372\t144\t145\tUAAAAA\tUADAAA\tAAAAxx\n3587\t2049\t1\t3\t7\t7\t87\t587\t1587\t3587\t3587\t174\t175\tZHAAAA\tVADAAA\tHHHHxx\n5323\t2050\t1\t3\t3\t3\t23\t323\t1323\t323\t5323\t46\t47\tTWAAAA\tWADAAA\tOOOOxx\n5902\t2051\t0\t2\t2\t2\t2\t902\t1902\t902\t5902\t4\t5\tATAAAA\tXADAAA\tVVVVxx\n3749\t2052\t1\t1\t9\t9\t49\t749\t1749\t3749\t3749\t98\t99\tFOAAAA\tYADAAA\tAAAAxx\n5965\t2053\t1\t1\t5\t5\t65\t965\t1965\t965\t5965\t130\t131\tLVAAAA\tZADAAA\tHHHHxx\n663\t2054\t1\t3\t3\t3\t63\t663\t663\t663\t663\t126\t127\tNZAAAA\tABDAAA\tOOOOxx\n36\t2055\t0\t0\t6\t16\t36\t36\t36\t36\t36\t72\t73\tKBAAAA\tBBDAAA\tVVVVxx\n9782\t2056\t0\t2\t2\t2\t82\t782\t1782\t4782\t9782\t164\t165\tGMAAAA\tCBDAAA\tAAAAxx\n5412\t2057\t0\t0\t2\t12\t12\t412\t1412\t412\t5412\t24\t25\tEAAAAA\tDBDAAA\tHHHHxx\n9961\t2058\t1\t1\t1\t1\t61\t961\t1961\t4961\t9961\t122\t123\tDTAAAA\tEBDAAA\tOOOOxx\n6492\t2059\t0\t0\t2\t12\t92\t492\t492\t1492\t6492\t184\t185\tSPAAAA\tFBDAAA\tVVVVxx\n4234\t2060\t0\t2\t4\t14\t34\t234\t234\t4234\t4234\t68\t69\tWGAAAA\tGBDAAA\tAAAAxx\n4922\t2061\t0\t2\t2\t2\t22\t922\t922\t4922\t4922\t44\t45\tIHAAAA\tHBDAAA\tHHHHxx\n6166\t2062\t0\t2\t6\t6\t66\t166\t166\t1166\t6166\t132\t133\tEDAAAA\tIBDAAA\tOOOOxx\n7019\t2063\t1\t3\t9\t19\t19\t19\t1019\t2019\t7019\t38\t39\tZJAAAA\tJBDAAA\tVVVVxx\n7805\t2064\t1\t1\t5\t5\t5\t805\t1805\t2805\t7805\t10\t11\tFOAAAA\tKBDAAA\tAAAAxx\n9808\t2065\t0\t0\t8\t8\t8\t808\t1808\t4808\t9808\t16\t17\tGNAAAA\tLBDAAA\tHHHHxx\n2550\t2066\t0\t2\t0\t10\t50\t550\t550\t2550\t2550\t100\t101\tCUAAAA\tMBDAAA\tOOOOxx\n8626\t2067\t0\t2\t6\t6\t26\t626\t626\t3626\t8626\t52\t53\tUTAAAA\tNBDAAA\tVVVVxx\n5649\t2068\t1\t1\t9\t9\t49\t649\t1649\t649\t5649\t98\t99\tHJAAAA\tOBDAAA\tAAAAxx\n3117\t2069\t1\t1\t7\t17\t17\t117\t1117\t3117\t3117\t34\t35\tXPAAAA\tPBDAAA\tHHHHxx\n866\t2070\t0\t2\t6\t6\t66\t866\t866\t866\t866\t132\t133\tIHAAAA\tQBDAAA\tOOOOxx\n2323\t2071\t1\t3\t3\t3\t23\t323\t323\t2323\t2323\t46\t47\tJLAAAA\tRBDAAA\tVVVVxx\n5132\t2072\t0\t0\t2\t12\t32\t132\t1132\t132\t5132\t64\t65\tKPAAAA\tSBDAAA\tAAAAxx\n9222\t2073\t0\t2\t2\t2\t22\t222\t1222\t4222\t9222\t44\t45\tSQAAAA\tTBDAAA\tHHHHxx\n3934\t2074\t0\t2\t4\t14\t34\t934\t1934\t3934\t3934\t68\t69\tIVAAAA\tUBDAAA\tOOOOxx\n4845\t2075\t1\t1\t5\t5\t45\t845\t845\t4845\t4845\t90\t91\tJEAAAA\tVBDAAA\tVVVVxx\n7714\t2076\t0\t2\t4\t14\t14\t714\t1714\t2714\t7714\t28\t29\tSKAAAA\tWBDAAA\tAAAAxx\n9818\t2077\t0\t2\t8\t18\t18\t818\t1818\t4818\t9818\t36\t37\tQNAAAA\tXBDAAA\tHHHHxx\n2219\t2078\t1\t3\t9\t19\t19\t219\t219\t2219\t2219\t38\t39\tJHAAAA\tYBDAAA\tOOOOxx\n6573\t2079\t1\t1\t3\t13\t73\t573\t573\t1573\t6573\t146\t147\tVSAAAA\tZBDAAA\tVVVVxx\n4555\t2080\t1\t3\t5\t15\t55\t555\t555\t4555\t4555\t110\t111\tFTAAAA\tACDAAA\tAAAAxx\n7306\t2081\t0\t2\t6\t6\t6\t306\t1306\t2306\t7306\t12\t13\tAVAAAA\tBCDAAA\tHHHHxx\n9313\t2082\t1\t1\t3\t13\t13\t313\t1313\t4313\t9313\t26\t27\tFUAAAA\tCCDAAA\tOOOOxx\n3924\t2083\t0\t0\t4\t4\t24\t924\t1924\t3924\t3924\t48\t49\tYUAAAA\tDCDAAA\tVVVVxx\n5176\t2084\t0\t0\t6\t16\t76\t176\t1176\t176\t5176\t152\t153\tCRAAAA\tECDAAA\tAAAAxx\n9767\t2085\t1\t3\t7\t7\t67\t767\t1767\t4767\t9767\t134\t135\tRLAAAA\tFCDAAA\tHHHHxx\n905\t2086\t1\t1\t5\t5\t5\t905\t905\t905\t905\t10\t11\tVIAAAA\tGCDAAA\tOOOOxx\n8037\t2087\t1\t1\t7\t17\t37\t37\t37\t3037\t8037\t74\t75\tDXAAAA\tHCDAAA\tVVVVxx\n8133\t2088\t1\t1\t3\t13\t33\t133\t133\t3133\t8133\t66\t67\tVAAAAA\tICDAAA\tAAAAxx\n2954\t2089\t0\t2\t4\t14\t54\t954\t954\t2954\t2954\t108\t109\tQJAAAA\tJCDAAA\tHHHHxx\n7262\t2090\t0\t2\t2\t2\t62\t262\t1262\t2262\t7262\t124\t125\tITAAAA\tKCDAAA\tOOOOxx\n8768\t2091\t0\t0\t8\t8\t68\t768\t768\t3768\t8768\t136\t137\tGZAAAA\tLCDAAA\tVVVVxx\n6953\t2092\t1\t1\t3\t13\t53\t953\t953\t1953\t6953\t106\t107\tLHAAAA\tMCDAAA\tAAAAxx\n1984\t2093\t0\t0\t4\t4\t84\t984\t1984\t1984\t1984\t168\t169\tIYAAAA\tNCDAAA\tHHHHxx\n9348\t2094\t0\t0\t8\t8\t48\t348\t1348\t4348\t9348\t96\t97\tOVAAAA\tOCDAAA\tOOOOxx\n7769\t2095\t1\t1\t9\t9\t69\t769\t1769\t2769\t7769\t138\t139\tVMAAAA\tPCDAAA\tVVVVxx\n2994\t2096\t0\t2\t4\t14\t94\t994\t994\t2994\t2994\t188\t189\tELAAAA\tQCDAAA\tAAAAxx\n5938\t2097\t0\t2\t8\t18\t38\t938\t1938\t938\t5938\t76\t77\tKUAAAA\tRCDAAA\tHHHHxx\n556\t2098\t0\t0\t6\t16\t56\t556\t556\t556\t556\t112\t113\tKVAAAA\tSCDAAA\tOOOOxx\n2577\t2099\t1\t1\t7\t17\t77\t577\t577\t2577\t2577\t154\t155\tDVAAAA\tTCDAAA\tVVVVxx\n8733\t2100\t1\t1\t3\t13\t33\t733\t733\t3733\t8733\t66\t67\tXXAAAA\tUCDAAA\tAAAAxx\n3108\t2101\t0\t0\t8\t8\t8\t108\t1108\t3108\t3108\t16\t17\tOPAAAA\tVCDAAA\tHHHHxx\n4166\t2102\t0\t2\t6\t6\t66\t166\t166\t4166\t4166\t132\t133\tGEAAAA\tWCDAAA\tOOOOxx\n3170\t2103\t0\t2\t0\t10\t70\t170\t1170\t3170\t3170\t140\t141\tYRAAAA\tXCDAAA\tVVVVxx\n8118\t2104\t0\t2\t8\t18\t18\t118\t118\t3118\t8118\t36\t37\tGAAAAA\tYCDAAA\tAAAAxx\n8454\t2105\t0\t2\t4\t14\t54\t454\t454\t3454\t8454\t108\t109\tENAAAA\tZCDAAA\tHHHHxx\n5338\t2106\t0\t2\t8\t18\t38\t338\t1338\t338\t5338\t76\t77\tIXAAAA\tADDAAA\tOOOOxx\n402\t2107\t0\t2\t2\t2\t2\t402\t402\t402\t402\t4\t5\tMPAAAA\tBDDAAA\tVVVVxx\n5673\t2108\t1\t1\t3\t13\t73\t673\t1673\t673\t5673\t146\t147\tFKAAAA\tCDDAAA\tAAAAxx\n4324\t2109\t0\t0\t4\t4\t24\t324\t324\t4324\t4324\t48\t49\tIKAAAA\tDDDAAA\tHHHHxx\n1943\t2110\t1\t3\t3\t3\t43\t943\t1943\t1943\t1943\t86\t87\tTWAAAA\tEDDAAA\tOOOOxx\n7703\t2111\t1\t3\t3\t3\t3\t703\t1703\t2703\t7703\t6\t7\tHKAAAA\tFDDAAA\tVVVVxx\n7180\t2112\t0\t0\t0\t0\t80\t180\t1180\t2180\t7180\t160\t161\tEQAAAA\tGDDAAA\tAAAAxx\n5478\t2113\t0\t2\t8\t18\t78\t478\t1478\t478\t5478\t156\t157\tSCAAAA\tHDDAAA\tHHHHxx\n5775\t2114\t1\t3\t5\t15\t75\t775\t1775\t775\t5775\t150\t151\tDOAAAA\tIDDAAA\tOOOOxx\n6952\t2115\t0\t0\t2\t12\t52\t952\t952\t1952\t6952\t104\t105\tKHAAAA\tJDDAAA\tVVVVxx\n9022\t2116\t0\t2\t2\t2\t22\t22\t1022\t4022\t9022\t44\t45\tAJAAAA\tKDDAAA\tAAAAxx\n547\t2117\t1\t3\t7\t7\t47\t547\t547\t547\t547\t94\t95\tBVAAAA\tLDDAAA\tHHHHxx\n5877\t2118\t1\t1\t7\t17\t77\t877\t1877\t877\t5877\t154\t155\tBSAAAA\tMDDAAA\tOOOOxx\n9580\t2119\t0\t0\t0\t0\t80\t580\t1580\t4580\t9580\t160\t161\tMEAAAA\tNDDAAA\tVVVVxx\n6094\t2120\t0\t2\t4\t14\t94\t94\t94\t1094\t6094\t188\t189\tKAAAAA\tODDAAA\tAAAAxx\n3398\t2121\t0\t2\t8\t18\t98\t398\t1398\t3398\t3398\t196\t197\tSAAAAA\tPDDAAA\tHHHHxx\n4574\t2122\t0\t2\t4\t14\t74\t574\t574\t4574\t4574\t148\t149\tYTAAAA\tQDDAAA\tOOOOxx\n3675\t2123\t1\t3\t5\t15\t75\t675\t1675\t3675\t3675\t150\t151\tJLAAAA\tRDDAAA\tVVVVxx\n6413\t2124\t1\t1\t3\t13\t13\t413\t413\t1413\t6413\t26\t27\tRMAAAA\tSDDAAA\tAAAAxx\n9851\t2125\t1\t3\t1\t11\t51\t851\t1851\t4851\t9851\t102\t103\tXOAAAA\tTDDAAA\tHHHHxx\n126\t2126\t0\t2\t6\t6\t26\t126\t126\t126\t126\t52\t53\tWEAAAA\tUDDAAA\tOOOOxx\n6803\t2127\t1\t3\t3\t3\t3\t803\t803\t1803\t6803\t6\t7\tRBAAAA\tVDDAAA\tVVVVxx\n6949\t2128\t1\t1\t9\t9\t49\t949\t949\t1949\t6949\t98\t99\tHHAAAA\tWDDAAA\tAAAAxx\n115\t2129\t1\t3\t5\t15\t15\t115\t115\t115\t115\t30\t31\tLEAAAA\tXDDAAA\tHHHHxx\n4165\t2130\t1\t1\t5\t5\t65\t165\t165\t4165\t4165\t130\t131\tFEAAAA\tYDDAAA\tOOOOxx\n201\t2131\t1\t1\t1\t1\t1\t201\t201\t201\t201\t2\t3\tTHAAAA\tZDDAAA\tVVVVxx\n9324\t2132\t0\t0\t4\t4\t24\t324\t1324\t4324\t9324\t48\t49\tQUAAAA\tAEDAAA\tAAAAxx\n6562\t2133\t0\t2\t2\t2\t62\t562\t562\t1562\t6562\t124\t125\tKSAAAA\tBEDAAA\tHHHHxx\n1917\t2134\t1\t1\t7\t17\t17\t917\t1917\t1917\t1917\t34\t35\tTVAAAA\tCEDAAA\tOOOOxx\n558\t2135\t0\t2\t8\t18\t58\t558\t558\t558\t558\t116\t117\tMVAAAA\tDEDAAA\tVVVVxx\n8515\t2136\t1\t3\t5\t15\t15\t515\t515\t3515\t8515\t30\t31\tNPAAAA\tEEDAAA\tAAAAxx\n6321\t2137\t1\t1\t1\t1\t21\t321\t321\t1321\t6321\t42\t43\tDJAAAA\tFEDAAA\tHHHHxx\n6892\t2138\t0\t0\t2\t12\t92\t892\t892\t1892\t6892\t184\t185\tCFAAAA\tGEDAAA\tOOOOxx\n1001\t2139\t1\t1\t1\t1\t1\t1\t1001\t1001\t1001\t2\t3\tNMAAAA\tHEDAAA\tVVVVxx\n2858\t2140\t0\t2\t8\t18\t58\t858\t858\t2858\t2858\t116\t117\tYFAAAA\tIEDAAA\tAAAAxx\n2434\t2141\t0\t2\t4\t14\t34\t434\t434\t2434\t2434\t68\t69\tQPAAAA\tJEDAAA\tHHHHxx\n4460\t2142\t0\t0\t0\t0\t60\t460\t460\t4460\t4460\t120\t121\tOPAAAA\tKEDAAA\tOOOOxx\n5447\t2143\t1\t3\t7\t7\t47\t447\t1447\t447\t5447\t94\t95\tNBAAAA\tLEDAAA\tVVVVxx\n3799\t2144\t1\t3\t9\t19\t99\t799\t1799\t3799\t3799\t198\t199\tDQAAAA\tMEDAAA\tAAAAxx\n4310\t2145\t0\t2\t0\t10\t10\t310\t310\t4310\t4310\t20\t21\tUJAAAA\tNEDAAA\tHHHHxx\n405\t2146\t1\t1\t5\t5\t5\t405\t405\t405\t405\t10\t11\tPPAAAA\tOEDAAA\tOOOOxx\n4573\t2147\t1\t1\t3\t13\t73\t573\t573\t4573\t4573\t146\t147\tXTAAAA\tPEDAAA\tVVVVxx\n706\t2148\t0\t2\t6\t6\t6\t706\t706\t706\t706\t12\t13\tEBAAAA\tQEDAAA\tAAAAxx\n7619\t2149\t1\t3\t9\t19\t19\t619\t1619\t2619\t7619\t38\t39\tBHAAAA\tREDAAA\tHHHHxx\n7959\t2150\t1\t3\t9\t19\t59\t959\t1959\t2959\t7959\t118\t119\tDUAAAA\tSEDAAA\tOOOOxx\n6712\t2151\t0\t0\t2\t12\t12\t712\t712\t1712\t6712\t24\t25\tEYAAAA\tTEDAAA\tVVVVxx\n6959\t2152\t1\t3\t9\t19\t59\t959\t959\t1959\t6959\t118\t119\tRHAAAA\tUEDAAA\tAAAAxx\n9791\t2153\t1\t3\t1\t11\t91\t791\t1791\t4791\t9791\t182\t183\tPMAAAA\tVEDAAA\tHHHHxx\n2112\t2154\t0\t0\t2\t12\t12\t112\t112\t2112\t2112\t24\t25\tGDAAAA\tWEDAAA\tOOOOxx\n9114\t2155\t0\t2\t4\t14\t14\t114\t1114\t4114\t9114\t28\t29\tOMAAAA\tXEDAAA\tVVVVxx\n3506\t2156\t0\t2\t6\t6\t6\t506\t1506\t3506\t3506\t12\t13\tWEAAAA\tYEDAAA\tAAAAxx\n5002\t2157\t0\t2\t2\t2\t2\t2\t1002\t2\t5002\t4\t5\tKKAAAA\tZEDAAA\tHHHHxx\n3518\t2158\t0\t2\t8\t18\t18\t518\t1518\t3518\t3518\t36\t37\tIFAAAA\tAFDAAA\tOOOOxx\n602\t2159\t0\t2\t2\t2\t2\t602\t602\t602\t602\t4\t5\tEXAAAA\tBFDAAA\tVVVVxx\n9060\t2160\t0\t0\t0\t0\t60\t60\t1060\t4060\t9060\t120\t121\tMKAAAA\tCFDAAA\tAAAAxx\n3292\t2161\t0\t0\t2\t12\t92\t292\t1292\t3292\t3292\t184\t185\tQWAAAA\tDFDAAA\tHHHHxx\n77\t2162\t1\t1\t7\t17\t77\t77\t77\t77\t77\t154\t155\tZCAAAA\tEFDAAA\tOOOOxx\n1420\t2163\t0\t0\t0\t0\t20\t420\t1420\t1420\t1420\t40\t41\tQCAAAA\tFFDAAA\tVVVVxx\n6001\t2164\t1\t1\t1\t1\t1\t1\t1\t1001\t6001\t2\t3\tVWAAAA\tGFDAAA\tAAAAxx\n7477\t2165\t1\t1\t7\t17\t77\t477\t1477\t2477\t7477\t154\t155\tPBAAAA\tHFDAAA\tHHHHxx\n6655\t2166\t1\t3\t5\t15\t55\t655\t655\t1655\t6655\t110\t111\tZVAAAA\tIFDAAA\tOOOOxx\n7845\t2167\t1\t1\t5\t5\t45\t845\t1845\t2845\t7845\t90\t91\tTPAAAA\tJFDAAA\tVVVVxx\n8484\t2168\t0\t0\t4\t4\t84\t484\t484\t3484\t8484\t168\t169\tIOAAAA\tKFDAAA\tAAAAxx\n4345\t2169\t1\t1\t5\t5\t45\t345\t345\t4345\t4345\t90\t91\tDLAAAA\tLFDAAA\tHHHHxx\n4250\t2170\t0\t2\t0\t10\t50\t250\t250\t4250\t4250\t100\t101\tMHAAAA\tMFDAAA\tOOOOxx\n2391\t2171\t1\t3\t1\t11\t91\t391\t391\t2391\t2391\t182\t183\tZNAAAA\tNFDAAA\tVVVVxx\n6884\t2172\t0\t0\t4\t4\t84\t884\t884\t1884\t6884\t168\t169\tUEAAAA\tOFDAAA\tAAAAxx\n7270\t2173\t0\t2\t0\t10\t70\t270\t1270\t2270\t7270\t140\t141\tQTAAAA\tPFDAAA\tHHHHxx\n2499\t2174\t1\t3\t9\t19\t99\t499\t499\t2499\t2499\t198\t199\tDSAAAA\tQFDAAA\tOOOOxx\n7312\t2175\t0\t0\t2\t12\t12\t312\t1312\t2312\t7312\t24\t25\tGVAAAA\tRFDAAA\tVVVVxx\n7113\t2176\t1\t1\t3\t13\t13\t113\t1113\t2113\t7113\t26\t27\tPNAAAA\tSFDAAA\tAAAAxx\n6695\t2177\t1\t3\t5\t15\t95\t695\t695\t1695\t6695\t190\t191\tNXAAAA\tTFDAAA\tHHHHxx\n6521\t2178\t1\t1\t1\t1\t21\t521\t521\t1521\t6521\t42\t43\tVQAAAA\tUFDAAA\tOOOOxx\n272\t2179\t0\t0\t2\t12\t72\t272\t272\t272\t272\t144\t145\tMKAAAA\tVFDAAA\tVVVVxx\n9976\t2180\t0\t0\t6\t16\t76\t976\t1976\t4976\t9976\t152\t153\tSTAAAA\tWFDAAA\tAAAAxx\n992\t2181\t0\t0\t2\t12\t92\t992\t992\t992\t992\t184\t185\tEMAAAA\tXFDAAA\tHHHHxx\n6158\t2182\t0\t2\t8\t18\t58\t158\t158\t1158\t6158\t116\t117\tWCAAAA\tYFDAAA\tOOOOxx\n3281\t2183\t1\t1\t1\t1\t81\t281\t1281\t3281\t3281\t162\t163\tFWAAAA\tZFDAAA\tVVVVxx\n7446\t2184\t0\t2\t6\t6\t46\t446\t1446\t2446\t7446\t92\t93\tKAAAAA\tAGDAAA\tAAAAxx\n4679\t2185\t1\t3\t9\t19\t79\t679\t679\t4679\t4679\t158\t159\tZXAAAA\tBGDAAA\tHHHHxx\n5203\t2186\t1\t3\t3\t3\t3\t203\t1203\t203\t5203\t6\t7\tDSAAAA\tCGDAAA\tOOOOxx\n9874\t2187\t0\t2\t4\t14\t74\t874\t1874\t4874\t9874\t148\t149\tUPAAAA\tDGDAAA\tVVVVxx\n8371\t2188\t1\t3\t1\t11\t71\t371\t371\t3371\t8371\t142\t143\tZJAAAA\tEGDAAA\tAAAAxx\n9086\t2189\t0\t2\t6\t6\t86\t86\t1086\t4086\t9086\t172\t173\tMLAAAA\tFGDAAA\tHHHHxx\n430\t2190\t0\t2\t0\t10\t30\t430\t430\t430\t430\t60\t61\tOQAAAA\tGGDAAA\tOOOOxx\n8749\t2191\t1\t1\t9\t9\t49\t749\t749\t3749\t8749\t98\t99\tNYAAAA\tHGDAAA\tVVVVxx\n577\t2192\t1\t1\t7\t17\t77\t577\t577\t577\t577\t154\t155\tFWAAAA\tIGDAAA\tAAAAxx\n4884\t2193\t0\t0\t4\t4\t84\t884\t884\t4884\t4884\t168\t169\tWFAAAA\tJGDAAA\tHHHHxx\n3421\t2194\t1\t1\t1\t1\t21\t421\t1421\t3421\t3421\t42\t43\tPBAAAA\tKGDAAA\tOOOOxx\n2812\t2195\t0\t0\t2\t12\t12\t812\t812\t2812\t2812\t24\t25\tEEAAAA\tLGDAAA\tVVVVxx\n5958\t2196\t0\t2\t8\t18\t58\t958\t1958\t958\t5958\t116\t117\tEVAAAA\tMGDAAA\tAAAAxx\n9901\t2197\t1\t1\t1\t1\t1\t901\t1901\t4901\t9901\t2\t3\tVQAAAA\tNGDAAA\tHHHHxx\n8478\t2198\t0\t2\t8\t18\t78\t478\t478\t3478\t8478\t156\t157\tCOAAAA\tOGDAAA\tOOOOxx\n6545\t2199\t1\t1\t5\t5\t45\t545\t545\t1545\t6545\t90\t91\tTRAAAA\tPGDAAA\tVVVVxx\n1479\t2200\t1\t3\t9\t19\t79\t479\t1479\t1479\t1479\t158\t159\tXEAAAA\tQGDAAA\tAAAAxx\n1046\t2201\t0\t2\t6\t6\t46\t46\t1046\t1046\t1046\t92\t93\tGOAAAA\tRGDAAA\tHHHHxx\n6372\t2202\t0\t0\t2\t12\t72\t372\t372\t1372\t6372\t144\t145\tCLAAAA\tSGDAAA\tOOOOxx\n8206\t2203\t0\t2\t6\t6\t6\t206\t206\t3206\t8206\t12\t13\tQDAAAA\tTGDAAA\tVVVVxx\n9544\t2204\t0\t0\t4\t4\t44\t544\t1544\t4544\t9544\t88\t89\tCDAAAA\tUGDAAA\tAAAAxx\n9287\t2205\t1\t3\t7\t7\t87\t287\t1287\t4287\t9287\t174\t175\tFTAAAA\tVGDAAA\tHHHHxx\n6786\t2206\t0\t2\t6\t6\t86\t786\t786\t1786\t6786\t172\t173\tABAAAA\tWGDAAA\tOOOOxx\n6511\t2207\t1\t3\t1\t11\t11\t511\t511\t1511\t6511\t22\t23\tLQAAAA\tXGDAAA\tVVVVxx\n603\t2208\t1\t3\t3\t3\t3\t603\t603\t603\t603\t6\t7\tFXAAAA\tYGDAAA\tAAAAxx\n2022\t2209\t0\t2\t2\t2\t22\t22\t22\t2022\t2022\t44\t45\tUZAAAA\tZGDAAA\tHHHHxx\n2086\t2210\t0\t2\t6\t6\t86\t86\t86\t2086\t2086\t172\t173\tGCAAAA\tAHDAAA\tOOOOxx\n1969\t2211\t1\t1\t9\t9\t69\t969\t1969\t1969\t1969\t138\t139\tTXAAAA\tBHDAAA\tVVVVxx\n4841\t2212\t1\t1\t1\t1\t41\t841\t841\t4841\t4841\t82\t83\tFEAAAA\tCHDAAA\tAAAAxx\n5845\t2213\t1\t1\t5\t5\t45\t845\t1845\t845\t5845\t90\t91\tVQAAAA\tDHDAAA\tHHHHxx\n4635\t2214\t1\t3\t5\t15\t35\t635\t635\t4635\t4635\t70\t71\tHWAAAA\tEHDAAA\tOOOOxx\n4658\t2215\t0\t2\t8\t18\t58\t658\t658\t4658\t4658\t116\t117\tEXAAAA\tFHDAAA\tVVVVxx\n2896\t2216\t0\t0\t6\t16\t96\t896\t896\t2896\t2896\t192\t193\tKHAAAA\tGHDAAA\tAAAAxx\n5179\t2217\t1\t3\t9\t19\t79\t179\t1179\t179\t5179\t158\t159\tFRAAAA\tHHDAAA\tHHHHxx\n8667\t2218\t1\t3\t7\t7\t67\t667\t667\t3667\t8667\t134\t135\tJVAAAA\tIHDAAA\tOOOOxx\n7294\t2219\t0\t2\t4\t14\t94\t294\t1294\t2294\t7294\t188\t189\tOUAAAA\tJHDAAA\tVVVVxx\n3706\t2220\t0\t2\t6\t6\t6\t706\t1706\t3706\t3706\t12\t13\tOMAAAA\tKHDAAA\tAAAAxx\n8389\t2221\t1\t1\t9\t9\t89\t389\t389\t3389\t8389\t178\t179\tRKAAAA\tLHDAAA\tHHHHxx\n2486\t2222\t0\t2\t6\t6\t86\t486\t486\t2486\t2486\t172\t173\tQRAAAA\tMHDAAA\tOOOOxx\n8743\t2223\t1\t3\t3\t3\t43\t743\t743\t3743\t8743\t86\t87\tHYAAAA\tNHDAAA\tVVVVxx\n2777\t2224\t1\t1\t7\t17\t77\t777\t777\t2777\t2777\t154\t155\tVCAAAA\tOHDAAA\tAAAAxx\n2113\t2225\t1\t1\t3\t13\t13\t113\t113\t2113\t2113\t26\t27\tHDAAAA\tPHDAAA\tHHHHxx\n2076\t2226\t0\t0\t6\t16\t76\t76\t76\t2076\t2076\t152\t153\tWBAAAA\tQHDAAA\tOOOOxx\n2300\t2227\t0\t0\t0\t0\t0\t300\t300\t2300\t2300\t0\t1\tMKAAAA\tRHDAAA\tVVVVxx\n6894\t2228\t0\t2\t4\t14\t94\t894\t894\t1894\t6894\t188\t189\tEFAAAA\tSHDAAA\tAAAAxx\n6939\t2229\t1\t3\t9\t19\t39\t939\t939\t1939\t6939\t78\t79\tXGAAAA\tTHDAAA\tHHHHxx\n446\t2230\t0\t2\t6\t6\t46\t446\t446\t446\t446\t92\t93\tERAAAA\tUHDAAA\tOOOOxx\n6218\t2231\t0\t2\t8\t18\t18\t218\t218\t1218\t6218\t36\t37\tEFAAAA\tVHDAAA\tVVVVxx\n1295\t2232\t1\t3\t5\t15\t95\t295\t1295\t1295\t1295\t190\t191\tVXAAAA\tWHDAAA\tAAAAxx\n5135\t2233\t1\t3\t5\t15\t35\t135\t1135\t135\t5135\t70\t71\tNPAAAA\tXHDAAA\tHHHHxx\n8122\t2234\t0\t2\t2\t2\t22\t122\t122\t3122\t8122\t44\t45\tKAAAAA\tYHDAAA\tOOOOxx\n316\t2235\t0\t0\t6\t16\t16\t316\t316\t316\t316\t32\t33\tEMAAAA\tZHDAAA\tVVVVxx\n514\t2236\t0\t2\t4\t14\t14\t514\t514\t514\t514\t28\t29\tUTAAAA\tAIDAAA\tAAAAxx\n7970\t2237\t0\t2\t0\t10\t70\t970\t1970\t2970\t7970\t140\t141\tOUAAAA\tBIDAAA\tHHHHxx\n9350\t2238\t0\t2\t0\t10\t50\t350\t1350\t4350\t9350\t100\t101\tQVAAAA\tCIDAAA\tOOOOxx\n3700\t2239\t0\t0\t0\t0\t0\t700\t1700\t3700\t3700\t0\t1\tIMAAAA\tDIDAAA\tVVVVxx\n582\t2240\t0\t2\t2\t2\t82\t582\t582\t582\t582\t164\t165\tKWAAAA\tEIDAAA\tAAAAxx\n9722\t2241\t0\t2\t2\t2\t22\t722\t1722\t4722\t9722\t44\t45\tYJAAAA\tFIDAAA\tHHHHxx\n7398\t2242\t0\t2\t8\t18\t98\t398\t1398\t2398\t7398\t196\t197\tOYAAAA\tGIDAAA\tOOOOxx\n2265\t2243\t1\t1\t5\t5\t65\t265\t265\t2265\t2265\t130\t131\tDJAAAA\tHIDAAA\tVVVVxx\n3049\t2244\t1\t1\t9\t9\t49\t49\t1049\t3049\t3049\t98\t99\tHNAAAA\tIIDAAA\tAAAAxx\n9121\t2245\t1\t1\t1\t1\t21\t121\t1121\t4121\t9121\t42\t43\tVMAAAA\tJIDAAA\tHHHHxx\n4275\t2246\t1\t3\t5\t15\t75\t275\t275\t4275\t4275\t150\t151\tLIAAAA\tKIDAAA\tOOOOxx\n6567\t2247\t1\t3\t7\t7\t67\t567\t567\t1567\t6567\t134\t135\tPSAAAA\tLIDAAA\tVVVVxx\n6755\t2248\t1\t3\t5\t15\t55\t755\t755\t1755\t6755\t110\t111\tVZAAAA\tMIDAAA\tAAAAxx\n4535\t2249\t1\t3\t5\t15\t35\t535\t535\t4535\t4535\t70\t71\tLSAAAA\tNIDAAA\tHHHHxx\n7968\t2250\t0\t0\t8\t8\t68\t968\t1968\t2968\t7968\t136\t137\tMUAAAA\tOIDAAA\tOOOOxx\n3412\t2251\t0\t0\t2\t12\t12\t412\t1412\t3412\t3412\t24\t25\tGBAAAA\tPIDAAA\tVVVVxx\n6112\t2252\t0\t0\t2\t12\t12\t112\t112\t1112\t6112\t24\t25\tCBAAAA\tQIDAAA\tAAAAxx\n6805\t2253\t1\t1\t5\t5\t5\t805\t805\t1805\t6805\t10\t11\tTBAAAA\tRIDAAA\tHHHHxx\n2880\t2254\t0\t0\t0\t0\t80\t880\t880\t2880\t2880\t160\t161\tUGAAAA\tSIDAAA\tOOOOxx\n7710\t2255\t0\t2\t0\t10\t10\t710\t1710\t2710\t7710\t20\t21\tOKAAAA\tTIDAAA\tVVVVxx\n7949\t2256\t1\t1\t9\t9\t49\t949\t1949\t2949\t7949\t98\t99\tTTAAAA\tUIDAAA\tAAAAxx\n7043\t2257\t1\t3\t3\t3\t43\t43\t1043\t2043\t7043\t86\t87\tXKAAAA\tVIDAAA\tHHHHxx\n9012\t2258\t0\t0\t2\t12\t12\t12\t1012\t4012\t9012\t24\t25\tQIAAAA\tWIDAAA\tOOOOxx\n878\t2259\t0\t2\t8\t18\t78\t878\t878\t878\t878\t156\t157\tUHAAAA\tXIDAAA\tVVVVxx\n7930\t2260\t0\t2\t0\t10\t30\t930\t1930\t2930\t7930\t60\t61\tATAAAA\tYIDAAA\tAAAAxx\n667\t2261\t1\t3\t7\t7\t67\t667\t667\t667\t667\t134\t135\tRZAAAA\tZIDAAA\tHHHHxx\n1905\t2262\t1\t1\t5\t5\t5\t905\t1905\t1905\t1905\t10\t11\tHVAAAA\tAJDAAA\tOOOOxx\n4958\t2263\t0\t2\t8\t18\t58\t958\t958\t4958\t4958\t116\t117\tSIAAAA\tBJDAAA\tVVVVxx\n2973\t2264\t1\t1\t3\t13\t73\t973\t973\t2973\t2973\t146\t147\tJKAAAA\tCJDAAA\tAAAAxx\n3631\t2265\t1\t3\t1\t11\t31\t631\t1631\t3631\t3631\t62\t63\tRJAAAA\tDJDAAA\tHHHHxx\n5868\t2266\t0\t0\t8\t8\t68\t868\t1868\t868\t5868\t136\t137\tSRAAAA\tEJDAAA\tOOOOxx\n2873\t2267\t1\t1\t3\t13\t73\t873\t873\t2873\t2873\t146\t147\tNGAAAA\tFJDAAA\tVVVVxx\n6941\t2268\t1\t1\t1\t1\t41\t941\t941\t1941\t6941\t82\t83\tZGAAAA\tGJDAAA\tAAAAxx\n6384\t2269\t0\t0\t4\t4\t84\t384\t384\t1384\t6384\t168\t169\tOLAAAA\tHJDAAA\tHHHHxx\n3806\t2270\t0\t2\t6\t6\t6\t806\t1806\t3806\t3806\t12\t13\tKQAAAA\tIJDAAA\tOOOOxx\n5079\t2271\t1\t3\t9\t19\t79\t79\t1079\t79\t5079\t158\t159\tJNAAAA\tJJDAAA\tVVVVxx\n1970\t2272\t0\t2\t0\t10\t70\t970\t1970\t1970\t1970\t140\t141\tUXAAAA\tKJDAAA\tAAAAxx\n7810\t2273\t0\t2\t0\t10\t10\t810\t1810\t2810\t7810\t20\t21\tKOAAAA\tLJDAAA\tHHHHxx\n4639\t2274\t1\t3\t9\t19\t39\t639\t639\t4639\t4639\t78\t79\tLWAAAA\tMJDAAA\tOOOOxx\n6527\t2275\t1\t3\t7\t7\t27\t527\t527\t1527\t6527\t54\t55\tBRAAAA\tNJDAAA\tVVVVxx\n8079\t2276\t1\t3\t9\t19\t79\t79\t79\t3079\t8079\t158\t159\tTYAAAA\tOJDAAA\tAAAAxx\n2740\t2277\t0\t0\t0\t0\t40\t740\t740\t2740\t2740\t80\t81\tKBAAAA\tPJDAAA\tHHHHxx\n2337\t2278\t1\t1\t7\t17\t37\t337\t337\t2337\t2337\t74\t75\tXLAAAA\tQJDAAA\tOOOOxx\n6670\t2279\t0\t2\t0\t10\t70\t670\t670\t1670\t6670\t140\t141\tOWAAAA\tRJDAAA\tVVVVxx\n2345\t2280\t1\t1\t5\t5\t45\t345\t345\t2345\t2345\t90\t91\tFMAAAA\tSJDAAA\tAAAAxx\n401\t2281\t1\t1\t1\t1\t1\t401\t401\t401\t401\t2\t3\tLPAAAA\tTJDAAA\tHHHHxx\n2704\t2282\t0\t0\t4\t4\t4\t704\t704\t2704\t2704\t8\t9\tAAAAAA\tUJDAAA\tOOOOxx\n5530\t2283\t0\t2\t0\t10\t30\t530\t1530\t530\t5530\t60\t61\tSEAAAA\tVJDAAA\tVVVVxx\n51\t2284\t1\t3\t1\t11\t51\t51\t51\t51\t51\t102\t103\tZBAAAA\tWJDAAA\tAAAAxx\n4282\t2285\t0\t2\t2\t2\t82\t282\t282\t4282\t4282\t164\t165\tSIAAAA\tXJDAAA\tHHHHxx\n7336\t2286\t0\t0\t6\t16\t36\t336\t1336\t2336\t7336\t72\t73\tEWAAAA\tYJDAAA\tOOOOxx\n8320\t2287\t0\t0\t0\t0\t20\t320\t320\t3320\t8320\t40\t41\tAIAAAA\tZJDAAA\tVVVVxx\n7772\t2288\t0\t0\t2\t12\t72\t772\t1772\t2772\t7772\t144\t145\tYMAAAA\tAKDAAA\tAAAAxx\n1894\t2289\t0\t2\t4\t14\t94\t894\t1894\t1894\t1894\t188\t189\tWUAAAA\tBKDAAA\tHHHHxx\n2320\t2290\t0\t0\t0\t0\t20\t320\t320\t2320\t2320\t40\t41\tGLAAAA\tCKDAAA\tOOOOxx\n6232\t2291\t0\t0\t2\t12\t32\t232\t232\t1232\t6232\t64\t65\tSFAAAA\tDKDAAA\tVVVVxx\n2833\t2292\t1\t1\t3\t13\t33\t833\t833\t2833\t2833\t66\t67\tZEAAAA\tEKDAAA\tAAAAxx\n8265\t2293\t1\t1\t5\t5\t65\t265\t265\t3265\t8265\t130\t131\tXFAAAA\tFKDAAA\tHHHHxx\n4589\t2294\t1\t1\t9\t9\t89\t589\t589\t4589\t4589\t178\t179\tNUAAAA\tGKDAAA\tOOOOxx\n8182\t2295\t0\t2\t2\t2\t82\t182\t182\t3182\t8182\t164\t165\tSCAAAA\tHKDAAA\tVVVVxx\n8337\t2296\t1\t1\t7\t17\t37\t337\t337\t3337\t8337\t74\t75\tRIAAAA\tIKDAAA\tAAAAxx\n8210\t2297\t0\t2\t0\t10\t10\t210\t210\t3210\t8210\t20\t21\tUDAAAA\tJKDAAA\tHHHHxx\n1406\t2298\t0\t2\t6\t6\t6\t406\t1406\t1406\t1406\t12\t13\tCCAAAA\tKKDAAA\tOOOOxx\n4463\t2299\t1\t3\t3\t3\t63\t463\t463\t4463\t4463\t126\t127\tRPAAAA\tLKDAAA\tVVVVxx\n4347\t2300\t1\t3\t7\t7\t47\t347\t347\t4347\t4347\t94\t95\tFLAAAA\tMKDAAA\tAAAAxx\n181\t2301\t1\t1\t1\t1\t81\t181\t181\t181\t181\t162\t163\tZGAAAA\tNKDAAA\tHHHHxx\n9986\t2302\t0\t2\t6\t6\t86\t986\t1986\t4986\t9986\t172\t173\tCUAAAA\tOKDAAA\tOOOOxx\n661\t2303\t1\t1\t1\t1\t61\t661\t661\t661\t661\t122\t123\tLZAAAA\tPKDAAA\tVVVVxx\n4105\t2304\t1\t1\t5\t5\t5\t105\t105\t4105\t4105\t10\t11\tXBAAAA\tQKDAAA\tAAAAxx\n2187\t2305\t1\t3\t7\t7\t87\t187\t187\t2187\t2187\t174\t175\tDGAAAA\tRKDAAA\tHHHHxx\n1628\t2306\t0\t0\t8\t8\t28\t628\t1628\t1628\t1628\t56\t57\tQKAAAA\tSKDAAA\tOOOOxx\n3119\t2307\t1\t3\t9\t19\t19\t119\t1119\t3119\t3119\t38\t39\tZPAAAA\tTKDAAA\tVVVVxx\n6804\t2308\t0\t0\t4\t4\t4\t804\t804\t1804\t6804\t8\t9\tSBAAAA\tUKDAAA\tAAAAxx\n9918\t2309\t0\t2\t8\t18\t18\t918\t1918\t4918\t9918\t36\t37\tMRAAAA\tVKDAAA\tHHHHxx\n8916\t2310\t0\t0\t6\t16\t16\t916\t916\t3916\t8916\t32\t33\tYEAAAA\tWKDAAA\tOOOOxx\n6057\t2311\t1\t1\t7\t17\t57\t57\t57\t1057\t6057\t114\t115\tZYAAAA\tXKDAAA\tVVVVxx\n3622\t2312\t0\t2\t2\t2\t22\t622\t1622\t3622\t3622\t44\t45\tIJAAAA\tYKDAAA\tAAAAxx\n9168\t2313\t0\t0\t8\t8\t68\t168\t1168\t4168\t9168\t136\t137\tQOAAAA\tZKDAAA\tHHHHxx\n3720\t2314\t0\t0\t0\t0\t20\t720\t1720\t3720\t3720\t40\t41\tCNAAAA\tALDAAA\tOOOOxx\n9927\t2315\t1\t3\t7\t7\t27\t927\t1927\t4927\t9927\t54\t55\tVRAAAA\tBLDAAA\tVVVVxx\n5616\t2316\t0\t0\t6\t16\t16\t616\t1616\t616\t5616\t32\t33\tAIAAAA\tCLDAAA\tAAAAxx\n5210\t2317\t0\t2\t0\t10\t10\t210\t1210\t210\t5210\t20\t21\tKSAAAA\tDLDAAA\tHHHHxx\n636\t2318\t0\t0\t6\t16\t36\t636\t636\t636\t636\t72\t73\tMYAAAA\tELDAAA\tOOOOxx\n9936\t2319\t0\t0\t6\t16\t36\t936\t1936\t4936\t9936\t72\t73\tESAAAA\tFLDAAA\tVVVVxx\n2316\t2320\t0\t0\t6\t16\t16\t316\t316\t2316\t2316\t32\t33\tCLAAAA\tGLDAAA\tAAAAxx\n4363\t2321\t1\t3\t3\t3\t63\t363\t363\t4363\t4363\t126\t127\tVLAAAA\tHLDAAA\tHHHHxx\n7657\t2322\t1\t1\t7\t17\t57\t657\t1657\t2657\t7657\t114\t115\tNIAAAA\tILDAAA\tOOOOxx\n697\t2323\t1\t1\t7\t17\t97\t697\t697\t697\t697\t194\t195\tVAAAAA\tJLDAAA\tVVVVxx\n912\t2324\t0\t0\t2\t12\t12\t912\t912\t912\t912\t24\t25\tCJAAAA\tKLDAAA\tAAAAxx\n8806\t2325\t0\t2\t6\t6\t6\t806\t806\t3806\t8806\t12\t13\tSAAAAA\tLLDAAA\tHHHHxx\n9698\t2326\t0\t2\t8\t18\t98\t698\t1698\t4698\t9698\t196\t197\tAJAAAA\tMLDAAA\tOOOOxx\n6191\t2327\t1\t3\t1\t11\t91\t191\t191\t1191\t6191\t182\t183\tDEAAAA\tNLDAAA\tVVVVxx\n1188\t2328\t0\t0\t8\t8\t88\t188\t1188\t1188\t1188\t176\t177\tSTAAAA\tOLDAAA\tAAAAxx\n7676\t2329\t0\t0\t6\t16\t76\t676\t1676\t2676\t7676\t152\t153\tGJAAAA\tPLDAAA\tHHHHxx\n7073\t2330\t1\t1\t3\t13\t73\t73\t1073\t2073\t7073\t146\t147\tBMAAAA\tQLDAAA\tOOOOxx\n8019\t2331\t1\t3\t9\t19\t19\t19\t19\t3019\t8019\t38\t39\tLWAAAA\tRLDAAA\tVVVVxx\n4726\t2332\t0\t2\t6\t6\t26\t726\t726\t4726\t4726\t52\t53\tUZAAAA\tSLDAAA\tAAAAxx\n4648\t2333\t0\t0\t8\t8\t48\t648\t648\t4648\t4648\t96\t97\tUWAAAA\tTLDAAA\tHHHHxx\n3227\t2334\t1\t3\t7\t7\t27\t227\t1227\t3227\t3227\t54\t55\tDUAAAA\tULDAAA\tOOOOxx\n7232\t2335\t0\t0\t2\t12\t32\t232\t1232\t2232\t7232\t64\t65\tESAAAA\tVLDAAA\tVVVVxx\n9761\t2336\t1\t1\t1\t1\t61\t761\t1761\t4761\t9761\t122\t123\tLLAAAA\tWLDAAA\tAAAAxx\n3105\t2337\t1\t1\t5\t5\t5\t105\t1105\t3105\t3105\t10\t11\tLPAAAA\tXLDAAA\tHHHHxx\n5266\t2338\t0\t2\t6\t6\t66\t266\t1266\t266\t5266\t132\t133\tOUAAAA\tYLDAAA\tOOOOxx\n6788\t2339\t0\t0\t8\t8\t88\t788\t788\t1788\t6788\t176\t177\tCBAAAA\tZLDAAA\tVVVVxx\n2442\t2340\t0\t2\t2\t2\t42\t442\t442\t2442\t2442\t84\t85\tYPAAAA\tAMDAAA\tAAAAxx\n8198\t2341\t0\t2\t8\t18\t98\t198\t198\t3198\t8198\t196\t197\tIDAAAA\tBMDAAA\tHHHHxx\n5806\t2342\t0\t2\t6\t6\t6\t806\t1806\t806\t5806\t12\t13\tIPAAAA\tCMDAAA\tOOOOxx\n8928\t2343\t0\t0\t8\t8\t28\t928\t928\t3928\t8928\t56\t57\tKFAAAA\tDMDAAA\tVVVVxx\n1657\t2344\t1\t1\t7\t17\t57\t657\t1657\t1657\t1657\t114\t115\tTLAAAA\tEMDAAA\tAAAAxx\n9164\t2345\t0\t0\t4\t4\t64\t164\t1164\t4164\t9164\t128\t129\tMOAAAA\tFMDAAA\tHHHHxx\n1851\t2346\t1\t3\t1\t11\t51\t851\t1851\t1851\t1851\t102\t103\tFTAAAA\tGMDAAA\tOOOOxx\n4744\t2347\t0\t0\t4\t4\t44\t744\t744\t4744\t4744\t88\t89\tMAAAAA\tHMDAAA\tVVVVxx\n8055\t2348\t1\t3\t5\t15\t55\t55\t55\t3055\t8055\t110\t111\tVXAAAA\tIMDAAA\tAAAAxx\n1533\t2349\t1\t1\t3\t13\t33\t533\t1533\t1533\t1533\t66\t67\tZGAAAA\tJMDAAA\tHHHHxx\n1260\t2350\t0\t0\t0\t0\t60\t260\t1260\t1260\t1260\t120\t121\tMWAAAA\tKMDAAA\tOOOOxx\n1290\t2351\t0\t2\t0\t10\t90\t290\t1290\t1290\t1290\t180\t181\tQXAAAA\tLMDAAA\tVVVVxx\n297\t2352\t1\t1\t7\t17\t97\t297\t297\t297\t297\t194\t195\tLLAAAA\tMMDAAA\tAAAAxx\n4145\t2353\t1\t1\t5\t5\t45\t145\t145\t4145\t4145\t90\t91\tLDAAAA\tNMDAAA\tHHHHxx\n863\t2354\t1\t3\t3\t3\t63\t863\t863\t863\t863\t126\t127\tFHAAAA\tOMDAAA\tOOOOxx\n3423\t2355\t1\t3\t3\t3\t23\t423\t1423\t3423\t3423\t46\t47\tRBAAAA\tPMDAAA\tVVVVxx\n8750\t2356\t0\t2\t0\t10\t50\t750\t750\t3750\t8750\t100\t101\tOYAAAA\tQMDAAA\tAAAAxx\n3546\t2357\t0\t2\t6\t6\t46\t546\t1546\t3546\t3546\t92\t93\tKGAAAA\tRMDAAA\tHHHHxx\n3678\t2358\t0\t2\t8\t18\t78\t678\t1678\t3678\t3678\t156\t157\tMLAAAA\tSMDAAA\tOOOOxx\n5313\t2359\t1\t1\t3\t13\t13\t313\t1313\t313\t5313\t26\t27\tJWAAAA\tTMDAAA\tVVVVxx\n6233\t2360\t1\t1\t3\t13\t33\t233\t233\t1233\t6233\t66\t67\tTFAAAA\tUMDAAA\tAAAAxx\n5802\t2361\t0\t2\t2\t2\t2\t802\t1802\t802\t5802\t4\t5\tEPAAAA\tVMDAAA\tHHHHxx\n7059\t2362\t1\t3\t9\t19\t59\t59\t1059\t2059\t7059\t118\t119\tNLAAAA\tWMDAAA\tOOOOxx\n6481\t2363\t1\t1\t1\t1\t81\t481\t481\t1481\t6481\t162\t163\tHPAAAA\tXMDAAA\tVVVVxx\n1596\t2364\t0\t0\t6\t16\t96\t596\t1596\t1596\t1596\t192\t193\tKJAAAA\tYMDAAA\tAAAAxx\n8181\t2365\t1\t1\t1\t1\t81\t181\t181\t3181\t8181\t162\t163\tRCAAAA\tZMDAAA\tHHHHxx\n5368\t2366\t0\t0\t8\t8\t68\t368\t1368\t368\t5368\t136\t137\tMYAAAA\tANDAAA\tOOOOxx\n9416\t2367\t0\t0\t6\t16\t16\t416\t1416\t4416\t9416\t32\t33\tEYAAAA\tBNDAAA\tVVVVxx\n9521\t2368\t1\t1\t1\t1\t21\t521\t1521\t4521\t9521\t42\t43\tFCAAAA\tCNDAAA\tAAAAxx\n1042\t2369\t0\t2\t2\t2\t42\t42\t1042\t1042\t1042\t84\t85\tCOAAAA\tDNDAAA\tHHHHxx\n4503\t2370\t1\t3\t3\t3\t3\t503\t503\t4503\t4503\t6\t7\tFRAAAA\tENDAAA\tOOOOxx\n3023\t2371\t1\t3\t3\t3\t23\t23\t1023\t3023\t3023\t46\t47\tHMAAAA\tFNDAAA\tVVVVxx\n1976\t2372\t0\t0\t6\t16\t76\t976\t1976\t1976\t1976\t152\t153\tAYAAAA\tGNDAAA\tAAAAxx\n5610\t2373\t0\t2\t0\t10\t10\t610\t1610\t610\t5610\t20\t21\tUHAAAA\tHNDAAA\tHHHHxx\n7410\t2374\t0\t2\t0\t10\t10\t410\t1410\t2410\t7410\t20\t21\tAZAAAA\tINDAAA\tOOOOxx\n7872\t2375\t0\t0\t2\t12\t72\t872\t1872\t2872\t7872\t144\t145\tUQAAAA\tJNDAAA\tVVVVxx\n8591\t2376\t1\t3\t1\t11\t91\t591\t591\t3591\t8591\t182\t183\tLSAAAA\tKNDAAA\tAAAAxx\n1804\t2377\t0\t0\t4\t4\t4\t804\t1804\t1804\t1804\t8\t9\tKRAAAA\tLNDAAA\tHHHHxx\n5299\t2378\t1\t3\t9\t19\t99\t299\t1299\t299\t5299\t198\t199\tVVAAAA\tMNDAAA\tOOOOxx\n4695\t2379\t1\t3\t5\t15\t95\t695\t695\t4695\t4695\t190\t191\tPYAAAA\tNNDAAA\tVVVVxx\n2672\t2380\t0\t0\t2\t12\t72\t672\t672\t2672\t2672\t144\t145\tUYAAAA\tONDAAA\tAAAAxx\n585\t2381\t1\t1\t5\t5\t85\t585\t585\t585\t585\t170\t171\tNWAAAA\tPNDAAA\tHHHHxx\n8622\t2382\t0\t2\t2\t2\t22\t622\t622\t3622\t8622\t44\t45\tQTAAAA\tQNDAAA\tOOOOxx\n3780\t2383\t0\t0\t0\t0\t80\t780\t1780\t3780\t3780\t160\t161\tKPAAAA\tRNDAAA\tVVVVxx\n7941\t2384\t1\t1\t1\t1\t41\t941\t1941\t2941\t7941\t82\t83\tLTAAAA\tSNDAAA\tAAAAxx\n3305\t2385\t1\t1\t5\t5\t5\t305\t1305\t3305\t3305\t10\t11\tDXAAAA\tTNDAAA\tHHHHxx\n8653\t2386\t1\t1\t3\t13\t53\t653\t653\t3653\t8653\t106\t107\tVUAAAA\tUNDAAA\tOOOOxx\n5756\t2387\t0\t0\t6\t16\t56\t756\t1756\t756\t5756\t112\t113\tKNAAAA\tVNDAAA\tVVVVxx\n576\t2388\t0\t0\t6\t16\t76\t576\t576\t576\t576\t152\t153\tEWAAAA\tWNDAAA\tAAAAxx\n1915\t2389\t1\t3\t5\t15\t15\t915\t1915\t1915\t1915\t30\t31\tRVAAAA\tXNDAAA\tHHHHxx\n4627\t2390\t1\t3\t7\t7\t27\t627\t627\t4627\t4627\t54\t55\tZVAAAA\tYNDAAA\tOOOOxx\n920\t2391\t0\t0\t0\t0\t20\t920\t920\t920\t920\t40\t41\tKJAAAA\tZNDAAA\tVVVVxx\n2537\t2392\t1\t1\t7\t17\t37\t537\t537\t2537\t2537\t74\t75\tPTAAAA\tAODAAA\tAAAAxx\n50\t2393\t0\t2\t0\t10\t50\t50\t50\t50\t50\t100\t101\tYBAAAA\tBODAAA\tHHHHxx\n1313\t2394\t1\t1\t3\t13\t13\t313\t1313\t1313\t1313\t26\t27\tNYAAAA\tCODAAA\tOOOOxx\n8542\t2395\t0\t2\t2\t2\t42\t542\t542\t3542\t8542\t84\t85\tOQAAAA\tDODAAA\tVVVVxx\n6428\t2396\t0\t0\t8\t8\t28\t428\t428\t1428\t6428\t56\t57\tGNAAAA\tEODAAA\tAAAAxx\n4351\t2397\t1\t3\t1\t11\t51\t351\t351\t4351\t4351\t102\t103\tJLAAAA\tFODAAA\tHHHHxx\n2050\t2398\t0\t2\t0\t10\t50\t50\t50\t2050\t2050\t100\t101\tWAAAAA\tGODAAA\tOOOOxx\n5162\t2399\t0\t2\t2\t2\t62\t162\t1162\t162\t5162\t124\t125\tOQAAAA\tHODAAA\tVVVVxx\n8229\t2400\t1\t1\t9\t9\t29\t229\t229\t3229\t8229\t58\t59\tNEAAAA\tIODAAA\tAAAAxx\n7782\t2401\t0\t2\t2\t2\t82\t782\t1782\t2782\t7782\t164\t165\tINAAAA\tJODAAA\tHHHHxx\n1563\t2402\t1\t3\t3\t3\t63\t563\t1563\t1563\t1563\t126\t127\tDIAAAA\tKODAAA\tOOOOxx\n267\t2403\t1\t3\t7\t7\t67\t267\t267\t267\t267\t134\t135\tHKAAAA\tLODAAA\tVVVVxx\n5138\t2404\t0\t2\t8\t18\t38\t138\t1138\t138\t5138\t76\t77\tQPAAAA\tMODAAA\tAAAAxx\n7022\t2405\t0\t2\t2\t2\t22\t22\t1022\t2022\t7022\t44\t45\tCKAAAA\tNODAAA\tHHHHxx\n6705\t2406\t1\t1\t5\t5\t5\t705\t705\t1705\t6705\t10\t11\tXXAAAA\tOODAAA\tOOOOxx\n6190\t2407\t0\t2\t0\t10\t90\t190\t190\t1190\t6190\t180\t181\tCEAAAA\tPODAAA\tVVVVxx\n8226\t2408\t0\t2\t6\t6\t26\t226\t226\t3226\t8226\t52\t53\tKEAAAA\tQODAAA\tAAAAxx\n8882\t2409\t0\t2\t2\t2\t82\t882\t882\t3882\t8882\t164\t165\tQDAAAA\tRODAAA\tHHHHxx\n5181\t2410\t1\t1\t1\t1\t81\t181\t1181\t181\t5181\t162\t163\tHRAAAA\tSODAAA\tOOOOxx\n4598\t2411\t0\t2\t8\t18\t98\t598\t598\t4598\t4598\t196\t197\tWUAAAA\tTODAAA\tVVVVxx\n4882\t2412\t0\t2\t2\t2\t82\t882\t882\t4882\t4882\t164\t165\tUFAAAA\tUODAAA\tAAAAxx\n7490\t2413\t0\t2\t0\t10\t90\t490\t1490\t2490\t7490\t180\t181\tCCAAAA\tVODAAA\tHHHHxx\n5224\t2414\t0\t0\t4\t4\t24\t224\t1224\t224\t5224\t48\t49\tYSAAAA\tWODAAA\tOOOOxx\n2174\t2415\t0\t2\t4\t14\t74\t174\t174\t2174\t2174\t148\t149\tQFAAAA\tXODAAA\tVVVVxx\n3059\t2416\t1\t3\t9\t19\t59\t59\t1059\t3059\t3059\t118\t119\tRNAAAA\tYODAAA\tAAAAxx\n8790\t2417\t0\t2\t0\t10\t90\t790\t790\t3790\t8790\t180\t181\tCAAAAA\tZODAAA\tHHHHxx\n2222\t2418\t0\t2\t2\t2\t22\t222\t222\t2222\t2222\t44\t45\tMHAAAA\tAPDAAA\tOOOOxx\n5473\t2419\t1\t1\t3\t13\t73\t473\t1473\t473\t5473\t146\t147\tNCAAAA\tBPDAAA\tVVVVxx\n937\t2420\t1\t1\t7\t17\t37\t937\t937\t937\t937\t74\t75\tBKAAAA\tCPDAAA\tAAAAxx\n2975\t2421\t1\t3\t5\t15\t75\t975\t975\t2975\t2975\t150\t151\tLKAAAA\tDPDAAA\tHHHHxx\n9569\t2422\t1\t1\t9\t9\t69\t569\t1569\t4569\t9569\t138\t139\tBEAAAA\tEPDAAA\tOOOOxx\n3456\t2423\t0\t0\t6\t16\t56\t456\t1456\t3456\t3456\t112\t113\tYCAAAA\tFPDAAA\tVVVVxx\n6657\t2424\t1\t1\t7\t17\t57\t657\t657\t1657\t6657\t114\t115\tBWAAAA\tGPDAAA\tAAAAxx\n3776\t2425\t0\t0\t6\t16\t76\t776\t1776\t3776\t3776\t152\t153\tGPAAAA\tHPDAAA\tHHHHxx\n6072\t2426\t0\t0\t2\t12\t72\t72\t72\t1072\t6072\t144\t145\tOZAAAA\tIPDAAA\tOOOOxx\n8129\t2427\t1\t1\t9\t9\t29\t129\t129\t3129\t8129\t58\t59\tRAAAAA\tJPDAAA\tVVVVxx\n1085\t2428\t1\t1\t5\t5\t85\t85\t1085\t1085\t1085\t170\t171\tTPAAAA\tKPDAAA\tAAAAxx\n2079\t2429\t1\t3\t9\t19\t79\t79\t79\t2079\t2079\t158\t159\tZBAAAA\tLPDAAA\tHHHHxx\n1200\t2430\t0\t0\t0\t0\t0\t200\t1200\t1200\t1200\t0\t1\tEUAAAA\tMPDAAA\tOOOOxx\n3276\t2431\t0\t0\t6\t16\t76\t276\t1276\t3276\t3276\t152\t153\tAWAAAA\tNPDAAA\tVVVVxx\n2608\t2432\t0\t0\t8\t8\t8\t608\t608\t2608\t2608\t16\t17\tIWAAAA\tOPDAAA\tAAAAxx\n702\t2433\t0\t2\t2\t2\t2\t702\t702\t702\t702\t4\t5\tABAAAA\tPPDAAA\tHHHHxx\n5750\t2434\t0\t2\t0\t10\t50\t750\t1750\t750\t5750\t100\t101\tENAAAA\tQPDAAA\tOOOOxx\n2776\t2435\t0\t0\t6\t16\t76\t776\t776\t2776\t2776\t152\t153\tUCAAAA\tRPDAAA\tVVVVxx\n9151\t2436\t1\t3\t1\t11\t51\t151\t1151\t4151\t9151\t102\t103\tZNAAAA\tSPDAAA\tAAAAxx\n3282\t2437\t0\t2\t2\t2\t82\t282\t1282\t3282\t3282\t164\t165\tGWAAAA\tTPDAAA\tHHHHxx\n408\t2438\t0\t0\t8\t8\t8\t408\t408\t408\t408\t16\t17\tSPAAAA\tUPDAAA\tOOOOxx\n3473\t2439\t1\t1\t3\t13\t73\t473\t1473\t3473\t3473\t146\t147\tPDAAAA\tVPDAAA\tVVVVxx\n7095\t2440\t1\t3\t5\t15\t95\t95\t1095\t2095\t7095\t190\t191\tXMAAAA\tWPDAAA\tAAAAxx\n3288\t2441\t0\t0\t8\t8\t88\t288\t1288\t3288\t3288\t176\t177\tMWAAAA\tXPDAAA\tHHHHxx\n8215\t2442\t1\t3\t5\t15\t15\t215\t215\t3215\t8215\t30\t31\tZDAAAA\tYPDAAA\tOOOOxx\n6244\t2443\t0\t0\t4\t4\t44\t244\t244\t1244\t6244\t88\t89\tEGAAAA\tZPDAAA\tVVVVxx\n8440\t2444\t0\t0\t0\t0\t40\t440\t440\t3440\t8440\t80\t81\tQMAAAA\tAQDAAA\tAAAAxx\n3800\t2445\t0\t0\t0\t0\t0\t800\t1800\t3800\t3800\t0\t1\tEQAAAA\tBQDAAA\tHHHHxx\n7279\t2446\t1\t3\t9\t19\t79\t279\t1279\t2279\t7279\t158\t159\tZTAAAA\tCQDAAA\tOOOOxx\n9206\t2447\t0\t2\t6\t6\t6\t206\t1206\t4206\t9206\t12\t13\tCQAAAA\tDQDAAA\tVVVVxx\n6465\t2448\t1\t1\t5\t5\t65\t465\t465\t1465\t6465\t130\t131\tROAAAA\tEQDAAA\tAAAAxx\n4127\t2449\t1\t3\t7\t7\t27\t127\t127\t4127\t4127\t54\t55\tTCAAAA\tFQDAAA\tHHHHxx\n7463\t2450\t1\t3\t3\t3\t63\t463\t1463\t2463\t7463\t126\t127\tBBAAAA\tGQDAAA\tOOOOxx\n5117\t2451\t1\t1\t7\t17\t17\t117\t1117\t117\t5117\t34\t35\tVOAAAA\tHQDAAA\tVVVVxx\n4715\t2452\t1\t3\t5\t15\t15\t715\t715\t4715\t4715\t30\t31\tJZAAAA\tIQDAAA\tAAAAxx\n2010\t2453\t0\t2\t0\t10\t10\t10\t10\t2010\t2010\t20\t21\tIZAAAA\tJQDAAA\tHHHHxx\n6486\t2454\t0\t2\t6\t6\t86\t486\t486\t1486\t6486\t172\t173\tMPAAAA\tKQDAAA\tOOOOxx\n6434\t2455\t0\t2\t4\t14\t34\t434\t434\t1434\t6434\t68\t69\tMNAAAA\tLQDAAA\tVVVVxx\n2151\t2456\t1\t3\t1\t11\t51\t151\t151\t2151\t2151\t102\t103\tTEAAAA\tMQDAAA\tAAAAxx\n4821\t2457\t1\t1\t1\t1\t21\t821\t821\t4821\t4821\t42\t43\tLDAAAA\tNQDAAA\tHHHHxx\n6507\t2458\t1\t3\t7\t7\t7\t507\t507\t1507\t6507\t14\t15\tHQAAAA\tOQDAAA\tOOOOxx\n8741\t2459\t1\t1\t1\t1\t41\t741\t741\t3741\t8741\t82\t83\tFYAAAA\tPQDAAA\tVVVVxx\n6846\t2460\t0\t2\t6\t6\t46\t846\t846\t1846\t6846\t92\t93\tIDAAAA\tQQDAAA\tAAAAxx\n4525\t2461\t1\t1\t5\t5\t25\t525\t525\t4525\t4525\t50\t51\tBSAAAA\tRQDAAA\tHHHHxx\n8299\t2462\t1\t3\t9\t19\t99\t299\t299\t3299\t8299\t198\t199\tFHAAAA\tSQDAAA\tOOOOxx\n5465\t2463\t1\t1\t5\t5\t65\t465\t1465\t465\t5465\t130\t131\tFCAAAA\tTQDAAA\tVVVVxx\n7206\t2464\t0\t2\t6\t6\t6\t206\t1206\t2206\t7206\t12\t13\tERAAAA\tUQDAAA\tAAAAxx\n2616\t2465\t0\t0\t6\t16\t16\t616\t616\t2616\t2616\t32\t33\tQWAAAA\tVQDAAA\tHHHHxx\n4440\t2466\t0\t0\t0\t0\t40\t440\t440\t4440\t4440\t80\t81\tUOAAAA\tWQDAAA\tOOOOxx\n6109\t2467\t1\t1\t9\t9\t9\t109\t109\t1109\t6109\t18\t19\tZAAAAA\tXQDAAA\tVVVVxx\n7905\t2468\t1\t1\t5\t5\t5\t905\t1905\t2905\t7905\t10\t11\tBSAAAA\tYQDAAA\tAAAAxx\n6498\t2469\t0\t2\t8\t18\t98\t498\t498\t1498\t6498\t196\t197\tYPAAAA\tZQDAAA\tHHHHxx\n2034\t2470\t0\t2\t4\t14\t34\t34\t34\t2034\t2034\t68\t69\tGAAAAA\tARDAAA\tOOOOxx\n7693\t2471\t1\t1\t3\t13\t93\t693\t1693\t2693\t7693\t186\t187\tXJAAAA\tBRDAAA\tVVVVxx\n7511\t2472\t1\t3\t1\t11\t11\t511\t1511\t2511\t7511\t22\t23\tXCAAAA\tCRDAAA\tAAAAxx\n7531\t2473\t1\t3\t1\t11\t31\t531\t1531\t2531\t7531\t62\t63\tRDAAAA\tDRDAAA\tHHHHxx\n6869\t2474\t1\t1\t9\t9\t69\t869\t869\t1869\t6869\t138\t139\tFEAAAA\tERDAAA\tOOOOxx\n2763\t2475\t1\t3\t3\t3\t63\t763\t763\t2763\t2763\t126\t127\tHCAAAA\tFRDAAA\tVVVVxx\n575\t2476\t1\t3\t5\t15\t75\t575\t575\t575\t575\t150\t151\tDWAAAA\tGRDAAA\tAAAAxx\n8953\t2477\t1\t1\t3\t13\t53\t953\t953\t3953\t8953\t106\t107\tJGAAAA\tHRDAAA\tHHHHxx\n5833\t2478\t1\t1\t3\t13\t33\t833\t1833\t833\t5833\t66\t67\tJQAAAA\tIRDAAA\tOOOOxx\n9035\t2479\t1\t3\t5\t15\t35\t35\t1035\t4035\t9035\t70\t71\tNJAAAA\tJRDAAA\tVVVVxx\n9123\t2480\t1\t3\t3\t3\t23\t123\t1123\t4123\t9123\t46\t47\tXMAAAA\tKRDAAA\tAAAAxx\n206\t2481\t0\t2\t6\t6\t6\t206\t206\t206\t206\t12\t13\tYHAAAA\tLRDAAA\tHHHHxx\n4155\t2482\t1\t3\t5\t15\t55\t155\t155\t4155\t4155\t110\t111\tVDAAAA\tMRDAAA\tOOOOxx\n532\t2483\t0\t0\t2\t12\t32\t532\t532\t532\t532\t64\t65\tMUAAAA\tNRDAAA\tVVVVxx\n1370\t2484\t0\t2\t0\t10\t70\t370\t1370\t1370\t1370\t140\t141\tSAAAAA\tORDAAA\tAAAAxx\n7656\t2485\t0\t0\t6\t16\t56\t656\t1656\t2656\t7656\t112\t113\tMIAAAA\tPRDAAA\tHHHHxx\n7735\t2486\t1\t3\t5\t15\t35\t735\t1735\t2735\t7735\t70\t71\tNLAAAA\tQRDAAA\tOOOOxx\n2118\t2487\t0\t2\t8\t18\t18\t118\t118\t2118\t2118\t36\t37\tMDAAAA\tRRDAAA\tVVVVxx\n6914\t2488\t0\t2\t4\t14\t14\t914\t914\t1914\t6914\t28\t29\tYFAAAA\tSRDAAA\tAAAAxx\n6277\t2489\t1\t1\t7\t17\t77\t277\t277\t1277\t6277\t154\t155\tLHAAAA\tTRDAAA\tHHHHxx\n6347\t2490\t1\t3\t7\t7\t47\t347\t347\t1347\t6347\t94\t95\tDKAAAA\tURDAAA\tOOOOxx\n4030\t2491\t0\t2\t0\t10\t30\t30\t30\t4030\t4030\t60\t61\tAZAAAA\tVRDAAA\tVVVVxx\n9673\t2492\t1\t1\t3\t13\t73\t673\t1673\t4673\t9673\t146\t147\tBIAAAA\tWRDAAA\tAAAAxx\n2015\t2493\t1\t3\t5\t15\t15\t15\t15\t2015\t2015\t30\t31\tNZAAAA\tXRDAAA\tHHHHxx\n1317\t2494\t1\t1\t7\t17\t17\t317\t1317\t1317\t1317\t34\t35\tRYAAAA\tYRDAAA\tOOOOxx\n404\t2495\t0\t0\t4\t4\t4\t404\t404\t404\t404\t8\t9\tOPAAAA\tZRDAAA\tVVVVxx\n1604\t2496\t0\t0\t4\t4\t4\t604\t1604\t1604\t1604\t8\t9\tSJAAAA\tASDAAA\tAAAAxx\n1912\t2497\t0\t0\t2\t12\t12\t912\t1912\t1912\t1912\t24\t25\tOVAAAA\tBSDAAA\tHHHHxx\n5727\t2498\t1\t3\t7\t7\t27\t727\t1727\t727\t5727\t54\t55\tHMAAAA\tCSDAAA\tOOOOxx\n4538\t2499\t0\t2\t8\t18\t38\t538\t538\t4538\t4538\t76\t77\tOSAAAA\tDSDAAA\tVVVVxx\n6868\t2500\t0\t0\t8\t8\t68\t868\t868\t1868\t6868\t136\t137\tEEAAAA\tESDAAA\tAAAAxx\n9801\t2501\t1\t1\t1\t1\t1\t801\t1801\t4801\t9801\t2\t3\tZMAAAA\tFSDAAA\tHHHHxx\n1781\t2502\t1\t1\t1\t1\t81\t781\t1781\t1781\t1781\t162\t163\tNQAAAA\tGSDAAA\tOOOOxx\n7061\t2503\t1\t1\t1\t1\t61\t61\t1061\t2061\t7061\t122\t123\tPLAAAA\tHSDAAA\tVVVVxx\n2412\t2504\t0\t0\t2\t12\t12\t412\t412\t2412\t2412\t24\t25\tUOAAAA\tISDAAA\tAAAAxx\n9191\t2505\t1\t3\t1\t11\t91\t191\t1191\t4191\t9191\t182\t183\tNPAAAA\tJSDAAA\tHHHHxx\n1958\t2506\t0\t2\t8\t18\t58\t958\t1958\t1958\t1958\t116\t117\tIXAAAA\tKSDAAA\tOOOOxx\n2203\t2507\t1\t3\t3\t3\t3\t203\t203\t2203\t2203\t6\t7\tTGAAAA\tLSDAAA\tVVVVxx\n9104\t2508\t0\t0\t4\t4\t4\t104\t1104\t4104\t9104\t8\t9\tEMAAAA\tMSDAAA\tAAAAxx\n3837\t2509\t1\t1\t7\t17\t37\t837\t1837\t3837\t3837\t74\t75\tPRAAAA\tNSDAAA\tHHHHxx\n7055\t2510\t1\t3\t5\t15\t55\t55\t1055\t2055\t7055\t110\t111\tJLAAAA\tOSDAAA\tOOOOxx\n4612\t2511\t0\t0\t2\t12\t12\t612\t612\t4612\t4612\t24\t25\tKVAAAA\tPSDAAA\tVVVVxx\n6420\t2512\t0\t0\t0\t0\t20\t420\t420\t1420\t6420\t40\t41\tYMAAAA\tQSDAAA\tAAAAxx\n613\t2513\t1\t1\t3\t13\t13\t613\t613\t613\t613\t26\t27\tPXAAAA\tRSDAAA\tHHHHxx\n1691\t2514\t1\t3\t1\t11\t91\t691\t1691\t1691\t1691\t182\t183\tBNAAAA\tSSDAAA\tOOOOxx\n33\t2515\t1\t1\t3\t13\t33\t33\t33\t33\t33\t66\t67\tHBAAAA\tTSDAAA\tVVVVxx\n875\t2516\t1\t3\t5\t15\t75\t875\t875\t875\t875\t150\t151\tRHAAAA\tUSDAAA\tAAAAxx\n9030\t2517\t0\t2\t0\t10\t30\t30\t1030\t4030\t9030\t60\t61\tIJAAAA\tVSDAAA\tHHHHxx\n4285\t2518\t1\t1\t5\t5\t85\t285\t285\t4285\t4285\t170\t171\tVIAAAA\tWSDAAA\tOOOOxx\n6236\t2519\t0\t0\t6\t16\t36\t236\t236\t1236\t6236\t72\t73\tWFAAAA\tXSDAAA\tVVVVxx\n4702\t2520\t0\t2\t2\t2\t2\t702\t702\t4702\t4702\t4\t5\tWYAAAA\tYSDAAA\tAAAAxx\n3441\t2521\t1\t1\t1\t1\t41\t441\t1441\t3441\t3441\t82\t83\tJCAAAA\tZSDAAA\tHHHHxx\n2150\t2522\t0\t2\t0\t10\t50\t150\t150\t2150\t2150\t100\t101\tSEAAAA\tATDAAA\tOOOOxx\n1852\t2523\t0\t0\t2\t12\t52\t852\t1852\t1852\t1852\t104\t105\tGTAAAA\tBTDAAA\tVVVVxx\n7713\t2524\t1\t1\t3\t13\t13\t713\t1713\t2713\t7713\t26\t27\tRKAAAA\tCTDAAA\tAAAAxx\n6849\t2525\t1\t1\t9\t9\t49\t849\t849\t1849\t6849\t98\t99\tLDAAAA\tDTDAAA\tHHHHxx\n3425\t2526\t1\t1\t5\t5\t25\t425\t1425\t3425\t3425\t50\t51\tTBAAAA\tETDAAA\tOOOOxx\n4681\t2527\t1\t1\t1\t1\t81\t681\t681\t4681\t4681\t162\t163\tBYAAAA\tFTDAAA\tVVVVxx\n1134\t2528\t0\t2\t4\t14\t34\t134\t1134\t1134\t1134\t68\t69\tQRAAAA\tGTDAAA\tAAAAxx\n7462\t2529\t0\t2\t2\t2\t62\t462\t1462\t2462\t7462\t124\t125\tABAAAA\tHTDAAA\tHHHHxx\n2148\t2530\t0\t0\t8\t8\t48\t148\t148\t2148\t2148\t96\t97\tQEAAAA\tITDAAA\tOOOOxx\n5921\t2531\t1\t1\t1\t1\t21\t921\t1921\t921\t5921\t42\t43\tTTAAAA\tJTDAAA\tVVVVxx\n118\t2532\t0\t2\t8\t18\t18\t118\t118\t118\t118\t36\t37\tOEAAAA\tKTDAAA\tAAAAxx\n3065\t2533\t1\t1\t5\t5\t65\t65\t1065\t3065\t3065\t130\t131\tXNAAAA\tLTDAAA\tHHHHxx\n6590\t2534\t0\t2\t0\t10\t90\t590\t590\t1590\t6590\t180\t181\tMTAAAA\tMTDAAA\tOOOOxx\n4993\t2535\t1\t1\t3\t13\t93\t993\t993\t4993\t4993\t186\t187\tBKAAAA\tNTDAAA\tVVVVxx\n6818\t2536\t0\t2\t8\t18\t18\t818\t818\t1818\t6818\t36\t37\tGCAAAA\tOTDAAA\tAAAAxx\n1449\t2537\t1\t1\t9\t9\t49\t449\t1449\t1449\t1449\t98\t99\tTDAAAA\tPTDAAA\tHHHHxx\n2039\t2538\t1\t3\t9\t19\t39\t39\t39\t2039\t2039\t78\t79\tLAAAAA\tQTDAAA\tOOOOxx\n2524\t2539\t0\t0\t4\t4\t24\t524\t524\t2524\t2524\t48\t49\tCTAAAA\tRTDAAA\tVVVVxx\n1481\t2540\t1\t1\t1\t1\t81\t481\t1481\t1481\t1481\t162\t163\tZEAAAA\tSTDAAA\tAAAAxx\n6984\t2541\t0\t0\t4\t4\t84\t984\t984\t1984\t6984\t168\t169\tQIAAAA\tTTDAAA\tHHHHxx\n3960\t2542\t0\t0\t0\t0\t60\t960\t1960\t3960\t3960\t120\t121\tIWAAAA\tUTDAAA\tOOOOxx\n1983\t2543\t1\t3\t3\t3\t83\t983\t1983\t1983\t1983\t166\t167\tHYAAAA\tVTDAAA\tVVVVxx\n6379\t2544\t1\t3\t9\t19\t79\t379\t379\t1379\t6379\t158\t159\tJLAAAA\tWTDAAA\tAAAAxx\n8975\t2545\t1\t3\t5\t15\t75\t975\t975\t3975\t8975\t150\t151\tFHAAAA\tXTDAAA\tHHHHxx\n1102\t2546\t0\t2\t2\t2\t2\t102\t1102\t1102\t1102\t4\t5\tKQAAAA\tYTDAAA\tOOOOxx\n2517\t2547\t1\t1\t7\t17\t17\t517\t517\t2517\t2517\t34\t35\tVSAAAA\tZTDAAA\tVVVVxx\n712\t2548\t0\t0\t2\t12\t12\t712\t712\t712\t712\t24\t25\tKBAAAA\tAUDAAA\tAAAAxx\n5419\t2549\t1\t3\t9\t19\t19\t419\t1419\t419\t5419\t38\t39\tLAAAAA\tBUDAAA\tHHHHxx\n723\t2550\t1\t3\t3\t3\t23\t723\t723\t723\t723\t46\t47\tVBAAAA\tCUDAAA\tOOOOxx\n8057\t2551\t1\t1\t7\t17\t57\t57\t57\t3057\t8057\t114\t115\tXXAAAA\tDUDAAA\tVVVVxx\n7471\t2552\t1\t3\t1\t11\t71\t471\t1471\t2471\t7471\t142\t143\tJBAAAA\tEUDAAA\tAAAAxx\n8855\t2553\t1\t3\t5\t15\t55\t855\t855\t3855\t8855\t110\t111\tPCAAAA\tFUDAAA\tHHHHxx\n5074\t2554\t0\t2\t4\t14\t74\t74\t1074\t74\t5074\t148\t149\tENAAAA\tGUDAAA\tOOOOxx\n7139\t2555\t1\t3\t9\t19\t39\t139\t1139\t2139\t7139\t78\t79\tPOAAAA\tHUDAAA\tVVVVxx\n3833\t2556\t1\t1\t3\t13\t33\t833\t1833\t3833\t3833\t66\t67\tLRAAAA\tIUDAAA\tAAAAxx\n5186\t2557\t0\t2\t6\t6\t86\t186\t1186\t186\t5186\t172\t173\tMRAAAA\tJUDAAA\tHHHHxx\n9436\t2558\t0\t0\t6\t16\t36\t436\t1436\t4436\t9436\t72\t73\tYYAAAA\tKUDAAA\tOOOOxx\n8859\t2559\t1\t3\t9\t19\t59\t859\t859\t3859\t8859\t118\t119\tTCAAAA\tLUDAAA\tVVVVxx\n6943\t2560\t1\t3\t3\t3\t43\t943\t943\t1943\t6943\t86\t87\tBHAAAA\tMUDAAA\tAAAAxx\n2315\t2561\t1\t3\t5\t15\t15\t315\t315\t2315\t2315\t30\t31\tBLAAAA\tNUDAAA\tHHHHxx\n1394\t2562\t0\t2\t4\t14\t94\t394\t1394\t1394\t1394\t188\t189\tQBAAAA\tOUDAAA\tOOOOxx\n8863\t2563\t1\t3\t3\t3\t63\t863\t863\t3863\t8863\t126\t127\tXCAAAA\tPUDAAA\tVVVVxx\n8812\t2564\t0\t0\t2\t12\t12\t812\t812\t3812\t8812\t24\t25\tYAAAAA\tQUDAAA\tAAAAxx\n7498\t2565\t0\t2\t8\t18\t98\t498\t1498\t2498\t7498\t196\t197\tKCAAAA\tRUDAAA\tHHHHxx\n8962\t2566\t0\t2\t2\t2\t62\t962\t962\t3962\t8962\t124\t125\tSGAAAA\tSUDAAA\tOOOOxx\n2533\t2567\t1\t1\t3\t13\t33\t533\t533\t2533\t2533\t66\t67\tLTAAAA\tTUDAAA\tVVVVxx\n8188\t2568\t0\t0\t8\t8\t88\t188\t188\t3188\t8188\t176\t177\tYCAAAA\tUUDAAA\tAAAAxx\n6137\t2569\t1\t1\t7\t17\t37\t137\t137\t1137\t6137\t74\t75\tBCAAAA\tVUDAAA\tHHHHxx\n974\t2570\t0\t2\t4\t14\t74\t974\t974\t974\t974\t148\t149\tMLAAAA\tWUDAAA\tOOOOxx\n2751\t2571\t1\t3\t1\t11\t51\t751\t751\t2751\t2751\t102\t103\tVBAAAA\tXUDAAA\tVVVVxx\n4975\t2572\t1\t3\t5\t15\t75\t975\t975\t4975\t4975\t150\t151\tJJAAAA\tYUDAAA\tAAAAxx\n3411\t2573\t1\t3\t1\t11\t11\t411\t1411\t3411\t3411\t22\t23\tFBAAAA\tZUDAAA\tHHHHxx\n3143\t2574\t1\t3\t3\t3\t43\t143\t1143\t3143\t3143\t86\t87\tXQAAAA\tAVDAAA\tOOOOxx\n8011\t2575\t1\t3\t1\t11\t11\t11\t11\t3011\t8011\t22\t23\tDWAAAA\tBVDAAA\tVVVVxx\n988\t2576\t0\t0\t8\t8\t88\t988\t988\t988\t988\t176\t177\tAMAAAA\tCVDAAA\tAAAAxx\n4289\t2577\t1\t1\t9\t9\t89\t289\t289\t4289\t4289\t178\t179\tZIAAAA\tDVDAAA\tHHHHxx\n8105\t2578\t1\t1\t5\t5\t5\t105\t105\t3105\t8105\t10\t11\tTZAAAA\tEVDAAA\tOOOOxx\n9885\t2579\t1\t1\t5\t5\t85\t885\t1885\t4885\t9885\t170\t171\tFQAAAA\tFVDAAA\tVVVVxx\n1002\t2580\t0\t2\t2\t2\t2\t2\t1002\t1002\t1002\t4\t5\tOMAAAA\tGVDAAA\tAAAAxx\n5827\t2581\t1\t3\t7\t7\t27\t827\t1827\t827\t5827\t54\t55\tDQAAAA\tHVDAAA\tHHHHxx\n1228\t2582\t0\t0\t8\t8\t28\t228\t1228\t1228\t1228\t56\t57\tGVAAAA\tIVDAAA\tOOOOxx\n6352\t2583\t0\t0\t2\t12\t52\t352\t352\t1352\t6352\t104\t105\tIKAAAA\tJVDAAA\tVVVVxx\n8868\t2584\t0\t0\t8\t8\t68\t868\t868\t3868\t8868\t136\t137\tCDAAAA\tKVDAAA\tAAAAxx\n3643\t2585\t1\t3\t3\t3\t43\t643\t1643\t3643\t3643\t86\t87\tDKAAAA\tLVDAAA\tHHHHxx\n1468\t2586\t0\t0\t8\t8\t68\t468\t1468\t1468\t1468\t136\t137\tMEAAAA\tMVDAAA\tOOOOxx\n8415\t2587\t1\t3\t5\t15\t15\t415\t415\t3415\t8415\t30\t31\tRLAAAA\tNVDAAA\tVVVVxx\n9631\t2588\t1\t3\t1\t11\t31\t631\t1631\t4631\t9631\t62\t63\tLGAAAA\tOVDAAA\tAAAAxx\n7408\t2589\t0\t0\t8\t8\t8\t408\t1408\t2408\t7408\t16\t17\tYYAAAA\tPVDAAA\tHHHHxx\n1934\t2590\t0\t2\t4\t14\t34\t934\t1934\t1934\t1934\t68\t69\tKWAAAA\tQVDAAA\tOOOOxx\n996\t2591\t0\t0\t6\t16\t96\t996\t996\t996\t996\t192\t193\tIMAAAA\tRVDAAA\tVVVVxx\n8027\t2592\t1\t3\t7\t7\t27\t27\t27\t3027\t8027\t54\t55\tTWAAAA\tSVDAAA\tAAAAxx\n8464\t2593\t0\t0\t4\t4\t64\t464\t464\t3464\t8464\t128\t129\tONAAAA\tTVDAAA\tHHHHxx\n5007\t2594\t1\t3\t7\t7\t7\t7\t1007\t7\t5007\t14\t15\tPKAAAA\tUVDAAA\tOOOOxx\n8356\t2595\t0\t0\t6\t16\t56\t356\t356\t3356\t8356\t112\t113\tKJAAAA\tVVDAAA\tVVVVxx\n4579\t2596\t1\t3\t9\t19\t79\t579\t579\t4579\t4579\t158\t159\tDUAAAA\tWVDAAA\tAAAAxx\n8513\t2597\t1\t1\t3\t13\t13\t513\t513\t3513\t8513\t26\t27\tLPAAAA\tXVDAAA\tHHHHxx\n383\t2598\t1\t3\t3\t3\t83\t383\t383\t383\t383\t166\t167\tTOAAAA\tYVDAAA\tOOOOxx\n9304\t2599\t0\t0\t4\t4\t4\t304\t1304\t4304\t9304\t8\t9\tWTAAAA\tZVDAAA\tVVVVxx\n7224\t2600\t0\t0\t4\t4\t24\t224\t1224\t2224\t7224\t48\t49\tWRAAAA\tAWDAAA\tAAAAxx\n6023\t2601\t1\t3\t3\t3\t23\t23\t23\t1023\t6023\t46\t47\tRXAAAA\tBWDAAA\tHHHHxx\n2746\t2602\t0\t2\t6\t6\t46\t746\t746\t2746\t2746\t92\t93\tQBAAAA\tCWDAAA\tOOOOxx\n137\t2603\t1\t1\t7\t17\t37\t137\t137\t137\t137\t74\t75\tHFAAAA\tDWDAAA\tVVVVxx\n9441\t2604\t1\t1\t1\t1\t41\t441\t1441\t4441\t9441\t82\t83\tDZAAAA\tEWDAAA\tAAAAxx\n3690\t2605\t0\t2\t0\t10\t90\t690\t1690\t3690\t3690\t180\t181\tYLAAAA\tFWDAAA\tHHHHxx\n913\t2606\t1\t1\t3\t13\t13\t913\t913\t913\t913\t26\t27\tDJAAAA\tGWDAAA\tOOOOxx\n1768\t2607\t0\t0\t8\t8\t68\t768\t1768\t1768\t1768\t136\t137\tAQAAAA\tHWDAAA\tVVVVxx\n8492\t2608\t0\t0\t2\t12\t92\t492\t492\t3492\t8492\t184\t185\tQOAAAA\tIWDAAA\tAAAAxx\n8083\t2609\t1\t3\t3\t3\t83\t83\t83\t3083\t8083\t166\t167\tXYAAAA\tJWDAAA\tHHHHxx\n4609\t2610\t1\t1\t9\t9\t9\t609\t609\t4609\t4609\t18\t19\tHVAAAA\tKWDAAA\tOOOOxx\n7520\t2611\t0\t0\t0\t0\t20\t520\t1520\t2520\t7520\t40\t41\tGDAAAA\tLWDAAA\tVVVVxx\n4231\t2612\t1\t3\t1\t11\t31\t231\t231\t4231\t4231\t62\t63\tTGAAAA\tMWDAAA\tAAAAxx\n6022\t2613\t0\t2\t2\t2\t22\t22\t22\t1022\t6022\t44\t45\tQXAAAA\tNWDAAA\tHHHHxx\n9784\t2614\t0\t0\t4\t4\t84\t784\t1784\t4784\t9784\t168\t169\tIMAAAA\tOWDAAA\tOOOOxx\n1343\t2615\t1\t3\t3\t3\t43\t343\t1343\t1343\t1343\t86\t87\tRZAAAA\tPWDAAA\tVVVVxx\n7549\t2616\t1\t1\t9\t9\t49\t549\t1549\t2549\t7549\t98\t99\tJEAAAA\tQWDAAA\tAAAAxx\n269\t2617\t1\t1\t9\t9\t69\t269\t269\t269\t269\t138\t139\tJKAAAA\tRWDAAA\tHHHHxx\n1069\t2618\t1\t1\t9\t9\t69\t69\t1069\t1069\t1069\t138\t139\tDPAAAA\tSWDAAA\tOOOOxx\n4610\t2619\t0\t2\t0\t10\t10\t610\t610\t4610\t4610\t20\t21\tIVAAAA\tTWDAAA\tVVVVxx\n482\t2620\t0\t2\t2\t2\t82\t482\t482\t482\t482\t164\t165\tOSAAAA\tUWDAAA\tAAAAxx\n3025\t2621\t1\t1\t5\t5\t25\t25\t1025\t3025\t3025\t50\t51\tJMAAAA\tVWDAAA\tHHHHxx\n7914\t2622\t0\t2\t4\t14\t14\t914\t1914\t2914\t7914\t28\t29\tKSAAAA\tWWDAAA\tOOOOxx\n3198\t2623\t0\t2\t8\t18\t98\t198\t1198\t3198\t3198\t196\t197\tATAAAA\tXWDAAA\tVVVVxx\n1187\t2624\t1\t3\t7\t7\t87\t187\t1187\t1187\t1187\t174\t175\tRTAAAA\tYWDAAA\tAAAAxx\n4707\t2625\t1\t3\t7\t7\t7\t707\t707\t4707\t4707\t14\t15\tBZAAAA\tZWDAAA\tHHHHxx\n8279\t2626\t1\t3\t9\t19\t79\t279\t279\t3279\t8279\t158\t159\tLGAAAA\tAXDAAA\tOOOOxx\n6127\t2627\t1\t3\t7\t7\t27\t127\t127\t1127\t6127\t54\t55\tRBAAAA\tBXDAAA\tVVVVxx\n1305\t2628\t1\t1\t5\t5\t5\t305\t1305\t1305\t1305\t10\t11\tFYAAAA\tCXDAAA\tAAAAxx\n4804\t2629\t0\t0\t4\t4\t4\t804\t804\t4804\t4804\t8\t9\tUCAAAA\tDXDAAA\tHHHHxx\n6069\t2630\t1\t1\t9\t9\t69\t69\t69\t1069\t6069\t138\t139\tLZAAAA\tEXDAAA\tOOOOxx\n9229\t2631\t1\t1\t9\t9\t29\t229\t1229\t4229\t9229\t58\t59\tZQAAAA\tFXDAAA\tVVVVxx\n4703\t2632\t1\t3\t3\t3\t3\t703\t703\t4703\t4703\t6\t7\tXYAAAA\tGXDAAA\tAAAAxx\n6410\t2633\t0\t2\t0\t10\t10\t410\t410\t1410\t6410\t20\t21\tOMAAAA\tHXDAAA\tHHHHxx\n944\t2634\t0\t0\t4\t4\t44\t944\t944\t944\t944\t88\t89\tIKAAAA\tIXDAAA\tOOOOxx\n3744\t2635\t0\t0\t4\t4\t44\t744\t1744\t3744\t3744\t88\t89\tAOAAAA\tJXDAAA\tVVVVxx\n1127\t2636\t1\t3\t7\t7\t27\t127\t1127\t1127\t1127\t54\t55\tJRAAAA\tKXDAAA\tAAAAxx\n6693\t2637\t1\t1\t3\t13\t93\t693\t693\t1693\t6693\t186\t187\tLXAAAA\tLXDAAA\tHHHHxx\n583\t2638\t1\t3\t3\t3\t83\t583\t583\t583\t583\t166\t167\tLWAAAA\tMXDAAA\tOOOOxx\n2684\t2639\t0\t0\t4\t4\t84\t684\t684\t2684\t2684\t168\t169\tGZAAAA\tNXDAAA\tVVVVxx\n6192\t2640\t0\t0\t2\t12\t92\t192\t192\t1192\t6192\t184\t185\tEEAAAA\tOXDAAA\tAAAAxx\n4157\t2641\t1\t1\t7\t17\t57\t157\t157\t4157\t4157\t114\t115\tXDAAAA\tPXDAAA\tHHHHxx\n6470\t2642\t0\t2\t0\t10\t70\t470\t470\t1470\t6470\t140\t141\tWOAAAA\tQXDAAA\tOOOOxx\n8965\t2643\t1\t1\t5\t5\t65\t965\t965\t3965\t8965\t130\t131\tVGAAAA\tRXDAAA\tVVVVxx\n1433\t2644\t1\t1\t3\t13\t33\t433\t1433\t1433\t1433\t66\t67\tDDAAAA\tSXDAAA\tAAAAxx\n4570\t2645\t0\t2\t0\t10\t70\t570\t570\t4570\t4570\t140\t141\tUTAAAA\tTXDAAA\tHHHHxx\n1806\t2646\t0\t2\t6\t6\t6\t806\t1806\t1806\t1806\t12\t13\tMRAAAA\tUXDAAA\tOOOOxx\n1230\t2647\t0\t2\t0\t10\t30\t230\t1230\t1230\t1230\t60\t61\tIVAAAA\tVXDAAA\tVVVVxx\n2283\t2648\t1\t3\t3\t3\t83\t283\t283\t2283\t2283\t166\t167\tVJAAAA\tWXDAAA\tAAAAxx\n6456\t2649\t0\t0\t6\t16\t56\t456\t456\t1456\t6456\t112\t113\tIOAAAA\tXXDAAA\tHHHHxx\n7427\t2650\t1\t3\t7\t7\t27\t427\t1427\t2427\t7427\t54\t55\tRZAAAA\tYXDAAA\tOOOOxx\n8310\t2651\t0\t2\t0\t10\t10\t310\t310\t3310\t8310\t20\t21\tQHAAAA\tZXDAAA\tVVVVxx\n8103\t2652\t1\t3\t3\t3\t3\t103\t103\t3103\t8103\t6\t7\tRZAAAA\tAYDAAA\tAAAAxx\n3947\t2653\t1\t3\t7\t7\t47\t947\t1947\t3947\t3947\t94\t95\tVVAAAA\tBYDAAA\tHHHHxx\n3414\t2654\t0\t2\t4\t14\t14\t414\t1414\t3414\t3414\t28\t29\tIBAAAA\tCYDAAA\tOOOOxx\n2043\t2655\t1\t3\t3\t3\t43\t43\t43\t2043\t2043\t86\t87\tPAAAAA\tDYDAAA\tVVVVxx\n4393\t2656\t1\t1\t3\t13\t93\t393\t393\t4393\t4393\t186\t187\tZMAAAA\tEYDAAA\tAAAAxx\n6664\t2657\t0\t0\t4\t4\t64\t664\t664\t1664\t6664\t128\t129\tIWAAAA\tFYDAAA\tHHHHxx\n4545\t2658\t1\t1\t5\t5\t45\t545\t545\t4545\t4545\t90\t91\tVSAAAA\tGYDAAA\tOOOOxx\n7637\t2659\t1\t1\t7\t17\t37\t637\t1637\t2637\t7637\t74\t75\tTHAAAA\tHYDAAA\tVVVVxx\n1359\t2660\t1\t3\t9\t19\t59\t359\t1359\t1359\t1359\t118\t119\tHAAAAA\tIYDAAA\tAAAAxx\n5018\t2661\t0\t2\t8\t18\t18\t18\t1018\t18\t5018\t36\t37\tALAAAA\tJYDAAA\tHHHHxx\n987\t2662\t1\t3\t7\t7\t87\t987\t987\t987\t987\t174\t175\tZLAAAA\tKYDAAA\tOOOOxx\n1320\t2663\t0\t0\t0\t0\t20\t320\t1320\t1320\t1320\t40\t41\tUYAAAA\tLYDAAA\tVVVVxx\n9311\t2664\t1\t3\t1\t11\t11\t311\t1311\t4311\t9311\t22\t23\tDUAAAA\tMYDAAA\tAAAAxx\n7993\t2665\t1\t1\t3\t13\t93\t993\t1993\t2993\t7993\t186\t187\tLVAAAA\tNYDAAA\tHHHHxx\n7588\t2666\t0\t0\t8\t8\t88\t588\t1588\t2588\t7588\t176\t177\tWFAAAA\tOYDAAA\tOOOOxx\n5983\t2667\t1\t3\t3\t3\t83\t983\t1983\t983\t5983\t166\t167\tDWAAAA\tPYDAAA\tVVVVxx\n4070\t2668\t0\t2\t0\t10\t70\t70\t70\t4070\t4070\t140\t141\tOAAAAA\tQYDAAA\tAAAAxx\n8349\t2669\t1\t1\t9\t9\t49\t349\t349\t3349\t8349\t98\t99\tDJAAAA\tRYDAAA\tHHHHxx\n3810\t2670\t0\t2\t0\t10\t10\t810\t1810\t3810\t3810\t20\t21\tOQAAAA\tSYDAAA\tOOOOxx\n6948\t2671\t0\t0\t8\t8\t48\t948\t948\t1948\t6948\t96\t97\tGHAAAA\tTYDAAA\tVVVVxx\n7153\t2672\t1\t1\t3\t13\t53\t153\t1153\t2153\t7153\t106\t107\tDPAAAA\tUYDAAA\tAAAAxx\n5371\t2673\t1\t3\t1\t11\t71\t371\t1371\t371\t5371\t142\t143\tPYAAAA\tVYDAAA\tHHHHxx\n8316\t2674\t0\t0\t6\t16\t16\t316\t316\t3316\t8316\t32\t33\tWHAAAA\tWYDAAA\tOOOOxx\n5903\t2675\t1\t3\t3\t3\t3\t903\t1903\t903\t5903\t6\t7\tBTAAAA\tXYDAAA\tVVVVxx\n6718\t2676\t0\t2\t8\t18\t18\t718\t718\t1718\t6718\t36\t37\tKYAAAA\tYYDAAA\tAAAAxx\n4759\t2677\t1\t3\t9\t19\t59\t759\t759\t4759\t4759\t118\t119\tBBAAAA\tZYDAAA\tHHHHxx\n2555\t2678\t1\t3\t5\t15\t55\t555\t555\t2555\t2555\t110\t111\tHUAAAA\tAZDAAA\tOOOOxx\n3457\t2679\t1\t1\t7\t17\t57\t457\t1457\t3457\t3457\t114\t115\tZCAAAA\tBZDAAA\tVVVVxx\n9626\t2680\t0\t2\t6\t6\t26\t626\t1626\t4626\t9626\t52\t53\tGGAAAA\tCZDAAA\tAAAAxx\n2570\t2681\t0\t2\t0\t10\t70\t570\t570\t2570\t2570\t140\t141\tWUAAAA\tDZDAAA\tHHHHxx\n7964\t2682\t0\t0\t4\t4\t64\t964\t1964\t2964\t7964\t128\t129\tIUAAAA\tEZDAAA\tOOOOxx\n1543\t2683\t1\t3\t3\t3\t43\t543\t1543\t1543\t1543\t86\t87\tJHAAAA\tFZDAAA\tVVVVxx\n929\t2684\t1\t1\t9\t9\t29\t929\t929\t929\t929\t58\t59\tTJAAAA\tGZDAAA\tAAAAxx\n9244\t2685\t0\t0\t4\t4\t44\t244\t1244\t4244\t9244\t88\t89\tORAAAA\tHZDAAA\tHHHHxx\n9210\t2686\t0\t2\t0\t10\t10\t210\t1210\t4210\t9210\t20\t21\tGQAAAA\tIZDAAA\tOOOOxx\n8334\t2687\t0\t2\t4\t14\t34\t334\t334\t3334\t8334\t68\t69\tOIAAAA\tJZDAAA\tVVVVxx\n9310\t2688\t0\t2\t0\t10\t10\t310\t1310\t4310\t9310\t20\t21\tCUAAAA\tKZDAAA\tAAAAxx\n5024\t2689\t0\t0\t4\t4\t24\t24\t1024\t24\t5024\t48\t49\tGLAAAA\tLZDAAA\tHHHHxx\n8794\t2690\t0\t2\t4\t14\t94\t794\t794\t3794\t8794\t188\t189\tGAAAAA\tMZDAAA\tOOOOxx\n4091\t2691\t1\t3\t1\t11\t91\t91\t91\t4091\t4091\t182\t183\tJBAAAA\tNZDAAA\tVVVVxx\n649\t2692\t1\t1\t9\t9\t49\t649\t649\t649\t649\t98\t99\tZYAAAA\tOZDAAA\tAAAAxx\n8505\t2693\t1\t1\t5\t5\t5\t505\t505\t3505\t8505\t10\t11\tDPAAAA\tPZDAAA\tHHHHxx\n6652\t2694\t0\t0\t2\t12\t52\t652\t652\t1652\t6652\t104\t105\tWVAAAA\tQZDAAA\tOOOOxx\n8945\t2695\t1\t1\t5\t5\t45\t945\t945\t3945\t8945\t90\t91\tBGAAAA\tRZDAAA\tVVVVxx\n2095\t2696\t1\t3\t5\t15\t95\t95\t95\t2095\t2095\t190\t191\tPCAAAA\tSZDAAA\tAAAAxx\n8676\t2697\t0\t0\t6\t16\t76\t676\t676\t3676\t8676\t152\t153\tSVAAAA\tTZDAAA\tHHHHxx\n3994\t2698\t0\t2\t4\t14\t94\t994\t1994\t3994\t3994\t188\t189\tQXAAAA\tUZDAAA\tOOOOxx\n2859\t2699\t1\t3\t9\t19\t59\t859\t859\t2859\t2859\t118\t119\tZFAAAA\tVZDAAA\tVVVVxx\n5403\t2700\t1\t3\t3\t3\t3\t403\t1403\t403\t5403\t6\t7\tVZAAAA\tWZDAAA\tAAAAxx\n3254\t2701\t0\t2\t4\t14\t54\t254\t1254\t3254\t3254\t108\t109\tEVAAAA\tXZDAAA\tHHHHxx\n7339\t2702\t1\t3\t9\t19\t39\t339\t1339\t2339\t7339\t78\t79\tHWAAAA\tYZDAAA\tOOOOxx\n7220\t2703\t0\t0\t0\t0\t20\t220\t1220\t2220\t7220\t40\t41\tSRAAAA\tZZDAAA\tVVVVxx\n4154\t2704\t0\t2\t4\t14\t54\t154\t154\t4154\t4154\t108\t109\tUDAAAA\tAAEAAA\tAAAAxx\n7570\t2705\t0\t2\t0\t10\t70\t570\t1570\t2570\t7570\t140\t141\tEFAAAA\tBAEAAA\tHHHHxx\n2576\t2706\t0\t0\t6\t16\t76\t576\t576\t2576\t2576\t152\t153\tCVAAAA\tCAEAAA\tOOOOxx\n5764\t2707\t0\t0\t4\t4\t64\t764\t1764\t764\t5764\t128\t129\tSNAAAA\tDAEAAA\tVVVVxx\n4314\t2708\t0\t2\t4\t14\t14\t314\t314\t4314\t4314\t28\t29\tYJAAAA\tEAEAAA\tAAAAxx\n2274\t2709\t0\t2\t4\t14\t74\t274\t274\t2274\t2274\t148\t149\tMJAAAA\tFAEAAA\tHHHHxx\n9756\t2710\t0\t0\t6\t16\t56\t756\t1756\t4756\t9756\t112\t113\tGLAAAA\tGAEAAA\tOOOOxx\n8274\t2711\t0\t2\t4\t14\t74\t274\t274\t3274\t8274\t148\t149\tGGAAAA\tHAEAAA\tVVVVxx\n1289\t2712\t1\t1\t9\t9\t89\t289\t1289\t1289\t1289\t178\t179\tPXAAAA\tIAEAAA\tAAAAxx\n7335\t2713\t1\t3\t5\t15\t35\t335\t1335\t2335\t7335\t70\t71\tDWAAAA\tJAEAAA\tHHHHxx\n5351\t2714\t1\t3\t1\t11\t51\t351\t1351\t351\t5351\t102\t103\tVXAAAA\tKAEAAA\tOOOOxx\n8978\t2715\t0\t2\t8\t18\t78\t978\t978\t3978\t8978\t156\t157\tIHAAAA\tLAEAAA\tVVVVxx\n2\t2716\t0\t2\t2\t2\t2\t2\t2\t2\t2\t4\t5\tCAAAAA\tMAEAAA\tAAAAxx\n8906\t2717\t0\t2\t6\t6\t6\t906\t906\t3906\t8906\t12\t13\tOEAAAA\tNAEAAA\tHHHHxx\n6388\t2718\t0\t0\t8\t8\t88\t388\t388\t1388\t6388\t176\t177\tSLAAAA\tOAEAAA\tOOOOxx\n5675\t2719\t1\t3\t5\t15\t75\t675\t1675\t675\t5675\t150\t151\tHKAAAA\tPAEAAA\tVVVVxx\n255\t2720\t1\t3\t5\t15\t55\t255\t255\t255\t255\t110\t111\tVJAAAA\tQAEAAA\tAAAAxx\n9538\t2721\t0\t2\t8\t18\t38\t538\t1538\t4538\t9538\t76\t77\tWCAAAA\tRAEAAA\tHHHHxx\n1480\t2722\t0\t0\t0\t0\t80\t480\t1480\t1480\t1480\t160\t161\tYEAAAA\tSAEAAA\tOOOOxx\n4015\t2723\t1\t3\t5\t15\t15\t15\t15\t4015\t4015\t30\t31\tLYAAAA\tTAEAAA\tVVVVxx\n5166\t2724\t0\t2\t6\t6\t66\t166\t1166\t166\t5166\t132\t133\tSQAAAA\tUAEAAA\tAAAAxx\n91\t2725\t1\t3\t1\t11\t91\t91\t91\t91\t91\t182\t183\tNDAAAA\tVAEAAA\tHHHHxx\n2958\t2726\t0\t2\t8\t18\t58\t958\t958\t2958\t2958\t116\t117\tUJAAAA\tWAEAAA\tOOOOxx\n9131\t2727\t1\t3\t1\t11\t31\t131\t1131\t4131\t9131\t62\t63\tFNAAAA\tXAEAAA\tVVVVxx\n3944\t2728\t0\t0\t4\t4\t44\t944\t1944\t3944\t3944\t88\t89\tSVAAAA\tYAEAAA\tAAAAxx\n4514\t2729\t0\t2\t4\t14\t14\t514\t514\t4514\t4514\t28\t29\tQRAAAA\tZAEAAA\tHHHHxx\n5661\t2730\t1\t1\t1\t1\t61\t661\t1661\t661\t5661\t122\t123\tTJAAAA\tABEAAA\tOOOOxx\n8724\t2731\t0\t0\t4\t4\t24\t724\t724\t3724\t8724\t48\t49\tOXAAAA\tBBEAAA\tVVVVxx\n6408\t2732\t0\t0\t8\t8\t8\t408\t408\t1408\t6408\t16\t17\tMMAAAA\tCBEAAA\tAAAAxx\n5013\t2733\t1\t1\t3\t13\t13\t13\t1013\t13\t5013\t26\t27\tVKAAAA\tDBEAAA\tHHHHxx\n6156\t2734\t0\t0\t6\t16\t56\t156\t156\t1156\t6156\t112\t113\tUCAAAA\tEBEAAA\tOOOOxx\n7350\t2735\t0\t2\t0\t10\t50\t350\t1350\t2350\t7350\t100\t101\tSWAAAA\tFBEAAA\tVVVVxx\n9858\t2736\t0\t2\t8\t18\t58\t858\t1858\t4858\t9858\t116\t117\tEPAAAA\tGBEAAA\tAAAAxx\n895\t2737\t1\t3\t5\t15\t95\t895\t895\t895\t895\t190\t191\tLIAAAA\tHBEAAA\tHHHHxx\n8368\t2738\t0\t0\t8\t8\t68\t368\t368\t3368\t8368\t136\t137\tWJAAAA\tIBEAAA\tOOOOxx\n179\t2739\t1\t3\t9\t19\t79\t179\t179\t179\t179\t158\t159\tXGAAAA\tJBEAAA\tVVVVxx\n4048\t2740\t0\t0\t8\t8\t48\t48\t48\t4048\t4048\t96\t97\tSZAAAA\tKBEAAA\tAAAAxx\n3073\t2741\t1\t1\t3\t13\t73\t73\t1073\t3073\t3073\t146\t147\tFOAAAA\tLBEAAA\tHHHHxx\n321\t2742\t1\t1\t1\t1\t21\t321\t321\t321\t321\t42\t43\tJMAAAA\tMBEAAA\tOOOOxx\n5352\t2743\t0\t0\t2\t12\t52\t352\t1352\t352\t5352\t104\t105\tWXAAAA\tNBEAAA\tVVVVxx\n1940\t2744\t0\t0\t0\t0\t40\t940\t1940\t1940\t1940\t80\t81\tQWAAAA\tOBEAAA\tAAAAxx\n8803\t2745\t1\t3\t3\t3\t3\t803\t803\t3803\t8803\t6\t7\tPAAAAA\tPBEAAA\tHHHHxx\n791\t2746\t1\t3\t1\t11\t91\t791\t791\t791\t791\t182\t183\tLEAAAA\tQBEAAA\tOOOOxx\n9809\t2747\t1\t1\t9\t9\t9\t809\t1809\t4809\t9809\t18\t19\tHNAAAA\tRBEAAA\tVVVVxx\n5519\t2748\t1\t3\t9\t19\t19\t519\t1519\t519\t5519\t38\t39\tHEAAAA\tSBEAAA\tAAAAxx\n7420\t2749\t0\t0\t0\t0\t20\t420\t1420\t2420\t7420\t40\t41\tKZAAAA\tTBEAAA\tHHHHxx\n7541\t2750\t1\t1\t1\t1\t41\t541\t1541\t2541\t7541\t82\t83\tBEAAAA\tUBEAAA\tOOOOxx\n6538\t2751\t0\t2\t8\t18\t38\t538\t538\t1538\t6538\t76\t77\tMRAAAA\tVBEAAA\tVVVVxx\n710\t2752\t0\t2\t0\t10\t10\t710\t710\t710\t710\t20\t21\tIBAAAA\tWBEAAA\tAAAAxx\n9488\t2753\t0\t0\t8\t8\t88\t488\t1488\t4488\t9488\t176\t177\tYAAAAA\tXBEAAA\tHHHHxx\n3135\t2754\t1\t3\t5\t15\t35\t135\t1135\t3135\t3135\t70\t71\tPQAAAA\tYBEAAA\tOOOOxx\n4273\t2755\t1\t1\t3\t13\t73\t273\t273\t4273\t4273\t146\t147\tJIAAAA\tZBEAAA\tVVVVxx\n629\t2756\t1\t1\t9\t9\t29\t629\t629\t629\t629\t58\t59\tFYAAAA\tACEAAA\tAAAAxx\n9167\t2757\t1\t3\t7\t7\t67\t167\t1167\t4167\t9167\t134\t135\tPOAAAA\tBCEAAA\tHHHHxx\n751\t2758\t1\t3\t1\t11\t51\t751\t751\t751\t751\t102\t103\tXCAAAA\tCCEAAA\tOOOOxx\n1126\t2759\t0\t2\t6\t6\t26\t126\t1126\t1126\t1126\t52\t53\tIRAAAA\tDCEAAA\tVVVVxx\n3724\t2760\t0\t0\t4\t4\t24\t724\t1724\t3724\t3724\t48\t49\tGNAAAA\tECEAAA\tAAAAxx\n1789\t2761\t1\t1\t9\t9\t89\t789\t1789\t1789\t1789\t178\t179\tVQAAAA\tFCEAAA\tHHHHxx\n792\t2762\t0\t0\t2\t12\t92\t792\t792\t792\t792\t184\t185\tMEAAAA\tGCEAAA\tOOOOxx\n2771\t2763\t1\t3\t1\t11\t71\t771\t771\t2771\t2771\t142\t143\tPCAAAA\tHCEAAA\tVVVVxx\n4313\t2764\t1\t1\t3\t13\t13\t313\t313\t4313\t4313\t26\t27\tXJAAAA\tICEAAA\tAAAAxx\n9312\t2765\t0\t0\t2\t12\t12\t312\t1312\t4312\t9312\t24\t25\tEUAAAA\tJCEAAA\tHHHHxx\n955\t2766\t1\t3\t5\t15\t55\t955\t955\t955\t955\t110\t111\tTKAAAA\tKCEAAA\tOOOOxx\n6382\t2767\t0\t2\t2\t2\t82\t382\t382\t1382\t6382\t164\t165\tMLAAAA\tLCEAAA\tVVVVxx\n7875\t2768\t1\t3\t5\t15\t75\t875\t1875\t2875\t7875\t150\t151\tXQAAAA\tMCEAAA\tAAAAxx\n7491\t2769\t1\t3\t1\t11\t91\t491\t1491\t2491\t7491\t182\t183\tDCAAAA\tNCEAAA\tHHHHxx\n8193\t2770\t1\t1\t3\t13\t93\t193\t193\t3193\t8193\t186\t187\tDDAAAA\tOCEAAA\tOOOOxx\n968\t2771\t0\t0\t8\t8\t68\t968\t968\t968\t968\t136\t137\tGLAAAA\tPCEAAA\tVVVVxx\n4951\t2772\t1\t3\t1\t11\t51\t951\t951\t4951\t4951\t102\t103\tLIAAAA\tQCEAAA\tAAAAxx\n2204\t2773\t0\t0\t4\t4\t4\t204\t204\t2204\t2204\t8\t9\tUGAAAA\tRCEAAA\tHHHHxx\n2066\t2774\t0\t2\t6\t6\t66\t66\t66\t2066\t2066\t132\t133\tMBAAAA\tSCEAAA\tOOOOxx\n2631\t2775\t1\t3\t1\t11\t31\t631\t631\t2631\t2631\t62\t63\tFXAAAA\tTCEAAA\tVVVVxx\n8947\t2776\t1\t3\t7\t7\t47\t947\t947\t3947\t8947\t94\t95\tDGAAAA\tUCEAAA\tAAAAxx\n8033\t2777\t1\t1\t3\t13\t33\t33\t33\t3033\t8033\t66\t67\tZWAAAA\tVCEAAA\tHHHHxx\n6264\t2778\t0\t0\t4\t4\t64\t264\t264\t1264\t6264\t128\t129\tYGAAAA\tWCEAAA\tOOOOxx\n7778\t2779\t0\t2\t8\t18\t78\t778\t1778\t2778\t7778\t156\t157\tENAAAA\tXCEAAA\tVVVVxx\n9701\t2780\t1\t1\t1\t1\t1\t701\t1701\t4701\t9701\t2\t3\tDJAAAA\tYCEAAA\tAAAAxx\n5091\t2781\t1\t3\t1\t11\t91\t91\t1091\t91\t5091\t182\t183\tVNAAAA\tZCEAAA\tHHHHxx\n7577\t2782\t1\t1\t7\t17\t77\t577\t1577\t2577\t7577\t154\t155\tLFAAAA\tADEAAA\tOOOOxx\n3345\t2783\t1\t1\t5\t5\t45\t345\t1345\t3345\t3345\t90\t91\tRYAAAA\tBDEAAA\tVVVVxx\n7329\t2784\t1\t1\t9\t9\t29\t329\t1329\t2329\t7329\t58\t59\tXVAAAA\tCDEAAA\tAAAAxx\n7551\t2785\t1\t3\t1\t11\t51\t551\t1551\t2551\t7551\t102\t103\tLEAAAA\tDDEAAA\tHHHHxx\n6207\t2786\t1\t3\t7\t7\t7\t207\t207\t1207\t6207\t14\t15\tTEAAAA\tEDEAAA\tOOOOxx\n8664\t2787\t0\t0\t4\t4\t64\t664\t664\t3664\t8664\t128\t129\tGVAAAA\tFDEAAA\tVVVVxx\n8394\t2788\t0\t2\t4\t14\t94\t394\t394\t3394\t8394\t188\t189\tWKAAAA\tGDEAAA\tAAAAxx\n7324\t2789\t0\t0\t4\t4\t24\t324\t1324\t2324\t7324\t48\t49\tSVAAAA\tHDEAAA\tHHHHxx\n2713\t2790\t1\t1\t3\t13\t13\t713\t713\t2713\t2713\t26\t27\tJAAAAA\tIDEAAA\tOOOOxx\n2230\t2791\t0\t2\t0\t10\t30\t230\t230\t2230\t2230\t60\t61\tUHAAAA\tJDEAAA\tVVVVxx\n9211\t2792\t1\t3\t1\t11\t11\t211\t1211\t4211\t9211\t22\t23\tHQAAAA\tKDEAAA\tAAAAxx\n1296\t2793\t0\t0\t6\t16\t96\t296\t1296\t1296\t1296\t192\t193\tWXAAAA\tLDEAAA\tHHHHxx\n8104\t2794\t0\t0\t4\t4\t4\t104\t104\t3104\t8104\t8\t9\tSZAAAA\tMDEAAA\tOOOOxx\n6916\t2795\t0\t0\t6\t16\t16\t916\t916\t1916\t6916\t32\t33\tAGAAAA\tNDEAAA\tVVVVxx\n2208\t2796\t0\t0\t8\t8\t8\t208\t208\t2208\t2208\t16\t17\tYGAAAA\tODEAAA\tAAAAxx\n3935\t2797\t1\t3\t5\t15\t35\t935\t1935\t3935\t3935\t70\t71\tJVAAAA\tPDEAAA\tHHHHxx\n7814\t2798\t0\t2\t4\t14\t14\t814\t1814\t2814\t7814\t28\t29\tOOAAAA\tQDEAAA\tOOOOxx\n6508\t2799\t0\t0\t8\t8\t8\t508\t508\t1508\t6508\t16\t17\tIQAAAA\tRDEAAA\tVVVVxx\n1703\t2800\t1\t3\t3\t3\t3\t703\t1703\t1703\t1703\t6\t7\tNNAAAA\tSDEAAA\tAAAAxx\n5640\t2801\t0\t0\t0\t0\t40\t640\t1640\t640\t5640\t80\t81\tYIAAAA\tTDEAAA\tHHHHxx\n6417\t2802\t1\t1\t7\t17\t17\t417\t417\t1417\t6417\t34\t35\tVMAAAA\tUDEAAA\tOOOOxx\n1713\t2803\t1\t1\t3\t13\t13\t713\t1713\t1713\t1713\t26\t27\tXNAAAA\tVDEAAA\tVVVVxx\n5309\t2804\t1\t1\t9\t9\t9\t309\t1309\t309\t5309\t18\t19\tFWAAAA\tWDEAAA\tAAAAxx\n4364\t2805\t0\t0\t4\t4\t64\t364\t364\t4364\t4364\t128\t129\tWLAAAA\tXDEAAA\tHHHHxx\n619\t2806\t1\t3\t9\t19\t19\t619\t619\t619\t619\t38\t39\tVXAAAA\tYDEAAA\tOOOOxx\n9498\t2807\t0\t2\t8\t18\t98\t498\t1498\t4498\t9498\t196\t197\tIBAAAA\tZDEAAA\tVVVVxx\n2804\t2808\t0\t0\t4\t4\t4\t804\t804\t2804\t2804\t8\t9\tWDAAAA\tAEEAAA\tAAAAxx\n2220\t2809\t0\t0\t0\t0\t20\t220\t220\t2220\t2220\t40\t41\tKHAAAA\tBEEAAA\tHHHHxx\n9542\t2810\t0\t2\t2\t2\t42\t542\t1542\t4542\t9542\t84\t85\tADAAAA\tCEEAAA\tOOOOxx\n3349\t2811\t1\t1\t9\t9\t49\t349\t1349\t3349\t3349\t98\t99\tVYAAAA\tDEEAAA\tVVVVxx\n9198\t2812\t0\t2\t8\t18\t98\t198\t1198\t4198\t9198\t196\t197\tUPAAAA\tEEEAAA\tAAAAxx\n2727\t2813\t1\t3\t7\t7\t27\t727\t727\t2727\t2727\t54\t55\tXAAAAA\tFEEAAA\tHHHHxx\n3768\t2814\t0\t0\t8\t8\t68\t768\t1768\t3768\t3768\t136\t137\tYOAAAA\tGEEAAA\tOOOOxx\n2334\t2815\t0\t2\t4\t14\t34\t334\t334\t2334\t2334\t68\t69\tULAAAA\tHEEAAA\tVVVVxx\n7770\t2816\t0\t2\t0\t10\t70\t770\t1770\t2770\t7770\t140\t141\tWMAAAA\tIEEAAA\tAAAAxx\n5963\t2817\t1\t3\t3\t3\t63\t963\t1963\t963\t5963\t126\t127\tJVAAAA\tJEEAAA\tHHHHxx\n4732\t2818\t0\t0\t2\t12\t32\t732\t732\t4732\t4732\t64\t65\tAAAAAA\tKEEAAA\tOOOOxx\n2448\t2819\t0\t0\t8\t8\t48\t448\t448\t2448\t2448\t96\t97\tEQAAAA\tLEEAAA\tVVVVxx\n5998\t2820\t0\t2\t8\t18\t98\t998\t1998\t998\t5998\t196\t197\tSWAAAA\tMEEAAA\tAAAAxx\n8577\t2821\t1\t1\t7\t17\t77\t577\t577\t3577\t8577\t154\t155\tXRAAAA\tNEEAAA\tHHHHxx\n266\t2822\t0\t2\t6\t6\t66\t266\t266\t266\t266\t132\t133\tGKAAAA\tOEEAAA\tOOOOxx\n2169\t2823\t1\t1\t9\t9\t69\t169\t169\t2169\t2169\t138\t139\tLFAAAA\tPEEAAA\tVVVVxx\n8228\t2824\t0\t0\t8\t8\t28\t228\t228\t3228\t8228\t56\t57\tMEAAAA\tQEEAAA\tAAAAxx\n4813\t2825\t1\t1\t3\t13\t13\t813\t813\t4813\t4813\t26\t27\tDDAAAA\tREEAAA\tHHHHxx\n2769\t2826\t1\t1\t9\t9\t69\t769\t769\t2769\t2769\t138\t139\tNCAAAA\tSEEAAA\tOOOOxx\n8382\t2827\t0\t2\t2\t2\t82\t382\t382\t3382\t8382\t164\t165\tKKAAAA\tTEEAAA\tVVVVxx\n1717\t2828\t1\t1\t7\t17\t17\t717\t1717\t1717\t1717\t34\t35\tBOAAAA\tUEEAAA\tAAAAxx\n7178\t2829\t0\t2\t8\t18\t78\t178\t1178\t2178\t7178\t156\t157\tCQAAAA\tVEEAAA\tHHHHxx\n9547\t2830\t1\t3\t7\t7\t47\t547\t1547\t4547\t9547\t94\t95\tFDAAAA\tWEEAAA\tOOOOxx\n8187\t2831\t1\t3\t7\t7\t87\t187\t187\t3187\t8187\t174\t175\tXCAAAA\tXEEAAA\tVVVVxx\n3168\t2832\t0\t0\t8\t8\t68\t168\t1168\t3168\t3168\t136\t137\tWRAAAA\tYEEAAA\tAAAAxx\n2180\t2833\t0\t0\t0\t0\t80\t180\t180\t2180\t2180\t160\t161\tWFAAAA\tZEEAAA\tHHHHxx\n859\t2834\t1\t3\t9\t19\t59\t859\t859\t859\t859\t118\t119\tBHAAAA\tAFEAAA\tOOOOxx\n1554\t2835\t0\t2\t4\t14\t54\t554\t1554\t1554\t1554\t108\t109\tUHAAAA\tBFEAAA\tVVVVxx\n3567\t2836\t1\t3\t7\t7\t67\t567\t1567\t3567\t3567\t134\t135\tFHAAAA\tCFEAAA\tAAAAxx\n5985\t2837\t1\t1\t5\t5\t85\t985\t1985\t985\t5985\t170\t171\tFWAAAA\tDFEAAA\tHHHHxx\n1\t2838\t1\t1\t1\t1\t1\t1\t1\t1\t1\t2\t3\tBAAAAA\tEFEAAA\tOOOOxx\n5937\t2839\t1\t1\t7\t17\t37\t937\t1937\t937\t5937\t74\t75\tJUAAAA\tFFEAAA\tVVVVxx\n7594\t2840\t0\t2\t4\t14\t94\t594\t1594\t2594\t7594\t188\t189\tCGAAAA\tGFEAAA\tAAAAxx\n3783\t2841\t1\t3\t3\t3\t83\t783\t1783\t3783\t3783\t166\t167\tNPAAAA\tHFEAAA\tHHHHxx\n6841\t2842\t1\t1\t1\t1\t41\t841\t841\t1841\t6841\t82\t83\tDDAAAA\tIFEAAA\tOOOOxx\n9694\t2843\t0\t2\t4\t14\t94\t694\t1694\t4694\t9694\t188\t189\tWIAAAA\tJFEAAA\tVVVVxx\n4322\t2844\t0\t2\t2\t2\t22\t322\t322\t4322\t4322\t44\t45\tGKAAAA\tKFEAAA\tAAAAxx\n6012\t2845\t0\t0\t2\t12\t12\t12\t12\t1012\t6012\t24\t25\tGXAAAA\tLFEAAA\tHHHHxx\n108\t2846\t0\t0\t8\t8\t8\t108\t108\t108\t108\t16\t17\tEEAAAA\tMFEAAA\tOOOOxx\n3396\t2847\t0\t0\t6\t16\t96\t396\t1396\t3396\t3396\t192\t193\tQAAAAA\tNFEAAA\tVVVVxx\n8643\t2848\t1\t3\t3\t3\t43\t643\t643\t3643\t8643\t86\t87\tLUAAAA\tOFEAAA\tAAAAxx\n6087\t2849\t1\t3\t7\t7\t87\t87\t87\t1087\t6087\t174\t175\tDAAAAA\tPFEAAA\tHHHHxx\n2629\t2850\t1\t1\t9\t9\t29\t629\t629\t2629\t2629\t58\t59\tDXAAAA\tQFEAAA\tOOOOxx\n3009\t2851\t1\t1\t9\t9\t9\t9\t1009\t3009\t3009\t18\t19\tTLAAAA\tRFEAAA\tVVVVxx\n438\t2852\t0\t2\t8\t18\t38\t438\t438\t438\t438\t76\t77\tWQAAAA\tSFEAAA\tAAAAxx\n2480\t2853\t0\t0\t0\t0\t80\t480\t480\t2480\t2480\t160\t161\tKRAAAA\tTFEAAA\tHHHHxx\n936\t2854\t0\t0\t6\t16\t36\t936\t936\t936\t936\t72\t73\tAKAAAA\tUFEAAA\tOOOOxx\n6\t2855\t0\t2\t6\t6\t6\t6\t6\t6\t6\t12\t13\tGAAAAA\tVFEAAA\tVVVVxx\n768\t2856\t0\t0\t8\t8\t68\t768\t768\t768\t768\t136\t137\tODAAAA\tWFEAAA\tAAAAxx\n1564\t2857\t0\t0\t4\t4\t64\t564\t1564\t1564\t1564\t128\t129\tEIAAAA\tXFEAAA\tHHHHxx\n3236\t2858\t0\t0\t6\t16\t36\t236\t1236\t3236\t3236\t72\t73\tMUAAAA\tYFEAAA\tOOOOxx\n3932\t2859\t0\t0\t2\t12\t32\t932\t1932\t3932\t3932\t64\t65\tGVAAAA\tZFEAAA\tVVVVxx\n8914\t2860\t0\t2\t4\t14\t14\t914\t914\t3914\t8914\t28\t29\tWEAAAA\tAGEAAA\tAAAAxx\n119\t2861\t1\t3\t9\t19\t19\t119\t119\t119\t119\t38\t39\tPEAAAA\tBGEAAA\tHHHHxx\n6034\t2862\t0\t2\t4\t14\t34\t34\t34\t1034\t6034\t68\t69\tCYAAAA\tCGEAAA\tOOOOxx\n5384\t2863\t0\t0\t4\t4\t84\t384\t1384\t384\t5384\t168\t169\tCZAAAA\tDGEAAA\tVVVVxx\n6885\t2864\t1\t1\t5\t5\t85\t885\t885\t1885\t6885\t170\t171\tVEAAAA\tEGEAAA\tAAAAxx\n232\t2865\t0\t0\t2\t12\t32\t232\t232\t232\t232\t64\t65\tYIAAAA\tFGEAAA\tHHHHxx\n1293\t2866\t1\t1\t3\t13\t93\t293\t1293\t1293\t1293\t186\t187\tTXAAAA\tGGEAAA\tOOOOxx\n9204\t2867\t0\t0\t4\t4\t4\t204\t1204\t4204\t9204\t8\t9\tAQAAAA\tHGEAAA\tVVVVxx\n527\t2868\t1\t3\t7\t7\t27\t527\t527\t527\t527\t54\t55\tHUAAAA\tIGEAAA\tAAAAxx\n6539\t2869\t1\t3\t9\t19\t39\t539\t539\t1539\t6539\t78\t79\tNRAAAA\tJGEAAA\tHHHHxx\n3679\t2870\t1\t3\t9\t19\t79\t679\t1679\t3679\t3679\t158\t159\tNLAAAA\tKGEAAA\tOOOOxx\n8282\t2871\t0\t2\t2\t2\t82\t282\t282\t3282\t8282\t164\t165\tOGAAAA\tLGEAAA\tVVVVxx\n5027\t2872\t1\t3\t7\t7\t27\t27\t1027\t27\t5027\t54\t55\tJLAAAA\tMGEAAA\tAAAAxx\n7694\t2873\t0\t2\t4\t14\t94\t694\t1694\t2694\t7694\t188\t189\tYJAAAA\tNGEAAA\tHHHHxx\n473\t2874\t1\t1\t3\t13\t73\t473\t473\t473\t473\t146\t147\tFSAAAA\tOGEAAA\tOOOOxx\n6325\t2875\t1\t1\t5\t5\t25\t325\t325\t1325\t6325\t50\t51\tHJAAAA\tPGEAAA\tVVVVxx\n8761\t2876\t1\t1\t1\t1\t61\t761\t761\t3761\t8761\t122\t123\tZYAAAA\tQGEAAA\tAAAAxx\n6184\t2877\t0\t0\t4\t4\t84\t184\t184\t1184\t6184\t168\t169\tWDAAAA\tRGEAAA\tHHHHxx\n419\t2878\t1\t3\t9\t19\t19\t419\t419\t419\t419\t38\t39\tDQAAAA\tSGEAAA\tOOOOxx\n6111\t2879\t1\t3\t1\t11\t11\t111\t111\t1111\t6111\t22\t23\tBBAAAA\tTGEAAA\tVVVVxx\n3836\t2880\t0\t0\t6\t16\t36\t836\t1836\t3836\t3836\t72\t73\tORAAAA\tUGEAAA\tAAAAxx\n4086\t2881\t0\t2\t6\t6\t86\t86\t86\t4086\t4086\t172\t173\tEBAAAA\tVGEAAA\tHHHHxx\n5818\t2882\t0\t2\t8\t18\t18\t818\t1818\t818\t5818\t36\t37\tUPAAAA\tWGEAAA\tOOOOxx\n4528\t2883\t0\t0\t8\t8\t28\t528\t528\t4528\t4528\t56\t57\tESAAAA\tXGEAAA\tVVVVxx\n7199\t2884\t1\t3\t9\t19\t99\t199\t1199\t2199\t7199\t198\t199\tXQAAAA\tYGEAAA\tAAAAxx\n1847\t2885\t1\t3\t7\t7\t47\t847\t1847\t1847\t1847\t94\t95\tBTAAAA\tZGEAAA\tHHHHxx\n2875\t2886\t1\t3\t5\t15\t75\t875\t875\t2875\t2875\t150\t151\tPGAAAA\tAHEAAA\tOOOOxx\n2872\t2887\t0\t0\t2\t12\t72\t872\t872\t2872\t2872\t144\t145\tMGAAAA\tBHEAAA\tVVVVxx\n3972\t2888\t0\t0\t2\t12\t72\t972\t1972\t3972\t3972\t144\t145\tUWAAAA\tCHEAAA\tAAAAxx\n7590\t2889\t0\t2\t0\t10\t90\t590\t1590\t2590\t7590\t180\t181\tYFAAAA\tDHEAAA\tHHHHxx\n1914\t2890\t0\t2\t4\t14\t14\t914\t1914\t1914\t1914\t28\t29\tQVAAAA\tEHEAAA\tOOOOxx\n1658\t2891\t0\t2\t8\t18\t58\t658\t1658\t1658\t1658\t116\t117\tULAAAA\tFHEAAA\tVVVVxx\n2126\t2892\t0\t2\t6\t6\t26\t126\t126\t2126\t2126\t52\t53\tUDAAAA\tGHEAAA\tAAAAxx\n645\t2893\t1\t1\t5\t5\t45\t645\t645\t645\t645\t90\t91\tVYAAAA\tHHEAAA\tHHHHxx\n6636\t2894\t0\t0\t6\t16\t36\t636\t636\t1636\t6636\t72\t73\tGVAAAA\tIHEAAA\tOOOOxx\n1469\t2895\t1\t1\t9\t9\t69\t469\t1469\t1469\t1469\t138\t139\tNEAAAA\tJHEAAA\tVVVVxx\n1377\t2896\t1\t1\t7\t17\t77\t377\t1377\t1377\t1377\t154\t155\tZAAAAA\tKHEAAA\tAAAAxx\n8425\t2897\t1\t1\t5\t5\t25\t425\t425\t3425\t8425\t50\t51\tBMAAAA\tLHEAAA\tHHHHxx\n9300\t2898\t0\t0\t0\t0\t0\t300\t1300\t4300\t9300\t0\t1\tSTAAAA\tMHEAAA\tOOOOxx\n5355\t2899\t1\t3\t5\t15\t55\t355\t1355\t355\t5355\t110\t111\tZXAAAA\tNHEAAA\tVVVVxx\n840\t2900\t0\t0\t0\t0\t40\t840\t840\t840\t840\t80\t81\tIGAAAA\tOHEAAA\tAAAAxx\n5185\t2901\t1\t1\t5\t5\t85\t185\t1185\t185\t5185\t170\t171\tLRAAAA\tPHEAAA\tHHHHxx\n6467\t2902\t1\t3\t7\t7\t67\t467\t467\t1467\t6467\t134\t135\tTOAAAA\tQHEAAA\tOOOOxx\n58\t2903\t0\t2\t8\t18\t58\t58\t58\t58\t58\t116\t117\tGCAAAA\tRHEAAA\tVVVVxx\n5051\t2904\t1\t3\t1\t11\t51\t51\t1051\t51\t5051\t102\t103\tHMAAAA\tSHEAAA\tAAAAxx\n8901\t2905\t1\t1\t1\t1\t1\t901\t901\t3901\t8901\t2\t3\tJEAAAA\tTHEAAA\tHHHHxx\n1550\t2906\t0\t2\t0\t10\t50\t550\t1550\t1550\t1550\t100\t101\tQHAAAA\tUHEAAA\tOOOOxx\n1698\t2907\t0\t2\t8\t18\t98\t698\t1698\t1698\t1698\t196\t197\tINAAAA\tVHEAAA\tVVVVxx\n802\t2908\t0\t2\t2\t2\t2\t802\t802\t802\t802\t4\t5\tWEAAAA\tWHEAAA\tAAAAxx\n2440\t2909\t0\t0\t0\t0\t40\t440\t440\t2440\t2440\t80\t81\tWPAAAA\tXHEAAA\tHHHHxx\n2260\t2910\t0\t0\t0\t0\t60\t260\t260\t2260\t2260\t120\t121\tYIAAAA\tYHEAAA\tOOOOxx\n8218\t2911\t0\t2\t8\t18\t18\t218\t218\t3218\t8218\t36\t37\tCEAAAA\tZHEAAA\tVVVVxx\n5144\t2912\t0\t0\t4\t4\t44\t144\t1144\t144\t5144\t88\t89\tWPAAAA\tAIEAAA\tAAAAxx\n4822\t2913\t0\t2\t2\t2\t22\t822\t822\t4822\t4822\t44\t45\tMDAAAA\tBIEAAA\tHHHHxx\n9476\t2914\t0\t0\t6\t16\t76\t476\t1476\t4476\t9476\t152\t153\tMAAAAA\tCIEAAA\tOOOOxx\n7535\t2915\t1\t3\t5\t15\t35\t535\t1535\t2535\t7535\t70\t71\tVDAAAA\tDIEAAA\tVVVVxx\n8738\t2916\t0\t2\t8\t18\t38\t738\t738\t3738\t8738\t76\t77\tCYAAAA\tEIEAAA\tAAAAxx\n7946\t2917\t0\t2\t6\t6\t46\t946\t1946\t2946\t7946\t92\t93\tQTAAAA\tFIEAAA\tHHHHxx\n8143\t2918\t1\t3\t3\t3\t43\t143\t143\t3143\t8143\t86\t87\tFBAAAA\tGIEAAA\tOOOOxx\n2623\t2919\t1\t3\t3\t3\t23\t623\t623\t2623\t2623\t46\t47\tXWAAAA\tHIEAAA\tVVVVxx\n5209\t2920\t1\t1\t9\t9\t9\t209\t1209\t209\t5209\t18\t19\tJSAAAA\tIIEAAA\tAAAAxx\n7674\t2921\t0\t2\t4\t14\t74\t674\t1674\t2674\t7674\t148\t149\tEJAAAA\tJIEAAA\tHHHHxx\n1135\t2922\t1\t3\t5\t15\t35\t135\t1135\t1135\t1135\t70\t71\tRRAAAA\tKIEAAA\tOOOOxx\n424\t2923\t0\t0\t4\t4\t24\t424\t424\t424\t424\t48\t49\tIQAAAA\tLIEAAA\tVVVVxx\n942\t2924\t0\t2\t2\t2\t42\t942\t942\t942\t942\t84\t85\tGKAAAA\tMIEAAA\tAAAAxx\n7813\t2925\t1\t1\t3\t13\t13\t813\t1813\t2813\t7813\t26\t27\tNOAAAA\tNIEAAA\tHHHHxx\n3539\t2926\t1\t3\t9\t19\t39\t539\t1539\t3539\t3539\t78\t79\tDGAAAA\tOIEAAA\tOOOOxx\n2909\t2927\t1\t1\t9\t9\t9\t909\t909\t2909\t2909\t18\t19\tXHAAAA\tPIEAAA\tVVVVxx\n3748\t2928\t0\t0\t8\t8\t48\t748\t1748\t3748\t3748\t96\t97\tEOAAAA\tQIEAAA\tAAAAxx\n2996\t2929\t0\t0\t6\t16\t96\t996\t996\t2996\t2996\t192\t193\tGLAAAA\tRIEAAA\tHHHHxx\n1869\t2930\t1\t1\t9\t9\t69\t869\t1869\t1869\t1869\t138\t139\tXTAAAA\tSIEAAA\tOOOOxx\n8151\t2931\t1\t3\t1\t11\t51\t151\t151\t3151\t8151\t102\t103\tNBAAAA\tTIEAAA\tVVVVxx\n6361\t2932\t1\t1\t1\t1\t61\t361\t361\t1361\t6361\t122\t123\tRKAAAA\tUIEAAA\tAAAAxx\n5568\t2933\t0\t0\t8\t8\t68\t568\t1568\t568\t5568\t136\t137\tEGAAAA\tVIEAAA\tHHHHxx\n2796\t2934\t0\t0\t6\t16\t96\t796\t796\t2796\t2796\t192\t193\tODAAAA\tWIEAAA\tOOOOxx\n8489\t2935\t1\t1\t9\t9\t89\t489\t489\t3489\t8489\t178\t179\tNOAAAA\tXIEAAA\tVVVVxx\n9183\t2936\t1\t3\t3\t3\t83\t183\t1183\t4183\t9183\t166\t167\tFPAAAA\tYIEAAA\tAAAAxx\n8227\t2937\t1\t3\t7\t7\t27\t227\t227\t3227\t8227\t54\t55\tLEAAAA\tZIEAAA\tHHHHxx\n1844\t2938\t0\t0\t4\t4\t44\t844\t1844\t1844\t1844\t88\t89\tYSAAAA\tAJEAAA\tOOOOxx\n3975\t2939\t1\t3\t5\t15\t75\t975\t1975\t3975\t3975\t150\t151\tXWAAAA\tBJEAAA\tVVVVxx\n6490\t2940\t0\t2\t0\t10\t90\t490\t490\t1490\t6490\t180\t181\tQPAAAA\tCJEAAA\tAAAAxx\n8303\t2941\t1\t3\t3\t3\t3\t303\t303\t3303\t8303\t6\t7\tJHAAAA\tDJEAAA\tHHHHxx\n7334\t2942\t0\t2\t4\t14\t34\t334\t1334\t2334\t7334\t68\t69\tCWAAAA\tEJEAAA\tOOOOxx\n2382\t2943\t0\t2\t2\t2\t82\t382\t382\t2382\t2382\t164\t165\tQNAAAA\tFJEAAA\tVVVVxx\n177\t2944\t1\t1\t7\t17\t77\t177\t177\t177\t177\t154\t155\tVGAAAA\tGJEAAA\tAAAAxx\n8117\t2945\t1\t1\t7\t17\t17\t117\t117\t3117\t8117\t34\t35\tFAAAAA\tHJEAAA\tHHHHxx\n5485\t2946\t1\t1\t5\t5\t85\t485\t1485\t485\t5485\t170\t171\tZCAAAA\tIJEAAA\tOOOOxx\n6544\t2947\t0\t0\t4\t4\t44\t544\t544\t1544\t6544\t88\t89\tSRAAAA\tJJEAAA\tVVVVxx\n8517\t2948\t1\t1\t7\t17\t17\t517\t517\t3517\t8517\t34\t35\tPPAAAA\tKJEAAA\tAAAAxx\n2252\t2949\t0\t0\t2\t12\t52\t252\t252\t2252\t2252\t104\t105\tQIAAAA\tLJEAAA\tHHHHxx\n4480\t2950\t0\t0\t0\t0\t80\t480\t480\t4480\t4480\t160\t161\tIQAAAA\tMJEAAA\tOOOOxx\n4785\t2951\t1\t1\t5\t5\t85\t785\t785\t4785\t4785\t170\t171\tBCAAAA\tNJEAAA\tVVVVxx\n9700\t2952\t0\t0\t0\t0\t0\t700\t1700\t4700\t9700\t0\t1\tCJAAAA\tOJEAAA\tAAAAxx\n2122\t2953\t0\t2\t2\t2\t22\t122\t122\t2122\t2122\t44\t45\tQDAAAA\tPJEAAA\tHHHHxx\n8783\t2954\t1\t3\t3\t3\t83\t783\t783\t3783\t8783\t166\t167\tVZAAAA\tQJEAAA\tOOOOxx\n1453\t2955\t1\t1\t3\t13\t53\t453\t1453\t1453\t1453\t106\t107\tXDAAAA\tRJEAAA\tVVVVxx\n3908\t2956\t0\t0\t8\t8\t8\t908\t1908\t3908\t3908\t16\t17\tIUAAAA\tSJEAAA\tAAAAxx\n7707\t2957\t1\t3\t7\t7\t7\t707\t1707\t2707\t7707\t14\t15\tLKAAAA\tTJEAAA\tHHHHxx\n9049\t2958\t1\t1\t9\t9\t49\t49\t1049\t4049\t9049\t98\t99\tBKAAAA\tUJEAAA\tOOOOxx\n654\t2959\t0\t2\t4\t14\t54\t654\t654\t654\t654\t108\t109\tEZAAAA\tVJEAAA\tVVVVxx\n3336\t2960\t0\t0\t6\t16\t36\t336\t1336\t3336\t3336\t72\t73\tIYAAAA\tWJEAAA\tAAAAxx\n622\t2961\t0\t2\t2\t2\t22\t622\t622\t622\t622\t44\t45\tYXAAAA\tXJEAAA\tHHHHxx\n8398\t2962\t0\t2\t8\t18\t98\t398\t398\t3398\t8398\t196\t197\tALAAAA\tYJEAAA\tOOOOxx\n9193\t2963\t1\t1\t3\t13\t93\t193\t1193\t4193\t9193\t186\t187\tPPAAAA\tZJEAAA\tVVVVxx\n7896\t2964\t0\t0\t6\t16\t96\t896\t1896\t2896\t7896\t192\t193\tSRAAAA\tAKEAAA\tAAAAxx\n9798\t2965\t0\t2\t8\t18\t98\t798\t1798\t4798\t9798\t196\t197\tWMAAAA\tBKEAAA\tHHHHxx\n2881\t2966\t1\t1\t1\t1\t81\t881\t881\t2881\t2881\t162\t163\tVGAAAA\tCKEAAA\tOOOOxx\n672\t2967\t0\t0\t2\t12\t72\t672\t672\t672\t672\t144\t145\tWZAAAA\tDKEAAA\tVVVVxx\n6743\t2968\t1\t3\t3\t3\t43\t743\t743\t1743\t6743\t86\t87\tJZAAAA\tEKEAAA\tAAAAxx\n8935\t2969\t1\t3\t5\t15\t35\t935\t935\t3935\t8935\t70\t71\tRFAAAA\tFKEAAA\tHHHHxx\n2426\t2970\t0\t2\t6\t6\t26\t426\t426\t2426\t2426\t52\t53\tIPAAAA\tGKEAAA\tOOOOxx\n722\t2971\t0\t2\t2\t2\t22\t722\t722\t722\t722\t44\t45\tUBAAAA\tHKEAAA\tVVVVxx\n5088\t2972\t0\t0\t8\t8\t88\t88\t1088\t88\t5088\t176\t177\tSNAAAA\tIKEAAA\tAAAAxx\n8677\t2973\t1\t1\t7\t17\t77\t677\t677\t3677\t8677\t154\t155\tTVAAAA\tJKEAAA\tHHHHxx\n6963\t2974\t1\t3\t3\t3\t63\t963\t963\t1963\t6963\t126\t127\tVHAAAA\tKKEAAA\tOOOOxx\n1653\t2975\t1\t1\t3\t13\t53\t653\t1653\t1653\t1653\t106\t107\tPLAAAA\tLKEAAA\tVVVVxx\n7295\t2976\t1\t3\t5\t15\t95\t295\t1295\t2295\t7295\t190\t191\tPUAAAA\tMKEAAA\tAAAAxx\n6675\t2977\t1\t3\t5\t15\t75\t675\t675\t1675\t6675\t150\t151\tTWAAAA\tNKEAAA\tHHHHxx\n7183\t2978\t1\t3\t3\t3\t83\t183\t1183\t2183\t7183\t166\t167\tHQAAAA\tOKEAAA\tOOOOxx\n4378\t2979\t0\t2\t8\t18\t78\t378\t378\t4378\t4378\t156\t157\tKMAAAA\tPKEAAA\tVVVVxx\n2157\t2980\t1\t1\t7\t17\t57\t157\t157\t2157\t2157\t114\t115\tZEAAAA\tQKEAAA\tAAAAxx\n2621\t2981\t1\t1\t1\t1\t21\t621\t621\t2621\t2621\t42\t43\tVWAAAA\tRKEAAA\tHHHHxx\n9278\t2982\t0\t2\t8\t18\t78\t278\t1278\t4278\t9278\t156\t157\tWSAAAA\tSKEAAA\tOOOOxx\n79\t2983\t1\t3\t9\t19\t79\t79\t79\t79\t79\t158\t159\tBDAAAA\tTKEAAA\tVVVVxx\n7358\t2984\t0\t2\t8\t18\t58\t358\t1358\t2358\t7358\t116\t117\tAXAAAA\tUKEAAA\tAAAAxx\n3589\t2985\t1\t1\t9\t9\t89\t589\t1589\t3589\t3589\t178\t179\tBIAAAA\tVKEAAA\tHHHHxx\n1254\t2986\t0\t2\t4\t14\t54\t254\t1254\t1254\t1254\t108\t109\tGWAAAA\tWKEAAA\tOOOOxx\n3490\t2987\t0\t2\t0\t10\t90\t490\t1490\t3490\t3490\t180\t181\tGEAAAA\tXKEAAA\tVVVVxx\n7533\t2988\t1\t1\t3\t13\t33\t533\t1533\t2533\t7533\t66\t67\tTDAAAA\tYKEAAA\tAAAAxx\n2800\t2989\t0\t0\t0\t0\t0\t800\t800\t2800\t2800\t0\t1\tSDAAAA\tZKEAAA\tHHHHxx\n351\t2990\t1\t3\t1\t11\t51\t351\t351\t351\t351\t102\t103\tNNAAAA\tALEAAA\tOOOOxx\n4359\t2991\t1\t3\t9\t19\t59\t359\t359\t4359\t4359\t118\t119\tRLAAAA\tBLEAAA\tVVVVxx\n5788\t2992\t0\t0\t8\t8\t88\t788\t1788\t788\t5788\t176\t177\tQOAAAA\tCLEAAA\tAAAAxx\n5521\t2993\t1\t1\t1\t1\t21\t521\t1521\t521\t5521\t42\t43\tJEAAAA\tDLEAAA\tHHHHxx\n3351\t2994\t1\t3\t1\t11\t51\t351\t1351\t3351\t3351\t102\t103\tXYAAAA\tELEAAA\tOOOOxx\n5129\t2995\t1\t1\t9\t9\t29\t129\t1129\t129\t5129\t58\t59\tHPAAAA\tFLEAAA\tVVVVxx\n315\t2996\t1\t3\t5\t15\t15\t315\t315\t315\t315\t30\t31\tDMAAAA\tGLEAAA\tAAAAxx\n7552\t2997\t0\t0\t2\t12\t52\t552\t1552\t2552\t7552\t104\t105\tMEAAAA\tHLEAAA\tHHHHxx\n9176\t2998\t0\t0\t6\t16\t76\t176\t1176\t4176\t9176\t152\t153\tYOAAAA\tILEAAA\tOOOOxx\n7458\t2999\t0\t2\t8\t18\t58\t458\t1458\t2458\t7458\t116\t117\tWAAAAA\tJLEAAA\tVVVVxx\n279\t3000\t1\t3\t9\t19\t79\t279\t279\t279\t279\t158\t159\tTKAAAA\tKLEAAA\tAAAAxx\n738\t3001\t0\t2\t8\t18\t38\t738\t738\t738\t738\t76\t77\tKCAAAA\tLLEAAA\tHHHHxx\n2557\t3002\t1\t1\t7\t17\t57\t557\t557\t2557\t2557\t114\t115\tJUAAAA\tMLEAAA\tOOOOxx\n9395\t3003\t1\t3\t5\t15\t95\t395\t1395\t4395\t9395\t190\t191\tJXAAAA\tNLEAAA\tVVVVxx\n7214\t3004\t0\t2\t4\t14\t14\t214\t1214\t2214\t7214\t28\t29\tMRAAAA\tOLEAAA\tAAAAxx\n6354\t3005\t0\t2\t4\t14\t54\t354\t354\t1354\t6354\t108\t109\tKKAAAA\tPLEAAA\tHHHHxx\n4799\t3006\t1\t3\t9\t19\t99\t799\t799\t4799\t4799\t198\t199\tPCAAAA\tQLEAAA\tOOOOxx\n1231\t3007\t1\t3\t1\t11\t31\t231\t1231\t1231\t1231\t62\t63\tJVAAAA\tRLEAAA\tVVVVxx\n5252\t3008\t0\t0\t2\t12\t52\t252\t1252\t252\t5252\t104\t105\tAUAAAA\tSLEAAA\tAAAAxx\n5250\t3009\t0\t2\t0\t10\t50\t250\t1250\t250\t5250\t100\t101\tYTAAAA\tTLEAAA\tHHHHxx\n9319\t3010\t1\t3\t9\t19\t19\t319\t1319\t4319\t9319\t38\t39\tLUAAAA\tULEAAA\tOOOOxx\n1724\t3011\t0\t0\t4\t4\t24\t724\t1724\t1724\t1724\t48\t49\tIOAAAA\tVLEAAA\tVVVVxx\n7947\t3012\t1\t3\t7\t7\t47\t947\t1947\t2947\t7947\t94\t95\tRTAAAA\tWLEAAA\tAAAAxx\n1105\t3013\t1\t1\t5\t5\t5\t105\t1105\t1105\t1105\t10\t11\tNQAAAA\tXLEAAA\tHHHHxx\n1417\t3014\t1\t1\t7\t17\t17\t417\t1417\t1417\t1417\t34\t35\tNCAAAA\tYLEAAA\tOOOOxx\n7101\t3015\t1\t1\t1\t1\t1\t101\t1101\t2101\t7101\t2\t3\tDNAAAA\tZLEAAA\tVVVVxx\n1088\t3016\t0\t0\t8\t8\t88\t88\t1088\t1088\t1088\t176\t177\tWPAAAA\tAMEAAA\tAAAAxx\n979\t3017\t1\t3\t9\t19\t79\t979\t979\t979\t979\t158\t159\tRLAAAA\tBMEAAA\tHHHHxx\n7589\t3018\t1\t1\t9\t9\t89\t589\t1589\t2589\t7589\t178\t179\tXFAAAA\tCMEAAA\tOOOOxx\n8952\t3019\t0\t0\t2\t12\t52\t952\t952\t3952\t8952\t104\t105\tIGAAAA\tDMEAAA\tVVVVxx\n2864\t3020\t0\t0\t4\t4\t64\t864\t864\t2864\t2864\t128\t129\tEGAAAA\tEMEAAA\tAAAAxx\n234\t3021\t0\t2\t4\t14\t34\t234\t234\t234\t234\t68\t69\tAJAAAA\tFMEAAA\tHHHHxx\n7231\t3022\t1\t3\t1\t11\t31\t231\t1231\t2231\t7231\t62\t63\tDSAAAA\tGMEAAA\tOOOOxx\n6792\t3023\t0\t0\t2\t12\t92\t792\t792\t1792\t6792\t184\t185\tGBAAAA\tHMEAAA\tVVVVxx\n4311\t3024\t1\t3\t1\t11\t11\t311\t311\t4311\t4311\t22\t23\tVJAAAA\tIMEAAA\tAAAAxx\n3374\t3025\t0\t2\t4\t14\t74\t374\t1374\t3374\t3374\t148\t149\tUZAAAA\tJMEAAA\tHHHHxx\n3367\t3026\t1\t3\t7\t7\t67\t367\t1367\t3367\t3367\t134\t135\tNZAAAA\tKMEAAA\tOOOOxx\n2598\t3027\t0\t2\t8\t18\t98\t598\t598\t2598\t2598\t196\t197\tYVAAAA\tLMEAAA\tVVVVxx\n1033\t3028\t1\t1\t3\t13\t33\t33\t1033\t1033\t1033\t66\t67\tTNAAAA\tMMEAAA\tAAAAxx\n7803\t3029\t1\t3\t3\t3\t3\t803\t1803\t2803\t7803\t6\t7\tDOAAAA\tNMEAAA\tHHHHxx\n3870\t3030\t0\t2\t0\t10\t70\t870\t1870\t3870\t3870\t140\t141\tWSAAAA\tOMEAAA\tOOOOxx\n4962\t3031\t0\t2\t2\t2\t62\t962\t962\t4962\t4962\t124\t125\tWIAAAA\tPMEAAA\tVVVVxx\n4842\t3032\t0\t2\t2\t2\t42\t842\t842\t4842\t4842\t84\t85\tGEAAAA\tQMEAAA\tAAAAxx\n8814\t3033\t0\t2\t4\t14\t14\t814\t814\t3814\t8814\t28\t29\tABAAAA\tRMEAAA\tHHHHxx\n3429\t3034\t1\t1\t9\t9\t29\t429\t1429\t3429\t3429\t58\t59\tXBAAAA\tSMEAAA\tOOOOxx\n6550\t3035\t0\t2\t0\t10\t50\t550\t550\t1550\t6550\t100\t101\tYRAAAA\tTMEAAA\tVVVVxx\n6317\t3036\t1\t1\t7\t17\t17\t317\t317\t1317\t6317\t34\t35\tZIAAAA\tUMEAAA\tAAAAxx\n5023\t3037\t1\t3\t3\t3\t23\t23\t1023\t23\t5023\t46\t47\tFLAAAA\tVMEAAA\tHHHHxx\n5825\t3038\t1\t1\t5\t5\t25\t825\t1825\t825\t5825\t50\t51\tBQAAAA\tWMEAAA\tOOOOxx\n5297\t3039\t1\t1\t7\t17\t97\t297\t1297\t297\t5297\t194\t195\tTVAAAA\tXMEAAA\tVVVVxx\n8764\t3040\t0\t0\t4\t4\t64\t764\t764\t3764\t8764\t128\t129\tCZAAAA\tYMEAAA\tAAAAxx\n5084\t3041\t0\t0\t4\t4\t84\t84\t1084\t84\t5084\t168\t169\tONAAAA\tZMEAAA\tHHHHxx\n6808\t3042\t0\t0\t8\t8\t8\t808\t808\t1808\t6808\t16\t17\tWBAAAA\tANEAAA\tOOOOxx\n1780\t3043\t0\t0\t0\t0\t80\t780\t1780\t1780\t1780\t160\t161\tMQAAAA\tBNEAAA\tVVVVxx\n4092\t3044\t0\t0\t2\t12\t92\t92\t92\t4092\t4092\t184\t185\tKBAAAA\tCNEAAA\tAAAAxx\n3618\t3045\t0\t2\t8\t18\t18\t618\t1618\t3618\t3618\t36\t37\tEJAAAA\tDNEAAA\tHHHHxx\n7299\t3046\t1\t3\t9\t19\t99\t299\t1299\t2299\t7299\t198\t199\tTUAAAA\tENEAAA\tOOOOxx\n8544\t3047\t0\t0\t4\t4\t44\t544\t544\t3544\t8544\t88\t89\tQQAAAA\tFNEAAA\tVVVVxx\n2359\t3048\t1\t3\t9\t19\t59\t359\t359\t2359\t2359\t118\t119\tTMAAAA\tGNEAAA\tAAAAxx\n1939\t3049\t1\t3\t9\t19\t39\t939\t1939\t1939\t1939\t78\t79\tPWAAAA\tHNEAAA\tHHHHxx\n5834\t3050\t0\t2\t4\t14\t34\t834\t1834\t834\t5834\t68\t69\tKQAAAA\tINEAAA\tOOOOxx\n1997\t3051\t1\t1\t7\t17\t97\t997\t1997\t1997\t1997\t194\t195\tVYAAAA\tJNEAAA\tVVVVxx\n7917\t3052\t1\t1\t7\t17\t17\t917\t1917\t2917\t7917\t34\t35\tNSAAAA\tKNEAAA\tAAAAxx\n2098\t3053\t0\t2\t8\t18\t98\t98\t98\t2098\t2098\t196\t197\tSCAAAA\tLNEAAA\tHHHHxx\n7576\t3054\t0\t0\t6\t16\t76\t576\t1576\t2576\t7576\t152\t153\tKFAAAA\tMNEAAA\tOOOOxx\n376\t3055\t0\t0\t6\t16\t76\t376\t376\t376\t376\t152\t153\tMOAAAA\tNNEAAA\tVVVVxx\n8535\t3056\t1\t3\t5\t15\t35\t535\t535\t3535\t8535\t70\t71\tHQAAAA\tONEAAA\tAAAAxx\n5659\t3057\t1\t3\t9\t19\t59\t659\t1659\t659\t5659\t118\t119\tRJAAAA\tPNEAAA\tHHHHxx\n2786\t3058\t0\t2\t6\t6\t86\t786\t786\t2786\t2786\t172\t173\tEDAAAA\tQNEAAA\tOOOOxx\n8820\t3059\t0\t0\t0\t0\t20\t820\t820\t3820\t8820\t40\t41\tGBAAAA\tRNEAAA\tVVVVxx\n1229\t3060\t1\t1\t9\t9\t29\t229\t1229\t1229\t1229\t58\t59\tHVAAAA\tSNEAAA\tAAAAxx\n9321\t3061\t1\t1\t1\t1\t21\t321\t1321\t4321\t9321\t42\t43\tNUAAAA\tTNEAAA\tHHHHxx\n7662\t3062\t0\t2\t2\t2\t62\t662\t1662\t2662\t7662\t124\t125\tSIAAAA\tUNEAAA\tOOOOxx\n5535\t3063\t1\t3\t5\t15\t35\t535\t1535\t535\t5535\t70\t71\tXEAAAA\tVNEAAA\tVVVVxx\n4889\t3064\t1\t1\t9\t9\t89\t889\t889\t4889\t4889\t178\t179\tBGAAAA\tWNEAAA\tAAAAxx\n8259\t3065\t1\t3\t9\t19\t59\t259\t259\t3259\t8259\t118\t119\tRFAAAA\tXNEAAA\tHHHHxx\n6789\t3066\t1\t1\t9\t9\t89\t789\t789\t1789\t6789\t178\t179\tDBAAAA\tYNEAAA\tOOOOxx\n5411\t3067\t1\t3\t1\t11\t11\t411\t1411\t411\t5411\t22\t23\tDAAAAA\tZNEAAA\tVVVVxx\n6992\t3068\t0\t0\t2\t12\t92\t992\t992\t1992\t6992\t184\t185\tYIAAAA\tAOEAAA\tAAAAxx\n7698\t3069\t0\t2\t8\t18\t98\t698\t1698\t2698\t7698\t196\t197\tCKAAAA\tBOEAAA\tHHHHxx\n2342\t3070\t0\t2\t2\t2\t42\t342\t342\t2342\t2342\t84\t85\tCMAAAA\tCOEAAA\tOOOOxx\n1501\t3071\t1\t1\t1\t1\t1\t501\t1501\t1501\t1501\t2\t3\tTFAAAA\tDOEAAA\tVVVVxx\n6322\t3072\t0\t2\t2\t2\t22\t322\t322\t1322\t6322\t44\t45\tEJAAAA\tEOEAAA\tAAAAxx\n9861\t3073\t1\t1\t1\t1\t61\t861\t1861\t4861\t9861\t122\t123\tHPAAAA\tFOEAAA\tHHHHxx\n9802\t3074\t0\t2\t2\t2\t2\t802\t1802\t4802\t9802\t4\t5\tANAAAA\tGOEAAA\tOOOOxx\n4750\t3075\t0\t2\t0\t10\t50\t750\t750\t4750\t4750\t100\t101\tSAAAAA\tHOEAAA\tVVVVxx\n5855\t3076\t1\t3\t5\t15\t55\t855\t1855\t855\t5855\t110\t111\tFRAAAA\tIOEAAA\tAAAAxx\n4304\t3077\t0\t0\t4\t4\t4\t304\t304\t4304\t4304\t8\t9\tOJAAAA\tJOEAAA\tHHHHxx\n2605\t3078\t1\t1\t5\t5\t5\t605\t605\t2605\t2605\t10\t11\tFWAAAA\tKOEAAA\tOOOOxx\n1802\t3079\t0\t2\t2\t2\t2\t802\t1802\t1802\t1802\t4\t5\tIRAAAA\tLOEAAA\tVVVVxx\n9368\t3080\t0\t0\t8\t8\t68\t368\t1368\t4368\t9368\t136\t137\tIWAAAA\tMOEAAA\tAAAAxx\n7107\t3081\t1\t3\t7\t7\t7\t107\t1107\t2107\t7107\t14\t15\tJNAAAA\tNOEAAA\tHHHHxx\n8895\t3082\t1\t3\t5\t15\t95\t895\t895\t3895\t8895\t190\t191\tDEAAAA\tOOEAAA\tOOOOxx\n3750\t3083\t0\t2\t0\t10\t50\t750\t1750\t3750\t3750\t100\t101\tGOAAAA\tPOEAAA\tVVVVxx\n8934\t3084\t0\t2\t4\t14\t34\t934\t934\t3934\t8934\t68\t69\tQFAAAA\tQOEAAA\tAAAAxx\n9464\t3085\t0\t0\t4\t4\t64\t464\t1464\t4464\t9464\t128\t129\tAAAAAA\tROEAAA\tHHHHxx\n1928\t3086\t0\t0\t8\t8\t28\t928\t1928\t1928\t1928\t56\t57\tEWAAAA\tSOEAAA\tOOOOxx\n3196\t3087\t0\t0\t6\t16\t96\t196\t1196\t3196\t3196\t192\t193\tYSAAAA\tTOEAAA\tVVVVxx\n5256\t3088\t0\t0\t6\t16\t56\t256\t1256\t256\t5256\t112\t113\tEUAAAA\tUOEAAA\tAAAAxx\n7119\t3089\t1\t3\t9\t19\t19\t119\t1119\t2119\t7119\t38\t39\tVNAAAA\tVOEAAA\tHHHHxx\n4495\t3090\t1\t3\t5\t15\t95\t495\t495\t4495\t4495\t190\t191\tXQAAAA\tWOEAAA\tOOOOxx\n9292\t3091\t0\t0\t2\t12\t92\t292\t1292\t4292\t9292\t184\t185\tKTAAAA\tXOEAAA\tVVVVxx\n1617\t3092\t1\t1\t7\t17\t17\t617\t1617\t1617\t1617\t34\t35\tFKAAAA\tYOEAAA\tAAAAxx\n481\t3093\t1\t1\t1\t1\t81\t481\t481\t481\t481\t162\t163\tNSAAAA\tZOEAAA\tHHHHxx\n56\t3094\t0\t0\t6\t16\t56\t56\t56\t56\t56\t112\t113\tECAAAA\tAPEAAA\tOOOOxx\n9120\t3095\t0\t0\t0\t0\t20\t120\t1120\t4120\t9120\t40\t41\tUMAAAA\tBPEAAA\tVVVVxx\n1306\t3096\t0\t2\t6\t6\t6\t306\t1306\t1306\t1306\t12\t13\tGYAAAA\tCPEAAA\tAAAAxx\n7773\t3097\t1\t1\t3\t13\t73\t773\t1773\t2773\t7773\t146\t147\tZMAAAA\tDPEAAA\tHHHHxx\n4863\t3098\t1\t3\t3\t3\t63\t863\t863\t4863\t4863\t126\t127\tBFAAAA\tEPEAAA\tOOOOxx\n1114\t3099\t0\t2\t4\t14\t14\t114\t1114\t1114\t1114\t28\t29\tWQAAAA\tFPEAAA\tVVVVxx\n8124\t3100\t0\t0\t4\t4\t24\t124\t124\t3124\t8124\t48\t49\tMAAAAA\tGPEAAA\tAAAAxx\n6254\t3101\t0\t2\t4\t14\t54\t254\t254\t1254\t6254\t108\t109\tOGAAAA\tHPEAAA\tHHHHxx\n8109\t3102\t1\t1\t9\t9\t9\t109\t109\t3109\t8109\t18\t19\tXZAAAA\tIPEAAA\tOOOOxx\n1747\t3103\t1\t3\t7\t7\t47\t747\t1747\t1747\t1747\t94\t95\tFPAAAA\tJPEAAA\tVVVVxx\n6185\t3104\t1\t1\t5\t5\t85\t185\t185\t1185\t6185\t170\t171\tXDAAAA\tKPEAAA\tAAAAxx\n3388\t3105\t0\t0\t8\t8\t88\t388\t1388\t3388\t3388\t176\t177\tIAAAAA\tLPEAAA\tHHHHxx\n4905\t3106\t1\t1\t5\t5\t5\t905\t905\t4905\t4905\t10\t11\tRGAAAA\tMPEAAA\tOOOOxx\n5728\t3107\t0\t0\t8\t8\t28\t728\t1728\t728\t5728\t56\t57\tIMAAAA\tNPEAAA\tVVVVxx\n7507\t3108\t1\t3\t7\t7\t7\t507\t1507\t2507\t7507\t14\t15\tTCAAAA\tOPEAAA\tAAAAxx\n5662\t3109\t0\t2\t2\t2\t62\t662\t1662\t662\t5662\t124\t125\tUJAAAA\tPPEAAA\tHHHHxx\n1686\t3110\t0\t2\t6\t6\t86\t686\t1686\t1686\t1686\t172\t173\tWMAAAA\tQPEAAA\tOOOOxx\n5202\t3111\t0\t2\t2\t2\t2\t202\t1202\t202\t5202\t4\t5\tCSAAAA\tRPEAAA\tVVVVxx\n6905\t3112\t1\t1\t5\t5\t5\t905\t905\t1905\t6905\t10\t11\tPFAAAA\tSPEAAA\tAAAAxx\n9577\t3113\t1\t1\t7\t17\t77\t577\t1577\t4577\t9577\t154\t155\tJEAAAA\tTPEAAA\tHHHHxx\n7194\t3114\t0\t2\t4\t14\t94\t194\t1194\t2194\t7194\t188\t189\tSQAAAA\tUPEAAA\tOOOOxx\n7016\t3115\t0\t0\t6\t16\t16\t16\t1016\t2016\t7016\t32\t33\tWJAAAA\tVPEAAA\tVVVVxx\n8905\t3116\t1\t1\t5\t5\t5\t905\t905\t3905\t8905\t10\t11\tNEAAAA\tWPEAAA\tAAAAxx\n3419\t3117\t1\t3\t9\t19\t19\t419\t1419\t3419\t3419\t38\t39\tNBAAAA\tXPEAAA\tHHHHxx\n6881\t3118\t1\t1\t1\t1\t81\t881\t881\t1881\t6881\t162\t163\tREAAAA\tYPEAAA\tOOOOxx\n8370\t3119\t0\t2\t0\t10\t70\t370\t370\t3370\t8370\t140\t141\tYJAAAA\tZPEAAA\tVVVVxx\n6117\t3120\t1\t1\t7\t17\t17\t117\t117\t1117\t6117\t34\t35\tHBAAAA\tAQEAAA\tAAAAxx\n1636\t3121\t0\t0\t6\t16\t36\t636\t1636\t1636\t1636\t72\t73\tYKAAAA\tBQEAAA\tHHHHxx\n6857\t3122\t1\t1\t7\t17\t57\t857\t857\t1857\t6857\t114\t115\tTDAAAA\tCQEAAA\tOOOOxx\n7163\t3123\t1\t3\t3\t3\t63\t163\t1163\t2163\t7163\t126\t127\tNPAAAA\tDQEAAA\tVVVVxx\n5040\t3124\t0\t0\t0\t0\t40\t40\t1040\t40\t5040\t80\t81\tWLAAAA\tEQEAAA\tAAAAxx\n6263\t3125\t1\t3\t3\t3\t63\t263\t263\t1263\t6263\t126\t127\tXGAAAA\tFQEAAA\tHHHHxx\n4809\t3126\t1\t1\t9\t9\t9\t809\t809\t4809\t4809\t18\t19\tZCAAAA\tGQEAAA\tOOOOxx\n900\t3127\t0\t0\t0\t0\t0\t900\t900\t900\t900\t0\t1\tQIAAAA\tHQEAAA\tVVVVxx\n3199\t3128\t1\t3\t9\t19\t99\t199\t1199\t3199\t3199\t198\t199\tBTAAAA\tIQEAAA\tAAAAxx\n4156\t3129\t0\t0\t6\t16\t56\t156\t156\t4156\t4156\t112\t113\tWDAAAA\tJQEAAA\tHHHHxx\n3501\t3130\t1\t1\t1\t1\t1\t501\t1501\t3501\t3501\t2\t3\tREAAAA\tKQEAAA\tOOOOxx\n164\t3131\t0\t0\t4\t4\t64\t164\t164\t164\t164\t128\t129\tIGAAAA\tLQEAAA\tVVVVxx\n9548\t3132\t0\t0\t8\t8\t48\t548\t1548\t4548\t9548\t96\t97\tGDAAAA\tMQEAAA\tAAAAxx\n1149\t3133\t1\t1\t9\t9\t49\t149\t1149\t1149\t1149\t98\t99\tFSAAAA\tNQEAAA\tHHHHxx\n1962\t3134\t0\t2\t2\t2\t62\t962\t1962\t1962\t1962\t124\t125\tMXAAAA\tOQEAAA\tOOOOxx\n4072\t3135\t0\t0\t2\t12\t72\t72\t72\t4072\t4072\t144\t145\tQAAAAA\tPQEAAA\tVVVVxx\n4280\t3136\t0\t0\t0\t0\t80\t280\t280\t4280\t4280\t160\t161\tQIAAAA\tQQEAAA\tAAAAxx\n1398\t3137\t0\t2\t8\t18\t98\t398\t1398\t1398\t1398\t196\t197\tUBAAAA\tRQEAAA\tHHHHxx\n725\t3138\t1\t1\t5\t5\t25\t725\t725\t725\t725\t50\t51\tXBAAAA\tSQEAAA\tOOOOxx\n3988\t3139\t0\t0\t8\t8\t88\t988\t1988\t3988\t3988\t176\t177\tKXAAAA\tTQEAAA\tVVVVxx\n5059\t3140\t1\t3\t9\t19\t59\t59\t1059\t59\t5059\t118\t119\tPMAAAA\tUQEAAA\tAAAAxx\n2632\t3141\t0\t0\t2\t12\t32\t632\t632\t2632\t2632\t64\t65\tGXAAAA\tVQEAAA\tHHHHxx\n1909\t3142\t1\t1\t9\t9\t9\t909\t1909\t1909\t1909\t18\t19\tLVAAAA\tWQEAAA\tOOOOxx\n6827\t3143\t1\t3\t7\t7\t27\t827\t827\t1827\t6827\t54\t55\tPCAAAA\tXQEAAA\tVVVVxx\n8156\t3144\t0\t0\t6\t16\t56\t156\t156\t3156\t8156\t112\t113\tSBAAAA\tYQEAAA\tAAAAxx\n1192\t3145\t0\t0\t2\t12\t92\t192\t1192\t1192\t1192\t184\t185\tWTAAAA\tZQEAAA\tHHHHxx\n9545\t3146\t1\t1\t5\t5\t45\t545\t1545\t4545\t9545\t90\t91\tDDAAAA\tAREAAA\tOOOOxx\n2249\t3147\t1\t1\t9\t9\t49\t249\t249\t2249\t2249\t98\t99\tNIAAAA\tBREAAA\tVVVVxx\n5580\t3148\t0\t0\t0\t0\t80\t580\t1580\t580\t5580\t160\t161\tQGAAAA\tCREAAA\tAAAAxx\n8403\t3149\t1\t3\t3\t3\t3\t403\t403\t3403\t8403\t6\t7\tFLAAAA\tDREAAA\tHHHHxx\n4024\t3150\t0\t0\t4\t4\t24\t24\t24\t4024\t4024\t48\t49\tUYAAAA\tEREAAA\tOOOOxx\n1866\t3151\t0\t2\t6\t6\t66\t866\t1866\t1866\t1866\t132\t133\tUTAAAA\tFREAAA\tVVVVxx\n9251\t3152\t1\t3\t1\t11\t51\t251\t1251\t4251\t9251\t102\t103\tVRAAAA\tGREAAA\tAAAAxx\n9979\t3153\t1\t3\t9\t19\t79\t979\t1979\t4979\t9979\t158\t159\tVTAAAA\tHREAAA\tHHHHxx\n9899\t3154\t1\t3\t9\t19\t99\t899\t1899\t4899\t9899\t198\t199\tTQAAAA\tIREAAA\tOOOOxx\n2540\t3155\t0\t0\t0\t0\t40\t540\t540\t2540\t2540\t80\t81\tSTAAAA\tJREAAA\tVVVVxx\n8957\t3156\t1\t1\t7\t17\t57\t957\t957\t3957\t8957\t114\t115\tNGAAAA\tKREAAA\tAAAAxx\n7702\t3157\t0\t2\t2\t2\t2\t702\t1702\t2702\t7702\t4\t5\tGKAAAA\tLREAAA\tHHHHxx\n4211\t3158\t1\t3\t1\t11\t11\t211\t211\t4211\t4211\t22\t23\tZFAAAA\tMREAAA\tOOOOxx\n6684\t3159\t0\t0\t4\t4\t84\t684\t684\t1684\t6684\t168\t169\tCXAAAA\tNREAAA\tVVVVxx\n3883\t3160\t1\t3\t3\t3\t83\t883\t1883\t3883\t3883\t166\t167\tJTAAAA\tOREAAA\tAAAAxx\n3531\t3161\t1\t3\t1\t11\t31\t531\t1531\t3531\t3531\t62\t63\tVFAAAA\tPREAAA\tHHHHxx\n9178\t3162\t0\t2\t8\t18\t78\t178\t1178\t4178\t9178\t156\t157\tAPAAAA\tQREAAA\tOOOOxx\n3389\t3163\t1\t1\t9\t9\t89\t389\t1389\t3389\t3389\t178\t179\tJAAAAA\tRREAAA\tVVVVxx\n7874\t3164\t0\t2\t4\t14\t74\t874\t1874\t2874\t7874\t148\t149\tWQAAAA\tSREAAA\tAAAAxx\n4522\t3165\t0\t2\t2\t2\t22\t522\t522\t4522\t4522\t44\t45\tYRAAAA\tTREAAA\tHHHHxx\n9399\t3166\t1\t3\t9\t19\t99\t399\t1399\t4399\t9399\t198\t199\tNXAAAA\tUREAAA\tOOOOxx\n9083\t3167\t1\t3\t3\t3\t83\t83\t1083\t4083\t9083\t166\t167\tJLAAAA\tVREAAA\tVVVVxx\n1530\t3168\t0\t2\t0\t10\t30\t530\t1530\t1530\t1530\t60\t61\tWGAAAA\tWREAAA\tAAAAxx\n2360\t3169\t0\t0\t0\t0\t60\t360\t360\t2360\t2360\t120\t121\tUMAAAA\tXREAAA\tHHHHxx\n4908\t3170\t0\t0\t8\t8\t8\t908\t908\t4908\t4908\t16\t17\tUGAAAA\tYREAAA\tOOOOxx\n4628\t3171\t0\t0\t8\t8\t28\t628\t628\t4628\t4628\t56\t57\tAWAAAA\tZREAAA\tVVVVxx\n3889\t3172\t1\t1\t9\t9\t89\t889\t1889\t3889\t3889\t178\t179\tPTAAAA\tASEAAA\tAAAAxx\n1331\t3173\t1\t3\t1\t11\t31\t331\t1331\t1331\t1331\t62\t63\tFZAAAA\tBSEAAA\tHHHHxx\n1942\t3174\t0\t2\t2\t2\t42\t942\t1942\t1942\t1942\t84\t85\tSWAAAA\tCSEAAA\tOOOOxx\n4734\t3175\t0\t2\t4\t14\t34\t734\t734\t4734\t4734\t68\t69\tCAAAAA\tDSEAAA\tVVVVxx\n8386\t3176\t0\t2\t6\t6\t86\t386\t386\t3386\t8386\t172\t173\tOKAAAA\tESEAAA\tAAAAxx\n3586\t3177\t0\t2\t6\t6\t86\t586\t1586\t3586\t3586\t172\t173\tYHAAAA\tFSEAAA\tHHHHxx\n2354\t3178\t0\t2\t4\t14\t54\t354\t354\t2354\t2354\t108\t109\tOMAAAA\tGSEAAA\tOOOOxx\n7108\t3179\t0\t0\t8\t8\t8\t108\t1108\t2108\t7108\t16\t17\tKNAAAA\tHSEAAA\tVVVVxx\n1857\t3180\t1\t1\t7\t17\t57\t857\t1857\t1857\t1857\t114\t115\tLTAAAA\tISEAAA\tAAAAxx\n2544\t3181\t0\t0\t4\t4\t44\t544\t544\t2544\t2544\t88\t89\tWTAAAA\tJSEAAA\tHHHHxx\n819\t3182\t1\t3\t9\t19\t19\t819\t819\t819\t819\t38\t39\tNFAAAA\tKSEAAA\tOOOOxx\n2878\t3183\t0\t2\t8\t18\t78\t878\t878\t2878\t2878\t156\t157\tSGAAAA\tLSEAAA\tVVVVxx\n1772\t3184\t0\t0\t2\t12\t72\t772\t1772\t1772\t1772\t144\t145\tEQAAAA\tMSEAAA\tAAAAxx\n354\t3185\t0\t2\t4\t14\t54\t354\t354\t354\t354\t108\t109\tQNAAAA\tNSEAAA\tHHHHxx\n3259\t3186\t1\t3\t9\t19\t59\t259\t1259\t3259\t3259\t118\t119\tJVAAAA\tOSEAAA\tOOOOxx\n2170\t3187\t0\t2\t0\t10\t70\t170\t170\t2170\t2170\t140\t141\tMFAAAA\tPSEAAA\tVVVVxx\n1190\t3188\t0\t2\t0\t10\t90\t190\t1190\t1190\t1190\t180\t181\tUTAAAA\tQSEAAA\tAAAAxx\n3607\t3189\t1\t3\t7\t7\t7\t607\t1607\t3607\t3607\t14\t15\tTIAAAA\tRSEAAA\tHHHHxx\n4661\t3190\t1\t1\t1\t1\t61\t661\t661\t4661\t4661\t122\t123\tHXAAAA\tSSEAAA\tOOOOxx\n1796\t3191\t0\t0\t6\t16\t96\t796\t1796\t1796\t1796\t192\t193\tCRAAAA\tTSEAAA\tVVVVxx\n1561\t3192\t1\t1\t1\t1\t61\t561\t1561\t1561\t1561\t122\t123\tBIAAAA\tUSEAAA\tAAAAxx\n4336\t3193\t0\t0\t6\t16\t36\t336\t336\t4336\t4336\t72\t73\tUKAAAA\tVSEAAA\tHHHHxx\n7550\t3194\t0\t2\t0\t10\t50\t550\t1550\t2550\t7550\t100\t101\tKEAAAA\tWSEAAA\tOOOOxx\n3238\t3195\t0\t2\t8\t18\t38\t238\t1238\t3238\t3238\t76\t77\tOUAAAA\tXSEAAA\tVVVVxx\n9870\t3196\t0\t2\t0\t10\t70\t870\t1870\t4870\t9870\t140\t141\tQPAAAA\tYSEAAA\tAAAAxx\n6502\t3197\t0\t2\t2\t2\t2\t502\t502\t1502\t6502\t4\t5\tCQAAAA\tZSEAAA\tHHHHxx\n3903\t3198\t1\t3\t3\t3\t3\t903\t1903\t3903\t3903\t6\t7\tDUAAAA\tATEAAA\tOOOOxx\n2869\t3199\t1\t1\t9\t9\t69\t869\t869\t2869\t2869\t138\t139\tJGAAAA\tBTEAAA\tVVVVxx\n5072\t3200\t0\t0\t2\t12\t72\t72\t1072\t72\t5072\t144\t145\tCNAAAA\tCTEAAA\tAAAAxx\n1201\t3201\t1\t1\t1\t1\t1\t201\t1201\t1201\t1201\t2\t3\tFUAAAA\tDTEAAA\tHHHHxx\n6245\t3202\t1\t1\t5\t5\t45\t245\t245\t1245\t6245\t90\t91\tFGAAAA\tETEAAA\tOOOOxx\n1402\t3203\t0\t2\t2\t2\t2\t402\t1402\t1402\t1402\t4\t5\tYBAAAA\tFTEAAA\tVVVVxx\n2594\t3204\t0\t2\t4\t14\t94\t594\t594\t2594\t2594\t188\t189\tUVAAAA\tGTEAAA\tAAAAxx\n9171\t3205\t1\t3\t1\t11\t71\t171\t1171\t4171\t9171\t142\t143\tTOAAAA\tHTEAAA\tHHHHxx\n2620\t3206\t0\t0\t0\t0\t20\t620\t620\t2620\t2620\t40\t41\tUWAAAA\tITEAAA\tOOOOxx\n6309\t3207\t1\t1\t9\t9\t9\t309\t309\t1309\t6309\t18\t19\tRIAAAA\tJTEAAA\tVVVVxx\n1285\t3208\t1\t1\t5\t5\t85\t285\t1285\t1285\t1285\t170\t171\tLXAAAA\tKTEAAA\tAAAAxx\n5466\t3209\t0\t2\t6\t6\t66\t466\t1466\t466\t5466\t132\t133\tGCAAAA\tLTEAAA\tHHHHxx\n168\t3210\t0\t0\t8\t8\t68\t168\t168\t168\t168\t136\t137\tMGAAAA\tMTEAAA\tOOOOxx\n1410\t3211\t0\t2\t0\t10\t10\t410\t1410\t1410\t1410\t20\t21\tGCAAAA\tNTEAAA\tVVVVxx\n6332\t3212\t0\t0\t2\t12\t32\t332\t332\t1332\t6332\t64\t65\tOJAAAA\tOTEAAA\tAAAAxx\n9530\t3213\t0\t2\t0\t10\t30\t530\t1530\t4530\t9530\t60\t61\tOCAAAA\tPTEAAA\tHHHHxx\n7749\t3214\t1\t1\t9\t9\t49\t749\t1749\t2749\t7749\t98\t99\tBMAAAA\tQTEAAA\tOOOOxx\n3656\t3215\t0\t0\t6\t16\t56\t656\t1656\t3656\t3656\t112\t113\tQKAAAA\tRTEAAA\tVVVVxx\n37\t3216\t1\t1\t7\t17\t37\t37\t37\t37\t37\t74\t75\tLBAAAA\tSTEAAA\tAAAAxx\n2744\t3217\t0\t0\t4\t4\t44\t744\t744\t2744\t2744\t88\t89\tOBAAAA\tTTEAAA\tHHHHxx\n4206\t3218\t0\t2\t6\t6\t6\t206\t206\t4206\t4206\t12\t13\tUFAAAA\tUTEAAA\tOOOOxx\n1846\t3219\t0\t2\t6\t6\t46\t846\t1846\t1846\t1846\t92\t93\tATAAAA\tVTEAAA\tVVVVxx\n9913\t3220\t1\t1\t3\t13\t13\t913\t1913\t4913\t9913\t26\t27\tHRAAAA\tWTEAAA\tAAAAxx\n4078\t3221\t0\t2\t8\t18\t78\t78\t78\t4078\t4078\t156\t157\tWAAAAA\tXTEAAA\tHHHHxx\n2080\t3222\t0\t0\t0\t0\t80\t80\t80\t2080\t2080\t160\t161\tACAAAA\tYTEAAA\tOOOOxx\n4169\t3223\t1\t1\t9\t9\t69\t169\t169\t4169\t4169\t138\t139\tJEAAAA\tZTEAAA\tVVVVxx\n2070\t3224\t0\t2\t0\t10\t70\t70\t70\t2070\t2070\t140\t141\tQBAAAA\tAUEAAA\tAAAAxx\n4500\t3225\t0\t0\t0\t0\t0\t500\t500\t4500\t4500\t0\t1\tCRAAAA\tBUEAAA\tHHHHxx\n4123\t3226\t1\t3\t3\t3\t23\t123\t123\t4123\t4123\t46\t47\tPCAAAA\tCUEAAA\tOOOOxx\n5594\t3227\t0\t2\t4\t14\t94\t594\t1594\t594\t5594\t188\t189\tEHAAAA\tDUEAAA\tVVVVxx\n9941\t3228\t1\t1\t1\t1\t41\t941\t1941\t4941\t9941\t82\t83\tJSAAAA\tEUEAAA\tAAAAxx\n7154\t3229\t0\t2\t4\t14\t54\t154\t1154\t2154\t7154\t108\t109\tEPAAAA\tFUEAAA\tHHHHxx\n8340\t3230\t0\t0\t0\t0\t40\t340\t340\t3340\t8340\t80\t81\tUIAAAA\tGUEAAA\tOOOOxx\n7110\t3231\t0\t2\t0\t10\t10\t110\t1110\t2110\t7110\t20\t21\tMNAAAA\tHUEAAA\tVVVVxx\n7795\t3232\t1\t3\t5\t15\t95\t795\t1795\t2795\t7795\t190\t191\tVNAAAA\tIUEAAA\tAAAAxx\n132\t3233\t0\t0\t2\t12\t32\t132\t132\t132\t132\t64\t65\tCFAAAA\tJUEAAA\tHHHHxx\n4603\t3234\t1\t3\t3\t3\t3\t603\t603\t4603\t4603\t6\t7\tBVAAAA\tKUEAAA\tOOOOxx\n9720\t3235\t0\t0\t0\t0\t20\t720\t1720\t4720\t9720\t40\t41\tWJAAAA\tLUEAAA\tVVVVxx\n1460\t3236\t0\t0\t0\t0\t60\t460\t1460\t1460\t1460\t120\t121\tEEAAAA\tMUEAAA\tAAAAxx\n4677\t3237\t1\t1\t7\t17\t77\t677\t677\t4677\t4677\t154\t155\tXXAAAA\tNUEAAA\tHHHHxx\n9272\t3238\t0\t0\t2\t12\t72\t272\t1272\t4272\t9272\t144\t145\tQSAAAA\tOUEAAA\tOOOOxx\n2279\t3239\t1\t3\t9\t19\t79\t279\t279\t2279\t2279\t158\t159\tRJAAAA\tPUEAAA\tVVVVxx\n4587\t3240\t1\t3\t7\t7\t87\t587\t587\t4587\t4587\t174\t175\tLUAAAA\tQUEAAA\tAAAAxx\n2244\t3241\t0\t0\t4\t4\t44\t244\t244\t2244\t2244\t88\t89\tIIAAAA\tRUEAAA\tHHHHxx\n742\t3242\t0\t2\t2\t2\t42\t742\t742\t742\t742\t84\t85\tOCAAAA\tSUEAAA\tOOOOxx\n4426\t3243\t0\t2\t6\t6\t26\t426\t426\t4426\t4426\t52\t53\tGOAAAA\tTUEAAA\tVVVVxx\n4571\t3244\t1\t3\t1\t11\t71\t571\t571\t4571\t4571\t142\t143\tVTAAAA\tUUEAAA\tAAAAxx\n4775\t3245\t1\t3\t5\t15\t75\t775\t775\t4775\t4775\t150\t151\tRBAAAA\tVUEAAA\tHHHHxx\n24\t3246\t0\t0\t4\t4\t24\t24\t24\t24\t24\t48\t49\tYAAAAA\tWUEAAA\tOOOOxx\n4175\t3247\t1\t3\t5\t15\t75\t175\t175\t4175\t4175\t150\t151\tPEAAAA\tXUEAAA\tVVVVxx\n9877\t3248\t1\t1\t7\t17\t77\t877\t1877\t4877\t9877\t154\t155\tXPAAAA\tYUEAAA\tAAAAxx\n7271\t3249\t1\t3\t1\t11\t71\t271\t1271\t2271\t7271\t142\t143\tRTAAAA\tZUEAAA\tHHHHxx\n5468\t3250\t0\t0\t8\t8\t68\t468\t1468\t468\t5468\t136\t137\tICAAAA\tAVEAAA\tOOOOxx\n6106\t3251\t0\t2\t6\t6\t6\t106\t106\t1106\t6106\t12\t13\tWAAAAA\tBVEAAA\tVVVVxx\n9005\t3252\t1\t1\t5\t5\t5\t5\t1005\t4005\t9005\t10\t11\tJIAAAA\tCVEAAA\tAAAAxx\n109\t3253\t1\t1\t9\t9\t9\t109\t109\t109\t109\t18\t19\tFEAAAA\tDVEAAA\tHHHHxx\n6365\t3254\t1\t1\t5\t5\t65\t365\t365\t1365\t6365\t130\t131\tVKAAAA\tEVEAAA\tOOOOxx\n7437\t3255\t1\t1\t7\t17\t37\t437\t1437\t2437\t7437\t74\t75\tBAAAAA\tFVEAAA\tVVVVxx\n7979\t3256\t1\t3\t9\t19\t79\t979\t1979\t2979\t7979\t158\t159\tXUAAAA\tGVEAAA\tAAAAxx\n6050\t3257\t0\t2\t0\t10\t50\t50\t50\t1050\t6050\t100\t101\tSYAAAA\tHVEAAA\tHHHHxx\n2853\t3258\t1\t1\t3\t13\t53\t853\t853\t2853\t2853\t106\t107\tTFAAAA\tIVEAAA\tOOOOxx\n7603\t3259\t1\t3\t3\t3\t3\t603\t1603\t2603\t7603\t6\t7\tLGAAAA\tJVEAAA\tVVVVxx\n483\t3260\t1\t3\t3\t3\t83\t483\t483\t483\t483\t166\t167\tPSAAAA\tKVEAAA\tAAAAxx\n5994\t3261\t0\t2\t4\t14\t94\t994\t1994\t994\t5994\t188\t189\tOWAAAA\tLVEAAA\tHHHHxx\n6708\t3262\t0\t0\t8\t8\t8\t708\t708\t1708\t6708\t16\t17\tAYAAAA\tMVEAAA\tOOOOxx\n5090\t3263\t0\t2\t0\t10\t90\t90\t1090\t90\t5090\t180\t181\tUNAAAA\tNVEAAA\tVVVVxx\n4608\t3264\t0\t0\t8\t8\t8\t608\t608\t4608\t4608\t16\t17\tGVAAAA\tOVEAAA\tAAAAxx\n4551\t3265\t1\t3\t1\t11\t51\t551\t551\t4551\t4551\t102\t103\tBTAAAA\tPVEAAA\tHHHHxx\n5437\t3266\t1\t1\t7\t17\t37\t437\t1437\t437\t5437\t74\t75\tDBAAAA\tQVEAAA\tOOOOxx\n4130\t3267\t0\t2\t0\t10\t30\t130\t130\t4130\t4130\t60\t61\tWCAAAA\tRVEAAA\tVVVVxx\n6363\t3268\t1\t3\t3\t3\t63\t363\t363\t1363\t6363\t126\t127\tTKAAAA\tSVEAAA\tAAAAxx\n1499\t3269\t1\t3\t9\t19\t99\t499\t1499\t1499\t1499\t198\t199\tRFAAAA\tTVEAAA\tHHHHxx\n384\t3270\t0\t0\t4\t4\t84\t384\t384\t384\t384\t168\t169\tUOAAAA\tUVEAAA\tOOOOxx\n2266\t3271\t0\t2\t6\t6\t66\t266\t266\t2266\t2266\t132\t133\tEJAAAA\tVVEAAA\tVVVVxx\n6018\t3272\t0\t2\t8\t18\t18\t18\t18\t1018\t6018\t36\t37\tMXAAAA\tWVEAAA\tAAAAxx\n7915\t3273\t1\t3\t5\t15\t15\t915\t1915\t2915\t7915\t30\t31\tLSAAAA\tXVEAAA\tHHHHxx\n6167\t3274\t1\t3\t7\t7\t67\t167\t167\t1167\t6167\t134\t135\tFDAAAA\tYVEAAA\tOOOOxx\n9988\t3275\t0\t0\t8\t8\t88\t988\t1988\t4988\t9988\t176\t177\tEUAAAA\tZVEAAA\tVVVVxx\n6599\t3276\t1\t3\t9\t19\t99\t599\t599\t1599\t6599\t198\t199\tVTAAAA\tAWEAAA\tAAAAxx\n1693\t3277\t1\t1\t3\t13\t93\t693\t1693\t1693\t1693\t186\t187\tDNAAAA\tBWEAAA\tHHHHxx\n5971\t3278\t1\t3\t1\t11\t71\t971\t1971\t971\t5971\t142\t143\tRVAAAA\tCWEAAA\tOOOOxx\n8470\t3279\t0\t2\t0\t10\t70\t470\t470\t3470\t8470\t140\t141\tUNAAAA\tDWEAAA\tVVVVxx\n2807\t3280\t1\t3\t7\t7\t7\t807\t807\t2807\t2807\t14\t15\tZDAAAA\tEWEAAA\tAAAAxx\n1120\t3281\t0\t0\t0\t0\t20\t120\t1120\t1120\t1120\t40\t41\tCRAAAA\tFWEAAA\tHHHHxx\n5924\t3282\t0\t0\t4\t4\t24\t924\t1924\t924\t5924\t48\t49\tWTAAAA\tGWEAAA\tOOOOxx\n9025\t3283\t1\t1\t5\t5\t25\t25\t1025\t4025\t9025\t50\t51\tDJAAAA\tHWEAAA\tVVVVxx\n9454\t3284\t0\t2\t4\t14\t54\t454\t1454\t4454\t9454\t108\t109\tQZAAAA\tIWEAAA\tAAAAxx\n2259\t3285\t1\t3\t9\t19\t59\t259\t259\t2259\t2259\t118\t119\tXIAAAA\tJWEAAA\tHHHHxx\n5249\t3286\t1\t1\t9\t9\t49\t249\t1249\t249\t5249\t98\t99\tXTAAAA\tKWEAAA\tOOOOxx\n6350\t3287\t0\t2\t0\t10\t50\t350\t350\t1350\t6350\t100\t101\tGKAAAA\tLWEAAA\tVVVVxx\n2930\t3288\t0\t2\t0\t10\t30\t930\t930\t2930\t2930\t60\t61\tSIAAAA\tMWEAAA\tAAAAxx\n6055\t3289\t1\t3\t5\t15\t55\t55\t55\t1055\t6055\t110\t111\tXYAAAA\tNWEAAA\tHHHHxx\n7691\t3290\t1\t3\t1\t11\t91\t691\t1691\t2691\t7691\t182\t183\tVJAAAA\tOWEAAA\tOOOOxx\n1573\t3291\t1\t1\t3\t13\t73\t573\t1573\t1573\t1573\t146\t147\tNIAAAA\tPWEAAA\tVVVVxx\n9943\t3292\t1\t3\t3\t3\t43\t943\t1943\t4943\t9943\t86\t87\tLSAAAA\tQWEAAA\tAAAAxx\n3085\t3293\t1\t1\t5\t5\t85\t85\t1085\t3085\t3085\t170\t171\tROAAAA\tRWEAAA\tHHHHxx\n5928\t3294\t0\t0\t8\t8\t28\t928\t1928\t928\t5928\t56\t57\tAUAAAA\tSWEAAA\tOOOOxx\n887\t3295\t1\t3\t7\t7\t87\t887\t887\t887\t887\t174\t175\tDIAAAA\tTWEAAA\tVVVVxx\n4630\t3296\t0\t2\t0\t10\t30\t630\t630\t4630\t4630\t60\t61\tCWAAAA\tUWEAAA\tAAAAxx\n9827\t3297\t1\t3\t7\t7\t27\t827\t1827\t4827\t9827\t54\t55\tZNAAAA\tVWEAAA\tHHHHxx\n8926\t3298\t0\t2\t6\t6\t26\t926\t926\t3926\t8926\t52\t53\tIFAAAA\tWWEAAA\tOOOOxx\n5726\t3299\t0\t2\t6\t6\t26\t726\t1726\t726\t5726\t52\t53\tGMAAAA\tXWEAAA\tVVVVxx\n1569\t3300\t1\t1\t9\t9\t69\t569\t1569\t1569\t1569\t138\t139\tJIAAAA\tYWEAAA\tAAAAxx\n8074\t3301\t0\t2\t4\t14\t74\t74\t74\t3074\t8074\t148\t149\tOYAAAA\tZWEAAA\tHHHHxx\n7909\t3302\t1\t1\t9\t9\t9\t909\t1909\t2909\t7909\t18\t19\tFSAAAA\tAXEAAA\tOOOOxx\n8367\t3303\t1\t3\t7\t7\t67\t367\t367\t3367\t8367\t134\t135\tVJAAAA\tBXEAAA\tVVVVxx\n7217\t3304\t1\t1\t7\t17\t17\t217\t1217\t2217\t7217\t34\t35\tPRAAAA\tCXEAAA\tAAAAxx\n5254\t3305\t0\t2\t4\t14\t54\t254\t1254\t254\t5254\t108\t109\tCUAAAA\tDXEAAA\tHHHHxx\n1181\t3306\t1\t1\t1\t1\t81\t181\t1181\t1181\t1181\t162\t163\tLTAAAA\tEXEAAA\tOOOOxx\n6907\t3307\t1\t3\t7\t7\t7\t907\t907\t1907\t6907\t14\t15\tRFAAAA\tFXEAAA\tVVVVxx\n5508\t3308\t0\t0\t8\t8\t8\t508\t1508\t508\t5508\t16\t17\tWDAAAA\tGXEAAA\tAAAAxx\n4782\t3309\t0\t2\t2\t2\t82\t782\t782\t4782\t4782\t164\t165\tYBAAAA\tHXEAAA\tHHHHxx\n793\t3310\t1\t1\t3\t13\t93\t793\t793\t793\t793\t186\t187\tNEAAAA\tIXEAAA\tOOOOxx\n5740\t3311\t0\t0\t0\t0\t40\t740\t1740\t740\t5740\t80\t81\tUMAAAA\tJXEAAA\tVVVVxx\n3107\t3312\t1\t3\t7\t7\t7\t107\t1107\t3107\t3107\t14\t15\tNPAAAA\tKXEAAA\tAAAAxx\n1197\t3313\t1\t1\t7\t17\t97\t197\t1197\t1197\t1197\t194\t195\tBUAAAA\tLXEAAA\tHHHHxx\n4376\t3314\t0\t0\t6\t16\t76\t376\t376\t4376\t4376\t152\t153\tIMAAAA\tMXEAAA\tOOOOxx\n6226\t3315\t0\t2\t6\t6\t26\t226\t226\t1226\t6226\t52\t53\tMFAAAA\tNXEAAA\tVVVVxx\n5033\t3316\t1\t1\t3\t13\t33\t33\t1033\t33\t5033\t66\t67\tPLAAAA\tOXEAAA\tAAAAxx\n5494\t3317\t0\t2\t4\t14\t94\t494\t1494\t494\t5494\t188\t189\tIDAAAA\tPXEAAA\tHHHHxx\n3244\t3318\t0\t0\t4\t4\t44\t244\t1244\t3244\t3244\t88\t89\tUUAAAA\tQXEAAA\tOOOOxx\n7670\t3319\t0\t2\t0\t10\t70\t670\t1670\t2670\t7670\t140\t141\tAJAAAA\tRXEAAA\tVVVVxx\n9273\t3320\t1\t1\t3\t13\t73\t273\t1273\t4273\t9273\t146\t147\tRSAAAA\tSXEAAA\tAAAAxx\n5248\t3321\t0\t0\t8\t8\t48\t248\t1248\t248\t5248\t96\t97\tWTAAAA\tTXEAAA\tHHHHxx\n3381\t3322\t1\t1\t1\t1\t81\t381\t1381\t3381\t3381\t162\t163\tBAAAAA\tUXEAAA\tOOOOxx\n4136\t3323\t0\t0\t6\t16\t36\t136\t136\t4136\t4136\t72\t73\tCDAAAA\tVXEAAA\tVVVVxx\n4163\t3324\t1\t3\t3\t3\t63\t163\t163\t4163\t4163\t126\t127\tDEAAAA\tWXEAAA\tAAAAxx\n4270\t3325\t0\t2\t0\t10\t70\t270\t270\t4270\t4270\t140\t141\tGIAAAA\tXXEAAA\tHHHHxx\n1729\t3326\t1\t1\t9\t9\t29\t729\t1729\t1729\t1729\t58\t59\tNOAAAA\tYXEAAA\tOOOOxx\n2778\t3327\t0\t2\t8\t18\t78\t778\t778\t2778\t2778\t156\t157\tWCAAAA\tZXEAAA\tVVVVxx\n5082\t3328\t0\t2\t2\t2\t82\t82\t1082\t82\t5082\t164\t165\tMNAAAA\tAYEAAA\tAAAAxx\n870\t3329\t0\t2\t0\t10\t70\t870\t870\t870\t870\t140\t141\tMHAAAA\tBYEAAA\tHHHHxx\n4192\t3330\t0\t0\t2\t12\t92\t192\t192\t4192\t4192\t184\t185\tGFAAAA\tCYEAAA\tOOOOxx\n308\t3331\t0\t0\t8\t8\t8\t308\t308\t308\t308\t16\t17\tWLAAAA\tDYEAAA\tVVVVxx\n6783\t3332\t1\t3\t3\t3\t83\t783\t783\t1783\t6783\t166\t167\tXAAAAA\tEYEAAA\tAAAAxx\n7611\t3333\t1\t3\t1\t11\t11\t611\t1611\t2611\t7611\t22\t23\tTGAAAA\tFYEAAA\tHHHHxx\n4221\t3334\t1\t1\t1\t1\t21\t221\t221\t4221\t4221\t42\t43\tJGAAAA\tGYEAAA\tOOOOxx\n6353\t3335\t1\t1\t3\t13\t53\t353\t353\t1353\t6353\t106\t107\tJKAAAA\tHYEAAA\tVVVVxx\n1830\t3336\t0\t2\t0\t10\t30\t830\t1830\t1830\t1830\t60\t61\tKSAAAA\tIYEAAA\tAAAAxx\n2437\t3337\t1\t1\t7\t17\t37\t437\t437\t2437\t2437\t74\t75\tTPAAAA\tJYEAAA\tHHHHxx\n3360\t3338\t0\t0\t0\t0\t60\t360\t1360\t3360\t3360\t120\t121\tGZAAAA\tKYEAAA\tOOOOxx\n1829\t3339\t1\t1\t9\t9\t29\t829\t1829\t1829\t1829\t58\t59\tJSAAAA\tLYEAAA\tVVVVxx\n9475\t3340\t1\t3\t5\t15\t75\t475\t1475\t4475\t9475\t150\t151\tLAAAAA\tMYEAAA\tAAAAxx\n4566\t3341\t0\t2\t6\t6\t66\t566\t566\t4566\t4566\t132\t133\tQTAAAA\tNYEAAA\tHHHHxx\n9944\t3342\t0\t0\t4\t4\t44\t944\t1944\t4944\t9944\t88\t89\tMSAAAA\tOYEAAA\tOOOOxx\n6054\t3343\t0\t2\t4\t14\t54\t54\t54\t1054\t6054\t108\t109\tWYAAAA\tPYEAAA\tVVVVxx\n4722\t3344\t0\t2\t2\t2\t22\t722\t722\t4722\t4722\t44\t45\tQZAAAA\tQYEAAA\tAAAAxx\n2779\t3345\t1\t3\t9\t19\t79\t779\t779\t2779\t2779\t158\t159\tXCAAAA\tRYEAAA\tHHHHxx\n8051\t3346\t1\t3\t1\t11\t51\t51\t51\t3051\t8051\t102\t103\tRXAAAA\tSYEAAA\tOOOOxx\n9671\t3347\t1\t3\t1\t11\t71\t671\t1671\t4671\t9671\t142\t143\tZHAAAA\tTYEAAA\tVVVVxx\n6084\t3348\t0\t0\t4\t4\t84\t84\t84\t1084\t6084\t168\t169\tAAAAAA\tUYEAAA\tAAAAxx\n3729\t3349\t1\t1\t9\t9\t29\t729\t1729\t3729\t3729\t58\t59\tLNAAAA\tVYEAAA\tHHHHxx\n6627\t3350\t1\t3\t7\t7\t27\t627\t627\t1627\t6627\t54\t55\tXUAAAA\tWYEAAA\tOOOOxx\n4769\t3351\t1\t1\t9\t9\t69\t769\t769\t4769\t4769\t138\t139\tLBAAAA\tXYEAAA\tVVVVxx\n2224\t3352\t0\t0\t4\t4\t24\t224\t224\t2224\t2224\t48\t49\tOHAAAA\tYYEAAA\tAAAAxx\n1404\t3353\t0\t0\t4\t4\t4\t404\t1404\t1404\t1404\t8\t9\tACAAAA\tZYEAAA\tHHHHxx\n8532\t3354\t0\t0\t2\t12\t32\t532\t532\t3532\t8532\t64\t65\tEQAAAA\tAZEAAA\tOOOOxx\n6759\t3355\t1\t3\t9\t19\t59\t759\t759\t1759\t6759\t118\t119\tZZAAAA\tBZEAAA\tVVVVxx\n6404\t3356\t0\t0\t4\t4\t4\t404\t404\t1404\t6404\t8\t9\tIMAAAA\tCZEAAA\tAAAAxx\n3144\t3357\t0\t0\t4\t4\t44\t144\t1144\t3144\t3144\t88\t89\tYQAAAA\tDZEAAA\tHHHHxx\n973\t3358\t1\t1\t3\t13\t73\t973\t973\t973\t973\t146\t147\tLLAAAA\tEZEAAA\tOOOOxx\n9789\t3359\t1\t1\t9\t9\t89\t789\t1789\t4789\t9789\t178\t179\tNMAAAA\tFZEAAA\tVVVVxx\n6181\t3360\t1\t1\t1\t1\t81\t181\t181\t1181\t6181\t162\t163\tTDAAAA\tGZEAAA\tAAAAxx\n1519\t3361\t1\t3\t9\t19\t19\t519\t1519\t1519\t1519\t38\t39\tLGAAAA\tHZEAAA\tHHHHxx\n9729\t3362\t1\t1\t9\t9\t29\t729\t1729\t4729\t9729\t58\t59\tFKAAAA\tIZEAAA\tOOOOxx\n8167\t3363\t1\t3\t7\t7\t67\t167\t167\t3167\t8167\t134\t135\tDCAAAA\tJZEAAA\tVVVVxx\n3830\t3364\t0\t2\t0\t10\t30\t830\t1830\t3830\t3830\t60\t61\tIRAAAA\tKZEAAA\tAAAAxx\n6286\t3365\t0\t2\t6\t6\t86\t286\t286\t1286\t6286\t172\t173\tUHAAAA\tLZEAAA\tHHHHxx\n3047\t3366\t1\t3\t7\t7\t47\t47\t1047\t3047\t3047\t94\t95\tFNAAAA\tMZEAAA\tOOOOxx\n3183\t3367\t1\t3\t3\t3\t83\t183\t1183\t3183\t3183\t166\t167\tLSAAAA\tNZEAAA\tVVVVxx\n6687\t3368\t1\t3\t7\t7\t87\t687\t687\t1687\t6687\t174\t175\tFXAAAA\tOZEAAA\tAAAAxx\n2783\t3369\t1\t3\t3\t3\t83\t783\t783\t2783\t2783\t166\t167\tBDAAAA\tPZEAAA\tHHHHxx\n9920\t3370\t0\t0\t0\t0\t20\t920\t1920\t4920\t9920\t40\t41\tORAAAA\tQZEAAA\tOOOOxx\n4847\t3371\t1\t3\t7\t7\t47\t847\t847\t4847\t4847\t94\t95\tLEAAAA\tRZEAAA\tVVVVxx\n3645\t3372\t1\t1\t5\t5\t45\t645\t1645\t3645\t3645\t90\t91\tFKAAAA\tSZEAAA\tAAAAxx\n7406\t3373\t0\t2\t6\t6\t6\t406\t1406\t2406\t7406\t12\t13\tWYAAAA\tTZEAAA\tHHHHxx\n6003\t3374\t1\t3\t3\t3\t3\t3\t3\t1003\t6003\t6\t7\tXWAAAA\tUZEAAA\tOOOOxx\n3408\t3375\t0\t0\t8\t8\t8\t408\t1408\t3408\t3408\t16\t17\tCBAAAA\tVZEAAA\tVVVVxx\n4243\t3376\t1\t3\t3\t3\t43\t243\t243\t4243\t4243\t86\t87\tFHAAAA\tWZEAAA\tAAAAxx\n1622\t3377\t0\t2\t2\t2\t22\t622\t1622\t1622\t1622\t44\t45\tKKAAAA\tXZEAAA\tHHHHxx\n5319\t3378\t1\t3\t9\t19\t19\t319\t1319\t319\t5319\t38\t39\tPWAAAA\tYZEAAA\tOOOOxx\n4033\t3379\t1\t1\t3\t13\t33\t33\t33\t4033\t4033\t66\t67\tDZAAAA\tZZEAAA\tVVVVxx\n8573\t3380\t1\t1\t3\t13\t73\t573\t573\t3573\t8573\t146\t147\tTRAAAA\tAAFAAA\tAAAAxx\n8404\t3381\t0\t0\t4\t4\t4\t404\t404\t3404\t8404\t8\t9\tGLAAAA\tBAFAAA\tHHHHxx\n6993\t3382\t1\t1\t3\t13\t93\t993\t993\t1993\t6993\t186\t187\tZIAAAA\tCAFAAA\tOOOOxx\n660\t3383\t0\t0\t0\t0\t60\t660\t660\t660\t660\t120\t121\tKZAAAA\tDAFAAA\tVVVVxx\n1136\t3384\t0\t0\t6\t16\t36\t136\t1136\t1136\t1136\t72\t73\tSRAAAA\tEAFAAA\tAAAAxx\n3393\t3385\t1\t1\t3\t13\t93\t393\t1393\t3393\t3393\t186\t187\tNAAAAA\tFAFAAA\tHHHHxx\n9743\t3386\t1\t3\t3\t3\t43\t743\t1743\t4743\t9743\t86\t87\tTKAAAA\tGAFAAA\tOOOOxx\n9705\t3387\t1\t1\t5\t5\t5\t705\t1705\t4705\t9705\t10\t11\tHJAAAA\tHAFAAA\tVVVVxx\n6960\t3388\t0\t0\t0\t0\t60\t960\t960\t1960\t6960\t120\t121\tSHAAAA\tIAFAAA\tAAAAxx\n2753\t3389\t1\t1\t3\t13\t53\t753\t753\t2753\t2753\t106\t107\tXBAAAA\tJAFAAA\tHHHHxx\n906\t3390\t0\t2\t6\t6\t6\t906\t906\t906\t906\t12\t13\tWIAAAA\tKAFAAA\tOOOOxx\n999\t3391\t1\t3\t9\t19\t99\t999\t999\t999\t999\t198\t199\tLMAAAA\tLAFAAA\tVVVVxx\n6927\t3392\t1\t3\t7\t7\t27\t927\t927\t1927\t6927\t54\t55\tLGAAAA\tMAFAAA\tAAAAxx\n4846\t3393\t0\t2\t6\t6\t46\t846\t846\t4846\t4846\t92\t93\tKEAAAA\tNAFAAA\tHHHHxx\n676\t3394\t0\t0\t6\t16\t76\t676\t676\t676\t676\t152\t153\tAAAAAA\tOAFAAA\tOOOOxx\n8612\t3395\t0\t0\t2\t12\t12\t612\t612\t3612\t8612\t24\t25\tGTAAAA\tPAFAAA\tVVVVxx\n4111\t3396\t1\t3\t1\t11\t11\t111\t111\t4111\t4111\t22\t23\tDCAAAA\tQAFAAA\tAAAAxx\n9994\t3397\t0\t2\t4\t14\t94\t994\t1994\t4994\t9994\t188\t189\tKUAAAA\tRAFAAA\tHHHHxx\n4399\t3398\t1\t3\t9\t19\t99\t399\t399\t4399\t4399\t198\t199\tFNAAAA\tSAFAAA\tOOOOxx\n4464\t3399\t0\t0\t4\t4\t64\t464\t464\t4464\t4464\t128\t129\tSPAAAA\tTAFAAA\tVVVVxx\n7316\t3400\t0\t0\t6\t16\t16\t316\t1316\t2316\t7316\t32\t33\tKVAAAA\tUAFAAA\tAAAAxx\n8982\t3401\t0\t2\t2\t2\t82\t982\t982\t3982\t8982\t164\t165\tMHAAAA\tVAFAAA\tHHHHxx\n1871\t3402\t1\t3\t1\t11\t71\t871\t1871\t1871\t1871\t142\t143\tZTAAAA\tWAFAAA\tOOOOxx\n4082\t3403\t0\t2\t2\t2\t82\t82\t82\t4082\t4082\t164\t165\tABAAAA\tXAFAAA\tVVVVxx\n3949\t3404\t1\t1\t9\t9\t49\t949\t1949\t3949\t3949\t98\t99\tXVAAAA\tYAFAAA\tAAAAxx\n9352\t3405\t0\t0\t2\t12\t52\t352\t1352\t4352\t9352\t104\t105\tSVAAAA\tZAFAAA\tHHHHxx\n9638\t3406\t0\t2\t8\t18\t38\t638\t1638\t4638\t9638\t76\t77\tSGAAAA\tABFAAA\tOOOOxx\n8177\t3407\t1\t1\t7\t17\t77\t177\t177\t3177\t8177\t154\t155\tNCAAAA\tBBFAAA\tVVVVxx\n3499\t3408\t1\t3\t9\t19\t99\t499\t1499\t3499\t3499\t198\t199\tPEAAAA\tCBFAAA\tAAAAxx\n4233\t3409\t1\t1\t3\t13\t33\t233\t233\t4233\t4233\t66\t67\tVGAAAA\tDBFAAA\tHHHHxx\n1953\t3410\t1\t1\t3\t13\t53\t953\t1953\t1953\t1953\t106\t107\tDXAAAA\tEBFAAA\tOOOOxx\n7372\t3411\t0\t0\t2\t12\t72\t372\t1372\t2372\t7372\t144\t145\tOXAAAA\tFBFAAA\tVVVVxx\n5127\t3412\t1\t3\t7\t7\t27\t127\t1127\t127\t5127\t54\t55\tFPAAAA\tGBFAAA\tAAAAxx\n4384\t3413\t0\t0\t4\t4\t84\t384\t384\t4384\t4384\t168\t169\tQMAAAA\tHBFAAA\tHHHHxx\n9964\t3414\t0\t0\t4\t4\t64\t964\t1964\t4964\t9964\t128\t129\tGTAAAA\tIBFAAA\tOOOOxx\n5392\t3415\t0\t0\t2\t12\t92\t392\t1392\t392\t5392\t184\t185\tKZAAAA\tJBFAAA\tVVVVxx\n616\t3416\t0\t0\t6\t16\t16\t616\t616\t616\t616\t32\t33\tSXAAAA\tKBFAAA\tAAAAxx\n591\t3417\t1\t3\t1\t11\t91\t591\t591\t591\t591\t182\t183\tTWAAAA\tLBFAAA\tHHHHxx\n6422\t3418\t0\t2\t2\t2\t22\t422\t422\t1422\t6422\t44\t45\tANAAAA\tMBFAAA\tOOOOxx\n6551\t3419\t1\t3\t1\t11\t51\t551\t551\t1551\t6551\t102\t103\tZRAAAA\tNBFAAA\tVVVVxx\n9286\t3420\t0\t2\t6\t6\t86\t286\t1286\t4286\t9286\t172\t173\tETAAAA\tOBFAAA\tAAAAxx\n3817\t3421\t1\t1\t7\t17\t17\t817\t1817\t3817\t3817\t34\t35\tVQAAAA\tPBFAAA\tHHHHxx\n7717\t3422\t1\t1\t7\t17\t17\t717\t1717\t2717\t7717\t34\t35\tVKAAAA\tQBFAAA\tOOOOxx\n8718\t3423\t0\t2\t8\t18\t18\t718\t718\t3718\t8718\t36\t37\tIXAAAA\tRBFAAA\tVVVVxx\n8608\t3424\t0\t0\t8\t8\t8\t608\t608\t3608\t8608\t16\t17\tCTAAAA\tSBFAAA\tAAAAxx\n2242\t3425\t0\t2\t2\t2\t42\t242\t242\t2242\t2242\t84\t85\tGIAAAA\tTBFAAA\tHHHHxx\n4811\t3426\t1\t3\t1\t11\t11\t811\t811\t4811\t4811\t22\t23\tBDAAAA\tUBFAAA\tOOOOxx\n6838\t3427\t0\t2\t8\t18\t38\t838\t838\t1838\t6838\t76\t77\tADAAAA\tVBFAAA\tVVVVxx\n787\t3428\t1\t3\t7\t7\t87\t787\t787\t787\t787\t174\t175\tHEAAAA\tWBFAAA\tAAAAxx\n7940\t3429\t0\t0\t0\t0\t40\t940\t1940\t2940\t7940\t80\t81\tKTAAAA\tXBFAAA\tHHHHxx\n336\t3430\t0\t0\t6\t16\t36\t336\t336\t336\t336\t72\t73\tYMAAAA\tYBFAAA\tOOOOxx\n9859\t3431\t1\t3\t9\t19\t59\t859\t1859\t4859\t9859\t118\t119\tFPAAAA\tZBFAAA\tVVVVxx\n3864\t3432\t0\t0\t4\t4\t64\t864\t1864\t3864\t3864\t128\t129\tQSAAAA\tACFAAA\tAAAAxx\n7162\t3433\t0\t2\t2\t2\t62\t162\t1162\t2162\t7162\t124\t125\tMPAAAA\tBCFAAA\tHHHHxx\n2071\t3434\t1\t3\t1\t11\t71\t71\t71\t2071\t2071\t142\t143\tRBAAAA\tCCFAAA\tOOOOxx\n7469\t3435\t1\t1\t9\t9\t69\t469\t1469\t2469\t7469\t138\t139\tHBAAAA\tDCFAAA\tVVVVxx\n2917\t3436\t1\t1\t7\t17\t17\t917\t917\t2917\t2917\t34\t35\tFIAAAA\tECFAAA\tAAAAxx\n7486\t3437\t0\t2\t6\t6\t86\t486\t1486\t2486\t7486\t172\t173\tYBAAAA\tFCFAAA\tHHHHxx\n3355\t3438\t1\t3\t5\t15\t55\t355\t1355\t3355\t3355\t110\t111\tBZAAAA\tGCFAAA\tOOOOxx\n6998\t3439\t0\t2\t8\t18\t98\t998\t998\t1998\t6998\t196\t197\tEJAAAA\tHCFAAA\tVVVVxx\n5498\t3440\t0\t2\t8\t18\t98\t498\t1498\t498\t5498\t196\t197\tMDAAAA\tICFAAA\tAAAAxx\n5113\t3441\t1\t1\t3\t13\t13\t113\t1113\t113\t5113\t26\t27\tROAAAA\tJCFAAA\tHHHHxx\n2846\t3442\t0\t2\t6\t6\t46\t846\t846\t2846\t2846\t92\t93\tMFAAAA\tKCFAAA\tOOOOxx\n6834\t3443\t0\t2\t4\t14\t34\t834\t834\t1834\t6834\t68\t69\tWCAAAA\tLCFAAA\tVVVVxx\n8925\t3444\t1\t1\t5\t5\t25\t925\t925\t3925\t8925\t50\t51\tHFAAAA\tMCFAAA\tAAAAxx\n2757\t3445\t1\t1\t7\t17\t57\t757\t757\t2757\t2757\t114\t115\tBCAAAA\tNCFAAA\tHHHHxx\n2775\t3446\t1\t3\t5\t15\t75\t775\t775\t2775\t2775\t150\t151\tTCAAAA\tOCFAAA\tOOOOxx\n6182\t3447\t0\t2\t2\t2\t82\t182\t182\t1182\t6182\t164\t165\tUDAAAA\tPCFAAA\tVVVVxx\n4488\t3448\t0\t0\t8\t8\t88\t488\t488\t4488\t4488\t176\t177\tQQAAAA\tQCFAAA\tAAAAxx\n8523\t3449\t1\t3\t3\t3\t23\t523\t523\t3523\t8523\t46\t47\tVPAAAA\tRCFAAA\tHHHHxx\n52\t3450\t0\t0\t2\t12\t52\t52\t52\t52\t52\t104\t105\tACAAAA\tSCFAAA\tOOOOxx\n7251\t3451\t1\t3\t1\t11\t51\t251\t1251\t2251\t7251\t102\t103\tXSAAAA\tTCFAAA\tVVVVxx\n6130\t3452\t0\t2\t0\t10\t30\t130\t130\t1130\t6130\t60\t61\tUBAAAA\tUCFAAA\tAAAAxx\n205\t3453\t1\t1\t5\t5\t5\t205\t205\t205\t205\t10\t11\tXHAAAA\tVCFAAA\tHHHHxx\n1186\t3454\t0\t2\t6\t6\t86\t186\t1186\t1186\t1186\t172\t173\tQTAAAA\tWCFAAA\tOOOOxx\n1738\t3455\t0\t2\t8\t18\t38\t738\t1738\t1738\t1738\t76\t77\tWOAAAA\tXCFAAA\tVVVVxx\n9485\t3456\t1\t1\t5\t5\t85\t485\t1485\t4485\t9485\t170\t171\tVAAAAA\tYCFAAA\tAAAAxx\n4235\t3457\t1\t3\t5\t15\t35\t235\t235\t4235\t4235\t70\t71\tXGAAAA\tZCFAAA\tHHHHxx\n7891\t3458\t1\t3\t1\t11\t91\t891\t1891\t2891\t7891\t182\t183\tNRAAAA\tADFAAA\tOOOOxx\n4960\t3459\t0\t0\t0\t0\t60\t960\t960\t4960\t4960\t120\t121\tUIAAAA\tBDFAAA\tVVVVxx\n8911\t3460\t1\t3\t1\t11\t11\t911\t911\t3911\t8911\t22\t23\tTEAAAA\tCDFAAA\tAAAAxx\n1219\t3461\t1\t3\t9\t19\t19\t219\t1219\t1219\t1219\t38\t39\tXUAAAA\tDDFAAA\tHHHHxx\n9652\t3462\t0\t0\t2\t12\t52\t652\t1652\t4652\t9652\t104\t105\tGHAAAA\tEDFAAA\tOOOOxx\n9715\t3463\t1\t3\t5\t15\t15\t715\t1715\t4715\t9715\t30\t31\tRJAAAA\tFDFAAA\tVVVVxx\n6629\t3464\t1\t1\t9\t9\t29\t629\t629\t1629\t6629\t58\t59\tZUAAAA\tGDFAAA\tAAAAxx\n700\t3465\t0\t0\t0\t0\t0\t700\t700\t700\t700\t0\t1\tYAAAAA\tHDFAAA\tHHHHxx\n9819\t3466\t1\t3\t9\t19\t19\t819\t1819\t4819\t9819\t38\t39\tRNAAAA\tIDFAAA\tOOOOxx\n5188\t3467\t0\t0\t8\t8\t88\t188\t1188\t188\t5188\t176\t177\tORAAAA\tJDFAAA\tVVVVxx\n5367\t3468\t1\t3\t7\t7\t67\t367\t1367\t367\t5367\t134\t135\tLYAAAA\tKDFAAA\tAAAAxx\n6447\t3469\t1\t3\t7\t7\t47\t447\t447\t1447\t6447\t94\t95\tZNAAAA\tLDFAAA\tHHHHxx\n720\t3470\t0\t0\t0\t0\t20\t720\t720\t720\t720\t40\t41\tSBAAAA\tMDFAAA\tOOOOxx\n9157\t3471\t1\t1\t7\t17\t57\t157\t1157\t4157\t9157\t114\t115\tFOAAAA\tNDFAAA\tVVVVxx\n1082\t3472\t0\t2\t2\t2\t82\t82\t1082\t1082\t1082\t164\t165\tQPAAAA\tODFAAA\tAAAAxx\n3179\t3473\t1\t3\t9\t19\t79\t179\t1179\t3179\t3179\t158\t159\tHSAAAA\tPDFAAA\tHHHHxx\n4818\t3474\t0\t2\t8\t18\t18\t818\t818\t4818\t4818\t36\t37\tIDAAAA\tQDFAAA\tOOOOxx\n7607\t3475\t1\t3\t7\t7\t7\t607\t1607\t2607\t7607\t14\t15\tPGAAAA\tRDFAAA\tVVVVxx\n2352\t3476\t0\t0\t2\t12\t52\t352\t352\t2352\t2352\t104\t105\tMMAAAA\tSDFAAA\tAAAAxx\n1170\t3477\t0\t2\t0\t10\t70\t170\t1170\t1170\t1170\t140\t141\tATAAAA\tTDFAAA\tHHHHxx\n4269\t3478\t1\t1\t9\t9\t69\t269\t269\t4269\t4269\t138\t139\tFIAAAA\tUDFAAA\tOOOOxx\n8767\t3479\t1\t3\t7\t7\t67\t767\t767\t3767\t8767\t134\t135\tFZAAAA\tVDFAAA\tVVVVxx\n3984\t3480\t0\t0\t4\t4\t84\t984\t1984\t3984\t3984\t168\t169\tGXAAAA\tWDFAAA\tAAAAxx\n3190\t3481\t0\t2\t0\t10\t90\t190\t1190\t3190\t3190\t180\t181\tSSAAAA\tXDFAAA\tHHHHxx\n7456\t3482\t0\t0\t6\t16\t56\t456\t1456\t2456\t7456\t112\t113\tUAAAAA\tYDFAAA\tOOOOxx\n4348\t3483\t0\t0\t8\t8\t48\t348\t348\t4348\t4348\t96\t97\tGLAAAA\tZDFAAA\tVVVVxx\n3150\t3484\t0\t2\t0\t10\t50\t150\t1150\t3150\t3150\t100\t101\tERAAAA\tAEFAAA\tAAAAxx\n8780\t3485\t0\t0\t0\t0\t80\t780\t780\t3780\t8780\t160\t161\tSZAAAA\tBEFAAA\tHHHHxx\n2553\t3486\t1\t1\t3\t13\t53\t553\t553\t2553\t2553\t106\t107\tFUAAAA\tCEFAAA\tOOOOxx\n7526\t3487\t0\t2\t6\t6\t26\t526\t1526\t2526\t7526\t52\t53\tMDAAAA\tDEFAAA\tVVVVxx\n2031\t3488\t1\t3\t1\t11\t31\t31\t31\t2031\t2031\t62\t63\tDAAAAA\tEEFAAA\tAAAAxx\n8793\t3489\t1\t1\t3\t13\t93\t793\t793\t3793\t8793\t186\t187\tFAAAAA\tFEFAAA\tHHHHxx\n1122\t3490\t0\t2\t2\t2\t22\t122\t1122\t1122\t1122\t44\t45\tERAAAA\tGEFAAA\tOOOOxx\n1855\t3491\t1\t3\t5\t15\t55\t855\t1855\t1855\t1855\t110\t111\tJTAAAA\tHEFAAA\tVVVVxx\n6613\t3492\t1\t1\t3\t13\t13\t613\t613\t1613\t6613\t26\t27\tJUAAAA\tIEFAAA\tAAAAxx\n3231\t3493\t1\t3\t1\t11\t31\t231\t1231\t3231\t3231\t62\t63\tHUAAAA\tJEFAAA\tHHHHxx\n9101\t3494\t1\t1\t1\t1\t1\t101\t1101\t4101\t9101\t2\t3\tBMAAAA\tKEFAAA\tOOOOxx\n4937\t3495\t1\t1\t7\t17\t37\t937\t937\t4937\t4937\t74\t75\tXHAAAA\tLEFAAA\tVVVVxx\n666\t3496\t0\t2\t6\t6\t66\t666\t666\t666\t666\t132\t133\tQZAAAA\tMEFAAA\tAAAAxx\n8943\t3497\t1\t3\t3\t3\t43\t943\t943\t3943\t8943\t86\t87\tZFAAAA\tNEFAAA\tHHHHxx\n6164\t3498\t0\t0\t4\t4\t64\t164\t164\t1164\t6164\t128\t129\tCDAAAA\tOEFAAA\tOOOOxx\n1081\t3499\t1\t1\t1\t1\t81\t81\t1081\t1081\t1081\t162\t163\tPPAAAA\tPEFAAA\tVVVVxx\n210\t3500\t0\t2\t0\t10\t10\t210\t210\t210\t210\t20\t21\tCIAAAA\tQEFAAA\tAAAAxx\n6024\t3501\t0\t0\t4\t4\t24\t24\t24\t1024\t6024\t48\t49\tSXAAAA\tREFAAA\tHHHHxx\n5715\t3502\t1\t3\t5\t15\t15\t715\t1715\t715\t5715\t30\t31\tVLAAAA\tSEFAAA\tOOOOxx\n8938\t3503\t0\t2\t8\t18\t38\t938\t938\t3938\t8938\t76\t77\tUFAAAA\tTEFAAA\tVVVVxx\n1326\t3504\t0\t2\t6\t6\t26\t326\t1326\t1326\t1326\t52\t53\tAZAAAA\tUEFAAA\tAAAAxx\n7111\t3505\t1\t3\t1\t11\t11\t111\t1111\t2111\t7111\t22\t23\tNNAAAA\tVEFAAA\tHHHHxx\n757\t3506\t1\t1\t7\t17\t57\t757\t757\t757\t757\t114\t115\tDDAAAA\tWEFAAA\tOOOOxx\n8933\t3507\t1\t1\t3\t13\t33\t933\t933\t3933\t8933\t66\t67\tPFAAAA\tXEFAAA\tVVVVxx\n6495\t3508\t1\t3\t5\t15\t95\t495\t495\t1495\t6495\t190\t191\tVPAAAA\tYEFAAA\tAAAAxx\n3134\t3509\t0\t2\t4\t14\t34\t134\t1134\t3134\t3134\t68\t69\tOQAAAA\tZEFAAA\tHHHHxx\n1304\t3510\t0\t0\t4\t4\t4\t304\t1304\t1304\t1304\t8\t9\tEYAAAA\tAFFAAA\tOOOOxx\n1835\t3511\t1\t3\t5\t15\t35\t835\t1835\t1835\t1835\t70\t71\tPSAAAA\tBFFAAA\tVVVVxx\n7275\t3512\t1\t3\t5\t15\t75\t275\t1275\t2275\t7275\t150\t151\tVTAAAA\tCFFAAA\tAAAAxx\n7337\t3513\t1\t1\t7\t17\t37\t337\t1337\t2337\t7337\t74\t75\tFWAAAA\tDFFAAA\tHHHHxx\n1282\t3514\t0\t2\t2\t2\t82\t282\t1282\t1282\t1282\t164\t165\tIXAAAA\tEFFAAA\tOOOOxx\n6566\t3515\t0\t2\t6\t6\t66\t566\t566\t1566\t6566\t132\t133\tOSAAAA\tFFFAAA\tVVVVxx\n3786\t3516\t0\t2\t6\t6\t86\t786\t1786\t3786\t3786\t172\t173\tQPAAAA\tGFFAAA\tAAAAxx\n5741\t3517\t1\t1\t1\t1\t41\t741\t1741\t741\t5741\t82\t83\tVMAAAA\tHFFAAA\tHHHHxx\n6076\t3518\t0\t0\t6\t16\t76\t76\t76\t1076\t6076\t152\t153\tSZAAAA\tIFFAAA\tOOOOxx\n9998\t3519\t0\t2\t8\t18\t98\t998\t1998\t4998\t9998\t196\t197\tOUAAAA\tJFFAAA\tVVVVxx\n6268\t3520\t0\t0\t8\t8\t68\t268\t268\t1268\t6268\t136\t137\tCHAAAA\tKFFAAA\tAAAAxx\n9647\t3521\t1\t3\t7\t7\t47\t647\t1647\t4647\t9647\t94\t95\tBHAAAA\tLFFAAA\tHHHHxx\n4877\t3522\t1\t1\t7\t17\t77\t877\t877\t4877\t4877\t154\t155\tPFAAAA\tMFFAAA\tOOOOxx\n2652\t3523\t0\t0\t2\t12\t52\t652\t652\t2652\t2652\t104\t105\tAYAAAA\tNFFAAA\tVVVVxx\n1247\t3524\t1\t3\t7\t7\t47\t247\t1247\t1247\t1247\t94\t95\tZVAAAA\tOFFAAA\tAAAAxx\n2721\t3525\t1\t1\t1\t1\t21\t721\t721\t2721\t2721\t42\t43\tRAAAAA\tPFFAAA\tHHHHxx\n5968\t3526\t0\t0\t8\t8\t68\t968\t1968\t968\t5968\t136\t137\tOVAAAA\tQFFAAA\tOOOOxx\n9570\t3527\t0\t2\t0\t10\t70\t570\t1570\t4570\t9570\t140\t141\tCEAAAA\tRFFAAA\tVVVVxx\n6425\t3528\t1\t1\t5\t5\t25\t425\t425\t1425\t6425\t50\t51\tDNAAAA\tSFFAAA\tAAAAxx\n5451\t3529\t1\t3\t1\t11\t51\t451\t1451\t451\t5451\t102\t103\tRBAAAA\tTFFAAA\tHHHHxx\n5668\t3530\t0\t0\t8\t8\t68\t668\t1668\t668\t5668\t136\t137\tAKAAAA\tUFFAAA\tOOOOxx\n9493\t3531\t1\t1\t3\t13\t93\t493\t1493\t4493\t9493\t186\t187\tDBAAAA\tVFFAAA\tVVVVxx\n7973\t3532\t1\t1\t3\t13\t73\t973\t1973\t2973\t7973\t146\t147\tRUAAAA\tWFFAAA\tAAAAxx\n8250\t3533\t0\t2\t0\t10\t50\t250\t250\t3250\t8250\t100\t101\tIFAAAA\tXFFAAA\tHHHHxx\n82\t3534\t0\t2\t2\t2\t82\t82\t82\t82\t82\t164\t165\tEDAAAA\tYFFAAA\tOOOOxx\n6258\t3535\t0\t2\t8\t18\t58\t258\t258\t1258\t6258\t116\t117\tSGAAAA\tZFFAAA\tVVVVxx\n9978\t3536\t0\t2\t8\t18\t78\t978\t1978\t4978\t9978\t156\t157\tUTAAAA\tAGFAAA\tAAAAxx\n6930\t3537\t0\t2\t0\t10\t30\t930\t930\t1930\t6930\t60\t61\tOGAAAA\tBGFAAA\tHHHHxx\n3746\t3538\t0\t2\t6\t6\t46\t746\t1746\t3746\t3746\t92\t93\tCOAAAA\tCGFAAA\tOOOOxx\n7065\t3539\t1\t1\t5\t5\t65\t65\t1065\t2065\t7065\t130\t131\tTLAAAA\tDGFAAA\tVVVVxx\n4281\t3540\t1\t1\t1\t1\t81\t281\t281\t4281\t4281\t162\t163\tRIAAAA\tEGFAAA\tAAAAxx\n4367\t3541\t1\t3\t7\t7\t67\t367\t367\t4367\t4367\t134\t135\tZLAAAA\tFGFAAA\tHHHHxx\n9526\t3542\t0\t2\t6\t6\t26\t526\t1526\t4526\t9526\t52\t53\tKCAAAA\tGGFAAA\tOOOOxx\n5880\t3543\t0\t0\t0\t0\t80\t880\t1880\t880\t5880\t160\t161\tESAAAA\tHGFAAA\tVVVVxx\n8480\t3544\t0\t0\t0\t0\t80\t480\t480\t3480\t8480\t160\t161\tEOAAAA\tIGFAAA\tAAAAxx\n2476\t3545\t0\t0\t6\t16\t76\t476\t476\t2476\t2476\t152\t153\tGRAAAA\tJGFAAA\tHHHHxx\n9074\t3546\t0\t2\t4\t14\t74\t74\t1074\t4074\t9074\t148\t149\tALAAAA\tKGFAAA\tOOOOxx\n4830\t3547\t0\t2\t0\t10\t30\t830\t830\t4830\t4830\t60\t61\tUDAAAA\tLGFAAA\tVVVVxx\n3207\t3548\t1\t3\t7\t7\t7\t207\t1207\t3207\t3207\t14\t15\tJTAAAA\tMGFAAA\tAAAAxx\n7894\t3549\t0\t2\t4\t14\t94\t894\t1894\t2894\t7894\t188\t189\tQRAAAA\tNGFAAA\tHHHHxx\n3860\t3550\t0\t0\t0\t0\t60\t860\t1860\t3860\t3860\t120\t121\tMSAAAA\tOGFAAA\tOOOOxx\n5293\t3551\t1\t1\t3\t13\t93\t293\t1293\t293\t5293\t186\t187\tPVAAAA\tPGFAAA\tVVVVxx\n6895\t3552\t1\t3\t5\t15\t95\t895\t895\t1895\t6895\t190\t191\tFFAAAA\tQGFAAA\tAAAAxx\n9908\t3553\t0\t0\t8\t8\t8\t908\t1908\t4908\t9908\t16\t17\tCRAAAA\tRGFAAA\tHHHHxx\n9247\t3554\t1\t3\t7\t7\t47\t247\t1247\t4247\t9247\t94\t95\tRRAAAA\tSGFAAA\tOOOOxx\n8110\t3555\t0\t2\t0\t10\t10\t110\t110\t3110\t8110\t20\t21\tYZAAAA\tTGFAAA\tVVVVxx\n4716\t3556\t0\t0\t6\t16\t16\t716\t716\t4716\t4716\t32\t33\tKZAAAA\tUGFAAA\tAAAAxx\n4979\t3557\t1\t3\t9\t19\t79\t979\t979\t4979\t4979\t158\t159\tNJAAAA\tVGFAAA\tHHHHxx\n5280\t3558\t0\t0\t0\t0\t80\t280\t1280\t280\t5280\t160\t161\tCVAAAA\tWGFAAA\tOOOOxx\n8326\t3559\t0\t2\t6\t6\t26\t326\t326\t3326\t8326\t52\t53\tGIAAAA\tXGFAAA\tVVVVxx\n5572\t3560\t0\t0\t2\t12\t72\t572\t1572\t572\t5572\t144\t145\tIGAAAA\tYGFAAA\tAAAAxx\n4665\t3561\t1\t1\t5\t5\t65\t665\t665\t4665\t4665\t130\t131\tLXAAAA\tZGFAAA\tHHHHxx\n3665\t3562\t1\t1\t5\t5\t65\t665\t1665\t3665\t3665\t130\t131\tZKAAAA\tAHFAAA\tOOOOxx\n6744\t3563\t0\t0\t4\t4\t44\t744\t744\t1744\t6744\t88\t89\tKZAAAA\tBHFAAA\tVVVVxx\n1897\t3564\t1\t1\t7\t17\t97\t897\t1897\t1897\t1897\t194\t195\tZUAAAA\tCHFAAA\tAAAAxx\n1220\t3565\t0\t0\t0\t0\t20\t220\t1220\t1220\t1220\t40\t41\tYUAAAA\tDHFAAA\tHHHHxx\n2614\t3566\t0\t2\t4\t14\t14\t614\t614\t2614\t2614\t28\t29\tOWAAAA\tEHFAAA\tOOOOxx\n8509\t3567\t1\t1\t9\t9\t9\t509\t509\t3509\t8509\t18\t19\tHPAAAA\tFHFAAA\tVVVVxx\n8521\t3568\t1\t1\t1\t1\t21\t521\t521\t3521\t8521\t42\t43\tTPAAAA\tGHFAAA\tAAAAxx\n4121\t3569\t1\t1\t1\t1\t21\t121\t121\t4121\t4121\t42\t43\tNCAAAA\tHHFAAA\tHHHHxx\n9663\t3570\t1\t3\t3\t3\t63\t663\t1663\t4663\t9663\t126\t127\tRHAAAA\tIHFAAA\tOOOOxx\n2346\t3571\t0\t2\t6\t6\t46\t346\t346\t2346\t2346\t92\t93\tGMAAAA\tJHFAAA\tVVVVxx\n3370\t3572\t0\t2\t0\t10\t70\t370\t1370\t3370\t3370\t140\t141\tQZAAAA\tKHFAAA\tAAAAxx\n1498\t3573\t0\t2\t8\t18\t98\t498\t1498\t1498\t1498\t196\t197\tQFAAAA\tLHFAAA\tHHHHxx\n7422\t3574\t0\t2\t2\t2\t22\t422\t1422\t2422\t7422\t44\t45\tMZAAAA\tMHFAAA\tOOOOxx\n3472\t3575\t0\t0\t2\t12\t72\t472\t1472\t3472\t3472\t144\t145\tODAAAA\tNHFAAA\tVVVVxx\n4126\t3576\t0\t2\t6\t6\t26\t126\t126\t4126\t4126\t52\t53\tSCAAAA\tOHFAAA\tAAAAxx\n4494\t3577\t0\t2\t4\t14\t94\t494\t494\t4494\t4494\t188\t189\tWQAAAA\tPHFAAA\tHHHHxx\n6323\t3578\t1\t3\t3\t3\t23\t323\t323\t1323\t6323\t46\t47\tFJAAAA\tQHFAAA\tOOOOxx\n2823\t3579\t1\t3\t3\t3\t23\t823\t823\t2823\t2823\t46\t47\tPEAAAA\tRHFAAA\tVVVVxx\n8596\t3580\t0\t0\t6\t16\t96\t596\t596\t3596\t8596\t192\t193\tQSAAAA\tSHFAAA\tAAAAxx\n6642\t3581\t0\t2\t2\t2\t42\t642\t642\t1642\t6642\t84\t85\tMVAAAA\tTHFAAA\tHHHHxx\n9276\t3582\t0\t0\t6\t16\t76\t276\t1276\t4276\t9276\t152\t153\tUSAAAA\tUHFAAA\tOOOOxx\n4148\t3583\t0\t0\t8\t8\t48\t148\t148\t4148\t4148\t96\t97\tODAAAA\tVHFAAA\tVVVVxx\n9770\t3584\t0\t2\t0\t10\t70\t770\t1770\t4770\t9770\t140\t141\tULAAAA\tWHFAAA\tAAAAxx\n9812\t3585\t0\t0\t2\t12\t12\t812\t1812\t4812\t9812\t24\t25\tKNAAAA\tXHFAAA\tHHHHxx\n4419\t3586\t1\t3\t9\t19\t19\t419\t419\t4419\t4419\t38\t39\tZNAAAA\tYHFAAA\tOOOOxx\n3802\t3587\t0\t2\t2\t2\t2\t802\t1802\t3802\t3802\t4\t5\tGQAAAA\tZHFAAA\tVVVVxx\n3210\t3588\t0\t2\t0\t10\t10\t210\t1210\t3210\t3210\t20\t21\tMTAAAA\tAIFAAA\tAAAAxx\n6794\t3589\t0\t2\t4\t14\t94\t794\t794\t1794\t6794\t188\t189\tIBAAAA\tBIFAAA\tHHHHxx\n242\t3590\t0\t2\t2\t2\t42\t242\t242\t242\t242\t84\t85\tIJAAAA\tCIFAAA\tOOOOxx\n962\t3591\t0\t2\t2\t2\t62\t962\t962\t962\t962\t124\t125\tALAAAA\tDIFAAA\tVVVVxx\n7151\t3592\t1\t3\t1\t11\t51\t151\t1151\t2151\t7151\t102\t103\tBPAAAA\tEIFAAA\tAAAAxx\n9440\t3593\t0\t0\t0\t0\t40\t440\t1440\t4440\t9440\t80\t81\tCZAAAA\tFIFAAA\tHHHHxx\n721\t3594\t1\t1\t1\t1\t21\t721\t721\t721\t721\t42\t43\tTBAAAA\tGIFAAA\tOOOOxx\n2119\t3595\t1\t3\t9\t19\t19\t119\t119\t2119\t2119\t38\t39\tNDAAAA\tHIFAAA\tVVVVxx\n9883\t3596\t1\t3\t3\t3\t83\t883\t1883\t4883\t9883\t166\t167\tDQAAAA\tIIFAAA\tAAAAxx\n5071\t3597\t1\t3\t1\t11\t71\t71\t1071\t71\t5071\t142\t143\tBNAAAA\tJIFAAA\tHHHHxx\n8239\t3598\t1\t3\t9\t19\t39\t239\t239\t3239\t8239\t78\t79\tXEAAAA\tKIFAAA\tOOOOxx\n7451\t3599\t1\t3\t1\t11\t51\t451\t1451\t2451\t7451\t102\t103\tPAAAAA\tLIFAAA\tVVVVxx\n9517\t3600\t1\t1\t7\t17\t17\t517\t1517\t4517\t9517\t34\t35\tBCAAAA\tMIFAAA\tAAAAxx\n9180\t3601\t0\t0\t0\t0\t80\t180\t1180\t4180\t9180\t160\t161\tCPAAAA\tNIFAAA\tHHHHxx\n9327\t3602\t1\t3\t7\t7\t27\t327\t1327\t4327\t9327\t54\t55\tTUAAAA\tOIFAAA\tOOOOxx\n5462\t3603\t0\t2\t2\t2\t62\t462\t1462\t462\t5462\t124\t125\tCCAAAA\tPIFAAA\tVVVVxx\n8306\t3604\t0\t2\t6\t6\t6\t306\t306\t3306\t8306\t12\t13\tMHAAAA\tQIFAAA\tAAAAxx\n6234\t3605\t0\t2\t4\t14\t34\t234\t234\t1234\t6234\t68\t69\tUFAAAA\tRIFAAA\tHHHHxx\n8771\t3606\t1\t3\t1\t11\t71\t771\t771\t3771\t8771\t142\t143\tJZAAAA\tSIFAAA\tOOOOxx\n5853\t3607\t1\t1\t3\t13\t53\t853\t1853\t853\t5853\t106\t107\tDRAAAA\tTIFAAA\tVVVVxx\n8373\t3608\t1\t1\t3\t13\t73\t373\t373\t3373\t8373\t146\t147\tBKAAAA\tUIFAAA\tAAAAxx\n5017\t3609\t1\t1\t7\t17\t17\t17\t1017\t17\t5017\t34\t35\tZKAAAA\tVIFAAA\tHHHHxx\n8025\t3610\t1\t1\t5\t5\t25\t25\t25\t3025\t8025\t50\t51\tRWAAAA\tWIFAAA\tOOOOxx\n2526\t3611\t0\t2\t6\t6\t26\t526\t526\t2526\t2526\t52\t53\tETAAAA\tXIFAAA\tVVVVxx\n7419\t3612\t1\t3\t9\t19\t19\t419\t1419\t2419\t7419\t38\t39\tJZAAAA\tYIFAAA\tAAAAxx\n4572\t3613\t0\t0\t2\t12\t72\t572\t572\t4572\t4572\t144\t145\tWTAAAA\tZIFAAA\tHHHHxx\n7744\t3614\t0\t0\t4\t4\t44\t744\t1744\t2744\t7744\t88\t89\tWLAAAA\tAJFAAA\tOOOOxx\n8825\t3615\t1\t1\t5\t5\t25\t825\t825\t3825\t8825\t50\t51\tLBAAAA\tBJFAAA\tVVVVxx\n6067\t3616\t1\t3\t7\t7\t67\t67\t67\t1067\t6067\t134\t135\tJZAAAA\tCJFAAA\tAAAAxx\n3291\t3617\t1\t3\t1\t11\t91\t291\t1291\t3291\t3291\t182\t183\tPWAAAA\tDJFAAA\tHHHHxx\n7115\t3618\t1\t3\t5\t15\t15\t115\t1115\t2115\t7115\t30\t31\tRNAAAA\tEJFAAA\tOOOOxx\n2626\t3619\t0\t2\t6\t6\t26\t626\t626\t2626\t2626\t52\t53\tAXAAAA\tFJFAAA\tVVVVxx\n4109\t3620\t1\t1\t9\t9\t9\t109\t109\t4109\t4109\t18\t19\tBCAAAA\tGJFAAA\tAAAAxx\n4056\t3621\t0\t0\t6\t16\t56\t56\t56\t4056\t4056\t112\t113\tAAAAAA\tHJFAAA\tHHHHxx\n6811\t3622\t1\t3\t1\t11\t11\t811\t811\t1811\t6811\t22\t23\tZBAAAA\tIJFAAA\tOOOOxx\n680\t3623\t0\t0\t0\t0\t80\t680\t680\t680\t680\t160\t161\tEAAAAA\tJJFAAA\tVVVVxx\n474\t3624\t0\t2\t4\t14\t74\t474\t474\t474\t474\t148\t149\tGSAAAA\tKJFAAA\tAAAAxx\n9294\t3625\t0\t2\t4\t14\t94\t294\t1294\t4294\t9294\t188\t189\tMTAAAA\tLJFAAA\tHHHHxx\n7555\t3626\t1\t3\t5\t15\t55\t555\t1555\t2555\t7555\t110\t111\tPEAAAA\tMJFAAA\tOOOOxx\n8076\t3627\t0\t0\t6\t16\t76\t76\t76\t3076\t8076\t152\t153\tQYAAAA\tNJFAAA\tVVVVxx\n3840\t3628\t0\t0\t0\t0\t40\t840\t1840\t3840\t3840\t80\t81\tSRAAAA\tOJFAAA\tAAAAxx\n5955\t3629\t1\t3\t5\t15\t55\t955\t1955\t955\t5955\t110\t111\tBVAAAA\tPJFAAA\tHHHHxx\n994\t3630\t0\t2\t4\t14\t94\t994\t994\t994\t994\t188\t189\tGMAAAA\tQJFAAA\tOOOOxx\n2089\t3631\t1\t1\t9\t9\t89\t89\t89\t2089\t2089\t178\t179\tJCAAAA\tRJFAAA\tVVVVxx\n869\t3632\t1\t1\t9\t9\t69\t869\t869\t869\t869\t138\t139\tLHAAAA\tSJFAAA\tAAAAxx\n1223\t3633\t1\t3\t3\t3\t23\t223\t1223\t1223\t1223\t46\t47\tBVAAAA\tTJFAAA\tHHHHxx\n1514\t3634\t0\t2\t4\t14\t14\t514\t1514\t1514\t1514\t28\t29\tGGAAAA\tUJFAAA\tOOOOxx\n4891\t3635\t1\t3\t1\t11\t91\t891\t891\t4891\t4891\t182\t183\tDGAAAA\tVJFAAA\tVVVVxx\n4190\t3636\t0\t2\t0\t10\t90\t190\t190\t4190\t4190\t180\t181\tEFAAAA\tWJFAAA\tAAAAxx\n4377\t3637\t1\t1\t7\t17\t77\t377\t377\t4377\t4377\t154\t155\tJMAAAA\tXJFAAA\tHHHHxx\n9195\t3638\t1\t3\t5\t15\t95\t195\t1195\t4195\t9195\t190\t191\tRPAAAA\tYJFAAA\tOOOOxx\n3827\t3639\t1\t3\t7\t7\t27\t827\t1827\t3827\t3827\t54\t55\tFRAAAA\tZJFAAA\tVVVVxx\n7386\t3640\t0\t2\t6\t6\t86\t386\t1386\t2386\t7386\t172\t173\tCYAAAA\tAKFAAA\tAAAAxx\n6665\t3641\t1\t1\t5\t5\t65\t665\t665\t1665\t6665\t130\t131\tJWAAAA\tBKFAAA\tHHHHxx\n7514\t3642\t0\t2\t4\t14\t14\t514\t1514\t2514\t7514\t28\t29\tADAAAA\tCKFAAA\tOOOOxx\n6431\t3643\t1\t3\t1\t11\t31\t431\t431\t1431\t6431\t62\t63\tJNAAAA\tDKFAAA\tVVVVxx\n3251\t3644\t1\t3\t1\t11\t51\t251\t1251\t3251\t3251\t102\t103\tBVAAAA\tEKFAAA\tAAAAxx\n8439\t3645\t1\t3\t9\t19\t39\t439\t439\t3439\t8439\t78\t79\tPMAAAA\tFKFAAA\tHHHHxx\n831\t3646\t1\t3\t1\t11\t31\t831\t831\t831\t831\t62\t63\tZFAAAA\tGKFAAA\tOOOOxx\n8485\t3647\t1\t1\t5\t5\t85\t485\t485\t3485\t8485\t170\t171\tJOAAAA\tHKFAAA\tVVVVxx\n7314\t3648\t0\t2\t4\t14\t14\t314\t1314\t2314\t7314\t28\t29\tIVAAAA\tIKFAAA\tAAAAxx\n3044\t3649\t0\t0\t4\t4\t44\t44\t1044\t3044\t3044\t88\t89\tCNAAAA\tJKFAAA\tHHHHxx\n4283\t3650\t1\t3\t3\t3\t83\t283\t283\t4283\t4283\t166\t167\tTIAAAA\tKKFAAA\tOOOOxx\n298\t3651\t0\t2\t8\t18\t98\t298\t298\t298\t298\t196\t197\tMLAAAA\tLKFAAA\tVVVVxx\n7114\t3652\t0\t2\t4\t14\t14\t114\t1114\t2114\t7114\t28\t29\tQNAAAA\tMKFAAA\tAAAAxx\n9664\t3653\t0\t0\t4\t4\t64\t664\t1664\t4664\t9664\t128\t129\tSHAAAA\tNKFAAA\tHHHHxx\n5315\t3654\t1\t3\t5\t15\t15\t315\t1315\t315\t5315\t30\t31\tLWAAAA\tOKFAAA\tOOOOxx\n2164\t3655\t0\t0\t4\t4\t64\t164\t164\t2164\t2164\t128\t129\tGFAAAA\tPKFAAA\tVVVVxx\n3390\t3656\t0\t2\t0\t10\t90\t390\t1390\t3390\t3390\t180\t181\tKAAAAA\tQKFAAA\tAAAAxx\n836\t3657\t0\t0\t6\t16\t36\t836\t836\t836\t836\t72\t73\tEGAAAA\tRKFAAA\tHHHHxx\n3316\t3658\t0\t0\t6\t16\t16\t316\t1316\t3316\t3316\t32\t33\tOXAAAA\tSKFAAA\tOOOOxx\n1284\t3659\t0\t0\t4\t4\t84\t284\t1284\t1284\t1284\t168\t169\tKXAAAA\tTKFAAA\tVVVVxx\n2497\t3660\t1\t1\t7\t17\t97\t497\t497\t2497\t2497\t194\t195\tBSAAAA\tUKFAAA\tAAAAxx\n1374\t3661\t0\t2\t4\t14\t74\t374\t1374\t1374\t1374\t148\t149\tWAAAAA\tVKFAAA\tHHHHxx\n9525\t3662\t1\t1\t5\t5\t25\t525\t1525\t4525\t9525\t50\t51\tJCAAAA\tWKFAAA\tOOOOxx\n2911\t3663\t1\t3\t1\t11\t11\t911\t911\t2911\t2911\t22\t23\tZHAAAA\tXKFAAA\tVVVVxx\n9686\t3664\t0\t2\t6\t6\t86\t686\t1686\t4686\t9686\t172\t173\tOIAAAA\tYKFAAA\tAAAAxx\n584\t3665\t0\t0\t4\t4\t84\t584\t584\t584\t584\t168\t169\tMWAAAA\tZKFAAA\tHHHHxx\n5653\t3666\t1\t1\t3\t13\t53\t653\t1653\t653\t5653\t106\t107\tLJAAAA\tALFAAA\tOOOOxx\n4986\t3667\t0\t2\t6\t6\t86\t986\t986\t4986\t4986\t172\t173\tUJAAAA\tBLFAAA\tVVVVxx\n6049\t3668\t1\t1\t9\t9\t49\t49\t49\t1049\t6049\t98\t99\tRYAAAA\tCLFAAA\tAAAAxx\n9891\t3669\t1\t3\t1\t11\t91\t891\t1891\t4891\t9891\t182\t183\tLQAAAA\tDLFAAA\tHHHHxx\n8809\t3670\t1\t1\t9\t9\t9\t809\t809\t3809\t8809\t18\t19\tVAAAAA\tELFAAA\tOOOOxx\n8598\t3671\t0\t2\t8\t18\t98\t598\t598\t3598\t8598\t196\t197\tSSAAAA\tFLFAAA\tVVVVxx\n2573\t3672\t1\t1\t3\t13\t73\t573\t573\t2573\t2573\t146\t147\tZUAAAA\tGLFAAA\tAAAAxx\n6864\t3673\t0\t0\t4\t4\t64\t864\t864\t1864\t6864\t128\t129\tAEAAAA\tHLFAAA\tHHHHxx\n7932\t3674\t0\t0\t2\t12\t32\t932\t1932\t2932\t7932\t64\t65\tCTAAAA\tILFAAA\tOOOOxx\n6605\t3675\t1\t1\t5\t5\t5\t605\t605\t1605\t6605\t10\t11\tBUAAAA\tJLFAAA\tVVVVxx\n9500\t3676\t0\t0\t0\t0\t0\t500\t1500\t4500\t9500\t0\t1\tKBAAAA\tKLFAAA\tAAAAxx\n8742\t3677\t0\t2\t2\t2\t42\t742\t742\t3742\t8742\t84\t85\tGYAAAA\tLLFAAA\tHHHHxx\n9815\t3678\t1\t3\t5\t15\t15\t815\t1815\t4815\t9815\t30\t31\tNNAAAA\tMLFAAA\tOOOOxx\n3319\t3679\t1\t3\t9\t19\t19\t319\t1319\t3319\t3319\t38\t39\tRXAAAA\tNLFAAA\tVVVVxx\n184\t3680\t0\t0\t4\t4\t84\t184\t184\t184\t184\t168\t169\tCHAAAA\tOLFAAA\tAAAAxx\n8886\t3681\t0\t2\t6\t6\t86\t886\t886\t3886\t8886\t172\t173\tUDAAAA\tPLFAAA\tHHHHxx\n7050\t3682\t0\t2\t0\t10\t50\t50\t1050\t2050\t7050\t100\t101\tELAAAA\tQLFAAA\tOOOOxx\n9781\t3683\t1\t1\t1\t1\t81\t781\t1781\t4781\t9781\t162\t163\tFMAAAA\tRLFAAA\tVVVVxx\n2443\t3684\t1\t3\t3\t3\t43\t443\t443\t2443\t2443\t86\t87\tZPAAAA\tSLFAAA\tAAAAxx\n1160\t3685\t0\t0\t0\t0\t60\t160\t1160\t1160\t1160\t120\t121\tQSAAAA\tTLFAAA\tHHHHxx\n4600\t3686\t0\t0\t0\t0\t0\t600\t600\t4600\t4600\t0\t1\tYUAAAA\tULFAAA\tOOOOxx\n813\t3687\t1\t1\t3\t13\t13\t813\t813\t813\t813\t26\t27\tHFAAAA\tVLFAAA\tVVVVxx\n5078\t3688\t0\t2\t8\t18\t78\t78\t1078\t78\t5078\t156\t157\tINAAAA\tWLFAAA\tAAAAxx\n9008\t3689\t0\t0\t8\t8\t8\t8\t1008\t4008\t9008\t16\t17\tMIAAAA\tXLFAAA\tHHHHxx\n9016\t3690\t0\t0\t6\t16\t16\t16\t1016\t4016\t9016\t32\t33\tUIAAAA\tYLFAAA\tOOOOxx\n2747\t3691\t1\t3\t7\t7\t47\t747\t747\t2747\t2747\t94\t95\tRBAAAA\tZLFAAA\tVVVVxx\n3106\t3692\t0\t2\t6\t6\t6\t106\t1106\t3106\t3106\t12\t13\tMPAAAA\tAMFAAA\tAAAAxx\n8235\t3693\t1\t3\t5\t15\t35\t235\t235\t3235\t8235\t70\t71\tTEAAAA\tBMFAAA\tHHHHxx\n5582\t3694\t0\t2\t2\t2\t82\t582\t1582\t582\t5582\t164\t165\tSGAAAA\tCMFAAA\tOOOOxx\n4334\t3695\t0\t2\t4\t14\t34\t334\t334\t4334\t4334\t68\t69\tSKAAAA\tDMFAAA\tVVVVxx\n1612\t3696\t0\t0\t2\t12\t12\t612\t1612\t1612\t1612\t24\t25\tAKAAAA\tEMFAAA\tAAAAxx\n5650\t3697\t0\t2\t0\t10\t50\t650\t1650\t650\t5650\t100\t101\tIJAAAA\tFMFAAA\tHHHHxx\n6086\t3698\t0\t2\t6\t6\t86\t86\t86\t1086\t6086\t172\t173\tCAAAAA\tGMFAAA\tOOOOxx\n9667\t3699\t1\t3\t7\t7\t67\t667\t1667\t4667\t9667\t134\t135\tVHAAAA\tHMFAAA\tVVVVxx\n4215\t3700\t1\t3\t5\t15\t15\t215\t215\t4215\t4215\t30\t31\tDGAAAA\tIMFAAA\tAAAAxx\n8553\t3701\t1\t1\t3\t13\t53\t553\t553\t3553\t8553\t106\t107\tZQAAAA\tJMFAAA\tHHHHxx\n9066\t3702\t0\t2\t6\t6\t66\t66\t1066\t4066\t9066\t132\t133\tSKAAAA\tKMFAAA\tOOOOxx\n1092\t3703\t0\t0\t2\t12\t92\t92\t1092\t1092\t1092\t184\t185\tAQAAAA\tLMFAAA\tVVVVxx\n2848\t3704\t0\t0\t8\t8\t48\t848\t848\t2848\t2848\t96\t97\tOFAAAA\tMMFAAA\tAAAAxx\n2765\t3705\t1\t1\t5\t5\t65\t765\t765\t2765\t2765\t130\t131\tJCAAAA\tNMFAAA\tHHHHxx\n6513\t3706\t1\t1\t3\t13\t13\t513\t513\t1513\t6513\t26\t27\tNQAAAA\tOMFAAA\tOOOOxx\n6541\t3707\t1\t1\t1\t1\t41\t541\t541\t1541\t6541\t82\t83\tPRAAAA\tPMFAAA\tVVVVxx\n9617\t3708\t1\t1\t7\t17\t17\t617\t1617\t4617\t9617\t34\t35\tXFAAAA\tQMFAAA\tAAAAxx\n5870\t3709\t0\t2\t0\t10\t70\t870\t1870\t870\t5870\t140\t141\tURAAAA\tRMFAAA\tHHHHxx\n8811\t3710\t1\t3\t1\t11\t11\t811\t811\t3811\t8811\t22\t23\tXAAAAA\tSMFAAA\tOOOOxx\n4529\t3711\t1\t1\t9\t9\t29\t529\t529\t4529\t4529\t58\t59\tFSAAAA\tTMFAAA\tVVVVxx\n161\t3712\t1\t1\t1\t1\t61\t161\t161\t161\t161\t122\t123\tFGAAAA\tUMFAAA\tAAAAxx\n641\t3713\t1\t1\t1\t1\t41\t641\t641\t641\t641\t82\t83\tRYAAAA\tVMFAAA\tHHHHxx\n4767\t3714\t1\t3\t7\t7\t67\t767\t767\t4767\t4767\t134\t135\tJBAAAA\tWMFAAA\tOOOOxx\n6293\t3715\t1\t1\t3\t13\t93\t293\t293\t1293\t6293\t186\t187\tBIAAAA\tXMFAAA\tVVVVxx\n3816\t3716\t0\t0\t6\t16\t16\t816\t1816\t3816\t3816\t32\t33\tUQAAAA\tYMFAAA\tAAAAxx\n4748\t3717\t0\t0\t8\t8\t48\t748\t748\t4748\t4748\t96\t97\tQAAAAA\tZMFAAA\tHHHHxx\n9924\t3718\t0\t0\t4\t4\t24\t924\t1924\t4924\t9924\t48\t49\tSRAAAA\tANFAAA\tOOOOxx\n6716\t3719\t0\t0\t6\t16\t16\t716\t716\t1716\t6716\t32\t33\tIYAAAA\tBNFAAA\tVVVVxx\n8828\t3720\t0\t0\t8\t8\t28\t828\t828\t3828\t8828\t56\t57\tOBAAAA\tCNFAAA\tAAAAxx\n4967\t3721\t1\t3\t7\t7\t67\t967\t967\t4967\t4967\t134\t135\tBJAAAA\tDNFAAA\tHHHHxx\n9680\t3722\t0\t0\t0\t0\t80\t680\t1680\t4680\t9680\t160\t161\tIIAAAA\tENFAAA\tOOOOxx\n2784\t3723\t0\t0\t4\t4\t84\t784\t784\t2784\t2784\t168\t169\tCDAAAA\tFNFAAA\tVVVVxx\n2882\t3724\t0\t2\t2\t2\t82\t882\t882\t2882\t2882\t164\t165\tWGAAAA\tGNFAAA\tAAAAxx\n3641\t3725\t1\t1\t1\t1\t41\t641\t1641\t3641\t3641\t82\t83\tBKAAAA\tHNFAAA\tHHHHxx\n5537\t3726\t1\t1\t7\t17\t37\t537\t1537\t537\t5537\t74\t75\tZEAAAA\tINFAAA\tOOOOxx\n820\t3727\t0\t0\t0\t0\t20\t820\t820\t820\t820\t40\t41\tOFAAAA\tJNFAAA\tVVVVxx\n5847\t3728\t1\t3\t7\t7\t47\t847\t1847\t847\t5847\t94\t95\tXQAAAA\tKNFAAA\tAAAAxx\n566\t3729\t0\t2\t6\t6\t66\t566\t566\t566\t566\t132\t133\tUVAAAA\tLNFAAA\tHHHHxx\n2246\t3730\t0\t2\t6\t6\t46\t246\t246\t2246\t2246\t92\t93\tKIAAAA\tMNFAAA\tOOOOxx\n6680\t3731\t0\t0\t0\t0\t80\t680\t680\t1680\t6680\t160\t161\tYWAAAA\tNNFAAA\tVVVVxx\n2014\t3732\t0\t2\t4\t14\t14\t14\t14\t2014\t2014\t28\t29\tMZAAAA\tONFAAA\tAAAAxx\n8355\t3733\t1\t3\t5\t15\t55\t355\t355\t3355\t8355\t110\t111\tJJAAAA\tPNFAAA\tHHHHxx\n1610\t3734\t0\t2\t0\t10\t10\t610\t1610\t1610\t1610\t20\t21\tYJAAAA\tQNFAAA\tOOOOxx\n9719\t3735\t1\t3\t9\t19\t19\t719\t1719\t4719\t9719\t38\t39\tVJAAAA\tRNFAAA\tVVVVxx\n8498\t3736\t0\t2\t8\t18\t98\t498\t498\t3498\t8498\t196\t197\tWOAAAA\tSNFAAA\tAAAAxx\n5883\t3737\t1\t3\t3\t3\t83\t883\t1883\t883\t5883\t166\t167\tHSAAAA\tTNFAAA\tHHHHxx\n7380\t3738\t0\t0\t0\t0\t80\t380\t1380\t2380\t7380\t160\t161\tWXAAAA\tUNFAAA\tOOOOxx\n8865\t3739\t1\t1\t5\t5\t65\t865\t865\t3865\t8865\t130\t131\tZCAAAA\tVNFAAA\tVVVVxx\n4743\t3740\t1\t3\t3\t3\t43\t743\t743\t4743\t4743\t86\t87\tLAAAAA\tWNFAAA\tAAAAxx\n5086\t3741\t0\t2\t6\t6\t86\t86\t1086\t86\t5086\t172\t173\tQNAAAA\tXNFAAA\tHHHHxx\n2739\t3742\t1\t3\t9\t19\t39\t739\t739\t2739\t2739\t78\t79\tJBAAAA\tYNFAAA\tOOOOxx\n9375\t3743\t1\t3\t5\t15\t75\t375\t1375\t4375\t9375\t150\t151\tPWAAAA\tZNFAAA\tVVVVxx\n7876\t3744\t0\t0\t6\t16\t76\t876\t1876\t2876\t7876\t152\t153\tYQAAAA\tAOFAAA\tAAAAxx\n453\t3745\t1\t1\t3\t13\t53\t453\t453\t453\t453\t106\t107\tLRAAAA\tBOFAAA\tHHHHxx\n6987\t3746\t1\t3\t7\t7\t87\t987\t987\t1987\t6987\t174\t175\tTIAAAA\tCOFAAA\tOOOOxx\n2860\t3747\t0\t0\t0\t0\t60\t860\t860\t2860\t2860\t120\t121\tAGAAAA\tDOFAAA\tVVVVxx\n8372\t3748\t0\t0\t2\t12\t72\t372\t372\t3372\t8372\t144\t145\tAKAAAA\tEOFAAA\tAAAAxx\n2048\t3749\t0\t0\t8\t8\t48\t48\t48\t2048\t2048\t96\t97\tUAAAAA\tFOFAAA\tHHHHxx\n9231\t3750\t1\t3\t1\t11\t31\t231\t1231\t4231\t9231\t62\t63\tBRAAAA\tGOFAAA\tOOOOxx\n634\t3751\t0\t2\t4\t14\t34\t634\t634\t634\t634\t68\t69\tKYAAAA\tHOFAAA\tVVVVxx\n3998\t3752\t0\t2\t8\t18\t98\t998\t1998\t3998\t3998\t196\t197\tUXAAAA\tIOFAAA\tAAAAxx\n4728\t3753\t0\t0\t8\t8\t28\t728\t728\t4728\t4728\t56\t57\tWZAAAA\tJOFAAA\tHHHHxx\n579\t3754\t1\t3\t9\t19\t79\t579\t579\t579\t579\t158\t159\tHWAAAA\tKOFAAA\tOOOOxx\n815\t3755\t1\t3\t5\t15\t15\t815\t815\t815\t815\t30\t31\tJFAAAA\tLOFAAA\tVVVVxx\n1009\t3756\t1\t1\t9\t9\t9\t9\t1009\t1009\t1009\t18\t19\tVMAAAA\tMOFAAA\tAAAAxx\n6596\t3757\t0\t0\t6\t16\t96\t596\t596\t1596\t6596\t192\t193\tSTAAAA\tNOFAAA\tHHHHxx\n2793\t3758\t1\t1\t3\t13\t93\t793\t793\t2793\t2793\t186\t187\tLDAAAA\tOOFAAA\tOOOOxx\n9589\t3759\t1\t1\t9\t9\t89\t589\t1589\t4589\t9589\t178\t179\tVEAAAA\tPOFAAA\tVVVVxx\n2794\t3760\t0\t2\t4\t14\t94\t794\t794\t2794\t2794\t188\t189\tMDAAAA\tQOFAAA\tAAAAxx\n2551\t3761\t1\t3\t1\t11\t51\t551\t551\t2551\t2551\t102\t103\tDUAAAA\tROFAAA\tHHHHxx\n1588\t3762\t0\t0\t8\t8\t88\t588\t1588\t1588\t1588\t176\t177\tCJAAAA\tSOFAAA\tOOOOxx\n4443\t3763\t1\t3\t3\t3\t43\t443\t443\t4443\t4443\t86\t87\tXOAAAA\tTOFAAA\tVVVVxx\n5009\t3764\t1\t1\t9\t9\t9\t9\t1009\t9\t5009\t18\t19\tRKAAAA\tUOFAAA\tAAAAxx\n4287\t3765\t1\t3\t7\t7\t87\t287\t287\t4287\t4287\t174\t175\tXIAAAA\tVOFAAA\tHHHHxx\n2167\t3766\t1\t3\t7\t7\t67\t167\t167\t2167\t2167\t134\t135\tJFAAAA\tWOFAAA\tOOOOxx\n2290\t3767\t0\t2\t0\t10\t90\t290\t290\t2290\t2290\t180\t181\tCKAAAA\tXOFAAA\tVVVVxx\n7225\t3768\t1\t1\t5\t5\t25\t225\t1225\t2225\t7225\t50\t51\tXRAAAA\tYOFAAA\tAAAAxx\n8992\t3769\t0\t0\t2\t12\t92\t992\t992\t3992\t8992\t184\t185\tWHAAAA\tZOFAAA\tHHHHxx\n1540\t3770\t0\t0\t0\t0\t40\t540\t1540\t1540\t1540\t80\t81\tGHAAAA\tAPFAAA\tOOOOxx\n2029\t3771\t1\t1\t9\t9\t29\t29\t29\t2029\t2029\t58\t59\tBAAAAA\tBPFAAA\tVVVVxx\n2855\t3772\t1\t3\t5\t15\t55\t855\t855\t2855\t2855\t110\t111\tVFAAAA\tCPFAAA\tAAAAxx\n3534\t3773\t0\t2\t4\t14\t34\t534\t1534\t3534\t3534\t68\t69\tYFAAAA\tDPFAAA\tHHHHxx\n8078\t3774\t0\t2\t8\t18\t78\t78\t78\t3078\t8078\t156\t157\tSYAAAA\tEPFAAA\tOOOOxx\n9778\t3775\t0\t2\t8\t18\t78\t778\t1778\t4778\t9778\t156\t157\tCMAAAA\tFPFAAA\tVVVVxx\n3543\t3776\t1\t3\t3\t3\t43\t543\t1543\t3543\t3543\t86\t87\tHGAAAA\tGPFAAA\tAAAAxx\n4778\t3777\t0\t2\t8\t18\t78\t778\t778\t4778\t4778\t156\t157\tUBAAAA\tHPFAAA\tHHHHxx\n8931\t3778\t1\t3\t1\t11\t31\t931\t931\t3931\t8931\t62\t63\tNFAAAA\tIPFAAA\tOOOOxx\n557\t3779\t1\t1\t7\t17\t57\t557\t557\t557\t557\t114\t115\tLVAAAA\tJPFAAA\tVVVVxx\n5546\t3780\t0\t2\t6\t6\t46\t546\t1546\t546\t5546\t92\t93\tIFAAAA\tKPFAAA\tAAAAxx\n7527\t3781\t1\t3\t7\t7\t27\t527\t1527\t2527\t7527\t54\t55\tNDAAAA\tLPFAAA\tHHHHxx\n5000\t3782\t0\t0\t0\t0\t0\t0\t1000\t0\t5000\t0\t1\tIKAAAA\tMPFAAA\tOOOOxx\n7587\t3783\t1\t3\t7\t7\t87\t587\t1587\t2587\t7587\t174\t175\tVFAAAA\tNPFAAA\tVVVVxx\n3014\t3784\t0\t2\t4\t14\t14\t14\t1014\t3014\t3014\t28\t29\tYLAAAA\tOPFAAA\tAAAAxx\n5276\t3785\t0\t0\t6\t16\t76\t276\t1276\t276\t5276\t152\t153\tYUAAAA\tPPFAAA\tHHHHxx\n6457\t3786\t1\t1\t7\t17\t57\t457\t457\t1457\t6457\t114\t115\tJOAAAA\tQPFAAA\tOOOOxx\n389\t3787\t1\t1\t9\t9\t89\t389\t389\t389\t389\t178\t179\tZOAAAA\tRPFAAA\tVVVVxx\n7104\t3788\t0\t0\t4\t4\t4\t104\t1104\t2104\t7104\t8\t9\tGNAAAA\tSPFAAA\tAAAAxx\n9995\t3789\t1\t3\t5\t15\t95\t995\t1995\t4995\t9995\t190\t191\tLUAAAA\tTPFAAA\tHHHHxx\n7368\t3790\t0\t0\t8\t8\t68\t368\t1368\t2368\t7368\t136\t137\tKXAAAA\tUPFAAA\tOOOOxx\n3258\t3791\t0\t2\t8\t18\t58\t258\t1258\t3258\t3258\t116\t117\tIVAAAA\tVPFAAA\tVVVVxx\n9208\t3792\t0\t0\t8\t8\t8\t208\t1208\t4208\t9208\t16\t17\tEQAAAA\tWPFAAA\tAAAAxx\n2396\t3793\t0\t0\t6\t16\t96\t396\t396\t2396\t2396\t192\t193\tEOAAAA\tXPFAAA\tHHHHxx\n1715\t3794\t1\t3\t5\t15\t15\t715\t1715\t1715\t1715\t30\t31\tZNAAAA\tYPFAAA\tOOOOxx\n1240\t3795\t0\t0\t0\t0\t40\t240\t1240\t1240\t1240\t80\t81\tSVAAAA\tZPFAAA\tVVVVxx\n1952\t3796\t0\t0\t2\t12\t52\t952\t1952\t1952\t1952\t104\t105\tCXAAAA\tAQFAAA\tAAAAxx\n4403\t3797\t1\t3\t3\t3\t3\t403\t403\t4403\t4403\t6\t7\tJNAAAA\tBQFAAA\tHHHHxx\n6333\t3798\t1\t1\t3\t13\t33\t333\t333\t1333\t6333\t66\t67\tPJAAAA\tCQFAAA\tOOOOxx\n2492\t3799\t0\t0\t2\t12\t92\t492\t492\t2492\t2492\t184\t185\tWRAAAA\tDQFAAA\tVVVVxx\n6543\t3800\t1\t3\t3\t3\t43\t543\t543\t1543\t6543\t86\t87\tRRAAAA\tEQFAAA\tAAAAxx\n5548\t3801\t0\t0\t8\t8\t48\t548\t1548\t548\t5548\t96\t97\tKFAAAA\tFQFAAA\tHHHHxx\n3458\t3802\t0\t2\t8\t18\t58\t458\t1458\t3458\t3458\t116\t117\tADAAAA\tGQFAAA\tOOOOxx\n2588\t3803\t0\t0\t8\t8\t88\t588\t588\t2588\t2588\t176\t177\tOVAAAA\tHQFAAA\tVVVVxx\n1364\t3804\t0\t0\t4\t4\t64\t364\t1364\t1364\t1364\t128\t129\tMAAAAA\tIQFAAA\tAAAAxx\n9856\t3805\t0\t0\t6\t16\t56\t856\t1856\t4856\t9856\t112\t113\tCPAAAA\tJQFAAA\tHHHHxx\n4964\t3806\t0\t0\t4\t4\t64\t964\t964\t4964\t4964\t128\t129\tYIAAAA\tKQFAAA\tOOOOxx\n773\t3807\t1\t1\t3\t13\t73\t773\t773\t773\t773\t146\t147\tTDAAAA\tLQFAAA\tVVVVxx\n6402\t3808\t0\t2\t2\t2\t2\t402\t402\t1402\t6402\t4\t5\tGMAAAA\tMQFAAA\tAAAAxx\n7213\t3809\t1\t1\t3\t13\t13\t213\t1213\t2213\t7213\t26\t27\tLRAAAA\tNQFAAA\tHHHHxx\n3385\t3810\t1\t1\t5\t5\t85\t385\t1385\t3385\t3385\t170\t171\tFAAAAA\tOQFAAA\tOOOOxx\n6005\t3811\t1\t1\t5\t5\t5\t5\t5\t1005\t6005\t10\t11\tZWAAAA\tPQFAAA\tVVVVxx\n9346\t3812\t0\t2\t6\t6\t46\t346\t1346\t4346\t9346\t92\t93\tMVAAAA\tQQFAAA\tAAAAxx\n1831\t3813\t1\t3\t1\t11\t31\t831\t1831\t1831\t1831\t62\t63\tLSAAAA\tRQFAAA\tHHHHxx\n5406\t3814\t0\t2\t6\t6\t6\t406\t1406\t406\t5406\t12\t13\tYZAAAA\tSQFAAA\tOOOOxx\n2154\t3815\t0\t2\t4\t14\t54\t154\t154\t2154\t2154\t108\t109\tWEAAAA\tTQFAAA\tVVVVxx\n3721\t3816\t1\t1\t1\t1\t21\t721\t1721\t3721\t3721\t42\t43\tDNAAAA\tUQFAAA\tAAAAxx\n2889\t3817\t1\t1\t9\t9\t89\t889\t889\t2889\t2889\t178\t179\tDHAAAA\tVQFAAA\tHHHHxx\n4410\t3818\t0\t2\t0\t10\t10\t410\t410\t4410\t4410\t20\t21\tQNAAAA\tWQFAAA\tOOOOxx\n7102\t3819\t0\t2\t2\t2\t2\t102\t1102\t2102\t7102\t4\t5\tENAAAA\tXQFAAA\tVVVVxx\n4057\t3820\t1\t1\t7\t17\t57\t57\t57\t4057\t4057\t114\t115\tBAAAAA\tYQFAAA\tAAAAxx\n9780\t3821\t0\t0\t0\t0\t80\t780\t1780\t4780\t9780\t160\t161\tEMAAAA\tZQFAAA\tHHHHxx\n9481\t3822\t1\t1\t1\t1\t81\t481\t1481\t4481\t9481\t162\t163\tRAAAAA\tARFAAA\tOOOOxx\n2366\t3823\t0\t2\t6\t6\t66\t366\t366\t2366\t2366\t132\t133\tANAAAA\tBRFAAA\tVVVVxx\n2708\t3824\t0\t0\t8\t8\t8\t708\t708\t2708\t2708\t16\t17\tEAAAAA\tCRFAAA\tAAAAxx\n7399\t3825\t1\t3\t9\t19\t99\t399\t1399\t2399\t7399\t198\t199\tPYAAAA\tDRFAAA\tHHHHxx\n5234\t3826\t0\t2\t4\t14\t34\t234\t1234\t234\t5234\t68\t69\tITAAAA\tERFAAA\tOOOOxx\n1843\t3827\t1\t3\t3\t3\t43\t843\t1843\t1843\t1843\t86\t87\tXSAAAA\tFRFAAA\tVVVVxx\n1006\t3828\t0\t2\t6\t6\t6\t6\t1006\t1006\t1006\t12\t13\tSMAAAA\tGRFAAA\tAAAAxx\n7696\t3829\t0\t0\t6\t16\t96\t696\t1696\t2696\t7696\t192\t193\tAKAAAA\tHRFAAA\tHHHHxx\n6411\t3830\t1\t3\t1\t11\t11\t411\t411\t1411\t6411\t22\t23\tPMAAAA\tIRFAAA\tOOOOxx\n3913\t3831\t1\t1\t3\t13\t13\t913\t1913\t3913\t3913\t26\t27\tNUAAAA\tJRFAAA\tVVVVxx\n2538\t3832\t0\t2\t8\t18\t38\t538\t538\t2538\t2538\t76\t77\tQTAAAA\tKRFAAA\tAAAAxx\n3019\t3833\t1\t3\t9\t19\t19\t19\t1019\t3019\t3019\t38\t39\tDMAAAA\tLRFAAA\tHHHHxx\n107\t3834\t1\t3\t7\t7\t7\t107\t107\t107\t107\t14\t15\tDEAAAA\tMRFAAA\tOOOOxx\n427\t3835\t1\t3\t7\t7\t27\t427\t427\t427\t427\t54\t55\tLQAAAA\tNRFAAA\tVVVVxx\n9849\t3836\t1\t1\t9\t9\t49\t849\t1849\t4849\t9849\t98\t99\tVOAAAA\tORFAAA\tAAAAxx\n4195\t3837\t1\t3\t5\t15\t95\t195\t195\t4195\t4195\t190\t191\tJFAAAA\tPRFAAA\tHHHHxx\n9215\t3838\t1\t3\t5\t15\t15\t215\t1215\t4215\t9215\t30\t31\tLQAAAA\tQRFAAA\tOOOOxx\n3165\t3839\t1\t1\t5\t5\t65\t165\t1165\t3165\t3165\t130\t131\tTRAAAA\tRRFAAA\tVVVVxx\n3280\t3840\t0\t0\t0\t0\t80\t280\t1280\t3280\t3280\t160\t161\tEWAAAA\tSRFAAA\tAAAAxx\n4477\t3841\t1\t1\t7\t17\t77\t477\t477\t4477\t4477\t154\t155\tFQAAAA\tTRFAAA\tHHHHxx\n5885\t3842\t1\t1\t5\t5\t85\t885\t1885\t885\t5885\t170\t171\tJSAAAA\tURFAAA\tOOOOxx\n3311\t3843\t1\t3\t1\t11\t11\t311\t1311\t3311\t3311\t22\t23\tJXAAAA\tVRFAAA\tVVVVxx\n6453\t3844\t1\t1\t3\t13\t53\t453\t453\t1453\t6453\t106\t107\tFOAAAA\tWRFAAA\tAAAAxx\n8527\t3845\t1\t3\t7\t7\t27\t527\t527\t3527\t8527\t54\t55\tZPAAAA\tXRFAAA\tHHHHxx\n1921\t3846\t1\t1\t1\t1\t21\t921\t1921\t1921\t1921\t42\t43\tXVAAAA\tYRFAAA\tOOOOxx\n2427\t3847\t1\t3\t7\t7\t27\t427\t427\t2427\t2427\t54\t55\tJPAAAA\tZRFAAA\tVVVVxx\n3691\t3848\t1\t3\t1\t11\t91\t691\t1691\t3691\t3691\t182\t183\tZLAAAA\tASFAAA\tAAAAxx\n3882\t3849\t0\t2\t2\t2\t82\t882\t1882\t3882\t3882\t164\t165\tITAAAA\tBSFAAA\tHHHHxx\n562\t3850\t0\t2\t2\t2\t62\t562\t562\t562\t562\t124\t125\tQVAAAA\tCSFAAA\tOOOOxx\n377\t3851\t1\t1\t7\t17\t77\t377\t377\t377\t377\t154\t155\tNOAAAA\tDSFAAA\tVVVVxx\n1497\t3852\t1\t1\t7\t17\t97\t497\t1497\t1497\t1497\t194\t195\tPFAAAA\tESFAAA\tAAAAxx\n4453\t3853\t1\t1\t3\t13\t53\t453\t453\t4453\t4453\t106\t107\tHPAAAA\tFSFAAA\tHHHHxx\n4678\t3854\t0\t2\t8\t18\t78\t678\t678\t4678\t4678\t156\t157\tYXAAAA\tGSFAAA\tOOOOxx\n2234\t3855\t0\t2\t4\t14\t34\t234\t234\t2234\t2234\t68\t69\tYHAAAA\tHSFAAA\tVVVVxx\n1073\t3856\t1\t1\t3\t13\t73\t73\t1073\t1073\t1073\t146\t147\tHPAAAA\tISFAAA\tAAAAxx\n6479\t3857\t1\t3\t9\t19\t79\t479\t479\t1479\t6479\t158\t159\tFPAAAA\tJSFAAA\tHHHHxx\n5665\t3858\t1\t1\t5\t5\t65\t665\t1665\t665\t5665\t130\t131\tXJAAAA\tKSFAAA\tOOOOxx\n586\t3859\t0\t2\t6\t6\t86\t586\t586\t586\t586\t172\t173\tOWAAAA\tLSFAAA\tVVVVxx\n1584\t3860\t0\t0\t4\t4\t84\t584\t1584\t1584\t1584\t168\t169\tYIAAAA\tMSFAAA\tAAAAxx\n2574\t3861\t0\t2\t4\t14\t74\t574\t574\t2574\t2574\t148\t149\tAVAAAA\tNSFAAA\tHHHHxx\n9833\t3862\t1\t1\t3\t13\t33\t833\t1833\t4833\t9833\t66\t67\tFOAAAA\tOSFAAA\tOOOOxx\n6726\t3863\t0\t2\t6\t6\t26\t726\t726\t1726\t6726\t52\t53\tSYAAAA\tPSFAAA\tVVVVxx\n8497\t3864\t1\t1\t7\t17\t97\t497\t497\t3497\t8497\t194\t195\tVOAAAA\tQSFAAA\tAAAAxx\n2914\t3865\t0\t2\t4\t14\t14\t914\t914\t2914\t2914\t28\t29\tCIAAAA\tRSFAAA\tHHHHxx\n8586\t3866\t0\t2\t6\t6\t86\t586\t586\t3586\t8586\t172\t173\tGSAAAA\tSSFAAA\tOOOOxx\n6973\t3867\t1\t1\t3\t13\t73\t973\t973\t1973\t6973\t146\t147\tFIAAAA\tTSFAAA\tVVVVxx\n1322\t3868\t0\t2\t2\t2\t22\t322\t1322\t1322\t1322\t44\t45\tWYAAAA\tUSFAAA\tAAAAxx\n5242\t3869\t0\t2\t2\t2\t42\t242\t1242\t242\t5242\t84\t85\tQTAAAA\tVSFAAA\tHHHHxx\n5581\t3870\t1\t1\t1\t1\t81\t581\t1581\t581\t5581\t162\t163\tRGAAAA\tWSFAAA\tOOOOxx\n1365\t3871\t1\t1\t5\t5\t65\t365\t1365\t1365\t1365\t130\t131\tNAAAAA\tXSFAAA\tVVVVxx\n2818\t3872\t0\t2\t8\t18\t18\t818\t818\t2818\t2818\t36\t37\tKEAAAA\tYSFAAA\tAAAAxx\n3758\t3873\t0\t2\t8\t18\t58\t758\t1758\t3758\t3758\t116\t117\tOOAAAA\tZSFAAA\tHHHHxx\n2665\t3874\t1\t1\t5\t5\t65\t665\t665\t2665\t2665\t130\t131\tNYAAAA\tATFAAA\tOOOOxx\n9823\t3875\t1\t3\t3\t3\t23\t823\t1823\t4823\t9823\t46\t47\tVNAAAA\tBTFAAA\tVVVVxx\n7057\t3876\t1\t1\t7\t17\t57\t57\t1057\t2057\t7057\t114\t115\tLLAAAA\tCTFAAA\tAAAAxx\n543\t3877\t1\t3\t3\t3\t43\t543\t543\t543\t543\t86\t87\tXUAAAA\tDTFAAA\tHHHHxx\n4008\t3878\t0\t0\t8\t8\t8\t8\t8\t4008\t4008\t16\t17\tEYAAAA\tETFAAA\tOOOOxx\n4397\t3879\t1\t1\t7\t17\t97\t397\t397\t4397\t4397\t194\t195\tDNAAAA\tFTFAAA\tVVVVxx\n8533\t3880\t1\t1\t3\t13\t33\t533\t533\t3533\t8533\t66\t67\tFQAAAA\tGTFAAA\tAAAAxx\n9728\t3881\t0\t0\t8\t8\t28\t728\t1728\t4728\t9728\t56\t57\tEKAAAA\tHTFAAA\tHHHHxx\n5198\t3882\t0\t2\t8\t18\t98\t198\t1198\t198\t5198\t196\t197\tYRAAAA\tITFAAA\tOOOOxx\n5036\t3883\t0\t0\t6\t16\t36\t36\t1036\t36\t5036\t72\t73\tSLAAAA\tJTFAAA\tVVVVxx\n4394\t3884\t0\t2\t4\t14\t94\t394\t394\t4394\t4394\t188\t189\tANAAAA\tKTFAAA\tAAAAxx\n9633\t3885\t1\t1\t3\t13\t33\t633\t1633\t4633\t9633\t66\t67\tNGAAAA\tLTFAAA\tHHHHxx\n3339\t3886\t1\t3\t9\t19\t39\t339\t1339\t3339\t3339\t78\t79\tLYAAAA\tMTFAAA\tOOOOxx\n9529\t3887\t1\t1\t9\t9\t29\t529\t1529\t4529\t9529\t58\t59\tNCAAAA\tNTFAAA\tVVVVxx\n4780\t3888\t0\t0\t0\t0\t80\t780\t780\t4780\t4780\t160\t161\tWBAAAA\tOTFAAA\tAAAAxx\n4862\t3889\t0\t2\t2\t2\t62\t862\t862\t4862\t4862\t124\t125\tAFAAAA\tPTFAAA\tHHHHxx\n8152\t3890\t0\t0\t2\t12\t52\t152\t152\t3152\t8152\t104\t105\tOBAAAA\tQTFAAA\tOOOOxx\n9330\t3891\t0\t2\t0\t10\t30\t330\t1330\t4330\t9330\t60\t61\tWUAAAA\tRTFAAA\tVVVVxx\n4362\t3892\t0\t2\t2\t2\t62\t362\t362\t4362\t4362\t124\t125\tULAAAA\tSTFAAA\tAAAAxx\n4688\t3893\t0\t0\t8\t8\t88\t688\t688\t4688\t4688\t176\t177\tIYAAAA\tTTFAAA\tHHHHxx\n1903\t3894\t1\t3\t3\t3\t3\t903\t1903\t1903\t1903\t6\t7\tFVAAAA\tUTFAAA\tOOOOxx\n9027\t3895\t1\t3\t7\t7\t27\t27\t1027\t4027\t9027\t54\t55\tFJAAAA\tVTFAAA\tVVVVxx\n5385\t3896\t1\t1\t5\t5\t85\t385\t1385\t385\t5385\t170\t171\tDZAAAA\tWTFAAA\tAAAAxx\n9854\t3897\t0\t2\t4\t14\t54\t854\t1854\t4854\t9854\t108\t109\tAPAAAA\tXTFAAA\tHHHHxx\n9033\t3898\t1\t1\t3\t13\t33\t33\t1033\t4033\t9033\t66\t67\tLJAAAA\tYTFAAA\tOOOOxx\n3185\t3899\t1\t1\t5\t5\t85\t185\t1185\t3185\t3185\t170\t171\tNSAAAA\tZTFAAA\tVVVVxx\n2618\t3900\t0\t2\t8\t18\t18\t618\t618\t2618\t2618\t36\t37\tSWAAAA\tAUFAAA\tAAAAxx\n371\t3901\t1\t3\t1\t11\t71\t371\t371\t371\t371\t142\t143\tHOAAAA\tBUFAAA\tHHHHxx\n3697\t3902\t1\t1\t7\t17\t97\t697\t1697\t3697\t3697\t194\t195\tFMAAAA\tCUFAAA\tOOOOxx\n1682\t3903\t0\t2\t2\t2\t82\t682\t1682\t1682\t1682\t164\t165\tSMAAAA\tDUFAAA\tVVVVxx\n3333\t3904\t1\t1\t3\t13\t33\t333\t1333\t3333\t3333\t66\t67\tFYAAAA\tEUFAAA\tAAAAxx\n1722\t3905\t0\t2\t2\t2\t22\t722\t1722\t1722\t1722\t44\t45\tGOAAAA\tFUFAAA\tHHHHxx\n2009\t3906\t1\t1\t9\t9\t9\t9\t9\t2009\t2009\t18\t19\tHZAAAA\tGUFAAA\tOOOOxx\n3517\t3907\t1\t1\t7\t17\t17\t517\t1517\t3517\t3517\t34\t35\tHFAAAA\tHUFAAA\tVVVVxx\n7640\t3908\t0\t0\t0\t0\t40\t640\t1640\t2640\t7640\t80\t81\tWHAAAA\tIUFAAA\tAAAAxx\n259\t3909\t1\t3\t9\t19\t59\t259\t259\t259\t259\t118\t119\tZJAAAA\tJUFAAA\tHHHHxx\n1400\t3910\t0\t0\t0\t0\t0\t400\t1400\t1400\t1400\t0\t1\tWBAAAA\tKUFAAA\tOOOOxx\n6663\t3911\t1\t3\t3\t3\t63\t663\t663\t1663\t6663\t126\t127\tHWAAAA\tLUFAAA\tVVVVxx\n1576\t3912\t0\t0\t6\t16\t76\t576\t1576\t1576\t1576\t152\t153\tQIAAAA\tMUFAAA\tAAAAxx\n8843\t3913\t1\t3\t3\t3\t43\t843\t843\t3843\t8843\t86\t87\tDCAAAA\tNUFAAA\tHHHHxx\n9474\t3914\t0\t2\t4\t14\t74\t474\t1474\t4474\t9474\t148\t149\tKAAAAA\tOUFAAA\tOOOOxx\n1597\t3915\t1\t1\t7\t17\t97\t597\t1597\t1597\t1597\t194\t195\tLJAAAA\tPUFAAA\tVVVVxx\n1143\t3916\t1\t3\t3\t3\t43\t143\t1143\t1143\t1143\t86\t87\tZRAAAA\tQUFAAA\tAAAAxx\n4162\t3917\t0\t2\t2\t2\t62\t162\t162\t4162\t4162\t124\t125\tCEAAAA\tRUFAAA\tHHHHxx\n1301\t3918\t1\t1\t1\t1\t1\t301\t1301\t1301\t1301\t2\t3\tBYAAAA\tSUFAAA\tOOOOxx\n2935\t3919\t1\t3\t5\t15\t35\t935\t935\t2935\t2935\t70\t71\tXIAAAA\tTUFAAA\tVVVVxx\n886\t3920\t0\t2\t6\t6\t86\t886\t886\t886\t886\t172\t173\tCIAAAA\tUUFAAA\tAAAAxx\n1661\t3921\t1\t1\t1\t1\t61\t661\t1661\t1661\t1661\t122\t123\tXLAAAA\tVUFAAA\tHHHHxx\n1026\t3922\t0\t2\t6\t6\t26\t26\t1026\t1026\t1026\t52\t53\tMNAAAA\tWUFAAA\tOOOOxx\n7034\t3923\t0\t2\t4\t14\t34\t34\t1034\t2034\t7034\t68\t69\tOKAAAA\tXUFAAA\tVVVVxx\n2305\t3924\t1\t1\t5\t5\t5\t305\t305\t2305\t2305\t10\t11\tRKAAAA\tYUFAAA\tAAAAxx\n1725\t3925\t1\t1\t5\t5\t25\t725\t1725\t1725\t1725\t50\t51\tJOAAAA\tZUFAAA\tHHHHxx\n909\t3926\t1\t1\t9\t9\t9\t909\t909\t909\t909\t18\t19\tZIAAAA\tAVFAAA\tOOOOxx\n9906\t3927\t0\t2\t6\t6\t6\t906\t1906\t4906\t9906\t12\t13\tARAAAA\tBVFAAA\tVVVVxx\n3309\t3928\t1\t1\t9\t9\t9\t309\t1309\t3309\t3309\t18\t19\tHXAAAA\tCVFAAA\tAAAAxx\n515\t3929\t1\t3\t5\t15\t15\t515\t515\t515\t515\t30\t31\tVTAAAA\tDVFAAA\tHHHHxx\n932\t3930\t0\t0\t2\t12\t32\t932\t932\t932\t932\t64\t65\tWJAAAA\tEVFAAA\tOOOOxx\n8144\t3931\t0\t0\t4\t4\t44\t144\t144\t3144\t8144\t88\t89\tGBAAAA\tFVFAAA\tVVVVxx\n5592\t3932\t0\t0\t2\t12\t92\t592\t1592\t592\t5592\t184\t185\tCHAAAA\tGVFAAA\tAAAAxx\n4003\t3933\t1\t3\t3\t3\t3\t3\t3\t4003\t4003\t6\t7\tZXAAAA\tHVFAAA\tHHHHxx\n9566\t3934\t0\t2\t6\t6\t66\t566\t1566\t4566\t9566\t132\t133\tYDAAAA\tIVFAAA\tOOOOxx\n4556\t3935\t0\t0\t6\t16\t56\t556\t556\t4556\t4556\t112\t113\tGTAAAA\tJVFAAA\tVVVVxx\n268\t3936\t0\t0\t8\t8\t68\t268\t268\t268\t268\t136\t137\tIKAAAA\tKVFAAA\tAAAAxx\n8107\t3937\t1\t3\t7\t7\t7\t107\t107\t3107\t8107\t14\t15\tVZAAAA\tLVFAAA\tHHHHxx\n5816\t3938\t0\t0\t6\t16\t16\t816\t1816\t816\t5816\t32\t33\tSPAAAA\tMVFAAA\tOOOOxx\n8597\t3939\t1\t1\t7\t17\t97\t597\t597\t3597\t8597\t194\t195\tRSAAAA\tNVFAAA\tVVVVxx\n9611\t3940\t1\t3\t1\t11\t11\t611\t1611\t4611\t9611\t22\t23\tRFAAAA\tOVFAAA\tAAAAxx\n8070\t3941\t0\t2\t0\t10\t70\t70\t70\t3070\t8070\t140\t141\tKYAAAA\tPVFAAA\tHHHHxx\n6040\t3942\t0\t0\t0\t0\t40\t40\t40\t1040\t6040\t80\t81\tIYAAAA\tQVFAAA\tOOOOxx\n3184\t3943\t0\t0\t4\t4\t84\t184\t1184\t3184\t3184\t168\t169\tMSAAAA\tRVFAAA\tVVVVxx\n9656\t3944\t0\t0\t6\t16\t56\t656\t1656\t4656\t9656\t112\t113\tKHAAAA\tSVFAAA\tAAAAxx\n1577\t3945\t1\t1\t7\t17\t77\t577\t1577\t1577\t1577\t154\t155\tRIAAAA\tTVFAAA\tHHHHxx\n1805\t3946\t1\t1\t5\t5\t5\t805\t1805\t1805\t1805\t10\t11\tLRAAAA\tUVFAAA\tOOOOxx\n8268\t3947\t0\t0\t8\t8\t68\t268\t268\t3268\t8268\t136\t137\tAGAAAA\tVVFAAA\tVVVVxx\n3489\t3948\t1\t1\t9\t9\t89\t489\t1489\t3489\t3489\t178\t179\tFEAAAA\tWVFAAA\tAAAAxx\n4564\t3949\t0\t0\t4\t4\t64\t564\t564\t4564\t4564\t128\t129\tOTAAAA\tXVFAAA\tHHHHxx\n4006\t3950\t0\t2\t6\t6\t6\t6\t6\t4006\t4006\t12\t13\tCYAAAA\tYVFAAA\tOOOOxx\n8466\t3951\t0\t2\t6\t6\t66\t466\t466\t3466\t8466\t132\t133\tQNAAAA\tZVFAAA\tVVVVxx\n938\t3952\t0\t2\t8\t18\t38\t938\t938\t938\t938\t76\t77\tCKAAAA\tAWFAAA\tAAAAxx\n5944\t3953\t0\t0\t4\t4\t44\t944\t1944\t944\t5944\t88\t89\tQUAAAA\tBWFAAA\tHHHHxx\n8363\t3954\t1\t3\t3\t3\t63\t363\t363\t3363\t8363\t126\t127\tRJAAAA\tCWFAAA\tOOOOxx\n5348\t3955\t0\t0\t8\t8\t48\t348\t1348\t348\t5348\t96\t97\tSXAAAA\tDWFAAA\tVVVVxx\n71\t3956\t1\t3\t1\t11\t71\t71\t71\t71\t71\t142\t143\tTCAAAA\tEWFAAA\tAAAAxx\n3620\t3957\t0\t0\t0\t0\t20\t620\t1620\t3620\t3620\t40\t41\tGJAAAA\tFWFAAA\tHHHHxx\n3230\t3958\t0\t2\t0\t10\t30\t230\t1230\t3230\t3230\t60\t61\tGUAAAA\tGWFAAA\tOOOOxx\n6132\t3959\t0\t0\t2\t12\t32\t132\t132\t1132\t6132\t64\t65\tWBAAAA\tHWFAAA\tVVVVxx\n6143\t3960\t1\t3\t3\t3\t43\t143\t143\t1143\t6143\t86\t87\tHCAAAA\tIWFAAA\tAAAAxx\n8781\t3961\t1\t1\t1\t1\t81\t781\t781\t3781\t8781\t162\t163\tTZAAAA\tJWFAAA\tHHHHxx\n5522\t3962\t0\t2\t2\t2\t22\t522\t1522\t522\t5522\t44\t45\tKEAAAA\tKWFAAA\tOOOOxx\n6320\t3963\t0\t0\t0\t0\t20\t320\t320\t1320\t6320\t40\t41\tCJAAAA\tLWFAAA\tVVVVxx\n3923\t3964\t1\t3\t3\t3\t23\t923\t1923\t3923\t3923\t46\t47\tXUAAAA\tMWFAAA\tAAAAxx\n2207\t3965\t1\t3\t7\t7\t7\t207\t207\t2207\t2207\t14\t15\tXGAAAA\tNWFAAA\tHHHHxx\n966\t3966\t0\t2\t6\t6\t66\t966\t966\t966\t966\t132\t133\tELAAAA\tOWFAAA\tOOOOxx\n9020\t3967\t0\t0\t0\t0\t20\t20\t1020\t4020\t9020\t40\t41\tYIAAAA\tPWFAAA\tVVVVxx\n4616\t3968\t0\t0\t6\t16\t16\t616\t616\t4616\t4616\t32\t33\tOVAAAA\tQWFAAA\tAAAAxx\n8289\t3969\t1\t1\t9\t9\t89\t289\t289\t3289\t8289\t178\t179\tVGAAAA\tRWFAAA\tHHHHxx\n5796\t3970\t0\t0\t6\t16\t96\t796\t1796\t796\t5796\t192\t193\tYOAAAA\tSWFAAA\tOOOOxx\n9259\t3971\t1\t3\t9\t19\t59\t259\t1259\t4259\t9259\t118\t119\tDSAAAA\tTWFAAA\tVVVVxx\n3710\t3972\t0\t2\t0\t10\t10\t710\t1710\t3710\t3710\t20\t21\tSMAAAA\tUWFAAA\tAAAAxx\n251\t3973\t1\t3\t1\t11\t51\t251\t251\t251\t251\t102\t103\tRJAAAA\tVWFAAA\tHHHHxx\n7669\t3974\t1\t1\t9\t9\t69\t669\t1669\t2669\t7669\t138\t139\tZIAAAA\tWWFAAA\tOOOOxx\n6304\t3975\t0\t0\t4\t4\t4\t304\t304\t1304\t6304\t8\t9\tMIAAAA\tXWFAAA\tVVVVxx\n6454\t3976\t0\t2\t4\t14\t54\t454\t454\t1454\t6454\t108\t109\tGOAAAA\tYWFAAA\tAAAAxx\n1489\t3977\t1\t1\t9\t9\t89\t489\t1489\t1489\t1489\t178\t179\tHFAAAA\tZWFAAA\tHHHHxx\n715\t3978\t1\t3\t5\t15\t15\t715\t715\t715\t715\t30\t31\tNBAAAA\tAXFAAA\tOOOOxx\n4319\t3979\t1\t3\t9\t19\t19\t319\t319\t4319\t4319\t38\t39\tDKAAAA\tBXFAAA\tVVVVxx\n7112\t3980\t0\t0\t2\t12\t12\t112\t1112\t2112\t7112\t24\t25\tONAAAA\tCXFAAA\tAAAAxx\n3726\t3981\t0\t2\t6\t6\t26\t726\t1726\t3726\t3726\t52\t53\tINAAAA\tDXFAAA\tHHHHxx\n7727\t3982\t1\t3\t7\t7\t27\t727\t1727\t2727\t7727\t54\t55\tFLAAAA\tEXFAAA\tOOOOxx\n8387\t3983\t1\t3\t7\t7\t87\t387\t387\t3387\t8387\t174\t175\tPKAAAA\tFXFAAA\tVVVVxx\n6555\t3984\t1\t3\t5\t15\t55\t555\t555\t1555\t6555\t110\t111\tDSAAAA\tGXFAAA\tAAAAxx\n1148\t3985\t0\t0\t8\t8\t48\t148\t1148\t1148\t1148\t96\t97\tESAAAA\tHXFAAA\tHHHHxx\n9000\t3986\t0\t0\t0\t0\t0\t0\t1000\t4000\t9000\t0\t1\tEIAAAA\tIXFAAA\tOOOOxx\n5278\t3987\t0\t2\t8\t18\t78\t278\t1278\t278\t5278\t156\t157\tAVAAAA\tJXFAAA\tVVVVxx\n2388\t3988\t0\t0\t8\t8\t88\t388\t388\t2388\t2388\t176\t177\tWNAAAA\tKXFAAA\tAAAAxx\n7984\t3989\t0\t0\t4\t4\t84\t984\t1984\t2984\t7984\t168\t169\tCVAAAA\tLXFAAA\tHHHHxx\n881\t3990\t1\t1\t1\t1\t81\t881\t881\t881\t881\t162\t163\tXHAAAA\tMXFAAA\tOOOOxx\n6830\t3991\t0\t2\t0\t10\t30\t830\t830\t1830\t6830\t60\t61\tSCAAAA\tNXFAAA\tVVVVxx\n7056\t3992\t0\t0\t6\t16\t56\t56\t1056\t2056\t7056\t112\t113\tKLAAAA\tOXFAAA\tAAAAxx\n7581\t3993\t1\t1\t1\t1\t81\t581\t1581\t2581\t7581\t162\t163\tPFAAAA\tPXFAAA\tHHHHxx\n5214\t3994\t0\t2\t4\t14\t14\t214\t1214\t214\t5214\t28\t29\tOSAAAA\tQXFAAA\tOOOOxx\n2505\t3995\t1\t1\t5\t5\t5\t505\t505\t2505\t2505\t10\t11\tJSAAAA\tRXFAAA\tVVVVxx\n5112\t3996\t0\t0\t2\t12\t12\t112\t1112\t112\t5112\t24\t25\tQOAAAA\tSXFAAA\tAAAAxx\n9884\t3997\t0\t0\t4\t4\t84\t884\t1884\t4884\t9884\t168\t169\tEQAAAA\tTXFAAA\tHHHHxx\n8040\t3998\t0\t0\t0\t0\t40\t40\t40\t3040\t8040\t80\t81\tGXAAAA\tUXFAAA\tOOOOxx\n7033\t3999\t1\t1\t3\t13\t33\t33\t1033\t2033\t7033\t66\t67\tNKAAAA\tVXFAAA\tVVVVxx\n9343\t4000\t1\t3\t3\t3\t43\t343\t1343\t4343\t9343\t86\t87\tJVAAAA\tWXFAAA\tAAAAxx\n2931\t4001\t1\t3\t1\t11\t31\t931\t931\t2931\t2931\t62\t63\tTIAAAA\tXXFAAA\tHHHHxx\n9024\t4002\t0\t0\t4\t4\t24\t24\t1024\t4024\t9024\t48\t49\tCJAAAA\tYXFAAA\tOOOOxx\n6485\t4003\t1\t1\t5\t5\t85\t485\t485\t1485\t6485\t170\t171\tLPAAAA\tZXFAAA\tVVVVxx\n3465\t4004\t1\t1\t5\t5\t65\t465\t1465\t3465\t3465\t130\t131\tHDAAAA\tAYFAAA\tAAAAxx\n3357\t4005\t1\t1\t7\t17\t57\t357\t1357\t3357\t3357\t114\t115\tDZAAAA\tBYFAAA\tHHHHxx\n2929\t4006\t1\t1\t9\t9\t29\t929\t929\t2929\t2929\t58\t59\tRIAAAA\tCYFAAA\tOOOOxx\n3086\t4007\t0\t2\t6\t6\t86\t86\t1086\t3086\t3086\t172\t173\tSOAAAA\tDYFAAA\tVVVVxx\n8897\t4008\t1\t1\t7\t17\t97\t897\t897\t3897\t8897\t194\t195\tFEAAAA\tEYFAAA\tAAAAxx\n9688\t4009\t0\t0\t8\t8\t88\t688\t1688\t4688\t9688\t176\t177\tQIAAAA\tFYFAAA\tHHHHxx\n6522\t4010\t0\t2\t2\t2\t22\t522\t522\t1522\t6522\t44\t45\tWQAAAA\tGYFAAA\tOOOOxx\n3241\t4011\t1\t1\t1\t1\t41\t241\t1241\t3241\t3241\t82\t83\tRUAAAA\tHYFAAA\tVVVVxx\n8770\t4012\t0\t2\t0\t10\t70\t770\t770\t3770\t8770\t140\t141\tIZAAAA\tIYFAAA\tAAAAxx\n2884\t4013\t0\t0\t4\t4\t84\t884\t884\t2884\t2884\t168\t169\tYGAAAA\tJYFAAA\tHHHHxx\n9579\t4014\t1\t3\t9\t19\t79\t579\t1579\t4579\t9579\t158\t159\tLEAAAA\tKYFAAA\tOOOOxx\n3125\t4015\t1\t1\t5\t5\t25\t125\t1125\t3125\t3125\t50\t51\tFQAAAA\tLYFAAA\tVVVVxx\n4604\t4016\t0\t0\t4\t4\t4\t604\t604\t4604\t4604\t8\t9\tCVAAAA\tMYFAAA\tAAAAxx\n2682\t4017\t0\t2\t2\t2\t82\t682\t682\t2682\t2682\t164\t165\tEZAAAA\tNYFAAA\tHHHHxx\n254\t4018\t0\t2\t4\t14\t54\t254\t254\t254\t254\t108\t109\tUJAAAA\tOYFAAA\tOOOOxx\n6569\t4019\t1\t1\t9\t9\t69\t569\t569\t1569\t6569\t138\t139\tRSAAAA\tPYFAAA\tVVVVxx\n2686\t4020\t0\t2\t6\t6\t86\t686\t686\t2686\t2686\t172\t173\tIZAAAA\tQYFAAA\tAAAAxx\n2123\t4021\t1\t3\t3\t3\t23\t123\t123\t2123\t2123\t46\t47\tRDAAAA\tRYFAAA\tHHHHxx\n1745\t4022\t1\t1\t5\t5\t45\t745\t1745\t1745\t1745\t90\t91\tDPAAAA\tSYFAAA\tOOOOxx\n247\t4023\t1\t3\t7\t7\t47\t247\t247\t247\t247\t94\t95\tNJAAAA\tTYFAAA\tVVVVxx\n5800\t4024\t0\t0\t0\t0\t0\t800\t1800\t800\t5800\t0\t1\tCPAAAA\tUYFAAA\tAAAAxx\n1121\t4025\t1\t1\t1\t1\t21\t121\t1121\t1121\t1121\t42\t43\tDRAAAA\tVYFAAA\tHHHHxx\n8893\t4026\t1\t1\t3\t13\t93\t893\t893\t3893\t8893\t186\t187\tBEAAAA\tWYFAAA\tOOOOxx\n7819\t4027\t1\t3\t9\t19\t19\t819\t1819\t2819\t7819\t38\t39\tTOAAAA\tXYFAAA\tVVVVxx\n1339\t4028\t1\t3\t9\t19\t39\t339\t1339\t1339\t1339\t78\t79\tNZAAAA\tYYFAAA\tAAAAxx\n5680\t4029\t0\t0\t0\t0\t80\t680\t1680\t680\t5680\t160\t161\tMKAAAA\tZYFAAA\tHHHHxx\n5093\t4030\t1\t1\t3\t13\t93\t93\t1093\t93\t5093\t186\t187\tXNAAAA\tAZFAAA\tOOOOxx\n3508\t4031\t0\t0\t8\t8\t8\t508\t1508\t3508\t3508\t16\t17\tYEAAAA\tBZFAAA\tVVVVxx\n933\t4032\t1\t1\t3\t13\t33\t933\t933\t933\t933\t66\t67\tXJAAAA\tCZFAAA\tAAAAxx\n1106\t4033\t0\t2\t6\t6\t6\t106\t1106\t1106\t1106\t12\t13\tOQAAAA\tDZFAAA\tHHHHxx\n4386\t4034\t0\t2\t6\t6\t86\t386\t386\t4386\t4386\t172\t173\tSMAAAA\tEZFAAA\tOOOOxx\n5895\t4035\t1\t3\t5\t15\t95\t895\t1895\t895\t5895\t190\t191\tTSAAAA\tFZFAAA\tVVVVxx\n2980\t4036\t0\t0\t0\t0\t80\t980\t980\t2980\t2980\t160\t161\tQKAAAA\tGZFAAA\tAAAAxx\n4400\t4037\t0\t0\t0\t0\t0\t400\t400\t4400\t4400\t0\t1\tGNAAAA\tHZFAAA\tHHHHxx\n7433\t4038\t1\t1\t3\t13\t33\t433\t1433\t2433\t7433\t66\t67\tXZAAAA\tIZFAAA\tOOOOxx\n6110\t4039\t0\t2\t0\t10\t10\t110\t110\t1110\t6110\t20\t21\tABAAAA\tJZFAAA\tVVVVxx\n867\t4040\t1\t3\t7\t7\t67\t867\t867\t867\t867\t134\t135\tJHAAAA\tKZFAAA\tAAAAxx\n5292\t4041\t0\t0\t2\t12\t92\t292\t1292\t292\t5292\t184\t185\tOVAAAA\tLZFAAA\tHHHHxx\n3926\t4042\t0\t2\t6\t6\t26\t926\t1926\t3926\t3926\t52\t53\tAVAAAA\tMZFAAA\tOOOOxx\n1107\t4043\t1\t3\t7\t7\t7\t107\t1107\t1107\t1107\t14\t15\tPQAAAA\tNZFAAA\tVVVVxx\n7355\t4044\t1\t3\t5\t15\t55\t355\t1355\t2355\t7355\t110\t111\tXWAAAA\tOZFAAA\tAAAAxx\n4689\t4045\t1\t1\t9\t9\t89\t689\t689\t4689\t4689\t178\t179\tJYAAAA\tPZFAAA\tHHHHxx\n4872\t4046\t0\t0\t2\t12\t72\t872\t872\t4872\t4872\t144\t145\tKFAAAA\tQZFAAA\tOOOOxx\n7821\t4047\t1\t1\t1\t1\t21\t821\t1821\t2821\t7821\t42\t43\tVOAAAA\tRZFAAA\tVVVVxx\n7277\t4048\t1\t1\t7\t17\t77\t277\t1277\t2277\t7277\t154\t155\tXTAAAA\tSZFAAA\tAAAAxx\n3268\t4049\t0\t0\t8\t8\t68\t268\t1268\t3268\t3268\t136\t137\tSVAAAA\tTZFAAA\tHHHHxx\n8877\t4050\t1\t1\t7\t17\t77\t877\t877\t3877\t8877\t154\t155\tLDAAAA\tUZFAAA\tOOOOxx\n343\t4051\t1\t3\t3\t3\t43\t343\t343\t343\t343\t86\t87\tFNAAAA\tVZFAAA\tVVVVxx\n621\t4052\t1\t1\t1\t1\t21\t621\t621\t621\t621\t42\t43\tXXAAAA\tWZFAAA\tAAAAxx\n5429\t4053\t1\t1\t9\t9\t29\t429\t1429\t429\t5429\t58\t59\tVAAAAA\tXZFAAA\tHHHHxx\n392\t4054\t0\t0\t2\t12\t92\t392\t392\t392\t392\t184\t185\tCPAAAA\tYZFAAA\tOOOOxx\n6004\t4055\t0\t0\t4\t4\t4\t4\t4\t1004\t6004\t8\t9\tYWAAAA\tZZFAAA\tVVVVxx\n6377\t4056\t1\t1\t7\t17\t77\t377\t377\t1377\t6377\t154\t155\tHLAAAA\tAAGAAA\tAAAAxx\n3037\t4057\t1\t1\t7\t17\t37\t37\t1037\t3037\t3037\t74\t75\tVMAAAA\tBAGAAA\tHHHHxx\n3514\t4058\t0\t2\t4\t14\t14\t514\t1514\t3514\t3514\t28\t29\tEFAAAA\tCAGAAA\tOOOOxx\n8740\t4059\t0\t0\t0\t0\t40\t740\t740\t3740\t8740\t80\t81\tEYAAAA\tDAGAAA\tVVVVxx\n3877\t4060\t1\t1\t7\t17\t77\t877\t1877\t3877\t3877\t154\t155\tDTAAAA\tEAGAAA\tAAAAxx\n5731\t4061\t1\t3\t1\t11\t31\t731\t1731\t731\t5731\t62\t63\tLMAAAA\tFAGAAA\tHHHHxx\n6407\t4062\t1\t3\t7\t7\t7\t407\t407\t1407\t6407\t14\t15\tLMAAAA\tGAGAAA\tOOOOxx\n2044\t4063\t0\t0\t4\t4\t44\t44\t44\t2044\t2044\t88\t89\tQAAAAA\tHAGAAA\tVVVVxx\n7362\t4064\t0\t2\t2\t2\t62\t362\t1362\t2362\t7362\t124\t125\tEXAAAA\tIAGAAA\tAAAAxx\n5458\t4065\t0\t2\t8\t18\t58\t458\t1458\t458\t5458\t116\t117\tYBAAAA\tJAGAAA\tHHHHxx\n6437\t4066\t1\t1\t7\t17\t37\t437\t437\t1437\t6437\t74\t75\tPNAAAA\tKAGAAA\tOOOOxx\n1051\t4067\t1\t3\t1\t11\t51\t51\t1051\t1051\t1051\t102\t103\tLOAAAA\tLAGAAA\tVVVVxx\n1203\t4068\t1\t3\t3\t3\t3\t203\t1203\t1203\t1203\t6\t7\tHUAAAA\tMAGAAA\tAAAAxx\n2176\t4069\t0\t0\t6\t16\t76\t176\t176\t2176\t2176\t152\t153\tSFAAAA\tNAGAAA\tHHHHxx\n8997\t4070\t1\t1\t7\t17\t97\t997\t997\t3997\t8997\t194\t195\tBIAAAA\tOAGAAA\tOOOOxx\n6378\t4071\t0\t2\t8\t18\t78\t378\t378\t1378\t6378\t156\t157\tILAAAA\tPAGAAA\tVVVVxx\n6006\t4072\t0\t2\t6\t6\t6\t6\t6\t1006\t6006\t12\t13\tAXAAAA\tQAGAAA\tAAAAxx\n2308\t4073\t0\t0\t8\t8\t8\t308\t308\t2308\t2308\t16\t17\tUKAAAA\tRAGAAA\tHHHHxx\n625\t4074\t1\t1\t5\t5\t25\t625\t625\t625\t625\t50\t51\tBYAAAA\tSAGAAA\tOOOOxx\n7298\t4075\t0\t2\t8\t18\t98\t298\t1298\t2298\t7298\t196\t197\tSUAAAA\tTAGAAA\tVVVVxx\n5575\t4076\t1\t3\t5\t15\t75\t575\t1575\t575\t5575\t150\t151\tLGAAAA\tUAGAAA\tAAAAxx\n3565\t4077\t1\t1\t5\t5\t65\t565\t1565\t3565\t3565\t130\t131\tDHAAAA\tVAGAAA\tHHHHxx\n47\t4078\t1\t3\t7\t7\t47\t47\t47\t47\t47\t94\t95\tVBAAAA\tWAGAAA\tOOOOxx\n2413\t4079\t1\t1\t3\t13\t13\t413\t413\t2413\t2413\t26\t27\tVOAAAA\tXAGAAA\tVVVVxx\n2153\t4080\t1\t1\t3\t13\t53\t153\t153\t2153\t2153\t106\t107\tVEAAAA\tYAGAAA\tAAAAxx\n752\t4081\t0\t0\t2\t12\t52\t752\t752\t752\t752\t104\t105\tYCAAAA\tZAGAAA\tHHHHxx\n4095\t4082\t1\t3\t5\t15\t95\t95\t95\t4095\t4095\t190\t191\tNBAAAA\tABGAAA\tOOOOxx\n2518\t4083\t0\t2\t8\t18\t18\t518\t518\t2518\t2518\t36\t37\tWSAAAA\tBBGAAA\tVVVVxx\n3681\t4084\t1\t1\t1\t1\t81\t681\t1681\t3681\t3681\t162\t163\tPLAAAA\tCBGAAA\tAAAAxx\n4213\t4085\t1\t1\t3\t13\t13\t213\t213\t4213\t4213\t26\t27\tBGAAAA\tDBGAAA\tHHHHxx\n2615\t4086\t1\t3\t5\t15\t15\t615\t615\t2615\t2615\t30\t31\tPWAAAA\tEBGAAA\tOOOOxx\n1471\t4087\t1\t3\t1\t11\t71\t471\t1471\t1471\t1471\t142\t143\tPEAAAA\tFBGAAA\tVVVVxx\n7315\t4088\t1\t3\t5\t15\t15\t315\t1315\t2315\t7315\t30\t31\tJVAAAA\tGBGAAA\tAAAAxx\n6013\t4089\t1\t1\t3\t13\t13\t13\t13\t1013\t6013\t26\t27\tHXAAAA\tHBGAAA\tHHHHxx\n3077\t4090\t1\t1\t7\t17\t77\t77\t1077\t3077\t3077\t154\t155\tJOAAAA\tIBGAAA\tOOOOxx\n2190\t4091\t0\t2\t0\t10\t90\t190\t190\t2190\t2190\t180\t181\tGGAAAA\tJBGAAA\tVVVVxx\n528\t4092\t0\t0\t8\t8\t28\t528\t528\t528\t528\t56\t57\tIUAAAA\tKBGAAA\tAAAAxx\n9508\t4093\t0\t0\t8\t8\t8\t508\t1508\t4508\t9508\t16\t17\tSBAAAA\tLBGAAA\tHHHHxx\n2473\t4094\t1\t1\t3\t13\t73\t473\t473\t2473\t2473\t146\t147\tDRAAAA\tMBGAAA\tOOOOxx\n167\t4095\t1\t3\t7\t7\t67\t167\t167\t167\t167\t134\t135\tLGAAAA\tNBGAAA\tVVVVxx\n8448\t4096\t0\t0\t8\t8\t48\t448\t448\t3448\t8448\t96\t97\tYMAAAA\tOBGAAA\tAAAAxx\n7538\t4097\t0\t2\t8\t18\t38\t538\t1538\t2538\t7538\t76\t77\tYDAAAA\tPBGAAA\tHHHHxx\n7638\t4098\t0\t2\t8\t18\t38\t638\t1638\t2638\t7638\t76\t77\tUHAAAA\tQBGAAA\tOOOOxx\n4328\t4099\t0\t0\t8\t8\t28\t328\t328\t4328\t4328\t56\t57\tMKAAAA\tRBGAAA\tVVVVxx\n3812\t4100\t0\t0\t2\t12\t12\t812\t1812\t3812\t3812\t24\t25\tQQAAAA\tSBGAAA\tAAAAxx\n2879\t4101\t1\t3\t9\t19\t79\t879\t879\t2879\t2879\t158\t159\tTGAAAA\tTBGAAA\tHHHHxx\n4741\t4102\t1\t1\t1\t1\t41\t741\t741\t4741\t4741\t82\t83\tJAAAAA\tUBGAAA\tOOOOxx\n9155\t4103\t1\t3\t5\t15\t55\t155\t1155\t4155\t9155\t110\t111\tDOAAAA\tVBGAAA\tVVVVxx\n5151\t4104\t1\t3\t1\t11\t51\t151\t1151\t151\t5151\t102\t103\tDQAAAA\tWBGAAA\tAAAAxx\n5591\t4105\t1\t3\t1\t11\t91\t591\t1591\t591\t5591\t182\t183\tBHAAAA\tXBGAAA\tHHHHxx\n1034\t4106\t0\t2\t4\t14\t34\t34\t1034\t1034\t1034\t68\t69\tUNAAAA\tYBGAAA\tOOOOxx\n765\t4107\t1\t1\t5\t5\t65\t765\t765\t765\t765\t130\t131\tLDAAAA\tZBGAAA\tVVVVxx\n2664\t4108\t0\t0\t4\t4\t64\t664\t664\t2664\t2664\t128\t129\tMYAAAA\tACGAAA\tAAAAxx\n6854\t4109\t0\t2\t4\t14\t54\t854\t854\t1854\t6854\t108\t109\tQDAAAA\tBCGAAA\tHHHHxx\n8263\t4110\t1\t3\t3\t3\t63\t263\t263\t3263\t8263\t126\t127\tVFAAAA\tCCGAAA\tOOOOxx\n8658\t4111\t0\t2\t8\t18\t58\t658\t658\t3658\t8658\t116\t117\tAVAAAA\tDCGAAA\tVVVVxx\n587\t4112\t1\t3\t7\t7\t87\t587\t587\t587\t587\t174\t175\tPWAAAA\tECGAAA\tAAAAxx\n4553\t4113\t1\t1\t3\t13\t53\t553\t553\t4553\t4553\t106\t107\tDTAAAA\tFCGAAA\tHHHHxx\n1368\t4114\t0\t0\t8\t8\t68\t368\t1368\t1368\t1368\t136\t137\tQAAAAA\tGCGAAA\tOOOOxx\n1718\t4115\t0\t2\t8\t18\t18\t718\t1718\t1718\t1718\t36\t37\tCOAAAA\tHCGAAA\tVVVVxx\n140\t4116\t0\t0\t0\t0\t40\t140\t140\t140\t140\t80\t81\tKFAAAA\tICGAAA\tAAAAxx\n8341\t4117\t1\t1\t1\t1\t41\t341\t341\t3341\t8341\t82\t83\tVIAAAA\tJCGAAA\tHHHHxx\n72\t4118\t0\t0\t2\t12\t72\t72\t72\t72\t72\t144\t145\tUCAAAA\tKCGAAA\tOOOOxx\n6589\t4119\t1\t1\t9\t9\t89\t589\t589\t1589\t6589\t178\t179\tLTAAAA\tLCGAAA\tVVVVxx\n2024\t4120\t0\t0\t4\t4\t24\t24\t24\t2024\t2024\t48\t49\tWZAAAA\tMCGAAA\tAAAAxx\n8024\t4121\t0\t0\t4\t4\t24\t24\t24\t3024\t8024\t48\t49\tQWAAAA\tNCGAAA\tHHHHxx\n9564\t4122\t0\t0\t4\t4\t64\t564\t1564\t4564\t9564\t128\t129\tWDAAAA\tOCGAAA\tOOOOxx\n8625\t4123\t1\t1\t5\t5\t25\t625\t625\t3625\t8625\t50\t51\tTTAAAA\tPCGAAA\tVVVVxx\n2680\t4124\t0\t0\t0\t0\t80\t680\t680\t2680\t2680\t160\t161\tCZAAAA\tQCGAAA\tAAAAxx\n4323\t4125\t1\t3\t3\t3\t23\t323\t323\t4323\t4323\t46\t47\tHKAAAA\tRCGAAA\tHHHHxx\n8981\t4126\t1\t1\t1\t1\t81\t981\t981\t3981\t8981\t162\t163\tLHAAAA\tSCGAAA\tOOOOxx\n8909\t4127\t1\t1\t9\t9\t9\t909\t909\t3909\t8909\t18\t19\tREAAAA\tTCGAAA\tVVVVxx\n5288\t4128\t0\t0\t8\t8\t88\t288\t1288\t288\t5288\t176\t177\tKVAAAA\tUCGAAA\tAAAAxx\n2057\t4129\t1\t1\t7\t17\t57\t57\t57\t2057\t2057\t114\t115\tDBAAAA\tVCGAAA\tHHHHxx\n5931\t4130\t1\t3\t1\t11\t31\t931\t1931\t931\t5931\t62\t63\tDUAAAA\tWCGAAA\tOOOOxx\n9794\t4131\t0\t2\t4\t14\t94\t794\t1794\t4794\t9794\t188\t189\tSMAAAA\tXCGAAA\tVVVVxx\n1012\t4132\t0\t0\t2\t12\t12\t12\t1012\t1012\t1012\t24\t25\tYMAAAA\tYCGAAA\tAAAAxx\n5496\t4133\t0\t0\t6\t16\t96\t496\t1496\t496\t5496\t192\t193\tKDAAAA\tZCGAAA\tHHHHxx\n9182\t4134\t0\t2\t2\t2\t82\t182\t1182\t4182\t9182\t164\t165\tEPAAAA\tADGAAA\tOOOOxx\n5258\t4135\t0\t2\t8\t18\t58\t258\t1258\t258\t5258\t116\t117\tGUAAAA\tBDGAAA\tVVVVxx\n3050\t4136\t0\t2\t0\t10\t50\t50\t1050\t3050\t3050\t100\t101\tINAAAA\tCDGAAA\tAAAAxx\n2083\t4137\t1\t3\t3\t3\t83\t83\t83\t2083\t2083\t166\t167\tDCAAAA\tDDGAAA\tHHHHxx\n3069\t4138\t1\t1\t9\t9\t69\t69\t1069\t3069\t3069\t138\t139\tBOAAAA\tEDGAAA\tOOOOxx\n8459\t4139\t1\t3\t9\t19\t59\t459\t459\t3459\t8459\t118\t119\tJNAAAA\tFDGAAA\tVVVVxx\n169\t4140\t1\t1\t9\t9\t69\t169\t169\t169\t169\t138\t139\tNGAAAA\tGDGAAA\tAAAAxx\n4379\t4141\t1\t3\t9\t19\t79\t379\t379\t4379\t4379\t158\t159\tLMAAAA\tHDGAAA\tHHHHxx\n5126\t4142\t0\t2\t6\t6\t26\t126\t1126\t126\t5126\t52\t53\tEPAAAA\tIDGAAA\tOOOOxx\n1415\t4143\t1\t3\t5\t15\t15\t415\t1415\t1415\t1415\t30\t31\tLCAAAA\tJDGAAA\tVVVVxx\n1163\t4144\t1\t3\t3\t3\t63\t163\t1163\t1163\t1163\t126\t127\tTSAAAA\tKDGAAA\tAAAAxx\n3500\t4145\t0\t0\t0\t0\t0\t500\t1500\t3500\t3500\t0\t1\tQEAAAA\tLDGAAA\tHHHHxx\n7202\t4146\t0\t2\t2\t2\t2\t202\t1202\t2202\t7202\t4\t5\tARAAAA\tMDGAAA\tOOOOxx\n747\t4147\t1\t3\t7\t7\t47\t747\t747\t747\t747\t94\t95\tTCAAAA\tNDGAAA\tVVVVxx\n9264\t4148\t0\t0\t4\t4\t64\t264\t1264\t4264\t9264\t128\t129\tISAAAA\tODGAAA\tAAAAxx\n8548\t4149\t0\t0\t8\t8\t48\t548\t548\t3548\t8548\t96\t97\tUQAAAA\tPDGAAA\tHHHHxx\n4228\t4150\t0\t0\t8\t8\t28\t228\t228\t4228\t4228\t56\t57\tQGAAAA\tQDGAAA\tOOOOxx\n7122\t4151\t0\t2\t2\t2\t22\t122\t1122\t2122\t7122\t44\t45\tYNAAAA\tRDGAAA\tVVVVxx\n3395\t4152\t1\t3\t5\t15\t95\t395\t1395\t3395\t3395\t190\t191\tPAAAAA\tSDGAAA\tAAAAxx\n5674\t4153\t0\t2\t4\t14\t74\t674\t1674\t674\t5674\t148\t149\tGKAAAA\tTDGAAA\tHHHHxx\n7293\t4154\t1\t1\t3\t13\t93\t293\t1293\t2293\t7293\t186\t187\tNUAAAA\tUDGAAA\tOOOOxx\n737\t4155\t1\t1\t7\t17\t37\t737\t737\t737\t737\t74\t75\tJCAAAA\tVDGAAA\tVVVVxx\n9595\t4156\t1\t3\t5\t15\t95\t595\t1595\t4595\t9595\t190\t191\tBFAAAA\tWDGAAA\tAAAAxx\n594\t4157\t0\t2\t4\t14\t94\t594\t594\t594\t594\t188\t189\tWWAAAA\tXDGAAA\tHHHHxx\n5322\t4158\t0\t2\t2\t2\t22\t322\t1322\t322\t5322\t44\t45\tSWAAAA\tYDGAAA\tOOOOxx\n2933\t4159\t1\t1\t3\t13\t33\t933\t933\t2933\t2933\t66\t67\tVIAAAA\tZDGAAA\tVVVVxx\n4955\t4160\t1\t3\t5\t15\t55\t955\t955\t4955\t4955\t110\t111\tPIAAAA\tAEGAAA\tAAAAxx\n4073\t4161\t1\t1\t3\t13\t73\t73\t73\t4073\t4073\t146\t147\tRAAAAA\tBEGAAA\tHHHHxx\n7249\t4162\t1\t1\t9\t9\t49\t249\t1249\t2249\t7249\t98\t99\tVSAAAA\tCEGAAA\tOOOOxx\n192\t4163\t0\t0\t2\t12\t92\t192\t192\t192\t192\t184\t185\tKHAAAA\tDEGAAA\tVVVVxx\n2617\t4164\t1\t1\t7\t17\t17\t617\t617\t2617\t2617\t34\t35\tRWAAAA\tEEGAAA\tAAAAxx\n7409\t4165\t1\t1\t9\t9\t9\t409\t1409\t2409\t7409\t18\t19\tZYAAAA\tFEGAAA\tHHHHxx\n4903\t4166\t1\t3\t3\t3\t3\t903\t903\t4903\t4903\t6\t7\tPGAAAA\tGEGAAA\tOOOOxx\n9797\t4167\t1\t1\t7\t17\t97\t797\t1797\t4797\t9797\t194\t195\tVMAAAA\tHEGAAA\tVVVVxx\n9919\t4168\t1\t3\t9\t19\t19\t919\t1919\t4919\t9919\t38\t39\tNRAAAA\tIEGAAA\tAAAAxx\n1878\t4169\t0\t2\t8\t18\t78\t878\t1878\t1878\t1878\t156\t157\tGUAAAA\tJEGAAA\tHHHHxx\n4851\t4170\t1\t3\t1\t11\t51\t851\t851\t4851\t4851\t102\t103\tPEAAAA\tKEGAAA\tOOOOxx\n5514\t4171\t0\t2\t4\t14\t14\t514\t1514\t514\t5514\t28\t29\tCEAAAA\tLEGAAA\tVVVVxx\n2582\t4172\t0\t2\t2\t2\t82\t582\t582\t2582\t2582\t164\t165\tIVAAAA\tMEGAAA\tAAAAxx\n3564\t4173\t0\t0\t4\t4\t64\t564\t1564\t3564\t3564\t128\t129\tCHAAAA\tNEGAAA\tHHHHxx\n7085\t4174\t1\t1\t5\t5\t85\t85\t1085\t2085\t7085\t170\t171\tNMAAAA\tOEGAAA\tOOOOxx\n3619\t4175\t1\t3\t9\t19\t19\t619\t1619\t3619\t3619\t38\t39\tFJAAAA\tPEGAAA\tVVVVxx\n261\t4176\t1\t1\t1\t1\t61\t261\t261\t261\t261\t122\t123\tBKAAAA\tQEGAAA\tAAAAxx\n7338\t4177\t0\t2\t8\t18\t38\t338\t1338\t2338\t7338\t76\t77\tGWAAAA\tREGAAA\tHHHHxx\n4251\t4178\t1\t3\t1\t11\t51\t251\t251\t4251\t4251\t102\t103\tNHAAAA\tSEGAAA\tOOOOxx\n5360\t4179\t0\t0\t0\t0\t60\t360\t1360\t360\t5360\t120\t121\tEYAAAA\tTEGAAA\tVVVVxx\n5678\t4180\t0\t2\t8\t18\t78\t678\t1678\t678\t5678\t156\t157\tKKAAAA\tUEGAAA\tAAAAxx\n9162\t4181\t0\t2\t2\t2\t62\t162\t1162\t4162\t9162\t124\t125\tKOAAAA\tVEGAAA\tHHHHxx\n5920\t4182\t0\t0\t0\t0\t20\t920\t1920\t920\t5920\t40\t41\tSTAAAA\tWEGAAA\tOOOOxx\n7156\t4183\t0\t0\t6\t16\t56\t156\t1156\t2156\t7156\t112\t113\tGPAAAA\tXEGAAA\tVVVVxx\n4271\t4184\t1\t3\t1\t11\t71\t271\t271\t4271\t4271\t142\t143\tHIAAAA\tYEGAAA\tAAAAxx\n4698\t4185\t0\t2\t8\t18\t98\t698\t698\t4698\t4698\t196\t197\tSYAAAA\tZEGAAA\tHHHHxx\n1572\t4186\t0\t0\t2\t12\t72\t572\t1572\t1572\t1572\t144\t145\tMIAAAA\tAFGAAA\tOOOOxx\n6974\t4187\t0\t2\t4\t14\t74\t974\t974\t1974\t6974\t148\t149\tGIAAAA\tBFGAAA\tVVVVxx\n4291\t4188\t1\t3\t1\t11\t91\t291\t291\t4291\t4291\t182\t183\tBJAAAA\tCFGAAA\tAAAAxx\n4036\t4189\t0\t0\t6\t16\t36\t36\t36\t4036\t4036\t72\t73\tGZAAAA\tDFGAAA\tHHHHxx\n7473\t4190\t1\t1\t3\t13\t73\t473\t1473\t2473\t7473\t146\t147\tLBAAAA\tEFGAAA\tOOOOxx\n4786\t4191\t0\t2\t6\t6\t86\t786\t786\t4786\t4786\t172\t173\tCCAAAA\tFFGAAA\tVVVVxx\n2662\t4192\t0\t2\t2\t2\t62\t662\t662\t2662\t2662\t124\t125\tKYAAAA\tGFGAAA\tAAAAxx\n916\t4193\t0\t0\t6\t16\t16\t916\t916\t916\t916\t32\t33\tGJAAAA\tHFGAAA\tHHHHxx\n668\t4194\t0\t0\t8\t8\t68\t668\t668\t668\t668\t136\t137\tSZAAAA\tIFGAAA\tOOOOxx\n4874\t4195\t0\t2\t4\t14\t74\t874\t874\t4874\t4874\t148\t149\tMFAAAA\tJFGAAA\tVVVVxx\n3752\t4196\t0\t0\t2\t12\t52\t752\t1752\t3752\t3752\t104\t105\tIOAAAA\tKFGAAA\tAAAAxx\n4865\t4197\t1\t1\t5\t5\t65\t865\t865\t4865\t4865\t130\t131\tDFAAAA\tLFGAAA\tHHHHxx\n7052\t4198\t0\t0\t2\t12\t52\t52\t1052\t2052\t7052\t104\t105\tGLAAAA\tMFGAAA\tOOOOxx\n5712\t4199\t0\t0\t2\t12\t12\t712\t1712\t712\t5712\t24\t25\tSLAAAA\tNFGAAA\tVVVVxx\n31\t4200\t1\t3\t1\t11\t31\t31\t31\t31\t31\t62\t63\tFBAAAA\tOFGAAA\tAAAAxx\n4944\t4201\t0\t0\t4\t4\t44\t944\t944\t4944\t4944\t88\t89\tEIAAAA\tPFGAAA\tHHHHxx\n1435\t4202\t1\t3\t5\t15\t35\t435\t1435\t1435\t1435\t70\t71\tFDAAAA\tQFGAAA\tOOOOxx\n501\t4203\t1\t1\t1\t1\t1\t501\t501\t501\t501\t2\t3\tHTAAAA\tRFGAAA\tVVVVxx\n9401\t4204\t1\t1\t1\t1\t1\t401\t1401\t4401\t9401\t2\t3\tPXAAAA\tSFGAAA\tAAAAxx\n5014\t4205\t0\t2\t4\t14\t14\t14\t1014\t14\t5014\t28\t29\tWKAAAA\tTFGAAA\tHHHHxx\n9125\t4206\t1\t1\t5\t5\t25\t125\t1125\t4125\t9125\t50\t51\tZMAAAA\tUFGAAA\tOOOOxx\n6144\t4207\t0\t0\t4\t4\t44\t144\t144\t1144\t6144\t88\t89\tICAAAA\tVFGAAA\tVVVVxx\n1743\t4208\t1\t3\t3\t3\t43\t743\t1743\t1743\t1743\t86\t87\tBPAAAA\tWFGAAA\tAAAAxx\n4316\t4209\t0\t0\t6\t16\t16\t316\t316\t4316\t4316\t32\t33\tAKAAAA\tXFGAAA\tHHHHxx\n8212\t4210\t0\t0\t2\t12\t12\t212\t212\t3212\t8212\t24\t25\tWDAAAA\tYFGAAA\tOOOOxx\n7344\t4211\t0\t0\t4\t4\t44\t344\t1344\t2344\t7344\t88\t89\tMWAAAA\tZFGAAA\tVVVVxx\n2051\t4212\t1\t3\t1\t11\t51\t51\t51\t2051\t2051\t102\t103\tXAAAAA\tAGGAAA\tAAAAxx\n8131\t4213\t1\t3\t1\t11\t31\t131\t131\t3131\t8131\t62\t63\tTAAAAA\tBGGAAA\tHHHHxx\n7023\t4214\t1\t3\t3\t3\t23\t23\t1023\t2023\t7023\t46\t47\tDKAAAA\tCGGAAA\tOOOOxx\n9674\t4215\t0\t2\t4\t14\t74\t674\t1674\t4674\t9674\t148\t149\tCIAAAA\tDGGAAA\tVVVVxx\n4984\t4216\t0\t0\t4\t4\t84\t984\t984\t4984\t4984\t168\t169\tSJAAAA\tEGGAAA\tAAAAxx\n111\t4217\t1\t3\t1\t11\t11\t111\t111\t111\t111\t22\t23\tHEAAAA\tFGGAAA\tHHHHxx\n2296\t4218\t0\t0\t6\t16\t96\t296\t296\t2296\t2296\t192\t193\tIKAAAA\tGGGAAA\tOOOOxx\n5025\t4219\t1\t1\t5\t5\t25\t25\t1025\t25\t5025\t50\t51\tHLAAAA\tHGGAAA\tVVVVxx\n1756\t4220\t0\t0\t6\t16\t56\t756\t1756\t1756\t1756\t112\t113\tOPAAAA\tIGGAAA\tAAAAxx\n2885\t4221\t1\t1\t5\t5\t85\t885\t885\t2885\t2885\t170\t171\tZGAAAA\tJGGAAA\tHHHHxx\n2541\t4222\t1\t1\t1\t1\t41\t541\t541\t2541\t2541\t82\t83\tTTAAAA\tKGGAAA\tOOOOxx\n1919\t4223\t1\t3\t9\t19\t19\t919\t1919\t1919\t1919\t38\t39\tVVAAAA\tLGGAAA\tVVVVxx\n6496\t4224\t0\t0\t6\t16\t96\t496\t496\t1496\t6496\t192\t193\tWPAAAA\tMGGAAA\tAAAAxx\n6103\t4225\t1\t3\t3\t3\t3\t103\t103\t1103\t6103\t6\t7\tTAAAAA\tNGGAAA\tHHHHxx\n98\t4226\t0\t2\t8\t18\t98\t98\t98\t98\t98\t196\t197\tUDAAAA\tOGGAAA\tOOOOxx\n3727\t4227\t1\t3\t7\t7\t27\t727\t1727\t3727\t3727\t54\t55\tJNAAAA\tPGGAAA\tVVVVxx\n689\t4228\t1\t1\t9\t9\t89\t689\t689\t689\t689\t178\t179\tNAAAAA\tQGGAAA\tAAAAxx\n7181\t4229\t1\t1\t1\t1\t81\t181\t1181\t2181\t7181\t162\t163\tFQAAAA\tRGGAAA\tHHHHxx\n8447\t4230\t1\t3\t7\t7\t47\t447\t447\t3447\t8447\t94\t95\tXMAAAA\tSGGAAA\tOOOOxx\n4569\t4231\t1\t1\t9\t9\t69\t569\t569\t4569\t4569\t138\t139\tTTAAAA\tTGGAAA\tVVVVxx\n8844\t4232\t0\t0\t4\t4\t44\t844\t844\t3844\t8844\t88\t89\tECAAAA\tUGGAAA\tAAAAxx\n2436\t4233\t0\t0\t6\t16\t36\t436\t436\t2436\t2436\t72\t73\tSPAAAA\tVGGAAA\tHHHHxx\n391\t4234\t1\t3\t1\t11\t91\t391\t391\t391\t391\t182\t183\tBPAAAA\tWGGAAA\tOOOOxx\n3035\t4235\t1\t3\t5\t15\t35\t35\t1035\t3035\t3035\t70\t71\tTMAAAA\tXGGAAA\tVVVVxx\n7583\t4236\t1\t3\t3\t3\t83\t583\t1583\t2583\t7583\t166\t167\tRFAAAA\tYGGAAA\tAAAAxx\n1145\t4237\t1\t1\t5\t5\t45\t145\t1145\t1145\t1145\t90\t91\tBSAAAA\tZGGAAA\tHHHHxx\n93\t4238\t1\t1\t3\t13\t93\t93\t93\t93\t93\t186\t187\tPDAAAA\tAHGAAA\tOOOOxx\n8896\t4239\t0\t0\t6\t16\t96\t896\t896\t3896\t8896\t192\t193\tEEAAAA\tBHGAAA\tVVVVxx\n6719\t4240\t1\t3\t9\t19\t19\t719\t719\t1719\t6719\t38\t39\tLYAAAA\tCHGAAA\tAAAAxx\n7728\t4241\t0\t0\t8\t8\t28\t728\t1728\t2728\t7728\t56\t57\tGLAAAA\tDHGAAA\tHHHHxx\n1349\t4242\t1\t1\t9\t9\t49\t349\t1349\t1349\t1349\t98\t99\tXZAAAA\tEHGAAA\tOOOOxx\n5349\t4243\t1\t1\t9\t9\t49\t349\t1349\t349\t5349\t98\t99\tTXAAAA\tFHGAAA\tVVVVxx\n3040\t4244\t0\t0\t0\t0\t40\t40\t1040\t3040\t3040\t80\t81\tYMAAAA\tGHGAAA\tAAAAxx\n2414\t4245\t0\t2\t4\t14\t14\t414\t414\t2414\t2414\t28\t29\tWOAAAA\tHHGAAA\tHHHHxx\n5122\t4246\t0\t2\t2\t2\t22\t122\t1122\t122\t5122\t44\t45\tAPAAAA\tIHGAAA\tOOOOxx\n9553\t4247\t1\t1\t3\t13\t53\t553\t1553\t4553\t9553\t106\t107\tLDAAAA\tJHGAAA\tVVVVxx\n5987\t4248\t1\t3\t7\t7\t87\t987\t1987\t987\t5987\t174\t175\tHWAAAA\tKHGAAA\tAAAAxx\n5939\t4249\t1\t3\t9\t19\t39\t939\t1939\t939\t5939\t78\t79\tLUAAAA\tLHGAAA\tHHHHxx\n3525\t4250\t1\t1\t5\t5\t25\t525\t1525\t3525\t3525\t50\t51\tPFAAAA\tMHGAAA\tOOOOxx\n1371\t4251\t1\t3\t1\t11\t71\t371\t1371\t1371\t1371\t142\t143\tTAAAAA\tNHGAAA\tVVVVxx\n618\t4252\t0\t2\t8\t18\t18\t618\t618\t618\t618\t36\t37\tUXAAAA\tOHGAAA\tAAAAxx\n6529\t4253\t1\t1\t9\t9\t29\t529\t529\t1529\t6529\t58\t59\tDRAAAA\tPHGAAA\tHHHHxx\n4010\t4254\t0\t2\t0\t10\t10\t10\t10\t4010\t4010\t20\t21\tGYAAAA\tQHGAAA\tOOOOxx\n328\t4255\t0\t0\t8\t8\t28\t328\t328\t328\t328\t56\t57\tQMAAAA\tRHGAAA\tVVVVxx\n6121\t4256\t1\t1\t1\t1\t21\t121\t121\t1121\t6121\t42\t43\tLBAAAA\tSHGAAA\tAAAAxx\n3505\t4257\t1\t1\t5\t5\t5\t505\t1505\t3505\t3505\t10\t11\tVEAAAA\tTHGAAA\tHHHHxx\n2033\t4258\t1\t1\t3\t13\t33\t33\t33\t2033\t2033\t66\t67\tFAAAAA\tUHGAAA\tOOOOxx\n4724\t4259\t0\t0\t4\t4\t24\t724\t724\t4724\t4724\t48\t49\tSZAAAA\tVHGAAA\tVVVVxx\n8717\t4260\t1\t1\t7\t17\t17\t717\t717\t3717\t8717\t34\t35\tHXAAAA\tWHGAAA\tAAAAxx\n5639\t4261\t1\t3\t9\t19\t39\t639\t1639\t639\t5639\t78\t79\tXIAAAA\tXHGAAA\tHHHHxx\n3448\t4262\t0\t0\t8\t8\t48\t448\t1448\t3448\t3448\t96\t97\tQCAAAA\tYHGAAA\tOOOOxx\n2919\t4263\t1\t3\t9\t19\t19\t919\t919\t2919\t2919\t38\t39\tHIAAAA\tZHGAAA\tVVVVxx\n3417\t4264\t1\t1\t7\t17\t17\t417\t1417\t3417\t3417\t34\t35\tLBAAAA\tAIGAAA\tAAAAxx\n943\t4265\t1\t3\t3\t3\t43\t943\t943\t943\t943\t86\t87\tHKAAAA\tBIGAAA\tHHHHxx\n775\t4266\t1\t3\t5\t15\t75\t775\t775\t775\t775\t150\t151\tVDAAAA\tCIGAAA\tOOOOxx\n2333\t4267\t1\t1\t3\t13\t33\t333\t333\t2333\t2333\t66\t67\tTLAAAA\tDIGAAA\tVVVVxx\n4801\t4268\t1\t1\t1\t1\t1\t801\t801\t4801\t4801\t2\t3\tRCAAAA\tEIGAAA\tAAAAxx\n7169\t4269\t1\t1\t9\t9\t69\t169\t1169\t2169\t7169\t138\t139\tTPAAAA\tFIGAAA\tHHHHxx\n2840\t4270\t0\t0\t0\t0\t40\t840\t840\t2840\t2840\t80\t81\tGFAAAA\tGIGAAA\tOOOOxx\n9034\t4271\t0\t2\t4\t14\t34\t34\t1034\t4034\t9034\t68\t69\tMJAAAA\tHIGAAA\tVVVVxx\n6154\t4272\t0\t2\t4\t14\t54\t154\t154\t1154\t6154\t108\t109\tSCAAAA\tIIGAAA\tAAAAxx\n1412\t4273\t0\t0\t2\t12\t12\t412\t1412\t1412\t1412\t24\t25\tICAAAA\tJIGAAA\tHHHHxx\n2263\t4274\t1\t3\t3\t3\t63\t263\t263\t2263\t2263\t126\t127\tBJAAAA\tKIGAAA\tOOOOxx\n7118\t4275\t0\t2\t8\t18\t18\t118\t1118\t2118\t7118\t36\t37\tUNAAAA\tLIGAAA\tVVVVxx\n1526\t4276\t0\t2\t6\t6\t26\t526\t1526\t1526\t1526\t52\t53\tSGAAAA\tMIGAAA\tAAAAxx\n491\t4277\t1\t3\t1\t11\t91\t491\t491\t491\t491\t182\t183\tXSAAAA\tNIGAAA\tHHHHxx\n9732\t4278\t0\t0\t2\t12\t32\t732\t1732\t4732\t9732\t64\t65\tIKAAAA\tOIGAAA\tOOOOxx\n7067\t4279\t1\t3\t7\t7\t67\t67\t1067\t2067\t7067\t134\t135\tVLAAAA\tPIGAAA\tVVVVxx\n212\t4280\t0\t0\t2\t12\t12\t212\t212\t212\t212\t24\t25\tEIAAAA\tQIGAAA\tAAAAxx\n1955\t4281\t1\t3\t5\t15\t55\t955\t1955\t1955\t1955\t110\t111\tFXAAAA\tRIGAAA\tHHHHxx\n3303\t4282\t1\t3\t3\t3\t3\t303\t1303\t3303\t3303\t6\t7\tBXAAAA\tSIGAAA\tOOOOxx\n2715\t4283\t1\t3\t5\t15\t15\t715\t715\t2715\t2715\t30\t31\tLAAAAA\tTIGAAA\tVVVVxx\n8168\t4284\t0\t0\t8\t8\t68\t168\t168\t3168\t8168\t136\t137\tECAAAA\tUIGAAA\tAAAAxx\n6799\t4285\t1\t3\t9\t19\t99\t799\t799\t1799\t6799\t198\t199\tNBAAAA\tVIGAAA\tHHHHxx\n5080\t4286\t0\t0\t0\t0\t80\t80\t1080\t80\t5080\t160\t161\tKNAAAA\tWIGAAA\tOOOOxx\n4939\t4287\t1\t3\t9\t19\t39\t939\t939\t4939\t4939\t78\t79\tZHAAAA\tXIGAAA\tVVVVxx\n6604\t4288\t0\t0\t4\t4\t4\t604\t604\t1604\t6604\t8\t9\tAUAAAA\tYIGAAA\tAAAAxx\n6531\t4289\t1\t3\t1\t11\t31\t531\t531\t1531\t6531\t62\t63\tFRAAAA\tZIGAAA\tHHHHxx\n9948\t4290\t0\t0\t8\t8\t48\t948\t1948\t4948\t9948\t96\t97\tQSAAAA\tAJGAAA\tOOOOxx\n7923\t4291\t1\t3\t3\t3\t23\t923\t1923\t2923\t7923\t46\t47\tTSAAAA\tBJGAAA\tVVVVxx\n9905\t4292\t1\t1\t5\t5\t5\t905\t1905\t4905\t9905\t10\t11\tZQAAAA\tCJGAAA\tAAAAxx\n340\t4293\t0\t0\t0\t0\t40\t340\t340\t340\t340\t80\t81\tCNAAAA\tDJGAAA\tHHHHxx\n1721\t4294\t1\t1\t1\t1\t21\t721\t1721\t1721\t1721\t42\t43\tFOAAAA\tEJGAAA\tOOOOxx\n9047\t4295\t1\t3\t7\t7\t47\t47\t1047\t4047\t9047\t94\t95\tZJAAAA\tFJGAAA\tVVVVxx\n4723\t4296\t1\t3\t3\t3\t23\t723\t723\t4723\t4723\t46\t47\tRZAAAA\tGJGAAA\tAAAAxx\n5748\t4297\t0\t0\t8\t8\t48\t748\t1748\t748\t5748\t96\t97\tCNAAAA\tHJGAAA\tHHHHxx\n6845\t4298\t1\t1\t5\t5\t45\t845\t845\t1845\t6845\t90\t91\tHDAAAA\tIJGAAA\tOOOOxx\n1556\t4299\t0\t0\t6\t16\t56\t556\t1556\t1556\t1556\t112\t113\tWHAAAA\tJJGAAA\tVVVVxx\n9505\t4300\t1\t1\t5\t5\t5\t505\t1505\t4505\t9505\t10\t11\tPBAAAA\tKJGAAA\tAAAAxx\n3573\t4301\t1\t1\t3\t13\t73\t573\t1573\t3573\t3573\t146\t147\tLHAAAA\tLJGAAA\tHHHHxx\n3785\t4302\t1\t1\t5\t5\t85\t785\t1785\t3785\t3785\t170\t171\tPPAAAA\tMJGAAA\tOOOOxx\n2772\t4303\t0\t0\t2\t12\t72\t772\t772\t2772\t2772\t144\t145\tQCAAAA\tNJGAAA\tVVVVxx\n7282\t4304\t0\t2\t2\t2\t82\t282\t1282\t2282\t7282\t164\t165\tCUAAAA\tOJGAAA\tAAAAxx\n8106\t4305\t0\t2\t6\t6\t6\t106\t106\t3106\t8106\t12\t13\tUZAAAA\tPJGAAA\tHHHHxx\n2847\t4306\t1\t3\t7\t7\t47\t847\t847\t2847\t2847\t94\t95\tNFAAAA\tQJGAAA\tOOOOxx\n9803\t4307\t1\t3\t3\t3\t3\t803\t1803\t4803\t9803\t6\t7\tBNAAAA\tRJGAAA\tVVVVxx\n7719\t4308\t1\t3\t9\t19\t19\t719\t1719\t2719\t7719\t38\t39\tXKAAAA\tSJGAAA\tAAAAxx\n4649\t4309\t1\t1\t9\t9\t49\t649\t649\t4649\t4649\t98\t99\tVWAAAA\tTJGAAA\tHHHHxx\n6196\t4310\t0\t0\t6\t16\t96\t196\t196\t1196\t6196\t192\t193\tIEAAAA\tUJGAAA\tOOOOxx\n6026\t4311\t0\t2\t6\t6\t26\t26\t26\t1026\t6026\t52\t53\tUXAAAA\tVJGAAA\tVVVVxx\n1646\t4312\t0\t2\t6\t6\t46\t646\t1646\t1646\t1646\t92\t93\tILAAAA\tWJGAAA\tAAAAxx\n6526\t4313\t0\t2\t6\t6\t26\t526\t526\t1526\t6526\t52\t53\tARAAAA\tXJGAAA\tHHHHxx\n5110\t4314\t0\t2\t0\t10\t10\t110\t1110\t110\t5110\t20\t21\tOOAAAA\tYJGAAA\tOOOOxx\n3946\t4315\t0\t2\t6\t6\t46\t946\t1946\t3946\t3946\t92\t93\tUVAAAA\tZJGAAA\tVVVVxx\n445\t4316\t1\t1\t5\t5\t45\t445\t445\t445\t445\t90\t91\tDRAAAA\tAKGAAA\tAAAAxx\n3249\t4317\t1\t1\t9\t9\t49\t249\t1249\t3249\t3249\t98\t99\tZUAAAA\tBKGAAA\tHHHHxx\n2501\t4318\t1\t1\t1\t1\t1\t501\t501\t2501\t2501\t2\t3\tFSAAAA\tCKGAAA\tOOOOxx\n3243\t4319\t1\t3\t3\t3\t43\t243\t1243\t3243\t3243\t86\t87\tTUAAAA\tDKGAAA\tVVVVxx\n4701\t4320\t1\t1\t1\t1\t1\t701\t701\t4701\t4701\t2\t3\tVYAAAA\tEKGAAA\tAAAAxx\n472\t4321\t0\t0\t2\t12\t72\t472\t472\t472\t472\t144\t145\tESAAAA\tFKGAAA\tHHHHxx\n3356\t4322\t0\t0\t6\t16\t56\t356\t1356\t3356\t3356\t112\t113\tCZAAAA\tGKGAAA\tOOOOxx\n9967\t4323\t1\t3\t7\t7\t67\t967\t1967\t4967\t9967\t134\t135\tJTAAAA\tHKGAAA\tVVVVxx\n4292\t4324\t0\t0\t2\t12\t92\t292\t292\t4292\t4292\t184\t185\tCJAAAA\tIKGAAA\tAAAAxx\n7005\t4325\t1\t1\t5\t5\t5\t5\t1005\t2005\t7005\t10\t11\tLJAAAA\tJKGAAA\tHHHHxx\n6267\t4326\t1\t3\t7\t7\t67\t267\t267\t1267\t6267\t134\t135\tBHAAAA\tKKGAAA\tOOOOxx\n6678\t4327\t0\t2\t8\t18\t78\t678\t678\t1678\t6678\t156\t157\tWWAAAA\tLKGAAA\tVVVVxx\n6083\t4328\t1\t3\t3\t3\t83\t83\t83\t1083\t6083\t166\t167\tZZAAAA\tMKGAAA\tAAAAxx\n760\t4329\t0\t0\t0\t0\t60\t760\t760\t760\t760\t120\t121\tGDAAAA\tNKGAAA\tHHHHxx\n7833\t4330\t1\t1\t3\t13\t33\t833\t1833\t2833\t7833\t66\t67\tHPAAAA\tOKGAAA\tOOOOxx\n2877\t4331\t1\t1\t7\t17\t77\t877\t877\t2877\t2877\t154\t155\tRGAAAA\tPKGAAA\tVVVVxx\n8810\t4332\t0\t2\t0\t10\t10\t810\t810\t3810\t8810\t20\t21\tWAAAAA\tQKGAAA\tAAAAxx\n1560\t4333\t0\t0\t0\t0\t60\t560\t1560\t1560\t1560\t120\t121\tAIAAAA\tRKGAAA\tHHHHxx\n1367\t4334\t1\t3\t7\t7\t67\t367\t1367\t1367\t1367\t134\t135\tPAAAAA\tSKGAAA\tOOOOxx\n8756\t4335\t0\t0\t6\t16\t56\t756\t756\t3756\t8756\t112\t113\tUYAAAA\tTKGAAA\tVVVVxx\n1346\t4336\t0\t2\t6\t6\t46\t346\t1346\t1346\t1346\t92\t93\tUZAAAA\tUKGAAA\tAAAAxx\n6449\t4337\t1\t1\t9\t9\t49\t449\t449\t1449\t6449\t98\t99\tBOAAAA\tVKGAAA\tHHHHxx\n6658\t4338\t0\t2\t8\t18\t58\t658\t658\t1658\t6658\t116\t117\tCWAAAA\tWKGAAA\tOOOOxx\n6745\t4339\t1\t1\t5\t5\t45\t745\t745\t1745\t6745\t90\t91\tLZAAAA\tXKGAAA\tVVVVxx\n4866\t4340\t0\t2\t6\t6\t66\t866\t866\t4866\t4866\t132\t133\tEFAAAA\tYKGAAA\tAAAAxx\n14\t4341\t0\t2\t4\t14\t14\t14\t14\t14\t14\t28\t29\tOAAAAA\tZKGAAA\tHHHHxx\n4506\t4342\t0\t2\t6\t6\t6\t506\t506\t4506\t4506\t12\t13\tIRAAAA\tALGAAA\tOOOOxx\n1923\t4343\t1\t3\t3\t3\t23\t923\t1923\t1923\t1923\t46\t47\tZVAAAA\tBLGAAA\tVVVVxx\n8365\t4344\t1\t1\t5\t5\t65\t365\t365\t3365\t8365\t130\t131\tTJAAAA\tCLGAAA\tAAAAxx\n1279\t4345\t1\t3\t9\t19\t79\t279\t1279\t1279\t1279\t158\t159\tFXAAAA\tDLGAAA\tHHHHxx\n7666\t4346\t0\t2\t6\t6\t66\t666\t1666\t2666\t7666\t132\t133\tWIAAAA\tELGAAA\tOOOOxx\n7404\t4347\t0\t0\t4\t4\t4\t404\t1404\t2404\t7404\t8\t9\tUYAAAA\tFLGAAA\tVVVVxx\n65\t4348\t1\t1\t5\t5\t65\t65\t65\t65\t65\t130\t131\tNCAAAA\tGLGAAA\tAAAAxx\n5820\t4349\t0\t0\t0\t0\t20\t820\t1820\t820\t5820\t40\t41\tWPAAAA\tHLGAAA\tHHHHxx\n459\t4350\t1\t3\t9\t19\t59\t459\t459\t459\t459\t118\t119\tRRAAAA\tILGAAA\tOOOOxx\n4787\t4351\t1\t3\t7\t7\t87\t787\t787\t4787\t4787\t174\t175\tDCAAAA\tJLGAAA\tVVVVxx\n5631\t4352\t1\t3\t1\t11\t31\t631\t1631\t631\t5631\t62\t63\tPIAAAA\tKLGAAA\tAAAAxx\n9717\t4353\t1\t1\t7\t17\t17\t717\t1717\t4717\t9717\t34\t35\tTJAAAA\tLLGAAA\tHHHHxx\n2560\t4354\t0\t0\t0\t0\t60\t560\t560\t2560\t2560\t120\t121\tMUAAAA\tMLGAAA\tOOOOxx\n8295\t4355\t1\t3\t5\t15\t95\t295\t295\t3295\t8295\t190\t191\tBHAAAA\tNLGAAA\tVVVVxx\n3596\t4356\t0\t0\t6\t16\t96\t596\t1596\t3596\t3596\t192\t193\tIIAAAA\tOLGAAA\tAAAAxx\n2023\t4357\t1\t3\t3\t3\t23\t23\t23\t2023\t2023\t46\t47\tVZAAAA\tPLGAAA\tHHHHxx\n5055\t4358\t1\t3\t5\t15\t55\t55\t1055\t55\t5055\t110\t111\tLMAAAA\tQLGAAA\tOOOOxx\n763\t4359\t1\t3\t3\t3\t63\t763\t763\t763\t763\t126\t127\tJDAAAA\tRLGAAA\tVVVVxx\n6733\t4360\t1\t1\t3\t13\t33\t733\t733\t1733\t6733\t66\t67\tZYAAAA\tSLGAAA\tAAAAxx\n9266\t4361\t0\t2\t6\t6\t66\t266\t1266\t4266\t9266\t132\t133\tKSAAAA\tTLGAAA\tHHHHxx\n4479\t4362\t1\t3\t9\t19\t79\t479\t479\t4479\t4479\t158\t159\tHQAAAA\tULGAAA\tOOOOxx\n1816\t4363\t0\t0\t6\t16\t16\t816\t1816\t1816\t1816\t32\t33\tWRAAAA\tVLGAAA\tVVVVxx\n899\t4364\t1\t3\t9\t19\t99\t899\t899\t899\t899\t198\t199\tPIAAAA\tWLGAAA\tAAAAxx\n230\t4365\t0\t2\t0\t10\t30\t230\t230\t230\t230\t60\t61\tWIAAAA\tXLGAAA\tHHHHxx\n5362\t4366\t0\t2\t2\t2\t62\t362\t1362\t362\t5362\t124\t125\tGYAAAA\tYLGAAA\tOOOOxx\n1609\t4367\t1\t1\t9\t9\t9\t609\t1609\t1609\t1609\t18\t19\tXJAAAA\tZLGAAA\tVVVVxx\n6750\t4368\t0\t2\t0\t10\t50\t750\t750\t1750\t6750\t100\t101\tQZAAAA\tAMGAAA\tAAAAxx\n9704\t4369\t0\t0\t4\t4\t4\t704\t1704\t4704\t9704\t8\t9\tGJAAAA\tBMGAAA\tHHHHxx\n3991\t4370\t1\t3\t1\t11\t91\t991\t1991\t3991\t3991\t182\t183\tNXAAAA\tCMGAAA\tOOOOxx\n3959\t4371\t1\t3\t9\t19\t59\t959\t1959\t3959\t3959\t118\t119\tHWAAAA\tDMGAAA\tVVVVxx\n9021\t4372\t1\t1\t1\t1\t21\t21\t1021\t4021\t9021\t42\t43\tZIAAAA\tEMGAAA\tAAAAxx\n7585\t4373\t1\t1\t5\t5\t85\t585\t1585\t2585\t7585\t170\t171\tTFAAAA\tFMGAAA\tHHHHxx\n7083\t4374\t1\t3\t3\t3\t83\t83\t1083\t2083\t7083\t166\t167\tLMAAAA\tGMGAAA\tOOOOxx\n7688\t4375\t0\t0\t8\t8\t88\t688\t1688\t2688\t7688\t176\t177\tSJAAAA\tHMGAAA\tVVVVxx\n2673\t4376\t1\t1\t3\t13\t73\t673\t673\t2673\t2673\t146\t147\tVYAAAA\tIMGAAA\tAAAAxx\n3554\t4377\t0\t2\t4\t14\t54\t554\t1554\t3554\t3554\t108\t109\tSGAAAA\tJMGAAA\tHHHHxx\n7416\t4378\t0\t0\t6\t16\t16\t416\t1416\t2416\t7416\t32\t33\tGZAAAA\tKMGAAA\tOOOOxx\n5672\t4379\t0\t0\t2\t12\t72\t672\t1672\t672\t5672\t144\t145\tEKAAAA\tLMGAAA\tVVVVxx\n1355\t4380\t1\t3\t5\t15\t55\t355\t1355\t1355\t1355\t110\t111\tDAAAAA\tMMGAAA\tAAAAxx\n3149\t4381\t1\t1\t9\t9\t49\t149\t1149\t3149\t3149\t98\t99\tDRAAAA\tNMGAAA\tHHHHxx\n5811\t4382\t1\t3\t1\t11\t11\t811\t1811\t811\t5811\t22\t23\tNPAAAA\tOMGAAA\tOOOOxx\n3759\t4383\t1\t3\t9\t19\t59\t759\t1759\t3759\t3759\t118\t119\tPOAAAA\tPMGAAA\tVVVVxx\n5634\t4384\t0\t2\t4\t14\t34\t634\t1634\t634\t5634\t68\t69\tSIAAAA\tQMGAAA\tAAAAxx\n8617\t4385\t1\t1\t7\t17\t17\t617\t617\t3617\t8617\t34\t35\tLTAAAA\tRMGAAA\tHHHHxx\n8949\t4386\t1\t1\t9\t9\t49\t949\t949\t3949\t8949\t98\t99\tFGAAAA\tSMGAAA\tOOOOxx\n3964\t4387\t0\t0\t4\t4\t64\t964\t1964\t3964\t3964\t128\t129\tMWAAAA\tTMGAAA\tVVVVxx\n3852\t4388\t0\t0\t2\t12\t52\t852\t1852\t3852\t3852\t104\t105\tESAAAA\tUMGAAA\tAAAAxx\n1555\t4389\t1\t3\t5\t15\t55\t555\t1555\t1555\t1555\t110\t111\tVHAAAA\tVMGAAA\tHHHHxx\n6536\t4390\t0\t0\t6\t16\t36\t536\t536\t1536\t6536\t72\t73\tKRAAAA\tWMGAAA\tOOOOxx\n4779\t4391\t1\t3\t9\t19\t79\t779\t779\t4779\t4779\t158\t159\tVBAAAA\tXMGAAA\tVVVVxx\n1893\t4392\t1\t1\t3\t13\t93\t893\t1893\t1893\t1893\t186\t187\tVUAAAA\tYMGAAA\tAAAAxx\n9358\t4393\t0\t2\t8\t18\t58\t358\t1358\t4358\t9358\t116\t117\tYVAAAA\tZMGAAA\tHHHHxx\n7438\t4394\t0\t2\t8\t18\t38\t438\t1438\t2438\t7438\t76\t77\tCAAAAA\tANGAAA\tOOOOxx\n941\t4395\t1\t1\t1\t1\t41\t941\t941\t941\t941\t82\t83\tFKAAAA\tBNGAAA\tVVVVxx\n4844\t4396\t0\t0\t4\t4\t44\t844\t844\t4844\t4844\t88\t89\tIEAAAA\tCNGAAA\tAAAAxx\n4745\t4397\t1\t1\t5\t5\t45\t745\t745\t4745\t4745\t90\t91\tNAAAAA\tDNGAAA\tHHHHxx\n1017\t4398\t1\t1\t7\t17\t17\t17\t1017\t1017\t1017\t34\t35\tDNAAAA\tENGAAA\tOOOOxx\n327\t4399\t1\t3\t7\t7\t27\t327\t327\t327\t327\t54\t55\tPMAAAA\tFNGAAA\tVVVVxx\n3152\t4400\t0\t0\t2\t12\t52\t152\t1152\t3152\t3152\t104\t105\tGRAAAA\tGNGAAA\tAAAAxx\n4711\t4401\t1\t3\t1\t11\t11\t711\t711\t4711\t4711\t22\t23\tFZAAAA\tHNGAAA\tHHHHxx\n141\t4402\t1\t1\t1\t1\t41\t141\t141\t141\t141\t82\t83\tLFAAAA\tINGAAA\tOOOOxx\n1303\t4403\t1\t3\t3\t3\t3\t303\t1303\t1303\t1303\t6\t7\tDYAAAA\tJNGAAA\tVVVVxx\n8873\t4404\t1\t1\t3\t13\t73\t873\t873\t3873\t8873\t146\t147\tHDAAAA\tKNGAAA\tAAAAxx\n8481\t4405\t1\t1\t1\t1\t81\t481\t481\t3481\t8481\t162\t163\tFOAAAA\tLNGAAA\tHHHHxx\n5445\t4406\t1\t1\t5\t5\t45\t445\t1445\t445\t5445\t90\t91\tLBAAAA\tMNGAAA\tOOOOxx\n7868\t4407\t0\t0\t8\t8\t68\t868\t1868\t2868\t7868\t136\t137\tQQAAAA\tNNGAAA\tVVVVxx\n6722\t4408\t0\t2\t2\t2\t22\t722\t722\t1722\t6722\t44\t45\tOYAAAA\tONGAAA\tAAAAxx\n6628\t4409\t0\t0\t8\t8\t28\t628\t628\t1628\t6628\t56\t57\tYUAAAA\tPNGAAA\tHHHHxx\n7738\t4410\t0\t2\t8\t18\t38\t738\t1738\t2738\t7738\t76\t77\tQLAAAA\tQNGAAA\tOOOOxx\n1018\t4411\t0\t2\t8\t18\t18\t18\t1018\t1018\t1018\t36\t37\tENAAAA\tRNGAAA\tVVVVxx\n3296\t4412\t0\t0\t6\t16\t96\t296\t1296\t3296\t3296\t192\t193\tUWAAAA\tSNGAAA\tAAAAxx\n1946\t4413\t0\t2\t6\t6\t46\t946\t1946\t1946\t1946\t92\t93\tWWAAAA\tTNGAAA\tHHHHxx\n6603\t4414\t1\t3\t3\t3\t3\t603\t603\t1603\t6603\t6\t7\tZTAAAA\tUNGAAA\tOOOOxx\n3562\t4415\t0\t2\t2\t2\t62\t562\t1562\t3562\t3562\t124\t125\tAHAAAA\tVNGAAA\tVVVVxx\n1147\t4416\t1\t3\t7\t7\t47\t147\t1147\t1147\t1147\t94\t95\tDSAAAA\tWNGAAA\tAAAAxx\n6031\t4417\t1\t3\t1\t11\t31\t31\t31\t1031\t6031\t62\t63\tZXAAAA\tXNGAAA\tHHHHxx\n6484\t4418\t0\t0\t4\t4\t84\t484\t484\t1484\t6484\t168\t169\tKPAAAA\tYNGAAA\tOOOOxx\n496\t4419\t0\t0\t6\t16\t96\t496\t496\t496\t496\t192\t193\tCTAAAA\tZNGAAA\tVVVVxx\n4563\t4420\t1\t3\t3\t3\t63\t563\t563\t4563\t4563\t126\t127\tNTAAAA\tAOGAAA\tAAAAxx\n1037\t4421\t1\t1\t7\t17\t37\t37\t1037\t1037\t1037\t74\t75\tXNAAAA\tBOGAAA\tHHHHxx\n9672\t4422\t0\t0\t2\t12\t72\t672\t1672\t4672\t9672\t144\t145\tAIAAAA\tCOGAAA\tOOOOxx\n9053\t4423\t1\t1\t3\t13\t53\t53\t1053\t4053\t9053\t106\t107\tFKAAAA\tDOGAAA\tVVVVxx\n2523\t4424\t1\t3\t3\t3\t23\t523\t523\t2523\t2523\t46\t47\tBTAAAA\tEOGAAA\tAAAAxx\n8519\t4425\t1\t3\t9\t19\t19\t519\t519\t3519\t8519\t38\t39\tRPAAAA\tFOGAAA\tHHHHxx\n8190\t4426\t0\t2\t0\t10\t90\t190\t190\t3190\t8190\t180\t181\tADAAAA\tGOGAAA\tOOOOxx\n2068\t4427\t0\t0\t8\t8\t68\t68\t68\t2068\t2068\t136\t137\tOBAAAA\tHOGAAA\tVVVVxx\n8569\t4428\t1\t1\t9\t9\t69\t569\t569\t3569\t8569\t138\t139\tPRAAAA\tIOGAAA\tAAAAxx\n6535\t4429\t1\t3\t5\t15\t35\t535\t535\t1535\t6535\t70\t71\tJRAAAA\tJOGAAA\tHHHHxx\n1810\t4430\t0\t2\t0\t10\t10\t810\t1810\t1810\t1810\t20\t21\tQRAAAA\tKOGAAA\tOOOOxx\n3099\t4431\t1\t3\t9\t19\t99\t99\t1099\t3099\t3099\t198\t199\tFPAAAA\tLOGAAA\tVVVVxx\n7466\t4432\t0\t2\t6\t6\t66\t466\t1466\t2466\t7466\t132\t133\tEBAAAA\tMOGAAA\tAAAAxx\n4017\t4433\t1\t1\t7\t17\t17\t17\t17\t4017\t4017\t34\t35\tNYAAAA\tNOGAAA\tHHHHxx\n1097\t4434\t1\t1\t7\t17\t97\t97\t1097\t1097\t1097\t194\t195\tFQAAAA\tOOGAAA\tOOOOxx\n7686\t4435\t0\t2\t6\t6\t86\t686\t1686\t2686\t7686\t172\t173\tQJAAAA\tPOGAAA\tVVVVxx\n6742\t4436\t0\t2\t2\t2\t42\t742\t742\t1742\t6742\t84\t85\tIZAAAA\tQOGAAA\tAAAAxx\n5966\t4437\t0\t2\t6\t6\t66\t966\t1966\t966\t5966\t132\t133\tMVAAAA\tROGAAA\tHHHHxx\n3632\t4438\t0\t0\t2\t12\t32\t632\t1632\t3632\t3632\t64\t65\tSJAAAA\tSOGAAA\tOOOOxx\n8837\t4439\t1\t1\t7\t17\t37\t837\t837\t3837\t8837\t74\t75\tXBAAAA\tTOGAAA\tVVVVxx\n1667\t4440\t1\t3\t7\t7\t67\t667\t1667\t1667\t1667\t134\t135\tDMAAAA\tUOGAAA\tAAAAxx\n8833\t4441\t1\t1\t3\t13\t33\t833\t833\t3833\t8833\t66\t67\tTBAAAA\tVOGAAA\tHHHHxx\n9805\t4442\t1\t1\t5\t5\t5\t805\t1805\t4805\t9805\t10\t11\tDNAAAA\tWOGAAA\tOOOOxx\n3650\t4443\t0\t2\t0\t10\t50\t650\t1650\t3650\t3650\t100\t101\tKKAAAA\tXOGAAA\tVVVVxx\n2237\t4444\t1\t1\t7\t17\t37\t237\t237\t2237\t2237\t74\t75\tBIAAAA\tYOGAAA\tAAAAxx\n9980\t4445\t0\t0\t0\t0\t80\t980\t1980\t4980\t9980\t160\t161\tWTAAAA\tZOGAAA\tHHHHxx\n2861\t4446\t1\t1\t1\t1\t61\t861\t861\t2861\t2861\t122\t123\tBGAAAA\tAPGAAA\tOOOOxx\n1334\t4447\t0\t2\t4\t14\t34\t334\t1334\t1334\t1334\t68\t69\tIZAAAA\tBPGAAA\tVVVVxx\n842\t4448\t0\t2\t2\t2\t42\t842\t842\t842\t842\t84\t85\tKGAAAA\tCPGAAA\tAAAAxx\n1116\t4449\t0\t0\t6\t16\t16\t116\t1116\t1116\t1116\t32\t33\tYQAAAA\tDPGAAA\tHHHHxx\n4055\t4450\t1\t3\t5\t15\t55\t55\t55\t4055\t4055\t110\t111\tZZAAAA\tEPGAAA\tOOOOxx\n3842\t4451\t0\t2\t2\t2\t42\t842\t1842\t3842\t3842\t84\t85\tURAAAA\tFPGAAA\tVVVVxx\n1886\t4452\t0\t2\t6\t6\t86\t886\t1886\t1886\t1886\t172\t173\tOUAAAA\tGPGAAA\tAAAAxx\n8589\t4453\t1\t1\t9\t9\t89\t589\t589\t3589\t8589\t178\t179\tJSAAAA\tHPGAAA\tHHHHxx\n5873\t4454\t1\t1\t3\t13\t73\t873\t1873\t873\t5873\t146\t147\tXRAAAA\tIPGAAA\tOOOOxx\n7711\t4455\t1\t3\t1\t11\t11\t711\t1711\t2711\t7711\t22\t23\tPKAAAA\tJPGAAA\tVVVVxx\n911\t4456\t1\t3\t1\t11\t11\t911\t911\t911\t911\t22\t23\tBJAAAA\tKPGAAA\tAAAAxx\n5837\t4457\t1\t1\t7\t17\t37\t837\t1837\t837\t5837\t74\t75\tNQAAAA\tLPGAAA\tHHHHxx\n897\t4458\t1\t1\t7\t17\t97\t897\t897\t897\t897\t194\t195\tNIAAAA\tMPGAAA\tOOOOxx\n4299\t4459\t1\t3\t9\t19\t99\t299\t299\t4299\t4299\t198\t199\tJJAAAA\tNPGAAA\tVVVVxx\n7774\t4460\t0\t2\t4\t14\t74\t774\t1774\t2774\t7774\t148\t149\tANAAAA\tOPGAAA\tAAAAxx\n7832\t4461\t0\t0\t2\t12\t32\t832\t1832\t2832\t7832\t64\t65\tGPAAAA\tPPGAAA\tHHHHxx\n9915\t4462\t1\t3\t5\t15\t15\t915\t1915\t4915\t9915\t30\t31\tJRAAAA\tQPGAAA\tOOOOxx\n9\t4463\t1\t1\t9\t9\t9\t9\t9\t9\t9\t18\t19\tJAAAAA\tRPGAAA\tVVVVxx\n9675\t4464\t1\t3\t5\t15\t75\t675\t1675\t4675\t9675\t150\t151\tDIAAAA\tSPGAAA\tAAAAxx\n7953\t4465\t1\t1\t3\t13\t53\t953\t1953\t2953\t7953\t106\t107\tXTAAAA\tTPGAAA\tHHHHxx\n8912\t4466\t0\t0\t2\t12\t12\t912\t912\t3912\t8912\t24\t25\tUEAAAA\tUPGAAA\tOOOOxx\n4188\t4467\t0\t0\t8\t8\t88\t188\t188\t4188\t4188\t176\t177\tCFAAAA\tVPGAAA\tVVVVxx\n8446\t4468\t0\t2\t6\t6\t46\t446\t446\t3446\t8446\t92\t93\tWMAAAA\tWPGAAA\tAAAAxx\n1600\t4469\t0\t0\t0\t0\t0\t600\t1600\t1600\t1600\t0\t1\tOJAAAA\tXPGAAA\tHHHHxx\n43\t4470\t1\t3\t3\t3\t43\t43\t43\t43\t43\t86\t87\tRBAAAA\tYPGAAA\tOOOOxx\n544\t4471\t0\t0\t4\t4\t44\t544\t544\t544\t544\t88\t89\tYUAAAA\tZPGAAA\tVVVVxx\n6977\t4472\t1\t1\t7\t17\t77\t977\t977\t1977\t6977\t154\t155\tJIAAAA\tAQGAAA\tAAAAxx\n3191\t4473\t1\t3\t1\t11\t91\t191\t1191\t3191\t3191\t182\t183\tTSAAAA\tBQGAAA\tHHHHxx\n418\t4474\t0\t2\t8\t18\t18\t418\t418\t418\t418\t36\t37\tCQAAAA\tCQGAAA\tOOOOxx\n3142\t4475\t0\t2\t2\t2\t42\t142\t1142\t3142\t3142\t84\t85\tWQAAAA\tDQGAAA\tVVVVxx\n5042\t4476\t0\t2\t2\t2\t42\t42\t1042\t42\t5042\t84\t85\tYLAAAA\tEQGAAA\tAAAAxx\n2194\t4477\t0\t2\t4\t14\t94\t194\t194\t2194\t2194\t188\t189\tKGAAAA\tFQGAAA\tHHHHxx\n2397\t4478\t1\t1\t7\t17\t97\t397\t397\t2397\t2397\t194\t195\tFOAAAA\tGQGAAA\tOOOOxx\n4684\t4479\t0\t0\t4\t4\t84\t684\t684\t4684\t4684\t168\t169\tEYAAAA\tHQGAAA\tVVVVxx\n34\t4480\t0\t2\t4\t14\t34\t34\t34\t34\t34\t68\t69\tIBAAAA\tIQGAAA\tAAAAxx\n3844\t4481\t0\t0\t4\t4\t44\t844\t1844\t3844\t3844\t88\t89\tWRAAAA\tJQGAAA\tHHHHxx\n7824\t4482\t0\t0\t4\t4\t24\t824\t1824\t2824\t7824\t48\t49\tYOAAAA\tKQGAAA\tOOOOxx\n6177\t4483\t1\t1\t7\t17\t77\t177\t177\t1177\t6177\t154\t155\tPDAAAA\tLQGAAA\tVVVVxx\n9657\t4484\t1\t1\t7\t17\t57\t657\t1657\t4657\t9657\t114\t115\tLHAAAA\tMQGAAA\tAAAAxx\n4546\t4485\t0\t2\t6\t6\t46\t546\t546\t4546\t4546\t92\t93\tWSAAAA\tNQGAAA\tHHHHxx\n599\t4486\t1\t3\t9\t19\t99\t599\t599\t599\t599\t198\t199\tBXAAAA\tOQGAAA\tOOOOxx\n153\t4487\t1\t1\t3\t13\t53\t153\t153\t153\t153\t106\t107\tXFAAAA\tPQGAAA\tVVVVxx\n6910\t4488\t0\t2\t0\t10\t10\t910\t910\t1910\t6910\t20\t21\tUFAAAA\tQQGAAA\tAAAAxx\n4408\t4489\t0\t0\t8\t8\t8\t408\t408\t4408\t4408\t16\t17\tONAAAA\tRQGAAA\tHHHHxx\n1164\t4490\t0\t0\t4\t4\t64\t164\t1164\t1164\t1164\t128\t129\tUSAAAA\tSQGAAA\tOOOOxx\n6469\t4491\t1\t1\t9\t9\t69\t469\t469\t1469\t6469\t138\t139\tVOAAAA\tTQGAAA\tVVVVxx\n5996\t4492\t0\t0\t6\t16\t96\t996\t1996\t996\t5996\t192\t193\tQWAAAA\tUQGAAA\tAAAAxx\n2639\t4493\t1\t3\t9\t19\t39\t639\t639\t2639\t2639\t78\t79\tNXAAAA\tVQGAAA\tHHHHxx\n2678\t4494\t0\t2\t8\t18\t78\t678\t678\t2678\t2678\t156\t157\tAZAAAA\tWQGAAA\tOOOOxx\n8392\t4495\t0\t0\t2\t12\t92\t392\t392\t3392\t8392\t184\t185\tUKAAAA\tXQGAAA\tVVVVxx\n1386\t4496\t0\t2\t6\t6\t86\t386\t1386\t1386\t1386\t172\t173\tIBAAAA\tYQGAAA\tAAAAxx\n5125\t4497\t1\t1\t5\t5\t25\t125\t1125\t125\t5125\t50\t51\tDPAAAA\tZQGAAA\tHHHHxx\n8453\t4498\t1\t1\t3\t13\t53\t453\t453\t3453\t8453\t106\t107\tDNAAAA\tARGAAA\tOOOOxx\n2369\t4499\t1\t1\t9\t9\t69\t369\t369\t2369\t2369\t138\t139\tDNAAAA\tBRGAAA\tVVVVxx\n1608\t4500\t0\t0\t8\t8\t8\t608\t1608\t1608\t1608\t16\t17\tWJAAAA\tCRGAAA\tAAAAxx\n3781\t4501\t1\t1\t1\t1\t81\t781\t1781\t3781\t3781\t162\t163\tLPAAAA\tDRGAAA\tHHHHxx\n903\t4502\t1\t3\t3\t3\t3\t903\t903\t903\t903\t6\t7\tTIAAAA\tERGAAA\tOOOOxx\n2099\t4503\t1\t3\t9\t19\t99\t99\t99\t2099\t2099\t198\t199\tTCAAAA\tFRGAAA\tVVVVxx\n538\t4504\t0\t2\t8\t18\t38\t538\t538\t538\t538\t76\t77\tSUAAAA\tGRGAAA\tAAAAxx\n9177\t4505\t1\t1\t7\t17\t77\t177\t1177\t4177\t9177\t154\t155\tZOAAAA\tHRGAAA\tHHHHxx\n420\t4506\t0\t0\t0\t0\t20\t420\t420\t420\t420\t40\t41\tEQAAAA\tIRGAAA\tOOOOxx\n9080\t4507\t0\t0\t0\t0\t80\t80\t1080\t4080\t9080\t160\t161\tGLAAAA\tJRGAAA\tVVVVxx\n2630\t4508\t0\t2\t0\t10\t30\t630\t630\t2630\t2630\t60\t61\tEXAAAA\tKRGAAA\tAAAAxx\n5978\t4509\t0\t2\t8\t18\t78\t978\t1978\t978\t5978\t156\t157\tYVAAAA\tLRGAAA\tHHHHxx\n9239\t4510\t1\t3\t9\t19\t39\t239\t1239\t4239\t9239\t78\t79\tJRAAAA\tMRGAAA\tOOOOxx\n4372\t4511\t0\t0\t2\t12\t72\t372\t372\t4372\t4372\t144\t145\tEMAAAA\tNRGAAA\tVVVVxx\n4357\t4512\t1\t1\t7\t17\t57\t357\t357\t4357\t4357\t114\t115\tPLAAAA\tORGAAA\tAAAAxx\n9857\t4513\t1\t1\t7\t17\t57\t857\t1857\t4857\t9857\t114\t115\tDPAAAA\tPRGAAA\tHHHHxx\n7933\t4514\t1\t1\t3\t13\t33\t933\t1933\t2933\t7933\t66\t67\tDTAAAA\tQRGAAA\tOOOOxx\n9574\t4515\t0\t2\t4\t14\t74\t574\t1574\t4574\t9574\t148\t149\tGEAAAA\tRRGAAA\tVVVVxx\n8294\t4516\t0\t2\t4\t14\t94\t294\t294\t3294\t8294\t188\t189\tAHAAAA\tSRGAAA\tAAAAxx\n627\t4517\t1\t3\t7\t7\t27\t627\t627\t627\t627\t54\t55\tDYAAAA\tTRGAAA\tHHHHxx\n3229\t4518\t1\t1\t9\t9\t29\t229\t1229\t3229\t3229\t58\t59\tFUAAAA\tURGAAA\tOOOOxx\n3163\t4519\t1\t3\t3\t3\t63\t163\t1163\t3163\t3163\t126\t127\tRRAAAA\tVRGAAA\tVVVVxx\n7349\t4520\t1\t1\t9\t9\t49\t349\t1349\t2349\t7349\t98\t99\tRWAAAA\tWRGAAA\tAAAAxx\n6889\t4521\t1\t1\t9\t9\t89\t889\t889\t1889\t6889\t178\t179\tZEAAAA\tXRGAAA\tHHHHxx\n2101\t4522\t1\t1\t1\t1\t1\t101\t101\t2101\t2101\t2\t3\tVCAAAA\tYRGAAA\tOOOOxx\n6476\t4523\t0\t0\t6\t16\t76\t476\t476\t1476\t6476\t152\t153\tCPAAAA\tZRGAAA\tVVVVxx\n6765\t4524\t1\t1\t5\t5\t65\t765\t765\t1765\t6765\t130\t131\tFAAAAA\tASGAAA\tAAAAxx\n4204\t4525\t0\t0\t4\t4\t4\t204\t204\t4204\t4204\t8\t9\tSFAAAA\tBSGAAA\tHHHHxx\n5915\t4526\t1\t3\t5\t15\t15\t915\t1915\t915\t5915\t30\t31\tNTAAAA\tCSGAAA\tOOOOxx\n2318\t4527\t0\t2\t8\t18\t18\t318\t318\t2318\t2318\t36\t37\tELAAAA\tDSGAAA\tVVVVxx\n294\t4528\t0\t2\t4\t14\t94\t294\t294\t294\t294\t188\t189\tILAAAA\tESGAAA\tAAAAxx\n5245\t4529\t1\t1\t5\t5\t45\t245\t1245\t245\t5245\t90\t91\tTTAAAA\tFSGAAA\tHHHHxx\n4481\t4530\t1\t1\t1\t1\t81\t481\t481\t4481\t4481\t162\t163\tJQAAAA\tGSGAAA\tOOOOxx\n7754\t4531\t0\t2\t4\t14\t54\t754\t1754\t2754\t7754\t108\t109\tGMAAAA\tHSGAAA\tVVVVxx\n8494\t4532\t0\t2\t4\t14\t94\t494\t494\t3494\t8494\t188\t189\tSOAAAA\tISGAAA\tAAAAxx\n4014\t4533\t0\t2\t4\t14\t14\t14\t14\t4014\t4014\t28\t29\tKYAAAA\tJSGAAA\tHHHHxx\n2197\t4534\t1\t1\t7\t17\t97\t197\t197\t2197\t2197\t194\t195\tNGAAAA\tKSGAAA\tOOOOxx\n1297\t4535\t1\t1\t7\t17\t97\t297\t1297\t1297\t1297\t194\t195\tXXAAAA\tLSGAAA\tVVVVxx\n1066\t4536\t0\t2\t6\t6\t66\t66\t1066\t1066\t1066\t132\t133\tAPAAAA\tMSGAAA\tAAAAxx\n5710\t4537\t0\t2\t0\t10\t10\t710\t1710\t710\t5710\t20\t21\tQLAAAA\tNSGAAA\tHHHHxx\n4100\t4538\t0\t0\t0\t0\t0\t100\t100\t4100\t4100\t0\t1\tSBAAAA\tOSGAAA\tOOOOxx\n7356\t4539\t0\t0\t6\t16\t56\t356\t1356\t2356\t7356\t112\t113\tYWAAAA\tPSGAAA\tVVVVxx\n7658\t4540\t0\t2\t8\t18\t58\t658\t1658\t2658\t7658\t116\t117\tOIAAAA\tQSGAAA\tAAAAxx\n3666\t4541\t0\t2\t6\t6\t66\t666\t1666\t3666\t3666\t132\t133\tALAAAA\tRSGAAA\tHHHHxx\n9713\t4542\t1\t1\t3\t13\t13\t713\t1713\t4713\t9713\t26\t27\tPJAAAA\tSSGAAA\tOOOOxx\n691\t4543\t1\t3\t1\t11\t91\t691\t691\t691\t691\t182\t183\tPAAAAA\tTSGAAA\tVVVVxx\n3112\t4544\t0\t0\t2\t12\t12\t112\t1112\t3112\t3112\t24\t25\tSPAAAA\tUSGAAA\tAAAAxx\n6035\t4545\t1\t3\t5\t15\t35\t35\t35\t1035\t6035\t70\t71\tDYAAAA\tVSGAAA\tHHHHxx\n8353\t4546\t1\t1\t3\t13\t53\t353\t353\t3353\t8353\t106\t107\tHJAAAA\tWSGAAA\tOOOOxx\n5679\t4547\t1\t3\t9\t19\t79\t679\t1679\t679\t5679\t158\t159\tLKAAAA\tXSGAAA\tVVVVxx\n2124\t4548\t0\t0\t4\t4\t24\t124\t124\t2124\t2124\t48\t49\tSDAAAA\tYSGAAA\tAAAAxx\n4714\t4549\t0\t2\t4\t14\t14\t714\t714\t4714\t4714\t28\t29\tIZAAAA\tZSGAAA\tHHHHxx\n9048\t4550\t0\t0\t8\t8\t48\t48\t1048\t4048\t9048\t96\t97\tAKAAAA\tATGAAA\tOOOOxx\n7692\t4551\t0\t0\t2\t12\t92\t692\t1692\t2692\t7692\t184\t185\tWJAAAA\tBTGAAA\tVVVVxx\n4542\t4552\t0\t2\t2\t2\t42\t542\t542\t4542\t4542\t84\t85\tSSAAAA\tCTGAAA\tAAAAxx\n8737\t4553\t1\t1\t7\t17\t37\t737\t737\t3737\t8737\t74\t75\tBYAAAA\tDTGAAA\tHHHHxx\n4977\t4554\t1\t1\t7\t17\t77\t977\t977\t4977\t4977\t154\t155\tLJAAAA\tETGAAA\tOOOOxx\n9349\t4555\t1\t1\t9\t9\t49\t349\t1349\t4349\t9349\t98\t99\tPVAAAA\tFTGAAA\tVVVVxx\n731\t4556\t1\t3\t1\t11\t31\t731\t731\t731\t731\t62\t63\tDCAAAA\tGTGAAA\tAAAAxx\n1788\t4557\t0\t0\t8\t8\t88\t788\t1788\t1788\t1788\t176\t177\tUQAAAA\tHTGAAA\tHHHHxx\n7830\t4558\t0\t2\t0\t10\t30\t830\t1830\t2830\t7830\t60\t61\tEPAAAA\tITGAAA\tOOOOxx\n3977\t4559\t1\t1\t7\t17\t77\t977\t1977\t3977\t3977\t154\t155\tZWAAAA\tJTGAAA\tVVVVxx\n2421\t4560\t1\t1\t1\t1\t21\t421\t421\t2421\t2421\t42\t43\tDPAAAA\tKTGAAA\tAAAAxx\n5891\t4561\t1\t3\t1\t11\t91\t891\t1891\t891\t5891\t182\t183\tPSAAAA\tLTGAAA\tHHHHxx\n1111\t4562\t1\t3\t1\t11\t11\t111\t1111\t1111\t1111\t22\t23\tTQAAAA\tMTGAAA\tOOOOxx\n9224\t4563\t0\t0\t4\t4\t24\t224\t1224\t4224\t9224\t48\t49\tUQAAAA\tNTGAAA\tVVVVxx\n9872\t4564\t0\t0\t2\t12\t72\t872\t1872\t4872\t9872\t144\t145\tSPAAAA\tOTGAAA\tAAAAxx\n2433\t4565\t1\t1\t3\t13\t33\t433\t433\t2433\t2433\t66\t67\tPPAAAA\tPTGAAA\tHHHHxx\n1491\t4566\t1\t3\t1\t11\t91\t491\t1491\t1491\t1491\t182\t183\tJFAAAA\tQTGAAA\tOOOOxx\n6653\t4567\t1\t1\t3\t13\t53\t653\t653\t1653\t6653\t106\t107\tXVAAAA\tRTGAAA\tVVVVxx\n1907\t4568\t1\t3\t7\t7\t7\t907\t1907\t1907\t1907\t14\t15\tJVAAAA\tSTGAAA\tAAAAxx\n889\t4569\t1\t1\t9\t9\t89\t889\t889\t889\t889\t178\t179\tFIAAAA\tTTGAAA\tHHHHxx\n561\t4570\t1\t1\t1\t1\t61\t561\t561\t561\t561\t122\t123\tPVAAAA\tUTGAAA\tOOOOxx\n7415\t4571\t1\t3\t5\t15\t15\t415\t1415\t2415\t7415\t30\t31\tFZAAAA\tVTGAAA\tVVVVxx\n2703\t4572\t1\t3\t3\t3\t3\t703\t703\t2703\t2703\t6\t7\tZZAAAA\tWTGAAA\tAAAAxx\n2561\t4573\t1\t1\t1\t1\t61\t561\t561\t2561\t2561\t122\t123\tNUAAAA\tXTGAAA\tHHHHxx\n1257\t4574\t1\t1\t7\t17\t57\t257\t1257\t1257\t1257\t114\t115\tJWAAAA\tYTGAAA\tOOOOxx\n2390\t4575\t0\t2\t0\t10\t90\t390\t390\t2390\t2390\t180\t181\tYNAAAA\tZTGAAA\tVVVVxx\n3915\t4576\t1\t3\t5\t15\t15\t915\t1915\t3915\t3915\t30\t31\tPUAAAA\tAUGAAA\tAAAAxx\n8476\t4577\t0\t0\t6\t16\t76\t476\t476\t3476\t8476\t152\t153\tAOAAAA\tBUGAAA\tHHHHxx\n607\t4578\t1\t3\t7\t7\t7\t607\t607\t607\t607\t14\t15\tJXAAAA\tCUGAAA\tOOOOxx\n3891\t4579\t1\t3\t1\t11\t91\t891\t1891\t3891\t3891\t182\t183\tRTAAAA\tDUGAAA\tVVVVxx\n7269\t4580\t1\t1\t9\t9\t69\t269\t1269\t2269\t7269\t138\t139\tPTAAAA\tEUGAAA\tAAAAxx\n9537\t4581\t1\t1\t7\t17\t37\t537\t1537\t4537\t9537\t74\t75\tVCAAAA\tFUGAAA\tHHHHxx\n8518\t4582\t0\t2\t8\t18\t18\t518\t518\t3518\t8518\t36\t37\tQPAAAA\tGUGAAA\tOOOOxx\n5221\t4583\t1\t1\t1\t1\t21\t221\t1221\t221\t5221\t42\t43\tVSAAAA\tHUGAAA\tVVVVxx\n3274\t4584\t0\t2\t4\t14\t74\t274\t1274\t3274\t3274\t148\t149\tYVAAAA\tIUGAAA\tAAAAxx\n6677\t4585\t1\t1\t7\t17\t77\t677\t677\t1677\t6677\t154\t155\tVWAAAA\tJUGAAA\tHHHHxx\n3114\t4586\t0\t2\t4\t14\t14\t114\t1114\t3114\t3114\t28\t29\tUPAAAA\tKUGAAA\tOOOOxx\n1966\t4587\t0\t2\t6\t6\t66\t966\t1966\t1966\t1966\t132\t133\tQXAAAA\tLUGAAA\tVVVVxx\n5941\t4588\t1\t1\t1\t1\t41\t941\t1941\t941\t5941\t82\t83\tNUAAAA\tMUGAAA\tAAAAxx\n9463\t4589\t1\t3\t3\t3\t63\t463\t1463\t4463\t9463\t126\t127\tZZAAAA\tNUGAAA\tHHHHxx\n8966\t4590\t0\t2\t6\t6\t66\t966\t966\t3966\t8966\t132\t133\tWGAAAA\tOUGAAA\tOOOOxx\n4402\t4591\t0\t2\t2\t2\t2\t402\t402\t4402\t4402\t4\t5\tINAAAA\tPUGAAA\tVVVVxx\n3364\t4592\t0\t0\t4\t4\t64\t364\t1364\t3364\t3364\t128\t129\tKZAAAA\tQUGAAA\tAAAAxx\n3698\t4593\t0\t2\t8\t18\t98\t698\t1698\t3698\t3698\t196\t197\tGMAAAA\tRUGAAA\tHHHHxx\n4651\t4594\t1\t3\t1\t11\t51\t651\t651\t4651\t4651\t102\t103\tXWAAAA\tSUGAAA\tOOOOxx\n2127\t4595\t1\t3\t7\t7\t27\t127\t127\t2127\t2127\t54\t55\tVDAAAA\tTUGAAA\tVVVVxx\n3614\t4596\t0\t2\t4\t14\t14\t614\t1614\t3614\t3614\t28\t29\tAJAAAA\tUUGAAA\tAAAAxx\n5430\t4597\t0\t2\t0\t10\t30\t430\t1430\t430\t5430\t60\t61\tWAAAAA\tVUGAAA\tHHHHxx\n3361\t4598\t1\t1\t1\t1\t61\t361\t1361\t3361\t3361\t122\t123\tHZAAAA\tWUGAAA\tOOOOxx\n4798\t4599\t0\t2\t8\t18\t98\t798\t798\t4798\t4798\t196\t197\tOCAAAA\tXUGAAA\tVVVVxx\n8269\t4600\t1\t1\t9\t9\t69\t269\t269\t3269\t8269\t138\t139\tBGAAAA\tYUGAAA\tAAAAxx\n6458\t4601\t0\t2\t8\t18\t58\t458\t458\t1458\t6458\t116\t117\tKOAAAA\tZUGAAA\tHHHHxx\n3358\t4602\t0\t2\t8\t18\t58\t358\t1358\t3358\t3358\t116\t117\tEZAAAA\tAVGAAA\tOOOOxx\n5898\t4603\t0\t2\t8\t18\t98\t898\t1898\t898\t5898\t196\t197\tWSAAAA\tBVGAAA\tVVVVxx\n1880\t4604\t0\t0\t0\t0\t80\t880\t1880\t1880\t1880\t160\t161\tIUAAAA\tCVGAAA\tAAAAxx\n782\t4605\t0\t2\t2\t2\t82\t782\t782\t782\t782\t164\t165\tCEAAAA\tDVGAAA\tHHHHxx\n3102\t4606\t0\t2\t2\t2\t2\t102\t1102\t3102\t3102\t4\t5\tIPAAAA\tEVGAAA\tOOOOxx\n6366\t4607\t0\t2\t6\t6\t66\t366\t366\t1366\t6366\t132\t133\tWKAAAA\tFVGAAA\tVVVVxx\n399\t4608\t1\t3\t9\t19\t99\t399\t399\t399\t399\t198\t199\tJPAAAA\tGVGAAA\tAAAAxx\n6773\t4609\t1\t1\t3\t13\t73\t773\t773\t1773\t6773\t146\t147\tNAAAAA\tHVGAAA\tHHHHxx\n7942\t4610\t0\t2\t2\t2\t42\t942\t1942\t2942\t7942\t84\t85\tMTAAAA\tIVGAAA\tOOOOxx\n6274\t4611\t0\t2\t4\t14\t74\t274\t274\t1274\t6274\t148\t149\tIHAAAA\tJVGAAA\tVVVVxx\n7447\t4612\t1\t3\t7\t7\t47\t447\t1447\t2447\t7447\t94\t95\tLAAAAA\tKVGAAA\tAAAAxx\n7648\t4613\t0\t0\t8\t8\t48\t648\t1648\t2648\t7648\t96\t97\tEIAAAA\tLVGAAA\tHHHHxx\n3997\t4614\t1\t1\t7\t17\t97\t997\t1997\t3997\t3997\t194\t195\tTXAAAA\tMVGAAA\tOOOOxx\n1759\t4615\t1\t3\t9\t19\t59\t759\t1759\t1759\t1759\t118\t119\tRPAAAA\tNVGAAA\tVVVVxx\n1785\t4616\t1\t1\t5\t5\t85\t785\t1785\t1785\t1785\t170\t171\tRQAAAA\tOVGAAA\tAAAAxx\n8930\t4617\t0\t2\t0\t10\t30\t930\t930\t3930\t8930\t60\t61\tMFAAAA\tPVGAAA\tHHHHxx\n7595\t4618\t1\t3\t5\t15\t95\t595\t1595\t2595\t7595\t190\t191\tDGAAAA\tQVGAAA\tOOOOxx\n6752\t4619\t0\t0\t2\t12\t52\t752\t752\t1752\t6752\t104\t105\tSZAAAA\tRVGAAA\tVVVVxx\n5635\t4620\t1\t3\t5\t15\t35\t635\t1635\t635\t5635\t70\t71\tTIAAAA\tSVGAAA\tAAAAxx\n1579\t4621\t1\t3\t9\t19\t79\t579\t1579\t1579\t1579\t158\t159\tTIAAAA\tTVGAAA\tHHHHxx\n7743\t4622\t1\t3\t3\t3\t43\t743\t1743\t2743\t7743\t86\t87\tVLAAAA\tUVGAAA\tOOOOxx\n5856\t4623\t0\t0\t6\t16\t56\t856\t1856\t856\t5856\t112\t113\tGRAAAA\tVVGAAA\tVVVVxx\n7273\t4624\t1\t1\t3\t13\t73\t273\t1273\t2273\t7273\t146\t147\tTTAAAA\tWVGAAA\tAAAAxx\n1399\t4625\t1\t3\t9\t19\t99\t399\t1399\t1399\t1399\t198\t199\tVBAAAA\tXVGAAA\tHHHHxx\n3694\t4626\t0\t2\t4\t14\t94\t694\t1694\t3694\t3694\t188\t189\tCMAAAA\tYVGAAA\tOOOOxx\n2782\t4627\t0\t2\t2\t2\t82\t782\t782\t2782\t2782\t164\t165\tADAAAA\tZVGAAA\tVVVVxx\n6951\t4628\t1\t3\t1\t11\t51\t951\t951\t1951\t6951\t102\t103\tJHAAAA\tAWGAAA\tAAAAxx\n6053\t4629\t1\t1\t3\t13\t53\t53\t53\t1053\t6053\t106\t107\tVYAAAA\tBWGAAA\tHHHHxx\n1753\t4630\t1\t1\t3\t13\t53\t753\t1753\t1753\t1753\t106\t107\tLPAAAA\tCWGAAA\tOOOOxx\n3985\t4631\t1\t1\t5\t5\t85\t985\t1985\t3985\t3985\t170\t171\tHXAAAA\tDWGAAA\tVVVVxx\n6159\t4632\t1\t3\t9\t19\t59\t159\t159\t1159\t6159\t118\t119\tXCAAAA\tEWGAAA\tAAAAxx\n6250\t4633\t0\t2\t0\t10\t50\t250\t250\t1250\t6250\t100\t101\tKGAAAA\tFWGAAA\tHHHHxx\n6240\t4634\t0\t0\t0\t0\t40\t240\t240\t1240\t6240\t80\t81\tAGAAAA\tGWGAAA\tOOOOxx\n6571\t4635\t1\t3\t1\t11\t71\t571\t571\t1571\t6571\t142\t143\tTSAAAA\tHWGAAA\tVVVVxx\n8624\t4636\t0\t0\t4\t4\t24\t624\t624\t3624\t8624\t48\t49\tSTAAAA\tIWGAAA\tAAAAxx\n9718\t4637\t0\t2\t8\t18\t18\t718\t1718\t4718\t9718\t36\t37\tUJAAAA\tJWGAAA\tHHHHxx\n5529\t4638\t1\t1\t9\t9\t29\t529\t1529\t529\t5529\t58\t59\tREAAAA\tKWGAAA\tOOOOxx\n7089\t4639\t1\t1\t9\t9\t89\t89\t1089\t2089\t7089\t178\t179\tRMAAAA\tLWGAAA\tVVVVxx\n5488\t4640\t0\t0\t8\t8\t88\t488\t1488\t488\t5488\t176\t177\tCDAAAA\tMWGAAA\tAAAAxx\n5444\t4641\t0\t0\t4\t4\t44\t444\t1444\t444\t5444\t88\t89\tKBAAAA\tNWGAAA\tHHHHxx\n4899\t4642\t1\t3\t9\t19\t99\t899\t899\t4899\t4899\t198\t199\tLGAAAA\tOWGAAA\tOOOOxx\n7928\t4643\t0\t0\t8\t8\t28\t928\t1928\t2928\t7928\t56\t57\tYSAAAA\tPWGAAA\tVVVVxx\n4736\t4644\t0\t0\t6\t16\t36\t736\t736\t4736\t4736\t72\t73\tEAAAAA\tQWGAAA\tAAAAxx\n4317\t4645\t1\t1\t7\t17\t17\t317\t317\t4317\t4317\t34\t35\tBKAAAA\tRWGAAA\tHHHHxx\n1174\t4646\t0\t2\t4\t14\t74\t174\t1174\t1174\t1174\t148\t149\tETAAAA\tSWGAAA\tOOOOxx\n6138\t4647\t0\t2\t8\t18\t38\t138\t138\t1138\t6138\t76\t77\tCCAAAA\tTWGAAA\tVVVVxx\n3943\t4648\t1\t3\t3\t3\t43\t943\t1943\t3943\t3943\t86\t87\tRVAAAA\tUWGAAA\tAAAAxx\n1545\t4649\t1\t1\t5\t5\t45\t545\t1545\t1545\t1545\t90\t91\tLHAAAA\tVWGAAA\tHHHHxx\n6867\t4650\t1\t3\t7\t7\t67\t867\t867\t1867\t6867\t134\t135\tDEAAAA\tWWGAAA\tOOOOxx\n6832\t4651\t0\t0\t2\t12\t32\t832\t832\t1832\t6832\t64\t65\tUCAAAA\tXWGAAA\tVVVVxx\n2987\t4652\t1\t3\t7\t7\t87\t987\t987\t2987\t2987\t174\t175\tXKAAAA\tYWGAAA\tAAAAxx\n5169\t4653\t1\t1\t9\t9\t69\t169\t1169\t169\t5169\t138\t139\tVQAAAA\tZWGAAA\tHHHHxx\n8998\t4654\t0\t2\t8\t18\t98\t998\t998\t3998\t8998\t196\t197\tCIAAAA\tAXGAAA\tOOOOxx\n9347\t4655\t1\t3\t7\t7\t47\t347\t1347\t4347\t9347\t94\t95\tNVAAAA\tBXGAAA\tVVVVxx\n4800\t4656\t0\t0\t0\t0\t0\t800\t800\t4800\t4800\t0\t1\tQCAAAA\tCXGAAA\tAAAAxx\n4200\t4657\t0\t0\t0\t0\t0\t200\t200\t4200\t4200\t0\t1\tOFAAAA\tDXGAAA\tHHHHxx\n4046\t4658\t0\t2\t6\t6\t46\t46\t46\t4046\t4046\t92\t93\tQZAAAA\tEXGAAA\tOOOOxx\n7142\t4659\t0\t2\t2\t2\t42\t142\t1142\t2142\t7142\t84\t85\tSOAAAA\tFXGAAA\tVVVVxx\n2733\t4660\t1\t1\t3\t13\t33\t733\t733\t2733\t2733\t66\t67\tDBAAAA\tGXGAAA\tAAAAxx\n1568\t4661\t0\t0\t8\t8\t68\t568\t1568\t1568\t1568\t136\t137\tIIAAAA\tHXGAAA\tHHHHxx\n5105\t4662\t1\t1\t5\t5\t5\t105\t1105\t105\t5105\t10\t11\tJOAAAA\tIXGAAA\tOOOOxx\n9115\t4663\t1\t3\t5\t15\t15\t115\t1115\t4115\t9115\t30\t31\tPMAAAA\tJXGAAA\tVVVVxx\n6475\t4664\t1\t3\t5\t15\t75\t475\t475\t1475\t6475\t150\t151\tBPAAAA\tKXGAAA\tAAAAxx\n3796\t4665\t0\t0\t6\t16\t96\t796\t1796\t3796\t3796\t192\t193\tAQAAAA\tLXGAAA\tHHHHxx\n5410\t4666\t0\t2\t0\t10\t10\t410\t1410\t410\t5410\t20\t21\tCAAAAA\tMXGAAA\tOOOOxx\n4023\t4667\t1\t3\t3\t3\t23\t23\t23\t4023\t4023\t46\t47\tTYAAAA\tNXGAAA\tVVVVxx\n8904\t4668\t0\t0\t4\t4\t4\t904\t904\t3904\t8904\t8\t9\tMEAAAA\tOXGAAA\tAAAAxx\n450\t4669\t0\t2\t0\t10\t50\t450\t450\t450\t450\t100\t101\tIRAAAA\tPXGAAA\tHHHHxx\n8087\t4670\t1\t3\t7\t7\t87\t87\t87\t3087\t8087\t174\t175\tBZAAAA\tQXGAAA\tOOOOxx\n6478\t4671\t0\t2\t8\t18\t78\t478\t478\t1478\t6478\t156\t157\tEPAAAA\tRXGAAA\tVVVVxx\n2696\t4672\t0\t0\t6\t16\t96\t696\t696\t2696\t2696\t192\t193\tSZAAAA\tSXGAAA\tAAAAxx\n1792\t4673\t0\t0\t2\t12\t92\t792\t1792\t1792\t1792\t184\t185\tYQAAAA\tTXGAAA\tHHHHxx\n9699\t4674\t1\t3\t9\t19\t99\t699\t1699\t4699\t9699\t198\t199\tBJAAAA\tUXGAAA\tOOOOxx\n9160\t4675\t0\t0\t0\t0\t60\t160\t1160\t4160\t9160\t120\t121\tIOAAAA\tVXGAAA\tVVVVxx\n9989\t4676\t1\t1\t9\t9\t89\t989\t1989\t4989\t9989\t178\t179\tFUAAAA\tWXGAAA\tAAAAxx\n9568\t4677\t0\t0\t8\t8\t68\t568\t1568\t4568\t9568\t136\t137\tAEAAAA\tXXGAAA\tHHHHxx\n487\t4678\t1\t3\t7\t7\t87\t487\t487\t487\t487\t174\t175\tTSAAAA\tYXGAAA\tOOOOxx\n7863\t4679\t1\t3\t3\t3\t63\t863\t1863\t2863\t7863\t126\t127\tLQAAAA\tZXGAAA\tVVVVxx\n1884\t4680\t0\t0\t4\t4\t84\t884\t1884\t1884\t1884\t168\t169\tMUAAAA\tAYGAAA\tAAAAxx\n2651\t4681\t1\t3\t1\t11\t51\t651\t651\t2651\t2651\t102\t103\tZXAAAA\tBYGAAA\tHHHHxx\n8285\t4682\t1\t1\t5\t5\t85\t285\t285\t3285\t8285\t170\t171\tRGAAAA\tCYGAAA\tOOOOxx\n3927\t4683\t1\t3\t7\t7\t27\t927\t1927\t3927\t3927\t54\t55\tBVAAAA\tDYGAAA\tVVVVxx\n4076\t4684\t0\t0\t6\t16\t76\t76\t76\t4076\t4076\t152\t153\tUAAAAA\tEYGAAA\tAAAAxx\n6149\t4685\t1\t1\t9\t9\t49\t149\t149\t1149\t6149\t98\t99\tNCAAAA\tFYGAAA\tHHHHxx\n6581\t4686\t1\t1\t1\t1\t81\t581\t581\t1581\t6581\t162\t163\tDTAAAA\tGYGAAA\tOOOOxx\n8293\t4687\t1\t1\t3\t13\t93\t293\t293\t3293\t8293\t186\t187\tZGAAAA\tHYGAAA\tVVVVxx\n7665\t4688\t1\t1\t5\t5\t65\t665\t1665\t2665\t7665\t130\t131\tVIAAAA\tIYGAAA\tAAAAxx\n4435\t4689\t1\t3\t5\t15\t35\t435\t435\t4435\t4435\t70\t71\tPOAAAA\tJYGAAA\tHHHHxx\n1271\t4690\t1\t3\t1\t11\t71\t271\t1271\t1271\t1271\t142\t143\tXWAAAA\tKYGAAA\tOOOOxx\n3928\t4691\t0\t0\t8\t8\t28\t928\t1928\t3928\t3928\t56\t57\tCVAAAA\tLYGAAA\tVVVVxx\n7045\t4692\t1\t1\t5\t5\t45\t45\t1045\t2045\t7045\t90\t91\tZKAAAA\tMYGAAA\tAAAAxx\n4943\t4693\t1\t3\t3\t3\t43\t943\t943\t4943\t4943\t86\t87\tDIAAAA\tNYGAAA\tHHHHxx\n8473\t4694\t1\t1\t3\t13\t73\t473\t473\t3473\t8473\t146\t147\tXNAAAA\tOYGAAA\tOOOOxx\n1707\t4695\t1\t3\t7\t7\t7\t707\t1707\t1707\t1707\t14\t15\tRNAAAA\tPYGAAA\tVVVVxx\n7509\t4696\t1\t1\t9\t9\t9\t509\t1509\t2509\t7509\t18\t19\tVCAAAA\tQYGAAA\tAAAAxx\n1593\t4697\t1\t1\t3\t13\t93\t593\t1593\t1593\t1593\t186\t187\tHJAAAA\tRYGAAA\tHHHHxx\n9281\t4698\t1\t1\t1\t1\t81\t281\t1281\t4281\t9281\t162\t163\tZSAAAA\tSYGAAA\tOOOOxx\n8986\t4699\t0\t2\t6\t6\t86\t986\t986\t3986\t8986\t172\t173\tQHAAAA\tTYGAAA\tVVVVxx\n3740\t4700\t0\t0\t0\t0\t40\t740\t1740\t3740\t3740\t80\t81\tWNAAAA\tUYGAAA\tAAAAxx\n9265\t4701\t1\t1\t5\t5\t65\t265\t1265\t4265\t9265\t130\t131\tJSAAAA\tVYGAAA\tHHHHxx\n1510\t4702\t0\t2\t0\t10\t10\t510\t1510\t1510\t1510\t20\t21\tCGAAAA\tWYGAAA\tOOOOxx\n3022\t4703\t0\t2\t2\t2\t22\t22\t1022\t3022\t3022\t44\t45\tGMAAAA\tXYGAAA\tVVVVxx\n9014\t4704\t0\t2\t4\t14\t14\t14\t1014\t4014\t9014\t28\t29\tSIAAAA\tYYGAAA\tAAAAxx\n6816\t4705\t0\t0\t6\t16\t16\t816\t816\t1816\t6816\t32\t33\tECAAAA\tZYGAAA\tHHHHxx\n5518\t4706\t0\t2\t8\t18\t18\t518\t1518\t518\t5518\t36\t37\tGEAAAA\tAZGAAA\tOOOOxx\n4451\t4707\t1\t3\t1\t11\t51\t451\t451\t4451\t4451\t102\t103\tFPAAAA\tBZGAAA\tVVVVxx\n8747\t4708\t1\t3\t7\t7\t47\t747\t747\t3747\t8747\t94\t95\tLYAAAA\tCZGAAA\tAAAAxx\n4646\t4709\t0\t2\t6\t6\t46\t646\t646\t4646\t4646\t92\t93\tSWAAAA\tDZGAAA\tHHHHxx\n7296\t4710\t0\t0\t6\t16\t96\t296\t1296\t2296\t7296\t192\t193\tQUAAAA\tEZGAAA\tOOOOxx\n9644\t4711\t0\t0\t4\t4\t44\t644\t1644\t4644\t9644\t88\t89\tYGAAAA\tFZGAAA\tVVVVxx\n5977\t4712\t1\t1\t7\t17\t77\t977\t1977\t977\t5977\t154\t155\tXVAAAA\tGZGAAA\tAAAAxx\n6270\t4713\t0\t2\t0\t10\t70\t270\t270\t1270\t6270\t140\t141\tEHAAAA\tHZGAAA\tHHHHxx\n5578\t4714\t0\t2\t8\t18\t78\t578\t1578\t578\t5578\t156\t157\tOGAAAA\tIZGAAA\tOOOOxx\n2465\t4715\t1\t1\t5\t5\t65\t465\t465\t2465\t2465\t130\t131\tVQAAAA\tJZGAAA\tVVVVxx\n6436\t4716\t0\t0\t6\t16\t36\t436\t436\t1436\t6436\t72\t73\tONAAAA\tKZGAAA\tAAAAxx\n8089\t4717\t1\t1\t9\t9\t89\t89\t89\t3089\t8089\t178\t179\tDZAAAA\tLZGAAA\tHHHHxx\n2409\t4718\t1\t1\t9\t9\t9\t409\t409\t2409\t2409\t18\t19\tROAAAA\tMZGAAA\tOOOOxx\n284\t4719\t0\t0\t4\t4\t84\t284\t284\t284\t284\t168\t169\tYKAAAA\tNZGAAA\tVVVVxx\n5576\t4720\t0\t0\t6\t16\t76\t576\t1576\t576\t5576\t152\t153\tMGAAAA\tOZGAAA\tAAAAxx\n6534\t4721\t0\t2\t4\t14\t34\t534\t534\t1534\t6534\t68\t69\tIRAAAA\tPZGAAA\tHHHHxx\n8848\t4722\t0\t0\t8\t8\t48\t848\t848\t3848\t8848\t96\t97\tICAAAA\tQZGAAA\tOOOOxx\n4305\t4723\t1\t1\t5\t5\t5\t305\t305\t4305\t4305\t10\t11\tPJAAAA\tRZGAAA\tVVVVxx\n5574\t4724\t0\t2\t4\t14\t74\t574\t1574\t574\t5574\t148\t149\tKGAAAA\tSZGAAA\tAAAAxx\n596\t4725\t0\t0\t6\t16\t96\t596\t596\t596\t596\t192\t193\tYWAAAA\tTZGAAA\tHHHHxx\n1253\t4726\t1\t1\t3\t13\t53\t253\t1253\t1253\t1253\t106\t107\tFWAAAA\tUZGAAA\tOOOOxx\n521\t4727\t1\t1\t1\t1\t21\t521\t521\t521\t521\t42\t43\tBUAAAA\tVZGAAA\tVVVVxx\n8739\t4728\t1\t3\t9\t19\t39\t739\t739\t3739\t8739\t78\t79\tDYAAAA\tWZGAAA\tAAAAxx\n908\t4729\t0\t0\t8\t8\t8\t908\t908\t908\t908\t16\t17\tYIAAAA\tXZGAAA\tHHHHxx\n6937\t4730\t1\t1\t7\t17\t37\t937\t937\t1937\t6937\t74\t75\tVGAAAA\tYZGAAA\tOOOOxx\n4515\t4731\t1\t3\t5\t15\t15\t515\t515\t4515\t4515\t30\t31\tRRAAAA\tZZGAAA\tVVVVxx\n8630\t4732\t0\t2\t0\t10\t30\t630\t630\t3630\t8630\t60\t61\tYTAAAA\tAAHAAA\tAAAAxx\n7518\t4733\t0\t2\t8\t18\t18\t518\t1518\t2518\t7518\t36\t37\tEDAAAA\tBAHAAA\tHHHHxx\n8300\t4734\t0\t0\t0\t0\t0\t300\t300\t3300\t8300\t0\t1\tGHAAAA\tCAHAAA\tOOOOxx\n8434\t4735\t0\t2\t4\t14\t34\t434\t434\t3434\t8434\t68\t69\tKMAAAA\tDAHAAA\tVVVVxx\n6000\t4736\t0\t0\t0\t0\t0\t0\t0\t1000\t6000\t0\t1\tUWAAAA\tEAHAAA\tAAAAxx\n4508\t4737\t0\t0\t8\t8\t8\t508\t508\t4508\t4508\t16\t17\tKRAAAA\tFAHAAA\tHHHHxx\n7861\t4738\t1\t1\t1\t1\t61\t861\t1861\t2861\t7861\t122\t123\tJQAAAA\tGAHAAA\tOOOOxx\n5953\t4739\t1\t1\t3\t13\t53\t953\t1953\t953\t5953\t106\t107\tZUAAAA\tHAHAAA\tVVVVxx\n5063\t4740\t1\t3\t3\t3\t63\t63\t1063\t63\t5063\t126\t127\tTMAAAA\tIAHAAA\tAAAAxx\n4501\t4741\t1\t1\t1\t1\t1\t501\t501\t4501\t4501\t2\t3\tDRAAAA\tJAHAAA\tHHHHxx\n7092\t4742\t0\t0\t2\t12\t92\t92\t1092\t2092\t7092\t184\t185\tUMAAAA\tKAHAAA\tOOOOxx\n4388\t4743\t0\t0\t8\t8\t88\t388\t388\t4388\t4388\t176\t177\tUMAAAA\tLAHAAA\tVVVVxx\n1826\t4744\t0\t2\t6\t6\t26\t826\t1826\t1826\t1826\t52\t53\tGSAAAA\tMAHAAA\tAAAAxx\n568\t4745\t0\t0\t8\t8\t68\t568\t568\t568\t568\t136\t137\tWVAAAA\tNAHAAA\tHHHHxx\n8184\t4746\t0\t0\t4\t4\t84\t184\t184\t3184\t8184\t168\t169\tUCAAAA\tOAHAAA\tOOOOxx\n4268\t4747\t0\t0\t8\t8\t68\t268\t268\t4268\t4268\t136\t137\tEIAAAA\tPAHAAA\tVVVVxx\n5798\t4748\t0\t2\t8\t18\t98\t798\t1798\t798\t5798\t196\t197\tAPAAAA\tQAHAAA\tAAAAxx\n5190\t4749\t0\t2\t0\t10\t90\t190\t1190\t190\t5190\t180\t181\tQRAAAA\tRAHAAA\tHHHHxx\n1298\t4750\t0\t2\t8\t18\t98\t298\t1298\t1298\t1298\t196\t197\tYXAAAA\tSAHAAA\tOOOOxx\n4035\t4751\t1\t3\t5\t15\t35\t35\t35\t4035\t4035\t70\t71\tFZAAAA\tTAHAAA\tVVVVxx\n4504\t4752\t0\t0\t4\t4\t4\t504\t504\t4504\t4504\t8\t9\tGRAAAA\tUAHAAA\tAAAAxx\n5992\t4753\t0\t0\t2\t12\t92\t992\t1992\t992\t5992\t184\t185\tMWAAAA\tVAHAAA\tHHHHxx\n770\t4754\t0\t2\t0\t10\t70\t770\t770\t770\t770\t140\t141\tQDAAAA\tWAHAAA\tOOOOxx\n7502\t4755\t0\t2\t2\t2\t2\t502\t1502\t2502\t7502\t4\t5\tOCAAAA\tXAHAAA\tVVVVxx\n824\t4756\t0\t0\t4\t4\t24\t824\t824\t824\t824\t48\t49\tSFAAAA\tYAHAAA\tAAAAxx\n7716\t4757\t0\t0\t6\t16\t16\t716\t1716\t2716\t7716\t32\t33\tUKAAAA\tZAHAAA\tHHHHxx\n5749\t4758\t1\t1\t9\t9\t49\t749\t1749\t749\t5749\t98\t99\tDNAAAA\tABHAAA\tOOOOxx\n9814\t4759\t0\t2\t4\t14\t14\t814\t1814\t4814\t9814\t28\t29\tMNAAAA\tBBHAAA\tVVVVxx\n350\t4760\t0\t2\t0\t10\t50\t350\t350\t350\t350\t100\t101\tMNAAAA\tCBHAAA\tAAAAxx\n1390\t4761\t0\t2\t0\t10\t90\t390\t1390\t1390\t1390\t180\t181\tMBAAAA\tDBHAAA\tHHHHxx\n6994\t4762\t0\t2\t4\t14\t94\t994\t994\t1994\t6994\t188\t189\tAJAAAA\tEBHAAA\tOOOOxx\n3629\t4763\t1\t1\t9\t9\t29\t629\t1629\t3629\t3629\t58\t59\tPJAAAA\tFBHAAA\tVVVVxx\n9937\t4764\t1\t1\t7\t17\t37\t937\t1937\t4937\t9937\t74\t75\tFSAAAA\tGBHAAA\tAAAAxx\n5285\t4765\t1\t1\t5\t5\t85\t285\t1285\t285\t5285\t170\t171\tHVAAAA\tHBHAAA\tHHHHxx\n3157\t4766\t1\t1\t7\t17\t57\t157\t1157\t3157\t3157\t114\t115\tLRAAAA\tIBHAAA\tOOOOxx\n9549\t4767\t1\t1\t9\t9\t49\t549\t1549\t4549\t9549\t98\t99\tHDAAAA\tJBHAAA\tVVVVxx\n4118\t4768\t0\t2\t8\t18\t18\t118\t118\t4118\t4118\t36\t37\tKCAAAA\tKBHAAA\tAAAAxx\n756\t4769\t0\t0\t6\t16\t56\t756\t756\t756\t756\t112\t113\tCDAAAA\tLBHAAA\tHHHHxx\n5964\t4770\t0\t0\t4\t4\t64\t964\t1964\t964\t5964\t128\t129\tKVAAAA\tMBHAAA\tOOOOxx\n7701\t4771\t1\t1\t1\t1\t1\t701\t1701\t2701\t7701\t2\t3\tFKAAAA\tNBHAAA\tVVVVxx\n1242\t4772\t0\t2\t2\t2\t42\t242\t1242\t1242\t1242\t84\t85\tUVAAAA\tOBHAAA\tAAAAxx\n7890\t4773\t0\t2\t0\t10\t90\t890\t1890\t2890\t7890\t180\t181\tMRAAAA\tPBHAAA\tHHHHxx\n1991\t4774\t1\t3\t1\t11\t91\t991\t1991\t1991\t1991\t182\t183\tPYAAAA\tQBHAAA\tOOOOxx\n110\t4775\t0\t2\t0\t10\t10\t110\t110\t110\t110\t20\t21\tGEAAAA\tRBHAAA\tVVVVxx\n9334\t4776\t0\t2\t4\t14\t34\t334\t1334\t4334\t9334\t68\t69\tAVAAAA\tSBHAAA\tAAAAxx\n6231\t4777\t1\t3\t1\t11\t31\t231\t231\t1231\t6231\t62\t63\tRFAAAA\tTBHAAA\tHHHHxx\n9871\t4778\t1\t3\t1\t11\t71\t871\t1871\t4871\t9871\t142\t143\tRPAAAA\tUBHAAA\tOOOOxx\n9471\t4779\t1\t3\t1\t11\t71\t471\t1471\t4471\t9471\t142\t143\tHAAAAA\tVBHAAA\tVVVVxx\n2697\t4780\t1\t1\t7\t17\t97\t697\t697\t2697\t2697\t194\t195\tTZAAAA\tWBHAAA\tAAAAxx\n4761\t4781\t1\t1\t1\t1\t61\t761\t761\t4761\t4761\t122\t123\tDBAAAA\tXBHAAA\tHHHHxx\n8493\t4782\t1\t1\t3\t13\t93\t493\t493\t3493\t8493\t186\t187\tROAAAA\tYBHAAA\tOOOOxx\n1045\t4783\t1\t1\t5\t5\t45\t45\t1045\t1045\t1045\t90\t91\tFOAAAA\tZBHAAA\tVVVVxx\n3403\t4784\t1\t3\t3\t3\t3\t403\t1403\t3403\t3403\t6\t7\tXAAAAA\tACHAAA\tAAAAxx\n9412\t4785\t0\t0\t2\t12\t12\t412\t1412\t4412\t9412\t24\t25\tAYAAAA\tBCHAAA\tHHHHxx\n7652\t4786\t0\t0\t2\t12\t52\t652\t1652\t2652\t7652\t104\t105\tIIAAAA\tCCHAAA\tOOOOxx\n5866\t4787\t0\t2\t6\t6\t66\t866\t1866\t866\t5866\t132\t133\tQRAAAA\tDCHAAA\tVVVVxx\n6942\t4788\t0\t2\t2\t2\t42\t942\t942\t1942\t6942\t84\t85\tAHAAAA\tECHAAA\tAAAAxx\n9353\t4789\t1\t1\t3\t13\t53\t353\t1353\t4353\t9353\t106\t107\tTVAAAA\tFCHAAA\tHHHHxx\n2600\t4790\t0\t0\t0\t0\t0\t600\t600\t2600\t2600\t0\t1\tAWAAAA\tGCHAAA\tOOOOxx\n6971\t4791\t1\t3\t1\t11\t71\t971\t971\t1971\t6971\t142\t143\tDIAAAA\tHCHAAA\tVVVVxx\n5391\t4792\t1\t3\t1\t11\t91\t391\t1391\t391\t5391\t182\t183\tJZAAAA\tICHAAA\tAAAAxx\n7654\t4793\t0\t2\t4\t14\t54\t654\t1654\t2654\t7654\t108\t109\tKIAAAA\tJCHAAA\tHHHHxx\n1797\t4794\t1\t1\t7\t17\t97\t797\t1797\t1797\t1797\t194\t195\tDRAAAA\tKCHAAA\tOOOOxx\n4530\t4795\t0\t2\t0\t10\t30\t530\t530\t4530\t4530\t60\t61\tGSAAAA\tLCHAAA\tVVVVxx\n3130\t4796\t0\t2\t0\t10\t30\t130\t1130\t3130\t3130\t60\t61\tKQAAAA\tMCHAAA\tAAAAxx\n9442\t4797\t0\t2\t2\t2\t42\t442\t1442\t4442\t9442\t84\t85\tEZAAAA\tNCHAAA\tHHHHxx\n6659\t4798\t1\t3\t9\t19\t59\t659\t659\t1659\t6659\t118\t119\tDWAAAA\tOCHAAA\tOOOOxx\n9714\t4799\t0\t2\t4\t14\t14\t714\t1714\t4714\t9714\t28\t29\tQJAAAA\tPCHAAA\tVVVVxx\n3660\t4800\t0\t0\t0\t0\t60\t660\t1660\t3660\t3660\t120\t121\tUKAAAA\tQCHAAA\tAAAAxx\n1906\t4801\t0\t2\t6\t6\t6\t906\t1906\t1906\t1906\t12\t13\tIVAAAA\tRCHAAA\tHHHHxx\n7927\t4802\t1\t3\t7\t7\t27\t927\t1927\t2927\t7927\t54\t55\tXSAAAA\tSCHAAA\tOOOOxx\n1767\t4803\t1\t3\t7\t7\t67\t767\t1767\t1767\t1767\t134\t135\tZPAAAA\tTCHAAA\tVVVVxx\n5523\t4804\t1\t3\t3\t3\t23\t523\t1523\t523\t5523\t46\t47\tLEAAAA\tUCHAAA\tAAAAxx\n9289\t4805\t1\t1\t9\t9\t89\t289\t1289\t4289\t9289\t178\t179\tHTAAAA\tVCHAAA\tHHHHxx\n2717\t4806\t1\t1\t7\t17\t17\t717\t717\t2717\t2717\t34\t35\tNAAAAA\tWCHAAA\tOOOOxx\n4099\t4807\t1\t3\t9\t19\t99\t99\t99\t4099\t4099\t198\t199\tRBAAAA\tXCHAAA\tVVVVxx\n4387\t4808\t1\t3\t7\t7\t87\t387\t387\t4387\t4387\t174\t175\tTMAAAA\tYCHAAA\tAAAAxx\n8864\t4809\t0\t0\t4\t4\t64\t864\t864\t3864\t8864\t128\t129\tYCAAAA\tZCHAAA\tHHHHxx\n1774\t4810\t0\t2\t4\t14\t74\t774\t1774\t1774\t1774\t148\t149\tGQAAAA\tADHAAA\tOOOOxx\n6292\t4811\t0\t0\t2\t12\t92\t292\t292\t1292\t6292\t184\t185\tAIAAAA\tBDHAAA\tVVVVxx\n847\t4812\t1\t3\t7\t7\t47\t847\t847\t847\t847\t94\t95\tPGAAAA\tCDHAAA\tAAAAxx\n5954\t4813\t0\t2\t4\t14\t54\t954\t1954\t954\t5954\t108\t109\tAVAAAA\tDDHAAA\tHHHHxx\n8032\t4814\t0\t0\t2\t12\t32\t32\t32\t3032\t8032\t64\t65\tYWAAAA\tEDHAAA\tOOOOxx\n3295\t4815\t1\t3\t5\t15\t95\t295\t1295\t3295\t3295\t190\t191\tTWAAAA\tFDHAAA\tVVVVxx\n8984\t4816\t0\t0\t4\t4\t84\t984\t984\t3984\t8984\t168\t169\tOHAAAA\tGDHAAA\tAAAAxx\n7809\t4817\t1\t1\t9\t9\t9\t809\t1809\t2809\t7809\t18\t19\tJOAAAA\tHDHAAA\tHHHHxx\n1670\t4818\t0\t2\t0\t10\t70\t670\t1670\t1670\t1670\t140\t141\tGMAAAA\tIDHAAA\tOOOOxx\n7733\t4819\t1\t1\t3\t13\t33\t733\t1733\t2733\t7733\t66\t67\tLLAAAA\tJDHAAA\tVVVVxx\n6187\t4820\t1\t3\t7\t7\t87\t187\t187\t1187\t6187\t174\t175\tZDAAAA\tKDHAAA\tAAAAxx\n9326\t4821\t0\t2\t6\t6\t26\t326\t1326\t4326\t9326\t52\t53\tSUAAAA\tLDHAAA\tHHHHxx\n2493\t4822\t1\t1\t3\t13\t93\t493\t493\t2493\t2493\t186\t187\tXRAAAA\tMDHAAA\tOOOOxx\n9512\t4823\t0\t0\t2\t12\t12\t512\t1512\t4512\t9512\t24\t25\tWBAAAA\tNDHAAA\tVVVVxx\n4342\t4824\t0\t2\t2\t2\t42\t342\t342\t4342\t4342\t84\t85\tALAAAA\tODHAAA\tAAAAxx\n5350\t4825\t0\t2\t0\t10\t50\t350\t1350\t350\t5350\t100\t101\tUXAAAA\tPDHAAA\tHHHHxx\n6009\t4826\t1\t1\t9\t9\t9\t9\t9\t1009\t6009\t18\t19\tDXAAAA\tQDHAAA\tOOOOxx\n1208\t4827\t0\t0\t8\t8\t8\t208\t1208\t1208\t1208\t16\t17\tMUAAAA\tRDHAAA\tVVVVxx\n7014\t4828\t0\t2\t4\t14\t14\t14\t1014\t2014\t7014\t28\t29\tUJAAAA\tSDHAAA\tAAAAxx\n2967\t4829\t1\t3\t7\t7\t67\t967\t967\t2967\t2967\t134\t135\tDKAAAA\tTDHAAA\tHHHHxx\n5831\t4830\t1\t3\t1\t11\t31\t831\t1831\t831\t5831\t62\t63\tHQAAAA\tUDHAAA\tOOOOxx\n3097\t4831\t1\t1\t7\t17\t97\t97\t1097\t3097\t3097\t194\t195\tDPAAAA\tVDHAAA\tVVVVxx\n1528\t4832\t0\t0\t8\t8\t28\t528\t1528\t1528\t1528\t56\t57\tUGAAAA\tWDHAAA\tAAAAxx\n6429\t4833\t1\t1\t9\t9\t29\t429\t429\t1429\t6429\t58\t59\tHNAAAA\tXDHAAA\tHHHHxx\n7320\t4834\t0\t0\t0\t0\t20\t320\t1320\t2320\t7320\t40\t41\tOVAAAA\tYDHAAA\tOOOOxx\n844\t4835\t0\t0\t4\t4\t44\t844\t844\t844\t844\t88\t89\tMGAAAA\tZDHAAA\tVVVVxx\n7054\t4836\t0\t2\t4\t14\t54\t54\t1054\t2054\t7054\t108\t109\tILAAAA\tAEHAAA\tAAAAxx\n1643\t4837\t1\t3\t3\t3\t43\t643\t1643\t1643\t1643\t86\t87\tFLAAAA\tBEHAAA\tHHHHxx\n7626\t4838\t0\t2\t6\t6\t26\t626\t1626\t2626\t7626\t52\t53\tIHAAAA\tCEHAAA\tOOOOxx\n8728\t4839\t0\t0\t8\t8\t28\t728\t728\t3728\t8728\t56\t57\tSXAAAA\tDEHAAA\tVVVVxx\n8277\t4840\t1\t1\t7\t17\t77\t277\t277\t3277\t8277\t154\t155\tJGAAAA\tEEHAAA\tAAAAxx\n189\t4841\t1\t1\t9\t9\t89\t189\t189\t189\t189\t178\t179\tHHAAAA\tFEHAAA\tHHHHxx\n3717\t4842\t1\t1\t7\t17\t17\t717\t1717\t3717\t3717\t34\t35\tZMAAAA\tGEHAAA\tOOOOxx\n1020\t4843\t0\t0\t0\t0\t20\t20\t1020\t1020\t1020\t40\t41\tGNAAAA\tHEHAAA\tVVVVxx\n9234\t4844\t0\t2\t4\t14\t34\t234\t1234\t4234\t9234\t68\t69\tERAAAA\tIEHAAA\tAAAAxx\n9541\t4845\t1\t1\t1\t1\t41\t541\t1541\t4541\t9541\t82\t83\tZCAAAA\tJEHAAA\tHHHHxx\n380\t4846\t0\t0\t0\t0\t80\t380\t380\t380\t380\t160\t161\tQOAAAA\tKEHAAA\tOOOOxx\n397\t4847\t1\t1\t7\t17\t97\t397\t397\t397\t397\t194\t195\tHPAAAA\tLEHAAA\tVVVVxx\n835\t4848\t1\t3\t5\t15\t35\t835\t835\t835\t835\t70\t71\tDGAAAA\tMEHAAA\tAAAAxx\n347\t4849\t1\t3\t7\t7\t47\t347\t347\t347\t347\t94\t95\tJNAAAA\tNEHAAA\tHHHHxx\n2490\t4850\t0\t2\t0\t10\t90\t490\t490\t2490\t2490\t180\t181\tURAAAA\tOEHAAA\tOOOOxx\n605\t4851\t1\t1\t5\t5\t5\t605\t605\t605\t605\t10\t11\tHXAAAA\tPEHAAA\tVVVVxx\n7960\t4852\t0\t0\t0\t0\t60\t960\t1960\t2960\t7960\t120\t121\tEUAAAA\tQEHAAA\tAAAAxx\n9681\t4853\t1\t1\t1\t1\t81\t681\t1681\t4681\t9681\t162\t163\tJIAAAA\tREHAAA\tHHHHxx\n5753\t4854\t1\t1\t3\t13\t53\t753\t1753\t753\t5753\t106\t107\tHNAAAA\tSEHAAA\tOOOOxx\n1676\t4855\t0\t0\t6\t16\t76\t676\t1676\t1676\t1676\t152\t153\tMMAAAA\tTEHAAA\tVVVVxx\n5533\t4856\t1\t1\t3\t13\t33\t533\t1533\t533\t5533\t66\t67\tVEAAAA\tUEHAAA\tAAAAxx\n8958\t4857\t0\t2\t8\t18\t58\t958\t958\t3958\t8958\t116\t117\tOGAAAA\tVEHAAA\tHHHHxx\n664\t4858\t0\t0\t4\t4\t64\t664\t664\t664\t664\t128\t129\tOZAAAA\tWEHAAA\tOOOOxx\n3005\t4859\t1\t1\t5\t5\t5\t5\t1005\t3005\t3005\t10\t11\tPLAAAA\tXEHAAA\tVVVVxx\n8576\t4860\t0\t0\t6\t16\t76\t576\t576\t3576\t8576\t152\t153\tWRAAAA\tYEHAAA\tAAAAxx\n7304\t4861\t0\t0\t4\t4\t4\t304\t1304\t2304\t7304\t8\t9\tYUAAAA\tZEHAAA\tHHHHxx\n3375\t4862\t1\t3\t5\t15\t75\t375\t1375\t3375\t3375\t150\t151\tVZAAAA\tAFHAAA\tOOOOxx\n6336\t4863\t0\t0\t6\t16\t36\t336\t336\t1336\t6336\t72\t73\tSJAAAA\tBFHAAA\tVVVVxx\n1392\t4864\t0\t0\t2\t12\t92\t392\t1392\t1392\t1392\t184\t185\tOBAAAA\tCFHAAA\tAAAAxx\n2925\t4865\t1\t1\t5\t5\t25\t925\t925\t2925\t2925\t50\t51\tNIAAAA\tDFHAAA\tHHHHxx\n1217\t4866\t1\t1\t7\t17\t17\t217\t1217\t1217\t1217\t34\t35\tVUAAAA\tEFHAAA\tOOOOxx\n3714\t4867\t0\t2\t4\t14\t14\t714\t1714\t3714\t3714\t28\t29\tWMAAAA\tFFHAAA\tVVVVxx\n2120\t4868\t0\t0\t0\t0\t20\t120\t120\t2120\t2120\t40\t41\tODAAAA\tGFHAAA\tAAAAxx\n2845\t4869\t1\t1\t5\t5\t45\t845\t845\t2845\t2845\t90\t91\tLFAAAA\tHFHAAA\tHHHHxx\n3865\t4870\t1\t1\t5\t5\t65\t865\t1865\t3865\t3865\t130\t131\tRSAAAA\tIFHAAA\tOOOOxx\n124\t4871\t0\t0\t4\t4\t24\t124\t124\t124\t124\t48\t49\tUEAAAA\tJFHAAA\tVVVVxx\n865\t4872\t1\t1\t5\t5\t65\t865\t865\t865\t865\t130\t131\tHHAAAA\tKFHAAA\tAAAAxx\n9361\t4873\t1\t1\t1\t1\t61\t361\t1361\t4361\t9361\t122\t123\tBWAAAA\tLFHAAA\tHHHHxx\n6338\t4874\t0\t2\t8\t18\t38\t338\t338\t1338\t6338\t76\t77\tUJAAAA\tMFHAAA\tOOOOxx\n7330\t4875\t0\t2\t0\t10\t30\t330\t1330\t2330\t7330\t60\t61\tYVAAAA\tNFHAAA\tVVVVxx\n513\t4876\t1\t1\t3\t13\t13\t513\t513\t513\t513\t26\t27\tTTAAAA\tOFHAAA\tAAAAxx\n5001\t4877\t1\t1\t1\t1\t1\t1\t1001\t1\t5001\t2\t3\tJKAAAA\tPFHAAA\tHHHHxx\n549\t4878\t1\t1\t9\t9\t49\t549\t549\t549\t549\t98\t99\tDVAAAA\tQFHAAA\tOOOOxx\n1808\t4879\t0\t0\t8\t8\t8\t808\t1808\t1808\t1808\t16\t17\tORAAAA\tRFHAAA\tVVVVxx\n7168\t4880\t0\t0\t8\t8\t68\t168\t1168\t2168\t7168\t136\t137\tSPAAAA\tSFHAAA\tAAAAxx\n9878\t4881\t0\t2\t8\t18\t78\t878\t1878\t4878\t9878\t156\t157\tYPAAAA\tTFHAAA\tHHHHxx\n233\t4882\t1\t1\t3\t13\t33\t233\t233\t233\t233\t66\t67\tZIAAAA\tUFHAAA\tOOOOxx\n4262\t4883\t0\t2\t2\t2\t62\t262\t262\t4262\t4262\t124\t125\tYHAAAA\tVFHAAA\tVVVVxx\n7998\t4884\t0\t2\t8\t18\t98\t998\t1998\t2998\t7998\t196\t197\tQVAAAA\tWFHAAA\tAAAAxx\n2419\t4885\t1\t3\t9\t19\t19\t419\t419\t2419\t2419\t38\t39\tBPAAAA\tXFHAAA\tHHHHxx\n9960\t4886\t0\t0\t0\t0\t60\t960\t1960\t4960\t9960\t120\t121\tCTAAAA\tYFHAAA\tOOOOxx\n3523\t4887\t1\t3\t3\t3\t23\t523\t1523\t3523\t3523\t46\t47\tNFAAAA\tZFHAAA\tVVVVxx\n5440\t4888\t0\t0\t0\t0\t40\t440\t1440\t440\t5440\t80\t81\tGBAAAA\tAGHAAA\tAAAAxx\n3030\t4889\t0\t2\t0\t10\t30\t30\t1030\t3030\t3030\t60\t61\tOMAAAA\tBGHAAA\tHHHHxx\n2745\t4890\t1\t1\t5\t5\t45\t745\t745\t2745\t2745\t90\t91\tPBAAAA\tCGHAAA\tOOOOxx\n7175\t4891\t1\t3\t5\t15\t75\t175\t1175\t2175\t7175\t150\t151\tZPAAAA\tDGHAAA\tVVVVxx\n640\t4892\t0\t0\t0\t0\t40\t640\t640\t640\t640\t80\t81\tQYAAAA\tEGHAAA\tAAAAxx\n1798\t4893\t0\t2\t8\t18\t98\t798\t1798\t1798\t1798\t196\t197\tERAAAA\tFGHAAA\tHHHHxx\n7499\t4894\t1\t3\t9\t19\t99\t499\t1499\t2499\t7499\t198\t199\tLCAAAA\tGGHAAA\tOOOOxx\n1924\t4895\t0\t0\t4\t4\t24\t924\t1924\t1924\t1924\t48\t49\tAWAAAA\tHGHAAA\tVVVVxx\n1327\t4896\t1\t3\t7\t7\t27\t327\t1327\t1327\t1327\t54\t55\tBZAAAA\tIGHAAA\tAAAAxx\n73\t4897\t1\t1\t3\t13\t73\t73\t73\t73\t73\t146\t147\tVCAAAA\tJGHAAA\tHHHHxx\n9558\t4898\t0\t2\t8\t18\t58\t558\t1558\t4558\t9558\t116\t117\tQDAAAA\tKGHAAA\tOOOOxx\n818\t4899\t0\t2\t8\t18\t18\t818\t818\t818\t818\t36\t37\tMFAAAA\tLGHAAA\tVVVVxx\n9916\t4900\t0\t0\t6\t16\t16\t916\t1916\t4916\t9916\t32\t33\tKRAAAA\tMGHAAA\tAAAAxx\n2978\t4901\t0\t2\t8\t18\t78\t978\t978\t2978\t2978\t156\t157\tOKAAAA\tNGHAAA\tHHHHxx\n8469\t4902\t1\t1\t9\t9\t69\t469\t469\t3469\t8469\t138\t139\tTNAAAA\tOGHAAA\tOOOOxx\n9845\t4903\t1\t1\t5\t5\t45\t845\t1845\t4845\t9845\t90\t91\tROAAAA\tPGHAAA\tVVVVxx\n2326\t4904\t0\t2\t6\t6\t26\t326\t326\t2326\t2326\t52\t53\tMLAAAA\tQGHAAA\tAAAAxx\n4032\t4905\t0\t0\t2\t12\t32\t32\t32\t4032\t4032\t64\t65\tCZAAAA\tRGHAAA\tHHHHxx\n5604\t4906\t0\t0\t4\t4\t4\t604\t1604\t604\t5604\t8\t9\tOHAAAA\tSGHAAA\tOOOOxx\n9610\t4907\t0\t2\t0\t10\t10\t610\t1610\t4610\t9610\t20\t21\tQFAAAA\tTGHAAA\tVVVVxx\n5101\t4908\t1\t1\t1\t1\t1\t101\t1101\t101\t5101\t2\t3\tFOAAAA\tUGHAAA\tAAAAxx\n7246\t4909\t0\t2\t6\t6\t46\t246\t1246\t2246\t7246\t92\t93\tSSAAAA\tVGHAAA\tHHHHxx\n1292\t4910\t0\t0\t2\t12\t92\t292\t1292\t1292\t1292\t184\t185\tSXAAAA\tWGHAAA\tOOOOxx\n6235\t4911\t1\t3\t5\t15\t35\t235\t235\t1235\t6235\t70\t71\tVFAAAA\tXGHAAA\tVVVVxx\n1733\t4912\t1\t1\t3\t13\t33\t733\t1733\t1733\t1733\t66\t67\tROAAAA\tYGHAAA\tAAAAxx\n4647\t4913\t1\t3\t7\t7\t47\t647\t647\t4647\t4647\t94\t95\tTWAAAA\tZGHAAA\tHHHHxx\n258\t4914\t0\t2\t8\t18\t58\t258\t258\t258\t258\t116\t117\tYJAAAA\tAHHAAA\tOOOOxx\n8438\t4915\t0\t2\t8\t18\t38\t438\t438\t3438\t8438\t76\t77\tOMAAAA\tBHHAAA\tVVVVxx\n7869\t4916\t1\t1\t9\t9\t69\t869\t1869\t2869\t7869\t138\t139\tRQAAAA\tCHHAAA\tAAAAxx\n9691\t4917\t1\t3\t1\t11\t91\t691\t1691\t4691\t9691\t182\t183\tTIAAAA\tDHHAAA\tHHHHxx\n5422\t4918\t0\t2\t2\t2\t22\t422\t1422\t422\t5422\t44\t45\tOAAAAA\tEHHAAA\tOOOOxx\n9630\t4919\t0\t2\t0\t10\t30\t630\t1630\t4630\t9630\t60\t61\tKGAAAA\tFHHAAA\tVVVVxx\n4439\t4920\t1\t3\t9\t19\t39\t439\t439\t4439\t4439\t78\t79\tTOAAAA\tGHHAAA\tAAAAxx\n3140\t4921\t0\t0\t0\t0\t40\t140\t1140\t3140\t3140\t80\t81\tUQAAAA\tHHHAAA\tHHHHxx\n9111\t4922\t1\t3\t1\t11\t11\t111\t1111\t4111\t9111\t22\t23\tLMAAAA\tIHHAAA\tOOOOxx\n4606\t4923\t0\t2\t6\t6\t6\t606\t606\t4606\t4606\t12\t13\tEVAAAA\tJHHAAA\tVVVVxx\n8620\t4924\t0\t0\t0\t0\t20\t620\t620\t3620\t8620\t40\t41\tOTAAAA\tKHHAAA\tAAAAxx\n7849\t4925\t1\t1\t9\t9\t49\t849\t1849\t2849\t7849\t98\t99\tXPAAAA\tLHHAAA\tHHHHxx\n346\t4926\t0\t2\t6\t6\t46\t346\t346\t346\t346\t92\t93\tINAAAA\tMHHAAA\tOOOOxx\n9528\t4927\t0\t0\t8\t8\t28\t528\t1528\t4528\t9528\t56\t57\tMCAAAA\tNHHAAA\tVVVVxx\n1811\t4928\t1\t3\t1\t11\t11\t811\t1811\t1811\t1811\t22\t23\tRRAAAA\tOHHAAA\tAAAAxx\n6068\t4929\t0\t0\t8\t8\t68\t68\t68\t1068\t6068\t136\t137\tKZAAAA\tPHHAAA\tHHHHxx\n6260\t4930\t0\t0\t0\t0\t60\t260\t260\t1260\t6260\t120\t121\tUGAAAA\tQHHAAA\tOOOOxx\n5909\t4931\t1\t1\t9\t9\t9\t909\t1909\t909\t5909\t18\t19\tHTAAAA\tRHHAAA\tVVVVxx\n4518\t4932\t0\t2\t8\t18\t18\t518\t518\t4518\t4518\t36\t37\tURAAAA\tSHHAAA\tAAAAxx\n7530\t4933\t0\t2\t0\t10\t30\t530\t1530\t2530\t7530\t60\t61\tQDAAAA\tTHHAAA\tHHHHxx\n3900\t4934\t0\t0\t0\t0\t0\t900\t1900\t3900\t3900\t0\t1\tAUAAAA\tUHHAAA\tOOOOxx\n3969\t4935\t1\t1\t9\t9\t69\t969\t1969\t3969\t3969\t138\t139\tRWAAAA\tVHHAAA\tVVVVxx\n8690\t4936\t0\t2\t0\t10\t90\t690\t690\t3690\t8690\t180\t181\tGWAAAA\tWHHAAA\tAAAAxx\n5532\t4937\t0\t0\t2\t12\t32\t532\t1532\t532\t5532\t64\t65\tUEAAAA\tXHHAAA\tHHHHxx\n5989\t4938\t1\t1\t9\t9\t89\t989\t1989\t989\t5989\t178\t179\tJWAAAA\tYHHAAA\tOOOOxx\n1870\t4939\t0\t2\t0\t10\t70\t870\t1870\t1870\t1870\t140\t141\tYTAAAA\tZHHAAA\tVVVVxx\n1113\t4940\t1\t1\t3\t13\t13\t113\t1113\t1113\t1113\t26\t27\tVQAAAA\tAIHAAA\tAAAAxx\n5155\t4941\t1\t3\t5\t15\t55\t155\t1155\t155\t5155\t110\t111\tHQAAAA\tBIHAAA\tHHHHxx\n7460\t4942\t0\t0\t0\t0\t60\t460\t1460\t2460\t7460\t120\t121\tYAAAAA\tCIHAAA\tOOOOxx\n6217\t4943\t1\t1\t7\t17\t17\t217\t217\t1217\t6217\t34\t35\tDFAAAA\tDIHAAA\tVVVVxx\n8333\t4944\t1\t1\t3\t13\t33\t333\t333\t3333\t8333\t66\t67\tNIAAAA\tEIHAAA\tAAAAxx\n6341\t4945\t1\t1\t1\t1\t41\t341\t341\t1341\t6341\t82\t83\tXJAAAA\tFIHAAA\tHHHHxx\n6230\t4946\t0\t2\t0\t10\t30\t230\t230\t1230\t6230\t60\t61\tQFAAAA\tGIHAAA\tOOOOxx\n6902\t4947\t0\t2\t2\t2\t2\t902\t902\t1902\t6902\t4\t5\tMFAAAA\tHIHAAA\tVVVVxx\n670\t4948\t0\t2\t0\t10\t70\t670\t670\t670\t670\t140\t141\tUZAAAA\tIIHAAA\tAAAAxx\n805\t4949\t1\t1\t5\t5\t5\t805\t805\t805\t805\t10\t11\tZEAAAA\tJIHAAA\tHHHHxx\n1340\t4950\t0\t0\t0\t0\t40\t340\t1340\t1340\t1340\t80\t81\tOZAAAA\tKIHAAA\tOOOOxx\n8649\t4951\t1\t1\t9\t9\t49\t649\t649\t3649\t8649\t98\t99\tRUAAAA\tLIHAAA\tVVVVxx\n3887\t4952\t1\t3\t7\t7\t87\t887\t1887\t3887\t3887\t174\t175\tNTAAAA\tMIHAAA\tAAAAxx\n5400\t4953\t0\t0\t0\t0\t0\t400\t1400\t400\t5400\t0\t1\tSZAAAA\tNIHAAA\tHHHHxx\n4354\t4954\t0\t2\t4\t14\t54\t354\t354\t4354\t4354\t108\t109\tMLAAAA\tOIHAAA\tOOOOxx\n950\t4955\t0\t2\t0\t10\t50\t950\t950\t950\t950\t100\t101\tOKAAAA\tPIHAAA\tVVVVxx\n1544\t4956\t0\t0\t4\t4\t44\t544\t1544\t1544\t1544\t88\t89\tKHAAAA\tQIHAAA\tAAAAxx\n3898\t4957\t0\t2\t8\t18\t98\t898\t1898\t3898\t3898\t196\t197\tYTAAAA\tRIHAAA\tHHHHxx\n8038\t4958\t0\t2\t8\t18\t38\t38\t38\t3038\t8038\t76\t77\tEXAAAA\tSIHAAA\tOOOOxx\n1095\t4959\t1\t3\t5\t15\t95\t95\t1095\t1095\t1095\t190\t191\tDQAAAA\tTIHAAA\tVVVVxx\n1748\t4960\t0\t0\t8\t8\t48\t748\t1748\t1748\t1748\t96\t97\tGPAAAA\tUIHAAA\tAAAAxx\n9154\t4961\t0\t2\t4\t14\t54\t154\t1154\t4154\t9154\t108\t109\tCOAAAA\tVIHAAA\tHHHHxx\n2182\t4962\t0\t2\t2\t2\t82\t182\t182\t2182\t2182\t164\t165\tYFAAAA\tWIHAAA\tOOOOxx\n6797\t4963\t1\t1\t7\t17\t97\t797\t797\t1797\t6797\t194\t195\tLBAAAA\tXIHAAA\tVVVVxx\n9149\t4964\t1\t1\t9\t9\t49\t149\t1149\t4149\t9149\t98\t99\tXNAAAA\tYIHAAA\tAAAAxx\n7351\t4965\t1\t3\t1\t11\t51\t351\t1351\t2351\t7351\t102\t103\tTWAAAA\tZIHAAA\tHHHHxx\n2820\t4966\t0\t0\t0\t0\t20\t820\t820\t2820\t2820\t40\t41\tMEAAAA\tAJHAAA\tOOOOxx\n9696\t4967\t0\t0\t6\t16\t96\t696\t1696\t4696\t9696\t192\t193\tYIAAAA\tBJHAAA\tVVVVxx\n253\t4968\t1\t1\t3\t13\t53\t253\t253\t253\t253\t106\t107\tTJAAAA\tCJHAAA\tAAAAxx\n3600\t4969\t0\t0\t0\t0\t0\t600\t1600\t3600\t3600\t0\t1\tMIAAAA\tDJHAAA\tHHHHxx\n3892\t4970\t0\t0\t2\t12\t92\t892\t1892\t3892\t3892\t184\t185\tSTAAAA\tEJHAAA\tOOOOxx\n231\t4971\t1\t3\t1\t11\t31\t231\t231\t231\t231\t62\t63\tXIAAAA\tFJHAAA\tVVVVxx\n8331\t4972\t1\t3\t1\t11\t31\t331\t331\t3331\t8331\t62\t63\tLIAAAA\tGJHAAA\tAAAAxx\n403\t4973\t1\t3\t3\t3\t3\t403\t403\t403\t403\t6\t7\tNPAAAA\tHJHAAA\tHHHHxx\n8642\t4974\t0\t2\t2\t2\t42\t642\t642\t3642\t8642\t84\t85\tKUAAAA\tIJHAAA\tOOOOxx\n3118\t4975\t0\t2\t8\t18\t18\t118\t1118\t3118\t3118\t36\t37\tYPAAAA\tJJHAAA\tVVVVxx\n3835\t4976\t1\t3\t5\t15\t35\t835\t1835\t3835\t3835\t70\t71\tNRAAAA\tKJHAAA\tAAAAxx\n1117\t4977\t1\t1\t7\t17\t17\t117\t1117\t1117\t1117\t34\t35\tZQAAAA\tLJHAAA\tHHHHxx\n7024\t4978\t0\t0\t4\t4\t24\t24\t1024\t2024\t7024\t48\t49\tEKAAAA\tMJHAAA\tOOOOxx\n2636\t4979\t0\t0\t6\t16\t36\t636\t636\t2636\t2636\t72\t73\tKXAAAA\tNJHAAA\tVVVVxx\n3778\t4980\t0\t2\t8\t18\t78\t778\t1778\t3778\t3778\t156\t157\tIPAAAA\tOJHAAA\tAAAAxx\n2003\t4981\t1\t3\t3\t3\t3\t3\t3\t2003\t2003\t6\t7\tBZAAAA\tPJHAAA\tHHHHxx\n5717\t4982\t1\t1\t7\t17\t17\t717\t1717\t717\t5717\t34\t35\tXLAAAA\tQJHAAA\tOOOOxx\n4869\t4983\t1\t1\t9\t9\t69\t869\t869\t4869\t4869\t138\t139\tHFAAAA\tRJHAAA\tVVVVxx\n8921\t4984\t1\t1\t1\t1\t21\t921\t921\t3921\t8921\t42\t43\tDFAAAA\tSJHAAA\tAAAAxx\n888\t4985\t0\t0\t8\t8\t88\t888\t888\t888\t888\t176\t177\tEIAAAA\tTJHAAA\tHHHHxx\n7599\t4986\t1\t3\t9\t19\t99\t599\t1599\t2599\t7599\t198\t199\tHGAAAA\tUJHAAA\tOOOOxx\n8621\t4987\t1\t1\t1\t1\t21\t621\t621\t3621\t8621\t42\t43\tPTAAAA\tVJHAAA\tVVVVxx\n811\t4988\t1\t3\t1\t11\t11\t811\t811\t811\t811\t22\t23\tFFAAAA\tWJHAAA\tAAAAxx\n9147\t4989\t1\t3\t7\t7\t47\t147\t1147\t4147\t9147\t94\t95\tVNAAAA\tXJHAAA\tHHHHxx\n1413\t4990\t1\t1\t3\t13\t13\t413\t1413\t1413\t1413\t26\t27\tJCAAAA\tYJHAAA\tOOOOxx\n5232\t4991\t0\t0\t2\t12\t32\t232\t1232\t232\t5232\t64\t65\tGTAAAA\tZJHAAA\tVVVVxx\n5912\t4992\t0\t0\t2\t12\t12\t912\t1912\t912\t5912\t24\t25\tKTAAAA\tAKHAAA\tAAAAxx\n3418\t4993\t0\t2\t8\t18\t18\t418\t1418\t3418\t3418\t36\t37\tMBAAAA\tBKHAAA\tHHHHxx\n3912\t4994\t0\t0\t2\t12\t12\t912\t1912\t3912\t3912\t24\t25\tMUAAAA\tCKHAAA\tOOOOxx\n9576\t4995\t0\t0\t6\t16\t76\t576\t1576\t4576\t9576\t152\t153\tIEAAAA\tDKHAAA\tVVVVxx\n4225\t4996\t1\t1\t5\t5\t25\t225\t225\t4225\t4225\t50\t51\tNGAAAA\tEKHAAA\tAAAAxx\n8222\t4997\t0\t2\t2\t2\t22\t222\t222\t3222\t8222\t44\t45\tGEAAAA\tFKHAAA\tHHHHxx\n7013\t4998\t1\t1\t3\t13\t13\t13\t1013\t2013\t7013\t26\t27\tTJAAAA\tGKHAAA\tOOOOxx\n7037\t4999\t1\t1\t7\t17\t37\t37\t1037\t2037\t7037\t74\t75\tRKAAAA\tHKHAAA\tVVVVxx\n1205\t5000\t1\t1\t5\t5\t5\t205\t1205\t1205\t1205\t10\t11\tJUAAAA\tIKHAAA\tAAAAxx\n8114\t5001\t0\t2\t4\t14\t14\t114\t114\t3114\t8114\t28\t29\tCAAAAA\tJKHAAA\tHHHHxx\n6585\t5002\t1\t1\t5\t5\t85\t585\t585\t1585\t6585\t170\t171\tHTAAAA\tKKHAAA\tOOOOxx\n155\t5003\t1\t3\t5\t15\t55\t155\t155\t155\t155\t110\t111\tZFAAAA\tLKHAAA\tVVVVxx\n2841\t5004\t1\t1\t1\t1\t41\t841\t841\t2841\t2841\t82\t83\tHFAAAA\tMKHAAA\tAAAAxx\n1996\t5005\t0\t0\t6\t16\t96\t996\t1996\t1996\t1996\t192\t193\tUYAAAA\tNKHAAA\tHHHHxx\n4948\t5006\t0\t0\t8\t8\t48\t948\t948\t4948\t4948\t96\t97\tIIAAAA\tOKHAAA\tOOOOxx\n3304\t5007\t0\t0\t4\t4\t4\t304\t1304\t3304\t3304\t8\t9\tCXAAAA\tPKHAAA\tVVVVxx\n5684\t5008\t0\t0\t4\t4\t84\t684\t1684\t684\t5684\t168\t169\tQKAAAA\tQKHAAA\tAAAAxx\n6962\t5009\t0\t2\t2\t2\t62\t962\t962\t1962\t6962\t124\t125\tUHAAAA\tRKHAAA\tHHHHxx\n8691\t5010\t1\t3\t1\t11\t91\t691\t691\t3691\t8691\t182\t183\tHWAAAA\tSKHAAA\tOOOOxx\n8501\t5011\t1\t1\t1\t1\t1\t501\t501\t3501\t8501\t2\t3\tZOAAAA\tTKHAAA\tVVVVxx\n4783\t5012\t1\t3\t3\t3\t83\t783\t783\t4783\t4783\t166\t167\tZBAAAA\tUKHAAA\tAAAAxx\n3762\t5013\t0\t2\t2\t2\t62\t762\t1762\t3762\t3762\t124\t125\tSOAAAA\tVKHAAA\tHHHHxx\n4534\t5014\t0\t2\t4\t14\t34\t534\t534\t4534\t4534\t68\t69\tKSAAAA\tWKHAAA\tOOOOxx\n4999\t5015\t1\t3\t9\t19\t99\t999\t999\t4999\t4999\t198\t199\tHKAAAA\tXKHAAA\tVVVVxx\n4618\t5016\t0\t2\t8\t18\t18\t618\t618\t4618\t4618\t36\t37\tQVAAAA\tYKHAAA\tAAAAxx\n4220\t5017\t0\t0\t0\t0\t20\t220\t220\t4220\t4220\t40\t41\tIGAAAA\tZKHAAA\tHHHHxx\n3384\t5018\t0\t0\t4\t4\t84\t384\t1384\t3384\t3384\t168\t169\tEAAAAA\tALHAAA\tOOOOxx\n3036\t5019\t0\t0\t6\t16\t36\t36\t1036\t3036\t3036\t72\t73\tUMAAAA\tBLHAAA\tVVVVxx\n545\t5020\t1\t1\t5\t5\t45\t545\t545\t545\t545\t90\t91\tZUAAAA\tCLHAAA\tAAAAxx\n9946\t5021\t0\t2\t6\t6\t46\t946\t1946\t4946\t9946\t92\t93\tOSAAAA\tDLHAAA\tHHHHxx\n1985\t5022\t1\t1\t5\t5\t85\t985\t1985\t1985\t1985\t170\t171\tJYAAAA\tELHAAA\tOOOOxx\n2310\t5023\t0\t2\t0\t10\t10\t310\t310\t2310\t2310\t20\t21\tWKAAAA\tFLHAAA\tVVVVxx\n6563\t5024\t1\t3\t3\t3\t63\t563\t563\t1563\t6563\t126\t127\tLSAAAA\tGLHAAA\tAAAAxx\n4886\t5025\t0\t2\t6\t6\t86\t886\t886\t4886\t4886\t172\t173\tYFAAAA\tHLHAAA\tHHHHxx\n9359\t5026\t1\t3\t9\t19\t59\t359\t1359\t4359\t9359\t118\t119\tZVAAAA\tILHAAA\tOOOOxx\n400\t5027\t0\t0\t0\t0\t0\t400\t400\t400\t400\t0\t1\tKPAAAA\tJLHAAA\tVVVVxx\n9742\t5028\t0\t2\t2\t2\t42\t742\t1742\t4742\t9742\t84\t85\tSKAAAA\tKLHAAA\tAAAAxx\n6736\t5029\t0\t0\t6\t16\t36\t736\t736\t1736\t6736\t72\t73\tCZAAAA\tLLHAAA\tHHHHxx\n8166\t5030\t0\t2\t6\t6\t66\t166\t166\t3166\t8166\t132\t133\tCCAAAA\tMLHAAA\tOOOOxx\n861\t5031\t1\t1\t1\t1\t61\t861\t861\t861\t861\t122\t123\tDHAAAA\tNLHAAA\tVVVVxx\n7492\t5032\t0\t0\t2\t12\t92\t492\t1492\t2492\t7492\t184\t185\tECAAAA\tOLHAAA\tAAAAxx\n1155\t5033\t1\t3\t5\t15\t55\t155\t1155\t1155\t1155\t110\t111\tLSAAAA\tPLHAAA\tHHHHxx\n9769\t5034\t1\t1\t9\t9\t69\t769\t1769\t4769\t9769\t138\t139\tTLAAAA\tQLHAAA\tOOOOxx\n6843\t5035\t1\t3\t3\t3\t43\t843\t843\t1843\t6843\t86\t87\tFDAAAA\tRLHAAA\tVVVVxx\n5625\t5036\t1\t1\t5\t5\t25\t625\t1625\t625\t5625\t50\t51\tJIAAAA\tSLHAAA\tAAAAxx\n1910\t5037\t0\t2\t0\t10\t10\t910\t1910\t1910\t1910\t20\t21\tMVAAAA\tTLHAAA\tHHHHxx\n9796\t5038\t0\t0\t6\t16\t96\t796\t1796\t4796\t9796\t192\t193\tUMAAAA\tULHAAA\tOOOOxx\n6950\t5039\t0\t2\t0\t10\t50\t950\t950\t1950\t6950\t100\t101\tIHAAAA\tVLHAAA\tVVVVxx\n3084\t5040\t0\t0\t4\t4\t84\t84\t1084\t3084\t3084\t168\t169\tQOAAAA\tWLHAAA\tAAAAxx\n2959\t5041\t1\t3\t9\t19\t59\t959\t959\t2959\t2959\t118\t119\tVJAAAA\tXLHAAA\tHHHHxx\n2093\t5042\t1\t1\t3\t13\t93\t93\t93\t2093\t2093\t186\t187\tNCAAAA\tYLHAAA\tOOOOxx\n2738\t5043\t0\t2\t8\t18\t38\t738\t738\t2738\t2738\t76\t77\tIBAAAA\tZLHAAA\tVVVVxx\n6406\t5044\t0\t2\t6\t6\t6\t406\t406\t1406\t6406\t12\t13\tKMAAAA\tAMHAAA\tAAAAxx\n9082\t5045\t0\t2\t2\t2\t82\t82\t1082\t4082\t9082\t164\t165\tILAAAA\tBMHAAA\tHHHHxx\n8568\t5046\t0\t0\t8\t8\t68\t568\t568\t3568\t8568\t136\t137\tORAAAA\tCMHAAA\tOOOOxx\n3566\t5047\t0\t2\t6\t6\t66\t566\t1566\t3566\t3566\t132\t133\tEHAAAA\tDMHAAA\tVVVVxx\n3016\t5048\t0\t0\t6\t16\t16\t16\t1016\t3016\t3016\t32\t33\tAMAAAA\tEMHAAA\tAAAAxx\n1207\t5049\t1\t3\t7\t7\t7\t207\t1207\t1207\t1207\t14\t15\tLUAAAA\tFMHAAA\tHHHHxx\n4045\t5050\t1\t1\t5\t5\t45\t45\t45\t4045\t4045\t90\t91\tPZAAAA\tGMHAAA\tOOOOxx\n4173\t5051\t1\t1\t3\t13\t73\t173\t173\t4173\t4173\t146\t147\tNEAAAA\tHMHAAA\tVVVVxx\n3939\t5052\t1\t3\t9\t19\t39\t939\t1939\t3939\t3939\t78\t79\tNVAAAA\tIMHAAA\tAAAAxx\n9683\t5053\t1\t3\t3\t3\t83\t683\t1683\t4683\t9683\t166\t167\tLIAAAA\tJMHAAA\tHHHHxx\n1684\t5054\t0\t0\t4\t4\t84\t684\t1684\t1684\t1684\t168\t169\tUMAAAA\tKMHAAA\tOOOOxx\n9271\t5055\t1\t3\t1\t11\t71\t271\t1271\t4271\t9271\t142\t143\tPSAAAA\tLMHAAA\tVVVVxx\n9317\t5056\t1\t1\t7\t17\t17\t317\t1317\t4317\t9317\t34\t35\tJUAAAA\tMMHAAA\tAAAAxx\n5793\t5057\t1\t1\t3\t13\t93\t793\t1793\t793\t5793\t186\t187\tVOAAAA\tNMHAAA\tHHHHxx\n352\t5058\t0\t0\t2\t12\t52\t352\t352\t352\t352\t104\t105\tONAAAA\tOMHAAA\tOOOOxx\n7328\t5059\t0\t0\t8\t8\t28\t328\t1328\t2328\t7328\t56\t57\tWVAAAA\tPMHAAA\tVVVVxx\n4582\t5060\t0\t2\t2\t2\t82\t582\t582\t4582\t4582\t164\t165\tGUAAAA\tQMHAAA\tAAAAxx\n7413\t5061\t1\t1\t3\t13\t13\t413\t1413\t2413\t7413\t26\t27\tDZAAAA\tRMHAAA\tHHHHxx\n6772\t5062\t0\t0\t2\t12\t72\t772\t772\t1772\t6772\t144\t145\tMAAAAA\tSMHAAA\tOOOOxx\n4973\t5063\t1\t1\t3\t13\t73\t973\t973\t4973\t4973\t146\t147\tHJAAAA\tTMHAAA\tVVVVxx\n7480\t5064\t0\t0\t0\t0\t80\t480\t1480\t2480\t7480\t160\t161\tSBAAAA\tUMHAAA\tAAAAxx\n5555\t5065\t1\t3\t5\t15\t55\t555\t1555\t555\t5555\t110\t111\tRFAAAA\tVMHAAA\tHHHHxx\n4227\t5066\t1\t3\t7\t7\t27\t227\t227\t4227\t4227\t54\t55\tPGAAAA\tWMHAAA\tOOOOxx\n4153\t5067\t1\t1\t3\t13\t53\t153\t153\t4153\t4153\t106\t107\tTDAAAA\tXMHAAA\tVVVVxx\n4601\t5068\t1\t1\t1\t1\t1\t601\t601\t4601\t4601\t2\t3\tZUAAAA\tYMHAAA\tAAAAxx\n3782\t5069\t0\t2\t2\t2\t82\t782\t1782\t3782\t3782\t164\t165\tMPAAAA\tZMHAAA\tHHHHxx\n3872\t5070\t0\t0\t2\t12\t72\t872\t1872\t3872\t3872\t144\t145\tYSAAAA\tANHAAA\tOOOOxx\n893\t5071\t1\t1\t3\t13\t93\t893\t893\t893\t893\t186\t187\tJIAAAA\tBNHAAA\tVVVVxx\n2430\t5072\t0\t2\t0\t10\t30\t430\t430\t2430\t2430\t60\t61\tMPAAAA\tCNHAAA\tAAAAxx\n2591\t5073\t1\t3\t1\t11\t91\t591\t591\t2591\t2591\t182\t183\tRVAAAA\tDNHAAA\tHHHHxx\n264\t5074\t0\t0\t4\t4\t64\t264\t264\t264\t264\t128\t129\tEKAAAA\tENHAAA\tOOOOxx\n6238\t5075\t0\t2\t8\t18\t38\t238\t238\t1238\t6238\t76\t77\tYFAAAA\tFNHAAA\tVVVVxx\n633\t5076\t1\t1\t3\t13\t33\t633\t633\t633\t633\t66\t67\tJYAAAA\tGNHAAA\tAAAAxx\n1029\t5077\t1\t1\t9\t9\t29\t29\t1029\t1029\t1029\t58\t59\tPNAAAA\tHNHAAA\tHHHHxx\n5934\t5078\t0\t2\t4\t14\t34\t934\t1934\t934\t5934\t68\t69\tGUAAAA\tINHAAA\tOOOOxx\n8694\t5079\t0\t2\t4\t14\t94\t694\t694\t3694\t8694\t188\t189\tKWAAAA\tJNHAAA\tVVVVxx\n7401\t5080\t1\t1\t1\t1\t1\t401\t1401\t2401\t7401\t2\t3\tRYAAAA\tKNHAAA\tAAAAxx\n1165\t5081\t1\t1\t5\t5\t65\t165\t1165\t1165\t1165\t130\t131\tVSAAAA\tLNHAAA\tHHHHxx\n9438\t5082\t0\t2\t8\t18\t38\t438\t1438\t4438\t9438\t76\t77\tAZAAAA\tMNHAAA\tOOOOxx\n4790\t5083\t0\t2\t0\t10\t90\t790\t790\t4790\t4790\t180\t181\tGCAAAA\tNNHAAA\tVVVVxx\n4531\t5084\t1\t3\t1\t11\t31\t531\t531\t4531\t4531\t62\t63\tHSAAAA\tONHAAA\tAAAAxx\n6099\t5085\t1\t3\t9\t19\t99\t99\t99\t1099\t6099\t198\t199\tPAAAAA\tPNHAAA\tHHHHxx\n8236\t5086\t0\t0\t6\t16\t36\t236\t236\t3236\t8236\t72\t73\tUEAAAA\tQNHAAA\tOOOOxx\n8551\t5087\t1\t3\t1\t11\t51\t551\t551\t3551\t8551\t102\t103\tXQAAAA\tRNHAAA\tVVVVxx\n3128\t5088\t0\t0\t8\t8\t28\t128\t1128\t3128\t3128\t56\t57\tIQAAAA\tSNHAAA\tAAAAxx\n3504\t5089\t0\t0\t4\t4\t4\t504\t1504\t3504\t3504\t8\t9\tUEAAAA\tTNHAAA\tHHHHxx\n9071\t5090\t1\t3\t1\t11\t71\t71\t1071\t4071\t9071\t142\t143\tXKAAAA\tUNHAAA\tOOOOxx\n5930\t5091\t0\t2\t0\t10\t30\t930\t1930\t930\t5930\t60\t61\tCUAAAA\tVNHAAA\tVVVVxx\n6825\t5092\t1\t1\t5\t5\t25\t825\t825\t1825\t6825\t50\t51\tNCAAAA\tWNHAAA\tAAAAxx\n2218\t5093\t0\t2\t8\t18\t18\t218\t218\t2218\t2218\t36\t37\tIHAAAA\tXNHAAA\tHHHHxx\n3604\t5094\t0\t0\t4\t4\t4\t604\t1604\t3604\t3604\t8\t9\tQIAAAA\tYNHAAA\tOOOOxx\n5761\t5095\t1\t1\t1\t1\t61\t761\t1761\t761\t5761\t122\t123\tPNAAAA\tZNHAAA\tVVVVxx\n5414\t5096\t0\t2\t4\t14\t14\t414\t1414\t414\t5414\t28\t29\tGAAAAA\tAOHAAA\tAAAAxx\n5892\t5097\t0\t0\t2\t12\t92\t892\t1892\t892\t5892\t184\t185\tQSAAAA\tBOHAAA\tHHHHxx\n4080\t5098\t0\t0\t0\t0\t80\t80\t80\t4080\t4080\t160\t161\tYAAAAA\tCOHAAA\tOOOOxx\n8018\t5099\t0\t2\t8\t18\t18\t18\t18\t3018\t8018\t36\t37\tKWAAAA\tDOHAAA\tVVVVxx\n1757\t5100\t1\t1\t7\t17\t57\t757\t1757\t1757\t1757\t114\t115\tPPAAAA\tEOHAAA\tAAAAxx\n5854\t5101\t0\t2\t4\t14\t54\t854\t1854\t854\t5854\t108\t109\tERAAAA\tFOHAAA\tHHHHxx\n1335\t5102\t1\t3\t5\t15\t35\t335\t1335\t1335\t1335\t70\t71\tJZAAAA\tGOHAAA\tOOOOxx\n3811\t5103\t1\t3\t1\t11\t11\t811\t1811\t3811\t3811\t22\t23\tPQAAAA\tHOHAAA\tVVVVxx\n9917\t5104\t1\t1\t7\t17\t17\t917\t1917\t4917\t9917\t34\t35\tLRAAAA\tIOHAAA\tAAAAxx\n5947\t5105\t1\t3\t7\t7\t47\t947\t1947\t947\t5947\t94\t95\tTUAAAA\tJOHAAA\tHHHHxx\n7263\t5106\t1\t3\t3\t3\t63\t263\t1263\t2263\t7263\t126\t127\tJTAAAA\tKOHAAA\tOOOOxx\n1730\t5107\t0\t2\t0\t10\t30\t730\t1730\t1730\t1730\t60\t61\tOOAAAA\tLOHAAA\tVVVVxx\n5747\t5108\t1\t3\t7\t7\t47\t747\t1747\t747\t5747\t94\t95\tBNAAAA\tMOHAAA\tAAAAxx\n3876\t5109\t0\t0\t6\t16\t76\t876\t1876\t3876\t3876\t152\t153\tCTAAAA\tNOHAAA\tHHHHxx\n2762\t5110\t0\t2\t2\t2\t62\t762\t762\t2762\t2762\t124\t125\tGCAAAA\tOOHAAA\tOOOOxx\n7613\t5111\t1\t1\t3\t13\t13\t613\t1613\t2613\t7613\t26\t27\tVGAAAA\tPOHAAA\tVVVVxx\n152\t5112\t0\t0\t2\t12\t52\t152\t152\t152\t152\t104\t105\tWFAAAA\tQOHAAA\tAAAAxx\n3941\t5113\t1\t1\t1\t1\t41\t941\t1941\t3941\t3941\t82\t83\tPVAAAA\tROHAAA\tHHHHxx\n5614\t5114\t0\t2\t4\t14\t14\t614\t1614\t614\t5614\t28\t29\tYHAAAA\tSOHAAA\tOOOOxx\n9279\t5115\t1\t3\t9\t19\t79\t279\t1279\t4279\t9279\t158\t159\tXSAAAA\tTOHAAA\tVVVVxx\n3048\t5116\t0\t0\t8\t8\t48\t48\t1048\t3048\t3048\t96\t97\tGNAAAA\tUOHAAA\tAAAAxx\n6152\t5117\t0\t0\t2\t12\t52\t152\t152\t1152\t6152\t104\t105\tQCAAAA\tVOHAAA\tHHHHxx\n5481\t5118\t1\t1\t1\t1\t81\t481\t1481\t481\t5481\t162\t163\tVCAAAA\tWOHAAA\tOOOOxx\n4675\t5119\t1\t3\t5\t15\t75\t675\t675\t4675\t4675\t150\t151\tVXAAAA\tXOHAAA\tVVVVxx\n3334\t5120\t0\t2\t4\t14\t34\t334\t1334\t3334\t3334\t68\t69\tGYAAAA\tYOHAAA\tAAAAxx\n4691\t5121\t1\t3\t1\t11\t91\t691\t691\t4691\t4691\t182\t183\tLYAAAA\tZOHAAA\tHHHHxx\n803\t5122\t1\t3\t3\t3\t3\t803\t803\t803\t803\t6\t7\tXEAAAA\tAPHAAA\tOOOOxx\n5409\t5123\t1\t1\t9\t9\t9\t409\t1409\t409\t5409\t18\t19\tBAAAAA\tBPHAAA\tVVVVxx\n1054\t5124\t0\t2\t4\t14\t54\t54\t1054\t1054\t1054\t108\t109\tOOAAAA\tCPHAAA\tAAAAxx\n103\t5125\t1\t3\t3\t3\t3\t103\t103\t103\t103\t6\t7\tZDAAAA\tDPHAAA\tHHHHxx\n8565\t5126\t1\t1\t5\t5\t65\t565\t565\t3565\t8565\t130\t131\tLRAAAA\tEPHAAA\tOOOOxx\n4666\t5127\t0\t2\t6\t6\t66\t666\t666\t4666\t4666\t132\t133\tMXAAAA\tFPHAAA\tVVVVxx\n6634\t5128\t0\t2\t4\t14\t34\t634\t634\t1634\t6634\t68\t69\tEVAAAA\tGPHAAA\tAAAAxx\n5538\t5129\t0\t2\t8\t18\t38\t538\t1538\t538\t5538\t76\t77\tAFAAAA\tHPHAAA\tHHHHxx\n3789\t5130\t1\t1\t9\t9\t89\t789\t1789\t3789\t3789\t178\t179\tTPAAAA\tIPHAAA\tOOOOxx\n4641\t5131\t1\t1\t1\t1\t41\t641\t641\t4641\t4641\t82\t83\tNWAAAA\tJPHAAA\tVVVVxx\n2458\t5132\t0\t2\t8\t18\t58\t458\t458\t2458\t2458\t116\t117\tOQAAAA\tKPHAAA\tAAAAxx\n5667\t5133\t1\t3\t7\t7\t67\t667\t1667\t667\t5667\t134\t135\tZJAAAA\tLPHAAA\tHHHHxx\n6524\t5134\t0\t0\t4\t4\t24\t524\t524\t1524\t6524\t48\t49\tYQAAAA\tMPHAAA\tOOOOxx\n9179\t5135\t1\t3\t9\t19\t79\t179\t1179\t4179\t9179\t158\t159\tBPAAAA\tNPHAAA\tVVVVxx\n6358\t5136\t0\t2\t8\t18\t58\t358\t358\t1358\t6358\t116\t117\tOKAAAA\tOPHAAA\tAAAAxx\n6668\t5137\t0\t0\t8\t8\t68\t668\t668\t1668\t6668\t136\t137\tMWAAAA\tPPHAAA\tHHHHxx\n6414\t5138\t0\t2\t4\t14\t14\t414\t414\t1414\t6414\t28\t29\tSMAAAA\tQPHAAA\tOOOOxx\n2813\t5139\t1\t1\t3\t13\t13\t813\t813\t2813\t2813\t26\t27\tFEAAAA\tRPHAAA\tVVVVxx\n8927\t5140\t1\t3\t7\t7\t27\t927\t927\t3927\t8927\t54\t55\tJFAAAA\tSPHAAA\tAAAAxx\n8695\t5141\t1\t3\t5\t15\t95\t695\t695\t3695\t8695\t190\t191\tLWAAAA\tTPHAAA\tHHHHxx\n363\t5142\t1\t3\t3\t3\t63\t363\t363\t363\t363\t126\t127\tZNAAAA\tUPHAAA\tOOOOxx\n9966\t5143\t0\t2\t6\t6\t66\t966\t1966\t4966\t9966\t132\t133\tITAAAA\tVPHAAA\tVVVVxx\n1323\t5144\t1\t3\t3\t3\t23\t323\t1323\t1323\t1323\t46\t47\tXYAAAA\tWPHAAA\tAAAAxx\n8211\t5145\t1\t3\t1\t11\t11\t211\t211\t3211\t8211\t22\t23\tVDAAAA\tXPHAAA\tHHHHxx\n4375\t5146\t1\t3\t5\t15\t75\t375\t375\t4375\t4375\t150\t151\tHMAAAA\tYPHAAA\tOOOOxx\n3257\t5147\t1\t1\t7\t17\t57\t257\t1257\t3257\t3257\t114\t115\tHVAAAA\tZPHAAA\tVVVVxx\n6239\t5148\t1\t3\t9\t19\t39\t239\t239\t1239\t6239\t78\t79\tZFAAAA\tAQHAAA\tAAAAxx\n3602\t5149\t0\t2\t2\t2\t2\t602\t1602\t3602\t3602\t4\t5\tOIAAAA\tBQHAAA\tHHHHxx\n9830\t5150\t0\t2\t0\t10\t30\t830\t1830\t4830\t9830\t60\t61\tCOAAAA\tCQHAAA\tOOOOxx\n7826\t5151\t0\t2\t6\t6\t26\t826\t1826\t2826\t7826\t52\t53\tAPAAAA\tDQHAAA\tVVVVxx\n2108\t5152\t0\t0\t8\t8\t8\t108\t108\t2108\t2108\t16\t17\tCDAAAA\tEQHAAA\tAAAAxx\n7245\t5153\t1\t1\t5\t5\t45\t245\t1245\t2245\t7245\t90\t91\tRSAAAA\tFQHAAA\tHHHHxx\n8330\t5154\t0\t2\t0\t10\t30\t330\t330\t3330\t8330\t60\t61\tKIAAAA\tGQHAAA\tOOOOxx\n7441\t5155\t1\t1\t1\t1\t41\t441\t1441\t2441\t7441\t82\t83\tFAAAAA\tHQHAAA\tVVVVxx\n9848\t5156\t0\t0\t8\t8\t48\t848\t1848\t4848\t9848\t96\t97\tUOAAAA\tIQHAAA\tAAAAxx\n1226\t5157\t0\t2\t6\t6\t26\t226\t1226\t1226\t1226\t52\t53\tEVAAAA\tJQHAAA\tHHHHxx\n414\t5158\t0\t2\t4\t14\t14\t414\t414\t414\t414\t28\t29\tYPAAAA\tKQHAAA\tOOOOxx\n1273\t5159\t1\t1\t3\t13\t73\t273\t1273\t1273\t1273\t146\t147\tZWAAAA\tLQHAAA\tVVVVxx\n9866\t5160\t0\t2\t6\t6\t66\t866\t1866\t4866\t9866\t132\t133\tMPAAAA\tMQHAAA\tAAAAxx\n4633\t5161\t1\t1\t3\t13\t33\t633\t633\t4633\t4633\t66\t67\tFWAAAA\tNQHAAA\tHHHHxx\n8727\t5162\t1\t3\t7\t7\t27\t727\t727\t3727\t8727\t54\t55\tRXAAAA\tOQHAAA\tOOOOxx\n5308\t5163\t0\t0\t8\t8\t8\t308\t1308\t308\t5308\t16\t17\tEWAAAA\tPQHAAA\tVVVVxx\n1395\t5164\t1\t3\t5\t15\t95\t395\t1395\t1395\t1395\t190\t191\tRBAAAA\tQQHAAA\tAAAAxx\n1825\t5165\t1\t1\t5\t5\t25\t825\t1825\t1825\t1825\t50\t51\tFSAAAA\tRQHAAA\tHHHHxx\n7606\t5166\t0\t2\t6\t6\t6\t606\t1606\t2606\t7606\t12\t13\tOGAAAA\tSQHAAA\tOOOOxx\n9390\t5167\t0\t2\t0\t10\t90\t390\t1390\t4390\t9390\t180\t181\tEXAAAA\tTQHAAA\tVVVVxx\n2376\t5168\t0\t0\t6\t16\t76\t376\t376\t2376\t2376\t152\t153\tKNAAAA\tUQHAAA\tAAAAxx\n2377\t5169\t1\t1\t7\t17\t77\t377\t377\t2377\t2377\t154\t155\tLNAAAA\tVQHAAA\tHHHHxx\n5346\t5170\t0\t2\t6\t6\t46\t346\t1346\t346\t5346\t92\t93\tQXAAAA\tWQHAAA\tOOOOxx\n4140\t5171\t0\t0\t0\t0\t40\t140\t140\t4140\t4140\t80\t81\tGDAAAA\tXQHAAA\tVVVVxx\n6032\t5172\t0\t0\t2\t12\t32\t32\t32\t1032\t6032\t64\t65\tAYAAAA\tYQHAAA\tAAAAxx\n9453\t5173\t1\t1\t3\t13\t53\t453\t1453\t4453\t9453\t106\t107\tPZAAAA\tZQHAAA\tHHHHxx\n9297\t5174\t1\t1\t7\t17\t97\t297\t1297\t4297\t9297\t194\t195\tPTAAAA\tARHAAA\tOOOOxx\n6455\t5175\t1\t3\t5\t15\t55\t455\t455\t1455\t6455\t110\t111\tHOAAAA\tBRHAAA\tVVVVxx\n4458\t5176\t0\t2\t8\t18\t58\t458\t458\t4458\t4458\t116\t117\tMPAAAA\tCRHAAA\tAAAAxx\n9516\t5177\t0\t0\t6\t16\t16\t516\t1516\t4516\t9516\t32\t33\tACAAAA\tDRHAAA\tHHHHxx\n6211\t5178\t1\t3\t1\t11\t11\t211\t211\t1211\t6211\t22\t23\tXEAAAA\tERHAAA\tOOOOxx\n526\t5179\t0\t2\t6\t6\t26\t526\t526\t526\t526\t52\t53\tGUAAAA\tFRHAAA\tVVVVxx\n3570\t5180\t0\t2\t0\t10\t70\t570\t1570\t3570\t3570\t140\t141\tIHAAAA\tGRHAAA\tAAAAxx\n4885\t5181\t1\t1\t5\t5\t85\t885\t885\t4885\t4885\t170\t171\tXFAAAA\tHRHAAA\tHHHHxx\n6390\t5182\t0\t2\t0\t10\t90\t390\t390\t1390\t6390\t180\t181\tULAAAA\tIRHAAA\tOOOOxx\n1606\t5183\t0\t2\t6\t6\t6\t606\t1606\t1606\t1606\t12\t13\tUJAAAA\tJRHAAA\tVVVVxx\n7850\t5184\t0\t2\t0\t10\t50\t850\t1850\t2850\t7850\t100\t101\tYPAAAA\tKRHAAA\tAAAAxx\n3315\t5185\t1\t3\t5\t15\t15\t315\t1315\t3315\t3315\t30\t31\tNXAAAA\tLRHAAA\tHHHHxx\n8322\t5186\t0\t2\t2\t2\t22\t322\t322\t3322\t8322\t44\t45\tCIAAAA\tMRHAAA\tOOOOxx\n3703\t5187\t1\t3\t3\t3\t3\t703\t1703\t3703\t3703\t6\t7\tLMAAAA\tNRHAAA\tVVVVxx\n9489\t5188\t1\t1\t9\t9\t89\t489\t1489\t4489\t9489\t178\t179\tZAAAAA\tORHAAA\tAAAAxx\n6104\t5189\t0\t0\t4\t4\t4\t104\t104\t1104\t6104\t8\t9\tUAAAAA\tPRHAAA\tHHHHxx\n3067\t5190\t1\t3\t7\t7\t67\t67\t1067\t3067\t3067\t134\t135\tZNAAAA\tQRHAAA\tOOOOxx\n2521\t5191\t1\t1\t1\t1\t21\t521\t521\t2521\t2521\t42\t43\tZSAAAA\tRRHAAA\tVVVVxx\n2581\t5192\t1\t1\t1\t1\t81\t581\t581\t2581\t2581\t162\t163\tHVAAAA\tSRHAAA\tAAAAxx\n595\t5193\t1\t3\t5\t15\t95\t595\t595\t595\t595\t190\t191\tXWAAAA\tTRHAAA\tHHHHxx\n8291\t5194\t1\t3\t1\t11\t91\t291\t291\t3291\t8291\t182\t183\tXGAAAA\tURHAAA\tOOOOxx\n1727\t5195\t1\t3\t7\t7\t27\t727\t1727\t1727\t1727\t54\t55\tLOAAAA\tVRHAAA\tVVVVxx\n6847\t5196\t1\t3\t7\t7\t47\t847\t847\t1847\t6847\t94\t95\tJDAAAA\tWRHAAA\tAAAAxx\n7494\t5197\t0\t2\t4\t14\t94\t494\t1494\t2494\t7494\t188\t189\tGCAAAA\tXRHAAA\tHHHHxx\n7093\t5198\t1\t1\t3\t13\t93\t93\t1093\t2093\t7093\t186\t187\tVMAAAA\tYRHAAA\tOOOOxx\n7357\t5199\t1\t1\t7\t17\t57\t357\t1357\t2357\t7357\t114\t115\tZWAAAA\tZRHAAA\tVVVVxx\n620\t5200\t0\t0\t0\t0\t20\t620\t620\t620\t620\t40\t41\tWXAAAA\tASHAAA\tAAAAxx\n2460\t5201\t0\t0\t0\t0\t60\t460\t460\t2460\t2460\t120\t121\tQQAAAA\tBSHAAA\tHHHHxx\n1598\t5202\t0\t2\t8\t18\t98\t598\t1598\t1598\t1598\t196\t197\tMJAAAA\tCSHAAA\tOOOOxx\n4112\t5203\t0\t0\t2\t12\t12\t112\t112\t4112\t4112\t24\t25\tECAAAA\tDSHAAA\tVVVVxx\n2956\t5204\t0\t0\t6\t16\t56\t956\t956\t2956\t2956\t112\t113\tSJAAAA\tESHAAA\tAAAAxx\n3193\t5205\t1\t1\t3\t13\t93\t193\t1193\t3193\t3193\t186\t187\tVSAAAA\tFSHAAA\tHHHHxx\n6356\t5206\t0\t0\t6\t16\t56\t356\t356\t1356\t6356\t112\t113\tMKAAAA\tGSHAAA\tOOOOxx\n730\t5207\t0\t2\t0\t10\t30\t730\t730\t730\t730\t60\t61\tCCAAAA\tHSHAAA\tVVVVxx\n8826\t5208\t0\t2\t6\t6\t26\t826\t826\t3826\t8826\t52\t53\tMBAAAA\tISHAAA\tAAAAxx\n9036\t5209\t0\t0\t6\t16\t36\t36\t1036\t4036\t9036\t72\t73\tOJAAAA\tJSHAAA\tHHHHxx\n2085\t5210\t1\t1\t5\t5\t85\t85\t85\t2085\t2085\t170\t171\tFCAAAA\tKSHAAA\tOOOOxx\n9007\t5211\t1\t3\t7\t7\t7\t7\t1007\t4007\t9007\t14\t15\tLIAAAA\tLSHAAA\tVVVVxx\n6047\t5212\t1\t3\t7\t7\t47\t47\t47\t1047\t6047\t94\t95\tPYAAAA\tMSHAAA\tAAAAxx\n3953\t5213\t1\t1\t3\t13\t53\t953\t1953\t3953\t3953\t106\t107\tBWAAAA\tNSHAAA\tHHHHxx\n1214\t5214\t0\t2\t4\t14\t14\t214\t1214\t1214\t1214\t28\t29\tSUAAAA\tOSHAAA\tOOOOxx\n4814\t5215\t0\t2\t4\t14\t14\t814\t814\t4814\t4814\t28\t29\tEDAAAA\tPSHAAA\tVVVVxx\n5738\t5216\t0\t2\t8\t18\t38\t738\t1738\t738\t5738\t76\t77\tSMAAAA\tQSHAAA\tAAAAxx\n7176\t5217\t0\t0\t6\t16\t76\t176\t1176\t2176\t7176\t152\t153\tAQAAAA\tRSHAAA\tHHHHxx\n3609\t5218\t1\t1\t9\t9\t9\t609\t1609\t3609\t3609\t18\t19\tVIAAAA\tSSHAAA\tOOOOxx\n592\t5219\t0\t0\t2\t12\t92\t592\t592\t592\t592\t184\t185\tUWAAAA\tTSHAAA\tVVVVxx\n9391\t5220\t1\t3\t1\t11\t91\t391\t1391\t4391\t9391\t182\t183\tFXAAAA\tUSHAAA\tAAAAxx\n5345\t5221\t1\t1\t5\t5\t45\t345\t1345\t345\t5345\t90\t91\tPXAAAA\tVSHAAA\tHHHHxx\n1171\t5222\t1\t3\t1\t11\t71\t171\t1171\t1171\t1171\t142\t143\tBTAAAA\tWSHAAA\tOOOOxx\n7238\t5223\t0\t2\t8\t18\t38\t238\t1238\t2238\t7238\t76\t77\tKSAAAA\tXSHAAA\tVVVVxx\n7561\t5224\t1\t1\t1\t1\t61\t561\t1561\t2561\t7561\t122\t123\tVEAAAA\tYSHAAA\tAAAAxx\n5876\t5225\t0\t0\t6\t16\t76\t876\t1876\t876\t5876\t152\t153\tASAAAA\tZSHAAA\tHHHHxx\n6611\t5226\t1\t3\t1\t11\t11\t611\t611\t1611\t6611\t22\t23\tHUAAAA\tATHAAA\tOOOOxx\n7300\t5227\t0\t0\t0\t0\t0\t300\t1300\t2300\t7300\t0\t1\tUUAAAA\tBTHAAA\tVVVVxx\n1506\t5228\t0\t2\t6\t6\t6\t506\t1506\t1506\t1506\t12\t13\tYFAAAA\tCTHAAA\tAAAAxx\n1153\t5229\t1\t1\t3\t13\t53\t153\t1153\t1153\t1153\t106\t107\tJSAAAA\tDTHAAA\tHHHHxx\n3831\t5230\t1\t3\t1\t11\t31\t831\t1831\t3831\t3831\t62\t63\tJRAAAA\tETHAAA\tOOOOxx\n9255\t5231\t1\t3\t5\t15\t55\t255\t1255\t4255\t9255\t110\t111\tZRAAAA\tFTHAAA\tVVVVxx\n1841\t5232\t1\t1\t1\t1\t41\t841\t1841\t1841\t1841\t82\t83\tVSAAAA\tGTHAAA\tAAAAxx\n5075\t5233\t1\t3\t5\t15\t75\t75\t1075\t75\t5075\t150\t151\tFNAAAA\tHTHAAA\tHHHHxx\n101\t5234\t1\t1\t1\t1\t1\t101\t101\t101\t101\t2\t3\tXDAAAA\tITHAAA\tOOOOxx\n2627\t5235\t1\t3\t7\t7\t27\t627\t627\t2627\t2627\t54\t55\tBXAAAA\tJTHAAA\tVVVVxx\n7078\t5236\t0\t2\t8\t18\t78\t78\t1078\t2078\t7078\t156\t157\tGMAAAA\tKTHAAA\tAAAAxx\n2850\t5237\t0\t2\t0\t10\t50\t850\t850\t2850\t2850\t100\t101\tQFAAAA\tLTHAAA\tHHHHxx\n8703\t5238\t1\t3\t3\t3\t3\t703\t703\t3703\t8703\t6\t7\tTWAAAA\tMTHAAA\tOOOOxx\n4101\t5239\t1\t1\t1\t1\t1\t101\t101\t4101\t4101\t2\t3\tTBAAAA\tNTHAAA\tVVVVxx\n318\t5240\t0\t2\t8\t18\t18\t318\t318\t318\t318\t36\t37\tGMAAAA\tOTHAAA\tAAAAxx\n6452\t5241\t0\t0\t2\t12\t52\t452\t452\t1452\t6452\t104\t105\tEOAAAA\tPTHAAA\tHHHHxx\n5558\t5242\t0\t2\t8\t18\t58\t558\t1558\t558\t5558\t116\t117\tUFAAAA\tQTHAAA\tOOOOxx\n3127\t5243\t1\t3\t7\t7\t27\t127\t1127\t3127\t3127\t54\t55\tHQAAAA\tRTHAAA\tVVVVxx\n535\t5244\t1\t3\t5\t15\t35\t535\t535\t535\t535\t70\t71\tPUAAAA\tSTHAAA\tAAAAxx\n270\t5245\t0\t2\t0\t10\t70\t270\t270\t270\t270\t140\t141\tKKAAAA\tTTHAAA\tHHHHxx\n4038\t5246\t0\t2\t8\t18\t38\t38\t38\t4038\t4038\t76\t77\tIZAAAA\tUTHAAA\tOOOOxx\n3404\t5247\t0\t0\t4\t4\t4\t404\t1404\t3404\t3404\t8\t9\tYAAAAA\tVTHAAA\tVVVVxx\n2374\t5248\t0\t2\t4\t14\t74\t374\t374\t2374\t2374\t148\t149\tINAAAA\tWTHAAA\tAAAAxx\n6446\t5249\t0\t2\t6\t6\t46\t446\t446\t1446\t6446\t92\t93\tYNAAAA\tXTHAAA\tHHHHxx\n7758\t5250\t0\t2\t8\t18\t58\t758\t1758\t2758\t7758\t116\t117\tKMAAAA\tYTHAAA\tOOOOxx\n356\t5251\t0\t0\t6\t16\t56\t356\t356\t356\t356\t112\t113\tSNAAAA\tZTHAAA\tVVVVxx\n9197\t5252\t1\t1\t7\t17\t97\t197\t1197\t4197\t9197\t194\t195\tTPAAAA\tAUHAAA\tAAAAxx\n9765\t5253\t1\t1\t5\t5\t65\t765\t1765\t4765\t9765\t130\t131\tPLAAAA\tBUHAAA\tHHHHxx\n4974\t5254\t0\t2\t4\t14\t74\t974\t974\t4974\t4974\t148\t149\tIJAAAA\tCUHAAA\tOOOOxx\n442\t5255\t0\t2\t2\t2\t42\t442\t442\t442\t442\t84\t85\tARAAAA\tDUHAAA\tVVVVxx\n4349\t5256\t1\t1\t9\t9\t49\t349\t349\t4349\t4349\t98\t99\tHLAAAA\tEUHAAA\tAAAAxx\n6119\t5257\t1\t3\t9\t19\t19\t119\t119\t1119\t6119\t38\t39\tJBAAAA\tFUHAAA\tHHHHxx\n7574\t5258\t0\t2\t4\t14\t74\t574\t1574\t2574\t7574\t148\t149\tIFAAAA\tGUHAAA\tOOOOxx\n4445\t5259\t1\t1\t5\t5\t45\t445\t445\t4445\t4445\t90\t91\tZOAAAA\tHUHAAA\tVVVVxx\n940\t5260\t0\t0\t0\t0\t40\t940\t940\t940\t940\t80\t81\tEKAAAA\tIUHAAA\tAAAAxx\n1875\t5261\t1\t3\t5\t15\t75\t875\t1875\t1875\t1875\t150\t151\tDUAAAA\tJUHAAA\tHHHHxx\n5951\t5262\t1\t3\t1\t11\t51\t951\t1951\t951\t5951\t102\t103\tXUAAAA\tKUHAAA\tOOOOxx\n9132\t5263\t0\t0\t2\t12\t32\t132\t1132\t4132\t9132\t64\t65\tGNAAAA\tLUHAAA\tVVVVxx\n6913\t5264\t1\t1\t3\t13\t13\t913\t913\t1913\t6913\t26\t27\tXFAAAA\tMUHAAA\tAAAAxx\n3308\t5265\t0\t0\t8\t8\t8\t308\t1308\t3308\t3308\t16\t17\tGXAAAA\tNUHAAA\tHHHHxx\n7553\t5266\t1\t1\t3\t13\t53\t553\t1553\t2553\t7553\t106\t107\tNEAAAA\tOUHAAA\tOOOOxx\n2138\t5267\t0\t2\t8\t18\t38\t138\t138\t2138\t2138\t76\t77\tGEAAAA\tPUHAAA\tVVVVxx\n6252\t5268\t0\t0\t2\t12\t52\t252\t252\t1252\t6252\t104\t105\tMGAAAA\tQUHAAA\tAAAAxx\n2171\t5269\t1\t3\t1\t11\t71\t171\t171\t2171\t2171\t142\t143\tNFAAAA\tRUHAAA\tHHHHxx\n4159\t5270\t1\t3\t9\t19\t59\t159\t159\t4159\t4159\t118\t119\tZDAAAA\tSUHAAA\tOOOOxx\n2401\t5271\t1\t1\t1\t1\t1\t401\t401\t2401\t2401\t2\t3\tJOAAAA\tTUHAAA\tVVVVxx\n6553\t5272\t1\t1\t3\t13\t53\t553\t553\t1553\t6553\t106\t107\tBSAAAA\tUUHAAA\tAAAAxx\n5217\t5273\t1\t1\t7\t17\t17\t217\t1217\t217\t5217\t34\t35\tRSAAAA\tVUHAAA\tHHHHxx\n1405\t5274\t1\t1\t5\t5\t5\t405\t1405\t1405\t1405\t10\t11\tBCAAAA\tWUHAAA\tOOOOxx\n1494\t5275\t0\t2\t4\t14\t94\t494\t1494\t1494\t1494\t188\t189\tMFAAAA\tXUHAAA\tVVVVxx\n5553\t5276\t1\t1\t3\t13\t53\t553\t1553\t553\t5553\t106\t107\tPFAAAA\tYUHAAA\tAAAAxx\n8296\t5277\t0\t0\t6\t16\t96\t296\t296\t3296\t8296\t192\t193\tCHAAAA\tZUHAAA\tHHHHxx\n6565\t5278\t1\t1\t5\t5\t65\t565\t565\t1565\t6565\t130\t131\tNSAAAA\tAVHAAA\tOOOOxx\n817\t5279\t1\t1\t7\t17\t17\t817\t817\t817\t817\t34\t35\tLFAAAA\tBVHAAA\tVVVVxx\n6947\t5280\t1\t3\t7\t7\t47\t947\t947\t1947\t6947\t94\t95\tFHAAAA\tCVHAAA\tAAAAxx\n4184\t5281\t0\t0\t4\t4\t84\t184\t184\t4184\t4184\t168\t169\tYEAAAA\tDVHAAA\tHHHHxx\n6577\t5282\t1\t1\t7\t17\t77\t577\t577\t1577\t6577\t154\t155\tZSAAAA\tEVHAAA\tOOOOxx\n6424\t5283\t0\t0\t4\t4\t24\t424\t424\t1424\t6424\t48\t49\tCNAAAA\tFVHAAA\tVVVVxx\n2482\t5284\t0\t2\t2\t2\t82\t482\t482\t2482\t2482\t164\t165\tMRAAAA\tGVHAAA\tAAAAxx\n6874\t5285\t0\t2\t4\t14\t74\t874\t874\t1874\t6874\t148\t149\tKEAAAA\tHVHAAA\tHHHHxx\n7601\t5286\t1\t1\t1\t1\t1\t601\t1601\t2601\t7601\t2\t3\tJGAAAA\tIVHAAA\tOOOOxx\n4552\t5287\t0\t0\t2\t12\t52\t552\t552\t4552\t4552\t104\t105\tCTAAAA\tJVHAAA\tVVVVxx\n8406\t5288\t0\t2\t6\t6\t6\t406\t406\t3406\t8406\t12\t13\tILAAAA\tKVHAAA\tAAAAxx\n2924\t5289\t0\t0\t4\t4\t24\t924\t924\t2924\t2924\t48\t49\tMIAAAA\tLVHAAA\tHHHHxx\n8255\t5290\t1\t3\t5\t15\t55\t255\t255\t3255\t8255\t110\t111\tNFAAAA\tMVHAAA\tOOOOxx\n4920\t5291\t0\t0\t0\t0\t20\t920\t920\t4920\t4920\t40\t41\tGHAAAA\tNVHAAA\tVVVVxx\n228\t5292\t0\t0\t8\t8\t28\t228\t228\t228\t228\t56\t57\tUIAAAA\tOVHAAA\tAAAAxx\n9431\t5293\t1\t3\t1\t11\t31\t431\t1431\t4431\t9431\t62\t63\tTYAAAA\tPVHAAA\tHHHHxx\n4021\t5294\t1\t1\t1\t1\t21\t21\t21\t4021\t4021\t42\t43\tRYAAAA\tQVHAAA\tOOOOxx\n2966\t5295\t0\t2\t6\t6\t66\t966\t966\t2966\t2966\t132\t133\tCKAAAA\tRVHAAA\tVVVVxx\n2862\t5296\t0\t2\t2\t2\t62\t862\t862\t2862\t2862\t124\t125\tCGAAAA\tSVHAAA\tAAAAxx\n4303\t5297\t1\t3\t3\t3\t3\t303\t303\t4303\t4303\t6\t7\tNJAAAA\tTVHAAA\tHHHHxx\n9643\t5298\t1\t3\t3\t3\t43\t643\t1643\t4643\t9643\t86\t87\tXGAAAA\tUVHAAA\tOOOOxx\n3008\t5299\t0\t0\t8\t8\t8\t8\t1008\t3008\t3008\t16\t17\tSLAAAA\tVVHAAA\tVVVVxx\n7476\t5300\t0\t0\t6\t16\t76\t476\t1476\t2476\t7476\t152\t153\tOBAAAA\tWVHAAA\tAAAAxx\n3686\t5301\t0\t2\t6\t6\t86\t686\t1686\t3686\t3686\t172\t173\tULAAAA\tXVHAAA\tHHHHxx\n9051\t5302\t1\t3\t1\t11\t51\t51\t1051\t4051\t9051\t102\t103\tDKAAAA\tYVHAAA\tOOOOxx\n6592\t5303\t0\t0\t2\t12\t92\t592\t592\t1592\t6592\t184\t185\tOTAAAA\tZVHAAA\tVVVVxx\n924\t5304\t0\t0\t4\t4\t24\t924\t924\t924\t924\t48\t49\tOJAAAA\tAWHAAA\tAAAAxx\n4406\t5305\t0\t2\t6\t6\t6\t406\t406\t4406\t4406\t12\t13\tMNAAAA\tBWHAAA\tHHHHxx\n5233\t5306\t1\t1\t3\t13\t33\t233\t1233\t233\t5233\t66\t67\tHTAAAA\tCWHAAA\tOOOOxx\n8881\t5307\t1\t1\t1\t1\t81\t881\t881\t3881\t8881\t162\t163\tPDAAAA\tDWHAAA\tVVVVxx\n2212\t5308\t0\t0\t2\t12\t12\t212\t212\t2212\t2212\t24\t25\tCHAAAA\tEWHAAA\tAAAAxx\n5804\t5309\t0\t0\t4\t4\t4\t804\t1804\t804\t5804\t8\t9\tGPAAAA\tFWHAAA\tHHHHxx\n2990\t5310\t0\t2\t0\t10\t90\t990\t990\t2990\t2990\t180\t181\tALAAAA\tGWHAAA\tOOOOxx\n4069\t5311\t1\t1\t9\t9\t69\t69\t69\t4069\t4069\t138\t139\tNAAAAA\tHWHAAA\tVVVVxx\n5380\t5312\t0\t0\t0\t0\t80\t380\t1380\t380\t5380\t160\t161\tYYAAAA\tIWHAAA\tAAAAxx\n5016\t5313\t0\t0\t6\t16\t16\t16\t1016\t16\t5016\t32\t33\tYKAAAA\tJWHAAA\tHHHHxx\n5056\t5314\t0\t0\t6\t16\t56\t56\t1056\t56\t5056\t112\t113\tMMAAAA\tKWHAAA\tOOOOxx\n3732\t5315\t0\t0\t2\t12\t32\t732\t1732\t3732\t3732\t64\t65\tONAAAA\tLWHAAA\tVVVVxx\n5527\t5316\t1\t3\t7\t7\t27\t527\t1527\t527\t5527\t54\t55\tPEAAAA\tMWHAAA\tAAAAxx\n1151\t5317\t1\t3\t1\t11\t51\t151\t1151\t1151\t1151\t102\t103\tHSAAAA\tNWHAAA\tHHHHxx\n7900\t5318\t0\t0\t0\t0\t0\t900\t1900\t2900\t7900\t0\t1\tWRAAAA\tOWHAAA\tOOOOxx\n1660\t5319\t0\t0\t0\t0\t60\t660\t1660\t1660\t1660\t120\t121\tWLAAAA\tPWHAAA\tVVVVxx\n8064\t5320\t0\t0\t4\t4\t64\t64\t64\t3064\t8064\t128\t129\tEYAAAA\tQWHAAA\tAAAAxx\n8240\t5321\t0\t0\t0\t0\t40\t240\t240\t3240\t8240\t80\t81\tYEAAAA\tRWHAAA\tHHHHxx\n413\t5322\t1\t1\t3\t13\t13\t413\t413\t413\t413\t26\t27\tXPAAAA\tSWHAAA\tOOOOxx\n8311\t5323\t1\t3\t1\t11\t11\t311\t311\t3311\t8311\t22\t23\tRHAAAA\tTWHAAA\tVVVVxx\n1065\t5324\t1\t1\t5\t5\t65\t65\t1065\t1065\t1065\t130\t131\tZOAAAA\tUWHAAA\tAAAAxx\n2741\t5325\t1\t1\t1\t1\t41\t741\t741\t2741\t2741\t82\t83\tLBAAAA\tVWHAAA\tHHHHxx\n5306\t5326\t0\t2\t6\t6\t6\t306\t1306\t306\t5306\t12\t13\tCWAAAA\tWWHAAA\tOOOOxx\n5464\t5327\t0\t0\t4\t4\t64\t464\t1464\t464\t5464\t128\t129\tECAAAA\tXWHAAA\tVVVVxx\n4237\t5328\t1\t1\t7\t17\t37\t237\t237\t4237\t4237\t74\t75\tZGAAAA\tYWHAAA\tAAAAxx\n3822\t5329\t0\t2\t2\t2\t22\t822\t1822\t3822\t3822\t44\t45\tARAAAA\tZWHAAA\tHHHHxx\n2548\t5330\t0\t0\t8\t8\t48\t548\t548\t2548\t2548\t96\t97\tAUAAAA\tAXHAAA\tOOOOxx\n2688\t5331\t0\t0\t8\t8\t88\t688\t688\t2688\t2688\t176\t177\tKZAAAA\tBXHAAA\tVVVVxx\n8061\t5332\t1\t1\t1\t1\t61\t61\t61\t3061\t8061\t122\t123\tBYAAAA\tCXHAAA\tAAAAxx\n9340\t5333\t0\t0\t0\t0\t40\t340\t1340\t4340\t9340\t80\t81\tGVAAAA\tDXHAAA\tHHHHxx\n4031\t5334\t1\t3\t1\t11\t31\t31\t31\t4031\t4031\t62\t63\tBZAAAA\tEXHAAA\tOOOOxx\n2635\t5335\t1\t3\t5\t15\t35\t635\t635\t2635\t2635\t70\t71\tJXAAAA\tFXHAAA\tVVVVxx\n809\t5336\t1\t1\t9\t9\t9\t809\t809\t809\t809\t18\t19\tDFAAAA\tGXHAAA\tAAAAxx\n3209\t5337\t1\t1\t9\t9\t9\t209\t1209\t3209\t3209\t18\t19\tLTAAAA\tHXHAAA\tHHHHxx\n3825\t5338\t1\t1\t5\t5\t25\t825\t1825\t3825\t3825\t50\t51\tDRAAAA\tIXHAAA\tOOOOxx\n1448\t5339\t0\t0\t8\t8\t48\t448\t1448\t1448\t1448\t96\t97\tSDAAAA\tJXHAAA\tVVVVxx\n9077\t5340\t1\t1\t7\t17\t77\t77\t1077\t4077\t9077\t154\t155\tDLAAAA\tKXHAAA\tAAAAxx\n3730\t5341\t0\t2\t0\t10\t30\t730\t1730\t3730\t3730\t60\t61\tMNAAAA\tLXHAAA\tHHHHxx\n9596\t5342\t0\t0\t6\t16\t96\t596\t1596\t4596\t9596\t192\t193\tCFAAAA\tMXHAAA\tOOOOxx\n3563\t5343\t1\t3\t3\t3\t63\t563\t1563\t3563\t3563\t126\t127\tBHAAAA\tNXHAAA\tVVVVxx\n4116\t5344\t0\t0\t6\t16\t16\t116\t116\t4116\t4116\t32\t33\tICAAAA\tOXHAAA\tAAAAxx\n4825\t5345\t1\t1\t5\t5\t25\t825\t825\t4825\t4825\t50\t51\tPDAAAA\tPXHAAA\tHHHHxx\n8376\t5346\t0\t0\t6\t16\t76\t376\t376\t3376\t8376\t152\t153\tEKAAAA\tQXHAAA\tOOOOxx\n3917\t5347\t1\t1\t7\t17\t17\t917\t1917\t3917\t3917\t34\t35\tRUAAAA\tRXHAAA\tVVVVxx\n4407\t5348\t1\t3\t7\t7\t7\t407\t407\t4407\t4407\t14\t15\tNNAAAA\tSXHAAA\tAAAAxx\n8202\t5349\t0\t2\t2\t2\t2\t202\t202\t3202\t8202\t4\t5\tMDAAAA\tTXHAAA\tHHHHxx\n7675\t5350\t1\t3\t5\t15\t75\t675\t1675\t2675\t7675\t150\t151\tFJAAAA\tUXHAAA\tOOOOxx\n4104\t5351\t0\t0\t4\t4\t4\t104\t104\t4104\t4104\t8\t9\tWBAAAA\tVXHAAA\tVVVVxx\n9225\t5352\t1\t1\t5\t5\t25\t225\t1225\t4225\t9225\t50\t51\tVQAAAA\tWXHAAA\tAAAAxx\n2834\t5353\t0\t2\t4\t14\t34\t834\t834\t2834\t2834\t68\t69\tAFAAAA\tXXHAAA\tHHHHxx\n1227\t5354\t1\t3\t7\t7\t27\t227\t1227\t1227\t1227\t54\t55\tFVAAAA\tYXHAAA\tOOOOxx\n3383\t5355\t1\t3\t3\t3\t83\t383\t1383\t3383\t3383\t166\t167\tDAAAAA\tZXHAAA\tVVVVxx\n67\t5356\t1\t3\t7\t7\t67\t67\t67\t67\t67\t134\t135\tPCAAAA\tAYHAAA\tAAAAxx\n1751\t5357\t1\t3\t1\t11\t51\t751\t1751\t1751\t1751\t102\t103\tJPAAAA\tBYHAAA\tHHHHxx\n8054\t5358\t0\t2\t4\t14\t54\t54\t54\t3054\t8054\t108\t109\tUXAAAA\tCYHAAA\tOOOOxx\n8571\t5359\t1\t3\t1\t11\t71\t571\t571\t3571\t8571\t142\t143\tRRAAAA\tDYHAAA\tVVVVxx\n2466\t5360\t0\t2\t6\t6\t66\t466\t466\t2466\t2466\t132\t133\tWQAAAA\tEYHAAA\tAAAAxx\n9405\t5361\t1\t1\t5\t5\t5\t405\t1405\t4405\t9405\t10\t11\tTXAAAA\tFYHAAA\tHHHHxx\n6883\t5362\t1\t3\t3\t3\t83\t883\t883\t1883\t6883\t166\t167\tTEAAAA\tGYHAAA\tOOOOxx\n4301\t5363\t1\t1\t1\t1\t1\t301\t301\t4301\t4301\t2\t3\tLJAAAA\tHYHAAA\tVVVVxx\n3705\t5364\t1\t1\t5\t5\t5\t705\t1705\t3705\t3705\t10\t11\tNMAAAA\tIYHAAA\tAAAAxx\n5420\t5365\t0\t0\t0\t0\t20\t420\t1420\t420\t5420\t40\t41\tMAAAAA\tJYHAAA\tHHHHxx\n3692\t5366\t0\t0\t2\t12\t92\t692\t1692\t3692\t3692\t184\t185\tAMAAAA\tKYHAAA\tOOOOxx\n6851\t5367\t1\t3\t1\t11\t51\t851\t851\t1851\t6851\t102\t103\tNDAAAA\tLYHAAA\tVVVVxx\n9363\t5368\t1\t3\t3\t3\t63\t363\t1363\t4363\t9363\t126\t127\tDWAAAA\tMYHAAA\tAAAAxx\n2269\t5369\t1\t1\t9\t9\t69\t269\t269\t2269\t2269\t138\t139\tHJAAAA\tNYHAAA\tHHHHxx\n4918\t5370\t0\t2\t8\t18\t18\t918\t918\t4918\t4918\t36\t37\tEHAAAA\tOYHAAA\tOOOOxx\n4297\t5371\t1\t1\t7\t17\t97\t297\t297\t4297\t4297\t194\t195\tHJAAAA\tPYHAAA\tVVVVxx\n1836\t5372\t0\t0\t6\t16\t36\t836\t1836\t1836\t1836\t72\t73\tQSAAAA\tQYHAAA\tAAAAxx\n237\t5373\t1\t1\t7\t17\t37\t237\t237\t237\t237\t74\t75\tDJAAAA\tRYHAAA\tHHHHxx\n6131\t5374\t1\t3\t1\t11\t31\t131\t131\t1131\t6131\t62\t63\tVBAAAA\tSYHAAA\tOOOOxx\n3174\t5375\t0\t2\t4\t14\t74\t174\t1174\t3174\t3174\t148\t149\tCSAAAA\tTYHAAA\tVVVVxx\n9987\t5376\t1\t3\t7\t7\t87\t987\t1987\t4987\t9987\t174\t175\tDUAAAA\tUYHAAA\tAAAAxx\n3630\t5377\t0\t2\t0\t10\t30\t630\t1630\t3630\t3630\t60\t61\tQJAAAA\tVYHAAA\tHHHHxx\n2899\t5378\t1\t3\t9\t19\t99\t899\t899\t2899\t2899\t198\t199\tNHAAAA\tWYHAAA\tOOOOxx\n4079\t5379\t1\t3\t9\t19\t79\t79\t79\t4079\t4079\t158\t159\tXAAAAA\tXYHAAA\tVVVVxx\n5049\t5380\t1\t1\t9\t9\t49\t49\t1049\t49\t5049\t98\t99\tFMAAAA\tYYHAAA\tAAAAxx\n2963\t5381\t1\t3\t3\t3\t63\t963\t963\t2963\t2963\t126\t127\tZJAAAA\tZYHAAA\tHHHHxx\n3962\t5382\t0\t2\t2\t2\t62\t962\t1962\t3962\t3962\t124\t125\tKWAAAA\tAZHAAA\tOOOOxx\n7921\t5383\t1\t1\t1\t1\t21\t921\t1921\t2921\t7921\t42\t43\tRSAAAA\tBZHAAA\tVVVVxx\n3967\t5384\t1\t3\t7\t7\t67\t967\t1967\t3967\t3967\t134\t135\tPWAAAA\tCZHAAA\tAAAAxx\n2752\t5385\t0\t0\t2\t12\t52\t752\t752\t2752\t2752\t104\t105\tWBAAAA\tDZHAAA\tHHHHxx\n7944\t5386\t0\t0\t4\t4\t44\t944\t1944\t2944\t7944\t88\t89\tOTAAAA\tEZHAAA\tOOOOxx\n2205\t5387\t1\t1\t5\t5\t5\t205\t205\t2205\t2205\t10\t11\tVGAAAA\tFZHAAA\tVVVVxx\n5035\t5388\t1\t3\t5\t15\t35\t35\t1035\t35\t5035\t70\t71\tRLAAAA\tGZHAAA\tAAAAxx\n1425\t5389\t1\t1\t5\t5\t25\t425\t1425\t1425\t1425\t50\t51\tVCAAAA\tHZHAAA\tHHHHxx\n832\t5390\t0\t0\t2\t12\t32\t832\t832\t832\t832\t64\t65\tAGAAAA\tIZHAAA\tOOOOxx\n1447\t5391\t1\t3\t7\t7\t47\t447\t1447\t1447\t1447\t94\t95\tRDAAAA\tJZHAAA\tVVVVxx\n6108\t5392\t0\t0\t8\t8\t8\t108\t108\t1108\t6108\t16\t17\tYAAAAA\tKZHAAA\tAAAAxx\n4936\t5393\t0\t0\t6\t16\t36\t936\t936\t4936\t4936\t72\t73\tWHAAAA\tLZHAAA\tHHHHxx\n7704\t5394\t0\t0\t4\t4\t4\t704\t1704\t2704\t7704\t8\t9\tIKAAAA\tMZHAAA\tOOOOxx\n142\t5395\t0\t2\t2\t2\t42\t142\t142\t142\t142\t84\t85\tMFAAAA\tNZHAAA\tVVVVxx\n4272\t5396\t0\t0\t2\t12\t72\t272\t272\t4272\t4272\t144\t145\tIIAAAA\tOZHAAA\tAAAAxx\n7667\t5397\t1\t3\t7\t7\t67\t667\t1667\t2667\t7667\t134\t135\tXIAAAA\tPZHAAA\tHHHHxx\n366\t5398\t0\t2\t6\t6\t66\t366\t366\t366\t366\t132\t133\tCOAAAA\tQZHAAA\tOOOOxx\n8866\t5399\t0\t2\t6\t6\t66\t866\t866\t3866\t8866\t132\t133\tADAAAA\tRZHAAA\tVVVVxx\n7712\t5400\t0\t0\t2\t12\t12\t712\t1712\t2712\t7712\t24\t25\tQKAAAA\tSZHAAA\tAAAAxx\n3880\t5401\t0\t0\t0\t0\t80\t880\t1880\t3880\t3880\t160\t161\tGTAAAA\tTZHAAA\tHHHHxx\n4631\t5402\t1\t3\t1\t11\t31\t631\t631\t4631\t4631\t62\t63\tDWAAAA\tUZHAAA\tOOOOxx\n2789\t5403\t1\t1\t9\t9\t89\t789\t789\t2789\t2789\t178\t179\tHDAAAA\tVZHAAA\tVVVVxx\n7720\t5404\t0\t0\t0\t0\t20\t720\t1720\t2720\t7720\t40\t41\tYKAAAA\tWZHAAA\tAAAAxx\n7618\t5405\t0\t2\t8\t18\t18\t618\t1618\t2618\t7618\t36\t37\tAHAAAA\tXZHAAA\tHHHHxx\n4990\t5406\t0\t2\t0\t10\t90\t990\t990\t4990\t4990\t180\t181\tYJAAAA\tYZHAAA\tOOOOxx\n7918\t5407\t0\t2\t8\t18\t18\t918\t1918\t2918\t7918\t36\t37\tOSAAAA\tZZHAAA\tVVVVxx\n5067\t5408\t1\t3\t7\t7\t67\t67\t1067\t67\t5067\t134\t135\tXMAAAA\tAAIAAA\tAAAAxx\n6370\t5409\t0\t2\t0\t10\t70\t370\t370\t1370\t6370\t140\t141\tALAAAA\tBAIAAA\tHHHHxx\n2268\t5410\t0\t0\t8\t8\t68\t268\t268\t2268\t2268\t136\t137\tGJAAAA\tCAIAAA\tOOOOxx\n1949\t5411\t1\t1\t9\t9\t49\t949\t1949\t1949\t1949\t98\t99\tZWAAAA\tDAIAAA\tVVVVxx\n5503\t5412\t1\t3\t3\t3\t3\t503\t1503\t503\t5503\t6\t7\tRDAAAA\tEAIAAA\tAAAAxx\n9951\t5413\t1\t3\t1\t11\t51\t951\t1951\t4951\t9951\t102\t103\tTSAAAA\tFAIAAA\tHHHHxx\n6823\t5414\t1\t3\t3\t3\t23\t823\t823\t1823\t6823\t46\t47\tLCAAAA\tGAIAAA\tOOOOxx\n6287\t5415\t1\t3\t7\t7\t87\t287\t287\t1287\t6287\t174\t175\tVHAAAA\tHAIAAA\tVVVVxx\n6016\t5416\t0\t0\t6\t16\t16\t16\t16\t1016\t6016\t32\t33\tKXAAAA\tIAIAAA\tAAAAxx\n1977\t5417\t1\t1\t7\t17\t77\t977\t1977\t1977\t1977\t154\t155\tBYAAAA\tJAIAAA\tHHHHxx\n8579\t5418\t1\t3\t9\t19\t79\t579\t579\t3579\t8579\t158\t159\tZRAAAA\tKAIAAA\tOOOOxx\n6204\t5419\t0\t0\t4\t4\t4\t204\t204\t1204\t6204\t8\t9\tQEAAAA\tLAIAAA\tVVVVxx\n9764\t5420\t0\t0\t4\t4\t64\t764\t1764\t4764\t9764\t128\t129\tOLAAAA\tMAIAAA\tAAAAxx\n2005\t5421\t1\t1\t5\t5\t5\t5\t5\t2005\t2005\t10\t11\tDZAAAA\tNAIAAA\tHHHHxx\n1648\t5422\t0\t0\t8\t8\t48\t648\t1648\t1648\t1648\t96\t97\tKLAAAA\tOAIAAA\tOOOOxx\n2457\t5423\t1\t1\t7\t17\t57\t457\t457\t2457\t2457\t114\t115\tNQAAAA\tPAIAAA\tVVVVxx\n2698\t5424\t0\t2\t8\t18\t98\t698\t698\t2698\t2698\t196\t197\tUZAAAA\tQAIAAA\tAAAAxx\n7730\t5425\t0\t2\t0\t10\t30\t730\t1730\t2730\t7730\t60\t61\tILAAAA\tRAIAAA\tHHHHxx\n7287\t5426\t1\t3\t7\t7\t87\t287\t1287\t2287\t7287\t174\t175\tHUAAAA\tSAIAAA\tOOOOxx\n2937\t5427\t1\t1\t7\t17\t37\t937\t937\t2937\t2937\t74\t75\tZIAAAA\tTAIAAA\tVVVVxx\n6824\t5428\t0\t0\t4\t4\t24\t824\t824\t1824\t6824\t48\t49\tMCAAAA\tUAIAAA\tAAAAxx\n9256\t5429\t0\t0\t6\t16\t56\t256\t1256\t4256\t9256\t112\t113\tASAAAA\tVAIAAA\tHHHHxx\n4810\t5430\t0\t2\t0\t10\t10\t810\t810\t4810\t4810\t20\t21\tADAAAA\tWAIAAA\tOOOOxx\n3869\t5431\t1\t1\t9\t9\t69\t869\t1869\t3869\t3869\t138\t139\tVSAAAA\tXAIAAA\tVVVVxx\n1993\t5432\t1\t1\t3\t13\t93\t993\t1993\t1993\t1993\t186\t187\tRYAAAA\tYAIAAA\tAAAAxx\n6048\t5433\t0\t0\t8\t8\t48\t48\t48\t1048\t6048\t96\t97\tQYAAAA\tZAIAAA\tHHHHxx\n6922\t5434\t0\t2\t2\t2\t22\t922\t922\t1922\t6922\t44\t45\tGGAAAA\tABIAAA\tOOOOxx\n8\t5435\t0\t0\t8\t8\t8\t8\t8\t8\t8\t16\t17\tIAAAAA\tBBIAAA\tVVVVxx\n6706\t5436\t0\t2\t6\t6\t6\t706\t706\t1706\t6706\t12\t13\tYXAAAA\tCBIAAA\tAAAAxx\n9159\t5437\t1\t3\t9\t19\t59\t159\t1159\t4159\t9159\t118\t119\tHOAAAA\tDBIAAA\tHHHHxx\n7020\t5438\t0\t0\t0\t0\t20\t20\t1020\t2020\t7020\t40\t41\tAKAAAA\tEBIAAA\tOOOOxx\n767\t5439\t1\t3\t7\t7\t67\t767\t767\t767\t767\t134\t135\tNDAAAA\tFBIAAA\tVVVVxx\n8602\t5440\t0\t2\t2\t2\t2\t602\t602\t3602\t8602\t4\t5\tWSAAAA\tGBIAAA\tAAAAxx\n4442\t5441\t0\t2\t2\t2\t42\t442\t442\t4442\t4442\t84\t85\tWOAAAA\tHBIAAA\tHHHHxx\n2040\t5442\t0\t0\t0\t0\t40\t40\t40\t2040\t2040\t80\t81\tMAAAAA\tIBIAAA\tOOOOxx\n5493\t5443\t1\t1\t3\t13\t93\t493\t1493\t493\t5493\t186\t187\tHDAAAA\tJBIAAA\tVVVVxx\n275\t5444\t1\t3\t5\t15\t75\t275\t275\t275\t275\t150\t151\tPKAAAA\tKBIAAA\tAAAAxx\n8876\t5445\t0\t0\t6\t16\t76\t876\t876\t3876\t8876\t152\t153\tKDAAAA\tLBIAAA\tHHHHxx\n7381\t5446\t1\t1\t1\t1\t81\t381\t1381\t2381\t7381\t162\t163\tXXAAAA\tMBIAAA\tOOOOxx\n1827\t5447\t1\t3\t7\t7\t27\t827\t1827\t1827\t1827\t54\t55\tHSAAAA\tNBIAAA\tVVVVxx\n3537\t5448\t1\t1\t7\t17\t37\t537\t1537\t3537\t3537\t74\t75\tBGAAAA\tOBIAAA\tAAAAxx\n6978\t5449\t0\t2\t8\t18\t78\t978\t978\t1978\t6978\t156\t157\tKIAAAA\tPBIAAA\tHHHHxx\n6160\t5450\t0\t0\t0\t0\t60\t160\t160\t1160\t6160\t120\t121\tYCAAAA\tQBIAAA\tOOOOxx\n9219\t5451\t1\t3\t9\t19\t19\t219\t1219\t4219\t9219\t38\t39\tPQAAAA\tRBIAAA\tVVVVxx\n5034\t5452\t0\t2\t4\t14\t34\t34\t1034\t34\t5034\t68\t69\tQLAAAA\tSBIAAA\tAAAAxx\n8463\t5453\t1\t3\t3\t3\t63\t463\t463\t3463\t8463\t126\t127\tNNAAAA\tTBIAAA\tHHHHxx\n2038\t5454\t0\t2\t8\t18\t38\t38\t38\t2038\t2038\t76\t77\tKAAAAA\tUBIAAA\tOOOOxx\n9562\t5455\t0\t2\t2\t2\t62\t562\t1562\t4562\t9562\t124\t125\tUDAAAA\tVBIAAA\tVVVVxx\n2687\t5456\t1\t3\t7\t7\t87\t687\t687\t2687\t2687\t174\t175\tJZAAAA\tWBIAAA\tAAAAxx\n5092\t5457\t0\t0\t2\t12\t92\t92\t1092\t92\t5092\t184\t185\tWNAAAA\tXBIAAA\tHHHHxx\n539\t5458\t1\t3\t9\t19\t39\t539\t539\t539\t539\t78\t79\tTUAAAA\tYBIAAA\tOOOOxx\n2139\t5459\t1\t3\t9\t19\t39\t139\t139\t2139\t2139\t78\t79\tHEAAAA\tZBIAAA\tVVVVxx\n9221\t5460\t1\t1\t1\t1\t21\t221\t1221\t4221\t9221\t42\t43\tRQAAAA\tACIAAA\tAAAAxx\n965\t5461\t1\t1\t5\t5\t65\t965\t965\t965\t965\t130\t131\tDLAAAA\tBCIAAA\tHHHHxx\n6051\t5462\t1\t3\t1\t11\t51\t51\t51\t1051\t6051\t102\t103\tTYAAAA\tCCIAAA\tOOOOxx\n5822\t5463\t0\t2\t2\t2\t22\t822\t1822\t822\t5822\t44\t45\tYPAAAA\tDCIAAA\tVVVVxx\n6397\t5464\t1\t1\t7\t17\t97\t397\t397\t1397\t6397\t194\t195\tBMAAAA\tECIAAA\tAAAAxx\n2375\t5465\t1\t3\t5\t15\t75\t375\t375\t2375\t2375\t150\t151\tJNAAAA\tFCIAAA\tHHHHxx\n9415\t5466\t1\t3\t5\t15\t15\t415\t1415\t4415\t9415\t30\t31\tDYAAAA\tGCIAAA\tOOOOxx\n6552\t5467\t0\t0\t2\t12\t52\t552\t552\t1552\t6552\t104\t105\tASAAAA\tHCIAAA\tVVVVxx\n2248\t5468\t0\t0\t8\t8\t48\t248\t248\t2248\t2248\t96\t97\tMIAAAA\tICIAAA\tAAAAxx\n2611\t5469\t1\t3\t1\t11\t11\t611\t611\t2611\t2611\t22\t23\tLWAAAA\tJCIAAA\tHHHHxx\n9609\t5470\t1\t1\t9\t9\t9\t609\t1609\t4609\t9609\t18\t19\tPFAAAA\tKCIAAA\tOOOOxx\n2132\t5471\t0\t0\t2\t12\t32\t132\t132\t2132\t2132\t64\t65\tAEAAAA\tLCIAAA\tVVVVxx\n8452\t5472\t0\t0\t2\t12\t52\t452\t452\t3452\t8452\t104\t105\tCNAAAA\tMCIAAA\tAAAAxx\n9407\t5473\t1\t3\t7\t7\t7\t407\t1407\t4407\t9407\t14\t15\tVXAAAA\tNCIAAA\tHHHHxx\n2814\t5474\t0\t2\t4\t14\t14\t814\t814\t2814\t2814\t28\t29\tGEAAAA\tOCIAAA\tOOOOxx\n1889\t5475\t1\t1\t9\t9\t89\t889\t1889\t1889\t1889\t178\t179\tRUAAAA\tPCIAAA\tVVVVxx\n7489\t5476\t1\t1\t9\t9\t89\t489\t1489\t2489\t7489\t178\t179\tBCAAAA\tQCIAAA\tAAAAxx\n2255\t5477\t1\t3\t5\t15\t55\t255\t255\t2255\t2255\t110\t111\tTIAAAA\tRCIAAA\tHHHHxx\n3380\t5478\t0\t0\t0\t0\t80\t380\t1380\t3380\t3380\t160\t161\tAAAAAA\tSCIAAA\tOOOOxx\n1167\t5479\t1\t3\t7\t7\t67\t167\t1167\t1167\t1167\t134\t135\tXSAAAA\tTCIAAA\tVVVVxx\n5369\t5480\t1\t1\t9\t9\t69\t369\t1369\t369\t5369\t138\t139\tNYAAAA\tUCIAAA\tAAAAxx\n2378\t5481\t0\t2\t8\t18\t78\t378\t378\t2378\t2378\t156\t157\tMNAAAA\tVCIAAA\tHHHHxx\n8315\t5482\t1\t3\t5\t15\t15\t315\t315\t3315\t8315\t30\t31\tVHAAAA\tWCIAAA\tOOOOxx\n2934\t5483\t0\t2\t4\t14\t34\t934\t934\t2934\t2934\t68\t69\tWIAAAA\tXCIAAA\tVVVVxx\n7924\t5484\t0\t0\t4\t4\t24\t924\t1924\t2924\t7924\t48\t49\tUSAAAA\tYCIAAA\tAAAAxx\n2867\t5485\t1\t3\t7\t7\t67\t867\t867\t2867\t2867\t134\t135\tHGAAAA\tZCIAAA\tHHHHxx\n9141\t5486\t1\t1\t1\t1\t41\t141\t1141\t4141\t9141\t82\t83\tPNAAAA\tADIAAA\tOOOOxx\n3613\t5487\t1\t1\t3\t13\t13\t613\t1613\t3613\t3613\t26\t27\tZIAAAA\tBDIAAA\tVVVVxx\n2461\t5488\t1\t1\t1\t1\t61\t461\t461\t2461\t2461\t122\t123\tRQAAAA\tCDIAAA\tAAAAxx\n4567\t5489\t1\t3\t7\t7\t67\t567\t567\t4567\t4567\t134\t135\tRTAAAA\tDDIAAA\tHHHHxx\n2906\t5490\t0\t2\t6\t6\t6\t906\t906\t2906\t2906\t12\t13\tUHAAAA\tEDIAAA\tOOOOxx\n4848\t5491\t0\t0\t8\t8\t48\t848\t848\t4848\t4848\t96\t97\tMEAAAA\tFDIAAA\tVVVVxx\n6614\t5492\t0\t2\t4\t14\t14\t614\t614\t1614\t6614\t28\t29\tKUAAAA\tGDIAAA\tAAAAxx\n6200\t5493\t0\t0\t0\t0\t0\t200\t200\t1200\t6200\t0\t1\tMEAAAA\tHDIAAA\tHHHHxx\n7895\t5494\t1\t3\t5\t15\t95\t895\t1895\t2895\t7895\t190\t191\tRRAAAA\tIDIAAA\tOOOOxx\n6829\t5495\t1\t1\t9\t9\t29\t829\t829\t1829\t6829\t58\t59\tRCAAAA\tJDIAAA\tVVVVxx\n4087\t5496\t1\t3\t7\t7\t87\t87\t87\t4087\t4087\t174\t175\tFBAAAA\tKDIAAA\tAAAAxx\n8787\t5497\t1\t3\t7\t7\t87\t787\t787\t3787\t8787\t174\t175\tZZAAAA\tLDIAAA\tHHHHxx\n3322\t5498\t0\t2\t2\t2\t22\t322\t1322\t3322\t3322\t44\t45\tUXAAAA\tMDIAAA\tOOOOxx\n9091\t5499\t1\t3\t1\t11\t91\t91\t1091\t4091\t9091\t182\t183\tRLAAAA\tNDIAAA\tVVVVxx\n5268\t5500\t0\t0\t8\t8\t68\t268\t1268\t268\t5268\t136\t137\tQUAAAA\tODIAAA\tAAAAxx\n2719\t5501\t1\t3\t9\t19\t19\t719\t719\t2719\t2719\t38\t39\tPAAAAA\tPDIAAA\tHHHHxx\n30\t5502\t0\t2\t0\t10\t30\t30\t30\t30\t30\t60\t61\tEBAAAA\tQDIAAA\tOOOOxx\n1975\t5503\t1\t3\t5\t15\t75\t975\t1975\t1975\t1975\t150\t151\tZXAAAA\tRDIAAA\tVVVVxx\n2641\t5504\t1\t1\t1\t1\t41\t641\t641\t2641\t2641\t82\t83\tPXAAAA\tSDIAAA\tAAAAxx\n8616\t5505\t0\t0\t6\t16\t16\t616\t616\t3616\t8616\t32\t33\tKTAAAA\tTDIAAA\tHHHHxx\n5980\t5506\t0\t0\t0\t0\t80\t980\t1980\t980\t5980\t160\t161\tAWAAAA\tUDIAAA\tOOOOxx\n5170\t5507\t0\t2\t0\t10\t70\t170\t1170\t170\t5170\t140\t141\tWQAAAA\tVDIAAA\tVVVVxx\n1960\t5508\t0\t0\t0\t0\t60\t960\t1960\t1960\t1960\t120\t121\tKXAAAA\tWDIAAA\tAAAAxx\n8141\t5509\t1\t1\t1\t1\t41\t141\t141\t3141\t8141\t82\t83\tDBAAAA\tXDIAAA\tHHHHxx\n6692\t5510\t0\t0\t2\t12\t92\t692\t692\t1692\t6692\t184\t185\tKXAAAA\tYDIAAA\tOOOOxx\n7621\t5511\t1\t1\t1\t1\t21\t621\t1621\t2621\t7621\t42\t43\tDHAAAA\tZDIAAA\tVVVVxx\n3890\t5512\t0\t2\t0\t10\t90\t890\t1890\t3890\t3890\t180\t181\tQTAAAA\tAEIAAA\tAAAAxx\n4300\t5513\t0\t0\t0\t0\t0\t300\t300\t4300\t4300\t0\t1\tKJAAAA\tBEIAAA\tHHHHxx\n736\t5514\t0\t0\t6\t16\t36\t736\t736\t736\t736\t72\t73\tICAAAA\tCEIAAA\tOOOOxx\n6626\t5515\t0\t2\t6\t6\t26\t626\t626\t1626\t6626\t52\t53\tWUAAAA\tDEIAAA\tVVVVxx\n1800\t5516\t0\t0\t0\t0\t0\t800\t1800\t1800\t1800\t0\t1\tGRAAAA\tEEIAAA\tAAAAxx\n3430\t5517\t0\t2\t0\t10\t30\t430\t1430\t3430\t3430\t60\t61\tYBAAAA\tFEIAAA\tHHHHxx\n9519\t5518\t1\t3\t9\t19\t19\t519\t1519\t4519\t9519\t38\t39\tDCAAAA\tGEIAAA\tOOOOxx\n5111\t5519\t1\t3\t1\t11\t11\t111\t1111\t111\t5111\t22\t23\tPOAAAA\tHEIAAA\tVVVVxx\n6915\t5520\t1\t3\t5\t15\t15\t915\t915\t1915\t6915\t30\t31\tZFAAAA\tIEIAAA\tAAAAxx\n9246\t5521\t0\t2\t6\t6\t46\t246\t1246\t4246\t9246\t92\t93\tQRAAAA\tJEIAAA\tHHHHxx\n5141\t5522\t1\t1\t1\t1\t41\t141\t1141\t141\t5141\t82\t83\tTPAAAA\tKEIAAA\tOOOOxx\n5922\t5523\t0\t2\t2\t2\t22\t922\t1922\t922\t5922\t44\t45\tUTAAAA\tLEIAAA\tVVVVxx\n3087\t5524\t1\t3\t7\t7\t87\t87\t1087\t3087\t3087\t174\t175\tTOAAAA\tMEIAAA\tAAAAxx\n1859\t5525\t1\t3\t9\t19\t59\t859\t1859\t1859\t1859\t118\t119\tNTAAAA\tNEIAAA\tHHHHxx\n8482\t5526\t0\t2\t2\t2\t82\t482\t482\t3482\t8482\t164\t165\tGOAAAA\tOEIAAA\tOOOOxx\n8414\t5527\t0\t2\t4\t14\t14\t414\t414\t3414\t8414\t28\t29\tQLAAAA\tPEIAAA\tVVVVxx\n6662\t5528\t0\t2\t2\t2\t62\t662\t662\t1662\t6662\t124\t125\tGWAAAA\tQEIAAA\tAAAAxx\n8614\t5529\t0\t2\t4\t14\t14\t614\t614\t3614\t8614\t28\t29\tITAAAA\tREIAAA\tHHHHxx\n42\t5530\t0\t2\t2\t2\t42\t42\t42\t42\t42\t84\t85\tQBAAAA\tSEIAAA\tOOOOxx\n7582\t5531\t0\t2\t2\t2\t82\t582\t1582\t2582\t7582\t164\t165\tQFAAAA\tTEIAAA\tVVVVxx\n8183\t5532\t1\t3\t3\t3\t83\t183\t183\t3183\t8183\t166\t167\tTCAAAA\tUEIAAA\tAAAAxx\n1299\t5533\t1\t3\t9\t19\t99\t299\t1299\t1299\t1299\t198\t199\tZXAAAA\tVEIAAA\tHHHHxx\n7004\t5534\t0\t0\t4\t4\t4\t4\t1004\t2004\t7004\t8\t9\tKJAAAA\tWEIAAA\tOOOOxx\n3298\t5535\t0\t2\t8\t18\t98\t298\t1298\t3298\t3298\t196\t197\tWWAAAA\tXEIAAA\tVVVVxx\n7884\t5536\t0\t0\t4\t4\t84\t884\t1884\t2884\t7884\t168\t169\tGRAAAA\tYEIAAA\tAAAAxx\n4191\t5537\t1\t3\t1\t11\t91\t191\t191\t4191\t4191\t182\t183\tFFAAAA\tZEIAAA\tHHHHxx\n7346\t5538\t0\t2\t6\t6\t46\t346\t1346\t2346\t7346\t92\t93\tOWAAAA\tAFIAAA\tOOOOxx\n7989\t5539\t1\t1\t9\t9\t89\t989\t1989\t2989\t7989\t178\t179\tHVAAAA\tBFIAAA\tVVVVxx\n5719\t5540\t1\t3\t9\t19\t19\t719\t1719\t719\t5719\t38\t39\tZLAAAA\tCFIAAA\tAAAAxx\n800\t5541\t0\t0\t0\t0\t0\t800\t800\t800\t800\t0\t1\tUEAAAA\tDFIAAA\tHHHHxx\n6509\t5542\t1\t1\t9\t9\t9\t509\t509\t1509\t6509\t18\t19\tJQAAAA\tEFIAAA\tOOOOxx\n4672\t5543\t0\t0\t2\t12\t72\t672\t672\t4672\t4672\t144\t145\tSXAAAA\tFFIAAA\tVVVVxx\n4434\t5544\t0\t2\t4\t14\t34\t434\t434\t4434\t4434\t68\t69\tOOAAAA\tGFIAAA\tAAAAxx\n8309\t5545\t1\t1\t9\t9\t9\t309\t309\t3309\t8309\t18\t19\tPHAAAA\tHFIAAA\tHHHHxx\n5134\t5546\t0\t2\t4\t14\t34\t134\t1134\t134\t5134\t68\t69\tMPAAAA\tIFIAAA\tOOOOxx\n5153\t5547\t1\t1\t3\t13\t53\t153\t1153\t153\t5153\t106\t107\tFQAAAA\tJFIAAA\tVVVVxx\n1522\t5548\t0\t2\t2\t2\t22\t522\t1522\t1522\t1522\t44\t45\tOGAAAA\tKFIAAA\tAAAAxx\n8629\t5549\t1\t1\t9\t9\t29\t629\t629\t3629\t8629\t58\t59\tXTAAAA\tLFIAAA\tHHHHxx\n4549\t5550\t1\t1\t9\t9\t49\t549\t549\t4549\t4549\t98\t99\tZSAAAA\tMFIAAA\tOOOOxx\n9506\t5551\t0\t2\t6\t6\t6\t506\t1506\t4506\t9506\t12\t13\tQBAAAA\tNFIAAA\tVVVVxx\n6542\t5552\t0\t2\t2\t2\t42\t542\t542\t1542\t6542\t84\t85\tQRAAAA\tOFIAAA\tAAAAxx\n2579\t5553\t1\t3\t9\t19\t79\t579\t579\t2579\t2579\t158\t159\tFVAAAA\tPFIAAA\tHHHHxx\n4664\t5554\t0\t0\t4\t4\t64\t664\t664\t4664\t4664\t128\t129\tKXAAAA\tQFIAAA\tOOOOxx\n696\t5555\t0\t0\t6\t16\t96\t696\t696\t696\t696\t192\t193\tUAAAAA\tRFIAAA\tVVVVxx\n7950\t5556\t0\t2\t0\t10\t50\t950\t1950\t2950\t7950\t100\t101\tUTAAAA\tSFIAAA\tAAAAxx\n5\t5557\t1\t1\t5\t5\t5\t5\t5\t5\t5\t10\t11\tFAAAAA\tTFIAAA\tHHHHxx\n7806\t5558\t0\t2\t6\t6\t6\t806\t1806\t2806\t7806\t12\t13\tGOAAAA\tUFIAAA\tOOOOxx\n2770\t5559\t0\t2\t0\t10\t70\t770\t770\t2770\t2770\t140\t141\tOCAAAA\tVFIAAA\tVVVVxx\n1344\t5560\t0\t0\t4\t4\t44\t344\t1344\t1344\t1344\t88\t89\tSZAAAA\tWFIAAA\tAAAAxx\n511\t5561\t1\t3\t1\t11\t11\t511\t511\t511\t511\t22\t23\tRTAAAA\tXFIAAA\tHHHHxx\n9070\t5562\t0\t2\t0\t10\t70\t70\t1070\t4070\t9070\t140\t141\tWKAAAA\tYFIAAA\tOOOOxx\n2961\t5563\t1\t1\t1\t1\t61\t961\t961\t2961\t2961\t122\t123\tXJAAAA\tZFIAAA\tVVVVxx\n8031\t5564\t1\t3\t1\t11\t31\t31\t31\t3031\t8031\t62\t63\tXWAAAA\tAGIAAA\tAAAAxx\n326\t5565\t0\t2\t6\t6\t26\t326\t326\t326\t326\t52\t53\tOMAAAA\tBGIAAA\tHHHHxx\n183\t5566\t1\t3\t3\t3\t83\t183\t183\t183\t183\t166\t167\tBHAAAA\tCGIAAA\tOOOOxx\n5917\t5567\t1\t1\t7\t17\t17\t917\t1917\t917\t5917\t34\t35\tPTAAAA\tDGIAAA\tVVVVxx\n8256\t5568\t0\t0\t6\t16\t56\t256\t256\t3256\t8256\t112\t113\tOFAAAA\tEGIAAA\tAAAAxx\n7889\t5569\t1\t1\t9\t9\t89\t889\t1889\t2889\t7889\t178\t179\tLRAAAA\tFGIAAA\tHHHHxx\n9029\t5570\t1\t1\t9\t9\t29\t29\t1029\t4029\t9029\t58\t59\tHJAAAA\tGGIAAA\tOOOOxx\n1316\t5571\t0\t0\t6\t16\t16\t316\t1316\t1316\t1316\t32\t33\tQYAAAA\tHGIAAA\tVVVVxx\n7442\t5572\t0\t2\t2\t2\t42\t442\t1442\t2442\t7442\t84\t85\tGAAAAA\tIGIAAA\tAAAAxx\n2810\t5573\t0\t2\t0\t10\t10\t810\t810\t2810\t2810\t20\t21\tCEAAAA\tJGIAAA\tHHHHxx\n20\t5574\t0\t0\t0\t0\t20\t20\t20\t20\t20\t40\t41\tUAAAAA\tKGIAAA\tOOOOxx\n2306\t5575\t0\t2\t6\t6\t6\t306\t306\t2306\t2306\t12\t13\tSKAAAA\tLGIAAA\tVVVVxx\n4694\t5576\t0\t2\t4\t14\t94\t694\t694\t4694\t4694\t188\t189\tOYAAAA\tMGIAAA\tAAAAxx\n9710\t5577\t0\t2\t0\t10\t10\t710\t1710\t4710\t9710\t20\t21\tMJAAAA\tNGIAAA\tHHHHxx\n1791\t5578\t1\t3\t1\t11\t91\t791\t1791\t1791\t1791\t182\t183\tXQAAAA\tOGIAAA\tOOOOxx\n6730\t5579\t0\t2\t0\t10\t30\t730\t730\t1730\t6730\t60\t61\tWYAAAA\tPGIAAA\tVVVVxx\n359\t5580\t1\t3\t9\t19\t59\t359\t359\t359\t359\t118\t119\tVNAAAA\tQGIAAA\tAAAAxx\n8097\t5581\t1\t1\t7\t17\t97\t97\t97\t3097\t8097\t194\t195\tLZAAAA\tRGIAAA\tHHHHxx\n6147\t5582\t1\t3\t7\t7\t47\t147\t147\t1147\t6147\t94\t95\tLCAAAA\tSGIAAA\tOOOOxx\n643\t5583\t1\t3\t3\t3\t43\t643\t643\t643\t643\t86\t87\tTYAAAA\tTGIAAA\tVVVVxx\n698\t5584\t0\t2\t8\t18\t98\t698\t698\t698\t698\t196\t197\tWAAAAA\tUGIAAA\tAAAAxx\n3881\t5585\t1\t1\t1\t1\t81\t881\t1881\t3881\t3881\t162\t163\tHTAAAA\tVGIAAA\tHHHHxx\n7600\t5586\t0\t0\t0\t0\t0\t600\t1600\t2600\t7600\t0\t1\tIGAAAA\tWGIAAA\tOOOOxx\n1583\t5587\t1\t3\t3\t3\t83\t583\t1583\t1583\t1583\t166\t167\tXIAAAA\tXGIAAA\tVVVVxx\n9612\t5588\t0\t0\t2\t12\t12\t612\t1612\t4612\t9612\t24\t25\tSFAAAA\tYGIAAA\tAAAAxx\n1032\t5589\t0\t0\t2\t12\t32\t32\t1032\t1032\t1032\t64\t65\tSNAAAA\tZGIAAA\tHHHHxx\n4834\t5590\t0\t2\t4\t14\t34\t834\t834\t4834\t4834\t68\t69\tYDAAAA\tAHIAAA\tOOOOxx\n5076\t5591\t0\t0\t6\t16\t76\t76\t1076\t76\t5076\t152\t153\tGNAAAA\tBHIAAA\tVVVVxx\n3070\t5592\t0\t2\t0\t10\t70\t70\t1070\t3070\t3070\t140\t141\tCOAAAA\tCHIAAA\tAAAAxx\n1421\t5593\t1\t1\t1\t1\t21\t421\t1421\t1421\t1421\t42\t43\tRCAAAA\tDHIAAA\tHHHHxx\n8970\t5594\t0\t2\t0\t10\t70\t970\t970\t3970\t8970\t140\t141\tAHAAAA\tEHIAAA\tOOOOxx\n6271\t5595\t1\t3\t1\t11\t71\t271\t271\t1271\t6271\t142\t143\tFHAAAA\tFHIAAA\tVVVVxx\n8547\t5596\t1\t3\t7\t7\t47\t547\t547\t3547\t8547\t94\t95\tTQAAAA\tGHIAAA\tAAAAxx\n1259\t5597\t1\t3\t9\t19\t59\t259\t1259\t1259\t1259\t118\t119\tLWAAAA\tHHIAAA\tHHHHxx\n8328\t5598\t0\t0\t8\t8\t28\t328\t328\t3328\t8328\t56\t57\tIIAAAA\tIHIAAA\tOOOOxx\n1503\t5599\t1\t3\t3\t3\t3\t503\t1503\t1503\t1503\t6\t7\tVFAAAA\tJHIAAA\tVVVVxx\n2253\t5600\t1\t1\t3\t13\t53\t253\t253\t2253\t2253\t106\t107\tRIAAAA\tKHIAAA\tAAAAxx\n7449\t5601\t1\t1\t9\t9\t49\t449\t1449\t2449\t7449\t98\t99\tNAAAAA\tLHIAAA\tHHHHxx\n3579\t5602\t1\t3\t9\t19\t79\t579\t1579\t3579\t3579\t158\t159\tRHAAAA\tMHIAAA\tOOOOxx\n1585\t5603\t1\t1\t5\t5\t85\t585\t1585\t1585\t1585\t170\t171\tZIAAAA\tNHIAAA\tVVVVxx\n5543\t5604\t1\t3\t3\t3\t43\t543\t1543\t543\t5543\t86\t87\tFFAAAA\tOHIAAA\tAAAAxx\n8627\t5605\t1\t3\t7\t7\t27\t627\t627\t3627\t8627\t54\t55\tVTAAAA\tPHIAAA\tHHHHxx\n8618\t5606\t0\t2\t8\t18\t18\t618\t618\t3618\t8618\t36\t37\tMTAAAA\tQHIAAA\tOOOOxx\n1911\t5607\t1\t3\t1\t11\t11\t911\t1911\t1911\t1911\t22\t23\tNVAAAA\tRHIAAA\tVVVVxx\n2758\t5608\t0\t2\t8\t18\t58\t758\t758\t2758\t2758\t116\t117\tCCAAAA\tSHIAAA\tAAAAxx\n5744\t5609\t0\t0\t4\t4\t44\t744\t1744\t744\t5744\t88\t89\tYMAAAA\tTHIAAA\tHHHHxx\n4976\t5610\t0\t0\t6\t16\t76\t976\t976\t4976\t4976\t152\t153\tKJAAAA\tUHIAAA\tOOOOxx\n6380\t5611\t0\t0\t0\t0\t80\t380\t380\t1380\t6380\t160\t161\tKLAAAA\tVHIAAA\tVVVVxx\n1937\t5612\t1\t1\t7\t17\t37\t937\t1937\t1937\t1937\t74\t75\tNWAAAA\tWHIAAA\tAAAAxx\n9903\t5613\t1\t3\t3\t3\t3\t903\t1903\t4903\t9903\t6\t7\tXQAAAA\tXHIAAA\tHHHHxx\n4409\t5614\t1\t1\t9\t9\t9\t409\t409\t4409\t4409\t18\t19\tPNAAAA\tYHIAAA\tOOOOxx\n4133\t5615\t1\t1\t3\t13\t33\t133\t133\t4133\t4133\t66\t67\tZCAAAA\tZHIAAA\tVVVVxx\n5263\t5616\t1\t3\t3\t3\t63\t263\t1263\t263\t5263\t126\t127\tLUAAAA\tAIIAAA\tAAAAxx\n7888\t5617\t0\t0\t8\t8\t88\t888\t1888\t2888\t7888\t176\t177\tKRAAAA\tBIIAAA\tHHHHxx\n6060\t5618\t0\t0\t0\t0\t60\t60\t60\t1060\t6060\t120\t121\tCZAAAA\tCIIAAA\tOOOOxx\n2522\t5619\t0\t2\t2\t2\t22\t522\t522\t2522\t2522\t44\t45\tATAAAA\tDIIAAA\tVVVVxx\n5550\t5620\t0\t2\t0\t10\t50\t550\t1550\t550\t5550\t100\t101\tMFAAAA\tEIIAAA\tAAAAxx\n9396\t5621\t0\t0\t6\t16\t96\t396\t1396\t4396\t9396\t192\t193\tKXAAAA\tFIIAAA\tHHHHxx\n176\t5622\t0\t0\t6\t16\t76\t176\t176\t176\t176\t152\t153\tUGAAAA\tGIIAAA\tOOOOxx\n5148\t5623\t0\t0\t8\t8\t48\t148\t1148\t148\t5148\t96\t97\tAQAAAA\tHIIAAA\tVVVVxx\n6691\t5624\t1\t3\t1\t11\t91\t691\t691\t1691\t6691\t182\t183\tJXAAAA\tIIIAAA\tAAAAxx\n4652\t5625\t0\t0\t2\t12\t52\t652\t652\t4652\t4652\t104\t105\tYWAAAA\tJIIAAA\tHHHHxx\n5096\t5626\t0\t0\t6\t16\t96\t96\t1096\t96\t5096\t192\t193\tAOAAAA\tKIIAAA\tOOOOxx\n2408\t5627\t0\t0\t8\t8\t8\t408\t408\t2408\t2408\t16\t17\tQOAAAA\tLIIAAA\tVVVVxx\n7322\t5628\t0\t2\t2\t2\t22\t322\t1322\t2322\t7322\t44\t45\tQVAAAA\tMIIAAA\tAAAAxx\n6782\t5629\t0\t2\t2\t2\t82\t782\t782\t1782\t6782\t164\t165\tWAAAAA\tNIIAAA\tHHHHxx\n4642\t5630\t0\t2\t2\t2\t42\t642\t642\t4642\t4642\t84\t85\tOWAAAA\tOIIAAA\tOOOOxx\n5427\t5631\t1\t3\t7\t7\t27\t427\t1427\t427\t5427\t54\t55\tTAAAAA\tPIIAAA\tVVVVxx\n4461\t5632\t1\t1\t1\t1\t61\t461\t461\t4461\t4461\t122\t123\tPPAAAA\tQIIAAA\tAAAAxx\n8416\t5633\t0\t0\t6\t16\t16\t416\t416\t3416\t8416\t32\t33\tSLAAAA\tRIIAAA\tHHHHxx\n2593\t5634\t1\t1\t3\t13\t93\t593\t593\t2593\t2593\t186\t187\tTVAAAA\tSIIAAA\tOOOOxx\n6202\t5635\t0\t2\t2\t2\t2\t202\t202\t1202\t6202\t4\t5\tOEAAAA\tTIIAAA\tVVVVxx\n3826\t5636\t0\t2\t6\t6\t26\t826\t1826\t3826\t3826\t52\t53\tERAAAA\tUIIAAA\tAAAAxx\n4417\t5637\t1\t1\t7\t17\t17\t417\t417\t4417\t4417\t34\t35\tXNAAAA\tVIIAAA\tHHHHxx\n7871\t5638\t1\t3\t1\t11\t71\t871\t1871\t2871\t7871\t142\t143\tTQAAAA\tWIIAAA\tOOOOxx\n5622\t5639\t0\t2\t2\t2\t22\t622\t1622\t622\t5622\t44\t45\tGIAAAA\tXIIAAA\tVVVVxx\n3010\t5640\t0\t2\t0\t10\t10\t10\t1010\t3010\t3010\t20\t21\tULAAAA\tYIIAAA\tAAAAxx\n3407\t5641\t1\t3\t7\t7\t7\t407\t1407\t3407\t3407\t14\t15\tBBAAAA\tZIIAAA\tHHHHxx\n1274\t5642\t0\t2\t4\t14\t74\t274\t1274\t1274\t1274\t148\t149\tAXAAAA\tAJIAAA\tOOOOxx\n2828\t5643\t0\t0\t8\t8\t28\t828\t828\t2828\t2828\t56\t57\tUEAAAA\tBJIAAA\tVVVVxx\n3427\t5644\t1\t3\t7\t7\t27\t427\t1427\t3427\t3427\t54\t55\tVBAAAA\tCJIAAA\tAAAAxx\n612\t5645\t0\t0\t2\t12\t12\t612\t612\t612\t612\t24\t25\tOXAAAA\tDJIAAA\tHHHHxx\n8729\t5646\t1\t1\t9\t9\t29\t729\t729\t3729\t8729\t58\t59\tTXAAAA\tEJIAAA\tOOOOxx\n1239\t5647\t1\t3\t9\t19\t39\t239\t1239\t1239\t1239\t78\t79\tRVAAAA\tFJIAAA\tVVVVxx\n8990\t5648\t0\t2\t0\t10\t90\t990\t990\t3990\t8990\t180\t181\tUHAAAA\tGJIAAA\tAAAAxx\n5609\t5649\t1\t1\t9\t9\t9\t609\t1609\t609\t5609\t18\t19\tTHAAAA\tHJIAAA\tHHHHxx\n4441\t5650\t1\t1\t1\t1\t41\t441\t441\t4441\t4441\t82\t83\tVOAAAA\tIJIAAA\tOOOOxx\n9078\t5651\t0\t2\t8\t18\t78\t78\t1078\t4078\t9078\t156\t157\tELAAAA\tJJIAAA\tVVVVxx\n6699\t5652\t1\t3\t9\t19\t99\t699\t699\t1699\t6699\t198\t199\tRXAAAA\tKJIAAA\tAAAAxx\n8390\t5653\t0\t2\t0\t10\t90\t390\t390\t3390\t8390\t180\t181\tSKAAAA\tLJIAAA\tHHHHxx\n5455\t5654\t1\t3\t5\t15\t55\t455\t1455\t455\t5455\t110\t111\tVBAAAA\tMJIAAA\tOOOOxx\n7537\t5655\t1\t1\t7\t17\t37\t537\t1537\t2537\t7537\t74\t75\tXDAAAA\tNJIAAA\tVVVVxx\n4669\t5656\t1\t1\t9\t9\t69\t669\t669\t4669\t4669\t138\t139\tPXAAAA\tOJIAAA\tAAAAxx\n5534\t5657\t0\t2\t4\t14\t34\t534\t1534\t534\t5534\t68\t69\tWEAAAA\tPJIAAA\tHHHHxx\n1920\t5658\t0\t0\t0\t0\t20\t920\t1920\t1920\t1920\t40\t41\tWVAAAA\tQJIAAA\tOOOOxx\n9465\t5659\t1\t1\t5\t5\t65\t465\t1465\t4465\t9465\t130\t131\tBAAAAA\tRJIAAA\tVVVVxx\n4897\t5660\t1\t1\t7\t17\t97\t897\t897\t4897\t4897\t194\t195\tJGAAAA\tSJIAAA\tAAAAxx\n1990\t5661\t0\t2\t0\t10\t90\t990\t1990\t1990\t1990\t180\t181\tOYAAAA\tTJIAAA\tHHHHxx\n7148\t5662\t0\t0\t8\t8\t48\t148\t1148\t2148\t7148\t96\t97\tYOAAAA\tUJIAAA\tOOOOxx\n533\t5663\t1\t1\t3\t13\t33\t533\t533\t533\t533\t66\t67\tNUAAAA\tVJIAAA\tVVVVxx\n4339\t5664\t1\t3\t9\t19\t39\t339\t339\t4339\t4339\t78\t79\tXKAAAA\tWJIAAA\tAAAAxx\n6450\t5665\t0\t2\t0\t10\t50\t450\t450\t1450\t6450\t100\t101\tCOAAAA\tXJIAAA\tHHHHxx\n9627\t5666\t1\t3\t7\t7\t27\t627\t1627\t4627\t9627\t54\t55\tHGAAAA\tYJIAAA\tOOOOxx\n5539\t5667\t1\t3\t9\t19\t39\t539\t1539\t539\t5539\t78\t79\tBFAAAA\tZJIAAA\tVVVVxx\n6758\t5668\t0\t2\t8\t18\t58\t758\t758\t1758\t6758\t116\t117\tYZAAAA\tAKIAAA\tAAAAxx\n3435\t5669\t1\t3\t5\t15\t35\t435\t1435\t3435\t3435\t70\t71\tDCAAAA\tBKIAAA\tHHHHxx\n4350\t5670\t0\t2\t0\t10\t50\t350\t350\t4350\t4350\t100\t101\tILAAAA\tCKIAAA\tOOOOxx\n9088\t5671\t0\t0\t8\t8\t88\t88\t1088\t4088\t9088\t176\t177\tOLAAAA\tDKIAAA\tVVVVxx\n6368\t5672\t0\t0\t8\t8\t68\t368\t368\t1368\t6368\t136\t137\tYKAAAA\tEKIAAA\tAAAAxx\n6337\t5673\t1\t1\t7\t17\t37\t337\t337\t1337\t6337\t74\t75\tTJAAAA\tFKIAAA\tHHHHxx\n4361\t5674\t1\t1\t1\t1\t61\t361\t361\t4361\t4361\t122\t123\tTLAAAA\tGKIAAA\tOOOOxx\n1719\t5675\t1\t3\t9\t19\t19\t719\t1719\t1719\t1719\t38\t39\tDOAAAA\tHKIAAA\tVVVVxx\n3109\t5676\t1\t1\t9\t9\t9\t109\t1109\t3109\t3109\t18\t19\tPPAAAA\tIKIAAA\tAAAAxx\n7135\t5677\t1\t3\t5\t15\t35\t135\t1135\t2135\t7135\t70\t71\tLOAAAA\tJKIAAA\tHHHHxx\n1964\t5678\t0\t0\t4\t4\t64\t964\t1964\t1964\t1964\t128\t129\tOXAAAA\tKKIAAA\tOOOOxx\n3\t5679\t1\t3\t3\t3\t3\t3\t3\t3\t3\t6\t7\tDAAAAA\tLKIAAA\tVVVVxx\n1868\t5680\t0\t0\t8\t8\t68\t868\t1868\t1868\t1868\t136\t137\tWTAAAA\tMKIAAA\tAAAAxx\n5182\t5681\t0\t2\t2\t2\t82\t182\t1182\t182\t5182\t164\t165\tIRAAAA\tNKIAAA\tHHHHxx\n7567\t5682\t1\t3\t7\t7\t67\t567\t1567\t2567\t7567\t134\t135\tBFAAAA\tOKIAAA\tOOOOxx\n3676\t5683\t0\t0\t6\t16\t76\t676\t1676\t3676\t3676\t152\t153\tKLAAAA\tPKIAAA\tVVVVxx\n9382\t5684\t0\t2\t2\t2\t82\t382\t1382\t4382\t9382\t164\t165\tWWAAAA\tQKIAAA\tAAAAxx\n8645\t5685\t1\t1\t5\t5\t45\t645\t645\t3645\t8645\t90\t91\tNUAAAA\tRKIAAA\tHHHHxx\n2018\t5686\t0\t2\t8\t18\t18\t18\t18\t2018\t2018\t36\t37\tQZAAAA\tSKIAAA\tOOOOxx\n217\t5687\t1\t1\t7\t17\t17\t217\t217\t217\t217\t34\t35\tJIAAAA\tTKIAAA\tVVVVxx\n6793\t5688\t1\t1\t3\t13\t93\t793\t793\t1793\t6793\t186\t187\tHBAAAA\tUKIAAA\tAAAAxx\n7280\t5689\t0\t0\t0\t0\t80\t280\t1280\t2280\t7280\t160\t161\tAUAAAA\tVKIAAA\tHHHHxx\n2168\t5690\t0\t0\t8\t8\t68\t168\t168\t2168\t2168\t136\t137\tKFAAAA\tWKIAAA\tOOOOxx\n5259\t5691\t1\t3\t9\t19\t59\t259\t1259\t259\t5259\t118\t119\tHUAAAA\tXKIAAA\tVVVVxx\n6019\t5692\t1\t3\t9\t19\t19\t19\t19\t1019\t6019\t38\t39\tNXAAAA\tYKIAAA\tAAAAxx\n877\t5693\t1\t1\t7\t17\t77\t877\t877\t877\t877\t154\t155\tTHAAAA\tZKIAAA\tHHHHxx\n4961\t5694\t1\t1\t1\t1\t61\t961\t961\t4961\t4961\t122\t123\tVIAAAA\tALIAAA\tOOOOxx\n1873\t5695\t1\t1\t3\t13\t73\t873\t1873\t1873\t1873\t146\t147\tBUAAAA\tBLIAAA\tVVVVxx\n13\t5696\t1\t1\t3\t13\t13\t13\t13\t13\t13\t26\t27\tNAAAAA\tCLIAAA\tAAAAxx\n1537\t5697\t1\t1\t7\t17\t37\t537\t1537\t1537\t1537\t74\t75\tDHAAAA\tDLIAAA\tHHHHxx\n3129\t5698\t1\t1\t9\t9\t29\t129\t1129\t3129\t3129\t58\t59\tJQAAAA\tELIAAA\tOOOOxx\n6473\t5699\t1\t1\t3\t13\t73\t473\t473\t1473\t6473\t146\t147\tZOAAAA\tFLIAAA\tVVVVxx\n7865\t5700\t1\t1\t5\t5\t65\t865\t1865\t2865\t7865\t130\t131\tNQAAAA\tGLIAAA\tAAAAxx\n7822\t5701\t0\t2\t2\t2\t22\t822\t1822\t2822\t7822\t44\t45\tWOAAAA\tHLIAAA\tHHHHxx\n239\t5702\t1\t3\t9\t19\t39\t239\t239\t239\t239\t78\t79\tFJAAAA\tILIAAA\tOOOOxx\n2062\t5703\t0\t2\t2\t2\t62\t62\t62\t2062\t2062\t124\t125\tIBAAAA\tJLIAAA\tVVVVxx\n762\t5704\t0\t2\t2\t2\t62\t762\t762\t762\t762\t124\t125\tIDAAAA\tKLIAAA\tAAAAxx\n3764\t5705\t0\t0\t4\t4\t64\t764\t1764\t3764\t3764\t128\t129\tUOAAAA\tLLIAAA\tHHHHxx\n465\t5706\t1\t1\t5\t5\t65\t465\t465\t465\t465\t130\t131\tXRAAAA\tMLIAAA\tOOOOxx\n2587\t5707\t1\t3\t7\t7\t87\t587\t587\t2587\t2587\t174\t175\tNVAAAA\tNLIAAA\tVVVVxx\n8402\t5708\t0\t2\t2\t2\t2\t402\t402\t3402\t8402\t4\t5\tELAAAA\tOLIAAA\tAAAAxx\n1055\t5709\t1\t3\t5\t15\t55\t55\t1055\t1055\t1055\t110\t111\tPOAAAA\tPLIAAA\tHHHHxx\n3072\t5710\t0\t0\t2\t12\t72\t72\t1072\t3072\t3072\t144\t145\tEOAAAA\tQLIAAA\tOOOOxx\n7359\t5711\t1\t3\t9\t19\t59\t359\t1359\t2359\t7359\t118\t119\tBXAAAA\tRLIAAA\tVVVVxx\n6558\t5712\t0\t2\t8\t18\t58\t558\t558\t1558\t6558\t116\t117\tGSAAAA\tSLIAAA\tAAAAxx\n48\t5713\t0\t0\t8\t8\t48\t48\t48\t48\t48\t96\t97\tWBAAAA\tTLIAAA\tHHHHxx\n5382\t5714\t0\t2\t2\t2\t82\t382\t1382\t382\t5382\t164\t165\tAZAAAA\tULIAAA\tOOOOxx\n947\t5715\t1\t3\t7\t7\t47\t947\t947\t947\t947\t94\t95\tLKAAAA\tVLIAAA\tVVVVxx\n2644\t5716\t0\t0\t4\t4\t44\t644\t644\t2644\t2644\t88\t89\tSXAAAA\tWLIAAA\tAAAAxx\n7516\t5717\t0\t0\t6\t16\t16\t516\t1516\t2516\t7516\t32\t33\tCDAAAA\tXLIAAA\tHHHHxx\n2362\t5718\t0\t2\t2\t2\t62\t362\t362\t2362\t2362\t124\t125\tWMAAAA\tYLIAAA\tOOOOxx\n839\t5719\t1\t3\t9\t19\t39\t839\t839\t839\t839\t78\t79\tHGAAAA\tZLIAAA\tVVVVxx\n2216\t5720\t0\t0\t6\t16\t16\t216\t216\t2216\t2216\t32\t33\tGHAAAA\tAMIAAA\tAAAAxx\n7673\t5721\t1\t1\t3\t13\t73\t673\t1673\t2673\t7673\t146\t147\tDJAAAA\tBMIAAA\tHHHHxx\n8173\t5722\t1\t1\t3\t13\t73\t173\t173\t3173\t8173\t146\t147\tJCAAAA\tCMIAAA\tOOOOxx\n1630\t5723\t0\t2\t0\t10\t30\t630\t1630\t1630\t1630\t60\t61\tSKAAAA\tDMIAAA\tVVVVxx\n9057\t5724\t1\t1\t7\t17\t57\t57\t1057\t4057\t9057\t114\t115\tJKAAAA\tEMIAAA\tAAAAxx\n4392\t5725\t0\t0\t2\t12\t92\t392\t392\t4392\t4392\t184\t185\tYMAAAA\tFMIAAA\tHHHHxx\n3695\t5726\t1\t3\t5\t15\t95\t695\t1695\t3695\t3695\t190\t191\tDMAAAA\tGMIAAA\tOOOOxx\n5751\t5727\t1\t3\t1\t11\t51\t751\t1751\t751\t5751\t102\t103\tFNAAAA\tHMIAAA\tVVVVxx\n5745\t5728\t1\t1\t5\t5\t45\t745\t1745\t745\t5745\t90\t91\tZMAAAA\tIMIAAA\tAAAAxx\n7945\t5729\t1\t1\t5\t5\t45\t945\t1945\t2945\t7945\t90\t91\tPTAAAA\tJMIAAA\tHHHHxx\n5174\t5730\t0\t2\t4\t14\t74\t174\t1174\t174\t5174\t148\t149\tARAAAA\tKMIAAA\tOOOOxx\n3829\t5731\t1\t1\t9\t9\t29\t829\t1829\t3829\t3829\t58\t59\tHRAAAA\tLMIAAA\tVVVVxx\n3317\t5732\t1\t1\t7\t17\t17\t317\t1317\t3317\t3317\t34\t35\tPXAAAA\tMMIAAA\tAAAAxx\n4253\t5733\t1\t1\t3\t13\t53\t253\t253\t4253\t4253\t106\t107\tPHAAAA\tNMIAAA\tHHHHxx\n1291\t5734\t1\t3\t1\t11\t91\t291\t1291\t1291\t1291\t182\t183\tRXAAAA\tOMIAAA\tOOOOxx\n3266\t5735\t0\t2\t6\t6\t66\t266\t1266\t3266\t3266\t132\t133\tQVAAAA\tPMIAAA\tVVVVxx\n2939\t5736\t1\t3\t9\t19\t39\t939\t939\t2939\t2939\t78\t79\tBJAAAA\tQMIAAA\tAAAAxx\n2755\t5737\t1\t3\t5\t15\t55\t755\t755\t2755\t2755\t110\t111\tZBAAAA\tRMIAAA\tHHHHxx\n6844\t5738\t0\t0\t4\t4\t44\t844\t844\t1844\t6844\t88\t89\tGDAAAA\tSMIAAA\tOOOOxx\n8594\t5739\t0\t2\t4\t14\t94\t594\t594\t3594\t8594\t188\t189\tOSAAAA\tTMIAAA\tVVVVxx\n704\t5740\t0\t0\t4\t4\t4\t704\t704\t704\t704\t8\t9\tCBAAAA\tUMIAAA\tAAAAxx\n1681\t5741\t1\t1\t1\t1\t81\t681\t1681\t1681\t1681\t162\t163\tRMAAAA\tVMIAAA\tHHHHxx\n364\t5742\t0\t0\t4\t4\t64\t364\t364\t364\t364\t128\t129\tAOAAAA\tWMIAAA\tOOOOxx\n2928\t5743\t0\t0\t8\t8\t28\t928\t928\t2928\t2928\t56\t57\tQIAAAA\tXMIAAA\tVVVVxx\n117\t5744\t1\t1\t7\t17\t17\t117\t117\t117\t117\t34\t35\tNEAAAA\tYMIAAA\tAAAAxx\n96\t5745\t0\t0\t6\t16\t96\t96\t96\t96\t96\t192\t193\tSDAAAA\tZMIAAA\tHHHHxx\n7796\t5746\t0\t0\t6\t16\t96\t796\t1796\t2796\t7796\t192\t193\tWNAAAA\tANIAAA\tOOOOxx\n3101\t5747\t1\t1\t1\t1\t1\t101\t1101\t3101\t3101\t2\t3\tHPAAAA\tBNIAAA\tVVVVxx\n3397\t5748\t1\t1\t7\t17\t97\t397\t1397\t3397\t3397\t194\t195\tRAAAAA\tCNIAAA\tAAAAxx\n1605\t5749\t1\t1\t5\t5\t5\t605\t1605\t1605\t1605\t10\t11\tTJAAAA\tDNIAAA\tHHHHxx\n4881\t5750\t1\t1\t1\t1\t81\t881\t881\t4881\t4881\t162\t163\tTFAAAA\tENIAAA\tOOOOxx\n4521\t5751\t1\t1\t1\t1\t21\t521\t521\t4521\t4521\t42\t43\tXRAAAA\tFNIAAA\tVVVVxx\n6430\t5752\t0\t2\t0\t10\t30\t430\t430\t1430\t6430\t60\t61\tINAAAA\tGNIAAA\tAAAAxx\n282\t5753\t0\t2\t2\t2\t82\t282\t282\t282\t282\t164\t165\tWKAAAA\tHNIAAA\tHHHHxx\n9645\t5754\t1\t1\t5\t5\t45\t645\t1645\t4645\t9645\t90\t91\tZGAAAA\tINIAAA\tOOOOxx\n8946\t5755\t0\t2\t6\t6\t46\t946\t946\t3946\t8946\t92\t93\tCGAAAA\tJNIAAA\tVVVVxx\n5064\t5756\t0\t0\t4\t4\t64\t64\t1064\t64\t5064\t128\t129\tUMAAAA\tKNIAAA\tAAAAxx\n7470\t5757\t0\t2\t0\t10\t70\t470\t1470\t2470\t7470\t140\t141\tIBAAAA\tLNIAAA\tHHHHxx\n5886\t5758\t0\t2\t6\t6\t86\t886\t1886\t886\t5886\t172\t173\tKSAAAA\tMNIAAA\tOOOOxx\n6280\t5759\t0\t0\t0\t0\t80\t280\t280\t1280\t6280\t160\t161\tOHAAAA\tNNIAAA\tVVVVxx\n5247\t5760\t1\t3\t7\t7\t47\t247\t1247\t247\t5247\t94\t95\tVTAAAA\tONIAAA\tAAAAxx\n412\t5761\t0\t0\t2\t12\t12\t412\t412\t412\t412\t24\t25\tWPAAAA\tPNIAAA\tHHHHxx\n5342\t5762\t0\t2\t2\t2\t42\t342\t1342\t342\t5342\t84\t85\tMXAAAA\tQNIAAA\tOOOOxx\n2271\t5763\t1\t3\t1\t11\t71\t271\t271\t2271\t2271\t142\t143\tJJAAAA\tRNIAAA\tVVVVxx\n849\t5764\t1\t1\t9\t9\t49\t849\t849\t849\t849\t98\t99\tRGAAAA\tSNIAAA\tAAAAxx\n1885\t5765\t1\t1\t5\t5\t85\t885\t1885\t1885\t1885\t170\t171\tNUAAAA\tTNIAAA\tHHHHxx\n5620\t5766\t0\t0\t0\t0\t20\t620\t1620\t620\t5620\t40\t41\tEIAAAA\tUNIAAA\tOOOOxx\n7079\t5767\t1\t3\t9\t19\t79\t79\t1079\t2079\t7079\t158\t159\tHMAAAA\tVNIAAA\tVVVVxx\n5819\t5768\t1\t3\t9\t19\t19\t819\t1819\t819\t5819\t38\t39\tVPAAAA\tWNIAAA\tAAAAxx\n7497\t5769\t1\t1\t7\t17\t97\t497\t1497\t2497\t7497\t194\t195\tJCAAAA\tXNIAAA\tHHHHxx\n5993\t5770\t1\t1\t3\t13\t93\t993\t1993\t993\t5993\t186\t187\tNWAAAA\tYNIAAA\tOOOOxx\n3739\t5771\t1\t3\t9\t19\t39\t739\t1739\t3739\t3739\t78\t79\tVNAAAA\tZNIAAA\tVVVVxx\n6296\t5772\t0\t0\t6\t16\t96\t296\t296\t1296\t6296\t192\t193\tEIAAAA\tAOIAAA\tAAAAxx\n2716\t5773\t0\t0\t6\t16\t16\t716\t716\t2716\t2716\t32\t33\tMAAAAA\tBOIAAA\tHHHHxx\n1130\t5774\t0\t2\t0\t10\t30\t130\t1130\t1130\t1130\t60\t61\tMRAAAA\tCOIAAA\tOOOOxx\n5593\t5775\t1\t1\t3\t13\t93\t593\t1593\t593\t5593\t186\t187\tDHAAAA\tDOIAAA\tVVVVxx\n6972\t5776\t0\t0\t2\t12\t72\t972\t972\t1972\t6972\t144\t145\tEIAAAA\tEOIAAA\tAAAAxx\n8360\t5777\t0\t0\t0\t0\t60\t360\t360\t3360\t8360\t120\t121\tOJAAAA\tFOIAAA\tHHHHxx\n6448\t5778\t0\t0\t8\t8\t48\t448\t448\t1448\t6448\t96\t97\tAOAAAA\tGOIAAA\tOOOOxx\n3689\t5779\t1\t1\t9\t9\t89\t689\t1689\t3689\t3689\t178\t179\tXLAAAA\tHOIAAA\tVVVVxx\n7951\t5780\t1\t3\t1\t11\t51\t951\t1951\t2951\t7951\t102\t103\tVTAAAA\tIOIAAA\tAAAAxx\n2974\t5781\t0\t2\t4\t14\t74\t974\t974\t2974\t2974\t148\t149\tKKAAAA\tJOIAAA\tHHHHxx\n6600\t5782\t0\t0\t0\t0\t0\t600\t600\t1600\t6600\t0\t1\tWTAAAA\tKOIAAA\tOOOOxx\n4662\t5783\t0\t2\t2\t2\t62\t662\t662\t4662\t4662\t124\t125\tIXAAAA\tLOIAAA\tVVVVxx\n4765\t5784\t1\t1\t5\t5\t65\t765\t765\t4765\t4765\t130\t131\tHBAAAA\tMOIAAA\tAAAAxx\n355\t5785\t1\t3\t5\t15\t55\t355\t355\t355\t355\t110\t111\tRNAAAA\tNOIAAA\tHHHHxx\n6228\t5786\t0\t0\t8\t8\t28\t228\t228\t1228\t6228\t56\t57\tOFAAAA\tOOIAAA\tOOOOxx\n964\t5787\t0\t0\t4\t4\t64\t964\t964\t964\t964\t128\t129\tCLAAAA\tPOIAAA\tVVVVxx\n3082\t5788\t0\t2\t2\t2\t82\t82\t1082\t3082\t3082\t164\t165\tOOAAAA\tQOIAAA\tAAAAxx\n7028\t5789\t0\t0\t8\t8\t28\t28\t1028\t2028\t7028\t56\t57\tIKAAAA\tROIAAA\tHHHHxx\n4505\t5790\t1\t1\t5\t5\t5\t505\t505\t4505\t4505\t10\t11\tHRAAAA\tSOIAAA\tOOOOxx\n8961\t5791\t1\t1\t1\t1\t61\t961\t961\t3961\t8961\t122\t123\tRGAAAA\tTOIAAA\tVVVVxx\n9571\t5792\t1\t3\t1\t11\t71\t571\t1571\t4571\t9571\t142\t143\tDEAAAA\tUOIAAA\tAAAAxx\n9394\t5793\t0\t2\t4\t14\t94\t394\t1394\t4394\t9394\t188\t189\tIXAAAA\tVOIAAA\tHHHHxx\n4245\t5794\t1\t1\t5\t5\t45\t245\t245\t4245\t4245\t90\t91\tHHAAAA\tWOIAAA\tOOOOxx\n7560\t5795\t0\t0\t0\t0\t60\t560\t1560\t2560\t7560\t120\t121\tUEAAAA\tXOIAAA\tVVVVxx\n2907\t5796\t1\t3\t7\t7\t7\t907\t907\t2907\t2907\t14\t15\tVHAAAA\tYOIAAA\tAAAAxx\n7817\t5797\t1\t1\t7\t17\t17\t817\t1817\t2817\t7817\t34\t35\tROAAAA\tZOIAAA\tHHHHxx\n5408\t5798\t0\t0\t8\t8\t8\t408\t1408\t408\t5408\t16\t17\tAAAAAA\tAPIAAA\tOOOOxx\n8092\t5799\t0\t0\t2\t12\t92\t92\t92\t3092\t8092\t184\t185\tGZAAAA\tBPIAAA\tVVVVxx\n1309\t5800\t1\t1\t9\t9\t9\t309\t1309\t1309\t1309\t18\t19\tJYAAAA\tCPIAAA\tAAAAxx\n6673\t5801\t1\t1\t3\t13\t73\t673\t673\t1673\t6673\t146\t147\tRWAAAA\tDPIAAA\tHHHHxx\n1245\t5802\t1\t1\t5\t5\t45\t245\t1245\t1245\t1245\t90\t91\tXVAAAA\tEPIAAA\tOOOOxx\n6790\t5803\t0\t2\t0\t10\t90\t790\t790\t1790\t6790\t180\t181\tEBAAAA\tFPIAAA\tVVVVxx\n8380\t5804\t0\t0\t0\t0\t80\t380\t380\t3380\t8380\t160\t161\tIKAAAA\tGPIAAA\tAAAAxx\n5786\t5805\t0\t2\t6\t6\t86\t786\t1786\t786\t5786\t172\t173\tOOAAAA\tHPIAAA\tHHHHxx\n9590\t5806\t0\t2\t0\t10\t90\t590\t1590\t4590\t9590\t180\t181\tWEAAAA\tIPIAAA\tOOOOxx\n5763\t5807\t1\t3\t3\t3\t63\t763\t1763\t763\t5763\t126\t127\tRNAAAA\tJPIAAA\tVVVVxx\n1345\t5808\t1\t1\t5\t5\t45\t345\t1345\t1345\t1345\t90\t91\tTZAAAA\tKPIAAA\tAAAAxx\n3480\t5809\t0\t0\t0\t0\t80\t480\t1480\t3480\t3480\t160\t161\tWDAAAA\tLPIAAA\tHHHHxx\n7864\t5810\t0\t0\t4\t4\t64\t864\t1864\t2864\t7864\t128\t129\tMQAAAA\tMPIAAA\tOOOOxx\n4853\t5811\t1\t1\t3\t13\t53\t853\t853\t4853\t4853\t106\t107\tREAAAA\tNPIAAA\tVVVVxx\n1445\t5812\t1\t1\t5\t5\t45\t445\t1445\t1445\t1445\t90\t91\tPDAAAA\tOPIAAA\tAAAAxx\n170\t5813\t0\t2\t0\t10\t70\t170\t170\t170\t170\t140\t141\tOGAAAA\tPPIAAA\tHHHHxx\n7348\t5814\t0\t0\t8\t8\t48\t348\t1348\t2348\t7348\t96\t97\tQWAAAA\tQPIAAA\tOOOOxx\n3920\t5815\t0\t0\t0\t0\t20\t920\t1920\t3920\t3920\t40\t41\tUUAAAA\tRPIAAA\tVVVVxx\n3307\t5816\t1\t3\t7\t7\t7\t307\t1307\t3307\t3307\t14\t15\tFXAAAA\tSPIAAA\tAAAAxx\n4584\t5817\t0\t0\t4\t4\t84\t584\t584\t4584\t4584\t168\t169\tIUAAAA\tTPIAAA\tHHHHxx\n3344\t5818\t0\t0\t4\t4\t44\t344\t1344\t3344\t3344\t88\t89\tQYAAAA\tUPIAAA\tOOOOxx\n4360\t5819\t0\t0\t0\t0\t60\t360\t360\t4360\t4360\t120\t121\tSLAAAA\tVPIAAA\tVVVVxx\n8757\t5820\t1\t1\t7\t17\t57\t757\t757\t3757\t8757\t114\t115\tVYAAAA\tWPIAAA\tAAAAxx\n4315\t5821\t1\t3\t5\t15\t15\t315\t315\t4315\t4315\t30\t31\tZJAAAA\tXPIAAA\tHHHHxx\n5243\t5822\t1\t3\t3\t3\t43\t243\t1243\t243\t5243\t86\t87\tRTAAAA\tYPIAAA\tOOOOxx\n8550\t5823\t0\t2\t0\t10\t50\t550\t550\t3550\t8550\t100\t101\tWQAAAA\tZPIAAA\tVVVVxx\n159\t5824\t1\t3\t9\t19\t59\t159\t159\t159\t159\t118\t119\tDGAAAA\tAQIAAA\tAAAAxx\n4710\t5825\t0\t2\t0\t10\t10\t710\t710\t4710\t4710\t20\t21\tEZAAAA\tBQIAAA\tHHHHxx\n7179\t5826\t1\t3\t9\t19\t79\t179\t1179\t2179\t7179\t158\t159\tDQAAAA\tCQIAAA\tOOOOxx\n2509\t5827\t1\t1\t9\t9\t9\t509\t509\t2509\t2509\t18\t19\tNSAAAA\tDQIAAA\tVVVVxx\n6981\t5828\t1\t1\t1\t1\t81\t981\t981\t1981\t6981\t162\t163\tNIAAAA\tEQIAAA\tAAAAxx\n5060\t5829\t0\t0\t0\t0\t60\t60\t1060\t60\t5060\t120\t121\tQMAAAA\tFQIAAA\tHHHHxx\n5601\t5830\t1\t1\t1\t1\t1\t601\t1601\t601\t5601\t2\t3\tLHAAAA\tGQIAAA\tOOOOxx\n703\t5831\t1\t3\t3\t3\t3\t703\t703\t703\t703\t6\t7\tBBAAAA\tHQIAAA\tVVVVxx\n8719\t5832\t1\t3\t9\t19\t19\t719\t719\t3719\t8719\t38\t39\tJXAAAA\tIQIAAA\tAAAAxx\n1570\t5833\t0\t2\t0\t10\t70\t570\t1570\t1570\t1570\t140\t141\tKIAAAA\tJQIAAA\tHHHHxx\n1036\t5834\t0\t0\t6\t16\t36\t36\t1036\t1036\t1036\t72\t73\tWNAAAA\tKQIAAA\tOOOOxx\n6703\t5835\t1\t3\t3\t3\t3\t703\t703\t1703\t6703\t6\t7\tVXAAAA\tLQIAAA\tVVVVxx\n252\t5836\t0\t0\t2\t12\t52\t252\t252\t252\t252\t104\t105\tSJAAAA\tMQIAAA\tAAAAxx\n631\t5837\t1\t3\t1\t11\t31\t631\t631\t631\t631\t62\t63\tHYAAAA\tNQIAAA\tHHHHxx\n5098\t5838\t0\t2\t8\t18\t98\t98\t1098\t98\t5098\t196\t197\tCOAAAA\tOQIAAA\tOOOOxx\n8346\t5839\t0\t2\t6\t6\t46\t346\t346\t3346\t8346\t92\t93\tAJAAAA\tPQIAAA\tVVVVxx\n4910\t5840\t0\t2\t0\t10\t10\t910\t910\t4910\t4910\t20\t21\tWGAAAA\tQQIAAA\tAAAAxx\n559\t5841\t1\t3\t9\t19\t59\t559\t559\t559\t559\t118\t119\tNVAAAA\tRQIAAA\tHHHHxx\n1477\t5842\t1\t1\t7\t17\t77\t477\t1477\t1477\t1477\t154\t155\tVEAAAA\tSQIAAA\tOOOOxx\n5115\t5843\t1\t3\t5\t15\t15\t115\t1115\t115\t5115\t30\t31\tTOAAAA\tTQIAAA\tVVVVxx\n8784\t5844\t0\t0\t4\t4\t84\t784\t784\t3784\t8784\t168\t169\tWZAAAA\tUQIAAA\tAAAAxx\n4422\t5845\t0\t2\t2\t2\t22\t422\t422\t4422\t4422\t44\t45\tCOAAAA\tVQIAAA\tHHHHxx\n2702\t5846\t0\t2\t2\t2\t2\t702\t702\t2702\t2702\t4\t5\tYZAAAA\tWQIAAA\tOOOOxx\n9599\t5847\t1\t3\t9\t19\t99\t599\t1599\t4599\t9599\t198\t199\tFFAAAA\tXQIAAA\tVVVVxx\n2463\t5848\t1\t3\t3\t3\t63\t463\t463\t2463\t2463\t126\t127\tTQAAAA\tYQIAAA\tAAAAxx\n498\t5849\t0\t2\t8\t18\t98\t498\t498\t498\t498\t196\t197\tETAAAA\tZQIAAA\tHHHHxx\n494\t5850\t0\t2\t4\t14\t94\t494\t494\t494\t494\t188\t189\tATAAAA\tARIAAA\tOOOOxx\n8632\t5851\t0\t0\t2\t12\t32\t632\t632\t3632\t8632\t64\t65\tAUAAAA\tBRIAAA\tVVVVxx\n3449\t5852\t1\t1\t9\t9\t49\t449\t1449\t3449\t3449\t98\t99\tRCAAAA\tCRIAAA\tAAAAxx\n5888\t5853\t0\t0\t8\t8\t88\t888\t1888\t888\t5888\t176\t177\tMSAAAA\tDRIAAA\tHHHHxx\n2211\t5854\t1\t3\t1\t11\t11\t211\t211\t2211\t2211\t22\t23\tBHAAAA\tERIAAA\tOOOOxx\n2835\t5855\t1\t3\t5\t15\t35\t835\t835\t2835\t2835\t70\t71\tBFAAAA\tFRIAAA\tVVVVxx\n4196\t5856\t0\t0\t6\t16\t96\t196\t196\t4196\t4196\t192\t193\tKFAAAA\tGRIAAA\tAAAAxx\n2177\t5857\t1\t1\t7\t17\t77\t177\t177\t2177\t2177\t154\t155\tTFAAAA\tHRIAAA\tHHHHxx\n1959\t5858\t1\t3\t9\t19\t59\t959\t1959\t1959\t1959\t118\t119\tJXAAAA\tIRIAAA\tOOOOxx\n5172\t5859\t0\t0\t2\t12\t72\t172\t1172\t172\t5172\t144\t145\tYQAAAA\tJRIAAA\tVVVVxx\n7898\t5860\t0\t2\t8\t18\t98\t898\t1898\t2898\t7898\t196\t197\tURAAAA\tKRIAAA\tAAAAxx\n5729\t5861\t1\t1\t9\t9\t29\t729\t1729\t729\t5729\t58\t59\tJMAAAA\tLRIAAA\tHHHHxx\n469\t5862\t1\t1\t9\t9\t69\t469\t469\t469\t469\t138\t139\tBSAAAA\tMRIAAA\tOOOOxx\n4456\t5863\t0\t0\t6\t16\t56\t456\t456\t4456\t4456\t112\t113\tKPAAAA\tNRIAAA\tVVVVxx\n3578\t5864\t0\t2\t8\t18\t78\t578\t1578\t3578\t3578\t156\t157\tQHAAAA\tORIAAA\tAAAAxx\n8623\t5865\t1\t3\t3\t3\t23\t623\t623\t3623\t8623\t46\t47\tRTAAAA\tPRIAAA\tHHHHxx\n6749\t5866\t1\t1\t9\t9\t49\t749\t749\t1749\t6749\t98\t99\tPZAAAA\tQRIAAA\tOOOOxx\n6735\t5867\t1\t3\t5\t15\t35\t735\t735\t1735\t6735\t70\t71\tBZAAAA\tRRIAAA\tVVVVxx\n5197\t5868\t1\t1\t7\t17\t97\t197\t1197\t197\t5197\t194\t195\tXRAAAA\tSRIAAA\tAAAAxx\n2067\t5869\t1\t3\t7\t7\t67\t67\t67\t2067\t2067\t134\t135\tNBAAAA\tTRIAAA\tHHHHxx\n5600\t5870\t0\t0\t0\t0\t0\t600\t1600\t600\t5600\t0\t1\tKHAAAA\tURIAAA\tOOOOxx\n7741\t5871\t1\t1\t1\t1\t41\t741\t1741\t2741\t7741\t82\t83\tTLAAAA\tVRIAAA\tVVVVxx\n9925\t5872\t1\t1\t5\t5\t25\t925\t1925\t4925\t9925\t50\t51\tTRAAAA\tWRIAAA\tAAAAxx\n9685\t5873\t1\t1\t5\t5\t85\t685\t1685\t4685\t9685\t170\t171\tNIAAAA\tXRIAAA\tHHHHxx\n7622\t5874\t0\t2\t2\t2\t22\t622\t1622\t2622\t7622\t44\t45\tEHAAAA\tYRIAAA\tOOOOxx\n6859\t5875\t1\t3\t9\t19\t59\t859\t859\t1859\t6859\t118\t119\tVDAAAA\tZRIAAA\tVVVVxx\n3094\t5876\t0\t2\t4\t14\t94\t94\t1094\t3094\t3094\t188\t189\tAPAAAA\tASIAAA\tAAAAxx\n2628\t5877\t0\t0\t8\t8\t28\t628\t628\t2628\t2628\t56\t57\tCXAAAA\tBSIAAA\tHHHHxx\n40\t5878\t0\t0\t0\t0\t40\t40\t40\t40\t40\t80\t81\tOBAAAA\tCSIAAA\tOOOOxx\n1644\t5879\t0\t0\t4\t4\t44\t644\t1644\t1644\t1644\t88\t89\tGLAAAA\tDSIAAA\tVVVVxx\n588\t5880\t0\t0\t8\t8\t88\t588\t588\t588\t588\t176\t177\tQWAAAA\tESIAAA\tAAAAxx\n7522\t5881\t0\t2\t2\t2\t22\t522\t1522\t2522\t7522\t44\t45\tIDAAAA\tFSIAAA\tHHHHxx\n162\t5882\t0\t2\t2\t2\t62\t162\t162\t162\t162\t124\t125\tGGAAAA\tGSIAAA\tOOOOxx\n3610\t5883\t0\t2\t0\t10\t10\t610\t1610\t3610\t3610\t20\t21\tWIAAAA\tHSIAAA\tVVVVxx\n3561\t5884\t1\t1\t1\t1\t61\t561\t1561\t3561\t3561\t122\t123\tZGAAAA\tISIAAA\tAAAAxx\n8185\t5885\t1\t1\t5\t5\t85\t185\t185\t3185\t8185\t170\t171\tVCAAAA\tJSIAAA\tHHHHxx\n7237\t5886\t1\t1\t7\t17\t37\t237\t1237\t2237\t7237\t74\t75\tJSAAAA\tKSIAAA\tOOOOxx\n4592\t5887\t0\t0\t2\t12\t92\t592\t592\t4592\t4592\t184\t185\tQUAAAA\tLSIAAA\tVVVVxx\n7082\t5888\t0\t2\t2\t2\t82\t82\t1082\t2082\t7082\t164\t165\tKMAAAA\tMSIAAA\tAAAAxx\n4719\t5889\t1\t3\t9\t19\t19\t719\t719\t4719\t4719\t38\t39\tNZAAAA\tNSIAAA\tHHHHxx\n3879\t5890\t1\t3\t9\t19\t79\t879\t1879\t3879\t3879\t158\t159\tFTAAAA\tOSIAAA\tOOOOxx\n1662\t5891\t0\t2\t2\t2\t62\t662\t1662\t1662\t1662\t124\t125\tYLAAAA\tPSIAAA\tVVVVxx\n3995\t5892\t1\t3\t5\t15\t95\t995\t1995\t3995\t3995\t190\t191\tRXAAAA\tQSIAAA\tAAAAxx\n5828\t5893\t0\t0\t8\t8\t28\t828\t1828\t828\t5828\t56\t57\tEQAAAA\tRSIAAA\tHHHHxx\n4197\t5894\t1\t1\t7\t17\t97\t197\t197\t4197\t4197\t194\t195\tLFAAAA\tSSIAAA\tOOOOxx\n5146\t5895\t0\t2\t6\t6\t46\t146\t1146\t146\t5146\t92\t93\tYPAAAA\tTSIAAA\tVVVVxx\n753\t5896\t1\t1\t3\t13\t53\t753\t753\t753\t753\t106\t107\tZCAAAA\tUSIAAA\tAAAAxx\n7064\t5897\t0\t0\t4\t4\t64\t64\t1064\t2064\t7064\t128\t129\tSLAAAA\tVSIAAA\tHHHHxx\n1312\t5898\t0\t0\t2\t12\t12\t312\t1312\t1312\t1312\t24\t25\tMYAAAA\tWSIAAA\tOOOOxx\n5573\t5899\t1\t1\t3\t13\t73\t573\t1573\t573\t5573\t146\t147\tJGAAAA\tXSIAAA\tVVVVxx\n7634\t5900\t0\t2\t4\t14\t34\t634\t1634\t2634\t7634\t68\t69\tQHAAAA\tYSIAAA\tAAAAxx\n2459\t5901\t1\t3\t9\t19\t59\t459\t459\t2459\t2459\t118\t119\tPQAAAA\tZSIAAA\tHHHHxx\n8636\t5902\t0\t0\t6\t16\t36\t636\t636\t3636\t8636\t72\t73\tEUAAAA\tATIAAA\tOOOOxx\n5318\t5903\t0\t2\t8\t18\t18\t318\t1318\t318\t5318\t36\t37\tOWAAAA\tBTIAAA\tVVVVxx\n1064\t5904\t0\t0\t4\t4\t64\t64\t1064\t1064\t1064\t128\t129\tYOAAAA\tCTIAAA\tAAAAxx\n9779\t5905\t1\t3\t9\t19\t79\t779\t1779\t4779\t9779\t158\t159\tDMAAAA\tDTIAAA\tHHHHxx\n6512\t5906\t0\t0\t2\t12\t12\t512\t512\t1512\t6512\t24\t25\tMQAAAA\tETIAAA\tOOOOxx\n3572\t5907\t0\t0\t2\t12\t72\t572\t1572\t3572\t3572\t144\t145\tKHAAAA\tFTIAAA\tVVVVxx\n816\t5908\t0\t0\t6\t16\t16\t816\t816\t816\t816\t32\t33\tKFAAAA\tGTIAAA\tAAAAxx\n3978\t5909\t0\t2\t8\t18\t78\t978\t1978\t3978\t3978\t156\t157\tAXAAAA\tHTIAAA\tHHHHxx\n5390\t5910\t0\t2\t0\t10\t90\t390\t1390\t390\t5390\t180\t181\tIZAAAA\tITIAAA\tOOOOxx\n4685\t5911\t1\t1\t5\t5\t85\t685\t685\t4685\t4685\t170\t171\tFYAAAA\tJTIAAA\tVVVVxx\n3003\t5912\t1\t3\t3\t3\t3\t3\t1003\t3003\t3003\t6\t7\tNLAAAA\tKTIAAA\tAAAAxx\n2638\t5913\t0\t2\t8\t18\t38\t638\t638\t2638\t2638\t76\t77\tMXAAAA\tLTIAAA\tHHHHxx\n9716\t5914\t0\t0\t6\t16\t16\t716\t1716\t4716\t9716\t32\t33\tSJAAAA\tMTIAAA\tOOOOxx\n9598\t5915\t0\t2\t8\t18\t98\t598\t1598\t4598\t9598\t196\t197\tEFAAAA\tNTIAAA\tVVVVxx\n9501\t5916\t1\t1\t1\t1\t1\t501\t1501\t4501\t9501\t2\t3\tLBAAAA\tOTIAAA\tAAAAxx\n1704\t5917\t0\t0\t4\t4\t4\t704\t1704\t1704\t1704\t8\t9\tONAAAA\tPTIAAA\tHHHHxx\n8609\t5918\t1\t1\t9\t9\t9\t609\t609\t3609\t8609\t18\t19\tDTAAAA\tQTIAAA\tOOOOxx\n5211\t5919\t1\t3\t1\t11\t11\t211\t1211\t211\t5211\t22\t23\tLSAAAA\tRTIAAA\tVVVVxx\n3605\t5920\t1\t1\t5\t5\t5\t605\t1605\t3605\t3605\t10\t11\tRIAAAA\tSTIAAA\tAAAAxx\n8730\t5921\t0\t2\t0\t10\t30\t730\t730\t3730\t8730\t60\t61\tUXAAAA\tTTIAAA\tHHHHxx\n4208\t5922\t0\t0\t8\t8\t8\t208\t208\t4208\t4208\t16\t17\tWFAAAA\tUTIAAA\tOOOOxx\n7784\t5923\t0\t0\t4\t4\t84\t784\t1784\t2784\t7784\t168\t169\tKNAAAA\tVTIAAA\tVVVVxx\n7501\t5924\t1\t1\t1\t1\t1\t501\t1501\t2501\t7501\t2\t3\tNCAAAA\tWTIAAA\tAAAAxx\n7862\t5925\t0\t2\t2\t2\t62\t862\t1862\t2862\t7862\t124\t125\tKQAAAA\tXTIAAA\tHHHHxx\n8922\t5926\t0\t2\t2\t2\t22\t922\t922\t3922\t8922\t44\t45\tEFAAAA\tYTIAAA\tOOOOxx\n3857\t5927\t1\t1\t7\t17\t57\t857\t1857\t3857\t3857\t114\t115\tJSAAAA\tZTIAAA\tVVVVxx\n6393\t5928\t1\t1\t3\t13\t93\t393\t393\t1393\t6393\t186\t187\tXLAAAA\tAUIAAA\tAAAAxx\n506\t5929\t0\t2\t6\t6\t6\t506\t506\t506\t506\t12\t13\tMTAAAA\tBUIAAA\tHHHHxx\n4232\t5930\t0\t0\t2\t12\t32\t232\t232\t4232\t4232\t64\t65\tUGAAAA\tCUIAAA\tOOOOxx\n8991\t5931\t1\t3\t1\t11\t91\t991\t991\t3991\t8991\t182\t183\tVHAAAA\tDUIAAA\tVVVVxx\n8578\t5932\t0\t2\t8\t18\t78\t578\t578\t3578\t8578\t156\t157\tYRAAAA\tEUIAAA\tAAAAxx\n3235\t5933\t1\t3\t5\t15\t35\t235\t1235\t3235\t3235\t70\t71\tLUAAAA\tFUIAAA\tHHHHxx\n963\t5934\t1\t3\t3\t3\t63\t963\t963\t963\t963\t126\t127\tBLAAAA\tGUIAAA\tOOOOxx\n113\t5935\t1\t1\t3\t13\t13\t113\t113\t113\t113\t26\t27\tJEAAAA\tHUIAAA\tVVVVxx\n8234\t5936\t0\t2\t4\t14\t34\t234\t234\t3234\t8234\t68\t69\tSEAAAA\tIUIAAA\tAAAAxx\n2613\t5937\t1\t1\t3\t13\t13\t613\t613\t2613\t2613\t26\t27\tNWAAAA\tJUIAAA\tHHHHxx\n5540\t5938\t0\t0\t0\t0\t40\t540\t1540\t540\t5540\t80\t81\tCFAAAA\tKUIAAA\tOOOOxx\n9727\t5939\t1\t3\t7\t7\t27\t727\t1727\t4727\t9727\t54\t55\tDKAAAA\tLUIAAA\tVVVVxx\n2229\t5940\t1\t1\t9\t9\t29\t229\t229\t2229\t2229\t58\t59\tTHAAAA\tMUIAAA\tAAAAxx\n6242\t5941\t0\t2\t2\t2\t42\t242\t242\t1242\t6242\t84\t85\tCGAAAA\tNUIAAA\tHHHHxx\n2502\t5942\t0\t2\t2\t2\t2\t502\t502\t2502\t2502\t4\t5\tGSAAAA\tOUIAAA\tOOOOxx\n6212\t5943\t0\t0\t2\t12\t12\t212\t212\t1212\t6212\t24\t25\tYEAAAA\tPUIAAA\tVVVVxx\n3495\t5944\t1\t3\t5\t15\t95\t495\t1495\t3495\t3495\t190\t191\tLEAAAA\tQUIAAA\tAAAAxx\n2364\t5945\t0\t0\t4\t4\t64\t364\t364\t2364\t2364\t128\t129\tYMAAAA\tRUIAAA\tHHHHxx\n6777\t5946\t1\t1\t7\t17\t77\t777\t777\t1777\t6777\t154\t155\tRAAAAA\tSUIAAA\tOOOOxx\n9811\t5947\t1\t3\t1\t11\t11\t811\t1811\t4811\t9811\t22\t23\tJNAAAA\tTUIAAA\tVVVVxx\n1450\t5948\t0\t2\t0\t10\t50\t450\t1450\t1450\t1450\t100\t101\tUDAAAA\tUUIAAA\tAAAAxx\n5008\t5949\t0\t0\t8\t8\t8\t8\t1008\t8\t5008\t16\t17\tQKAAAA\tVUIAAA\tHHHHxx\n1318\t5950\t0\t2\t8\t18\t18\t318\t1318\t1318\t1318\t36\t37\tSYAAAA\tWUIAAA\tOOOOxx\n3373\t5951\t1\t1\t3\t13\t73\t373\t1373\t3373\t3373\t146\t147\tTZAAAA\tXUIAAA\tVVVVxx\n398\t5952\t0\t2\t8\t18\t98\t398\t398\t398\t398\t196\t197\tIPAAAA\tYUIAAA\tAAAAxx\n3804\t5953\t0\t0\t4\t4\t4\t804\t1804\t3804\t3804\t8\t9\tIQAAAA\tZUIAAA\tHHHHxx\n9148\t5954\t0\t0\t8\t8\t48\t148\t1148\t4148\t9148\t96\t97\tWNAAAA\tAVIAAA\tOOOOxx\n4382\t5955\t0\t2\t2\t2\t82\t382\t382\t4382\t4382\t164\t165\tOMAAAA\tBVIAAA\tVVVVxx\n4026\t5956\t0\t2\t6\t6\t26\t26\t26\t4026\t4026\t52\t53\tWYAAAA\tCVIAAA\tAAAAxx\n7804\t5957\t0\t0\t4\t4\t4\t804\t1804\t2804\t7804\t8\t9\tEOAAAA\tDVIAAA\tHHHHxx\n6839\t5958\t1\t3\t9\t19\t39\t839\t839\t1839\t6839\t78\t79\tBDAAAA\tEVIAAA\tOOOOxx\n3756\t5959\t0\t0\t6\t16\t56\t756\t1756\t3756\t3756\t112\t113\tMOAAAA\tFVIAAA\tVVVVxx\n6734\t5960\t0\t2\t4\t14\t34\t734\t734\t1734\t6734\t68\t69\tAZAAAA\tGVIAAA\tAAAAxx\n2228\t5961\t0\t0\t8\t8\t28\t228\t228\t2228\t2228\t56\t57\tSHAAAA\tHVIAAA\tHHHHxx\n3273\t5962\t1\t1\t3\t13\t73\t273\t1273\t3273\t3273\t146\t147\tXVAAAA\tIVIAAA\tOOOOxx\n3708\t5963\t0\t0\t8\t8\t8\t708\t1708\t3708\t3708\t16\t17\tQMAAAA\tJVIAAA\tVVVVxx\n4320\t5964\t0\t0\t0\t0\t20\t320\t320\t4320\t4320\t40\t41\tEKAAAA\tKVIAAA\tAAAAxx\n74\t5965\t0\t2\t4\t14\t74\t74\t74\t74\t74\t148\t149\tWCAAAA\tLVIAAA\tHHHHxx\n2520\t5966\t0\t0\t0\t0\t20\t520\t520\t2520\t2520\t40\t41\tYSAAAA\tMVIAAA\tOOOOxx\n9619\t5967\t1\t3\t9\t19\t19\t619\t1619\t4619\t9619\t38\t39\tZFAAAA\tNVIAAA\tVVVVxx\n1801\t5968\t1\t1\t1\t1\t1\t801\t1801\t1801\t1801\t2\t3\tHRAAAA\tOVIAAA\tAAAAxx\n6399\t5969\t1\t3\t9\t19\t99\t399\t399\t1399\t6399\t198\t199\tDMAAAA\tPVIAAA\tHHHHxx\n8313\t5970\t1\t1\t3\t13\t13\t313\t313\t3313\t8313\t26\t27\tTHAAAA\tQVIAAA\tOOOOxx\n7003\t5971\t1\t3\t3\t3\t3\t3\t1003\t2003\t7003\t6\t7\tJJAAAA\tRVIAAA\tVVVVxx\n329\t5972\t1\t1\t9\t9\t29\t329\t329\t329\t329\t58\t59\tRMAAAA\tSVIAAA\tAAAAxx\n9090\t5973\t0\t2\t0\t10\t90\t90\t1090\t4090\t9090\t180\t181\tQLAAAA\tTVIAAA\tHHHHxx\n2299\t5974\t1\t3\t9\t19\t99\t299\t299\t2299\t2299\t198\t199\tLKAAAA\tUVIAAA\tOOOOxx\n3925\t5975\t1\t1\t5\t5\t25\t925\t1925\t3925\t3925\t50\t51\tZUAAAA\tVVIAAA\tVVVVxx\n8145\t5976\t1\t1\t5\t5\t45\t145\t145\t3145\t8145\t90\t91\tHBAAAA\tWVIAAA\tAAAAxx\n8561\t5977\t1\t1\t1\t1\t61\t561\t561\t3561\t8561\t122\t123\tHRAAAA\tXVIAAA\tHHHHxx\n2797\t5978\t1\t1\t7\t17\t97\t797\t797\t2797\t2797\t194\t195\tPDAAAA\tYVIAAA\tOOOOxx\n1451\t5979\t1\t3\t1\t11\t51\t451\t1451\t1451\t1451\t102\t103\tVDAAAA\tZVIAAA\tVVVVxx\n7977\t5980\t1\t1\t7\t17\t77\t977\t1977\t2977\t7977\t154\t155\tVUAAAA\tAWIAAA\tAAAAxx\n112\t5981\t0\t0\t2\t12\t12\t112\t112\t112\t112\t24\t25\tIEAAAA\tBWIAAA\tHHHHxx\n5265\t5982\t1\t1\t5\t5\t65\t265\t1265\t265\t5265\t130\t131\tNUAAAA\tCWIAAA\tOOOOxx\n3819\t5983\t1\t3\t9\t19\t19\t819\t1819\t3819\t3819\t38\t39\tXQAAAA\tDWIAAA\tVVVVxx\n3648\t5984\t0\t0\t8\t8\t48\t648\t1648\t3648\t3648\t96\t97\tIKAAAA\tEWIAAA\tAAAAxx\n6306\t5985\t0\t2\t6\t6\t6\t306\t306\t1306\t6306\t12\t13\tOIAAAA\tFWIAAA\tHHHHxx\n2385\t5986\t1\t1\t5\t5\t85\t385\t385\t2385\t2385\t170\t171\tTNAAAA\tGWIAAA\tOOOOxx\n9084\t5987\t0\t0\t4\t4\t84\t84\t1084\t4084\t9084\t168\t169\tKLAAAA\tHWIAAA\tVVVVxx\n4499\t5988\t1\t3\t9\t19\t99\t499\t499\t4499\t4499\t198\t199\tBRAAAA\tIWIAAA\tAAAAxx\n1154\t5989\t0\t2\t4\t14\t54\t154\t1154\t1154\t1154\t108\t109\tKSAAAA\tJWIAAA\tHHHHxx\n6800\t5990\t0\t0\t0\t0\t0\t800\t800\t1800\t6800\t0\t1\tOBAAAA\tKWIAAA\tOOOOxx\n8049\t5991\t1\t1\t9\t9\t49\t49\t49\t3049\t8049\t98\t99\tPXAAAA\tLWIAAA\tVVVVxx\n3733\t5992\t1\t1\t3\t13\t33\t733\t1733\t3733\t3733\t66\t67\tPNAAAA\tMWIAAA\tAAAAxx\n8496\t5993\t0\t0\t6\t16\t96\t496\t496\t3496\t8496\t192\t193\tUOAAAA\tNWIAAA\tHHHHxx\n9952\t5994\t0\t0\t2\t12\t52\t952\t1952\t4952\t9952\t104\t105\tUSAAAA\tOWIAAA\tOOOOxx\n9792\t5995\t0\t0\t2\t12\t92\t792\t1792\t4792\t9792\t184\t185\tQMAAAA\tPWIAAA\tVVVVxx\n5081\t5996\t1\t1\t1\t1\t81\t81\t1081\t81\t5081\t162\t163\tLNAAAA\tQWIAAA\tAAAAxx\n7908\t5997\t0\t0\t8\t8\t8\t908\t1908\t2908\t7908\t16\t17\tESAAAA\tRWIAAA\tHHHHxx\n5398\t5998\t0\t2\t8\t18\t98\t398\t1398\t398\t5398\t196\t197\tQZAAAA\tSWIAAA\tOOOOxx\n8423\t5999\t1\t3\t3\t3\t23\t423\t423\t3423\t8423\t46\t47\tZLAAAA\tTWIAAA\tVVVVxx\n3362\t6000\t0\t2\t2\t2\t62\t362\t1362\t3362\t3362\t124\t125\tIZAAAA\tUWIAAA\tAAAAxx\n7767\t6001\t1\t3\t7\t7\t67\t767\t1767\t2767\t7767\t134\t135\tTMAAAA\tVWIAAA\tHHHHxx\n7063\t6002\t1\t3\t3\t3\t63\t63\t1063\t2063\t7063\t126\t127\tRLAAAA\tWWIAAA\tOOOOxx\n8350\t6003\t0\t2\t0\t10\t50\t350\t350\t3350\t8350\t100\t101\tEJAAAA\tXWIAAA\tVVVVxx\n6779\t6004\t1\t3\t9\t19\t79\t779\t779\t1779\t6779\t158\t159\tTAAAAA\tYWIAAA\tAAAAxx\n5742\t6005\t0\t2\t2\t2\t42\t742\t1742\t742\t5742\t84\t85\tWMAAAA\tZWIAAA\tHHHHxx\n9045\t6006\t1\t1\t5\t5\t45\t45\t1045\t4045\t9045\t90\t91\tXJAAAA\tAXIAAA\tOOOOxx\n8792\t6007\t0\t0\t2\t12\t92\t792\t792\t3792\t8792\t184\t185\tEAAAAA\tBXIAAA\tVVVVxx\n8160\t6008\t0\t0\t0\t0\t60\t160\t160\t3160\t8160\t120\t121\tWBAAAA\tCXIAAA\tAAAAxx\n3061\t6009\t1\t1\t1\t1\t61\t61\t1061\t3061\t3061\t122\t123\tTNAAAA\tDXIAAA\tHHHHxx\n4721\t6010\t1\t1\t1\t1\t21\t721\t721\t4721\t4721\t42\t43\tPZAAAA\tEXIAAA\tOOOOxx\n9817\t6011\t1\t1\t7\t17\t17\t817\t1817\t4817\t9817\t34\t35\tPNAAAA\tFXIAAA\tVVVVxx\n9257\t6012\t1\t1\t7\t17\t57\t257\t1257\t4257\t9257\t114\t115\tBSAAAA\tGXIAAA\tAAAAxx\n7779\t6013\t1\t3\t9\t19\t79\t779\t1779\t2779\t7779\t158\t159\tFNAAAA\tHXIAAA\tHHHHxx\n2663\t6014\t1\t3\t3\t3\t63\t663\t663\t2663\t2663\t126\t127\tLYAAAA\tIXIAAA\tOOOOxx\n3885\t6015\t1\t1\t5\t5\t85\t885\t1885\t3885\t3885\t170\t171\tLTAAAA\tJXIAAA\tVVVVxx\n9469\t6016\t1\t1\t9\t9\t69\t469\t1469\t4469\t9469\t138\t139\tFAAAAA\tKXIAAA\tAAAAxx\n6766\t6017\t0\t2\t6\t6\t66\t766\t766\t1766\t6766\t132\t133\tGAAAAA\tLXIAAA\tHHHHxx\n7173\t6018\t1\t1\t3\t13\t73\t173\t1173\t2173\t7173\t146\t147\tXPAAAA\tMXIAAA\tOOOOxx\n4709\t6019\t1\t1\t9\t9\t9\t709\t709\t4709\t4709\t18\t19\tDZAAAA\tNXIAAA\tVVVVxx\n4210\t6020\t0\t2\t0\t10\t10\t210\t210\t4210\t4210\t20\t21\tYFAAAA\tOXIAAA\tAAAAxx\n3715\t6021\t1\t3\t5\t15\t15\t715\t1715\t3715\t3715\t30\t31\tXMAAAA\tPXIAAA\tHHHHxx\n5089\t6022\t1\t1\t9\t9\t89\t89\t1089\t89\t5089\t178\t179\tTNAAAA\tQXIAAA\tOOOOxx\n1639\t6023\t1\t3\t9\t19\t39\t639\t1639\t1639\t1639\t78\t79\tBLAAAA\tRXIAAA\tVVVVxx\n5757\t6024\t1\t1\t7\t17\t57\t757\t1757\t757\t5757\t114\t115\tLNAAAA\tSXIAAA\tAAAAxx\n3545\t6025\t1\t1\t5\t5\t45\t545\t1545\t3545\t3545\t90\t91\tJGAAAA\tTXIAAA\tHHHHxx\n709\t6026\t1\t1\t9\t9\t9\t709\t709\t709\t709\t18\t19\tHBAAAA\tUXIAAA\tOOOOxx\n6519\t6027\t1\t3\t9\t19\t19\t519\t519\t1519\t6519\t38\t39\tTQAAAA\tVXIAAA\tVVVVxx\n4341\t6028\t1\t1\t1\t1\t41\t341\t341\t4341\t4341\t82\t83\tZKAAAA\tWXIAAA\tAAAAxx\n2381\t6029\t1\t1\t1\t1\t81\t381\t381\t2381\t2381\t162\t163\tPNAAAA\tXXIAAA\tHHHHxx\n7215\t6030\t1\t3\t5\t15\t15\t215\t1215\t2215\t7215\t30\t31\tNRAAAA\tYXIAAA\tOOOOxx\n9323\t6031\t1\t3\t3\t3\t23\t323\t1323\t4323\t9323\t46\t47\tPUAAAA\tZXIAAA\tVVVVxx\n3593\t6032\t1\t1\t3\t13\t93\t593\t1593\t3593\t3593\t186\t187\tFIAAAA\tAYIAAA\tAAAAxx\n3123\t6033\t1\t3\t3\t3\t23\t123\t1123\t3123\t3123\t46\t47\tDQAAAA\tBYIAAA\tHHHHxx\n8673\t6034\t1\t1\t3\t13\t73\t673\t673\t3673\t8673\t146\t147\tPVAAAA\tCYIAAA\tOOOOxx\n5094\t6035\t0\t2\t4\t14\t94\t94\t1094\t94\t5094\t188\t189\tYNAAAA\tDYIAAA\tVVVVxx\n6477\t6036\t1\t1\t7\t17\t77\t477\t477\t1477\t6477\t154\t155\tDPAAAA\tEYIAAA\tAAAAxx\n9734\t6037\t0\t2\t4\t14\t34\t734\t1734\t4734\t9734\t68\t69\tKKAAAA\tFYIAAA\tHHHHxx\n2998\t6038\t0\t2\t8\t18\t98\t998\t998\t2998\t2998\t196\t197\tILAAAA\tGYIAAA\tOOOOxx\n7807\t6039\t1\t3\t7\t7\t7\t807\t1807\t2807\t7807\t14\t15\tHOAAAA\tHYIAAA\tVVVVxx\n5739\t6040\t1\t3\t9\t19\t39\t739\t1739\t739\t5739\t78\t79\tTMAAAA\tIYIAAA\tAAAAxx\n138\t6041\t0\t2\t8\t18\t38\t138\t138\t138\t138\t76\t77\tIFAAAA\tJYIAAA\tHHHHxx\n2403\t6042\t1\t3\t3\t3\t3\t403\t403\t2403\t2403\t6\t7\tLOAAAA\tKYIAAA\tOOOOxx\n2484\t6043\t0\t0\t4\t4\t84\t484\t484\t2484\t2484\t168\t169\tORAAAA\tLYIAAA\tVVVVxx\n2805\t6044\t1\t1\t5\t5\t5\t805\t805\t2805\t2805\t10\t11\tXDAAAA\tMYIAAA\tAAAAxx\n5189\t6045\t1\t1\t9\t9\t89\t189\t1189\t189\t5189\t178\t179\tPRAAAA\tNYIAAA\tHHHHxx\n8336\t6046\t0\t0\t6\t16\t36\t336\t336\t3336\t8336\t72\t73\tQIAAAA\tOYIAAA\tOOOOxx\n5241\t6047\t1\t1\t1\t1\t41\t241\t1241\t241\t5241\t82\t83\tPTAAAA\tPYIAAA\tVVVVxx\n2612\t6048\t0\t0\t2\t12\t12\t612\t612\t2612\t2612\t24\t25\tMWAAAA\tQYIAAA\tAAAAxx\n2571\t6049\t1\t3\t1\t11\t71\t571\t571\t2571\t2571\t142\t143\tXUAAAA\tRYIAAA\tHHHHxx\n926\t6050\t0\t2\t6\t6\t26\t926\t926\t926\t926\t52\t53\tQJAAAA\tSYIAAA\tOOOOxx\n337\t6051\t1\t1\t7\t17\t37\t337\t337\t337\t337\t74\t75\tZMAAAA\tTYIAAA\tVVVVxx\n2821\t6052\t1\t1\t1\t1\t21\t821\t821\t2821\t2821\t42\t43\tNEAAAA\tUYIAAA\tAAAAxx\n2658\t6053\t0\t2\t8\t18\t58\t658\t658\t2658\t2658\t116\t117\tGYAAAA\tVYIAAA\tHHHHxx\n9054\t6054\t0\t2\t4\t14\t54\t54\t1054\t4054\t9054\t108\t109\tGKAAAA\tWYIAAA\tOOOOxx\n5492\t6055\t0\t0\t2\t12\t92\t492\t1492\t492\t5492\t184\t185\tGDAAAA\tXYIAAA\tVVVVxx\n7313\t6056\t1\t1\t3\t13\t13\t313\t1313\t2313\t7313\t26\t27\tHVAAAA\tYYIAAA\tAAAAxx\n75\t6057\t1\t3\t5\t15\t75\t75\t75\t75\t75\t150\t151\tXCAAAA\tZYIAAA\tHHHHxx\n5489\t6058\t1\t1\t9\t9\t89\t489\t1489\t489\t5489\t178\t179\tDDAAAA\tAZIAAA\tOOOOxx\n8413\t6059\t1\t1\t3\t13\t13\t413\t413\t3413\t8413\t26\t27\tPLAAAA\tBZIAAA\tVVVVxx\n3693\t6060\t1\t1\t3\t13\t93\t693\t1693\t3693\t3693\t186\t187\tBMAAAA\tCZIAAA\tAAAAxx\n9820\t6061\t0\t0\t0\t0\t20\t820\t1820\t4820\t9820\t40\t41\tSNAAAA\tDZIAAA\tHHHHxx\n8157\t6062\t1\t1\t7\t17\t57\t157\t157\t3157\t8157\t114\t115\tTBAAAA\tEZIAAA\tOOOOxx\n4161\t6063\t1\t1\t1\t1\t61\t161\t161\t4161\t4161\t122\t123\tBEAAAA\tFZIAAA\tVVVVxx\n8339\t6064\t1\t3\t9\t19\t39\t339\t339\t3339\t8339\t78\t79\tTIAAAA\tGZIAAA\tAAAAxx\n4141\t6065\t1\t1\t1\t1\t41\t141\t141\t4141\t4141\t82\t83\tHDAAAA\tHZIAAA\tHHHHxx\n9001\t6066\t1\t1\t1\t1\t1\t1\t1001\t4001\t9001\t2\t3\tFIAAAA\tIZIAAA\tOOOOxx\n8247\t6067\t1\t3\t7\t7\t47\t247\t247\t3247\t8247\t94\t95\tFFAAAA\tJZIAAA\tVVVVxx\n1182\t6068\t0\t2\t2\t2\t82\t182\t1182\t1182\t1182\t164\t165\tMTAAAA\tKZIAAA\tAAAAxx\n9876\t6069\t0\t0\t6\t16\t76\t876\t1876\t4876\t9876\t152\t153\tWPAAAA\tLZIAAA\tHHHHxx\n4302\t6070\t0\t2\t2\t2\t2\t302\t302\t4302\t4302\t4\t5\tMJAAAA\tMZIAAA\tOOOOxx\n6674\t6071\t0\t2\t4\t14\t74\t674\t674\t1674\t6674\t148\t149\tSWAAAA\tNZIAAA\tVVVVxx\n4214\t6072\t0\t2\t4\t14\t14\t214\t214\t4214\t4214\t28\t29\tCGAAAA\tOZIAAA\tAAAAxx\n5584\t6073\t0\t0\t4\t4\t84\t584\t1584\t584\t5584\t168\t169\tUGAAAA\tPZIAAA\tHHHHxx\n265\t6074\t1\t1\t5\t5\t65\t265\t265\t265\t265\t130\t131\tFKAAAA\tQZIAAA\tOOOOxx\n9207\t6075\t1\t3\t7\t7\t7\t207\t1207\t4207\t9207\t14\t15\tDQAAAA\tRZIAAA\tVVVVxx\n9434\t6076\t0\t2\t4\t14\t34\t434\t1434\t4434\t9434\t68\t69\tWYAAAA\tSZIAAA\tAAAAxx\n2921\t6077\t1\t1\t1\t1\t21\t921\t921\t2921\t2921\t42\t43\tJIAAAA\tTZIAAA\tHHHHxx\n9355\t6078\t1\t3\t5\t15\t55\t355\t1355\t4355\t9355\t110\t111\tVVAAAA\tUZIAAA\tOOOOxx\n8538\t6079\t0\t2\t8\t18\t38\t538\t538\t3538\t8538\t76\t77\tKQAAAA\tVZIAAA\tVVVVxx\n4559\t6080\t1\t3\t9\t19\t59\t559\t559\t4559\t4559\t118\t119\tJTAAAA\tWZIAAA\tAAAAxx\n9175\t6081\t1\t3\t5\t15\t75\t175\t1175\t4175\t9175\t150\t151\tXOAAAA\tXZIAAA\tHHHHxx\n4489\t6082\t1\t1\t9\t9\t89\t489\t489\t4489\t4489\t178\t179\tRQAAAA\tYZIAAA\tOOOOxx\n1485\t6083\t1\t1\t5\t5\t85\t485\t1485\t1485\t1485\t170\t171\tDFAAAA\tZZIAAA\tVVVVxx\n8853\t6084\t1\t1\t3\t13\t53\t853\t853\t3853\t8853\t106\t107\tNCAAAA\tAAJAAA\tAAAAxx\n9143\t6085\t1\t3\t3\t3\t43\t143\t1143\t4143\t9143\t86\t87\tRNAAAA\tBAJAAA\tHHHHxx\n9551\t6086\t1\t3\t1\t11\t51\t551\t1551\t4551\t9551\t102\t103\tJDAAAA\tCAJAAA\tOOOOxx\n49\t6087\t1\t1\t9\t9\t49\t49\t49\t49\t49\t98\t99\tXBAAAA\tDAJAAA\tVVVVxx\n8351\t6088\t1\t3\t1\t11\t51\t351\t351\t3351\t8351\t102\t103\tFJAAAA\tEAJAAA\tAAAAxx\n9748\t6089\t0\t0\t8\t8\t48\t748\t1748\t4748\t9748\t96\t97\tYKAAAA\tFAJAAA\tHHHHxx\n4536\t6090\t0\t0\t6\t16\t36\t536\t536\t4536\t4536\t72\t73\tMSAAAA\tGAJAAA\tOOOOxx\n930\t6091\t0\t2\t0\t10\t30\t930\t930\t930\t930\t60\t61\tUJAAAA\tHAJAAA\tVVVVxx\n2206\t6092\t0\t2\t6\t6\t6\t206\t206\t2206\t2206\t12\t13\tWGAAAA\tIAJAAA\tAAAAxx\n8004\t6093\t0\t0\t4\t4\t4\t4\t4\t3004\t8004\t8\t9\tWVAAAA\tJAJAAA\tHHHHxx\n219\t6094\t1\t3\t9\t19\t19\t219\t219\t219\t219\t38\t39\tLIAAAA\tKAJAAA\tOOOOxx\n2724\t6095\t0\t0\t4\t4\t24\t724\t724\t2724\t2724\t48\t49\tUAAAAA\tLAJAAA\tVVVVxx\n4868\t6096\t0\t0\t8\t8\t68\t868\t868\t4868\t4868\t136\t137\tGFAAAA\tMAJAAA\tAAAAxx\n5952\t6097\t0\t0\t2\t12\t52\t952\t1952\t952\t5952\t104\t105\tYUAAAA\tNAJAAA\tHHHHxx\n2094\t6098\t0\t2\t4\t14\t94\t94\t94\t2094\t2094\t188\t189\tOCAAAA\tOAJAAA\tOOOOxx\n5707\t6099\t1\t3\t7\t7\t7\t707\t1707\t707\t5707\t14\t15\tNLAAAA\tPAJAAA\tVVVVxx\n5200\t6100\t0\t0\t0\t0\t0\t200\t1200\t200\t5200\t0\t1\tASAAAA\tQAJAAA\tAAAAxx\n967\t6101\t1\t3\t7\t7\t67\t967\t967\t967\t967\t134\t135\tFLAAAA\tRAJAAA\tHHHHxx\n1982\t6102\t0\t2\t2\t2\t82\t982\t1982\t1982\t1982\t164\t165\tGYAAAA\tSAJAAA\tOOOOxx\n3410\t6103\t0\t2\t0\t10\t10\t410\t1410\t3410\t3410\t20\t21\tEBAAAA\tTAJAAA\tVVVVxx\n174\t6104\t0\t2\t4\t14\t74\t174\t174\t174\t174\t148\t149\tSGAAAA\tUAJAAA\tAAAAxx\n9217\t6105\t1\t1\t7\t17\t17\t217\t1217\t4217\t9217\t34\t35\tNQAAAA\tVAJAAA\tHHHHxx\n9103\t6106\t1\t3\t3\t3\t3\t103\t1103\t4103\t9103\t6\t7\tDMAAAA\tWAJAAA\tOOOOxx\n868\t6107\t0\t0\t8\t8\t68\t868\t868\t868\t868\t136\t137\tKHAAAA\tXAJAAA\tVVVVxx\n8261\t6108\t1\t1\t1\t1\t61\t261\t261\t3261\t8261\t122\t123\tTFAAAA\tYAJAAA\tAAAAxx\n2720\t6109\t0\t0\t0\t0\t20\t720\t720\t2720\t2720\t40\t41\tQAAAAA\tZAJAAA\tHHHHxx\n2999\t6110\t1\t3\t9\t19\t99\t999\t999\t2999\t2999\t198\t199\tJLAAAA\tABJAAA\tOOOOxx\n769\t6111\t1\t1\t9\t9\t69\t769\t769\t769\t769\t138\t139\tPDAAAA\tBBJAAA\tVVVVxx\n4533\t6112\t1\t1\t3\t13\t33\t533\t533\t4533\t4533\t66\t67\tJSAAAA\tCBJAAA\tAAAAxx\n2030\t6113\t0\t2\t0\t10\t30\t30\t30\t2030\t2030\t60\t61\tCAAAAA\tDBJAAA\tHHHHxx\n5824\t6114\t0\t0\t4\t4\t24\t824\t1824\t824\t5824\t48\t49\tAQAAAA\tEBJAAA\tOOOOxx\n2328\t6115\t0\t0\t8\t8\t28\t328\t328\t2328\t2328\t56\t57\tOLAAAA\tFBJAAA\tVVVVxx\n9970\t6116\t0\t2\t0\t10\t70\t970\t1970\t4970\t9970\t140\t141\tMTAAAA\tGBJAAA\tAAAAxx\n3192\t6117\t0\t0\t2\t12\t92\t192\t1192\t3192\t3192\t184\t185\tUSAAAA\tHBJAAA\tHHHHxx\n3387\t6118\t1\t3\t7\t7\t87\t387\t1387\t3387\t3387\t174\t175\tHAAAAA\tIBJAAA\tOOOOxx\n1936\t6119\t0\t0\t6\t16\t36\t936\t1936\t1936\t1936\t72\t73\tMWAAAA\tJBJAAA\tVVVVxx\n6934\t6120\t0\t2\t4\t14\t34\t934\t934\t1934\t6934\t68\t69\tSGAAAA\tKBJAAA\tAAAAxx\n5615\t6121\t1\t3\t5\t15\t15\t615\t1615\t615\t5615\t30\t31\tZHAAAA\tLBJAAA\tHHHHxx\n2241\t6122\t1\t1\t1\t1\t41\t241\t241\t2241\t2241\t82\t83\tFIAAAA\tMBJAAA\tOOOOxx\n1842\t6123\t0\t2\t2\t2\t42\t842\t1842\t1842\t1842\t84\t85\tWSAAAA\tNBJAAA\tVVVVxx\n8044\t6124\t0\t0\t4\t4\t44\t44\t44\t3044\t8044\t88\t89\tKXAAAA\tOBJAAA\tAAAAxx\n8902\t6125\t0\t2\t2\t2\t2\t902\t902\t3902\t8902\t4\t5\tKEAAAA\tPBJAAA\tHHHHxx\n4519\t6126\t1\t3\t9\t19\t19\t519\t519\t4519\t4519\t38\t39\tVRAAAA\tQBJAAA\tOOOOxx\n492\t6127\t0\t0\t2\t12\t92\t492\t492\t492\t492\t184\t185\tYSAAAA\tRBJAAA\tVVVVxx\n2694\t6128\t0\t2\t4\t14\t94\t694\t694\t2694\t2694\t188\t189\tQZAAAA\tSBJAAA\tAAAAxx\n5861\t6129\t1\t1\t1\t1\t61\t861\t1861\t861\t5861\t122\t123\tLRAAAA\tTBJAAA\tHHHHxx\n2104\t6130\t0\t0\t4\t4\t4\t104\t104\t2104\t2104\t8\t9\tYCAAAA\tUBJAAA\tOOOOxx\n5376\t6131\t0\t0\t6\t16\t76\t376\t1376\t376\t5376\t152\t153\tUYAAAA\tVBJAAA\tVVVVxx\n3147\t6132\t1\t3\t7\t7\t47\t147\t1147\t3147\t3147\t94\t95\tBRAAAA\tWBJAAA\tAAAAxx\n9880\t6133\t0\t0\t0\t0\t80\t880\t1880\t4880\t9880\t160\t161\tAQAAAA\tXBJAAA\tHHHHxx\n6171\t6134\t1\t3\t1\t11\t71\t171\t171\t1171\t6171\t142\t143\tJDAAAA\tYBJAAA\tOOOOxx\n1850\t6135\t0\t2\t0\t10\t50\t850\t1850\t1850\t1850\t100\t101\tETAAAA\tZBJAAA\tVVVVxx\n1775\t6136\t1\t3\t5\t15\t75\t775\t1775\t1775\t1775\t150\t151\tHQAAAA\tACJAAA\tAAAAxx\n9261\t6137\t1\t1\t1\t1\t61\t261\t1261\t4261\t9261\t122\t123\tFSAAAA\tBCJAAA\tHHHHxx\n9648\t6138\t0\t0\t8\t8\t48\t648\t1648\t4648\t9648\t96\t97\tCHAAAA\tCCJAAA\tOOOOxx\n7846\t6139\t0\t2\t6\t6\t46\t846\t1846\t2846\t7846\t92\t93\tUPAAAA\tDCJAAA\tVVVVxx\n1446\t6140\t0\t2\t6\t6\t46\t446\t1446\t1446\t1446\t92\t93\tQDAAAA\tECJAAA\tAAAAxx\n3139\t6141\t1\t3\t9\t19\t39\t139\t1139\t3139\t3139\t78\t79\tTQAAAA\tFCJAAA\tHHHHxx\n6142\t6142\t0\t2\t2\t2\t42\t142\t142\t1142\t6142\t84\t85\tGCAAAA\tGCJAAA\tOOOOxx\n5812\t6143\t0\t0\t2\t12\t12\t812\t1812\t812\t5812\t24\t25\tOPAAAA\tHCJAAA\tVVVVxx\n6728\t6144\t0\t0\t8\t8\t28\t728\t728\t1728\t6728\t56\t57\tUYAAAA\tICJAAA\tAAAAxx\n4428\t6145\t0\t0\t8\t8\t28\t428\t428\t4428\t4428\t56\t57\tIOAAAA\tJCJAAA\tHHHHxx\n502\t6146\t0\t2\t2\t2\t2\t502\t502\t502\t502\t4\t5\tITAAAA\tKCJAAA\tOOOOxx\n2363\t6147\t1\t3\t3\t3\t63\t363\t363\t2363\t2363\t126\t127\tXMAAAA\tLCJAAA\tVVVVxx\n3808\t6148\t0\t0\t8\t8\t8\t808\t1808\t3808\t3808\t16\t17\tMQAAAA\tMCJAAA\tAAAAxx\n1010\t6149\t0\t2\t0\t10\t10\t10\t1010\t1010\t1010\t20\t21\tWMAAAA\tNCJAAA\tHHHHxx\n9565\t6150\t1\t1\t5\t5\t65\t565\t1565\t4565\t9565\t130\t131\tXDAAAA\tOCJAAA\tOOOOxx\n1587\t6151\t1\t3\t7\t7\t87\t587\t1587\t1587\t1587\t174\t175\tBJAAAA\tPCJAAA\tVVVVxx\n1474\t6152\t0\t2\t4\t14\t74\t474\t1474\t1474\t1474\t148\t149\tSEAAAA\tQCJAAA\tAAAAxx\n6215\t6153\t1\t3\t5\t15\t15\t215\t215\t1215\t6215\t30\t31\tBFAAAA\tRCJAAA\tHHHHxx\n2395\t6154\t1\t3\t5\t15\t95\t395\t395\t2395\t2395\t190\t191\tDOAAAA\tSCJAAA\tOOOOxx\n8753\t6155\t1\t1\t3\t13\t53\t753\t753\t3753\t8753\t106\t107\tRYAAAA\tTCJAAA\tVVVVxx\n2446\t6156\t0\t2\t6\t6\t46\t446\t446\t2446\t2446\t92\t93\tCQAAAA\tUCJAAA\tAAAAxx\n60\t6157\t0\t0\t0\t0\t60\t60\t60\t60\t60\t120\t121\tICAAAA\tVCJAAA\tHHHHxx\n982\t6158\t0\t2\t2\t2\t82\t982\t982\t982\t982\t164\t165\tULAAAA\tWCJAAA\tOOOOxx\n6489\t6159\t1\t1\t9\t9\t89\t489\t489\t1489\t6489\t178\t179\tPPAAAA\tXCJAAA\tVVVVxx\n5334\t6160\t0\t2\t4\t14\t34\t334\t1334\t334\t5334\t68\t69\tEXAAAA\tYCJAAA\tAAAAxx\n8540\t6161\t0\t0\t0\t0\t40\t540\t540\t3540\t8540\t80\t81\tMQAAAA\tZCJAAA\tHHHHxx\n490\t6162\t0\t2\t0\t10\t90\t490\t490\t490\t490\t180\t181\tWSAAAA\tADJAAA\tOOOOxx\n6763\t6163\t1\t3\t3\t3\t63\t763\t763\t1763\t6763\t126\t127\tDAAAAA\tBDJAAA\tVVVVxx\n8273\t6164\t1\t1\t3\t13\t73\t273\t273\t3273\t8273\t146\t147\tFGAAAA\tCDJAAA\tAAAAxx\n8327\t6165\t1\t3\t7\t7\t27\t327\t327\t3327\t8327\t54\t55\tHIAAAA\tDDJAAA\tHHHHxx\n8541\t6166\t1\t1\t1\t1\t41\t541\t541\t3541\t8541\t82\t83\tNQAAAA\tEDJAAA\tOOOOxx\n3459\t6167\t1\t3\t9\t19\t59\t459\t1459\t3459\t3459\t118\t119\tBDAAAA\tFDJAAA\tVVVVxx\n5557\t6168\t1\t1\t7\t17\t57\t557\t1557\t557\t5557\t114\t115\tTFAAAA\tGDJAAA\tAAAAxx\n158\t6169\t0\t2\t8\t18\t58\t158\t158\t158\t158\t116\t117\tCGAAAA\tHDJAAA\tHHHHxx\n1741\t6170\t1\t1\t1\t1\t41\t741\t1741\t1741\t1741\t82\t83\tZOAAAA\tIDJAAA\tOOOOxx\n8385\t6171\t1\t1\t5\t5\t85\t385\t385\t3385\t8385\t170\t171\tNKAAAA\tJDJAAA\tVVVVxx\n617\t6172\t1\t1\t7\t17\t17\t617\t617\t617\t617\t34\t35\tTXAAAA\tKDJAAA\tAAAAxx\n3560\t6173\t0\t0\t0\t0\t60\t560\t1560\t3560\t3560\t120\t121\tYGAAAA\tLDJAAA\tHHHHxx\n5216\t6174\t0\t0\t6\t16\t16\t216\t1216\t216\t5216\t32\t33\tQSAAAA\tMDJAAA\tOOOOxx\n8443\t6175\t1\t3\t3\t3\t43\t443\t443\t3443\t8443\t86\t87\tTMAAAA\tNDJAAA\tVVVVxx\n2700\t6176\t0\t0\t0\t0\t0\t700\t700\t2700\t2700\t0\t1\tWZAAAA\tODJAAA\tAAAAxx\n3661\t6177\t1\t1\t1\t1\t61\t661\t1661\t3661\t3661\t122\t123\tVKAAAA\tPDJAAA\tHHHHxx\n4875\t6178\t1\t3\t5\t15\t75\t875\t875\t4875\t4875\t150\t151\tNFAAAA\tQDJAAA\tOOOOxx\n6721\t6179\t1\t1\t1\t1\t21\t721\t721\t1721\t6721\t42\t43\tNYAAAA\tRDJAAA\tVVVVxx\n3659\t6180\t1\t3\t9\t19\t59\t659\t1659\t3659\t3659\t118\t119\tTKAAAA\tSDJAAA\tAAAAxx\n8944\t6181\t0\t0\t4\t4\t44\t944\t944\t3944\t8944\t88\t89\tAGAAAA\tTDJAAA\tHHHHxx\n9133\t6182\t1\t1\t3\t13\t33\t133\t1133\t4133\t9133\t66\t67\tHNAAAA\tUDJAAA\tOOOOxx\n9882\t6183\t0\t2\t2\t2\t82\t882\t1882\t4882\t9882\t164\t165\tCQAAAA\tVDJAAA\tVVVVxx\n2102\t6184\t0\t2\t2\t2\t2\t102\t102\t2102\t2102\t4\t5\tWCAAAA\tWDJAAA\tAAAAxx\n9445\t6185\t1\t1\t5\t5\t45\t445\t1445\t4445\t9445\t90\t91\tHZAAAA\tXDJAAA\tHHHHxx\n5559\t6186\t1\t3\t9\t19\t59\t559\t1559\t559\t5559\t118\t119\tVFAAAA\tYDJAAA\tOOOOxx\n6096\t6187\t0\t0\t6\t16\t96\t96\t96\t1096\t6096\t192\t193\tMAAAAA\tZDJAAA\tVVVVxx\n9336\t6188\t0\t0\t6\t16\t36\t336\t1336\t4336\t9336\t72\t73\tCVAAAA\tAEJAAA\tAAAAxx\n2162\t6189\t0\t2\t2\t2\t62\t162\t162\t2162\t2162\t124\t125\tEFAAAA\tBEJAAA\tHHHHxx\n7459\t6190\t1\t3\t9\t19\t59\t459\t1459\t2459\t7459\t118\t119\tXAAAAA\tCEJAAA\tOOOOxx\n3248\t6191\t0\t0\t8\t8\t48\t248\t1248\t3248\t3248\t96\t97\tYUAAAA\tDEJAAA\tVVVVxx\n9539\t6192\t1\t3\t9\t19\t39\t539\t1539\t4539\t9539\t78\t79\tXCAAAA\tEEJAAA\tAAAAxx\n4449\t6193\t1\t1\t9\t9\t49\t449\t449\t4449\t4449\t98\t99\tDPAAAA\tFEJAAA\tHHHHxx\n2809\t6194\t1\t1\t9\t9\t9\t809\t809\t2809\t2809\t18\t19\tBEAAAA\tGEJAAA\tOOOOxx\n7058\t6195\t0\t2\t8\t18\t58\t58\t1058\t2058\t7058\t116\t117\tMLAAAA\tHEJAAA\tVVVVxx\n3512\t6196\t0\t0\t2\t12\t12\t512\t1512\t3512\t3512\t24\t25\tCFAAAA\tIEJAAA\tAAAAxx\n2802\t6197\t0\t2\t2\t2\t2\t802\t802\t2802\t2802\t4\t5\tUDAAAA\tJEJAAA\tHHHHxx\n6289\t6198\t1\t1\t9\t9\t89\t289\t289\t1289\t6289\t178\t179\tXHAAAA\tKEJAAA\tOOOOxx\n1947\t6199\t1\t3\t7\t7\t47\t947\t1947\t1947\t1947\t94\t95\tXWAAAA\tLEJAAA\tVVVVxx\n9572\t6200\t0\t0\t2\t12\t72\t572\t1572\t4572\t9572\t144\t145\tEEAAAA\tMEJAAA\tAAAAxx\n2356\t6201\t0\t0\t6\t16\t56\t356\t356\t2356\t2356\t112\t113\tQMAAAA\tNEJAAA\tHHHHxx\n3039\t6202\t1\t3\t9\t19\t39\t39\t1039\t3039\t3039\t78\t79\tXMAAAA\tOEJAAA\tOOOOxx\n9452\t6203\t0\t0\t2\t12\t52\t452\t1452\t4452\t9452\t104\t105\tOZAAAA\tPEJAAA\tVVVVxx\n6328\t6204\t0\t0\t8\t8\t28\t328\t328\t1328\t6328\t56\t57\tKJAAAA\tQEJAAA\tAAAAxx\n7661\t6205\t1\t1\t1\t1\t61\t661\t1661\t2661\t7661\t122\t123\tRIAAAA\tREJAAA\tHHHHxx\n2566\t6206\t0\t2\t6\t6\t66\t566\t566\t2566\t2566\t132\t133\tSUAAAA\tSEJAAA\tOOOOxx\n6095\t6207\t1\t3\t5\t15\t95\t95\t95\t1095\t6095\t190\t191\tLAAAAA\tTEJAAA\tVVVVxx\n6367\t6208\t1\t3\t7\t7\t67\t367\t367\t1367\t6367\t134\t135\tXKAAAA\tUEJAAA\tAAAAxx\n3368\t6209\t0\t0\t8\t8\t68\t368\t1368\t3368\t3368\t136\t137\tOZAAAA\tVEJAAA\tHHHHxx\n5567\t6210\t1\t3\t7\t7\t67\t567\t1567\t567\t5567\t134\t135\tDGAAAA\tWEJAAA\tOOOOxx\n9834\t6211\t0\t2\t4\t14\t34\t834\t1834\t4834\t9834\t68\t69\tGOAAAA\tXEJAAA\tVVVVxx\n9695\t6212\t1\t3\t5\t15\t95\t695\t1695\t4695\t9695\t190\t191\tXIAAAA\tYEJAAA\tAAAAxx\n7291\t6213\t1\t3\t1\t11\t91\t291\t1291\t2291\t7291\t182\t183\tLUAAAA\tZEJAAA\tHHHHxx\n4806\t6214\t0\t2\t6\t6\t6\t806\t806\t4806\t4806\t12\t13\tWCAAAA\tAFJAAA\tOOOOxx\n2000\t6215\t0\t0\t0\t0\t0\t0\t0\t2000\t2000\t0\t1\tYYAAAA\tBFJAAA\tVVVVxx\n6817\t6216\t1\t1\t7\t17\t17\t817\t817\t1817\t6817\t34\t35\tFCAAAA\tCFJAAA\tAAAAxx\n8487\t6217\t1\t3\t7\t7\t87\t487\t487\t3487\t8487\t174\t175\tLOAAAA\tDFJAAA\tHHHHxx\n3245\t6218\t1\t1\t5\t5\t45\t245\t1245\t3245\t3245\t90\t91\tVUAAAA\tEFJAAA\tOOOOxx\n632\t6219\t0\t0\t2\t12\t32\t632\t632\t632\t632\t64\t65\tIYAAAA\tFFJAAA\tVVVVxx\n8067\t6220\t1\t3\t7\t7\t67\t67\t67\t3067\t8067\t134\t135\tHYAAAA\tGFJAAA\tAAAAxx\n7140\t6221\t0\t0\t0\t0\t40\t140\t1140\t2140\t7140\t80\t81\tQOAAAA\tHFJAAA\tHHHHxx\n6802\t6222\t0\t2\t2\t2\t2\t802\t802\t1802\t6802\t4\t5\tQBAAAA\tIFJAAA\tOOOOxx\n3980\t6223\t0\t0\t0\t0\t80\t980\t1980\t3980\t3980\t160\t161\tCXAAAA\tJFJAAA\tVVVVxx\n1321\t6224\t1\t1\t1\t1\t21\t321\t1321\t1321\t1321\t42\t43\tVYAAAA\tKFJAAA\tAAAAxx\n2273\t6225\t1\t1\t3\t13\t73\t273\t273\t2273\t2273\t146\t147\tLJAAAA\tLFJAAA\tHHHHxx\n6787\t6226\t1\t3\t7\t7\t87\t787\t787\t1787\t6787\t174\t175\tBBAAAA\tMFJAAA\tOOOOxx\n9480\t6227\t0\t0\t0\t0\t80\t480\t1480\t4480\t9480\t160\t161\tQAAAAA\tNFJAAA\tVVVVxx\n9404\t6228\t0\t0\t4\t4\t4\t404\t1404\t4404\t9404\t8\t9\tSXAAAA\tOFJAAA\tAAAAxx\n3914\t6229\t0\t2\t4\t14\t14\t914\t1914\t3914\t3914\t28\t29\tOUAAAA\tPFJAAA\tHHHHxx\n5507\t6230\t1\t3\t7\t7\t7\t507\t1507\t507\t5507\t14\t15\tVDAAAA\tQFJAAA\tOOOOxx\n1813\t6231\t1\t1\t3\t13\t13\t813\t1813\t1813\t1813\t26\t27\tTRAAAA\tRFJAAA\tVVVVxx\n1999\t6232\t1\t3\t9\t19\t99\t999\t1999\t1999\t1999\t198\t199\tXYAAAA\tSFJAAA\tAAAAxx\n3848\t6233\t0\t0\t8\t8\t48\t848\t1848\t3848\t3848\t96\t97\tASAAAA\tTFJAAA\tHHHHxx\n9693\t6234\t1\t1\t3\t13\t93\t693\t1693\t4693\t9693\t186\t187\tVIAAAA\tUFJAAA\tOOOOxx\n1353\t6235\t1\t1\t3\t13\t53\t353\t1353\t1353\t1353\t106\t107\tBAAAAA\tVFJAAA\tVVVVxx\n7218\t6236\t0\t2\t8\t18\t18\t218\t1218\t2218\t7218\t36\t37\tQRAAAA\tWFJAAA\tAAAAxx\n8223\t6237\t1\t3\t3\t3\t23\t223\t223\t3223\t8223\t46\t47\tHEAAAA\tXFJAAA\tHHHHxx\n9982\t6238\t0\t2\t2\t2\t82\t982\t1982\t4982\t9982\t164\t165\tYTAAAA\tYFJAAA\tOOOOxx\n8799\t6239\t1\t3\t9\t19\t99\t799\t799\t3799\t8799\t198\t199\tLAAAAA\tZFJAAA\tVVVVxx\n8929\t6240\t1\t1\t9\t9\t29\t929\t929\t3929\t8929\t58\t59\tLFAAAA\tAGJAAA\tAAAAxx\n4626\t6241\t0\t2\t6\t6\t26\t626\t626\t4626\t4626\t52\t53\tYVAAAA\tBGJAAA\tHHHHxx\n7958\t6242\t0\t2\t8\t18\t58\t958\t1958\t2958\t7958\t116\t117\tCUAAAA\tCGJAAA\tOOOOxx\n3743\t6243\t1\t3\t3\t3\t43\t743\t1743\t3743\t3743\t86\t87\tZNAAAA\tDGJAAA\tVVVVxx\n8165\t6244\t1\t1\t5\t5\t65\t165\t165\t3165\t8165\t130\t131\tBCAAAA\tEGJAAA\tAAAAxx\n7899\t6245\t1\t3\t9\t19\t99\t899\t1899\t2899\t7899\t198\t199\tVRAAAA\tFGJAAA\tHHHHxx\n8698\t6246\t0\t2\t8\t18\t98\t698\t698\t3698\t8698\t196\t197\tOWAAAA\tGGJAAA\tOOOOxx\n9270\t6247\t0\t2\t0\t10\t70\t270\t1270\t4270\t9270\t140\t141\tOSAAAA\tHGJAAA\tVVVVxx\n6348\t6248\t0\t0\t8\t8\t48\t348\t348\t1348\t6348\t96\t97\tEKAAAA\tIGJAAA\tAAAAxx\n6999\t6249\t1\t3\t9\t19\t99\t999\t999\t1999\t6999\t198\t199\tFJAAAA\tJGJAAA\tHHHHxx\n8467\t6250\t1\t3\t7\t7\t67\t467\t467\t3467\t8467\t134\t135\tRNAAAA\tKGJAAA\tOOOOxx\n3907\t6251\t1\t3\t7\t7\t7\t907\t1907\t3907\t3907\t14\t15\tHUAAAA\tLGJAAA\tVVVVxx\n4738\t6252\t0\t2\t8\t18\t38\t738\t738\t4738\t4738\t76\t77\tGAAAAA\tMGJAAA\tAAAAxx\n248\t6253\t0\t0\t8\t8\t48\t248\t248\t248\t248\t96\t97\tOJAAAA\tNGJAAA\tHHHHxx\n8769\t6254\t1\t1\t9\t9\t69\t769\t769\t3769\t8769\t138\t139\tHZAAAA\tOGJAAA\tOOOOxx\n9922\t6255\t0\t2\t2\t2\t22\t922\t1922\t4922\t9922\t44\t45\tQRAAAA\tPGJAAA\tVVVVxx\n778\t6256\t0\t2\t8\t18\t78\t778\t778\t778\t778\t156\t157\tYDAAAA\tQGJAAA\tAAAAxx\n1233\t6257\t1\t1\t3\t13\t33\t233\t1233\t1233\t1233\t66\t67\tLVAAAA\tRGJAAA\tHHHHxx\n1183\t6258\t1\t3\t3\t3\t83\t183\t1183\t1183\t1183\t166\t167\tNTAAAA\tSGJAAA\tOOOOxx\n2838\t6259\t0\t2\t8\t18\t38\t838\t838\t2838\t2838\t76\t77\tEFAAAA\tTGJAAA\tVVVVxx\n3096\t6260\t0\t0\t6\t16\t96\t96\t1096\t3096\t3096\t192\t193\tCPAAAA\tUGJAAA\tAAAAxx\n8566\t6261\t0\t2\t6\t6\t66\t566\t566\t3566\t8566\t132\t133\tMRAAAA\tVGJAAA\tHHHHxx\n7635\t6262\t1\t3\t5\t15\t35\t635\t1635\t2635\t7635\t70\t71\tRHAAAA\tWGJAAA\tOOOOxx\n5428\t6263\t0\t0\t8\t8\t28\t428\t1428\t428\t5428\t56\t57\tUAAAAA\tXGJAAA\tVVVVxx\n7430\t6264\t0\t2\t0\t10\t30\t430\t1430\t2430\t7430\t60\t61\tUZAAAA\tYGJAAA\tAAAAxx\n7210\t6265\t0\t2\t0\t10\t10\t210\t1210\t2210\t7210\t20\t21\tIRAAAA\tZGJAAA\tHHHHxx\n4485\t6266\t1\t1\t5\t5\t85\t485\t485\t4485\t4485\t170\t171\tNQAAAA\tAHJAAA\tOOOOxx\n9623\t6267\t1\t3\t3\t3\t23\t623\t1623\t4623\t9623\t46\t47\tDGAAAA\tBHJAAA\tVVVVxx\n3670\t6268\t0\t2\t0\t10\t70\t670\t1670\t3670\t3670\t140\t141\tELAAAA\tCHJAAA\tAAAAxx\n1575\t6269\t1\t3\t5\t15\t75\t575\t1575\t1575\t1575\t150\t151\tPIAAAA\tDHJAAA\tHHHHxx\n5874\t6270\t0\t2\t4\t14\t74\t874\t1874\t874\t5874\t148\t149\tYRAAAA\tEHJAAA\tOOOOxx\n673\t6271\t1\t1\t3\t13\t73\t673\t673\t673\t673\t146\t147\tXZAAAA\tFHJAAA\tVVVVxx\n9712\t6272\t0\t0\t2\t12\t12\t712\t1712\t4712\t9712\t24\t25\tOJAAAA\tGHJAAA\tAAAAxx\n7729\t6273\t1\t1\t9\t9\t29\t729\t1729\t2729\t7729\t58\t59\tHLAAAA\tHHJAAA\tHHHHxx\n4318\t6274\t0\t2\t8\t18\t18\t318\t318\t4318\t4318\t36\t37\tCKAAAA\tIHJAAA\tOOOOxx\n4143\t6275\t1\t3\t3\t3\t43\t143\t143\t4143\t4143\t86\t87\tJDAAAA\tJHJAAA\tVVVVxx\n4932\t6276\t0\t0\t2\t12\t32\t932\t932\t4932\t4932\t64\t65\tSHAAAA\tKHJAAA\tAAAAxx\n5835\t6277\t1\t3\t5\t15\t35\t835\t1835\t835\t5835\t70\t71\tLQAAAA\tLHJAAA\tHHHHxx\n4966\t6278\t0\t2\t6\t6\t66\t966\t966\t4966\t4966\t132\t133\tAJAAAA\tMHJAAA\tOOOOxx\n6711\t6279\t1\t3\t1\t11\t11\t711\t711\t1711\t6711\t22\t23\tDYAAAA\tNHJAAA\tVVVVxx\n3990\t6280\t0\t2\t0\t10\t90\t990\t1990\t3990\t3990\t180\t181\tMXAAAA\tOHJAAA\tAAAAxx\n990\t6281\t0\t2\t0\t10\t90\t990\t990\t990\t990\t180\t181\tCMAAAA\tPHJAAA\tHHHHxx\n220\t6282\t0\t0\t0\t0\t20\t220\t220\t220\t220\t40\t41\tMIAAAA\tQHJAAA\tOOOOxx\n5693\t6283\t1\t1\t3\t13\t93\t693\t1693\t693\t5693\t186\t187\tZKAAAA\tRHJAAA\tVVVVxx\n3662\t6284\t0\t2\t2\t2\t62\t662\t1662\t3662\t3662\t124\t125\tWKAAAA\tSHJAAA\tAAAAxx\n7844\t6285\t0\t0\t4\t4\t44\t844\t1844\t2844\t7844\t88\t89\tSPAAAA\tTHJAAA\tHHHHxx\n5515\t6286\t1\t3\t5\t15\t15\t515\t1515\t515\t5515\t30\t31\tDEAAAA\tUHJAAA\tOOOOxx\n5551\t6287\t1\t3\t1\t11\t51\t551\t1551\t551\t5551\t102\t103\tNFAAAA\tVHJAAA\tVVVVxx\n2358\t6288\t0\t2\t8\t18\t58\t358\t358\t2358\t2358\t116\t117\tSMAAAA\tWHJAAA\tAAAAxx\n8977\t6289\t1\t1\t7\t17\t77\t977\t977\t3977\t8977\t154\t155\tHHAAAA\tXHJAAA\tHHHHxx\n7040\t6290\t0\t0\t0\t0\t40\t40\t1040\t2040\t7040\t80\t81\tUKAAAA\tYHJAAA\tOOOOxx\n105\t6291\t1\t1\t5\t5\t5\t105\t105\t105\t105\t10\t11\tBEAAAA\tZHJAAA\tVVVVxx\n4496\t6292\t0\t0\t6\t16\t96\t496\t496\t4496\t4496\t192\t193\tYQAAAA\tAIJAAA\tAAAAxx\n2254\t6293\t0\t2\t4\t14\t54\t254\t254\t2254\t2254\t108\t109\tSIAAAA\tBIJAAA\tHHHHxx\n411\t6294\t1\t3\t1\t11\t11\t411\t411\t411\t411\t22\t23\tVPAAAA\tCIJAAA\tOOOOxx\n2373\t6295\t1\t1\t3\t13\t73\t373\t373\t2373\t2373\t146\t147\tHNAAAA\tDIJAAA\tVVVVxx\n3477\t6296\t1\t1\t7\t17\t77\t477\t1477\t3477\t3477\t154\t155\tTDAAAA\tEIJAAA\tAAAAxx\n8964\t6297\t0\t0\t4\t4\t64\t964\t964\t3964\t8964\t128\t129\tUGAAAA\tFIJAAA\tHHHHxx\n8471\t6298\t1\t3\t1\t11\t71\t471\t471\t3471\t8471\t142\t143\tVNAAAA\tGIJAAA\tOOOOxx\n5776\t6299\t0\t0\t6\t16\t76\t776\t1776\t776\t5776\t152\t153\tEOAAAA\tHIJAAA\tVVVVxx\n9921\t6300\t1\t1\t1\t1\t21\t921\t1921\t4921\t9921\t42\t43\tPRAAAA\tIIJAAA\tAAAAxx\n7816\t6301\t0\t0\t6\t16\t16\t816\t1816\t2816\t7816\t32\t33\tQOAAAA\tJIJAAA\tHHHHxx\n2439\t6302\t1\t3\t9\t19\t39\t439\t439\t2439\t2439\t78\t79\tVPAAAA\tKIJAAA\tOOOOxx\n9298\t6303\t0\t2\t8\t18\t98\t298\t1298\t4298\t9298\t196\t197\tQTAAAA\tLIJAAA\tVVVVxx\n9424\t6304\t0\t0\t4\t4\t24\t424\t1424\t4424\t9424\t48\t49\tMYAAAA\tMIJAAA\tAAAAxx\n3252\t6305\t0\t0\t2\t12\t52\t252\t1252\t3252\t3252\t104\t105\tCVAAAA\tNIJAAA\tHHHHxx\n1401\t6306\t1\t1\t1\t1\t1\t401\t1401\t1401\t1401\t2\t3\tXBAAAA\tOIJAAA\tOOOOxx\n9632\t6307\t0\t0\t2\t12\t32\t632\t1632\t4632\t9632\t64\t65\tMGAAAA\tPIJAAA\tVVVVxx\n370\t6308\t0\t2\t0\t10\t70\t370\t370\t370\t370\t140\t141\tGOAAAA\tQIJAAA\tAAAAxx\n728\t6309\t0\t0\t8\t8\t28\t728\t728\t728\t728\t56\t57\tACAAAA\tRIJAAA\tHHHHxx\n2888\t6310\t0\t0\t8\t8\t88\t888\t888\t2888\t2888\t176\t177\tCHAAAA\tSIJAAA\tOOOOxx\n1441\t6311\t1\t1\t1\t1\t41\t441\t1441\t1441\t1441\t82\t83\tLDAAAA\tTIJAAA\tVVVVxx\n8308\t6312\t0\t0\t8\t8\t8\t308\t308\t3308\t8308\t16\t17\tOHAAAA\tUIJAAA\tAAAAxx\n2165\t6313\t1\t1\t5\t5\t65\t165\t165\t2165\t2165\t130\t131\tHFAAAA\tVIJAAA\tHHHHxx\n6359\t6314\t1\t3\t9\t19\t59\t359\t359\t1359\t6359\t118\t119\tPKAAAA\tWIJAAA\tOOOOxx\n9637\t6315\t1\t1\t7\t17\t37\t637\t1637\t4637\t9637\t74\t75\tRGAAAA\tXIJAAA\tVVVVxx\n5208\t6316\t0\t0\t8\t8\t8\t208\t1208\t208\t5208\t16\t17\tISAAAA\tYIJAAA\tAAAAxx\n4705\t6317\t1\t1\t5\t5\t5\t705\t705\t4705\t4705\t10\t11\tZYAAAA\tZIJAAA\tHHHHxx\n2341\t6318\t1\t1\t1\t1\t41\t341\t341\t2341\t2341\t82\t83\tBMAAAA\tAJJAAA\tOOOOxx\n8539\t6319\t1\t3\t9\t19\t39\t539\t539\t3539\t8539\t78\t79\tLQAAAA\tBJJAAA\tVVVVxx\n7528\t6320\t0\t0\t8\t8\t28\t528\t1528\t2528\t7528\t56\t57\tODAAAA\tCJJAAA\tAAAAxx\n7969\t6321\t1\t1\t9\t9\t69\t969\t1969\t2969\t7969\t138\t139\tNUAAAA\tDJJAAA\tHHHHxx\n6381\t6322\t1\t1\t1\t1\t81\t381\t381\t1381\t6381\t162\t163\tLLAAAA\tEJJAAA\tOOOOxx\n4906\t6323\t0\t2\t6\t6\t6\t906\t906\t4906\t4906\t12\t13\tSGAAAA\tFJJAAA\tVVVVxx\n8697\t6324\t1\t1\t7\t17\t97\t697\t697\t3697\t8697\t194\t195\tNWAAAA\tGJJAAA\tAAAAxx\n6301\t6325\t1\t1\t1\t1\t1\t301\t301\t1301\t6301\t2\t3\tJIAAAA\tHJJAAA\tHHHHxx\n7554\t6326\t0\t2\t4\t14\t54\t554\t1554\t2554\t7554\t108\t109\tOEAAAA\tIJJAAA\tOOOOxx\n5107\t6327\t1\t3\t7\t7\t7\t107\t1107\t107\t5107\t14\t15\tLOAAAA\tJJJAAA\tVVVVxx\n5046\t6328\t0\t2\t6\t6\t46\t46\t1046\t46\t5046\t92\t93\tCMAAAA\tKJJAAA\tAAAAxx\n4063\t6329\t1\t3\t3\t3\t63\t63\t63\t4063\t4063\t126\t127\tHAAAAA\tLJJAAA\tHHHHxx\n7580\t6330\t0\t0\t0\t0\t80\t580\t1580\t2580\t7580\t160\t161\tOFAAAA\tMJJAAA\tOOOOxx\n2245\t6331\t1\t1\t5\t5\t45\t245\t245\t2245\t2245\t90\t91\tJIAAAA\tNJJAAA\tVVVVxx\n3711\t6332\t1\t3\t1\t11\t11\t711\t1711\t3711\t3711\t22\t23\tTMAAAA\tOJJAAA\tAAAAxx\n3220\t6333\t0\t0\t0\t0\t20\t220\t1220\t3220\t3220\t40\t41\tWTAAAA\tPJJAAA\tHHHHxx\n6463\t6334\t1\t3\t3\t3\t63\t463\t463\t1463\t6463\t126\t127\tPOAAAA\tQJJAAA\tOOOOxx\n8196\t6335\t0\t0\t6\t16\t96\t196\t196\t3196\t8196\t192\t193\tGDAAAA\tRJJAAA\tVVVVxx\n9875\t6336\t1\t3\t5\t15\t75\t875\t1875\t4875\t9875\t150\t151\tVPAAAA\tSJJAAA\tAAAAxx\n1333\t6337\t1\t1\t3\t13\t33\t333\t1333\t1333\t1333\t66\t67\tHZAAAA\tTJJAAA\tHHHHxx\n7880\t6338\t0\t0\t0\t0\t80\t880\t1880\t2880\t7880\t160\t161\tCRAAAA\tUJJAAA\tOOOOxx\n2322\t6339\t0\t2\t2\t2\t22\t322\t322\t2322\t2322\t44\t45\tILAAAA\tVJJAAA\tVVVVxx\n2163\t6340\t1\t3\t3\t3\t63\t163\t163\t2163\t2163\t126\t127\tFFAAAA\tWJJAAA\tAAAAxx\n421\t6341\t1\t1\t1\t1\t21\t421\t421\t421\t421\t42\t43\tFQAAAA\tXJJAAA\tHHHHxx\n2042\t6342\t0\t2\t2\t2\t42\t42\t42\t2042\t2042\t84\t85\tOAAAAA\tYJJAAA\tOOOOxx\n1424\t6343\t0\t0\t4\t4\t24\t424\t1424\t1424\t1424\t48\t49\tUCAAAA\tZJJAAA\tVVVVxx\n7870\t6344\t0\t2\t0\t10\t70\t870\t1870\t2870\t7870\t140\t141\tSQAAAA\tAKJAAA\tAAAAxx\n2653\t6345\t1\t1\t3\t13\t53\t653\t653\t2653\t2653\t106\t107\tBYAAAA\tBKJAAA\tHHHHxx\n4216\t6346\t0\t0\t6\t16\t16\t216\t216\t4216\t4216\t32\t33\tEGAAAA\tCKJAAA\tOOOOxx\n1515\t6347\t1\t3\t5\t15\t15\t515\t1515\t1515\t1515\t30\t31\tHGAAAA\tDKJAAA\tVVVVxx\n7860\t6348\t0\t0\t0\t0\t60\t860\t1860\t2860\t7860\t120\t121\tIQAAAA\tEKJAAA\tAAAAxx\n2984\t6349\t0\t0\t4\t4\t84\t984\t984\t2984\t2984\t168\t169\tUKAAAA\tFKJAAA\tHHHHxx\n6269\t6350\t1\t1\t9\t9\t69\t269\t269\t1269\t6269\t138\t139\tDHAAAA\tGKJAAA\tOOOOxx\n2609\t6351\t1\t1\t9\t9\t9\t609\t609\t2609\t2609\t18\t19\tJWAAAA\tHKJAAA\tVVVVxx\n3671\t6352\t1\t3\t1\t11\t71\t671\t1671\t3671\t3671\t142\t143\tFLAAAA\tIKJAAA\tAAAAxx\n4544\t6353\t0\t0\t4\t4\t44\t544\t544\t4544\t4544\t88\t89\tUSAAAA\tJKJAAA\tHHHHxx\n4668\t6354\t0\t0\t8\t8\t68\t668\t668\t4668\t4668\t136\t137\tOXAAAA\tKKJAAA\tOOOOxx\n2565\t6355\t1\t1\t5\t5\t65\t565\t565\t2565\t2565\t130\t131\tRUAAAA\tLKJAAA\tVVVVxx\n3126\t6356\t0\t2\t6\t6\t26\t126\t1126\t3126\t3126\t52\t53\tGQAAAA\tMKJAAA\tAAAAxx\n7573\t6357\t1\t1\t3\t13\t73\t573\t1573\t2573\t7573\t146\t147\tHFAAAA\tNKJAAA\tHHHHxx\n1476\t6358\t0\t0\t6\t16\t76\t476\t1476\t1476\t1476\t152\t153\tUEAAAA\tOKJAAA\tOOOOxx\n2146\t6359\t0\t2\t6\t6\t46\t146\t146\t2146\t2146\t92\t93\tOEAAAA\tPKJAAA\tVVVVxx\n9990\t6360\t0\t2\t0\t10\t90\t990\t1990\t4990\t9990\t180\t181\tGUAAAA\tQKJAAA\tAAAAxx\n2530\t6361\t0\t2\t0\t10\t30\t530\t530\t2530\t2530\t60\t61\tITAAAA\tRKJAAA\tHHHHxx\n9288\t6362\t0\t0\t8\t8\t88\t288\t1288\t4288\t9288\t176\t177\tGTAAAA\tSKJAAA\tOOOOxx\n9755\t6363\t1\t3\t5\t15\t55\t755\t1755\t4755\t9755\t110\t111\tFLAAAA\tTKJAAA\tVVVVxx\n5305\t6364\t1\t1\t5\t5\t5\t305\t1305\t305\t5305\t10\t11\tBWAAAA\tUKJAAA\tAAAAxx\n2495\t6365\t1\t3\t5\t15\t95\t495\t495\t2495\t2495\t190\t191\tZRAAAA\tVKJAAA\tHHHHxx\n5443\t6366\t1\t3\t3\t3\t43\t443\t1443\t443\t5443\t86\t87\tJBAAAA\tWKJAAA\tOOOOxx\n1930\t6367\t0\t2\t0\t10\t30\t930\t1930\t1930\t1930\t60\t61\tGWAAAA\tXKJAAA\tVVVVxx\n9134\t6368\t0\t2\t4\t14\t34\t134\t1134\t4134\t9134\t68\t69\tINAAAA\tYKJAAA\tAAAAxx\n2844\t6369\t0\t0\t4\t4\t44\t844\t844\t2844\t2844\t88\t89\tKFAAAA\tZKJAAA\tHHHHxx\n896\t6370\t0\t0\t6\t16\t96\t896\t896\t896\t896\t192\t193\tMIAAAA\tALJAAA\tOOOOxx\n1330\t6371\t0\t2\t0\t10\t30\t330\t1330\t1330\t1330\t60\t61\tEZAAAA\tBLJAAA\tVVVVxx\n8980\t6372\t0\t0\t0\t0\t80\t980\t980\t3980\t8980\t160\t161\tKHAAAA\tCLJAAA\tAAAAxx\n5940\t6373\t0\t0\t0\t0\t40\t940\t1940\t940\t5940\t80\t81\tMUAAAA\tDLJAAA\tHHHHxx\n6494\t6374\t0\t2\t4\t14\t94\t494\t494\t1494\t6494\t188\t189\tUPAAAA\tELJAAA\tOOOOxx\n165\t6375\t1\t1\t5\t5\t65\t165\t165\t165\t165\t130\t131\tJGAAAA\tFLJAAA\tVVVVxx\n2510\t6376\t0\t2\t0\t10\t10\t510\t510\t2510\t2510\t20\t21\tOSAAAA\tGLJAAA\tAAAAxx\n9950\t6377\t0\t2\t0\t10\t50\t950\t1950\t4950\t9950\t100\t101\tSSAAAA\tHLJAAA\tHHHHxx\n3854\t6378\t0\t2\t4\t14\t54\t854\t1854\t3854\t3854\t108\t109\tGSAAAA\tILJAAA\tOOOOxx\n7493\t6379\t1\t1\t3\t13\t93\t493\t1493\t2493\t7493\t186\t187\tFCAAAA\tJLJAAA\tVVVVxx\n4124\t6380\t0\t0\t4\t4\t24\t124\t124\t4124\t4124\t48\t49\tQCAAAA\tKLJAAA\tAAAAxx\n8563\t6381\t1\t3\t3\t3\t63\t563\t563\t3563\t8563\t126\t127\tJRAAAA\tLLJAAA\tHHHHxx\n8735\t6382\t1\t3\t5\t15\t35\t735\t735\t3735\t8735\t70\t71\tZXAAAA\tMLJAAA\tOOOOxx\n9046\t6383\t0\t2\t6\t6\t46\t46\t1046\t4046\t9046\t92\t93\tYJAAAA\tNLJAAA\tVVVVxx\n1754\t6384\t0\t2\t4\t14\t54\t754\t1754\t1754\t1754\t108\t109\tMPAAAA\tOLJAAA\tAAAAxx\n6954\t6385\t0\t2\t4\t14\t54\t954\t954\t1954\t6954\t108\t109\tMHAAAA\tPLJAAA\tHHHHxx\n4953\t6386\t1\t1\t3\t13\t53\t953\t953\t4953\t4953\t106\t107\tNIAAAA\tQLJAAA\tOOOOxx\n8142\t6387\t0\t2\t2\t2\t42\t142\t142\t3142\t8142\t84\t85\tEBAAAA\tRLJAAA\tVVVVxx\n9661\t6388\t1\t1\t1\t1\t61\t661\t1661\t4661\t9661\t122\t123\tPHAAAA\tSLJAAA\tAAAAxx\n6415\t6389\t1\t3\t5\t15\t15\t415\t415\t1415\t6415\t30\t31\tTMAAAA\tTLJAAA\tHHHHxx\n5782\t6390\t0\t2\t2\t2\t82\t782\t1782\t782\t5782\t164\t165\tKOAAAA\tULJAAA\tOOOOxx\n7721\t6391\t1\t1\t1\t1\t21\t721\t1721\t2721\t7721\t42\t43\tZKAAAA\tVLJAAA\tVVVVxx\n580\t6392\t0\t0\t0\t0\t80\t580\t580\t580\t580\t160\t161\tIWAAAA\tWLJAAA\tAAAAxx\n3784\t6393\t0\t0\t4\t4\t84\t784\t1784\t3784\t3784\t168\t169\tOPAAAA\tXLJAAA\tHHHHxx\n9810\t6394\t0\t2\t0\t10\t10\t810\t1810\t4810\t9810\t20\t21\tINAAAA\tYLJAAA\tOOOOxx\n8488\t6395\t0\t0\t8\t8\t88\t488\t488\t3488\t8488\t176\t177\tMOAAAA\tZLJAAA\tVVVVxx\n6214\t6396\t0\t2\t4\t14\t14\t214\t214\t1214\t6214\t28\t29\tAFAAAA\tAMJAAA\tAAAAxx\n9433\t6397\t1\t1\t3\t13\t33\t433\t1433\t4433\t9433\t66\t67\tVYAAAA\tBMJAAA\tHHHHxx\n9959\t6398\t1\t3\t9\t19\t59\t959\t1959\t4959\t9959\t118\t119\tBTAAAA\tCMJAAA\tOOOOxx\n554\t6399\t0\t2\t4\t14\t54\t554\t554\t554\t554\t108\t109\tIVAAAA\tDMJAAA\tVVVVxx\n6646\t6400\t0\t2\t6\t6\t46\t646\t646\t1646\t6646\t92\t93\tQVAAAA\tEMJAAA\tAAAAxx\n1138\t6401\t0\t2\t8\t18\t38\t138\t1138\t1138\t1138\t76\t77\tURAAAA\tFMJAAA\tHHHHxx\n9331\t6402\t1\t3\t1\t11\t31\t331\t1331\t4331\t9331\t62\t63\tXUAAAA\tGMJAAA\tOOOOxx\n7331\t6403\t1\t3\t1\t11\t31\t331\t1331\t2331\t7331\t62\t63\tZVAAAA\tHMJAAA\tVVVVxx\n3482\t6404\t0\t2\t2\t2\t82\t482\t1482\t3482\t3482\t164\t165\tYDAAAA\tIMJAAA\tAAAAxx\n3795\t6405\t1\t3\t5\t15\t95\t795\t1795\t3795\t3795\t190\t191\tZPAAAA\tJMJAAA\tHHHHxx\n2441\t6406\t1\t1\t1\t1\t41\t441\t441\t2441\t2441\t82\t83\tXPAAAA\tKMJAAA\tOOOOxx\n5229\t6407\t1\t1\t9\t9\t29\t229\t1229\t229\t5229\t58\t59\tDTAAAA\tLMJAAA\tVVVVxx\n7012\t6408\t0\t0\t2\t12\t12\t12\t1012\t2012\t7012\t24\t25\tSJAAAA\tMMJAAA\tAAAAxx\n7036\t6409\t0\t0\t6\t16\t36\t36\t1036\t2036\t7036\t72\t73\tQKAAAA\tNMJAAA\tHHHHxx\n8243\t6410\t1\t3\t3\t3\t43\t243\t243\t3243\t8243\t86\t87\tBFAAAA\tOMJAAA\tOOOOxx\n9320\t6411\t0\t0\t0\t0\t20\t320\t1320\t4320\t9320\t40\t41\tMUAAAA\tPMJAAA\tVVVVxx\n4693\t6412\t1\t1\t3\t13\t93\t693\t693\t4693\t4693\t186\t187\tNYAAAA\tQMJAAA\tAAAAxx\n6741\t6413\t1\t1\t1\t1\t41\t741\t741\t1741\t6741\t82\t83\tHZAAAA\tRMJAAA\tHHHHxx\n2997\t6414\t1\t1\t7\t17\t97\t997\t997\t2997\t2997\t194\t195\tHLAAAA\tSMJAAA\tOOOOxx\n4838\t6415\t0\t2\t8\t18\t38\t838\t838\t4838\t4838\t76\t77\tCEAAAA\tTMJAAA\tVVVVxx\n6945\t6416\t1\t1\t5\t5\t45\t945\t945\t1945\t6945\t90\t91\tDHAAAA\tUMJAAA\tAAAAxx\n8253\t6417\t1\t1\t3\t13\t53\t253\t253\t3253\t8253\t106\t107\tLFAAAA\tVMJAAA\tHHHHxx\n8989\t6418\t1\t1\t9\t9\t89\t989\t989\t3989\t8989\t178\t179\tTHAAAA\tWMJAAA\tOOOOxx\n2640\t6419\t0\t0\t0\t0\t40\t640\t640\t2640\t2640\t80\t81\tOXAAAA\tXMJAAA\tVVVVxx\n5647\t6420\t1\t3\t7\t7\t47\t647\t1647\t647\t5647\t94\t95\tFJAAAA\tYMJAAA\tAAAAxx\n7186\t6421\t0\t2\t6\t6\t86\t186\t1186\t2186\t7186\t172\t173\tKQAAAA\tZMJAAA\tHHHHxx\n3278\t6422\t0\t2\t8\t18\t78\t278\t1278\t3278\t3278\t156\t157\tCWAAAA\tANJAAA\tOOOOxx\n8546\t6423\t0\t2\t6\t6\t46\t546\t546\t3546\t8546\t92\t93\tSQAAAA\tBNJAAA\tVVVVxx\n8297\t6424\t1\t1\t7\t17\t97\t297\t297\t3297\t8297\t194\t195\tDHAAAA\tCNJAAA\tAAAAxx\n9534\t6425\t0\t2\t4\t14\t34\t534\t1534\t4534\t9534\t68\t69\tSCAAAA\tDNJAAA\tHHHHxx\n9618\t6426\t0\t2\t8\t18\t18\t618\t1618\t4618\t9618\t36\t37\tYFAAAA\tENJAAA\tOOOOxx\n8839\t6427\t1\t3\t9\t19\t39\t839\t839\t3839\t8839\t78\t79\tZBAAAA\tFNJAAA\tVVVVxx\n7605\t6428\t1\t1\t5\t5\t5\t605\t1605\t2605\t7605\t10\t11\tNGAAAA\tGNJAAA\tAAAAxx\n6421\t6429\t1\t1\t1\t1\t21\t421\t421\t1421\t6421\t42\t43\tZMAAAA\tHNJAAA\tHHHHxx\n3582\t6430\t0\t2\t2\t2\t82\t582\t1582\t3582\t3582\t164\t165\tUHAAAA\tINJAAA\tOOOOxx\n485\t6431\t1\t1\t5\t5\t85\t485\t485\t485\t485\t170\t171\tRSAAAA\tJNJAAA\tVVVVxx\n1925\t6432\t1\t1\t5\t5\t25\t925\t1925\t1925\t1925\t50\t51\tBWAAAA\tKNJAAA\tAAAAxx\n4296\t6433\t0\t0\t6\t16\t96\t296\t296\t4296\t4296\t192\t193\tGJAAAA\tLNJAAA\tHHHHxx\n8874\t6434\t0\t2\t4\t14\t74\t874\t874\t3874\t8874\t148\t149\tIDAAAA\tMNJAAA\tOOOOxx\n1443\t6435\t1\t3\t3\t3\t43\t443\t1443\t1443\t1443\t86\t87\tNDAAAA\tNNJAAA\tVVVVxx\n4239\t6436\t1\t3\t9\t19\t39\t239\t239\t4239\t4239\t78\t79\tBHAAAA\tONJAAA\tAAAAxx\n9760\t6437\t0\t0\t0\t0\t60\t760\t1760\t4760\t9760\t120\t121\tKLAAAA\tPNJAAA\tHHHHxx\n136\t6438\t0\t0\t6\t16\t36\t136\t136\t136\t136\t72\t73\tGFAAAA\tQNJAAA\tOOOOxx\n6472\t6439\t0\t0\t2\t12\t72\t472\t472\t1472\t6472\t144\t145\tYOAAAA\tRNJAAA\tVVVVxx\n4896\t6440\t0\t0\t6\t16\t96\t896\t896\t4896\t4896\t192\t193\tIGAAAA\tSNJAAA\tAAAAxx\n9028\t6441\t0\t0\t8\t8\t28\t28\t1028\t4028\t9028\t56\t57\tGJAAAA\tTNJAAA\tHHHHxx\n8354\t6442\t0\t2\t4\t14\t54\t354\t354\t3354\t8354\t108\t109\tIJAAAA\tUNJAAA\tOOOOxx\n8648\t6443\t0\t0\t8\t8\t48\t648\t648\t3648\t8648\t96\t97\tQUAAAA\tVNJAAA\tVVVVxx\n918\t6444\t0\t2\t8\t18\t18\t918\t918\t918\t918\t36\t37\tIJAAAA\tWNJAAA\tAAAAxx\n6606\t6445\t0\t2\t6\t6\t6\t606\t606\t1606\t6606\t12\t13\tCUAAAA\tXNJAAA\tHHHHxx\n2462\t6446\t0\t2\t2\t2\t62\t462\t462\t2462\t2462\t124\t125\tSQAAAA\tYNJAAA\tOOOOxx\n7536\t6447\t0\t0\t6\t16\t36\t536\t1536\t2536\t7536\t72\t73\tWDAAAA\tZNJAAA\tVVVVxx\n1700\t6448\t0\t0\t0\t0\t0\t700\t1700\t1700\t1700\t0\t1\tKNAAAA\tAOJAAA\tAAAAxx\n6740\t6449\t0\t0\t0\t0\t40\t740\t740\t1740\t6740\t80\t81\tGZAAAA\tBOJAAA\tHHHHxx\n28\t6450\t0\t0\t8\t8\t28\t28\t28\t28\t28\t56\t57\tCBAAAA\tCOJAAA\tOOOOxx\n6044\t6451\t0\t0\t4\t4\t44\t44\t44\t1044\t6044\t88\t89\tMYAAAA\tDOJAAA\tVVVVxx\n5053\t6452\t1\t1\t3\t13\t53\t53\t1053\t53\t5053\t106\t107\tJMAAAA\tEOJAAA\tAAAAxx\n4832\t6453\t0\t0\t2\t12\t32\t832\t832\t4832\t4832\t64\t65\tWDAAAA\tFOJAAA\tHHHHxx\n9145\t6454\t1\t1\t5\t5\t45\t145\t1145\t4145\t9145\t90\t91\tTNAAAA\tGOJAAA\tOOOOxx\n5482\t6455\t0\t2\t2\t2\t82\t482\t1482\t482\t5482\t164\t165\tWCAAAA\tHOJAAA\tVVVVxx\n7644\t6456\t0\t0\t4\t4\t44\t644\t1644\t2644\t7644\t88\t89\tAIAAAA\tIOJAAA\tAAAAxx\n2128\t6457\t0\t0\t8\t8\t28\t128\t128\t2128\t2128\t56\t57\tWDAAAA\tJOJAAA\tHHHHxx\n6583\t6458\t1\t3\t3\t3\t83\t583\t583\t1583\t6583\t166\t167\tFTAAAA\tKOJAAA\tOOOOxx\n4224\t6459\t0\t0\t4\t4\t24\t224\t224\t4224\t4224\t48\t49\tMGAAAA\tLOJAAA\tVVVVxx\n5253\t6460\t1\t1\t3\t13\t53\t253\t1253\t253\t5253\t106\t107\tBUAAAA\tMOJAAA\tAAAAxx\n8219\t6461\t1\t3\t9\t19\t19\t219\t219\t3219\t8219\t38\t39\tDEAAAA\tNOJAAA\tHHHHxx\n8113\t6462\t1\t1\t3\t13\t13\t113\t113\t3113\t8113\t26\t27\tBAAAAA\tOOJAAA\tOOOOxx\n3616\t6463\t0\t0\t6\t16\t16\t616\t1616\t3616\t3616\t32\t33\tCJAAAA\tPOJAAA\tVVVVxx\n1361\t6464\t1\t1\t1\t1\t61\t361\t1361\t1361\t1361\t122\t123\tJAAAAA\tQOJAAA\tAAAAxx\n949\t6465\t1\t1\t9\t9\t49\t949\t949\t949\t949\t98\t99\tNKAAAA\tROJAAA\tHHHHxx\n8582\t6466\t0\t2\t2\t2\t82\t582\t582\t3582\t8582\t164\t165\tCSAAAA\tSOJAAA\tOOOOxx\n5104\t6467\t0\t0\t4\t4\t4\t104\t1104\t104\t5104\t8\t9\tIOAAAA\tTOJAAA\tVVVVxx\n6146\t6468\t0\t2\t6\t6\t46\t146\t146\t1146\t6146\t92\t93\tKCAAAA\tUOJAAA\tAAAAxx\n7681\t6469\t1\t1\t1\t1\t81\t681\t1681\t2681\t7681\t162\t163\tLJAAAA\tVOJAAA\tHHHHxx\n1904\t6470\t0\t0\t4\t4\t4\t904\t1904\t1904\t1904\t8\t9\tGVAAAA\tWOJAAA\tOOOOxx\n1989\t6471\t1\t1\t9\t9\t89\t989\t1989\t1989\t1989\t178\t179\tNYAAAA\tXOJAAA\tVVVVxx\n4179\t6472\t1\t3\t9\t19\t79\t179\t179\t4179\t4179\t158\t159\tTEAAAA\tYOJAAA\tAAAAxx\n1739\t6473\t1\t3\t9\t19\t39\t739\t1739\t1739\t1739\t78\t79\tXOAAAA\tZOJAAA\tHHHHxx\n2447\t6474\t1\t3\t7\t7\t47\t447\t447\t2447\t2447\t94\t95\tDQAAAA\tAPJAAA\tOOOOxx\n3029\t6475\t1\t1\t9\t9\t29\t29\t1029\t3029\t3029\t58\t59\tNMAAAA\tBPJAAA\tVVVVxx\n9783\t6476\t1\t3\t3\t3\t83\t783\t1783\t4783\t9783\t166\t167\tHMAAAA\tCPJAAA\tAAAAxx\n8381\t6477\t1\t1\t1\t1\t81\t381\t381\t3381\t8381\t162\t163\tJKAAAA\tDPJAAA\tHHHHxx\n8755\t6478\t1\t3\t5\t15\t55\t755\t755\t3755\t8755\t110\t111\tTYAAAA\tEPJAAA\tOOOOxx\n8384\t6479\t0\t0\t4\t4\t84\t384\t384\t3384\t8384\t168\t169\tMKAAAA\tFPJAAA\tVVVVxx\n7655\t6480\t1\t3\t5\t15\t55\t655\t1655\t2655\t7655\t110\t111\tLIAAAA\tGPJAAA\tAAAAxx\n4766\t6481\t0\t2\t6\t6\t66\t766\t766\t4766\t4766\t132\t133\tIBAAAA\tHPJAAA\tHHHHxx\n3324\t6482\t0\t0\t4\t4\t24\t324\t1324\t3324\t3324\t48\t49\tWXAAAA\tIPJAAA\tOOOOxx\n5022\t6483\t0\t2\t2\t2\t22\t22\t1022\t22\t5022\t44\t45\tELAAAA\tJPJAAA\tVVVVxx\n2856\t6484\t0\t0\t6\t16\t56\t856\t856\t2856\t2856\t112\t113\tWFAAAA\tKPJAAA\tAAAAxx\n6503\t6485\t1\t3\t3\t3\t3\t503\t503\t1503\t6503\t6\t7\tDQAAAA\tLPJAAA\tHHHHxx\n6872\t6486\t0\t0\t2\t12\t72\t872\t872\t1872\t6872\t144\t145\tIEAAAA\tMPJAAA\tOOOOxx\n1663\t6487\t1\t3\t3\t3\t63\t663\t1663\t1663\t1663\t126\t127\tZLAAAA\tNPJAAA\tVVVVxx\n6964\t6488\t0\t0\t4\t4\t64\t964\t964\t1964\t6964\t128\t129\tWHAAAA\tOPJAAA\tAAAAxx\n4622\t6489\t0\t2\t2\t2\t22\t622\t622\t4622\t4622\t44\t45\tUVAAAA\tPPJAAA\tHHHHxx\n6089\t6490\t1\t1\t9\t9\t89\t89\t89\t1089\t6089\t178\t179\tFAAAAA\tQPJAAA\tOOOOxx\n8567\t6491\t1\t3\t7\t7\t67\t567\t567\t3567\t8567\t134\t135\tNRAAAA\tRPJAAA\tVVVVxx\n597\t6492\t1\t1\t7\t17\t97\t597\t597\t597\t597\t194\t195\tZWAAAA\tSPJAAA\tAAAAxx\n4222\t6493\t0\t2\t2\t2\t22\t222\t222\t4222\t4222\t44\t45\tKGAAAA\tTPJAAA\tHHHHxx\n9322\t6494\t0\t2\t2\t2\t22\t322\t1322\t4322\t9322\t44\t45\tOUAAAA\tUPJAAA\tOOOOxx\n624\t6495\t0\t0\t4\t4\t24\t624\t624\t624\t624\t48\t49\tAYAAAA\tVPJAAA\tVVVVxx\n4329\t6496\t1\t1\t9\t9\t29\t329\t329\t4329\t4329\t58\t59\tNKAAAA\tWPJAAA\tAAAAxx\n6781\t6497\t1\t1\t1\t1\t81\t781\t781\t1781\t6781\t162\t163\tVAAAAA\tXPJAAA\tHHHHxx\n1673\t6498\t1\t1\t3\t13\t73\t673\t1673\t1673\t1673\t146\t147\tJMAAAA\tYPJAAA\tOOOOxx\n6633\t6499\t1\t1\t3\t13\t33\t633\t633\t1633\t6633\t66\t67\tDVAAAA\tZPJAAA\tVVVVxx\n2569\t6500\t1\t1\t9\t9\t69\t569\t569\t2569\t2569\t138\t139\tVUAAAA\tAQJAAA\tAAAAxx\n4995\t6501\t1\t3\t5\t15\t95\t995\t995\t4995\t4995\t190\t191\tDKAAAA\tBQJAAA\tHHHHxx\n2749\t6502\t1\t1\t9\t9\t49\t749\t749\t2749\t2749\t98\t99\tTBAAAA\tCQJAAA\tOOOOxx\n9044\t6503\t0\t0\t4\t4\t44\t44\t1044\t4044\t9044\t88\t89\tWJAAAA\tDQJAAA\tVVVVxx\n5823\t6504\t1\t3\t3\t3\t23\t823\t1823\t823\t5823\t46\t47\tZPAAAA\tEQJAAA\tAAAAxx\n9366\t6505\t0\t2\t6\t6\t66\t366\t1366\t4366\t9366\t132\t133\tGWAAAA\tFQJAAA\tHHHHxx\n1169\t6506\t1\t1\t9\t9\t69\t169\t1169\t1169\t1169\t138\t139\tZSAAAA\tGQJAAA\tOOOOxx\n1300\t6507\t0\t0\t0\t0\t0\t300\t1300\t1300\t1300\t0\t1\tAYAAAA\tHQJAAA\tVVVVxx\n9973\t6508\t1\t1\t3\t13\t73\t973\t1973\t4973\t9973\t146\t147\tPTAAAA\tIQJAAA\tAAAAxx\n2092\t6509\t0\t0\t2\t12\t92\t92\t92\t2092\t2092\t184\t185\tMCAAAA\tJQJAAA\tHHHHxx\n9776\t6510\t0\t0\t6\t16\t76\t776\t1776\t4776\t9776\t152\t153\tAMAAAA\tKQJAAA\tOOOOxx\n7612\t6511\t0\t0\t2\t12\t12\t612\t1612\t2612\t7612\t24\t25\tUGAAAA\tLQJAAA\tVVVVxx\n7190\t6512\t0\t2\t0\t10\t90\t190\t1190\t2190\t7190\t180\t181\tOQAAAA\tMQJAAA\tAAAAxx\n5147\t6513\t1\t3\t7\t7\t47\t147\t1147\t147\t5147\t94\t95\tZPAAAA\tNQJAAA\tHHHHxx\n3722\t6514\t0\t2\t2\t2\t22\t722\t1722\t3722\t3722\t44\t45\tENAAAA\tOQJAAA\tOOOOxx\n5858\t6515\t0\t2\t8\t18\t58\t858\t1858\t858\t5858\t116\t117\tIRAAAA\tPQJAAA\tVVVVxx\n3204\t6516\t0\t0\t4\t4\t4\t204\t1204\t3204\t3204\t8\t9\tGTAAAA\tQQJAAA\tAAAAxx\n8994\t6517\t0\t2\t4\t14\t94\t994\t994\t3994\t8994\t188\t189\tYHAAAA\tRQJAAA\tHHHHxx\n7478\t6518\t0\t2\t8\t18\t78\t478\t1478\t2478\t7478\t156\t157\tQBAAAA\tSQJAAA\tOOOOxx\n9624\t6519\t0\t0\t4\t4\t24\t624\t1624\t4624\t9624\t48\t49\tEGAAAA\tTQJAAA\tVVVVxx\n6639\t6520\t1\t3\t9\t19\t39\t639\t639\t1639\t6639\t78\t79\tJVAAAA\tUQJAAA\tAAAAxx\n369\t6521\t1\t1\t9\t9\t69\t369\t369\t369\t369\t138\t139\tFOAAAA\tVQJAAA\tHHHHxx\n7766\t6522\t0\t2\t6\t6\t66\t766\t1766\t2766\t7766\t132\t133\tSMAAAA\tWQJAAA\tOOOOxx\n4094\t6523\t0\t2\t4\t14\t94\t94\t94\t4094\t4094\t188\t189\tMBAAAA\tXQJAAA\tVVVVxx\n9556\t6524\t0\t0\t6\t16\t56\t556\t1556\t4556\t9556\t112\t113\tODAAAA\tYQJAAA\tAAAAxx\n4887\t6525\t1\t3\t7\t7\t87\t887\t887\t4887\t4887\t174\t175\tZFAAAA\tZQJAAA\tHHHHxx\n2321\t6526\t1\t1\t1\t1\t21\t321\t321\t2321\t2321\t42\t43\tHLAAAA\tARJAAA\tOOOOxx\n9201\t6527\t1\t1\t1\t1\t1\t201\t1201\t4201\t9201\t2\t3\tXPAAAA\tBRJAAA\tVVVVxx\n1627\t6528\t1\t3\t7\t7\t27\t627\t1627\t1627\t1627\t54\t55\tPKAAAA\tCRJAAA\tAAAAxx\n150\t6529\t0\t2\t0\t10\t50\t150\t150\t150\t150\t100\t101\tUFAAAA\tDRJAAA\tHHHHxx\n8010\t6530\t0\t2\t0\t10\t10\t10\t10\t3010\t8010\t20\t21\tCWAAAA\tERJAAA\tOOOOxx\n8026\t6531\t0\t2\t6\t6\t26\t26\t26\t3026\t8026\t52\t53\tSWAAAA\tFRJAAA\tVVVVxx\n5495\t6532\t1\t3\t5\t15\t95\t495\t1495\t495\t5495\t190\t191\tJDAAAA\tGRJAAA\tAAAAxx\n6213\t6533\t1\t1\t3\t13\t13\t213\t213\t1213\t6213\t26\t27\tZEAAAA\tHRJAAA\tHHHHxx\n6464\t6534\t0\t0\t4\t4\t64\t464\t464\t1464\t6464\t128\t129\tQOAAAA\tIRJAAA\tOOOOxx\n1158\t6535\t0\t2\t8\t18\t58\t158\t1158\t1158\t1158\t116\t117\tOSAAAA\tJRJAAA\tVVVVxx\n8669\t6536\t1\t1\t9\t9\t69\t669\t669\t3669\t8669\t138\t139\tLVAAAA\tKRJAAA\tAAAAxx\n3225\t6537\t1\t1\t5\t5\t25\t225\t1225\t3225\t3225\t50\t51\tBUAAAA\tLRJAAA\tHHHHxx\n1294\t6538\t0\t2\t4\t14\t94\t294\t1294\t1294\t1294\t188\t189\tUXAAAA\tMRJAAA\tOOOOxx\n2166\t6539\t0\t2\t6\t6\t66\t166\t166\t2166\t2166\t132\t133\tIFAAAA\tNRJAAA\tVVVVxx\n9328\t6540\t0\t0\t8\t8\t28\t328\t1328\t4328\t9328\t56\t57\tUUAAAA\tORJAAA\tAAAAxx\n8431\t6541\t1\t3\t1\t11\t31\t431\t431\t3431\t8431\t62\t63\tHMAAAA\tPRJAAA\tHHHHxx\n7100\t6542\t0\t0\t0\t0\t0\t100\t1100\t2100\t7100\t0\t1\tCNAAAA\tQRJAAA\tOOOOxx\n8126\t6543\t0\t2\t6\t6\t26\t126\t126\t3126\t8126\t52\t53\tOAAAAA\tRRJAAA\tVVVVxx\n2185\t6544\t1\t1\t5\t5\t85\t185\t185\t2185\t2185\t170\t171\tBGAAAA\tSRJAAA\tAAAAxx\n5697\t6545\t1\t1\t7\t17\t97\t697\t1697\t697\t5697\t194\t195\tDLAAAA\tTRJAAA\tHHHHxx\n5531\t6546\t1\t3\t1\t11\t31\t531\t1531\t531\t5531\t62\t63\tTEAAAA\tURJAAA\tOOOOxx\n3020\t6547\t0\t0\t0\t0\t20\t20\t1020\t3020\t3020\t40\t41\tEMAAAA\tVRJAAA\tVVVVxx\n3076\t6548\t0\t0\t6\t16\t76\t76\t1076\t3076\t3076\t152\t153\tIOAAAA\tWRJAAA\tAAAAxx\n9228\t6549\t0\t0\t8\t8\t28\t228\t1228\t4228\t9228\t56\t57\tYQAAAA\tXRJAAA\tHHHHxx\n1734\t6550\t0\t2\t4\t14\t34\t734\t1734\t1734\t1734\t68\t69\tSOAAAA\tYRJAAA\tOOOOxx\n7616\t6551\t0\t0\t6\t16\t16\t616\t1616\t2616\t7616\t32\t33\tYGAAAA\tZRJAAA\tVVVVxx\n9059\t6552\t1\t3\t9\t19\t59\t59\t1059\t4059\t9059\t118\t119\tLKAAAA\tASJAAA\tAAAAxx\n323\t6553\t1\t3\t3\t3\t23\t323\t323\t323\t323\t46\t47\tLMAAAA\tBSJAAA\tHHHHxx\n1283\t6554\t1\t3\t3\t3\t83\t283\t1283\t1283\t1283\t166\t167\tJXAAAA\tCSJAAA\tOOOOxx\n9535\t6555\t1\t3\t5\t15\t35\t535\t1535\t4535\t9535\t70\t71\tTCAAAA\tDSJAAA\tVVVVxx\n2580\t6556\t0\t0\t0\t0\t80\t580\t580\t2580\t2580\t160\t161\tGVAAAA\tESJAAA\tAAAAxx\n7633\t6557\t1\t1\t3\t13\t33\t633\t1633\t2633\t7633\t66\t67\tPHAAAA\tFSJAAA\tHHHHxx\n9497\t6558\t1\t1\t7\t17\t97\t497\t1497\t4497\t9497\t194\t195\tHBAAAA\tGSJAAA\tOOOOxx\n9842\t6559\t0\t2\t2\t2\t42\t842\t1842\t4842\t9842\t84\t85\tOOAAAA\tHSJAAA\tVVVVxx\n3426\t6560\t0\t2\t6\t6\t26\t426\t1426\t3426\t3426\t52\t53\tUBAAAA\tISJAAA\tAAAAxx\n7650\t6561\t0\t2\t0\t10\t50\t650\t1650\t2650\t7650\t100\t101\tGIAAAA\tJSJAAA\tHHHHxx\n9935\t6562\t1\t3\t5\t15\t35\t935\t1935\t4935\t9935\t70\t71\tDSAAAA\tKSJAAA\tOOOOxx\n9354\t6563\t0\t2\t4\t14\t54\t354\t1354\t4354\t9354\t108\t109\tUVAAAA\tLSJAAA\tVVVVxx\n5569\t6564\t1\t1\t9\t9\t69\t569\t1569\t569\t5569\t138\t139\tFGAAAA\tMSJAAA\tAAAAxx\n5765\t6565\t1\t1\t5\t5\t65\t765\t1765\t765\t5765\t130\t131\tTNAAAA\tNSJAAA\tHHHHxx\n7283\t6566\t1\t3\t3\t3\t83\t283\t1283\t2283\t7283\t166\t167\tDUAAAA\tOSJAAA\tOOOOxx\n1068\t6567\t0\t0\t8\t8\t68\t68\t1068\t1068\t1068\t136\t137\tCPAAAA\tPSJAAA\tVVVVxx\n1641\t6568\t1\t1\t1\t1\t41\t641\t1641\t1641\t1641\t82\t83\tDLAAAA\tQSJAAA\tAAAAxx\n1688\t6569\t0\t0\t8\t8\t88\t688\t1688\t1688\t1688\t176\t177\tYMAAAA\tRSJAAA\tHHHHxx\n1133\t6570\t1\t1\t3\t13\t33\t133\t1133\t1133\t1133\t66\t67\tPRAAAA\tSSJAAA\tOOOOxx\n4493\t6571\t1\t1\t3\t13\t93\t493\t493\t4493\t4493\t186\t187\tVQAAAA\tTSJAAA\tVVVVxx\n3354\t6572\t0\t2\t4\t14\t54\t354\t1354\t3354\t3354\t108\t109\tAZAAAA\tUSJAAA\tAAAAxx\n4029\t6573\t1\t1\t9\t9\t29\t29\t29\t4029\t4029\t58\t59\tZYAAAA\tVSJAAA\tHHHHxx\n6704\t6574\t0\t0\t4\t4\t4\t704\t704\t1704\t6704\t8\t9\tWXAAAA\tWSJAAA\tOOOOxx\n3221\t6575\t1\t1\t1\t1\t21\t221\t1221\t3221\t3221\t42\t43\tXTAAAA\tXSJAAA\tVVVVxx\n9432\t6576\t0\t0\t2\t12\t32\t432\t1432\t4432\t9432\t64\t65\tUYAAAA\tYSJAAA\tAAAAxx\n6990\t6577\t0\t2\t0\t10\t90\t990\t990\t1990\t6990\t180\t181\tWIAAAA\tZSJAAA\tHHHHxx\n1760\t6578\t0\t0\t0\t0\t60\t760\t1760\t1760\t1760\t120\t121\tSPAAAA\tATJAAA\tOOOOxx\n4754\t6579\t0\t2\t4\t14\t54\t754\t754\t4754\t4754\t108\t109\tWAAAAA\tBTJAAA\tVVVVxx\n7724\t6580\t0\t0\t4\t4\t24\t724\t1724\t2724\t7724\t48\t49\tCLAAAA\tCTJAAA\tAAAAxx\n9487\t6581\t1\t3\t7\t7\t87\t487\t1487\t4487\t9487\t174\t175\tXAAAAA\tDTJAAA\tHHHHxx\n166\t6582\t0\t2\t6\t6\t66\t166\t166\t166\t166\t132\t133\tKGAAAA\tETJAAA\tOOOOxx\n5479\t6583\t1\t3\t9\t19\t79\t479\t1479\t479\t5479\t158\t159\tTCAAAA\tFTJAAA\tVVVVxx\n8744\t6584\t0\t0\t4\t4\t44\t744\t744\t3744\t8744\t88\t89\tIYAAAA\tGTJAAA\tAAAAxx\n5746\t6585\t0\t2\t6\t6\t46\t746\t1746\t746\t5746\t92\t93\tANAAAA\tHTJAAA\tHHHHxx\n907\t6586\t1\t3\t7\t7\t7\t907\t907\t907\t907\t14\t15\tXIAAAA\tITJAAA\tOOOOxx\n3968\t6587\t0\t0\t8\t8\t68\t968\t1968\t3968\t3968\t136\t137\tQWAAAA\tJTJAAA\tVVVVxx\n5721\t6588\t1\t1\t1\t1\t21\t721\t1721\t721\t5721\t42\t43\tBMAAAA\tKTJAAA\tAAAAxx\n6738\t6589\t0\t2\t8\t18\t38\t738\t738\t1738\t6738\t76\t77\tEZAAAA\tLTJAAA\tHHHHxx\n4097\t6590\t1\t1\t7\t17\t97\t97\t97\t4097\t4097\t194\t195\tPBAAAA\tMTJAAA\tOOOOxx\n8456\t6591\t0\t0\t6\t16\t56\t456\t456\t3456\t8456\t112\t113\tGNAAAA\tNTJAAA\tVVVVxx\n1269\t6592\t1\t1\t9\t9\t69\t269\t1269\t1269\t1269\t138\t139\tVWAAAA\tOTJAAA\tAAAAxx\n7997\t6593\t1\t1\t7\t17\t97\t997\t1997\t2997\t7997\t194\t195\tPVAAAA\tPTJAAA\tHHHHxx\n9457\t6594\t1\t1\t7\t17\t57\t457\t1457\t4457\t9457\t114\t115\tTZAAAA\tQTJAAA\tOOOOxx\n1159\t6595\t1\t3\t9\t19\t59\t159\t1159\t1159\t1159\t118\t119\tPSAAAA\tRTJAAA\tVVVVxx\n1631\t6596\t1\t3\t1\t11\t31\t631\t1631\t1631\t1631\t62\t63\tTKAAAA\tSTJAAA\tAAAAxx\n2019\t6597\t1\t3\t9\t19\t19\t19\t19\t2019\t2019\t38\t39\tRZAAAA\tTTJAAA\tHHHHxx\n3186\t6598\t0\t2\t6\t6\t86\t186\t1186\t3186\t3186\t172\t173\tOSAAAA\tUTJAAA\tOOOOxx\n5587\t6599\t1\t3\t7\t7\t87\t587\t1587\t587\t5587\t174\t175\tXGAAAA\tVTJAAA\tVVVVxx\n9172\t6600\t0\t0\t2\t12\t72\t172\t1172\t4172\t9172\t144\t145\tUOAAAA\tWTJAAA\tAAAAxx\n5589\t6601\t1\t1\t9\t9\t89\t589\t1589\t589\t5589\t178\t179\tZGAAAA\tXTJAAA\tHHHHxx\n5103\t6602\t1\t3\t3\t3\t3\t103\t1103\t103\t5103\t6\t7\tHOAAAA\tYTJAAA\tOOOOxx\n3177\t6603\t1\t1\t7\t17\t77\t177\t1177\t3177\t3177\t154\t155\tFSAAAA\tZTJAAA\tVVVVxx\n8887\t6604\t1\t3\t7\t7\t87\t887\t887\t3887\t8887\t174\t175\tVDAAAA\tAUJAAA\tAAAAxx\n12\t6605\t0\t0\t2\t12\t12\t12\t12\t12\t12\t24\t25\tMAAAAA\tBUJAAA\tHHHHxx\n8575\t6606\t1\t3\t5\t15\t75\t575\t575\t3575\t8575\t150\t151\tVRAAAA\tCUJAAA\tOOOOxx\n4335\t6607\t1\t3\t5\t15\t35\t335\t335\t4335\t4335\t70\t71\tTKAAAA\tDUJAAA\tVVVVxx\n4581\t6608\t1\t1\t1\t1\t81\t581\t581\t4581\t4581\t162\t163\tFUAAAA\tEUJAAA\tAAAAxx\n4444\t6609\t0\t0\t4\t4\t44\t444\t444\t4444\t4444\t88\t89\tYOAAAA\tFUJAAA\tHHHHxx\n7978\t6610\t0\t2\t8\t18\t78\t978\t1978\t2978\t7978\t156\t157\tWUAAAA\tGUJAAA\tOOOOxx\n3081\t6611\t1\t1\t1\t1\t81\t81\t1081\t3081\t3081\t162\t163\tNOAAAA\tHUJAAA\tVVVVxx\n4059\t6612\t1\t3\t9\t19\t59\t59\t59\t4059\t4059\t118\t119\tDAAAAA\tIUJAAA\tAAAAxx\n5711\t6613\t1\t3\t1\t11\t11\t711\t1711\t711\t5711\t22\t23\tRLAAAA\tJUJAAA\tHHHHxx\n7069\t6614\t1\t1\t9\t9\t69\t69\t1069\t2069\t7069\t138\t139\tXLAAAA\tKUJAAA\tOOOOxx\n6150\t6615\t0\t2\t0\t10\t50\t150\t150\t1150\t6150\t100\t101\tOCAAAA\tLUJAAA\tVVVVxx\n9550\t6616\t0\t2\t0\t10\t50\t550\t1550\t4550\t9550\t100\t101\tIDAAAA\tMUJAAA\tAAAAxx\n7087\t6617\t1\t3\t7\t7\t87\t87\t1087\t2087\t7087\t174\t175\tPMAAAA\tNUJAAA\tHHHHxx\n9557\t6618\t1\t1\t7\t17\t57\t557\t1557\t4557\t9557\t114\t115\tPDAAAA\tOUJAAA\tOOOOxx\n7856\t6619\t0\t0\t6\t16\t56\t856\t1856\t2856\t7856\t112\t113\tEQAAAA\tPUJAAA\tVVVVxx\n1115\t6620\t1\t3\t5\t15\t15\t115\t1115\t1115\t1115\t30\t31\tXQAAAA\tQUJAAA\tAAAAxx\n1086\t6621\t0\t2\t6\t6\t86\t86\t1086\t1086\t1086\t172\t173\tUPAAAA\tRUJAAA\tHHHHxx\n5048\t6622\t0\t0\t8\t8\t48\t48\t1048\t48\t5048\t96\t97\tEMAAAA\tSUJAAA\tOOOOxx\n5168\t6623\t0\t0\t8\t8\t68\t168\t1168\t168\t5168\t136\t137\tUQAAAA\tTUJAAA\tVVVVxx\n6029\t6624\t1\t1\t9\t9\t29\t29\t29\t1029\t6029\t58\t59\tXXAAAA\tUUJAAA\tAAAAxx\n546\t6625\t0\t2\t6\t6\t46\t546\t546\t546\t546\t92\t93\tAVAAAA\tVUJAAA\tHHHHxx\n2908\t6626\t0\t0\t8\t8\t8\t908\t908\t2908\t2908\t16\t17\tWHAAAA\tWUJAAA\tOOOOxx\n779\t6627\t1\t3\t9\t19\t79\t779\t779\t779\t779\t158\t159\tZDAAAA\tXUJAAA\tVVVVxx\n4202\t6628\t0\t2\t2\t2\t2\t202\t202\t4202\t4202\t4\t5\tQFAAAA\tYUJAAA\tAAAAxx\n9984\t6629\t0\t0\t4\t4\t84\t984\t1984\t4984\t9984\t168\t169\tAUAAAA\tZUJAAA\tHHHHxx\n4730\t6630\t0\t2\t0\t10\t30\t730\t730\t4730\t4730\t60\t61\tYZAAAA\tAVJAAA\tOOOOxx\n6517\t6631\t1\t1\t7\t17\t17\t517\t517\t1517\t6517\t34\t35\tRQAAAA\tBVJAAA\tVVVVxx\n8410\t6632\t0\t2\t0\t10\t10\t410\t410\t3410\t8410\t20\t21\tMLAAAA\tCVJAAA\tAAAAxx\n4793\t6633\t1\t1\t3\t13\t93\t793\t793\t4793\t4793\t186\t187\tJCAAAA\tDVJAAA\tHHHHxx\n3431\t6634\t1\t3\t1\t11\t31\t431\t1431\t3431\t3431\t62\t63\tZBAAAA\tEVJAAA\tOOOOxx\n2481\t6635\t1\t1\t1\t1\t81\t481\t481\t2481\t2481\t162\t163\tLRAAAA\tFVJAAA\tVVVVxx\n3905\t6636\t1\t1\t5\t5\t5\t905\t1905\t3905\t3905\t10\t11\tFUAAAA\tGVJAAA\tAAAAxx\n8807\t6637\t1\t3\t7\t7\t7\t807\t807\t3807\t8807\t14\t15\tTAAAAA\tHVJAAA\tHHHHxx\n2660\t6638\t0\t0\t0\t0\t60\t660\t660\t2660\t2660\t120\t121\tIYAAAA\tIVJAAA\tOOOOxx\n4985\t6639\t1\t1\t5\t5\t85\t985\t985\t4985\t4985\t170\t171\tTJAAAA\tJVJAAA\tVVVVxx\n3080\t6640\t0\t0\t0\t0\t80\t80\t1080\t3080\t3080\t160\t161\tMOAAAA\tKVJAAA\tAAAAxx\n1090\t6641\t0\t2\t0\t10\t90\t90\t1090\t1090\t1090\t180\t181\tYPAAAA\tLVJAAA\tHHHHxx\n6917\t6642\t1\t1\t7\t17\t17\t917\t917\t1917\t6917\t34\t35\tBGAAAA\tMVJAAA\tOOOOxx\n5177\t6643\t1\t1\t7\t17\t77\t177\t1177\t177\t5177\t154\t155\tDRAAAA\tNVJAAA\tVVVVxx\n2729\t6644\t1\t1\t9\t9\t29\t729\t729\t2729\t2729\t58\t59\tZAAAAA\tOVJAAA\tAAAAxx\n9706\t6645\t0\t2\t6\t6\t6\t706\t1706\t4706\t9706\t12\t13\tIJAAAA\tPVJAAA\tHHHHxx\n9929\t6646\t1\t1\t9\t9\t29\t929\t1929\t4929\t9929\t58\t59\tXRAAAA\tQVJAAA\tOOOOxx\n1547\t6647\t1\t3\t7\t7\t47\t547\t1547\t1547\t1547\t94\t95\tNHAAAA\tRVJAAA\tVVVVxx\n2798\t6648\t0\t2\t8\t18\t98\t798\t798\t2798\t2798\t196\t197\tQDAAAA\tSVJAAA\tAAAAxx\n4420\t6649\t0\t0\t0\t0\t20\t420\t420\t4420\t4420\t40\t41\tAOAAAA\tTVJAAA\tHHHHxx\n6771\t6650\t1\t3\t1\t11\t71\t771\t771\t1771\t6771\t142\t143\tLAAAAA\tUVJAAA\tOOOOxx\n2004\t6651\t0\t0\t4\t4\t4\t4\t4\t2004\t2004\t8\t9\tCZAAAA\tVVJAAA\tVVVVxx\n8686\t6652\t0\t2\t6\t6\t86\t686\t686\t3686\t8686\t172\t173\tCWAAAA\tWVJAAA\tAAAAxx\n3663\t6653\t1\t3\t3\t3\t63\t663\t1663\t3663\t3663\t126\t127\tXKAAAA\tXVJAAA\tHHHHxx\n806\t6654\t0\t2\t6\t6\t6\t806\t806\t806\t806\t12\t13\tAFAAAA\tYVJAAA\tOOOOxx\n4309\t6655\t1\t1\t9\t9\t9\t309\t309\t4309\t4309\t18\t19\tTJAAAA\tZVJAAA\tVVVVxx\n7443\t6656\t1\t3\t3\t3\t43\t443\t1443\t2443\t7443\t86\t87\tHAAAAA\tAWJAAA\tAAAAxx\n5779\t6657\t1\t3\t9\t19\t79\t779\t1779\t779\t5779\t158\t159\tHOAAAA\tBWJAAA\tHHHHxx\n8821\t6658\t1\t1\t1\t1\t21\t821\t821\t3821\t8821\t42\t43\tHBAAAA\tCWJAAA\tOOOOxx\n4198\t6659\t0\t2\t8\t18\t98\t198\t198\t4198\t4198\t196\t197\tMFAAAA\tDWJAAA\tVVVVxx\n8115\t6660\t1\t3\t5\t15\t15\t115\t115\t3115\t8115\t30\t31\tDAAAAA\tEWJAAA\tAAAAxx\n9554\t6661\t0\t2\t4\t14\t54\t554\t1554\t4554\t9554\t108\t109\tMDAAAA\tFWJAAA\tHHHHxx\n8956\t6662\t0\t0\t6\t16\t56\t956\t956\t3956\t8956\t112\t113\tMGAAAA\tGWJAAA\tOOOOxx\n4733\t6663\t1\t1\t3\t13\t33\t733\t733\t4733\t4733\t66\t67\tBAAAAA\tHWJAAA\tVVVVxx\n5417\t6664\t1\t1\t7\t17\t17\t417\t1417\t417\t5417\t34\t35\tJAAAAA\tIWJAAA\tAAAAxx\n4792\t6665\t0\t0\t2\t12\t92\t792\t792\t4792\t4792\t184\t185\tICAAAA\tJWJAAA\tHHHHxx\n462\t6666\t0\t2\t2\t2\t62\t462\t462\t462\t462\t124\t125\tURAAAA\tKWJAAA\tOOOOxx\n3687\t6667\t1\t3\t7\t7\t87\t687\t1687\t3687\t3687\t174\t175\tVLAAAA\tLWJAAA\tVVVVxx\n2013\t6668\t1\t1\t3\t13\t13\t13\t13\t2013\t2013\t26\t27\tLZAAAA\tMWJAAA\tAAAAxx\n5386\t6669\t0\t2\t6\t6\t86\t386\t1386\t386\t5386\t172\t173\tEZAAAA\tNWJAAA\tHHHHxx\n2816\t6670\t0\t0\t6\t16\t16\t816\t816\t2816\t2816\t32\t33\tIEAAAA\tOWJAAA\tOOOOxx\n7827\t6671\t1\t3\t7\t7\t27\t827\t1827\t2827\t7827\t54\t55\tBPAAAA\tPWJAAA\tVVVVxx\n5077\t6672\t1\t1\t7\t17\t77\t77\t1077\t77\t5077\t154\t155\tHNAAAA\tQWJAAA\tAAAAxx\n6039\t6673\t1\t3\t9\t19\t39\t39\t39\t1039\t6039\t78\t79\tHYAAAA\tRWJAAA\tHHHHxx\n215\t6674\t1\t3\t5\t15\t15\t215\t215\t215\t215\t30\t31\tHIAAAA\tSWJAAA\tOOOOxx\n855\t6675\t1\t3\t5\t15\t55\t855\t855\t855\t855\t110\t111\tXGAAAA\tTWJAAA\tVVVVxx\n9692\t6676\t0\t0\t2\t12\t92\t692\t1692\t4692\t9692\t184\t185\tUIAAAA\tUWJAAA\tAAAAxx\n8391\t6677\t1\t3\t1\t11\t91\t391\t391\t3391\t8391\t182\t183\tTKAAAA\tVWJAAA\tHHHHxx\n8424\t6678\t0\t0\t4\t4\t24\t424\t424\t3424\t8424\t48\t49\tAMAAAA\tWWJAAA\tOOOOxx\n6331\t6679\t1\t3\t1\t11\t31\t331\t331\t1331\t6331\t62\t63\tNJAAAA\tXWJAAA\tVVVVxx\n6561\t6680\t1\t1\t1\t1\t61\t561\t561\t1561\t6561\t122\t123\tJSAAAA\tYWJAAA\tAAAAxx\n8955\t6681\t1\t3\t5\t15\t55\t955\t955\t3955\t8955\t110\t111\tLGAAAA\tZWJAAA\tHHHHxx\n1764\t6682\t0\t0\t4\t4\t64\t764\t1764\t1764\t1764\t128\t129\tWPAAAA\tAXJAAA\tOOOOxx\n6623\t6683\t1\t3\t3\t3\t23\t623\t623\t1623\t6623\t46\t47\tTUAAAA\tBXJAAA\tVVVVxx\n2900\t6684\t0\t0\t0\t0\t0\t900\t900\t2900\t2900\t0\t1\tOHAAAA\tCXJAAA\tAAAAxx\n7048\t6685\t0\t0\t8\t8\t48\t48\t1048\t2048\t7048\t96\t97\tCLAAAA\tDXJAAA\tHHHHxx\n3843\t6686\t1\t3\t3\t3\t43\t843\t1843\t3843\t3843\t86\t87\tVRAAAA\tEXJAAA\tOOOOxx\n4855\t6687\t1\t3\t5\t15\t55\t855\t855\t4855\t4855\t110\t111\tTEAAAA\tFXJAAA\tVVVVxx\n7383\t6688\t1\t3\t3\t3\t83\t383\t1383\t2383\t7383\t166\t167\tZXAAAA\tGXJAAA\tAAAAxx\n7765\t6689\t1\t1\t5\t5\t65\t765\t1765\t2765\t7765\t130\t131\tRMAAAA\tHXJAAA\tHHHHxx\n1125\t6690\t1\t1\t5\t5\t25\t125\t1125\t1125\t1125\t50\t51\tHRAAAA\tIXJAAA\tOOOOxx\n755\t6691\t1\t3\t5\t15\t55\t755\t755\t755\t755\t110\t111\tBDAAAA\tJXJAAA\tVVVVxx\n2995\t6692\t1\t3\t5\t15\t95\t995\t995\t2995\t2995\t190\t191\tFLAAAA\tKXJAAA\tAAAAxx\n8907\t6693\t1\t3\t7\t7\t7\t907\t907\t3907\t8907\t14\t15\tPEAAAA\tLXJAAA\tHHHHxx\n9357\t6694\t1\t1\t7\t17\t57\t357\t1357\t4357\t9357\t114\t115\tXVAAAA\tMXJAAA\tOOOOxx\n4469\t6695\t1\t1\t9\t9\t69\t469\t469\t4469\t4469\t138\t139\tXPAAAA\tNXJAAA\tVVVVxx\n2147\t6696\t1\t3\t7\t7\t47\t147\t147\t2147\t2147\t94\t95\tPEAAAA\tOXJAAA\tAAAAxx\n2952\t6697\t0\t0\t2\t12\t52\t952\t952\t2952\t2952\t104\t105\tOJAAAA\tPXJAAA\tHHHHxx\n1324\t6698\t0\t0\t4\t4\t24\t324\t1324\t1324\t1324\t48\t49\tYYAAAA\tQXJAAA\tOOOOxx\n1173\t6699\t1\t1\t3\t13\t73\t173\t1173\t1173\t1173\t146\t147\tDTAAAA\tRXJAAA\tVVVVxx\n3169\t6700\t1\t1\t9\t9\t69\t169\t1169\t3169\t3169\t138\t139\tXRAAAA\tSXJAAA\tAAAAxx\n5149\t6701\t1\t1\t9\t9\t49\t149\t1149\t149\t5149\t98\t99\tBQAAAA\tTXJAAA\tHHHHxx\n9660\t6702\t0\t0\t0\t0\t60\t660\t1660\t4660\t9660\t120\t121\tOHAAAA\tUXJAAA\tOOOOxx\n3446\t6703\t0\t2\t6\t6\t46\t446\t1446\t3446\t3446\t92\t93\tOCAAAA\tVXJAAA\tVVVVxx\n6988\t6704\t0\t0\t8\t8\t88\t988\t988\t1988\t6988\t176\t177\tUIAAAA\tWXJAAA\tAAAAxx\n5829\t6705\t1\t1\t9\t9\t29\t829\t1829\t829\t5829\t58\t59\tFQAAAA\tXXJAAA\tHHHHxx\n7166\t6706\t0\t2\t6\t6\t66\t166\t1166\t2166\t7166\t132\t133\tQPAAAA\tYXJAAA\tOOOOxx\n3940\t6707\t0\t0\t0\t0\t40\t940\t1940\t3940\t3940\t80\t81\tOVAAAA\tZXJAAA\tVVVVxx\n2645\t6708\t1\t1\t5\t5\t45\t645\t645\t2645\t2645\t90\t91\tTXAAAA\tAYJAAA\tAAAAxx\n478\t6709\t0\t2\t8\t18\t78\t478\t478\t478\t478\t156\t157\tKSAAAA\tBYJAAA\tHHHHxx\n1156\t6710\t0\t0\t6\t16\t56\t156\t1156\t1156\t1156\t112\t113\tMSAAAA\tCYJAAA\tOOOOxx\n2731\t6711\t1\t3\t1\t11\t31\t731\t731\t2731\t2731\t62\t63\tBBAAAA\tDYJAAA\tVVVVxx\n5637\t6712\t1\t1\t7\t17\t37\t637\t1637\t637\t5637\t74\t75\tVIAAAA\tEYJAAA\tAAAAxx\n7517\t6713\t1\t1\t7\t17\t17\t517\t1517\t2517\t7517\t34\t35\tDDAAAA\tFYJAAA\tHHHHxx\n5331\t6714\t1\t3\t1\t11\t31\t331\t1331\t331\t5331\t62\t63\tBXAAAA\tGYJAAA\tOOOOxx\n9640\t6715\t0\t0\t0\t0\t40\t640\t1640\t4640\t9640\t80\t81\tUGAAAA\tHYJAAA\tVVVVxx\n4108\t6716\t0\t0\t8\t8\t8\t108\t108\t4108\t4108\t16\t17\tACAAAA\tIYJAAA\tAAAAxx\n1087\t6717\t1\t3\t7\t7\t87\t87\t1087\t1087\t1087\t174\t175\tVPAAAA\tJYJAAA\tHHHHxx\n8017\t6718\t1\t1\t7\t17\t17\t17\t17\t3017\t8017\t34\t35\tJWAAAA\tKYJAAA\tOOOOxx\n8795\t6719\t1\t3\t5\t15\t95\t795\t795\t3795\t8795\t190\t191\tHAAAAA\tLYJAAA\tVVVVxx\n7060\t6720\t0\t0\t0\t0\t60\t60\t1060\t2060\t7060\t120\t121\tOLAAAA\tMYJAAA\tAAAAxx\n9450\t6721\t0\t2\t0\t10\t50\t450\t1450\t4450\t9450\t100\t101\tMZAAAA\tNYJAAA\tHHHHxx\n390\t6722\t0\t2\t0\t10\t90\t390\t390\t390\t390\t180\t181\tAPAAAA\tOYJAAA\tOOOOxx\n66\t6723\t0\t2\t6\t6\t66\t66\t66\t66\t66\t132\t133\tOCAAAA\tPYJAAA\tVVVVxx\n8789\t6724\t1\t1\t9\t9\t89\t789\t789\t3789\t8789\t178\t179\tBAAAAA\tQYJAAA\tAAAAxx\n9260\t6725\t0\t0\t0\t0\t60\t260\t1260\t4260\t9260\t120\t121\tESAAAA\tRYJAAA\tHHHHxx\n6679\t6726\t1\t3\t9\t19\t79\t679\t679\t1679\t6679\t158\t159\tXWAAAA\tSYJAAA\tOOOOxx\n9052\t6727\t0\t0\t2\t12\t52\t52\t1052\t4052\t9052\t104\t105\tEKAAAA\tTYJAAA\tVVVVxx\n9561\t6728\t1\t1\t1\t1\t61\t561\t1561\t4561\t9561\t122\t123\tTDAAAA\tUYJAAA\tAAAAxx\n9725\t6729\t1\t1\t5\t5\t25\t725\t1725\t4725\t9725\t50\t51\tBKAAAA\tVYJAAA\tHHHHxx\n6298\t6730\t0\t2\t8\t18\t98\t298\t298\t1298\t6298\t196\t197\tGIAAAA\tWYJAAA\tOOOOxx\n8654\t6731\t0\t2\t4\t14\t54\t654\t654\t3654\t8654\t108\t109\tWUAAAA\tXYJAAA\tVVVVxx\n8725\t6732\t1\t1\t5\t5\t25\t725\t725\t3725\t8725\t50\t51\tPXAAAA\tYYJAAA\tAAAAxx\n9377\t6733\t1\t1\t7\t17\t77\t377\t1377\t4377\t9377\t154\t155\tRWAAAA\tZYJAAA\tHHHHxx\n3807\t6734\t1\t3\t7\t7\t7\t807\t1807\t3807\t3807\t14\t15\tLQAAAA\tAZJAAA\tOOOOxx\n8048\t6735\t0\t0\t8\t8\t48\t48\t48\t3048\t8048\t96\t97\tOXAAAA\tBZJAAA\tVVVVxx\n764\t6736\t0\t0\t4\t4\t64\t764\t764\t764\t764\t128\t129\tKDAAAA\tCZJAAA\tAAAAxx\n9702\t6737\t0\t2\t2\t2\t2\t702\t1702\t4702\t9702\t4\t5\tEJAAAA\tDZJAAA\tHHHHxx\n8060\t6738\t0\t0\t0\t0\t60\t60\t60\t3060\t8060\t120\t121\tAYAAAA\tEZJAAA\tOOOOxx\n6371\t6739\t1\t3\t1\t11\t71\t371\t371\t1371\t6371\t142\t143\tBLAAAA\tFZJAAA\tVVVVxx\n5237\t6740\t1\t1\t7\t17\t37\t237\t1237\t237\t5237\t74\t75\tLTAAAA\tGZJAAA\tAAAAxx\n743\t6741\t1\t3\t3\t3\t43\t743\t743\t743\t743\t86\t87\tPCAAAA\tHZJAAA\tHHHHxx\n7395\t6742\t1\t3\t5\t15\t95\t395\t1395\t2395\t7395\t190\t191\tLYAAAA\tIZJAAA\tOOOOxx\n3365\t6743\t1\t1\t5\t5\t65\t365\t1365\t3365\t3365\t130\t131\tLZAAAA\tJZJAAA\tVVVVxx\n6667\t6744\t1\t3\t7\t7\t67\t667\t667\t1667\t6667\t134\t135\tLWAAAA\tKZJAAA\tAAAAxx\n3445\t6745\t1\t1\t5\t5\t45\t445\t1445\t3445\t3445\t90\t91\tNCAAAA\tLZJAAA\tHHHHxx\n4019\t6746\t1\t3\t9\t19\t19\t19\t19\t4019\t4019\t38\t39\tPYAAAA\tMZJAAA\tOOOOxx\n7035\t6747\t1\t3\t5\t15\t35\t35\t1035\t2035\t7035\t70\t71\tPKAAAA\tNZJAAA\tVVVVxx\n5274\t6748\t0\t2\t4\t14\t74\t274\t1274\t274\t5274\t148\t149\tWUAAAA\tOZJAAA\tAAAAxx\n519\t6749\t1\t3\t9\t19\t19\t519\t519\t519\t519\t38\t39\tZTAAAA\tPZJAAA\tHHHHxx\n2801\t6750\t1\t1\t1\t1\t1\t801\t801\t2801\t2801\t2\t3\tTDAAAA\tQZJAAA\tOOOOxx\n3320\t6751\t0\t0\t0\t0\t20\t320\t1320\t3320\t3320\t40\t41\tSXAAAA\tRZJAAA\tVVVVxx\n3153\t6752\t1\t1\t3\t13\t53\t153\t1153\t3153\t3153\t106\t107\tHRAAAA\tSZJAAA\tAAAAxx\n7680\t6753\t0\t0\t0\t0\t80\t680\t1680\t2680\t7680\t160\t161\tKJAAAA\tTZJAAA\tHHHHxx\n8942\t6754\t0\t2\t2\t2\t42\t942\t942\t3942\t8942\t84\t85\tYFAAAA\tUZJAAA\tOOOOxx\n3195\t6755\t1\t3\t5\t15\t95\t195\t1195\t3195\t3195\t190\t191\tXSAAAA\tVZJAAA\tVVVVxx\n2287\t6756\t1\t3\t7\t7\t87\t287\t287\t2287\t2287\t174\t175\tZJAAAA\tWZJAAA\tAAAAxx\n8325\t6757\t1\t1\t5\t5\t25\t325\t325\t3325\t8325\t50\t51\tFIAAAA\tXZJAAA\tHHHHxx\n2603\t6758\t1\t3\t3\t3\t3\t603\t603\t2603\t2603\t6\t7\tDWAAAA\tYZJAAA\tOOOOxx\n5871\t6759\t1\t3\t1\t11\t71\t871\t1871\t871\t5871\t142\t143\tVRAAAA\tZZJAAA\tVVVVxx\n1773\t6760\t1\t1\t3\t13\t73\t773\t1773\t1773\t1773\t146\t147\tFQAAAA\tAAKAAA\tAAAAxx\n3323\t6761\t1\t3\t3\t3\t23\t323\t1323\t3323\t3323\t46\t47\tVXAAAA\tBAKAAA\tHHHHxx\n2053\t6762\t1\t1\t3\t13\t53\t53\t53\t2053\t2053\t106\t107\tZAAAAA\tCAKAAA\tOOOOxx\n4062\t6763\t0\t2\t2\t2\t62\t62\t62\t4062\t4062\t124\t125\tGAAAAA\tDAKAAA\tVVVVxx\n4611\t6764\t1\t3\t1\t11\t11\t611\t611\t4611\t4611\t22\t23\tJVAAAA\tEAKAAA\tAAAAxx\n3451\t6765\t1\t3\t1\t11\t51\t451\t1451\t3451\t3451\t102\t103\tTCAAAA\tFAKAAA\tHHHHxx\n1819\t6766\t1\t3\t9\t19\t19\t819\t1819\t1819\t1819\t38\t39\tZRAAAA\tGAKAAA\tOOOOxx\n9806\t6767\t0\t2\t6\t6\t6\t806\t1806\t4806\t9806\t12\t13\tENAAAA\tHAKAAA\tVVVVxx\n6619\t6768\t1\t3\t9\t19\t19\t619\t619\t1619\t6619\t38\t39\tPUAAAA\tIAKAAA\tAAAAxx\n1031\t6769\t1\t3\t1\t11\t31\t31\t1031\t1031\t1031\t62\t63\tRNAAAA\tJAKAAA\tHHHHxx\n1865\t6770\t1\t1\t5\t5\t65\t865\t1865\t1865\t1865\t130\t131\tTTAAAA\tKAKAAA\tOOOOxx\n6282\t6771\t0\t2\t2\t2\t82\t282\t282\t1282\t6282\t164\t165\tQHAAAA\tLAKAAA\tVVVVxx\n1178\t6772\t0\t2\t8\t18\t78\t178\t1178\t1178\t1178\t156\t157\tITAAAA\tMAKAAA\tAAAAxx\n8007\t6773\t1\t3\t7\t7\t7\t7\t7\t3007\t8007\t14\t15\tZVAAAA\tNAKAAA\tHHHHxx\n9126\t6774\t0\t2\t6\t6\t26\t126\t1126\t4126\t9126\t52\t53\tANAAAA\tOAKAAA\tOOOOxx\n9113\t6775\t1\t1\t3\t13\t13\t113\t1113\t4113\t9113\t26\t27\tNMAAAA\tPAKAAA\tVVVVxx\n537\t6776\t1\t1\t7\t17\t37\t537\t537\t537\t537\t74\t75\tRUAAAA\tQAKAAA\tAAAAxx\n6208\t6777\t0\t0\t8\t8\t8\t208\t208\t1208\t6208\t16\t17\tUEAAAA\tRAKAAA\tHHHHxx\n1626\t6778\t0\t2\t6\t6\t26\t626\t1626\t1626\t1626\t52\t53\tOKAAAA\tSAKAAA\tOOOOxx\n7188\t6779\t0\t0\t8\t8\t88\t188\t1188\t2188\t7188\t176\t177\tMQAAAA\tTAKAAA\tVVVVxx\n9216\t6780\t0\t0\t6\t16\t16\t216\t1216\t4216\t9216\t32\t33\tMQAAAA\tUAKAAA\tAAAAxx\n6134\t6781\t0\t2\t4\t14\t34\t134\t134\t1134\t6134\t68\t69\tYBAAAA\tVAKAAA\tHHHHxx\n2074\t6782\t0\t2\t4\t14\t74\t74\t74\t2074\t2074\t148\t149\tUBAAAA\tWAKAAA\tOOOOxx\n6369\t6783\t1\t1\t9\t9\t69\t369\t369\t1369\t6369\t138\t139\tZKAAAA\tXAKAAA\tVVVVxx\n9306\t6784\t0\t2\t6\t6\t6\t306\t1306\t4306\t9306\t12\t13\tYTAAAA\tYAKAAA\tAAAAxx\n3155\t6785\t1\t3\t5\t15\t55\t155\t1155\t3155\t3155\t110\t111\tJRAAAA\tZAKAAA\tHHHHxx\n3611\t6786\t1\t3\t1\t11\t11\t611\t1611\t3611\t3611\t22\t23\tXIAAAA\tABKAAA\tOOOOxx\n6530\t6787\t0\t2\t0\t10\t30\t530\t530\t1530\t6530\t60\t61\tERAAAA\tBBKAAA\tVVVVxx\n6979\t6788\t1\t3\t9\t19\t79\t979\t979\t1979\t6979\t158\t159\tLIAAAA\tCBKAAA\tAAAAxx\n9129\t6789\t1\t1\t9\t9\t29\t129\t1129\t4129\t9129\t58\t59\tDNAAAA\tDBKAAA\tHHHHxx\n8013\t6790\t1\t1\t3\t13\t13\t13\t13\t3013\t8013\t26\t27\tFWAAAA\tEBKAAA\tOOOOxx\n6926\t6791\t0\t2\t6\t6\t26\t926\t926\t1926\t6926\t52\t53\tKGAAAA\tFBKAAA\tVVVVxx\n1877\t6792\t1\t1\t7\t17\t77\t877\t1877\t1877\t1877\t154\t155\tFUAAAA\tGBKAAA\tAAAAxx\n1882\t6793\t0\t2\t2\t2\t82\t882\t1882\t1882\t1882\t164\t165\tKUAAAA\tHBKAAA\tHHHHxx\n6720\t6794\t0\t0\t0\t0\t20\t720\t720\t1720\t6720\t40\t41\tMYAAAA\tIBKAAA\tOOOOxx\n690\t6795\t0\t2\t0\t10\t90\t690\t690\t690\t690\t180\t181\tOAAAAA\tJBKAAA\tVVVVxx\n143\t6796\t1\t3\t3\t3\t43\t143\t143\t143\t143\t86\t87\tNFAAAA\tKBKAAA\tAAAAxx\n7241\t6797\t1\t1\t1\t1\t41\t241\t1241\t2241\t7241\t82\t83\tNSAAAA\tLBKAAA\tHHHHxx\n6461\t6798\t1\t1\t1\t1\t61\t461\t461\t1461\t6461\t122\t123\tNOAAAA\tMBKAAA\tOOOOxx\n2258\t6799\t0\t2\t8\t18\t58\t258\t258\t2258\t2258\t116\t117\tWIAAAA\tNBKAAA\tVVVVxx\n2280\t6800\t0\t0\t0\t0\t80\t280\t280\t2280\t2280\t160\t161\tSJAAAA\tOBKAAA\tAAAAxx\n7556\t6801\t0\t0\t6\t16\t56\t556\t1556\t2556\t7556\t112\t113\tQEAAAA\tPBKAAA\tHHHHxx\n1038\t6802\t0\t2\t8\t18\t38\t38\t1038\t1038\t1038\t76\t77\tYNAAAA\tQBKAAA\tOOOOxx\n2634\t6803\t0\t2\t4\t14\t34\t634\t634\t2634\t2634\t68\t69\tIXAAAA\tRBKAAA\tVVVVxx\n7847\t6804\t1\t3\t7\t7\t47\t847\t1847\t2847\t7847\t94\t95\tVPAAAA\tSBKAAA\tAAAAxx\n4415\t6805\t1\t3\t5\t15\t15\t415\t415\t4415\t4415\t30\t31\tVNAAAA\tTBKAAA\tHHHHxx\n1933\t6806\t1\t1\t3\t13\t33\t933\t1933\t1933\t1933\t66\t67\tJWAAAA\tUBKAAA\tOOOOxx\n8034\t6807\t0\t2\t4\t14\t34\t34\t34\t3034\t8034\t68\t69\tAXAAAA\tVBKAAA\tVVVVxx\n9233\t6808\t1\t1\t3\t13\t33\t233\t1233\t4233\t9233\t66\t67\tDRAAAA\tWBKAAA\tAAAAxx\n6572\t6809\t0\t0\t2\t12\t72\t572\t572\t1572\t6572\t144\t145\tUSAAAA\tXBKAAA\tHHHHxx\n1586\t6810\t0\t2\t6\t6\t86\t586\t1586\t1586\t1586\t172\t173\tAJAAAA\tYBKAAA\tOOOOxx\n8512\t6811\t0\t0\t2\t12\t12\t512\t512\t3512\t8512\t24\t25\tKPAAAA\tZBKAAA\tVVVVxx\n7421\t6812\t1\t1\t1\t1\t21\t421\t1421\t2421\t7421\t42\t43\tLZAAAA\tACKAAA\tAAAAxx\n503\t6813\t1\t3\t3\t3\t3\t503\t503\t503\t503\t6\t7\tJTAAAA\tBCKAAA\tHHHHxx\n5332\t6814\t0\t0\t2\t12\t32\t332\t1332\t332\t5332\t64\t65\tCXAAAA\tCCKAAA\tOOOOxx\n2602\t6815\t0\t2\t2\t2\t2\t602\t602\t2602\t2602\t4\t5\tCWAAAA\tDCKAAA\tVVVVxx\n2902\t6816\t0\t2\t2\t2\t2\t902\t902\t2902\t2902\t4\t5\tQHAAAA\tECKAAA\tAAAAxx\n2979\t6817\t1\t3\t9\t19\t79\t979\t979\t2979\t2979\t158\t159\tPKAAAA\tFCKAAA\tHHHHxx\n1431\t6818\t1\t3\t1\t11\t31\t431\t1431\t1431\t1431\t62\t63\tBDAAAA\tGCKAAA\tOOOOxx\n8639\t6819\t1\t3\t9\t19\t39\t639\t639\t3639\t8639\t78\t79\tHUAAAA\tHCKAAA\tVVVVxx\n4218\t6820\t0\t2\t8\t18\t18\t218\t218\t4218\t4218\t36\t37\tGGAAAA\tICKAAA\tAAAAxx\n7453\t6821\t1\t1\t3\t13\t53\t453\t1453\t2453\t7453\t106\t107\tRAAAAA\tJCKAAA\tHHHHxx\n5448\t6822\t0\t0\t8\t8\t48\t448\t1448\t448\t5448\t96\t97\tOBAAAA\tKCKAAA\tOOOOxx\n6768\t6823\t0\t0\t8\t8\t68\t768\t768\t1768\t6768\t136\t137\tIAAAAA\tLCKAAA\tVVVVxx\n3104\t6824\t0\t0\t4\t4\t4\t104\t1104\t3104\t3104\t8\t9\tKPAAAA\tMCKAAA\tAAAAxx\n2297\t6825\t1\t1\t7\t17\t97\t297\t297\t2297\t2297\t194\t195\tJKAAAA\tNCKAAA\tHHHHxx\n7994\t6826\t0\t2\t4\t14\t94\t994\t1994\t2994\t7994\t188\t189\tMVAAAA\tOCKAAA\tOOOOxx\n550\t6827\t0\t2\t0\t10\t50\t550\t550\t550\t550\t100\t101\tEVAAAA\tPCKAAA\tVVVVxx\n4777\t6828\t1\t1\t7\t17\t77\t777\t777\t4777\t4777\t154\t155\tTBAAAA\tQCKAAA\tAAAAxx\n5962\t6829\t0\t2\t2\t2\t62\t962\t1962\t962\t5962\t124\t125\tIVAAAA\tRCKAAA\tHHHHxx\n1763\t6830\t1\t3\t3\t3\t63\t763\t1763\t1763\t1763\t126\t127\tVPAAAA\tSCKAAA\tOOOOxx\n3654\t6831\t0\t2\t4\t14\t54\t654\t1654\t3654\t3654\t108\t109\tOKAAAA\tTCKAAA\tVVVVxx\n4106\t6832\t0\t2\t6\t6\t6\t106\t106\t4106\t4106\t12\t13\tYBAAAA\tUCKAAA\tAAAAxx\n5156\t6833\t0\t0\t6\t16\t56\t156\t1156\t156\t5156\t112\t113\tIQAAAA\tVCKAAA\tHHHHxx\n422\t6834\t0\t2\t2\t2\t22\t422\t422\t422\t422\t44\t45\tGQAAAA\tWCKAAA\tOOOOxx\n5011\t6835\t1\t3\t1\t11\t11\t11\t1011\t11\t5011\t22\t23\tTKAAAA\tXCKAAA\tVVVVxx\n218\t6836\t0\t2\t8\t18\t18\t218\t218\t218\t218\t36\t37\tKIAAAA\tYCKAAA\tAAAAxx\n9762\t6837\t0\t2\t2\t2\t62\t762\t1762\t4762\t9762\t124\t125\tMLAAAA\tZCKAAA\tHHHHxx\n6074\t6838\t0\t2\t4\t14\t74\t74\t74\t1074\t6074\t148\t149\tQZAAAA\tADKAAA\tOOOOxx\n4060\t6839\t0\t0\t0\t0\t60\t60\t60\t4060\t4060\t120\t121\tEAAAAA\tBDKAAA\tVVVVxx\n8680\t6840\t0\t0\t0\t0\t80\t680\t680\t3680\t8680\t160\t161\tWVAAAA\tCDKAAA\tAAAAxx\n5863\t6841\t1\t3\t3\t3\t63\t863\t1863\t863\t5863\t126\t127\tNRAAAA\tDDKAAA\tHHHHxx\n8042\t6842\t0\t2\t2\t2\t42\t42\t42\t3042\t8042\t84\t85\tIXAAAA\tEDKAAA\tOOOOxx\n2964\t6843\t0\t0\t4\t4\t64\t964\t964\t2964\t2964\t128\t129\tAKAAAA\tFDKAAA\tVVVVxx\n6931\t6844\t1\t3\t1\t11\t31\t931\t931\t1931\t6931\t62\t63\tPGAAAA\tGDKAAA\tAAAAxx\n6715\t6845\t1\t3\t5\t15\t15\t715\t715\t1715\t6715\t30\t31\tHYAAAA\tHDKAAA\tHHHHxx\n5859\t6846\t1\t3\t9\t19\t59\t859\t1859\t859\t5859\t118\t119\tJRAAAA\tIDKAAA\tOOOOxx\n6173\t6847\t1\t1\t3\t13\t73\t173\t173\t1173\t6173\t146\t147\tLDAAAA\tJDKAAA\tVVVVxx\n7788\t6848\t0\t0\t8\t8\t88\t788\t1788\t2788\t7788\t176\t177\tONAAAA\tKDKAAA\tAAAAxx\n9370\t6849\t0\t2\t0\t10\t70\t370\t1370\t4370\t9370\t140\t141\tKWAAAA\tLDKAAA\tHHHHxx\n3038\t6850\t0\t2\t8\t18\t38\t38\t1038\t3038\t3038\t76\t77\tWMAAAA\tMDKAAA\tOOOOxx\n6483\t6851\t1\t3\t3\t3\t83\t483\t483\t1483\t6483\t166\t167\tJPAAAA\tNDKAAA\tVVVVxx\n7534\t6852\t0\t2\t4\t14\t34\t534\t1534\t2534\t7534\t68\t69\tUDAAAA\tODKAAA\tAAAAxx\n5769\t6853\t1\t1\t9\t9\t69\t769\t1769\t769\t5769\t138\t139\tXNAAAA\tPDKAAA\tHHHHxx\n9152\t6854\t0\t0\t2\t12\t52\t152\t1152\t4152\t9152\t104\t105\tAOAAAA\tQDKAAA\tOOOOxx\n6251\t6855\t1\t3\t1\t11\t51\t251\t251\t1251\t6251\t102\t103\tLGAAAA\tRDKAAA\tVVVVxx\n9209\t6856\t1\t1\t9\t9\t9\t209\t1209\t4209\t9209\t18\t19\tFQAAAA\tSDKAAA\tAAAAxx\n5365\t6857\t1\t1\t5\t5\t65\t365\t1365\t365\t5365\t130\t131\tJYAAAA\tTDKAAA\tHHHHxx\n509\t6858\t1\t1\t9\t9\t9\t509\t509\t509\t509\t18\t19\tPTAAAA\tUDKAAA\tOOOOxx\n3132\t6859\t0\t0\t2\t12\t32\t132\t1132\t3132\t3132\t64\t65\tMQAAAA\tVDKAAA\tVVVVxx\n5373\t6860\t1\t1\t3\t13\t73\t373\t1373\t373\t5373\t146\t147\tRYAAAA\tWDKAAA\tAAAAxx\n4247\t6861\t1\t3\t7\t7\t47\t247\t247\t4247\t4247\t94\t95\tJHAAAA\tXDKAAA\tHHHHxx\n3491\t6862\t1\t3\t1\t11\t91\t491\t1491\t3491\t3491\t182\t183\tHEAAAA\tYDKAAA\tOOOOxx\n495\t6863\t1\t3\t5\t15\t95\t495\t495\t495\t495\t190\t191\tBTAAAA\tZDKAAA\tVVVVxx\n1594\t6864\t0\t2\t4\t14\t94\t594\t1594\t1594\t1594\t188\t189\tIJAAAA\tAEKAAA\tAAAAxx\n2243\t6865\t1\t3\t3\t3\t43\t243\t243\t2243\t2243\t86\t87\tHIAAAA\tBEKAAA\tHHHHxx\n7780\t6866\t0\t0\t0\t0\t80\t780\t1780\t2780\t7780\t160\t161\tGNAAAA\tCEKAAA\tOOOOxx\n5632\t6867\t0\t0\t2\t12\t32\t632\t1632\t632\t5632\t64\t65\tQIAAAA\tDEKAAA\tVVVVxx\n2679\t6868\t1\t3\t9\t19\t79\t679\t679\t2679\t2679\t158\t159\tBZAAAA\tEEKAAA\tAAAAxx\n1354\t6869\t0\t2\t4\t14\t54\t354\t1354\t1354\t1354\t108\t109\tCAAAAA\tFEKAAA\tHHHHxx\n180\t6870\t0\t0\t0\t0\t80\t180\t180\t180\t180\t160\t161\tYGAAAA\tGEKAAA\tOOOOxx\n7017\t6871\t1\t1\t7\t17\t17\t17\t1017\t2017\t7017\t34\t35\tXJAAAA\tHEKAAA\tVVVVxx\n1867\t6872\t1\t3\t7\t7\t67\t867\t1867\t1867\t1867\t134\t135\tVTAAAA\tIEKAAA\tAAAAxx\n2213\t6873\t1\t1\t3\t13\t13\t213\t213\t2213\t2213\t26\t27\tDHAAAA\tJEKAAA\tHHHHxx\n8773\t6874\t1\t1\t3\t13\t73\t773\t773\t3773\t8773\t146\t147\tLZAAAA\tKEKAAA\tOOOOxx\n1784\t6875\t0\t0\t4\t4\t84\t784\t1784\t1784\t1784\t168\t169\tQQAAAA\tLEKAAA\tVVVVxx\n5961\t6876\t1\t1\t1\t1\t61\t961\t1961\t961\t5961\t122\t123\tHVAAAA\tMEKAAA\tAAAAxx\n8801\t6877\t1\t1\t1\t1\t1\t801\t801\t3801\t8801\t2\t3\tNAAAAA\tNEKAAA\tHHHHxx\n4860\t6878\t0\t0\t0\t0\t60\t860\t860\t4860\t4860\t120\t121\tYEAAAA\tOEKAAA\tOOOOxx\n2214\t6879\t0\t2\t4\t14\t14\t214\t214\t2214\t2214\t28\t29\tEHAAAA\tPEKAAA\tVVVVxx\n1735\t6880\t1\t3\t5\t15\t35\t735\t1735\t1735\t1735\t70\t71\tTOAAAA\tQEKAAA\tAAAAxx\n578\t6881\t0\t2\t8\t18\t78\t578\t578\t578\t578\t156\t157\tGWAAAA\tREKAAA\tHHHHxx\n7853\t6882\t1\t1\t3\t13\t53\t853\t1853\t2853\t7853\t106\t107\tBQAAAA\tSEKAAA\tOOOOxx\n2215\t6883\t1\t3\t5\t15\t15\t215\t215\t2215\t2215\t30\t31\tFHAAAA\tTEKAAA\tVVVVxx\n4704\t6884\t0\t0\t4\t4\t4\t704\t704\t4704\t4704\t8\t9\tYYAAAA\tUEKAAA\tAAAAxx\n9379\t6885\t1\t3\t9\t19\t79\t379\t1379\t4379\t9379\t158\t159\tTWAAAA\tVEKAAA\tHHHHxx\n9745\t6886\t1\t1\t5\t5\t45\t745\t1745\t4745\t9745\t90\t91\tVKAAAA\tWEKAAA\tOOOOxx\n5636\t6887\t0\t0\t6\t16\t36\t636\t1636\t636\t5636\t72\t73\tUIAAAA\tXEKAAA\tVVVVxx\n4548\t6888\t0\t0\t8\t8\t48\t548\t548\t4548\t4548\t96\t97\tYSAAAA\tYEKAAA\tAAAAxx\n6537\t6889\t1\t1\t7\t17\t37\t537\t537\t1537\t6537\t74\t75\tLRAAAA\tZEKAAA\tHHHHxx\n7748\t6890\t0\t0\t8\t8\t48\t748\t1748\t2748\t7748\t96\t97\tAMAAAA\tAFKAAA\tOOOOxx\n687\t6891\t1\t3\t7\t7\t87\t687\t687\t687\t687\t174\t175\tLAAAAA\tBFKAAA\tVVVVxx\n1243\t6892\t1\t3\t3\t3\t43\t243\t1243\t1243\t1243\t86\t87\tVVAAAA\tCFKAAA\tAAAAxx\n852\t6893\t0\t0\t2\t12\t52\t852\t852\t852\t852\t104\t105\tUGAAAA\tDFKAAA\tHHHHxx\n785\t6894\t1\t1\t5\t5\t85\t785\t785\t785\t785\t170\t171\tFEAAAA\tEFKAAA\tOOOOxx\n2002\t6895\t0\t2\t2\t2\t2\t2\t2\t2002\t2002\t4\t5\tAZAAAA\tFFKAAA\tVVVVxx\n2748\t6896\t0\t0\t8\t8\t48\t748\t748\t2748\t2748\t96\t97\tSBAAAA\tGFKAAA\tAAAAxx\n6075\t6897\t1\t3\t5\t15\t75\t75\t75\t1075\t6075\t150\t151\tRZAAAA\tHFKAAA\tHHHHxx\n7029\t6898\t1\t1\t9\t9\t29\t29\t1029\t2029\t7029\t58\t59\tJKAAAA\tIFKAAA\tOOOOxx\n7474\t6899\t0\t2\t4\t14\t74\t474\t1474\t2474\t7474\t148\t149\tMBAAAA\tJFKAAA\tVVVVxx\n7755\t6900\t1\t3\t5\t15\t55\t755\t1755\t2755\t7755\t110\t111\tHMAAAA\tKFKAAA\tAAAAxx\n1456\t6901\t0\t0\t6\t16\t56\t456\t1456\t1456\t1456\t112\t113\tAEAAAA\tLFKAAA\tHHHHxx\n2808\t6902\t0\t0\t8\t8\t8\t808\t808\t2808\t2808\t16\t17\tAEAAAA\tMFKAAA\tOOOOxx\n4089\t6903\t1\t1\t9\t9\t89\t89\t89\t4089\t4089\t178\t179\tHBAAAA\tNFKAAA\tVVVVxx\n4718\t6904\t0\t2\t8\t18\t18\t718\t718\t4718\t4718\t36\t37\tMZAAAA\tOFKAAA\tAAAAxx\n910\t6905\t0\t2\t0\t10\t10\t910\t910\t910\t910\t20\t21\tAJAAAA\tPFKAAA\tHHHHxx\n2868\t6906\t0\t0\t8\t8\t68\t868\t868\t2868\t2868\t136\t137\tIGAAAA\tQFKAAA\tOOOOxx\n2103\t6907\t1\t3\t3\t3\t3\t103\t103\t2103\t2103\t6\t7\tXCAAAA\tRFKAAA\tVVVVxx\n2407\t6908\t1\t3\t7\t7\t7\t407\t407\t2407\t2407\t14\t15\tPOAAAA\tSFKAAA\tAAAAxx\n4353\t6909\t1\t1\t3\t13\t53\t353\t353\t4353\t4353\t106\t107\tLLAAAA\tTFKAAA\tHHHHxx\n7988\t6910\t0\t0\t8\t8\t88\t988\t1988\t2988\t7988\t176\t177\tGVAAAA\tUFKAAA\tOOOOxx\n2750\t6911\t0\t2\t0\t10\t50\t750\t750\t2750\t2750\t100\t101\tUBAAAA\tVFKAAA\tVVVVxx\n2006\t6912\t0\t2\t6\t6\t6\t6\t6\t2006\t2006\t12\t13\tEZAAAA\tWFKAAA\tAAAAxx\n4617\t6913\t1\t1\t7\t17\t17\t617\t617\t4617\t4617\t34\t35\tPVAAAA\tXFKAAA\tHHHHxx\n1251\t6914\t1\t3\t1\t11\t51\t251\t1251\t1251\t1251\t102\t103\tDWAAAA\tYFKAAA\tOOOOxx\n4590\t6915\t0\t2\t0\t10\t90\t590\t590\t4590\t4590\t180\t181\tOUAAAA\tZFKAAA\tVVVVxx\n1144\t6916\t0\t0\t4\t4\t44\t144\t1144\t1144\t1144\t88\t89\tASAAAA\tAGKAAA\tAAAAxx\n7131\t6917\t1\t3\t1\t11\t31\t131\t1131\t2131\t7131\t62\t63\tHOAAAA\tBGKAAA\tHHHHxx\n95\t6918\t1\t3\t5\t15\t95\t95\t95\t95\t95\t190\t191\tRDAAAA\tCGKAAA\tOOOOxx\n4827\t6919\t1\t3\t7\t7\t27\t827\t827\t4827\t4827\t54\t55\tRDAAAA\tDGKAAA\tVVVVxx\n4307\t6920\t1\t3\t7\t7\t7\t307\t307\t4307\t4307\t14\t15\tRJAAAA\tEGKAAA\tAAAAxx\n1505\t6921\t1\t1\t5\t5\t5\t505\t1505\t1505\t1505\t10\t11\tXFAAAA\tFGKAAA\tHHHHxx\n8191\t6922\t1\t3\t1\t11\t91\t191\t191\t3191\t8191\t182\t183\tBDAAAA\tGGKAAA\tOOOOxx\n5037\t6923\t1\t1\t7\t17\t37\t37\t1037\t37\t5037\t74\t75\tTLAAAA\tHGKAAA\tVVVVxx\n7363\t6924\t1\t3\t3\t3\t63\t363\t1363\t2363\t7363\t126\t127\tFXAAAA\tIGKAAA\tAAAAxx\n8427\t6925\t1\t3\t7\t7\t27\t427\t427\t3427\t8427\t54\t55\tDMAAAA\tJGKAAA\tHHHHxx\n5231\t6926\t1\t3\t1\t11\t31\t231\t1231\t231\t5231\t62\t63\tFTAAAA\tKGKAAA\tOOOOxx\n2943\t6927\t1\t3\t3\t3\t43\t943\t943\t2943\t2943\t86\t87\tFJAAAA\tLGKAAA\tVVVVxx\n4624\t6928\t0\t0\t4\t4\t24\t624\t624\t4624\t4624\t48\t49\tWVAAAA\tMGKAAA\tAAAAxx\n2020\t6929\t0\t0\t0\t0\t20\t20\t20\t2020\t2020\t40\t41\tSZAAAA\tNGKAAA\tHHHHxx\n6155\t6930\t1\t3\t5\t15\t55\t155\t155\t1155\t6155\t110\t111\tTCAAAA\tOGKAAA\tOOOOxx\n4381\t6931\t1\t1\t1\t1\t81\t381\t381\t4381\t4381\t162\t163\tNMAAAA\tPGKAAA\tVVVVxx\n1057\t6932\t1\t1\t7\t17\t57\t57\t1057\t1057\t1057\t114\t115\tROAAAA\tQGKAAA\tAAAAxx\n9010\t6933\t0\t2\t0\t10\t10\t10\t1010\t4010\t9010\t20\t21\tOIAAAA\tRGKAAA\tHHHHxx\n4947\t6934\t1\t3\t7\t7\t47\t947\t947\t4947\t4947\t94\t95\tHIAAAA\tSGKAAA\tOOOOxx\n335\t6935\t1\t3\t5\t15\t35\t335\t335\t335\t335\t70\t71\tXMAAAA\tTGKAAA\tVVVVxx\n6890\t6936\t0\t2\t0\t10\t90\t890\t890\t1890\t6890\t180\t181\tAFAAAA\tUGKAAA\tAAAAxx\n5070\t6937\t0\t2\t0\t10\t70\t70\t1070\t70\t5070\t140\t141\tANAAAA\tVGKAAA\tHHHHxx\n5270\t6938\t0\t2\t0\t10\t70\t270\t1270\t270\t5270\t140\t141\tSUAAAA\tWGKAAA\tOOOOxx\n8657\t6939\t1\t1\t7\t17\t57\t657\t657\t3657\t8657\t114\t115\tZUAAAA\tXGKAAA\tVVVVxx\n7625\t6940\t1\t1\t5\t5\t25\t625\t1625\t2625\t7625\t50\t51\tHHAAAA\tYGKAAA\tAAAAxx\n5759\t6941\t1\t3\t9\t19\t59\t759\t1759\t759\t5759\t118\t119\tNNAAAA\tZGKAAA\tHHHHxx\n9483\t6942\t1\t3\t3\t3\t83\t483\t1483\t4483\t9483\t166\t167\tTAAAAA\tAHKAAA\tOOOOxx\n8304\t6943\t0\t0\t4\t4\t4\t304\t304\t3304\t8304\t8\t9\tKHAAAA\tBHKAAA\tVVVVxx\n296\t6944\t0\t0\t6\t16\t96\t296\t296\t296\t296\t192\t193\tKLAAAA\tCHKAAA\tAAAAxx\n1176\t6945\t0\t0\t6\t16\t76\t176\t1176\t1176\t1176\t152\t153\tGTAAAA\tDHKAAA\tHHHHxx\n2069\t6946\t1\t1\t9\t9\t69\t69\t69\t2069\t2069\t138\t139\tPBAAAA\tEHKAAA\tOOOOxx\n1531\t6947\t1\t3\t1\t11\t31\t531\t1531\t1531\t1531\t62\t63\tXGAAAA\tFHKAAA\tVVVVxx\n5329\t6948\t1\t1\t9\t9\t29\t329\t1329\t329\t5329\t58\t59\tZWAAAA\tGHKAAA\tAAAAxx\n3702\t6949\t0\t2\t2\t2\t2\t702\t1702\t3702\t3702\t4\t5\tKMAAAA\tHHKAAA\tHHHHxx\n6520\t6950\t0\t0\t0\t0\t20\t520\t520\t1520\t6520\t40\t41\tUQAAAA\tIHKAAA\tOOOOxx\n7310\t6951\t0\t2\t0\t10\t10\t310\t1310\t2310\t7310\t20\t21\tEVAAAA\tJHKAAA\tVVVVxx\n1175\t6952\t1\t3\t5\t15\t75\t175\t1175\t1175\t1175\t150\t151\tFTAAAA\tKHKAAA\tAAAAxx\n9107\t6953\t1\t3\t7\t7\t7\t107\t1107\t4107\t9107\t14\t15\tHMAAAA\tLHKAAA\tHHHHxx\n2737\t6954\t1\t1\t7\t17\t37\t737\t737\t2737\t2737\t74\t75\tHBAAAA\tMHKAAA\tOOOOxx\n3437\t6955\t1\t1\t7\t17\t37\t437\t1437\t3437\t3437\t74\t75\tFCAAAA\tNHKAAA\tVVVVxx\n281\t6956\t1\t1\t1\t1\t81\t281\t281\t281\t281\t162\t163\tVKAAAA\tOHKAAA\tAAAAxx\n6676\t6957\t0\t0\t6\t16\t76\t676\t676\t1676\t6676\t152\t153\tUWAAAA\tPHKAAA\tHHHHxx\n145\t6958\t1\t1\t5\t5\t45\t145\t145\t145\t145\t90\t91\tPFAAAA\tQHKAAA\tOOOOxx\n3172\t6959\t0\t0\t2\t12\t72\t172\t1172\t3172\t3172\t144\t145\tASAAAA\tRHKAAA\tVVVVxx\n4049\t6960\t1\t1\t9\t9\t49\t49\t49\t4049\t4049\t98\t99\tTZAAAA\tSHKAAA\tAAAAxx\n6042\t6961\t0\t2\t2\t2\t42\t42\t42\t1042\t6042\t84\t85\tKYAAAA\tTHKAAA\tHHHHxx\n9122\t6962\t0\t2\t2\t2\t22\t122\t1122\t4122\t9122\t44\t45\tWMAAAA\tUHKAAA\tOOOOxx\n7244\t6963\t0\t0\t4\t4\t44\t244\t1244\t2244\t7244\t88\t89\tQSAAAA\tVHKAAA\tVVVVxx\n5361\t6964\t1\t1\t1\t1\t61\t361\t1361\t361\t5361\t122\t123\tFYAAAA\tWHKAAA\tAAAAxx\n8647\t6965\t1\t3\t7\t7\t47\t647\t647\t3647\t8647\t94\t95\tPUAAAA\tXHKAAA\tHHHHxx\n7956\t6966\t0\t0\t6\t16\t56\t956\t1956\t2956\t7956\t112\t113\tAUAAAA\tYHKAAA\tOOOOxx\n7812\t6967\t0\t0\t2\t12\t12\t812\t1812\t2812\t7812\t24\t25\tMOAAAA\tZHKAAA\tVVVVxx\n570\t6968\t0\t2\t0\t10\t70\t570\t570\t570\t570\t140\t141\tYVAAAA\tAIKAAA\tAAAAxx\n4115\t6969\t1\t3\t5\t15\t15\t115\t115\t4115\t4115\t30\t31\tHCAAAA\tBIKAAA\tHHHHxx\n1856\t6970\t0\t0\t6\t16\t56\t856\t1856\t1856\t1856\t112\t113\tKTAAAA\tCIKAAA\tOOOOxx\n9582\t6971\t0\t2\t2\t2\t82\t582\t1582\t4582\t9582\t164\t165\tOEAAAA\tDIKAAA\tVVVVxx\n2025\t6972\t1\t1\t5\t5\t25\t25\t25\t2025\t2025\t50\t51\tXZAAAA\tEIKAAA\tAAAAxx\n986\t6973\t0\t2\t6\t6\t86\t986\t986\t986\t986\t172\t173\tYLAAAA\tFIKAAA\tHHHHxx\n8358\t6974\t0\t2\t8\t18\t58\t358\t358\t3358\t8358\t116\t117\tMJAAAA\tGIKAAA\tOOOOxx\n510\t6975\t0\t2\t0\t10\t10\t510\t510\t510\t510\t20\t21\tQTAAAA\tHIKAAA\tVVVVxx\n6101\t6976\t1\t1\t1\t1\t1\t101\t101\t1101\t6101\t2\t3\tRAAAAA\tIIKAAA\tAAAAxx\n4167\t6977\t1\t3\t7\t7\t67\t167\t167\t4167\t4167\t134\t135\tHEAAAA\tJIKAAA\tHHHHxx\n6139\t6978\t1\t3\t9\t19\t39\t139\t139\t1139\t6139\t78\t79\tDCAAAA\tKIKAAA\tOOOOxx\n6912\t6979\t0\t0\t2\t12\t12\t912\t912\t1912\t6912\t24\t25\tWFAAAA\tLIKAAA\tVVVVxx\n339\t6980\t1\t3\t9\t19\t39\t339\t339\t339\t339\t78\t79\tBNAAAA\tMIKAAA\tAAAAxx\n8759\t6981\t1\t3\t9\t19\t59\t759\t759\t3759\t8759\t118\t119\tXYAAAA\tNIKAAA\tHHHHxx\n246\t6982\t0\t2\t6\t6\t46\t246\t246\t246\t246\t92\t93\tMJAAAA\tOIKAAA\tOOOOxx\n2831\t6983\t1\t3\t1\t11\t31\t831\t831\t2831\t2831\t62\t63\tXEAAAA\tPIKAAA\tVVVVxx\n2327\t6984\t1\t3\t7\t7\t27\t327\t327\t2327\t2327\t54\t55\tNLAAAA\tQIKAAA\tAAAAxx\n7001\t6985\t1\t1\t1\t1\t1\t1\t1001\t2001\t7001\t2\t3\tHJAAAA\tRIKAAA\tHHHHxx\n4398\t6986\t0\t2\t8\t18\t98\t398\t398\t4398\t4398\t196\t197\tENAAAA\tSIKAAA\tOOOOxx\n1495\t6987\t1\t3\t5\t15\t95\t495\t1495\t1495\t1495\t190\t191\tNFAAAA\tTIKAAA\tVVVVxx\n8522\t6988\t0\t2\t2\t2\t22\t522\t522\t3522\t8522\t44\t45\tUPAAAA\tUIKAAA\tAAAAxx\n7090\t6989\t0\t2\t0\t10\t90\t90\t1090\t2090\t7090\t180\t181\tSMAAAA\tVIKAAA\tHHHHxx\n8457\t6990\t1\t1\t7\t17\t57\t457\t457\t3457\t8457\t114\t115\tHNAAAA\tWIKAAA\tOOOOxx\n4238\t6991\t0\t2\t8\t18\t38\t238\t238\t4238\t4238\t76\t77\tAHAAAA\tXIKAAA\tVVVVxx\n6791\t6992\t1\t3\t1\t11\t91\t791\t791\t1791\t6791\t182\t183\tFBAAAA\tYIKAAA\tAAAAxx\n1342\t6993\t0\t2\t2\t2\t42\t342\t1342\t1342\t1342\t84\t85\tQZAAAA\tZIKAAA\tHHHHxx\n4580\t6994\t0\t0\t0\t0\t80\t580\t580\t4580\t4580\t160\t161\tEUAAAA\tAJKAAA\tOOOOxx\n1475\t6995\t1\t3\t5\t15\t75\t475\t1475\t1475\t1475\t150\t151\tTEAAAA\tBJKAAA\tVVVVxx\n9184\t6996\t0\t0\t4\t4\t84\t184\t1184\t4184\t9184\t168\t169\tGPAAAA\tCJKAAA\tAAAAxx\n1189\t6997\t1\t1\t9\t9\t89\t189\t1189\t1189\t1189\t178\t179\tTTAAAA\tDJKAAA\tHHHHxx\n638\t6998\t0\t2\t8\t18\t38\t638\t638\t638\t638\t76\t77\tOYAAAA\tEJKAAA\tOOOOxx\n5867\t6999\t1\t3\t7\t7\t67\t867\t1867\t867\t5867\t134\t135\tRRAAAA\tFJKAAA\tVVVVxx\n9911\t7000\t1\t3\t1\t11\t11\t911\t1911\t4911\t9911\t22\t23\tFRAAAA\tGJKAAA\tAAAAxx\n8147\t7001\t1\t3\t7\t7\t47\t147\t147\t3147\t8147\t94\t95\tJBAAAA\tHJKAAA\tHHHHxx\n4492\t7002\t0\t0\t2\t12\t92\t492\t492\t4492\t4492\t184\t185\tUQAAAA\tIJKAAA\tOOOOxx\n385\t7003\t1\t1\t5\t5\t85\t385\t385\t385\t385\t170\t171\tVOAAAA\tJJKAAA\tVVVVxx\n5235\t7004\t1\t3\t5\t15\t35\t235\t1235\t235\t5235\t70\t71\tJTAAAA\tKJKAAA\tAAAAxx\n4812\t7005\t0\t0\t2\t12\t12\t812\t812\t4812\t4812\t24\t25\tCDAAAA\tLJKAAA\tHHHHxx\n9807\t7006\t1\t3\t7\t7\t7\t807\t1807\t4807\t9807\t14\t15\tFNAAAA\tMJKAAA\tOOOOxx\n9588\t7007\t0\t0\t8\t8\t88\t588\t1588\t4588\t9588\t176\t177\tUEAAAA\tNJKAAA\tVVVVxx\n9832\t7008\t0\t0\t2\t12\t32\t832\t1832\t4832\t9832\t64\t65\tEOAAAA\tOJKAAA\tAAAAxx\n3757\t7009\t1\t1\t7\t17\t57\t757\t1757\t3757\t3757\t114\t115\tNOAAAA\tPJKAAA\tHHHHxx\n9703\t7010\t1\t3\t3\t3\t3\t703\t1703\t4703\t9703\t6\t7\tFJAAAA\tQJKAAA\tOOOOxx\n1022\t7011\t0\t2\t2\t2\t22\t22\t1022\t1022\t1022\t44\t45\tINAAAA\tRJKAAA\tVVVVxx\n5165\t7012\t1\t1\t5\t5\t65\t165\t1165\t165\t5165\t130\t131\tRQAAAA\tSJKAAA\tAAAAxx\n7129\t7013\t1\t1\t9\t9\t29\t129\t1129\t2129\t7129\t58\t59\tFOAAAA\tTJKAAA\tHHHHxx\n4164\t7014\t0\t0\t4\t4\t64\t164\t164\t4164\t4164\t128\t129\tEEAAAA\tUJKAAA\tOOOOxx\n7239\t7015\t1\t3\t9\t19\t39\t239\t1239\t2239\t7239\t78\t79\tLSAAAA\tVJKAAA\tVVVVxx\n523\t7016\t1\t3\t3\t3\t23\t523\t523\t523\t523\t46\t47\tDUAAAA\tWJKAAA\tAAAAxx\n4670\t7017\t0\t2\t0\t10\t70\t670\t670\t4670\t4670\t140\t141\tQXAAAA\tXJKAAA\tHHHHxx\n8503\t7018\t1\t3\t3\t3\t3\t503\t503\t3503\t8503\t6\t7\tBPAAAA\tYJKAAA\tOOOOxx\n714\t7019\t0\t2\t4\t14\t14\t714\t714\t714\t714\t28\t29\tMBAAAA\tZJKAAA\tVVVVxx\n1350\t7020\t0\t2\t0\t10\t50\t350\t1350\t1350\t1350\t100\t101\tYZAAAA\tAKKAAA\tAAAAxx\n8318\t7021\t0\t2\t8\t18\t18\t318\t318\t3318\t8318\t36\t37\tYHAAAA\tBKKAAA\tHHHHxx\n1834\t7022\t0\t2\t4\t14\t34\t834\t1834\t1834\t1834\t68\t69\tOSAAAA\tCKKAAA\tOOOOxx\n4306\t7023\t0\t2\t6\t6\t6\t306\t306\t4306\t4306\t12\t13\tQJAAAA\tDKKAAA\tVVVVxx\n8543\t7024\t1\t3\t3\t3\t43\t543\t543\t3543\t8543\t86\t87\tPQAAAA\tEKKAAA\tAAAAxx\n9397\t7025\t1\t1\t7\t17\t97\t397\t1397\t4397\t9397\t194\t195\tLXAAAA\tFKKAAA\tHHHHxx\n3145\t7026\t1\t1\t5\t5\t45\t145\t1145\t3145\t3145\t90\t91\tZQAAAA\tGKKAAA\tOOOOxx\n3942\t7027\t0\t2\t2\t2\t42\t942\t1942\t3942\t3942\t84\t85\tQVAAAA\tHKKAAA\tVVVVxx\n8583\t7028\t1\t3\t3\t3\t83\t583\t583\t3583\t8583\t166\t167\tDSAAAA\tIKKAAA\tAAAAxx\n8073\t7029\t1\t1\t3\t13\t73\t73\t73\t3073\t8073\t146\t147\tNYAAAA\tJKKAAA\tHHHHxx\n4940\t7030\t0\t0\t0\t0\t40\t940\t940\t4940\t4940\t80\t81\tAIAAAA\tKKKAAA\tOOOOxx\n9573\t7031\t1\t1\t3\t13\t73\t573\t1573\t4573\t9573\t146\t147\tFEAAAA\tLKKAAA\tVVVVxx\n5325\t7032\t1\t1\t5\t5\t25\t325\t1325\t325\t5325\t50\t51\tVWAAAA\tMKKAAA\tAAAAxx\n1833\t7033\t1\t1\t3\t13\t33\t833\t1833\t1833\t1833\t66\t67\tNSAAAA\tNKKAAA\tHHHHxx\n1337\t7034\t1\t1\t7\t17\t37\t337\t1337\t1337\t1337\t74\t75\tLZAAAA\tOKKAAA\tOOOOxx\n9749\t7035\t1\t1\t9\t9\t49\t749\t1749\t4749\t9749\t98\t99\tZKAAAA\tPKKAAA\tVVVVxx\n7505\t7036\t1\t1\t5\t5\t5\t505\t1505\t2505\t7505\t10\t11\tRCAAAA\tQKKAAA\tAAAAxx\n9731\t7037\t1\t3\t1\t11\t31\t731\t1731\t4731\t9731\t62\t63\tHKAAAA\tRKKAAA\tHHHHxx\n4098\t7038\t0\t2\t8\t18\t98\t98\t98\t4098\t4098\t196\t197\tQBAAAA\tSKKAAA\tOOOOxx\n1418\t7039\t0\t2\t8\t18\t18\t418\t1418\t1418\t1418\t36\t37\tOCAAAA\tTKKAAA\tVVVVxx\n63\t7040\t1\t3\t3\t3\t63\t63\t63\t63\t63\t126\t127\tLCAAAA\tUKKAAA\tAAAAxx\n9889\t7041\t1\t1\t9\t9\t89\t889\t1889\t4889\t9889\t178\t179\tJQAAAA\tVKKAAA\tHHHHxx\n2871\t7042\t1\t3\t1\t11\t71\t871\t871\t2871\t2871\t142\t143\tLGAAAA\tWKKAAA\tOOOOxx\n1003\t7043\t1\t3\t3\t3\t3\t3\t1003\t1003\t1003\t6\t7\tPMAAAA\tXKKAAA\tVVVVxx\n8796\t7044\t0\t0\t6\t16\t96\t796\t796\t3796\t8796\t192\t193\tIAAAAA\tYKKAAA\tAAAAxx\n22\t7045\t0\t2\t2\t2\t22\t22\t22\t22\t22\t44\t45\tWAAAAA\tZKKAAA\tHHHHxx\n8244\t7046\t0\t0\t4\t4\t44\t244\t244\t3244\t8244\t88\t89\tCFAAAA\tALKAAA\tOOOOxx\n2282\t7047\t0\t2\t2\t2\t82\t282\t282\t2282\t2282\t164\t165\tUJAAAA\tBLKAAA\tVVVVxx\n3487\t7048\t1\t3\t7\t7\t87\t487\t1487\t3487\t3487\t174\t175\tDEAAAA\tCLKAAA\tAAAAxx\n8633\t7049\t1\t1\t3\t13\t33\t633\t633\t3633\t8633\t66\t67\tBUAAAA\tDLKAAA\tHHHHxx\n6418\t7050\t0\t2\t8\t18\t18\t418\t418\t1418\t6418\t36\t37\tWMAAAA\tELKAAA\tOOOOxx\n4682\t7051\t0\t2\t2\t2\t82\t682\t682\t4682\t4682\t164\t165\tCYAAAA\tFLKAAA\tVVVVxx\n4103\t7052\t1\t3\t3\t3\t3\t103\t103\t4103\t4103\t6\t7\tVBAAAA\tGLKAAA\tAAAAxx\n6256\t7053\t0\t0\t6\t16\t56\t256\t256\t1256\t6256\t112\t113\tQGAAAA\tHLKAAA\tHHHHxx\n4040\t7054\t0\t0\t0\t0\t40\t40\t40\t4040\t4040\t80\t81\tKZAAAA\tILKAAA\tOOOOxx\n9342\t7055\t0\t2\t2\t2\t42\t342\t1342\t4342\t9342\t84\t85\tIVAAAA\tJLKAAA\tVVVVxx\n9969\t7056\t1\t1\t9\t9\t69\t969\t1969\t4969\t9969\t138\t139\tLTAAAA\tKLKAAA\tAAAAxx\n223\t7057\t1\t3\t3\t3\t23\t223\t223\t223\t223\t46\t47\tPIAAAA\tLLKAAA\tHHHHxx\n4593\t7058\t1\t1\t3\t13\t93\t593\t593\t4593\t4593\t186\t187\tRUAAAA\tMLKAAA\tOOOOxx\n44\t7059\t0\t0\t4\t4\t44\t44\t44\t44\t44\t88\t89\tSBAAAA\tNLKAAA\tVVVVxx\n3513\t7060\t1\t1\t3\t13\t13\t513\t1513\t3513\t3513\t26\t27\tDFAAAA\tOLKAAA\tAAAAxx\n5771\t7061\t1\t3\t1\t11\t71\t771\t1771\t771\t5771\t142\t143\tZNAAAA\tPLKAAA\tHHHHxx\n5083\t7062\t1\t3\t3\t3\t83\t83\t1083\t83\t5083\t166\t167\tNNAAAA\tQLKAAA\tOOOOxx\n3839\t7063\t1\t3\t9\t19\t39\t839\t1839\t3839\t3839\t78\t79\tRRAAAA\tRLKAAA\tVVVVxx\n2986\t7064\t0\t2\t6\t6\t86\t986\t986\t2986\t2986\t172\t173\tWKAAAA\tSLKAAA\tAAAAxx\n2200\t7065\t0\t0\t0\t0\t0\t200\t200\t2200\t2200\t0\t1\tQGAAAA\tTLKAAA\tHHHHxx\n197\t7066\t1\t1\t7\t17\t97\t197\t197\t197\t197\t194\t195\tPHAAAA\tULKAAA\tOOOOxx\n7455\t7067\t1\t3\t5\t15\t55\t455\t1455\t2455\t7455\t110\t111\tTAAAAA\tVLKAAA\tVVVVxx\n1379\t7068\t1\t3\t9\t19\t79\t379\t1379\t1379\t1379\t158\t159\tBBAAAA\tWLKAAA\tAAAAxx\n4356\t7069\t0\t0\t6\t16\t56\t356\t356\t4356\t4356\t112\t113\tOLAAAA\tXLKAAA\tHHHHxx\n6888\t7070\t0\t0\t8\t8\t88\t888\t888\t1888\t6888\t176\t177\tYEAAAA\tYLKAAA\tOOOOxx\n9139\t7071\t1\t3\t9\t19\t39\t139\t1139\t4139\t9139\t78\t79\tNNAAAA\tZLKAAA\tVVVVxx\n7682\t7072\t0\t2\t2\t2\t82\t682\t1682\t2682\t7682\t164\t165\tMJAAAA\tAMKAAA\tAAAAxx\n4873\t7073\t1\t1\t3\t13\t73\t873\t873\t4873\t4873\t146\t147\tLFAAAA\tBMKAAA\tHHHHxx\n783\t7074\t1\t3\t3\t3\t83\t783\t783\t783\t783\t166\t167\tDEAAAA\tCMKAAA\tOOOOxx\n6071\t7075\t1\t3\t1\t11\t71\t71\t71\t1071\t6071\t142\t143\tNZAAAA\tDMKAAA\tVVVVxx\n5160\t7076\t0\t0\t0\t0\t60\t160\t1160\t160\t5160\t120\t121\tMQAAAA\tEMKAAA\tAAAAxx\n2291\t7077\t1\t3\t1\t11\t91\t291\t291\t2291\t2291\t182\t183\tDKAAAA\tFMKAAA\tHHHHxx\n187\t7078\t1\t3\t7\t7\t87\t187\t187\t187\t187\t174\t175\tFHAAAA\tGMKAAA\tOOOOxx\n7786\t7079\t0\t2\t6\t6\t86\t786\t1786\t2786\t7786\t172\t173\tMNAAAA\tHMKAAA\tVVVVxx\n3432\t7080\t0\t0\t2\t12\t32\t432\t1432\t3432\t3432\t64\t65\tACAAAA\tIMKAAA\tAAAAxx\n5450\t7081\t0\t2\t0\t10\t50\t450\t1450\t450\t5450\t100\t101\tQBAAAA\tJMKAAA\tHHHHxx\n2699\t7082\t1\t3\t9\t19\t99\t699\t699\t2699\t2699\t198\t199\tVZAAAA\tKMKAAA\tOOOOxx\n692\t7083\t0\t0\t2\t12\t92\t692\t692\t692\t692\t184\t185\tQAAAAA\tLMKAAA\tVVVVxx\n6081\t7084\t1\t1\t1\t1\t81\t81\t81\t1081\t6081\t162\t163\tXZAAAA\tMMKAAA\tAAAAxx\n4829\t7085\t1\t1\t9\t9\t29\t829\t829\t4829\t4829\t58\t59\tTDAAAA\tNMKAAA\tHHHHxx\n238\t7086\t0\t2\t8\t18\t38\t238\t238\t238\t238\t76\t77\tEJAAAA\tOMKAAA\tOOOOxx\n9100\t7087\t0\t0\t0\t0\t0\t100\t1100\t4100\t9100\t0\t1\tAMAAAA\tPMKAAA\tVVVVxx\n1968\t7088\t0\t0\t8\t8\t68\t968\t1968\t1968\t1968\t136\t137\tSXAAAA\tQMKAAA\tAAAAxx\n1872\t7089\t0\t0\t2\t12\t72\t872\t1872\t1872\t1872\t144\t145\tAUAAAA\tRMKAAA\tHHHHxx\n7051\t7090\t1\t3\t1\t11\t51\t51\t1051\t2051\t7051\t102\t103\tFLAAAA\tSMKAAA\tOOOOxx\n2743\t7091\t1\t3\t3\t3\t43\t743\t743\t2743\t2743\t86\t87\tNBAAAA\tTMKAAA\tVVVVxx\n1237\t7092\t1\t1\t7\t17\t37\t237\t1237\t1237\t1237\t74\t75\tPVAAAA\tUMKAAA\tAAAAxx\n3052\t7093\t0\t0\t2\t12\t52\t52\t1052\t3052\t3052\t104\t105\tKNAAAA\tVMKAAA\tHHHHxx\n8021\t7094\t1\t1\t1\t1\t21\t21\t21\t3021\t8021\t42\t43\tNWAAAA\tWMKAAA\tOOOOxx\n657\t7095\t1\t1\t7\t17\t57\t657\t657\t657\t657\t114\t115\tHZAAAA\tXMKAAA\tVVVVxx\n2236\t7096\t0\t0\t6\t16\t36\t236\t236\t2236\t2236\t72\t73\tAIAAAA\tYMKAAA\tAAAAxx\n7011\t7097\t1\t3\t1\t11\t11\t11\t1011\t2011\t7011\t22\t23\tRJAAAA\tZMKAAA\tHHHHxx\n4067\t7098\t1\t3\t7\t7\t67\t67\t67\t4067\t4067\t134\t135\tLAAAAA\tANKAAA\tOOOOxx\n9449\t7099\t1\t1\t9\t9\t49\t449\t1449\t4449\t9449\t98\t99\tLZAAAA\tBNKAAA\tVVVVxx\n7428\t7100\t0\t0\t8\t8\t28\t428\t1428\t2428\t7428\t56\t57\tSZAAAA\tCNKAAA\tAAAAxx\n1272\t7101\t0\t0\t2\t12\t72\t272\t1272\t1272\t1272\t144\t145\tYWAAAA\tDNKAAA\tHHHHxx\n6897\t7102\t1\t1\t7\t17\t97\t897\t897\t1897\t6897\t194\t195\tHFAAAA\tENKAAA\tOOOOxx\n5839\t7103\t1\t3\t9\t19\t39\t839\t1839\t839\t5839\t78\t79\tPQAAAA\tFNKAAA\tVVVVxx\n6835\t7104\t1\t3\t5\t15\t35\t835\t835\t1835\t6835\t70\t71\tXCAAAA\tGNKAAA\tAAAAxx\n1887\t7105\t1\t3\t7\t7\t87\t887\t1887\t1887\t1887\t174\t175\tPUAAAA\tHNKAAA\tHHHHxx\n1551\t7106\t1\t3\t1\t11\t51\t551\t1551\t1551\t1551\t102\t103\tRHAAAA\tINKAAA\tOOOOxx\n4667\t7107\t1\t3\t7\t7\t67\t667\t667\t4667\t4667\t134\t135\tNXAAAA\tJNKAAA\tVVVVxx\n9603\t7108\t1\t3\t3\t3\t3\t603\t1603\t4603\t9603\t6\t7\tJFAAAA\tKNKAAA\tAAAAxx\n4332\t7109\t0\t0\t2\t12\t32\t332\t332\t4332\t4332\t64\t65\tQKAAAA\tLNKAAA\tHHHHxx\n5681\t7110\t1\t1\t1\t1\t81\t681\t1681\t681\t5681\t162\t163\tNKAAAA\tMNKAAA\tOOOOxx\n8062\t7111\t0\t2\t2\t2\t62\t62\t62\t3062\t8062\t124\t125\tCYAAAA\tNNKAAA\tVVVVxx\n2302\t7112\t0\t2\t2\t2\t2\t302\t302\t2302\t2302\t4\t5\tOKAAAA\tONKAAA\tAAAAxx\n2825\t7113\t1\t1\t5\t5\t25\t825\t825\t2825\t2825\t50\t51\tREAAAA\tPNKAAA\tHHHHxx\n4527\t7114\t1\t3\t7\t7\t27\t527\t527\t4527\t4527\t54\t55\tDSAAAA\tQNKAAA\tOOOOxx\n4230\t7115\t0\t2\t0\t10\t30\t230\t230\t4230\t4230\t60\t61\tSGAAAA\tRNKAAA\tVVVVxx\n3053\t7116\t1\t1\t3\t13\t53\t53\t1053\t3053\t3053\t106\t107\tLNAAAA\tSNKAAA\tAAAAxx\n983\t7117\t1\t3\t3\t3\t83\t983\t983\t983\t983\t166\t167\tVLAAAA\tTNKAAA\tHHHHxx\n9458\t7118\t0\t2\t8\t18\t58\t458\t1458\t4458\t9458\t116\t117\tUZAAAA\tUNKAAA\tOOOOxx\n4128\t7119\t0\t0\t8\t8\t28\t128\t128\t4128\t4128\t56\t57\tUCAAAA\tVNKAAA\tVVVVxx\n425\t7120\t1\t1\t5\t5\t25\t425\t425\t425\t425\t50\t51\tJQAAAA\tWNKAAA\tAAAAxx\n3911\t7121\t1\t3\t1\t11\t11\t911\t1911\t3911\t3911\t22\t23\tLUAAAA\tXNKAAA\tHHHHxx\n6607\t7122\t1\t3\t7\t7\t7\t607\t607\t1607\t6607\t14\t15\tDUAAAA\tYNKAAA\tOOOOxx\n5431\t7123\t1\t3\t1\t11\t31\t431\t1431\t431\t5431\t62\t63\tXAAAAA\tZNKAAA\tVVVVxx\n6330\t7124\t0\t2\t0\t10\t30\t330\t330\t1330\t6330\t60\t61\tMJAAAA\tAOKAAA\tAAAAxx\n3592\t7125\t0\t0\t2\t12\t92\t592\t1592\t3592\t3592\t184\t185\tEIAAAA\tBOKAAA\tHHHHxx\n154\t7126\t0\t2\t4\t14\t54\t154\t154\t154\t154\t108\t109\tYFAAAA\tCOKAAA\tOOOOxx\n9879\t7127\t1\t3\t9\t19\t79\t879\t1879\t4879\t9879\t158\t159\tZPAAAA\tDOKAAA\tVVVVxx\n3202\t7128\t0\t2\t2\t2\t2\t202\t1202\t3202\t3202\t4\t5\tETAAAA\tEOKAAA\tAAAAxx\n3056\t7129\t0\t0\t6\t16\t56\t56\t1056\t3056\t3056\t112\t113\tONAAAA\tFOKAAA\tHHHHxx\n9890\t7130\t0\t2\t0\t10\t90\t890\t1890\t4890\t9890\t180\t181\tKQAAAA\tGOKAAA\tOOOOxx\n5840\t7131\t0\t0\t0\t0\t40\t840\t1840\t840\t5840\t80\t81\tQQAAAA\tHOKAAA\tVVVVxx\n9804\t7132\t0\t0\t4\t4\t4\t804\t1804\t4804\t9804\t8\t9\tCNAAAA\tIOKAAA\tAAAAxx\n681\t7133\t1\t1\t1\t1\t81\t681\t681\t681\t681\t162\t163\tFAAAAA\tJOKAAA\tHHHHxx\n3443\t7134\t1\t3\t3\t3\t43\t443\t1443\t3443\t3443\t86\t87\tLCAAAA\tKOKAAA\tOOOOxx\n8088\t7135\t0\t0\t8\t8\t88\t88\t88\t3088\t8088\t176\t177\tCZAAAA\tLOKAAA\tVVVVxx\n9447\t7136\t1\t3\t7\t7\t47\t447\t1447\t4447\t9447\t94\t95\tJZAAAA\tMOKAAA\tAAAAxx\n1490\t7137\t0\t2\t0\t10\t90\t490\t1490\t1490\t1490\t180\t181\tIFAAAA\tNOKAAA\tHHHHxx\n3684\t7138\t0\t0\t4\t4\t84\t684\t1684\t3684\t3684\t168\t169\tSLAAAA\tOOKAAA\tOOOOxx\n3113\t7139\t1\t1\t3\t13\t13\t113\t1113\t3113\t3113\t26\t27\tTPAAAA\tPOKAAA\tVVVVxx\n9004\t7140\t0\t0\t4\t4\t4\t4\t1004\t4004\t9004\t8\t9\tIIAAAA\tQOKAAA\tAAAAxx\n7147\t7141\t1\t3\t7\t7\t47\t147\t1147\t2147\t7147\t94\t95\tXOAAAA\tROKAAA\tHHHHxx\n7571\t7142\t1\t3\t1\t11\t71\t571\t1571\t2571\t7571\t142\t143\tFFAAAA\tSOKAAA\tOOOOxx\n5545\t7143\t1\t1\t5\t5\t45\t545\t1545\t545\t5545\t90\t91\tHFAAAA\tTOKAAA\tVVVVxx\n4558\t7144\t0\t2\t8\t18\t58\t558\t558\t4558\t4558\t116\t117\tITAAAA\tUOKAAA\tAAAAxx\n6206\t7145\t0\t2\t6\t6\t6\t206\t206\t1206\t6206\t12\t13\tSEAAAA\tVOKAAA\tHHHHxx\n5695\t7146\t1\t3\t5\t15\t95\t695\t1695\t695\t5695\t190\t191\tBLAAAA\tWOKAAA\tOOOOxx\n9600\t7147\t0\t0\t0\t0\t0\t600\t1600\t4600\t9600\t0\t1\tGFAAAA\tXOKAAA\tVVVVxx\n5432\t7148\t0\t0\t2\t12\t32\t432\t1432\t432\t5432\t64\t65\tYAAAAA\tYOKAAA\tAAAAxx\n9299\t7149\t1\t3\t9\t19\t99\t299\t1299\t4299\t9299\t198\t199\tRTAAAA\tZOKAAA\tHHHHxx\n2386\t7150\t0\t2\t6\t6\t86\t386\t386\t2386\t2386\t172\t173\tUNAAAA\tAPKAAA\tOOOOxx\n2046\t7151\t0\t2\t6\t6\t46\t46\t46\t2046\t2046\t92\t93\tSAAAAA\tBPKAAA\tVVVVxx\n3293\t7152\t1\t1\t3\t13\t93\t293\t1293\t3293\t3293\t186\t187\tRWAAAA\tCPKAAA\tAAAAxx\n3046\t7153\t0\t2\t6\t6\t46\t46\t1046\t3046\t3046\t92\t93\tENAAAA\tDPKAAA\tHHHHxx\n214\t7154\t0\t2\t4\t14\t14\t214\t214\t214\t214\t28\t29\tGIAAAA\tEPKAAA\tOOOOxx\n7893\t7155\t1\t1\t3\t13\t93\t893\t1893\t2893\t7893\t186\t187\tPRAAAA\tFPKAAA\tVVVVxx\n891\t7156\t1\t3\t1\t11\t91\t891\t891\t891\t891\t182\t183\tHIAAAA\tGPKAAA\tAAAAxx\n6499\t7157\t1\t3\t9\t19\t99\t499\t499\t1499\t6499\t198\t199\tZPAAAA\tHPKAAA\tHHHHxx\n5003\t7158\t1\t3\t3\t3\t3\t3\t1003\t3\t5003\t6\t7\tLKAAAA\tIPKAAA\tOOOOxx\n6487\t7159\t1\t3\t7\t7\t87\t487\t487\t1487\t6487\t174\t175\tNPAAAA\tJPKAAA\tVVVVxx\n9403\t7160\t1\t3\t3\t3\t3\t403\t1403\t4403\t9403\t6\t7\tRXAAAA\tKPKAAA\tAAAAxx\n945\t7161\t1\t1\t5\t5\t45\t945\t945\t945\t945\t90\t91\tJKAAAA\tLPKAAA\tHHHHxx\n6713\t7162\t1\t1\t3\t13\t13\t713\t713\t1713\t6713\t26\t27\tFYAAAA\tMPKAAA\tOOOOxx\n9928\t7163\t0\t0\t8\t8\t28\t928\t1928\t4928\t9928\t56\t57\tWRAAAA\tNPKAAA\tVVVVxx\n8585\t7164\t1\t1\t5\t5\t85\t585\t585\t3585\t8585\t170\t171\tFSAAAA\tOPKAAA\tAAAAxx\n4004\t7165\t0\t0\t4\t4\t4\t4\t4\t4004\t4004\t8\t9\tAYAAAA\tPPKAAA\tHHHHxx\n2528\t7166\t0\t0\t8\t8\t28\t528\t528\t2528\t2528\t56\t57\tGTAAAA\tQPKAAA\tOOOOxx\n3350\t7167\t0\t2\t0\t10\t50\t350\t1350\t3350\t3350\t100\t101\tWYAAAA\tRPKAAA\tVVVVxx\n2160\t7168\t0\t0\t0\t0\t60\t160\t160\t2160\t2160\t120\t121\tCFAAAA\tSPKAAA\tAAAAxx\n1521\t7169\t1\t1\t1\t1\t21\t521\t1521\t1521\t1521\t42\t43\tNGAAAA\tTPKAAA\tHHHHxx\n5660\t7170\t0\t0\t0\t0\t60\t660\t1660\t660\t5660\t120\t121\tSJAAAA\tUPKAAA\tOOOOxx\n5755\t7171\t1\t3\t5\t15\t55\t755\t1755\t755\t5755\t110\t111\tJNAAAA\tVPKAAA\tVVVVxx\n7614\t7172\t0\t2\t4\t14\t14\t614\t1614\t2614\t7614\t28\t29\tWGAAAA\tWPKAAA\tAAAAxx\n3121\t7173\t1\t1\t1\t1\t21\t121\t1121\t3121\t3121\t42\t43\tBQAAAA\tXPKAAA\tHHHHxx\n2735\t7174\t1\t3\t5\t15\t35\t735\t735\t2735\t2735\t70\t71\tFBAAAA\tYPKAAA\tOOOOxx\n7506\t7175\t0\t2\t6\t6\t6\t506\t1506\t2506\t7506\t12\t13\tSCAAAA\tZPKAAA\tVVVVxx\n2693\t7176\t1\t1\t3\t13\t93\t693\t693\t2693\t2693\t186\t187\tPZAAAA\tAQKAAA\tAAAAxx\n2892\t7177\t0\t0\t2\t12\t92\t892\t892\t2892\t2892\t184\t185\tGHAAAA\tBQKAAA\tHHHHxx\n3310\t7178\t0\t2\t0\t10\t10\t310\t1310\t3310\t3310\t20\t21\tIXAAAA\tCQKAAA\tOOOOxx\n3484\t7179\t0\t0\t4\t4\t84\t484\t1484\t3484\t3484\t168\t169\tAEAAAA\tDQKAAA\tVVVVxx\n9733\t7180\t1\t1\t3\t13\t33\t733\t1733\t4733\t9733\t66\t67\tJKAAAA\tEQKAAA\tAAAAxx\n29\t7181\t1\t1\t9\t9\t29\t29\t29\t29\t29\t58\t59\tDBAAAA\tFQKAAA\tHHHHxx\n9013\t7182\t1\t1\t3\t13\t13\t13\t1013\t4013\t9013\t26\t27\tRIAAAA\tGQKAAA\tOOOOxx\n3847\t7183\t1\t3\t7\t7\t47\t847\t1847\t3847\t3847\t94\t95\tZRAAAA\tHQKAAA\tVVVVxx\n6724\t7184\t0\t0\t4\t4\t24\t724\t724\t1724\t6724\t48\t49\tQYAAAA\tIQKAAA\tAAAAxx\n2559\t7185\t1\t3\t9\t19\t59\t559\t559\t2559\t2559\t118\t119\tLUAAAA\tJQKAAA\tHHHHxx\n5326\t7186\t0\t2\t6\t6\t26\t326\t1326\t326\t5326\t52\t53\tWWAAAA\tKQKAAA\tOOOOxx\n4802\t7187\t0\t2\t2\t2\t2\t802\t802\t4802\t4802\t4\t5\tSCAAAA\tLQKAAA\tVVVVxx\n131\t7188\t1\t3\t1\t11\t31\t131\t131\t131\t131\t62\t63\tBFAAAA\tMQKAAA\tAAAAxx\n1634\t7189\t0\t2\t4\t14\t34\t634\t1634\t1634\t1634\t68\t69\tWKAAAA\tNQKAAA\tHHHHxx\n919\t7190\t1\t3\t9\t19\t19\t919\t919\t919\t919\t38\t39\tJJAAAA\tOQKAAA\tOOOOxx\n9575\t7191\t1\t3\t5\t15\t75\t575\t1575\t4575\t9575\t150\t151\tHEAAAA\tPQKAAA\tVVVVxx\n1256\t7192\t0\t0\t6\t16\t56\t256\t1256\t1256\t1256\t112\t113\tIWAAAA\tQQKAAA\tAAAAxx\n9428\t7193\t0\t0\t8\t8\t28\t428\t1428\t4428\t9428\t56\t57\tQYAAAA\tRQKAAA\tHHHHxx\n5121\t7194\t1\t1\t1\t1\t21\t121\t1121\t121\t5121\t42\t43\tZOAAAA\tSQKAAA\tOOOOxx\n6584\t7195\t0\t0\t4\t4\t84\t584\t584\t1584\t6584\t168\t169\tGTAAAA\tTQKAAA\tVVVVxx\n7193\t7196\t1\t1\t3\t13\t93\t193\t1193\t2193\t7193\t186\t187\tRQAAAA\tUQKAAA\tAAAAxx\n4047\t7197\t1\t3\t7\t7\t47\t47\t47\t4047\t4047\t94\t95\tRZAAAA\tVQKAAA\tHHHHxx\n104\t7198\t0\t0\t4\t4\t4\t104\t104\t104\t104\t8\t9\tAEAAAA\tWQKAAA\tOOOOxx\n1527\t7199\t1\t3\t7\t7\t27\t527\t1527\t1527\t1527\t54\t55\tTGAAAA\tXQKAAA\tVVVVxx\n3460\t7200\t0\t0\t0\t0\t60\t460\t1460\t3460\t3460\t120\t121\tCDAAAA\tYQKAAA\tAAAAxx\n8526\t7201\t0\t2\t6\t6\t26\t526\t526\t3526\t8526\t52\t53\tYPAAAA\tZQKAAA\tHHHHxx\n8959\t7202\t1\t3\t9\t19\t59\t959\t959\t3959\t8959\t118\t119\tPGAAAA\tARKAAA\tOOOOxx\n3633\t7203\t1\t1\t3\t13\t33\t633\t1633\t3633\t3633\t66\t67\tTJAAAA\tBRKAAA\tVVVVxx\n1799\t7204\t1\t3\t9\t19\t99\t799\t1799\t1799\t1799\t198\t199\tFRAAAA\tCRKAAA\tAAAAxx\n461\t7205\t1\t1\t1\t1\t61\t461\t461\t461\t461\t122\t123\tTRAAAA\tDRKAAA\tHHHHxx\n718\t7206\t0\t2\t8\t18\t18\t718\t718\t718\t718\t36\t37\tQBAAAA\tERKAAA\tOOOOxx\n3219\t7207\t1\t3\t9\t19\t19\t219\t1219\t3219\t3219\t38\t39\tVTAAAA\tFRKAAA\tVVVVxx\n3494\t7208\t0\t2\t4\t14\t94\t494\t1494\t3494\t3494\t188\t189\tKEAAAA\tGRKAAA\tAAAAxx\n9402\t7209\t0\t2\t2\t2\t2\t402\t1402\t4402\t9402\t4\t5\tQXAAAA\tHRKAAA\tHHHHxx\n7983\t7210\t1\t3\t3\t3\t83\t983\t1983\t2983\t7983\t166\t167\tBVAAAA\tIRKAAA\tOOOOxx\n7919\t7211\t1\t3\t9\t19\t19\t919\t1919\t2919\t7919\t38\t39\tPSAAAA\tJRKAAA\tVVVVxx\n8036\t7212\t0\t0\t6\t16\t36\t36\t36\t3036\t8036\t72\t73\tCXAAAA\tKRKAAA\tAAAAxx\n5164\t7213\t0\t0\t4\t4\t64\t164\t1164\t164\t5164\t128\t129\tQQAAAA\tLRKAAA\tHHHHxx\n4160\t7214\t0\t0\t0\t0\t60\t160\t160\t4160\t4160\t120\t121\tAEAAAA\tMRKAAA\tOOOOxx\n5370\t7215\t0\t2\t0\t10\t70\t370\t1370\t370\t5370\t140\t141\tOYAAAA\tNRKAAA\tVVVVxx\n5347\t7216\t1\t3\t7\t7\t47\t347\t1347\t347\t5347\t94\t95\tRXAAAA\tORKAAA\tAAAAxx\n7109\t7217\t1\t1\t9\t9\t9\t109\t1109\t2109\t7109\t18\t19\tLNAAAA\tPRKAAA\tHHHHxx\n4826\t7218\t0\t2\t6\t6\t26\t826\t826\t4826\t4826\t52\t53\tQDAAAA\tQRKAAA\tOOOOxx\n1338\t7219\t0\t2\t8\t18\t38\t338\t1338\t1338\t1338\t76\t77\tMZAAAA\tRRKAAA\tVVVVxx\n2711\t7220\t1\t3\t1\t11\t11\t711\t711\t2711\t2711\t22\t23\tHAAAAA\tSRKAAA\tAAAAxx\n6299\t7221\t1\t3\t9\t19\t99\t299\t299\t1299\t6299\t198\t199\tHIAAAA\tTRKAAA\tHHHHxx\n1616\t7222\t0\t0\t6\t16\t16\t616\t1616\t1616\t1616\t32\t33\tEKAAAA\tURKAAA\tOOOOxx\n7519\t7223\t1\t3\t9\t19\t19\t519\t1519\t2519\t7519\t38\t39\tFDAAAA\tVRKAAA\tVVVVxx\n1262\t7224\t0\t2\t2\t2\t62\t262\t1262\t1262\t1262\t124\t125\tOWAAAA\tWRKAAA\tAAAAxx\n7228\t7225\t0\t0\t8\t8\t28\t228\t1228\t2228\t7228\t56\t57\tASAAAA\tXRKAAA\tHHHHxx\n7892\t7226\t0\t0\t2\t12\t92\t892\t1892\t2892\t7892\t184\t185\tORAAAA\tYRKAAA\tOOOOxx\n7929\t7227\t1\t1\t9\t9\t29\t929\t1929\t2929\t7929\t58\t59\tZSAAAA\tZRKAAA\tVVVVxx\n7705\t7228\t1\t1\t5\t5\t5\t705\t1705\t2705\t7705\t10\t11\tJKAAAA\tASKAAA\tAAAAxx\n3111\t7229\t1\t3\t1\t11\t11\t111\t1111\t3111\t3111\t22\t23\tRPAAAA\tBSKAAA\tHHHHxx\n3066\t7230\t0\t2\t6\t6\t66\t66\t1066\t3066\t3066\t132\t133\tYNAAAA\tCSKAAA\tOOOOxx\n9559\t7231\t1\t3\t9\t19\t59\t559\t1559\t4559\t9559\t118\t119\tRDAAAA\tDSKAAA\tVVVVxx\n3787\t7232\t1\t3\t7\t7\t87\t787\t1787\t3787\t3787\t174\t175\tRPAAAA\tESKAAA\tAAAAxx\n8710\t7233\t0\t2\t0\t10\t10\t710\t710\t3710\t8710\t20\t21\tAXAAAA\tFSKAAA\tHHHHxx\n4870\t7234\t0\t2\t0\t10\t70\t870\t870\t4870\t4870\t140\t141\tIFAAAA\tGSKAAA\tOOOOxx\n1883\t7235\t1\t3\t3\t3\t83\t883\t1883\t1883\t1883\t166\t167\tLUAAAA\tHSKAAA\tVVVVxx\n9689\t7236\t1\t1\t9\t9\t89\t689\t1689\t4689\t9689\t178\t179\tRIAAAA\tISKAAA\tAAAAxx\n9491\t7237\t1\t3\t1\t11\t91\t491\t1491\t4491\t9491\t182\t183\tBBAAAA\tJSKAAA\tHHHHxx\n2035\t7238\t1\t3\t5\t15\t35\t35\t35\t2035\t2035\t70\t71\tHAAAAA\tKSKAAA\tOOOOxx\n655\t7239\t1\t3\t5\t15\t55\t655\t655\t655\t655\t110\t111\tFZAAAA\tLSKAAA\tVVVVxx\n6305\t7240\t1\t1\t5\t5\t5\t305\t305\t1305\t6305\t10\t11\tNIAAAA\tMSKAAA\tAAAAxx\n9423\t7241\t1\t3\t3\t3\t23\t423\t1423\t4423\t9423\t46\t47\tLYAAAA\tNSKAAA\tHHHHxx\n283\t7242\t1\t3\t3\t3\t83\t283\t283\t283\t283\t166\t167\tXKAAAA\tOSKAAA\tOOOOxx\n2607\t7243\t1\t3\t7\t7\t7\t607\t607\t2607\t2607\t14\t15\tHWAAAA\tPSKAAA\tVVVVxx\n7740\t7244\t0\t0\t0\t0\t40\t740\t1740\t2740\t7740\t80\t81\tSLAAAA\tQSKAAA\tAAAAxx\n6956\t7245\t0\t0\t6\t16\t56\t956\t956\t1956\t6956\t112\t113\tOHAAAA\tRSKAAA\tHHHHxx\n884\t7246\t0\t0\t4\t4\t84\t884\t884\t884\t884\t168\t169\tAIAAAA\tSSKAAA\tOOOOxx\n5730\t7247\t0\t2\t0\t10\t30\t730\t1730\t730\t5730\t60\t61\tKMAAAA\tTSKAAA\tVVVVxx\n3438\t7248\t0\t2\t8\t18\t38\t438\t1438\t3438\t3438\t76\t77\tGCAAAA\tUSKAAA\tAAAAxx\n3250\t7249\t0\t2\t0\t10\t50\t250\t1250\t3250\t3250\t100\t101\tAVAAAA\tVSKAAA\tHHHHxx\n5470\t7250\t0\t2\t0\t10\t70\t470\t1470\t470\t5470\t140\t141\tKCAAAA\tWSKAAA\tOOOOxx\n2037\t7251\t1\t1\t7\t17\t37\t37\t37\t2037\t2037\t74\t75\tJAAAAA\tXSKAAA\tVVVVxx\n6593\t7252\t1\t1\t3\t13\t93\t593\t593\t1593\t6593\t186\t187\tPTAAAA\tYSKAAA\tAAAAxx\n3893\t7253\t1\t1\t3\t13\t93\t893\t1893\t3893\t3893\t186\t187\tTTAAAA\tZSKAAA\tHHHHxx\n3200\t7254\t0\t0\t0\t0\t0\t200\t1200\t3200\t3200\t0\t1\tCTAAAA\tATKAAA\tOOOOxx\n7125\t7255\t1\t1\t5\t5\t25\t125\t1125\t2125\t7125\t50\t51\tBOAAAA\tBTKAAA\tVVVVxx\n2295\t7256\t1\t3\t5\t15\t95\t295\t295\t2295\t2295\t190\t191\tHKAAAA\tCTKAAA\tAAAAxx\n2056\t7257\t0\t0\t6\t16\t56\t56\t56\t2056\t2056\t112\t113\tCBAAAA\tDTKAAA\tHHHHxx\n2962\t7258\t0\t2\t2\t2\t62\t962\t962\t2962\t2962\t124\t125\tYJAAAA\tETKAAA\tOOOOxx\n993\t7259\t1\t1\t3\t13\t93\t993\t993\t993\t993\t186\t187\tFMAAAA\tFTKAAA\tVVVVxx\n9127\t7260\t1\t3\t7\t7\t27\t127\t1127\t4127\t9127\t54\t55\tBNAAAA\tGTKAAA\tAAAAxx\n2075\t7261\t1\t3\t5\t15\t75\t75\t75\t2075\t2075\t150\t151\tVBAAAA\tHTKAAA\tHHHHxx\n9338\t7262\t0\t2\t8\t18\t38\t338\t1338\t4338\t9338\t76\t77\tEVAAAA\tITKAAA\tOOOOxx\n8100\t7263\t0\t0\t0\t0\t0\t100\t100\t3100\t8100\t0\t1\tOZAAAA\tJTKAAA\tVVVVxx\n5047\t7264\t1\t3\t7\t7\t47\t47\t1047\t47\t5047\t94\t95\tDMAAAA\tKTKAAA\tAAAAxx\n7032\t7265\t0\t0\t2\t12\t32\t32\t1032\t2032\t7032\t64\t65\tMKAAAA\tLTKAAA\tHHHHxx\n6374\t7266\t0\t2\t4\t14\t74\t374\t374\t1374\t6374\t148\t149\tELAAAA\tMTKAAA\tOOOOxx\n4137\t7267\t1\t1\t7\t17\t37\t137\t137\t4137\t4137\t74\t75\tDDAAAA\tNTKAAA\tVVVVxx\n7132\t7268\t0\t0\t2\t12\t32\t132\t1132\t2132\t7132\t64\t65\tIOAAAA\tOTKAAA\tAAAAxx\n3064\t7269\t0\t0\t4\t4\t64\t64\t1064\t3064\t3064\t128\t129\tWNAAAA\tPTKAAA\tHHHHxx\n3621\t7270\t1\t1\t1\t1\t21\t621\t1621\t3621\t3621\t42\t43\tHJAAAA\tQTKAAA\tOOOOxx\n6199\t7271\t1\t3\t9\t19\t99\t199\t199\t1199\t6199\t198\t199\tLEAAAA\tRTKAAA\tVVVVxx\n4926\t7272\t0\t2\t6\t6\t26\t926\t926\t4926\t4926\t52\t53\tMHAAAA\tSTKAAA\tAAAAxx\n8035\t7273\t1\t3\t5\t15\t35\t35\t35\t3035\t8035\t70\t71\tBXAAAA\tTTKAAA\tHHHHxx\n2195\t7274\t1\t3\t5\t15\t95\t195\t195\t2195\t2195\t190\t191\tLGAAAA\tUTKAAA\tOOOOxx\n5366\t7275\t0\t2\t6\t6\t66\t366\t1366\t366\t5366\t132\t133\tKYAAAA\tVTKAAA\tVVVVxx\n3478\t7276\t0\t2\t8\t18\t78\t478\t1478\t3478\t3478\t156\t157\tUDAAAA\tWTKAAA\tAAAAxx\n1926\t7277\t0\t2\t6\t6\t26\t926\t1926\t1926\t1926\t52\t53\tCWAAAA\tXTKAAA\tHHHHxx\n7265\t7278\t1\t1\t5\t5\t65\t265\t1265\t2265\t7265\t130\t131\tLTAAAA\tYTKAAA\tOOOOxx\n7668\t7279\t0\t0\t8\t8\t68\t668\t1668\t2668\t7668\t136\t137\tYIAAAA\tZTKAAA\tVVVVxx\n3335\t7280\t1\t3\t5\t15\t35\t335\t1335\t3335\t3335\t70\t71\tHYAAAA\tAUKAAA\tAAAAxx\n7660\t7281\t0\t0\t0\t0\t60\t660\t1660\t2660\t7660\t120\t121\tQIAAAA\tBUKAAA\tHHHHxx\n9604\t7282\t0\t0\t4\t4\t4\t604\t1604\t4604\t9604\t8\t9\tKFAAAA\tCUKAAA\tOOOOxx\n7301\t7283\t1\t1\t1\t1\t1\t301\t1301\t2301\t7301\t2\t3\tVUAAAA\tDUKAAA\tVVVVxx\n4475\t7284\t1\t3\t5\t15\t75\t475\t475\t4475\t4475\t150\t151\tDQAAAA\tEUKAAA\tAAAAxx\n9954\t7285\t0\t2\t4\t14\t54\t954\t1954\t4954\t9954\t108\t109\tWSAAAA\tFUKAAA\tHHHHxx\n5723\t7286\t1\t3\t3\t3\t23\t723\t1723\t723\t5723\t46\t47\tDMAAAA\tGUKAAA\tOOOOxx\n2669\t7287\t1\t1\t9\t9\t69\t669\t669\t2669\t2669\t138\t139\tRYAAAA\tHUKAAA\tVVVVxx\n1685\t7288\t1\t1\t5\t5\t85\t685\t1685\t1685\t1685\t170\t171\tVMAAAA\tIUKAAA\tAAAAxx\n2233\t7289\t1\t1\t3\t13\t33\t233\t233\t2233\t2233\t66\t67\tXHAAAA\tJUKAAA\tHHHHxx\n8111\t7290\t1\t3\t1\t11\t11\t111\t111\t3111\t8111\t22\t23\tZZAAAA\tKUKAAA\tOOOOxx\n7685\t7291\t1\t1\t5\t5\t85\t685\t1685\t2685\t7685\t170\t171\tPJAAAA\tLUKAAA\tVVVVxx\n3773\t7292\t1\t1\t3\t13\t73\t773\t1773\t3773\t3773\t146\t147\tDPAAAA\tMUKAAA\tAAAAxx\n7172\t7293\t0\t0\t2\t12\t72\t172\t1172\t2172\t7172\t144\t145\tWPAAAA\tNUKAAA\tHHHHxx\n1740\t7294\t0\t0\t0\t0\t40\t740\t1740\t1740\t1740\t80\t81\tYOAAAA\tOUKAAA\tOOOOxx\n5416\t7295\t0\t0\t6\t16\t16\t416\t1416\t416\t5416\t32\t33\tIAAAAA\tPUKAAA\tVVVVxx\n1823\t7296\t1\t3\t3\t3\t23\t823\t1823\t1823\t1823\t46\t47\tDSAAAA\tQUKAAA\tAAAAxx\n1668\t7297\t0\t0\t8\t8\t68\t668\t1668\t1668\t1668\t136\t137\tEMAAAA\tRUKAAA\tHHHHxx\n1795\t7298\t1\t3\t5\t15\t95\t795\t1795\t1795\t1795\t190\t191\tBRAAAA\tSUKAAA\tOOOOxx\n8599\t7299\t1\t3\t9\t19\t99\t599\t599\t3599\t8599\t198\t199\tTSAAAA\tTUKAAA\tVVVVxx\n5542\t7300\t0\t2\t2\t2\t42\t542\t1542\t542\t5542\t84\t85\tEFAAAA\tUUKAAA\tAAAAxx\n5658\t7301\t0\t2\t8\t18\t58\t658\t1658\t658\t5658\t116\t117\tQJAAAA\tVUKAAA\tHHHHxx\n9824\t7302\t0\t0\t4\t4\t24\t824\t1824\t4824\t9824\t48\t49\tWNAAAA\tWUKAAA\tOOOOxx\n19\t7303\t1\t3\t9\t19\t19\t19\t19\t19\t19\t38\t39\tTAAAAA\tXUKAAA\tVVVVxx\n9344\t7304\t0\t0\t4\t4\t44\t344\t1344\t4344\t9344\t88\t89\tKVAAAA\tYUKAAA\tAAAAxx\n5900\t7305\t0\t0\t0\t0\t0\t900\t1900\t900\t5900\t0\t1\tYSAAAA\tZUKAAA\tHHHHxx\n7818\t7306\t0\t2\t8\t18\t18\t818\t1818\t2818\t7818\t36\t37\tSOAAAA\tAVKAAA\tOOOOxx\n8377\t7307\t1\t1\t7\t17\t77\t377\t377\t3377\t8377\t154\t155\tFKAAAA\tBVKAAA\tVVVVxx\n6886\t7308\t0\t2\t6\t6\t86\t886\t886\t1886\t6886\t172\t173\tWEAAAA\tCVKAAA\tAAAAxx\n3201\t7309\t1\t1\t1\t1\t1\t201\t1201\t3201\t3201\t2\t3\tDTAAAA\tDVKAAA\tHHHHxx\n87\t7310\t1\t3\t7\t7\t87\t87\t87\t87\t87\t174\t175\tJDAAAA\tEVKAAA\tOOOOxx\n1089\t7311\t1\t1\t9\t9\t89\t89\t1089\t1089\t1089\t178\t179\tXPAAAA\tFVKAAA\tVVVVxx\n3948\t7312\t0\t0\t8\t8\t48\t948\t1948\t3948\t3948\t96\t97\tWVAAAA\tGVKAAA\tAAAAxx\n6383\t7313\t1\t3\t3\t3\t83\t383\t383\t1383\t6383\t166\t167\tNLAAAA\tHVKAAA\tHHHHxx\n837\t7314\t1\t1\t7\t17\t37\t837\t837\t837\t837\t74\t75\tFGAAAA\tIVKAAA\tOOOOxx\n6285\t7315\t1\t1\t5\t5\t85\t285\t285\t1285\t6285\t170\t171\tTHAAAA\tJVKAAA\tVVVVxx\n78\t7316\t0\t2\t8\t18\t78\t78\t78\t78\t78\t156\t157\tADAAAA\tKVKAAA\tAAAAxx\n4389\t7317\t1\t1\t9\t9\t89\t389\t389\t4389\t4389\t178\t179\tVMAAAA\tLVKAAA\tHHHHxx\n4795\t7318\t1\t3\t5\t15\t95\t795\t795\t4795\t4795\t190\t191\tLCAAAA\tMVKAAA\tOOOOxx\n9369\t7319\t1\t1\t9\t9\t69\t369\t1369\t4369\t9369\t138\t139\tJWAAAA\tNVKAAA\tVVVVxx\n69\t7320\t1\t1\t9\t9\t69\t69\t69\t69\t69\t138\t139\tRCAAAA\tOVKAAA\tAAAAxx\n7689\t7321\t1\t1\t9\t9\t89\t689\t1689\t2689\t7689\t178\t179\tTJAAAA\tPVKAAA\tHHHHxx\n5642\t7322\t0\t2\t2\t2\t42\t642\t1642\t642\t5642\t84\t85\tAJAAAA\tQVKAAA\tOOOOxx\n2348\t7323\t0\t0\t8\t8\t48\t348\t348\t2348\t2348\t96\t97\tIMAAAA\tRVKAAA\tVVVVxx\n9308\t7324\t0\t0\t8\t8\t8\t308\t1308\t4308\t9308\t16\t17\tAUAAAA\tSVKAAA\tAAAAxx\n9093\t7325\t1\t1\t3\t13\t93\t93\t1093\t4093\t9093\t186\t187\tTLAAAA\tTVKAAA\tHHHHxx\n1199\t7326\t1\t3\t9\t19\t99\t199\t1199\t1199\t1199\t198\t199\tDUAAAA\tUVKAAA\tOOOOxx\n307\t7327\t1\t3\t7\t7\t7\t307\t307\t307\t307\t14\t15\tVLAAAA\tVVKAAA\tVVVVxx\n3814\t7328\t0\t2\t4\t14\t14\t814\t1814\t3814\t3814\t28\t29\tSQAAAA\tWVKAAA\tAAAAxx\n8817\t7329\t1\t1\t7\t17\t17\t817\t817\t3817\t8817\t34\t35\tDBAAAA\tXVKAAA\tHHHHxx\n2329\t7330\t1\t1\t9\t9\t29\t329\t329\t2329\t2329\t58\t59\tPLAAAA\tYVKAAA\tOOOOxx\n2932\t7331\t0\t0\t2\t12\t32\t932\t932\t2932\t2932\t64\t65\tUIAAAA\tZVKAAA\tVVVVxx\n1986\t7332\t0\t2\t6\t6\t86\t986\t1986\t1986\t1986\t172\t173\tKYAAAA\tAWKAAA\tAAAAxx\n5279\t7333\t1\t3\t9\t19\t79\t279\t1279\t279\t5279\t158\t159\tBVAAAA\tBWKAAA\tHHHHxx\n5357\t7334\t1\t1\t7\t17\t57\t357\t1357\t357\t5357\t114\t115\tBYAAAA\tCWKAAA\tOOOOxx\n6778\t7335\t0\t2\t8\t18\t78\t778\t778\t1778\t6778\t156\t157\tSAAAAA\tDWKAAA\tVVVVxx\n2773\t7336\t1\t1\t3\t13\t73\t773\t773\t2773\t2773\t146\t147\tRCAAAA\tEWKAAA\tAAAAxx\n244\t7337\t0\t0\t4\t4\t44\t244\t244\t244\t244\t88\t89\tKJAAAA\tFWKAAA\tHHHHxx\n6900\t7338\t0\t0\t0\t0\t0\t900\t900\t1900\t6900\t0\t1\tKFAAAA\tGWKAAA\tOOOOxx\n4739\t7339\t1\t3\t9\t19\t39\t739\t739\t4739\t4739\t78\t79\tHAAAAA\tHWKAAA\tVVVVxx\n3217\t7340\t1\t1\t7\t17\t17\t217\t1217\t3217\t3217\t34\t35\tTTAAAA\tIWKAAA\tAAAAxx\n7563\t7341\t1\t3\t3\t3\t63\t563\t1563\t2563\t7563\t126\t127\tXEAAAA\tJWKAAA\tHHHHxx\n1807\t7342\t1\t3\t7\t7\t7\t807\t1807\t1807\t1807\t14\t15\tNRAAAA\tKWKAAA\tOOOOxx\n4199\t7343\t1\t3\t9\t19\t99\t199\t199\t4199\t4199\t198\t199\tNFAAAA\tLWKAAA\tVVVVxx\n1077\t7344\t1\t1\t7\t17\t77\t77\t1077\t1077\t1077\t154\t155\tLPAAAA\tMWKAAA\tAAAAxx\n8348\t7345\t0\t0\t8\t8\t48\t348\t348\t3348\t8348\t96\t97\tCJAAAA\tNWKAAA\tHHHHxx\n841\t7346\t1\t1\t1\t1\t41\t841\t841\t841\t841\t82\t83\tJGAAAA\tOWKAAA\tOOOOxx\n8154\t7347\t0\t2\t4\t14\t54\t154\t154\t3154\t8154\t108\t109\tQBAAAA\tPWKAAA\tVVVVxx\n5261\t7348\t1\t1\t1\t1\t61\t261\t1261\t261\t5261\t122\t123\tJUAAAA\tQWKAAA\tAAAAxx\n1950\t7349\t0\t2\t0\t10\t50\t950\t1950\t1950\t1950\t100\t101\tAXAAAA\tRWKAAA\tHHHHxx\n8472\t7350\t0\t0\t2\t12\t72\t472\t472\t3472\t8472\t144\t145\tWNAAAA\tSWKAAA\tOOOOxx\n8745\t7351\t1\t1\t5\t5\t45\t745\t745\t3745\t8745\t90\t91\tJYAAAA\tTWKAAA\tVVVVxx\n8715\t7352\t1\t3\t5\t15\t15\t715\t715\t3715\t8715\t30\t31\tFXAAAA\tUWKAAA\tAAAAxx\n9708\t7353\t0\t0\t8\t8\t8\t708\t1708\t4708\t9708\t16\t17\tKJAAAA\tVWKAAA\tHHHHxx\n5860\t7354\t0\t0\t0\t0\t60\t860\t1860\t860\t5860\t120\t121\tKRAAAA\tWWKAAA\tOOOOxx\n9142\t7355\t0\t2\t2\t2\t42\t142\t1142\t4142\t9142\t84\t85\tQNAAAA\tXWKAAA\tVVVVxx\n6582\t7356\t0\t2\t2\t2\t82\t582\t582\t1582\t6582\t164\t165\tETAAAA\tYWKAAA\tAAAAxx\n1255\t7357\t1\t3\t5\t15\t55\t255\t1255\t1255\t1255\t110\t111\tHWAAAA\tZWKAAA\tHHHHxx\n6459\t7358\t1\t3\t9\t19\t59\t459\t459\t1459\t6459\t118\t119\tLOAAAA\tAXKAAA\tOOOOxx\n6327\t7359\t1\t3\t7\t7\t27\t327\t327\t1327\t6327\t54\t55\tJJAAAA\tBXKAAA\tVVVVxx\n4692\t7360\t0\t0\t2\t12\t92\t692\t692\t4692\t4692\t184\t185\tMYAAAA\tCXKAAA\tAAAAxx\n3772\t7361\t0\t0\t2\t12\t72\t772\t1772\t3772\t3772\t144\t145\tCPAAAA\tDXKAAA\tHHHHxx\n4203\t7362\t1\t3\t3\t3\t3\t203\t203\t4203\t4203\t6\t7\tRFAAAA\tEXKAAA\tOOOOxx\n2946\t7363\t0\t2\t6\t6\t46\t946\t946\t2946\t2946\t92\t93\tIJAAAA\tFXKAAA\tVVVVxx\n3524\t7364\t0\t0\t4\t4\t24\t524\t1524\t3524\t3524\t48\t49\tOFAAAA\tGXKAAA\tAAAAxx\n8409\t7365\t1\t1\t9\t9\t9\t409\t409\t3409\t8409\t18\t19\tLLAAAA\tHXKAAA\tHHHHxx\n1824\t7366\t0\t0\t4\t4\t24\t824\t1824\t1824\t1824\t48\t49\tESAAAA\tIXKAAA\tOOOOxx\n4637\t7367\t1\t1\t7\t17\t37\t637\t637\t4637\t4637\t74\t75\tJWAAAA\tJXKAAA\tVVVVxx\n589\t7368\t1\t1\t9\t9\t89\t589\t589\t589\t589\t178\t179\tRWAAAA\tKXKAAA\tAAAAxx\n484\t7369\t0\t0\t4\t4\t84\t484\t484\t484\t484\t168\t169\tQSAAAA\tLXKAAA\tHHHHxx\n8963\t7370\t1\t3\t3\t3\t63\t963\t963\t3963\t8963\t126\t127\tTGAAAA\tMXKAAA\tOOOOxx\n5502\t7371\t0\t2\t2\t2\t2\t502\t1502\t502\t5502\t4\t5\tQDAAAA\tNXKAAA\tVVVVxx\n6982\t7372\t0\t2\t2\t2\t82\t982\t982\t1982\t6982\t164\t165\tOIAAAA\tOXKAAA\tAAAAxx\n8029\t7373\t1\t1\t9\t9\t29\t29\t29\t3029\t8029\t58\t59\tVWAAAA\tPXKAAA\tHHHHxx\n4395\t7374\t1\t3\t5\t15\t95\t395\t395\t4395\t4395\t190\t191\tBNAAAA\tQXKAAA\tOOOOxx\n2595\t7375\t1\t3\t5\t15\t95\t595\t595\t2595\t2595\t190\t191\tVVAAAA\tRXKAAA\tVVVVxx\n2133\t7376\t1\t1\t3\t13\t33\t133\t133\t2133\t2133\t66\t67\tBEAAAA\tSXKAAA\tAAAAxx\n1414\t7377\t0\t2\t4\t14\t14\t414\t1414\t1414\t1414\t28\t29\tKCAAAA\tTXKAAA\tHHHHxx\n8201\t7378\t1\t1\t1\t1\t1\t201\t201\t3201\t8201\t2\t3\tLDAAAA\tUXKAAA\tOOOOxx\n4706\t7379\t0\t2\t6\t6\t6\t706\t706\t4706\t4706\t12\t13\tAZAAAA\tVXKAAA\tVVVVxx\n5310\t7380\t0\t2\t0\t10\t10\t310\t1310\t310\t5310\t20\t21\tGWAAAA\tWXKAAA\tAAAAxx\n7333\t7381\t1\t1\t3\t13\t33\t333\t1333\t2333\t7333\t66\t67\tBWAAAA\tXXKAAA\tHHHHxx\n9420\t7382\t0\t0\t0\t0\t20\t420\t1420\t4420\t9420\t40\t41\tIYAAAA\tYXKAAA\tOOOOxx\n1383\t7383\t1\t3\t3\t3\t83\t383\t1383\t1383\t1383\t166\t167\tFBAAAA\tZXKAAA\tVVVVxx\n6225\t7384\t1\t1\t5\t5\t25\t225\t225\t1225\t6225\t50\t51\tLFAAAA\tAYKAAA\tAAAAxx\n2064\t7385\t0\t0\t4\t4\t64\t64\t64\t2064\t2064\t128\t129\tKBAAAA\tBYKAAA\tHHHHxx\n6700\t7386\t0\t0\t0\t0\t0\t700\t700\t1700\t6700\t0\t1\tSXAAAA\tCYKAAA\tOOOOxx\n1352\t7387\t0\t0\t2\t12\t52\t352\t1352\t1352\t1352\t104\t105\tAAAAAA\tDYKAAA\tVVVVxx\n4249\t7388\t1\t1\t9\t9\t49\t249\t249\t4249\t4249\t98\t99\tLHAAAA\tEYKAAA\tAAAAxx\n9429\t7389\t1\t1\t9\t9\t29\t429\t1429\t4429\t9429\t58\t59\tRYAAAA\tFYKAAA\tHHHHxx\n8090\t7390\t0\t2\t0\t10\t90\t90\t90\t3090\t8090\t180\t181\tEZAAAA\tGYKAAA\tOOOOxx\n5378\t7391\t0\t2\t8\t18\t78\t378\t1378\t378\t5378\t156\t157\tWYAAAA\tHYKAAA\tVVVVxx\n9085\t7392\t1\t1\t5\t5\t85\t85\t1085\t4085\t9085\t170\t171\tLLAAAA\tIYKAAA\tAAAAxx\n7468\t7393\t0\t0\t8\t8\t68\t468\t1468\t2468\t7468\t136\t137\tGBAAAA\tJYKAAA\tHHHHxx\n9955\t7394\t1\t3\t5\t15\t55\t955\t1955\t4955\t9955\t110\t111\tXSAAAA\tKYKAAA\tOOOOxx\n8692\t7395\t0\t0\t2\t12\t92\t692\t692\t3692\t8692\t184\t185\tIWAAAA\tLYKAAA\tVVVVxx\n1463\t7396\t1\t3\t3\t3\t63\t463\t1463\t1463\t1463\t126\t127\tHEAAAA\tMYKAAA\tAAAAxx\n3577\t7397\t1\t1\t7\t17\t77\t577\t1577\t3577\t3577\t154\t155\tPHAAAA\tNYKAAA\tHHHHxx\n5654\t7398\t0\t2\t4\t14\t54\t654\t1654\t654\t5654\t108\t109\tMJAAAA\tOYKAAA\tOOOOxx\n7955\t7399\t1\t3\t5\t15\t55\t955\t1955\t2955\t7955\t110\t111\tZTAAAA\tPYKAAA\tVVVVxx\n4843\t7400\t1\t3\t3\t3\t43\t843\t843\t4843\t4843\t86\t87\tHEAAAA\tQYKAAA\tAAAAxx\n1776\t7401\t0\t0\t6\t16\t76\t776\t1776\t1776\t1776\t152\t153\tIQAAAA\tRYKAAA\tHHHHxx\n2223\t7402\t1\t3\t3\t3\t23\t223\t223\t2223\t2223\t46\t47\tNHAAAA\tSYKAAA\tOOOOxx\n8442\t7403\t0\t2\t2\t2\t42\t442\t442\t3442\t8442\t84\t85\tSMAAAA\tTYKAAA\tVVVVxx\n9738\t7404\t0\t2\t8\t18\t38\t738\t1738\t4738\t9738\t76\t77\tOKAAAA\tUYKAAA\tAAAAxx\n4867\t7405\t1\t3\t7\t7\t67\t867\t867\t4867\t4867\t134\t135\tFFAAAA\tVYKAAA\tHHHHxx\n2983\t7406\t1\t3\t3\t3\t83\t983\t983\t2983\t2983\t166\t167\tTKAAAA\tWYKAAA\tOOOOxx\n3300\t7407\t0\t0\t0\t0\t0\t300\t1300\t3300\t3300\t0\t1\tYWAAAA\tXYKAAA\tVVVVxx\n3815\t7408\t1\t3\t5\t15\t15\t815\t1815\t3815\t3815\t30\t31\tTQAAAA\tYYKAAA\tAAAAxx\n1779\t7409\t1\t3\t9\t19\t79\t779\t1779\t1779\t1779\t158\t159\tLQAAAA\tZYKAAA\tHHHHxx\n1123\t7410\t1\t3\t3\t3\t23\t123\t1123\t1123\t1123\t46\t47\tFRAAAA\tAZKAAA\tOOOOxx\n4824\t7411\t0\t0\t4\t4\t24\t824\t824\t4824\t4824\t48\t49\tODAAAA\tBZKAAA\tVVVVxx\n5407\t7412\t1\t3\t7\t7\t7\t407\t1407\t407\t5407\t14\t15\tZZAAAA\tCZKAAA\tAAAAxx\n5123\t7413\t1\t3\t3\t3\t23\t123\t1123\t123\t5123\t46\t47\tBPAAAA\tDZKAAA\tHHHHxx\n2515\t7414\t1\t3\t5\t15\t15\t515\t515\t2515\t2515\t30\t31\tTSAAAA\tEZKAAA\tOOOOxx\n4781\t7415\t1\t1\t1\t1\t81\t781\t781\t4781\t4781\t162\t163\tXBAAAA\tFZKAAA\tVVVVxx\n7831\t7416\t1\t3\t1\t11\t31\t831\t1831\t2831\t7831\t62\t63\tFPAAAA\tGZKAAA\tAAAAxx\n6946\t7417\t0\t2\t6\t6\t46\t946\t946\t1946\t6946\t92\t93\tEHAAAA\tHZKAAA\tHHHHxx\n1215\t7418\t1\t3\t5\t15\t15\t215\t1215\t1215\t1215\t30\t31\tTUAAAA\tIZKAAA\tOOOOxx\n7783\t7419\t1\t3\t3\t3\t83\t783\t1783\t2783\t7783\t166\t167\tJNAAAA\tJZKAAA\tVVVVxx\n4532\t7420\t0\t0\t2\t12\t32\t532\t532\t4532\t4532\t64\t65\tISAAAA\tKZKAAA\tAAAAxx\n9068\t7421\t0\t0\t8\t8\t68\t68\t1068\t4068\t9068\t136\t137\tUKAAAA\tLZKAAA\tHHHHxx\n7030\t7422\t0\t2\t0\t10\t30\t30\t1030\t2030\t7030\t60\t61\tKKAAAA\tMZKAAA\tOOOOxx\n436\t7423\t0\t0\t6\t16\t36\t436\t436\t436\t436\t72\t73\tUQAAAA\tNZKAAA\tVVVVxx\n6549\t7424\t1\t1\t9\t9\t49\t549\t549\t1549\t6549\t98\t99\tXRAAAA\tOZKAAA\tAAAAxx\n3348\t7425\t0\t0\t8\t8\t48\t348\t1348\t3348\t3348\t96\t97\tUYAAAA\tPZKAAA\tHHHHxx\n6229\t7426\t1\t1\t9\t9\t29\t229\t229\t1229\t6229\t58\t59\tPFAAAA\tQZKAAA\tOOOOxx\n3933\t7427\t1\t1\t3\t13\t33\t933\t1933\t3933\t3933\t66\t67\tHVAAAA\tRZKAAA\tVVVVxx\n1876\t7428\t0\t0\t6\t16\t76\t876\t1876\t1876\t1876\t152\t153\tEUAAAA\tSZKAAA\tAAAAxx\n8920\t7429\t0\t0\t0\t0\t20\t920\t920\t3920\t8920\t40\t41\tCFAAAA\tTZKAAA\tHHHHxx\n7926\t7430\t0\t2\t6\t6\t26\t926\t1926\t2926\t7926\t52\t53\tWSAAAA\tUZKAAA\tOOOOxx\n8805\t7431\t1\t1\t5\t5\t5\t805\t805\t3805\t8805\t10\t11\tRAAAAA\tVZKAAA\tVVVVxx\n6729\t7432\t1\t1\t9\t9\t29\t729\t729\t1729\t6729\t58\t59\tVYAAAA\tWZKAAA\tAAAAxx\n7397\t7433\t1\t1\t7\t17\t97\t397\t1397\t2397\t7397\t194\t195\tNYAAAA\tXZKAAA\tHHHHxx\n9303\t7434\t1\t3\t3\t3\t3\t303\t1303\t4303\t9303\t6\t7\tVTAAAA\tYZKAAA\tOOOOxx\n4255\t7435\t1\t3\t5\t15\t55\t255\t255\t4255\t4255\t110\t111\tRHAAAA\tZZKAAA\tVVVVxx\n7229\t7436\t1\t1\t9\t9\t29\t229\t1229\t2229\t7229\t58\t59\tBSAAAA\tAALAAA\tAAAAxx\n854\t7437\t0\t2\t4\t14\t54\t854\t854\t854\t854\t108\t109\tWGAAAA\tBALAAA\tHHHHxx\n6723\t7438\t1\t3\t3\t3\t23\t723\t723\t1723\t6723\t46\t47\tPYAAAA\tCALAAA\tOOOOxx\n9597\t7439\t1\t1\t7\t17\t97\t597\t1597\t4597\t9597\t194\t195\tDFAAAA\tDALAAA\tVVVVxx\n6532\t7440\t0\t0\t2\t12\t32\t532\t532\t1532\t6532\t64\t65\tGRAAAA\tEALAAA\tAAAAxx\n2910\t7441\t0\t2\t0\t10\t10\t910\t910\t2910\t2910\t20\t21\tYHAAAA\tFALAAA\tHHHHxx\n6717\t7442\t1\t1\t7\t17\t17\t717\t717\t1717\t6717\t34\t35\tJYAAAA\tGALAAA\tOOOOxx\n1790\t7443\t0\t2\t0\t10\t90\t790\t1790\t1790\t1790\t180\t181\tWQAAAA\tHALAAA\tVVVVxx\n3761\t7444\t1\t1\t1\t1\t61\t761\t1761\t3761\t3761\t122\t123\tROAAAA\tIALAAA\tAAAAxx\n1565\t7445\t1\t1\t5\t5\t65\t565\t1565\t1565\t1565\t130\t131\tFIAAAA\tJALAAA\tHHHHxx\n6205\t7446\t1\t1\t5\t5\t5\t205\t205\t1205\t6205\t10\t11\tREAAAA\tKALAAA\tOOOOxx\n2726\t7447\t0\t2\t6\t6\t26\t726\t726\t2726\t2726\t52\t53\tWAAAAA\tLALAAA\tVVVVxx\n799\t7448\t1\t3\t9\t19\t99\t799\t799\t799\t799\t198\t199\tTEAAAA\tMALAAA\tAAAAxx\n3540\t7449\t0\t0\t0\t0\t40\t540\t1540\t3540\t3540\t80\t81\tEGAAAA\tNALAAA\tHHHHxx\n5878\t7450\t0\t2\t8\t18\t78\t878\t1878\t878\t5878\t156\t157\tCSAAAA\tOALAAA\tOOOOxx\n2542\t7451\t0\t2\t2\t2\t42\t542\t542\t2542\t2542\t84\t85\tUTAAAA\tPALAAA\tVVVVxx\n4888\t7452\t0\t0\t8\t8\t88\t888\t888\t4888\t4888\t176\t177\tAGAAAA\tQALAAA\tAAAAxx\n5290\t7453\t0\t2\t0\t10\t90\t290\t1290\t290\t5290\t180\t181\tMVAAAA\tRALAAA\tHHHHxx\n7995\t7454\t1\t3\t5\t15\t95\t995\t1995\t2995\t7995\t190\t191\tNVAAAA\tSALAAA\tOOOOxx\n3519\t7455\t1\t3\t9\t19\t19\t519\t1519\t3519\t3519\t38\t39\tJFAAAA\tTALAAA\tVVVVxx\n3571\t7456\t1\t3\t1\t11\t71\t571\t1571\t3571\t3571\t142\t143\tJHAAAA\tUALAAA\tAAAAxx\n7854\t7457\t0\t2\t4\t14\t54\t854\t1854\t2854\t7854\t108\t109\tCQAAAA\tVALAAA\tHHHHxx\n5184\t7458\t0\t0\t4\t4\t84\t184\t1184\t184\t5184\t168\t169\tKRAAAA\tWALAAA\tOOOOxx\n3498\t7459\t0\t2\t8\t18\t98\t498\t1498\t3498\t3498\t196\t197\tOEAAAA\tXALAAA\tVVVVxx\n1264\t7460\t0\t0\t4\t4\t64\t264\t1264\t1264\t1264\t128\t129\tQWAAAA\tYALAAA\tAAAAxx\n3159\t7461\t1\t3\t9\t19\t59\t159\t1159\t3159\t3159\t118\t119\tNRAAAA\tZALAAA\tHHHHxx\n5480\t7462\t0\t0\t0\t0\t80\t480\t1480\t480\t5480\t160\t161\tUCAAAA\tABLAAA\tOOOOxx\n1706\t7463\t0\t2\t6\t6\t6\t706\t1706\t1706\t1706\t12\t13\tQNAAAA\tBBLAAA\tVVVVxx\n4540\t7464\t0\t0\t0\t0\t40\t540\t540\t4540\t4540\t80\t81\tQSAAAA\tCBLAAA\tAAAAxx\n2799\t7465\t1\t3\t9\t19\t99\t799\t799\t2799\t2799\t198\t199\tRDAAAA\tDBLAAA\tHHHHxx\n7389\t7466\t1\t1\t9\t9\t89\t389\t1389\t2389\t7389\t178\t179\tFYAAAA\tEBLAAA\tOOOOxx\n5565\t7467\t1\t1\t5\t5\t65\t565\t1565\t565\t5565\t130\t131\tBGAAAA\tFBLAAA\tVVVVxx\n3896\t7468\t0\t0\t6\t16\t96\t896\t1896\t3896\t3896\t192\t193\tWTAAAA\tGBLAAA\tAAAAxx\n2100\t7469\t0\t0\t0\t0\t0\t100\t100\t2100\t2100\t0\t1\tUCAAAA\tHBLAAA\tHHHHxx\n3507\t7470\t1\t3\t7\t7\t7\t507\t1507\t3507\t3507\t14\t15\tXEAAAA\tIBLAAA\tOOOOxx\n7971\t7471\t1\t3\t1\t11\t71\t971\t1971\t2971\t7971\t142\t143\tPUAAAA\tJBLAAA\tVVVVxx\n2312\t7472\t0\t0\t2\t12\t12\t312\t312\t2312\t2312\t24\t25\tYKAAAA\tKBLAAA\tAAAAxx\n2494\t7473\t0\t2\t4\t14\t94\t494\t494\t2494\t2494\t188\t189\tYRAAAA\tLBLAAA\tHHHHxx\n2474\t7474\t0\t2\t4\t14\t74\t474\t474\t2474\t2474\t148\t149\tERAAAA\tMBLAAA\tOOOOxx\n3136\t7475\t0\t0\t6\t16\t36\t136\t1136\t3136\t3136\t72\t73\tQQAAAA\tNBLAAA\tVVVVxx\n7242\t7476\t0\t2\t2\t2\t42\t242\t1242\t2242\t7242\t84\t85\tOSAAAA\tOBLAAA\tAAAAxx\n9430\t7477\t0\t2\t0\t10\t30\t430\t1430\t4430\t9430\t60\t61\tSYAAAA\tPBLAAA\tHHHHxx\n1052\t7478\t0\t0\t2\t12\t52\t52\t1052\t1052\t1052\t104\t105\tMOAAAA\tQBLAAA\tOOOOxx\n4172\t7479\t0\t0\t2\t12\t72\t172\t172\t4172\t4172\t144\t145\tMEAAAA\tRBLAAA\tVVVVxx\n970\t7480\t0\t2\t0\t10\t70\t970\t970\t970\t970\t140\t141\tILAAAA\tSBLAAA\tAAAAxx\n882\t7481\t0\t2\t2\t2\t82\t882\t882\t882\t882\t164\t165\tYHAAAA\tTBLAAA\tHHHHxx\n9799\t7482\t1\t3\t9\t19\t99\t799\t1799\t4799\t9799\t198\t199\tXMAAAA\tUBLAAA\tOOOOxx\n5850\t7483\t0\t2\t0\t10\t50\t850\t1850\t850\t5850\t100\t101\tARAAAA\tVBLAAA\tVVVVxx\n9473\t7484\t1\t1\t3\t13\t73\t473\t1473\t4473\t9473\t146\t147\tJAAAAA\tWBLAAA\tAAAAxx\n8635\t7485\t1\t3\t5\t15\t35\t635\t635\t3635\t8635\t70\t71\tDUAAAA\tXBLAAA\tHHHHxx\n2349\t7486\t1\t1\t9\t9\t49\t349\t349\t2349\t2349\t98\t99\tJMAAAA\tYBLAAA\tOOOOxx\n2270\t7487\t0\t2\t0\t10\t70\t270\t270\t2270\t2270\t140\t141\tIJAAAA\tZBLAAA\tVVVVxx\n7887\t7488\t1\t3\t7\t7\t87\t887\t1887\t2887\t7887\t174\t175\tJRAAAA\tACLAAA\tAAAAxx\n3091\t7489\t1\t3\t1\t11\t91\t91\t1091\t3091\t3091\t182\t183\tXOAAAA\tBCLAAA\tHHHHxx\n3728\t7490\t0\t0\t8\t8\t28\t728\t1728\t3728\t3728\t56\t57\tKNAAAA\tCCLAAA\tOOOOxx\n3658\t7491\t0\t2\t8\t18\t58\t658\t1658\t3658\t3658\t116\t117\tSKAAAA\tDCLAAA\tVVVVxx\n5975\t7492\t1\t3\t5\t15\t75\t975\t1975\t975\t5975\t150\t151\tVVAAAA\tECLAAA\tAAAAxx\n332\t7493\t0\t0\t2\t12\t32\t332\t332\t332\t332\t64\t65\tUMAAAA\tFCLAAA\tHHHHxx\n7990\t7494\t0\t2\t0\t10\t90\t990\t1990\t2990\t7990\t180\t181\tIVAAAA\tGCLAAA\tOOOOxx\n8688\t7495\t0\t0\t8\t8\t88\t688\t688\t3688\t8688\t176\t177\tEWAAAA\tHCLAAA\tVVVVxx\n9601\t7496\t1\t1\t1\t1\t1\t601\t1601\t4601\t9601\t2\t3\tHFAAAA\tICLAAA\tAAAAxx\n8401\t7497\t1\t1\t1\t1\t1\t401\t401\t3401\t8401\t2\t3\tDLAAAA\tJCLAAA\tHHHHxx\n8093\t7498\t1\t1\t3\t13\t93\t93\t93\t3093\t8093\t186\t187\tHZAAAA\tKCLAAA\tOOOOxx\n4278\t7499\t0\t2\t8\t18\t78\t278\t278\t4278\t4278\t156\t157\tOIAAAA\tLCLAAA\tVVVVxx\n5467\t7500\t1\t3\t7\t7\t67\t467\t1467\t467\t5467\t134\t135\tHCAAAA\tMCLAAA\tAAAAxx\n3137\t7501\t1\t1\t7\t17\t37\t137\t1137\t3137\t3137\t74\t75\tRQAAAA\tNCLAAA\tHHHHxx\n204\t7502\t0\t0\t4\t4\t4\t204\t204\t204\t204\t8\t9\tWHAAAA\tOCLAAA\tOOOOxx\n8224\t7503\t0\t0\t4\t4\t24\t224\t224\t3224\t8224\t48\t49\tIEAAAA\tPCLAAA\tVVVVxx\n2944\t7504\t0\t0\t4\t4\t44\t944\t944\t2944\t2944\t88\t89\tGJAAAA\tQCLAAA\tAAAAxx\n7593\t7505\t1\t1\t3\t13\t93\t593\t1593\t2593\t7593\t186\t187\tBGAAAA\tRCLAAA\tHHHHxx\n814\t7506\t0\t2\t4\t14\t14\t814\t814\t814\t814\t28\t29\tIFAAAA\tSCLAAA\tOOOOxx\n8047\t7507\t1\t3\t7\t7\t47\t47\t47\t3047\t8047\t94\t95\tNXAAAA\tTCLAAA\tVVVVxx\n7802\t7508\t0\t2\t2\t2\t2\t802\t1802\t2802\t7802\t4\t5\tCOAAAA\tUCLAAA\tAAAAxx\n901\t7509\t1\t1\t1\t1\t1\t901\t901\t901\t901\t2\t3\tRIAAAA\tVCLAAA\tHHHHxx\n6168\t7510\t0\t0\t8\t8\t68\t168\t168\t1168\t6168\t136\t137\tGDAAAA\tWCLAAA\tOOOOxx\n2950\t7511\t0\t2\t0\t10\t50\t950\t950\t2950\t2950\t100\t101\tMJAAAA\tXCLAAA\tVVVVxx\n5393\t7512\t1\t1\t3\t13\t93\t393\t1393\t393\t5393\t186\t187\tLZAAAA\tYCLAAA\tAAAAxx\n3585\t7513\t1\t1\t5\t5\t85\t585\t1585\t3585\t3585\t170\t171\tXHAAAA\tZCLAAA\tHHHHxx\n9392\t7514\t0\t0\t2\t12\t92\t392\t1392\t4392\t9392\t184\t185\tGXAAAA\tADLAAA\tOOOOxx\n8314\t7515\t0\t2\t4\t14\t14\t314\t314\t3314\t8314\t28\t29\tUHAAAA\tBDLAAA\tVVVVxx\n9972\t7516\t0\t0\t2\t12\t72\t972\t1972\t4972\t9972\t144\t145\tOTAAAA\tCDLAAA\tAAAAxx\n9130\t7517\t0\t2\t0\t10\t30\t130\t1130\t4130\t9130\t60\t61\tENAAAA\tDDLAAA\tHHHHxx\n975\t7518\t1\t3\t5\t15\t75\t975\t975\t975\t975\t150\t151\tNLAAAA\tEDLAAA\tOOOOxx\n5720\t7519\t0\t0\t0\t0\t20\t720\t1720\t720\t5720\t40\t41\tAMAAAA\tFDLAAA\tVVVVxx\n3769\t7520\t1\t1\t9\t9\t69\t769\t1769\t3769\t3769\t138\t139\tZOAAAA\tGDLAAA\tAAAAxx\n5303\t7521\t1\t3\t3\t3\t3\t303\t1303\t303\t5303\t6\t7\tZVAAAA\tHDLAAA\tHHHHxx\n6564\t7522\t0\t0\t4\t4\t64\t564\t564\t1564\t6564\t128\t129\tMSAAAA\tIDLAAA\tOOOOxx\n7855\t7523\t1\t3\t5\t15\t55\t855\t1855\t2855\t7855\t110\t111\tDQAAAA\tJDLAAA\tVVVVxx\n8153\t7524\t1\t1\t3\t13\t53\t153\t153\t3153\t8153\t106\t107\tPBAAAA\tKDLAAA\tAAAAxx\n2292\t7525\t0\t0\t2\t12\t92\t292\t292\t2292\t2292\t184\t185\tEKAAAA\tLDLAAA\tHHHHxx\n3156\t7526\t0\t0\t6\t16\t56\t156\t1156\t3156\t3156\t112\t113\tKRAAAA\tMDLAAA\tOOOOxx\n6580\t7527\t0\t0\t0\t0\t80\t580\t580\t1580\t6580\t160\t161\tCTAAAA\tNDLAAA\tVVVVxx\n5324\t7528\t0\t0\t4\t4\t24\t324\t1324\t324\t5324\t48\t49\tUWAAAA\tODLAAA\tAAAAxx\n8871\t7529\t1\t3\t1\t11\t71\t871\t871\t3871\t8871\t142\t143\tFDAAAA\tPDLAAA\tHHHHxx\n2543\t7530\t1\t3\t3\t3\t43\t543\t543\t2543\t2543\t86\t87\tVTAAAA\tQDLAAA\tOOOOxx\n7857\t7531\t1\t1\t7\t17\t57\t857\t1857\t2857\t7857\t114\t115\tFQAAAA\tRDLAAA\tVVVVxx\n4084\t7532\t0\t0\t4\t4\t84\t84\t84\t4084\t4084\t168\t169\tCBAAAA\tSDLAAA\tAAAAxx\n9887\t7533\t1\t3\t7\t7\t87\t887\t1887\t4887\t9887\t174\t175\tHQAAAA\tTDLAAA\tHHHHxx\n6940\t7534\t0\t0\t0\t0\t40\t940\t940\t1940\t6940\t80\t81\tYGAAAA\tUDLAAA\tOOOOxx\n3415\t7535\t1\t3\t5\t15\t15\t415\t1415\t3415\t3415\t30\t31\tJBAAAA\tVDLAAA\tVVVVxx\n5012\t7536\t0\t0\t2\t12\t12\t12\t1012\t12\t5012\t24\t25\tUKAAAA\tWDLAAA\tAAAAxx\n3187\t7537\t1\t3\t7\t7\t87\t187\t1187\t3187\t3187\t174\t175\tPSAAAA\tXDLAAA\tHHHHxx\n8556\t7538\t0\t0\t6\t16\t56\t556\t556\t3556\t8556\t112\t113\tCRAAAA\tYDLAAA\tOOOOxx\n7966\t7539\t0\t2\t6\t6\t66\t966\t1966\t2966\t7966\t132\t133\tKUAAAA\tZDLAAA\tVVVVxx\n7481\t7540\t1\t1\t1\t1\t81\t481\t1481\t2481\t7481\t162\t163\tTBAAAA\tAELAAA\tAAAAxx\n8524\t7541\t0\t0\t4\t4\t24\t524\t524\t3524\t8524\t48\t49\tWPAAAA\tBELAAA\tHHHHxx\n3021\t7542\t1\t1\t1\t1\t21\t21\t1021\t3021\t3021\t42\t43\tFMAAAA\tCELAAA\tOOOOxx\n6045\t7543\t1\t1\t5\t5\t45\t45\t45\t1045\t6045\t90\t91\tNYAAAA\tDELAAA\tVVVVxx\n8022\t7544\t0\t2\t2\t2\t22\t22\t22\t3022\t8022\t44\t45\tOWAAAA\tEELAAA\tAAAAxx\n3626\t7545\t0\t2\t6\t6\t26\t626\t1626\t3626\t3626\t52\t53\tMJAAAA\tFELAAA\tHHHHxx\n1030\t7546\t0\t2\t0\t10\t30\t30\t1030\t1030\t1030\t60\t61\tQNAAAA\tGELAAA\tOOOOxx\n8903\t7547\t1\t3\t3\t3\t3\t903\t903\t3903\t8903\t6\t7\tLEAAAA\tHELAAA\tVVVVxx\n7488\t7548\t0\t0\t8\t8\t88\t488\t1488\t2488\t7488\t176\t177\tACAAAA\tIELAAA\tAAAAxx\n9293\t7549\t1\t1\t3\t13\t93\t293\t1293\t4293\t9293\t186\t187\tLTAAAA\tJELAAA\tHHHHxx\n4586\t7550\t0\t2\t6\t6\t86\t586\t586\t4586\t4586\t172\t173\tKUAAAA\tKELAAA\tOOOOxx\n9282\t7551\t0\t2\t2\t2\t82\t282\t1282\t4282\t9282\t164\t165\tATAAAA\tLELAAA\tVVVVxx\n1948\t7552\t0\t0\t8\t8\t48\t948\t1948\t1948\t1948\t96\t97\tYWAAAA\tMELAAA\tAAAAxx\n2534\t7553\t0\t2\t4\t14\t34\t534\t534\t2534\t2534\t68\t69\tMTAAAA\tNELAAA\tHHHHxx\n1150\t7554\t0\t2\t0\t10\t50\t150\t1150\t1150\t1150\t100\t101\tGSAAAA\tOELAAA\tOOOOxx\n4931\t7555\t1\t3\t1\t11\t31\t931\t931\t4931\t4931\t62\t63\tRHAAAA\tPELAAA\tVVVVxx\n2866\t7556\t0\t2\t6\t6\t66\t866\t866\t2866\t2866\t132\t133\tGGAAAA\tQELAAA\tAAAAxx\n6172\t7557\t0\t0\t2\t12\t72\t172\t172\t1172\t6172\t144\t145\tKDAAAA\tRELAAA\tHHHHxx\n4819\t7558\t1\t3\t9\t19\t19\t819\t819\t4819\t4819\t38\t39\tJDAAAA\tSELAAA\tOOOOxx\n569\t7559\t1\t1\t9\t9\t69\t569\t569\t569\t569\t138\t139\tXVAAAA\tTELAAA\tVVVVxx\n1146\t7560\t0\t2\t6\t6\t46\t146\t1146\t1146\t1146\t92\t93\tCSAAAA\tUELAAA\tAAAAxx\n3062\t7561\t0\t2\t2\t2\t62\t62\t1062\t3062\t3062\t124\t125\tUNAAAA\tVELAAA\tHHHHxx\n7690\t7562\t0\t2\t0\t10\t90\t690\t1690\t2690\t7690\t180\t181\tUJAAAA\tWELAAA\tOOOOxx\n8611\t7563\t1\t3\t1\t11\t11\t611\t611\t3611\t8611\t22\t23\tFTAAAA\tXELAAA\tVVVVxx\n1142\t7564\t0\t2\t2\t2\t42\t142\t1142\t1142\t1142\t84\t85\tYRAAAA\tYELAAA\tAAAAxx\n1193\t7565\t1\t1\t3\t13\t93\t193\t1193\t1193\t1193\t186\t187\tXTAAAA\tZELAAA\tHHHHxx\n2507\t7566\t1\t3\t7\t7\t7\t507\t507\t2507\t2507\t14\t15\tLSAAAA\tAFLAAA\tOOOOxx\n1043\t7567\t1\t3\t3\t3\t43\t43\t1043\t1043\t1043\t86\t87\tDOAAAA\tBFLAAA\tVVVVxx\n7472\t7568\t0\t0\t2\t12\t72\t472\t1472\t2472\t7472\t144\t145\tKBAAAA\tCFLAAA\tAAAAxx\n1817\t7569\t1\t1\t7\t17\t17\t817\t1817\t1817\t1817\t34\t35\tXRAAAA\tDFLAAA\tHHHHxx\n3868\t7570\t0\t0\t8\t8\t68\t868\t1868\t3868\t3868\t136\t137\tUSAAAA\tEFLAAA\tOOOOxx\n9031\t7571\t1\t3\t1\t11\t31\t31\t1031\t4031\t9031\t62\t63\tJJAAAA\tFFLAAA\tVVVVxx\n7254\t7572\t0\t2\t4\t14\t54\t254\t1254\t2254\t7254\t108\t109\tATAAAA\tGFLAAA\tAAAAxx\n5030\t7573\t0\t2\t0\t10\t30\t30\t1030\t30\t5030\t60\t61\tMLAAAA\tHFLAAA\tHHHHxx\n6594\t7574\t0\t2\t4\t14\t94\t594\t594\t1594\t6594\t188\t189\tQTAAAA\tIFLAAA\tOOOOxx\n6862\t7575\t0\t2\t2\t2\t62\t862\t862\t1862\t6862\t124\t125\tYDAAAA\tJFLAAA\tVVVVxx\n1994\t7576\t0\t2\t4\t14\t94\t994\t1994\t1994\t1994\t188\t189\tSYAAAA\tKFLAAA\tAAAAxx\n9017\t7577\t1\t1\t7\t17\t17\t17\t1017\t4017\t9017\t34\t35\tVIAAAA\tLFLAAA\tHHHHxx\n5716\t7578\t0\t0\t6\t16\t16\t716\t1716\t716\t5716\t32\t33\tWLAAAA\tMFLAAA\tOOOOxx\n1900\t7579\t0\t0\t0\t0\t0\t900\t1900\t1900\t1900\t0\t1\tCVAAAA\tNFLAAA\tVVVVxx\n120\t7580\t0\t0\t0\t0\t20\t120\t120\t120\t120\t40\t41\tQEAAAA\tOFLAAA\tAAAAxx\n9003\t7581\t1\t3\t3\t3\t3\t3\t1003\t4003\t9003\t6\t7\tHIAAAA\tPFLAAA\tHHHHxx\n4178\t7582\t0\t2\t8\t18\t78\t178\t178\t4178\t4178\t156\t157\tSEAAAA\tQFLAAA\tOOOOxx\n8777\t7583\t1\t1\t7\t17\t77\t777\t777\t3777\t8777\t154\t155\tPZAAAA\tRFLAAA\tVVVVxx\n3653\t7584\t1\t1\t3\t13\t53\t653\t1653\t3653\t3653\t106\t107\tNKAAAA\tSFLAAA\tAAAAxx\n1137\t7585\t1\t1\t7\t17\t37\t137\t1137\t1137\t1137\t74\t75\tTRAAAA\tTFLAAA\tHHHHxx\n6362\t7586\t0\t2\t2\t2\t62\t362\t362\t1362\t6362\t124\t125\tSKAAAA\tUFLAAA\tOOOOxx\n8537\t7587\t1\t1\t7\t17\t37\t537\t537\t3537\t8537\t74\t75\tJQAAAA\tVFLAAA\tVVVVxx\n1590\t7588\t0\t2\t0\t10\t90\t590\t1590\t1590\t1590\t180\t181\tEJAAAA\tWFLAAA\tAAAAxx\n374\t7589\t0\t2\t4\t14\t74\t374\t374\t374\t374\t148\t149\tKOAAAA\tXFLAAA\tHHHHxx\n2597\t7590\t1\t1\t7\t17\t97\t597\t597\t2597\t2597\t194\t195\tXVAAAA\tYFLAAA\tOOOOxx\n8071\t7591\t1\t3\t1\t11\t71\t71\t71\t3071\t8071\t142\t143\tLYAAAA\tZFLAAA\tVVVVxx\n9009\t7592\t1\t1\t9\t9\t9\t9\t1009\t4009\t9009\t18\t19\tNIAAAA\tAGLAAA\tAAAAxx\n1978\t7593\t0\t2\t8\t18\t78\t978\t1978\t1978\t1978\t156\t157\tCYAAAA\tBGLAAA\tHHHHxx\n1541\t7594\t1\t1\t1\t1\t41\t541\t1541\t1541\t1541\t82\t83\tHHAAAA\tCGLAAA\tOOOOxx\n4998\t7595\t0\t2\t8\t18\t98\t998\t998\t4998\t4998\t196\t197\tGKAAAA\tDGLAAA\tVVVVxx\n1649\t7596\t1\t1\t9\t9\t49\t649\t1649\t1649\t1649\t98\t99\tLLAAAA\tEGLAAA\tAAAAxx\n5426\t7597\t0\t2\t6\t6\t26\t426\t1426\t426\t5426\t52\t53\tSAAAAA\tFGLAAA\tHHHHxx\n1492\t7598\t0\t0\t2\t12\t92\t492\t1492\t1492\t1492\t184\t185\tKFAAAA\tGGLAAA\tOOOOxx\n9622\t7599\t0\t2\t2\t2\t22\t622\t1622\t4622\t9622\t44\t45\tCGAAAA\tHGLAAA\tVVVVxx\n701\t7600\t1\t1\t1\t1\t1\t701\t701\t701\t701\t2\t3\tZAAAAA\tIGLAAA\tAAAAxx\n2781\t7601\t1\t1\t1\t1\t81\t781\t781\t2781\t2781\t162\t163\tZCAAAA\tJGLAAA\tHHHHxx\n3982\t7602\t0\t2\t2\t2\t82\t982\t1982\t3982\t3982\t164\t165\tEXAAAA\tKGLAAA\tOOOOxx\n7259\t7603\t1\t3\t9\t19\t59\t259\t1259\t2259\t7259\t118\t119\tFTAAAA\tLGLAAA\tVVVVxx\n9868\t7604\t0\t0\t8\t8\t68\t868\t1868\t4868\t9868\t136\t137\tOPAAAA\tMGLAAA\tAAAAxx\n564\t7605\t0\t0\t4\t4\t64\t564\t564\t564\t564\t128\t129\tSVAAAA\tNGLAAA\tHHHHxx\n6315\t7606\t1\t3\t5\t15\t15\t315\t315\t1315\t6315\t30\t31\tXIAAAA\tOGLAAA\tOOOOxx\n9092\t7607\t0\t0\t2\t12\t92\t92\t1092\t4092\t9092\t184\t185\tSLAAAA\tPGLAAA\tVVVVxx\n8237\t7608\t1\t1\t7\t17\t37\t237\t237\t3237\t8237\t74\t75\tVEAAAA\tQGLAAA\tAAAAxx\n1513\t7609\t1\t1\t3\t13\t13\t513\t1513\t1513\t1513\t26\t27\tFGAAAA\tRGLAAA\tHHHHxx\n1922\t7610\t0\t2\t2\t2\t22\t922\t1922\t1922\t1922\t44\t45\tYVAAAA\tSGLAAA\tOOOOxx\n5396\t7611\t0\t0\t6\t16\t96\t396\t1396\t396\t5396\t192\t193\tOZAAAA\tTGLAAA\tVVVVxx\n2485\t7612\t1\t1\t5\t5\t85\t485\t485\t2485\t2485\t170\t171\tPRAAAA\tUGLAAA\tAAAAxx\n5774\t7613\t0\t2\t4\t14\t74\t774\t1774\t774\t5774\t148\t149\tCOAAAA\tVGLAAA\tHHHHxx\n3983\t7614\t1\t3\t3\t3\t83\t983\t1983\t3983\t3983\t166\t167\tFXAAAA\tWGLAAA\tOOOOxx\n221\t7615\t1\t1\t1\t1\t21\t221\t221\t221\t221\t42\t43\tNIAAAA\tXGLAAA\tVVVVxx\n8662\t7616\t0\t2\t2\t2\t62\t662\t662\t3662\t8662\t124\t125\tEVAAAA\tYGLAAA\tAAAAxx\n2456\t7617\t0\t0\t6\t16\t56\t456\t456\t2456\t2456\t112\t113\tMQAAAA\tZGLAAA\tHHHHxx\n9736\t7618\t0\t0\t6\t16\t36\t736\t1736\t4736\t9736\t72\t73\tMKAAAA\tAHLAAA\tOOOOxx\n8936\t7619\t0\t0\t6\t16\t36\t936\t936\t3936\t8936\t72\t73\tSFAAAA\tBHLAAA\tVVVVxx\n5395\t7620\t1\t3\t5\t15\t95\t395\t1395\t395\t5395\t190\t191\tNZAAAA\tCHLAAA\tAAAAxx\n9523\t7621\t1\t3\t3\t3\t23\t523\t1523\t4523\t9523\t46\t47\tHCAAAA\tDHLAAA\tHHHHxx\n6980\t7622\t0\t0\t0\t0\t80\t980\t980\t1980\t6980\t160\t161\tMIAAAA\tEHLAAA\tOOOOxx\n2091\t7623\t1\t3\t1\t11\t91\t91\t91\t2091\t2091\t182\t183\tLCAAAA\tFHLAAA\tVVVVxx\n6807\t7624\t1\t3\t7\t7\t7\t807\t807\t1807\t6807\t14\t15\tVBAAAA\tGHLAAA\tAAAAxx\n8818\t7625\t0\t2\t8\t18\t18\t818\t818\t3818\t8818\t36\t37\tEBAAAA\tHHLAAA\tHHHHxx\n5298\t7626\t0\t2\t8\t18\t98\t298\t1298\t298\t5298\t196\t197\tUVAAAA\tIHLAAA\tOOOOxx\n1726\t7627\t0\t2\t6\t6\t26\t726\t1726\t1726\t1726\t52\t53\tKOAAAA\tJHLAAA\tVVVVxx\n3878\t7628\t0\t2\t8\t18\t78\t878\t1878\t3878\t3878\t156\t157\tETAAAA\tKHLAAA\tAAAAxx\n8700\t7629\t0\t0\t0\t0\t0\t700\t700\t3700\t8700\t0\t1\tQWAAAA\tLHLAAA\tHHHHxx\n5201\t7630\t1\t1\t1\t1\t1\t201\t1201\t201\t5201\t2\t3\tBSAAAA\tMHLAAA\tOOOOxx\n3936\t7631\t0\t0\t6\t16\t36\t936\t1936\t3936\t3936\t72\t73\tKVAAAA\tNHLAAA\tVVVVxx\n776\t7632\t0\t0\t6\t16\t76\t776\t776\t776\t776\t152\t153\tWDAAAA\tOHLAAA\tAAAAxx\n5302\t7633\t0\t2\t2\t2\t2\t302\t1302\t302\t5302\t4\t5\tYVAAAA\tPHLAAA\tHHHHxx\n3595\t7634\t1\t3\t5\t15\t95\t595\t1595\t3595\t3595\t190\t191\tHIAAAA\tQHLAAA\tOOOOxx\n9061\t7635\t1\t1\t1\t1\t61\t61\t1061\t4061\t9061\t122\t123\tNKAAAA\tRHLAAA\tVVVVxx\n6261\t7636\t1\t1\t1\t1\t61\t261\t261\t1261\t6261\t122\t123\tVGAAAA\tSHLAAA\tAAAAxx\n8878\t7637\t0\t2\t8\t18\t78\t878\t878\t3878\t8878\t156\t157\tMDAAAA\tTHLAAA\tHHHHxx\n3312\t7638\t0\t0\t2\t12\t12\t312\t1312\t3312\t3312\t24\t25\tKXAAAA\tUHLAAA\tOOOOxx\n9422\t7639\t0\t2\t2\t2\t22\t422\t1422\t4422\t9422\t44\t45\tKYAAAA\tVHLAAA\tVVVVxx\n7321\t7640\t1\t1\t1\t1\t21\t321\t1321\t2321\t7321\t42\t43\tPVAAAA\tWHLAAA\tAAAAxx\n3813\t7641\t1\t1\t3\t13\t13\t813\t1813\t3813\t3813\t26\t27\tRQAAAA\tXHLAAA\tHHHHxx\n5848\t7642\t0\t0\t8\t8\t48\t848\t1848\t848\t5848\t96\t97\tYQAAAA\tYHLAAA\tOOOOxx\n3535\t7643\t1\t3\t5\t15\t35\t535\t1535\t3535\t3535\t70\t71\tZFAAAA\tZHLAAA\tVVVVxx\n1040\t7644\t0\t0\t0\t0\t40\t40\t1040\t1040\t1040\t80\t81\tAOAAAA\tAILAAA\tAAAAxx\n8572\t7645\t0\t0\t2\t12\t72\t572\t572\t3572\t8572\t144\t145\tSRAAAA\tBILAAA\tHHHHxx\n5435\t7646\t1\t3\t5\t15\t35\t435\t1435\t435\t5435\t70\t71\tBBAAAA\tCILAAA\tOOOOxx\n8199\t7647\t1\t3\t9\t19\t99\t199\t199\t3199\t8199\t198\t199\tJDAAAA\tDILAAA\tVVVVxx\n8775\t7648\t1\t3\t5\t15\t75\t775\t775\t3775\t8775\t150\t151\tNZAAAA\tEILAAA\tAAAAxx\n7722\t7649\t0\t2\t2\t2\t22\t722\t1722\t2722\t7722\t44\t45\tALAAAA\tFILAAA\tHHHHxx\n3549\t7650\t1\t1\t9\t9\t49\t549\t1549\t3549\t3549\t98\t99\tNGAAAA\tGILAAA\tOOOOxx\n2578\t7651\t0\t2\t8\t18\t78\t578\t578\t2578\t2578\t156\t157\tEVAAAA\tHILAAA\tVVVVxx\n1695\t7652\t1\t3\t5\t15\t95\t695\t1695\t1695\t1695\t190\t191\tFNAAAA\tIILAAA\tAAAAxx\n1902\t7653\t0\t2\t2\t2\t2\t902\t1902\t1902\t1902\t4\t5\tEVAAAA\tJILAAA\tHHHHxx\n6058\t7654\t0\t2\t8\t18\t58\t58\t58\t1058\t6058\t116\t117\tAZAAAA\tKILAAA\tOOOOxx\n6591\t7655\t1\t3\t1\t11\t91\t591\t591\t1591\t6591\t182\t183\tNTAAAA\tLILAAA\tVVVVxx\n7962\t7656\t0\t2\t2\t2\t62\t962\t1962\t2962\t7962\t124\t125\tGUAAAA\tMILAAA\tAAAAxx\n5612\t7657\t0\t0\t2\t12\t12\t612\t1612\t612\t5612\t24\t25\tWHAAAA\tNILAAA\tHHHHxx\n3341\t7658\t1\t1\t1\t1\t41\t341\t1341\t3341\t3341\t82\t83\tNYAAAA\tOILAAA\tOOOOxx\n5460\t7659\t0\t0\t0\t0\t60\t460\t1460\t460\t5460\t120\t121\tACAAAA\tPILAAA\tVVVVxx\n2368\t7660\t0\t0\t8\t8\t68\t368\t368\t2368\t2368\t136\t137\tCNAAAA\tQILAAA\tAAAAxx\n8646\t7661\t0\t2\t6\t6\t46\t646\t646\t3646\t8646\t92\t93\tOUAAAA\tRILAAA\tHHHHxx\n4987\t7662\t1\t3\t7\t7\t87\t987\t987\t4987\t4987\t174\t175\tVJAAAA\tSILAAA\tOOOOxx\n9018\t7663\t0\t2\t8\t18\t18\t18\t1018\t4018\t9018\t36\t37\tWIAAAA\tTILAAA\tVVVVxx\n8685\t7664\t1\t1\t5\t5\t85\t685\t685\t3685\t8685\t170\t171\tBWAAAA\tUILAAA\tAAAAxx\n694\t7665\t0\t2\t4\t14\t94\t694\t694\t694\t694\t188\t189\tSAAAAA\tVILAAA\tHHHHxx\n2012\t7666\t0\t0\t2\t12\t12\t12\t12\t2012\t2012\t24\t25\tKZAAAA\tWILAAA\tOOOOxx\n2417\t7667\t1\t1\t7\t17\t17\t417\t417\t2417\t2417\t34\t35\tZOAAAA\tXILAAA\tVVVVxx\n4022\t7668\t0\t2\t2\t2\t22\t22\t22\t4022\t4022\t44\t45\tSYAAAA\tYILAAA\tAAAAxx\n5935\t7669\t1\t3\t5\t15\t35\t935\t1935\t935\t5935\t70\t71\tHUAAAA\tZILAAA\tHHHHxx\n1656\t7670\t0\t0\t6\t16\t56\t656\t1656\t1656\t1656\t112\t113\tSLAAAA\tAJLAAA\tOOOOxx\n6195\t7671\t1\t3\t5\t15\t95\t195\t195\t1195\t6195\t190\t191\tHEAAAA\tBJLAAA\tVVVVxx\n3057\t7672\t1\t1\t7\t17\t57\t57\t1057\t3057\t3057\t114\t115\tPNAAAA\tCJLAAA\tAAAAxx\n2852\t7673\t0\t0\t2\t12\t52\t852\t852\t2852\t2852\t104\t105\tSFAAAA\tDJLAAA\tHHHHxx\n4634\t7674\t0\t2\t4\t14\t34\t634\t634\t4634\t4634\t68\t69\tGWAAAA\tEJLAAA\tOOOOxx\n1689\t7675\t1\t1\t9\t9\t89\t689\t1689\t1689\t1689\t178\t179\tZMAAAA\tFJLAAA\tVVVVxx\n4102\t7676\t0\t2\t2\t2\t2\t102\t102\t4102\t4102\t4\t5\tUBAAAA\tGJLAAA\tAAAAxx\n3287\t7677\t1\t3\t7\t7\t87\t287\t1287\t3287\t3287\t174\t175\tLWAAAA\tHJLAAA\tHHHHxx\n5246\t7678\t0\t2\t6\t6\t46\t246\t1246\t246\t5246\t92\t93\tUTAAAA\tIJLAAA\tOOOOxx\n7450\t7679\t0\t2\t0\t10\t50\t450\t1450\t2450\t7450\t100\t101\tOAAAAA\tJJLAAA\tVVVVxx\n6548\t7680\t0\t0\t8\t8\t48\t548\t548\t1548\t6548\t96\t97\tWRAAAA\tKJLAAA\tAAAAxx\n379\t7681\t1\t3\t9\t19\t79\t379\t379\t379\t379\t158\t159\tPOAAAA\tLJLAAA\tHHHHxx\n7435\t7682\t1\t3\t5\t15\t35\t435\t1435\t2435\t7435\t70\t71\tZZAAAA\tMJLAAA\tOOOOxx\n2041\t7683\t1\t1\t1\t1\t41\t41\t41\t2041\t2041\t82\t83\tNAAAAA\tNJLAAA\tVVVVxx\n8462\t7684\t0\t2\t2\t2\t62\t462\t462\t3462\t8462\t124\t125\tMNAAAA\tOJLAAA\tAAAAxx\n9076\t7685\t0\t0\t6\t16\t76\t76\t1076\t4076\t9076\t152\t153\tCLAAAA\tPJLAAA\tHHHHxx\n761\t7686\t1\t1\t1\t1\t61\t761\t761\t761\t761\t122\t123\tHDAAAA\tQJLAAA\tOOOOxx\n795\t7687\t1\t3\t5\t15\t95\t795\t795\t795\t795\t190\t191\tPEAAAA\tRJLAAA\tVVVVxx\n1671\t7688\t1\t3\t1\t11\t71\t671\t1671\t1671\t1671\t142\t143\tHMAAAA\tSJLAAA\tAAAAxx\n695\t7689\t1\t3\t5\t15\t95\t695\t695\t695\t695\t190\t191\tTAAAAA\tTJLAAA\tHHHHxx\n4981\t7690\t1\t1\t1\t1\t81\t981\t981\t4981\t4981\t162\t163\tPJAAAA\tUJLAAA\tOOOOxx\n1211\t7691\t1\t3\t1\t11\t11\t211\t1211\t1211\t1211\t22\t23\tPUAAAA\tVJLAAA\tVVVVxx\n5914\t7692\t0\t2\t4\t14\t14\t914\t1914\t914\t5914\t28\t29\tMTAAAA\tWJLAAA\tAAAAxx\n9356\t7693\t0\t0\t6\t16\t56\t356\t1356\t4356\t9356\t112\t113\tWVAAAA\tXJLAAA\tHHHHxx\n1500\t7694\t0\t0\t0\t0\t0\t500\t1500\t1500\t1500\t0\t1\tSFAAAA\tYJLAAA\tOOOOxx\n3353\t7695\t1\t1\t3\t13\t53\t353\t1353\t3353\t3353\t106\t107\tZYAAAA\tZJLAAA\tVVVVxx\n1060\t7696\t0\t0\t0\t0\t60\t60\t1060\t1060\t1060\t120\t121\tUOAAAA\tAKLAAA\tAAAAxx\n7910\t7697\t0\t2\t0\t10\t10\t910\t1910\t2910\t7910\t20\t21\tGSAAAA\tBKLAAA\tHHHHxx\n1329\t7698\t1\t1\t9\t9\t29\t329\t1329\t1329\t1329\t58\t59\tDZAAAA\tCKLAAA\tOOOOxx\n6011\t7699\t1\t3\t1\t11\t11\t11\t11\t1011\t6011\t22\t23\tFXAAAA\tDKLAAA\tVVVVxx\n7146\t7700\t0\t2\t6\t6\t46\t146\t1146\t2146\t7146\t92\t93\tWOAAAA\tEKLAAA\tAAAAxx\n4602\t7701\t0\t2\t2\t2\t2\t602\t602\t4602\t4602\t4\t5\tAVAAAA\tFKLAAA\tHHHHxx\n6751\t7702\t1\t3\t1\t11\t51\t751\t751\t1751\t6751\t102\t103\tRZAAAA\tGKLAAA\tOOOOxx\n2666\t7703\t0\t2\t6\t6\t66\t666\t666\t2666\t2666\t132\t133\tOYAAAA\tHKLAAA\tVVVVxx\n2785\t7704\t1\t1\t5\t5\t85\t785\t785\t2785\t2785\t170\t171\tDDAAAA\tIKLAAA\tAAAAxx\n5851\t7705\t1\t3\t1\t11\t51\t851\t1851\t851\t5851\t102\t103\tBRAAAA\tJKLAAA\tHHHHxx\n2435\t7706\t1\t3\t5\t15\t35\t435\t435\t2435\t2435\t70\t71\tRPAAAA\tKKLAAA\tOOOOxx\n7429\t7707\t1\t1\t9\t9\t29\t429\t1429\t2429\t7429\t58\t59\tTZAAAA\tLKLAAA\tVVVVxx\n4241\t7708\t1\t1\t1\t1\t41\t241\t241\t4241\t4241\t82\t83\tDHAAAA\tMKLAAA\tAAAAxx\n5691\t7709\t1\t3\t1\t11\t91\t691\t1691\t691\t5691\t182\t183\tXKAAAA\tNKLAAA\tHHHHxx\n7731\t7710\t1\t3\t1\t11\t31\t731\t1731\t2731\t7731\t62\t63\tJLAAAA\tOKLAAA\tOOOOxx\n249\t7711\t1\t1\t9\t9\t49\t249\t249\t249\t249\t98\t99\tPJAAAA\tPKLAAA\tVVVVxx\n1731\t7712\t1\t3\t1\t11\t31\t731\t1731\t1731\t1731\t62\t63\tPOAAAA\tQKLAAA\tAAAAxx\n8716\t7713\t0\t0\t6\t16\t16\t716\t716\t3716\t8716\t32\t33\tGXAAAA\tRKLAAA\tHHHHxx\n2670\t7714\t0\t2\t0\t10\t70\t670\t670\t2670\t2670\t140\t141\tSYAAAA\tSKLAAA\tOOOOxx\n4654\t7715\t0\t2\t4\t14\t54\t654\t654\t4654\t4654\t108\t109\tAXAAAA\tTKLAAA\tVVVVxx\n1027\t7716\t1\t3\t7\t7\t27\t27\t1027\t1027\t1027\t54\t55\tNNAAAA\tUKLAAA\tAAAAxx\n1099\t7717\t1\t3\t9\t19\t99\t99\t1099\t1099\t1099\t198\t199\tHQAAAA\tVKLAAA\tHHHHxx\n3617\t7718\t1\t1\t7\t17\t17\t617\t1617\t3617\t3617\t34\t35\tDJAAAA\tWKLAAA\tOOOOxx\n4330\t7719\t0\t2\t0\t10\t30\t330\t330\t4330\t4330\t60\t61\tOKAAAA\tXKLAAA\tVVVVxx\n9750\t7720\t0\t2\t0\t10\t50\t750\t1750\t4750\t9750\t100\t101\tALAAAA\tYKLAAA\tAAAAxx\n467\t7721\t1\t3\t7\t7\t67\t467\t467\t467\t467\t134\t135\tZRAAAA\tZKLAAA\tHHHHxx\n8525\t7722\t1\t1\t5\t5\t25\t525\t525\t3525\t8525\t50\t51\tXPAAAA\tALLAAA\tOOOOxx\n5990\t7723\t0\t2\t0\t10\t90\t990\t1990\t990\t5990\t180\t181\tKWAAAA\tBLLAAA\tVVVVxx\n4839\t7724\t1\t3\t9\t19\t39\t839\t839\t4839\t4839\t78\t79\tDEAAAA\tCLLAAA\tAAAAxx\n9914\t7725\t0\t2\t4\t14\t14\t914\t1914\t4914\t9914\t28\t29\tIRAAAA\tDLLAAA\tHHHHxx\n7047\t7726\t1\t3\t7\t7\t47\t47\t1047\t2047\t7047\t94\t95\tBLAAAA\tELLAAA\tOOOOxx\n874\t7727\t0\t2\t4\t14\t74\t874\t874\t874\t874\t148\t149\tQHAAAA\tFLLAAA\tVVVVxx\n6061\t7728\t1\t1\t1\t1\t61\t61\t61\t1061\t6061\t122\t123\tDZAAAA\tGLLAAA\tAAAAxx\n5491\t7729\t1\t3\t1\t11\t91\t491\t1491\t491\t5491\t182\t183\tFDAAAA\tHLLAAA\tHHHHxx\n4344\t7730\t0\t0\t4\t4\t44\t344\t344\t4344\t4344\t88\t89\tCLAAAA\tILLAAA\tOOOOxx\n1281\t7731\t1\t1\t1\t1\t81\t281\t1281\t1281\t1281\t162\t163\tHXAAAA\tJLLAAA\tVVVVxx\n3597\t7732\t1\t1\t7\t17\t97\t597\t1597\t3597\t3597\t194\t195\tJIAAAA\tKLLAAA\tAAAAxx\n4992\t7733\t0\t0\t2\t12\t92\t992\t992\t4992\t4992\t184\t185\tAKAAAA\tLLLAAA\tHHHHxx\n3849\t7734\t1\t1\t9\t9\t49\t849\t1849\t3849\t3849\t98\t99\tBSAAAA\tMLLAAA\tOOOOxx\n2655\t7735\t1\t3\t5\t15\t55\t655\t655\t2655\t2655\t110\t111\tDYAAAA\tNLLAAA\tVVVVxx\n147\t7736\t1\t3\t7\t7\t47\t147\t147\t147\t147\t94\t95\tRFAAAA\tOLLAAA\tAAAAxx\n9110\t7737\t0\t2\t0\t10\t10\t110\t1110\t4110\t9110\t20\t21\tKMAAAA\tPLLAAA\tHHHHxx\n1637\t7738\t1\t1\t7\t17\t37\t637\t1637\t1637\t1637\t74\t75\tZKAAAA\tQLLAAA\tOOOOxx\n9826\t7739\t0\t2\t6\t6\t26\t826\t1826\t4826\t9826\t52\t53\tYNAAAA\tRLLAAA\tVVVVxx\n5957\t7740\t1\t1\t7\t17\t57\t957\t1957\t957\t5957\t114\t115\tDVAAAA\tSLLAAA\tAAAAxx\n6932\t7741\t0\t0\t2\t12\t32\t932\t932\t1932\t6932\t64\t65\tQGAAAA\tTLLAAA\tHHHHxx\n9684\t7742\t0\t0\t4\t4\t84\t684\t1684\t4684\t9684\t168\t169\tMIAAAA\tULLAAA\tOOOOxx\n4653\t7743\t1\t1\t3\t13\t53\t653\t653\t4653\t4653\t106\t107\tZWAAAA\tVLLAAA\tVVVVxx\n8065\t7744\t1\t1\t5\t5\t65\t65\t65\t3065\t8065\t130\t131\tFYAAAA\tWLLAAA\tAAAAxx\n1202\t7745\t0\t2\t2\t2\t2\t202\t1202\t1202\t1202\t4\t5\tGUAAAA\tXLLAAA\tHHHHxx\n9214\t7746\t0\t2\t4\t14\t14\t214\t1214\t4214\t9214\t28\t29\tKQAAAA\tYLLAAA\tOOOOxx\n196\t7747\t0\t0\t6\t16\t96\t196\t196\t196\t196\t192\t193\tOHAAAA\tZLLAAA\tVVVVxx\n4486\t7748\t0\t2\t6\t6\t86\t486\t486\t4486\t4486\t172\t173\tOQAAAA\tAMLAAA\tAAAAxx\n2585\t7749\t1\t1\t5\t5\t85\t585\t585\t2585\t2585\t170\t171\tLVAAAA\tBMLAAA\tHHHHxx\n2464\t7750\t0\t0\t4\t4\t64\t464\t464\t2464\t2464\t128\t129\tUQAAAA\tCMLAAA\tOOOOxx\n3467\t7751\t1\t3\t7\t7\t67\t467\t1467\t3467\t3467\t134\t135\tJDAAAA\tDMLAAA\tVVVVxx\n9295\t7752\t1\t3\t5\t15\t95\t295\t1295\t4295\t9295\t190\t191\tNTAAAA\tEMLAAA\tAAAAxx\n517\t7753\t1\t1\t7\t17\t17\t517\t517\t517\t517\t34\t35\tXTAAAA\tFMLAAA\tHHHHxx\n6870\t7754\t0\t2\t0\t10\t70\t870\t870\t1870\t6870\t140\t141\tGEAAAA\tGMLAAA\tOOOOxx\n5732\t7755\t0\t0\t2\t12\t32\t732\t1732\t732\t5732\t64\t65\tMMAAAA\tHMLAAA\tVVVVxx\n9376\t7756\t0\t0\t6\t16\t76\t376\t1376\t4376\t9376\t152\t153\tQWAAAA\tIMLAAA\tAAAAxx\n838\t7757\t0\t2\t8\t18\t38\t838\t838\t838\t838\t76\t77\tGGAAAA\tJMLAAA\tHHHHxx\n9254\t7758\t0\t2\t4\t14\t54\t254\t1254\t4254\t9254\t108\t109\tYRAAAA\tKMLAAA\tOOOOxx\n8879\t7759\t1\t3\t9\t19\t79\t879\t879\t3879\t8879\t158\t159\tNDAAAA\tLMLAAA\tVVVVxx\n6281\t7760\t1\t1\t1\t1\t81\t281\t281\t1281\t6281\t162\t163\tPHAAAA\tMMLAAA\tAAAAxx\n8216\t7761\t0\t0\t6\t16\t16\t216\t216\t3216\t8216\t32\t33\tAEAAAA\tNMLAAA\tHHHHxx\n9213\t7762\t1\t1\t3\t13\t13\t213\t1213\t4213\t9213\t26\t27\tJQAAAA\tOMLAAA\tOOOOxx\n7234\t7763\t0\t2\t4\t14\t34\t234\t1234\t2234\t7234\t68\t69\tGSAAAA\tPMLAAA\tVVVVxx\n5692\t7764\t0\t0\t2\t12\t92\t692\t1692\t692\t5692\t184\t185\tYKAAAA\tQMLAAA\tAAAAxx\n693\t7765\t1\t1\t3\t13\t93\t693\t693\t693\t693\t186\t187\tRAAAAA\tRMLAAA\tHHHHxx\n9050\t7766\t0\t2\t0\t10\t50\t50\t1050\t4050\t9050\t100\t101\tCKAAAA\tSMLAAA\tOOOOxx\n3623\t7767\t1\t3\t3\t3\t23\t623\t1623\t3623\t3623\t46\t47\tJJAAAA\tTMLAAA\tVVVVxx\n2130\t7768\t0\t2\t0\t10\t30\t130\t130\t2130\t2130\t60\t61\tYDAAAA\tUMLAAA\tAAAAxx\n2514\t7769\t0\t2\t4\t14\t14\t514\t514\t2514\t2514\t28\t29\tSSAAAA\tVMLAAA\tHHHHxx\n1812\t7770\t0\t0\t2\t12\t12\t812\t1812\t1812\t1812\t24\t25\tSRAAAA\tWMLAAA\tOOOOxx\n9037\t7771\t1\t1\t7\t17\t37\t37\t1037\t4037\t9037\t74\t75\tPJAAAA\tXMLAAA\tVVVVxx\n5054\t7772\t0\t2\t4\t14\t54\t54\t1054\t54\t5054\t108\t109\tKMAAAA\tYMLAAA\tAAAAxx\n7801\t7773\t1\t1\t1\t1\t1\t801\t1801\t2801\t7801\t2\t3\tBOAAAA\tZMLAAA\tHHHHxx\n7939\t7774\t1\t3\t9\t19\t39\t939\t1939\t2939\t7939\t78\t79\tJTAAAA\tANLAAA\tOOOOxx\n7374\t7775\t0\t2\t4\t14\t74\t374\t1374\t2374\t7374\t148\t149\tQXAAAA\tBNLAAA\tVVVVxx\n1058\t7776\t0\t2\t8\t18\t58\t58\t1058\t1058\t1058\t116\t117\tSOAAAA\tCNLAAA\tAAAAxx\n1972\t7777\t0\t0\t2\t12\t72\t972\t1972\t1972\t1972\t144\t145\tWXAAAA\tDNLAAA\tHHHHxx\n3741\t7778\t1\t1\t1\t1\t41\t741\t1741\t3741\t3741\t82\t83\tXNAAAA\tENLAAA\tOOOOxx\n2227\t7779\t1\t3\t7\t7\t27\t227\t227\t2227\t2227\t54\t55\tRHAAAA\tFNLAAA\tVVVVxx\n304\t7780\t0\t0\t4\t4\t4\t304\t304\t304\t304\t8\t9\tSLAAAA\tGNLAAA\tAAAAxx\n4914\t7781\t0\t2\t4\t14\t14\t914\t914\t4914\t4914\t28\t29\tAHAAAA\tHNLAAA\tHHHHxx\n2428\t7782\t0\t0\t8\t8\t28\t428\t428\t2428\t2428\t56\t57\tKPAAAA\tINLAAA\tOOOOxx\n6660\t7783\t0\t0\t0\t0\t60\t660\t660\t1660\t6660\t120\t121\tEWAAAA\tJNLAAA\tVVVVxx\n2676\t7784\t0\t0\t6\t16\t76\t676\t676\t2676\t2676\t152\t153\tYYAAAA\tKNLAAA\tAAAAxx\n2454\t7785\t0\t2\t4\t14\t54\t454\t454\t2454\t2454\t108\t109\tKQAAAA\tLNLAAA\tHHHHxx\n3798\t7786\t0\t2\t8\t18\t98\t798\t1798\t3798\t3798\t196\t197\tCQAAAA\tMNLAAA\tOOOOxx\n1341\t7787\t1\t1\t1\t1\t41\t341\t1341\t1341\t1341\t82\t83\tPZAAAA\tNNLAAA\tVVVVxx\n1611\t7788\t1\t3\t1\t11\t11\t611\t1611\t1611\t1611\t22\t23\tZJAAAA\tONLAAA\tAAAAxx\n2681\t7789\t1\t1\t1\t1\t81\t681\t681\t2681\t2681\t162\t163\tDZAAAA\tPNLAAA\tHHHHxx\n7292\t7790\t0\t0\t2\t12\t92\t292\t1292\t2292\t7292\t184\t185\tMUAAAA\tQNLAAA\tOOOOxx\n7775\t7791\t1\t3\t5\t15\t75\t775\t1775\t2775\t7775\t150\t151\tBNAAAA\tRNLAAA\tVVVVxx\n794\t7792\t0\t2\t4\t14\t94\t794\t794\t794\t794\t188\t189\tOEAAAA\tSNLAAA\tAAAAxx\n8709\t7793\t1\t1\t9\t9\t9\t709\t709\t3709\t8709\t18\t19\tZWAAAA\tTNLAAA\tHHHHxx\n1901\t7794\t1\t1\t1\t1\t1\t901\t1901\t1901\t1901\t2\t3\tDVAAAA\tUNLAAA\tOOOOxx\n3089\t7795\t1\t1\t9\t9\t89\t89\t1089\t3089\t3089\t178\t179\tVOAAAA\tVNLAAA\tVVVVxx\n7797\t7796\t1\t1\t7\t17\t97\t797\t1797\t2797\t7797\t194\t195\tXNAAAA\tWNLAAA\tAAAAxx\n6070\t7797\t0\t2\t0\t10\t70\t70\t70\t1070\t6070\t140\t141\tMZAAAA\tXNLAAA\tHHHHxx\n2191\t7798\t1\t3\t1\t11\t91\t191\t191\t2191\t2191\t182\t183\tHGAAAA\tYNLAAA\tOOOOxx\n3497\t7799\t1\t1\t7\t17\t97\t497\t1497\t3497\t3497\t194\t195\tNEAAAA\tZNLAAA\tVVVVxx\n8302\t7800\t0\t2\t2\t2\t2\t302\t302\t3302\t8302\t4\t5\tIHAAAA\tAOLAAA\tAAAAxx\n4365\t7801\t1\t1\t5\t5\t65\t365\t365\t4365\t4365\t130\t131\tXLAAAA\tBOLAAA\tHHHHxx\n3588\t7802\t0\t0\t8\t8\t88\t588\t1588\t3588\t3588\t176\t177\tAIAAAA\tCOLAAA\tOOOOxx\n8292\t7803\t0\t0\t2\t12\t92\t292\t292\t3292\t8292\t184\t185\tYGAAAA\tDOLAAA\tVVVVxx\n4696\t7804\t0\t0\t6\t16\t96\t696\t696\t4696\t4696\t192\t193\tQYAAAA\tEOLAAA\tAAAAxx\n5641\t7805\t1\t1\t1\t1\t41\t641\t1641\t641\t5641\t82\t83\tZIAAAA\tFOLAAA\tHHHHxx\n9386\t7806\t0\t2\t6\t6\t86\t386\t1386\t4386\t9386\t172\t173\tAXAAAA\tGOLAAA\tOOOOxx\n507\t7807\t1\t3\t7\t7\t7\t507\t507\t507\t507\t14\t15\tNTAAAA\tHOLAAA\tVVVVxx\n7201\t7808\t1\t1\t1\t1\t1\t201\t1201\t2201\t7201\t2\t3\tZQAAAA\tIOLAAA\tAAAAxx\n7785\t7809\t1\t1\t5\t5\t85\t785\t1785\t2785\t7785\t170\t171\tLNAAAA\tJOLAAA\tHHHHxx\n463\t7810\t1\t3\t3\t3\t63\t463\t463\t463\t463\t126\t127\tVRAAAA\tKOLAAA\tOOOOxx\n6656\t7811\t0\t0\t6\t16\t56\t656\t656\t1656\t6656\t112\t113\tAWAAAA\tLOLAAA\tVVVVxx\n807\t7812\t1\t3\t7\t7\t7\t807\t807\t807\t807\t14\t15\tBFAAAA\tMOLAAA\tAAAAxx\n7278\t7813\t0\t2\t8\t18\t78\t278\t1278\t2278\t7278\t156\t157\tYTAAAA\tNOLAAA\tHHHHxx\n6237\t7814\t1\t1\t7\t17\t37\t237\t237\t1237\t6237\t74\t75\tXFAAAA\tOOLAAA\tOOOOxx\n7671\t7815\t1\t3\t1\t11\t71\t671\t1671\t2671\t7671\t142\t143\tBJAAAA\tPOLAAA\tVVVVxx\n2235\t7816\t1\t3\t5\t15\t35\t235\t235\t2235\t2235\t70\t71\tZHAAAA\tQOLAAA\tAAAAxx\n4042\t7817\t0\t2\t2\t2\t42\t42\t42\t4042\t4042\t84\t85\tMZAAAA\tROLAAA\tHHHHxx\n5273\t7818\t1\t1\t3\t13\t73\t273\t1273\t273\t5273\t146\t147\tVUAAAA\tSOLAAA\tOOOOxx\n7557\t7819\t1\t1\t7\t17\t57\t557\t1557\t2557\t7557\t114\t115\tREAAAA\tTOLAAA\tVVVVxx\n4007\t7820\t1\t3\t7\t7\t7\t7\t7\t4007\t4007\t14\t15\tDYAAAA\tUOLAAA\tAAAAxx\n1428\t7821\t0\t0\t8\t8\t28\t428\t1428\t1428\t1428\t56\t57\tYCAAAA\tVOLAAA\tHHHHxx\n9739\t7822\t1\t3\t9\t19\t39\t739\t1739\t4739\t9739\t78\t79\tPKAAAA\tWOLAAA\tOOOOxx\n7836\t7823\t0\t0\t6\t16\t36\t836\t1836\t2836\t7836\t72\t73\tKPAAAA\tXOLAAA\tVVVVxx\n1777\t7824\t1\t1\t7\t17\t77\t777\t1777\t1777\t1777\t154\t155\tJQAAAA\tYOLAAA\tAAAAxx\n5192\t7825\t0\t0\t2\t12\t92\t192\t1192\t192\t5192\t184\t185\tSRAAAA\tZOLAAA\tHHHHxx\n7236\t7826\t0\t0\t6\t16\t36\t236\t1236\t2236\t7236\t72\t73\tISAAAA\tAPLAAA\tOOOOxx\n1623\t7827\t1\t3\t3\t3\t23\t623\t1623\t1623\t1623\t46\t47\tLKAAAA\tBPLAAA\tVVVVxx\n8288\t7828\t0\t0\t8\t8\t88\t288\t288\t3288\t8288\t176\t177\tUGAAAA\tCPLAAA\tAAAAxx\n2827\t7829\t1\t3\t7\t7\t27\t827\t827\t2827\t2827\t54\t55\tTEAAAA\tDPLAAA\tHHHHxx\n458\t7830\t0\t2\t8\t18\t58\t458\t458\t458\t458\t116\t117\tQRAAAA\tEPLAAA\tOOOOxx\n1818\t7831\t0\t2\t8\t18\t18\t818\t1818\t1818\t1818\t36\t37\tYRAAAA\tFPLAAA\tVVVVxx\n6837\t7832\t1\t1\t7\t17\t37\t837\t837\t1837\t6837\t74\t75\tZCAAAA\tGPLAAA\tAAAAxx\n7825\t7833\t1\t1\t5\t5\t25\t825\t1825\t2825\t7825\t50\t51\tZOAAAA\tHPLAAA\tHHHHxx\n9146\t7834\t0\t2\t6\t6\t46\t146\t1146\t4146\t9146\t92\t93\tUNAAAA\tIPLAAA\tOOOOxx\n8451\t7835\t1\t3\t1\t11\t51\t451\t451\t3451\t8451\t102\t103\tBNAAAA\tJPLAAA\tVVVVxx\n6438\t7836\t0\t2\t8\t18\t38\t438\t438\t1438\t6438\t76\t77\tQNAAAA\tKPLAAA\tAAAAxx\n4020\t7837\t0\t0\t0\t0\t20\t20\t20\t4020\t4020\t40\t41\tQYAAAA\tLPLAAA\tHHHHxx\n4068\t7838\t0\t0\t8\t8\t68\t68\t68\t4068\t4068\t136\t137\tMAAAAA\tMPLAAA\tOOOOxx\n2411\t7839\t1\t3\t1\t11\t11\t411\t411\t2411\t2411\t22\t23\tTOAAAA\tNPLAAA\tVVVVxx\n6222\t7840\t0\t2\t2\t2\t22\t222\t222\t1222\t6222\t44\t45\tIFAAAA\tOPLAAA\tAAAAxx\n3164\t7841\t0\t0\t4\t4\t64\t164\t1164\t3164\t3164\t128\t129\tSRAAAA\tPPLAAA\tHHHHxx\n311\t7842\t1\t3\t1\t11\t11\t311\t311\t311\t311\t22\t23\tZLAAAA\tQPLAAA\tOOOOxx\n5683\t7843\t1\t3\t3\t3\t83\t683\t1683\t683\t5683\t166\t167\tPKAAAA\tRPLAAA\tVVVVxx\n3993\t7844\t1\t1\t3\t13\t93\t993\t1993\t3993\t3993\t186\t187\tPXAAAA\tSPLAAA\tAAAAxx\n9897\t7845\t1\t1\t7\t17\t97\t897\t1897\t4897\t9897\t194\t195\tRQAAAA\tTPLAAA\tHHHHxx\n6609\t7846\t1\t1\t9\t9\t9\t609\t609\t1609\t6609\t18\t19\tFUAAAA\tUPLAAA\tOOOOxx\n1362\t7847\t0\t2\t2\t2\t62\t362\t1362\t1362\t1362\t124\t125\tKAAAAA\tVPLAAA\tVVVVxx\n3918\t7848\t0\t2\t8\t18\t18\t918\t1918\t3918\t3918\t36\t37\tSUAAAA\tWPLAAA\tAAAAxx\n7376\t7849\t0\t0\t6\t16\t76\t376\t1376\t2376\t7376\t152\t153\tSXAAAA\tXPLAAA\tHHHHxx\n6996\t7850\t0\t0\t6\t16\t96\t996\t996\t1996\t6996\t192\t193\tCJAAAA\tYPLAAA\tOOOOxx\n9567\t7851\t1\t3\t7\t7\t67\t567\t1567\t4567\t9567\t134\t135\tZDAAAA\tZPLAAA\tVVVVxx\n7525\t7852\t1\t1\t5\t5\t25\t525\t1525\t2525\t7525\t50\t51\tLDAAAA\tAQLAAA\tAAAAxx\n9069\t7853\t1\t1\t9\t9\t69\t69\t1069\t4069\t9069\t138\t139\tVKAAAA\tBQLAAA\tHHHHxx\n9999\t7854\t1\t3\t9\t19\t99\t999\t1999\t4999\t9999\t198\t199\tPUAAAA\tCQLAAA\tOOOOxx\n9237\t7855\t1\t1\t7\t17\t37\t237\t1237\t4237\t9237\t74\t75\tHRAAAA\tDQLAAA\tVVVVxx\n8441\t7856\t1\t1\t1\t1\t41\t441\t441\t3441\t8441\t82\t83\tRMAAAA\tEQLAAA\tAAAAxx\n6769\t7857\t1\t1\t9\t9\t69\t769\t769\t1769\t6769\t138\t139\tJAAAAA\tFQLAAA\tHHHHxx\n6073\t7858\t1\t1\t3\t13\t73\t73\t73\t1073\t6073\t146\t147\tPZAAAA\tGQLAAA\tOOOOxx\n1091\t7859\t1\t3\t1\t11\t91\t91\t1091\t1091\t1091\t182\t183\tZPAAAA\tHQLAAA\tVVVVxx\n9886\t7860\t0\t2\t6\t6\t86\t886\t1886\t4886\t9886\t172\t173\tGQAAAA\tIQLAAA\tAAAAxx\n3971\t7861\t1\t3\t1\t11\t71\t971\t1971\t3971\t3971\t142\t143\tTWAAAA\tJQLAAA\tHHHHxx\n4621\t7862\t1\t1\t1\t1\t21\t621\t621\t4621\t4621\t42\t43\tTVAAAA\tKQLAAA\tOOOOxx\n3120\t7863\t0\t0\t0\t0\t20\t120\t1120\t3120\t3120\t40\t41\tAQAAAA\tLQLAAA\tVVVVxx\n9773\t7864\t1\t1\t3\t13\t73\t773\t1773\t4773\t9773\t146\t147\tXLAAAA\tMQLAAA\tAAAAxx\n8712\t7865\t0\t0\t2\t12\t12\t712\t712\t3712\t8712\t24\t25\tCXAAAA\tNQLAAA\tHHHHxx\n801\t7866\t1\t1\t1\t1\t1\t801\t801\t801\t801\t2\t3\tVEAAAA\tOQLAAA\tOOOOxx\n9478\t7867\t0\t2\t8\t18\t78\t478\t1478\t4478\t9478\t156\t157\tOAAAAA\tPQLAAA\tVVVVxx\n3466\t7868\t0\t2\t6\t6\t66\t466\t1466\t3466\t3466\t132\t133\tIDAAAA\tQQLAAA\tAAAAxx\n6326\t7869\t0\t2\t6\t6\t26\t326\t326\t1326\t6326\t52\t53\tIJAAAA\tRQLAAA\tHHHHxx\n1723\t7870\t1\t3\t3\t3\t23\t723\t1723\t1723\t1723\t46\t47\tHOAAAA\tSQLAAA\tOOOOxx\n4978\t7871\t0\t2\t8\t18\t78\t978\t978\t4978\t4978\t156\t157\tMJAAAA\tTQLAAA\tVVVVxx\n2311\t7872\t1\t3\t1\t11\t11\t311\t311\t2311\t2311\t22\t23\tXKAAAA\tUQLAAA\tAAAAxx\n9532\t7873\t0\t0\t2\t12\t32\t532\t1532\t4532\t9532\t64\t65\tQCAAAA\tVQLAAA\tHHHHxx\n3680\t7874\t0\t0\t0\t0\t80\t680\t1680\t3680\t3680\t160\t161\tOLAAAA\tWQLAAA\tOOOOxx\n1244\t7875\t0\t0\t4\t4\t44\t244\t1244\t1244\t1244\t88\t89\tWVAAAA\tXQLAAA\tVVVVxx\n3821\t7876\t1\t1\t1\t1\t21\t821\t1821\t3821\t3821\t42\t43\tZQAAAA\tYQLAAA\tAAAAxx\n9586\t7877\t0\t2\t6\t6\t86\t586\t1586\t4586\t9586\t172\t173\tSEAAAA\tZQLAAA\tHHHHxx\n3894\t7878\t0\t2\t4\t14\t94\t894\t1894\t3894\t3894\t188\t189\tUTAAAA\tARLAAA\tOOOOxx\n6169\t7879\t1\t1\t9\t9\t69\t169\t169\t1169\t6169\t138\t139\tHDAAAA\tBRLAAA\tVVVVxx\n5919\t7880\t1\t3\t9\t19\t19\t919\t1919\t919\t5919\t38\t39\tRTAAAA\tCRLAAA\tAAAAxx\n4187\t7881\t1\t3\t7\t7\t87\t187\t187\t4187\t4187\t174\t175\tBFAAAA\tDRLAAA\tHHHHxx\n5477\t7882\t1\t1\t7\t17\t77\t477\t1477\t477\t5477\t154\t155\tRCAAAA\tERLAAA\tOOOOxx\n2806\t7883\t0\t2\t6\t6\t6\t806\t806\t2806\t2806\t12\t13\tYDAAAA\tFRLAAA\tVVVVxx\n8158\t7884\t0\t2\t8\t18\t58\t158\t158\t3158\t8158\t116\t117\tUBAAAA\tGRLAAA\tAAAAxx\n7130\t7885\t0\t2\t0\t10\t30\t130\t1130\t2130\t7130\t60\t61\tGOAAAA\tHRLAAA\tHHHHxx\n7133\t7886\t1\t1\t3\t13\t33\t133\t1133\t2133\t7133\t66\t67\tJOAAAA\tIRLAAA\tOOOOxx\n6033\t7887\t1\t1\t3\t13\t33\t33\t33\t1033\t6033\t66\t67\tBYAAAA\tJRLAAA\tVVVVxx\n2415\t7888\t1\t3\t5\t15\t15\t415\t415\t2415\t2415\t30\t31\tXOAAAA\tKRLAAA\tAAAAxx\n8091\t7889\t1\t3\t1\t11\t91\t91\t91\t3091\t8091\t182\t183\tFZAAAA\tLRLAAA\tHHHHxx\n8347\t7890\t1\t3\t7\t7\t47\t347\t347\t3347\t8347\t94\t95\tBJAAAA\tMRLAAA\tOOOOxx\n7879\t7891\t1\t3\t9\t19\t79\t879\t1879\t2879\t7879\t158\t159\tBRAAAA\tNRLAAA\tVVVVxx\n9360\t7892\t0\t0\t0\t0\t60\t360\t1360\t4360\t9360\t120\t121\tAWAAAA\tORLAAA\tAAAAxx\n3369\t7893\t1\t1\t9\t9\t69\t369\t1369\t3369\t3369\t138\t139\tPZAAAA\tPRLAAA\tHHHHxx\n8536\t7894\t0\t0\t6\t16\t36\t536\t536\t3536\t8536\t72\t73\tIQAAAA\tQRLAAA\tOOOOxx\n8628\t7895\t0\t0\t8\t8\t28\t628\t628\t3628\t8628\t56\t57\tWTAAAA\tRRLAAA\tVVVVxx\n1580\t7896\t0\t0\t0\t0\t80\t580\t1580\t1580\t1580\t160\t161\tUIAAAA\tSRLAAA\tAAAAxx\n705\t7897\t1\t1\t5\t5\t5\t705\t705\t705\t705\t10\t11\tDBAAAA\tTRLAAA\tHHHHxx\n4650\t7898\t0\t2\t0\t10\t50\t650\t650\t4650\t4650\t100\t101\tWWAAAA\tURLAAA\tOOOOxx\n9165\t7899\t1\t1\t5\t5\t65\t165\t1165\t4165\t9165\t130\t131\tNOAAAA\tVRLAAA\tVVVVxx\n4820\t7900\t0\t0\t0\t0\t20\t820\t820\t4820\t4820\t40\t41\tKDAAAA\tWRLAAA\tAAAAxx\n3538\t7901\t0\t2\t8\t18\t38\t538\t1538\t3538\t3538\t76\t77\tCGAAAA\tXRLAAA\tHHHHxx\n9947\t7902\t1\t3\t7\t7\t47\t947\t1947\t4947\t9947\t94\t95\tPSAAAA\tYRLAAA\tOOOOxx\n4954\t7903\t0\t2\t4\t14\t54\t954\t954\t4954\t4954\t108\t109\tOIAAAA\tZRLAAA\tVVVVxx\n1104\t7904\t0\t0\t4\t4\t4\t104\t1104\t1104\t1104\t8\t9\tMQAAAA\tASLAAA\tAAAAxx\n8455\t7905\t1\t3\t5\t15\t55\t455\t455\t3455\t8455\t110\t111\tFNAAAA\tBSLAAA\tHHHHxx\n8307\t7906\t1\t3\t7\t7\t7\t307\t307\t3307\t8307\t14\t15\tNHAAAA\tCSLAAA\tOOOOxx\n9203\t7907\t1\t3\t3\t3\t3\t203\t1203\t4203\t9203\t6\t7\tZPAAAA\tDSLAAA\tVVVVxx\n7565\t7908\t1\t1\t5\t5\t65\t565\t1565\t2565\t7565\t130\t131\tZEAAAA\tESLAAA\tAAAAxx\n7745\t7909\t1\t1\t5\t5\t45\t745\t1745\t2745\t7745\t90\t91\tXLAAAA\tFSLAAA\tHHHHxx\n1787\t7910\t1\t3\t7\t7\t87\t787\t1787\t1787\t1787\t174\t175\tTQAAAA\tGSLAAA\tOOOOxx\n4861\t7911\t1\t1\t1\t1\t61\t861\t861\t4861\t4861\t122\t123\tZEAAAA\tHSLAAA\tVVVVxx\n5183\t7912\t1\t3\t3\t3\t83\t183\t1183\t183\t5183\t166\t167\tJRAAAA\tISLAAA\tAAAAxx\n529\t7913\t1\t1\t9\t9\t29\t529\t529\t529\t529\t58\t59\tJUAAAA\tJSLAAA\tHHHHxx\n2470\t7914\t0\t2\t0\t10\t70\t470\t470\t2470\t2470\t140\t141\tARAAAA\tKSLAAA\tOOOOxx\n1267\t7915\t1\t3\t7\t7\t67\t267\t1267\t1267\t1267\t134\t135\tTWAAAA\tLSLAAA\tVVVVxx\n2059\t7916\t1\t3\t9\t19\t59\t59\t59\t2059\t2059\t118\t119\tFBAAAA\tMSLAAA\tAAAAxx\n1862\t7917\t0\t2\t2\t2\t62\t862\t1862\t1862\t1862\t124\t125\tQTAAAA\tNSLAAA\tHHHHxx\n7382\t7918\t0\t2\t2\t2\t82\t382\t1382\t2382\t7382\t164\t165\tYXAAAA\tOSLAAA\tOOOOxx\n4796\t7919\t0\t0\t6\t16\t96\t796\t796\t4796\t4796\t192\t193\tMCAAAA\tPSLAAA\tVVVVxx\n2331\t7920\t1\t3\t1\t11\t31\t331\t331\t2331\t2331\t62\t63\tRLAAAA\tQSLAAA\tAAAAxx\n8870\t7921\t0\t2\t0\t10\t70\t870\t870\t3870\t8870\t140\t141\tEDAAAA\tRSLAAA\tHHHHxx\n9581\t7922\t1\t1\t1\t1\t81\t581\t1581\t4581\t9581\t162\t163\tNEAAAA\tSSLAAA\tOOOOxx\n9063\t7923\t1\t3\t3\t3\t63\t63\t1063\t4063\t9063\t126\t127\tPKAAAA\tTSLAAA\tVVVVxx\n2192\t7924\t0\t0\t2\t12\t92\t192\t192\t2192\t2192\t184\t185\tIGAAAA\tUSLAAA\tAAAAxx\n6466\t7925\t0\t2\t6\t6\t66\t466\t466\t1466\t6466\t132\t133\tSOAAAA\tVSLAAA\tHHHHxx\n7096\t7926\t0\t0\t6\t16\t96\t96\t1096\t2096\t7096\t192\t193\tYMAAAA\tWSLAAA\tOOOOxx\n6257\t7927\t1\t1\t7\t17\t57\t257\t257\t1257\t6257\t114\t115\tRGAAAA\tXSLAAA\tVVVVxx\n7009\t7928\t1\t1\t9\t9\t9\t9\t1009\t2009\t7009\t18\t19\tPJAAAA\tYSLAAA\tAAAAxx\n8136\t7929\t0\t0\t6\t16\t36\t136\t136\t3136\t8136\t72\t73\tYAAAAA\tZSLAAA\tHHHHxx\n1854\t7930\t0\t2\t4\t14\t54\t854\t1854\t1854\t1854\t108\t109\tITAAAA\tATLAAA\tOOOOxx\n3644\t7931\t0\t0\t4\t4\t44\t644\t1644\t3644\t3644\t88\t89\tEKAAAA\tBTLAAA\tVVVVxx\n4437\t7932\t1\t1\t7\t17\t37\t437\t437\t4437\t4437\t74\t75\tROAAAA\tCTLAAA\tAAAAxx\n7209\t7933\t1\t1\t9\t9\t9\t209\t1209\t2209\t7209\t18\t19\tHRAAAA\tDTLAAA\tHHHHxx\n1516\t7934\t0\t0\t6\t16\t16\t516\t1516\t1516\t1516\t32\t33\tIGAAAA\tETLAAA\tOOOOxx\n822\t7935\t0\t2\t2\t2\t22\t822\t822\t822\t822\t44\t45\tQFAAAA\tFTLAAA\tVVVVxx\n1778\t7936\t0\t2\t8\t18\t78\t778\t1778\t1778\t1778\t156\t157\tKQAAAA\tGTLAAA\tAAAAxx\n8161\t7937\t1\t1\t1\t1\t61\t161\t161\t3161\t8161\t122\t123\tXBAAAA\tHTLAAA\tHHHHxx\n6030\t7938\t0\t2\t0\t10\t30\t30\t30\t1030\t6030\t60\t61\tYXAAAA\tITLAAA\tOOOOxx\n3515\t7939\t1\t3\t5\t15\t15\t515\t1515\t3515\t3515\t30\t31\tFFAAAA\tJTLAAA\tVVVVxx\n1702\t7940\t0\t2\t2\t2\t2\t702\t1702\t1702\t1702\t4\t5\tMNAAAA\tKTLAAA\tAAAAxx\n2671\t7941\t1\t3\t1\t11\t71\t671\t671\t2671\t2671\t142\t143\tTYAAAA\tLTLAAA\tHHHHxx\n7623\t7942\t1\t3\t3\t3\t23\t623\t1623\t2623\t7623\t46\t47\tFHAAAA\tMTLAAA\tOOOOxx\n9828\t7943\t0\t0\t8\t8\t28\t828\t1828\t4828\t9828\t56\t57\tAOAAAA\tNTLAAA\tVVVVxx\n1888\t7944\t0\t0\t8\t8\t88\t888\t1888\t1888\t1888\t176\t177\tQUAAAA\tOTLAAA\tAAAAxx\n4520\t7945\t0\t0\t0\t0\t20\t520\t520\t4520\t4520\t40\t41\tWRAAAA\tPTLAAA\tHHHHxx\n3461\t7946\t1\t1\t1\t1\t61\t461\t1461\t3461\t3461\t122\t123\tDDAAAA\tQTLAAA\tOOOOxx\n1488\t7947\t0\t0\t8\t8\t88\t488\t1488\t1488\t1488\t176\t177\tGFAAAA\tRTLAAA\tVVVVxx\n7753\t7948\t1\t1\t3\t13\t53\t753\t1753\t2753\t7753\t106\t107\tFMAAAA\tSTLAAA\tAAAAxx\n5525\t7949\t1\t1\t5\t5\t25\t525\t1525\t525\t5525\t50\t51\tNEAAAA\tTTLAAA\tHHHHxx\n5220\t7950\t0\t0\t0\t0\t20\t220\t1220\t220\t5220\t40\t41\tUSAAAA\tUTLAAA\tOOOOxx\n305\t7951\t1\t1\t5\t5\t5\t305\t305\t305\t305\t10\t11\tTLAAAA\tVTLAAA\tVVVVxx\n7883\t7952\t1\t3\t3\t3\t83\t883\t1883\t2883\t7883\t166\t167\tFRAAAA\tWTLAAA\tAAAAxx\n1222\t7953\t0\t2\t2\t2\t22\t222\t1222\t1222\t1222\t44\t45\tAVAAAA\tXTLAAA\tHHHHxx\n8552\t7954\t0\t0\t2\t12\t52\t552\t552\t3552\t8552\t104\t105\tYQAAAA\tYTLAAA\tOOOOxx\n6097\t7955\t1\t1\t7\t17\t97\t97\t97\t1097\t6097\t194\t195\tNAAAAA\tZTLAAA\tVVVVxx\n2298\t7956\t0\t2\t8\t18\t98\t298\t298\t2298\t2298\t196\t197\tKKAAAA\tAULAAA\tAAAAxx\n956\t7957\t0\t0\t6\t16\t56\t956\t956\t956\t956\t112\t113\tUKAAAA\tBULAAA\tHHHHxx\n9351\t7958\t1\t3\t1\t11\t51\t351\t1351\t4351\t9351\t102\t103\tRVAAAA\tCULAAA\tOOOOxx\n6669\t7959\t1\t1\t9\t9\t69\t669\t669\t1669\t6669\t138\t139\tNWAAAA\tDULAAA\tVVVVxx\n9383\t7960\t1\t3\t3\t3\t83\t383\t1383\t4383\t9383\t166\t167\tXWAAAA\tEULAAA\tAAAAxx\n1607\t7961\t1\t3\t7\t7\t7\t607\t1607\t1607\t1607\t14\t15\tVJAAAA\tFULAAA\tHHHHxx\n812\t7962\t0\t0\t2\t12\t12\t812\t812\t812\t812\t24\t25\tGFAAAA\tGULAAA\tOOOOxx\n2109\t7963\t1\t1\t9\t9\t9\t109\t109\t2109\t2109\t18\t19\tDDAAAA\tHULAAA\tVVVVxx\n207\t7964\t1\t3\t7\t7\t7\t207\t207\t207\t207\t14\t15\tZHAAAA\tIULAAA\tAAAAxx\n7124\t7965\t0\t0\t4\t4\t24\t124\t1124\t2124\t7124\t48\t49\tAOAAAA\tJULAAA\tHHHHxx\n9333\t7966\t1\t1\t3\t13\t33\t333\t1333\t4333\t9333\t66\t67\tZUAAAA\tKULAAA\tOOOOxx\n3262\t7967\t0\t2\t2\t2\t62\t262\t1262\t3262\t3262\t124\t125\tMVAAAA\tLULAAA\tVVVVxx\n1070\t7968\t0\t2\t0\t10\t70\t70\t1070\t1070\t1070\t140\t141\tEPAAAA\tMULAAA\tAAAAxx\n7579\t7969\t1\t3\t9\t19\t79\t579\t1579\t2579\t7579\t158\t159\tNFAAAA\tNULAAA\tHHHHxx\n9283\t7970\t1\t3\t3\t3\t83\t283\t1283\t4283\t9283\t166\t167\tBTAAAA\tOULAAA\tOOOOxx\n4917\t7971\t1\t1\t7\t17\t17\t917\t917\t4917\t4917\t34\t35\tDHAAAA\tPULAAA\tVVVVxx\n1328\t7972\t0\t0\t8\t8\t28\t328\t1328\t1328\t1328\t56\t57\tCZAAAA\tQULAAA\tAAAAxx\n3042\t7973\t0\t2\t2\t2\t42\t42\t1042\t3042\t3042\t84\t85\tANAAAA\tRULAAA\tHHHHxx\n8352\t7974\t0\t0\t2\t12\t52\t352\t352\t3352\t8352\t104\t105\tGJAAAA\tSULAAA\tOOOOxx\n2710\t7975\t0\t2\t0\t10\t10\t710\t710\t2710\t2710\t20\t21\tGAAAAA\tTULAAA\tVVVVxx\n3330\t7976\t0\t2\t0\t10\t30\t330\t1330\t3330\t3330\t60\t61\tCYAAAA\tUULAAA\tAAAAxx\n2822\t7977\t0\t2\t2\t2\t22\t822\t822\t2822\t2822\t44\t45\tOEAAAA\tVULAAA\tHHHHxx\n5627\t7978\t1\t3\t7\t7\t27\t627\t1627\t627\t5627\t54\t55\tLIAAAA\tWULAAA\tOOOOxx\n7848\t7979\t0\t0\t8\t8\t48\t848\t1848\t2848\t7848\t96\t97\tWPAAAA\tXULAAA\tVVVVxx\n7384\t7980\t0\t0\t4\t4\t84\t384\t1384\t2384\t7384\t168\t169\tAYAAAA\tYULAAA\tAAAAxx\n727\t7981\t1\t3\t7\t7\t27\t727\t727\t727\t727\t54\t55\tZBAAAA\tZULAAA\tHHHHxx\n9926\t7982\t0\t2\t6\t6\t26\t926\t1926\t4926\t9926\t52\t53\tURAAAA\tAVLAAA\tOOOOxx\n2647\t7983\t1\t3\t7\t7\t47\t647\t647\t2647\t2647\t94\t95\tVXAAAA\tBVLAAA\tVVVVxx\n6416\t7984\t0\t0\t6\t16\t16\t416\t416\t1416\t6416\t32\t33\tUMAAAA\tCVLAAA\tAAAAxx\n8751\t7985\t1\t3\t1\t11\t51\t751\t751\t3751\t8751\t102\t103\tPYAAAA\tDVLAAA\tHHHHxx\n6515\t7986\t1\t3\t5\t15\t15\t515\t515\t1515\t6515\t30\t31\tPQAAAA\tEVLAAA\tOOOOxx\n2472\t7987\t0\t0\t2\t12\t72\t472\t472\t2472\t2472\t144\t145\tCRAAAA\tFVLAAA\tVVVVxx\n7205\t7988\t1\t1\t5\t5\t5\t205\t1205\t2205\t7205\t10\t11\tDRAAAA\tGVLAAA\tAAAAxx\n9654\t7989\t0\t2\t4\t14\t54\t654\t1654\t4654\t9654\t108\t109\tIHAAAA\tHVLAAA\tHHHHxx\n5646\t7990\t0\t2\t6\t6\t46\t646\t1646\t646\t5646\t92\t93\tEJAAAA\tIVLAAA\tOOOOxx\n4217\t7991\t1\t1\t7\t17\t17\t217\t217\t4217\t4217\t34\t35\tFGAAAA\tJVLAAA\tVVVVxx\n4484\t7992\t0\t0\t4\t4\t84\t484\t484\t4484\t4484\t168\t169\tMQAAAA\tKVLAAA\tAAAAxx\n6654\t7993\t0\t2\t4\t14\t54\t654\t654\t1654\t6654\t108\t109\tYVAAAA\tLVLAAA\tHHHHxx\n4876\t7994\t0\t0\t6\t16\t76\t876\t876\t4876\t4876\t152\t153\tOFAAAA\tMVLAAA\tOOOOxx\n9690\t7995\t0\t2\t0\t10\t90\t690\t1690\t4690\t9690\t180\t181\tSIAAAA\tNVLAAA\tVVVVxx\n2453\t7996\t1\t1\t3\t13\t53\t453\t453\t2453\t2453\t106\t107\tJQAAAA\tOVLAAA\tAAAAxx\n829\t7997\t1\t1\t9\t9\t29\t829\t829\t829\t829\t58\t59\tXFAAAA\tPVLAAA\tHHHHxx\n2547\t7998\t1\t3\t7\t7\t47\t547\t547\t2547\t2547\t94\t95\tZTAAAA\tQVLAAA\tOOOOxx\n9726\t7999\t0\t2\t6\t6\t26\t726\t1726\t4726\t9726\t52\t53\tCKAAAA\tRVLAAA\tVVVVxx\n9267\t8000\t1\t3\t7\t7\t67\t267\t1267\t4267\t9267\t134\t135\tLSAAAA\tSVLAAA\tAAAAxx\n7448\t8001\t0\t0\t8\t8\t48\t448\t1448\t2448\t7448\t96\t97\tMAAAAA\tTVLAAA\tHHHHxx\n610\t8002\t0\t2\t0\t10\t10\t610\t610\t610\t610\t20\t21\tMXAAAA\tUVLAAA\tOOOOxx\n2791\t8003\t1\t3\t1\t11\t91\t791\t791\t2791\t2791\t182\t183\tJDAAAA\tVVLAAA\tVVVVxx\n3651\t8004\t1\t3\t1\t11\t51\t651\t1651\t3651\t3651\t102\t103\tLKAAAA\tWVLAAA\tAAAAxx\n5206\t8005\t0\t2\t6\t6\t6\t206\t1206\t206\t5206\t12\t13\tGSAAAA\tXVLAAA\tHHHHxx\n8774\t8006\t0\t2\t4\t14\t74\t774\t774\t3774\t8774\t148\t149\tMZAAAA\tYVLAAA\tOOOOxx\n4753\t8007\t1\t1\t3\t13\t53\t753\t753\t4753\t4753\t106\t107\tVAAAAA\tZVLAAA\tVVVVxx\n4755\t8008\t1\t3\t5\t15\t55\t755\t755\t4755\t4755\t110\t111\tXAAAAA\tAWLAAA\tAAAAxx\n686\t8009\t0\t2\t6\t6\t86\t686\t686\t686\t686\t172\t173\tKAAAAA\tBWLAAA\tHHHHxx\n8281\t8010\t1\t1\t1\t1\t81\t281\t281\t3281\t8281\t162\t163\tNGAAAA\tCWLAAA\tOOOOxx\n2058\t8011\t0\t2\t8\t18\t58\t58\t58\t2058\t2058\t116\t117\tEBAAAA\tDWLAAA\tVVVVxx\n8900\t8012\t0\t0\t0\t0\t0\t900\t900\t3900\t8900\t0\t1\tIEAAAA\tEWLAAA\tAAAAxx\n8588\t8013\t0\t0\t8\t8\t88\t588\t588\t3588\t8588\t176\t177\tISAAAA\tFWLAAA\tHHHHxx\n2904\t8014\t0\t0\t4\t4\t4\t904\t904\t2904\t2904\t8\t9\tSHAAAA\tGWLAAA\tOOOOxx\n8917\t8015\t1\t1\t7\t17\t17\t917\t917\t3917\t8917\t34\t35\tZEAAAA\tHWLAAA\tVVVVxx\n9026\t8016\t0\t2\t6\t6\t26\t26\t1026\t4026\t9026\t52\t53\tEJAAAA\tIWLAAA\tAAAAxx\n2416\t8017\t0\t0\t6\t16\t16\t416\t416\t2416\t2416\t32\t33\tYOAAAA\tJWLAAA\tHHHHxx\n1053\t8018\t1\t1\t3\t13\t53\t53\t1053\t1053\t1053\t106\t107\tNOAAAA\tKWLAAA\tOOOOxx\n7141\t8019\t1\t1\t1\t1\t41\t141\t1141\t2141\t7141\t82\t83\tROAAAA\tLWLAAA\tVVVVxx\n9771\t8020\t1\t3\t1\t11\t71\t771\t1771\t4771\t9771\t142\t143\tVLAAAA\tMWLAAA\tAAAAxx\n2774\t8021\t0\t2\t4\t14\t74\t774\t774\t2774\t2774\t148\t149\tSCAAAA\tNWLAAA\tHHHHxx\n3213\t8022\t1\t1\t3\t13\t13\t213\t1213\t3213\t3213\t26\t27\tPTAAAA\tOWLAAA\tOOOOxx\n5694\t8023\t0\t2\t4\t14\t94\t694\t1694\t694\t5694\t188\t189\tALAAAA\tPWLAAA\tVVVVxx\n6631\t8024\t1\t3\t1\t11\t31\t631\t631\t1631\t6631\t62\t63\tBVAAAA\tQWLAAA\tAAAAxx\n6638\t8025\t0\t2\t8\t18\t38\t638\t638\t1638\t6638\t76\t77\tIVAAAA\tRWLAAA\tHHHHxx\n7407\t8026\t1\t3\t7\t7\t7\t407\t1407\t2407\t7407\t14\t15\tXYAAAA\tSWLAAA\tOOOOxx\n8972\t8027\t0\t0\t2\t12\t72\t972\t972\t3972\t8972\t144\t145\tCHAAAA\tTWLAAA\tVVVVxx\n2202\t8028\t0\t2\t2\t2\t2\t202\t202\t2202\t2202\t4\t5\tSGAAAA\tUWLAAA\tAAAAxx\n6135\t8029\t1\t3\t5\t15\t35\t135\t135\t1135\t6135\t70\t71\tZBAAAA\tVWLAAA\tHHHHxx\n5043\t8030\t1\t3\t3\t3\t43\t43\t1043\t43\t5043\t86\t87\tZLAAAA\tWWLAAA\tOOOOxx\n5163\t8031\t1\t3\t3\t3\t63\t163\t1163\t163\t5163\t126\t127\tPQAAAA\tXWLAAA\tVVVVxx\n1191\t8032\t1\t3\t1\t11\t91\t191\t1191\t1191\t1191\t182\t183\tVTAAAA\tYWLAAA\tAAAAxx\n6576\t8033\t0\t0\t6\t16\t76\t576\t576\t1576\t6576\t152\t153\tYSAAAA\tZWLAAA\tHHHHxx\n3455\t8034\t1\t3\t5\t15\t55\t455\t1455\t3455\t3455\t110\t111\tXCAAAA\tAXLAAA\tOOOOxx\n3688\t8035\t0\t0\t8\t8\t88\t688\t1688\t3688\t3688\t176\t177\tWLAAAA\tBXLAAA\tVVVVxx\n4982\t8036\t0\t2\t2\t2\t82\t982\t982\t4982\t4982\t164\t165\tQJAAAA\tCXLAAA\tAAAAxx\n4180\t8037\t0\t0\t0\t0\t80\t180\t180\t4180\t4180\t160\t161\tUEAAAA\tDXLAAA\tHHHHxx\n4708\t8038\t0\t0\t8\t8\t8\t708\t708\t4708\t4708\t16\t17\tCZAAAA\tEXLAAA\tOOOOxx\n1241\t8039\t1\t1\t1\t1\t41\t241\t1241\t1241\t1241\t82\t83\tTVAAAA\tFXLAAA\tVVVVxx\n4921\t8040\t1\t1\t1\t1\t21\t921\t921\t4921\t4921\t42\t43\tHHAAAA\tGXLAAA\tAAAAxx\n3197\t8041\t1\t1\t7\t17\t97\t197\t1197\t3197\t3197\t194\t195\tZSAAAA\tHXLAAA\tHHHHxx\n8225\t8042\t1\t1\t5\t5\t25\t225\t225\t3225\t8225\t50\t51\tJEAAAA\tIXLAAA\tOOOOxx\n5913\t8043\t1\t1\t3\t13\t13\t913\t1913\t913\t5913\t26\t27\tLTAAAA\tJXLAAA\tVVVVxx\n6387\t8044\t1\t3\t7\t7\t87\t387\t387\t1387\t6387\t174\t175\tRLAAAA\tKXLAAA\tAAAAxx\n2706\t8045\t0\t2\t6\t6\t6\t706\t706\t2706\t2706\t12\t13\tCAAAAA\tLXLAAA\tHHHHxx\n1461\t8046\t1\t1\t1\t1\t61\t461\t1461\t1461\t1461\t122\t123\tFEAAAA\tMXLAAA\tOOOOxx\n7646\t8047\t0\t2\t6\t6\t46\t646\t1646\t2646\t7646\t92\t93\tCIAAAA\tNXLAAA\tVVVVxx\n8066\t8048\t0\t2\t6\t6\t66\t66\t66\t3066\t8066\t132\t133\tGYAAAA\tOXLAAA\tAAAAxx\n4171\t8049\t1\t3\t1\t11\t71\t171\t171\t4171\t4171\t142\t143\tLEAAAA\tPXLAAA\tHHHHxx\n8008\t8050\t0\t0\t8\t8\t8\t8\t8\t3008\t8008\t16\t17\tAWAAAA\tQXLAAA\tOOOOxx\n2088\t8051\t0\t0\t8\t8\t88\t88\t88\t2088\t2088\t176\t177\tICAAAA\tRXLAAA\tVVVVxx\n7907\t8052\t1\t3\t7\t7\t7\t907\t1907\t2907\t7907\t14\t15\tDSAAAA\tSXLAAA\tAAAAxx\n2429\t8053\t1\t1\t9\t9\t29\t429\t429\t2429\t2429\t58\t59\tLPAAAA\tTXLAAA\tHHHHxx\n9629\t8054\t1\t1\t9\t9\t29\t629\t1629\t4629\t9629\t58\t59\tJGAAAA\tUXLAAA\tOOOOxx\n1470\t8055\t0\t2\t0\t10\t70\t470\t1470\t1470\t1470\t140\t141\tOEAAAA\tVXLAAA\tVVVVxx\n4346\t8056\t0\t2\t6\t6\t46\t346\t346\t4346\t4346\t92\t93\tELAAAA\tWXLAAA\tAAAAxx\n7219\t8057\t1\t3\t9\t19\t19\t219\t1219\t2219\t7219\t38\t39\tRRAAAA\tXXLAAA\tHHHHxx\n1185\t8058\t1\t1\t5\t5\t85\t185\t1185\t1185\t1185\t170\t171\tPTAAAA\tYXLAAA\tOOOOxx\n8776\t8059\t0\t0\t6\t16\t76\t776\t776\t3776\t8776\t152\t153\tOZAAAA\tZXLAAA\tVVVVxx\n684\t8060\t0\t0\t4\t4\t84\t684\t684\t684\t684\t168\t169\tIAAAAA\tAYLAAA\tAAAAxx\n2343\t8061\t1\t3\t3\t3\t43\t343\t343\t2343\t2343\t86\t87\tDMAAAA\tBYLAAA\tHHHHxx\n4470\t8062\t0\t2\t0\t10\t70\t470\t470\t4470\t4470\t140\t141\tYPAAAA\tCYLAAA\tOOOOxx\n5116\t8063\t0\t0\t6\t16\t16\t116\t1116\t116\t5116\t32\t33\tUOAAAA\tDYLAAA\tVVVVxx\n1746\t8064\t0\t2\t6\t6\t46\t746\t1746\t1746\t1746\t92\t93\tEPAAAA\tEYLAAA\tAAAAxx\n3216\t8065\t0\t0\t6\t16\t16\t216\t1216\t3216\t3216\t32\t33\tSTAAAA\tFYLAAA\tHHHHxx\n4594\t8066\t0\t2\t4\t14\t94\t594\t594\t4594\t4594\t188\t189\tSUAAAA\tGYLAAA\tOOOOxx\n3013\t8067\t1\t1\t3\t13\t13\t13\t1013\t3013\t3013\t26\t27\tXLAAAA\tHYLAAA\tVVVVxx\n2307\t8068\t1\t3\t7\t7\t7\t307\t307\t2307\t2307\t14\t15\tTKAAAA\tIYLAAA\tAAAAxx\n7663\t8069\t1\t3\t3\t3\t63\t663\t1663\t2663\t7663\t126\t127\tTIAAAA\tJYLAAA\tHHHHxx\n8504\t8070\t0\t0\t4\t4\t4\t504\t504\t3504\t8504\t8\t9\tCPAAAA\tKYLAAA\tOOOOxx\n3683\t8071\t1\t3\t3\t3\t83\t683\t1683\t3683\t3683\t166\t167\tRLAAAA\tLYLAAA\tVVVVxx\n144\t8072\t0\t0\t4\t4\t44\t144\t144\t144\t144\t88\t89\tOFAAAA\tMYLAAA\tAAAAxx\n203\t8073\t1\t3\t3\t3\t3\t203\t203\t203\t203\t6\t7\tVHAAAA\tNYLAAA\tHHHHxx\n5255\t8074\t1\t3\t5\t15\t55\t255\t1255\t255\t5255\t110\t111\tDUAAAA\tOYLAAA\tOOOOxx\n4150\t8075\t0\t2\t0\t10\t50\t150\t150\t4150\t4150\t100\t101\tQDAAAA\tPYLAAA\tVVVVxx\n5701\t8076\t1\t1\t1\t1\t1\t701\t1701\t701\t5701\t2\t3\tHLAAAA\tQYLAAA\tAAAAxx\n7400\t8077\t0\t0\t0\t0\t0\t400\t1400\t2400\t7400\t0\t1\tQYAAAA\tRYLAAA\tHHHHxx\n8203\t8078\t1\t3\t3\t3\t3\t203\t203\t3203\t8203\t6\t7\tNDAAAA\tSYLAAA\tOOOOxx\n637\t8079\t1\t1\t7\t17\t37\t637\t637\t637\t637\t74\t75\tNYAAAA\tTYLAAA\tVVVVxx\n2898\t8080\t0\t2\t8\t18\t98\t898\t898\t2898\t2898\t196\t197\tMHAAAA\tUYLAAA\tAAAAxx\n1110\t8081\t0\t2\t0\t10\t10\t110\t1110\t1110\t1110\t20\t21\tSQAAAA\tVYLAAA\tHHHHxx\n6255\t8082\t1\t3\t5\t15\t55\t255\t255\t1255\t6255\t110\t111\tPGAAAA\tWYLAAA\tOOOOxx\n1071\t8083\t1\t3\t1\t11\t71\t71\t1071\t1071\t1071\t142\t143\tFPAAAA\tXYLAAA\tVVVVxx\n541\t8084\t1\t1\t1\t1\t41\t541\t541\t541\t541\t82\t83\tVUAAAA\tYYLAAA\tAAAAxx\n8077\t8085\t1\t1\t7\t17\t77\t77\t77\t3077\t8077\t154\t155\tRYAAAA\tZYLAAA\tHHHHxx\n6809\t8086\t1\t1\t9\t9\t9\t809\t809\t1809\t6809\t18\t19\tXBAAAA\tAZLAAA\tOOOOxx\n4749\t8087\t1\t1\t9\t9\t49\t749\t749\t4749\t4749\t98\t99\tRAAAAA\tBZLAAA\tVVVVxx\n2886\t8088\t0\t2\t6\t6\t86\t886\t886\t2886\t2886\t172\t173\tAHAAAA\tCZLAAA\tAAAAxx\n5510\t8089\t0\t2\t0\t10\t10\t510\t1510\t510\t5510\t20\t21\tYDAAAA\tDZLAAA\tHHHHxx\n713\t8090\t1\t1\t3\t13\t13\t713\t713\t713\t713\t26\t27\tLBAAAA\tEZLAAA\tOOOOxx\n8388\t8091\t0\t0\t8\t8\t88\t388\t388\t3388\t8388\t176\t177\tQKAAAA\tFZLAAA\tVVVVxx\n9524\t8092\t0\t0\t4\t4\t24\t524\t1524\t4524\t9524\t48\t49\tICAAAA\tGZLAAA\tAAAAxx\n9949\t8093\t1\t1\t9\t9\t49\t949\t1949\t4949\t9949\t98\t99\tRSAAAA\tHZLAAA\tHHHHxx\n885\t8094\t1\t1\t5\t5\t85\t885\t885\t885\t885\t170\t171\tBIAAAA\tIZLAAA\tOOOOxx\n8699\t8095\t1\t3\t9\t19\t99\t699\t699\t3699\t8699\t198\t199\tPWAAAA\tJZLAAA\tVVVVxx\n2232\t8096\t0\t0\t2\t12\t32\t232\t232\t2232\t2232\t64\t65\tWHAAAA\tKZLAAA\tAAAAxx\n5142\t8097\t0\t2\t2\t2\t42\t142\t1142\t142\t5142\t84\t85\tUPAAAA\tLZLAAA\tHHHHxx\n8891\t8098\t1\t3\t1\t11\t91\t891\t891\t3891\t8891\t182\t183\tZDAAAA\tMZLAAA\tOOOOxx\n1881\t8099\t1\t1\t1\t1\t81\t881\t1881\t1881\t1881\t162\t163\tJUAAAA\tNZLAAA\tVVVVxx\n3751\t8100\t1\t3\t1\t11\t51\t751\t1751\t3751\t3751\t102\t103\tHOAAAA\tOZLAAA\tAAAAxx\n1896\t8101\t0\t0\t6\t16\t96\t896\t1896\t1896\t1896\t192\t193\tYUAAAA\tPZLAAA\tHHHHxx\n8258\t8102\t0\t2\t8\t18\t58\t258\t258\t3258\t8258\t116\t117\tQFAAAA\tQZLAAA\tOOOOxx\n3820\t8103\t0\t0\t0\t0\t20\t820\t1820\t3820\t3820\t40\t41\tYQAAAA\tRZLAAA\tVVVVxx\n6617\t8104\t1\t1\t7\t17\t17\t617\t617\t1617\t6617\t34\t35\tNUAAAA\tSZLAAA\tAAAAxx\n5100\t8105\t0\t0\t0\t0\t0\t100\t1100\t100\t5100\t0\t1\tEOAAAA\tTZLAAA\tHHHHxx\n4277\t8106\t1\t1\t7\t17\t77\t277\t277\t4277\t4277\t154\t155\tNIAAAA\tUZLAAA\tOOOOxx\n2498\t8107\t0\t2\t8\t18\t98\t498\t498\t2498\t2498\t196\t197\tCSAAAA\tVZLAAA\tVVVVxx\n4343\t8108\t1\t3\t3\t3\t43\t343\t343\t4343\t4343\t86\t87\tBLAAAA\tWZLAAA\tAAAAxx\n8319\t8109\t1\t3\t9\t19\t19\t319\t319\t3319\t8319\t38\t39\tZHAAAA\tXZLAAA\tHHHHxx\n4803\t8110\t1\t3\t3\t3\t3\t803\t803\t4803\t4803\t6\t7\tTCAAAA\tYZLAAA\tOOOOxx\n3100\t8111\t0\t0\t0\t0\t0\t100\t1100\t3100\t3100\t0\t1\tGPAAAA\tZZLAAA\tVVVVxx\n428\t8112\t0\t0\t8\t8\t28\t428\t428\t428\t428\t56\t57\tMQAAAA\tAAMAAA\tAAAAxx\n2811\t8113\t1\t3\t1\t11\t11\t811\t811\t2811\t2811\t22\t23\tDEAAAA\tBAMAAA\tHHHHxx\n2989\t8114\t1\t1\t9\t9\t89\t989\t989\t2989\t2989\t178\t179\tZKAAAA\tCAMAAA\tOOOOxx\n1100\t8115\t0\t0\t0\t0\t0\t100\t1100\t1100\t1100\t0\t1\tIQAAAA\tDAMAAA\tVVVVxx\n6586\t8116\t0\t2\t6\t6\t86\t586\t586\t1586\t6586\t172\t173\tITAAAA\tEAMAAA\tAAAAxx\n3124\t8117\t0\t0\t4\t4\t24\t124\t1124\t3124\t3124\t48\t49\tEQAAAA\tFAMAAA\tHHHHxx\n1635\t8118\t1\t3\t5\t15\t35\t635\t1635\t1635\t1635\t70\t71\tXKAAAA\tGAMAAA\tOOOOxx\n3888\t8119\t0\t0\t8\t8\t88\t888\t1888\t3888\t3888\t176\t177\tOTAAAA\tHAMAAA\tVVVVxx\n8369\t8120\t1\t1\t9\t9\t69\t369\t369\t3369\t8369\t138\t139\tXJAAAA\tIAMAAA\tAAAAxx\n3148\t8121\t0\t0\t8\t8\t48\t148\t1148\t3148\t3148\t96\t97\tCRAAAA\tJAMAAA\tHHHHxx\n2842\t8122\t0\t2\t2\t2\t42\t842\t842\t2842\t2842\t84\t85\tIFAAAA\tKAMAAA\tOOOOxx\n4965\t8123\t1\t1\t5\t5\t65\t965\t965\t4965\t4965\t130\t131\tZIAAAA\tLAMAAA\tVVVVxx\n3742\t8124\t0\t2\t2\t2\t42\t742\t1742\t3742\t3742\t84\t85\tYNAAAA\tMAMAAA\tAAAAxx\n5196\t8125\t0\t0\t6\t16\t96\t196\t1196\t196\t5196\t192\t193\tWRAAAA\tNAMAAA\tHHHHxx\n9105\t8126\t1\t1\t5\t5\t5\t105\t1105\t4105\t9105\t10\t11\tFMAAAA\tOAMAAA\tOOOOxx\n6806\t8127\t0\t2\t6\t6\t6\t806\t806\t1806\t6806\t12\t13\tUBAAAA\tPAMAAA\tVVVVxx\n5849\t8128\t1\t1\t9\t9\t49\t849\t1849\t849\t5849\t98\t99\tZQAAAA\tQAMAAA\tAAAAxx\n6504\t8129\t0\t0\t4\t4\t4\t504\t504\t1504\t6504\t8\t9\tEQAAAA\tRAMAAA\tHHHHxx\n9841\t8130\t1\t1\t1\t1\t41\t841\t1841\t4841\t9841\t82\t83\tNOAAAA\tSAMAAA\tOOOOxx\n457\t8131\t1\t1\t7\t17\t57\t457\t457\t457\t457\t114\t115\tPRAAAA\tTAMAAA\tVVVVxx\n8856\t8132\t0\t0\t6\t16\t56\t856\t856\t3856\t8856\t112\t113\tQCAAAA\tUAMAAA\tAAAAxx\n8043\t8133\t1\t3\t3\t3\t43\t43\t43\t3043\t8043\t86\t87\tJXAAAA\tVAMAAA\tHHHHxx\n5933\t8134\t1\t1\t3\t13\t33\t933\t1933\t933\t5933\t66\t67\tFUAAAA\tWAMAAA\tOOOOxx\n5725\t8135\t1\t1\t5\t5\t25\t725\t1725\t725\t5725\t50\t51\tFMAAAA\tXAMAAA\tVVVVxx\n8607\t8136\t1\t3\t7\t7\t7\t607\t607\t3607\t8607\t14\t15\tBTAAAA\tYAMAAA\tAAAAxx\n9280\t8137\t0\t0\t0\t0\t80\t280\t1280\t4280\t9280\t160\t161\tYSAAAA\tZAMAAA\tHHHHxx\n6017\t8138\t1\t1\t7\t17\t17\t17\t17\t1017\t6017\t34\t35\tLXAAAA\tABMAAA\tOOOOxx\n4946\t8139\t0\t2\t6\t6\t46\t946\t946\t4946\t4946\t92\t93\tGIAAAA\tBBMAAA\tVVVVxx\n7373\t8140\t1\t1\t3\t13\t73\t373\t1373\t2373\t7373\t146\t147\tPXAAAA\tCBMAAA\tAAAAxx\n8096\t8141\t0\t0\t6\t16\t96\t96\t96\t3096\t8096\t192\t193\tKZAAAA\tDBMAAA\tHHHHxx\n3178\t8142\t0\t2\t8\t18\t78\t178\t1178\t3178\t3178\t156\t157\tGSAAAA\tEBMAAA\tOOOOxx\n1849\t8143\t1\t1\t9\t9\t49\t849\t1849\t1849\t1849\t98\t99\tDTAAAA\tFBMAAA\tVVVVxx\n8813\t8144\t1\t1\t3\t13\t13\t813\t813\t3813\t8813\t26\t27\tZAAAAA\tGBMAAA\tAAAAxx\n460\t8145\t0\t0\t0\t0\t60\t460\t460\t460\t460\t120\t121\tSRAAAA\tHBMAAA\tHHHHxx\n7756\t8146\t0\t0\t6\t16\t56\t756\t1756\t2756\t7756\t112\t113\tIMAAAA\tIBMAAA\tOOOOxx\n4425\t8147\t1\t1\t5\t5\t25\t425\t425\t4425\t4425\t50\t51\tFOAAAA\tJBMAAA\tVVVVxx\n1602\t8148\t0\t2\t2\t2\t2\t602\t1602\t1602\t1602\t4\t5\tQJAAAA\tKBMAAA\tAAAAxx\n5981\t8149\t1\t1\t1\t1\t81\t981\t1981\t981\t5981\t162\t163\tBWAAAA\tLBMAAA\tHHHHxx\n8139\t8150\t1\t3\t9\t19\t39\t139\t139\t3139\t8139\t78\t79\tBBAAAA\tMBMAAA\tOOOOxx\n754\t8151\t0\t2\t4\t14\t54\t754\t754\t754\t754\t108\t109\tADAAAA\tNBMAAA\tVVVVxx\n26\t8152\t0\t2\t6\t6\t26\t26\t26\t26\t26\t52\t53\tABAAAA\tOBMAAA\tAAAAxx\n106\t8153\t0\t2\t6\t6\t6\t106\t106\t106\t106\t12\t13\tCEAAAA\tPBMAAA\tHHHHxx\n7465\t8154\t1\t1\t5\t5\t65\t465\t1465\t2465\t7465\t130\t131\tDBAAAA\tQBMAAA\tOOOOxx\n1048\t8155\t0\t0\t8\t8\t48\t48\t1048\t1048\t1048\t96\t97\tIOAAAA\tRBMAAA\tVVVVxx\n2303\t8156\t1\t3\t3\t3\t3\t303\t303\t2303\t2303\t6\t7\tPKAAAA\tSBMAAA\tAAAAxx\n5794\t8157\t0\t2\t4\t14\t94\t794\t1794\t794\t5794\t188\t189\tWOAAAA\tTBMAAA\tHHHHxx\n3321\t8158\t1\t1\t1\t1\t21\t321\t1321\t3321\t3321\t42\t43\tTXAAAA\tUBMAAA\tOOOOxx\n6122\t8159\t0\t2\t2\t2\t22\t122\t122\t1122\t6122\t44\t45\tMBAAAA\tVBMAAA\tVVVVxx\n6474\t8160\t0\t2\t4\t14\t74\t474\t474\t1474\t6474\t148\t149\tAPAAAA\tWBMAAA\tAAAAxx\n827\t8161\t1\t3\t7\t7\t27\t827\t827\t827\t827\t54\t55\tVFAAAA\tXBMAAA\tHHHHxx\n6616\t8162\t0\t0\t6\t16\t16\t616\t616\t1616\t6616\t32\t33\tMUAAAA\tYBMAAA\tOOOOxx\n2131\t8163\t1\t3\t1\t11\t31\t131\t131\t2131\t2131\t62\t63\tZDAAAA\tZBMAAA\tVVVVxx\n5483\t8164\t1\t3\t3\t3\t83\t483\t1483\t483\t5483\t166\t167\tXCAAAA\tACMAAA\tAAAAxx\n606\t8165\t0\t2\t6\t6\t6\t606\t606\t606\t606\t12\t13\tIXAAAA\tBCMAAA\tHHHHxx\n922\t8166\t0\t2\t2\t2\t22\t922\t922\t922\t922\t44\t45\tMJAAAA\tCCMAAA\tOOOOxx\n8475\t8167\t1\t3\t5\t15\t75\t475\t475\t3475\t8475\t150\t151\tZNAAAA\tDCMAAA\tVVVVxx\n7645\t8168\t1\t1\t5\t5\t45\t645\t1645\t2645\t7645\t90\t91\tBIAAAA\tECMAAA\tAAAAxx\n5097\t8169\t1\t1\t7\t17\t97\t97\t1097\t97\t5097\t194\t195\tBOAAAA\tFCMAAA\tHHHHxx\n5377\t8170\t1\t1\t7\t17\t77\t377\t1377\t377\t5377\t154\t155\tVYAAAA\tGCMAAA\tOOOOxx\n6116\t8171\t0\t0\t6\t16\t16\t116\t116\t1116\t6116\t32\t33\tGBAAAA\tHCMAAA\tVVVVxx\n8674\t8172\t0\t2\t4\t14\t74\t674\t674\t3674\t8674\t148\t149\tQVAAAA\tICMAAA\tAAAAxx\n8063\t8173\t1\t3\t3\t3\t63\t63\t63\t3063\t8063\t126\t127\tDYAAAA\tJCMAAA\tHHHHxx\n5271\t8174\t1\t3\t1\t11\t71\t271\t1271\t271\t5271\t142\t143\tTUAAAA\tKCMAAA\tOOOOxx\n1619\t8175\t1\t3\t9\t19\t19\t619\t1619\t1619\t1619\t38\t39\tHKAAAA\tLCMAAA\tVVVVxx\n6419\t8176\t1\t3\t9\t19\t19\t419\t419\t1419\t6419\t38\t39\tXMAAAA\tMCMAAA\tAAAAxx\n7651\t8177\t1\t3\t1\t11\t51\t651\t1651\t2651\t7651\t102\t103\tHIAAAA\tNCMAAA\tHHHHxx\n2897\t8178\t1\t1\t7\t17\t97\t897\t897\t2897\t2897\t194\t195\tLHAAAA\tOCMAAA\tOOOOxx\n8148\t8179\t0\t0\t8\t8\t48\t148\t148\t3148\t8148\t96\t97\tKBAAAA\tPCMAAA\tVVVVxx\n7461\t8180\t1\t1\t1\t1\t61\t461\t1461\t2461\t7461\t122\t123\tZAAAAA\tQCMAAA\tAAAAxx\n9186\t8181\t0\t2\t6\t6\t86\t186\t1186\t4186\t9186\t172\t173\tIPAAAA\tRCMAAA\tHHHHxx\n7127\t8182\t1\t3\t7\t7\t27\t127\t1127\t2127\t7127\t54\t55\tDOAAAA\tSCMAAA\tOOOOxx\n8233\t8183\t1\t1\t3\t13\t33\t233\t233\t3233\t8233\t66\t67\tREAAAA\tTCMAAA\tVVVVxx\n9651\t8184\t1\t3\t1\t11\t51\t651\t1651\t4651\t9651\t102\t103\tFHAAAA\tUCMAAA\tAAAAxx\n6746\t8185\t0\t2\t6\t6\t46\t746\t746\t1746\t6746\t92\t93\tMZAAAA\tVCMAAA\tHHHHxx\n7835\t8186\t1\t3\t5\t15\t35\t835\t1835\t2835\t7835\t70\t71\tJPAAAA\tWCMAAA\tOOOOxx\n8815\t8187\t1\t3\t5\t15\t15\t815\t815\t3815\t8815\t30\t31\tBBAAAA\tXCMAAA\tVVVVxx\n6398\t8188\t0\t2\t8\t18\t98\t398\t398\t1398\t6398\t196\t197\tCMAAAA\tYCMAAA\tAAAAxx\n5344\t8189\t0\t0\t4\t4\t44\t344\t1344\t344\t5344\t88\t89\tOXAAAA\tZCMAAA\tHHHHxx\n8209\t8190\t1\t1\t9\t9\t9\t209\t209\t3209\t8209\t18\t19\tTDAAAA\tADMAAA\tOOOOxx\n8444\t8191\t0\t0\t4\t4\t44\t444\t444\t3444\t8444\t88\t89\tUMAAAA\tBDMAAA\tVVVVxx\n5669\t8192\t1\t1\t9\t9\t69\t669\t1669\t669\t5669\t138\t139\tBKAAAA\tCDMAAA\tAAAAxx\n2455\t8193\t1\t3\t5\t15\t55\t455\t455\t2455\t2455\t110\t111\tLQAAAA\tDDMAAA\tHHHHxx\n6767\t8194\t1\t3\t7\t7\t67\t767\t767\t1767\t6767\t134\t135\tHAAAAA\tEDMAAA\tOOOOxx\n135\t8195\t1\t3\t5\t15\t35\t135\t135\t135\t135\t70\t71\tFFAAAA\tFDMAAA\tVVVVxx\n3503\t8196\t1\t3\t3\t3\t3\t503\t1503\t3503\t3503\t6\t7\tTEAAAA\tGDMAAA\tAAAAxx\n6102\t8197\t0\t2\t2\t2\t2\t102\t102\t1102\t6102\t4\t5\tSAAAAA\tHDMAAA\tHHHHxx\n7136\t8198\t0\t0\t6\t16\t36\t136\t1136\t2136\t7136\t72\t73\tMOAAAA\tIDMAAA\tOOOOxx\n4933\t8199\t1\t1\t3\t13\t33\t933\t933\t4933\t4933\t66\t67\tTHAAAA\tJDMAAA\tVVVVxx\n8804\t8200\t0\t0\t4\t4\t4\t804\t804\t3804\t8804\t8\t9\tQAAAAA\tKDMAAA\tAAAAxx\n3760\t8201\t0\t0\t0\t0\t60\t760\t1760\t3760\t3760\t120\t121\tQOAAAA\tLDMAAA\tHHHHxx\n8603\t8202\t1\t3\t3\t3\t3\t603\t603\t3603\t8603\t6\t7\tXSAAAA\tMDMAAA\tOOOOxx\n7411\t8203\t1\t3\t1\t11\t11\t411\t1411\t2411\t7411\t22\t23\tBZAAAA\tNDMAAA\tVVVVxx\n834\t8204\t0\t2\t4\t14\t34\t834\t834\t834\t834\t68\t69\tCGAAAA\tODMAAA\tAAAAxx\n7385\t8205\t1\t1\t5\t5\t85\t385\t1385\t2385\t7385\t170\t171\tBYAAAA\tPDMAAA\tHHHHxx\n3696\t8206\t0\t0\t6\t16\t96\t696\t1696\t3696\t3696\t192\t193\tEMAAAA\tQDMAAA\tOOOOxx\n8720\t8207\t0\t0\t0\t0\t20\t720\t720\t3720\t8720\t40\t41\tKXAAAA\tRDMAAA\tVVVVxx\n4539\t8208\t1\t3\t9\t19\t39\t539\t539\t4539\t4539\t78\t79\tPSAAAA\tSDMAAA\tAAAAxx\n9837\t8209\t1\t1\t7\t17\t37\t837\t1837\t4837\t9837\t74\t75\tJOAAAA\tTDMAAA\tHHHHxx\n8595\t8210\t1\t3\t5\t15\t95\t595\t595\t3595\t8595\t190\t191\tPSAAAA\tUDMAAA\tOOOOxx\n3673\t8211\t1\t1\t3\t13\t73\t673\t1673\t3673\t3673\t146\t147\tHLAAAA\tVDMAAA\tVVVVxx\n475\t8212\t1\t3\t5\t15\t75\t475\t475\t475\t475\t150\t151\tHSAAAA\tWDMAAA\tAAAAxx\n2256\t8213\t0\t0\t6\t16\t56\t256\t256\t2256\t2256\t112\t113\tUIAAAA\tXDMAAA\tHHHHxx\n6349\t8214\t1\t1\t9\t9\t49\t349\t349\t1349\t6349\t98\t99\tFKAAAA\tYDMAAA\tOOOOxx\n9968\t8215\t0\t0\t8\t8\t68\t968\t1968\t4968\t9968\t136\t137\tKTAAAA\tZDMAAA\tVVVVxx\n7261\t8216\t1\t1\t1\t1\t61\t261\t1261\t2261\t7261\t122\t123\tHTAAAA\tAEMAAA\tAAAAxx\n5799\t8217\t1\t3\t9\t19\t99\t799\t1799\t799\t5799\t198\t199\tBPAAAA\tBEMAAA\tHHHHxx\n8159\t8218\t1\t3\t9\t19\t59\t159\t159\t3159\t8159\t118\t119\tVBAAAA\tCEMAAA\tOOOOxx\n92\t8219\t0\t0\t2\t12\t92\t92\t92\t92\t92\t184\t185\tODAAAA\tDEMAAA\tVVVVxx\n5927\t8220\t1\t3\t7\t7\t27\t927\t1927\t927\t5927\t54\t55\tZTAAAA\tEEMAAA\tAAAAxx\n7925\t8221\t1\t1\t5\t5\t25\t925\t1925\t2925\t7925\t50\t51\tVSAAAA\tFEMAAA\tHHHHxx\n5836\t8222\t0\t0\t6\t16\t36\t836\t1836\t836\t5836\t72\t73\tMQAAAA\tGEMAAA\tOOOOxx\n7935\t8223\t1\t3\t5\t15\t35\t935\t1935\t2935\t7935\t70\t71\tFTAAAA\tHEMAAA\tVVVVxx\n5505\t8224\t1\t1\t5\t5\t5\t505\t1505\t505\t5505\t10\t11\tTDAAAA\tIEMAAA\tAAAAxx\n5882\t8225\t0\t2\t2\t2\t82\t882\t1882\t882\t5882\t164\t165\tGSAAAA\tJEMAAA\tHHHHxx\n4411\t8226\t1\t3\t1\t11\t11\t411\t411\t4411\t4411\t22\t23\tRNAAAA\tKEMAAA\tOOOOxx\n64\t8227\t0\t0\t4\t4\t64\t64\t64\t64\t64\t128\t129\tMCAAAA\tLEMAAA\tVVVVxx\n2851\t8228\t1\t3\t1\t11\t51\t851\t851\t2851\t2851\t102\t103\tRFAAAA\tMEMAAA\tAAAAxx\n1665\t8229\t1\t1\t5\t5\t65\t665\t1665\t1665\t1665\t130\t131\tBMAAAA\tNEMAAA\tHHHHxx\n2895\t8230\t1\t3\t5\t15\t95\t895\t895\t2895\t2895\t190\t191\tJHAAAA\tOEMAAA\tOOOOxx\n2210\t8231\t0\t2\t0\t10\t10\t210\t210\t2210\t2210\t20\t21\tAHAAAA\tPEMAAA\tVVVVxx\n9873\t8232\t1\t1\t3\t13\t73\t873\t1873\t4873\t9873\t146\t147\tTPAAAA\tQEMAAA\tAAAAxx\n5402\t8233\t0\t2\t2\t2\t2\t402\t1402\t402\t5402\t4\t5\tUZAAAA\tREMAAA\tHHHHxx\n285\t8234\t1\t1\t5\t5\t85\t285\t285\t285\t285\t170\t171\tZKAAAA\tSEMAAA\tOOOOxx\n8545\t8235\t1\t1\t5\t5\t45\t545\t545\t3545\t8545\t90\t91\tRQAAAA\tTEMAAA\tVVVVxx\n5328\t8236\t0\t0\t8\t8\t28\t328\t1328\t328\t5328\t56\t57\tYWAAAA\tUEMAAA\tAAAAxx\n733\t8237\t1\t1\t3\t13\t33\t733\t733\t733\t733\t66\t67\tFCAAAA\tVEMAAA\tHHHHxx\n7726\t8238\t0\t2\t6\t6\t26\t726\t1726\t2726\t7726\t52\t53\tELAAAA\tWEMAAA\tOOOOxx\n5418\t8239\t0\t2\t8\t18\t18\t418\t1418\t418\t5418\t36\t37\tKAAAAA\tXEMAAA\tVVVVxx\n7761\t8240\t1\t1\t1\t1\t61\t761\t1761\t2761\t7761\t122\t123\tNMAAAA\tYEMAAA\tAAAAxx\n9263\t8241\t1\t3\t3\t3\t63\t263\t1263\t4263\t9263\t126\t127\tHSAAAA\tZEMAAA\tHHHHxx\n5579\t8242\t1\t3\t9\t19\t79\t579\t1579\t579\t5579\t158\t159\tPGAAAA\tAFMAAA\tOOOOxx\n5434\t8243\t0\t2\t4\t14\t34\t434\t1434\t434\t5434\t68\t69\tABAAAA\tBFMAAA\tVVVVxx\n5230\t8244\t0\t2\t0\t10\t30\t230\t1230\t230\t5230\t60\t61\tETAAAA\tCFMAAA\tAAAAxx\n9981\t8245\t1\t1\t1\t1\t81\t981\t1981\t4981\t9981\t162\t163\tXTAAAA\tDFMAAA\tHHHHxx\n5830\t8246\t0\t2\t0\t10\t30\t830\t1830\t830\t5830\t60\t61\tGQAAAA\tEFMAAA\tOOOOxx\n128\t8247\t0\t0\t8\t8\t28\t128\t128\t128\t128\t56\t57\tYEAAAA\tFFMAAA\tVVVVxx\n2734\t8248\t0\t2\t4\t14\t34\t734\t734\t2734\t2734\t68\t69\tEBAAAA\tGFMAAA\tAAAAxx\n4537\t8249\t1\t1\t7\t17\t37\t537\t537\t4537\t4537\t74\t75\tNSAAAA\tHFMAAA\tHHHHxx\n3899\t8250\t1\t3\t9\t19\t99\t899\t1899\t3899\t3899\t198\t199\tZTAAAA\tIFMAAA\tOOOOxx\n1000\t8251\t0\t0\t0\t0\t0\t0\t1000\t1000\t1000\t0\t1\tMMAAAA\tJFMAAA\tVVVVxx\n9896\t8252\t0\t0\t6\t16\t96\t896\t1896\t4896\t9896\t192\t193\tQQAAAA\tKFMAAA\tAAAAxx\n3640\t8253\t0\t0\t0\t0\t40\t640\t1640\t3640\t3640\t80\t81\tAKAAAA\tLFMAAA\tHHHHxx\n2568\t8254\t0\t0\t8\t8\t68\t568\t568\t2568\t2568\t136\t137\tUUAAAA\tMFMAAA\tOOOOxx\n2026\t8255\t0\t2\t6\t6\t26\t26\t26\t2026\t2026\t52\t53\tYZAAAA\tNFMAAA\tVVVVxx\n3955\t8256\t1\t3\t5\t15\t55\t955\t1955\t3955\t3955\t110\t111\tDWAAAA\tOFMAAA\tAAAAxx\n7152\t8257\t0\t0\t2\t12\t52\t152\t1152\t2152\t7152\t104\t105\tCPAAAA\tPFMAAA\tHHHHxx\n2402\t8258\t0\t2\t2\t2\t2\t402\t402\t2402\t2402\t4\t5\tKOAAAA\tQFMAAA\tOOOOxx\n9522\t8259\t0\t2\t2\t2\t22\t522\t1522\t4522\t9522\t44\t45\tGCAAAA\tRFMAAA\tVVVVxx\n4011\t8260\t1\t3\t1\t11\t11\t11\t11\t4011\t4011\t22\t23\tHYAAAA\tSFMAAA\tAAAAxx\n3297\t8261\t1\t1\t7\t17\t97\t297\t1297\t3297\t3297\t194\t195\tVWAAAA\tTFMAAA\tHHHHxx\n4915\t8262\t1\t3\t5\t15\t15\t915\t915\t4915\t4915\t30\t31\tBHAAAA\tUFMAAA\tOOOOxx\n5397\t8263\t1\t1\t7\t17\t97\t397\t1397\t397\t5397\t194\t195\tPZAAAA\tVFMAAA\tVVVVxx\n5454\t8264\t0\t2\t4\t14\t54\t454\t1454\t454\t5454\t108\t109\tUBAAAA\tWFMAAA\tAAAAxx\n4568\t8265\t0\t0\t8\t8\t68\t568\t568\t4568\t4568\t136\t137\tSTAAAA\tXFMAAA\tHHHHxx\n5875\t8266\t1\t3\t5\t15\t75\t875\t1875\t875\t5875\t150\t151\tZRAAAA\tYFMAAA\tOOOOxx\n3642\t8267\t0\t2\t2\t2\t42\t642\t1642\t3642\t3642\t84\t85\tCKAAAA\tZFMAAA\tVVVVxx\n8506\t8268\t0\t2\t6\t6\t6\t506\t506\t3506\t8506\t12\t13\tEPAAAA\tAGMAAA\tAAAAxx\n9621\t8269\t1\t1\t1\t1\t21\t621\t1621\t4621\t9621\t42\t43\tBGAAAA\tBGMAAA\tHHHHxx\n7739\t8270\t1\t3\t9\t19\t39\t739\t1739\t2739\t7739\t78\t79\tRLAAAA\tCGMAAA\tOOOOxx\n3987\t8271\t1\t3\t7\t7\t87\t987\t1987\t3987\t3987\t174\t175\tJXAAAA\tDGMAAA\tVVVVxx\n2090\t8272\t0\t2\t0\t10\t90\t90\t90\t2090\t2090\t180\t181\tKCAAAA\tEGMAAA\tAAAAxx\n3838\t8273\t0\t2\t8\t18\t38\t838\t1838\t3838\t3838\t76\t77\tQRAAAA\tFGMAAA\tHHHHxx\n17\t8274\t1\t1\t7\t17\t17\t17\t17\t17\t17\t34\t35\tRAAAAA\tGGMAAA\tOOOOxx\n3406\t8275\t0\t2\t6\t6\t6\t406\t1406\t3406\t3406\t12\t13\tABAAAA\tHGMAAA\tVVVVxx\n8312\t8276\t0\t0\t2\t12\t12\t312\t312\t3312\t8312\t24\t25\tSHAAAA\tIGMAAA\tAAAAxx\n4034\t8277\t0\t2\t4\t14\t34\t34\t34\t4034\t4034\t68\t69\tEZAAAA\tJGMAAA\tHHHHxx\n1535\t8278\t1\t3\t5\t15\t35\t535\t1535\t1535\t1535\t70\t71\tBHAAAA\tKGMAAA\tOOOOxx\n7198\t8279\t0\t2\t8\t18\t98\t198\t1198\t2198\t7198\t196\t197\tWQAAAA\tLGMAAA\tVVVVxx\n8885\t8280\t1\t1\t5\t5\t85\t885\t885\t3885\t8885\t170\t171\tTDAAAA\tMGMAAA\tAAAAxx\n4081\t8281\t1\t1\t1\t1\t81\t81\t81\t4081\t4081\t162\t163\tZAAAAA\tNGMAAA\tHHHHxx\n980\t8282\t0\t0\t0\t0\t80\t980\t980\t980\t980\t160\t161\tSLAAAA\tOGMAAA\tOOOOxx\n551\t8283\t1\t3\t1\t11\t51\t551\t551\t551\t551\t102\t103\tFVAAAA\tPGMAAA\tVVVVxx\n7746\t8284\t0\t2\t6\t6\t46\t746\t1746\t2746\t7746\t92\t93\tYLAAAA\tQGMAAA\tAAAAxx\n4756\t8285\t0\t0\t6\t16\t56\t756\t756\t4756\t4756\t112\t113\tYAAAAA\tRGMAAA\tHHHHxx\n3655\t8286\t1\t3\t5\t15\t55\t655\t1655\t3655\t3655\t110\t111\tPKAAAA\tSGMAAA\tOOOOxx\n7075\t8287\t1\t3\t5\t15\t75\t75\t1075\t2075\t7075\t150\t151\tDMAAAA\tTGMAAA\tVVVVxx\n3950\t8288\t0\t2\t0\t10\t50\t950\t1950\t3950\t3950\t100\t101\tYVAAAA\tUGMAAA\tAAAAxx\n2314\t8289\t0\t2\t4\t14\t14\t314\t314\t2314\t2314\t28\t29\tALAAAA\tVGMAAA\tHHHHxx\n8432\t8290\t0\t0\t2\t12\t32\t432\t432\t3432\t8432\t64\t65\tIMAAAA\tWGMAAA\tOOOOxx\n62\t8291\t0\t2\t2\t2\t62\t62\t62\t62\t62\t124\t125\tKCAAAA\tXGMAAA\tVVVVxx\n6920\t8292\t0\t0\t0\t0\t20\t920\t920\t1920\t6920\t40\t41\tEGAAAA\tYGMAAA\tAAAAxx\n4077\t8293\t1\t1\t7\t17\t77\t77\t77\t4077\t4077\t154\t155\tVAAAAA\tZGMAAA\tHHHHxx\n9118\t8294\t0\t2\t8\t18\t18\t118\t1118\t4118\t9118\t36\t37\tSMAAAA\tAHMAAA\tOOOOxx\n5375\t8295\t1\t3\t5\t15\t75\t375\t1375\t375\t5375\t150\t151\tTYAAAA\tBHMAAA\tVVVVxx\n178\t8296\t0\t2\t8\t18\t78\t178\t178\t178\t178\t156\t157\tWGAAAA\tCHMAAA\tAAAAxx\n1079\t8297\t1\t3\t9\t19\t79\t79\t1079\t1079\t1079\t158\t159\tNPAAAA\tDHMAAA\tHHHHxx\n4279\t8298\t1\t3\t9\t19\t79\t279\t279\t4279\t4279\t158\t159\tPIAAAA\tEHMAAA\tOOOOxx\n8436\t8299\t0\t0\t6\t16\t36\t436\t436\t3436\t8436\t72\t73\tMMAAAA\tFHMAAA\tVVVVxx\n1931\t8300\t1\t3\t1\t11\t31\t931\t1931\t1931\t1931\t62\t63\tHWAAAA\tGHMAAA\tAAAAxx\n2096\t8301\t0\t0\t6\t16\t96\t96\t96\t2096\t2096\t192\t193\tQCAAAA\tHHMAAA\tHHHHxx\n1638\t8302\t0\t2\t8\t18\t38\t638\t1638\t1638\t1638\t76\t77\tALAAAA\tIHMAAA\tOOOOxx\n2788\t8303\t0\t0\t8\t8\t88\t788\t788\t2788\t2788\t176\t177\tGDAAAA\tJHMAAA\tVVVVxx\n4751\t8304\t1\t3\t1\t11\t51\t751\t751\t4751\t4751\t102\t103\tTAAAAA\tKHMAAA\tAAAAxx\n8824\t8305\t0\t0\t4\t4\t24\t824\t824\t3824\t8824\t48\t49\tKBAAAA\tLHMAAA\tHHHHxx\n3098\t8306\t0\t2\t8\t18\t98\t98\t1098\t3098\t3098\t196\t197\tEPAAAA\tMHMAAA\tOOOOxx\n4497\t8307\t1\t1\t7\t17\t97\t497\t497\t4497\t4497\t194\t195\tZQAAAA\tNHMAAA\tVVVVxx\n5223\t8308\t1\t3\t3\t3\t23\t223\t1223\t223\t5223\t46\t47\tXSAAAA\tOHMAAA\tAAAAxx\n9212\t8309\t0\t0\t2\t12\t12\t212\t1212\t4212\t9212\t24\t25\tIQAAAA\tPHMAAA\tHHHHxx\n4265\t8310\t1\t1\t5\t5\t65\t265\t265\t4265\t4265\t130\t131\tBIAAAA\tQHMAAA\tOOOOxx\n6898\t8311\t0\t2\t8\t18\t98\t898\t898\t1898\t6898\t196\t197\tIFAAAA\tRHMAAA\tVVVVxx\n8808\t8312\t0\t0\t8\t8\t8\t808\t808\t3808\t8808\t16\t17\tUAAAAA\tSHMAAA\tAAAAxx\n5629\t8313\t1\t1\t9\t9\t29\t629\t1629\t629\t5629\t58\t59\tNIAAAA\tTHMAAA\tHHHHxx\n3779\t8314\t1\t3\t9\t19\t79\t779\t1779\t3779\t3779\t158\t159\tJPAAAA\tUHMAAA\tOOOOxx\n4972\t8315\t0\t0\t2\t12\t72\t972\t972\t4972\t4972\t144\t145\tGJAAAA\tVHMAAA\tVVVVxx\n4511\t8316\t1\t3\t1\t11\t11\t511\t511\t4511\t4511\t22\t23\tNRAAAA\tWHMAAA\tAAAAxx\n6761\t8317\t1\t1\t1\t1\t61\t761\t761\t1761\t6761\t122\t123\tBAAAAA\tXHMAAA\tHHHHxx\n2335\t8318\t1\t3\t5\t15\t35\t335\t335\t2335\t2335\t70\t71\tVLAAAA\tYHMAAA\tOOOOxx\n732\t8319\t0\t0\t2\t12\t32\t732\t732\t732\t732\t64\t65\tECAAAA\tZHMAAA\tVVVVxx\n4757\t8320\t1\t1\t7\t17\t57\t757\t757\t4757\t4757\t114\t115\tZAAAAA\tAIMAAA\tAAAAxx\n6624\t8321\t0\t0\t4\t4\t24\t624\t624\t1624\t6624\t48\t49\tUUAAAA\tBIMAAA\tHHHHxx\n5869\t8322\t1\t1\t9\t9\t69\t869\t1869\t869\t5869\t138\t139\tTRAAAA\tCIMAAA\tOOOOxx\n5842\t8323\t0\t2\t2\t2\t42\t842\t1842\t842\t5842\t84\t85\tSQAAAA\tDIMAAA\tVVVVxx\n5735\t8324\t1\t3\t5\t15\t35\t735\t1735\t735\t5735\t70\t71\tPMAAAA\tEIMAAA\tAAAAxx\n8276\t8325\t0\t0\t6\t16\t76\t276\t276\t3276\t8276\t152\t153\tIGAAAA\tFIMAAA\tHHHHxx\n7227\t8326\t1\t3\t7\t7\t27\t227\t1227\t2227\t7227\t54\t55\tZRAAAA\tGIMAAA\tOOOOxx\n4923\t8327\t1\t3\t3\t3\t23\t923\t923\t4923\t4923\t46\t47\tJHAAAA\tHIMAAA\tVVVVxx\n9135\t8328\t1\t3\t5\t15\t35\t135\t1135\t4135\t9135\t70\t71\tJNAAAA\tIIMAAA\tAAAAxx\n5813\t8329\t1\t1\t3\t13\t13\t813\t1813\t813\t5813\t26\t27\tPPAAAA\tJIMAAA\tHHHHxx\n9697\t8330\t1\t1\t7\t17\t97\t697\t1697\t4697\t9697\t194\t195\tZIAAAA\tKIMAAA\tOOOOxx\n3222\t8331\t0\t2\t2\t2\t22\t222\t1222\t3222\t3222\t44\t45\tYTAAAA\tLIMAAA\tVVVVxx\n2394\t8332\t0\t2\t4\t14\t94\t394\t394\t2394\t2394\t188\t189\tCOAAAA\tMIMAAA\tAAAAxx\n5784\t8333\t0\t0\t4\t4\t84\t784\t1784\t784\t5784\t168\t169\tMOAAAA\tNIMAAA\tHHHHxx\n3652\t8334\t0\t0\t2\t12\t52\t652\t1652\t3652\t3652\t104\t105\tMKAAAA\tOIMAAA\tOOOOxx\n8175\t8335\t1\t3\t5\t15\t75\t175\t175\t3175\t8175\t150\t151\tLCAAAA\tPIMAAA\tVVVVxx\n7568\t8336\t0\t0\t8\t8\t68\t568\t1568\t2568\t7568\t136\t137\tCFAAAA\tQIMAAA\tAAAAxx\n6645\t8337\t1\t1\t5\t5\t45\t645\t645\t1645\t6645\t90\t91\tPVAAAA\tRIMAAA\tHHHHxx\n8176\t8338\t0\t0\t6\t16\t76\t176\t176\t3176\t8176\t152\t153\tMCAAAA\tSIMAAA\tOOOOxx\n530\t8339\t0\t2\t0\t10\t30\t530\t530\t530\t530\t60\t61\tKUAAAA\tTIMAAA\tVVVVxx\n5439\t8340\t1\t3\t9\t19\t39\t439\t1439\t439\t5439\t78\t79\tFBAAAA\tUIMAAA\tAAAAxx\n61\t8341\t1\t1\t1\t1\t61\t61\t61\t61\t61\t122\t123\tJCAAAA\tVIMAAA\tHHHHxx\n3951\t8342\t1\t3\t1\t11\t51\t951\t1951\t3951\t3951\t102\t103\tZVAAAA\tWIMAAA\tOOOOxx\n5283\t8343\t1\t3\t3\t3\t83\t283\t1283\t283\t5283\t166\t167\tFVAAAA\tXIMAAA\tVVVVxx\n7226\t8344\t0\t2\t6\t6\t26\t226\t1226\t2226\t7226\t52\t53\tYRAAAA\tYIMAAA\tAAAAxx\n1954\t8345\t0\t2\t4\t14\t54\t954\t1954\t1954\t1954\t108\t109\tEXAAAA\tZIMAAA\tHHHHxx\n334\t8346\t0\t2\t4\t14\t34\t334\t334\t334\t334\t68\t69\tWMAAAA\tAJMAAA\tOOOOxx\n3921\t8347\t1\t1\t1\t1\t21\t921\t1921\t3921\t3921\t42\t43\tVUAAAA\tBJMAAA\tVVVVxx\n6276\t8348\t0\t0\t6\t16\t76\t276\t276\t1276\t6276\t152\t153\tKHAAAA\tCJMAAA\tAAAAxx\n3378\t8349\t0\t2\t8\t18\t78\t378\t1378\t3378\t3378\t156\t157\tYZAAAA\tDJMAAA\tHHHHxx\n5236\t8350\t0\t0\t6\t16\t36\t236\t1236\t236\t5236\t72\t73\tKTAAAA\tEJMAAA\tOOOOxx\n7781\t8351\t1\t1\t1\t1\t81\t781\t1781\t2781\t7781\t162\t163\tHNAAAA\tFJMAAA\tVVVVxx\n8601\t8352\t1\t1\t1\t1\t1\t601\t601\t3601\t8601\t2\t3\tVSAAAA\tGJMAAA\tAAAAxx\n1473\t8353\t1\t1\t3\t13\t73\t473\t1473\t1473\t1473\t146\t147\tREAAAA\tHJMAAA\tHHHHxx\n3246\t8354\t0\t2\t6\t6\t46\t246\t1246\t3246\t3246\t92\t93\tWUAAAA\tIJMAAA\tOOOOxx\n3601\t8355\t1\t1\t1\t1\t1\t601\t1601\t3601\t3601\t2\t3\tNIAAAA\tJJMAAA\tVVVVxx\n6861\t8356\t1\t1\t1\t1\t61\t861\t861\t1861\t6861\t122\t123\tXDAAAA\tKJMAAA\tAAAAxx\n9032\t8357\t0\t0\t2\t12\t32\t32\t1032\t4032\t9032\t64\t65\tKJAAAA\tLJMAAA\tHHHHxx\n216\t8358\t0\t0\t6\t16\t16\t216\t216\t216\t216\t32\t33\tIIAAAA\tMJMAAA\tOOOOxx\n3824\t8359\t0\t0\t4\t4\t24\t824\t1824\t3824\t3824\t48\t49\tCRAAAA\tNJMAAA\tVVVVxx\n8486\t8360\t0\t2\t6\t6\t86\t486\t486\t3486\t8486\t172\t173\tKOAAAA\tOJMAAA\tAAAAxx\n276\t8361\t0\t0\t6\t16\t76\t276\t276\t276\t276\t152\t153\tQKAAAA\tPJMAAA\tHHHHxx\n1838\t8362\t0\t2\t8\t18\t38\t838\t1838\t1838\t1838\t76\t77\tSSAAAA\tQJMAAA\tOOOOxx\n6175\t8363\t1\t3\t5\t15\t75\t175\t175\t1175\t6175\t150\t151\tNDAAAA\tRJMAAA\tVVVVxx\n3719\t8364\t1\t3\t9\t19\t19\t719\t1719\t3719\t3719\t38\t39\tBNAAAA\tSJMAAA\tAAAAxx\n6958\t8365\t0\t2\t8\t18\t58\t958\t958\t1958\t6958\t116\t117\tQHAAAA\tTJMAAA\tHHHHxx\n6822\t8366\t0\t2\t2\t2\t22\t822\t822\t1822\t6822\t44\t45\tKCAAAA\tUJMAAA\tOOOOxx\n3318\t8367\t0\t2\t8\t18\t18\t318\t1318\t3318\t3318\t36\t37\tQXAAAA\tVJMAAA\tVVVVxx\n7222\t8368\t0\t2\t2\t2\t22\t222\t1222\t2222\t7222\t44\t45\tURAAAA\tWJMAAA\tAAAAxx\n85\t8369\t1\t1\t5\t5\t85\t85\t85\t85\t85\t170\t171\tHDAAAA\tXJMAAA\tHHHHxx\n5158\t8370\t0\t2\t8\t18\t58\t158\t1158\t158\t5158\t116\t117\tKQAAAA\tYJMAAA\tOOOOxx\n6360\t8371\t0\t0\t0\t0\t60\t360\t360\t1360\t6360\t120\t121\tQKAAAA\tZJMAAA\tVVVVxx\n2599\t8372\t1\t3\t9\t19\t99\t599\t599\t2599\t2599\t198\t199\tZVAAAA\tAKMAAA\tAAAAxx\n4002\t8373\t0\t2\t2\t2\t2\t2\t2\t4002\t4002\t4\t5\tYXAAAA\tBKMAAA\tHHHHxx\n6597\t8374\t1\t1\t7\t17\t97\t597\t597\t1597\t6597\t194\t195\tTTAAAA\tCKMAAA\tOOOOxx\n5762\t8375\t0\t2\t2\t2\t62\t762\t1762\t762\t5762\t124\t125\tQNAAAA\tDKMAAA\tVVVVxx\n8383\t8376\t1\t3\t3\t3\t83\t383\t383\t3383\t8383\t166\t167\tLKAAAA\tEKMAAA\tAAAAxx\n4686\t8377\t0\t2\t6\t6\t86\t686\t686\t4686\t4686\t172\t173\tGYAAAA\tFKMAAA\tHHHHxx\n5972\t8378\t0\t0\t2\t12\t72\t972\t1972\t972\t5972\t144\t145\tSVAAAA\tGKMAAA\tOOOOxx\n1432\t8379\t0\t0\t2\t12\t32\t432\t1432\t1432\t1432\t64\t65\tCDAAAA\tHKMAAA\tVVVVxx\n1601\t8380\t1\t1\t1\t1\t1\t601\t1601\t1601\t1601\t2\t3\tPJAAAA\tIKMAAA\tAAAAxx\n3012\t8381\t0\t0\t2\t12\t12\t12\t1012\t3012\t3012\t24\t25\tWLAAAA\tJKMAAA\tHHHHxx\n9345\t8382\t1\t1\t5\t5\t45\t345\t1345\t4345\t9345\t90\t91\tLVAAAA\tKKMAAA\tOOOOxx\n8869\t8383\t1\t1\t9\t9\t69\t869\t869\t3869\t8869\t138\t139\tDDAAAA\tLKMAAA\tVVVVxx\n6612\t8384\t0\t0\t2\t12\t12\t612\t612\t1612\t6612\t24\t25\tIUAAAA\tMKMAAA\tAAAAxx\n262\t8385\t0\t2\t2\t2\t62\t262\t262\t262\t262\t124\t125\tCKAAAA\tNKMAAA\tHHHHxx\n300\t8386\t0\t0\t0\t0\t0\t300\t300\t300\t300\t0\t1\tOLAAAA\tOKMAAA\tOOOOxx\n3045\t8387\t1\t1\t5\t5\t45\t45\t1045\t3045\t3045\t90\t91\tDNAAAA\tPKMAAA\tVVVVxx\n7252\t8388\t0\t0\t2\t12\t52\t252\t1252\t2252\t7252\t104\t105\tYSAAAA\tQKMAAA\tAAAAxx\n9099\t8389\t1\t3\t9\t19\t99\t99\t1099\t4099\t9099\t198\t199\tZLAAAA\tRKMAAA\tHHHHxx\n9006\t8390\t0\t2\t6\t6\t6\t6\t1006\t4006\t9006\t12\t13\tKIAAAA\tSKMAAA\tOOOOxx\n3078\t8391\t0\t2\t8\t18\t78\t78\t1078\t3078\t3078\t156\t157\tKOAAAA\tTKMAAA\tVVVVxx\n5159\t8392\t1\t3\t9\t19\t59\t159\t1159\t159\t5159\t118\t119\tLQAAAA\tUKMAAA\tAAAAxx\n9329\t8393\t1\t1\t9\t9\t29\t329\t1329\t4329\t9329\t58\t59\tVUAAAA\tVKMAAA\tHHHHxx\n1393\t8394\t1\t1\t3\t13\t93\t393\t1393\t1393\t1393\t186\t187\tPBAAAA\tWKMAAA\tOOOOxx\n5894\t8395\t0\t2\t4\t14\t94\t894\t1894\t894\t5894\t188\t189\tSSAAAA\tXKMAAA\tVVVVxx\n11\t8396\t1\t3\t1\t11\t11\t11\t11\t11\t11\t22\t23\tLAAAAA\tYKMAAA\tAAAAxx\n5606\t8397\t0\t2\t6\t6\t6\t606\t1606\t606\t5606\t12\t13\tQHAAAA\tZKMAAA\tHHHHxx\n5541\t8398\t1\t1\t1\t1\t41\t541\t1541\t541\t5541\t82\t83\tDFAAAA\tALMAAA\tOOOOxx\n2689\t8399\t1\t1\t9\t9\t89\t689\t689\t2689\t2689\t178\t179\tLZAAAA\tBLMAAA\tVVVVxx\n1023\t8400\t1\t3\t3\t3\t23\t23\t1023\t1023\t1023\t46\t47\tJNAAAA\tCLMAAA\tAAAAxx\n8134\t8401\t0\t2\t4\t14\t34\t134\t134\t3134\t8134\t68\t69\tWAAAAA\tDLMAAA\tHHHHxx\n5923\t8402\t1\t3\t3\t3\t23\t923\t1923\t923\t5923\t46\t47\tVTAAAA\tELMAAA\tOOOOxx\n6056\t8403\t0\t0\t6\t16\t56\t56\t56\t1056\t6056\t112\t113\tYYAAAA\tFLMAAA\tVVVVxx\n653\t8404\t1\t1\t3\t13\t53\t653\t653\t653\t653\t106\t107\tDZAAAA\tGLMAAA\tAAAAxx\n367\t8405\t1\t3\t7\t7\t67\t367\t367\t367\t367\t134\t135\tDOAAAA\tHLMAAA\tHHHHxx\n1828\t8406\t0\t0\t8\t8\t28\t828\t1828\t1828\t1828\t56\t57\tISAAAA\tILMAAA\tOOOOxx\n6506\t8407\t0\t2\t6\t6\t6\t506\t506\t1506\t6506\t12\t13\tGQAAAA\tJLMAAA\tVVVVxx\n5772\t8408\t0\t0\t2\t12\t72\t772\t1772\t772\t5772\t144\t145\tAOAAAA\tKLMAAA\tAAAAxx\n8052\t8409\t0\t0\t2\t12\t52\t52\t52\t3052\t8052\t104\t105\tSXAAAA\tLLMAAA\tHHHHxx\n2633\t8410\t1\t1\t3\t13\t33\t633\t633\t2633\t2633\t66\t67\tHXAAAA\tMLMAAA\tOOOOxx\n4878\t8411\t0\t2\t8\t18\t78\t878\t878\t4878\t4878\t156\t157\tQFAAAA\tNLMAAA\tVVVVxx\n5621\t8412\t1\t1\t1\t1\t21\t621\t1621\t621\t5621\t42\t43\tFIAAAA\tOLMAAA\tAAAAxx\n41\t8413\t1\t1\t1\t1\t41\t41\t41\t41\t41\t82\t83\tPBAAAA\tPLMAAA\tHHHHxx\n4613\t8414\t1\t1\t3\t13\t13\t613\t613\t4613\t4613\t26\t27\tLVAAAA\tQLMAAA\tOOOOxx\n9389\t8415\t1\t1\t9\t9\t89\t389\t1389\t4389\t9389\t178\t179\tDXAAAA\tRLMAAA\tVVVVxx\n9414\t8416\t0\t2\t4\t14\t14\t414\t1414\t4414\t9414\t28\t29\tCYAAAA\tSLMAAA\tAAAAxx\n3583\t8417\t1\t3\t3\t3\t83\t583\t1583\t3583\t3583\t166\t167\tVHAAAA\tTLMAAA\tHHHHxx\n3454\t8418\t0\t2\t4\t14\t54\t454\t1454\t3454\t3454\t108\t109\tWCAAAA\tULMAAA\tOOOOxx\n719\t8419\t1\t3\t9\t19\t19\t719\t719\t719\t719\t38\t39\tRBAAAA\tVLMAAA\tVVVVxx\n6188\t8420\t0\t0\t8\t8\t88\t188\t188\t1188\t6188\t176\t177\tAEAAAA\tWLMAAA\tAAAAxx\n2288\t8421\t0\t0\t8\t8\t88\t288\t288\t2288\t2288\t176\t177\tAKAAAA\tXLMAAA\tHHHHxx\n1287\t8422\t1\t3\t7\t7\t87\t287\t1287\t1287\t1287\t174\t175\tNXAAAA\tYLMAAA\tOOOOxx\n1397\t8423\t1\t1\t7\t17\t97\t397\t1397\t1397\t1397\t194\t195\tTBAAAA\tZLMAAA\tVVVVxx\n7763\t8424\t1\t3\t3\t3\t63\t763\t1763\t2763\t7763\t126\t127\tPMAAAA\tAMMAAA\tAAAAxx\n5194\t8425\t0\t2\t4\t14\t94\t194\t1194\t194\t5194\t188\t189\tURAAAA\tBMMAAA\tHHHHxx\n3167\t8426\t1\t3\t7\t7\t67\t167\t1167\t3167\t3167\t134\t135\tVRAAAA\tCMMAAA\tOOOOxx\n9218\t8427\t0\t2\t8\t18\t18\t218\t1218\t4218\t9218\t36\t37\tOQAAAA\tDMMAAA\tVVVVxx\n2065\t8428\t1\t1\t5\t5\t65\t65\t65\t2065\t2065\t130\t131\tLBAAAA\tEMMAAA\tAAAAxx\n9669\t8429\t1\t1\t9\t9\t69\t669\t1669\t4669\t9669\t138\t139\tXHAAAA\tFMMAAA\tHHHHxx\n146\t8430\t0\t2\t6\t6\t46\t146\t146\t146\t146\t92\t93\tQFAAAA\tGMMAAA\tOOOOxx\n6141\t8431\t1\t1\t1\t1\t41\t141\t141\t1141\t6141\t82\t83\tFCAAAA\tHMMAAA\tVVVVxx\n2843\t8432\t1\t3\t3\t3\t43\t843\t843\t2843\t2843\t86\t87\tJFAAAA\tIMMAAA\tAAAAxx\n7934\t8433\t0\t2\t4\t14\t34\t934\t1934\t2934\t7934\t68\t69\tETAAAA\tJMMAAA\tHHHHxx\n2536\t8434\t0\t0\t6\t16\t36\t536\t536\t2536\t2536\t72\t73\tOTAAAA\tKMMAAA\tOOOOxx\n7088\t8435\t0\t0\t8\t8\t88\t88\t1088\t2088\t7088\t176\t177\tQMAAAA\tLMMAAA\tVVVVxx\n2519\t8436\t1\t3\t9\t19\t19\t519\t519\t2519\t2519\t38\t39\tXSAAAA\tMMMAAA\tAAAAxx\n6650\t8437\t0\t2\t0\t10\t50\t650\t650\t1650\t6650\t100\t101\tUVAAAA\tNMMAAA\tHHHHxx\n3007\t8438\t1\t3\t7\t7\t7\t7\t1007\t3007\t3007\t14\t15\tRLAAAA\tOMMAAA\tOOOOxx\n4507\t8439\t1\t3\t7\t7\t7\t507\t507\t4507\t4507\t14\t15\tJRAAAA\tPMMAAA\tVVVVxx\n4892\t8440\t0\t0\t2\t12\t92\t892\t892\t4892\t4892\t184\t185\tEGAAAA\tQMMAAA\tAAAAxx\n7159\t8441\t1\t3\t9\t19\t59\t159\t1159\t2159\t7159\t118\t119\tJPAAAA\tRMMAAA\tHHHHxx\n3171\t8442\t1\t3\t1\t11\t71\t171\t1171\t3171\t3171\t142\t143\tZRAAAA\tSMMAAA\tOOOOxx\n1080\t8443\t0\t0\t0\t0\t80\t80\t1080\t1080\t1080\t160\t161\tOPAAAA\tTMMAAA\tVVVVxx\n7248\t8444\t0\t0\t8\t8\t48\t248\t1248\t2248\t7248\t96\t97\tUSAAAA\tUMMAAA\tAAAAxx\n7230\t8445\t0\t2\t0\t10\t30\t230\t1230\t2230\t7230\t60\t61\tCSAAAA\tVMMAAA\tHHHHxx\n3823\t8446\t1\t3\t3\t3\t23\t823\t1823\t3823\t3823\t46\t47\tBRAAAA\tWMMAAA\tOOOOxx\n5517\t8447\t1\t1\t7\t17\t17\t517\t1517\t517\t5517\t34\t35\tFEAAAA\tXMMAAA\tVVVVxx\n1482\t8448\t0\t2\t2\t2\t82\t482\t1482\t1482\t1482\t164\t165\tAFAAAA\tYMMAAA\tAAAAxx\n9953\t8449\t1\t1\t3\t13\t53\t953\t1953\t4953\t9953\t106\t107\tVSAAAA\tZMMAAA\tHHHHxx\n2754\t8450\t0\t2\t4\t14\t54\t754\t754\t2754\t2754\t108\t109\tYBAAAA\tANMAAA\tOOOOxx\n3875\t8451\t1\t3\t5\t15\t75\t875\t1875\t3875\t3875\t150\t151\tBTAAAA\tBNMAAA\tVVVVxx\n9800\t8452\t0\t0\t0\t0\t0\t800\t1800\t4800\t9800\t0\t1\tYMAAAA\tCNMAAA\tAAAAxx\n8819\t8453\t1\t3\t9\t19\t19\t819\t819\t3819\t8819\t38\t39\tFBAAAA\tDNMAAA\tHHHHxx\n8267\t8454\t1\t3\t7\t7\t67\t267\t267\t3267\t8267\t134\t135\tZFAAAA\tENMAAA\tOOOOxx\n520\t8455\t0\t0\t0\t0\t20\t520\t520\t520\t520\t40\t41\tAUAAAA\tFNMAAA\tVVVVxx\n5770\t8456\t0\t2\t0\t10\t70\t770\t1770\t770\t5770\t140\t141\tYNAAAA\tGNMAAA\tAAAAxx\n2114\t8457\t0\t2\t4\t14\t14\t114\t114\t2114\t2114\t28\t29\tIDAAAA\tHNMAAA\tHHHHxx\n5045\t8458\t1\t1\t5\t5\t45\t45\t1045\t45\t5045\t90\t91\tBMAAAA\tINMAAA\tOOOOxx\n1094\t8459\t0\t2\t4\t14\t94\t94\t1094\t1094\t1094\t188\t189\tCQAAAA\tJNMAAA\tVVVVxx\n8786\t8460\t0\t2\t6\t6\t86\t786\t786\t3786\t8786\t172\t173\tYZAAAA\tKNMAAA\tAAAAxx\n353\t8461\t1\t1\t3\t13\t53\t353\t353\t353\t353\t106\t107\tPNAAAA\tLNMAAA\tHHHHxx\n290\t8462\t0\t2\t0\t10\t90\t290\t290\t290\t290\t180\t181\tELAAAA\tMNMAAA\tOOOOxx\n3376\t8463\t0\t0\t6\t16\t76\t376\t1376\t3376\t3376\t152\t153\tWZAAAA\tNNMAAA\tVVVVxx\n9305\t8464\t1\t1\t5\t5\t5\t305\t1305\t4305\t9305\t10\t11\tXTAAAA\tONMAAA\tAAAAxx\n186\t8465\t0\t2\t6\t6\t86\t186\t186\t186\t186\t172\t173\tEHAAAA\tPNMAAA\tHHHHxx\n4817\t8466\t1\t1\t7\t17\t17\t817\t817\t4817\t4817\t34\t35\tHDAAAA\tQNMAAA\tOOOOxx\n4638\t8467\t0\t2\t8\t18\t38\t638\t638\t4638\t4638\t76\t77\tKWAAAA\tRNMAAA\tVVVVxx\n3558\t8468\t0\t2\t8\t18\t58\t558\t1558\t3558\t3558\t116\t117\tWGAAAA\tSNMAAA\tAAAAxx\n9285\t8469\t1\t1\t5\t5\t85\t285\t1285\t4285\t9285\t170\t171\tDTAAAA\tTNMAAA\tHHHHxx\n848\t8470\t0\t0\t8\t8\t48\t848\t848\t848\t848\t96\t97\tQGAAAA\tUNMAAA\tOOOOxx\n8923\t8471\t1\t3\t3\t3\t23\t923\t923\t3923\t8923\t46\t47\tFFAAAA\tVNMAAA\tVVVVxx\n6826\t8472\t0\t2\t6\t6\t26\t826\t826\t1826\t6826\t52\t53\tOCAAAA\tWNMAAA\tAAAAxx\n5187\t8473\t1\t3\t7\t7\t87\t187\t1187\t187\t5187\t174\t175\tNRAAAA\tXNMAAA\tHHHHxx\n2398\t8474\t0\t2\t8\t18\t98\t398\t398\t2398\t2398\t196\t197\tGOAAAA\tYNMAAA\tOOOOxx\n7653\t8475\t1\t1\t3\t13\t53\t653\t1653\t2653\t7653\t106\t107\tJIAAAA\tZNMAAA\tVVVVxx\n8835\t8476\t1\t3\t5\t15\t35\t835\t835\t3835\t8835\t70\t71\tVBAAAA\tAOMAAA\tAAAAxx\n5736\t8477\t0\t0\t6\t16\t36\t736\t1736\t736\t5736\t72\t73\tQMAAAA\tBOMAAA\tHHHHxx\n1238\t8478\t0\t2\t8\t18\t38\t238\t1238\t1238\t1238\t76\t77\tQVAAAA\tCOMAAA\tOOOOxx\n6021\t8479\t1\t1\t1\t1\t21\t21\t21\t1021\t6021\t42\t43\tPXAAAA\tDOMAAA\tVVVVxx\n6815\t8480\t1\t3\t5\t15\t15\t815\t815\t1815\t6815\t30\t31\tDCAAAA\tEOMAAA\tAAAAxx\n2549\t8481\t1\t1\t9\t9\t49\t549\t549\t2549\t2549\t98\t99\tBUAAAA\tFOMAAA\tHHHHxx\n5657\t8482\t1\t1\t7\t17\t57\t657\t1657\t657\t5657\t114\t115\tPJAAAA\tGOMAAA\tOOOOxx\n6855\t8483\t1\t3\t5\t15\t55\t855\t855\t1855\t6855\t110\t111\tRDAAAA\tHOMAAA\tVVVVxx\n1225\t8484\t1\t1\t5\t5\t25\t225\t1225\t1225\t1225\t50\t51\tDVAAAA\tIOMAAA\tAAAAxx\n7452\t8485\t0\t0\t2\t12\t52\t452\t1452\t2452\t7452\t104\t105\tQAAAAA\tJOMAAA\tHHHHxx\n2479\t8486\t1\t3\t9\t19\t79\t479\t479\t2479\t2479\t158\t159\tJRAAAA\tKOMAAA\tOOOOxx\n7974\t8487\t0\t2\t4\t14\t74\t974\t1974\t2974\t7974\t148\t149\tSUAAAA\tLOMAAA\tVVVVxx\n1212\t8488\t0\t0\t2\t12\t12\t212\t1212\t1212\t1212\t24\t25\tQUAAAA\tMOMAAA\tAAAAxx\n8883\t8489\t1\t3\t3\t3\t83\t883\t883\t3883\t8883\t166\t167\tRDAAAA\tNOMAAA\tHHHHxx\n8150\t8490\t0\t2\t0\t10\t50\t150\t150\t3150\t8150\t100\t101\tMBAAAA\tOOMAAA\tOOOOxx\n3392\t8491\t0\t0\t2\t12\t92\t392\t1392\t3392\t3392\t184\t185\tMAAAAA\tPOMAAA\tVVVVxx\n6774\t8492\t0\t2\t4\t14\t74\t774\t774\t1774\t6774\t148\t149\tOAAAAA\tQOMAAA\tAAAAxx\n904\t8493\t0\t0\t4\t4\t4\t904\t904\t904\t904\t8\t9\tUIAAAA\tROMAAA\tHHHHxx\n5068\t8494\t0\t0\t8\t8\t68\t68\t1068\t68\t5068\t136\t137\tYMAAAA\tSOMAAA\tOOOOxx\n9339\t8495\t1\t3\t9\t19\t39\t339\t1339\t4339\t9339\t78\t79\tFVAAAA\tTOMAAA\tVVVVxx\n1062\t8496\t0\t2\t2\t2\t62\t62\t1062\t1062\t1062\t124\t125\tWOAAAA\tUOMAAA\tAAAAxx\n3841\t8497\t1\t1\t1\t1\t41\t841\t1841\t3841\t3841\t82\t83\tTRAAAA\tVOMAAA\tHHHHxx\n8924\t8498\t0\t0\t4\t4\t24\t924\t924\t3924\t8924\t48\t49\tGFAAAA\tWOMAAA\tOOOOxx\n9795\t8499\t1\t3\t5\t15\t95\t795\t1795\t4795\t9795\t190\t191\tTMAAAA\tXOMAAA\tVVVVxx\n3981\t8500\t1\t1\t1\t1\t81\t981\t1981\t3981\t3981\t162\t163\tDXAAAA\tYOMAAA\tAAAAxx\n4290\t8501\t0\t2\t0\t10\t90\t290\t290\t4290\t4290\t180\t181\tAJAAAA\tZOMAAA\tHHHHxx\n1067\t8502\t1\t3\t7\t7\t67\t67\t1067\t1067\t1067\t134\t135\tBPAAAA\tAPMAAA\tOOOOxx\n8679\t8503\t1\t3\t9\t19\t79\t679\t679\t3679\t8679\t158\t159\tVVAAAA\tBPMAAA\tVVVVxx\n2894\t8504\t0\t2\t4\t14\t94\t894\t894\t2894\t2894\t188\t189\tIHAAAA\tCPMAAA\tAAAAxx\n9248\t8505\t0\t0\t8\t8\t48\t248\t1248\t4248\t9248\t96\t97\tSRAAAA\tDPMAAA\tHHHHxx\n1072\t8506\t0\t0\t2\t12\t72\t72\t1072\t1072\t1072\t144\t145\tGPAAAA\tEPMAAA\tOOOOxx\n3510\t8507\t0\t2\t0\t10\t10\t510\t1510\t3510\t3510\t20\t21\tAFAAAA\tFPMAAA\tVVVVxx\n6871\t8508\t1\t3\t1\t11\t71\t871\t871\t1871\t6871\t142\t143\tHEAAAA\tGPMAAA\tAAAAxx\n8701\t8509\t1\t1\t1\t1\t1\t701\t701\t3701\t8701\t2\t3\tRWAAAA\tHPMAAA\tHHHHxx\n8170\t8510\t0\t2\t0\t10\t70\t170\t170\t3170\t8170\t140\t141\tGCAAAA\tIPMAAA\tOOOOxx\n2730\t8511\t0\t2\t0\t10\t30\t730\t730\t2730\t2730\t60\t61\tABAAAA\tJPMAAA\tVVVVxx\n2668\t8512\t0\t0\t8\t8\t68\t668\t668\t2668\t2668\t136\t137\tQYAAAA\tKPMAAA\tAAAAxx\n8723\t8513\t1\t3\t3\t3\t23\t723\t723\t3723\t8723\t46\t47\tNXAAAA\tLPMAAA\tHHHHxx\n3439\t8514\t1\t3\t9\t19\t39\t439\t1439\t3439\t3439\t78\t79\tHCAAAA\tMPMAAA\tOOOOxx\n6219\t8515\t1\t3\t9\t19\t19\t219\t219\t1219\t6219\t38\t39\tFFAAAA\tNPMAAA\tVVVVxx\n4264\t8516\t0\t0\t4\t4\t64\t264\t264\t4264\t4264\t128\t129\tAIAAAA\tOPMAAA\tAAAAxx\n3929\t8517\t1\t1\t9\t9\t29\t929\t1929\t3929\t3929\t58\t59\tDVAAAA\tPPMAAA\tHHHHxx\n7\t8518\t1\t3\t7\t7\t7\t7\t7\t7\t7\t14\t15\tHAAAAA\tQPMAAA\tOOOOxx\n3737\t8519\t1\t1\t7\t17\t37\t737\t1737\t3737\t3737\t74\t75\tTNAAAA\tRPMAAA\tVVVVxx\n358\t8520\t0\t2\t8\t18\t58\t358\t358\t358\t358\t116\t117\tUNAAAA\tSPMAAA\tAAAAxx\n5128\t8521\t0\t0\t8\t8\t28\t128\t1128\t128\t5128\t56\t57\tGPAAAA\tTPMAAA\tHHHHxx\n7353\t8522\t1\t1\t3\t13\t53\t353\t1353\t2353\t7353\t106\t107\tVWAAAA\tUPMAAA\tOOOOxx\n8758\t8523\t0\t2\t8\t18\t58\t758\t758\t3758\t8758\t116\t117\tWYAAAA\tVPMAAA\tVVVVxx\n7284\t8524\t0\t0\t4\t4\t84\t284\t1284\t2284\t7284\t168\t169\tEUAAAA\tWPMAAA\tAAAAxx\n4037\t8525\t1\t1\t7\t17\t37\t37\t37\t4037\t4037\t74\t75\tHZAAAA\tXPMAAA\tHHHHxx\n435\t8526\t1\t3\t5\t15\t35\t435\t435\t435\t435\t70\t71\tTQAAAA\tYPMAAA\tOOOOxx\n3580\t8527\t0\t0\t0\t0\t80\t580\t1580\t3580\t3580\t160\t161\tSHAAAA\tZPMAAA\tVVVVxx\n4554\t8528\t0\t2\t4\t14\t54\t554\t554\t4554\t4554\t108\t109\tETAAAA\tAQMAAA\tAAAAxx\n4337\t8529\t1\t1\t7\t17\t37\t337\t337\t4337\t4337\t74\t75\tVKAAAA\tBQMAAA\tHHHHxx\n512\t8530\t0\t0\t2\t12\t12\t512\t512\t512\t512\t24\t25\tSTAAAA\tCQMAAA\tOOOOxx\n2032\t8531\t0\t0\t2\t12\t32\t32\t32\t2032\t2032\t64\t65\tEAAAAA\tDQMAAA\tVVVVxx\n1755\t8532\t1\t3\t5\t15\t55\t755\t1755\t1755\t1755\t110\t111\tNPAAAA\tEQMAAA\tAAAAxx\n9923\t8533\t1\t3\t3\t3\t23\t923\t1923\t4923\t9923\t46\t47\tRRAAAA\tFQMAAA\tHHHHxx\n3747\t8534\t1\t3\t7\t7\t47\t747\t1747\t3747\t3747\t94\t95\tDOAAAA\tGQMAAA\tOOOOxx\n27\t8535\t1\t3\t7\t7\t27\t27\t27\t27\t27\t54\t55\tBBAAAA\tHQMAAA\tVVVVxx\n3075\t8536\t1\t3\t5\t15\t75\t75\t1075\t3075\t3075\t150\t151\tHOAAAA\tIQMAAA\tAAAAxx\n6259\t8537\t1\t3\t9\t19\t59\t259\t259\t1259\t6259\t118\t119\tTGAAAA\tJQMAAA\tHHHHxx\n2940\t8538\t0\t0\t0\t0\t40\t940\t940\t2940\t2940\t80\t81\tCJAAAA\tKQMAAA\tOOOOxx\n5724\t8539\t0\t0\t4\t4\t24\t724\t1724\t724\t5724\t48\t49\tEMAAAA\tLQMAAA\tVVVVxx\n5638\t8540\t0\t2\t8\t18\t38\t638\t1638\t638\t5638\t76\t77\tWIAAAA\tMQMAAA\tAAAAxx\n479\t8541\t1\t3\t9\t19\t79\t479\t479\t479\t479\t158\t159\tLSAAAA\tNQMAAA\tHHHHxx\n4125\t8542\t1\t1\t5\t5\t25\t125\t125\t4125\t4125\t50\t51\tRCAAAA\tOQMAAA\tOOOOxx\n1525\t8543\t1\t1\t5\t5\t25\t525\t1525\t1525\t1525\t50\t51\tRGAAAA\tPQMAAA\tVVVVxx\n7529\t8544\t1\t1\t9\t9\t29\t529\t1529\t2529\t7529\t58\t59\tPDAAAA\tQQMAAA\tAAAAxx\n931\t8545\t1\t3\t1\t11\t31\t931\t931\t931\t931\t62\t63\tVJAAAA\tRQMAAA\tHHHHxx\n5175\t8546\t1\t3\t5\t15\t75\t175\t1175\t175\t5175\t150\t151\tBRAAAA\tSQMAAA\tOOOOxx\n6798\t8547\t0\t2\t8\t18\t98\t798\t798\t1798\t6798\t196\t197\tMBAAAA\tTQMAAA\tVVVVxx\n2111\t8548\t1\t3\t1\t11\t11\t111\t111\t2111\t2111\t22\t23\tFDAAAA\tUQMAAA\tAAAAxx\n6145\t8549\t1\t1\t5\t5\t45\t145\t145\t1145\t6145\t90\t91\tJCAAAA\tVQMAAA\tHHHHxx\n4712\t8550\t0\t0\t2\t12\t12\t712\t712\t4712\t4712\t24\t25\tGZAAAA\tWQMAAA\tOOOOxx\n3110\t8551\t0\t2\t0\t10\t10\t110\t1110\t3110\t3110\t20\t21\tQPAAAA\tXQMAAA\tVVVVxx\n97\t8552\t1\t1\t7\t17\t97\t97\t97\t97\t97\t194\t195\tTDAAAA\tYQMAAA\tAAAAxx\n758\t8553\t0\t2\t8\t18\t58\t758\t758\t758\t758\t116\t117\tEDAAAA\tZQMAAA\tHHHHxx\n1895\t8554\t1\t3\t5\t15\t95\t895\t1895\t1895\t1895\t190\t191\tXUAAAA\tARMAAA\tOOOOxx\n5289\t8555\t1\t1\t9\t9\t89\t289\t1289\t289\t5289\t178\t179\tLVAAAA\tBRMAAA\tVVVVxx\n5026\t8556\t0\t2\t6\t6\t26\t26\t1026\t26\t5026\t52\t53\tILAAAA\tCRMAAA\tAAAAxx\n4725\t8557\t1\t1\t5\t5\t25\t725\t725\t4725\t4725\t50\t51\tTZAAAA\tDRMAAA\tHHHHxx\n1679\t8558\t1\t3\t9\t19\t79\t679\t1679\t1679\t1679\t158\t159\tPMAAAA\tERMAAA\tOOOOxx\n4433\t8559\t1\t1\t3\t13\t33\t433\t433\t4433\t4433\t66\t67\tNOAAAA\tFRMAAA\tVVVVxx\n5340\t8560\t0\t0\t0\t0\t40\t340\t1340\t340\t5340\t80\t81\tKXAAAA\tGRMAAA\tAAAAxx\n6340\t8561\t0\t0\t0\t0\t40\t340\t340\t1340\t6340\t80\t81\tWJAAAA\tHRMAAA\tHHHHxx\n3261\t8562\t1\t1\t1\t1\t61\t261\t1261\t3261\t3261\t122\t123\tLVAAAA\tIRMAAA\tOOOOxx\n8108\t8563\t0\t0\t8\t8\t8\t108\t108\t3108\t8108\t16\t17\tWZAAAA\tJRMAAA\tVVVVxx\n8785\t8564\t1\t1\t5\t5\t85\t785\t785\t3785\t8785\t170\t171\tXZAAAA\tKRMAAA\tAAAAxx\n7391\t8565\t1\t3\t1\t11\t91\t391\t1391\t2391\t7391\t182\t183\tHYAAAA\tLRMAAA\tHHHHxx\n1496\t8566\t0\t0\t6\t16\t96\t496\t1496\t1496\t1496\t192\t193\tOFAAAA\tMRMAAA\tOOOOxx\n1484\t8567\t0\t0\t4\t4\t84\t484\t1484\t1484\t1484\t168\t169\tCFAAAA\tNRMAAA\tVVVVxx\n5884\t8568\t0\t0\t4\t4\t84\t884\t1884\t884\t5884\t168\t169\tISAAAA\tORMAAA\tAAAAxx\n342\t8569\t0\t2\t2\t2\t42\t342\t342\t342\t342\t84\t85\tENAAAA\tPRMAAA\tHHHHxx\n7659\t8570\t1\t3\t9\t19\t59\t659\t1659\t2659\t7659\t118\t119\tPIAAAA\tQRMAAA\tOOOOxx\n6635\t8571\t1\t3\t5\t15\t35\t635\t635\t1635\t6635\t70\t71\tFVAAAA\tRRMAAA\tVVVVxx\n8507\t8572\t1\t3\t7\t7\t7\t507\t507\t3507\t8507\t14\t15\tFPAAAA\tSRMAAA\tAAAAxx\n2583\t8573\t1\t3\t3\t3\t83\t583\t583\t2583\t2583\t166\t167\tJVAAAA\tTRMAAA\tHHHHxx\n6533\t8574\t1\t1\t3\t13\t33\t533\t533\t1533\t6533\t66\t67\tHRAAAA\tURMAAA\tOOOOxx\n5879\t8575\t1\t3\t9\t19\t79\t879\t1879\t879\t5879\t158\t159\tDSAAAA\tVRMAAA\tVVVVxx\n5511\t8576\t1\t3\t1\t11\t11\t511\t1511\t511\t5511\t22\t23\tZDAAAA\tWRMAAA\tAAAAxx\n3682\t8577\t0\t2\t2\t2\t82\t682\t1682\t3682\t3682\t164\t165\tQLAAAA\tXRMAAA\tHHHHxx\n7182\t8578\t0\t2\t2\t2\t82\t182\t1182\t2182\t7182\t164\t165\tGQAAAA\tYRMAAA\tOOOOxx\n1409\t8579\t1\t1\t9\t9\t9\t409\t1409\t1409\t1409\t18\t19\tFCAAAA\tZRMAAA\tVVVVxx\n3363\t8580\t1\t3\t3\t3\t63\t363\t1363\t3363\t3363\t126\t127\tJZAAAA\tASMAAA\tAAAAxx\n729\t8581\t1\t1\t9\t9\t29\t729\t729\t729\t729\t58\t59\tBCAAAA\tBSMAAA\tHHHHxx\n5857\t8582\t1\t1\t7\t17\t57\t857\t1857\t857\t5857\t114\t115\tHRAAAA\tCSMAAA\tOOOOxx\n235\t8583\t1\t3\t5\t15\t35\t235\t235\t235\t235\t70\t71\tBJAAAA\tDSMAAA\tVVVVxx\n193\t8584\t1\t1\t3\t13\t93\t193\t193\t193\t193\t186\t187\tLHAAAA\tESMAAA\tAAAAxx\n5586\t8585\t0\t2\t6\t6\t86\t586\t1586\t586\t5586\t172\t173\tWGAAAA\tFSMAAA\tHHHHxx\n6203\t8586\t1\t3\t3\t3\t3\t203\t203\t1203\t6203\t6\t7\tPEAAAA\tGSMAAA\tOOOOxx\n6795\t8587\t1\t3\t5\t15\t95\t795\t795\t1795\t6795\t190\t191\tJBAAAA\tHSMAAA\tVVVVxx\n3211\t8588\t1\t3\t1\t11\t11\t211\t1211\t3211\t3211\t22\t23\tNTAAAA\tISMAAA\tAAAAxx\n9763\t8589\t1\t3\t3\t3\t63\t763\t1763\t4763\t9763\t126\t127\tNLAAAA\tJSMAAA\tHHHHxx\n9043\t8590\t1\t3\t3\t3\t43\t43\t1043\t4043\t9043\t86\t87\tVJAAAA\tKSMAAA\tOOOOxx\n2854\t8591\t0\t2\t4\t14\t54\t854\t854\t2854\t2854\t108\t109\tUFAAAA\tLSMAAA\tVVVVxx\n565\t8592\t1\t1\t5\t5\t65\t565\t565\t565\t565\t130\t131\tTVAAAA\tMSMAAA\tAAAAxx\n9284\t8593\t0\t0\t4\t4\t84\t284\t1284\t4284\t9284\t168\t169\tCTAAAA\tNSMAAA\tHHHHxx\n7886\t8594\t0\t2\t6\t6\t86\t886\t1886\t2886\t7886\t172\t173\tIRAAAA\tOSMAAA\tOOOOxx\n122\t8595\t0\t2\t2\t2\t22\t122\t122\t122\t122\t44\t45\tSEAAAA\tPSMAAA\tVVVVxx\n4934\t8596\t0\t2\t4\t14\t34\t934\t934\t4934\t4934\t68\t69\tUHAAAA\tQSMAAA\tAAAAxx\n1766\t8597\t0\t2\t6\t6\t66\t766\t1766\t1766\t1766\t132\t133\tYPAAAA\tRSMAAA\tHHHHxx\n2554\t8598\t0\t2\t4\t14\t54\t554\t554\t2554\t2554\t108\t109\tGUAAAA\tSSMAAA\tOOOOxx\n488\t8599\t0\t0\t8\t8\t88\t488\t488\t488\t488\t176\t177\tUSAAAA\tTSMAAA\tVVVVxx\n825\t8600\t1\t1\t5\t5\t25\t825\t825\t825\t825\t50\t51\tTFAAAA\tUSMAAA\tAAAAxx\n678\t8601\t0\t2\t8\t18\t78\t678\t678\t678\t678\t156\t157\tCAAAAA\tVSMAAA\tHHHHxx\n4543\t8602\t1\t3\t3\t3\t43\t543\t543\t4543\t4543\t86\t87\tTSAAAA\tWSMAAA\tOOOOxx\n1699\t8603\t1\t3\t9\t19\t99\t699\t1699\t1699\t1699\t198\t199\tJNAAAA\tXSMAAA\tVVVVxx\n3771\t8604\t1\t3\t1\t11\t71\t771\t1771\t3771\t3771\t142\t143\tBPAAAA\tYSMAAA\tAAAAxx\n1234\t8605\t0\t2\t4\t14\t34\t234\t1234\t1234\t1234\t68\t69\tMVAAAA\tZSMAAA\tHHHHxx\n4152\t8606\t0\t0\t2\t12\t52\t152\t152\t4152\t4152\t104\t105\tSDAAAA\tATMAAA\tOOOOxx\n1632\t8607\t0\t0\t2\t12\t32\t632\t1632\t1632\t1632\t64\t65\tUKAAAA\tBTMAAA\tVVVVxx\n4988\t8608\t0\t0\t8\t8\t88\t988\t988\t4988\t4988\t176\t177\tWJAAAA\tCTMAAA\tAAAAxx\n1980\t8609\t0\t0\t0\t0\t80\t980\t1980\t1980\t1980\t160\t161\tEYAAAA\tDTMAAA\tHHHHxx\n7479\t8610\t1\t3\t9\t19\t79\t479\t1479\t2479\t7479\t158\t159\tRBAAAA\tETMAAA\tOOOOxx\n2586\t8611\t0\t2\t6\t6\t86\t586\t586\t2586\t2586\t172\t173\tMVAAAA\tFTMAAA\tVVVVxx\n5433\t8612\t1\t1\t3\t13\t33\t433\t1433\t433\t5433\t66\t67\tZAAAAA\tGTMAAA\tAAAAxx\n2261\t8613\t1\t1\t1\t1\t61\t261\t261\t2261\t2261\t122\t123\tZIAAAA\tHTMAAA\tHHHHxx\n1180\t8614\t0\t0\t0\t0\t80\t180\t1180\t1180\t1180\t160\t161\tKTAAAA\tITMAAA\tOOOOxx\n3938\t8615\t0\t2\t8\t18\t38\t938\t1938\t3938\t3938\t76\t77\tMVAAAA\tJTMAAA\tVVVVxx\n6714\t8616\t0\t2\t4\t14\t14\t714\t714\t1714\t6714\t28\t29\tGYAAAA\tKTMAAA\tAAAAxx\n2890\t8617\t0\t2\t0\t10\t90\t890\t890\t2890\t2890\t180\t181\tEHAAAA\tLTMAAA\tHHHHxx\n7379\t8618\t1\t3\t9\t19\t79\t379\t1379\t2379\t7379\t158\t159\tVXAAAA\tMTMAAA\tOOOOxx\n5896\t8619\t0\t0\t6\t16\t96\t896\t1896\t896\t5896\t192\t193\tUSAAAA\tNTMAAA\tVVVVxx\n5949\t8620\t1\t1\t9\t9\t49\t949\t1949\t949\t5949\t98\t99\tVUAAAA\tOTMAAA\tAAAAxx\n3194\t8621\t0\t2\t4\t14\t94\t194\t1194\t3194\t3194\t188\t189\tWSAAAA\tPTMAAA\tHHHHxx\n9325\t8622\t1\t1\t5\t5\t25\t325\t1325\t4325\t9325\t50\t51\tRUAAAA\tQTMAAA\tOOOOxx\n9531\t8623\t1\t3\t1\t11\t31\t531\t1531\t4531\t9531\t62\t63\tPCAAAA\tRTMAAA\tVVVVxx\n711\t8624\t1\t3\t1\t11\t11\t711\t711\t711\t711\t22\t23\tJBAAAA\tSTMAAA\tAAAAxx\n2450\t8625\t0\t2\t0\t10\t50\t450\t450\t2450\t2450\t100\t101\tGQAAAA\tTTMAAA\tHHHHxx\n1929\t8626\t1\t1\t9\t9\t29\t929\t1929\t1929\t1929\t58\t59\tFWAAAA\tUTMAAA\tOOOOxx\n6165\t8627\t1\t1\t5\t5\t65\t165\t165\t1165\t6165\t130\t131\tDDAAAA\tVTMAAA\tVVVVxx\n4050\t8628\t0\t2\t0\t10\t50\t50\t50\t4050\t4050\t100\t101\tUZAAAA\tWTMAAA\tAAAAxx\n9011\t8629\t1\t3\t1\t11\t11\t11\t1011\t4011\t9011\t22\t23\tPIAAAA\tXTMAAA\tHHHHxx\n7916\t8630\t0\t0\t6\t16\t16\t916\t1916\t2916\t7916\t32\t33\tMSAAAA\tYTMAAA\tOOOOxx\n9136\t8631\t0\t0\t6\t16\t36\t136\t1136\t4136\t9136\t72\t73\tKNAAAA\tZTMAAA\tVVVVxx\n8782\t8632\t0\t2\t2\t2\t82\t782\t782\t3782\t8782\t164\t165\tUZAAAA\tAUMAAA\tAAAAxx\n8491\t8633\t1\t3\t1\t11\t91\t491\t491\t3491\t8491\t182\t183\tPOAAAA\tBUMAAA\tHHHHxx\n5114\t8634\t0\t2\t4\t14\t14\t114\t1114\t114\t5114\t28\t29\tSOAAAA\tCUMAAA\tOOOOxx\n5815\t8635\t1\t3\t5\t15\t15\t815\t1815\t815\t5815\t30\t31\tRPAAAA\tDUMAAA\tVVVVxx\n5628\t8636\t0\t0\t8\t8\t28\t628\t1628\t628\t5628\t56\t57\tMIAAAA\tEUMAAA\tAAAAxx\n810\t8637\t0\t2\t0\t10\t10\t810\t810\t810\t810\t20\t21\tEFAAAA\tFUMAAA\tHHHHxx\n6178\t8638\t0\t2\t8\t18\t78\t178\t178\t1178\t6178\t156\t157\tQDAAAA\tGUMAAA\tOOOOxx\n2619\t8639\t1\t3\t9\t19\t19\t619\t619\t2619\t2619\t38\t39\tTWAAAA\tHUMAAA\tVVVVxx\n3340\t8640\t0\t0\t0\t0\t40\t340\t1340\t3340\t3340\t80\t81\tMYAAAA\tIUMAAA\tAAAAxx\n2491\t8641\t1\t3\t1\t11\t91\t491\t491\t2491\t2491\t182\t183\tVRAAAA\tJUMAAA\tHHHHxx\n3574\t8642\t0\t2\t4\t14\t74\t574\t1574\t3574\t3574\t148\t149\tMHAAAA\tKUMAAA\tOOOOxx\n6754\t8643\t0\t2\t4\t14\t54\t754\t754\t1754\t6754\t108\t109\tUZAAAA\tLUMAAA\tVVVVxx\n1566\t8644\t0\t2\t6\t6\t66\t566\t1566\t1566\t1566\t132\t133\tGIAAAA\tMUMAAA\tAAAAxx\n9174\t8645\t0\t2\t4\t14\t74\t174\t1174\t4174\t9174\t148\t149\tWOAAAA\tNUMAAA\tHHHHxx\n1520\t8646\t0\t0\t0\t0\t20\t520\t1520\t1520\t1520\t40\t41\tMGAAAA\tOUMAAA\tOOOOxx\n2691\t8647\t1\t3\t1\t11\t91\t691\t691\t2691\t2691\t182\t183\tNZAAAA\tPUMAAA\tVVVVxx\n6961\t8648\t1\t1\t1\t1\t61\t961\t961\t1961\t6961\t122\t123\tTHAAAA\tQUMAAA\tAAAAxx\n5722\t8649\t0\t2\t2\t2\t22\t722\t1722\t722\t5722\t44\t45\tCMAAAA\tRUMAAA\tHHHHxx\n9707\t8650\t1\t3\t7\t7\t7\t707\t1707\t4707\t9707\t14\t15\tJJAAAA\tSUMAAA\tOOOOxx\n2891\t8651\t1\t3\t1\t11\t91\t891\t891\t2891\t2891\t182\t183\tFHAAAA\tTUMAAA\tVVVVxx\n341\t8652\t1\t1\t1\t1\t41\t341\t341\t341\t341\t82\t83\tDNAAAA\tUUMAAA\tAAAAxx\n4690\t8653\t0\t2\t0\t10\t90\t690\t690\t4690\t4690\t180\t181\tKYAAAA\tVUMAAA\tHHHHxx\n7841\t8654\t1\t1\t1\t1\t41\t841\t1841\t2841\t7841\t82\t83\tPPAAAA\tWUMAAA\tOOOOxx\n6615\t8655\t1\t3\t5\t15\t15\t615\t615\t1615\t6615\t30\t31\tLUAAAA\tXUMAAA\tVVVVxx\n9169\t8656\t1\t1\t9\t9\t69\t169\t1169\t4169\t9169\t138\t139\tROAAAA\tYUMAAA\tAAAAxx\n6689\t8657\t1\t1\t9\t9\t89\t689\t689\t1689\t6689\t178\t179\tHXAAAA\tZUMAAA\tHHHHxx\n8721\t8658\t1\t1\t1\t1\t21\t721\t721\t3721\t8721\t42\t43\tLXAAAA\tAVMAAA\tOOOOxx\n7508\t8659\t0\t0\t8\t8\t8\t508\t1508\t2508\t7508\t16\t17\tUCAAAA\tBVMAAA\tVVVVxx\n8631\t8660\t1\t3\t1\t11\t31\t631\t631\t3631\t8631\t62\t63\tZTAAAA\tCVMAAA\tAAAAxx\n480\t8661\t0\t0\t0\t0\t80\t480\t480\t480\t480\t160\t161\tMSAAAA\tDVMAAA\tHHHHxx\n7094\t8662\t0\t2\t4\t14\t94\t94\t1094\t2094\t7094\t188\t189\tWMAAAA\tEVMAAA\tOOOOxx\n319\t8663\t1\t3\t9\t19\t19\t319\t319\t319\t319\t38\t39\tHMAAAA\tFVMAAA\tVVVVxx\n9421\t8664\t1\t1\t1\t1\t21\t421\t1421\t4421\t9421\t42\t43\tJYAAAA\tGVMAAA\tAAAAxx\n4352\t8665\t0\t0\t2\t12\t52\t352\t352\t4352\t4352\t104\t105\tKLAAAA\tHVMAAA\tHHHHxx\n5019\t8666\t1\t3\t9\t19\t19\t19\t1019\t19\t5019\t38\t39\tBLAAAA\tIVMAAA\tOOOOxx\n3956\t8667\t0\t0\t6\t16\t56\t956\t1956\t3956\t3956\t112\t113\tEWAAAA\tJVMAAA\tVVVVxx\n114\t8668\t0\t2\t4\t14\t14\t114\t114\t114\t114\t28\t29\tKEAAAA\tKVMAAA\tAAAAxx\n1196\t8669\t0\t0\t6\t16\t96\t196\t1196\t1196\t1196\t192\t193\tAUAAAA\tLVMAAA\tHHHHxx\n1407\t8670\t1\t3\t7\t7\t7\t407\t1407\t1407\t1407\t14\t15\tDCAAAA\tMVMAAA\tOOOOxx\n7432\t8671\t0\t0\t2\t12\t32\t432\t1432\t2432\t7432\t64\t65\tWZAAAA\tNVMAAA\tVVVVxx\n3141\t8672\t1\t1\t1\t1\t41\t141\t1141\t3141\t3141\t82\t83\tVQAAAA\tOVMAAA\tAAAAxx\n2073\t8673\t1\t1\t3\t13\t73\t73\t73\t2073\t2073\t146\t147\tTBAAAA\tPVMAAA\tHHHHxx\n3400\t8674\t0\t0\t0\t0\t0\t400\t1400\t3400\t3400\t0\t1\tUAAAAA\tQVMAAA\tOOOOxx\n505\t8675\t1\t1\t5\t5\t5\t505\t505\t505\t505\t10\t11\tLTAAAA\tRVMAAA\tVVVVxx\n1263\t8676\t1\t3\t3\t3\t63\t263\t1263\t1263\t1263\t126\t127\tPWAAAA\tSVMAAA\tAAAAxx\n190\t8677\t0\t2\t0\t10\t90\t190\t190\t190\t190\t180\t181\tIHAAAA\tTVMAAA\tHHHHxx\n6686\t8678\t0\t2\t6\t6\t86\t686\t686\t1686\t6686\t172\t173\tEXAAAA\tUVMAAA\tOOOOxx\n9821\t8679\t1\t1\t1\t1\t21\t821\t1821\t4821\t9821\t42\t43\tTNAAAA\tVVMAAA\tVVVVxx\n1119\t8680\t1\t3\t9\t19\t19\t119\t1119\t1119\t1119\t38\t39\tBRAAAA\tWVMAAA\tAAAAxx\n2955\t8681\t1\t3\t5\t15\t55\t955\t955\t2955\t2955\t110\t111\tRJAAAA\tXVMAAA\tHHHHxx\n224\t8682\t0\t0\t4\t4\t24\t224\t224\t224\t224\t48\t49\tQIAAAA\tYVMAAA\tOOOOxx\n7562\t8683\t0\t2\t2\t2\t62\t562\t1562\t2562\t7562\t124\t125\tWEAAAA\tZVMAAA\tVVVVxx\n8845\t8684\t1\t1\t5\t5\t45\t845\t845\t3845\t8845\t90\t91\tFCAAAA\tAWMAAA\tAAAAxx\n5405\t8685\t1\t1\t5\t5\t5\t405\t1405\t405\t5405\t10\t11\tXZAAAA\tBWMAAA\tHHHHxx\n9192\t8686\t0\t0\t2\t12\t92\t192\t1192\t4192\t9192\t184\t185\tOPAAAA\tCWMAAA\tOOOOxx\n4927\t8687\t1\t3\t7\t7\t27\t927\t927\t4927\t4927\t54\t55\tNHAAAA\tDWMAAA\tVVVVxx\n997\t8688\t1\t1\t7\t17\t97\t997\t997\t997\t997\t194\t195\tJMAAAA\tEWMAAA\tAAAAxx\n989\t8689\t1\t1\t9\t9\t89\t989\t989\t989\t989\t178\t179\tBMAAAA\tFWMAAA\tHHHHxx\n7258\t8690\t0\t2\t8\t18\t58\t258\t1258\t2258\t7258\t116\t117\tETAAAA\tGWMAAA\tOOOOxx\n6899\t8691\t1\t3\t9\t19\t99\t899\t899\t1899\t6899\t198\t199\tJFAAAA\tHWMAAA\tVVVVxx\n1770\t8692\t0\t2\t0\t10\t70\t770\t1770\t1770\t1770\t140\t141\tCQAAAA\tIWMAAA\tAAAAxx\n4423\t8693\t1\t3\t3\t3\t23\t423\t423\t4423\t4423\t46\t47\tDOAAAA\tJWMAAA\tHHHHxx\n5671\t8694\t1\t3\t1\t11\t71\t671\t1671\t671\t5671\t142\t143\tDKAAAA\tKWMAAA\tOOOOxx\n8393\t8695\t1\t1\t3\t13\t93\t393\t393\t3393\t8393\t186\t187\tVKAAAA\tLWMAAA\tVVVVxx\n4355\t8696\t1\t3\t5\t15\t55\t355\t355\t4355\t4355\t110\t111\tNLAAAA\tMWMAAA\tAAAAxx\n3919\t8697\t1\t3\t9\t19\t19\t919\t1919\t3919\t3919\t38\t39\tTUAAAA\tNWMAAA\tHHHHxx\n338\t8698\t0\t2\t8\t18\t38\t338\t338\t338\t338\t76\t77\tANAAAA\tOWMAAA\tOOOOxx\n5790\t8699\t0\t2\t0\t10\t90\t790\t1790\t790\t5790\t180\t181\tSOAAAA\tPWMAAA\tVVVVxx\n1452\t8700\t0\t0\t2\t12\t52\t452\t1452\t1452\t1452\t104\t105\tWDAAAA\tQWMAAA\tAAAAxx\n939\t8701\t1\t3\t9\t19\t39\t939\t939\t939\t939\t78\t79\tDKAAAA\tRWMAAA\tHHHHxx\n8913\t8702\t1\t1\t3\t13\t13\t913\t913\t3913\t8913\t26\t27\tVEAAAA\tSWMAAA\tOOOOxx\n7157\t8703\t1\t1\t7\t17\t57\t157\t1157\t2157\t7157\t114\t115\tHPAAAA\tTWMAAA\tVVVVxx\n7240\t8704\t0\t0\t0\t0\t40\t240\t1240\t2240\t7240\t80\t81\tMSAAAA\tUWMAAA\tAAAAxx\n3492\t8705\t0\t0\t2\t12\t92\t492\t1492\t3492\t3492\t184\t185\tIEAAAA\tVWMAAA\tHHHHxx\n3464\t8706\t0\t0\t4\t4\t64\t464\t1464\t3464\t3464\t128\t129\tGDAAAA\tWWMAAA\tOOOOxx\n388\t8707\t0\t0\t8\t8\t88\t388\t388\t388\t388\t176\t177\tYOAAAA\tXWMAAA\tVVVVxx\n4135\t8708\t1\t3\t5\t15\t35\t135\t135\t4135\t4135\t70\t71\tBDAAAA\tYWMAAA\tAAAAxx\n1194\t8709\t0\t2\t4\t14\t94\t194\t1194\t1194\t1194\t188\t189\tYTAAAA\tZWMAAA\tHHHHxx\n5476\t8710\t0\t0\t6\t16\t76\t476\t1476\t476\t5476\t152\t153\tQCAAAA\tAXMAAA\tOOOOxx\n9844\t8711\t0\t0\t4\t4\t44\t844\t1844\t4844\t9844\t88\t89\tQOAAAA\tBXMAAA\tVVVVxx\n9364\t8712\t0\t0\t4\t4\t64\t364\t1364\t4364\t9364\t128\t129\tEWAAAA\tCXMAAA\tAAAAxx\n5238\t8713\t0\t2\t8\t18\t38\t238\t1238\t238\t5238\t76\t77\tMTAAAA\tDXMAAA\tHHHHxx\n3712\t8714\t0\t0\t2\t12\t12\t712\t1712\t3712\t3712\t24\t25\tUMAAAA\tEXMAAA\tOOOOxx\n6189\t8715\t1\t1\t9\t9\t89\t189\t189\t1189\t6189\t178\t179\tBEAAAA\tFXMAAA\tVVVVxx\n5257\t8716\t1\t1\t7\t17\t57\t257\t1257\t257\t5257\t114\t115\tFUAAAA\tGXMAAA\tAAAAxx\n81\t8717\t1\t1\t1\t1\t81\t81\t81\t81\t81\t162\t163\tDDAAAA\tHXMAAA\tHHHHxx\n3289\t8718\t1\t1\t9\t9\t89\t289\t1289\t3289\t3289\t178\t179\tNWAAAA\tIXMAAA\tOOOOxx\n1177\t8719\t1\t1\t7\t17\t77\t177\t1177\t1177\t1177\t154\t155\tHTAAAA\tJXMAAA\tVVVVxx\n5038\t8720\t0\t2\t8\t18\t38\t38\t1038\t38\t5038\t76\t77\tULAAAA\tKXMAAA\tAAAAxx\n325\t8721\t1\t1\t5\t5\t25\t325\t325\t325\t325\t50\t51\tNMAAAA\tLXMAAA\tHHHHxx\n7221\t8722\t1\t1\t1\t1\t21\t221\t1221\t2221\t7221\t42\t43\tTRAAAA\tMXMAAA\tOOOOxx\n7123\t8723\t1\t3\t3\t3\t23\t123\t1123\t2123\t7123\t46\t47\tZNAAAA\tNXMAAA\tVVVVxx\n6364\t8724\t0\t0\t4\t4\t64\t364\t364\t1364\t6364\t128\t129\tUKAAAA\tOXMAAA\tAAAAxx\n4468\t8725\t0\t0\t8\t8\t68\t468\t468\t4468\t4468\t136\t137\tWPAAAA\tPXMAAA\tHHHHxx\n9185\t8726\t1\t1\t5\t5\t85\t185\t1185\t4185\t9185\t170\t171\tHPAAAA\tQXMAAA\tOOOOxx\n4158\t8727\t0\t2\t8\t18\t58\t158\t158\t4158\t4158\t116\t117\tYDAAAA\tRXMAAA\tVVVVxx\n9439\t8728\t1\t3\t9\t19\t39\t439\t1439\t4439\t9439\t78\t79\tBZAAAA\tSXMAAA\tAAAAxx\n7759\t8729\t1\t3\t9\t19\t59\t759\t1759\t2759\t7759\t118\t119\tLMAAAA\tTXMAAA\tHHHHxx\n3325\t8730\t1\t1\t5\t5\t25\t325\t1325\t3325\t3325\t50\t51\tXXAAAA\tUXMAAA\tOOOOxx\n7991\t8731\t1\t3\t1\t11\t91\t991\t1991\t2991\t7991\t182\t183\tJVAAAA\tVXMAAA\tVVVVxx\n1650\t8732\t0\t2\t0\t10\t50\t650\t1650\t1650\t1650\t100\t101\tMLAAAA\tWXMAAA\tAAAAxx\n8395\t8733\t1\t3\t5\t15\t95\t395\t395\t3395\t8395\t190\t191\tXKAAAA\tXXMAAA\tHHHHxx\n286\t8734\t0\t2\t6\t6\t86\t286\t286\t286\t286\t172\t173\tALAAAA\tYXMAAA\tOOOOxx\n1507\t8735\t1\t3\t7\t7\t7\t507\t1507\t1507\t1507\t14\t15\tZFAAAA\tZXMAAA\tVVVVxx\n4122\t8736\t0\t2\t2\t2\t22\t122\t122\t4122\t4122\t44\t45\tOCAAAA\tAYMAAA\tAAAAxx\n2625\t8737\t1\t1\t5\t5\t25\t625\t625\t2625\t2625\t50\t51\tZWAAAA\tBYMAAA\tHHHHxx\n1140\t8738\t0\t0\t0\t0\t40\t140\t1140\t1140\t1140\t80\t81\tWRAAAA\tCYMAAA\tOOOOxx\n5262\t8739\t0\t2\t2\t2\t62\t262\t1262\t262\t5262\t124\t125\tKUAAAA\tDYMAAA\tVVVVxx\n4919\t8740\t1\t3\t9\t19\t19\t919\t919\t4919\t4919\t38\t39\tFHAAAA\tEYMAAA\tAAAAxx\n7266\t8741\t0\t2\t6\t6\t66\t266\t1266\t2266\t7266\t132\t133\tMTAAAA\tFYMAAA\tHHHHxx\n630\t8742\t0\t2\t0\t10\t30\t630\t630\t630\t630\t60\t61\tGYAAAA\tGYMAAA\tOOOOxx\n2129\t8743\t1\t1\t9\t9\t29\t129\t129\t2129\t2129\t58\t59\tXDAAAA\tHYMAAA\tVVVVxx\n9552\t8744\t0\t0\t2\t12\t52\t552\t1552\t4552\t9552\t104\t105\tKDAAAA\tIYMAAA\tAAAAxx\n3018\t8745\t0\t2\t8\t18\t18\t18\t1018\t3018\t3018\t36\t37\tCMAAAA\tJYMAAA\tHHHHxx\n7145\t8746\t1\t1\t5\t5\t45\t145\t1145\t2145\t7145\t90\t91\tVOAAAA\tKYMAAA\tOOOOxx\n1633\t8747\t1\t1\t3\t13\t33\t633\t1633\t1633\t1633\t66\t67\tVKAAAA\tLYMAAA\tVVVVxx\n7957\t8748\t1\t1\t7\t17\t57\t957\t1957\t2957\t7957\t114\t115\tBUAAAA\tMYMAAA\tAAAAxx\n774\t8749\t0\t2\t4\t14\t74\t774\t774\t774\t774\t148\t149\tUDAAAA\tNYMAAA\tHHHHxx\n9371\t8750\t1\t3\t1\t11\t71\t371\t1371\t4371\t9371\t142\t143\tLWAAAA\tOYMAAA\tOOOOxx\n6007\t8751\t1\t3\t7\t7\t7\t7\t7\t1007\t6007\t14\t15\tBXAAAA\tPYMAAA\tVVVVxx\n5277\t8752\t1\t1\t7\t17\t77\t277\t1277\t277\t5277\t154\t155\tZUAAAA\tQYMAAA\tAAAAxx\n9426\t8753\t0\t2\t6\t6\t26\t426\t1426\t4426\t9426\t52\t53\tOYAAAA\tRYMAAA\tHHHHxx\n9190\t8754\t0\t2\t0\t10\t90\t190\t1190\t4190\t9190\t180\t181\tMPAAAA\tSYMAAA\tOOOOxx\n8996\t8755\t0\t0\t6\t16\t96\t996\t996\t3996\t8996\t192\t193\tAIAAAA\tTYMAAA\tVVVVxx\n3409\t8756\t1\t1\t9\t9\t9\t409\t1409\t3409\t3409\t18\t19\tDBAAAA\tUYMAAA\tAAAAxx\n7212\t8757\t0\t0\t2\t12\t12\t212\t1212\t2212\t7212\t24\t25\tKRAAAA\tVYMAAA\tHHHHxx\n416\t8758\t0\t0\t6\t16\t16\t416\t416\t416\t416\t32\t33\tAQAAAA\tWYMAAA\tOOOOxx\n7211\t8759\t1\t3\t1\t11\t11\t211\t1211\t2211\t7211\t22\t23\tJRAAAA\tXYMAAA\tVVVVxx\n7454\t8760\t0\t2\t4\t14\t54\t454\t1454\t2454\t7454\t108\t109\tSAAAAA\tYYMAAA\tAAAAxx\n8417\t8761\t1\t1\t7\t17\t17\t417\t417\t3417\t8417\t34\t35\tTLAAAA\tZYMAAA\tHHHHxx\n5562\t8762\t0\t2\t2\t2\t62\t562\t1562\t562\t5562\t124\t125\tYFAAAA\tAZMAAA\tOOOOxx\n4996\t8763\t0\t0\t6\t16\t96\t996\t996\t4996\t4996\t192\t193\tEKAAAA\tBZMAAA\tVVVVxx\n5718\t8764\t0\t2\t8\t18\t18\t718\t1718\t718\t5718\t36\t37\tYLAAAA\tCZMAAA\tAAAAxx\n7838\t8765\t0\t2\t8\t18\t38\t838\t1838\t2838\t7838\t76\t77\tMPAAAA\tDZMAAA\tHHHHxx\n7715\t8766\t1\t3\t5\t15\t15\t715\t1715\t2715\t7715\t30\t31\tTKAAAA\tEZMAAA\tOOOOxx\n2780\t8767\t0\t0\t0\t0\t80\t780\t780\t2780\t2780\t160\t161\tYCAAAA\tFZMAAA\tVVVVxx\n1013\t8768\t1\t1\t3\t13\t13\t13\t1013\t1013\t1013\t26\t27\tZMAAAA\tGZMAAA\tAAAAxx\n8465\t8769\t1\t1\t5\t5\t65\t465\t465\t3465\t8465\t130\t131\tPNAAAA\tHZMAAA\tHHHHxx\n7976\t8770\t0\t0\t6\t16\t76\t976\t1976\t2976\t7976\t152\t153\tUUAAAA\tIZMAAA\tOOOOxx\n7150\t8771\t0\t2\t0\t10\t50\t150\t1150\t2150\t7150\t100\t101\tAPAAAA\tJZMAAA\tVVVVxx\n6471\t8772\t1\t3\t1\t11\t71\t471\t471\t1471\t6471\t142\t143\tXOAAAA\tKZMAAA\tAAAAxx\n1927\t8773\t1\t3\t7\t7\t27\t927\t1927\t1927\t1927\t54\t55\tDWAAAA\tLZMAAA\tHHHHxx\n227\t8774\t1\t3\t7\t7\t27\t227\t227\t227\t227\t54\t55\tTIAAAA\tMZMAAA\tOOOOxx\n6462\t8775\t0\t2\t2\t2\t62\t462\t462\t1462\t6462\t124\t125\tOOAAAA\tNZMAAA\tVVVVxx\n5227\t8776\t1\t3\t7\t7\t27\t227\t1227\t227\t5227\t54\t55\tBTAAAA\tOZMAAA\tAAAAxx\n1074\t8777\t0\t2\t4\t14\t74\t74\t1074\t1074\t1074\t148\t149\tIPAAAA\tPZMAAA\tHHHHxx\n9448\t8778\t0\t0\t8\t8\t48\t448\t1448\t4448\t9448\t96\t97\tKZAAAA\tQZMAAA\tOOOOxx\n4459\t8779\t1\t3\t9\t19\t59\t459\t459\t4459\t4459\t118\t119\tNPAAAA\tRZMAAA\tVVVVxx\n2478\t8780\t0\t2\t8\t18\t78\t478\t478\t2478\t2478\t156\t157\tIRAAAA\tSZMAAA\tAAAAxx\n5005\t8781\t1\t1\t5\t5\t5\t5\t1005\t5\t5005\t10\t11\tNKAAAA\tTZMAAA\tHHHHxx\n2418\t8782\t0\t2\t8\t18\t18\t418\t418\t2418\t2418\t36\t37\tAPAAAA\tUZMAAA\tOOOOxx\n6991\t8783\t1\t3\t1\t11\t91\t991\t991\t1991\t6991\t182\t183\tXIAAAA\tVZMAAA\tVVVVxx\n4729\t8784\t1\t1\t9\t9\t29\t729\t729\t4729\t4729\t58\t59\tXZAAAA\tWZMAAA\tAAAAxx\n3548\t8785\t0\t0\t8\t8\t48\t548\t1548\t3548\t3548\t96\t97\tMGAAAA\tXZMAAA\tHHHHxx\n9616\t8786\t0\t0\t6\t16\t16\t616\t1616\t4616\t9616\t32\t33\tWFAAAA\tYZMAAA\tOOOOxx\n2901\t8787\t1\t1\t1\t1\t1\t901\t901\t2901\t2901\t2\t3\tPHAAAA\tZZMAAA\tVVVVxx\n10\t8788\t0\t2\t0\t10\t10\t10\t10\t10\t10\t20\t21\tKAAAAA\tAANAAA\tAAAAxx\n2637\t8789\t1\t1\t7\t17\t37\t637\t637\t2637\t2637\t74\t75\tLXAAAA\tBANAAA\tHHHHxx\n6747\t8790\t1\t3\t7\t7\t47\t747\t747\t1747\t6747\t94\t95\tNZAAAA\tCANAAA\tOOOOxx\n797\t8791\t1\t1\t7\t17\t97\t797\t797\t797\t797\t194\t195\tREAAAA\tDANAAA\tVVVVxx\n7609\t8792\t1\t1\t9\t9\t9\t609\t1609\t2609\t7609\t18\t19\tRGAAAA\tEANAAA\tAAAAxx\n8290\t8793\t0\t2\t0\t10\t90\t290\t290\t3290\t8290\t180\t181\tWGAAAA\tFANAAA\tHHHHxx\n8765\t8794\t1\t1\t5\t5\t65\t765\t765\t3765\t8765\t130\t131\tDZAAAA\tGANAAA\tOOOOxx\n8053\t8795\t1\t1\t3\t13\t53\t53\t53\t3053\t8053\t106\t107\tTXAAAA\tHANAAA\tVVVVxx\n5602\t8796\t0\t2\t2\t2\t2\t602\t1602\t602\t5602\t4\t5\tMHAAAA\tIANAAA\tAAAAxx\n3672\t8797\t0\t0\t2\t12\t72\t672\t1672\t3672\t3672\t144\t145\tGLAAAA\tJANAAA\tHHHHxx\n7513\t8798\t1\t1\t3\t13\t13\t513\t1513\t2513\t7513\t26\t27\tZCAAAA\tKANAAA\tOOOOxx\n3462\t8799\t0\t2\t2\t2\t62\t462\t1462\t3462\t3462\t124\t125\tEDAAAA\tLANAAA\tVVVVxx\n4457\t8800\t1\t1\t7\t17\t57\t457\t457\t4457\t4457\t114\t115\tLPAAAA\tMANAAA\tAAAAxx\n6547\t8801\t1\t3\t7\t7\t47\t547\t547\t1547\t6547\t94\t95\tVRAAAA\tNANAAA\tHHHHxx\n7417\t8802\t1\t1\t7\t17\t17\t417\t1417\t2417\t7417\t34\t35\tHZAAAA\tOANAAA\tOOOOxx\n8641\t8803\t1\t1\t1\t1\t41\t641\t641\t3641\t8641\t82\t83\tJUAAAA\tPANAAA\tVVVVxx\n149\t8804\t1\t1\t9\t9\t49\t149\t149\t149\t149\t98\t99\tTFAAAA\tQANAAA\tAAAAxx\n5041\t8805\t1\t1\t1\t1\t41\t41\t1041\t41\t5041\t82\t83\tXLAAAA\tRANAAA\tHHHHxx\n9232\t8806\t0\t0\t2\t12\t32\t232\t1232\t4232\t9232\t64\t65\tCRAAAA\tSANAAA\tOOOOxx\n3603\t8807\t1\t3\t3\t3\t3\t603\t1603\t3603\t3603\t6\t7\tPIAAAA\tTANAAA\tVVVVxx\n2792\t8808\t0\t0\t2\t12\t92\t792\t792\t2792\t2792\t184\t185\tKDAAAA\tUANAAA\tAAAAxx\n6620\t8809\t0\t0\t0\t0\t20\t620\t620\t1620\t6620\t40\t41\tQUAAAA\tVANAAA\tHHHHxx\n4000\t8810\t0\t0\t0\t0\t0\t0\t0\t4000\t4000\t0\t1\tWXAAAA\tWANAAA\tOOOOxx\n659\t8811\t1\t3\t9\t19\t59\t659\t659\t659\t659\t118\t119\tJZAAAA\tXANAAA\tVVVVxx\n8174\t8812\t0\t2\t4\t14\t74\t174\t174\t3174\t8174\t148\t149\tKCAAAA\tYANAAA\tAAAAxx\n4599\t8813\t1\t3\t9\t19\t99\t599\t599\t4599\t4599\t198\t199\tXUAAAA\tZANAAA\tHHHHxx\n7851\t8814\t1\t3\t1\t11\t51\t851\t1851\t2851\t7851\t102\t103\tZPAAAA\tABNAAA\tOOOOxx\n6284\t8815\t0\t0\t4\t4\t84\t284\t284\t1284\t6284\t168\t169\tSHAAAA\tBBNAAA\tVVVVxx\n7116\t8816\t0\t0\t6\t16\t16\t116\t1116\t2116\t7116\t32\t33\tSNAAAA\tCBNAAA\tAAAAxx\n5595\t8817\t1\t3\t5\t15\t95\t595\t1595\t595\t5595\t190\t191\tFHAAAA\tDBNAAA\tHHHHxx\n2903\t8818\t1\t3\t3\t3\t3\t903\t903\t2903\t2903\t6\t7\tRHAAAA\tEBNAAA\tOOOOxx\n5948\t8819\t0\t0\t8\t8\t48\t948\t1948\t948\t5948\t96\t97\tUUAAAA\tFBNAAA\tVVVVxx\n225\t8820\t1\t1\t5\t5\t25\t225\t225\t225\t225\t50\t51\tRIAAAA\tGBNAAA\tAAAAxx\n524\t8821\t0\t0\t4\t4\t24\t524\t524\t524\t524\t48\t49\tEUAAAA\tHBNAAA\tHHHHxx\n7639\t8822\t1\t3\t9\t19\t39\t639\t1639\t2639\t7639\t78\t79\tVHAAAA\tIBNAAA\tOOOOxx\n7297\t8823\t1\t1\t7\t17\t97\t297\t1297\t2297\t7297\t194\t195\tRUAAAA\tJBNAAA\tVVVVxx\n2606\t8824\t0\t2\t6\t6\t6\t606\t606\t2606\t2606\t12\t13\tGWAAAA\tKBNAAA\tAAAAxx\n4771\t8825\t1\t3\t1\t11\t71\t771\t771\t4771\t4771\t142\t143\tNBAAAA\tLBNAAA\tHHHHxx\n8162\t8826\t0\t2\t2\t2\t62\t162\t162\t3162\t8162\t124\t125\tYBAAAA\tMBNAAA\tOOOOxx\n8999\t8827\t1\t3\t9\t19\t99\t999\t999\t3999\t8999\t198\t199\tDIAAAA\tNBNAAA\tVVVVxx\n2309\t8828\t1\t1\t9\t9\t9\t309\t309\t2309\t2309\t18\t19\tVKAAAA\tOBNAAA\tAAAAxx\n3594\t8829\t0\t2\t4\t14\t94\t594\t1594\t3594\t3594\t188\t189\tGIAAAA\tPBNAAA\tHHHHxx\n6092\t8830\t0\t0\t2\t12\t92\t92\t92\t1092\t6092\t184\t185\tIAAAAA\tQBNAAA\tOOOOxx\n7467\t8831\t1\t3\t7\t7\t67\t467\t1467\t2467\t7467\t134\t135\tFBAAAA\tRBNAAA\tVVVVxx\n6986\t8832\t0\t2\t6\t6\t86\t986\t986\t1986\t6986\t172\t173\tSIAAAA\tSBNAAA\tAAAAxx\n9898\t8833\t0\t2\t8\t18\t98\t898\t1898\t4898\t9898\t196\t197\tSQAAAA\tTBNAAA\tHHHHxx\n9578\t8834\t0\t2\t8\t18\t78\t578\t1578\t4578\t9578\t156\t157\tKEAAAA\tUBNAAA\tOOOOxx\n156\t8835\t0\t0\t6\t16\t56\t156\t156\t156\t156\t112\t113\tAGAAAA\tVBNAAA\tVVVVxx\n5810\t8836\t0\t2\t0\t10\t10\t810\t1810\t810\t5810\t20\t21\tMPAAAA\tWBNAAA\tAAAAxx\n790\t8837\t0\t2\t0\t10\t90\t790\t790\t790\t790\t180\t181\tKEAAAA\tXBNAAA\tHHHHxx\n6840\t8838\t0\t0\t0\t0\t40\t840\t840\t1840\t6840\t80\t81\tCDAAAA\tYBNAAA\tOOOOxx\n6725\t8839\t1\t1\t5\t5\t25\t725\t725\t1725\t6725\t50\t51\tRYAAAA\tZBNAAA\tVVVVxx\n5528\t8840\t0\t0\t8\t8\t28\t528\t1528\t528\t5528\t56\t57\tQEAAAA\tACNAAA\tAAAAxx\n4120\t8841\t0\t0\t0\t0\t20\t120\t120\t4120\t4120\t40\t41\tMCAAAA\tBCNAAA\tHHHHxx\n6694\t8842\t0\t2\t4\t14\t94\t694\t694\t1694\t6694\t188\t189\tMXAAAA\tCCNAAA\tOOOOxx\n3552\t8843\t0\t0\t2\t12\t52\t552\t1552\t3552\t3552\t104\t105\tQGAAAA\tDCNAAA\tVVVVxx\n1478\t8844\t0\t2\t8\t18\t78\t478\t1478\t1478\t1478\t156\t157\tWEAAAA\tECNAAA\tAAAAxx\n8084\t8845\t0\t0\t4\t4\t84\t84\t84\t3084\t8084\t168\t169\tYYAAAA\tFCNAAA\tHHHHxx\n7578\t8846\t0\t2\t8\t18\t78\t578\t1578\t2578\t7578\t156\t157\tMFAAAA\tGCNAAA\tOOOOxx\n6314\t8847\t0\t2\t4\t14\t14\t314\t314\t1314\t6314\t28\t29\tWIAAAA\tHCNAAA\tVVVVxx\n6123\t8848\t1\t3\t3\t3\t23\t123\t123\t1123\t6123\t46\t47\tNBAAAA\tICNAAA\tAAAAxx\n9443\t8849\t1\t3\t3\t3\t43\t443\t1443\t4443\t9443\t86\t87\tFZAAAA\tJCNAAA\tHHHHxx\n9628\t8850\t0\t0\t8\t8\t28\t628\t1628\t4628\t9628\t56\t57\tIGAAAA\tKCNAAA\tOOOOxx\n8508\t8851\t0\t0\t8\t8\t8\t508\t508\t3508\t8508\t16\t17\tGPAAAA\tLCNAAA\tVVVVxx\n5552\t8852\t0\t0\t2\t12\t52\t552\t1552\t552\t5552\t104\t105\tOFAAAA\tMCNAAA\tAAAAxx\n5327\t8853\t1\t3\t7\t7\t27\t327\t1327\t327\t5327\t54\t55\tXWAAAA\tNCNAAA\tHHHHxx\n7771\t8854\t1\t3\t1\t11\t71\t771\t1771\t2771\t7771\t142\t143\tXMAAAA\tOCNAAA\tOOOOxx\n8932\t8855\t0\t0\t2\t12\t32\t932\t932\t3932\t8932\t64\t65\tOFAAAA\tPCNAAA\tVVVVxx\n3526\t8856\t0\t2\t6\t6\t26\t526\t1526\t3526\t3526\t52\t53\tQFAAAA\tQCNAAA\tAAAAxx\n4340\t8857\t0\t0\t0\t0\t40\t340\t340\t4340\t4340\t80\t81\tYKAAAA\tRCNAAA\tHHHHxx\n9419\t8858\t1\t3\t9\t19\t19\t419\t1419\t4419\t9419\t38\t39\tHYAAAA\tSCNAAA\tOOOOxx\n8421\t8859\t1\t1\t1\t1\t21\t421\t421\t3421\t8421\t42\t43\tXLAAAA\tTCNAAA\tVVVVxx\n7431\t8860\t1\t3\t1\t11\t31\t431\t1431\t2431\t7431\t62\t63\tVZAAAA\tUCNAAA\tAAAAxx\n172\t8861\t0\t0\t2\t12\t72\t172\t172\t172\t172\t144\t145\tQGAAAA\tVCNAAA\tHHHHxx\n3279\t8862\t1\t3\t9\t19\t79\t279\t1279\t3279\t3279\t158\t159\tDWAAAA\tWCNAAA\tOOOOxx\n1508\t8863\t0\t0\t8\t8\t8\t508\t1508\t1508\t1508\t16\t17\tAGAAAA\tXCNAAA\tVVVVxx\n7091\t8864\t1\t3\t1\t11\t91\t91\t1091\t2091\t7091\t182\t183\tTMAAAA\tYCNAAA\tAAAAxx\n1419\t8865\t1\t3\t9\t19\t19\t419\t1419\t1419\t1419\t38\t39\tPCAAAA\tZCNAAA\tHHHHxx\n3032\t8866\t0\t0\t2\t12\t32\t32\t1032\t3032\t3032\t64\t65\tQMAAAA\tADNAAA\tOOOOxx\n8683\t8867\t1\t3\t3\t3\t83\t683\t683\t3683\t8683\t166\t167\tZVAAAA\tBDNAAA\tVVVVxx\n4763\t8868\t1\t3\t3\t3\t63\t763\t763\t4763\t4763\t126\t127\tFBAAAA\tCDNAAA\tAAAAxx\n4424\t8869\t0\t0\t4\t4\t24\t424\t424\t4424\t4424\t48\t49\tEOAAAA\tDDNAAA\tHHHHxx\n8640\t8870\t0\t0\t0\t0\t40\t640\t640\t3640\t8640\t80\t81\tIUAAAA\tEDNAAA\tOOOOxx\n7187\t8871\t1\t3\t7\t7\t87\t187\t1187\t2187\t7187\t174\t175\tLQAAAA\tFDNAAA\tVVVVxx\n6247\t8872\t1\t3\t7\t7\t47\t247\t247\t1247\t6247\t94\t95\tHGAAAA\tGDNAAA\tAAAAxx\n7340\t8873\t0\t0\t0\t0\t40\t340\t1340\t2340\t7340\t80\t81\tIWAAAA\tHDNAAA\tHHHHxx\n182\t8874\t0\t2\t2\t2\t82\t182\t182\t182\t182\t164\t165\tAHAAAA\tIDNAAA\tOOOOxx\n2948\t8875\t0\t0\t8\t8\t48\t948\t948\t2948\t2948\t96\t97\tKJAAAA\tJDNAAA\tVVVVxx\n9462\t8876\t0\t2\t2\t2\t62\t462\t1462\t4462\t9462\t124\t125\tYZAAAA\tKDNAAA\tAAAAxx\n5997\t8877\t1\t1\t7\t17\t97\t997\t1997\t997\t5997\t194\t195\tRWAAAA\tLDNAAA\tHHHHxx\n5608\t8878\t0\t0\t8\t8\t8\t608\t1608\t608\t5608\t16\t17\tSHAAAA\tMDNAAA\tOOOOxx\n1472\t8879\t0\t0\t2\t12\t72\t472\t1472\t1472\t1472\t144\t145\tQEAAAA\tNDNAAA\tVVVVxx\n277\t8880\t1\t1\t7\t17\t77\t277\t277\t277\t277\t154\t155\tRKAAAA\tODNAAA\tAAAAxx\n4807\t8881\t1\t3\t7\t7\t7\t807\t807\t4807\t4807\t14\t15\tXCAAAA\tPDNAAA\tHHHHxx\n4969\t8882\t1\t1\t9\t9\t69\t969\t969\t4969\t4969\t138\t139\tDJAAAA\tQDNAAA\tOOOOxx\n5611\t8883\t1\t3\t1\t11\t11\t611\t1611\t611\t5611\t22\t23\tVHAAAA\tRDNAAA\tVVVVxx\n372\t8884\t0\t0\t2\t12\t72\t372\t372\t372\t372\t144\t145\tIOAAAA\tSDNAAA\tAAAAxx\n6666\t8885\t0\t2\t6\t6\t66\t666\t666\t1666\t6666\t132\t133\tKWAAAA\tTDNAAA\tHHHHxx\n476\t8886\t0\t0\t6\t16\t76\t476\t476\t476\t476\t152\t153\tISAAAA\tUDNAAA\tOOOOxx\n5225\t8887\t1\t1\t5\t5\t25\t225\t1225\t225\t5225\t50\t51\tZSAAAA\tVDNAAA\tVVVVxx\n5143\t8888\t1\t3\t3\t3\t43\t143\t1143\t143\t5143\t86\t87\tVPAAAA\tWDNAAA\tAAAAxx\n1853\t8889\t1\t1\t3\t13\t53\t853\t1853\t1853\t1853\t106\t107\tHTAAAA\tXDNAAA\tHHHHxx\n675\t8890\t1\t3\t5\t15\t75\t675\t675\t675\t675\t150\t151\tZZAAAA\tYDNAAA\tOOOOxx\n5643\t8891\t1\t3\t3\t3\t43\t643\t1643\t643\t5643\t86\t87\tBJAAAA\tZDNAAA\tVVVVxx\n5317\t8892\t1\t1\t7\t17\t17\t317\t1317\t317\t5317\t34\t35\tNWAAAA\tAENAAA\tAAAAxx\n8102\t8893\t0\t2\t2\t2\t2\t102\t102\t3102\t8102\t4\t5\tQZAAAA\tBENAAA\tHHHHxx\n978\t8894\t0\t2\t8\t18\t78\t978\t978\t978\t978\t156\t157\tQLAAAA\tCENAAA\tOOOOxx\n4620\t8895\t0\t0\t0\t0\t20\t620\t620\t4620\t4620\t40\t41\tSVAAAA\tDENAAA\tVVVVxx\n151\t8896\t1\t3\t1\t11\t51\t151\t151\t151\t151\t102\t103\tVFAAAA\tEENAAA\tAAAAxx\n972\t8897\t0\t0\t2\t12\t72\t972\t972\t972\t972\t144\t145\tKLAAAA\tFENAAA\tHHHHxx\n6820\t8898\t0\t0\t0\t0\t20\t820\t820\t1820\t6820\t40\t41\tICAAAA\tGENAAA\tOOOOxx\n7387\t8899\t1\t3\t7\t7\t87\t387\t1387\t2387\t7387\t174\t175\tDYAAAA\tHENAAA\tVVVVxx\n9634\t8900\t0\t2\t4\t14\t34\t634\t1634\t4634\t9634\t68\t69\tOGAAAA\tIENAAA\tAAAAxx\n6308\t8901\t0\t0\t8\t8\t8\t308\t308\t1308\t6308\t16\t17\tQIAAAA\tJENAAA\tHHHHxx\n8323\t8902\t1\t3\t3\t3\t23\t323\t323\t3323\t8323\t46\t47\tDIAAAA\tKENAAA\tOOOOxx\n6672\t8903\t0\t0\t2\t12\t72\t672\t672\t1672\t6672\t144\t145\tQWAAAA\tLENAAA\tVVVVxx\n8283\t8904\t1\t3\t3\t3\t83\t283\t283\t3283\t8283\t166\t167\tPGAAAA\tMENAAA\tAAAAxx\n7996\t8905\t0\t0\t6\t16\t96\t996\t1996\t2996\t7996\t192\t193\tOVAAAA\tNENAAA\tHHHHxx\n6488\t8906\t0\t0\t8\t8\t88\t488\t488\t1488\t6488\t176\t177\tOPAAAA\tOENAAA\tOOOOxx\n2365\t8907\t1\t1\t5\t5\t65\t365\t365\t2365\t2365\t130\t131\tZMAAAA\tPENAAA\tVVVVxx\n9746\t8908\t0\t2\t6\t6\t46\t746\t1746\t4746\t9746\t92\t93\tWKAAAA\tQENAAA\tAAAAxx\n8605\t8909\t1\t1\t5\t5\t5\t605\t605\t3605\t8605\t10\t11\tZSAAAA\tRENAAA\tHHHHxx\n3342\t8910\t0\t2\t2\t2\t42\t342\t1342\t3342\t3342\t84\t85\tOYAAAA\tSENAAA\tOOOOxx\n8429\t8911\t1\t1\t9\t9\t29\t429\t429\t3429\t8429\t58\t59\tFMAAAA\tTENAAA\tVVVVxx\n1162\t8912\t0\t2\t2\t2\t62\t162\t1162\t1162\t1162\t124\t125\tSSAAAA\tUENAAA\tAAAAxx\n531\t8913\t1\t3\t1\t11\t31\t531\t531\t531\t531\t62\t63\tLUAAAA\tVENAAA\tHHHHxx\n8408\t8914\t0\t0\t8\t8\t8\t408\t408\t3408\t8408\t16\t17\tKLAAAA\tWENAAA\tOOOOxx\n8862\t8915\t0\t2\t2\t2\t62\t862\t862\t3862\t8862\t124\t125\tWCAAAA\tXENAAA\tVVVVxx\n5843\t8916\t1\t3\t3\t3\t43\t843\t1843\t843\t5843\t86\t87\tTQAAAA\tYENAAA\tAAAAxx\n8704\t8917\t0\t0\t4\t4\t4\t704\t704\t3704\t8704\t8\t9\tUWAAAA\tZENAAA\tHHHHxx\n7070\t8918\t0\t2\t0\t10\t70\t70\t1070\t2070\t7070\t140\t141\tYLAAAA\tAFNAAA\tOOOOxx\n9119\t8919\t1\t3\t9\t19\t19\t119\t1119\t4119\t9119\t38\t39\tTMAAAA\tBFNAAA\tVVVVxx\n8344\t8920\t0\t0\t4\t4\t44\t344\t344\t3344\t8344\t88\t89\tYIAAAA\tCFNAAA\tAAAAxx\n8979\t8921\t1\t3\t9\t19\t79\t979\t979\t3979\t8979\t158\t159\tJHAAAA\tDFNAAA\tHHHHxx\n2971\t8922\t1\t3\t1\t11\t71\t971\t971\t2971\t2971\t142\t143\tHKAAAA\tEFNAAA\tOOOOxx\n7700\t8923\t0\t0\t0\t0\t0\t700\t1700\t2700\t7700\t0\t1\tEKAAAA\tFFNAAA\tVVVVxx\n8280\t8924\t0\t0\t0\t0\t80\t280\t280\t3280\t8280\t160\t161\tMGAAAA\tGFNAAA\tAAAAxx\n9096\t8925\t0\t0\t6\t16\t96\t96\t1096\t4096\t9096\t192\t193\tWLAAAA\tHFNAAA\tHHHHxx\n99\t8926\t1\t3\t9\t19\t99\t99\t99\t99\t99\t198\t199\tVDAAAA\tIFNAAA\tOOOOxx\n6696\t8927\t0\t0\t6\t16\t96\t696\t696\t1696\t6696\t192\t193\tOXAAAA\tJFNAAA\tVVVVxx\n9490\t8928\t0\t2\t0\t10\t90\t490\t1490\t4490\t9490\t180\t181\tABAAAA\tKFNAAA\tAAAAxx\n9073\t8929\t1\t1\t3\t13\t73\t73\t1073\t4073\t9073\t146\t147\tZKAAAA\tLFNAAA\tHHHHxx\n1861\t8930\t1\t1\t1\t1\t61\t861\t1861\t1861\t1861\t122\t123\tPTAAAA\tMFNAAA\tOOOOxx\n4413\t8931\t1\t1\t3\t13\t13\t413\t413\t4413\t4413\t26\t27\tTNAAAA\tNFNAAA\tVVVVxx\n6002\t8932\t0\t2\t2\t2\t2\t2\t2\t1002\t6002\t4\t5\tWWAAAA\tOFNAAA\tAAAAxx\n439\t8933\t1\t3\t9\t19\t39\t439\t439\t439\t439\t78\t79\tXQAAAA\tPFNAAA\tHHHHxx\n5449\t8934\t1\t1\t9\t9\t49\t449\t1449\t449\t5449\t98\t99\tPBAAAA\tQFNAAA\tOOOOxx\n9737\t8935\t1\t1\t7\t17\t37\t737\t1737\t4737\t9737\t74\t75\tNKAAAA\tRFNAAA\tVVVVxx\n1898\t8936\t0\t2\t8\t18\t98\t898\t1898\t1898\t1898\t196\t197\tAVAAAA\tSFNAAA\tAAAAxx\n4189\t8937\t1\t1\t9\t9\t89\t189\t189\t4189\t4189\t178\t179\tDFAAAA\tTFNAAA\tHHHHxx\n1408\t8938\t0\t0\t8\t8\t8\t408\t1408\t1408\t1408\t16\t17\tECAAAA\tUFNAAA\tOOOOxx\n394\t8939\t0\t2\t4\t14\t94\t394\t394\t394\t394\t188\t189\tEPAAAA\tVFNAAA\tVVVVxx\n1935\t8940\t1\t3\t5\t15\t35\t935\t1935\t1935\t1935\t70\t71\tLWAAAA\tWFNAAA\tAAAAxx\n3965\t8941\t1\t1\t5\t5\t65\t965\t1965\t3965\t3965\t130\t131\tNWAAAA\tXFNAAA\tHHHHxx\n6821\t8942\t1\t1\t1\t1\t21\t821\t821\t1821\t6821\t42\t43\tJCAAAA\tYFNAAA\tOOOOxx\n349\t8943\t1\t1\t9\t9\t49\t349\t349\t349\t349\t98\t99\tLNAAAA\tZFNAAA\tVVVVxx\n8428\t8944\t0\t0\t8\t8\t28\t428\t428\t3428\t8428\t56\t57\tEMAAAA\tAGNAAA\tAAAAxx\n8200\t8945\t0\t0\t0\t0\t0\t200\t200\t3200\t8200\t0\t1\tKDAAAA\tBGNAAA\tHHHHxx\n1737\t8946\t1\t1\t7\t17\t37\t737\t1737\t1737\t1737\t74\t75\tVOAAAA\tCGNAAA\tOOOOxx\n6516\t8947\t0\t0\t6\t16\t16\t516\t516\t1516\t6516\t32\t33\tQQAAAA\tDGNAAA\tVVVVxx\n5441\t8948\t1\t1\t1\t1\t41\t441\t1441\t441\t5441\t82\t83\tHBAAAA\tEGNAAA\tAAAAxx\n5999\t8949\t1\t3\t9\t19\t99\t999\t1999\t999\t5999\t198\t199\tTWAAAA\tFGNAAA\tHHHHxx\n1539\t8950\t1\t3\t9\t19\t39\t539\t1539\t1539\t1539\t78\t79\tFHAAAA\tGGNAAA\tOOOOxx\n9067\t8951\t1\t3\t7\t7\t67\t67\t1067\t4067\t9067\t134\t135\tTKAAAA\tHGNAAA\tVVVVxx\n4061\t8952\t1\t1\t1\t1\t61\t61\t61\t4061\t4061\t122\t123\tFAAAAA\tIGNAAA\tAAAAxx\n1642\t8953\t0\t2\t2\t2\t42\t642\t1642\t1642\t1642\t84\t85\tELAAAA\tJGNAAA\tHHHHxx\n4657\t8954\t1\t1\t7\t17\t57\t657\t657\t4657\t4657\t114\t115\tDXAAAA\tKGNAAA\tOOOOxx\n9934\t8955\t0\t2\t4\t14\t34\t934\t1934\t4934\t9934\t68\t69\tCSAAAA\tLGNAAA\tVVVVxx\n6385\t8956\t1\t1\t5\t5\t85\t385\t385\t1385\t6385\t170\t171\tPLAAAA\tMGNAAA\tAAAAxx\n6775\t8957\t1\t3\t5\t15\t75\t775\t775\t1775\t6775\t150\t151\tPAAAAA\tNGNAAA\tHHHHxx\n3873\t8958\t1\t1\t3\t13\t73\t873\t1873\t3873\t3873\t146\t147\tZSAAAA\tOGNAAA\tOOOOxx\n3862\t8959\t0\t2\t2\t2\t62\t862\t1862\t3862\t3862\t124\t125\tOSAAAA\tPGNAAA\tVVVVxx\n1224\t8960\t0\t0\t4\t4\t24\t224\t1224\t1224\t1224\t48\t49\tCVAAAA\tQGNAAA\tAAAAxx\n4483\t8961\t1\t3\t3\t3\t83\t483\t483\t4483\t4483\t166\t167\tLQAAAA\tRGNAAA\tHHHHxx\n3685\t8962\t1\t1\t5\t5\t85\t685\t1685\t3685\t3685\t170\t171\tTLAAAA\tSGNAAA\tOOOOxx\n6082\t8963\t0\t2\t2\t2\t82\t82\t82\t1082\t6082\t164\t165\tYZAAAA\tTGNAAA\tVVVVxx\n7798\t8964\t0\t2\t8\t18\t98\t798\t1798\t2798\t7798\t196\t197\tYNAAAA\tUGNAAA\tAAAAxx\n9039\t8965\t1\t3\t9\t19\t39\t39\t1039\t4039\t9039\t78\t79\tRJAAAA\tVGNAAA\tHHHHxx\n985\t8966\t1\t1\t5\t5\t85\t985\t985\t985\t985\t170\t171\tXLAAAA\tWGNAAA\tOOOOxx\n5389\t8967\t1\t1\t9\t9\t89\t389\t1389\t389\t5389\t178\t179\tHZAAAA\tXGNAAA\tVVVVxx\n1716\t8968\t0\t0\t6\t16\t16\t716\t1716\t1716\t1716\t32\t33\tAOAAAA\tYGNAAA\tAAAAxx\n4209\t8969\t1\t1\t9\t9\t9\t209\t209\t4209\t4209\t18\t19\tXFAAAA\tZGNAAA\tHHHHxx\n746\t8970\t0\t2\t6\t6\t46\t746\t746\t746\t746\t92\t93\tSCAAAA\tAHNAAA\tOOOOxx\n6295\t8971\t1\t3\t5\t15\t95\t295\t295\t1295\t6295\t190\t191\tDIAAAA\tBHNAAA\tVVVVxx\n9754\t8972\t0\t2\t4\t14\t54\t754\t1754\t4754\t9754\t108\t109\tELAAAA\tCHNAAA\tAAAAxx\n2336\t8973\t0\t0\t6\t16\t36\t336\t336\t2336\t2336\t72\t73\tWLAAAA\tDHNAAA\tHHHHxx\n3701\t8974\t1\t1\t1\t1\t1\t701\t1701\t3701\t3701\t2\t3\tJMAAAA\tEHNAAA\tOOOOxx\n3551\t8975\t1\t3\t1\t11\t51\t551\t1551\t3551\t3551\t102\t103\tPGAAAA\tFHNAAA\tVVVVxx\n8516\t8976\t0\t0\t6\t16\t16\t516\t516\t3516\t8516\t32\t33\tOPAAAA\tGHNAAA\tAAAAxx\n9290\t8977\t0\t2\t0\t10\t90\t290\t1290\t4290\t9290\t180\t181\tITAAAA\tHHNAAA\tHHHHxx\n5686\t8978\t0\t2\t6\t6\t86\t686\t1686\t686\t5686\t172\t173\tSKAAAA\tIHNAAA\tOOOOxx\n2893\t8979\t1\t1\t3\t13\t93\t893\t893\t2893\t2893\t186\t187\tHHAAAA\tJHNAAA\tVVVVxx\n6279\t8980\t1\t3\t9\t19\t79\t279\t279\t1279\t6279\t158\t159\tNHAAAA\tKHNAAA\tAAAAxx\n2278\t8981\t0\t2\t8\t18\t78\t278\t278\t2278\t2278\t156\t157\tQJAAAA\tLHNAAA\tHHHHxx\n1618\t8982\t0\t2\t8\t18\t18\t618\t1618\t1618\t1618\t36\t37\tGKAAAA\tMHNAAA\tOOOOxx\n3450\t8983\t0\t2\t0\t10\t50\t450\t1450\t3450\t3450\t100\t101\tSCAAAA\tNHNAAA\tVVVVxx\n8857\t8984\t1\t1\t7\t17\t57\t857\t857\t3857\t8857\t114\t115\tRCAAAA\tOHNAAA\tAAAAxx\n1005\t8985\t1\t1\t5\t5\t5\t5\t1005\t1005\t1005\t10\t11\tRMAAAA\tPHNAAA\tHHHHxx\n4727\t8986\t1\t3\t7\t7\t27\t727\t727\t4727\t4727\t54\t55\tVZAAAA\tQHNAAA\tOOOOxx\n7617\t8987\t1\t1\t7\t17\t17\t617\t1617\t2617\t7617\t34\t35\tZGAAAA\tRHNAAA\tVVVVxx\n2021\t8988\t1\t1\t1\t1\t21\t21\t21\t2021\t2021\t42\t43\tTZAAAA\tSHNAAA\tAAAAxx\n9124\t8989\t0\t0\t4\t4\t24\t124\t1124\t4124\t9124\t48\t49\tYMAAAA\tTHNAAA\tHHHHxx\n3175\t8990\t1\t3\t5\t15\t75\t175\t1175\t3175\t3175\t150\t151\tDSAAAA\tUHNAAA\tOOOOxx\n2949\t8991\t1\t1\t9\t9\t49\t949\t949\t2949\t2949\t98\t99\tLJAAAA\tVHNAAA\tVVVVxx\n2424\t8992\t0\t0\t4\t4\t24\t424\t424\t2424\t2424\t48\t49\tGPAAAA\tWHNAAA\tAAAAxx\n4791\t8993\t1\t3\t1\t11\t91\t791\t791\t4791\t4791\t182\t183\tHCAAAA\tXHNAAA\tHHHHxx\n7500\t8994\t0\t0\t0\t0\t0\t500\t1500\t2500\t7500\t0\t1\tMCAAAA\tYHNAAA\tOOOOxx\n4893\t8995\t1\t1\t3\t13\t93\t893\t893\t4893\t4893\t186\t187\tFGAAAA\tZHNAAA\tVVVVxx\n121\t8996\t1\t1\t1\t1\t21\t121\t121\t121\t121\t42\t43\tREAAAA\tAINAAA\tAAAAxx\n1965\t8997\t1\t1\t5\t5\t65\t965\t1965\t1965\t1965\t130\t131\tPXAAAA\tBINAAA\tHHHHxx\n2972\t8998\t0\t0\t2\t12\t72\t972\t972\t2972\t2972\t144\t145\tIKAAAA\tCINAAA\tOOOOxx\n662\t8999\t0\t2\t2\t2\t62\t662\t662\t662\t662\t124\t125\tMZAAAA\tDINAAA\tVVVVxx\n7074\t9000\t0\t2\t4\t14\t74\t74\t1074\t2074\t7074\t148\t149\tCMAAAA\tEINAAA\tAAAAxx\n981\t9001\t1\t1\t1\t1\t81\t981\t981\t981\t981\t162\t163\tTLAAAA\tFINAAA\tHHHHxx\n3520\t9002\t0\t0\t0\t0\t20\t520\t1520\t3520\t3520\t40\t41\tKFAAAA\tGINAAA\tOOOOxx\n6540\t9003\t0\t0\t0\t0\t40\t540\t540\t1540\t6540\t80\t81\tORAAAA\tHINAAA\tVVVVxx\n6648\t9004\t0\t0\t8\t8\t48\t648\t648\t1648\t6648\t96\t97\tSVAAAA\tIINAAA\tAAAAxx\n7076\t9005\t0\t0\t6\t16\t76\t76\t1076\t2076\t7076\t152\t153\tEMAAAA\tJINAAA\tHHHHxx\n6919\t9006\t1\t3\t9\t19\t19\t919\t919\t1919\t6919\t38\t39\tDGAAAA\tKINAAA\tOOOOxx\n1108\t9007\t0\t0\t8\t8\t8\t108\t1108\t1108\t1108\t16\t17\tQQAAAA\tLINAAA\tVVVVxx\n317\t9008\t1\t1\t7\t17\t17\t317\t317\t317\t317\t34\t35\tFMAAAA\tMINAAA\tAAAAxx\n3483\t9009\t1\t3\t3\t3\t83\t483\t1483\t3483\t3483\t166\t167\tZDAAAA\tNINAAA\tHHHHxx\n6764\t9010\t0\t0\t4\t4\t64\t764\t764\t1764\t6764\t128\t129\tEAAAAA\tOINAAA\tOOOOxx\n1235\t9011\t1\t3\t5\t15\t35\t235\t1235\t1235\t1235\t70\t71\tNVAAAA\tPINAAA\tVVVVxx\n7121\t9012\t1\t1\t1\t1\t21\t121\t1121\t2121\t7121\t42\t43\tXNAAAA\tQINAAA\tAAAAxx\n426\t9013\t0\t2\t6\t6\t26\t426\t426\t426\t426\t52\t53\tKQAAAA\tRINAAA\tHHHHxx\n6880\t9014\t0\t0\t0\t0\t80\t880\t880\t1880\t6880\t160\t161\tQEAAAA\tSINAAA\tOOOOxx\n5401\t9015\t1\t1\t1\t1\t1\t401\t1401\t401\t5401\t2\t3\tTZAAAA\tTINAAA\tVVVVxx\n7323\t9016\t1\t3\t3\t3\t23\t323\t1323\t2323\t7323\t46\t47\tRVAAAA\tUINAAA\tAAAAxx\n9751\t9017\t1\t3\t1\t11\t51\t751\t1751\t4751\t9751\t102\t103\tBLAAAA\tVINAAA\tHHHHxx\n3436\t9018\t0\t0\t6\t16\t36\t436\t1436\t3436\t3436\t72\t73\tECAAAA\tWINAAA\tOOOOxx\n7319\t9019\t1\t3\t9\t19\t19\t319\t1319\t2319\t7319\t38\t39\tNVAAAA\tXINAAA\tVVVVxx\n7882\t9020\t0\t2\t2\t2\t82\t882\t1882\t2882\t7882\t164\t165\tERAAAA\tYINAAA\tAAAAxx\n8260\t9021\t0\t0\t0\t0\t60\t260\t260\t3260\t8260\t120\t121\tSFAAAA\tZINAAA\tHHHHxx\n9758\t9022\t0\t2\t8\t18\t58\t758\t1758\t4758\t9758\t116\t117\tILAAAA\tAJNAAA\tOOOOxx\n4205\t9023\t1\t1\t5\t5\t5\t205\t205\t4205\t4205\t10\t11\tTFAAAA\tBJNAAA\tVVVVxx\n8884\t9024\t0\t0\t4\t4\t84\t884\t884\t3884\t8884\t168\t169\tSDAAAA\tCJNAAA\tAAAAxx\n1112\t9025\t0\t0\t2\t12\t12\t112\t1112\t1112\t1112\t24\t25\tUQAAAA\tDJNAAA\tHHHHxx\n2186\t9026\t0\t2\t6\t6\t86\t186\t186\t2186\t2186\t172\t173\tCGAAAA\tEJNAAA\tOOOOxx\n8666\t9027\t0\t2\t6\t6\t66\t666\t666\t3666\t8666\t132\t133\tIVAAAA\tFJNAAA\tVVVVxx\n4325\t9028\t1\t1\t5\t5\t25\t325\t325\t4325\t4325\t50\t51\tJKAAAA\tGJNAAA\tAAAAxx\n4912\t9029\t0\t0\t2\t12\t12\t912\t912\t4912\t4912\t24\t25\tYGAAAA\tHJNAAA\tHHHHxx\n6497\t9030\t1\t1\t7\t17\t97\t497\t497\t1497\t6497\t194\t195\tXPAAAA\tIJNAAA\tOOOOxx\n9072\t9031\t0\t0\t2\t12\t72\t72\t1072\t4072\t9072\t144\t145\tYKAAAA\tJJNAAA\tVVVVxx\n8899\t9032\t1\t3\t9\t19\t99\t899\t899\t3899\t8899\t198\t199\tHEAAAA\tKJNAAA\tAAAAxx\n5619\t9033\t1\t3\t9\t19\t19\t619\t1619\t619\t5619\t38\t39\tDIAAAA\tLJNAAA\tHHHHxx\n4110\t9034\t0\t2\t0\t10\t10\t110\t110\t4110\t4110\t20\t21\tCCAAAA\tMJNAAA\tOOOOxx\n7025\t9035\t1\t1\t5\t5\t25\t25\t1025\t2025\t7025\t50\t51\tFKAAAA\tNJNAAA\tVVVVxx\n5605\t9036\t1\t1\t5\t5\t5\t605\t1605\t605\t5605\t10\t11\tPHAAAA\tOJNAAA\tAAAAxx\n2572\t9037\t0\t0\t2\t12\t72\t572\t572\t2572\t2572\t144\t145\tYUAAAA\tPJNAAA\tHHHHxx\n3895\t9038\t1\t3\t5\t15\t95\t895\t1895\t3895\t3895\t190\t191\tVTAAAA\tQJNAAA\tOOOOxx\n9138\t9039\t0\t2\t8\t18\t38\t138\t1138\t4138\t9138\t76\t77\tMNAAAA\tRJNAAA\tVVVVxx\n4713\t9040\t1\t1\t3\t13\t13\t713\t713\t4713\t4713\t26\t27\tHZAAAA\tSJNAAA\tAAAAxx\n6079\t9041\t1\t3\t9\t19\t79\t79\t79\t1079\t6079\t158\t159\tVZAAAA\tTJNAAA\tHHHHxx\n8898\t9042\t0\t2\t8\t18\t98\t898\t898\t3898\t8898\t196\t197\tGEAAAA\tUJNAAA\tOOOOxx\n2650\t9043\t0\t2\t0\t10\t50\t650\t650\t2650\t2650\t100\t101\tYXAAAA\tVJNAAA\tVVVVxx\n5316\t9044\t0\t0\t6\t16\t16\t316\t1316\t316\t5316\t32\t33\tMWAAAA\tWJNAAA\tAAAAxx\n5133\t9045\t1\t1\t3\t13\t33\t133\t1133\t133\t5133\t66\t67\tLPAAAA\tXJNAAA\tHHHHxx\n2184\t9046\t0\t0\t4\t4\t84\t184\t184\t2184\t2184\t168\t169\tAGAAAA\tYJNAAA\tOOOOxx\n2728\t9047\t0\t0\t8\t8\t28\t728\t728\t2728\t2728\t56\t57\tYAAAAA\tZJNAAA\tVVVVxx\n6737\t9048\t1\t1\t7\t17\t37\t737\t737\t1737\t6737\t74\t75\tDZAAAA\tAKNAAA\tAAAAxx\n1128\t9049\t0\t0\t8\t8\t28\t128\t1128\t1128\t1128\t56\t57\tKRAAAA\tBKNAAA\tHHHHxx\n9662\t9050\t0\t2\t2\t2\t62\t662\t1662\t4662\t9662\t124\t125\tQHAAAA\tCKNAAA\tOOOOxx\n9384\t9051\t0\t0\t4\t4\t84\t384\t1384\t4384\t9384\t168\t169\tYWAAAA\tDKNAAA\tVVVVxx\n4576\t9052\t0\t0\t6\t16\t76\t576\t576\t4576\t4576\t152\t153\tAUAAAA\tEKNAAA\tAAAAxx\n9613\t9053\t1\t1\t3\t13\t13\t613\t1613\t4613\t9613\t26\t27\tTFAAAA\tFKNAAA\tHHHHxx\n4001\t9054\t1\t1\t1\t1\t1\t1\t1\t4001\t4001\t2\t3\tXXAAAA\tGKNAAA\tOOOOxx\n3628\t9055\t0\t0\t8\t8\t28\t628\t1628\t3628\t3628\t56\t57\tOJAAAA\tHKNAAA\tVVVVxx\n6968\t9056\t0\t0\t8\t8\t68\t968\t968\t1968\t6968\t136\t137\tAIAAAA\tIKNAAA\tAAAAxx\n6491\t9057\t1\t3\t1\t11\t91\t491\t491\t1491\t6491\t182\t183\tRPAAAA\tJKNAAA\tHHHHxx\n1265\t9058\t1\t1\t5\t5\t65\t265\t1265\t1265\t1265\t130\t131\tRWAAAA\tKKNAAA\tOOOOxx\n6128\t9059\t0\t0\t8\t8\t28\t128\t128\t1128\t6128\t56\t57\tSBAAAA\tLKNAAA\tVVVVxx\n4274\t9060\t0\t2\t4\t14\t74\t274\t274\t4274\t4274\t148\t149\tKIAAAA\tMKNAAA\tAAAAxx\n3598\t9061\t0\t2\t8\t18\t98\t598\t1598\t3598\t3598\t196\t197\tKIAAAA\tNKNAAA\tHHHHxx\n7961\t9062\t1\t1\t1\t1\t61\t961\t1961\t2961\t7961\t122\t123\tFUAAAA\tOKNAAA\tOOOOxx\n2643\t9063\t1\t3\t3\t3\t43\t643\t643\t2643\t2643\t86\t87\tRXAAAA\tPKNAAA\tVVVVxx\n4547\t9064\t1\t3\t7\t7\t47\t547\t547\t4547\t4547\t94\t95\tXSAAAA\tQKNAAA\tAAAAxx\n3568\t9065\t0\t0\t8\t8\t68\t568\t1568\t3568\t3568\t136\t137\tGHAAAA\tRKNAAA\tHHHHxx\n8954\t9066\t0\t2\t4\t14\t54\t954\t954\t3954\t8954\t108\t109\tKGAAAA\tSKNAAA\tOOOOxx\n8802\t9067\t0\t2\t2\t2\t2\t802\t802\t3802\t8802\t4\t5\tOAAAAA\tTKNAAA\tVVVVxx\n7829\t9068\t1\t1\t9\t9\t29\t829\t1829\t2829\t7829\t58\t59\tDPAAAA\tUKNAAA\tAAAAxx\n1008\t9069\t0\t0\t8\t8\t8\t8\t1008\t1008\t1008\t16\t17\tUMAAAA\tVKNAAA\tHHHHxx\n3627\t9070\t1\t3\t7\t7\t27\t627\t1627\t3627\t3627\t54\t55\tNJAAAA\tWKNAAA\tOOOOxx\n3999\t9071\t1\t3\t9\t19\t99\t999\t1999\t3999\t3999\t198\t199\tVXAAAA\tXKNAAA\tVVVVxx\n7697\t9072\t1\t1\t7\t17\t97\t697\t1697\t2697\t7697\t194\t195\tBKAAAA\tYKNAAA\tAAAAxx\n9380\t9073\t0\t0\t0\t0\t80\t380\t1380\t4380\t9380\t160\t161\tUWAAAA\tZKNAAA\tHHHHxx\n2707\t9074\t1\t3\t7\t7\t7\t707\t707\t2707\t2707\t14\t15\tDAAAAA\tALNAAA\tOOOOxx\n4430\t9075\t0\t2\t0\t10\t30\t430\t430\t4430\t4430\t60\t61\tKOAAAA\tBLNAAA\tVVVVxx\n6440\t9076\t0\t0\t0\t0\t40\t440\t440\t1440\t6440\t80\t81\tSNAAAA\tCLNAAA\tAAAAxx\n9958\t9077\t0\t2\t8\t18\t58\t958\t1958\t4958\t9958\t116\t117\tATAAAA\tDLNAAA\tHHHHxx\n7592\t9078\t0\t0\t2\t12\t92\t592\t1592\t2592\t7592\t184\t185\tAGAAAA\tELNAAA\tOOOOxx\n7852\t9079\t0\t0\t2\t12\t52\t852\t1852\t2852\t7852\t104\t105\tAQAAAA\tFLNAAA\tVVVVxx\n9253\t9080\t1\t1\t3\t13\t53\t253\t1253\t4253\t9253\t106\t107\tXRAAAA\tGLNAAA\tAAAAxx\n5910\t9081\t0\t2\t0\t10\t10\t910\t1910\t910\t5910\t20\t21\tITAAAA\tHLNAAA\tHHHHxx\n7487\t9082\t1\t3\t7\t7\t87\t487\t1487\t2487\t7487\t174\t175\tZBAAAA\tILNAAA\tOOOOxx\n6324\t9083\t0\t0\t4\t4\t24\t324\t324\t1324\t6324\t48\t49\tGJAAAA\tJLNAAA\tVVVVxx\n5792\t9084\t0\t0\t2\t12\t92\t792\t1792\t792\t5792\t184\t185\tUOAAAA\tKLNAAA\tAAAAxx\n7390\t9085\t0\t2\t0\t10\t90\t390\t1390\t2390\t7390\t180\t181\tGYAAAA\tLLNAAA\tHHHHxx\n8534\t9086\t0\t2\t4\t14\t34\t534\t534\t3534\t8534\t68\t69\tGQAAAA\tMLNAAA\tOOOOxx\n2690\t9087\t0\t2\t0\t10\t90\t690\t690\t2690\t2690\t180\t181\tMZAAAA\tNLNAAA\tVVVVxx\n3992\t9088\t0\t0\t2\t12\t92\t992\t1992\t3992\t3992\t184\t185\tOXAAAA\tOLNAAA\tAAAAxx\n6928\t9089\t0\t0\t8\t8\t28\t928\t928\t1928\t6928\t56\t57\tMGAAAA\tPLNAAA\tHHHHxx\n7815\t9090\t1\t3\t5\t15\t15\t815\t1815\t2815\t7815\t30\t31\tPOAAAA\tQLNAAA\tOOOOxx\n9477\t9091\t1\t1\t7\t17\t77\t477\t1477\t4477\t9477\t154\t155\tNAAAAA\tRLNAAA\tVVVVxx\n497\t9092\t1\t1\t7\t17\t97\t497\t497\t497\t497\t194\t195\tDTAAAA\tSLNAAA\tAAAAxx\n7532\t9093\t0\t0\t2\t12\t32\t532\t1532\t2532\t7532\t64\t65\tSDAAAA\tTLNAAA\tHHHHxx\n9838\t9094\t0\t2\t8\t18\t38\t838\t1838\t4838\t9838\t76\t77\tKOAAAA\tULNAAA\tOOOOxx\n1557\t9095\t1\t1\t7\t17\t57\t557\t1557\t1557\t1557\t114\t115\tXHAAAA\tVLNAAA\tVVVVxx\n2467\t9096\t1\t3\t7\t7\t67\t467\t467\t2467\t2467\t134\t135\tXQAAAA\tWLNAAA\tAAAAxx\n2367\t9097\t1\t3\t7\t7\t67\t367\t367\t2367\t2367\t134\t135\tBNAAAA\tXLNAAA\tHHHHxx\n5677\t9098\t1\t1\t7\t17\t77\t677\t1677\t677\t5677\t154\t155\tJKAAAA\tYLNAAA\tOOOOxx\n6193\t9099\t1\t1\t3\t13\t93\t193\t193\t1193\t6193\t186\t187\tFEAAAA\tZLNAAA\tVVVVxx\n7126\t9100\t0\t2\t6\t6\t26\t126\t1126\t2126\t7126\t52\t53\tCOAAAA\tAMNAAA\tAAAAxx\n5264\t9101\t0\t0\t4\t4\t64\t264\t1264\t264\t5264\t128\t129\tMUAAAA\tBMNAAA\tHHHHxx\n850\t9102\t0\t2\t0\t10\t50\t850\t850\t850\t850\t100\t101\tSGAAAA\tCMNAAA\tOOOOxx\n4854\t9103\t0\t2\t4\t14\t54\t854\t854\t4854\t4854\t108\t109\tSEAAAA\tDMNAAA\tVVVVxx\n4414\t9104\t0\t2\t4\t14\t14\t414\t414\t4414\t4414\t28\t29\tUNAAAA\tEMNAAA\tAAAAxx\n8971\t9105\t1\t3\t1\t11\t71\t971\t971\t3971\t8971\t142\t143\tBHAAAA\tFMNAAA\tHHHHxx\n9240\t9106\t0\t0\t0\t0\t40\t240\t1240\t4240\t9240\t80\t81\tKRAAAA\tGMNAAA\tOOOOxx\n7341\t9107\t1\t1\t1\t1\t41\t341\t1341\t2341\t7341\t82\t83\tJWAAAA\tHMNAAA\tVVVVxx\n3151\t9108\t1\t3\t1\t11\t51\t151\t1151\t3151\t3151\t102\t103\tFRAAAA\tIMNAAA\tAAAAxx\n1742\t9109\t0\t2\t2\t2\t42\t742\t1742\t1742\t1742\t84\t85\tAPAAAA\tJMNAAA\tHHHHxx\n1347\t9110\t1\t3\t7\t7\t47\t347\t1347\t1347\t1347\t94\t95\tVZAAAA\tKMNAAA\tOOOOxx\n9418\t9111\t0\t2\t8\t18\t18\t418\t1418\t4418\t9418\t36\t37\tGYAAAA\tLMNAAA\tVVVVxx\n5452\t9112\t0\t0\t2\t12\t52\t452\t1452\t452\t5452\t104\t105\tSBAAAA\tMMNAAA\tAAAAxx\n8637\t9113\t1\t1\t7\t17\t37\t637\t637\t3637\t8637\t74\t75\tFUAAAA\tNMNAAA\tHHHHxx\n8287\t9114\t1\t3\t7\t7\t87\t287\t287\t3287\t8287\t174\t175\tTGAAAA\tOMNAAA\tOOOOxx\n9865\t9115\t1\t1\t5\t5\t65\t865\t1865\t4865\t9865\t130\t131\tLPAAAA\tPMNAAA\tVVVVxx\n1664\t9116\t0\t0\t4\t4\t64\t664\t1664\t1664\t1664\t128\t129\tAMAAAA\tQMNAAA\tAAAAxx\n9933\t9117\t1\t1\t3\t13\t33\t933\t1933\t4933\t9933\t66\t67\tBSAAAA\tRMNAAA\tHHHHxx\n3416\t9118\t0\t0\t6\t16\t16\t416\t1416\t3416\t3416\t32\t33\tKBAAAA\tSMNAAA\tOOOOxx\n7981\t9119\t1\t1\t1\t1\t81\t981\t1981\t2981\t7981\t162\t163\tZUAAAA\tTMNAAA\tVVVVxx\n1981\t9120\t1\t1\t1\t1\t81\t981\t1981\t1981\t1981\t162\t163\tFYAAAA\tUMNAAA\tAAAAxx\n441\t9121\t1\t1\t1\t1\t41\t441\t441\t441\t441\t82\t83\tZQAAAA\tVMNAAA\tHHHHxx\n1380\t9122\t0\t0\t0\t0\t80\t380\t1380\t1380\t1380\t160\t161\tCBAAAA\tWMNAAA\tOOOOxx\n7325\t9123\t1\t1\t5\t5\t25\t325\t1325\t2325\t7325\t50\t51\tTVAAAA\tXMNAAA\tVVVVxx\n5682\t9124\t0\t2\t2\t2\t82\t682\t1682\t682\t5682\t164\t165\tOKAAAA\tYMNAAA\tAAAAxx\n1024\t9125\t0\t0\t4\t4\t24\t24\t1024\t1024\t1024\t48\t49\tKNAAAA\tZMNAAA\tHHHHxx\n1096\t9126\t0\t0\t6\t16\t96\t96\t1096\t1096\t1096\t192\t193\tEQAAAA\tANNAAA\tOOOOxx\n4717\t9127\t1\t1\t7\t17\t17\t717\t717\t4717\t4717\t34\t35\tLZAAAA\tBNNAAA\tVVVVxx\n7948\t9128\t0\t0\t8\t8\t48\t948\t1948\t2948\t7948\t96\t97\tSTAAAA\tCNNAAA\tAAAAxx\n4074\t9129\t0\t2\t4\t14\t74\t74\t74\t4074\t4074\t148\t149\tSAAAAA\tDNNAAA\tHHHHxx\n211\t9130\t1\t3\t1\t11\t11\t211\t211\t211\t211\t22\t23\tDIAAAA\tENNAAA\tOOOOxx\n8993\t9131\t1\t1\t3\t13\t93\t993\t993\t3993\t8993\t186\t187\tXHAAAA\tFNNAAA\tVVVVxx\n4509\t9132\t1\t1\t9\t9\t9\t509\t509\t4509\t4509\t18\t19\tLRAAAA\tGNNAAA\tAAAAxx\n823\t9133\t1\t3\t3\t3\t23\t823\t823\t823\t823\t46\t47\tRFAAAA\tHNNAAA\tHHHHxx\n4747\t9134\t1\t3\t7\t7\t47\t747\t747\t4747\t4747\t94\t95\tPAAAAA\tINNAAA\tOOOOxx\n6955\t9135\t1\t3\t5\t15\t55\t955\t955\t1955\t6955\t110\t111\tNHAAAA\tJNNAAA\tVVVVxx\n7922\t9136\t0\t2\t2\t2\t22\t922\t1922\t2922\t7922\t44\t45\tSSAAAA\tKNNAAA\tAAAAxx\n6936\t9137\t0\t0\t6\t16\t36\t936\t936\t1936\t6936\t72\t73\tUGAAAA\tLNNAAA\tHHHHxx\n1546\t9138\t0\t2\t6\t6\t46\t546\t1546\t1546\t1546\t92\t93\tMHAAAA\tMNNAAA\tOOOOxx\n9836\t9139\t0\t0\t6\t16\t36\t836\t1836\t4836\t9836\t72\t73\tIOAAAA\tNNNAAA\tVVVVxx\n5626\t9140\t0\t2\t6\t6\t26\t626\t1626\t626\t5626\t52\t53\tKIAAAA\tONNAAA\tAAAAxx\n4879\t9141\t1\t3\t9\t19\t79\t879\t879\t4879\t4879\t158\t159\tRFAAAA\tPNNAAA\tHHHHxx\n8590\t9142\t0\t2\t0\t10\t90\t590\t590\t3590\t8590\t180\t181\tKSAAAA\tQNNAAA\tOOOOxx\n8842\t9143\t0\t2\t2\t2\t42\t842\t842\t3842\t8842\t84\t85\tCCAAAA\tRNNAAA\tVVVVxx\n6505\t9144\t1\t1\t5\t5\t5\t505\t505\t1505\t6505\t10\t11\tFQAAAA\tSNNAAA\tAAAAxx\n2803\t9145\t1\t3\t3\t3\t3\t803\t803\t2803\t2803\t6\t7\tVDAAAA\tTNNAAA\tHHHHxx\n9258\t9146\t0\t2\t8\t18\t58\t258\t1258\t4258\t9258\t116\t117\tCSAAAA\tUNNAAA\tOOOOxx\n741\t9147\t1\t1\t1\t1\t41\t741\t741\t741\t741\t82\t83\tNCAAAA\tVNNAAA\tVVVVxx\n1457\t9148\t1\t1\t7\t17\t57\t457\t1457\t1457\t1457\t114\t115\tBEAAAA\tWNNAAA\tAAAAxx\n5777\t9149\t1\t1\t7\t17\t77\t777\t1777\t777\t5777\t154\t155\tFOAAAA\tXNNAAA\tHHHHxx\n2883\t9150\t1\t3\t3\t3\t83\t883\t883\t2883\t2883\t166\t167\tXGAAAA\tYNNAAA\tOOOOxx\n6610\t9151\t0\t2\t0\t10\t10\t610\t610\t1610\t6610\t20\t21\tGUAAAA\tZNNAAA\tVVVVxx\n4331\t9152\t1\t3\t1\t11\t31\t331\t331\t4331\t4331\t62\t63\tPKAAAA\tAONAAA\tAAAAxx\n2712\t9153\t0\t0\t2\t12\t12\t712\t712\t2712\t2712\t24\t25\tIAAAAA\tBONAAA\tHHHHxx\n9268\t9154\t0\t0\t8\t8\t68\t268\t1268\t4268\t9268\t136\t137\tMSAAAA\tCONAAA\tOOOOxx\n410\t9155\t0\t2\t0\t10\t10\t410\t410\t410\t410\t20\t21\tUPAAAA\tDONAAA\tVVVVxx\n9411\t9156\t1\t3\t1\t11\t11\t411\t1411\t4411\t9411\t22\t23\tZXAAAA\tEONAAA\tAAAAxx\n4683\t9157\t1\t3\t3\t3\t83\t683\t683\t4683\t4683\t166\t167\tDYAAAA\tFONAAA\tHHHHxx\n7072\t9158\t0\t0\t2\t12\t72\t72\t1072\t2072\t7072\t144\t145\tAMAAAA\tGONAAA\tOOOOxx\n5050\t9159\t0\t2\t0\t10\t50\t50\t1050\t50\t5050\t100\t101\tGMAAAA\tHONAAA\tVVVVxx\n5932\t9160\t0\t0\t2\t12\t32\t932\t1932\t932\t5932\t64\t65\tEUAAAA\tIONAAA\tAAAAxx\n2756\t9161\t0\t0\t6\t16\t56\t756\t756\t2756\t2756\t112\t113\tACAAAA\tJONAAA\tHHHHxx\n9813\t9162\t1\t1\t3\t13\t13\t813\t1813\t4813\t9813\t26\t27\tLNAAAA\tKONAAA\tOOOOxx\n7388\t9163\t0\t0\t8\t8\t88\t388\t1388\t2388\t7388\t176\t177\tEYAAAA\tLONAAA\tVVVVxx\n2596\t9164\t0\t0\t6\t16\t96\t596\t596\t2596\t2596\t192\t193\tWVAAAA\tMONAAA\tAAAAxx\n5102\t9165\t0\t2\t2\t2\t2\t102\t1102\t102\t5102\t4\t5\tGOAAAA\tNONAAA\tHHHHxx\n208\t9166\t0\t0\t8\t8\t8\t208\t208\t208\t208\t16\t17\tAIAAAA\tOONAAA\tOOOOxx\n86\t9167\t0\t2\t6\t6\t86\t86\t86\t86\t86\t172\t173\tIDAAAA\tPONAAA\tVVVVxx\n8127\t9168\t1\t3\t7\t7\t27\t127\t127\t3127\t8127\t54\t55\tPAAAAA\tQONAAA\tAAAAxx\n5154\t9169\t0\t2\t4\t14\t54\t154\t1154\t154\t5154\t108\t109\tGQAAAA\tRONAAA\tHHHHxx\n4491\t9170\t1\t3\t1\t11\t91\t491\t491\t4491\t4491\t182\t183\tTQAAAA\tSONAAA\tOOOOxx\n7423\t9171\t1\t3\t3\t3\t23\t423\t1423\t2423\t7423\t46\t47\tNZAAAA\tTONAAA\tVVVVxx\n6441\t9172\t1\t1\t1\t1\t41\t441\t441\t1441\t6441\t82\t83\tTNAAAA\tUONAAA\tAAAAxx\n2920\t9173\t0\t0\t0\t0\t20\t920\t920\t2920\t2920\t40\t41\tIIAAAA\tVONAAA\tHHHHxx\n6386\t9174\t0\t2\t6\t6\t86\t386\t386\t1386\t6386\t172\t173\tQLAAAA\tWONAAA\tOOOOxx\n9744\t9175\t0\t0\t4\t4\t44\t744\t1744\t4744\t9744\t88\t89\tUKAAAA\tXONAAA\tVVVVxx\n2667\t9176\t1\t3\t7\t7\t67\t667\t667\t2667\t2667\t134\t135\tPYAAAA\tYONAAA\tAAAAxx\n5754\t9177\t0\t2\t4\t14\t54\t754\t1754\t754\t5754\t108\t109\tINAAAA\tZONAAA\tHHHHxx\n4645\t9178\t1\t1\t5\t5\t45\t645\t645\t4645\t4645\t90\t91\tRWAAAA\tAPNAAA\tOOOOxx\n4327\t9179\t1\t3\t7\t7\t27\t327\t327\t4327\t4327\t54\t55\tLKAAAA\tBPNAAA\tVVVVxx\n843\t9180\t1\t3\t3\t3\t43\t843\t843\t843\t843\t86\t87\tLGAAAA\tCPNAAA\tAAAAxx\n4085\t9181\t1\t1\t5\t5\t85\t85\t85\t4085\t4085\t170\t171\tDBAAAA\tDPNAAA\tHHHHxx\n2849\t9182\t1\t1\t9\t9\t49\t849\t849\t2849\t2849\t98\t99\tPFAAAA\tEPNAAA\tOOOOxx\n5734\t9183\t0\t2\t4\t14\t34\t734\t1734\t734\t5734\t68\t69\tOMAAAA\tFPNAAA\tVVVVxx\n5307\t9184\t1\t3\t7\t7\t7\t307\t1307\t307\t5307\t14\t15\tDWAAAA\tGPNAAA\tAAAAxx\n8433\t9185\t1\t1\t3\t13\t33\t433\t433\t3433\t8433\t66\t67\tJMAAAA\tHPNAAA\tHHHHxx\n3031\t9186\t1\t3\t1\t11\t31\t31\t1031\t3031\t3031\t62\t63\tPMAAAA\tIPNAAA\tOOOOxx\n5714\t9187\t0\t2\t4\t14\t14\t714\t1714\t714\t5714\t28\t29\tULAAAA\tJPNAAA\tVVVVxx\n5969\t9188\t1\t1\t9\t9\t69\t969\t1969\t969\t5969\t138\t139\tPVAAAA\tKPNAAA\tAAAAxx\n2532\t9189\t0\t0\t2\t12\t32\t532\t532\t2532\t2532\t64\t65\tKTAAAA\tLPNAAA\tHHHHxx\n5219\t9190\t1\t3\t9\t19\t19\t219\t1219\t219\t5219\t38\t39\tTSAAAA\tMPNAAA\tOOOOxx\n7343\t9191\t1\t3\t3\t3\t43\t343\t1343\t2343\t7343\t86\t87\tLWAAAA\tNPNAAA\tVVVVxx\n9089\t9192\t1\t1\t9\t9\t89\t89\t1089\t4089\t9089\t178\t179\tPLAAAA\tOPNAAA\tAAAAxx\n9337\t9193\t1\t1\t7\t17\t37\t337\t1337\t4337\t9337\t74\t75\tDVAAAA\tPPNAAA\tHHHHxx\n5131\t9194\t1\t3\t1\t11\t31\t131\t1131\t131\t5131\t62\t63\tJPAAAA\tQPNAAA\tOOOOxx\n6253\t9195\t1\t1\t3\t13\t53\t253\t253\t1253\t6253\t106\t107\tNGAAAA\tRPNAAA\tVVVVxx\n5140\t9196\t0\t0\t0\t0\t40\t140\t1140\t140\t5140\t80\t81\tSPAAAA\tSPNAAA\tAAAAxx\n2953\t9197\t1\t1\t3\t13\t53\t953\t953\t2953\t2953\t106\t107\tPJAAAA\tTPNAAA\tHHHHxx\n4293\t9198\t1\t1\t3\t13\t93\t293\t293\t4293\t4293\t186\t187\tDJAAAA\tUPNAAA\tOOOOxx\n9974\t9199\t0\t2\t4\t14\t74\t974\t1974\t4974\t9974\t148\t149\tQTAAAA\tVPNAAA\tVVVVxx\n5061\t9200\t1\t1\t1\t1\t61\t61\t1061\t61\t5061\t122\t123\tRMAAAA\tWPNAAA\tAAAAxx\n8570\t9201\t0\t2\t0\t10\t70\t570\t570\t3570\t8570\t140\t141\tQRAAAA\tXPNAAA\tHHHHxx\n9504\t9202\t0\t0\t4\t4\t4\t504\t1504\t4504\t9504\t8\t9\tOBAAAA\tYPNAAA\tOOOOxx\n604\t9203\t0\t0\t4\t4\t4\t604\t604\t604\t604\t8\t9\tGXAAAA\tZPNAAA\tVVVVxx\n4991\t9204\t1\t3\t1\t11\t91\t991\t991\t4991\t4991\t182\t183\tZJAAAA\tAQNAAA\tAAAAxx\n880\t9205\t0\t0\t0\t0\t80\t880\t880\t880\t880\t160\t161\tWHAAAA\tBQNAAA\tHHHHxx\n3861\t9206\t1\t1\t1\t1\t61\t861\t1861\t3861\t3861\t122\t123\tNSAAAA\tCQNAAA\tOOOOxx\n8262\t9207\t0\t2\t2\t2\t62\t262\t262\t3262\t8262\t124\t125\tUFAAAA\tDQNAAA\tVVVVxx\n5689\t9208\t1\t1\t9\t9\t89\t689\t1689\t689\t5689\t178\t179\tVKAAAA\tEQNAAA\tAAAAxx\n1793\t9209\t1\t1\t3\t13\t93\t793\t1793\t1793\t1793\t186\t187\tZQAAAA\tFQNAAA\tHHHHxx\n2661\t9210\t1\t1\t1\t1\t61\t661\t661\t2661\t2661\t122\t123\tJYAAAA\tGQNAAA\tOOOOxx\n7954\t9211\t0\t2\t4\t14\t54\t954\t1954\t2954\t7954\t108\t109\tYTAAAA\tHQNAAA\tVVVVxx\n1874\t9212\t0\t2\t4\t14\t74\t874\t1874\t1874\t1874\t148\t149\tCUAAAA\tIQNAAA\tAAAAxx\n2982\t9213\t0\t2\t2\t2\t82\t982\t982\t2982\t2982\t164\t165\tSKAAAA\tJQNAAA\tHHHHxx\n331\t9214\t1\t3\t1\t11\t31\t331\t331\t331\t331\t62\t63\tTMAAAA\tKQNAAA\tOOOOxx\n5021\t9215\t1\t1\t1\t1\t21\t21\t1021\t21\t5021\t42\t43\tDLAAAA\tLQNAAA\tVVVVxx\n9894\t9216\t0\t2\t4\t14\t94\t894\t1894\t4894\t9894\t188\t189\tOQAAAA\tMQNAAA\tAAAAxx\n7709\t9217\t1\t1\t9\t9\t9\t709\t1709\t2709\t7709\t18\t19\tNKAAAA\tNQNAAA\tHHHHxx\n4980\t9218\t0\t0\t0\t0\t80\t980\t980\t4980\t4980\t160\t161\tOJAAAA\tOQNAAA\tOOOOxx\n8249\t9219\t1\t1\t9\t9\t49\t249\t249\t3249\t8249\t98\t99\tHFAAAA\tPQNAAA\tVVVVxx\n7120\t9220\t0\t0\t0\t0\t20\t120\t1120\t2120\t7120\t40\t41\tWNAAAA\tQQNAAA\tAAAAxx\n7464\t9221\t0\t0\t4\t4\t64\t464\t1464\t2464\t7464\t128\t129\tCBAAAA\tRQNAAA\tHHHHxx\n8086\t9222\t0\t2\t6\t6\t86\t86\t86\t3086\t8086\t172\t173\tAZAAAA\tSQNAAA\tOOOOxx\n3509\t9223\t1\t1\t9\t9\t9\t509\t1509\t3509\t3509\t18\t19\tZEAAAA\tTQNAAA\tVVVVxx\n3902\t9224\t0\t2\t2\t2\t2\t902\t1902\t3902\t3902\t4\t5\tCUAAAA\tUQNAAA\tAAAAxx\n9907\t9225\t1\t3\t7\t7\t7\t907\t1907\t4907\t9907\t14\t15\tBRAAAA\tVQNAAA\tHHHHxx\n6278\t9226\t0\t2\t8\t18\t78\t278\t278\t1278\t6278\t156\t157\tMHAAAA\tWQNAAA\tOOOOxx\n9316\t9227\t0\t0\t6\t16\t16\t316\t1316\t4316\t9316\t32\t33\tIUAAAA\tXQNAAA\tVVVVxx\n2824\t9228\t0\t0\t4\t4\t24\t824\t824\t2824\t2824\t48\t49\tQEAAAA\tYQNAAA\tAAAAxx\n1558\t9229\t0\t2\t8\t18\t58\t558\t1558\t1558\t1558\t116\t117\tYHAAAA\tZQNAAA\tHHHHxx\n5436\t9230\t0\t0\t6\t16\t36\t436\t1436\t436\t5436\t72\t73\tCBAAAA\tARNAAA\tOOOOxx\n1161\t9231\t1\t1\t1\t1\t61\t161\t1161\t1161\t1161\t122\t123\tRSAAAA\tBRNAAA\tVVVVxx\n7569\t9232\t1\t1\t9\t9\t69\t569\t1569\t2569\t7569\t138\t139\tDFAAAA\tCRNAAA\tAAAAxx\n9614\t9233\t0\t2\t4\t14\t14\t614\t1614\t4614\t9614\t28\t29\tUFAAAA\tDRNAAA\tHHHHxx\n6970\t9234\t0\t2\t0\t10\t70\t970\t970\t1970\t6970\t140\t141\tCIAAAA\tERNAAA\tOOOOxx\n2422\t9235\t0\t2\t2\t2\t22\t422\t422\t2422\t2422\t44\t45\tEPAAAA\tFRNAAA\tVVVVxx\n8860\t9236\t0\t0\t0\t0\t60\t860\t860\t3860\t8860\t120\t121\tUCAAAA\tGRNAAA\tAAAAxx\n9912\t9237\t0\t0\t2\t12\t12\t912\t1912\t4912\t9912\t24\t25\tGRAAAA\tHRNAAA\tHHHHxx\n1109\t9238\t1\t1\t9\t9\t9\t109\t1109\t1109\t1109\t18\t19\tRQAAAA\tIRNAAA\tOOOOxx\n3286\t9239\t0\t2\t6\t6\t86\t286\t1286\t3286\t3286\t172\t173\tKWAAAA\tJRNAAA\tVVVVxx\n2277\t9240\t1\t1\t7\t17\t77\t277\t277\t2277\t2277\t154\t155\tPJAAAA\tKRNAAA\tAAAAxx\n8656\t9241\t0\t0\t6\t16\t56\t656\t656\t3656\t8656\t112\t113\tYUAAAA\tLRNAAA\tHHHHxx\n4656\t9242\t0\t0\t6\t16\t56\t656\t656\t4656\t4656\t112\t113\tCXAAAA\tMRNAAA\tOOOOxx\n6965\t9243\t1\t1\t5\t5\t65\t965\t965\t1965\t6965\t130\t131\tXHAAAA\tNRNAAA\tVVVVxx\n7591\t9244\t1\t3\t1\t11\t91\t591\t1591\t2591\t7591\t182\t183\tZFAAAA\tORNAAA\tAAAAxx\n4883\t9245\t1\t3\t3\t3\t83\t883\t883\t4883\t4883\t166\t167\tVFAAAA\tPRNAAA\tHHHHxx\n452\t9246\t0\t0\t2\t12\t52\t452\t452\t452\t452\t104\t105\tKRAAAA\tQRNAAA\tOOOOxx\n4018\t9247\t0\t2\t8\t18\t18\t18\t18\t4018\t4018\t36\t37\tOYAAAA\tRRNAAA\tVVVVxx\n4066\t9248\t0\t2\t6\t6\t66\t66\t66\t4066\t4066\t132\t133\tKAAAAA\tSRNAAA\tAAAAxx\n6480\t9249\t0\t0\t0\t0\t80\t480\t480\t1480\t6480\t160\t161\tGPAAAA\tTRNAAA\tHHHHxx\n8634\t9250\t0\t2\t4\t14\t34\t634\t634\t3634\t8634\t68\t69\tCUAAAA\tURNAAA\tOOOOxx\n9387\t9251\t1\t3\t7\t7\t87\t387\t1387\t4387\t9387\t174\t175\tBXAAAA\tVRNAAA\tVVVVxx\n3476\t9252\t0\t0\t6\t16\t76\t476\t1476\t3476\t3476\t152\t153\tSDAAAA\tWRNAAA\tAAAAxx\n5995\t9253\t1\t3\t5\t15\t95\t995\t1995\t995\t5995\t190\t191\tPWAAAA\tXRNAAA\tHHHHxx\n9677\t9254\t1\t1\t7\t17\t77\t677\t1677\t4677\t9677\t154\t155\tFIAAAA\tYRNAAA\tOOOOxx\n3884\t9255\t0\t0\t4\t4\t84\t884\t1884\t3884\t3884\t168\t169\tKTAAAA\tZRNAAA\tVVVVxx\n6500\t9256\t0\t0\t0\t0\t0\t500\t500\t1500\t6500\t0\t1\tAQAAAA\tASNAAA\tAAAAxx\n7972\t9257\t0\t0\t2\t12\t72\t972\t1972\t2972\t7972\t144\t145\tQUAAAA\tBSNAAA\tHHHHxx\n5281\t9258\t1\t1\t1\t1\t81\t281\t1281\t281\t5281\t162\t163\tDVAAAA\tCSNAAA\tOOOOxx\n1288\t9259\t0\t0\t8\t8\t88\t288\t1288\t1288\t1288\t176\t177\tOXAAAA\tDSNAAA\tVVVVxx\n4366\t9260\t0\t2\t6\t6\t66\t366\t366\t4366\t4366\t132\t133\tYLAAAA\tESNAAA\tAAAAxx\n6557\t9261\t1\t1\t7\t17\t57\t557\t557\t1557\t6557\t114\t115\tFSAAAA\tFSNAAA\tHHHHxx\n7086\t9262\t0\t2\t6\t6\t86\t86\t1086\t2086\t7086\t172\t173\tOMAAAA\tGSNAAA\tOOOOxx\n6588\t9263\t0\t0\t8\t8\t88\t588\t588\t1588\t6588\t176\t177\tKTAAAA\tHSNAAA\tVVVVxx\n9062\t9264\t0\t2\t2\t2\t62\t62\t1062\t4062\t9062\t124\t125\tOKAAAA\tISNAAA\tAAAAxx\n9230\t9265\t0\t2\t0\t10\t30\t230\t1230\t4230\t9230\t60\t61\tARAAAA\tJSNAAA\tHHHHxx\n7672\t9266\t0\t0\t2\t12\t72\t672\t1672\t2672\t7672\t144\t145\tCJAAAA\tKSNAAA\tOOOOxx\n5204\t9267\t0\t0\t4\t4\t4\t204\t1204\t204\t5204\t8\t9\tESAAAA\tLSNAAA\tVVVVxx\n2836\t9268\t0\t0\t6\t16\t36\t836\t836\t2836\t2836\t72\t73\tCFAAAA\tMSNAAA\tAAAAxx\n7165\t9269\t1\t1\t5\t5\t65\t165\t1165\t2165\t7165\t130\t131\tPPAAAA\tNSNAAA\tHHHHxx\n971\t9270\t1\t3\t1\t11\t71\t971\t971\t971\t971\t142\t143\tJLAAAA\tOSNAAA\tOOOOxx\n3851\t9271\t1\t3\t1\t11\t51\t851\t1851\t3851\t3851\t102\t103\tDSAAAA\tPSNAAA\tVVVVxx\n8593\t9272\t1\t1\t3\t13\t93\t593\t593\t3593\t8593\t186\t187\tNSAAAA\tQSNAAA\tAAAAxx\n7742\t9273\t0\t2\t2\t2\t42\t742\t1742\t2742\t7742\t84\t85\tULAAAA\tRSNAAA\tHHHHxx\n2887\t9274\t1\t3\t7\t7\t87\t887\t887\t2887\t2887\t174\t175\tBHAAAA\tSSNAAA\tOOOOxx\n8479\t9275\t1\t3\t9\t19\t79\t479\t479\t3479\t8479\t158\t159\tDOAAAA\tTSNAAA\tVVVVxx\n9514\t9276\t0\t2\t4\t14\t14\t514\t1514\t4514\t9514\t28\t29\tYBAAAA\tUSNAAA\tAAAAxx\n273\t9277\t1\t1\t3\t13\t73\t273\t273\t273\t273\t146\t147\tNKAAAA\tVSNAAA\tHHHHxx\n2938\t9278\t0\t2\t8\t18\t38\t938\t938\t2938\t2938\t76\t77\tAJAAAA\tWSNAAA\tOOOOxx\n9793\t9279\t1\t1\t3\t13\t93\t793\t1793\t4793\t9793\t186\t187\tRMAAAA\tXSNAAA\tVVVVxx\n8050\t9280\t0\t2\t0\t10\t50\t50\t50\t3050\t8050\t100\t101\tQXAAAA\tYSNAAA\tAAAAxx\n6702\t9281\t0\t2\t2\t2\t2\t702\t702\t1702\t6702\t4\t5\tUXAAAA\tZSNAAA\tHHHHxx\n7290\t9282\t0\t2\t0\t10\t90\t290\t1290\t2290\t7290\t180\t181\tKUAAAA\tATNAAA\tOOOOxx\n1837\t9283\t1\t1\t7\t17\t37\t837\t1837\t1837\t1837\t74\t75\tRSAAAA\tBTNAAA\tVVVVxx\n3206\t9284\t0\t2\t6\t6\t6\t206\t1206\t3206\t3206\t12\t13\tITAAAA\tCTNAAA\tAAAAxx\n4925\t9285\t1\t1\t5\t5\t25\t925\t925\t4925\t4925\t50\t51\tLHAAAA\tDTNAAA\tHHHHxx\n5066\t9286\t0\t2\t6\t6\t66\t66\t1066\t66\t5066\t132\t133\tWMAAAA\tETNAAA\tOOOOxx\n3401\t9287\t1\t1\t1\t1\t1\t401\t1401\t3401\t3401\t2\t3\tVAAAAA\tFTNAAA\tVVVVxx\n3474\t9288\t0\t2\t4\t14\t74\t474\t1474\t3474\t3474\t148\t149\tQDAAAA\tGTNAAA\tAAAAxx\n57\t9289\t1\t1\t7\t17\t57\t57\t57\t57\t57\t114\t115\tFCAAAA\tHTNAAA\tHHHHxx\n2082\t9290\t0\t2\t2\t2\t82\t82\t82\t2082\t2082\t164\t165\tCCAAAA\tITNAAA\tOOOOxx\n100\t9291\t0\t0\t0\t0\t0\t100\t100\t100\t100\t0\t1\tWDAAAA\tJTNAAA\tVVVVxx\n9665\t9292\t1\t1\t5\t5\t65\t665\t1665\t4665\t9665\t130\t131\tTHAAAA\tKTNAAA\tAAAAxx\n8284\t9293\t0\t0\t4\t4\t84\t284\t284\t3284\t8284\t168\t169\tQGAAAA\tLTNAAA\tHHHHxx\n958\t9294\t0\t2\t8\t18\t58\t958\t958\t958\t958\t116\t117\tWKAAAA\tMTNAAA\tOOOOxx\n5282\t9295\t0\t2\t2\t2\t82\t282\t1282\t282\t5282\t164\t165\tEVAAAA\tNTNAAA\tVVVVxx\n4257\t9296\t1\t1\t7\t17\t57\t257\t257\t4257\t4257\t114\t115\tTHAAAA\tOTNAAA\tAAAAxx\n3160\t9297\t0\t0\t0\t0\t60\t160\t1160\t3160\t3160\t120\t121\tORAAAA\tPTNAAA\tHHHHxx\n8449\t9298\t1\t1\t9\t9\t49\t449\t449\t3449\t8449\t98\t99\tZMAAAA\tQTNAAA\tOOOOxx\n500\t9299\t0\t0\t0\t0\t0\t500\t500\t500\t500\t0\t1\tGTAAAA\tRTNAAA\tVVVVxx\n6432\t9300\t0\t0\t2\t12\t32\t432\t432\t1432\t6432\t64\t65\tKNAAAA\tSTNAAA\tAAAAxx\n6220\t9301\t0\t0\t0\t0\t20\t220\t220\t1220\t6220\t40\t41\tGFAAAA\tTTNAAA\tHHHHxx\n7233\t9302\t1\t1\t3\t13\t33\t233\t1233\t2233\t7233\t66\t67\tFSAAAA\tUTNAAA\tOOOOxx\n2723\t9303\t1\t3\t3\t3\t23\t723\t723\t2723\t2723\t46\t47\tTAAAAA\tVTNAAA\tVVVVxx\n1899\t9304\t1\t3\t9\t19\t99\t899\t1899\t1899\t1899\t198\t199\tBVAAAA\tWTNAAA\tAAAAxx\n7158\t9305\t0\t2\t8\t18\t58\t158\t1158\t2158\t7158\t116\t117\tIPAAAA\tXTNAAA\tHHHHxx\n202\t9306\t0\t2\t2\t2\t2\t202\t202\t202\t202\t4\t5\tUHAAAA\tYTNAAA\tOOOOxx\n2286\t9307\t0\t2\t6\t6\t86\t286\t286\t2286\t2286\t172\t173\tYJAAAA\tZTNAAA\tVVVVxx\n5356\t9308\t0\t0\t6\t16\t56\t356\t1356\t356\t5356\t112\t113\tAYAAAA\tAUNAAA\tAAAAxx\n3809\t9309\t1\t1\t9\t9\t9\t809\t1809\t3809\t3809\t18\t19\tNQAAAA\tBUNAAA\tHHHHxx\n3979\t9310\t1\t3\t9\t19\t79\t979\t1979\t3979\t3979\t158\t159\tBXAAAA\tCUNAAA\tOOOOxx\n8359\t9311\t1\t3\t9\t19\t59\t359\t359\t3359\t8359\t118\t119\tNJAAAA\tDUNAAA\tVVVVxx\n3479\t9312\t1\t3\t9\t19\t79\t479\t1479\t3479\t3479\t158\t159\tVDAAAA\tEUNAAA\tAAAAxx\n4895\t9313\t1\t3\t5\t15\t95\t895\t895\t4895\t4895\t190\t191\tHGAAAA\tFUNAAA\tHHHHxx\n6059\t9314\t1\t3\t9\t19\t59\t59\t59\t1059\t6059\t118\t119\tBZAAAA\tGUNAAA\tOOOOxx\n9560\t9315\t0\t0\t0\t0\t60\t560\t1560\t4560\t9560\t120\t121\tSDAAAA\tHUNAAA\tVVVVxx\n6756\t9316\t0\t0\t6\t16\t56\t756\t756\t1756\t6756\t112\t113\tWZAAAA\tIUNAAA\tAAAAxx\n7504\t9317\t0\t0\t4\t4\t4\t504\t1504\t2504\t7504\t8\t9\tQCAAAA\tJUNAAA\tHHHHxx\n6762\t9318\t0\t2\t2\t2\t62\t762\t762\t1762\t6762\t124\t125\tCAAAAA\tKUNAAA\tOOOOxx\n5304\t9319\t0\t0\t4\t4\t4\t304\t1304\t304\t5304\t8\t9\tAWAAAA\tLUNAAA\tVVVVxx\n9533\t9320\t1\t1\t3\t13\t33\t533\t1533\t4533\t9533\t66\t67\tRCAAAA\tMUNAAA\tAAAAxx\n6649\t9321\t1\t1\t9\t9\t49\t649\t649\t1649\t6649\t98\t99\tTVAAAA\tNUNAAA\tHHHHxx\n38\t9322\t0\t2\t8\t18\t38\t38\t38\t38\t38\t76\t77\tMBAAAA\tOUNAAA\tOOOOxx\n5713\t9323\t1\t1\t3\t13\t13\t713\t1713\t713\t5713\t26\t27\tTLAAAA\tPUNAAA\tVVVVxx\n3000\t9324\t0\t0\t0\t0\t0\t0\t1000\t3000\t3000\t0\t1\tKLAAAA\tQUNAAA\tAAAAxx\n3738\t9325\t0\t2\t8\t18\t38\t738\t1738\t3738\t3738\t76\t77\tUNAAAA\tRUNAAA\tHHHHxx\n3327\t9326\t1\t3\t7\t7\t27\t327\t1327\t3327\t3327\t54\t55\tZXAAAA\tSUNAAA\tOOOOxx\n3922\t9327\t0\t2\t2\t2\t22\t922\t1922\t3922\t3922\t44\t45\tWUAAAA\tTUNAAA\tVVVVxx\n9245\t9328\t1\t1\t5\t5\t45\t245\t1245\t4245\t9245\t90\t91\tPRAAAA\tUUNAAA\tAAAAxx\n2172\t9329\t0\t0\t2\t12\t72\t172\t172\t2172\t2172\t144\t145\tOFAAAA\tVUNAAA\tHHHHxx\n7128\t9330\t0\t0\t8\t8\t28\t128\t1128\t2128\t7128\t56\t57\tEOAAAA\tWUNAAA\tOOOOxx\n1195\t9331\t1\t3\t5\t15\t95\t195\t1195\t1195\t1195\t190\t191\tZTAAAA\tXUNAAA\tVVVVxx\n8445\t9332\t1\t1\t5\t5\t45\t445\t445\t3445\t8445\t90\t91\tVMAAAA\tYUNAAA\tAAAAxx\n8638\t9333\t0\t2\t8\t18\t38\t638\t638\t3638\t8638\t76\t77\tGUAAAA\tZUNAAA\tHHHHxx\n1249\t9334\t1\t1\t9\t9\t49\t249\t1249\t1249\t1249\t98\t99\tBWAAAA\tAVNAAA\tOOOOxx\n8659\t9335\t1\t3\t9\t19\t59\t659\t659\t3659\t8659\t118\t119\tBVAAAA\tBVNAAA\tVVVVxx\n3556\t9336\t0\t0\t6\t16\t56\t556\t1556\t3556\t3556\t112\t113\tUGAAAA\tCVNAAA\tAAAAxx\n3347\t9337\t1\t3\t7\t7\t47\t347\t1347\t3347\t3347\t94\t95\tTYAAAA\tDVNAAA\tHHHHxx\n3260\t9338\t0\t0\t0\t0\t60\t260\t1260\t3260\t3260\t120\t121\tKVAAAA\tEVNAAA\tOOOOxx\n5139\t9339\t1\t3\t9\t19\t39\t139\t1139\t139\t5139\t78\t79\tRPAAAA\tFVNAAA\tVVVVxx\n9991\t9340\t1\t3\t1\t11\t91\t991\t1991\t4991\t9991\t182\t183\tHUAAAA\tGVNAAA\tAAAAxx\n5499\t9341\t1\t3\t9\t19\t99\t499\t1499\t499\t5499\t198\t199\tNDAAAA\tHVNAAA\tHHHHxx\n8082\t9342\t0\t2\t2\t2\t82\t82\t82\t3082\t8082\t164\t165\tWYAAAA\tIVNAAA\tOOOOxx\n1640\t9343\t0\t0\t0\t0\t40\t640\t1640\t1640\t1640\t80\t81\tCLAAAA\tJVNAAA\tVVVVxx\n8726\t9344\t0\t2\t6\t6\t26\t726\t726\t3726\t8726\t52\t53\tQXAAAA\tKVNAAA\tAAAAxx\n2339\t9345\t1\t3\t9\t19\t39\t339\t339\t2339\t2339\t78\t79\tZLAAAA\tLVNAAA\tHHHHxx\n2601\t9346\t1\t1\t1\t1\t1\t601\t601\t2601\t2601\t2\t3\tBWAAAA\tMVNAAA\tOOOOxx\n9940\t9347\t0\t0\t0\t0\t40\t940\t1940\t4940\t9940\t80\t81\tISAAAA\tNVNAAA\tVVVVxx\n4185\t9348\t1\t1\t5\t5\t85\t185\t185\t4185\t4185\t170\t171\tZEAAAA\tOVNAAA\tAAAAxx\n9546\t9349\t0\t2\t6\t6\t46\t546\t1546\t4546\t9546\t92\t93\tEDAAAA\tPVNAAA\tHHHHxx\n5218\t9350\t0\t2\t8\t18\t18\t218\t1218\t218\t5218\t36\t37\tSSAAAA\tQVNAAA\tOOOOxx\n4374\t9351\t0\t2\t4\t14\t74\t374\t374\t4374\t4374\t148\t149\tGMAAAA\tRVNAAA\tVVVVxx\n288\t9352\t0\t0\t8\t8\t88\t288\t288\t288\t288\t176\t177\tCLAAAA\tSVNAAA\tAAAAxx\n7445\t9353\t1\t1\t5\t5\t45\t445\t1445\t2445\t7445\t90\t91\tJAAAAA\tTVNAAA\tHHHHxx\n1710\t9354\t0\t2\t0\t10\t10\t710\t1710\t1710\t1710\t20\t21\tUNAAAA\tUVNAAA\tOOOOxx\n6409\t9355\t1\t1\t9\t9\t9\t409\t409\t1409\t6409\t18\t19\tNMAAAA\tVVNAAA\tVVVVxx\n7982\t9356\t0\t2\t2\t2\t82\t982\t1982\t2982\t7982\t164\t165\tAVAAAA\tWVNAAA\tAAAAxx\n4950\t9357\t0\t2\t0\t10\t50\t950\t950\t4950\t4950\t100\t101\tKIAAAA\tXVNAAA\tHHHHxx\n9242\t9358\t0\t2\t2\t2\t42\t242\t1242\t4242\t9242\t84\t85\tMRAAAA\tYVNAAA\tOOOOxx\n3272\t9359\t0\t0\t2\t12\t72\t272\t1272\t3272\t3272\t144\t145\tWVAAAA\tZVNAAA\tVVVVxx\n739\t9360\t1\t3\t9\t19\t39\t739\t739\t739\t739\t78\t79\tLCAAAA\tAWNAAA\tAAAAxx\n5526\t9361\t0\t2\t6\t6\t26\t526\t1526\t526\t5526\t52\t53\tOEAAAA\tBWNAAA\tHHHHxx\n8189\t9362\t1\t1\t9\t9\t89\t189\t189\t3189\t8189\t178\t179\tZCAAAA\tCWNAAA\tOOOOxx\n9106\t9363\t0\t2\t6\t6\t6\t106\t1106\t4106\t9106\t12\t13\tGMAAAA\tDWNAAA\tVVVVxx\n9775\t9364\t1\t3\t5\t15\t75\t775\t1775\t4775\t9775\t150\t151\tZLAAAA\tEWNAAA\tAAAAxx\n4643\t9365\t1\t3\t3\t3\t43\t643\t643\t4643\t4643\t86\t87\tPWAAAA\tFWNAAA\tHHHHxx\n8396\t9366\t0\t0\t6\t16\t96\t396\t396\t3396\t8396\t192\t193\tYKAAAA\tGWNAAA\tOOOOxx\n3255\t9367\t1\t3\t5\t15\t55\t255\t1255\t3255\t3255\t110\t111\tFVAAAA\tHWNAAA\tVVVVxx\n301\t9368\t1\t1\t1\t1\t1\t301\t301\t301\t301\t2\t3\tPLAAAA\tIWNAAA\tAAAAxx\n6014\t9369\t0\t2\t4\t14\t14\t14\t14\t1014\t6014\t28\t29\tIXAAAA\tJWNAAA\tHHHHxx\n6046\t9370\t0\t2\t6\t6\t46\t46\t46\t1046\t6046\t92\t93\tOYAAAA\tKWNAAA\tOOOOxx\n984\t9371\t0\t0\t4\t4\t84\t984\t984\t984\t984\t168\t169\tWLAAAA\tLWNAAA\tVVVVxx\n2420\t9372\t0\t0\t0\t0\t20\t420\t420\t2420\t2420\t40\t41\tCPAAAA\tMWNAAA\tAAAAxx\n2922\t9373\t0\t2\t2\t2\t22\t922\t922\t2922\t2922\t44\t45\tKIAAAA\tNWNAAA\tHHHHxx\n2317\t9374\t1\t1\t7\t17\t17\t317\t317\t2317\t2317\t34\t35\tDLAAAA\tOWNAAA\tOOOOxx\n7332\t9375\t0\t0\t2\t12\t32\t332\t1332\t2332\t7332\t64\t65\tAWAAAA\tPWNAAA\tVVVVxx\n6451\t9376\t1\t3\t1\t11\t51\t451\t451\t1451\t6451\t102\t103\tDOAAAA\tQWNAAA\tAAAAxx\n2589\t9377\t1\t1\t9\t9\t89\t589\t589\t2589\t2589\t178\t179\tPVAAAA\tRWNAAA\tHHHHxx\n4333\t9378\t1\t1\t3\t13\t33\t333\t333\t4333\t4333\t66\t67\tRKAAAA\tSWNAAA\tOOOOxx\n8650\t9379\t0\t2\t0\t10\t50\t650\t650\t3650\t8650\t100\t101\tSUAAAA\tTWNAAA\tVVVVxx\n6856\t9380\t0\t0\t6\t16\t56\t856\t856\t1856\t6856\t112\t113\tSDAAAA\tUWNAAA\tAAAAxx\n4194\t9381\t0\t2\t4\t14\t94\t194\t194\t4194\t4194\t188\t189\tIFAAAA\tVWNAAA\tHHHHxx\n6246\t9382\t0\t2\t6\t6\t46\t246\t246\t1246\t6246\t92\t93\tGGAAAA\tWWNAAA\tOOOOxx\n4371\t9383\t1\t3\t1\t11\t71\t371\t371\t4371\t4371\t142\t143\tDMAAAA\tXWNAAA\tVVVVxx\n1388\t9384\t0\t0\t8\t8\t88\t388\t1388\t1388\t1388\t176\t177\tKBAAAA\tYWNAAA\tAAAAxx\n1056\t9385\t0\t0\t6\t16\t56\t56\t1056\t1056\t1056\t112\t113\tQOAAAA\tZWNAAA\tHHHHxx\n6041\t9386\t1\t1\t1\t1\t41\t41\t41\t1041\t6041\t82\t83\tJYAAAA\tAXNAAA\tOOOOxx\n6153\t9387\t1\t1\t3\t13\t53\t153\t153\t1153\t6153\t106\t107\tRCAAAA\tBXNAAA\tVVVVxx\n8450\t9388\t0\t2\t0\t10\t50\t450\t450\t3450\t8450\t100\t101\tANAAAA\tCXNAAA\tAAAAxx\n3469\t9389\t1\t1\t9\t9\t69\t469\t1469\t3469\t3469\t138\t139\tLDAAAA\tDXNAAA\tHHHHxx\n5226\t9390\t0\t2\t6\t6\t26\t226\t1226\t226\t5226\t52\t53\tATAAAA\tEXNAAA\tOOOOxx\n8112\t9391\t0\t0\t2\t12\t12\t112\t112\t3112\t8112\t24\t25\tAAAAAA\tFXNAAA\tVVVVxx\n647\t9392\t1\t3\t7\t7\t47\t647\t647\t647\t647\t94\t95\tXYAAAA\tGXNAAA\tAAAAxx\n2567\t9393\t1\t3\t7\t7\t67\t567\t567\t2567\t2567\t134\t135\tTUAAAA\tHXNAAA\tHHHHxx\n9064\t9394\t0\t0\t4\t4\t64\t64\t1064\t4064\t9064\t128\t129\tQKAAAA\tIXNAAA\tOOOOxx\n5161\t9395\t1\t1\t1\t1\t61\t161\t1161\t161\t5161\t122\t123\tNQAAAA\tJXNAAA\tVVVVxx\n5260\t9396\t0\t0\t0\t0\t60\t260\t1260\t260\t5260\t120\t121\tIUAAAA\tKXNAAA\tAAAAxx\n8988\t9397\t0\t0\t8\t8\t88\t988\t988\t3988\t8988\t176\t177\tSHAAAA\tLXNAAA\tHHHHxx\n9678\t9398\t0\t2\t8\t18\t78\t678\t1678\t4678\t9678\t156\t157\tGIAAAA\tMXNAAA\tOOOOxx\n6853\t9399\t1\t1\t3\t13\t53\t853\t853\t1853\t6853\t106\t107\tPDAAAA\tNXNAAA\tVVVVxx\n5294\t9400\t0\t2\t4\t14\t94\t294\t1294\t294\t5294\t188\t189\tQVAAAA\tOXNAAA\tAAAAxx\n9864\t9401\t0\t0\t4\t4\t64\t864\t1864\t4864\t9864\t128\t129\tKPAAAA\tPXNAAA\tHHHHxx\n8702\t9402\t0\t2\t2\t2\t2\t702\t702\t3702\t8702\t4\t5\tSWAAAA\tQXNAAA\tOOOOxx\n1132\t9403\t0\t0\t2\t12\t32\t132\t1132\t1132\t1132\t64\t65\tORAAAA\tRXNAAA\tVVVVxx\n1524\t9404\t0\t0\t4\t4\t24\t524\t1524\t1524\t1524\t48\t49\tQGAAAA\tSXNAAA\tAAAAxx\n4560\t9405\t0\t0\t0\t0\t60\t560\t560\t4560\t4560\t120\t121\tKTAAAA\tTXNAAA\tHHHHxx\n2137\t9406\t1\t1\t7\t17\t37\t137\t137\t2137\t2137\t74\t75\tFEAAAA\tUXNAAA\tOOOOxx\n3283\t9407\t1\t3\t3\t3\t83\t283\t1283\t3283\t3283\t166\t167\tHWAAAA\tVXNAAA\tVVVVxx\n3377\t9408\t1\t1\t7\t17\t77\t377\t1377\t3377\t3377\t154\t155\tXZAAAA\tWXNAAA\tAAAAxx\n2267\t9409\t1\t3\t7\t7\t67\t267\t267\t2267\t2267\t134\t135\tFJAAAA\tXXNAAA\tHHHHxx\n8987\t9410\t1\t3\t7\t7\t87\t987\t987\t3987\t8987\t174\t175\tRHAAAA\tYXNAAA\tOOOOxx\n6709\t9411\t1\t1\t9\t9\t9\t709\t709\t1709\t6709\t18\t19\tBYAAAA\tZXNAAA\tVVVVxx\n8059\t9412\t1\t3\t9\t19\t59\t59\t59\t3059\t8059\t118\t119\tZXAAAA\tAYNAAA\tAAAAxx\n3402\t9413\t0\t2\t2\t2\t2\t402\t1402\t3402\t3402\t4\t5\tWAAAAA\tBYNAAA\tHHHHxx\n6443\t9414\t1\t3\t3\t3\t43\t443\t443\t1443\t6443\t86\t87\tVNAAAA\tCYNAAA\tOOOOxx\n8858\t9415\t0\t2\t8\t18\t58\t858\t858\t3858\t8858\t116\t117\tSCAAAA\tDYNAAA\tVVVVxx\n3974\t9416\t0\t2\t4\t14\t74\t974\t1974\t3974\t3974\t148\t149\tWWAAAA\tEYNAAA\tAAAAxx\n3521\t9417\t1\t1\t1\t1\t21\t521\t1521\t3521\t3521\t42\t43\tLFAAAA\tFYNAAA\tHHHHxx\n9509\t9418\t1\t1\t9\t9\t9\t509\t1509\t4509\t9509\t18\t19\tTBAAAA\tGYNAAA\tOOOOxx\n5442\t9419\t0\t2\t2\t2\t42\t442\t1442\t442\t5442\t84\t85\tIBAAAA\tHYNAAA\tVVVVxx\n8968\t9420\t0\t0\t8\t8\t68\t968\t968\t3968\t8968\t136\t137\tYGAAAA\tIYNAAA\tAAAAxx\n333\t9421\t1\t1\t3\t13\t33\t333\t333\t333\t333\t66\t67\tVMAAAA\tJYNAAA\tHHHHxx\n952\t9422\t0\t0\t2\t12\t52\t952\t952\t952\t952\t104\t105\tQKAAAA\tKYNAAA\tOOOOxx\n7482\t9423\t0\t2\t2\t2\t82\t482\t1482\t2482\t7482\t164\t165\tUBAAAA\tLYNAAA\tVVVVxx\n1486\t9424\t0\t2\t6\t6\t86\t486\t1486\t1486\t1486\t172\t173\tEFAAAA\tMYNAAA\tAAAAxx\n1815\t9425\t1\t3\t5\t15\t15\t815\t1815\t1815\t1815\t30\t31\tVRAAAA\tNYNAAA\tHHHHxx\n7937\t9426\t1\t1\t7\t17\t37\t937\t1937\t2937\t7937\t74\t75\tHTAAAA\tOYNAAA\tOOOOxx\n1436\t9427\t0\t0\t6\t16\t36\t436\t1436\t1436\t1436\t72\t73\tGDAAAA\tPYNAAA\tVVVVxx\n3470\t9428\t0\t2\t0\t10\t70\t470\t1470\t3470\t3470\t140\t141\tMDAAAA\tQYNAAA\tAAAAxx\n8195\t9429\t1\t3\t5\t15\t95\t195\t195\t3195\t8195\t190\t191\tFDAAAA\tRYNAAA\tHHHHxx\n6906\t9430\t0\t2\t6\t6\t6\t906\t906\t1906\t6906\t12\t13\tQFAAAA\tSYNAAA\tOOOOxx\n2539\t9431\t1\t3\t9\t19\t39\t539\t539\t2539\t2539\t78\t79\tRTAAAA\tTYNAAA\tVVVVxx\n5988\t9432\t0\t0\t8\t8\t88\t988\t1988\t988\t5988\t176\t177\tIWAAAA\tUYNAAA\tAAAAxx\n8908\t9433\t0\t0\t8\t8\t8\t908\t908\t3908\t8908\t16\t17\tQEAAAA\tVYNAAA\tHHHHxx\n2319\t9434\t1\t3\t9\t19\t19\t319\t319\t2319\t2319\t38\t39\tFLAAAA\tWYNAAA\tOOOOxx\n3263\t9435\t1\t3\t3\t3\t63\t263\t1263\t3263\t3263\t126\t127\tNVAAAA\tXYNAAA\tVVVVxx\n4039\t9436\t1\t3\t9\t19\t39\t39\t39\t4039\t4039\t78\t79\tJZAAAA\tYYNAAA\tAAAAxx\n6373\t9437\t1\t1\t3\t13\t73\t373\t373\t1373\t6373\t146\t147\tDLAAAA\tZYNAAA\tHHHHxx\n1168\t9438\t0\t0\t8\t8\t68\t168\t1168\t1168\t1168\t136\t137\tYSAAAA\tAZNAAA\tOOOOxx\n8338\t9439\t0\t2\t8\t18\t38\t338\t338\t3338\t8338\t76\t77\tSIAAAA\tBZNAAA\tVVVVxx\n1172\t9440\t0\t0\t2\t12\t72\t172\t1172\t1172\t1172\t144\t145\tCTAAAA\tCZNAAA\tAAAAxx\n200\t9441\t0\t0\t0\t0\t0\t200\t200\t200\t200\t0\t1\tSHAAAA\tDZNAAA\tHHHHxx\n6355\t9442\t1\t3\t5\t15\t55\t355\t355\t1355\t6355\t110\t111\tLKAAAA\tEZNAAA\tOOOOxx\n7768\t9443\t0\t0\t8\t8\t68\t768\t1768\t2768\t7768\t136\t137\tUMAAAA\tFZNAAA\tVVVVxx\n25\t9444\t1\t1\t5\t5\t25\t25\t25\t25\t25\t50\t51\tZAAAAA\tGZNAAA\tAAAAxx\n7144\t9445\t0\t0\t4\t4\t44\t144\t1144\t2144\t7144\t88\t89\tUOAAAA\tHZNAAA\tHHHHxx\n8671\t9446\t1\t3\t1\t11\t71\t671\t671\t3671\t8671\t142\t143\tNVAAAA\tIZNAAA\tOOOOxx\n9163\t9447\t1\t3\t3\t3\t63\t163\t1163\t4163\t9163\t126\t127\tLOAAAA\tJZNAAA\tVVVVxx\n8889\t9448\t1\t1\t9\t9\t89\t889\t889\t3889\t8889\t178\t179\tXDAAAA\tKZNAAA\tAAAAxx\n5950\t9449\t0\t2\t0\t10\t50\t950\t1950\t950\t5950\t100\t101\tWUAAAA\tLZNAAA\tHHHHxx\n6163\t9450\t1\t3\t3\t3\t63\t163\t163\t1163\t6163\t126\t127\tBDAAAA\tMZNAAA\tOOOOxx\n8119\t9451\t1\t3\t9\t19\t19\t119\t119\t3119\t8119\t38\t39\tHAAAAA\tNZNAAA\tVVVVxx\n1416\t9452\t0\t0\t6\t16\t16\t416\t1416\t1416\t1416\t32\t33\tMCAAAA\tOZNAAA\tAAAAxx\n4132\t9453\t0\t0\t2\t12\t32\t132\t132\t4132\t4132\t64\t65\tYCAAAA\tPZNAAA\tHHHHxx\n2294\t9454\t0\t2\t4\t14\t94\t294\t294\t2294\t2294\t188\t189\tGKAAAA\tQZNAAA\tOOOOxx\n9094\t9455\t0\t2\t4\t14\t94\t94\t1094\t4094\t9094\t188\t189\tULAAAA\tRZNAAA\tVVVVxx\n4168\t9456\t0\t0\t8\t8\t68\t168\t168\t4168\t4168\t136\t137\tIEAAAA\tSZNAAA\tAAAAxx\n9108\t9457\t0\t0\t8\t8\t8\t108\t1108\t4108\t9108\t16\t17\tIMAAAA\tTZNAAA\tHHHHxx\n5706\t9458\t0\t2\t6\t6\t6\t706\t1706\t706\t5706\t12\t13\tMLAAAA\tUZNAAA\tOOOOxx\n2231\t9459\t1\t3\t1\t11\t31\t231\t231\t2231\t2231\t62\t63\tVHAAAA\tVZNAAA\tVVVVxx\n2173\t9460\t1\t1\t3\t13\t73\t173\t173\t2173\t2173\t146\t147\tPFAAAA\tWZNAAA\tAAAAxx\n90\t9461\t0\t2\t0\t10\t90\t90\t90\t90\t90\t180\t181\tMDAAAA\tXZNAAA\tHHHHxx\n9996\t9462\t0\t0\t6\t16\t96\t996\t1996\t4996\t9996\t192\t193\tMUAAAA\tYZNAAA\tOOOOxx\n330\t9463\t0\t2\t0\t10\t30\t330\t330\t330\t330\t60\t61\tSMAAAA\tZZNAAA\tVVVVxx\n2052\t9464\t0\t0\t2\t12\t52\t52\t52\t2052\t2052\t104\t105\tYAAAAA\tAAOAAA\tAAAAxx\n1093\t9465\t1\t1\t3\t13\t93\t93\t1093\t1093\t1093\t186\t187\tBQAAAA\tBAOAAA\tHHHHxx\n5817\t9466\t1\t1\t7\t17\t17\t817\t1817\t817\t5817\t34\t35\tTPAAAA\tCAOAAA\tOOOOxx\n1559\t9467\t1\t3\t9\t19\t59\t559\t1559\t1559\t1559\t118\t119\tZHAAAA\tDAOAAA\tVVVVxx\n8405\t9468\t1\t1\t5\t5\t5\t405\t405\t3405\t8405\t10\t11\tHLAAAA\tEAOAAA\tAAAAxx\n9962\t9469\t0\t2\t2\t2\t62\t962\t1962\t4962\t9962\t124\t125\tETAAAA\tFAOAAA\tHHHHxx\n9461\t9470\t1\t1\t1\t1\t61\t461\t1461\t4461\t9461\t122\t123\tXZAAAA\tGAOAAA\tOOOOxx\n3028\t9471\t0\t0\t8\t8\t28\t28\t1028\t3028\t3028\t56\t57\tMMAAAA\tHAOAAA\tVVVVxx\n6814\t9472\t0\t2\t4\t14\t14\t814\t814\t1814\t6814\t28\t29\tCCAAAA\tIAOAAA\tAAAAxx\n9587\t9473\t1\t3\t7\t7\t87\t587\t1587\t4587\t9587\t174\t175\tTEAAAA\tJAOAAA\tHHHHxx\n6863\t9474\t1\t3\t3\t3\t63\t863\t863\t1863\t6863\t126\t127\tZDAAAA\tKAOAAA\tOOOOxx\n4963\t9475\t1\t3\t3\t3\t63\t963\t963\t4963\t4963\t126\t127\tXIAAAA\tLAOAAA\tVVVVxx\n7811\t9476\t1\t3\t1\t11\t11\t811\t1811\t2811\t7811\t22\t23\tLOAAAA\tMAOAAA\tAAAAxx\n7608\t9477\t0\t0\t8\t8\t8\t608\t1608\t2608\t7608\t16\t17\tQGAAAA\tNAOAAA\tHHHHxx\n5321\t9478\t1\t1\t1\t1\t21\t321\t1321\t321\t5321\t42\t43\tRWAAAA\tOAOAAA\tOOOOxx\n9971\t9479\t1\t3\t1\t11\t71\t971\t1971\t4971\t9971\t142\t143\tNTAAAA\tPAOAAA\tVVVVxx\n6161\t9480\t1\t1\t1\t1\t61\t161\t161\t1161\t6161\t122\t123\tZCAAAA\tQAOAAA\tAAAAxx\n2181\t9481\t1\t1\t1\t1\t81\t181\t181\t2181\t2181\t162\t163\tXFAAAA\tRAOAAA\tHHHHxx\n3828\t9482\t0\t0\t8\t8\t28\t828\t1828\t3828\t3828\t56\t57\tGRAAAA\tSAOAAA\tOOOOxx\n348\t9483\t0\t0\t8\t8\t48\t348\t348\t348\t348\t96\t97\tKNAAAA\tTAOAAA\tVVVVxx\n5459\t9484\t1\t3\t9\t19\t59\t459\t1459\t459\t5459\t118\t119\tZBAAAA\tUAOAAA\tAAAAxx\n9406\t9485\t0\t2\t6\t6\t6\t406\t1406\t4406\t9406\t12\t13\tUXAAAA\tVAOAAA\tHHHHxx\n9852\t9486\t0\t0\t2\t12\t52\t852\t1852\t4852\t9852\t104\t105\tYOAAAA\tWAOAAA\tOOOOxx\n3095\t9487\t1\t3\t5\t15\t95\t95\t1095\t3095\t3095\t190\t191\tBPAAAA\tXAOAAA\tVVVVxx\n5597\t9488\t1\t1\t7\t17\t97\t597\t1597\t597\t5597\t194\t195\tHHAAAA\tYAOAAA\tAAAAxx\n8841\t9489\t1\t1\t1\t1\t41\t841\t841\t3841\t8841\t82\t83\tBCAAAA\tZAOAAA\tHHHHxx\n3536\t9490\t0\t0\t6\t16\t36\t536\t1536\t3536\t3536\t72\t73\tAGAAAA\tABOAAA\tOOOOxx\n4009\t9491\t1\t1\t9\t9\t9\t9\t9\t4009\t4009\t18\t19\tFYAAAA\tBBOAAA\tVVVVxx\n7366\t9492\t0\t2\t6\t6\t66\t366\t1366\t2366\t7366\t132\t133\tIXAAAA\tCBOAAA\tAAAAxx\n7327\t9493\t1\t3\t7\t7\t27\t327\t1327\t2327\t7327\t54\t55\tVVAAAA\tDBOAAA\tHHHHxx\n1613\t9494\t1\t1\t3\t13\t13\t613\t1613\t1613\t1613\t26\t27\tBKAAAA\tEBOAAA\tOOOOxx\n8619\t9495\t1\t3\t9\t19\t19\t619\t619\t3619\t8619\t38\t39\tNTAAAA\tFBOAAA\tVVVVxx\n4880\t9496\t0\t0\t0\t0\t80\t880\t880\t4880\t4880\t160\t161\tSFAAAA\tGBOAAA\tAAAAxx\n1552\t9497\t0\t0\t2\t12\t52\t552\t1552\t1552\t1552\t104\t105\tSHAAAA\tHBOAAA\tHHHHxx\n7636\t9498\t0\t0\t6\t16\t36\t636\t1636\t2636\t7636\t72\t73\tSHAAAA\tIBOAAA\tOOOOxx\n8397\t9499\t1\t1\t7\t17\t97\t397\t397\t3397\t8397\t194\t195\tZKAAAA\tJBOAAA\tVVVVxx\n6224\t9500\t0\t0\t4\t4\t24\t224\t224\t1224\t6224\t48\t49\tKFAAAA\tKBOAAA\tAAAAxx\n9102\t9501\t0\t2\t2\t2\t2\t102\t1102\t4102\t9102\t4\t5\tCMAAAA\tLBOAAA\tHHHHxx\n7906\t9502\t0\t2\t6\t6\t6\t906\t1906\t2906\t7906\t12\t13\tCSAAAA\tMBOAAA\tOOOOxx\n9467\t9503\t1\t3\t7\t7\t67\t467\t1467\t4467\t9467\t134\t135\tDAAAAA\tNBOAAA\tVVVVxx\n828\t9504\t0\t0\t8\t8\t28\t828\t828\t828\t828\t56\t57\tWFAAAA\tOBOAAA\tAAAAxx\n9585\t9505\t1\t1\t5\t5\t85\t585\t1585\t4585\t9585\t170\t171\tREAAAA\tPBOAAA\tHHHHxx\n925\t9506\t1\t1\t5\t5\t25\t925\t925\t925\t925\t50\t51\tPJAAAA\tQBOAAA\tOOOOxx\n7375\t9507\t1\t3\t5\t15\t75\t375\t1375\t2375\t7375\t150\t151\tRXAAAA\tRBOAAA\tVVVVxx\n4027\t9508\t1\t3\t7\t7\t27\t27\t27\t4027\t4027\t54\t55\tXYAAAA\tSBOAAA\tAAAAxx\n766\t9509\t0\t2\t6\t6\t66\t766\t766\t766\t766\t132\t133\tMDAAAA\tTBOAAA\tHHHHxx\n5633\t9510\t1\t1\t3\t13\t33\t633\t1633\t633\t5633\t66\t67\tRIAAAA\tUBOAAA\tOOOOxx\n5648\t9511\t0\t0\t8\t8\t48\t648\t1648\t648\t5648\t96\t97\tGJAAAA\tVBOAAA\tVVVVxx\n148\t9512\t0\t0\t8\t8\t48\t148\t148\t148\t148\t96\t97\tSFAAAA\tWBOAAA\tAAAAxx\n2072\t9513\t0\t0\t2\t12\t72\t72\t72\t2072\t2072\t144\t145\tSBAAAA\tXBOAAA\tHHHHxx\n431\t9514\t1\t3\t1\t11\t31\t431\t431\t431\t431\t62\t63\tPQAAAA\tYBOAAA\tOOOOxx\n1711\t9515\t1\t3\t1\t11\t11\t711\t1711\t1711\t1711\t22\t23\tVNAAAA\tZBOAAA\tVVVVxx\n9378\t9516\t0\t2\t8\t18\t78\t378\t1378\t4378\t9378\t156\t157\tSWAAAA\tACOAAA\tAAAAxx\n6776\t9517\t0\t0\t6\t16\t76\t776\t776\t1776\t6776\t152\t153\tQAAAAA\tBCOAAA\tHHHHxx\n6842\t9518\t0\t2\t2\t2\t42\t842\t842\t1842\t6842\t84\t85\tEDAAAA\tCCOAAA\tOOOOxx\n2656\t9519\t0\t0\t6\t16\t56\t656\t656\t2656\t2656\t112\t113\tEYAAAA\tDCOAAA\tVVVVxx\n3116\t9520\t0\t0\t6\t16\t16\t116\t1116\t3116\t3116\t32\t33\tWPAAAA\tECOAAA\tAAAAxx\n7904\t9521\t0\t0\t4\t4\t4\t904\t1904\t2904\t7904\t8\t9\tASAAAA\tFCOAAA\tHHHHxx\n3529\t9522\t1\t1\t9\t9\t29\t529\t1529\t3529\t3529\t58\t59\tTFAAAA\tGCOAAA\tOOOOxx\n3240\t9523\t0\t0\t0\t0\t40\t240\t1240\t3240\t3240\t80\t81\tQUAAAA\tHCOAAA\tVVVVxx\n5801\t9524\t1\t1\t1\t1\t1\t801\t1801\t801\t5801\t2\t3\tDPAAAA\tICOAAA\tAAAAxx\n4090\t9525\t0\t2\t0\t10\t90\t90\t90\t4090\t4090\t180\t181\tIBAAAA\tJCOAAA\tHHHHxx\n7687\t9526\t1\t3\t7\t7\t87\t687\t1687\t2687\t7687\t174\t175\tRJAAAA\tKCOAAA\tOOOOxx\n9711\t9527\t1\t3\t1\t11\t11\t711\t1711\t4711\t9711\t22\t23\tNJAAAA\tLCOAAA\tVVVVxx\n4760\t9528\t0\t0\t0\t0\t60\t760\t760\t4760\t4760\t120\t121\tCBAAAA\tMCOAAA\tAAAAxx\n5524\t9529\t0\t0\t4\t4\t24\t524\t1524\t524\t5524\t48\t49\tMEAAAA\tNCOAAA\tHHHHxx\n2251\t9530\t1\t3\t1\t11\t51\t251\t251\t2251\t2251\t102\t103\tPIAAAA\tOCOAAA\tOOOOxx\n1511\t9531\t1\t3\t1\t11\t11\t511\t1511\t1511\t1511\t22\t23\tDGAAAA\tPCOAAA\tVVVVxx\n5991\t9532\t1\t3\t1\t11\t91\t991\t1991\t991\t5991\t182\t183\tLWAAAA\tQCOAAA\tAAAAxx\n7808\t9533\t0\t0\t8\t8\t8\t808\t1808\t2808\t7808\t16\t17\tIOAAAA\tRCOAAA\tHHHHxx\n8708\t9534\t0\t0\t8\t8\t8\t708\t708\t3708\t8708\t16\t17\tYWAAAA\tSCOAAA\tOOOOxx\n8939\t9535\t1\t3\t9\t19\t39\t939\t939\t3939\t8939\t78\t79\tVFAAAA\tTCOAAA\tVVVVxx\n4295\t9536\t1\t3\t5\t15\t95\t295\t295\t4295\t4295\t190\t191\tFJAAAA\tUCOAAA\tAAAAxx\n5905\t9537\t1\t1\t5\t5\t5\t905\t1905\t905\t5905\t10\t11\tDTAAAA\tVCOAAA\tHHHHxx\n2649\t9538\t1\t1\t9\t9\t49\t649\t649\t2649\t2649\t98\t99\tXXAAAA\tWCOAAA\tOOOOxx\n2347\t9539\t1\t3\t7\t7\t47\t347\t347\t2347\t2347\t94\t95\tHMAAAA\tXCOAAA\tVVVVxx\n6339\t9540\t1\t3\t9\t19\t39\t339\t339\t1339\t6339\t78\t79\tVJAAAA\tYCOAAA\tAAAAxx\n292\t9541\t0\t0\t2\t12\t92\t292\t292\t292\t292\t184\t185\tGLAAAA\tZCOAAA\tHHHHxx\n9314\t9542\t0\t2\t4\t14\t14\t314\t1314\t4314\t9314\t28\t29\tGUAAAA\tADOAAA\tOOOOxx\n6893\t9543\t1\t1\t3\t13\t93\t893\t893\t1893\t6893\t186\t187\tDFAAAA\tBDOAAA\tVVVVxx\n3970\t9544\t0\t2\t0\t10\t70\t970\t1970\t3970\t3970\t140\t141\tSWAAAA\tCDOAAA\tAAAAxx\n1652\t9545\t0\t0\t2\t12\t52\t652\t1652\t1652\t1652\t104\t105\tOLAAAA\tDDOAAA\tHHHHxx\n4326\t9546\t0\t2\t6\t6\t26\t326\t326\t4326\t4326\t52\t53\tKKAAAA\tEDOAAA\tOOOOxx\n7881\t9547\t1\t1\t1\t1\t81\t881\t1881\t2881\t7881\t162\t163\tDRAAAA\tFDOAAA\tVVVVxx\n5291\t9548\t1\t3\t1\t11\t91\t291\t1291\t291\t5291\t182\t183\tNVAAAA\tGDOAAA\tAAAAxx\n957\t9549\t1\t1\t7\t17\t57\t957\t957\t957\t957\t114\t115\tVKAAAA\tHDOAAA\tHHHHxx\n2313\t9550\t1\t1\t3\t13\t13\t313\t313\t2313\t2313\t26\t27\tZKAAAA\tIDOAAA\tOOOOxx\n5463\t9551\t1\t3\t3\t3\t63\t463\t1463\t463\t5463\t126\t127\tDCAAAA\tJDOAAA\tVVVVxx\n1268\t9552\t0\t0\t8\t8\t68\t268\t1268\t1268\t1268\t136\t137\tUWAAAA\tKDOAAA\tAAAAxx\n5028\t9553\t0\t0\t8\t8\t28\t28\t1028\t28\t5028\t56\t57\tKLAAAA\tLDOAAA\tHHHHxx\n656\t9554\t0\t0\t6\t16\t56\t656\t656\t656\t656\t112\t113\tGZAAAA\tMDOAAA\tOOOOxx\n9274\t9555\t0\t2\t4\t14\t74\t274\t1274\t4274\t9274\t148\t149\tSSAAAA\tNDOAAA\tVVVVxx\n8217\t9556\t1\t1\t7\t17\t17\t217\t217\t3217\t8217\t34\t35\tBEAAAA\tODOAAA\tAAAAxx\n2175\t9557\t1\t3\t5\t15\t75\t175\t175\t2175\t2175\t150\t151\tRFAAAA\tPDOAAA\tHHHHxx\n6028\t9558\t0\t0\t8\t8\t28\t28\t28\t1028\t6028\t56\t57\tWXAAAA\tQDOAAA\tOOOOxx\n7584\t9559\t0\t0\t4\t4\t84\t584\t1584\t2584\t7584\t168\t169\tSFAAAA\tRDOAAA\tVVVVxx\n4114\t9560\t0\t2\t4\t14\t14\t114\t114\t4114\t4114\t28\t29\tGCAAAA\tSDOAAA\tAAAAxx\n8894\t9561\t0\t2\t4\t14\t94\t894\t894\t3894\t8894\t188\t189\tCEAAAA\tTDOAAA\tHHHHxx\n781\t9562\t1\t1\t1\t1\t81\t781\t781\t781\t781\t162\t163\tBEAAAA\tUDOAAA\tOOOOxx\n133\t9563\t1\t1\t3\t13\t33\t133\t133\t133\t133\t66\t67\tDFAAAA\tVDOAAA\tVVVVxx\n7572\t9564\t0\t0\t2\t12\t72\t572\t1572\t2572\t7572\t144\t145\tGFAAAA\tWDOAAA\tAAAAxx\n8514\t9565\t0\t2\t4\t14\t14\t514\t514\t3514\t8514\t28\t29\tMPAAAA\tXDOAAA\tHHHHxx\n3352\t9566\t0\t0\t2\t12\t52\t352\t1352\t3352\t3352\t104\t105\tYYAAAA\tYDOAAA\tOOOOxx\n8098\t9567\t0\t2\t8\t18\t98\t98\t98\t3098\t8098\t196\t197\tMZAAAA\tZDOAAA\tVVVVxx\n9116\t9568\t0\t0\t6\t16\t16\t116\t1116\t4116\t9116\t32\t33\tQMAAAA\tAEOAAA\tAAAAxx\n9444\t9569\t0\t0\t4\t4\t44\t444\t1444\t4444\t9444\t88\t89\tGZAAAA\tBEOAAA\tHHHHxx\n2590\t9570\t0\t2\t0\t10\t90\t590\t590\t2590\t2590\t180\t181\tQVAAAA\tCEOAAA\tOOOOxx\n7302\t9571\t0\t2\t2\t2\t2\t302\t1302\t2302\t7302\t4\t5\tWUAAAA\tDEOAAA\tVVVVxx\n7444\t9572\t0\t0\t4\t4\t44\t444\t1444\t2444\t7444\t88\t89\tIAAAAA\tEEOAAA\tAAAAxx\n8748\t9573\t0\t0\t8\t8\t48\t748\t748\t3748\t8748\t96\t97\tMYAAAA\tFEOAAA\tHHHHxx\n7615\t9574\t1\t3\t5\t15\t15\t615\t1615\t2615\t7615\t30\t31\tXGAAAA\tGEOAAA\tOOOOxx\n6090\t9575\t0\t2\t0\t10\t90\t90\t90\t1090\t6090\t180\t181\tGAAAAA\tHEOAAA\tVVVVxx\n1529\t9576\t1\t1\t9\t9\t29\t529\t1529\t1529\t1529\t58\t59\tVGAAAA\tIEOAAA\tAAAAxx\n9398\t9577\t0\t2\t8\t18\t98\t398\t1398\t4398\t9398\t196\t197\tMXAAAA\tJEOAAA\tHHHHxx\n6114\t9578\t0\t2\t4\t14\t14\t114\t114\t1114\t6114\t28\t29\tEBAAAA\tKEOAAA\tOOOOxx\n2736\t9579\t0\t0\t6\t16\t36\t736\t736\t2736\t2736\t72\t73\tGBAAAA\tLEOAAA\tVVVVxx\n468\t9580\t0\t0\t8\t8\t68\t468\t468\t468\t468\t136\t137\tASAAAA\tMEOAAA\tAAAAxx\n1487\t9581\t1\t3\t7\t7\t87\t487\t1487\t1487\t1487\t174\t175\tFFAAAA\tNEOAAA\tHHHHxx\n4784\t9582\t0\t0\t4\t4\t84\t784\t784\t4784\t4784\t168\t169\tACAAAA\tOEOAAA\tOOOOxx\n6731\t9583\t1\t3\t1\t11\t31\t731\t731\t1731\t6731\t62\t63\tXYAAAA\tPEOAAA\tVVVVxx\n3328\t9584\t0\t0\t8\t8\t28\t328\t1328\t3328\t3328\t56\t57\tAYAAAA\tQEOAAA\tAAAAxx\n6891\t9585\t1\t3\t1\t11\t91\t891\t891\t1891\t6891\t182\t183\tBFAAAA\tREOAAA\tHHHHxx\n8039\t9586\t1\t3\t9\t19\t39\t39\t39\t3039\t8039\t78\t79\tFXAAAA\tSEOAAA\tOOOOxx\n4064\t9587\t0\t0\t4\t4\t64\t64\t64\t4064\t4064\t128\t129\tIAAAAA\tTEOAAA\tVVVVxx\n542\t9588\t0\t2\t2\t2\t42\t542\t542\t542\t542\t84\t85\tWUAAAA\tUEOAAA\tAAAAxx\n1039\t9589\t1\t3\t9\t19\t39\t39\t1039\t1039\t1039\t78\t79\tZNAAAA\tVEOAAA\tHHHHxx\n5603\t9590\t1\t3\t3\t3\t3\t603\t1603\t603\t5603\t6\t7\tNHAAAA\tWEOAAA\tOOOOxx\n6641\t9591\t1\t1\t1\t1\t41\t641\t641\t1641\t6641\t82\t83\tLVAAAA\tXEOAAA\tVVVVxx\n6307\t9592\t1\t3\t7\t7\t7\t307\t307\t1307\t6307\t14\t15\tPIAAAA\tYEOAAA\tAAAAxx\n5354\t9593\t0\t2\t4\t14\t54\t354\t1354\t354\t5354\t108\t109\tYXAAAA\tZEOAAA\tHHHHxx\n7878\t9594\t0\t2\t8\t18\t78\t878\t1878\t2878\t7878\t156\t157\tARAAAA\tAFOAAA\tOOOOxx\n6391\t9595\t1\t3\t1\t11\t91\t391\t391\t1391\t6391\t182\t183\tVLAAAA\tBFOAAA\tVVVVxx\n4575\t9596\t1\t3\t5\t15\t75\t575\t575\t4575\t4575\t150\t151\tZTAAAA\tCFOAAA\tAAAAxx\n6644\t9597\t0\t0\t4\t4\t44\t644\t644\t1644\t6644\t88\t89\tOVAAAA\tDFOAAA\tHHHHxx\n5207\t9598\t1\t3\t7\t7\t7\t207\t1207\t207\t5207\t14\t15\tHSAAAA\tEFOAAA\tOOOOxx\n1736\t9599\t0\t0\t6\t16\t36\t736\t1736\t1736\t1736\t72\t73\tUOAAAA\tFFOAAA\tVVVVxx\n3547\t9600\t1\t3\t7\t7\t47\t547\t1547\t3547\t3547\t94\t95\tLGAAAA\tGFOAAA\tAAAAxx\n6647\t9601\t1\t3\t7\t7\t47\t647\t647\t1647\t6647\t94\t95\tRVAAAA\tHFOAAA\tHHHHxx\n4107\t9602\t1\t3\t7\t7\t7\t107\t107\t4107\t4107\t14\t15\tZBAAAA\tIFOAAA\tOOOOxx\n8125\t9603\t1\t1\t5\t5\t25\t125\t125\t3125\t8125\t50\t51\tNAAAAA\tJFOAAA\tVVVVxx\n9223\t9604\t1\t3\t3\t3\t23\t223\t1223\t4223\t9223\t46\t47\tTQAAAA\tKFOAAA\tAAAAxx\n6903\t9605\t1\t3\t3\t3\t3\t903\t903\t1903\t6903\t6\t7\tNFAAAA\tLFOAAA\tHHHHxx\n3639\t9606\t1\t3\t9\t19\t39\t639\t1639\t3639\t3639\t78\t79\tZJAAAA\tMFOAAA\tOOOOxx\n9606\t9607\t0\t2\t6\t6\t6\t606\t1606\t4606\t9606\t12\t13\tMFAAAA\tNFOAAA\tVVVVxx\n3232\t9608\t0\t0\t2\t12\t32\t232\t1232\t3232\t3232\t64\t65\tIUAAAA\tOFOAAA\tAAAAxx\n2063\t9609\t1\t3\t3\t3\t63\t63\t63\t2063\t2063\t126\t127\tJBAAAA\tPFOAAA\tHHHHxx\n3731\t9610\t1\t3\t1\t11\t31\t731\t1731\t3731\t3731\t62\t63\tNNAAAA\tQFOAAA\tOOOOxx\n2558\t9611\t0\t2\t8\t18\t58\t558\t558\t2558\t2558\t116\t117\tKUAAAA\tRFOAAA\tVVVVxx\n2357\t9612\t1\t1\t7\t17\t57\t357\t357\t2357\t2357\t114\t115\tRMAAAA\tSFOAAA\tAAAAxx\n6008\t9613\t0\t0\t8\t8\t8\t8\t8\t1008\t6008\t16\t17\tCXAAAA\tTFOAAA\tHHHHxx\n8246\t9614\t0\t2\t6\t6\t46\t246\t246\t3246\t8246\t92\t93\tEFAAAA\tUFOAAA\tOOOOxx\n8220\t9615\t0\t0\t0\t0\t20\t220\t220\t3220\t8220\t40\t41\tEEAAAA\tVFOAAA\tVVVVxx\n1075\t9616\t1\t3\t5\t15\t75\t75\t1075\t1075\t1075\t150\t151\tJPAAAA\tWFOAAA\tAAAAxx\n2410\t9617\t0\t2\t0\t10\t10\t410\t410\t2410\t2410\t20\t21\tSOAAAA\tXFOAAA\tHHHHxx\n3253\t9618\t1\t1\t3\t13\t53\t253\t1253\t3253\t3253\t106\t107\tDVAAAA\tYFOAAA\tOOOOxx\n4370\t9619\t0\t2\t0\t10\t70\t370\t370\t4370\t4370\t140\t141\tCMAAAA\tZFOAAA\tVVVVxx\n8426\t9620\t0\t2\t6\t6\t26\t426\t426\t3426\t8426\t52\t53\tCMAAAA\tAGOAAA\tAAAAxx\n2262\t9621\t0\t2\t2\t2\t62\t262\t262\t2262\t2262\t124\t125\tAJAAAA\tBGOAAA\tHHHHxx\n4149\t9622\t1\t1\t9\t9\t49\t149\t149\t4149\t4149\t98\t99\tPDAAAA\tCGOAAA\tOOOOxx\n2732\t9623\t0\t0\t2\t12\t32\t732\t732\t2732\t2732\t64\t65\tCBAAAA\tDGOAAA\tVVVVxx\n8606\t9624\t0\t2\t6\t6\t6\t606\t606\t3606\t8606\t12\t13\tATAAAA\tEGOAAA\tAAAAxx\n6311\t9625\t1\t3\t1\t11\t11\t311\t311\t1311\t6311\t22\t23\tTIAAAA\tFGOAAA\tHHHHxx\n7223\t9626\t1\t3\t3\t3\t23\t223\t1223\t2223\t7223\t46\t47\tVRAAAA\tGGOAAA\tOOOOxx\n3054\t9627\t0\t2\t4\t14\t54\t54\t1054\t3054\t3054\t108\t109\tMNAAAA\tHGOAAA\tVVVVxx\n3952\t9628\t0\t0\t2\t12\t52\t952\t1952\t3952\t3952\t104\t105\tAWAAAA\tIGOAAA\tAAAAxx\n8252\t9629\t0\t0\t2\t12\t52\t252\t252\t3252\t8252\t104\t105\tKFAAAA\tJGOAAA\tHHHHxx\n6020\t9630\t0\t0\t0\t0\t20\t20\t20\t1020\t6020\t40\t41\tOXAAAA\tKGOAAA\tOOOOxx\n3846\t9631\t0\t2\t6\t6\t46\t846\t1846\t3846\t3846\t92\t93\tYRAAAA\tLGOAAA\tVVVVxx\n3755\t9632\t1\t3\t5\t15\t55\t755\t1755\t3755\t3755\t110\t111\tLOAAAA\tMGOAAA\tAAAAxx\n3765\t9633\t1\t1\t5\t5\t65\t765\t1765\t3765\t3765\t130\t131\tVOAAAA\tNGOAAA\tHHHHxx\n3434\t9634\t0\t2\t4\t14\t34\t434\t1434\t3434\t3434\t68\t69\tCCAAAA\tOGOAAA\tOOOOxx\n1381\t9635\t1\t1\t1\t1\t81\t381\t1381\t1381\t1381\t162\t163\tDBAAAA\tPGOAAA\tVVVVxx\n287\t9636\t1\t3\t7\t7\t87\t287\t287\t287\t287\t174\t175\tBLAAAA\tQGOAAA\tAAAAxx\n4476\t9637\t0\t0\t6\t16\t76\t476\t476\t4476\t4476\t152\t153\tEQAAAA\tRGOAAA\tHHHHxx\n2916\t9638\t0\t0\t6\t16\t16\t916\t916\t2916\t2916\t32\t33\tEIAAAA\tSGOAAA\tOOOOxx\n4517\t9639\t1\t1\t7\t17\t17\t517\t517\t4517\t4517\t34\t35\tTRAAAA\tTGOAAA\tVVVVxx\n4561\t9640\t1\t1\t1\t1\t61\t561\t561\t4561\t4561\t122\t123\tLTAAAA\tUGOAAA\tAAAAxx\n5106\t9641\t0\t2\t6\t6\t6\t106\t1106\t106\t5106\t12\t13\tKOAAAA\tVGOAAA\tHHHHxx\n2077\t9642\t1\t1\t7\t17\t77\t77\t77\t2077\t2077\t154\t155\tXBAAAA\tWGOAAA\tOOOOxx\n5269\t9643\t1\t1\t9\t9\t69\t269\t1269\t269\t5269\t138\t139\tRUAAAA\tXGOAAA\tVVVVxx\n5688\t9644\t0\t0\t8\t8\t88\t688\t1688\t688\t5688\t176\t177\tUKAAAA\tYGOAAA\tAAAAxx\n8831\t9645\t1\t3\t1\t11\t31\t831\t831\t3831\t8831\t62\t63\tRBAAAA\tZGOAAA\tHHHHxx\n3867\t9646\t1\t3\t7\t7\t67\t867\t1867\t3867\t3867\t134\t135\tTSAAAA\tAHOAAA\tOOOOxx\n6062\t9647\t0\t2\t2\t2\t62\t62\t62\t1062\t6062\t124\t125\tEZAAAA\tBHOAAA\tVVVVxx\n8460\t9648\t0\t0\t0\t0\t60\t460\t460\t3460\t8460\t120\t121\tKNAAAA\tCHOAAA\tAAAAxx\n3138\t9649\t0\t2\t8\t18\t38\t138\t1138\t3138\t3138\t76\t77\tSQAAAA\tDHOAAA\tHHHHxx\n3173\t9650\t1\t1\t3\t13\t73\t173\t1173\t3173\t3173\t146\t147\tBSAAAA\tEHOAAA\tOOOOxx\n7018\t9651\t0\t2\t8\t18\t18\t18\t1018\t2018\t7018\t36\t37\tYJAAAA\tFHOAAA\tVVVVxx\n4836\t9652\t0\t0\t6\t16\t36\t836\t836\t4836\t4836\t72\t73\tAEAAAA\tGHOAAA\tAAAAxx\n1007\t9653\t1\t3\t7\t7\t7\t7\t1007\t1007\t1007\t14\t15\tTMAAAA\tHHOAAA\tHHHHxx\n658\t9654\t0\t2\t8\t18\t58\t658\t658\t658\t658\t116\t117\tIZAAAA\tIHOAAA\tOOOOxx\n5205\t9655\t1\t1\t5\t5\t5\t205\t1205\t205\t5205\t10\t11\tFSAAAA\tJHOAAA\tVVVVxx\n5805\t9656\t1\t1\t5\t5\t5\t805\t1805\t805\t5805\t10\t11\tHPAAAA\tKHOAAA\tAAAAxx\n5959\t9657\t1\t3\t9\t19\t59\t959\t1959\t959\t5959\t118\t119\tFVAAAA\tLHOAAA\tHHHHxx\n2863\t9658\t1\t3\t3\t3\t63\t863\t863\t2863\t2863\t126\t127\tDGAAAA\tMHOAAA\tOOOOxx\n7272\t9659\t0\t0\t2\t12\t72\t272\t1272\t2272\t7272\t144\t145\tSTAAAA\tNHOAAA\tVVVVxx\n8437\t9660\t1\t1\t7\t17\t37\t437\t437\t3437\t8437\t74\t75\tNMAAAA\tOHOAAA\tAAAAxx\n4900\t9661\t0\t0\t0\t0\t0\t900\t900\t4900\t4900\t0\t1\tMGAAAA\tPHOAAA\tHHHHxx\n890\t9662\t0\t2\t0\t10\t90\t890\t890\t890\t890\t180\t181\tGIAAAA\tQHOAAA\tOOOOxx\n3530\t9663\t0\t2\t0\t10\t30\t530\t1530\t3530\t3530\t60\t61\tUFAAAA\tRHOAAA\tVVVVxx\n6209\t9664\t1\t1\t9\t9\t9\t209\t209\t1209\t6209\t18\t19\tVEAAAA\tSHOAAA\tAAAAxx\n4595\t9665\t1\t3\t5\t15\t95\t595\t595\t4595\t4595\t190\t191\tTUAAAA\tTHOAAA\tHHHHxx\n5982\t9666\t0\t2\t2\t2\t82\t982\t1982\t982\t5982\t164\t165\tCWAAAA\tUHOAAA\tOOOOxx\n1101\t9667\t1\t1\t1\t1\t1\t101\t1101\t1101\t1101\t2\t3\tJQAAAA\tVHOAAA\tVVVVxx\n9555\t9668\t1\t3\t5\t15\t55\t555\t1555\t4555\t9555\t110\t111\tNDAAAA\tWHOAAA\tAAAAxx\n1918\t9669\t0\t2\t8\t18\t18\t918\t1918\t1918\t1918\t36\t37\tUVAAAA\tXHOAAA\tHHHHxx\n3527\t9670\t1\t3\t7\t7\t27\t527\t1527\t3527\t3527\t54\t55\tRFAAAA\tYHOAAA\tOOOOxx\n7309\t9671\t1\t1\t9\t9\t9\t309\t1309\t2309\t7309\t18\t19\tDVAAAA\tZHOAAA\tVVVVxx\n8213\t9672\t1\t1\t3\t13\t13\t213\t213\t3213\t8213\t26\t27\tXDAAAA\tAIOAAA\tAAAAxx\n306\t9673\t0\t2\t6\t6\t6\t306\t306\t306\t306\t12\t13\tULAAAA\tBIOAAA\tHHHHxx\n845\t9674\t1\t1\t5\t5\t45\t845\t845\t845\t845\t90\t91\tNGAAAA\tCIOAAA\tOOOOxx\n16\t9675\t0\t0\t6\t16\t16\t16\t16\t16\t16\t32\t33\tQAAAAA\tDIOAAA\tVVVVxx\n437\t9676\t1\t1\t7\t17\t37\t437\t437\t437\t437\t74\t75\tVQAAAA\tEIOAAA\tAAAAxx\n9518\t9677\t0\t2\t8\t18\t18\t518\t1518\t4518\t9518\t36\t37\tCCAAAA\tFIOAAA\tHHHHxx\n2142\t9678\t0\t2\t2\t2\t42\t142\t142\t2142\t2142\t84\t85\tKEAAAA\tGIOAAA\tOOOOxx\n8121\t9679\t1\t1\t1\t1\t21\t121\t121\t3121\t8121\t42\t43\tJAAAAA\tHIOAAA\tVVVVxx\n7354\t9680\t0\t2\t4\t14\t54\t354\t1354\t2354\t7354\t108\t109\tWWAAAA\tIIOAAA\tAAAAxx\n1720\t9681\t0\t0\t0\t0\t20\t720\t1720\t1720\t1720\t40\t41\tEOAAAA\tJIOAAA\tHHHHxx\n6078\t9682\t0\t2\t8\t18\t78\t78\t78\t1078\t6078\t156\t157\tUZAAAA\tKIOAAA\tOOOOxx\n5929\t9683\t1\t1\t9\t9\t29\t929\t1929\t929\t5929\t58\t59\tBUAAAA\tLIOAAA\tVVVVxx\n3856\t9684\t0\t0\t6\t16\t56\t856\t1856\t3856\t3856\t112\t113\tISAAAA\tMIOAAA\tAAAAxx\n3424\t9685\t0\t0\t4\t4\t24\t424\t1424\t3424\t3424\t48\t49\tSBAAAA\tNIOAAA\tHHHHxx\n1712\t9686\t0\t0\t2\t12\t12\t712\t1712\t1712\t1712\t24\t25\tWNAAAA\tOIOAAA\tOOOOxx\n2340\t9687\t0\t0\t0\t0\t40\t340\t340\t2340\t2340\t80\t81\tAMAAAA\tPIOAAA\tVVVVxx\n5570\t9688\t0\t2\t0\t10\t70\t570\t1570\t570\t5570\t140\t141\tGGAAAA\tQIOAAA\tAAAAxx\n8734\t9689\t0\t2\t4\t14\t34\t734\t734\t3734\t8734\t68\t69\tYXAAAA\tRIOAAA\tHHHHxx\n6077\t9690\t1\t1\t7\t17\t77\t77\t77\t1077\t6077\t154\t155\tTZAAAA\tSIOAAA\tOOOOxx\n2960\t9691\t0\t0\t0\t0\t60\t960\t960\t2960\t2960\t120\t121\tWJAAAA\tTIOAAA\tVVVVxx\n5062\t9692\t0\t2\t2\t2\t62\t62\t1062\t62\t5062\t124\t125\tSMAAAA\tUIOAAA\tAAAAxx\n1532\t9693\t0\t0\t2\t12\t32\t532\t1532\t1532\t1532\t64\t65\tYGAAAA\tVIOAAA\tHHHHxx\n8298\t9694\t0\t2\t8\t18\t98\t298\t298\t3298\t8298\t196\t197\tEHAAAA\tWIOAAA\tOOOOxx\n2496\t9695\t0\t0\t6\t16\t96\t496\t496\t2496\t2496\t192\t193\tASAAAA\tXIOAAA\tVVVVxx\n8412\t9696\t0\t0\t2\t12\t12\t412\t412\t3412\t8412\t24\t25\tOLAAAA\tYIOAAA\tAAAAxx\n724\t9697\t0\t0\t4\t4\t24\t724\t724\t724\t724\t48\t49\tWBAAAA\tZIOAAA\tHHHHxx\n1019\t9698\t1\t3\t9\t19\t19\t19\t1019\t1019\t1019\t38\t39\tFNAAAA\tAJOAAA\tOOOOxx\n6265\t9699\t1\t1\t5\t5\t65\t265\t265\t1265\t6265\t130\t131\tZGAAAA\tBJOAAA\tVVVVxx\n740\t9700\t0\t0\t0\t0\t40\t740\t740\t740\t740\t80\t81\tMCAAAA\tCJOAAA\tAAAAxx\n8495\t9701\t1\t3\t5\t15\t95\t495\t495\t3495\t8495\t190\t191\tTOAAAA\tDJOAAA\tHHHHxx\n6983\t9702\t1\t3\t3\t3\t83\t983\t983\t1983\t6983\t166\t167\tPIAAAA\tEJOAAA\tOOOOxx\n991\t9703\t1\t3\t1\t11\t91\t991\t991\t991\t991\t182\t183\tDMAAAA\tFJOAAA\tVVVVxx\n3189\t9704\t1\t1\t9\t9\t89\t189\t1189\t3189\t3189\t178\t179\tRSAAAA\tGJOAAA\tAAAAxx\n4487\t9705\t1\t3\t7\t7\t87\t487\t487\t4487\t4487\t174\t175\tPQAAAA\tHJOAAA\tHHHHxx\n5554\t9706\t0\t2\t4\t14\t54\t554\t1554\t554\t5554\t108\t109\tQFAAAA\tIJOAAA\tOOOOxx\n1258\t9707\t0\t2\t8\t18\t58\t258\t1258\t1258\t1258\t116\t117\tKWAAAA\tJJOAAA\tVVVVxx\n5359\t9708\t1\t3\t9\t19\t59\t359\t1359\t359\t5359\t118\t119\tDYAAAA\tKJOAAA\tAAAAxx\n2709\t9709\t1\t1\t9\t9\t9\t709\t709\t2709\t2709\t18\t19\tFAAAAA\tLJOAAA\tHHHHxx\n361\t9710\t1\t1\t1\t1\t61\t361\t361\t361\t361\t122\t123\tXNAAAA\tMJOAAA\tOOOOxx\n4028\t9711\t0\t0\t8\t8\t28\t28\t28\t4028\t4028\t56\t57\tYYAAAA\tNJOAAA\tVVVVxx\n3735\t9712\t1\t3\t5\t15\t35\t735\t1735\t3735\t3735\t70\t71\tRNAAAA\tOJOAAA\tAAAAxx\n4427\t9713\t1\t3\t7\t7\t27\t427\t427\t4427\t4427\t54\t55\tHOAAAA\tPJOAAA\tHHHHxx\n7540\t9714\t0\t0\t0\t0\t40\t540\t1540\t2540\t7540\t80\t81\tAEAAAA\tQJOAAA\tOOOOxx\n3569\t9715\t1\t1\t9\t9\t69\t569\t1569\t3569\t3569\t138\t139\tHHAAAA\tRJOAAA\tVVVVxx\n1916\t9716\t0\t0\t6\t16\t16\t916\t1916\t1916\t1916\t32\t33\tSVAAAA\tSJOAAA\tAAAAxx\n7596\t9717\t0\t0\t6\t16\t96\t596\t1596\t2596\t7596\t192\t193\tEGAAAA\tTJOAAA\tHHHHxx\n9721\t9718\t1\t1\t1\t1\t21\t721\t1721\t4721\t9721\t42\t43\tXJAAAA\tUJOAAA\tOOOOxx\n4429\t9719\t1\t1\t9\t9\t29\t429\t429\t4429\t4429\t58\t59\tJOAAAA\tVJOAAA\tVVVVxx\n3471\t9720\t1\t3\t1\t11\t71\t471\t1471\t3471\t3471\t142\t143\tNDAAAA\tWJOAAA\tAAAAxx\n1157\t9721\t1\t1\t7\t17\t57\t157\t1157\t1157\t1157\t114\t115\tNSAAAA\tXJOAAA\tHHHHxx\n5700\t9722\t0\t0\t0\t0\t0\t700\t1700\t700\t5700\t0\t1\tGLAAAA\tYJOAAA\tOOOOxx\n4431\t9723\t1\t3\t1\t11\t31\t431\t431\t4431\t4431\t62\t63\tLOAAAA\tZJOAAA\tVVVVxx\n9409\t9724\t1\t1\t9\t9\t9\t409\t1409\t4409\t9409\t18\t19\tXXAAAA\tAKOAAA\tAAAAxx\n8752\t9725\t0\t0\t2\t12\t52\t752\t752\t3752\t8752\t104\t105\tQYAAAA\tBKOAAA\tHHHHxx\n9484\t9726\t0\t0\t4\t4\t84\t484\t1484\t4484\t9484\t168\t169\tUAAAAA\tCKOAAA\tOOOOxx\n1266\t9727\t0\t2\t6\t6\t66\t266\t1266\t1266\t1266\t132\t133\tSWAAAA\tDKOAAA\tVVVVxx\n9097\t9728\t1\t1\t7\t17\t97\t97\t1097\t4097\t9097\t194\t195\tXLAAAA\tEKOAAA\tAAAAxx\n3068\t9729\t0\t0\t8\t8\t68\t68\t1068\t3068\t3068\t136\t137\tAOAAAA\tFKOAAA\tHHHHxx\n5490\t9730\t0\t2\t0\t10\t90\t490\t1490\t490\t5490\t180\t181\tEDAAAA\tGKOAAA\tOOOOxx\n1375\t9731\t1\t3\t5\t15\t75\t375\t1375\t1375\t1375\t150\t151\tXAAAAA\tHKOAAA\tVVVVxx\n2487\t9732\t1\t3\t7\t7\t87\t487\t487\t2487\t2487\t174\t175\tRRAAAA\tIKOAAA\tAAAAxx\n1705\t9733\t1\t1\t5\t5\t5\t705\t1705\t1705\t1705\t10\t11\tPNAAAA\tJKOAAA\tHHHHxx\n1571\t9734\t1\t3\t1\t11\t71\t571\t1571\t1571\t1571\t142\t143\tLIAAAA\tKKOAAA\tOOOOxx\n4005\t9735\t1\t1\t5\t5\t5\t5\t5\t4005\t4005\t10\t11\tBYAAAA\tLKOAAA\tVVVVxx\n5497\t9736\t1\t1\t7\t17\t97\t497\t1497\t497\t5497\t194\t195\tLDAAAA\tMKOAAA\tAAAAxx\n2144\t9737\t0\t0\t4\t4\t44\t144\t144\t2144\t2144\t88\t89\tMEAAAA\tNKOAAA\tHHHHxx\n4052\t9738\t0\t0\t2\t12\t52\t52\t52\t4052\t4052\t104\t105\tWZAAAA\tOKOAAA\tOOOOxx\n4942\t9739\t0\t2\t2\t2\t42\t942\t942\t4942\t4942\t84\t85\tCIAAAA\tPKOAAA\tVVVVxx\n5504\t9740\t0\t0\t4\t4\t4\t504\t1504\t504\t5504\t8\t9\tSDAAAA\tQKOAAA\tAAAAxx\n2913\t9741\t1\t1\t3\t13\t13\t913\t913\t2913\t2913\t26\t27\tBIAAAA\tRKOAAA\tHHHHxx\n5617\t9742\t1\t1\t7\t17\t17\t617\t1617\t617\t5617\t34\t35\tBIAAAA\tSKOAAA\tOOOOxx\n8179\t9743\t1\t3\t9\t19\t79\t179\t179\t3179\t8179\t158\t159\tPCAAAA\tTKOAAA\tVVVVxx\n9437\t9744\t1\t1\t7\t17\t37\t437\t1437\t4437\t9437\t74\t75\tZYAAAA\tUKOAAA\tAAAAxx\n1821\t9745\t1\t1\t1\t1\t21\t821\t1821\t1821\t1821\t42\t43\tBSAAAA\tVKOAAA\tHHHHxx\n5737\t9746\t1\t1\t7\t17\t37\t737\t1737\t737\t5737\t74\t75\tRMAAAA\tWKOAAA\tOOOOxx\n4207\t9747\t1\t3\t7\t7\t7\t207\t207\t4207\t4207\t14\t15\tVFAAAA\tXKOAAA\tVVVVxx\n4815\t9748\t1\t3\t5\t15\t15\t815\t815\t4815\t4815\t30\t31\tFDAAAA\tYKOAAA\tAAAAxx\n8707\t9749\t1\t3\t7\t7\t7\t707\t707\t3707\t8707\t14\t15\tXWAAAA\tZKOAAA\tHHHHxx\n5970\t9750\t0\t2\t0\t10\t70\t970\t1970\t970\t5970\t140\t141\tQVAAAA\tALOAAA\tOOOOxx\n5501\t9751\t1\t1\t1\t1\t1\t501\t1501\t501\t5501\t2\t3\tPDAAAA\tBLOAAA\tVVVVxx\n4013\t9752\t1\t1\t3\t13\t13\t13\t13\t4013\t4013\t26\t27\tJYAAAA\tCLOAAA\tAAAAxx\n9235\t9753\t1\t3\t5\t15\t35\t235\t1235\t4235\t9235\t70\t71\tFRAAAA\tDLOAAA\tHHHHxx\n2503\t9754\t1\t3\t3\t3\t3\t503\t503\t2503\t2503\t6\t7\tHSAAAA\tELOAAA\tOOOOxx\n9181\t9755\t1\t1\t1\t1\t81\t181\t1181\t4181\t9181\t162\t163\tDPAAAA\tFLOAAA\tVVVVxx\n2289\t9756\t1\t1\t9\t9\t89\t289\t289\t2289\t2289\t178\t179\tBKAAAA\tGLOAAA\tAAAAxx\n4256\t9757\t0\t0\t6\t16\t56\t256\t256\t4256\t4256\t112\t113\tSHAAAA\tHLOAAA\tHHHHxx\n191\t9758\t1\t3\t1\t11\t91\t191\t191\t191\t191\t182\t183\tJHAAAA\tILOAAA\tOOOOxx\n9655\t9759\t1\t3\t5\t15\t55\t655\t1655\t4655\t9655\t110\t111\tJHAAAA\tJLOAAA\tVVVVxx\n8615\t9760\t1\t3\t5\t15\t15\t615\t615\t3615\t8615\t30\t31\tJTAAAA\tKLOAAA\tAAAAxx\n3011\t9761\t1\t3\t1\t11\t11\t11\t1011\t3011\t3011\t22\t23\tVLAAAA\tLLOAAA\tHHHHxx\n6376\t9762\t0\t0\t6\t16\t76\t376\t376\t1376\t6376\t152\t153\tGLAAAA\tMLOAAA\tOOOOxx\n68\t9763\t0\t0\t8\t8\t68\t68\t68\t68\t68\t136\t137\tQCAAAA\tNLOAAA\tVVVVxx\n4720\t9764\t0\t0\t0\t0\t20\t720\t720\t4720\t4720\t40\t41\tOZAAAA\tOLOAAA\tAAAAxx\n6848\t9765\t0\t0\t8\t8\t48\t848\t848\t1848\t6848\t96\t97\tKDAAAA\tPLOAAA\tHHHHxx\n456\t9766\t0\t0\t6\t16\t56\t456\t456\t456\t456\t112\t113\tORAAAA\tQLOAAA\tOOOOxx\n5887\t9767\t1\t3\t7\t7\t87\t887\t1887\t887\t5887\t174\t175\tLSAAAA\tRLOAAA\tVVVVxx\n9249\t9768\t1\t1\t9\t9\t49\t249\t1249\t4249\t9249\t98\t99\tTRAAAA\tSLOAAA\tAAAAxx\n4041\t9769\t1\t1\t1\t1\t41\t41\t41\t4041\t4041\t82\t83\tLZAAAA\tTLOAAA\tHHHHxx\n2304\t9770\t0\t0\t4\t4\t4\t304\t304\t2304\t2304\t8\t9\tQKAAAA\tULOAAA\tOOOOxx\n8763\t9771\t1\t3\t3\t3\t63\t763\t763\t3763\t8763\t126\t127\tBZAAAA\tVLOAAA\tVVVVxx\n2115\t9772\t1\t3\t5\t15\t15\t115\t115\t2115\t2115\t30\t31\tJDAAAA\tWLOAAA\tAAAAxx\n8014\t9773\t0\t2\t4\t14\t14\t14\t14\t3014\t8014\t28\t29\tGWAAAA\tXLOAAA\tHHHHxx\n9895\t9774\t1\t3\t5\t15\t95\t895\t1895\t4895\t9895\t190\t191\tPQAAAA\tYLOAAA\tOOOOxx\n671\t9775\t1\t3\t1\t11\t71\t671\t671\t671\t671\t142\t143\tVZAAAA\tZLOAAA\tVVVVxx\n3774\t9776\t0\t2\t4\t14\t74\t774\t1774\t3774\t3774\t148\t149\tEPAAAA\tAMOAAA\tAAAAxx\n134\t9777\t0\t2\t4\t14\t34\t134\t134\t134\t134\t68\t69\tEFAAAA\tBMOAAA\tHHHHxx\n534\t9778\t0\t2\t4\t14\t34\t534\t534\t534\t534\t68\t69\tOUAAAA\tCMOAAA\tOOOOxx\n7308\t9779\t0\t0\t8\t8\t8\t308\t1308\t2308\t7308\t16\t17\tCVAAAA\tDMOAAA\tVVVVxx\n5244\t9780\t0\t0\t4\t4\t44\t244\t1244\t244\t5244\t88\t89\tSTAAAA\tEMOAAA\tAAAAxx\n1512\t9781\t0\t0\t2\t12\t12\t512\t1512\t1512\t1512\t24\t25\tEGAAAA\tFMOAAA\tHHHHxx\n8960\t9782\t0\t0\t0\t0\t60\t960\t960\t3960\t8960\t120\t121\tQGAAAA\tGMOAAA\tOOOOxx\n6602\t9783\t0\t2\t2\t2\t2\t602\t602\t1602\t6602\t4\t5\tYTAAAA\tHMOAAA\tVVVVxx\n593\t9784\t1\t1\t3\t13\t93\t593\t593\t593\t593\t186\t187\tVWAAAA\tIMOAAA\tAAAAxx\n2353\t9785\t1\t1\t3\t13\t53\t353\t353\t2353\t2353\t106\t107\tNMAAAA\tJMOAAA\tHHHHxx\n4139\t9786\t1\t3\t9\t19\t39\t139\t139\t4139\t4139\t78\t79\tFDAAAA\tKMOAAA\tOOOOxx\n3063\t9787\t1\t3\t3\t3\t63\t63\t1063\t3063\t3063\t126\t127\tVNAAAA\tLMOAAA\tVVVVxx\n652\t9788\t0\t0\t2\t12\t52\t652\t652\t652\t652\t104\t105\tCZAAAA\tMMOAAA\tAAAAxx\n7405\t9789\t1\t1\t5\t5\t5\t405\t1405\t2405\t7405\t10\t11\tVYAAAA\tNMOAAA\tHHHHxx\n3034\t9790\t0\t2\t4\t14\t34\t34\t1034\t3034\t3034\t68\t69\tSMAAAA\tOMOAAA\tOOOOxx\n4614\t9791\t0\t2\t4\t14\t14\t614\t614\t4614\t4614\t28\t29\tMVAAAA\tPMOAAA\tVVVVxx\n2351\t9792\t1\t3\t1\t11\t51\t351\t351\t2351\t2351\t102\t103\tLMAAAA\tQMOAAA\tAAAAxx\n8208\t9793\t0\t0\t8\t8\t8\t208\t208\t3208\t8208\t16\t17\tSDAAAA\tRMOAAA\tHHHHxx\n5475\t9794\t1\t3\t5\t15\t75\t475\t1475\t475\t5475\t150\t151\tPCAAAA\tSMOAAA\tOOOOxx\n6875\t9795\t1\t3\t5\t15\t75\t875\t875\t1875\t6875\t150\t151\tLEAAAA\tTMOAAA\tVVVVxx\n563\t9796\t1\t3\t3\t3\t63\t563\t563\t563\t563\t126\t127\tRVAAAA\tUMOAAA\tAAAAxx\n3346\t9797\t0\t2\t6\t6\t46\t346\t1346\t3346\t3346\t92\t93\tSYAAAA\tVMOAAA\tHHHHxx\n291\t9798\t1\t3\t1\t11\t91\t291\t291\t291\t291\t182\t183\tFLAAAA\tWMOAAA\tOOOOxx\n6345\t9799\t1\t1\t5\t5\t45\t345\t345\t1345\t6345\t90\t91\tBKAAAA\tXMOAAA\tVVVVxx\n8099\t9800\t1\t3\t9\t19\t99\t99\t99\t3099\t8099\t198\t199\tNZAAAA\tYMOAAA\tAAAAxx\n2078\t9801\t0\t2\t8\t18\t78\t78\t78\t2078\t2078\t156\t157\tYBAAAA\tZMOAAA\tHHHHxx\n8238\t9802\t0\t2\t8\t18\t38\t238\t238\t3238\t8238\t76\t77\tWEAAAA\tANOAAA\tOOOOxx\n4482\t9803\t0\t2\t2\t2\t82\t482\t482\t4482\t4482\t164\t165\tKQAAAA\tBNOAAA\tVVVVxx\n716\t9804\t0\t0\t6\t16\t16\t716\t716\t716\t716\t32\t33\tOBAAAA\tCNOAAA\tAAAAxx\n7288\t9805\t0\t0\t8\t8\t88\t288\t1288\t2288\t7288\t176\t177\tIUAAAA\tDNOAAA\tHHHHxx\n5906\t9806\t0\t2\t6\t6\t6\t906\t1906\t906\t5906\t12\t13\tETAAAA\tENOAAA\tOOOOxx\n5618\t9807\t0\t2\t8\t18\t18\t618\t1618\t618\t5618\t36\t37\tCIAAAA\tFNOAAA\tVVVVxx\n1141\t9808\t1\t1\t1\t1\t41\t141\t1141\t1141\t1141\t82\t83\tXRAAAA\tGNOAAA\tAAAAxx\n8231\t9809\t1\t3\t1\t11\t31\t231\t231\t3231\t8231\t62\t63\tPEAAAA\tHNOAAA\tHHHHxx\n3713\t9810\t1\t1\t3\t13\t13\t713\t1713\t3713\t3713\t26\t27\tVMAAAA\tINOAAA\tOOOOxx\n9158\t9811\t0\t2\t8\t18\t58\t158\t1158\t4158\t9158\t116\t117\tGOAAAA\tJNOAAA\tVVVVxx\n4051\t9812\t1\t3\t1\t11\t51\t51\t51\t4051\t4051\t102\t103\tVZAAAA\tKNOAAA\tAAAAxx\n1973\t9813\t1\t1\t3\t13\t73\t973\t1973\t1973\t1973\t146\t147\tXXAAAA\tLNOAAA\tHHHHxx\n6710\t9814\t0\t2\t0\t10\t10\t710\t710\t1710\t6710\t20\t21\tCYAAAA\tMNOAAA\tOOOOxx\n1021\t9815\t1\t1\t1\t1\t21\t21\t1021\t1021\t1021\t42\t43\tHNAAAA\tNNOAAA\tVVVVxx\n2196\t9816\t0\t0\t6\t16\t96\t196\t196\t2196\t2196\t192\t193\tMGAAAA\tONOAAA\tAAAAxx\n8335\t9817\t1\t3\t5\t15\t35\t335\t335\t3335\t8335\t70\t71\tPIAAAA\tPNOAAA\tHHHHxx\n2272\t9818\t0\t0\t2\t12\t72\t272\t272\t2272\t2272\t144\t145\tKJAAAA\tQNOAAA\tOOOOxx\n3818\t9819\t0\t2\t8\t18\t18\t818\t1818\t3818\t3818\t36\t37\tWQAAAA\tRNOAAA\tVVVVxx\n679\t9820\t1\t3\t9\t19\t79\t679\t679\t679\t679\t158\t159\tDAAAAA\tSNOAAA\tAAAAxx\n7512\t9821\t0\t0\t2\t12\t12\t512\t1512\t2512\t7512\t24\t25\tYCAAAA\tTNOAAA\tHHHHxx\n493\t9822\t1\t1\t3\t13\t93\t493\t493\t493\t493\t186\t187\tZSAAAA\tUNOAAA\tOOOOxx\n5663\t9823\t1\t3\t3\t3\t63\t663\t1663\t663\t5663\t126\t127\tVJAAAA\tVNOAAA\tVVVVxx\n4655\t9824\t1\t3\t5\t15\t55\t655\t655\t4655\t4655\t110\t111\tBXAAAA\tWNOAAA\tAAAAxx\n3996\t9825\t0\t0\t6\t16\t96\t996\t1996\t3996\t3996\t192\t193\tSXAAAA\tXNOAAA\tHHHHxx\n8797\t9826\t1\t1\t7\t17\t97\t797\t797\t3797\t8797\t194\t195\tJAAAAA\tYNOAAA\tOOOOxx\n2991\t9827\t1\t3\t1\t11\t91\t991\t991\t2991\t2991\t182\t183\tBLAAAA\tZNOAAA\tVVVVxx\n7038\t9828\t0\t2\t8\t18\t38\t38\t1038\t2038\t7038\t76\t77\tSKAAAA\tAOOAAA\tAAAAxx\n4174\t9829\t0\t2\t4\t14\t74\t174\t174\t4174\t4174\t148\t149\tOEAAAA\tBOOAAA\tHHHHxx\n6908\t9830\t0\t0\t8\t8\t8\t908\t908\t1908\t6908\t16\t17\tSFAAAA\tCOOAAA\tOOOOxx\n8477\t9831\t1\t1\t7\t17\t77\t477\t477\t3477\t8477\t154\t155\tBOAAAA\tDOOAAA\tVVVVxx\n3576\t9832\t0\t0\t6\t16\t76\t576\t1576\t3576\t3576\t152\t153\tOHAAAA\tEOOAAA\tAAAAxx\n2685\t9833\t1\t1\t5\t5\t85\t685\t685\t2685\t2685\t170\t171\tHZAAAA\tFOOAAA\tHHHHxx\n9161\t9834\t1\t1\t1\t1\t61\t161\t1161\t4161\t9161\t122\t123\tJOAAAA\tGOOAAA\tOOOOxx\n2951\t9835\t1\t3\t1\t11\t51\t951\t951\t2951\t2951\t102\t103\tNJAAAA\tHOOAAA\tVVVVxx\n8362\t9836\t0\t2\t2\t2\t62\t362\t362\t3362\t8362\t124\t125\tQJAAAA\tIOOAAA\tAAAAxx\n2379\t9837\t1\t3\t9\t19\t79\t379\t379\t2379\t2379\t158\t159\tNNAAAA\tJOOAAA\tHHHHxx\n1277\t9838\t1\t1\t7\t17\t77\t277\t1277\t1277\t1277\t154\t155\tDXAAAA\tKOOAAA\tOOOOxx\n1728\t9839\t0\t0\t8\t8\t28\t728\t1728\t1728\t1728\t56\t57\tMOAAAA\tLOOAAA\tVVVVxx\n9816\t9840\t0\t0\t6\t16\t16\t816\t1816\t4816\t9816\t32\t33\tONAAAA\tMOOAAA\tAAAAxx\n6288\t9841\t0\t0\t8\t8\t88\t288\t288\t1288\t6288\t176\t177\tWHAAAA\tNOOAAA\tHHHHxx\n8985\t9842\t1\t1\t5\t5\t85\t985\t985\t3985\t8985\t170\t171\tPHAAAA\tOOOAAA\tOOOOxx\n771\t9843\t1\t3\t1\t11\t71\t771\t771\t771\t771\t142\t143\tRDAAAA\tPOOAAA\tVVVVxx\n464\t9844\t0\t0\t4\t4\t64\t464\t464\t464\t464\t128\t129\tWRAAAA\tQOOAAA\tAAAAxx\n9625\t9845\t1\t1\t5\t5\t25\t625\t1625\t4625\t9625\t50\t51\tFGAAAA\tROOAAA\tHHHHxx\n9608\t9846\t0\t0\t8\t8\t8\t608\t1608\t4608\t9608\t16\t17\tOFAAAA\tSOOAAA\tOOOOxx\n9170\t9847\t0\t2\t0\t10\t70\t170\t1170\t4170\t9170\t140\t141\tSOAAAA\tTOOAAA\tVVVVxx\n9658\t9848\t0\t2\t8\t18\t58\t658\t1658\t4658\t9658\t116\t117\tMHAAAA\tUOOAAA\tAAAAxx\n7515\t9849\t1\t3\t5\t15\t15\t515\t1515\t2515\t7515\t30\t31\tBDAAAA\tVOOAAA\tHHHHxx\n9400\t9850\t0\t0\t0\t0\t0\t400\t1400\t4400\t9400\t0\t1\tOXAAAA\tWOOAAA\tOOOOxx\n2045\t9851\t1\t1\t5\t5\t45\t45\t45\t2045\t2045\t90\t91\tRAAAAA\tXOOAAA\tVVVVxx\n324\t9852\t0\t0\t4\t4\t24\t324\t324\t324\t324\t48\t49\tMMAAAA\tYOOAAA\tAAAAxx\n4252\t9853\t0\t0\t2\t12\t52\t252\t252\t4252\t4252\t104\t105\tOHAAAA\tZOOAAA\tHHHHxx\n8329\t9854\t1\t1\t9\t9\t29\t329\t329\t3329\t8329\t58\t59\tJIAAAA\tAPOAAA\tOOOOxx\n4472\t9855\t0\t0\t2\t12\t72\t472\t472\t4472\t4472\t144\t145\tAQAAAA\tBPOAAA\tVVVVxx\n1047\t9856\t1\t3\t7\t7\t47\t47\t1047\t1047\t1047\t94\t95\tHOAAAA\tCPOAAA\tAAAAxx\n9341\t9857\t1\t1\t1\t1\t41\t341\t1341\t4341\t9341\t82\t83\tHVAAAA\tDPOAAA\tHHHHxx\n7000\t9858\t0\t0\t0\t0\t0\t0\t1000\t2000\t7000\t0\t1\tGJAAAA\tEPOAAA\tOOOOxx\n1429\t9859\t1\t1\t9\t9\t29\t429\t1429\t1429\t1429\t58\t59\tZCAAAA\tFPOAAA\tVVVVxx\n2701\t9860\t1\t1\t1\t1\t1\t701\t701\t2701\t2701\t2\t3\tXZAAAA\tGPOAAA\tAAAAxx\n6630\t9861\t0\t2\t0\t10\t30\t630\t630\t1630\t6630\t60\t61\tAVAAAA\tHPOAAA\tHHHHxx\n3669\t9862\t1\t1\t9\t9\t69\t669\t1669\t3669\t3669\t138\t139\tDLAAAA\tIPOAAA\tOOOOxx\n8613\t9863\t1\t1\t3\t13\t13\t613\t613\t3613\t8613\t26\t27\tHTAAAA\tJPOAAA\tVVVVxx\n7080\t9864\t0\t0\t0\t0\t80\t80\t1080\t2080\t7080\t160\t161\tIMAAAA\tKPOAAA\tAAAAxx\n8788\t9865\t0\t0\t8\t8\t88\t788\t788\t3788\t8788\t176\t177\tAAAAAA\tLPOAAA\tHHHHxx\n6291\t9866\t1\t3\t1\t11\t91\t291\t291\t1291\t6291\t182\t183\tZHAAAA\tMPOAAA\tOOOOxx\n7885\t9867\t1\t1\t5\t5\t85\t885\t1885\t2885\t7885\t170\t171\tHRAAAA\tNPOAAA\tVVVVxx\n7160\t9868\t0\t0\t0\t0\t60\t160\t1160\t2160\t7160\t120\t121\tKPAAAA\tOPOAAA\tAAAAxx\n6140\t9869\t0\t0\t0\t0\t40\t140\t140\t1140\t6140\t80\t81\tECAAAA\tPPOAAA\tHHHHxx\n9881\t9870\t1\t1\t1\t1\t81\t881\t1881\t4881\t9881\t162\t163\tBQAAAA\tQPOAAA\tOOOOxx\n9140\t9871\t0\t0\t0\t0\t40\t140\t1140\t4140\t9140\t80\t81\tONAAAA\tRPOAAA\tVVVVxx\n644\t9872\t0\t0\t4\t4\t44\t644\t644\t644\t644\t88\t89\tUYAAAA\tSPOAAA\tAAAAxx\n3667\t9873\t1\t3\t7\t7\t67\t667\t1667\t3667\t3667\t134\t135\tBLAAAA\tTPOAAA\tHHHHxx\n2675\t9874\t1\t3\t5\t15\t75\t675\t675\t2675\t2675\t150\t151\tXYAAAA\tUPOAAA\tOOOOxx\n9492\t9875\t0\t0\t2\t12\t92\t492\t1492\t4492\t9492\t184\t185\tCBAAAA\tVPOAAA\tVVVVxx\n5004\t9876\t0\t0\t4\t4\t4\t4\t1004\t4\t5004\t8\t9\tMKAAAA\tWPOAAA\tAAAAxx\n9456\t9877\t0\t0\t6\t16\t56\t456\t1456\t4456\t9456\t112\t113\tSZAAAA\tXPOAAA\tHHHHxx\n8197\t9878\t1\t1\t7\t17\t97\t197\t197\t3197\t8197\t194\t195\tHDAAAA\tYPOAAA\tOOOOxx\n2837\t9879\t1\t1\t7\t17\t37\t837\t837\t2837\t2837\t74\t75\tDFAAAA\tZPOAAA\tVVVVxx\n127\t9880\t1\t3\t7\t7\t27\t127\t127\t127\t127\t54\t55\tXEAAAA\tAQOAAA\tAAAAxx\n9772\t9881\t0\t0\t2\t12\t72\t772\t1772\t4772\t9772\t144\t145\tWLAAAA\tBQOAAA\tHHHHxx\n5743\t9882\t1\t3\t3\t3\t43\t743\t1743\t743\t5743\t86\t87\tXMAAAA\tCQOAAA\tOOOOxx\n2007\t9883\t1\t3\t7\t7\t7\t7\t7\t2007\t2007\t14\t15\tFZAAAA\tDQOAAA\tVVVVxx\n7586\t9884\t0\t2\t6\t6\t86\t586\t1586\t2586\t7586\t172\t173\tUFAAAA\tEQOAAA\tAAAAxx\n45\t9885\t1\t1\t5\t5\t45\t45\t45\t45\t45\t90\t91\tTBAAAA\tFQOAAA\tHHHHxx\n6482\t9886\t0\t2\t2\t2\t82\t482\t482\t1482\t6482\t164\t165\tIPAAAA\tGQOAAA\tOOOOxx\n4565\t9887\t1\t1\t5\t5\t65\t565\t565\t4565\t4565\t130\t131\tPTAAAA\tHQOAAA\tVVVVxx\n6975\t9888\t1\t3\t5\t15\t75\t975\t975\t1975\t6975\t150\t151\tHIAAAA\tIQOAAA\tAAAAxx\n7260\t9889\t0\t0\t0\t0\t60\t260\t1260\t2260\t7260\t120\t121\tGTAAAA\tJQOAAA\tHHHHxx\n2830\t9890\t0\t2\t0\t10\t30\t830\t830\t2830\t2830\t60\t61\tWEAAAA\tKQOAAA\tOOOOxx\n9365\t9891\t1\t1\t5\t5\t65\t365\t1365\t4365\t9365\t130\t131\tFWAAAA\tLQOAAA\tVVVVxx\n8207\t9892\t1\t3\t7\t7\t7\t207\t207\t3207\t8207\t14\t15\tRDAAAA\tMQOAAA\tAAAAxx\n2506\t9893\t0\t2\t6\t6\t6\t506\t506\t2506\t2506\t12\t13\tKSAAAA\tNQOAAA\tHHHHxx\n8081\t9894\t1\t1\t1\t1\t81\t81\t81\t3081\t8081\t162\t163\tVYAAAA\tOQOAAA\tOOOOxx\n8678\t9895\t0\t2\t8\t18\t78\t678\t678\t3678\t8678\t156\t157\tUVAAAA\tPQOAAA\tVVVVxx\n9932\t9896\t0\t0\t2\t12\t32\t932\t1932\t4932\t9932\t64\t65\tASAAAA\tQQOAAA\tAAAAxx\n447\t9897\t1\t3\t7\t7\t47\t447\t447\t447\t447\t94\t95\tFRAAAA\tRQOAAA\tHHHHxx\n9187\t9898\t1\t3\t7\t7\t87\t187\t1187\t4187\t9187\t174\t175\tJPAAAA\tSQOAAA\tOOOOxx\n89\t9899\t1\t1\t9\t9\t89\t89\t89\t89\t89\t178\t179\tLDAAAA\tTQOAAA\tVVVVxx\n7027\t9900\t1\t3\t7\t7\t27\t27\t1027\t2027\t7027\t54\t55\tHKAAAA\tUQOAAA\tAAAAxx\n1536\t9901\t0\t0\t6\t16\t36\t536\t1536\t1536\t1536\t72\t73\tCHAAAA\tVQOAAA\tHHHHxx\n160\t9902\t0\t0\t0\t0\t60\t160\t160\t160\t160\t120\t121\tEGAAAA\tWQOAAA\tOOOOxx\n7679\t9903\t1\t3\t9\t19\t79\t679\t1679\t2679\t7679\t158\t159\tJJAAAA\tXQOAAA\tVVVVxx\n5973\t9904\t1\t1\t3\t13\t73\t973\t1973\t973\t5973\t146\t147\tTVAAAA\tYQOAAA\tAAAAxx\n4401\t9905\t1\t1\t1\t1\t1\t401\t401\t4401\t4401\t2\t3\tHNAAAA\tZQOAAA\tHHHHxx\n395\t9906\t1\t3\t5\t15\t95\t395\t395\t395\t395\t190\t191\tFPAAAA\tAROAAA\tOOOOxx\n4904\t9907\t0\t0\t4\t4\t4\t904\t904\t4904\t4904\t8\t9\tQGAAAA\tBROAAA\tVVVVxx\n2759\t9908\t1\t3\t9\t19\t59\t759\t759\t2759\t2759\t118\t119\tDCAAAA\tCROAAA\tAAAAxx\n8713\t9909\t1\t1\t3\t13\t13\t713\t713\t3713\t8713\t26\t27\tDXAAAA\tDROAAA\tHHHHxx\n3770\t9910\t0\t2\t0\t10\t70\t770\t1770\t3770\t3770\t140\t141\tAPAAAA\tEROAAA\tOOOOxx\n8272\t9911\t0\t0\t2\t12\t72\t272\t272\t3272\t8272\t144\t145\tEGAAAA\tFROAAA\tVVVVxx\n5358\t9912\t0\t2\t8\t18\t58\t358\t1358\t358\t5358\t116\t117\tCYAAAA\tGROAAA\tAAAAxx\n9747\t9913\t1\t3\t7\t7\t47\t747\t1747\t4747\t9747\t94\t95\tXKAAAA\tHROAAA\tHHHHxx\n1567\t9914\t1\t3\t7\t7\t67\t567\t1567\t1567\t1567\t134\t135\tHIAAAA\tIROAAA\tOOOOxx\n2136\t9915\t0\t0\t6\t16\t36\t136\t136\t2136\t2136\t72\t73\tEEAAAA\tJROAAA\tVVVVxx\n314\t9916\t0\t2\t4\t14\t14\t314\t314\t314\t314\t28\t29\tCMAAAA\tKROAAA\tAAAAxx\n4583\t9917\t1\t3\t3\t3\t83\t583\t583\t4583\t4583\t166\t167\tHUAAAA\tLROAAA\tHHHHxx\n375\t9918\t1\t3\t5\t15\t75\t375\t375\t375\t375\t150\t151\tLOAAAA\tMROAAA\tOOOOxx\n5566\t9919\t0\t2\t6\t6\t66\t566\t1566\t566\t5566\t132\t133\tCGAAAA\tNROAAA\tVVVVxx\n6865\t9920\t1\t1\t5\t5\t65\t865\t865\t1865\t6865\t130\t131\tBEAAAA\tOROAAA\tAAAAxx\n894\t9921\t0\t2\t4\t14\t94\t894\t894\t894\t894\t188\t189\tKIAAAA\tPROAAA\tHHHHxx\n5399\t9922\t1\t3\t9\t19\t99\t399\t1399\t399\t5399\t198\t199\tRZAAAA\tQROAAA\tOOOOxx\n1385\t9923\t1\t1\t5\t5\t85\t385\t1385\t1385\t1385\t170\t171\tHBAAAA\tRROAAA\tVVVVxx\n2156\t9924\t0\t0\t6\t16\t56\t156\t156\t2156\t2156\t112\t113\tYEAAAA\tSROAAA\tAAAAxx\n9659\t9925\t1\t3\t9\t19\t59\t659\t1659\t4659\t9659\t118\t119\tNHAAAA\tTROAAA\tHHHHxx\n477\t9926\t1\t1\t7\t17\t77\t477\t477\t477\t477\t154\t155\tJSAAAA\tUROAAA\tOOOOxx\n8194\t9927\t0\t2\t4\t14\t94\t194\t194\t3194\t8194\t188\t189\tEDAAAA\tVROAAA\tVVVVxx\n3937\t9928\t1\t1\t7\t17\t37\t937\t1937\t3937\t3937\t74\t75\tLVAAAA\tWROAAA\tAAAAxx\n3745\t9929\t1\t1\t5\t5\t45\t745\t1745\t3745\t3745\t90\t91\tBOAAAA\tXROAAA\tHHHHxx\n4096\t9930\t0\t0\t6\t16\t96\t96\t96\t4096\t4096\t192\t193\tOBAAAA\tYROAAA\tOOOOxx\n5487\t9931\t1\t3\t7\t7\t87\t487\t1487\t487\t5487\t174\t175\tBDAAAA\tZROAAA\tVVVVxx\n2475\t9932\t1\t3\t5\t15\t75\t475\t475\t2475\t2475\t150\t151\tFRAAAA\tASOAAA\tAAAAxx\n6105\t9933\t1\t1\t5\t5\t5\t105\t105\t1105\t6105\t10\t11\tVAAAAA\tBSOAAA\tHHHHxx\n6036\t9934\t0\t0\t6\t16\t36\t36\t36\t1036\t6036\t72\t73\tEYAAAA\tCSOAAA\tOOOOxx\n1315\t9935\t1\t3\t5\t15\t15\t315\t1315\t1315\t1315\t30\t31\tPYAAAA\tDSOAAA\tVVVVxx\n4473\t9936\t1\t1\t3\t13\t73\t473\t473\t4473\t4473\t146\t147\tBQAAAA\tESOAAA\tAAAAxx\n4016\t9937\t0\t0\t6\t16\t16\t16\t16\t4016\t4016\t32\t33\tMYAAAA\tFSOAAA\tHHHHxx\n8135\t9938\t1\t3\t5\t15\t35\t135\t135\t3135\t8135\t70\t71\tXAAAAA\tGSOAAA\tOOOOxx\n8892\t9939\t0\t0\t2\t12\t92\t892\t892\t3892\t8892\t184\t185\tAEAAAA\tHSOAAA\tVVVVxx\n4850\t9940\t0\t2\t0\t10\t50\t850\t850\t4850\t4850\t100\t101\tOEAAAA\tISOAAA\tAAAAxx\n2545\t9941\t1\t1\t5\t5\t45\t545\t545\t2545\t2545\t90\t91\tXTAAAA\tJSOAAA\tHHHHxx\n3788\t9942\t0\t0\t8\t8\t88\t788\t1788\t3788\t3788\t176\t177\tSPAAAA\tKSOAAA\tOOOOxx\n1672\t9943\t0\t0\t2\t12\t72\t672\t1672\t1672\t1672\t144\t145\tIMAAAA\tLSOAAA\tVVVVxx\n3664\t9944\t0\t0\t4\t4\t64\t664\t1664\t3664\t3664\t128\t129\tYKAAAA\tMSOAAA\tAAAAxx\n3775\t9945\t1\t3\t5\t15\t75\t775\t1775\t3775\t3775\t150\t151\tFPAAAA\tNSOAAA\tHHHHxx\n3103\t9946\t1\t3\t3\t3\t3\t103\t1103\t3103\t3103\t6\t7\tJPAAAA\tOSOAAA\tOOOOxx\n9335\t9947\t1\t3\t5\t15\t35\t335\t1335\t4335\t9335\t70\t71\tBVAAAA\tPSOAAA\tVVVVxx\n9200\t9948\t0\t0\t0\t0\t0\t200\t1200\t4200\t9200\t0\t1\tWPAAAA\tQSOAAA\tAAAAxx\n8665\t9949\t1\t1\t5\t5\t65\t665\t665\t3665\t8665\t130\t131\tHVAAAA\tRSOAAA\tHHHHxx\n1356\t9950\t0\t0\t6\t16\t56\t356\t1356\t1356\t1356\t112\t113\tEAAAAA\tSSOAAA\tOOOOxx\n6118\t9951\t0\t2\t8\t18\t18\t118\t118\t1118\t6118\t36\t37\tIBAAAA\tTSOAAA\tVVVVxx\n4605\t9952\t1\t1\t5\t5\t5\t605\t605\t4605\t4605\t10\t11\tDVAAAA\tUSOAAA\tAAAAxx\n5651\t9953\t1\t3\t1\t11\t51\t651\t1651\t651\t5651\t102\t103\tJJAAAA\tVSOAAA\tHHHHxx\n9055\t9954\t1\t3\t5\t15\t55\t55\t1055\t4055\t9055\t110\t111\tHKAAAA\tWSOAAA\tOOOOxx\n8461\t9955\t1\t1\t1\t1\t61\t461\t461\t3461\t8461\t122\t123\tLNAAAA\tXSOAAA\tVVVVxx\n6107\t9956\t1\t3\t7\t7\t7\t107\t107\t1107\t6107\t14\t15\tXAAAAA\tYSOAAA\tAAAAxx\n1967\t9957\t1\t3\t7\t7\t67\t967\t1967\t1967\t1967\t134\t135\tRXAAAA\tZSOAAA\tHHHHxx\n8910\t9958\t0\t2\t0\t10\t10\t910\t910\t3910\t8910\t20\t21\tSEAAAA\tATOAAA\tOOOOxx\n8257\t9959\t1\t1\t7\t17\t57\t257\t257\t3257\t8257\t114\t115\tPFAAAA\tBTOAAA\tVVVVxx\n851\t9960\t1\t3\t1\t11\t51\t851\t851\t851\t851\t102\t103\tTGAAAA\tCTOAAA\tAAAAxx\n7823\t9961\t1\t3\t3\t3\t23\t823\t1823\t2823\t7823\t46\t47\tXOAAAA\tDTOAAA\tHHHHxx\n3208\t9962\t0\t0\t8\t8\t8\t208\t1208\t3208\t3208\t16\t17\tKTAAAA\tETOAAA\tOOOOxx\n856\t9963\t0\t0\t6\t16\t56\t856\t856\t856\t856\t112\t113\tYGAAAA\tFTOAAA\tVVVVxx\n2654\t9964\t0\t2\t4\t14\t54\t654\t654\t2654\t2654\t108\t109\tCYAAAA\tGTOAAA\tAAAAxx\n7185\t9965\t1\t1\t5\t5\t85\t185\t1185\t2185\t7185\t170\t171\tJQAAAA\tHTOAAA\tHHHHxx\n309\t9966\t1\t1\t9\t9\t9\t309\t309\t309\t309\t18\t19\tXLAAAA\tITOAAA\tOOOOxx\n9752\t9967\t0\t0\t2\t12\t52\t752\t1752\t4752\t9752\t104\t105\tCLAAAA\tJTOAAA\tVVVVxx\n6405\t9968\t1\t1\t5\t5\t5\t405\t405\t1405\t6405\t10\t11\tJMAAAA\tKTOAAA\tAAAAxx\n6113\t9969\t1\t1\t3\t13\t13\t113\t113\t1113\t6113\t26\t27\tDBAAAA\tLTOAAA\tHHHHxx\n9774\t9970\t0\t2\t4\t14\t74\t774\t1774\t4774\t9774\t148\t149\tYLAAAA\tMTOAAA\tOOOOxx\n1674\t9971\t0\t2\t4\t14\t74\t674\t1674\t1674\t1674\t148\t149\tKMAAAA\tNTOAAA\tVVVVxx\n9602\t9972\t0\t2\t2\t2\t2\t602\t1602\t4602\t9602\t4\t5\tIFAAAA\tOTOAAA\tAAAAxx\n1363\t9973\t1\t3\t3\t3\t63\t363\t1363\t1363\t1363\t126\t127\tLAAAAA\tPTOAAA\tHHHHxx\n6887\t9974\t1\t3\t7\t7\t87\t887\t887\t1887\t6887\t174\t175\tXEAAAA\tQTOAAA\tOOOOxx\n6170\t9975\t0\t2\t0\t10\t70\t170\t170\t1170\t6170\t140\t141\tIDAAAA\tRTOAAA\tVVVVxx\n8888\t9976\t0\t0\t8\t8\t88\t888\t888\t3888\t8888\t176\t177\tWDAAAA\tSTOAAA\tAAAAxx\n2981\t9977\t1\t1\t1\t1\t81\t981\t981\t2981\t2981\t162\t163\tRKAAAA\tTTOAAA\tHHHHxx\n7369\t9978\t1\t1\t9\t9\t69\t369\t1369\t2369\t7369\t138\t139\tLXAAAA\tUTOAAA\tOOOOxx\n6227\t9979\t1\t3\t7\t7\t27\t227\t227\t1227\t6227\t54\t55\tNFAAAA\tVTOAAA\tVVVVxx\n8002\t9980\t0\t2\t2\t2\t2\t2\t2\t3002\t8002\t4\t5\tUVAAAA\tWTOAAA\tAAAAxx\n4288\t9981\t0\t0\t8\t8\t88\t288\t288\t4288\t4288\t176\t177\tYIAAAA\tXTOAAA\tHHHHxx\n5136\t9982\t0\t0\t6\t16\t36\t136\t1136\t136\t5136\t72\t73\tOPAAAA\tYTOAAA\tOOOOxx\n1084\t9983\t0\t0\t4\t4\t84\t84\t1084\t1084\t1084\t168\t169\tSPAAAA\tZTOAAA\tVVVVxx\n9117\t9984\t1\t1\t7\t17\t17\t117\t1117\t4117\t9117\t34\t35\tRMAAAA\tAUOAAA\tAAAAxx\n2406\t9985\t0\t2\t6\t6\t6\t406\t406\t2406\t2406\t12\t13\tOOAAAA\tBUOAAA\tHHHHxx\n1384\t9986\t0\t0\t4\t4\t84\t384\t1384\t1384\t1384\t168\t169\tGBAAAA\tCUOAAA\tOOOOxx\n9194\t9987\t0\t2\t4\t14\t94\t194\t1194\t4194\t9194\t188\t189\tQPAAAA\tDUOAAA\tVVVVxx\n858\t9988\t0\t2\t8\t18\t58\t858\t858\t858\t858\t116\t117\tAHAAAA\tEUOAAA\tAAAAxx\n8592\t9989\t0\t0\t2\t12\t92\t592\t592\t3592\t8592\t184\t185\tMSAAAA\tFUOAAA\tHHHHxx\n4773\t9990\t1\t1\t3\t13\t73\t773\t773\t4773\t4773\t146\t147\tPBAAAA\tGUOAAA\tOOOOxx\n4093\t9991\t1\t1\t3\t13\t93\t93\t93\t4093\t4093\t186\t187\tLBAAAA\tHUOAAA\tVVVVxx\n6587\t9992\t1\t3\t7\t7\t87\t587\t587\t1587\t6587\t174\t175\tJTAAAA\tIUOAAA\tAAAAxx\n6093\t9993\t1\t1\t3\t13\t93\t93\t93\t1093\t6093\t186\t187\tJAAAAA\tJUOAAA\tHHHHxx\n429\t9994\t1\t1\t9\t9\t29\t429\t429\t429\t429\t58\t59\tNQAAAA\tKUOAAA\tOOOOxx\n5780\t9995\t0\t0\t0\t0\t80\t780\t1780\t780\t5780\t160\t161\tIOAAAA\tLUOAAA\tVVVVxx\n1783\t9996\t1\t3\t3\t3\t83\t783\t1783\t1783\t1783\t166\t167\tPQAAAA\tMUOAAA\tAAAAxx\n2992\t9997\t0\t0\t2\t12\t92\t992\t992\t2992\t2992\t184\t185\tCLAAAA\tNUOAAA\tHHHHxx\n0\t9998\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t1\tAAAAAA\tOUOAAA\tOOOOxx\n2968\t9999\t0\t0\t8\t8\t68\t968\t968\t2968\t2968\t136\t137\tEKAAAA\tPUOAAA\tVVVVxx\n"
  },
  {
    "path": "test/sql/ddl.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA IF NOT EXISTS \"customSchema\" AUTHORIZATION :ROLE_DEFAULT_PERM_USER;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n\n\\ir include/ddl_ops_1.sql\n\nSELECT * FROM PUBLIC.\"Hypertable_1\";\nSELECT * FROM ONLY PUBLIC.\"Hypertable_1\";\nEXPLAIN (buffers off, costs off) SELECT * FROM ONLY PUBLIC.\"Hypertable_1\";\n\nSELECT * FROM test.show_columns('PUBLIC.\"Hypertable_1\"');\nSELECT * FROM test.show_columns('_timescaledb_internal._hyper_1_1_chunk');\n\n\\ir include/ddl_ops_2.sql\n\nSELECT * FROM test.show_columns('PUBLIC.\"Hypertable_1\"');\nSELECT * FROM test.show_columns('_timescaledb_internal._hyper_1_1_chunk');\n\nSELECT * FROM PUBLIC.\"Hypertable_1\";\n\n-- alter column tests\nCREATE TABLE alter_test(time timestamptz, temp float, color varchar(10));\n\n-- create hypertable with two chunks\nSELECT create_hypertable('alter_test', 'time', 'color', 2, chunk_time_interval => 2628000000000);\n\nINSERT INTO alter_test VALUES ('2017-01-20T09:00:01', 17.5, 'blue'),\n                              ('2017-01-21T09:00:01', 19.1, 'yellow'),\n                              ('2017-04-20T09:00:01', 89.5, 'green'),\n                              ('2017-04-21T09:00:01', 17.1, 'black');\nSELECT * FROM test.show_columns('alter_test');\nSELECT * FROM test.show_columnsp('_timescaledb_internal._hyper_9_%chunk');\n\n-- show the column name and type of the partitioning dimension in the\n-- metadata table\nSELECT * FROM _timescaledb_catalog.dimension WHERE hypertable_id = 9;\n\nEXPLAIN (buffers off, costs off)\nSELECT * FROM alter_test WHERE time > '2017-05-20T10:00:01';\n\n-- rename column and change its type\nALTER TABLE alter_test RENAME COLUMN time TO time_us;\n--converting timestamptz->timestamp should happen under UTC\nSET timezone = 'UTC';\nALTER TABLE alter_test ALTER COLUMN time_us TYPE timestamp;\nRESET timezone;\nALTER TABLE alter_test RENAME COLUMN color TO colorname;\n\\set ON_ERROR_STOP 0\n-- Changing types on hash-partitioned columns is not safe for some\n-- types and is therefore blocked.\nALTER TABLE alter_test ALTER COLUMN colorname TYPE text;\n\\set ON_ERROR_STOP 1\n\nSELECT * FROM test.show_columns('alter_test');\nSELECT * FROM test.show_columnsp('_timescaledb_internal._hyper_9_%chunk');\n\n-- show that the metadata has been updated\nSELECT * FROM _timescaledb_catalog.dimension WHERE hypertable_id = 9;\n\n-- constraint exclusion should still work with updated column\nEXPLAIN (buffers off, costs off)\nSELECT * FROM alter_test WHERE time_us > '2017-05-20T10:00:01';\n\n\\set ON_ERROR_STOP 0\n-- verify that we cannot change the column type to something incompatible\nALTER TABLE alter_test ALTER COLUMN colorname TYPE varchar(3);\n-- conversion that messes up partitioning fails\nALTER TABLE alter_test ALTER COLUMN time_us TYPE timestamptz USING time_us::timestamptz+INTERVAL '1 year';\n-- dropping column that messes up partiitoning fails\nALTER TABLE alter_test DROP COLUMN colorname;\n--ONLY blocked\nALTER TABLE ONLY alter_test RENAME COLUMN colorname TO colorname2;\nALTER TABLE ONLY alter_test ALTER COLUMN colorname TYPE varchar(10);\n\\set ON_ERROR_STOP 1\n\nCREATE TABLE alter_test_bigint(time bigint, temp float);\nSELECT create_hypertable('alter_test_bigint', 'time', chunk_time_interval => 2628000000000);\n\n\\set ON_ERROR_STOP 0\n-- Changing type of time dimension to a non-supported type\n-- shall not be allowed\nALTER TABLE alter_test_bigint\nALTER COLUMN time TYPE TEXT;\n-- dropping open time dimension shall not be allowed.\nALTER TABLE alter_test_bigint\nDROP COLUMN time;\n\\set ON_ERROR_STOP 1\n\n\n-- test expression index creation where physical layout of chunks differs from hypertable\nCREATE TABLE i2504(time timestamp NOT NULL, a int, b int, c int, d int);\n\nselect create_hypertable('i2504', 'time');\n\nINSERT INTO i2504 VALUES (now(), 1, 2, 3, 4);\nALTER TABLE i2504 DROP COLUMN b;\n\nINSERT INTO i2504(time, a, c, d) VALUES\n(now() - interval '1 year', 1, 2, 3),\n(now() - interval '2 years', 1, 2, 3);\n\nCREATE INDEX idx2 ON i2504(a,d) WHERE c IS NOT NULL;\nDROP INDEX idx2;\nCREATE INDEX idx2 ON i2504(a,d) WITH (timescaledb.transaction_per_chunk) WHERE c IS NOT NULL;\n\n-- Make sure custom composite types are supported as dimensions\nCREATE TYPE TUPLE as (val1 int4, val2 int4);\nCREATE TABLE part_custom_dim (time TIMESTAMPTZ, combo TUPLE, device TEXT);\n\\set ON_ERROR_STOP 0\n-- should fail on PG < 14 because no partitioning function supplied and the given custom type\n-- has no default hash function\n-- on PG14 custom types are hashable\nSELECT create_hypertable('part_custom_dim', 'time', 'combo', 4);\n\\set ON_ERROR_STOP 1\n\n-- immutable functions with sub-transaction (issue #4489)\nCREATE FUNCTION i4489(value TEXT DEFAULT '') RETURNS INTEGER\nAS\n$$\nBEGIN\n  RETURN value::INTEGER;\nEXCEPTION WHEN invalid_text_representation THEN\n  RETURN 0;\nEND;\n$$\nLANGUAGE PLPGSQL IMMUTABLE;\n\n-- should return 1 (one) in both cases\nSELECT i4489('1'), i4489('1');\n-- should return 0 (zero) in all cases handled by the exception\nSELECT i4489(), i4489();\nSELECT i4489('a'), i4489('a');\n\n-- test ALTER TABLE ONLY for hypertables\nCREATE TABLE at_test(time timestamptz) WITH (tsdb.hypertable);\n\n-- adding column only on the parent table should be blocked\n\\set ON_ERROR_STOP 0\nALTER TABLE ONLY at_test ADD COLUMN value INT;\n\\set ON_ERROR_STOP 1\n\nALTER TABLE ONLY at_test SET (autovacuum_enabled = false);\nALTER TABLE ONLY at_test RESET (autovacuum_enabled);\n\n-- test again after creating some chunks\nINSERT INTO at_test VALUES ('2025-01-01');\nINSERT INTO at_test VALUES ('2025-02-01');\n\nALTER TABLE ONLY at_test SET (autovacuum_enabled = false);\nALTER TABLE ONLY at_test RESET (autovacuum_enabled);\n\n-- test DDL inside function\nCREATE OR REPLACE FUNCTION ddl_function() RETURNS VOID LANGUAGE PLPGSQL AS $$\nBEGIN\n  DROP TABLE IF EXISTS func_table;\n  CREATE TABLE func_table(time timestamptz) WITH (tsdb.hypertable);\nEND\n$$;\n\nSELECT ddl_function();\nSELECT hypertable_name from timescaledb_information.hypertables WHERE hypertable_name='func_table';\n\nSELECT ddl_function();\nSELECT hypertable_name from timescaledb_information.hypertables WHERE hypertable_name='func_table';\n\n"
  },
  {
    "path": "test/sql/ddl_errors.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE TABLE PUBLIC.\"Hypertable_1\" (\n  time BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  temp_c int NOT NULL DEFAULT -1\n);\nCREATE INDEX ON PUBLIC.\"Hypertable_1\" (time, \"Device_id\");\n\n-- Default integer interval is supported as part of\n-- hypertable generalization, verify additional secnarios\n\n\\set ON_ERROR_STOP 0\nSELECT * FROM create_hypertable(NULL, NULL);\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1\"', NULL);\n-- space dimensions require explicit number of partitions\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1\"', 'time', 'Device_id', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1_mispelled\"', 'time', 'Device_id', 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1\"', 'time_mispelled', 'Device_id', 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1\"', 'Device_id', 'Device_id', 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1\"', 'time', 'Device_id_mispelled', 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\nINSERT INTO PUBLIC.\"Hypertable_1\" VALUES(1,'dev_1', 3);\n\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1\"', 'time', 'Device_id', 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\nDELETE FROM  PUBLIC.\"Hypertable_1\" ;\n\\set ON_ERROR_STOP 1\n\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1\"', 'time', 'Device_id', 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\n\\set ON_ERROR_STOP 0\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1\"', 'time', 'Device_id', 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\\set ON_ERROR_STOP 1\n\nINSERT INTO \"Hypertable_1\" VALUES (0, 1, 0);\n\n\\set ON_ERROR_STOP 0\nALTER TABLE _timescaledb_internal._hyper_1_1_chunk ALTER COLUMN temp_c DROP NOT NULL;\n\\set ON_ERROR_STOP 1\n\nCREATE TABLE PUBLIC.\"Parent\" (\n  time BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  temp_c int NOT NULL DEFAULT -1\n);\n\n\\set ON_ERROR_STOP 0\nALTER TABLE \"Hypertable_1\" INHERIT \"Parent\";\nALTER TABLE _timescaledb_internal._hyper_1_1_chunk INHERIT \"Parent\";\nALTER TABLE _timescaledb_internal._hyper_1_1_chunk NO INHERIT \"Parent\";\n\\set ON_ERROR_STOP 1\n\nCREATE TABLE PUBLIC.\"Child\" () INHERITS (\"Parent\");\n\n\\set ON_ERROR_STOP 0\nSELECT * FROM create_hypertable('\"public\".\"Parent\"', 'time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nSELECT * FROM create_hypertable('\"public\".\"Child\"', 'time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\\set ON_ERROR_STOP 1\n\n\\set ON_ERROR_STOP 0\nCREATE TEMPORARY TABLE temp_table (time timestamptz) WITH (tsdb.hypertable);\n\\set ON_ERROR_STOP 1\n\nCREATE TEMP TABLE \"Hypertable_temp\" (\n  time BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  temp_c int NOT NULL DEFAULT -1\n);\n\n\\set ON_ERROR_STOP 0\nSELECT * FROM create_hypertable('\"Hypertable_temp\"', 'time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\nALTER TABLE \"Hypertable_1\" SET UNLOGGED;\n\\set ON_ERROR_STOP 1\n\nALTER TABLE \"Hypertable_1\" SET LOGGED;\n\nCREATE TABLE PUBLIC.\"Hypertable_1_rule\" (\n  time BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  temp_c int NOT NULL DEFAULT -1\n);\nCREATE RULE notify_me AS ON UPDATE TO \"Hypertable_1_rule\" DO ALSO NOTIFY \"Hypertable_1_rule\";\n\n\\set ON_ERROR_STOP 0\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1_rule\"', 'time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\\set ON_ERROR_STOP 1\n\nALTER TABLE \"Hypertable_1_rule\" DISABLE RULE notify_me;\n\n\\set ON_ERROR_STOP 0\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1_rule\"', 'time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\\set ON_ERROR_STOP 1\n\nDROP RULE notify_me ON \"Hypertable_1_rule\";\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1_rule\"', 'time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\n\\set ON_ERROR_STOP 0\nCREATE RULE notify_me AS ON UPDATE TO \"Hypertable_1_rule\" DO ALSO NOTIFY \"Hypertable_1_rule\";\n\\set ON_ERROR_STOP 1\n\n\\set ON_ERROR_STOP 0\nSELECT add_dimension(NULL,NULL);\n\\set ON_ERROR_STOP 1\n\n\\set ON_ERROR_STOP 0\nSELECT attach_tablespace(NULL,NULL);\n\\set ON_ERROR_STOP 1\n\n\\set ON_ERROR_STOP 0\nselect set_number_partitions(NULL,NULL);\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "test/sql/ddl_extra.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE OR REPLACE FUNCTION show_columns_ext(rel regclass)\nRETURNS TABLE(\"Column\" name,\n              \"Type\" text,\n              \"NotNull\" boolean,\n              \"Compression\" text) LANGUAGE SQL STABLE AS\n$BODY$\n    SELECT a.attname,\n    format_type(t.oid, t.typtypmod),\n    a.attnotnull,\n    (CASE WHEN a.attcompression = 'l' THEN 'lz4' WHEN a.attcompression = 'p' THEN 'pglz' ELSE '' END)\n    FROM pg_attribute a, pg_type t\n    WHERE a.attrelid = rel\n    AND a.atttypid = t.oid\n    AND a.attnum >= 0\n    ORDER BY a.attnum;\n$BODY$;\n\nCREATE TABLE conditions (\n  time TIMESTAMP NOT NULL,\n  location TEXT NOT NULL,\n  temperature DOUBLE PRECISION NULL,\n  humidity DOUBLE PRECISION NULL\n);\n\nSELECT create_hypertable('conditions', 'time', chunk_time_interval := '1 day'::interval);\n\nINSERT INTO conditions\nSELECT generate_series('2021-10-10 00:00'::timestamp, '2021-10-11 00:00'::timestamp, '1 day'), 'POR', 55, 75;\n\nCREATE VIEW t AS\n    SELECT 'conditions'::regclass AS r\n    UNION ALL\n    SELECT * FROM show_chunks('conditions');\n\nSELECT * FROM t, LATERAL show_columns_ext(r) WHERE \"Column\" = 'location' ORDER BY 1, 2;\n\nALTER TABLE conditions ALTER COLUMN location SET COMPRESSION pglz;\nSELECT * FROM t, LATERAL show_columns_ext(r) WHERE \"Column\" = 'location' ORDER BY 1, 2;\n\nINSERT INTO conditions VALUES ('2021-10-12 00:00'::timestamp, 'BRA', 66, 77);\nSELECT * FROM t, LATERAL show_columns_ext(r) WHERE \"Column\" = 'location' ORDER BY 1, 2;\n\nALTER TABLE conditions ALTER COLUMN location SET COMPRESSION default;\nSELECT * FROM t, LATERAL show_columns_ext(r) WHERE \"Column\" = 'location' ORDER BY 1, 2;\n\n\\set ON_ERROR_STOP 0\n-- failing test because compression is not allowed in \"non-TOASTable\" datatypes\nALTER TABLE conditions ALTER COLUMN temperature SET COMPRESSION pglz;\n\nSELECT * FROM t, LATERAL show_columns_ext(r) WHERE \"Column\" = 'temperature' ORDER BY 1, 2;\n"
  },
  {
    "path": "test/sql/debug_utils.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n\nSELECT _timescaledb_functions.extension_state();\n\nRESET ROLE;\n\nDO $$\nDECLARE\n    module text;\nBEGIN\n    SELECT probin INTO module FROM pg_proc WHERE proname = 'extension_state' AND pronamespace = '_timescaledb_functions'::regnamespace;\n    EXECUTE format('CREATE FUNCTION extension_state() RETURNS TEXT AS ''%s'', ''ts_extension_get_state'' LANGUAGE C', module);\nEND\n$$;\n\nDROP EXTENSION timescaledb;\nSELECT * FROM extension_state();\n\n\\c\nCREATE EXTENSION timescaledb;\nSELECT * FROM extension_state();\n"
  },
  {
    "path": "test/sql/delete.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\o /dev/null\n\\ir include/insert_two_partitions.sql\n\\o\n\nSELECT * FROM \"two_Partitions\" ORDER BY \"timeCustom\", device_id, series_0, series_1;\n\nDELETE FROM \"two_Partitions\" WHERE series_0 = 1.5;\nDELETE FROM \"two_Partitions\" WHERE series_0 = 100;\nSELECT * FROM \"two_Partitions\" ORDER BY \"timeCustom\", device_id, series_0, series_1;\n\n-- Make sure DELETE isn't optimized if it includes Append plans\n-- Need to turn of nestloop to make append appear the same on PG96 and PG10\nset enable_nestloop = 'off';\n\nCREATE OR REPLACE FUNCTION series_val()\nRETURNS integer LANGUAGE PLPGSQL STABLE AS\n$BODY$\nBEGIN\n    RETURN 5;\nEND;\n$BODY$;\n\n-- ConstraintAwareAppend applied for SELECT\nEXPLAIN (buffers off, costs off)\nSELECT FROM \"two_Partitions\"\nWHERE series_1 IN (SELECT series_1 FROM \"two_Partitions\" WHERE series_1 > series_val());\n\n-- ConstraintAwareAppend NOT applied for DELETE\nEXPLAIN (buffers off, costs off)\nDELETE FROM \"two_Partitions\"\nWHERE series_1 IN (SELECT series_1 FROM \"two_Partitions\" WHERE series_1 > series_val());\n\n\nSELECT * FROM \"two_Partitions\" ORDER BY \"timeCustom\", device_id, series_0, series_1;\nBEGIN;\nDELETE FROM \"two_Partitions\"\nWHERE series_1 IN (SELECT series_1 FROM \"two_Partitions\" WHERE series_1 > series_val());\nSELECT * FROM \"two_Partitions\" ORDER BY \"timeCustom\", device_id, series_0, series_1;\nROLLBACK;\n\nBEGIN;\nDELETE FROM \"two_Partitions\"\nWHERE series_1 IN (SELECT series_1 FROM \"two_Partitions\" WHERE series_1 > series_val()) RETURNING \"timeCustom\";\nSELECT * FROM \"two_Partitions\" ORDER BY \"timeCustom\", device_id, series_0, series_1;\nROLLBACK;\n\n-- test update on chunks directly\nCREATE TABLE direct_delete(time timestamptz) WITH (tsdb.hypertable);\n\nINSERT INTO direct_delete VALUES ('2020-01-01');\nSELECT show_chunks('direct_delete') AS \"CHUNK\" \\gset\n\n--should have ModifyHyperable node\nEXPLAIN (costs off, timing off, summary off) DELETE FROM :CHUNK;\nEXPLAIN (costs off, timing off, summary off) DELETE FROM ONLY :CHUNK;\n\n-- DELETE should succeed\nBEGIN;\nDELETE FROM :CHUNK RETURNING *;\nROLLBACK;\n\nBEGIN;\nDELETE FROM ONLY :CHUNK RETURNING *;\nROLLBACK;\n\n-- Test that EXPLAIN VERBOSE on prepared statements does not corrupt cached plans.\nSET plan_cache_mode = 'force_generic_plan';\nCREATE TABLE explain_verbose_ht( time timestamptz NOT NULL, device int, value float) WITH (tsdb.hypertable);\n\nINSERT INTO explain_verbose_ht SELECT t, 1, 0.1 FROM generate_series('2026-01-01'::timestamptz, '2026-01-08'::timestamptz, interval '6 hours') t;\n\n-- Verify the DELETE plan uses ChunkAppend\nEXPLAIN (costs off) DELETE FROM explain_verbose_ht WHERE time > '2025-01-01'::text::timestamptz;\n\nPREPARE delete_ht AS DELETE FROM explain_verbose_ht WHERE time > '2025-01-01'::text::timestamptz AND device = 2;\n\nEXECUTE delete_ht;\nEXPLAIN (verbose, costs off) EXECUTE delete_ht;\nEXECUTE delete_ht;\n\nDEALLOCATE delete_ht;\n\n-- repeat test with explain analyze\nPREPARE delete_ht AS DELETE FROM explain_verbose_ht WHERE time > '2025-01-01'::text::timestamptz AND device = 2;\n\nEXECUTE delete_ht;\nEXPLAIN (verbose, analyze, buffers off, costs off, timing off, summary off) EXECUTE delete_ht;\nEXECUTE delete_ht;\n\nDEALLOCATE delete_ht;\n\nRESET plan_cache_mode;\n\n-- github issue #6790\n-- test DELETE with WHERE EXISTS on hypertable\nCREATE TABLE i6790(time timestamptz NOT NULL, device int, value float) WITH (tsdb.hypertable);\nINSERT INTO i6790 SELECT t, 1, 0.1 FROM generate_series('2026-01-01'::timestamptz, '2026-01-03'::timestamptz, interval '12 hours') t;\n\n-- DELETE with simple EXISTS - creates gating Result node wrapping ChunkAppend\nDELETE FROM i6790 WHERE EXISTS (SELECT 1);\n-- all rows should be gone\nSELECT count(*) FROM i6790;\n\n-- repopulate for next test\nINSERT INTO i6790 SELECT t, 1, 0.1 FROM generate_series('2026-01-01'::timestamptz, '2026-01-03'::timestamptz, interval '12 hours') t;\n\n-- DELETE with correlated EXISTS\nDELETE FROM i6790 WHERE EXISTS (SELECT 1 FROM i6790 g WHERE g.device = i6790.device);\nSELECT count(*) FROM i6790;\n\n"
  },
  {
    "path": "test/sql/drop_extension.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE TABLE drop_test(time timestamp, temp float8, device text);\n\nSELECT create_hypertable('drop_test', 'time', 'device', 2);\nSELECT * FROM _timescaledb_catalog.hypertable;\nINSERT INTO drop_test VALUES('Mon Mar 20 09:17:00.936242 2017', 23.4, 'dev1');\nSELECT * FROM drop_test;\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nDROP EXTENSION timescaledb CASCADE;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n\n-- Querying the original table should not return any rows since all of\n-- them actually existed in chunks that are now gone\nSELECT * FROM drop_test;\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- Recreate the extension\nSET client_min_messages=error;\nCREATE EXTENSION timescaledb;\nRESET client_min_messages;\n\n-- Test that calling twice generates proper error\n\\set ON_ERROR_STOP 0\nCREATE EXTENSION timescaledb;\n\\set ON_ERROR_STOP 1\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n\n-- CREATE twice with IF NOT EXISTS should be OK\nCREATE EXTENSION IF NOT EXISTS timescaledb;\n\n-- Make the table a hypertable again\nSELECT create_hypertable('drop_test', 'time', 'device', 2);\n\nSELECT * FROM _timescaledb_catalog.hypertable;\nINSERT INTO drop_test VALUES('Mon Mar 20 09:18:19.100462 2017', 22.1, 'dev1');\nSELECT * FROM drop_test;\n\n--test drops thru cascades of other objects\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- Stop background workers to prevent them from interfering with the drop public schema\nSELECT _timescaledb_functions.stop_background_workers();\nSET client_min_messages TO ERROR;\nREVOKE CONNECT ON DATABASE :TEST_DBNAME FROM public;\nSELECT count(pg_terminate_backend(pg_stat_activity.pid)) AS TERMINATED\nFROM pg_stat_activity\nWHERE pg_stat_activity.datname = :'TEST_DBNAME'\nAND pg_stat_activity.pid <> pg_backend_pid() \\gset\nRESET client_min_messages;\n-- drop the public schema and all its objects\nDROP SCHEMA public CASCADE;\n\\dn\n\n-- Recreate the public schema and extension in the same session.\n-- This should work without requiring a reconnect (issue #5884).\nCREATE SCHEMA public;\nSET client_min_messages=error;\nCREATE EXTENSION timescaledb SCHEMA public;\nRESET client_min_messages;\nSELECT extname FROM pg_extension WHERE extname = 'timescaledb';\n\n-- Verify the extension is functional after re-creation\nCREATE TABLE drop_test2(time timestamptz, temp float8);\nSELECT create_hypertable('drop_test2', 'time');\nINSERT INTO drop_test2 VALUES('2024-01-01', 23.4);\nSELECT * FROM drop_test2;\nDROP TABLE drop_test2;\n\n-- Test that dropping and recreating extension directly also works in the same session\nDROP EXTENSION timescaledb CASCADE;\nSET client_min_messages=error;\nCREATE EXTENSION timescaledb;\nRESET client_min_messages;\nSELECT extname FROM pg_extension WHERE extname = 'timescaledb';\n"
  },
  {
    "path": "test/sql/drop_hypertable.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nSELECT * from _timescaledb_catalog.hypertable;\nSELECT * from _timescaledb_catalog.dimension;\n\nCREATE TABLE should_drop (time timestamp, temp float8);\nSELECT create_hypertable('should_drop', 'time');\n\nCREATE TABLE hyper_with_dependencies (time timestamp, temp float8);\nSELECT create_hypertable('hyper_with_dependencies', 'time');\n\nCREATE VIEW dependent_view AS SELECT * FROM hyper_with_dependencies;\n\nINSERT INTO hyper_with_dependencies VALUES (now(), 1.0);\n\n\\set ON_ERROR_STOP 0\nDROP TABLE hyper_with_dependencies;\n\\set ON_ERROR_STOP 1\nDROP TABLE hyper_with_dependencies CASCADE;\n-- check that the view is dropped\nSELECT oid FROM pg_class WHERE relname = 'dependent_view';\n\nCREATE TABLE chunk_with_dependencies (time timestamp, temp float8);\nSELECT create_hypertable('chunk_with_dependencies', 'time');\n\nINSERT INTO chunk_with_dependencies VALUES (now(), 1.0);\n\nCREATE VIEW dependent_view_chunk AS SELECT * FROM _timescaledb_internal._hyper_3_2_chunk;\n\n\\set ON_ERROR_STOP 0\nDROP TABLE chunk_with_dependencies;\n\\set ON_ERROR_STOP 1\nDROP TABLE chunk_with_dependencies CASCADE;\n-- check that the view is dropped\nSELECT oid FROM pg_class WHERE relname = 'dependent_view_chunk';\n\n-- Calling create hypertable again will increment hypertable ID\n-- although no new hypertable is created. Make sure we can handle this.\nSELECT create_hypertable('should_drop', 'time', if_not_exists => true);\nSELECT * from _timescaledb_catalog.hypertable;\nSELECT * from _timescaledb_catalog.dimension;\nDROP TABLE should_drop;\n\nCREATE TABLE should_drop (time timestamp, temp float8);\nSELECT create_hypertable('should_drop', 'time');\n\nINSERT INTO should_drop VALUES (now(), 1.0);\nSELECT * from _timescaledb_catalog.hypertable;\nSELECT * from _timescaledb_catalog.dimension;\n\n-- test dropping multiple objects at once\nCREATE TABLE t1 (time timestamptz) WITH (tsdb.hypertable);\nINSERT INTO t1 VALUES ('2025-01-01');\nCREATE TABLE t2 (time timestamptz) WITH (tsdb.hypertable);\nINSERT INTO t2 VALUES ('2025-01-01');\nCREATE TABLE t3 (time timestamptz);\n\nDROP TABLE t1, t2, t3;\n\n"
  },
  {
    "path": "test/sql/drop_owned.sql.in",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA hypertable_schema;\nGRANT ALL ON SCHEMA hypertable_schema TO :ROLE_DEFAULT_PERM_USER;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n\nCREATE TABLE hypertable_schema.default_perm_user (time timestamptz, temp float, location int);\nSELECT create_hypertable('hypertable_schema.default_perm_user', 'time', 'location', 2);\nINSERT INTO hypertable_schema.default_perm_user VALUES ('2001-01-01 01:01:01', 23.3, 1);\n\n\nRESET ROLE;\nCREATE TABLE hypertable_schema.superuser (time timestamptz, temp float, location int);\nSELECT create_hypertable('hypertable_schema.superuser', 'time', 'location', 2);\nINSERT INTO hypertable_schema.superuser VALUES ('2001-01-01 01:01:01', 23.3, 1);\n\nSELECT * FROM _timescaledb_catalog.hypertable ORDER BY id;\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n\nDROP OWNED BY :ROLE_DEFAULT_PERM_USER;\n\nSELECT * FROM _timescaledb_catalog.hypertable ORDER BY id;\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n\nDROP TABLE  hypertable_schema.superuser;\n\n--everything should be cleaned up\nSELECT * FROM _timescaledb_catalog.hypertable GROUP BY id;\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\nSELECT * FROM _timescaledb_catalog.dimension;\nSELECT * FROM _timescaledb_catalog.dimension_slice;\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\n\n-- test drop owned in database without extension installed\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE database test_drop_owned;\n\\c test_drop_owned\nDROP OWNED BY :ROLE_SUPERUSER;\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nDROP DATABASE test_drop_owned WITH (FORCE);\n\n-- Test that dependencies on roles are added to chunks when creating\n-- new chunks. If that is not done, DROP OWNED BY will not revoke the\n-- privilege on the chunk.\nCREATE TABLE sensor_data(time timestamptz not null, cpu double precision null);\n\nSELECT * FROM create_hypertable('sensor_data','time');\n\nINSERT INTO sensor_data\nSELECT time,\n       random() AS cpu\nFROM generate_series('2020-01-01'::timestamptz, '2020-01-24'::timestamptz, INTERVAL '10 minute') AS g1(time);\n\n\\dp sensor_data\n\\dp _timescaledb_internal._hyper_3*\n\nGRANT SELECT ON sensor_data TO :ROLE_DEFAULT_PERM_USER;\n\n\\dp sensor_data\n\\dp _timescaledb_internal._hyper_3*\n\n-- Insert more chunks after adding the user to the hypertable. These\n-- will now get the privileges of the hypertable.\nINSERT INTO sensor_data\nSELECT time,\n       random() AS cpu\nFROM generate_series('2020-01-20'::timestamptz, '2020-02-05'::timestamptz, INTERVAL '10 minute') AS g1(time);\n\n\\dp _timescaledb_internal._hyper_3*\n\n-- This should revoke the privileges on both the hypertable and the chunks.\nDROP OWNED BY :ROLE_DEFAULT_PERM_USER;\n\n\\dp sensor_data\n\\dp _timescaledb_internal._hyper_3*\n"
  },
  {
    "path": "test/sql/drop_rename_hypertable.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\o /dev/null\n\\ir include/insert_two_partitions.sql\n\\o\n\nSELECT * FROM test.show_columnsp('_timescaledb_internal.%_hyper%');\n\n-- Test that renaming hypertable works\nSELECT * FROM test.show_columns('_timescaledb_internal._hyper_1_1_chunk');\nALTER TABLE \"two_Partitions\" RENAME TO \"newname\";\nSELECT * FROM \"newname\";\nSELECT * FROM _timescaledb_catalog.hypertable;\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA \"newschema\" AUTHORIZATION :ROLE_DEFAULT_PERM_USER;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n\nALTER TABLE \"newname\" SET SCHEMA \"newschema\";\nSELECT * FROM \"newschema\".\"newname\";\nSELECT * FROM _timescaledb_catalog.hypertable;\n\nDROP TABLE \"newschema\".\"newname\";\n\nSELECT * FROM _timescaledb_catalog.hypertable;\nSELECT schema, name FROM test.relation WHERE schema IN ('public', '_timescaledb_catalog', '_timescaledb_internal');\n\n-- Test that renaming ordinary table works\n\nCREATE TABLE renametable (foo int);\nALTER TABLE \"renametable\" RENAME TO \"newname_none_ht\";\nSELECT * FROM \"newname_none_ht\";\n"
  },
  {
    "path": "test/sql/drop_schema.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA chunk_schema1;\nCREATE SCHEMA chunk_schema2;\nCREATE SCHEMA hypertable_schema;\nCREATE SCHEMA extra_schema;\nGRANT ALL ON SCHEMA hypertable_schema TO :ROLE_DEFAULT_PERM_USER;\nGRANT ALL ON SCHEMA chunk_schema1 TO :ROLE_DEFAULT_PERM_USER;\nGRANT ALL ON SCHEMA chunk_schema2 TO :ROLE_DEFAULT_PERM_USER;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n\nCREATE TABLE hypertable_schema.test1 (time timestamptz, temp float, location int);\nCREATE TABLE hypertable_schema.test2 (time timestamptz, temp float, location int);\n\n--create two identical tables with their own chunk schemas\nSELECT create_hypertable('hypertable_schema.test1', 'time', 'location', 2, associated_schema_name => 'chunk_schema1');\nSELECT create_hypertable('hypertable_schema.test2', 'time', 'location', 2, associated_schema_name => 'chunk_schema2');\nINSERT INTO hypertable_schema.test1 VALUES ('2001-01-01 01:01:01', 23.3, 1);\nINSERT INTO hypertable_schema.test2 VALUES ('2001-01-01 01:01:01', 23.3, 1);\n\nSELECT * FROM _timescaledb_catalog.hypertable ORDER BY id;\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n\nRESET ROLE;\n--drop the associated schema. We drop the extra schema to show we can\n--handle multi-schema drops\nDROP SCHEMA chunk_schema1, extra_schema CASCADE;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n\n--show that the metadata for the table using the dropped schema is\n--changed. The other table is not affected.\nSELECT * FROM _timescaledb_catalog.hypertable ORDER BY id;\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n\n--new chunk should be created in the internal associated schema\nINSERT INTO hypertable_schema.test1 VALUES ('2001-01-01 01:01:01', 23.3, 1);\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n\nRESET ROLE;\n--dropping the internal schema should not work\n\\set ON_ERROR_STOP 0\nDROP SCHEMA _timescaledb_internal CASCADE;\n\\set ON_ERROR_STOP 1\n--dropping the hypertable schema should delete everything\nDROP SCHEMA hypertable_schema CASCADE;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n\n--everything should be cleaned up\nSELECT * FROM _timescaledb_catalog.hypertable GROUP BY id;\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\nSELECT * FROM _timescaledb_catalog.dimension;\nSELECT * FROM _timescaledb_catalog.dimension_slice;\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\n"
  },
  {
    "path": "test/sql/dump_meta.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\ir include/insert_two_partitions.sql\n\n\\ir ../../scripts/dump_meta_data.sql\n"
  },
  {
    "path": "test/sql/extension_scripts.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nDROP EXTENSION timescaledb;\n\n-- test that installation script errors when any of our internal schemas already exists\n\\set ON_ERROR_STOP 0\nCREATE SCHEMA _timescaledb_catalog;\nCREATE EXTENSION timescaledb;\nDROP SCHEMA _timescaledb_catalog;\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA _timescaledb_internal;\nCREATE EXTENSION timescaledb;\nDROP SCHEMA _timescaledb_internal;\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA _timescaledb_cache;\nCREATE EXTENSION timescaledb;\nDROP SCHEMA _timescaledb_cache;\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA timescaledb_experimental;\nCREATE EXTENSION timescaledb;\nDROP SCHEMA timescaledb_experimental;\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA timescaledb_information;\nCREATE EXTENSION timescaledb;\nDROP SCHEMA timescaledb_information;\n\n-- test that installation script errors when any of the function in public schema already exists\n-- we don't test every public function but just a few common ones\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE FUNCTION time_bucket(int,int) RETURNS int LANGUAGE SQL AS $$ SELECT 1::int; $$;\nCREATE EXTENSION timescaledb;\nDROP FUNCTION time_bucket;\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION show_chunks(relation regclass, older_than \"any\" DEFAULT NULL, newer_than \"any\" DEFAULT NULL, created_before \"any\" DEFAULT NULL, created_after \"any\" DEFAULT NULL) RETURNS SETOF regclass language internal as 'pg_partition_ancestors';\nCREATE EXTENSION timescaledb;\nDROP FUNCTION show_chunks;\n\n-- Create a user that is not all-lowercase\nCREATE USER \"FooBar\" WITH SUPERUSER;\n\n\\c :TEST_DBNAME \"FooBar\"\nSET client_min_messages TO error;\nCREATE EXTENSION timescaledb;\nDROP EXTENSION timescaledb;\nRESET client_min_messages;\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nDROP USER \"FooBar\";\nSET client_min_messages TO ERROR;\nCREATE EXTENSION timescaledb;\n\n"
  },
  {
    "path": "test/sql/generated_as_identity.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE table test_gen (\n    id int generated by default AS IDENTITY primary key,\n    payload text\n);\n\n SELECT create_hypertable('test_gen', 'id', chunk_time_interval=>10);\n\ninsert into test_gen (payload) select generate_series(1,15) returning *;\n\nselect * from test_gen;\n\n\\set ON_ERROR_STOP 0\ninsert into test_gen values('1', 'a');\n\\set ON_ERROR_STOP 1\n\nALTER TABLE test_gen ALTER COLUMN id DROP IDENTITY;\n\\set ON_ERROR_STOP 0\ninsert into test_gen (payload) select generate_series(15,20) returning *;\n\\set ON_ERROR_STOP 1\n\nALTER TABLE test_gen ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY;\n\\set ON_ERROR_STOP 0\ninsert into test_gen (payload) select generate_series(15,20) returning *;\n\\set ON_ERROR_STOP 1\nALTER TABLE test_gen ALTER COLUMN id SET GENERATED BY DEFAULT RESTART 100;\n\ninsert into test_gen (payload) select generate_series(15,20) returning *;\nselect * from test_gen;\n\nSELECT * FROM test.show_subtables('test_gen');\n"
  },
  {
    "path": "test/sql/grant_hypertable.sql.in",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\nCREATE TABLE conditions(\n    time TIMESTAMPTZ NOT NULL,\n    device INTEGER,\n    temperature FLOAT\n);\n\n-- Create a hypertable and show that it does not have any privileges\nSELECT * FROM create_hypertable('conditions', 'time', chunk_time_interval => '5 days'::interval);\nINSERT INTO conditions\nSELECT time, (random()*30)::int, random()*80 - 40\nFROM generate_series('2018-12-01 00:00'::timestamp, '2018-12-10 00:00'::timestamp, '1h') AS time;\n\\z conditions\n\\z _timescaledb_internal.*chunk\n\n-- Add privileges and show that they propagate to the chunks\nGRANT SELECT, INSERT ON conditions TO PUBLIC;\n\\z conditions\n\\z _timescaledb_internal.*chunk\n\n-- Create some more chunks and show that they also get the privileges.\nINSERT INTO conditions\nSELECT time, (random()*30)::int, random()*80 - 40\nFROM generate_series('2018-12-10 00:00'::timestamp, '2018-12-20 00:00'::timestamp, '1h') AS time;\n\\z conditions\n\\z _timescaledb_internal.*chunk\n\n-- Revoke one of the privileges and show that it propagate to the\n-- chunks.\nREVOKE INSERT ON conditions FROM PUBLIC;\n\\z conditions\n\\z _timescaledb_internal.*chunk\n\n-- Add some more chunks and show that it inherits the grants from the\n-- hypertable.\nINSERT INTO conditions\nSELECT time, (random()*30)::int, random()*80 - 40\nFROM generate_series('2018-12-20 00:00'::timestamp, '2018-12-30 00:00'::timestamp, '1h') AS time;\n\\z conditions\n\\z _timescaledb_internal.*chunk\n\n-- Change grants of one chunk explicitly and check that it is possible\n\\z _timescaledb_internal._hyper_1_1_chunk\nGRANT UPDATE ON _timescaledb_internal._hyper_1_1_chunk TO PUBLIC;\n\\z _timescaledb_internal._hyper_1_1_chunk\nREVOKE SELECT ON _timescaledb_internal._hyper_1_1_chunk FROM PUBLIC;\n\\z _timescaledb_internal._hyper_1_1_chunk\n\n-- Check that revoking a permission first on the chunk and then on the\n-- hypertable that was added through the hypertable (INSERT and\n-- SELECT, in this case) still do not copy permissions from the\n-- hypertable (so there should not be a select permission to public on\n-- the chunk but there should be one on the hypertable).\nGRANT INSERT ON conditions TO PUBLIC;\n\\z conditions\n\\z _timescaledb_internal._hyper_1_2_chunk\nREVOKE SELECT ON _timescaledb_internal._hyper_1_2_chunk FROM PUBLIC;\nREVOKE INSERT ON conditions FROM PUBLIC;\n\\z conditions\n\\z _timescaledb_internal._hyper_1_2_chunk\n\n-- Check that granting permissions through hypertable does not remove\n-- separate grants on chunk.\nGRANT UPDATE ON _timescaledb_internal._hyper_1_3_chunk TO PUBLIC;\n\\z conditions\n\\z _timescaledb_internal._hyper_1_3_chunk\nGRANT INSERT ON conditions TO PUBLIC;\nREVOKE INSERT ON conditions FROM PUBLIC;\n\\z conditions\n\\z _timescaledb_internal._hyper_1_3_chunk\n\n-- Check that GRANT ALL IN SCHEMA adds privileges to the parent\n-- and also goes to chunks in another schema\nGRANT ALL ON ALL TABLES  IN SCHEMA public TO :ROLE_DEFAULT_PERM_USER_2;\n\\z conditions\n\\z _timescaledb_internal.*chunk\n\n-- Check that REVOKE ALL IN SCHEMA removes privileges of the parent\n-- and also goes to chunks in another schema\nREVOKE ALL ON ALL TABLES  IN SCHEMA public FROM :ROLE_DEFAULT_PERM_USER_2;\n\\z conditions\n\\z _timescaledb_internal.*chunk\n\n-- Create chunks in the same schema as the hypertable and check that\n-- they also get the same privileges as the hypertable\nCREATE TABLE measurements(\n    time TIMESTAMPTZ NOT NULL,\n    device INTEGER,\n    temperature FLOAT\n);\n\n-- Create a hypertable with chunks in the same schema\nSELECT * FROM create_hypertable('public.measurements', 'time', chunk_time_interval => '5 days'::interval, associated_schema_name => 'public');\nINSERT INTO measurements\nSELECT time, (random()*30)::int, random()*80 - 40\nFROM generate_series('2018-12-01 00:00'::timestamp, '2018-12-10 00:00'::timestamp, '1h') AS time;\n\n-- GRANT ALL and check privileges\nGRANT ALL ON ALL TABLES  IN SCHEMA public TO :ROLE_DEFAULT_PERM_USER_2;\n\\z measurements\n\\z conditions\n\\z public.*chunk\n\n-- REVOKE ALL and check privileges\nREVOKE ALL ON ALL TABLES  IN SCHEMA public FROM :ROLE_DEFAULT_PERM_USER_2;\n\\z measurements\n\\z conditions\n\\z public.*chunk\n\n-- GRANT/REVOKE in an empty schema (Issue #4581)\nCREATE SCHEMA test_grant;\nGRANT ALL ON ALL TABLES IN SCHEMA test_grant TO :ROLE_DEFAULT_PERM_USER_2;\nREVOKE ALL ON ALL TABLES IN SCHEMA test_grant FROM :ROLE_DEFAULT_PERM_USER_2;\n"
  },
  {
    "path": "test/sql/hash.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Test hashing Const values. We should expect the same hash value for\n-- all integer types when values are compatible\nSELECT _timescaledb_functions.get_partition_hash(1::int);\nSELECT _timescaledb_functions.get_partition_hash(1::bigint);\nSELECT _timescaledb_functions.get_partition_hash(1::smallint);\nSELECT _timescaledb_functions.get_partition_hash(true);\n\n-- Floating point types should also hash the same for compatible values\nSELECT _timescaledb_functions.get_partition_hash(1.0::real);\nSELECT _timescaledb_functions.get_partition_hash(1.0::double precision);\n-- Float aliases\nSELECT _timescaledb_functions.get_partition_hash(1.0::float);\nSELECT _timescaledb_functions.get_partition_hash(1.0::float4);\nSELECT _timescaledb_functions.get_partition_hash(1.0::float8);\nSELECT _timescaledb_functions.get_partition_hash(1.0::numeric);\n\n-- 'name' and '\"char\"' are internal PostgreSQL types, which are not\n-- intended for use by the general user. They are included here only\n-- for completeness\n-- https://www.postgresql.org/docs/10/static/datatype-character.html#datatype-character-special-table\nSELECT _timescaledb_functions.get_partition_hash('c'::name);\nSELECT _timescaledb_functions.get_partition_hash('c'::\"char\");\n\n-- String and character hashes should also have the same output for\n-- compatible values\nSELECT _timescaledb_functions.get_partition_hash('c'::char);\nSELECT _timescaledb_functions.get_partition_hash('c'::varchar(2));\nSELECT _timescaledb_functions.get_partition_hash('c'::text);\n-- 'c' is 0x63 in ASCII\nSELECT _timescaledb_functions.get_partition_hash(E'\\\\x63'::bytea);\n\n-- Time and date types\nSELECT _timescaledb_functions.get_partition_hash(interval '1 day');\nSELECT _timescaledb_functions.get_partition_hash('2017-03-22T09:18:23'::timestamp);\nSELECT _timescaledb_functions.get_partition_hash('2017-03-22T09:18:23'::timestamptz);\nSELECT _timescaledb_functions.get_partition_hash('2017-03-22'::date);\nSELECT _timescaledb_functions.get_partition_hash('10:00:00'::time);\nSELECT _timescaledb_functions.get_partition_hash('10:00:00-1'::timetz);\n\n-- Other types\nSELECT _timescaledb_functions.get_partition_hash(ARRAY[1,2,3]);\nSELECT _timescaledb_functions.get_partition_hash('08002b:010203'::macaddr);\nSELECT _timescaledb_functions.get_partition_hash('192.168.100.128/25'::cidr);\nSELECT _timescaledb_functions.get_partition_hash('192.168.100.128'::inet);\nSELECT _timescaledb_functions.get_partition_hash('2001:4f8:3:ba:2e0:81ff:fe22:d1f1'::inet);\nSELECT _timescaledb_functions.get_partition_hash('2001:4f8:3:ba:2e0:81ff:fe22:d1f1/128'::cidr);\nSELECT _timescaledb_functions.get_partition_hash('{ \"foo\": \"bar\" }'::jsonb);\nSELECT _timescaledb_functions.get_partition_hash('4b6a5eec-b344-11e7-abc4-cec278b6b50a'::uuid);\nSELECT _timescaledb_functions.get_partition_hash(1::regclass);\nSELECT _timescaledb_functions.get_partition_hash(int4range(10, 20));\nSELECT _timescaledb_functions.get_partition_hash(int8range(10, 20));\nSELECT _timescaledb_functions.get_partition_hash(numrange(10, 20));\nSELECT _timescaledb_functions.get_partition_hash(tsrange('2017-03-22T09:18:23', '2017-03-23T09:18:23'));\nSELECT _timescaledb_functions.get_partition_hash(tstzrange('2017-03-22T09:18:23+01', '2017-03-23T09:18:23+00'));\n\n-- Test hashing Var values\nCREATE TABLE hash_test(id int, value text);\nINSERT INTO hash_test VALUES (1, 'test');\n\n-- Test Vars\nSELECT _timescaledb_functions.get_partition_hash(id) FROM hash_test;\nSELECT _timescaledb_functions.get_partition_hash(value) FROM hash_test;\n-- Test coerced value\nSELECT _timescaledb_functions.get_partition_hash(id::text) FROM hash_test;\n\n-- Test legacy function that converts values to text first\nSELECT _timescaledb_functions.get_partition_for_key('4b6a5eec-b344-11e7-abc4-cec278b6b50a'::text);\nSELECT _timescaledb_functions.get_partition_for_key('4b6a5eec-b344-11e7-abc4-cec278b6b50a'::varchar);\nSELECT _timescaledb_functions.get_partition_for_key(187);\nSELECT _timescaledb_functions.get_partition_for_key(187::bigint);\nSELECT _timescaledb_functions.get_partition_for_key(187::numeric);\nSELECT _timescaledb_functions.get_partition_for_key(187::double precision);\nSELECT _timescaledb_functions.get_partition_for_key(int4range(10, 20));\nSELECT _timescaledb_functions.get_partition_hash('08002b:010203'::macaddr);\n\n-- Test inside IMMUTABLE function (Issue #4575)\nCREATE FUNCTION my_get_partition_hash(INTEGER) RETURNS INTEGER\nAS 'SELECT _timescaledb_functions.get_partition_hash($1);'\nLANGUAGE SQL IMMUTABLE;\n\nCREATE FUNCTION my_get_partition_for_key(INTEGER) RETURNS INTEGER\nAS 'SELECT _timescaledb_functions.get_partition_for_key($1);'\nLANGUAGE SQL IMMUTABLE;\n\nSELECT my_get_partition_hash(1);\nSELECT my_get_partition_for_key(1);\n"
  },
  {
    "path": "test/sql/histogram_test.sql.in",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- table 1\nCREATE TABLE \"hitest1\"(key real, val varchar(40));\n\n-- insertions\nINSERT INTO \"hitest1\" VALUES(0, 'hi');\nINSERT INTO \"hitest1\" VALUES(1, 'sup');\nINSERT INTO \"hitest1\" VALUES(2, 'hello');\nINSERT INTO \"hitest1\" VALUES(3, 'yo');\nINSERT INTO \"hitest1\" VALUES(4, 'howdy');\nINSERT INTO \"hitest1\" VALUES(5, 'hola');\nINSERT INTO \"hitest1\" VALUES(6, 'ya');\nINSERT INTO \"hitest1\" VALUES(1, 'sup');\nINSERT INTO \"hitest1\" VALUES(2, 'hello');\nINSERT INTO \"hitest1\" VALUES(1, 'sup');\n\n-- table 2\nCREATE TABLE \"hitest2\"(name varchar(30), score integer, qualify boolean);\n\n-- insertions\nINSERT INTO \"hitest2\" VALUES('Tom', 6, TRUE);\nINSERT INTO \"hitest2\" VALUES('Mary', 4, FALSE);\nINSERT INTO \"hitest2\" VALUES('Jaq', 3, FALSE);\nINSERT INTO \"hitest2\" VALUES('Jane', 10, TRUE);\n\n-- standard 2 bucket\nSELECT histogram(key, 0, 9, 2) FROM hitest1;\n-- standard multi-bucket\nSELECT histogram(key, 0, 9, 5) FROM hitest1;\n-- standard 3 bucket\nSELECT val, histogram(key, 0, 7, 3) FROM hitest1 GROUP BY val ORDER BY val;\n-- standard element beneath lb\nSELECT histogram(key, 1, 7, 3) FROM hitest1;\n-- standard element above ub\nSELECT histogram(key, 0, 3, 3) FROM hitest1;\n-- standard element beneath and above lb and ub, respectively\nSELECT histogram(key, 1, 3, 2) FROM hitest1;\n\n-- standard 1 bucket\nSELECT histogram(key, 1, 3, 1) FROM hitest1;\n\n-- standard 2 bucket\nSELECT qualify, histogram(score, 0, 10, 2) FROM hitest2 GROUP BY qualify ORDER BY qualify;\n-- standard multi-bucket\nSELECT qualify, histogram(score, 0, 10, 5) FROM hitest2 GROUP BY qualify ORDER BY qualify;\n\n-- check number of buckets is constant\n\\set ON_ERROR_STOP 0\nselect histogram(i,10,90,case when i=1 then 1 else 1000000 end) FROM generate_series(1,100) i;\n\\set ON_ERROR_STOP 1\n\nCREATE TABLE weather (\n       time TIMESTAMPTZ NOT NULL,\n       city TEXT,\n       temperature FLOAT,\n       PRIMARY KEY(time, city)\n);\n\n-- There is a bug in width_bucket() causing a NaN as a result, so we\n-- check that it is not causing a crash in histogram().\nSELECT * FROM create_hypertable('weather', 'time', 'city', 3);\nINSERT INTO weather VALUES\n       ('2023-02-10 09:16:51.133584+00','city1',10.4),\n       ('2023-02-10 11:16:51.611618+00','city1',10.3),\n       ('2023-02-10 06:58:59.999999+00','city1',10.3),\n       ('2023-02-10 01:58:59.999999+00','city1',10.3),\n       ('2023-02-09 01:58:59.999999+00','city1',10.3),\n       ('2023-02-10 08:58:59.999999+00','city1',10.3),\n       ('2023-03-23 06:12:02.73765+00 ','city1', 9.7),\n       ('2023-03-23 06:12:06.990998+00','city1',11.7);\n\n-- This will currently generate an error on PG15 and prior versions\n\\set ON_ERROR_STOP 0\nSELECT histogram(temperature, -1.79769e+308, 1.79769e+308,10) FROM weather GROUP BY city;\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "test/sql/include/agg_bookends_load.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE TABLE btest(time timestamp NOT NULL, time_alt timestamp, gp INTEGER, temp float, strid TEXT DEFAULT 'testing');\nSELECT schema_name, table_name, created FROM create_hypertable('btest', 'time');\nINSERT INTO btest VALUES('2017-01-20T09:00:01', '2017-01-20T10:00:00', 1, 22.5);\nINSERT INTO btest VALUES('2017-01-20T09:00:21', '2017-01-20T09:00:59', 1, 21.2);\nINSERT INTO btest VALUES('2017-01-20T09:00:47', '2017-01-20T09:00:58', 1, 25.1);\nINSERT INTO btest VALUES('2017-01-20T09:00:02', '2017-01-20T09:00:57', 2, 35.5);\nINSERT INTO btest VALUES('2017-01-20T09:00:21', '2017-01-20T09:00:56', 2, 30.2);\n--TOASTED;\nINSERT INTO btest VALUES('2017-01-20T09:00:43', '2017-01-20T09:01:55', 2, 20.1, repeat('xyz', 1000000) );\n\nCREATE TABLE btest_numeric (time timestamp NOT NULL, quantity numeric);\nSELECT schema_name, table_name, created FROM create_hypertable('btest_numeric', 'time');\n\n"
  },
  {
    "path": "test/sql/include/agg_bookends_query.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- canary for results diff\n-- this should be only output of results diff\n\nSELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations')) v(setting);\n\n\n:PREFIX SELECT time, gp, temp FROM btest ORDER BY time;\n\n:PREFIX SELECT last(temp, time) FROM btest;\n:PREFIX SELECT first(temp, time) FROM btest;\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n\n:PREFIX SELECT gp, last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, first(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n\n--check whole row\n:PREFIX SELECT gp, first(btest, time) FROM btest GROUP BY gp ORDER BY gp;\n\n--check toasted col\n:PREFIX SELECT gp, left(last(strid, time), 10) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, last(temp, strid) FROM btest GROUP BY gp ORDER BY gp;\n:PREFIX SELECT gp, last(strid, temp) FROM btest GROUP BY gp ORDER BY gp;\n\nBEGIN;\n\n--check null value as last element\nINSERT INTO btest VALUES('2018-01-20T09:00:43', '2017-01-20T09:00:55', 2, NULL);\n:PREFIX SELECT last(temp, time) FROM btest;\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest VALUES('2019-01-20T09:00:43', '2018-01-20T09:00:55', 2, 30.5);\n:PREFIX SELECT last(temp, time) FROM btest;\n\n--check null cmp element is skipped\nINSERT INTO btest VALUES('2018-01-20T09:00:43', NULL, 2, 32.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n-- fist returns NULL value\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n-- test first return non NULL value\nINSERT INTO btest VALUES('2016-01-20T09:00:00', '2016-01-20T09:00:00', 2, 36.5);\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--check non null cmp element insert after null cmp\nINSERT INTO btest VALUES('2020-01-20T09:00:43', '2020-01-20T09:00:43', 2, 35.3);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n--cmp nulls should be ignored and not present in groups\n:PREFIX SELECT gp, last(temp, time_alt) FROM btest GROUP BY gp ORDER BY gp;\n\n--Previously, some bugs were found with NULLS and numeric types, so test that\nINSERT INTO btest_numeric VALUES ('2019-01-20T09:00:43', NULL);\n\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n\n--check non-null element \"overrides\" NULL because it comes after.\nINSERT INTO btest_numeric VALUES('2020-01-20T09:00:43', 30.5);\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n\n-- do index scan for last\n:PREFIX SELECT last(temp, time) FROM btest;\n\n-- do index scan for first\n:PREFIX SELECT first(temp, time) FROM btest;\n\n-- can't do index scan when ordering on non-index column\n:PREFIX SELECT first(temp, time_alt) FROM btest;\n\n-- do index scan for subquery\n:PREFIX SELECT * FROM (SELECT last(temp, time) FROM btest) last;\n\n-- can't do index scan when using group by\n:PREFIX SELECT last(temp, time) FROM btest GROUP BY gp ORDER BY gp;\n\n-- do index scan when agg function is used in CTE subquery\n:PREFIX WITH last_temp AS (SELECT last(temp, time) FROM btest) SELECT * from last_temp;\n\n-- do index scan when using both FIRST and LAST aggregate functions\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n\n-- verify results when using both FIRST and LAST\n:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;\n\n-- do index scan when using WHERE\n:PREFIX SELECT last(temp, time) FROM btest WHERE time <= '2017-01-20T09:00:02';\n\n-- can't do index scan for MAX and LAST combined (MinMax optimization fails when having different aggregate functions)\n:PREFIX SELECT max(time), last(temp, time) FROM btest;\n\n-- can't do index scan when using FIRST/LAST in ORDER BY\n:PREFIX SELECT last(temp, time) FROM btest ORDER BY last(temp, time);\n\n-- do index scan\n:PREFIX SELECT last(temp, time) FROM btest WHERE temp < 30;\n\n-- SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n\n-- do index scan\n:PREFIX SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47';\n\n-- can't do index scan when using WINDOW function\n:PREFIX SELECT gp, last(temp, time) OVER (PARTITION BY gp) AS last FROM btest;\n\n-- test constants\n:PREFIX SELECT first(100, 100) FROM btest;\n\n-- create an index so we can test optimization\nCREATE INDEX btest_time_alt_idx ON btest(time_alt);\n:PREFIX SELECT last(temp, time_alt) FROM btest;\n\n--test nested FIRST/LAST - should optimize\n:PREFIX SELECT abs(last(temp, time)) FROM btest;\n\n-- test nested FIRST/LAST in ORDER BY - no optimization possible\n:PREFIX SELECT abs(last(temp, time)) FROM btest ORDER BY abs(last(temp,time));\n\nROLLBACK;\n\n-- Test with NULL numeric values\nBEGIN;\nTRUNCATE btest_numeric;\n\n-- Empty table\n:PREFIX SELECT first(btest_numeric, time) FROM btest_numeric;\n:PREFIX SELECT last(btest_numeric, time) FROM btest_numeric;\n\n-- Only NULL values\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n\n-- NULL values followed by non-NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n\nTRUNCATE btest_numeric;\n\n-- non-NULL values followed by NULL values\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);\nINSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\nINSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);\n:PREFIX SELECT first(quantity, time) FROM btest_numeric;\n:PREFIX SELECT last(quantity, time) FROM btest_numeric;\n:PREFIX SELECT first(time, quantity) FROM btest_numeric;\n:PREFIX SELECT last(time, quantity) FROM btest_numeric;\n\nROLLBACK;\n"
  },
  {
    "path": "test/sql/include/append_load.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- create a now() function for repeatable testing that always returns\n-- the same timestamp. It needs to be marked STABLE\nCREATE OR REPLACE FUNCTION now_s()\nRETURNS timestamptz LANGUAGE PLPGSQL STABLE PARALLEL SAFE AS\n$BODY$\nBEGIN\n    RAISE NOTICE 'Stable function now_s() called!';\n    RETURN '2017-08-22T10:00:00'::timestamptz;\nEND;\n$BODY$;\n\nCREATE OR REPLACE FUNCTION now_i()\nRETURNS timestamptz LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nBEGIN\n    RAISE NOTICE 'Immutable function now_i() called!';\n    RETURN '2017-08-22T10:00:00'::timestamptz;\nEND;\n$BODY$;\n\nCREATE OR REPLACE FUNCTION now_v()\nRETURNS timestamptz LANGUAGE PLPGSQL VOLATILE AS\n$BODY$\nBEGIN\n    RAISE NOTICE 'Volatile function now_v() called!';\n    RETURN '2017-08-22T10:00:00'::timestamptz;\nEND;\n$BODY$;\n\nCREATE OR REPLACE PROCEDURE force_parallel(on_or_off bool)\nLANGUAGE PLPGSQL AS\n$$\nBEGIN\n    IF current_setting('server_version_num')::int < 160000 THEN\n        IF on_or_off THEN\n            set force_parallel_mode = 'on';\n        ELSE\n            set force_parallel_mode = 'off';\n        END IF;\n    ELSE\n        IF on_or_off THEN\n            set debug_parallel_query = 'on';\n        ELSE\n            set debug_parallel_query = 'off';\n        END IF;\n    END IF;\nEND;\n$$;\n\n\nCREATE TABLE append_test(time timestamptz, temp float, colorid integer, attr jsonb);\nSELECT create_hypertable('append_test', 'time', chunk_time_interval => 2628000000000);\n\n-- create three chunks\nINSERT INTO append_test VALUES ('2017-03-22T09:18:22', 23.5, 1, '{\"a\": 1, \"b\": 2}'),\n                               ('2017-03-22T09:18:23', 21.5, 1, '{\"a\": 1, \"b\": 2}'),\n                               ('2017-05-22T09:18:22', 36.2, 2, '{\"c\": 3, \"b\": 2}'),\n                               ('2017-05-22T09:18:23', 15.2, 2, '{\"c\": 3}'),\n                               ('2017-08-22T09:18:22', 34.1, 3, '{\"c\": 4}');\nVACUUM (ANALYZE) append_test;\n\n-- Create another hypertable to join with\nCREATE TABLE join_test(time timestamptz, temp float, colorid integer);\nSELECT create_hypertable('join_test', 'time', chunk_time_interval => 2628000000000);\n\nINSERT INTO join_test VALUES ('2017-01-22T09:18:22', 15.2, 1),\n                             ('2017-02-22T09:18:22', 24.5, 2),\n                             ('2017-08-22T09:18:22', 23.1, 3);\nVACUUM (ANALYZE) join_test;\n\n-- Create another table to join with which is not a hypertable.\nCREATE TABLE join_test_plain(time timestamptz, temp float, colorid integer, attr jsonb);\n\nINSERT INTO join_test_plain VALUES ('2017-01-22T09:18:22', 15.2, 1, '{\"a\": 1}'),\n                             ('2017-02-22T09:18:22', 24.5, 2, '{\"b\": 2}'),\n                             ('2017-08-22T09:18:22', 23.1, 3, '{\"c\": 3}');\nVACUUM (ANALYZE) join_test_plain;\n\n-- create hypertable with DATE time dimension\nCREATE TABLE metrics_date(time DATE NOT NULL);\nSELECT create_hypertable('metrics_date','time');\nINSERT INTO metrics_date SELECT generate_series('2000-01-01'::date, '2000-02-01'::date, '5m'::interval);\nVACUUM (ANALYZE) metrics_date;\n\n-- create hypertable with TIMESTAMP time dimension\nCREATE TABLE metrics_timestamp(time TIMESTAMP NOT NULL);\nSELECT create_hypertable('metrics_timestamp','time');\nINSERT INTO metrics_timestamp SELECT generate_series('2000-01-01'::date, '2000-02-01'::date, '5m'::interval);\nVACUUM (ANALYZE) metrics_timestamp;\n\n-- create hypertable with TIMESTAMPTZ time dimension\nCREATE TABLE metrics_timestamptz(time TIMESTAMPTZ NOT NULL, device_id INT NOT NULL);\nCREATE INDEX ON metrics_timestamptz(device_id,time);\nSELECT create_hypertable('metrics_timestamptz','time');\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::date, '2000-02-01'::date, '5m'::interval), 1;\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::date, '2000-02-01'::date, '5m'::interval), 2;\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::date, '2000-02-01'::date, '5m'::interval), 3;\nVACUUM (ANALYZE) metrics_timestamptz;\n\n-- create space partitioned hypertable\nCREATE TABLE metrics_space(time timestamptz NOT NULL, device_id int NOT NULL, v1 float, v2 float, v3 text);\nSELECT create_hypertable('metrics_space','time','device_id',3);\n\nINSERT INTO metrics_space\nSELECT time, device_id, device_id + 0.25, device_id + 0.75, device_id\nFROM generate_series('2000-01-01'::timestamptz, '2000-01-14'::timestamptz, '5m'::interval) g1(time),\n  generate_series(1,10,1) g2(device_id)\nORDER BY time, device_id;\n\nVACUUM (ANALYZE) metrics_space;\n\n-- test ChunkAppend projection #2661\nCREATE TABLE i2661 (\n  machine_id int4 NOT NULL,\n  \"name\" varchar(255) NOT NULL,\n  \"timestamp\" timestamptz NOT NULL,\n  \"first\" float4 NULL\n);\nSELECT create_hypertable('i2661', 'timestamp');\n\nINSERT INTO i2661 SELECT 1, 'speed', generate_series('2019-12-31 00:00:00', '2020-01-10 00:00:00', '2m'::interval), 0;\nVACUUM (ANALYZE) i2661;\n"
  },
  {
    "path": "test/sql/include/append_query.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- canary for results diff\n-- this should be the only output of the results diff\nSELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations'),('timescaledb.enable_chunk_append')) v(setting);\n\n-- query should exclude all chunks with optimization on\n:PREFIX\nSELECT * FROM append_test WHERE time > now_s() + '1 month'\nORDER BY time DESC;\n\n--query should exclude all chunks and be a MergeAppend\n:PREFIX\nSELECT * FROM append_test WHERE time > now_s() + '1 month'\nORDER BY time DESC limit 1;\n\n-- when optimized, the plan should be a constraint-aware append and\n-- cover only one chunk. It should be a backward index scan due to\n-- descending index on time. Should also skip the main table, since it\n-- cannot hold tuples\n:PREFIX\nSELECT * FROM append_test WHERE time > now_s() - interval '2 months';\n\n-- adding ORDER BY and LIMIT should turn the plan into an optimized\n-- ordered append plan\n:PREFIX\nSELECT * FROM append_test WHERE time > now_s() - interval '2 months'\nORDER BY time LIMIT 3;\n\n-- no optimized plan for queries with restrictions that can be\n-- constified at planning time. Regular planning-time constraint\n-- exclusion should occur.\n:PREFIX\nSELECT * FROM append_test WHERE time > now_i() - interval '2 months'\nORDER BY time;\n\n-- currently, we cannot distinguish between stable and volatile\n-- functions as far as applying our modified plan. However, volatile\n-- function should not be pre-evaluated to constants, so no chunk\n-- exclusion should occur.\n:PREFIX\nSELECT * FROM append_test WHERE time > now_v() - interval '2 months'\nORDER BY time;\n\n-- prepared statement output should be the same regardless of\n-- optimizations\nPREPARE query_opt AS\nSELECT * FROM append_test WHERE time > now_s() - interval '2 months'\nORDER BY time;\n\n:PREFIX EXECUTE query_opt;\n\nDEALLOCATE query_opt;\n\n-- aggregates should produce same output\n:PREFIX\nSELECT date_trunc('year', time) t, avg(temp) FROM append_test\nWHERE time > now_s() - interval '4 months'\nGROUP BY t\nORDER BY t DESC;\n\n-- querying outside the time range should return nothing. This tests\n-- that ConstraintAwareAppend can handle the case when an Append node\n-- is turned into a Result node due to no children\n:PREFIX\nSELECT date_trunc('year', time) t, avg(temp)\nFROM append_test\nWHERE time < '2016-03-22'\nAND date_part('dow', time) between 1 and 5\nGROUP BY t\nORDER BY t DESC;\n\n-- a parameterized query can safely constify params, so won't be\n-- optimized by constraint-aware append since regular constraint\n-- exclusion works just fine\nPREPARE query_param AS\nSELECT * FROM append_test WHERE time > $1 ORDER BY time;\n\n:PREFIX\nEXECUTE query_param(now_s() - interval '2 months');\nDEALLOCATE query_param;\n\n--test with cte\n:PREFIX\nWITH data AS (\n    SELECT time_bucket(INTERVAL '30 day', TIME) AS btime, AVG(temp) AS VALUE\n    FROM append_test\n    WHERE\n        TIME > now_s() - INTERVAL '400 day'\n    AND colorid > 0\n    GROUP BY btime\n),\nperiod AS (\n    SELECT time_bucket(INTERVAL '30 day', TIME) AS btime\n      FROM  GENERATE_SERIES('2017-03-22T01:01:01', '2017-08-23T01:01:01', INTERVAL '30 day') TIME\n  )\nSELECT period.btime, VALUE\n    FROM period\n    LEFT JOIN DATA USING (btime)\n    ORDER BY period.btime;\n\nWITH data AS (\n    SELECT time_bucket(INTERVAL '30 day', TIME) AS btime, AVG(temp) AS VALUE\n    FROM append_test\n    WHERE\n        TIME > now_s() - INTERVAL '400 day'\n    AND colorid > 0\n    GROUP BY btime\n),\nperiod AS (\n    SELECT time_bucket(INTERVAL '30 day', TIME) AS btime\n      FROM  GENERATE_SERIES('2017-03-22T01:01:01', '2017-08-23T01:01:01', INTERVAL '30 day') TIME\n  )\nSELECT period.btime, VALUE\n    FROM period\n    LEFT JOIN DATA USING (btime)\n    ORDER BY period.btime;\n\n-- force nested loop join with no materialization. This tests that the\n-- inner ConstraintAwareScan supports resetting its scan for every\n-- iteration of the outer relation loop\nset enable_hashjoin = 'off';\nset enable_mergejoin = 'off';\nset enable_material = 'off';\n\n:PREFIX\nSELECT * FROM append_test a INNER JOIN join_test j ON (a.colorid = j.colorid)\nWHERE a.time > now_s() - interval '3 hours' AND j.time > now_s() - interval '3 hours';\n\nreset enable_hashjoin;\nreset enable_mergejoin;\nreset enable_material;\n\n-- test constraint_exclusion with date time dimension and DATE/TIMESTAMP/TIMESTAMPTZ constraints\n-- the queries should all have 3 chunks\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::date ORDER BY time;\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::timestamp ORDER BY time;\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::timestamptz ORDER BY time;\n\n-- test Const OP Var\n-- the queries should all have 3 chunks\n:PREFIX SELECT * FROM metrics_date WHERE '2000-01-15'::date < time ORDER BY time;\n:PREFIX SELECT * FROM metrics_date WHERE '2000-01-15'::timestamp < time ORDER BY time;\n:PREFIX SELECT * FROM metrics_date WHERE '2000-01-15'::timestamptz < time ORDER BY time;\n\n-- test 2 constraints\n-- the queries should all have 2 chunks\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::date AND time < '2000-01-21'::date ORDER BY time;\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::timestamp AND time < '2000-01-21'::timestamp ORDER BY time;\n:PREFIX SELECT * FROM metrics_date WHERE time > '2000-01-15'::timestamptz AND time < '2000-01-21'::timestamptz ORDER BY time;\n\n-- test constraint_exclusion with timestamp time dimension and DATE/TIMESTAMP/TIMESTAMPTZ constraints\n-- the queries should all have 3 chunks\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::date ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::timestamp ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::timestamptz ORDER BY time;\n\n-- test Const OP Var\n-- the queries should all have 3 chunks\n:PREFIX SELECT * FROM metrics_timestamp WHERE '2000-01-15'::date < time ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamp WHERE '2000-01-15'::timestamp < time ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamp WHERE '2000-01-15'::timestamptz < time ORDER BY time;\n\n-- test 2 constraints\n-- the queries should all have 2 chunks\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::date AND time < '2000-01-21'::date ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::timestamp AND time < '2000-01-21'::timestamp ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamp WHERE time > '2000-01-15'::timestamptz AND time < '2000-01-21'::timestamptz ORDER BY time;\n\n-- test constraint_exclusion with timestamptz time dimension and DATE/TIMESTAMP/TIMESTAMPTZ constraints\n-- the queries should all have 3 chunks\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::date ORDER BY time;\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::timestamp ORDER BY time;\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::timestamptz ORDER BY time;\n\n-- test Const OP Var\n-- the queries should all have 3 chunks\n:PREFIX SELECT time FROM metrics_timestamptz WHERE '2000-01-15'::date < time ORDER BY time;\n:PREFIX SELECT time FROM metrics_timestamptz WHERE '2000-01-15'::timestamp < time ORDER BY time;\n:PREFIX SELECT time FROM metrics_timestamptz WHERE '2000-01-15'::timestamptz < time ORDER BY time;\n\n-- test 2 constraints\n-- the queries should all have 2 chunks\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::date AND time < '2000-01-21'::date ORDER BY time;\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::timestamp AND time < '2000-01-21'::timestamp ORDER BY time;\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > '2000-01-15'::timestamptz AND time < '2000-01-21'::timestamptz ORDER BY time;\n\n-- test constraint_exclusion with space partitioning and DATE/TIMESTAMP/TIMESTAMPTZ constraints\n-- exclusion for constraints with non-matching datatypes not working for space partitioning atm\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::date ORDER BY time;\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamp ORDER BY time;\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz ORDER BY time;\n\n-- test Const OP Var\n-- exclusion for constraints with non-matching datatypes not working for space partitioning atm\n:PREFIX SELECT time FROM metrics_space WHERE '2000-01-10'::date < time ORDER BY time;\n:PREFIX SELECT time FROM metrics_space WHERE '2000-01-10'::timestamp < time ORDER BY time;\n:PREFIX SELECT time FROM metrics_space WHERE '2000-01-10'::timestamptz < time ORDER BY time;\n\n-- test 2 constraints\n-- exclusion for constraints with non-matching datatypes not working for space partitioning atm\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::date AND time < '2000-01-15'::date ORDER BY time;\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamp AND time < '2000-01-15'::timestamp ORDER BY time;\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz AND time < '2000-01-15'::timestamptz ORDER BY time;\n\n-- test filtering on space partition\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz AND device_id = 1 ORDER BY time;\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz AND device_id IN (1,2) ORDER BY time;\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz AND device_id IN (VALUES(1)) ORDER BY time;\n:PREFIX SELECT time FROM metrics_space WHERE time > '2000-01-10'::timestamptz AND v3 IN (VALUES('1')) ORDER BY time;\n\n:PREFIX SELECT * FROM metrics_space\nWHERE time = (VALUES ('2019-12-24' at time zone 'UTC'))\n  AND v3 NOT IN (VALUES ('1'));\n\n-- test CURRENT_DATE\n-- should be 0 chunks\n:PREFIX SELECT time FROM metrics_date WHERE time > CURRENT_DATE ORDER BY time;\n:PREFIX SELECT time FROM metrics_timestamp WHERE time > CURRENT_DATE ORDER BY time;\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > CURRENT_DATE ORDER BY time;\n:PREFIX SELECT time FROM metrics_space WHERE time > CURRENT_DATE ORDER BY time;\n\n-- test CURRENT_TIMESTAMP\n-- should be 0 chunks\n:PREFIX SELECT time FROM metrics_date WHERE time > CURRENT_TIMESTAMP ORDER BY time;\n:PREFIX SELECT time FROM metrics_timestamp WHERE time > CURRENT_TIMESTAMP ORDER BY time;\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > CURRENT_TIMESTAMP ORDER BY time;\n:PREFIX SELECT time FROM metrics_space WHERE time > CURRENT_TIMESTAMP ORDER BY time;\n\n-- test now()\n-- should be 0 chunks\n:PREFIX SELECT time FROM metrics_date WHERE time > now() ORDER BY time;\n:PREFIX SELECT time FROM metrics_timestamp WHERE time > now() ORDER BY time;\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time > now() ORDER BY time;\n:PREFIX SELECT time FROM metrics_space WHERE time > now() ORDER BY time;\n\n-- query with tablesample and planner exclusion\n:PREFIX\nSELECT * FROM metrics_date TABLESAMPLE BERNOULLI(5) REPEATABLE(0)\nWHERE time > '2000-01-15'\nORDER BY time DESC;\n\n-- query with tablesample and startup exclusion\n:PREFIX\nSELECT * FROM metrics_date TABLESAMPLE BERNOULLI(5) REPEATABLE(0)\nWHERE time > '2000-01-15'::text::date\nORDER BY time DESC;\n\n-- query with tablesample, space partitioning and planner exclusion\n:PREFIX\nSELECT * FROM metrics_space TABLESAMPLE BERNOULLI(5) REPEATABLE(0)\nWHERE time > '2000-01-10'::timestamptz\nORDER BY time DESC, device_id;\n\n-- test runtime exclusion\n\n-- test runtime exclusion with LATERAL and 2 hypertables\n:PREFIX SELECT m1.time, m2.time FROM metrics_timestamptz m1 LEFT JOIN LATERAL(SELECT time FROM metrics_timestamptz m2 WHERE m1.time = m2.time LIMIT 1) m2 ON true ORDER BY m1.time;\n\n-- test runtime exclusion and startup exclusions\n:PREFIX SELECT m1.time, m2.time FROM metrics_timestamptz m1 LEFT JOIN LATERAL(SELECT time FROM metrics_timestamptz m2 WHERE m1.time = m2.time AND m2.time < '2000-01-10'::text::timestamptz LIMIT 1) m2 ON true ORDER BY m1.time;\n\n-- test runtime exclusion does not activate for constraints on non-partitioning columns\n-- should not use runtime exclusion\n:PREFIX SELECT * FROM append_test a LEFT JOIN LATERAL(SELECT * FROM join_test j WHERE a.colorid = j.colorid ORDER BY time DESC LIMIT 1) j ON true ORDER BY a.time LIMIT 1;\n\n-- test runtime exclusion with LATERAL and generate_series\n:PREFIX SELECT g.time FROM generate_series('2000-01-01'::timestamptz, '2000-02-01'::timestamptz, '1d'::interval) g(time) LEFT JOIN LATERAL(SELECT time FROM metrics_timestamptz m WHERE m.time=g.time LIMIT 1) m ON true;\n:PREFIX SELECT * FROM generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval) AS g(time) INNER JOIN LATERAL (SELECT time FROM metrics_timestamptz m WHERE time=g.time) m ON true;\n:PREFIX SELECT * FROM generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval) AS g(time) INNER JOIN LATERAL (SELECT time FROM metrics_timestamptz m WHERE time=g.time ORDER BY time) m ON true;\n:PREFIX SELECT * FROM generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval) AS g(time) INNER JOIN LATERAL (SELECT time FROM metrics_timestamptz m WHERE time>g.time + '1 day' ORDER BY time LIMIT 1) m ON true;\n\n-- test runtime exclusion with subquery\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 WHERE m1.time=(SELECT max(time) FROM metrics_timestamptz);\n\n-- test runtime exclusion with correlated subquery\n:PREFIX SELECT m1.time, (SELECT m2.time FROM metrics_timestamptz m2 WHERE m2.time < m1.time ORDER BY m2.time DESC LIMIT 1) FROM metrics_timestamptz m1 WHERE m1.time < '2000-01-10' ORDER BY m1.time;\n\n-- test EXISTS\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 WHERE EXISTS(SELECT 1 FROM metrics_timestamptz m2 WHERE m1.time < m2.time) ORDER BY m1.time DESC limit 1000;\n\n-- test constraint exclusion for subqueries with append\n-- should include 2 chunks\n:PREFIX SELECT time FROM (SELECT time FROM metrics_timestamptz WHERE time < '2000-01-10'::text::timestamptz ORDER BY time) m;\n\n-- test constraint exclusion for subqueries with mergeappend\n-- should include 2 chunks\n:PREFIX SELECT device_id, time FROM (SELECT device_id, time FROM metrics_timestamptz WHERE time < '2000-01-10'::text::timestamptz ORDER BY device_id, time) m;\n\n-- test LIMIT pushdown\n-- no aggregates/window functions/SRF should pushdown limit\n:PREFIX SELECT FROM metrics_timestamptz ORDER BY time LIMIT 1;\n\n-- aggregates should prevent pushdown\n:PREFIX SELECT count(*) FROM metrics_timestamptz LIMIT 1;\n:PREFIX SELECT count(*) FROM metrics_space LIMIT 1;\n\n-- HAVING should prevent pushdown\n:PREFIX SELECT 1 FROM metrics_timestamptz HAVING count(*) > 1 LIMIT 1;\n:PREFIX SELECT 1 FROM metrics_space HAVING count(*) > 1 LIMIT 1;\n\n-- DISTINCT should prevent pushdown\nSET enable_hashagg TO false;\n:PREFIX SELECT DISTINCT device_id FROM metrics_timestamptz ORDER BY device_id LIMIT 3;\n:PREFIX SELECT DISTINCT device_id FROM metrics_space ORDER BY device_id LIMIT 3;\nRESET enable_hashagg;\n\n-- JOINs should prevent pushdown\n-- when LIMIT gets pushed to a Sort node it will switch to top-N heapsort\n-- if more tuples then LIMIT are requested this will trigger an error\n-- to trigger this we need a Sort node that is below ChunkAppend\nCREATE TABLE join_limit (time timestamptz, device_id int);\nSELECT table_name FROM create_hypertable('join_limit','time',create_default_indexes:=false);\nCREATE INDEX ON join_limit(time,device_id);\n\nINSERT INTO join_limit\nSELECT time, device_id\nFROM generate_series('2000-01-01'::timestamptz,'2000-01-21','30m') g1(time),\n  generate_series(1,10,1) g2(device_id)\nORDER BY time, device_id;\nVACUUM (ANALYZE) join_limit;\n\n-- get 2nd chunk oid\nSELECT tableoid AS \"CHUNK_OID\" FROM join_limit WHERE time > '2000-01-07' ORDER BY time LIMIT 1\n\\gset\n--get index name for 2nd chunk\nSELECT indexrelid::regclass AS \"INDEX_NAME\" FROM pg_index WHERE indrelid = :CHUNK_OID\n\\gset\nDROP INDEX :INDEX_NAME;\n\n:PREFIX SELECT * FROM metrics_timestamptz m1 INNER JOIN join_limit m2 ON m1.time = m2.time AND m1.device_id=m2.device_id WHERE m1.time > '2000-01-07' ORDER BY m1.time, m1.device_id LIMIT 3;\n\nDROP TABLE join_limit;\n\n-- test ChunkAppend projection #2661\n:PREFIX SELECT ts.timestamp, ht.timestamp\nFROM (\n  SELECT generate_series(\n    to_timestamp(FLOOR(EXTRACT (EPOCH FROM '2020-01-01T00:01:00Z'::timestamp) / 300) * 300) AT TIME ZONE 'UTC',\n    '2020-01-01T01:00:00Z',\n    '5 minutes'::interval\n  ) AS timestamp\n) ts\nLEFT JOIN i2661 ht ON\n  (FLOOR(EXTRACT (EPOCH FROM ht.\"timestamp\") / 300) * 300 = EXTRACT (EPOCH FROM ts.timestamp))\n  AND ht.timestamp > '2019-12-30T00:00:00Z'::timestamp\nORDER BY ts.timestamp, ht.timestamp;\n\n-- #3030 test chunkappend keeps pathkeys when subpath is append\n-- on PG11 this will not use ChunkAppend but MergeAppend\nSET enable_seqscan TO FALSE;\nCREATE TABLE i3030(time timestamptz NOT NULL, a int, b int);\nSELECT table_name FROM create_hypertable('i3030', 'time', create_default_indexes=>false);\nCREATE INDEX ON i3030(a,time);\nINSERT INTO i3030 (time,a) SELECT time, a FROM generate_series('2000-01-01'::timestamptz,'2000-01-01 3:00:00'::timestamptz,'1min'::interval) time, generate_series(1,30) a;\nVACUUM (ANALYZE) i3030;\n\n:PREFIX SELECT * FROM i3030 where time BETWEEN '2000-01-01'::text::timestamptz AND '2000-01-03'::text::timestamptz ORDER BY a,time LIMIT 1;\nDROP TABLE i3030;\nRESET enable_seqscan;\n\n\n--parent runtime exclusion tests:\n--optimization works with ANY (array)\n:PREFIX\nSELECT *\nFROM append_test a\nWHERE a.attr @> ANY((SELECT coalesce(array_agg(attr), array[]::jsonb[]) FROM join_test_plain WHERE temp > 100)::jsonb[]);\n\n--optimization does not work for ANY subquery (does not force an initplan)\n:PREFIX\nSELECT *\nFROM append_test a\nWHERE a.attr @> ANY((SELECT attr FROM join_test_plain WHERE temp > 100));\n\n--works on any strict operator without ANY\n:PREFIX\nSELECT *\nFROM append_test a\nWHERE a.attr @> (SELECT attr FROM join_test_plain WHERE temp > 100 limit 1);\n\n\n--optimization works with function calls\nCREATE OR REPLACE FUNCTION select_tag(_min_temp int)\n RETURNS jsonb[]\n LANGUAGE sql\n STABLE PARALLEL SAFE\nAS $function$\n   SELECT coalesce(array_agg(attr), array[]::jsonb[])\n  FROM join_test_plain\n  WHERE temp > _min_temp\n$function$;\n\n:PREFIX\nSELECT *\nFROM append_test a\nWHERE a.attr @> ANY((SELECT select_tag(100))::jsonb[]);\n\n--optimization does not work when result is null\n:PREFIX\nSELECT *\nFROM append_test a\nWHERE a.attr @> ANY((SELECT array_agg(attr) FROM join_test_plain WHERE temp > 100)::jsonb[]);\n\n-- Test that ConstraintAwareAppend properly locks relations in\n-- parallel query mode\nset timescaledb.enable_chunk_append=false;\ncall force_parallel(true);\n\n:PREFIX\nselect time, avg(temp), colorid from append_test\nwhere time > now_s() - interval '3 months 20 days'\ngroup by time, colorid;\nreset timescaledb.enable_chunk_append;\n\nreset max_parallel_workers_per_gather;\ncall force_parallel(false);\n"
  },
  {
    "path": "test/sql/include/bgw_launcher_utils.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Note on testing: need a couple wrappers that pg_sleep in a loop to wait for changes\n-- to appear in pg_stat_activity.\n-- Further Note: PG 9.6 changed what appeared in pg_stat_activity, so the launcher doesn't actually show up.\n-- we can still test its interactions with its children, but can't test some of the things specific to the launcher.\n-- So we've added some bits about the version number as needed.\n\nCREATE VIEW worker_counts as\nSELECT count(*) filter (WHERE backend_type = 'TimescaleDB Background Worker Launcher') as launcher,\n       count(*) filter (WHERE backend_type = 'TimescaleDB Background Worker Scheduler' AND datname = :'TEST_DBNAME') as single_scheduler,\n       count(*) filter (WHERE backend_type = 'TimescaleDB Background Worker Scheduler' AND datname = :'TEST_DBNAME_2') as single_2_scheduler,\n       count(*) filter (WHERE backend_type = 'TimescaleDB Background Worker Scheduler' AND datname = 'template1') as template1_scheduler\n  FROM pg_stat_activity;\n\nCREATE FUNCTION wait_worker_counts(launcher_ct INTEGER,  scheduler1_ct INTEGER, scheduler2_ct INTEGER, template1_ct INTEGER) RETURNS BOOLEAN LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\nr INTEGER;\nBEGIN\nFOR i in 1..10\nLOOP\nSELECT COUNT(*) from worker_counts where launcher = launcher_ct\n\tAND single_scheduler = scheduler1_ct AND single_2_scheduler = scheduler2_ct and template1_scheduler = template1_ct into r;\nif(r < 1) THEN\n  PERFORM pg_sleep(0.1);\n  PERFORM pg_stat_clear_snapshot();\nELSE\n\t--We have the correct counts!\n  RETURN TRUE;\nEND IF;\nEND LOOP;\nRETURN FALSE;\nEND\n$BODY$;\n\nCREATE FUNCTION wait_for_bgw_scheduler(_datname NAME, _count INT DEFAULT 1, _ticks INT DEFAULT 10) RETURNS TEXT LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n  r INTEGER;\nBEGIN\n  FOR i in 1.._ticks\n  LOOP\n    SELECT count(*)\n    FROM pg_stat_activity\n    WHERE\n      application_name = 'TimescaleDB Background Worker Scheduler' AND\n      datname = _datname\n    INTO r;\n\n    IF(r <> _count) THEN\n      PERFORM pg_sleep(0.1);\n      PERFORM pg_stat_clear_snapshot();\n    ELSE\n      RETURN 'BGW Scheduler found.';\n    END IF;\n\n  END LOOP;\n\n  RETURN 'BGW Scheduler NOT found.';\n\nEND\n$BODY$;\n\nCREATE PROCEDURE kill_database_backends(_datname NAME) LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n  r INTEGER;\nBEGIN\n  FOR i in 1..100\n  LOOP\n    SELECT count(pg_terminate_backend(pg_stat_activity.pid))\n    FROM pg_stat_activity\n    WHERE\n      datname = _datname\n          AND pg_stat_activity.pid <> pg_backend_pid()\n    INTO r;\n\n    IF(r = 0) THEN\n        RETURN;\n    END IF;\n\n    PERFORM pg_sleep(0.1);\n    PERFORM pg_stat_clear_snapshot();\n  END LOOP;\n\n  RAISE 'Failed to terminate backends';\n\nEND\n$BODY$;\n"
  },
  {
    "path": "test/sql/include/bgw_launcher_utils_cleanup.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nDROP FUNCTION wait_worker_counts(integer, integer, integer, integer);\nDROP VIEW worker_counts;\nDROP FUNCTION wait_for_bgw_scheduler(name,int,int);\nDROP PROCEDURE kill_database_backends(name);\n\n"
  },
  {
    "path": "test/sql/include/ddl_ops_1.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE TABLE PUBLIC.\"Hypertable_1\" (\n  time BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  temp_c int NOT NULL DEFAULT -1,\n  humidity numeric NULL DEFAULT 0,\n  sensor_1 NUMERIC NULL DEFAULT 1,\n  sensor_2 NUMERIC NOT NULL DEFAULT 1,\n  sensor_3 NUMERIC NOT NULL DEFAULT 1,\n  sensor_4 NUMERIC NOT NULL DEFAULT 1\n);\nCREATE INDEX ON PUBLIC.\"Hypertable_1\" (time, \"Device_id\");\n\nCREATE TABLE \"customSchema\".\"Hypertable_1\" (\n  time BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  temp_c int NOT NULL DEFAULT -1,\n  humidity numeric NULL DEFAULT 0,\n  sensor_1 NUMERIC NULL DEFAULT 1,\n  sensor_2 NUMERIC NOT NULL DEFAULT 1,\n  sensor_3 NUMERIC NOT NULL DEFAULT 1,\n  sensor_4 NUMERIC NOT NULL DEFAULT 1\n);\nCREATE INDEX ON \"customSchema\".\"Hypertable_1\" (time, \"Device_id\");\n\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1\"', 'time', 'Device_id', 1, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\nSELECT * FROM create_hypertable('\"customSchema\".\"Hypertable_1\"', 'time', NULL, 1, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\nSELECT * FROM _timescaledb_catalog.hypertable;\n\nCREATE INDEX ON PUBLIC.\"Hypertable_1\" (time, \"temp_c\");\nCREATE INDEX \"ind_humidity\" ON PUBLIC.\"Hypertable_1\" (time, \"humidity\");\nCREATE INDEX \"ind_sensor_1\" ON PUBLIC.\"Hypertable_1\" (time, \"sensor_1\");\n\nINSERT INTO PUBLIC.\"Hypertable_1\"(time, \"Device_id\", temp_c, humidity, sensor_1, sensor_2, sensor_3, sensor_4)\nVALUES(1257894000000000000, 'dev1', 30, 70, 1, 2, 3, 100);\n\n\nCREATE UNIQUE INDEX \"Unique1\" ON PUBLIC.\"Hypertable_1\" (time, \"Device_id\");\nCREATE UNIQUE INDEX \"Unique1\" ON \"customSchema\".\"Hypertable_1\" (time);\n\nINSERT INTO \"customSchema\".\"Hypertable_1\"(time, \"Device_id\", temp_c, humidity, sensor_1, sensor_2, sensor_3, sensor_4)\nVALUES(1257894000000000000, 'dev1', 30, 70, 1, 2, 3, 100);\nINSERT INTO \"customSchema\".\"Hypertable_1\"(time, \"Device_id\", temp_c, humidity, sensor_1, sensor_2, sensor_3, sensor_4)\nVALUES(1257894000000000001, 'dev1', 30, 70, 1, 2, 3, 100);\n\nSELECT * FROM test.show_indexesp('%.%');\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper_%');\n\n--expect error cases\n\\set ON_ERROR_STOP 0\nINSERT INTO \"customSchema\".\"Hypertable_1\"(time, \"Device_id\", temp_c, humidity, sensor_1, sensor_2, sensor_3, sensor_4)\nVALUES(1257894000000000000, 'dev1', 31, 71, 72, 4, 1, 102);\nCREATE UNIQUE INDEX \"Unique2\" ON PUBLIC.\"Hypertable_1\" (\"Device_id\");\nCREATE UNIQUE INDEX \"Unique2\" ON PUBLIC.\"Hypertable_1\" (time);\nCREATE UNIQUE INDEX \"Unique2\" ON PUBLIC.\"Hypertable_1\" (sensor_1);\nUPDATE ONLY PUBLIC.\"Hypertable_1\" SET time = 0 WHERE TRUE;\nDELETE FROM ONLY PUBLIC.\"Hypertable_1\" WHERE \"Device_id\" = 'dev1';\n\\set ON_ERROR_STOP 1\n\n\nCREATE TABLE my_ht (time BIGINT, val integer);\nSELECT * FROM create_hypertable('my_ht', 'time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nALTER TABLE my_ht ADD COLUMN val2 integer;\nSELECT * FROM test.show_columns('my_ht');\n\n-- Should error when adding again\n\\set ON_ERROR_STOP 0\nALTER TABLE my_ht ADD COLUMN val2 integer;\n\\set ON_ERROR_STOP 1\n\n-- Should create\nALTER TABLE my_ht ADD COLUMN IF NOT EXISTS val3 integer;\nSELECT * FROM test.show_columns('my_ht');\n\n-- Should skip and not error\nALTER TABLE my_ht ADD COLUMN IF NOT EXISTS val3 integer;\nSELECT * FROM test.show_columns('my_ht');\n\n-- Should drop\nALTER TABLE my_ht DROP COLUMN IF EXISTS val3;\nSELECT * FROM test.show_columns('my_ht');\n\n-- Should skip and not error\nALTER TABLE my_ht DROP COLUMN IF EXISTS val3;\nSELECT * FROM test.show_columns('my_ht');\n\n--Test default index creation on create_hypertable().\n--Make sure that we do not duplicate indexes that already exists\n--\n--No existing indexes: both time and space-time indexes created\nBEGIN;\nCREATE TABLE PUBLIC.\"Hypertable_1_with_default_index_enabled\" (\n  \"Time\" BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1\n);\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1_with_default_index_enabled\"', 'Time', 'Device_id', 1, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nSELECT * FROM test.show_indexes('\"Hypertable_1_with_default_index_enabled\"');\nROLLBACK;\n\n--Space index exists: only time index created\nBEGIN;\nCREATE TABLE PUBLIC.\"Hypertable_1_with_default_index_enabled\" (\n  \"Time\" BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1\n);\nCREATE INDEX ON PUBLIC.\"Hypertable_1_with_default_index_enabled\" (\"Device_id\", \"Time\" DESC);\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1_with_default_index_enabled\"', 'Time', 'Device_id', 1, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nSELECT * FROM test.show_indexes('\"Hypertable_1_with_default_index_enabled\"');\nROLLBACK;\n\n--Time index exists, only partition index created\nBEGIN;\nCREATE TABLE PUBLIC.\"Hypertable_1_with_default_index_enabled\" (\n  \"Time\" BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1\n);\nCREATE INDEX ON PUBLIC.\"Hypertable_1_with_default_index_enabled\" (\"Time\" DESC);\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1_with_default_index_enabled\"', 'Time', 'Device_id', 1, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nSELECT * FROM test.show_indexes('\"Hypertable_1_with_default_index_enabled\"');\nROLLBACK;\n\n--No space partitioning, only time index created\nBEGIN;\nCREATE TABLE PUBLIC.\"Hypertable_1_with_default_index_enabled\" (\n  \"Time\" BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1\n);\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1_with_default_index_enabled\"', 'Time', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nSELECT * FROM test.show_indexes('\"Hypertable_1_with_default_index_enabled\"');\nROLLBACK;\n\n--Disable index creation: no default indexes created\nBEGIN;\nCREATE TABLE PUBLIC.\"Hypertable_1_with_default_index_enabled\" (\n  \"Time\" BIGINT NOT NULL,\n  \"Device_id\" TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1\n);\nSELECT * FROM create_hypertable('\"public\".\"Hypertable_1_with_default_index_enabled\"', 'Time', 'Device_id', 1, create_default_indexes=>FALSE, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\nSELECT * FROM test.show_indexes('\"Hypertable_1_with_default_index_enabled\"');\nROLLBACK;\n"
  },
  {
    "path": "test/sql/include/ddl_ops_2.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nALTER TABLE PUBLIC.\"Hypertable_1\" ADD COLUMN temp_f INTEGER NOT NULL DEFAULT 31;\nALTER TABLE PUBLIC.\"Hypertable_1\" DROP COLUMN temp_c;\nALTER TABLE PUBLIC.\"Hypertable_1\" DROP COLUMN sensor_4;\nALTER TABLE PUBLIC.\"Hypertable_1\" ALTER COLUMN humidity SET DEFAULT 100;\nALTER TABLE PUBLIC.\"Hypertable_1\" ALTER COLUMN sensor_1 DROP DEFAULT;\nALTER TABLE PUBLIC.\"Hypertable_1\" ALTER COLUMN sensor_2 SET DEFAULT NULL;\nALTER TABLE PUBLIC.\"Hypertable_1\" ALTER COLUMN sensor_1 SET NOT NULL;\nALTER TABLE PUBLIC.\"Hypertable_1\" ALTER COLUMN sensor_2 DROP NOT NULL;\nALTER TABLE PUBLIC.\"Hypertable_1\" RENAME COLUMN sensor_2 TO sensor_2_renamed;\nALTER TABLE PUBLIC.\"Hypertable_1\" RENAME COLUMN sensor_3 TO sensor_3_renamed;\nDROP INDEX \"ind_sensor_1\";\n\n\nCREATE OR REPLACE FUNCTION empty_trigger_func()\n    RETURNS TRIGGER LANGUAGE PLPGSQL AS\n$BODY$\nBEGIN\nEND\n$BODY$;\n\nCREATE TRIGGER test_trigger BEFORE UPDATE OR DELETE ON PUBLIC.\"Hypertable_1\"\nFOR EACH STATEMENT EXECUTE FUNCTION empty_trigger_func();\n\nALTER TABLE PUBLIC.\"Hypertable_1\" ALTER COLUMN sensor_2_renamed SET DATA TYPE int;\nALTER INDEX \"ind_humidity\" RENAME TO \"ind_humdity2\";\n\n-- Change should be reflected here\nSELECT * FROM test.show_indexesp('%.%');\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%');\n\n--create column with same name as previously renamed one\nALTER TABLE PUBLIC.\"Hypertable_1\" ADD COLUMN sensor_3 BIGINT NOT NULL DEFAULT 131;\n--create column with same name as previously dropped one\nALTER TABLE PUBLIC.\"Hypertable_1\" ADD COLUMN sensor_4 BIGINT NOT NULL DEFAULT 131;\n"
  },
  {
    "path": "test/sql/include/insert_single.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE TABLE PUBLIC.\"one_Partition\" (\n  \"timeCustom\" BIGINT NOT NULL,\n  device_id TEXT NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL,\n  series_bool BOOLEAN NULL\n);\nCREATE INDEX ON PUBLIC.\"one_Partition\" (device_id, \"timeCustom\" DESC NULLS LAST) WHERE device_id IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"one_Partition\" (\"timeCustom\" DESC NULLS LAST, series_0) WHERE series_0 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"one_Partition\" (\"timeCustom\" DESC NULLS LAST, series_1)  WHERE series_1 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"one_Partition\" (\"timeCustom\" DESC NULLS LAST, series_2) WHERE series_2 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"one_Partition\" (\"timeCustom\" DESC NULLS LAST, series_bool) WHERE series_bool IS NOT NULL;\n\n\\c :DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA \"one_Partition\" AUTHORIZATION :ROLE_DEFAULT_PERM_USER;\n\\c :DBNAME :ROLE_DEFAULT_PERM_USER;\n\nSELECT * FROM create_hypertable('\"public\".\"one_Partition\"', 'timeCustom', associated_schema_name=>'one_Partition', chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\n--output command tags\n\\set QUIET off\nBEGIN;\n\\COPY \"one_Partition\" FROM 'data/ds1_dev1_1.tsv' NULL AS '';\nCOMMIT;\n\nINSERT INTO \"one_Partition\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257987600000000000, 'dev1', 1.5, 1),\n(1257987600000000000, 'dev1', 1.5, 2),\n(1257894000000000000, 'dev2', 1.5, 1),\n(1257894002000000000, 'dev1', 2.5, 3);\n\nINSERT INTO \"one_Partition\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257894000000000000, 'dev2', 1.5, 2);\n\\set QUIET on\n"
  },
  {
    "path": "test/sql/include/insert_two_partitions.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE TABLE PUBLIC.\"two_Partitions\" (\n  \"timeCustom\" BIGINT NOT NULL,\n  device_id TEXT NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL,\n  series_bool BOOLEAN NULL\n);\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (device_id, \"timeCustom\" DESC NULLS LAST) WHERE device_id IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_0) WHERE series_0 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_1)  WHERE series_1 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_2) WHERE series_2 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_bool) WHERE series_bool IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, device_id);\n\nSELECT * FROM create_hypertable('\"public\".\"two_Partitions\"'::regclass, 'timeCustom'::name, 'device_id'::name, associated_schema_name=>'_timescaledb_internal'::text, number_partitions => 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n\n\\set QUIET off\nBEGIN;\n\\COPY public.\"two_Partitions\" FROM 'data/ds1_dev1_1.tsv' NULL AS '';\nCOMMIT;\n\nINSERT INTO public.\"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n\n(1257987600000000000, 'dev1', 1.5, 1),\n(1257987600000000000, 'dev1', 1.5, 2),\n(1257894000000000000, 'dev2', 1.5, 1),\n(1257894002000000000, 'dev1', 2.5, 3);\n\nINSERT INTO \"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257894000000000000, 'dev2', 1.5, 2);\n\\set QUIET on\n"
  },
  {
    "path": "test/sql/include/join_load.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- these table definitions have been adjusted from\n-- table defintions of the postgres test suite\n\nCREATE TABLE INT2_TBL(f1 int2, ts timestamptz NOT NULL DEFAULT '2000-01-01');\nSELECT table_name FROM create_hypertable('int2_tbl','ts');\n\nINSERT INTO INT2_TBL(f1) VALUES ('0   ');\nINSERT INTO INT2_TBL(f1) VALUES ('  1234 ');\nINSERT INTO INT2_TBL(f1) VALUES ('    -1234');\n-- largest and smallest values\nINSERT INTO INT2_TBL(f1) VALUES ('32767');\nINSERT INTO INT2_TBL(f1) VALUES ('-32767');\n\n\nCREATE TABLE INT4_TBL(f1 int4, ts timestamptz NOT NULL DEFAULT '2000-01-01');\nSELECT table_name FROM create_hypertable('int4_tbl','ts');\n\nINSERT INTO INT4_TBL(f1) VALUES ('   0  ');\nINSERT INTO INT4_TBL(f1) VALUES ('123456     ');\nINSERT INTO INT4_TBL(f1) VALUES ('    -123456');\n-- largest and smallest values\nINSERT INTO INT4_TBL(f1) VALUES ('2147483647');\nINSERT INTO INT4_TBL(f1) VALUES ('-2147483647');\n\n\nCREATE TABLE INT8_TBL(q1 int8, q2 int8, ts timestamptz NOT NULL DEFAULT '2000-01-01');\nSELECT table_name FROM create_hypertable('int8_tbl','ts');\n\nINSERT INTO INT8_TBL VALUES('  123   ','  456');\nINSERT INTO INT8_TBL VALUES('123   ','4567890123456789');\nINSERT INTO INT8_TBL VALUES('4567890123456789','123');\nINSERT INTO INT8_TBL VALUES(+4567890123456789,'4567890123456789');\nINSERT INTO INT8_TBL VALUES('+4567890123456789','-4567890123456789');\n\n\nCREATE TABLE FLOAT8_TBL(f1 float8, ts timestamptz NOT NULL DEFAULT '2000-01-01');\nSELECT table_name FROM create_hypertable('float8_tbl','ts');\n\nINSERT INTO FLOAT8_TBL(f1) VALUES ('    0.0   ');\nINSERT INTO FLOAT8_TBL(f1) VALUES ('1004.30  ');\nINSERT INTO FLOAT8_TBL(f1) VALUES ('   -34.84');\nINSERT INTO FLOAT8_TBL(f1) VALUES ('1.2345678901234e+200');\nINSERT INTO FLOAT8_TBL(f1) VALUES ('1.2345678901234e-200');\n\nCREATE TABLE TEXT_TBL (f1 text, ts timestamptz NOT NULL DEFAULT '2000-01-01');\nSELECT table_name FROM create_hypertable('text_tbl','ts');\n\nINSERT INTO TEXT_TBL VALUES ('doh!');\nINSERT INTO TEXT_TBL VALUES ('hi de ho neighbor');\n\nCREATE TABLE a (aa TEXT);\nCREATE TABLE b (bb TEXT) INHERITS (a);\nCREATE TABLE c (cc TEXT) INHERITS (a);\nCREATE TABLE d (dd TEXT) INHERITS (b,c,a);\n\nINSERT INTO a(aa) VALUES('aaa');\nINSERT INTO a(aa) VALUES('aaaa');\nINSERT INTO a(aa) VALUES('aaaaa');\nINSERT INTO a(aa) VALUES('aaaaaa');\nINSERT INTO a(aa) VALUES('aaaaaaa');\nINSERT INTO a(aa) VALUES('aaaaaaaa');\n\nINSERT INTO b(aa) VALUES('bbb');\nINSERT INTO b(aa) VALUES('bbbb');\nINSERT INTO b(aa) VALUES('bbbbb');\nINSERT INTO b(aa) VALUES('bbbbbb');\nINSERT INTO b(aa) VALUES('bbbbbbb');\nINSERT INTO b(aa) VALUES('bbbbbbbb');\n\nINSERT INTO c(aa) VALUES('ccc');\nINSERT INTO c(aa) VALUES('cccc');\nINSERT INTO c(aa) VALUES('ccccc');\nINSERT INTO c(aa) VALUES('cccccc');\nINSERT INTO c(aa) VALUES('ccccccc');\nINSERT INTO c(aa) VALUES('cccccccc');\n\nINSERT INTO d(aa) VALUES('ddd');\nINSERT INTO d(aa) VALUES('dddd');\nINSERT INTO d(aa) VALUES('ddddd');\nINSERT INTO d(aa) VALUES('dddddd');\nINSERT INTO d(aa) VALUES('ddddddd');\nINSERT INTO d(aa) VALUES('dddddddd');\n\nCREATE TABLE onek (\n  unique1   int4,\n  unique2   int4,\n  two     int4,\n  four    int4,\n  ten     int4,\n  twenty    int4,\n  hundred   int4,\n  thousand  int4,\n  twothousand int4,\n  fivethous int4,\n  tenthous  int4,\n  odd     int4,\n  even    int4,\n  stringu1  name,\n  stringu2  name,\n  string4   name\n);\n\nSELECT table_name FROM create_hypertable('onek','unique2',chunk_time_interval:=1000);\n\\copy onek FROM 'data/onek.data'\n\nCREATE TABLE tenk1 (\n  unique1   int4,\n  unique2   int4,\n  two     int4,\n  four    int4,\n  ten     int4,\n  twenty    int4,\n  hundred   int4,\n  thousand  int4,\n  twothousand int4,\n  fivethous int4,\n  tenthous  int4,\n  odd     int4,\n  even    int4,\n  stringu1  name,\n  stringu2  name,\n  string4   name\n);\n\nSELECT table_name FROM create_hypertable('tenk1','unique2',chunk_time_interval:=1000);\n\\copy tenk1 FROM 'data/tenk.data'\n\nCREATE TABLE tenk2 (\n  unique1   int4,\n  unique2   int4,\n  two     int4,\n  four    int4,\n  ten     int4,\n  twenty    int4,\n  hundred   int4,\n  thousand  int4,\n  twothousand int4,\n  fivethous int4,\n  tenthous  int4,\n  odd     int4,\n  even    int4,\n  stringu1  name,\n  stringu2  name,\n  string4   name\n);\n\nSELECT table_name FROM create_hypertable('tenk2','unique2',chunk_time_interval:=1000);\nINSERT INTO tenk2 SELECT * FROM tenk1;\n\n"
  },
  {
    "path": "test/sql/include/join_query.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- these queries are based on the postgres JOIN tests\n-- and have been adjusted to run on hypertables\n\n-- canary for results diff\n-- this should be the only output of the results diff\nSELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations')) v(setting);\n\n--\n-- JOIN\n-- Test JOIN clauses\n--\n\nCREATE TABLE J1_TBL (\n  i integer,\n  j integer,\n  t text,\n  ts_j1 timestamptz NOT NULL DEFAULT '2000-01-01'\n);\n\nCREATE TABLE J2_TBL (\n  i integer,\n  k integer,\n  ts_j2 timestamptz NOT NULL DEFAULT '2000-01-02'\n);\n\nSELECT (SELECT table_name FROM create_hypertable(tbl||'_tbl', 'ts_' || tbl)) FROM (VALUES ('j1'),('j2')) v(tbl);\n\nINSERT INTO J1_TBL VALUES (1, 4, 'one');\nINSERT INTO J1_TBL VALUES (2, 3, 'two');\nINSERT INTO J1_TBL VALUES (3, 2, 'three');\nINSERT INTO J1_TBL VALUES (4, 1, 'four');\nINSERT INTO J1_TBL VALUES (5, 0, 'five');\nINSERT INTO J1_TBL VALUES (6, 6, 'six');\nINSERT INTO J1_TBL VALUES (7, 7, 'seven');\nINSERT INTO J1_TBL VALUES (8, 8, 'eight');\nINSERT INTO J1_TBL VALUES (0, NULL, 'zero');\nINSERT INTO J1_TBL VALUES (NULL, NULL, 'null');\nINSERT INTO J1_TBL VALUES (NULL, 0, 'zero');\n\nINSERT INTO J2_TBL VALUES (1, -1);\nINSERT INTO J2_TBL VALUES (2, 2);\nINSERT INTO J2_TBL VALUES (3, -3);\nINSERT INTO J2_TBL VALUES (2, 4);\nINSERT INTO J2_TBL VALUES (5, -5);\nINSERT INTO J2_TBL VALUES (5, -5);\nINSERT INTO J2_TBL VALUES (0, NULL);\nINSERT INTO J2_TBL VALUES (NULL, NULL);\nINSERT INTO J2_TBL VALUES (NULL, 0);\n\n--\n-- CORRELATION NAMES\n-- Make sure that table/column aliases are supported\n-- before diving into more complex join syntax.\n--\n\nSELECT '' AS \"xxx\", *\n  FROM J1_TBL AS tx;\n\nSELECT '' AS \"xxx\", *\n  FROM J1_TBL tx;\n\nSELECT '' AS \"xxx\", *\n  FROM J1_TBL AS t1 (a, b, c);\n\nSELECT '' AS \"xxx\", *\n  FROM J1_TBL t1 (a, b, c);\n\nSELECT '' AS \"xxx\", *\n  FROM J1_TBL t1 (a, b, c), J2_TBL t2 (d, e);\n\nSELECT '' AS \"xxx\", t1.a, t2.e\n  FROM J1_TBL t1 (a, b, c), J2_TBL t2 (d, e)\n  WHERE t1.a = t2.d;\n\n\n--\n-- CROSS JOIN\n-- Qualifications are not allowed on cross joins,\n-- which degenerate into a standard unqualified inner join.\n--\n\nSELECT '' AS \"xxx\", *\n  FROM J1_TBL CROSS JOIN J2_TBL;\n\n-- ambiguous column\n-- SELECT '' AS \"xxx\", i, k, t\n--   FROM J1_TBL CROSS JOIN J2_TBL;\n\n-- resolve previous ambiguity by specifying the table name\nSELECT '' AS \"xxx\", t1.i, k, t\n  FROM J1_TBL t1 CROSS JOIN J2_TBL t2;\n\nSELECT '' AS \"xxx\", ii, tt, kk\n  FROM (J1_TBL CROSS JOIN J2_TBL)\n    AS tx (ii, jj, tt, ii2, kk);\n\nSELECT '' AS \"xxx\", tx.ii, tx.jj, tx.kk\n  FROM (J1_TBL t1 (a, b, c) CROSS JOIN J2_TBL t2 (d, e))\n    AS tx (ii, jj, tt, ii2, kk);\n\nSELECT '' AS \"xxx\", *\n  FROM J1_TBL CROSS JOIN J2_TBL a CROSS JOIN J2_TBL b;\n\n\n--\n--\n-- Inner joins (equi-joins)\n--\n--\n\n--\n-- Inner joins (equi-joins) with USING clause\n-- The USING syntax changes the shape of the resulting table\n-- by including a column in the USING clause only once in the result.\n--\n\n-- Inner equi-join on specified column\nSELECT '' AS \"xxx\", *\n  FROM J1_TBL INNER JOIN J2_TBL USING (i);\n\n-- Same as above, slightly different syntax\nSELECT '' AS \"xxx\", *\n  FROM J1_TBL JOIN J2_TBL USING (i);\n\nSELECT '' AS \"xxx\", *\n  FROM J1_TBL t1 (a, b, c) JOIN J2_TBL t2 (a, d) USING (a)\n  ORDER BY a, d;\n\nSELECT '' AS \"xxx\", *\n  FROM J1_TBL t1 (a, b, c) JOIN J2_TBL t2 (a, b) USING (b)\n  ORDER BY b, t1.a;\n\n\n--\n-- NATURAL JOIN\n-- Inner equi-join on all columns with the same name\n--\n\nSELECT '' AS \"xxx\", *\n  FROM J1_TBL NATURAL JOIN J2_TBL;\n\nSELECT '' AS \"xxx\", *\n  FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (a, d);\n\nSELECT '' AS \"xxx\", *\n  FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (d, a);\n\n-- mismatch number of columns\n-- currently, Postgres will fill in with underlying names\nSELECT '' AS \"xxx\", *\n  FROM J1_TBL t1 (a, b) NATURAL JOIN J2_TBL t2 (a);\n\n\n--\n-- Inner joins (equi-joins)\n--\n\nSELECT '' AS \"xxx\", *\n  FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i = J2_TBL.i);\n\nSELECT '' AS \"xxx\", *\n  FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i = J2_TBL.k);\n\n\n--\n-- Non-equi-joins\n--\n\nSELECT '' AS \"xxx\", *\n  FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i <= J2_TBL.k);\n\n\n--\n-- Outer joins\n-- Note that OUTER is a noise word\n--\n\nSELECT '' AS \"xxx\", *\n  FROM J1_TBL LEFT OUTER JOIN J2_TBL USING (i)\n  ORDER BY i, k, t;\n\nSELECT '' AS \"xxx\", *\n  FROM J1_TBL LEFT JOIN J2_TBL USING (i)\n  ORDER BY i, k, t;\n\nSELECT '' AS \"xxx\", *\n  FROM J1_TBL RIGHT OUTER JOIN J2_TBL USING (i);\n\nSELECT '' AS \"xxx\", *\n  FROM J1_TBL RIGHT JOIN J2_TBL USING (i);\n\nSELECT '' AS \"xxx\", *\n  FROM J1_TBL FULL OUTER JOIN J2_TBL USING (i)\n  ORDER BY i, k, t;\n\nSELECT '' AS \"xxx\", *\n  FROM J1_TBL FULL JOIN J2_TBL USING (i)\n  ORDER BY i, k, t;\n\nSELECT '' AS \"xxx\", *\n  FROM J1_TBL LEFT JOIN J2_TBL USING (i) WHERE (k = 1);\n\nSELECT '' AS \"xxx\", *\n  FROM J1_TBL LEFT JOIN J2_TBL USING (i) WHERE (i = 1);\n\n--\n-- semijoin selectivity for <>\n--\n:PREFIX\nselect * from int4_tbl i4, tenk1 a\nwhere exists(select * from tenk1 b\n             where a.twothousand = b.twothousand and a.fivethous <> b.fivethous)\n      and i4.f1 = a.tenthous;\n\n\n--\n-- More complicated constructs\n--\n\n--\n-- Multiway full join\n--\n\nCREATE TABLE t1 (name TEXT, n INTEGER, ts_t1 timestamptz NOT NULL DEFAULT '2000-01-01');\nCREATE TABLE t2 (name TEXT, n INTEGER, ts_t2 timestamptz NOT NULL DEFAULT '2000-01-02');\nCREATE TABLE t3 (name TEXT, n INTEGER, ts_t3 timestamptz NOT NULL DEFAULT '2000-01-03');\n\nSELECT (SELECT table_name FROM create_hypertable(tbl, 'ts_' || tbl)) FROM (VALUES ('t1'),('t2'),('t3')) v(tbl);\n\nINSERT INTO t1 VALUES ( 'bb', 11 );\nINSERT INTO t2 VALUES ( 'bb', 12 );\nINSERT INTO t2 VALUES ( 'cc', 22 );\nINSERT INTO t2 VALUES ( 'ee', 42 );\nINSERT INTO t3 VALUES ( 'bb', 13 );\nINSERT INTO t3 VALUES ( 'cc', 23 );\nINSERT INTO t3 VALUES ( 'dd', 33 );\n\nSELECT * FROM t1 FULL JOIN t2 USING (name) FULL JOIN t3 USING (name);\n\n--\n-- Test interactions of join syntax and subqueries\n--\n\n-- Basic cases (we expect planner to pull up the subquery here)\nSELECT * FROM\n(SELECT * FROM t2) as s2\nINNER JOIN\n(SELECT * FROM t3) s3\nUSING (name);\n\nSELECT * FROM\n(SELECT * FROM t2) as s2\nLEFT JOIN\n(SELECT * FROM t3) s3\nUSING (name);\n\nSELECT * FROM\n(SELECT * FROM t2) as s2\nFULL JOIN\n(SELECT * FROM t3) s3\nUSING (name);\n\n-- Cases with non-nullable expressions in subquery results;\n-- make sure these go to null as expected\nSELECT * FROM\n(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2\nNATURAL INNER JOIN\n(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3;\n\nSELECT * FROM\n(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2\nNATURAL LEFT JOIN\n(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3;\n\nSELECT * FROM\n(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2\nNATURAL FULL JOIN\n(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3;\n\nSELECT * FROM\n(SELECT name, n as s1_n, 1 as s1_1 FROM t1) as s1\nNATURAL INNER JOIN\n(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2\nNATURAL INNER JOIN\n(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3;\n\nSELECT * FROM\n(SELECT name, n as s1_n, 1 as s1_1 FROM t1) as s1\nNATURAL FULL JOIN\n(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2\nNATURAL FULL JOIN\n(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3;\n\nSELECT * FROM\n(SELECT name, n as s1_n FROM t1) as s1\nNATURAL FULL JOIN\n  (SELECT * FROM\n    (SELECT name, n as s2_n FROM t2) as s2\n    NATURAL FULL JOIN\n    (SELECT name, n as s3_n FROM t3) as s3\n  ) ss2;\n\nSELECT * FROM\n(SELECT name, n as s1_n FROM t1) as s1\nNATURAL FULL JOIN\n  (SELECT * FROM\n    (SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2\n    NATURAL FULL JOIN\n    (SELECT name, n as s3_n FROM t3) as s3\n  ) ss2;\n\n\n-- Test for propagation of nullability constraints into sub-joins\n\ncreate table x (x1 int, x2 int, ts_x timestamptz NOT NULL DEFAULT '2000-01-01');\nSELECT table_name FROM create_hypertable('x','ts_x');\ninsert into x values (1,11);\ninsert into x values (2,22);\ninsert into x values (3,null);\ninsert into x values (4,44);\ninsert into x values (5,null);\n\ncreate table y (y1 int, y2 int, ts_y timestamptz NOT NULL DEFAULT '2000-01-02');\nSELECT table_name FROM create_hypertable('y','ts_y');\ninsert into y values (1,111);\ninsert into y values (2,222);\ninsert into y values (3,333);\ninsert into y values (4,null);\n\nselect * from x;\nselect * from y;\n\nselect * from x left join y on (x1 = y1 and x2 is not null);\nselect * from x left join y on (x1 = y1 and y2 is not null);\n\nselect * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2)\non (x1 = xx1);\nselect * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2)\non (x1 = xx1 and x2 is not null);\nselect * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2)\non (x1 = xx1 and y2 is not null);\nselect * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2)\non (x1 = xx1 and xx2 is not null);\n-- these should NOT give the same answers as above\nselect * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2)\non (x1 = xx1) where (x2 is not null);\nselect * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2)\non (x1 = xx1) where (y2 is not null);\nselect * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2)\non (x1 = xx1) where (xx2 is not null);\n\n--\n-- regression test: check for bug with propagation of implied equality\n-- to outside an IN\n--\nselect count(*) from tenk1 a where unique1 in\n  (select unique1 from tenk1 b join tenk1 c using (unique1)\n   where b.unique2 = 42);\n\n--\n-- regression test: check for failure to generate a plan with multiple\n-- degenerate IN clauses\n--\nselect count(*) from tenk1 x where\n  x.unique1 in (select a.f1 from int4_tbl a,float8_tbl b where a.f1=b.f1) and\n  x.unique1 = 0 and\n  x.unique1 in (select aa.f1 from int4_tbl aa,float8_tbl bb where aa.f1=bb.f1);\n\n-- try that with GEQO too\nbegin;\nset geqo = on;\nset geqo_threshold = 2;\nselect count(*) from tenk1 x where\n  x.unique1 in (select a.f1 from int4_tbl a,float8_tbl b where a.f1=b.f1) and\n  x.unique1 = 0 and\n  x.unique1 in (select aa.f1 from int4_tbl aa,float8_tbl bb where aa.f1=bb.f1);\nrollback;\n\n--\n-- regression test: be sure we cope with proven-dummy append rels\n--\n\n:PREFIX\nselect aa, bb, unique1, unique1\n  from tenk1 right join b on aa::int = unique1\n  where bb < bb and bb is null;\n\n--\n-- regression test: check handling of empty-FROM subquery underneath outer join\n--\n\n:PREFIX\nselect * from int8_tbl i1 left join (int8_tbl i2 join\n  (select 123 as x) ss on i2.q1 = x) on i1.q2 = i2.q2\norder by 1, 2;\n\n--\n-- regression test: check a case where join_clause_is_movable_into() gives\n-- an imprecise result, causing an assertion failure\n--\nselect count(*)\nfrom\n  (select t3.tenthous as x1, coalesce(t1.stringu1, t2.stringu1) as x2\n   from tenk1 t1\n   left join tenk1 t2 on t1.unique1 = t2.unique1\n   join tenk1 t3 on t1.unique2 = t3.unique2) ss,\n  tenk1 t4,\n  tenk1 t5\nwhere t4.thousand = t5.unique1 and ss.x1 = t4.tenthous and ss.x2 = t5.stringu1;\n\n--\n-- regression test: check a case where we formerly missed including an EC\n-- enforcement clause because it was expected to be handled at scan level\n--\n\n:PREFIX\nselect a.f1, b.f1, t.thousand, t.tenthous from\n  tenk1 t,\n  (select sum(f1)+1 as f1 from int4_tbl i4a) a,\n  (select sum(f1) as f1 from int4_tbl i4b) b\nwhere b.f1 = t.thousand and a.f1 = b.f1 and (a.f1+b.f1+999) = t.tenthous;\n\n--\n-- check a case where we formerly got confused by conflicting sort orders\n-- in redundant merge join path keys\n--\n\n:PREFIX\nselect * from\n  j1_tbl full join\n  (select * from j2_tbl order by j2_tbl.i desc, j2_tbl.k asc) j2_tbl\n  on j1_tbl.i = j2_tbl.i and j1_tbl.i = j2_tbl.k;\n\n--\n-- a different check for handling of redundant sort keys in merge joins\n--\n\n:PREFIX\nselect count(*) from\n  (select * from tenk1 x order by x.thousand, x.twothousand, x.fivethous) x\n  left join\n  (select * from tenk1 y order by y.unique2) y\n  on x.thousand = y.unique2 and x.twothousand = y.hundred and x.fivethous = y.unique2;\n\n\n--\n-- Clean up\n--\n\nDROP TABLE t1;\nDROP TABLE t2;\nDROP TABLE t3;\n\nDROP TABLE J1_TBL;\nDROP TABLE J2_TBL;\n\nDROP TABLE x;\nDROP TABLE y;\n\n-- Both DELETE and UPDATE allow the specification of additional tables\n-- to \"join\" against to determine which rows should be modified.\n\nCREATE TABLE t1 (a int, b int, ts_t1 timestamptz NOT NULL DEFAULT '2000-01-01');\nCREATE TABLE t2 (a int, b int, ts_t2 timestamptz NOT NULL DEFAULT '2000-01-02');\nCREATE TABLE t3 (x int, y int, ts_t3 timestamptz NOT NULL DEFAULT '2000-01-03');\n\nSELECT (SELECT table_name FROM create_hypertable(tbl, 'ts_' || tbl)) FROM (VALUES ('t1'),('t2'),('t3')) v(tbl);\n\nINSERT INTO t1 VALUES (5, 10);\nINSERT INTO t1 VALUES (15, 20);\nINSERT INTO t1 VALUES (100, 100);\nINSERT INTO t1 VALUES (200, 1000);\nINSERT INTO t2 VALUES (200, 2000);\nINSERT INTO t3 VALUES (5, 20);\nINSERT INTO t3 VALUES (6, 7);\nINSERT INTO t3 VALUES (7, 8);\nINSERT INTO t3 VALUES (500, 100);\n\nDELETE FROM t3 USING t1 table1 WHERE t3.x = table1.a;\nSELECT * FROM t3;\nDELETE FROM t3 USING t1 JOIN t2 USING (a) WHERE t3.x > t1.a;\nSELECT * FROM t3;\nDELETE FROM t3 USING t3 t3_other WHERE t3.x = t3_other.x AND t3.y = t3_other.y;\nSELECT * FROM t3;\n\n-- Test matching of column name with wrong alias\n\n-- select t1.x from t1 join t3 on (t1.a = t3.x);\n\nDROP TABLE t1;\nDROP TABLE t2;\nDROP TABLE t3;\n\n--\n-- regression test for 8.1 merge right join bug\n--\n\nCREATE TABLE tt1 ( tt1_id int4, joincol int4, ts_tt1 timestamptz NOT NULL DEFAULT '2000-01-01' );\nSELECT table_name FROM create_hypertable('tt1','ts_tt1');\nINSERT INTO tt1 VALUES (1, 11);\nINSERT INTO tt1 VALUES (2, NULL);\n\nCREATE TABLE tt2 ( tt2_id int4, joincol int4, ts_tt2 timestamptz NOT NULL DEFAULT '2000-01-02' );\nSELECT table_name FROM create_hypertable('tt2','ts_tt2');\nINSERT INTO tt2 VALUES (21, 11);\nINSERT INTO tt2 VALUES (22, 11);\n\nset enable_hashjoin to off;\nset enable_nestloop to off;\n\n-- these should give the same results\n\nselect tt1.*, tt2.* from tt1 left join tt2 on tt1.joincol = tt2.joincol;\n\nselect tt1.*, tt2.* from tt2 right join tt1 on tt1.joincol = tt2.joincol;\n\nreset enable_hashjoin;\nreset enable_nestloop;\n\nDROP TABLE tt1;\nDROP TABLE tt2;\n\n--\n-- regression test for bug #13908 (hash join with skew tuples & nbatch increase)\n--\n\nset work_mem to '64kB';\nset enable_mergejoin to off;\n\n:PREFIX\nselect count(*) from tenk1 a, tenk1 b\n  where a.hundred = b.thousand and (b.fivethous % 10) < 10;\n\nreset work_mem;\nreset enable_mergejoin;\n\n--\n-- regression test for 8.2 bug with improper re-ordering of left joins\n--\n\ncreate table tt3(f1 int, f2 text);\nSELECT table_name FROM create_hypertable('tt3','f1',chunk_time_interval:=2000);\ninsert into tt3 select x, repeat('xyzzy', 100) from generate_series(1,10000) x;\ncreate index tt3i on tt3(f1);\nanalyze tt3;\n\ncreate table tt4(f1 int);\nSELECT table_name FROM create_hypertable('tt4','f1',chunk_time_interval:=2000);\ninsert into tt4 values (0),(1),(9999);\nanalyze tt4;\n\nSELECT a.f1\nFROM tt4 a\nLEFT JOIN (\n        SELECT b.f1\n        FROM tt3 b LEFT JOIN tt3 c ON (b.f1 = c.f1)\n        WHERE c.f1 IS NULL\n) AS d ON (a.f1 = d.f1)\nWHERE d.f1 IS NULL;\n\nDROP TABLE tt3;\nDROP TABLE tt4;\n\n--\n-- regression test for proper handling of outer joins within antijoins\n--\n\ncreate table tt4x(c1 int, c2 int, c3 int);\nSELECT table_name FROM create_hypertable('tt4x','c1',chunk_time_interval:=2000);\n\n:PREFIX\nselect * from tt4x t1\nwhere not exists (\n  select 1 from tt4x t2\n    left join tt4x t3 on t2.c3 = t3.c1\n    left join ( select t5.c1 as c1\n                from tt4x t4 left join tt4x t5 on t4.c2 = t5.c1\n              ) a1 on t3.c2 = a1.c1\n  where t1.c1 = t2.c2\n);\n\nDROP TABLE tt4x;\n\n--\n-- regression test for problems of the sort depicted in bug #3494\n--\n\ncreate table tt5(f1 int, f2 int);\ncreate table tt6(f1 int, f2 int);\nSELECT table_name FROM create_hypertable('tt5','f1',chunk_time_interval:=2000);\nSELECT table_name FROM create_hypertable('tt6','f1',chunk_time_interval:=2000);\n\ninsert into tt5 values(1, 10);\ninsert into tt5 values(1, 11);\n\ninsert into tt6 values(1, 9);\ninsert into tt6 values(1, 2);\ninsert into tt6 values(2, 9);\n\nselect * from tt5,tt6 where tt5.f1 = tt6.f1 and tt5.f1 = tt5.f2 - tt6.f2;\n\nDROP TABLE tt5;\nDROP TABLE tt6;\n\n--\n-- regression test for problems of the sort depicted in bug #3588\n--\n\ncreate table xx (pkxx int);\ncreate table yy (pkyy int, pkxx int);\nselect table_name FROM create_hypertable('xx','pkxx',chunk_time_interval:=2000);\nselect table_name FROM create_hypertable('yy','pkyy',chunk_time_interval:=2000);\n\ninsert into xx values (1);\ninsert into xx values (2);\ninsert into xx values (3);\n\ninsert into yy values (101, 1);\ninsert into yy values (201, 2);\ninsert into yy values (301, NULL);\n\nselect yy.pkyy as yy_pkyy, yy.pkxx as yy_pkxx, yya.pkyy as yya_pkyy,\n       xxa.pkxx as xxa_pkxx, xxb.pkxx as xxb_pkxx\nfrom yy\n     left join (SELECT * FROM yy where pkyy = 101) as yya ON yy.pkyy = yya.pkyy\n     left join xx xxa on yya.pkxx = xxa.pkxx\n     left join xx xxb on coalesce (xxa.pkxx, 1) = xxb.pkxx;\n\nDROP TABLE xx;\nDROP TABLE yy;\n\n--\n-- regression test for improper pushing of constants across outer-join clauses\n-- (as seen in early 8.2.x releases)\n--\n\ncreate table zt1 (f1 int primary key);\ncreate table zt2 (f2 int primary key);\ncreate table zt3 (f3 int primary key);\nselect table_name FROM create_hypertable('zt1','f1',chunk_time_interval:=2000);\nselect table_name FROM create_hypertable('zt2','f2',chunk_time_interval:=2000);\nselect table_name FROM create_hypertable('zt3','f3',chunk_time_interval:=2000);\n\ninsert into zt1 values(53);\ninsert into zt2 values(53);\n\nselect * from\n  zt2 left join zt3 on (f2 = f3)\n      left join zt1 on (f3 = f1)\nwhere f2 = 53;\n\ncreate temp view zv1 as select *,'dummy'::text AS junk from zt1;\n\nselect * from\n  zt2 left join zt3 on (f2 = f3)\n      left join zv1 on (f3 = f1)\nwhere f2 = 53;\n\ndrop table zt1 cascade;\ndrop table zt2;\ndrop table zt3;\n\n--\n-- regression test for improper extraction of OR indexqual conditions\n-- (as seen in early 8.3.x releases)\n--\n\nselect a.unique2, a.ten, b.tenthous, b.unique2, b.hundred\nfrom tenk1 a left join tenk1 b on a.unique2 = b.tenthous\nwhere a.unique1 = 42 and\n      ((b.unique2 is null and a.ten = 2) or b.hundred = 3);\n\n--\n-- test proper positioning of one-time quals in EXISTS (8.4devel bug)\n--\nprepare foo(bool) as\n  select count(*) from tenk1 a left join tenk1 b\n    on (a.unique2 = b.unique1 and exists\n        (select 1 from tenk1 c where c.thousand = b.unique2 and $1));\nexecute foo(true);\nexecute foo(false);\n\ndeallocate foo;\n\n--\n-- test for sane behavior with noncanonical merge clauses, per bug #4926\n--\n\nbegin;\n\nset enable_mergejoin = 1;\nset enable_hashjoin = 0;\nset enable_nestloop = 0;\n\ncreate temp table a (i integer);\ncreate temp table b (x integer, y integer);\n\nselect * from a left join b on i = x and i = y and x = i;\n\nrollback;\n\n--\n-- test handling of merge clauses using record_ops\n--\nbegin;\n\ncreate type mycomptype as (id int, v bigint);\n\ncreate temp table tidv (idv mycomptype);\ncreate index on tidv (idv);\n\n:PREFIX\nselect a.idv, b.idv from tidv a, tidv b where a.idv = b.idv;\n\nset enable_mergejoin = 0;\n\n:PREFIX\nselect a.idv, b.idv from tidv a, tidv b where a.idv = b.idv;\n\nrollback;\n\n--\n-- test NULL behavior of whole-row Vars, per bug #5025\n--\nselect t1.q2, count(t2.*)\nfrom int8_tbl t1 left join int8_tbl t2 on (t1.q2 = t2.q1)\ngroup by t1.q2 order by 1;\n\nselect t1.q2, count(t2.*)\nfrom int8_tbl t1 left join (select * from int8_tbl) t2 on (t1.q2 = t2.q1)\ngroup by t1.q2 order by 1;\n\nselect t1.q2, count(t2.*)\nfrom int8_tbl t1 left join (select * from int8_tbl offset 0) t2 on (t1.q2 = t2.q1)\ngroup by t1.q2 order by 1;\n\nselect t1.q2, count(t2.*)\nfrom int8_tbl t1 left join\n  (select q1, case when q2=1 then 1 else q2 end as q2 from int8_tbl) t2\n  on (t1.q2 = t2.q1)\ngroup by t1.q2 order by 1;\n\n--\n-- test incorrect failure to NULL pulled-up subexpressions\n--\nbegin;\n\ncreate temp table a (\n     code char not null,\n     constraint a_pk primary key (code)\n);\ncreate temp table b (\n     a char not null,\n     num integer not null,\n     constraint b_pk primary key (a, num)\n);\ncreate temp table c (\n     name char not null,\n     a char,\n     constraint c_pk primary key (name)\n);\n\ninsert into a (code) values ('p');\ninsert into a (code) values ('q');\ninsert into b (a, num) values ('p', 1);\ninsert into b (a, num) values ('p', 2);\ninsert into c (name, a) values ('A', 'p');\ninsert into c (name, a) values ('B', 'q');\ninsert into c (name, a) values ('C', null);\n\nselect c.name, ss.code, ss.b_cnt, ss.const\nfrom c left join\n  (select a.code, coalesce(b_grp.cnt, 0) as b_cnt, -1 as const\n   from a left join\n     (select count(1) as cnt, b.a from b group by b.a) as b_grp\n     on a.code = b_grp.a\n  ) as ss\n  on (c.a = ss.code)\norder by c.name;\n\nrollback;\n\n--\n-- test incorrect handling of placeholders that only appear in targetlists,\n-- per bug #6154\n--\nSELECT * FROM\n( SELECT 1 as key1 ) sub1\nLEFT JOIN\n( SELECT sub3.key3, sub4.value2, COALESCE(sub4.value2, 66) as value3 FROM\n    ( SELECT 1 as key3 ) sub3\n    LEFT JOIN\n    ( SELECT sub5.key5, COALESCE(sub6.value1, 1) as value2 FROM\n        ( SELECT 1 as key5 ) sub5\n        LEFT JOIN\n        ( SELECT 2 as key6, 42 as value1 ) sub6\n        ON sub5.key5 = sub6.key6\n    ) sub4\n    ON sub4.key5 = sub3.key3\n) sub2\nON sub1.key1 = sub2.key3;\n\n-- test the path using join aliases, too\nSELECT * FROM\n( SELECT 1 as key1 ) sub1\nLEFT JOIN\n( SELECT sub3.key3, value2, COALESCE(value2, 66) as value3 FROM\n    ( SELECT 1 as key3 ) sub3\n    LEFT JOIN\n    ( SELECT sub5.key5, COALESCE(sub6.value1, 1) as value2 FROM\n        ( SELECT 1 as key5 ) sub5\n        LEFT JOIN\n        ( SELECT 2 as key6, 42 as value1 ) sub6\n        ON sub5.key5 = sub6.key6\n    ) sub4\n    ON sub4.key5 = sub3.key3\n) sub2\nON sub1.key1 = sub2.key3;\n\n--\n-- test case where a PlaceHolderVar is used as a nestloop parameter\n--\n\n:PREFIX\nSELECT qq, unique1\n  FROM\n  ( SELECT COALESCE(q1, 0) AS qq FROM int8_tbl a ) AS ss1\n  FULL OUTER JOIN\n  ( SELECT COALESCE(q2, -1) AS qq FROM int8_tbl b ) AS ss2\n  USING (qq)\n  INNER JOIN tenk1 c ON qq = unique2;\n\n--\n-- nested nestloops can require nested PlaceHolderVars\n--\n\ncreate table nt1 (\n  id int primary key,\n  a1 boolean,\n  a2 boolean\n);\ncreate table nt2 (\n  id int primary key,\n  nt1_id int,\n  b1 boolean,\n  b2 boolean\n--  foreign key (nt1_id) references nt1(id)\n);\ncreate table nt3 (\n  id int primary key,\n  nt2_id int,\n  c1 boolean\n--  foreign key (nt2_id) references nt2(id)\n);\nselect (select table_name from create_hypertable(tbl,'id',chunk_time_interval:=1000)) from (VALUES ('nt1'),('nt2'),('nt3')) v(tbl);\n\ninsert into nt1 values (1,true,true);\ninsert into nt1 values (2,true,false);\ninsert into nt1 values (3,false,false);\ninsert into nt2 values (1,1,true,true);\ninsert into nt2 values (2,2,true,false);\ninsert into nt2 values (3,3,false,false);\ninsert into nt3 values (1,1,true);\ninsert into nt3 values (2,2,false);\ninsert into nt3 values (3,3,true);\n\n:PREFIX\nselect nt3.id\nfrom nt3 as nt3\n  left join\n    (select nt2.*, (nt2.b1 and ss1.a3) AS b3\n     from nt2 as nt2\n       left join\n         (select nt1.*, (nt1.id is not null) as a3 from nt1) as ss1\n         on ss1.id = nt2.nt1_id\n    ) as ss2\n    on ss2.id = nt3.nt2_id\nwhere nt3.id = 1 and ss2.b3;\n\ndrop table nt1;\ndrop table nt2;\ndrop table nt3;\n\n--\n-- test case where a PlaceHolderVar is propagated into a subquery\n--\n\n:PREFIX\nselect * from\n  int8_tbl t1 left join\n  (select q1 as x, 42 as y from int8_tbl t2) ss\n  on t1.q2 = ss.x\nwhere\n  1 = (select 1 from int8_tbl t3 where ss.y is not null limit 1)\norder by 1,2;\n\n--\n-- test the corner cases FULL JOIN ON TRUE and FULL JOIN ON FALSE\n--\nselect * from int4_tbl a full join int4_tbl b on true;\nselect * from int4_tbl a full join int4_tbl b on false;\n\n--\n-- test for ability to use a cartesian join when necessary\n--\n\n:PREFIX\nselect * from\n  tenk1 join int4_tbl on f1 = twothousand,\n  int4(sin(1)) q1,\n  int4(sin(0)) q2\nwhere q1 = thousand or q2 = thousand;\n\n:PREFIX\nselect * from\n  tenk1 join int4_tbl on f1 = twothousand,\n  int4(sin(1)) q1,\n  int4(sin(0)) q2\nwhere thousand = (q1 + q2);\n\n--\n-- test ability to generate a suitable plan for a star-schema query\n--\n\n:PREFIX\nselect * from\n  tenk1, int8_tbl a, int8_tbl b\nwhere thousand = a.q1 and tenthous = b.q1 and a.q2 = 1 and b.q2 = 2;\n\n--\n-- test a corner case in which we shouldn't apply the star-schema optimization\n--\n\n:PREFIX\nselect t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from\n  tenk1 t1\n  inner join int4_tbl i1\n    left join (select v1.x2, v2.y1, 11 AS d1\n               from (values(1,0)) v1(x1,x2)\n               left join (values(3,1)) v2(y1,y2)\n               on v1.x1 = v2.y2) subq1\n    on (i1.f1 = subq1.x2)\n  on (t1.unique2 = subq1.d1)\n  left join tenk1 t2\n  on (subq1.y1 = t2.unique1)\nwhere t1.unique2 < 42 and t1.stringu1 > t2.stringu2;\n\n-- variant that isn't quite a star-schema case\n\n:PREFIX\nselect ss1.d1 from\n  tenk1 as t1\n  inner join tenk1 as t2\n  on t1.tenthous = t2.ten\n  inner join\n    int8_tbl as i8\n    left join int4_tbl as i4\n      inner join (select 64::information_schema.cardinal_number as d1\n                  from tenk1 t3,\n                       lateral (select abs(t3.unique1) + random()) ss0(x)\n                  where t3.fivethous < 0) as ss1\n      on i4.f1 = ss1.d1\n    on i8.q1 = i4.f1\n  on t1.tenthous = ss1.d1\nwhere t1.unique1 < i4.f1;\n\n--\n-- test extraction of restriction OR clauses from join OR clause\n-- (we used to only do this for indexable clauses)\n--\n\n:PREFIX\nselect * from tenk1 a join tenk1 b on\n  (a.unique1 = 1 and b.unique1 = 2) or (a.unique2 = 3 and b.hundred = 4) ORDER BY a.unique2,b.unique2;\n:PREFIX\nselect * from tenk1 a join tenk1 b on\n  (a.unique1 = 1 and b.unique1 = 2) or (a.unique2 = 3 and b.ten = 4) ORDER BY a.unique2,b.unique2;\n:PREFIX\nselect * from tenk1 a join tenk1 b on\n  (a.unique1 = 1 and b.unique1 = 2) or\n  ((a.unique2 = 3 or a.unique2 = 7) and b.hundred = 4) ORDER BY a.unique2,b.unique2;\n\n--\n-- test placement of movable quals in a parameterized join tree\n--\n\n:PREFIX\nselect * from tenk1 t1 left join\n  (tenk1 t2 join tenk1 t3 on t2.thousand = t3.unique2)\n  on t1.hundred = t2.hundred and t1.ten = t3.ten\nwhere t1.unique1 = 1 ORDER BY t1,t2,t3;\n\n:PREFIX\nselect * from tenk1 t1 left join\n  (tenk1 t2 join tenk1 t3 on t2.thousand = t3.unique2)\n  on t1.hundred = t2.hundred and t1.ten + t2.ten = t3.ten\nwhere t1.unique1 = 1;\n\n:PREFIX\nselect count(*) from\n  tenk1 a join tenk1 b on a.unique1 = b.unique2\n  left join tenk1 c on a.unique2 = b.unique1 and c.thousand = a.thousand\n  join int4_tbl on b.thousand = f1;\n\n:PREFIX\nselect b.unique1 from\n  tenk1 a join tenk1 b on a.unique1 = b.unique2\n  left join tenk1 c on b.unique1 = 42 and c.thousand = a.thousand\n  join int4_tbl i1 on b.thousand = f1\n  right join int4_tbl i2 on i2.f1 = b.tenthous\n  order by 1;\n\n:PREFIX\nselect * from\n(\n  select unique1, q1, coalesce(unique1, -1) + q1 as fault\n  from int8_tbl left join tenk1 on (q2 = unique2)\n) ss\nwhere fault = 122\norder by fault;\n\n:PREFIX\nselect * from\n(values (1, array[10,20]), (2, array[20,30])) as v1(v1x,v1ys)\nleft join (values (1, 10), (2, 20)) as v2(v2x,v2y) on v2x = v1x\nleft join unnest(v1ys) as u1(u1y) on u1y = v2y;\n\n--\n-- test handling of potential equivalence clauses above outer joins\n--\n\n:PREFIX\nselect q1, unique2, thousand, hundred\n  from int8_tbl a left join tenk1 b on q1 = unique2\n  where coalesce(thousand,123) = q1 and q1 = coalesce(hundred,123);\n\n:PREFIX\nselect f1, unique2, case when unique2 is null then f1 else 0 end\n  from int4_tbl a left join tenk1 b on f1 = unique2\n  where (case when unique2 is null then f1 else 0 end) = 0;\n\n--\n-- another case with equivalence clauses above outer joins (bug #8591)\n--\n\n:PREFIX\nselect a.unique1, b.unique1, c.unique1, coalesce(b.twothousand, a.twothousand)\n  from tenk1 a left join tenk1 b on b.thousand = a.unique1                        left join tenk1 c on c.unique2 = coalesce(b.twothousand, a.twothousand)\n  where a.unique2 < 10 and coalesce(b.twothousand, a.twothousand) = 44;\n\n--\n-- check handling of join aliases when flattening multiple levels of subquery\n--\n\n:PREFIX\nselect foo1.join_key as foo1_id, foo3.join_key AS foo3_id, bug_field from\n  (values (0),(1)) foo1(join_key)\nleft join\n  (select join_key, bug_field from\n    (select ss1.join_key, ss1.bug_field from\n      (select f1 as join_key, 666 as bug_field from int4_tbl i1) ss1\n    ) foo2\n   left join\n    (select unique2 as join_key from tenk1 i2) ss2\n   using (join_key)\n  ) foo3\nusing (join_key);\n\n--\n-- test successful handling of nested outer joins with degenerate join quals\n--\n\n:PREFIX\nselect t1.* from\n  text_tbl t1\n  left join (select *, '***'::text as d1 from int8_tbl i8b1) b1\n    left join int8_tbl i8\n      left join (select *, null::int as d2 from int8_tbl i8b2) b2\n      on (i8.q1 = b2.q1)\n    on (b2.d2 = b1.q2)\n  on (t1.f1 = b1.d1)\n  left join int4_tbl i4\n  on (i8.q2 = i4.f1);\n\n:PREFIX\nselect t1.* from\n  text_tbl t1\n  left join (select *, '***'::text as d1 from int8_tbl i8b1) b1\n    left join int8_tbl i8\n      left join (select *, null::int as d2 from int8_tbl i8b2, int4_tbl i4b2) b2\n      on (i8.q1 = b2.q1)\n    on (b2.d2 = b1.q2)\n  on (t1.f1 = b1.d1)\n  left join int4_tbl i4\n  on (i8.q2 = i4.f1);\n\n:PREFIX\nselect t1.* from\n  text_tbl t1\n  left join (select *, '***'::text as d1 from int8_tbl i8b1) b1\n    left join int8_tbl i8\n      left join (select *, null::int as d2 from int8_tbl i8b2, int4_tbl i4b2\n                 where q1 = f1) b2\n      on (i8.q1 = b2.q1)\n    on (b2.d2 = b1.q2)\n  on (t1.f1 = b1.d1)\n  left join int4_tbl i4\n  on (i8.q2 = i4.f1);\n\n:PREFIX\nselect * from\n  text_tbl t1\n  inner join int8_tbl i8\n  on i8.q2 = 456\n  right join text_tbl t2\n  on t1.f1 = 'doh!'\n  left join int4_tbl i4\n  on i8.q1 = i4.f1;\n\n--\n-- test for appropriate join order in the presence of lateral references\n--\n\n:PREFIX\nselect * from\n  text_tbl t1\n  left join int8_tbl i8\n  on i8.q2 = 123,\n  lateral (select i8.q1, t2.f1 from text_tbl t2 limit 1) as ss\nwhere t1.f1 = ss.f1;\n\n:PREFIX\nselect * from\n  text_tbl t1\n  left join int8_tbl i8\n  on i8.q2 = 123,\n  lateral (select i8.q1, t2.f1 from text_tbl t2 limit 1) as ss1,\n  lateral (select ss1.* from text_tbl t3 limit 1) as ss2\nwhere t1.f1 = ss2.f1;\n\n:PREFIX\nselect 1 from\n  text_tbl as tt1\n  inner join text_tbl as tt2 on (tt1.f1 = 'foo')\n  left join text_tbl as tt3 on (tt3.f1 = 'foo')\n  left join text_tbl as tt4 on (tt3.f1 = tt4.f1),\n  lateral (select tt4.f1 as c0 from text_tbl as tt5 limit 1) as ss1\nwhere tt1.f1 = ss1.c0;\n\n--\n-- check a case in which a PlaceHolderVar forces join order\n--\n\n:PREFIX\nselect ss2.* from\n  int4_tbl i41\n  left join int8_tbl i8\n    join (select i42.f1 as c1, i43.f1 as c2, 42 as c3\n          from int4_tbl i42, int4_tbl i43) ss1\n    on i8.q1 = ss1.c2\n  on i41.f1 = ss1.c1,\n  lateral (select i41.*, i8.*, ss1.* from text_tbl limit 1) ss2\nwhere ss1.c2 = 0;\n\n--\n-- test successful handling of full join underneath left join (bug #14105)\n--\n\n:PREFIX\nselect * from\n  (select 1 as id) as xx\n  left join\n    (tenk1 as a1 full join (select 1 as id) as yy on (a1.unique1 = yy.id))\n  on (xx.id = coalesce(yy.id));\n\n--\n-- test ability to push constants through outer join clauses\n--\n\n:PREFIX\n  select * from int4_tbl a left join tenk1 b on f1 = unique2 where f1 = 0;\n\n:PREFIX\n  select * from tenk1 a full join tenk1 b using(unique2) where unique2 = 42;\n\n--\n-- test that quals attached to an outer join have correct semantics,\n-- specifically that they don't re-use expressions computed below the join;\n-- we force a mergejoin so that coalesce(b.q1, 1) appears as a join input\n--\n\nset enable_hashjoin to off;\nset enable_nestloop to off;\n\n:PREFIX\n  select a.q2, b.q1\n    from int8_tbl a left join int8_tbl b on a.q2 = coalesce(b.q1, 1)\n    where coalesce(b.q1, 1) > 0;\n\nreset enable_hashjoin;\nreset enable_nestloop;\n\n--\n-- test join removal\n--\n\nbegin;\n\nCREATE TEMP TABLE a (id int PRIMARY KEY, b_id int);\nCREATE TEMP TABLE b (id int PRIMARY KEY, c_id int);\nCREATE TEMP TABLE c (id int PRIMARY KEY);\nCREATE TEMP TABLE d (a int, b int);\nINSERT INTO a VALUES (0, 0), (1, NULL);\nINSERT INTO b VALUES (0, 0), (1, NULL);\nINSERT INTO c VALUES (0), (1);\nINSERT INTO d VALUES (1,3), (2,2), (3,1);\n\n-- all three cases should be optimizable into a simple seqscan\n:PREFIX SELECT a.* FROM a LEFT JOIN b ON a.b_id = b.id;\n:PREFIX SELECT b.* FROM b LEFT JOIN c ON b.c_id = c.id;\n:PREFIX\n  SELECT a.* FROM a LEFT JOIN (b left join c on b.c_id = c.id)\n  ON (a.b_id = b.id);\n\n-- check optimization of outer join within another special join\n:PREFIX\nselect id from a where id in (\n\tselect b.id from b left join c on b.id = c.id\n);\n\n-- check that join removal works for a left join when joining a subquery\n-- that is guaranteed to be unique by its GROUP BY clause\n:PREFIX\nselect d.* from d left join (select * from b group by b.id, b.c_id) s\n  on d.a = s.id and d.b = s.c_id;\n\n-- similarly, but keying off a DISTINCT clause\n:PREFIX\nselect d.* from d left join (select distinct * from b) s\n  on d.a = s.id and d.b = s.c_id;\n\n-- join removal is not possible when the GROUP BY contains a column that is\n-- not in the join condition.  (Note: as of 9.6, we notice that b.id is a\n-- primary key and so drop b.c_id from the GROUP BY of the resulting plan;\n-- but this happens too late for join removal in the outer plan level.)\n:PREFIX\nselect d.* from d left join (select * from b group by b.id, b.c_id) s\n  on d.a = s.id;\n\n-- similarly, but keying off a DISTINCT clause\n:PREFIX\nselect d.* from d left join (select distinct * from b) s\n  on d.a = s.id;\n\n-- check join removal works when uniqueness of the join condition is enforced\n-- by a UNION\n:PREFIX\nselect d.* from d left join (select id from a union select id from b) s\n  on d.a = s.id;\n\n-- check join removal with a cross-type comparison operator\n:PREFIX\nselect i8.* from int8_tbl i8 left join (select f1 from int4_tbl group by f1) i4\n  on i8.q1 = i4.f1;\n\n-- check join removal with lateral references\n:PREFIX\nselect 1 from (select a.id FROM a left join b on a.b_id = b.id) q,\n\t\t\t  lateral generate_series(1, q.id) gs(i) where q.id = gs.i;\n\nrollback;\n\ncreate table parent (k int primary key, pd int);\ncreate table child (k int unique, cd int);\nselect table_name from create_hypertable('parent','k',chunk_time_interval:=1000);\nselect table_name from create_hypertable('child','k',chunk_time_interval:=1000);\ninsert into parent values (1, 10), (2, 20), (3, 30);\ninsert into child values (1, 100), (4, 400);\n\n-- this case is optimizable\nselect p.* from parent p left join child c on (p.k = c.k);\n:PREFIX\n  select p.* from parent p left join child c on (p.k = c.k);\n\n-- this case is not\nselect p.*, linked from parent p\n  left join (select c.*, true as linked from child c) as ss\n  on (p.k = ss.k);\n:PREFIX\n  select p.*, linked from parent p\n    left join (select c.*, true as linked from child c) as ss\n    on (p.k = ss.k);\n\n-- check for a 9.0rc1 bug: join removal breaks pseudoconstant qual handling\nselect p.* from\n  parent p left join child c on (p.k = c.k)\n  where p.k = 1 and p.k = 2;\n:PREFIX\nselect p.* from\n  parent p left join child c on (p.k = c.k)\n  where p.k = 1 and p.k = 2;\n\nselect p.* from\n  (parent p left join child c on (p.k = c.k)) join parent x on p.k = x.k\n  where p.k = 1 and p.k = 2;\n:PREFIX\nselect p.* from\n  (parent p left join child c on (p.k = c.k)) join parent x on p.k = x.k\n  where p.k = 1 and p.k = 2;\n\ndrop table parent;\ndrop table child;\n\n-- bug 5255: this is not optimizable by join removal\nbegin;\n\nCREATE TEMP TABLE a (id int PRIMARY KEY);\nCREATE TEMP TABLE b (id int PRIMARY KEY, a_id int);\nINSERT INTO a VALUES (0), (1);\nINSERT INTO b VALUES (0, 0), (1, NULL);\n\nSELECT * FROM b LEFT JOIN a ON (b.a_id = a.id) WHERE (a.id IS NULL OR a.id > 0);\nSELECT b.* FROM b LEFT JOIN a ON (b.a_id = a.id) WHERE (a.id IS NULL OR a.id > 0);\n\nrollback;\n\n-- another join removal bug: this is not optimizable, either\nbegin;\n\ncreate temp table innertab (id int8 primary key, dat1 int8);\ninsert into innertab values(123, 42);\n\nSELECT * FROM\n    (SELECT 1 AS x) ss1\n  LEFT JOIN\n    (SELECT q1, q2, COALESCE(dat1, q1) AS y\n     FROM int8_tbl LEFT JOIN innertab ON q2 = id) ss2\n  ON true;\n\nrollback;\n\n-- another join removal bug: we must clean up correctly when removing a PHV\nbegin;\n\ncreate temp table uniquetbl (f1 text unique);\n\n:PREFIX\nselect t1.* from\n  uniquetbl as t1\n  left join (select *, '***'::text as d1 from uniquetbl) t2\n  on t1.f1 = t2.f1\n  left join uniquetbl t3\n  on t2.d1 = t3.f1;\n\n:PREFIX\nselect t0.*\nfrom\n text_tbl t0\n left join\n   (select case t1.ten when 0 then 'doh!'::text else null::text end as case1,\n           t1.stringu2\n     from tenk1 t1\n     join int4_tbl i4 ON i4.f1 = t1.unique2\n     left join uniquetbl u1 ON u1.f1 = t1.string4) ss\n  on t0.f1 = ss.case1\nwhere ss.stringu2 !~* ss.case1;\n\nselect t0.*\nfrom\n text_tbl t0\n left join\n   (select case t1.ten when 0 then 'doh!'::text else null::text end as case1,\n           t1.stringu2\n     from tenk1 t1\n     join int4_tbl i4 ON i4.f1 = t1.unique2\n     left join uniquetbl u1 ON u1.f1 = t1.string4) ss\n  on t0.f1 = ss.case1\nwhere ss.stringu2 !~* ss.case1;\n\nrollback;\n\n-- bug #8444: we've historically allowed duplicate aliases within aliased JOINs\n\n-- select * from\n--   int8_tbl x join (int4_tbl x cross join int4_tbl y) j on q1 = f1; -- error\n-- select * from\n--   int8_tbl x join (int4_tbl x cross join int4_tbl y) j on q1 = y.f1; -- error\nselect * from\n  int8_tbl x join (int4_tbl x cross join int4_tbl y(ff)) j on q1 = f1; -- ok\n\n--\n-- Test hints given on incorrect column references are useful\n--\n\n-- select t1.uunique1 from\n--   tenk1 t1 join tenk2 t2 on t1.two = t2.two; -- error, prefer \"t1\" suggestion\n-- select t2.uunique1 from\n--   tenk1 t1 join tenk2 t2 on t1.two = t2.two; -- error, prefer \"t2\" suggestion\n-- select uunique1 from\n--   tenk1 t1 join tenk2 t2 on t1.two = t2.two; -- error, suggest both at once\n\n--\n-- Take care to reference the correct RTE\n--\n\n-- select atts.relid::regclass, s.* from pg_stats s join\n--     pg_attribute a on s.attname = a.attname and s.tablename =\n--     a.attrelid::regclass::text join (select unnest(indkey) attnum,\n--     indexrelid from pg_index i) atts on atts.attnum = a.attnum where\n--     schemaname != 'pg_catalog';\n\n--\n-- Test LATERAL\n--\n\nselect unique2, x.*\nfrom tenk1 a, lateral (select * from int4_tbl b where f1 = a.unique1) x;\n:PREFIX\n  select unique2, x.*\n  from tenk1 a, lateral (select * from int4_tbl b where f1 = a.unique1) x;\nselect unique2, x.*\nfrom int4_tbl x, lateral (select unique2 from tenk1 where f1 = unique1) ss;\n:PREFIX\n  select unique2, x.*\n  from int4_tbl x, lateral (select unique2 from tenk1 where f1 = unique1) ss;\n:PREFIX\n  select unique2, x.*\n  from int4_tbl x cross join lateral (select unique2 from tenk1 where f1 = unique1) ss;\nselect unique2, x.*\nfrom int4_tbl x left join lateral (select unique1, unique2 from tenk1 where f1 = unique1) ss on true;\n:PREFIX\n  select unique2, x.*\n  from int4_tbl x left join lateral (select unique1, unique2 from tenk1 where f1 = unique1) ss on true;\n\n-- check scoping of lateral versus parent references\n-- the first of these should return int8_tbl.q2, the second int8_tbl.q1\nselect *, (select r from (select q1 as q2) x, (select q2 as r) y) from int8_tbl;\nselect *, (select r from (select q1 as q2) x, lateral (select q2 as r) y) from int8_tbl;\n\n-- lateral with function in FROM\nselect count(*) from tenk1 a, lateral generate_series(1,two) g;\n:PREFIX\n  select count(*) from tenk1 a, lateral generate_series(1,two) g;\n:PREFIX\n  select count(*) from tenk1 a cross join lateral generate_series(1,two) g;\n-- don't need the explicit LATERAL keyword for functions\n:PREFIX\n  select count(*) from tenk1 a, generate_series(1,two) g;\n\n-- lateral with UNION ALL subselect\n:PREFIX\n  select * from generate_series(100,200) g,\n    lateral (select * from int8_tbl a where g = q1 union all\n             select * from int8_tbl b where g = q2) ss;\nselect * from generate_series(100,200) g,\n  lateral (select * from int8_tbl a where g = q1 union all\n           select * from int8_tbl b where g = q2) ss;\n\n-- lateral with VALUES\n:PREFIX\n  select count(*) from tenk1 a,\n    tenk1 b join lateral (values(a.unique1)) ss(x) on b.unique2 = ss.x;\nselect count(*) from tenk1 a,\n  tenk1 b join lateral (values(a.unique1)) ss(x) on b.unique2 = ss.x;\n\n-- lateral with VALUES, no flattening possible\n:PREFIX\n  select count(*) from tenk1 a,\n    tenk1 b join lateral (values(a.unique1),(-1)) ss(x) on b.unique2 = ss.x;\nselect count(*) from tenk1 a,\n  tenk1 b join lateral (values(a.unique1),(-1)) ss(x) on b.unique2 = ss.x;\n\n-- lateral injecting a strange outer join condition\n:PREFIX\n  select * from int8_tbl a,\n    int8_tbl x left join lateral (select a.q1 from int4_tbl y) ss(z)\n      on x.q2 = ss.z\n  order by a.q1, a.q2, x.q1, x.q2, ss.z;\nselect * from int8_tbl a,\n  int8_tbl x left join lateral (select a.q1 from int4_tbl y) ss(z)\n    on x.q2 = ss.z\n  order by a.q1, a.q2, x.q1, x.q2, ss.z;\n\n-- lateral reference to a join alias variable\nselect * from (select f1/2 as x from int4_tbl) ss1 join int4_tbl i4 on x = f1,\n  lateral (select x) ss2(y);\nselect * from (select f1 as x from int4_tbl) ss1 join int4_tbl i4 on x = f1,\n  lateral (values(x)) ss2(y);\nselect * from ((select f1/2 as x from int4_tbl) ss1 join int4_tbl i4 on x = f1) j,\n  lateral (select x) ss2(y);\n\n-- lateral references requiring pullup\nselect * from (values(1)) x(lb),\n  lateral generate_series(lb,4) x4;\nselect * from (select f1/1000000000 from int4_tbl) x(lb),\n  lateral generate_series(lb,4) x4;\nselect * from (values(1)) x(lb),\n  lateral (values(lb)) y(lbcopy);\nselect * from (values(1)) x(lb),\n  lateral (select lb from int4_tbl) y(lbcopy);\nselect * from\n  int8_tbl x left join (select q1,coalesce(q2,0) q2 from int8_tbl) y on x.q2 = y.q1,\n  lateral (values(x.q1,y.q1,y.q2)) v(xq1,yq1,yq2);\nselect * from\n  int8_tbl x left join (select q1,coalesce(q2,0) q2 from int8_tbl) y on x.q2 = y.q1,\n  lateral (select x.q1,y.q1,y.q2) v(xq1,yq1,yq2);\nselect x.* from\n  int8_tbl x left join (select q1,coalesce(q2,0) q2 from int8_tbl) y on x.q2 = y.q1,\n  lateral (select x.q1,y.q1,y.q2) v(xq1,yq1,yq2);\nselect v.* from\n  (int8_tbl x left join (select q1,coalesce(q2,0) q2 from int8_tbl) y on x.q2 = y.q1)\n  left join int4_tbl z on z.f1 = x.q2,\n  lateral (select x.q1,y.q1 union all select x.q2,y.q2) v(vx,vy);\nselect v.* from\n  (int8_tbl x left join (select q1,(select coalesce(q2,0)) q2 from int8_tbl) y on x.q2 = y.q1)\n  left join int4_tbl z on z.f1 = x.q2,\n  lateral (select x.q1,y.q1 union all select x.q2,y.q2) v(vx,vy);\ncreate temp table dual();\ninsert into dual default values;\nanalyze dual;\nselect v.* from\n  (int8_tbl x left join (select q1,(select coalesce(q2,0)) q2 from int8_tbl) y on x.q2 = y.q1)\n  left join int4_tbl z on z.f1 = x.q2,\n  lateral (select x.q1,y.q1 from dual union all select x.q2,y.q2 from dual) v(vx,vy);\n\ndrop table dual;\n\n:PREFIX\nselect * from\n  int8_tbl a left join\n  lateral (select *, a.q2 as x from int8_tbl b) ss on a.q2 = ss.q1;\nselect * from\n  int8_tbl a left join\n  lateral (select *, a.q2 as x from int8_tbl b) ss on a.q2 = ss.q1;\n:PREFIX\nselect * from\n  int8_tbl a left join\n  lateral (select *, coalesce(a.q2, 42) as x from int8_tbl b) ss on a.q2 = ss.q1;\nselect * from\n  int8_tbl a left join\n  lateral (select *, coalesce(a.q2, 42) as x from int8_tbl b) ss on a.q2 = ss.q1;\n\n-- lateral can result in join conditions appearing below their\n-- real semantic level\n:PREFIX\nselect * from int4_tbl i left join\n  lateral (select * from int2_tbl j where i.f1 = j.f1) k on true;\nselect * from int4_tbl i left join\n  lateral (select * from int2_tbl j where i.f1 = j.f1) k on true;\n:PREFIX\nselect * from int4_tbl i left join\n  lateral (select coalesce(i) from int2_tbl j where i.f1 = j.f1) k on true;\nselect * from int4_tbl i left join\n  lateral (select coalesce(i) from int2_tbl j where i.f1 = j.f1) k on true;\n:PREFIX\nselect * from int4_tbl a,\n  lateral (\n    select * from int4_tbl b left join int8_tbl c on (b.f1 = q1 and a.f1 = q2)\n  ) ss;\nselect * from int4_tbl a,\n  lateral (\n    select * from int4_tbl b left join int8_tbl c on (b.f1 = q1 and a.f1 = q2)\n  ) ss;\n\n-- lateral reference in a PlaceHolderVar evaluated at join level\n:PREFIX\nselect * from\n  int8_tbl a left join lateral\n  (select b.q1 as bq1, c.q1 as cq1, least(a.q1,b.q1,c.q1) from\n   int8_tbl b cross join int8_tbl c) ss\n  on a.q2 = ss.bq1;\nselect * from\n  int8_tbl a left join lateral\n  (select b.q1 as bq1, c.q1 as cq1, least(a.q1,b.q1,c.q1) from\n   int8_tbl b cross join int8_tbl c) ss\n  on a.q2 = ss.bq1;\n\n-- case requiring nested PlaceHolderVars\n:PREFIX\nselect * from\n  int8_tbl c left join (\n    int8_tbl a left join (select q1, coalesce(q2,42) as x from int8_tbl b) ss1\n      on a.q2 = ss1.q1\n    cross join\n    lateral (select q1, coalesce(ss1.x,q2) as y from int8_tbl d) ss2\n  ) on c.q2 = ss2.q1,\n  lateral (select ss2.y offset 0) ss3;\n\n-- case that breaks the old ph_may_need optimization\n:PREFIX\nselect c.*,a.*,ss1.q1,ss2.q1,ss3.* from\n  int8_tbl c left join (\n    int8_tbl a left join\n      (select q1, coalesce(q2,f1) as x from int8_tbl b, int4_tbl b2\n       where q1 < f1) ss1\n      on a.q2 = ss1.q1\n    cross join\n    lateral (select q1, coalesce(ss1.x,q2) as y from int8_tbl d) ss2\n  ) on c.q2 = ss2.q1,\n  lateral (select * from int4_tbl i where ss2.y > f1) ss3;\n\n-- check processing of postponed quals (bug #9041)\n:PREFIX\nselect * from\n  (select 1 as x offset 0) x cross join (select 2 as y offset 0) y\n  left join lateral (\n    select * from (select 3 as z offset 0) z where z.z = x.x\n  ) zz on zz.z = y.y;\n\n-- check dummy rels with lateral references (bug #15694)\n:PREFIX\nselect * from int8_tbl i8 left join lateral\n  (select *, i8.q2 from int4_tbl where false) ss on true;\n:PREFIX\nselect * from int8_tbl i8 left join lateral\n  (select *, i8.q2 from int4_tbl i1, int4_tbl i2 where false) ss on true;\n\n-- check handling of nested appendrels inside LATERAL\nselect * from\n  ((select 2 as v) union all (select 3 as v)) as q1\n  cross join lateral\n  ((select * from\n      ((select 4 as v) union all (select 5 as v)) as q3)\n   union all\n   (select q1.v)\n  ) as q2;\n\n-- check we don't try to do a unique-ified semijoin with LATERAL\n:PREFIX\nselect * from\n  (values (0,9998), (1,1000)) v(id,x),\n  lateral (select f1 from int4_tbl\n           where f1 = any (select unique1 from tenk1\n                           where unique2 = v.x offset 0)) ss;\nselect * from\n  (values (0,9998), (1,1000)) v(id,x),\n  lateral (select f1 from int4_tbl\n           where f1 = any (select unique1 from tenk1\n                           where unique2 = v.x offset 0)) ss;\n\n-- check proper extParam/allParam handling (this isn't exactly a LATERAL issue,\n-- but we can make the test case much more compact with LATERAL)\n:PREFIX\nselect * from (values (0), (1)) v(id),\nlateral (select * from int8_tbl t1,\n         lateral (select * from\n                    (select * from int8_tbl t2\n                     where q1 = any (select q2 from int8_tbl t3\n                                     where q2 = (select greatest(t1.q1,t2.q2))\n                                       and (select v.id=0)) offset 0) ss2) ss\n         where t1.q1 = ss.q2) ss0;\n\nselect * from (values (0), (1)) v(id),\nlateral (select * from int8_tbl t1,\n         lateral (select * from\n                    (select * from int8_tbl t2\n                     where q1 = any (select q2 from int8_tbl t3\n                                     where q2 = (select greatest(t1.q1,t2.q2))\n                                       and (select v.id=0)) offset 0) ss2) ss\n         where t1.q1 = ss.q2) ss0;\n\n-- test some error cases where LATERAL should have been used but wasn't\n-- select f1,g from int4_tbl a, (select f1 as g) ss;\n-- select f1,g from int4_tbl a, (select a.f1 as g) ss;\n-- select f1,g from int4_tbl a cross join (select f1 as g) ss;\n-- select f1,g from int4_tbl a cross join (select a.f1 as g) ss;\n\n-- SQL:2008 says the left table is in scope but illegal to access here\n-- select f1,g from int4_tbl a right join lateral generate_series(0, a.f1) g on true;\n-- select f1,g from int4_tbl a full join lateral generate_series(0, a.f1) g on true;\n\n-- check we complain about ambiguous table references\n-- select * from\n--   int8_tbl x cross join (int4_tbl x cross join lateral (select x.f1) ss);\n\n-- LATERAL can be used to put an aggregate into the FROM clause of its query\n-- select 1 from tenk1 a, lateral (select max(a.unique1) from int4_tbl b) ss;\n\n-- check behavior of LATERAL in UPDATE/DELETE\n\n-- create temp table xx1 as select f1 as x1, -f1 as x2 from int4_tbl;\n\n-- error, can't do this:\n-- update xx1 set x2 = f1 from (select * from int4_tbl where f1 = x1) ss;\n-- update xx1 set x2 = f1 from (select * from int4_tbl where f1 = xx1.x1) ss;\n-- can't do it even with LATERAL:\n-- update xx1 set x2 = f1 from lateral (select * from int4_tbl where f1 = x1) ss;\n-- we might in future allow something like this, but for now it's an error:\n-- update xx1 set x2 = f1 from xx1, lateral (select * from int4_tbl where f1 = x1) ss;\n\n-- also errors:\n-- delete from xx1 using (select * from int4_tbl where f1 = x1) ss;\n-- delete from xx1 using (select * from int4_tbl where f1 = xx1.x1) ss;\n-- delete from xx1 using lateral (select * from int4_tbl where f1 = x1) ss;\n\n--\n-- test LATERAL reference propagation down a multi-level inheritance hierarchy\n-- produced for a multi-level partitioned table hierarchy.\n--\ncreate table join_pt1 (a int, b int, c varchar) partition by range(a);\ncreate table join_pt1p1 partition of join_pt1 for values from (0) to (100) partition by range(b);\ncreate table join_pt1p2 partition of join_pt1 for values from (100) to (200);\ncreate table join_pt1p1p1 partition of join_pt1p1 for values from (0) to (100);\ninsert into join_pt1 values (1, 1, 'x'), (101, 101, 'y');\ncreate table join_ut1 (a int, b int, c varchar);\ninsert into join_ut1 values (101, 101, 'y'), (2, 2, 'z');\n:PREFIX\nselect t1.b, ss.phv from join_ut1 t1 left join lateral\n              (select t2.a as t2a, t3.a t3a, least(t1.a, t2.a, t3.a) phv\n\t\t\t\t\t  from join_pt1 t2 join join_ut1 t3 on t2.a = t3.b) ss\n              on t1.a = ss.t2a order by t1.a;\nselect t1.b, ss.phv from join_ut1 t1 left join lateral\n              (select t2.a as t2a, t3.a t3a, least(t1.a, t2.a, t3.a) phv\n\t\t\t\t\t  from join_pt1 t2 join join_ut1 t3 on t2.a = t3.b) ss\n              on t1.a = ss.t2a order by t1.a;\n\ndrop table join_pt1;\ndrop table join_ut1;\n--\n-- test that foreign key join estimation performs sanely for outer joins\n--\n\nbegin;\n\ncreate table fkest (a int, b int, c int unique, primary key(a,b));\ncreate table fkest1 (a int, b int, primary key(a,b));\n\ninsert into fkest select x/10, x%10, x from generate_series(1,1000) x;\ninsert into fkest1 select x/10, x%10 from generate_series(1,1000) x;\n\nalter table fkest1\n  add constraint fkest1_a_b_fkey foreign key (a,b) references fkest;\n\nanalyze fkest;\nanalyze fkest1;\n\n:PREFIX\nselect *\nfrom fkest f\n  left join fkest1 f1 on f.a = f1.a and f.b = f1.b\n  left join fkest1 f2 on f.a = f2.a and f.b = f2.b\n  left join fkest1 f3 on f.a = f3.a and f.b = f3.b\nwhere f.c = 1;\n\nrollback;\n\n--\n-- test planner's ability to mark joins as unique\n--\n\ncreate table j1 (id int primary key);\ncreate table j2 (id int primary key);\ncreate table j3 (id int);\n\nSELECT (SELECT table_name FROM create_hypertable(tbl,'id',chunk_time_interval:=1000)) FROM (VALUES ('j1'),('j2'),('j3')) v(tbl);\n\ninsert into j1 values(1),(2),(3);\ninsert into j2 values(1),(2),(3);\ninsert into j3 values(1),(1);\n\nanalyze j1;\nanalyze j2;\nanalyze j3;\n\n-- ensure join is properly marked as unique\n:PREFIX\nselect * from j1 inner join j2 on j1.id = j2.id;\n\n-- ensure join is not unique when not an equi-join\n:PREFIX\nselect * from j1 inner join j2 on j1.id > j2.id;\n\n-- ensure non-unique rel is not chosen as inner\n:PREFIX\nselect * from j1 inner join j3 on j1.id = j3.id;\n\n-- ensure left join is marked as unique\n:PREFIX\nselect * from j1 left join j2 on j1.id = j2.id;\n\n-- ensure right join is marked as unique\n:PREFIX\nselect * from j1 right join j2 on j1.id = j2.id;\n\n-- ensure full join is marked as unique\n:PREFIX\nselect * from j1 full join j2 on j1.id = j2.id;\n\n-- a clauseless (cross) join can't be unique\n:PREFIX\nselect * from j1 cross join j2;\n\n-- ensure a natural join is marked as unique\n:PREFIX\nselect * from j1 natural join j2;\n\n-- ensure a distinct clause allows the inner to become unique\n:PREFIX\nselect * from j1\ninner join (select distinct id from j3) j3 on j1.id = j3.id;\n\n-- ensure group by clause allows the inner to become unique\n:PREFIX\nselect * from j1\ninner join (select id from j3 group by id) j3 on j1.id = j3.id;\n\ndrop table j1;\ndrop table j2;\ndrop table j3;\n\n-- test more complex permutations of unique joins\n\ncreate table j1 (id1 int, id2 int, primary key(id1,id2));\ncreate table j2 (id1 int, id2 int, primary key(id1,id2));\ncreate table j3 (id1 int, id2 int, primary key(id1,id2));\n\nSELECT (SELECT table_name FROM create_hypertable(tbl,'id1',chunk_time_interval:=1000,create_default_indexes:=false)) FROM (VALUES ('j1'),('j2'),('j3')) v(tbl);\n\ninsert into j1 values(1,1),(1,2);\ninsert into j2 values(1,1);\ninsert into j3 values(1,1);\n\nanalyze j1;\nanalyze j2;\nanalyze j3;\n\n-- ensure there's no unique join when not all columns which are part of the\n-- unique index are seen in the join clause\n:PREFIX\nselect * from j1\ninner join j2 on j1.id1 = j2.id1;\n\n-- ensure proper unique detection with multiple join quals\n:PREFIX\nselect * from j1\ninner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2;\n\n-- ensure we don't detect the join to be unique when quals are not part of the\n-- join condition\n:PREFIX\nselect * from j1\ninner join j2 on j1.id1 = j2.id1 where j1.id2 = 1;\n\n-- as above, but for left joins.\n:PREFIX\nselect * from j1\nleft join j2 on j1.id1 = j2.id1 where j1.id2 = 1;\n\n-- validate logic in merge joins which skips mark and restore.\n-- it should only do this if all quals which were used to detect the unique\n-- are present as join quals, and not plain quals.\nset enable_nestloop to 0;\nset enable_hashjoin to 0;\nset enable_sort to 0;\n\n-- create an index that will be preferred over the PK to perform the join\ncreate index j1_id1_idx on j1 (id1) where id1 % 1000 = 1;\n\n:PREFIX select * from j1 j1\ninner join j1 j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2\nwhere j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1;\n\nselect * from j1 j1\ninner join j1 j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2\nwhere j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1;\n\nreset enable_nestloop;\nreset enable_hashjoin;\nreset enable_sort;\n\ndrop table j1;\ndrop table j2;\ndrop table j3;\n\n-- check that semijoin inner is not seen as unique for a portion of the outerrel\n:PREFIX\nselect t1.unique1, t2.hundred\nfrom onek t1, tenk1 t2\nwhere exists (select 1 from tenk1 t3\n              where t3.thousand = t1.unique1 and t3.tenthous = t2.hundred)\n      and t1.unique1 < 1;\n\n-- ... unless it actually is unique\ncreate table j3 as select unique1, tenthous from onek LIMIT 0;\nselect table_name FROM create_hypertable('j3','unique1',chunk_time_interval:=10000);\ninsert into j3 SELECT unique1, tenthous from onek;\nvacuum analyze j3;\ncreate unique index on j3(unique1, tenthous);\n\n:PREFIX\nselect t1.unique1, t2.hundred\nfrom onek t1, tenk1 t2\nwhere exists (select 1 from j3\n              where j3.unique1 = t1.unique1 and j3.tenthous = t2.hundred)\n      and t1.unique1 < 1;\n\ndrop table j3;\n\n--\n-- exercises for the hash join code\n--\n\nbegin;\n\nset local min_parallel_table_scan_size = 0;\nset local parallel_setup_cost = 0;\nset local enable_hashjoin = on;\n\n-- Extract bucket and batch counts from an explain analyze plan.  In\n-- general we can't make assertions about how many batches (or\n-- buckets) will be required because it can vary, but we can in some\n-- special cases and we can check for growth.\ncreate or replace function find_hash(node json)\nreturns json language plpgsql\nas\n$$\ndeclare\n  x json;\n  child json;\nbegin\n  if node->>'Node Type' = 'Hash' then\n    return node;\n  else\n    for child in select json_array_elements(node->'Plans')\n    loop\n      x := find_hash(child);\n      if x is not null then\n        return x;\n      end if;\n    end loop;\n    return null;\n  end if;\nend;\n$$;\ncreate or replace function hash_join_batches(query text)\nreturns table (original int, final int) language plpgsql\nas\n$$\ndeclare\n  whole_plan json;\n  hash_node json;\nbegin\n  for whole_plan in\n    execute 'explain (analyze, format ''json'') ' || query\n  loop\n    hash_node := find_hash(json_extract_path(whole_plan, '0', 'Plan'));\n    original := hash_node->>'Original Hash Batches';\n    final := hash_node->>'Hash Batches';\n    return next;\n  end loop;\nend;\n$$;\n\n-- Make a simple relation with well distributed keys and correctly\n-- estimated size.\ncreate table simple(id int, t text);\nselect table_name from create_hypertable('simple','id',chunk_time_interval:=5000);\n\ninsert into simple\n  select generate_series(1, 20000) AS id, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';\nalter table simple set (parallel_workers = 2);\nanalyze simple;\n\n-- Make a relation whose size we will under-estimate.  We want stats\n-- to say 1000 rows, but actually there are 20,000 rows.\ncreate table bigger_than_it_looks(id int, t text);\nselect table_name from create_hypertable('bigger_than_it_looks','id',chunk_time_interval:=5000);\ninsert into bigger_than_it_looks\n  select generate_series(1, 20000) as id, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';\nalter table bigger_than_it_looks set (autovacuum_enabled = 'false');\nalter table bigger_than_it_looks set (parallel_workers = 2);\nanalyze bigger_than_it_looks;\nupdate pg_class set reltuples = 1000 where relname = 'bigger_than_it_looks';\n\n-- Make a relation whose size we underestimate and that also has a\n-- kind of skew that breaks our batching scheme.  We want stats to say\n-- 2 rows, but actually there are 20,000 rows with the same key.\ncreate table extremely_skewed (id int, t text);\nselect table_name from create_hypertable('extremely_skewed','id',chunk_time_interval:=5000);\nalter table extremely_skewed set (autovacuum_enabled = 'false');\nalter table extremely_skewed set (parallel_workers = 2);\nanalyze extremely_skewed;\ninsert into extremely_skewed\n  select 42 as id, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'\n  from generate_series(1, 20000);\nupdate pg_class\n  set reltuples = 2, relpages = pg_relation_size('extremely_skewed') / 8192\n  where relname = 'extremely_skewed';\n\n-- Make a relation with a couple of enormous tuples.\ncreate table wide(id int, t text);\nselect table_name from create_hypertable('wide','id',chunk_time_interval:=5000);\ninsert into wide select generate_series(1, 10) as id, rpad('', 320000, 'x') as t;\nalter table wide set (parallel_workers = 2);\n\n-- update statistics\nanalyze wide;\n\n-- The \"optimal\" case: the hash table fits in memory; we plan for 1\n-- batch, we stick to that number, and peak memory usage stays within\n-- our work_mem budget\n\n-- non-parallel\nsavepoint settings;\nset local max_parallel_workers_per_gather = 0;\nset local work_mem = '4MB';\n:PREFIX\n  select count(*) from simple r join simple s using (id);\nselect count(*) from simple r join simple s using (id);\nselect original > 1 as initially_multibatch, final > original as increased_batches\n  from hash_join_batches(\n$$\n  select count(*) from simple r join simple s using (id);\n$$);\nrollback to settings;\n\n-- parallel with parallel-oblivious hash join\nsavepoint settings;\nset local max_parallel_workers_per_gather = 2;\nset local work_mem = '4MB';\nset local enable_parallel_hash = off;\n:PREFIX\n  select count(*) from simple r join simple s using (id);\nselect count(*) from simple r join simple s using (id);\nselect original > 1 as initially_multibatch, final > original as increased_batches\n  from hash_join_batches(\n$$\n  select count(*) from simple r join simple s using (id);\n$$);\nrollback to settings;\n\n-- parallel with parallel-aware hash join\nsavepoint settings;\nset local max_parallel_workers_per_gather = 2;\nset local work_mem = '4MB';\nset local enable_parallel_hash = on;\n:PREFIX\n  select count(*) from simple r join simple s using (id);\nselect count(*) from simple r join simple s using (id);\nselect original > 1 as initially_multibatch, final > original as increased_batches\n  from hash_join_batches(\n$$\n  select count(*) from simple r join simple s using (id);\n$$);\nrollback to settings;\n\n-- The \"good\" case: batches required, but we plan the right number; we\n-- plan for some number of batches, and we stick to that number, and\n-- peak memory usage says within our work_mem budget\n\n-- non-parallel\nsavepoint settings;\nset local max_parallel_workers_per_gather = 0;\nset local work_mem = '128kB';\n:PREFIX\n  select count(*) from simple r join simple s using (id);\nselect count(*) from simple r join simple s using (id);\nselect original > 1 as initially_multibatch, final > original as increased_batches\n  from hash_join_batches(\n$$\n  select count(*) from simple r join simple s using (id);\n$$);\nrollback to settings;\n\n-- parallel with parallel-oblivious hash join\nsavepoint settings;\nset local max_parallel_workers_per_gather = 2;\nset local work_mem = '128kB';\nset local enable_parallel_hash = off;\n:PREFIX\n  select count(*) from simple r join simple s using (id);\nselect count(*) from simple r join simple s using (id);\nselect original > 1 as initially_multibatch, final > original as increased_batches\n  from hash_join_batches(\n$$\n  select count(*) from simple r join simple s using (id);\n$$);\nrollback to settings;\n\n-- parallel with parallel-aware hash join\nsavepoint settings;\nset local max_parallel_workers_per_gather = 2;\nset local work_mem = '192kB';\nset local enable_parallel_hash = on;\n:PREFIX\n  select count(*) from simple r join simple s using (id);\nselect count(*) from simple r join simple s using (id);\nselect original > 1 as initially_multibatch, final > original as increased_batches\n  from hash_join_batches(\n$$\n  select count(*) from simple r join simple s using (id);\n$$);\nrollback to settings;\n\n-- The \"bad\" case: during execution we need to increase number of\n-- batches; in this case we plan for 1 batch, and increase at least a\n-- couple of times, and peak memory usage stays within our work_mem\n-- budget\n\n-- non-parallel\nsavepoint settings;\nset local max_parallel_workers_per_gather = 0;\nset local work_mem = '128kB';\n:PREFIX\n  select count(*) FROM simple r JOIN bigger_than_it_looks s USING (id);\nselect count(*) FROM simple r JOIN bigger_than_it_looks s USING (id);\nselect original > 1 as initially_multibatch, final > original as increased_batches\n  from hash_join_batches(\n$$\n  select count(*) FROM simple r JOIN bigger_than_it_looks s USING (id);\n$$);\nrollback to settings;\n\n-- parallel with parallel-oblivious hash join\nsavepoint settings;\nset local max_parallel_workers_per_gather = 2;\nset local work_mem = '128kB';\nset local enable_parallel_hash = off;\n:PREFIX\n  select count(*) from simple r join bigger_than_it_looks s using (id);\nselect count(*) from simple r join bigger_than_it_looks s using (id);\nselect original > 1 as initially_multibatch, final > original as increased_batches\n  from hash_join_batches(\n$$\n  select count(*) from simple r join bigger_than_it_looks s using (id);\n$$);\nrollback to settings;\n\n-- parallel with parallel-aware hash join\nsavepoint settings;\nset local max_parallel_workers_per_gather = 1;\nset local work_mem = '192kB';\nset local enable_parallel_hash = on;\n:PREFIX\n  select count(*) from simple r join bigger_than_it_looks s using (id);\nselect count(*) from simple r join bigger_than_it_looks s using (id);\nselect original > 1 as initially_multibatch, final > original as increased_batches\n  from hash_join_batches(\n$$\n  select count(*) from simple r join bigger_than_it_looks s using (id);\n$$);\nrollback to settings;\n\n-- The \"ugly\" case: increasing the number of batches during execution\n-- doesn't help, so stop trying to fit in work_mem and hope for the\n-- best; in this case we plan for 1 batch, increases just once and\n-- then stop increasing because that didn't help at all, so we blow\n-- right through the work_mem budget and hope for the best...\n\n-- non-parallel\nsavepoint settings;\nset local max_parallel_workers_per_gather = 0;\nset local work_mem = '128kB';\nset local enable_mergejoin to false;\n:PREFIX\n  select count(*) from simple r join extremely_skewed s using (id);\nselect count(*) from simple r join extremely_skewed s using (id);\nselect * from hash_join_batches(\n$$\n  select count(*) from simple r join extremely_skewed s using (id);\n$$);\nrollback to settings;\n\n-- parallel with parallel-oblivious hash join\nsavepoint settings;\nset local max_parallel_workers_per_gather = 2;\nset local work_mem = '128kB';\nset local enable_parallel_hash = off;\n:PREFIX\n  select count(*) from simple r join extremely_skewed s using (id);\nselect count(*) from simple r join extremely_skewed s using (id);\nselect * from hash_join_batches(\n$$\n  select count(*) from simple r join extremely_skewed s using (id);\n$$);\nrollback to settings;\n\n-- parallel with parallel-aware hash join\nsavepoint settings;\nset local max_parallel_workers_per_gather = 1;\nset local work_mem = '128kB';\nset local enable_parallel_hash = on;\n:PREFIX\n  select count(*) from simple r join extremely_skewed s using (id);\nselect count(*) from simple r join extremely_skewed s using (id);\nselect * from hash_join_batches(\n$$\n  select count(*) from simple r join extremely_skewed s using (id);\n$$);\nrollback to settings;\n\n-- A couple of other hash join tests unrelated to work_mem management.\n\n-- Check that EXPLAIN ANALYZE has data even if the leader doesn't participate\nsavepoint settings;\nset local max_parallel_workers_per_gather = 2;\nset local work_mem = '4MB';\nset local parallel_leader_participation = off;\nselect * from hash_join_batches(\n$$\n  select count(*) from simple r join simple s using (id);\n$$);\nrollback to settings;\n\n-- Exercise rescans.  We'll turn off parallel_leader_participation so\n-- that we can check that instrumentation comes back correctly.\n\ncreate table join_foo as select generate_series(1, 3) as id, 'xxxxx'::text as t;\nalter table join_foo set (parallel_workers = 0);\ncreate table join_bar as select generate_series(1, 10000) as id, 'xxxxx'::text as t;\nalter table join_bar set (parallel_workers = 2);\n\n-- update statistics\nanalyze join_foo;\nanalyze join_bar;\n\n-- multi-batch with rescan, parallel-oblivious\nsavepoint settings;\nset enable_parallel_hash = off;\nset parallel_leader_participation = off;\nset min_parallel_table_scan_size = 0;\nset parallel_setup_cost = 0;\nset parallel_tuple_cost = 0;\nset max_parallel_workers_per_gather = 2;\nset enable_material = off;\nset enable_mergejoin = off;\nset work_mem = '64kB';\n:PREFIX\n  select count(*) from join_foo\n    left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss\n    on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;\nselect count(*) from join_foo\n  left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss\n  on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;\nselect final > 1 as multibatch\n  from hash_join_batches(\n$$\n  select count(*) from join_foo\n    left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss\n    on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;\n$$);\nrollback to settings;\n\n-- single-batch with rescan, parallel-oblivious\nsavepoint settings;\nset enable_parallel_hash = off;\nset parallel_leader_participation = off;\nset min_parallel_table_scan_size = 0;\nset parallel_setup_cost = 0;\nset parallel_tuple_cost = 0;\nset max_parallel_workers_per_gather = 2;\nset enable_material = off;\nset enable_mergejoin = off;\nset work_mem = '4MB';\n:PREFIX\n  select count(*) from join_foo\n    left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss\n    on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;\nselect final > 1 as multibatch\n  from hash_join_batches(\n$$\n  select count(*) from join_foo\n    left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss\n    on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;\n$$);\nrollback to settings;\n\n-- multi-batch with rescan, parallel-aware\nsavepoint settings;\nset enable_parallel_hash = on;\nset parallel_leader_participation = off;\nset min_parallel_table_scan_size = 0;\nset parallel_setup_cost = 0;\nset parallel_tuple_cost = 0;\nset max_parallel_workers_per_gather = 2;\nset enable_material = off;\nset enable_mergejoin = off;\nset work_mem = '64kB';\n:PREFIX\n  select count(*) from join_foo\n    left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss\n    on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;\nselect count(*) from join_foo\n  left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss\n  on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;\nselect final > 1 as multibatch\n  from hash_join_batches(\n$$\n  select count(*) from join_foo\n    left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss\n    on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;\n$$);\nrollback to settings;\n\n-- single-batch with rescan, parallel-aware\nsavepoint settings;\nset enable_parallel_hash = on;\nset parallel_leader_participation = off;\nset min_parallel_table_scan_size = 0;\nset parallel_setup_cost = 0;\nset parallel_tuple_cost = 0;\nset max_parallel_workers_per_gather = 2;\nset enable_material = off;\nset enable_mergejoin = off;\nset work_mem = '4MB';\n:PREFIX\n  select count(*) from join_foo\n    left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss\n    on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;\nselect count(*) from join_foo\n  left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss\n  on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;\nselect final > 1 as multibatch\n  from hash_join_batches(\n$$\n  select count(*) from join_foo\n    left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss\n    on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1;\n$$);\nrollback to settings;\n\n-- A full outer join where every record is matched.\n\n-- non-parallel\nsavepoint settings;\nset local max_parallel_workers_per_gather = 0;\n:PREFIX\n     select  count(*) from simple r full outer join simple s using (id);\nrollback to settings;\n\n-- parallelism not possible with parallel-oblivious outer hash join\nsavepoint settings;\nset local max_parallel_workers_per_gather = 2;\n:PREFIX\n     select  count(*) from simple r full outer join simple s using (id);\nselect  count(*) from simple r full outer join simple s using (id);\nrollback to settings;\n\n-- An full outer join where every record is not matched.\n\n-- non-parallel\nsavepoint settings;\nset local max_parallel_workers_per_gather = 0;\n:PREFIX\n     select  count(*) from simple r full outer join simple s on (r.id = 0 - s.id);\nselect  count(*) from simple r full outer join simple s on (r.id = 0 - s.id);\nrollback to settings;\n\n-- parallelism not possible with parallel-oblivious outer hash join\nsavepoint settings;\nset local max_parallel_workers_per_gather = 2;\n:PREFIX\n     select  count(*) from simple r full outer join simple s on (r.id = 0 - s.id);\nselect  count(*) from simple r full outer join simple s on (r.id = 0 - s.id);\nrollback to settings;\n\n-- exercise special code paths for huge tuples (note use of non-strict\n-- expression and left join required to get the detoasted tuple into\n-- the hash table)\n\n-- parallel with parallel-aware hash join (hits ExecParallelHashLoadTuple and\n-- sts_puttuple oversized tuple cases because it's multi-batch)\nsavepoint settings;\nset max_parallel_workers_per_gather = 2;\nset enable_parallel_hash = on;\nset work_mem = '128kB';\n:PREFIX\n  select length(max(s.t))\n  from wide left join (select id, coalesce(t, '') || '' as t from wide) s using (id);\nselect length(max(s.t))\nfrom wide left join (select id, coalesce(t, '') || '' as t from wide) s using (id);\nselect final > 1 as multibatch\n  from hash_join_batches(\n$$\n  select length(max(s.t))\n  from wide left join (select id, coalesce(t, '') || '' as t from wide) s using (id);\n$$);\nrollback to settings;\n\nrollback;\n"
  },
  {
    "path": "test/sql/include/plan_expand_hypertable_load.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n--single time dimension\nCREATE TABLE hyper (\"time_broken\" bigint NOT NULL, \"value\" integer);\n\nALTER TABLE hyper\nDROP COLUMN time_broken,\nADD COLUMN time BIGINT;\n\nSELECT create_hypertable('hyper', 'time',  chunk_time_interval => 10);\n\nINSERT INTO hyper SELECT g, g FROM generate_series(0,1000) g;\n\n--insert a point with INT_MAX_64\nINSERT INTO hyper (time, value) SELECT 9223372036854775807::bigint, 0;\n\n--time and space\nCREATE TABLE hyper_w_space (\"time_broken\" bigint NOT NULL, \"device_id\" text, \"value\" integer);\n\nALTER TABLE hyper_w_space\nDROP COLUMN time_broken,\nADD COLUMN time BIGINT;\n\nSELECT create_hypertable('hyper_w_space', 'time', 'device_id', 4, chunk_time_interval => 10);\n\nINSERT INTO hyper_w_space (time, device_id, value) SELECT g, 'dev' || g, g FROM generate_series(0,30) g;\n\nCREATE VIEW hyper_w_space_view AS (SELECT * FROM hyper_w_space);\n\n\n--with timestamp and space\nCREATE TABLE tag (id serial PRIMARY KEY, name text);\nCREATE TABLE hyper_ts (\"time_broken\" timestamptz NOT NULL, \"device_id\" text, tag_id INT REFERENCES tag(id), \"value\" integer);\n\nALTER TABLE hyper_ts\nDROP COLUMN time_broken,\nADD COLUMN time TIMESTAMPTZ;\n\nSELECT create_hypertable('hyper_ts', 'time', 'device_id', 2, chunk_time_interval => '10 seconds'::interval);\n\nINSERT INTO tag(name) SELECT 'tag'||g FROM generate_series(0,10) g;\nINSERT INTO hyper_ts (time, device_id, tag_id, value) SELECT to_timestamp(g), 'dev' || g, (random() /10)+1, g FROM generate_series(0,30) g;\n\n--one in the future\nINSERT INTO hyper_ts (time, device_id, tag_id, value)  VALUES ('2100-01-01 02:03:04 PST', 'dev101', 1, 0);\n\n--time partitioning function\nCREATE OR REPLACE FUNCTION unix_to_timestamp(unixtime float8)\n    RETURNS TIMESTAMPTZ LANGUAGE SQL IMMUTABLE PARALLEL SAFE STRICT AS\n$BODY$\n    SELECT to_timestamp(unixtime);\n$BODY$;\nCREATE TABLE hyper_timefunc (\"time\" float8 NOT NULL, \"device_id\" text, \"value\" integer);\n\nSELECT create_hypertable('hyper_timefunc', 'time', 'device_id', 4, chunk_time_interval => 10, time_partitioning_func => 'unix_to_timestamp');\n\nINSERT INTO hyper_timefunc (time, device_id, value) SELECT g, 'dev' || g, g FROM generate_series(0,30) g;\n\nCREATE TABLE metrics_timestamp(time timestamp);\nSELECT create_hypertable('metrics_timestamp','time');\nINSERT INTO metrics_timestamp SELECT generate_series('2000-01-01'::timestamp,'2000-02-01'::timestamp,'1d'::interval);\n\nCREATE TABLE metrics_timestamptz(time timestamptz, device_id int);\nSELECT create_hypertable('metrics_timestamptz','time');\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval), 1;\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval), 2;\nINSERT INTO metrics_timestamptz SELECT generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval), 3;\n\n--create a second table to test joins with\nCREATE TABLE metrics_timestamptz_2 (LIKE metrics_timestamptz);\nSELECT create_hypertable('metrics_timestamptz_2','time');\nINSERT INTO metrics_timestamptz_2\nSELECT * FROM metrics_timestamptz;\nINSERT INTO metrics_timestamptz_2 VALUES ('2000-12-01'::timestamptz, 3);\n\nCREATE TABLE metrics_date(time date);\nSELECT create_hypertable('metrics_date','time');\nINSERT INTO metrics_date SELECT generate_series('2000-01-01'::date,'2000-02-01'::date,'1d'::interval);\n\nANALYZE hyper;\nANALYZE hyper_w_space;\nANALYZE tag;\nANALYZE hyper_ts;\nANALYZE hyper_timefunc;\n\n-- create normal table for JOIN tests\nCREATE TABLE regular_timestamptz(time timestamptz);\nINSERT INTO regular_timestamptz SELECT generate_series('2000-01-01'::timestamptz,'2000-02-01'::timestamptz,'1d'::interval);\n"
  },
  {
    "path": "test/sql/include/plan_expand_hypertable_query.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n--we want to see how our logic excludes chunks\n--and not how much work constraint_exclusion does\nSET constraint_exclusion = 'off';\n\n\\qecho test upper bounds\n:PREFIX SELECT * FROM hyper WHERE time < 10 ORDER BY value;\n:PREFIX SELECT * FROM hyper WHERE time < 11 ORDER BY value;\n:PREFIX SELECT * FROM hyper WHERE time = 10 ORDER BY value;\n:PREFIX SELECT * FROM hyper WHERE 10 >= time ORDER BY value;\n\n\\qecho test lower bounds\n:PREFIX SELECT * FROM hyper WHERE time >= 10 and time < 20 ORDER BY value;\n:PREFIX SELECT * FROM hyper WHERE 10 < time and 20 >= time ORDER BY value;\n:PREFIX SELECT * FROM hyper WHERE time >= 9 and time < 20 ORDER BY value;\n:PREFIX SELECT * FROM hyper WHERE time > 9 and time < 20 ORDER BY value;\n\n\\qecho test empty result\n:PREFIX SELECT * FROM hyper WHERE time < 0;\n\n\\qecho test expression evaluation\n:PREFIX SELECT * FROM hyper WHERE time < (5*2)::smallint;\n\n\\qecho test logic at INT64_MAX\n:PREFIX SELECT * FROM hyper WHERE time = 9223372036854775807::bigint ORDER BY value;\n:PREFIX SELECT * FROM hyper WHERE time = 9223372036854775806::bigint ORDER BY value;\n:PREFIX SELECT * FROM hyper WHERE time >= 9223372036854775807::bigint ORDER BY value;\n:PREFIX SELECT * FROM hyper WHERE time > 9223372036854775807::bigint ORDER BY value;\n:PREFIX SELECT * FROM hyper WHERE time > 9223372036854775806::bigint ORDER BY value;\n\n\\qecho cte\n:PREFIX WITH cte AS(\n  SELECT * FROM hyper WHERE time < 10\n)\nSELECT * FROM cte ORDER BY value;\n\n\\qecho subquery\n:PREFIX SELECT 0 = ANY (SELECT value FROM hyper WHERE time < 10);\n\n\\qecho no space constraint\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 ORDER BY value;\n\n\\qecho valid space constraint\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 and device_id = 'dev5' ORDER BY value;\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 and 'dev5' = device_id ORDER BY value;\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 and 'dev'||(2+3) = device_id ORDER BY value;\n\n\\qecho only space constraint\n:PREFIX SELECT * FROM hyper_w_space WHERE 'dev5' = device_id ORDER BY value;\n\n\\qecho unhandled space constraint\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 and device_id > 'dev5' ORDER BY value;\n\n\\qecho use of OR - does not filter chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND (device_id = 'dev5' or device_id = 'dev6') ORDER BY value;\n\n\\qecho cte\n:PREFIX WITH cte AS(\n   SELECT * FROM hyper_w_space WHERE time < 10 and device_id = 'dev5'\n)\nSELECT * FROM cte ORDER BY value;\n\n\\qecho subquery\n:PREFIX SELECT 0 = ANY (SELECT value FROM hyper_w_space WHERE time < 10 and device_id = 'dev5');\n\n\\qecho view\n:PREFIX SELECT * FROM hyper_w_space_view WHERE time < 10 and device_id = 'dev5' ORDER BY value;\n\n\\qecho IN statement - simple\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id IN ('dev5') ORDER BY value;\n\n\\qecho IN statement - two chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id IN ('dev5','dev6') ORDER BY value;\n\n\\qecho IN statement - one chunk\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id IN ('dev4','dev5') ORDER BY value;\n\n\\qecho NOT IN - does not filter chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id NOT IN ('dev5','dev6') ORDER BY value;\n\n\\qecho IN statement with subquery - does not filter chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id IN (SELECT 'dev5'::text) ORDER BY value;\n\n\\qecho ANY\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id = ANY(ARRAY['dev5','dev6']) ORDER BY value;\n\n\\qecho ANY with intersection\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id = ANY(ARRAY['dev5','dev6']) AND device_id = ANY(ARRAY['dev6','dev7']) ORDER BY value;\n\n\\qecho ANY without intersection shouldnt scan any chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND device_id = ANY(ARRAY['dev5','dev6']) AND device_id = ANY(ARRAY['dev8','dev9']) ORDER BY value;\n\n\\qecho ANY/IN/ALL only works for equals operator\n:PREFIX SELECT * FROM hyper_w_space WHERE device_id < ANY(ARRAY['dev5','dev6']) ORDER BY value;\n\n\\qecho ALL with equals and different values shouldnt scan any chunks\n:PREFIX SELECT * FROM hyper_w_space WHERE device_id = ALL(ARRAY['dev5','dev6']) ORDER BY value;\n\n\\qecho Multi AND\n:PREFIX SELECT * FROM hyper_w_space WHERE time < 10 AND time < 100 ORDER BY value;\n\n\\qecho Time dimension doesnt filter chunks when using non-equality IN/ANY with multiple arguments\n:PREFIX SELECT * FROM hyper_w_space WHERE time < ANY(ARRAY[1,2]) ORDER BY value;\n\n\\qecho Time dimension chunk exclusion with IN/ANY equality uses bounding range\n:PREFIX SELECT * FROM hyper WHERE time IN (5, 15) ORDER BY value;\n:PREFIX SELECT * FROM hyper WHERE time = ANY(ARRAY[5, 15, 25]) ORDER BY value;\n:PREFIX SELECT * FROM hyper WHERE time = ANY(ARRAY[25, 15, 5]) ORDER BY value;\n:PREFIX SELECT * FROM hyper_w_space WHERE time IN (5, 15) ORDER BY value;\n:PREFIX SELECT * FROM metrics_timestamp WHERE time IN ('2000-01-05'::timestamp, '2000-01-15'::timestamp) ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time IN ('2000-01-05'::timestamptz, '2000-01-15'::timestamptz) ORDER BY time;\n:PREFIX SELECT * FROM metrics_date WHERE time IN ('2000-01-05'::date, '2000-01-15'::date) ORDER BY time;\n\n\\qecho cross-type IN/ANY: timestamp to timestamptz column does not use bounding range (stable cast)\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time IN ('2000-01-05'::timestamp, '2000-01-15'::timestamp) ORDER BY time;\n\n\\qecho Time dimension chunk filtering works for ANY with single argument\n:PREFIX SELECT * FROM hyper_w_space WHERE time < ANY(ARRAY[1]) ORDER BY value;\n\n\\qecho Time dimension chunk filtering works for ALL with single argument\n:PREFIX SELECT * FROM hyper_w_space WHERE time < ALL(ARRAY[1]) ORDER BY value;\n\n\\qecho Time dimension chunk filtering works for ALL with multiple arguments\n:PREFIX SELECT * FROM hyper_w_space WHERE time < ALL(ARRAY[1,10,20,30]) ORDER BY value;\n\n\\qecho AND intersection using IN and EQUALS\n:PREFIX SELECT * FROM hyper_w_space WHERE device_id IN ('dev1','dev2') AND device_id = 'dev1' ORDER BY value;\n\n\\qecho AND with no intersection using IN and EQUALS\n:PREFIX SELECT * FROM hyper_w_space WHERE device_id IN ('dev1','dev2') AND device_id = 'dev3' ORDER BY value;\n\n\\qecho timestamps\n\\qecho these should work since they are immutable functions\n:PREFIX SELECT * FROM hyper_ts WHERE time < 'Wed Dec 31 16:00:10 1969 PST'::timestamptz ORDER BY value;\n:PREFIX SELECT * FROM hyper_ts WHERE time < to_timestamp(10) ORDER BY value;\n:PREFIX SELECT * FROM hyper_ts WHERE time < 'Wed Dec 31 16:00:10 1969'::timestamp AT TIME ZONE 'PST' ORDER BY value;\n:PREFIX SELECT * FROM hyper_ts WHERE time < to_timestamp(10) and device_id = 'dev1' ORDER BY value;\n\n\\qecho these should not work since uses stable functions;\n:PREFIX SELECT * FROM hyper_ts WHERE time < 'Wed Dec 31 16:00:10 1969'::timestamp ORDER BY value;\n:PREFIX SELECT * FROM hyper_ts WHERE time < ('Wed Dec 31 16:00:10 1969'::timestamp::timestamptz) ORDER BY value;\n:PREFIX SELECT * FROM hyper_ts WHERE NOW() < time ORDER BY value;\n\n\\qecho joins\n:PREFIX SELECT * FROM hyper_ts WHERE tag_id IN (SELECT id FROM tag WHERE tag.id=1) and time < to_timestamp(10) and device_id = 'dev1' ORDER BY value;\n:PREFIX SELECT * FROM hyper_ts WHERE tag_id IN (SELECT id FROM tag WHERE tag.id=1) or (time < to_timestamp(10) and device_id = 'dev1') ORDER BY value;\n:PREFIX SELECT * FROM hyper_ts WHERE tag_id IN (SELECT id FROM tag WHERE tag.name='tag1') and time < to_timestamp(10) and device_id = 'dev1' ORDER BY value;\n:PREFIX SELECT * FROM hyper_ts JOIN tag on (hyper_ts.tag_id = tag.id ) WHERE time < to_timestamp(10) and device_id = 'dev1' ORDER BY value;\n:PREFIX SELECT * FROM hyper_ts JOIN tag on (hyper_ts.tag_id = tag.id ) WHERE tag.name = 'tag1' and time < to_timestamp(10) and device_id = 'dev1' ORDER BY value;\n\n\\qecho test constraint exclusion for constraints in ON clause of JOINs\n\\qecho should exclude chunks on m1 and propagate qual to m2 because of INNER JOIN\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m1.time < '2000-01-10' ORDER BY m1.time;\n\n\\qecho should exclude chunks on m2 and propagate qual to m1 because of INNER JOIN\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time < '2000-01-10' ORDER BY m1.time;\n\n\\qecho must not exclude on m1\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m1.time < '2000-01-10' ORDER BY m1.time;\n\n\\qecho should exclude chunks on m2\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time < '2000-01-10' ORDER BY m1.time;\n\n\\qecho should exclude chunks on m1\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m1.time < '2000-01-10' ORDER BY m1.time;\n\n\\qecho must not exclude chunks on m2\n:PREFIX SELECT m1.time,m2.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time < '2000-01-10' ORDER BY m1.time, m2.time;\n\n\\qecho time_bucket exclusion\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time) < 10::bigint ORDER BY time;\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time) < 11::bigint ORDER BY time;\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time) <= 10::bigint ORDER BY time;\n:PREFIX SELECT * FROM hyper WHERE 10::bigint > time_bucket(10, time) ORDER BY time;\n:PREFIX SELECT * FROM hyper WHERE 11::bigint > time_bucket(10, time) ORDER BY time;\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time, 5) < 10::bigint ORDER BY time;\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time, 5) < 11::bigint ORDER BY time;\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time, 5) <= 10::bigint ORDER BY time;\n:PREFIX SELECT * FROM hyper WHERE 10::bigint > time_bucket(10, time, 5) ORDER BY time;\n:PREFIX SELECT * FROM hyper WHERE 11::bigint > time_bucket(10, time, 5) ORDER BY time;\n\n\\qecho timestamp time_bucket exclusion\nSELECT count(DISTINCT tableoid) FROM metrics_timestamp;\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time) < '2000-01-05' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time) <= '2000-01-05' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time) > '2000-01-25' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time) >= '2000-01-15' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'3d'::interval) < '2000-01-05' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'3d'::interval) <= '2000-01-05' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'3d'::interval) > '2000-01-25' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'3d'::interval) >= '2000-01-25' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'2000-01-10'::timestamp) < '2000-01-05' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'2000-01-10'::timestamp) <= '2000-01-05' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'2000-01-10'::timestamp) > '2000-01-25' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('7d',time,'2000-01-10'::timestamp) >= '2000-01-25' ORDER BY time;\n\n\\qecho timestamptz time_bucket exclusion\nSELECT count(DISTINCT tableoid) FROM metrics_timestamptz;\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time) < '2000-01-05' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time) <= '2000-01-05' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time) > '2000-01-25' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time) >= '2000-01-25' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'3d'::interval) < '2000-01-05' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'3d'::interval) <= '2000-01-05' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'3d'::interval) > '2000-01-25' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'3d'::interval) >= '2000-01-25' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'2000-01-10'::timestamptz) < '2000-01-05' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'2000-01-10'::timestamptz) <= '2000-01-05' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'2000-01-10'::timestamptz) > '2000-01-25' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'2000-01-10'::timestamptz) >= '2000-01-25' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'Europe/Berlin') < '2000-01-05' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'Europe/Berlin') <= '2000-01-05' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'Europe/Berlin') > '2000-01-25' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamptz WHERE time_bucket('7d',time,'Europe/Berlin') >= '2000-01-25' ORDER BY time;\n\n\\qecho test overflow behaviour of time_bucket exclusion\n:PREFIX SELECT * FROM hyper WHERE time > 950 AND time_bucket(10, time) < '9223372036854775807'::bigint ORDER BY time;\n\n\\qecho test timestamp upper boundary\n\\qecho there should be no transformation if we are out of the supported (TimescaleDB-specific) range\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('1d',time) < '294276-01-01'::timestamp ORDER BY time;\n\\qecho transformation would be out of range\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('1000d',time) < '294276-01-01'::timestamp ORDER BY time;\n\n\\qecho test timestamptz upper boundary\n\\qecho there should be no transformation if we are out of the supported (TimescaleDB-specific) range\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('1d',time) < '294276-01-01'::timestamptz ORDER BY time;\n\\qecho transformation would be out of range\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('1000d',time) < '294276-01-01'::timestamptz ORDER BY time;\n\n\n\\qecho time_bucket exclusion with run-time constants\n-- These queries have a stable time_bucket expression, because the text::interval conversion is stable.\nPREPARE P1(text    , text) AS SELECT * FROM metrics_timestamptz WHERE time_bucket($1::interval, time) > $2::timestamptz ORDER BY time;\nPREPARE P2(text    , text) AS SELECT * FROM metrics_timestamp   WHERE time_bucket($1::interval, time) > $2::timestamptz ORDER BY time;\nPREPARE P3(text    , text) AS SELECT * FROM metrics_timestamptz WHERE time_bucket($1::interval, time) > $2::timestamp   ORDER BY time;\nPREPARE P4(text    , text) AS SELECT * FROM metrics_timestamp   WHERE time_bucket($1::interval, time) > $2::timestamp   ORDER BY time;\n-- These queries have an immutable time_bucket expression, because the parameter is passed as interval, and no conversion is involved.\nPREPARE P5(interval, text) AS SELECT * FROM metrics_timestamptz WHERE time_bucket($1::interval, time) > $2::timestamptz ORDER BY time;\nPREPARE P6(interval, text) AS SELECT * FROM metrics_timestamp   WHERE time_bucket($1::interval, time) > $2::timestamptz ORDER BY time;\nPREPARE P7(interval, text) AS SELECT * FROM metrics_timestamptz WHERE time_bucket($1::interval, time) > $2::timestamp   ORDER BY time;\nPREPARE P8(interval, text) AS SELECT * FROM metrics_timestamp   WHERE time_bucket($1::interval, time) > $2::timestamp   ORDER BY time;\n\nSET plan_cache_mode TO 'force_custom_plan';\n\n:PREFIX EXECUTE P1('60 mins', '2024-01-01 UTC');\n:PREFIX EXECUTE P2('60 mins', '2024-01-01 UTC');\n:PREFIX EXECUTE P3('60 mins', '2024-01-01 UTC');\n:PREFIX EXECUTE P4('60 mins', '2024-01-01 UTC');\n:PREFIX EXECUTE P5('60 mins', '2024-01-01 UTC');\n:PREFIX EXECUTE P6('60 mins', '2024-01-01 UTC');\n:PREFIX EXECUTE P7('60 mins', '2024-01-01 UTC');\n:PREFIX EXECUTE P8('60 mins', '2024-01-01 UTC');\n\n:PREFIX EXECUTE P1('60 mins', '2000-01-01 UTC');\n:PREFIX EXECUTE P2('60 mins', '2000-01-01 UTC');\n:PREFIX EXECUTE P3('60 mins', '2000-01-01 UTC');\n:PREFIX EXECUTE P4('60 mins', '2000-01-01 UTC');\n:PREFIX EXECUTE P5('60 mins', '2000-01-01 UTC');\n:PREFIX EXECUTE P6('60 mins', '2000-01-01 UTC');\n:PREFIX EXECUTE P7('60 mins', '2000-01-01 UTC');\n:PREFIX EXECUTE P8('60 mins', '2000-01-01 UTC');\n\nSET plan_cache_mode TO 'force_generic_plan';\n\n:PREFIX EXECUTE P1('60 mins', '2024-01-01 UTC');\n:PREFIX EXECUTE P2('60 mins', '2024-01-01 UTC');\n:PREFIX EXECUTE P3('60 mins', '2024-01-01 UTC');\n:PREFIX EXECUTE P4('60 mins', '2024-01-01 UTC');\n:PREFIX EXECUTE P5('60 mins', '2024-01-01 UTC');\n:PREFIX EXECUTE P6('60 mins', '2024-01-01 UTC');\n:PREFIX EXECUTE P7('60 mins', '2024-01-01 UTC');\n:PREFIX EXECUTE P8('60 mins', '2024-01-01 UTC');\n\n:PREFIX EXECUTE P1('60 mins', '2000-01-01 UTC');\n:PREFIX EXECUTE P2('60 mins', '2000-01-01 UTC');\n:PREFIX EXECUTE P3('60 mins', '2000-01-01 UTC');\n:PREFIX EXECUTE P4('60 mins', '2000-01-01 UTC');\n:PREFIX EXECUTE P5('60 mins', '2000-01-01 UTC');\n:PREFIX EXECUTE P6('60 mins', '2000-01-01 UTC');\n:PREFIX EXECUTE P7('60 mins', '2000-01-01 UTC');\n:PREFIX EXECUTE P8('60 mins', '2000-01-01 UTC');\n\nRESET plan_cache_mode;\nDEALLOCATE P1;\nDEALLOCATE P2;\nDEALLOCATE P3;\nDEALLOCATE P4;\nDEALLOCATE P5;\nDEALLOCATE P6;\nDEALLOCATE P7;\nDEALLOCATE P8;\n\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time) > 10 AND time_bucket(10, time) < 100 ORDER BY time;\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10, time) > 10 AND time_bucket(10, time) < 20 ORDER BY time;\n:PREFIX SELECT * FROM hyper WHERE time_bucket(1, time) > 11 AND time_bucket(1, time) < 19 ORDER BY time;\n:PREFIX SELECT * FROM hyper WHERE 10 < time_bucket(10, time) AND 20 > time_bucket(10,time) ORDER BY time;\n\n\\qecho time_bucket exclusion with date\n:PREFIX SELECT * FROM metrics_date WHERE time_bucket('1d',time) < '2000-01-03' ORDER BY time;\n:PREFIX SELECT * FROM metrics_date WHERE time_bucket('1d',time) >= '2000-01-03' AND time_bucket('1d',time) <= '2000-01-10' ORDER BY time;\n\n\\qecho time_bucket exclusion with timestamp\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('1d',time) < '2000-01-03' ORDER BY time;\n:PREFIX SELECT * FROM metrics_timestamp WHERE time_bucket('1d',time) >= '2000-01-03' AND time_bucket('1d',time) <= '2000-01-10' ORDER BY time;\n\n\\qecho time_bucket exclusion with timestamptz\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('6h',time) < '2000-01-03' ORDER BY time;\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('6h',time) >= '2000-01-03' AND time_bucket('6h',time) <= '2000-01-10' ORDER BY time;\n\n\\qecho time_bucket exclusion with timestamptz and day interval\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('1d',time) < '2000-01-03' ORDER BY time;\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('1d',time) >= '2000-01-03' AND time_bucket('1d',time) <= '2000-01-10' ORDER BY time;\n:PREFIX SELECT time FROM metrics_timestamptz WHERE time_bucket('1d',time) >= '2000-01-03' AND time_bucket('7d',time) <= '2000-01-10' ORDER BY time;\n\n\\qecho no transformation\n:PREFIX SELECT * FROM hyper WHERE time_bucket(10 + floor(random())::int, time) > 10 AND time_bucket(10 + floor(random())::int, time) < 100 AND time < 150 ORDER BY time;\n\n\\qecho exclude chunks based on time column with partitioning function. This\n\\qecho transparently applies the time partitioning function on the time\n\\qecho value to be able to exclude chunks (similar to a closed dimension).\n:PREFIX SELECT * FROM hyper_timefunc WHERE time < 4 ORDER BY value;\n\\qecho excluding based on time expression is currently unoptimized\n:PREFIX SELECT * FROM hyper_timefunc WHERE unix_to_timestamp(time) < 'Wed Dec 31 16:00:04 1969 PST' ORDER BY value;\n\n\\qecho test qual propagation for joins\nRESET constraint_exclusion;\n\n\\qecho nothing to propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1, metrics_timestamptz_2 m2 WHERE m1.time = m2.time ORDER BY m1.time;\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time ORDER BY m1.time;\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time ORDER BY m1.time;\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time ORDER BY m1.time;\n\n\\qecho OR constraints should not propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10' OR m1.time > '2001-01-01' ORDER BY m1.time;\n\n\\qecho test single constraint\n\\qecho constraint should be on both scans\n\\qecho these will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1, metrics_timestamptz_2 m2 WHERE m1.time = m2.time AND m1.time < '2000-01-10' ORDER BY m1.time;\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10' ORDER BY m1.time;\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10' ORDER BY m1.time;\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10' ORDER BY m1.time;\n\n\\qecho test 2 constraints on single relation\n\\qecho these will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1, metrics_timestamptz_2 m2 WHERE m1.time = m2.time AND m1.time > '2000-01-01' AND m1.time < '2000-01-10' ORDER BY m1.time;\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m1.time < '2000-01-10' ORDER BY m1.time;\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m1.time < '2000-01-10' ORDER BY m1.time;\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m1.time < '2000-01-10' ORDER BY m1.time;\n\n\\qecho test 2 constraints with 1 constraint on each relation\n\\qecho these will propagate even for LEFT/RIGHT JOIN because the constraints are not in the ON clause and therefore imply a NOT NULL condition on the JOIN column\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1, metrics_timestamptz_2 m2 WHERE m1.time = m2.time AND m1.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n\n\\qecho test constraints in ON clause of INNER JOIN\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n\n\\qecho test constraints in ON clause of LEFT JOIN\n\\qecho must not propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 LEFT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n\n\\qecho test constraints in ON clause of RIGHT JOIN\n\\qecho must not propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 RIGHT JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time AND m2.time > '2000-01-01' AND m2.time < '2000-01-10' ORDER BY m1.time;\n\n\\qecho test equality condition not in ON clause\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON true WHERE m2.time = m1.time AND m2.time < '2000-01-10' ORDER BY m1.time;\n\n\\qecho test constraints not joined on\n\\qecho device_id constraint must not propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON true WHERE m2.time = m1.time AND m2.time < '2000-01-10' AND m1.device_id = 1 ORDER BY m1.time;\n\n\\qecho test multiple join conditions\n\\qecho device_id constraint should propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON true WHERE m2.time = m1.time AND m1.device_id = m2.device_id AND m2.time < '2000-01-10' AND m1.device_id = 1 ORDER BY m1.time;\n\n\\qecho test join with 3 tables\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time INNER JOIN metrics_timestamptz m3 ON m2.time=m3.time WHERE m1.time > '2000-01-01' AND m1.time < '2000-01-10' ORDER BY m1.time;\n\n\\qecho test non-Const constraints\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10'::text::timestamptz ORDER BY m1.time;\n\n\\qecho test now()\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < now() ORDER BY m1.time;\n\n\\qecho test volatile function\n\\qecho should not propagate\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m1.time < clock_timestamp() ORDER BY m1.time;\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN metrics_timestamptz_2 m2 ON m1.time = m2.time WHERE m2.time < clock_timestamp() ORDER BY m1.time;\n\n\\qecho test JOINs with normal table\n\\qecho will not propagate because constraints are only added to hypertables\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN regular_timestamptz m2 ON m1.time = m2.time WHERE m1.time < '2000-01-10' ORDER BY m1.time;\n\n\\qecho test JOINs with normal table\n:PREFIX SELECT m1.time FROM metrics_timestamptz m1 INNER JOIN regular_timestamptz m2 ON m1.time = m2.time WHERE m2.time < '2000-01-10' ORDER BY m1.time;\n\n\\qecho test quals are not pushed into OUTER JOIN\nCREATE TABLE outer_join_1 (id int, name text,time timestamptz NOT NULL DEFAULT '2000-01-01');\nCREATE TABLE outer_join_2 (id int, name text,time timestamptz NOT NULL DEFAULT '2000-01-01');\n\nSELECT (SELECT table_name FROM create_hypertable(tbl, 'time')) FROM (VALUES ('outer_join_1'),('outer_join_2')) v(tbl);\n\nINSERT INTO outer_join_1 VALUES(1,'a'), (2,'b');\nINSERT INTO outer_join_2 VALUES(1,'a');\n\n:PREFIX SELECT one.id, two.name FROM outer_join_1 one LEFT OUTER JOIN outer_join_2 two ON one.id=two.id WHERE one.id=2;\n:PREFIX SELECT one.id, two.name FROM outer_join_2 two RIGHT OUTER JOIN outer_join_1 one ON one.id=two.id WHERE one.id=2;\n\nDROP TABLE outer_join_1;\nDROP TABLE outer_join_2;\n\n-- test UNION between regular table and hypertable\nSELECT time FROM regular_timestamptz UNION SELECT time FROM metrics_timestamptz ORDER BY 1;\n\n-- test UNION ALL between regular table and hypertable\nSELECT time FROM regular_timestamptz UNION ALL SELECT time FROM metrics_timestamptz ORDER BY 1;\n\n-- test nested join qual propagation\n:PREFIX\nSELECT * FROM (\nSELECT o1_m1.time FROM metrics_timestamptz o1_m1 INNER JOIN metrics_timestamptz_2 o1_m2 ON true WHERE o1_m2.time = o1_m1.time AND o1_m1.device_id = o1_m2.device_id AND o1_m2.time < '2000-01-10' AND o1_m1.device_id = 1\n) o1 FULL OUTER JOIN (\nSELECT o2_m1.time FROM metrics_timestamptz o2_m1 FULL OUTER JOIN metrics_timestamptz_2 o2_m2 ON true WHERE o2_m2.time = o2_m1.time AND o2_m1.device_id = o2_m2.device_id AND o2_m2.time > '2000-01-20' AND o2_m1.device_id = 2\n) o2 ON o1.time = o2.time ORDER BY 1,2;\n\n:PREFIX\nSELECT * FROM (\nSELECT o1_m1.time FROM metrics_timestamptz o1_m1 INNER JOIN metrics_timestamptz_2 o1_m2 ON o1_m2.time = o1_m1.time AND o1_m1.device_id = o1_m2.device_id WHERE o1_m2.time < '2000-01-10' AND o1_m1.device_id = 1\n) o1 FULL OUTER JOIN (\nSELECT o2_m1.time FROM metrics_timestamptz o2_m1 FULL OUTER JOIN metrics_timestamptz_2 o2_m2 ON o2_m2.time = o2_m1.time AND o2_m1.device_id = o2_m2.device_id WHERE o2_m2.time > '2000-01-20' AND o2_m1.device_id = 2\n) o2 ON o1.time = o2.time ORDER BY 1,2;\n"
  },
  {
    "path": "test/sql/include/plan_hashagg_load.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE TABLE metric (id SERIAL PRIMARY KEY, value INT);\nCREATE TABLE hyper(time TIMESTAMP NOT NULL, time_int BIGINT, time_broken DATE, metricid int, value double precision);\nCREATE TABLE regular(time TIMESTAMP NOT NULL, time_int BIGINT, time_date DATE, metricid int, value double precision);\n\nSELECT create_hypertable('hyper', 'time', chunk_time_interval => interval '20 day', create_default_indexes=>FALSE);\n\nALTER TABLE hyper\nDROP COLUMN time_broken,\nADD COLUMN time_date DATE;\n\nINSERT INTO metric(value) SELECT random()*100 FROM generate_series(0,10);\n\nINSERT INTO hyper SELECT t,  EXTRACT(EPOCH FROM t), (EXTRACT(EPOCH FROM t)::int % 10)+1, 1.0, t::date FROM generate_series('2001-01-01', '2001-01-10', INTERVAL '1 second') t;\nINSERT INTO regular(time, time_int, time_date, metricid, value)\n  SELECT t,  EXTRACT(EPOCH FROM t), t::date, (EXTRACT(EPOCH FROM t)::int % 10) + 1, 1.0 FROM generate_series('2001-01-01', '2001-01-02', INTERVAL '1 second') t;\n\n--test some queries before analyze;\nEXPLAIN (buffers off, costs off) SELECT time_bucket('1 minute', time) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n\nEXPLAIN (buffers off, costs off) SELECT date_trunc('minute', time) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n\n-- Test partitioning function on an open (time) dimension\nCREATE OR REPLACE FUNCTION unix_to_timestamp(unixtime float8)\n    RETURNS TIMESTAMPTZ LANGUAGE SQL IMMUTABLE AS\n$BODY$\n    SELECT to_timestamp(unixtime);\n$BODY$;\nCREATE TABLE hyper_timefunc(time float8 NOT NULL, metricid int, VALUE double precision, time_date DATE);\n\nSELECT create_hypertable('hyper_timefunc', 'time', chunk_time_interval => interval '20 day', create_default_indexes=>FALSE, time_partitioning_func => 'unix_to_timestamp');\nINSERT INTO hyper_timefunc SELECT time_int, metricid, VALUE, time_date FROM hyper;\n\nANALYZE metric;\nANALYZE hyper;\nANALYZE regular;\nANALYZE hyper_timefunc;\n"
  },
  {
    "path": "test/sql/include/plan_hashagg_query.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n:PREFIX SELECT time_bucket('1 minute', time) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n\n:PREFIX SELECT time_bucket('1 hour', time) AS MetricMinuteTs, metricid, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs, metricid\nORDER BY MetricMinuteTs DESC, metricid;\n\n--should be too many groups will not hashaggregate\n:PREFIX SELECT time_bucket('1 second', time) AS MetricMinuteTs, metricid, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs, metricid\nORDER BY MetricMinuteTs DESC, metricid;\n\n:PREFIX SELECT time_bucket('1 minute', time, INTERVAL '30 seconds') AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n\n:PREFIX SELECT time_bucket(60, time_int) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n\n:PREFIX SELECT time_bucket(60, time_int, 10) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n\n:PREFIX SELECT time_bucket('1 day', time_date) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n\n:PREFIX SELECT date_trunc('minute', time) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n\n\\set ON_ERROR_STOP 0\n--can't optimize invalid time unit\n:PREFIX SELECT date_trunc('invalid', time) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n\\set ON_ERROR_STOP 1\n\n:PREFIX SELECT date_trunc('day', time_date) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n\n--joins\n--with hypertable, optimize\n:PREFIX SELECT time_bucket(3600, time_int, 10) AS MetricMinuteTs, metric.value, AVG(hyper.value) as avg\nFROM hyper\nJOIN metric ON (hyper.metricid = metric.id)\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs, metric.id\nORDER BY MetricMinuteTs DESC, metric.id;\n\n--no hypertable involved, no optimization\n:PREFIX SELECT time_bucket(3600, time_int, 10) AS MetricMinuteTs, metric.value, AVG(regular.value) as avg\nFROM regular\nJOIN metric ON (regular.metricid = metric.id)\nWHERE time >= '2001-01-04T00:00:00' AND time <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs, metric.id\nORDER BY MetricMinuteTs DESC, metric.id;\n\n-- Try with time partitioning function. Currently not optimized for hash aggregates\n:PREFIX SELECT time_bucket('1 minute', unix_to_timestamp(time)) AS MetricMinuteTs, AVG(value) as avg\nFROM hyper_timefunc\nWHERE unix_to_timestamp(time) >= '2001-01-04T00:00:00' AND unix_to_timestamp(time) <= '2001-01-05T01:00:00'\nGROUP BY MetricMinuteTs\nORDER BY MetricMinuteTs DESC;\n"
  },
  {
    "path": "test/sql/include/plan_ordered_append_load.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- create a now() function for repeatable testing that always returns\n-- the same timestamp. It needs to be marked STABLE\nCREATE OR REPLACE FUNCTION now_s()\nRETURNS timestamptz LANGUAGE PLPGSQL STABLE AS\n$BODY$\nBEGIN\n    RETURN '2000-01-08T0:00:00+0'::timestamptz;\nEND;\n$BODY$;\n\nCREATE TABLE devices(device_id INT PRIMARY KEY, name TEXT);\nINSERT INTO devices VALUES\n(1,'Device 1'),\n(2,'Device 2'),\n(3,'Device 3');\n\n-- create a second table where we create chunks in reverse order\nCREATE TABLE ordered_append_reverse(time timestamptz NOT NULL, device_id INT, value float);\nSELECT create_hypertable('ordered_append_reverse','time');\n\nINSERT INTO ordered_append_reverse SELECT generate_series('2000-01-18'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 0.5;\n\n-- table where dimension column is last column\nCREATE TABLE IF NOT EXISTS dimension_last(\n    id INT8 NOT NULL,\n    device_id INT NOT NULL,\n    name TEXT NOT NULL,\n    time timestamptz NOT NULL\n);\n\nSELECT create_hypertable('dimension_last', 'time', chunk_time_interval => interval '1day', if_not_exists => True);\n\n-- table with only dimension column\nCREATE TABLE IF NOT EXISTS dimension_only(\n    time timestamptz NOT NULL\n);\n\nSELECT create_hypertable('dimension_only', 'time', chunk_time_interval => interval '1day', if_not_exists => True);\n\nINSERT INTO dimension_last SELECT 1,1,'Device 1',generate_series('2000-01-01 0:00:00+0'::timestamptz,'2000-01-04 23:59:00+0'::timestamptz,'1m'::interval);\n\nINSERT INTO dimension_only VALUES\n('2000-01-01'),\n('2000-01-03'),\n('2000-01-05'),\n('2000-01-07');\n\nANALYZE devices;\nANALYZE ordered_append_reverse;\nANALYZE dimension_last;\nANALYZE dimension_only;\n\n-- create hypertable with indexes not on all chunks\nCREATE TABLE ht_missing_indexes(time timestamptz NOT NULL, device_id int, value float);\n\nSELECT create_hypertable('ht_missing_indexes','time');\n\nINSERT INTO ht_missing_indexes SELECT generate_series('2000-01-01'::timestamptz,'2000-01-18'::timestamptz,'1m'::interval), 1, 0.5;\nINSERT INTO ht_missing_indexes SELECT generate_series('2000-01-01'::timestamptz,'2000-01-18'::timestamptz,'1m'::interval), 2, 1.5;\nINSERT INTO ht_missing_indexes SELECT generate_series('2000-01-01'::timestamptz,'2000-01-18'::timestamptz,'1m'::interval), 3, 2.5;\n\n-- drop index from 2nd chunk of ht_missing_indexes\nSELECT format('%I.%I',i.schemaname,i.indexname) AS \"INDEX_NAME\"\nFROM _timescaledb_catalog.chunk c\nINNER JOIN _timescaledb_catalog.hypertable ht ON c.hypertable_id = ht.id\nINNER JOIN pg_indexes i ON i.schemaname = c.schema_name AND i.tablename=c.table_name\nWHERE ht.table_name = 'ht_missing_indexes'\nORDER BY c.id LIMIT 1 OFFSET 1 \\gset\n\nDROP INDEX :INDEX_NAME;\n\nANALYZE ht_missing_indexes;\n\n-- create hypertable with with dropped columns\nCREATE TABLE ht_dropped_columns(c1 int, c2 int, c3 int, c4 int, c5 int, time timestamptz NOT NULL, device_id int, value float);\n\nSELECT create_hypertable('ht_dropped_columns','time');\n\nALTER TABLE ht_dropped_columns DROP COLUMN c1;\nINSERT INTO ht_dropped_columns(time,device_id,value) SELECT generate_series('2000-01-01'::timestamptz,'2000-01-02'::timestamptz,'1m'::interval), 1, 0.5;\nALTER TABLE ht_dropped_columns DROP COLUMN c2;\nINSERT INTO ht_dropped_columns(time,device_id,value) SELECT generate_series('2000-01-08'::timestamptz,'2000-01-09'::timestamptz,'1m'::interval), 1, 0.5;\nALTER TABLE ht_dropped_columns DROP COLUMN c3;\nINSERT INTO ht_dropped_columns(time,device_id,value) SELECT generate_series('2000-01-15'::timestamptz,'2000-01-16'::timestamptz,'1m'::interval), 1, 0.5;\nALTER TABLE ht_dropped_columns DROP COLUMN c4;\nINSERT INTO ht_dropped_columns(time,device_id,value) SELECT generate_series('2000-01-22'::timestamptz,'2000-01-23'::timestamptz,'1m'::interval), 1, 0.5;\nALTER TABLE ht_dropped_columns DROP COLUMN c5;\nINSERT INTO ht_dropped_columns(time,device_id,value) SELECT generate_series('2000-01-29'::timestamptz,'2000-01-30'::timestamptz,'1m'::interval), 1, 0.5;\n\nANALYZE ht_dropped_columns;\n\nCREATE TABLE space2(time timestamptz NOT NULL, device_id int NOT NULL, tag_id int NOT NULL, value float);\nSELECT create_hypertable('space2','time','device_id',number_partitions:=3);\nSELECT add_dimension('space2','tag_id',number_partitions:=3);\n\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 1, 1.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 1, 2.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 3, 1, 3.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 2, 1.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 2, 2.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 3, 2, 3.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 3, 1.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 3, 2.5;\nINSERT INTO space2 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 3, 3, 3.5;\n\nANALYZE space2;\n\nCREATE TABLE space3(time timestamptz NOT NULL, x int NOT NULL, y int NOT NULL, z int NOT NULL, value float);\nSELECT create_hypertable('space3','time','x',number_partitions:=2);\nSELECT add_dimension('space3','y',number_partitions:=2);\nSELECT add_dimension('space3','z',number_partitions:=2);\n\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 1, 1, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 1, 2, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 2, 1, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 1, 2, 2, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 1, 1, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 1, 2, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 2, 1, 1.5;\nINSERT INTO space3 SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 2, 2, 2, 1.5;\n\nANALYZE space3;\n\nCREATE TABLE sortopt_test(time timestamptz NOT NULL, device TEXT);\nSELECT create_hypertable('sortopt_test','time',create_default_indexes:=false);\n\n-- since alpine does not support locales we cant test collations in our ci\n-- CREATE COLLATION IF NOT EXISTS en_US(LOCALE='en_US.utf8');\n-- CREATE INDEX time_device_utf8 ON sortopt_test(time, device COLLATE \"en_US\");\n\nCREATE INDEX time_device_nullsfirst ON sortopt_test(time, device NULLS FIRST);\nCREATE INDEX time_device_nullslast ON sortopt_test(time, device DESC NULLS LAST);\n\nINSERT INTO sortopt_test SELECT generate_series('2000-01-10'::timestamptz,'2000-01-01'::timestamptz,'-1m'::interval), 'Device 1';\n\nANALYZE sortopt_test;\n\n"
  },
  {
    "path": "test/sql/include/plan_ordered_append_query.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- print chunks ordered by time to ensure ordering we want\nSELECT\n  ht.table_name AS hypertable,\n  c.table_name AS chunk,\n  ds.range_start\nFROM\n  _timescaledb_catalog.chunk c\n  INNER JOIN LATERAL(SELECT * FROM _timescaledb_catalog.chunk_constraint cc WHERE c.id = cc.chunk_id ORDER BY cc.dimension_slice_id LIMIT 1) cc ON true\n  INNER JOIN _timescaledb_catalog.dimension_slice ds ON ds.id=cc.dimension_slice_id\n  INNER JOIN _timescaledb_catalog.dimension d ON ds.dimension_id = d.id\n  INNER JOIN _timescaledb_catalog.hypertable ht ON d.hypertable_id = ht.id\nORDER BY ht.table_name, range_start, chunk;\n\n-- test ASC for reverse ordered chunks\n:PREFIX SELECT\n  time, device_id, value\nFROM ordered_append_reverse\nORDER BY time ASC LIMIT 1;\n\n-- test DESC for reverse ordered chunks\n:PREFIX SELECT\n  time, device_id, value\nFROM ordered_append_reverse\nORDER BY time DESC LIMIT 1;\n\n-- test query with ORDER BY time_bucket, device_id\n-- must not use ordered append\n:PREFIX SELECT\n  time_bucket('1d',time), device_id, name\nFROM dimension_last\nORDER BY time_bucket('1d',time), device_id LIMIT 1;\n\n-- test query with ORDER BY date_trunc, device_id\n-- must not use ordered append\n:PREFIX SELECT\n  date_trunc('day',time), device_id, name\nFROM dimension_last\nORDER BY 1,2 LIMIT 1;\n\n-- test with table with only dimension column\n:PREFIX SELECT * FROM dimension_only ORDER BY time DESC LIMIT 1;\n\n-- test LEFT JOIN against hypertable\n:PREFIX_NO_ANALYZE SELECT *\nFROM dimension_last\nLEFT JOIN dimension_only USING (time)\nORDER BY dimension_last.time DESC\nLIMIT 2;\n\n-- test INNER JOIN against non-hypertable\n:PREFIX_NO_ANALYZE SELECT *\nFROM dimension_last\nINNER JOIN dimension_only USING (time)\nORDER BY dimension_last.time DESC\nLIMIT 2;\n\n-- test join against non-hypertable\n:PREFIX SELECT *\nFROM dimension_last\nINNER JOIN devices USING(device_id)\nORDER BY dimension_last.time DESC\nLIMIT 2;\n\n-- test hypertable with index missing on one chunk\n:PREFIX SELECT\n  time, device_id, value\nFROM ht_missing_indexes\nORDER BY time ASC LIMIT 1;\n\n-- test hypertable with index missing on one chunk\n-- and no data\n:PREFIX SELECT\n  time, device_id, value\nFROM ht_missing_indexes\nWHERE device_id = 2\nORDER BY time DESC LIMIT 1;\n\n-- test hypertable with index missing on one chunk\n-- and no data\n:PREFIX SELECT\n  time, device_id, value\nFROM ht_missing_indexes\nWHERE time > '2000-01-07'\nORDER BY time LIMIT 10;\n\n-- test hypertable with dropped columns\n:PREFIX SELECT\n  time, device_id, value\nFROM ht_dropped_columns\nORDER BY time ASC LIMIT 1;\n\n-- test hypertable with dropped columns\n:PREFIX SELECT\n  time, device_id, value\nFROM ht_dropped_columns\nWHERE device_id = 1\nORDER BY time DESC;\n\n-- test hypertable with 2 space dimensions\n:PREFIX SELECT\n  time, device_id, value\nFROM space2\nORDER BY time DESC;\n\n-- test hypertable with 3 space dimensions\n:PREFIX SELECT\n  time\nFROM space3\nORDER BY time DESC;\n\n-- test COLLATION\n-- cant be tested in our ci because alpine doesnt support locales\n-- :PREFIX SELECT * FROM sortopt_test ORDER BY time, device COLLATE \"en_US.utf8\";\n\n-- test NULLS FIRST\n:PREFIX SELECT * FROM sortopt_test ORDER BY time, device NULLS FIRST;\n\n-- test NULLS LAST\n:PREFIX SELECT * FROM sortopt_test ORDER BY time, device DESC NULLS LAST;\n"
  },
  {
    "path": "test/sql/include/query_load.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE TABLE PUBLIC.hyper_1 (\n  time TIMESTAMP NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL\n);\n\nCREATE INDEX \"time_plain\" ON PUBLIC.hyper_1 (time DESC, series_0);\nSELECT * FROM create_hypertable('\"public\".\"hyper_1\"'::regclass, 'time'::name, number_partitions => 1, create_default_indexes=>false);\n\nINSERT INTO hyper_1 SELECT to_timestamp(ser), ser, ser+10000, sqrt(ser::numeric) FROM generate_series(0,10000) ser;\nINSERT INTO hyper_1 SELECT to_timestamp(ser), ser, ser+10000, sqrt(ser::numeric) FROM generate_series(10001,20000) ser;\n\nCREATE TABLE PUBLIC.hyper_1_tz (\n  time TIMESTAMPTZ NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL\n);\n\nCREATE INDEX \"time_plain_tz\" ON PUBLIC.hyper_1_tz (time DESC, series_0);\nSELECT * FROM create_hypertable('\"public\".\"hyper_1_tz\"'::regclass, 'time'::name, number_partitions => 1, create_default_indexes=>false);\nINSERT INTO hyper_1_tz SELECT to_timestamp(ser), ser, ser+10000, sqrt(ser::numeric) FROM generate_series(0,10000) ser;\nINSERT INTO hyper_1_tz SELECT to_timestamp(ser), ser, ser+10000, sqrt(ser::numeric) FROM generate_series(10001,20000) ser;\n\nCREATE TABLE PUBLIC.hyper_1_int (\n  time int NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL\n);\n\nCREATE INDEX \"time_plain_int\" ON PUBLIC.hyper_1_int (time DESC, series_0);\nSELECT * FROM create_hypertable('\"public\".\"hyper_1_int\"'::regclass, 'time'::name, number_partitions => 1, chunk_time_interval=>10000, create_default_indexes=>FALSE);\nINSERT INTO hyper_1_int SELECT ser, ser, ser+10000, sqrt(ser::numeric) FROM generate_series(0,10000) ser;\nINSERT INTO hyper_1_int SELECT ser, ser, ser+10000, sqrt(ser::numeric) FROM generate_series(10001,20000) ser;\n\nCREATE TABLE PUBLIC.hyper_1_date (\n  time date NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL\n);\n\nCREATE INDEX \"time_plain_date\" ON PUBLIC.hyper_1_date (time DESC, series_0);\nSELECT * FROM create_hypertable('\"public\".\"hyper_1_date\"'::regclass, 'time'::name, number_partitions => 1, chunk_time_interval=>86400000000, create_default_indexes=>FALSE);\nINSERT INTO hyper_1_date SELECT to_timestamp(ser)::date, ser, ser+10000, sqrt(ser::numeric) FROM generate_series(0,10000) ser;\nINSERT INTO hyper_1_date SELECT to_timestamp(ser)::date, ser, ser+10000, sqrt(ser::numeric) FROM generate_series(10001,20000) ser;\n--below needed to create enough unique dates to trigger an index scan\nINSERT INTO hyper_1_date SELECT to_timestamp(ser*100)::date, ser, ser+10000, sqrt(ser::numeric) FROM generate_series(10001,20000) ser;\n\nCREATE TABLE PUBLIC.plain_table (\n  time TIMESTAMPTZ NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL\n);\n\nCREATE INDEX \"time_plain_plain_table\" ON PUBLIC.plain_table (time DESC, series_0);\nINSERT INTO plain_table SELECT to_timestamp(ser), ser, ser+10000, sqrt(ser::numeric) FROM generate_series(0,10000) ser;\nINSERT INTO plain_table SELECT to_timestamp(ser), ser, ser+10000, sqrt(ser::numeric) FROM generate_series(10001,20000) ser;\n\n-- Table with a time partitioning function\nCREATE TABLE PUBLIC.hyper_timefunc (\n  time float8 NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL\n);\n\nCREATE OR REPLACE FUNCTION unix_to_timestamp(unixtime float8)\n    RETURNS TIMESTAMPTZ LANGUAGE SQL IMMUTABLE AS\n$BODY$\n    SELECT to_timestamp(unixtime);\n$BODY$;\n\nCREATE INDEX \"time_plain_timefunc\" ON PUBLIC.hyper_timefunc (to_timestamp(time) DESC, series_0);\nSELECT * FROM create_hypertable('\"public\".\"hyper_timefunc\"'::regclass, 'time'::name, number_partitions => 1, create_default_indexes=>false, time_partitioning_func => 'unix_to_timestamp');\n\nINSERT INTO hyper_timefunc SELECT ser, ser, ser+10000, sqrt(ser::numeric) FROM generate_series(0,10000) ser;\nINSERT INTO hyper_timefunc SELECT ser, ser, ser+10000, sqrt(ser::numeric) FROM generate_series(10001,20000) ser;\n\nANALYZE plain_table;\nANALYZE hyper_timefunc;\nANALYZE hyper_1;\nANALYZE hyper_1_tz;\nANALYZE hyper_1_int;\nANALYZE hyper_1_date;\n"
  },
  {
    "path": "test/sql/include/query_query.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nSHOW timescaledb.enable_optimizations;\n\n--non-aggregates use MergeAppend in both optimized and non-optimized\n:PREFIX SELECT * FROM hyper_1 ORDER BY \"time\" DESC limit 2;\n\n:PREFIX SELECT * FROM hyper_timefunc ORDER BY unix_to_timestamp(\"time\") DESC limit 2;\n\n--Aggregates use MergeAppend only in optimized\n:PREFIX SELECT date_trunc('minute', time) t, avg(series_0), min(series_1), avg(series_2) FROM hyper_1 GROUP BY t ORDER BY t DESC limit 2;\n:PREFIX SELECT date_trunc('minute', time) t, avg(series_0), min(series_1), avg(series_2) FROM hyper_1_date GROUP BY t ORDER BY t DESC limit 2;\n\n--the minute and second results should be diff\n:PREFIX SELECT date_trunc('minute', time) t, avg(series_0), min(series_1), avg(series_2) FROM hyper_1 GROUP BY t ORDER BY t DESC limit 2;\n:PREFIX SELECT date_trunc('second', time) t, avg(series_0), min(series_1), avg(series_2) FROM hyper_1 GROUP BY t ORDER BY t DESC limit 2;\n\n--test that when index on time used by constraint, still works correctly\n:PREFIX\nSELECT date_trunc('minute', time) t, avg(series_0), min(series_1), avg(series_2)\nFROM hyper_1\nWHERE time < to_timestamp(900)\nGROUP BY t\nORDER BY t DESC\nLIMIT 2;\n\n--test on table with time partitioning function. Currently not\n--optimized to use index for ordering since the index is an expression\n--on time (e.g., timefunc(time)), and we currently don't handle that\n--case.\n:PREFIX\nSELECT date_trunc('minute', to_timestamp(time)) t, avg(series_0), min(series_1), avg(series_2)\nFROM hyper_timefunc\nWHERE to_timestamp(time) < to_timestamp(900)\nGROUP BY t\nORDER BY t DESC\nLIMIT 2;\n\nBEGIN;\n  --test that still works with an expression index on data_trunc.\n  DROP INDEX \"time_plain\";\n  CREATE INDEX \"time_trunc\" ON PUBLIC.hyper_1 (date_trunc('minute', time));\n  ANALYZE hyper_1;\n\n  :PREFIX SELECT date_trunc('minute', time) t, avg(series_0), min(series_1), avg(series_2) FROM hyper_1 GROUP BY t ORDER BY t DESC limit 2;\n\n  --test that works with both indexes\n  CREATE INDEX \"time_plain\" ON PUBLIC.hyper_1 (time DESC, series_0);\n  ANALYZE hyper_1;\n\n  :PREFIX SELECT date_trunc('minute', time) t, avg(series_0), min(series_1), avg(series_2) FROM hyper_1 GROUP BY t ORDER BY t DESC limit 2;\n\n  :PREFIX SELECT time_bucket('1 minute', time) t, avg(series_0), min(series_1), trunc(avg(series_2)::numeric, 5)\n  FROM hyper_1 GROUP BY t ORDER BY t DESC limit 2;\n\n  :PREFIX SELECT time_bucket('1 minute', time, INTERVAL '30 seconds') t, avg(series_0), min(series_1), trunc(avg(series_2)::numeric,5)\n  FROM hyper_1 GROUP BY t ORDER BY t DESC limit 2;\n\n  :PREFIX SELECT time_bucket('1 minute', time - INTERVAL '30 seconds') t, avg(series_0), min(series_1), trunc(avg(series_2)::numeric,5)\n  FROM hyper_1 GROUP BY t ORDER BY t DESC limit 2;\n\n  :PREFIX SELECT time_bucket('1 minute', time - INTERVAL '30 seconds') + INTERVAL '30 seconds' t, avg(series_0), min(series_1), trunc(avg(series_2)::numeric,5)\n  FROM hyper_1 GROUP BY t ORDER BY t DESC limit 2;\n\n  :PREFIX SELECT time_bucket('1 minute', time) t, avg(series_0), min(series_1), avg(series_2)\n  FROM hyper_1_tz GROUP BY t ORDER BY t DESC limit 2;\n\n  :PREFIX SELECT time_bucket('1 minute', time::timestamp) t, avg(series_0), min(series_1), avg(series_2)\n  FROM hyper_1_tz GROUP BY t ORDER BY t DESC limit 2;\n\n  :PREFIX SELECT time_bucket(10, time) t, avg(series_0), min(series_1), avg(series_2)\n  FROM hyper_1_int GROUP BY t ORDER BY t DESC limit 2;\n\n  :PREFIX SELECT time_bucket(10, time, 2) t, avg(series_0), min(series_1), avg(series_2)\n  FROM hyper_1_int GROUP BY t ORDER BY t DESC limit 2;\nROLLBACK;\n\n-- sort order optimization should not be applied to non-hypertables\n:PREFIX\nSELECT date_trunc('minute', time) t, avg(series_0), min(series_1), avg(series_2)\nFROM plain_table\nWHERE time < to_timestamp(900)\nGROUP BY t\nORDER BY t DESC\nLIMIT 2;\n\n"
  },
  {
    "path": "test/sql/include/query_result_test_equal.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n--expects QUERY1 and QUERY2 to be set, expects data can be compared\nset enable_hashjoin = off;\nset enable_mergejoin = on;\nwith query1 AS (\n  SELECT row_number() OVER(ORDER BY q.*) row_number, * FROM (:QUERY1) as q\n),\nquery2 AS (\n  SELECT row_number() OVER (ORDER BY v.*) row_number, * FROM (:QUERY2) as v\n)\nSELECT count(*) FILTER (WHERE query1.row_number IS DISTINCT FROM query2.row_number OR query1.show_chunks IS DISTINCT FROM query2.drop_chunks) AS \"Different Rows\",\ncoalesce(max(query1.row_number), 0) AS \"Total Rows from Query 1\", coalesce(max(query2.row_number), 0) AS \"Total Rows from Query 2\"\nFROM query1 FULL OUTER JOIN query2 ON (query1.row_number = query2.row_number);\nreset enable_hashjoin;\nreset enable_mergejoin;\n"
  },
  {
    "path": "test/sql/include/test_utils.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE OR REPLACE FUNCTION assert_true(\n    val boolean\n)\n RETURNS VOID LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nBEGIN\n    IF val IS NOT TRUE THEN\n        RAISE 'Assert failed';\n    END IF;\nEND\n$BODY$;\n\n\nCREATE OR REPLACE FUNCTION assert_equal(\n    val1 anyelement,\n    val2 anyelement\n)\n RETURNS VOID LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nBEGIN\n    IF (val1 = val2) IS NOT TRUE THEN\n        RAISE 'Assert failed: % = %',val1,val2;\n    END IF;\nEND\n$BODY$;\n"
  },
  {
    "path": "test/sql/include/ts_merge_load.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE USER regress_merge_privs;\nCREATE USER regress_merge_no_privs;\nDROP TABLE IF EXISTS target;\nDROP TABLE IF EXISTS source;\nCREATE TABLE target (tid integer, balance integer)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE source (sid integer, delta integer) -- no index\n  WITH (autovacuum_enabled=off);\nINSERT INTO target VALUES (1, 10);\nINSERT INTO target VALUES (2, 20);\nINSERT INTO target VALUES (3, 30);\nSELECT t.ctid is not null as matched, t.*, s.* FROM source s FULL OUTER JOIN target t ON s.sid = t.tid ORDER BY t.tid, s.sid;\n\nALTER TABLE target OWNER TO regress_merge_privs;\nALTER TABLE source OWNER TO regress_merge_privs;\n\nCREATE TABLE target2 (tid integer, balance integer)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE source2 (sid integer, delta integer)\n  WITH (autovacuum_enabled=off);\n\nALTER TABLE target2 OWNER TO regress_merge_no_privs;\nALTER TABLE source2 OWNER TO regress_merge_no_privs;\n\nGRANT INSERT ON target TO regress_merge_no_privs;\nGRANT CREATE ON SCHEMA public TO regress_merge_privs;\nSET SESSION AUTHORIZATION regress_merge_privs;\n\nCREATE TABLE sq_target (tid integer NOT NULL, balance integer)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE sq_source (delta integer, sid integer, balance integer DEFAULT 0)\n  WITH (autovacuum_enabled=off);\n\nINSERT INTO sq_target(tid, balance) VALUES (1,100), (2,200), (3,300);\nINSERT INTO sq_source(sid, delta) VALUES (1,10), (2,20), (4,40);\n\n-- conditional WHEN clause\nCREATE TABLE wq_target (tid integer not null, balance integer DEFAULT -1)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE wq_source (balance integer, sid integer)\n  WITH (autovacuum_enabled=off);\n\nINSERT INTO wq_source (sid, balance) VALUES (1, 100);\n\nCREATE TABLE cj_target (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE cj_source1 (sid1 integer, scat integer, delta integer)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE cj_source2 (sid2 integer, sval text)\n  WITH (autovacuum_enabled=off);\nINSERT INTO cj_source1 VALUES (1, 10, 100);\nINSERT INTO cj_source1 VALUES (1, 20, 200);\nINSERT INTO cj_source1 VALUES (2, 20, 300);\nINSERT INTO cj_source1 VALUES (3, 10, 400);\nINSERT INTO cj_source2 VALUES (1, 'initial source2');\nINSERT INTO cj_source2 VALUES (2, 'initial source2');\nINSERT INTO cj_source2 VALUES (3, 'initial source2');\n\nCREATE TABLE fs_target (a int, b int, c text)\n  WITH (autovacuum_enabled=off);\n"
  },
  {
    "path": "test/sql/include/ts_merge_load_ht.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE USER regress_merge_privs;\nCREATE USER regress_merge_no_privs;\nDROP TABLE IF EXISTS target;\nDROP TABLE IF EXISTS source;\nCREATE TABLE target (tid integer, balance integer)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('target', 'tid', chunk_time_interval => 3);\nCREATE TABLE source (sid integer, delta integer) -- no index\n  WITH (autovacuum_enabled=off);\nINSERT INTO target VALUES (1, 10);\nINSERT INTO target VALUES (2, 20);\nINSERT INTO target VALUES (3, 30);\nSELECT t.ctid is not null as matched, t.*, s.* FROM source s FULL OUTER JOIN target t ON s.sid = t.tid ORDER BY t.tid, s.sid;\n\nALTER TABLE target OWNER TO regress_merge_privs;\nALTER TABLE source OWNER TO regress_merge_privs;\n\nCREATE TABLE target2 (tid integer, balance integer)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('target2', 'tid', chunk_time_interval => 3);\nCREATE TABLE source2 (sid integer, delta integer)\n  WITH (autovacuum_enabled=off);\n\nALTER TABLE target2 OWNER TO regress_merge_no_privs;\nALTER TABLE source2 OWNER TO regress_merge_no_privs;\n\nGRANT INSERT ON target TO regress_merge_no_privs;\nGRANT CREATE ON SCHEMA public TO regress_merge_privs;\nSET SESSION AUTHORIZATION regress_merge_privs;\n\nCREATE TABLE sq_target (tid integer NOT NULL, balance integer)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('sq_target', 'tid', chunk_time_interval => 3);\n\nCREATE TABLE sq_source (delta integer, sid integer, balance integer DEFAULT 0)\n  WITH (autovacuum_enabled=off);\n\nINSERT INTO sq_target(tid, balance) VALUES (1,100), (2,200), (3,300);\nINSERT INTO sq_source(sid, delta) VALUES (1,10), (2,20), (4,40);\n\n-- conditional WHEN clause\nCREATE TABLE wq_target (tid integer not null, balance integer DEFAULT -1)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('wq_target', 'tid', chunk_time_interval => 3);\nCREATE TABLE wq_source (balance integer, sid integer)\n  WITH (autovacuum_enabled=off);\n\nINSERT INTO wq_source (sid, balance) VALUES (1, 100);\n\n-- some complex joins on the source side\n\nCREATE TABLE cj_target (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('cj_target', 'tid', chunk_time_interval => 3);\nCREATE TABLE cj_source1 (sid1 integer, scat integer, delta integer)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE cj_source2 (sid2 integer, sval text)\n  WITH (autovacuum_enabled=off);\nINSERT INTO cj_source1 VALUES (1, 10, 100);\nINSERT INTO cj_source1 VALUES (1, 20, 200);\nINSERT INTO cj_source1 VALUES (2, 20, 300);\nINSERT INTO cj_source1 VALUES (3, 10, 400);\nINSERT INTO cj_source2 VALUES (1, 'initial source2');\nINSERT INTO cj_source2 VALUES (2, 'initial source2');\nINSERT INTO cj_source2 VALUES (3, 'initial source2');\n\nCREATE TABLE fs_target (a int, b int, c text)\n  WITH (autovacuum_enabled=off);\nSELECT create_hypertable('fs_target', 'a', chunk_time_interval => 3);"
  },
  {
    "path": "test/sql/include/ts_merge_query.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n--\n-- Errors\n--\nMERGE INTO target t RANDOMWORD\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\n-- MATCHED/INSERT error\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tINSERT DEFAULT VALUES;\n-- incorrectly specifying INTO target\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT INTO target DEFAULT VALUES;\n-- Multiple VALUES clause\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (1,1), (2,2);\n-- SELECT query for INSERT\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT SELECT (1, 1);\n-- NOT MATCHED/UPDATE\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tUPDATE SET balance = 0;\n-- UPDATE tablename\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE target SET balance = 0;\n-- source and target names the same\nMERGE INTO target\nUSING target\nON tid = tid\nWHEN MATCHED THEN DO NOTHING;\n-- used in a CTE\nWITH foo AS (\n  MERGE INTO target USING source ON (true)\n  WHEN MATCHED THEN DELETE\n) SELECT * FROM foo;\n-- used in COPY\nCOPY (\n  MERGE INTO target USING source ON (true)\n  WHEN MATCHED THEN DELETE\n) TO stdout;\n\n-- unsupported relation types\n-- view\nCREATE VIEW tv AS SELECT * FROM target;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\nDROP VIEW tv;\n\n-- materialized view\nCREATE MATERIALIZED VIEW mv AS SELECT * FROM target;\nMERGE INTO mv t\nUSING source s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\nDROP MATERIALIZED VIEW mv;\n\n-- permissions\n\nMERGE INTO target\nUSING source2\nON target.tid = source2.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\n\nGRANT INSERT ON target TO regress_merge_no_privs;\nSET SESSION AUTHORIZATION regress_merge_no_privs;\n\nMERGE INTO target\nUSING source2\nON target.tid = source2.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\n\nGRANT UPDATE ON target2 TO regress_merge_privs;\nSET SESSION AUTHORIZATION regress_merge_privs;\n\nMERGE INTO target2\nUSING source\nON target2.tid = source.sid\nWHEN MATCHED THEN\n\tDELETE;\n\nMERGE INTO target2\nUSING source\nON target2.tid = source.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\n\n-- check if the target can be accessed from source relation subquery; we should\n-- not be able to do so\nMERGE INTO target t\nUSING (SELECT * FROM source WHERE t.tid > sid) s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\n\n--\n-- initial tests\n--\n-- zero rows in source has no effect\nMERGE INTO target\nUSING source\nON target.tid = source.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\n\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT DEFAULT VALUES;\nROLLBACK;\n\n-- insert some non-matching source rows to work from\nINSERT INTO source VALUES (4, 40);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tDO NOTHING;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (5, 50);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n\n-- index plans\nINSERT INTO target SELECT generate_series(1000,2500), 0;\nALTER TABLE target ADD PRIMARY KEY (tid);\nANALYZE target;\nDELETE FROM target WHERE tid > 100;\nANALYZE target;\n\n-- insert some matching source rows to work from\nINSERT INTO source VALUES (2, 5);\nINSERT INTO source VALUES (3, 20);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n\n-- equivalent of an UPDATE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n\n-- equivalent of a DELETE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDO NOTHING;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, NULL);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n\n-- duplicate source row causes multiple target row update ERROR\nINSERT INTO source VALUES (2, 5);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nROLLBACK;\n\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tDELETE;\nROLLBACK;\n\n-- remove duplicate MATCHED data from source data\nDELETE FROM source WHERE sid = 2;\nINSERT INTO source VALUES (2, 5);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n\n-- duplicate source row on INSERT should fail because of target_pkey\nINSERT INTO source VALUES (4, 40);\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n  INSERT VALUES (4, NULL);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n\n-- remove duplicate NOT MATCHED data from source data\nDELETE FROM source WHERE sid = 4;\nINSERT INTO source VALUES (4, 40);\nSELECT * FROM source ORDER BY sid;\nSELECT * FROM target ORDER BY tid;\n\n-- remove constraints\nalter table target drop CONSTRAINT target_pkey;\nalter table target alter column tid drop not null;\n\n-- multiple actions\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, 4)\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n\n-- should be equivalent\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = 0\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (4, 4);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n\n-- column references\n-- do a simple equivalent of an UPDATE join\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance + s.delta;\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n\n-- do a simple equivalent of an INSERT SELECT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n\n-- and again with duplicate source rows\nINSERT INTO source VALUES (5, 50);\nINSERT INTO source VALUES (5, 50);\n\n-- do a simple equivalent of an INSERT SELECT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n  INSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n\n-- removing duplicate source rows\nDELETE FROM source WHERE sid = 5;\n\n-- and again with explicitly identified column list\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n\n-- and again with a subtle error: referring to non-existent target row for NOT MATCHED\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (t.tid, s.delta);\n\n-- and again with a constant ON clause\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON (SELECT true)\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (t.tid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n\n-- now the classic UPSERT\nBEGIN;\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance + s.delta\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n\n-- this time with a FALSE condition\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND FALSE THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\n\n-- this time with an actual condition which returns false\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance <> 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\n\nBEGIN;\n-- and now with a condition which returns true\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\nROLLBACK;\n\n-- conditions in the NOT MATCHED clause can only refer to source columns\nBEGIN;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND t.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\nROLLBACK;\n\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN NOT MATCHED AND s.balance = 100 THEN\n\tINSERT (tid) VALUES (s.sid);\nSELECT * FROM wq_target;\n\n-- conditions in MATCHED clause can refer to both source and target\nSELECT * FROM wq_source;\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND s.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n\n-- check if AND works\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 AND s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 AND s.balance = 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n\n-- check if OR works\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 99 OR s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance = 199 OR s.balance > 100 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n\n-- check source-side whole-row references\nBEGIN;\nMERGE INTO wq_target t\nUSING wq_source s ON (t.tid = s.sid)\nWHEN matched and t = s or t.tid = s.sid THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\nROLLBACK;\n\n-- check if subqueries work in the conditions?\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.balance > (SELECT max(balance) FROM target) THEN\n\tUPDATE SET balance = t.balance + s.balance;\n\n-- check if we can access system columns in the conditions\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.xmin = t.xmax THEN\n\tUPDATE SET balance = t.balance + s.balance;\n\nMERGE INTO wq_target t\nUSING wq_source s ON t.tid = s.sid\nWHEN MATCHED AND t.tableoid >= 0 THEN\n\tUPDATE SET balance = t.balance + s.balance;\nSELECT * FROM wq_target;\n\nDROP TABLE wq_target CASCADE;\nDROP TABLE wq_source;\n\n-- test triggers\ncreate or replace function merge_trigfunc () returns trigger\nlanguage plpgsql as\n$$\nDECLARE\n\tline text;\nBEGIN\n\tSELECT INTO line format('%s %s %s trigger%s',\n\t\tTG_WHEN, TG_OP, TG_LEVEL, CASE\n\t\tWHEN TG_OP = 'INSERT' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s', NEW)\n\t\tWHEN TG_OP = 'UPDATE' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s -> %s', OLD, NEW)\n\t\tWHEN TG_OP = 'DELETE' AND TG_LEVEL = 'ROW'\n\t\t\tTHEN format(' row: %s', OLD)\n\t\tEND);\n\n\tRAISE NOTICE '%', line;\n\tIF (TG_WHEN = 'BEFORE' AND TG_LEVEL = 'ROW') THEN\n\t\tIF (TG_OP = 'DELETE') THEN\n\t\t\tRETURN OLD;\n\t\tELSE\n\t\t\tRETURN NEW;\n\t\tEND IF;\n\tELSE\n\t\tRETURN NULL;\n\tEND IF;\nEND;\n$$;\nCREATE TRIGGER merge_bsi BEFORE INSERT ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bsu BEFORE UPDATE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bsd BEFORE DELETE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asi AFTER INSERT ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asu AFTER UPDATE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_asd AFTER DELETE ON target FOR EACH STATEMENT EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bri BEFORE INSERT ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_bru BEFORE UPDATE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_brd BEFORE DELETE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_ari AFTER INSERT ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_aru AFTER UPDATE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\nCREATE TRIGGER merge_ard AFTER DELETE ON target FOR EACH ROW EXECUTE PROCEDURE merge_trigfunc ();\n\n-- now the classic UPSERT, with a DELETE\nBEGIN;\nUPDATE target SET balance = 0 WHERE tid = 3;\n--EXPLAIN (ANALYZE ON, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF)\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND t.balance > s.delta THEN\n\tUPDATE SET balance = t.balance - s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n\n-- Test behavior of triggers that turn UPDATE/DELETE into no-ops\ncreate or replace function skip_merge_op() returns trigger\nlanguage plpgsql as\n$$\nBEGIN\n\tRETURN NULL;\nEND;\n$$;\n\nSELECT * FROM target full outer join source on (sid = tid);\ncreate trigger merge_skip BEFORE INSERT OR UPDATE or DELETE\n  ON target FOR EACH ROW EXECUTE FUNCTION skip_merge_op();\nDO $$\nDECLARE\n  result integer;\nBEGIN\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND s.sid = 3 THEN UPDATE SET balance = t.balance + s.delta\nWHEN MATCHED THEN DELETE\nWHEN NOT MATCHED THEN INSERT VALUES (sid, delta);\nIF FOUND THEN\n  RAISE NOTICE 'Found';\nELSE\n  RAISE NOTICE 'Not found';\nEND IF;\nGET DIAGNOSTICS result := ROW_COUNT;\nRAISE NOTICE 'ROW_COUNT = %', result;\nEND;\n$$;\nSELECT * FROM target FULL OUTER JOIN source ON (sid = tid);\nDROP TRIGGER merge_skip ON target;\nDROP FUNCTION skip_merge_op();\n\n-- test from PL/pgSQL\n-- make sure MERGE INTO isn't interpreted to mean returning variables like SELECT INTO\nBEGIN;\nDO LANGUAGE plpgsql $$\nBEGIN\nMERGE INTO target t\nUSING source AS s\nON t.tid = s.sid\nWHEN MATCHED AND t.balance > s.delta THEN\n\tUPDATE SET balance = t.balance - s.delta;\nEND;\n$$;\nROLLBACK;\n\n--source constants\nBEGIN;\nMERGE INTO target t\nUSING (SELECT 9 AS sid, 57 AS delta) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n\n--source query\nBEGIN;\nMERGE INTO target t\nUSING (SELECT sid, delta FROM source WHERE delta > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n\nBEGIN;\nMERGE INTO target t\nUSING (SELECT sid, delta as newname FROM source WHERE delta > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.newname);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n\n--self-merge\nBEGIN;\nMERGE INTO target t1\nUSING target t2\nON t1.tid = t2.tid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t1.balance + t2.balance\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (t2.tid, t2.balance);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n\nBEGIN;\nMERGE INTO target t\nUSING (SELECT tid as sid, balance as delta FROM target WHERE balance > 0) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n\nBEGIN;\nMERGE INTO target t\nUSING\n(SELECT sid, max(delta) AS delta\n FROM source\n GROUP BY sid\n HAVING count(*) = 1\n ORDER BY sid ASC) AS s\nON t.tid = s.sid\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (s.sid, s.delta);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n\n-- plpgsql parameters and results\nBEGIN;\nCREATE FUNCTION merge_func (p_id integer, p_bal integer)\nRETURNS INTEGER\nLANGUAGE plpgsql\nAS $$\nDECLARE\n result integer;\nBEGIN\nMERGE INTO target t\nUSING (SELECT p_id AS sid) AS s\nON t.tid = s.sid\nWHEN MATCHED THEN\n\tUPDATE SET balance = t.balance - p_bal;\nIF FOUND THEN\n\tGET DIAGNOSTICS result := ROW_COUNT;\nEND IF;\nRETURN result;\nEND;\n$$;\nSELECT merge_func(3, 4);\nSELECT * FROM target ORDER BY tid;\nROLLBACK;\n\n-- PREPARE\nBEGIN;\nprepare foom as merge into target t using (select 1 as sid) s on (t.tid = s.sid) when matched then update set balance = 1;\nexecute foom;\nROLLBACK;\n\nBEGIN;\nPREPARE foom2 (integer, integer) AS\nMERGE INTO target t\nUSING (SELECT 1) s\nON t.tid = $1\nWHEN MATCHED THEN\nUPDATE SET balance = $2;\n--EXPLAIN (ANALYZE ON, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF)\nexecute foom2 (1, 1);\nROLLBACK;\n\n-- subqueries in source relation\nBEGIN;\nMERGE INTO sq_target t\nUSING (SELECT * FROM sq_source) s\nON tid = sid\nWHEN MATCHED AND t.balance > delta THEN\n\tUPDATE SET balance = t.balance + delta;\nSELECT * FROM sq_target ORDER BY tid;\nROLLBACK;\n\n-- try a view\nCREATE VIEW v AS SELECT * FROM sq_source WHERE sid < 2;\n\nBEGIN;\nMERGE INTO sq_target\nUSING v\nON tid = sid\nWHEN MATCHED THEN\n    UPDATE SET balance = v.balance + delta;\nSELECT * FROM sq_target ORDER BY tid;\nROLLBACK;\n\n-- ambiguous reference to a column\nBEGIN;\nMERGE INTO sq_target\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\nROLLBACK;\n\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\nSELECT * FROM sq_target;\nROLLBACK;\n\n-- CTEs\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nWITH targq AS (\n\tSELECT * FROM v\n)\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE;\nROLLBACK;\n\n-- RETURNING\nBEGIN;\nINSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND tid > 2 THEN\n    UPDATE SET balance = t.balance + delta\nWHEN NOT MATCHED THEN\n\tINSERT (balance, tid) VALUES (balance + delta, sid)\nWHEN MATCHED AND tid < 2 THEN\n\tDELETE\nRETURNING *;\nROLLBACK;\n\n-- PG17-specific tests for views, returning and merge_action. These throw syntax errors for previous versions of Postgres.\n-- However, since the error is the same for both hypertables and regular tables, this test should still pass for previous versions.\n\n-- RETURNING\n\nINSERT INTO source(sid, delta) VALUES(1, 40), (5, 50);\n\nBEGIN;\nMERGE INTO target t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta);\nSELECT * from target;\nROLLBACK;\n\nBEGIN;\nMERGE INTO target t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 1 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta)\nRETURNING merge_action(), t.*;\nROLLBACK;\n\n-- Views\nCREATE VIEW tv AS SELECT * FROM target;\n\nBEGIN;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta);\nSELECT * from tv;\nSELECT * from target; -- should also update the underlying table\nROLLBACK;\n\nBEGIN;\nMERGE INTO tv t\nUSING source s\nON t.tid = s.sid\nWHEN MATCHED AND tid > 2 THEN\n\tUPDATE set balance = balance + s.delta\nWHEN MATCHED THEN\n\tDELETE\nWHEN NOT MATCHED THEN\n\tINSERT (tid, balance) VALUES (sid, delta)\nRETURNING merge_action(), t.*;\nROLLBACK;\n\nDROP VIEW tv;\n\nDELETE FROM source where sid in (1, 5);\n\n\n-- EXPLAIN\nCREATE TABLE ex_mtarget (a int, b int)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE ex_msource (a int, b int)\n  WITH (autovacuum_enabled=off);\nINSERT INTO ex_mtarget SELECT i, i*10 FROM generate_series(1,100,2) i;\nINSERT INTO ex_msource SELECT i, i*10 FROM generate_series(1,100,1) i;\n\nCREATE FUNCTION explain_merge(query text) RETURNS SETOF text\nLANGUAGE plpgsql AS\n$$\nDECLARE ln text;\nBEGIN\n    FOR ln IN\n        EXECUTE 'explain (analyze, timing off, summary off, buffers off, costs off) ' ||\n\t\t  query\n    LOOP\n        ln := regexp_replace(ln, '(Memory( Usage)?|Buckets|Batches): \\S*',  '\\1: xxx', 'g');\n        RETURN NEXT ln;\n    END LOOP;\nEND;\n$$;\n\n-- only updates\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED THEN\n\tUPDATE SET b = t.b + 1');\n\n-- only updates to selected tuples\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1');\n\n-- updates + deletes\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1\nWHEN MATCHED AND t.a >= 10 AND t.a <= 20 THEN\n\tDELETE');\n\n-- only inserts\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN NOT MATCHED AND s.a < 10 THEN\n\tINSERT VALUES (a, b)');\n\n-- all three\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a\nWHEN MATCHED AND t.a < 10 THEN\n\tUPDATE SET b = t.b + 1\nWHEN MATCHED AND t.a >= 30 AND t.a <= 40 THEN\n\tDELETE\nWHEN NOT MATCHED AND s.a < 20 THEN\n\tINSERT VALUES (a, b)');\n\n-- nothing\nSELECT explain_merge('\nMERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a AND t.a < -1000\nWHEN MATCHED AND t.a < 10 THEN\n\tDO NOTHING');\n\nDROP TABLE ex_msource, ex_mtarget;\nDROP FUNCTION explain_merge(text);\n\n-- Subqueries\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED THEN\n    UPDATE SET balance = (SELECT count(*) FROM sq_target);\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\n\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid\nWHEN MATCHED AND (SELECT count(*) > 0 FROM sq_target) THEN\n    UPDATE SET balance = 42;\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\n\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid AND (SELECT count(*) > 0 FROM sq_target)\nWHEN MATCHED THEN\n    UPDATE SET balance = 42;\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\n\n--  Test RETURNING with subqueries\nBEGIN;\nMERGE INTO sq_target t\nUSING v\nON tid = sid AND (SELECT count(*) > 0 FROM sq_target)\nWHEN MATCHED THEN\n    UPDATE SET balance = 42\nRETURNING *;\nSELECT * FROM sq_target WHERE tid = 1;\nROLLBACK;\n\nDROP TABLE sq_target CASCADE;\nDROP TABLE sq_source CASCADE;\n\nCREATE TABLE pa_target (tid integer, balance float, val text)\n\tPARTITION BY LIST (tid);\n\nCREATE TABLE part1 PARTITION OF pa_target FOR VALUES IN (1,4)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part2 PARTITION OF pa_target FOR VALUES IN (2,5,6)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part3 PARTITION OF pa_target FOR VALUES IN (3,8,9)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part4 PARTITION OF pa_target DEFAULT\n  WITH (autovacuum_enabled=off);\n\nCREATE TABLE pa_source (sid integer, delta float);\n-- insert many rows to the source table\nINSERT INTO pa_source SELECT id, id * 10  FROM generate_series(1,14) AS id;\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT id, id * 100, 'initial' FROM generate_series(1,14,2) AS id;\n\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n\n-- same with a constant qual\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid AND tid = 1\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n\n-- try updating the partition key column\nBEGIN;\nCREATE FUNCTION merge_func() RETURNS integer LANGUAGE plpgsql AS $$\nDECLARE\n  result integer;\nBEGIN\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nIF FOUND THEN\n  GET DIAGNOSTICS result := ROW_COUNT;\nEND IF;\nRETURN result;\nEND;\n$$;\nSELECT merge_func();\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n\nDROP TABLE pa_target CASCADE;\n\n-- The target table is partitioned in the same way, but this time by attaching\n-- partitions which have columns in different order, dropped columns etc.\nCREATE TABLE pa_target (tid integer, balance float, val text)\n\tPARTITION BY LIST (tid);\n\nCREATE TABLE part1 (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part2 (balance float, tid integer, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part3 (tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nCREATE TABLE part4 (extraid text, tid integer, balance float, val text)\n  WITH (autovacuum_enabled=off);\nALTER TABLE part4 DROP COLUMN extraid;\n\nALTER TABLE pa_target ATTACH PARTITION part1 FOR VALUES IN (1,4);\nALTER TABLE pa_target ATTACH PARTITION part2 FOR VALUES IN (2,5,6);\nALTER TABLE pa_target ATTACH PARTITION part3 FOR VALUES IN (3,8,9);\nALTER TABLE pa_target ATTACH PARTITION part4 DEFAULT;\n\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT id, id * 100, 'initial' FROM generate_series(1,14,2) AS id;\n\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n\n-- same with a constant qual\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid AND tid IN (1, 5)\n  WHEN MATCHED AND tid % 5 = 0 THEN DELETE\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n\n-- try updating the partition key column\nBEGIN;\nMERGE INTO pa_target t\n  USING pa_source s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n\nDROP TABLE pa_source;\nDROP TABLE pa_target CASCADE;\n\n-- Sub-partitioning\nCREATE TABLE pa_target (logts timestamp, tid integer, balance float, val text)\n\tPARTITION BY RANGE (logts);\n\nCREATE TABLE part_m01 PARTITION OF pa_target\n\tFOR VALUES FROM ('2017-01-01') TO ('2017-02-01')\n\tPARTITION BY LIST (tid);\nCREATE TABLE part_m01_odd PARTITION OF part_m01\n\tFOR VALUES IN (1,3,5,7,9) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m01_even PARTITION OF part_m01\n\tFOR VALUES IN (2,4,6,8) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m02 PARTITION OF pa_target\n\tFOR VALUES FROM ('2017-02-01') TO ('2017-03-01')\n\tPARTITION BY LIST (tid);\nCREATE TABLE part_m02_odd PARTITION OF part_m02\n\tFOR VALUES IN (1,3,5,7,9) WITH (autovacuum_enabled=off);\nCREATE TABLE part_m02_even PARTITION OF part_m02\n\tFOR VALUES IN (2,4,6,8) WITH (autovacuum_enabled=off);\n\nCREATE TABLE pa_source (sid integer, delta float)\n  WITH (autovacuum_enabled=off);\n-- insert many rows to the source table\nINSERT INTO pa_source SELECT id, id * 10  FROM generate_series(1,14) AS id;\n-- insert a few rows in the target table (odd numbered tid)\nINSERT INTO pa_target SELECT '2017-01-31', id, id * 100, 'initial' FROM generate_series(1,9,3) AS id;\nINSERT INTO pa_target SELECT '2017-02-28', id, id * 100, 'initial' FROM generate_series(2,9,3) AS id;\n\n-- try simple MERGE\nBEGIN;\nMERGE INTO pa_target t\n  USING (SELECT '2017-01-15' AS slogts, * FROM pa_source WHERE sid < 10) s\n  ON t.tid = s.sid\n  WHEN MATCHED THEN\n    UPDATE SET balance = balance + delta, val = val || ' updated by merge'\n  WHEN NOT MATCHED THEN\n    INSERT VALUES (slogts::timestamp, sid, delta, 'inserted by merge');\nSELECT * FROM pa_target ORDER BY tid;\nROLLBACK;\n\nDROP TABLE pa_source;\nDROP TABLE pa_target CASCADE;\n\n-- some complex joins on the source side\n-- source relation is an unaliased join\nMERGE INTO cj_target t\nUSING cj_source1 s1\n\tINNER JOIN cj_source2 s2 ON sid1 = sid2\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid1, delta, sval);\n\n-- try accessing columns from either side of the source join\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2 AND scat = 20\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid2, delta, sval)\nWHEN MATCHED THEN\n\tDELETE;\n\n-- some simple expressions in INSERT targetlist\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2\nON t.tid = sid1\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (sid2, delta + scat, sval)\nWHEN MATCHED THEN\n\tUPDATE SET val = val || ' updated by merge';\n\nMERGE INTO cj_target t\nUSING cj_source2 s2\n\tINNER JOIN cj_source1 s1 ON sid1 = sid2 AND scat = 20\nON t.tid = sid1\nWHEN MATCHED THEN\n\tUPDATE SET val = val || ' ' || delta::text;\n\nSELECT * FROM cj_target ORDER BY tid;\n\nALTER TABLE cj_source1 RENAME COLUMN sid1 TO sid;\nALTER TABLE cj_source2 RENAME COLUMN sid2 TO sid;\n\nTRUNCATE cj_target;\n\nMERGE INTO cj_target t\nUSING cj_source1 s1\n\tINNER JOIN cj_source2 s2 ON s1.sid = s2.sid\nON t.tid = s1.sid\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (s2.sid, delta, sval);\n\nDROP TABLE cj_source2, cj_source1;\nDROP TABLE cj_target CASCADE;\n\n-- Function scans\nMERGE INTO fs_target t\nUSING generate_series(1,100,1) AS id\nON t.a = id\nWHEN MATCHED THEN\n\tUPDATE SET b = b + id\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (id, -1);\n\nMERGE INTO fs_target t\nUSING generate_series(1,100,2) AS id\nON t.a = id\nWHEN MATCHED THEN\n\tUPDATE SET b = b + id, c = 'updated '|| id.*::text\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (id, -1, 'inserted ' || id.*::text);\n\nSELECT count(*) FROM fs_target;\nDROP TABLE fs_target CASCADE;\n\n-- SERIALIZABLE test\n-- handled in isolation tests\n\n-- Inheritance-based partitioning\nCREATE TABLE measurement (\n    city_id         int not null,\n    logdate         date not null,\n    peaktemp        int,\n    unitsales       int\n) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2006m02 (\n    CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )\n) INHERITS (measurement) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2006m03 (\n    CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )\n) INHERITS (measurement) WITH (autovacuum_enabled=off);\nCREATE TABLE measurement_y2007m01 (\n    filler          text,\n    peaktemp        int,\n    logdate         date not null,\n    city_id         int not null,\n    unitsales       int\n    CHECK ( logdate >= DATE '2007-01-01' AND logdate < DATE '2007-02-01')\n) WITH (autovacuum_enabled=off);\nALTER TABLE measurement_y2007m01 DROP COLUMN filler;\nALTER TABLE measurement_y2007m01 INHERIT measurement;\nINSERT INTO measurement VALUES (0, '2005-07-21', 5, 15);\n\nCREATE OR REPLACE FUNCTION measurement_insert_trigger()\nRETURNS TRIGGER AS $$\nBEGIN\n    IF ( NEW.logdate >= DATE '2006-02-01' AND\n         NEW.logdate < DATE '2006-03-01' ) THEN\n        INSERT INTO measurement_y2006m02 VALUES (NEW.*);\n    ELSIF ( NEW.logdate >= DATE '2006-03-01' AND\n            NEW.logdate < DATE '2006-04-01' ) THEN\n        INSERT INTO measurement_y2006m03 VALUES (NEW.*);\n    ELSIF ( NEW.logdate >= DATE '2007-01-01' AND\n            NEW.logdate < DATE '2007-02-01' ) THEN\n        INSERT INTO measurement_y2007m01 (city_id, logdate, peaktemp, unitsales)\n            VALUES (NEW.*);\n    ELSE\n        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';\n    END IF;\n    RETURN NULL;\nEND;\n$$ LANGUAGE plpgsql ;\nCREATE TRIGGER insert_measurement_trigger\n    BEFORE INSERT ON measurement\n    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();\nINSERT INTO measurement VALUES (1, '2006-02-10', 35, 10);\nINSERT INTO measurement VALUES (1, '2006-02-16', 45, 20);\nINSERT INTO measurement VALUES (1, '2006-03-17', 25, 10);\nINSERT INTO measurement VALUES (1, '2006-03-27', 15, 40);\nINSERT INTO measurement VALUES (1, '2007-01-15', 10, 10);\nINSERT INTO measurement VALUES (1, '2007-01-17', 10, 10);\n\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate;\n\nCREATE TABLE new_measurement (LIKE measurement) WITH (autovacuum_enabled=off);\nINSERT INTO new_measurement VALUES (0, '2005-07-21', 25, 20);\nINSERT INTO new_measurement VALUES (1, '2006-03-01', 20, 10);\nINSERT INTO new_measurement VALUES (1, '2006-02-16', 50, 10);\nINSERT INTO new_measurement VALUES (2, '2006-02-10', 20, 20);\nINSERT INTO new_measurement VALUES (1, '2006-03-27', NULL, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-17', NULL, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-15', 5, NULL);\nINSERT INTO new_measurement VALUES (1, '2007-01-16', 10, 10);\n\nBEGIN;\nMERGE INTO ONLY measurement m\n USING new_measurement nm ON\n      (m.city_id = nm.city_id and m.logdate=nm.logdate)\nWHEN MATCHED AND nm.peaktemp IS NULL THEN DELETE\nWHEN MATCHED THEN UPDATE\n     SET peaktemp = greatest(m.peaktemp, nm.peaktemp),\n        unitsales = m.unitsales + coalesce(nm.unitsales, 0)\nWHEN NOT MATCHED THEN INSERT\n     (city_id, logdate, peaktemp, unitsales)\n   VALUES (city_id, logdate, peaktemp, unitsales);\n\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate, peaktemp;\nROLLBACK;\n\nMERGE into measurement m\n USING new_measurement nm ON\n      (m.city_id = nm.city_id and m.logdate=nm.logdate)\nWHEN MATCHED AND nm.peaktemp IS NULL THEN DELETE\nWHEN MATCHED THEN UPDATE\n     SET peaktemp = greatest(m.peaktemp, nm.peaktemp),\n        unitsales = m.unitsales + coalesce(nm.unitsales, 0)\nWHEN NOT MATCHED THEN INSERT\n     (city_id, logdate, peaktemp, unitsales)\n   VALUES (city_id, logdate, peaktemp, unitsales);\n\nSELECT tableoid::regclass, * FROM measurement ORDER BY city_id, logdate;\n\nBEGIN;\nMERGE INTO new_measurement nm\n USING ONLY measurement m ON\n      (nm.city_id = m.city_id and nm.logdate=m.logdate)\nWHEN MATCHED THEN DELETE;\n\nSELECT * FROM new_measurement ORDER BY city_id, logdate;\nROLLBACK;\n\nMERGE INTO new_measurement nm\n USING measurement m ON\n      (nm.city_id = m.city_id and nm.logdate=m.logdate)\nWHEN MATCHED THEN DELETE;\n\nSELECT * FROM new_measurement ORDER BY city_id, logdate;\n\nDROP TABLE measurement, new_measurement CASCADE;\nDROP FUNCTION measurement_insert_trigger();\n\nRESET SESSION AUTHORIZATION;\nDROP TABLE target CASCADE;\nDROP TABLE target2 CASCADE;\nDROP TABLE source, source2;\nDROP FUNCTION merge_trigfunc();\nREVOKE CREATE ON SCHEMA public FROM regress_merge_privs;\nDROP USER regress_merge_privs;\nDROP USER regress_merge_no_privs;\n"
  },
  {
    "path": "test/sql/index.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE TABLE index_test(time timestamptz, temp float);\n\nSELECT create_hypertable('index_test', 'time');\n\n-- Default indexes created\nSELECT * FROM test.show_indexes('index_test');\n\nDROP TABLE index_test;\nCREATE TABLE index_test(time timestamptz, device integer, temp float);\n\n-- Create index before create_hypertable()\nCREATE UNIQUE INDEX index_test_time_idx ON index_test (time);\n\n\\set ON_ERROR_STOP 0\n-- Creating a hypertable from a table with an index that doesn't cover\n-- all partitioning columns should fail\nSELECT create_hypertable('index_test', 'time', 'device', 2);\n\\set ON_ERROR_STOP 1\n\n-- Partitioning on only time should work\nSELECT create_hypertable('index_test', 'time');\n\nINSERT INTO index_test VALUES ('2017-01-20T09:00:01', 1, 17.5);\n\n-- Check that index is also created on chunk\nSELECT * FROM test.show_indexes('index_test');\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n\n-- Create another chunk\nINSERT INTO index_test VALUES ('2017-05-20T09:00:01', 3, 17.5);\n\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n\n-- Delete the index on only one chunk\nDROP INDEX _timescaledb_internal._hyper_3_1_chunk_index_test_time_idx;\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n\n-- Recreate table with new partitioning\nDROP TABLE index_test;\nCREATE TABLE index_test(id serial, time timestamptz, device integer, temp float);\nSELECT * FROM test.show_columns('index_test');\n\n-- Test that we can handle difference in attnos across hypertable and\n-- chunks by dropping the ID column\nALTER TABLE index_test DROP COLUMN id;\nSELECT * FROM test.show_columns('index_test');\n\n-- No pre-existing UNIQUE index, so partitioning on two columns should work\nSELECT create_hypertable('index_test', 'time', 'device', 2);\n\nINSERT INTO index_test VALUES ('2017-01-20T09:00:01', 1, 17.5);\n\n\\set ON_ERROR_STOP 0\n-- Create unique index without all partitioning columns should fail\nCREATE UNIQUE INDEX index_test_time_device_idx ON index_test (time);\n\\set ON_ERROR_STOP 1\n\nCREATE UNIQUE INDEX index_test_time_device_idx ON index_test (time, device);\n\n-- Regular index need not cover all partitioning columns\nCREATE INDEX ON index_test (time, temp);\n\n-- Create another chunk\nINSERT INTO index_test VALUES ('2017-04-20T09:00:01', 1, 17.5);\n\n-- New index should have been recursed to chunks\nSELECT * FROM test.show_indexes('index_test');\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n\nALTER INDEX index_test_time_idx RENAME TO index_test_time_idx2;\n\n-- Metadata and index should have changed name\nSELECT * FROM test.show_indexes('index_test');\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n\nDROP INDEX index_test_time_idx2;\nDROP INDEX index_test_time_device_idx;\n\n-- Index should have been dropped\nSELECT * FROM test.show_indexes('index_test');\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n\n-- Create index with long name to see how this is handled on chunks\nCREATE INDEX a_hypertable_index_with_a_very_very_long_name_that_truncates ON index_test (time DESC, temp);\nCREATE INDEX a_hypertable_index_with_a_very_very_long_name_that_truncates_2 ON index_test (time DESC, temp DESC);\n\nSELECT * FROM test.show_indexes('index_test');\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n\nDROP INDEX a_hypertable_index_with_a_very_very_long_name_that_truncates;\nDROP INDEX a_hypertable_index_with_a_very_very_long_name_that_truncates_2;\n\n\\set ON_ERROR_STOP 0\n-- Create index CONCURRENTLY\nCREATE UNIQUE INDEX CONCURRENTLY index_test_time_device_idx ON index_test (time, device);\n\\set ON_ERROR_STOP 1\n\n-- Test tablespaces. Chunk indexes should end up in same tablespace as\n-- main index.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET client_min_messages = ERROR;\nDROP TABLESPACE IF EXISTS tablespace1;\nDROP TABLESPACE IF EXISTS tablespace2;\nSET client_min_messages = NOTICE;\n\nCREATE TABLESPACE tablespace1 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE1_PATH;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nCREATE INDEX index_test_time_idx ON index_test (time) TABLESPACE tablespace1;\n\nSELECT * FROM test.show_indexes('index_test');\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE TABLESPACE tablespace2 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE2_PATH;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nALTER INDEX index_test_time_idx SET TABLESPACE tablespace2;\n\nSELECT * FROM test.show_indexes('index_test');\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n\n-- Add constraint index\nALTER TABLE index_test ADD UNIQUE (time, device);\n\nSELECT * FROM test.show_indexes('index_test');\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n\n-- Constraints are added to chunk_constraint table.\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\n\nDROP TABLE index_test;\n\n-- Create table in a tablespace\nCREATE TABLE index_test(time timestamptz, temp float, device int) TABLESPACE tablespace1;\n\n-- Default indexes should be in the table's tablespace\nSELECT create_hypertable('index_test', 'time');\n\n-- Explicitly defining an index tablespace should work and propagate\n-- to chunks\nCREATE INDEX ON index_test (time, device) TABLESPACE tablespace2;\n\n-- New indexes without explicit tablespaces should use the default\n-- tablespace\nCREATE INDEX ON index_test (device);\n\n-- Create chunk\nINSERT INTO index_test VALUES ('2017-01-20T09:00:01', 17.5);\n\n-- Check that the tablespaces of chunk indexes match the tablespace of\n-- the main index\nSELECT * FROM test.show_indexes('index_test');\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n\n-- Creating a new index should propagate to existing chunks, including\n-- the given tablespace\nCREATE INDEX ON index_test (time, temp) TABLESPACE tablespace2;\n\nSELECT * FROM test.show_indexes('index_test');\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n\n-- Cleanup\nDROP TABLE index_test CASCADE;\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nDROP TABLESPACE tablespace1;\nDROP TABLESPACE tablespace2;\n\n-- Test expression indexes\nCREATE TABLE index_expr_test(id serial, time timestamptz, temp float, meta jsonb);\n\n-- Screw up the attribute numbers\nALTER TABLE index_expr_test DROP COLUMN id;\n\nCREATE INDEX ON index_expr_test ((meta ->> 'field')) ;\nINSERT INTO index_expr_test VALUES ('2017-01-20T09:00:01', 17.5, '{\"field\": \"value1\"}');\nINSERT INTO index_expr_test VALUES ('2017-01-20T09:00:01', 17.5, '{\"field\": \"value2\"}');\n\nEXPLAIN (verbose, buffers off, costs off)\nSELECT * FROM index_expr_test WHERE meta ->> 'field' = 'value1';\nSELECT * FROM index_expr_test WHERE meta ->> 'field' = 'value1';\n\n-- Test INDEX DROP error for multiple objects\nCREATE TABLE index_test(time timestamptz, temp float);\nCREATE UNIQUE INDEX index_test_idx ON index_test (time, temp);\nSELECT create_hypertable('index_test', 'time');\n\nCREATE TABLE index_test_2(time timestamptz, temp float);\nCREATE UNIQUE INDEX index_test_2_idx ON index_test_2 (time, temp);\n\n\\set ON_ERROR_STOP 0\nDROP INDEX index_test_idx, index_test_2_idx;\n\\set ON_ERROR_STOP 1\n\n-- test expression index with dropped columns\nCREATE TABLE idx_expr_test(filler int, time timestamptz, meta text);\nSELECT table_name FROM create_hypertable('idx_expr_test', 'time');\nALTER TABLE idx_expr_test DROP COLUMN filler;\nCREATE INDEX tag_idx ON idx_expr_test(('foo'||meta));\nINSERT INTO idx_expr_test(time, meta) VALUES ('2000-01-01', 'bar');\nDROP TABLE idx_expr_test CASCADE;\n\n-- test multicolumn expression index with dropped columns\nCREATE TABLE idx_expr_test(filler int, time timestamptz, t1 text, t2 text, t3 text);\nSELECT table_name FROM create_hypertable('idx_expr_test', 'time');\nALTER TABLE idx_expr_test DROP COLUMN filler;\nCREATE INDEX tag_idx ON idx_expr_test((t1||t2||t3));\nINSERT INTO idx_expr_test(time, t1, t2, t3) VALUES ('2000-01-01', 'foo', 'bar', 'baz');\nDROP TABLE idx_expr_test CASCADE;\n\n-- test index with predicate and dropped columns\nCREATE TABLE idx_predicate_test(filler int, time timestamptz);\nSELECT table_name FROM create_hypertable('idx_predicate_test', 'time');\nALTER TABLE idx_predicate_test DROP COLUMN filler;\nALTER TABLE idx_predicate_test ADD COLUMN b1 bool;\nCREATE INDEX idx_predicate_test_b1 ON idx_predicate_test(b1) WHERE b1=true;\nINSERT INTO idx_predicate_test VALUES ('2000-01-01',true);\nDROP TABLE idx_predicate_test;\n\n-- test index with table references\nCREATE TABLE idx_tableref_test(time timestamptz);\nSELECT table_name FROM create_hypertable('idx_tableref_test', 'time');\n-- we use security definer to prevent function inlining\nCREATE OR REPLACE FUNCTION tableref_func(t idx_tableref_test) RETURNS timestamptz LANGUAGE SQL IMMUTABLE SECURITY DEFINER AS $f$ SELECT t.time; $f$;\n-- try creating index with no existing chunks\nCREATE INDEX tableref_idx ON idx_tableref_test(tableref_func(idx_tableref_test));\n-- insert data to trigger chunk creation\nINSERT INTO idx_tableref_test SELECT '2000-01-01';\nDROP INDEX tableref_idx;\n-- try creating index on hypertable with existing chunks\nCREATE INDEX tableref_idx ON idx_tableref_test(tableref_func(idx_tableref_test));\n\n-- test index creation with if not exists\nCREATE TABLE idx_exists(time timestamptz NOT NULL);\nSELECT table_name FROM create_hypertable('idx_exists', 'time');\n-- should be skipped since this index was already created by create_hypertable\nCREATE INDEX IF NOT EXISTS idx_exists_time_idx ON idx_exists(time DESC);\n-- should create index\nCREATE INDEX IF NOT EXISTS idx_exists_time_asc_idx ON idx_exists(time ASC);\n-- should be skipped since it was created in previous command\nCREATE INDEX IF NOT EXISTS idx_exists_time_asc_idx ON idx_exists(time ASC);\nDROP INDEX idx_exists_time_asc_idx;\n\nINSERT INTO idx_exists VALUES ('2000-01-01'),('2001-01-01');\n-- should create index\nCREATE INDEX IF NOT EXISTS idx_exists_time_asc_idx ON idx_exists(time ASC);\n-- should be skipped since it was created in previous command\nCREATE INDEX IF NOT EXISTS idx_exists_time_asc_idx ON idx_exists(time ASC);\n\n-- test reindex\nCREATE TABLE reindex_test(time timestamp, temp float, PRIMARY KEY(time, temp));\nCREATE UNIQUE INDEX reindex_test_time_unique_idx ON reindex_test(time);\n\n-- create hypertable with three chunks\nSELECT create_hypertable('reindex_test', 'time', chunk_time_interval => 2628000000000);\n\nINSERT INTO reindex_test VALUES ('2017-01-20T09:00:01', 17.5),\n                                ('2017-01-21T09:00:01', 19.1),\n                                ('2017-04-20T09:00:01', 89.5),\n                                ('2017-04-21T09:00:01', 17.1),\n                                ('2017-06-20T09:00:01', 18.5),\n                                ('2017-06-21T09:00:01', 11.0);\n\nSELECT * FROM test.show_columns('reindex_test');\nSELECT * FROM test.show_subtables('reindex_test');\n\n-- show reindexing\nREINDEX (VERBOSE) TABLE reindex_test;\n\n\\set ON_ERROR_STOP 0\n\n-- REINDEX TABLE CONCURRENTLY on hypertables is not supported\nREINDEX TABLE CONCURRENTLY reindex_test;\n\n-- this one currently doesn't recurse to chunks and instead gives an\n-- error\nREINDEX (VERBOSE) INDEX reindex_test_time_unique_idx;\n\\set ON_ERROR_STOP 1\n\n-- show reindexing on a normal table\nCREATE TABLE reindex_norm(time timestamp, temp float);\nCREATE UNIQUE INDEX reindex_norm_time_unique_idx ON reindex_norm(time);\n\nINSERT INTO reindex_norm VALUES ('2017-01-20T09:00:01', 17.5),\n                                ('2017-01-21T09:00:01', 19.1),\n                                ('2017-04-20T09:00:01', 89.5),\n                                ('2017-04-21T09:00:01', 17.1),\n                                ('2017-06-20T09:00:01', 18.5),\n                                ('2017-06-21T09:00:01', 11.0);\n\nREINDEX (VERBOSE) TABLE reindex_norm;\nREINDEX (VERBOSE) INDEX reindex_norm_time_unique_idx;\n\nSELECT * FROM test.show_constraintsp('_timescaledb_internal._hyper_12%');\nSELECT * FROM reindex_norm;\n\nCREATE TABLE ht_dropped(time timestamptz, d0 int, d1 int, c0 int, c1 int, c2 int);\nSELECT create_hypertable('ht_dropped','time');\nINSERT INTO ht_dropped(time,c0,c1,c2) SELECT '2000-01-01',1,2,3;\nALTER TABLE ht_dropped DROP COLUMN d0;\nINSERT INTO ht_dropped(time,c0,c1,c2) SELECT '2001-01-01',1,2,3;\nALTER TABLE ht_dropped DROP COLUMN d1;\nINSERT INTO ht_dropped(time,c0,c1,c2) SELECT '2002-01-01',1,2,3;\n\nCREATE INDEX ON ht_dropped(c0,c1,c2) WHERE c1 IS NOT NULL;\nCREATE INDEX ON ht_dropped(c0,c1,c2) WITH(timescaledb.transaction_per_chunk) WHERE c2 IS NOT NULL;\n\nSELECT\n  oid::TEXT AS \"Chunk\",\n  i.*\nFROM\n  (SELECT tableoid::REGCLASS FROM ht_dropped GROUP BY tableoid) ch (oid)\n  LEFT JOIN LATERAL ( SELECT * FROM test.show_indexes (ch.oid)) i ON TRUE\nORDER BY\n  1, 2;\n\n-- #3056 check chunk index column name mapping\nCREATE TABLE i3056(c int, order_number int NOT NULL, date_created timestamptz NOT NULL);\nCREATE INDEX ON i3056(order_number) INCLUDE(order_number);\nCREATE INDEX ON i3056(date_created, (order_number % 5)) INCLUDE(order_number);\nSELECT table_name FROM create_hypertable('i3056', 'date_created');\nALTER TABLE i3056 DROP COLUMN c;\nINSERT INTO i3056(order_number,date_created) VALUES (1, '2000-01-01');\n\n-- #5908 test CREATE INDEX ON ONLY main table\nCREATE TABLE test(time timestamptz, temp float);\nSELECT create_hypertable('test', 'time');\n\nINSERT INTO test (time,temp) VALUES\n       (generate_series(TIMESTAMP '2019-08-01', TIMESTAMP '2019-08-10', INTERVAL '10 minutes'), ROUND(RANDOM()*10)::float);\nSELECT * FROM show_chunks('test');\nSELECT * FROM test.show_indexes('_timescaledb_internal._hyper_15_21_chunk');\n\n-- create index per chunk\nCREATE INDEX _hyper_15_21_chunk_test_temp_idx ON  _timescaledb_internal._hyper_15_21_chunk(temp);\nSELECT * FROM test.show_indexes('_timescaledb_internal._hyper_15_21_chunk');\nSELECT * FROM test.show_indexes('test');\n\n-- create index only on main table\nCREATE INDEX test_temp_idx ON ONLY test (time);\n\nSELECT * FROM test.show_indexes('test');\nSELECT * FROM test.show_indexes('_timescaledb_internal._hyper_15_21_chunk');\n"
  },
  {
    "path": "test/sql/information_views.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nSELECT * FROM timescaledb_information.hypertables;\n\n-- create simple hypertable with 1 chunk\nCREATE TABLE ht1(time TIMESTAMPTZ NOT NULL);\nSELECT create_hypertable('ht1','time');\nINSERT INTO ht1 SELECT '2000-01-01'::TIMESTAMPTZ;\n\n-- create simple hypertable with 1 chunk and toasted data\nCREATE TABLE ht2(time TIMESTAMPTZ NOT NULL, data TEXT);\nSELECT create_hypertable('ht2','time');\nINSERT INTO ht2 SELECT '2000-01-01'::TIMESTAMPTZ, repeat('8k',4096);\n\nSELECT * FROM timescaledb_information.hypertables\nORDER BY hypertable_schema, hypertable_name;\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\n-- create schema open and hypertable with 3 chunks\nCREATE SCHEMA open;\nGRANT USAGE ON SCHEMA open TO :ROLE_DEFAULT_PERM_USER;\nCREATE TABLE open.open_ht(time TIMESTAMPTZ NOT NULL);\nSELECT create_hypertable('open.open_ht','time');\nINSERT INTO open.open_ht SELECT '2000-01-01'::TIMESTAMPTZ;\nINSERT INTO open.open_ht SELECT '2001-01-01'::TIMESTAMPTZ;\nINSERT INTO open.open_ht SELECT '2002-01-01'::TIMESTAMPTZ;\n\n-- create schema closed and hypertable\nCREATE SCHEMA closed;\nCREATE TABLE closed.closed_ht(time TIMESTAMPTZ NOT NULL);\nSELECT create_hypertable('closed.closed_ht','time');\nINSERT INTO closed.closed_ht SELECT '2000-01-01'::TIMESTAMPTZ;\n\nSELECT * FROM timescaledb_information.hypertables\nORDER BY hypertable_schema, hypertable_name;\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n\\set ON_ERROR_STOP 0\n\\x\nSELECT * FROM timescaledb_information.hypertables\nORDER BY hypertable_schema, hypertable_name;\n\n-- filter by schema\nSELECT * FROM timescaledb_information.hypertables\nWHERE hypertable_schema = 'closed'\nORDER BY hypertable_schema, hypertable_name;\n\n-- filter by table name\nSELECT * FROM timescaledb_information.hypertables\nWHERE hypertable_name = 'ht1'\nORDER BY hypertable_schema, hypertable_name;\n\n-- filter by owner\nSELECT * FROM timescaledb_information.hypertables\nWHERE owner = 'super_user'\nORDER BY hypertable_schema, hypertable_name;\n\\x\n\n---Add integer table --\nCREATE TABLE test_table_int(time bigint, junk int);\nSELECT create_hypertable('test_table_int', 'time', chunk_time_interval => 10);\nCREATE OR REPLACE function table_int_now() returns BIGINT LANGUAGE SQL IMMUTABLE as  'SELECT 1::BIGINT';\nSELECT set_integer_now_func('test_table_int', 'table_int_now');\nINSERT into test_table_int SELECT generate_series( 1, 20), 100;\n\n\\d timescaledb_information.chunks\nSELECT hypertable_schema,\n       hypertable_name,\n       chunk_schema,\n       chunk_name,\n       primary_dimension,\n       primary_dimension_type,\n       range_start,\n       range_end,\n       range_start_integer,\n       range_end_integer,\n       is_compressed,\n       chunk_tablespace,\n       data_nodes\nFROM timescaledb_information.chunks WHERE hypertable_name = 'ht1' ORDER BY chunk_name;\n\nSELECT hypertable_schema,\n       hypertable_name,\n       chunk_schema,\n       chunk_name,\n       primary_dimension,\n       primary_dimension_type,\n       range_start,\n       range_end,\n       range_start_integer,\n       range_end_integer,\n       is_compressed,\n       chunk_tablespace,\n       data_nodes\nFROM timescaledb_information.chunks WHERE hypertable_name = 'test_table_int' ORDER BY chunk_name;\n\n\\x\nSELECT * FROM timescaledb_information.dimensions ORDER BY hypertable_name, dimension_number;\n\\x\n"
  },
  {
    "path": "test/sql/insert.sql.in",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nSET enable_seqscan TO off;\n\n\\ir include/insert_two_partitions.sql\n\nSELECT * FROM test.show_columnsp('_timescaledb_internal.%_hyper%');\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%');\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n\nSELECT * FROM \"two_Partitions\" ORDER BY \"timeCustom\", device_id, series_0, series_1;\nSELECT * FROM ONLY \"two_Partitions\";\n\nCREATE TABLE error_test(time timestamp, temp float8, device text NOT NULL);\nSELECT create_hypertable('error_test', 'time', 'device', 2);\n\n\\set QUIET off\nINSERT INTO error_test VALUES ('Mon Mar 20 09:18:20.1 2017', 21.3, 'dev1');\n\\set ON_ERROR_STOP 0\n-- generate insert error\nINSERT INTO error_test VALUES ('Mon Mar 20 09:18:22.3 2017', 21.1, NULL);\n\\set ON_ERROR_STOP 1\nINSERT INTO error_test VALUES ('Mon Mar 20 09:18:25.7 2017', 22.4, 'dev2');\n\\set QUIET on\nSELECT * FROM error_test;\n\n--test character(9) partition keys since there were issues with padding causing partitioning errors\nCREATE TABLE tick_character (\n    symbol      character(9) NOT NULL,\n    mid       REAL NOT NULL,\n    spread      REAL NOT NULL,\n    time        TIMESTAMPTZ       NOT NULL\n);\n\nSELECT create_hypertable ('tick_character', 'time', 'symbol', 2);\nINSERT INTO tick_character ( symbol, mid, spread, time ) VALUES ( 'GBPJPY', 142.639000, 5.80, 'Mon Mar 20 09:18:22.3 2017') RETURNING time, symbol, mid;\nSELECT * FROM tick_character;\n\nCREATE TABLE  date_col_test(time date, temp float8, device text NOT NULL);\nSELECT create_hypertable('date_col_test', 'time', 'device', 1000, chunk_time_interval => INTERVAL '1 Day');\nINSERT INTO date_col_test\nVALUES ('2001-02-01', 98, 'dev1'),\n('2001-03-02', 98, 'dev1');\n\nSELECT * FROM date_col_test WHERE time > '2001-01-01';\n\n-- Out-of-order insertion regression test.\n-- this used to trip an assert in subspace_store.c checking that\n-- max_open_chunks_per_insert was obeyed\nset timescaledb.max_open_chunks_per_insert=1;\n\nCREATE TABLE chunk_assert_fail(i bigint, j bigint);\nSELECT create_hypertable('chunk_assert_fail', 'i', 'j', 1000, chunk_time_interval=>1);\ninsert into chunk_assert_fail values (1, 1), (1, 2), (2,1);\nselect * from chunk_assert_fail;\n\nCREATE TABLE one_space_test(time timestamp, temp float8, device text NOT NULL);\nSELECT create_hypertable('one_space_test', 'time', 'device', 1);\nINSERT INTO one_space_test VALUES\n('2001-01-01 01:01:01', 1.0, 'device'),\n('2002-01-01 01:02:01', 1.0, 'device');\nSELECT * FROM one_space_test;\n\n--CTE & EXPLAIN ANALYZE TESTS\nWITH insert_cte as (\n\tINSERT INTO one_space_test VALUES\n\t\t('2001-01-01 01:02:01', 1.0, 'device')\n\tRETURNING *)\nSELECT * FROM insert_cte;\n\nEXPLAIN (analyze, buffers off, costs off, timing off) --can't turn summary off in 9.6 so instead grep it away at end.\nWITH insert_cte as (\n\tINSERT INTO one_space_test VALUES\n\t\t('2001-01-01 01:03:01', 1.0, 'device')\n\t)\nSELECT 1 \\g | grep -v \"Planning\" | grep -v \"Execution\"\n\n-- INSERTs can exclude chunks based on constraints\nEXPLAIN (buffers off, costs off) INSERT INTO chunk_assert_fail SELECT i, j FROM chunk_assert_fail;\nEXPLAIN (buffers off, costs off) INSERT INTO chunk_assert_fail SELECT i, j FROM chunk_assert_fail WHERE i < 1;\nEXPLAIN (buffers off, costs off) INSERT INTO chunk_assert_fail SELECT i, j FROM chunk_assert_fail WHERE i = 1;\nEXPLAIN (buffers off, costs off) INSERT INTO chunk_assert_fail SELECT i, j FROM chunk_assert_fail WHERE i > 1;\nINSERT INTO chunk_assert_fail SELECT i, j FROM chunk_assert_fail WHERE i > 1;\n\nEXPLAIN (buffers off, costs off) INSERT INTO one_space_test SELECT * FROM one_space_test WHERE time < 'infinity' LIMIT 1;\nEXPLAIN (buffers off, costs off) INSERT INTO one_space_test SELECT * FROM one_space_test WHERE time >= 'infinity' LIMIT 1;\nEXPLAIN (buffers off, costs off) INSERT INTO one_space_test SELECT * FROM one_space_test WHERE time <= '-infinity' LIMIT 1;\nEXPLAIN (buffers off, costs off) INSERT INTO one_space_test SELECT * FROM one_space_test WHERE time > '-infinity' LIMIT 1;\nINSERT INTO one_space_test SELECT * FROM one_space_test WHERE time < 'infinity' LIMIT 1;\nINSERT INTO one_space_test SELECT * FROM one_space_test WHERE time >= 'infinity' LIMIT 1;\nINSERT INTO one_space_test SELECT * FROM one_space_test WHERE time <= '-infinity' LIMIT 1;\nINSERT INTO one_space_test SELECT * FROM one_space_test WHERE time > '-infinity' LIMIT 1;\n\nCREATE TABLE timestamp_inf(time TIMESTAMP);\nSELECT create_hypertable('timestamp_inf', 'time');\n\nINSERT INTO timestamp_inf VALUES ('2018/01/02'), ('2019/01/02');\n\nEXPLAIN (buffers off, costs off) INSERT INTO timestamp_inf SELECT * FROM timestamp_inf\n    WHERE time < 'infinity' LIMIT 1;\nEXPLAIN (buffers off, costs off) INSERT INTO timestamp_inf SELECT * FROM timestamp_inf\n    WHERE time >= 'infinity' LIMIT 1;\nEXPLAIN (buffers off, costs off) INSERT INTO timestamp_inf SELECT * FROM timestamp_inf\n    WHERE time <= '-infinity' LIMIT 1;\nEXPLAIN (buffers off, costs off) INSERT INTO timestamp_inf SELECT * FROM timestamp_inf\n    WHERE time > '-infinity' LIMIT 1;\n\nCREATE TABLE date_inf(time DATE);\nSELECT create_hypertable('date_inf', 'time');\n\nINSERT INTO date_inf VALUES ('2018/01/02'), ('2019/01/02');\n\nEXPLAIN (buffers off, costs off) INSERT INTO date_inf SELECT * FROM date_inf\n    WHERE time < 'infinity' LIMIT 1;\nEXPLAIN (buffers off, costs off) INSERT INTO date_inf SELECT * FROM date_inf\n    WHERE time >= 'infinity' LIMIT 1;\nEXPLAIN (buffers off, costs off) INSERT INTO date_inf SELECT * FROM date_inf\n    WHERE time <= '-infinity' LIMIT 1;\nEXPLAIN (buffers off, costs off) INSERT INTO date_inf SELECT * FROM date_inf\n    WHERE time > '-infinity' LIMIT 1;\n\n-- test INSERT with cached plans / plpgsql functions\n-- https://github.com/timescale/timescaledb/issues/1809\nCREATE TABLE status_table(a int, b int, last_ts timestamptz, UNIQUE(a,b));\nCREATE TABLE metrics(time timestamptz NOT NULL, value float);\nCREATE TABLE metrics2(time timestamptz NOT NULL, value float);\nSELECT (create_hypertable(t,'time')).table_name FROM (VALUES ('metrics'),('metrics2')) v(t);\n\nINSERT INTO metrics VALUES ('2000-01-01',random()), ('2000-02-01',random()), ('2000-03-01',random());\n\nCREATE OR REPLACE FUNCTION insert_test() RETURNS VOID LANGUAGE plpgsql AS\n$$\n  DECLARE\n    r RECORD;\n  BEGIN\n    FOR r IN\n      SELECT * FROM metrics\n    LOOP\n      WITH foo AS (\n        INSERT INTO metrics2 SELECT * FROM metrics RETURNING *\n      )\n      INSERT INTO status_table (a,b, last_ts)\n        VALUES (1,1, now())\n        ON CONFLICT (a,b) DO UPDATE SET last_ts=(SELECT max(time) FROM metrics);\n    END LOOP;\n  END;\n$$;\n\nSELECT insert_test(), insert_test(), insert_test();\n\n-- test Postgres crashes on INSERT ... SELECT ... WHERE NOT EXISTS with empty table\n-- https://github.com/timescale/timescaledb/issues/1883\nCREATE TABLE readings (\n\ttoe TIMESTAMPTZ NOT NULL,\n\tsensor_id INT NOT NULL,\n\tvalue INT NOT NULL\n);\nSELECT create_hypertable(\n\t'readings',\n\t'toe',\n\tchunk_time_interval => interval '1 day',\n\tif_not_exists => TRUE,\n\tmigrate_data => TRUE\n);\nEXPLAIN (buffers off, costs off)\nINSERT INTO readings\nSELECT '2020-05-09 10:34:35.296288+00', 1, 0\nWHERE NOT EXISTS (\n\tSELECT 1\n\tFROM readings\n\tWHERE sensor_id = 1\n\t\tAND toe = '2020-05-09 10:34:35.296288+00'\n);\nINSERT INTO readings\nSELECT '2020-05-09 10:34:35.296288+00', 1, 0\nWHERE NOT EXISTS (\n\tSELECT 1\n\tFROM readings\n\tWHERE sensor_id = 1\n\t\tAND toe = '2020-05-09 10:34:35.296288+00'\n);\nDROP TABLE readings;\nCREATE TABLE sample_table (\n       sequence INTEGER NOT NULL,\n       time TIMESTAMP WITHOUT TIME ZONE NOT NULL,\n       value NUMERIC NOT NULL,\n       UNIQUE (sequence, time)\n);\nSELECT * FROM create_hypertable('sample_table', 'time',\n       chunk_time_interval => INTERVAL '1 day');\nINSERT INTO sample_table (sequence,time,value) VALUES\n       (7, generate_series(TIMESTAMP '2019-08-01', TIMESTAMP '2019-08-10', INTERVAL '10 minutes'), ROUND(RANDOM()*10)::int);\n\\set ON_ERROR_STOP 0\nINSERT INTO sample_table (sequence,time,value) VALUES\n       (7, generate_series(TIMESTAMP '2019-07-21', TIMESTAMP '2019-08-01', INTERVAL '10 minutes'), ROUND(RANDOM()*10)::int);\n\\set ON_ERROR_STOP 1\nINSERT INTO sample_table (sequence,time,value) VALUES\n       (7,generate_series(TIMESTAMP '2019-01-01', TIMESTAMP '2019-07-01', '10 minutes'), ROUND(RANDOM()*10)::int);\n\nDROP TABLE sample_table;\n\n-- test on conflict clause on columns with default value\n-- issue #3037\nCREATE TABLE i3037(time timestamptz PRIMARY KEY);\nSELECT create_hypertable('i3037','time');\nALTER TABLE i3037 ADD COLUMN value float DEFAULT 0;\n\nINSERT INTO i3037 VALUES ('2000-01-01');\nINSERT INTO i3037 VALUES ('2000-01-01') ON CONFLICT(time) DO UPDATE SET value = EXCLUDED.value;\n\n-- test inserting into chunks directly\nCREATE TABLE direct_insert(time timestamptz, meta text) WITH (tsdb.hypertable);\n\nINSERT INTO direct_insert VALUES ('2020-01-01');\nSELECT show_chunks('direct_insert') AS \"CHUNK\" \\gset\n\n--should have ModifyHyperable node\nEXPLAIN (costs off, timing off, summary off) INSERT INTO :CHUNK VALUES ('2020-01-01');\n\n-- correct time range should succeed\nINSERT INTO :CHUNK VALUES ('2020-01-01') RETURNING *;\n-- incorrect time range should fail\n\\set ON_ERROR_STOP 0\nINSERT INTO :CHUNK VALUES ('2020-01-01'), ('2021-01-01') RETURNING *;\nINSERT INTO :CHUNK VALUES ('2025-01-01') RETURNING *;\n\\set ON_ERROR_STOP 1\n\n-- test that triggers on chunks work\nCREATE FUNCTION test_trigger() RETURNS TRIGGER AS $$\nBEGIN\n  RAISE NOTICE 'trigger called';\n  NEW.meta = 'triggered';\n  RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nCREATE TRIGGER test_trigger BEFORE INSERT ON direct_insert FOR EACH ROW EXECUTE PROCEDURE test_trigger();\nINSERT INTO :CHUNK VALUES ('2020-01-01') RETURNING *;\n\n-- test upsert\nDELETE FROM direct_insert;\nALTER TABLE direct_insert ADD CONSTRAINT direct_insert_pkey PRIMARY KEY (time);\nINSERT INTO :CHUNK VALUES ('2020-01-01') RETURNING *;\n\n-- DO NOTHING should succeed\nINSERT INTO :CHUNK VALUES ('2020-01-01') ON CONFLICT DO NOTHING RETURNING *;\nINSERT INTO :CHUNK VALUES ('2020-01-01') ON CONFLICT (time) DO UPDATE SET meta = 'update' RETURNING *;\n\n\\set ON_ERROR_STOP 0\n-- conflict should fail\nINSERT INTO :CHUNK VALUES ('2020-01-01') RETURNING *;\nINSERT INTO :CHUNK VALUES ('2020-01-01') ON CONFLICT (time) DO UPDATE SET time = '2000-01-01' RETURNING *;\nINSERT INTO :CHUNK VALUES ('2020-01-01') ON CONFLICT (time) DO UPDATE SET time = EXCLUDED.time + '1 year' RETURNING *;\n\\set ON_ERROR_STOP 1\n\n"
  },
  {
    "path": "test/sql/insert_many.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE TABLE  many_partitions_test(time timestamp, temp float8, device text NOT NULL);\nSELECT create_hypertable('many_partitions_test', 'time', 'device', 1000);\n--NOTE: how much slower the first two queries are -- they are creating chunks\nINSERT INTO many_partitions_test\n    SELECT to_timestamp(ser), ser, ser::text FROM generate_series(1,100) ser;\nINSERT INTO many_partitions_test\n    SELECT to_timestamp(ser), ser, ser::text FROM generate_series(101,200) ser;\nINSERT INTO many_partitions_test\n    SELECT to_timestamp(ser), ser, (ser-201)::text FROM generate_series(201,300) ser;\n\nSELECT * FROM  many_partitions_test ORDER BY time DESC LIMIT 2;\nSELECT count(*) FROM  many_partitions_test;\n\nCREATE TABLE many_partitions_test_1m (time timestamp, temp float8, device text NOT NULL);\nSELECT create_hypertable('many_partitions_test_1m', 'time', 'device', 1000);\n\nEXPLAIN (verbose on, buffers off, costs off)\nINSERT INTO many_partitions_test_1m(time, temp, device)\nSELECT time_bucket('1 minute', time) AS period, avg(temp), device\nFROM many_partitions_test\nGROUP BY period, device;\n\nINSERT INTO many_partitions_test_1m(time, temp, device)\nSELECT time_bucket('1 minute', time) AS period, avg(temp), device\nFROM many_partitions_test\nGROUP BY period, device;\n\nSELECT * FROM many_partitions_test_1m ORDER BY time, device LIMIT 10;\n"
  },
  {
    "path": "test/sql/insert_returning.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE TABLE standard_table (\n    standard_id integer PRIMARY KEY,\n    name text not null\n);\n\nCREATE TABLE hypertable (\n    time timestamptz not null,\n    name text not null,\n    standard_id integer not null\n);\n\nselect * from create_hypertable('hypertable', 'time');\n\nINSERT INTO standard_table (standard_id, name)\nVALUES (1, 'standard_1');\n\nINSERT INTO hypertable (time, name, standard_id)\nVALUES ('2021-01-01 01:01:01+00', 'hypertable_1', 1);\n\nINSERT INTO hypertable (time, name, standard_id)\nVALUES ('2022-02-02 02:02:02+00', 'hypertable_2', 1)\nRETURNING *, EXISTS (\n  SELECT *\n  FROM standard_table\n  WHERE standard_table.standard_id = hypertable.standard_id\n);\n\nINSERT INTO hypertable (time, name, standard_id)\nVALUES ('2023-03-03 03:03:03+00', 'hypertable_3', 1)\nRETURNING *, EXISTS (\n  SELECT *\n  FROM standard_table\n  WHERE standard_table.standard_id = hypertable.standard_id\n);\n\nINSERT INTO hypertable (time, name, standard_id)\nVALUES ('2024-04-04 04:04:04+00', 'hypertable_4', 2)\nRETURNING *, EXISTS (\n  SELECT *\n  FROM standard_table\n  WHERE standard_table.standard_id = hypertable.standard_id\n);"
  },
  {
    "path": "test/sql/insert_returning_old_new.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Test OLD/NEW references in RETURNING clause (PG18+ feature)\n\nCREATE TABLE ht_returning(\n    time timestamptz NOT NULL,\n    value int NOT NULL,\n    UNIQUE(time)\n) WITH (tsdb.hypertable);\n\n-- Insert initial rows\nINSERT INTO ht_returning(time, value) VALUES ('2024-01-01', 10);\nINSERT INTO ht_returning(time, value) VALUES ('2024-01-02', 20);\n\n-- Test 1: INSERT ON CONFLICT DO UPDATE with RETURNING OLD.col, NEW.col\n-- This should show the old value (10) and the new value (100)\nINSERT INTO ht_returning(time, value) VALUES ('2024-01-01', 100)\nON CONFLICT (time) DO UPDATE SET value = EXCLUDED.value\nRETURNING OLD.value AS old_val, NEW.value AS new_val;\n\n-- Test 2: INSERT ON CONFLICT DO UPDATE with RETURNING arithmetic on OLD/NEW\nINSERT INTO ht_returning(time, value) VALUES ('2024-01-02', 50)\nON CONFLICT (time) DO UPDATE SET value = EXCLUDED.value\nRETURNING OLD.value AS old_val, NEW.value AS new_val, NEW.value - OLD.value AS diff;\n\n-- Test 3: Plain INSERT with RETURNING NEW.col (OLD should be NULL for fresh inserts)\nINSERT INTO ht_returning(time, value) VALUES ('2024-01-03', 30)\nRETURNING OLD.value AS old_val, NEW.value AS new_val;\n\n-- Test 4: MERGE with both UPDATE and INSERT paths, returning OLD/NEW values and merge_action()\nCREATE TABLE t1(time timestamptz NOT NULL, value int NOT NULL);\nINSERT INTO t1(time, value) VALUES ('2024-01-01', 5);   -- Will trigger UPDATE (existing row)\nINSERT INTO t1(time, value) VALUES ('2024-01-05', 10);   -- Will trigger INSERT (new row)\n\nMERGE INTO ht_returning AS t\nUSING t1 AS s\nON t.time = s.time\nWHEN MATCHED THEN\n    UPDATE SET\n        value = t.value + s.value\nWHEN NOT MATCHED THEN\n    INSERT (time, value)\n    VALUES (s.time, s.value)\nRETURNING NEW.time, NEW.value, t.value, OLD.value, s.value, merge_action();\n\n-- Verify final state\nSELECT * FROM ht_returning ORDER BY time;\n\n-- Cleanup\nDROP TABLE ht_returning;\n"
  },
  {
    "path": "test/sql/insert_single.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\ir include/insert_single.sql\n\nSELECT * FROM test.show_columnsp('\"one_Partition\".%');\nSELECT * FROM \"one_Partition\" ORDER BY \"timeCustom\", device_id, series_0, series_1, series_2;\n\n--test that we can insert data into a 1-dimensional table (only time partitioning)\nCREATE TABLE \"1dim\"(time timestamp PRIMARY KEY, temp float);\nSELECT create_hypertable('\"1dim\"', 'time');\nINSERT INTO \"1dim\" VALUES('2017-01-20T09:00:01', 22.5) RETURNING *;\nINSERT INTO \"1dim\" VALUES('2017-01-20T09:00:21', 21.2);\nINSERT INTO \"1dim\" VALUES('2017-01-20T09:00:47', 25.1);\nSELECT * FROM \"1dim\";\n\nCREATE TABLE regular_table (time timestamp, temp float);\nINSERT INTO regular_table SELECT * FROM \"1dim\";\nSELECT * FROM regular_table;\n\nTRUNCATE TABLE regular_table;\nINSERT INTO regular_table VALUES('2017-01-20T09:00:59', 29.2);\nINSERT INTO \"1dim\" SELECT * FROM regular_table;\nSELECT * FROM \"1dim\";\nSELECT \"1dim\" FROM \"1dim\";\n\n--test that we can insert pre-1970 dates\nCREATE TABLE \"1dim_pre1970\"(time timestamp PRIMARY KEY, temp float);\nSELECT create_hypertable('\"1dim_pre1970\"', 'time', chunk_time_interval=> INTERVAL '1 Month');\nINSERT INTO \"1dim_pre1970\" VALUES('1969-12-01T19:00:00', 21.2);\nINSERT INTO \"1dim_pre1970\" VALUES('1969-12-20T09:00:00', 25.1);\nINSERT INTO \"1dim_pre1970\" VALUES('1970-01-20T09:00:00', 26.6);\nINSERT INTO \"1dim_pre1970\" VALUES('1969-02-20T09:00:00', 29.9);\n\n--should show warning\nBEGIN;\nCREATE TABLE \"1dim_usec_interval\"(time timestamp PRIMARY KEY, temp float);\nSELECT create_hypertable('\"1dim_usec_interval\"', 'time', chunk_time_interval=> 10);\nINSERT INTO \"1dim_usec_interval\" VALUES('1969-12-01T19:00:00', 21.2);\nROLLBACK;\n\nCREATE TABLE \"1dim_usec_interval\"(time timestamp PRIMARY KEY, temp float);\nSELECT create_hypertable('\"1dim_usec_interval\"', 'time', chunk_time_interval=> 1000000);\nINSERT INTO \"1dim_usec_interval\" VALUES('1969-12-01T19:00:00', 21.2);\n\nCREATE TABLE \"1dim_neg\"(time INTEGER, temp float);\nSELECT create_hypertable('\"1dim_neg\"', 'time', chunk_time_interval=>10);\nINSERT INTO \"1dim_neg\" VALUES (-20, 21.2);\nINSERT INTO \"1dim_neg\" VALUES (-19, 21.2);\nINSERT INTO \"1dim_neg\" VALUES (-1, 21.2);\nINSERT INTO \"1dim_neg\" VALUES (0, 21.2);\nINSERT INTO \"1dim_neg\" VALUES (1, 21.2);\nINSERT INTO \"1dim_neg\" VALUES (19, 21.2);\nINSERT INTO \"1dim_neg\" VALUES (20, 21.2);\nSELECT * FROM \"1dim_pre1970\";\nSELECT * FROM \"1dim_neg\";\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\nSELECT * FROM _timescaledb_catalog.dimension_slice;\n\n\n-- Create a three-dimensional table\nCREATE TABLE \"3dim\" (time timestamp, temp float, device text, location text);\nSELECT create_hypertable('\"3dim\"', 'time', 'device', 2);\nSELECT add_dimension('\"3dim\"', 'location', 2);\nINSERT INTO \"3dim\" VALUES('2017-01-20T09:00:01', 22.5, 'blue', 'nyc');\nINSERT INTO \"3dim\" VALUES('2017-01-20T09:00:21', 21.2, 'brown', 'sthlm');\nINSERT INTO \"3dim\" VALUES('2017-01-20T09:00:47', 25.1, 'yellow', 'la');\n\n--show the constraints on the three-dimensional chunk\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_7_16_chunk');\n\n--queries should work in three dimensions\nSELECT * FROM \"3dim\";\n\n-- test that explain works\nEXPLAIN (BUFFERS FALSE, COSTS FALSE)\nINSERT INTO \"3dim\" VALUES('2017-01-21T09:00:01', 32.9, 'green', 'nyc'),\n                         ('2017-01-21T09:00:47', 27.3, 'purple', 'la') RETURNING *;\n\nEXPLAIN (BUFFERS FALSE, COSTS FALSE)\nWITH \"3dim_insert\" AS (\n     INSERT INTO \"3dim\" VALUES('2017-01-21T09:01:44', 19.3, 'black', 'la') RETURNING time, temp\n), regular_insert AS (\n   INSERT INTO regular_table VALUES('2017-01-21T10:00:51', 14.3) RETURNING time, temp\n) INSERT INTO \"1dim\" (SELECT time, temp FROM \"3dim_insert\" UNION SELECT time, temp FROM regular_insert);\n\n-- test prepared statement INSERT\nPREPARE \"1dim_plan\" (timestamp, float) AS\nINSERT INTO \"1dim\" VALUES($1, $2) ON CONFLICT (time) DO NOTHING;\nEXECUTE \"1dim_plan\" ('2017-04-17 23:35', 31.4);\nEXECUTE \"1dim_plan\" ('2017-04-17 23:35', 32.6);\n\n-- test prepared statement with generic plan (forced when no parameters)\nPREPARE \"1dim_plan_generic\" AS\nINSERT INTO \"1dim\" VALUES('2017-05-18 17:24', 18.3);\nEXECUTE \"1dim_plan_generic\";\n\nSELECT * FROM \"1dim\" ORDER BY time;\nSELECT * FROM \"3dim\" ORDER BY (time, device);\n\n-- Test large intervals as default interval for integer is\n-- supported as part of hypertable generalization\n\\set ON_ERROR_STOP 0\nCREATE TABLE \"inttime_err\"(time INTEGER PRIMARY KEY, temp float);\nSELECT create_hypertable('\"inttime_err\"', 'time', chunk_time_interval=>2147483648);\n\\set ON_ERROR_STOP 1\nSELECT create_hypertable('\"inttime_err\"', 'time', chunk_time_interval=>2147483647);\n\n-- Test large intervals as default interval is supported\n-- for integer types as part of hypertable generalization.\n\n\\set ON_ERROR_STOP 0\nCREATE TABLE \"smallinttime_err\"(time SMALLINT PRIMARY KEY, temp float);\nSELECT create_hypertable('\"smallinttime_err\"', 'time', chunk_time_interval=>32768);\n\\set ON_ERROR_STOP 1\nSELECT create_hypertable('\"smallinttime_err\"', 'time', chunk_time_interval=>32767);\n\n--make sure date inserts work even when the timezone changes the\nCREATE TABLE hyper_date(time date, temp float);\nSELECT create_hypertable('\"hyper_date\"', 'time');\nSET timezone=+1;\nINSERT INTO \"hyper_date\" VALUES('2011-01-26', 22.5);\nRESET timezone;\n\n--make sure timestamp inserts work even when the timezone changes the\nSET timezone = 'UTC';\nCREATE TABLE \"test_tz\"(time timestamp PRIMARY KEY, temp float);\nSELECT create_hypertable('\"test_tz\"', 'time', chunk_time_interval=> INTERVAL '1 day');\nINSERT INTO \"test_tz\" VALUES('2017-09-22 10:00:00', 21.2);\nINSERT INTO \"test_tz\" VALUES('2017-09-21 19:00:00', 21.2);\nSET timezone = 'US/central';\nINSERT INTO \"test_tz\" VALUES('2017-09-21 19:01:00', 21.2);\n\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_10_20_chunk');\nSELECT * FROM test_tz;\n\n-- test various memory settings --\nSET timescaledb.max_open_chunks_per_insert = 10;\nSET timescaledb.max_cached_chunks_per_hypertable = 10;\nCREATE TABLE \"nondefault_mem_settings\"(time timestamp PRIMARY KEY, temp float);\nSELECT create_hypertable('\"nondefault_mem_settings\"', 'time', chunk_time_interval=> INTERVAL '1 Month');\nINSERT INTO \"nondefault_mem_settings\" VALUES('2000-12-01T19:00:00', 21.2);\nINSERT INTO \"nondefault_mem_settings\" VALUES('2001-12-20T09:00:00', 25.1);\n\n--lowest possible\nSET timescaledb.max_open_chunks_per_insert = 1;\nSET timescaledb.max_cached_chunks_per_hypertable = 1;\nINSERT INTO \"nondefault_mem_settings\" VALUES\n('2001-01-20T09:00:00', 26.6),\n('2002-02-20T09:00:00', 27.9),\n('2003-02-20T09:00:00', 28.9);\nINSERT INTO \"nondefault_mem_settings\" VALUES\n('2001-03-20T09:00:00', 30.6),\n('2002-03-20T09:00:00', 31.9),\n('2003-03-20T09:00:00', 32.9);\n\n--warning about mismatched cache sizes\nSET timescaledb.max_open_chunks_per_insert = 100;\nSET timescaledb.max_cached_chunks_per_hypertable = 10;\nINSERT INTO \"nondefault_mem_settings\" VALUES\n('2001-05-20T09:00:00', 36.6),\n('2002-05-20T09:00:00', 37.9),\n('2003-05-20T09:00:00', 38.9);\n\n--unlimited\nSET timescaledb.max_open_chunks_per_insert = 0;\nSET timescaledb.max_cached_chunks_per_hypertable = 0;\nINSERT INTO \"nondefault_mem_settings\" VALUES\n('2001-04-20T09:00:00', 33.6),\n('2002-04-20T09:00:00', 34.9),\n('2003-04-20T09:00:00', 35.9);\n\nSELECT * FROM \"nondefault_mem_settings\";\n\n\n--test rollback\nBEGIN;\n\\set QUIET off\nCREATE TABLE \"data_records\" (\"time\" bigint NOT NULL, \"value\" integer CHECK (VALUE >= 0));\nSELECT create_hypertable('data_records', 'time', chunk_time_interval => 2592000000);\n\nINSERT INTO \"data_records\" (\"time\", \"value\") VALUES (0, 1);\nSAVEPOINT savepoint_1;\nINSERT INTO \"data_records\" (\"time\", \"value\") VALUES (1, 0);\nROLLBACK TO SAVEPOINT savepoint_1;\nINSERT INTO \"data_records\" (\"time\", \"value\") VALUES (2, 1);\n\nSAVEPOINT savepoint_2;\n\\set ON_ERROR_STOP 0\nINSERT INTO \"data_records\" (\"time\", \"value\") VALUES (3, -1);\n\\set ON_ERROR_STOP 1\nROLLBACK TO SAVEPOINT savepoint_2;\nINSERT INTO \"data_records\" (\"time\", \"value\") VALUES (4, 1);\n\nSAVEPOINT savepoint_3;\nINSERT INTO \"data_records\" (\"time\", \"value\") VALUES (5, 0);\nROLLBACK TO SAVEPOINT savepoint_3;\n\nSELECT * FROM data_records;\n\n\\set QUIET on\nROLLBACK;\n\n-- Test INSERT into hypertable with a generated column whose type is a\n-- domain with a NOT NULL constraint\nCREATE DOMAIN nn_int AS int CHECK (VALUE IS NOT NULL);\n\nCREATE TABLE generated_col_ht(\n    time timestamptz NOT NULL,\n    val int NOT NULL,\n    doubled nn_int GENERATED ALWAYS AS (val * 2) STORED\n);\nSELECT create_hypertable('generated_col_ht', 'time');\n\nINSERT INTO generated_col_ht(time, val) VALUES ('2024-01-01', 5);\nINSERT INTO generated_col_ht(time, val) VALUES ('2024-01-02', 10) RETURNING *;\n\nSELECT * FROM generated_col_ht ORDER BY time;\n\nDROP TABLE generated_col_ht;\nDROP DOMAIN nn_int;\n"
  },
  {
    "path": "test/sql/lateral.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE TABLE regular_table(name text, junk text);\nCREATE TABLE ht(time timestamptz NOT NULL, location text);\nSELECT create_hypertable('ht', 'time');\n\nINSERT INTO ht(time) select timestamp 'epoch' + (i * interval '1 second') from generate_series(1, 100) as T(i);\nINSERT INTO regular_table values('name', 'junk');\n\nSELECT * FROM regular_table ik LEFT JOIN LATERAL (select max(time::timestamptz) from ht s where ik.name='name' and s.time < now()) s on true;\nselect * from regular_table ik LEFT JOIN LATERAL (select max(time::timestamptz) from ht s where ik.name='name' and s.time > now()) s on true;\n\nDROP TABLE regular_table;\nDROP TABLE ht;\n\nCREATE TABLE orders(id int, user_id int, time TIMESTAMPTZ NOT NULL);\nSELECT create_hypertable('orders', 'time');\nINSERT INTO orders values(1,1,timestamp 'epoch' + '1 second');\nINSERT INTO orders values(2,1,timestamp 'epoch' + '2 second');\nINSERT INTO orders values(3,1,timestamp 'epoch' + '3 second');\nINSERT INTO orders values(4,2,timestamp 'epoch' + '4 second');\nINSERT INTO orders values(5,1,timestamp 'epoch' + '5 second');\nINSERT INTO orders values(6,3,timestamp 'epoch' + '6 second');\nINSERT INTO orders values(7,1,timestamp 'epoch' + '7 second');\nINSERT INTO orders values(8,4,timestamp 'epoch' + '8 second');\nINSERT INTO orders values(9,2,timestamp 'epoch' + '9 second');\n\n-- Need a LATERAL query with a reference to the upper-level table and\n-- with a restriction on time\n-- Upper-level table constraint should be a constant in order to trigger\n-- creation of a one-time filter in the planner\nSELECT user_id, first_order_time, max_time FROM\n(SELECT user_id, min(time) AS first_order_time FROM orders GROUP BY user_id) o1\nLEFT JOIN LATERAL\n(SELECT max(time) AS max_time FROM orders WHERE o1.user_id = '2' AND time > now()) o2 ON true\nORDER BY user_id, first_order_time, max_time;\n\nSELECT user_id, first_order_time, max_time FROM\n(SELECT user_id, min(time) AS first_order_time FROM orders GROUP BY user_id) o1\nLEFT JOIN LATERAL\n(SELECT max(time) AS max_time FROM orders WHERE o1.user_id = '2' AND time < now()) o2 ON true\nORDER BY user_id, first_order_time, max_time;\n\n-- Nested LATERALs\nSELECT user_id, first_order_time, time1, min_time FROM\n(SELECT user_id, min(time) AS first_order_time FROM orders GROUP BY user_id) o1\nLEFT JOIN LATERAL\n(SELECT user_id as o2user_id, time AS time1 FROM orders WHERE o1.user_id = '2' AND time < now()) o2 ON true\nLEFT JOIN LATERAL\n(SELECT min(time) as min_time FROM orders WHERE o2.o2user_id = '1' AND time < now()) o3 ON true\nORDER BY user_id, first_order_time, time1, min_time;\n\n-- Cleanup\nDROP TABLE orders;\n\n---- OUTER JOIN tests ---\n--github issue 2500\n\nCREATE TABLE t1_timescale (a int, b int);\nCREATE TABLE t2 (a int, b int);\nSELECT create_hypertable('t1_timescale', 'a', chunk_time_interval=>1000);\n\nINSERT into t2 values (3, 3), (15 , 15);\nINSERT into t1_timescale select generate_series(5, 25, 1), 77;\nUPDATE t1_timescale SET b = 15 WHERE a = 15;\n\nSELECT * FROM t1_timescale\nFULL OUTER JOIN  t2 on t1_timescale.b=t2.b and t2.b between 10 and 20\nORDER BY 1, 2, 3, 4;\n\nSELECT * FROM t1_timescale\nLEFT OUTER JOIN  t2 on t1_timescale.b=t2.b and t2.b between 10 and 20\nWHERE t1_timescale.a=5\nORDER BY 1, 2, 3, 4;\n\nSELECT * FROM t1_timescale\nRIGHT JOIN  t2 on t1_timescale.b=t2.b and t2.b between 10 and 20\nORDER BY 1, 2, 3, 4;\n\nSELECT * FROM t1_timescale\nRIGHT JOIN  t2 on t1_timescale.b=t2.b and t2.b between 10 and 20\nWHERE t1_timescale.a=5\nORDER BY 1, 2, 3, 4;\n\nSELECT * FROM t1_timescale\nLEFT OUTER JOIN  t2 on t1_timescale.a=t2.a and t2.b between 10 and 20\nWHERE t1_timescale.a IN ( 10, 15, 20, 25)\nORDER BY 1, 2, 3, 4;\n\nSELECT * FROM t1_timescale\nRIGHT OUTER JOIN  t2 on t1_timescale.a=t2.a and t2.b between 10 and 20\nORDER BY 1, 2, 3, 4;\n"
  },
  {
    "path": "test/sql/license.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\n\\set ECHO queries\n\\set VERBOSITY default\n\nSHOW timescaledb.license;\nSELECT _timescaledb_functions.tsl_loaded();\n\n-- User shouldn't be able to change the license in the session\n\\set ON_ERROR_STOP 0\nSET timescaledb.license='apache';\nSET timescaledb.license='timescale';\nSET timescaledb.license='something_else';\n\\set ON_ERROR_STOP 1\n\n-- make sure apache license blocks tsl features\n\\set ON_ERROR_STOP 0\n\nSELECT locf(1);\nSELECT interpolate(1);\nSELECT time_bucket_gapfill(1,1,1,1);\n\nCREATE OR REPLACE FUNCTION custom_func(jobid int, args jsonb) RETURNS VOID AS $$\nDECLARE\nBEGIN\nEND;\n$$ LANGUAGE plpgsql;\nSELECT add_job('custom_func','1h', config:='{\"type\":\"function\"}'::jsonb);\nDROP FUNCTION custom_func;\n\nCREATE TABLE metrics(time timestamptz NOT NULL, value float);\nSELECT create_hypertable('metrics', 'time');\nALTER TABLE metrics SET (timescaledb.compress);\n\nINSERT INTO metrics\nVALUES ('2022-01-01 00:00:00', 1), ('2022-01-01 01:00:00', 2), ('2022-01-01 02:00:00', 3);\n\nCREATE MATERIALIZED VIEW metrics_hourly\nWITH (timescaledb.continuous) AS\nSELECT time_bucket(INTERVAL '1 hour', time) AS bucket,\n   AVG(value),\n   MAX(value),\n   MIN(value)\nFROM metrics\nGROUP BY bucket\nWITH NO DATA;\n\nCREATE MATERIALIZED VIEW metrics_hourly\nAS\nSELECT time_bucket(INTERVAL '1 hour', time) AS bucket,\n   AVG(value),\n   MAX(value),\n   MIN(value)\nFROM metrics\nGROUP BY bucket;\n\nCALL refresh_continuous_aggregate('metrics_hourly', NULL, NULL);\n\nDROP MATERIALIZED VIEW metrics_hourly;\nDROP TABLE metrics;\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "test/sql/loader/CMakeLists.txt",
    "content": "if(CMAKE_BUILD_TYPE MATCHES Debug)\n  install(\n    FILES timescaledb--mock-1.sql\n          timescaledb--mock-2.sql\n          timescaledb--mock-3.sql\n          timescaledb--mock-4.sql\n          timescaledb--mock-5.sql\n          timescaledb--mock-6.sql\n          timescaledb--mock-broken.sql\n          timescaledb--mock-1--mock-2.sql\n          timescaledb--mock-2--mock-3.sql\n          timescaledb--mock-3--mock-4.sql\n          timescaledb--mock-5--mock-6.sql\n          timescaledb--mock-broken--mock-5.sql\n          timescaledb--0.0.0.sql\n          timescaledb_osm.control\n          timescaledb_osm--mock-1.sql\n    DESTINATION \"${PG_SHAREDIR}/extension\")\nendif(CMAKE_BUILD_TYPE MATCHES Debug)\n"
  },
  {
    "path": "test/sql/loader/timescaledb--0.0.0.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n"
  },
  {
    "path": "test/sql/loader/timescaledb--mock-1--mock-2.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n--test that the extension is deactivated during upgrade\nSELECT 1;\nSELECT 1;\nSELECT 1;\nSELECT 1;\nCREATE OR REPLACE FUNCTION mock_function() RETURNS VOID\n    AS '$libdir/timescaledb-mock-2', 'ts_mock_function' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\n"
  },
  {
    "path": "test/sql/loader/timescaledb--mock-1.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE SCHEMA IF NOT EXISTS _timescaledb_cache;\nCREATE TABLE IF NOT EXISTS  _timescaledb_cache.cache_inval_extension();\nCREATE OR REPLACE FUNCTION mock_function() RETURNS VOID\n    AS '$libdir/timescaledb-mock-1', 'ts_mock_function' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n"
  },
  {
    "path": "test/sql/loader/timescaledb--mock-2--mock-3.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nbroken sql;\nCREATE OR REPLACE FUNCTION mock_function() RETURNS VOID\n    AS '$libdir/timescaledb-mock-3', 'ts_mock_function' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n"
  },
  {
    "path": "test/sql/loader/timescaledb--mock-2.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n--create function before proxy table\nCREATE OR REPLACE FUNCTION mock_function() RETURNS VOID\n    AS '$libdir/timescaledb-mock-2', 'ts_mock_function' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nCREATE SCHEMA IF NOT EXISTS _timescaledb_cache;\nCREATE TABLE IF NOT EXISTS  _timescaledb_cache.cache_inval_extension();\n\n"
  },
  {
    "path": "test/sql/loader/timescaledb--mock-3--mock-4.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n--test that the extension is deactivated during upgrade\nSELECT 1;\nSELECT 1;\nSELECT 1;\nSELECT 1;\nCREATE OR REPLACE FUNCTION mock_function() RETURNS VOID\n    AS '$libdir/timescaledb-mock-4', 'ts_mock_function' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n"
  },
  {
    "path": "test/sql/loader/timescaledb--mock-3.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE SCHEMA IF NOT EXISTS _timescaledb_cache;\nCREATE TABLE IF NOT EXISTS  _timescaledb_cache.cache_inval_extension();\nCREATE OR REPLACE FUNCTION mock_function() RETURNS VOID\n    AS '$libdir/timescaledb-mock-3', 'ts_mock_function' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n"
  },
  {
    "path": "test/sql/loader/timescaledb--mock-4.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE SCHEMA IF NOT EXISTS _timescaledb_cache;\nCREATE TABLE IF NOT EXISTS  _timescaledb_cache.cache_inval_extension();\nCREATE OR REPLACE FUNCTION mock_function() RETURNS VOID\n    AS '$libdir/timescaledb-mock-4', 'ts_mock_function' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n"
  },
  {
    "path": "test/sql/loader/timescaledb--mock-5--mock-6.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n--test that the extension is deactivated during upgrade\nSELECT 1;\nSELECT 1;\nSELECT 1;\nSELECT 1;\n--intentionally forget to updat func\n--CREATE OR REPLACE FUNCTION mock_function() RETURNS VOID\n--    AS '$libdir/timescaledb-mock-6', 'ts_mock_function' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n"
  },
  {
    "path": "test/sql/loader/timescaledb--mock-5.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE SCHEMA IF NOT EXISTS _timescaledb_cache;\nCREATE TABLE IF NOT EXISTS  _timescaledb_cache.cache_inval_extension();\nCREATE OR REPLACE FUNCTION mock_function() RETURNS VOID\n    AS '$libdir/timescaledb-mock-5', 'ts_mock_function' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n"
  },
  {
    "path": "test/sql/loader/timescaledb--mock-6.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE SCHEMA IF NOT EXISTS _timescaledb_cache;\nCREATE TABLE IF NOT EXISTS  _timescaledb_cache.cache_inval_extension();\nCREATE OR REPLACE FUNCTION mock_function() RETURNS VOID\n    AS '$libdir/timescaledb-mock-6', 'ts_mock_function' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n"
  },
  {
    "path": "test/sql/loader/timescaledb--mock-broken--mock-5.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n--test that the extension is deactivated during upgrade\nSELECT 1;\nSELECT 1;\nSELECT 1;\nSELECT 1;\nCREATE OR REPLACE FUNCTION mock_function() RETURNS VOID\n   AS '$libdir/timescaledb-mock-5', 'ts_mock_function' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n"
  },
  {
    "path": "test/sql/loader/timescaledb--mock-broken.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE SCHEMA IF NOT EXISTS _timescaledb_cache;\nCREATE TABLE IF NOT EXISTS  _timescaledb_cache.cache_inval_extension();\nCREATE OR REPLACE FUNCTION mock_function() RETURNS VOID\n    AS '$libdir/timescaledb-mock-broken', 'ts_mock_function' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n"
  },
  {
    "path": "test/sql/loader/timescaledb_osm--mock-1.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE SCHEMA _osm_catalog;\nCREATE TABLE _osm_catalog.metadata();\n\nCREATE OR REPLACE FUNCTION mock_osm() RETURNS VOID\n    AS '$libdir/timescaledb_osm-mock-1', 'ts_mock_osm' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n"
  },
  {
    "path": "test/sql/loader/timescaledb_osm.control",
    "content": "comment = 'Manages object storage on S3'\ndefault_version = '0'\nmodule_pathname = '$libdir/timescaledb_osm-0'\nrelocatable = false\nsuperuser = false\nrequires = timescaledb\n"
  },
  {
    "path": "test/sql/loader.sql.in",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\set TEST_DBNAME_2 :TEST_DBNAME _2\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\nCREATE DATABASE :\"TEST_DBNAME_2\";\n\nDROP EXTENSION timescaledb;\n--no extension\nSELECT * FROM test.extension;\nSELECT 1;\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE EXTENSION timescaledb VERSION 'mock-1';\nSELECT 1;\nSELECT * FROM test.extension;\n\nCREATE EXTENSION IF NOT EXISTS timescaledb VERSION 'mock-1';\nCREATE EXTENSION IF NOT EXISTS timescaledb VERSION 'mock-2';\n\nDROP EXTENSION timescaledb;\n\\set ON_ERROR_STOP 0\n--test that we cannot accidentally load another library version\nCREATE EXTENSION IF NOT EXISTS timescaledb VERSION 'mock-2';\n\\set ON_ERROR_STOP 1\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n--no extension\nSELECT * FROM test.extension;\n\nSELECT 1;\n\nCREATE EXTENSION timescaledb VERSION 'mock-1';\n--same backend as create extension;\nSELECT 1;\nSELECT * FROM test.extension;\n\n--start new backend;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n\nSELECT 1;\nSELECT 1;\n--test fn call after load\nSELECT mock_function();\nSELECT * FROM test.extension;\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n--test fn call as first command\nSELECT mock_function();\n\n--use guc to prevent loading\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET timescaledb.disable_load = 'on';\nSELECT 1;\nSELECT 1;\nSET timescaledb.disable_load = 'off';\nSELECT 1;\n\\set ON_ERROR_STOP 0\nSET timescaledb.disable_load = 'not bool';\n\\set ON_ERROR_STOP 1\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nRESET ALL;\nSELECT 1;\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET timescaledb.disable_load TO DEFAULT;\nSELECT 1;\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nRESET timescaledb.disable_load;\nSELECT 1;\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET timescaledb.other = 'on';\nSELECT 1;\n\n\\set ON_ERROR_STOP 0\n--cannot update extension after .so of previous version already loaded\nALTER EXTENSION timescaledb UPDATE TO 'mock-2';\n\\set ON_ERROR_STOP 1\n\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\nSELECT * FROM test.extension;\nCREATE EXTENSION timescaledb VERSION 'mock-1';\nSELECT * FROM test.extension;\n--start a new backend to update\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\nALTER EXTENSION timescaledb UPDATE TO 'mock-2';\nSELECT 1;\nSELECT * FROM test.extension;\n\n--drop extension\nDROP EXTENSION timescaledb;\nSELECT 1;\nSELECT * FROM test.extension;\n\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\nCREATE EXTENSION timescaledb VERSION 'mock-2';\nSELECT 1;\nSELECT * FROM test.extension;\n\n-- test db 1 still has old version\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT 1;\nSELECT * FROM test.extension;\n\n--try a broken upgrade\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\nSELECT * FROM test.extension;\n\\set ON_ERROR_STOP 0\nALTER EXTENSION timescaledb UPDATE TO 'mock-3';\n\\set ON_ERROR_STOP 1\n--should still be on mock-2\nSELECT 1;\nSELECT * FROM test.extension;\n\n--drop extension\nDROP EXTENSION timescaledb;\nSELECT 1;\nSELECT * FROM test.extension;\n\n--create extension anew, only upgrade was broken\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\nCREATE EXTENSION timescaledb VERSION 'mock-3';\nSELECT 1;\nSELECT * FROM test.extension;\nDROP EXTENSION timescaledb;\nSELECT 1;\n\n--mismatched version errors\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\n--mock-4 has mismatched versions, so the .so load should be fatal\nSELECT format($$\\! utils/test_fatal_command.sh %1$s \"CREATE EXTENSION timescaledb VERSION 'mock-4'\"$$, :'TEST_DBNAME_2') as command_to_run \\gset\n:command_to_run\n--mock-4 not installed.\nSELECT * FROM test.extension;\n\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\n--broken version and drop\nCREATE EXTENSION timescaledb VERSION 'mock-broken';\n\n\\set ON_ERROR_STOP 0\n--intentional broken version\nSELECT * FROM test.extension;\nSELECT 1;\nSELECT 1;\n--cannot drop extension; already loaded broken version\nDROP EXTENSION timescaledb;\n\\set ON_ERROR_STOP 1\n\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\n--can drop extension now. Since drop first command.\nDROP EXTENSION timescaledb;\nSELECT * FROM test.extension;\n\n--broken version and update to fixed\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\nCREATE EXTENSION timescaledb VERSION 'mock-broken';\n\\set ON_ERROR_STOP 0\n--intentional broken version\nSELECT 1;\n--cannot update extension; already loaded bad version\nALTER EXTENSION timescaledb UPDATE TO 'mock-5';\n\\set ON_ERROR_STOP 1\n\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\n--can update extension now.\nALTER EXTENSION timescaledb UPDATE TO 'mock-5';\nSELECT 1;\nSELECT mock_function();\n\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\nALTER EXTENSION timescaledb UPDATE TO 'mock-6';\n--The mock-5->mock_6 upgrade is intentionally broken.\n--The mock_function was never changed to point to mock-6 in the update script.\n--Thus mock_function is defined incorrectly to point to the mock-5.so\n--This will now be a FATAL error.\nSELECT format($$\\! utils/test_fatal_command.sh %1$s \"SELECT mock_function()\"$$, :'TEST_DBNAME_2') as command_to_run \\gset\n:command_to_run\nSELECT * FROM test.extension;\n\n--TEST: create extension when old .so already loaded\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT * FROM test.extension;\nDROP EXTENSION timescaledb;\nSELECT * FROM test.extension;\n\n\\set ON_ERROR_STOP 0\nCREATE EXTENSION timescaledb VERSION 'mock-2';\n\\set ON_ERROR_STOP 1\nSELECT * FROM test.extension;\n--can create in a new session.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE EXTENSION timescaledb VERSION 'mock-2';\nSELECT * FROM test.extension;\n\n--make sure parallel workers started after a 'DISCARD ALL' work\nCREATE TABLE test (i int, j double precision);\nINSERT INTO test SELECT x, x+0.1 FROM generate_series(1,100) AS x;\n\nDISCARD ALL;\nSELECT set_config(CASE WHEN current_setting('server_version_num')::int < 160000 THEN 'force_parallel_mode' ELSE 'debug_parallel_query' END,'on', false);\nSET max_parallel_workers_per_gather = 1;\nSELECT count(*) FROM test;\n\nCREATE EXTENSION timescaledb_osm VERSION 'mock-1';\n\n-- Test that OSM process utility hook works:  it should see this DROP TABLE.\nDROP TABLE test;\n\n-- clean up additional database\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nDROP DATABASE :\"TEST_DBNAME_2\" WITH (FORCE);\n"
  },
  {
    "path": "test/sql/merge.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\n-- Create target table with location and temperature\nCREATE TABLE target (\n   time        TIMESTAMPTZ       NOT NULL,\n   location    SMALLINT          NOT NULL,\n   temperature DOUBLE PRECISION  NULL,\n   to_be_dropped text\n);\n\nSELECT create_hypertable(\n  'target',\n  'time',\n  chunk_time_interval => INTERVAL '5 seconds');\n\nINSERT INTO target\nSELECT time, location, 14 as temperature\nFROM generate_series(\n\t'2021-01-01 00:00:00',\n    '2021-01-01 00:00:09',\n    INTERVAL '5 seconds'\n  ) as time,\ngenerate_series(1,4) as location;\n\n-- This makes sure we have one column with attisdropped and one column\n-- with atthasmissing set to true. These two cases can cause problems\n-- with chunk dispatch execution when merging using a when-clause with\n-- inserts. Unfortunately they are hard to trigger, so this is not a\n-- definitive test.\nALTER TABLE target DROP COLUMN to_be_dropped;\nALTER TABLE target ADD COLUMN val text default 'string -';\n\n-- Create source table with location and temperature\nCREATE TABLE source (\n   time        TIMESTAMPTZ       NOT NULL,\n   location    SMALLINT          NOT NULL,\n   temperature DOUBLE PRECISION  NULL\n);\n\nSELECT create_hypertable(\n  'source',\n  'time',\n  chunk_time_interval => INTERVAL '5 seconds');\n\n-- Generate data that overlaps with target table\nINSERT INTO source\nSELECT time, location, 80 as temperature\nFROM generate_series(\n\t'2021-01-01 00:00:05',\n    '2021-01-01 00:00:14',\n    INTERVAL '5 seconds'\n  ) as time,\ngenerate_series(1,4) as location;\n\n-- Print table/rows/num of chunks\nselect * from target order by time, location asc;\nselect * from source order by time, location asc;\n\n-- CREATE normal PostgreSQL tables\nCREATE TABLE target_pg AS SELECT * FROM target;\nCREATE TABLE source_pg AS SELECT * FROM source;\n\n-- Merge UPDATE matched rows for normal PG tables\nMERGE INTO target_pg t\nUSING source_pg s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nUPDATE SET temperature = (t.temperature + s.temperature)/2, val = val || ' UPDATED BY MERGE';\n\n-- Merge UPDATE matched rows for hypertables\nMERGE INTO target t\nUSING source s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nUPDATE SET temperature = (t.temperature + s.temperature)/2, val = val || ' UPDATED BY MERGE';\n\n-- ensure TARGET PG table and hypertable are same\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n\n-- Merge DELETE matched rows for normal PG tables\nMERGE INTO target_pg t\nUSING source_pg s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nDELETE;\n\n-- Merge DELETE matched rows for hypertables\nMERGE INTO target t\nUSING source s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nDELETE;\n\n-- ensure TARGET PG table and hypertable are same\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n\n-- clean up tables\nDELETE FROM target_pg;\nDELETE FROM target;\nDELETE FROM source_pg;\nDELETE FROM source;\n\nINSERT INTO target\nSELECT time, location, 14 as temperature\nFROM generate_series(\n\t'2021-01-01 00:00:00',\n    '2021-01-01 00:00:09',\n    INTERVAL '5 seconds'\n  ) as time,\ngenerate_series(1,4) as location;\n\nINSERT INTO source\nSELECT time, location, 80 as temperature\nFROM generate_series(\n\t'2021-01-01 00:00:05',\n    '2021-01-01 00:00:14',\n    INTERVAL '5 seconds'\n  ) as time,\ngenerate_series(1,4) as location;\n\nINSERT INTO target_pg SELECT * FROM target;\nINSERT INTO source_pg SELECT * FROM source;\n\n-- Merge UPDATE matched rows and INSERT new row for unmatched rows for normal PG tables\nMERGE INTO target_pg t\nUSING source_pg s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nUPDATE SET temperature = (t.temperature + s.temperature)/2, val = val || ' UPDATED BY MERGE'\nWHEN NOT MATCHED THEN\nINSERT (time, location, temperature, val) VALUES (s.time, s.location, s.temperature, 'string - INSERTED BY MERGE');\n\n-- Merge UPDATE matched rows and INSERT new row for unmatched rows for hypertables\nMERGE INTO target t\nUSING source s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nUPDATE SET temperature = (t.temperature + s.temperature)/2, val = val || ' UPDATED BY MERGE'\nWHEN NOT MATCHED THEN\nINSERT (time, location, temperature, val) VALUES (s.time, s.location, s.temperature, 'string - INSERTED BY MERGE');\n\n-- ensure TARGET PG table and hypertable are same\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n\n-- Merge INSERT with constant literals for normal PG tables\nMERGE INTO target_pg t\nUSING source_pg s\nON t.location = 1234\nWHEN NOT MATCHED THEN\nINSERT VALUES ('2021-11-01 00:00:05'::timestamp with time zone, 5, 210, 'string - INSERTED BY MERGE');\n\n-- Merge INSERT with constant literals for hypertables\nMERGE INTO target t\nUSING source s\nON t.location = 1234\nWHEN NOT MATCHED THEN\nINSERT VALUES ('2021-11-01 00:00:05'::timestamp with time zone, 5, 210, 'string - INSERTED BY MERGE');\n\n-- ensure TARGET PG table and hypertable are same\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n\n-- Merge with INSERT/DELETE/UPDATE on PG tables\nMERGE INTO target_pg t\nUSING source_pg s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED AND t.location = 560076 THEN\n UPDATE SET temperature = (t.temperature + s.temperature) * 2, val = val || ' UPDATED BY MERGE'\nWHEN MATCHED AND t.location = 560083 THEN\n DELETE\nWHEN NOT MATCHED THEN\n INSERT (time, location, temperature, val) VALUES (s.time, s.location, s.temperature, 'string - INSERTED BY MERGE');\n\n-- Merge with INSERT/DELETE/UPDATE on hypertables\nMERGE INTO target t\nUSING source s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED AND t.location = 560076 THEN\n UPDATE SET temperature = (t.temperature + s.temperature) * 2, val = val || ' UPDATED BY MERGE'\nWHEN MATCHED AND t.location = 560083 THEN\n DELETE\nWHEN NOT MATCHED THEN\n INSERT (time, location, temperature, val) VALUES (s.time, s.location, s.temperature, 'string - INSERTED BY MERGE');\n\n-- ensure TARGET PG table and hypertable are same\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n\n-- Merge with Subqueries on PG tables\nMERGE INTO target_pg t\nUSING source_pg s\nON t.time = s.time AND t.location > (SELECT count(*) FROM source_pg)\nWHEN MATCHED AND t.temperature = 23 THEN\n UPDATE SET temperature = (SELECT count(*) FROM target_pg) * 2, val = val || ' UPDATED BY MERGE'\nWHEN MATCHED AND t.temperature = 47 THEN\n DELETE\nWHEN NOT MATCHED THEN\n INSERT (time, location, temperature, val) VALUES (s.time, s.location, s.temperature, 'SUBQUERY string - INSERTED BY MERGE');\n\n-- Merge with Subqueries on hypertables\nMERGE INTO target t\nUSING source s\nON t.time = s.time AND t.location > (SELECT count(*) FROM source)\nWHEN MATCHED AND t.temperature = 23 THEN\n UPDATE SET temperature = (SELECT count(*) FROM target) * 2, val = val || ' UPDATED BY MERGE'\nWHEN MATCHED AND t.temperature = 47 THEN\n DELETE\nWHEN NOT MATCHED THEN\n INSERT (time, location, temperature, val) VALUES (s.time, s.location, s.temperature, 'SUBQUERY string - INSERTED BY MERGE');\n\n-- ensure TARGET PG table and hypertable are same\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n\n-- clean up tables\nDELETE FROM target_pg;\nDELETE FROM target;\nDELETE FROM source_pg;\nDELETE FROM source;\n\n-- TEST with target as hypertable and source as normal PG table\nINSERT INTO target\nSELECT time, location, 14 as temperature\nFROM generate_series(\n\t'2021-01-01 00:00:00',\n    '2021-01-01 00:00:09',\n    INTERVAL '5 seconds'\n  ) as time,\ngenerate_series(1,4) as location;\n\nINSERT INTO source\nSELECT time, location, 80 as temperature\nFROM generate_series(\n\t'2021-01-01 00:00:05',\n    '2021-01-01 00:00:14',\n    INTERVAL '5 seconds'\n  ) as time,\ngenerate_series(1,4) as location;\n\nINSERT INTO target_pg SELECT * FROM target;\nINSERT INTO source_pg SELECT * FROM source;\n\n-- Merge UPDATE matched rows for normal PG tables\nMERGE INTO target_pg t\nUSING source_pg s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nUPDATE SET temperature = (t.temperature + s.temperature)/2, val = val || ' UPDATED BY MERGE';\n\n-- Merge UPDATE with target as hypertables and source as normal PG tables\nMERGE INTO target t\nUSING source_pg s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nUPDATE SET temperature = (t.temperature + s.temperature)/2, val = val || ' UPDATED BY MERGE';\n\n-- ensure TARGET PG table and hypertable are same\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n\n-- Merge DELETE matched rows for normal PG tables\nMERGE INTO target_pg t\nUSING source_pg s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nDELETE;\n\n-- Merge DELETE with target as hypertables and source as normal PG tables\nMERGE INTO target t\nUSING source_pg s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nDELETE;\n\n-- ensure TARGET PG table and hypertable are same\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n\n-- Merge INSERT with constant literals for normal PG tables\nMERGE INTO target_pg t\nUSING source_pg s\nON t.location = 1234\nWHEN NOT MATCHED THEN\nINSERT VALUES ('2021-11-01 00:00:05'::timestamp with time zone, 5, 210, 'string - INSERTED BY MERGE');\n\n-- Merge INSERT with constant literals for target as hypertables and source as normal PG tables\nMERGE INTO target t\nUSING source s\nON t.location = 1234\nWHEN NOT MATCHED THEN\nINSERT VALUES ('2021-11-01 00:00:05'::timestamp with time zone, 5, 210, 'string - INSERTED BY MERGE');\n\n-- ensure TARGET PG table and hypertable are same\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n\n-- Merge with INSERT/DELETE/UPDATE on PG tables\nMERGE INTO target_pg t\nUSING source_pg s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED AND t.temperature = 23 THEN\n UPDATE SET temperature = (t.temperature + s.temperature) * 2, val = val || ' UPDATED BY MERGE'\nWHEN MATCHED AND t.temperature = 47 THEN\n DELETE\nWHEN NOT MATCHED THEN\n INSERT (time, location, temperature, val) VALUES (s.time, s.location, s.temperature, 'string - INSERTED BY MERGE');\n\n-- Merge with INSERT/DELETE/UPDATE on target as hypertables and source as normal PG tables\nMERGE INTO target t\nUSING source s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED  AND t.temperature = 23 THEN\n UPDATE SET temperature = (t.temperature + s.temperature) * 2, val = val || ' UPDATED BY MERGE'\nWHEN MATCHED  AND t.temperature = 47 THEN\n DELETE\nWHEN NOT MATCHED THEN\n INSERT (time, location, temperature, val) VALUES (s.time, s.location, s.temperature, 'string - INSERTED BY MERGE');\n\n-- ensure TARGET PG table and hypertable are same\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n\nDROP TABLE target_pg CASCADE;\nDROP TABLE target CASCADE;\nDROP TABLE source_pg CASCADE;\nDROP TABLE source CASCADE;\n\n-- test MERGE with source being a PARTITION table\nCREATE TABLE source_pg(\n   id INT NOT NULL,\n   dev INT NOT NULL,\n   value INT,\n   CONSTRAINT cstr_source_pky PRIMARY KEY (id)\n) PARTITION BY LIST (id);\n\nCREATE TABLE source_1_2_3_4 PARTITION OF source_pg FOR VALUES IN (1,2,3,4);\nCREATE TABLE source_5_6_7_8 PARTITION OF source_pg FOR VALUES IN (5,6,7,8);\n\nINSERT INTO source_pg SELECT generate_series(1,8), 44,55;\n\nCREATE TABLE target (\n   ts TIMESTAMP WITH TIME ZONE NOT NULL,\n   id INT NOT NULL,\n   dev INT NOT NULL,\n   FOREIGN KEY (id)  REFERENCES source_pg(id) ON DELETE CASCADE\n);\n\nSELECT create_hypertable(\n   relation => 'target',\n   time_column_name => 'ts'\n);\n\ninsert into target values ('2023-01-12 00:00:05'::timestamp with time zone, 1,2);\ninsert into target values ('2023-01-12 00:00:10'::timestamp with time zone, 2,2);\ninsert into target values ('2023-01-12 00:00:15'::timestamp with time zone, 3,2);\ninsert into target values ('2023-01-12 00:00:20'::timestamp with time zone, 4,2);\ninsert into target values ('2023-01-14 00:00:25'::timestamp with time zone, 5,2);\ninsert into target values ('2023-01-14 00:00:30'::timestamp with time zone, 6,2);\ninsert into target values ('2023-01-14 00:00:35'::timestamp with time zone, 7,2);\ninsert into target values ('2023-01-14 00:00:40'::timestamp with time zone, 8,2);\n\nCREATE TABLE target_pg AS SELECT * FROM target;\n\n-- Merge UPDATE matched rows for normal PG tables\nMERGE INTO target_pg t\nUSING source_pg s\nON t.id = s.id\nWHEN MATCHED THEN\nUPDATE SET dev = (t.dev + s.dev)/2;\n\n-- Merge UPDATE matched rows for hypertables\nMERGE INTO target t\nUSING source_pg s\nON t.id = s.id\nWHEN MATCHED THEN\nUPDATE SET dev = (t.dev + s.dev)/2;\n\n-- ensure TARGET PG table and hypertable are same\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n\n-- Merge DELETE matched rows for normal PG tables\nMERGE INTO target_pg t\nUSING source_pg s\nON t.id = s.id\nWHEN MATCHED THEN\nDELETE;\n\n-- Merge DELETE matched rows for hypertables\nMERGE INTO target t\nUSING source_pg s\nON t.id = s.id\nWHEN MATCHED THEN\nDELETE;\n\n-- ensure TARGET PG table and hypertable are same\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n\n-- clean up tables\nDROP TABLE target_pg CASCADE;\nDROP TABLE target CASCADE;\nDROP TABLE source_pg CASCADE;\n\n-- test MERGE with hypertables with time and space partitions\nCREATE TABLE target (\n     filler_1 int,\n     filler_2 int,\n     filler_3 int,\n     time timestamptz NOT NULL,\n     device_id int,\n     device_id_peer int,\n     v0 int,\n     v1 float,\n     v2 float,\n     v3 float\n );\n\nSELECT create_hypertable ('target', 'time', 'device_id', 5);\nSELECT add_dimension('target', 'device_id_peer', 5);\nSELECT add_dimension('target', 'v2', 5);\nINSERT INTO target (time, device_id, device_id_peer, v0, v1, v2, v3)\n  SELECT time,\n    device_id,\n    0,\n    device_id + 1,\n    device_id + 2,\n    device_id + 0.5,\n    NULL\nFROM generate_series('2000-01-01 0:00:00+0'::timestamptz, '2000-01-05 23:55:00+0', '20m') gtime (time),\n    generate_series(1, 2, 1) gdevice (device_id);\n\nCREATE TABLE source (\n         filler_1 int,\n         filler_2 int,\n         filler_3 int,\n         time timestamptz NOT NULL,\n         device_id int\n  );\nSELECT create_hypertable ('source', 'time', 'device_id', 3);\n\nINSERT INTO source (time, device_id, filler_2, filler_3, filler_1)\n  SELECT time,\n    device_id,\n    device_id + 134,\n    device_id + 209,\n    device_id + 0.50127\nFROM generate_series('2000-01-01 0:00:00+0'::timestamptz, '2000-01-05 23:55:00+0', '20m') gtime (time),\n    generate_series(1, 5, 1) gdevice (device_id);\n\n-- create PG tables to compare PG target and hypertable target tables\nCREATE table target_pg as SELECT * FROM target;\nCREATE table source_pg as SELECT * FROM source;\n\n-- Merge UDPATE matched rows for normal PG tables\nMERGE INTO target_pg t\nUSING source_pg s\nON t.time = s.time AND t.device_id = s.device_id\nWHEN MATCHED THEN\nUPDATE SET filler_2 = s.filler_1 + 100;\n\n-- Merge UDPATE matched rows for space partitioned hypertables\nMERGE INTO target t\nUSING source s\nON t.time = s.time AND t.device_id = s.device_id\nWHEN MATCHED THEN\nUPDATE SET filler_2 = s.filler_1 + 100;\n\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n\n-- Merge DELETE matched rows for normal PG tables\nMERGE INTO target_pg t\n       USING source_pg s\n       ON t.time = s.time AND t.device_id = s.device_id\n       WHEN MATCHED THEN\n       DELETE;\n\n-- Merge DELETE matched rows for space partitioned hypertables\nMERGE INTO target t\n       USING source s\n       ON t.time = s.time AND t.device_id = s.device_id\n       WHEN MATCHED THEN\n       DELETE;\n\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n\n-- Merge INSERT matched rows for normal PG tables\nMERGE INTO target_pg t\n              USING source_pg s\n              ON t.time = s.time AND t.device_id = s.device_id\n              WHEN NOT MATCHED THEN\n              INSERT (filler_1, filler_2, filler_3, time, device_id, device_id_peer, v0, v1, v2, v3) VALUES\n                     (s.filler_1, s.filler_2, s.filler_3, s.time, s.device_id, s.device_id + 10, 1,2,3,4);\n\n-- Merge INSERT matched rows for space partitioned hypertables\nMERGE INTO target t\n              USING source s\n              ON t.time = s.time AND t.device_id = s.device_id\n              WHEN NOT MATCHED THEN\n              INSERT (filler_1, filler_2, filler_3, time, device_id, device_id_peer, v0, v1, v2, v3) VALUES\n                     (s.filler_1, s.filler_2, s.filler_3, s.time, s.device_id, s.device_id + 10, 1,2,3,4);\n\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n\n-- Merge with INSERT/DELETE/UPDATE on PG tables\nMERGE INTO target_pg t\n    USING source_pg s\n        ON t.time = s.time AND t.device_id = s.device_id\n              WHEN MATCHED AND t.device_id_peer = 2 THEN\n                  UPDATE SET filler_2 = s.filler_1 + s.filler_2 + s.filler_3 + 100\n              WHEN MATCHED AND t.device_id_peer = 7 THEN\n                  DELETE\n              WHEN NOT MATCHED THEN\n                  INSERT (filler_1, filler_2, filler_3, time, device_id, device_id_peer, v0, v1, v2, v3) VALUES\n                         (s.filler_1, s.filler_2, s.filler_3, s.time, s.device_id, s.device_id + 10, 1,2,3,4);\n\n-- Merge with INSERT/DELETE/UPDATE on space partitioned hypertables\nMERGE INTO target t\n    USING source s\n        ON t.time = s.time AND t.device_id = s.device_id\n              WHEN MATCHED AND t.device_id_peer = 2 THEN\n                  UPDATE SET filler_2 = s.filler_1 + s.filler_2 + s.filler_3 + 100\n              WHEN MATCHED AND t.device_id_peer = 7 THEN\n                  DELETE\n              WHEN NOT MATCHED THEN\n                  INSERT (filler_1, filler_2, filler_3, time, device_id, device_id_peer, v0, v1, v2, v3) VALUES\n                         (s.filler_1, s.filler_2, s.filler_3, s.time, s.device_id, s.device_id + 10, 1,2,3,4);\n\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n\n-- clean up tables\nDROP TABLE target_pg CASCADE;\nDROP TABLE target CASCADE;\nDROP TABLE source_pg CASCADE;\nDROP TABLE source CASCADE;\n\n-- TEST with parition column place after similar data type column\nCREATE TABLE target (\n     filler_1 int,\n     filler_2 int,\n     filler_3 int,\n     time timestamptz NOT NULL,\n     device_id int,\n     device_id_peer int,\n     v0 int,\n     v1 float,\n     v2 float,\n     v3 float,\n     partition_column TIMESTAMPTZ NOT NULL\n );\n\nSELECT create_hypertable ('target', 'partition_column');\n\nINSERT INTO target (time, device_id, device_id_peer, v0, v1, v2, v3, partition_column)\n  SELECT time,\n    device_id,\n    0,\n    device_id + 1,\n    device_id + 2,\n    device_id + 0.5,\n    NULL,\n    time + interval '10m'\nFROM generate_series('2000-01-01 0:00:00+0'::timestamptz, '2000-01-05 23:55:00+0', '20m') gtime (time),\n    generate_series(1, 2, 1) gdevice (device_id);\n\nCREATE TABLE source (\n         filler_1 int,\n         filler_2 int,\n         filler_3 int,\n         time timestamptz NOT NULL,\n         device_id int\n  );\nSELECT create_hypertable ('source', 'time', 'device_id', 3);\n\nINSERT INTO source (time, device_id, filler_2, filler_3, filler_1)\n  SELECT time,\n    device_id,\n    device_id + 134,\n    device_id + 209,\n    device_id + 0.50127\nFROM generate_series('2000-01-01 0:00:00+0'::timestamptz, '2000-01-05 23:55:00+0', '20m') gtime (time),\n    generate_series(1, 5, 1) gdevice (device_id);\n\n-- create PG tables to compare PG target and hypertable target tables\nCREATE table target_pg as SELECT * FROM target;\n\nMERGE INTO target_pg t\nUSING source s\nON t.time = s.time AND t.device_id = s.device_id\nWHEN NOT MATCHED THEN\nINSERT (time, device_id, device_id_peer, v0, v1, v2, v3, partition_column) VALUES\n('2010-01-06 05:30:00+05:30', 23, 2, 11, 22, 33, 44, '2023-01-06 05:33:00+05:30');\n\nMERGE INTO target t\nUSING source s\nON t.time = s.time AND t.device_id = s.device_id\nWHEN NOT MATCHED THEN\nINSERT (time, device_id, device_id_peer, v0, v1, v2, v3, partition_column) VALUES\n('2010-01-06 05:30:00+05:30', 23, 2, 11, 22, 33, 44, '2023-01-06 05:33:00+05:30');\n\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n\nMERGE INTO target_pg t\nUSING source s\nON t.time = s.time AND t.device_id = s.device_id\nWHEN  MATCHED THEN\nDELETE;\n\nMERGE INTO target t\nUSING source s\nON t.time = s.time AND t.device_id = s.device_id\nWHEN  MATCHED THEN\nDELETE;\n\nSELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)\n              OR EXISTS (TABLE target_pg EXCEPT TABLE target)\n            THEN 'different'\n            ELSE 'same'\n       END AS result;\n\nMERGE INTO target_pg t\n              USING source s\n              ON t.time = s.time AND t.device_id = s.device_id\n              WHEN NOT MATCHED THEN\n              INSERT (filler_1, filler_2, filler_3, time, device_id, device_id_peer, v0, v1, v2, v3) VALUES\n                     (s.filler_1, s.filler_2, s.filler_3, s.time, s.device_id, s.device_id + 10, 1,2,3,4);\n\n-- time dimension column is NULL, this will report an null constraint violation error\n\\set ON_ERROR_STOP 0\nMERGE INTO target t\n              USING source s\n              ON t.time = s.time AND t.device_id = s.device_id\n              WHEN NOT MATCHED THEN\n              INSERT (filler_1, filler_2, filler_3, time, device_id, device_id_peer, v0, v1, v2, v3) VALUES\n                     (s.filler_1, s.filler_2, s.filler_3, s.time, s.device_id, s.device_id + 10, 1,2,3,4);\n\\set ON_ERROR_STOP 1\n\nDROP TABLE target CASCADE;\nDROP TABLE target_pg CASCADE;\nDROP TABLE source CASCADE;\n\n-- TEST with target table have CHECK constraints\nCREATE TABLE target (\n   time        TIMESTAMPTZ       NOT NULL,\n   location    SMALLINT          NOT NULL,\n   temperature DOUBLE PRECISION  NULL CHECK (temperature > 10),\n   val text default 'string -'\n);\n\nSELECT create_hypertable(\n  'target',\n  'time',\n  chunk_time_interval => INTERVAL '5 seconds');\n\nINSERT INTO target\nSELECT time, location, 14 as temperature\nFROM generate_series(\n\t'2021-01-01 00:00:00',\n    '2021-01-01 00:00:09',\n    INTERVAL '5 seconds'\n  ) as time,\ngenerate_series(1,4) as location;\n\n-- Create source table with location and temperature\nCREATE TABLE source (\n   time        TIMESTAMPTZ       NOT NULL,\n   location    SMALLINT          NOT NULL,\n   temperature DOUBLE PRECISION  NULL\n);\n\n-- Generate data that overlaps with target table\nINSERT INTO source\nSELECT time, location, 80 as temperature\nFROM generate_series(\n\t'2021-01-01 00:00:05',\n    '2021-01-01 00:00:14',\n    INTERVAL '5 seconds'\n  ) as time,\ngenerate_series(1,4) as location;\n\n-- CREATE normal PostgreSQL tables\nCREATE TABLE target_pg AS SELECT * FROM target;\n\n-- Merge UPDATE/DELETE with DO NOTHING on pg tables\nMERGE INTO target_pg t\nUSING source s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nDO NOTHING\nWHEN NOT MATCHED THEN\nDO NOTHING;\n\n-- Merge UPDATE/DELETE with DO NOTHING on hypertable\nMERGE INTO target t\nUSING source s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nDO NOTHING\nWHEN NOT MATCHED THEN\nDO NOTHING;\n\n-- Error cases for Merge\n\\set ON_ERROR_STOP 0\n\n-- Merge UPDATE should fail with check constraint violation\nMERGE INTO target_pg t\nUSING source s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nUPDATE SET temperature = 8, val = val || ' UPDATED BY MERGE';\n\n-- Merge UPDATE should fail with check constraint violation\nMERGE INTO target t\nUSING source s\nON t.time = s.time AND t.location = s.location\nWHEN MATCHED THEN\nUPDATE SET temperature = 8, val = val || ' UPDATED BY MERGE';\n\n-- Merge error with unreachable WHEN clause on pg tables\nMERGE INTO target_pg t\nUSING source s\nON t.time = s.time AND t.location != s.location\nWHEN MATCHED THEN\nUPDATE SET temperature = 8, val = val || ' UPDATED BY MERGE'\nWHEN MATCHED AND t.time < now() THEN\nDELETE\nWHEN NOT MATCHED THEN\nDO NOTHING;\n\n-- Merge error with unreachable WHEN clause on hypertable\nMERGE INTO target t\nUSING source s\nON t.time = s.time AND t.location != s.location\nWHEN MATCHED THEN\nUPDATE SET temperature = 8, val = val || ' UPDATED BY MERGE'\nWHEN MATCHED AND t.time < now() THEN\nDELETE\nWHEN NOT MATCHED THEN\nDO NOTHING;\n\n-- Merge error with unknown action in MERGE WHEN MATCHED clause on pg tables\nMERGE INTO target_pg t\nUSING source s\nON t.time = s.time AND t.location != s.location\nWHEN MATCHED THEN\nSELECT 1;\n\n-- Merge error with unknown action in MERGE WHEN MATCHED clause on hypertable\nMERGE INTO target t\nUSING source s\nON t.time = s.time AND t.location != s.location\nWHEN MATCHED THEN\nSELECT 1;\n\n-- Merge error cannot affect row a second time on pg tables\nMERGE INTO target_pg t\nUSING source s\nON  t.location = s.location\nWHEN MATCHED THEN\nUPDATE SET temperature = 28, val = val || ' UPDATED BY MERGE';\n\n-- Merge error cannot affect row a second time on hypertable\nMERGE INTO target t\nUSING source s\nON  t.location = s.location\nWHEN MATCHED THEN\nUPDATE SET temperature = 28, val = val || ' UPDATED BY MERGE';\n\n\\set ON_ERROR_STOP 1\n\nDROP TABLE target CASCADE;\nDROP TABLE target_pg CASCADE;\nDROP TABLE source CASCADE;\n\n-- TEST for PERMISSIONS\nCREATE USER priv_user;\nCREATE USER non_priv_user;\n\nCREATE TABLE target (\n    value DOUBLE PRECISION NOT NULL,\n    time TIMESTAMPTZ NOT NULL\n);\n\nSELECT table_name FROM create_hypertable(\n                            'target'::regclass,\n                            'time'::name, chunk_time_interval=>interval '8 hours',\n                            create_default_indexes=> false);\n\nSELECT '2022-10-10 14:33:44.1234+05:30' as start_date \\gset\nINSERT INTO target (value, time)\n  SELECT 1,t from generate_series(:'start_date'::timestamptz, :'start_date'::timestamptz + interval '1 day', '5m') t cross join\n    generate_series(1,3) s;\n\nCREATE TABLE source (\n        time TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL,\n        value DOUBLE PRECISION NOT NULL\n    );\n\nSELECT table_name FROM create_hypertable(\n                                'source'::regclass,\n                                'time'::name, chunk_time_interval=>interval '6 hours',\n                                create_default_indexes=> false);\n\nALTER TABLE target OWNER TO priv_user;\nALTER TABLE source OWNER TO priv_user;\n\nGRANT SELECT ON source TO non_priv_user;\nSET SESSION AUTHORIZATION non_priv_user;\n\n\\set ON_ERROR_STOP 0\n-- non_priv_user does not have UPDATE privilege on target table\nMERGE INTO target\nUSING source\nON target.time = source.time\nWHEN MATCHED THEN\n\tUPDATE SET value = 0;\n\n-- non_priv_user does not have DELETE privilege on target table\nMERGE INTO target\nUSING source\nON target.time = source.time\nWHEN MATCHED THEN\n\tDELETE;\n\n-- non_priv_user does not have INSERT privilege on target table\nMERGE INTO target\nUSING source\nON target.time = source.time\nWHEN NOT MATCHED THEN\n\tINSERT VALUES (10, '2023-01-15 00:00:10'::timestamp with time zone);\n\n\\set ON_ERROR_STOP 1\n\nRESET SESSION AUTHORIZATION;\nDROP TABLE target;\nDROP TABLE source;\nDROP USER priv_user;\nDROP USER non_priv_user;\n"
  },
  {
    "path": "test/sql/metadata.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_uuid() RETURNS UUID\n    AS :MODULE_PATHNAME, 'ts_test_uuid' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_exported_uuid() RETURNS UUID\n    AS :MODULE_PATHNAME, 'ts_test_exported_uuid' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_install_timestamp() RETURNS TIMESTAMPTZ\n    AS :MODULE_PATHNAME, 'ts_test_install_timestamp' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nINSERT INTO _timescaledb_catalog.metadata (key, value, include_in_telemetry) SELECT 'metadata_test', 'FOO', TRUE;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n\n-- uuid and install_timestamp should already be in the table before we generate\nSELECT COUNT(*) from _timescaledb_catalog.metadata;\n\nSELECT _timescaledb_internal.test_uuid() as uuid_1 \\gset\nSELECT _timescaledb_internal.test_exported_uuid() as uuid_ex_1 \\gset\nSELECT _timescaledb_internal.test_install_timestamp() as timestamp_1 \\gset\n\n-- Check that there is exactly 1 UUID row\nSELECT COUNT(*) from _timescaledb_catalog.metadata where key='uuid';\n\n-- Check that exported_uuid and timestamp are also generated\nSELECT COUNT(*) from _timescaledb_catalog.metadata where key='exported_uuid';\nSELECT COUNT(*) from _timescaledb_catalog.metadata where key='install_timestamp';\n\n-- Make sure that the UUID is idempotent\nSELECT _timescaledb_internal.test_uuid() = :'uuid_1' as uuids_equal;\nSELECT _timescaledb_internal.test_uuid() = :'uuid_1' as uuids_equal;\n-- Also make sure install_time and exported_uuid are idempotent\nSELECT _timescaledb_internal.test_exported_uuid() = :'uuid_ex_1' as exported_uuids_equal;\nSELECT _timescaledb_internal.test_exported_uuid() = :'uuid_ex_1' as exported_uuids_equal;\nSELECT _timescaledb_internal.test_install_timestamp() = :'timestamp_1' as timestamps_equal;\nSELECT _timescaledb_internal.test_install_timestamp() = :'timestamp_1' as timestamps_equal;\n\n-- Now make sure that only the exported_uuid is exported on pg_dump\n\\c postgres :ROLE_SUPERUSER\n\n\\setenv PGOPTIONS '--client-min-messages=warning'\n\\! utils/pg_dump_aux_dump.sh dump/instmeta.sql\nALTER DATABASE :TEST_DBNAME SET timescaledb.restoring='on';\n-- Redirect to /dev/null to suppress NOTICE\n\\! utils/pg_dump_aux_restore.sh dump/instmeta.sql\nALTER DATABASE :TEST_DBNAME SET timescaledb.restoring='off';\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n\n-- Should have all 3 row, because pg_dump includes the insertion of uuid and timestamp.\nSELECT COUNT(*) FROM _timescaledb_catalog.metadata;\n-- Verify that this is the old exported_uuid\nSELECT _timescaledb_internal.test_exported_uuid() = :'uuid_ex_1' as exported_uuids_equal;\n-- Verify that the uuid is new\nSELECT _timescaledb_internal.test_uuid() = :'uuid_1' as exported_uuids_diff;\n-- Verify that the install_timestamp got restored\nSELECT _timescaledb_internal.test_install_timestamp() = :'timestamp_1' as timestamps_equal;\nSELECT * FROM _timescaledb_catalog.metadata WHERE key = 'metadata_test';\n\n-- check metadata version matches expected value\nSELECT x.extversion = m.value AS \"version match\"\nFROM pg_extension x\nJOIN _timescaledb_catalog.metadata m ON m.key='timescaledb_version'\nWHERE x.extname='timescaledb';\n\n-- test version check in post_restore\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nUPDATE _timescaledb_catalog.metadata SET value = '1.2.3' WHERE key = 'timescaledb_version';\n\\set ON_ERROR_STOP 0\n-- set verbosity to sqlstate to suppress version dependant error message\n\\set VERBOSITY sqlstate\nSELECT timescaledb_post_restore();\n\\set ON_ERROR_STOP 1\n\nUPDATE _timescaledb_catalog.metadata m SET value = x.extversion FROM pg_extension x WHERE m.key = 'timescaledb_version' AND x.extname='timescaledb';\nSELECT timescaledb_post_restore();\n\n\n"
  },
  {
    "path": "test/sql/multi_transaction_index.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE TABLE index_test(id serial, time timestamptz, device integer, temp float);\nSELECT * FROM test.show_columns('index_test');\n\n-- Test that we can handle difference in attnos across hypertable and\n-- chunks by dropping the ID column\nALTER TABLE index_test DROP COLUMN id;\nSELECT * FROM test.show_columns('index_test');\n\n-- No pre-existing UNIQUE index, so partitioning on two columns should work\nSELECT create_hypertable('index_test', 'time', 'device', 2);\n\nINSERT INTO index_test VALUES ('2017-01-20T09:00:01', 1, 17.5);\n\n\\set ON_ERROR_STOP 0\n-- cannot create a UNIQUE index with transaction_per_chunk\nCREATE UNIQUE INDEX index_test_time_device_idx ON index_test (time) WITH (timescaledb.transaction_per_chunk);\nCREATE UNIQUE INDEX index_test_time_device_idx ON index_test (time, device) WITH(timescaledb.transaction_per_chunk);\n\\set ON_ERROR_STOP 1\n\nCREATE INDEX index_test_time_device_idx ON index_test (time, device) WITH (timescaledb.transaction_per_chunk);\n\n-- Regular index need not cover all partitioning columns\nCREATE INDEX ON index_test (time, temp) WITH (timescaledb.transaction_per_chunk);\n\n-- Create another chunk\nINSERT INTO index_test VALUES ('2017-04-20T09:00:01', 1, 17.5);\n\n-- New index should have been recursed to chunks\nSELECT * FROM test.show_indexes('index_test');\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk') ORDER BY 1,2;\n\nALTER INDEX index_test_time_idx RENAME TO index_test_time_idx2;\n\n-- Metadata and index should have changed name\nSELECT * FROM test.show_indexes('index_test');\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk') ORDER BY 1,2;\n\nDROP INDEX index_test_time_idx2;\nDROP INDEX index_test_time_device_idx;\n\n-- Index should have been dropped\nSELECT * FROM test.show_indexes('index_test');\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n\n-- Create index with long name to see how this is handled on chunks\nCREATE INDEX a_hypertable_index_with_a_very_very_long_name_that_truncates ON index_test (time ASC, temp DESC) WITH (timescaledb.transaction_per_chunk);\nCREATE INDEX a_hypertable_index_with_a_very_very_long_name_that_truncates_2 ON index_test (time DESC, temp ASC) WITH (timescaledb.transaction_per_chunk);\n\nSELECT * FROM test.show_indexes('index_test');\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n\nDROP INDEX a_hypertable_index_with_a_very_very_long_name_that_truncates;\nDROP INDEX a_hypertable_index_with_a_very_very_long_name_that_truncates_2;\n\n\nSELECT * FROM test.show_indexes('index_test');\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n\nSELECT * FROM test.show_indexes('index_test');\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n\n-- Add constraint index\nALTER TABLE index_test ADD UNIQUE (time, device);\n\nSELECT * FROM test.show_indexes('index_test');\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n\n-- Constraints are added to chunk_constraint table.\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\n\nDROP TABLE index_test;\n\n-- Test that indexes are planned correctly\nCREATE TABLE index_expr_test(id serial, time timestamptz, temp float, meta int);\nselect create_hypertable('index_expr_test', 'time');\n\n-- Screw up the attribute numbers\nALTER TABLE index_expr_test DROP COLUMN id;\n\nCREATE INDEX ON index_expr_test (meta) WITH (timescaledb.transaction_per_chunk);\nINSERT INTO index_expr_test VALUES ('2017-01-20T09:00:01', 17.5, 1);\nINSERT INTO index_expr_test VALUES ('2017-01-20T09:00:01', 17.5, 2);\n\n\nSET enable_seqscan TO false;\nSET enable_bitmapscan TO false;\nEXPLAIN (verbose, buffers off, costs off)\nSELECT * FROM index_expr_test WHERE meta = 1;\nSELECT * FROM index_expr_test WHERE meta = 1;\nSET enable_seqscan TO default;\nSET enable_bitmapscan TO default;\n\n\\set ON_ERROR_STOP 0\n-- cannot create a transaction_per_chunk index within a transaction block\nBEGIN;\nCREATE INDEX ON index_expr_test (temp) WITH (timescaledb.transaction_per_chunk);\nROLLBACK;\n\\set ON_ERROR_STOP 1\n\nDROP TABLE index_expr_test CASCADE;\n\nCREATE TABLE partial_index_test(time INTEGER);\nSELECT create_hypertable('partial_index_test', 'time', chunk_time_interval => 1, create_default_indexes => false);\n\n-- create 3 chunks\nINSERT INTO partial_index_test(time) SELECT generate_series(0, 2);\n\nselect * from partial_index_test order by 1;\n\n-- create indexes on only 1 of the chunks\nCREATE INDEX ON partial_index_test (time) WITH (timescaledb.transaction_per_chunk, timescaledb.max_chunks='1');\n\nSELECT * FROM test.show_indexes('partial_index_test');\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n\n-- regerssion test for bug fixed by PR #1008.\n-- this caused an assertion failure when a MergeAppend node contained unsorted children\nSET enable_bitmapscan TO false;\nEXPLAIN (verbose, buffers off, costs off) SELECT * FROM partial_index_test WHERE time < 2 ORDER BY time LIMIT 2;\nSELECT * FROM partial_index_test WHERE time < 2 ORDER BY time LIMIT 2;\n\n-- we can drop the partially created index\nDROP INDEX partial_index_test_time_idx;\n\nSELECT * FROM test.show_indexes('partial_index_test');\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n\nEXPLAIN (verbose, buffers off, costs off) SELECT * FROM partial_index_test WHERE time < 2 ORDER BY time LIMIT 2;\nSELECT * FROM partial_index_test WHERE time < 2 ORDER BY time LIMIT 2;\n\nSET enable_seqscan TO true;\nSET enable_bitmapscan TO true;\n\n\\c  :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n\\set ON_ERROR_STOP 0\nCREATE INDEX ON partial_index_test (time) WITH (timescaledb.transaction_per_chunk, timescaledb.max_chunks='1');\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "test/sql/net.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_http_parsing(int) RETURNS VOID\n    AS :MODULE_PATHNAME, 'ts_test_http_parsing' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_http_parsing_full() RETURNS VOID\n    AS :MODULE_PATHNAME, 'ts_test_http_parsing_full' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_http_request_build() RETURNS VOID\n    AS :MODULE_PATHNAME, 'ts_test_http_request_build' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_conn() RETURNS VOID\n    AS :MODULE_PATHNAME, 'ts_test_conn' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT _timescaledb_internal.test_http_parsing(10000);\nSELECT _timescaledb_internal.test_http_parsing_full();\nSELECT _timescaledb_internal.test_http_request_build();\nSELECT _timescaledb_internal.test_conn();\n"
  },
  {
    "path": "test/sql/null_exclusion.sql.in",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\ncreate table metrics(ts timestamp, id int, value float);\nselect create_hypertable('metrics', 'ts');\ninsert into metrics values ('2022-02-02 02:02:02', 2, 2.),\n    ('2023-03-03 03:03:03', 3, 3.);\nanalyze metrics;\n\n-- non-const condition\nexplain (analyze, buffers off, costs off, summary off, timing off)\nselect * from metrics\nwhere ts >= (select max(ts) from metrics);\n\n-- two non-const conditions\nexplain (analyze, buffers off, costs off, summary off, timing off)\nselect * from metrics\nwhere ts >= (select max(ts) from metrics)\n    and id = 1;\n\n-- condition that becomes const null after evaluating the param\nexplain (analyze, buffers off, costs off, summary off, timing off)\nselect * from metrics\nwhere ts >= (select max(ts) from metrics where id = -1);\n\n-- const null condition and some other condition\nexplain (analyze, buffers off, costs off, summary off, timing off)\nselect * from metrics\nwhere ts >= (select max(ts) from metrics where id = -1)\n    and id = 1;\n"
  },
  {
    "path": "test/sql/parallel.sql.in",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n--parallel queries require big-ish tables so collect them all here\n--so that we need to generate queries only once.\n\n-- output with analyze is not stable because it depends on worker assignment\n\\set PREFIX 'EXPLAIN (buffers off, costs off)'\n\n\\set CHUNK1 _timescaledb_internal._hyper_1_1_chunk\n\\set CHUNK2 _timescaledb_internal._hyper_1_2_chunk\n\nCREATE TABLE test (i int, j double precision, ts timestamp);\nSELECT create_hypertable('test','i',chunk_time_interval:=500000);\nINSERT INTO test SELECT x, x+0.1, _timescaledb_functions.to_timestamp(x*1000)  FROM generate_series(0,1000000-1,10) AS x;\n\nANALYZE test;\nALTER TABLE :CHUNK1 SET (parallel_workers=2);\nALTER TABLE :CHUNK2 SET (parallel_workers=2);\n\nSET work_mem TO '50MB';\nSELECT set_config(CASE WHEN current_setting('server_version_num')::int < 160000 THEN 'force_parallel_mode' ELSE 'debug_parallel_query' END,'on', false);\nSET max_parallel_workers_per_gather = 4;\nSET parallel_setup_cost TO 0;\n\nEXPLAIN (buffers off, costs off) SELECT first(i, j) FROM \"test\";\nSELECT first(i, j) FROM \"test\";\n\nEXPLAIN (buffers off, costs off) SELECT last(i, j) FROM \"test\";\nSELECT last(i, j) FROM \"test\";\n\nEXPLAIN (buffers off, costs off) SELECT time_bucket('1 second', ts) sec, last(i, j)\nFROM \"test\"\nGROUP BY sec\nORDER BY sec\nLIMIT 5;\n\n-- test single copy parallel plan with parallel chunk append\n:PREFIX SELECT time_bucket('1 second', ts) sec, last(i, j)\nFROM \"test\"\nWHERE length(version()) > 0\nGROUP BY sec\nORDER BY sec\nLIMIT 5;\n\nSELECT time_bucket('1 second', ts) sec, last(i, j)\nFROM \"test\"\nGROUP BY sec\nORDER BY sec\nLIMIT 5;\n\n--test variants of histogram\nEXPLAIN (buffers off, costs off) SELECT histogram(i, 1, 1000000, 2) FROM \"test\";\nSELECT histogram(i, 1, 1000000, 2) FROM \"test\";\n\nEXPLAIN (buffers off, costs off) SELECT histogram(i, 1,1000001,10) FROM \"test\";\nSELECT histogram(i, 1, 1000001, 10) FROM \"test\";\n\nEXPLAIN (buffers off, costs off) SELECT histogram(i, 0,100000,5) FROM \"test\";\nSELECT histogram(i, 0, 100000, 5) FROM \"test\";\n\nEXPLAIN (buffers off, costs off) SELECT histogram(i, 10,100000,5) FROM \"test\";\nSELECT histogram(i, 10, 100000, 5) FROM \"test\";\n\nEXPLAIN (buffers off, costs off) SELECT histogram(NULL, 10,100000,5) FROM \"test\" WHERE  i = coalesce(-1,j);\nSELECT histogram(NULL, 10,100000,5) FROM \"test\" WHERE  i = coalesce(-1,j);\n\n-- test parallel ChunkAppend\n:PREFIX SELECT i FROM \"test\" WHERE length(version()) > 0;\n\n:PREFIX SELECT count(*) FROM \"test\" WHERE i > 1 AND length(version()) > 0;\nSELECT count(*) FROM \"test\" WHERE i > 1 AND length(version()) > 0;\n\n-- test parallel ChunkAppend with only work done in the parallel workers\nSET parallel_leader_participation = off;\nSELECT count(*) FROM \"test\" WHERE i > 1 AND length(version()) > 0;\nRESET parallel_leader_participation;\n\n-- Test parallel chunk append is used (index scan is disabled to trigger a parallel chunk append)\nSET parallel_tuple_cost = 0;\nSET enable_indexscan = OFF;\n:PREFIX SELECT * FROM (SELECT * FROM \"test\" WHERE length(version()) > 0 ORDER BY I LIMIT 10) AS t1 LEFT JOIN (SELECT * FROM \"test\" WHERE i < 500000 ORDER BY I LIMIT 10) AS t2 ON (t1.i = t2.i) ORDER BY t1.i, t2.i;\nSELECT * FROM (SELECT * FROM \"test\" WHERE length(version()) > 0 ORDER BY I LIMIT 10) AS t1 LEFT JOIN (SELECT * FROM \"test\" WHERE i < 500000 ORDER BY I LIMIT 10) AS t2 ON (t1.i = t2.i) ORDER BY t1.i, t2.i;\nSET enable_indexscan = ON;\n\n-- Test normal chunk append can be used in a parallel worker\n:PREFIX SELECT * FROM (SELECT * FROM \"test\" WHERE i >= 999000 ORDER BY i) AS t1 JOIN (SELECT * FROM \"test\" WHERE i >= 400000 ORDER BY i) AS t2 ON (TRUE) ORDER BY t1.i, t2.i LIMIT 10;\nSELECT * FROM (SELECT * FROM \"test\" WHERE i >= 999000 ORDER BY i) AS t1 JOIN (SELECT * FROM \"test\" WHERE i >= 400000 ORDER BY i) AS t2 ON (TRUE) ORDER BY t1.i, t2.i LIMIT 10;\n\n-- Test parallel ChunkAppend reinit\nSET enable_material = off;\nSET min_parallel_table_scan_size = 0;\nSET min_parallel_index_scan_size = 0;\nSET enable_hashjoin = 'off';\nSET enable_nestloop = 'off';\n\nCREATE TABLE sensor_data(\n      time timestamptz NOT NULL,\n      sensor_id integer NOT NULL);\n\nSELECT FROM create_hypertable(relation=>'sensor_data', time_column_name=> 'time');\n\n-- Sensors 1 and 2\nINSERT INTO sensor_data\nSELECT time, sensor_id\nFROM\ngenerate_series('2000-01-01 00:00:30', '2022-01-01 00:00:30', INTERVAL '3 months') AS g1(time),\ngenerate_series(1, 2, 1) AS g2(sensor_id)\nORDER BY time;\n\n-- Sensor 100\nINSERT INTO sensor_data\nSELECT time, 100 as sensor_id\nFROM\ngenerate_series('2000-01-01 00:00:30', '2022-01-01 00:00:30', INTERVAL '1 year') AS g1(time)\nORDER BY time;\n\n:PREFIX SELECT * FROM sensor_data AS s1 JOIN sensor_data AS s2 ON (TRUE) WHERE s1.time > '2020-01-01 00:00:30'::text::timestamptz AND s2.time > '2020-01-01 00:00:30' AND s2.time < '2021-01-01 00:00:30' AND s1.sensor_id > 50 ORDER BY s2.time, s1.time, s1.sensor_id, s2.sensor_id;\n\n-- Check query result\nSELECT * FROM sensor_data AS s1 JOIN sensor_data AS s2 ON (TRUE) WHERE s1.time > '2020-01-01 00:00:30'::text::timestamptz AND s2.time > '2020-01-01 00:00:30' AND s2.time < '2021-01-01 00:00:30' AND s1.sensor_id > 50 ORDER BY s2.time, s1.time, s1.sensor_id, s2.sensor_id;\n\n-- Ensure the same result is produced if only the parallel workers have to produce them (i.e., the pstate is reinitialized properly)\nSET parallel_leader_participation = off;\nSELECT * FROM sensor_data AS s1 JOIN sensor_data AS s2 ON (TRUE) WHERE s1.time > '2020-01-01 00:00:30'::text::timestamptz AND s2.time > '2020-01-01 00:00:30' AND s2.time < '2021-01-01 00:00:30' AND s1.sensor_id > 50 ORDER BY s2.time, s1.time, s1.sensor_id, s2.sensor_id;\nRESET parallel_leader_participation;\n\n-- Ensure the same query result is produced by a sequencial query\nSET max_parallel_workers_per_gather TO 0;\nSELECT set_config(CASE WHEN current_setting('server_version_num')::int < 160000 THEN 'force_parallel_mode' ELSE 'debug_parallel_query' END,'off', false);\nSELECT * FROM sensor_data AS s1 JOIN sensor_data AS s2 ON (TRUE) WHERE s1.time > '2020-01-01 00:00:30'::text::timestamptz AND s2.time > '2020-01-01 00:00:30' AND s2.time < '2021-01-01 00:00:30' AND s1.sensor_id > 50 ORDER BY s2.time, s1.time, s1.sensor_id, s2.sensor_id;\n\nRESET enable_material;\nRESET min_parallel_table_scan_size;\nRESET min_parallel_index_scan_size;\nRESET enable_hashjoin;\nRESET enable_nestloop;\nRESET parallel_tuple_cost;\nSELECT set_config(CASE WHEN current_setting('server_version_num')::int < 160000 THEN 'force_parallel_mode' ELSE 'debug_parallel_query' END,'on', false);\n\n-- test worker assignment\n-- first chunk should have 1 worker and second chunk should have 2\nSET max_parallel_workers_per_gather TO 2;\n:PREFIX SELECT count(*) FROM \"test\" WHERE i >= 400000 AND length(version()) > 0;\nSELECT count(*) FROM \"test\" WHERE i >= 400000 AND length(version()) > 0;\n\n-- test worker assignment\n-- first chunk should have 2 worker and second chunk should have 1\n:PREFIX SELECT count(*) FROM \"test\" WHERE i < 600000 AND length(version()) > 0;\nSELECT count(*) FROM \"test\" WHERE i < 600000 AND length(version()) > 0;\n\n-- test ChunkAppend with # workers < # childs\nSET max_parallel_workers_per_gather TO 1;\n:PREFIX SELECT count(*) FROM \"test\" WHERE length(version()) > 0;\nSELECT count(*) FROM \"test\" WHERE length(version()) > 0;\n\n-- test ChunkAppend with # workers > # childs\nSET max_parallel_workers_per_gather TO 2;\n:PREFIX SELECT count(*) FROM \"test\" WHERE i >= 500000 AND length(version()) > 0;\nSELECT count(*) FROM \"test\" WHERE i >= 500000 AND length(version()) > 0;\n\nRESET max_parallel_workers_per_gather;\n\n-- test partial and non-partial plans\n-- these will not be parallel on PG < 11\nALTER TABLE :CHUNK1 SET (parallel_workers=0);\nALTER TABLE :CHUNK2 SET (parallel_workers=2);\n:PREFIX SELECT count(*) FROM \"test\" WHERE i > 400000 AND length(version()) > 0;\n\nALTER TABLE :CHUNK1 SET (parallel_workers=2);\nALTER TABLE :CHUNK2 SET (parallel_workers=0);\n:PREFIX SELECT count(*) FROM \"test\" WHERE i < 600000 AND length(version()) > 0;\n\nALTER TABLE :CHUNK1 RESET (parallel_workers);\nALTER TABLE :CHUNK2 RESET (parallel_workers);\n\n-- now() is not marked parallel safe in PostgreSQL < 12 so using now()\n-- in a query will prevent parallelism but CURRENT_TIMESTAMP and\n-- transaction_timestamp() are marked parallel safe\n:PREFIX SELECT i FROM \"test\" WHERE ts < CURRENT_TIMESTAMP;\n\n:PREFIX SELECT i FROM \"test\" WHERE ts < transaction_timestamp();\n\n-- this won't be parallel query because now() is parallel restricted in PG < 12\n:PREFIX SELECT i FROM \"test\" WHERE ts < now();\n\n"
  },
  {
    "path": "test/sql/partition.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE TABLE part_legacy(time timestamptz, temp float, device int);\nSELECT create_hypertable('part_legacy', 'time', 'device', 2, partitioning_func => '_timescaledb_functions.get_partition_for_key');\n\n-- Show legacy partitioning function is used\nSELECT * FROM _timescaledb_catalog.dimension;\n\nINSERT INTO part_legacy VALUES ('2017-03-22T09:18:23', 23.4, 1);\nINSERT INTO part_legacy VALUES ('2017-03-22T09:18:23', 23.4, 76);\n\nVACUUM part_legacy;\n\n-- Show two chunks and CHECK constraint with cast\nSELECT * FROM test.show_constraintsp('_timescaledb_internal._hyper_1_%_chunk');\n\n-- Make sure constraint exclusion works on device column\nBEGIN;\n-- For plan stability between versions\nSET LOCAL enable_bitmapscan = false;\nSET LOCAL enable_indexscan = false;\nEXPLAIN (verbose, buffers off, costs off)\nSELECT * FROM part_legacy WHERE device = 1;\nCOMMIT;\n\nCREATE TABLE part_new(time timestamptz, temp float, device int);\nSELECT create_hypertable('part_new', 'time', 'device', 2);\n\nSELECT * FROM _timescaledb_catalog.dimension;\n\nINSERT INTO part_new VALUES ('2017-03-22T09:18:23', 23.4, 1);\nINSERT INTO part_new VALUES ('2017-03-22T09:18:23', 23.4, 2);\n\nVACUUM part_new;\n\n-- Show two chunks and CHECK constraint without cast\nSELECT * FROM test.show_constraintsp('_timescaledb_internal._hyper_2_%_chunk');\n\n-- Make sure constraint exclusion works on device column\nBEGIN;\n-- For plan stability between versions\nSET LOCAL enable_bitmapscan = false;\nSET LOCAL enable_indexscan = false;\nEXPLAIN (verbose, buffers off, costs off)\nSELECT * FROM part_new WHERE device = 1;\nCOMMIT;\n\nCREATE TABLE part_new_convert1(time timestamptz, temp float8, device int);\nSELECT create_hypertable('part_new_convert1', 'time', 'temp', 2);\n\nINSERT INTO part_new_convert1 VALUES ('2017-03-22T09:18:23', 1.0, 2);\n\\set ON_ERROR_STOP 0\n-- Changing the type of a hash-partitioned column should not be supported\nALTER TABLE part_new_convert1 ALTER COLUMN temp TYPE numeric;\n\\set ON_ERROR_STOP 1\n\n-- Should be able to change if not hash partitioned though\nALTER TABLE part_new_convert1 ALTER COLUMN time TYPE timestamp;\n\nSELECT * FROM test.show_columnsp('_timescaledb_internal._hyper_3_%_chunk');\n\nCREATE TABLE part_add_dim(time timestamptz, temp float8, device int, location int);\nSELECT create_hypertable('part_add_dim', 'time', 'temp', 2);\n\n\\set ON_ERROR_STOP 0\nSELECT add_dimension('part_add_dim', 'location', 2, partitioning_func => 'bad_func');\n\\set ON_ERROR_STOP 1\n\nSELECT add_dimension('part_add_dim', 'location', 2, partitioning_func => '_timescaledb_functions.get_partition_for_key');\nSELECT * FROM _timescaledb_catalog.dimension;\n\n-- Test that we support custom SQL-based partitioning functions and\n-- that our native partitioning function handles function expressions\n-- as argument\nCREATE OR REPLACE FUNCTION custom_partfunc(source anyelement)\n    RETURNS INTEGER LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nDECLARE\n    retval INTEGER;\nBEGIN\n    retval = _timescaledb_functions.get_partition_hash(substring(source::text FROM '[A-za-z0-9 ]+'));\n    RAISE NOTICE 'hash value for % is %', source, retval;\n    RETURN retval;\nEND\n$BODY$;\n\nCREATE TABLE part_custom_func(time timestamptz, temp float8, device text);\nSELECT create_hypertable('part_custom_func', 'time', 'device', 2, partitioning_func => 'custom_partfunc');\n\nSELECT _timescaledb_functions.get_partition_hash(substring('dev1' FROM '[A-za-z0-9 ]+'));\nSELECT _timescaledb_functions.get_partition_hash('dev1'::text);\nSELECT _timescaledb_functions.get_partition_hash('dev7'::text);\n\nINSERT INTO part_custom_func VALUES ('2017-03-22T09:18:23', 23.4, 'dev1'),\n                                    ('2017-03-22T09:18:23', 23.4, 'dev7');\n\nSELECT * FROM test.show_subtables('part_custom_func');\n\n-- This first test is slightly trivial, but segfaulted in old versions\nCREATE TYPE simpl AS (val1 int4);\n\nCREATE OR REPLACE FUNCTION simpl_type_hash(ANYELEMENT) RETURNS int4 AS $$\n    SELECT $1.val1;\n$$ LANGUAGE SQL IMMUTABLE;\n\nCREATE TABLE simpl_partition (\"timestamp\" TIMESTAMPTZ, object simpl);\n\nSELECT create_hypertable(\n    'simpl_partition',\n    'timestamp',\n    'object',\n    1000,\n    chunk_time_interval => interval '1 day',\n    partitioning_func=>'simpl_type_hash');\n\nINSERT INTO simpl_partition VALUES ('2017-03-22T09:18:23', ROW(1)::simpl);\n\nSELECT * from simpl_partition;\n\n-- Also test that the fix works when we have more chunks than allowed at once\nSET timescaledb.max_open_chunks_per_insert=1;\n\nINSERT INTO simpl_partition VALUES\n    ('2017-03-22T10:18:23', ROW(0)::simpl),\n    ('2017-03-22T10:18:23', ROW(1)::simpl),\n    ('2017-03-22T10:18:23', ROW(2)::simpl),\n    ('2017-03-22T10:18:23', ROW(3)::simpl),\n    ('2017-03-22T10:18:23', ROW(4)::simpl),\n    ('2017-03-22T10:18:23', ROW(5)::simpl);\n\nSET timescaledb.max_open_chunks_per_insert=default;\n\nSELECT * from simpl_partition;\n\n-- Test that index creation is handled correctly.\nCREATE TABLE hyper_with_index(time timestamptz, temp float, device int);\nCREATE UNIQUE INDEX temp_index ON hyper_with_index(temp);\n\n\\set ON_ERROR_STOP 0\nSELECT create_hypertable('hyper_with_index', 'time');\nSELECT create_hypertable('hyper_with_index', 'time', 'device', 2);\nSELECT create_hypertable('hyper_with_index', 'time', 'temp', 2);\n\\set ON_ERROR_STOP 1\n\nDROP INDEX temp_index;\nCREATE UNIQUE INDEX time_index ON hyper_with_index(time);\n\n\\set ON_ERROR_STOP 0\n-- should error because device not in index\nSELECT create_hypertable('hyper_with_index', 'time', 'device', 4);\n\\set ON_ERROR_STOP 1\nSELECT create_hypertable('hyper_with_index', 'time');\n-- make sure user created index is used.\n-- not using \\d or \\d+ because output syntax differs\n-- between postgres 9 and postgres 10.\nSELECT indexname FROM pg_indexes WHERE tablename = 'hyper_with_index';\n\\set ON_ERROR_STOP 0\nSELECT add_dimension('hyper_with_index', 'device', 4);\n\\set ON_ERROR_STOP 1\n\nDROP INDEX time_index;\nCREATE UNIQUE INDEX time_space_index ON hyper_with_index(time, device);\nSELECT add_dimension('hyper_with_index', 'device', 4);\n\n\nCREATE TABLE hyper_with_primary(time TIMESTAMPTZ PRIMARY KEY, temp float, device int);\n\\set ON_ERROR_STOP 0\nSELECT create_hypertable('hyper_with_primary', 'time', 'device', 4);\n\\set ON_ERROR_STOP 1\n\nSELECT create_hypertable('hyper_with_primary', 'time');\n\\set ON_ERROR_STOP 0\nSELECT add_dimension('hyper_with_primary', 'device', 4);\n\\set ON_ERROR_STOP 1\n\n-- NON-unique indexes can still be created\nCREATE INDEX temp_index ON hyper_with_index(temp);\n\n-- Make sure custom composite types are supported as dimensions\nCREATE TYPE TUPLE as (val1 int4, val2 int4);\nCREATE FUNCTION tuple_hash(value ANYELEMENT) RETURNS INT4\nLANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nBEGIN\n    RAISE NOTICE 'custom hash value is: %', value.val1+value.val2;\n    RETURN value.val1+value.val2;\nEND\n$BODY$;\n\nCREATE TABLE part_custom_dim (time TIMESTAMPTZ, combo TUPLE, device TEXT);\n\nSELECT create_hypertable('part_custom_dim', 'time', 'combo', 4, partitioning_func=>'tuple_hash');\n\nINSERT INTO part_custom_dim(time, combo) VALUES (now(), (1,2));\n\nDROP TABLE part_custom_dim;\n-- Now make sure that renaming partitioning_func_schema will get updated properly\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA IF NOT EXISTS my_partitioning_schema;\n\nCREATE FUNCTION my_partitioning_schema.tuple_hash(value ANYELEMENT) RETURNS INT4\nLANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nBEGIN\n    RAISE NOTICE 'custom hash value is: %', value.val1+value.val2;\n    RETURN value.val1+value.val2;\nEND\n$BODY$;\n\nCREATE TABLE part_custom_dim (time TIMESTAMPTZ, combo TUPLE, device TEXT);\n\nSELECT create_hypertable('part_custom_dim', 'time', 'combo', 4, partitioning_func=>'my_partitioning_schema.tuple_hash');\nINSERT INTO part_custom_dim(time, combo) VALUES (now(), (1,2));\n\nALTER SCHEMA my_partitioning_schema RENAME TO new_partitioning_schema;\n-- Inserts should work even after we rename the schema\nINSERT INTO part_custom_dim(time, combo) VALUES (now(), (3,4));\n\n-- Test partitioning function on an open (time) dimension\nCREATE OR REPLACE FUNCTION time_partfunc(unixtime float8)\n    RETURNS TIMESTAMPTZ LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nDECLARE\n    retval TIMESTAMPTZ;\nBEGIN\n\n    retval := to_timestamp(unixtime);\n    RAISE NOTICE 'time value for % is %', unixtime, timezone('UTC', retval);\n    RETURN retval;\nEND\n$BODY$;\n\nCREATE OR REPLACE FUNCTION time_partfunc_bad_parameters(unixtime float8, extra text)\n    RETURNS TIMESTAMPTZ LANGUAGE SQL IMMUTABLE AS\n$BODY$\n    SELECT to_timestamp(unixtime);\n$BODY$;\n\nCREATE OR REPLACE FUNCTION time_partfunc_bad_return_type(unixtime float8)\n    RETURNS FLOAT8 LANGUAGE SQL IMMUTABLE AS\n$BODY$\n    SELECT unixtime;\n$BODY$;\n\nCREATE TABLE part_time_func(time float8, temp float8, device text);\n\\set ON_ERROR_STOP 0\n-- Should fail due to invalid time column\nSELECT create_hypertable('part_time_func', 'time');\n\n-- Should fail due to bad signature of time partitioning function\nSELECT create_hypertable('part_time_func', 'time', time_partitioning_func => 'time_partfunc_bad_parameters');\nSELECT create_hypertable('part_time_func', 'time', time_partitioning_func => 'time_partfunc_bad_return_type');\n\\set ON_ERROR_STOP 1\n\n-- Should work with time partitioning function that returns a valid time type\nSELECT create_hypertable('part_time_func', 'time', time_partitioning_func => 'time_partfunc');\n\nINSERT INTO part_time_func VALUES (1530214157.134, 23.4, 'dev1'),\n                                  (1533214157.8734, 22.3, 'dev7');\n\nSELECT time, temp, device FROM part_time_func;\nSELECT time_partfunc(time) at time zone 'UTC', temp, device FROM part_time_func;\nSELECT * FROM test.show_subtables('part_time_func');\nSELECT (test.show_constraints(\"Child\")).*\nFROM test.show_subtables('part_time_func');\nSELECT (test.show_indexes(\"Child\")).*\nFROM test.show_subtables('part_time_func');\n\n-- Check that constraint exclusion works with time partitioning\n-- function (scan only one chunk)\n\n-- No exclusion\nEXPLAIN (verbose, buffers off, costs off)\nSELECT * FROM part_time_func;\n\n-- Exclude using the function on time\nEXPLAIN (verbose, buffers off, costs off)\nSELECT * FROM part_time_func WHERE time_partfunc(time) < '2018-07-01';\n\n-- Exclude using the same date but as a UNIX timestamp. Won't do an\n-- index scan since the index is on the time function expression\nEXPLAIN (verbose, buffers off, costs off)\nSELECT * FROM part_time_func WHERE time < 1530403200.0;\n\n-- Check that inserts will fail if we use a time partitioning function\n-- that returns NULL\nCREATE OR REPLACE FUNCTION time_partfunc_null_ret(unixtime float8)\n    RETURNS TIMESTAMPTZ LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nBEGIN\n    RETURN NULL;\nEND\n$BODY$;\n\nCREATE TABLE part_time_func_null_ret(time float8, temp float8, device text);\nSELECT create_hypertable('part_time_func_null_ret', 'time', time_partitioning_func => 'time_partfunc_null_ret');\n\n\\set ON_ERROR_STOP 0\nINSERT INTO part_time_func_null_ret VALUES (1530214157.134, 23.4, 'dev1'),\n                                           (1533214157.8734, 22.3, 'dev7');\n\\set ON_ERROR_STOP 1\n\n"
  },
  {
    "path": "test/sql/partition_coercion.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Test partition chunk exclusion with cross-type comparisons\n\n-- wrong result: text column + name literal\nCREATE TABLE hash_text(time timestamptz NOT NULL, device text);\nSELECT create_hypertable('hash_text', 'time');\nSELECT add_dimension('hash_text', 'device', number_partitions => 3);\nINSERT INTO hash_text VALUES ('2000-01-01', 'abc');\nSELECT count(*) FROM hash_text WHERE device = 'abc'::name;\nDROP TABLE hash_text;\n\n-- x86_64 wrong result: int4 time column + large int8 literal + range query\n-- 4294967196::int8 truncates to -100 as signed int4\n-- Chunk exclusion uses time < -100, excluding all positive-time chunks\nCREATE FUNCTION time_part_int4(val int4) RETURNS int4 AS $$ SELECT val $$ LANGUAGE SQL IMMUTABLE;\nCREATE TABLE time_int4(time int4 NOT NULL, v int);\nSELECT create_hypertable('time_int4', 'time', chunk_time_interval => 100, time_partitioning_func => 'time_part_int4');\nINSERT INTO time_int4 VALUES (100, 1), (200, 2);\n-- Both rows satisfy time < 4294967196, but bug truncates to time < -100\nSELECT count(*) FROM time_int4 WHERE time < 4294967196::int8;\nDROP TABLE time_int4;\nDROP FUNCTION time_part_int4;\n\n-- i386 crash: int8 time column + int4 literal + custom partitioning\n-- On i386: SEGFAULT (DatumGetInt64 dereferences byval int4 as pointer)\n-- On x86_64: works by coincidence (both int4 and int8 are byval)\nCREATE FUNCTION time_part_int8(val int8) RETURNS int8 AS $$ SELECT val $$ LANGUAGE SQL IMMUTABLE;\nCREATE TABLE time_int8(time int8 NOT NULL, v int);\nSELECT create_hypertable('time_int8', 'time', chunk_time_interval => 10, time_partitioning_func => 'time_part_int8');\nINSERT INTO time_int8 VALUES (1, 1), (11, 2), (21, 3);\nSELECT count(*) FROM time_int8 WHERE time = 1::int4;\nDROP TABLE time_int8;\nDROP FUNCTION time_part_int8;\n\n-- Exact type match: text column + text literal (no coercion needed)\nCREATE TABLE hash_text_exact(time timestamptz NOT NULL, device text);\nSELECT create_hypertable('hash_text_exact', 'time');\nSELECT add_dimension('hash_text_exact', 'device', number_partitions => 3);\nINSERT INTO hash_text_exact VALUES ('2000-01-01', 'abc');\nSELECT count(*) FROM hash_text_exact WHERE device = 'abc'::text;\nDROP TABLE hash_text_exact;\n\n-- Binary compatible types: text column + varchar literal\n-- PostgreSQL coerces varchar to text at parse time\nCREATE TABLE hash_text_varchar(time timestamptz NOT NULL, device text);\nSELECT create_hypertable('hash_text_varchar', 'time');\nSELECT add_dimension('hash_text_varchar', 'device', number_partitions => 3);\nINSERT INTO hash_text_varchar VALUES ('2000-01-01', 'abc');\nSELECT count(*) FROM hash_text_varchar WHERE device = 'abc'::varchar;\nDROP TABLE hash_text_varchar;\n\n-- Array coercion: text column + name[] array (ScalarArrayOpExpr)\n-- Test both ANY (OR) and ALL (AND) semantics\nCREATE TABLE hash_text_array(time timestamptz NOT NULL, device text);\nSELECT create_hypertable('hash_text_array', 'time');\nSELECT add_dimension('hash_text_array', 'device', number_partitions => 3);\nINSERT INTO hash_text_array VALUES ('2000-01-01', 'abc'), ('2000-01-01', 'def'), ('2000-01-01', 'ghi');\n-- OR: match any element\nSELECT count(*) FROM hash_text_array WHERE device = ANY(ARRAY['abc', 'def']::name[]);\n-- AND: match all elements (logically empty for different values, but exercises code path)\nSELECT count(*) FROM hash_text_array WHERE device = ALL(ARRAY['abc', 'def']::name[]);\n-- AND: single element (equivalent to =)\nSELECT count(*) FROM hash_text_array WHERE device = ALL(ARRAY['abc']::name[]);\nDROP TABLE hash_text_array;\n\n-- Time dimension with SAOP + type coercion (int4 column, int8 array)\n-- Note: open (time/range) dimensions can't use SAOP with multiple OR values\n-- for chunk exclusion, but AND with single effective bound works.\n-- These tests verify correct results and that AND cases use chunk exclusion.\nCREATE FUNCTION time_part_int4_saop(val int4) RETURNS int4 AS $$ SELECT val $$ LANGUAGE SQL IMMUTABLE;\nCREATE TABLE time_int4_saop(time int4 NOT NULL, v int);\nSELECT create_hypertable('time_int4_saop', 'time', chunk_time_interval => 100, time_partitioning_func => 'time_part_int4_saop');\nINSERT INTO time_int4_saop VALUES (50, 1), (150, 2), (250, 3);\n-- AND: time < ALL(array) means time < min(array) = 100\n-- Single effective bound, chunk exclusion should work\nSELECT count(*) FROM time_int4_saop WHERE time < ALL(ARRAY[100, 200]::int8[]);\n-- AND: time > ALL(array) means time > max(array) = 200\nSELECT count(*) FROM time_int4_saop WHERE time > ALL(ARRAY[100, 200]::int8[]);\n-- OR cases: chunk exclusion not used (multiple OR values rejected),\n-- but results must still be correct\nSELECT count(*) FROM time_int4_saop WHERE time < ANY(ARRAY[100, 200]::int8[]);\nSELECT count(*) FROM time_int4_saop WHERE time > ANY(ARRAY[100, 200]::int8[]);\nDROP TABLE time_int4_saop;\nDROP FUNCTION time_part_int4_saop;\n\n-- Prepared statement with varchar parameter, text column\n-- Custom plan: coercion at plan time, chunk exclusion works\n-- Generic plan: no chunk exclusion (param unknown), but correct result\nCREATE TABLE hash_prep(time timestamptz NOT NULL, device text);\nSELECT create_hypertable('hash_prep', 'time');\nSELECT add_dimension('hash_prep', 'device', number_partitions => 3);\nINSERT INTO hash_prep VALUES ('2000-01-01', 'abc'), ('2000-01-01', 'def');\nPREPARE hash_q(varchar) AS SELECT count(*) FROM hash_prep WHERE device = $1;\nSET plan_cache_mode = force_custom_plan;\nEXECUTE hash_q('abc');\nEXECUTE hash_q('def');\nSET plan_cache_mode = force_generic_plan;\nEXECUTE hash_q('abc');\nEXECUTE hash_q('def');\nRESET plan_cache_mode;\nDEALLOCATE hash_q;\nDROP TABLE hash_prep;\n\n-- Multiple ANDed restrictions on closed dimension\n-- Use IN + = to exercise intersection: IN creates list, = intersects with it\nCREATE TABLE hash_multi(time timestamptz NOT NULL, device text);\nSELECT create_hypertable('hash_multi', 'time');\nSELECT add_dimension('hash_multi', 'device', number_partitions => 3);\nINSERT INTO hash_multi VALUES ('2000-01-01', 'abc'), ('2000-01-01', 'def');\n-- IN creates partition list [abc,def], then = intersects with [abc] => [abc]\nSELECT count(*) FROM hash_multi WHERE device IN ('abc', 'def') AND device = 'abc';\nDROP TABLE hash_multi;\n\n-- NULL array elements: exercises branch when iterating arrays with NULLs\n-- The NULL elements should be skipped, non-NULL elements should work\nCREATE TABLE hash_null_array(time timestamptz NOT NULL, device text);\nSELECT create_hypertable('hash_null_array', 'time');\nSELECT add_dimension('hash_null_array', 'device', number_partitions => 3);\nINSERT INTO hash_null_array VALUES ('2000-01-01', 'abc'), ('2000-01-01', 'def');\n-- Array with NULL elements (type coercion path)\nSELECT count(*) FROM hash_null_array WHERE device = ANY(ARRAY['abc', NULL, 'def']::name[]);\n-- Array with NULL elements (no type coercion path)\nSELECT count(*) FROM hash_null_array WHERE device = ANY(ARRAY['abc', NULL, 'def']::text[]);\nDROP TABLE hash_null_array;\n"
  },
  {
    "path": "test/sql/partitioned_hypertable.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Test declarative partitioning for hypertables\n\n\n-- Enable declarative partitioning for all subsequent tests\nSET timescaledb.enable_partitioned_hypertables = true;\n\n-- Basic hypertable creation with TIMESTAMPTZ\nCREATE TABLE metrics(\n    time TIMESTAMP WITH TIME ZONE,\n    device TEXT,\n    value FLOAT\n) WITH (timescaledb.hypertable, timescaledb.partition_column='time');\n\n-- Create with TIMESTAMP\nCREATE TABLE metrics_ts(\n    time TIMESTAMP NOT NULL,\n    device TEXT,\n    value FLOAT\n) WITH (timescaledb.hypertable, timescaledb.partition_column='time');\n\n-- Create with DATE\nCREATE TABLE metrics_date(\n    time DATE NOT NULL,\n    device TEXT,\n    value FLOAT\n) WITH (timescaledb.hypertable, timescaledb.partition_column='time');\n\n-- Create with int\nCREATE TABLE metrics_int(\n    time INT NOT NULL,\n    device TEXT,\n    value FLOAT\n) WITH (timescaledb.hypertable, timescaledb.partition_column='time');\n\n-- Create with custom chunk_time_interval\nCREATE TABLE metrics_custom_interval(\n    time TIMESTAMPTZ NOT NULL,\n    device TEXT,\n    value FLOAT\n) WITH (timescaledb.hypertable, timescaledb.partition_column='time', timescaledb.chunk_interval='30 days');\n\n-- Verify hypertables are actually created and partitioned\nSELECT hypertable_name FROM timescaledb_information.hypertables\nWHERE hypertable_name IN ('metrics', 'metrics_ts', 'metrics_date', 'metrics_int', 'metrics_custom_interval')\nORDER BY hypertable_name;\n\nSELECT DISTINCT(relkind) = 'p' FROM pg_class\nWHERE relname IN ('metrics', 'metrics_ts', 'metrics_date', 'metrics_int', 'metrics_custom_interval');\n\n\\set ON_ERROR_STOP 0\n-- Try to create with invalid partition column type\nCREATE TABLE invalid(time TEXT, value FLOAT) WITH (timescaledb.hypertable, timescaledb.partition_column='time');\nCREATE TABLE invalid(time TEXT, value FLOAT) WITH (timescaledb.hypertable);\n\\set ON_ERROR_STOP 1\n\nDROP TABLE IF EXISTS metrics_ts;\nDROP TABLE IF EXISTS metrics_date;\nDROP TABLE IF EXISTS metrics_int;\nDROP TABLE IF EXISTS metrics_custom_interval;\nDROP TABLE IF EXISTS invalid;\n\n-- Test PARTITION BY syntax\nCREATE TABLE metrics_partition_by(\n    time TIMESTAMPTZ NOT NULL,\n    device TEXT,\n    value FLOAT\n) PARTITION BY RANGE (time) WITH (timescaledb.hypertable);\n\n\\set ON_ERROR_STOP 0\nCREATE TABLE part_col_specified(time TIMESTAMPTZ, device TEXT) PARTITION BY RANGE (time) WITH (timescaledb.hypertable, timescaledb.partition_column='time');\nCREATE TABLE multiple_part_key(time TIMESTAMPTZ, time2 TIMESTAMP, device TEXT) PARTITION BY RANGE (time, time2) WITH (timescaledb.hypertable, timescaledb.partition_column='time');\nCREATE TABLE bad_strategy(time TIMESTAMPTZ, device TEXT) PARTITION BY LIST (time) WITH (timescaledb.hypertable);\n\\set ON_ERROR_STOP 1\nDROP TABLE IF EXISTS metrics_partition_by;\n\n-- Insert Operations and Chunk Creation\nINSERT INTO metrics VALUES\n  ('2025-01-15 00:00:00+00', 'device1', 11.0),\n  ('2025-02-15 00:00:00+00', 'device2', 12.0),\n  ('2025-03-15 00:00:00+00', 'device3', 13.0);\nSELECT count(*) FROM show_chunks('metrics');\nSELECT count(*) FROM metrics;\n\nSELECT * FROM metrics ORDER BY time;\n\n-- Verify chunk was created and attached as partition\nSELECT count(*) FROM show_chunks('metrics');\n\nSELECT child.relname AS chunk, parent.relname AS hypertable\nFROM pg_inherits\nJOIN pg_class child ON inhrelid = child.oid\nJOIN pg_class parent ON inhparent = parent.oid\nWHERE parent.relname = 'metrics'\nLIMIT 1;\n\n-- Insert with CHECK constraint\nALTER TABLE metrics ADD CONSTRAINT valcheck CHECK (value >= 0);\n\n-- Try inserting into existing and new chunk to violate CHECK constraint\n\\set ON_ERROR_STOP 0\nINSERT INTO metrics VALUES ('2025-03-15 00:00:00+00', 'device1', -10.0);\nINSERT INTO metrics VALUES ('2025-04-15 00:00:00+00', 'device1', -10.0);\n\\set ON_ERROR_STOP 1\n\n\n-- SELECT with WHERE on time (partition pruning)\nEXPLAIN (COSTS OFF)\nSELECT * FROM metrics WHERE time >= '2025-01-01' AND time < '2025-02-01';\n\n\n-- FOREIGN KEY from hypertable to regular table\nCREATE TABLE ref_table(id INT PRIMARY KEY, name TEXT);\nINSERT INTO ref_table VALUES (1, 'ref1'), (2, 'ref2');\n\nCREATE TABLE fk_table(\n  time TIMESTAMPTZ NOT NULL,\n  ref_id INT REFERENCES ref_table(id),\n  value FLOAT\n) WITH (timescaledb.hypertable, timescaledb.partition_column='time');\nINSERT INTO fk_table VALUES ('2025-11-01', 1, 10.0);\n\n\\set ON_ERROR_STOP 0\nINSERT INTO fk_table VALUES ('2025-11-01', 999, 20.0);\n\\set ON_ERROR_STOP 1\n\nDROP TABLE ref_table CASCADE;\nDROP TABLE fk_table CASCADE;\n\n-- FOREIGN KEY from hypertable to hypertable\nCREATE TABLE ref_ht(\n    time TIMESTAMPTZ NOT NULL ,\n    id INT,\n    CONSTRAINT ref_ht_pkey PRIMARY KEY (time, id)\n) WITH (timescaledb.hypertable, timescaledb.partition_column='time');\nINSERT INTO ref_ht VALUES\n  ('2025-06-15 00:00:00+00', 1),\n  ('2025-07-15 00:00:00+00', 2);\n\nCREATE TABLE fk_ht(\n    time TIMESTAMPTZ NOT NULL,\n    ref_time TIMESTAMPTZ,\n    ref_id INT,\n    value FLOAT,\n    FOREIGN KEY (ref_time, ref_id) REFERENCES ref_ht(time, id)\n) WITH (timescaledb.hypertable, timescaledb.partition_column='time');\nINSERT INTO fk_ht VALUES\n    ('2025-08-15 00:00:00+00', '2025-06-15 00:00:00+00', 1, 31.0);\n\\set ON_ERROR_STOP 0\nINSERT INTO fk_ht VALUES\n    ('2025-08-15 00:00:00+00', '2025-01-01 00:00:00+00', 999, 32.0);\n\\set ON_ERROR_STOP 1\n\nDROP TABLE ref_ht CASCADE;\nDROP TABLE fk_ht CASCADE;\n\n-- Test if foreign keys to hypertables not using declarative partitioning are still disallowed\nSET timescaledb.enable_partitioned_hypertables = false;\nCREATE TABLE ref_ht(\n    time TIMESTAMPTZ NOT NULL ,\n    id INT,\n    CONSTRAINT ref_ht_pkey PRIMARY KEY (time, id)\n) WITH (timescaledb.hypertable, timescaledb.partition_column='time');\nINSERT INTO ref_ht VALUES\n  ('2025-06-15 00:00:00+00', 1),\n  ('2025-07-15 00:00:00+00', 2);\nSET timescaledb.enable_partitioned_hypertables = true;\n\n\\set ON_ERROR_STOP 0\nCREATE TABLE fk_ht(\n    time TIMESTAMPTZ NOT NULL,\n    ref_time TIMESTAMPTZ,\n    ref_id INT,\n    value FLOAT,\n    FOREIGN KEY (ref_time, ref_id) REFERENCES ref_ht(time, id)\n) WITH (timescaledb.hypertable, timescaledb.partition_column='time');\n\\set ON_ERROR_STOP 1\n\nDROP TABLE ref_ht CASCADE;\nDROP TABLE IF EXISTS fk_ht CASCADE;\n\n-- Test partition wise joins\nCREATE TABLE metrics_pwj(\n    time TIMESTAMPTZ NOT NULL,\n    device TEXT,\n    value FLOAT\n) WITH (timescaledb.hypertable, timescaledb.partition_column='time');\nINSERT INTO metrics_pwj VALUES\n  ('2025-01-15 00:00:00+00', 'device1', 11.0),\n  ('2025-02-15 00:00:00+00', 'device2', 12.0),\n  ('2025-03-15 00:00:00+00', 'device3', 13.0);\n\nSET enable_partitionwise_join = true;\nEXPLAIN (COSTS OFF)\nSELECT m1.device, m2.device\nFROM metrics AS m1\nJOIN metrics_pwj AS m2 ON m1.time = m2.time;\nSET enable_partitionwise_join = false;\n\n-- Transaction - ROLLBACK\nBEGIN;\nINSERT INTO metrics VALUES ('2024-12-20', 'rollback_test', 42.0);\nSELECT count(*)=1 FROM metrics WHERE device = 'rollback_test';\nROLLBACK;\nSELECT count(*)=0 FROM metrics WHERE device = 'rollback_test';\n\n-- Reset GUC\nSET timescaledb.enable_partitioned_hypertables = false;\n\n-- Cleanup\nDROP TABLE IF EXISTS metrics CASCADE;\nDROP TABLE IF EXISTS metrics_pwj CASCADE;\n"
  },
  {
    "path": "test/sql/partitioning.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Should expect an error when creating a hypertable from a partition\n\\set ON_ERROR_STOP 0\nCREATE TABLE partitioned_ht_create(time timestamptz, temp float, device int) PARTITION BY RANGE (time);\nSELECT create_hypertable('partitioned_ht_create', 'time');\n\\set ON_ERROR_STOP 1\n\n-- Should expect an error when attaching a hypertable to a partition\n\\set ON_ERROR_STOP 0\nCREATE TABLE partitioned_attachment_vanilla(time timestamptz, temp float, device int) PARTITION BY RANGE (time);\nCREATE TABLE attachment_hypertable(time timestamptz, temp float, device int);\nSELECT create_hypertable('attachment_hypertable', 'time');\nALTER TABLE partitioned_attachment_vanilla ATTACH PARTITION attachment_hypertable FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');\n\\set ON_ERROR_STOP 1\n\n-- Should not expect an error when attaching a normal table to a partition\nCREATE TABLE partitioned_vanilla(time timestamptz, temp float, device int) PARTITION BY RANGE (time);\nCREATE TABLE attachment_vanilla(time timestamptz, temp float, device int);\nALTER TABLE partitioned_vanilla ATTACH PARTITION attachment_vanilla FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');\n"
  },
  {
    "path": "test/sql/partitionwise.sql.in",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\set PREFIX 'EXPLAIN (VERBOSE, BUFFERS OFF, COSTS OFF)'\n\n-- Create a two dimensional hypertable\nCREATE TABLE hyper (time timestamptz, device int, temp float);\nSELECT * FROM create_hypertable('hyper', 'time', 'device', 2);\n\n-- Create a similar PostgreSQL partitioned table\nCREATE TABLE pg2dim (time timestamptz, device int, temp float) PARTITION BY HASH (device);\nCREATE TABLE pg2dim_h1 PARTITION OF pg2dim FOR VALUES WITH (MODULUS 2, REMAINDER 0) PARTITION BY RANGE(time);\nCREATE TABLE pg2dim_h2 PARTITION OF pg2dim FOR VALUES WITH (MODULUS 2, REMAINDER 1) PARTITION BY RANGE(time);\nCREATE TABLE pg2dim_h1_t1 PARTITION OF pg2dim_h1 FOR VALUES FROM ('2018-01-01 00:00') TO ('2018-09-01 00:00');\nCREATE TABLE pg2dim_h1_t2 PARTITION OF pg2dim_h1 FOR VALUES FROM ('2018-09-01 00:00') TO ('2018-12-01 00:00');\nCREATE TABLE pg2dim_h2_t1 PARTITION OF pg2dim_h2 FOR VALUES FROM ('2018-01-01 00:00') TO ('2018-09-01 00:00');\nCREATE TABLE pg2dim_h2_t2 PARTITION OF pg2dim_h2 FOR VALUES FROM ('2018-09-01 00:00') TO ('2018-12-01 00:00');\n\n-- Create a 1-dimensional partitioned table for comparison\nCREATE TABLE pg1dim (time timestamptz, device int, temp float) PARTITION BY HASH (device);\nCREATE TABLE pg1dim_h1 PARTITION OF pg1dim FOR VALUES WITH (MODULUS 2, REMAINDER 0);\nCREATE TABLE pg1dim_h2 PARTITION OF pg1dim FOR VALUES WITH (MODULUS 2, REMAINDER 1);\n\nINSERT INTO hyper VALUES\n       ('2018-02-19 13:01', 1, 2.3),\n       ('2018-02-19 13:02', 3, 3.1),\n       ('2018-10-19 13:01', 1, 7.6),\n       ('2018-10-19 13:02', 3, 9.0);\n\nINSERT INTO pg2dim VALUES\n       ('2018-02-19 13:01', 1, 2.3),\n       ('2018-02-19 13:02', 3, 3.1),\n       ('2018-10-19 13:01', 1, 7.6),\n       ('2018-10-19 13:02', 3, 9.0);\n\nINSERT INTO pg1dim VALUES\n       ('2018-02-19 13:01', 1, 2.3),\n       ('2018-02-19 13:02', 3, 3.1),\n       ('2018-10-19 13:01', 1, 7.6),\n       ('2018-10-19 13:02', 3, 9.0);\n\nSELECT * FROM test.show_subtables('hyper');\n\nSELECT * FROM pg2dim_h1_t1;\nSELECT * FROM pg2dim_h1_t2;\nSELECT * FROM pg2dim_h2_t1;\nSELECT * FROM pg2dim_h2_t2;\n\n\n-- Compare partitionwise aggreate enabled/disabled. First run queries\n-- on PG partitioned tables for reference.\n\n-- All partition keys covered by GROUP BY\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT device, avg(temp)\nFROM pg1dim\nGROUP BY 1\nORDER BY 1;\n\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT device, avg(temp)\nFROM pg1dim\nGROUP BY 1\nORDER BY 1;\n\n-- All partition keys not covered by GROUP BY (partial partitionwise)\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT device, avg(temp)\nFROM pg2dim\nGROUP BY 1\nORDER BY 1;\n\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT device, avg(temp)\nFROM pg2dim\nGROUP BY 1\nORDER BY 1;\n\n-- All partition keys covered by GROUP BY (full partitionwise)\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM pg2dim\nGROUP BY 1, 2\nORDER BY 1, 2;\n\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM pg2dim\nGROUP BY 1, 2\nORDER BY 1, 2;\n\n-- All partition keys not covered by GROUP BY because of date_trunc\n-- expression on time (partial partitionwise)\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT date_trunc('month', time), device, avg(temp)\nFROM pg2dim\nGROUP BY 1, 2\nORDER BY 1, 2;\n\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT date_trunc('month', time), device, avg(temp)\nFROM pg2dim\nGROUP BY 1, 2\nORDER BY 1, 2;\n\n-- Now run on hypertable\n\n-- All partition keys not covered by GROUP BY (partial partitionwise)\nSET timescaledb.enable_chunkwise_aggregation = 'off';\n:PREFIX\nSELECT device, avg(temp)\nFROM hyper\nGROUP BY 1\nORDER BY 1;\n\nSET timescaledb.enable_chunkwise_aggregation = 'on';\n:PREFIX\nSELECT device, avg(temp)\nFROM hyper\nGROUP BY 1\nORDER BY 1;\n\n-- All partition keys covered (full partitionwise)\nSET timescaledb.enable_chunkwise_aggregation = 'off';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n\nSET timescaledb.enable_chunkwise_aggregation = 'on';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n\n-- Partial aggregation since date_trunc(time) is not a partition key\nSET enable_partitionwise_aggregate = 'off';\n:PREFIX\nSELECT date_trunc('month', time), device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n\n-- Partial aggregation pushdown is currently not supported for this query by\n-- the TSDB pushdown code since a projection is used in the path.\nSET enable_partitionwise_aggregate = 'on';\n:PREFIX\nSELECT date_trunc('month', time), device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n\n-- Also test time_bucket\nSET timescaledb.enable_chunkwise_aggregation = 'off';\n:PREFIX\nSELECT time_bucket('1 month', time), device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n\nSET timescaledb.enable_chunkwise_aggregation = 'on';\n:PREFIX\nSELECT time_bucket('1 month', time), device, avg(temp)\nFROM hyper\nGROUP BY 1, 2\nORDER BY 1, 2;\n\n-- Test partitionwise joins, mostly to see that we do not break\n-- anything\nCREATE TABLE hyper_meta (time timestamptz, device int, info text);\nSELECT * FROM create_hypertable('hyper_meta', 'time', 'device', 2);\n\nINSERT INTO hyper_meta VALUES\n       ('2018-02-19 13:01', 1, 'device_1'),\n       ('2018-02-19 13:02', 3, 'device_3');\n\nSET enable_partitionwise_join = 'off';\n\n:PREFIX\nSELECT h.time, h.device, h.temp, hm.info\nFROM hyper h, hyper_meta hm\nWHERE h.device = hm.device;\n\n:PREFIX\nSELECT pg2.time, pg2.device, pg2.temp, pg1.temp\nFROM pg2dim pg2, pg1dim pg1\nWHERE pg2.device = pg1.device;\n\nSET enable_partitionwise_join = 'on';\n\n:PREFIX\nSELECT h.time, h.device, h.temp, hm.info\nFROM hyper h, hyper_meta hm\nWHERE h.device = hm.device;\n\n:PREFIX\nSELECT pg2.time, pg2.device, pg2.temp, pg1.temp\nFROM pg2dim pg2, pg1dim pg1\nWHERE pg2.device = pg1.device;\n\n-- Test hypertable with time partitioning function\nCREATE OR REPLACE FUNCTION time_func(unixtime float8)\n    RETURNS TIMESTAMPTZ LANGUAGE PLPGSQL IMMUTABLE AS\n$BODY$\nDECLARE\n    retval TIMESTAMPTZ;\nBEGIN\n    retval := to_timestamp(unixtime);\n    RETURN retval;\nEND\n$BODY$;\n\nCREATE TABLE hyper_timepart (time float8, device int, temp float);\nSELECT * FROM create_hypertable('hyper_timepart', 'time', 'device', 2, time_partitioning_func => 'time_func');\n\n-- Planner won't pick push-down aggs on table with time function\n-- unless a certain amount of data\nSELECT setseed(1);\nINSERT INTO hyper_timepart\nSELECT x, ceil(random() * 8), random() * 20\nFROM generate_series(0,5000-1) AS x;\n\n-- All partition keys covered (full partitionwise)\nSET timescaledb.enable_chunkwise_aggregation = 'off';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n\n:PREFIX\nSELECT time_func(time), device, avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n\n-- Grouping on original time column should be pushed-down\nSET timescaledb.enable_chunkwise_aggregation = 'on';\n:PREFIX\nSELECT time, device, avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n\n-- Applying the time partitioning function should also allow push-down\n-- on open dimensions\n:PREFIX\nSELECT time_func(time), device, avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n\n-- Partial aggregation pushdown is currently not supported for this query by\n-- the TSDB pushdown code since a projection is used in the path.\n:PREFIX\nSELECT time_func(time), _timescaledb_functions.get_partition_hash(device), avg(temp)\nFROM hyper_timepart\nGROUP BY 1, 2\nORDER BY 1, 2\nLIMIT 10;\n\n-- Test removal of redundant group key optimization in PG16\n-- All lower versions include the redundant key on device column\n:PREFIX\nSELECT device, avg(temp)\nFROM hyper_timepart\nWHERE device = 1\nGROUP BY 1\nLIMIT 10;\n"
  },
  {
    "path": "test/sql/pg_dump.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\set TEST_DBNAME_EXTRA :TEST_DBNAME _extra\n\n\\o /dev/null\n\\ir include/insert_two_partitions.sql\n\\o\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\nCREATE OR REPLACE FUNCTION bgw_wait(database TEXT, timeout INT, raise_error BOOLEAN DEFAULT TRUE)\nRETURNS VOID\nAS :MODULE_PATHNAME, 'ts_bgw_wait'\nLANGUAGE C VOLATILE;\n\nCREATE SCHEMA test_schema AUTHORIZATION :ROLE_DEFAULT_PERM_USER;\n\\c :TEST_DBNAME\nALTER TABLE PUBLIC.\"two_Partitions\" SET SCHEMA \"test_schema\";\n\n-- Test that we can restore constraints\nALTER TABLE \"test_schema\".\"two_Partitions\"\nADD CONSTRAINT timeCustom_device_id_series_2_key\nUNIQUE (\"timeCustom\", device_id, series_2);\n\n-- Test that we can restore triggers\nCREATE OR REPLACE FUNCTION test_trigger()\n    RETURNS TRIGGER LANGUAGE PLPGSQL AS\n$BODY$\nBEGIN\n    RETURN NEW;\nEND\n$BODY$;\n\n-- Test that a custom chunk sizing function is restored\nCREATE OR REPLACE FUNCTION custom_calculate_chunk_interval(\n        dimension_id INTEGER,\n        dimension_coord BIGINT,\n        chunk_target_size BIGINT\n)\n    RETURNS BIGINT LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\nBEGIN\n    RETURN -1;\nEND\n$BODY$;\n\nSELECT * FROM set_adaptive_chunking('\"test_schema\".\"two_Partitions\"', '1 MB', 'custom_calculate_chunk_interval');\n\n-- Chunk sizing func set\nSELECT * FROM _timescaledb_catalog.hypertable;\nSELECT proname, pronamespace, pronargs\nFROM pg_proc WHERE proname = 'custom_calculate_chunk_interval';\n\nCREATE TRIGGER restore_trigger BEFORE INSERT ON \"test_schema\".\"two_Partitions\"\n\nFOR EACH ROW EXECUTE FUNCTION test_trigger();\n\n-- Save the number of dependent objects so we can make sure we have the same number later\nSELECT count(*) as num_dependent_objects\n  FROM pg_depend\n WHERE refclassid = 'pg_extension'::regclass\n     AND refobjid = (SELECT oid FROM pg_extension WHERE extname = 'timescaledb')\n\\gset\n\nSELECT * FROM test.show_columns('\"test_schema\".\"two_Partitions\"');\nSELECT * FROM test.show_columns('_timescaledb_internal._hyper_1_1_chunk');\nSELECT * FROM test.show_indexes('\"test_schema\".\"two_Partitions\"');\nSELECT * FROM test.show_indexes('_timescaledb_internal._hyper_1_1_chunk');\nSELECT * FROM test.show_constraints('\"test_schema\".\"two_Partitions\"');\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_1_1_chunk');\nSELECT * FROM test.show_triggers('\"test_schema\".\"two_Partitions\"');\nSELECT * FROM test.show_triggers('_timescaledb_internal._hyper_1_1_chunk');\n\nSELECT * FROM \"test_schema\".\"two_Partitions\" ORDER BY \"timeCustom\", device_id, series_0, series_1;\nSELECT * FROM _timescaledb_internal._hyper_1_1_chunk ORDER BY \"timeCustom\", device_id, series_0, series_1;\nSELECT * FROM _timescaledb_internal._hyper_1_2_chunk ORDER BY \"timeCustom\", device_id, series_0, series_1;\n\n-- Show all constraints\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\n\nINSERT INTO _timescaledb_catalog.metadata VALUES ('exported_uuid', 'original_uuid', true);\nINSERT INTO _timescaledb_catalog.metadata VALUES ('metadata_test', 'FOO', false);\n\n\\c postgres :ROLE_SUPERUSER\n-- We shell out to a script in order to grab the correct hostname from the\n-- environmental variables that originally called this psql command. Sadly\n-- vars passed to psql do not work in \\! commands so we can't do it that way.\n\\! utils/pg_dump_aux_dump.sh dump/pg_dump.sql\n\n\\c :TEST_DBNAME\nSET client_min_messages = ERROR;\nCREATE EXTENSION timescaledb CASCADE;\n--create a exported uuid before restoring (mocks telemetry running before restore)\nINSERT INTO _timescaledb_catalog.metadata VALUES ('exported_uuid', 'new_db_uuid', true);\n-- disable background jobs\nUPDATE _timescaledb_catalog.bgw_job SET scheduled = false;\nRESET client_min_messages;\nSELECT timescaledb_pre_restore();\nSHOW timescaledb.restoring;\n-- reconnect and check GUC value in new session\n\\c\nSHOW timescaledb.restoring;\n\n\\! utils/pg_dump_aux_restore.sh dump/pg_dump.sql\n\n-- Now run our post-restore function.\nSELECT timescaledb_post_restore();\nSHOW timescaledb.restoring;\n-- timescaledb_post_restore restarts background worker so we have to stop them\n-- to make sure they dont interfere with this database being used as template below\nSELECT _timescaledb_functions.stop_background_workers();\n\n--should be same as count above\nSELECT count(*) = :num_dependent_objects as dependent_objects_match\n  FROM pg_depend\n WHERE refclassid = 'pg_extension'::regclass\n     AND refobjid = (SELECT oid FROM pg_extension WHERE extname = 'timescaledb');\n\n--we should have the original uuid from the backed up db set as the exported_uuid\nSELECT value = 'original_uuid' FROM _timescaledb_catalog.metadata  WHERE key='exported_uuid';\nSELECT count(*) = 1 FROM _timescaledb_catalog.metadata WHERE key LIKE 'exported%';\n\n--we should have the original value of metadata_test\nSELECT * FROM _timescaledb_catalog.metadata  WHERE key='metadata_test';\n\n--main table and chunk schemas should be the same\nSELECT * FROM test.show_columns('\"test_schema\".\"two_Partitions\"');\nSELECT * FROM test.show_columns('_timescaledb_internal._hyper_1_1_chunk');\nSELECT * FROM test.show_indexes('\"test_schema\".\"two_Partitions\"');\nSELECT * FROM test.show_indexes('_timescaledb_internal._hyper_1_1_chunk');\nSELECT * FROM test.show_constraints('\"test_schema\".\"two_Partitions\"');\nSELECT * FROM test.show_constraints('_timescaledb_internal._hyper_1_1_chunk');\nSELECT * FROM test.show_triggers('\"test_schema\".\"two_Partitions\"');\nSELECT * FROM test.show_triggers('_timescaledb_internal._hyper_1_1_chunk');\n\n--data should be the same\nSELECT * FROM \"test_schema\".\"two_Partitions\" ORDER BY \"timeCustom\", device_id, series_0, series_1;\nSELECT * FROM _timescaledb_internal._hyper_1_1_chunk ORDER BY \"timeCustom\", device_id, series_0, series_1;\nSELECT * FROM _timescaledb_internal._hyper_1_2_chunk ORDER BY \"timeCustom\", device_id, series_0, series_1;\n\nSELECT * FROM _timescaledb_catalog.chunk_constraint;\n\n--Chunk sizing function should have been restored\nSELECT * FROM _timescaledb_catalog.hypertable;\nSELECT proname, pronamespace, pronargs\nFROM pg_proc WHERE proname = 'custom_calculate_chunk_interval';\n\n--check simple ddl still works\nALTER TABLE \"test_schema\".\"two_Partitions\" ADD COLUMN series_3 integer;\nINSERT INTO \"test_schema\".\"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1, series_3) VALUES\n(1357894000000000000, 'dev5', 1.5, 2, 4);\n\nSELECT * FROM ONLY \"test_schema\".\"two_Partitions\";\n\n--query for the extension tables/sequences that will not be dumped by pg_dump (should\n--be empty except for views and explicitly excluded tables)\nSELECT objid::regclass\nFROM pg_catalog.pg_depend\nWHERE   refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass AND\n        refobjid = (select oid from pg_extension where extname='timescaledb') AND\n        deptype = 'e' AND\n        classid='pg_catalog.pg_class'::pg_catalog.regclass\n        AND objid NOT IN (select unnest(extconfig) from pg_extension where extname='timescaledb')\n        ORDER BY objid::regclass::text COLLATE \"C\";\n\n-- Make sure we can't run our restoring functions as a normal perm user as that would disable functionality for the whole db\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n-- Hides error messages in cases where error messages differ between Postgres versions\ncreate or replace function get_sqlstate(in_text TEXT) RETURNS TEXT AS\n$$\nBEGIN\n    BEGIN\n        EXECUTE in_text;\n    EXCEPTION WHEN others THEN GET STACKED DIAGNOSTICS in_text = RETURNED_SQLSTATE;\n    END;\n    RETURN in_text;\nEND;\n$$\nLANGUAGE PLPGSQL;\n\nSELECT get_sqlstate('SELECT timescaledb_pre_restore()');\nSELECT get_sqlstate('SELECT timescaledb_post_restore()');\n\ndrop function get_sqlstate(TEXT);\n\n-- Check that the extension can be copied from an existing database\n-- without explicitly installing it. Stop background workers since we\n-- cannot have any backends connected to the database when cloning it.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT timescaledb_pre_restore();\nSELECT bgw_wait(:'TEST_DBNAME', 60, FALSE);\n\n-- Force other sessions connected to the TEST_DBNAME to be finished\n\\c postgres :ROLE_SUPERUSER\nREVOKE CONNECT ON DATABASE :TEST_DBNAME FROM public;\nALTER DATABASE :TEST_DBNAME allow_connections = off;\nSET client_min_messages TO ERROR;\nSELECT COUNT(pg_catalog.pg_terminate_backend(pid))>=0\nFROM pg_stat_activity\nWHERE datname = ':TEST_DBNAME';\nRESET client_min_messages;\n\nCREATE DATABASE :TEST_DBNAME_EXTRA WITH TEMPLATE :TEST_DBNAME;\nALTER DATABASE :TEST_DBNAME allow_connections = on;\nGRANT CONNECT ON DATABASE :TEST_DBNAME TO public;\n\n-- Connect to the database and do some basic stuff to check that the\n-- extension works.\n\\c :TEST_DBNAME_EXTRA :ROLE_DEFAULT_PERM_USER\nCREATE TABLE test_tz(time timestamptz not null, temp float8, device text);\n\nSELECT create_hypertable('test_tz', 'time', 'device', 2);\n\nSELECT id, schema_name, table_name FROM _timescaledb_catalog.hypertable;\n\nINSERT INTO test_tz VALUES('Mon Mar 20 09:17:00.936242 2017', 23.4, 'dev1');\nINSERT INTO test_tz VALUES('Mon Mar 20 09:27:00.936242 2017', 22, 'dev2');\nINSERT INTO test_tz VALUES('Mon Mar 20 09:28:00.936242 2017', 21.2, 'dev1');\nINSERT INTO test_tz VALUES('Mon Mar 20 09:37:00.936242 2017', 30, 'dev3');\nSELECT * FROM test_tz ORDER BY time;\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\n-- make sure nobody is using it\nSET client_min_messages TO ERROR;\nREVOKE CONNECT ON DATABASE :TEST_DBNAME_EXTRA FROM public;\nSELECT count(pg_terminate_backend(pg_stat_activity.pid)) AS TERMINATED\nFROM pg_stat_activity\nWHERE pg_stat_activity.datname = :'TEST_DBNAME_EXTRA'\nAND pg_stat_activity.pid <> pg_backend_pid() \\gset\nRESET client_min_messages;\n\nDROP DATABASE :TEST_DBNAME_EXTRA WITH (FORCE);\n\n"
  },
  {
    "path": "test/sql/pg_dump_unprivileged.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\c template1 :ROLE_SUPERUSER\n\nSET client_min_messages TO ERROR;\nCREATE EXTENSION IF NOT EXISTS timescaledb;\nRESET client_min_messages;\n\nCREATE USER dump_unprivileged CREATEDB;\n\n\\c template1 dump_unprivileged\nCREATE database dump_unprivileged;\n\n\\! utils/pg_dump_unprivileged.sh\n\n\\c dump_unprivileged :ROLE_SUPERUSER\nDROP EXTENSION timescaledb;\nGRANT ALL ON DATABASE dump_unprivileged TO dump_unprivileged;\n\\c dump_unprivileged dump_unprivileged\n-- Create the timescale extension and table as underprivileged user\nCREATE EXTENSION timescaledb;\nCREATE TABLE t1 (a int);\n-- pg_dump currently fails when dumped\n\\! utils/pg_dump_unprivileged.sh\n\n\\c template1 :ROLE_SUPERUSER\nDROP EXTENSION timescaledb;\nDROP DATABASE dump_unprivileged WITH (FORCE);\nDROP USER dump_unprivileged;\n\n"
  },
  {
    "path": "test/sql/pg_join.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- this test suite is based on the postgres join tests\n--\n-- the tests have been adjusted to work with hypertables\n-- statements that would generate an error have been commented out\n-- because errors don't play nicely with psql output redirection\n-- plan output has been disabled, because we are not interested in\n-- the actual plans produced but in the correctness of the results\n\n-- we need superuser because some of the tests modify statistics\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\n\\set TEST_BASE_NAME join\nSELECT format('include/%s_load.sql', :'TEST_BASE_NAME') as \"TEST_LOAD_NAME\",\n       format('include/%s_query.sql', :'TEST_BASE_NAME') as \"TEST_QUERY_NAME\",\n       format('%s/results/%s_results_optimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_OPTIMIZED\",\n       format('%s/results/%s_results_unoptimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_UNOPTIMIZED\"\n\\gset\nSELECT format('\\! diff -u --label \"Unoptimized results\" --label \"Optimized results\" %s %s', :'TEST_RESULTS_UNOPTIMIZED', :'TEST_RESULTS_OPTIMIZED') as \"DIFF_CMD\"\n\\gset\n\nset client_min_messages to warning;\n\\ir :TEST_LOAD_NAME\n\n\\set PREFIX ''\n\\set ECHO errors\n-- get results with optimizations disabled\n\\o :TEST_RESULTS_UNOPTIMIZED\nSET timescaledb.enable_optimizations TO false;\n\\ir :TEST_QUERY_NAME\n\\o\n\n-- get query results with all optimizations\n\\o :TEST_RESULTS_OPTIMIZED\nSET timescaledb.enable_optimizations TO true;\n\\ir :TEST_QUERY_NAME\n\\o\n\n:DIFF_CMD\n"
  },
  {
    "path": "test/sql/plain.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Tests for plain PostgreSQL commands to ensure that they work while\n-- the TimescaleDB extension is loaded. This is a mix of statements\n-- added mostly as regression checks when bugs are discovered and\n-- fixed.\n\nCREATE TABLE regular_table(time timestamp, temp float8, tag text, color integer);\n\n-- Renaming indexes should work\nCREATE INDEX time_color_idx ON regular_table(time, color);\nALTER INDEX time_color_idx RENAME TO time_color_idx2;\nALTER TABLE regular_table ALTER COLUMN color TYPE bigint;\n\nSELECT * FROM test.show_columns('regular_table');\nSELECT * FROM test.show_indexes('regular_table');\n\n-- Renaming types should work\nCREATE TYPE rainbow AS ENUM ('red', 'orange', 'yellow', 'green', 'blue', 'purple');\nALTER TYPE rainbow RENAME TO colors;\n\n\\dT+\n\nREINDEX TABLE regular_table;\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nREINDEX SCHEMA public;\n\n-- Not only simple statements should work\nCREATE TABLE a (aa TEXT);\nCREATE TABLE z (b TEXT, PRIMARY KEY(aa, b)) inherits (a);\n"
  },
  {
    "path": "test/sql/plan_expand_hypertable.sql.in",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\set PREFIX 'EXPLAIN (buffers off, costs off) '\n\\ir include/plan_expand_hypertable_load.sql\n\\ir include/plan_expand_hypertable_query.sql\n\n\\set ECHO errors\n\\set TEST_BASE_NAME plan_expand_hypertable\nSELECT format('include/%s_load.sql', :'TEST_BASE_NAME') as \"TEST_LOAD_NAME\",\n       format('include/%s_query.sql', :'TEST_BASE_NAME') as \"TEST_QUERY_NAME\",\n       format('%s/results/%s_results_optimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_OPTIMIZED\",\n       format('%s/results/%s_results_unoptimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_UNOPTIMIZED\"\n\\gset\nSELECT format('\\! diff -u  --label \"Unoptimized result\" --label \"Optimized result\" %s %s', :'TEST_RESULTS_UNOPTIMIZED', :'TEST_RESULTS_OPTIMIZED') as \"DIFF_CMD\"\n\\gset\n\n-- run queries with optimization on and off and diff results\n\\set PREFIX ''\n\\o :TEST_RESULTS_OPTIMIZED\nSET timescaledb.enable_optimizations TO true;\n\\ir :TEST_QUERY_NAME\n\\o\n\\o :TEST_RESULTS_UNOPTIMIZED\nSET timescaledb.enable_optimizations TO false;\n\\ir :TEST_QUERY_NAME\n\\o\n\n:DIFF_CMD\n\\set ECHO queries\n\n\\set PREFIX 'EXPLAIN (buffers off, costs off)'\nRESET timescaledb.enable_optimizations;\n\n-- test enable_qual_propagation GUC\nCREATE TABLE t(time timestamptz NOT NULL);\nSELECT table_name FROM create_hypertable('t','time');\nINSERT INTO t VALUES ('2000-01-01'), ('2010-01-01'), ('2020-01-01');\n\n-- time constraint should be in both scans\n:PREFIX SELECT * FROM t t1 INNER JOIN t t2 ON t1.time = t2.time WHERE t1.time < timestamptz '2010-01-01';\n\nSET timescaledb.enable_qual_propagation TO false;\n-- time constraint should only be in t1 scan\n:PREFIX SELECT * FROM t t1 INNER JOIN t t2 ON t1.time = t2.time WHERE t1.time < timestamptz '2010-01-01';\nRESET timescaledb.enable_qual_propagation;\n\n-- test hypertable classification when hypertable is not in cache\n-- https://github.com/timescale/timescaledb/issues/1832\nCREATE TABLE test (a int, time timestamptz NOT NULL);\nSELECT table_name FROM create_hypertable('public.test', 'time');\nINSERT INTO test SELECT i, '2020-04-01'::date-10-i from generate_series(1,20) i;\n\nCREATE OR REPLACE FUNCTION test_f(_ts timestamptz)\nRETURNS SETOF test LANGUAGE SQL STABLE PARALLEL SAFE\nAS $f$\n   SELECT DISTINCT ON (a) * FROM test WHERE time >= _ts ORDER BY a, time DESC\n$f$;\n\n:PREFIX SELECT * FROM test_f(now());\n\n-- create new session\n\\c\n\n-- plan output should be identical to previous session\n:PREFIX SELECT * FROM test_f(now());\n\n\n-- test hypertable expansion in nested function calls\nCREATE TABLE t1 (a int, b int NOT NULL);\nSELECT create_hypertable('t1', 'b', chunk_time_interval=>10);\n\nCREATE TABLE t2 (a int, b int NOT NULL);\nSELECT create_hypertable('t2', 'b', chunk_time_interval=>10);\n\nCREATE OR REPLACE FUNCTION f_t1(_a int, _b int)\n RETURNS SETOF t1\n LANGUAGE SQL\n STABLE PARALLEL SAFE\nAS $function$\n   SELECT DISTINCT ON (a) * FROM t1 WHERE a = _a and b = _b ORDER BY a, b DESC\n$function$\n;\n\nCREATE OR REPLACE FUNCTION f_t2(_a int, _b int) RETURNS SETOF t2 LANGUAGE sql STABLE PARALLEL SAFE\nAS $function$\n   SELECT DISTINCT ON (j.a) j.*\n   FROM\n      f_t1(_a, _b) sc,\n      t2 j\n   WHERE\n      j.b = _b AND\n      j.a = _a\n   ORDER BY j.a, j.b DESC\n$function$\n;\n\nCREATE OR REPLACE FUNCTION f_t1_2(_b int) RETURNS SETOF t1 LANGUAGE SQL STABLE PARALLEL SAFE\nAS $function$\n   SELECT DISTINCT ON (j.a) jt.* FROM t1 j, f_t1(j.a, _b) jt\n$function$;\n\n:PREFIX SELECT * FROM f_t1_2(10);\n:PREFIX SELECT * FROM f_t1_2(10) sc, f_t2(sc.a, 10);\n\n\\set PREFIX 'EXPLAIN (buffers off, costs off, timing off, summary off, analyze)'\n\n-- test qual filtering on hypertable with integer time column\nCREATE TABLE metrics_int1(time int, device text, value float) WITH (tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval=1);\nINSERT INTO metrics_int1 SELECT i, i::text, i FROM generate_series(3,7) i;\n\nSELECT tableoid::regclass, time FROM metrics_int1 ORDER BY time;\n\n-- no contraint on any chunk\n:PREFIX SELECT * FROM metrics_int1 WHERE time >= 2;\n:PREFIX SELECT * FROM metrics_int1 WHERE time >= 1 AND time >= 2;\n\n-- no constraint on any chunk but first chunk excluded\n:PREFIX SELECT * FROM metrics_int1 WHERE time >= 4;\n\n-- test all four combinations of open/closed interval\n:PREFIX SELECT * FROM metrics_int1 WHERE time > 4 AND time < 6;\n:PREFIX SELECT * FROM metrics_int1 WHERE time > 4 AND time <= 6;\n:PREFIX SELECT * FROM metrics_int1 WHERE time >= 4 AND time < 6;\n:PREFIX SELECT * FROM metrics_int1 WHERE time >= 4 AND time <= 6;\n\n-- test between\n:PREFIX SELECT * FROM metrics_int1 WHERE time BETWEEN 4 AND 5;\n\n-- test equality\n:PREFIX SELECT * FROM metrics_int1 WHERE time = 5;\n\n-- test qual filtering on hypertable with integer time column\nSET TIMEZONE='UTC';\nCREATE TABLE metrics_tstz(time timestamptz, device text, value float) WITH (tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval='1day');\nINSERT INTO metrics_tstz SELECT '2000-01-01'::timestamptz + format('%s day',i)::interval, i::text, i FROM generate_series(2,6) i;\n\nSELECT tableoid::regclass, time FROM metrics_tstz ORDER BY time;\n\n-- no contraint on any chunk\n:PREFIX SELECT * FROM metrics_tstz WHERE time >= '2000-01-02';\n:PREFIX SELECT * FROM metrics_tstz WHERE time >= '2000-01-01' AND time >= '2000-01-02';\n\n-- no constraint on any chunk but first chunk excluded\n:PREFIX SELECT * FROM metrics_tstz WHERE time >= '2000-01-04';\n\n-- test all four combinations of open/closed interval\n:PREFIX SELECT * FROM metrics_tstz WHERE time > '2000-01-04' AND time < '2000-01-06';\n:PREFIX SELECT * FROM metrics_tstz WHERE time > '2000-01-04' AND time <= '2000-01-06';\n:PREFIX SELECT * FROM metrics_tstz WHERE time >= '2000-01-04' AND time < '2000-01-06';\n:PREFIX SELECT * FROM metrics_tstz WHERE time >= '2000-01-04' AND time <= '2000-01-06';\n\n-- test between (between is inclusive on both ends)\n:PREFIX SELECT * FROM metrics_tstz WHERE time BETWEEN '2000-01-04' AND '2000-01-05';\n\n-- test equality\n:PREFIX SELECT * FROM metrics_tstz WHERE time = '2000-01-05';\n\n-- constraints on non-dimension columns remain unaffected\n:PREFIX SELECT * FROM metrics_tstz WHERE time >= '2000-01-04' AND time <= '2000-01-06' AND device = '5';\n:PREFIX SELECT * FROM metrics_tstz WHERE time >= '2000-01-04' AND time <= '2000-01-06' AND device IS NOT NULL;\n\n-- non-constant constraints on time dimension column remain unaffected\n:PREFIX SELECT * FROM metrics_tstz WHERE time >= '2000-01-04' AND time <= '2000-01-06' AND time < now();\n\n-- test hypertable with space partitioning\nCREATE TABLE metrics_space(time timestamptz, device text, value float) WITH (tsdb.hypertable,tsdb.partition_column='time',tsdb.chunk_interval='1day');\nSELECT add_dimension('metrics_space', 'device', 4);\nINSERT INTO metrics_space SELECT '2000-01-01'::timestamptz + format('%s day',i)::interval, i::text, i FROM generate_series(2,6) i;\n\n-- no contraints removed due to space partitioning\n:PREFIX SELECT * FROM metrics_space WHERE time >= '2000-01-02';\n\n\\qecho '--TEST END--'\n"
  },
  {
    "path": "test/sql/plan_hashagg.sql.in",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nSET max_parallel_workers_per_gather TO 0;\n\n\\set PREFIX 'EXPLAIN (buffers off, costs off) '\n\\ir include/plan_hashagg_load.sql\n\\ir include/plan_hashagg_query.sql\n\n\\set ECHO none\n\\set TEST_BASE_NAME plan_hashagg\nSELECT format('include/%s_load.sql', :'TEST_BASE_NAME') as \"TEST_LOAD_NAME\",\n       format('include/%s_query.sql', :'TEST_BASE_NAME') as \"TEST_QUERY_NAME\",\n       format('%s/results/%s_results_optimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_OPTIMIZED\",\n       format('%s/results/%s_results_unoptimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_UNOPTIMIZED\"\n\\gset\nSELECT format('\\! diff -u  --label \"Unoptimized result\" --label \"Optimized result\" %s %s', :'TEST_RESULTS_UNOPTIMIZED', :'TEST_RESULTS_OPTIMIZED') as \"DIFF_CMD\"\n\\gset\n\n\nSET client_min_messages TO error;\n--generate the results into two different files\n\\set PREFIX ''\n\\o :TEST_RESULTS_OPTIMIZED\nSET timescaledb.enable_optimizations TO true;\n\\ir :TEST_QUERY_NAME\n\\o\n\\o :TEST_RESULTS_UNOPTIMIZED\nSET timescaledb.enable_optimizations TO false;\n\\ir :TEST_QUERY_NAME\n\\o\n\n:DIFF_CMD\n"
  },
  {
    "path": "test/sql/plan_hypertable_inline.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- test hypertable classification when query is in an inlineable function\n\n\\set PREFIX 'EXPLAIN (buffers off, costs off)'\n\nCREATE TABLE test (a int, b bigint NOT NULL);\nSELECT create_hypertable('public.test', 'b', chunk_time_interval=>10);\nINSERT INTO test SELECT i, i FROM generate_series(1, 20) i;\n\nCREATE OR REPLACE FUNCTION test_f(_ts bigint)\nRETURNS SETOF test LANGUAGE SQL STABLE\nas $f$\n   SELECT DISTINCT ON (a) * FROM test WHERE b >= _ts AND b <= _ts + 2\n$f$;\n\n-- plans must be the same in both cases\n-- specifically, the first plan should not contain the parent hypertable\n-- as that is a sign the pruning was not done successfully\n:PREFIX SELECT * FROM test_f(5);\n\n:PREFIX SELECT DISTINCT ON (a) * FROM test WHERE b >= 5 AND b <= 5 + 2;\n\n\n-- test with FOR UPDATE\nCREATE OR REPLACE FUNCTION test_f(_ts bigint)\nRETURNS SETOF test LANGUAGE SQL STABLE\nas $f$\n   SELECT * FROM test WHERE b >= _ts AND b <= _ts + 2 FOR UPDATE\n$f$;\n\n\n-- pruning should not be done by TimescaleDb in this case\n-- specifically, the parent hypertable must exist in the output plan\n:PREFIX SELECT * FROM test_f(5);\n\n:PREFIX SELECT * FROM test WHERE b >= 5 AND b <= 5 + 2 FOR UPDATE;\n\n-- test with CTE\n-- these cases are just to make sure we're everything is alright with\n-- the way we identify hypertables to prune chunks - we abuse ctename\n-- for this purpose. So double-check if we're not breaking plans\n-- with CTEs here.\nCREATE OR REPLACE FUNCTION test_f(_ts bigint)\nRETURNS SETOF test LANGUAGE SQL STABLE\nas $f$\n   WITH ct AS MATERIALIZED (\n      SELECT DISTINCT ON (a) * FROM test WHERE b >= _ts AND b <= _ts + 2\n   )\n   SELECT * FROM ct\n$f$;\n\n:PREFIX SELECT * FROM test_f(5);\n\n:PREFIX\nWITH ct AS MATERIALIZED (\n   SELECT DISTINCT ON (a) * FROM test WHERE b >= 5 AND b <= 5 + 2\n)\nSELECT * FROM ct;\n\n-- CTE within CTE\n:PREFIX\nWITH ct AS MATERIALIZED (\n   SELECT * FROM test_f(5)\n)\nSELECT * FROM ct;\n\n-- CTE within NO MATERIALIZED CTE\n:PREFIX\nWITH ct AS NOT MATERIALIZED (\n   SELECT * FROM test_f(5)\n)\nSELECT * FROM ct;\n"
  },
  {
    "path": "test/sql/plan_ordered_append.sql.in",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- we run these with analyze to confirm that nodes that are not\n-- needed to fulfill the limit are not executed\n-- unfortunately this doesn't work on PostgreSQL 9.6 which lacks\n-- the ability to turn off analyze timing summary so we run\n-- them without ANALYZE on PostgreSQL 9.6, but since LATERAL plans\n-- are different across versions we need version specific output\n-- here anyway.\n\n\\set TEST_BASE_NAME plan_ordered_append\nSELECT format('include/%s_load.sql', :'TEST_BASE_NAME') as \"TEST_LOAD_NAME\",\n       format('include/%s_query.sql', :'TEST_BASE_NAME') as \"TEST_QUERY_NAME\",\n       format('%s/results/%s_results_optimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_OPTIMIZED\",\n       format('%s/results/%s_results_unoptimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_UNOPTIMIZED\"\n\\gset\nSELECT format('\\! diff -u --label \"Unoptimized result\" --label \"Optimized result\" %s %s', :'TEST_RESULTS_UNOPTIMIZED', :'TEST_RESULTS_OPTIMIZED') as \"DIFF_CMD\"\n\\gset\n\n\\set PREFIX 'EXPLAIN (analyze, buffers off, costs off, timing off, summary off)'\n\\set PREFIX_NO_ANALYZE 'EXPLAIN (buffers off, costs off)'\n\n\\ir :TEST_LOAD_NAME\n\\ir :TEST_QUERY_NAME\n\n--generate the results into two different files\n\\set ECHO errors\n--make output contain query results\n\\set PREFIX ''\n\\set PREFIX_NO_ANALYZE ''\n\\o :TEST_RESULTS_OPTIMIZED\nSET timescaledb.ordered_append = 'on';\n\\ir :TEST_QUERY_NAME\n\\o\n\\o :TEST_RESULTS_UNOPTIMIZED\nSET timescaledb.ordered_append = 'off';\n\\ir :TEST_QUERY_NAME\n\\o\n\n:DIFF_CMD\n"
  },
  {
    "path": "test/sql/query.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\set TEST_BASE_NAME query\nSELECT format('include/%s_load.sql', :'TEST_BASE_NAME') as \"TEST_LOAD_NAME\",\n       format('include/%s_query.sql', :'TEST_BASE_NAME') as \"TEST_QUERY_NAME\",\n       format('%s/results/%s_results_optimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_OPTIMIZED\",\n       format('%s/results/%s_results_unoptimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_UNOPTIMIZED\"\n\\gset\nSELECT format('\\! diff -u  --label \"Unoptimized result\" --label \"Optimized result\" %s %s', :'TEST_RESULTS_UNOPTIMIZED', :'TEST_RESULTS_OPTIMIZED') as \"DIFF_CMD\"\n\\gset\n\n\\set PREFIX 'EXPLAIN (buffers OFF, costs OFF)'\n\\ir :TEST_LOAD_NAME\n\\ir :TEST_QUERY_NAME\n\n--generate the results into two different files\n\\set ECHO errors\nSET client_min_messages TO error;\n--make output contain query results\n\\set PREFIX ''\n\\o :TEST_RESULTS_OPTIMIZED\nSET timescaledb.enable_optimizations TO true;\n\\ir :TEST_QUERY_NAME\n\\o\n\\o :TEST_RESULTS_UNOPTIMIZED\nSET timescaledb.enable_optimizations TO false;\n\\ir :TEST_QUERY_NAME\n\\o\n\n:DIFF_CMD\nSELECT 'Done'\n"
  },
  {
    "path": "test/sql/relocate_extension.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Set this variable to avoid using a hard-coded path each time query\n-- results are compared\n\\set QUERY_RESULT_TEST_EQUAL_RELPATH 'include/query_result_test_equal.sql'\n\n\\c postgres :ROLE_SUPERUSER\nDROP DATABASE :TEST_DBNAME WITH (FORCE);\nCREATE DATABASE :TEST_DBNAME;\n\n\\c :TEST_DBNAME\nCREATE SCHEMA \"testSchema0\";\nSET client_min_messages=error;\nCREATE EXTENSION IF NOT EXISTS timescaledb SCHEMA \"testSchema0\";\nRESET client_min_messages;\n\nCREATE TABLE test_ts(time timestamp, temp float8, device text);\nCREATE TABLE test_tz(time timestamptz, temp float8, device text);\nCREATE TABLE test_dt(time date, temp float8, device text);\n\n\nSELECT \"testSchema0\".create_hypertable('test_ts', 'time', 'device', 2);\nSELECT \"testSchema0\".create_hypertable('test_tz', 'time', 'device', 2);\nSELECT \"testSchema0\".create_hypertable('test_dt', 'time', 'device', 2);\n\nSELECT * FROM _timescaledb_catalog.hypertable;\n\nINSERT INTO test_ts VALUES('Mon Mar 20 09:17:00.936242 2017', 23.4, 'dev1');\nINSERT INTO test_ts VALUES('Mon Mar 20 09:27:00.936242 2017', 22, 'dev2');\nINSERT INTO test_ts VALUES('Mon Mar 20 09:28:00.936242 2017', 21.2, 'dev1');\nINSERT INTO test_ts VALUES('Mon Mar 20 09:37:00.936242 2017', 30, 'dev3');\nSELECT * FROM test_ts ORDER BY time;\n\nINSERT INTO test_tz VALUES('Mon Mar 20 09:17:00.936242 2017', 23.4, 'dev1');\nINSERT INTO test_tz VALUES('Mon Mar 20 09:27:00.936242 2017', 22, 'dev2');\nINSERT INTO test_tz VALUES('Mon Mar 20 09:28:00.936242 2017', 21.2, 'dev1');\nINSERT INTO test_tz VALUES('Mon Mar 20 09:37:00.936242 2017', 30, 'dev3');\nSELECT * FROM test_tz ORDER BY time;\n\nINSERT INTO test_dt VALUES('Mon Mar 20 09:17:00.936242 2017', 23.4, 'dev1');\nINSERT INTO test_dt VALUES('Mon Mar 21 09:27:00.936242 2017', 22, 'dev2');\nINSERT INTO test_dt VALUES('Mon Mar 22 09:28:00.936242 2017', 21.2, 'dev1');\nINSERT INTO test_dt VALUES('Mon Mar 23 09:37:00.936242 2017', 30, 'dev3');\nSELECT * FROM test_dt ORDER BY time;\n-- testing time_bucket START\nSELECT AVG(temp) AS avg_tmp, \"testSchema0\".time_bucket('5 minutes', time, INTERVAL '1 minutes') AS ten_min FROM test_ts GROUP BY ten_min ORDER BY avg_tmp;\nSELECT AVG(temp) AS avg_tmp, \"testSchema0\".time_bucket('5 minutes', time, INTERVAL '1 minutes') AS ten_min FROM test_tz GROUP BY ten_min ORDER BY avg_tmp;\nSELECT AVG(temp) AS avg_tmp, \"testSchema0\".time_bucket('1 day', time, INTERVAL '-0.5 day') AS ten_min FROM test_dt GROUP BY ten_min ORDER BY avg_tmp;\n-- testing time_bucket END\n\n-- testing drop_chunks START\n-- show_chunks and drop_chunks output should be the same\n\\set QUERY1 'SELECT \"testSchema0\".show_chunks(older_than => \\'2017-03-01\\'::timestamp, relation => \\'test_ts\\')::REGCLASS::TEXT'\n\\set QUERY2 'SELECT \"testSchema0\".drop_chunks(\\'test_ts\\', \\'2017-03-01\\'::timestamp)::TEXT'\n\\set ECHO errors\n\\ir  :QUERY_RESULT_TEST_EQUAL_RELPATH\n\\set ECHO all\nSELECT * FROM test_ts ORDER BY time;\n\\set QUERY1 'SELECT \"testSchema0\".show_chunks(older_than => interval \\'1 minutes\\', relation => \\'test_tz\\')::REGCLASS::TEXT'\n\\set QUERY2 'SELECT \"testSchema0\".drop_chunks(\\'test_tz\\', interval \\'1 minutes\\')::TEXT'\n\\set ECHO errors\n\\ir  :QUERY_RESULT_TEST_EQUAL_RELPATH\n\\set ECHO all\nSELECT * FROM test_tz ORDER BY time;\n\\set QUERY1 'SELECT \"testSchema0\".show_chunks(older_than => interval \\'1 minutes\\', relation => \\'test_dt\\')::REGCLASS::TEXT'\n\\set QUERY2 'SELECT \"testSchema0\".drop_chunks(\\'test_dt\\', interval \\'1 minutes\\')::TEXT'\n\\set ECHO errors\n\\ir  :QUERY_RESULT_TEST_EQUAL_RELPATH\n\\set ECHO all\nSELECT * FROM test_dt ORDER BY time;\n-- testing drop_chunks END\n\n-- testing hypertable_detailed_size START\nSELECT * FROM \"testSchema0\".hypertable_detailed_size('test_ts');\n-- testing hypertable_detailed_size END\n\nSELECT * FROM \"testSchema0\".hypertable_index_size('test_ts_time_idx');\nSELECT * FROM \"testSchema0\".hypertable_index_size('test_ts_device_time_idx');\n\nCREATE SCHEMA \"testSchema\";\n\n\\set ON_ERROR_STOP 0\nALTER EXTENSION timescaledb SET SCHEMA \"testSchema\";\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "test/sql/reloptions.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE TABLE reloptions_test(time integer, temp float8, color integer)\nWITH (fillfactor=75, autovacuum_vacuum_threshold=100);\n\nSELECT create_hypertable('reloptions_test', 'time', chunk_time_interval => 3);\n\nINSERT INTO reloptions_test VALUES (4, 24.3, 1), (9, 13.3, 2);\n\n-- Show that reloptions are inherited by chunks\nSELECT relname, reloptions FROM pg_class\nWHERE relname ~ '^_hyper.*' AND relkind = 'r';\n\n-- Alter reloptions. We support multiple options for the ALTER TABLE\nALTER TABLE reloptions_test SET (fillfactor=80, parallel_workers=8);\nALTER TABLE reloptions_test SET (fillfactor=80), SET (parallel_workers=8);\n\nSELECT relname, reloptions FROM pg_class\nWHERE relname ~ '^_hyper.*' AND relkind = 'r';\n\nALTER TABLE reloptions_test RESET (fillfactor);\n\nSELECT relname, reloptions FROM pg_class\nWHERE relname ~ '^_hyper.*' AND relkind = 'r';\n\n-- Test reloptions on a regular table\nCREATE TABLE reloptions_test2(time integer, temp float8, color integer);\nALTER TABLE reloptions_test2 SET (fillfactor=80, parallel_workers=8);\nALTER TABLE reloptions_test2 SET (fillfactor=80), SET (parallel_workers=8);\nDROP TABLE reloptions_test2;\n"
  },
  {
    "path": "test/sql/repair.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- We are testing different repair functions here to make sure that\n-- they work as expected.\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\n\\set TMP_USER :TEST_DBNAME _wizard\n\nCREATE USER :TMP_USER;\nCREATE USER \"Random L User\";\n\nCREATE TABLE test_table_1(time timestamptz not null, temp float);\nSELECT create_hypertable('test_table_1', by_range('time', '1 day'::interval));\n\nINSERT INTO test_table_1(time,temp)\nSELECT time, 100 * random()\n  FROM generate_series(\n           '2000-01-01'::timestamptz,\n           '2000-01-05'::timestamptz,\n           '1min'::interval\n       ) time;\n\nCREATE TABLE test_table_2(time timestamptz not null, temp float);\nSELECT create_hypertable('test_table_2', by_range('time', '1 day'::interval));\n\nINSERT INTO test_table_2(time,temp)\nSELECT time, 100 * random()\n  FROM generate_series(\n           '2000-01-01'::timestamptz,\n           '2000-01-05'::timestamptz,\n           '1min'::interval\n       ) time;\n\nGRANT ALL ON test_table_1 TO :TMP_USER;\nGRANT ALL ON test_table_2 TO :TMP_USER;\nGRANT SELECT, INSERT ON test_table_1 TO \"Random L User\";\nGRANT INSERT ON test_table_2 TO \"Random L User\";\n\n-- Break the relacl of the table by deleting users\nDELETE FROM pg_authid WHERE rolname IN (:'TMP_USER', 'Random L User');\n\nCREATE TABLE saved (LIKE pg_class);\nINSERT INTO saved SELECT * FROM pg_class;\n\nCALL _timescaledb_functions.repair_relation_acls();\n\n-- The only thing we should see here are the relations we broke and\n-- the privileges we added for that user. No other relations should be\n-- touched.\nWITH\n   lhs AS (SELECT oid, aclexplode(relacl) FROM pg_class),\n   rhs AS (SELECT oid, aclexplode(relacl) FROM saved)\nSELECT rhs.oid::regclass\nFROM lhs FULL OUTER JOIN rhs ON row(lhs) = row(rhs)\nWHERE lhs.oid IS NULL AND rhs.oid IS NOT NULL\nGROUP BY rhs.oid;\n\nDROP TABLE saved;\nDROP TABLE test_table_1;\nDROP TABLE test_table_2;\n"
  },
  {
    "path": "test/sql/rowsecurity.sql.in",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n--\n-- Test of Row-level security feature\n--\n\n-- Clean up in case a prior regression run failed\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\\set ON_ERROR_STOP 0\n\\set VERBOSITY default\n\nSET timescaledb.enable_constraint_exclusion TO off;\n\n-- Suppress NOTICE messages when users/groups don't exist\nSET client_min_messages TO 'warning';\n\nDROP USER IF EXISTS regress_rls_alice;\nDROP USER IF EXISTS regress_rls_bob;\nDROP USER IF EXISTS regress_rls_carol;\nDROP USER IF EXISTS regress_rls_dave;\nDROP USER IF EXISTS regress_rls_exempt_user;\nDROP ROLE IF EXISTS regress_rls_group1;\nDROP ROLE IF EXISTS regress_rls_group2;\n\nDROP SCHEMA IF EXISTS regress_rls_schema CASCADE;\n\nRESET client_min_messages;\n\n-- initial setup\nCREATE USER regress_rls_alice NOLOGIN;\nCREATE USER regress_rls_bob NOLOGIN;\nCREATE USER regress_rls_carol NOLOGIN;\nCREATE USER regress_rls_dave NOLOGIN;\nCREATE USER regress_rls_exempt_user BYPASSRLS NOLOGIN;\nCREATE ROLE regress_rls_group1 NOLOGIN;\nCREATE ROLE regress_rls_group2 NOLOGIN;\n\nGRANT regress_rls_group1 TO regress_rls_bob;\nGRANT regress_rls_group2 TO regress_rls_carol;\n\nCREATE SCHEMA regress_rls_schema;\nGRANT ALL ON SCHEMA regress_rls_schema to public;\nSET search_path = regress_rls_schema;\n\n-- setup of malicious function\nCREATE OR REPLACE FUNCTION f_leak(text) RETURNS bool\n    COST 0.0000001 LANGUAGE plpgsql\n    AS 'BEGIN RAISE NOTICE ''f_leak => %'', $1; RETURN true; END';\nGRANT EXECUTE ON FUNCTION f_leak(text) TO public;\n\n-- BASIC Row-Level Security Scenario\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE uaccount (\n    pguser      name primary key,\n    seclv       int\n);\nGRANT SELECT ON uaccount TO public;\nINSERT INTO uaccount VALUES\n    ('regress_rls_alice', 99),\n    ('regress_rls_bob', 1),\n    ('regress_rls_carol', 2),\n    ('regress_rls_dave', 3);\n\nCREATE TABLE category (\n    cid        int primary key,\n    cname      text\n);\nGRANT ALL ON category TO public;\nINSERT INTO category VALUES\n    (11, 'novel'),\n    (22, 'science fiction'),\n    (33, 'technology'),\n    (44, 'manga');\n\nCREATE TABLE document (\n    did         int primary key,\n    cid         int references category(cid),\n    dlevel      int not null,\n    dauthor     name,\n    dtitle      text\n);\nGRANT ALL ON document TO public;\n\nSELECT public.create_hypertable('document', 'did', chunk_time_interval=>2);\n\nINSERT INTO document VALUES\n    ( 1, 11, 1, 'regress_rls_bob', 'my first novel'),\n    ( 2, 11, 2, 'regress_rls_bob', 'my second novel'),\n    ( 3, 22, 2, 'regress_rls_bob', 'my science fiction'),\n    ( 4, 44, 1, 'regress_rls_bob', 'my first manga'),\n    ( 5, 44, 2, 'regress_rls_bob', 'my second manga'),\n    ( 6, 22, 1, 'regress_rls_carol', 'great science fiction'),\n    ( 7, 33, 2, 'regress_rls_carol', 'great technology book'),\n    ( 8, 44, 1, 'regress_rls_carol', 'great manga'),\n    ( 9, 22, 1, 'regress_rls_dave', 'awesome science fiction'),\n    (10, 33, 2, 'regress_rls_dave', 'awesome technology book');\n\nALTER TABLE document ENABLE ROW LEVEL SECURITY;\n\n-- user's security level must be higher than or equal to document's\nCREATE POLICY p1 ON document AS PERMISSIVE\n    USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));\n\n-- try to create a policy of bogus type\nCREATE POLICY p1 ON document AS UGLY\n    USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));\n\n-- but Dave isn't allowed to anything at cid 50 or above\n-- this is to make sure that we sort the policies by name first\n-- when applying WITH CHECK, a later INSERT by Dave should fail due\n-- to p1r first\nCREATE POLICY p2r ON document AS RESTRICTIVE TO regress_rls_dave\n    USING (cid <> 44 AND cid < 50);\n\n-- and Dave isn't allowed to see manga documents\nCREATE POLICY p1r ON document AS RESTRICTIVE TO regress_rls_dave\n    USING (cid <> 44);\n\n\\dp\n\\d document\nSELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename = 'document' ORDER BY policyname;\n\n-- viewpoint from regress_rls_bob\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO ON;\nSELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;\nSELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;\n\n-- try a sampled version\nSELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0)\n  WHERE f_leak(dtitle) ORDER BY did;\n\n-- viewpoint from regress_rls_carol\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;\nSELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;\n\n-- try a sampled version\nSELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0)\n  WHERE f_leak(dtitle) ORDER BY did;\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);\n\n-- viewpoint from regress_rls_dave\nSET SESSION AUTHORIZATION regress_rls_dave;\nSELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;\nSELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);\n\n-- 44 would technically fail for both p2r and p1r, but we should get an error\n-- back from p1r for this because it sorts first\nINSERT INTO document VALUES (100, 44, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail\n-- Just to see a p2r error\nINSERT INTO document VALUES (100, 55, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail\n\n-- only owner can change policies\nALTER POLICY p1 ON document USING (true);    --fail\nDROP POLICY p1 ON document;                  --fail\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER POLICY p1 ON document USING (dauthor = current_user);\n\n-- viewpoint from regress_rls_bob again\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;\nSELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did;\n\n-- viewpoint from rls_regres_carol again\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;\nSELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did;\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);\n\n-- interaction of FK/PK constraints\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE POLICY p2 ON category\n    USING (CASE WHEN current_user = 'regress_rls_bob' THEN cid IN (11, 33)\n           WHEN current_user = 'regress_rls_carol' THEN cid IN (22, 44)\n           ELSE false END);\n\nALTER TABLE category ENABLE ROW LEVEL SECURITY;\n\n-- cannot delete PK referenced by invisible FK\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid ORDER BY d.did, c.cid;\n\\set VERBOSITY sqlstate\nDELETE FROM category WHERE cid = 33;    -- fails with FK violation\n\\set VERBOSITY default\n\n-- can insert FK referencing invisible PK\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid ORDER BY d.did, c.cid;\nINSERT INTO document VALUES (11, 33, 1, current_user, 'hoge');\n\n-- UNIQUE or PRIMARY KEY constraint violation DOES reveal presence of row\nSET SESSION AUTHORIZATION regress_rls_bob;\nINSERT INTO document VALUES (8, 44, 1, 'regress_rls_bob', 'my third manga'); -- Must fail with unique violation, revealing presence of did we can't see\nSELECT * FROM document WHERE did = 8; -- and confirm we can't see it\n\n-- RLS policies are checked before constraints\nINSERT INTO document VALUES (8, 44, 1, 'regress_rls_carol', 'my third manga'); -- Should fail with RLS check violation, not duplicate key violation\nUPDATE document SET did = 8, dauthor = 'regress_rls_carol' WHERE did = 5; -- Should fail with RLS check violation, not duplicate key violation\n\n-- database superuser does bypass RLS policy when enabled\nRESET SESSION AUTHORIZATION;\nSET row_security TO ON;\nSELECT * FROM document;\nSELECT * FROM category;\n\n-- database superuser does bypass RLS policy when disabled\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nSELECT * FROM document;\nSELECT * FROM category;\n\n-- database non-superuser with bypass privilege can bypass RLS policy when disabled\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO OFF;\nSELECT * FROM document;\nSELECT * FROM category;\n\n-- RLS policy does not apply to table owner when RLS enabled.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO ON;\nSELECT * FROM document;\nSELECT * FROM category;\n\n-- RLS policy does not apply to table owner when RLS disabled.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO OFF;\nSELECT * FROM document;\nSELECT * FROM category;\n\n--\n-- Table inheritance and RLS policy\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\n\nSET row_security TO ON;\n\nCREATE TABLE t1 (a int, junk1 text, b text);\nALTER TABLE t1 DROP COLUMN junk1;    -- just a disturbing factor\nGRANT ALL ON t1 TO public;\n\nCOPY t1 FROM stdin;\n1\taba\n2\tbbb\n3\tccc\n4\tdad\n\\.\n\nCREATE TABLE t2 (c float) INHERITS (t1);\nGRANT ALL ON t2 TO public;\n\nCOPY t2 FROM stdin;\n1\tabc\t1.1\n2\tbcd\t2.2\n3\tcde\t3.3\n4\tdef\t4.4\n\\.\n\nCREATE TABLE t3 (c text, b text, a int);\nALTER TABLE t3 INHERIT t1;\nGRANT ALL ON t3 TO public;\n\nCOPY t3(a,b,c) FROM stdin;\n1\txxx\tX\n2\tyyy\tY\n3\tzzz\tZ\n\\.\n\nCREATE POLICY p1 ON t1 FOR ALL TO PUBLIC USING (a % 2 = 0); -- be even number\nCREATE POLICY p2 ON t2 FOR ALL TO PUBLIC USING (a % 2 = 1); -- be odd number\n\nALTER TABLE t1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE t2 ENABLE ROW LEVEL SECURITY;\n\nSET SESSION AUTHORIZATION regress_rls_bob;\n\nSELECT * FROM t1;\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1;\n\nSELECT * FROM t1 WHERE f_leak(b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);\n\n-- reference to system column\nSELECT ctid, * FROM t1;\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT *, t1 FROM t1;\n\n-- reference to whole-row reference\nSELECT *, t1 FROM t1;\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT *, t1 FROM t1;\n\n-- for share/update lock\nSELECT * FROM t1 FOR SHARE;\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 FOR SHARE;\n\nSELECT * FROM t1 WHERE f_leak(b) FOR SHARE;\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;\n\n-- union all query\nSELECT a, b, ctid FROM t2 UNION ALL SELECT a, b, ctid FROM t3;\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT a, b, ctid FROM t2 UNION ALL SELECT a, b, ctid FROM t3;\n\n-- superuser is allowed to bypass RLS checks\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nSELECT * FROM t1 WHERE f_leak(b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);\n\n-- non-superuser with bypass privilege can bypass RLS policy when disabled\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO OFF;\nSELECT * FROM t1 WHERE f_leak(b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);\n\n--\n-- Hyper Tables\n--\n\nSET SESSION AUTHORIZATION regress_rls_alice;\n\nCREATE TABLE hyper_document (\n    did         int,\n    cid         int,\n    dlevel      int not null,\n    dauthor     name,\n    dtitle      text\n);\nGRANT ALL ON hyper_document TO public;\n\nSELECT public.create_hypertable('hyper_document', 'did', chunk_time_interval=>2);\n\nINSERT INTO hyper_document VALUES\n    ( 1, 11, 1, 'regress_rls_bob', 'my first novel'),\n    ( 2, 11, 2, 'regress_rls_bob', 'my second novel'),\n    ( 3, 99, 2, 'regress_rls_bob', 'my science textbook'),\n    ( 4, 55, 1, 'regress_rls_bob', 'my first satire'),\n    ( 5, 99, 2, 'regress_rls_bob', 'my history book'),\n    ( 6, 11, 1, 'regress_rls_carol', 'great science fiction'),\n    ( 7, 99, 2, 'regress_rls_carol', 'great technology book'),\n    ( 8, 55, 2, 'regress_rls_carol', 'great satire'),\n    ( 9, 11, 1, 'regress_rls_dave', 'awesome science fiction'),\n    (10, 99, 2, 'regress_rls_dave', 'awesome technology book');\n\nALTER TABLE hyper_document ENABLE ROW LEVEL SECURITY;\n\nGRANT ALL ON _timescaledb_internal._hyper_2_9_chunk TO public;\n\n-- Create policy on parent\n-- user's security level must be higher than or equal to document's\nCREATE POLICY pp1 ON hyper_document AS PERMISSIVE\n    USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));\n\n-- Dave is only allowed to see cid < 55\nCREATE POLICY pp1r ON hyper_document AS RESTRICTIVE TO regress_rls_dave\n    USING (cid < 55);\n\n\\d+ hyper_document\nSELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename like '%hyper_document%' ORDER BY policyname;\n\n-- viewpoint from regress_rls_bob\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO ON;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did;\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n\n-- viewpoint from regress_rls_carol\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did;\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n\n-- viewpoint from regress_rls_dave\nSET SESSION AUTHORIZATION regress_rls_dave;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did;\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n\n-- pp1 ERROR\nINSERT INTO hyper_document VALUES (1, 11, 5, 'regress_rls_dave', 'testing pp1'); -- fail\n-- pp1r ERROR\nINSERT INTO hyper_document VALUES (1, 99, 1, 'regress_rls_dave', 'testing pp1r'); -- fail\n\n-- Show that RLS policy does not apply for direct inserts to children\n-- This should fail with RLS POLICY pp1r violation.\nINSERT INTO hyper_document VALUES (1, 55, 1, 'regress_rls_dave', 'testing RLS with hypertables'); -- fail\n-- But this should succeed.\nINSERT INTO _timescaledb_internal._hyper_2_9_chunk VALUES (1, 55, 1, 'regress_rls_dave', 'testing RLS with hypertables'); -- success\n-- We still cannot see the row using the parent\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did, cid;\n-- But we can if we look directly\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk WHERE f_leak(dtitle) ORDER BY did, cid;\n\n-- Turn on RLS and create policy on child to show RLS is checked before constraints\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER TABLE _timescaledb_internal._hyper_2_9_chunk ENABLE ROW LEVEL SECURITY;\nCREATE POLICY pp3 ON _timescaledb_internal._hyper_2_9_chunk AS RESTRICTIVE\n    USING (cid < 55);\n-- This should fail with RLS violation now.\nSET SESSION AUTHORIZATION regress_rls_dave;\nINSERT INTO _timescaledb_internal._hyper_2_9_chunk VALUES (1, 55, 1, 'regress_rls_dave', 'testing RLS with hypertables - round 2'); -- fail\n-- And now we cannot see directly into the partition either, due to RLS\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk WHERE f_leak(dtitle) ORDER BY did, cid;\n-- The parent looks same as before\n-- viewpoint from regress_rls_dave\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did, cid;\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n\n-- viewpoint from regress_rls_carol\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did, cid;\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n\n-- only owner can change policies\nALTER POLICY pp1 ON hyper_document USING (true);    --fail\nDROP POLICY pp1 ON hyper_document;                  --fail\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER POLICY pp1 ON hyper_document USING (dauthor = current_user);\n\n-- viewpoint from regress_rls_bob again\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did, cid;\n\n-- viewpoint from rls_regres_carol again\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM hyper_document WHERE f_leak(dtitle) ORDER BY did, cid;\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM hyper_document WHERE f_leak(dtitle);\n\n-- database superuser does bypass RLS policy when enabled\nRESET SESSION AUTHORIZATION;\nSET row_security TO ON;\nSELECT * FROM hyper_document ORDER BY did, cid;\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk ORDER BY did, cid;\n\n-- database non-superuser with bypass privilege can bypass RLS policy when disabled\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO OFF;\nSELECT * FROM hyper_document ORDER BY did, cid;\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk ORDER BY did, cid;\n\n-- RLS policy does not apply to table owner when RLS enabled.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO ON;\nSELECT * FROM hyper_document ORDER BY did, cid;\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk ORDER BY did, cid;\n\n-- When RLS disabled, other users get ERROR.\nSET SESSION AUTHORIZATION regress_rls_dave;\nSET row_security TO OFF;\nSELECT * FROM hyper_document ORDER BY did, cid;\nSELECT * FROM _timescaledb_internal._hyper_2_9_chunk ORDER BY did, cid;\n\n-- Check behavior with a policy that uses a SubPlan not an InitPlan.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO ON;\nCREATE POLICY pp3 ON hyper_document AS RESTRICTIVE\n    USING ((SELECT dlevel <= seclv FROM uaccount WHERE pguser = current_user));\n\nSET SESSION AUTHORIZATION regress_rls_carol;\nINSERT INTO hyper_document VALUES (100, 11, 5, 'regress_rls_carol', 'testing pp3'); -- fail\n\n----- Dependencies -----\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security TO ON;\n\nCREATE TABLE dependee (x integer, y integer);\nSELECT public.create_hypertable('dependee', 'x', chunk_time_interval=>2);\n\nCREATE TABLE dependent (x integer, y integer);\nSELECT public.create_hypertable('dependent', 'x', chunk_time_interval=>2);\n\nCREATE POLICY d1 ON dependent FOR ALL\n    TO PUBLIC\n    USING (x = (SELECT d.x FROM dependee d WHERE d.y = y));\n\nDROP TABLE dependee; -- Should fail without CASCADE due to dependency on row security qual?\n\nDROP TABLE dependee CASCADE;\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM dependent; -- After drop, should be unqualified\n\n-----   RECURSION    ----\n\n--\n-- Simple recursion\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE rec1 (x integer, y integer);\nSELECT public.create_hypertable('rec1', 'x', chunk_time_interval=>2);\nCREATE POLICY r1 ON rec1 USING (x = (SELECT r.x FROM rec1 r WHERE y = r.y));\nALTER TABLE rec1 ENABLE ROW LEVEL SECURITY;\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rec1; -- fail, direct recursion\n\n--\n-- Mutual recursion\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE rec2 (a integer, b integer);\nSELECT public.create_hypertable('rec2', 'x', chunk_time_interval=>2);\nALTER POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2 WHERE b = y));\nCREATE POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1 WHERE y = b));\nALTER TABLE rec2 ENABLE ROW LEVEL SECURITY;\n\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rec1;    -- fail, mutual recursion\n\n--\n-- Mutual recursion via views\n--\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE VIEW rec1v AS SELECT * FROM rec1;\nCREATE VIEW rec2v AS SELECT * FROM rec2;\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2v WHERE b = y));\nALTER POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b));\n\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rec1;    -- fail, mutual recursion via views\n\n--\n-- Mutual recursion via .s.b views\n--\nSET SESSION AUTHORIZATION regress_rls_bob;\n\n\\set VERBOSITY terse \\\\ -- suppress cascade details\nDROP VIEW rec1v, rec2v CASCADE;\n\\set VERBOSITY default\n\nCREATE VIEW rec1v WITH (security_barrier) AS SELECT * FROM rec1;\nCREATE VIEW rec2v WITH (security_barrier) AS SELECT * FROM rec2;\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2v WHERE b = y));\nCREATE POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b));\n\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rec1;    -- fail, mutual recursion via s.b. views\n\n--\n-- recursive RLS and VIEWs in policy\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE s1 (a int, b text);\nSELECT public.create_hypertable('s1', 'a', chunk_time_interval=>2);\nINSERT INTO s1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x);\n\nCREATE TABLE s2 (x int, y text);\nSELECT public.create_hypertable('s2', 'x', chunk_time_interval=>2);\nINSERT INTO s2 (SELECT x, md5(x::text) FROM generate_series(-6,6) x);\n\nGRANT SELECT ON s1, s2 TO regress_rls_bob;\n\nCREATE POLICY p1 ON s1 USING (a in (select x from s2 where y like '%2f%'));\nCREATE POLICY p2 ON s2 USING (x in (select a from s1 where b like '%22%'));\nCREATE POLICY p3 ON s1 FOR INSERT WITH CHECK (a = (SELECT a FROM s1));\n\nALTER TABLE s1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE s2 ENABLE ROW LEVEL SECURITY;\n\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE VIEW v2 AS SELECT * FROM s2 WHERE y like '%af%';\nSELECT * FROM s1 WHERE f_leak(b); -- fail (infinite recursion)\n\nINSERT INTO s1 VALUES (1, 'foo'); -- fail (infinite recursion)\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP POLICY p3 on s1;\nALTER POLICY p2 ON s2 USING (x % 2 = 0);\n\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM s1 WHERE f_leak(b);\t-- OK\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM only s1 WHERE f_leak(b);\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER POLICY p1 ON s1 USING (a in (select x from v2)); -- using VIEW in RLS policy\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM s1 WHERE f_leak(b);\t-- OK\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM s1 WHERE f_leak(b);\n\nSELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%';\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%';\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nALTER POLICY p2 ON s2 USING (x in (select a from s1 where b like '%d2%'));\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM s1 WHERE f_leak(b);\t-- fail (infinite recursion via view)\n\n-- prepared statement with regress_rls_alice privilege\nPREPARE p1(int) AS SELECT * FROM t1 WHERE a <= $1;\nEXECUTE p1(2);\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE p1(2);\n\n-- superuser is allowed to bypass RLS checks\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nSELECT * FROM t1 WHERE f_leak(b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);\n\n-- plan cache should be invalidated\nEXECUTE p1(2);\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE p1(2);\n\nPREPARE p2(int) AS SELECT * FROM t1 WHERE a = $1;\nEXECUTE p2(2);\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE p2(2);\n\n-- also, case when privilege switch from superuser\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO ON;\nEXECUTE p2(2);\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE p2(2);\n\n--\n-- UPDATE / DELETE and Row-level security\n--\nSET SESSION AUTHORIZATION regress_rls_bob;\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t1 SET b = b || b WHERE f_leak(b);\nUPDATE t1 SET b = b || b WHERE f_leak(b);\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b);\nUPDATE only t1 SET b = b || '_updt' WHERE f_leak(b);\n\n-- returning clause with system column\nUPDATE only t1 SET b = b WHERE f_leak(b) RETURNING ctid, *, t1;\nUPDATE t1 SET b = b WHERE f_leak(b) RETURNING *;\nUPDATE t1 SET b = b WHERE f_leak(b) RETURNING ctid, *, t1;\n\n-- updates with from clause\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t2 SET b=t2.b FROM t3\nWHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b);\n\nUPDATE t2 SET b=t2.b FROM t3\nWHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b);\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t1 SET b=t1.b FROM t2\nWHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);\n\nUPDATE t1 SET b=t1.b FROM t2\nWHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t2 SET b=t2.b FROM t1\nWHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);\n\nUPDATE t2 SET b=t2.b FROM t1\nWHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);\n\n-- updates with from clause self join\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2\nWHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b\nAND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2;\n\nUPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2\nWHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b\nAND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2;\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2\nWHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b\nAND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;\n\nUPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2\nWHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b\nAND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;\n\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nSELECT * FROM t1 ORDER BY a,b;\n\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO ON;\nEXPLAIN (BUFFERS OFF, COSTS OFF) DELETE FROM only t1 WHERE f_leak(b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) DELETE FROM t1 WHERE f_leak(b);\n\nDELETE FROM only t1 WHERE f_leak(b) RETURNING ctid, *, t1;\nDELETE FROM t1 WHERE f_leak(b) RETURNING ctid, *, t1;\n\n--\n-- S.b. view on top of Row-level security\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE b1 (a int, b text);\nSELECT public.create_hypertable('b1', 'a', chunk_time_interval=>2);\nINSERT INTO b1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x);\n\nCREATE POLICY p1 ON b1 USING (a % 2 = 0);\nALTER TABLE b1 ENABLE ROW LEVEL SECURITY;\nGRANT ALL ON b1 TO regress_rls_bob;\n\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE VIEW bv1 WITH (security_barrier) AS SELECT * FROM b1 WHERE a > 0 WITH CHECK OPTION;\nGRANT ALL ON bv1 TO regress_rls_carol;\n\nSET SESSION AUTHORIZATION regress_rls_carol;\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM bv1 WHERE f_leak(b);\nSELECT * FROM bv1 WHERE f_leak(b);\n\nINSERT INTO bv1 VALUES (-1, 'xxx'); -- should fail view WCO\nINSERT INTO bv1 VALUES (11, 'xxx'); -- should fail RLS check\nINSERT INTO bv1 VALUES (12, 'xxx'); -- ok\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b);\nUPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b);\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) DELETE FROM bv1 WHERE a = 6 AND f_leak(b);\nDELETE FROM bv1 WHERE a = 6 AND f_leak(b);\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nSELECT * FROM b1;\n--\n-- INSERT ... ON CONFLICT DO UPDATE and Row-level security\n--\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP POLICY p1 ON document;\nDROP POLICY p1r ON document;\n\nCREATE POLICY p1 ON document FOR SELECT USING (true);\nCREATE POLICY p2 ON document FOR INSERT WITH CHECK (dauthor = current_user);\nCREATE POLICY p3 ON document FOR UPDATE\n  USING (cid = (SELECT cid from category WHERE cname = 'novel'))\n  WITH CHECK (dauthor = current_user);\n\nSET SESSION AUTHORIZATION regress_rls_bob;\n\n-- Exists...\nSELECT * FROM document WHERE did = 2;\n\n-- ...so violates actual WITH CHECK OPTION within UPDATE (not INSERT, since\n-- alternative UPDATE path happens to be taken):\nINSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_carol', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, dauthor = EXCLUDED.dauthor;\n\n-- Violates USING qual for UPDATE policy p3.\n--\n-- UPDATE path is taken, but UPDATE fails purely because *existing* row to be\n-- updated is not a \"novel\"/cid 11 (row is not leaked, even though we have\n-- SELECT privileges sufficient to see the row in this instance):\nINSERT INTO document VALUES (33, 22, 1, 'regress_rls_bob', 'okay science fiction'); -- preparation for next statement\nINSERT INTO document VALUES (33, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'Some novel, replaces sci-fi') -- takes UPDATE path\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle;\n-- Fine (we UPDATE, since INSERT WCOs and UPDATE security barrier quals + WCOs\n-- not violated):\nINSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;\n-- Fine (we INSERT, so \"cid = 33\" (\"technology\") isn't evaluated):\nINSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *;\n-- Fine (same query, but we UPDATE, so \"cid = 33\", (\"technology\") is not the\n-- case in respect of *existing* tuple):\nINSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *;\n-- Same query a third time, but now fails due to existing tuple finally not\n-- passing quals:\nINSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *;\n-- Don't fail just because INSERT doesn't satisfy WITH CHECK option that\n-- originated as a barrier/USING() qual from the UPDATE.  Note that the UPDATE\n-- path *isn't* taken, and so UPDATE-related policy does not apply:\nINSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;\n-- But this time, the same statement fails, because the UPDATE path is taken,\n-- and updating the row just inserted falls afoul of security barrier qual\n-- (enforced as WCO) -- what we might have updated target tuple to is\n-- irrelevant, in fact.\nINSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;\n\n-- Test default USING qual enforced as WCO\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP POLICY p1 ON document;\nDROP POLICY p2 ON document;\nDROP POLICY p3 ON document;\n\nCREATE POLICY p3_with_default ON document FOR UPDATE\n  USING (cid = (SELECT cid from category WHERE cname = 'novel'));\n\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Just because WCO-style enforcement of USING quals occurs with\n-- existing/target tuple does not mean that the implementation can be allowed\n-- to fail to also enforce this qual against the final tuple appended to\n-- relation (since in the absence of an explicit WCO, this is also interpreted\n-- as an UPDATE/ALL WCO in general).\n--\n-- UPDATE path is taken here (fails due to existing tuple).  Note that this is\n-- not reported as a \"USING expression\", because it's an RLS UPDATE check that originated as\n-- a USING qual for the purposes of RLS in general, as opposed to an explicit\n-- USING qual that is ordinarily a security barrier.  We leave it up to the\n-- UPDATE to make this fail:\nINSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;\n\n-- UPDATE path is taken here.  Existing tuple passes, since it's cid\n-- corresponds to \"novel\", but default USING qual is enforced against\n-- post-UPDATE tuple too (as always when updating with a policy that lacks an\n-- explicit WCO), and so this fails:\nINSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET cid = EXCLUDED.cid, dtitle = EXCLUDED.dtitle RETURNING *;\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP POLICY p3_with_default ON document;\n\n--\n-- Test ALL policies with ON CONFLICT DO UPDATE (much the same as existing UPDATE\n-- tests)\n--\nCREATE POLICY p3_with_all ON document FOR ALL\n  USING (cid = (SELECT cid from category WHERE cname = 'novel'))\n  WITH CHECK (dauthor = current_user);\n\nSET SESSION AUTHORIZATION regress_rls_bob;\n\n-- Fails, since ALL WCO is enforced in insert path:\nINSERT INTO document VALUES (80, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_carol', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33;\n-- Fails, since ALL policy USING qual is enforced (existing, target tuple is in\n-- violation, since it has the \"manga\" cid):\nINSERT INTO document VALUES (4, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle;\n-- Fails, since ALL WCO are enforced:\nINSERT INTO document VALUES (1, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel')\n    ON CONFLICT (did) DO UPDATE SET dauthor = 'regress_rls_carol';\n\n--\n-- ROLE/GROUP\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE z1 (a int, b text);\nSELECT public.create_hypertable('z1', 'a', chunk_time_interval=>2);\nCREATE TABLE z2 (a int, b text);\nSELECT public.create_hypertable('z2', 'a', chunk_time_interval=>2);\n\nGRANT SELECT ON z1,z2 TO regress_rls_group1, regress_rls_group2,\n    regress_rls_bob, regress_rls_carol;\n\nINSERT INTO z1 VALUES\n    (1, 'aba'),\n    (2, 'bbb'),\n    (3, 'ccc'),\n    (4, 'dad');\n\nCREATE POLICY p1 ON z1 TO regress_rls_group1 USING (a % 2 = 0);\nCREATE POLICY p2 ON z1 TO regress_rls_group2 USING (a % 2 = 1);\n\nALTER TABLE z1 ENABLE ROW LEVEL SECURITY;\n\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM z1 WHERE f_leak(b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);\n\nPREPARE plancache_test AS SELECT * FROM z1 WHERE f_leak(b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test;\n\nPREPARE plancache_test2 AS WITH q AS MATERIALIZED (SELECT * FROM z1 WHERE f_leak(b)) SELECT * FROM q,z2;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test2;\n\nPREPARE plancache_test4 AS WITH q AS (SELECT * FROM z1 WHERE f_leak(b)) SELECT * FROM q,z2;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test4;\n\nPREPARE plancache_test6 AS WITH q AS NOT MATERIALIZED (SELECT * FROM z1 WHERE f_leak(b)) SELECT * FROM q,z2;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test6;\n\nPREPARE plancache_test3 AS WITH q AS MATERIALIZED (SELECT * FROM z2) SELECT * FROM q,z1 WHERE f_leak(z1.b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test3;\n\nPREPARE plancache_test5 AS WITH q AS (SELECT * FROM z2) SELECT * FROM q,z1 WHERE f_leak(z1.b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test5;\n\nPREPARE plancache_test7 AS WITH q AS NOT MATERIALIZED (SELECT * FROM z2) SELECT * FROM q,z1 WHERE f_leak(z1.b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test7;\n\nSET ROLE regress_rls_group1;\nSELECT * FROM z1 WHERE f_leak(b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test2;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test4;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test3;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test5;\n\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM z1 WHERE f_leak(b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test2;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test4;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test3;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test5;\n\nSET ROLE regress_rls_group2;\nSELECT * FROM z1 WHERE f_leak(b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test2;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test4;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test3;\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE plancache_test5;\n\n--\n-- Views should follow policy for view owner.\n--\n-- View and Table owner are the same.\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b);\nGRANT SELECT ON rls_view TO regress_rls_bob;\n\n-- Query as role that is not owner of view or table.  Should return all records.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rls_view;\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view;\n\n-- Query as view/table owner.  Should return all records.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSELECT * FROM rls_view;\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view;\nDROP VIEW rls_view;\n\n-- View and Table owners are different.\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b);\nGRANT SELECT ON rls_view TO regress_rls_alice;\n\n-- Query as role that is not owner of view but is owner of table.\n-- Should return records based on view owner policies.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSELECT * FROM rls_view;\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view;\n\n-- Query as role that is not owner of table but is owner of view.\n-- Should return records based on view owner policies.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM rls_view;\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view;\n\n-- Query as role that is not the owner of the table or view without permissions.\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM rls_view; --fail - permission denied.\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.\n\n-- Query as role that is not the owner of the table or view with permissions.\nSET SESSION AUTHORIZATION regress_rls_bob;\nGRANT SELECT ON rls_view TO regress_rls_carol;\nSELECT * FROM rls_view;\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_view;\n\nSET SESSION AUTHORIZATION regress_rls_bob;\nDROP VIEW rls_view;\n\n--\n-- Command specific\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\n\nCREATE TABLE x1 (a int, b text, c text);\nSELECT public.create_hypertable('x1', 'a', chunk_time_interval=>2);\nGRANT ALL ON x1 TO PUBLIC;\n\nINSERT INTO x1 VALUES\n    (1, 'abc', 'regress_rls_bob'),\n    (2, 'bcd', 'regress_rls_bob'),\n    (3, 'cde', 'regress_rls_carol'),\n    (4, 'def', 'regress_rls_carol'),\n    (5, 'efg', 'regress_rls_bob'),\n    (6, 'fgh', 'regress_rls_bob'),\n    (7, 'fgh', 'regress_rls_carol'),\n    (8, 'fgh', 'regress_rls_carol');\n\nCREATE POLICY p0 ON x1 FOR ALL USING (c = current_user);\nCREATE POLICY p1 ON x1 FOR SELECT USING (a % 2 = 0);\nCREATE POLICY p2 ON x1 FOR INSERT WITH CHECK (a % 2 = 1);\nCREATE POLICY p3 ON x1 FOR UPDATE USING (a % 2 = 0);\nCREATE POLICY p4 ON x1 FOR DELETE USING (a < 8);\n\nALTER TABLE x1 ENABLE ROW LEVEL SECURITY;\n\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM x1 WHERE f_leak(b) ORDER BY a ASC;\nUPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *;\n\nSET SESSION AUTHORIZATION regress_rls_carol;\nSELECT * FROM x1 WHERE f_leak(b) ORDER BY a ASC;\nUPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *;\nDELETE FROM x1 WHERE f_leak(b) RETURNING *;\n\n--\n-- Duplicate Policy Names\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE y1 (a int, b text);\nSELECT public.create_hypertable('y1', 'a', chunk_time_interval=>2);\nINSERT INTO y1 VALUES(1,2);\nCREATE TABLE y2 (a int, b text);\nSELECT public.create_hypertable('y2', 'a', chunk_time_interval=>2);\n\nGRANT ALL ON y1, y2 TO regress_rls_bob;\n\nCREATE POLICY p1 ON y1 FOR ALL USING (a % 2 = 0);\nCREATE POLICY p2 ON y1 FOR SELECT USING (a > 2);\nCREATE POLICY p1 ON y1 FOR SELECT USING (a % 2 = 1);  --fail\nCREATE POLICY p1 ON y2 FOR ALL USING (a % 2 = 0);  --OK\n\nALTER TABLE y1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE y2 ENABLE ROW LEVEL SECURITY;\n\n--\n-- Expression structure with SBV\n--\n-- Create view as table owner.  RLS should NOT be applied.\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE VIEW rls_sbv WITH (security_barrier) AS\n    SELECT * FROM y1 WHERE f_leak(b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1);\nDROP VIEW rls_sbv;\n\n-- Create view as role that does not own table.  RLS should be applied.\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE VIEW rls_sbv WITH (security_barrier) AS\n    SELECT * FROM y1 WHERE f_leak(b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1);\nDROP VIEW rls_sbv;\n\n--\n-- Expression structure\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nINSERT INTO y2 (SELECT x, md5(x::text) FROM generate_series(0,20) x);\nCREATE POLICY p2 ON y2 USING (a % 3 = 0);\nCREATE POLICY p3 ON y2 USING (a % 4 = 0);\n\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM y2 WHERE f_leak(b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM y2 WHERE f_leak(b);\n\n--\n-- Qual push-down of leaky functions, when not referring to table\n--\nSELECT * FROM y2 WHERE f_leak('abc');\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM y2 WHERE f_leak('abc');\n\nCREATE TABLE test_qual_pushdown (\n    abc text\n);\n\nINSERT INTO test_qual_pushdown VALUES ('abc'),('def');\n\nSELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc);\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc);\n\nSELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b);\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b);\n\nDROP TABLE test_qual_pushdown;\n\n--\n-- Plancache invalidate on user change.\n--\nRESET SESSION AUTHORIZATION;\n\n\\set VERBOSITY terse \\\\ -- suppress cascade details\nDROP TABLE t1 CASCADE;\n\\set VERBOSITY default\n\nCREATE TABLE t1 (a integer);\nSELECT public.create_hypertable('t1', 'a', chunk_time_interval=>2);\n\nGRANT SELECT ON t1 TO regress_rls_bob, regress_rls_carol;\n\nCREATE POLICY p1 ON t1 TO regress_rls_bob USING ((a % 2) = 0);\nCREATE POLICY p2 ON t1 TO regress_rls_carol USING ((a % 4) = 0);\n\nALTER TABLE t1 ENABLE ROW LEVEL SECURITY;\n\n-- Prepare as regress_rls_bob\nSET ROLE regress_rls_bob;\nPREPARE role_inval AS SELECT * FROM t1;\n-- Check plan\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE role_inval;\n\n-- Change to regress_rls_carol\nSET ROLE regress_rls_carol;\n-- Check plan- should be different\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE role_inval;\n\n-- Change back to regress_rls_bob\nSET ROLE regress_rls_bob;\n-- Check plan- should be back to original\nEXPLAIN (BUFFERS OFF, COSTS OFF) EXECUTE role_inval;\n\n--\n-- CTE and RLS\n--\nRESET SESSION AUTHORIZATION;\nDROP TABLE t1 CASCADE;\nCREATE TABLE t1 (a integer, b text);\nSELECT public.create_hypertable('t1', 'a', chunk_time_interval=>2);\nCREATE POLICY p1 ON t1 USING (a % 2 = 0);\n\nALTER TABLE t1 ENABLE ROW LEVEL SECURITY;\n\nGRANT ALL ON t1 TO regress_rls_bob;\n\nINSERT INTO t1 (SELECT x, md5(x::text) FROM generate_series(0,20) x);\n\nSET SESSION AUTHORIZATION regress_rls_bob;\n\nWITH cte1 AS (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1;\nEXPLAIN (BUFFERS OFF, COSTS OFF) WITH cte1 AS (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1;\n\nWITH cte1 AS (UPDATE t1 SET a = a + 1 RETURNING *) SELECT * FROM cte1; --fail\nWITH cte1 AS (UPDATE t1 SET a = a RETURNING *) SELECT * FROM cte1; --ok\n\nWITH cte1 AS (INSERT INTO t1 VALUES (21, 'Fail') RETURNING *) SELECT * FROM cte1; --fail\nWITH cte1 AS (INSERT INTO t1 VALUES (20, 'Success') RETURNING *) SELECT * FROM cte1; --ok\n\n--\n-- Rename Policy\n--\nRESET SESSION AUTHORIZATION;\nALTER POLICY p1 ON t1 RENAME TO p1; --fail\n\nSELECT polname, relname\n    FROM pg_policy pol\n    JOIN pg_class pc ON (pc.oid = pol.polrelid)\n    WHERE relname = 't1';\n\nALTER POLICY p1 ON t1 RENAME TO p2; --ok\n\nSELECT polname, relname\n    FROM pg_policy pol\n    JOIN pg_class pc ON (pc.oid = pol.polrelid)\n    WHERE relname = 't1';\n\n--\n-- Check INSERT SELECT\n--\nSET SESSION AUTHORIZATION regress_rls_bob;\nCREATE TABLE t2 (a integer, b text);\nSELECT public.create_hypertable('t2', 'a', chunk_time_interval=>2);\nINSERT INTO t2 (SELECT * FROM t1);\nEXPLAIN (BUFFERS OFF, COSTS OFF) INSERT INTO t2 (SELECT * FROM t1);\nSELECT * FROM t2;\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t2;\nCREATE TABLE t3 AS SELECT * FROM t1;\nSELECT public.create_hypertable('t2', 'a', chunk_time_interval=>2);\nSELECT * FROM t3;\nSELECT * INTO t4 FROM t1;\nSELECT * FROM t4;\n\n--\n-- RLS with JOIN\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE blog (id integer, author text, post text);\nSELECT public.create_hypertable('blog', 'id', chunk_time_interval=>2);\nCREATE TABLE comment (blog_id integer, message text);\nSELECT public.create_hypertable('comment', 'blog_id', chunk_time_interval=>2);\n\nGRANT ALL ON blog, comment TO regress_rls_bob;\n\nCREATE POLICY blog_1 ON blog USING (id % 2 = 0);\n\nALTER TABLE blog ENABLE ROW LEVEL SECURITY;\n\nINSERT INTO blog VALUES\n    (1, 'alice', 'blog #1'),\n    (2, 'bob', 'blog #1'),\n    (3, 'alice', 'blog #2'),\n    (4, 'alice', 'blog #3'),\n    (5, 'john', 'blog #1');\n\nINSERT INTO comment VALUES\n    (1, 'cool blog'),\n    (1, 'fun blog'),\n    (3, 'crazy blog'),\n    (5, 'what?'),\n    (4, 'insane!'),\n    (2, 'who did it?');\n\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Check RLS JOIN with Non-RLS.\nSELECT id, author, message FROM blog JOIN comment ON id = blog_id;\n-- Check Non-RLS JOIN with RLS.\nSELECT id, author, message FROM comment JOIN blog ON id = blog_id;\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE POLICY comment_1 ON comment USING (blog_id < 4);\n\nALTER TABLE comment ENABLE ROW LEVEL SECURITY;\n\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Check RLS JOIN RLS\nSELECT id, author, message FROM blog JOIN comment ON id = blog_id;\nSELECT id, author, message FROM comment JOIN blog ON id = blog_id;\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP TABLE blog;\nDROP TABLE comment;\n\n--\n-- Default Deny Policy\n--\nRESET SESSION AUTHORIZATION;\nDROP POLICY p2 ON t1;\nALTER TABLE t1 OWNER TO regress_rls_alice;\n\n-- Check that default deny does not apply to superuser.\nRESET SESSION AUTHORIZATION;\nSELECT * FROM t1;\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1;\n\n-- Check that default deny does not apply to table owner.\nSET SESSION AUTHORIZATION regress_rls_alice;\nSELECT * FROM t1;\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1;\n\n-- Check that default deny applies to non-owner/non-superuser when RLS on.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO ON;\nSELECT * FROM t1;\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1;\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM t1;\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM t1;\n\n--\n-- COPY TO/FROM\n--\n\nRESET SESSION AUTHORIZATION;\nDROP TABLE copy_t CASCADE;\nCREATE TABLE copy_t (a integer, b text);\nSELECT public.create_hypertable('copy_t', 'a', chunk_time_interval=>2);\nCREATE POLICY p1 ON copy_t USING (a % 2 = 0);\n\nALTER TABLE copy_t ENABLE ROW LEVEL SECURITY;\n\nGRANT ALL ON copy_t TO regress_rls_bob, regress_rls_exempt_user;\n\nINSERT INTO copy_t (SELECT x, md5(x::text) FROM generate_series(0,10) x);\n\n-- Check COPY TO as Superuser/owner.\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';\n\n-- Check COPY TO as user with permissions.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok\n\n-- Check COPY TO as user with permissions and BYPASSRLS\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok\n\n-- Check COPY TO as user without permissions. SET row_security TO OFF;\nSET SESSION AUTHORIZATION regress_rls_carol;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - permission denied\n\n-- Check COPY relation TO; keep it just one row to avoid reordering issues\nRESET SESSION AUTHORIZATION;\nSET row_security TO ON;\nCREATE TABLE copy_rel_to (a integer, b text);\nSELECT public.create_hypertable('copy_rel_to', 'a', chunk_time_interval=>2);\nCREATE POLICY p1 ON copy_rel_to USING (a % 2 = 0);\n\nALTER TABLE copy_rel_to ENABLE ROW LEVEL SECURITY;\n\nGRANT ALL ON copy_rel_to TO regress_rls_bob, regress_rls_exempt_user;\n\nINSERT INTO copy_rel_to VALUES (1, md5('1'));\n\n-- Check COPY TO as Superuser/owner.\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ',';\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ',';\n\n-- Check COPY TO as user with permissions.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --ok\n\n-- Check COPY TO as user with permissions and BYPASSRLS\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --ok\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --ok\n\n-- Check COPY TO as user without permissions. SET row_security TO OFF;\nSET SESSION AUTHORIZATION regress_rls_carol;\nSET row_security TO OFF;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --fail - permission denied\nSET row_security TO ON;\nCOPY (SELECT * FROM copy_rel_to) TO STDOUT WITH DELIMITER ','; --fail - permission denied\n\n-- Check COPY FROM as Superuser/owner.\nRESET SESSION AUTHORIZATION;\nSET row_security TO OFF;\nCOPY copy_t FROM STDIN; --ok\n1\tabc\n2\tbcd\n3\tcde\n4\tdef\n\\.\nSET row_security TO ON;\nCOPY copy_t FROM STDIN; --ok\n1\tabc\n2\tbcd\n3\tcde\n4\tdef\n\\.\n\n-- Check COPY FROM as user with permissions.\nSET SESSION AUTHORIZATION regress_rls_bob;\nSET row_security TO OFF;\nCOPY copy_t FROM STDIN; --fail - would be affected by RLS.\nSET row_security TO ON;\nCOPY copy_t FROM STDIN; --fail - COPY FROM not supported by RLS.\n\n-- Check COPY FROM as user with permissions and BYPASSRLS\nSET SESSION AUTHORIZATION regress_rls_exempt_user;\nSET row_security TO ON;\nCOPY copy_t FROM STDIN; --ok\n1\tabc\n2\tbcd\n3\tcde\n4\tdef\n\\.\n\n-- Check COPY FROM as user without permissions.\nSET SESSION AUTHORIZATION regress_rls_carol;\nSET row_security TO OFF;\nCOPY copy_t FROM STDIN; --fail - permission denied.\nSET row_security TO ON;\nCOPY copy_t FROM STDIN; --fail - permission denied.\n\nRESET SESSION AUTHORIZATION;\nDROP TABLE copy_t;\nDROP TABLE copy_rel_to CASCADE;\n\n-- Check WHERE CURRENT OF\nSET SESSION AUTHORIZATION regress_rls_alice;\n\nCREATE TABLE current_check (currentid int, payload text, rlsuser text);\nSELECT public.create_hypertable('current_check', 'currentid', chunk_time_interval=>10);\nGRANT ALL ON current_check TO PUBLIC;\n\nINSERT INTO current_check VALUES\n    (1, 'abc', 'regress_rls_bob'),\n    (2, 'bcd', 'regress_rls_bob'),\n    (3, 'cde', 'regress_rls_bob'),\n    (4, 'def', 'regress_rls_bob');\n\nCREATE POLICY p1 ON current_check FOR SELECT USING (currentid % 2 = 0);\nCREATE POLICY p2 ON current_check FOR DELETE USING (currentid = 4 AND rlsuser = current_user);\nCREATE POLICY p3 ON current_check FOR UPDATE USING (currentid = 4) WITH CHECK (rlsuser = current_user);\n\nALTER TABLE current_check ENABLE ROW LEVEL SECURITY;\n\nSET SESSION AUTHORIZATION regress_rls_bob;\n\n-- Can SELECT even rows\nSELECT * FROM current_check;\n\n-- Cannot UPDATE row 2\nUPDATE current_check SET payload = payload || '_new' WHERE currentid = 2 RETURNING *;\n\nBEGIN;\n\n-- WHERE CURRENT OF does not work with custom scan nodes\n-- so we have to disable chunk append here\nSET timescaledb.enable_chunk_append TO false;\n\nDECLARE current_check_cursor SCROLL CURSOR FOR SELECT * FROM current_check;\n-- Returns rows that can be seen according to SELECT policy, like plain SELECT\n-- above (even rows)\nFETCH ABSOLUTE 1 FROM current_check_cursor;\n-- Still cannot UPDATE row 2 through cursor\nUPDATE current_check SET payload = payload || '_new' WHERE CURRENT OF current_check_cursor RETURNING *;\n-- Can update row 4 through cursor, which is the next visible row\nFETCH RELATIVE 1 FROM current_check_cursor;\nUPDATE current_check SET payload = payload || '_new' WHERE CURRENT OF current_check_cursor RETURNING *;\nSELECT * FROM current_check;\n-- Plan should be a subquery TID scan\nEXPLAIN (BUFFERS OFF, COSTS OFF) UPDATE current_check SET payload = payload WHERE CURRENT OF current_check_cursor;\n-- Similarly can only delete row 4\nFETCH ABSOLUTE 1 FROM current_check_cursor;\nDELETE FROM current_check WHERE CURRENT OF current_check_cursor RETURNING *;\nFETCH RELATIVE 1 FROM current_check_cursor;\nDELETE FROM current_check WHERE CURRENT OF current_check_cursor RETURNING *;\nSELECT * FROM current_check;\n\nRESET timescaledb.enable_chunk_append;\n\nCOMMIT;\n\n--\n-- check pg_stats view filtering\n--\nSET row_security TO ON;\nSET SESSION AUTHORIZATION regress_rls_alice;\nANALYZE current_check;\n-- Stats visible\nSELECT row_security_active('current_check');\nSELECT attname, most_common_vals FROM pg_stats\n  WHERE tablename = 'current_check'\n  ORDER BY 1;\n\nSET SESSION AUTHORIZATION regress_rls_bob;\n-- Stats not visible\nSELECT row_security_active('current_check');\nSELECT attname, most_common_vals FROM pg_stats\n  WHERE tablename = 'current_check'\n  ORDER BY 1;\n\n--\n-- Collation support\n--\nBEGIN;\nCREATE TABLE coll_t (c) AS VALUES ('bar'::text);\nCREATE POLICY coll_p ON coll_t USING (c < ('foo'::text COLLATE \"C\"));\nALTER TABLE coll_t ENABLE ROW LEVEL SECURITY;\nGRANT SELECT ON coll_t TO regress_rls_alice;\nSELECT (string_to_array(polqual, ':'))[7] AS inputcollid FROM pg_policy WHERE polrelid = 'coll_t'::regclass;\nSET SESSION AUTHORIZATION regress_rls_alice;\nSELECT * FROM coll_t;\nROLLBACK;\n\n--\n-- Shared Object Dependencies\n--\nRESET SESSION AUTHORIZATION;\nBEGIN;\nCREATE ROLE regress_rls_eve;\nCREATE ROLE regress_rls_frank;\nCREATE TABLE tbl1 (c) AS VALUES ('bar'::text);\nGRANT SELECT ON TABLE tbl1 TO regress_rls_eve;\nCREATE POLICY P ON tbl1 TO regress_rls_eve, regress_rls_frank USING (true);\nSELECT refclassid::regclass, deptype\n  FROM pg_depend\n  WHERE classid = 'pg_policy'::regclass\n  AND refobjid = 'tbl1'::regclass;\nSELECT refclassid::regclass, deptype\n  FROM pg_shdepend\n  WHERE classid = 'pg_policy'::regclass\n  AND refobjid IN ('regress_rls_eve'::regrole, 'regress_rls_frank'::regrole);\n\nSAVEPOINT q;\nDROP ROLE regress_rls_eve; --fails due to dependency on POLICY p\nROLLBACK TO q;\n\nALTER POLICY p ON tbl1 TO regress_rls_frank USING (true);\nSAVEPOINT q;\nDROP ROLE regress_rls_eve; --fails due to dependency on GRANT SELECT\nROLLBACK TO q;\n\nREVOKE ALL ON TABLE tbl1 FROM regress_rls_eve;\nSAVEPOINT q;\nDROP ROLE regress_rls_eve; --succeeds\nROLLBACK TO q;\n\nSAVEPOINT q;\nDROP ROLE regress_rls_frank; --fails due to dependency on POLICY p\nROLLBACK TO q;\n\nDROP POLICY p ON tbl1;\nSAVEPOINT q;\nDROP ROLE regress_rls_frank; -- succeeds\nROLLBACK TO q;\n\nROLLBACK; -- cleanup\n\n--\n-- Converting table to view\n--\nBEGIN;\nCREATE TABLE t (c int);\nSELECT public.create_hypertable('t', 'c', chunk_time_interval=>2);\nCREATE POLICY p ON t USING (c % 2 = 1);\nALTER TABLE t ENABLE ROW LEVEL SECURITY;\n\nSAVEPOINT q;\nCREATE RULE \"_RETURN\" AS ON SELECT TO t DO INSTEAD\n  SELECT * FROM generate_series(1,5) t0(c); -- fails due to row level security enabled\nROLLBACK TO q;\n\nALTER TABLE t DISABLE ROW LEVEL SECURITY;\nSAVEPOINT q;\nCREATE RULE \"_RETURN\" AS ON SELECT TO t DO INSTEAD\n  SELECT * FROM generate_series(1,5) t0(c); -- fails due to policy p on t\nROLLBACK TO q;\n\nDROP POLICY p ON t;\nCREATE RULE \"_RETURN\" AS ON SELECT TO t DO INSTEAD\n  SELECT * FROM generate_series(1,5) t0(c); -- succeeds\nROLLBACK;\n\n--\n-- Policy expression handling\n--\nBEGIN;\nCREATE TABLE t (c) AS VALUES ('bar'::text);\nCREATE POLICY p ON t USING (max(c)); -- fails: aggregate functions are not allowed in policy expressions\nROLLBACK;\n\n--\n-- Non-target relations are only subject to SELECT policies\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nCREATE TABLE r1 (a int);\nSELECT public.create_hypertable('r1', 'a', chunk_time_interval=>2);\nCREATE TABLE r2 (a int);\nSELECT public.create_hypertable('r2', 'a', chunk_time_interval=>2);\nINSERT INTO r1 VALUES (10), (20);\nINSERT INTO r2 VALUES (10), (20);\n\nGRANT ALL ON r1, r2 TO regress_rls_bob;\n\nCREATE POLICY p1 ON r1 USING (true);\nALTER TABLE r1 ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY p1 ON r2 FOR SELECT USING (true);\nCREATE POLICY p2 ON r2 FOR INSERT WITH CHECK (false);\nCREATE POLICY p3 ON r2 FOR UPDATE USING (false);\nCREATE POLICY p4 ON r2 FOR DELETE USING (false);\nALTER TABLE r2 ENABLE ROW LEVEL SECURITY;\n\nSET SESSION AUTHORIZATION regress_rls_bob;\nSELECT * FROM r1;\nSELECT * FROM r2;\n\n-- r2 is read-only\nINSERT INTO r2 VALUES (2); -- Not allowed\n\\pset tuples_only 1\nUPDATE r2 SET a = 2 RETURNING *; -- Updates nothing\nDELETE FROM r2 RETURNING *; -- Deletes nothing\n\\pset tuples_only 0\n\n-- r2 can be used as a non-target relation in DML\nINSERT INTO r1 SELECT a + 1 FROM r2 RETURNING *; -- OK\nUPDATE r1 SET a = r2.a + 2 FROM r2 WHERE r1.a = r2.a RETURNING *; -- OK\nDELETE FROM r1 USING r2 WHERE r1.a = r2.a + 2 RETURNING *; -- OK\nSELECT * FROM r1;\nSELECT * FROM r2;\n\nSET SESSION AUTHORIZATION regress_rls_alice;\nDROP TABLE r1;\nDROP TABLE r2;\n\n--\n-- FORCE ROW LEVEL SECURITY applies RLS to owners too\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security = on;\nCREATE TABLE r1 (a int);\nSELECT public.create_hypertable('r1', 'a', chunk_time_interval=>2);\nINSERT INTO r1 VALUES (10), (20);\n\nCREATE POLICY p1 ON r1 USING (false);\nALTER TABLE r1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r1 FORCE ROW LEVEL SECURITY;\n\n-- No error, but no rows\nTABLE r1;\n\n-- RLS error\nINSERT INTO r1 VALUES (1);\n\n-- No error (unable to see any rows to update)\nUPDATE r1 SET a = 1;\nTABLE r1;\n\n-- No error (unable to see any rows to delete)\nDELETE FROM r1;\nTABLE r1;\n\nSET row_security = off;\n-- these all fail, would be affected by RLS\nTABLE r1;\nUPDATE r1 SET a = 1;\nDELETE FROM r1;\n\nDROP TABLE r1;\n\n--\n-- FORCE ROW LEVEL SECURITY does not break RI\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security = on;\nCREATE TABLE r1 (a int PRIMARY KEY);\n-- r1 is not a hypertable since r1.a is referenced by r2\nCREATE TABLE r2 (a int REFERENCES r1);\nSELECT public.create_hypertable('r2', 'a', chunk_time_interval=>2);\nINSERT INTO r1 VALUES (10), (20);\nINSERT INTO r2 VALUES (10), (20);\n\n-- Create policies on r2 which prevent the\n-- owner from seeing any rows, but RI should\n-- still see them.\nCREATE POLICY p1 ON r2 USING (false);\nALTER TABLE r2 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r2 FORCE ROW LEVEL SECURITY;\n\n-- Errors due to rows in r2\nDELETE FROM r1;\n\n-- Reset r2 to no-RLS\nDROP POLICY p1 ON r2;\nALTER TABLE r2 NO FORCE ROW LEVEL SECURITY;\nALTER TABLE r2 DISABLE ROW LEVEL SECURITY;\n\n-- clean out r2 for INSERT test below\nDELETE FROM r2;\n\n-- Change r1 to not allow rows to be seen\nCREATE POLICY p1 ON r1 USING (false);\nALTER TABLE r1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r1 FORCE ROW LEVEL SECURITY;\n\n-- No rows seen\nTABLE r1;\n\n-- No error, RI still sees that row exists in r1\nINSERT INTO r2 VALUES (10);\n\nDROP TABLE r2;\nDROP TABLE r1;\n\n-- Ensure cascaded DELETE works\nCREATE TABLE r1 (a int PRIMARY KEY);\n-- r1 is not a hypertable since r1.a is referenced by r2\nCREATE TABLE r2 (a int REFERENCES r1 ON DELETE CASCADE);\nSELECT public.create_hypertable('r2', 'a', chunk_time_interval=>2);\nINSERT INTO r1 VALUES (10), (20);\nINSERT INTO r2 VALUES (10), (20);\n\n-- Create policies on r2 which prevent the\n-- owner from seeing any rows, but RI should\n-- still see them.\nCREATE POLICY p1 ON r2 USING (false);\nALTER TABLE r2 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r2 FORCE ROW LEVEL SECURITY;\n\n-- Deletes all records from both\nDELETE FROM r1;\n\n-- Remove FORCE from r2\nALTER TABLE r2 NO FORCE ROW LEVEL SECURITY;\n\n-- As owner, we now bypass RLS\n-- verify no rows in r2 now\nTABLE r2;\n\nDROP TABLE r2;\nDROP TABLE r1;\n\n-- Ensure cascaded UPDATE works\nCREATE TABLE r1 (a int PRIMARY KEY);\n-- r1 is not a hypertable since r1.a is referenced by r2\nCREATE TABLE r2 (a int REFERENCES r1 ON UPDATE CASCADE);\nSELECT public.create_hypertable('r2', 'a', chunk_time_interval=>2);\nINSERT INTO r1 VALUES (10), (20);\nINSERT INTO r2 VALUES (10), (20);\n\n-- Create policies on r2 which prevent the\n-- owner from seeing any rows, but RI should\n-- still see them.\nCREATE POLICY p1 ON r2 USING (false);\nALTER TABLE r2 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r2 FORCE ROW LEVEL SECURITY;\n\n-- Updates records in both (terse output to not print CONTEXT, which can be different).\n\\set VERBOSITY terse\nUPDATE r1 SET a = a+5;\n\\set VERBOSITY default\n\n-- Remove FORCE from r2\nALTER TABLE r2 NO FORCE ROW LEVEL SECURITY;\n\n-- As owner, we now bypass RLS\n-- verify records in r2 updated\nTABLE r2;\n\nDROP TABLE r2;\nDROP TABLE r1;\n\n--\n-- Test INSERT+RETURNING applies SELECT policies as\n-- WithCheckOptions (meaning an error is thrown)\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security = on;\nCREATE TABLE r1 (a int);\nSELECT public.create_hypertable('r1', 'a', chunk_time_interval=>2);\n\nCREATE POLICY p1 ON r1 FOR SELECT USING (false);\nCREATE POLICY p2 ON r1 FOR INSERT WITH CHECK (true);\nALTER TABLE r1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r1 FORCE ROW LEVEL SECURITY;\n\n-- Works fine\nINSERT INTO r1 VALUES (10), (20);\n\n-- No error, but no rows\nTABLE r1;\n\nSET row_security = off;\n-- fail, would be affected by RLS\nTABLE r1;\n\nSET row_security = on;\n\n-- Error\nINSERT INTO r1 VALUES (10), (20) RETURNING *;\n\nDROP TABLE r1;\n\n--\n-- Test UPDATE+RETURNING applies SELECT policies as\n-- WithCheckOptions (meaning an error is thrown)\n--\nSET SESSION AUTHORIZATION regress_rls_alice;\nSET row_security = on;\nCREATE TABLE r1 (a int PRIMARY KEY);\nSELECT public.create_hypertable('r1', 'a', chunk_time_interval=>100);\n\nCREATE POLICY p1 ON r1 FOR SELECT USING (a < 20);\nCREATE POLICY p2 ON r1 FOR UPDATE USING (a < 20) WITH CHECK (true);\nCREATE POLICY p3 ON r1 FOR INSERT WITH CHECK (true);\nINSERT INTO r1 VALUES (10);\nALTER TABLE r1 ENABLE ROW LEVEL SECURITY;\nALTER TABLE r1 FORCE ROW LEVEL SECURITY;\n\n-- Works fine\nUPDATE r1 SET a = 30;\n\n-- Show updated rows\nALTER TABLE r1 NO FORCE ROW LEVEL SECURITY;\nTABLE r1;\n-- reset value in r1 for test with RETURNING\nUPDATE r1 SET a = 10;\n\n-- Verify row reset\nTABLE r1;\n\nALTER TABLE r1 FORCE ROW LEVEL SECURITY;\n\n-- Error\nUPDATE r1 SET a = 30 RETURNING *;\n\n-- UPDATE path of INSERT ... ON CONFLICT DO UPDATE should also error out\nINSERT INTO r1 VALUES (10)\n    ON CONFLICT (a) DO UPDATE SET a = 30 RETURNING *;\n\n-- Should still error out without RETURNING (use of arbiter always requires\n-- SELECT permissions)\nINSERT INTO r1 VALUES (10)\n    ON CONFLICT (a) DO UPDATE SET a = 30;\n\n-- ON CONFLICT ON CONSTRAINT\nINSERT INTO r1 VALUES (10)\n    ON CONFLICT ON CONSTRAINT r1_pkey DO UPDATE SET a = 30;\n\nDROP TABLE r1;\n\n-- Check dependency handling\nRESET SESSION AUTHORIZATION;\nCREATE TABLE dep1 (c1 int);\nSELECT public.create_hypertable('dep1', 'c1', chunk_time_interval=>2);\nCREATE TABLE dep2 (c1 int);\nSELECT public.create_hypertable('dep2', 'c1', chunk_time_interval=>2);\n\nCREATE POLICY dep_p1 ON dep1 TO regress_rls_bob USING (c1 > (select max(dep2.c1) from dep2));\nALTER POLICY dep_p1 ON dep1 TO regress_rls_bob,regress_rls_carol;\n\n-- Should return one\nSELECT count(*) = 1 FROM pg_depend\n\t\t\t\t   WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1')\n\t\t\t\t\t AND refobjid = (SELECT oid FROM pg_class WHERE relname = 'dep2');\n\nALTER POLICY dep_p1 ON dep1 USING (true);\n\n-- Should return one\nSELECT count(*) = 1 FROM pg_shdepend\n\t\t\t\t   WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1')\n\t\t\t\t\t AND refobjid = (SELECT oid FROM pg_authid WHERE rolname = 'regress_rls_bob');\n\n-- Should return one\nSELECT count(*) = 1 FROM pg_shdepend\n\t\t\t\t   WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1')\n\t\t\t\t\t AND refobjid = (SELECT oid FROM pg_authid WHERE rolname = 'regress_rls_carol');\n\n-- Should return zero\nSELECT count(*) = 0 FROM pg_depend\n\t\t\t\t   WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1')\n\t\t\t\t\t AND refobjid = (SELECT oid FROM pg_class WHERE relname = 'dep2');\n\n-- DROP OWNED BY testing\nRESET SESSION AUTHORIZATION;\n\nCREATE ROLE regress_rls_dob_role1;\nCREATE ROLE regress_rls_dob_role2;\n\nCREATE TABLE dob_t1 (c1 int);\nSELECT public.create_hypertable('dob_t1', 'c1', chunk_time_interval=>2);\nCREATE TABLE dob_t2 (c1 int) PARTITION BY RANGE (c1);\n\nCREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1 USING (true);\nDROP OWNED BY regress_rls_dob_role1;\nDROP POLICY p1 ON dob_t1; -- should fail, already gone\n\nCREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1,regress_rls_dob_role2 USING (true);\nDROP OWNED BY regress_rls_dob_role1;\nDROP POLICY p1 ON dob_t1; -- should succeed\n\nCREATE POLICY p1 ON dob_t2 TO regress_rls_dob_role1,regress_rls_dob_role2 USING (true);\nDROP OWNED BY regress_rls_dob_role1;\nDROP POLICY p1 ON dob_t2; -- should succeed\n\nDROP USER regress_rls_dob_role1;\nDROP USER regress_rls_dob_role2;\n\n--\n-- Clean up objects\n--\nRESET SESSION AUTHORIZATION;\n\n\\set VERBOSITY terse \\\\ -- suppress cascade details\nDROP SCHEMA regress_rls_schema CASCADE;\n\\set VERBOSITY default\n\nDROP USER regress_rls_alice;\nDROP USER regress_rls_bob;\nDROP USER regress_rls_carol;\nDROP USER regress_rls_dave;\nDROP USER regress_rls_exempt_user;\nDROP ROLE regress_rls_group1;\nDROP ROLE regress_rls_group2;\n\n-- Arrange to have a few policies left over, for testing\n-- pg_dump/pg_restore\nCREATE SCHEMA regress_rls_schema;\nCREATE TABLE rls_tbl (c1 int);\nSELECT public.create_hypertable('rls_tbl', 'c1', chunk_time_interval=>2);\nALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;\nCREATE POLICY p1 ON rls_tbl USING (c1 > 5);\nCREATE POLICY p2 ON rls_tbl FOR SELECT USING (c1 <= 3);\nCREATE POLICY p3 ON rls_tbl FOR UPDATE USING (c1 <= 3) WITH CHECK (c1 > 5);\nCREATE POLICY p4 ON rls_tbl FOR DELETE USING (c1 <= 3);\n\nCREATE TABLE rls_tbl_force (c1 int);\nSELECT public.create_hypertable('rls_tbl_force', 'c1', chunk_time_interval=>2);\nALTER TABLE rls_tbl_force ENABLE ROW LEVEL SECURITY;\nALTER TABLE rls_tbl_force FORCE ROW LEVEL SECURITY;\nCREATE POLICY p1 ON rls_tbl_force USING (c1 = 5) WITH CHECK (c1 < 5);\nCREATE POLICY p2 ON rls_tbl_force FOR SELECT USING (c1 = 8);\nCREATE POLICY p3 ON rls_tbl_force FOR UPDATE USING (c1 = 8) WITH CHECK (c1 >= 5);\nCREATE POLICY p4 ON rls_tbl_force FOR DELETE USING (c1 = 8);\n"
  },
  {
    "path": "test/sql/size_utils.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\ir include/insert_two_partitions.sql\n\nSELECT * FROM hypertable_detailed_size('\"public\".\"two_Partitions\"');\nSELECT * FROM hypertable_index_size('\"public\".\"two_Partitions_device_id_timeCustom_idx\"');\nSELECT * FROM hypertable_index_size('\"public\".\"two_Partitions_timeCustom_device_id_idx\"');\nSELECT * FROM hypertable_index_size('\"public\".\"two_Partitions_timeCustom_idx\"');\nSELECT * FROM hypertable_index_size('\"public\".\"two_Partitions_timeCustom_series_0_idx\"');\nSELECT * FROM hypertable_index_size('\"public\".\"two_Partitions_timeCustom_series_1_idx\"');\nSELECT * FROM hypertable_index_size('\"public\".\"two_Partitions_timeCustom_series_2_idx\"');\nSELECT * FROM hypertable_index_size('\"public\".\"two_Partitions_timeCustom_series_bool_idx\"');\nSELECT * FROM chunks_detailed_size('\"public\".\"two_Partitions\"') order by chunk_name;\n\nCREATE TABLE timestamp_partitioned(time TIMESTAMP, value TEXT);\nSELECT * FROM create_hypertable('timestamp_partitioned', 'time', 'value', 2);\n\nINSERT INTO timestamp_partitioned VALUES('2004-10-19 10:23:54', '10');\nINSERT INTO timestamp_partitioned VALUES('2004-12-19 10:23:54', '30');\nSELECT * FROM chunks_detailed_size('timestamp_partitioned') order by chunk_name;\n\nCREATE TABLE timestamp_partitioned_2(time TIMESTAMP, value CHAR(9));\nSELECT * FROM create_hypertable('timestamp_partitioned_2', 'time', 'value', 2);\n\nINSERT INTO timestamp_partitioned_2 VALUES('2004-10-19 10:23:54', '10');\nINSERT INTO timestamp_partitioned_2 VALUES('2004-12-19 10:23:54', '30');\nSELECT * FROM chunks_detailed_size('timestamp_partitioned_2') order by chunk_name;\n\nCREATE TABLE toast_test(time TIMESTAMP, value TEXT);\n-- Set storage type to EXTERNAL to prevent PostgreSQL from compressing my\n-- easily compressable string and instead store it with TOAST\nALTER TABLE toast_test ALTER COLUMN value SET STORAGE EXTERNAL;\nSELECT * FROM create_hypertable('toast_test', 'time');\n\nINSERT INTO toast_test VALUES('2004-10-19 10:23:54', $$\nthis must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k. this must be over 2k.\n$$);\nSELECT * FROM chunks_detailed_size('toast_test');\n\n--\n-- Tests for approximate_row_count()\n--\n\n-- Regular table\n--\nCREATE TABLE approx_count(time TIMESTAMP, value int);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:01', 1);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:02', 2);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:03', 3);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:04', 4);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:05', 5);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:06', 6);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:07', 7);\nSELECT * FROM approximate_row_count('approx_count');\nANALYZE approx_count;\nSELECT count(*) FROM approx_count;\nSELECT * FROM approximate_row_count('approx_count');\nDROP TABLE approx_count;\n\n-- Regular table with basic inheritance\n--\nCREATE TABLE approx_count(id int);\nINSERT INTO approx_count VALUES(1);\nSELECT count(*) FROM approx_count;\nSELECT * FROM approximate_row_count('approx_count');\nANALYZE approx_count;\nSELECT * FROM approximate_row_count('approx_count');\nCREATE TABLE approx_count_child(id2 int) INHERITS (approx_count);\nINSERT INTO approx_count_child VALUES(0);\nSELECT count(*) FROM approx_count;\nSELECT * FROM approximate_row_count('approx_count');\nANALYZE approx_count_child;\nSELECT * FROM approximate_row_count('approx_count');\nDROP TABLE approx_count CASCADE;\n\n-- Regular table with nested inheritance\n--\nCREATE TABLE approx_count(id int);\nCREATE TABLE approx_count_a(id2 int) INHERITS (approx_count);\nCREATE TABLE approx_count_b(id3 int) INHERITS (approx_count_a);\nCREATE TABLE approx_count_c(id4 int) INHERITS (approx_count_b);\n\nINSERT INTO approx_count_a VALUES(0);\nINSERT INTO approx_count_b VALUES(1);\nINSERT INTO approx_count_c VALUES(2);\nINSERT INTO approx_count VALUES(3);\n\nSELECT * FROM approximate_row_count('approx_count');\n\nANALYZE approx_count_a;\nANALYZE approx_count_b;\nANALYZE approx_count_c;\nANALYZE approx_count;\n\nSELECT count(*) FROM approx_count;\nSELECT * FROM approximate_row_count('approx_count');\nSELECT count(*) FROM approx_count_a;\nSELECT * FROM approximate_row_count('approx_count_a');\nSELECT count(*) FROM approx_count_b;\nSELECT * FROM approximate_row_count('approx_count_b');\nSELECT count(*) FROM approx_count_c;\nSELECT * FROM approximate_row_count('approx_count_c');\n\nDROP TABLE approx_count CASCADE;\n\n-- table with declarative partitioning\n--\n\nCREATE TABLE approx_count_dp(time TIMESTAMP, value int) PARTITION BY RANGE(time);\n\nCREATE TABLE approx_count_dp0 PARTITION OF approx_count_dp\nFOR VALUES FROM ('2004-01-01 00:00:00') TO ('2005-01-01 00:00:00');\nCREATE TABLE approx_count_dp1 PARTITION OF approx_count_dp\nFOR VALUES FROM ('2005-01-01 00:00:00') TO ('2006-01-01 00:00:00');\nCREATE TABLE approx_count_dp2 PARTITION OF approx_count_dp\nFOR VALUES FROM ('2006-01-01 00:00:00') TO ('2007-01-01 00:00:00');\n\nINSERT INTO approx_count_dp VALUES('2004-01-01 10:00:00', 1);\nINSERT INTO approx_count_dp VALUES('2004-01-01 11:00:00', 1);\nINSERT INTO approx_count_dp VALUES('2004-01-01 12:00:01', 1);\n\nINSERT INTO approx_count_dp VALUES('2005-01-01 10:00:00', 1);\nINSERT INTO approx_count_dp VALUES('2005-01-01 11:00:00', 1);\nINSERT INTO approx_count_dp VALUES('2005-01-01 12:00:01', 1);\n\nINSERT INTO approx_count_dp VALUES('2006-01-01 10:00:00', 1);\nINSERT INTO approx_count_dp VALUES('2006-01-01 11:00:00', 1);\nINSERT INTO approx_count_dp VALUES('2006-01-01 12:00:01', 1);\n\nSELECT count(*) FROM approx_count_dp;\nSELECT count(*) FROM approx_count_dp0;\nSELECT count(*) FROM approx_count_dp1;\nSELECT count(*) FROM approx_count_dp2;\n\nSELECT * FROM approximate_row_count('approx_count_dp');\nANALYZE approx_count_dp;\nSELECT * FROM approximate_row_count('approx_count_dp');\nSELECT * FROM approximate_row_count('approx_count_dp0');\nSELECT * FROM approximate_row_count('approx_count_dp1');\nSELECT * FROM approximate_row_count('approx_count_dp2');\n\nCREATE TABLE approx_count_dp_nested(time TIMESTAMP, device_id int, value int) PARTITION BY RANGE(time);\nCREATE TABLE approx_count_dp_nested_0 PARTITION OF approx_count_dp_nested FOR VALUES FROM ('2004-01-01 00:00:00') TO ('2005-01-01 00:00:00') PARTITION BY RANGE (device_id);\nCREATE TABLE approx_count_dp_nested_0_0 PARTITION OF approx_count_dp_nested_0 FOR VALUES FROM (0) TO (10);\nCREATE TABLE approx_count_dp_nested_0_1 PARTITION OF approx_count_dp_nested_0 FOR VALUES FROM (10) TO (20);\nCREATE TABLE approx_count_dp_nested_1 PARTITION OF approx_count_dp_nested FOR VALUES FROM ('2005-01-01 00:00:00') TO ('2006-01-01 00:00:00') PARTITION BY RANGE (device_id);\nCREATE TABLE approx_count_dp_nested_1_0 PARTITION OF approx_count_dp_nested_1 FOR VALUES FROM (0) TO (10);\nCREATE TABLE approx_count_dp_nested_1_1 PARTITION OF approx_count_dp_nested_1 FOR VALUES FROM (10) TO (20);\n\nINSERT INTO approx_count_dp_nested VALUES('2004-01-01 10:00:00', 1, 1);\nINSERT INTO approx_count_dp_nested VALUES('2004-01-01 10:00:00', 2, 1);\nINSERT INTO approx_count_dp_nested VALUES('2004-01-01 10:00:00', 3, 1);\nINSERT INTO approx_count_dp_nested VALUES('2004-01-01 10:00:00', 11, 1);\nINSERT INTO approx_count_dp_nested VALUES('2004-01-01 10:00:00', 12, 1);\nINSERT INTO approx_count_dp_nested VALUES('2004-01-01 10:00:00', 13, 1);\nINSERT INTO approx_count_dp_nested VALUES('2005-01-01 10:00:00', 1, 1);\nINSERT INTO approx_count_dp_nested VALUES('2005-01-01 10:00:00', 2, 1);\nINSERT INTO approx_count_dp_nested VALUES('2005-01-01 10:00:00', 3, 1);\nINSERT INTO approx_count_dp_nested VALUES('2005-01-01 10:00:00', 11, 1);\nINSERT INTO approx_count_dp_nested VALUES('2005-01-01 10:00:00', 12, 1);\nINSERT INTO approx_count_dp_nested VALUES('2005-01-01 10:00:00', 13, 1);\n\nSELECT * FROM approximate_row_count('approx_count_dp_nested');\nANALYZE approx_count_dp_nested;\n\nSELECT\n  (SELECT count(*) FROM approx_count_dp_nested) AS dp_nested,\n  (SELECT count(*) FROM approx_count_dp_nested_0) AS dp_nested_0,\n  (SELECT count(*) FROM approx_count_dp_nested_0_0) AS dp_nested_0_0,\n  (SELECT count(*) FROM approx_count_dp_nested_0_1) AS dp_nested_0_1,\n  (SELECT count(*) FROM approx_count_dp_nested_1) AS dp_nested_1,\n  (SELECT count(*) FROM approx_count_dp_nested_1_0) AS dp_nested_1_0,\n  (SELECT count(*) FROM approx_count_dp_nested_1_1) AS dp_nested_1_1\nUNION ALL\nSELECT\n  approximate_row_count('approx_count_dp_nested'),\n  approximate_row_count('approx_count_dp_nested_0'),\n  approximate_row_count('approx_count_dp_nested_0_0'),\n  approximate_row_count('approx_count_dp_nested_0_1'),\n  approximate_row_count('approx_count_dp_nested_1'),\n  approximate_row_count('approx_count_dp_nested_1_0'),\n  approximate_row_count('approx_count_dp_nested_1_1');\n\n-- Hypertable\n--\nCREATE TABLE approx_count(time TIMESTAMP, value int);\nSELECT * FROM create_hypertable('approx_count', 'time');\nINSERT INTO approx_count VALUES('2004-01-01 10:00:01', 1);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:02', 2);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:03', 3);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:04', 4);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:05', 5);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:06', 6);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:07', 7);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:08', 8);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:09', 9);\nINSERT INTO approx_count VALUES('2004-01-01 10:00:10', 10);\nSELECT count(*) FROM approx_count;\nSELECT * FROM approximate_row_count('approx_count');\nANALYZE approx_count;\nSELECT * FROM approximate_row_count('approx_count');\n\n\\set ON_ERROR_STOP 0\nSELECT * FROM approximate_row_count('unexisting');\nSELECT * FROM approximate_row_count();\nSELECT * FROM approximate_row_count(NULL);\n\\set ON_ERROR_STOP 1\n\n-- Test size functions with invalid or non-existing OID\nSELECT * FROM hypertable_size(0);\nSELECT * FROM hypertable_detailed_size(0) ORDER BY node_name;\nSELECT * FROM chunks_detailed_size(0) ORDER BY node_name;\nSELECT * FROM hypertable_compression_stats(0) ORDER BY node_name;\nSELECT * FROM chunk_compression_stats(0) ORDER BY node_name;\nSELECT * FROM hypertable_index_size(0);\nSELECT * FROM _timescaledb_functions.relation_size(0);\n\nSELECT * FROM hypertable_size(1);\nSELECT * FROM hypertable_detailed_size(1) ORDER BY node_name;\nSELECT * FROM chunks_detailed_size(1) ORDER BY node_name;\nSELECT * FROM hypertable_compression_stats(1) ORDER BY node_name;\nSELECT * FROM chunk_compression_stats(1) ORDER BY node_name;\nSELECT * FROM hypertable_index_size(1);\nSELECT * FROM _timescaledb_functions.relation_size(1);\n\n-- Test size functions with NULL input\nSELECT * FROM hypertable_size(NULL);\nSELECT * FROM hypertable_detailed_size(NULL) ORDER BY node_name;\nSELECT * FROM chunks_detailed_size(NULL) ORDER BY node_name;\nSELECT * FROM hypertable_compression_stats(NULL) ORDER BY node_name;\nSELECT * FROM chunk_compression_stats(NULL) ORDER BY node_name;\nSELECT * FROM hypertable_index_size(NULL);\nSELECT * FROM _timescaledb_functions.relation_size(NULL);\n\n-- Test approximate size functions with invalid input\nSELECT * FROM hypertable_approximate_size(0);\nSELECT * FROM hypertable_approximate_detailed_size(0);\nSELECT * FROM _timescaledb_functions.relation_approximate_size(0);\nSELECT * FROM hypertable_approximate_size(NULL);\nSELECT * FROM hypertable_approximate_detailed_size(NULL);\nSELECT * FROM _timescaledb_functions.relation_approximate_size(NULL);\n\n-- Test size on view, sequence and composite type\nCREATE VIEW view1 as SELECT 1;\nSELECT * FROM _timescaledb_functions.relation_approximate_size('view1');\nSELECT * FROM _timescaledb_functions.relation_size('view1');\nCREATE SEQUENCE test_id_seq\n    INCREMENT 1\n    START 1    MINVALUE 1\n    MAXVALUE 9223372036854775807\n    CACHE 1;\nSELECT * FROM _timescaledb_functions.relation_approximate_size('test_id_seq');\nSELECT * FROM _timescaledb_functions.relation_size('test_id_seq');\nCREATE TYPE test_type AS (time timestamp, temp float);\nSELECT * FROM _timescaledb_functions.relation_approximate_size('test_type');\nSELECT * FROM _timescaledb_functions.relation_size('test_type');\n\n-- Test size functions on regular table\nCREATE TABLE hypersize(time timestamptz, device int);\nCREATE INDEX hypersize_time_idx ON hypersize (time);\n\\set ON_ERROR_STOP 0\n\\set VERBOSITY default\n\\set SHOW_CONTEXT never\nSELECT pg_relation_size('hypersize'), pg_table_size('hypersize'), pg_indexes_size('hypersize'), pg_total_relation_size('hypersize'), pg_relation_size('hypersize_time_idx');\nSELECT * FROM _timescaledb_functions.relation_size('hypersize');\nSELECT * FROM hypertable_size('hypersize');\nSELECT * FROM hypertable_detailed_size('hypersize') ORDER BY node_name;\nSELECT * FROM chunks_detailed_size('hypersize') ORDER BY node_name;\nSELECT * FROM hypertable_compression_stats('hypersize') ORDER BY node_name;\nSELECT * FROM chunk_compression_stats('hypersize') ORDER BY node_name;\nSELECT * FROM hypertable_index_size('hypersize_time_idx');\nSELECT * FROM _timescaledb_functions.relation_approximate_size('hypersize');\nSELECT * FROM hypertable_approximate_size('hypersize');\nSELECT * FROM hypertable_approximate_detailed_size('hypersize');\n\\set VERBOSITY terse\n\\set ON_ERROR_STOP 1\n\n-- Test size functions on empty hypertable\nSELECT * FROM create_hypertable('hypersize', 'time');\nSELECT pg_relation_size('hypersize'), pg_table_size('hypersize'), pg_indexes_size('hypersize'), pg_total_relation_size('hypersize'), pg_relation_size('hypersize_time_idx');\nSELECT * FROM _timescaledb_functions.relation_size('hypersize');\nSELECT * FROM hypertable_size('hypersize');\nSELECT * FROM hypertable_detailed_size('hypersize') ORDER BY node_name;\nSELECT * FROM chunks_detailed_size('hypersize') ORDER BY node_name;\nSELECT * FROM hypertable_compression_stats('hypersize') ORDER BY node_name;\nSELECT * FROM chunk_compression_stats('hypersize') ORDER BY node_name;\nSELECT * FROM hypertable_index_size('hypersize_time_idx');\nSELECT * FROM _timescaledb_functions.relation_approximate_size('hypersize');\nSELECT * FROM hypertable_approximate_size('hypersize');\nSELECT * FROM hypertable_approximate_detailed_size('hypersize');\n\n-- Test size functions on non-empty hypertable\nINSERT INTO hypersize VALUES('2021-02-25', 1);\nSELECT pg_relation_size('hypersize'), pg_table_size('hypersize'), pg_indexes_size('hypersize'), pg_total_relation_size('hypersize'), pg_relation_size('hypersize_time_idx');\nSELECT pg_relation_size(ch), pg_table_size(ch), pg_indexes_size(ch), pg_total_relation_size(ch)\nFROM show_chunks('hypersize') ch\nORDER BY ch;\nSELECT * FROM show_chunks('hypersize') ch JOIN LATERAL _timescaledb_functions.relation_size(ch) ON true;\nSELECT * FROM hypertable_size('hypersize');\nSELECT * FROM hypertable_detailed_size('hypersize') ORDER BY node_name;\nSELECT * FROM chunks_detailed_size('hypersize') ORDER BY node_name;\nSELECT * FROM hypertable_compression_stats('hypersize') ORDER BY node_name;\nSELECT * FROM chunk_compression_stats('hypersize') ORDER BY node_name;\nSELECT * FROM hypertable_index_size('hypersize_time_idx');\nSELECT * FROM _timescaledb_functions.relation_approximate_size('hypersize');\nSELECT * FROM hypertable_approximate_size('hypersize');\nSELECT * FROM hypertable_approximate_detailed_size('hypersize');\n-- Test approx size functions with toast entries\nSELECT * FROM _timescaledb_functions.relation_approximate_size('toast_test');\nSELECT * FROM hypertable_approximate_size('toast_test');\nSELECT * FROM hypertable_approximate_detailed_size('toast_test');\n-- Test approx size function against a regular table\n\\set ON_ERROR_STOP 0\nCREATE TABLE regular(time TIMESTAMP, value TEXT);\nSELECT * FROM hypertable_approximate_size('regular');\n\\set ON_ERROR_STOP 1\n-- Test approx size functions with dropped chunks\nCREATE TABLE drop_chunks_table(time BIGINT NOT NULL, data INTEGER);\nSELECT hypertable_id AS drop_chunks_table_id\n    FROM create_hypertable('drop_chunks_table', 'time', chunk_time_interval => 10) \\gset\nINSERT INTO drop_chunks_table SELECT i, i FROM generate_series(0, 29) AS i;\nSELECT * FROM hypertable_approximate_size('drop_chunks_table');\nSELECT drop_chunks('drop_chunks_table', older_than => 19);\nSELECT * FROM hypertable_approximate_size('drop_chunks_table');\n\n-- github issue #4857\n-- below procedure should not crash\nSET client_min_messages = ERROR;\ndo\n$$\nDECLARE\n  o INT;\nBEGIN\n  FOR c IN 1..20 LOOP\n    ANALYZE;\n  END LOOP;\nEND;\n$$;\nRESET client_min_messages;\n"
  },
  {
    "path": "test/sql/sort_optimization.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\set PREFIX 'EXPLAIN (BUFFERS OFF, COSTS OFF) '\n\nCREATE TABLE order_test(time int NOT NULL, device_id int, value float);\nCREATE INDEX ON order_test(time,device_id);\nCREATE INDEX ON order_test(device_id,time);\n\nSELECT create_hypertable('order_test','time',chunk_time_interval:=1000);\n\nINSERT INTO order_test SELECT 0,10,0.5;\nINSERT INTO order_test SELECT 1,9,0.5;\nINSERT INTO order_test SELECT 2,8,0.5;\n\n-- we want to see here that index scans are possible for the chosen expressions\n-- so we disable seqscan so we dont need to worry about other factors which would\n-- make PostgreSQL prefer seqscan over index scan\nSET enable_seqscan TO off;\n\n-- test sort optimization with single member order by\nSELECT time_bucket(10,time),device_id,value FROM order_test ORDER BY 1;\n-- should use index scan\n:PREFIX SELECT time_bucket(10,time),device_id,value FROM order_test ORDER BY 1;\n\n-- test sort optimization with ordering by multiple columns and time_bucket not last\nSELECT time_bucket(10,time),device_id,value FROM order_test ORDER BY 1,2;\nSET enable_seqscan TO default;\n-- must not use index scan\n:PREFIX SELECT time_bucket(10,time),device_id,value FROM order_test ORDER BY 1,2;\nSET enable_seqscan TO off;\n\n-- test sort optimization with ordering by multiple columns and time_bucket as last member\nSELECT time_bucket(10,time),device_id,value FROM order_test ORDER BY 2,1;\n-- should use index scan\n:PREFIX SELECT time_bucket(10,time),device_id,value FROM order_test ORDER BY 2,1;\n\n-- test sort optimization with interval calculation with non-fixed interval\n-- #7097\nCREATE TABLE i7097_1(time timestamptz NOT NULL, quantity float, \"isText\" boolean);\nCREATE TABLE i7097_2(time timestamptz NOT NULL, quantity float, \"isText\" boolean);\n\nCREATE INDEX ON i7097_1(time) WHERE \"isText\" IS NULL;\nCREATE INDEX ON i7097_2(time) WHERE \"isText\" IS NULL;\n\nSELECT table_name FROM create_hypertable('i7097_1', 'time', create_default_indexes => false);\nSELECT table_name FROM create_hypertable('i7097_2', 'time', create_default_indexes => false);\n\nINSERT INTO i7097_1(time, quantity)\nSELECT time, round((random() * (100-3) + 3)::NUMERIC) AS quantity\nFROM generate_series('2023-01-01T00:00:00+01:00', '2023-05-01T00:00:00+01:00', interval 'PT10M') AS t(time);\n\nINSERT INTO i7097_2(time, quantity)\nSELECT time, round((random() * (100-3) + 3)::NUMERIC) AS quantity\nFROM generate_series('2023-01-01T00:00:00+01:00', '2023-05-01T00:00:00+01:00', interval 'PT10M') AS t(time);\n\nVACUUM ANALYZE i7097_1, i7097_2;\n\nSET TIME ZONE 'Europe/Paris';\nWITH\n\"cte1\" AS (SELECT time + interval 'P1Y' AS time, avg(quantity) AS quantity FROM i7097_1 WHERE time >= '2024-03-31T00:00:00+01:00'::timestamptz - interval 'P1Y' AND time < '2024-03-31T23:59:59+02:00'::timestamptz + (- interval 'P1Y') AND \"isText\" IS NULL GROUP BY 1 ORDER BY 1 ASC),\n\"cte2\" AS (SELECT time + interval 'P1Y' AS time, avg(quantity) AS quantity FROM i7097_2 WHERE time >= '2024-03-31T00:00:00+01:00'::timestamptz - interval 'P1Y' AND time < '2024-03-31T23:59:59+02:00'::timestamptz + (- interval 'P1Y') AND \"isText\" IS NULL GROUP BY 1 ORDER BY 1 ASC)\nSELECT count(*) FROM (SELECT time, cte1.quantity + cte2.quantity FROM cte1 FULL OUTER JOIN cte2 USING (time)) j;\n\n-- github issue 9214\n-- test off-by one error in sort optimization\nCREATE TABLE i9214(time timestamptz NOT NULL, machine_id INT NOT NULL, name TEXT NOT NULL, value FLOAT NOT NULL) WITH (tsdb.hypertable);\n\nINSERT INTO i9214\nVALUES\n('2026-01-30 10:00:00+00', 1, 'tag1', 10.5),\n('2026-01-30 10:00:00+00', 1, 'tag2', 20.5),\n('2026-01-30 10:01:00+00', 1, 'tag1', 11.0),\n('2026-01-30 10:01:00+00', 1, 'tag2', 21.0);\n\nWITH rule1 AS (\n  SELECT date_trunc('minute', time) AS time, machine_id FROM i9214 WHERE machine_id = 1 AND name = 'tag1' AND value > 5\n), row_numbered AS (\n  SELECT time, machine_id, row_number() OVER (ORDER BY time) AS seqnum FROM rule1\n)\nSELECT min(time) AS start_time, machine_id, count(*) AS duration_minutes\nFROM row_numbered\nGROUP BY machine_id, (time - (seqnum * interval '1 minute'))\nORDER BY min(time);\n\nWITH rule1 AS (\n\tSELECT date_trunc('minute', time) AS time, machine_id FROM i9214 WHERE machine_id = 1 AND name = 'tag1' AND value > 5\n), rule2 AS (\n  SELECT date_trunc('minute', time) AS time, machine_id FROM i9214 WHERE machine_id = 1 AND name = 'tag2' AND value > 5\n), joined_rules AS (\n  SELECT r1.time, r1.machine_id FROM rule1 r1 INNER JOIN rule2 r2 USING(time,machine_id)\n), row_numbered AS (\n  SELECT time, machine_id, row_number() OVER (ORDER BY time) AS seqnum FROM joined_rules\n)\nSELECT min(time) AS start_time, machine_id, count(*) AS duration_minutes\nFROM row_numbered\nGROUP BY machine_id, (time - (seqnum * interval '1 minute'))\nORDER BY min(time);\n\n"
  },
  {
    "path": "test/sql/sql_query.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\o /dev/null\n\\ir include/insert_two_partitions.sql\n\\o\n\nSELECT * FROM PUBLIC.\"two_Partitions\";\n\nEXPLAIN (verbose ON, buffers off, costs off) SELECT * FROM PUBLIC.\"two_Partitions\";\n\n\\echo \"The following queries should NOT scan two_Partitions._hyper_1_1_chunk\"\nEXPLAIN (verbose ON, buffers off, costs off) SELECT * FROM PUBLIC.\"two_Partitions\" WHERE device_id = 'dev2';\nEXPLAIN (verbose ON, buffers off, costs off) SELECT * FROM PUBLIC.\"two_Partitions\" WHERE device_id = 'dev'||'2';\nEXPLAIN (verbose ON, buffers off, costs off) SELECT * FROM PUBLIC.\"two_Partitions\" WHERE 'dev'||'2' = device_id;\n\n--test integer partition key\nCREATE TABLE \"int_part\"(time timestamp, object_id int, temp float);\nSELECT create_hypertable('\"int_part\"', 'time', 'object_id', 2);\nINSERT INTO \"int_part\" VALUES('2017-01-20T09:00:01', 1, 22.5);\nINSERT INTO \"int_part\" VALUES('2017-01-20T09:00:01', 2, 22.5);\n\n--check that there are two chunks\nSELECT * FROM test.show_subtables('int_part');\n\nSELECT * FROM \"int_part\" WHERE object_id = 1;\n\n--check that queries with IN/ANY/= work for the \"time\" column\nSELECT * FROM \"int_part\" WHERE time IN (NULL);\nSELECT * FROM \"int_part\" WHERE time = ANY (NULL);\nSELECT * FROM \"int_part\" WHERE time = NULL;\n\n--make sure this touches only one partititon\nEXPLAIN (verbose ON, buffers off, costs off) SELECT * FROM \"int_part\" WHERE object_id = 1;\n\n--Need to verify space partitions are currently pruned in this query\n--EXPLAIN (verbose ON, buffers off, costs off) SELECT * FROM \"two_Partitions\" WHERE device_id IN ('dev2', 'dev21');\n\n\\echo \"The following shows non-aggregated queries with time desc using merge append\"\nEXPLAIN (verbose ON, buffers off, costs off)SELECT * FROM PUBLIC.\"two_Partitions\" ORDER BY \"timeCustom\" DESC NULLS LAST limit 2;\n\n--shows that more specific indexes are used if the WHERE clauses \"match\", uses the series_1 index here.\nEXPLAIN (verbose ON, buffers off, costs off)SELECT * FROM PUBLIC.\"two_Partitions\" WHERE series_1 IS NOT NULL ORDER BY \"timeCustom\" DESC NULLS LAST limit 2;\n--here the \"match\" is implication series_1 > 1 => series_1 IS NOT NULL\nEXPLAIN (verbose ON, buffers off, costs off)SELECT * FROM PUBLIC.\"two_Partitions\" WHERE series_1 > 1 ORDER BY \"timeCustom\" DESC NULLS LAST limit 2;\n\n--note that without time transform things work too\nEXPLAIN (verbose ON, buffers off, costs off)SELECT \"timeCustom\" t, min(series_0) FROM PUBLIC.\"two_Partitions\" GROUP BY t ORDER BY t DESC NULLS LAST limit 2;\n\n--The query should still use the index on timeCustom, even though the GROUP BY/ORDER BY is on the transformed time 't'.\n--However, current query plans show that it does not.\nEXPLAIN (verbose ON, buffers off, costs off)SELECT \"timeCustom\"/10 t, min(series_0) FROM PUBLIC.\"two_Partitions\" GROUP BY t ORDER BY t DESC NULLS LAST limit 2;\nEXPLAIN (verbose ON, buffers off, costs off)SELECT \"timeCustom\"%10 t, min(series_0) FROM PUBLIC.\"two_Partitions\" GROUP BY t ORDER BY t DESC NULLS LAST limit 2;\n"
  },
  {
    "path": "test/sql/symbol_conflict.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\n-- Test for symbol conflicts between the loader module and the\n-- versioned extension module.\n-- This test fails on, e.g. Linux, unless compiled with -fvisibility=hidden\nCREATE OR REPLACE FUNCTION hello_loader() RETURNS TEXT\nAS 'timescaledb', 'loader_hello' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;\n\nSELECT hello_loader();\n\nCREATE OR REPLACE FUNCTION hello_timescaledb() RETURNS TEXT\nAS :MODULE_PATHNAME, 'timescaledb_hello' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;\n\n-- This calls an internal function with a conflicting name in the loader\nSELECT hello_loader();\n-- This calls the identically named internal function in the versioned extension\nSELECT hello_timescaledb();\n"
  },
  {
    "path": "test/sql/tableam.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Test support for setting table access method on hypertables\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- create a new access method that reuses the heap handler\nCREATE ACCESS METHOD testam TYPE TABLE HANDLER heap_tableam_handler;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n\nCREATE TABLE testam (time timestamptz, device int, temp float) USING testam;\nSELECT create_hypertable('testam', 'time', 'device', 2);\n\n-- show that the hypertable is using the 'testam' table access method\nSELECT amname AS hypertable_amname\nFROM pg_class cl, pg_am am\nWHERE cl.oid = 'testam'::regclass\nAND cl.relam = am.oid;\n\n-- insert data to create a chunk\nINSERT INTO testam VALUES('2020-01-22:11:30', 1, 29.3);\n\n-- make sure the table access method for a chunk is the same as the\n-- hypertable root\nSELECT amname AS chunk_amname\nFROM pg_class cl, pg_am am, show_chunks('testam') ch\nWHERE cl.oid = ch\nAND cl.relam = am.oid;\n"
  },
  {
    "path": "test/sql/tableam_alter.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Test support for setting table access method on hypertables using\n-- ALTER TABLE. It should propagate to the chunks.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE ACCESS METHOD testam TYPE TABLE HANDLER heap_tableam_handler;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n\nCREATE VIEW chunk_info AS\nSELECT hypertable_name AS hypertable,\n       chunk_name AS chunk,\n       amname\n  FROM timescaledb_information.chunks ch\n  JOIN pg_class cl ON (format('%I.%I', ch.chunk_schema, ch.chunk_name)::regclass = cl.oid)\n  JOIN pg_am am ON (am.oid = cl.relam);\n\nCREATE TABLE test_table (time timestamptz not null, device int, temp float);\n\nSELECT create_hypertable('test_table', by_range('time'));\n\nINSERT INTO test_table\nSELECT ts, 10 * random(), 100 * random()\nFROM generate_series('2001-01-01'::timestamp, '2001-02-01', '1d'::interval) as x(ts);\n\nSELECT cl.relname, amname\n  FROM pg_class cl JOIN pg_am am ON cl.relam = am.oid\n WHERE cl.relname = 'test_table';\nSELECT * FROM chunk_info WHERE hypertable = 'test_table';\n\n-- Test setting the access method together with other options. This\n-- should not generate an error.\nALTER TABLE test_table\n      SET ACCESS METHOD testam,\n      SET (autovacuum_vacuum_threshold = 100);\n\n-- Create more chunks. These will use the new access method, but the\n-- old chunks will use the old access method.\nINSERT INTO test_table\nSELECT ts, 10 * random(), 100 * random()\nFROM generate_series('2001-02-01'::timestamp, '2001-03-01', '1d'::interval) as x(ts);\n\nSELECT cl.relname, amname\n  FROM pg_class cl JOIN pg_am am ON cl.relam = am.oid\n WHERE cl.relname = 'test_table';\nSELECT * FROM chunk_info WHERE hypertable = 'test_table';\n"
  },
  {
    "path": "test/sql/tableam_alter_defaults.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Test support for setting table access method on hypertables using\n-- ALTER TABLE for version 17 and later. This is in addition to the\n-- tests in tableam_alter.sql.\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE ACCESS METHOD testam TYPE TABLE HANDLER heap_tableam_handler;\n\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n\nCREATE VIEW chunk_info AS\nSELECT hypertable_name AS hypertable,\n       chunk_name AS chunk,\n       amname\n  FROM timescaledb_information.chunks ch\n  JOIN pg_class cl ON (format('%I.%I', ch.chunk_schema, ch.chunk_name)::regclass = cl.oid)\n  JOIN pg_am am ON (am.oid = cl.relam);\n\nCREATE TABLE test_table (time timestamptz not null, device int, temp float);\n\nSELECT cl.relname, amname\n  FROM pg_class cl JOIN pg_am am ON cl.relam = am.oid\n WHERE cl.relname = 'test_table';\n\n-- Check that setting default access method of a normal table works.\nALTER TABLE test_table SET ACCESS METHOD DEFAULT;\n\n-- Check that changing the access method and then changing it back\n-- works.\nALTER TABLE test_table SET ACCESS METHOD testam;\nALTER TABLE test_table SET ACCESS METHOD DEFAULT;\n\n-- Check that setting default access method of a hypertable works.\nSELECT create_hypertable('test_table', by_range('time'));\nALTER TABLE test_table SET ACCESS METHOD DEFAULT;\n\nSELECT cl.relname, amname\n  FROM pg_class cl JOIN pg_am am ON cl.relam = am.oid\n WHERE cl.relname = 'test_table';\n\n-- Test setting the access method together with other options. This\n-- should not generate an error.\nALTER TABLE test_table\n      SET ACCESS METHOD testam,\n      SET (autovacuum_vacuum_threshold = 100);\n\n-- Add some rows to generate a chunk. This should get the access\n-- method of the hypertable.\nINSERT INTO test_table\nSELECT ts, 10 * random(), 100 * random()\nFROM generate_series('2001-01-01'::timestamp, '2001-01-14', '1d'::interval) as x(ts);\n\nSELECT * FROM chunk_info WHERE hypertable = 'test_table';\n\n-- Setting it to the default method after we have set it to a test\n-- access method should work fine also on a hypertable.\nSELECT cl.relname, amname\n  FROM pg_class cl JOIN pg_am am ON cl.relam = am.oid\n WHERE cl.relname = 'test_table';\n\nALTER TABLE test_table SET ACCESS METHOD DEFAULT;\n\nSELECT cl.relname, amname\n  FROM pg_class cl JOIN pg_am am ON cl.relam = am.oid\n WHERE cl.relname = 'test_table';\n\nSELECT chunk FROM show_chunks('test_table') t(chunk) limit 1 \\gset\n\nALTER TABLE :chunk SET ACCESS METHOD DEFAULT;\n\nSELECT * FROM chunk_info WHERE hypertable = 'test_table';\n"
  },
  {
    "path": "test/sql/tablespace.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\set ON_ERROR_STOP 0\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE VIEW hypertable_tablespaces AS\nSELECT cls.relname AS hypertable,\n       (SELECT spcname FROM pg_tablespace WHERE oid = reltablespace) AS tablespace\n  FROM _timescaledb_catalog.hypertable,\n  LATERAL (SELECT * FROM pg_class WHERE oid = format('%I.%I', schema_name, table_name)::regclass) AS cls\n  ORDER BY hypertable, tablespace;\nGRANT SELECT ON hypertable_tablespaces TO PUBLIC;\n\n--Test hypertable with tablespace. Tablespaces are cluster-wide, so we\n--attach the test name as prefix to allow tests to be executed in\n--parallel.\nCREATE TABLESPACE tablespace1 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE1_PATH;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n\n--assigning a tablespace via the main table should work\nCREATE TABLE tspace_2dim(time timestamp, temp float, device text) TABLESPACE tablespace1;\nSELECT create_hypertable('tspace_2dim', 'time', 'device', 2);\n\nINSERT INTO tspace_2dim VALUES ('2017-01-20T09:00:01', 24.3, 'blue');\n\n-- Tablespace for tspace_2dim should be set\nSELECT * FROM hypertable_tablespaces WHERE hypertable = 'tspace_2dim';\nSELECT show_tablespaces('tspace_2dim');\n\n--verify that the table chunk has the correct tablespace\nSELECT relname, spcname FROM pg_class c\nINNER JOIN pg_tablespace t ON (c.reltablespace = t.oid)\nINNER JOIN _timescaledb_catalog.chunk ch ON (ch.table_name = c.relname);\n\n--check some error conditions\nSELECT attach_tablespace(NULL,NULL);\nSELECT attach_tablespace('tablespace2', NULL);\nSELECT attach_tablespace(NULL, 'tspace_2dim');\nSELECT attach_tablespace('none_existing_tablespace', 'tspace_2dim');\nSELECT attach_tablespace('tablespace2', 'none_existing_table');\nSELECT detach_tablespace(NULL);\nSELECT detach_tablespaces(NULL);\nSELECT show_tablespaces(NULL);\n\n--attach another tablespace without first creating it --> should generate error\nSELECT attach_tablespace('tablespace2', 'tspace_2dim');\n--attach the same tablespace twice to same table should also generate error\nSELECT attach_tablespace('tablespace1', 'tspace_2dim');\n\n--no error if if_not_attached is given\nSELECT attach_tablespace('tablespace1', 'tspace_2dim', if_not_attached => true);\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n--Tablespaces are cluster-wide, so we attach the test name as prefix\n--to allow tests to be executed in parallel.\nCREATE TABLESPACE tablespace2 OWNER :ROLE_DEFAULT_PERM_USER_2 LOCATION :TEST_TABLESPACE2_PATH;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n\n--attach without permissions on the table should fail\nSELECT attach_tablespace('tablespace2', 'tspace_2dim');\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n\n--attach without permissions on the tablespace should also fail\nSELECT attach_tablespace('tablespace2', 'tspace_2dim');\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nGRANT :ROLE_DEFAULT_PERM_USER_2 TO :ROLE_DEFAULT_PERM_USER;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n\n--should work with permissions on both the table and the tablespace\nSELECT attach_tablespace('tablespace2', 'tspace_2dim');\n\nSELECT * FROM _timescaledb_catalog.tablespace;\nSELECT * FROM show_tablespaces('tspace_2dim');\n\n--insert into another chunk\nINSERT INTO tspace_2dim VALUES ('2017-01-20T09:00:01', 24.3, 'brown');\n\nSELECT * FROM test.show_subtables('tspace_2dim');\n\n--indexes should inherit the tablespace of their chunk\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n\n\\x\nSELECT * FROM timescaledb_information.hypertables\nORDER BY hypertable_schema, hypertable_name;\nSELECT hypertable_schema,\n       hypertable_name,\n       chunk_schema,\n       chunk_name,\n       chunk_tablespace\nFROM timescaledb_information.chunks\nORDER BY chunk_name;\n\\x\n--\nSET ROLE :ROLE_DEFAULT_PERM_USER_2;\nCREATE TABLE tspace_1dim(time timestamp, temp float, device text);\nSELECT create_hypertable('tspace_1dim', 'time');\n--user doesn't have permission on tablespace1 --> error\nSELECT attach_tablespace('tablespace1', 'tspace_1dim');\n\n--grant permission to tablespace1\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nGRANT CREATE ON TABLESPACE tablespace1 TO :ROLE_DEFAULT_PERM_USER_2;\nSET ROLE :ROLE_DEFAULT_PERM_USER_2;\n--should work fine now. Test SELECT INTO utility statements to ensure\n--internal alter table function call works with event triggers.\nSELECT true INTO attached FROM attach_tablespace('tablespace1', 'tspace_1dim');\nSELECT attach_tablespace('tablespace2', 'tspace_1dim');\n-- Tablespace for tspace_1dim should be set and attached\nSELECT * FROM hypertable_tablespaces WHERE hypertable = 'tspace_1dim';\nSELECT show_tablespaces('tspace_1dim');\n\n--trying to revoke permissions while attached should fail\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nREVOKE CREATE ON TABLESPACE tablespace1 FROM :ROLE_DEFAULT_PERM_USER_2;\nREVOKE ALL ON TABLESPACE tablespace1 FROM :ROLE_DEFAULT_PERM_USER_2;\nSET ROLE :ROLE_DEFAULT_PERM_USER_2;\n\nSELECT * FROM _timescaledb_catalog.tablespace;\n\nINSERT INTO tspace_1dim VALUES ('2017-01-20T09:00:01', 24.3, 'blue');\nINSERT INTO tspace_1dim VALUES ('2017-03-20T09:00:01', 24.3, 'brown');\n\nSELECT * FROM test.show_subtablesp('tspace_%');\n--indexes should inherit the tablespace of their chunk, unless the\n--parent index has a tablespace set, in which case the chunks'\n--corresponding indexes are pinned to the parent index's\n--tablespace. The parent index can have a tablespace set in two cases:\n--(1) if explicitly set in CREATE INDEX, or (2) if the main table was\n--created with a tablespace, because then default indexes will be\n--created in that tablespace too.\nSELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');\n\n--detach tablespace1 from tspace_2dim should fail due to lack of permissions\nSELECT detach_tablespace('tablespace1', 'tspace_2dim');\n--detach tablespace1 from all tables. Should only detach from\n--'tspace_1dim' (1 tablespace) due to lack of permissions\nSELECT * FROM hypertable_tablespaces;\nSELECT * INTO detached FROM detach_tablespace('tablespace1');\nSELECT * FROM detached;\nSELECT * FROM _timescaledb_catalog.tablespace;\nSELECT * FROM show_tablespaces('tspace_1dim');\nSELECT * FROM show_tablespaces('tspace_2dim');\nSELECT * FROM hypertable_tablespaces;\n\n--it should now be possible to revoke permissions on tablespace1\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nREVOKE CREATE ON TABLESPACE tablespace1 FROM :ROLE_DEFAULT_PERM_USER_2;\nSET ROLE :ROLE_DEFAULT_PERM_USER_2;\n\n--detach the other tablespace\nSELECT detach_tablespace('tablespace2', 'tspace_1dim');\nSELECT * FROM _timescaledb_catalog.tablespace;\nSELECT * FROM show_tablespaces('tspace_1dim');\nSELECT * FROM show_tablespaces('tspace_2dim');\nSELECT * FROM hypertable_tablespaces;\n\n--detaching tablespace2 from a table without permissions should fail\nSELECT detach_tablespace('tablespace2', 'tspace_2dim');\nSELECT detach_tablespaces('tspace_2dim');\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- PERM_USER_2 owns tablespace2, and PERM_USER owns the table\n-- 'tspace_2dim', which has tablespace2 attached. Revoking PERM_USER_2\n-- FROM PERM_USER should therefore fail\nREVOKE :ROLE_DEFAULT_PERM_USER_2 FROM :ROLE_DEFAULT_PERM_USER;\nSET ROLE :ROLE_DEFAULT_PERM_USER_2;\n\n--set other user should make detach work\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT * INTO detached_all FROM detach_tablespaces('tspace_2dim');\nSELECT * FROM detached_all;\nSELECT * FROM _timescaledb_catalog.tablespace;\nSELECT * FROM show_tablespaces('tspace_1dim');\nSELECT * FROM show_tablespaces('tspace_2dim');\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- It should now be possible to revoke PERM_USER_2 from PERM_USER\n-- since tablespace2 is no longer attched to tspace_2dim\nREVOKE :ROLE_DEFAULT_PERM_USER_2 FROM :ROLE_DEFAULT_PERM_USER;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n\n--detaching twice should fail\nSELECT detach_tablespace('tablespace2', 'tspace_2dim');\n--adding if_attached should only generate notice\nSELECT detach_tablespace('tablespace2', 'tspace_2dim', if_attached => true);\n\n--attach tablespaces again to verify that tablespaces are cleaned up\n--when tables are dropped\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT attach_tablespace('tablespace2', 'tspace_1dim');\nSELECT attach_tablespace('tablespace1', 'tspace_2dim');\n\nSELECT * FROM _timescaledb_catalog.tablespace;\nDROP TABLE tspace_1dim;\nSELECT * FROM _timescaledb_catalog.tablespace;\nDROP TABLE tspace_2dim;\nSELECT * FROM _timescaledb_catalog.tablespace;\n\n-- Create two tables and attach multiple tablespaces to them. Verify\n-- that dropping a tablespace from multiple tables work as expected.\nCREATE TABLE tbl_1(time timestamp, temp float, device text);\nSELECT create_hypertable('tbl_1', 'time');\nCREATE TABLE tbl_2(time timestamp, temp float, device text);\nSELECT create_hypertable('tbl_2', 'time');\nCREATE TABLE tbl_3(time timestamp, temp float, device text);\nSELECT create_hypertable('tbl_3', 'time');\nSELECT * FROM hypertable_tablespaces;\nSELECT * FROM show_tablespaces('tbl_1');\nSELECT * FROM show_tablespaces('tbl_2');\nSELECT * FROM show_tablespaces('tbl_3');\n\nSELECT attach_tablespace('tablespace1', 'tbl_1');\nSELECT attach_tablespace('tablespace2', 'tbl_1');\nSELECT attach_tablespace('tablespace2', 'tbl_2');\nSELECT attach_tablespace('tablespace2', 'tbl_3');\nSELECT * FROM hypertable_tablespaces;\nSELECT * FROM show_tablespaces('tbl_1');\nSELECT * FROM show_tablespaces('tbl_2');\nSELECT * FROM show_tablespaces('tbl_3');\n\nSELECT detach_tablespace('tablespace2');\nSELECT * FROM hypertable_tablespaces;\nSELECT * FROM show_tablespaces('tbl_1');\nSELECT * FROM show_tablespaces('tbl_2');\nSELECT * FROM show_tablespaces('tbl_3');\n\nDROP TABLE tbl_1;\nDROP TABLE tbl_2;\nDROP TABLE tbl_3;\n\n-- verify that one cannot DROP a tablespace while it is attached to a\n-- hypertable\nCREATE TABLE tbl_1(time timestamp, temp float, device text);\n\nSELECT create_hypertable('tbl_1', 'time');\nSELECT attach_tablespace('tablespace1', 'tbl_1');\nSELECT * FROM show_tablespaces('tbl_1');\n\nDROP TABLESPACE tablespace1;\n--after detaching we should now be able to drop the tablespace\nSELECT detach_tablespace('tablespace1', 'tbl_1');\nDROP TABLESPACE tablespace1;\nDROP TABLESPACE tablespace2;\n"
  },
  {
    "path": "test/sql/telemetry.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_status(int) RETURNS JSONB\n    AS :MODULE_PATHNAME, 'ts_test_status' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_status_ssl(int) RETURNS JSONB\n    AS :MODULE_PATHNAME, 'ts_test_status_ssl' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_status_mock(text) RETURNS JSONB\n    AS :MODULE_PATHNAME, 'ts_test_status_mock' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_validate_server_version(response text)\n    RETURNS TEXT\n    AS :MODULE_PATHNAME, 'ts_test_validate_server_version' LANGUAGE C IMMUTABLE PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_telemetry_main_conn(text, text)\nRETURNS BOOLEAN AS :MODULE_PATHNAME, 'ts_test_telemetry_main_conn' LANGUAGE C IMMUTABLE PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_telemetry(host text = NULL, servname text = NULL, port int = NULL) RETURNS JSONB AS :MODULE_PATHNAME, 'ts_test_telemetry' LANGUAGE C IMMUTABLE PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION _timescaledb_internal.test_privacy() RETURNS BOOLEAN\n    AS :MODULE_PATHNAME, 'ts_test_privacy' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION test_check_version_response(response text)\n       RETURNS VOID\n       AS :MODULE_PATHNAME, 'ts_test_check_version_response'\n       LANGUAGE C\n       IMMUTABLE STRICT PARALLEL SAFE;\n\nINSERT INTO _timescaledb_catalog.metadata VALUES ('foo','bar',TRUE);\nINSERT INTO _timescaledb_catalog.metadata VALUES ('bar','baz',FALSE);\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT _timescaledb_internal.test_status_ssl(200);\nSELECT _timescaledb_internal.test_status_ssl(201);\n\\set ON_ERROR_STOP 0\nSELECT _timescaledb_internal.test_status_ssl(304);\nSELECT _timescaledb_internal.test_status_ssl(400);\nSELECT _timescaledb_internal.test_status_ssl(401);\nSELECT _timescaledb_internal.test_status_ssl(404);\nSELECT _timescaledb_internal.test_status_ssl(500);\nSELECT _timescaledb_internal.test_status_ssl(503);\n\n\\set ON_ERROR_STOP 1\n\n-- This function runs the test 5 times, because each time the internal C function is choosing a random length to send from the server on each socket read. We hit many cases this way.\nCREATE OR REPLACE FUNCTION mocker(TEXT) RETURNS SETOF TEXT AS\n$BODY$\nDECLARE\nr TEXT;\nBEGIN\nFOR i in 1..5 LOOP\nSELECT _timescaledb_internal.test_status_mock($1) INTO r;\nRETURN NEXT r;\nEND LOOP;\nRETURN;\nEND\n$BODY$\nLANGUAGE 'plpgsql';\n\nselect * from mocker(\n        E'HTTP/1.1 200 OK\\r\\n'\n        'Content-Type: application/json; charset=utf-8\\r\\n'\n        'Date: Thu, 12 Jul 2018 18:33:04 GMT\\r\\n'\n        'ETag: W/\\\"e-upYEWCL+q6R/++2nWHz5b76hBgo\\\"\\r\\n'\n        'Server: nginx\\r\\n'\n        'Vary: Accept-Encoding\\r\\n'\n        'Content-Length: 14\\r\\n'\n        'Connection: Close\\r\\n\\r\\n'\n        '{\\\"status\\\":200}');\nselect * from mocker(\n        E'HTTP/1.1 201 OK\\r\\n'\n        'Content-Type: application/json; charset=utf-8\\r\\n'\n        'Vary: Accept-Encoding\\r\\n'\n        'Content-Length: 14\\r\\n'\n        'Connection: Close\\r\\n\\r\\n'\n        '{\\\"status\\\":201}');\n\n\\set ON_ERROR_STOP 0\n\\set test_string 'HTTP/1.1 404 Not Found\\r\\nContent-Length: 14\\r\\nConnection: Close\\r\\n\\r\\n{\\\"status\\\":404}';\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\n\\set test_string 'Content-Length: 14\\r\\nConnection: Close\\r\\n\\r\\n{\\\"status\\\":404}';\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\n\\set test_string 'HTTP/1.1 404 Not Found\\r\\nContent-Length: 14\\r\\nConnection: Close\\r\\n{\\\"status\\\":404}';\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\nSELECT _timescaledb_internal.test_status_mock(:'test_string');\n\\set ON_ERROR_STOP 1\n\n-- Test parsing version response\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"status\": \"200\", \"current_timescaledb_version\": \"10.1.0\"}');\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"10.1\"}');\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"10\"}');\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"9.2.0\"}');\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"9.1.2\"}');\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"1.0.0\"}');\n\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"1.0.0-rc1\"}');\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"1.0.0-rc2\"}');\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"1.0.0-rc1\"}');\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"1.0.0-alpha\"}');\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"123456789\"}');\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"!@#$%\"}');\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"\"}');\n\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \" 10 \"}');\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"a\"}');\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"a.b.c\"}');\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"10.1.1a\"}');\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"10.1.1+rc1\"}');\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"10.1.1.1\"}');\nSELECT * FROM _timescaledb_internal.test_validate_server_version('{\"current_timescaledb_version\": \"1.0.0-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz\"}');\n\n\n----------------------------------------------------------------\n-- Test well-formed response and valid versions\nSELECT test_check_version_response('{\"current_timescaledb_version\": \"1.6.1\", \"is_up_to_date\": true}');\nSELECT test_check_version_response('{\"current_timescaledb_version\": \"1.6.1\", \"is_up_to_date\": false}');\nSELECT test_check_version_response('{\"current_timescaledb_version\": \"10.1\", \"is_up_to_date\": false}');\nSELECT test_check_version_response('{\"current_timescaledb_version\": \"10.1.1-rc1\", \"is_up_to_date\": false}');\n\n----------------------------------------------------------------\n-- Test well-formed response but invalid versions\nSELECT test_check_version_response('{\"current_timescaledb_version\": \"1.0.0-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz\", \"is_up_to_date\": false}');\nSELECT test_check_version_response('{\"current_timescaledb_version\": \"10.1.1+rc1\", \"is_up_to_date\": false}');\nSELECT test_check_version_response('{\"current_timescaledb_version\": \"@10.1.1\", \"is_up_to_date\": false}');\nSELECT test_check_version_response('{\"current_timescaledb_version\": \"10.1.1@\", \"is_up_to_date\": false}');\n\n----------------------------------------------------------------\n-- Test malformed responses\n\n\\set ON_ERROR_STOP 0\n-- Empty response\nSELECT test_check_version_response('{}');\n\n-- Field \"is_up_to_date\" missing\nSELECT test_check_version_response('{\"current_timescaledb_version\": \"1.6.1\"}');\n\n-- Field \"current_timescaledb_version\" is missing\nSELECT test_check_version_response('{\"is_up_to_date\": false}');\n\\set ON_ERROR_STOP 1\n\nSET timescaledb.last_tune_time = '2024-01-01 00:00:00+00';\nSET timescaledb.last_tune_version = '1.2.3';\n\nSET timescaledb.telemetry_level=basic;\n-- Connect to a bogus host and path to test error handling in telemetry_main()\nSELECT _timescaledb_internal.test_telemetry_main_conn('noservice.timescale.com', 'path');\n\n-- Test telemetry report contents\nSET timescaledb.telemetry_level=basic;\n\nSELECT * FROM jsonb_object_keys(get_telemetry_report()) AS key\nWHERE key != 'os_name_pretty';\n\nCREATE MATERIALIZED VIEW telemetry_report AS\nSELECT t FROM get_telemetry_report() t;\n\n-- check telemetry picks up flagged content from metadata\nSELECT t -> 'db_metadata' FROM telemetry_report;\n\n-- check timescaledb_telemetry.cloud\nSELECT t -> 'instance_metadata' FROM telemetry_report;\n\n-- Check access methods\nSELECT t->'access_methods' ? 'btree',\n       t->'access_methods' ? 'heap',\n       CAST(t->'access_methods'->'btree'->'pages' AS int) > 0,\n       CAST(t->'access_methods'->'btree'->'instances' AS int) > 0\n  FROM telemetry_report;\n\nWITH t AS (\n\t SELECT t -> 'relations' AS rels\n\t FROM telemetry_report\n)\nSELECT rels -> 'hypertables' -> 'num_relations' AS num_hypertables,\n\t   rels -> 'continuous_aggregates' -> 'num_relations' AS num_caggs\nFROM t;\n\nCREATE TABLE device_readings (\n      observation_time  TIMESTAMPTZ       NOT NULL\n);\nSELECT table_name FROM create_hypertable('device_readings', 'observation_time');\n\nREFRESH MATERIALIZED VIEW telemetry_report;\n\nWITH t AS (\n\t SELECT t -> 'relations' AS rels\n\t FROM telemetry_report\n)\nSELECT rels -> 'hypertables' -> 'num_relations' AS num_hypertables,\n\t   rels -> 'continuous_aggregates' -> 'num_relations' AS num_caggs\nFROM t;\n\nset datestyle to iso;\n-- check that installed_time formatting in telemetry report does not depend on local date settings\nSELECT t -> 'installed_time' AS installed_time FROM telemetry_report\n\\gset\nset datestyle to sql;\nSELECT t-> 'installed_time' AS installed_time2 FROM telemetry_report\n\\gset\n\nSELECT :'installed_time' = :'installed_time2' AS equal, length(:'installed_time'), length(:'installed_time2');\n\nRESET datestyle;\n\n-- test function call telemetry\nCREATE FUNCTION not_visible_in_telemetry() RETURNS INT AS $$\n    SELECT 1;\n$$ LANGUAGE SQL;\n\n-- drain old function call telemetry so we have fixed out put;\nSELECT FROM get_telemetry_report();\n\n-- call some arbirary functions\nSELECT 1 + 1, not_visible_in_telemetry(), 1 + 1, abs(-1), not_visible_in_telemetry()\nWHERE 1 + 1 = 2;\n\n-- call some aggregates\nSELECT min(not_visible_in_telemetry()), sum(not_visible_in_telemetry());\n\n-- check that we can record from a prepared statement\nPREPARE record_from_prepared AS SELECT 1 - 1;\n\n-- execute 10 times to make sure turning it into generic plan works\nEXECUTE record_from_prepared;\nEXECUTE record_from_prepared;\nEXECUTE record_from_prepared;\nEXECUTE record_from_prepared;\nEXECUTE record_from_prepared;\nEXECUTE record_from_prepared;\nEXECUTE record_from_prepared;\nEXECUTE record_from_prepared;\nEXECUTE record_from_prepared;\nEXECUTE record_from_prepared;\n\nDEALLOCATE record_from_prepared;\n\nSELECT get_telemetry_report()->'functions_used';\n\n-- check the report again to see if resetting works\nSELECT get_telemetry_report()->'functions_used';\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE _timescaledb_catalog.metadata;\n\nSET timescaledb.telemetry_level=off;\n-- returns false which means telemetry got canceled\nSELECT * FROM _timescaledb_internal.test_privacy();\n\nRESET timescaledb.telemetry_level;\n-- returns false which means telemetry got canceled\nSELECT * FROM _timescaledb_internal.test_privacy();\n\n-- To make sure nothing was sent, we check the UUID table to make sure no exported UUID row was created\nSELECT key from _timescaledb_catalog.metadata;\n\n\\set ON_ERROR_STOP 0\n-- test that the telemetry gathering code doesn't break nonexistent statements\nEXECUTE noexistent_statement;\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- Insert some data into the telemetry event table\nINSERT INTO _timescaledb_catalog.telemetry_event(tag, body) VALUES\n    ('ummagumma', '{\"title\": \"Careful with that Axe Eugene!\"}'),\n    ('kaboom', '{\"title\": \"Where is that kaboom?\"}');\n\n-- Check that it is present in the telemetry report\nSELECT * FROM jsonb_to_recordset(get_telemetry_report()->'db_telemetry_events') AS x(tag name, body text);\n"
  },
  {
    "path": "test/sql/test_tss_callbacks.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\nCREATE OR REPLACE FUNCTION test.setup_tss_hook_v0() RETURNS VOID\n    AS :MODULE_PATHNAME, 'ts_setup_tss_hook_v0' LANGUAGE C VOLATILE;\n\nCREATE OR REPLACE FUNCTION test.setup_tss_hook_v1() RETURNS VOID\n    AS :MODULE_PATHNAME, 'ts_setup_tss_hook_v1' LANGUAGE C VOLATILE;\n\nCREATE OR REPLACE FUNCTION test.teardown_tss_hook_v1() RETURNS VOID\n    AS :MODULE_PATHNAME, 'ts_teardown_tss_hook_v1' LANGUAGE C VOLATILE;\n\nSELECT test.setup_tss_hook_v1();\n\nCREATE TABLE copy_test (\n    \"time\" timestamptz NOT NULL,\n    \"value\" double precision NOT NULL\n);\n\nSELECT create_hypertable('copy_test', 'time');\n\n-- We should se a mock message\nCOPY copy_test FROM STDIN DELIMITER ',';\n2020-01-01 01:10:00+01,1\n2021-01-01 01:10:00+01,1\n\\.\n\nSELECT test.teardown_tss_hook_v1();\n\n-- Without the hook registered we cannot see the mock message\nCOPY copy_test FROM STDIN DELIMITER ',';\n2020-01-01 01:10:00+01,1\n2021-01-01 01:10:00+01,1\n\\.\n\n-- Test for mismatch version\nSELECT test.setup_tss_hook_v0();\n\n-- Warning because the mismatch versions\nCOPY copy_test FROM STDIN DELIMITER ',';\n2020-01-01 01:10:00+01,1\n2021-01-01 01:10:00+01,1\n\\.\n"
  },
  {
    "path": "test/sql/test_utils.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION test.condition() RETURNS VOID\n    AS :MODULE_PATHNAME, 'ts_test_utils_condition' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION test.int64_eq() RETURNS VOID\n    AS :MODULE_PATHNAME, 'ts_test_utils_int64_eq' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION test.ptr_eq() RETURNS VOID\n    AS :MODULE_PATHNAME, 'ts_test_utils_ptr_eq' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nCREATE OR REPLACE FUNCTION test.double_eq() RETURNS VOID\n    AS :MODULE_PATHNAME, 'ts_test_utils_double_eq' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n\n-- We're testing that the test utils work and generate errors on\n-- failing conditions\n\\set ON_ERROR_STOP 0\nSELECT test.condition();\nSELECT test.int64_eq();\nSELECT test.ptr_eq();\nSELECT test.double_eq();\n\\set ON_ERROR_STOP 1\n\n-- Test debug points\n--\n\\set ECHO all\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- debug point already enabled\nSELECT debug_waitpoint_enable('test_debug_point');\n\\set ON_ERROR_STOP 0\nSELECT debug_waitpoint_enable('test_debug_point');\n\\set ON_ERROR_STOP 1\nSELECT debug_waitpoint_release('test_debug_point');\n\n-- debug point not enabled\n\\set ON_ERROR_STOP 0\nSELECT debug_waitpoint_release('test_debug_point');\n\\set ON_ERROR_STOP 1\n\n-- error injections\n--\nCREATE OR REPLACE FUNCTION test_error_injection(TEXT)\nRETURNS VOID\nAS :MODULE_PATHNAME, 'ts_test_error_injection'\nLANGUAGE C VOLATILE STRICT;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n\nSELECT test_error_injection('test_error');\n\nSELECT debug_waitpoint_enable('test_error');\n\\set ON_ERROR_STOP 0\nSELECT test_error_injection('test_error');\n\\set ON_ERROR_STOP 1\n\nSELECT debug_waitpoint_release('test_error');\nSELECT test_error_injection('test_error');\n\n-- Test Scanner\nRESET ROLE;\nCREATE OR REPLACE FUNCTION test.scanner() RETURNS VOID\n    AS :MODULE_PATHNAME, 'ts_test_scanner' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n\n-- Create two chunks to scan in the test\nCREATE TABLE hyper (time timestamptz, temp float);\nSELECT create_hypertable('hyper', 'time');\nINSERT INTO hyper VALUES ('2021-01-01', 1.0), ('2022-01-01', 2.0);\nSELECT test.scanner();\n\n-- Test errdata_to_jsonb\nRESET ROLE;\nCREATE OR REPLACE FUNCTION test.errdata_to_jsonb() RETURNS JSONB\nAS :MODULE_PATHNAME, 'ts_test_errdata_to_jsonb' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;\nSELECT test.errdata_to_jsonb();\n"
  },
  {
    "path": "test/sql/timestamp.sql.in",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Utility function for grouping/slotting time with a given interval.\nCREATE OR REPLACE FUNCTION date_group(\n    field           timestamp,\n    group_interval  interval\n)\n    RETURNS timestamp LANGUAGE SQL STABLE AS\n$BODY$\n    SELECT to_timestamp((EXTRACT(EPOCH from $1)::int /\n        EXTRACT(EPOCH from group_interval)::int) *\n        EXTRACT(EPOCH from group_interval)::int)::timestamp;\n$BODY$;\n\nCREATE TABLE PUBLIC.\"testNs\" (\n  \"timeCustom\" TIMESTAMP NOT NULL,\n  device_id TEXT NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL,\n  series_bool BOOLEAN NULL\n);\nCREATE INDEX ON PUBLIC.\"testNs\" (device_id, \"timeCustom\" DESC NULLS LAST) WHERE device_id IS NOT NULL;\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA \"testNs\" AUTHORIZATION :ROLE_DEFAULT_PERM_USER;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT * FROM create_hypertable('\"public\".\"testNs\"', 'timeCustom', 'device_id', 2, associated_schema_name=>'testNs' );\n\n\\c :TEST_DBNAME\nINSERT INTO PUBLIC.\"testNs\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n('2009-11-12T01:00:00+00:00', 'dev1', 1.5, 1),\n('2009-11-12T01:00:00+00:00', 'dev1', 1.5, 2),\n('2009-11-10T23:00:02+00:00', 'dev1', 2.5, 3);\n\nINSERT INTO PUBLIC.\"testNs\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n('2009-11-10T23:00:00+00:00', 'dev2', 1.5, 1),\n('2009-11-10T23:00:00+00:00', 'dev2', 1.5, 2);\n\nSELECT * FROM PUBLIC.\"testNs\";\n\nSET client_min_messages = WARNING;\n\n\\echo 'The next 2 queries will differ in output between UTC and EST since the mod is on the 100th hour UTC'\nSET timezone = 'UTC';\nSELECT date_group(\"timeCustom\", '100 days') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC;\n\nSET timezone = 'EST';\nSELECT date_group(\"timeCustom\", '100 days') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC;\n\n\\echo 'The rest of the queries will be the same in output between UTC and EST'\nSET timezone = 'UTC';\nSELECT date_group(\"timeCustom\", '1 day') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC;\n\nSET timezone = 'EST';\nSELECT date_group(\"timeCustom\", '1 day') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC;\n\nSET timezone = 'UTC';\n\nSELECT *\nFROM PUBLIC.\"testNs\"\nWHERE \"timeCustom\" >= TIMESTAMP '2009-11-10T23:00:00'\nAND \"timeCustom\" < TIMESTAMP '2009-11-12T01:00:00' ORDER BY \"timeCustom\" DESC, device_id, series_1;\n\nSET timezone = 'EST';\nSELECT *\nFROM PUBLIC.\"testNs\"\nWHERE \"timeCustom\" >= TIMESTAMP '2009-11-10T23:00:00'\nAND \"timeCustom\" < TIMESTAMP '2009-11-12T01:00:00' ORDER BY \"timeCustom\" DESC, device_id, series_1;\n\nSET timezone = 'UTC';\nSELECT date_group(\"timeCustom\", '1 day') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC LIMIT 2;\n\nSET timezone = 'EST';\nSELECT date_group(\"timeCustom\", '1 day') AS time, sum(series_0)\nFROM PUBLIC.\"testNs\" GROUP BY time ORDER BY time ASC LIMIT 2;\n\n------------------------------------\n-- Test time conversion functions --\n------------------------------------\n\\set ON_ERROR_STOP 0\n\nSET timezone = 'UTC';\n\n-- Conversion to timestamp using Postgres built-in function taking\n-- double. Gives inaccurate result on Postgres <= 9.6.2. Accurate on\n-- Postgres >= 9.6.3.\nSELECT to_timestamp(1486480176.236538);\n\n-- extension-specific version taking microsecond UNIX timestamp\nSELECT _timescaledb_functions.to_timestamp(1486480176236538);\n\n-- Should be the inverse of the statement above.\nSELECT _timescaledb_functions.to_unix_microseconds('2017-02-07 15:09:36.236538+00');\n\n-- For timestamps, BIGINT MAX represents +Infinity and BIGINT MIN\n-- -Infinity. We keep this notion for UNIX epoch time:\nSELECT _timescaledb_functions.to_unix_microseconds('+infinity');\nSELECT _timescaledb_functions.to_timestamp(9223372036854775807);\nSELECT _timescaledb_functions.to_unix_microseconds('-infinity');\nSELECT _timescaledb_functions.to_timestamp(-9223372036854775808);\n\n-- In UNIX microseconds, the largest bigint value below infinity\n-- (BIGINT MAX) is smaller than internal date upper bound and should\n-- therefore be OK. Further, converting to the internal postgres epoch\n-- cannot overflow a 64-bit INTEGER since the postgres epoch is at a\n-- later date compared to the UNIX epoch, and is therefore represented\n-- by a smaller number\nSELECT _timescaledb_functions.to_timestamp(9223372036854775806);\n\n-- Julian day zero is -210866803200000000 microseconds from UNIX epoch\nSELECT _timescaledb_functions.to_timestamp(-210866803200000000);\n\n\\set VERBOSITY default\n-- Going beyond Julian day zero should give out-of-range error\nSELECT _timescaledb_functions.to_timestamp(-210866803200000001);\n\n-- Lower bound on date (should return the Julian day zero UNIX timestamp above)\nSELECT _timescaledb_functions.to_unix_microseconds('4714-11-24 00:00:00+00 BC');\n\n-- Going beyond lower bound on date should return out-of-range\nSELECT _timescaledb_functions.to_unix_microseconds('4714-11-23 23:59:59.999999+00 BC');\n\n-- The upper bound for Postgres TIMESTAMPTZ\nSELECT timestamp '294276-12-31 23:59:59.999999+00';\n\n-- Going beyond the upper bound, should fail\nSELECT timestamp '294276-12-31 23:59:59.999999+00' + interval '1 us';\n\n-- Cannot represent the upper bound timestamp with a UNIX microsecond timestamp\n-- since the Postgres epoch is at a later date than the UNIX epoch.\nSELECT _timescaledb_functions.to_unix_microseconds('294276-12-31 23:59:59.999999+00');\n\n-- Subtracting the difference between the two epochs (10957 days) should bring\n-- us within range.\nSELECT timestamp '294276-12-31 23:59:59.999999+00' - interval '10957 days';\n\nSELECT _timescaledb_functions.to_unix_microseconds('294247-01-01 23:59:59.999999');\n\n-- Adding one microsecond should take us out-of-range again\nSELECT timestamp '294247-01-01 23:59:59.999999' + interval '1 us';\nSELECT _timescaledb_functions.to_unix_microseconds(timestamp '294247-01-01 23:59:59.999999' + interval '1 us');\n\n--no time_bucketing of dates not by integer # of days\n\nSELECT time_bucket('1 hour', DATE '2012-01-01');\nSELECT time_bucket('25 hour', DATE '2012-01-01');\n\n\\set ON_ERROR_STOP 1\n\nSELECT time_bucket(INTERVAL '1 day', TIMESTAMP '2011-01-02 01:01:01');\n\nSELECT time, time_bucket(INTERVAL '2 day ', time)\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-01 01:01:01',\n    TIMESTAMP '2011-01-02 01:01:01',\n    TIMESTAMP '2011-01-03 01:01:01',\n    TIMESTAMP '2011-01-04 01:01:01'\n    ]) AS time;\n\n\nSELECT int_def, time_bucket(int_def,TIMESTAMP '2011-01-02 01:01:01.111')\nFROM unnest(ARRAY[\n    INTERVAL '1 millisecond',\n    INTERVAL '1 second',\n    INTERVAL '1 minute',\n    INTERVAL '1 hour',\n    INTERVAL '1 day',\n    INTERVAL '2 millisecond',\n    INTERVAL '2 second',\n    INTERVAL '2 minute',\n    INTERVAL '2 hour',\n    INTERVAL '2 day'\n    ]) AS int_def;\n\n\\set ON_ERROR_STOP 0\nSELECT time_bucket(INTERVAL '1 year 1d',TIMESTAMP '2011-01-02 01:01:01.111');\nSELECT time_bucket(INTERVAL '1 month 1 minute',TIMESTAMP '2011-01-02 01:01:01.111');\n\\set ON_ERROR_STOP 1\n\nSELECT time, time_bucket(INTERVAL '5 minute', time)\nFROM unnest(ARRAY[\n    TIMESTAMP '1970-01-01 00:59:59.999999',\n    TIMESTAMP '1970-01-01 01:01:00',\n    TIMESTAMP '1970-01-01 01:04:59.999999',\n    TIMESTAMP '1970-01-01 01:05:00'\n    ]) AS time;\n\n\nSELECT time, time_bucket(INTERVAL '5 minute', time)\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-02 01:04:59.999999',\n    TIMESTAMP '2011-01-02 01:05:00',\n    TIMESTAMP '2011-01-02 01:09:59.999999',\n    TIMESTAMP '2011-01-02 01:10:00'\n    ]) AS time;\n\n--offset with interval\nSELECT time, time_bucket(INTERVAL '5 minute', time ,  INTERVAL '2 minutes')\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-02 01:01:59.999999',\n    TIMESTAMP '2011-01-02 01:02:00',\n    TIMESTAMP '2011-01-02 01:06:59.999999',\n    TIMESTAMP '2011-01-02 01:07:00'\n    ]) AS time;\n\nSELECT time, time_bucket(INTERVAL '5 minute', time , - INTERVAL '2 minutes')\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-02 01:02:59.999999',\n    TIMESTAMP '2011-01-02 01:03:00',\n    TIMESTAMP '2011-01-02 01:07:59.999999',\n    TIMESTAMP '2011-01-02 01:08:00'\n    ]) AS time;\n\n--offset with infinity\n-- timestamp\nSELECT time, time_bucket(INTERVAL '1 week', time, INTERVAL '1 day')\nFROM unnest(ARRAY[\n    timestamp '-Infinity',\n    timestamp 'Infinity'\n    ]) AS time;\n\n-- timestamptz\nSELECT time, time_bucket(INTERVAL '1 week', time, INTERVAL '1 day')\nFROM unnest(ARRAY[\n    timestamp with time zone '-Infinity',\n    timestamp with time zone 'Infinity'\n    ]) AS time;\n\n-- Date\nSELECT date, time_bucket(INTERVAL '1 week', date, INTERVAL '1 day')\nFROM unnest(ARRAY[\n    date '-Infinity',\n    date 'Infinity'\n    ]) AS date;\n\n--example to align with an origin\nSELECT time, time_bucket(INTERVAL '5 minute', time - (TIMESTAMP '2011-01-02 00:02:00' - TIMESTAMP 'epoch')) +  (TIMESTAMP '2011-01-02 00:02:00'-TIMESTAMP 'epoch')\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-02 01:01:59.999999',\n    TIMESTAMP '2011-01-02 01:02:00',\n    TIMESTAMP '2011-01-02 01:06:59.999999',\n    TIMESTAMP '2011-01-02 01:07:00'\n    ]) AS time;\n\n\n--rounding version\nSELECT time, time_bucket(INTERVAL '5 minute', time , - INTERVAL '2.5 minutes') + INTERVAL '2 minutes 30 seconds'\nFROM unnest(ARRAY[\n    TIMESTAMP '2011-01-02 01:05:01',\n    TIMESTAMP '2011-01-02 01:07:29',\n    TIMESTAMP '2011-01-02 01:02:30',\n    TIMESTAMP '2011-01-02 01:07:30',\n    TIMESTAMP '2011-01-02 01:02:29'\n    ]) AS time;\n\n--time_bucket with timezone should mimick date_trunc\nSET timezone TO 'UTC';\nSELECT time, time_bucket(INTERVAL '1 hour', time), date_trunc('hour', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+02'\n    ]) AS time;\n\nSELECT time, time_bucket(INTERVAL '1 day', time), date_trunc('day', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+02'\n    ]) AS time;\n\n--what happens with a local tz\nSET timezone TO 'America/New_York';\nSELECT time, time_bucket(INTERVAL '1 hour', time), date_trunc('hour', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+02'\n    ]) AS time;\n\n--Note the timestamp tz input is aligned with UTC day /not/ local day. different than date_trunc.\nSELECT time, time_bucket(INTERVAL '1 day', time), date_trunc('day', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-03 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-04 01:01:01+02'\n    ]) AS time;\n\n--can force local bucketing with simple cast.\nSELECT time, time_bucket(INTERVAL '1 day', time::timestamp), date_trunc('day', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-03 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-04 01:01:01+02'\n    ]) AS time;\n\n--can also use interval to correct\nSELECT time, time_bucket(INTERVAL '1 day', time, -INTERVAL '19 hours'), date_trunc('day', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',\n    TIMESTAMP WITH TIME ZONE '2011-01-03 01:01:01+01',\n    TIMESTAMP WITH TIME ZONE '2011-01-04 01:01:01+02'\n    ]) AS time;\n\n--dst: same local hour bucketed as two different hours.\nSELECT time, time_bucket(INTERVAL '1 hour', time), date_trunc('hour', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2017-11-05 12:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 13:05:00+07'\n    ]) AS time;\n\n--local alignment changes when bucketing by UTC across dst boundary\nSELECT time, time_bucket(INTERVAL '2 hour', time)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2017-11-05 10:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 12:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 13:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 15:05:00+07'\n    ]) AS time;\n\n--local alignment is preserved when bucketing by local time across DST boundary.\nSELECT time, time_bucket(INTERVAL '2 hour', time::timestamp)\nFROM unnest(ARRAY[\n    TIMESTAMP WITH TIME ZONE '2017-11-05 10:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 12:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 13:05:00+07',\n    TIMESTAMP WITH TIME ZONE '2017-11-05 15:05:00+07'\n    ]) AS time;\n\n-- GitHub issue #7059: time_bucket with timezone + offset across DST boundary\n-- Asia/Amman: clocks skip from 00:00 to 01:00 on 2021-03-26\n-- Input: 01:00+03 = 22:00 UTC → Result: 22:15 UTC = 01:15 local (00:00 + 15min offset)\nSELECT time_bucket('1 day', '2021-03-26 01:00:00+03'::timestamptz,\n    timezone := 'Asia/Amman', \"offset\" := '15 minutes'::interval);\n\n-- GitHub issue #8851: time_bucket with negative offset during DST fall-back\n-- Europe/Berlin: clocks repeat 02:00-02:59 on 2025-10-26\n-- Input: 02:00+02 = 00:00 UTC → Result: 23:59:45 UTC = 01:59:45 local (02:00 - 15s offset)\nSELECT time_bucket('30 seconds', '2025-10-26 02:00:00+02'::timestamptz,\n    timezone := 'Europe/Berlin', \"offset\" := '-15 seconds'::interval);\n\n-- Additional DST edge cases for coverage of DST direction × offset sign combinations\n\n-- Spring-forward + negative offset\n-- Input: 01:30+03 = 22:30 UTC → Result: 22:45 UTC = 01:45 local (01:00 + 45min = 02:00 - 15min)\nSELECT time_bucket('1 hour', '2021-03-26 01:30:00+03'::timestamptz,\n    timezone := 'Asia/Amman', \"offset\" := '-15 minutes'::interval);\n\n-- Fall-back + positive offset\n-- Input: 02:30+01 = 01:30 UTC → Result: 01:15 UTC = 02:15 local (02:00 + 15min offset)\nSELECT time_bucket('1 hour', '2025-10-26 02:30:00+01'::timestamptz,\n    timezone := 'Europe/Berlin', \"offset\" := '15 minutes'::interval);\n\n-- Input exactly at DST spring-forward transition\n-- Input: 22:00 UTC = 00:00 local (the moment clocks jump to 01:00)\n-- Result: 22:15 UTC = 01:15 local (01:00 + 15min offset)\nSELECT time_bucket('1 hour', '2021-03-25 22:00:00+00'::timestamptz,\n    timezone := 'Asia/Amman', \"offset\" := '15 minutes'::interval);\n\n-- Input exactly at DST fall-back transition\n-- Input: 01:00 UTC = 03:00 CEST (the moment clocks go back to 02:00 CET)\n-- Result: 23:15 UTC = 01:15 local (01:00 + 15min offset, but in CET now)\nSELECT time_bucket('1 hour', '2025-10-26 01:00:00+00'::timestamptz,\n    timezone := 'Europe/Berlin', \"offset\" := '15 minutes'::interval);\n\n-- Offset larger than bucket size (1h offset with 30min bucket)\n-- Input: 01:30+03 = 22:30 UTC → Result: 22:30 UTC = 01:30 local (01:00 + 30min = 00:30 + 1h)\nSELECT time_bucket('30 minutes', '2021-03-26 01:30:00+03'::timestamptz,\n    timezone := 'Asia/Amman', \"offset\" := '1 hour'::interval);\n\n-- GitHub issue #9136: time_bucket with origin during DST fall-back\n-- When origin is in standard time but timestamp is in daylight time,\n-- the bucket could incorrectly start AFTER the timestamp.\n-- America/New_York: clocks go back at 02:00 EDT on 2024-11-03\n-- Input: 01:30-04 (EDT) = 05:30 UTC; origin in EST\n-- Result should have bucket start <= timestamp (bucket in EDT, not EST)\nSELECT time_bucket('1 hour', '2024-11-03 01:30:00-04'::timestamptz,\n    'America/New_York', '2000-01-01 00:00:00 America/New_York'::timestamptz) as bucket,\n    '2024-11-03 01:30:00-04'::timestamptz < time_bucket('1 hour',\n        '2024-11-03 01:30:00-04'::timestamptz, 'America/New_York',\n        '2000-01-01 00:00:00 America/New_York'::timestamptz) as ts_before_bucket;\n\nSELECT time,\n    time_bucket(10::smallint, time) AS time_bucket_smallint,\n    time_bucket(10::int, time) AS time_bucket_int,\n    time_bucket(10::bigint, time) AS time_bucket_bigint\nFROM unnest(ARRAY[\n     '-11',\n     '-10',\n      '-9',\n      '-1',\n       '0',\n       '1',\n      '99',\n     '100',\n     '109',\n     '110'\n    ]::smallint[]) AS time;\n\nSELECT time,\n    time_bucket(10::smallint, time, 2::smallint) AS time_bucket_smallint,\n    time_bucket(10::int, time, 2::int) AS time_bucket_int,\n    time_bucket(10::bigint, time, 2::bigint) AS time_bucket_bigint\nFROM unnest(ARRAY[\n      '-9',\n      '-8',\n      '-7',\n       '1',\n       '2',\n       '3',\n     '101',\n     '102',\n     '111',\n     '112'\n    ]::smallint[]) AS time;\n\nSELECT time,\n    time_bucket(10::smallint, time, -2::smallint) AS time_bucket_smallint,\n    time_bucket(10::int, time, -2::int) AS time_bucket_int,\n    time_bucket(10::bigint, time, -2::bigint) AS time_bucket_bigint\nFROM unnest(ARRAY[\n    '-13',\n    '-12',\n    '-11',\n     '-3',\n     '-2',\n     '-1',\n     '97',\n     '98',\n    '107',\n    '108'\n    ]::smallint[]) AS time;\n\n\n\\set ON_ERROR_STOP 0\nSELECT time_bucket(10::smallint, '-32768'::smallint);\nSELECT time_bucket(10::smallint, '-32761'::smallint);\nselect time_bucket(10::smallint, '-32768'::smallint, 1000::smallint);\nselect time_bucket(10::smallint, '-32768'::smallint, '32767'::smallint);\nselect time_bucket(10::smallint, '32767'::smallint, '-32768'::smallint);\n\\set ON_ERROR_STOP 1\n\nSELECT time, time_bucket(10::smallint, time)\nFROM unnest(ARRAY[\n    '-32760',\n    '-32759',\n    '32767'\n    ]::smallint[]) AS time;\n\n\\set ON_ERROR_STOP 0\nSELECT time_bucket(10::int, '-2147483648'::int);\nSELECT time_bucket(10::int, '-2147483641'::int);\nSELECT time_bucket(1000::int, '-2147483000'::int, 1::int);\nSELECT time_bucket(1000::int, '-2147483648'::int, '2147483647'::int);\nSELECT time_bucket(1000::int, '2147483647'::int, '-2147483648'::int);\n\\set ON_ERROR_STOP 1\n\nSELECT time, time_bucket(10::int, time)\nFROM unnest(ARRAY[\n    '-2147483640',\n    '-2147483639',\n    '2147483647'\n    ]::int[]) AS time;\n\n\\set ON_ERROR_STOP 0\nSELECT time_bucket(10::bigint, '-9223372036854775808'::bigint);\nSELECT time_bucket(10::bigint, '-9223372036854775801'::bigint);\nSELECT time_bucket(1000::bigint, '-9223372036854775000'::bigint, 1::bigint);\nSELECT time_bucket(1000::bigint, '-9223372036854775808'::bigint, '9223372036854775807'::bigint);\nSELECT time_bucket(1000::bigint, '9223372036854775807'::bigint, '-9223372036854775808'::bigint);\n\\set ON_ERROR_STOP 1\n\nSELECT time, time_bucket(10::bigint, time)\nFROM unnest(ARRAY[\n    '-9223372036854775800',\n    '-9223372036854775799',\n    '9223372036854775807'\n    ]::bigint[]) AS time;\n\nSELECT time, time_bucket(INTERVAL '1 day', time::date)\nFROM unnest(ARRAY[\n    date '2017-11-05',\n    date '2017-11-06'\n    ]) AS time;\n\nSELECT time, time_bucket(INTERVAL '4 day', time::date)\nFROM unnest(ARRAY[\n    date '2017-11-04',\n    date '2017-11-05',\n    date '2017-11-08',\n    date '2017-11-09'\n    ]) AS time;\n\nSELECT time, time_bucket(INTERVAL '4 day', time::date, INTERVAL '2 day')\nFROM unnest(ARRAY[\n    date '2017-11-06',\n    date '2017-11-07',\n    date '2017-11-10',\n    date '2017-11-11'\n    ]) AS time;\n\n-- 2019-09-24 is a Monday, and we want to ensure that time_bucket returns the week starting with a Monday as date_trunc does,\n-- Rather than a Saturday which is the date of the PostgreSQL epoch\nSELECT time, time_bucket(INTERVAL '1 week', time::date)\nFROM unnest(ARRAY[\n    date '2018-09-16',\n    date '2018-09-17',\n    date '2018-09-23',\n    date '2018-09-24'\n    ]) AS time;\n\nSELECT time, time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp without time zone '2018-09-16',\n    timestamp without time zone '2018-09-17',\n    timestamp without time zone '2018-09-23',\n    timestamp without time zone '2018-09-24'\n    ]) AS time;\n\nSELECT time, time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp with time zone '2018-09-16',\n    timestamp with time zone '2018-09-17',\n    timestamp with time zone '2018-09-23',\n    timestamp with time zone '2018-09-24'\n    ]) AS time;\n\nSELECT time, time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp with time zone '-Infinity',\n    timestamp with time zone 'Infinity'\n    ]) AS time;\n\nSELECT time, time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp without time zone '-Infinity',\n    timestamp without time zone 'Infinity'\n    ]) AS time;\n\n\nSELECT time, time_bucket(INTERVAL '1 week', time), date_trunc('week', time) = time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp without time zone '4714-11-24 01:01:01.0 BC',\n    timestamp without time zone '294276-12-31 23:59:59.9999'\n    ]) AS time;\n\n--1000 years later weeks still align.\nSELECT time, time_bucket(INTERVAL '1 week', time), date_trunc('week', time) = time_bucket(INTERVAL '1 week', time)\nFROM unnest(ARRAY[\n    timestamp without time zone '3018-09-14',\n    timestamp without time zone '3018-09-20',\n    timestamp without time zone '3018-09-21',\n    timestamp without time zone '3018-09-22'\n    ]) AS time;\n\n--weeks align for timestamptz as well if cast to local time, (but not if done at UTC).\nSELECT time, date_trunc('week', time) = time_bucket(INTERVAL '1 week', time),  date_trunc('week', time) = time_bucket(INTERVAL '1 week', time::timestamp)\nFROM unnest(ARRAY[\n    timestamp with time zone '3018-09-14',\n    timestamp with time zone '3018-09-20',\n    timestamp with time zone '3018-09-21',\n    timestamp with time zone '3018-09-22'\n    ]) AS time;\n\n--check functions with origin\n--note that the default origin is at 0 UTC, using origin parameter it is easy to provide a EDT origin point\n\\x\nSELECT time, time_bucket(INTERVAL '1 week', time) no_epoch,\n             time_bucket(INTERVAL '1 week', time::timestamp) no_epoch_local,\n             time_bucket(INTERVAL '1 week', time) = time_bucket(INTERVAL '1 week', time, timestamptz '2000-01-03 00:00:00+0') always_true,\n             time_bucket(INTERVAL '1 week', time, timestamptz '2000-01-01 00:00:00+0') pg_epoch,\n             time_bucket(INTERVAL '1 week', time, timestamptz 'epoch') unix_epoch,\n             time_bucket(INTERVAL '1 week', time, timestamptz '3018-09-13') custom_1,\n             time_bucket(INTERVAL '1 week', time, timestamptz '3018-09-14') custom_2\nFROM unnest(ARRAY[\n    timestamp with time zone '2000-01-01 00:00:00+0'- interval '1 second',\n    timestamp with time zone '2000-01-01 00:00:00+0',\n    timestamp with time zone '2000-01-03 00:00:00+0'- interval '1 second',\n    timestamp with time zone '2000-01-03 00:00:00+0',\n    timestamp with time zone '2000-01-01',\n    timestamp with time zone '2000-01-02',\n    timestamp with time zone '2000-01-03',\n    timestamp with time zone '3018-09-12',\n    timestamp with time zone '3018-09-13',\n    timestamp with time zone '3018-09-14',\n    timestamp with time zone '3018-09-15'\n    ]) AS time;\n\nSELECT time, time_bucket(INTERVAL '1 week', time) no_epoch,\n             time_bucket(INTERVAL '1 week', time) = time_bucket(INTERVAL '1 week', time, timestamp '2000-01-03 00:00:00') always_true,\n             time_bucket(INTERVAL '1 week', time, timestamp '2000-01-01 00:00:00+0') pg_epoch,\n             time_bucket(INTERVAL '1 week', time, timestamp 'epoch') unix_epoch,\n             time_bucket(INTERVAL '1 week', time, timestamp '3018-09-13') custom_1,\n             time_bucket(INTERVAL '1 week', time, timestamp '3018-09-14') custom_2\nFROM unnest(ARRAY[\n    timestamp without time zone '2000-01-01 00:00:00'- interval '1 second',\n    timestamp without time zone '2000-01-01 00:00:00',\n    timestamp without time zone '2000-01-03 00:00:00'- interval '1 second',\n    timestamp without time zone '2000-01-03 00:00:00',\n    timestamp without time zone '2000-01-01',\n    timestamp without time zone '2000-01-02',\n    timestamp without time zone '2000-01-03',\n    timestamp without time zone '3018-09-12',\n    timestamp without time zone '3018-09-13',\n    timestamp without time zone '3018-09-14',\n    timestamp without time zone '3018-09-15'\n    ]) AS time;\n\nSELECT time, time_bucket(INTERVAL '1 week', time) no_epoch,\n             time_bucket(INTERVAL '1 week', time) = time_bucket(INTERVAL '1 week', time, date '2000-01-03') always_true,\n             time_bucket(INTERVAL '1 week', time, date '2000-01-01') pg_epoch,\n             time_bucket(INTERVAL '1 week', time, (timestamp 'epoch')::date) unix_epoch,\n             time_bucket(INTERVAL '1 week', time, date '3018-09-13') custom_1,\n             time_bucket(INTERVAL '1 week', time, date '3018-09-14') custom_2\nFROM unnest(ARRAY[\n    date '1999-12-31',\n    date '2000-01-01',\n    date '2000-01-02',\n    date '2000-01-03',\n    date '3018-09-12',\n    date '3018-09-13',\n    date '3018-09-14',\n    date '3018-09-15'\n    ]) AS time;\n\\x\n\n--really old origin works if date around that time\nSELECT time, time_bucket(INTERVAL '1 week', time, timestamp without time zone '4710-11-24 01:01:01.0 BC')\nFROM unnest(ARRAY[\n    timestamp without time zone '4710-11-24 01:01:01.0 BC',\n    timestamp without time zone '4710-11-25 01:01:01.0 BC',\n    timestamp without time zone '2001-01-01',\n    timestamp without time zone '3001-01-01'\n    ]) AS time;\n\nSELECT time, time_bucket(INTERVAL '1 week', time, timestamp without time zone '294270-12-30 23:59:59.9999')\nFROM unnest(ARRAY[\n    timestamp without time zone '294270-12-29 23:59:59.9999',\n    timestamp without time zone '294270-12-30 23:59:59.9999',\n    timestamp without time zone '294270-12-31 23:59:59.9999',\n    timestamp without time zone '2001-01-01',\n    timestamp without time zone '3001-01-01'\n    ]) AS time;\n\n\\set ON_ERROR_STOP 0\n--really old origin + very new data + long period errors\nSELECT time, time_bucket(INTERVAL '100000 day', time, timestamp without time zone '4710-11-24 01:01:01.0 BC')\nFROM unnest(ARRAY[\n    timestamp without time zone '294270-12-31 23:59:59.9999'\n    ]) AS time;\nSELECT time, time_bucket(INTERVAL '100000 day', time, timestamp with time zone '4710-11-25 01:01:01.0 BC')\nFROM unnest(ARRAY[\n    timestamp with time zone '294270-12-30 23:59:59.9999'\n    ]) AS time;\n\n--really high origin + old data + long period errors out\nSELECT time, time_bucket(INTERVAL '10000000 day', time, timestamp without time zone '294270-12-31 23:59:59.9999')\nFROM unnest(ARRAY[\n    timestamp without time zone '4710-11-24 01:01:01.0 BC'\n    ]) AS time;\nSELECT time, time_bucket(INTERVAL '10000000 day', time, timestamp with time zone '294270-12-31 23:59:59.9999')\nFROM unnest(ARRAY[\n    timestamp with time zone '4710-11-24 01:01:01.0 BC'\n    ]) AS time;\n\\set ON_ERROR_STOP 1\n\n-------------------------------------------\n--- Test time_bucket with month periods ---\n-------------------------------------------\n\nSET datestyle TO ISO;\n\nSELECT\n  time::date,\n  time_bucket('1 month', time::date) AS \"1m\",\n  time_bucket('2 month', time::date) AS \"2m\",\n  time_bucket('3 month', time::date) AS \"3m\",\n  time_bucket('1 month', time::date, '2000-02-01'::date) AS \"1m origin\",\n  time_bucket('2 month', time::date, '2000-02-01'::date) AS \"2m origin\",\n  time_bucket('3 month', time::date, '2000-02-01'::date) AS \"3m origin\"\nFROM generate_series('1990-01-03'::date,'1990-06-03'::date,'1month'::interval) time;\n\nSELECT\n  time,\n  time_bucket('1 month', time) AS \"1m\",\n  time_bucket('2 month', time) AS \"2m\",\n  time_bucket('3 month', time) AS \"3m\",\n  time_bucket('1 month', time, '2000-02-01'::timestamp) AS \"1m origin\",\n  time_bucket('2 month', time, '2000-02-01'::timestamp) AS \"2m origin\",\n  time_bucket('3 month', time, '2000-02-01'::timestamp) AS \"3m origin\"\nFROM generate_series('1990-01-03'::timestamp,'1990-06-03'::timestamp,'1month'::interval) time;\n\nSELECT\n  time,\n  time_bucket('1 month', time) AS \"1m\",\n  time_bucket('2 month', time) AS \"2m\",\n  time_bucket('3 month', time) AS \"3m\",\n  time_bucket('1 month', time, '2000-02-01'::timestamptz) AS \"1m origin\",\n  time_bucket('2 month', time, '2000-02-01'::timestamptz) AS \"2m origin\",\n  time_bucket('3 month', time, '2000-02-01'::timestamptz) AS \"3m origin\"\nFROM generate_series('1990-01-03'::timestamptz,'1990-06-03'::timestamptz,'1month'::interval) time;\n\n---------------------------------------\n--- Test time_bucket with timezones ---\n---------------------------------------\n\n-- test NULL args\nSELECT\ntime_bucket(NULL::interval,now(),'Europe/Berlin'),\ntime_bucket('1day',NULL::timestamptz,'Europe/Berlin'),\ntime_bucket('1day',now(),NULL::text),\ntime_bucket('1day',timestamptz '2020-02-03','Europe/Berlin',NULL),\ntime_bucket('1day',timestamptz '2020-02-03','Europe/Berlin','2020-04-01',NULL),\ntime_bucket('1day',timestamptz '2020-02-03','Europe/Berlin',NULL,NULL),\ntime_bucket('1day',timestamptz '2020-02-03','Europe/Berlin',\"offset\":=NULL::interval),\ntime_bucket('1day',timestamptz '2020-02-03','Europe/Berlin',origin:=NULL::timestamptz);\n\nSET datestyle TO ISO;\nSELECT\n  time_bucket('1day', ts) AS \"UTC\",\n  time_bucket('1day', ts, 'Europe/Berlin') AS \"Berlin\",\n  time_bucket('1day', ts, 'Europe/London') AS \"London\",\n  time_bucket('1day', ts, 'America/New_York') AS \"New York\",\n  time_bucket('1day', ts, 'PST') AS \"PST\",\n  time_bucket('1day', ts, current_setting('timezone')) AS \"current\"\n\nFROM generate_series('1999-12-31 17:00'::timestamptz,'2000-01-02 3:00'::timestamptz, '1hour'::interval) ts;\n\nSELECT\n  time_bucket('1month', ts) AS \"UTC\",\n  time_bucket('1month', ts, 'Europe/Berlin') AS \"Berlin\",\n  time_bucket('1month', ts, 'America/New_York') AS \"New York\",\n  time_bucket('1month', ts, current_setting('timezone')) AS \"current\",\n  time_bucket('2month', ts, current_setting('timezone')) AS \"2m\",\n  time_bucket('2month', ts, current_setting('timezone'), '2000-02-01'::timestamp) AS \"2m origin\",\n  time_bucket('2month', ts, current_setting('timezone'), \"offset\":='14 day'::interval) AS \"2m offset\",\n  time_bucket('2month', ts, current_setting('timezone'), '2000-02-01'::timestamp, '7 day'::interval) AS \"2m offset + origin\"\n\nFROM generate_series('1999-12-01'::timestamptz,'2000-09-01'::timestamptz, '9 day'::interval) ts;\n\nRESET datestyle;\n\n-------------------------------------\n--- Test time input functions --\n-------------------------------------\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION test.interval_to_internal(coltype REGTYPE, value ANYELEMENT = NULL::BIGINT) RETURNS BIGINT\nAS :MODULE_PATHNAME, 'ts_dimension_interval_to_internal_test' LANGUAGE C VOLATILE;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n\nSELECT test.interval_to_internal('TIMESTAMP'::regtype, INTERVAL '1 day');\nSELECT test.interval_to_internal('TIMESTAMP'::regtype, 86400000000);\n---should give warning\nSELECT test.interval_to_internal('TIMESTAMP'::regtype, 86400);\n\nSELECT test.interval_to_internal('TIMESTAMP'::regtype);\nSELECT test.interval_to_internal('BIGINT'::regtype, 2147483649::bigint);\n\n-- Default interval for integer is supported as part of\n-- hypertable generalization\nSELECT test.interval_to_internal('INT'::regtype);\nSELECT test.interval_to_internal('SMALLINT'::regtype);\nSELECT test.interval_to_internal('BIGINT'::regtype);\nSELECT test.interval_to_internal('TIMESTAMPTZ'::regtype);\nSELECT test.interval_to_internal('TIMESTAMP'::regtype);\nSELECT test.interval_to_internal('DATE'::regtype);\n\n\\set VERBOSITY terse\n\\set ON_ERROR_STOP 0\nSELECT test.interval_to_internal('INT'::regtype, 2147483649::bigint);\nSELECT test.interval_to_internal('SMALLINT'::regtype, 32768::bigint);\nSELECT test.interval_to_internal('TEXT'::regtype, 32768::bigint);\nSELECT test.interval_to_internal('INT'::regtype, INTERVAL '1 day');\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "test/sql/triggers.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE TABLE hyper (\n  time BIGINT NOT NULL,\n  device_id TEXT NOT NULL,\n  sensor_1 NUMERIC NULL DEFAULT 1\n);\n\nCREATE OR REPLACE FUNCTION test_trigger()\n    RETURNS TRIGGER LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n    cnt INTEGER;\nBEGIN\n    SELECT count(*) INTO cnt FROM hyper;\n    RAISE WARNING 'FIRING trigger when: % level: % op: % cnt: % trigger_name %',\n        tg_when, tg_level, tg_op, cnt, tg_name;\n\n    IF TG_OP = 'DELETE' THEN\n        RETURN OLD;\n    END IF;\n    RETURN NEW;\nEND\n$BODY$;\n\n-- row triggers: BEFORE\nCREATE TRIGGER _0_test_trigger_insert\n    BEFORE INSERT ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\n\nCREATE TRIGGER _0_test_trigger_update\n    BEFORE UPDATE ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\n\nCREATE TRIGGER _0_test_trigger_delete\n    BEFORE delete ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\n\nCREATE TRIGGER z_test_trigger_all\n    BEFORE INSERT OR UPDATE OR DELETE ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\n\n-- row triggers: AFTER\nCREATE TRIGGER _0_test_trigger_insert_after\n    AFTER INSERT ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\n\nCREATE TRIGGER _0_test_trigger_insert_after_when_dev1\n    AFTER INSERT ON hyper\n    FOR EACH ROW\n    WHEN (NEW.device_id = 'dev1')\n    EXECUTE FUNCTION test_trigger();\n\nCREATE TRIGGER _0_test_trigger_update_after\n    AFTER UPDATE ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\n\nCREATE TRIGGER _0_test_trigger_delete_after\n    AFTER delete ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\n\nCREATE TRIGGER z_test_trigger_all_after\n    AFTER INSERT OR UPDATE OR DELETE ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\n\n-- statement triggers: BEFORE\nCREATE TRIGGER _0_test_trigger_insert_s_before\n    BEFORE INSERT ON hyper\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\n\nCREATE TRIGGER _0_test_trigger_update_s_before\n    BEFORE UPDATE ON hyper\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\n\nCREATE TRIGGER _0_test_trigger_delete_s_before\n    BEFORE DELETE ON hyper\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\n\n-- statement triggers: AFTER\nCREATE TRIGGER _0_test_trigger_insert_s_after\n    AFTER INSERT ON hyper\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\n\nCREATE TRIGGER _0_test_trigger_update_s_after\n    AFTER UPDATE ON hyper\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\n\nCREATE TRIGGER _0_test_trigger_delete_s_after\n    AFTER DELETE ON hyper\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\n\n-- CONSTRAINT TRIGGER\nCREATE CONSTRAINT TRIGGER _0_test_trigger_constraint_insert\n  AFTER INSERT ON hyper FOR EACH ROW EXECUTE FUNCTION test_trigger();\n\nCREATE CONSTRAINT TRIGGER _0_test_trigger_constraint_update\n  AFTER UPDATE ON hyper FOR EACH ROW EXECUTE FUNCTION test_trigger();\n\nCREATE CONSTRAINT TRIGGER _0_test_trigger_constraint_delete\n  AFTER DELETE ON hyper FOR EACH ROW EXECUTE FUNCTION test_trigger();\n\nSELECT * FROM create_hypertable('hyper', 'time', chunk_time_interval => 10);\n\n--test triggers before create_hypertable\nINSERT INTO hyper(time, device_id,sensor_1) VALUES\n(1257987600000000000, 'dev1', 1);\n\nINSERT INTO hyper(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 1), (1257987800000000000, 'dev2', 1);\n\nUPDATE hyper SET sensor_1 = 2;\n\nDELETE FROM hyper;\n\n--test drop trigger\nDROP TRIGGER _0_test_trigger_insert ON hyper;\nDROP TRIGGER _0_test_trigger_insert_s_before ON hyper;\nDROP TRIGGER _0_test_trigger_insert_after ON hyper;\nDROP TRIGGER _0_test_trigger_insert_s_after ON hyper;\nINSERT INTO hyper(time, device_id,sensor_1) VALUES\n(1257987600000000000, 'dev1', 1);\nINSERT INTO hyper(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 1), (1257987800000000000, 'dev2', 1);\n\nDROP TRIGGER _0_test_trigger_update ON hyper;\nDROP TRIGGER _0_test_trigger_update_s_before ON hyper;\nDROP TRIGGER _0_test_trigger_update_after ON hyper;\nDROP TRIGGER _0_test_trigger_update_s_after ON hyper;\nUPDATE hyper SET sensor_1 = 2;\n\nDROP TRIGGER _0_test_trigger_delete ON hyper;\nDROP TRIGGER _0_test_trigger_delete_s_before ON hyper;\nDROP TRIGGER _0_test_trigger_delete_after ON hyper;\nDROP TRIGGER _0_test_trigger_delete_s_after ON hyper;\nDELETE FROM hyper;\n\nDROP TRIGGER z_test_trigger_all ON hyper;\nDROP TRIGGER z_test_trigger_all_after ON hyper;\n\n--test create trigger on hypertable\n\n-- row triggers: BEFORE\nCREATE TRIGGER _0_test_trigger_insert\n    BEFORE INSERT ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\n\nCREATE TRIGGER _0_test_trigger_update\n    BEFORE UPDATE ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\n\nCREATE TRIGGER _0_test_trigger_delete\n    BEFORE delete ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\n\nCREATE TRIGGER z_test_trigger_all\n    BEFORE INSERT OR UPDATE OR DELETE ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\n\n-- row triggers: AFTER\nCREATE TRIGGER _0_test_trigger_insert_after\n    AFTER INSERT ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\n\nCREATE TRIGGER _0_test_trigger_update_after\n    AFTER UPDATE ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\n\nCREATE TRIGGER _0_test_trigger_delete_after\n    AFTER delete ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\n\nCREATE TRIGGER z_test_trigger_all_after\n    AFTER INSERT OR UPDATE OR DELETE ON hyper\n    FOR EACH ROW EXECUTE FUNCTION test_trigger();\n\n-- statement triggers: BEFORE\nCREATE TRIGGER _0_test_trigger_insert_s_before\n    BEFORE INSERT ON hyper\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\n\nCREATE TRIGGER _0_test_trigger_update_s_before\n    BEFORE UPDATE ON hyper\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\n\nCREATE TRIGGER _0_test_trigger_delete_s_before\n    BEFORE DELETE ON hyper\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\n\n-- statement triggers: AFTER\nCREATE TRIGGER _0_test_trigger_insert_s_after\n    AFTER INSERT ON hyper\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\n\nCREATE TRIGGER _0_test_trigger_update_s_after\n    AFTER UPDATE ON hyper\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\n\nCREATE TRIGGER _0_test_trigger_delete_s_after\n    AFTER DELETE ON hyper\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\n\n\nINSERT INTO hyper(time, device_id,sensor_1) VALUES\n(1257987600000000000, 'dev1', 1);\n\nINSERT INTO hyper(time, device_id,sensor_1) VALUES\n(1257987700000000000, 'dev2', 1), (1257987800000000000, 'dev2', 1);\n\nUPDATE hyper SET sensor_1 = 2;\n\nDELETE FROM hyper;\n\nCREATE TABLE vehicles (\n  vehicle_id INTEGER PRIMARY KEY,\n  vin_number CHAR(17),\n  last_checkup TIMESTAMP\n);\n\nCREATE TABLE color (\n  color_id INTEGER PRIMARY KEY,\n  notes text\n);\n\n\nCREATE TABLE location (\n  time TIMESTAMP NOT NULL,\n  vehicle_id INTEGER REFERENCES vehicles (vehicle_id),\n  color_id INTEGER, --no reference since gonna populate a hypertable\n  latitude FLOAT,\n  longitude FLOAT\n);\n\nCREATE OR REPLACE FUNCTION create_vehicle_trigger_fn()\n    RETURNS TRIGGER LANGUAGE PLPGSQL AS\n$BODY$\nBEGIN\n    INSERT INTO vehicles VALUES(NEW.vehicle_id, NULL, NULL) ON CONFLICT DO NOTHING;\n    RETURN NEW;\nEND\n$BODY$;\n\n\nCREATE OR REPLACE FUNCTION create_color_trigger_fn()\n    RETURNS TRIGGER LANGUAGE PLPGSQL AS\n$BODY$\nBEGIN\n    --test subtxns within triggers\n    BEGIN\n        INSERT INTO color VALUES(NEW.color_id, 'n/a');\n    EXCEPTION WHEN unique_violation THEN\n            -- Nothing to do, just continue\n    END;\n    RETURN NEW;\nEND\n$BODY$;\n\nCREATE TRIGGER create_color_trigger\n    BEFORE INSERT OR UPDATE ON location\n    FOR EACH ROW EXECUTE FUNCTION create_color_trigger_fn();\n\nSELECT create_hypertable('location', 'time');\n\n--make color also a hypertable\nSELECT create_hypertable('color', 'color_id', chunk_time_interval=>10);\n\n-- Test that we can create and use triggers with another user\nGRANT TRIGGER, INSERT, SELECT, UPDATE ON location TO :ROLE_DEFAULT_PERM_USER_2;\nGRANT SELECT, INSERT, UPDATE ON color TO :ROLE_DEFAULT_PERM_USER_2;\nGRANT SELECT, INSERT, UPDATE ON vehicles TO :ROLE_DEFAULT_PERM_USER_2;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2;\n\nCREATE TRIGGER create_vehicle_trigger\n    BEFORE INSERT OR UPDATE ON location\n    FOR EACH ROW EXECUTE FUNCTION create_vehicle_trigger_fn();\n\nINSERT INTO location VALUES('2017-01-01 01:02:03', 1, 1, 40.7493226,-73.9771259);\nINSERT INTO location VALUES('2017-01-01 01:02:04', 1, 20, 24.7493226,-73.9771259);\nINSERT INTO location VALUES('2017-01-01 01:02:03', 23, 1, 40.7493226,-73.9771269);\nINSERT INTO location VALUES('2017-01-01 01:02:03', 53, 20, 40.7493226,-73.9771269);\n\nUPDATE location SET vehicle_id = 52 WHERE vehicle_id = 53;\n\nSELECT * FROM location;\nSELECT * FROM vehicles;\nSELECT * FROM color;\n\n-- switch back to default user to run some dropping tests\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER;\n\n\\set ON_ERROR_STOP 0\n-- test that disable trigger is disallowed\nALTER TABLE location DISABLE TRIGGER create_vehicle_trigger;\n\\set ON_ERROR_STOP 1\n\n-- test that drop trigger works\nDROP TRIGGER create_color_trigger ON location;\nDROP TRIGGER create_vehicle_trigger ON location;\n\n-- test that drop trigger doesn't cause leftovers that mean that dropping chunks or hypertables no longer works\nSELECT count(1) FROM pg_depend d WHERE d.classid = 'pg_trigger'::regclass AND NOT EXISTS (SELECT 1 FROM pg_trigger WHERE oid = d.objid);\nDROP TABLE location;\n\n-- test triggers with transition tables\n-- test creating hypertable from table with triggers with transition tables\nCREATE TABLE transition_test(time timestamptz NOT NULL);\nCREATE TRIGGER t1_stmt AFTER INSERT ON transition_test REFERENCING NEW TABLE AS new_trans FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER t1_row AFTER INSERT ON transition_test REFERENCING NEW TABLE AS new_trans FOR EACH ROW EXECUTE FUNCTION test_trigger();\n\n-- We do not support ROW triggers with transition tables, so we need\n-- to remove it to be able to create the hypertable.\n\\set ON_ERROR_STOP 0\nSELECT create_hypertable('transition_test','time');\n\\set ON_ERROR_STOP 1\nDROP TRIGGER t1_row ON transition_test;\nSELECT create_hypertable('transition_test','time');\n\n-- Insert some rows to create a chunk\nINSERT INTO transition_test values ('2020-01-10');\nSELECT chunk FROM show_chunks('transition_test') tbl(chunk) limit 1 \\gset\n\n-- test creating trigger with transition tables on existing hypertable\nCREATE TRIGGER t3 AFTER UPDATE ON transition_test REFERENCING NEW TABLE AS new_trans OLD TABLE AS old_trans FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER t4 AFTER DELETE ON transition_test REFERENCING OLD TABLE AS old_trans FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\n\nINSERT INTO transition_test values ('2020-01-11');\nCOPY transition_test FROM STDIN;\n2020-01-09\n\\.\nUPDATE transition_test SET time = '2020-01-12' WHERE time = '2020-01-11';\nDELETE FROM transition_test WHERE time = '2020-01-12';\n\n\\set ON_ERROR_STOP 0\nCREATE TRIGGER t3 AFTER UPDATE ON :chunk REFERENCING NEW TABLE AS new_trans OLD TABLE AS old_trans FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER t4 AFTER DELETE ON :chunk REFERENCING OLD TABLE AS old_trans FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER t5 AFTER INSERT ON transition_test REFERENCING NEW TABLE AS new_trans FOR EACH ROW EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER t6 AFTER UPDATE ON transition_test REFERENCING NEW TABLE AS new_trans OLD TABLE AS old_trans FOR EACH ROW EXECUTE FUNCTION test_trigger();\nCREATE TRIGGER t7 AFTER DELETE ON transition_test REFERENCING OLD TABLE AS old_trans FOR EACH ROW EXECUTE FUNCTION test_trigger();\n\\set ON_ERROR_STOP 1\n\n"
  },
  {
    "path": "test/sql/truncate.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\o /dev/null\n\\ir include/insert_two_partitions.sql\n\\o\n\nSELECT * FROM _timescaledb_catalog.hypertable;\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\nSELECT * FROM test.show_subtables('\"two_Partitions\"');\nSELECT * FROM \"two_Partitions\";\n\nSET client_min_messages = WARNING;\nTRUNCATE \"two_Partitions\";\n\nSELECT * FROM _timescaledb_catalog.hypertable;\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n\n-- should be empty\nSELECT * FROM test.show_subtables('\"two_Partitions\"');\nSELECT * FROM \"two_Partitions\";\n\nINSERT INTO public.\"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1) VALUES\n(1257987600000000000, 'dev1', 1.5, 1),\n(1257987600000000000, 'dev1', 1.5, 2),\n(1257894000000000000, 'dev2', 1.5, 1),\n(1257894002000000000, 'dev1', 2.5, 3);\n\nSELECT id, hypertable_id, schema_name, table_name, compressed_chunk_id, status, osm_chunk FROM _timescaledb_catalog.chunk;\n\nCREATE VIEW dependent_view AS SELECT * FROM _timescaledb_internal._hyper_1_5_chunk;\n\nCREATE OR REPLACE FUNCTION test_trigger()\n    RETURNS TRIGGER LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n    cnt INTEGER;\nBEGIN\n    RAISE WARNING 'FIRING trigger when: % level: % op: % cnt: % trigger_name %',\n          tg_when, tg_level, tg_op, cnt, tg_name;\n    IF TG_OP = 'DELETE' THEN\n        RETURN OLD;\n    END IF;\n    RETURN NEW;\nEND\n$BODY$;\n\n-- test truncate on a chunk\nCREATE TRIGGER _test_truncate_before\n    BEFORE TRUNCATE ON _timescaledb_internal._hyper_1_5_chunk\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\n\nCREATE TRIGGER _test_truncate_after\n    AFTER TRUNCATE ON _timescaledb_internal._hyper_1_5_chunk\n    FOR EACH STATEMENT EXECUTE FUNCTION test_trigger();\n\n\\set ON_ERROR_STOP 0\nTRUNCATE \"two_Partitions\";\n-- cannot TRUNCATE ONLY a hypertable\nTRUNCATE ONLY \"two_Partitions\" CASCADE;\n\\set ON_ERROR_STOP 1\n\n-- create a regular table to make sure we can truncate it in the same call\nCREATE TABLE truncate_normal (color int);\nINSERT INTO truncate_normal VALUES (1);\nSELECT * FROM truncate_normal;\n\n-- fix for bug #3580\n\\set ON_ERROR_STOP 0\nTRUNCATE nonexistentrelation;\n\\set ON_ERROR_STOP 1\nCREATE TABLE truncate_nested (color int);\nINSERT INTO truncate_nested VALUES (2);\n\nSELECT * FROM truncate_normal, truncate_nested;\n\nCREATE FUNCTION fn_truncate_nested()\nRETURNS trigger LANGUAGE plpgsql\nAS $$\nBEGIN\n    TRUNCATE truncate_nested;\n    RETURN NEW;\nEND;\n$$;\n\nCREATE TRIGGER tg_truncate_nested\n    BEFORE TRUNCATE ON truncate_normal\n    FOR EACH STATEMENT EXECUTE FUNCTION fn_truncate_nested();\n\nTRUNCATE truncate_normal;\nSELECT * FROM truncate_normal, truncate_nested;\n\nINSERT INTO truncate_normal VALUES (3);\nINSERT INTO truncate_nested VALUES (4);\n\nSELECT * FROM truncate_normal, truncate_nested;\n\nTRUNCATE truncate_normal;\nSELECT * FROM truncate_normal, truncate_nested;\n\nINSERT INTO truncate_normal VALUES (5);\nINSERT INTO truncate_nested VALUES (6);\nSELECT * FROM truncate_normal, truncate_nested;\n\nSELECT * FROM test.show_subtables('\"two_Partitions\"');\n\nTRUNCATE \"two_Partitions\", truncate_normal CASCADE;\n-- should be empty\nSELECT * FROM test.show_subtables('\"two_Partitions\"');\nSELECT * FROM \"two_Partitions\";\nSELECT * FROM truncate_normal, truncate_nested;\n\n-- test TRUNCATE can be performed by a user\n-- with TRUNCATE privilege who is not table owner\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\nCREATE ROLE owner WITH LOGIN;\nCREATE ROLE truncator WITH LOGIN;\nCREATE DATABASE test_trunc_ht OWNER owner;\n\n\\c test_trunc_ht :ROLE_SUPERUSER\nSET client_min_messages = ERROR;\nCREATE EXTENSION timescaledb;\nRESET client_min_messages;\n\n\\c test_trunc_ht owner\nCREATE TABLE test_hypertable (time TIMESTAMP WITHOUT TIME ZONE NOT NULL, value DOUBLE PRECISION);\nSELECT create_hypertable('test_hypertable', 'time');\n\n-- fail since we don't have TRUNCATE privileges yet\n\\set ON_ERROR_STOP 0\n\n\\c test_trunc_ht truncator\nTRUNCATE TABLE test_hypertable;\n\n\\set ON_ERROR_STOP 1\n\n\\c test_trunc_ht owner\nGRANT TRUNCATE ON test_hypertable TO truncator;\n\n-- now succeed after privilege was granted\n\\c test_trunc_ht truncator;\nTRUNCATE TABLE test_hypertable;\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- set client_min_messages to ERROR to suppress warnings about orphaned files\nSET client_min_messages TO ERROR;\nDROP DATABASE test_trunc_ht WITH (FORCE);\nDROP ROLE owner;\nDROP ROLE truncator;\n"
  },
  {
    "path": "test/sql/trusted_extension.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE DATABASE trusted_test;\nGRANT CREATE ON DATABASE trusted_test TO :ROLE_1;\n\n\\c trusted_test :ROLE_READ_ONLY\n\\set ON_ERROR_STOP 0\nCREATE EXTENSION timescaledb;\n\\set ON_ERROR_STOP 1\n\n\\c trusted_test :ROLE_1\n-- user shouldn't have superuser privilege\nSELECT rolsuper FROM pg_roles WHERE rolname=user;\n\nSET client_min_messages TO ERROR;\nCREATE EXTENSION timescaledb;\nRESET client_min_messages;\n\nCREATE TABLE t(time timestamptz);\nSELECT create_hypertable('t','time');\n\nINSERT INTO t VALUES ('2000-01-01'), ('2001-01-01');\n\nSELECT * FROM t ORDER BY 1;\nSELECT * FROM timescaledb_information.hypertables;\nSELECT * FROM test.relation WHERE schema = '_timescaledb_internal' AND name LIKE '\\_hyper%';\n\nDROP EXTENSION timescaledb CASCADE;\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nDROP DATABASE trusted_test WITH (FORCE);\n"
  },
  {
    "path": "test/sql/ts_merge.sql.in",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\\set ON_ERROR_STOP 0\n\\set VERBOSITY default\nSET client_min_messages TO error;\n\n\\set TEST_BASE_NAME ts_merge\nSELECT format('include/%s_load.sql', :'TEST_BASE_NAME') AS \"TEST_LOAD_NAME\",\n    format('include/%s_load_ht.sql', :'TEST_BASE_NAME') AS \"TEST_LOAD_HT_NAME\",\n    format('include/%s_query.sql', :'TEST_BASE_NAME') AS \"TEST_QUERY_NAME\",\n    format('%s/results/%s_ht_results.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') AS \"TEST_RESULTS_WITH_HYPERTABLE\",\n    format('%s/results/%s_results.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') AS \"TEST_RESULTS_WITH_NO_HYPERTABLE\" \\gset\n\nSELECT format('\\! diff -u --label \"Base pg table results\" --label \"Hypertable results\" %s %s', :'TEST_RESULTS_WITH_HYPERTABLE', :'TEST_RESULTS_WITH_NO_HYPERTABLE') AS \"DIFF_CMD\" \\gset\n\n\\ir :TEST_LOAD_NAME\n\n-- run tests on normal table\n\\o :TEST_RESULTS_WITH_NO_HYPERTABLE\n\\ir :TEST_QUERY_NAME\n\\o\n\n\\ir :TEST_LOAD_HT_NAME\n\n-- run tests on hypertable\n\\o :TEST_RESULTS_WITH_HYPERTABLE\n\\ir :TEST_QUERY_NAME\n\\o\n\n:DIFF_CMD\n"
  },
  {
    "path": "test/sql/update.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\o /dev/null\n\\ir include/insert_single.sql\n\\o\n\n-- Make sure UPDATE isn't optimized if it includes Append plans\n-- Need to turn of nestloop to make append appear the same on PG96 and PG10\nset enable_nestloop = 'off';\n\nCREATE OR REPLACE FUNCTION series_val()\nRETURNS integer LANGUAGE PLPGSQL STABLE AS\n$BODY$\nBEGIN\n    RETURN 5;\nEND;\n$BODY$;\n\n-- ConstraintAwareAppend applied for SELECT\nEXPLAIN (buffers off, costs off)\nSELECT FROM \"one_Partition\"\nWHERE series_1 IN (SELECT series_1 FROM \"one_Partition\" WHERE series_1 > series_val());\n\n-- ConstraintAwareAppend NOT applied for UPDATE\nEXPLAIN (buffers off, costs off)\nUPDATE \"one_Partition\"\nSET series_1 = 8\nWHERE series_1 IN (SELECT series_1 FROM \"one_Partition\" WHERE series_1 > series_val());\n\nSELECT * FROM \"one_Partition\" ORDER BY \"timeCustom\", device_id, series_0, series_1, series_2;\nUPDATE \"one_Partition\"\nSET series_1 = 8\nWHERE series_1 IN (SELECT series_1 FROM \"one_Partition\" WHERE series_1 > series_val());\nSELECT * FROM \"one_Partition\" ORDER BY \"timeCustom\", device_id, series_0, series_1, series_2;\n\nUPDATE \"one_Partition\" SET series_1 = 47;\nUPDATE \"one_Partition\" SET series_bool = true;\nSELECT * FROM \"one_Partition\" ORDER BY \"timeCustom\", device_id, series_0, series_1, series_2;\n\n-- test update on chunks directly\nCREATE TABLE direct_update(time timestamptz) WITH (tsdb.hypertable);\n\nINSERT INTO direct_update VALUES ('2020-01-01');\nSELECT show_chunks('direct_update') AS \"CHUNK\" \\gset\n\n--should have ModifyHyperable node\nEXPLAIN (costs off, timing off, summary off) UPDATE :CHUNK SET time = time + INTERVAL '1 minute';\nEXPLAIN (costs off, timing off, summary off) UPDATE ONLY :CHUNK SET time = time + INTERVAL '1 minute';\n\n-- correct time range should succeed\nUPDATE :CHUNK SET time = time + INTERVAL '1 minute' RETURNING *;\nUPDATE ONLY :CHUNK SET time = time + INTERVAL '1 minute' RETURNING *;\n-- crossing chunk boundary should fail\n\\set ON_ERROR_STOP 0\nUPDATE :CHUNK SET time = time + INTERVAL '1 month' RETURNING *;\nUPDATE ONLY :CHUNK SET time = time + INTERVAL '1 month' RETURNING *;\n\\set ON_ERROR_STOP 1\n\n-- github issue #6790\n-- test UPDATE with WHERE EXISTS on hypertable\nCREATE TABLE i6790_update(time timestamptz NOT NULL, device int, value float) WITH (tsdb.hypertable);\nINSERT INTO i6790_update SELECT t, 1, 0.1 FROM generate_series('2026-01-01'::timestamptz, '2026-01-03'::timestamptz, interval '12 hours') t;\n\n-- UPDATE with simple EXISTS - creates gating Result node(s) wrapping ChunkAppend\nUPDATE i6790_update SET value = 0.2 WHERE EXISTS (SELECT 1);\nSELECT count(*) FROM i6790_update WHERE value = 0.2;\n\n-- UPDATE with correlated EXISTS\nUPDATE i6790_update SET value = 0.3 WHERE EXISTS (SELECT 1 FROM i6790_update g WHERE g.device = i6790_update.device);\nSELECT count(*) FROM i6790_update WHERE value = 0.3;\n\n"
  },
  {
    "path": "test/sql/updates/catalog_missing_columns.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n--PG11 added an optimization where columns that were added by\n--an ALTER TABLE that had a DEFAULT value did not cause a table re-write.\n--Instead, those columns are filled with the default value on read.\n--But, this mechanism does not apply to catalog tables and does\n--not work with our catalog scanning code.\n--Thus make sure all catalog tables do not have this enabled (a.atthasmissing == false)\nCREATE OR REPLACE FUNCTION timescaledb_catalog_has_no_missing_columns()\n    RETURNS VOID LANGUAGE PLPGSQL STABLE AS\n$BODY$\nDECLARE\n    cnt   INTEGER;\n    rel_names TEXT;\nBEGIN\n\n    SELECT count(*), string_agg(c.relname,' ;')\n    FROM pg_namespace n\n    INNER JOIN pg_class c ON (c.relnamespace = n.oid)\n    INNER JOIN pg_attribute a ON attrelid=c.oid\n    WHERE   a.attnum >= 0 AND\n            (n.nspname='_timescaledb_catalog' OR n.nspname = '_timescaledb_config' OR\n            n.nspname='_timescaledb_internal')\n        AND a.atthasmissing\n    INTO STRICT cnt, rel_names;\n\n    IF cnt != 0 THEN\n        RAISE EXCEPTION 'Some catalog tables were altered without a table re-write: %', rel_names;\n    END IF;\nEND;\n$BODY$;\n\nSELECT timescaledb_catalog_has_no_missing_columns();\n\n"
  },
  {
    "path": "test/sql/updates/cleanup.bigint.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nDROP TABLE PUBLIC.\"two_Partitions\" CASCADE;\n"
  },
  {
    "path": "test/sql/updates/cleanup.chunk_skipping.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nDROP TABLE IF EXISTS chunkskip;\n"
  },
  {
    "path": "test/sql/updates/cleanup.compression.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nDROP TABLE compress;\nDROP TYPE custom_type_for_compression;\n"
  },
  {
    "path": "test/sql/updates/cleanup.constraints.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nALTER TABLE \"two_Partitions\"\n  DROP CONSTRAINT IF EXISTS two_Partitions_device_id_2_fkey;\n\nDROP TABLE IF EXISTS devices;\n"
  },
  {
    "path": "test/sql/updates/cleanup.continuous_aggs.v2.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Collect information about different features so that we can pick\n-- the right usage. Some of these are changed in the same version, but\n-- we keep them separate anyway so that we can do additional checking\n-- if necessary.\nSELECT extversion < '2.0.0' AS has_refresh_mat_view,\n       extversion < '2.0.0' AS has_drop_chunks_old_interface,\n       extversion < '2.0.0' AS has_ignore_invalidations_older_than,\n       extversion < '2.0.0' AS has_max_interval_per_job,\n       extversion >= '2.0.0' AS has_create_mat_view,\n       extversion >= '2.0.0' AS has_continuous_aggs_policy\n  FROM pg_extension\n WHERE extname = 'timescaledb' \\gset\n\n\\if :has_continuous_aggs_policy\nSELECT remove_continuous_aggregate_policy('mat_drop');\n\\endif\n\n\\if :has_create_mat_view\nDROP MATERIALIZED VIEW mat_drop;\n\\else\nDROP VIEW mat_drop;\n\\endif\n\nDROP TABLE drop_test;\n\n\\if :has_create_mat_view\nDROP MATERIALIZED VIEW mat_conflict;\n\\else\nDROP VIEW mat_conflict;\n\\endif\n\nDROP TABLE conflict_test;\n\n\\if :has_create_mat_view\nDROP MATERIALIZED VIEW mat_inttime;\nDROP MATERIALIZED VIEW mat_inttime2;\n\\else\nDROP VIEW mat_inttime;\nDROP VIEW mat_inttime2;\n\\endif\n\nDROP FUNCTION integer_now_test;\nDROP TABLE IF EXISTS int_time_test;\n\n\\if :has_create_mat_view\nDROP MATERIALIZED VIEW mat_inval;\n\\else\nDROP VIEW mat_inval;\n\\endif\n\nDROP TABLE inval_test;\n\n\\if :has_create_mat_view\nDROP MATERIALIZED VIEW mat_ignoreinval;\n\\else\nDROP VIEW mat_ignoreinval;\n\\endif\n\n\\if :has_create_mat_view\nDROP MATERIALIZED VIEW cagg.realtime_mat;\n\\else\nDROP VIEW cagg.realtime_mat;\n\\endif\n\nDROP SCHEMA cagg;\n\n\\if :has_create_mat_view\nDROP MATERIALIZED VIEW mat_before;\n\\else\nDROP VIEW mat_before;\n\\endif\n\n\\if :has_create_mat_view\nDROP MATERIALIZED VIEW rename_cols;\n\\else\nDROP VIEW rename_cols;\n\\endif\n\nDROP TABLE conditions_before;\nDROP TYPE custom_type;\n\n\n"
  },
  {
    "path": "test/sql/updates/cleanup.policies.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nSELECT remove_reorder_policy('policy_test_timestamptz');\nSELECT remove_retention_policy('policy_test_timestamptz');\nSELECT remove_compression_policy('policy_test_timestamptz');\n\n"
  },
  {
    "path": "test/sql/updates/cleanup.sparse_index.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nDROP TABLE IF EXISTS bloom;\n"
  },
  {
    "path": "test/sql/updates/cleanup.timestamp.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nDROP TABLE IF EXISTS PUBLIC.hyper_timestamp;\n"
  },
  {
    "path": "test/sql/updates/cleanup.v10.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\ir cleanup.v9.sql\n\\ir cleanup.sparse_index.sql\n"
  },
  {
    "path": "test/sql/updates/cleanup.v7.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\ir cleanup.bigint.sql\n\\ir cleanup.constraints.sql\n\\ir cleanup.timestamp.sql\n\\ir cleanup.continuous_aggs.v2.sql\n\\ir cleanup.compression.sql\n\\ir cleanup.policies.sql\n"
  },
  {
    "path": "test/sql/updates/cleanup.v8.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\ir cleanup.v7.sql\n\n"
  },
  {
    "path": "test/sql/updates/cleanup.v9.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\ir cleanup.v8.sql\n\\ir cleanup.chunk_skipping.sql\n"
  },
  {
    "path": "test/sql/updates/post.catalog.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nSELECT NOT (extversion >= '2.19.0' AND extversion <= '2.20.3') AS has_fixed_compression_algorithms\n  FROM pg_extension\n WHERE extname = 'timescaledb' \\gset\n\n\\if :PG_UPGRADE_TEST\n\\else\n\\d+ _timescaledb_catalog.hypertable\n\\d+ _timescaledb_catalog.chunk\n\\d+ _timescaledb_catalog.dimension\n\\d+ _timescaledb_catalog.dimension_slice\n\\d+ _timescaledb_catalog.chunk_constraint\n\\d+ _timescaledb_catalog.tablespace\n\\endif\n\n-- since we forgot to add bool and null compression with 2.19.0 to the preinstall\n-- script fresh installations of 2.19+ won't have these compression algorithms\n\\if :has_fixed_compression_algorithms\nSELECT * from _timescaledb_catalog.compression_algorithm algo ORDER BY algo;\n\\endif\n\nSELECT nspname AS Schema,\n       relname AS Name,\n       -- PG17 introduced MAINTAIN acl (m) so removed it to keep output backward compatible\n       replace(unnest(relacl)::text, 'm', '') as ACL\nFROM pg_class JOIN pg_namespace ns ON relnamespace = ns.oid\nWHERE nspname IN ('_timescaledb_catalog', '_timescaledb_config')\nORDER BY Schema, Name, ACL;\n\nSELECT nspname AS schema,\n       relname AS name,\n       -- PG17 introduced MAINTAIN acl (m) so removed it to keep output backward compatible\n       replace(unnest(initprivs)::text, 'm', '') AS initpriv\nFROM pg_class cl JOIN pg_namespace ns ON ns.oid = relnamespace\n            LEFT JOIN pg_init_privs ON objoid = cl.oid AND objsubid = 0\nWHERE classoid = 'pg_class'::regclass\n  AND nspname IN ('_timescaledb_catalog', '_timescaledb_config')\nORDER BY schema, name, initpriv;\n\n-- indexes in _timescaledb_catalog schema\nSELECT n.nspname as \"Schema\",\n  c.relname as \"Name\",\n  CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 't' THEN 'TOAST table' WHEN 'f' THEN 'foreign table' WHEN 'p' THEN 'partitioned table' WHEN 'I' THEN 'partitioned index' END as \"Type\",\n  pg_catalog.pg_get_userbyid(c.relowner) as \"Owner\",\n  c2.relname as \"Table\"\nFROM pg_catalog.pg_class c\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n     LEFT JOIN pg_catalog.pg_am am ON am.oid = c.relam\n     LEFT JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid\n     LEFT JOIN pg_catalog.pg_class c2 ON i.indrelid = c2.oid\nWHERE c.relkind IN ('i','I','')\n      AND n.nspname = '_timescaledb_catalog'\n  AND pg_catalog.pg_table_is_visible(c.oid)\nORDER BY 1,2;\n\n-- sequences in _timescaledb_catalog schema\nSELECT n.nspname as \"Schema\",\n  c.relname as \"Name\",\n  CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 't' THEN 'TOAST table' WHEN 'f' THEN 'foreign table' WHEN 'p' THEN 'partitioned table' WHEN 'I' THEN 'partitioned index' END as \"Type\",\n  pg_catalog.pg_get_userbyid(c.relowner) as \"Owner\",\n  CASE c.relpersistence WHEN 'p' THEN 'permanent' WHEN 't' THEN 'temporary' WHEN 'u' THEN 'unlogged' END as \"Persistence\",\n  pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) as \"Size\",\n  pg_catalog.obj_description(c.oid, 'pg_class') as \"Description\"\nFROM pg_catalog.pg_class c\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\nWHERE c.relkind IN ('S','')\n      AND n.nspname = '_timescaledb_catalog'\n  AND pg_catalog.pg_table_is_visible(c.oid)\nORDER BY 1,2;\n\n-- Functions in schemas:\n--   * _timescaledb_internal\n--   * _timescaledb_functions\n--   * public\nSELECT n.nspname as \"Schema\",\n  p.proname as \"Name\",\n  pg_catalog.pg_get_function_result(p.oid) as \"Result data type\",\n  pg_catalog.pg_get_function_arguments(p.oid) as \"Argument data types\",\n CASE p.prokind\n  WHEN 'a' THEN 'agg'\n  WHEN 'w' THEN 'window'\n  WHEN 'p' THEN 'proc'\n  ELSE 'func'\n END as \"Type\",\n CASE\n  WHEN p.provolatile = 'i' THEN 'immutable'\n  WHEN p.provolatile = 's' THEN 'stable'\n  WHEN p.provolatile = 'v' THEN 'volatile'\n END as \"Volatility\",\n CASE\n  WHEN p.proparallel = 'r' THEN 'restricted'\n  WHEN p.proparallel = 's' THEN 'safe'\n  WHEN p.proparallel = 'u' THEN 'unsafe'\n END as \"Parallel\",\n pg_catalog.pg_get_userbyid(p.proowner) as \"Owner\",\n CASE WHEN prosecdef THEN 'definer' ELSE 'invoker' END AS \"Security\",\n CASE WHEN pg_catalog.array_length(p.proacl, 1) = 0 THEN '(none)' ELSE pg_catalog.array_to_string(p.proacl, E'\\n') END AS \"Access privileges\",\n l.lanname as \"Language\",\n p.prosrc as \"Source code\",\n CASE WHEN l.lanname IN ('internal', 'c') THEN p.prosrc END as \"Internal name\",\n pg_catalog.obj_description(p.oid, 'pg_proc') as \"Description\"\nFROM pg_catalog.pg_proc p\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace\n     LEFT JOIN pg_catalog.pg_language l ON l.oid = p.prolang\nWHERE n.nspname OPERATOR(pg_catalog.~) '^(_timescaledb_internal|_timescaledb_functions|public)$' COLLATE pg_catalog.default\nORDER BY 1, 2, 4;\n\n\\dy\n\\a\n\\d public.*\n\\a\n\n-- Keep the output backward compatible\n\\if :PG_UPGRADE_TEST\n  SELECT oid AS extoid FROM pg_catalog.pg_extension WHERE extname = 'timescaledb' \\gset\n\n  WITH ext AS (\n    SELECT pg_catalog.pg_describe_object(classid, objid, 0) AS objdesc\n    FROM pg_catalog.pg_depend\n    WHERE refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass AND refobjid = :'extoid' AND deptype = 'e'\n    ORDER BY 1\n  )\n  SELECT objdesc AS \"Object description\" FROM ext\n  WHERE objdesc !~ '^type' OR objdesc ~ '^type _timescaledb_internal.(compressed_data|dimension_info)$'\n  ORDER BY 1;\n\n  WITH ext AS (\n    SELECT pg_catalog.pg_describe_object(classid, objid, 0) AS objdesc\n    FROM pg_catalog.pg_depend\n    WHERE refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass AND refobjid = :'extoid' AND deptype = 'e'\n    ORDER BY 1\n  )\n  SELECT count(*) FROM ext\n  WHERE objdesc !~ '^type' OR objdesc ~ '^type _timescaledb_internal.(compressed_data|dimension_info)$';\n\\else\n  \\dx+ timescaledb\n  SELECT count(*)\n    FROM pg_depend\n   WHERE refclassid = 'pg_extension'::regclass\n       AND refobjid = (SELECT oid FROM pg_extension WHERE extname = 'timescaledb');\n\\endif\n\n-- The list of tables configured to be dumped.\nSELECT unnest(extconfig)::regclass::text, unnest(extcondition) FROM pg_extension WHERE extname = 'timescaledb' ORDER BY 1;\n\n-- Show chunks that include owner in the output\nSELECT c.id, c.hypertable_id, c.schema_name, c.table_name, cl.relowner::regrole\nFROM  _timescaledb_catalog.chunk c\nINNER JOIN pg_class cl ON (cl.oid=format('%I.%I', schema_name, table_name)::regclass)\nORDER BY c.id, c.hypertable_id;\n\nSELECT chunk_constraint.* FROM _timescaledb_catalog.chunk_constraint\nJOIN _timescaledb_catalog.chunk ON chunk.id = chunk_constraint.chunk_id\nORDER BY chunk_constraint.chunk_id, chunk_constraint.dimension_slice_id, chunk_constraint.constraint_name;\n\n-- Show attnum of all regclass objects belonging to our extension\n-- if those are not the same between fresh install/update our update scripts are broken\nSELECT\n  att.attrelid::regclass,\n  att.attnum,\n  att.attname\nFROM pg_depend dep\n  INNER JOIN pg_extension ext ON (dep.refobjid=ext.oid AND ext.extname = 'timescaledb')\n  INNER JOIN pg_attribute att ON (att.attrelid=dep.objid AND att.attnum > 0)\nWHERE classid='pg_class'::regclass\nORDER BY attrelid::regclass::text,att.attnum;\n\n-- Show constraints\nSELECT conrelid::regclass::text, conname, pg_get_constraintdef(oid)\nFROM pg_constraint\nWHERE conrelid::regclass::text ~ '^_timescaledb_'\n\\if :PG_UPGRADE_TEST\nAND pg_get_constraintdef(oid) NOT LIKE 'NOT NULL %'\n\\endif\nORDER BY 1, 2, 3;\n\nSELECT * FROM _timescaledb_catalog.compression_settings ORDER BY relid::regclass;\n"
  },
  {
    "path": "test/sql/updates/post.chunk_skipping.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Show chunk column stats after updating the extension. We need to\n-- exclude the rows with chunk_id = 0 because we cannot keep those on\n-- downgrade due to FK constraint. Showing them would mean a diff in\n-- the output. We can still test that 0 chunk_ids are converted to\n-- NULL values during upgrades, however.\nSELECT * FROM _timescaledb_catalog.chunk_column_stats\nWHERE chunk_id IS NULL OR chunk_id > 0 ORDER BY id;\n"
  },
  {
    "path": "test/sql/updates/post.compression.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nSELECT * FROM compress ORDER BY time DESC, small_cardinality, large_cardinality, some_double, some_int, some_custom, some_bool;\n\n-- This recompression is necessary only for downgrades from 2.17 to 2.16.1\n-- due to downgrade migration requiring to add sequence number metadata\n-- column and causing compressed chunks to be unordered.\n-- Recompressing the chunks fully fixes the difference.\nSELECT count(decompress_chunk(ch, true)) FROM show_chunks('compress') ch;\nSELECT count(compress_chunk(ch, true)) FROM show_chunks('compress') ch;\n\n-- Running this query again to confirm data is consistent even after above recompression\nSELECT * FROM compress ORDER BY time DESC, small_cardinality, large_cardinality, some_double, some_int, some_custom, some_bool;\n\nINSERT INTO compress(time, small_cardinality, large_cardinality, some_double, some_int, some_custom, some_bool)\nSELECT g, 'QW', g::text, 2, 0, (100,4)::custom_type_for_compression, false\nFROM generate_series('2019-11-01 00:00'::timestamp, '2019-12-15 00:00'::timestamp, '1 day') g;\n\nSELECT count(compress_chunk(ch, true)) FROM show_chunks('compress') ch;\n\nSELECT * FROM compress ORDER BY time DESC, small_cardinality, large_cardinality, some_double, some_int, some_custom, some_bool;\n\n\\x on\nWITH hypertables AS (\n        SELECT ht.id hypertable_id,\n\t       ht.schema_name,\n\t       ht.table_name,\n\t       ht.compressed_hypertable_id\n          FROM pg_class cl JOIN pg_namespace ns ON ns.oid = relnamespace\n\t  JOIN _timescaledb_catalog.hypertable ht ON relname = ht.table_name AND nspname = ht.schema_name\n    ),\n    table_summary AS (\n\tSELECT format('%I.%I', ht1.schema_name, ht1.table_name) AS hypertable_name,\n\t       format('%I.%I', ht2.schema_name, ht2.table_name) AS compressed_hypertable_name,\n\t       format('%I.%I', ch2.schema_name, ch2.table_name) AS compressed_chunk_name\n\tFROM hypertables ht1\n\tJOIN hypertables ht2 ON ht1.compressed_hypertable_id = ht2.hypertable_id\n        JOIN _timescaledb_catalog.chunk ch2 ON ch2.hypertable_id = ht2.hypertable_id\n    )\nSELECT hypertable_name,\n       (SELECT relacl FROM pg_class WHERE oid = hypertable_name::regclass) AS hypertable_acl,\n       compressed_hypertable_name,\n       (SELECT relacl FROM pg_class WHERE oid = compressed_hypertable_name::regclass) AS compressed_hypertable_acl,\n       compressed_chunk_name,\n       (SELECT relacl FROM pg_class WHERE oid = compressed_chunk_name::regclass) AS compressed_chunk_acl\n  FROM table_summary\n  ORDER BY hypertable_name, compressed_hypertable_name, compressed_chunk_name;\n\\x off\n\n"
  },
  {
    "path": "test/sql/updates/post.continuous_aggs.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nSELECT\n\textversion < '2.0.0' AS has_refresh_mat_view\n  FROM pg_extension\n WHERE extname = 'timescaledb' \\gset\n\n\\if :has_refresh_mat_view\nREFRESH MATERIALIZED VIEW mat_before;\n\\else\nCALL refresh_continuous_aggregate('mat_before',NULL,NULL);\n\\endif\n\n\\x on\nSELECT * FROM mat_before ORDER BY bucket, location;\n\\x off\n\n--cause invalidations in the time range that is already\n--materialized. However, shift time by one second so that each\n--(timestamp, location) pair is unique. Otherwise last(temperature,\n--timec) won't be deterministic.\nINSERT INTO conditions_before\nSELECT generate_series('2018-12-01 00:01'::timestamp, '2018-12-31 00:01'::timestamp, '1 day'), 'POR', 165, 75, 40, 70, NULL, (1,2)::custom_type, 2, true;\n\n--cause invalidations way in the past\nINSERT INTO conditions_before\nSELECT generate_series('2017-12-01 00:01'::timestamp, '2017-12-31 00:01'::timestamp, '1 day'), 'POR', 1065, 75, 40, 70, NULL, (1,2)::custom_type, 2, true;\n\n\\x on\nSELECT * FROM mat_before ORDER BY bucket, location;\n\\x off\n\nCALL refresh_continuous_aggregate('mat_before',NULL,NULL);\n\n--the max of the temp for the POR should now be 165\n\\x on\nSELECT * FROM mat_before ORDER BY bucket, location;\n\\x off\n\n-- Output the ACLs for each internal cagg object\nSELECT cl.oid::regclass::text AS reloid,\n       unnest(relacl)::text AS relacl\nFROM _timescaledb_catalog.continuous_agg ca\nJOIN _timescaledb_catalog.hypertable h\nON (ca.mat_hypertable_id = h.id)\nJOIN pg_class cl\nON (cl.oid IN (format('%I.%I', h.schema_name, h.table_name)::regclass,\n               format('%I.%I', direct_view_schema, direct_view_name)::regclass,\n               format('%I.%I', partial_view_schema, partial_view_name)::regclass))\nORDER BY reloid, relacl;\n\n-- Output ACLs for chunks on materialized hypertables\nSELECT inhparent::regclass::text AS parent,\n       cl.oid::regclass::text AS chunk,\n       unnest(relacl)::text AS acl\nFROM _timescaledb_catalog.continuous_agg ca\nJOIN _timescaledb_catalog.hypertable h\nON (ca.mat_hypertable_id = h.id)\nJOIN pg_inherits inh ON (inh.inhparent = format('%I.%I', h.schema_name, h.table_name)::regclass)\nJOIN pg_class cl\nON (cl.oid = inh.inhrelid)\nORDER BY parent, chunk, acl;\n\n-- Verify privileges on internal cagg objects.  The privileges on the\n-- materialized hypertable, partial view, and direct view should match\n-- the user-facing user view.\nDO $$\nDECLARE\n    user_view_rel regclass;\n    user_view_acl aclitem[];\n    rel regclass;\n    acl aclitem[];\n    acl_matches boolean;\nBEGIN\n    FOR user_view_rel, user_view_acl IN\n        SELECT cl.oid, cl.relacl\n        FROM pg_class cl\n        JOIN _timescaledb_catalog.continuous_agg ca\n        ON (format('%I.%I', ca.user_view_schema, ca.user_view_name)::regclass = cl.oid)\n    LOOP\n        FOR rel, acl, acl_matches IN\n            SELECT cl.oid,\n                   cl.relacl,\n                   COALESCE(cl.relacl, ARRAY[]::aclitem[]) @> COALESCE(user_view_acl, ARRAY[]::aclitem[])\n            FROM _timescaledb_catalog.continuous_agg ca\n            JOIN _timescaledb_catalog.hypertable h\n            ON (ca.mat_hypertable_id = h.id)\n            JOIN pg_class cl\n            ON (cl.oid IN (format('%I.%I', h.schema_name, h.table_name)::regclass,\n                           format('%I.%I', direct_view_schema, direct_view_name)::regclass,\n                           format('%I.%I', partial_view_schema, partial_view_name)::regclass))\n            WHERE format('%I.%I', ca.user_view_schema, ca.user_view_name)::regclass = user_view_rel\n        LOOP\n            IF NOT acl_matches THEN\n               RAISE EXCEPTION 'privileges mismatch for continuous aggregate \"%\"', user_view_rel\n                     USING DETAIL = format('Privileges for internal object \"%s\" are [%s], expected [%s].',\n                            rel, acl, user_view_acl);\n            END IF;\n        END LOOP;\n    END LOOP;\nEND\n$$ LANGUAGE PLPGSQL;\n"
  },
  {
    "path": "test/sql/updates/post.continuous_aggs.v2.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\ir post.continuous_aggs.sql\n\n\\d cagg.*\n\n\\x on\nSELECT * FROM cagg.realtime_mat ORDER BY bucket, location;\n\\x off\n\nCALL refresh_continuous_aggregate('cagg.realtime_mat',NULL,NULL);\n\n\\x on\nSELECT * FROM cagg.realtime_mat ORDER BY bucket, location;\n\\x off\n\nSELECT view_name, materialized_only, materialization_hypertable_name\nFROM timescaledb_information.continuous_aggregates\nORDER BY view_name::text;\n\nSELECT schedule_interval\nFROM timescaledb_information.jobs\nORDER BY job_id;\n\nSELECT maxtemp FROM mat_ignoreinval ORDER BY 1;\n\nSELECT materialization_id FROM _timescaledb_catalog.continuous_aggs_materialization_invalidation_log\nWHERE lowest_modified_value = -9223372036854775808 ORDER BY 1;\n\nSELECT count(*) FROM mat_inval;\nCALL refresh_continuous_aggregate('mat_inval',NULL,NULL);\nSELECT pg_sleep(0.1); -- ensure refresh completes\nSELECT count(*) FROM mat_inval;\n"
  },
  {
    "path": "test/sql/updates/post.continuous_aggs.v3.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\ir post.continuous_aggs.v2.sql\n\n-- Ensure CAgg is refreshed\nSELECT\n\textversion < '2.0.0' AS has_refresh_mat_view\n  FROM pg_extension\n WHERE extname = 'timescaledb' \\gset\n\n\\if :has_refresh_mat_view\nREFRESH MATERIALIZED VIEW rename_cols;\n\\else\nCALL refresh_continuous_aggregate('rename_cols',NULL,NULL);\n\\endif\n\nSELECT \"time\", count(*) from rename_cols GROUP BY 1 ORDER BY 1;\n\n--verify compression can be enabled\nALTER MATERIALIZED VIEW rename_cols SET ( timescaledb.compress='true');\n\nSELECT \"time\", count(*) from rename_cols GROUP BY 1 ORDER BY 1;\n"
  },
  {
    "path": "test/sql/updates/post.functions.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- check all functions point to the correct library\nSELECT\n  oid::REGPROCEDURE,\n  probin\nFROM\n  pg_proc\nWHERE\n  probin LIKE '%timescale%'\nORDER BY\n  oid::REGPROCEDURE::TEXT;\n\n"
  },
  {
    "path": "test/sql/updates/post.insert.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- INSERT data to create a new chunk after update or restore.\nINSERT INTO devices(id,floor) VALUES\n('dev5', 5);\nINSERT INTO \"two_Partitions\"(\"timeCustom\", device_id, device_id_2, series_0, series_1, series_2) VALUES\n(1258894000000000000, 'dev5', 'dev1', 2.2, 1, 2);\n\n"
  },
  {
    "path": "test/sql/updates/post.integrity_test.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- We do not dump the size of the tables here since that might differ\n-- between an updated node and a restored node. For examples, stats\n-- tables can have different sizes, and this is not relevant for an\n-- update test.\n\\dt _timescaledb_internal.*\n\nCREATE OR REPLACE FUNCTION timescaledb_integrity_test()\n    RETURNS VOID LANGUAGE PLPGSQL STABLE AS\n$BODY$\nDECLARE\n    dimension_slice RECORD;\n    constraint_row RECORD;\n    index_row      RECORD;\n    chunk_count    INTEGER;\n    chunk_constraint_count INTEGER;\nBEGIN\n    -- Check integrity of chunk_constraints\n    FOR constraint_row IN\n    SELECT c.conname, h.id AS hypertable_id FROM _timescaledb_catalog.hypertable h INNER JOIN\n           pg_constraint c ON (c.conrelid = format('%I.%I', h.schema_name, h.table_name)::regclass)\n        WHERE c.contype NOT IN ('c','n')\n    LOOP\n        SELECT count(*) FROM _timescaledb_catalog.chunk c\n        WHERE c.hypertable_id = constraint_row.hypertable_id\n        INTO STRICT chunk_count;\n\n        SELECT count(cc.*) FROM _timescaledb_catalog.chunk_constraint cc,\n        _timescaledb_catalog.chunk c\n        WHERE hypertable_constraint_name = constraint_row.conname\n        AND c.id = cc.chunk_id\n        AND c.hypertable_id = constraint_row.hypertable_id\n        INTO STRICT chunk_constraint_count;\n\n        IF chunk_constraint_count != chunk_count THEN\n           RAISE EXCEPTION 'Missing chunk constraints for %. Expected %, but found %', constraint_row.conname, chunk_count, chunk_constraint_count;\n        END IF;\n    END LOOP;\n\n    FOR dimension_slice IN\n    SELECT chunk_id, dimension_slice_id FROM _timescaledb_catalog.chunk_constraint\n     WHERE dimension_slice_id NOT IN (SELECT id FROM _timescaledb_catalog.dimension_slice)\n    LOOP\n      RAISE EXCEPTION 'Missing dimension slice with id % for chunk %.', dimension_slice.dimension_slice_id, dimension_slice.chunk_id;\n    END LOOP;\nEND;\n$BODY$;\n\nSELECT timescaledb_integrity_test();\n\n-- Verify that the default jobs are the same in bgw_job\nSELECT relnamespace::regnamespace \"JOB_SCHEMA\" FROM pg_class WHERE relname='bgw_job' and relkind = 'r' \\gset\nSELECT id, application_name FROM :JOB_SCHEMA.bgw_job ORDER BY id;\n\n"
  },
  {
    "path": "test/sql/updates/post.pg_upgrade.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\pset format aligned\n\\pset tuples_only off\n\n\\set PG_UPGRADE_TEST true\n\\ir post.catalog.sql\n\\unset PG_UPGRADE_TEST\n\\ir post.policies.sql\n\\ir post.functions.sql\n\nSELECT * FROM cagg_join.measurement_daily ORDER BY 1, 2, 3, 4, 5, 6;\n"
  },
  {
    "path": "test/sql/updates/post.policies.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- SELECT * FROM _timescaledb_config.bgw_job WHERE id <> 1 ORDER BY id;\nSELECT relnamespace::regnamespace \"JOB_SCHEMA\" FROM pg_class WHERE relname='bgw_job' and relkind = 'r' \\gset\nSELECT id, application_name, schedule_interval, max_runtime, max_retries, retry_period, proc_schema, proc_name, owner, scheduled, hypertable_id, config FROM :JOB_SCHEMA.bgw_job WHERE id <> 1 ORDER BY id;\n\n"
  },
  {
    "path": "test/sql/updates/post.repair.hierarchical_cagg.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nSELECT count(*) FROM agg_test_monthly;\n\n"
  },
  {
    "path": "test/sql/updates/post.repair.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\z repair_test_int\n\\z repair_test_extra\n\n\n"
  },
  {
    "path": "test/sql/updates/post.sequences.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- check sequences did not get reset\nSELECT seqrelid::regclass,\n  nextval(seqrelid),\n  seqstart,\n  seqincrement,\n  seqmax,\n  seqmin\nFROM pg_sequence\nORDER BY seqrelid::regclass::text;\n\n"
  },
  {
    "path": "test/sql/updates/post.sparse_index.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Show chunk column stats after updating the extension. We need to\n-- exclude the rows with chunk_id = 0 because we cannot keep those on\n-- downgrade due to FK constraint. Showing them would mean a diff in\n-- the output. We can still test that 0 chunk_ids are converted to\n-- NULL values during upgrades, however.\nSELECT * FROM _timescaledb_catalog.compression_settings ORDER BY relid::regclass::text;\n\nSELECT\n    schema_name || '.' || table_name AS chunk\nFROM _timescaledb_catalog.chunk\nWHERE id = (\n    SELECT compressed_chunk_id\n    FROM _timescaledb_catalog.chunk\n    WHERE hypertable_id = (\n        SELECT id\n        FROM _timescaledb_catalog.hypertable\n        WHERE table_name = 'bloom'\n    )\n    LIMIT 1\n)\n\\gset\n\n-- Note: we can't use \\d+ here because it prevents changing the TOAST storage flag\n--   if we do, like in the UUID compression changes from external to extended, then\n--   the upgrade tests fail\n\\a\n\\d :chunk\n\\a\n\n"
  },
  {
    "path": "test/sql/updates/post.v10.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\ir post.v9.sql\n\\ir post.sparse_index.sql\n"
  },
  {
    "path": "test/sql/updates/post.v7.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\set PG_UPGRADE_TEST false\n\\ir post.catalog.sql\n\\unset PG_UPGRADE_TEST\n\\ir post.insert.sql\n\\ir post.integrity_test.sql\n\\ir catalog_missing_columns.sql\n\\ir post.compression.sql\n\\ir post.continuous_aggs.v2.sql\n\\ir post.policies.sql\n\\if :WITH_SUPERUSER\n\\ir post.sequences.sql\n\\endif\n\\ir post.functions.sql\n"
  },
  {
    "path": "test/sql/updates/post.v8.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\set PG_UPGRADE_TEST false\n\\ir post.catalog.sql\n\\unset PG_UPGRADE_TEST\n\\ir post.insert.sql\n\\ir post.integrity_test.sql\n\\ir catalog_missing_columns.sql\n\\ir post.compression.sql\n\\ir post.continuous_aggs.v3.sql\n\\ir post.policies.sql\n\\ir post.sequences.sql\n\\ir post.functions.sql\n"
  },
  {
    "path": "test/sql/updates/post.v9.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\ir post.v8.sql\n\\ir post.chunk_skipping.sql\n"
  },
  {
    "path": "test/sql/updates/pre.cleanup.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Clean up objects that are created by the setup files. Ideally, we\n-- should clean them up in each post.*.sql file after generating the\n-- output, but now we do it here.\n\nSET client_min_messages TO WARNING;\n\nDROP MATERIALIZED VIEW IF EXISTS mat_inval CASCADE;\nDROP MATERIALIZED VIEW IF EXISTS mat_drop CASCADE;\nDROP MATERIALIZED VIEW IF EXISTS mat_before CASCADE;\nDROP MATERIALIZED VIEW IF EXISTS mat_conflict CASCADE;\nDROP MATERIALIZED VIEW IF EXISTS mat_inttime CASCADE;\nDROP MATERIALIZED VIEW IF EXISTS mat_inttime2 CASCADE;\nDROP MATERIALIZED VIEW IF EXISTS mat_ignoreinval CASCADE;\nDROP MATERIALIZED VIEW IF EXISTS cagg.realtime_mat CASCADE;\n\nDROP TABLE IF EXISTS public.hyper_timestamp;\nDROP TABLE IF EXISTS public.\"two_Partitions\";\nDROP TABLE IF EXISTS conditions_before;\nDROP TABLE IF EXISTS inval_test;\nDROP TABLE IF EXISTS int_time_test;\nDROP TABLE IF EXISTS conflict_test;\nDROP TABLE IF EXISTS drop_test;\nDROP TABLE IF EXISTS repair_test_timestamptz;\nDROP TABLE IF EXISTS repair_test_int;\nDROP TABLE IF EXISTS repair_test_extra;\nDROP TABLE IF EXISTS repair_test_timestamp;\nDROP TABLE IF EXISTS repair_test_date;\nDROP TABLE IF EXISTS compress;\nDROP TABLE IF EXISTS skip;\nDROP TABLE IF EXISTS devices;\nDROP TABLE IF EXISTS disthyper;\nDROP TABLE IF EXISTS policy_test_timestamptz;\n\nDROP TYPE IF EXISTS custom_type;\nDROP TYPE IF EXISTS custom_type_for_compression;\n\nDROP PROCEDURE IF EXISTS _timescaledb_testing.restart_dimension_slice_id;\nDROP PROCEDURE IF EXISTS _timescaledb_testing.stop_workers;\n\nDROP FUNCTION IF EXISTS timescaledb_integrity_test;\nDROP FUNCTION IF EXISTS timescaledb_catalog_has_no_missing_columns;\nDROP FUNCTION IF EXISTS integer_now_test;\n\nDROP SCHEMA IF EXISTS cagg;\nDROP SCHEMA IF EXISTS _timescaledb_testing;\n\nRESET client_min_messages;\n\n"
  },
  {
    "path": "test/sql/updates/pre.smoke.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- These functions are used when running smoke tests. For smoke tests\n-- we assume that we do not have SUPER privileges.\n\nCREATE SCHEMA IF NOT EXISTS _timescaledb_testing;\n\nCREATE PROCEDURE _timescaledb_testing.restart_dimension_slice_id() LANGUAGE SQL AS '';\n\nCREATE PROCEDURE _timescaledb_testing.stop_workers() LANGUAGE SQL AS '';\n"
  },
  {
    "path": "test/sql/updates/pre.testing.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- These functions are used when running normal update tests.\n\nCREATE SCHEMA IF NOT EXISTS _timescaledb_testing;\n\nCREATE OR REPLACE PROCEDURE _timescaledb_testing.restart_dimension_slice_id()\nLANGUAGE SQL\nAS $$\n   ALTER SEQUENCE _timescaledb_catalog.dimension_slice_id_seq RESTART WITH 100;\n$$;\n\nCREATE OR REPLACE PROCEDURE _timescaledb_testing.stop_workers()\nLANGUAGE PLPGSQL\nAS $$\nBEGIN\n   IF EXISTS (SELECT FROM pg_proc WHERE proname='stop_background_workers' AND pronamespace='_timescaledb_internal'::regnamespace)\n   THEN PERFORM _timescaledb_internal.stop_background_workers();\n   ELSE PERFORM _timescaledb_functions.stop_background_workers();\n   END IF;\nEND\n$$;\n\n"
  },
  {
    "path": "test/sql/updates/setup.bigint.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE TABLE PUBLIC.\"two_Partitions\" (\n  \"timeCustom\" BIGINT NOT NULL,\n  device_id TEXT NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL,\n  series_bool BOOLEAN NULL,\n  UNIQUE(\"timeCustom\", device_id, series_2)\n);\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (device_id, \"timeCustom\" DESC NULLS LAST) WHERE device_id IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_0) WHERE series_0 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_1)  WHERE series_1 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_2) WHERE series_2 IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, series_bool) WHERE series_bool IS NOT NULL;\nCREATE INDEX ON PUBLIC.\"two_Partitions\" (\"timeCustom\" DESC NULLS LAST, device_id);\n\nDO $$\nBEGIN\n  IF (EXISTS (SELECT FROM pg_proc WHERE proname = 'interval_to_usec' AND pronamespace='_timescaledb_internal'::regnamespace))\n  THEN\n    PERFORM create_hypertable('\"public\".\"two_Partitions\"'::regclass, 'timeCustom'::name, 'device_id'::name, associated_schema_name=>'_timescaledb_internal'::text, number_partitions => 2, chunk_time_interval=>_timescaledb_internal.interval_to_usec('1 month'));\n  ELSE\n    PERFORM create_hypertable('\"public\".\"two_Partitions\"'::regclass, 'timeCustom'::name, 'device_id'::name, associated_schema_name=>'_timescaledb_internal'::text, number_partitions => 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n  END IF;\nEND;\n$$;\n\n\n"
  },
  {
    "path": "test/sql/updates/setup.catalog.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Catalog tables are occasionally rewritten as part of updates, so\n-- this is to test that privileges are maintained over updates of the\n-- extension. We could verify that other properties (e.g., comments)\n-- are maintained here as well, but this is not something we use right\n-- now.\n--\n-- We do not alter the privileges on _timescaledb_internal since this\n-- affects both internal objects and two tables that are metadata\n-- placed in the _timescaledb_internal schema.\n\nGRANT SELECT ON ALL TABLES IN SCHEMA _timescaledb_catalog TO tsdbadmin;\nGRANT SELECT ON ALL SEQUENCES IN SCHEMA _timescaledb_catalog TO tsdbadmin;\n\nALTER DEFAULT PRIVILEGES IN SCHEMA _timescaledb_catalog\n      GRANT SELECT ON TABLES TO tsdbadmin;\nALTER DEFAULT PRIVILEGES IN SCHEMA _timescaledb_catalog\n    GRANT SELECT ON SEQUENCES TO tsdbadmin;\n\nDO $$\nBEGIN\n  IF EXISTS(SELECT FROM pg_namespace WHERE nspname = '_timescaledb_config') THEN\n    GRANT SELECT ON ALL TABLES IN SCHEMA _timescaledb_config TO tsdbadmin;\n    GRANT SELECT ON ALL SEQUENCES IN SCHEMA _timescaledb_config TO tsdbadmin;\n    ALTER DEFAULT PRIVILEGES IN SCHEMA _timescaledb_config GRANT SELECT ON TABLES TO tsdbadmin;\n    ALTER DEFAULT PRIVILEGES IN SCHEMA _timescaledb_config GRANT SELECT ON SEQUENCES TO tsdbadmin;\n  END IF;\nEND\n$$;\n"
  },
  {
    "path": "test/sql/updates/setup.check.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\echo **** Missing dimension slices ****\nSELECT hypertable_id,\n       (\n\t   SELECT format('%I.%I', schema_name, table_name)::regclass\n\t   FROM _timescaledb_catalog.hypertable ht\n\t   WHERE ht.id = ch.hypertable_id\n       ) AS hypertable,\n       chunk_id,\n       dimension_slice_id,\n       constraint_name,\n       attname AS column_name,\n       pg_get_expr(conbin, conrelid) AS constraint_expr\nFROM _timescaledb_catalog.chunk_constraint cc\nJOIN _timescaledb_catalog.chunk ch ON cc.chunk_id = ch.id\nJOIN pg_constraint ON conname = constraint_name\nJOIN pg_namespace ns ON connamespace = ns.oid AND ns.nspname = ch.schema_name\nJOIN pg_attribute ON attnum = conkey[1] AND attrelid = conrelid\nWHERE dimension_slice_id NOT IN (SELECT id FROM _timescaledb_catalog.dimension_slice);\n"
  },
  {
    "path": "test/sql/updates/setup.chunk_skipping.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\nCREATE TABLE chunkskip (\n      time                  TIMESTAMPTZ         NOT NULL,\n      updated_at            TIMESTAMPTZ         NOT NULL,\n      location              INT,\n      temp                  FLOAT\n    );\n\nSELECT setseed(0.1);\nSELECT table_name FROM create_hypertable( 'chunkskip', 'time');\n\nINSERT INTO chunkskip\nSELECT g, g+interval '1 hour', ceil(random()*20), random()*30\nFROM generate_series('2018-12-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day') g;\n\nALTER TABLE chunkskip SET (timescaledb.compress, timescaledb.compress_segmentby='location', timescaledb.compress_orderby='\"time\" desc');\nSELECT count(compress_chunk(ch, true)) FROM show_chunks('chunkskip') ch;\n\nDO $$\nDECLARE\n  version text[] := (SELECT regexp_split_to_array(extversion,'(\\.|-)') FROM pg_extension WHERE extname = 'timescaledb');\nBEGIN\n\n  -- Enable chunk skipping. Doing this after compression to have a\n  -- \"special\" chunk_id entry of 0. We check that this is changed to\n  -- NULL during upgrade.\n  IF version[1]::int >= 2 AND version[2]::int >= 17 AND (version[2]::int > 17 OR version[3]::int > 0) THEN\n     SET timescaledb.enable_chunk_skipping = true;\n  END IF;\nEND\n$$;\n\nSELECT enable_chunk_skipping('chunkskip', 'updated_at');\n"
  },
  {
    "path": "test/sql/updates/setup.compression.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE TYPE custom_type_for_compression AS (high int, low int);\n\nCREATE TABLE compress (\n      time                  TIMESTAMPTZ         NOT NULL,\n      small_cardinality     TEXT                NULL,\n      large_cardinality     TEXT                NULL,\n      dropped               TEXT                NULL,\n      some_double           DOUBLE PRECISION    NULL,\n      some_int              integer             NULL,\n      some_custom           custom_type_for_compression         NULL,\n      some_bool             boolean             NULL\n    );\n\nSELECT table_name FROM create_hypertable( 'compress', 'time');\n\nINSERT INTO compress\nSELECT g, 'POR', g::text, 'lint', 75.0, 40, (1,2)::custom_type_for_compression, true\nFROM generate_series('2018-12-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day') g;\n\nINSERT INTO compress\nSELECT g, 'POR', NULL, 'lint', NULL, NULL, NULL, NULL\nFROM generate_series('2018-11-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day') g;\n\nINSERT INTO compress\nSELECT g, 'POR', g::text, 'lint', 94.0, 45, (3,4)::custom_type_for_compression, true\nFROM generate_series('2018-11-01 00:00'::timestamp, '2018-12-15 00:00'::timestamp, '1 day') g;\n\nDO $$\nDECLARE\n  ts_minor int := (SELECT (string_to_array(extversion,'.'))[2]::int FROM pg_extension WHERE extname = 'timescaledb');\nBEGIN\n\n  -- support for dropping columns on compressed hypertables was added in 2.10.0\n  -- so we drop before compressing if the version is before 2.10.0\n  IF ts_minor < 10 THEN\n    ALTER TABLE compress DROP COLUMN dropped;\n  END IF;\n\n  ALTER TABLE compress SET (timescaledb.compress, timescaledb.compress_segmentby='small_cardinality', timescaledb.compress_orderby='\"time\" desc');\n  PERFORM count(compress_chunk(ch, true)) FROM show_chunks('compress') ch;\n\n  IF ts_minor >= 10 THEN\n    ALTER TABLE compress DROP COLUMN dropped;\n  END IF;\nEND\n$$;\n\n\\if :WITH_ROLES\nGRANT SELECT ON compress TO tsdbadmin;\n\\endif\n"
  },
  {
    "path": "test/sql/updates/setup.constraints.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Secondary devices table to test foreign keys in \"two_Partitions\"\nCREATE TABLE devices (\n    id TEXT PRIMARY KEY,\n    floor INTEGER\n);\n\nINSERT INTO devices(id,floor) VALUES\n('dev1', 1),\n('dev2', 2),\n('dev3', 3);\n\n-- Setup \"two_Partitions\" to use foreign key constraints\nALTER TABLE  \"two_Partitions\" ADD COLUMN device_id_2 TEXT NOT NULL;\n\nALTER TABLE \"two_Partitions\" ADD CONSTRAINT two_Partitions_device_id_2_fkey\nFOREIGN KEY (device_id_2) REFERENCES devices(id);\n"
  },
  {
    "path": "test/sql/updates/setup.continuous_aggs.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Collect information about different features so that we can pick\n-- the right usage. Some of these are changed in the same version, but\n-- we keep them separate anyway so that we can do additional checking\n-- if necessary.\n\n-- disable background workers to prevent deadlocks between background processes\n-- on timescaledb 1.7.x\nCALL _timescaledb_testing.stop_workers();\n\n-- disable chunkwise aggregation and hash aggregation, because it might lead to\n-- different order of chunk creation in the cagg table, based on the underlying\n-- aggregation plan.\nSET timescaledb.enable_chunkwise_aggregation TO OFF;\nSET enable_hashagg TO OFF;\n\nCREATE TYPE custom_type AS (high int, low int);\n\nCREATE TABLE conditions_before (\n      timec       TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null,\n      highlow     custom_type null,\n      bit_int     smallint,\n      good_life   boolean\n    );\n\nSELECT table_name FROM create_hypertable( 'conditions_before', 'timec');\n\nINSERT INTO conditions_before\nSELECT generate_series('2018-12-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'POR', 55, 75, 40, 70, NULL, (1,2)::custom_type, 2, true;\nINSERT INTO conditions_before\nSELECT generate_series('2018-11-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'NYC', 35, 45, 50, 40, NULL, (3,4)::custom_type, 4, false;\nINSERT INTO conditions_before\nSELECT generate_series('2018-11-01 00:00'::timestamp, '2018-12-15 00:00'::timestamp, '1 day'), 'LA', 73, 55, NULL, 28, NULL, NULL, 8, true;\n\n-- rename_cols cagg view is also used for another test: if we can enable\n-- compression on a cagg after an upgrade\n-- This view has 3 cols which is fewer than the number of cols on the table\n-- we had a bug related to that and need to verify if compression can be\n-- enabled on such a view\nCREATE MATERIALIZED VIEW rename_cols\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket('1 week', timec) AS bucket,\n       location,\n round(avg(humidity)) AS humidity\nFROM conditions_before\nGROUP BY bucket, location\nWITH NO DATA;\n\nCREATE MATERIALIZED VIEW IF NOT EXISTS mat_before\nWITH ( timescaledb.continuous, timescaledb.materialized_only=true)\nAS\nSELECT time_bucket('1week', timec) as bucket,\n\tlocation,\n\tround(min(allnull)) as min_allnull,\n\tround(max(temperature)) as max_temp,\n\tround(sum(temperature)+sum(humidity)) as agg_sum_expr,\n\tround(avg(humidity)) AS avg_humidity,\n\tround(stddev(humidity)) as stddev,\n\tbit_and(bit_int),\n\tbit_or(bit_int),\n\tbool_and(good_life),\n\tevery(temperature > 0),\n\tbool_or(good_life),\n\tcount(*) as count_rows,\n\tcount(temperature) as count_temp,\n\tcount(allnull) as count_zero,\n\tround(corr(temperature, humidity)) as corr,\n\tround(covar_pop(temperature, humidity)) as covar_pop,\n\tround(covar_samp(temperature, humidity)) as covar_samp,\n\tround(regr_avgx(temperature, humidity)) as regr_avgx,\n\tround(regr_avgy(temperature, humidity)) as regr_avgy,\n\tround(regr_count(temperature, humidity)) as regr_count,\n\tround(regr_intercept(temperature, humidity)) as regr_intercept,\n\tround(regr_r2(temperature, humidity)) as regr_r2,\n\tround(regr_slope(temperature, humidity)) as regr_slope,\n\tround(regr_sxx(temperature, humidity)) as regr_sxx,\n\tround(regr_sxy(temperature, humidity)) as regr_sxy,\n\tround(regr_syy(temperature, humidity)) as regr_syy,\n\tround(stddev(temperature)) as stddev_temp,\n\tround(stddev_pop(temperature)) as stddev_pop,\n\tround(stddev_samp(temperature)) as stddev_samp,\n\tround(variance(temperature)) as variance,\n\tround(var_pop(temperature)) as var_pop,\n\tround(var_samp(temperature)) as var_samp,\n\tlast(temperature, timec) as last_temp,\n\tlast(highlow, timec) as last_hl,\n\tfirst(highlow, timec) as first_hl,\n\thistogram(temperature, 0, 100, 5)\nFROM conditions_before\nGROUP BY bucket, location\nHAVING min(location) >= 'NYC' and avg(temperature) > 2 WITH NO DATA;\n\nALTER MATERIALIZED VIEW rename_cols RENAME COLUMN bucket TO \"time\";\n\n\\if :WITH_SUPERUSER\nGRANT SELECT ON mat_before TO cagg_user WITH GRANT OPTION;\n\\endif\n\nCALL refresh_continuous_aggregate('rename_cols',NULL,NULL);\nCALL refresh_continuous_aggregate('mat_before',NULL,NULL);\n\n-- we create separate schema for realtime agg since we dump all view definitions in public schema\n-- but realtime agg view definition is not stable across versions\nCREATE SCHEMA cagg;\n\nCREATE MATERIALIZED VIEW IF NOT EXISTS cagg.realtime_mat\nWITH ( timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT time_bucket('1week', timec) as bucket,\n\tlocation,\n\tround(min(allnull)) as min_allnull,\n\tround(max(temperature)) as max_temp,\n\tround(sum(temperature)+sum(humidity)) as agg_sum_expr,\n\tround(avg(humidity)) AS avg_humidity,\n\tround(stddev(humidity)) as stddev_humidity,\n\tbit_and(bit_int),\n\tbit_or(bit_int),\n\tbool_and(good_life),\n\tevery(temperature > 0),\n\tbool_or(good_life),\n\tcount(*) as count_rows,\n\tcount(temperature) as count_temp,\n\tcount(allnull) as count_zero,\n\tround(corr(temperature, humidity)) as corr,\n\tround(covar_pop(temperature, humidity)) as covar_pop,\n\tround(covar_samp(temperature, humidity)) as covar_samp,\n\tround(regr_avgx(temperature, humidity)) as regr_avgx,\n\tround(regr_avgy(temperature, humidity)) as regr_avgy,\n\tround(regr_count(temperature, humidity)) as regr_count,\n\tround(regr_intercept(temperature, humidity)) as regr_intercept,\n\tround(regr_r2(temperature, humidity)) as regr_r2,\n\tround(regr_slope(temperature, humidity)) as regr_slope,\n\tround(regr_sxx(temperature, humidity)) as regr_sxx,\n\tround(regr_sxy(temperature, humidity)) as regr_sxy,\n\tround(regr_syy(temperature, humidity)) as regr_syy,\n\tround(stddev(temperature)) as stddev_temp,\n\tround(stddev_pop(temperature)) as stddev_pop,\n\tround(stddev_samp(temperature)) as stddev_samp,\n\tround(variance(temperature)) as variance,\n\tround(var_pop(temperature)) as var_pop,\n\tround(var_samp(temperature)) as var_samp,\n\tlast(temperature, timec) as last_temp,\n\tlast(highlow, timec) as last_hl,\n\tfirst(highlow, timec) as first_hl,\n\thistogram(temperature, 0, 100, 5)\nFROM conditions_before\nGROUP BY bucket, location\nHAVING min(location) >= 'NYC' and avg(temperature) > 2 WITH NO DATA;\n\n\\if :WITH_SUPERUSER\nGRANT SELECT ON cagg.realtime_mat TO cagg_user;\n\\endif\n\nCALL refresh_continuous_aggregate('cagg.realtime_mat',NULL,NULL);\n\n-- test ignore_invalidation_older_than migration --\nCREATE MATERIALIZED VIEW IF NOT EXISTS  mat_ignoreinval\nWITH ( timescaledb.continuous, timescaledb.materialized_only=true)\nAS\n  SELECT time_bucket('1 week', timec) as bucket,\n    max(temperature) as maxtemp\n  FROM conditions_before\n  GROUP BY bucket WITH NO DATA;\n\nSELECT add_continuous_aggregate_policy('mat_ignoreinval', '30 days'::interval, '-30 days'::interval, '336 h');\n\nCALL refresh_continuous_aggregate('mat_ignoreinval',NULL,NULL);\n\n-- test new data beyond the invalidation threshold is properly handled --\nCREATE TABLE inval_test (time TIMESTAMPTZ NOT NULL, location TEXT, temperature DOUBLE PRECISION);\nSELECT create_hypertable('inval_test', 'time', chunk_time_interval => INTERVAL '1 week');\n\nINSERT INTO inval_test\nSELECT generate_series('2018-12-01 00:00'::timestamp, '2018-12-20 00:00'::timestamp, '1 day'), 'POR', generate_series(40.5, 50.0, 0.5);\nINSERT INTO inval_test\nSELECT generate_series('2018-12-01 00:00'::timestamp, '2018-12-20 00:00'::timestamp, '1 day'), 'NYC', generate_series(31.0, 50.0, 1.0);\n\nCREATE MATERIALIZED VIEW mat_inval\nWITH ( timescaledb.continuous, timescaledb.materialized_only=true )\nAS\n  SELECT time_bucket('10 minute', time) as bucket, location, min(temperature) as min_temp,\n    max(temperature) as max_temp, round(avg(temperature)) as avg_temp\n  FROM inval_test\n  GROUP BY bucket, location WITH NO DATA;\n\nSELECT add_continuous_aggregate_policy('mat_inval', NULL, '-20 days'::interval, '12 hours');\n\nCALL refresh_continuous_aggregate('mat_inval',NULL,NULL);\n\nINSERT INTO inval_test\nSELECT generate_series('2118-12-01 00:00'::timestamp, '2118-12-20 00:00'::timestamp, '1 day'), 'POR', generate_series(135.25, 140.0, 0.25);\nINSERT INTO inval_test\nSELECT generate_series('2118-12-01 00:00'::timestamp, '2118-12-20 00:00'::timestamp, '1 day'), 'NYC', generate_series(131.0, 150.0, 1.0);\n\n-- Add an integer base table to ensure we handle it correctly\nCREATE TABLE int_time_test(timeval integer not null, col1 integer, col2 integer);\nselect create_hypertable('int_time_test', 'timeval', chunk_time_interval=> 2);\n\nCREATE OR REPLACE FUNCTION integer_now_test() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(timeval), 0) FROM public.int_time_test $$;\nSELECT set_integer_now_func('int_time_test', 'integer_now_test');\n\nINSERT INTO int_time_test VALUES\n(10, - 4, 1), (11, - 3, 5), (12, - 3, 7), (13, - 3, 9), (14,-4, 11),\n(15, -4, 22), (16, -4, 23);\n\nCREATE MATERIALIZED VIEW mat_inttime\nWITH ( timescaledb.continuous, timescaledb.materialized_only=true )\nAS\n  SELECT time_bucket( 2, timeval), COUNT(col1)\n  FROM int_time_test\n  GROUP BY 1 WITH NO DATA;\n\nCREATE MATERIALIZED VIEW mat_inttime2\nWITH ( timescaledb.continuous, timescaledb.materialized_only=true )\nAS\n  SELECT time_bucket( 2, timeval), COUNT(col1)\n  FROM int_time_test\n  GROUP BY 1 WITH NO DATA;\n\nSELECT add_continuous_aggregate_policy('mat_inttime', 6, 2, '12 hours');\nSELECT add_continuous_aggregate_policy('mat_inttime2', NULL, 2, '12 hours');\n\nCALL refresh_continuous_aggregate('mat_inttime',NULL,NULL);\nCALL refresh_continuous_aggregate('mat_inttime2',NULL,NULL);\n\n-- Test that retention policies that conflict with continuous aggs are disabled --\nCREATE TABLE conflict_test (time TIMESTAMPTZ NOT NULL, location TEXT, temperature DOUBLE PRECISION);\nSELECT create_hypertable('conflict_test', 'time', chunk_time_interval => INTERVAL '1 week');\n\nCREATE MATERIALIZED VIEW mat_conflict\nWITH ( timescaledb.continuous, timescaledb.materialized_only=true )\nAS\n  SELECT time_bucket('10 minute', time) as bucket, location, min(temperature) as min_temp,\n    max(temperature) as max_temp, round(avg(temperature)) as avg_temp\n  FROM conflict_test\n  GROUP BY bucket, location WITH NO DATA;\n\nSELECT add_continuous_aggregate_policy('mat_conflict', '28 days', '1 day', '12 hours');\nSELECT add_retention_policy('conflict_test', '14 days'::interval) AS retention_jobid \\gset\nSELECT alter_job(:retention_jobid, scheduled=>false);\n\n\\if :WITH_SUPERUSER\nGRANT SELECT, TRIGGER, UPDATE\nON mat_conflict TO cagg_user\nWITH GRANT OPTION;\n\\endif\n\n-- Test that calling drop chunks on the hypertable does not break the\n-- update process when chunks are marked as dropped rather than\n-- removed. This happens when a continuous aggregate is defined on the\n-- hypertable, so we create a hypertable and a continuous aggregate\n-- here and then drop chunks from the hypertable and make sure that\n-- the update from 1.7 to 2.0 works as expected.\nCREATE TABLE drop_test (\n    time timestamptz not null,\n    location INT,\n    temperature double PRECISION\n);\n\nSELECT create_hypertable ('drop_test', 'time', chunk_time_interval => interval '1 week');\n\nINSERT INTO drop_test\nSELECT\n    time,\n    (random() * 3 + 1)::int,\n    random() * 100.0\nFROM\n    generate_series(now() - interval '28 days', now(), '1 hour') AS time;\n\nCREATE MATERIALIZED VIEW mat_drop\nWITH (\n     timescaledb.materialized_only = TRUE,\n     timescaledb.continuous\n) AS\nSELECT\n    time_bucket ('10 minute',time) AS bucket,\n    LOCATION,\n    min(temperature) AS min_temp,\n    max(temperature) AS max_temp,\n    round(avg(temperature)) AS avg_temp\nFROM\n    drop_test\nGROUP BY\n    bucket,\n    LOCATION;\n\nSELECT add_continuous_aggregate_policy('mat_drop', '7 days', '-30 days'::interval, '20 min');\n\nCALL refresh_continuous_aggregate('mat_drop',NULL,NULL);\n\nSELECT drop_chunks('drop_test', NOW() - INTERVAL '7 days');\n\nRESET timescaledb.enable_chunkwise_aggregation;\nRESET enable_hashagg;\n"
  },
  {
    "path": "test/sql/updates/setup.databases.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE DATABASE single;\n\n\\c single\nCREATE EXTENSION IF NOT EXISTS timescaledb;\n\n"
  },
  {
    "path": "test/sql/updates/setup.drop_meta.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- DROP some chunks to test metadata cleanup\n\\if :WITH_CHUNK\nDROP TABLE _timescaledb_internal._hyper_1_2_chunk;\nDROP TABLE _timescaledb_internal._hyper_1_3_chunk;\n\\endif\n"
  },
  {
    "path": "test/sql/updates/setup.fix_sparse_index_migration.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Fix wrong migration by removing all sparse index configurations\n-- which only contain auto sparse indexing definitions on hypertable\nDO $$\nDECLARE\n    rec RECORD;\n    num_config INT;\nBEGIN\n\tIF NOT EXISTS (\n\t\tSELECT column_name\n\t\tFROM information_schema.columns\n\t\tWHERE table_schema = '_timescaledb_catalog'\n\t\tAND table_name = 'compression_settings'\n\t\tAND column_name = 'index') THEN\n\t\tRETURN;\n\tEND IF;\n\n    FOR rec IN\n        SELECT relid, compress_relid, index\n        FROM _timescaledb_catalog.compression_settings\n\t\tWHERE compress_relid IS NULL\n    LOOP\n\t\tnum_config:=0;\n\n\t\tSELECT count(*)\n\t\tINTO num_config\n\t\tFROM jsonb_array_elements(rec.index) AS idx\n\t\tWHERE idx::jsonb @> '{\"storage\":\"config\"}'::jsonb;\n\n\t\tIF num_config = 0 THEN\n            UPDATE _timescaledb_catalog.compression_settings\n            SET index = NULL\n            WHERE relid = rec.relid;\n\t\tEND IF;\n    END LOOP;\nEND $$;\n"
  },
  {
    "path": "test/sql/updates/setup.insert_bigint.v1.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nINSERT INTO public.\"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1, series_2) VALUES\n(1257987600000000000, 'dev1', 1.5, 1, 1),\n(1257987600000000000, 'dev1', 1.5, 2, 2),\n(1257894000000000000, 'dev2', 1.5, 1, 3),\n(1257894002000000000, 'dev1', 2.5, 3, 4);\n\nINSERT INTO \"two_Partitions\"(\"timeCustom\", device_id, series_0, series_1, series_2) VALUES\n(1257894000000000000, 'dev2', 1.5, 2, 6);\n"
  },
  {
    "path": "test/sql/updates/setup.insert_bigint.v2.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nINSERT INTO public.\"two_Partitions\"(\"timeCustom\", device_id, device_id_2, series_0, series_1, series_2) VALUES\n(1257987600000000000, 'dev1', 'dev2', 1.5, 2, 2),\n(1257894000000000000, 'dev2', 'dev2', 1.5, 1, 3),\n(1257987600000000000, 'dev3', 'dev2', 1.5, 1, 1),\n(1257894002000000000, 'dev1', 'dev2', 2.5, 3, 4);\n\nINSERT INTO \"two_Partitions\"(\"timeCustom\", device_id, device_id_2, series_0, series_1, series_2) VALUES\n(1257894100000000000, 'dev2', 'dev2', 1.5, 2, 6);\n"
  },
  {
    "path": "test/sql/updates/setup.insert_timestamp.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nINSERT INTO hyper_timestamp VALUES\n('2017-01-20T09:00:01', 'dev1', 1),\n('2017-01-20T08:00:01', 'dev2', 2),\n('2016-01-20T09:00:01', 'dev1', 3);\n"
  },
  {
    "path": "test/sql/updates/setup.pg_upgrade.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE SCHEMA cagg_join;\nCREATE TABLE cagg_join.sensor(\n    id SERIAL PRIMARY KEY,\n    name TEXT,\n    enabled BOOLEAN\n);\n\nCREATE TABLE cagg_join.measurement(\n    sensor_id INTEGER REFERENCES cagg_join.sensor(id),\n    observed TIMESTAMPTZ,\n    value FLOAT\n);\n\nSELECT create_hypertable('cagg_join.measurement', 'observed');\n\nCREATE MATERIALIZED VIEW cagg_join.measurement_daily\nWITH (timescaledb.continuous) AS\n-- Column s.name is functionally dependent on s.id (primary key)\nSELECT s.id, s.name, time_bucket(interval '1 day', observed) as bucket, avg(value), min(value), max(value)\nFROM cagg_join.sensor s\nJOIN cagg_join.measurement m on (s.id = m.sensor_id)\nGROUP BY s.id, bucket;\n"
  },
  {
    "path": "test/sql/updates/setup.policies.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE TABLE policy_test_timestamptz(time timestamptz not null, device_id int, value float);\nSELECT table_name FROM create_hypertable('policy_test_timestamptz','time');\n\nALTER TABLE policy_test_timestamptz SET (timescaledb.compress, timescaledb.compress_orderby = '\"time\" desc');\n\nINSERT INTO policy_test_timestamptz(time, device_id, value) VALUES ('3020-01-01 00:00:00', 1, 1.0);\nSELECT compress_chunk(show_chunks('policy_test_timestamptz'));\n\nDO LANGUAGE PLPGSQL $$\nDECLARE\n  ts_major INT;\n  ts_minor INT;\nBEGIN\n\n  WITH timescale_version AS (\n      SELECT string_to_array(extversion,'.') AS v\n      FROM pg_extension\n      WHERE extname = 'timescaledb'\n  )\n  SELECT v[1], v[2]\n  INTO ts_major, ts_minor\n  FROM timescale_version;\n\n  PERFORM add_reorder_policy('policy_test_timestamptz','policy_test_timestamptz_time_idx');\n  PERFORM add_retention_policy('policy_test_timestamptz','60d'::interval);\n\n  -- some policy API functions got renamed for 2.0 so we need to make\n  -- sure to use the right name for the version. The schedule_interval\n  -- parameter of add_compression_policy was introduced in 2.8.0\n  IF ts_major = 2 AND ts_minor < 8 THEN\n    PERFORM add_compression_policy('policy_test_timestamptz','10d'::interval);\n  ELSE\n    PERFORM add_compression_policy('policy_test_timestamptz','10d'::interval, schedule_interval => '3 days 12:00:00'::interval);\n  END IF;\nEND\n$$;\n\n\\if :WITH_ROLES\n-- For PostgreSQL 15 and later\nGRANT ALL ON SCHEMA PUBLIC TO \"dotted.name\";\nGRANT ALL ON SCHEMA PUBLIC TO \"Kim Possible\";\n\nSET ROLE \"dotted.name\";\nCREATE TABLE policy_test_user_1(time timestamptz not null, device_id int, value float);\nSELECT table_name FROM create_hypertable('policy_test_user_1','time');\nSELECT add_retention_policy('policy_test_user_1', '14 days'::interval);\n\nSET ROLE \"Kim Possible\";\nCREATE TABLE policy_test_user_2(time timestamptz not null, device_id int, value float);\nSELECT table_name FROM create_hypertable('policy_test_user_2','time');\nSELECT add_retention_policy('policy_test_user_2', '14 days'::interval);\nRESET ROLE;\n\\endif\n"
  },
  {
    "path": "test/sql/updates/setup.post-downgrade.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- When running a downgrade tests, the extension is first updated to\n-- the later version and then downgraded to the previous version. This\n-- means that in some cases, changes done by the update is not\n-- reversed by the downgrade. If these changes are harmless, we can\n-- apply changes to the clean rerun to incorporate these changes\n-- directly and prevent a diff between the clean-rerun version and the\n-- upgrade-downgrade version of the database.\n\nSELECT\n     split_part(extversion, '.', 1)::int * 100000 +\n     split_part(extversion, '.', 2)::int *    100 AS extversion_num\nFROM\n     pg_extension WHERE extname = 'timescaledb' \\gset\n\nSELECT\n     :extversion_num >= 200000 AS has_create_mat_view \\gset\n\n-- Rebuild the user views based on the renamed views\n\\if :has_create_mat_view\nALTER MATERIALIZED VIEW rename_cols SET (timescaledb.materialized_only = FALSE);\n\\else\nALTER VIEW rename_cols SET (timescaledb.materialized_only = FALSE);\n\\endif\n"
  },
  {
    "path": "test/sql/updates/setup.repair.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Test file to check that the repair script works. It will create a\n-- bunch of tables and \"break\" them by removing dimension slices from\n-- the dimension slice table. The repair script should then repair all\n-- of them and there should be no dimension slices missing.\n\nCREATE USER wizard;\nCREATE USER \"Random L User\";\n\nCREATE TABLE repair_test_int(time integer not null, temp float8, tag integer, color integer);\nCREATE TABLE repair_test_timestamptz(time timestamptz not null, temp float8, tag integer, color integer);\nCREATE TABLE repair_test_extra(time timestamptz not null, temp float8, tag integer, color integer);\nCREATE TABLE repair_test_timestamp(time timestamp not null, temp float8, tag integer, color integer);\nCREATE TABLE repair_test_date(time date not null, temp float8, tag integer, color integer);\n\n-- We only break the dimension slice table if there is repair that is\n-- going to be done, but we create the tables regardless so that we\n-- can compare the databases.\nSELECT create_hypertable('repair_test_int', 'time', 'tag', 2, chunk_time_interval => '3'::bigint);\nSELECT create_hypertable('repair_test_timestamptz', 'time', 'tag', 2, chunk_time_interval => '1 day'::interval);\nSELECT create_hypertable('repair_test_extra', 'time', 'tag', 2, chunk_time_interval => '1 day'::interval);\nSELECT create_hypertable('repair_test_timestamp', 'time', 'tag', 2, chunk_time_interval => '1 day'::interval);\nSELECT create_hypertable('repair_test_date', 'time', 'tag', 2, chunk_time_interval => '1 day'::interval);\n\n-- These rows will create four constraints for each table.\nINSERT INTO repair_test_int VALUES\n       (4, 24.3, 1, 1),\n       (4, 24.3, 2, 1),\n       (10, 24.3, 2, 1);\n\nINSERT INTO repair_test_timestamptz VALUES\n       ('2020-01-01 10:11:12', 24.3, 1, 1),\n       ('2020-01-01 10:11:13', 24.3, 2, 1),\n       ('2020-01-02 10:11:14', 24.3, 2, 1);\n\nINSERT INTO repair_test_extra VALUES\n       ('2020-01-01 10:11:12', 24.3, 1, 1),\n       ('2020-01-01 10:11:13', 24.3, 2, 1),\n       ('2020-01-02 10:11:14', 24.3, 2, 1);\n\nINSERT INTO repair_test_timestamp VALUES\n       ('2020-01-01 10:11:12', 24.3, 1, 1),\n       ('2020-01-01 10:11:13', 24.3, 2, 1),\n       ('2020-01-02 10:11:14', 24.3, 2, 1);\n\nINSERT INTO repair_test_date VALUES\n       ('2020-01-01 10:11:12', 24.3, 1, 1),\n       ('2020-01-01 10:11:13', 24.3, 2, 1),\n       ('2020-01-02 10:11:14', 24.3, 2, 1);\n\n-- We always drop the constraint and restore it in the\n-- post.repair.sql.\n--\n-- This way if there are constraint violations remaining that wasn't\n-- repaired properly, we will notice them when restoring the\n-- constraint.\nALTER TABLE _timescaledb_catalog.chunk_constraint\n      DROP CONSTRAINT chunk_constraint_dimension_slice_id_fkey;\n\n-- Grant privileges to some tables above. All should be repaired.\nGRANT ALL ON repair_test_int TO wizard;\nGRANT ALL ON repair_test_extra TO wizard;\nGRANT SELECT, INSERT ON repair_test_int TO \"Random L User\";\nGRANT INSERT ON repair_test_extra TO \"Random L User\";\n\n-- Break the relacl of the table by deleting users directly from\n-- pg_authid table.\nDELETE FROM pg_authid WHERE rolname IN ('wizard', 'Random L User');\n\n"
  },
  {
    "path": "test/sql/updates/setup.roles.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE ROLE cagg_user;\nCREATE USER tsdbadmin;\n\n-- These are used to test job creation and updating job owners.\nCREATE USER \"dotted.name\";\t--non-identifier character in name\nCREATE USER \"Kim Possible\";\t--case-sensitive names\n"
  },
  {
    "path": "test/sql/updates/setup.sparse_index.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Test a table with auto sparse indexes\nCREATE TABLE bloom (\n    x     INT,\n    v1    TEXT,\n    u     UUID,\n    ts    TIMESTAMP\n);\n\nSELECT create_hypertable('bloom', 'x');\n\nINSERT INTO bloom\nSELECT\n    x,\n    md5(x::text),\n    CASE\n        WHEN x = 7134 THEN '90ec9e8e-4501-4232-9d03-6d7cf6132815'\n        ELSE '6c1d0998-05f3-452c-abd3-45afe72bbcab'::uuid\n    END,\n    '2021-01-01'::timestamp + (INTERVAL '1 hour') * x\nFROM generate_series(1, 10000) x;\n\nCREATE INDEX ON bloom USING brin(v1 text_bloom_ops);\nCREATE INDEX ON bloom USING brin(u uuid_bloom_ops);\nCREATE INDEX ON bloom USING brin(ts timestamp_minmax_ops);\n\nALTER TABLE bloom SET (\n    timescaledb.compress,\n    timescaledb.compress_segmentby = 'v1',\n    timescaledb.compress_orderby = 'x'\n);\n\nSELECT COUNT(compress_chunk(x)) FROM show_chunks('bloom') x;\n\nVACUUM FULL ANALYZE bloom;\n\nSELECT * FROM _timescaledb_catalog.compression_settings;\n\nSELECT\n    schema_name || '.' || table_name AS chunk\nFROM _timescaledb_catalog.chunk\nWHERE id = (\n    SELECT compressed_chunk_id\n    FROM _timescaledb_catalog.chunk\n    WHERE hypertable_id = (\n        SELECT id\n        FROM _timescaledb_catalog.hypertable\n        WHERE table_name = 'bloom'\n    )\n    LIMIT 1\n)\n\\gset\n\n\\d+ :chunk"
  },
  {
    "path": "test/sql/updates/setup.timestamp.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Test a hypertable using timestamps\nCREATE TABLE PUBLIC.hyper_timestamp (\n  time timestamp NOT NULL,\n  device_id TEXT NOT NULL,\n  value int NOT NULL\n);\n\n\nDO $$\nBEGIN\n  IF (EXISTS (SELECT FROM pg_proc WHERE proname = 'interval_to_usec' AND pronamespace='_timescaledb_internal'::regnamespace))\n  THEN\n    PERFORM create_hypertable('hyper_timestamp'::regclass, 'time'::name, 'device_id'::name, number_partitions => 2, chunk_time_interval=> _timescaledb_internal.interval_to_usec('1 minute'));\n  ELSE\n    PERFORM create_hypertable('hyper_timestamp'::regclass, 'time'::name, 'device_id'::name, number_partitions => 2, chunk_time_interval=> _timescaledb_functions.interval_to_usec('1 minute'));\n  END IF;\nEND;\n$$;\n\n--some old versions use more slice_ids than newer ones. Make this uniform\nCALL _timescaledb_testing.restart_dimension_slice_id();\n"
  },
  {
    "path": "test/sql/updates/setup.v10.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\ir setup.v9.sql\n\\ir setup.sparse_index.sql\n\\ir setup.fix_sparse_index_migration.sql\n"
  },
  {
    "path": "test/sql/updates/setup.v7.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\ir setup.catalog.sql\n\\ir setup.bigint.sql\n\\ir setup.constraints.sql\n\\ir setup.insert_bigint.v2.sql\n\\ir setup.timestamp.sql\n\nALTER TABLE PUBLIC.hyper_timestamp\n  ADD CONSTRAINT exclude_const\n  EXCLUDE USING btree (\n        \"time\" WITH =, device_id WITH =\n   ) WHERE (value > 0);\n\n\\ir setup.insert_timestamp.sql\n\\ir setup.drop_meta.sql\n\\ir setup.continuous_aggs.sql\n\\ir setup.compression.sql\n\\ir setup.policies.sql\n"
  },
  {
    "path": "test/sql/updates/setup.v8.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\ir setup.v7.sql\n"
  },
  {
    "path": "test/sql/updates/setup.v9.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\ir setup.v8.sql\n\\ir setup.chunk_skipping.sql\n"
  },
  {
    "path": "test/sql/upsert.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE TABLE upsert_test(time timestamp PRIMARY KEY, temp float, color text);\nSELECT create_hypertable('upsert_test', 'time');\nINSERT INTO upsert_test VALUES ('2017-01-20T09:00:01', 22.5, 'yellow') RETURNING *;\nINSERT INTO upsert_test VALUES ('2017-01-20T09:00:01', 23.8, 'yellow') ON CONFLICT (time)\nDO UPDATE SET temp = 23.8 RETURNING *;\nINSERT INTO upsert_test VALUES ('2017-01-20T09:00:01', 78.4, 'yellow') ON CONFLICT DO NOTHING;\nSELECT * FROM upsert_test;\n\n-- Test 'Tuples Inserted' and 'Conflicting Tuples' values in EXPLAIN ANALYZE\nEXPLAIN (VERBOSE, ANALYZE, BUFFERS FALSE, COSTS FALSE, TIMING FALSE, SUMMARY FALSE)\n  INSERT INTO upsert_test VALUES\n    ('2017-01-20T09:00:01', 28.5, 'blue'),\n    ('2017-01-20T09:00:01', 21.9, 'red'),\n    ('2017-01-20T10:00:01', 2.4, 'pink') ON CONFLICT DO NOTHING;\n\n-- Test ON CONFLICT ON CONSTRAINT\nINSERT INTO upsert_test VALUES ('2017-01-20T09:00:01', 12.3, 'yellow') ON CONFLICT ON CONSTRAINT upsert_test_pkey\nDO UPDATE SET temp = 12.3 RETURNING time, temp, color;\n\n-- Test that update generates error on conflicts\n\\set ON_ERROR_STOP 0\nINSERT INTO upsert_test VALUES ('2017-01-21T09:00:01', 22.5, 'yellow') RETURNING *;\nUPDATE upsert_test SET time = '2017-01-20T09:00:01';\n\\set ON_ERROR_STOP 1\n\n-- Test with UNIQUE index on multiple columns instead of PRIMARY KEY constraint\nCREATE TABLE upsert_test_unique(time timestamp, temp float, color text);\nSELECT create_hypertable('upsert_test_unique', 'time');\nCREATE UNIQUE INDEX time_color_idx ON upsert_test_unique (time, color);\nINSERT INTO upsert_test_unique VALUES ('2017-01-20T09:00:01', 22.5, 'yellow') RETURNING *;\nINSERT INTO upsert_test_unique VALUES ('2017-01-20T09:00:01', 21.2, 'brown');\nSELECT * FROM upsert_test_unique ORDER BY time, color DESC;\nINSERT INTO upsert_test_unique VALUES ('2017-01-20T09:00:01', 31.8, 'yellow') ON CONFLICT (time, color)\nDO UPDATE SET temp = 31.8;\nINSERT INTO upsert_test_unique VALUES ('2017-01-20T09:00:01', 54.3, 'yellow') ON CONFLICT DO NOTHING;\nSELECT * FROM upsert_test_unique ORDER BY time, color DESC;\n\n-- Test with multiple UNIQUE indexes\nCREATE TABLE upsert_test_multi_unique(time timestamp, temp float, color text);\nSELECT create_hypertable('upsert_test_multi_unique', 'time');\nALTER TABLE upsert_test_multi_unique ADD CONSTRAINT multi_time_temp UNIQUE (time, temp);\nCREATE UNIQUE INDEX multi_time_color_idx ON upsert_test_multi_unique (time, color);\nINSERT INTO upsert_test_multi_unique VALUES ('2017-01-20T09:00:01', 25.9, 'yellow');\nINSERT INTO upsert_test_multi_unique VALUES ('2017-01-21T09:00:01', 25.9, 'yellow');\nINSERT INTO upsert_test_multi_unique VALUES ('2017-01-20T09:00:01', 23.5, 'brown');\nINSERT INTO upsert_test_multi_unique VALUES ('2017-01-20T09:00:01', 25.9, 'purple') ON CONFLICT DO NOTHING;\nSELECT * FROM upsert_test_multi_unique ORDER BY time, color DESC;\nINSERT INTO upsert_test_multi_unique VALUES ('2017-01-20T09:00:01', 25.9, 'blue') ON CONFLICT (time, temp)\nDO UPDATE SET color = 'blue';\nINSERT INTO upsert_test_multi_unique VALUES ('2017-01-20T09:00:01', 23.5, 'orange') ON CONFLICT ON CONSTRAINT multi_time_temp\nDO UPDATE SET color = excluded.color;\nSELECT * FROM upsert_test_multi_unique ORDER BY time, color DESC;\nINSERT INTO upsert_test_multi_unique VALUES ('2017-01-21T09:00:01', 45.7, 'yellow') ON CONFLICT (time, color)\nDO UPDATE SET temp = 45.7;\n\nSELECT * FROM upsert_test_multi_unique ORDER BY time, color DESC;\n\\set ON_ERROR_STOP 0\n-- Here the constraint in the ON CONFLICT clause is not the one that is\n-- actually violated by the INSERT, so it should still fail.\nINSERT INTO upsert_test_multi_unique VALUES ('2017-01-20T09:00:01', 23.5, 'purple') ON CONFLICT (time, color)\nDO UPDATE set temp = 23.5;\nINSERT INTO upsert_test_multi_unique VALUES ('2017-01-20T09:00:01', 22.5, 'orange') ON CONFLICT ON CONSTRAINT multi_time_temp\nDO UPDATE set color = 'orange';\n\\set ON_ERROR_STOP 1\n\nCREATE TABLE upsert_test_space(time timestamp, device_id_1 char(20), to_drop int, temp float, color text);\n--drop two columns; create one.\nALTER TABLE upsert_test_space DROP to_drop;\nALTER TABLE upsert_test_space DROP device_id_1, ADD device_id char(20);\nALTER TABLE upsert_test_space ADD CONSTRAINT time_space_constraint UNIQUE (time, device_id);\nSELECT create_hypertable('upsert_test_space', 'time', 'device_id', 2, partitioning_func=>'_timescaledb_functions.get_partition_for_key'::regproc);\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev1', 25.9, 'yellow') RETURNING *;\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev2', 25.9, 'yellow');\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev1', 23.5, 'green') ON CONFLICT (time, device_id)\nDO UPDATE SET color = excluded.color;\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev1', 23.5, 'orange') ON CONFLICT ON CONSTRAINT time_space_constraint\nDO UPDATE SET color = excluded.color;\n\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev2', 23.5, 'orange3') ON CONFLICT (time, device_id)\nDO UPDATE SET color = excluded.color||' (originally '|| upsert_test_space.color ||')' RETURNING *;\n\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev3', 23.5, 'orange3.1') ON CONFLICT (time, device_id)\nDO UPDATE SET color = excluded.color||' (originally '|| upsert_test_space.color ||')' RETURNING *;\n\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev2', 23.5, 'orange4') ON CONFLICT (time, device_id)\nDO NOTHING RETURNING *;\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev4', 23.5, 'orange5') ON CONFLICT (time, device_id)\nDO NOTHING RETURNING *;\n\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev5', 23.5, 'orange5') ON CONFLICT (time, device_id)\nDO NOTHING RETURNING *;\n\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev5', 23.5, 'orange6') ON CONFLICT ON CONSTRAINT time_space_constraint\nDO NOTHING RETURNING *;\n\n--restore a column with the same name as a previously deleted one;\nALTER TABLE upsert_test_space ADD device_id_1 char(20);\nINSERT INTO upsert_test_space (time, device_id, temp, color, device_id_1) VALUES ('2017-01-20T09:00:01', 'dev4', 23.5, 'orange5.1', 'dev-id-1') ON CONFLICT (time, device_id)\nDO UPDATE SET color = excluded.color||' (originally '|| upsert_test_space.color ||')' RETURNING *;\n\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev5', 23.5, 'orange6') ON CONFLICT (time, device_id)\nDO UPDATE SET color = excluded.color WHERE upsert_test_space.temp < 20 RETURNING *;\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev5', 23.5, 'orange7') ON CONFLICT (time, device_id)\nDO UPDATE SET color = excluded.color WHERE excluded.temp < 20 RETURNING *;\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev5', 3.5, 'orange7') ON CONFLICT (time, device_id)\nDO UPDATE SET color = excluded.color, temp=excluded.temp WHERE excluded.temp < 20 RETURNING *;\nINSERT INTO upsert_test_space (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev5', 43.5, 'orange8') ON CONFLICT (time, device_id)\nDO UPDATE SET color = excluded.color WHERE upsert_test_space.temp < 20 RETURNING *;\nINSERT INTO upsert_test_space (time, device_id, temp, color, device_id_1) VALUES ('2017-01-20T09:00:01', 'dev5', 43.5, 'orange8', 'device-id-1-new') ON CONFLICT (time, device_id)\nDO UPDATE SET device_id_1 = excluded.device_id_1 RETURNING *;\nINSERT INTO upsert_test_space (time, device_id, temp, color, device_id_1) VALUES ('2017-01-20T09:00:01', 'dev5', 43.5, 'orange8', 'device-id-1-new') ON CONFLICT (time, device_id)\nDO UPDATE SET device_id_1 = 'device-id-1-new-2', color = 'orange9'  RETURNING *;\n\nSELECT * FROM upsert_test_space;\n\nALTER TABLE upsert_test_space DROP device_id_1, ADD device_id_2 char(20);\nINSERT INTO upsert_test_space (time, device_id, temp, color, device_id_2) VALUES ('2017-01-20T09:00:01', 'dev5', 43.5, 'orange8', 'device-id-2')\nON CONFLICT (time, device_id)\nDO UPDATE SET device_id_2 = 'device-id-2-new', color = 'orange10' RETURNING *;\n\n--test inserting to to a chunk already in the chunk dispatch cache again.\nINSERT INTO upsert_test_space as current (time, device_id, temp, color, device_id_2) VALUES ('2017-01-20T09:00:01', 'dev5', 43.5, 'orange8', 'device-id-2'),\n('2018-01-20T09:00:01', 'dev5', 43.5, 'orange8', 'device-id-2'),\n('2017-01-20T09:00:01', 'dev3', 43.5, 'orange7', 'device-id-2'),\n('2018-01-21T09:00:01', 'dev5', 43.5, 'orange9', 'device-id-2')\nON CONFLICT (time, device_id)\nDO UPDATE SET device_id_2 = coalesce(excluded.device_id_2,current.device_id_2), color = coalesce(excluded.color,current.color) RETURNING *;\n\nWITH CTE AS (\n    INSERT INTO upsert_test_multi_unique\n    VALUES ('2017-01-20T09:00:01', 25.9, 'purple')\n    ON CONFLICT DO NOTHING\n    RETURNING *\n) SELECT 1;\n\nWITH CTE AS (\n    INSERT INTO upsert_test_multi_unique\n    VALUES ('2017-01-20T09:00:01', 25.9, 'purple'),\n    ('2017-01-20T09:00:01', 29.9, 'purple1')\n    ON CONFLICT DO NOTHING\n    RETURNING *\n) SELECT * FROM CTE;\n\nWITH CTE AS (\n    INSERT INTO upsert_test_multi_unique\n    VALUES ('2017-01-20T09:00:01', 25.9, 'blue')\n    ON CONFLICT (time, temp) DO UPDATE SET color = 'blue'\n    RETURNING *\n)\nSELECT * FROM CTE;\n\n--test error conditions when an index is dropped on a chunk\nDROP INDEX _timescaledb_internal._hyper_3_3_chunk_multi_time_color_idx;\n\n--everything is ok if not used as an arbiter index\nINSERT INTO upsert_test_multi_unique\nVALUES ('2017-01-20T09:00:01', 25.9, 'purple')\nON CONFLICT DO NOTHING\nRETURNING *;\n\n--errors out if used as an arbiter index\n\\set ON_ERROR_STOP 0\nINSERT INTO upsert_test_multi_unique\nVALUES ('2017-01-20T09:00:01', 25.9, 'purple')\nON CONFLICT (time, color) DO NOTHING\nRETURNING *;\n\\set ON_ERROR_STOP 1\n\n--create table with one chunk that has a tup_conv_map and one that does not\n--to ensure this, create a chunk before altering the table this chunk will not have a tup_conv_map\nCREATE TABLE upsert_test_diffchunk(time timestamp, device_id char(20), to_drop int, temp float, color text);\nSELECT create_hypertable('upsert_test_diffchunk', 'time', chunk_time_interval=> interval '1 month');\nCREATE UNIQUE INDEX time_device_idx ON upsert_test_diffchunk (time, device_id);\n--this is the chunk with no tup_conv_map\nINSERT INTO upsert_test_diffchunk (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev1', 25.9, 'yellow') RETURNING *;\nINSERT INTO upsert_test_diffchunk (time, device_id, temp, color) VALUES ('2017-01-20T09:00:01', 'dev2', 25.9, 'yellow') RETURNING *;\n--alter the table\nALTER TABLE upsert_test_diffchunk DROP to_drop;\nALTER TABLE upsert_test_diffchunk ADD device_id_2 char(20);\n--new chunk that does have a tup conv map\nINSERT INTO upsert_test_diffchunk (time, device_id, temp, color) VALUES ('2019-01-20T09:00:01', 'dev1', 23.5, 'orange') ;\nINSERT INTO upsert_test_diffchunk (time, device_id, temp, color) VALUES ('2019-01-20T09:00:01', 'dev2', 23.5, 'orange') ;\nselect * from upsert_test_diffchunk order by time, device_id;\n\n--make sure current works\nINSERT INTO upsert_test_diffchunk as current (time, device_id, temp, color, device_id_2) VALUES\n('2019-01-20T09:00:01', 'dev1', 43.5, 'orange2', 'device-id-2'),\n('2017-01-20T09:00:01', 'dev1', 43.5, 'yellow2', 'device-id-2'),\n('2019-01-20T09:00:01', 'dev2', 43.5, 'orange2', 'device-id-2')\nON CONFLICT (time, device_id)\nDO UPDATE SET\ndevice_id_2 = coalesce(excluded.device_id_2,current.device_id_2),\ntemp = coalesce(excluded.temp,current.temp) ,\ncolor = coalesce(excluded.color,current.color);\nselect * from upsert_test_diffchunk order by time, device_id;\n\n\n--arbiter index tests\nCREATE TABLE upsert_test_arbiter(time timestamp, to_drop int);\nSELECT create_hypertable('upsert_test_arbiter', 'time', chunk_time_interval=> interval '1 month');\n--this is the chunk with no tup_conv_map\nINSERT INTO upsert_test_arbiter (time, to_drop) VALUES ('2017-01-20T09:00:01', 1) RETURNING *;\nINSERT INTO upsert_test_arbiter (time, to_drop) VALUES ('2017-01-21T09:00:01', 2) RETURNING *;\nINSERT INTO upsert_test_arbiter (time, to_drop) VALUES ('2017-03-20T09:00:01', 3) RETURNING *;\n--alter the table\nALTER TABLE upsert_test_arbiter DROP to_drop;\nALTER TABLE upsert_test_arbiter ADD device_id char(20) DEFAULT 'dev1';\nCREATE UNIQUE INDEX arbiter_time_device_idx ON upsert_test_arbiter (time, device_id);\n\nINSERT INTO upsert_test_arbiter as current (time, device_id) VALUES\n    ('2018-01-21T09:00:01', 'dev1'),\n    ('2017-01-20T09:00:01', 'dev1'),\n    ('2017-01-21T09:00:01', 'dev2'),\n    ('2018-01-21T09:00:01', 'dev2')\n ON CONFLICT (time, device_id) DO UPDATE SET device_id = coalesce(excluded.device_id,current.device_id)\nRETURNING *;\n\nwith cte as (\nINSERT INTO upsert_test_arbiter (time, device_id) VALUES\n    ('2017-01-21T09:00:01', 'dev2'),\n    ('2018-01-21T09:00:01', 'dev2')\n ON CONFLICT (time, device_id) DO UPDATE SET device_id = 'dev3'\nRETURNING *)\nselect * from cte;\n\n-- test ON CONFLICT with prepared statements\nCREATE TABLE prepared_test(time timestamptz PRIMARY KEY, value float CHECK(value > 0));\nSELECT create_hypertable('prepared_test','time');\n\nCREATE TABLE source_data(time timestamptz PRIMARY KEY, value float);\nINSERT INTO source_data VALUES('2000-01-01',0.5), ('2001-01-01',0.5);\n\n-- at some point PostgreSQL will turn the plan into a generic plan\n-- so we execute the prepared statement 10 times\n-- check that an error in the prepared statement does not lead to the plan becoming unusable\nPREPARE prep_insert_select AS INSERT INTO prepared_test select * from source_data ON CONFLICT (time) DO UPDATE SET value = EXCLUDED.value;\nEXECUTE prep_insert_select;\nEXECUTE prep_insert_select;\nEXECUTE prep_insert_select;\nEXECUTE prep_insert_select;\nEXECUTE prep_insert_select;\nEXECUTE prep_insert_select;\nEXECUTE prep_insert_select;\nEXECUTE prep_insert_select;\nEXECUTE prep_insert_select;\nEXECUTE prep_insert_select;\n--this insert will create an invalid tuple in source_data\n--so that future calls to prep_insert_select will fail\nINSERT INTO source_data VALUES('2000-01-02',-0.5);\n\\set ON_ERROR_STOP 0\nEXECUTE prep_insert_select;\nEXECUTE prep_insert_select;\n\\set ON_ERROR_STOP 1\nDELETE FROM source_data WHERE value <= 0;\nEXECUTE prep_insert_select;\n\nPREPARE prep_insert AS INSERT INTO prepared_test VALUES('2000-01-01',0.5) ON CONFLICT (time) DO UPDATE SET value = EXCLUDED.value;\n\n-- at some point PostgreSQL will turn the plan into a generic plan\n-- so we execute the prepared statement 10 times\nEXECUTE prep_insert;\nEXECUTE prep_insert;\nEXECUTE prep_insert;\nEXECUTE prep_insert;\nEXECUTE prep_insert;\nEXECUTE prep_insert;\nEXECUTE prep_insert;\nEXECUTE prep_insert;\nEXECUTE prep_insert;\nEXECUTE prep_insert;\n\nSELECT * FROM prepared_test;\nDELETE FROM prepared_test;\n\n-- test ON CONFLICT with functions\nCREATE OR REPLACE FUNCTION test_upsert(t timestamptz, v float) RETURNS VOID AS $sql$\nBEGIN\nINSERT INTO prepared_test VALUES(t,v) ON CONFLICT (time) DO UPDATE SET value = EXCLUDED.value;\nEND;\n$sql$ LANGUAGE PLPGSQL;\n\n-- at some point PostgreSQL will turn the plan into a generic plan\n-- so we execute the function 10 times\nSELECT counter,test_upsert('2000-01-01',0.5) FROM generate_series(1,10) AS g(counter);\n\nSELECT * FROM prepared_test;\nDELETE FROM prepared_test;\n\n-- at some point PostgreSQL will turn the plan into a generic plan\n-- so we execute the function 10 times\nSELECT counter,test_upsert('2000-01-01',0.5) FROM generate_series(1,10) AS g(counter);\n\nSELECT * FROM prepared_test;\nDELETE FROM prepared_test;\n\n-- run it again to ensure INSERT path is still working as well\nSELECT counter,test_upsert('2000-01-01',0.5) FROM generate_series(1,10) AS g(counter);\n\nSELECT * FROM prepared_test;\nDELETE FROM prepared_test;\n\n-- test ON CONFLICT with functions\nCREATE OR REPLACE FUNCTION test_upsert2(t timestamptz, v float) RETURNS VOID AS $sql$\nBEGIN\nINSERT INTO prepared_test VALUES(t,v) ON CONFLICT (time) DO UPDATE SET value = prepared_test.value + 1.0;\nEND;\n$sql$ LANGUAGE PLPGSQL;\n\n-- at some point PostgreSQL will turn the plan into a generic plan\n-- so we execute the function 10 times\nSELECT counter,test_upsert2('2000-01-01',1.0) FROM generate_series(1,10) AS g(counter);\n\nSELECT * FROM prepared_test;\n"
  },
  {
    "path": "test/sql/util.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n\\set ECHO errors\n\\set VERBOSITY default\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\n\\set TMP_USER :TEST_DBNAME _wizard\n\nDO $$\nBEGIN\n  ASSERT( _timescaledb_functions.get_partition_for_key(''::text) = 669664877 );\n  ASSERT( _timescaledb_functions.get_partition_for_key('dev1'::text) = 1129986420 );\n  ASSERT( _timescaledb_functions.get_partition_for_key('longlonglonglongpartitionkey'::text) = 1169179734);\nEND$$;\n\n\\pset null '[NULL]'\nCREATE USER :TMP_USER;\nSELECT * FROM (\n    VALUES\n       (_timescaledb_functions.makeaclitem(:'TMP_USER', :'TMP_USER', 'insert', false)),\n       (_timescaledb_functions.makeaclitem(:'TMP_USER', :'TMP_USER', 'insert,select', false)),\n       (_timescaledb_functions.makeaclitem(:'TMP_USER', :'TMP_USER', 'insert', true)),\n       (_timescaledb_functions.makeaclitem(:'TMP_USER', :'TMP_USER', 'insert,select', true)),\n       (_timescaledb_functions.makeaclitem(NULL, :'TMP_USER', 'insert,select', true)),\n       (_timescaledb_functions.makeaclitem(:'TMP_USER', NULL, 'insert,select', true)),\n       (_timescaledb_functions.makeaclitem(:'TMP_USER', :'TMP_USER', NULL, true)),\n       (_timescaledb_functions.makeaclitem(:'TMP_USER', :'TMP_USER', 'insert,select', NULL)),\n       (_timescaledb_functions.makeaclitem(0, :'TMP_USER', 'insert,select', true)),\n       (_timescaledb_functions.makeaclitem(:'TMP_USER', 0, 'insert,select', true))\n    ) AS t(item);\nDROP USER :TMP_USER;\n\nCREATE TABLE data (vid serial, rng text);\nINSERT INTO data(rng)\nVALUES ('[\"2025-04-25 11:10:00+02\",\"2025-04-25 11:14:00+02\"]'),\n       ('[\"2025-04-25 11:10:00+02\",\"2025-04-25 11:17:00+02\"]'),\n       ('[\"2025-04-25 11:10:00+02\",\"2025-04-25 11:20:00+02\")');\n\n\\set ECHO all\nSELECT _timescaledb_functions.align_to_bucket('5 minutes'::interval, rng::tstzrange) FROM data;\n\n\\set ON_ERROR_STOP 0\nSELECT _timescaledb_functions.align_to_bucket(null, null);\nSELECT _timescaledb_functions.align_to_bucket(null::interval, null::tstzrange);\nSELECT _timescaledb_functions.align_to_bucket(\n       null::interval,\n       '[\"2025-04-25 11:10:00+02\",\"2025-04-25 11:14:00+02\"]'::tstzrange\n);\n\\set ON_ERROR_STOP 1\n\nSELECT typ,\n       _timescaledb_functions.get_internal_time_min(typ),\n       _timescaledb_functions.get_internal_time_max(typ)\n  FROM (VALUES\n    ('bigint'::regtype), ('int'::regtype), ('smallint'::regtype),\n    ('timestamp'::regtype), ('timestamptz'::regtype), ('date'::regtype),\n    (null::regtype)\n  ) t(typ);\n\n\\set ON_ERROR_STOP 0\nSELECT _timescaledb_functions.get_internal_time_min(0);\nSELECT _timescaledb_functions.get_internal_time_max(0);\n\\set ON_ERROR_STOP 1\n\nWITH\n  tstzranges AS (\n    SELECT vid,\n           rng::tstzrange,\n           lower(rng::tstzrange) AS lower_ts,\n           upper(rng::tstzrange) AS upper_ts\n      FROM data\n  ),\n  usecranges AS (\n    SELECT vid,\n           _timescaledb_functions.to_unix_microseconds(lower_ts) AS lower_usec,\n           _timescaledb_functions.to_unix_microseconds(upper_ts) AS upper_usec\n      FROM tstzranges\n  )\nSELECT _timescaledb_functions.make_multirange_from_internal_time(rng, lower_usec, upper_usec),\n       _timescaledb_functions.make_range_from_internal_time(rng, lower_ts, upper_ts)\n  FROM tstzranges join usecranges using (vid);\n\nWITH\n  tsranges AS (\n    SELECT vid,\n           rng::tsrange,\n           lower(rng::tsrange) AS lower_ts,\n           upper(rng::tsrange) AS upper_ts\n      FROM data\n  ),\n  usecranges AS (\n    SELECT vid,\n           _timescaledb_functions.to_unix_microseconds(lower_ts) AS lower_usec,\n           _timescaledb_functions.to_unix_microseconds(upper_ts) AS upper_usec\n      FROM tsranges\n  )\nSELECT _timescaledb_functions.make_multirange_from_internal_time(rng, lower_usec, upper_usec),\n       _timescaledb_functions.make_range_from_internal_time(rng, lower_ts, upper_ts)\n  FROM tsranges join usecranges using (vid);\n"
  },
  {
    "path": "test/sql/utils/pg_dump_aux_dump.sh",
    "content": "DUMPFILE=${DUMPFILE:-$1}\nEXTRA_PGOPTIONS=${EXTRA_PGOPTIONS:-$2}\n# Override PGOPTIONS to remove verbose output\nPGOPTIONS=\"--client-min-messages=warning $EXTRA_PGOPTIONS\"\n\nexport PGOPTIONS\n\n${PG_BINDIR}/pg_dump -h ${PGHOST} -U ${TEST_ROLE_SUPERUSER} -Fc ${TEST_DBNAME} > /dev/null 2>&1 -f ${DUMPFILE}\n${PG_BINDIR}/dropdb -f -h ${PGHOST} -U ${TEST_ROLE_SUPERUSER} ${TEST_DBNAME}\n${PG_BINDIR}/createdb -h ${PGHOST} -U ${TEST_ROLE_SUPERUSER} ${TEST_DBNAME}\n"
  },
  {
    "path": "test/sql/utils/pg_dump_aux_plain_dump.sh",
    "content": "DUMPFILE=${DUMPFILE:-$1}\nEXTRA_PGOPTIONS=${EXTRA_PGOPTIONS:-$2}\nDUMP_OPTIONS=${DUMP_OPTIONS:-$3}\n# Override PGOPTIONS to remove verbose output\nPGOPTIONS=\"--client-min-messages=warning $EXTRA_PGOPTIONS\"\n\nexport PGOPTIONS\necho ${DUMP_OPTIONS}\necho $DUMP_OPTIONS\necho $(echo $DUMP_OPTIONS)\n\n${PG_BINDIR}/pg_dump -h ${PGHOST} -U ${TEST_ROLE_SUPERUSER} ${DUMP_OPTIONS} -Fp ${TEST_DBNAME} -f ${DUMPFILE}\n# ${PG_BINDIR}/pg_dump -h ${PGHOST} -U ${TEST_ROLE_SUPERUSER} ${DUMP_OPTIONS} -Fp ${TEST_DBNAME} > /dev/null 2>&1 -f ${DUMPFILE}\n${PG_BINDIR}/dropdb -f -h ${PGHOST} -U ${TEST_ROLE_SUPERUSER} ${TEST_DBNAME}\n${PG_BINDIR}/createdb -h ${PGHOST} -U ${TEST_ROLE_SUPERUSER} ${TEST_DBNAME}\n"
  },
  {
    "path": "test/sql/utils/pg_dump_aux_restore.sh",
    "content": "DUMPFILE=${DUMPFILE:-$1}\n# Override PGOPTIONS to remove verbose output\nPGOPTIONS='--client-min-messages=warning'\n\nexport PGOPTIONS\n\n# Redirect output to /dev/null to suppress NOTICE\n${PG_BINDIR}/pg_restore -h ${PGHOST} -U ${TEST_ROLE_SUPERUSER} -d ${TEST_DBNAME} ${DUMPFILE} > /dev/null 2>&1\n"
  },
  {
    "path": "test/sql/utils/pg_dump_unprivileged.sh",
    "content": "export PGOPTIONS\nPGOPTIONS='--client-min-messages=warning'\n\n${PG_BINDIR}/pg_dump -h ${PGHOST} -U dump_unprivileged dump_unprivileged > /dev/null 2>&1\n\nif [ $? -eq 0 ]; then\n  echo \"Database dumped successfully\"\nfi\n"
  },
  {
    "path": "test/sql/utils/test_fatal_command.sh",
    "content": "DB=$1\nCOMMAND=$2\n${PG_BINDIR}/psql -X -h ${PGHOST} -U ${TEST_ROLE_SUPERUSER} -d ${DB} --command=\"${COMMAND}\" 2>&1 | grep -v CONTEXT | grep -v \"extension script file\"\n"
  },
  {
    "path": "test/sql/utils/testsupport.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE SCHEMA IF NOT EXISTS test;\nGRANT USAGE ON SCHEMA test TO PUBLIC;\n\n-- Utility functions to show relation information in tests. These\n-- functions generate output which is the same across PostgreSQL\n-- versions. Their usage is preferred over psql's '\\d <relation>',\n-- since that output typically changes across PostgreSQL versions.\n\n-- this function is duplicated in test/isolation/specs/multi_transaction_indexing.spec\n-- if it changes, that copy may need to change as well\nCREATE OR REPLACE FUNCTION test.show_columns(rel regclass)\nRETURNS TABLE(\"Column\" name,\n              \"Type\" text,\n              \"NotNull\" boolean) LANGUAGE SQL STABLE AS\n$BODY$\n    SELECT a.attname,\n    format_type(t.oid, t.typtypmod),\n    a.attnotnull\n    FROM pg_attribute a, pg_type t\n    WHERE a.attrelid = rel\n    AND a.atttypid = t.oid\n    AND a.attnum >= 0\n    ORDER BY a.attnum;\n$BODY$;\n\nCREATE OR REPLACE FUNCTION test.show_columnsp(pattern text)\nRETURNS TABLE(\"Relation\" regclass,\n              \"Kind\" \"char\",\n              \"Column\" name,\n              \"Column type\" text,\n              \"NotNull\" boolean) LANGUAGE PLPGSQL STABLE AS\n$BODY$\nDECLARE\n    schema_name name = split_part(pattern, '.', 1);\n    table_name name = split_part(pattern, '.', 2);\nBEGIN\n    IF schema_name = '' OR table_name = '' THEN\n       schema_name := current_schema();\n       table_name := pattern;\n    END IF;\n\n    RETURN QUERY\n    SELECT c.oid::regclass,\n    c.relkind,\n    a.attname,\n    format_type(t.oid, t.typtypmod),\n    a.attnotnull\n    FROM pg_class c, pg_attribute a, pg_type t\n    WHERE format('%I.%I', c.relnamespace::regnamespace::name, c.relname) LIKE format('%I.%s', schema_name, table_name)\n    AND a.attrelid = c.oid\n    AND a.atttypid = t.oid\n    AND a.attnum >= 0\n    ORDER BY c.relname, a.attnum;\nEND\n$BODY$;\n\n-- Extended output about columns analogous to \\d+\nCREATE OR REPLACE FUNCTION test.show_columns_ext(rel regclass)\nRETURNS TABLE(\n  \"Column\" name,\n  \"Type\" text,\n  \"Collation\" name,\n  \"Nullable\" text,\n  \"Default\" text,\n  \"Storage\" text,\n  \"Stats target\" integer,\n  \"Description\" text\n)\nLANGUAGE SQL\nSTABLE\nAS\n$BODY$\n  SELECT\n    a.attname AS \"Column\",\n    pg_catalog.format_type(a.atttypid, a.atttypmod) AS \"Type\",\n    c.collname AS \"Collation\",\n    CASE WHEN a.attnotnull THEN 'not null' ELSE '' END AS \"Nullable\",\n    pg_catalog.pg_get_expr(ad.adbin, ad.adrelid) AS \"Default\",\n    CASE a.attstorage\n      WHEN 'p' THEN 'plain'\n      WHEN 'm' THEN 'main'\n      WHEN 'e' THEN 'external'\n      WHEN 'x' THEN 'extended'\n      ELSE NULL\n    END AS \"Storage\",\n    a.attstattarget AS \"Stats target\",\n    d.description AS \"Description\"\n  FROM pg_catalog.pg_attribute a\n  JOIN pg_catalog.pg_type t\n    ON t.oid = a.atttypid\n  LEFT JOIN pg_catalog.pg_collation c\n    ON a.attcollation = c.oid\n   AND a.attcollation <> 0\n  LEFT JOIN pg_catalog.pg_attrdef ad\n    ON ad.adrelid = a.attrelid\n   AND ad.adnum = a.attnum\n  LEFT JOIN pg_catalog.pg_description d\n    ON d.objoid = a.attrelid\n   AND d.objsubid = a.attnum\n  WHERE a.attrelid = rel\n    AND a.attnum > 0\n    AND NOT a.attisdropped\n  ORDER BY a.attnum;\n$BODY$;\n\n\nCREATE OR REPLACE FUNCTION test.show_indexes(rel regclass)\nRETURNS TABLE(\"Index\" regclass,\n              \"Columns\" name[],\n              \"Expr\" text,\n              \"Unique\" boolean,\n              \"Primary\" boolean,\n              \"Exclusion\" boolean,\n              \"Tablespace\" name) LANGUAGE SQL STABLE AS\n$BODY$\n    SELECT c.oid::regclass,\n    array(SELECT \"Column\" FROM test.show_columns(i.indexrelid)),\n    pg_get_expr(i.indexprs, c.oid, true),\n    i.indisunique,\n    i.indisprimary,\n    i.indisexclusion,\n    (SELECT t.spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace)\n    FROM pg_class c, pg_index i\n    WHERE c.oid = i.indexrelid AND i.indrelid = rel\n    ORDER BY c.relname;\n$BODY$;\n\nCREATE OR REPLACE FUNCTION test.show_indexespred(rel regclass)\nRETURNS TABLE(\"Index\" regclass,\n              \"Columns\" name[],\n              \"Expr\" text,\n              \"Pred\" text,\n              \"Unique\" boolean,\n              \"Primary\" boolean,\n              \"Exclusion\" boolean,\n              \"Tablespace\" name) LANGUAGE SQL STABLE AS\n$BODY$\n    SELECT c.oid::regclass,\n    array(SELECT \"Column\" FROM test.show_columns(i.indexrelid)),\n    pg_get_expr(i.indexprs, i.indrelid, true),\n    pg_get_expr(i.indpred, i.indrelid, true),\n    i.indisunique,\n    i.indisprimary,\n    i.indisexclusion,\n    (SELECT t.spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace)\n    FROM pg_class c, pg_index i\n    WHERE c.oid = i.indexrelid AND i.indrelid = rel\n    ORDER BY c.relname;\n$BODY$;\n\n-- this function is duplicated in test/isolation/specs/multi_transaction_indexing.spec\n-- if it changes, that copy may need to change as well\nCREATE OR REPLACE FUNCTION test.show_indexesp(pattern text)\nRETURNS TABLE(\"Table\" regclass,\n              \"Index\" regclass,\n              \"Columns\" name[],\n              \"Expr\" text,\n              \"Unique\" boolean,\n              \"Primary\" boolean,\n              \"Exclusion\" boolean,\n              \"Tablespace\" name) LANGUAGE PLPGSQL STABLE AS\n$BODY$\nDECLARE\n    schema_name name = split_part(pattern, '.', 1);\n    table_name name = split_part(pattern, '.', 2);\nBEGIN\n    IF schema_name = '' OR table_name = '' THEN\n       schema_name := current_schema();\n       table_name := pattern;\n    END IF;\n\n    RETURN QUERY\n    SELECT c.oid::regclass,\n    i.indexrelid::regclass,\n    array(SELECT \"Column\" FROM test.show_columns(i.indexrelid)),\n    pg_get_expr(i.indexprs, c.oid, true),\n    i.indisunique,\n    i.indisprimary,\n    i.indisexclusion,\n    (SELECT t.spcname FROM pg_class cc, pg_tablespace t WHERE cc.oid = i.indexrelid AND t.oid = cc.reltablespace)\n    FROM pg_class c, pg_index i\n    WHERE format('%I.%I', c.relnamespace::regnamespace::name, c.relname) LIKE format('%I.%s', schema_name, table_name)\n    AND c.oid = i.indrelid\n    ORDER BY c.oid, i.indexrelid;\nEND\n$BODY$;\n\nCREATE OR REPLACE FUNCTION test.show_constraints(rel regclass)\nRETURNS TABLE(\"Constraint\" name,\n              \"Type\" \"char\",\n              \"Columns\" name[],\n              \"Index\" regclass,\n              \"Expr\" text,\n              \"Deferrable\" bool,\n              \"Deferred\" bool,\n              \"Validated\" bool) LANGUAGE SQL STABLE AS\n$BODY$\n    SELECT c.conname,\n    c.contype,\n    array(SELECT attname FROM pg_attribute a, unnest(conkey) k WHERE a.attrelid = rel AND k = a.attnum),\n    c.conindid::regclass,\n    pg_get_expr(c.conbin, c.conrelid),\n    c.condeferrable,\n    c.condeferred,\n    c.convalidated\n    FROM pg_constraint c\n    WHERE c.conrelid = rel\n-- to avoid showing not null constraints which are new to PG18\n-- https://github.com/postgres/postgres/commit/14e87ffa\n\tAND c.conname NOT LIKE '%not_null%'\n    ORDER BY c.conname;\n$BODY$;\n\nCREATE OR REPLACE FUNCTION test.show_constraintsp(pattern text)\nRETURNS TABLE(\"Table\" regclass,\n              \"Constraint\" name,\n              \"Type\" \"char\",\n              \"Columns\" name[],\n              \"Index\" regclass,\n              \"Expr\" text,\n              \"Deferrable\" bool,\n              \"Deferred\" bool,\n              \"Validated\" bool) LANGUAGE PLPGSQL STABLE AS\n$BODY$\nDECLARE\n    schema_name name = split_part(pattern, '.', 1);\n    table_name name = split_part(pattern, '.', 2);\nBEGIN\n    IF schema_name = '' OR table_name = '' THEN\n       schema_name := current_schema();\n       table_name := pattern;\n    END IF;\n\n    RETURN QUERY\n    SELECT cl.oid::regclass,\n    c.conname,\n    c.contype,\n    array(SELECT attname FROM pg_attribute a, unnest(conkey) k WHERE a.attrelid = cl.oid AND k = a.attnum),\n    c.conindid::regclass,\n    pg_get_expr(c.conbin, c.conrelid),\n    c.condeferrable,\n    c.condeferred,\n    c.convalidated\n    FROM pg_class cl, pg_constraint c\n    WHERE format('%I.%I', cl.relnamespace::regnamespace::name, cl.relname) LIKE format('%I.%s', schema_name, table_name)\n-- to avoid showing not null constraints which are new to PG18\n-- https://github.com/postgres/postgres/commit/14e87ffa\n\tAND c.conname NOT LIKE '%not_null%'\n    AND c.conrelid = cl.oid\n    ORDER BY cl.relname, c.conname;\nEND\n$BODY$;\n\nCREATE OR REPLACE FUNCTION test.show_triggers(rel regclass, show_internal boolean = false)\nRETURNS TABLE(\"Trigger\" name,\n              \"Type\" smallint,\n              \"Function\" regproc) LANGUAGE SQL STABLE AS\n$BODY$\n    SELECT t.tgname,\n    t.tgtype,\n    t.tgfoid::regproc\n    FROM pg_trigger t\n    WHERE t.tgrelid = rel\n    AND t.tgisinternal = show_internal\n    ORDER BY t.tgname;\n$BODY$;\n\nCREATE OR REPLACE FUNCTION test.show_triggersp(pattern text, show_internal boolean = false)\nRETURNS TABLE(\"Table\" regclass,\n              \"Trigger\" name,\n              \"Type\" smallint,\n              \"Function\" regproc) LANGUAGE PLPGSQL STABLE AS\n$BODY$\nDECLARE\n    schema_name name = split_part(pattern, '.', 1);\n    table_name name = split_part(pattern, '.', 2);\nBEGIN\n    IF schema_name = '' OR table_name = '' THEN\n       schema_name := current_schema();\n       table_name := pattern;\n    END IF;\n\n    RETURN QUERY\n    SELECT t.tgrelid::regclass,\n    t.tgname,\n    t.tgtype,\n    t.tgfoid::regproc\n    FROM pg_class cl, pg_trigger t\n    WHERE format('%I.%I', cl.relnamespace::regnamespace::name, cl.relname) LIKE format('%I.%s', schema_name, table_name)\n    AND t.tgrelid = cl.oid\n    AND t.tgisinternal = show_internal\n    ORDER BY t.tgrelid, t.tgname;\nEND\n$BODY$;\n\nCREATE OR REPLACE FUNCTION test.show_subtables(rel regclass)\nRETURNS TABLE(\"Child\" regclass,\n              \"Tablespace\" name) LANGUAGE SQL STABLE AS\n$BODY$\n    SELECT objid::regclass, (SELECT t.spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace)\n    FROM pg_depend d, pg_class c\n    WHERE d.refobjid = rel\n    AND d.deptype = 'n'\n    AND d.classid = 'pg_class'::regclass\n    AND d.objid = c.oid\n    ORDER BY d.refobjid, d.objid;\n$BODY$;\n\nCREATE OR REPLACE FUNCTION test.show_subtablesp(pattern text)\nRETURNS TABLE(\"Parent\" regclass,\n              \"Child\" regclass,\n              \"Tablespace\" name) LANGUAGE PLPGSQL STABLE AS\n$BODY$\nDECLARE\n    schema_name name = split_part(pattern, '.', 1);\n    table_name name = split_part(pattern, '.', 2);\nBEGIN\n    IF schema_name = '' OR table_name = '' THEN\n       schema_name := current_schema();\n       table_name := pattern;\n    END IF;\n\n    RETURN QUERY\n    SELECT refobjid::regclass,\n    objid::regclass,\n    (SELECT t.spcname FROM pg_class cc, pg_tablespace t WHERE cc.oid = d.objid AND t.oid = cc.reltablespace)\n    FROM pg_class c, pg_depend d\n    WHERE format('%I.%I', c.relnamespace::regnamespace::name, c.relname) LIKE format('%I.%s', schema_name, table_name)\n    AND d.refobjid = c.oid\n    AND d.deptype = 'n'\n    AND d.classid = 'pg_class'::regclass\n    ORDER BY d.refobjid, d.objid;\nEND\n$BODY$;\n\nCREATE OR REPLACE FUNCTION test.execute_sql(cmd TEXT)\nRETURNS TEXT LANGUAGE PLPGSQL AS $BODY$\nBEGIN\n  EXECUTE cmd;\n  RETURN cmd;\nEND\n$BODY$;\n\n-- Used to set a deterministic memory setting during tests\nCREATE OR REPLACE FUNCTION test.set_memory_cache_size(memory_amount text)\nRETURNS BIGINT AS :MODULE_PATHNAME, 'ts_set_memory_cache_size' LANGUAGE C VOLATILE STRICT;\n\nCREATE OR REPLACE FUNCTION test.make_tablespace_path(prefix TEXT, test_name TEXT)\n       RETURNS TEXT LANGUAGE plpgsql AS\n$BODY$\nDECLARE\n    mkdirFlag TEXT := CASE WHEN sysname = 'Windows' THEN '' ELSE '-p ' END\n        FROM _timescaledb_functions.get_os_info();\n    dirPath TEXT := format('%s%s', prefix, test_name);\n    createDir TEXT := format('mkdir %s%s', mkdirFlag, dirPath);\nBEGIN\n    EXECUTE format('COPY (SELECT 1) TO PROGRAM %s', quote_literal(createDir));\n    RETURN dirPath;\nEND;\n$BODY$;\n\n-- Wait for job to execute with success or failure\nCREATE OR REPLACE FUNCTION test.wait_for_job_to_run(job_param_id INTEGER, expected_runs INTEGER, spins INTEGER=:TEST_SPINWAIT_ITERS)\nRETURNS BOOLEAN LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n    r RECORD;\nBEGIN\n    FOR i in 1..spins\n    LOOP\n        SELECT total_successes, total_failures FROM _timescaledb_internal.bgw_job_stat WHERE job_id=job_param_id INTO r;\n        IF (r.total_failures > 0) THEN\n            RAISE INFO 'wait_for_job_to_run: job execution failed';\n            RETURN false;\n        ELSEIF (r.total_successes = expected_runs) THEN\n            RETURN true;\n        ELSEIF (r.total_successes > expected_runs) THEN\n            RAISE 'num_runs > expected';\n        ELSE\n            PERFORM pg_sleep(0.1);\n        END IF;\n    END LOOP;\n    RAISE INFO 'wait_for_job_to_run: timeout after % tries', spins;\n    RETURN false;\nEND\n$BODY$;\n\n-- Wait for job to run or fail\nCREATE OR REPLACE FUNCTION test.wait_for_job_to_run_or_fail(job_param_id INTEGER, spins INTEGER=:TEST_SPINWAIT_ITERS)\nRETURNS BOOLEAN LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n    r RECORD;\nBEGIN\n    FOR i in 1..spins\n    LOOP\n        SELECT total_runs FROM _timescaledb_internal.bgw_job_stat WHERE job_id=job_param_id INTO r;\n        IF (r.total_runs > 0) THEN\n            RETURN true;\n        ELSE\n            PERFORM pg_sleep(0.1);\n        END IF;\n    END LOOP;\n    RAISE INFO 'wait_for_job_to_run_or_fail: timeout after % tries', spins;\n    RETURN false;\nEND\n$BODY$;\n\nCREATE OR REPLACE VIEW test.extension AS\nSELECT e.extname AS \"Name\",\n       e.extversion AS \"Version\",\n       n.nspname AS \"Schema\",\n       c.description AS \"Description\"\nFROM pg_extension e\nLEFT JOIN pg_namespace n ON n.oid = e.extnamespace\nLEFT JOIN pg_description c ON c.objoid = e.oid AND c.classoid = 'pg_extension'::regclass\nORDER BY 1;\n\nGRANT SELECT ON test.extension TO PUBLIC;\n\n-- View to replace \\dt commands in tests for consistent output across PostgreSQL versions\nCREATE OR REPLACE VIEW test.relation AS\nSELECT\n    n.nspname AS schema,\n    c.relname AS name,\n    CASE c.relkind\n        WHEN 'r' THEN 'table'\n        WHEN 'v' THEN 'view'\n        WHEN 'm' THEN 'materialized view'\n        WHEN 'f' THEN 'foreign table'\n        WHEN 'p' THEN 'partitioned table'\n        ELSE c.relkind::text\n    END AS type,\n    pg_catalog.pg_get_userbyid(c.relowner) AS owner\nFROM pg_catalog.pg_class c\nLEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\nWHERE c.relkind IN ('r','p','v','m','f','')\n  AND n.nspname <> 'information_schema'\n  AND n.nspname <> 'pg_catalog'\n  AND n.nspname !~ '^pg_toast'\nORDER BY 1, 2;\n\nGRANT SELECT ON test.relation TO PUBLIC;\n\n"
  },
  {
    "path": "test/sql/utils/testsupport_init.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nSELECT _timescaledb_functions.stop_background_workers();\n\n-- Cleanup any system job stats that can lead to flaky test\nDELETE FROM _timescaledb_internal.bgw_job_stat_history WHERE job_id < 1000;\nDELETE FROM _timescaledb_internal.bgw_job_stat WHERE job_id < 1000;\n"
  },
  {
    "path": "test/sql/uuid.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n--\n--\n-- Test \"time\" partitioning on UUIDv7\n--\n--\nCREATE TABLE uuid_events(id uuid primary key, device int, temp float);\n\n\\set ON_ERROR_STOP 0\n-- Test invalid interval type\nSELECT create_hypertable('uuid_events', 'id', chunk_time_interval => true);\n\\set ON_ERROR_STOP 1\n\nSELECT create_hypertable('uuid_events', 'id', chunk_time_interval => interval '1 day');\n\nSELECT time_interval\nFROM timescaledb_information.dimensions\nWHERE hypertable_name = 'uuid_events';\n\n--\n-- Test that inserting boundary values generates the right constraints\n-- on chunks.\n--\n\n-- First value with min time: 00000000-0000-7000-8000-000000000000\nBEGIN;\nINSERT INTO uuid_events VALUES ('00000000-0000-7000-8000-000000000000', 1, 1.0);\nSELECT (test.show_constraints(ch)).* from show_chunks('uuid_events') ch;\nSELECT uuid_timestamp(id), device, temp\nFROM uuid_events ORDER BY id;\n\n-- Update v7 UUID to a v4 UUID that doesn't violate the chunk's range\n-- constraint. Currently we don't prevent this \"loophole\".\nUPDATE uuid_events SET id = '00000000-0001-4000-8000-000000000000'\nWHERE id = '00000000-0000-7000-8000-000000000000';\n\nSELECT uuid_timestamp(id), device, temp\nFROM uuid_events ORDER BY id;\n\n-- Update v7 UUID to a v4 that violates the chunk constraint:\n\\set ON_ERROR_STOP 0\nUPDATE uuid_events SET id = 'ffff0000-0000-4000-8000-000000000000'\nWHERE id = '00000000-0001-4000-8000-000000000000';\n\\set ON_ERROR_STOP 1\n\nROLLBACK;\n\n-- Last value with min time: 00000000-0000-7fff-bfff-ffffffffffff\nBEGIN;\nINSERT INTO uuid_events VALUES ('00000000-0000-7fff-bfff-ffffffffffff', 1, 1.0);\nSELECT (test.show_constraints(ch)).* from show_chunks('uuid_events') ch;\nROLLBACK;\n\n-- First value with max time: ffffffff-ffff-7000-8000-000000000000\nBEGIN;\nINSERT INTO uuid_events VALUES ('ffffffff-ffff-7000-8000-000000000000', 1, 1.0);\nSELECT (test.show_constraints(ch)).* from show_chunks('uuid_events') ch;\nROLLBACK;\n\n-- (Max time with min value) + 1\nBEGIN;\nINSERT INTO uuid_events VALUES ('ffffffff-ffff-7000-8000-000000000001', 1, 1.0);\nSELECT (test.show_constraints(ch)).* from show_chunks('uuid_events') ch;\nROLLBACK;\n\n-- Last value with max time: ffffffff-ffff-7fff-bfff-ffffffffffff\nBEGIN;\nINSERT INTO uuid_events VALUES ('ffffffff-ffff-7fff-bfff-ffffffffffff', 1, 1.0);\nSELECT (test.show_constraints(ch)).* from show_chunks('uuid_events') ch;\nROLLBACK;\n\n--\n-- It is possible to generate UUIDs like follows, but the random\n-- generator used doesn't respect setseed() so used constant UUIDs for\n-- determinism.\n--\n-- (_timescaledb_functions.uuid_v7_from_timestamptz('2025-01-01 01:00 PST'), 1, 1.0),\n-- (_timescaledb_functions.uuid_v7_from_timestamptz('2025-01-01 02:00 PST'), 2, 2.0),\n-- (_timescaledb_functions.uuid_v7_from_timestamptz('2025-01-02 01:00 PST'), 3, 3.0),\n-- (_timescaledb_functions.uuid_v7_from_timestamptz('2025-01-02 02:00 PST'), 4, 4.0),\n-- (_timescaledb_functions.uuid_v7_from_timestamptz('2025-01-03 03:00 PST'), 5, 5.0),\n-- (_timescaledb_functions.uuid_v7_from_timestamptz('2025-01-03 10:00 PST'), 6, 6.0);\n--\nINSERT INTO uuid_events VALUES\n       ('0194214e-cd00-7000-a9a7-63f1416dab45', 2, 2.0),\n       ('01942117-de80-7000-8121-f12b2b69dd96', 1, 1.0),\n       ('0194263e-3a80-7000-8f40-82c987b1bc1f', 3, 3.0),\n       ('01942675-2900-7000-8db1-a98694b18785', 4, 4.0),\n       ('01942bd2-7380-7000-9bc4-5f97443907b8', 5, 5.0),\n       ('01942d52-f900-7000-866e-07d6404d53c1', 6, 6.0);\n\nSELECT * FROM show_chunks('uuid_events');\n\nSELECT (test.show_constraints(ch)).* from show_chunks('uuid_events') ch;\nSELECT id, device, temp FROM uuid_events;\n\nSELECT uuid_timestamp(id), device, temp\nFROM uuid_events;\n\nSELECT uuid_timestamp(id), device, temp\nFROM uuid_events ORDER BY id;\n\nSELECT\n    _timescaledb_functions.to_timestamp(range_start) AS range_start,\n    _timescaledb_functions.to_timestamp(range_end) AS range_end\nFROM _timescaledb_catalog.dimension_slice ds\nJOIN _timescaledb_catalog.dimension d ON (ds.dimension_id = d.id)\nJOIN _timescaledb_catalog.hypertable h ON (d.hypertable_id = h.id)\nWHERE h.table_name = 'uuid_events';\n\nSELECT\n    _timescaledb_functions.to_timestamp(range_start) AS chunk_range_start,\n    _timescaledb_functions.to_timestamp(range_end) AS chunk_range_end\nFROM _timescaledb_catalog.dimension_slice ds\nJOIN _timescaledb_catalog.dimension d ON (ds.dimension_id = d.id)\nJOIN _timescaledb_catalog.hypertable h ON (d.hypertable_id = h.id)\nWHERE h.table_name = 'uuid_events'\nLIMIT 1 OFFSET 1 \\gset\n\n-- Test that chunk exclusion on uuidv7 column works\nSELECT :'chunk_range_start',  to_uuidv7_boundary(:'chunk_range_start');\n\n-- Exclude all but one chunk\nEXPLAIN (verbose, buffers off, costs off, timing off)\nSELECT uuid_timestamp(id), device, temp\nFROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_start');\n\nSELECT uuid_timestamp(id), device, temp\nFROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_start') ORDER BY id;\n\n-- Exclude only one chunk. Add ordering (DESC)\nEXPLAIN (verbose, buffers off, costs off, timing off)\nSELECT uuid_timestamp(id), device, temp\nFROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')\nORDER BY id DESC;\n\nSELECT uuid_timestamp(id), device, temp\nFROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')\nORDER BY id DESC;\n\nSELECT time_bucket('1 day', id) AS day, avg(temp)\nFROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')\nGROUP BY id ORDER BY id DESC;\n\nSELECT time_bucket('1 day', uuid_timestamp(id)) AS day, avg(temp)\nFROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')\nGROUP BY id ORDER BY id DESC;\n\n-- Bucket with offset\nSELECT time_bucket('1 day', id, \"offset\" => '1 week') AS day, avg(temp)\nFROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')\nGROUP BY id ORDER BY id DESC;\n\n-- Bucket with origin\nSELECT time_bucket('1 day', id, timestamptz '2000-01-01 00:00') AS day, avg(temp)\nFROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')\nGROUP BY id ORDER BY id DESC;\n\n-- Bucket with time zone\nSELECT time_bucket('1 day', id, 'Europe/Stockholm') at time zone 'Europe/Stockholm' as day, avg(temp)\nFROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')\nGROUP BY id ORDER BY id DESC;\n\n-- Test NULL arguments\nSELECT time_bucket('1 day', id, 'Europe/Stockholm'::text, NULL::timestamptz) AS day, avg(temp)\nFROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')\nGROUP BY id ORDER BY id DESC;\n\nSELECT time_bucket('1 day', id, 'Europe/Stockholm'::text, '2000-01-01 00:00'::timestamptz, NULL::interval) AS day, avg(temp)\nFROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')\nGROUP BY id ORDER BY id DESC;\n\n-- Test UUID time_bucket in WHERE clause. Note that there is currently no chunk\n-- exclusion when using time_bucket() in the WHERE clause qual. This requires\n-- special handling of the time_bucket() transform optimizatios for different\n-- operators.\nEXPLAIN (COSTS OFF) SELECT time_bucket('1 day', id) AS day, avg(temp)\nFROM uuid_events WHERE time_bucket('1 day', id) >= :'chunk_range_end'\nGROUP BY id ORDER BY id DESC;\n\nSELECT time_bucket('1 day', id) AS day, avg(temp)\nFROM uuid_events WHERE time_bucket('1 day', id) >= :'chunk_range_end'\nGROUP BY id ORDER BY id DESC;\n\nEXPLAIN (COSTS OFF) SELECT time_bucket('1 day', id) AS day, avg(temp)\nFROM uuid_events WHERE time_bucket('1 day', id) > :'chunk_range_start'\nGROUP BY id ORDER BY id DESC;\n\nSELECT time_bucket('1 day', id) AS day, avg(temp)\nFROM uuid_events WHERE time_bucket('1 day', id) > :'chunk_range_start'\nGROUP BY id ORDER BY id DESC;\n\nEXPLAIN (COSTS OFF) SELECT time_bucket('1 day', id) AS day, avg(temp)\nFROM uuid_events WHERE time_bucket('1 day', id) < :'chunk_range_end'\nGROUP BY id ORDER BY id DESC;\n\nSELECT time_bucket('1 day', id) AS day, avg(temp)\nFROM uuid_events WHERE time_bucket('1 day', id) < :'chunk_range_end'\nGROUP BY id ORDER BY id DESC;\n\nEXPLAIN (COSTS OFF) SELECT time_bucket('1 day', id) AS day, avg(temp)\nFROM uuid_events WHERE time_bucket('1 day', id) <= :'chunk_range_start'\nGROUP BY id ORDER BY id DESC;\n\nSELECT time_bucket('1 day', id) AS day, avg(temp)\nFROM uuid_events WHERE time_bucket('1 day', id) <= :'chunk_range_start'\nGROUP BY id ORDER BY id DESC;\n\n-- Test time_bucket on non-v7 UUID\n\\set ON_ERROR_STOP 0\nSELECT time_bucket('1 day', 'ffff0000-0000-4000-8000-000000000000'::uuid);\n\\set ON_ERROR_STOP 1\n\nCREATE VIEW chunk_ranges AS\nSELECT\n  chunk_name,\n  range_start,\n  range_end\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'uuid_events';\n\nSELECT * FROM chunk_ranges;\nSELECT show_chunks('uuid_events', older_than => INTERVAL '1 day');\nSELECT show_chunks('uuid_events', older_than => '2025-01-02');\nSELECT show_chunks('uuid_events', newer_than => '2025-01-02');\nSELECT drop_chunks('uuid_events', older_than => '2025-01-02');\nSELECT show_chunks('uuid_events');\n\n-- Insert non-v7 UUIDs\n\\set ON_ERROR_STOP 0\nINSERT INTO uuid_events SELECT 'a8961135-cd89-4c4b-aa05-79df642407dd', 5, 5.0;\n\\set ON_ERROR_STOP 1\n\n-- Insert as v7 UUID and later change to non-v7 to show effect on show_chunks()\n-- and drop_chunks()\nINSERT INTO uuid_events SELECT 'a8961135-cd89-7000-aa05-79df642407dd', 5, 5.0;\nSELECT * FROM chunk_ranges;\nUPDATE uuid_events\nSET id = 'a8961135-cd89-4c4b-aa05-79df642407dd'\nWHERE id = 'a8961135-cd89-7000-aa05-79df642407dd';\n\nSELECT show_chunks('uuid_events', newer_than => '2025-01-02');\nSELECT drop_chunks('uuid_events', newer_than => '2025-01-02');\nSELECT * FROM chunk_ranges;\n\nINSERT INTO uuid_events SELECT to_uuidv7(now()), 6, 6.0;\nSELECT show_chunks('uuid_events', newer_than => INTERVAL '2 months');\nSELECT drop_chunks('uuid_events', newer_than => INTERVAL '2 months');\nSELECT show_chunks('uuid_events', newer_than => INTERVAL '2 months');\n\nDROP TABLE uuid_events;\n\nBEGIN;\n-- Test UUID partition when using CREATE TABLE ... WITH\nCREATE TABLE IF NOT EXISTS events (\n     event_id UUID NOT NULL,\n     entity_id VARCHAR(100) NOT NULL,\n     ts TIMESTAMPTZ NOT NULL,\n     event_type VARCHAR(100) NOT NULL,\n     metadata JSONB,\n     PRIMARY KEY (event_id)\n)\nWITH (\n     tsdb.hypertable,\n     tsdb.partition_column='event_id',\n     tsdb.chunk_interval='2 hours'\n);\n\n-- Verify that the chunk time interval is two hours\nSELECT ((interval_length/1000000)/60)/60 AS hours\nFROM _timescaledb_catalog.dimension d JOIN _timescaledb_catalog.hypertable h ON (h.id = d.hypertable_id)\nWHERE h.table_name='events';\nROLLBACK;\n\n-- Test a different interval\nBEGIN;\nCREATE TABLE IF NOT EXISTS events (\n     event_id UUID NOT NULL,\n     entity_id VARCHAR(100) NOT NULL,\n     ts TIMESTAMPTZ NOT NULL,\n     event_type VARCHAR(100) NOT NULL,\n     metadata JSONB,\n     PRIMARY KEY (event_id)\n)\nWITH (\n     tsdb.hypertable,\n     tsdb.partition_column='event_id',\n     tsdb.chunk_interval='2 months'\n);\n\n-- Verify that the chunk time interval is two hours\nSELECT (((interval_length/1000000)/60)/60)/24 AS days\nFROM _timescaledb_catalog.dimension d JOIN _timescaledb_catalog.hypertable h ON (h.id = d.hypertable_id)\nWHERE h.table_name='events';\nROLLBACK;\n\n-- Verify same behavior without CREATE TABLE WITH:\nBEGIN;\nCREATE TABLE IF NOT EXISTS events (\n     event_id UUID NOT NULL,\n     entity_id VARCHAR(100) NOT NULL,\n     ts TIMESTAMPTZ NOT NULL,\n     event_type VARCHAR(100) NOT NULL,\n     metadata JSONB,\n     PRIMARY KEY (event_id)\n);\n\nSELECT create_hypertable('events', 'event_id', chunk_time_interval => interval '2 hours');\n-- Verify that the chunk time interval is two hours\nSELECT ((interval_length/1000000)/60)/60 AS hours\nFROM _timescaledb_catalog.dimension d JOIN _timescaledb_catalog.hypertable h ON (h.id = d.hypertable_id)\nWHERE h.table_name='events';\nROLLBACK;\n\nBEGIN;\nCREATE TABLE IF NOT EXISTS events (\n     event_id UUID NOT NULL,\n     entity_id VARCHAR(100) NOT NULL,\n     ts TIMESTAMPTZ NOT NULL,\n     event_type VARCHAR(100) NOT NULL,\n     metadata JSONB,\n     PRIMARY KEY (event_id)\n);\n\nSELECT create_hypertable('events', 'event_id', chunk_time_interval => interval '2 months');\n-- Verify that the chunk time interval is two hours\nSELECT (((interval_length/1000000)/60)/60)/24 AS days\nFROM _timescaledb_catalog.dimension d JOIN _timescaledb_catalog.hypertable h ON (h.id = d.hypertable_id)\nWHERE h.table_name='events';\n\nROLLBACK;\n\n\n"
  },
  {
    "path": "test/sql/vacuum.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\nCREATE TABLE vacuum_test(time timestamp, temp float);\n\n-- create hypertable with three chunks\nSELECT create_hypertable('vacuum_test', 'time', chunk_time_interval => 2628000000000, create_default_indexes => false);\n\nINSERT INTO vacuum_test VALUES ('2017-01-20T16:00:01', 17.5),\n                               ('2017-01-21T16:00:01', 19.1),\n                               ('2017-04-20T16:00:01', 89.5),\n                               ('2017-04-21T16:00:01', 17.1),\n                               ('2017-06-20T16:00:01', 18.5),\n                               ('2017-06-21T16:00:01', 11.0);\n\n-- no stats\nSELECT tablename, attname, histogram_bounds, n_distinct FROM pg_stats\nWHERE schemaname = '_timescaledb_internal' AND tablename LIKE '_hyper_%_chunk'\nORDER BY tablename, attname, array_to_string(histogram_bounds, ',');\n\nSELECT tablename, attname, histogram_bounds, n_distinct FROM pg_stats\nWHERE schemaname = 'public' AND tablename LIKE 'vacuum_test'\nORDER BY tablename, attname, array_to_string(histogram_bounds, ',');\n\nVACUUM ANALYZE vacuum_test;\n\n-- stats should exist for all three chunks\nSELECT tablename, attname, histogram_bounds, n_distinct FROM pg_stats\nWHERE schemaname = '_timescaledb_internal' AND tablename LIKE '_hyper_%_chunk'\nORDER BY tablename, attname, array_to_string(histogram_bounds, ',');\n\n-- stats should exist on parent hypertable\nSELECT tablename, attname, histogram_bounds, n_distinct FROM pg_stats\nWHERE schemaname = 'public' AND tablename LIKE 'vacuum_test'\nORDER BY tablename, attname, array_to_string(histogram_bounds, ',');\n\nDROP TABLE vacuum_test;\n\n--test plain analyze (no_vacuum)\nCREATE TABLE analyze_test(time timestamp, temp float);\n\nSELECT create_hypertable('analyze_test', 'time', chunk_time_interval => 2628000000000, create_default_indexes => false);\n\nINSERT INTO analyze_test VALUES ('2017-01-20T16:00:01', 17.5),\n                               ('2017-01-21T16:00:01', 19.1),\n                               ('2017-04-20T16:00:01', 89.5),\n                               ('2017-04-21T16:00:01', 17.1),\n                               ('2017-06-20T16:00:01', 18.5),\n                               ('2017-06-21T16:00:01', 11.0);\n\n-- no stats\nSELECT tablename, attname, histogram_bounds, n_distinct FROM pg_stats\nWHERE schemaname = '_timescaledb_internal' AND tablename LIKE '_hyper_%_chunk'\nORDER BY tablename, attname, array_to_string(histogram_bounds, ',');\n\nSELECT tablename, attname, histogram_bounds, n_distinct FROM pg_stats\nWHERE schemaname = 'public' AND tablename LIKE 'analyze_test'\nORDER BY tablename, attname, array_to_string(histogram_bounds, ',');\n\nANALYZE analyze_test;\n\n-- stats should exist for all three chunks\nSELECT tablename, attname, histogram_bounds, n_distinct FROM pg_stats\nWHERE schemaname = '_timescaledb_internal' AND tablename LIKE '_hyper_%_chunk'\nORDER BY tablename, attname, array_to_string(histogram_bounds, ',');\n\n-- stats should exist on parent hypertable\nSELECT tablename, attname, histogram_bounds, n_distinct FROM pg_stats\nWHERE schemaname = 'public' AND tablename LIKE 'analyze_test'\nORDER BY tablename, attname, array_to_string(histogram_bounds, ',');\nDROP TABLE analyze_test;\n\n-- Run vacuum on a normal (non-hypertable) table\nCREATE TABLE vacuum_norm(time timestamp, temp float);\n\nINSERT INTO vacuum_norm VALUES ('2017-01-20T09:00:01', 17.5),\n                               ('2017-01-21T09:00:01', 19.1),\n                               ('2017-04-20T09:00:01', 89.5),\n                               ('2017-04-21T09:00:01', 17.1),\n                               ('2017-06-20T09:00:01', 18.5),\n                               ('2017-06-21T09:00:01', 11.0);\n\nVACUUM ANALYZE vacuum_norm;\nDROP TABLE vacuum_norm;\n\n--Similar to normal vacuum tests, but PG11 introduced ability to vacuum multiple tables at once, we make sure that works for hypertables as well.\nCREATE TABLE vacuum_test(time timestamp, temp float);\n\n-- create hypertable with three chunks\nSELECT create_hypertable('vacuum_test', 'time', chunk_time_interval => 2628000000000, create_default_indexes => false);\n\nINSERT INTO vacuum_test VALUES ('2017-01-20T16:00:01', 17.5),\n                               ('2017-01-21T16:00:01', 19.1),\n                               ('2017-04-20T16:00:01', 89.5),\n                               ('2017-04-21T16:00:01', 17.1),\n                               ('2017-06-20T16:00:01', 18.5),\n                               ('2017-06-21T16:00:01', 11.0);\nCREATE TABLE analyze_test(time timestamp, temp float);\n\nSELECT create_hypertable('analyze_test', 'time', chunk_time_interval => 2628000000000, create_default_indexes => false);\n\nINSERT INTO analyze_test VALUES ('2017-01-20T16:00:01', 17.5),\n                               ('2017-01-21T16:00:01', 19.1),\n                               ('2017-04-20T16:00:01', 89.5),\n                               ('2017-04-21T16:00:01', 17.1),\n                               ('2017-06-20T16:00:01', 18.5),\n                               ('2017-06-21T16:00:01', 11.0);\n\nCREATE TABLE vacuum_norm(time timestamp, temp float);\n\nINSERT INTO vacuum_norm VALUES ('2017-01-20T09:00:01', 17.5),\n                               ('2017-01-21T09:00:01', 19.1),\n                               ('2017-04-20T09:00:01', 89.5),\n                               ('2017-04-21T09:00:01', 17.1),\n                               ('2017-06-20T09:00:01', 18.5),\n                               ('2017-06-21T09:00:01', 11.0);\n-- no stats\nSELECT tablename, attname, histogram_bounds, n_distinct FROM pg_stats\nWHERE schemaname = '_timescaledb_internal' AND tablename LIKE '_hyper_%_chunk'\nORDER BY tablename, attname, array_to_string(histogram_bounds, ',');\n\nSELECT tablename, attname, histogram_bounds, n_distinct FROM pg_stats\nWHERE schemaname = 'public'\nORDER BY tablename, attname, array_to_string(histogram_bounds, ',');\n\nVACUUM ANALYZE vacuum_norm, vacuum_test, analyze_test;\n\n-- stats should exist for all 6 chunks\nSELECT tablename, attname, histogram_bounds, n_distinct FROM pg_stats\nWHERE schemaname = '_timescaledb_internal' AND tablename LIKE '_hyper_%_chunk'\nORDER BY tablename, attname, array_to_string(histogram_bounds, ',');\n\n-- stats should exist on parent hypertable and normal table\nSELECT tablename, attname, histogram_bounds, n_distinct FROM pg_stats\nWHERE schemaname = 'public'\nORDER BY tablename, attname, array_to_string(histogram_bounds, ',');\n\n"
  },
  {
    "path": "test/sql/vacuum_parallel.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- PG13 introduced parallel VACUUM functionality. It gets invoked when a table\n-- has two or more indexes on it. Read up more at\n-- https://www.postgresql.org/docs/13/sql-vacuum.html#PARALLEL\n\nCREATE TABLE vacuum_test(time timestamp NOT NULL, temp1 float, temp2 int);\n\n-- create hypertable\n-- we create chunks in public schema cause otherwise we would need\n-- elevated privileges to create indexes directly\nSELECT create_hypertable('vacuum_test', 'time', create_default_indexes => false, associated_schema_name => 'public');\n\n-- parallel vacuum needs the index size to be larger than min_parallel_index_scan_size to kick in\nSET min_parallel_index_scan_size TO 0;\nINSERT INTO vacuum_test SELECT TIMESTAMP 'epoch' + (i * INTERVAL '4h'),\n                i, i+1 FROM generate_series(1, 100) as T(i);\n\n-- create indexes on the temp columns\n-- we create indexes manually because otherwise vacuum verbose output\n-- would be different between 13.2 and 13.3+\n-- 13.2 would try to vacuum the parent table index too while 13.3+ wouldn't\nCREATE INDEX ON _hyper_1_1_chunk(time);\nCREATE INDEX ON _hyper_1_1_chunk(temp1);\nCREATE INDEX ON _hyper_1_1_chunk(temp2);\nCREATE INDEX ON _hyper_1_2_chunk(time);\nCREATE INDEX ON _hyper_1_2_chunk(temp1);\nCREATE INDEX ON _hyper_1_2_chunk(temp2);\nCREATE INDEX ON _hyper_1_3_chunk(time);\nCREATE INDEX ON _hyper_1_3_chunk(temp1);\nCREATE INDEX ON _hyper_1_3_chunk(temp2);\n\n-- INSERT only will not trigger vacuum on indexes for PG13.3+\nUPDATE vacuum_test SET time = time + '1s'::interval, temp1 = random(), temp2 = random();\n\n-- we should see two parallel workers for each chunk\nVACUUM (PARALLEL 3) vacuum_test;\n\nDROP TABLE vacuum_test;\n"
  },
  {
    "path": "test/sql/version.sql",
    "content": "-- This file and its contents are licensed under the Apache License 2.0.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-APACHE for a copy of the license.\n\n-- Test that get_os_info returns 3 x text\nselect pg_typeof(sysname) AS sysname_type,pg_typeof(version) AS version_type,pg_typeof(release) AS release_type from _timescaledb_functions.get_os_info();\n"
  },
  {
    "path": "test/src/CMakeLists.txt",
    "content": "set(SOURCES\n    adt_tests.c\n    metadata.c\n    symbol_conflict.c\n    test_compression_settings.c\n    test_bmslist_utils.c\n    test_jsonb_utils.c\n    test_scanner.c\n    test_time_to_internal.c\n    test_time_utils.c\n    test_tss_callbacks.c\n    test_utils.c\n    test_with_clause_parser.c)\n\ninclude(${PROJECT_SOURCE_DIR}/src/build-defs.cmake)\n\nadd_library(${TESTS_LIB_NAME} OBJECT ${SOURCES})\n\n# Since the test library will be linked into the loadable extension module, it\n# needs to be compiled as position-independent code (e.g., the -fPIC compiler\n# flag for GCC)\nset_target_properties(${TESTS_LIB_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON)\n\n# Set the MODULE_NAME for the symbol conflict test (see symbol_conflict.c)\ntarget_compile_definitions(${TESTS_LIB_NAME} PUBLIC MODULE_NAME=timescaledb)\ntarget_include_directories(${TESTS_LIB_NAME}\n                           PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})\n\nadd_subdirectory(bgw)\nadd_subdirectory(net)\nif(USE_TELEMETRY)\n  add_subdirectory(telemetry)\nendif()\n\nadd_subdirectory(loader)\n"
  },
  {
    "path": "test/src/adt_tests.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <fmgr.h>\n\n#include \"export.h\"\n\n#include \"test_utils.h\"\n\nTS_FUNCTION_INFO_V1(ts_test_adts);\n\n#define VEC_PREFIX int32\n#define VEC_ELEMENT_TYPE int32\n#define VEC_DECLARE 1\n#define VEC_DEFINE 1\n#define VEC_SCOPE static inline\n#include <adts/vec.h>\n\n/* We have to stub this for the unit tests. */\n#ifndef CheckCompressedData\n#define CheckCompressedData(X) Assert(X)\n#define GLOBAL_MAX_ROWS_PER_COMPRESSION 1015\n#endif\n\n#include <adts/bit_array.h>\n\nstatic void\ni32_vec_test(void)\n{\n\tint32_vec *vec = int32_vec_create(CurrentMemoryContext, 0);\n\tint i;\n\tuint32 old_capacity;\n\n\tfor (i = 0; i < 100; i++)\n\t\tint32_vec_append(vec, i);\n\n\tTestAssertInt64Eq(vec->num_elements, 100);\n\n\tif (vec->max_elements < 100)\n\t\telog(ERROR, \"vec capacity %d, should be at least 100\", vec->max_elements);\n\n\tfor (i = 0; i < 100; i++)\n\t\tTestAssertInt64Eq(*int32_vec_at(vec, i), i);\n\n\tTestAssertPtrEq(int32_vec_last(vec), int32_vec_at(vec, vec->num_elements - 1));\n\n\told_capacity = vec->max_elements;\n\tint32_vec_delete_range(vec, 30, 19);\n\tTestAssertInt64Eq(vec->num_elements, 81);\n\tTestAssertInt64Eq(vec->max_elements, old_capacity);\n\n\tfor (i = 0; i < 30; i++)\n\t\tTestAssertInt64Eq(*int32_vec_at(vec, i), i);\n\n\tfor (; i < 51; i++)\n\t\tTestAssertInt64Eq(*int32_vec_at(vec, i), i + 19);\n\n\tTestAssertPtrEq(int32_vec_last(vec), int32_vec_at(vec, vec->num_elements - 1));\n\n\tint32_vec_clear(vec);\n\tTestAssertInt64Eq(vec->num_elements, 0);\n\tTestAssertInt64Eq(vec->max_elements, old_capacity);\n\n\tint32_vec_free_data(vec);\n\tTestAssertInt64Eq(vec->num_elements, 0);\n\tTestAssertInt64Eq(vec->max_elements, 0);\n\tTestAssertPtrEq(vec->data, NULL);\n\n\t/* free_data is idempotent */\n\tint32_vec_free_data(vec);\n\tTestAssertInt64Eq(vec->num_elements, 0);\n\tTestAssertInt64Eq(vec->max_elements, 0);\n\tTestAssertPtrEq(vec->data, NULL);\n\n\tint32_vec_free(vec);\n}\n\n/* including BitArray should give us uint64_vec */\nstatic void\nuint64_vec_test(void)\n{\n\tuint64_vec vec;\n\tint i;\n\tuint64_vec_init(&vec, CurrentMemoryContext, 100);\n\tfor (i = 0; i < 30; i++)\n\t\tuint64_vec_append(&vec, i + 3);\n\n\tTestAssertInt64Eq(vec.num_elements, 30);\n\tTestAssertInt64Eq(vec.max_elements, 100);\n\tfor (i = 0; i < 30; i++)\n\t\tTestAssertInt64Eq(*uint64_vec_at(&vec, i), i + 3);\n\n\tuint64_vec_free_data(&vec);\n\tTestAssertInt64Eq(vec.num_elements, 0);\n\tTestAssertInt64Eq(vec.max_elements, 0);\n\tTestAssertPtrEq(vec.data, NULL);\n}\n\nstatic void\nbit_array_test(void)\n{\n\tBitArray bits;\n\tBitArrayIterator iter;\n\tint i;\n\tbit_array_init(&bits, 0);\n\n\tfor (i = 0; i < 65; i++)\n\t\tbit_array_append(&bits, i, i);\n\n\tbit_array_append(&bits, 0, 0);\n\tbit_array_append(&bits, 0, 0);\n\tbit_array_append(&bits, 64, 0x9069060909009090);\n\tbit_array_append(&bits, 1, 0);\n\tbit_array_append(&bits, 64, ~0x9069060909009090);\n\tbit_array_append(&bits, 1, 1);\n\n\tbit_array_iterator_init(&iter, &bits);\n\tfor (i = 0; i < 65; i++)\n\t\tTestAssertInt64Eq(bit_array_iter_next(&iter, i), i);\n\n\tTestAssertInt64Eq(bit_array_iter_next(&iter, 0), 0);\n\tTestAssertInt64Eq(bit_array_iter_next(&iter, 0), 0);\n\tTestAssertInt64Eq(bit_array_iter_next(&iter, 64), 0x9069060909009090);\n\tTestAssertInt64Eq(bit_array_iter_next(&iter, 1), 0);\n\tTestAssertInt64Eq(bit_array_iter_next(&iter, 64), ~0x9069060909009090);\n\tTestAssertInt64Eq(bit_array_iter_next(&iter, 1), 1);\n\n\tbit_array_iterator_init_rev(&iter, &bits);\n\tTestAssertInt64Eq(bit_array_iter_next_rev(&iter, 1), 1);\n\tTestAssertInt64Eq(bit_array_iter_next_rev(&iter, 64), ~0x9069060909009090);\n\tTestAssertInt64Eq(bit_array_iter_next_rev(&iter, 1), 0);\n\tTestAssertInt64Eq(bit_array_iter_next_rev(&iter, 64), 0x9069060909009090);\n\tTestAssertInt64Eq(bit_array_iter_next_rev(&iter, 0), 0);\n\tTestAssertInt64Eq(bit_array_iter_next_rev(&iter, 0), 0);\n\tfor (i = 64; i >= 0; i--)\n\t\tTestAssertInt64Eq(bit_array_iter_next_rev(&iter, i), i);\n}\n\nDatum\nts_test_adts(PG_FUNCTION_ARGS)\n{\n\ti32_vec_test();\n\tuint64_vec_test();\n\tbit_array_test();\n\tPG_RETURN_VOID();\n}\n"
  },
  {
    "path": "test/src/bgw/CMakeLists.txt",
    "content": "set(SOURCES\n    ${CMAKE_CURRENT_SOURCE_DIR}/log.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/timer_mock.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/scheduler_mock.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/params.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/test_job_refresh.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/test_job_utils.c)\n\ntarget_sources(${TESTS_LIB_NAME} PRIVATE ${SOURCES})\n"
  },
  {
    "path": "test/src/bgw/README.md",
    "content": "# Background Worker Test Infrastructure\r\n\r\nThis directory contains mocks and hooks to enable testing of timescale\r\nbackground workers. There are three main components: a counter-based timer used\r\nto test the scheduler in a deterministic manner; a scheduler that inserts test\r\nshims to the background worker and understands the tests we will run; and shims\r\nto dynamically set time and intercept background worker output.\r\n\r\n## Output\r\n\r\nBackground workers started by the test scheduler contain a hook storing all\r\n`elog` and `ereport` output in the table\r\n\r\n```SQL\r\npublic.bgw_log(\r\n    msg_no INT,\r\n    mock_time BIGINT,\r\n    application_name TEXT,\r\n    msg TEXT,\r\n)\r\n```\r\n\r\nwhich must be created in order for tests to check background worker output.\r\n`msg_no` contains which message this was in the total-order of all background\r\nworker messages sent since the table was created; `mock_time` is the\r\nvirtualized timestamp ([see that section](## Timer)) at which the message was\r\nwritten; `application_name` the name of the application that wrote the message;\r\n`msg` is the messgage string itself.\r\n\r\nSee [`log.c`](log.c) for more detail.\r\n\r\n(We want to print more data from `ErrorData` at a later date)\r\n\r\n## Timer\r\n\r\nWe virtualize the timer to allow deterministic execution by tests. Our timer\r\nstore a virtual microsecond counter in shared memory, backround processes can\r\nread this counter to determine the current time. The scheduler can \"wait\" on\r\nthis timer which optionally waits for a process to finish and updates the counter\r\nto the waited time. The timer can be reset manually (for instance, to allow multiple\r\ntests in one file) with `ts_bgw_params_reset_time`).\r\n\r\nSee [`timer_mock.c`](timer_mock.c) for more detail.\r\n\r\n## Configuration\r\n\r\nSettings and the timer for background worker tests are stored in shared memory.\r\nThis memory segment must be created with `ts_bgw_params_create` before use.\r\n\r\nsee [params.c](params.c) and [params.h](params.h) for more detail.\r\n"
  },
  {
    "path": "test/src/bgw/log.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/xact.h>\n#include <catalog/namespace.h>\n#include <postmaster/bgworker.h>\n#include <storage/proc.h>\n#include <utils/builtins.h>\n#include <utils/lsyscache.h>\n#include <utils/snapmgr.h>\n\n#include \"log.h\"\n#include \"params.h\"\n#include \"scanner.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"utils.h\"\n\n#include \"compat/compat.h\"\n\nstatic char *bgw_application_name = \"unset\";\n\nvoid\nts_bgw_log_set_application_name(char *name)\n{\n\tbgw_application_name = name;\n}\n\nstatic bool\nbgw_log_insert_relation(Relation rel, char *msg)\n{\n\tTupleDesc desc = RelationGetDescr(rel);\n\tstatic int32 msg_no = 0;\n\tDatum values[4];\n\tbool nulls[4] = { false, false, false };\n\n\tvalues[0] = Int32GetDatum(msg_no++);\n\tvalues[1] = Int64GetDatum(ts_params_get()->current_time);\n\tvalues[2] = CStringGetTextDatum(bgw_application_name);\n\tvalues[3] = CStringGetTextDatum(msg);\n\n\tts_catalog_insert_values(rel, desc, values, nulls);\n\n\treturn true;\n}\n\n/* Insert a new entry into public.bgw_log\n * This table is used for testing as a way for mock background jobs\n * to insert messages into a log that could then be output into the golden file\n */\nstatic void\nbgw_log_insert(char *msg)\n{\n\tRelation rel;\n\tPushActiveSnapshot(GetTransactionSnapshot());\n\n\tOid log_oid = ts_get_relation_relid(\"public\", \"bgw_log\", false);\n\n\trel = table_open(log_oid, RowExclusiveLock);\n\tbgw_log_insert_relation(rel, msg);\n\ttable_close(rel, RowExclusiveLock);\n\tPopActiveSnapshot();\n}\n\nstatic emit_log_hook_type prev_emit_log_hook = NULL;\n\n/*\n * NOTE: using transactions in emit_log_hook functions is not recommended.\n * However we rely on this current functionality for our test verifications,\n * so have to live with it for now.\n */\nstatic void\nemit_log_hook_callback(ErrorData *edata)\n{\n\t/*\n\t * once proc_exit has started we may no longer be able to start transactions\n\t */\n\tif (MyProc == NULL)\n\t\treturn;\n\n\t/*\n\t * Block signals so we don't lose messages generated during signal\n\t * processing if they occur while we are saving this log message (since\n\t * emit_log_hook is modified and restored below)\n\t */\n\tBackgroundWorkerBlockSignals();\n\tPG_TRY();\n\t{\n\t\t/*\n\t\t * If we do encounter some error writing to our log hook, remove the\n\t\t * hook to prevent potentially infinite recursion where this callback\n\t\t * keeps encountering an error, and it is its own logging callback. We\n\t\t * reinstall the hook when we're successfully done with this function.\n\t\t */\n\t\temit_log_hook = NULL;\n\n\t\tbool started_txn = false;\n\n\t\tif (!IsTransactionState())\n\t\t{\n\t\t\tStartTransactionCommand();\n\t\t\tstarted_txn = true;\n\t\t}\n\n\t\tbgw_log_insert(edata->message);\n\n\t\tif (started_txn)\n\t\t\tCommitTransactionCommand();\n\n\t\tif (prev_emit_log_hook != NULL)\n\t\t\tprev_emit_log_hook(edata);\n\n\t\t/* Reinstall the hook if log was successful. */\n\t\temit_log_hook = emit_log_hook_callback;\n\t}\n\tPG_CATCH();\n\t{\n\t\t/* If there was an error, rollback what was done before the error */\n\t\tif (IsTransactionState())\n\t\t\tAbortCurrentTransaction();\n\n\t\t/*\n\t\t * Reinstall the hook because we are out of the main body of the\n\t\t * function.\n\t\t */\n\t\temit_log_hook = emit_log_hook_callback;\n\t}\n\tPG_END_TRY();\n\tBackgroundWorkerUnblockSignals();\n}\n\nvoid\nts_register_emit_log_hook()\n{\n\tprev_emit_log_hook = emit_log_hook;\n\temit_log_hook = emit_log_hook_callback;\n}\n"
  },
  {
    "path": "test/src/bgw/log.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\nextern void ts_bgw_log_set_application_name(char *name);\nextern void ts_register_emit_log_hook(void);\n"
  },
  {
    "path": "test/src/bgw/params.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/relscan.h>\n#include <access/xact.h>\n#include <catalog/namespace.h>\n#include <storage/bufmgr.h>\n#include <storage/dsm.h>\n#include <storage/lmgr.h>\n#include <storage/spin.h>\n#include <utils/builtins.h>\n#include <utils/lsyscache.h>\n#include <utils/rel.h>\n\n#include \"log.h\"\n#include \"params.h\"\n#include \"scanner.h\"\n#include \"test_utils.h\"\n#include \"timer_mock.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"utils.h\"\n\ntypedef struct FormData_bgw_dsm_handle\n{\n\t/* handle is actually a uint32 */\n\tint64 handle;\n} FormData_bgw_dsm_handle;\n\ntypedef struct TestParamsWrapper\n{\n\tTestParams params;\n\tslock_t mutex;\n} TestParamsWrapper;\n\nstatic Oid\nget_dsm_handle_table_oid()\n{\n\treturn ts_get_relation_relid(\"public\", \"bgw_dsm_handle_store\", false);\n}\n\nstatic void\nparams_register_dsm_handle(dsm_handle handle)\n{\n\tRelation rel;\n\tTableScanDesc scan;\n\tHeapTuple tuple;\n\tFormData_bgw_dsm_handle *fd;\n\n\trel = table_open(get_dsm_handle_table_oid(), RowExclusiveLock);\n\tscan = table_beginscan(rel, SnapshotSelf, 0, NULL);\n\ttuple = heap_copytuple(heap_getnext(scan, ForwardScanDirection));\n\tfd = (FormData_bgw_dsm_handle *) GETSTRUCT(tuple);\n\tfd->handle = handle;\n\tts_catalog_update(rel, tuple);\n\theap_freetuple(tuple);\n\ttable_endscan(scan);\n\ttable_close(rel, RowExclusiveLock);\n}\n\nstatic dsm_handle\nparams_load_dsm_handle()\n{\n\tRelation rel;\n\tTableScanDesc scan;\n\tHeapTuple tuple;\n\tFormData_bgw_dsm_handle *fd;\n\tdsm_handle handle;\n\n\trel = table_open(get_dsm_handle_table_oid(), RowExclusiveLock);\n\tscan = table_beginscan(rel, SnapshotSelf, 0, NULL);\n\ttuple = heap_getnext(scan, ForwardScanDirection);\n\tTestAssertTrue(tuple != NULL);\n\ttuple = heap_copytuple(tuple);\n\tfd = (FormData_bgw_dsm_handle *) GETSTRUCT(tuple);\n\thandle = fd->handle;\n\theap_freetuple(tuple);\n\ttable_endscan(scan);\n\ttable_close(rel, RowExclusiveLock);\n\n\treturn handle;\n}\n\nstatic dsm_handle\nparams_get_dsm_handle()\n{\n\tstatic dsm_handle handle = 0;\n\n\tif (handle == 0)\n\t\thandle = params_load_dsm_handle();\n\n\treturn handle;\n}\n\nstatic TestParamsWrapper *\nparams_open_wrapper(bool *do_close)\n{\n\tdsm_segment *seg;\n\tdsm_handle handle = params_get_dsm_handle();\n\tTestParamsWrapper *wrapper;\n\n\t/*\n\t * If segment is returned via the mapping then there's no need to call\n\t * dsm_detach on it in params_close_wrapper\n\t */\n\tseg = dsm_find_mapping(handle);\n\tif (seg == NULL)\n\t{\n\t\tseg = dsm_attach(handle);\n\t\tif (seg == NULL)\n\t\t\telog(ERROR, \"got NULL segment in params_open_wrapper\");\n\t\t*do_close = true;\n\t}\n\telse\n\t\t*do_close = false;\n\n\tTestAssertTrue(seg != NULL);\n\n\twrapper = dsm_segment_address(seg);\n\n\tTestAssertTrue(wrapper != NULL);\n\n\treturn wrapper;\n};\n\nstatic void\nparams_close_wrapper(TestParamsWrapper *wrapper)\n{\n\tdsm_segment *seg = dsm_find_mapping(params_get_dsm_handle());\n\n\tTestAssertTrue(seg != NULL);\n\tdsm_detach(seg);\n}\n\nTestParams *\nts_params_get()\n{\n\tbool do_close;\n\tTestParamsWrapper *wrapper = params_open_wrapper(&do_close);\n\tTestParams *res;\n\n\tTestAssertTrue(wrapper != NULL);\n\n\tres = palloc(sizeof(TestParams));\n\n\tSpinLockAcquire(&wrapper->mutex);\n\n\tmemcpy(res, &wrapper->params, sizeof(TestParams));\n\n\tSpinLockRelease(&wrapper->mutex);\n\n\tif (do_close)\n\t\tparams_close_wrapper(wrapper);\n\n\treturn res;\n};\n\nvoid\nts_params_set_time(int64 new_val, bool set_latch)\n{\n\tbool do_close;\n\tTestParamsWrapper *wrapper = params_open_wrapper(&do_close);\n\n\tTestAssertTrue(wrapper != NULL);\n\n\tSpinLockAcquire(&wrapper->mutex);\n\twrapper->params.current_time = new_val;\n\tSpinLockRelease(&wrapper->mutex);\n\n\tif (set_latch)\n\t\tSetLatch(&wrapper->params.timer_latch);\n\n\tif (do_close)\n\t\tparams_close_wrapper(wrapper);\n}\n\nvoid\nts_initialize_timer_latch()\n{\n\tbool do_close;\n\tTestParamsWrapper *wrapper = params_open_wrapper(&do_close);\n\n\tTestAssertTrue(wrapper != NULL);\n\n\tSpinLockAcquire(&wrapper->mutex);\n\n\tInitLatch(&wrapper->params.timer_latch);\n\n\tSpinLockRelease(&wrapper->mutex);\n\n\tif (do_close)\n\t\tparams_close_wrapper(wrapper);\n}\n\nvoid\nts_reset_and_wait_timer_latch()\n{\n\tbool do_close;\n\tTestParamsWrapper *wrapper = params_open_wrapper(&do_close);\n\n\tTestAssertTrue(wrapper != NULL);\n\n\tResetLatch(&wrapper->params.timer_latch);\n\tWaitLatch(&wrapper->params.timer_latch,\n\t\t\t  WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,\n\t\t\t  10000,\n\t\t\t  PG_WAIT_EXTENSION);\n\n\tif (do_close)\n\t\tparams_close_wrapper(wrapper);\n}\n\nstatic void\nparams_set_mock_wait_type(MockWaitType new_val)\n{\n\tbool do_close;\n\tTestParamsWrapper *wrapper = params_open_wrapper(&do_close);\n\n\tTestAssertTrue(wrapper != NULL);\n\n\tSpinLockAcquire(&wrapper->mutex);\n\n\twrapper->params.mock_wait_type = new_val;\n\n\tSpinLockRelease(&wrapper->mutex);\n\n\tif (do_close)\n\t\tparams_close_wrapper(wrapper);\n}\n\nTS_FUNCTION_INFO_V1(ts_bgw_params_reset_time);\nDatum\nts_bgw_params_reset_time(PG_FUNCTION_ARGS)\n{\n\tts_params_set_time(PG_GETARG_INT64(0), PG_GETARG_BOOL(1));\n\n\tPG_RETURN_VOID();\n}\n\nTS_FUNCTION_INFO_V1(ts_bgw_params_mock_wait_returns_immediately);\nDatum\nts_bgw_params_mock_wait_returns_immediately(PG_FUNCTION_ARGS)\n{\n\tparams_set_mock_wait_type(PG_GETARG_INT32(0));\n\n\tPG_RETURN_VOID();\n}\n\nTS_FUNCTION_INFO_V1(ts_bgw_params_create);\nDatum\nts_bgw_params_create(PG_FUNCTION_ARGS)\n{\n\tdsm_segment *seg = dsm_create(sizeof(TestParamsWrapper), 0);\n\tTestParamsWrapper *params;\n\n\tTestAssertTrue(seg != NULL);\n\n\tparams = dsm_segment_address(seg);\n\t*params = (TestParamsWrapper)\n\t{\n\t\t.params =\n\t\t{\n\t\t\t.current_time = 0,\n\t\t},\n\t};\n\tSpinLockInit(&params->mutex);\n\n\tparams_register_dsm_handle(dsm_segment_handle(seg));\n\n\tdsm_pin_mapping(seg);\n\tdsm_pin_segment(seg);\n\n\tPG_RETURN_VOID();\n}\n\nTS_FUNCTION_INFO_V1(ts_bgw_params_destroy);\nDatum\nts_bgw_params_destroy(PG_FUNCTION_ARGS)\n{\n\t/*\n\t * Removing shared memory segment unpin for now because:\n\t * 1) This can fail in EXEC_BACKEND cases.\n\t * 2) There's no way to unpin in PG9.6.\n\t * 3) The EXEC_BACKEND compile-time flag is not correctly passed down.\n\t * 4) This should only affect tests, not actual DB functionality.\n\t * #if PG10 && !defined(EXEC_BACKEND)\n\t *\tdsm_unpin_segment(params_get_dsm_handle());\n\t * #endif\n\t */\n\tPG_RETURN_VOID();\n}\n"
  },
  {
    "path": "test/src/bgw/params.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <storage/latch.h>\n\ntypedef enum MockWaitType\n{\n\tWAIT_ON_JOB = 0,\n\tIMMEDIATELY_SET_UNTIL,\n\tWAIT_FOR_OTHER_TO_ADVANCE,\n\tWAIT_FOR_STANDARD_WAITLATCH,\n\t_MAX_MOCK_WAIT_TYPE\n} MockWaitType;\n\ntypedef struct TestParams\n{\n\tLatch timer_latch;\n\tint64 current_time;\n\tMockWaitType mock_wait_type;\n} TestParams;\n\nextern TestParams *ts_params_get(void);\nextern void ts_params_set_time(int64 new_val, bool set_latch);\nextern void ts_initialize_timer_latch(void);\nextern void ts_reset_and_wait_timer_latch(void);\n"
  },
  {
    "path": "test/src/bgw/scheduler_mock.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n\n#include <access/xact.h>\n#include <miscadmin.h>\n#include <pgstat.h>\n#include <postmaster/bgworker.h>\n#include <signal.h>\n#include <storage/ipc.h>\n#include <storage/latch.h>\n#include <storage/lmgr.h>\n#include <storage/lwlock.h>\n#include <storage/proc.h>\n#include <storage/shmem.h>\n#include <utils/builtins.h>\n#include <utils/errcodes.h>\n#include <utils/guc.h>\n#include <utils/jsonb.h>\n#include <utils/memutils.h>\n#include <utils/snapmgr.h>\n#include <utils/timestamp.h>\n\n#include \"bgw/job.h\"\n#include \"bgw/job_stat.h\"\n#include \"bgw/scheduler.h\"\n#include \"cross_module_fn.h\"\n#include \"extension.h\"\n#include \"log.h\"\n#include \"params.h\"\n#include \"test_utils.h\"\n#include \"time_bucket.h\"\n#include \"timer_mock.h\"\n\nTS_FUNCTION_INFO_V1(ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish);\nTS_FUNCTION_INFO_V1(ts_bgw_db_scheduler_test_run);\nTS_FUNCTION_INFO_V1(ts_bgw_db_scheduler_test_wait_for_scheduler_finish);\nTS_FUNCTION_INFO_V1(ts_bgw_db_scheduler_test_main);\nTS_FUNCTION_INFO_V1(ts_bgw_job_execute_test);\n/* function for testing the correctness of the next_scheduled_slot calculation */\nTS_FUNCTION_INFO_V1(ts_test_next_scheduled_execution_slot);\n\ntypedef enum TestJobType\n{\n\tTEST_JOB_TYPE_JOB_1 = 0,\n\tTEST_JOB_TYPE_JOB_2_ERROR,\n\tTEST_JOB_TYPE_JOB_3_LONG,\n\tTEST_JOB_TYPE_JOB_4,\n\t_MAX_TEST_JOB_TYPE\n} TestJobType;\n\nstatic const char *test_job_type_names[_MAX_TEST_JOB_TYPE] = {\n\t[TEST_JOB_TYPE_JOB_1] = \"bgw_test_job_1\",\n\t[TEST_JOB_TYPE_JOB_2_ERROR] = \"bgw_test_job_2_error\",\n\t[TEST_JOB_TYPE_JOB_3_LONG] = \"bgw_test_job_3_long\",\n\t[TEST_JOB_TYPE_JOB_4] = \"bgw_test_job_4\",\n};\n\n/* this is copied from the job_stat/ts_get_next_scheduled_execution_slot */\nextern Datum\nts_test_next_scheduled_execution_slot(PG_FUNCTION_ARGS)\n{\n\tInterval *schedule_interval = PG_GETARG_INTERVAL_P(0);\n\tTimestampTz finish_time = PG_GETARG_TIMESTAMPTZ(1);\n\tTimestampTz initial_start = PG_GETARG_TIMESTAMPTZ(2);\n\ttext *timezone = PG_ARGISNULL(3) ? NULL : PG_GETARG_TEXT_PP(3);\n\n\tDatum timebucket_fini, timebucket_init, result;\n\tDatum schedint_datum = IntervalPGetDatum(schedule_interval);\n\tInterval one_month = {\n\t\t.day = 0,\n\t\t.time = 0,\n\t\t.month = 1,\n\t};\n\n\tif (schedule_interval->month > 0)\n\t{\n\t\tif (timezone == NULL)\n\t\t{\n\t\t\ttimebucket_init = DirectFunctionCall2(ts_timestamptz_bucket,\n\t\t\t\t\t\t\t\t\t\t\t\t  schedint_datum,\n\t\t\t\t\t\t\t\t\t\t\t\t  TimestampTzGetDatum(initial_start));\n\t\t\ttimebucket_fini = DirectFunctionCall2(ts_timestamptz_bucket,\n\t\t\t\t\t\t\t\t\t\t\t\t  schedint_datum,\n\t\t\t\t\t\t\t\t\t\t\t\t  TimestampTzGetDatum(finish_time));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tchar *tz = text_to_cstring(timezone);\n\t\t\ttimebucket_fini = DirectFunctionCall3(ts_timestamptz_timezone_bucket,\n\t\t\t\t\t\t\t\t\t\t\t\t  schedint_datum,\n\t\t\t\t\t\t\t\t\t\t\t\t  TimestampTzGetDatum(finish_time),\n\t\t\t\t\t\t\t\t\t\t\t\t  CStringGetTextDatum(tz));\n\n\t\t\ttimebucket_init = DirectFunctionCall3(ts_timestamptz_timezone_bucket,\n\t\t\t\t\t\t\t\t\t\t\t\t  schedint_datum,\n\t\t\t\t\t\t\t\t\t\t\t\t  TimestampTzGetDatum(initial_start),\n\t\t\t\t\t\t\t\t\t\t\t\t  CStringGetTextDatum(tz));\n\t\t}\n\t\t/* always the next bucket */\n\t\ttimebucket_fini =\n\t\t\tDirectFunctionCall2(timestamptz_pl_interval, timebucket_fini, schedint_datum);\n\t\t/* get the number of months between them */\n\t\tDatum year_init =\n\t\t\tDirectFunctionCall2(timestamptz_part, CStringGetTextDatum(\"year\"), timebucket_init);\n\t\tDatum year_fini =\n\t\t\tDirectFunctionCall2(timestamptz_part, CStringGetTextDatum(\"year\"), timebucket_fini);\n\n\t\tDatum month_init =\n\t\t\tDirectFunctionCall2(timestamptz_part, CStringGetTextDatum(\"month\"), timebucket_init);\n\t\tDatum month_fini =\n\t\t\tDirectFunctionCall2(timestamptz_part, CStringGetTextDatum(\"month\"), timebucket_fini);\n\n\t\t/* convert everything to months */\n\t\tfloat8 month_diff = (DatumGetFloat8(year_fini) * 12) + DatumGetFloat8(month_fini) -\n\t\t\t\t\t\t\t((DatumGetFloat8(year_init) * 12) + DatumGetFloat8(month_init));\n\n\t\tDatum months_to_add = DirectFunctionCall2(interval_mul,\n\t\t\t\t\t\t\t\t\t\t\t\t  IntervalPGetDatum(&one_month),\n\t\t\t\t\t\t\t\t\t\t\t\t  Float8GetDatum(month_diff));\n\t\tresult = DirectFunctionCall2(timestamptz_pl_interval,\n\t\t\t\t\t\t\t\t\t TimestampTzGetDatum(initial_start),\n\t\t\t\t\t\t\t\t\t months_to_add);\n\t}\n\telse\n\t{\n\t\tif (timezone == NULL)\n\t\t{\n\t\t\t/* it is safe to use the origin in time_bucket calculation */\n\t\t\ttimebucket_fini = DirectFunctionCall3(ts_timestamptz_bucket,\n\t\t\t\t\t\t\t\t\t\t\t\t  schedint_datum,\n\t\t\t\t\t\t\t\t\t\t\t\t  TimestampTzGetDatum(finish_time),\n\t\t\t\t\t\t\t\t\t\t\t\t  TimestampTzGetDatum(initial_start));\n\t\t\tresult = timebucket_fini;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tchar *tz = text_to_cstring(timezone);\n\t\t\ttimebucket_fini = DirectFunctionCall4(ts_timestamptz_timezone_bucket,\n\t\t\t\t\t\t\t\t\t\t\t\t  schedint_datum,\n\t\t\t\t\t\t\t\t\t\t\t\t  TimestampTzGetDatum(finish_time),\n\t\t\t\t\t\t\t\t\t\t\t\t  CStringGetTextDatum(tz),\n\t\t\t\t\t\t\t\t\t\t\t\t  TimestampTzGetDatum(initial_start));\n\t\t\tresult = timebucket_fini;\n\t\t}\n\t}\n\twhile (DatumGetTimestampTz(result) <= finish_time)\n\t{\n\t\tresult = DirectFunctionCall2(timestamptz_pl_interval, result, schedint_datum);\n\t}\n\treturn result;\n}\n\nextern Datum\nts_bgw_db_scheduler_test_main(PG_FUNCTION_ARGS)\n{\n\tOid db_oid = DatumGetObjectId(MyBgworkerEntry->bgw_main_arg);\n\tBgwParams bgw_params;\n\n\tBackgroundWorkerBlockSignals();\n\t/* Setup any signal handlers here */\n\tts_bgw_scheduler_register_signal_handlers();\n\tBackgroundWorkerUnblockSignals();\n\tts_bgw_scheduler_setup_callbacks();\n\n\tmemcpy(&bgw_params, MyBgworkerEntry->bgw_extra, sizeof(bgw_params));\n\n\telog(NOTICE, \"scheduler user id %u\", bgw_params.user_oid);\n\telog(NOTICE, \"running a test in the background: db=%u ttl=%d\", db_oid, bgw_params.ttl);\n\n\tBackgroundWorkerInitializeConnectionByOid(db_oid, bgw_params.user_oid, 0);\n\n\tStartTransactionCommand();\n\tts_params_get();\n\tts_initialize_timer_latch();\n\tCommitTransactionCommand();\n\n\tts_bgw_log_set_application_name(\"DB Scheduler\");\n\tts_register_emit_log_hook();\n\n\tts_timer_set(&ts_mock_timer);\n\n\tts_bgw_job_set_job_entrypoint_function_name(\"ts_bgw_job_execute_test\");\n\n\tpgstat_report_appname(\"DB Scheduler Test\");\n\n\tts_bgw_scheduler_setup_mctx();\n\n\tts_bgw_scheduler_process(bgw_params.ttl, ts_timer_mock_register_bgw_handle);\n\n\tPG_RETURN_VOID();\n}\n\nstatic BackgroundWorkerHandle *\nstart_test_scheduler(int32 ttl, Oid user_oid)\n{\n\tconst BgwParams bgw_params = {\n\t\t.bgw_main = \"ts_bgw_db_scheduler_test_main\",\n\t\t.ttl = ttl,\n\t\t.user_oid = user_oid,\n\t};\n\n\t/*\n\t * This is where we would increment the number of bgw used, if we\n\t * decide to do so\n\t */\n\tts_bgw_scheduler_setup_mctx();\n\n\treturn ts_bgw_start_worker(\"ts_bgw_db_scheduler_test_main\", &bgw_params);\n}\n\n/* this function will start up a bgw for the scheduler and set the ttl to the given value\n * (microseconds) */\nextern Datum\nts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(PG_FUNCTION_ARGS)\n{\n\tBackgroundWorkerHandle *worker_handle;\n\tpid_t pid;\n\n\tworker_handle = start_test_scheduler(PG_GETARG_INT32(0), GetUserId());\n\tTestAssertTrue(worker_handle != NULL);\n\n\t/*\n\t * If RegisterDynamicbackgroundworker fails, worker_handle will be\n\t * NULL. Since messages have already been printed in, just exit.\n\t */\n\tif (!worker_handle)\n\t\tPG_RETURN_VOID();\n\n\tBgwHandleStatus status = WaitForBackgroundWorkerStartup(worker_handle, &pid);\n\tTestAssertTrue(BGWH_STARTED == status);\n\tif (status != BGWH_STARTED)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg(\"bgw not started\")));\n\n\tstatus = WaitForBackgroundWorkerShutdown(worker_handle);\n\tTestAssertTrue(BGWH_STOPPED == status);\n\tif (status != BGWH_STOPPED)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg(\"bgw not stopped\")));\n\n\tPG_RETURN_VOID();\n}\n\nstatic BackgroundWorkerHandle *current_handle = NULL;\n\nextern Datum\nts_bgw_db_scheduler_test_run(PG_FUNCTION_ARGS)\n{\n\tpid_t pid;\n\tMemoryContext old_ctx;\n\tBgwHandleStatus status;\n\n\told_ctx = MemoryContextSwitchTo(TopMemoryContext);\n\tcurrent_handle = start_test_scheduler(PG_GETARG_INT32(0), GetUserId());\n\tMemoryContextSwitchTo(old_ctx);\n\n\t/*\n\t * If RegisterDynamicbackgroundworker fails, current_handle will be\n\t * NULL. Since messages have already been printed in, just exit.\n\t */\n\tif (!current_handle)\n\t\tPG_RETURN_VOID();\n\n\tstatus = WaitForBackgroundWorkerStartup(current_handle, &pid);\n\tTestAssertTrue(BGWH_STARTED == status);\n\tif (status != BGWH_STARTED)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg(\"bgw not started\")));\n\n\tPG_RETURN_VOID();\n}\n\nextern Datum\nts_bgw_db_scheduler_test_wait_for_scheduler_finish(PG_FUNCTION_ARGS)\n{\n\tif (current_handle != NULL)\n\t{\n\t\tBgwHandleStatus status = WaitForBackgroundWorkerShutdown(current_handle);\n\t\tTestAssertTrue(BGWH_STOPPED == status);\n\t\tif (status != BGWH_STOPPED)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg(\"bgw not stopped\")));\n\t}\n\tPG_RETURN_VOID();\n}\n\nstatic bool\ntest_job_1()\n{\n\tStartTransactionCommand();\n\telog(WARNING, \"Execute job 1\");\n\n\tCommitTransactionCommand();\n\treturn true;\n}\n\nstatic bool\ntest_job_2_error()\n{\n\tStartTransactionCommand();\n\telog(WARNING, \"Before error job 2\");\n\n\tereport(ERROR, (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION), errmsg(\"Error job 2\")));\n\n\telog(WARNING, \"After error job 2\");\n\n\tCommitTransactionCommand();\n\treturn true;\n}\n\nstatic void\nlog_terminate_signal(SIGNAL_ARGS)\n{\n\twrite_stderr(\"job got term signal\\n\");\n\tdie(postgres_signal_arg);\n}\n\nTS_FUNCTION_INFO_V1(ts_bgw_test_job_sleep);\n\n/*\n * This function is used for testing removing jobs with\n * a currently running background job.\n */\nDatum\nts_bgw_test_job_sleep(PG_FUNCTION_ARGS)\n{\n\tBackgroundWorkerBlockSignals();\n\n\tpqsignal(SIGTERM, log_terminate_signal);\n\n\t/* Setup any signal handlers here */\n\tBackgroundWorkerUnblockSignals();\n\n\telog(WARNING, \"Before sleep\");\n\tPopActiveSnapshot();\n\t/*\n\t * we commit here so the effect of the elog which is written\n\t * to a table with a emit_log_hook is seen by other transactions\n\t * to verify the background job started\n\t */\n\tCommitTransactionCommand();\n\n\tStartTransactionCommand();\n\tDirectFunctionCall1(pg_sleep, Float8GetDatum(10));\n\n\telog(WARNING, \"After sleep\");\n\n\tPG_RETURN_VOID();\n}\n\nstatic bool\ntest_job_3_long()\n{\n\tBackgroundWorkerBlockSignals();\n\n\tpqsignal(SIGTERM, log_terminate_signal);\n\n\t/* Setup any signal handlers here */\n\tBackgroundWorkerUnblockSignals();\n\n\telog(WARNING, \"Before sleep job 3\");\n\n\tDirectFunctionCall1(pg_sleep, Float8GetDatum(2.0L));\n\n\telog(WARNING, \"After sleep job 3\");\n\treturn true;\n}\n\n/* Exactly like job 1, except a wrapper will change its next_start. */\nstatic bool\ntest_job_4(void)\n{\n\telog(WARNING, \"Execute job 4\");\n\treturn true;\n}\n\nstatic TestJobType\nget_test_job_type_from_name(Name job_type_name)\n{\n\tint i;\n\n\tfor (i = 0; i < _MAX_TEST_JOB_TYPE; i++)\n\t{\n\t\tif (namestrcmp(job_type_name, test_job_type_names[i]) == 0)\n\t\t\treturn i;\n\t}\n\treturn _MAX_TEST_JOB_TYPE;\n}\n\nstatic bool\ntest_job_dispatcher(BgwJob *job)\n{\n\tts_register_emit_log_hook();\n\tts_bgw_log_set_application_name(strdup(NameStr(job->fd.application_name)));\n\n\tStartTransactionCommand();\n\tts_params_get();\n\tCommitTransactionCommand();\n\n\tswitch (get_test_job_type_from_name(&job->fd.proc_name))\n\t{\n\t\tcase TEST_JOB_TYPE_JOB_1:\n\t\t\treturn test_job_1();\n\t\tcase TEST_JOB_TYPE_JOB_2_ERROR:\n\t\t\treturn test_job_2_error();\n\t\tcase TEST_JOB_TYPE_JOB_3_LONG:\n\t\t\treturn test_job_3_long();\n\t\tcase TEST_JOB_TYPE_JOB_4:\n\t\t{\n\t\t\t/* Set next_start to 200ms */\n\t\t\tInterval new_interval = { .time = .2 * USECS_PER_SEC };\n\t\t\treturn ts_bgw_job_run_and_set_next_start(job,\n\t\t\t\t\t\t\t\t\t\t\t\t\t test_job_4,\n\t\t\t\t\t\t\t\t\t\t\t\t\t 3,\n\t\t\t\t\t\t\t\t\t\t\t\t\t &new_interval,\n\t\t\t\t\t\t\t\t\t\t\t\t\t /* atomic */ true,\n\t\t\t\t\t\t\t\t\t\t\t\t\t /* mark */ false);\n\t\t}\n\t\tdefault:\n\t\t\treturn ts_cm_functions->job_execute(job);\n\t}\n\treturn false;\n}\n\nDatum\nts_bgw_job_execute_test(PG_FUNCTION_ARGS)\n{\n\tts_timer_set(&ts_mock_timer);\n\tts_bgw_job_set_scheduler_test_hook(test_job_dispatcher);\n\n\treturn ts_bgw_job_entrypoint(fcinfo);\n}\n"
  },
  {
    "path": "test/src/bgw/test_job_refresh.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <string.h>\n#include <postgres.h>\n#include <access/htup_details.h>\n#include <fmgr.h>\n#include <funcapi.h>\n#include <stdlib.h>\n#include <time.h>\n#include <utils/memutils.h>\n\n#include \"compat/compat.h\"\n#include \"bgw/scheduler.h\"\n#include \"export.h\"\n\nTS_FUNCTION_INFO_V1(ts_test_job_refresh);\n\nstatic List *cur_scheduled_jobs = NIL;\n\n/* Test update_scheduled_jobs_list will correctly update with jobs in bgw_job table.\n * Call this function after loading up bgw_job table\n */\nDatum\nts_test_job_refresh(PG_FUNCTION_ARGS)\n{\n\tFuncCallContext *funcctx;\n\tListCell *lc;\n\n\tif (SRF_IS_FIRSTCALL())\n\t{\n\t\tMemoryContext oldcontext;\n\t\tTupleDesc tupdesc;\n\n\t\t/* Use top-level memory context to preserve the global static list */\n\t\tcur_scheduled_jobs = ts_update_scheduled_jobs_list(cur_scheduled_jobs, TopMemoryContext);\n\n\t\tfuncctx = SRF_FIRSTCALL_INIT();\n\t\toldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);\n\n\t\tfuncctx->user_fctx = list_head(cur_scheduled_jobs);\n\n\t\tif (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"function returning record called in context \"\n\t\t\t\t\t\t\t\"that cannot accept type record\")));\n\t\t}\n\n\t\tfuncctx->tuple_desc = BlessTupleDesc(tupdesc);\n\t\tMemoryContextSwitchTo(oldcontext);\n\t}\n\n\tfuncctx = SRF_PERCALL_SETUP();\n\tlc = (ListCell *) funcctx->user_fctx;\n\n\tif (lc == NULL)\n\t\tSRF_RETURN_DONE(funcctx);\n\telse\n\t{\n\t\t/* Return the current list_cell and advance ptr */\n\t\tHeapTuple tuple;\n\t\tDatum *values = palloc(sizeof(*values) * funcctx->tuple_desc->natts);\n\t\tbool *nulls = palloc(sizeof(*nulls) * funcctx->tuple_desc->natts);\n\n\t\tts_populate_scheduled_job_tuple(lfirst(lc), values);\n\t\tmemset(nulls, 0, sizeof(*nulls) * funcctx->tuple_desc->natts);\n\t\ttuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);\n\n\t\tfuncctx->user_fctx = lnext(cur_scheduled_jobs, lc);\n\t\tSRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));\n\t}\n\n\tPG_RETURN_NULL();\n}\n"
  },
  {
    "path": "test/src/bgw/test_job_utils.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <fmgr.h>\n#include <funcapi.h>\n\n#include \"bgw/job.h\"\n#include \"test_utils.h\"\n\nTS_TEST_FN(ts_test_bgw_job_function_call_string)\n{\n\tint32 job_id = PG_GETARG_INT32(0);\n\tBgwJob *job = ts_bgw_job_find(job_id, CurrentMemoryContext, true);\n\n\tconst char *stmt = ts_bgw_job_function_call_string(job);\n\n\tPG_RETURN_TEXT_P(cstring_to_text(stmt));\n}\n"
  },
  {
    "path": "test/src/bgw/timer_mock.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/relscan.h>\n#include <access/xact.h>\n#include <catalog/namespace.h>\n#include <storage/bufmgr.h>\n#include <storage/lmgr.h>\n#include <utils/builtins.h>\n#include <utils/lsyscache.h>\n#include <utils/rel.h>\n\n#include \"annotations.h\"\n#include \"bgw/launcher_interface.h\"\n#include \"log.h\"\n#include \"params.h\"\n#include \"scanner.h\"\n#include \"timer_mock.h\"\n#include \"ts_catalog/catalog.h\"\n\nstatic List *bgw_handles = NIL;\n\nstatic bool mock_wait(TimestampTz until);\nstatic TimestampTz mock_current_time(void);\n\nconst Timer ts_mock_timer = {\n\t.get_current_timestamp = mock_current_time,\n\t.wait = mock_wait,\n};\n\nvoid\nts_timer_mock_register_bgw_handle(BackgroundWorkerHandle *handle, MemoryContext scheduler_mctx)\n{\n\telog(WARNING, \"[TESTING] Registered new background worker\");\n\tMemoryContext old_context = MemoryContextSwitchTo(scheduler_mctx);\n\tbgw_handles = lappend(bgw_handles, handle);\n\tMemoryContextSwitchTo(old_context);\n}\n\n/* WARNING: mock_wait must _only_ be called from the bgw_scheduler, calling it from a worker will\n * clobber the timer state */\nstatic bool\nmock_wait(TimestampTz until)\n{\n\telog(WARNING,\n\t\t \"[TESTING] Wait until \" INT64_FORMAT \", started at \" INT64_FORMAT,\n\t\t until,\n\t\t ts_params_get()->current_time);\n\n\tListCell *lc;\n\n\tswitch (ts_params_get()->mock_wait_type)\n\t{\n\t\tcase WAIT_ON_JOB:\n\t\t\tforeach (lc, bgw_handles)\n\t\t\t{\n\t\t\t\tBackgroundWorkerHandle *bgw_handle = lfirst(lc);\n\t\t\t\tWaitForBackgroundWorkerShutdown(bgw_handle);\n\t\t\t}\n\t\t\tbgw_handles = NIL;\n\t\t\tTS_FALLTHROUGH;\n\t\tcase IMMEDIATELY_SET_UNTIL:\n\t\t\tts_params_set_time(until, false);\n\t\t\treturn true;\n\t\tcase WAIT_FOR_OTHER_TO_ADVANCE:\n\t\t{\n\t\t\t/* Wait for another process to set \"next time\" */\n\t\t\tts_reset_and_wait_timer_latch();\n\n\t\t\treturn true;\n\t\t}\n\t\tcase WAIT_FOR_STANDARD_WAITLATCH:\n\t\t\tts_get_standard_timer()->wait(until);\n\t\t\treturn true;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\nstatic TimestampTz\nmock_current_time()\n{\n\treturn ts_params_get()->current_time;\n}\n"
  },
  {
    "path": "test/src/bgw/timer_mock.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <postmaster/bgworker.h>\n\n#include \"bgw/timer.h\"\n\nextern void ts_timer_mock_register_bgw_handle(BackgroundWorkerHandle *handle,\n\t\t\t\t\t\t\t\t\t\t\t  MemoryContext scheduler_mctx);\n\nextern const Timer ts_mock_timer;\n"
  },
  {
    "path": "test/src/loader/CMakeLists.txt",
    "content": "set(SOURCES init.c ${PROJECT_SOURCE_DIR}/src/extension.c\n            ${PROJECT_SOURCE_DIR}/src/guc.c)\n\ninclude_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}\n                    ${PROJECT_SOURCE_DIR}/src)\n\nadd_library(${PROJECT_NAME}-mock-1 MODULE ${SOURCES} config.h)\nadd_library(${PROJECT_NAME}-mock-2 MODULE ${SOURCES} config.h)\nadd_library(${PROJECT_NAME}-mock-3 MODULE ${SOURCES} config.h)\n# mock-4 will be broken mismatched .so\nadd_library(${PROJECT_NAME}-mock-4 MODULE ${SOURCES} config.h)\nadd_library(${PROJECT_NAME}-mock-5 MODULE ${SOURCES} config.h)\nadd_library(${PROJECT_NAME}-mock-broken MODULE ${SOURCES} config.h)\nadd_library(${PROJECT_NAME}-mock-6 MODULE ${SOURCES} config.h)\n\ntarget_compile_definitions(${PROJECT_NAME}-mock-1\n                           PRIVATE TIMESCALEDB_VERSION_MOD=\"mock-1\" BROKEN=0)\ntarget_compile_definitions(${PROJECT_NAME}-mock-2\n                           PRIVATE TIMESCALEDB_VERSION_MOD=\"mock-2\" BROKEN=0)\ntarget_compile_definitions(${PROJECT_NAME}-mock-3\n                           PRIVATE TIMESCALEDB_VERSION_MOD=\"mock-3\" BROKEN=0)\n# mock 4 is intentionally incorrect version mod\ntarget_compile_definitions(\n  ${PROJECT_NAME}-mock-4 PRIVATE TIMESCALEDB_VERSION_MOD=\"mock-4-mismatch\"\n                                 BROKEN=0)\ntarget_compile_definitions(${PROJECT_NAME}-mock-5\n                           PRIVATE TIMESCALEDB_VERSION_MOD=\"mock-5\" BROKEN=0)\ntarget_compile_definitions(\n  ${PROJECT_NAME}-mock-broken PRIVATE TIMESCALEDB_VERSION_MOD=\"mock-broken\"\n                                      BROKEN=1)\ntarget_compile_definitions(${PROJECT_NAME}-mock-6\n                           PRIVATE TIMESCALEDB_VERSION_MOD=\"mock-6\" BROKEN=0)\n\nforeach(\n  MOCK_VERSION\n  mock-1\n  mock-2\n  mock-3\n  mock-4\n  mock-broken\n  mock-5\n  mock-6)\n  set_target_properties(\n    ${PROJECT_NAME}-${MOCK_VERSION}\n    PROPERTIES OUTPUT_NAME ${PROJECT_NAME}-${MOCK_VERSION} PREFIX \"\")\n\n  install(\n    TARGETS ${PROJECT_NAME}-${MOCK_VERSION}\n    DESTINATION ${PG_PKGLIBDIR}\n    OPTIONAL)\nendforeach(MOCK_VERSION)\n\nadd_library(${PROJECT_NAME}_osm-mock-1 MODULE osm_init.c)\ntarget_compile_definitions(${PROJECT_NAME}_osm-mock-1\n                           PRIVATE OSM_VERSION_MOD=\"mock-1\")\n\nforeach(MOCK_VERSION mock-1)\n  set_target_properties(\n    ${PROJECT_NAME}_osm-${MOCK_VERSION}\n    PROPERTIES OUTPUT_NAME ${PROJECT_NAME}_osm-${MOCK_VERSION} PREFIX \"\")\n\n  install(\n    TARGETS ${PROJECT_NAME}_osm-${MOCK_VERSION}\n    DESTINATION ${PG_PKGLIBDIR}\n    OPTIONAL)\nendforeach(MOCK_VERSION)\n"
  },
  {
    "path": "test/src/loader/config.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n/* Overwrite main config.h so we could inject our own version #s here. */\n#define TELEMETRY_DEFAULT TELEMETRY_OFF\n"
  },
  {
    "path": "test/src/loader/init.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n\n#include <access/xact.h>\n#include <config.h>\n#ifndef WIN32\n#include <access/parallel.h>\n#endif\n#include \"compat/compat.h\"\n#include \"export.h\"\n#include \"extension.h\"\n#include <commands/extension.h>\n#include <miscadmin.h>\n#include <parser/analyze.h>\n#include <utils/guc.h>\n#include <utils/inval.h>\n\n#define STR_EXPAND(x) #x\n#define STR(x) STR_EXPAND(x)\n\n#ifdef PG_MODULE_MAGIC\nPG_MODULE_MAGIC;\n#endif\n\n#if PG16_LT\nextern void PGDLLEXPORT _PG_init(void);\n#endif\n\nstatic post_parse_analyze_hook_type prev_post_parse_analyze_hook;\n\nbool ts_license_guc_check_hook(char **newval, void **extra, GucSource source);\nvoid ts_license_guc_assign_hook(const char *newval, void *extra);\n\nTS_FUNCTION_INFO_V1(ts_post_load_init);\n\nstatic void\ncache_invalidate_callback(Datum arg, Oid relid)\n{\n\tif (ts_extension_is_proxy_table_relid(relid))\n\t\tts_extension_invalidate();\n}\n\nstatic void\npost_analyze_hook(ParseState *pstate, Query *query, JumbleState *jstate)\n{\n\tif (ts_extension_is_loaded_and_not_upgrading())\n\t\telog(WARNING, \"mock post_analyze_hook \" STR(TIMESCALEDB_VERSION_MOD));\n\n\t\t/*\n\t\t * a symbol needed by IsParallelWorker is not exported on windows so we do\n\t\t * not perform this check\n\t\t */\n#ifndef WIN32\n\tif (prev_post_parse_analyze_hook != NULL && !IsParallelWorker())\n\t\telog(ERROR, \"the extension called with a loader should always have a NULL prev hook\");\n#endif\n\tif (BROKEN && !creating_extension)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),\n\t\t\t\t errmsg(\"mock broken \" STR(TIMESCALEDB_VERSION_MOD))));\n}\n\nvoid\n_PG_init(void)\n{\n\t/*\n\t * Check extension_is loaded to catch certain errors such as calls to\n\t * functions defined on the wrong extension version\n\t */\n\tts_extension_check_version(TIMESCALEDB_VERSION_MOD);\n\telog(WARNING, \"mock init \" STR(TIMESCALEDB_VERSION_MOD));\n\tprev_post_parse_analyze_hook = post_parse_analyze_hook;\n\n\t/*\n\t * a symbol needed by IsParallelWorker is not exported on windows so we do\n\t * not perform this check\n\t */\n#ifndef WIN32\n\tif (prev_post_parse_analyze_hook != NULL && !IsParallelWorker())\n\t\telog(ERROR, \"the extension called with a loader should always have a NULL prev hook\");\n#endif\n\tpost_parse_analyze_hook = post_analyze_hook;\n\tCacheRegisterRelcacheCallback(cache_invalidate_callback, PointerGetDatum(NULL));\n}\n\n/* mock for extension.c */\nvoid ts_catalog_reset(void);\nvoid\nts_catalog_reset()\n{\n}\n\n/* mock for guc.c */\nvoid ts_hypertable_cache_invalidate_callback(void);\nvoid\nts_hypertable_cache_invalidate_callback(void)\n{\n}\n\nTS_FUNCTION_INFO_V1(ts_mock_function);\n\nDatum\nts_mock_function(PG_FUNCTION_ARGS)\n{\n\telog(WARNING, \"mock function call \" STR(TIMESCALEDB_VERSION_MOD));\n\tPG_RETURN_VOID();\n}\n\nTSDLLEXPORT Datum\nts_post_load_init(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_CHAR(0);\n}\n\nbool\nts_license_guc_check_hook(char **newval, void **extra, GucSource source)\n{\n\treturn true;\n}\n\nvoid\nts_license_guc_assign_hook(const char *newval, void *extra)\n{\n}\n"
  },
  {
    "path": "test/src/loader/osm_init.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <catalog/namespace.h>\n#include <tcop/utility.h>\n\n#include \"compat/compat.h\"\n#include \"export.h\"\n#include \"loader/lwlocks.h\"\n\n#ifdef PG_MODULE_MAGIC\nPG_MODULE_MAGIC;\n#endif\n\nstatic ProcessUtility_hook_type prev_ProcessUtility_hook;\n\nstatic void osm_process_utility_hook(PlannedStmt *pstmt, const char *queryString, bool readOnlyTree,\n\t\t\t\t\t\t\t\t\t ProcessUtilityContext context, ParamListInfo params,\n\t\t\t\t\t\t\t\t\t QueryEnvironment *queryEnv, DestReceiver *dest,\n\t\t\t\t\t\t\t\t\t QueryCompletion *qc);\n\n#if PG16_LT\nextern void PGDLLEXPORT _PG_init(void);\n#endif\n\nvoid\n_PG_init(void)\n{\n\telog(WARNING, \"OSM-%s _PG_init\", OSM_VERSION_MOD);\n\tvoid **osm_lock_pointer = find_rendezvous_variable(RENDEZVOUS_OSM_PARALLEL_LWLOCK);\n\tif (osm_lock_pointer != NULL)\n\t{\n\t\telog(WARNING, \"got lwlock osm lock\");\n\t}\n\telse\n\t{\n\t\telog(WARNING, \"NO lwlock osm lock\");\n\t}\n\n\tprev_ProcessUtility_hook = ProcessUtility_hook;\n\tProcessUtility_hook = osm_process_utility_hook;\n}\n\nTS_FUNCTION_INFO_V1(ts_mock_osm);\nDatum\nts_mock_osm(PG_FUNCTION_ARGS)\n{\n\telog(WARNING, \"OSM-%s mock function call\", OSM_VERSION_MOD);\n\tPG_RETURN_VOID();\n}\n\nstatic void\nosm_process_utility_hook(PlannedStmt *pstmt, const char *queryString, bool readOnlyTree,\n\t\t\t\t\t\t ProcessUtilityContext context, ParamListInfo params,\n\t\t\t\t\t\t QueryEnvironment *queryEnv, DestReceiver *dest, QueryCompletion *qc)\n{\n\tif (nodeTag(pstmt->utilityStmt) == T_DropStmt)\n\t{\n\t\tDropStmt *stmt = (DropStmt *) pstmt->utilityStmt;\n\t\tif (stmt->removeType == OBJECT_TABLE)\n\t\t{\n\t\t\tListCell *lc;\n\t\t\tforeach (lc, stmt->objects)\n\t\t\t{\n\t\t\t\tRangeVar *relation = makeRangeVarFromNameList(lfirst(lc));\n\t\t\t\tif (relation != NULL)\n\t\t\t\t{\n\t\t\t\t\tOid relid = RangeVarGetRelid(relation, NoLock, true);\n\t\t\t\t\telog(NOTICE,\n\t\t\t\t\t\t \"OSM-%s got DROP TABLE '%s'\",\n\t\t\t\t\t\t OSM_VERSION_MOD,\n\t\t\t\t\t\t get_rel_name(relid));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (prev_ProcessUtility_hook)\n\t\tprev_ProcessUtility_hook(pstmt,\n\t\t\t\t\t\t\t\t queryString,\n\t\t\t\t\t\t\t\t readOnlyTree,\n\t\t\t\t\t\t\t\t context,\n\t\t\t\t\t\t\t\t params,\n\t\t\t\t\t\t\t\t queryEnv,\n\t\t\t\t\t\t\t\t dest,\n\t\t\t\t\t\t\t\t qc);\n}\n"
  },
  {
    "path": "test/src/metadata.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <string.h>\n#include <unistd.h>\n#include <postgres.h>\n#include <fmgr.h>\n#include <utils/builtins.h>\n\n#include \"export.h\"\n#include \"ts_catalog/metadata.h\"\n\nTS_FUNCTION_INFO_V1(ts_test_uuid);\nTS_FUNCTION_INFO_V1(ts_test_exported_uuid);\nTS_FUNCTION_INFO_V1(ts_test_install_timestamp);\n\nDatum\nts_test_uuid(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_DATUM(ts_metadata_get_uuid());\n}\n\nDatum\nts_test_exported_uuid(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_DATUM(ts_metadata_get_exported_uuid());\n}\n\nDatum\nts_test_install_timestamp(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_DATUM(ts_metadata_get_install_timestamp());\n}\n"
  },
  {
    "path": "test/src/net/CMakeLists.txt",
    "content": "set(SOURCES\n    ${CMAKE_CURRENT_SOURCE_DIR}/test_conn.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/test_http.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/conn_mock.c)\n\ntarget_sources(${TESTS_LIB_NAME} PRIVATE ${SOURCES})\ntarget_include_directories(${TESTS_LIB_NAME}\n                           PRIVATE ${PROJECT_SOURCE_DIR}/src/net)\n"
  },
  {
    "path": "test/src/net/conn_mock.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <unistd.h>\n#include <postgres.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <time.h>\n\n#include \"conn_internal.h\"\n#include \"conn_mock.h\"\n\n#define MOCK_MAX_BUF_SIZE 1024\n\ntypedef struct MockConnection\n{\n\tConnection conn;\n\tchar recv_buf[MOCK_MAX_BUF_SIZE];\n\tsize_t recv_buf_offset;\n\tsize_t recv_buf_len;\n} MockConnection;\n\nstatic int\nmock_connect(Connection *conn, const char *host, const char *servname, int port)\n{\n\treturn 0;\n}\n\nstatic void\nmock_close(Connection *conn)\n{\n}\n\nstatic ssize_t\nmock_write(Connection *conn, const char *buf, size_t writelen)\n{\n\treturn writelen;\n}\n\nstatic ssize_t\nmock_read(Connection *conn, char *buf, size_t readlen)\n{\n\tsize_t bytes_to_read = 0;\n\tsize_t max = readlen;\n\tMockConnection *mock = (MockConnection *) conn;\n\n\tif (mock->recv_buf_offset >= mock->recv_buf_len)\n\t\treturn 0;\n\n\tif (max >= mock->recv_buf_len - mock->recv_buf_offset)\n\t\tmax = mock->recv_buf_len - mock->recv_buf_offset;\n\n\t/* Now read a random amount */\n\twhile (bytes_to_read == 0)\n\t{\n\t\tbytes_to_read = rand() % (max + 1);\n\t}\n\tmemcpy(buf, mock->recv_buf + mock->recv_buf_offset, bytes_to_read);\n\tmock->recv_buf_offset += bytes_to_read;\n\n\treturn bytes_to_read;\n}\n\nstatic int\nmock_init(Connection *conn)\n{\n\tsrand(time(0));\n\treturn 0;\n}\n\nstatic ConnOps mock_ops = {\n\t.size = sizeof(MockConnection),\n\t.init = mock_init,\n\t.connect = mock_connect,\n\t.close = mock_close,\n\t.write = mock_write,\n\t.read = mock_read,\n};\n\nssize_t\nts_connection_mock_set_recv_buf(Connection *conn, char *buf, size_t buf_len)\n{\n\tMockConnection *mock = (MockConnection *) conn;\n\n\tif (buf_len > MOCK_MAX_BUF_SIZE)\n\t\treturn -1;\n\n\tmemcpy(mock->recv_buf, buf, buf_len);\n\tmock->recv_buf_len = buf_len;\n\treturn mock->recv_buf_len;\n}\n\nextern void _conn_mock_init(void);\nextern void _conn_mock_fini(void);\n\nvoid\n_conn_mock_init(void)\n{\n\tts_connection_register(CONNECTION_MOCK, &mock_ops);\n}\n\nvoid\n_conn_mock_fini(void)\n{\n}\n"
  },
  {
    "path": "test/src/net/conn_mock.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <sys/socket.h>\n\ntypedef struct Connection Connection;\n\nextern ssize_t ts_connection_mock_set_recv_buf(Connection *conn, char *buf, size_t buf_len);\n"
  },
  {
    "path": "test/src/net/test_conn.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <string.h>\n#include <unistd.h>\n#include <postgres.h>\n#include <fmgr.h>\n\n#include \"compat/compat.h\"\n#include \"config.h\"\n#include \"net/conn.h\"\n\n#define MAX_RESULT_SIZE 2048\n\nTS_FUNCTION_INFO_V1(ts_test_conn);\n\nDatum\nts_test_conn(PG_FUNCTION_ARGS)\n{\n\tchar response[MAX_RESULT_SIZE];\n\tConnection *conn;\n\tint ret;\n\tint port = 80;\n#ifdef TS_USE_OPENSSL\n\tint ssl_port = 443;\n#endif\n\tchar *host = \"httpbin.org\";\n\n\t/* Test connection_init/destroy */\n\tconn = ts_connection_create(CONNECTION_PLAIN);\n\tts_connection_destroy(conn);\n\n\t/* Check pass NULL won't crash */\n\tts_connection_destroy(NULL);\n\n\t/* Check that delays on the socket are properly handled */\n\tconn = ts_connection_create(CONNECTION_PLAIN);\n\n\tts_connection_set_timeout_millis(conn, 200);\n\n\t/* This is a brittle assert function because we might not necessarily have */\n\t/* connectivity on the server running this test? */\n\tret = ts_connection_connect(conn, host, NULL, port);\n\n\tif (ret < 0)\n\t\telog(ERROR, \"%s\", ts_connection_get_and_clear_error(conn));\n\n\t/* should timeout */\n\tret = ts_connection_read(conn, response, 1);\n\n\tif (ret == 0)\n\t\telog(ERROR, \"Expected timeout\");\n\n\tts_connection_close(conn);\n\tts_connection_destroy(conn);\n\n#ifdef TS_USE_OPENSSL\n\t/* Now test ssl_ops */\n\tconn = ts_connection_create(CONNECTION_SSL);\n\n\tts_connection_set_timeout_millis(conn, 200);\n\n\tret = ts_connection_connect(conn, host, NULL, ssl_port);\n\n\tif (ret < 0)\n\t\telog(ERROR, \"%s\", ts_connection_get_and_clear_error(conn));\n\n\tret = ts_connection_read(conn, response, 1);\n\n\tif (ret == 0)\n\t\telog(ERROR, \"Expected timeout\");\n\n\tts_connection_close(conn);\n\tts_connection_destroy(conn);\n#endif\n\n\tPG_RETURN_NULL();\n}\n"
  },
  {
    "path": "test/src/net/test_http.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <string.h>\n#include <postgres.h>\n#include <fmgr.h>\n#include <stdlib.h>\n#include <time.h>\n\n#include \"export.h\"\n#include \"net/http.h\"\n#include \"test_utils.h\"\n\n#define MAX_REQUEST_SIZE 4096\n\n/*  Tests for auxiliary HttpResponseState functions in http_parsing.h */\n\nstatic const char *TEST_RESPONSES[] = {\n\t\"HTTP/1.1 200 OK\\r\\n\"\n\t\"Content-Type: application/json; charset=utf-8\\r\\n\"\n\t\"Date: Thu, 12 Jul 2018 18:33:04 GMT\\r\\n\"\n\t\"ETag: W/\\\"e-upYEWCL+q6R/++2nWHz5b76hBgo\\\"\\r\\n\"\n\t\"Server: nginx \"\n\t\"Vary: Accept-Encoding\\r\\n\"\n\t\"Content-Length: 14\\r\\n\"\n\t\"Connection: Close\\r\\n\\r\\n\"\n\t\"{\\\"status\\\":200}\",\n\t\"HTTP/1.1 200 OK\\r\\n\"\n\t\"Content-Length: 14\\r\\n\"\n\t\"Content-Type: application/json; charset=utf-8\\r\\n\"\n\t\"Date: Thu, 12 Jul 2018 18:33:04 GMT\\r\\n\"\n\t\"ETag: W/\\\"e-upYEWCL+q6R/++2nWHz5b76hBgo\\\"\\r\\n\"\n\t\"Vary: Accept-Encoding\\r\\n\\r\\n\"\n\t\"{\\\"status\\\":200}\",\n\t\"HTTP/1.1 200 OK\\r\\n\"\n\t\"Content-Length: 14\\r\\n\"\n\t\"Connection: Close\\r\\n\\r\\n\"\n\t\"{\\\"status\\\":200}\",\n\t\"HTTP/1.1 201 OK\\r\\n\"\n\t\"Date: Thu, 12 Jul 2018 18:33:04 GMT\\r\\n\"\n\t\"Content-Length: 14\\r\\n\"\n\t\"ETag: W/\\\"e-upYEWCL+q6R/++2nWHz5b76hBgo\\\"\\r\\n\"\n\t\"Connection: Close\\r\\n\\r\\n\"\n\t\"{\\\"status\\\":201}\",\n};\n\nstatic const char *const BAD_RESPONSES[] = { \"HTTP/1.1 200 OK\\r\\n\"\n\t\t\t\t\t\t\t\t\t\t\t \"Content-Type: application/json; charset=utf-8\\r\\n\"\n\t\t\t\t\t\t\t\t\t\t\t \"Date: Thu, 12 Jul 2018 18:33:04 GMT\\r\\n\"\n\t\t\t\t\t\t\t\t\t\t\t \"ETag: W/\\\"e-upYEWCL+q6R/++2nWHz5b76hBgo\\\"\\r\\n\"\n\t\t\t\t\t\t\t\t\t\t\t \"Connection: Close\\r\\n\"\n\t\t\t\t\t\t\t\t\t\t\t \"{\\\"status\\\":200}\",\n\t\t\t\t\t\t\t\t\t\t\t \"Content-Length: 14\\r\\n\"\n\t\t\t\t\t\t\t\t\t\t\t \"{\\\"status\\\":200}\",\n\t\t\t\t\t\t\t\t\t\t\t \"Content-Length: 14\\r\\n\"\n\t\t\t\t\t\t\t\t\t\t\t \"HTTP/1.1 404 Not Found\\r\\n\"\n\t\t\t\t\t\t\t\t\t\t\t \"Connection: Close\\r\\n\\r\\n\"\n\t\t\t\t\t\t\t\t\t\t\t \"{\\\"status\\\":404}\",\n\t\t\t\t\t\t\t\t\t\t\t NULL };\n\nstatic size_t TEST_LENGTHS[] = { 14, 14, 14, 14 };\nstatic const char *MESSAGE_BODY[] = {\n\t\"{\\\"status\\\":200}\", \"{\\\"status\\\":200}\", \"{\\\"status\\\":200}\", \"{\\\"status\\\":201}\"\n};\n\nTS_FUNCTION_INFO_V1(ts_test_http_parsing);\nTS_FUNCTION_INFO_V1(ts_test_http_parsing_full);\nTS_FUNCTION_INFO_V1(ts_test_http_request_build);\n\nstatic int\nnum_test_strings()\n{\n\treturn sizeof(TEST_LENGTHS) / sizeof(TEST_LENGTHS[0]);\n}\n\n/*  Check we can successfully parse partial by well-formed HTTP responses */\nDatum\nts_test_http_parsing(PG_FUNCTION_ARGS)\n{\n\tint num_iterations = PG_GETARG_INT32(0);\n\tint i, j;\n\tsize_t bytes;\n\n\tsrand(time(0));\n\n\tfor (j = 0; j < num_iterations; j++)\n\t{\n\t\tfor (i = 0; i < num_test_strings(); i++)\n\t\t{\n\t\t\tHttpResponseState *state = ts_http_response_state_create();\n\t\t\tbool success;\n\t\t\tssize_t bufsize = 0;\n\t\t\tchar *buf;\n\n\t\t\tbytes = rand() % (strlen(TEST_RESPONSES[i]) + 1);\n\n\t\t\tbuf = ts_http_response_state_next_buffer(state, &bufsize);\n\n\t\t\tTestAssertTrue(bufsize >= (ssize_t) bytes);\n\n\t\t\t/* Copy part of the message into the parsing state */\n\t\t\tmemcpy(buf, TEST_RESPONSES[i], bytes);\n\n\t\t\t/* Now do the parse */\n\t\t\tsuccess = ts_http_response_state_parse(state, bytes);\n\n\t\t\tTestAssertTrue(success);\n\t\t\tif (!success)\n\t\t\t\telog(ERROR, \"could not parse http state\");\n\n\t\t\tsuccess = ts_http_response_state_is_done(state);\n\n\t\t\tTestAssertTrue(bytes < strlen(TEST_RESPONSES[i]) ? !success : success);\n\n\t\t\tts_http_response_state_destroy(state);\n\t\t}\n\t}\n\tPG_RETURN_NULL();\n}\n\n/*  Check we can successfully parse full, well-formed HTTP response AND\n *  successfully find error with full, poorly-formed HTTP responses\n */\nDatum\nts_test_http_parsing_full(PG_FUNCTION_ARGS)\n{\n\tint i;\n\tsize_t bytes;\n\n\tsrand(time(0));\n\n\tfor (i = 0; i < num_test_strings(); i++)\n\t{\n\t\tHttpResponseState *state = ts_http_response_state_create();\n\t\tssize_t bufsize = 0;\n\t\tchar *buf;\n\t\tint cmp;\n\n\t\tbuf = ts_http_response_state_next_buffer(state, &bufsize);\n\n\t\tbytes = strlen(TEST_RESPONSES[i]);\n\n\t\tTestAssertTrue(bufsize >= (ssize_t) bytes);\n\n\t\t/* Copy all of the message into the parsing state */\n\t\tmemcpy(buf, TEST_RESPONSES[i], bytes);\n\n\t\t/* Now do the parse */\n\t\tTestAssertTrue(ts_http_response_state_parse(state, bytes));\n\n\t\tTestAssertTrue(ts_http_response_state_is_done(state));\n\t\tTestAssertTrue(ts_http_response_state_content_length(state) == TEST_LENGTHS[i]);\n\t\t/* Make sure we read the right message body */\n\t\tcmp = !strncmp(MESSAGE_BODY[i],\n\t\t\t\t\t   ts_http_response_state_body_start(state),\n\t\t\t\t\t   ts_http_response_state_content_length(state));\n\t\tTestAssertTrue(cmp);\n\t\tif (!cmp)\n\t\t\telog(ERROR, \"bad message\");\n\n\t\tts_http_response_state_destroy(state);\n\t}\n\n\t/* Now do the bad responses */\n\tfor (i = 0; i < 3; i++)\n\t{\n\t\tHttpResponseState *state = ts_http_response_state_create();\n\t\tssize_t bufsize = 0;\n\t\tchar *buf;\n\n\t\tbuf = ts_http_response_state_next_buffer(state, &bufsize);\n\n\t\tbytes = strlen(BAD_RESPONSES[i]);\n\n\t\tTestAssertTrue(bufsize >= (ssize_t) bytes);\n\n\t\tmemcpy(buf, BAD_RESPONSES[i], bytes);\n\n\t\tTestAssertTrue(!ts_http_response_state_parse(state, bytes) ||\n\t\t\t\t\t   !ts_http_response_state_valid_status(state));\n\n\t\tts_http_response_state_destroy(state);\n\t}\n\tPG_RETURN_NULL();\n}\n\nDatum\nts_test_http_request_build(PG_FUNCTION_ARGS)\n{\n\tconst char *serialized;\n\tsize_t request_len;\n\tconst char *expected_response = \"GET /v1/alerts HTTP/1.1\\r\\n\"\n\t\t\t\t\t\t\t\t\t\"Host: herp.com\\r\\nContent-Length: 0\\r\\n\\r\\n\";\n\tchar *host = \"herp.com\";\n\tHttpRequest *req = ts_http_request_create(HTTP_GET);\n\tint cmp_res;\n\n\tts_http_request_set_uri(req, \"/v1/alerts\");\n\tts_http_request_set_version(req, HTTP_VERSION_11);\n\tts_http_request_set_header(req, HTTP_CONTENT_LENGTH, \"0\");\n\tts_http_request_set_header(req, HTTP_HOST, host);\n\n\tserialized = ts_http_request_build(req, &request_len);\n\n\tcmp_res = !strncmp(expected_response, serialized, request_len);\n\tTestAssertTrue(cmp_res);\n\tif (!cmp_res)\n\t\telog(ERROR, \"bad response\");\n\tts_http_request_destroy(req);\n\n\texpected_response =\n\t\t\"GET /tmp/path/to/uri HTTP/1.0\\r\\n\"\n\t\t\"Content-Length: 0\\r\\nHost: herp.com\\r\\nContent-Type: application/json\\r\\n\\r\\n\";\n\n\treq = ts_http_request_create(HTTP_GET);\n\tts_http_request_set_uri(req, \"/tmp/path/to/uri\");\n\tts_http_request_set_version(req, HTTP_VERSION_10);\n\tts_http_request_set_header(req, HTTP_CONTENT_TYPE, \"application/json\");\n\tts_http_request_set_header(req, HTTP_HOST, host);\n\tts_http_request_set_header(req, HTTP_CONTENT_LENGTH, \"0\");\n\n\tserialized = ts_http_request_build(req, &request_len);\n\n\tTestAssertTrue(!strncmp(expected_response, serialized, request_len));\n\tts_http_request_destroy(req);\n\n\texpected_response = \"POST /tmp/status/1234 HTTP/1.1\\r\\n\"\n\t\t\t\t\t\t\"Content-Length: 0\\r\\nHost: herp.com\\r\\n\\r\\n\";\n\n\treq = ts_http_request_create(HTTP_POST);\n\tts_http_request_set_uri(req, \"/tmp/status/1234\");\n\tts_http_request_set_version(req, HTTP_VERSION_11);\n\tts_http_request_set_header(req, HTTP_HOST, host);\n\tts_http_request_set_header(req, HTTP_CONTENT_LENGTH, \"0\");\n\n\tserialized = ts_http_request_build(req, &request_len);\n\n\tTestAssertTrue(!strncmp(expected_response, serialized, request_len));\n\tts_http_request_destroy(req);\n\n\t/* Check that content-length checking works */\n\treq = ts_http_request_create(HTTP_POST);\n\tts_http_request_set_uri(req, \"/tmp/status/1234\");\n\tts_http_request_set_version(req, HTTP_VERSION_11);\n\tts_http_request_set_header(req, HTTP_HOST, host);\n\tts_http_request_set_header(req, HTTP_CONTENT_LENGTH, \"9\");\n\n\tTestAssertTrue(!ts_http_request_build(req, &request_len));\n\tts_http_request_destroy(req);\n\n\tPG_RETURN_NULL();\n}\n"
  },
  {
    "path": "test/src/symbol_conflict.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <fmgr.h>\n#include <utils/builtins.h>\n\n#include \"export.h\"\n\n#define STR_EXPAND(x) #x\n#define STR(x) STR_EXPAND(x)\n\n#define FUNC_EXPAND(prefix, name) prefix##_##name\n#define FUNC(prefix, name) FUNC_EXPAND(prefix, name)\n\n/* Function with conflicting name when included in multiple modules */\nextern const char *test_symbol_conflict(void);\n\nconst char *\ntest_symbol_conflict(void)\n{\n\treturn \"hello from \" STR(MODULE_NAME);\n}\n\nTS_FUNCTION_INFO_V1(FUNC(MODULE_NAME, hello));\n\nDatum\nFUNC(MODULE_NAME, hello)(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_TEXT_P(cstring_to_text(test_symbol_conflict()));\n}\n"
  },
  {
    "path": "test/src/telemetry/CMakeLists.txt",
    "content": "set(SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/test_privacy.c\n            ${CMAKE_CURRENT_SOURCE_DIR}/test_telemetry.c)\n\ntarget_sources(${TESTS_LIB_NAME} PRIVATE ${SOURCES})\n"
  },
  {
    "path": "test/src/telemetry/test_privacy.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <string.h>\n#include <unistd.h>\n#include <postgres.h>\n#include <fmgr.h>\n#include <miscadmin.h>\n\n#include \"compat/compat.h\"\n#include \"telemetry/telemetry.h\"\n#include \"uuid.h\"\n\nTS_FUNCTION_INFO_V1(ts_test_privacy);\n\nDatum\nts_test_privacy(PG_FUNCTION_ARGS)\n{\n\t/* This test should only run when timescaledb.telemetry_level=off */\n\tPG_RETURN_BOOL(ts_telemetry_main(\"\", \"\", \"\"));\n}\n"
  },
  {
    "path": "test/src/telemetry/test_telemetry.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/htup_details.h>\n#include <fmgr.h>\n#include <funcapi.h>\n#include <utils/builtins.h>\n#include <utils/jsonb.h>\n\n#include \"compat/compat.h\"\n#include \"config.h\"\n#include \"export.h\"\n#include \"net/http.h\"\n#include \"telemetry/telemetry.h\"\n\n#ifdef TS_DEBUG\n#include \"net/conn_mock.h\"\n#endif\n\n#define HTTPS_PORT 443\n#define TEST_ENDPOINT \"postman-echo.com\"\n\n/* Since we rely on an external service to test request statuses, we should retry\n * a few times if we are not getting the correct response. This should reduce\n * test flakiness.\n */\n#define INVALID_RESPONSE_RETRIES 5\n\nTS_FUNCTION_INFO_V1(ts_test_status);\nTS_FUNCTION_INFO_V1(ts_test_status_ssl);\nTS_FUNCTION_INFO_V1(ts_test_status_mock);\nTS_FUNCTION_INFO_V1(ts_test_telemetry_main_conn);\nTS_FUNCTION_INFO_V1(ts_test_telemetry);\nTS_FUNCTION_INFO_V1(ts_test_check_version_response);\n\n#ifdef TS_DEBUG\nstatic char *test_string;\n#endif\n\nstatic HttpRequest *\nbuild_request(int status)\n{\n\tHttpRequest *req = ts_http_request_create(HTTP_GET);\n\tchar uri[20];\n\n\tsnprintf(uri, 20, \"/status/%d\", status);\n\n\tts_http_request_set_uri(req, uri);\n\tts_http_request_set_version(req, HTTP_VERSION_10);\n\tts_http_request_set_header(req, HTTP_HOST, TEST_ENDPOINT);\n\tts_http_request_set_header(req, HTTP_CONTENT_LENGTH, \"0\");\n\treturn req;\n}\n\nstatic Datum\ntest_factory(ConnectionType type, int status, char *host, int port)\n{\n\tConnection *conn;\n\tHttpRequest *req;\n\tHttpResponseState *rsp = NULL;\n\tHttpError err;\n\tDatum json;\n\n\tconn = ts_connection_create(type);\n\n\tif (conn == NULL)\n\t\treturn CStringGetTextDatum(\"could not initialize a connection\");\n\n\tif (ts_connection_connect(conn, host, NULL, port) < 0)\n\t{\n\t\tconst char *err_msg = ts_connection_get_and_clear_error(conn);\n\n\t\tts_connection_destroy(conn);\n\t\telog(ERROR, \"connection error: %s\", err_msg);\n\t}\n\n#ifdef TS_DEBUG\n\tif (type == CONNECTION_MOCK)\n\t\tts_connection_mock_set_recv_buf(conn, test_string, strlen(test_string));\n#endif\n\n\tfor (int retries = 0; retries < INVALID_RESPONSE_RETRIES; retries++)\n\t{\n\t\treq = build_request(status);\n\n\t\trsp = ts_http_response_state_create();\n\n\t\terr = ts_http_send_and_recv(conn, req, rsp);\n\n\t\tts_http_request_destroy(req);\n\n\t\t/* We are mocking the connection, no need to retry */\n\t\tif (type == CONNECTION_MOCK)\n\t\t\tbreak;\n\n\t\t/* Could be a transient HTTP error, lets try again */\n\t\tif (err != HTTP_ERROR_NONE)\n\t\t\tcontinue;\n\n\t\t/* Got what we want, no need to retry */\n\t\tif (ts_http_response_state_valid_status(rsp) ||\n\t\t\tts_http_response_state_status_code(rsp) == status)\n\t\t\tbreak;\n\t}\n\n\tif (err != HTTP_ERROR_NONE)\n\t{\n\t\tereport(ERROR, (errcode(ERRCODE_IO_ERROR), errmsg(\"%s\", ts_http_strerror(err))));\n\t}\n\n\tts_connection_destroy(conn);\n\n\tif (!ts_http_response_state_valid_status(rsp))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_IO_ERROR),\n\t\t\t\t errmsg(\"endpoint sent back unexpected HTTP status: %d\",\n\t\t\t\t\t\tts_http_response_state_status_code(rsp))));\n\n\tjson = DirectFunctionCall1(jsonb_in, CStringGetDatum(ts_http_response_state_body_start(rsp)));\n\n\tts_http_response_state_destroy(rsp);\n\n\treturn json;\n}\n\n/*  Test ssl_ops */\nDatum\nts_test_status_ssl(PG_FUNCTION_ARGS)\n{\n\tint status = PG_GETARG_INT32(0);\n#ifdef TS_USE_OPENSSL\n\n\treturn test_factory(CONNECTION_SSL, status, TEST_ENDPOINT, HTTPS_PORT);\n#else\n\tchar buf[128] = { '\\0' };\n\n\tif (status / 100 != 2)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_IO_ERROR),\n\t\t\t\t errmsg(\"endpoint sent back unexpected HTTP status: %d\", status)));\n\n\tsnprintf(buf, sizeof(buf) - 1, \"{\\\"status\\\":%d}\", status);\n\n\tPG_RETURN_JSONB_P(DatumGetJsonbP(DirectFunctionCall1(jsonb_in, CStringGetDatum(buf))));\n#endif\n}\n\n/*  Test default_ops */\nDatum\nts_test_status(PG_FUNCTION_ARGS)\n{\n\tint port = 80;\n\tint status = PG_GETARG_INT32(0);\n\n\tPG_RETURN_DATUM(test_factory(CONNECTION_PLAIN, status, TEST_ENDPOINT, port));\n}\n\n#ifdef TS_DEBUG\n/* Test mock_ops */\nDatum\nts_test_status_mock(PG_FUNCTION_ARGS)\n{\n\tint port = 80;\n\ttext *arg1 = PG_GETARG_TEXT_P(0);\n\n\ttest_string = text_to_cstring(arg1);\n\n\tPG_RETURN_DATUM(test_factory(CONNECTION_MOCK, 123, TEST_ENDPOINT, port));\n}\n#endif\n\nTS_FUNCTION_INFO_V1(ts_test_validate_server_version);\n\nDatum\nts_test_validate_server_version(PG_FUNCTION_ARGS)\n{\n\ttext *response = PG_GETARG_TEXT_P(0);\n\tVersionResult result;\n\n\tif (ts_validate_server_version(text_to_cstring(response), &result))\n\t\tPG_RETURN_TEXT_P(cstring_to_text(result.versionstr));\n\n\tPG_RETURN_NULL();\n}\n\nDatum\nts_test_check_version_response(PG_FUNCTION_ARGS)\n{\n\ttext *response = PG_GETARG_TEXT_P(0);\n\tconst char *volatile json = text_to_cstring(response);\n\tPG_TRY();\n\t{\n\t\tts_check_version_response(json);\n\t}\n\tPG_CATCH();\n\t{\n\t\t/* If the response is malformed, ts_check_version_response() will\n\t\t * throw an error, so we capture the error here. The error message\n\t\t * contains the function pointer, which will vary between test runs,\n\t\t * so we do not re-throw the error here and instead print our own. */\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_DATA_EXCEPTION), errmsg(\"malformed telemetry response body\")));\n\t}\n\tPG_END_TRY();\n\tPG_RETURN_VOID();\n}\n\n/* Try to get the telemetry function to handle errors. Never connect to the\n * actual endpoint. Only test cases that will result in connection errors. */\nDatum\nts_test_telemetry_main_conn(PG_FUNCTION_ARGS)\n{\n\ttext *host = PG_GETARG_TEXT_P(0);\n\ttext *path = PG_GETARG_TEXT_P(1);\n\tconst char *scheme;\n\n#ifdef TS_USE_OPENSSL\n\tscheme = \"https\";\n#else\n\tscheme = \"http\";\n#endif\n\n\tPG_RETURN_BOOL(ts_telemetry_main(text_to_cstring(host), text_to_cstring(path), scheme));\n}\n\nDatum\nts_test_telemetry(PG_FUNCTION_ARGS)\n{\n\tConnection *conn;\n\tConnectionType conntype;\n\tHttpRequest *req;\n\tHttpResponseState *rsp;\n\tHttpError err;\n\tDatum json_body;\n\tconst char *host = PG_ARGISNULL(0) ? TELEMETRY_HOST : text_to_cstring(PG_GETARG_TEXT_P(0));\n\tconst char *servname = PG_ARGISNULL(1) ? \"https\" : text_to_cstring(PG_GETARG_TEXT_P(1));\n\tint port = PG_ARGISNULL(2) ? 0 : PG_GETARG_INT32(2);\n\tint ret;\n\n\tif (PG_NARGS() > 3)\n\t\telog(ERROR, \"invalid number of arguments\");\n\n\tif (strcmp(\"http\", servname) == 0)\n\t\tconntype = CONNECTION_PLAIN;\n\telse if (strcmp(\"https\", servname) == 0)\n\t\tconntype = CONNECTION_SSL;\n\telse\n\t\telog(ERROR, \"invalid service type '%s'\", servname);\n\n\tconn = ts_connection_create(conntype);\n\n\tif (conn == NULL)\n\t\telog(ERROR, \"could not create telemetry connection\");\n\n\tret = ts_connection_connect(conn, host, servname, port);\n\n\tif (ret < 0)\n\t{\n\t\tconst char *errstr = ts_connection_get_and_clear_error(conn);\n\n\t\tts_connection_destroy(conn);\n\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t errmsg(\"could not make a connection to %s://%s\", servname, host),\n\t\t\t\t errdetail(\"%s\", errstr)));\n\t}\n\n\treq = ts_build_version_request(host, TELEMETRY_PATH);\n\n\trsp = ts_http_response_state_create();\n\n\terr = ts_http_send_and_recv(conn, req, rsp);\n\n\tts_http_request_destroy(req);\n\tts_connection_destroy(conn);\n\n\tif (err != HTTP_ERROR_NONE)\n\t{\n\t\tts_http_response_state_destroy(rsp);\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_IO_ERROR), errmsg(\"telemetry error: %s\", ts_http_strerror(err))));\n\t}\n\n\tif (!ts_http_response_state_valid_status(rsp))\n\t{\n\t\tts_http_response_state_destroy(rsp);\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_IO_ERROR),\n\t\t\t\t errmsg(\"telemetry got unexpected HTTP response status: %d\",\n\t\t\t\t\t\tts_http_response_state_status_code(rsp))));\n\t}\n\n\tjson_body =\n\t\tDirectFunctionCall1(jsonb_in, CStringGetDatum(ts_http_response_state_body_start(rsp)));\n\n\tts_http_response_state_destroy(rsp);\n\n\tPG_RETURN_DATUM(json_body);\n}\n"
  },
  {
    "path": "test/src/test_bmslist_utils.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n#include \"bmslist_utils.h\"\n#include \"test_utils.h\"\n#include <fmgr.h>\n#include <funcapi.h>\n#include <ts_catalog/compression_settings.h>\n\nstatic void\ntest_empty_bmslist_contains_items(void)\n{\n\tTsBmsList bmslist = ts_bmslist_create();\n\tint items[] = { 1, 2, 3 };\n\tbool result = ts_bmslist_contains_items(bmslist, items, 3);\n\tTestAssertBoolEq(result, false);\n}\n\nstatic void\ntest_non_empty_bmslist_contains_items(void)\n{\n\tTsBmsList bmslist = ts_bmslist_create();\n\tint items[] = { 1, 2, 3 };\n\tbmslist = ts_bmslist_add_member(bmslist, items, 1);\n\tbmslist = ts_bmslist_add_member(bmslist, items, 2);\n\tbmslist = ts_bmslist_add_member(bmslist, items, 3);\n\n\tbool result = ts_bmslist_contains_items(bmslist, items, 3);\n\tTestAssertBoolEq(result, true);\n\n\tresult = ts_bmslist_contains_items(bmslist, items, 2);\n\tTestAssertBoolEq(result, true);\n\n\tresult = ts_bmslist_contains_items(bmslist, items, 1);\n\tTestAssertBoolEq(result, true);\n\n\titems[0] = 4;\n\tresult = ts_bmslist_contains_items(bmslist, items, 3);\n\tTestAssertBoolEq(result, false);\n\n\tresult = ts_bmslist_contains_items(bmslist, items, 2);\n\tTestAssertBoolEq(result, false);\n\n\tresult = ts_bmslist_contains_items(bmslist, items, 1);\n\tTestAssertBoolEq(result, false);\n\n\tts_bmslist_free(bmslist);\n}\n\nTS_TEST_FN(ts_test_bmslist_utils)\n{\n\ttest_empty_bmslist_contains_items();\n\ttest_non_empty_bmslist_contains_items();\n\tPG_RETURN_VOID();\n}\n"
  },
  {
    "path": "test/src/test_compression_settings.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n#include \"foreach_ptr.h\"\n#include <fmgr.h>\n#include <funcapi.h>\n#include <ts_catalog/compression_settings.h>\n#include <utils/jsonb.h>\n\n/* Include test_utils.h after all other headers */\n#include <test_utils.h>\n\n#define TestAssertParsedCompressionSettingsEqCstring(a, b)                                         \\\n\tdo                                                                                             \\\n\t{                                                                                              \\\n\t\tSparseIndexSettings *a_ps = (a);                                                           \\\n\t\tAssert(a_ps != NULL);                                                                      \\\n\t\tconst char *a_i = (a) == NULL ? \"<null>\" : ts_sparse_index_settings_to_cstring(a_ps);      \\\n\t\tconst char *b_i = (b) == NULL ? \"<null>\" : (b);                                            \\\n\t\tif (strcmp(a_i, b_i) != 0)                                                                 \\\n\t\t\tTestFailure(\"(%s == %s)\", a_i, b_i);                                                   \\\n\t} while (0)\n\nstatic void\ntest_alter_table_rename_column_effect_jsonb()\n{\n\tconst char *jsonb_str =\n\t\t\"[{\\\"type\\\": \\\"bloom\\\", \\\"column\\\": \\\"big1\\\", \\\"source\\\": \\\"config\\\"}, \"\n\t\t\"{\\\"type\\\": \\\"bloom\\\", \\\"column\\\": \\\"big2\\\", \\\"source\\\": \\\"config\\\"}, \"\n\t\t\"{\\\"type\\\": \\\"bloom\\\", \\\"column\\\": \\\"value\\\", \\\"source\\\": \\\"config\\\"}, \"\n\t\t\"{\\\"type\\\": \\\"bloom\\\", \\\"column\\\": [\\\"value\\\", \\\"big1\\\", \\\"big2\\\"], \\\"source\\\": \"\n\t\t\"\\\"config\\\"}, \"\n\t\t\"{\\\"type\\\": \\\"bloom\\\", \\\"column\\\": [\\\"o\\\", \\\"big2\\\"], \\\"source\\\": \\\"config\\\"}, \"\n\t\t\"{\\\"type\\\": \\\"minmax\\\", \\\"column\\\": \\\"ts\\\", \\\"source\\\": \\\"orderby\\\"}]\";\n\n\tconst char *jsonb_str_expected =\n\t\t\"[{\\\"type\\\": \\\"bloom\\\", \\\"column\\\": \\\"big1\\\", \\\"source\\\": \\\"config\\\"}, \"\n\t\t\"{\\\"type\\\": \\\"bloom\\\", \\\"column\\\": \\\"xxl\\\", \\\"source\\\": \\\"config\\\"}, \"\n\t\t\"{\\\"type\\\": \\\"bloom\\\", \\\"column\\\": \\\"value\\\", \\\"source\\\": \\\"config\\\"}, \"\n\t\t\"{\\\"type\\\": \\\"bloom\\\", \\\"column\\\": [\\\"value\\\", \\\"big1\\\", \\\"xxl\\\"], \\\"source\\\": \"\n\t\t\"\\\"config\\\"}, \"\n\t\t\"{\\\"type\\\": \\\"bloom\\\", \\\"column\\\": [\\\"o\\\", \\\"xxl\\\"], \\\"source\\\": \\\"config\\\"}, \"\n\t\t\"{\\\"type\\\": \\\"minmax\\\", \\\"column\\\": \\\"ts\\\", \\\"source\\\": \\\"orderby\\\"}]\";\n\n\tJsonb *jb = cstring_to_jsonb(jsonb_str);\n\tSparseIndexSettings *parsed_settings = ts_convert_to_sparse_index_settings(jb);\n\n\tTestAssertInt64Eq(list_length(parsed_settings->objects), 6);\n\tforeach_ptr(SparseIndexSettingsObject, obj, parsed_settings->objects)\n\t{\n\t\tAssert(obj != NULL);\n\t\tTestAssertInt64Eq(list_length(obj->pairs), 3);\n\n\t\tforeach_ptr(SparseIndexSettingsPair, pair, obj->pairs)\n\t\t{\n\t\t\tAssert(pair != NULL);\n\t\t\tif (strcmp(pair->key, \"column\") != 0)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tListCell *value_cell = NULL;\n\t\t\tforeach (value_cell, pair->values)\n\t\t\t{\n\t\t\t\tconst char *value = (const char *) lfirst(value_cell);\n\t\t\t\tAssert(value != NULL);\n\t\t\t\tif (strcmp(value, \"big2\") == 0)\n\t\t\t\t{\n\t\t\t\t\t/* Replace the value with the new one, allocate from the parsed settings context\n\t\t\t\t\t */\n\t\t\t\t\tvalue_cell->ptr_value =\n\t\t\t\t\t\tts_sparse_index_settings_pstrdup(parsed_settings, \"xxl\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tJsonb *result = ts_convert_from_sparse_index_settings(parsed_settings);\n\tTestAssertJsonbEqCstring(result, jsonb_str_expected);\n\tTestAssertParsedCompressionSettingsEqCstring(parsed_settings, jsonb_str_expected);\n\n\t/* test the per column settings */\n\tList *per_column_settings = ts_get_per_column_compression_settings(parsed_settings);\n\tAssert(per_column_settings != NIL);\n\tTestAssertInt64Eq(list_length(per_column_settings), 5);\n\tPerColumnCompressionSettings *per_column_setting = NULL;\n\t{\n\t\tper_column_setting =\n\t\t\t(PerColumnCompressionSettings *) lfirst(list_head(per_column_settings));\n\t\tAssert(per_column_setting != NULL);\n\t\tTestAssertCStringEq(per_column_setting->column_name, \"big1\");\n\t\tTestAssertInt64Eq(per_column_setting->single_bloom_obj_id, 0);\n\t\tTestAssertInt64Eq(per_column_setting->minmax_obj_id, -1);\n\t\t/* only part of a single composite bloom index */\n\t\tTestAssertInt64Eq(bms_num_members(per_column_setting->composite_bloom_index_obj_ids), 1);\n\t\tTestAssertBoolEq(bms_is_member(3, per_column_setting->composite_bloom_index_obj_ids), true);\n\t}\n\n\t{\n\t\tper_column_setting = (PerColumnCompressionSettings *) lsecond(per_column_settings);\n\t\tAssert(per_column_setting != NULL);\n\t\tTestAssertCStringEq(per_column_setting->column_name, \"xxl\");\n\t\tTestAssertInt64Eq(per_column_setting->single_bloom_obj_id, 1);\n\t\tTestAssertInt64Eq(per_column_setting->minmax_obj_id, -1);\n\t\t/* part of two composite bloom indices */\n\t\tTestAssertInt64Eq(bms_num_members(per_column_setting->composite_bloom_index_obj_ids), 2);\n\t\tTestAssertBoolEq(bms_is_member(3, per_column_setting->composite_bloom_index_obj_ids), true);\n\t\tTestAssertBoolEq(bms_is_member(4, per_column_setting->composite_bloom_index_obj_ids), true);\n\t}\n\n\t{\n\t\tper_column_setting = (PerColumnCompressionSettings *) lthird(per_column_settings);\n\t\tAssert(per_column_setting != NULL);\n\t\tTestAssertCStringEq(per_column_setting->column_name, \"value\");\n\t\tTestAssertInt64Eq(per_column_setting->single_bloom_obj_id, 2);\n\t\tTestAssertInt64Eq(per_column_setting->minmax_obj_id, -1);\n\t\t/* part of a single composite bloom index */\n\t\tTestAssertInt64Eq(bms_num_members(per_column_setting->composite_bloom_index_obj_ids), 1);\n\t\tTestAssertBoolEq(bms_is_member(3, per_column_setting->composite_bloom_index_obj_ids), true);\n\t}\n\n\t{\n\t\tper_column_setting = (PerColumnCompressionSettings *) lfourth(per_column_settings);\n\t\tAssert(per_column_setting != NULL);\n\t\tTestAssertCStringEq(per_column_setting->column_name, \"o\");\n\t\tTestAssertInt64Eq(per_column_setting->single_bloom_obj_id, -1);\n\t\tTestAssertInt64Eq(per_column_setting->minmax_obj_id, -1);\n\t\t/* part of a single composite bloom index */\n\t\tTestAssertInt64Eq(bms_num_members(per_column_setting->composite_bloom_index_obj_ids), 1);\n\t\tTestAssertBoolEq(bms_is_member(4, per_column_setting->composite_bloom_index_obj_ids), true);\n\t}\n\n\t{\n\t\tper_column_setting = (PerColumnCompressionSettings *) lfifth(per_column_settings);\n\t\tAssert(per_column_setting != NULL);\n\t\tTestAssertCStringEq(per_column_setting->column_name, \"ts\");\n\t\tTestAssertInt64Eq(per_column_setting->single_bloom_obj_id, -1);\n\t\tTestAssertInt64Eq(per_column_setting->minmax_obj_id, 5);\n\t\tTestAssertPtrEq(per_column_setting->composite_bloom_index_obj_ids, NULL);\n\t}\n\n\tpfree(result);\n\tts_free_sparse_index_settings(parsed_settings);\n\tpfree(jb);\n}\n\nstatic void\ntest_alter_table_drop_column_effect_jsonb()\n{\n\tconst char *jsonb_str =\n\t\t\"[{\\\"type\\\": \\\"bloom\\\", \\\"column\\\": \\\"big1\\\", \\\"source\\\": \\\"config\\\"}, \"\n\t\t\"{\\\"type\\\": \\\"bloom\\\", \\\"column\\\": \\\"big2\\\", \\\"source\\\": \\\"config\\\"}, \"\n\t\t\"{\\\"type\\\": \\\"bloom\\\", \\\"column\\\": \\\"value\\\", \\\"source\\\": \\\"config\\\"}, \"\n\t\t\"{\\\"type\\\": \\\"bloom\\\", \\\"column\\\": [\\\"value\\\", \\\"big1\\\", \\\"big2\\\"], \\\"source\\\": \"\n\t\t\"\\\"config\\\"}, \"\n\t\t\"{\\\"type\\\": \\\"bloom\\\", \\\"column\\\": [\\\"o\\\", \\\"big2\\\"], \\\"source\\\": \\\"config\\\"}, \"\n\t\t\"{\\\"type\\\": \\\"minmax\\\", \\\"column\\\": \\\"ts\\\", \\\"source\\\": \\\"orderby\\\"}]\";\n\n\tconst char *jsonb_str_expected =\n\t\t\"[{\\\"type\\\": \\\"bloom\\\", \\\"column\\\": \\\"big1\\\", \\\"source\\\": \\\"config\\\"}, \"\n\t\t/* DROP: \"{\\\"type\\\": \\\"bloom\\\", \\\"column\\\": \\\"big2\\\", \\\"source\\\": \\\"config\\\"}, \" */\n\t\t\"{\\\"type\\\": \\\"bloom\\\", \\\"column\\\": \\\"value\\\", \\\"source\\\": \\\"config\\\"}, \"\n\t\t/* DROP: \"{\\\"type\\\": \\\"bloom\\\", \\\"column\\\": [\\\"value\\\", \\\"big1\\\", \\\"big2\\\"], \\\"source\\\":\n\t\t   \\\"config\\\"}, */\n\t\t/* DROP: \"{\\\"type\\\": \\\"bloom\\\", \\\"column\\\": [\\\"o\\\", \\\"big2\\\"], \\\"source\\\": \\\"config\\\"}, \" */\n\t\t\"{\\\"type\\\": \\\"minmax\\\", \\\"column\\\": \\\"ts\\\", \\\"source\\\": \\\"orderby\\\"}]\";\n\n\tJsonb *jb = cstring_to_jsonb(jsonb_str);\n\tSparseIndexSettings *parsed_settings = ts_convert_to_sparse_index_settings(jb);\n\n\tTestAssertInt64Eq(list_length(parsed_settings->objects), 6);\n\tListCell *obj_cell = NULL;\n\tforeach (obj_cell, parsed_settings->objects)\n\t{\n\t\tSparseIndexSettingsObject *obj = (SparseIndexSettingsObject *) lfirst(obj_cell);\n\t\tAssert(obj != NULL);\n\t\tTestAssertInt64Eq(list_length(obj->pairs), 3);\n\t\tbool to_remove = false;\n\t\tforeach_ptr(SparseIndexSettingsPair, pair, obj->pairs)\n\t\t{\n\t\t\tAssert(pair != NULL);\n\t\t\tif (strcmp(pair->key, \"column\") != 0)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tforeach_ptr(const char, value, pair->values)\n\t\t\t{\n\t\t\t\tAssert(value != NULL);\n\t\t\t\tif (strcmp(value, \"big2\") == 0)\n\t\t\t\t{\n\t\t\t\t\tto_remove = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (to_remove)\n\t\t\t{\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (to_remove)\n\t\t{\n\t\t\t/* Remove the object from the list of objects */\n\t\t\tparsed_settings->objects = foreach_delete_current(parsed_settings->objects, obj_cell);\n\t\t}\n\t}\n\n\tTestAssertInt64Eq(list_length(parsed_settings->objects), 3);\n\tJsonb *result = ts_convert_from_sparse_index_settings(parsed_settings);\n\tTestAssertJsonbEqCstring(result, jsonb_str_expected);\n\tTestAssertParsedCompressionSettingsEqCstring(parsed_settings, jsonb_str_expected);\n\tts_free_sparse_index_settings(parsed_settings);\n\tpfree(result);\n\tpfree(jb);\n}\n\nstatic void\ntest_convert_to_sparse_index_settings()\n{\n\t{\n\t\t/* Objects with a single pair are converted to SparseIndexSettings */\n\t\tJsonb *jb = cstring_to_jsonb(\"{\\\"key\\\": \\\"value\\\"}\");\n\t\tSparseIndexSettings *parsed_settings = ts_convert_to_sparse_index_settings(jb);\n\t\tTestAssertInt64Eq(list_length(parsed_settings->objects), 1);\n\t\tTestAssertParsedCompressionSettingsEqCstring(parsed_settings, \"[{\\\"key\\\": \\\"value\\\"}]\");\n\t\tJsonb *result = ts_convert_from_sparse_index_settings(parsed_settings);\n\t\tTestAssertJsonbEqCstring(result, \"[{\\\"key\\\": \\\"value\\\"}]\");\n\t\t/* per column should be empty because there is no column and no index type */\n\t\tList *per_column_settings = ts_get_per_column_compression_settings(parsed_settings);\n\t\tTestAssertPtrEq(per_column_settings, NIL);\n\t\tts_free_sparse_index_settings(parsed_settings);\n\t\tpfree(jb);\n\t\tpfree(result);\n\t}\n\n\t{\n\t\t/* Objects with an array value are converted to SparseIndexSettings */\n\t\tJsonb *jb = cstring_to_jsonb(\"{\\\"key\\\": [\\\"value\\\", \\\"value2\\\"]}\");\n\t\tSparseIndexSettings *parsed_settings = ts_convert_to_sparse_index_settings(jb);\n\t\tTestAssertInt64Eq(list_length(parsed_settings->objects), 1);\n\t\tTestAssertParsedCompressionSettingsEqCstring(parsed_settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t \"[{\\\"key\\\": [\\\"value\\\", \\\"value2\\\"]}]\");\n\t\tJsonb *result = ts_convert_from_sparse_index_settings(parsed_settings);\n\t\tTestAssertJsonbEqCstring(result, \"[{\\\"key\\\": [\\\"value\\\", \\\"value2\\\"]}]\");\n\t\tts_free_sparse_index_settings(parsed_settings);\n\t\tpfree(jb);\n\t\tpfree(result);\n\t}\n\n\t{\n\t\t/* Objects with multiple pairs are converted to SparseIndexSettings */\n\t\tJsonb *jb = cstring_to_jsonb(\"{\\\"key\\\": [\\\"value\\\", \\\"value2\\\"], \\\"key2\\\": \\\"value3\\\"}\");\n\t\tSparseIndexSettings *parsed_settings = ts_convert_to_sparse_index_settings(jb);\n\t\tTestAssertInt64Eq(list_length(parsed_settings->objects), 1);\n\t\tTestAssertParsedCompressionSettingsEqCstring(parsed_settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t \"[{\\\"key\\\": [\\\"value\\\", \\\"value2\\\"], \"\n\t\t\t\t\t\t\t\t\t\t\t\t\t \"\\\"key2\\\": \\\"value3\\\"}]\");\n\t\tJsonb *result = ts_convert_from_sparse_index_settings(parsed_settings);\n\t\tTestAssertJsonbEqCstring(result,\n\t\t\t\t\t\t\t\t \"[{\\\"key\\\": [\\\"value\\\", \\\"value2\\\"], \\\"key2\\\": \\\"value3\\\"}]\");\n\t\tts_free_sparse_index_settings(parsed_settings);\n\t\tpfree(jb);\n\t\tpfree(result);\n\t}\n\n\t{\n\t\t/* Empty objects are converted to NULL */\n\t\tJsonb *jb = cstring_to_jsonb(\"{}\");\n\t\tSparseIndexSettings *parsed_settings = ts_convert_to_sparse_index_settings(jb);\n\t\tTestAssertPtrEq(parsed_settings, NULL);\n\t\tpfree(jb);\n\t}\n\n\t{\n\t\t/* Empty arrays are ignored and converted to NULL */\n\t\tJsonb *jb = cstring_to_jsonb(\"[]\");\n\t\tSparseIndexSettings *parsed_settings = ts_convert_to_sparse_index_settings(jb);\n\t\tTestAssertPtrEq(parsed_settings, NULL);\n\t\tpfree(jb);\n\t}\n\n\t{\n\t\t/* Empty objects are ignored */\n\t\tJsonb *jb = cstring_to_jsonb(\"[{}, {\\\"key\\\": \\\"value\\\"}, {}, {}]\");\n\t\tSparseIndexSettings *parsed_settings = ts_convert_to_sparse_index_settings(jb);\n\t\tTestAssertInt64Eq(list_length(parsed_settings->objects), 1);\n\t\tTestAssertParsedCompressionSettingsEqCstring(parsed_settings, \"[{\\\"key\\\": \\\"value\\\"}]\");\n\t\tJsonb *result = ts_convert_from_sparse_index_settings(parsed_settings);\n\t\tTestAssertJsonbEqCstring(result, \"[{\\\"key\\\": \\\"value\\\"}]\");\n\t\tts_free_sparse_index_settings(parsed_settings);\n\t\tpfree(jb);\n\t\tpfree(result);\n\t}\n\n\t{\n\t\t/* Unexpected nesting of objects return an error */\n\t\tJsonb *jb = cstring_to_jsonb(\"{\\\"key\\\": [{\\\"key2\\\": \\\"value2\\\"}]}\");\n\t\tTestEnsureError(ts_convert_to_sparse_index_settings(jb));\n\t\tpfree(jb);\n\t}\n\n\t{\n\t\t/* Unexpected nesting of objects return an error */\n\t\tJsonb *jb = cstring_to_jsonb(\"{\\\"key\\\": {\\\"key2\\\": \\\"value2\\\"}}\");\n\t\tTestEnsureError(ts_convert_to_sparse_index_settings(jb));\n\t\tpfree(jb);\n\t}\n\n\t{\n\t\t/* Unexpected nesting of objects return an error */\n\t\tJsonb *jb = cstring_to_jsonb(\"{\\\"key\\\": [{\\\"key2\\\": \\\"value2\\\"}, {\\\"key3\\\": \\\"value3\\\"}]}\");\n\t\tTestEnsureError(ts_convert_to_sparse_index_settings(jb));\n\t\tpfree(jb);\n\t}\n\n\t{\n\t\t/* Unexpected nesting of arrays return an error */\n\t\tJsonb *jb = cstring_to_jsonb(\"{\\\"key\\\": [[\\\"value2\\\", \\\"value3\\\"]]}\");\n\t\tTestEnsureError(ts_convert_to_sparse_index_settings(jb));\n\t\tpfree(jb);\n\t}\n\n\t{\n\t\t/* Unexpected nesting of arrays return an error */\n\t\tJsonb *jb = cstring_to_jsonb(\"[[{\\\"key\\\": [\\\"value2\\\", \\\"value3\\\"]}]]\");\n\t\tTestEnsureError(ts_convert_to_sparse_index_settings(jb));\n\t\tpfree(jb);\n\t}\n\n\t{\n\t\t/* Unexpected nesting of arrays return an error */\n\t\tJsonb *jb = cstring_to_jsonb(\"[{\\\"key\\\": [\\\"value2\\\", [\\\"value3\\\"]]}]\");\n\t\tTestEnsureError(ts_convert_to_sparse_index_settings(jb));\n\t\tpfree(jb);\n\t}\n}\n\nTS_TEST_FN(ts_test_compression_settings)\n{\n\ttest_alter_table_rename_column_effect_jsonb();\n\ttest_alter_table_drop_column_effect_jsonb();\n\ttest_convert_to_sparse_index_settings();\n\tPG_RETURN_VOID();\n}\n"
  },
  {
    "path": "test/src/test_jsonb_utils.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n#include \"jsonb_utils.h\"\n#include \"test_utils.h\"\n#include \"ts_catalog/compression_settings.h\"\n#include \"utils/jsonb.h\"\n#include <fmgr.h>\n#include <funcapi.h>\n\n// Declare jsonb_in explicitly\nextern Datum jsonb_in(PG_FUNCTION_ARGS);\n\nconst char *\njsonb_to_cstring(Jsonb *jsonb)\n{\n\tStringInfoData buf;\n\tinitStringInfo(&buf);\n\tJsonbToCString(&buf, &jsonb->root, 0);\n\treturn buf.data;\n}\n\nJsonb *\ncstring_to_jsonb(const char *cstring)\n{\n\tDatum jsonb_datum = DirectFunctionCall1(jsonb_in, CStringGetDatum(cstring));\n\tJsonb *jsonb = DatumGetJsonbP(jsonb_datum);\n\treturn jsonb;\n}\n\nstatic void\ntest_get_str_field()\n{\n\t{\n\t\t/* Empty JSONB  doesn't have the key */\n\t\tJsonb *jb = cstring_to_jsonb(\"{}\");\n\t\tTestAssertCStringEq(ts_jsonb_get_str_field(jb, \"key\"), NULL);\n\t\tTestAssertJsonbEqCstring(jb, \"{}\");\n\t\tpfree(jb);\n\t}\n\n\t{\n\t\t/* JSONB with the key, string value */\n\t\tJsonb *jb = cstring_to_jsonb(\"{ \\\"key\\\": \\\"value\\\" }\");\n\t\tTestAssertCStringEq(ts_jsonb_get_str_field(jb, \"key\"), \"value\");\n\t\tpfree(jb);\n\t}\n\n\t{\n\t\t/* JSONB with the key, integer value */\n\t\tJsonb *jb = cstring_to_jsonb(\"{ \\\"key\\\": 1 }\");\n\t\tTestAssertCStringEq(ts_jsonb_get_str_field(jb, \"key\"), \"1\");\n\t\tpfree(jb);\n\t}\n\n\t{\n\t\t/* JSONB with the key, empty object value */\n\t\tJsonb *jb = cstring_to_jsonb(\"{ \\\"key\\\": {} }\");\n\t\tTestAssertCStringEq(ts_jsonb_get_str_field(jb, \"key\"), \"{}\");\n\t\tpfree(jb);\n\t}\n\n\t{\n\t\t/* JSONB with the key, array value */\n\t\tJsonb *jb = cstring_to_jsonb(\"{ \\\"key\\\": [1, 2, 3] }\");\n\t\tTestAssertCStringEq(ts_jsonb_get_str_field(jb, \"key\"), \"[1, 2, 3]\");\n\t\tpfree(jb);\n\t}\n}\n\nstatic void\ntest_get_bool_field()\n{\n\t{\n\t\t/* JSONB with missing key */\n\t\tJsonb *jb = cstring_to_jsonb(\"{ \\\"something_else\\\": {} }\");\n\t\tbool found;\n\t\tTestAssertBoolEq(ts_jsonb_get_bool_field(jb, \"key\", &found), false);\n\t\tTestAssertBoolEq(found, false);\n\t\tpfree(jb);\n\t}\n\n\t{\n\t\t/* JSONB with the key, true value */\n\t\tJsonb *jb = cstring_to_jsonb(\"{ \\\"key\\\": true }\");\n\t\tbool found;\n\t\tTestAssertBoolEq(ts_jsonb_get_bool_field(jb, \"key\", &found), true);\n\t\tTestAssertBoolEq(found, true);\n\t\tpfree(jb);\n\t}\n\n\t{\n\t\t/* JSONB with the key, false value */\n\t\tJsonb *jb = cstring_to_jsonb(\"{ \\\"key\\\": false }\");\n\t\tbool found;\n\t\tTestAssertBoolEq(ts_jsonb_get_bool_field(jb, \"key\", &found), false);\n\t\tTestAssertBoolEq(found, true);\n\t\tpfree(jb);\n\t}\n}\n\nstatic void\ntest_has_key_value_str_field()\n{\n\t{\n\t\t/* JSONB with missing key */\n\t\tJsonb *jb = cstring_to_jsonb(\"{ \\\"something_else\\\": {} }\");\n\t\tTestAssertBoolEq(ts_jsonb_has_key_value_str_field(jb, \"key\", \"value\"), false);\n\t\tpfree(jb);\n\t}\n\n\t{\n\t\t/* JSONB with the key, string value */\n\t\tJsonb *jb = cstring_to_jsonb(\"{ \\\"key\\\": \\\"value\\\" }\");\n\t\tTestAssertBoolEq(ts_jsonb_has_key_value_str_field(jb, \"key\", \"value\"), true);\n\t\tpfree(jb);\n\t}\n\n\t{\n\t\t/* JSONB with the key, array value, only string key=value pairs should be matched */\n\t\tJsonb *jb = cstring_to_jsonb(\"{ \\\"key\\\": [\\\"value\\\", \\\"value2\\\"] }\");\n\t\tTestAssertBoolEq(ts_jsonb_has_key_value_str_field(jb, \"key\", \"value\"), false);\n\t\tpfree(jb);\n\t}\n\n\t{\n\t\t/* JSONB with the key, array value, only string key=value pairs should be matched */\n\t\tJsonb *jb = cstring_to_jsonb(\"{ \\\"key\\\": [\\\"value2\\\", \\\"value\\\"] }\");\n\t\tTestAssertBoolEq(ts_jsonb_has_key_value_str_field(jb, \"key\", \"value\"), false);\n\t\tpfree(jb);\n\t}\n\n\t{\n\t\t/* Key value pair nested in an object */\n\t\tJsonb *jb = cstring_to_jsonb(\"{ \\\"key\\\": [\\\"value\\\", \\\"value2\\\"], \\\"x\\\": {\\\"y\\\": \\\"z\\\", \"\n\t\t\t\t\t\t\t\t\t \"\\\"key\\\": \\\"value\\\"}, \\\"z\\\": [{\\\"key\\\": \\\"value\\\"}] }\");\n\t\tTestAssertBoolEq(ts_jsonb_has_key_value_str_field(jb, \"key\", \"value\"), true);\n\t\tTestAssertBoolEq(ts_jsonb_has_key_value_str_field(jb, \"y\", \"z\"), true);\n\t\tTestAssertBoolEq(ts_jsonb_has_key_value_str_field(jb, \"z\", \"value\"), false);\n\t\tTestAssertBoolEq(ts_jsonb_has_key_value_str_field(jb, \"z\", \"key\"), false);\n\t\tpfree(jb);\n\t}\n}\n\nstatic void\ntest_contains_sparse_index_config()\n{\n\t{\n\t\t/* Single column bloom filter */\n\t\tCompressionSettings settings = {\n\t\t\t.fd = { .index = cstring_to_jsonb(\n\t\t\t\t\t\t\"{\\\"type\\\": \\\"bloom\\\", \\\"column\\\": \\\"big1\\\", \\\"source\\\": \\\"config\\\"}\") }\n\t\t};\n\t\tTestAssertBoolEq(ts_contains_sparse_index_config(&settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"big1\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"bloom\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t false /* skip_column_arrays */),\n\t\t\t\t\t\t true);\n\t\tTestAssertBoolEq(ts_contains_sparse_index_config(&settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"big1\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"bloom\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t true /* skip_column_arrays */),\n\t\t\t\t\t\t true);\n\t\tTestAssertBoolEq(ts_contains_sparse_index_config(&settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"big1\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"no_such_type\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t true /* skip_column_arrays */),\n\t\t\t\t\t\t false);\n\t\tTestAssertBoolEq(ts_contains_sparse_index_config(&settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"big1\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"no_such_type\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t false /* skip_column_arrays */),\n\t\t\t\t\t\t false);\n\t\tTestAssertBoolEq(ts_contains_sparse_index_config(&settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"non_existent\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"bloom\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t false /* skip_column_arrays */),\n\t\t\t\t\t\t false);\n\t\tTestAssertBoolEq(ts_contains_sparse_index_config(&settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"non_existent\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"bloom\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t true /* skip_column_arrays */),\n\t\t\t\t\t\t false);\n\t}\n\n\t{\n\t\t/* Composite bloom filter */\n\t\tCompressionSettings settings = { .fd = { .index = cstring_to_jsonb(\n\t\t\t\t\t\t\t\t\t\t\t\t\t \"{\\\"type\\\": \\\"bloom\\\", \\\"column\\\": [\\\"big1\\\", \"\n\t\t\t\t\t\t\t\t\t\t\t\t\t \"\\\"big2\\\"], \\\"source\\\": \\\"config\\\"}\") } };\n\t\tTestAssertBoolEq(ts_contains_sparse_index_config(&settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"big1\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"bloom\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t true /* skip_column_arrays */),\n\t\t\t\t\t\t false);\n\t\tTestAssertBoolEq(ts_contains_sparse_index_config(&settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"big1\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"bloom\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t false /* skip_column_arrays */),\n\t\t\t\t\t\t true);\n\t\tTestAssertBoolEq(ts_contains_sparse_index_config(&settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"big2\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"bloom\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t true /* skip_column_arrays */),\n\t\t\t\t\t\t false);\n\t\tTestAssertBoolEq(ts_contains_sparse_index_config(&settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"big2\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"bloom\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t false /* skip_column_arrays */),\n\t\t\t\t\t\t true);\n\t\tTestAssertBoolEq(ts_contains_sparse_index_config(&settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"big1\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"no_such_type\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t true /* skip_column_arrays */),\n\t\t\t\t\t\t false);\n\t\tTestAssertBoolEq(ts_contains_sparse_index_config(&settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"big1\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"no_such_type\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t false /* skip_column_arrays */),\n\t\t\t\t\t\t false);\n\t\tTestAssertBoolEq(ts_contains_sparse_index_config(&settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"big2\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"no_such_type\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t true /* skip_column_arrays */),\n\t\t\t\t\t\t false);\n\t\tTestAssertBoolEq(ts_contains_sparse_index_config(&settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"big2\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"no_such_type\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t false /* skip_column_arrays */),\n\t\t\t\t\t\t false);\n\t\tTestAssertBoolEq(ts_contains_sparse_index_config(&settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"non_existent\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"bloom\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t true /* skip_column_arrays */),\n\t\t\t\t\t\t false);\n\t\tTestAssertBoolEq(ts_contains_sparse_index_config(&settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"non_existent\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"bloom\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t false /* skip_column_arrays */),\n\t\t\t\t\t\t false);\n\t}\n}\n\nTS_TEST_FN(ts_test_jsonb_utils)\n{\n\ttest_get_str_field();\n\ttest_get_bool_field();\n\ttest_has_key_value_str_field();\n\ttest_contains_sparse_index_config();\n\tPG_RETURN_VOID();\n}\n"
  },
  {
    "path": "test/src/test_scanner.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n\n#include \"chunk.h\"\n#include \"scan_iterator.h\"\n#include \"scanner.h\"\n#include \"test_utils.h\"\n\nTS_TEST_FN(ts_test_scanner)\n{\n\tScanIterator it;\n\tRelation chunkrel;\n\tint32 chunk_id[2] = { -1, -1 };\n\tsize_t i = 0;\n\n\t/* Test pre-open relation */\n\tit = ts_chunk_scan_iterator_create(CurrentMemoryContext);\n\tchunkrel = table_open(it.ctx.table, AccessShareLock);\n\tit.ctx.tablerel = chunkrel;\n\n\t/* Explicit start scan to test that we can call it twice without\n\t * issue. The loop will also call it */\n\tts_scan_iterator_start_scan(&it);\n\n\tts_scanner_foreach(&it)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&it);\n\t\tFormData_chunk fd;\n\n\t\tts_chunk_formdata_fill(&fd, ti);\n\n\t\telog(NOTICE, \"1. Scan: \\\"%s.%s\\\"\", NameStr(fd.schema_name), NameStr(fd.table_name));\n\n\t\tif (i < lengthof(chunk_id) && chunk_id[i] == -1)\n\t\t{\n\t\t\tchunk_id[i] = fd.id;\n\t\t\ti++;\n\t\t}\n\t}\n\n\tts_scan_iterator_end(&it);\n\n\t/* Add a chunk filter and scan again */\n\tts_scan_iterator_scan_key_init(&it,\n\t\t\t\t\t\t\t\t   Anum_chunk_idx_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(chunk_id[0]));\n\n\tts_scanner_foreach(&it)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&it);\n\t\tFormData_chunk fd;\n\n\t\tts_chunk_formdata_fill(&fd, ti);\n\n\t\telog(NOTICE,\n\t\t\t \"2. Scan with filter: \\\"%s.%s\\\"\",\n\t\t\t NameStr(fd.schema_name),\n\t\t\t NameStr(fd.table_name));\n\t}\n\n\t/* Rescan */\n\tts_scan_iterator_scan_key_reset(&it);\n\tts_scan_iterator_scan_key_init(&it,\n\t\t\t\t\t\t\t\t   Anum_chunk_idx_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(chunk_id[1]));\n\tts_scan_iterator_rescan(&it);\n\n\tts_scanner_foreach(&it)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&it);\n\t\tFormData_chunk fd;\n\n\t\tts_chunk_formdata_fill(&fd, ti);\n\n\t\telog(NOTICE, \"3. ReScan: \\\"%s.%s\\\"\", NameStr(fd.schema_name), NameStr(fd.table_name));\n\t}\n\n\tts_scan_iterator_end(&it);\n\ttable_close(chunkrel, AccessShareLock);\n\n\t/* Do another scan, but an index scan this time */\n\tit.ctx.tablerel = NULL;\n\tit.ctx.index = catalog_get_index(ts_catalog_get(), CHUNK, CHUNK_ID_INDEX);\n\n\tts_scanner_foreach(&it)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&it);\n\t\tFormData_chunk fd;\n\n\t\tts_chunk_formdata_fill(&fd, ti);\n\n\t\telog(NOTICE, \"4. IndexScan: \\\"%s.%s\\\"\", NameStr(fd.schema_name), NameStr(fd.table_name));\n\t}\n\n\tts_scan_iterator_close(&it);\n\n\tPG_RETURN_VOID();\n}\n"
  },
  {
    "path": "test/src/test_time_to_internal.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <catalog/pg_type.h>\n#include <fmgr.h>\n#include <utils/date.h>\n\n#include \"export.h\"\n\n#include \"time_utils.h\"\n#include \"utils.h\"\n\n#include \"test_utils.h\"\n\nTS_FUNCTION_INFO_V1(ts_test_time_to_internal_conversion);\nTS_FUNCTION_INFO_V1(ts_test_interval_to_internal_conversion);\n\nDatum\nts_test_time_to_internal_conversion(PG_FUNCTION_ARGS)\n{\n\tint16 i16;\n\tint32 i32;\n\tint64 i64;\n\n\t/* test integer values */\n\n\t/* int16 */\n\tfor (i16 = -100; i16 < 100; i16++)\n\t{\n\t\tTestAssertInt64Eq(i16, ts_time_value_to_internal(Int16GetDatum(i16), INT2OID));\n\t\tTestAssertInt64Eq(DatumGetInt16(ts_internal_to_time_value(i16, INT2OID)), i16);\n\t}\n\n\tTestAssertInt64Eq(PG_INT16_MAX,\n\t\t\t\t\t  ts_time_value_to_internal(Int16GetDatum(PG_INT16_MAX), INT2OID));\n\tTestAssertInt64Eq(DatumGetInt16(ts_internal_to_time_value(PG_INT16_MAX, INT2OID)),\n\t\t\t\t\t  PG_INT16_MAX);\n\n\tTestAssertInt64Eq(PG_INT16_MIN,\n\t\t\t\t\t  ts_time_value_to_internal(Int16GetDatum(PG_INT16_MIN), INT2OID));\n\tTestAssertInt64Eq(DatumGetInt16(ts_internal_to_time_value(PG_INT16_MIN, INT2OID)),\n\t\t\t\t\t  PG_INT16_MIN);\n\n\t/* int32 */\n\tfor (i32 = -100; i32 < 100; i32++)\n\t{\n\t\tTestAssertInt64Eq(i32, ts_time_value_to_internal(Int32GetDatum(i32), INT4OID));\n\t\tTestAssertInt64Eq(DatumGetInt32(ts_internal_to_time_value(i32, INT4OID)), i32);\n\t}\n\n\tTestAssertInt64Eq(PG_INT16_MAX,\n\t\t\t\t\t  ts_time_value_to_internal(Int32GetDatum(PG_INT16_MAX), INT4OID));\n\tTestAssertInt64Eq(DatumGetInt32(ts_internal_to_time_value(PG_INT16_MAX, INT4OID)),\n\t\t\t\t\t  PG_INT16_MAX);\n\n\tTestAssertInt64Eq(PG_INT32_MAX,\n\t\t\t\t\t  ts_time_value_to_internal(Int32GetDatum(PG_INT32_MAX), INT4OID));\n\tTestAssertInt64Eq(DatumGetInt32(ts_internal_to_time_value(PG_INT32_MAX, INT4OID)),\n\t\t\t\t\t  PG_INT32_MAX);\n\n\tTestAssertInt64Eq(PG_INT32_MIN,\n\t\t\t\t\t  ts_time_value_to_internal(Int32GetDatum(PG_INT32_MIN), INT4OID));\n\tTestAssertInt64Eq(DatumGetInt32(ts_internal_to_time_value(PG_INT32_MIN, INT4OID)),\n\t\t\t\t\t  PG_INT32_MIN);\n\n\t/* int64 */\n\tfor (i64 = -100; i64 < 100; i64++)\n\t{\n\t\tTestAssertInt64Eq(i64, ts_time_value_to_internal(Int64GetDatum(i64), INT8OID));\n\t\tTestAssertInt64Eq(DatumGetInt64(ts_internal_to_time_value(i64, INT8OID)), i64);\n\t}\n\n\tTestAssertInt64Eq(PG_INT16_MIN,\n\t\t\t\t\t  ts_time_value_to_internal(Int64GetDatum(PG_INT16_MIN), INT8OID));\n\tTestAssertInt64Eq(DatumGetInt64(ts_internal_to_time_value(PG_INT16_MIN, INT8OID)),\n\t\t\t\t\t  PG_INT16_MIN);\n\n\tTestAssertInt64Eq(PG_INT32_MAX,\n\t\t\t\t\t  ts_time_value_to_internal(Int64GetDatum(PG_INT32_MAX), INT8OID));\n\tTestAssertInt64Eq(DatumGetInt64(ts_internal_to_time_value(PG_INT32_MAX, INT8OID)),\n\t\t\t\t\t  PG_INT32_MAX);\n\n\tTestAssertInt64Eq(PG_INT64_MAX,\n\t\t\t\t\t  ts_time_value_to_internal(Int64GetDatum(PG_INT64_MAX), INT8OID));\n\tTestAssertInt64Eq(DatumGetInt64(ts_internal_to_time_value(PG_INT64_MAX, INT8OID)),\n\t\t\t\t\t  PG_INT64_MAX);\n\n\tTestAssertInt64Eq(PG_INT64_MIN,\n\t\t\t\t\t  ts_time_value_to_internal(Int64GetDatum(PG_INT64_MIN), INT8OID));\n\tTestAssertInt64Eq(DatumGetInt64(ts_internal_to_time_value(PG_INT64_MIN, INT8OID)),\n\t\t\t\t\t  PG_INT64_MIN);\n\n\t/* test time values round trip */\n\n\t/* TIMESTAMP */\n\tfor (i64 = -100; i64 < 100; i64++)\n\t\tTestAssertInt64Eq(i64,\n\t\t\t\t\t\t  ts_time_value_to_internal(ts_internal_to_time_value(i64, TIMESTAMPOID),\n\t\t\t\t\t\t\t\t\t\t\t\t\tTIMESTAMPOID));\n\n\tfor (i64 = -10000000; i64 < 100000000; i64 += 1000000)\n\t\tTestAssertInt64Eq(i64,\n\t\t\t\t\t\t  ts_time_value_to_internal(ts_internal_to_time_value(i64, TIMESTAMPOID),\n\t\t\t\t\t\t\t\t\t\t\t\t\tTIMESTAMPOID));\n\n\tfor (i64 = -1000000000; i64 < 10000000000; i64 += 100000000)\n\t\tTestAssertInt64Eq(i64,\n\t\t\t\t\t\t  ts_time_value_to_internal(ts_internal_to_time_value(i64, TIMESTAMPOID),\n\t\t\t\t\t\t\t\t\t\t\t\t\tTIMESTAMPOID));\n\n\tTestAssertInt64Eq(TS_TIME_NOBEGIN,\n\t\t\t\t\t  ts_time_value_to_internal(TimestampGetDatum(DT_NOBEGIN), TIMESTAMPOID));\n\tTestAssertInt64Eq(TS_TIME_NOEND,\n\t\t\t\t\t  ts_time_value_to_internal(TimestampGetDatum(DT_NOEND), TIMESTAMPOID));\n\n\tTestAssertInt64Eq(DT_NOBEGIN,\n\t\t\t\t\t  DatumGetTimestamp(ts_internal_to_time_value(PG_INT64_MIN, TIMESTAMPOID)));\n\tTestEnsureError(ts_internal_to_time_value(TS_TIMESTAMP_INTERNAL_MIN - 1, TIMESTAMPOID));\n\n\tTestAssertInt64Eq(TS_TIMESTAMP_INTERNAL_MIN,\n\t\t\t\t\t  ts_time_value_to_internal(ts_internal_to_time_value(TS_TIMESTAMP_INTERNAL_MIN,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  TIMESTAMPOID),\n\t\t\t\t\t\t\t\t\t\t\t\tTIMESTAMPOID));\n\n\tTestAssertInt64Eq(DT_NOEND,\n\t\t\t\t\t  (ts_time_value_to_internal(ts_internal_to_time_value(PG_INT64_MAX,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   TIMESTAMPOID),\n\t\t\t\t\t\t\t\t\t\t\t\t TIMESTAMPOID)));\n\n\t/* TIMESTAMPTZ */\n\tfor (i64 = -100; i64 < 100; i64++)\n\t\tTestAssertInt64Eq(i64,\n\t\t\t\t\t\t  ts_time_value_to_internal(ts_internal_to_time_value(i64, TIMESTAMPTZOID),\n\t\t\t\t\t\t\t\t\t\t\t\t\tTIMESTAMPTZOID));\n\n\tfor (i64 = -10000000; i64 < 100000000; i64 += 1000000)\n\t\tTestAssertInt64Eq(i64,\n\t\t\t\t\t\t  ts_time_value_to_internal(ts_internal_to_time_value(i64, TIMESTAMPTZOID),\n\t\t\t\t\t\t\t\t\t\t\t\t\tTIMESTAMPTZOID));\n\n\tfor (i64 = -1000000000; i64 < 10000000000; i64 += 100000000)\n\t\tTestAssertInt64Eq(i64,\n\t\t\t\t\t\t  ts_time_value_to_internal(ts_internal_to_time_value(i64, TIMESTAMPTZOID),\n\t\t\t\t\t\t\t\t\t\t\t\t\tTIMESTAMPTZOID));\n\n\tTestAssertInt64Eq(TS_TIME_NOBEGIN,\n\t\t\t\t\t  ts_time_value_to_internal(TimestampTzGetDatum(DT_NOBEGIN), TIMESTAMPTZOID));\n\tTestAssertInt64Eq(TS_TIME_NOEND,\n\t\t\t\t\t  ts_time_value_to_internal(TimestampTzGetDatum(DT_NOEND), TIMESTAMPTZOID));\n\n\tTestAssertInt64Eq(DT_NOBEGIN,\n\t\t\t\t\t  DatumGetTimestampTz(ts_internal_to_time_value(PG_INT64_MIN, TIMESTAMPTZOID)));\n\tTestEnsureError(ts_internal_to_time_value(TS_TIMESTAMP_INTERNAL_MIN - 1, TIMESTAMPTZOID));\n\n\tTestAssertInt64Eq(TS_TIMESTAMP_INTERNAL_MIN,\n\t\t\t\t\t  ts_time_value_to_internal(ts_internal_to_time_value(TS_TIMESTAMP_INTERNAL_MIN,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  TIMESTAMPTZOID),\n\t\t\t\t\t\t\t\t\t\t\t\tTIMESTAMPTZOID));\n\n\tTestAssertInt64Eq(DT_NOEND,\n\t\t\t\t\t  ts_time_value_to_internal(ts_internal_to_time_value(PG_INT64_MAX,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  TIMESTAMPTZOID),\n\t\t\t\t\t\t\t\t\t\t\t\tTIMESTAMPTZOID));\n\n\t/* DATE */\n\n\tfor (i64 = -100 * USECS_PER_DAY; i64 < 100 * USECS_PER_DAY; i64 += USECS_PER_DAY)\n\t\tTestAssertInt64Eq(i64,\n\t\t\t\t\t\t  ts_time_value_to_internal(ts_internal_to_time_value(i64, DATEOID),\n\t\t\t\t\t\t\t\t\t\t\t\t\tDATEOID));\n\tTestAssertInt64Eq(DATEVAL_NOBEGIN,\n\t\t\t\t\t  DatumGetDateADT(ts_internal_to_time_value(PG_INT64_MIN, DATEOID)));\n\tTestAssertInt64Eq(DATEVAL_NOEND,\n\t\t\t\t\t  DatumGetDateADT(ts_internal_to_time_value(PG_INT64_MAX, DATEOID)));\n\tTestEnsureError(ts_time_value_to_internal(DateADTGetDatum(DATEVAL_NOBEGIN + 1), DATEOID));\n\tTestEnsureError(ts_time_value_to_internal(DateADTGetDatum(DATEVAL_NOEND - 1), DATEOID));\n\n\tPG_RETURN_VOID();\n};\n\nDatum\nts_test_interval_to_internal_conversion(PG_FUNCTION_ARGS)\n{\n\tint16 i16;\n\tint32 i32;\n\tint64 i64;\n\n\t/* test integer values */\n\n\t/* int16 */\n\tfor (i16 = -100; i16 < 100; i16++)\n\t{\n\t\tTestAssertInt64Eq(i16, ts_interval_value_to_internal(Int16GetDatum(i16), INT2OID));\n\t\tTestAssertInt64Eq(DatumGetInt16(ts_internal_to_interval_value(i16, INT2OID)), i16);\n\t}\n\n\tTestAssertInt64Eq(PG_INT16_MAX,\n\t\t\t\t\t  ts_interval_value_to_internal(Int16GetDatum(PG_INT16_MAX), INT2OID));\n\tTestAssertInt64Eq(DatumGetInt16(ts_internal_to_interval_value(PG_INT16_MAX, INT2OID)),\n\t\t\t\t\t  PG_INT16_MAX);\n\n\tTestAssertInt64Eq(PG_INT16_MIN,\n\t\t\t\t\t  ts_interval_value_to_internal(Int16GetDatum(PG_INT16_MIN), INT2OID));\n\tTestAssertInt64Eq(DatumGetInt16(ts_internal_to_interval_value(PG_INT16_MIN, INT2OID)),\n\t\t\t\t\t  PG_INT16_MIN);\n\n\t/* int32 */\n\tfor (i32 = -100; i32 < 100; i32++)\n\t{\n\t\tTestAssertInt64Eq(i32, ts_interval_value_to_internal(Int32GetDatum(i32), INT4OID));\n\t\tTestAssertInt64Eq(DatumGetInt32(ts_internal_to_interval_value(i32, INT4OID)), i32);\n\t}\n\n\tTestAssertInt64Eq(PG_INT16_MAX,\n\t\t\t\t\t  ts_interval_value_to_internal(Int32GetDatum(PG_INT16_MAX), INT4OID));\n\tTestAssertInt64Eq(DatumGetInt32(ts_internal_to_interval_value(PG_INT16_MAX, INT4OID)),\n\t\t\t\t\t  PG_INT16_MAX);\n\n\tTestAssertInt64Eq(PG_INT32_MAX,\n\t\t\t\t\t  ts_interval_value_to_internal(Int32GetDatum(PG_INT32_MAX), INT4OID));\n\tTestAssertInt64Eq(DatumGetInt32(ts_internal_to_interval_value(PG_INT32_MAX, INT4OID)),\n\t\t\t\t\t  PG_INT32_MAX);\n\n\tTestAssertInt64Eq(PG_INT32_MIN,\n\t\t\t\t\t  ts_interval_value_to_internal(Int32GetDatum(PG_INT32_MIN), INT4OID));\n\tTestAssertInt64Eq(DatumGetInt32(ts_internal_to_interval_value(PG_INT32_MIN, INT4OID)),\n\t\t\t\t\t  PG_INT32_MIN);\n\n\t/* int64 */\n\tfor (i64 = -100; i64 < 100; i64++)\n\t{\n\t\tTestAssertInt64Eq(i64, ts_interval_value_to_internal(Int64GetDatum(i64), INT8OID));\n\t\tTestAssertInt64Eq(DatumGetInt64(ts_internal_to_interval_value(i64, INT8OID)), i64);\n\t}\n\n\tTestAssertInt64Eq(PG_INT16_MIN,\n\t\t\t\t\t  ts_interval_value_to_internal(Int64GetDatum(PG_INT16_MIN), INT8OID));\n\tTestAssertInt64Eq(DatumGetInt64(ts_internal_to_interval_value(PG_INT16_MIN, INT8OID)),\n\t\t\t\t\t  PG_INT16_MIN);\n\n\tTestAssertInt64Eq(PG_INT32_MAX,\n\t\t\t\t\t  ts_interval_value_to_internal(Int64GetDatum(PG_INT32_MAX), INT8OID));\n\tTestAssertInt64Eq(DatumGetInt64(ts_internal_to_interval_value(PG_INT32_MAX, INT8OID)),\n\t\t\t\t\t  PG_INT32_MAX);\n\n\tTestAssertInt64Eq(PG_INT64_MAX,\n\t\t\t\t\t  ts_interval_value_to_internal(Int64GetDatum(PG_INT64_MAX), INT8OID));\n\tTestAssertInt64Eq(DatumGetInt64(ts_internal_to_interval_value(PG_INT64_MAX, INT8OID)),\n\t\t\t\t\t  PG_INT64_MAX);\n\n\tTestAssertInt64Eq(PG_INT64_MIN,\n\t\t\t\t\t  ts_interval_value_to_internal(Int64GetDatum(PG_INT64_MIN), INT8OID));\n\tTestAssertInt64Eq(DatumGetInt64(ts_internal_to_interval_value(PG_INT64_MIN, INT8OID)),\n\t\t\t\t\t  PG_INT64_MIN);\n\n\t/* INTERVAL */\n\tfor (i64 = -100; i64 < 100; i64++)\n\t\tTestAssertInt64Eq(i64,\n\t\t\t\t\t\t  ts_interval_value_to_internal(ts_internal_to_interval_value(i64,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  INTERVALOID),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tINTERVALOID));\n\n\tfor (i64 = -10000000; i64 < 100000000; i64 += 1000000)\n\t\tTestAssertInt64Eq(i64,\n\t\t\t\t\t\t  ts_interval_value_to_internal(ts_internal_to_interval_value(i64,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  INTERVALOID),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tINTERVALOID));\n\n\tfor (i64 = -1000000000; i64 < 10000000000; i64 += 100000000)\n\t\tTestAssertInt64Eq(i64,\n\t\t\t\t\t\t  ts_interval_value_to_internal(ts_internal_to_interval_value(i64,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  INTERVALOID),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tINTERVALOID));\n\n\tTestAssertInt64Eq(PG_INT64_MIN,\n\t\t\t\t\t  ts_interval_value_to_internal(ts_internal_to_interval_value(PG_INT64_MIN,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  INTERVALOID),\n\t\t\t\t\t\t\t\t\t\t\t\t\tINTERVALOID));\n\tTestAssertInt64Eq(PG_INT64_MAX,\n\t\t\t\t\t  ts_interval_value_to_internal(ts_internal_to_interval_value(PG_INT64_MAX,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  INTERVALOID),\n\t\t\t\t\t\t\t\t\t\t\t\t\tINTERVALOID));\n\n\tPG_RETURN_VOID();\n}\n"
  },
  {
    "path": "test/src/test_time_utils.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n#include <catalog/pg_type.h>\n#include <fmgr.h>\n#include <funcapi.h>\n#include <utils/date.h>\n\n#include <time_utils.h>\n#include <utils.h>\n\n#include \"test_utils.h\"\n\n/*\n * Functions to show TimescaleDB-specific limits of timestamps and dates:\n */\n\n/*\n * TIMESTAMP WITH TIME ZONE\n */\nTS_FUNCTION_INFO_V1(ts_timestamptz_pg_min);\n\nDatum\nts_timestamptz_pg_min(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_TIMESTAMPTZ(MIN_TIMESTAMP);\n}\n\nTS_FUNCTION_INFO_V1(ts_timestamptz_pg_end);\n\nDatum\nts_timestamptz_pg_end(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_TIMESTAMPTZ(END_TIMESTAMP);\n}\n\nTS_FUNCTION_INFO_V1(ts_timestamptz_min);\n\nDatum\nts_timestamptz_min(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_TIMESTAMPTZ(TS_TIMESTAMP_MIN);\n}\n\nTS_FUNCTION_INFO_V1(ts_timestamptz_end);\n\nDatum\nts_timestamptz_end(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_TIMESTAMPTZ(TS_TIMESTAMP_END);\n}\n\nTS_FUNCTION_INFO_V1(ts_timestamptz_internal_min);\n\nDatum\nts_timestamptz_internal_min(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_INT64(TS_TIMESTAMP_INTERNAL_MIN);\n}\n\nTS_FUNCTION_INFO_V1(ts_timestamptz_internal_end);\n\nDatum\nts_timestamptz_internal_end(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_INT64(TS_TIMESTAMP_INTERNAL_END);\n}\n\n/*\n * TIMESTAMP\n */\nTS_FUNCTION_INFO_V1(ts_timestamp_pg_min);\n\nDatum\nts_timestamp_pg_min(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_TIMESTAMP(MIN_TIMESTAMP);\n}\n\nTS_FUNCTION_INFO_V1(ts_timestamp_pg_end);\n\nDatum\nts_timestamp_pg_end(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_TIMESTAMP(END_TIMESTAMP);\n}\n\nTS_FUNCTION_INFO_V1(ts_timestamp_min);\n\nDatum\nts_timestamp_min(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_TIMESTAMP(TS_TIMESTAMP_MIN);\n}\n\nTS_FUNCTION_INFO_V1(ts_timestamp_end);\n\nDatum\nts_timestamp_end(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_TIMESTAMP(TS_TIMESTAMP_END);\n}\n\nTS_FUNCTION_INFO_V1(ts_timestamp_internal_min);\n\nDatum\nts_timestamp_internal_min(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_INT64(TS_TIMESTAMP_INTERNAL_MIN);\n}\n\nTS_FUNCTION_INFO_V1(ts_timestamp_internal_end);\n\nDatum\nts_timestamp_internal_end(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_INT64(TS_TIMESTAMP_INTERNAL_END);\n}\n\n/*\n * DATE\n */\nTS_FUNCTION_INFO_V1(ts_date_pg_min);\n\nDatum\nts_date_pg_min(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_DATEADT(DATETIME_MIN_JULIAN - POSTGRES_EPOCH_JDATE);\n}\n\nTS_FUNCTION_INFO_V1(ts_date_pg_end);\n\nDatum\nts_date_pg_end(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_DATEADT(DATE_END_JULIAN - POSTGRES_EPOCH_JDATE);\n}\n\nTS_FUNCTION_INFO_V1(ts_date_min);\n\nDatum\nts_date_min(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_DATEADT(TS_DATE_MIN);\n}\n\nTS_FUNCTION_INFO_V1(ts_date_end);\n\nDatum\nts_date_end(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_DATEADT(TS_DATE_END);\n}\n\nTS_FUNCTION_INFO_V1(ts_date_internal_min);\n\nDatum\nts_date_internal_min(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_INT64(TS_DATE_INTERNAL_MIN);\n}\n\nTS_FUNCTION_INFO_V1(ts_date_internal_end);\n\nDatum\nts_date_internal_end(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_INT64(TS_DATE_INTERNAL_END);\n}\n\nTS_FUNCTION_INFO_V1(ts_test_time_utils);\n\nDatum\nts_test_time_utils(PG_FUNCTION_ARGS)\n{\n\tTestAssertInt64Eq(ts_time_get_min(INT8OID), PG_INT64_MIN);\n\tTestAssertInt64Eq(ts_time_get_max(INT8OID), PG_INT64_MAX);\n\tTestAssertInt64Eq(ts_time_get_end_or_max(INT8OID), PG_INT64_MAX);\n\tTestEnsureError(ts_time_get_end(INT8OID));\n\tTestEnsureError(ts_time_get_nobegin(INT8OID));\n\tTestEnsureError(ts_time_get_noend(INT8OID));\n\tTestAssertInt64Eq(DatumGetInt64(ts_time_datum_get_nobegin_or_min(INT8OID)), PG_INT64_MIN);\n\tTestAssertInt64Eq(DatumGetInt64(ts_time_datum_get_min(INT8OID)), PG_INT64_MIN);\n\tTestAssertInt64Eq(DatumGetInt64(ts_time_datum_get_max(INT8OID)), PG_INT64_MAX);\n\tTestEnsureError(ts_time_datum_get_end(INT8OID));\n\tTestEnsureError(ts_time_datum_get_nobegin(INT8OID));\n\tTestEnsureError(ts_time_datum_get_noend(INT8OID));\n\n\tTestAssertInt64Eq(ts_time_get_min(INT4OID), PG_INT32_MIN);\n\tTestAssertInt64Eq(ts_time_get_max(INT4OID), PG_INT32_MAX);\n\tTestAssertInt64Eq(ts_time_get_end_or_max(INT4OID), PG_INT32_MAX);\n\tTestEnsureError(ts_time_get_end(INT4OID));\n\tTestEnsureError(ts_time_get_nobegin(INT4OID));\n\tTestEnsureError(ts_time_get_noend(INT4OID));\n\tTestAssertInt64Eq(DatumGetInt32(ts_time_datum_get_nobegin_or_min(INT4OID)), PG_INT32_MIN);\n\tTestAssertInt64Eq(DatumGetInt32(ts_time_datum_get_min(INT4OID)), PG_INT32_MIN);\n\tTestAssertInt64Eq(DatumGetInt32(ts_time_datum_get_max(INT4OID)), PG_INT32_MAX);\n\tTestEnsureError(ts_time_datum_get_end(INT4OID));\n\tTestEnsureError(ts_time_datum_get_nobegin(INT4OID));\n\tTestEnsureError(ts_time_datum_get_noend(INT4OID));\n\n\tTestAssertInt64Eq(ts_time_get_min(INT2OID), PG_INT16_MIN);\n\tTestAssertInt64Eq(ts_time_get_max(INT2OID), PG_INT16_MAX);\n\tTestAssertInt64Eq(ts_time_get_end_or_max(INT2OID), PG_INT16_MAX);\n\tTestEnsureError(ts_time_get_end(INT2OID));\n\tTestEnsureError(ts_time_get_nobegin(INT2OID));\n\tTestEnsureError(ts_time_get_noend(INT2OID));\n\tTestAssertInt64Eq(DatumGetInt16(ts_time_datum_get_nobegin_or_min(INT2OID)), PG_INT16_MIN);\n\tTestAssertInt64Eq(DatumGetInt16(ts_time_datum_get_min(INT2OID)), PG_INT16_MIN);\n\tTestAssertInt64Eq(DatumGetInt16(ts_time_datum_get_max(INT2OID)), PG_INT16_MAX);\n\tTestEnsureError(ts_time_datum_get_end(INT2OID));\n\tTestEnsureError(ts_time_datum_get_nobegin(INT2OID));\n\tTestEnsureError(ts_time_datum_get_noend(INT2OID));\n\n\tTestAssertInt64Eq(ts_time_get_min(TIMESTAMPOID), TS_TIMESTAMP_INTERNAL_MIN);\n\tTestAssertInt64Eq(ts_time_get_max(TIMESTAMPOID), TS_TIMESTAMP_INTERNAL_MAX);\n\tTestAssertInt64Eq(ts_time_get_end(TIMESTAMPOID), TS_TIMESTAMP_INTERNAL_END);\n\tTestAssertInt64Eq(ts_time_get_end_or_max(TIMESTAMPOID), TS_TIMESTAMP_INTERNAL_END);\n\tTestAssertInt64Eq(ts_time_get_nobegin(TIMESTAMPOID), TS_TIME_NOBEGIN);\n\tTestAssertInt64Eq(ts_time_get_noend(TIMESTAMPOID), TS_TIME_NOEND);\n\tTestAssertInt64Eq(DatumGetTimestamp(ts_time_datum_get_nobegin_or_min(TIMESTAMPOID)),\n\t\t\t\t\t  DT_NOBEGIN);\n\tTestAssertInt64Eq(DatumGetTimestamp(ts_time_datum_get_min(TIMESTAMPOID)), TS_TIMESTAMP_MIN);\n\tTestAssertInt64Eq(DatumGetTimestamp(ts_time_datum_get_max(TIMESTAMPOID)), TS_TIMESTAMP_MAX);\n\tTestAssertInt64Eq(DatumGetTimestamp(ts_time_datum_get_end(TIMESTAMPOID)), TS_TIMESTAMP_END);\n\tTestAssertInt64Eq(DatumGetTimestamp(ts_time_datum_get_nobegin(TIMESTAMPOID)), DT_NOBEGIN);\n\tTestAssertInt64Eq(DatumGetTimestamp(ts_time_datum_get_noend(TIMESTAMPOID)), DT_NOEND);\n\n\tTestAssertInt64Eq(ts_time_get_min(TIMESTAMPTZOID), TS_TIMESTAMP_INTERNAL_MIN);\n\tTestAssertInt64Eq(ts_time_get_max(TIMESTAMPTZOID), TS_TIMESTAMP_INTERNAL_MAX);\n\tTestAssertInt64Eq(ts_time_get_end(TIMESTAMPTZOID), TS_TIMESTAMP_INTERNAL_END);\n\tTestAssertInt64Eq(ts_time_get_end_or_max(TIMESTAMPTZOID), TS_TIMESTAMP_INTERNAL_END);\n\tTestAssertInt64Eq(ts_time_get_nobegin(TIMESTAMPTZOID), TS_TIME_NOBEGIN);\n\tTestAssertInt64Eq(ts_time_get_noend(TIMESTAMPTZOID), TS_TIME_NOEND);\n\tTestAssertInt64Eq(DatumGetTimestampTz(ts_time_datum_get_nobegin_or_min(TIMESTAMPTZOID)),\n\t\t\t\t\t  DT_NOBEGIN);\n\tTestAssertInt64Eq(DatumGetTimestampTz(ts_time_datum_get_min(TIMESTAMPTZOID)), TS_TIMESTAMP_MIN);\n\tTestAssertInt64Eq(DatumGetTimestampTz(ts_time_datum_get_max(TIMESTAMPTZOID)), TS_TIMESTAMP_MAX);\n\tTestAssertInt64Eq(DatumGetTimestampTz(ts_time_datum_get_end(TIMESTAMPTZOID)), TS_TIMESTAMP_END);\n\tTestAssertInt64Eq(DatumGetTimestampTz(ts_time_datum_get_nobegin(TIMESTAMPTZOID)), DT_NOBEGIN);\n\tTestAssertInt64Eq(DatumGetTimestampTz(ts_time_datum_get_noend(TIMESTAMPTZOID)), DT_NOEND);\n\n\tTestAssertInt64Eq(ts_time_get_min(DATEOID), TS_DATE_INTERNAL_MIN);\n\tTestAssertInt64Eq(ts_time_get_max(DATEOID), TS_DATE_INTERNAL_MAX);\n\tTestAssertInt64Eq(ts_time_get_end(DATEOID), TS_DATE_INTERNAL_END);\n\tTestAssertInt64Eq(ts_time_get_end_or_max(DATEOID), TS_DATE_INTERNAL_END);\n\tTestAssertInt64Eq(ts_time_get_nobegin(DATEOID), TS_TIME_NOBEGIN);\n\tTestAssertInt64Eq(ts_time_get_noend(DATEOID), TS_TIME_NOEND);\n\tTestAssertInt64Eq(DatumGetDateADT(ts_time_datum_get_nobegin_or_min(DATEOID)), DATEVAL_NOBEGIN);\n\tTestAssertInt64Eq(DatumGetDateADT(ts_time_datum_get_min(DATEOID)), TS_DATE_MIN);\n\tTestAssertInt64Eq(DatumGetDateADT(ts_time_datum_get_max(DATEOID)), TS_DATE_MAX);\n\tTestAssertInt64Eq(DatumGetDateADT(ts_time_datum_get_end(DATEOID)), TS_DATE_END);\n\tTestAssertInt64Eq(DatumGetDateADT(ts_time_datum_get_nobegin(DATEOID)), DATEVAL_NOBEGIN);\n\tTestAssertInt64Eq(DatumGetDateADT(ts_time_datum_get_noend(DATEOID)), DATEVAL_NOEND);\n\n\t/* Test boundary routines with unsupported time type */\n\tTestEnsureError(ts_time_get_min(NUMERICOID));\n\tTestEnsureError(ts_time_get_max(NUMERICOID));\n\tTestEnsureError(ts_time_get_end(NUMERICOID));\n\tTestEnsureError(ts_time_get_nobegin(NUMERICOID));\n\tTestEnsureError(ts_time_get_noend(NUMERICOID));\n\tTestEnsureError(ts_time_datum_get_nobegin_or_min(NUMERICOID));\n\tTestEnsureError(ts_time_datum_get_min(NUMERICOID));\n\tTestEnsureError(ts_time_datum_get_max(NUMERICOID));\n\tTestEnsureError(ts_time_datum_get_end(NUMERICOID));\n\tTestEnsureError(ts_time_datum_get_nobegin(NUMERICOID));\n\tTestEnsureError(ts_time_datum_get_noend(NUMERICOID));\n\n\t/* Test conversion of min, end, nobegin, and noend between native and\n\t * internal (Unix) time */\n\n\tTestAssertInt64Eq(ts_time_value_to_internal(ts_time_datum_get_min(INT2OID), INT2OID),\n\t\t\t\t\t  ts_time_get_min(INT2OID));\n\tTestAssertInt64Eq(ts_time_value_to_internal(ts_time_datum_get_min(INT4OID), INT4OID),\n\t\t\t\t\t  ts_time_get_min(INT4OID));\n\tTestAssertInt64Eq(ts_time_value_to_internal(ts_time_datum_get_min(INT8OID), INT8OID),\n\t\t\t\t\t  ts_time_get_min(INT8OID));\n\tTestAssertInt64Eq(ts_time_value_to_internal(ts_time_datum_get_min(DATEOID), DATEOID),\n\t\t\t\t\t  ts_time_get_min(DATEOID));\n\tTestAssertInt64Eq(ts_time_value_to_internal(ts_time_datum_get_min(TIMESTAMPOID), TIMESTAMPOID),\n\t\t\t\t\t  ts_time_get_min(TIMESTAMPOID));\n\tTestAssertInt64Eq(ts_time_value_to_internal(ts_time_datum_get_min(TIMESTAMPTZOID),\n\t\t\t\t\t\t\t\t\t\t\t\tTIMESTAMPTZOID),\n\t\t\t\t\t  ts_time_get_min(TIMESTAMPTZOID));\n\n\t/* Test saturating addition */\n\tTestAssertInt64Eq(ts_time_saturating_add(ts_time_get_max(INT2OID), 1, INT2OID),\n\t\t\t\t\t  ts_time_get_max(INT2OID));\n\tTestAssertInt64Eq(ts_time_saturating_add(ts_time_get_max(INT4OID), 1, INT4OID),\n\t\t\t\t\t  ts_time_get_max(INT4OID));\n\tTestAssertInt64Eq(ts_time_saturating_add(ts_time_get_max(INT8OID), 1, INT8OID),\n\t\t\t\t\t  ts_time_get_max(INT8OID));\n\tTestAssertInt64Eq(ts_time_saturating_add(ts_time_get_max(DATEOID), 1, DATEOID),\n\t\t\t\t\t  ts_time_get_noend(DATEOID));\n\tTestAssertInt64Eq(ts_time_saturating_add(ts_time_get_max(DATEOID), 1, DATEOID),\n\t\t\t\t\t  ts_time_get_noend(DATEOID));\n\tTestAssertInt64Eq(ts_time_saturating_add(ts_time_get_max(TIMESTAMPOID), 1, TIMESTAMPOID),\n\t\t\t\t\t  ts_time_get_noend(TIMESTAMPOID));\n\tTestAssertInt64Eq(ts_time_saturating_add(ts_time_get_max(TIMESTAMPTZOID), 1, TIMESTAMPOID),\n\t\t\t\t\t  ts_time_get_noend(TIMESTAMPOID));\n\tTestAssertInt64Eq(ts_time_saturating_add(ts_time_get_end(DATEOID) - 2, 1, DATEOID),\n\t\t\t\t\t  ts_time_get_max(DATEOID));\n\tTestAssertInt64Eq(ts_time_saturating_add(ts_time_get_end(TIMESTAMPOID) - 2, 1, TIMESTAMPOID),\n\t\t\t\t\t  ts_time_get_max(TIMESTAMPOID));\n\tTestAssertInt64Eq(ts_time_saturating_add(ts_time_get_end(TIMESTAMPTZOID) - 2, 1, TIMESTAMPOID),\n\t\t\t\t\t  ts_time_get_max(TIMESTAMPOID));\n\n\tTestAssertInt64Eq(ts_time_saturating_add(ts_time_get_min(INT2OID), -1, INT2OID),\n\t\t\t\t\t  ts_time_get_min(INT2OID));\n\tTestAssertInt64Eq(ts_time_saturating_add(ts_time_get_min(INT4OID), -1, INT4OID),\n\t\t\t\t\t  ts_time_get_min(INT4OID));\n\tTestAssertInt64Eq(ts_time_saturating_add(ts_time_get_min(INT8OID), -1, INT8OID),\n\t\t\t\t\t  ts_time_get_min(INT8OID));\n\tTestAssertInt64Eq(ts_time_saturating_add(ts_time_get_min(DATEOID), -1, DATEOID),\n\t\t\t\t\t  ts_time_get_nobegin(DATEOID));\n\tTestAssertInt64Eq(ts_time_saturating_add(ts_time_get_min(DATEOID), -1, DATEOID),\n\t\t\t\t\t  ts_time_get_nobegin(DATEOID));\n\tTestAssertInt64Eq(ts_time_saturating_add(ts_time_get_min(TIMESTAMPOID), -1, TIMESTAMPOID),\n\t\t\t\t\t  ts_time_get_nobegin(TIMESTAMPOID));\n\tTestAssertInt64Eq(ts_time_saturating_add(ts_time_get_min(TIMESTAMPTZOID), -1, TIMESTAMPOID),\n\t\t\t\t\t  ts_time_get_nobegin(TIMESTAMPOID));\n\n\t/* Test saturating subtraction */\n\tTestAssertInt64Eq(ts_time_saturating_sub(ts_time_get_min(INT2OID), 1, INT2OID),\n\t\t\t\t\t  ts_time_get_min(INT2OID));\n\tTestAssertInt64Eq(ts_time_saturating_sub(ts_time_get_min(INT4OID), 1, INT4OID),\n\t\t\t\t\t  ts_time_get_min(INT4OID));\n\tTestAssertInt64Eq(ts_time_saturating_sub(ts_time_get_min(INT8OID), 1, INT8OID),\n\t\t\t\t\t  ts_time_get_min(INT8OID));\n\tTestAssertInt64Eq(ts_time_saturating_sub(ts_time_get_min(DATEOID), 1, DATEOID),\n\t\t\t\t\t  ts_time_get_nobegin(DATEOID));\n\tTestAssertInt64Eq(ts_time_saturating_sub(ts_time_get_min(TIMESTAMPOID), 1, TIMESTAMPOID),\n\t\t\t\t\t  ts_time_get_nobegin(TIMESTAMPOID));\n\tTestAssertInt64Eq(ts_time_saturating_sub(ts_time_get_min(TIMESTAMPTZOID), 1, TIMESTAMPTZOID),\n\t\t\t\t\t  ts_time_get_nobegin(TIMESTAMPTZOID));\n\tTestAssertInt64Eq(ts_time_saturating_sub(ts_time_get_min(DATEOID) + 1, 1, DATEOID),\n\t\t\t\t\t  ts_time_get_min(DATEOID));\n\tTestAssertInt64Eq(ts_time_saturating_sub(ts_time_get_min(TIMESTAMPOID) + 1, 1, TIMESTAMPOID),\n\t\t\t\t\t  ts_time_get_min(TIMESTAMPOID));\n\tTestAssertInt64Eq(ts_time_saturating_sub(ts_time_get_min(TIMESTAMPTZOID) + 1,\n\t\t\t\t\t\t\t\t\t\t\t 1,\n\t\t\t\t\t\t\t\t\t\t\t TIMESTAMPTZOID),\n\t\t\t\t\t  ts_time_get_min(TIMESTAMPTZOID));\n\n\tTestAssertInt64Eq(ts_time_saturating_sub(ts_time_get_max(INT2OID), -1, INT2OID),\n\t\t\t\t\t  ts_time_get_max(INT2OID));\n\tTestAssertInt64Eq(ts_time_saturating_sub(ts_time_get_max(INT4OID), -1, INT4OID),\n\t\t\t\t\t  ts_time_get_max(INT4OID));\n\tTestAssertInt64Eq(ts_time_saturating_sub(ts_time_get_max(INT8OID), -1, INT8OID),\n\t\t\t\t\t  ts_time_get_max(INT8OID));\n\tTestAssertInt64Eq(ts_time_saturating_sub(ts_time_get_max(DATEOID), -1, DATEOID),\n\t\t\t\t\t  ts_time_get_noend(DATEOID));\n\tTestAssertInt64Eq(ts_time_saturating_sub(ts_time_get_max(TIMESTAMPOID), -1, TIMESTAMPOID),\n\t\t\t\t\t  ts_time_get_noend(TIMESTAMPOID));\n\tTestAssertInt64Eq(ts_time_saturating_sub(ts_time_get_max(TIMESTAMPTZOID), -1, TIMESTAMPTZOID),\n\t\t\t\t\t  ts_time_get_noend(TIMESTAMPTZOID));\n\n\tPG_RETURN_VOID();\n}\n"
  },
  {
    "path": "test/src/test_tss_callbacks.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include <postgres.h>\n\n#include <fmgr.h>\n#include <funcapi.h>\n\n#include \"export.h\"\n#include \"tss_callbacks.h\"\n\nstatic void\ntest_tss_store_hook(const char *query, int query_location, int query_len, uint64 query_id,\n\t\t\t\t\tuint64 total_time, uint64 rows, const BufferUsage *bufusage,\n\t\t\t\t\tconst WalUsage *walusage)\n{\n\telog(NOTICE,\n\t\t \"test_tss_callbacks (mock): query=%s, len=%d, id=\" INT64_FORMAT \", rows=\" INT64_FORMAT,\n\t\t query,\n\t\t query_len,\n\t\t query_id,\n\t\t rows);\n}\n\nstatic bool\ntest_tss_enabled_hook(int level)\n{\n\treturn true;\n}\n\nTSSCallbacks test_tss_callbacks_v1 = {\n\t.version_num = 1,\n\t.tss_store_hook = test_tss_store_hook,\n\t.tss_enabled_hook_type = test_tss_enabled_hook,\n};\n\nTS_FUNCTION_INFO_V1(ts_setup_tss_hook_v1);\nDatum\nts_setup_tss_hook_v1(PG_FUNCTION_ARGS)\n{\n\tTSSCallbacks **ptr = (TSSCallbacks **) find_rendezvous_variable(TSS_CALLBACKS_VAR_NAME);\n\t*ptr = &test_tss_callbacks_v1;\n\n\tPG_RETURN_NULL();\n}\n\nTS_FUNCTION_INFO_V1(ts_teardown_tss_hook_v1);\nDatum\nts_teardown_tss_hook_v1(PG_FUNCTION_ARGS)\n{\n\tTSSCallbacks **ptr = (TSSCallbacks **) find_rendezvous_variable(TSS_CALLBACKS_VAR_NAME);\n\t*ptr = NULL;\n\n\tPG_RETURN_NULL();\n}\n\n/* This version will mismatch with the current supported */\nTSSCallbacks test_tss_callbacks_v0 = {\n\t.version_num = 0,\n\t.tss_store_hook = test_tss_store_hook,\n\t.tss_enabled_hook_type = test_tss_enabled_hook,\n};\n\nTS_FUNCTION_INFO_V1(ts_setup_tss_hook_v0);\nDatum\nts_setup_tss_hook_v0(PG_FUNCTION_ARGS)\n{\n\tTSSCallbacks **ptr = (TSSCallbacks **) find_rendezvous_variable(TSS_CALLBACKS_VAR_NAME);\n\t*ptr = &test_tss_callbacks_v0;\n\n\tPG_RETURN_NULL();\n}\n"
  },
  {
    "path": "test/src/test_utils.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#include \"test_utils.h\"\n\n#include <postgres.h>\n\n#include <compat/compat.h>\n#include <commands/dbcommands.h>\n#include <fmgr.h>\n#include <miscadmin.h>\n#include <storage/latch.h>\n#include <storage/proc.h>\n#include <storage/procarray.h>\n#include <utils/builtins.h>\n#include <utils/elog.h>\n#include <utils/guc.h>\n#include <utils/memutils.h>\n\n#include \"debug_point.h\"\n#include \"extension_constants.h\"\n#include \"utils.h\"\n\nTS_FUNCTION_INFO_V1(ts_test_error_injection);\nTS_FUNCTION_INFO_V1(ts_debug_shippable_error_after_n_rows);\nTS_FUNCTION_INFO_V1(ts_debug_shippable_fatal_after_n_rows);\n\n/*\n * Test assertion macros.\n *\n * Errors are expected since we want to test that the macros work. For each\n * macro, test one failing and one non-failing condition. The non-failing must\n * come first since the failing one will abort the function.\n */\nTS_TEST_FN(ts_test_utils_condition)\n{\n\tbool true_value = true;\n\tbool false_value = false;\n\n\tTestAssertTrue(true_value == true_value);\n\tTestAssertTrue(true_value == false_value);\n\n\tPG_RETURN_VOID();\n}\n\nTS_TEST_FN(ts_test_utils_int64_eq)\n{\n\tint64 big = 32532978;\n\tint64 small = 3242234;\n\n\tTestAssertInt64Eq(big, small);\n\tTestAssertInt64Eq(big, big);\n\n\tPG_RETURN_VOID();\n}\n\nTS_TEST_FN(ts_test_utils_ptr_eq)\n{\n\tbool true_value = true;\n\tbool false_value = false;\n\tbool *true_ptr = &true_value;\n\tbool *false_ptr = &false_value;\n\n\tTestAssertPtrEq(true_ptr, true_ptr);\n\tTestAssertPtrEq(true_ptr, false_ptr);\n\n\tPG_RETURN_VOID();\n}\n\nTS_TEST_FN(ts_test_utils_double_eq)\n{\n\tdouble big_double = 923423478.3242;\n\tdouble small_double = 324.3;\n\n\tTestAssertDoubleEq(big_double, big_double);\n\tTestAssertDoubleEq(big_double, small_double);\n\n\tPG_RETURN_VOID();\n}\n\nDatum\nts_test_error_injection(PG_FUNCTION_ARGS)\n{\n\ttext *name = PG_GETARG_TEXT_PP(0);\n\tDEBUG_ERROR_INJECTION(text_to_cstring(name));\n\tPG_RETURN_VOID();\n}\n\nstatic int\ntransaction_row_counter(void)\n{\n\tstatic LocalTransactionId last_lxid = 0;\n\tstatic int rows_seen = 0;\n#if PG17_GE\n\tif (last_lxid != MyProc->vxid.lxid)\n\t{\n\t\t/* Reset it for each new transaction for predictable results. */\n\t\trows_seen = 0;\n\t\tlast_lxid = MyProc->vxid.lxid;\n\t}\n#else\n\tif (last_lxid != MyProc->lxid)\n\t{\n\t\trows_seen = 0;\n\t\tlast_lxid = MyProc->lxid;\n\t}\n#endif\n\n\treturn rows_seen++;\n}\n\nstatic int\nthrow_after_n_rows(int max_rows, int severity)\n{\n\tint rows_seen = transaction_row_counter();\n\n\tif (max_rows <= rows_seen)\n\t{\n\t\tereport(severity,\n\t\t\t\t(errmsg(\"debug point: requested to error out after %d rows, %d rows seen\",\n\t\t\t\t\t\tmax_rows,\n\t\t\t\t\t\trows_seen)));\n\t}\n\n\treturn rows_seen;\n}\n\nDatum\nts_debug_shippable_error_after_n_rows(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_INT32(throw_after_n_rows(PG_GETARG_INT32(0), ERROR));\n}\n\nDatum\nts_debug_shippable_fatal_after_n_rows(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_INT32(throw_after_n_rows(PG_GETARG_INT32(0), FATAL));\n}\n\n/*\n * After how many rows should we error out according to the user-set option.\n */\nstatic int\nget_error_after_rows()\n{\n\tint error_after = 7103; /* default is an arbitrary prime */\n\n\tconst char *error_after_option =\n\t\tGetConfigOption(MAKE_EXTOPTION(\"debug_broken_sendrecv_error_after\"), true, false);\n\tif (error_after_option)\n\t{\n\t\terror_after = pg_strtoint32(error_after_option);\n\t}\n\n\treturn error_after;\n}\n\n/*\n * Broken send/receive functions for int4 that throw after an (arbitrarily\n * chosen prime or configured) number of rows.\n */\nstatic void\nbroken_sendrecv_throw()\n{\n\t/*\n\t * Use ERROR, not FATAL, because PG versions < 14 are unable to report a\n\t * FATAL error to the access node before closing the connection, so the test\n\t * results would be different.\n\t */\n\t(void) throw_after_n_rows(get_error_after_rows(), ERROR);\n}\n\nTS_FUNCTION_INFO_V1(ts_debug_broken_int4recv);\n\nDatum\nts_debug_broken_int4recv(PG_FUNCTION_ARGS)\n{\n\tbroken_sendrecv_throw();\n\treturn int4recv(fcinfo);\n}\n\nTS_FUNCTION_INFO_V1(ts_debug_broken_int4send);\n\nDatum\nts_debug_broken_int4send(PG_FUNCTION_ARGS)\n{\n\tbroken_sendrecv_throw();\n\treturn int4send(fcinfo);\n}\n\n/* An incorrect int4out that sometimes returns not a number. */\nTS_FUNCTION_INFO_V1(ts_debug_incorrect_int4out);\n\nDatum\nts_debug_incorrect_int4out(PG_FUNCTION_ARGS)\n{\n\tint rows_seen = transaction_row_counter();\n\n\tif (rows_seen >= get_error_after_rows())\n\t{\n\t\tPG_RETURN_CSTRING(\"surprise\");\n\t}\n\n\treturn int4out(fcinfo);\n}\n\n/* Sleeps after a certain number of calls. */\nstatic void\nts_debug_sleepy_function()\n{\n\tstatic LocalTransactionId last_lxid = 0;\n\tstatic int rows_seen = 0;\n\n#if PG17_GE\n\tif (last_lxid != MyProc->vxid.lxid)\n\t{\n\t\t/* Reset it for each new transaction for predictable results. */\n\t\trows_seen = 0;\n\t\tlast_lxid = MyProc->vxid.lxid;\n\t}\n#else\n\tif (last_lxid != MyProc->lxid)\n\t{\n\t\trows_seen = 0;\n\t\tlast_lxid = MyProc->lxid;\n\t}\n#endif\n\n\trows_seen++;\n\n\tif (rows_seen >= 997)\n\t{\n\t\t(void) WaitLatch(MyLatch,\n\t\t\t\t\t\t WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,\n\t\t\t\t\t\t 1000,\n\t\t\t\t\t\t /* wait_event_info = */ 0);\n\t\tResetLatch(MyLatch);\n\n\t\trows_seen = 0;\n\t}\n}\n\nTS_FUNCTION_INFO_V1(ts_debug_sleepy_int4recv);\n\nDatum\nts_debug_sleepy_int4recv(PG_FUNCTION_ARGS)\n{\n\tts_debug_sleepy_function();\n\treturn int4recv(fcinfo);\n}\n\nTS_FUNCTION_INFO_V1(ts_debug_sleepy_int4send);\n\nDatum\nts_debug_sleepy_int4send(PG_FUNCTION_ARGS)\n{\n\tts_debug_sleepy_function();\n\treturn int4send(fcinfo);\n}\n\nTS_FUNCTION_INFO_V1(ts_bgw_wait);\nDatum\nts_bgw_wait(PG_FUNCTION_ARGS)\n{\n\ttext *datname = PG_GETARG_TEXT_PP(0);\n\t/* The timeout is given in seconds, so we compute the number of iterations\n\t * necessary to get a coverage of that time */\n\tuint32 iterations = PG_ARGISNULL(1) ? 5 : (PG_GETARG_UINT32(1) + 4) / 5;\n\tbool raise_error = PG_ARGISNULL(2) ? true : PG_GETARG_BOOL(2);\n\tOid dboid = get_database_oid(text_to_cstring(datname), false);\n\n\t/* This function contains a timeout of 5 seconds, so we iterate a few\n\t * times to make sure that it really has terminated. */\n\tint notherbackends = 0;\n\tint npreparedxacts = 0;\n\twhile (iterations-- > 0)\n\t{\n\t\tif (!CountOtherDBBackends(dboid, &notherbackends, &npreparedxacts))\n\t\t\tPG_RETURN_NULL();\n\t\tereport(NOTICE,\n\t\t\t\t(errmsg(\"source database \\\"%s\\\" is being accessed by other users\",\n\t\t\t\t\t\ttext_to_cstring(datname)),\n\t\t\t\t errdetail(\"There are %d other session(s) and %d prepared transaction(s) using the \"\n\t\t\t\t\t\t   \"database.\",\n\t\t\t\t\t\t   notherbackends,\n\t\t\t\t\t\t   npreparedxacts)));\n\t}\n\n\tif (raise_error)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_IN_USE),\n\t\t\t\t errmsg(\"source database \\\"%s\\\" is being accessed by other users\",\n\t\t\t\t\t\ttext_to_cstring(datname)),\n\t\t\t\t errdetail(\"There are %d other session(s) and %d prepared transaction(s) using the \"\n\t\t\t\t\t\t   \"database.\",\n\t\t\t\t\t\t   notherbackends,\n\t\t\t\t\t\t   npreparedxacts)));\n\t}\n\n\tpg_unreachable();\n}\n\n/*\n * Return the number of bytes allocated in a given memory context and its\n * children.\n */\nTS_FUNCTION_INFO_V1(ts_debug_allocated_bytes);\nDatum\nts_debug_allocated_bytes(PG_FUNCTION_ARGS)\n{\n\tMemoryContext context = NULL;\n\tchar *context_name = text_to_cstring(PG_GETARG_TEXT_PP(0));\n\tif (strcmp(context_name, \"PortalContext\") == 0)\n\t{\n\t\tcontext = PortalContext;\n\t}\n\telse if (strcmp(context_name, \"CacheMemoryContext\") == 0)\n\t{\n\t\tcontext = CacheMemoryContext;\n\t}\n\telse if (strcmp(context_name, \"TopMemoryContext\") == 0)\n\t{\n\t\tcontext = TopMemoryContext;\n\t}\n\telse\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errmsg(\"unknown memory context '%s' (search for arbitrary contexts by name is not\"\n\t\t\t\t\t\t\"implemented)\",\n\t\t\t\t\t\tcontext_name)));\n\t\tPG_RETURN_NULL();\n\t}\n\n\tPG_RETURN_UINT64(MemoryContextMemAllocated(context, /* recurse = */ true));\n}\n\nTS_TEST_FN(ts_test_errdata_to_jsonb)\n{\n\tErrorData *edata = (ErrorData *) palloc(sizeof(ErrorData));\n\tedata->elevel = ERROR;\n\tedata->output_to_server = true;\n\tedata->output_to_client = true;\n\tedata->hide_stmt = false;\n\tedata->hide_ctx = false;\n\tedata->filename = \"test error filename\";\n\tedata->lineno = 123;\n\tedata->funcname = \"test error function\";\n\tedata->domain = \"test error domain\";\n\tedata->context_domain = \"test error context domain\";\n\tedata->sqlerrcode = ERRCODE_INVALID_PARAMETER_VALUE;\n\tedata->message = \"test error message\";\n\tedata->detail = \"test error detail\";\n\tedata->detail_log = \"test error detail log\";\n\tedata->hint = \"test error hint\";\n\tedata->context = \"test error context\";\n\tedata->backtrace = \"test error backtrace\";\n\tedata->message_id = \"test error message id\";\n\tedata->schema_name = \"test error schema\";\n\tedata->table_name = \"test error table\";\n\tedata->column_name = \"test error column\";\n\tedata->datatype_name = \"test error datatype\";\n\tedata->constraint_name = \"test error constraint\";\n\tedata->cursorpos = 42;\n\tedata->internalpos = 42;\n\tedata->internalquery = \"test error internal query\";\n\tedata->saved_errno = 42;\n\n\tNameData proc_schema = { .data = { 0 } };\n\tNameData proc_name = { .data = { 0 } };\n\tnamestrcpy(&proc_schema, \"proc_schema\");\n\tnamestrcpy(&proc_name, \"proc_name\");\n\n\tJsonb *out = ts_errdata_to_jsonb(edata, &proc_schema, &proc_name);\n\n\tPG_RETURN_JSONB_P(out);\n}\n"
  },
  {
    "path": "test/src/test_utils.h",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <access/xact.h>\n#include <fmgr.h>\n\n#include \"export.h\"\n\nstatic inline const char *\nstrip_path(const char *filename)\n{\n\tint i = 0, slash = 0;\n\n\twhile (filename[i] != '\\0')\n\t{\n\t\tif (filename[i] == '/' || filename[i] == '\\\\')\n\t\t\tslash = i;\n\t\ti++;\n\t}\n\n\treturn &filename[slash + 1];\n}\n\n#define TestFailure(fmt, ...)                                                                      \\\n\tdo                                                                                             \\\n\t{                                                                                              \\\n\t\telog(WARNING, \"TestFailure in %s() at line:%d\", __func__, __LINE__);                       \\\n\t\telog(ERROR, \"TestFailure | \" fmt \"\", ##__VA_ARGS__);                                       \\\n\t\tpg_unreachable();                                                                          \\\n\t} while (0)\n\n#define TestAssertInt64Eq(a, b)                                                                    \\\n\tdo                                                                                             \\\n\t{                                                                                              \\\n\t\tint64 a_i = (a);                                                                           \\\n\t\tint64 b_i = (b);                                                                           \\\n\t\tif (a_i != b_i)                                                                            \\\n\t\t\tTestFailure(\"(%s == %s) [\" INT64_FORMAT \" == \" INT64_FORMAT \"]\", #a, #b, a_i, b_i);    \\\n\t} while (0)\n\n#define TestAssertPtrEq(a, b)                                                                      \\\n\tdo                                                                                             \\\n\t{                                                                                              \\\n\t\tvoid *a_i = (a);                                                                           \\\n\t\tvoid *b_i = (b);                                                                           \\\n\t\tif (a_i != b_i)                                                                            \\\n\t\t\tTestFailure(\"(%s == %s)\", #a, #b);                                                     \\\n\t} while (0)\n\n#define TestAssertCStringEq(a, b)                                                                  \\\n\tdo                                                                                             \\\n\t{                                                                                              \\\n\t\tconst char *a_i = (a) == NULL ? \"<null>\" : (a);                                            \\\n\t\tconst char *b_i = (b) == NULL ? \"<null>\" : (b);                                            \\\n\t\tif (strcmp(a_i, b_i) != 0)                                                                 \\\n\t\t\tTestFailure(\"(%s == %s) [%s == %s]\", #a, #b, a_i, b_i);                                \\\n\t} while (0)\n\n#define TestAssertDoubleEq(a, b)                                                                   \\\n\tdo                                                                                             \\\n\t{                                                                                              \\\n\t\tdouble a_i = (a);                                                                          \\\n\t\tdouble b_i = (b);                                                                          \\\n\t\tif (a_i != b_i)                                                                            \\\n\t\t\tTestFailure(\"(%s == %s) [%f == %f]\", #a, #b, a_i, b_i);                                \\\n\t} while (0)\n\n#define TestAssertBoolEq(a, b)                                                                     \\\n\tdo                                                                                             \\\n\t{                                                                                              \\\n\t\tconst char *a_i = (a) ? \"true\" : \"false\";                                                  \\\n\t\tconst char *b_i = (b) ? \"true\" : \"false\";                                                  \\\n\t\tbool a_bool = (a);                                                                         \\\n\t\tbool b_bool = (b);                                                                         \\\n\t\tif (a_bool != b_bool)                                                                      \\\n\t\t\tTestFailure(\"(%s == %s) [%s == %s]\", #a, #b, a_i, b_i);                                \\\n\t} while (0)\n\n#define TestEnsureError(a)                                                                         \\\n\tdo                                                                                             \\\n\t{                                                                                              \\\n\t\tvolatile bool this_has_panicked = false;                                                   \\\n\t\tMemoryContext oldctx = CurrentMemoryContext;                                               \\\n\t\tBeginInternalSubTransaction(\"error expected\");                                             \\\n\t\tPG_TRY();                                                                                  \\\n\t\t{                                                                                          \\\n\t\t\t(a);                                                                                   \\\n\t\t}                                                                                          \\\n\t\tPG_CATCH();                                                                                \\\n\t\t{                                                                                          \\\n\t\t\tthis_has_panicked = true;                                                              \\\n\t\t\tRollbackAndReleaseCurrentSubTransaction();                                             \\\n\t\t\tFlushErrorState();                                                                     \\\n\t\t}                                                                                          \\\n\t\tPG_END_TRY();                                                                              \\\n\t\tMemoryContextSwitchTo(oldctx);                                                             \\\n\t\tif (!this_has_panicked)                                                                    \\\n\t\t{                                                                                          \\\n\t\t\telog(ERROR, \"failed to panic\");                                                        \\\n\t\t}                                                                                          \\\n\t} while (0)\n\n#define TestAssertTrue(cond)                                                                       \\\n\tdo                                                                                             \\\n\t{                                                                                              \\\n\t\tif (!(cond))                                                                               \\\n\t\t\tTestFailure(\"(%s)\", #cond);                                                            \\\n\t} while (0)\n\n#define TS_TEST_FN(name)                                                                           \\\n\tTS_FUNCTION_INFO_V1(name);                                                                     \\\n\tDatum name(PG_FUNCTION_ARGS)\n\n#ifdef __JSONB_H__\nextern const char *jsonb_to_cstring(Jsonb *jsonb);\nextern Jsonb *cstring_to_jsonb(const char *cstring);\n\n#define TestAssertJsonbEqCstring(a, b)                                                             \\\n\tdo                                                                                             \\\n\t{                                                                                              \\\n\t\tJsonb *a_j = (a);                                                                          \\\n\t\tconst char *a_i = (a) == NULL ? \"<null>\" : jsonb_to_cstring(a_j);                          \\\n\t\tconst char *b_i = (b) == NULL ? \"<null>\" : (b);                                            \\\n\t\tif (strcmp(a_i, b_i) != 0)                                                                 \\\n\t\t\tTestFailure(\"(%s == %s)\", a_i, b_i);                                                   \\\n\t} while (0)\n#endif\n"
  },
  {
    "path": "test/src/test_with_clause_parser.c",
    "content": "/*\n * This file and its contents are licensed under the Apache License 2.0.\n * Please see the included NOTICE for copyright information and\n * LICENSE-APACHE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <access/htup_details.h>\n#include <catalog/pg_type.h>\n#include <commands/defrem.h>\n#include <fmgr.h>\n#include <funcapi.h>\n#include <postgres_ext.h>\n#include <utils/array.h>\n#include <utils/builtins.h>\n#include <utils/elog.h>\n#include <utils/lsyscache.h>\n#include <utils/memutils.h>\n#include <utils/regproc.h>\n\n#include \"annotations.h\"\n#include \"export.h\"\n#include \"test_utils.h\"\n#include \"with_clause/with_clause_parser.h\"\n\nTS_FUNCTION_INFO_V1(ts_sqlstate_raise_in);\nTS_FUNCTION_INFO_V1(ts_sqlstate_raise_out);\n\n/*\n * Input function that will raise the error code give. This means that you can\n * trigger an error when reading and converting a string to this type.\n */\nDatum\nts_sqlstate_raise_in(PG_FUNCTION_ARGS)\n{\n\tchar *code = PG_GETARG_CSTRING(0);\n\tif (strlen(code) != 5)\n\t\tereport(ERROR,\n\t\t\t\terrcode(ERRCODE_SYNTAX_ERROR),\n\t\t\t\terrmsg(\"error code \\\"%s\\\" was not of length 5\", code));\n\tint sqlstate = MAKE_SQLSTATE(code[0], code[1], code[2], code[3], code[4]);\n\tereport(ERROR, errcode(sqlstate), errmsg(\"raised requested error code \\\"%s\\\"\", code));\n\treturn 0;\n}\n\n/*\n * Dummy function, we do not store values of this type anywhere.\n */\nDatum\nts_sqlstate_raise_out(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_CSTRING(\"uninteresting\");\n}\n\nstatic DefElem *\ndef_elem_from_texts(Datum *texts, int nelems)\n{\n\tDefElem *elem = palloc0(sizeof(*elem));\n\tswitch (nelems)\n\t{\n\t\tcase 1:\n\t\t\telem->defname = text_to_cstring(DatumGetTextP(texts[0]));\n\t\t\tbreak;\n\t\tcase 3:\n\t\t\telem->arg = (Node *) makeString(text_to_cstring(DatumGetTextP(texts[2])));\n\t\t\tTS_FALLTHROUGH;\n\t\tcase 2:\n\t\t\telem->defname = text_to_cstring(DatumGetTextP(texts[1]));\n\t\t\telem->defnamespace = text_to_cstring(DatumGetTextP(texts[0]));\n\t\t\tbreak;\n\t\tdefault:\n\t\t\telog(ERROR, \"%d elements invalid for defelem\", nelems);\n\t}\n\treturn elem;\n}\n\nstatic List *\ndef_elems_from_array(ArrayType *with_clause_array)\n{\n\tArrayMetaState with_clause_meta = { .element_type = TEXTOID };\n\tArrayIterator with_clause_iter;\n\tDatum with_clause_datum;\n\tbool with_clause_null;\n\tList *def_elems = NIL;\n\n\tget_typlenbyvalalign(with_clause_meta.element_type,\n\t\t\t\t\t\t &with_clause_meta.typlen,\n\t\t\t\t\t\t &with_clause_meta.typbyval,\n\t\t\t\t\t\t &with_clause_meta.typalign);\n\twith_clause_iter = array_create_iterator(with_clause_array, 1, &with_clause_meta);\n\n\twhile (array_iterate(with_clause_iter, &with_clause_datum, &with_clause_null))\n\t{\n\t\tDatum *with_clause_fields;\n\t\tint with_clause_elems;\n\t\tArrayType *with_clause = DatumGetArrayTypeP(with_clause_datum);\n\t\tTestAssertTrue(!with_clause_null);\n\t\tdeconstruct_array(with_clause,\n\t\t\t\t\t\t  TEXTOID,\n\t\t\t\t\t\t  with_clause_meta.typlen,\n\t\t\t\t\t\t  with_clause_meta.typbyval,\n\t\t\t\t\t\t  with_clause_meta.typalign,\n\t\t\t\t\t\t  &with_clause_fields,\n\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t  &with_clause_elems);\n\t\tdef_elems = lappend(def_elems, def_elem_from_texts(with_clause_fields, with_clause_elems));\n\t}\n\treturn def_elems;\n}\n\ntypedef struct FilteredWithClauses\n{\n\tList *within;\n\tList *non_ts_namespace_option;\n\tList *without;\n} FilteredWithClauses;\n\nstatic HeapTuple\ncreate_filter_tuple(TupleDesc tuple_desc, DefElem *d, bool within)\n{\n\tDatum *values = palloc0(sizeof(*values) * tuple_desc->natts);\n\tbool *nulls = palloc0(sizeof(*nulls) * tuple_desc->natts);\n\n\tTestAssertTrue(tuple_desc->natts >= 4);\n\n\tif (d->defnamespace != NULL)\n\t\tvalues[0] = CStringGetTextDatum(d->defnamespace);\n\telse\n\t\tnulls[0] = true;\n\n\tif (d->defname != NULL)\n\t\tvalues[1] = CStringGetTextDatum(d->defname);\n\telse\n\t\tnulls[1] = true;\n\n\tif (d->arg != NULL)\n\t\tvalues[2] = CStringGetTextDatum(defGetString(d));\n\telse\n\t\tnulls[2] = true;\n\n\tvalues[3] = BoolGetDatum(within);\n\treturn heap_form_tuple(tuple_desc, values, nulls);\n}\n\nTS_TEST_FN(ts_test_with_clause_filter)\n{\n\tFuncCallContext *funcctx;\n\tFilteredWithClauses *filtered;\n\tMemoryContext oldcontext;\n\n\tif (SRF_IS_FIRSTCALL())\n\t{\n\t\tArrayType *with_clause_array;\n\t\tTupleDesc tupdesc;\n\t\tList *def_elems;\n\n\t\tfuncctx = SRF_FIRSTCALL_INIT();\n\t\toldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);\n\t\twith_clause_array = DatumGetArrayTypeP(PG_GETARG_DATUM(0));\n\n\t\tif (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"function returning record called in context \"\n\t\t\t\t\t\t\t\"that cannot accept type record\")));\n\n\t\tfuncctx->tuple_desc = BlessTupleDesc(tupdesc);\n\n\t\tdef_elems = def_elems_from_array(with_clause_array);\n\n\t\tfiltered = palloc(sizeof(*filtered));\n\t\tfiltered->within = NIL;\n\t\tfiltered->non_ts_namespace_option = NIL;\n\t\tfiltered->without = NIL;\n\n\t\tts_with_clause_filter(def_elems,\n\t\t\t\t\t\t\t  &filtered->within,\n\t\t\t\t\t\t\t  &filtered->non_ts_namespace_option,\n\t\t\t\t\t\t\t  &filtered->without);\n\n\t\tfuncctx->user_fctx = filtered;\n\n\t\tMemoryContextSwitchTo(oldcontext);\n\t}\n\n\tfuncctx = SRF_PERCALL_SETUP();\n\n\tfiltered = funcctx->user_fctx;\n\tif (filtered->within != NIL)\n\t{\n\t\tHeapTuple tuple;\n\t\tDefElem *d = linitial(filtered->within);\n\t\ttuple = create_filter_tuple(funcctx->tuple_desc, d, true);\n\t\tfiltered->within = list_delete_first(filtered->within);\n\t\tSRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));\n\t}\n\telse if (filtered->non_ts_namespace_option != NIL)\n\t{\n\t\tHeapTuple tuple;\n\t\tDefElem *d = linitial(filtered->non_ts_namespace_option);\n\t\ttuple = create_filter_tuple(funcctx->tuple_desc, d, true);\n\t\tfiltered->non_ts_namespace_option = list_delete_first(filtered->non_ts_namespace_option);\n\t\tSRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));\n\t}\n\telse if (filtered->without != NIL)\n\t{\n\t\tHeapTuple tuple;\n\t\tDefElem *d = linitial(filtered->without);\n\t\ttuple = create_filter_tuple(funcctx->tuple_desc, d, false);\n\t\tfiltered->without = list_delete_first(filtered->without);\n\t\tSRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));\n\t}\n\telse\n\t\tSRF_RETURN_DONE(funcctx);\n}\n\ntypedef enum TestArgs\n{\n\tTestArgUnimpl = 0,\n\tTestArgBool,\n\tTestArgInt32,\n\tTestArgDefault,\n\tTestArgName,\n\tTestArgRegclass,\n\tTestArgRaise,\n} TestArgs;\n\nstatic WithClauseDefinition test_args[] = {\n\t[TestArgUnimpl] = { .arg_names = {\"unimplemented\", NULL},\n\t\t\t\t\t\t.type_id = InvalidOid, },\n\t[TestArgBool] = { .arg_names = {\"bool\", NULL}, .type_id = BOOLOID, },\n\t[TestArgInt32] = { .arg_names = {\"int32\", NULL}, .type_id = INT4OID, },\n\t[TestArgDefault] = { .arg_names = {\"default\", NULL},\n\t\t\t\t\t\t .type_id = INT4OID,\n\t\t\t\t\t\t .default_val = (Datum)-100 },\n\t[TestArgName] = { .arg_names = {\"name\", NULL}, .type_id = NAMEOID, },\n\t[TestArgRegclass] = {\n\t\t.arg_names = {\"regclass\", NULL},\n\t\t.type_id = REGCLASSOID,\n\t},\n\t[TestArgRaise] = {\n\t\t.arg_names = {\"sqlstate_raise\", NULL},\n\t\t.type_id = InvalidOid,\n\t},\n};\n\ntypedef struct WithClauseValue\n{\n\tWithClauseResult *parsed;\n\tint i;\n} WithClauseValue;\n\nTS_TEST_FN(ts_test_with_clause_parse)\n{\n\tFuncCallContext *funcctx;\n\tMemoryContext oldcontext;\n\tDatum *values;\n\tbool *nulls;\n\tHeapTuple tuple;\n\tWithClauseValue *result;\n\n\t/*\n\t * Look up any missing type ids before using it below to allow\n\t * user-defined types.\n\t *\n\t * Note that this will not look up types we have found in previous calls\n\t * of this function.\n\t *\n\t * We use the slightly more complicated way of calling to_regtype since\n\t * that exists on all versions of PostgreSQL. We cannot use regtypein\n\t * since that can generate errors and we do not want to deal with that.\n\t */\n\tfor (unsigned int i = 0; i < TS_ARRAY_LEN(test_args); ++i)\n\t{\n\t\tLOCAL_FCINFO(fcinfo_in, 1);\n\t\tDatum result;\n\t\tif (!OidIsValid(test_args[i].type_id))\n\t\t{\n\t\t\tInitFunctionCallInfoData(*fcinfo_in, NULL, 1, InvalidOid, NULL, NULL);\n\t\t\tfcinfo_in->args[0].value = CStringGetTextDatum(test_args[i].arg_names[0]);\n\t\t\tfcinfo_in->args[0].isnull = false;\n\t\t\tresult = to_regtype(fcinfo_in);\n\t\t\tif (!fcinfo_in->isnull)\n\t\t\t\ttest_args[i].type_id = DatumGetObjectId(result);\n\t\t}\n\t}\n\n\tif (SRF_IS_FIRSTCALL())\n\t{\n\t\tArrayType *with_clause_array;\n\t\tList *def_elems;\n\t\tTupleDesc tupdesc;\n\t\tWithClauseResult *parsed;\n\n\t\tfuncctx = SRF_FIRSTCALL_INIT();\n\t\toldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);\n\n\t\tif (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"function returning record called in context \"\n\t\t\t\t\t\t\t\"that cannot accept type record\")));\n\n\t\tfuncctx->tuple_desc = BlessTupleDesc(tupdesc);\n\n\t\twith_clause_array = DatumGetArrayTypeP(PG_GETARG_DATUM(0));\n\t\tdef_elems = def_elems_from_array(with_clause_array);\n\n\t\tparsed = ts_with_clauses_parse(def_elems, test_args, TS_ARRAY_LEN(test_args));\n\t\tresult = palloc(sizeof(*result));\n\t\tresult->parsed = parsed;\n\t\tresult->i = 0;\n\n\t\tfuncctx->user_fctx = result;\n\t\tMemoryContextSwitchTo(oldcontext);\n\t}\n\n\tfuncctx = SRF_PERCALL_SETUP();\n\n\tresult = funcctx->user_fctx;\n\tif (result == NULL || (size_t) result->i >= TS_ARRAY_LEN(test_args))\n\t\tSRF_RETURN_DONE(funcctx);\n\n\tvalues = palloc0(sizeof(*values) * funcctx->tuple_desc->natts);\n\tnulls = palloc(sizeof(*nulls) * funcctx->tuple_desc->natts);\n\tmemset(nulls, true, sizeof(*nulls) * funcctx->tuple_desc->natts);\n\n\tvalues[0] = CStringGetTextDatum(test_args[result->i].arg_names[0]);\n\tnulls[0] = false;\n\tif (!result->parsed[result->i].is_default || result->i == TestArgDefault)\n\t{\n\t\tvalues[result->i + 1] = result->parsed[result->i].parsed;\n\t\tnulls[result->i + 1] = false;\n\t}\n\n\ttuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);\n\n\tresult->i += 1;\n\tSRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));\n}\n"
  },
  {
    "path": "test/t/001_replication_telemetry.pl",
    "content": "# This file and its contents are licensed under the Timescale License.\n# Please see the included NOTICE for copyright information and\n# LICENSE-TIMESCALE for a copy of the license.\n\nuse strict;\nuse warnings;\nuse TimescaleNode;\nuse Data::Dumper;\nuse Test::More tests => 4;\n\n# This test checks that the extension state is handled correctly\n# across multiple sessions. Specifically, if the extension state\n# changes in one session (e.g., the extension is created or dropped),\n# this should be reflected in other concurrent sessions.\n#\n# To test this, we start one background psql session that stays open\n# for the duration of the tests and then change the extension state\n# from other sessions.\nmy $node_primary = TimescaleNode->create(\n\t'primary',\n\tallows_streaming => 1,\n\tauth_extra       => [ '--create-role', 'repl_role' ]);\nmy $backup_name = 'my_backup';\n\n# Take backup\n$node_primary->backup($backup_name);\n\n# Create streaming standby linking to primary\nmy $node_standby = PostgreSQL::Test::Cluster->new('standby_1');\n$node_standby->init_from_backup($node_primary, $backup_name,\n\thas_streaming => 1);\n$node_standby->start;\n\n# Wait for standby to catch up\n$node_primary->wait_for_catchup($node_standby, 'replay',\n\t$node_primary->lsn('insert'));\n\n\nmy $result = $node_primary->safe_psql('postgres',\n\t\"SELECT get_telemetry_report()->'replication'->>'num_wal_senders'\");\nis($result, qq(1), 'number of wal senders on primary');\n\n$result = $node_primary->safe_psql('postgres',\n\t\"SELECT get_telemetry_report()->'replication'->>'is_wal_receiver'\");\nis($result, qq(false), 'primary is wal receiver');\n\n$result = $node_standby->safe_psql('postgres',\n\t\"SELECT get_telemetry_report()->'replication'->>'num_wal_senders'\");\nis($result, qq(0), 'number of wal senders on standby');\n\n$result = $node_standby->safe_psql('postgres',\n\t\"SELECT get_telemetry_report()->'replication'->>'is_wal_receiver'\");\nis($result, qq(true), 'standby is wal receiver');\n\ndone_testing();\n\n1;\n"
  },
  {
    "path": "test/t/CMakeLists.txt",
    "content": "if(CMAKE_BUILD_TYPE MATCHES Debug)\n  list(APPEND PROVE_TEST_FILES 001_replication_telemetry.pl)\nendif(CMAKE_BUILD_TYPE MATCHES Debug)\n\nforeach(P_FILE ${PROVE_TEST_FILES})\n  configure_file(${P_FILE} ${CMAKE_CURRENT_BINARY_DIR}/${P_FILE} COPYONLY)\nendforeach(P_FILE)\n"
  },
  {
    "path": "test/test-defs.cmake",
    "content": "set(TEST_ROLE_SUPERUSER super_user)\nset(TEST_ROLE_DEFAULT_PERM_USER default_perm_user)\nset(TEST_ROLE_DEFAULT_PERM_USER_2 default_perm_user_2)\n\nset(TEST_ROLE_1 test_role_1)\nset(TEST_ROLE_2 test_role_2)\nset(TEST_ROLE_3 test_role_3)\nset(TEST_ROLE_4 test_role_4)\nset(TEST_ROLE_READ_ONLY test_role_read_only)\n\n# TEST_ROLE_2 has password in passfile\nset(TEST_ROLE_2_PASS pass)\n# TEST_ROLE_3 does not have password in passfile\nset(TEST_ROLE_3_PASS pass)\n# TEST_ROLE_4 does not have password in passfile\nset(TEST_ROLE_4_PASS pass)\n\nset(TEST_INPUT_DIR ${CMAKE_CURRENT_SOURCE_DIR})\nset(TEST_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR})\nset(TEST_CLUSTER ${TEST_OUTPUT_DIR}/testcluster)\n\n# Basic connection info for test instance\nset(TEST_PGPORT_LOCAL\n    5432\n    CACHE STRING \"The port of a running PostgreSQL instance\")\nset(TEST_PGHOST\n    localhost\n    CACHE STRING \"The hostname of a running PostgreSQL instance\")\nset(TEST_PGUSER\n    ${TEST_ROLE_DEFAULT_PERM_USER}\n    CACHE STRING \"The PostgreSQL test user\")\nset(TEST_DBNAME\n    single\n    CACHE STRING \"The database name to use for tests\")\nset(TEST_PGPORT_TEMP_INSTANCE\n    55432\n    CACHE STRING \"The port to run a temporary test PostgreSQL instance on\")\nset(TEST_SCHEDULE ${CMAKE_CURRENT_BINARY_DIR}/test_schedule)\nset(TEST_SCHEDULE_SHARED\n    ${CMAKE_CURRENT_BINARY_DIR}/shared/test_schedule_shared)\nset(ISOLATION_TEST_SCHEDULE ${CMAKE_CURRENT_BINARY_DIR}/isolation_test_schedule)\nset(TEST_PASSFILE ${TEST_OUTPUT_DIR}/pgpass.conf)\nset(TEST_TIMEOUT\n    120\n    CACHE STRING \"Timeout in seconds for individual tests\")\n\n# Windows does not support local connections (unix domain sockets)\nif(WIN32)\n  set(TEST_HBA_LOCAL \"#local\")\nelse()\n  set(TEST_HBA_LOCAL \"local\")\nendif()\n\n# This variable is set differently in CI. We use it to save the logs outside the\n# tmp instance, because it is deleted by pg_regress on successful test\n# completion, and we want to run some additional checks on the logs in any case.\nset(TEST_PG_LOG_DIRECTORY\n    \"log\"\n    CACHE STRING \"Log directory for regression tests\")\n\nif(USE_TELEMETRY)\n  set(TELEMETRY_DEFAULT_SETTING \"timescaledb.telemetry_level=off\")\nelse()\n  set(TELEMETRY_DEFAULT_SETTING)\nendif()\nconfigure_file(postgresql.conf.in postgresql.conf)\nconfigure_file(${PRIMARY_TEST_DIR}/pgtest.conf.in pgtest.conf)\n\n# pgpass file requires chmod 0600\nconfigure_file(${PRIMARY_TEST_DIR}/pgpass.conf.in\n               ${TEST_OUTPUT_DIR}${CMAKE_FILES_DIRECTORY}/pgpass.conf)\nfile(\n  COPY ${TEST_OUTPUT_DIR}${CMAKE_FILES_DIRECTORY}/pgpass.conf\n  DESTINATION ${TEST_OUTPUT_DIR}\n  NO_SOURCE_PERMISSIONS\n  FILE_PERMISSIONS OWNER_READ OWNER_WRITE)\n\nset(PG_REGRESS_OPTS_BASE --host=${TEST_PGHOST}\n                         --dlpath=${PROJECT_BINARY_DIR}/src)\n\nset(PG_REGRESS_OPTS_EXTRA\n    --create-role=${TEST_ROLE_SUPERUSER},${TEST_ROLE_DEFAULT_PERM_USER},${TEST_ROLE_DEFAULT_PERM_USER_2},${TEST_ROLE_1},${TEST_ROLE_2},${TEST_ROLE_3},${TEST_ROLE_4},${TEST_ROLE_READ_ONLY}\n    --dbname=${TEST_DBNAME}\n    --launcher=${PRIMARY_TEST_DIR}/runner.sh)\n\nset(PG_REGRESS_SHARED_OPTS_EXTRA\n    --create-role=${TEST_ROLE_SUPERUSER},${TEST_ROLE_DEFAULT_PERM_USER},${TEST_ROLE_DEFAULT_PERM_USER_2}\n    --dbname=${TEST_DBNAME}\n    --launcher=${PRIMARY_TEST_DIR}/runner_shared.sh)\n\nset(PG_ISOLATION_REGRESS_OPTS_EXTRA\n    --create-role=${TEST_ROLE_SUPERUSER},${TEST_ROLE_DEFAULT_PERM_USER},${TEST_ROLE_DEFAULT_PERM_USER_2},${TEST_ROLE_1},${TEST_ROLE_2},${TEST_ROLE_3},${TEST_ROLE_READ_ONLY}\n    --dbname=${TEST_DBNAME}\n    --launcher=${PRIMARY_TEST_DIR}/runner_isolation.sh)\n\nset(PG_REGRESS_OPTS_INOUT --inputdir=${TEST_INPUT_DIR}\n                          --outputdir=${TEST_OUTPUT_DIR})\n\nset(PG_REGRESS_SHARED_OPTS_INOUT\n    --inputdir=${TEST_INPUT_DIR}/shared --outputdir=${TEST_OUTPUT_DIR}/shared\n    --load-extension=timescaledb)\n\nset(PG_ISOLATION_REGRESS_OPTS_INOUT\n    --inputdir=${TEST_INPUT_DIR}/isolation\n    --outputdir=${TEST_OUTPUT_DIR}/isolation --load-extension=timescaledb)\n\nset(PG_REGRESS_OPTS_TEMP_INSTANCE\n    --port=${TEST_PGPORT_TEMP_INSTANCE} --temp-instance=${TEST_CLUSTER}\n    --temp-config=${TEST_OUTPUT_DIR}/postgresql.conf)\n\nset(PG_REGRESS_OPTS_TEMP_INSTANCE_PGTEST\n    --port=${TEST_PGPORT_TEMP_INSTANCE} --temp-instance=${TEST_CLUSTER}\n    --temp-config=${TEST_OUTPUT_DIR}/pgtest.conf)\n\nset(PG_REGRESS_OPTS_LOCAL_INSTANCE --port=${TEST_PGPORT_LOCAL})\n\nif(PG_REGRESS)\n  set(PG_REGRESS_ENV\n      PG_BINDIR=${PG_BINDIR}\n      PG_REGRESS=${PG_REGRESS}\n      PG_REGRESS_USE_FAKETIME=1\n      TEST_DBNAME=${TEST_DBNAME}\n      TEST_INPUT_DIR=${TEST_INPUT_DIR}\n      TEST_OUTPUT_DIR=${TEST_OUTPUT_DIR}\n      TEST_PGHOST=${TEST_PGHOST}\n      TEST_PGUSER=${TEST_PGUSER}\n      TEST_ROLE_1=${TEST_ROLE_1}\n      TEST_ROLE_2=${TEST_ROLE_2}\n      TEST_ROLE_2_PASS=${TEST_ROLE_2_PASS}\n      TEST_ROLE_3=${TEST_ROLE_3}\n      TEST_ROLE_3_PASS=${TEST_ROLE_3_PASS}\n      TEST_ROLE_4_PASS=${TEST_ROLE_4_PASS}\n      TEST_ROLE_DEFAULT_PERM_USER=${TEST_ROLE_DEFAULT_PERM_USER}\n      TEST_ROLE_DEFAULT_PERM_USER_2=${TEST_ROLE_DEFAULT_PERM_USER_2}\n      TEST_ROLE_READ_ONLY=${TEST_ROLE_READ_ONLY}\n      TEST_ROLE_SUPERUSER=${TEST_ROLE_SUPERUSER}\n      TEST_SCHEDULE=${TEST_SCHEDULE})\nendif()\n\nif(PG_ISOLATION_REGRESS)\n  set(PG_ISOLATION_REGRESS_ENV\n      PG_BINDIR=${PG_BINDIR}\n      TEST_PGUSER=${TEST_PGUSER}\n      TEST_ROLE_SUPERUSER=${TEST_ROLE_SUPERUSER}\n      TEST_ROLE_DEFAULT_PERM_USER=${TEST_ROLE_DEFAULT_PERM_USER}\n      TEST_ROLE_DEFAULT_PERM_USER_2=${TEST_ROLE_DEFAULT_PERM_USER_2}\n      TEST_ROLE_1=${TEST_ROLE_1}\n      TEST_ROLE_2=${TEST_ROLE_2}\n      TEST_ROLE_3=${TEST_ROLE_3}\n      TEST_ROLE_2_PASS=${TEST_2_PASS}\n      TEST_ROLE_3_PASS=${TEST_3_PASS}\n      TEST_ROLE_READ_ONLY=${TEST_ROLE_READ_ONLY}\n      TEST_DBNAME=${TEST_DBNAME}\n      TEST_INPUT_DIR=${TEST_INPUT_DIR}\n      TEST_OUTPUT_DIR=${TEST_OUTPUT_DIR}\n      TEST_SCHEDULE=${ISOLATION_TEST_SCHEDULE}\n      PG_REGRESS=${PG_ISOLATION_REGRESS})\nendif()\n\nset(TEST_VERSION_SUFFIX ${PG_VERSION_MAJOR})\nif(APACHE_ONLY)\n  set(TEST_LICENSE_SUFFIX \"oss\")\nelse()\n  set(TEST_LICENSE_SUFFIX \"tsl\")\nendif()\n"
  },
  {
    "path": "timescaledb.control.in",
    "content": "# timescaledb extension\ncomment = 'Enables scalable inserts and complex queries for time-series data'\ndefault_version = '@PROJECT_VERSION_MOD@'\nmodule_pathname = '$libdir/timescaledb-@PROJECT_VERSION_MOD@'\n#extension cannot be relocatable once installed because it uses multiple schemas and that is forbidden by PG.\n#(though this extension is relocatable during installation).\nrelocatable = false\ntrusted = true\n"
  },
  {
    "path": "tsl/CMakeLists.txt",
    "content": "option(CODECOVERAGE \"Enable fuzzing of compression using Libfuzzer\" OFF)\n\nif(COMPRESSION_FUZZING)\n  add_compile_definitions(TS_COMPRESSION_FUZZING=1)\nendif()\n\n# Add the subdirectories\nadd_subdirectory(test)\nadd_subdirectory(src)\n"
  },
  {
    "path": "tsl/LICENSE-TIMESCALE",
    "content": "TIMESCALE LICENSE AGREEMENT\n\nPosted Date:  September 24, 2020\n\nPLEASE READ CAREFULLY THIS TIMESCALE LICENSE AGREEMENT (\"TSL Agreement\"), WHICH\nCONSTITUTES A LEGALLY BINDING AGREEMENT AND GOVERNS USE OF THE TIMESCALE\nTIME-SERIES DATABASE SOFTWARE AND RELATED SOFTWARE THAT IS PROVIDED SUBJECT TO\nTHIS TSL AGREEMENT.  BY INSTALLING OR USING SUCH SOFTWARE, YOU AGREE THAT YOU\nHAVE READ AND AGREE TO BE BOUND BY THE TERMS AND CONDITIONS OF THIS TSL\nAGREEMENT.  IF YOU DO NOT AGREE WITH SUCH TERMS AND CONDITIONS, YOU MAY NOT\nINSTALL OR USE SUCH SOFTWARE.  IF YOU ARE INSTALLING OR USING SUCH SOFTWARE ON\nBEHALF OF A LEGAL ENTITY, YOU REPRESENT AND WARRANT THAT YOU HAVE THE AUTHORITY\nTO AGREE TO THE TERMS AND CONDITIONS OF THIS TSL AGREEMENT ON BEHALF OF THAT\nLEGAL ENTITY AND THE RIGHT TO BIND THAT LEGAL ENTITY TO THIS TSL AGREEMENT.\n\nThis TSL Agreement is entered into by and between Timescale, Inc. (\"Timescale\")\nand you or the legal entity on whose behalf you are accepting this TSL\nAgreement (\"You\").\n\n0. BACKGROUND\n\n   The Timescale time-series database software and related software is offered\n   as \"open code\" or \"source-available\" code.  This means that all source code\n   of the software is available for inspection and download at\n   https://github.com/timescale.  The Timescale software is composed of two\n   major pieces.\n\n   The first piece (referred to herein as the Timescale Open Source Software,\n   as defined below) is open source software that is licensed under the Apache\n   Version 2.0 license.\n\n   The second piece (referred to herein as the TSL Licensed Software, as\n   defined below) is all of the Timescale Software other than the Timescale\n   Open Source Software. The TSL Licensed Software may be used under this TSL\n   Agreement without charge.\n\n1. GOVERNING LICENSES\n\n   1.1 Source Code.  The source code for all Timescale Software is made\n   publicly available by Timescale at https://github.com/timescale.  However,\n   different license agreements govern the use of different parts of the\n   Timescale Software source code.  The use of Timescale Open Source Software,\n   in both source and executable forms, is governed by the terms of the Apache\n   License Version 2.0, a copy of which is available at\n   https://opensource.org/licenses/Apache-2.0.  The use of all other Timescale\n   Software, in both source and executable forms, is governed by this TSL\n   Agreement.\n\n   1.2 License Rights to Your Customers.  As set forth in Section 2.1 below,\n   the use by Your customers of the Timescale Software as part of any Value\n   Added Products or Services that You distribute will be subject to the most\n   current version of this TSL Agreement.\n\n2. GRANT OF LICENSES\n\n   2.1 Grant.  Conditioned upon compliance with all of the terms and conditions\n   of this TSL Agreement, Timescale grants to You at no charge the following\n   limited, non-exclusive, non-transferable, fully paid up, worldwide licenses,\n   without the right to grant or authorize sublicenses (except as set forth in\n   Section 2.3):\n\n      (a) Internal Use.  A license to copy, compile, install, and use the\n      Timescale Software and Derivative Works solely for Your own internal\n      business purposes in a manner that does not expose or give access to,\n      directly or indirectly (e.g., via a wrapper), the Timescale Data\n      Definition Interfaces or the Timescale Data Manipulation Interfaces to\n      any person or entity other than You or Your employees and Contractors\n      working on Your behalf.\n\n      (b) Value Added Products or Services.  A license (i) to copy, compile,\n      install, and use the Timescale Software, Derivative Works, or parts\n      thereof to develop and maintain Your Value Added Products or Services,\n      (ii) to utilize (in the case of services) copies of the Timescale\n      Software, Derivative Works, or parts thereof solely as incorporated\n      into or utilized with Your Value Added Products or Services, and\n      (iii) to distribute (in the case of products that are distributed to\n      Your customers) copies of the Timescale Software binaries or of\n      Derivative Works solely in binary form, and both solely as incorporated\n      into or utilized with Your Value Added Products or Services; provided\n      that (1) You notify Your customers that use of such Timescale Software\n      or Derivative Works is subject to this TSL Agreement and You provide to\n      each such customer a copy of the most current version of this TSL\n      Agreement or a URL from which the most current version of this TSL\n      Agreement may be obtained, and (2) the customer is prohibited, either\n      contractually or technically, from defining, redefining, or modifying\n      the database schema or other structural aspects of database objects,\n      such as through use of the Timescale Data Definition Interfaces, in a\n      Timescale Database utilized by such Value Added Products or Services.\n\n      (c) Distribution of Source Code or Binaries in Standalone Form.  Subject\n      to the prohibitions in Section 2.2 below, a license to copy and\n      distribute the Timescale Software source code and binaries solely in\n      unmodified standalone form and subject to the terms and conditions of\n      the most current version of this TSL Agreement.\n\n      (d) Derivative Works.  A license (i) to prepare, compile, and test\n      Derivative Works of the TSL Licensed Software; (ii) to use Derivative\n      Works for Internal Use solely as expressly permitted in Section 2.1(a);\n      (iii) to utilize Derivative Works with Your Value Added Products or\n      Services solely as expressly permitted in Section 2.1(b); (iv) to\n      distribute Derivative Works in binary form with Your Value Added\n      Products or Services solely as expressly permitted in Section 2.1(b);\n      and (v) to distribute Derivative Works back to Timescale under\n      Timescale's Contributor Agreement for potential incorporation into\n      Timescale's maintained code base at its sole discretion.\n\n   2.2 Prohibitions.  Notwithstanding any other provision in this TSL\n   Agreement, You are prohibited from (i) using any TSL Licensed Software to\n   provide time-sharing services or database-as-a-service services, or to\n   provide any form of software-as-a-service or service offering in which the\n   TSL Licensed Software is offered or made available to third parties to\n   provide time-series database functions or operations, other than as part of\n   Your Value Added Products or Services, or (ii) copying or distributing any\n   TSL Licensed Software for use in any of the foregoing ways.  In addition,\n   You agree not to, except as expressly permitted in Section 2.1(d), prepare\n   Derivative Works of any TSL Licensed Software or, except as expressly\n   permitted herein, transfer, sell, rent, lease, sublicense, loan, or\n   otherwise transfer or make available any TSL Licensed Software, whether in\n   source code or binary executable form.\n\n   2.3 Affiliates and Contractors.  You may permit Your Contractors and\n   Affiliates to exercise the licenses set forth in Section 2.1, provided that\n   such exercise by Contractors must be solely for your benefit and/or the\n   benefit of Your Affiliates, and You shall be responsible for all acts and\n   omissions of such Contractors and Affiliates in connection with such\n   exercise of the licenses, including but not limited to breach of any terms\n   of this TSL Agreement.\n\n   2.4 Reservation of Rights.  Except as expressly set forth in Section 2.1, no\n   other license or rights to the Timescale Software are granted to You under\n   this TSL Agreement, whether by implication, estoppel, or otherwise.\n\n3. DEFINITIONS\n\n   In addition to other terms defined elsewhere in this TSL Agreement, the\n   terms below have the following meanings:\n\n   3.1 \"Affiliate\" means, if You are a legal entity, any legal entity that\n   controls, is controlled by, or which is under common control with, You,\n   where \"control\" means ownership of at least fifty percent (50%) of the\n   outstanding voting shares of the legal entity, or the contractual right to\n   establish policy for, and manage the operations of, the legal entity.\n\n   3.2 \"Contractor\" means a person or entity engaged as a consultant or\n   contractor to perform work on Your behalf, but only to the extent such\n   person or entity is performing such work on Your behalf.\n\n   3.3 \"Derivative Work\" means any modification or enhancement made by You to\n   the TSL Licensed Software, whether in source code, binary executable,\n   intermediate, or other form.\n\n   3.4 \"Timescale Database\" means a time-series database that is created\n   and/or used by the Timescale Software.\n\n   3.5 \"Timescale Data Definition Interfaces\" means SQL commands and other\n   interfaces of the Timescale Software that can be used to define or modify\n   the database schema and other structural aspects of database objects in a\n   Timescale Database, including Data Definition Language (DDL) commands such\n   as CREATE, DROP, ALTER, TRUNCATE, COMMENT, and RENAME.\n\n   3.6 \"Timescale Data Manipulation Interfaces\" means SQL commands and\n   analytical function, procedural, and other types of application programming\n   interfaces or commands, that allow the use, manipulation, and control of\n   data present in a Timescale Database, including Data Manipulation Language\n   (DML) commands such as SELECT, INSERT, UPDATE, and DELETE, Data Control\n   Language (DCL) commands such as GRANT and REVOKE, and Transaction Control\n   Language (TCL) commands such as COMMIT, ROLLBACK, SAVEPOINT, and SET\n   TRANSACTION.\n\n   3.7 \"Timescale Open Source Software\" means those portions of the Timescale\n   Software that Timescale makes publicly available for distribution from time\n   to time as open source software under the terms of the Apache License\n   Version 2.0 or, in some limited instances, under other open source licenses\n   (such as the PostgreSQL license) as identified in the applicable source\n   code files and/or accompanying notices.\n\n   3.8 \"Timescale Software\" means, collectively, all time-series database\n   software and related software made publicly available by Timescale for\n   distribution from time to time, in both source code and binary executable\n   form, which includes the Timescale Open Source Software and the TSL\n   Licensed Software.\n\n   3.9 \"TSL Licensed Software\" means those parts of the Timescale Software\n   other than the Timescale Open Source Software.\n\n   3.10 \"Value Added Products or Services\" means products or services developed\n   by or for You that utilize (for example, as a back-end function or part of a\n   software stack) all or parts of the Timescale Software to provide\n   time-series database storage and operations in support of larger value-added\n   products or services (for example, an IoT platform or vertical-specific\n   application) with respect to which all of the following are true:\n\n      (i) such value-added products or services are not primarily database\n      storage or operations products or services;\n\n      (ii) such value-added products or services add substantial value of a\n      different nature to the time-series database storage and operations\n      afforded by the Timescale Software and are the key functions upon which\n      such products or services are offered and marketed; and\n\n      (iii) users of such Value Added Products or Services are prohibited,\n      either contractually or technically, from defining, redefining, or\n      modifying the database schema or other structural aspects of database\n      objects, such as through use of the Timescale Data Definition Interfaces,\n      in a Timescale Database utilized by such Value Added Products or\n      Services.\n\n4. TERMINATION\n\n   This TSL Agreement will automatically terminate, whether or not You receive\n   notice of such termination from Timescale, in the event You breach any of\n   its terms or conditions.  In accordance with Section 6 below, Timescale\n   shall have no liability for any damage, loss, or expense of any kind,\n   whether consequential, indirect, or direct, suffered or incurred by You\n   arising from or incident to the termination of this TSL Agreement, whether\n   or not Timescale has been advised or is aware of any such potential damage,\n   loss, or expense.\n\n5. DISCLAIMER OF WARRANTIES\n\n   TO THE MAXIMUM EXTENT PERMITTED UNDER APPLICABLE LAW, ALL TIMESCALE SOFTWARE\n   PROVIDED UNDER THIS TSL AGREEMENT, INCLUDING ALL PORTIONS OF THE TIMESCALE\n   SOFTWARE SUPPLIED ON A TRIAL BASIS, ARE PROVIDED \"AS IS\" WITHOUT WARRANTY OF\n   ANY KIND AND TIMESCALE DISCLAIMS ALL SUCH WARRANTIES, WHETHER EXPRESS,\n   STATUTORY, OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF\n   MERCHANTABILITY, TITLE, FITNESS FOR A PARTICULAR PURPOSE, OR\n   NON-INFRINGEMENT, AND ANY IMPLIED WARRANTIES ARISING FROM USAGE OF TRADE,\n   COURSE OF DEALING, OR COURSE OF PERFORMANCE.  WITHOUT LIMITING THE\n   FOREGOING, TIMESCALE MAKES NO WARRANTY OR REPRESENTATION AS TO THE\n   RELIABILITY, TIMELINESS, QUALITY, SUITABILITY, PROFITABILITY, SUPPORT,\n   PERFORMANCE, LOSS OF USE OR LOSS OF DATA, AVAILABILITY, OR ACCURACY OF THE\n   TIMESCALE SOFTWARE.  YOU ACKNOWLEDGE THAT CHANGES MADE BY TIMESCALE TO THE\n   TIMESCALE SOFTWARE MAY DISRUPT INTEROPERATION WITH YOUR VALUE ADDED PRODUCTS\n   OR SERVICES.  TIMESCALE AND ITS LICENSORS DO NOT WARRANT THAT THE TIMESCALE\n   SOFTWARE, OR ANY PORTION THEREOF, IS ERROR FREE OR WILL OPERATE WITHOUT\n   INTERRUPTION, OR THAT ANY VALUE ADDED PRODUCT OR SERVICE INTEROPERATING WITH\n   THE TIMESCALE SOFTWARE WILL NOT EXPERIENCE LOSS OF USE OR LOSS OF DATA.  YOU\n   ACKNOWLEDGE THAT IN ENTERING INTO THIS TSL AGREEMENT, YOU HAVE NOT RELIED ON\n   ANY PROMISE, WARRANTY, OR REPRESENTATION NOT EXPRESSLY SET FORTH IN THIS\n   AGREEMENT.\n\n6. LIMITATION OF LIABILITY\n\n   TO THE MAXIMUM EXTENT PERMITTED UNDER APPLICABLE LAW, IN NO EVENT SHALL\n   TIMESCALE OR ITS LICENSORS BE LIABLE TO YOU OR ANY THIRD PARTY FOR ANY\n   DIRECT OR INDIRECT DAMAGES, INCLUDING BUT NOT LIMITED TO ANY LOSS OF PROFITS\n   OR REVENUE, LOSS OF USE, BUSINESS INTERRUPTION, LOSS OF DATA, COST OF COVER\n   OR SUBSTITUTE GOODS OR SERVICES, OR FOR ANY SPECIAL, INCIDENTAL,\n   CONSEQUENTIAL, PUNITIVE, OR EXEMPLARY DAMAGES OF ANY KIND, HOWEVER CAUSED,\n   RELATED TO, OR ARISING OUT OF THIS TSL AGREEMENT, ITS TERMINATION OR THE\n   PERFORMANCE OR FAILURE TO PERFORM THIS TSL AGREEMENT, OR THE USE OR\n   INABILITY TO USE THE TIMESCALE SOFTWARE, WHETHER ALLEGED AS A BREACH OF\n   CONTRACT, BREACH OF WARRANTY, TORTIOUS CONDUCT, INCLUDING NEGLIGENCE, OR ANY\n   OTHER LEGAL THEORY, EVEN IF TIMESCALE HAS BEEN ADVISED OR IS AWARE OF THE\n   POSSIBILITY OF SUCH DAMAGES.\n\n7. GENERAL\n\n   7.1 Complete Agreement.  This TSL Agreement completely and exclusively\n   states the entire agreement of the parties regarding the subject matter\n   hereof and supersedes all prior proposals, agreements, or other\n   communications between the parties, oral or written, regarding such subject\n   matter.\n\n   7.2 Modification.  This TSL Agreement may be modified by Timescale from time\n   to time, and any such modifications will be effective upon the \"Posted Date\"\n   set forth at the top of the modified agreement. The modified agreement shall\n   govern any new version of the TSL Licensed Software (and all its constituent\n   source code and binaries) that is officially released as a complete version\n   release by Timescale on or after such Posted Date. Except as set forth in\n   this Section 7.2, this TSL Agreement may not be amended except by a writing\n   executed by both parties.\n\n   7.3 Governing Law.  This TSL Agreement shall be governed by and construed\n   solely under the laws of the State of New York, without application of any\n   choice of law rules or principles that would lead to the applicability of\n   the law of any other jurisdiction.  None of the provisions of either the\n   United Nations Convention on Contracts for the International Sale of Goods\n   or the Uniform Computer Information Transactions Act shall apply.\n\n   7.4 Unenforceability.  If any provision of this TSL Agreement is held\n   unenforceable, the remaining provisions of this TSL Agreement shall remain\n   in effect and the unenforceable provision shall be replaced by an\n   enforceable provision that best reflects the original intent of the parties.\n\n   7.5 Injunctive Relief.  You acknowledge that a breach or threatened breach\n   of any provision of this TSL Agreement will cause irreparable harm to\n   Timescale for which damages at law will not provide adequate relief, and\n   Timescale shall therefore be entitled to injunctive relief against such\n   breach or threatened breach without being required to post a bond.\n\n   7.6 Assignment.  You may not assign this TSL Agreement, including by\n   operation of law in connection with a merger or acquisition or otherwise,\n   in whole or in part, without the prior written consent of Timescale, which\n   Timescale may grant or withhold in its sole and absolute discretion.  Any\n   assignment in violation of the preceding sentence is void.\n\n   7.7 Independent Contractors.  The parties to this TSL Agreement are\n   independent contractors and this TSL Agreement does not establish any\n   relationship of partnership, joint venture, employment, franchise, or agency\n   between the parties.\n\n   7.8 U.S. Government Rights.  The Timescale Software and related\n   documentation are \"Commercial Items\", as that term is defined at 48\n   C.F.R. §2.101, consisting of \"Commercial Computer Software\" and \"Commercial\n   Computer Software Documentation,\" as such terms are used in 48\n   C.F.R. §12.212 or 48 C.F.R. §227.7202, as applicable, and\n   are being licensed to U.S. Government end users (a) only as\n   Commercial Items and (b) with only those rights as are granted to all other\n   end users pursuant to the terms and conditions of this TSL Agreement.\n\n"
  },
  {
    "path": "tsl/README.md",
    "content": "## TimescaleDB TSL Library ##\n\nThe TimescaleDB TSL library is licensed under the [Timescale License](LICENSE-TIMESCALE).\n\n- [Continuous Aggregates](src/continuous_aggs/README.md)\n- [Compression](src/compression/README.md)\n- [Query optimization for time series](src/nodes/README.md)\n\n\n\n"
  },
  {
    "path": "tsl/src/CMakeLists.txt",
    "content": "set(SOURCES\n    chunk_api.c\n    chunk.c\n    chunk_merge.c\n    chunk_split.c\n    chunkwise_agg.c\n    init.c\n    planner.c\n    process_utility.c\n    reorder.c)\n\n# Add test source code in Debug builds\nif(CMAKE_BUILD_TYPE MATCHES Debug)\n  set(TS_DEBUG 1)\n  set(DEBUG 1)\nendif(CMAKE_BUILD_TYPE MATCHES Debug)\n\nset(TSL_LIBRARY_NAME ${PROJECT_NAME}-tsl)\n\ninclude(build-defs.cmake)\n\nif(CMAKE_BUILD_TYPE MATCHES Debug OR COMPRESSION_FUZZING)\n  add_library(${TSL_LIBRARY_NAME} MODULE\n              ${SOURCES} $<TARGET_OBJECTS:${TSL_TESTS_LIB_NAME}>)\nelse()\n  add_library(${TSL_LIBRARY_NAME} MODULE ${SOURCES})\nendif()\n\nset_target_properties(\n  ${TSL_LIBRARY_NAME}\n  PROPERTIES OUTPUT_NAME ${TSL_LIBRARY_NAME}-${PROJECT_VERSION_MOD} PREFIX \"\")\n\ntarget_include_directories(${TSL_LIBRARY_NAME} PRIVATE ${PG_INCLUDEDIR})\n\ntarget_compile_definitions(${TSL_LIBRARY_NAME} PUBLIC TS_TSL)\ntarget_compile_definitions(${TSL_LIBRARY_NAME} PUBLIC TS_SUBMODULE)\n\nif(WIN32)\n  target_link_libraries(${TSL_LIBRARY_NAME} ${PG_LIBDIR}/libpq.lib)\nelse()\n  target_link_libraries(${TSL_LIBRARY_NAME} pq)\nendif()\n\ninstall(TARGETS ${TSL_LIBRARY_NAME} DESTINATION ${PG_PKGLIBDIR})\n\n# if (WIN32) target_link_libraries(${PROJECT_NAME}\n# ${PROJECT_NAME}-${PROJECT_VERSION_MOD}.lib) endif(WIN32)\n\n# We use the UMASH library for hashing in vectorized grouping. If it was not\n# explicitly disabled already, detect if we can compile it on this platform.\nif((NOT DEFINED USE_UMASH) OR USE_UMASH)\n  # Check whether we can enable the pclmul instruction required for the UMASH\n  # hashing on amd64. Shouldn't be done if the user has manually specified the\n  # target architecture, no idea how to detect this, but at least we shouldn't\n  # do this when cross-compiling.\n  if(NOT CMAKE_CROSSCOMPILING)\n    check_c_compiler_flag(-mpclmul CC_PCLMUL)\n    if(CC_PCLMUL)\n      add_compile_options(-mpclmul)\n    endif()\n  endif()\n\n  # The C compiler flags that we add using add_compile_options() are not\n  # automatically used by check_c_source_compiles() because it works in a\n  # separate project, so we have to add them manually.\n  get_directory_property(DIR_COMPILE_OPTIONS_LIST COMPILE_OPTIONS)\n  list(JOIN DIR_COMPILE_OPTIONS_LIST \" \" DIR_COMPILE_OPTIONS)\n  set(CMAKE_REQUIRED_FLAGS\n      \"${CMAKE_REQUIRED_FLAGS} ${CMAKE_C_FLAGS} ${DIR_COMPILE_OPTIONS} -Werror=implicit-function-declaration\"\n  )\n\n  check_c_source_compiles(\n    \"\n#if defined(__PCLMUL__)\n#include <stdint.h>\n#include <immintrin.h>\n/*\n * For some reason, this doesn't compile on our i386 CI, but I also can't detect\n * it using the standard condition of defined(__x86_64__) && !defined(__ILP32__),\n * as described at https://wiki.debian.org/X32Port .\n */\nint main() { (void) _mm_cvtsi64_si128((uint64_t) 0); return 0; }\n#elif defined(__ARM_FEATURE_CRYPTO)\n/* OK */\nint main() { return 0; }\n#else\n#error Unsupported platform for UMASH\n#endif\n\"\n    UMASH_SUPPORTED)\n  unset(CMAKE_REQUIRED_FLAGS)\nelse()\n  set(UMASH_SUPPORTED OFF)\nendif()\n\noption(USE_UMASH\n       \"Use the UMASH hash for string and multi-column vectorized grouping\"\n       ${UMASH_SUPPORTED})\n\nif(USE_UMASH)\n  if(NOT UMASH_SUPPORTED)\n    message(\n      FATAL_ERROR\n        \"UMASH use is requested, but it is not supported in the current configuration\"\n    )\n  endif()\n  add_compile_definitions(TS_USE_UMASH)\nendif()\n\nadd_subdirectory(bgw_policy)\nadd_subdirectory(compression)\nadd_subdirectory(continuous_aggs)\nadd_subdirectory(import)\nadd_subdirectory(nodes)\n"
  },
  {
    "path": "tsl/src/README.module.md",
    "content": "# Submodule Licensing and Initialization #\n\n## Loading and Activation ##\n\nWe link module loading and activation to the license GUC itself.\nWe have a single GUC, the license, and load submodules based on\nwhat capabilities the license enables, i.e., an `apache` license-key does\nnot load this module, while `timescale` key does. This\nensures that the loader \"does the right thing\" with respect to the license, and\na user cannot accidentally activate features they aren't licensed to use.\n\nThe actual loading and activation is done through `check` and `assign` hooks on\nthe license GUC. On `check` we validate the license type \nand on `assign` we set the capabilities-struct in this module,\nif needed. The `check` and `assign` functions can be found in\n[`license_guc.c/h`](/src/license_guc.c) in the Apache-Licensed src.\n\n### Cross License Functions ###\n\nTo enable binaries which only contain Apache-Licensed code,\nwe dynamically link in Timescale-Licensed code on license activation,\nand handle all function calls into the module via function pointers.\n\nThe registry in `ts_cm_functions` of type `CrossModuleFunctions` (declared in\n[`cross_module_fn.h`](/src/cross_module_fn.h) and defined in\n[`cross_module_fn.c`](/src/cross_module_fn.c)) stores all of the cross-module\nfunctions.\n\nTo add a new cross-module function you must:\n\n  - Add a struct member `CrossModuleFunctions.<function name>`.\n  - Add default function to `ts_cm_functions_default` that will be called from the Apache version, usually this function should just call `error_no_default_fn`. **NOTE** Due to function-pointer casting rules, the default function must have the exact same signature as the function pointer; you may _not_ cast another function pointer of another type.\n  - Add the overriding function to `tsl_cm_functions`in `init.c` in this module.\n\nTo call a cross-module functions use `ts_cm_functions-><function name>(args)`.\n"
  },
  {
    "path": "tsl/src/bgw_policy/CMakeLists.txt",
    "content": "set(SOURCES\n    ${CMAKE_CURRENT_SOURCE_DIR}/compression_api.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/continuous_aggregate_api.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/process_hyper_inval_api.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/job.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/job_api.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/reorder_api.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/policy_config.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/retention_api.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/policy_utils.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/policies_v2.c)\ntarget_sources(${TSL_LIBRARY_NAME} PRIVATE ${SOURCES})\ntarget_include_directories(${TSL_LIBRARY_NAME} PRIVATE ${CMAKE_SOURCE_DIR})\n"
  },
  {
    "path": "tsl/src/bgw_policy/compression_api.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <access/xact.h>\n#include <fmgr.h>\n#include <miscadmin.h>\n#include <utils/builtins.h>\n\n#include \"compression_api.h\"\n\n#include \"bgw/job.h\"\n#include \"bgw/job_stat.h\"\n#include \"bgw/timer.h\"\n#include \"bgw_policy/continuous_aggregate_api.h\"\n#include \"bgw_policy/job.h\"\n#include \"bgw_policy/job_api.h\"\n#include \"bgw_policy/policies_v2.h\"\n#include \"bgw_policy/policy_config.h\"\n#include \"errors.h\"\n#include \"guc.h\"\n#include \"hypertable.h\"\n#include \"hypertable_cache.h\"\n#include \"jsonb_utils.h\"\n#include \"policy_utils.h\"\n#include \"utils.h\"\n#include <utils/elog.h>\n\n/* Default max runtime is unlimited for compress chunks */\n#define DEFAULT_MAX_RUNTIME                                                                        \\\n\tDatumGetIntervalP(DirectFunctionCall3(interval_in, CStringGetDatum(\"0\"), InvalidOid, -1))\n\n/* Default retry period for reorder_jobs is currently 1 hour */\n#define DEFAULT_RETRY_PERIOD                                                                       \\\n\tDatumGetIntervalP(DirectFunctionCall3(interval_in, CStringGetDatum(\"1 hour\"), InvalidOid, -1))\n\n/* Default max schedule period for the compression policy is 12 hours. The actual schedule period\n * will be chunk_interval/2 if the chunk_interval is < 12 hours. */\n#define DEFAULT_MAX_SCHEDULE_PERIOD (int64)(12 * 3600 * 1000 * (int64) 1000)\n\nstatic Hypertable *validate_compress_chunks_hypertable(Cache *hcache, Oid user_htoid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   bool *is_cagg);\n\nint32\npolicy_compression_get_maxchunks_per_job(const Jsonb *config)\n{\n\tbool found;\n\tint32 maxchunks =\n\t\tts_jsonb_get_int32_field(config, POL_COMPRESSION_CONF_KEY_MAXCHUNKS_TO_COMPRESS, &found);\n\treturn (found && maxchunks > 0) ? maxchunks : 0;\n}\n\nint64\npolicy_recompression_get_recompress_after_int(const Jsonb *config)\n{\n\tbool found;\n\tint64 compress_after =\n\t\tts_jsonb_get_int64_field(config, POL_RECOMPRESSION_CONF_KEY_RECOMPRESS_AFTER, &found);\n\n\tif (!found)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t errmsg(\"could not find %s in config for job\",\n\t\t\t\t\t\tPOL_RECOMPRESSION_CONF_KEY_RECOMPRESS_AFTER)));\n\n\treturn compress_after;\n}\n\nInterval *\npolicy_recompression_get_recompress_after_interval(const Jsonb *config)\n{\n\tInterval *interval =\n\t\tts_jsonb_get_interval_field(config, POL_RECOMPRESSION_CONF_KEY_RECOMPRESS_AFTER);\n\n\tif (interval == NULL)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t errmsg(\"could not find %s in config for job\",\n\t\t\t\t\t\tPOL_RECOMPRESSION_CONF_KEY_RECOMPRESS_AFTER)));\n\n\treturn interval;\n}\n\nDatum\npolicy_recompression_proc(PG_FUNCTION_ARGS)\n{\n\tif (PG_NARGS() != 2 || PG_ARGISNULL(0) || PG_ARGISNULL(1))\n\t\tPG_RETURN_VOID();\n\n\tts_feature_flag_check(FEATURE_POLICY);\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\tpolicy_recompression_execute(PG_GETARG_INT32(0), PG_GETARG_JSONB_P(1));\n\n\tPG_RETURN_VOID();\n}\n\nstatic void\nvalidate_compress_after_type(const Dimension *dim, Oid partitioning_type, Oid compress_after_type)\n{\n\tOid expected_type = InvalidOid;\n\tif (IS_INTEGER_TYPE(partitioning_type))\n\t{\n\t\tOid now_func = ts_get_integer_now_func(dim, false);\n\n\t\tif (!IS_INTEGER_TYPE(compress_after_type) && OidIsValid(now_func))\n\t\t\texpected_type = partitioning_type;\n\t}\n\telse if (compress_after_type != INTERVALOID)\n\t{\n\t\texpected_type = INTERVALOID;\n\t}\n\tif (OidIsValid(expected_type))\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"unsupported compress_after argument type, expected type : %s\",\n\t\t\t\t\t\tformat_type_be(expected_type))));\n\t}\n}\n\nDatum\npolicy_compression_check(PG_FUNCTION_ARGS)\n{\n\tPolicyCompressionData policy_data;\n\n\tif (PG_ARGISNULL(0))\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg(\"config must not be NULL\")));\n\t}\n\n\tpolicy_compression_read_and_validate_config(PG_GETARG_JSONB_P(0), &policy_data);\n\tts_cache_release(&policy_data.hcache);\n\n\tPG_RETURN_VOID();\n}\n\n/* compression policies are added to hypertables or continuous aggregates */\nDatum\npolicy_compression_add_internal(Oid user_rel_oid, Datum compress_after_datum,\n\t\t\t\t\t\t\t\tOid compress_after_type, Interval *created_before,\n\t\t\t\t\t\t\t\tInterval *default_schedule_interval,\n\t\t\t\t\t\t\t\tbool user_defined_schedule_interval, bool if_not_exists,\n\t\t\t\t\t\t\t\tbool fixed_schedule, TimestampTz initial_start,\n\t\t\t\t\t\t\t\tconst char *timezone)\n{\n\tNameData application_name;\n\tNameData proc_name, proc_schema, check_schema, check_name, owner;\n\tint32 job_id;\n\tHypertable *hypertable;\n\tCache *hcache;\n\tconst Dimension *dim;\n\tOid owner_id;\n\tbool is_cagg = false;\n\n\thcache = ts_hypertable_cache_pin();\n\thypertable = validate_compress_chunks_hypertable(hcache, user_rel_oid, &is_cagg);\n\n\t/* creation time usage not supported with caggs yet */\n\tif (is_cagg && created_before != NULL)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot use \\\"compress_created_before\\\" with continuous aggregate \\\"%s\\\" \",\n\t\t\t\t\t\tget_rel_name(user_rel_oid))));\n\n\towner_id = ts_hypertable_permissions_check(user_rel_oid, GetUserId());\n\tts_bgw_job_validate_job_owner(owner_id);\n\n\t/* Make sure that an existing policy doesn't exist on this hypertable */\n\tList *jobs = ts_bgw_job_find_by_proc_and_hypertable_id(POLICY_COMPRESSION_PROC_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   FUNCTIONS_SCHEMA_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   hypertable->fd.id);\n\n\tdim = hyperspace_get_open_dimension(hypertable->space, 0);\n\tOid partitioning_type = ts_dimension_get_partition_type(dim);\n\n\tif (jobs != NIL)\n\t{\n\t\tbool is_equal = false;\n\n\t\tif (!if_not_exists)\n\t\t{\n\t\t\tts_cache_release(&hcache);\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_DUPLICATE_OBJECT),\n\t\t\t\t\t errmsg(\"columnstore policy already exists for hypertable or continuous \"\n\t\t\t\t\t\t\t\"aggregate \\\"%s\\\"\",\n\t\t\t\t\t\t\tget_rel_name(user_rel_oid)),\n\t\t\t\t\t errhint(\"Set option \\\"if_not_exists\\\" to true to avoid error.\")));\n\t\t}\n\t\tAssert(list_length(jobs) == 1);\n\t\tBgwJob *existing = linitial(jobs);\n\n\t\tif (OidIsValid(compress_after_type))\n\t\t\tis_equal =\n\t\t\t\tpolicy_config_check_hypertable_lag_equality(existing->fd.config,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tPOL_COMPRESSION_CONF_KEY_COMPRESS_AFTER,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tpartitioning_type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcompress_after_type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcompress_after_datum,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfalse /* isnull */);\n\t\telse\n\t\t{\n\t\t\tAssert(created_before != NULL);\n\t\t\tis_equal = policy_config_check_hypertable_lag_equality(\n\t\t\t\texisting->fd.config,\n\t\t\t\tPOL_COMPRESSION_CONF_KEY_COMPRESS_CREATED_BEFORE,\n\t\t\t\tpartitioning_type,\n\t\t\t\tINTERVALOID,\n\t\t\t\tIntervalPGetDatum(created_before),\n\t\t\t\tfalse /* isnull */);\n\t\t}\n\n\t\tif (is_equal)\n\t\t{\n\t\t\t/* If all arguments are the same, do nothing */\n\t\t\tts_cache_release(&hcache);\n\t\t\tereport(NOTICE,\n\t\t\t\t\t(errmsg(\"columnstore policy already exists for hypertable \\\"%s\\\", skipping\",\n\t\t\t\t\t\t\tget_rel_name(user_rel_oid))));\n\t\t\tPG_RETURN_INT32(-1);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tts_cache_release(&hcache);\n\t\t\tereport(WARNING,\n\t\t\t\t\t(errmsg(\"columnstore policy already exists for hypertable \\\"%s\\\"\",\n\t\t\t\t\t\t\tget_rel_name(user_rel_oid)),\n\t\t\t\t\t errdetail(\"A policy already exists with different arguments.\"),\n\t\t\t\t\t errhint(\"Remove the existing policy before adding a new one.\")));\n\t\t\tPG_RETURN_INT32(-1);\n\t\t}\n\t}\n\n\tif (created_before)\n\t{\n\t\tAssert(!OidIsValid(compress_after_type));\n\t\tcompress_after_type = INTERVALOID;\n\t}\n\n\tif (!is_cagg && IS_INTEGER_TYPE(partitioning_type) && !IS_INTEGER_TYPE(compress_after_type) &&\n\t\tcreated_before == NULL)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid value for parameter %s\", POL_COMPRESSION_CONF_KEY_COMPRESS_AFTER),\n\t\t\t\t errhint(\"Integer duration in \\\"compress_after\\\" or interval time duration\"\n\t\t\t\t\t\t \" in \\\"compress_created_before\\\" is required for hypertables with integer \"\n\t\t\t\t\t\t \"time dimension.\")));\n\n\tif (dim && IS_TIMESTAMP_TYPE(ts_dimension_get_partition_type(dim)) &&\n\t\t!user_defined_schedule_interval)\n\t{\n\t\tint64 hypertable_schedule_interval = dim->fd.interval_length / 2;\n\n\t\t/* On hypertables with a small chunk_time_interval, schedule the compression job more often\n\t\t * than DEFAULT_MAX_SCHEDULE_PERIOD */\n\t\tif (DEFAULT_MAX_SCHEDULE_PERIOD > hypertable_schedule_interval)\n\t\t{\n\t\t\tdefault_schedule_interval = DatumGetIntervalP(\n\t\t\t\tts_internal_to_interval_value(hypertable_schedule_interval, INTERVALOID));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tdefault_schedule_interval = DatumGetIntervalP(\n\t\t\t\tts_internal_to_interval_value(DEFAULT_MAX_SCHEDULE_PERIOD, INTERVALOID));\n\t\t}\n\t}\n\n\t/* insert a new job into jobs table */\n\tnamestrcpy(&application_name, \"Columnstore Policy\");\n\tnamestrcpy(&proc_name, POLICY_COMPRESSION_PROC_NAME);\n\tnamestrcpy(&proc_schema, FUNCTIONS_SCHEMA_NAME);\n\tnamestrcpy(&check_name, POLICY_COMPRESSION_CHECK_NAME);\n\tnamestrcpy(&check_schema, FUNCTIONS_SCHEMA_NAME);\n\tnamestrcpy(&owner, GetUserNameFromId(owner_id, false));\n\n\tJsonbParseState *parse_state = NULL;\n\n\tpushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);\n\tts_jsonb_add_int32(parse_state, POLICY_CONFIG_KEY_HYPERTABLE_ID, hypertable->fd.id);\n\tvalidate_compress_after_type(dim, partitioning_type, compress_after_type);\n\n\tswitch (compress_after_type)\n\t{\n\t\tcase INTERVALOID:\n\t\t\tif (created_before)\n\t\t\t\tts_jsonb_add_interval(parse_state,\n\t\t\t\t\t\t\t\t\t  POL_COMPRESSION_CONF_KEY_COMPRESS_CREATED_BEFORE,\n\t\t\t\t\t\t\t\t\t  created_before);\n\t\t\telse\n\t\t\t\tts_jsonb_add_interval(parse_state,\n\t\t\t\t\t\t\t\t\t  POL_COMPRESSION_CONF_KEY_COMPRESS_AFTER,\n\t\t\t\t\t\t\t\t\t  DatumGetIntervalP(compress_after_datum));\n\t\t\tbreak;\n\t\tcase INT2OID:\n\t\t\tts_jsonb_add_int64(parse_state,\n\t\t\t\t\t\t\t   POL_COMPRESSION_CONF_KEY_COMPRESS_AFTER,\n\t\t\t\t\t\t\t   DatumGetInt16(compress_after_datum));\n\t\t\tbreak;\n\t\tcase INT4OID:\n\t\t\tts_jsonb_add_int64(parse_state,\n\t\t\t\t\t\t\t   POL_COMPRESSION_CONF_KEY_COMPRESS_AFTER,\n\t\t\t\t\t\t\t   DatumGetInt32(compress_after_datum));\n\t\t\tbreak;\n\t\tcase INT8OID:\n\t\t\tts_jsonb_add_int64(parse_state,\n\t\t\t\t\t\t\t   POL_COMPRESSION_CONF_KEY_COMPRESS_AFTER,\n\t\t\t\t\t\t\t   DatumGetInt64(compress_after_datum));\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"unsupported datatype for %s: %s\",\n\t\t\t\t\t\t\tPOL_COMPRESSION_CONF_KEY_COMPRESS_AFTER,\n\t\t\t\t\t\t\tformat_type_be(compress_after_type))));\n\t}\n\n\tJsonbValue *result = pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);\n\tJsonb *config = JsonbValueToJsonb(result);\n\n\tjob_id = ts_bgw_job_insert_relation(&application_name,\n\t\t\t\t\t\t\t\t\t\tdefault_schedule_interval,\n\t\t\t\t\t\t\t\t\t\tDEFAULT_MAX_RUNTIME,\n\t\t\t\t\t\t\t\t\t\tJOB_RETRY_UNLIMITED,\n\t\t\t\t\t\t\t\t\t\tDEFAULT_RETRY_PERIOD,\n\t\t\t\t\t\t\t\t\t\t&proc_schema,\n\t\t\t\t\t\t\t\t\t\t&proc_name,\n\t\t\t\t\t\t\t\t\t\t&check_schema,\n\t\t\t\t\t\t\t\t\t\t&check_name,\n\t\t\t\t\t\t\t\t\t\towner_id,\n\t\t\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\t\t\tfixed_schedule,\n\t\t\t\t\t\t\t\t\t\thypertable->fd.id,\n\t\t\t\t\t\t\t\t\t\tconfig,\n\t\t\t\t\t\t\t\t\t\tinitial_start,\n\t\t\t\t\t\t\t\t\t\ttimezone);\n\n\tif (!TIMESTAMP_NOT_FINITE(initial_start))\n\t{\n\t\tts_bgw_job_stat_upsert_next_start(job_id, initial_start);\n\t}\n\n\tts_cache_release(&hcache);\n\tPG_RETURN_INT32(job_id);\n}\n\n/* compression policies are added to hypertables or continuous aggregates */\nDatum\npolicy_compression_add(PG_FUNCTION_ARGS)\n{\n\t/*\n\t * The function is not STRICT but we can't allow required args to be NULL\n\t * so we need to act like a strict function in those cases\n\t */\n\tif (PG_ARGISNULL(0) || PG_ARGISNULL(2))\n\t{\n\t\tts_feature_flag_check(FEATURE_POLICY);\n\t\tPG_RETURN_NULL();\n\t}\n\n\tOid user_rel_oid = PG_GETARG_OID(0);\n\tDatum compress_after_datum = PG_GETARG_DATUM(1);\n\tOid compress_after_type = PG_ARGISNULL(1) ? InvalidOid : get_fn_expr_argtype(fcinfo->flinfo, 1);\n\tbool if_not_exists = PG_GETARG_BOOL(2);\n\tbool user_defined_schedule_interval = !(PG_ARGISNULL(3));\n\tInterval *default_schedule_interval =\n\t\tPG_ARGISNULL(3) ? DEFAULT_COMPRESSION_SCHEDULE_INTERVAL : PG_GETARG_INTERVAL_P(3);\n\tTimestampTz initial_start = PG_ARGISNULL(4) ? DT_NOBEGIN : PG_GETARG_TIMESTAMPTZ(4);\n\t// if not providing initial_start, then we still get the old behavior\n\tbool fixed_schedule = !PG_ARGISNULL(4);\n\ttext *timezone = PG_ARGISNULL(5) ? NULL : PG_GETARG_TEXT_PP(5);\n\tchar *valid_timezone = NULL;\n\tInterval *created_before = PG_GETARG_INTERVAL_P(6);\n\n\tts_feature_flag_check(FEATURE_POLICY);\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\t/* compress_after and created_before cannot be specified [or omitted] together */\n\tif ((PG_ARGISNULL(1) && PG_ARGISNULL(6)) || (!PG_ARGISNULL(1) && !PG_ARGISNULL(6)))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\n\t\t\t\t\t \"need to specify one of \\\"compress_after\\\" or \\\"compress_created_before\\\"\")));\n\n\t/* if users pass in -infinity for initial_start, then use the current_timestamp instead */\n\tif (fixed_schedule)\n\t{\n\t\tts_bgw_job_validate_schedule_interval(default_schedule_interval);\n\t\tif (TIMESTAMP_NOT_FINITE(initial_start))\n\t\t\tinitial_start = ts_timer_get_current_timestamp();\n\t}\n\n\tif (timezone != NULL)\n\t\tvalid_timezone = ts_bgw_job_validate_timezone(PG_GETARG_DATUM(5));\n\n\tDatum retval;\n\tretval = policy_compression_add_internal(user_rel_oid,\n\t\t\t\t\t\t\t\t\t\t\t compress_after_datum,\n\t\t\t\t\t\t\t\t\t\t\t compress_after_type,\n\t\t\t\t\t\t\t\t\t\t\t created_before,\n\t\t\t\t\t\t\t\t\t\t\t default_schedule_interval,\n\t\t\t\t\t\t\t\t\t\t\t user_defined_schedule_interval,\n\t\t\t\t\t\t\t\t\t\t\t if_not_exists,\n\t\t\t\t\t\t\t\t\t\t\t fixed_schedule,\n\t\t\t\t\t\t\t\t\t\t\t initial_start,\n\t\t\t\t\t\t\t\t\t\t\t valid_timezone);\n\n\treturn retval;\n}\n\nbool\npolicy_compression_remove_internal(Oid user_rel_oid, bool if_exists)\n{\n\tHypertable *ht;\n\tCache *hcache;\n\n\tht = ts_hypertable_cache_get_cache_and_entry(user_rel_oid, CACHE_FLAG_MISSING_OK, &hcache);\n\tif (!ht)\n\t{\n\t\tconst char *view_name = get_rel_name(user_rel_oid);\n\n\t\tif (!view_name)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t\t errmsg(\"relation is not a hypertable or continuous aggregate\")));\n\t\telse\n\t\t{\n\t\t\tContinuousAgg *ca = ts_continuous_agg_find_by_relid(user_rel_oid);\n\n\t\t\tif (!ca)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t\t\t errmsg(\"relation \\\"%s\\\" is not a hypertable or continuous aggregate\",\n\t\t\t\t\t\t\t\tview_name)));\n\t\t\tht = ts_hypertable_get_by_id(ca->data.mat_hypertable_id);\n\t\t}\n\t}\n\n\tList *jobs = ts_bgw_job_find_by_proc_and_hypertable_id(POLICY_COMPRESSION_PROC_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   FUNCTIONS_SCHEMA_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   ht->fd.id);\n\n\tts_cache_release(&hcache);\n\n\tif (jobs == NIL)\n\t{\n\t\tif (!if_exists)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t\t errmsg(\"columnstore policy not found for hypertable \\\"%s\\\"\",\n\t\t\t\t\t\t\tget_rel_name(user_rel_oid))));\n\t\telse\n\t\t{\n\t\t\tereport(NOTICE,\n\t\t\t\t\t(errmsg(\"columnstore policy not found for hypertable \\\"%s\\\", skipping\",\n\t\t\t\t\t\t\tget_rel_name(user_rel_oid))));\n\t\t\tPG_RETURN_BOOL(false);\n\t\t}\n\t}\n\n\tts_hypertable_permissions_check(user_rel_oid, GetUserId());\n\n\tAssert(list_length(jobs) == 1);\n\tBgwJob *job = linitial(jobs);\n\n\tts_bgw_job_delete_by_id(job->fd.id);\n\n\tPG_RETURN_BOOL(true);\n}\n\n/* remove compression policy from ht or cagg */\nDatum\npolicy_compression_remove(PG_FUNCTION_ARGS)\n{\n\tOid user_rel_oid = PG_GETARG_OID(0);\n\tbool if_exists = PG_GETARG_BOOL(1);\n\n\tts_feature_flag_check(FEATURE_POLICY);\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\treturn policy_compression_remove_internal(user_rel_oid, if_exists);\n}\n\n/* compare cagg job config  with compression job config. If there is an overlap, then\n * throw an error. We do this since we cannot refresh compressed\n * regions. We do not want cont. aggregate jobs to fail\n */\n\n/* If this is a cagg, then mark it as a cagg */\nstatic Hypertable *\nvalidate_compress_chunks_hypertable(Cache *hcache, Oid user_htoid, bool *is_cagg)\n{\n\tContinuousAggHypertableStatus status;\n\tHypertable *ht = ts_hypertable_cache_get_entry(hcache, user_htoid, true /* missing_ok */);\n\t*is_cagg = false;\n\n\tif (ht != NULL)\n\t{\n\t\tif (!TS_HYPERTABLE_HAS_COMPRESSION_ENABLED(ht))\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"columnstore not enabled on hypertable \\\"%s\\\"\",\n\t\t\t\t\t\t\tget_rel_name(user_htoid)),\n\t\t\t\t\t errhint(\"Enable columnstore before adding a columnstore policy.\")));\n\t\t}\n\t\tstatus = ts_continuous_agg_hypertable_status(ht->fd.id);\n\t\tif ((status == HypertableIsMaterialization || status == HypertableIsMaterializationAndRaw))\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"cannot add compression policy to materialized hypertable \\\"%s\\\" \",\n\t\t\t\t\t\t\tget_rel_name(user_htoid)),\n\t\t\t\t\t errhint(\"Please add the policy to the corresponding continuous aggregate \"\n\t\t\t\t\t\t\t \"instead.\")));\n\t\t}\n\t}\n\telse\n\t{\n\t\t/*check if this is a cont aggregate view */\n\t\tint32 mat_id;\n\t\tbool found;\n\n\t\tContinuousAgg *cagg = ts_continuous_agg_find_by_relid(user_htoid);\n\n\t\tif (cagg == NULL)\n\t\t{\n\t\t\tts_cache_release(&hcache);\n\t\t\tconst char *relname = get_rel_name(user_htoid);\n\t\t\tif (relname)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_TS_HYPERTABLE_NOT_EXIST),\n\t\t\t\t\t\t errmsg(\"\\\"%s\\\" is not a hypertable or a continuous aggregate\", relname)));\n\t\t\telse\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t\t\t errmsg(\"object with id \\\"%u\\\" not found\", user_htoid)));\n\t\t}\n\t\t*is_cagg = true;\n\t\tmat_id = cagg->data.mat_hypertable_id;\n\t\tht = ts_hypertable_get_by_id(mat_id);\n\n\t\tfound = policy_refresh_cagg_exists(mat_id);\n\t\tif (!found)\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"continuous aggregate policy does not exist for \\\"%s\\\"\",\n\t\t\t\t\t\t\tget_rel_name(user_htoid)),\n\t\t\t\t\t errmsg(\"setup a refresh policy for \\\"%s\\\" before setting up a columnstore \"\n\t\t\t\t\t\t\t\"policy\",\n\t\t\t\t\t\t\tget_rel_name(user_htoid))));\n\t\t}\n\t\tif (!TS_HYPERTABLE_HAS_COMPRESSION_ENABLED(ht))\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"columnstore not enabled on continuous aggregate \\\"%s\\\"\",\n\t\t\t\t\t\t\tget_rel_name(user_htoid)),\n\t\t\t\t\t errhint(\"Enable columnstore before adding a columnstore policy.\")));\n\t\t}\n\t}\n\tAssert(ht != NULL);\n\treturn ht;\n}\n"
  },
  {
    "path": "tsl/src/bgw_policy/compression_api.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <utils/jsonb.h>\n#include <utils/timestamp.h>\n\n/* User-facing API functions */\nextern Datum policy_compression_add(PG_FUNCTION_ARGS);\nextern Datum policy_compression_remove(PG_FUNCTION_ARGS);\n\nextern Datum policy_recompression_proc(PG_FUNCTION_ARGS);\nextern Datum policy_compression_check(PG_FUNCTION_ARGS);\n\nint32 policy_compression_get_hypertable_id(const Jsonb *config);\nint32 policy_compression_get_maxchunks_per_job(const Jsonb *config);\nint64 policy_recompression_get_recompress_after_int(const Jsonb *config);\nInterval *policy_recompression_get_recompress_after_interval(const Jsonb *config);\n\nDatum policy_compression_add_internal(Oid user_rel_oid, Datum compress_after_datum,\n\t\t\t\t\t\t\t\t\t  Oid compress_after_type, Interval *created_before,\n\t\t\t\t\t\t\t\t\t  Interval *default_schedule_interval,\n\t\t\t\t\t\t\t\t\t  bool user_defined_schedule_interval, bool if_not_exists,\n\t\t\t\t\t\t\t\t\t  bool fixed_schedule, TimestampTz initial_start,\n\t\t\t\t\t\t\t\t\t  const char *timezone);\nbool policy_compression_remove_internal(Oid user_rel_oid, bool if_exists);\n"
  },
  {
    "path": "tsl/src/bgw_policy/continuous_aggregate_api.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <access/xact.h>\n#include <common/int128.h>\n#include <miscadmin.h>\n#include <parser/parse_coerce.h>\n#include <utils/acl.h>\n#include <utils/builtins.h>\n#include <utils/rangetypes.h>\n\n#include \"bgw/job.h\"\n#include \"bgw/job_stat.h\"\n#include \"bgw/timer.h\"\n#include \"bgw_policy/continuous_aggregate_api.h\"\n#include \"bgw_policy/job.h\"\n#include \"bgw_policy/job_api.h\"\n#include \"bgw_policy/policies_v2.h\"\n#include \"bgw_policy/policy_utils.h\"\n#include \"dimension.h\"\n#include \"guc.h\"\n#include \"jsonb_utils.h\"\n#include \"policy_config.h\"\n#include \"policy_utils.h\"\n#include \"time_utils.h\"\n#include \"ts_catalog/continuous_agg.h\"\n\n/* Default max runtime for a continuous aggregate jobs is unlimited for now */\n#define DEFAULT_MAX_RUNTIME                                                                        \\\n\tDatumGetIntervalP(DirectFunctionCall3(interval_in, CStringGetDatum(\"0\"), InvalidOid, -1))\n/* Default buckets per batch is 1, which means that the job will refresh 1 bucket at a time */\n#define DEFAULT_BUCKETS_PER_BATCH 10\n/* Default max batches per execution is 0, which means no limit */\n#define DEFAULT_MAX_BATCHES_PER_EXECUTION 0\n/* Default refresh newest first is true, which means from newest data to the oldest */\n#define DEFAULT_REFRESH_NEWEST_FIRST true\n\nint32\npolicy_continuous_aggregate_get_mat_hypertable_id(const Jsonb *config)\n{\n\tbool found;\n\tint32 mat_hypertable_id =\n\t\tts_jsonb_get_int32_field(config, POL_REFRESH_CONF_KEY_MAT_HYPERTABLE_ID, &found);\n\n\tif (!found)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_SQL_JSON_MEMBER_NOT_FOUND),\n\t\t\t\t errmsg(\"could not find \\\"%s\\\" in config for job\",\n\t\t\t\t\t\tPOL_REFRESH_CONF_KEY_MAT_HYPERTABLE_ID)));\n\n\treturn mat_hypertable_id;\n}\n\nstatic int64\nget_time_from_interval(const Dimension *dim, Datum interval, Oid type)\n{\n\tOid partitioning_type = ts_dimension_get_partition_type(dim);\n\n\tif (IS_INTEGER_TYPE(type))\n\t{\n\t\tOid now_func = ts_get_integer_now_func(dim, true);\n\t\tint64 value = ts_interval_value_to_internal(interval, type);\n\n\t\tAssert(now_func);\n\n\t\treturn ts_subtract_integer_from_now_saturating(now_func, value, partitioning_type);\n\t}\n\telse if (type == INTERVALOID)\n\t{\n\t\tDatum res = ts_subtract_interval_from_now(DatumGetIntervalP(interval), partitioning_type);\n\t\treturn ts_time_value_to_internal(res, partitioning_type);\n\t}\n\telse\n\t\telog(ERROR, \"unsupported offset type for continuous aggregate policy\");\n\n\tpg_unreachable();\n\n\treturn 0;\n}\n\nstatic int64\nget_time_from_config(const Dimension *dim, const Jsonb *config, const char *json_label,\n\t\t\t\t\t bool *isnull)\n{\n\tOid partitioning_type = ts_dimension_get_partition_type(dim);\n\t*isnull = false;\n\n\tif (IS_INTEGER_TYPE(partitioning_type))\n\t{\n\t\tbool found;\n\t\tint64 interval_val = ts_jsonb_get_int64_field(config, json_label, &found);\n\n\t\tif (!found)\n\t\t{\n\t\t\t*isnull = true;\n\t\t\treturn 0;\n\t\t}\n\t\treturn get_time_from_interval(dim, Int64GetDatum(interval_val), INT8OID);\n\t}\n\telse\n\t{\n\t\tInterval *interval_val = ts_jsonb_get_interval_field(config, json_label);\n\n\t\tif (!interval_val)\n\t\t{\n\t\t\t*isnull = true;\n\t\t\treturn 0;\n\t\t}\n\n\t\treturn get_time_from_interval(dim, IntervalPGetDatum(interval_val), INTERVALOID);\n\t}\n}\n\nint64\npolicy_refresh_cagg_get_refresh_start(const ContinuousAgg *cagg, const Dimension *dim,\n\t\t\t\t\t\t\t\t\t  const Jsonb *config, bool *start_isnull)\n{\n\tint64 res = get_time_from_config(dim, config, POL_REFRESH_CONF_KEY_START_OFFSET, start_isnull);\n\n\t/* interpret NULL as min value for that type */\n\tif (*start_isnull)\n\t{\n\t\tAssert(cagg->partition_type == ts_dimension_get_partition_type(dim));\n\t\treturn cagg_get_time_min(cagg);\n\t}\n\n\treturn res;\n}\n\nint64\npolicy_refresh_cagg_get_refresh_end(const Dimension *dim, const Jsonb *config, bool *end_isnull)\n{\n\tint64 res = get_time_from_config(dim, config, POL_REFRESH_CONF_KEY_END_OFFSET, end_isnull);\n\n\tif (*end_isnull)\n\t\treturn ts_time_get_noend_or_max(ts_dimension_get_partition_type(dim));\n\treturn res;\n}\n\nbool\npolicy_refresh_cagg_get_include_tiered_data(const Jsonb *config, bool *isnull)\n{\n\tbool found;\n\tbool res = ts_jsonb_get_bool_field(config, POL_REFRESH_CONF_KEY_INCLUDE_TIERED_DATA, &found);\n\n\t*isnull = !found;\n\treturn res;\n}\n\nint32\npolicy_refresh_cagg_get_buckets_per_batch(const Jsonb *config)\n{\n\tbool found;\n\tint32 res = ts_jsonb_get_int32_field(config, POL_REFRESH_CONF_KEY_BUCKETS_PER_BATCH, &found);\n\n\tif (!found)\n\t\tres = DEFAULT_BUCKETS_PER_BATCH; /* default value */\n\n\treturn res;\n}\n\nint32\npolicy_refresh_cagg_get_max_batches_per_execution(const Jsonb *config)\n{\n\tbool found;\n\tint32 res =\n\t\tts_jsonb_get_int32_field(config, POL_REFRESH_CONF_KEY_MAX_BATCHES_PER_EXECUTION, &found);\n\n\tif (!found)\n\t\tres = DEFAULT_MAX_BATCHES_PER_EXECUTION; /* default value */\n\n\treturn res;\n}\n\nbool\npolicy_refresh_cagg_get_refresh_newest_first(const Jsonb *config)\n{\n\tbool found;\n\tbool res = ts_jsonb_get_bool_field(config, POL_REFRESH_CONF_KEY_REFRESH_NEWEST_FIRST, &found);\n\n\tif (!found)\n\t\tres = DEFAULT_REFRESH_NEWEST_FIRST; /* default value */\n\n\treturn res;\n}\n\n/* returns false if a policy could not be found */\nbool\npolicy_refresh_cagg_exists(int32 materialization_id)\n{\n\tHypertable *mat_ht = ts_hypertable_get_by_id(materialization_id);\n\n\tif (!mat_ht)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"configuration materialization hypertable id %d not found\",\n\t\t\t\t\t\tmaterialization_id)));\n\n\tList *jobs = ts_bgw_job_find_by_proc_and_hypertable_id(POLICY_REFRESH_CAGG_PROC_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   FUNCTIONS_SCHEMA_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   materialization_id);\n\tif (jobs == NIL)\n\t\treturn false;\n\n\treturn true;\n}\n\nDatum\npolicy_refresh_cagg_proc(PG_FUNCTION_ARGS)\n{\n\tif (PG_NARGS() != 2 || PG_ARGISNULL(0) || PG_ARGISNULL(1))\n\t\tPG_RETURN_VOID();\n\n\tts_feature_flag_check(FEATURE_POLICY);\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\tpolicy_refresh_cagg_execute(PG_GETARG_INT32(0), PG_GETARG_JSONB_P(1));\n\n\tPG_RETURN_VOID();\n}\n\nDatum\npolicy_refresh_cagg_check(PG_FUNCTION_ARGS)\n{\n\tif (PG_ARGISNULL(0))\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg(\"config must not be NULL\")));\n\t}\n\n\tpolicy_refresh_cagg_read_and_validate_config(PG_GETARG_JSONB_P(0), NULL);\n\n\tPG_RETURN_VOID();\n}\n\nstatic void\njson_add_dim_interval_value(JsonbParseState *parse_state, const char *json_label, Oid dim_type,\n\t\t\t\t\t\t\tDatum value)\n{\n\tswitch (dim_type)\n\t{\n\t\tcase INTERVALOID:\n\t\t\tts_jsonb_add_interval(parse_state, json_label, DatumGetIntervalP(value));\n\t\t\tbreak;\n\t\tcase INT2OID:\n\t\t\tts_jsonb_add_int64(parse_state, json_label, DatumGetInt16(value));\n\t\t\tbreak;\n\t\tcase INT4OID:\n\t\t\tts_jsonb_add_int64(parse_state, json_label, DatumGetInt32(value));\n\t\t\tbreak;\n\t\tcase INT8OID:\n\t\t\tts_jsonb_add_int64(parse_state, json_label, DatumGetInt64(value));\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"unsupported interval argument type, expected type : %s\",\n\t\t\t\t\t\t\tformat_type_be(dim_type))));\n\t}\n}\n\nstatic Datum\nconvert_interval_arg(Oid dim_type, Datum interval, Oid *interval_type, const char *str_msg)\n{\n\tOid convert_to = dim_type;\n\tDatum converted;\n\n\tif (IS_TIMESTAMP_TYPE(dim_type))\n\t\tconvert_to = INTERVALOID;\n\n\tif (*interval_type != convert_to)\n\t{\n\t\tif (!can_coerce_type(1, interval_type, &convert_to, COERCION_IMPLICIT))\n\t\t{\n\t\t\tif (IS_INTEGER_TYPE(dim_type))\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t\t errmsg(\"invalid parameter value for %s\", str_msg),\n\t\t\t\t\t\t errhint(\"Use time interval of type %s with the continuous aggregate.\",\n\t\t\t\t\t\t\t\t format_type_be(dim_type))));\n\t\t\telse if (IS_TIMESTAMP_TYPE(dim_type))\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t\t errmsg(\"invalid parameter value for %s\", str_msg),\n\t\t\t\t\t\t errhint(\"Use time interval with a continuous aggregate using \"\n\t\t\t\t\t\t\t\t \"timestamp-based time \"\n\t\t\t\t\t\t\t\t \"bucket.\")));\n\t\t}\n\t}\n\n\tconverted = ts_time_datum_convert_arg(interval, interval_type, convert_to);\n\n\t/* For integer types, first convert all types to int64 to get on a common\n\t * type. Then check valid time ranges against the partition/dimension\n\t * type */\n\tswitch (*interval_type)\n\t{\n\t\tcase INT2OID:\n\t\t\tconverted = Int64GetDatum((int64) DatumGetInt16(converted));\n\t\t\tbreak;\n\t\tcase INT4OID:\n\t\t\tconverted = Int64GetDatum((int64) DatumGetInt32(converted));\n\t\t\tbreak;\n\t\tcase INT8OID:\n\t\t\tbreak;\n\t\tcase INTERVALOID:\n\t\t\t/* For timestamp types, we only support Interval, so nothing further\n\t\t\t * to do. */\n\t\t\treturn converted;\n\t\tdefault:\n\t\t\tpg_unreachable();\n\t\t\tbreak;\n\t}\n\n\t/* Cap at min and max */\n\tif (DatumGetInt64(converted) < ts_time_get_min(dim_type))\n\t\tconverted = ts_time_get_min(dim_type);\n\telse if (DatumGetInt64(converted) > ts_time_get_max(dim_type))\n\t\tconverted = ts_time_get_max(dim_type);\n\n\t/* Convert to the desired integer type */\n\tswitch (dim_type)\n\t{\n\t\tcase INT2OID:\n\t\t\tconverted = Int16GetDatum((int16) DatumGetInt64(converted));\n\t\t\tbreak;\n\t\tcase INT4OID:\n\t\t\tconverted = Int32GetDatum((int32) DatumGetInt64(converted));\n\t\t\tbreak;\n\t\tcase INT8OID:\n\t\t\t/* Already int64, so nothing to do. */\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tpg_unreachable();\n\t\t\tbreak;\n\t}\n\n\t*interval_type = dim_type;\n\n\treturn converted;\n}\n\n/*\n * Convert an interval to a 128 integer value.\n *\n * Based on PostgreSQL's interval_cmp_value().\n */\nstatic inline INT128\ninterval_to_int128(const Interval *interval)\n{\n\tINT128 span;\n\tint64 dayfraction;\n\tint64 days;\n\n\t/*\n\t * Separate time field into days and dayfraction, then add the month and\n\t * day fields to the days part.  We cannot overflow int64 days here.\n\t */\n\tdayfraction = interval->time % USECS_PER_DAY;\n\tdays = interval->time / USECS_PER_DAY;\n\tdays += interval->month * INT64CONST(30);\n\tdays += interval->day;\n\n\t/* Widen dayfraction to 128 bits */\n\tspan = int64_to_int128(dayfraction);\n\n\t/* Scale up days to microseconds, forming a 128-bit product */\n\tint128_add_int64_mul_int64(&span, days, USECS_PER_DAY);\n\n\treturn span;\n}\n\nint64\ninterval_to_int64(Datum interval, Oid type)\n{\n\tswitch (type)\n\t{\n\t\tcase INT2OID:\n\t\t\treturn DatumGetInt16(interval);\n\t\tcase INT4OID:\n\t\t\treturn DatumGetInt32(interval);\n\t\tcase INT8OID:\n\t\t\treturn DatumGetInt64(interval);\n\t\tcase INTERVALOID:\n\t\t{\n\t\t\tconst int64 max = ts_time_get_max(TIMESTAMPTZOID);\n\t\t\tconst int64 min = ts_time_get_min(TIMESTAMPTZOID);\n\t\t\tINT128 bigres = interval_to_int128(DatumGetIntervalP(interval));\n\n\t\t\tif (int128_compare(bigres, int64_to_int128(max)) >= 0)\n\t\t\t\treturn max;\n\t\t\telse if (int128_compare(bigres, int64_to_int128(min)) <= 0)\n\t\t\t\treturn min;\n\t\t\telse\n\t\t\t\treturn int128_to_int64(bigres);\n\t\t}\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\n\tereport(ERROR,\n\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t errmsg(\"unsupported interval argument type: %s\", format_type_be(type))));\n\n\t/* Needed to make windows compiler happy */\n\tpg_unreachable();\n}\n\n/*\n * Enforce that a policy has a refresh window of at least two buckets to\n * ensure we materialize at least one bucket each run.\n *\n * Why two buckets? Note that the policy probably won't execute at at time\n * that exactly aligns with a bucket boundary, so a window of one bucket\n * might not cover a full bucket that we want to materialize:\n *\n * Refresh window:                   [-----)\n * Materialized buckets:   |-----|-----|-----|\n */\nstatic void\nvalidate_window_size(const ContinuousAgg *cagg, const CaggPolicyConfig *config)\n{\n\tint64 start_offset;\n\tint64 end_offset;\n\tint64 bucket_width;\n\n\tif (config->offset_start.isnull)\n\t\tstart_offset = ts_time_get_max(cagg->partition_type);\n\telse\n\t\tstart_offset = interval_to_int64(config->offset_start.value, config->offset_start.type);\n\n\tif (config->offset_end.isnull)\n\t\tend_offset = ts_time_get_min(cagg->partition_type);\n\telse\n\t\tend_offset = interval_to_int64(config->offset_end.value, config->offset_end.type);\n\n\tbucket_width = ts_continuous_agg_bucket_width(cagg->bucket_function);\n\tAssert(bucket_width > 0);\n\n\tif (ts_time_saturating_add(end_offset, bucket_width * 2, INT8OID) > start_offset)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"policy refresh window too small\"),\n\t\t\t\t errdetail(\"The start and end offsets must cover at least\"\n\t\t\t\t\t\t   \" two buckets in the valid time range of type \\\"%s\\\".\",\n\t\t\t\t\t\t   format_type_be(cagg->partition_type))));\n}\n\nstatic void\nparse_offset_arg(const ContinuousAgg *cagg, Oid offset_type, NullableDatum arg,\n\t\t\t\t ContinuousAggPolicyOffset *offset)\n{\n\toffset->isnull = arg.isnull;\n\n\tif (!offset->isnull)\n\t{\n\t\toffset->value =\n\t\t\tconvert_interval_arg(cagg->partition_type, arg.value, &offset_type, offset->name);\n\t\toffset->type = offset_type;\n\t}\n}\n\nstatic void\nparse_cagg_policy_config(const ContinuousAgg *cagg, Oid start_offset_type,\n\t\t\t\t\t\t NullableDatum start_offset, Oid end_offset_type, NullableDatum end_offset,\n\t\t\t\t\t\t CaggPolicyConfig *config)\n{\n\tMemSet(config, 0, sizeof(CaggPolicyConfig));\n\tconfig->partition_type = cagg->partition_type;\n\t/* This might seem backwards, but since we are dealing with offsets, start\n\t * actually translates to max and end to min for maximum window. */\n\tconfig->offset_start.value = ts_time_datum_get_max(config->partition_type);\n\tconfig->offset_end.value = ts_time_datum_get_min(config->partition_type);\n\tconfig->offset_start.type = config->offset_end.type =\n\t\tIS_TIMESTAMP_TYPE(cagg->partition_type) ? INTERVALOID : cagg->partition_type;\n\tconfig->offset_start.name = POL_REFRESH_CONF_KEY_START_OFFSET;\n\tconfig->offset_end.name = POL_REFRESH_CONF_KEY_END_OFFSET;\n\tparse_offset_arg(cagg, start_offset_type, start_offset, &config->offset_start);\n\tparse_offset_arg(cagg, end_offset_type, end_offset, &config->offset_end);\n\n\tAssert(config->offset_start.type == config->offset_end.type);\n\tvalidate_window_size(cagg, config);\n}\n\nbool\npolicy_refresh_cagg_check_if_last_policy(PolicyContinuousAggData *policy_data)\n{\n\tContinuousAgg *cagg = policy_data->cagg;\n\tint64 end_offset = policy_data->refresh_window.end;\n\tbool end_isnull = policy_data->refresh_window.end_isnull;\n\n\tif (end_isnull)\n\t\treturn true;\n\n\tHypertable *mat_ht = ts_hypertable_get_by_id(cagg->data.mat_hypertable_id);\n\tconst Dimension *dim = get_open_dimension_for_hypertable(mat_ht, true);\n\n\tList *jobs = ts_bgw_job_find_by_proc_and_hypertable_id(POLICY_REFRESH_CAGG_PROC_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   FUNCTIONS_SCHEMA_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   cagg->data.mat_hypertable_id);\n\n\tListCell *lc;\n\n\t/* We need to go through all jobs in order to determine if there is a job which starts after\n\t * this one */\n\tforeach (lc, jobs)\n\t{\n\t\tBgwJob *job = (BgwJob *) lfirst(lc);\n\n\t\tbool end_offset_job_isnull;\n\t\tint64 end_offset_job =\n\t\t\tpolicy_refresh_cagg_get_refresh_end(dim, job->fd.config, &end_offset_job_isnull);\n\n\t\tif (end_offset_job_isnull || end_offset < end_offset_job)\n\t\t{\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/* Ensures the refresh range of the new policy doesn't overlap with an existing one*/\nPolicyRefreshOffsetOverlapResult\npolicy_refresh_cagg_check_for_overlaps(ContinuousAgg *cagg, Jsonb *policy_config,\n\t\t\t\t\t\t\t\t\t   int32 existing_job_id)\n{\n\tList *jobs = ts_bgw_job_find_by_proc_and_hypertable_id(POLICY_REFRESH_CAGG_PROC_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   FUNCTIONS_SCHEMA_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   cagg->data.mat_hypertable_id);\n\tPolicyRefreshOffsetOverlapResult overlap_result = POLICY_REFRESH_OFFSET_OVERLAP_NONE;\n\n\tif (jobs == NIL)\n\t\treturn overlap_result;\n\n\tHypertable *mat_ht = ts_hypertable_get_by_id(cagg->data.mat_hypertable_id);\n\tconst Dimension *dim = get_open_dimension_for_hypertable(mat_ht, true);\n\n\tbool start_offset_isnull, end_offset_isnull;\n\tint64 start_offset =\n\t\tpolicy_refresh_cagg_get_refresh_start(cagg, dim, policy_config, &start_offset_isnull);\n\tint64 end_offset = policy_refresh_cagg_get_refresh_end(dim, policy_config, &end_offset_isnull);\n\n\tRangeBound lower = {\n\t\t.val = Int64GetDatum(start_offset),\n\t\t.infinite = start_offset_isnull,\n\t\t.inclusive = true,\n\t\t.lower = true,\n\t};\n\n\tRangeBound upper = {\n\t\t.val = Int64GetDatum(end_offset),\n\t\t.infinite = end_offset_isnull,\n\t\t.inclusive = false,\n\t\t.lower = false,\n\t};\n\n\tTypeCacheEntry *typcache = lookup_type_cache(INT8RANGEOID, TYPECACHE_RANGE_INFO);\n\tif (typcache == NULL || typcache->rngelemtype == NULL)\n\t\telog(ERROR, \"cache lookup failed\");\n\n\tRangeType *range = make_range_compat(typcache, &lower, &upper, false, NULL);\n\n\tListCell *lc;\n\n\telog(DEBUG1,\n\t\t \"start_offset: \" INT64_FORMAT \", end_offset: \" INT64_FORMAT,\n\t\t start_offset,\n\t\t end_offset);\n\n\t/* We need to go through all jobs in order to determine if there is an existing job with the\n\t * exact same offsets */\n\tforeach (lc, jobs)\n\t{\n\t\tBgwJob *job = (BgwJob *) lfirst(lc);\n\n\t\tif (existing_job_id == job->fd.id)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\tbool start_offset_job_isnull, end_offset_job_isnull;\n\t\tint64 start_offset_job = policy_refresh_cagg_get_refresh_start(cagg,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   dim,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   job->fd.config,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   &start_offset_job_isnull);\n\n\t\tint64 end_offset_job =\n\t\t\tpolicy_refresh_cagg_get_refresh_end(dim, job->fd.config, &end_offset_job_isnull);\n\n\t\tRangeBound lower_job = {\n\t\t\t.val = Int64GetDatum(start_offset_job),\n\t\t\t.infinite = start_offset_job_isnull,\n\t\t\t.inclusive = true,\n\t\t\t.lower = true,\n\t\t};\n\n\t\tRangeBound upper_job = {\n\t\t\t.val = Int64GetDatum(end_offset_job),\n\t\t\t.infinite = end_offset_job_isnull,\n\t\t\t.inclusive = false,\n\t\t\t.lower = false,\n\t\t};\n\n\t\tRangeType *range_job = make_range_compat(typcache, &lower_job, &upper_job, false, NULL);\n\n\t\telog(DEBUG1,\n\t\t\t \"start_offset_job: \" INT64_FORMAT \", end_offset_job: \" INT64_FORMAT,\n\t\t\t start_offset_job,\n\t\t\t end_offset_job);\n\n\t\t/* Check if exact same job exists, in which case throw an error or notice depending on\n\t\t * `if_not_exists` */\n\t\tif (start_offset == start_offset_job && end_offset == end_offset_job)\n\t\t{\n\t\t\t/* If all arguments are the same, do nothing */\n\t\t\treturn POLICY_REFRESH_OFFSET_OVERLAP_EQUAL;\n\t\t}\n\t\t/* We need to first check all other jobs to see if there is an exact match, since we prefer\n\t\t * returning an exact match over an overlap, and the list of jobs isn't guaranteed to be\n\t\t * sorted by start/end offset. */\n\t\telse if (range_overlaps_internal(typcache, range_job, range))\n\t\t{\n\t\t\toverlap_result = POLICY_REFRESH_OFFSET_OVERLAP;\n\t\t}\n\t}\n\n\t/* We cannot check if the CAgg is hierarchical first and abort early since\n\t * we need to respect the if_not_exists parameter that is passed in.\n\t * So we check for overlap first, and only if there is no exact match, we block multiple\n\t * policies on hierarchical caggs */\n\tif (ContinuousAggIsHierarchical(cagg))\n\t{\n\t\t/* if this is an existing job, it will also be in the list of jobs */\n\t\tint max_concurrent = existing_job_id ? 1 : 0;\n\n\t\tif (list_length(jobs) > max_concurrent)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"multiple refresh policies are not supported for hierarchical \"\n\t\t\t\t\t\t\t\"continuous aggregates\")));\n\t}\n\n\treturn overlap_result;\n}\n\nDatum\npolicy_refresh_cagg_add_internal(Oid cagg_oid, Oid start_offset_type, NullableDatum start_offset,\n\t\t\t\t\t\t\t\t Oid end_offset_type, NullableDatum end_offset,\n\t\t\t\t\t\t\t\t Interval refresh_interval, bool if_not_exists, bool fixed_schedule,\n\t\t\t\t\t\t\t\t TimestampTz initial_start, const char *timezone,\n\t\t\t\t\t\t\t\t NullableDatum include_tiered_data, NullableDatum buckets_per_batch,\n\t\t\t\t\t\t\t\t NullableDatum max_batches_per_execution,\n\t\t\t\t\t\t\t\t NullableDatum refresh_newest_first)\n{\n\tNameData application_name;\n\tNameData proc_name, proc_schema, check_name, check_schema, owner;\n\tContinuousAgg *cagg;\n\tCaggPolicyConfig policyconf;\n\tint32 job_id;\n\tOid owner_id;\n\tJsonbParseState *parse_state = NULL;\n\n\t/* Verify that the owner can create a background worker */\n\towner_id = ts_cagg_permissions_check(cagg_oid, GetUserId());\n\tts_bgw_job_validate_job_owner(owner_id);\n\n\tcagg = ts_continuous_agg_find_by_relid(cagg_oid);\n\tif (!cagg)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"\\\"%s\\\" is not a continuous aggregate\", get_rel_name(cagg_oid))));\n\n\tif (!start_offset.isnull)\n\t\tstart_offset.isnull =\n\t\t\tts_if_offset_is_infinity(start_offset.value, start_offset_type, true /* is_start */);\n\tif (!end_offset.isnull)\n\t\tend_offset.isnull =\n\t\t\tts_if_offset_is_infinity(end_offset.value, end_offset_type, false /* is_start */);\n\n\tparse_cagg_policy_config(cagg,\n\t\t\t\t\t\t\t start_offset_type,\n\t\t\t\t\t\t\t start_offset,\n\t\t\t\t\t\t\t end_offset_type,\n\t\t\t\t\t\t\t end_offset,\n\t\t\t\t\t\t\t &policyconf);\n\n\t/* Insert a new job into jobs table */\n\tnamestrcpy(&application_name, \"Refresh Continuous Aggregate Policy\");\n\tnamestrcpy(&proc_name, POLICY_REFRESH_CAGG_PROC_NAME);\n\tnamestrcpy(&proc_schema, FUNCTIONS_SCHEMA_NAME);\n\tnamestrcpy(&check_name, POLICY_REFRESH_CAGG_CHECK_NAME);\n\tnamestrcpy(&check_schema, FUNCTIONS_SCHEMA_NAME);\n\tnamestrcpy(&owner, GetUserNameFromId(owner_id, false));\n\n\tpushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);\n\tts_jsonb_add_int32(parse_state,\n\t\t\t\t\t   POL_REFRESH_CONF_KEY_MAT_HYPERTABLE_ID,\n\t\t\t\t\t   cagg->data.mat_hypertable_id);\n\n\tif (!policyconf.offset_start.isnull)\n\t\tjson_add_dim_interval_value(parse_state,\n\t\t\t\t\t\t\t\t\tPOL_REFRESH_CONF_KEY_START_OFFSET,\n\t\t\t\t\t\t\t\t\tpolicyconf.offset_start.type,\n\t\t\t\t\t\t\t\t\tpolicyconf.offset_start.value);\n\telse\n\t\tts_jsonb_add_null(parse_state, POL_REFRESH_CONF_KEY_START_OFFSET);\n\n\tif (!policyconf.offset_end.isnull)\n\t\tjson_add_dim_interval_value(parse_state,\n\t\t\t\t\t\t\t\t\tPOL_REFRESH_CONF_KEY_END_OFFSET,\n\t\t\t\t\t\t\t\t\tpolicyconf.offset_end.type,\n\t\t\t\t\t\t\t\t\tpolicyconf.offset_end.value);\n\telse\n\t\tts_jsonb_add_null(parse_state, POL_REFRESH_CONF_KEY_END_OFFSET);\n\n\tif (!include_tiered_data.isnull)\n\t\tts_jsonb_add_bool(parse_state,\n\t\t\t\t\t\t  POL_REFRESH_CONF_KEY_INCLUDE_TIERED_DATA,\n\t\t\t\t\t\t  include_tiered_data.value);\n\n\tif (!buckets_per_batch.isnull)\n\t\tts_jsonb_add_int32(parse_state,\n\t\t\t\t\t\t   POL_REFRESH_CONF_KEY_BUCKETS_PER_BATCH,\n\t\t\t\t\t\t   buckets_per_batch.value);\n\n\tif (!max_batches_per_execution.isnull)\n\t\tts_jsonb_add_int32(parse_state,\n\t\t\t\t\t\t   POL_REFRESH_CONF_KEY_MAX_BATCHES_PER_EXECUTION,\n\t\t\t\t\t\t   max_batches_per_execution.value);\n\n\tif (!refresh_newest_first.isnull)\n\t\tts_jsonb_add_bool(parse_state,\n\t\t\t\t\t\t  POL_REFRESH_CONF_KEY_REFRESH_NEWEST_FIRST,\n\t\t\t\t\t\t  refresh_newest_first.value);\n\n\tJsonbValue *result = pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);\n\tJsonb *config = JsonbValueToJsonb(result);\n\n\tPolicyRefreshOffsetOverlapResult res = policy_refresh_cagg_check_for_overlaps(cagg, config, 0);\n\tswitch (res)\n\t{\n\t\tcase POLICY_REFRESH_OFFSET_OVERLAP_NONE:\n\t\t\tbreak;\n\t\tcase POLICY_REFRESH_OFFSET_OVERLAP_EQUAL:\n\t\t\tif (if_not_exists)\n\t\t\t{\n\t\t\t\tereport(NOTICE,\n\t\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\n\t\t\t\t\t\t errmsg(\"continuous aggregate refresh policy already exists for \"\n\t\t\t\t\t\t\t\t\"\\\"%s\\\", skipping\",\n\t\t\t\t\t\t\t\tget_rel_name(cagg->relid)),\n\t\t\t\t\t\t errdetail(\"A refresh policy with the same start and end offset already \"\n\t\t\t\t\t\t\t\t   \"exists for continuous aggregate \\\"%s\\\".\",\n\t\t\t\t\t\t\t\t   get_rel_name(cagg->relid))));\n\t\t\t\tPG_RETURN_INT32(-1);\n\t\t\t}\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\n\t\t\t\t\t errmsg(\"continuous aggregate refresh policy already exists for \"\n\t\t\t\t\t\t\t\"\\\"%s\\\"\",\n\t\t\t\t\t\t\tget_rel_name(cagg->relid)),\n\t\t\t\t\t errdetail(\"A refresh policy with the same start and end offset already exists \"\n\t\t\t\t\t\t\t   \"for \"\n\t\t\t\t\t\t\t   \"continuous aggregate \\\"%s\\\".\",\n\t\t\t\t\t\t\t   get_rel_name(cagg->relid))));\n\t\t\tbreak;\n\t\tcase POLICY_REFRESH_OFFSET_OVERLAP:\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"refresh interval overlaps with an existing continuous aggregate \"\n\t\t\t\t\t\t\t\"policy on \\\"%s\\\"\",\n\t\t\t\t\t\t\tget_rel_name(cagg->relid))));\n\t\t\tbreak;\n\t}\n\n\tjob_id = ts_bgw_job_insert_relation(&application_name,\n\t\t\t\t\t\t\t\t\t\t&refresh_interval,\n\t\t\t\t\t\t\t\t\t\tDEFAULT_MAX_RUNTIME,\n\t\t\t\t\t\t\t\t\t\tJOB_RETRY_UNLIMITED,\n\t\t\t\t\t\t\t\t\t\t&refresh_interval,\n\t\t\t\t\t\t\t\t\t\t&proc_schema,\n\t\t\t\t\t\t\t\t\t\t&proc_name,\n\t\t\t\t\t\t\t\t\t\t&check_schema,\n\t\t\t\t\t\t\t\t\t\t&check_name,\n\t\t\t\t\t\t\t\t\t\towner_id,\n\t\t\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\t\t\tfixed_schedule,\n\t\t\t\t\t\t\t\t\t\tcagg->data.mat_hypertable_id,\n\t\t\t\t\t\t\t\t\t\tconfig,\n\t\t\t\t\t\t\t\t\t\tinitial_start,\n\t\t\t\t\t\t\t\t\t\ttimezone);\n\n\tPG_RETURN_INT32(job_id);\n}\n\nDatum\npolicy_refresh_cagg_add(PG_FUNCTION_ARGS)\n{\n\tOid cagg_oid, start_offset_type, end_offset_type;\n\tInterval refresh_interval;\n\tbool if_not_exists;\n\tNullableDatum start_offset, end_offset;\n\tNullableDatum include_tiered_data;\n\tNullableDatum buckets_per_batch;\n\tNullableDatum max_batches_per_execution;\n\tNullableDatum refresh_newest_first;\n\n\tts_feature_flag_check(FEATURE_POLICY);\n\n\tcagg_oid = PG_GETARG_OID(0);\n\n\tif (PG_ARGISNULL(3))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"cannot use NULL refresh_schedule_interval\")));\n\n\tstart_offset_type = get_fn_expr_argtype(fcinfo->flinfo, 1);\n\tstart_offset.value = PG_GETARG_DATUM(1);\n\tstart_offset.isnull = PG_ARGISNULL(1);\n\tend_offset_type = get_fn_expr_argtype(fcinfo->flinfo, 2);\n\tend_offset.value = PG_GETARG_DATUM(2);\n\tend_offset.isnull = PG_ARGISNULL(2);\n\trefresh_interval = *PG_GETARG_INTERVAL_P(3);\n\tif_not_exists = PG_GETARG_BOOL(4);\n\tTimestampTz initial_start = PG_ARGISNULL(5) ? DT_NOBEGIN : PG_GETARG_TIMESTAMPTZ(5);\n\tbool fixed_schedule = !PG_ARGISNULL(5);\n\ttext *timezone = PG_ARGISNULL(6) ? NULL : PG_GETARG_TEXT_PP(6);\n\tchar *valid_timezone = NULL;\n\tinclude_tiered_data.value = PG_GETARG_DATUM(7);\n\tinclude_tiered_data.isnull = PG_ARGISNULL(7);\n\tbuckets_per_batch.value = PG_GETARG_DATUM(8);\n\tbuckets_per_batch.isnull = PG_ARGISNULL(8);\n\tmax_batches_per_execution.value = PG_GETARG_DATUM(9);\n\tmax_batches_per_execution.isnull = PG_ARGISNULL(9);\n\trefresh_newest_first.value = PG_GETARG_DATUM(10);\n\trefresh_newest_first.isnull = PG_ARGISNULL(10);\n\n\tDatum retval;\n\t/* if users pass in -infinity for initial_start, then use the current_timestamp instead */\n\tif (fixed_schedule)\n\t{\n\t\tts_bgw_job_validate_schedule_interval(&refresh_interval);\n\t\tif (TIMESTAMP_NOT_FINITE(initial_start))\n\t\t\tinitial_start = ts_timer_get_current_timestamp();\n\t}\n\n\tif (timezone != NULL)\n\t\tvalid_timezone = ts_bgw_job_validate_timezone(PG_GETARG_DATUM(6));\n\n\tretval = policy_refresh_cagg_add_internal(cagg_oid,\n\t\t\t\t\t\t\t\t\t\t\t  start_offset_type,\n\t\t\t\t\t\t\t\t\t\t\t  start_offset,\n\t\t\t\t\t\t\t\t\t\t\t  end_offset_type,\n\t\t\t\t\t\t\t\t\t\t\t  end_offset,\n\t\t\t\t\t\t\t\t\t\t\t  refresh_interval,\n\t\t\t\t\t\t\t\t\t\t\t  if_not_exists,\n\t\t\t\t\t\t\t\t\t\t\t  fixed_schedule,\n\t\t\t\t\t\t\t\t\t\t\t  initial_start,\n\t\t\t\t\t\t\t\t\t\t\t  valid_timezone,\n\t\t\t\t\t\t\t\t\t\t\t  include_tiered_data,\n\t\t\t\t\t\t\t\t\t\t\t  buckets_per_batch,\n\t\t\t\t\t\t\t\t\t\t\t  max_batches_per_execution,\n\t\t\t\t\t\t\t\t\t\t\t  refresh_newest_first);\n\tif (!TIMESTAMP_NOT_FINITE(initial_start))\n\t{\n\t\tint32 job_id = DatumGetInt32(retval);\n\t\tts_bgw_job_stat_upsert_next_start(job_id, initial_start);\n\t}\n\treturn retval;\n}\n\nDatum\npolicy_refresh_cagg_remove_internal(Oid cagg_oid, bool if_exists)\n{\n\tint32 mat_htid;\n\n\tContinuousAgg *cagg = ts_continuous_agg_find_by_relid(cagg_oid);\n\n\tif (!cagg)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"\\\"%s\\\" is not a continuous aggregate\", get_rel_name(cagg_oid))));\n\n\tts_cagg_permissions_check(cagg_oid, GetUserId());\n\tmat_htid = cagg->data.mat_hypertable_id;\n\tList *jobs = ts_bgw_job_find_by_proc_and_hypertable_id(POLICY_REFRESH_CAGG_PROC_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   FUNCTIONS_SCHEMA_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   mat_htid);\n\tif (jobs == NIL)\n\t{\n\t\tif (!if_exists)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t\t (errmsg(\"continuous aggregate policy not found for \\\"%s\\\"\",\n\t\t\t\t\t\t\t get_rel_name(cagg_oid)))));\n\t\telse\n\t\t{\n\t\t\tereport(NOTICE,\n\t\t\t\t\t(errmsg(\"continuous aggregate policy not found for \\\"%s\\\", skipping\",\n\t\t\t\t\t\t\tget_rel_name(cagg_oid))));\n\t\t\tPG_RETURN_BOOL(false);\n\t\t}\n\t}\n\n\t// Delete all bgw jobs associated with this CAgg\n\tListCell *lc;\n\tforeach (lc, jobs)\n\t{\n\t\tBgwJob *job = (BgwJob *) lfirst(lc);\n\t\tts_bgw_job_delete_by_id(job->fd.id);\n\t}\n\n\tPG_RETURN_BOOL(true);\n}\n\nDatum\npolicy_refresh_cagg_remove(PG_FUNCTION_ARGS)\n{\n\tOid cagg_oid = PG_GETARG_OID(0);\n\tbool if_not_exists = PG_GETARG_BOOL(1); /* Deprecating this argument */\n\tbool if_exists;\n\n\t/* For backward compatibility, we use IF_NOT_EXISTS when IF_EXISTS is not given */\n\tif_exists = PG_ARGISNULL(2) ? if_not_exists : PG_GETARG_BOOL(2);\n\n\tts_feature_flag_check(FEATURE_POLICY);\n\t(void) policy_refresh_cagg_remove_internal(cagg_oid, if_exists);\n\tPG_RETURN_VOID();\n}\n"
  },
  {
    "path": "tsl/src/bgw_policy/continuous_aggregate_api.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include \"bgw_policy/job.h\"\n#include \"dimension.h\"\n#include <continuous_aggs/materialize.h>\n#include <utils/jsonb.h>\n\ntypedef enum PolicyRefreshOffsetOverlapResult\n{\n\tPOLICY_REFRESH_OFFSET_OVERLAP,\t\t /* overlap but not exact */\n\tPOLICY_REFRESH_OFFSET_OVERLAP_EQUAL, /* exact match */\n\tPOLICY_REFRESH_OFFSET_OVERLAP_NONE,\t /* no overlap */\n} PolicyRefreshOffsetOverlapResult;\n\nextern Datum policy_refresh_cagg_add(PG_FUNCTION_ARGS);\nextern Datum policy_refresh_cagg_proc(PG_FUNCTION_ARGS);\nextern Datum policy_refresh_cagg_check(PG_FUNCTION_ARGS);\nextern Datum policy_refresh_cagg_remove(PG_FUNCTION_ARGS);\n\nint32 policy_continuous_aggregate_get_mat_hypertable_id(const Jsonb *config);\nint64 policy_refresh_cagg_get_refresh_start(const ContinuousAgg *cagg, const Dimension *dim,\n\t\t\t\t\t\t\t\t\t\t\tconst Jsonb *config, bool *start_isnull);\nint64 policy_refresh_cagg_get_refresh_end(const Dimension *dim, const Jsonb *config,\n\t\t\t\t\t\t\t\t\t\t  bool *end_isnull);\nbool policy_refresh_cagg_get_include_tiered_data(const Jsonb *config, bool *isnull);\nint32 policy_refresh_cagg_get_buckets_per_batch(const Jsonb *config);\nint32 policy_refresh_cagg_get_max_batches_per_execution(const Jsonb *config);\nbool policy_refresh_cagg_get_refresh_newest_first(const Jsonb *config);\nbool policy_refresh_cagg_exists(int32 materialization_id);\n\nDatum policy_refresh_cagg_add_internal(\n\tOid cagg_oid, Oid start_offset_type, NullableDatum start_offset, Oid end_offset_type,\n\tNullableDatum end_offset, Interval refresh_interval, bool if_not_exists, bool fixed_schedule,\n\tTimestampTz initial_start, const char *timezone, NullableDatum include_tiered_data,\n\tNullableDatum buckets_per_batch, NullableDatum max_batches_per_execution,\n\tNullableDatum refresh_newest_first);\nDatum policy_refresh_cagg_remove_internal(Oid cagg_oid, bool if_exists);\n\nPolicyRefreshOffsetOverlapResult policy_refresh_cagg_check_for_overlaps(ContinuousAgg *cagg,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tJsonb *policy_config,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tint32 existing_job_id);\n\nbool policy_refresh_cagg_check_if_last_policy(PolicyContinuousAggData *policy_data);\n"
  },
  {
    "path": "tsl/src/bgw_policy/job.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n#include \"bgw_policy/policies_v2.h\"\n#include \"cache.h\"\n#include <access/xact.h>\n#include <catalog/namespace.h>\n#include <catalog/pg_type.h>\n#include <commands/defrem.h>\n#include <extension.h>\n#include <funcapi.h>\n#include <hypertable_cache.h>\n#include <nodes/makefuncs.h>\n#include <nodes/pg_list.h>\n#include <nodes/primnodes.h>\n#include <parser/parse_func.h>\n#include <parser/parser.h>\n#include <tcop/pquery.h>\n#include <utils/builtins.h>\n#include <utils/guc.h>\n#include <utils/lsyscache.h>\n#include <utils/portal.h>\n#include <utils/snapmgr.h>\n#include <utils/syscache.h>\n#include <utils/timestamp.h>\n\n#include \"compat/compat.h\"\n#include \"bgw/job.h\"\n#include \"bgw/job_stat.h\"\n#include \"bgw/timer.h\"\n#include \"bgw_policy/chunk_stats.h\"\n#include \"bgw_policy/compression_api.h\"\n#include \"bgw_policy/continuous_aggregate_api.h\"\n#include \"bgw_policy/policy_config.h\"\n#include \"bgw_policy/policy_utils.h\"\n#include \"bgw_policy/process_hyper_inval_api.h\"\n#include \"bgw_policy/reorder_api.h\"\n#include \"bgw_policy/retention_api.h\"\n#include \"compression/api.h\"\n#include \"continuous_aggs/invalidation_threshold.h\"\n#include \"continuous_aggs/materialize.h\"\n#include \"continuous_aggs/refresh.h\"\n#include \"ts_catalog/continuous_agg.h\"\n#ifdef USE_TELEMETRY\n#include \"telemetry/telemetry.h\"\n#endif\n\n#include \"tsl/src/chunk.h\"\n\n#include \"chunk.h\"\n#include \"config.h\"\n#include \"dimension.h\"\n#include \"dimension_slice.h\"\n#include \"guc.h\"\n#include \"job.h\"\n#include \"jsonb_utils.h\"\n#include \"reorder.h\"\n#include \"utils.h\"\n\n#define REORDER_SKIP_RECENT_DIM_SLICES_N 3\n\nstatic void\nlog_retention_boundary(int elevel, PolicyRetentionData *policy_data, const char *message)\n{\n\tif (OidIsValid(policy_data->boundary_type))\n\t\telog(elevel,\n\t\t\t \"%s \\\"%s\\\": dropping data %s %s\",\n\t\t\t message,\n\t\t\t get_rel_name(policy_data->object_relid),\n\t\t\t policy_data->use_creation_time ? \"created before\" : \"older than\",\n\t\t\t ts_datum_to_string(policy_data->boundary, policy_data->boundary_type));\n}\n\nstatic void\nenable_fast_restart(int32 job_id, const char *job_name)\n{\n\tBgwJobStat *job_stat = ts_bgw_job_stat_find(job_id);\n\tif (job_stat != NULL)\n\t{\n\t\t/* job might not have a valid last_start if it was not\n\t\t * run by the bgw framework.\n\t\t */\n\t\tts_bgw_job_stat_set_next_start(job_id,\n\t\t\t\t\t\t\t\t\t   job_stat->fd.last_start != DT_NOBEGIN ?\n\t\t\t\t\t\t\t\t\t\t   job_stat->fd.last_start :\n\t\t\t\t\t\t\t\t\t\t   GetCurrentTransactionStartTimestamp());\n\t}\n\telse\n\t\tts_bgw_job_stat_upsert_next_start(job_id, GetCurrentTransactionStartTimestamp());\n\n\telog(DEBUG1, \"the %s job is scheduled to run again immediately\", job_name);\n}\n\n/*\n * Returns the ID of a chunk to reorder. Eligible chunks must be at least the\n * 3rd newest chunk in the hypertable (not entirely exact because we use the number\n * of dimension slices as a proxy for the number of chunks),\n * not compressed, not dropped and hasn't been reordered recently.\n * For this version of automatic reordering, \"not reordered\n * recently\" means the chunk has not been reordered at all. This information\n * is available in the bgw_policy_chunk_stats metadata table.\n */\nstatic int\nget_chunk_id_to_reorder(int32 job_id, Hypertable *ht)\n{\n\tconst Dimension *time_dimension = hyperspace_get_open_dimension(ht->space, 0);\n\tconst DimensionSlice *nth_dimension =\n\t\tts_dimension_slice_nth_latest_slice(time_dimension->fd.id,\n\t\t\t\t\t\t\t\t\t\t\tREORDER_SKIP_RECENT_DIM_SLICES_N);\n\n\tif (!nth_dimension)\n\t\treturn -1;\n\n\tAssert(time_dimension != NULL);\n\n\treturn ts_dimension_slice_oldest_valid_chunk_for_reorder(job_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t time_dimension->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t BTLessEqualStrategyNumber,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t nth_dimension->fd.range_start,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t InvalidStrategy,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t -1);\n}\n\n/*\n * returns now() - window as partitioning type datum\n */\nstatic Datum\nget_window_boundary(const Dimension *dim, const Jsonb *config, int64 (*int_getter)(const Jsonb *),\n\t\t\t\t\tInterval *(*interval_getter)(const Jsonb *) )\n{\n\tOid partitioning_type = ts_dimension_get_partition_type(dim);\n\n\tif (IS_INTEGER_TYPE(partitioning_type))\n\t{\n\t\tOid now_func = ts_get_integer_now_func(dim, false);\n\n\t\t/* If \"now_func\" is provided then we use that for calculating the window. */\n\t\tif (OidIsValid(now_func))\n\t\t{\n\t\t\tint64 res, lag = int_getter(config);\n\t\t\tres = ts_sub_integer_from_now(lag, partitioning_type, now_func);\n\t\t\treturn Int64GetDatum(res);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/*\n\t\t\t * Otherwise, the interval value can be returned without subtracting it\n\t\t\t * from now().\n\t\t\t */\n\t\t\tInterval *lag = interval_getter(config);\n\t\t\treturn IntervalPGetDatum(lag);\n\t\t}\n\t}\n\telse\n\t{\n\t\tInterval *lag = interval_getter(config);\n\t\t/*\n\t\t * For UUID (v7) partitioned hypertables, drop_chunks expects TIMESTAMPTZ\n\t\t * input, so we compute the boundary as TIMESTAMPTZ instead of UUID.\n\t\t */\n\t\tif (IS_UUID_TYPE(partitioning_type))\n\t\t\tpartitioning_type = TIMESTAMPTZOID;\n\t\treturn ts_subtract_interval_from_now(lag, partitioning_type);\n\t}\n}\n\nstatic List *\nget_chunk_to_recompress(const Dimension *dim, const Jsonb *config)\n{\n\tOid partitioning_type = ts_dimension_get_partition_type(dim);\n\tOid boundary_type = partitioning_type;\n\tStrategyNumber end_strategy = BTLessStrategyNumber;\n\tint32 numchunks = policy_compression_get_maxchunks_per_job(config);\n\n\t/*\n\t * For UUID-partitioned hypertables, the boundary is computed as TIMESTAMPTZ\n\t * by get_window_boundary, so we need to use TIMESTAMPTZOID for conversion.\n\t */\n\tif (IS_UUID_TYPE(partitioning_type))\n\t\tboundary_type = TIMESTAMPTZOID;\n\n\tDatum boundary = get_window_boundary(dim,\n\t\t\t\t\t\t\t\t\t\t config,\n\t\t\t\t\t\t\t\t\t\t policy_recompression_get_recompress_after_int,\n\t\t\t\t\t\t\t\t\t\t policy_recompression_get_recompress_after_interval);\n\n\treturn ts_dimension_slice_get_chunkids_to_compress(dim->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   InvalidStrategy, /*start_strategy*/\n\t\t\t\t\t\t\t\t\t\t\t\t\t   -1,\t\t\t\t/*start_value*/\n\t\t\t\t\t\t\t\t\t\t\t\t\t   end_strategy,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   ts_time_value_to_internal(boundary,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t boundary_type),\n\t\t\t\t\t\t\t\t\t\t\t\t\t   false,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   true,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   numchunks);\n}\n\nstatic void\ncheck_valid_index(Hypertable *ht, const char *index_name)\n{\n\tOid index_oid;\n\tHeapTuple idxtuple;\n\tForm_pg_index index_form;\n\n\tindex_oid = ts_get_relation_relid(NameStr(ht->fd.schema_name), (char *) index_name, true);\n\tidxtuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid));\n\tif (!HeapTupleIsValid(idxtuple))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"reorder index not found\"),\n\t\t\t\t errdetail(\"The index \\\"%s\\\" could not be found\", index_name)));\n\n\tindex_form = (Form_pg_index) GETSTRUCT(idxtuple);\n\tif (index_form->indrelid != ht->main_table_relid)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid reorder index\"),\n\t\t\t\t errhint(\"The reorder index must by an index on hypertable \\\"%s\\\".\",\n\t\t\t\t\t\t NameStr(ht->fd.table_name))));\n\n\tReleaseSysCache(idxtuple);\n}\n\nbool\npolicy_reorder_execute(int32 job_id, Jsonb *config)\n{\n\tint chunk_id;\n\tChunk *chunk;\n\tPolicyReorderData policy;\n\n\tpolicy_reorder_read_and_validate_config(config, &policy);\n\n\t/* Find a chunk to reorder in the selected hypertable */\n\tchunk_id = get_chunk_id_to_reorder(job_id, policy.hypertable);\n\n\tif (chunk_id == -1)\n\t{\n\t\telog(NOTICE,\n\t\t\t \"no chunks need reordering for hypertable %s.%s\",\n\t\t\t NameStr(policy.hypertable->fd.schema_name),\n\t\t\t NameStr(policy.hypertable->fd.table_name));\n\t\treturn true;\n\t}\n\n\t/*\n\t * NOTE: We pass the Oid of the hypertable's index, and the true reorder\n\t * function should translate this to the Oid of the index on the specific\n\t * chunk.\n\t */\n\tchunk = ts_chunk_get_by_id(chunk_id, false);\n\telog(DEBUG1,\n\t\t \"reordering chunk %s.%s\",\n\t\t NameStr(chunk->fd.schema_name),\n\t\t NameStr(chunk->fd.table_name));\n\treorder_chunk(chunk->table_id, policy.index_relid, false, InvalidOid, InvalidOid, InvalidOid);\n\telog(DEBUG1,\n\t\t \"completed reordering chunk %s.%s\",\n\t\t NameStr(chunk->fd.schema_name),\n\t\t NameStr(chunk->fd.table_name));\n\n\t/* Now update chunk_stats table */\n\tts_bgw_policy_chunk_stats_record_job_run(job_id, chunk_id, ts_timer_get_current_timestamp());\n\n\tif (get_chunk_id_to_reorder(job_id, policy.hypertable) != -1)\n\t\tenable_fast_restart(job_id, \"reorder\");\n\n\treturn true;\n}\n\nvoid\npolicy_reorder_read_and_validate_config(Jsonb *config, PolicyReorderData *policy)\n{\n\tint32 htid = policy_config_get_hypertable_id(config);\n\tHypertable *ht = ts_hypertable_get_by_id(htid);\n\n\tif (!ht)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"configuration hypertable id %d not found\", htid)));\n\n\tconst char *index_name = policy_reorder_get_index_name(config);\n\tcheck_valid_index(ht, index_name);\n\n\tif (policy)\n\t{\n\t\tpolicy->hypertable = ht;\n\t\tpolicy->index_relid =\n\t\t\tts_get_relation_relid(NameStr(ht->fd.schema_name), (char *) index_name, false);\n\t}\n}\n\nbool\npolicy_retention_execute(int32 job_id, Jsonb *config)\n{\n\tPolicyRetentionData policy_data;\n\tbool verbose_log;\n\n\tpolicy_retention_read_and_validate_config(config, &policy_data);\n\n\tverbose_log = policy_get_verbose_log(config);\n\tif (verbose_log)\n\t\tlog_retention_boundary(LOG, &policy_data, \"applying retention policy to hypertable\");\n\n\tchunk_invoke_drop_chunks(policy_data.object_relid,\n\t\t\t\t\t\t\t policy_data.boundary,\n\t\t\t\t\t\t\t policy_data.boundary_type,\n\t\t\t\t\t\t\t policy_data.use_creation_time);\n\n\treturn true;\n}\n\nvoid\npolicy_retention_read_and_validate_config(Jsonb *config, PolicyRetentionData *policy_data)\n{\n\tOid object_relid;\n\tHypertable *hypertable;\n\tCache *hcache;\n\tconst Dimension *open_dim;\n\tDatum boundary;\n\tOid boundary_type;\n\tContinuousAgg *cagg;\n\tInterval *(*interval_getter)(const Jsonb *);\n\tinterval_getter = policy_retention_get_drop_after_interval;\n\tbool use_creation_time = false;\n\n\tobject_relid = ts_hypertable_id_to_relid(policy_config_get_hypertable_id(config), false);\n\thypertable = ts_hypertable_cache_get_cache_and_entry(object_relid, CACHE_FLAG_NONE, &hcache);\n\n\topen_dim = get_open_dimension_for_hypertable(hypertable, false);\n\n\t/* if dim is NULL, then it should be an INTEGER partition with no int_now function */\n\tif (open_dim == NULL)\n\t{\n\t\tOid partition_type;\n\n\t\topen_dim = hyperspace_get_open_dimension(hypertable->space, 0);\n\t\tpartition_type = ts_dimension_get_partition_type(open_dim);\n\t\tif (!IS_INTEGER_TYPE(partition_type))\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"incorrect partition type %d.  Expected integer\", partition_type)));\n\n\t\t/* if there's no int_now function the boundary is considered as an INTERVAL */\n\t\tboundary_type = INTERVALOID;\n\t\tinterval_getter = policy_retention_get_drop_created_before_interval;\n\t\tuse_creation_time = true;\n\t}\n\telse\n\t{\n\t\tboundary_type = ts_dimension_get_partition_type(open_dim);\n\t\t/*\n\t\t * For UUID (v7) partitioned hypertables, drop_chunks expects TIMESTAMPTZ\n\t\t * input (the timestamp is extracted from the UUID internally). We need to\n\t\t * pass the boundary as TIMESTAMPTZ, not as UUID.\n\t\t */\n\t\tif (IS_UUID_TYPE(boundary_type))\n\t\t\tboundary_type = TIMESTAMPTZOID;\n\t}\n\n\tboundary =\n\t\tget_window_boundary(open_dim, config, policy_retention_get_drop_after_int, interval_getter);\n\n\t/* We need to do a reverse lookup here since the given hypertable might be\n\t   a materialized hypertable, and thus need to call drop_chunks on the\n\t   continuous aggregate instead. */\n\tcagg = ts_continuous_agg_find_by_mat_hypertable_id(hypertable->fd.id, true);\n\tif (cagg)\n\t{\n\t\tobject_relid = ts_get_relation_relid(NameStr(cagg->data.user_view_schema),\n\t\t\t\t\t\t\t\t\t\t\t NameStr(cagg->data.user_view_name),\n\t\t\t\t\t\t\t\t\t\t\t false);\n\t}\n\n\tts_cache_release(&hcache);\n\n\tif (policy_data)\n\t{\n\t\tpolicy_data->object_relid = object_relid;\n\t\tpolicy_data->boundary = boundary;\n\t\tpolicy_data->boundary_type = boundary_type;\n\t\tpolicy_data->use_creation_time = use_creation_time;\n\t}\n}\n\nbool\npolicy_refresh_cagg_execute(int32 job_id, Jsonb *config)\n{\n\tPolicyContinuousAggData policy_data;\n\n\tStringInfoData str;\n\tinitStringInfo(&str);\n\tJsonbToCStringIndent(&str, &config->root, VARSIZE(config));\n\n\tpolicy_refresh_cagg_read_and_validate_config(config, &policy_data);\n\tbool extend_last_bucket = !policy_refresh_cagg_check_if_last_policy(&policy_data);\n\n\tbool enable_osm_reads_old = ts_guc_enable_osm_reads;\n\n\tif (!policy_data.include_tiered_data_isnull)\n\t{\n\t\tSetConfigOption(\"timescaledb.enable_tiered_reads\",\n\t\t\t\t\t\tpolicy_data.include_tiered_data ? \"on\" : \"off\",\n\t\t\t\t\t\tPGC_USERSET,\n\t\t\t\t\t\tPGC_S_SESSION);\n\t}\n\n\tContinuousAggRefreshContext context = { .callctx = CAGG_REFRESH_POLICY };\n\n\t/* Try to split window range into a list of ranges */\n\tList *refresh_window_list =\n\t\tcontinuous_agg_split_refresh_window(policy_data.cagg,\n\t\t\t\t\t\t\t\t\t\t\t&policy_data.refresh_window,\n\t\t\t\t\t\t\t\t\t\t\tpolicy_data.buckets_per_batch,\n\t\t\t\t\t\t\t\t\t\t\tpolicy_data.refresh_newest_first);\n\tif (refresh_window_list == NIL)\n\t\trefresh_window_list = lappend(refresh_window_list, &policy_data.refresh_window);\n\telse\n\t\tcontext.callctx = CAGG_REFRESH_POLICY_BATCHED;\n\n\tcontext.number_of_batches = list_length(refresh_window_list);\n\n\tListCell *lc;\n\tint32 processing_batch = 0;\n\tforeach (lc, refresh_window_list)\n\t{\n\t\tInternalTimeRange *refresh_window = (InternalTimeRange *) lfirst(lc);\n\t\telog(DEBUG1,\n\t\t\t \"refreshing continuous aggregate \\\"%s\\\" from %s to %s\",\n\t\t\t NameStr(policy_data.cagg->data.user_view_name),\n\t\t\t ts_internal_to_time_string(refresh_window->start, refresh_window->type),\n\t\t\t ts_internal_to_time_string(refresh_window->end, refresh_window->type));\n\n\t\tcontext.processing_batch = ++processing_batch;\n\t\tcontinuous_agg_refresh_internal(policy_data.cagg,\n\t\t\t\t\t\t\t\t\t\trefresh_window,\n\t\t\t\t\t\t\t\t\t\tcontext,\n\t\t\t\t\t\t\t\t\t\trefresh_window->start_isnull,\n\t\t\t\t\t\t\t\t\t\trefresh_window->end_isnull,\n\t\t\t\t\t\t\t\t\t\t(context.callctx != CAGG_REFRESH_POLICY_BATCHED),\n\t\t\t\t\t\t\t\t\t\tfalse, /* force */\n\t\t\t\t\t\t\t\t\t\tpolicy_data.process_hypertable_invalidations,\n\t\t\t\t\t\t\t\t\t\textend_last_bucket);\n\t\tif (processing_batch >= policy_data.max_batches_per_execution &&\n\t\t\tprocessing_batch < context.number_of_batches &&\n\t\t\tpolicy_data.max_batches_per_execution > 0)\n\t\t{\n\t\t\telog(LOG,\n\t\t\t\t \"reached maximum number of batches per execution (%d), batches not processed (%d)\",\n\t\t\t\t policy_data.max_batches_per_execution,\n\t\t\t\t context.number_of_batches - processing_batch);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (!policy_data.include_tiered_data_isnull)\n\t{\n\t\tSetConfigOption(\"timescaledb.enable_tiered_reads\",\n\t\t\t\t\t\tenable_osm_reads_old ? \"on\" : \"off\",\n\t\t\t\t\t\tPGC_USERSET,\n\t\t\t\t\t\tPGC_S_SESSION);\n\t}\n\n\treturn true;\n}\n\nvoid\npolicy_refresh_cagg_read_and_validate_config(Jsonb *config, PolicyContinuousAggData *policy_data)\n{\n\tint32 materialization_id;\n\tHypertable *mat_ht;\n\tconst Dimension *open_dim;\n\tOid dim_type;\n\tint64 refresh_start, refresh_end;\n\tint32 buckets_per_batch, max_batches_per_execution;\n\tbool start_isnull, end_isnull;\n\tbool include_tiered_data, include_tiered_data_isnull;\n\tbool refresh_newest_first;\n\n\tmaterialization_id = policy_continuous_aggregate_get_mat_hypertable_id(config);\n\tmat_ht = ts_hypertable_get_by_id(materialization_id);\n\n\tif (!mat_ht)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"configuration materialization hypertable id %d not found\",\n\t\t\t\t\t\tmaterialization_id)));\n\n\tContinuousAgg *cagg = ts_continuous_agg_find_by_mat_hypertable_id(materialization_id, false);\n\n\topen_dim = get_open_dimension_for_hypertable(mat_ht, true);\n\tdim_type = ts_dimension_get_partition_type(open_dim);\n\trefresh_start = policy_refresh_cagg_get_refresh_start(cagg, open_dim, config, &start_isnull);\n\trefresh_end = policy_refresh_cagg_get_refresh_end(open_dim, config, &end_isnull);\n\n\tif (refresh_start >= refresh_end)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid refresh window\"),\n\t\t\t\t errdetail(\"start_offset: %s, end_offset: %s\",\n\t\t\t\t\t\t   ts_internal_to_time_string(refresh_start, dim_type),\n\t\t\t\t\t\t   ts_internal_to_time_string(refresh_end, dim_type)),\n\t\t\t\t errhint(\"The start of the window must be before the end.\")));\n\n\tinclude_tiered_data =\n\t\tpolicy_refresh_cagg_get_include_tiered_data(config, &include_tiered_data_isnull);\n\n\tbuckets_per_batch = policy_refresh_cagg_get_buckets_per_batch(config);\n\n\tif (buckets_per_batch < 0)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid buckets per batch\"),\n\t\t\t\t errdetail(\"buckets_per_batch: %d\", buckets_per_batch),\n\t\t\t\t errhint(\"The buckets per batch should be greater than or equal to zero.\")));\n\n\tmax_batches_per_execution = policy_refresh_cagg_get_max_batches_per_execution(config);\n\n\tif (max_batches_per_execution < 0)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid max batches per execution\"),\n\t\t\t\t errdetail(\"max_batches_per_execution: %d\", max_batches_per_execution),\n\t\t\t\t errhint(\n\t\t\t\t\t \"The max batches per execution should be greater than or equal to zero.\")));\n\n\trefresh_newest_first = policy_refresh_cagg_get_refresh_newest_first(config);\n\n\tbool process_hypertable_invalidations_found;\n\tbool process_hypertable_invalidations =\n\t\tts_jsonb_get_bool_field(config,\n\t\t\t\t\t\t\t\tPOL_REFRESH_CONF_KEY_PROCESS_HYPERTABLE_INVALIDATIONS,\n\t\t\t\t\t\t\t\t&process_hypertable_invalidations_found);\n\tif (policy_data)\n\t{\n\t\tpolicy_data->refresh_window.type = dim_type;\n\t\tpolicy_data->refresh_window.start = refresh_start;\n\t\tpolicy_data->refresh_window.start_isnull = start_isnull;\n\t\tpolicy_data->refresh_window.end = refresh_end;\n\t\tpolicy_data->refresh_window.end_isnull = end_isnull;\n\t\tpolicy_data->cagg = cagg;\n\t\tpolicy_data->include_tiered_data = include_tiered_data;\n\t\tpolicy_data->include_tiered_data_isnull = include_tiered_data_isnull;\n\t\tpolicy_data->buckets_per_batch = buckets_per_batch;\n\t\tpolicy_data->max_batches_per_execution = max_batches_per_execution;\n\t\tpolicy_data->refresh_newest_first = refresh_newest_first;\n\t\tpolicy_data->process_hypertable_invalidations =\n\t\t\t!process_hypertable_invalidations_found || process_hypertable_invalidations;\n\t}\n}\n\n/* Read configuration for compression job from config object. */\nvoid\npolicy_compression_read_and_validate_config(Jsonb *config, PolicyCompressionData *policy_data)\n{\n\tOid table_relid = ts_hypertable_id_to_relid(policy_config_get_hypertable_id(config), false);\n\tCache *hcache;\n\tHypertable *hypertable =\n\t\tts_hypertable_cache_get_cache_and_entry(table_relid, CACHE_FLAG_NONE, &hcache);\n\tif (policy_data)\n\t{\n\t\tpolicy_data->hypertable = hypertable;\n\t\tpolicy_data->hcache = hcache;\n\t}\n}\n\nvoid\npolicy_recompression_read_and_validate_config(Jsonb *config, PolicyCompressionData *policy_data)\n{\n\tOid table_relid = ts_hypertable_id_to_relid(policy_config_get_hypertable_id(config), false);\n\tCache *hcache;\n\tHypertable *hypertable =\n\t\tts_hypertable_cache_get_cache_and_entry(table_relid, CACHE_FLAG_NONE, &hcache);\n\tif (policy_data)\n\t{\n\t\tpolicy_data->hypertable = hypertable;\n\t\tpolicy_data->hcache = hcache;\n\t}\n}\n\nbool\npolicy_recompression_execute(int32 job_id, Jsonb *config)\n{\n\tList *chunkid_lst;\n\tListCell *lc;\n\tconst Dimension *dim;\n\tPolicyCompressionData policy_data;\n\tbool used_portalcxt = false;\n\tMemoryContext saved_cxt, multitxn_cxt;\n\n\tpolicy_recompression_read_and_validate_config(config, &policy_data);\n\tdim = hyperspace_get_open_dimension(policy_data.hypertable->space, 0);\n\t/* we want the chunk id list to survive across transactions. So alloc in\n\t * a different context\n\t */\n\tif (PortalContext)\n\t{\n\t\t/*if we have a portal context use that - it will get freed automatically*/\n\t\tmultitxn_cxt = PortalContext;\n\t\tused_portalcxt = true;\n\t}\n\telse\n\t{\n\t\t/* background worker job does not go via usual CALL path, so we do\n\t\t * not have a PortalContext */\n\t\tmultitxn_cxt =\n\t\t\tAllocSetContextCreate(TopMemoryContext, \"CompressionJobCxt\", ALLOCSET_DEFAULT_SIZES);\n\t}\n\tsaved_cxt = MemoryContextSwitchTo(multitxn_cxt);\n\tchunkid_lst = get_chunk_to_recompress(dim, config);\n\tMemoryContextSwitchTo(saved_cxt);\n\n\tif (!chunkid_lst)\n\t{\n\t\telog(NOTICE,\n\t\t\t \"no chunks for hypertable \\\"%s.%s\\\" that satisfy recompress chunk policy\",\n\t\t\t NameStr(policy_data.hypertable->fd.schema_name),\n\t\t\t NameStr(policy_data.hypertable->fd.table_name));\n\t\tts_cache_release(&policy_data.hcache);\n\t\tif (!used_portalcxt)\n\t\t\tMemoryContextDelete(multitxn_cxt);\n\t\treturn true;\n\t}\n\tts_cache_release(&policy_data.hcache);\n\tif (ActiveSnapshotSet())\n\t\tPopActiveSnapshot();\n\t/* process each chunk in a new transaction */\n\tforeach (lc, chunkid_lst)\n\t{\n\t\tCommitTransactionCommand();\n\t\tStartTransactionCommand();\n\t\tint32 chunkid = lfirst_int(lc);\n\t\tChunk *chunk = ts_chunk_get_by_id(chunkid, true);\n\t\tAssert(chunk);\n\t\tif (!ts_chunk_needs_recompression(chunk))\n\t\t\tcontinue;\n\n\t\ttsl_compress_chunk_wrapper(chunk, true, false);\n\n\t\telog(LOG,\n\t\t\t \"completed recompressing chunk \\\"%s.%s\\\"\",\n\t\t\t NameStr(chunk->fd.schema_name),\n\t\t\t NameStr(chunk->fd.table_name));\n\t}\n\n\telog(DEBUG1, \"job %d completed recompressing chunk\", job_id);\n\treturn true;\n}\n\nvoid\npolicy_process_hyper_inval_read_and_validate_config(Jsonb *config,\n\t\t\t\t\t\t\t\t\t\t\t\t\tPolicyMoveHyperInvalData *policy_data)\n{\n\tint32 hypertable_id = policy_config_get_hypertable_id(config);\n\tOid table_relid = ts_hypertable_id_to_relid(hypertable_id, true);\n\n\tif (!OidIsValid(table_relid))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"configuration hypertable id %d not found\", hypertable_id)));\n\n\tCache *hcache;\n\tHypertable *hypertable =\n\t\tts_hypertable_cache_get_cache_and_entry(table_relid, CACHE_FLAG_NONE, &hcache);\n\tif (policy_data)\n\t{\n\t\tpolicy_data->hypertable = hypertable;\n\t\tpolicy_data->hcache = hcache;\n\t}\n\telse\n\t{\n\t\tts_cache_release(&hcache);\n\t}\n}\n\nbool\npolicy_process_hyper_inval_execute(int32 job_id, Jsonb *config)\n{\n\tPolicyMoveHyperInvalData policy_data;\n\n\tpolicy_process_hyper_inval_read_and_validate_config(config, &policy_data);\n\n\tconst Dimension *dim = hyperspace_get_open_dimension(policy_data.hypertable->space, 0);\n\tOid dimtype = ts_dimension_get_partition_type(dim);\n\tint32 hypertable_id = policy_data.hypertable->fd.id;\n\n\t/* We serialized on the invalidation threshold, so we get and lock it. */\n\tinvalidation_threshold_get(hypertable_id);\n\tinvalidation_process_hypertable_log(hypertable_id, dimtype);\n\tts_cache_release(&policy_data.hcache);\n\n\treturn true;\n}\n\nstatic void\njob_execute_function(FuncExpr *funcexpr)\n{\n\tbool isnull;\n\n\tEState *estate = CreateExecutorState();\n\tExprContext *econtext = CreateExprContext(estate);\n\n\tExprState *es = ExecPrepareExpr((Expr *) funcexpr, estate);\n\tExecEvalExpr(es, econtext, &isnull);\n\n\tFreeExprContext(econtext, true);\n\tFreeExecutorState(estate);\n}\n\nstatic void\njob_execute_procedure(FuncExpr *funcexpr)\n{\n\tCallStmt *call = makeNode(CallStmt);\n\tcall->funcexpr = funcexpr;\n\tDestReceiver *dest = CreateDestReceiver(DestNone);\n\t/* we don't need to create proper param list cause we pass in all arguments as Const */\n\tParamListInfo params = makeParamList(0);\n\tExecuteCallStmt(call, params, false, dest);\n}\n\n/*\n * Execute the job.\n *\n * This function can be called both from a portal and from a background\n * worker.\n */\nbool\njob_execute(BgwJob *job)\n{\n\tConst *arg1, *arg2;\n\tbool portal_created = false;\n\tchar prokind;\n\tOid proc;\n\tFuncExpr *funcexpr;\n\tMemoryContext parent_ctx = CurrentMemoryContext;\n\tStringInfoData query;\n\tPortal portal = ActivePortal;\n\n\t/* Check for work_mem setting in config and apply it */\n\tif (job->fd.config)\n\t{\n\t\tchar *work_mem_setting = ts_jsonb_get_str_field(job->fd.config, \"work_mem\");\n\t\tif (work_mem_setting != NULL)\n\t\t{\n\t\t\tSetConfigOption(\"work_mem\", work_mem_setting, PGC_USERSET, PGC_S_SESSION);\n\t\t}\n\t}\n\n\tif (job->fd.config)\n\t\telog(DEBUG1,\n\t\t\t \"Executing %s with parameters %s\",\n\t\t\t NameStr(job->fd.proc_name),\n\t\t\t DatumGetCString(DirectFunctionCall1(jsonb_out, JsonbPGetDatum(job->fd.config))));\n\telse\n\t\telog(DEBUG1, \"Executing %s with no parameters\", NameStr(job->fd.proc_name));\n\t/* Create a portal if there's no active */\n\tif (!PortalIsValid(portal))\n\t{\n\t\tportal_created = true;\n\t\tportal = CreatePortal(\"\", true, true);\n\t\tportal->visible = false;\n\t\tportal->resowner = CurrentResourceOwner;\n\t\tActivePortal = portal;\n\t\tPortalContext = portal->portalContext;\n\n\t\tStartTransactionCommand();\n\t\tEnsurePortalSnapshotExists();\n\t}\n\n#ifdef USE_TELEMETRY\n\t/* The telemetry job has a separate code path and since we can reach this\n\t * code also when using run_job(), we have a special case here. This will\n\t * not be triggered when executed from ts_bgw_job_execute(). */\n\tif (ts_is_telemetry_job(job))\n\t{\n\t\t/*\n\t\t * In the first 12 hours, we want telemetry to ping every\n\t\t * hour. After that initial period, we default to the\n\t\t * schedule_interval listed in the job table.\n\t\t */\n\t\tInterval one_hour = { .time = 1 * USECS_PER_HOUR };\n\t\treturn ts_bgw_job_run_and_set_next_start(job,\n\t\t\t\t\t\t\t\t\t\t\t\t ts_telemetry_main_wrapper,\n\t\t\t\t\t\t\t\t\t\t\t\t TELEMETRY_INITIAL_NUM_RUNS,\n\t\t\t\t\t\t\t\t\t\t\t\t &one_hour,\n\t\t\t\t\t\t\t\t\t\t\t\t /* atomic */ false,\n\t\t\t\t\t\t\t\t\t\t\t\t /* mark */ true);\n\t}\n#endif\n\n\tproc = ts_bgw_job_get_funcid(job);\n\tprokind = get_func_prokind(proc);\n\n\t/*\n\t * We need to switch back to parent MemoryContext as StartTransactionCommand\n\t * switched to CurTransactionContext and this context will be destroyed\n\t * on CommitTransactionCommand which may be too short-lived if a policy\n\t * has its own transaction handling.\n\t */\n\tMemoryContextSwitchTo(parent_ctx);\n\targ1 = makeConst(INT4OID, -1, InvalidOid, 4, Int32GetDatum(job->fd.id), false, true);\n\tif (job->fd.config == NULL)\n\t\targ2 = makeNullConst(JSONBOID, -1, InvalidOid);\n\telse\n\t\targ2 =\n\t\t\tmakeConst(JSONBOID, -1, InvalidOid, -1, JsonbPGetDatum(job->fd.config), false, false);\n\n\tfuncexpr = makeFuncExpr(proc,\n\t\t\t\t\t\t\tVOIDOID,\n\t\t\t\t\t\t\tlist_make2(arg1, arg2),\n\t\t\t\t\t\t\tInvalidOid,\n\t\t\t\t\t\t\tInvalidOid,\n\t\t\t\t\t\t\tCOERCE_EXPLICIT_CALL);\n\n\t/* Here we create a query string from the function/procedure name that we\n\t * are calling. We do not update the status after the execution has\n\t * finished since this is wrapped inside the code that starts and stops\n\t * any job, not just custom jobs. We just provide more detailed\n\t * information here that we are actually calling a specific custom\n\t * function. */\n\tinitStringInfo(&query);\n\tappendStringInfo(&query,\n\t\t\t\t\t \"CALL %s.%s()\",\n\t\t\t\t\t quote_identifier(NameStr(job->fd.proc_schema)),\n\t\t\t\t\t quote_identifier(NameStr(job->fd.proc_name)));\n\tpgstat_report_activity(STATE_RUNNING, query.data);\n\n\tswitch (prokind)\n\t{\n\t\tcase PROKIND_FUNCTION:\n\t\t\tjob_execute_function(funcexpr);\n\t\t\tbreak;\n\t\tcase PROKIND_PROCEDURE:\n\t\t\tjob_execute_procedure(funcexpr);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg(\"unsupported function type\")));\n\t\t\tbreak;\n\t}\n\n\t/* Drop portal if it was created */\n\tif (portal_created)\n\t{\n\t\tif (ActiveSnapshotSet())\n\t\t\tPopActiveSnapshot();\n\t\tCommitTransactionCommand();\n\t\tPortalDrop(portal, false);\n\t\tActivePortal = NULL;\n\t\tPortalContext = NULL;\n\t}\n\n\treturn true;\n}\n"
  },
  {
    "path": "tsl/src/bgw_policy/job.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <utils/jsonb.h>\n\n#include <bgw/job.h>\n#include <hypertable.h>\n\n#include \"bgw_policy/chunk_stats.h\"\n#include \"cache.h\"\n#include \"continuous_aggs/materialize.h\"\n\n/* Add config keys common across job types here */\n#define CONFIG_KEY_VERBOSE_LOG \"verbose_log\" /*used only by retention now*/\n\ntypedef struct PolicyMoveHyperInvalData\n{\n\tHypertable *hypertable;\n\tCache *hcache;\n} PolicyMoveHyperInvalData;\n\ntypedef struct PolicyReorderData\n{\n\tHypertable *hypertable;\n\tOid index_relid;\n} PolicyReorderData;\n\ntypedef struct PolicyRetentionData\n{\n\tOid object_relid;\n\tDatum boundary;\n\tOid boundary_type;\n\tbool use_creation_time;\n} PolicyRetentionData;\n\ntypedef struct PolicyContinuousAggData\n{\n\tInternalTimeRange refresh_window;\n\tContinuousAgg *cagg;\n\tbool include_tiered_data;\n\tbool include_tiered_data_isnull;\n\tint32 buckets_per_batch;\n\tint32 max_batches_per_execution;\n\tbool refresh_newest_first;\n\tbool process_hypertable_invalidations;\n} PolicyContinuousAggData;\n\ntypedef struct PolicyCompressionData\n{\n\tHypertable *hypertable;\n\tCache *hcache;\n} PolicyCompressionData;\n\n/* Reorder function type. Necessary for testing */\ntypedef void (*reorder_func)(Oid tableOid, Oid indexOid, bool verbose, Oid wait_id,\n\t\t\t\t\t\t\t Oid destination_tablespace, Oid index_tablespace);\n\n/* Functions exposed only for testing */\nextern bool policy_reorder_execute(int32 job_id, Jsonb *config);\nextern bool policy_retention_execute(int32 job_id, Jsonb *config);\nextern bool policy_refresh_cagg_execute(int32 job_id, Jsonb *config);\nextern bool policy_process_hyper_inval_execute(int32 job_id, Jsonb *config);\nextern bool policy_recompression_execute(int32 job_id, Jsonb *config);\nextern void policy_reorder_read_and_validate_config(Jsonb *config, PolicyReorderData *policy_data);\nextern void policy_retention_read_and_validate_config(Jsonb *config,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  PolicyRetentionData *policy_data);\nextern void policy_refresh_cagg_read_and_validate_config(Jsonb *config,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t PolicyContinuousAggData *policy_data);\nextern void\npolicy_process_hyper_inval_read_and_validate_config(Jsonb *config,\n\t\t\t\t\t\t\t\t\t\t\t\t\tPolicyMoveHyperInvalData *policy_data);\nextern void policy_compression_read_and_validate_config(Jsonb *config,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tPolicyCompressionData *policy_data);\nextern void policy_recompression_read_and_validate_config(Jsonb *config,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  PolicyCompressionData *policy_data);\nextern bool job_execute(BgwJob *job);\n"
  },
  {
    "path": "tsl/src/bgw_policy/job_api.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <funcapi.h>\n#include <miscadmin.h>\n#include <utils/acl.h>\n#include <utils/builtins.h>\n\n#include <parser/parse_func.h>\n#include <parser/parser.h>\n\n#include <bgw/job.h>\n#include <bgw/job_stat.h>\n\n#include \"bgw/timer.h\"\n#include \"debug_assert.h\"\n#include \"hypertable_cache.h\"\n#include \"job.h\"\n#include \"job_api.h\"\n#include \"policies_v2.h\"\n\n/* Default max runtime for a custom job is unlimited for now */\n#define DEFAULT_MAX_RUNTIME 0\n\n/* Default retry period for reorder_jobs is currently 5 minutes */\n#define DEFAULT_RETRY_PERIOD (5 * USECS_PER_MINUTE)\n\n#define ALTER_JOB_NUM_COLS 13\n\n/*\n * This function ensures that the check function has the required signature\n * @param check A valid Oid\n */\nstatic inline void\nvalidate_check_signature(Oid check)\n{\n\tOid proc = InvalidOid;\n\tObjectWithArgs *object;\n\tNameData check_name = { .data = { 0 } };\n\tNameData check_schema = { .data = { 0 } };\n\n\tnamestrcpy(&check_schema, get_namespace_name(get_func_namespace(check)));\n\tnamestrcpy(&check_name, get_func_name(check));\n\n\tobject = makeNode(ObjectWithArgs);\n\tobject->objname =\n\t\tlist_make2(makeString(NameStr(check_schema)), makeString(NameStr(check_name)));\n\tobject->objargs = list_make1(SystemTypeName(\"jsonb\"));\n\tproc = LookupFuncWithArgs(OBJECT_ROUTINE, object, true);\n\n\tif (!OidIsValid(proc))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"function or procedure %s.%s(config jsonb) not found\",\n\t\t\t\t\t\tNameStr(check_schema),\n\t\t\t\t\t\tNameStr(check_name)),\n\t\t\t\t errhint(\"The check function's signature must be (config jsonb).\")));\n}\n\n/*\n * CREATE FUNCTION add_job(\n * 0 proc REGPROC,\n * 1 schedule_interval INTERVAL,\n * 2 config JSONB DEFAULT NULL,\n * 3 initial_start TIMESTAMPTZ DEFAULT NULL,\n * 4 scheduled BOOL DEFAULT true\n * 5 check_config REGPROC DEFAULT NULL\n * 6 fixed_schedule BOOL DEFAULT TRUE\n * 7 timezone TEXT DEFAULT NULL\n * 8 job_name TEXT DEFAULT NULL\n * ) RETURNS INTEGER\n */\nDatum\njob_add(PG_FUNCTION_ARGS)\n{\n\tNameData application_name;\n\tNameData proc_name;\n\tNameData proc_schema;\n\tNameData check_name = { .data = { 0 } };\n\tNameData check_schema = { .data = { 0 } };\n\tInterval max_runtime = { .time = DEFAULT_MAX_RUNTIME };\n\tInterval retry_period = { .time = DEFAULT_RETRY_PERIOD };\n\tint32 job_id;\n\tchar *func_name = NULL;\n\tchar *check_name_str = NULL;\n\tchar *valid_timezone = NULL;\n\tTimestampTz initial_start = PG_ARGISNULL(3) ? DT_NOBEGIN : PG_GETARG_TIMESTAMPTZ(3);\n\n\tOid owner = GetUserId();\n\tOid proc = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\tInterval *schedule_interval = PG_ARGISNULL(1) ? NULL : PG_GETARG_INTERVAL_P(1);\n\tJsonb *config = PG_ARGISNULL(2) ? NULL : PG_GETARG_JSONB_P(2);\n\tbool scheduled = PG_ARGISNULL(4) ? true : PG_GETARG_BOOL(4);\n\tOid check = PG_ARGISNULL(5) ? InvalidOid : PG_GETARG_OID(5);\n\tbool fixed_schedule = PG_ARGISNULL(6) ? true : PG_GETARG_BOOL(6);\n\ttext *timezone = PG_ARGISNULL(7) ? NULL : PG_GETARG_TEXT_PP(7);\n\t/* verify it's a valid timezone */\n\tif (timezone != NULL)\n\t\tvalid_timezone = ts_bgw_job_validate_timezone(PG_GETARG_DATUM(7));\n\tchar *job_name_str = PG_ARGISNULL(8) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(8));\n\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\tif (PG_ARGISNULL(0))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"function or procedure cannot be NULL\")));\n\n\tif (NULL == schedule_interval)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"schedule interval cannot be NULL\")));\n\n\t/* for fixed schedules, we use time_bucket in the calculation of next_start\n\t Therefore, we cannot allow schedule intervals containing both month and day components */\n\tif (fixed_schedule)\n\t\tts_bgw_job_validate_schedule_interval(schedule_interval);\n\n\tfunc_name = get_func_name(proc);\n\tif (func_name == NULL)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t errmsg(\"function or procedure with OID %u does not exist\", proc)));\n\n\tif (object_aclcheck(ProcedureRelationId, proc, owner, ACL_EXECUTE) != ACLCHECK_OK)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),\n\t\t\t\t errmsg(\"permission denied for function \\\"%s\\\"\", func_name),\n\t\t\t\t errhint(\"Job owner must have EXECUTE privilege on the function.\")));\n\n\tif (OidIsValid(check))\n\t{\n\t\tcheck_name_str = get_func_name(check);\n\t\tif (check_name_str == NULL)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t\t errmsg(\"function with OID %d does not exist\", check)));\n\n\t\tif (object_aclcheck(ProcedureRelationId, check, owner, ACL_EXECUTE) != ACLCHECK_OK)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),\n\t\t\t\t\t errmsg(\"permission denied for function \\\"%s\\\"\", check_name_str),\n\t\t\t\t\t errhint(\"Job owner must have EXECUTE privilege on the function.\")));\n\n\t\tnamestrcpy(&check_schema, get_namespace_name(get_func_namespace(check)));\n\t\tnamestrcpy(&check_name, check_name_str);\n\t}\n\n\t/* if no initial_start was provided for a fixed schedule, use the current time */\n\tif (fixed_schedule && TIMESTAMP_NOT_FINITE(initial_start))\n\t{\n\t\tinitial_start = ts_timer_get_current_timestamp();\n\t\telog(DEBUG1,\n\t\t\t \"Using current time [%s] as initial start\",\n\t\t\t DatumGetCString(\n\t\t\t\t DirectFunctionCall1(timestamptz_out, TimestampTzGetDatum(initial_start))));\n\t}\n\n\t/* Verify that the owner can create a background worker */\n\tts_bgw_job_validate_job_owner(owner);\n\n\t/* Next, insert a new job into jobs table */\n\tif (job_name_str)\n\t\tnamestrcpy(&application_name, job_name_str);\n\telse\n\t\tnamestrcpy(&application_name, \"User-Defined Action\");\n\tnamestrcpy(&proc_schema, get_namespace_name(get_func_namespace(proc)));\n\tnamestrcpy(&proc_name, func_name);\n\n\t/* The check exists but may not have the expected signature: (config jsonb) */\n\tif (OidIsValid(check))\n\t\tvalidate_check_signature(check);\n\n\tts_bgw_job_run_config_check(check, 0, config);\n\n\tjob_id = ts_bgw_job_insert_relation(&application_name,\n\t\t\t\t\t\t\t\t\t\tschedule_interval,\n\t\t\t\t\t\t\t\t\t\t&max_runtime,\n\t\t\t\t\t\t\t\t\t\tJOB_RETRY_UNLIMITED,\n\t\t\t\t\t\t\t\t\t\t&retry_period,\n\t\t\t\t\t\t\t\t\t\t&proc_schema,\n\t\t\t\t\t\t\t\t\t\t&proc_name,\n\t\t\t\t\t\t\t\t\t\t&check_schema,\n\t\t\t\t\t\t\t\t\t\t&check_name,\n\t\t\t\t\t\t\t\t\t\towner,\n\t\t\t\t\t\t\t\t\t\tscheduled,\n\t\t\t\t\t\t\t\t\t\tfixed_schedule,\n\t\t\t\t\t\t\t\t\t\tINVALID_HYPERTABLE_ID,\n\t\t\t\t\t\t\t\t\t\tconfig,\n\t\t\t\t\t\t\t\t\t\tinitial_start,\n\t\t\t\t\t\t\t\t\t\tvalid_timezone);\n\n\tif (!TIMESTAMP_NOT_FINITE(initial_start))\n\t\tts_bgw_job_stat_upsert_next_start(job_id, initial_start);\n\n\tPG_RETURN_INT32(job_id);\n}\n\nstatic BgwJob *\nfind_job(int32 job_id, bool null_job_id, bool missing_ok)\n{\n\tBgwJob *job;\n\n\tif (null_job_id && !missing_ok)\n\t\tereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"job ID cannot be NULL\")));\n\n\tjob = ts_bgw_job_find(job_id, CurrentMemoryContext, !missing_ok);\n\n\tif (NULL == job)\n\t{\n\t\tAssert(missing_ok);\n\t\tereport(NOTICE,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT), errmsg(\"job %d not found, skipping\", job_id)));\n\t}\n\n\treturn job;\n}\n\n/*\n * CREATE OR REPLACE FUNCTION delete_job(job_id INTEGER) RETURNS VOID\n */\nDatum\njob_delete(PG_FUNCTION_ARGS)\n{\n\tint32 job_id = PG_GETARG_INT32(0);\n\tBgwJob *job;\n\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\tjob = find_job(job_id, PG_ARGISNULL(0), false);\n\n\tif (!has_privs_of_role(GetUserId(), job->fd.owner))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),\n\t\t\t\t errmsg(\"insufficient permissions to delete job owned by \\\"%s\\\"\",\n\t\t\t\t\t\tGetUserNameFromId(job->fd.owner, false))));\n\n\tts_bgw_job_delete_by_id(job_id);\n\n\tPG_RETURN_VOID();\n}\n\n/*\n * CREATE OR REPLACE PROCEDURE run_job(job_id INTEGER)\n */\nDatum\njob_run(PG_FUNCTION_ARGS)\n{\n\tint32 job_id = PG_GETARG_INT32(0);\n\tBgwJob *job = find_job(job_id, PG_ARGISNULL(0), false);\n\n\tts_bgw_job_permission_check(job, \"run\");\n\n\tjob_execute(job);\n\n\tPG_RETURN_VOID();\n}\n\n/*\n * CREATE OR REPLACE FUNCTION alter_job(\n * 0    job_id INTEGER,\n * 1    schedule_interval INTERVAL = NULL,\n * 2    max_runtime INTERVAL = NULL,\n * 3    max_retries INTEGER = NULL,\n * 4    retry_period INTERVAL = NULL,\n * 5    scheduled BOOL = NULL,\n * 6    config JSONB = NULL,\n * 7    next_start TIMESTAMPTZ = NULL\n * 8    if_exists BOOL = FALSE,\n * 9    check_config REGPROC = NULL\n * 10   fixed_schedule BOOL = NULL,\n * 11   initial_start TIMESTAMPTZ = NULL\n * 12   timezone TEXT = NULL\n * 13\tjob_name TEXT = NULL\n * ) RETURNS TABLE (\n *      job_id INTEGER,\n *      schedule_interval INTERVAL,\n *      max_runtime INTERVAL,\n *      max_retries INTEGER,\n *      retry_period INTERVAL,\n *      scheduled BOOL,\n *      config JSONB,\n *      next_start TIMESTAMPTZ\n *      check_config TEXT\n *      fixed_schedule BOOL\n *      initial_start TIMESTAMPTZ\n *      timezone TEXT\n * \t \tjob_name TEXT\n * )\n */\nDatum\njob_alter(PG_FUNCTION_ARGS)\n{\n\tBgwJobStat *stat;\n\tTupleDesc tupdesc;\n\tDatum values[ALTER_JOB_NUM_COLS] = { 0 };\n\tbool nulls[ALTER_JOB_NUM_COLS] = { false };\n\tHeapTuple tuple;\n\tTimestampTz next_start;\n\tint job_id = PG_GETARG_INT32(0);\n\tbool if_exists = PG_GETARG_BOOL(8);\n\tBgwJob *job;\n\tNameData check_name = { .data = { 0 } };\n\tNameData check_schema = { .data = { 0 } };\n\tOid check = PG_ARGISNULL(9) ? InvalidOid : PG_GETARG_OID(9);\n\tchar *check_name_str = NULL;\n\t/* Added space for period and NULL */\n\tchar schema_qualified_check_name[(2 * NAMEDATALEN) + 2] = { 0 };\n\tbool unregister_check = (!PG_ARGISNULL(9) && !OidIsValid(check));\n\tTimestampTz initial_start = PG_ARGISNULL(11) ? DT_NOBEGIN : PG_GETARG_TIMESTAMPTZ(11);\n\ttext *timezone = PG_ARGISNULL(12) ? NULL : PG_GETARG_TEXT_PP(12);\n\tchar *valid_timezone = NULL;\n\t/* verify it's a valid timezone */\n\tif (timezone != NULL)\n\t\tvalid_timezone = ts_bgw_job_validate_timezone(PG_GETARG_DATUM(12));\n\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\t/* check that caller accepts tuple and abort early if that is not the\n\t * case */\n\tif (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"function returning record called in context \"\n\t\t\t\t\t\t\"that cannot accept type record\")));\n\n\tjob = find_job(job_id, PG_ARGISNULL(0), if_exists);\n\tif (job == NULL)\n\t\tPG_RETURN_NULL();\n\n\tts_bgw_job_permission_check(job, \"alter\");\n\n\tif (!PG_ARGISNULL(1))\n\t\tjob->fd.schedule_interval = *PG_GETARG_INTERVAL_P(1);\n\tif (!PG_ARGISNULL(2))\n\t\tjob->fd.max_runtime = *PG_GETARG_INTERVAL_P(2);\n\tif (!PG_ARGISNULL(3))\n\t\tjob->fd.max_retries = PG_GETARG_INT32(3);\n\tif (!PG_ARGISNULL(4))\n\t\tjob->fd.retry_period = *PG_GETARG_INTERVAL_P(4);\n\tif (!PG_ARGISNULL(5))\n\t\tjob->fd.scheduled = PG_GETARG_BOOL(5);\n\tif (!PG_ARGISNULL(6))\n\t\tjob->fd.config = PG_GETARG_JSONB_P(6);\n\n\tif (!PG_ARGISNULL(9))\n\t{\n\t\tif (OidIsValid(check))\n\t\t{\n\t\t\tcheck_name_str = get_func_name(check);\n\t\t\tif (check_name_str == NULL)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t\t\t errmsg(\"function with OID %d does not exist\", check)));\n\n\t\t\tif (object_aclcheck(ProcedureRelationId, check, GetUserId(), ACL_EXECUTE) !=\n\t\t\t\tACLCHECK_OK)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),\n\t\t\t\t\t\t errmsg(\"permission denied for function \\\"%s\\\"\", check_name_str),\n\t\t\t\t\t\t errhint(\"Job owner must have EXECUTE privilege on the function.\")));\n\n\t\t\tnamestrcpy(&check_schema, get_namespace_name(get_func_namespace(check)));\n\t\t\tnamestrcpy(&check_name, check_name_str);\n\n\t\t\t/* The check exists but may not have the expected signature: (config jsonb) */\n\t\t\tvalidate_check_signature(check);\n\n\t\t\tnamestrcpy(&job->fd.check_schema, NameStr(check_schema));\n\t\t\tnamestrcpy(&job->fd.check_name, NameStr(check_name));\n\t\t\tsnprintf(schema_qualified_check_name,\n\t\t\t\t\t sizeof(schema_qualified_check_name) / sizeof(schema_qualified_check_name[0]),\n\t\t\t\t\t \"%s.%s\",\n\t\t\t\t\t NameStr(check_schema),\n\t\t\t\t\t check_name_str);\n\t\t}\n\t}\n\telse\n\t\tsnprintf(schema_qualified_check_name,\n\t\t\t\t sizeof(schema_qualified_check_name) / sizeof(schema_qualified_check_name[0]),\n\t\t\t\t \"%s.%s\",\n\t\t\t\t NameStr(job->fd.check_schema),\n\t\t\t\t NameStr(job->fd.check_name));\n\n\t/*\n\t * If the CAgg refresh policy job is being altered, then always check for overlap.\n\t * There is probably a better place to do this, but we choose to do this here since we need\n\t * access to the job_id, which we don't have inside the `policy_check` function called above.\n\t */\n\tif (namestrcmp(&job->fd.proc_name, POLICY_REFRESH_CAGG_PROC_NAME) == 0)\n\t{\n\t\tint32 materialization_id =\n\t\t\tpolicy_continuous_aggregate_get_mat_hypertable_id(job->fd.config);\n\t\tContinuousAgg *cagg =\n\t\t\tts_continuous_agg_find_by_mat_hypertable_id(materialization_id, false);\n\t\tPolicyRefreshOffsetOverlapResult res =\n\t\t\tpolicy_refresh_cagg_check_for_overlaps(cagg, job->fd.config, job->fd.id);\n\t\tswitch (res)\n\t\t{\n\t\t\tcase POLICY_REFRESH_OFFSET_OVERLAP_NONE:\n\t\t\t\tbreak;\n\t\t\tcase POLICY_REFRESH_OFFSET_OVERLAP_EQUAL:\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\n\t\t\t\t\t\t errmsg(\"continuous aggregate refresh policy already exists for \"\n\t\t\t\t\t\t\t\t\"\\\"%s\\\"\",\n\t\t\t\t\t\t\t\tget_rel_name(cagg->relid)),\n\t\t\t\t\t\t errdetail(\"A refresh policy with the same start and end offset already \"\n\t\t\t\t\t\t\t\t   \"exists for \"\n\t\t\t\t\t\t\t\t   \"continuous aggregate \\\"%s\\\".\",\n\t\t\t\t\t\t\t\t   get_rel_name(cagg->relid))));\n\t\t\t\tbreak;\n\t\t\tcase POLICY_REFRESH_OFFSET_OVERLAP:\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t\t errmsg(\"refresh interval overlaps with an existing continuous aggregate \"\n\t\t\t\t\t\t\t\t\"policy on \\\"%s\\\"\",\n\t\t\t\t\t\t\t\tget_rel_name(cagg->relid))));\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (unregister_check)\n\t{\n\t\tNameData empty_namedata = { .data = { 0 } };\n\t\tnamestrcpy(&job->fd.check_schema, NameStr(empty_namedata));\n\t\tnamestrcpy(&job->fd.check_name, NameStr(empty_namedata));\n\t}\n\n\tif (!PG_ARGISNULL(10))\n\t{\n\t\tbool fixed_schedule = PG_GETARG_BOOL(10);\n\t\t/*\n\t\t * initial_start is a required argument for fixed schedules\n\t\t * so we use the current timestamp if it's not provided\n\t\t */\n\t\tif (fixed_schedule)\n\t\t{\n\t\t\tif (TIMESTAMP_NOT_FINITE(initial_start))\n\t\t\t{\n\t\t\t\tinitial_start = ts_timer_get_current_timestamp();\n\t\t\t\telog(NOTICE,\n\t\t\t\t\t \"Using current time [%s] as initial start for job %d\",\n\t\t\t\t\t DatumGetCString(\n\t\t\t\t\t\t DirectFunctionCall1(timestamptz_out, TimestampTzGetDatum(initial_start))),\n\t\t\t\t\t job->fd.id);\n\t\t\t\tjob->fd.initial_start = initial_start;\n\t\t\t}\n\t\t}\n\t\tjob->fd.fixed_schedule = fixed_schedule;\n\t}\n\tif (!PG_ARGISNULL(11))\n\t{\n\t\t/* user provided +- infinity as initial_start, this is not acceptable */\n\t\tif (TIMESTAMP_NOT_FINITE(initial_start))\n\t\t{\n\t\t\tinitial_start = ts_timer_get_current_timestamp();\n\t\t\telog(NOTICE,\n\t\t\t\t \"Using current time [%s] as initial start for job %d\",\n\t\t\t\t DatumGetCString(\n\t\t\t\t\t DirectFunctionCall1(timestamptz_out, TimestampTzGetDatum(initial_start))),\n\t\t\t\t job->fd.id);\n\t\t}\n\t\tjob->fd.initial_start = initial_start;\n\t}\n\n\tif (!PG_ARGISNULL(13))\n\t{\n\t\tchar app_name[NAMEDATALEN];\n\t\tint name_len;\n\n\t\tname_len = snprintf(app_name,\n\t\t\t\t\t\t\tNAMEDATALEN,\n\t\t\t\t\t\t\t\"%s [%d]\",\n\t\t\t\t\t\t\ttext_to_cstring(PG_GETARG_TEXT_PP(13)),\n\t\t\t\t\t\t\tjob_id);\n\n\t\tif (name_len >= NAMEDATALEN)\n\t\t\tereport(ERROR, (errcode(ERRCODE_NAME_TOO_LONG), errmsg(\"application name too long.\")));\n\n\t\tnamestrcpy(&job->fd.application_name, app_name);\n\t}\n\n\tif (valid_timezone != NULL)\n\t\tjob->fd.timezone = cstring_to_text(valid_timezone);\n\telse\n\t\tjob->fd.timezone = NULL;\n\t/* it's also possible to alter the fields initial_start and timezone without\n\t * specifying fixed_schedule. In that case, update them and also update the\n\t * next_start accordingly.\n\t * If the job is not on a fixed schedule, then this has no effect on the next_start,\n\t * so maybe print a message to the user\n\t * that these changes are not really doing anything */\n\n\t/* this function will also update the next_start if the schedule interval is changed,\n\tbut I'm not going to rely on this to change stuff */\n\tts_bgw_job_update_by_id(job_id, job);\n\t/* one of the fields below changing necessitates a next_start update */\n\tif (!PG_ARGISNULL(10) || !TIMESTAMP_NOT_FINITE(initial_start) || (valid_timezone != NULL))\n\t{\n\t\tTimestampTz next_start_calculated;\n\t\tif (job->fd.fixed_schedule)\n\t\t{\n\t\t\tnext_start_calculated =\n\t\t\t\tts_get_next_scheduled_execution_slot(job, ts_timer_get_current_timestamp());\n\t\t\tts_bgw_job_stat_update_next_start(job->fd.id, next_start_calculated, false);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* last finish time plus schedule interval */\n\t\t\tBgwJobStat *stat = ts_bgw_job_stat_find(job->fd.id);\n\n\t\t\tif (stat != NULL)\n\t\t\t{\n\t\t\t\tnext_start_calculated = DatumGetTimestampTz(\n\t\t\t\t\tDirectFunctionCall2(timestamptz_pl_interval,\n\t\t\t\t\t\t\t\t\t\tTimestampTzGetDatum(stat->fd.last_finish),\n\t\t\t\t\t\t\t\t\t\tIntervalPGetDatum(&job->fd.schedule_interval)));\n\t\t\t\t/* allow DT_NOBEGIN for next_start here through allow_unset=true in the case that\n\t\t\t\t * last_finish is DT_NOBEGIN,\n\t\t\t\t * This means the value is counted as unset which is what we want */\n\t\t\t\tts_bgw_job_stat_update_next_start(job->fd.id, next_start_calculated, true);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!PG_ARGISNULL(7))\n\t\tts_bgw_job_stat_upsert_next_start(job_id, PG_GETARG_TIMESTAMPTZ(7));\n\n\tstat = ts_bgw_job_stat_find(job_id);\n\tif (stat != NULL)\n\t\tnext_start = stat->fd.next_start;\n\telse\n\t\tnext_start = DT_NOBEGIN;\n\n\ttupdesc = BlessTupleDesc(tupdesc);\n\tvalues[0] = Int32GetDatum(job->fd.id);\n\tvalues[1] = IntervalPGetDatum(&job->fd.schedule_interval);\n\tvalues[2] = IntervalPGetDatum(&job->fd.max_runtime);\n\tvalues[3] = Int32GetDatum(job->fd.max_retries);\n\tvalues[4] = IntervalPGetDatum(&job->fd.retry_period);\n\tvalues[5] = BoolGetDatum(job->fd.scheduled);\n\n\tif (job->fd.config == NULL)\n\t\tnulls[6] = true;\n\telse\n\t\tvalues[6] = JsonbPGetDatum(job->fd.config);\n\n\tvalues[7] = TimestampTzGetDatum(next_start);\n\n\tif (unregister_check)\n\t\tnulls[8] = true;\n\telse if (strlen(NameStr(job->fd.check_schema)) > 0)\n\t\tvalues[8] = CStringGetTextDatum(schema_qualified_check_name);\n\telse\n\t\tnulls[8] = true;\n\n\t/* values/nulls[9]: fixed_schedule */\n\tvalues[9] = job->fd.fixed_schedule;\n\t/* values/nulls[10]: initial_start */\n\tif (TIMESTAMP_NOT_FINITE(job->fd.initial_start))\n\t{\n\t\tnulls[10] = true;\n\t}\n\telse\n\t\tvalues[10] = TimestampTzGetDatum(job->fd.initial_start);\n\t/* values/nulls[11]: timezone */\n\tif (valid_timezone)\n\t\tvalues[11] = CStringGetTextDatum(valid_timezone);\n\telse\n\t\tnulls[11] = true;\n\n\tvalues[12] = NameGetDatum(&job->fd.application_name);\n\n\ttuple = heap_form_tuple(tupdesc, values, nulls);\n\treturn HeapTupleGetDatum(tuple);\n}\n\nstatic Hypertable *\nget_hypertable_from_oid(Cache **hcache, Oid table_oid)\n{\n\tHypertable *hypertable = NULL;\n\thypertable = ts_hypertable_cache_get_cache_and_entry(table_oid, CACHE_FLAG_MISSING_OK, hcache);\n\tif (!hypertable)\n\t{\n\t\tconst char *view_name = get_rel_name(table_oid);\n\n\t\tif (!view_name)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t\t errmsg(\"relation is not a hypertable or continuous aggregate\")));\n\t\telse\n\t\t{\n\t\t\tContinuousAgg *ca = ts_continuous_agg_find_by_relid(table_oid);\n\n\t\t\tif (!ca)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t\t\t errmsg(\"relation \\\"%s\\\" is not a hypertable or continuous aggregate\",\n\t\t\t\t\t\t\t\tview_name)));\n\t\t\thypertable = ts_hypertable_get_by_id(ca->data.mat_hypertable_id);\n\t\t}\n\t}\n\tAssert(hypertable != NULL);\n\treturn hypertable;\n}\n\nDatum\njob_alter_set_hypertable_id(PG_FUNCTION_ARGS)\n{\n\tint32 job_id = PG_GETARG_INT32(0);\n\tOid table_oid = PG_GETARG_OID(1);\n\tCache *hcache = NULL;\n\tHypertable *ht = NULL;\n\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\tBgwJob *job = find_job(job_id, PG_ARGISNULL(0), false /* missing_ok */);\n\tif (job == NULL)\n\t\tPG_RETURN_NULL();\n\n\tts_bgw_job_permission_check(job, \"alter\");\n\n\tif (!PG_ARGISNULL(1))\n\t{\n\t\tht = get_hypertable_from_oid(&hcache, table_oid);\n\t\tts_hypertable_permissions_check(ht->main_table_relid, GetUserId());\n\t}\n\n\tjob->fd.hypertable_id = (ht != NULL ? ht->fd.id : 0);\n\tts_bgw_job_update_by_id(job_id, job);\n\tif (hcache)\n\t\tts_cache_release(&hcache);\n\tPG_RETURN_INT32(job_id);\n}\n"
  },
  {
    "path": "tsl/src/bgw_policy/job_api.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n/* Special values for the number of retries of a failed job */\n#define JOB_RETRY_UNLIMITED (-1)\n#define JOB_RETRY_NONE 0\n\nextern Datum job_add(PG_FUNCTION_ARGS);\nextern Datum job_alter(PG_FUNCTION_ARGS);\nextern Datum job_delete(PG_FUNCTION_ARGS);\nextern Datum job_run(PG_FUNCTION_ARGS);\nextern Datum job_alter_set_hypertable_id(PG_FUNCTION_ARGS);\n"
  },
  {
    "path": "tsl/src/bgw_policy/policies_v2.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <access/xact.h>\n#include <fmgr.h>\n#include <miscadmin.h>\n#include <parser/parse_coerce.h>\n#include <utils/builtins.h>\n#include <utils/float.h>\n\n#include \"compat/compat.h\"\n#include \"bgw/job.h\"\n#include \"bgw_policy/continuous_aggregate_api.h\"\n#include \"bgw_policy/job.h\"\n#include \"bgw_policy/policies_v2.h\"\n#include \"compression_api.h\"\n#include \"errors.h\"\n#include \"funcapi.h\"\n#include \"guc.h\"\n#include \"hypertable.h\"\n#include \"hypertable_cache.h\"\n#include \"jsonb_utils.h\"\n#include \"policy_utils.h\"\n#include \"utils.h\"\n#if PG16_GE\n#include \"nodes/miscnodes.h\"\n#endif\n\n/* Check if the provided argument is infinity */\nbool\nts_if_offset_is_infinity(Datum arg, Oid argtype, bool is_start)\n{\n\tif (OidIsValid(argtype) && argtype != UNKNOWNOID && argtype != FLOAT8OID)\n\t\treturn false;\n\n\tif (argtype != FLOAT8OID)\n\t{\n\t\tdouble val;\n\t\tchar *num = DatumGetCString(arg);\n#if PG16_LT\n\t\tbool have_error = false;\n\t\tval = float8in_internal_opt_error(num, NULL, \"double precision\", num, &have_error);\n\n\t\tif (have_error)\n\t\t\treturn false;\n#else\n\t\tErrorSaveContext escontext = { .type = T_ErrorSaveContext };\n\t\tval = float8in_internal(num, NULL, \"double precision\", num, (Node *) &escontext);\n\n\t\tif (escontext.error_occurred)\n\t\t\treturn false;\n#endif\n\n\t\targ = Float8GetDatum(val);\n\t}\n\tfloat8 result = DatumGetFloat8(arg);\n\treturn ((result == -get_float8_infinity() && is_start) ||\n\t\t\t(result == get_float8_infinity() && !is_start));\n}\n\nstatic void\nemit_error(const char *err)\n{\n\tereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"%s\", err)));\n}\n\nstatic int64\noffset_to_int64(NullableDatum arg, Oid argtype, Oid partition_type, bool is_start)\n{\n\tif (arg.isnull || ts_if_offset_is_infinity(arg.value, argtype, is_start))\n\t{\n\t\tif (is_start)\n\t\t\treturn ts_time_get_max(partition_type);\n\t\telse\n\t\t\treturn ts_time_get_min(partition_type);\n\t}\n\telse\n\t\treturn interval_to_int64(arg.value, argtype);\n}\n/*\n * Check different conditions if the requested policy parameters\n * are compatible with each other and then create/update the\n * provided policies.\n */\nbool\nvalidate_and_create_policies(policies_info all_policies, bool if_exists)\n{\n\tint refresh_job_id = 0, compression_job_id = 0, retention_job_id = 0;\n\tint64 refresh_interval = 0, compress_after = 0, drop_after = 0, drop_after_HT = 0;\n\tint64 start_offset = 0, end_offset = 0, refresh_total_interval = 0;\n\tList *jobs = NIL;\n\tBgwJob *orig_ht_reten_job = NULL;\n\n\tchar *err_gap_refresh = \"there are gaps in refresh policy\";\n\tchar *err_refresh_compress_overlap = \"refresh and columnstore policies overlap\";\n\tchar *err_refresh_reten_overlap = \"refresh and retention policies overlap\";\n\tchar *err_refresh_reten_ht_overlap = \"refresh policy of continuous aggregate and retention \"\n\t\t\t\t\t\t\t\t\t\t \"policy of underlying hypertable overlap\";\n\tchar *err_compress_reten_overlap = \"columnstore and retention policies overlap\";\n\n\tjobs = ts_bgw_job_find_by_proc_and_hypertable_id(POLICY_RETENTION_PROC_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t FUNCTIONS_SCHEMA_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t all_policies.original_HT);\n\tif (jobs != NIL)\n\t{\n\t\tAssert(list_length(jobs) == 1);\n\t\torig_ht_reten_job = linitial(jobs);\n\t}\n\n\tif (all_policies.refresh)\n\t{\n\t\tstart_offset = offset_to_int64(all_policies.refresh->start_offset,\n\t\t\t\t\t\t\t\t\t   all_policies.refresh->start_offset_type,\n\t\t\t\t\t\t\t\t\t   all_policies.partition_type,\n\t\t\t\t\t\t\t\t\t   true);\n\t\tend_offset = offset_to_int64(all_policies.refresh->end_offset,\n\t\t\t\t\t\t\t\t\t all_policies.refresh->end_offset_type,\n\t\t\t\t\t\t\t\t\t all_policies.partition_type,\n\t\t\t\t\t\t\t\t\t false);\n\t\trefresh_interval =\n\t\t\tinterval_to_int64(IntervalPGetDatum(&all_policies.refresh->schedule_interval),\n\t\t\t\t\t\t\t  INTERVALOID);\n\t\trefresh_total_interval = start_offset;\n\t\tif (!IS_INTEGER_TYPE(all_policies.partition_type) &&\n\t\t\trefresh_total_interval != ts_time_get_max(all_policies.partition_type))\n\t\t\trefresh_total_interval += refresh_interval;\n\t}\n\n\tif (all_policies.compress)\n\t\tcompress_after = interval_to_int64(all_policies.compress->compress_after,\n\t\t\t\t\t\t\t\t\t\t   all_policies.compress->compress_after_type);\n\tif (all_policies.retention)\n\t\tdrop_after = interval_to_int64(all_policies.retention->drop_after,\n\t\t\t\t\t\t\t\t\t   all_policies.retention->drop_after_type);\n\n\tif (orig_ht_reten_job)\n\t{\n\t\tif (IS_INTEGER_TYPE(all_policies.partition_type))\n\t\t{\n\t\t\tbool found_drop_after = false;\n\t\t\tdrop_after_HT = ts_jsonb_get_int64_field(orig_ht_reten_job->fd.config,\n\t\t\t\t\t\t\t\t\t\t\t\t\t POL_RETENTION_CONF_KEY_DROP_AFTER,\n\t\t\t\t\t\t\t\t\t\t\t\t\t &found_drop_after);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tdrop_after_HT = interval_to_int64(\n\t\t\t\tIntervalPGetDatum(ts_jsonb_get_interval_field(orig_ht_reten_job->fd.config,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  POL_RETENTION_CONF_KEY_DROP_AFTER)),\n\t\t\t\tINTERVALOID);\n\t\t}\n\t}\n\n\t/* Per policy checks */\n\tif (all_policies.refresh && !IS_INTEGER_TYPE(all_policies.partition_type))\n\t{\n\t\t/*\n\t\t * Check if there are any gaps in the refresh policy. The below code is\n\t\t * a little suspect. But since we are planning to do away with the\n\t\t * add_policies/remove_policies APIs there's no need to spend a lot\n\t\t * of time on fixing it below.\n\t\t */\n\t\tint64 refresh_window_size;\n\t\tif (start_offset == ts_time_get_max(all_policies.partition_type) ||\n\t\t\tend_offset == ts_time_get_min(all_policies.partition_type) ||\n\t\t\tend_offset > start_offset ||\n\t\t\tpg_sub_s64_overflow(start_offset, end_offset, &refresh_window_size))\n\t\t\trefresh_window_size = start_offset;\n\n\t\t/* if refresh_interval is greater than half of refresh_window_size, then there are gaps */\n\t\tif (refresh_interval > (refresh_window_size / 2))\n\t\t\temit_error(err_gap_refresh);\n\n\t\t/* Disallow refreshed data to be deleted */\n\t\tif (orig_ht_reten_job)\n\t\t{\n\t\t\tif (refresh_total_interval > drop_after_HT)\n\t\t\t\temit_error(err_refresh_reten_ht_overlap);\n\t\t}\n\t}\n\n\t/* Cross policy checks */\n\tif (all_policies.refresh && all_policies.compress)\n\t{\n\t\t/* Check if refresh policy does not overlap with compression */\n\t\tif (refresh_total_interval > compress_after)\n\t\t\temit_error(err_refresh_compress_overlap);\n\t}\n\tif (all_policies.refresh && all_policies.retention)\n\t{\n\t\t/* Check if refresh policy does not overlap with retention */\n\t\tif (refresh_total_interval > drop_after)\n\t\t\temit_error(err_refresh_reten_overlap);\n\t}\n\tif (all_policies.retention && all_policies.compress)\n\t{\n\t\t/* Check if compression and retention policy overlap */\n\t\tif (compress_after == drop_after)\n\t\t\temit_error(err_compress_reten_overlap);\n\t}\n\n\t/* Create policies as required, delete the old ones if coming from alter */\n\tif (all_policies.refresh && all_policies.refresh->create_policy)\n\t{\n\t\tNullableDatum include_tiered_data = { .isnull = true };\n\t\tNullableDatum nbuckets_per_refresh = { .isnull = true };\n\t\tNullableDatum max_batches_per_execution = { .isnull = true };\n\t\tNullableDatum refresh_newest_first = { .isnull = true };\n\n\t\tif (all_policies.is_alter_policy)\n\t\t\tpolicy_refresh_cagg_remove_internal(all_policies.rel_oid, if_exists);\n\t\trefresh_job_id = policy_refresh_cagg_add_internal(all_policies.rel_oid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  all_policies.refresh->start_offset_type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  all_policies.refresh->start_offset,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  all_policies.refresh->end_offset_type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  all_policies.refresh->end_offset,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  all_policies.refresh->schedule_interval,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  false,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  false,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  DT_NOBEGIN,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  include_tiered_data,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  nbuckets_per_refresh,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  max_batches_per_execution,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  refresh_newest_first);\n\t}\n\tif (all_policies.compress && all_policies.compress->create_policy)\n\t{\n\t\tif (all_policies.is_alter_policy)\n\t\t\tpolicy_compression_remove_internal(all_policies.rel_oid, if_exists);\n\t\tcompression_job_id =\n\t\t\tpolicy_compression_add_internal(all_policies.rel_oid,\n\t\t\t\t\t\t\t\t\t\t\tall_policies.compress->compress_after,\n\t\t\t\t\t\t\t\t\t\t\tall_policies.compress->compress_after_type,\n\t\t\t\t\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\t\t\t\t\tDEFAULT_COMPRESSION_SCHEDULE_INTERVAL,\n\t\t\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\t\t\tif_exists,\n\t\t\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\t\t\tDT_NOBEGIN,\n\t\t\t\t\t\t\t\t\t\t\tNULL);\n\t}\n\n\tif (all_policies.retention && all_policies.retention->create_policy)\n\t{\n\t\tif (all_policies.is_alter_policy)\n\t\t\tpolicy_retention_remove_internal(all_policies.rel_oid, if_exists);\n\t\tretention_job_id =\n\t\t\tpolicy_retention_add_internal(all_policies.rel_oid,\n\t\t\t\t\t\t\t\t\t\t  all_policies.retention->drop_after_type,\n\t\t\t\t\t\t\t\t\t\t  all_policies.retention->drop_after,\n\t\t\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t\t\t  (Interval) DEFAULT_RETENTION_SCHEDULE_INTERVAL,\n\t\t\t\t\t\t\t\t\t\t  false,\n\t\t\t\t\t\t\t\t\t\t  false,\n\t\t\t\t\t\t\t\t\t\t  DT_NOBEGIN,\n\t\t\t\t\t\t\t\t\t\t  NULL);\n\t}\n\treturn (refresh_job_id || compression_job_id || retention_job_id);\n}\n\nDatum\npolicies_add(PG_FUNCTION_ARGS)\n{\n\tOid rel_oid;\n\tbool if_not_exists;\n\tContinuousAgg *cagg;\n\tpolicies_info all_policies = { .refresh = NULL, .compress = NULL, .retention = NULL };\n\trefresh_policy ref;\n\tcompression_policy comp;\n\tretention_policy ret;\n\n\tts_feature_flag_check(FEATURE_POLICY);\n\n\trel_oid = PG_GETARG_OID(0);\n\tif_not_exists = PG_GETARG_BOOL(1);\n\n\tcagg = ts_continuous_agg_find_by_relid(rel_oid);\n\tif (!cagg)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"\\\"%s\\\" is not a continuous aggregate\", get_rel_name(rel_oid))));\n\n\tall_policies.rel_oid = rel_oid;\n\tall_policies.is_alter_policy = false;\n\tall_policies.original_HT = cagg->data.raw_hypertable_id;\n\tall_policies.partition_type = cagg->partition_type;\n\n\tif (!PG_ARGISNULL(2) || !PG_ARGISNULL(3))\n\t{\n\t\tNullableDatum start_offset, end_offset;\n\t\tOid start_offset_type, end_offset_type;\n\n\t\tstart_offset.isnull = PG_ARGISNULL(2);\n\t\tend_offset.isnull = PG_ARGISNULL(3);\n\t\tstart_offset.value = PG_GETARG_DATUM(2);\n\t\tend_offset.value = PG_GETARG_DATUM(3);\n\t\tstart_offset_type = get_fn_expr_argtype(fcinfo->flinfo, 2);\n\t\tend_offset_type = get_fn_expr_argtype(fcinfo->flinfo, 3);\n\n\t\trefresh_policy tmp = {\n\t\t\t.create_policy = true,\n\t\t\t.start_offset = start_offset,\n\t\t\t.end_offset = end_offset,\n\t\t\t.schedule_interval = *DEFAULT_REFRESH_SCHEDULE_INTERVAL,\n\t\t\t.start_offset_type = start_offset_type,\n\t\t\t.end_offset_type = end_offset_type,\n\t\t};\n\t\tref = tmp;\n\t\tall_policies.refresh = &ref;\n\t}\n\tif (!PG_ARGISNULL(4))\n\t{\n\t\tcompression_policy tmp = {\n\t\t\t.create_policy = true,\n\t\t\t.compress_after = PG_GETARG_DATUM(4),\n\t\t\t.compress_after_type = get_fn_expr_argtype(fcinfo->flinfo, 4),\n\t\t};\n\t\tcomp = tmp;\n\t\tall_policies.compress = &comp;\n\t}\n\tif (!PG_ARGISNULL(5))\n\t{\n\t\tretention_policy tmp = { .create_policy = true,\n\t\t\t\t\t\t\t\t .drop_after = PG_GETARG_DATUM(5),\n\t\t\t\t\t\t\t\t .drop_after_type = get_fn_expr_argtype(fcinfo->flinfo, 5) };\n\t\tret = tmp;\n\t\tall_policies.retention = &ret;\n\t}\n\tPG_RETURN_BOOL(validate_and_create_policies(all_policies, if_not_exists));\n}\nDatum\npolicies_remove(PG_FUNCTION_ARGS)\n{\n\tOid cagg_oid = PG_GETARG_OID(0);\n\tArrayType *policy_array = PG_ARGISNULL(2) ? NULL : PG_GETARG_ARRAYTYPE_P(2);\n\tbool if_exists = PG_GETARG_BOOL(1);\n\tDatum *policy;\n\tint npolicies, failures = 0;\n\tint i;\n\tbool success = false;\n\n\tts_feature_flag_check(FEATURE_POLICY);\n\n\tif (policy_array == NULL)\n\t\tPG_RETURN_BOOL(false);\n\n\tdeconstruct_array(policy_array, TEXTOID, -1, false, TYPALIGN_INT, &policy, NULL, &npolicies);\n\n\tfor (i = 0; i < npolicies; i++)\n\t{\n\t\tchar *curr_policy = VARDATA(policy[i]);\n\n\t\tif (pg_strcasecmp(curr_policy, POLICY_REFRESH_CAGG_PROC_NAME) == 0)\n\t\t\tsuccess = policy_refresh_cagg_remove_internal(cagg_oid, if_exists);\n\t\telse if (pg_strcasecmp(curr_policy, POLICY_COMPRESSION_PROC_NAME) == 0)\n\t\t\tsuccess = policy_compression_remove_internal(cagg_oid, if_exists);\n\t\telse if (pg_strncasecmp(curr_policy,\n\t\t\t\t\t\t\t\tPOLICY_RETENTION_PROC_NAME,\n\t\t\t\t\t\t\t\tstrlen(POLICY_RETENTION_PROC_NAME)) == 0)\n\t\t\tsuccess = policy_retention_remove_internal(cagg_oid, if_exists);\n\t\telse\n\t\t\tereport(NOTICE, (errmsg(\"No relevant policy found\")));\n\t\tif (!success)\n\t\t\t++failures;\n\t}\n\tPG_RETURN_BOOL(success && (0 == failures));\n}\n\nDatum\npolicies_remove_all(PG_FUNCTION_ARGS)\n{\n\tif (PG_ARGISNULL(0))\n\t\tPG_RETURN_BOOL(false);\n\n\tOid cagg_oid = PG_GETARG_OID(0);\n\tbool if_exists = PG_GETARG_BOOL(1);\n\tList *jobs;\n\tListCell *lc;\n\tbool success = if_exists;\n\tint failures = 0;\n\tContinuousAgg *cagg = ts_continuous_agg_find_by_relid(cagg_oid);\n\n\tts_feature_flag_check(FEATURE_POLICY);\n\n\tif (!cagg)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"\\\"%s\\\" is not a continuous aggregate\", get_rel_name(cagg_oid))));\n\n\tjobs = ts_bgw_job_find_by_hypertable_id(cagg->data.mat_hypertable_id);\n\tforeach (lc, jobs)\n\t{\n\t\tBgwJob *job = lfirst(lc);\n\t\tif (namestrcmp(&(job->fd.proc_name), POLICY_REFRESH_CAGG_PROC_NAME) == 0)\n\t\t\tsuccess = policy_refresh_cagg_remove_internal(cagg_oid, if_exists);\n\t\telse if (namestrcmp(&(job->fd.proc_name), POLICY_COMPRESSION_PROC_NAME) == 0)\n\t\t\tsuccess = policy_compression_remove_internal(cagg_oid, if_exists);\n\t\telse if (namestrcmp(&(job->fd.proc_name), POLICY_RETENTION_PROC_NAME) == 0)\n\t\t\tsuccess = policy_retention_remove_internal(cagg_oid, if_exists);\n\t\telse\n\t\t\tereport(NOTICE, (errmsg(\"Ignoring custom job\")));\n\t\tif (!success)\n\t\t\t++failures;\n\t}\n\tPG_RETURN_BOOL(success && (0 == failures));\n}\n\nDatum\npolicies_alter(PG_FUNCTION_ARGS)\n{\n\tOid rel_oid = PG_GETARG_OID(0);\n\tContinuousAgg *cagg;\n\tList *jobs;\n\tListCell *lc;\n\tbool if_exists = false, found, start_found, end_found;\n\tpolicies_info all_policies = { .refresh = NULL, .compress = NULL, .retention = NULL };\n\trefresh_policy ref;\n\tcompression_policy comp;\n\tretention_policy ret;\n\n\tts_feature_flag_check(FEATURE_POLICY);\n\n\tcagg = ts_continuous_agg_find_by_relid(rel_oid);\n\tif (!cagg)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"\\\"%s\\\" is not a continuous aggregate\", get_rel_name(rel_oid))));\n\n\tall_policies.is_alter_policy = true;\n\tall_policies.rel_oid = rel_oid;\n\tall_policies.original_HT = cagg->data.raw_hypertable_id;\n\tall_policies.partition_type = cagg->partition_type;\n\n\tjobs = ts_bgw_job_find_by_hypertable_id(cagg->data.mat_hypertable_id);\n\tif ((NIL == jobs))\n\t\tereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"no jobs found\")));\n\tforeach (lc, jobs)\n\t{\n\t\tBgwJob *job = lfirst(lc);\n\t\tif (namestrcmp(&(job->fd.proc_name), POLICY_REFRESH_CAGG_PROC_NAME) == 0)\n\t\t{\n\t\t\trefresh_policy tmp = { .create_policy = false,\n\t\t\t\t\t\t\t\t   .end_offset = { 0 },\n\t\t\t\t\t\t\t\t   .end_offset_type = InvalidOid,\n\t\t\t\t\t\t\t\t   .start_offset = { 0 },\n\t\t\t\t\t\t\t\t   .start_offset_type = InvalidOid,\n\t\t\t\t\t\t\t\t   .schedule_interval = job->fd.schedule_interval };\n\t\t\tref = tmp;\n\t\t\tall_policies.refresh = &ref;\n\t\t\tif (IS_INTEGER_TYPE(cagg->partition_type))\n\t\t\t{\n\t\t\t\tint64 start_value = ts_jsonb_get_int64_field(job->fd.config,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t POL_REFRESH_CONF_KEY_START_OFFSET,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t &start_found);\n\t\t\t\tint64 end_value = ts_jsonb_get_int64_field(job->fd.config,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   POL_REFRESH_CONF_KEY_END_OFFSET,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   &end_found);\n\t\t\t\t/*\n\t\t\t\t * If there is job then start_offset has to be there because policy is\n\t\t\t\t * not created without it. However if found it to be NULL, then we\n\t\t\t\t * want to keep it to NULL in this alter command also.\n\t\t\t\t */\n\t\t\t\tall_policies.refresh->start_offset.isnull = !start_found;\n\t\t\t\tall_policies.refresh->start_offset_type = cagg->partition_type;\n\t\t\t\tall_policies.refresh->end_offset.isnull = !end_found;\n\t\t\t\tall_policies.refresh->end_offset_type = cagg->partition_type;\n\t\t\t\tswitch (all_policies.refresh->start_offset_type)\n\t\t\t\t{\n\t\t\t\t\tcase INT2OID:\n\t\t\t\t\t\tall_policies.refresh->start_offset.value =\n\t\t\t\t\t\t\tInt16GetDatum((int16) start_value);\n\t\t\t\t\t\tall_policies.refresh->end_offset.value = Int16GetDatum((int16) end_value);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase INT4OID:\n\t\t\t\t\t\tall_policies.refresh->start_offset.value =\n\t\t\t\t\t\t\tInt32GetDatum((int32) start_value);\n\t\t\t\t\t\tall_policies.refresh->end_offset.value = Int32GetDatum((int32) end_value);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase INT8OID:\n\t\t\t\t\t\tall_policies.refresh->start_offset.value = Int64GetDatum(start_value);\n\t\t\t\t\t\tall_policies.refresh->end_offset.value = Int64GetDatum(end_value);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tAssert(0);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tall_policies.refresh->start_offset.value = IntervalPGetDatum(\n\t\t\t\t\tts_jsonb_get_interval_field(job->fd.config, POL_REFRESH_CONF_KEY_START_OFFSET));\n\t\t\t\tall_policies.refresh->start_offset.isnull =\n\t\t\t\t\t(DatumGetIntervalP(all_policies.refresh->start_offset.value) == NULL);\n\t\t\t\tall_policies.refresh->start_offset_type = INTERVALOID;\n\n\t\t\t\tall_policies.refresh->end_offset.value = IntervalPGetDatum(\n\t\t\t\t\tts_jsonb_get_interval_field(job->fd.config, POL_REFRESH_CONF_KEY_END_OFFSET));\n\t\t\t\tall_policies.refresh->end_offset.isnull =\n\t\t\t\t\t(DatumGetIntervalP(all_policies.refresh->end_offset.value) == NULL);\n\t\t\t\tall_policies.refresh->end_offset_type = INTERVALOID;\n\t\t\t}\n\t\t}\n\t\telse if (namestrcmp(&(job->fd.proc_name), POLICY_COMPRESSION_PROC_NAME) == 0)\n\t\t{\n\t\t\tcompression_policy tmp = { .compress_after = 0,\n\t\t\t\t\t\t\t\t\t   .compress_after_type = InvalidOid,\n\t\t\t\t\t\t\t\t\t   .create_policy = false };\n\t\t\tcomp = tmp;\n\t\t\tall_policies.compress = &comp;\n\n\t\t\tif (IS_INTEGER_TYPE(cagg->partition_type))\n\t\t\t{\n\t\t\t\tint64 compress_value =\n\t\t\t\t\tts_jsonb_get_int64_field(job->fd.config,\n\t\t\t\t\t\t\t\t\t\t\t POL_COMPRESSION_CONF_KEY_COMPRESS_AFTER,\n\t\t\t\t\t\t\t\t\t\t\t &found);\n\t\t\t\tall_policies.compress->compress_after_type = cagg->partition_type;\n\t\t\t\tswitch (all_policies.compress->compress_after_type)\n\t\t\t\t{\n\t\t\t\t\tcase INT2OID:\n\t\t\t\t\t\tall_policies.compress->compress_after =\n\t\t\t\t\t\t\tInt16GetDatum((int16) compress_value);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase INT4OID:\n\t\t\t\t\t\tall_policies.compress->compress_after =\n\t\t\t\t\t\t\tInt32GetDatum((int32) compress_value);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase INT8OID:\n\t\t\t\t\t\tall_policies.compress->compress_after = Int64GetDatum(compress_value);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tAssert(0);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tall_policies.compress->compress_after = IntervalPGetDatum(\n\t\t\t\t\tts_jsonb_get_interval_field(job->fd.config,\n\t\t\t\t\t\t\t\t\t\t\t\tPOL_COMPRESSION_CONF_KEY_COMPRESS_AFTER));\n\t\t\t\tall_policies.compress->compress_after_type = INTERVALOID;\n\t\t\t}\n\t\t}\n\t\telse if (namestrcmp(&(job->fd.proc_name), POLICY_RETENTION_PROC_NAME) == 0)\n\t\t{\n\t\t\tretention_policy tmp = { .create_policy = false,\n\t\t\t\t\t\t\t\t\t .drop_after = 0,\n\t\t\t\t\t\t\t\t\t .drop_after_type = InvalidOid };\n\t\t\tret = tmp;\n\t\t\tall_policies.retention = &ret;\n\t\t\tif (IS_INTEGER_TYPE(cagg->partition_type))\n\t\t\t{\n\t\t\t\tint64 drop_value = ts_jsonb_get_int64_field(job->fd.config,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tPOL_RETENTION_CONF_KEY_DROP_AFTER,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t&found);\n\t\t\t\tall_policies.retention->drop_after_type = cagg->partition_type;\n\t\t\t\tswitch (all_policies.retention->drop_after_type)\n\t\t\t\t{\n\t\t\t\t\tcase INT2OID:\n\t\t\t\t\t\tall_policies.retention->drop_after = Int16GetDatum((int16) drop_value);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase INT4OID:\n\t\t\t\t\t\tall_policies.retention->drop_after = Int32GetDatum((int32) drop_value);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase INT8OID:\n\t\t\t\t\t\tall_policies.retention->drop_after = Int64GetDatum(drop_value);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tAssert(0);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tall_policies.retention->drop_after = IntervalPGetDatum(\n\t\t\t\t\tts_jsonb_get_interval_field(job->fd.config, POL_RETENTION_CONF_KEY_DROP_AFTER));\n\t\t\t\tall_policies.retention->drop_after_type = INTERVALOID;\n\t\t\t}\n\t\t}\n\t}\n\tif (!PG_ARGISNULL(2))\n\t{\n\t\tif (!all_policies.refresh)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"no refresh job found\")));\n\t\tall_policies.refresh->start_offset.value = PG_GETARG_DATUM(2);\n\t\tall_policies.refresh->start_offset_type = get_fn_expr_argtype(fcinfo->flinfo, 2);\n\t\tall_policies.refresh->start_offset.isnull = false;\n\t\tall_policies.refresh->create_policy = true;\n\t}\n\tif (!PG_ARGISNULL(3))\n\t{\n\t\tif (!all_policies.refresh)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"no refresh job found\")));\n\t\tall_policies.refresh->end_offset.value = PG_GETARG_DATUM(3);\n\t\tall_policies.refresh->end_offset_type = get_fn_expr_argtype(fcinfo->flinfo, 3);\n\t\tall_policies.refresh->end_offset.isnull = false;\n\t\tall_policies.refresh->create_policy = true;\n\t}\n\tif (!PG_ARGISNULL(4))\n\t{\n\t\tif (!all_policies.compress)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"no compress job found\")));\n\t\tall_policies.compress->compress_after = PG_GETARG_DATUM(4);\n\t\tall_policies.compress->compress_after_type = get_fn_expr_argtype(fcinfo->flinfo, 4);\n\t\tall_policies.compress->create_policy = true;\n\t}\n\tif (!PG_ARGISNULL(5))\n\t{\n\t\tif (!all_policies.retention)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"no retention job found\")));\n\t\tall_policies.retention->drop_after = PG_GETARG_DATUM(5);\n\t\tall_policies.retention->drop_after_type = get_fn_expr_argtype(fcinfo->flinfo, 5);\n\t\tall_policies.retention->create_policy = true;\n\t}\n\n\tPG_RETURN_BOOL(validate_and_create_policies(all_policies, if_exists));\n}\n\nstatic void\npush_to_json(Oid type, JsonbParseState *parse_state, BgwJob *job, char *json_label,\n\t\t\t char *show_config)\n{\n\tif (IS_INTEGER_TYPE(type))\n\t{\n\t\tbool found;\n\t\tint64 value = ts_jsonb_get_int64_field(job->fd.config, json_label, &found);\n\t\tif (!found)\n\t\t\tts_jsonb_add_null(parse_state, show_config);\n\t\telse\n\t\t\tts_jsonb_add_int64(parse_state, show_config, value);\n\t}\n\telse\n\t{\n\t\tInterval *value = ts_jsonb_get_interval_field(job->fd.config, json_label);\n\t\tif (value == NULL)\n\t\t\tts_jsonb_add_null(parse_state, show_config);\n\t\telse\n\t\t\tts_jsonb_add_interval(parse_state, show_config, value);\n\t}\n}\n\nDatum\npolicies_show(PG_FUNCTION_ARGS)\n{\n\tOid rel_oid = PG_GETARG_OID(0);\n\tOid type;\n\tContinuousAgg *cagg;\n\tListCell *lc;\n\tFuncCallContext *funcctx;\n\tstatic List *jobs;\n\tJsonbParseState *parse_state = NULL;\n\n\tts_feature_flag_check(FEATURE_POLICY);\n\n\tcagg = ts_continuous_agg_find_by_relid(rel_oid);\n\tif (!cagg)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"\\\"%s\\\" is not a continuous aggregate\", get_rel_name(rel_oid))));\n\n\ttype = IS_TIMESTAMP_TYPE(cagg->partition_type) ? INTERVALOID : cagg->partition_type;\n\n\tpushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);\n\tif (SRF_IS_FIRSTCALL())\n\t{\n\t\tMemoryContext oldcontext;\n\n\t\tfuncctx = SRF_FIRSTCALL_INIT();\n\t\toldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);\n\n\t\t/* Use top-level memory context to preserve the global static list */\n\t\tjobs = ts_bgw_job_find_by_hypertable_id(cagg->data.mat_hypertable_id);\n\n\t\tfuncctx->user_fctx = list_head(jobs);\n\t\tMemoryContextSwitchTo(oldcontext);\n\t}\n\n\tfuncctx = SRF_PERCALL_SETUP();\n\tlc = (ListCell *) funcctx->user_fctx;\n\n\t/*\n\t * clang doesn't understand that jobs won't be NIL due to the above FIRSTCALL. However\n\t * it's also possible that the ts_bgw_job_find_by_hypertable_id function above doesn't\n\t * find a job for this hypertable\n\t */\n\tif (lc == NULL || jobs == NULL)\n\t\tSRF_RETURN_DONE(funcctx);\n\telse\n\t{\n\t\tBgwJob *job = lfirst(lc);\n\n\t\tif (!namestrcmp(&(job->fd.proc_name), POLICY_REFRESH_CAGG_PROC_NAME))\n\t\t{\n\t\t\tts_jsonb_add_str(parse_state,\n\t\t\t\t\t\t\t SHOW_POLICY_KEY_POLICY_NAME,\n\t\t\t\t\t\t\t POLICY_REFRESH_CAGG_PROC_NAME);\n\t\t\tpush_to_json(type,\n\t\t\t\t\t\t parse_state,\n\t\t\t\t\t\t job,\n\t\t\t\t\t\t POL_REFRESH_CONF_KEY_START_OFFSET,\n\t\t\t\t\t\t SHOW_POLICY_KEY_REFRESH_START_OFFSET);\n\t\t\tpush_to_json(type,\n\t\t\t\t\t\t parse_state,\n\t\t\t\t\t\t job,\n\t\t\t\t\t\t POL_REFRESH_CONF_KEY_END_OFFSET,\n\t\t\t\t\t\t SHOW_POLICY_KEY_REFRESH_END_OFFSET);\n\t\t\tts_jsonb_add_interval(parse_state,\n\t\t\t\t\t\t\t\t  SHOW_POLICY_KEY_REFRESH_INTERVAL,\n\t\t\t\t\t\t\t\t  &(job->fd.schedule_interval));\n\t\t}\n\t\telse if (!namestrcmp(&(job->fd.proc_name), POLICY_COMPRESSION_PROC_NAME))\n\t\t{\n\t\t\tts_jsonb_add_str(parse_state,\n\t\t\t\t\t\t\t SHOW_POLICY_KEY_POLICY_NAME,\n\t\t\t\t\t\t\t POLICY_COMPRESSION_PROC_NAME);\n\t\t\tpush_to_json(type,\n\t\t\t\t\t\t parse_state,\n\t\t\t\t\t\t job,\n\t\t\t\t\t\t POL_COMPRESSION_CONF_KEY_COMPRESS_AFTER,\n\t\t\t\t\t\t SHOW_POLICY_KEY_COMPRESS_AFTER);\n\t\t\t/* POL_COMPRESSION_CONF_KEY_COMPRESS_CREATED_BEFORE not supported with caggs */\n\t\t\tts_jsonb_add_interval(parse_state,\n\t\t\t\t\t\t\t\t  SHOW_POLICY_KEY_COMPRESS_INTERVAL,\n\t\t\t\t\t\t\t\t  &(job->fd.schedule_interval));\n\t\t}\n\t\telse if (!namestrcmp(&(job->fd.proc_name), POLICY_RETENTION_PROC_NAME))\n\t\t{\n\t\t\tts_jsonb_add_str(parse_state, SHOW_POLICY_KEY_POLICY_NAME, POLICY_RETENTION_PROC_NAME);\n\t\t\tpush_to_json(type,\n\t\t\t\t\t\t parse_state,\n\t\t\t\t\t\t job,\n\t\t\t\t\t\t POL_RETENTION_CONF_KEY_DROP_AFTER,\n\t\t\t\t\t\t SHOW_POLICY_KEY_DROP_AFTER);\n\t\t\t/* POL_RETENTION_CONF_KEY_DROP_CREATED_BEFORE not supported with caggs */\n\t\t\tts_jsonb_add_interval(parse_state,\n\t\t\t\t\t\t\t\t  SHOW_POLICY_KEY_RETENTION_INTERVAL,\n\t\t\t\t\t\t\t\t  &(job->fd.schedule_interval));\n\t\t}\n\t\telse\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"\\\"%s\\\" unsupported proc\", NameStr(job->fd.proc_name))));\n\n\t\tJsonbValue *result = pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);\n\n\t\tfuncctx->user_fctx = lnext(jobs, (ListCell *) funcctx->user_fctx);\n\t\tSRF_RETURN_NEXT(funcctx, PointerGetDatum(JsonbValueToJsonb(result)));\n\t}\n\tPG_RETURN_NULL();\n}\n"
  },
  {
    "path": "tsl/src/bgw_policy/policies_v2.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include \"compression/api.h\"\n#include \"dimension.h\"\n#include <bgw_policy/compression_api.h>\n#include <bgw_policy/continuous_aggregate_api.h>\n#include <bgw_policy/retention_api.h>\n#include <continuous_aggs/materialize.h>\n#include <utils/jsonb.h>\n\n#define POLICY_REFRESH_CAGG_PROC_NAME \"policy_refresh_continuous_aggregate\"\n#define POLICY_REFRESH_CAGG_CHECK_NAME \"policy_refresh_continuous_aggregate_check\"\n#define POL_REFRESH_CONF_KEY_MAT_HYPERTABLE_ID \"mat_hypertable_id\"\n#define POL_REFRESH_CONF_KEY_START_OFFSET \"start_offset\"\n#define POL_REFRESH_CONF_KEY_END_OFFSET \"end_offset\"\n#define POL_REFRESH_CONF_KEY_INCLUDE_TIERED_DATA \"include_tiered_data\"\n#define POL_REFRESH_CONF_KEY_BUCKETS_PER_BATCH \"buckets_per_batch\"\n#define POL_REFRESH_CONF_KEY_MAX_BATCHES_PER_EXECUTION \"max_batches_per_execution\"\n#define POL_REFRESH_CONF_KEY_REFRESH_NEWEST_FIRST \"refresh_newest_first\"\n#define POL_REFRESH_CONF_KEY_PROCESS_HYPERTABLE_INVALIDATIONS \"process_hypertable_invalidations\"\n\n#define POLICY_COMPRESSION_PROC_NAME \"policy_compression\"\n#define POLICY_COMPRESSION_CHECK_NAME \"policy_compression_check\"\n#define POL_COMPRESSION_CONF_KEY_COMPRESS_AFTER \"compress_after\"\n#define POL_COMPRESSION_CONF_KEY_MAXCHUNKS_TO_COMPRESS \"maxchunks_to_compress\"\n#define POL_COMPRESSION_CONF_KEY_COMPRESS_CREATED_BEFORE \"compress_created_before\"\n\n#define POLICY_RECOMPRESSION_PROC_NAME \"policy_recompression\"\n#define POL_RECOMPRESSION_CONF_KEY_RECOMPRESS_AFTER \"recompress_after\"\n\n#define POLICY_RETENTION_PROC_NAME \"policy_retention\"\n#define POLICY_RETENTION_CHECK_NAME \"policy_retention_check\"\n#define POL_RETENTION_CONF_KEY_DROP_AFTER \"drop_after\"\n#define POL_RETENTION_CONF_KEY_DROP_CREATED_BEFORE \"drop_created_before\"\n\n#define SHOW_POLICY_KEY_POLICY_NAME \"policy_name\"\n#define SHOW_POLICY_KEY_REFRESH_INTERVAL \"refresh_interval\"\n#define SHOW_POLICY_KEY_REFRESH_START_OFFSET \"refresh_start_offset\"\n#define SHOW_POLICY_KEY_REFRESH_END_OFFSET \"refresh_end_offset\"\n#define SHOW_POLICY_KEY_COMPRESS_AFTER POL_COMPRESSION_CONF_KEY_COMPRESS_AFTER\n#define SHOW_POLICY_KEY_COMPRESS_CREATED_BEFORE POL_COMPRESSION_CONF_KEY_COMPRESS_CREATED_BEFORE\n#define SHOW_POLICY_KEY_COMPRESS_INTERVAL \"compress_interval\"\n#define SHOW_POLICY_KEY_DROP_AFTER POL_RETENTION_CONF_KEY_DROP_AFTER\n#define SHOW_POLICY_KEY_DROP_CREATED_BEFORE POL_RETENTION_CONF_KEY_DROP_CREATED_BEFORE\n#define SHOW_POLICY_KEY_RETENTION_INTERVAL \"retention_interval\"\n\n#define DEFAULT_RETENTION_SCHEDULE_INTERVAL                                                        \\\n\t{                                                                                              \\\n\t\t.day = 1                                                                                   \\\n\t}\n/*\n * Default scheduled interval for compress jobs = default chunk length.\n * If this is non-timestamp based hypertable, then default is 1 day\n */\n#define DEFAULT_COMPRESSION_SCHEDULE_INTERVAL                                                      \\\n\tDatumGetIntervalP(DirectFunctionCall3(interval_in, CStringGetDatum(\"1 day\"), InvalidOid, -1))\n\n#define DEFAULT_REFRESH_SCHEDULE_INTERVAL                                                          \\\n\tDatumGetIntervalP(DirectFunctionCall3(interval_in, CStringGetDatum(\"1 hour\"), InvalidOid, -1))\n\nextern Datum policies_add(PG_FUNCTION_ARGS);\nextern Datum policies_remove(PG_FUNCTION_ARGS);\nextern Datum policies_remove_all(PG_FUNCTION_ARGS);\nextern Datum policies_alter(PG_FUNCTION_ARGS);\nextern Datum policies_show(PG_FUNCTION_ARGS);\n\ntypedef struct CaggPolicyConfig\n{\n\tOid partition_type;\n\tContinuousAggPolicyOffset offset_start;\n\tContinuousAggPolicyOffset offset_end;\n} CaggPolicyConfig;\n\ntypedef struct refresh_policy\n{\n\tInterval schedule_interval;\n\tNullableDatum start_offset;\n\tNullableDatum end_offset;\n\tOid start_offset_type, end_offset_type;\n\tbool create_policy;\n} refresh_policy;\n\ntypedef struct compression_policy\n{\n\tDatum compress_after;\n\tOid compress_after_type;\n\tbool create_policy;\n} compression_policy;\n\ntypedef struct retention_policy\n{\n\tDatum drop_after;\n\tOid drop_after_type;\n\tbool create_policy;\n} retention_policy;\n\ntypedef struct policies_info\n{\n\tOid rel_oid;\n\tOid original_HT;\n\tOid partition_type;\n\trefresh_policy *refresh;\n\tcompression_policy *compress;\n\tretention_policy *retention;\n\tbool is_alter_policy;\n} policies_info;\n\nbool ts_if_offset_is_infinity(Datum arg, Oid argtype, bool is_start);\nbool validate_and_create_policies(policies_info all_policies, bool if_exists);\nint64 interval_to_int64(Datum interval, Oid type);\n"
  },
  {
    "path": "tsl/src/bgw_policy/policy_config.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * Module with common functions, variables, and constants for working with\n * policy configurations.\n */\n\n#include <postgres.h>\n\n#include \"jsonb_utils.h\"\n#include \"policies_v2.h\"\n#include \"policy_config.h\"\n\nint32\npolicy_config_get_hypertable_id(const Jsonb *config)\n{\n\tbool found;\n\tint32 hypertable_id = ts_jsonb_get_int32_field(config, POLICY_CONFIG_KEY_HYPERTABLE_ID, &found);\n\n\tif (!found)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_SQL_JSON_MEMBER_NOT_FOUND),\n\t\t\t\t errmsg(\"could not find hypertable_id in config for job\")));\n\n\treturn hypertable_id;\n}\n\n/* Helper function to compare jsonb label value in the config\n * with passed in value.\n * This function is used for labels defined on the hypertable's dimension\n * Parameters:\n * config - jsonb config value\n * label - label we are looking for inside the config\n * partitioning_type - Oid for hypertable's dimension column\n * lag_value - value we will compare against the config's\n *             value for the label\n * lag_type - Oid for lag_value\n * Returns:\n *    True, if config value is equal to lag_value\n */\nbool\npolicy_config_check_hypertable_lag_equality(Jsonb *config, const char *json_label,\n\t\t\t\t\t\t\t\t\t\t\tOid partitioning_type, Oid lag_type, Datum lag_datum,\n\t\t\t\t\t\t\t\t\t\t\tbool isnull)\n{\n\t/*\n\t * start_offset and end_offset for CAgg policies are allowed to have NULL values\n\t * In that case, config_value will be NULL but this is not an error\n\t */\n\n\tbool null_ok = (strcmp(json_label, POL_REFRESH_CONF_KEY_END_OFFSET) == 0 ||\n\t\t\t\t\tstrcmp(json_label, POL_REFRESH_CONF_KEY_START_OFFSET) == 0);\n\n\tif (IS_INTEGER_TYPE(partitioning_type) && lag_type != INTERVALOID)\n\t{\n\t\tbool found;\n\t\tint64 config_value = ts_jsonb_get_int64_field(config, json_label, &found);\n\n\t\tif (!found && !null_ok)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t\t errmsg(\"could not find %s in config for existing job\", json_label)));\n\n\t\tif (!found && isnull)\n\t\t\treturn true;\n\n\t\tif ((!found && !isnull) || (found && isnull))\n\t\t\treturn false;\n\n\t\tswitch (lag_type)\n\t\t{\n\t\t\tcase INT2OID:\n\t\t\t\treturn config_value == DatumGetInt16(lag_datum);\n\t\t\tcase INT4OID:\n\t\t\t\treturn config_value == DatumGetInt32(lag_datum);\n\t\t\tcase INT8OID:\n\t\t\t\treturn config_value == DatumGetInt64(lag_datum);\n\t\t\tdefault:\n\t\t\t\treturn false;\n\t\t}\n\t}\n\telse\n\t{\n\t\tif (lag_type != INTERVALOID)\n\t\t\treturn false;\n\t\tInterval *config_value = ts_jsonb_get_interval_field(config, json_label);\n\t\tif (config_value == NULL && !null_ok)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t\t errmsg(\"could not find %s in config for job\", json_label)));\n\n\t\tif (config_value == NULL && isnull)\n\t\t\treturn true;\n\n\t\tif ((config_value == NULL && !isnull) || (config_value != NULL && isnull))\n\t\t\treturn false;\n\n\t\treturn DatumGetBool(\n\t\t\tDirectFunctionCall2(interval_eq, IntervalPGetDatum(config_value), lag_datum));\n\t}\n}\n"
  },
  {
    "path": "tsl/src/bgw_policy/policy_config.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n/*\n * Common functions, variables, and constants for working with policy\n * configurations.\n */\n\n#include <postgres.h>\n#include <utils/jsonb.h>\n\n#define POLICY_CONFIG_KEY_HYPERTABLE_ID \"hypertable_id\"\n\nextern int32 policy_config_get_hypertable_id(const Jsonb *config);\nextern bool policy_config_check_hypertable_lag_equality(Jsonb *config, const char *json_label,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tOid partitioning_type, Oid lag_type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tDatum lag_datum, bool isnull);\n"
  },
  {
    "path": "tsl/src/bgw_policy/policy_utils.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n#include \"policy_utils.h\"\n#include \"dimension.h\"\n#include \"errors.h\"\n#include \"guc.h\"\n#include \"hypertable.h\"\n#include \"jsonb_utils.h\"\n#include \"policies_v2.h\"\n#include \"time_utils.h\"\n#include \"ts_catalog/continuous_agg.h\"\n#include <utils/builtins.h>\n\nconst Dimension *\nget_open_dimension_for_hypertable(const Hypertable *ht, bool fail_if_not_found)\n{\n\tint32 mat_id = ht->fd.id;\n\n\tif (TS_HYPERTABLE_IS_INTERNAL_COMPRESSION_TABLE(ht))\n\t\telog(ERROR, \"invalid operation on compressed hypertable\");\n\n\tconst Dimension *open_dim = hyperspace_get_open_dimension(ht->space, 0);\n\tOid partitioning_type = ts_dimension_get_partition_type(open_dim);\n\tif (IS_INTEGER_TYPE(partitioning_type))\n\t{\n\t\t/* if this a materialization hypertable related to cont agg\n\t\t * then need to get the right dimension which has\n\t\t * integer_now function\n\t\t */\n\n\t\topen_dim = ts_continuous_agg_find_integer_now_func_by_materialization_id(mat_id);\n\t\tif (open_dim == NULL && fail_if_not_found)\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_TS_UNEXPECTED),\n\t\t\t\t\t errmsg(\"missing integer_now function for hypertable \\\"%s\\\" \",\n\t\t\t\t\t\t\tget_rel_name(ht->main_table_relid))));\n\t\t}\n\t}\n\treturn open_dim;\n}\n\nbool\npolicy_get_verbose_log(const Jsonb *config)\n{\n\tbool found;\n\tbool verbose_log = ts_jsonb_get_bool_field(config, CONFIG_KEY_VERBOSE_LOG, &found);\n\n\treturn found ? verbose_log : false;\n}\n"
  },
  {
    "path": "tsl/src/bgw_policy/policy_utils.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include \"job.h\"\n\nint64 subtract_integer_from_now_internal(int64 interval, Oid time_dim_type, Oid now_func,\n\t\t\t\t\t\t\t\t\t\t bool *overflow);\nconst Dimension *get_open_dimension_for_hypertable(const Hypertable *ht, bool fail_if_not_found);\nbool policy_get_verbose_log(const Jsonb *config);\n"
  },
  {
    "path": "tsl/src/bgw_policy/process_hyper_inval_api.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n\n#include \"process_hyper_inval_api.h\"\n\n#include \"bgw/job.h\"\n#include \"bgw/job_stat.h\"\n#include \"bgw/timer.h\"\n#include \"bgw_policy/job.h\"\n#include \"bgw_policy/job_api.h\"\n#include \"guc.h\"\n#include \"hypertable.h\"\n#include \"jsonb_utils.h\"\n#include \"policy_config.h\"\n#include <utils/elog.h>\n\n#define DEFAULT_MAX_RUNTIME                                                                        \\\n\tDatumGetIntervalP(DirectFunctionCall3(interval_in, CStringGetDatum(\"0\"), InvalidOid, -1))\n\nDatum\npolicy_process_hyper_inval_proc(PG_FUNCTION_ARGS)\n{\n\tif (PG_NARGS() != 2 || PG_ARGISNULL(0) || PG_ARGISNULL(1))\n\t\tPG_RETURN_VOID();\n\n\tts_feature_flag_check(FEATURE_POLICY);\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\tpolicy_process_hyper_inval_execute(PG_GETARG_INT32(0), PG_GETARG_JSONB_P(1));\n\n\tPG_RETURN_VOID();\n}\n\nDatum\npolicy_process_hyper_inval_check(PG_FUNCTION_ARGS)\n{\n\tif (PG_ARGISNULL(0))\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg(\"config must not be NULL\")));\n\t}\n\n\tpolicy_process_hyper_inval_read_and_validate_config(PG_GETARG_JSONB_P(0), NULL);\n\n\tPG_RETURN_VOID();\n}\n\nstatic int32\npolicy_process_hyper_inval_add_internal(Hypertable *ht, bool if_not_exists,\n\t\t\t\t\t\t\t\t\t\tInterval *schedule_interval, Oid owner_id,\n\t\t\t\t\t\t\t\t\t\tTimestampTz initial_start, const bool fixed_schedule,\n\t\t\t\t\t\t\t\t\t\tchar *timezone)\n{\n\tNameData application_name, proc_name, proc_schema, check_schema, check_name, owner;\n\n\tList *jobs = ts_bgw_job_find_by_proc_and_hypertable_id(POLICY_PROCESS_HYPER_INVAL_PROC_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   FUNCTIONS_SCHEMA_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   ht->fd.id);\n\tif (jobs != NIL)\n\t{\n\t\tAssert(list_length(jobs) == 1);\n\t\tif (!if_not_exists)\n\t\t\tereport(ERROR,\n\t\t\t\t\terrcode(ERRCODE_DUPLICATE_OBJECT),\n\t\t\t\t\terrmsg(\"move hypertable invalidations policy already exists for \\\"%s\\\"\",\n\t\t\t\t\t\t   get_rel_name(ht->main_table_relid)));\n\t\telse\n\t\t\tereport(NOTICE,\n\t\t\t\t\terrmsg(\"move hypertable invalidations policy already exists for \\\"%s\\\", \"\n\t\t\t\t\t\t   \"skipping\",\n\t\t\t\t\t\t   get_rel_name(ht->main_table_relid)));\n\t\treturn -1;\n\t}\n\n\tnamestrcpy(&application_name, \"Move Hypertables Invalidation Policy\");\n\tnamestrcpy(&proc_name, POLICY_PROCESS_HYPER_INVAL_PROC_NAME);\n\tnamestrcpy(&proc_schema, FUNCTIONS_SCHEMA_NAME);\n\tnamestrcpy(&check_name, POLICY_PROCESS_HYPER_INVAL_CHECK_NAME);\n\tnamestrcpy(&check_schema, FUNCTIONS_SCHEMA_NAME);\n\tnamestrcpy(&owner, GetUserNameFromId(owner_id, false));\n\n\tJsonbParseState *parse_state = NULL;\n\n\tpushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);\n\tts_jsonb_add_int32(parse_state, POLICY_CONFIG_KEY_HYPERTABLE_ID, ht->fd.id);\n\n\tJsonbValue *result = pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);\n\tJsonb *config = JsonbValueToJsonb(result);\n\n\treturn ts_bgw_job_insert_relation(&application_name,\n\t\t\t\t\t\t\t\t\t  schedule_interval,\n\t\t\t\t\t\t\t\t\t  DEFAULT_MAX_RUNTIME,\n\t\t\t\t\t\t\t\t\t  JOB_RETRY_UNLIMITED,\n\t\t\t\t\t\t\t\t\t  schedule_interval,\n\t\t\t\t\t\t\t\t\t  &proc_schema,\n\t\t\t\t\t\t\t\t\t  &proc_name,\n\t\t\t\t\t\t\t\t\t  &check_schema,\n\t\t\t\t\t\t\t\t\t  &check_name,\n\t\t\t\t\t\t\t\t\t  owner_id,\n\t\t\t\t\t\t\t\t\t  true,\n\t\t\t\t\t\t\t\t\t  fixed_schedule,\n\t\t\t\t\t\t\t\t\t  ht->fd.id,\n\t\t\t\t\t\t\t\t\t  config,\n\t\t\t\t\t\t\t\t\t  initial_start,\n\t\t\t\t\t\t\t\t\t  timezone);\n}\n\nDatum\npolicy_process_hyper_inval_add(PG_FUNCTION_ARGS)\n{\n\tOid ht_oid = PG_GETARG_OID(0);\n\tInterval *schedule_interval = PG_GETARG_INTERVAL_P(1);\n\tconst bool if_not_exists = PG_GETARG_BOOL(2);\n\tTimestampTz initial_start = PG_ARGISNULL(3) ? DT_NOBEGIN : PG_GETARG_TIMESTAMPTZ(3);\n\tconst bool fixed_schedule = !PG_ARGISNULL(3);\n\tchar *valid_timezone = NULL;\n\n\tts_feature_flag_check(FEATURE_POLICY);\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\tif (fixed_schedule)\n\t{\n\t\tts_bgw_job_validate_schedule_interval(schedule_interval);\n\t\tif (TIMESTAMP_NOT_FINITE(initial_start))\n\t\t\tinitial_start = ts_timer_get_current_timestamp();\n\t}\n\n\tif (!PG_ARGISNULL(4))\n\t\tvalid_timezone = ts_bgw_job_validate_timezone(PG_GETARG_DATUM(4));\n\n\tCache *hcache;\n\tHypertable *ht = ts_hypertable_cache_get_cache_and_entry(ht_oid, CACHE_FLAG_NONE, &hcache);\n\n\t/* Check that we have a continuous aggregate attached */\n\tList *caggs = ts_continuous_aggs_find_by_raw_table_id(ht->fd.id);\n\tif (list_length(caggs) == 0)\n\t\tereport(ERROR,\n\t\t\t\terrcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\terrmsg(\"\\\"%s\\\" does not have an associated continuous aggregate\",\n\t\t\t\t\t   get_rel_name(ht_oid)));\n\n\tOid owner_id = ts_hypertable_permissions_check(ht_oid, GetUserId());\n\tts_bgw_job_validate_job_owner(owner_id);\n\n\tint32 job_id = policy_process_hyper_inval_add_internal(ht,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   if_not_exists,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   schedule_interval,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   owner_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   initial_start,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   fixed_schedule,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   valid_timezone);\n\n\tts_cache_release(&hcache);\n\n\tif (!TIMESTAMP_NOT_FINITE(initial_start))\n\t\tts_bgw_job_stat_upsert_next_start(job_id, initial_start);\n\n\tPG_RETURN_INT32(job_id);\n}\n\nstatic void\npolicy_process_hyper_inval_remove_internal(Oid ht_oid, bool if_exists)\n{\n\tCache *hcache;\n\tHypertable *ht = ts_hypertable_cache_get_cache_and_entry(ht_oid, CACHE_FLAG_NONE, &hcache);\n\tif (!ht)\n\t\tereport(ERROR,\n\t\t\t\terrcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\terrmsg(\"\\\"%s\\\" is not a hypertable\", get_rel_name(ht_oid)));\n\n\t/* Check permissions */\n\tOid owner_id = ts_hypertable_permissions_check(ht_oid, GetUserId());\n\tts_bgw_job_validate_job_owner(owner_id);\n\n\t/* Find jobs */\n\tList *jobs = ts_bgw_job_find_by_proc_and_hypertable_id(POLICY_PROCESS_HYPER_INVAL_PROC_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   FUNCTIONS_SCHEMA_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   ht->fd.id);\n\tif (!jobs && !if_exists)\n\t\tereport(ERROR,\n\t\t\t\terrcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\terrmsg(\"move invalidations policy for \\\"%s\\\" not found\", get_rel_name(ht_oid)));\n\n\tif (!jobs && if_exists)\n\t{\n\t\tereport(NOTICE,\n\t\t\t\terrmsg(\"move invalidations policy for \\\"%s\\\" not found, skipping\",\n\t\t\t\t\t   get_rel_name(ht_oid)));\n\t\tts_cache_release(&hcache);\n\t\treturn;\n\t}\n\n\tAssert(list_length(jobs) == 1);\n\tBgwJob *job = linitial(jobs);\n\n\t/* Delete all jobs, not just the first one? */\n\tts_bgw_job_delete_by_id(job->fd.id);\n\n\tts_cache_release(&hcache);\n}\n\nDatum\npolicy_process_hyper_inval_remove(PG_FUNCTION_ARGS)\n{\n\tts_feature_flag_check(FEATURE_POLICY);\n\tpolicy_process_hyper_inval_remove_internal(PG_GETARG_OID(0), PG_GETARG_BOOL(1));\n\tPG_RETURN_VOID();\n}\n"
  },
  {
    "path": "tsl/src/bgw_policy/process_hyper_inval_api.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <fmgr.h>\n\n#define POLICY_PROCESS_HYPER_INVAL_PROC_NAME \"policy_process_hypertable_invalidations\"\n#define POLICY_PROCESS_HYPER_INVAL_CHECK_NAME \"policy_process_hypertable_invalidations_check\"\n\nextern Datum policy_process_hyper_inval_add(PG_FUNCTION_ARGS);\nextern Datum policy_process_hyper_inval_proc(PG_FUNCTION_ARGS);\nextern Datum policy_process_hyper_inval_check(PG_FUNCTION_ARGS);\nextern Datum policy_process_hyper_inval_remove(PG_FUNCTION_ARGS);\n"
  },
  {
    "path": "tsl/src/bgw_policy/reorder_api.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <catalog/namespace.h>\n#include <catalog/pg_collation.h>\n#include <catalog/pg_type.h>\n#include <miscadmin.h>\n#include <utils/builtins.h>\n#include <utils/lsyscache.h>\n#include <utils/syscache.h>\n#include <utils/timestamp.h>\n\n#include <compat/compat.h>\n#include <dimension.h>\n#include <hypertable_cache.h>\n#include <jsonb_utils.h>\n\n#include \"bgw/job.h\"\n#include \"bgw/job_stat.h\"\n#include \"bgw/timer.h\"\n#include \"bgw_policy/job.h\"\n#include \"bgw_policy/job_api.h\"\n#include \"bgw_policy/policy_config.h\"\n#include \"bgw_policy/reorder_api.h\"\n#include \"guc.h\"\n#include \"hypertable.h\"\n#include \"utils.h\"\n/*\n * Default scheduled interval for reorder jobs should be 1/2 of the default chunk length.\n * If no such length is specified for the hypertable, then\n * the default is 4 days, which is approximately 1/2 of the default chunk size, 7 days.\n */\n#define DEFAULT_SCHEDULE_INTERVAL                                                                  \\\n\t{                                                                                              \\\n\t\t.day = 4                                                                                   \\\n\t}\n\n/* Default max runtime for a reorder job is unlimited for now */\n#define DEFAULT_MAX_RUNTIME                                                                        \\\n\tDatumGetIntervalP(DirectFunctionCall3(interval_in, CStringGetDatum(\"0\"), InvalidOid, -1))\n\n/* Default retry period for reorder_jobs is currently 5 minutes */\n#define DEFAULT_RETRY_PERIOD                                                                       \\\n\tDatumGetIntervalP(DirectFunctionCall3(interval_in, CStringGetDatum(\"5 min\"), InvalidOid, -1))\n\n#define CONFIG_KEY_INDEX_NAME \"index_name\"\n\n#define POLICY_REORDER_PROC_NAME \"policy_reorder\"\n#define POLICY_REORDER_CHECK_NAME \"policy_reorder_check\"\n\nchar *\npolicy_reorder_get_index_name(const Jsonb *config)\n{\n\tchar *index_name = NULL;\n\n\tif (config != NULL)\n\t\tindex_name = ts_jsonb_get_str_field(config, CONFIG_KEY_INDEX_NAME);\n\n\tif (index_name == NULL)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t errmsg(\"could not find index_name in config for job\")));\n\n\treturn index_name;\n}\n\nstatic void\ncheck_valid_index(Hypertable *ht, Name index_name)\n{\n\tOid index_oid;\n\tHeapTuple idxtuple;\n\tForm_pg_index indexForm;\n\n\tindex_oid = ts_get_relation_relid(NameStr(ht->fd.schema_name), NameStr(*index_name), true);\n\tidxtuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid));\n\tif (!HeapTupleIsValid(idxtuple))\n\t\tereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"invalid reorder index\")));\n\n\tindexForm = (Form_pg_index) GETSTRUCT(idxtuple);\n\tif (indexForm->indrelid != ht->main_table_relid)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid reorder index\"),\n\t\t\t\t errhint(\"The reorder index must by an index on hypertable \\\"%s\\\".\",\n\t\t\t\t\t\t NameStr(ht->fd.table_name))));\n\n\tReleaseSysCache(idxtuple);\n}\n\nDatum\npolicy_reorder_check(PG_FUNCTION_ARGS)\n{\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\tif (PG_ARGISNULL(0))\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg(\"config must not be NULL\")));\n\t}\n\n\tpolicy_reorder_read_and_validate_config(PG_GETARG_JSONB_P(0), NULL);\n\n\tPG_RETURN_VOID();\n}\n\nDatum\npolicy_reorder_proc(PG_FUNCTION_ARGS)\n{\n\tif (PG_NARGS() != 2 || PG_ARGISNULL(0) || PG_ARGISNULL(1))\n\t\tPG_RETURN_VOID();\n\n\tts_feature_flag_check(FEATURE_POLICY);\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\tpolicy_reorder_execute(PG_GETARG_INT32(0), PG_GETARG_JSONB_P(1));\n\n\tPG_RETURN_VOID();\n}\n\nDatum\npolicy_reorder_add(PG_FUNCTION_ARGS)\n{\n\t/* behave like a strict function */\n\tif (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2))\n\t\tPG_RETURN_NULL();\n\n\tNameData application_name;\n\tNameData proc_name, proc_schema, check_name, check_schema, owner;\n\tint32 job_id;\n\tconst Dimension *dim;\n\tInterval schedule_interval = DEFAULT_SCHEDULE_INTERVAL;\n\tOid ht_oid = PG_GETARG_OID(0);\n\tName index_name = PG_GETARG_NAME(1);\n\tbool if_not_exists = PG_GETARG_BOOL(2);\n\tCache *hcache;\n\tHypertable *ht;\n\tint32 hypertable_id;\n\tOid partitioning_type;\n\tOid owner_id;\n\tList *jobs;\n\tTimestampTz initial_start = PG_ARGISNULL(3) ? DT_NOBEGIN : PG_GETARG_TIMESTAMPTZ(3);\n\tbool fixed_schedule = !PG_ARGISNULL(3);\n\ttext *timezone = PG_ARGISNULL(4) ? NULL : PG_GETARG_TEXT_PP(4);\n\tchar *valid_timezone = NULL;\n\n\tts_feature_flag_check(FEATURE_POLICY);\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\tif (timezone != NULL)\n\t\tvalid_timezone = ts_bgw_job_validate_timezone(PG_GETARG_DATUM(4));\n\n\tht = ts_hypertable_cache_get_cache_and_entry(ht_oid, CACHE_FLAG_NONE, &hcache);\n\tAssert(ht != NULL);\n\thypertable_id = ht->fd.id;\n\n\t/* First verify that the hypertable corresponds to a valid table */\n\towner_id = ts_hypertable_permissions_check(ht_oid, GetUserId());\n\n\tif (TS_HYPERTABLE_IS_INTERNAL_COMPRESSION_TABLE(ht))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot add reorder policy to compressed hypertable \\\"%s\\\"\",\n\t\t\t\t\t\tget_rel_name(ht_oid)),\n\t\t\t\t errhint(\"Please add the policy to the corresponding uncompressed hypertable \"\n\t\t\t\t\t\t \"instead.\")));\n\n\t/* Now verify that the index is an actual index on that hypertable */\n\tcheck_valid_index(ht, index_name);\n\n\t/* Verify that the hypertable owner can create a background worker */\n\tts_bgw_job_validate_job_owner(owner_id);\n\n\t/* Make sure that an existing reorder policy doesn't exist on this hypertable */\n\tjobs = ts_bgw_job_find_by_proc_and_hypertable_id(POLICY_REORDER_PROC_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t FUNCTIONS_SCHEMA_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t ht->fd.id);\n\n\t/*\n\t * Try to see if the hypertable has a specified chunk length for the\n\t * default schedule interval\n\t */\n\tdim = hyperspace_get_open_dimension(ht->space, 0);\n\tAssert(dim);\n\n\tpartitioning_type = ts_dimension_get_partition_type(dim);\n\tif (IS_TIMESTAMP_TYPE(partitioning_type))\n\t{\n\t\tschedule_interval.time = dim->fd.interval_length / 2;\n\t\tschedule_interval.day = 0;\n\t\tschedule_interval.month = 0;\n\t}\n\n\tts_cache_release(&hcache);\n\n\tif (jobs != NIL)\n\t{\n\t\tBgwJob *existing = linitial(jobs);\n\t\tAssert(list_length(jobs) == 1);\n\n\t\tif (!if_not_exists)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_DUPLICATE_OBJECT),\n\t\t\t\t\t errmsg(\"reorder policy already exists for hypertable \\\"%s\\\"\",\n\t\t\t\t\t\t\tget_rel_name(ht_oid))));\n\n\t\tif (!DatumGetBool(DirectFunctionCall2Coll(nameeq,\n\t\t\t\t\t\t\t\t\t\t\t\t  C_COLLATION_OID,\n\t\t\t\t\t\t\t\t\t\t\t\t  CStringGetDatum(policy_reorder_get_index_name(\n\t\t\t\t\t\t\t\t\t\t\t\t\t  existing->fd.config)),\n\t\t\t\t\t\t\t\t\t\t\t\t  NameGetDatum(index_name))))\n\t\t{\n\t\t\tereport(WARNING,\n\t\t\t\t\t(errmsg(\"reorder policy already exists for hypertable \\\"%s\\\"\",\n\t\t\t\t\t\t\tget_rel_name(ht_oid)),\n\t\t\t\t\t errdetail(\"A policy already exists with different arguments.\"),\n\t\t\t\t\t errhint(\"Remove the existing policy before adding a new one.\")));\n\t\t\tPG_RETURN_INT32(-1);\n\t\t}\n\t\t/* If all arguments are the same, do nothing */\n\t\tereport(NOTICE,\n\t\t\t\t(errmsg(\"reorder policy already exists on hypertable \\\"%s\\\", skipping\",\n\t\t\t\t\t\tget_rel_name(ht_oid))));\n\t\tPG_RETURN_INT32(-1);\n\t}\n\n\t/* if users pass in -infinity for initial_start, then use the current_timestamp instead */\n\tif (fixed_schedule)\n\t{\n\t\tts_bgw_job_validate_schedule_interval(&schedule_interval);\n\t\tif (TIMESTAMP_NOT_FINITE(initial_start))\n\t\t\tinitial_start = ts_timer_get_current_timestamp();\n\t}\n\n\t/* Next, insert a new job into jobs table */\n\tnamestrcpy(&application_name, \"Reorder Policy\");\n\tnamestrcpy(&proc_name, POLICY_REORDER_PROC_NAME);\n\tnamestrcpy(&proc_schema, FUNCTIONS_SCHEMA_NAME);\n\tnamestrcpy(&check_name, POLICY_REORDER_CHECK_NAME);\n\tnamestrcpy(&check_schema, FUNCTIONS_SCHEMA_NAME);\n\tnamestrcpy(&owner, GetUserNameFromId(owner_id, false));\n\n\tJsonbParseState *parse_state = NULL;\n\n\tpushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);\n\tts_jsonb_add_int32(parse_state, POLICY_CONFIG_KEY_HYPERTABLE_ID, hypertable_id);\n\tts_jsonb_add_str(parse_state, CONFIG_KEY_INDEX_NAME, NameStr(*index_name));\n\tJsonbValue *result = pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);\n\tJsonb *config = JsonbValueToJsonb(result);\n\n\t/* for the reorder policy, we choose a drifting schedule\n\t since the user does not control the schedule interval either */\n\tjob_id = ts_bgw_job_insert_relation(&application_name,\n\t\t\t\t\t\t\t\t\t\t&schedule_interval,\n\t\t\t\t\t\t\t\t\t\tDEFAULT_MAX_RUNTIME,\n\t\t\t\t\t\t\t\t\t\tJOB_RETRY_UNLIMITED,\n\t\t\t\t\t\t\t\t\t\tDEFAULT_RETRY_PERIOD,\n\t\t\t\t\t\t\t\t\t\t&proc_schema,\n\t\t\t\t\t\t\t\t\t\t&proc_name,\n\t\t\t\t\t\t\t\t\t\t&check_schema,\n\t\t\t\t\t\t\t\t\t\t&check_name,\n\t\t\t\t\t\t\t\t\t\towner_id,\n\t\t\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\t\t\tfixed_schedule,\n\t\t\t\t\t\t\t\t\t\thypertable_id,\n\t\t\t\t\t\t\t\t\t\tconfig,\n\t\t\t\t\t\t\t\t\t\tinitial_start,\n\t\t\t\t\t\t\t\t\t\tvalid_timezone);\n\n\tif (!TIMESTAMP_NOT_FINITE(initial_start))\n\t\tts_bgw_job_stat_upsert_next_start(job_id, initial_start);\n\n\tPG_RETURN_INT32(job_id);\n}\n\nDatum\npolicy_reorder_remove(PG_FUNCTION_ARGS)\n{\n\tOid hypertable_oid = PG_GETARG_OID(0);\n\tbool if_exists = PG_GETARG_BOOL(1);\n\tHypertable *ht;\n\tCache *hcache;\n\n\tts_feature_flag_check(FEATURE_POLICY);\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\tht = ts_hypertable_cache_get_cache_and_entry(hypertable_oid, CACHE_FLAG_NONE, &hcache);\n\n\tList *jobs = ts_bgw_job_find_by_proc_and_hypertable_id(POLICY_REORDER_PROC_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   FUNCTIONS_SCHEMA_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   ht->fd.id);\n\tts_cache_release(&hcache);\n\n\tif (jobs == NIL)\n\t{\n\t\tif (!if_exists)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t\t errmsg(\"reorder policy not found for hypertable \\\"%s\\\"\",\n\t\t\t\t\t\t\tget_rel_name(hypertable_oid))));\n\t\telse\n\t\t{\n\t\t\tereport(NOTICE,\n\t\t\t\t\t(errmsg(\"reorder policy not found for hypertable \\\"%s\\\", skipping\",\n\t\t\t\t\t\t\tget_rel_name(hypertable_oid))));\n\t\t\tPG_RETURN_NULL();\n\t\t}\n\t}\n\tAssert(list_length(jobs) == 1);\n\tBgwJob *job = linitial(jobs);\n\n\tts_hypertable_permissions_check(hypertable_oid, GetUserId());\n\n\tts_bgw_job_delete_by_id(job->fd.id);\n\n\tPG_RETURN_NULL();\n}\n"
  },
  {
    "path": "tsl/src/bgw_policy/reorder_api.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n/* User-facing API functions */\nextern Datum policy_reorder_add(PG_FUNCTION_ARGS);\nextern Datum policy_reorder_remove(PG_FUNCTION_ARGS);\nextern Datum policy_reorder_proc(PG_FUNCTION_ARGS);\nextern Datum policy_reorder_check(PG_FUNCTION_ARGS);\n\nextern char *policy_reorder_get_index_name(const Jsonb *config);\n"
  },
  {
    "path": "tsl/src/bgw_policy/retention_api.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <access/xact.h>\n#include <catalog/pg_type.h>\n#include <miscadmin.h>\n#include <utils/builtins.h>\n#include <utils/lsyscache.h>\n#include <utils/syscache.h>\n\n#include <hypertable_cache.h>\n\n#include \"bgw/job.h\"\n#include \"bgw/job_stat.h\"\n#include \"bgw/timer.h\"\n#include \"bgw_policy/job.h\"\n#include \"bgw_policy/policies_v2.h\"\n#include \"bgw_policy/policy_config.h\"\n#include \"chunk.h\"\n#include \"dimension.h\"\n#include \"errors.h\"\n#include \"guc.h\"\n#include \"hypertable.h\"\n#include \"jsonb_utils.h\"\n#include \"policy_utils.h\"\n#include \"retention_api.h\"\n#include \"ts_catalog/continuous_agg.h\"\n#include \"utils.h\"\n\nDatum\npolicy_retention_proc(PG_FUNCTION_ARGS)\n{\n\tif (PG_NARGS() != 2 || PG_ARGISNULL(0) || PG_ARGISNULL(1))\n\t\tPG_RETURN_VOID();\n\n\tts_feature_flag_check(FEATURE_POLICY);\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\tpolicy_retention_execute(PG_GETARG_INT32(0), PG_GETARG_JSONB_P(1));\n\n\tPG_RETURN_VOID();\n}\n\nDatum\npolicy_retention_check(PG_FUNCTION_ARGS)\n{\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\tif (PG_ARGISNULL(0))\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg(\"config must not be NULL\")));\n\t}\n\n\tpolicy_retention_read_and_validate_config(PG_GETARG_JSONB_P(0), NULL);\n\n\tPG_RETURN_VOID();\n}\n\nint64\npolicy_retention_get_drop_after_int(const Jsonb *config)\n{\n\tbool found;\n\tint64 drop_after = ts_jsonb_get_int64_field(config, POL_RETENTION_CONF_KEY_DROP_AFTER, &found);\n\n\tif (!found)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t errmsg(\"could not find %s in config for job\", POL_RETENTION_CONF_KEY_DROP_AFTER)));\n\n\treturn drop_after;\n}\n\nInterval *\npolicy_retention_get_drop_after_interval(const Jsonb *config)\n{\n\tInterval *interval = ts_jsonb_get_interval_field(config, POL_RETENTION_CONF_KEY_DROP_AFTER);\n\n\tif (interval == NULL)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t errmsg(\"could not find %s in config for job\", POL_RETENTION_CONF_KEY_DROP_AFTER)));\n\n\treturn interval;\n}\n\nInterval *\npolicy_retention_get_drop_created_before_interval(const Jsonb *config)\n{\n\tInterval *interval =\n\t\tts_jsonb_get_interval_field(config, POL_RETENTION_CONF_KEY_DROP_CREATED_BEFORE);\n\n\tif (interval == NULL)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t errmsg(\"could not find %s in config for job\",\n\t\t\t\t\t\tPOL_RETENTION_CONF_KEY_DROP_CREATED_BEFORE)));\n\n\treturn interval;\n}\n\nstatic Hypertable *\nvalidate_drop_chunks_hypertable(Cache *hcache, Oid user_htoid)\n{\n\tContinuousAggHypertableStatus status;\n\tHypertable *ht = ts_hypertable_cache_get_entry(hcache, user_htoid, true /* missing_ok */);\n\n\tif (ht != NULL)\n\t{\n\t\tif (TS_HYPERTABLE_IS_INTERNAL_COMPRESSION_TABLE(ht))\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"cannot add retention policy to compressed hypertable \\\"%s\\\"\",\n\t\t\t\t\t\t\tget_rel_name(user_htoid)),\n\t\t\t\t\t errhint(\"Please add the policy to the corresponding uncompressed hypertable \"\n\t\t\t\t\t\t\t \"instead.\")));\n\t\t}\n\t\tstatus = ts_continuous_agg_hypertable_status(ht->fd.id);\n\t\tif ((status == HypertableIsMaterialization || status == HypertableIsMaterializationAndRaw))\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"cannot add retention policy to materialized hypertable \\\"%s\\\" \",\n\t\t\t\t\t\t\tget_rel_name(user_htoid)),\n\t\t\t\t\t errhint(\"Please add the policy to the corresponding continuous aggregate \"\n\t\t\t\t\t\t\t \"instead.\")));\n\t\t}\n\t}\n\telse\n\t{\n\t\t/*check if this is a cont aggregate view */\n\t\tint32 mat_id;\n\t\tContinuousAgg *ca;\n\n\t\tca = ts_continuous_agg_find_by_relid(user_htoid);\n\n\t\tif (ca == NULL)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_TS_HYPERTABLE_NOT_EXIST),\n\t\t\t\t\t errmsg(\"\\\"%s\\\" is not a hypertable or a continuous aggregate\",\n\t\t\t\t\t\t\tget_rel_name(user_htoid))));\n\t\tmat_id = ca->data.mat_hypertable_id;\n\t\tht = ts_hypertable_get_by_id(mat_id);\n\t}\n\tAssert(ht != NULL);\n\treturn ht;\n}\n\nDatum\npolicy_retention_add_internal(Oid ht_oid, Oid window_type, Datum window_datum,\n\t\t\t\t\t\t\t  Interval *created_before, Interval default_schedule_interval,\n\t\t\t\t\t\t\t  bool if_not_exists, bool fixed_schedule, TimestampTz initial_start,\n\t\t\t\t\t\t\t  const char *timezone)\n{\n\tNameData application_name;\n\tint32 job_id;\n\tHypertable *hypertable;\n\tCache *hcache;\n\n\tOid owner_id = ts_hypertable_permissions_check(ht_oid, GetUserId());\n\tOid partitioning_type;\n\tconst Dimension *dim;\n\t/* Default scheduled interval for drop_chunks jobs is currently 1 day (24 hours) */\n\t/* Default max runtime should not be very long. Right now set to 5 minutes */\n\tInterval default_max_runtime = { .time = 5 * USECS_PER_MINUTE };\n\t/* Default retry period is currently 5 minutes */\n\tInterval default_retry_period = { .time = 5 * USECS_PER_MINUTE };\n\t/* Right now, there is an infinite number of retries for drop_chunks jobs */\n\tint default_max_retries = -1;\n\n\t/* Verify that the hypertable owner can create a background worker */\n\tts_bgw_job_validate_job_owner(owner_id);\n\n\t/* Make sure that an existing policy doesn't exist on this hypertable */\n\thcache = ts_hypertable_cache_pin();\n\thypertable = validate_drop_chunks_hypertable(hcache, ht_oid);\n\n\tdim = hyperspace_get_open_dimension(hypertable->space, 0);\n\tpartitioning_type = ts_dimension_get_partition_type(dim);\n\n\tList *jobs = ts_bgw_job_find_by_proc_and_hypertable_id(POLICY_RETENTION_PROC_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   FUNCTIONS_SCHEMA_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   hypertable->fd.id);\n\tif (jobs != NIL)\n\t{\n\t\tbool is_equal = false;\n\n\t\tif (!if_not_exists)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_DUPLICATE_OBJECT),\n\t\t\t\t\t errmsg(\"retention policy already exists for hypertable \\\"%s\\\"\",\n\t\t\t\t\t\t\tget_rel_name(ht_oid))));\n\n\t\tAssert(list_length(jobs) == 1);\n\t\tBgwJob *existing = linitial(jobs);\n\n\t\tif (OidIsValid(window_type))\n\t\t\tis_equal =\n\t\t\t\tpolicy_config_check_hypertable_lag_equality(existing->fd.config,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tPOL_RETENTION_CONF_KEY_DROP_AFTER,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tpartitioning_type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\twindow_type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\twindow_datum,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfalse /* isnull */);\n\t\telse\n\t\t{\n\t\t\tAssert(created_before != NULL);\n\t\t\tis_equal = policy_config_check_hypertable_lag_equality(\n\t\t\t\texisting->fd.config,\n\t\t\t\tPOL_RETENTION_CONF_KEY_DROP_CREATED_BEFORE,\n\t\t\t\tpartitioning_type,\n\t\t\t\tINTERVALOID,\n\t\t\t\tIntervalPGetDatum(created_before),\n\t\t\t\tfalse /* isnull */);\n\t\t}\n\n\t\tif (is_equal)\n\t\t{\n\t\t\t/* If all arguments are the same, do nothing */\n\t\t\tts_cache_release(&hcache);\n\t\t\tereport(NOTICE,\n\t\t\t\t\t(errmsg(\"retention policy already exists for hypertable \\\"%s\\\", skipping\",\n\t\t\t\t\t\t\tget_rel_name(ht_oid))));\n\t\t\tPG_RETURN_INT32(-1);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tts_cache_release(&hcache);\n\t\t\tereport(WARNING,\n\t\t\t\t\t(errmsg(\"retention policy already exists for hypertable \\\"%s\\\"\",\n\t\t\t\t\t\t\tget_rel_name(ht_oid)),\n\t\t\t\t\t errdetail(\"A policy already exists with different arguments.\"),\n\t\t\t\t\t errhint(\"Remove the existing policy before adding a new one.\")));\n\t\t\tPG_RETURN_INT32(-1);\n\t\t}\n\t}\n\n\tif (created_before)\n\t{\n\t\tAssert(!OidIsValid(window_type));\n\t\twindow_type = INTERVALOID;\n\t}\n\n\tif (IS_INTEGER_TYPE(partitioning_type))\n\t{\n\t\tContinuousAgg *cagg = ts_continuous_agg_find_by_relid(ht_oid);\n\n\t\tif ((IS_INTEGER_TYPE(window_type) && cagg == NULL &&\n\t\t\t !OidIsValid(ts_get_integer_now_func(dim, false))) ||\n\t\t\t(!IS_INTEGER_TYPE(window_type) && created_before == NULL))\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"invalid value for parameter %s\", POL_RETENTION_CONF_KEY_DROP_AFTER),\n\t\t\t\t\t errhint(\n\t\t\t\t\t\t \"Integer duration in \\\"drop_after\\\" with valid \\\"integer_now\\\" function\"\n\t\t\t\t\t\t \" or interval time duration\"\n\t\t\t\t\t\t \" in \\\"drop_created_before\\\" is required for hypertables with integer \"\n\t\t\t\t\t\t \"time dimension.\")));\n\t}\n\n\tif ((IS_TIMESTAMP_TYPE(partitioning_type) || IS_UUID_TYPE(partitioning_type)) &&\n\t\twindow_type != INTERVALOID)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid value for parameter %s\", POL_RETENTION_CONF_KEY_DROP_AFTER),\n\t\t\t\t errhint(\"Interval time duration is required for hypertable\"\n\t\t\t\t\t\t \" with timestamp or UUID time dimension.\")));\n\n\tJsonbParseState *parse_state = NULL;\n\n\tpushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);\n\tts_jsonb_add_int32(parse_state, POLICY_CONFIG_KEY_HYPERTABLE_ID, hypertable->fd.id);\n\n\tswitch (window_type)\n\t{\n\t\tcase INTERVALOID:\n\t\t\tif (created_before)\n\t\t\t\tts_jsonb_add_interval(parse_state,\n\t\t\t\t\t\t\t\t\t  POL_RETENTION_CONF_KEY_DROP_CREATED_BEFORE,\n\t\t\t\t\t\t\t\t\t  created_before);\n\t\t\telse\n\t\t\t\tts_jsonb_add_interval(parse_state,\n\t\t\t\t\t\t\t\t\t  POL_RETENTION_CONF_KEY_DROP_AFTER,\n\t\t\t\t\t\t\t\t\t  DatumGetIntervalP(window_datum));\n\t\t\tbreak;\n\t\tcase INT2OID:\n\t\t\tts_jsonb_add_int64(parse_state,\n\t\t\t\t\t\t\t   POL_RETENTION_CONF_KEY_DROP_AFTER,\n\t\t\t\t\t\t\t   DatumGetInt16(window_datum));\n\t\t\tbreak;\n\t\tcase INT4OID:\n\t\t\tts_jsonb_add_int64(parse_state,\n\t\t\t\t\t\t\t   POL_RETENTION_CONF_KEY_DROP_AFTER,\n\t\t\t\t\t\t\t   DatumGetInt32(window_datum));\n\t\t\tbreak;\n\t\tcase INT8OID:\n\t\t\tts_jsonb_add_int64(parse_state,\n\t\t\t\t\t\t\t   POL_RETENTION_CONF_KEY_DROP_AFTER,\n\t\t\t\t\t\t\t   DatumGetInt64(window_datum));\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"unsupported datatype for %s: %s\",\n\t\t\t\t\t\t\tPOL_RETENTION_CONF_KEY_DROP_AFTER,\n\t\t\t\t\t\t\tformat_type_be(window_type))));\n\t}\n\n\tJsonbValue *result = pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);\n\tJsonb *config = JsonbValueToJsonb(result);\n\n\t/* Next, insert a new job into jobs table */\n\tnamestrcpy(&application_name, \"Retention Policy\");\n\tNameData proc_name, proc_schema, check_schema, check_name;\n\tnamestrcpy(&proc_name, POLICY_RETENTION_PROC_NAME);\n\tnamestrcpy(&proc_schema, FUNCTIONS_SCHEMA_NAME);\n\tnamestrcpy(&check_name, POLICY_RETENTION_CHECK_NAME);\n\tnamestrcpy(&check_schema, FUNCTIONS_SCHEMA_NAME);\n\n\tjob_id = ts_bgw_job_insert_relation(&application_name,\n\t\t\t\t\t\t\t\t\t\t&default_schedule_interval,\n\t\t\t\t\t\t\t\t\t\t&default_max_runtime,\n\t\t\t\t\t\t\t\t\t\tdefault_max_retries,\n\t\t\t\t\t\t\t\t\t\t&default_retry_period,\n\t\t\t\t\t\t\t\t\t\t&proc_schema,\n\t\t\t\t\t\t\t\t\t\t&proc_name,\n\t\t\t\t\t\t\t\t\t\t&check_schema,\n\t\t\t\t\t\t\t\t\t\t&check_name,\n\t\t\t\t\t\t\t\t\t\towner_id,\n\t\t\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\t\t\tfixed_schedule,\n\t\t\t\t\t\t\t\t\t\thypertable->fd.id,\n\t\t\t\t\t\t\t\t\t\tconfig,\n\t\t\t\t\t\t\t\t\t\tinitial_start,\n\t\t\t\t\t\t\t\t\t\ttimezone);\n\n\tts_cache_release(&hcache);\n\n\tPG_RETURN_INT32(job_id);\n}\n\nDatum\npolicy_retention_add(PG_FUNCTION_ARGS)\n{\n\t/* behave like a strict function */\n\tif (PG_ARGISNULL(0) || PG_ARGISNULL(2))\n\t\tPG_RETURN_NULL();\n\n\tOid ht_oid = PG_GETARG_OID(0);\n\tDatum window_datum = PG_GETARG_DATUM(1);\n\tbool if_not_exists = PG_GETARG_BOOL(2);\n\tOid window_type = PG_ARGISNULL(1) ? InvalidOid : get_fn_expr_argtype(fcinfo->flinfo, 1);\n\tInterval default_schedule_interval =\n\t\tPG_ARGISNULL(3) ? (Interval) DEFAULT_RETENTION_SCHEDULE_INTERVAL : *PG_GETARG_INTERVAL_P(3);\n\tTimestampTz initial_start = PG_ARGISNULL(4) ? DT_NOBEGIN : PG_GETARG_TIMESTAMPTZ(4);\n\tbool fixed_schedule = !PG_ARGISNULL(4);\n\ttext *timezone = PG_ARGISNULL(5) ? NULL : PG_GETARG_TEXT_PP(5);\n\tchar *valid_timezone = NULL;\n\t// Interval *created_before = PG_ARGISNULL(6) ? NULL: PG_GETARG_INTERVAL_P(6);\n\tInterval *created_before = PG_GETARG_INTERVAL_P(6);\n\n\tts_feature_flag_check(FEATURE_POLICY);\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\tDatum retval;\n\n\t/* drop_after and created_before cannot be specified [or omitted] together */\n\tif ((PG_ARGISNULL(1) && PG_ARGISNULL(6)) || (!PG_ARGISNULL(1) && !PG_ARGISNULL(6)))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"need to specify one of \\\"drop_after\\\" or \\\"drop_created_before\\\"\")));\n\n\t/* if users pass in -infinity for initial_start, then use the current_timestamp instead */\n\tif (fixed_schedule)\n\t{\n\t\tts_bgw_job_validate_schedule_interval(&default_schedule_interval);\n\t\tif (TIMESTAMP_NOT_FINITE(initial_start))\n\t\t\tinitial_start = ts_timer_get_current_timestamp();\n\t}\n\n\tif (timezone != NULL)\n\t\tvalid_timezone = ts_bgw_job_validate_timezone(PG_GETARG_DATUM(5));\n\n\tretval = policy_retention_add_internal(ht_oid,\n\t\t\t\t\t\t\t\t\t\t   window_type,\n\t\t\t\t\t\t\t\t\t\t   window_datum,\n\t\t\t\t\t\t\t\t\t\t   created_before,\n\t\t\t\t\t\t\t\t\t\t   default_schedule_interval,\n\t\t\t\t\t\t\t\t\t\t   if_not_exists,\n\t\t\t\t\t\t\t\t\t\t   fixed_schedule,\n\t\t\t\t\t\t\t\t\t\t   initial_start,\n\t\t\t\t\t\t\t\t\t\t   valid_timezone);\n\tif (!TIMESTAMP_NOT_FINITE(initial_start))\n\t{\n\t\tint32 job_id = DatumGetInt32(retval);\n\t\tts_bgw_job_stat_upsert_next_start(job_id, initial_start);\n\t}\n\treturn retval;\n}\n\nDatum\npolicy_retention_remove_internal(Oid table_oid, bool if_exists)\n{\n\tCache *hcache;\n\tHypertable *hypertable;\n\n\thypertable = ts_hypertable_cache_get_cache_and_entry(table_oid, CACHE_FLAG_MISSING_OK, &hcache);\n\tif (!hypertable)\n\t{\n\t\tconst char *view_name = get_rel_name(table_oid);\n\n\t\tif (!view_name)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t\t errmsg(\"relation is not a hypertable or continuous aggregate\")));\n\t\telse\n\t\t{\n\t\t\tContinuousAgg *ca = ts_continuous_agg_find_by_relid(table_oid);\n\n\t\t\tif (!ca)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t\t\t errmsg(\"relation \\\"%s\\\" is not a hypertable or continuous aggregate\",\n\t\t\t\t\t\t\t\tview_name)));\n\t\t\thypertable = ts_hypertable_get_by_id(ca->data.mat_hypertable_id);\n\t\t}\n\t}\n\n\tAssert(hypertable != NULL);\n\tint32 ht_id = hypertable->fd.id;\n\tts_cache_release(&hcache);\n\tts_hypertable_permissions_check(table_oid, GetUserId());\n\n\tList *jobs = ts_bgw_job_find_by_proc_and_hypertable_id(POLICY_RETENTION_PROC_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   FUNCTIONS_SCHEMA_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   ht_id);\n\tif (jobs == NIL)\n\t{\n\t\tif (!if_exists)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t\t errmsg(\"retention policy not found for hypertable \\\"%s\\\"\",\n\t\t\t\t\t\t\tget_rel_name(table_oid))));\n\t\telse\n\t\t{\n\t\t\tereport(NOTICE,\n\t\t\t\t\t(errmsg(\"retention policy not found for hypertable \\\"%s\\\", skipping\",\n\t\t\t\t\t\t\tget_rel_name(table_oid))));\n\t\t\tPG_RETURN_BOOL(false);\n\t\t}\n\t}\n\tAssert(list_length(jobs) == 1);\n\tBgwJob *job = linitial(jobs);\n\n\tts_bgw_job_delete_by_id(job->fd.id);\n\n\tPG_RETURN_BOOL(true);\n}\n\nDatum\npolicy_retention_remove(PG_FUNCTION_ARGS)\n{\n\tOid table_oid = PG_GETARG_OID(0);\n\tbool if_exists = PG_GETARG_BOOL(1);\n\n\tts_feature_flag_check(FEATURE_POLICY);\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\treturn policy_retention_remove_internal(table_oid, if_exists);\n}\n"
  },
  {
    "path": "tsl/src/bgw_policy/retention_api.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n/* User-facing API functions */\nextern Datum policy_retention_add(PG_FUNCTION_ARGS);\nextern Datum policy_retention_proc(PG_FUNCTION_ARGS);\nextern Datum policy_retention_check(PG_FUNCTION_ARGS);\nextern Datum policy_retention_remove(PG_FUNCTION_ARGS);\n\nint64 policy_retention_get_drop_after_int(const Jsonb *config);\nInterval *policy_retention_get_drop_after_interval(const Jsonb *config);\nInterval *policy_retention_get_drop_created_before_interval(const Jsonb *config);\n\nDatum policy_retention_add_internal(Oid ht_oid, Oid window_type, Datum window_datum,\n\t\t\t\t\t\t\t\t\tInterval *created_before, Interval default_schedule_interval,\n\t\t\t\t\t\t\t\t\tbool if_not_exists, bool fixed_schedule,\n\t\t\t\t\t\t\t\t\tTimestampTz initial_start, const char *timezone);\nDatum policy_retention_remove_internal(Oid table_oid, bool if_exists);\n"
  },
  {
    "path": "tsl/src/build-defs.cmake",
    "content": "# Hide symbols by default in shared libraries\nset(CMAKE_C_VISIBILITY_PRESET \"hidden\")\n\nif(UNIX)\n  set(CMAKE_C_STANDARD 11)\n  set(CMAKE_SHARED_LINKER_FLAGS \"${CMAKE_SHARED_LINKER_FLAGS} -L${PG_LIBDIR}\")\n  set(CMAKE_MODULE_LINKER_FLAGS \"${CMAKE_MODULE_LINKER_FLAGS} -L${PG_LIBDIR}\")\n  set(CMAKE_C_FLAGS \"${PG_CFLAGS} ${PG_CPPFLAGS} ${CMAKE_C_FLAGS}\")\n  set(CMAKE_C_FLAGS_DEBUG \"${CMAKE_C_FLAGS_DEBUG} -g\")\nendif()\n\nif(APPLE)\n  if((${PG_VERSION_MAJOR} GREATER_EQUAL \"16\"))\n    set(CMAKE_SHARED_MODULE_SUFFIX \".dylib\")\n  endif()\n  set(CMAKE_SHARED_LINKER_FLAGS\n      \"${CMAKE_SHARED_LINKER_FLAGS} -multiply_defined suppress\")\n  set(CMAKE_MODULE_LINKER_FLAGS\n      \"${CMAKE_MODULE_LINKER_FLAGS} -multiply_defined suppress -Wl,-undefined,dynamic_lookup -bundle_loader ${PG_BINDIR}/postgres\"\n  )\nelseif(WIN32)\n  set(CMAKE_SHARED_LINKER_FLAGS \"${CMAKE_SHARED_LINKER_FLAGS} /MANIFEST:NO\")\n  set(CMAKE_MODULE_LINKER_FLAGS \"${CMAKE_MODULE_LINKER_FLAGS} /MANIFEST:NO\")\nendif()\n\n# PG_LDFLAGS can have strange values if not found, so we just add the flags if\n# they are defined.\nif(PG_LDFLAGS)\n  set(CMAKE_SHARED_LINKER_FLAGS \"${CMAKE_SHARED_LINKER_FLAGS} ${PG_LDFLAGS}\")\n  set(CMAKE_MODULE_LINKER_FLAGS \"${CMAKE_MODULE_LINKER_FLAGS} ${PG_LDFLAGS}\")\nendif()\n\ninclude_directories(${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/tsl/src\n                    ${PROJECT_BINARY_DIR}/src ${PROJECT_BINARY_DIR}/tsl/src)\n\ninclude_directories(SYSTEM ${PG_INCLUDEDIR_SERVER})\n\n# Only Windows and FreeBSD need the base include/ dir instead of\n# include/server/, and including both causes problems on Ubuntu where they\n# frequently get out of sync\nif(WIN32 OR (CMAKE_SYSTEM_NAME STREQUAL \"FreeBSD\"))\n  include_directories(SYSTEM ${PG_INCLUDEDIR})\nendif()\n\nif(WIN32)\n  link_directories(${PROJECT_BINARY_DIR}/src)\n  set(CMAKE_MODULE_LINKER_FLAGS\n      \"${CMAKE_MODULE_LINKER_FLAGS} ${PG_LIBDIR}/postgres.lib ws2_32.lib Version.lib ${PROJECT_NAME}-${PROJECT_VERSION_MOD}.lib\"\n  )\n  set(CMAKE_C_FLAGS \"-D_CRT_SECURE_NO_WARNINGS\")\n  include_directories(SYSTEM ${PG_INCLUDEDIR_SERVER}/port/win32)\n\n  if(MSVC)\n    include_directories(SYSTEM ${PG_INCLUDEDIR_SERVER}/port/win32_msvc)\n  endif(MSVC)\nendif(WIN32)\n\n# Name of library with test-specific code\nset(TSL_TESTS_LIB_NAME ${PROJECT_NAME}-tsl-tests)\n"
  },
  {
    "path": "tsl/src/chunk.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/attnum.h>\n#include <access/table.h>\n#include <catalog/catalog.h>\n#include <catalog/heap.h>\n#include <catalog/namespace.h>\n#include <catalog/pg_class.h>\n#include <catalog/pg_foreign_server.h>\n#include <catalog/pg_foreign_table.h>\n#include <fmgr.h>\n#include <foreign/foreign.h>\n#include <funcapi.h>\n#include <miscadmin.h>\n#include <nodes/lockoptions.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodes.h>\n#include <nodes/value.h>\n#include <parser/parse_coerce.h>\n#include <parser/parse_func.h>\n#include <utils/acl.h>\n#include <utils/builtins.h>\n#include <utils/elog.h>\n#include <utils/lsyscache.h>\n#include <utils/memutils.h>\n#include <utils/palloc.h>\n#include <utils/rel.h>\n#include <utils/syscache.h>\n\n#include \"chunk.h\"\n#include \"compression/compression.h\"\n#include \"debug_point.h\"\n#include \"extension.h\"\n#include \"hypertable.h\"\n#include \"utils.h\"\n\n/* Data in a frozen chunk cannot be modified. So any operation\n * that rewrites data for a frozen chunk will be blocked.\n * Note that a frozen chunk can still be dropped.\n */\nDatum\nchunk_freeze_chunk(PG_FUNCTION_ARGS)\n{\n\tOid chunk_relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\tChunk *chunk = ts_chunk_get_by_relid(chunk_relid, true);\n\tAssert(chunk != NULL);\n\tif (chunk->relkind == RELKIND_FOREIGN_TABLE)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"operation not supported on tiered chunk \\\"%s\\\"\",\n\t\t\t\t\t\tget_rel_name(chunk_relid))));\n\t}\n\tif (ts_chunk_is_frozen(chunk))\n\t\tPG_RETURN_BOOL(true);\n\t/* get Share lock. will wait for other concurrent transactions that are\n\t * modifying the chunk. Does not block SELECTs on the chunk.\n\t * Does not block other DDL on the chunk table.\n\t */\n\tDEBUG_WAITPOINT(\"freeze_chunk_before_lock\");\n\tLockRelationOid(chunk_relid, ShareLock);\n\tbool ret = ts_chunk_set_frozen(chunk);\n\tPG_RETURN_BOOL(ret);\n}\n\nDatum\nchunk_unfreeze_chunk(PG_FUNCTION_ARGS)\n{\n\tOid chunk_relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\tChunk *chunk = ts_chunk_get_by_relid(chunk_relid, true);\n\tAssert(chunk != NULL);\n\tif (chunk->relkind == RELKIND_FOREIGN_TABLE)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"operation not supported on foreign table \\\"%s\\\"\",\n\t\t\t\t\t\tget_rel_name(chunk_relid))));\n\t}\n\tif (!ts_chunk_is_frozen(chunk))\n\t\tPG_RETURN_BOOL(true);\n\t/* This is a previously frozen chunk. Only selects are permitted on this chunk.\n\t * This changes the status in the catalog to allow previously blocked operations.\n\t */\n\tbool ret = ts_chunk_unset_frozen(chunk);\n\tPG_RETURN_BOOL(ret);\n}\n\n/*\n * Invoke drop_chunks via fmgr so that the call can be deparsed and sent to\n * remote data nodes.\n *\n * Given that drop_chunks is an SRF, and has pseudo parameter types, we need\n * to provide a FuncExpr with type information for the deparser.\n *\n * Returns the number of dropped chunks.\n */\nint\nchunk_invoke_drop_chunks(Oid relid, Datum older_than, Datum older_than_type, bool use_creation_time)\n{\n\tEState *estate;\n\tExprContext *econtext;\n\tFuncExpr *fexpr;\n\tList *args = NIL;\n\tint num_results = 0;\n\tSetExprState *state;\n\tOid restype;\n\tOid func_oid;\n\tConst *TypeNullCons = makeNullConst(older_than_type, -1, InvalidOid);\n\tConst *IntervalVal = makeConst(older_than_type,\n\t\t\t\t\t\t\t\t   -1,\n\t\t\t\t\t\t\t\t   InvalidOid,\n\t\t\t\t\t\t\t\t   get_typlen(older_than_type),\n\t\t\t\t\t\t\t\t   older_than,\n\t\t\t\t\t\t\t\t   false,\n\t\t\t\t\t\t\t\t   get_typbyval(older_than_type));\n\tConst *argarr[DROP_CHUNKS_NARGS] = { makeConst(REGCLASSOID,\n\t\t\t\t\t\t\t\t\t\t\t\t   -1,\n\t\t\t\t\t\t\t\t\t\t\t\t   InvalidOid,\n\t\t\t\t\t\t\t\t\t\t\t\t   sizeof(relid),\n\t\t\t\t\t\t\t\t\t\t\t\t   ObjectIdGetDatum(relid),\n\t\t\t\t\t\t\t\t\t\t\t\t   false,\n\t\t\t\t\t\t\t\t\t\t\t\t   false),\n\t\t\t\t\t\t\t\t\t\t TypeNullCons,\n\t\t\t\t\t\t\t\t\t\t TypeNullCons,\n\t\t\t\t\t\t\t\t\t\t castNode(Const, makeBoolConst(false, true)),\n\t\t\t\t\t\t\t\t\t\t TypeNullCons,\n\t\t\t\t\t\t\t\t\t\t TypeNullCons };\n\tOid type_id[DROP_CHUNKS_NARGS] = { REGCLASSOID, ANYOID, ANYOID, BOOLOID, ANYOID, ANYOID };\n\tchar *const schema_name = ts_extension_schema_name();\n\tList *const fqn = list_make2(makeString(schema_name), makeString(DROP_CHUNKS_FUNCNAME));\n\n\tStaticAssertStmt(lengthof(type_id) == lengthof(argarr),\n\t\t\t\t\t \"argarr and type_id should have matching lengths\");\n\n\tfunc_oid = LookupFuncName(fqn, lengthof(type_id), type_id, false);\n\tAssert(func_oid); /* LookupFuncName should not return an invalid OID */\n\n\t/* decide whether to use \"older_than\" or \"drop_created_before\" */\n\tif (use_creation_time)\n\t\targarr[4] = IntervalVal;\n\telse\n\t\targarr[1] = IntervalVal;\n\n\t/* Prepare the function expr with argument list */\n\tget_func_result_type(func_oid, &restype, NULL);\n\n\tfor (size_t i = 0; i < lengthof(argarr); i++)\n\t\targs = lappend(args, argarr[i]);\n\n\tfexpr = makeFuncExpr(func_oid, restype, args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL);\n\tfexpr->funcretset = true;\n\n\t/* Execute the SRF */\n\testate = CreateExecutorState();\n\tecontext = CreateExprContext(estate);\n\tstate = ExecInitFunctionResultSet(&fexpr->xpr, econtext, NULL);\n\n\twhile (true)\n\t{\n\t\tExprDoneCond isdone;\n\t\tbool isnull;\n\n\t\tExecMakeFunctionResultSet(state, econtext, estate->es_query_cxt, &isnull, &isdone);\n\n\t\tif (isdone == ExprEndResult)\n\t\t\tbreak;\n\n\t\tif (!isnull)\n\t\t\tnum_results++;\n\t}\n\n\t/* Cleanup */\n\tFreeExprContext(econtext, false);\n\tFreeExecutorState(estate);\n\n\treturn num_results;\n}\n"
  },
  {
    "path": "tsl/src/chunk.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <chunk.h>\n#include <fmgr.h>\n\nextern Datum chunk_freeze_chunk(PG_FUNCTION_ARGS);\nextern Datum chunk_unfreeze_chunk(PG_FUNCTION_ARGS);\nextern int chunk_invoke_drop_chunks(Oid relid, Datum older_than, Datum older_than_type,\n\t\t\t\t\t\t\t\t\tbool use_creation_time);\nextern Datum chunk_merge_chunks(PG_FUNCTION_ARGS);\nextern Datum chunk_split_chunk(PG_FUNCTION_ARGS);\nextern void update_relstats(Relation catrel, Oid relid, BlockNumber num_pages, double ntuples);\nextern void compute_rel_vacuum_cutoffs(Relation rel, struct VacuumCutoffs *cutoffs);\nextern void chunk_update_constraints(const Chunk *chunk, const Hypercube *new_cube);\n"
  },
  {
    "path": "tsl/src/chunk_api.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n\n#include <access/htup.h>\n#include <access/htup_details.h>\n#include <access/multixact.h>\n#include <access/visibilitymap.h>\n#include <access/xact.h>\n#include <catalog/indexing.h>\n#include <catalog/pg_class.h>\n#include <catalog/pg_inherits.h>\n#include <catalog/pg_namespace.h>\n#include <catalog/pg_operator.h>\n#include <catalog/pg_type.h>\n#include <commands/vacuum.h>\n#include <fmgr.h>\n#include <funcapi.h>\n#include <miscadmin.h>\n#include <nodes/makefuncs.h>\n#include <storage/lmgr.h>\n#include <storage/lockdefs.h>\n#include <utils/array.h>\n#include <utils/builtins.h>\n#include <utils/jsonb.h>\n#include <utils/lsyscache.h>\n#include <utils/palloc.h>\n#include <utils/syscache.h>\n\n#include \"compat/compat.h\"\n#include \"chunk.h\"\n#include \"chunk_api.h\"\n#include \"debug_point.h\"\n#include \"error_utils.h\"\n#include \"errors.h\"\n#include \"hypercube.h\"\n#include \"hypertable_cache.h\"\n#include \"ts_catalog/array_utils.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"utils.h\"\n\n/*\n * Convert a hypercube to a JSONB value.\n *\n * For instance, a two-dimensional hypercube, with dimensions \"time\" and\n * \"device\", might look as follows:\n *\n * {\"time\": [1514419200000000, 1515024000000000],\n *  \"device\": [-9223372036854775808, 1073741823]}\n */\nstatic JsonbValue *\nhypercube_to_jsonb_value(Hypercube *hc, Hyperspace *hs, JsonbParseState **ps)\n{\n\tint i;\n\n\tAssert(hs->num_dimensions == hc->num_slices);\n\n\tpushJsonbValue(ps, WJB_BEGIN_OBJECT, NULL);\n\n\tfor (i = 0; i < hc->num_slices; i++)\n\t{\n\t\tJsonbValue k, v;\n\t\tchar *dim_name = NameStr(hs->dimensions[i].fd.column_name);\n\t\tDatum range_start =\n\t\t\tDirectFunctionCall1(int8_numeric, Int64GetDatum(hc->slices[i]->fd.range_start));\n\t\tDatum range_end =\n\t\t\tDirectFunctionCall1(int8_numeric, Int64GetDatum(hc->slices[i]->fd.range_end));\n\n\t\tAssert(hs->dimensions[i].fd.id == hc->slices[i]->fd.dimension_id);\n\n\t\tk.type = jbvString;\n\t\tk.val.string.len = strlen(dim_name);\n\t\tk.val.string.val = dim_name;\n\n\t\tpushJsonbValue(ps, WJB_KEY, &k);\n\t\tpushJsonbValue(ps, WJB_BEGIN_ARRAY, NULL);\n\n\t\tv.type = jbvNumeric;\n\t\tv.val.numeric = DatumGetNumeric(range_start);\n\t\tpushJsonbValue(ps, WJB_ELEM, &v);\n\t\tv.val.numeric = DatumGetNumeric(range_end);\n\t\tpushJsonbValue(ps, WJB_ELEM, &v);\n\n\t\tpushJsonbValue(ps, WJB_END_ARRAY, NULL);\n\t}\n\n\treturn pushJsonbValue(ps, WJB_END_OBJECT, NULL);\n}\n\n/*\n * Create a hypercube from a JSONB object.\n *\n * Takes a JSONB object with a hypercube's dimensional constraints and outputs\n * a Hypercube. The JSONB is the same format as output by\n * hypercube_to_jsonb_value() above, i.e.:\n *\n * {\"time\": [1514419200000000, 1515024000000000],\n *  \"device\": [-9223372036854775808, 1073741823]}\n */\nstatic Hypercube *\nhypercube_from_jsonb(Jsonb *json, const Hyperspace *hs, const char **parse_error)\n{\n\tJsonbIterator *it;\n\tJsonbIteratorToken type;\n\tJsonbValue v;\n\tHypercube *hc = NULL;\n\tconst char *err = NULL;\n\n\tit = JsonbIteratorInit(&json->root);\n\n\ttype = JsonbIteratorNext(&it, &v, false);\n\n\tif (type != WJB_BEGIN_OBJECT)\n\t{\n\t\terr = \"invalid JSON format\";\n\t\tgoto out_err;\n\t}\n\n\tif (v.val.object.nPairs != hs->num_dimensions)\n\t{\n\t\terr = \"invalid number of hypercube dimensions\";\n\t\tgoto out_err;\n\t}\n\n\thc = ts_hypercube_alloc(hs->num_dimensions);\n\n\twhile ((type = JsonbIteratorNext(&it, &v, false)))\n\t{\n\t\tint i;\n\t\tconst Dimension *dim;\n\t\tint64 range[2];\n\t\tconst char *name;\n\n\t\tif (type == WJB_END_OBJECT)\n\t\t\tbreak;\n\n\t\tif (type != WJB_KEY)\n\t\t{\n\t\t\terr = \"invalid JSON format\";\n\t\t\tgoto out_err;\n\t\t}\n\n\t\tname = pnstrdup(v.val.string.val, v.val.string.len);\n\t\tdim = ts_hyperspace_get_dimension_by_name(hs, DIMENSION_TYPE_ANY, name);\n\n\t\tif (NULL == dim)\n\t\t{\n\t\t\terr = psprintf(\"dimension \\\"%s\\\" does not exist in hypertable\", name);\n\t\t\tgoto out_err;\n\t\t}\n\n\t\ttype = JsonbIteratorNext(&it, &v, false);\n\n\t\tif (type != WJB_BEGIN_ARRAY)\n\t\t{\n\t\t\terr = \"invalid JSON format\";\n\t\t\tgoto out_err;\n\t\t}\n\n\t\tif (v.val.array.nElems != 2)\n\t\t{\n\t\t\terr = psprintf(\"unexpected number of dimensional bounds for dimension \\\"%s\\\"\", name);\n\t\t\tgoto out_err;\n\t\t}\n\n\t\tfor (i = 0; i < 2; i++)\n\t\t{\n\t\t\ttype = JsonbIteratorNext(&it, &v, false);\n\n\t\t\tif (type != WJB_ELEM)\n\t\t\t{\n\t\t\t\terr = \"invalid JSON format\";\n\t\t\t\tgoto out_err;\n\t\t\t}\n\n\t\t\tif (v.type == jbvString)\n\t\t\t{\n\t\t\t\tif (!IS_TIMESTAMP_TYPE(dim->fd.column_type))\n\t\t\t\t{\n\t\t\t\t\terr =\n\t\t\t\t\t\tpsprintf(\"constraint for dimension \\\"%s\\\" can be string only for date time\",\n\t\t\t\t\t\t\t\t name);\n\t\t\t\t\tgoto out_err;\n\t\t\t\t}\n\n\t\t\t\tchar *v_str = (char *) palloc(v.val.string.len + 1);\n\t\t\t\tmemcpy(v_str, v.val.string.val, v.val.string.len);\n\t\t\t\tv_str[v.val.string.len] = '\\0';\n\t\t\t\trange[i] = ts_time_value_from_arg(CStringGetDatum(v_str),\n\t\t\t\t\t\t\t\t\t\t\t\t  InvalidOid,\n\t\t\t\t\t\t\t\t\t\t\t\t  dim->fd.column_type,\n\t\t\t\t\t\t\t\t\t\t\t\t  true);\n\t\t\t}\n\t\t\telse if (v.type == jbvNumeric)\n\t\t\t{\n\t\t\t\trange[i] = DatumGetInt64(\n\t\t\t\t\tDirectFunctionCall1(numeric_int8, NumericGetDatum(v.val.numeric)));\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\terr = psprintf(\"constraint for dimension \\\"%s\\\" should be either numeric or string\",\n\t\t\t\t\t\t\t   name);\n\t\t\t\tgoto out_err;\n\t\t\t}\n\t\t}\n\n\t\ttype = JsonbIteratorNext(&it, &v, false);\n\n\t\tif (type != WJB_END_ARRAY)\n\t\t{\n\t\t\terr = \"invalid JSON format\";\n\t\t\tgoto out_err;\n\t\t}\n\n\t\tts_hypercube_add_slice_from_range(hc, dim->fd.id, range[0], range[1]);\n\t}\n\nout_err:\n\tif (NULL != parse_error)\n\t\t*parse_error = err;\n\n\tif (NULL != err)\n\t\treturn NULL;\n\n\treturn hc;\n}\n\nenum Anum_create_chunk\n{\n\tAnum_create_chunk_id = 1,\n\tAnum_create_chunk_hypertable_id,\n\tAnum_create_chunk_schema_name,\n\tAnum_create_chunk_table_name,\n\tAnum_create_chunk_relkind,\n\tAnum_create_chunk_slices,\n\tAnum_create_chunk_created,\n\t_Anum_create_chunk_max,\n};\n\n#define Natts_create_chunk (_Anum_create_chunk_max - 1)\n\nstatic HeapTuple\nchunk_form_tuple(Chunk *chunk, Hypertable *ht, TupleDesc tupdesc, bool created)\n{\n\tDatum values[Natts_create_chunk];\n\tbool nulls[Natts_create_chunk] = { false };\n\tJsonbParseState *ps = NULL;\n\tJsonbValue *jv = hypercube_to_jsonb_value(chunk->cube, ht->space, &ps);\n\n\tif (NULL == jv)\n\t\treturn NULL;\n\n\tvalues[AttrNumberGetAttrOffset(Anum_create_chunk_id)] = Int32GetDatum(chunk->fd.id);\n\tvalues[AttrNumberGetAttrOffset(Anum_create_chunk_hypertable_id)] =\n\t\tInt32GetDatum(chunk->fd.hypertable_id);\n\tvalues[AttrNumberGetAttrOffset(Anum_create_chunk_schema_name)] =\n\t\tNameGetDatum(&chunk->fd.schema_name);\n\tvalues[AttrNumberGetAttrOffset(Anum_create_chunk_table_name)] =\n\t\tNameGetDatum(&chunk->fd.table_name);\n\tvalues[AttrNumberGetAttrOffset(Anum_create_chunk_relkind)] = CharGetDatum(chunk->relkind);\n\tvalues[AttrNumberGetAttrOffset(Anum_create_chunk_slices)] =\n\t\tJsonbPGetDatum(JsonbValueToJsonb(jv));\n\tvalues[AttrNumberGetAttrOffset(Anum_create_chunk_created)] = BoolGetDatum(created);\n\n\treturn heap_form_tuple(tupdesc, values, nulls);\n}\n\nDatum\nchunk_show(PG_FUNCTION_ARGS)\n{\n\tOid chunk_relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\tChunk *chunk = ts_chunk_get_by_relid(chunk_relid, true);\n\tCache *hcache = ts_hypertable_cache_pin();\n\tHypertable *ht =\n\t\tts_hypertable_cache_get_entry(hcache, chunk->hypertable_relid, CACHE_FLAG_NONE);\n\tTupleDesc tupdesc;\n\tHeapTuple tuple;\n\n\tAssert(NULL != chunk);\n\tAssert(NULL != ht);\n\n\tif (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"function returning record called in context \"\n\t\t\t\t\t\t\"that cannot accept type record\")));\n\n\t/*\n\t * We use the create_chunk tuple for show_chunk, because they only differ\n\t * in the created column at the end. That column will not be included here\n\t * since it is not part of the tuple descriptor.\n\t */\n\ttuple = chunk_form_tuple(chunk, ht, tupdesc, false);\n\n\tts_cache_release(&hcache);\n\n\tif (NULL == tuple)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR), errmsg(\"could not create tuple from chunk\")));\n\n\tPG_RETURN_DATUM(HeapTupleGetDatum(tuple));\n}\n\nstatic void\ncheck_privileges_for_creating_chunk(Oid hyper_relid)\n{\n\tAclResult acl_result;\n\n\tacl_result = pg_class_aclcheck(hyper_relid, GetUserId(), ACL_INSERT);\n\tif (acl_result != ACLCHECK_OK)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),\n\t\t\t\t errmsg(\"permission denied for table \\\"%s\\\"\", get_rel_name(hyper_relid)),\n\t\t\t\t errdetail(\"Insert privileges required on \\\"%s\\\" to create chunks.\",\n\t\t\t\t\t\t   get_rel_name(hyper_relid))));\n}\n\nstatic Hypercube *\nget_hypercube_from_slices(Jsonb *slices, const Hypertable *ht)\n{\n\tHypercube *hc;\n\tconst char *parse_err;\n\n\thc = hypercube_from_jsonb(slices, ht->space, &parse_err);\n\n\tif (hc == NULL)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid hypercube for hypertable \\\"%s\\\"\",\n\t\t\t\t\t\tget_rel_name(ht->main_table_relid)),\n\t\t\t\t errdetail(\"%s\", parse_err)));\n\n\treturn hc;\n}\n\n/*\n * Create a chunk and its metadata.\n *\n * This function will create a chunk, either from an existing table or by\n * creating a new table. If chunk_table_relid is InvalidOid, the chunk table\n * will be created, otherwise the table referenced by the relid will be\n * used. The chunk will be associated with the hypertable given by\n * hypertable_relid.\n */\nDatum\nchunk_create(PG_FUNCTION_ARGS)\n{\n\tOid hypertable_relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\tJsonb *slices = PG_ARGISNULL(1) ? NULL : PG_GETARG_JSONB_P(1);\n\tconst char *schema_name = PG_ARGISNULL(2) ? NULL : PG_GETARG_CSTRING(2);\n\tconst char *table_name = PG_ARGISNULL(3) ? NULL : PG_GETARG_CSTRING(3);\n\tOid chunk_table_relid = PG_ARGISNULL(4) ? InvalidOid : PG_GETARG_OID(4);\n\tCache *hcache = ts_hypertable_cache_pin();\n\tHypertable *ht = ts_hypertable_cache_get_entry(hcache, hypertable_relid, CACHE_FLAG_NONE);\n\tHypercube *hc;\n\tChunk *chunk;\n\tTupleDesc tupdesc;\n\tHeapTuple tuple;\n\tbool created;\n\n\tAssert(NULL != ht);\n\tAssert(OidIsValid(ht->main_table_relid));\n\tcheck_privileges_for_creating_chunk(hypertable_relid);\n\n\tif (NULL == slices)\n\t\tereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"invalid slices\")));\n\n\tif (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"function returning record called in context \"\n\t\t\t\t\t\t\"that cannot accept type record\")));\n\n\thc = get_hypercube_from_slices(slices, ht);\n\tAssert(NULL != hc);\n\tchunk = ts_chunk_find_or_create_without_cuts(ht,\n\t\t\t\t\t\t\t\t\t\t\t\t hc,\n\t\t\t\t\t\t\t\t\t\t\t\t schema_name,\n\t\t\t\t\t\t\t\t\t\t\t\t table_name,\n\t\t\t\t\t\t\t\t\t\t\t\t chunk_table_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t &created);\n\tAssert(NULL != chunk);\n\n\ttuple = chunk_form_tuple(chunk, ht, tupdesc, created);\n\n\tts_cache_release(&hcache);\n\n\tif (NULL == tuple)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR), errmsg(\"could not create tuple from chunk\")));\n\n\tPG_RETURN_DATUM(HeapTupleGetDatum(tuple));\n}\n\n/*\n * Detach a chunk from a hypertable.\n */\nDatum\nchunk_detach(PG_FUNCTION_ARGS)\n{\n\tOid chunk_relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\tCache *hcache;\n\tHypertable *ht;\n\tChunk *chunk;\n\tOid ht_rel;\n\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\tif (!OidIsValid(chunk_relid))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"invalid chunk relation OID\")));\n\n\tDEBUG_WAITPOINT(\"chunk_detach_before_lock\");\n\n\tht_rel = ts_hypertable_id_to_relid(ts_chunk_get_hypertable_id_by_reloid(chunk_relid), true);\n\tif (!OidIsValid(ht_rel))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"hypertable not found for the chunk\")));\n\n\t/* Take the same locks taken by PostgreSQL partitioning to be consistent */\n\tLockRelationOid(ht_rel, ShareUpdateExclusiveLock);\n\tLockRelationOid(chunk_relid, AccessExclusiveLock);\n\n\tchunk = ts_chunk_get_by_relid(chunk_relid, true);\n\tAssert(chunk != NULL);\n\n\tht = ts_hypertable_cache_get_cache_and_entry(chunk->hypertable_relid, CACHE_FLAG_NONE, &hcache);\n\tAssert(ht != NULL);\n\n\tif (!object_ownercheck(RelationRelationId, ht->main_table_relid, GetUserId()))\n\t\taclcheck_error(ACLCHECK_NOT_OWNER,\n\t\t\t\t\t   get_relkind_objtype(get_rel_relkind(ht->main_table_relid)),\n\t\t\t\t\t   get_rel_name(ht->main_table_relid));\n\n\tif (ts_chunk_is_compressed(chunk))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot detach compressed chunk \\\"%s\\\"\", get_rel_name(chunk_relid)),\n\t\t\t\t errhint(\"Decompress the chunk first.\")));\n\n\tif (chunk->fd.osm_chunk)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot detach OSM chunk \\\"%s\\\"\", get_rel_name(chunk_relid))));\n\n\tAlterTableCmd cmd = {\n\t\t.type = T_AlterTableCmd,\n\t\t.subtype = AT_DropInherit,\n\t\t.def = (Node *) makeRangeVar(NameStr(ht->fd.schema_name), NameStr(ht->fd.table_name), 0),\n\t};\n\tAlterTableStmt stmt = {\n\t\t.type = T_AlterTableStmt,\n\t\t.cmds = list_make1(&cmd),\n\t\t.relation = makeRangeVar(NameStr(ht->fd.schema_name), NameStr(ht->fd.table_name), 0),\n\t};\n\n\tts_alter_table_with_event_trigger(chunk->table_id, (Node *) &stmt, list_make1(&cmd), false);\n\n\tts_chunk_detach_by_relid(chunk_relid);\n\n\tts_cache_release(&hcache);\n\n\tPG_RETURN_VOID();\n}\n\n/*\n * Attach an existing relation to a hypertable as a chunk.\n */\nDatum\nchunk_attach(PG_FUNCTION_ARGS)\n{\n\tOid ht_relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\tOid chunk_relid = PG_ARGISNULL(1) ? InvalidOid : PG_GETARG_OID(1);\n\tJsonb *slices = PG_ARGISNULL(2) ? NULL : PG_GETARG_JSONB_P(2);\n\tCache *hcache;\n\tHypertable *ht;\n\tHypercube *hc;\n\tChunk PG_USED_FOR_ASSERTS_ONLY *chunk;\n\tbool created;\n\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\tif (!OidIsValid(chunk_relid))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"invalid chunk relation OID\")));\n\n\tif (!OidIsValid(ht_relid))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid hypertable relation OID\")));\n\n\tif (chunk_relid == ht_relid)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"chunk relation cannot be the same as hypertable relation\")));\n\n\tif (NULL == slices)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid dimension slices argument\"),\n\t\t\t\t errhint(\"Provide a json-formatted definition of dimensional constraints for the \"\n\t\t\t\t\t\t \"chunk partition.\")));\n\n\tDEBUG_WAITPOINT(\"chunk_attach_before_lock\");\n\n\t/* Take the same locks taken by PostgreSQL partitioning to be consistent */\n\tLockRelationOid(ht_relid, ShareUpdateExclusiveLock);\n\tLockRelationOid(chunk_relid, AccessExclusiveLock);\n\n\t/* Only owner is allowed */\n\tif (!object_ownercheck(RelationRelationId, chunk_relid, GetUserId()))\n\t\taclcheck_error(ACLCHECK_NOT_OWNER,\n\t\t\t\t\t   get_relkind_objtype(get_rel_relkind(chunk_relid)),\n\t\t\t\t\t   get_rel_name(chunk_relid));\n\n\tif (is_inheritance_child(chunk_relid))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot attach chunk that is already a child of another table\")));\n\n\tif (ts_is_hypertable(chunk_relid))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot attach hypertable as a chunk\")));\n\n\t/* Check if the table still exists after taking the lock */\n\tif (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(chunk_relid)))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_TABLE),\n\t\t\t\t errmsg(\"relation with OID %u does not exist\", chunk_relid)));\n\n\tcheck_privileges_for_creating_chunk(ht_relid);\n\tht = ts_hypertable_cache_get_cache_and_entry(ht_relid, CACHE_FLAG_NONE, &hcache);\n\tAssert(ht != NULL);\n\n\thc = get_hypercube_from_slices(slices, ht);\n\tAssert(hc != NULL);\n\n\tchunk = ts_chunk_find_or_create_without_cuts(ht,\n\t\t\t\t\t\t\t\t\t\t\t\t hc,\n\t\t\t\t\t\t\t\t\t\t\t\t get_namespace_name(get_rel_namespace(chunk_relid)),\n\t\t\t\t\t\t\t\t\t\t\t\t get_rel_name(chunk_relid),\n\t\t\t\t\t\t\t\t\t\t\t\t chunk_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t &created);\n\tAssert(chunk != NULL);\n\tts_cache_release(&hcache);\n\n\tPG_RETURN_VOID();\n}\n"
  },
  {
    "path": "tsl/src/chunk_api.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include <chunk.h>\n\nextern Datum chunk_status(PG_FUNCTION_ARGS);\nextern Datum chunk_show(PG_FUNCTION_ARGS);\nextern Datum chunk_create(PG_FUNCTION_ARGS);\nextern Datum chunk_detach(PG_FUNCTION_ARGS);\nextern Datum chunk_attach(PG_FUNCTION_ARGS);\n"
  },
  {
    "path": "tsl/src/chunk_merge.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/multixact.h>\n#include <access/xact.h>\n#include <catalog/catalog.h>\n#include <catalog/dependency.h>\n#include <catalog/heap.h>\n#include <catalog/objectaddress.h>\n#include <catalog/pg_am.h>\n#include <catalog/pg_constraint.h>\n#include <catalog/pg_trigger_d.h>\n#include <commands/tablecmds.h>\n#include <commands/trigger.h>\n#include <executor/spi.h>\n#include <nodes/makefuncs.h>\n#include <nodes/parsenodes.h>\n#include <port.h>\n#include <storage/block.h>\n#include <storage/bufmgr.h>\n#include <storage/itemptr.h>\n#include <storage/lmgr.h>\n#include <storage/lockdefs.h>\n#include <utils/acl.h>\n#include <utils/elog.h>\n#include <utils/guc.h>\n#include <utils/memutils.h>\n#include <utils/palloc.h>\n#include <utils/rel.h>\n#include <utils/relcache.h>\n#include <utils/snapmgr.h>\n#include <utils/syscache.h>\n\n#include \"chunk.h\"\n#include \"chunk_index.h\"\n#include \"debug_point.h\"\n#include \"hypercube.h\"\n#include \"import/heapswap.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"ts_catalog/chunk_rewrite.h\"\n#include \"ts_catalog/compression_chunk_size.h\"\n\ntypedef struct RelationMergeInfo\n{\n\tOid relid;\n\tstruct VacuumCutoffs cutoffs;\n\tFormData_compression_chunk_size ccs;\n\tChunk *chunk;\n\tRelation rel;\n\tchar relpersistence;\n\tbool isresult;\n\tbool iscompressed_rel;\n\tItemPointerData chunk_rewrite_tid;\n\tList *ind_oids_old;\n\tList *ind_oids_new;\n} RelationMergeInfo;\n\ntypedef struct RelationMergeStats\n{\n\tOid relid;\n\tint32 chunk_id;\n\tFormData_compression_chunk_size ccs;\n\tBlockNumber num_pages;\n\tdouble reltuples;\n} RelationMergeStats;\n\nstatic void\nupdate_stats_after_merge(const RelationMergeStats *stats)\n{\n\t/* Update table stats */\n\tRelation relRelation = table_open(RelationRelationId, RowExclusiveLock);\n\tupdate_relstats(relRelation, stats->relid, stats->num_pages, stats->reltuples);\n\ttable_close(relRelation, RowExclusiveLock);\n\n\t/*\n\t * Update compression chunk size stats, but only if this is a\n\t * non-compressed chunk and at least one of the merged chunks was\n\t * compressed. In that case the merged metadata should be non-zero.\n\t */\n\tif (stats->ccs.compressed_heap_size > 0)\n\t{\n\t\t/*\n\t\t * The result relation should always be compressed because we pick the\n\t\t * first compressed one, if one exists.\n\t\t */\n\t\tFormData_compression_chunk_size form;\n\t\tmemcpy(&form, &stats->ccs, sizeof(form));\n\t\tts_compression_chunk_size_update(stats->chunk_id, &form);\n\t}\n}\n\nvoid\ncompute_rel_vacuum_cutoffs(Relation rel, struct VacuumCutoffs *cutoffs)\n{\n\tVacuumParams params;\n\n\tmemset(&params, 0, sizeof(VacuumParams));\n\tvacuum_get_cutoffs(rel, &params, cutoffs);\n\n\t/* Frozen Id should not go backwards */\n\tTransactionId relfrozenxid = rel->rd_rel->relfrozenxid;\n\n\tif (TransactionIdIsValid(relfrozenxid) &&\n\t\tTransactionIdPrecedes(cutoffs->FreezeLimit, relfrozenxid))\n\t\tcutoffs->FreezeLimit = relfrozenxid;\n\n\tMultiXactId relminmxid = rel->rd_rel->relminmxid;\n\n\tif (MultiXactIdIsValid(relminmxid) && MultiXactIdPrecedes(cutoffs->MultiXactCutoff, relminmxid))\n\t\tcutoffs->MultiXactCutoff = relminmxid;\n}\n\nstatic void\nmerge_chunks_finish(Oid new_relid, RelationMergeInfo *relinfos, int nrelids,\n\t\t\t\t\tconst RelationMergeStats *stats)\n{\n\tRelationMergeInfo *result_minfo = NULL;\n\n\tfor (int i = 0; i < nrelids; i++)\n\t{\n\t\tif (relinfos[i].isresult)\n\t\t{\n\t\t\tresult_minfo = &relinfos[i];\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tEnsure(result_minfo != NULL, \"no chunk to merge into found\");\n\tstruct VacuumCutoffs *cutoffs = &result_minfo->cutoffs;\n\tbool reindex = result_minfo->ind_oids_new == NIL;\n\n\tif (!reindex)\n\t{\n\t\tListCell *lc, *lc2;\n\n\t\tforboth (lc, result_minfo->ind_oids_old, lc2, result_minfo->ind_oids_new)\n\t\t{\n\t\t\tOid ind_old = lfirst_oid(lc);\n\t\t\tOid ind_new = lfirst_oid(lc2);\n\t\t\tOid mapped_tables[4];\n\n\t\t\tLockRelationOid(ind_old, AccessExclusiveLock);\n\t\t\tLockRelationOid(ind_new, AccessExclusiveLock);\n\n\t\t\t/* Zero out possible results from swapped_relation_files */\n\t\t\tmemset(mapped_tables, 0, sizeof(mapped_tables));\n\n\t\t\tts_swap_relation_files(ind_old,\n\t\t\t\t\t\t\t\t   ind_new,\n\t\t\t\t\t\t\t\t   false,\n\t\t\t\t\t\t\t\t   false,\n\t\t\t\t\t\t\t\t   true,\n\t\t\t\t\t\t\t\t   InvalidTransactionId,\n\t\t\t\t\t\t\t\t   InvalidMultiXactId,\n\t\t\t\t\t\t\t\t   mapped_tables);\n\t\t}\n\n\t\t/* The new indexes must be visible for deletion. */\n\t\tCommandCounterIncrement();\n\t}\n\n\tts_finish_heap_swap(result_minfo->relid,\n\t\t\t\t\t\tnew_relid,\n\t\t\t\t\t\tfalse, /* system catalog */\n\t\t\t\t\t\tfalse /* swap toast by content */,\n\t\t\t\t\t\tfalse, /* check constraints */\n\t\t\t\t\t\ttrue,  /* internal? */\n\t\t\t\t\t\treindex,\n\t\t\t\t\t\tcutoffs->FreezeLimit,\n\t\t\t\t\t\tcutoffs->MultiXactCutoff,\n\t\t\t\t\t\tresult_minfo->relpersistence);\n\n\tupdate_stats_after_merge(stats);\n\n\t/* Clear the chunk merge mapping for the result relation. The mappings for\n\t * non-result relations are deleted when the corresponding chunk is\n\t * dropped. Only done in concurrent mode as indicated by a valid TID. */\n\tif (ItemPointerIsValid(&result_minfo->chunk_rewrite_tid))\n\t\tts_chunk_rewrite_delete_by_tid(&result_minfo->chunk_rewrite_tid);\n\n\t/* Don't need to drop objects for internal compressed relations, they are\n\t * dropped when the main chunk is dropped. */\n\tif (result_minfo->iscompressed_rel)\n\t\treturn;\n\n\tif (ts_chunk_is_compressed(result_minfo->chunk))\n\t\tts_chunk_set_partial(result_minfo->chunk);\n\n\tAssert(stats->relid == result_minfo->relid);\n\n\t/*\n\t * Delete all the merged relations except the result one, since we are\n\t * keeping it for the heap swap.\n\t */\n\tObjectAddresses *objects = new_object_addresses();\n\n\tDEBUG_WAITPOINT(\"merge_chunks_before_drop\");\n\n\tfor (int i = 0; i < nrelids; i++)\n\t{\n\t\tRelationMergeInfo *relinfo = &relinfos[i];\n\t\tOid relid = relinfo->relid;\n\t\tObjectAddress object = {\n\t\t\t.classId = RelationRelationId,\n\t\t\t.objectId = relid,\n\t\t};\n\n\t\tif (!OidIsValid(relid))\n\t\t\tcontinue;\n\n\t\tif (!relinfo->isresult)\n\t\t{\n\t\t\t/* Cannot drop if relation is still open */\n\t\t\tAssert(relinfo->rel == NULL);\n\n\t\t\tif (relinfo->chunk)\n\t\t\t{\n\t\t\t\tconst Oid namespaceid = get_rel_namespace(relid);\n\t\t\t\tconst char *schemaname = get_namespace_name(namespaceid);\n\t\t\t\tconst char *tablename = get_rel_name(relid);\n\n\t\t\t\tts_chunk_delete_by_name(schemaname, tablename, DROP_RESTRICT);\n\t\t\t}\n\n\t\t\tadd_exact_object_address(&object, objects);\n\t\t}\n\t}\n\n\tperformMultipleDeletions(objects, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);\n\tfree_object_addresses(objects);\n}\n\nstatic int\ncmp_relations(const void *left, const void *right)\n{\n\tconst RelationMergeInfo *linfo = ((RelationMergeInfo *) left);\n\tconst RelationMergeInfo *rinfo = ((RelationMergeInfo *) right);\n\n\tif (linfo->chunk && rinfo->chunk)\n\t{\n\t\tconst Hypercube *lcube = linfo->chunk->cube;\n\t\tconst Hypercube *rcube = rinfo->chunk->cube;\n\n\t\tAssert(lcube->num_slices == rcube->num_slices);\n\n\t\tfor (int i = 0; i < lcube->num_slices; i++)\n\t\t{\n\t\t\tconst DimensionSlice *lslice = lcube->slices[i];\n\t\t\tconst DimensionSlice *rslice = rcube->slices[i];\n\n\t\t\tAssert(lslice->fd.dimension_id == rslice->fd.dimension_id);\n\n\t\t\t/* Compare start of range for the dimension */\n\t\t\tif (lslice->fd.range_start < rslice->fd.range_start)\n\t\t\t\treturn -1;\n\n\t\t\tif (lslice->fd.range_start > rslice->fd.range_start)\n\t\t\t\treturn 1;\n\n\t\t\t/* If start of range is equal, compare by end of range */\n\t\t\tif (lslice->fd.range_end < rslice->fd.range_end)\n\t\t\t\treturn -1;\n\n\t\t\tif (lslice->fd.range_end > rslice->fd.range_end)\n\t\t\t\treturn 1;\n\t\t}\n\n\t\t/* Should only reach here if partitioning is equal across all\n\t\t * dimensions. Fall back to comparing relids. */\n\t}\n\n\treturn pg_cmp_u32(linfo->relid, rinfo->relid);\n}\n\n/*\n * Check that the partition boundaries of two chunks align so that a new valid\n * hypercube can be formed if the chunks are merged. This check assumes that\n * the hypercubes are sorted so that cube2 \"follows\" cube1.\n *\n * The algorithm is simple and only allows merging along a single dimension in\n * the same merge. For example, these two cases are mergeable:\n *\n * ' ____\n * ' |__|\n * ' |__|\n *\n * ' _______\n * ' |__|__|\n *\n * while these cases are not mergeable:\n * '    ____\n * '  __|__|\n * ' |__|\n *\n * ' ______\n * ' |____|\n * ' |__|\n *\n *\n * The validation can handle merges of many chunks at once if they are\n * \"naively\" aligned and this function is called on chunk hypercubes in\n * \"partition order\":\n *\n * ' _____________\n * ' |__|__|__|__|\n *\n * However, the validation currently won't accept merges of multiple\n * dimensions at once:\n *\n * ' _____________\n * ' |__|__|__|__|\n * ' |__|__|__|__|\n *\n * It also cannot handle complicated merges of multi-dimensional partitioning\n * schemes like the one below.\n *\n * ' _________\n * ' |__a____|\n * ' |_b_|_c_|\n *\n * Merging a,b,c, should be possible but the validation currently cannot\n * handle such cases. Instead, it is necessary to first merge b,c. Then merge\n * a with the result (b,c) in a separate merge. Note that it is not possible\n * to merge only a,b or a,c.\n *\n * A future, more advanced, validation needs to handle corner-cases like the\n * one below that has gaps:\n *\n * ' _____________\n * ' |__|__|__|__|\n * ' |____|  |___|\n * '\n */\nstatic void\nvalidate_merge_possible(const Hypercube *cube1, const Hypercube *cube2)\n{\n\tint follow_edges = 0;\n\tint equal_edges = 0;\n\n\tAssert(cube1->num_slices == cube2->num_slices);\n\n\tfor (int i = 0; i < cube1->num_slices; i++)\n\t{\n\t\tconst DimensionSlice *slice1 = cube1->slices[i];\n\t\tconst DimensionSlice *slice2 = cube2->slices[i];\n\n\t\tif (ts_dimension_slices_equal(slice1, slice2))\n\t\t\tequal_edges++;\n\n\t\tif (slice1->fd.range_end == slice2->fd.range_start)\n\t\t\tfollow_edges++;\n\t}\n\n\tif (follow_edges != 1 || (cube1->num_slices - equal_edges) != 1)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot create new chunk partition boundaries\"),\n\t\t\t\t errhint(\"Try merging chunks that have adjacent partitions.\")));\n}\n\nstatic const ChunkConstraint *\nget_chunk_constraint_by_slice_id(const ChunkConstraints *ccs, int32 slice_id)\n{\n\tfor (int i = 0; i < ccs->num_constraints; i++)\n\t{\n\t\tconst ChunkConstraint *cc = &ccs->constraints[i];\n\n\t\tif (cc->fd.dimension_slice_id == slice_id)\n\t\t\treturn cc;\n\t}\n\n\treturn NULL;\n}\n\nvoid\nchunk_update_constraints(const Chunk *chunk, const Hypercube *new_cube)\n{\n\tCache *hcache;\n\tconst Hypertable *ht =\n\t\tts_hypertable_cache_get_cache_and_entry(chunk->hypertable_relid, CACHE_FLAG_NONE, &hcache);\n\tList *new_constraints = NIL;\n\n\tfor (int i = 0; i < new_cube->num_slices; i++)\n\t{\n\t\tconst DimensionSlice *old_slice = chunk->cube->slices[i];\n\t\tDimensionSlice *new_slice = new_cube->slices[i];\n\t\tconst ChunkConstraint *cc;\n\t\tScanTupLock tuplock = {\n\t\t\t.waitpolicy = LockWaitBlock,\n\t\t\t.lockmode = LockTupleShare,\n\t\t};\n\n\t\t/* If nothing changed in this dimension, move on to the next */\n\t\tif (ts_dimension_slices_equal(old_slice, new_slice))\n\t\t\tcontinue;\n\n\t\tcc = get_chunk_constraint_by_slice_id(chunk->constraints, old_slice->fd.id);\n\n\t\tif (cc)\n\t\t{\n\t\t\tObjectAddress constrobj = {\n\t\t\t\t.classId = ConstraintRelationId,\n\t\t\t\t.objectId = get_relation_constraint_oid(chunk->table_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tNameStr(cc->fd.constraint_name),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfalse),\n\t\t\t};\n\n\t\t\tperformDeletion(&constrobj, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);\n\n\t\t\t/* Create the new check constraint */\n\t\t\tconst Dimension *dim =\n\t\t\t\tts_hyperspace_get_dimension_by_id(ht->space, old_slice->fd.dimension_id);\n\t\t\tConstraint *constr =\n\t\t\t\tts_chunk_constraint_dimensional_create(dim,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   new_slice,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   NameStr(cc->fd.constraint_name));\n\n\t\t\t/* Constraint could be NULL, e.g., if the merged chunk covers the\n\t\t\t * entire range in a space dimension it needs no constraint. */\n\t\t\tif (constr != NULL)\n\t\t\t\tnew_constraints = lappend(new_constraints, constr);\n\t\t}\n\n\t\t/* Check if there's already a slice with the new range. If so, avoid\n\t\t * inserting a new slice. */\n\t\tif (!ts_dimension_slice_scan_for_existing(new_slice, &tuplock))\n\t\t{\n\t\t\tnew_slice->fd.id = -1;\n\t\t\tts_dimension_slice_insert(new_slice);\n\t\t\t/* A new Id should be assigned */\n\t\t\tAssert(new_slice->fd.id > 0);\n\t\t}\n\n\t\t/* Update the chunk constraint to point to the new slice ID */\n\t\tts_chunk_constraint_update_slice_id(chunk->fd.id, old_slice->fd.id, new_slice->fd.id);\n\n\t\t/* Delete the old slice if it is orphaned now */\n\t\tif (ts_chunk_constraint_scan_by_dimension_slice_id(old_slice->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   CurrentMemoryContext) == 0)\n\t\t{\n\t\t\tts_dimension_slice_delete_by_id(old_slice->fd.id, false);\n\t\t}\n\t}\n\n\t/* Add new check constraints, if any */\n\tif (new_constraints != NIL)\n\t{\n\t\t/* Adding a constraint should require AccessExclusivelock. It should\n\t\t * already be taken at this point, but specify it to be sure. */\n\t\tRelation rel = table_open(chunk->table_id, AccessExclusiveLock);\n\t\tAddRelationNewConstraints(rel,\n\t\t\t\t\t\t\t\t  NIL /* List *newColDefaults */,\n\t\t\t\t\t\t\t\t  new_constraints,\n\t\t\t\t\t\t\t\t  false /* allow_merge */,\n\t\t\t\t\t\t\t\t  true /* is_local */,\n\t\t\t\t\t\t\t\t  false /* is_internal */,\n\t\t\t\t\t\t\t\t  NULL /* query string */);\n\t\ttable_close(rel, NoLock);\n\t}\n\n\tts_cache_release(&hcache);\n}\n\nstatic void\nmerge_cubes(Hypercube *merged_cube, const Hypercube *cube)\n{\n\t/* Merge dimension slices */\n\tfor (int i = 0; i < cube->num_slices; i++)\n\t{\n\t\tconst DimensionSlice *slice = cube->slices[i];\n\t\tDimensionSlice *merged_slice = merged_cube->slices[i];\n\n\t\tAssert(slice->fd.dimension_id == merged_slice->fd.dimension_id);\n\n\t\tif (slice->fd.range_start < merged_slice->fd.range_start)\n\t\t\tmerged_slice->fd.range_start = slice->fd.range_start;\n\n\t\tif (slice->fd.range_end > merged_slice->fd.range_end)\n\t\t\tmerged_slice->fd.range_end = slice->fd.range_end;\n\t}\n}\n\n/*\n * Use anonymous settings value to disable multidim merges due to a bug in the\n * routing cache with non-aligned partitions/chunks.\n */\nstatic bool\nmerge_chunks_multidim_allowed(void)\n{\n\tconst char *multidim_merge_enabled =\n\t\tGetConfigOption(MAKE_EXTOPTION(\"enable_merge_multidim_chunks\"), true, false);\n\n\tif (multidim_merge_enabled == NULL)\n\t\treturn false;\n\n\treturn (pg_strcasecmp(\"on\", multidim_merge_enabled) == 0 ||\n\t\t\tpg_strcasecmp(\"1\", multidim_merge_enabled) == 0 ||\n\t\t\tpg_strcasecmp(\"true\", multidim_merge_enabled) == 0);\n}\n\n#if (PG_VERSION_NUM >= 170000 && PG_VERSION_NUM <= 170002)\n/*\n * Workaround for changed behavior in the relation rewrite code that appeared\n * in PostgreSQL 17.0, but was fixed in 17.3.\n *\n * Merge chunks uses the relation rewrite functionality from CLUSTER and\n * VACUUM FULL. This works for merge because, when writing into a non-empty\n * relation, new pages are appended while the existing pages remain the\n * same. In PG17.0, however, that changed so that existing pages in the\n * relation were zeroed out. The changed behavior was introduced as part of\n * this commit:\n *\n * https://github.com/postgres/postgres/commit/8af256524893987a3e534c6578dd60edfb782a77\n *\n * Fortunately, this was fixed in a follow up commit:\n *\n * https://github.com/postgres/postgres/commit/9695835538c2c8e9cd0048028b8c85e1bbf5c79c\n *\n * The fix is part of PG 17.3. Howevever, this still leaves PG 17.0 - 17.2\n * with different behavior.\n *\n * To make the merge chunks code work for the \"broken\" versions we make PG\n * believe the first rewrite operation is the size of the fully merged\n * relation so that we reserve the full space needed and then \"append\"\n * backwards into the zeroed space (see illustration below). By doing this, we\n * ensure that no valid data is zeroed out. The downside of this approach is\n * that there will be a lot of unnecessary writing of zero pages. Below is an\n * example of what the rewrite would look like for merging three relations\n * with one page each. When writing the first relation, PG believes the merged\n * relation already contains two pages when starting the rewrite. These two\n * existing pages will be zeroed. When writing the next relation we tell PG\n * that there is only one existing page in the merged relation, and so forth.\n *\n *  _____________\n *  |_0_|_0_|_x_|\n *  _________\n *  |_0_|_x_|\n *  _____\n *  |_x_|\n *\n *  Result:\n *  _____________\n *  |_x_|_x_|_x_|\n *\n */\nstatic BlockNumber merge_rel_nblocks = 0;\nstatic BlockNumber *blockoff = NULL;\nstatic const TableAmRoutine *old_routine = NULL;\nstatic TableAmRoutine routine = {};\n\n/*\n * TAM relation size function to make PG believe that the merged relation\n * contains as specific amount of existing data.\n */\nstatic uint64\npq17_workaround_merge_relation_size(Relation rel, ForkNumber forkNumber)\n{\n\tuint64 nblocks = merge_rel_nblocks;\n\n\tif (forkNumber == MAIN_FORKNUM)\n\t\treturn nblocks * BLCKSZ;\n\n\treturn old_routine->relation_size(rel, forkNumber);\n}\n\nstatic inline void\npg17_workaround_init(Relation rel, RelationMergeInfo *relinfos, int nrelids)\n{\n\troutine = *rel->rd_tableam;\n\troutine.relation_size = pq17_workaround_merge_relation_size;\n\told_routine = rel->rd_tableam;\n\trel->rd_tableam = &routine;\n\tblockoff = palloc(sizeof(BlockNumber) * nrelids);\n\tuint64 totalblocks = 0;\n\n\tfor (int i = 0; i < nrelids; i++)\n\t{\n\t\tblockoff[i] = (BlockNumber) totalblocks;\n\n\t\tif (relinfos[i].rel)\n\t\t{\n\t\t\ttotalblocks += smgrnblocks(RelationGetSmgr(relinfos[i].rel), MAIN_FORKNUM);\n\n\t\t\t/* Ensure the offsets don't overflow. For the merge itself, it is\n\t\t\t * assumed that the write will fail when writing too many blocks */\n\t\t\tEnsure(totalblocks <= MaxBlockNumber, \"max number of blocks exceeded for merge\");\n\t\t}\n\t}\n}\n\nstatic inline void\npg17_workaround_cleanup(Relation rel)\n{\n\tpfree(blockoff);\n\trel->rd_tableam = old_routine;\n}\n\nstatic inline RelationMergeInfo *\nget_relmergeinfo(RelationMergeInfo *relinfos, int nrelids, int i)\n{\n\tRelationMergeInfo *relinfo = &relinfos[nrelids - i - 1];\n\tmerge_rel_nblocks = blockoff[nrelids - i - 1];\n\treturn relinfo;\n}\n\n#else\n#define pg17_workaround_init(rel, relinfos, nrelids)\n#define pg17_workaround_cleanup(rel)\n#define get_relmergeinfo(relinfos, nrelids, i) &(relinfos)[i]\n#endif\n\n/* Update table stats */\n\nvoid\nupdate_relstats(Relation catrel, Oid relid, BlockNumber num_pages, double ntuples)\n{\n\tHeapTuple reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));\n\tif (!HeapTupleIsValid(reltup))\n\t\telog(ERROR, \"cache lookup failed for relation %u\", relid);\n\tForm_pg_class relform = (Form_pg_class) GETSTRUCT(reltup);\n\trelform->relpages = num_pages;\n\trelform->reltuples = ntuples;\n\n\tCatalogTupleUpdate(catrel, &reltup->t_self, reltup);\n\theap_freetuple(reltup);\n}\n\nstatic double\ncopy_table_data(Relation fromrel, Relation torel, struct VacuumCutoffs *cutoffs,\n\t\t\t\tstruct VacuumCutoffs *merged_cutoffs)\n{\n\tdouble num_tuples = 0.0;\n\tdouble tups_vacuumed = 0.0;\n\tdouble tups_recently_dead = 0.0;\n\n\ttable_relation_copy_for_cluster(fromrel,\n\t\t\t\t\t\t\t\t\ttorel,\n\t\t\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\tcutoffs->OldestXmin,\n\t\t\t\t\t\t\t\t\t&cutoffs->FreezeLimit,\n\t\t\t\t\t\t\t\t\t&cutoffs->MultiXactCutoff,\n\t\t\t\t\t\t\t\t\t&num_tuples,\n\t\t\t\t\t\t\t\t\t&tups_vacuumed,\n\t\t\t\t\t\t\t\t\t&tups_recently_dead);\n\n\telog(LOG,\n\t\t \"merged rows from \\\"%s\\\" into \\\"%s\\\": tuples %lf vacuumed %lf recently dead %lf\",\n\t\t RelationGetRelationName(fromrel),\n\t\t RelationGetRelationName(torel),\n\t\t num_tuples,\n\t\t tups_vacuumed,\n\t\t tups_recently_dead);\n\n\tif (TransactionIdPrecedes(merged_cutoffs->FreezeLimit, cutoffs->FreezeLimit))\n\t\tmerged_cutoffs->FreezeLimit = cutoffs->FreezeLimit;\n\n\tif (MultiXactIdPrecedes(merged_cutoffs->MultiXactCutoff, cutoffs->MultiXactCutoff))\n\t\tmerged_cutoffs->MultiXactCutoff = cutoffs->MultiXactCutoff;\n\n\treturn num_tuples;\n}\n\ntypedef struct SessionLockInfo\n{\n\tLockRelId locktag;\n\tLOCKMODE lockmode;\n} SessionLockInfo;\n\nstatic List *\nappend_rellock(List *rellocks, Relation rel, LOCKMODE lockmode, MemoryContext mcxt)\n{\n\tMemoryContext oldcontext = MemoryContextSwitchTo(mcxt);\n\tSessionLockInfo *lockinfo = palloc_object(SessionLockInfo);\n\tlockinfo->locktag = rel->rd_lockInfo.lockRelId;\n\tlockinfo->lockmode = lockmode;\n\trellocks = lappend(rellocks, lockinfo);\n\tMemoryContextSwitchTo(oldcontext);\n\n\treturn rellocks;\n}\n\nstatic Oid\nmerge_relinfos(RelationMergeInfo *relinfos, int nrelids, int mergeindex, LOCKMODE old_heap_lockmode,\n\t\t\t   List **rellocks, RelationMergeStats *stats, MemoryContext merge_mcxt,\n\t\t\t   bool concurrently)\n{\n\tRelationMergeInfo *result_minfo = &relinfos[mergeindex];\n\tRelation result_rel = result_minfo->rel;\n\n\tMemSet(stats, 0, sizeof(RelationMergeStats));\n\n\tif (result_rel == NULL)\n\t\treturn InvalidOid;\n\n\tstats->relid = result_minfo->relid;\n\tstats->chunk_id = result_minfo->chunk->fd.id;\n\n\tOid tablespace = result_rel->rd_rel->reltablespace;\n\tstruct VacuumCutoffs *merged_cutoffs = &result_minfo->cutoffs;\n\n\t/* Create the transient heap that will receive the re-ordered data */\n\tOid new_relid = make_new_heap(RelationGetRelid(result_rel),\n\t\t\t\t\t\t\t\t  tablespace,\n\t\t\t\t\t\t\t\t  result_rel->rd_rel->relam,\n\t\t\t\t\t\t\t\t  result_minfo->relpersistence,\n\t\t\t\t\t\t\t\t  old_heap_lockmode);\n\n\tRelation new_rel = table_open(new_relid, AccessExclusiveLock);\n\n\t*rellocks = append_rellock(*rellocks, new_rel, AccessExclusiveLock, merge_mcxt);\n\n\tpg17_workaround_init(new_rel, relinfos, nrelids);\n\n\t/* Step 3: write the data from all the rels into a new merged heap */\n\tfor (int i = 0; i < nrelids; i++)\n\t{\n\t\tRelationMergeInfo *relinfo = get_relmergeinfo(relinfos, nrelids, i);\n\t\tstruct VacuumCutoffs *cutoffs_i = &relinfo->cutoffs;\n\t\tdouble num_tuples = 0.0;\n\n\t\tif (relinfo->rel)\n\t\t{\n\t\t\tnum_tuples = copy_table_data(relinfo->rel, new_rel, cutoffs_i, merged_cutoffs);\n\t\t\tstats->reltuples += num_tuples;\n\n\t\t\tif (concurrently)\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * Mark this chunk as being rewritten by adding an entry in the\n\t\t\t\t * chunk_rewrite catalog. This allows cleaning up replacement\n\t\t\t\t * heaps in case the second transaction fails.\n\t\t\t\t *\n\t\t\t\t * This should not fail because any conflicting entries were\n\t\t\t\t * removed at the start of the merge and a lock on the relation\n\t\t\t\t * protects against adding new conflicting entries. However, if\n\t\t\t\t * a conflicting entry exists for some reason, adding a new\n\t\t\t\t * entry will fail with a unique constraint violation.\n\t\t\t\t */\n\t\t\t\tts_chunk_rewrite_add(relinfo->relid, new_relid);\n\t\t\t}\n\t\t}\n\n\t\t/*\n\t\t * Merge compression chunk size stats.\n\t\t *\n\t\t * Simply sum up the stats for all compressed relations that are\n\t\t * merged. Note that we don't add anything for non-compressed\n\t\t * relations that are merged because they don't have stats. This is a\n\t\t * bit weird because the data from uncompressed relations will not be\n\t\t * reflected in the stats of the merged chunk although the data is\n\t\t * part of the chunk.\n\t\t */\n\t\tstats->ccs.compressed_heap_size += relinfo->ccs.compressed_heap_size;\n\t\tstats->ccs.compressed_toast_size += relinfo->ccs.compressed_toast_size;\n\t\tstats->ccs.compressed_index_size += relinfo->ccs.compressed_index_size;\n\t\tstats->ccs.uncompressed_heap_size += relinfo->ccs.uncompressed_heap_size;\n\t\tstats->ccs.uncompressed_toast_size += relinfo->ccs.uncompressed_toast_size;\n\t\tstats->ccs.uncompressed_index_size += relinfo->ccs.uncompressed_index_size;\n\t\tstats->ccs.numrows_post_compression += relinfo->ccs.numrows_post_compression;\n\t\tstats->ccs.numrows_pre_compression += relinfo->ccs.numrows_pre_compression;\n\t\tstats->ccs.numrows_frozen_immediately += relinfo->ccs.numrows_frozen_immediately;\n\t}\n\n\t/*\n\t * Rebuild indexes on new heap (if in concurrent mode). In non-concurrent\n\t * mode, indexes are rebuilt as part of the heap swap (this is how PG\n\t * normally does it).\n\t */\n\tif (concurrently)\n\t{\n\t\t/* Create versions of the tables indexes for the new table */\n\t\tresult_minfo->ind_oids_new = ts_chunk_index_duplicate(result_rel->rd_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  new_rel->rd_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  &result_minfo->ind_oids_old,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  InvalidOid);\n\t}\n\n\tstats->num_pages = RelationGetNumberOfBlocks(new_rel);\n\tpg17_workaround_cleanup(new_rel);\n\n\t/* Now close all relations */\n\tfor (int i = 0; i < nrelids; i++)\n\t{\n\t\tRelationMergeInfo *relinfo = get_relmergeinfo(relinfos, nrelids, i);\n\n\t\t/*\n\t\t * Close the relations before the heap swap, but keep the locks until\n\t\t * end of transaction. Note that some relations might be NULL because\n\t\t * not all chunks are compressed. We still maintain a NULL entry in\n\t\t * the the array for the compressed chunk.\n\t\t */\n\t\tif (relinfo->rel)\n\t\t{\n\t\t\ttable_close(relinfo->rel, NoLock);\n\t\t\trelinfo->rel = NULL;\n\t\t}\n\t}\n\n\ttable_close(new_rel, NoLock);\n\n\treturn new_relid;\n}\n\n/*\n * Relock a relation being concurrently merged.\n *\n * The locking happens in the second transaction before swapping\n * the merged heaps.\n */\nstatic void\nrelock_rel(const Relation hyper_rel, RelationMergeInfo *rmi, LOCKMODE lockmode)\n{\n\trmi->rel = try_table_open(rmi->relid, lockmode);\n\n\tif (NULL == rmi->rel)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"chunk \\\"%s\\\" was removed concurrently\",\n\t\t\t\t\t\tNameStr(rmi->chunk->fd.table_name))));\n\n\t/* Re-lock toast tables, heap swap expects it */\n\tif (OidIsValid(rmi->rel->rd_rel->reltoastrelid))\n\t\tLockRelationOid(rmi->rel->rd_rel->reltoastrelid, lockmode);\n\n\t/* Get a lock on the rewrite entry for this merge */\n\tif (!ts_chunk_rewrite_get_with_lock(rmi->relid, NULL, &rmi->chunk_rewrite_tid))\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"chunk rewrite entry for \\\"%s\\\" does not exist\",\n\t\t\t\t\t\tRelationGetRelationName(rmi->rel))));\n\t}\n\n\t/* Now that we know the relations still exist, they can be\n\t * closed. We just need the locks. */\n\ttable_close(rmi->rel, NoLock);\n\trmi->rel = NULL;\n}\n\nstatic void\nlock_merged_rels(Oid hyper_relid, RelationMergeInfo *relinfos, RelationMergeInfo *crelinfos,\n\t\t\t\t int nrelids, LOCKMODE lockmode)\n{\n\tRelation hyper_rel = try_relation_open(hyper_relid, ShareUpdateExclusiveLock);\n\n\tif (NULL == hyper_rel)\n\t{\n\t\tconst RelationMergeInfo *rmi = &relinfos[0];\n\n\t\tif (NULL != rmi->rel)\n\t\t\telog(WARNING,\n\t\t\t\t \"dangling chunk \\\"%s\\\" remains, can't fix\",\n\t\t\t\t RelationGetRelationName(rmi->rel));\n\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"hypertable was removed concurrently\")));\n\t}\n\n\tfor (int i = 0; i < nrelids; i++)\n\t{\n\t\trelock_rel(hyper_rel, &relinfos[i], lockmode);\n\n\t\tif (OidIsValid(crelinfos[i].relid))\n\t\t\trelock_rel(hyper_rel, &crelinfos[i], lockmode);\n\t}\n\n\ttable_close(hyper_rel, NoLock);\n}\n\n/*\n * Relock the new relation heaps.\n *\n * This lock is taken in the second transaction before these heaps\n * are swapped with the old heaps.\n */\nstatic void\nrelock_new_rels(Oid new_relid, Oid new_crelid, LOCKMODE lockmode)\n{\n\t/*\n\t * Re-lock the new heaps, including any toast tables.\n\t */\n\tRelation new_rel = try_table_open(new_relid, lockmode);\n\n\tif (NULL == new_rel)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"new heap was removed concurrently during chunk merge\")));\n\n\tif (OidIsValid(new_rel->rd_rel->reltoastrelid))\n\t{\n\t\tRelation new_toast_rel = table_open(new_rel->rd_rel->reltoastrelid, lockmode);\n\t\tList *indexes = RelationGetIndexList(new_toast_rel);\n\t\tListCell *lc;\n\n\t\tforeach (lc, indexes)\n\t\t{\n\t\t\tOid indexrelid = lfirst_oid(lc);\n\t\t\tLockRelationOid(indexrelid, lockmode);\n\t\t}\n\t\ttable_close(new_toast_rel, NoLock);\n\t}\n\n\ttable_close(new_rel, NoLock);\n\n\tif (OidIsValid(new_crelid))\n\t{\n\t\tRelation new_crel = try_table_open(new_crelid, lockmode);\n\n\t\tif (NULL == new_crel)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t\t errmsg(\"new compressed heap was removed concurrently during chunk merge\")));\n\n\t\tif (OidIsValid(new_crel->rd_rel->reltoastrelid))\n\t\t{\n\t\t\tRelation new_toast_crel = table_open(new_crel->rd_rel->reltoastrelid, lockmode);\n\n\t\t\tList *indexes = RelationGetIndexList(new_toast_crel);\n\t\t\tListCell *lc;\n\n\t\t\tforeach (lc, indexes)\n\t\t\t{\n\t\t\t\tOid indexrelid = lfirst_oid(lc);\n\t\t\t\tLockRelationOid(indexrelid, lockmode);\n\t\t\t}\n\t\t\ttable_close(new_toast_crel, NoLock);\n\t\t}\n\n\t\ttable_close(new_crel, NoLock);\n\t}\n}\n\n/*\n * Merge N chunk relations into one chunk based on Oids.\n *\n * The input chunk relations are ordered according to partition ranges and the\n * \"first\" relation in that ordered list will be \"kept\" to hold the merged\n * data. The merged chunk will have its partition ranges updated to cover the\n * ranges of all the merged chunks.\n *\n * The merge happens via a heap rewrite, followed by a heap swap, essentially\n * the same approach implemented by CLUSTER and VACUUM FULL, but applied on\n * several relations in the same operation (many to one).\n *\n *\n * The heap swap approach handles visibility across all PG isolation levels,\n * as implemented by the cluster code.\n *\n * In the first step, all data from each chunk is written to a temporary heap\n * (accounting for vacuum, half-dead/visible, and frozen tuples). In the\n * second step, a heap swap is performed on one of the chunks and all metadata\n * is rewritten to handle, e.g., new partition ranges. Finally, the old chunks\n * are dropped, except for the chunk that received the heap swap.\n *\n * To be able to merge, the function checks that:\n *\n * - all relations are tables (not, e.g,, views)\n * - all relations use same (or compatible) storage on disk\n * - all relations are chunks (and not, e.g., foreign/OSM chunks)\n *\n * Compressed chunks can be merged, and in that case the non-compressed chunk\n * and the (internal) compressed chunk are merged in separate\n * steps. Currently, the merge does not move and recompress data across the\n * two relations so whatever data was compressed or not compressed prior to\n * the merge will remain in the same state after the merge.\n */\nDatum\nchunk_merge_chunks(PG_FUNCTION_ARGS)\n{\n\tArrayType *chunks_array = PG_ARGISNULL(0) ? NULL : PG_GETARG_ARRAYTYPE_P(0);\n\tbool concurrently = (PG_NARGS() > 1 && !PG_ARGISNULL(1)) ? PG_GETARG_BOOL(1) : false;\n\tDatum *relids;\n\tbool *nulls;\n\tint nrelids;\n\tRelationMergeInfo *relinfos;\n\tRelationMergeInfo *crelinfos; /* For compressed relations */\n\tOid hypertable_relid = InvalidOid;\n\tNameData hypertable_name;\n\tint32 hypertable_id = INVALID_HYPERTABLE_ID;\n\tHypercube *merged_cube = NULL;\n\tconst Hypercube *prev_cube = NULL;\n\tint mergeindex = -1;\n\tMemoryContext merge_cxt = NULL;\n\tList *rellocks = NIL;\n\tLOCKMODE lockmode = concurrently ? ExclusiveLock : AccessExclusiveLock;\n\n\tPreventCommandIfReadOnly(\"merge_chunks\");\n\n\tif (concurrently)\n\t\tPreventInTransactionBlock(true, \"merge_chunks\");\n\n\tif (chunks_array == NULL)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"no chunks to merge specified\")));\n\n\tdeconstruct_array(chunks_array,\n\t\t\t\t\t  REGCLASSOID,\n\t\t\t\t\t  sizeof(Oid),\n\t\t\t\t\t  true,\n\t\t\t\t\t  TYPALIGN_INT,\n\t\t\t\t\t  &relids,\n\t\t\t\t\t  &nulls,\n\t\t\t\t\t  &nrelids);\n\n\tif (nrelids < 2)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"must specify at least two chunks to merge\")));\n\n\t/* Create a private memory context that will survive transaction boundaries */\n\tmerge_cxt = AllocSetContextCreate(PortalContext, \"MergeChunksConcurrent\", ALLOCSET_SMALL_SIZES);\n\t/*\n\t * The RelationMergeInfos are allocated on the Portal context since they\n\t * need to survive across transactions in case of merge with\n\t * \"concurrently\".\n\t */\n\trelinfos = MemoryContextAllocZero(merge_cxt, sizeof(struct RelationMergeInfo) * nrelids);\n\tcrelinfos = MemoryContextAllocZero(merge_cxt, sizeof(struct RelationMergeInfo) * nrelids);\n\n\t/* Sort relids array in order to find duplicates and lock relations in\n\t * consistent order to avoid deadlocks. It doesn't matter that we don't\n\t * order the nulls array the same since we only care about all relids\n\t * being non-null. */\n\tqsort(relids, nrelids, sizeof(Datum), oid_cmp);\n\n\t/* Step 1: Do sanity checks and then prepare to sort rels in consistent order. */\n\tfor (int i = 0; i < nrelids; i++)\n\t{\n\t\tOid relid = DatumGetObjectId(relids[i]);\n\t\tRelationMergeInfo *relinfo = &relinfos[i];\n\t\tChunk *chunk;\n\t\tRelation rel;\n\t\tScanTupLock slice_lock = {\n\t\t\t.lockmode = LockTupleKeyShare,\n\t\t\t.waitpolicy = LockWaitBlock,\n\t\t\t.lockflags = TUPLE_LOCK_FLAG_FIND_LAST_VERSION,\n\t\t};\n\n\t\tif (nulls[i] || !OidIsValid(relid))\n\t\t\tereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"invalid relation\")));\n\n\t\tif (i > 0 && DatumGetObjectId(relids[i]) == DatumGetObjectId(relids[i - 1]))\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"duplicate relation \\\"%s\\\" in merge\",\n\t\t\t\t\t\t\tget_rel_name(DatumGetObjectId(relids[i])))));\n\n\t\t/* Lock the relation before doing other checks that can lock dependent\n\t\t * objects (this can otherwise lead to deadlocks with concurrent\n\t\t * operations). Note that if we take ExclusiveLock here to allow\n\t\t * readers while we are rewriting/merging the relations, the lock\n\t\t * needs to be upgraded to an AccessExclusiveLock later. This can also\n\t\t * lead to deadlocks.\n\t\t *\n\t\t * Ideally, we should probably take locks on all dependent objects as\n\t\t * well, at least on chunk-related objects that will be\n\t\t * dropped. Otherwise, that might also cause deadlocks later. For\n\t\t * example, if doing a concurrent DROP TABLE on one of the chunks will\n\t\t * lead to deadlock because it grabs locks on all dependencies before\n\t\t * dropping.\n\t\t *\n\t\t * However, for now we won't do that because that requires scanning\n\t\t * pg_depends and concurrent operations will probably fail anyway if\n\t\t * we remove the objects. We might as well fail with a deadlock.\n\t\t */\n\t\tchunk = ts_chunk_get_by_relid_locked(relid, lockmode, &slice_lock, false);\n\n\t\tif (chunk == NULL)\n\t\t{\n\t\t\t/*\n\t\t\t * The relation is not a chunk, or it was deleted. Use\n\t\t\t * try_relation_open() to figure out which case. It will\n\t\t\t * automatically check that the relation still exists after the\n\t\t\t * lock is acquired. We only need AccessShareLock here since we\n\t\t\t * know the relation isn't a chunk and we only want to generate an\n\t\t\t * informative error.\n\t\t\t */\n\t\t\trel = try_relation_open(relid, AccessShareLock);\n\n\t\t\tif (rel)\n\t\t\t{\n\t\t\t\tbool is_table = (rel->rd_rel->relkind == RELKIND_RELATION);\n\t\t\t\trelation_close(rel, AccessShareLock);\n\n\t\t\t\tif (is_table)\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t\t errmsg(\"can only merge hypertable chunks\")));\n\t\t\t\telse\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t\t errmsg(\"cannot merge non-table relations\")));\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_TABLE),\n\t\t\t\t\t\t errmsg(\"chunk does not exist\"),\n\t\t\t\t\t\t errdetail(\"The relation with OID %u might have been removed \"\n\t\t\t\t\t\t\t\t   \"by a concurrent merge or other operation.\",\n\t\t\t\t\t\t\t\t   relid)));\n\t\t\t}\n\t\t}\n\n\t\tif (chunk->fd.osm_chunk)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg(\"cannot merge OSM chunks\")));\n\n\t\tif (ts_chunk_is_frozen(chunk))\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"cannot merge frozen chunk \\\"%s.%s\\\" scheduled for tiering\",\n\t\t\t\t\t\t\tNameStr(chunk->fd.schema_name),\n\t\t\t\t\t\t\tNameStr(chunk->fd.table_name)),\n\t\t\t\t\t errhint(\"Untier the chunk before merging.\")));\n\n\t\tChunkRewriteDeleteResult rewrite_result = ts_chunk_rewrite_delete(relid, true);\n\n\t\tswitch (rewrite_result)\n\t\t{\n\t\t\tcase ChunkRewriteOngoing:\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_OBJECT_IN_USE),\n\t\t\t\t\t\t errmsg(\"chunk is being merged by another process\")));\n\t\t\t\tbreak;\n\t\t\tcase ChunkRewriteEntryDeleted:\n\t\t\tcase ChunkRewriteEntryDeletedAndTableDropped:\n\t\t\tcase ChunkRewriteEntryDoesNotExist:\n\t\t\t\tbreak;\n\t\t}\n\n\t\t/* Chunk already locked so we can get the relation directly from the cache */\n\t\trel = table_open(relid, NoLock);\n\n\t\t/* Only owner is allowed to merge */\n\t\tif (!object_ownercheck(RelationRelationId, relid, GetUserId()))\n\t\t\taclcheck_error(ACLCHECK_NOT_OWNER,\n\t\t\t\t\t\t   get_relkind_objtype(rel->rd_rel->relkind),\n\t\t\t\t\t\t   get_rel_name(relid));\n\n\t\t/* Lock toast table to prevent it from being concurrently vacuumed */\n\t\tif (rel->rd_rel->reltoastrelid)\n\t\t\tLockRelationOid(rel->rd_rel->reltoastrelid, lockmode);\n\n\t\t/* Add heap relation to the list of locked relations. We need this to\n\t\t * later grab session locks. */\n\t\trellocks = append_rellock(rellocks, rel, lockmode, merge_cxt);\n\n\t\t/*\n\t\t * Check for active uses of the relation in the current transaction,\n\t\t * including open scans and pending AFTER trigger events.\n\t\t */\n\t\tCheckTableNotInUse(rel, \"merge_chunks\");\n\n\t\tif (!merge_chunks_multidim_allowed())\n\t\t{\n\t\t\tCache *hcache;\n\t\t\tHypertable *ht = ts_hypertable_cache_get_cache_and_entry(chunk->hypertable_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t CACHE_FLAG_NONE,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t &hcache);\n\t\t\tEnsure(ht, \"missing hypertable for chunk\");\n\n\t\t\tif (ht->fd.num_dimensions > 1)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t errmsg(\"cannot merge chunk in multi-dimensional hypertable\")));\n\n\t\t\tts_cache_release(&hcache);\n\t\t}\n\n\t\t/*\n\t\t * Lock also internal compressed relation, if it exists.\n\t\t *\n\t\t * Don't fill in its MergeRelInfo until we sort relations in partition\n\t\t * order below, because the compressed relations need to be in the\n\t\t * same order.\n\t\t */\n\t\tif (chunk->fd.compressed_chunk_id != INVALID_CHUNK_ID)\n\t\t{\n\t\t\tOid crelid = ts_chunk_get_relid(chunk->fd.compressed_chunk_id, false);\n\t\t\tRelation crel = table_open(crelid, lockmode);\n\t\t\trellocks = append_rellock(rellocks, crel, lockmode, merge_cxt);\n\t\t\ttable_close(crel, NoLock);\n\n\t\t\tif (mergeindex == -1)\n\t\t\t\tmergeindex = i;\n\n\t\t\t/* Read compression chunk size stats */\n\t\t\tbool found = ts_compression_chunk_size_get(chunk->fd.id, &relinfo->ccs);\n\n\t\t\tif (!found)\n\t\t\t\telog(WARNING,\n\t\t\t\t\t \"missing compression chunk size stats for compressed chunk \\\"%s\\\"\",\n\t\t\t\t\t NameStr(chunk->fd.table_name));\n\t\t}\n\n\t\tif (hypertable_id == INVALID_HYPERTABLE_ID)\n\t\t{\n\t\t\thypertable_id = chunk->fd.hypertable_id;\n\t\t\thypertable_relid = chunk->hypertable_relid;\n\t\t\tnamestrcpy(&hypertable_name, get_rel_name(hypertable_relid));\n\t\t}\n\t\telse if (hypertable_id != chunk->fd.hypertable_id)\n\t\t{\n\t\t\tAssert(i > 0);\n\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"cannot merge chunks across different hypertables\"),\n\t\t\t\t\t errdetail(\"Chunk \\\"%s\\\" is part of hypertable \\\"%s\\\" while chunk \\\"%s\\\" is \"\n\t\t\t\t\t\t\t   \"part of hypertable \\\"%s\\\"\",\n\t\t\t\t\t\t\t   get_rel_name(chunk->table_id),\n\t\t\t\t\t\t\t   get_rel_name(chunk->hypertable_relid),\n\t\t\t\t\t\t\t   get_rel_name(relinfos[i - 1].chunk->table_id),\n\t\t\t\t\t\t\t   get_rel_name(relinfos[i - 1].chunk->hypertable_relid))));\n\t\t}\n\n\t\t/*\n\t\t * It might not be possible to merge two chunks with different\n\t\t * storage, so better safe than sorry for now.\n\t\t */\n\t\tOid amoid = rel->rd_rel->relam;\n\n\t\tif (amoid != HEAP_TABLE_AM_OID)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"access method \\\"%s\\\" is not supported for merge\",\n\t\t\t\t\t\t\tget_am_name(amoid))));\n\n\t\trelinfo->relid = relid;\n\t\trelinfo->rel = rel;\n\t\trelinfo->relpersistence = rel->rd_rel->relpersistence;\n\t\t/*\n\t\t * Make sure the chunk is on the merge_cxt to survive\n\t\t * transaction when merge is concurrent\n\t\t */\n\t\tMemoryContext old_mcxt = MemoryContextSwitchTo(merge_cxt);\n\t\trelinfo->chunk = ts_chunk_copy(chunk);\n\t\tMemoryContextSwitchTo(old_mcxt);\n\t}\n\n\t/* No compressed chunk found, so use index 0 for resulting merged chunk */\n\tif (mergeindex == -1)\n\t\tmergeindex = 0;\n\n\trelinfos[mergeindex].isresult = true;\n\n\t/* Sort rels in partition order (in case of chunks). This is necessary to\n\t * validate that a merge is possible. */\n\tqsort(relinfos, nrelids, sizeof(RelationMergeInfo), cmp_relations);\n\n\t/*\n\t * Step 2: Check alignment/mergeability and create the merged hypercube\n\t * (partition ranges).\n\t *\n\t * Also, create the final MergeRelationInfo array for any compressed\n\t * chunks in the same sort order as the non-compressed ones.\n\t */\n\tfor (int i = 0; i < nrelids; i++)\n\t{\n\t\tconst Chunk *chunk = relinfos[i].chunk;\n\n\t\tAssert(chunk != NULL);\n\n\t\tif (merged_cube == NULL)\n\t\t{\n\t\t\t/*\n\t\t\t * Make sure the chunk is on the merge_cxt to survive\n\t\t\t * transaction when merge is concurrent\n\t\t\t */\n\t\t\tMemoryContext old_mcxt = MemoryContextSwitchTo(merge_cxt);\n\t\t\tmerged_cube = ts_hypercube_copy(chunk->cube);\n\t\t\tMemoryContextSwitchTo(old_mcxt);\n\t\t\tAssert(prev_cube == NULL);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tAssert(chunk->cube->num_slices == merged_cube->num_slices);\n\t\t\tAssert(prev_cube != NULL);\n\t\t\tvalidate_merge_possible(prev_cube, chunk->cube);\n\t\t\tmerge_cubes(merged_cube, chunk->cube);\n\t\t}\n\n\t\tprev_cube = chunk->cube;\n\t\tcompute_rel_vacuum_cutoffs(relinfos[i].rel, &relinfos[i].cutoffs);\n\n\t\t/*\n\t\t * Fill in the compressed mergerelinfo array here after final sort of\n\t\t * rels so that the two arrays have the same order.\n\t\t */\n\t\tif (chunk->fd.compressed_chunk_id != INVALID_CHUNK_ID)\n\t\t{\n\t\t\tRelationMergeInfo *crelinfo = &crelinfos[i];\n\t\t\tChunk *cchunk = ts_chunk_get_by_id(chunk->fd.compressed_chunk_id, true);\n\t\t\t/*\n\t\t\t * Allocate on merge_cxt to survive transaction end in\n\t\t\t * concurrent mode.\n\t\t\t */\n\t\t\tMemoryContext old_mcxt = MemoryContextSwitchTo(merge_cxt);\n\t\t\tcrelinfo->chunk = ts_chunk_copy(cchunk);\n\t\t\tMemoryContextSwitchTo(old_mcxt);\n\n\t\t\tcrelinfo->relid = crelinfo->chunk->table_id;\n\t\t\tcrelinfo->rel = table_open(crelinfo->relid, lockmode);\n\t\t\tcrelinfo->isresult = relinfos[i].isresult;\n\t\t\tcrelinfo->iscompressed_rel = true;\n\t\t\tcrelinfo->relpersistence = crelinfo->rel->rd_rel->relpersistence;\n\t\t\tcompute_rel_vacuum_cutoffs(crelinfos[i].rel, &crelinfos[i].cutoffs);\n\t\t\trellocks = append_rellock(rellocks, crelinfo->rel, lockmode, merge_cxt);\n\t\t}\n\n\t\t/* Need to update the index of the result (merged) relation after\n\t\t * resort */\n\t\tif (relinfos[i].isresult)\n\t\t\tmergeindex = i;\n\t}\n\n\tDEBUG_WAITPOINT(\"merge_chunks_before_rewrite\");\n\n\t/*\n\t * Step 3: create new heaps and copy all data.\n\t *\n\t * Now merge all the data into a new temporary heap relation. Do it\n\t * separately for the non-compressed and compressed relations.\n\t */\n\tRelationMergeStats merge_stats, cmerge_stats;\n\n\tOid new_relid = merge_relinfos(relinfos,\n\t\t\t\t\t\t\t\t   nrelids,\n\t\t\t\t\t\t\t\t   mergeindex,\n\t\t\t\t\t\t\t\t   lockmode,\n\t\t\t\t\t\t\t\t   &rellocks,\n\t\t\t\t\t\t\t\t   &merge_stats,\n\t\t\t\t\t\t\t\t   merge_cxt,\n\t\t\t\t\t\t\t\t   concurrently);\n\tOid new_crelid = merge_relinfos(crelinfos,\n\t\t\t\t\t\t\t\t\tnrelids,\n\t\t\t\t\t\t\t\t\tmergeindex,\n\t\t\t\t\t\t\t\t\tlockmode,\n\t\t\t\t\t\t\t\t\t&rellocks,\n\t\t\t\t\t\t\t\t\t&cmerge_stats,\n\t\t\t\t\t\t\t\t\tmerge_cxt,\n\t\t\t\t\t\t\t\t\tconcurrently);\n\n\t/*\n\t * From here on we only need the relinfos arrays.\n\t */\n\tpfree(relids);\n\tpfree(nulls);\n\n\tif (concurrently)\n\t{\n\t\tListCell *lc;\n\n\t\t/*\n\t\t * In concurrent mode, get a session-level lock on each chunk table to\n\t\t * protect against modifications across transactions.\n\t\t *\n\t\t * A session lock also allows us to release all locks on other\n\t\t * objects, reducing the risk of deadlocks when we upgrade the\n\t\t * ExclusiveLock session lock to an AccessExclusivelock transaction\n\t\t * lock to do the heap swap.\n\t\t */\n\t\tforeach (lc, rellocks)\n\t\t{\n\t\t\tSessionLockInfo *lockinfo = (SessionLockInfo *) lfirst(lc);\n\n\t\t\tLockRelationIdForSession(&lockinfo->locktag, lockinfo->lockmode);\n\t\t}\n\n\t\tDEBUG_WAITPOINT(\"merge_chunks_before_first_commit\");\n\n\t\t/*\n\t\t * Check if we are being called from another procedure that has an SPI\n\t\t * context. In that case, we need to use SPI calls to start a new\n\t\t * transaction.\n\t\t */\n\t\tif (SPI_inside_nonatomic_context())\n\t\t{\n\t\t\t/*\n\t\t\t * Commit and retain transaction semantics. The commit_and_chain\n\t\t\t * call will automatically start a new transaction.\n\t\t\t */\n\t\t\tSPI_commit_and_chain();\n\t\t}\n\t\telse\n\t\t{\n\t\t\tPopActiveSnapshot();\n\t\t\tCommitTransactionCommand();\n\t\t\tStartTransactionCommand();\n\t\t}\n\n\t\tDEBUG_WAITPOINT(\"merge_chunks_after_first_commit\");\n\n\t\t/*\n\t\t * In new transaction, get a new snapshot and take AccessExclusivelock\n\t\t * on all merge relations.\n\t\t */\n\t\tPushActiveSnapshot(GetTransactionSnapshot());\n\t\tlock_merged_rels(hypertable_relid, relinfos, crelinfos, nrelids, AccessExclusiveLock);\n\t\trelock_new_rels(new_relid, new_crelid, AccessExclusiveLock);\n\t}\n\telse\n\t{\n\t\t/* Make new table stats visible */\n\t\tCommandCounterIncrement();\n\t}\n\n\t/*\n\t * Step 4: Finish the merge by swapping relation files.\n\t */\n\tDEBUG_WAITPOINT(\"merge_chunks_before_heap_swap\");\n\n\tmerge_chunks_finish(new_relid, relinfos, nrelids, &merge_stats);\n\n\tif (OidIsValid(new_crelid))\n\t\tmerge_chunks_finish(new_crelid, crelinfos, nrelids, &cmerge_stats);\n\n\t/*\n\t * Step 5: Update the dimensional metadata and constraints for the chunk\n\t * we are keeping.\n\t */\n\tif (merged_cube)\n\t{\n\t\tRelationMergeInfo *result_minfo = &relinfos[mergeindex];\n\t\tAssert(result_minfo->chunk);\n\n\t\tDEBUG_WAITPOINT(\"merge_chunks_before_constraints\");\n\t\tchunk_update_constraints(result_minfo->chunk, merged_cube);\n\t\tts_hypercube_free(merged_cube);\n\t}\n\n\t/*\n\t * Cleanup for concurrent mode.\n\t */\n\tif (concurrently)\n\t{\n\t\tListCell *lc;\n\n\t\tforeach (lc, rellocks)\n\t\t{\n\t\t\tSessionLockInfo *lockinfo = (SessionLockInfo *) lfirst(lc);\n\n\t\t\tUnlockRelationIdForSession(&lockinfo->locktag, lockinfo->lockmode);\n\t\t}\n\n\t\tPopActiveSnapshot();\n\t}\n\n\tMemoryContextDelete(merge_cxt);\n\n\tDEBUG_ERROR_INJECTION(\"merge_chunks_fail\");\n\tDEBUG_WAITPOINT(\"merge_chunks_before_exit\");\n\n\tPG_RETURN_VOID();\n}\n"
  },
  {
    "path": "tsl/src/chunk_split.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/multixact.h>\n#include <access/rewriteheap.h>\n#include <catalog/dependency.h>\n#include <catalog/heap.h>\n#include <catalog/indexing.h>\n#include <catalog/pg_am.h>\n#include <catalog/pg_collation.h>\n#include <catalog/pg_constraint.h>\n#include <commands/tablecmds.h>\n#include <nodes/lockoptions.h>\n#include <storage/bufmgr.h>\n#include <storage/lockdefs.h>\n#include <utils/acl.h>\n#include <utils/snapshot.h>\n#include <utils/syscache.h>\n\n#include <math.h>\n\n#include \"chunk.h\"\n#include \"compression/api.h\"\n#include \"compression/compression.h\"\n#include \"compression/create.h\"\n#include \"debug_point.h\"\n#include \"hypercube.h\"\n#include \"partitioning.h\"\n#include \"trigger.h\"\n#include \"ts_catalog/array_utils.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"ts_catalog/compression_chunk_size.h\"\n/*\n * The split_chunk() procedure currently only supports two-way split.\n */\n#define SPLIT_FACTOR 2\n\ntypedef struct SplitContext SplitContext;\n\n/*\n * SplitPointInfo\n *\n * Information about point where split happens, including column/dimension and\n * type we split along. Needed to route tuples to correct result relation.\n */\ntypedef struct SplitPoint\n{\n\tconst Dimension *dim;\n\tint64 point; /* Point at which we split */\n\t/*\n\t * Function to route a tuple to a result relation during the split. The\n\t * function's implementation is different depending on whether compressed\n\t * or non-compressed relations are split.\n\t */\n\tHeapTuple (*route_next_tuple)(TupleTableSlot *slot, SplitContext *scontext, int *routing_index);\n} SplitPoint;\n\n/*\n * CompressedSplitPoint\n *\n * Version of SplitPoint for a compressed relation.\n *\n * Since tuples are compressed, routing happens on min/max-metadata so column\n * references are different.\n */\ntypedef struct CompressedSplitPoint\n{\n\tSplitPoint base;\n\tAttrNumber attnum_min;\n\tAttrNumber attnum_max;\n\tAttrNumber attnum_count;\n\tTupleDesc noncompressed_tupdesc;\n} CompressedSplitPoint;\n\ntypedef struct RewriteStats\n{\n\tint64 tuples_written;\n\tint64 tuples_alive;\n\tint64 tuples_recently_dead;\n\tint64 tuples_in_segments;\n} RewriteStats;\n\n/*\n * RelationWriteState\n *\n * State used to rewrite the resulting relations when splitting. Holds\n * information about attribute mappings in case the relations have different\n * tuple descriptors (e.g., due to dropped or added columns).\n */\ntypedef struct RelationWriteState\n{\n\tBulkInsertState bistate;\n\tTupleTableSlot *dstslot;\n\tRewriteState rwstate;\n\tRelation targetrel;\n\tDatum *values;\n\tbool *isnull;\n\t/*\n\t * Tuple mapping is needed in case the old relation has dropped\n\t * columns. New relations (as result of split) are \"clean\" without dropped\n\t * columns. The tuple map converts tuples between the source and\n\t * destination chunks.\n\t */\n\tTupleConversionMap *tupmap;\n\tRowCompressor compressor;\n\tRewriteStats stats;\n} RelationWriteState;\n\n/*\n * SplitContext\n *\n * Main state for doing a split.\n */\ntypedef struct SplitContext\n{\n\tRelation rel; /* Relation/chunk being split */\n\tSplitPoint *sp;\n\tstruct VacuumCutoffs cutoffs;\n\tint split_factor; /* Number of relations to split into */\n\t/* Array of rewrite states used to write the new relations. Size of\n\t * split_factor. */\n\tRelationWriteState *rws;\n\tint rws_index; /* Index into rsi array indicating currently routed\n\t\t\t\t\t* relation. Set to -1 if no currently routed relation. */\n} SplitContext;\n\n/*\n * SplitRelationInfo\n *\n * Information about the result relations in a split. Also, information about\n * the number of tuples written to the relation is returned in the struct.\n */\ntypedef struct SplitRelationInfo\n{\n\tOid relid;\t\t/* The relid of the result relation */\n\tint32 chunk_id; /* The corresponding chunk's ID */\n\tbool heap_swap; /* The original relation getting split will receive a heap\n\t\t\t\t\t * swap. New chunks won't get a heap swap since they are\n\t\t\t\t\t * new and not visible to anyone else. */\n\tRewriteStats stats;\n} SplitRelationInfo;\n\nstatic void\nrelation_split_info_init(RelationWriteState *rws, Relation srcrel, Oid target_relid,\n\t\t\t\t\t\t struct VacuumCutoffs *cutoffs)\n{\n\trws->targetrel = table_open(target_relid, AccessExclusiveLock);\n\trws->bistate = GetBulkInsertState();\n\n\trws->rwstate = begin_heap_rewrite(srcrel,\n\t\t\t\t\t\t\t\t\t  rws->targetrel,\n\t\t\t\t\t\t\t\t\t  cutoffs->OldestXmin,\n\t\t\t\t\t\t\t\t\t  cutoffs->FreezeLimit,\n\t\t\t\t\t\t\t\t\t  cutoffs->MultiXactCutoff);\n\n\trws->tupmap =\n\t\tconvert_tuples_by_name(RelationGetDescr(srcrel), RelationGetDescr(rws->targetrel));\n\n\t/* Create tuple slot for new partition. */\n\trws->dstslot = table_slot_create(rws->targetrel, NULL);\n\tExecStoreAllNullTuple(rws->dstslot);\n\n\trws->values = (Datum *) palloc0(RelationGetDescr(srcrel)->natts * sizeof(Datum));\n\trws->isnull = (bool *) palloc0(RelationGetDescr(srcrel)->natts * sizeof(bool));\n}\n\nstatic void\nrelation_split_info_cleanup(RelationWriteState *rws, int ti_options)\n{\n\tExecDropSingleTupleTableSlot(rws->dstslot);\n\tFreeBulkInsertState(rws->bistate);\n\ttable_finish_bulk_insert(rws->targetrel, ti_options);\n\tend_heap_rewrite(rws->rwstate);\n\ttable_close(rws->targetrel, NoLock);\n\tpfree(rws->values);\n\tpfree(rws->isnull);\n\n\tif (rws->tupmap)\n\t\tfree_conversion_map(rws->tupmap);\n\n\trws->targetrel = NULL;\n\trws->bistate = NULL;\n\trws->dstslot = NULL;\n\trws->tupmap = NULL;\n\trws->values = NULL;\n\trws->isnull = NULL;\n}\n\n/*\n * Reconstruct and rewrite the given tuple.\n *\n * Mostly taken from heapam module.\n *\n * When splitting a relation in two, the old relation is retained for one of\n * the result relations while the other is created new. This might lead to a\n * situation where the two result relations have different attribute mappings\n * because the old one could have dropped columns while the new one is \"clean\"\n * without dropped columns. Therefore, the rewrite function needs to account\n * for this when the tuple is rewritten.\n */\nstatic void\nreform_and_rewrite_tuple(HeapTuple tuple, Relation srcrel, RelationWriteState *rws)\n{\n\tTupleDesc oldTupDesc = RelationGetDescr(srcrel);\n\tTupleDesc newTupDesc = RelationGetDescr(rws->targetrel);\n\tHeapTuple tupcopy;\n\n\tif (rws->tupmap)\n\t{\n\t\t/*\n\t\t * If this is the \"new\" relation, the tuple map might be different\n\t\t * from the \"source\" relation.\n\t\t */\n\t\ttupcopy = execute_attr_map_tuple(tuple, rws->tupmap);\n\t}\n\telse\n\t{\n\t\tint i;\n\n\t\theap_deform_tuple(tuple, oldTupDesc, rws->values, rws->isnull);\n\n\t\t/* Be sure to null out any dropped columns if this is the \"old\"\n\t\t * relation. A relation created new doesn't have dropped columns. */\n\t\tfor (i = 0; i < newTupDesc->natts; i++)\n\t\t{\n\t\t\tif (TupleDescAttr(newTupDesc, i)->attisdropped)\n\t\t\t\trws->isnull[i] = true;\n\t\t}\n\n\t\ttupcopy = heap_form_tuple(newTupDesc, rws->values, rws->isnull);\n\t}\n\n\t/* The heap rewrite module does the rest */\n\trewrite_heap_tuple(rws->rwstate, tuple, tupcopy);\n\theap_freetuple(tupcopy);\n}\n\nstatic Datum\nslot_get_partition_value(TupleTableSlot *slot, AttrNumber attnum, const SplitPoint *sp)\n{\n\tbool isnull = false;\n\tDatum value = slot_getattr(slot, attnum, &isnull);\n\n\t/*\n\t * Space-partition columns can have NULL values, but we only support\n\t * splits on time dimensions at the moment.\n\t */\n\tEnsure(!isnull, \"unexpected NULL value in partitioning column\");\n\n\t/*\n\t * Both time and space dimensions can have partitioning functions, so it\n\t * is necessary to always check for a function.\n\t */\n\tif (NULL != sp->dim->partitioning)\n\t{\n\t\tOid collation;\n\n\t\tcollation =\n\t\t\tTupleDescAttr(slot->tts_tupleDescriptor, AttrNumberGetAttrOffset(attnum))->attcollation;\n\t\tvalue = ts_partitioning_func_apply(sp->dim->partitioning, collation, value);\n\t}\n\n\treturn value;\n}\n\n/*\n * Compute the partition/routing index for a tuple.\n *\n * Returns 0 or 1 for first or second partition, respectively.\n */\nstatic int\nroute_tuple(TupleTableSlot *slot, const SplitPoint *sp)\n{\n\tOid dimtype = ts_dimension_get_partition_type(sp->dim);\n\tDatum value = slot_get_partition_value(slot, sp->dim->column_attno, sp);\n\tint64 point = ts_time_value_to_internal(value, dimtype);\n\n\t/*\n\t * Route to partition based on new boundaries. Only 2-way split is\n\t * supported now, so routing is easy. An N-way split requires, e.g.,\n\t * binary search.\n\t */\n\treturn (point < sp->point) ? 0 : 1;\n}\n\n/*\n * Compute the partition/routing index for a compressed tuple.\n *\n * Returns 0 or 1 for first or second partition, and -1 if the split point\n * falls within the given compressed tuple.\n */\nstatic int\nroute_compressed_tuple(TupleTableSlot *slot, const SplitPoint *sp)\n{\n\tconst CompressedSplitPoint *csp = (const CompressedSplitPoint *) sp;\n\tOid dimtype = ts_dimension_get_partition_type(sp->dim);\n\tDatum min_value = slot_get_partition_value(slot, csp->attnum_min, sp);\n\tDatum max_value = slot_get_partition_value(slot, csp->attnum_max, sp);\n\tint64 min_point = ts_time_value_to_internal(min_value, dimtype);\n\tint64 max_point = ts_time_value_to_internal(max_value, dimtype);\n\n\tif (max_point < sp->point)\n\t\treturn 0;\n\n\tif (min_point >= sp->point)\n\t\treturn 1;\n\n\tAssert(min_point < sp->point && max_point >= sp->point);\n\treturn -1;\n}\n\n/*\n * Route a tuple to its partition.\n *\n * Only a 2-way split is supported at this time.\n *\n * For every non-NULL tuple returned, the routing_index will be set to 0 for\n * the first partition, and 1 for then second.\n */\nstatic HeapTuple\nroute_next_non_compressed_tuple(TupleTableSlot *slot, SplitContext *scontext, int *routing_index)\n{\n\tif (scontext->rws_index != -1)\n\t{\n\t\tscontext->rws_index = -1;\n\t\treturn NULL;\n\t}\n\n\tscontext->rws_index = route_tuple(slot, scontext->sp);\n\t*routing_index = scontext->rws_index;\n\n\treturn ExecFetchSlotHeapTuple(slot, false, NULL);\n}\n\n/*\n * Route a compressed tuple (segment) to its corresponding result partition\n * for the split.\n *\n * If the split point is found to be within the segment, it needs to be split\n * and sub-segments returned instead. Therefore, this function should be\n * called in a loop until returning NULL (no sub-segments left). If the\n * segment is not split, only the original segment is returned.\n *\n * For every non-NULL tuple returned, the routing_index will be set to 0 for\n * the first partition, and 1 for the second.\n */\nstatic HeapTuple\nroute_next_compressed_tuple(TupleTableSlot *slot, SplitContext *scontext, int *routing_index)\n{\n\tCompressedSplitPoint *csp = (CompressedSplitPoint *) scontext->sp;\n\n\tAssert(scontext->rws_index >= -1 && scontext->rws_index <= scontext->split_factor);\n\n\tif (scontext->rws_index == scontext->split_factor)\n\t{\n\t\t/* Nothing more to route for this tuple, so return NULL */\n\t\tscontext->rws_index = -1;\n\t\t*routing_index = -1;\n\t\treturn NULL;\n\t}\n\telse if (scontext->rws_index >= 0)\n\t{\n\t\t/* Segment is being split and recompressed into a sub-segment per\n\t\t * partition. Return the sub-segments until done. */\n\t\tAssert(scontext->rws_index < scontext->split_factor);\n\t\tRelationWriteState *rws = &scontext->rws[scontext->rws_index];\n\t\tHeapTuple new_tuple = row_compressor_build_tuple(&rws->compressor);\n\t\tHeapTuple old_tuple = ExecFetchSlotHeapTuple(slot, false, NULL);\n\n\t\t/* Copy over visibility information from the original segment\n\t\t * tuple. First copy the HeapTupleFields holding the xmin and\n\t\t * xmax. Then copy the infomask which has, among other things, the\n\t\t * frozen flag bits. */\n\t\tmemcpy(&new_tuple->t_data->t_choice.t_heap,\n\t\t\t   &old_tuple->t_data->t_choice.t_heap,\n\t\t\t   sizeof(HeapTupleFields));\n\n\t\tnew_tuple->t_data->t_infomask &= ~HEAP_XACT_MASK;\n\t\tnew_tuple->t_data->t_infomask2 &= ~HEAP2_XACT_MASK;\n\t\tnew_tuple->t_data->t_infomask |= old_tuple->t_data->t_infomask & HEAP_XACT_MASK;\n\n\t\tnew_tuple->t_tableOid = RelationGetRelid(rws->targetrel);\n\n\t\trow_compressor_clear_batch(&rws->compressor, false);\n\t\trws->stats.tuples_in_segments += rws->compressor.rowcnt_pre_compression;\n\t\t*routing_index = scontext->rws_index;\n\t\tscontext->rws_index++;\n\t\trow_compressor_close(&rws->compressor);\n\n\t\treturn new_tuple;\n\t}\n\n\t*routing_index = route_compressed_tuple(slot, scontext->sp);\n\n\tif (*routing_index == -1)\n\t{\n\t\t/*\n\t\t * The split point is within the current compressed segment. It needs\n\t\t * to be split across the partitions by decompressing and\n\t\t * recompressing into sub-segments.\n\t\t */\n\t\tHeapTuple tuple;\n\t\tCompressionSettings *csettings =\n\t\t\tts_compression_settings_get_by_compress_relid(RelationGetRelid(scontext->rel));\n\n\t\ttuple = ExecFetchSlotHeapTuple(slot, false, NULL);\n\n\t\tRowDecompressor decompressor =\n\t\t\tbuild_decompressor(slot->tts_tupleDescriptor, csp->noncompressed_tupdesc);\n\n\t\theap_deform_tuple(tuple,\n\t\t\t\t\t\t  decompressor.in_desc,\n\t\t\t\t\t\t  decompressor.compressed_datums,\n\t\t\t\t\t\t  decompressor.compressed_is_nulls);\n\n\t\tint nrows = decompress_batch(&decompressor);\n\n\t\t/*\n\t\t * Initialize a compressor for each new partition.\n\t\t */\n\t\tfor (int i = 0; i < scontext->split_factor; i++)\n\t\t{\n\t\t\tRelationWriteState *rws = &scontext->rws[i];\n\t\t\trow_compressor_init(&rws->compressor,\n\t\t\t\t\t\t\t\tcsettings,\n\t\t\t\t\t\t\t\tcsp->noncompressed_tupdesc,\n\t\t\t\t\t\t\t\tRelationGetDescr(scontext->rws[i].targetrel));\n\t\t}\n\n\t\t/*\n\t\t * Route each decompressed tuple to its corresponding partition's\n\t\t * compressor.\n\t\t */\n\t\tfor (int i = 0; i < nrows; i++)\n\t\t{\n\t\t\tint routing_index = route_tuple(decompressor.decompressed_slots[i], scontext->sp);\n\t\t\tAssert(routing_index == 0 || routing_index == 1);\n\t\t\tRelationWriteState *rws = &scontext->rws[routing_index];\n\t\t\t/*\n\t\t\t * Since we're splitting a segment, the new segments will be\n\t\t\t * ordered like the original segment. Also, there is no risk of\n\t\t\t * the segments getting too big since we are only making segments\n\t\t\t * smaller.\n\t\t\t */\n\t\t\trow_compressor_append_ordered_slot(&rws->compressor,\n\t\t\t\t\t\t\t\t\t\t\t   decompressor.decompressed_slots[i]);\n\t\t}\n\n\t\trow_decompressor_close(&decompressor);\n\t\tscontext->rws_index = 0;\n\n\t\t/*\n\t\t * Call this function again to return the sub-segments.\n\t\t */\n\t\treturn route_next_compressed_tuple(slot, scontext, routing_index);\n\t}\n\n\t/* Update tuple count stats for compressed data */\n\tbool isnull;\n\tDatum count = slot_getattr(slot, csp->attnum_count, &isnull);\n\tscontext->rws[*routing_index].stats.tuples_in_segments += DatumGetInt32(count);\n\n\t/*\n\t * The compressed tuple (segment) can be routed without splitting it.\n\t */\n\tAssert(*routing_index >= 0 && *routing_index < scontext->split_factor);\n\tscontext->rws_index = scontext->split_factor;\n\n\treturn ExecFetchSlotHeapTuple(slot, false, NULL);\n}\n\nstatic double\ncopy_tuples_for_split(SplitContext *scontext)\n{\n\tRelation srcrel = scontext->rel;\n\tTupleTableSlot *srcslot;\n\tMemoryContext oldcxt;\n\tEState *estate;\n\tExprContext *econtext;\n\tTableScanDesc scan;\n\tSplitPoint *sp = scontext->sp;\n\n\testate = CreateExecutorState();\n\n\t/* Create the tuple slot */\n\tsrcslot = table_slot_create(srcrel, NULL);\n\n\t/*\n\t * Scan through the rows using SnapshotAny to see everything so that we\n\t * can transfer tuples that are deleted or updated but still visible to\n\t * concurrent transactions.\n\t */\n\tscan = table_beginscan(srcrel, SnapshotAny, 0, NULL);\n\n\t/*\n\t * Switch to per-tuple memory context and reset it for each tuple\n\t * produced, so we don't leak memory.\n\t */\n\tecontext = GetPerTupleExprContext(estate);\n\toldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));\n\n\t/*\n\t * Read all the data from the split relation and route the tuples to the\n\t * new partitions. Do some vacuuming and cleanup at the same\n\t * time. Transfer all visibility information to the new relations.\n\t *\n\t * Main loop inspired by heapam_relation_copy_for_cluster() used to run\n\t * CLUSTER and VACUUM FULL on a table.\n\t */\n\tdouble num_tuples = 0.0;\n\tdouble tups_vacuumed = 0.0;\n\tdouble tups_recently_dead = 0.0;\n\n\tBufferHeapTupleTableSlot *hslot;\n\tint routingindex = -1;\n\n\twhile (table_scan_getnextslot(scan, ForwardScanDirection, srcslot))\n\t{\n\t\tRelationWriteState *rws = NULL;\n\t\tHeapTuple tuple;\n\t\tBuffer buf;\n\t\tbool isdead;\n\t\tbool isalive = false;\n\n\t\tCHECK_FOR_INTERRUPTS();\n\t\tResetExprContext(econtext);\n\n\t\ttuple = ExecFetchSlotHeapTuple(srcslot, false, NULL);\n\n\t\thslot = (BufferHeapTupleTableSlot *) srcslot;\n\n\t\tbuf = hslot->buffer;\n\n\t\tLockBuffer(buf, BUFFER_LOCK_SHARE);\n\n\t\tswitch (HeapTupleSatisfiesVacuum(tuple, scontext->cutoffs.OldestXmin, buf))\n\t\t{\n\t\t\tcase HEAPTUPLE_DEAD:\n\t\t\t\t/* Definitely dead */\n\t\t\t\tisdead = true;\n\t\t\t\tbreak;\n\t\t\tcase HEAPTUPLE_RECENTLY_DEAD:\n\t\t\t\ttups_recently_dead += 1;\n\t\t\t\tisdead = false;\n\t\t\t\tbreak;\n\t\t\tcase HEAPTUPLE_LIVE:\n\t\t\t\t/* Live or recently dead, must copy it */\n\t\t\t\tisdead = false;\n\t\t\t\tisalive = true;\n\t\t\t\tbreak;\n\t\t\tcase HEAPTUPLE_INSERT_IN_PROGRESS:\n\t\t\t\t/*\n\t\t\t\t * Since we hold exclusive lock on the relation, normally the\n\t\t\t\t * only way to see this is if it was inserted earlier in our\n\t\t\t\t * own transaction. Give a warning if this case does not\n\t\t\t\t * apply; in any case we better copy it.\n\t\t\t\t */\n\t\t\t\tif (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple->t_data)))\n\t\t\t\t\telog(WARNING,\n\t\t\t\t\t\t \"concurrent insert in progress within table \\\"%s\\\"\",\n\t\t\t\t\t\t RelationGetRelationName(srcrel));\n\t\t\t\t/* treat as live */\n\t\t\t\tisdead = false;\n\t\t\t\tisalive = true;\n\t\t\t\tbreak;\n\t\t\tcase HEAPTUPLE_DELETE_IN_PROGRESS:\n\t\t\t\t/*\n\t\t\t\t * Similar situation to INSERT_IN_PROGRESS case.\n\t\t\t\t */\n\t\t\t\tif (!TransactionIdIsCurrentTransactionId(\n\t\t\t\t\t\tHeapTupleHeaderGetUpdateXid(tuple->t_data)))\n\t\t\t\t\telog(WARNING,\n\t\t\t\t\t\t \"concurrent delete in progress within table \\\"%s\\\"\",\n\t\t\t\t\t\t RelationGetRelationName(srcrel));\n\t\t\t\t/* treat as recently dead */\n\t\t\t\ttups_recently_dead += 1;\n\t\t\t\tisalive = true;\n\t\t\t\tisdead = false;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\telog(ERROR, \"unexpected HeapTupleSatisfiesVacuum result\");\n\t\t\t\tisdead = false; /* keep compiler quiet */\n\t\t\t\tbreak;\n\t\t}\n\n\t\tLockBuffer(buf, BUFFER_LOCK_UNLOCK);\n\n\t\tHeapTuple tuple2;\n\n\t\t/*\n\t\t * Route the tuple to the matching (new) partition. The routing is\n\t\t * done in a loop because compressed tuple segments might be split\n\t\t * into multiple sub-segment tuples if the split is in the middle of\n\t\t * that segment.\n\t\t */\n\t\twhile ((tuple2 = sp->route_next_tuple(srcslot, scontext, &routingindex)))\n\t\t{\n\t\t\tAssert(routingindex >= 0 && routingindex < scontext->split_factor);\n\t\t\trws = &scontext->rws[routingindex];\n\n\t\t\tif (isdead)\n\t\t\t{\n\t\t\t\ttups_vacuumed += 1;\n\t\t\t\t/* heap rewrite module still needs to see it... */\n\t\t\t\tif (rewrite_heap_dead_tuple(rws->rwstate, tuple2))\n\t\t\t\t{\n\t\t\t\t\t/* A previous recently-dead tuple is now known dead */\n\t\t\t\t\ttups_vacuumed += 1;\n\t\t\t\t\ttups_recently_dead -= 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tnum_tuples++;\n\t\t\t\trws->stats.tuples_written++;\n\n\t\t\t\tif (isalive)\n\t\t\t\t\trws->stats.tuples_alive++;\n\n\t\t\t\treform_and_rewrite_tuple(tuple2, srcrel, rws);\n\t\t\t}\n\t\t}\n\t}\n\n\tMemoryContextSwitchTo(oldcxt);\n\n\tconst char *nspname = get_namespace_name(RelationGetNamespace(srcrel));\n\n\tereport(DEBUG1,\n\t\t\t(errmsg(\"\\\"%s.%s\\\": found %.0f removable, %.0f nonremovable row versions\",\n\t\t\t\t\tnspname,\n\t\t\t\t\tRelationGetRelationName(srcrel),\n\t\t\t\t\ttups_vacuumed,\n\t\t\t\t\tnum_tuples),\n\t\t\t errdetail(\"%.0f dead row versions cannot be removed yet.\", tups_recently_dead)));\n\n\ttable_endscan(scan);\n\tExecDropSingleTupleTableSlot(srcslot);\n\tFreeExecutorState(estate);\n\n\treturn num_tuples;\n}\n\n/*\n * Split a relation into \"split_factor\" pieces.\n */\nstatic void\nsplit_relation(Relation rel, SplitPoint *sp, unsigned int split_factor,\n\t\t\t   SplitRelationInfo *split_relations)\n{\n\tchar relpersistence = rel->rd_rel->relpersistence;\n\tSplitContext scontext = {\n\t\t.rel = rel,\n\t\t.split_factor = split_factor,\n\t\t.rws = palloc0(sizeof(RelationWriteState) * split_factor),\n\t\t.sp = sp,\n\t\t.rws_index = -1,\n\t};\n\n\tcompute_rel_vacuum_cutoffs(scontext.rel, &scontext.cutoffs);\n\n\tfor (unsigned int i = 0; i < split_factor; i++)\n\t{\n\t\tSplitRelationInfo *sri = &split_relations[i];\n\t\tOid write_relid = sri->relid;\n\n\t\tif (sri->heap_swap)\n\t\t{\n\t\t\twrite_relid = make_new_heap(RelationGetRelid(rel),\n\t\t\t\t\t\t\t\t\t\trel->rd_rel->reltablespace,\n\t\t\t\t\t\t\t\t\t\trel->rd_rel->relam,\n\t\t\t\t\t\t\t\t\t\trelpersistence,\n\t\t\t\t\t\t\t\t\t\tAccessExclusiveLock);\n\t\t}\n\n\t\trelation_split_info_init(&scontext.rws[i], rel, write_relid, &scontext.cutoffs);\n\t}\n\n\tDEBUG_WAITPOINT(\"split_chunk_before_tuple_routing\");\n\n\tcopy_tuples_for_split(&scontext);\n\ttable_close(rel, NoLock);\n\n\tfor (unsigned int i = 0; i < split_factor; i++)\n\t{\n\t\tRelationWriteState *rws = &scontext.rws[i];\n\t\tSplitRelationInfo *sri = &split_relations[i];\n\t\tReindexParams reindex_params = { 0 };\n\t\tint reindex_flags = REINDEX_REL_SUPPRESS_INDEX_USE;\n\t\tOid write_relid = RelationGetRelid(rws->targetrel);\n\n\t\tEnsure(relpersistence == RELPERSISTENCE_PERMANENT, \"only permanent chunks can be split\");\n\t\treindex_flags |= REINDEX_REL_FORCE_INDEXES_PERMANENT;\n\n\t\t/* Save stats before cleaning up rewrite state */\n\t\tmemcpy(&sri->stats, &rws->stats, sizeof(sri->stats));\n\n\t\trelation_split_info_cleanup(rws, TABLE_INSERT_SKIP_FSM);\n\n\t\t/*\n\t\t * Only reindex new chunks. Existing chunk will be reindexed during\n\t\t * the heap swap.\n\t\t */\n\t\tif (sri->heap_swap)\n\t\t{\n\t\t\t/* Finally, swap the heap of the chunk that we split so that it only\n\t\t\t * contains the tuples for its new partition boundaries. AccessExclusive\n\t\t\t * lock is held during the swap. */\n\t\t\tfinish_heap_swap(sri->relid,\n\t\t\t\t\t\t\t write_relid,\n\t\t\t\t\t\t\t false, /* system catalog */\n\t\t\t\t\t\t\t false /* swap toast by content */,\n\t\t\t\t\t\t\t true, /* check constraints */\n\t\t\t\t\t\t\t true, /* internal? */\n\t\t\t\t\t\t\t scontext.cutoffs.FreezeLimit,\n\t\t\t\t\t\t\t scontext.cutoffs.MultiXactCutoff,\n\t\t\t\t\t\t\t relpersistence);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/*\n\t\t\t * Update relfrozenxid and relminmxid for the new chunk.\n\t\t\t * This is necessary because the heap rewrite preserved tuple\n\t\t\t * visibility information (xmin/xmax), and the new relation's\n\t\t\t * relfrozenxid must reflect the freeze limit used during the rewrite.\n\t\t\t * Without this, VACUUM may find tuples with xmin < relfrozenxid\n\t\t\t * that aren't frozen, causing \"found xmin from before relfrozenxid\" errors.\n\t\t\t */\n\t\t\tRelation relRelation = table_open(RelationRelationId, RowExclusiveLock);\n\t\t\tHeapTuple reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(sri->relid));\n\n\t\t\tif (!HeapTupleIsValid(reltup))\n\t\t\t\telog(ERROR, \"cache lookup failed for relation %u\", sri->relid);\n\n\t\t\tForm_pg_class relform = (Form_pg_class) GETSTRUCT(reltup);\n\t\t\trelform->relfrozenxid = scontext.cutoffs.FreezeLimit;\n\t\t\trelform->relminmxid = scontext.cutoffs.MultiXactCutoff;\n\t\t\tCatalogTupleUpdate(relRelation, &reltup->t_self, reltup);\n\t\t\theap_freetuple(reltup);\n\t\t\ttable_close(relRelation, RowExclusiveLock);\n\n\t\t\treindex_relation_compat(NULL, sri->relid, reindex_flags, &reindex_params);\n\t\t}\n\t}\n\n\tpfree(scontext.rws);\n}\n\nstatic void\ncompute_compression_size_stats_fraction(Form_compression_chunk_size ccs, double fraction)\n{\n\tccs->compressed_heap_size = (int64) rint((double) ccs->compressed_heap_size * fraction);\n\tccs->uncompressed_heap_size = (int64) rint((double) ccs->uncompressed_heap_size * fraction);\n\tccs->uncompressed_index_size = (int64) rint((double) ccs->uncompressed_index_size * fraction);\n\tccs->compressed_index_size = (int64) rint((double) ccs->compressed_index_size * fraction);\n\tccs->uncompressed_toast_size = (int64) rint((double) ccs->uncompressed_toast_size * fraction);\n\tccs->compressed_toast_size = (int64) rint((double) ccs->compressed_toast_size * fraction);\n\tccs->numrows_frozen_immediately =\n\t\t(int64) rint((double) ccs->numrows_frozen_immediately * fraction);\n\tccs->numrows_pre_compression = (int64) rint((double) ccs->numrows_pre_compression * fraction);\n\tccs->numrows_post_compression = (int64) rint((double) ccs->numrows_post_compression * fraction);\n}\n\nstatic void\nupdate_compression_stats_for_split(const SplitRelationInfo *split_relations,\n\t\t\t\t\t\t\t\t   const SplitRelationInfo *compressed_split_relations,\n\t\t\t\t\t\t\t\t   int split_factor)\n{\n\tdouble total_tuples = 0;\n\n\tAssert(split_factor > 1);\n\n\t/*\n\t * Set the new chunk status and calculate the total amount of tuples\n\t * (compressed and non-compressed), which is used to calculated the\n\t * fraction of data each new partition received.\n\t */\n\tfor (int i = 0; i < split_factor; i++)\n\t{\n\t\tconst SplitRelationInfo *sri = &split_relations[i];\n\t\tconst SplitRelationInfo *csri = &compressed_split_relations[i];\n\t\tChunk *chunk = ts_chunk_get_by_relid(sri->relid, true);\n\n\t\tif (sri->stats.tuples_written > 0)\n\t\t\tts_chunk_set_partial(chunk);\n\t\telse\n\t\t\tts_chunk_clear_status(chunk, CHUNK_STATUS_COMPRESSED_PARTIAL);\n\n\t\ttotal_tuples += sri->stats.tuples_alive + csri->stats.tuples_in_segments;\n\t}\n\n\t/*\n\t * Get the existing stats for the original chunk. The stats will be split\n\t * across the resulting new chunks.\n\t */\n\tFormData_compression_chunk_size ccs;\n\n\tts_compression_chunk_size_get(split_relations[0].chunk_id, &ccs);\n\n\tfor (int i = 0; i < split_factor; i++)\n\t{\n\t\tconst SplitRelationInfo *sri = &split_relations[i];\n\t\tconst SplitRelationInfo *csri = &compressed_split_relations[i];\n\t\tFormData_compression_chunk_size new_ccs;\n\n\t\t/* Calculate the fraction of compressed and non-compressed data received\n\t\t * by the first partition (chunk) */\n\t\tdouble fraction = 0.0;\n\n\t\tif (total_tuples > 0)\n\t\t\tfraction = (sri->stats.tuples_alive + csri->stats.tuples_in_segments) / total_tuples;\n\n\t\tmemcpy(&new_ccs, &ccs, sizeof(ccs));\n\t\tcompute_compression_size_stats_fraction(&new_ccs, fraction);\n\n\t\tif (sri->heap_swap)\n\t\t{\n\t\t\tts_compression_chunk_size_update(sri->chunk_id, &new_ccs);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* The new partition (chunk) doesn't have stats so create new. */\n\t\t\tRelationSize relsize = {\n\t\t\t\t.heap_size = new_ccs.uncompressed_heap_size,\n\t\t\t\t.index_size = new_ccs.uncompressed_index_size,\n\t\t\t\t.toast_size = new_ccs.uncompressed_toast_size,\n\t\t\t};\n\t\t\tRelationSize compressed_relsize = {\n\t\t\t\t.heap_size = new_ccs.compressed_heap_size,\n\t\t\t\t.index_size = new_ccs.compressed_index_size,\n\t\t\t\t.toast_size = new_ccs.compressed_toast_size,\n\t\t\t};\n\n\t\t\tcompression_chunk_size_catalog_insert(sri->chunk_id,\n\t\t\t\t\t\t\t\t\t\t\t\t  &relsize,\n\t\t\t\t\t\t\t\t\t\t\t\t  csri->chunk_id,\n\t\t\t\t\t\t\t\t\t\t\t\t  &compressed_relsize,\n\t\t\t\t\t\t\t\t\t\t\t\t  new_ccs.numrows_pre_compression,\n\t\t\t\t\t\t\t\t\t\t\t\t  new_ccs.numrows_post_compression,\n\t\t\t\t\t\t\t\t\t\t\t\t  new_ccs.numrows_frozen_immediately);\n\t\t}\n\t}\n}\n\n/*\n * Update the chunk stats for the split. Also set the chunk state (partial or\n * non-partial) and reltuples in pg_class.\n *\n * To calculate new compression chunk size stats, the existing stats are\n * simply split across the result partitions based on the fraction of data\n * they received. New stats are not calculated since the pre-compression sizes\n * for the split relations are not known (it would require decompression and\n * then measuring the disk usage). The advantage of splitting the stats is\n * that the total size stats is the the same after the split as they were\n * before the split.\n */\nstatic void\nupdate_chunk_stats_for_split(const SplitRelationInfo *split_relations,\n\t\t\t\t\t\t\t const SplitRelationInfo *compressed_split_relations, int split_factor)\n{\n\tif (compressed_split_relations)\n\t\tupdate_compression_stats_for_split(split_relations,\n\t\t\t\t\t\t\t\t\t\t   compressed_split_relations,\n\t\t\t\t\t\t\t\t\t\t   split_factor);\n\t/*\n\t * Update reltuples in pg_class. The reltuples are normally updated on\n\t * reindex, so this update only matters in case of no indexes.\n\t */\n\tRelation relRelation = table_open(RelationRelationId, RowExclusiveLock);\n\n\tfor (int i = 0; i < split_factor; i++)\n\t{\n\t\tconst SplitRelationInfo *sri = &split_relations[i];\n\t\tRelation rel;\n\t\tdouble ntuples = sri->stats.tuples_alive;\n\n\t\trel = table_open(sri->relid, AccessShareLock);\n\t\tupdate_relstats(relRelation, sri->relid, RelationGetNumberOfBlocks(rel), ntuples);\n\t\ttable_close(rel, NoLock);\n\t}\n\n\ttable_close(relRelation, RowExclusiveLock);\n}\n\n/*\n * Split a chunk along a given dimension and split point.\n *\n * The column/dimension and \"split at\" point are optional. If these arguments\n * are not specified, the chunk is split in two equal ranges based on the\n * primary partitioning column.\n *\n * The split is done using the table rewrite approach used by the PostgreSQL\n * CLUSTER code (also used for VACUUM FULL). It uses the rewrite module to\n * retain the visibility information of tuples, and also transferring (old)\n * deleted or updated tuples that are still visible to concurrent transactions\n * reading an older snapshot. Completely dead tuples are garbage collected.\n *\n * The advantage of the rewrite approach is that it is fully MVCC compliant\n * and ensures the result relations have minimal garbage after the split. Note\n * that locks don't fully protect against visibility issues since a concurrent\n * transaction can be pinned to an older snapshot while not (yet) holding any\n * locks on relations (chunks and hypertables) being split.\n */\nDatum\nchunk_split_chunk(PG_FUNCTION_ARGS)\n{\n\tOid relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\tconst Chunk *chunk;\n\tRelation srcrel;\n\tScanTupLock slice_lock = {\n\t\t.lockmode = LockTupleNoKeyExclusive,\n\t\t.waitpolicy = LockWaitBlock,\n\t\t.lockflags = TUPLE_LOCK_FLAG_FIND_LAST_VERSION,\n\t};\n\n\tchunk = ts_chunk_get_by_relid_locked(relid, AccessExclusiveLock, &slice_lock, true);\n\n\t/* Chunk already locked, so use NoLock */\n\tsrcrel = table_open(relid, NoLock);\n\n\tif (srcrel->rd_rel->relkind != RELKIND_RELATION)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot split non-table relations\")));\n\n\tOid amoid = srcrel->rd_rel->relam;\n\n\tif (amoid != HEAP_TABLE_AM_OID)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"access method \\\"%s\\\" is not supported for split\", get_am_name(amoid))));\n\n\t/* Only owner is allowed to split */\n\tif (!object_ownercheck(RelationRelationId, relid, GetUserId()))\n\t\taclcheck_error(ACLCHECK_NOT_OWNER,\n\t\t\t\t\t   get_relkind_objtype(srcrel->rd_rel->relkind),\n\t\t\t\t\t   get_rel_name(relid));\n\n\t/* Lock toast table to prevent it from being concurrently vacuumed */\n\tif (srcrel->rd_rel->reltoastrelid)\n\t\tLockRelationOid(srcrel->rd_rel->reltoastrelid, AccessExclusiveLock);\n\n\t/*\n\t * Check for active uses of the relation in the current transaction,\n\t * including open scans and pending AFTER trigger events.\n\t */\n\tCheckTableNotInUse(srcrel, \"split_chunk\");\n\n\tif (chunk->fd.osm_chunk)\n\t\tereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg(\"cannot split OSM chunks\")));\n\n\tif (ts_chunk_is_frozen(chunk))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot split frozen chunk \\\"%s.%s\\\" scheduled for tiering\",\n\t\t\t\t\t\tNameStr(chunk->fd.schema_name),\n\t\t\t\t\t\tNameStr(chunk->fd.table_name)),\n\t\t\t\t errhint(\"Untier the chunk before splitting it.\")));\n\n\tCache *hcache;\n\tconst Hypertable *ht =\n\t\tts_hypertable_cache_get_cache_and_entry(chunk->hypertable_relid, CACHE_FLAG_NONE, &hcache);\n\tconst Dimension *dim = hyperspace_get_open_dimension(ht->space, 0);\n\n\tEnsure(dim, \"no primary dimension for chunk\");\n\n\tif (ht->fd.num_dimensions > 1)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot split chunk in multi-dimensional hypertable\")));\n\n\tNameData splitdim_name;\n\tnamestrcpy(&splitdim_name, NameStr(dim->fd.column_name));\n\n\tOid splitdim_type = ts_dimension_get_partition_type(dim);\n\tOid splitcolumn_type = dim->fd.column_type;\n\tDatum split_at_datum;\n\tbool have_split_at = false;\n\n\t/* Check split_at argument */\n\tif (!PG_ARGISNULL(1))\n\t{\n\t\tOid argtype = get_fn_expr_argtype(fcinfo->flinfo, 1);\n\t\tDatum arg = PG_GETARG_DATUM(1);\n\n\t\tif (argtype == UNKNOWNOID)\n\t\t{\n\t\t\tOid infuncid = InvalidOid;\n\t\t\tOid typioparam;\n\n\t\t\tgetTypeInputInfo(splitdim_type, &infuncid, &typioparam);\n\n\t\t\tswitch (get_func_nargs(infuncid))\n\t\t\t{\n\t\t\t\tcase 1:\n\t\t\t\t\t/* Functions that take one input argument, e.g., the Date function */\n\t\t\t\t\tsplit_at_datum = OidFunctionCall1(infuncid, arg);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 3:\n\t\t\t\t\t/* Timestamp functions take three input arguments */\n\t\t\t\t\tsplit_at_datum = OidFunctionCall3(infuncid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  arg,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  ObjectIdGetDatum(InvalidOid),\n\t\t\t\t\t\t\t\t\t\t\t\t\t  Int32GetDatum(-1));\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\t/* Shouldn't be any time types with other number of args */\n\t\t\t\t\tEnsure(false, \"invalid type for split_at\");\n\t\t\t\t\tpg_unreachable();\n\t\t\t}\n\t\t\targtype = splitdim_type;\n\t\t}\n\t\telse\n\t\t\tsplit_at_datum = arg;\n\n\t\tif (argtype != splitcolumn_type)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"invalid type '%s' for split_at argument\", format_type_be(argtype)),\n\t\t\t\t\t errdetail(\"The argument type must match the dimension \\\"%s\\\"\",\n\t\t\t\t\t\t\t   NameStr(dim->fd.column_name))));\n\n\t\thave_split_at = true;\n\t}\n\n\t/* Serialize chunk creation around the root hypertable. NOTE: also taken\n\t * in ts_chunk_find_or_create_without_cuts() below. */\n\tLockRelationOid(ht->main_table_relid, ShareUpdateExclusiveLock);\n\n\t/*\n\t * Find the existing partition slice for the chunk being split.\n\t */\n\tDimensionSlice *slice = NULL;\n\tHypercube *new_cube = ts_hypercube_copy(chunk->cube);\n\n\tfor (int i = 0; i < new_cube->num_slices; i++)\n\t{\n\t\tDimensionSlice *curr_slice = new_cube->slices[i];\n\n\t\tif (curr_slice->fd.dimension_id == dim->fd.id)\n\t\t{\n\t\t\tslice = curr_slice;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tEnsure(slice, \"no chunk slice for dimension %s\", NameStr(dim->fd.column_name));\n\n\t/*\n\t * Pick split point and calculate new ranges. If no split point is given\n\t * by the user, then split in the middle.\n\t */\n\tint64 interval_range = slice->fd.range_end - slice->fd.range_start;\n\tint64 split_at = 0;\n\n\tif (have_split_at)\n\t{\n\t\tDatum dim_datum;\n\n\t\tif (NULL != dim->partitioning)\n\t\t\tdim_datum =\n\t\t\t\tts_partitioning_func_apply(dim->partitioning, C_COLLATION_OID, split_at_datum);\n\t\telse\n\t\t\tdim_datum = split_at_datum;\n\n\t\tsplit_at = ts_time_value_to_internal(dim_datum, splitdim_type);\n\n\t\t/*\n\t\t * Check that the split_at value actually produces a valid split. Note\n\t\t * that range_start is inclusive while range_end is non-inclusive. The\n\t\t * split_at value needs to produce partition ranges of at least length\n\t\t * 1.\n\t\t */\n\t\tif (split_at < (slice->fd.range_start + 1) || split_at > (slice->fd.range_end - 2))\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"cannot split chunk at %s\",\n\t\t\t\t\t\t\tts_datum_to_string(dim_datum, splitdim_type))));\n\t\t}\n\t}\n\telse\n\t\tsplit_at = slice->fd.range_start + (interval_range / 2);\n\n\telog(DEBUG1,\n\t\t \"splitting chunk %s at %s\",\n\t\t get_rel_name(relid),\n\t\t ts_internal_to_time_string(split_at, splitdim_type));\n\n\tconst CompressionSettings *compress_settings = ts_compression_settings_get(relid);\n\tint64 old_end = slice->fd.range_end;\n\n\t/* Update the slice range for the existing chunk */\n\tslice->fd.range_end = split_at;\n\tchunk_update_constraints(chunk, new_cube);\n\n\t/* Update the slice for the new chunk */\n\tslice->fd.range_start = split_at;\n\tslice->fd.range_end = old_end;\n\tslice->fd.id = 0; /* Must set to 0 to mark as new for it to be created */\n\n\t/* Make updated constraints visible */\n\tCommandCounterIncrement();\n\n\t/* Reread hypertable after constraints changed */\n\tts_cache_release(&hcache);\n\tht = ts_hypertable_cache_get_cache_and_entry(chunk->hypertable_relid, CACHE_FLAG_NONE, &hcache);\n\tbool created = false;\n\tChunk *new_chunk = ts_chunk_find_or_create_without_cuts(ht,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tnew_cube,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tNameStr(chunk->fd.schema_name),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tInvalidOid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t&created);\n\tEnsure(created, \"could not create chunk for split\");\n\tAssert(new_chunk);\n\n\tChunk *new_compressed_chunk = NULL;\n\n\tif (compress_settings != NULL)\n\t{\n\t\tHypertable *ht_compressed = ts_hypertable_get_by_id(ht->fd.compressed_hypertable_id);\n\t\tnew_compressed_chunk = create_compress_chunk(ht_compressed, new_chunk, InvalidOid);\n\t\tts_trigger_create_all_on_chunk(new_compressed_chunk);\n\t\tts_chunk_set_compressed_chunk(new_chunk, new_compressed_chunk->fd.id);\n\t}\n\n\tCommandCounterIncrement();\n\n\tDEBUG_WAITPOINT(\"split_chunk_after_creating_new_chunk\");\n\n\tSplitPoint sp = {\n\t\t.point = split_at,\n\t\t.dim = hyperspace_get_open_dimension(ht->space, 0),\n\t\t.route_next_tuple = route_next_non_compressed_tuple,\n\t};\n\n\t/*\n\t * Array of the heap Oids of the resulting relations. Those relations that\n\t * will get a heap swap (i.e., the original chunk) has heap_swap set to\n\t * true.\n\t */\n\tSplitRelationInfo split_relations[SPLIT_FACTOR] = {\n\t\t[0] = { .relid = relid, .chunk_id = chunk->fd.id, .heap_swap = true },\n\t\t[1] = { .relid = new_chunk->table_id, .chunk_id = new_chunk->fd.id, .heap_swap = false }\n\t};\n\tSplitRelationInfo csplit_relations[SPLIT_FACTOR] = {};\n\tSplitRelationInfo *compressed_split_relations = NULL;\n\n\t/* Split and rewrite the compressed relation first, if one exists. */\n\tif (new_compressed_chunk)\n\t{\n\t\tint orderby_pos = ts_array_position(compress_settings->fd.orderby, NameStr(splitdim_name));\n\t\tEnsure(orderby_pos > 0,\n\t\t\t   \"primary dimension \\\"%s\\\" is not in compression settings\",\n\t\t\t   NameStr(splitdim_name));\n\n\t\t/*\n\t\t * Get the attribute numbers for the primary dimension's min and max\n\t\t * values in the compressed relation. We'll use these to get the time\n\t\t * range of compressed segments in order to route segments to the\n\t\t * right result chunk.\n\t\t */\n\t\tconst char *min_attname = column_segment_min_name(orderby_pos);\n\t\tconst char *max_attname = column_segment_max_name(orderby_pos);\n\n\t\tCompressedSplitPoint csp = {\n\t\t\t.base = {\n\t\t\t\t.point = split_at,\n\t\t\t\t.dim = hyperspace_get_open_dimension(ht->space, 0),\n\t\t\t\t.route_next_tuple = route_next_compressed_tuple,\n\t\t\t},\n\t\t\t.attnum_min = get_attnum(compress_settings->fd.compress_relid, min_attname),\n\t\t\t.attnum_max = get_attnum(compress_settings->fd.compress_relid, max_attname),\n\t\t\t.attnum_count = get_attnum(compress_settings->fd.compress_relid, COMPRESSION_COLUMN_METADATA_COUNT_NAME),\n\t\t\t.noncompressed_tupdesc = CreateTupleDescCopy(RelationGetDescr(srcrel)),\n\t\t};\n\n\t\tcsplit_relations[0] = (SplitRelationInfo){ .relid = compress_settings->fd.compress_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t   .chunk_id = chunk->fd.compressed_chunk_id,\n\t\t\t\t\t\t\t\t\t\t\t\t   .heap_swap = true };\n\t\tcsplit_relations[1] = (SplitRelationInfo){ .relid = new_compressed_chunk->table_id,\n\t\t\t\t\t\t\t\t\t\t\t\t   .chunk_id = new_chunk->fd.compressed_chunk_id,\n\t\t\t\t\t\t\t\t\t\t\t\t   .heap_swap = false };\n\n\t\tRelation compressed_rel =\n\t\t\ttable_open(compress_settings->fd.compress_relid, AccessExclusiveLock);\n\t\tcompressed_split_relations = csplit_relations;\n\t\tsplit_relation(compressed_rel, &csp.base, SPLIT_FACTOR, compressed_split_relations);\n\t}\n\n\t/* Now split the non-compressed relation */\n\tsplit_relation(srcrel, &sp, SPLIT_FACTOR, split_relations);\n\n\tts_cache_release(&hcache);\n\n\t/* Update stats after split is done */\n\tupdate_chunk_stats_for_split(split_relations, compressed_split_relations, SPLIT_FACTOR);\n\n\tDEBUG_WAITPOINT(\"split_chunk_at_end\");\n\n\tPG_RETURN_VOID();\n}\n"
  },
  {
    "path": "tsl/src/chunkwise_agg.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n\n#include <optimizer/appendinfo.h>\n#include <optimizer/cost.h>\n#include <optimizer/pathnode.h>\n#include <optimizer/paths.h>\n#include <optimizer/prep.h>\n#include <optimizer/tlist.h>\n\n#include \"chunkwise_agg.h\"\n\n#include \"guc.h\"\n#include \"import/planner.h\"\n#include \"nodes/chunk_append/chunk_append.h\"\n#include \"nodes/columnar_scan/columnar_scan.h\"\n#include \"planner.h\"\n\n/* Helper function to find the first node of the provided type in the pathlist of the relation */\nstatic Node *\nfind_node(const RelOptInfo *relation, NodeTag type)\n{\n\tListCell *lc;\n\tforeach (lc, relation->pathlist)\n\t{\n\t\tNode *node = lfirst(lc);\n\t\tif (nodeTag(node) == type)\n\t\t\treturn node;\n\t}\n\n\treturn NULL;\n}\n\n/* Check if the relation already has a min/max path */\nstatic bool\nhas_min_max_agg_path(const RelOptInfo *relation)\n{\n\treturn find_node(relation, T_MinMaxAggPath) != NULL;\n}\n\n/*\n * Get an an existing aggregation path for the given relation or NULL if no aggregation path exists.\n */\nstatic AggPath *\nget_existing_agg_path(const RelOptInfo *relation)\n{\n\tNode *node = find_node(relation, T_AggPath);\n\treturn node ? castNode(AggPath, node) : NULL;\n}\n\n/*\n * Get all subpaths from a Append, MergeAppend, or ChunkAppend path\n */\nstatic void\nget_subpaths_from_append_path(Path *path, List **subpaths, Path **append, Path **gather)\n{\n\tif (IsA(path, AppendPath))\n\t{\n\t\tAppendPath *append_path = castNode(AppendPath, path);\n\t\t*subpaths = append_path->subpaths;\n\t\t*append = path;\n\t\treturn;\n\t}\n\n\tif (IsA(path, MergeAppendPath))\n\t{\n\t\tMergeAppendPath *merge_append_path = castNode(MergeAppendPath, path);\n\t\t*subpaths = merge_append_path->subpaths;\n\t\t*append = path;\n\t\treturn;\n\t}\n\n\tif (ts_is_chunk_append_path(path))\n\t{\n\t\tCustomPath *custom_path = castNode(CustomPath, path);\n\t\t*subpaths = custom_path->custom_paths;\n\t\t*append = path;\n\t\treturn;\n\t}\n\n\tif (IsA(path, GatherPath))\n\t{\n\t\t*gather = path;\n\t\tget_subpaths_from_append_path(castNode(GatherPath, path)->subpath,\n\t\t\t\t\t\t\t\t\t  subpaths,\n\t\t\t\t\t\t\t\t\t  append,\n\t\t\t\t\t\t\t\t\t  /* gather = */ NULL);\n\t\treturn;\n\t}\n\n\tif (IsA(path, GatherMergePath))\n\t{\n\t\t*gather = path;\n\t\tget_subpaths_from_append_path(castNode(GatherMergePath, path)->subpath,\n\t\t\t\t\t\t\t\t\t  subpaths,\n\t\t\t\t\t\t\t\t\t  append,\n\t\t\t\t\t\t\t\t\t  /* gather = */ NULL);\n\t\treturn;\n\t}\n\n\tif (IsA(path, SortPath))\n\t{\n\t\t/* Can see GatherMerge -> Sort -> Partial HashAggregate in parallel plans. */\n\t\tget_subpaths_from_append_path(castNode(SortPath, path)->subpath, subpaths, append, gather);\n\t\treturn;\n\t}\n\n\tif (IsA(path, AggPath))\n\t{\n\t\t/* Can see GatherMerge -> Sort -> Partial HashAggregate in parallel plans. */\n\t\tget_subpaths_from_append_path(castNode(AggPath, path)->subpath, subpaths, append, gather);\n\t\treturn;\n\t}\n\n\tif (IsA(path, ProjectionPath))\n\t{\n\t\tProjectionPath *projection = castNode(ProjectionPath, path);\n\t\tget_subpaths_from_append_path(projection->subpath, subpaths, append, gather);\n\t\treturn;\n\t}\n\n\t/* Aggregation push-down is not supported for other path types so far */\n}\n\n/*\n * Copy an AppendPath and set new subpaths.\n */\nstatic AppendPath *\ncopy_append_path(AppendPath *path, List *subpaths, PathTarget *pathtarget)\n{\n\tAppendPath *newPath = makeNode(AppendPath);\n\tmemcpy(newPath, path, sizeof(AppendPath));\n\tnewPath->subpaths = subpaths;\n\tnewPath->path.pathtarget = copy_pathtarget(pathtarget);\n\n\tcost_append(newPath);\n\n\treturn newPath;\n}\n\n/*\n * Copy a MergeAppendPath and set new subpaths.\n */\nstatic MergeAppendPath *\ncopy_merge_append_path(PlannerInfo *root, MergeAppendPath *path, List *subpaths,\n\t\t\t\t\t   PathTarget *pathtarget)\n{\n\tMergeAppendPath *newPath =\n\t\tcreate_merge_append_path(root, path->path.parent, subpaths, path->path.pathkeys, NULL);\n\n\tnewPath->path.param_info = path->path.param_info;\n\tnewPath->path.pathtarget = copy_pathtarget(pathtarget);\n\n\treturn newPath;\n}\n\n/*\n * Copy an append-like path and set new subpaths\n */\nstatic Path *\ncopy_append_like_path(PlannerInfo *root, Path *path, List *new_subpaths, PathTarget *pathtarget)\n{\n\tif (IsA(path, AppendPath))\n\t{\n\t\tAppendPath *append_path = castNode(AppendPath, path);\n\t\tAppendPath *new_append_path = copy_append_path(append_path, new_subpaths, pathtarget);\n\t\treturn &new_append_path->path;\n\t}\n\telse if (IsA(path, MergeAppendPath))\n\t{\n\t\tMergeAppendPath *merge_append_path = castNode(MergeAppendPath, path);\n\t\tMergeAppendPath *new_merge_append_path =\n\t\t\tcopy_merge_append_path(root, merge_append_path, new_subpaths, pathtarget);\n\t\treturn &new_merge_append_path->path;\n\t}\n\telse if (ts_is_chunk_append_path(path))\n\t{\n\t\tCustomPath *custom_path = castNode(CustomPath, path);\n\t\tChunkAppendPath *chunk_append_path = (ChunkAppendPath *) custom_path;\n\t\tChunkAppendPath *new_chunk_append_path =\n\t\t\tts_chunk_append_path_copy(chunk_append_path, new_subpaths, pathtarget);\n\t\treturn &new_chunk_append_path->cpath.path;\n\t}\n\telse if (IsA(path, ProjectionPath))\n\t{\n\t\t/*\n\t\t * Projection goes under partial aggregation, so here we can just ignore\n\t\t * it.\n\t\t */\n\t\treturn copy_append_like_path(root,\n\t\t\t\t\t\t\t\t\t castNode(ProjectionPath, path)->subpath,\n\t\t\t\t\t\t\t\t\t new_subpaths,\n\t\t\t\t\t\t\t\t\t pathtarget);\n\t}\n\n\t/* Should never happen, already checked by caller */\n\tEnsure(false, \"unknown path type\");\n\tpg_unreachable();\n}\n\n/*\n * Generate a partially sorted aggregated agg path on top of a path\n */\nstatic AggPath *\ncreate_sorted_partial_agg_path(PlannerInfo *root, Path *path, PathTarget *target,\n\t\t\t\t\t\t\t   double d_num_groups, GroupPathExtraData *extra_data)\n{\n\tQuery *parse = root->parse;\n\n\t/* Determine costs for aggregations */\n\tAggClauseCosts *agg_partial_costs = &extra_data->agg_partial_costs;\n\n\tbool is_sorted = pathkeys_contained_in(root->group_pathkeys, path->pathkeys);\n\n\tif (!is_sorted)\n\t{\n\t\tpath = (Path *) create_sort_path(root, path->parent, path, root->group_pathkeys, -1.0);\n\t}\n\n\tAggPath *sorted_agg_path = create_agg_path(root,\n\t\t\t\t\t\t\t\t\t\t\t   path->parent,\n\t\t\t\t\t\t\t\t\t\t\t   path,\n\t\t\t\t\t\t\t\t\t\t\t   target,\n\t\t\t\t\t\t\t\t\t\t\t   parse->groupClause ? AGG_SORTED : AGG_PLAIN,\n\t\t\t\t\t\t\t\t\t\t\t   AGGSPLIT_INITIAL_SERIAL,\n#if PG16_LT\n\t\t\t\t\t\t\t\t\t\t\t   parse->groupClause,\n#else\n\t\t\t\t\t\t\t\t\t\t\t   root->processed_groupClause,\n#endif\n\t\t\t\t\t\t\t\t\t\t\t   NIL,\n\t\t\t\t\t\t\t\t\t\t\t   agg_partial_costs,\n\t\t\t\t\t\t\t\t\t\t\t   d_num_groups);\n\n\treturn sorted_agg_path;\n}\n\n/*\n * Generate a partially hashed aggregated add path on top of a path\n */\nstatic AggPath *\ncreate_hashed_partial_agg_path(PlannerInfo *root, Path *path, PathTarget *target,\n\t\t\t\t\t\t\t   double d_num_groups, GroupPathExtraData *extra_data)\n{\n\t/* Determine costs for aggregations */\n\tAggClauseCosts *agg_partial_costs = &extra_data->agg_partial_costs;\n\n\tAggPath *hash_path = create_agg_path(root,\n\t\t\t\t\t\t\t\t\t\t path->parent,\n\t\t\t\t\t\t\t\t\t\t path,\n\t\t\t\t\t\t\t\t\t\t target,\n\t\t\t\t\t\t\t\t\t\t AGG_HASHED,\n\t\t\t\t\t\t\t\t\t\t AGGSPLIT_INITIAL_SERIAL,\n#if PG16_LT\n\t\t\t\t\t\t\t\t\t\t root->parse->groupClause,\n#else\n\t\t\t\t\t\t\t\t\t\t root->processed_groupClause,\n#endif\n\t\t\t\t\t\t\t\t\t\t NIL,\n\t\t\t\t\t\t\t\t\t\t agg_partial_costs,\n\t\t\t\t\t\t\t\t\t\t d_num_groups);\n\treturn hash_path;\n}\n\n/*\n * Add partially aggregated subpath\n */\nstatic void\nadd_partially_aggregated_subpaths(PlannerInfo *root, PathTarget *input_target,\n\t\t\t\t\t\t\t\t  PathTarget *partial_grouping_target, double d_num_groups,\n\t\t\t\t\t\t\t\t  GroupPathExtraData *extra_data, Path *subpath,\n\t\t\t\t\t\t\t\t  List **sorted_paths, List **hashed_paths)\n{\n\t/* Translate targetlist for partition */\n\tAppendRelInfo *appinfo = ts_get_appendrelinfo(root, subpath->parent->relid, false);\n\tPathTarget *chunk_grouped_target = copy_pathtarget(partial_grouping_target);\n\tchunk_grouped_target->exprs =\n\t\tcastNode(List,\n\t\t\t\t adjust_appendrel_attrs(root,\n\t\t\t\t\t\t\t\t\t\t(Node *) chunk_grouped_target->exprs,\n\t\t\t\t\t\t\t\t\t\t/* nappinfos = */ 1,\n\t\t\t\t\t\t\t\t\t\t&appinfo));\n\n\t/*\n\t * We might have to project before aggregation. In declarative partitioning\n\t * planning, the projection is applied by apply_scanjoin_target_to_path().\n\t */\n\tPathTarget *chunk_target_before_grouping = copy_pathtarget(input_target);\n\tchunk_target_before_grouping->exprs =\n\t\tcastNode(List,\n\t\t\t\t adjust_appendrel_attrs(root,\n\t\t\t\t\t\t\t\t\t\t(Node *) chunk_target_before_grouping->exprs,\n\t\t\t\t\t\t\t\t\t\t/* nappinfos = */ 1,\n\t\t\t\t\t\t\t\t\t\t&appinfo));\n\t/*\n\t * Note that we cannot use apply_projection_to_path() here, because it might\n\t * modify the targetlist of the projection-capable paths in place, which\n\t * would cause a mismatch when these paths are used in another context.\n\t *\n\t * In case of ColumnarScan path, we can make a copy of it and push the\n\t * projection down to it.\n\t *\n\t * In general, the projection here arises because the pathtarget of the\n\t * table scans is determined early based on the reltarget which lists all\n\t * used columns in attno order, and the pathtarget before grouping is\n\t * computed later and has the grouping columns in front.\n\t */\n\tif (ts_is_columnar_scan_path(subpath))\n\t{\n\t\tsubpath = (Path *) copy_columnar_scan_path((ColumnarScanPath *) subpath);\n\t\tsubpath->pathtarget = chunk_target_before_grouping;\n\t}\n\telse\n\t{\n\t\tsubpath = (Path *)\n\t\t\tcreate_projection_path(root, subpath->parent, subpath, chunk_target_before_grouping);\n\t}\n\n\tif (extra_data->flags & GROUPING_CAN_USE_SORT)\n\t{\n\t\tAggPath *agg_path = create_sorted_partial_agg_path(root,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   subpath,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   chunk_grouped_target,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   d_num_groups,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   extra_data);\n\n\t\t*sorted_paths = lappend(*sorted_paths, (Path *) agg_path);\n\t}\n\n\tif (extra_data->flags & GROUPING_CAN_USE_HASH)\n\t{\n\t\tAggPath *agg_path = create_hashed_partial_agg_path(root,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   subpath,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   chunk_grouped_target,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   d_num_groups,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   extra_data);\n\n\t\t*hashed_paths = lappend(*hashed_paths, (Path *) agg_path);\n\t}\n}\n\n/*\n * Generate a total aggregation path for partial aggregations.\n *\n * The generated paths contain partial aggregations (created by using AGGSPLIT_INITIAL_SERIAL).\n * These aggregations need to be finished by the caller by adding a node that performs the\n * AGGSPLIT_FINAL_DESERIAL step.\n *\n * The original path can be either parallel or non-parallel aggregation, and the\n * resulting path will be parallel accordingly.\n */\nstatic void\ngenerate_agg_pushdown_path(PlannerInfo *root, Path *cheapest_total_path, RelOptInfo *input_rel,\n\t\t\t\t\t\t   RelOptInfo *output_rel, RelOptInfo *partially_grouped_rel,\n\t\t\t\t\t\t   PathTarget *grouping_target, PathTarget *partial_grouping_target,\n\t\t\t\t\t\t   double d_num_groups, GroupPathExtraData *extra_data)\n{\n\t/* Get subpaths */\n\tList *subpaths = NIL;\n\tPath *top_gather = NULL;\n\tPath *top_append = NULL;\n\tget_subpaths_from_append_path(cheapest_total_path, &subpaths, &top_append, &top_gather);\n\n\t/* No subpaths available or unsupported append node */\n\tif (subpaths == NIL)\n\t{\n\t\treturn;\n\t}\n\n\tAssert(top_append != NULL);\n\n\tif (list_length(subpaths) < 2)\n\t{\n\t\t/*\n\t\t * Doesn't make sense to add per-chunk aggregation paths if there's\n\t\t * only one chunk.\n\t\t */\n\t\treturn;\n\t}\n\n\t/* Generate agg paths on top of the append children */\n\tList *sorted_subpaths = NIL;\n\tList *hashed_subpaths = NIL;\n\n\tListCell *lc;\n\tforeach (lc, subpaths)\n\t{\n\t\tPath *subpath = lfirst(lc);\n\n\t\t/* Check if we have an append path under an append path (e.g., a partially compressed\n\t\t * chunk. The first append path merges the chunk results. The second append path merges the\n\t\t * uncompressed and the compressed part of the chunk).\n\t\t *\n\t\t * In this case, the partial aggregation needs to be pushed down below the lower\n\t\t * append path.\n\t\t */\n\t\tList *partially_compressed_paths = NIL;\n\t\tPath *partially_compressed_append = NULL;\n\t\tPath *partially_compressed_gather = NULL;\n\t\tget_subpaths_from_append_path(subpath,\n\t\t\t\t\t\t\t\t\t  &partially_compressed_paths,\n\t\t\t\t\t\t\t\t\t  &partially_compressed_append,\n\t\t\t\t\t\t\t\t\t  &partially_compressed_gather);\n\t\tAssert(partially_compressed_gather == NULL);\n\n\t\tif (partially_compressed_append != NULL)\n\t\t{\n\t\t\tList *partially_compressed_sorted = NIL;\n\t\t\tList *partially_compressed_hashed = NIL;\n\n\t\t\tListCell *lc2;\n\t\t\tforeach (lc2, partially_compressed_paths)\n\t\t\t{\n\t\t\t\tPath *partially_compressed_path = lfirst(lc2);\n\n\t\t\t\tadd_partially_aggregated_subpaths(root,\n\t\t\t\t\t\t\t\t\t\t\t\t  input_rel->reltarget,\n\t\t\t\t\t\t\t\t\t\t\t\t  partial_grouping_target,\n\t\t\t\t\t\t\t\t\t\t\t\t  d_num_groups,\n\t\t\t\t\t\t\t\t\t\t\t\t  extra_data,\n\t\t\t\t\t\t\t\t\t\t\t\t  partially_compressed_path,\n\t\t\t\t\t\t\t\t\t\t\t\t  &partially_compressed_sorted /* Result path */,\n\t\t\t\t\t\t\t\t\t\t\t\t  &partially_compressed_hashed /* Result path */);\n\t\t\t}\n\n\t\t\tif (extra_data->flags & GROUPING_CAN_USE_SORT)\n\t\t\t{\n\t\t\t\tsorted_subpaths = lappend(sorted_subpaths,\n\t\t\t\t\t\t\t\t\t\t  copy_append_like_path(root,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tpartially_compressed_append,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tpartially_compressed_sorted,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tpartial_grouping_target));\n\t\t\t}\n\n\t\t\tif (extra_data->flags & GROUPING_CAN_USE_HASH)\n\t\t\t{\n\t\t\t\thashed_subpaths = lappend(hashed_subpaths,\n\t\t\t\t\t\t\t\t\t\t  copy_append_like_path(root,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tpartially_compressed_append,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tpartially_compressed_hashed,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tpartial_grouping_target));\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tadd_partially_aggregated_subpaths(root,\n\t\t\t\t\t\t\t\t\t\t\t  input_rel->reltarget,\n\t\t\t\t\t\t\t\t\t\t\t  partial_grouping_target,\n\t\t\t\t\t\t\t\t\t\t\t  d_num_groups,\n\t\t\t\t\t\t\t\t\t\t\t  extra_data,\n\t\t\t\t\t\t\t\t\t\t\t  subpath,\n\t\t\t\t\t\t\t\t\t\t\t  &sorted_subpaths /* Result paths */,\n\t\t\t\t\t\t\t\t\t\t\t  &hashed_subpaths /* Result paths */);\n\t\t}\n\t}\n\n\t/* Create new append paths */\n\tif (top_gather == NULL)\n\t{\n\t\t/*\n\t\t * The original aggregation plan was non-parallel, so we're creating a\n\t\t * non-parallel plan as well.\n\t\t */\n\t\tif (sorted_subpaths != NIL)\n\t\t{\n\t\t\tadd_path(partially_grouped_rel,\n\t\t\t\t\t copy_append_like_path(root,\n\t\t\t\t\t\t\t\t\t\t   top_append,\n\t\t\t\t\t\t\t\t\t\t   sorted_subpaths,\n\t\t\t\t\t\t\t\t\t\t   partial_grouping_target));\n\t\t}\n\n\t\tif (hashed_subpaths != NIL)\n\t\t{\n\t\t\tadd_path(partially_grouped_rel,\n\t\t\t\t\t copy_append_like_path(root,\n\t\t\t\t\t\t\t\t\t\t   top_append,\n\t\t\t\t\t\t\t\t\t\t   hashed_subpaths,\n\t\t\t\t\t\t\t\t\t\t   partial_grouping_target));\n\t\t}\n\t}\n\telse\n\t{\n\t\t/*\n\t\t * The cheapest aggregation plan was parallel, so we're creating a\n\t\t * parallel plan as well.\n\t\t */\n\t\tif (sorted_subpaths != NIL)\n\t\t{\n\t\t\tadd_partial_path(partially_grouped_rel,\n\t\t\t\t\t\t\t copy_append_like_path(root,\n\t\t\t\t\t\t\t\t\t\t\t\t   top_append,\n\t\t\t\t\t\t\t\t\t\t\t\t   sorted_subpaths,\n\t\t\t\t\t\t\t\t\t\t\t\t   partial_grouping_target));\n\t\t}\n\n\t\tif (hashed_subpaths != NIL)\n\t\t{\n\t\t\tadd_partial_path(partially_grouped_rel,\n\t\t\t\t\t\t\t copy_append_like_path(root,\n\t\t\t\t\t\t\t\t\t\t\t\t   top_append,\n\t\t\t\t\t\t\t\t\t\t\t\t   hashed_subpaths,\n\t\t\t\t\t\t\t\t\t\t\t\t   partial_grouping_target));\n\t\t}\n\t}\n}\n\n/*\n Is the provided path a agg path that uses a sorted or plain agg strategy?\n*/\nstatic bool pg_nodiscard\nis_path_sorted_or_plain_agg_path(Path *path)\n{\n\tAggPath *agg_path = castNode(AggPath, path);\n\tAssert(agg_path->aggstrategy == AGG_SORTED || agg_path->aggstrategy == AGG_PLAIN ||\n\t\t   agg_path->aggstrategy == AGG_HASHED);\n\treturn agg_path->aggstrategy == AGG_SORTED || agg_path->aggstrategy == AGG_PLAIN;\n}\n\n/*\n * Check if this path belongs to a plain or sorted aggregation\n */\nstatic bool\ncontains_path_plain_or_sorted_agg(Path *path)\n{\n\tList *subpaths = NIL;\n\tPath *append = NULL;\n\tPath *gather = NULL;\n\tget_subpaths_from_append_path(path, &subpaths, &append, &gather);\n\n\tEnsure(subpaths != NIL, \"Unable to determine aggregation type\");\n\n\tListCell *lc;\n\tforeach (lc, subpaths)\n\t{\n\t\tPath *subpath = lfirst(lc);\n\n\t\tif (IsA(subpath, AggPath))\n\t\t\treturn is_path_sorted_or_plain_agg_path(subpath);\n\t}\n\n\t/*\n\t * No dedicated aggregation nodes found directly underneath the append node. This could be\n\t * due to two reasons.\n\t *\n\t * (1) Only vectorized aggregation is used and we don't have dedicated Aggregation nods.\n\t * (2) The query plan uses multi-level appends to keep a certain sorting\n\t *     - ChunkAppend\n\t *          - Merge Append\n\t *             - Agg Chunk 1\n\t *             - Agg Chunk 2\n\t *          - Merge Append\n\t *             - Agg Chunk 3\n\t *             - Agg Chunk 4\n\t *\n\t * in both cases, we use a sorted aggregation node to finalize the partial aggregation and\n\t * produce a proper sorting.\n\t */\n\treturn true;\n}\n\n/*\n * Replan the aggregation and create a partial aggregation at chunk level and finalize the\n * aggregation on top of an append node.\n *\n * The functionality is inspired by PostgreSQL's create_partitionwise_grouping_paths() function\n *\n * Generated aggregation paths:\n *\n * Finalize Aggregate\n *   -> Append\n *      -> Partial Aggregation\n *        - Chunk 1\n *      ...\n *      -> Append of partially compressed chunk 2\n *         -> Partial Aggregation\n *             -> Scan on uncompressed part of chunk 2\n *         -> Partial Aggregation\n *             -> Scan on compressed part of chunk 2\n *      ...\n *      -> Partial Aggregation N\n *        - Chunk N\n */\nvoid\ntsl_pushdown_partial_agg(PlannerInfo *root, Hypertable *ht, RelOptInfo *input_rel,\n\t\t\t\t\t\t RelOptInfo *output_rel, void *extra)\n{\n\tQuery *parse = root->parse;\n\n\t/* We are only interested in hypertables */\n\tif (!ht)\n\t\treturn;\n\n\t/* Grouping sets are not supported by the partial aggregation pushdown */\n\tif (parse->groupingSets)\n\t\treturn;\n\n\t/* Don't replan aggregation if we already have a MinMaxAggPath (e.g., created by\n\t * ts_preprocess_first_last_aggregates) */\n\tif (has_min_max_agg_path(output_rel))\n\t\treturn;\n\n\tAssert(extra != NULL);\n\tGroupPathExtraData *extra_data = (GroupPathExtraData *) extra;\n\n\t/* Determine the number of groups from the already planned aggregation */\n\tAggPath *existing_agg_path = get_existing_agg_path(output_rel);\n\tif (existing_agg_path == NULL)\n\t{\n\t\treturn;\n\t}\n\n\t/* Don't replan aggregation if it contains already partials or non-serializable aggregates */\n\tif (root->hasNonPartialAggs || root->hasNonSerialAggs)\n\t\treturn;\n\n\tdouble d_num_groups = existing_agg_path->numGroups;\n\tAssert(d_num_groups > 0);\n\n\t/* Construct partial group agg upper relation */\n\tRelOptInfo *partially_grouped_rel =\n\t\tfetch_upper_rel(root, UPPERREL_PARTIAL_GROUP_AGG, input_rel->relids);\n\tpartially_grouped_rel->consider_parallel = input_rel->consider_parallel;\n\tpartially_grouped_rel->consider_startup = input_rel->consider_startup;\n\tpartially_grouped_rel->reloptkind = input_rel->reloptkind;\n\tpartially_grouped_rel->serverid = input_rel->serverid;\n\tpartially_grouped_rel->userid = input_rel->userid;\n\tpartially_grouped_rel->useridiscurrent = input_rel->useridiscurrent;\n\tpartially_grouped_rel->fdwroutine = input_rel->fdwroutine;\n\n\t/* Build target list for partial aggregate paths */\n\tPathTarget *grouping_target = output_rel->reltarget;\n\tPathTarget *partial_grouping_target = ts_make_partial_grouping_target(root, grouping_target);\n\tpartially_grouped_rel->reltarget = partial_grouping_target;\n\n\t/* Calculate aggregation costs */\n\tif (!extra_data->partial_costs_set)\n\t{\n\t\t/* Init costs */\n\t\tMemSet(&extra_data->agg_partial_costs, 0, sizeof(AggClauseCosts));\n\t\tMemSet(&extra_data->agg_final_costs, 0, sizeof(AggClauseCosts));\n\n\t\t/* partial phase */\n\t\tget_agg_clause_costs(root, AGGSPLIT_INITIAL_SERIAL, &extra_data->agg_partial_costs);\n\n\t\t/* final phase */\n\t\tget_agg_clause_costs(root, AGGSPLIT_FINAL_DESERIAL, &extra_data->agg_final_costs);\n\n\t\textra_data->partial_costs_set = true;\n\t}\n\n\t/*\n\t * For queries with LIMIT, the aggregated relation can have a path with low\n\t * total cost, and a path with low startup cost. We must partialize both, so\n\t * loop through the entire pathlist.\n\t */\n\tListCell *lc;\n\tforeach (lc, output_rel->pathlist)\n\t{\n\t\tNode *path = lfirst(lc);\n\t\tif (!IsA(path, AggPath))\n\t\t{\n\t\t\t/*\n\t\t\t * Shouldn't happen, but here we work with arbitrary paths we don't\n\t\t\t * control, so it's not an assertion.\n\t\t\t */\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Generate the aggregation pushdown path */\n\t\tgenerate_agg_pushdown_path(root,\n\t\t\t\t\t\t\t\t   (Path *) path,\n\t\t\t\t\t\t\t\t   input_rel,\n\t\t\t\t\t\t\t\t   output_rel,\n\t\t\t\t\t\t\t\t   partially_grouped_rel,\n\t\t\t\t\t\t\t\t   grouping_target,\n\t\t\t\t\t\t\t\t   partial_grouping_target,\n\t\t\t\t\t\t\t\t   d_num_groups,\n\t\t\t\t\t\t\t\t   extra_data);\n\t}\n\n\t/* Replan aggregation if we were able to generate partially grouped rel paths */\n\tList *partially_grouped_paths =\n\t\tlist_concat(partially_grouped_rel->pathlist, partially_grouped_rel->partial_pathlist);\n\tif (partially_grouped_paths == NIL)\n\t\treturn;\n\n\t/* Prefer our paths */\n\toutput_rel->pathlist = NIL;\n\toutput_rel->partial_pathlist = NIL;\n\n\t/*\n\t * Finalize the created partially aggregated paths by adding a\n\t * 'Finalize Aggregate' node on top of them, and adding Sort and Gather\n\t * nodes as required.\n\t */\n\tAggClauseCosts *agg_final_costs = &extra_data->agg_final_costs;\n\tforeach (lc, partially_grouped_paths)\n\t{\n\t\tPath *partially_aggregated_path = lfirst(lc);\n\t\tAggStrategy final_strategy;\n\t\tif (contains_path_plain_or_sorted_agg(partially_aggregated_path))\n\t\t{\n\t\t\tconst bool is_sorted =\n\t\t\t\tpathkeys_contained_in(root->group_pathkeys, partially_aggregated_path->pathkeys);\n\t\t\tif (!is_sorted)\n\t\t\t{\n\t\t\t\tpartially_aggregated_path = (Path *) create_sort_path(root,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  output_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  partially_aggregated_path,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  root->group_pathkeys,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  -1.0);\n\t\t\t}\n\n\t\t\tfinal_strategy = parse->groupClause ? AGG_SORTED : AGG_PLAIN;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfinal_strategy = AGG_HASHED;\n\t\t}\n\n\t\t/*\n\t\t * We have to add a Gather or Gather Merge on top of parallel plans. It\n\t\t * goes above the Sort we might have added just before, so that the Sort\n\t\t * is parallelized as well.\n\t\t */\n\t\tif (partially_aggregated_path->parallel_workers > 0)\n\t\t{\n\t\t\tdouble total_groups =\n\t\t\t\tpartially_aggregated_path->rows * partially_aggregated_path->parallel_workers;\n\t\t\tif (partially_aggregated_path->pathkeys == NIL)\n\t\t\t{\n\t\t\t\tpartially_aggregated_path =\n\t\t\t\t\t(Path *) create_gather_path(root,\n\t\t\t\t\t\t\t\t\t\t\t\tpartially_grouped_rel,\n\t\t\t\t\t\t\t\t\t\t\t\tpartially_aggregated_path,\n\t\t\t\t\t\t\t\t\t\t\t\tpartially_grouped_rel->reltarget,\n\t\t\t\t\t\t\t\t\t\t\t\t/* required_outer = */ NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t&total_groups);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tpartially_aggregated_path =\n\t\t\t\t\t(Path *) create_gather_merge_path(root,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  partially_grouped_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  partially_aggregated_path,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  partially_grouped_rel->reltarget,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  partially_aggregated_path->pathkeys,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  /* required_outer = */ NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  &total_groups);\n\t\t\t}\n\t\t}\n\n\t\tadd_path(output_rel,\n\t\t\t\t (Path *) create_agg_path(root,\n\t\t\t\t\t\t\t\t\t\t  output_rel,\n\t\t\t\t\t\t\t\t\t\t  partially_aggregated_path,\n\t\t\t\t\t\t\t\t\t\t  grouping_target,\n\t\t\t\t\t\t\t\t\t\t  final_strategy,\n\t\t\t\t\t\t\t\t\t\t  AGGSPLIT_FINAL_DESERIAL,\n#if PG16_LT\n\t\t\t\t\t\t\t\t\t\t  parse->groupClause,\n#else\n\t\t\t\t\t\t\t\t\t\t  root->processed_groupClause,\n#endif\n\t\t\t\t\t\t\t\t\t\t  (List *) parse->havingQual,\n\t\t\t\t\t\t\t\t\t\t  agg_final_costs,\n\t\t\t\t\t\t\t\t\t\t  d_num_groups));\n\t}\n}\n"
  },
  {
    "path": "tsl/src/chunkwise_agg.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include <nodes/pathnodes.h>\n\n#include \"export.h\"\n#include \"hypertable.h\"\n\nvoid tsl_pushdown_partial_agg(PlannerInfo *root, Hypertable *ht, RelOptInfo *input_rel,\n\t\t\t\t\t\t\t  RelOptInfo *output_rel, void *extra);\n"
  },
  {
    "path": "tsl/src/compression/CMakeLists.txt",
    "content": "set(SOURCES\n    ${CMAKE_CURRENT_SOURCE_DIR}/api.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/batch_metadata_builder_bloom1.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/batch_metadata_builder_minmax.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/compression.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/compression_dml.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/compression_scankey.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/compression_storage.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/create.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/recompress.c)\ntarget_sources(${TSL_LIBRARY_NAME} PRIVATE ${SOURCES})\n\nadd_subdirectory(algorithms)\n"
  },
  {
    "path": "tsl/src/compression/README.md",
    "content": "# Compression Algorithms\n\nThis is a collection of compression algorithms that are used to compress data of different types.\nThe algorithms are optimized for time-series use-cases; many of them assume that adjacent rows will have \"similar\" values.\n\n## API\n\nEach compression algorithm the API is divided into two parts: a _compressor_ and a _decompression iterator_. The compressor\nis used to compress new data.\n\n- `<algorithm name>_compressor_alloc` - creates the compressor\n- `<algorithm_name>_compressor_append_null` - appends a null\n- `<algorithm_name>_compressor_append_value` - appends a non-null value\n- `<agorithm_name>_compressor_finish` - finalizes the compression and returns the compressed data\n\nData can be read back out using the decompression iterator. An iterator can operate backwards or forwards.\nThere is no random access. The api is\n\n- `<algorithm_name>_decompression_iterator_from_datum_<forward|reverse>` - create a new DatumIterator in the forward or reverse direction.\n- a DatumIterator has a function pointer called `try_next` that returns the next `DecompressResult`.\n\nA `DecompressResult` can either be a decompressed value datum, null, or a done marker to indicate that the iterator is done.\n\nEach decompression algorithm also contains send and recv function to get the external binary representations.\n\n`CompressionAlgorithmDefinition` is a structure that defines function pointers to get forward and reverse iterators\nas well as send and recv functions. The `definitions` array in  `compression.c` contains a `CompressionAlgorithmDefinition`\nfor each compression algorithm.\n\n## Base algorithms\n\nThe `simple8b rle` algorithm is a building block for many of the compression algorithms.\nIt compresses a series of `uint64` values. It compresses the data by packing the values into the least\namount of bits necessary for the magnitude of the int values, using run-length-encoding for large numbers of repeated values,\nA complete description is in the header file. Note that this is a header-only implementation as performance\nis paramount here as it is used as a primitive in all the other compression algorithms.\n\n## Compression Algorithms\n\n### DeltaDelta\n\nfor each integer, it takes the delta-of-deltas with the pervious integer,\nzigzag encodes this deltadelta, then finally simple8b_rle encodes this\nzigzagged result. This algorithm performs very well when the magnitude of the\ndelta between adjacent values tends not to vary much, and is optimal for\nfixed rate-of-change.\n\n\n### Gorilla\n\n`gorilla` encodes floats using the Facebook gorilla algorithm. It stores the\ncompressed xors of adjacent values. It is one of the few simple algorithms\nthat compresses floating point numbers reasonably well.\n\n### Dictionary\n\nThe dictionary mechanism stores data in two parts: a \"dictionary\" storing\neach unique value in the dataset (stored as an array, see below) and\nsimple8b_rle compressed list of indexes into the dictionary, ordered by row.\nThis scheme can store any type of data, but will only be a space improvement\nif the data set is of relatively low cardinality.\n\n### Array\n\nThe array \"compression\" method simply stores the data in an array-like\nstructure and does not actually compress it (though TOAST-based compression\ncan be applied on top). It is the compression mechanism used when no other\ncompression mechanism works. It can store any type of data.\n\n### Bool Compressor\n\nThe bool compressor is a simple compression algorithm that stores boolean values\nusing the simple8b_rle algorithm only, without any additional processing. During\ndecompression it decompresses the data and stores it in memory as a bitmap. The\nrow based iterators then walk through the bitmap. The bool compressor differs from\nthe other compressors in that it stores the last non-value as a place holder for\nthe null values. This is done to make vectorization easier.\n\n### UUID Compressor\n\nThe uuid compressor is a compression algorithm that aims at storing UUID v7 values\ncompressed as much as possible by taking advantage of the timestamp values being\npresent in the UUID.\n\nThe first part of the UUID where the timestamp resides is stored using the delta-delta\nalgorithm. The second part of the UUID is stored without compression, as a sequence of\nuint64 values.\n\nThe algorithm checks the cardinality of the values in the compressed batch and based on\nthe cardinality it decides wether it is worth to recompress the batch using the dictionary\ncompression algorithm. In that case it recompresses and stores the UUIDs as a dictionary.\n\n# Merging chunks while compressing #\n\n## Setup ##\n\nChunks will be merged during compression if we specify the `compress_chunk_time_interval` parameter.\nThis value will be used to merge chunks adjacent on the time dimension if possible. This allows usage\nof smaller chunk intervals which are rolled into bigger compressed chunks.\n\n## Operation ##\n\nCompression itself is altered by changing the destination compressed chunk from a newly created one to\nan already existing chunk which satisfies the necessary requirements (is adjacent to the compressed chunk\nand chunk interval can be increased not to go over compress chunk time interval).\n\nAfter compression completes, catalog is updated by dropping the compressed chunk and increasing the chunk\ninterval of the adjacent chunk to include its time dimension slice. Chunk constraints are updated as necessary.\n\n## Compression setup where time dimension is not the first column on order by ##\n\nWhen merging such chunks, due to the nature of sequence number ordering, we will inherently be left with\nchunks where the sequence numbers are not correctly ordered. In order to mitigate this issue, chunks are\nrecompressed immediately. This has obvious performance implications which might make merging chunks\nnot optimal for certain setups.\n\n# Picking default for `segment_by` and `order_by`.\n\nWe have two functions to determine the columns for `timescaledb.compress_segmentby` and `timescaledb.compress_orderby` . These functions can be called\nby the UI to give good defaults. They can also be called internally when a hypertable has compression enabled\nbut no values are provided to specify these options.\n\n## `_timescaledb_functions.get_segmentby_defaults`\n\nThis function determines a segment-by column to use. It returns a JSONB with the following top-level keys:\n- columns: an array of column names that should be used for segment by. Right now it always returns a single column.\n- confidence: a number between 0 and 10 (most confident) indicating how sure we are.\n- message: a message that should be shown to the user to evaluate the result.\n\nThe intuition is as follows:\n\nwe use 3 criterias:\n- We want to pick an \"important\" column for querying. We measure \"importance\", in terms of how early the column comes in an index (i.e. leading columns are very important, others less so). If there are no indexes, all columns will be considered if statistics are populated\n- The column has many rows for the same column value so that the segments will have many rows. We establish that a column will have many values if (i) it is not a dimension and (ii) either statistics tell us so (via `stadistinct` > 1) or, if statistics aren't populated, we check whether the column is a generated identity or serial column.\n- When we have multiple qualifying rows, we select the column where rows are spread most evenly across the distinct values. \n\nNaturally, statistics give us more confidence that the column has enough rows per segment. In this case we break ties by preferring columns from unique indexes. Otherwise, we prefer columns from non-unique indexes (we are less likely to run into a unique column there).\n\nThus, our preference is based on the whether the column is from a unique or regular index as well as the position of the column in the index. Given these preferences, we think ties happened rarely but will be resolved arbitrarily.\n\nOne final point: a number of tables don't have any indexed columns that aren't dimensions or serial columns. In this case, we have medium confidence that an empty segment by is correct.\n\n## `_timescaledb_functions.get_orderby_defaults`\n\nThis function determines which order by columns to use. It returns a JSONB with the following top-level keys:\n\n- clauses: an array of column names and sort order key words that shold be used for order by.\n- confidence: a number between 0 and 10 (most confident) indicating how sure we are.\n- message: a message that should be shown to the user to evaluate the result.\n\nThe order by is built in three steps:\n1) Use the column order in a unique index (removing the segment_by columns).\n2) Add any dimension columns\n3) Add the first attribute of any other index (to establish min-max filters on those columns).\n\nAll non-dimension columns are returned without a sort specifier (thus using `ASC` as default). The dimension columns use `DESC`."
  },
  {
    "path": "tsl/src/compression/algorithms/CMakeLists.txt",
    "content": "set(SOURCES\n    ${CMAKE_CURRENT_SOURCE_DIR}/array.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/datum_serialize.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/deltadelta.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/dictionary.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/gorilla.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/bool_compress.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/null.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/uuid_compress.c)\ntarget_sources(${TSL_LIBRARY_NAME} PRIVATE ${SOURCES})\n"
  },
  {
    "path": "tsl/src/compression/algorithms/array.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/htup_details.h>\n#include <access/tupmacs.h>\n#include <adts/char_vec.h>\n#include <catalog/namespace.h>\n#include <catalog/pg_type.h>\n#include <common/base64.h>\n#include <funcapi.h>\n#include <utils/lsyscache.h>\n#include <utils/syscache.h>\n\n#include \"array.h\"\n#include \"compression/compression.h\"\n#include \"datum_serialize.h\"\n#include \"guc.h\"\n#include \"simple8b_rle.h\"\n#include \"simple8b_rle_bitarray.h\"\n#include \"simple8b_rle_bitmap.h\"\n\n#include \"compression/arrow_c_data_interface.h\"\n\n/* A \"compressed\" array\n *     uint8 has_nulls: 1 iff this has a nulls bitmap stored before the data\n *     Oid element_type: the element stored by this array\n *     simple8b_rle nulls: optional bitmap of nulls within the array\n *     simple8b_rle sizes: the sizes of each data element\n *     char data[]: the elements of the array\n */\n\ntypedef struct ArrayCompressed\n{\n\tCompressedDataHeaderFields;\n\tuint8 has_nulls;\n\tuint8 padding[6];\n\tOid element_type;\n\t/* 8-byte alignment sentinel for the following fields */\n\tuint64 alignment_sentinel[FLEXIBLE_ARRAY_MEMBER];\n} ArrayCompressed;\n\nbool\narray_compressed_has_nulls(const CompressedDataHeader *header)\n{\n\tconst ArrayCompressed *ac = (const ArrayCompressed *) header;\n\treturn ac->has_nulls;\n}\n\nstatic void\npg_attribute_unused() assertions(void)\n{\n\tArrayCompressed test_val = { .vl_len_ = { 0 } };\n\tSimple8bRleSerialized test_simple8b = { 0 };\n\t/* make sure no padding bytes make it to disk */\n\tStaticAssertStmt(sizeof(ArrayCompressed) ==\n\t\t\t\t\t\t sizeof(test_val.vl_len_) + sizeof(test_val.compression_algorithm) +\n\t\t\t\t\t\t\t sizeof(test_val.has_nulls) + sizeof(test_val.padding) +\n\t\t\t\t\t\t\t sizeof(test_val.element_type),\n\t\t\t\t\t \"ArrayCompressed wrong size\");\n\tStaticAssertStmt(sizeof(ArrayCompressed) == 16, \"ArrayCompressed wrong size\");\n\n\t/* Note about alignment: the data[] field stores arbitrary Postgres types using store_att_byval\n\t * and fetch_att. For this to work the data must be aligned according to the types alignment\n\t * parameter (in CREATE TYPE; valid values are 1,2,4,8 bytes). In order to ease implementation,\n\t * we simply align the start of data[] on a MAXALIGN (8-byte) boundary. Individual items in the\n\t * array are then aligned as specified by the array element type. See top of array.h header in\n\t * Postgres source code since it uses the same trick. Thus, we make sure that all fields\n\t * before the alignment sentinel are 8-byte aligned, and also that the two Simple8bRleSerialized\n\t * elements before the data element are themselves 8-byte aligned as well.\n\t */\n\n\tStaticAssertStmt(offsetof(ArrayCompressed, alignment_sentinel) % MAXIMUM_ALIGNOF == 0,\n\t\t\t\t\t \"variable sized data must be 8-byte aligned\");\n\tStaticAssertStmt(sizeof(Simple8bRleSerialized) % MAXIMUM_ALIGNOF == 0,\n\t\t\t\t\t \"Simple8bRle data must be 8-byte aligned\");\n\tStaticAssertStmt(sizeof(test_simple8b.slots[0]) % MAXIMUM_ALIGNOF == 0,\n\t\t\t\t\t \"Simple8bRle variable-length slots must be 8-byte aligned\");\n}\n\ntypedef struct ArrayCompressedData\n{\n\tOid element_type;\n\tSimple8bRleSerialized *nulls; /* NULL if no nulls */\n\tSimple8bRleSerialized *sizes;\n\tconst char *data;\n\tSize data_len;\n} ArrayCompressedData;\n\ntypedef struct ArrayCompressor\n{\n\tSimple8bRleCompressor nulls;\n\tSimple8bRleCompressor sizes;\n\tchar_vec data;\n\tOid type;\n\tDatumSerializer *serializer;\n\tbool has_nulls;\n} ArrayCompressor;\n\ntypedef struct ExtendedCompressor\n{\n\tCompressor base;\n\tArrayCompressor *internal;\n\tOid element_type;\n} ExtendedCompressor;\n\ntypedef struct ArrayDecompressionIterator\n{\n\tDecompressionIterator base;\n\tSimple8bRleDecompressionIterator nulls;\n\tSimple8bRleDecompressionIterator sizes;\n\tconst char *data;\n\tuint32 num_data_bytes;\n\tuint32 data_offset;\n\tDatumDeserializer *deserializer;\n\tbool has_nulls;\n} ArrayDecompressionIterator;\n\n/******************\n *** Compressor ***\n ******************/\n\nstatic void\narray_compressor_append_datum(Compressor *compressor, Datum val)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tif (extended->internal == NULL)\n\t\textended->internal = array_compressor_alloc(extended->element_type);\n\n\tarray_compressor_append(extended->internal, val);\n}\n\nstatic void\narray_compressor_append_null_value(Compressor *compressor)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tif (extended->internal == NULL)\n\t\textended->internal = array_compressor_alloc(extended->element_type);\n\n\tarray_compressor_append_null(extended->internal);\n}\n\nstatic bool\narray_compressor_is_full(Compressor *compressor, Datum val)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tif (extended->internal == NULL)\n\t\textended->internal = array_compressor_alloc(extended->element_type);\n\n\tSize datum_size_and_align;\n\tArrayCompressor *array_comp = extended->internal;\n\tif (datum_serializer_value_may_be_toasted(array_comp->serializer))\n\t\tval = PointerGetDatum(PG_DETOAST_DATUM_PACKED(val));\n\n\tdatum_size_and_align =\n\t\tdatum_get_bytes_size(array_comp->serializer, array_comp->data.num_elements, val) -\n\t\tarray_comp->data.num_elements;\n\n\t/* If we can't fit new datum in the max size, we are full */\n\treturn (datum_size_and_align + array_comp->data.num_elements) > MAX_ARRAY_COMPRESSOR_SIZE_BYTES;\n}\n\nstatic void *\narray_compressor_finish_and_reset(Compressor *compressor)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tvoid *compressed = array_compressor_finish(extended->internal);\n\tpfree(extended->internal);\n\textended->internal = NULL;\n\treturn compressed;\n}\n\nconst Compressor array_compressor = {\n\t.append_val = array_compressor_append_datum,\n\t.append_null = array_compressor_append_null_value,\n\t.is_full = array_compressor_is_full,\n\t.finish = array_compressor_finish_and_reset,\n};\n\nCompressor *\narray_compressor_for_type(Oid element_type)\n{\n\tExtendedCompressor *compressor = palloc(sizeof(*compressor));\n\t*compressor = (ExtendedCompressor){\n\t\t.base = array_compressor,\n\t\t.element_type = element_type,\n\t};\n\treturn &compressor->base;\n}\n\nArrayCompressor *\narray_compressor_alloc(Oid type_to_compress)\n{\n\tArrayCompressor *compressor = palloc(sizeof(*compressor));\n\tcompressor->has_nulls = false;\n\n\tsimple8brle_compressor_init(&compressor->nulls);\n\tsimple8brle_compressor_init(&compressor->sizes);\n\tchar_vec_init(&compressor->data, CurrentMemoryContext, 0);\n\n\tcompressor->type = type_to_compress;\n\tcompressor->serializer = create_datum_serializer(type_to_compress);\n\treturn compressor;\n}\n\nvoid\narray_compressor_append_null(ArrayCompressor *compressor)\n{\n\tcompressor->has_nulls = true;\n\tsimple8brle_compressor_append(&compressor->nulls, 1);\n}\n\nvoid\narray_compressor_append(ArrayCompressor *compressor, Datum val)\n{\n\tSize datum_size_and_align;\n\tchar *start_ptr;\n\tsimple8brle_compressor_append(&compressor->nulls, 0);\n\tif (datum_serializer_value_may_be_toasted(compressor->serializer))\n\t\tval = PointerGetDatum(PG_DETOAST_DATUM_PACKED(val));\n\n\tdatum_size_and_align =\n\t\tdatum_get_bytes_size(compressor->serializer, compressor->data.num_elements, val) -\n\t\tcompressor->data.num_elements;\n\n\tsimple8brle_compressor_append(&compressor->sizes, datum_size_and_align);\n\n\t/* datum_to_bytes_and_advance will zero any padding bytes, so we need not do so here */\n\tchar_vec_reserve(&compressor->data, datum_size_and_align);\n\tstart_ptr = compressor->data.data + compressor->data.num_elements;\n\tcompressor->data.num_elements += datum_size_and_align;\n\n\tdatum_to_bytes_and_advance(compressor->serializer, start_ptr, &datum_size_and_align, val);\n\tAssert(datum_size_and_align == 0);\n}\n\ntypedef struct ArrayCompressorSerializationInfo\n{\n\tSimple8bRleSerialized *sizes;\n\tSimple8bRleSerialized *nulls;\n\tchar_vec data;\n\tSize total;\n} ArrayCompressorSerializationInfo;\n\nArrayCompressorSerializationInfo *\narray_compressor_get_serialization_info(ArrayCompressor *compressor)\n{\n\tArrayCompressorSerializationInfo *info = palloc(sizeof(*info));\n\t*info = (ArrayCompressorSerializationInfo){\n\t\t.sizes = simple8brle_compressor_finish(&compressor->sizes),\n\t\t.nulls = compressor->has_nulls ? simple8brle_compressor_finish(&compressor->nulls) : NULL,\n\t\t.data = compressor->data,\n\t\t.total = 0,\n\t};\n\tif (info->nulls != NULL)\n\t\tinfo->total += simple8brle_serialized_total_size(info->nulls);\n\n\tif (info->sizes != NULL)\n\t\tinfo->total += simple8brle_serialized_total_size(info->sizes);\n\n\tinfo->total += compressor->data.num_elements;\n\treturn info;\n}\n\nSize\narray_compression_serialization_size(ArrayCompressorSerializationInfo *info)\n{\n\treturn info->total;\n}\n\nuint32\narray_compression_serialization_num_elements(ArrayCompressorSerializationInfo *info)\n{\n\tCheckCompressedData(info->sizes != NULL);\n\treturn info->sizes->num_elements;\n}\n\nchar *\nbytes_serialize_array_compressor_and_advance(char *dst, PG_USED_FOR_ASSERTS_ONLY Size dst_size,\n\t\t\t\t\t\t\t\t\t\t\t ArrayCompressorSerializationInfo *info)\n{\n\tuint32 sizes_bytes = simple8brle_serialized_total_size(info->sizes);\n\n\tAssert(dst_size == info->total);\n\n\tif (info->nulls != NULL)\n\t{\n\t\tuint32 nulls_bytes = simple8brle_serialized_total_size(info->nulls);\n\t\tAssert(dst_size >= nulls_bytes);\n\t\tdst = bytes_serialize_simple8b_and_advance(dst, nulls_bytes, info->nulls);\n\t\tdst_size -= nulls_bytes;\n\t}\n\n\tAssert(dst_size >= sizes_bytes);\n\tdst = bytes_serialize_simple8b_and_advance(dst, sizes_bytes, info->sizes);\n\tdst_size -= sizes_bytes;\n\n\tAssert(dst_size == info->data.num_elements);\n\tmemcpy(dst, info->data.data, info->data.num_elements);\n\treturn dst + info->data.num_elements;\n}\n\nstatic ArrayCompressed *\narray_compressed_from_serialization_info(ArrayCompressorSerializationInfo *info, Oid element_type)\n{\n\tchar *compressed_data;\n\tArrayCompressed *compressed_array;\n\tSize compressed_size = sizeof(ArrayCompressed) + info->total;\n\n\tif (!AllocSizeIsValid(compressed_size))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),\n\t\t\t\t errmsg(\"compressed size exceeds the maximum allowed (%d)\", (int) MaxAllocSize)));\n\n\tcompressed_data = palloc0(compressed_size);\n\tcompressed_array = (ArrayCompressed *) compressed_data;\n\t*compressed_array = (ArrayCompressed){\n\t\t.compression_algorithm = COMPRESSION_ALGORITHM_ARRAY,\n\t\t.has_nulls = info->nulls != NULL,\n\t\t.element_type = element_type,\n\t};\n\tSET_VARSIZE(compressed_array->vl_len_, compressed_size);\n\tcompressed_data += sizeof(ArrayCompressed);\n\tcompressed_size -= sizeof(ArrayCompressed);\n\n\tbytes_serialize_array_compressor_and_advance(compressed_data, compressed_size, info);\n\treturn compressed_array;\n}\n\nvoid *\narray_compressor_finish(ArrayCompressor *compressor)\n{\n\tArrayCompressorSerializationInfo *info = array_compressor_get_serialization_info(compressor);\n\tif (info->sizes == NULL)\n\t\treturn NULL;\n\n\treturn array_compressed_from_serialization_info(info, compressor->type);\n}\n\n/******************\n *** Decompress ***\n ******************/\n\nstatic ArrayCompressedData\narray_compressed_data_from_bytes(StringInfo serialized_data, Oid element_type, bool has_nulls)\n{\n\tArrayCompressedData data = { .element_type = element_type };\n\n\tif (has_nulls)\n\t{\n\t\tdata.nulls = bytes_deserialize_simple8b_and_advance(serialized_data);\n\t}\n\n\tdata.sizes = bytes_deserialize_simple8b_and_advance(serialized_data);\n\n\tdata.data = serialized_data->data + serialized_data->cursor;\n\tdata.data_len = serialized_data->len - serialized_data->cursor;\n\n\treturn data;\n}\n\nDecompressionIterator *\narray_decompression_iterator_alloc_forward(StringInfo serialized_data, Oid element_type,\n\t\t\t\t\t\t\t\t\t\t   bool has_nulls)\n{\n\tArrayCompressedData data =\n\t\tarray_compressed_data_from_bytes(serialized_data, element_type, has_nulls);\n\n\tArrayDecompressionIterator *iterator = palloc(sizeof(*iterator));\n\titerator->base.compression_algorithm = COMPRESSION_ALGORITHM_ARRAY;\n\titerator->base.forward = true;\n\titerator->base.element_type = element_type;\n\titerator->base.try_next = array_decompression_iterator_try_next_forward;\n\n\titerator->has_nulls = data.nulls != NULL;\n\tif (iterator->has_nulls)\n\t\tsimple8brle_decompression_iterator_init_forward(&iterator->nulls, data.nulls);\n\n\tsimple8brle_decompression_iterator_init_forward(&iterator->sizes, data.sizes);\n\n\titerator->data = data.data;\n\titerator->num_data_bytes = data.data_len;\n\titerator->data_offset = 0;\n\titerator->deserializer = create_datum_deserializer(iterator->base.element_type);\n\n\treturn &iterator->base;\n}\n\nDecompressionIterator *\ntsl_array_decompression_iterator_from_datum_forward(Datum compressed_array, Oid element_type)\n{\n\tvoid *compressed_data = (void *) PG_DETOAST_DATUM(compressed_array);\n\tStringInfoData si = { .data = compressed_data, .len = VARSIZE(compressed_data) };\n\n\tArrayCompressed *compressed_array_header = consumeCompressedData(&si, sizeof(ArrayCompressed));\n\n\tAssert(compressed_array_header->compression_algorithm == COMPRESSION_ALGORITHM_ARRAY);\n\n\tCheckCompressedData(element_type == compressed_array_header->element_type);\n\n\treturn array_decompression_iterator_alloc_forward(&si,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  compressed_array_header->element_type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  compressed_array_header->has_nulls == 1);\n}\n\nextern DecompressResult\narray_decompression_iterator_try_next_forward(DecompressionIterator *general_iter)\n{\n\tSimple8bRleDecompressResult datum_size;\n\tArrayDecompressionIterator *iter;\n\tDatum val;\n\tconst char *start_pointer;\n\n\tAssert(general_iter->compression_algorithm == COMPRESSION_ALGORITHM_ARRAY &&\n\t\t   general_iter->forward);\n\titer = (ArrayDecompressionIterator *) general_iter;\n\n\tif (iter->has_nulls)\n\t{\n\t\tSimple8bRleDecompressResult null =\n\t\t\tsimple8brle_decompression_iterator_try_next_forward(&iter->nulls);\n\t\tif (null.is_done)\n\t\t\treturn (DecompressResult){\n\t\t\t\t.is_done = true,\n\t\t\t};\n\n\t\tif ((null.val & 1) != 0)\n\t\t{\n\t\t\treturn (DecompressResult){\n\t\t\t\t.is_null = true,\n\t\t\t};\n\t\t}\n\t}\n\n\tdatum_size = simple8brle_decompression_iterator_try_next_forward(&iter->sizes);\n\tif (datum_size.is_done)\n\t\treturn (DecompressResult){\n\t\t\t.is_done = true,\n\t\t};\n\n\tCheckCompressedData(iter->data_offset + datum_size.val <= iter->num_data_bytes);\n\n\tstart_pointer = iter->data + iter->data_offset;\n\tval = bytes_to_datum_and_advance(iter->deserializer, &start_pointer);\n\titer->data_offset += datum_size.val;\n\tCheckCompressedData(iter->data + iter->data_offset == start_pointer);\n\n\treturn (DecompressResult){\n\t\t.val = val,\n\t};\n}\n\n/**************************\n *** Decompress Reverse ***\n **************************/\n\nDecompressionIterator *\ntsl_array_decompression_iterator_from_datum_reverse(Datum compressed_array, Oid element_type)\n{\n\tArrayCompressed *compressed_array_header;\n\tArrayCompressedData array_compressed_data;\n\tArrayDecompressionIterator *iterator = palloc(sizeof(*iterator));\n\titerator->base.compression_algorithm = COMPRESSION_ALGORITHM_ARRAY;\n\titerator->base.forward = false;\n\titerator->base.element_type = element_type;\n\titerator->base.try_next = array_decompression_iterator_try_next_reverse;\n\n\tvoid *compressed_data = PG_DETOAST_DATUM(compressed_array);\n\tStringInfoData si = { .data = compressed_data, .len = VARSIZE(compressed_data) };\n\tcompressed_array_header = consumeCompressedData(&si, sizeof(ArrayCompressed));\n\n\tAssert(compressed_array_header->compression_algorithm == COMPRESSION_ALGORITHM_ARRAY);\n\tif (element_type != compressed_array_header->element_type)\n\t\telog(ERROR, \"trying to decompress the wrong type\");\n\n\tarray_compressed_data = array_compressed_data_from_bytes(&si,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t compressed_array_header->element_type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t compressed_array_header->has_nulls);\n\n\titerator->has_nulls = array_compressed_data.nulls != NULL;\n\tif (iterator->has_nulls)\n\t\tsimple8brle_decompression_iterator_init_reverse(&iterator->nulls,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tarray_compressed_data.nulls);\n\n\tsimple8brle_decompression_iterator_init_reverse(&iterator->sizes, array_compressed_data.sizes);\n\n\titerator->data = array_compressed_data.data;\n\titerator->num_data_bytes = array_compressed_data.data_len;\n\titerator->data_offset = iterator->num_data_bytes;\n\titerator->deserializer = create_datum_deserializer(iterator->base.element_type);\n\n\treturn &iterator->base;\n}\n\nstatic ArrowArray *tsl_bool_array_decompress_all(Datum compressed_array, Oid element_type,\n\t\t\t\t\t\t\t\t\t\t\t\t MemoryContext dest_mctx);\nstatic ArrowArray *tsl_text_array_decompress_all(Datum compressed_array, Oid element_type,\n\t\t\t\t\t\t\t\t\t\t\t\t MemoryContext dest_mctx);\nstatic ArrowArray *tsl_uuid_array_decompress_all(Datum compressed_array, Oid element_type,\n\t\t\t\t\t\t\t\t\t\t\t\t MemoryContext dest_mctx);\n\n/* Pass through to the specialized functions below for BOOL and TEXT */\nArrowArray *\ntsl_array_decompress_all(Datum compressed_array, Oid element_type, MemoryContext dest_mctx)\n{\n\tswitch (element_type)\n\t{\n\t\tcase BOOLOID:\n\t\t\treturn tsl_bool_array_decompress_all(compressed_array, element_type, dest_mctx);\n\t\tcase TEXTOID:\n\t\t\treturn tsl_text_array_decompress_all(compressed_array, element_type, dest_mctx);\n\t\tcase UUIDOID:\n\t\t\treturn tsl_uuid_array_decompress_all(compressed_array, element_type, dest_mctx);\n\t\tdefault:\n\t\t\telog(ERROR, \"unsupported array type %u for bulk decompression\", element_type);\n\t\t\tbreak;\n\t}\n\treturn NULL;\n}\n\nstatic ArrowArray *\ntsl_bool_array_decompress_all(Datum compressed_array, Oid element_type, MemoryContext dest_mctx)\n{\n\tAssert(element_type == BOOLOID);\n\tvoid *compressed_data = PG_DETOAST_DATUM(compressed_array);\n\tStringInfoData si = { .data = compressed_data, .len = VARSIZE(compressed_data) };\n\tArrayCompressed *header = consumeCompressedData(&si, sizeof(ArrayCompressed));\n\n\tAssert(header->compression_algorithm == COMPRESSION_ALGORITHM_ARRAY);\n\tCheckCompressedData(header->element_type == BOOLOID);\n\n\tSimple8bRleSerialized *nulls_serialized = NULL;\n\tif (header->has_nulls)\n\t{\n\t\tnulls_serialized = bytes_deserialize_simple8b_and_advance(&si);\n\t}\n\n\tSimple8bRleSerialized *sizes_serialized = bytes_deserialize_simple8b_and_advance(&si);\n\n\tconst uint32 n_notnull = sizes_serialized->num_elements;\n\tconst uint32 n_total = header->has_nulls ? nulls_serialized->num_elements : n_notnull;\n\tconst uint32 n_padded_bits = n_total + 63;\n\tconst uint32 n_padded_bytes = n_padded_bits / 8;\n\n\tuint64 *validity_bitmap = NULL;\n\tuint64 *values = MemoryContextAllocZero(dest_mctx, n_padded_bytes);\n\n\tMemoryContext old_context = MemoryContextSwitchTo(dest_mctx);\n\t/* Decompress the nulls */\n\tSimple8bRleBitArray validity_bits =\n\t\tsimple8brle_bitarray_decompress(nulls_serialized, /* inverted*/ true);\n\tvalidity_bitmap = validity_bits.data;\n\tMemoryContextSwitchTo(old_context);\n\n\t/* Decompress the values using the iterator based decompressor */\n\t{\n\t\tint position = 0;\n\t\tDecompressionIterator *iter =\n\t\t\ttsl_array_decompression_iterator_from_datum_forward(PointerGetDatum(compressed_data),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tBOOLOID);\n\t\tfor (DecompressResult r = array_decompression_iterator_try_next_forward(iter); !r.is_done;\n\t\t\t r = array_decompression_iterator_try_next_forward(iter))\n\t\t{\n\t\t\tif (!r.is_null)\n\t\t\t{\n\t\t\t\tbool data = DatumGetBool(r.val) == true;\n\t\t\t\tif (data)\n\t\t\t\t{\n\t\t\t\t\tarrow_set_row_validity(values, position, true);\n\t\t\t\t}\n\t\t\t}\n\t\t\t++position;\n\t\t}\n\t}\n\n\tArrowArray *result =\n\t\tMemoryContextAllocZero(dest_mctx, sizeof(ArrowArray) + (sizeof(void *) * 2));\n\tconst void **buffers = (const void **) &result[1];\n\tbuffers[0] = validity_bitmap;\n\tbuffers[1] = values;\n\tresult->n_buffers = 2;\n\tresult->buffers = buffers;\n\tresult->length = n_total;\n\tresult->null_count = n_total - n_notnull;\n\treturn result;\n}\n\nstatic ArrowArray *\ntsl_uuid_array_decompress_all(Datum compressed_array, Oid element_type, MemoryContext dest_mctx)\n{\n\tAssert(element_type == UUIDOID);\n\tvoid *compressed_data = PG_DETOAST_DATUM(compressed_array);\n\tStringInfoData si = { .data = compressed_data, .len = VARSIZE_ANY(compressed_data) };\n\tArrayCompressed *header = consumeCompressedData(&si, sizeof(ArrayCompressed));\n\n\tAssert(header->compression_algorithm == COMPRESSION_ALGORITHM_ARRAY);\n\tCheckCompressedData(header->element_type == UUIDOID);\n\n\tSimple8bRleSerialized *nulls_serialized = NULL;\n\tif (header->has_nulls)\n\t{\n\t\tnulls_serialized = bytes_deserialize_simple8b_and_advance(&si);\n\t}\n\n\tSimple8bRleSerialized *sizes_serialized = bytes_deserialize_simple8b_and_advance(&si);\n\n\tconst uint32 n_notnull = sizes_serialized->num_elements;\n\t/* the nulls_serialized test shouldn't be necessary, but clang needs this to pass without\n\t * warnings */\n\tconst uint32 n_total = (header->has_nulls && nulls_serialized != NULL) ?\n\t\t\t\t\t\t\t   nulls_serialized->num_elements :\n\t\t\t\t\t\t\t   n_notnull;\n\tconst uint32 n_bytes = n_total * 16;\n\n\tconst uint64 *restrict compressed_non_null_values = (const uint64 *) (si.data + si.cursor);\n\tCheckCompressedData(n_notnull * 16 <= (uint32) (si.len - si.cursor));\n\n\tuint64 *restrict validity_bitmap = NULL;\n\tuint64 *restrict values = MemoryContextAlloc(dest_mctx, n_bytes);\n\n\tMemoryContext old_context = MemoryContextSwitchTo(dest_mctx);\n\t/* Decompress the nulls */\n\tSimple8bRleBitArray validity_bits =\n\t\tsimple8brle_bitarray_decompress(nulls_serialized, /* inverted*/ true);\n\tvalidity_bitmap = validity_bits.data;\n\tMemoryContextSwitchTo(old_context);\n\n\t/* Check the alignment of compressed_non_null_values */\n\tif (((uintptr_t) compressed_non_null_values % 8) == 0)\n\t{\n\t\tint position = 0;\n\t\tfor (uint32 i = 0; i < n_total; i++)\n\t\t{\n\t\t\tif (arrow_row_is_valid(validity_bitmap, i))\n\t\t\t{\n\t\t\t\t/* Copy the 16 bytes of the UUID with simple assignment, because we know it is\n\t\t\t\t * aligned */\n\t\t\t\tvalues[i * 2] = compressed_non_null_values[position * 2];\n\t\t\t\tvalues[i * 2 + 1] = compressed_non_null_values[position * 2 + 1];\n\t\t\t\tposition++;\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t{\n\t\tint position = 0;\n\t\tfor (uint32 i = 0; i < n_total; i++)\n\t\t{\n\t\t\tif (arrow_row_is_valid(validity_bitmap, i))\n\t\t\t{\n\t\t\t\t/* Copy the 16 bytes of the UUID with memcpy */\n\t\t\t\tmemcpy(&values[i * 2], &compressed_non_null_values[position * 2], 16);\n\t\t\t\tposition++;\n\t\t\t}\n\t\t}\n\t}\n\n\tArrowArray *result =\n\t\tMemoryContextAllocZero(dest_mctx, sizeof(ArrowArray) + (sizeof(void *) * 2));\n\tconst void **buffers = (const void **) &result[1];\n\tbuffers[0] = validity_bitmap;\n\tbuffers[1] = values;\n\tresult->n_buffers = 2;\n\tresult->buffers = buffers;\n\tresult->length = n_total;\n\tresult->null_count = n_total - n_notnull;\n\treturn result;\n}\n\n#define ELEMENT_TYPE uint32\n#include \"simple8b_rle_decompress_all.h\"\n#undef ELEMENT_TYPE\n\nstatic ArrowArray *\ntsl_text_array_decompress_all(Datum compressed_array, Oid element_type, MemoryContext dest_mctx)\n{\n\tAssert(element_type == TEXTOID);\n\tvoid *compressed_data = PG_DETOAST_DATUM(compressed_array);\n\tStringInfoData si = { .data = compressed_data, .len = VARSIZE(compressed_data) };\n\tArrayCompressed *header = consumeCompressedData(&si, sizeof(ArrayCompressed));\n\n\tAssert(header->compression_algorithm == COMPRESSION_ALGORITHM_ARRAY);\n\tCheckCompressedData(header->element_type == TEXTOID);\n\n\treturn text_array_decompress_all_serialized_no_header(&si, header->has_nulls, dest_mctx);\n}\n\nArrowArray *\ntext_array_decompress_all_serialized_no_header(StringInfo si, bool has_nulls,\n\t\t\t\t\t\t\t\t\t\t\t   MemoryContext dest_mctx)\n{\n\tSimple8bRleSerialized *nulls_serialized = NULL;\n\tif (has_nulls)\n\t{\n\t\tnulls_serialized = bytes_deserialize_simple8b_and_advance(si);\n\t}\n\n\tSimple8bRleSerialized *sizes_serialized = bytes_deserialize_simple8b_and_advance(si);\n\n\tuint32 n_notnull;\n\tconst uint32 *sizes = simple8brle_decompress_all_uint32(sizes_serialized, &n_notnull);\n\tconst uint32 n_total = has_nulls ? nulls_serialized->num_elements : n_notnull;\n\tCheckCompressedData(n_total >= n_notnull);\n\n\tuint32 *offsets =\n\t\t(uint32 *) MemoryContextAlloc(dest_mctx,\n\t\t\t\t\t\t\t\t\t  pad_to_multiple(64, sizeof(*offsets) * (n_total + 1)));\n\tuint8 *arrow_bodies =\n\t\t(uint8 *) MemoryContextAlloc(dest_mctx, pad_to_multiple(64, si->len - si->cursor));\n\n\tuint32 offset = 0;\n\tfor (uint32 i = 0; i < n_notnull; i++)\n\t{\n\t\tconst void *unaligned = consumeCompressedData(si, sizes[i]);\n\n\t\t/*\n\t\t * We start reading from the end of previous datum, but this pointer\n\t\t * might be not aligned as required for varlena-4b struct. We have to\n\t\t * align it here. Note that sizes[i] includes the alignment as well in\n\t\t * addition to the varlena size.\n\t\t *\n\t\t * See the corresponding row-by-row code in bytes_to_datum_and_advance().\n\t\t */\n\t\tconst void *vardata =\n\t\t\tDatumGetPointer(att_align_pointer(unaligned, TYPALIGN_INT, -1, unaligned));\n\n\t\t/*\n\t\t * Check for potentially corrupt varlena headers since we're reading them\n\t\t * directly from compressed data.\n\t\t */\n\t\tif (VARATT_IS_4B_U(vardata))\n\t\t{\n\t\t\t/*\n\t\t\t * Full varsize must be larger or equal than the header size so that\n\t\t\t * the calculation of size without header doesn't overflow.\n\t\t\t */\n\t\t\tCheckCompressedData(VARSIZE_4B(vardata) >= VARHDRSZ);\n\t\t}\n\t\telse if (VARATT_IS_1B(vardata))\n\t\t{\n\t\t\t/* Can't have a TOAST pointer here. */\n\t\t\tCheckCompressedData(!VARATT_IS_1B_E(vardata));\n\n\t\t\t/*\n\t\t\t * Full varsize must be larger or equal than the header size so that\n\t\t\t * the calculation of size without header doesn't overflow.\n\t\t\t */\n\t\t\tCheckCompressedData(VARSIZE_1B(vardata) >= VARHDRSZ_SHORT);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/*\n\t\t\t * Can only have an uncompressed datum with 1-byte or 4-byte header\n\t\t\t * here, no TOAST or compressed data.\n\t\t\t */\n\t\t\tCheckCompressedData(false);\n\t\t}\n\n\t\t/*\n\t\t * Size of varlena plus alignment must match the size stored in the\n\t\t * sizes array for this element.\n\t\t */\n\t\tconst Datum alignment_bytes = PointerGetDatum(vardata) - PointerGetDatum(unaligned);\n\t\tCheckCompressedData(VARSIZE_ANY(vardata) + alignment_bytes == sizes[i]);\n\n\t\tconst uint32 textlen = VARSIZE_ANY_EXHDR(vardata);\n\t\tmemcpy(&arrow_bodies[offset], VARDATA_ANY(vardata), textlen);\n\n\t\toffsets[i] = offset;\n\n\t\tCheckCompressedData(offset <= offset + textlen); /* Check for overflow. */\n\t\toffset += textlen;\n\t}\n\toffsets[n_notnull] = offset;\n\n\tuint64 *restrict validity_bitmap = NULL;\n\tif (has_nulls)\n\t{\n\t\tconst int validity_bitmap_bytes = sizeof(uint64) * (pad_to_multiple(64, n_total) / 64);\n\t\tvalidity_bitmap = MemoryContextAlloc(dest_mctx, validity_bitmap_bytes);\n\n\t\t/*\n\t\t * First, mark all data as valid, we will fill the nulls later if needed.\n\t\t * Note that the validity bitmap size is a multiple of 64 bits. We have to\n\t\t * fill the tail bits with zeros, because the corresponding elements are not\n\t\t * valid.\n\t\t *\n\t\t */\n\t\tmemset(validity_bitmap, 0xFF, validity_bitmap_bytes);\n\t\tif (n_total % 64)\n\t\t{\n\t\t\tconst uint64 tail_mask = ~0ULL >> (64 - n_total % 64);\n\t\t\tvalidity_bitmap[n_total / 64] &= tail_mask;\n\t\t}\n\n\t\t/*\n\t\t * We have decompressed the data with nulls skipped, reshuffle it\n\t\t * according to the nulls bitmap.\n\t\t */\n\t\tconst Simple8bRleBitmap nulls = simple8brle_bitmap_decompress(nulls_serialized);\n\t\tCheckCompressedData(n_notnull + simple8brle_bitmap_num_ones(&nulls) == n_total);\n\n\t\tint current_notnull_element = n_notnull - 1;\n\t\tfor (int i = n_total - 1; i >= 0; i--)\n\t\t{\n\t\t\tAssert(i >= current_notnull_element);\n\n\t\t\t/*\n\t\t\t * The index of the corresponding offset is higher by one than\n\t\t\t * the index of the element. The offset[0] is never affected by\n\t\t\t * this shuffling and is always 0.\n\t\t\t * Note that unlike the usual null reshuffling in other algorithms,\n\t\t\t * for offsets, even if all elements are null, the starting offset\n\t\t\t * is well-defined and we can do this assignment. This case is only\n\t\t\t * accessible through fuzzing. Through SQL, all-null batches result\n\t\t\t * in a null compressed value.\n\t\t\t */\n\t\t\tAssert(current_notnull_element + 1 >= 0);\n\t\t\toffsets[i + 1] = offsets[current_notnull_element + 1];\n\n\t\t\tif (simple8brle_bitmap_get_at(&nulls, i))\n\t\t\t{\n\t\t\t\tarrow_set_row_validity(validity_bitmap, i, false);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tAssert(current_notnull_element >= 0);\n\t\t\t\tcurrent_notnull_element--;\n\t\t\t}\n\t\t}\n\n\t\tAssert(current_notnull_element == -1);\n\t}\n\n\tArrowArray *result =\n\t\tMemoryContextAllocZero(dest_mctx, sizeof(ArrowArray) + (sizeof(void *) * 3));\n\tconst void **buffers = (const void **) &result[1];\n\tbuffers[0] = validity_bitmap;\n\tbuffers[1] = offsets;\n\tbuffers[2] = arrow_bodies;\n\tresult->n_buffers = 3;\n\tresult->buffers = buffers;\n\tresult->length = n_total;\n\tresult->null_count = n_total - n_notnull;\n\treturn result;\n}\n\nDecompressResult\narray_decompression_iterator_try_next_reverse(DecompressionIterator *base_iter)\n{\n\tSimple8bRleDecompressResult datum_size;\n\tArrayDecompressionIterator *iter;\n\tDatum val;\n\tconst char *start_pointer;\n\n\tAssert(base_iter->compression_algorithm == COMPRESSION_ALGORITHM_ARRAY && !base_iter->forward);\n\titer = (ArrayDecompressionIterator *) base_iter;\n\n\tif (iter->has_nulls)\n\t{\n\t\tSimple8bRleDecompressResult null =\n\t\t\tsimple8brle_decompression_iterator_try_next_reverse(&iter->nulls);\n\t\tif (null.is_done)\n\t\t\treturn (DecompressResult){\n\t\t\t\t.is_done = true,\n\t\t\t};\n\n\t\tif ((null.val & 1) != 0)\n\t\t{\n\t\t\treturn (DecompressResult){\n\t\t\t\t.is_null = true,\n\t\t\t};\n\t\t}\n\t}\n\n\tdatum_size = simple8brle_decompression_iterator_try_next_reverse(&iter->sizes);\n\tif (datum_size.is_done)\n\t\treturn (DecompressResult){\n\t\t\t.is_done = true,\n\t\t};\n\n\tAssert((int64) iter->data_offset - (int64) datum_size.val >= 0);\n\n\titer->data_offset -= datum_size.val;\n\tstart_pointer = iter->data + iter->data_offset;\n\tval = bytes_to_datum_and_advance(iter->deserializer, &start_pointer);\n\n\treturn (DecompressResult){\n\t\t.val = val,\n\t};\n}\n\n/*********************\n ***  send / recv  ***\n *********************/\n\nArrayCompressorSerializationInfo *\narray_compressed_data_recv(StringInfo buffer, Oid element_type)\n{\n\tArrayCompressor *compressor = array_compressor_alloc(element_type);\n\tSimple8bRleDecompressionIterator nulls;\n\tuint8 has_nulls;\n\tDatumDeserializer *deser = create_datum_deserializer(element_type);\n\tbool use_binary_recv;\n\tuint32 num_elements;\n\tuint32 i;\n\n\thas_nulls = pq_getmsgbyte(buffer) != 0;\n\tif (has_nulls)\n\t\tsimple8brle_decompression_iterator_init_forward(&nulls,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsimple8brle_serialized_recv(buffer));\n\n\tuse_binary_recv = pq_getmsgbyte(buffer) != 0;\n\n\t/* This is actually the number of not-null elements */\n\tnum_elements = pq_getmsgint32(buffer);\n\n\t/* if there are nulls, use that count instead */\n\tif (has_nulls)\n\t\tnum_elements = nulls.num_elements;\n\n\tfor (i = 0; i < num_elements; i++)\n\t{\n\t\tDatum val;\n\t\tif (has_nulls)\n\t\t{\n\t\t\tSimple8bRleDecompressResult null =\n\t\t\t\tsimple8brle_decompression_iterator_try_next_forward(&nulls);\n\t\t\tAssert(!null.is_done);\n\t\t\tif (null.val)\n\t\t\t{\n\t\t\t\tarray_compressor_append_null(compressor);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tval = binary_string_to_datum(deser,\n\t\t\t\t\t\t\t\t\t use_binary_recv ? BINARY_ENCODING : TEXT_ENCODING,\n\t\t\t\t\t\t\t\t\t buffer);\n\n\t\tarray_compressor_append(compressor, val);\n\t}\n\n\treturn array_compressor_get_serialization_info(compressor);\n}\n\nvoid\narray_compressed_data_send(StringInfo buffer, const char *_serialized_data, Size _data_size,\n\t\t\t\t\t\t   Oid element_type, bool has_nulls)\n{\n\tDecompressResult datum;\n\tDatumSerializer *serializer = create_datum_serializer(element_type);\n\tBinaryStringEncoding encoding = datum_serializer_binary_string_encoding(serializer);\n\n\tStringInfoData si = { .data = (char *) _serialized_data, .len = _data_size };\n\tArrayCompressedData array_compressed_data =\n\t\tarray_compressed_data_from_bytes(&si, element_type, has_nulls);\n\n\tsi.cursor = 0;\n\tDecompressionIterator *data_iter =\n\t\tarray_decompression_iterator_alloc_forward(&si, element_type, has_nulls);\n\n\tpq_sendbyte(buffer, array_compressed_data.nulls != NULL);\n\tif (array_compressed_data.nulls != NULL)\n\t\tsimple8brle_serialized_send(buffer, array_compressed_data.nulls);\n\n\tpq_sendbyte(buffer, encoding == BINARY_ENCODING);\n\n\t/*\n\t * we do not send data.sizes because the sizes need not be the same once\n\t * deserialized, and we will need to recalculate them on recv. We do need\n\t * to send the number of elements, which is always the same as the number\n\t * of sizes.\n\t */\n\tpq_sendint32(buffer, array_compressed_data.sizes->num_elements);\n\tfor (datum = array_decompression_iterator_try_next_forward(data_iter); !datum.is_done;\n\t\t datum = array_decompression_iterator_try_next_forward(data_iter))\n\t{\n\t\tif (datum.is_null)\n\t\t\tcontinue;\n\n\t\tdatum_append_to_binary_string(serializer, encoding, buffer, datum.val);\n\t}\n}\n\n/********************\n *** SQL Bindings ***\n ********************/\n\nDatum\narray_compressed_recv(StringInfo buffer)\n{\n\tuint8 has_nulls;\n\tOid element_type;\n\n\thas_nulls = pq_getmsgbyte(buffer);\n\tCheckCompressedData(has_nulls == 0 || has_nulls == 1);\n\n\telement_type = binary_string_get_type(buffer);\n\n\tArrayCompressorSerializationInfo *info = array_compressed_data_recv(buffer, element_type);\n\n\tCheckCompressedData(info->sizes != NULL);\n\tCheckCompressedData(has_nulls == (info->nulls != NULL));\n\n\tPG_RETURN_POINTER(array_compressed_from_serialization_info(info, element_type));\n}\n\nvoid\narray_compressed_send(CompressedDataHeader *header, StringInfo buffer)\n{\n\tconst char *compressed_data = (char *) header;\n\tuint32 data_size;\n\tArrayCompressed *compressed_array_header;\n\n\tAssert(header->compression_algorithm == COMPRESSION_ALGORITHM_ARRAY);\n\tcompressed_array_header = (ArrayCompressed *) header;\n\n\tcompressed_data += sizeof(*compressed_array_header);\n\n\tdata_size = VARSIZE(compressed_array_header);\n\tdata_size -= sizeof(*compressed_array_header);\n\n\tpq_sendbyte(buffer, compressed_array_header->has_nulls == true);\n\n\ttype_append_to_binary_string(compressed_array_header->element_type, buffer);\n\n\tarray_compressed_data_send(buffer,\n\t\t\t\t\t\t\t   compressed_data,\n\t\t\t\t\t\t\t   data_size,\n\t\t\t\t\t\t\t   compressed_array_header->element_type,\n\t\t\t\t\t\t\t   compressed_array_header->has_nulls);\n}\n\nextern Datum\ntsl_array_compressor_append(PG_FUNCTION_ARGS)\n{\n\tArrayCompressor *compressor =\n\t\t(ArrayCompressor *) (PG_ARGISNULL(0) ? NULL : PG_GETARG_POINTER(0));\n\tMemoryContext agg_context;\n\tMemoryContext old_context;\n\n\tif (!AggCheckCallContext(fcinfo, &agg_context))\n\t{\n\t\t/* cannot be called directly because of internal-type argument */\n\t\telog(ERROR, \"tsl_array_compressor_append called in non-aggregate context\");\n\t}\n\n\told_context = MemoryContextSwitchTo(agg_context);\n\n\tif (compressor == NULL)\n\t{\n\t\tOid type_to_compress = get_fn_expr_argtype(fcinfo->flinfo, 1);\n\t\tcompressor = array_compressor_alloc(type_to_compress);\n\t}\n\tif (PG_ARGISNULL(1))\n\t\tarray_compressor_append_null(compressor);\n\telse\n\t\tarray_compressor_append(compressor, PG_GETARG_DATUM(1));\n\n\tMemoryContextSwitchTo(old_context);\n\tPG_RETURN_POINTER(compressor);\n}\n\nextern Datum\ntsl_array_compressor_finish(PG_FUNCTION_ARGS)\n{\n\tArrayCompressor *compressor =\n\t\t(ArrayCompressor *) (PG_ARGISNULL(0) ? NULL : PG_GETARG_POINTER(0));\n\tvoid *compressed;\n\tif (compressor == NULL)\n\t\tPG_RETURN_NULL();\n\n\tcompressed = array_compressor_finish(compressor);\n\tif (compressed == NULL)\n\t\tPG_RETURN_NULL();\n\n\tPG_RETURN_POINTER(compressed);\n}\n"
  },
  {
    "path": "tsl/src/compression/algorithms/array.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n/*\n * The `array` compression method can store any type of data. It simply puts it into an\n * array-like structure and does not compress it. TOAST-based compression should be applied on top.\n *\n * Array compression is are also used as a building block for dictionary compression.\n */\n\n#include <postgres.h>\n#include <fmgr.h>\n#include <utils/memutils.h>\n\n#include \"compression/compression.h\"\n\n#define MAX_ARRAY_COMPRESSOR_SIZE_BYTES MaxAllocSize\n\ntypedef struct StringInfoData StringInfoData;\ntypedef StringInfoData *StringInfo;\n\ntypedef struct ArrayCompressor ArrayCompressor;\ntypedef struct ArrayCompressed ArrayCompressed;\ntypedef struct ArrayDecompressionIterator ArrayDecompressionIterator;\n\nextern const Compressor array_compressor;\n\nextern bool array_compressed_has_nulls(const CompressedDataHeader *header);\nextern Compressor *array_compressor_for_type(Oid element_type);\nextern ArrayCompressor *array_compressor_alloc(Oid type_to_compress);\nextern void array_compressor_append_null(ArrayCompressor *compressor);\nextern void array_compressor_append(ArrayCompressor *compressor, Datum val);\nextern void *array_compressor_finish(ArrayCompressor *compressor);\n\nextern ArrayDecompressionIterator *array_decompression_iterator_alloc(void);\nextern DecompressionIterator *\ntsl_array_decompression_iterator_from_datum_forward(Datum compressed_array, Oid element_type);\nextern DecompressResult array_decompression_iterator_try_next_forward(DecompressionIterator *iter);\n\nextern DecompressionIterator *\ntsl_array_decompression_iterator_from_datum_reverse(Datum compressed_array, Oid element_type);\nextern DecompressResult array_decompression_iterator_try_next_reverse(DecompressionIterator *iter);\n\n/* API for using this as an embedded data structure */\ntypedef struct ArrayCompressorSerializationInfo ArrayCompressorSerializationInfo;\nextern ArrayCompressorSerializationInfo *\narray_compressor_get_serialization_info(ArrayCompressor *compressor);\nSize array_compression_serialization_size(ArrayCompressorSerializationInfo *info);\nuint32 array_compression_serialization_num_elements(ArrayCompressorSerializationInfo *info);\nextern char *bytes_serialize_array_compressor_and_advance(char *dst, Size dst_size,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  ArrayCompressorSerializationInfo *info);\nextern DecompressionIterator *array_decompression_iterator_alloc_forward(StringInfo serialized_data,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t Oid element_type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t bool has_nulls);\n\nextern ArrayCompressorSerializationInfo *array_compressed_data_recv(StringInfo buffer,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tOid element_type);\nextern void array_compressed_data_send(StringInfo buffer, const char *serialized_data,\n\t\t\t\t\t\t\t\t\t   Size data_size, Oid element_type, bool has_nulls);\n\nextern Datum array_compressed_recv(StringInfo buffer);\nextern void array_compressed_send(CompressedDataHeader *header, StringInfo buffer);\n\nextern Datum tsl_array_compressor_append(PG_FUNCTION_ARGS);\nextern Datum tsl_array_compressor_finish(PG_FUNCTION_ARGS);\n\n/* Pass through to the specialized functions below for BOOL and TEXT */\nArrowArray *tsl_array_decompress_all(Datum compressed_array, Oid element_type,\n\t\t\t\t\t\t\t\t\t MemoryContext dest_mctx);\n\nArrowArray *text_array_decompress_all_serialized_no_header(StringInfo si, bool has_nulls,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   MemoryContext dest_mctx);\n\n#define ARRAY_ALGORITHM_DEFINITION                                                                 \\\n\t{                                                                                              \\\n\t\t.iterator_init_forward = tsl_array_decompression_iterator_from_datum_forward,              \\\n\t\t.iterator_init_reverse = tsl_array_decompression_iterator_from_datum_reverse,              \\\n\t\t.compressed_data_send = array_compressed_send,                                             \\\n\t\t.compressed_data_recv = array_compressed_recv,                                             \\\n\t\t.compressor_for_type = array_compressor_for_type,                                          \\\n\t\t.compressed_data_storage = TOAST_STORAGE_EXTENDED,                                         \\\n\t\t.decompress_all = tsl_array_decompress_all,                                                \\\n\t}\n"
  },
  {
    "path": "tsl/src/compression/algorithms/bool_compress.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include \"bool_compress.h\"\n#include \"compression/arrow_c_data_interface.h\"\n#include \"compression/compression.h\"\n#include \"guc.h\"\n#include \"simple8b_rle.h\"\n#include \"simple8b_rle_bitarray.h\"\n#include \"simple8b_rle_bitmap.h\"\n\ntypedef struct BoolCompressed\n{\n\tCompressedDataHeaderFields;\n\tuint8 has_nulls;  /* 1 if this has a NULLs bitmap after the values, 0 otherwise */\n\tuint8 padding[2]; /* padding added because of Simple8bRleSerialized format */\n\tchar values[FLEXIBLE_ARRAY_MEMBER];\n} BoolCompressed;\n\ntypedef struct BoolDecompressionIterator\n{\n\tDecompressionIterator base;\n\tSimple8bRleBitmap values;\n\tSimple8bRleBitmap validity_bitmap;\n\tint32 position;\n} BoolDecompressionIterator;\n\ntypedef struct BoolCompressor\n{\n\tSimple8bRleCompressor values;\n\tSimple8bRleCompressor validity_bitmap;\n\tbool has_nulls;\n\tbool last_value;\n\tuint32 num_nulls;\n} BoolCompressor;\n\ntypedef struct ExtendedCompressor\n{\n\tCompressor base;\n\tBoolCompressor *internal;\n} ExtendedCompressor;\n\n/*\n * Local helpers\n */\nstatic void bool_compressor_append_bool(Compressor *compressor, Datum val);\n\nstatic void bool_compressor_append_null_value(Compressor *compressor);\n\nstatic void *bool_compressor_finish_and_reset(Compressor *compressor);\n\nconst Compressor bool_compressor_initializer = {\n\t.append_val = bool_compressor_append_bool,\n\t.append_null = bool_compressor_append_null_value,\n\t.is_full = NULL,\n\t.finish = bool_compressor_finish_and_reset,\n};\n\nstatic BoolCompressed *bool_compressed_from_parts(Simple8bRleSerialized *values,\n\t\t\t\t\t\t\t\t\t\t\t\t  Simple8bRleSerialized *validity_bitmap);\n\nstatic void decompression_iterator_init(BoolDecompressionIterator *iter, void *compressed,\n\t\t\t\t\t\t\t\t\t\tOid element_type, bool forward);\n\n/*\n * Compressor framework functions and definitions for the bool_compress algorithm.\n */\n\nextern BoolCompressor *\nbool_compressor_alloc(void)\n{\n\tBoolCompressor *compressor = palloc0(sizeof(*compressor));\n\tsimple8brle_compressor_init(&compressor->values);\n\tsimple8brle_compressor_init(&compressor->validity_bitmap);\n\treturn compressor;\n}\n\nextern void\nbool_compressor_append_null(BoolCompressor *compressor)\n{\n\t/*\n\t * We use parallel bitmaps of same size for validity and values, to support\n\t * zero-copy decompression into ArrowArray. When an element is null,\n\t * the particular value that goes into the values bitmap doesn't matter, so\n\t * we add the last seen value, not to break the RLE sequences.\n\t */\n\tcompressor->has_nulls = true;\n\tsimple8brle_compressor_append(&compressor->values, compressor->last_value);\n\tsimple8brle_compressor_append(&compressor->validity_bitmap, 0);\n\tcompressor->num_nulls++;\n}\n\nextern void\nbool_compressor_append_value(BoolCompressor *compressor, bool next_val)\n{\n\tcompressor->last_value = next_val;\n\tsimple8brle_compressor_append(&compressor->values, next_val);\n\tsimple8brle_compressor_append(&compressor->validity_bitmap, 1);\n}\n\nextern void *\nbool_compressor_finish(BoolCompressor *compressor)\n{\n\tif (compressor == NULL)\n\t\treturn NULL;\n\n\tSimple8bRleSerialized *values = simple8brle_compressor_finish(&compressor->values);\n\tif (values == NULL)\n\t\treturn NULL;\n\n\tif (compressor->num_nulls == compressor->values.num_elements)\n\t\treturn NULL;\n\n\tSimple8bRleSerialized *validity_bitmap =\n\t\tsimple8brle_compressor_finish(&compressor->validity_bitmap);\n\tBoolCompressed *compressed;\n\n\tcompressed = bool_compressed_from_parts(values, compressor->has_nulls ? validity_bitmap : NULL);\n\t/* When only nulls are present, we can return NULL */\n\tAssert(compressed == NULL || compressed->compression_algorithm == COMPRESSION_ALGORITHM_BOOL);\n\treturn compressed;\n}\n\nextern bool\nbool_compressed_has_nulls(const CompressedDataHeader *header)\n{\n\tconst BoolCompressed *ddc = (const BoolCompressed *) header;\n\treturn ddc->has_nulls;\n}\n\nextern DecompressResult\nbool_decompression_iterator_try_next_forward(DecompressionIterator *iter)\n{\n\tAssert(iter->compression_algorithm == COMPRESSION_ALGORITHM_BOOL && iter->forward);\n\tAssert(iter->element_type == BOOLOID);\n\n\tBoolDecompressionIterator *bool_iter = (BoolDecompressionIterator *) iter;\n\n\tif (bool_iter->position >= bool_iter->values.num_elements)\n\t\treturn (DecompressResult){\n\t\t\t.is_done = true,\n\t\t};\n\n\t/* check nulls */\n\tif (bool_iter->validity_bitmap.num_elements > 0)\n\t{\n\t\tbool is_null = !simple8brle_bitmap_get_at(&bool_iter->validity_bitmap, bool_iter->position);\n\t\tif (is_null)\n\t\t{\n\t\t\tbool_iter->position++;\n\t\t\treturn (DecompressResult){\n\t\t\t\t.is_null = true,\n\t\t\t};\n\t\t}\n\t}\n\n\tbool val = simple8brle_bitmap_get_at(&bool_iter->values, bool_iter->position);\n\tbool_iter->position++;\n\n\treturn (DecompressResult){\n\t\t.val = BoolGetDatum(val),\n\t};\n}\n\nextern DecompressionIterator *\nbool_decompression_iterator_from_datum_forward(Datum bool_compressed, Oid element_type)\n{\n\tBoolDecompressionIterator *iterator = palloc(sizeof(*iterator));\n\tCheckCompressedData(DatumGetPointer(bool_compressed) != NULL);\n\tdecompression_iterator_init(iterator,\n\t\t\t\t\t\t\t\t(void *) PG_DETOAST_DATUM(bool_compressed),\n\t\t\t\t\t\t\t\telement_type,\n\t\t\t\t\t\t\t\ttrue);\n\treturn &iterator->base;\n}\n\nextern DecompressResult\nbool_decompression_iterator_try_next_reverse(DecompressionIterator *iter)\n{\n\tAssert(iter->compression_algorithm == COMPRESSION_ALGORITHM_BOOL && !iter->forward);\n\tAssert(iter->element_type == BOOLOID);\n\n\tBoolDecompressionIterator *bool_iter = (BoolDecompressionIterator *) iter;\n\n\tif (bool_iter->position < 0)\n\t\treturn (DecompressResult){\n\t\t\t.is_done = true,\n\t\t};\n\n\t/* check nulls */\n\tif (bool_iter->validity_bitmap.num_elements > 0)\n\t{\n\t\tbool is_null = !simple8brle_bitmap_get_at(&bool_iter->validity_bitmap, bool_iter->position);\n\t\tif (is_null)\n\t\t{\n\t\t\tbool_iter->position--;\n\t\t\treturn (DecompressResult){\n\t\t\t\t.is_null = true,\n\t\t\t};\n\t\t}\n\t}\n\n\tbool val = simple8brle_bitmap_get_at(&bool_iter->values, bool_iter->position);\n\tbool_iter->position--;\n\n\treturn (DecompressResult){\n\t\t.val = BoolGetDatum(val),\n\t};\n}\n\nextern DecompressionIterator *\nbool_decompression_iterator_from_datum_reverse(Datum bool_compressed, Oid element_type)\n{\n\tBoolDecompressionIterator *iterator = palloc(sizeof(*iterator));\n\tCheckCompressedData(DatumGetPointer(bool_compressed) != NULL);\n\tdecompression_iterator_init(iterator,\n\t\t\t\t\t\t\t\t(void *) PG_DETOAST_DATUM(bool_compressed),\n\t\t\t\t\t\t\t\telement_type,\n\t\t\t\t\t\t\t\tfalse);\n\treturn &iterator->base;\n}\n\nextern void\nbool_compressed_send(CompressedDataHeader *header, StringInfo buffer)\n{\n\tconst BoolCompressed *data = (BoolCompressed *) header;\n\tAssert(header->compression_algorithm == COMPRESSION_ALGORITHM_BOOL);\n\tpq_sendbyte(buffer, data->has_nulls);\n\tsimple8brle_serialized_send(buffer, (Simple8bRleSerialized *) data->values);\n\tif (data->has_nulls)\n\t{\n\t\tSimple8bRleSerialized *validity_bitmap =\n\t\t\t(Simple8bRleSerialized *) (((char *) data->values) +\n\t\t\t\t\t\t\t\t\t   simple8brle_serialized_total_size(\n\t\t\t\t\t\t\t\t\t\t   (Simple8bRleSerialized *) data->values));\n\t\tsimple8brle_serialized_send(buffer, validity_bitmap);\n\t}\n}\n\nextern Datum\nbool_compressed_recv(StringInfo buffer)\n{\n\tuint8 has_nulls;\n\tSimple8bRleSerialized *values;\n\tSimple8bRleSerialized *validity_bitmap = NULL;\n\tBoolCompressed *compressed;\n\n\thas_nulls = pq_getmsgbyte(buffer);\n\tCheckCompressedData(has_nulls == 0 || has_nulls == 1);\n\n\tvalues = simple8brle_serialized_recv(buffer);\n\tif (has_nulls)\n\t\tvalidity_bitmap = simple8brle_serialized_recv(buffer);\n\n\tcompressed = bool_compressed_from_parts(values, validity_bitmap);\n\n\tPG_RETURN_POINTER(compressed);\n}\n\nextern Compressor *\nbool_compressor_for_type(Oid element_type)\n{\n\tExtendedCompressor *compressor = palloc(sizeof(*compressor));\n\tswitch (element_type)\n\t{\n\t\tcase BOOLOID:\n\t\t\t*compressor = (ExtendedCompressor){ .base = bool_compressor_initializer };\n\t\t\treturn &compressor->base;\n\t\tdefault:\n\t\t\telog(ERROR, \"invalid type for bool compressor \\\"%s\\\"\", format_type_be(element_type));\n\t}\n\n\tpg_unreachable();\n}\n\n/*\n * Cross-module functions for the bool_compress algorithm.\n */\nextern Datum\ntsl_bool_compressor_append(PG_FUNCTION_ARGS)\n{\n\tMemoryContext old_context;\n\tMemoryContext agg_context;\n\tBoolCompressor *compressor = (BoolCompressor *) (PG_ARGISNULL(0) ? NULL : PG_GETARG_POINTER(0));\n\n\tif (!AggCheckCallContext(fcinfo, &agg_context))\n\t{\n\t\t/* cannot be called directly because of internal-type argument */\n\t\telog(ERROR, \"tsl_bool_compressor_append called in non-aggregate context\");\n\t}\n\n\told_context = MemoryContextSwitchTo(agg_context);\n\n\tif (compressor == NULL)\n\t{\n\t\tcompressor = bool_compressor_alloc();\n\t\tif (PG_NARGS() > 2)\n\t\t\telog(ERROR, \"append expects two arguments\");\n\t}\n\n\tif (PG_ARGISNULL(1))\n\t\tbool_compressor_append_null(compressor);\n\telse\n\t{\n\t\tbool next_val = PG_GETARG_BOOL(1);\n\t\tbool_compressor_append_value(compressor, next_val);\n\t}\n\n\tMemoryContextSwitchTo(old_context);\n\tPG_RETURN_POINTER(compressor);\n}\n\nextern Datum\ntsl_bool_compressor_finish(PG_FUNCTION_ARGS)\n{\n\tBoolCompressor *compressor = PG_ARGISNULL(0) ? NULL : (BoolCompressor *) PG_GETARG_POINTER(0);\n\tvoid *compressed;\n\tif (compressor == NULL)\n\t\tPG_RETURN_NULL();\n\n\tcompressed = bool_compressor_finish(compressor);\n\tif (compressed == NULL)\n\t\tPG_RETURN_NULL();\n\tPG_RETURN_POINTER(compressed);\n}\n\nextern ArrowArray *\nbool_decompress_all(Datum compressed, Oid element_type, MemoryContext dest_mctx)\n{\n\tMemoryContext old_context;\n\tSimple8bRleBitArray value_bits;\n\tSimple8bRleBitArray validity_bits;\n\n\tSimple8bRleSerialized *serialized_values = NULL;\n\tSimple8bRleSerialized *serialized_validity_bitmap = NULL;\n\n\tArrowArray *result = NULL;\n\tuint64 *validity_bitmap = NULL;\n\tuint64 *decompressed_values = NULL;\n\n\tCheckCompressedData(DatumGetPointer(compressed) != NULL);\n\n\tvoid *detoasted = PG_DETOAST_DATUM(compressed);\n\tStringInfoData si = { .data = detoasted, .len = VARSIZE(compressed) };\n\tBoolCompressed *header = consumeCompressedData(&si, sizeof(BoolCompressed));\n\n\tAssert(header->has_nulls == 0 || header->has_nulls == 1);\n\tAssert(element_type == BOOLOID);\n\n\tserialized_values = bytes_deserialize_simple8b_and_advance(&si);\n\tconst bool has_nulls = header->has_nulls == 1;\n\n\tif (has_nulls)\n\t{\n\t\tserialized_validity_bitmap = bytes_deserialize_simple8b_and_advance(&si);\n\t}\n\n\t/* Decompress the values directly to bit arrays */\n\told_context = MemoryContextSwitchTo(dest_mctx);\n\tvalue_bits = simple8brle_bitarray_decompress(serialized_values, /* inverted*/ false);\n\tdecompressed_values = value_bits.data;\n\tvalidity_bits =\n\t\tsimple8brle_bitarray_decompress(serialized_validity_bitmap, /* inverted*/ false);\n\tvalidity_bitmap = validity_bits.data;\n\tMemoryContextSwitchTo(old_context);\n\n\tresult = MemoryContextAllocZero(dest_mctx, sizeof(ArrowArray) + sizeof(void *) * 2);\n\tconst void **buffers = (const void **) &result[1];\n\tbuffers[0] = validity_bitmap;\n\tbuffers[1] = decompressed_values;\n\tresult->n_buffers = 2;\n\tresult->buffers = buffers;\n\tresult->length = value_bits.num_elements;\n\tresult->null_count = has_nulls ? (result->length - validity_bits.num_ones) : 0;\n\treturn result;\n}\n\n/*\n * Local helpers\n */\nstatic void\nbool_compressor_append_bool(Compressor *compressor, Datum val)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tif (extended->internal == NULL)\n\t\textended->internal = bool_compressor_alloc();\n\n\tbool_compressor_append_value(extended->internal, DatumGetBool(val) ? true : false);\n}\n\nstatic void\nbool_compressor_append_null_value(Compressor *compressor)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tif (extended->internal == NULL)\n\t\textended->internal = bool_compressor_alloc();\n\n\tbool_compressor_append_null(extended->internal);\n}\n\nstatic void *\nbool_compressor_finish_and_reset(Compressor *compressor)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tvoid *compressed = NULL;\n\tif (extended != NULL && extended->internal != NULL)\n\t{\n\t\tcompressed = bool_compressor_finish(extended->internal);\n\t\tpfree(extended->internal);\n\t\textended->internal = NULL;\n\t}\n\treturn compressed;\n}\n\nstatic BoolCompressed *\nbool_compressed_from_parts(Simple8bRleSerialized *values, Simple8bRleSerialized *validity_bitmap)\n{\n\tuint32 validity_bitmap_size = 0;\n\tSize compressed_size;\n\tchar *compressed_data;\n\tBoolCompressed *compressed;\n\tuint32 num_values = values != NULL ? values->num_elements : 0;\n\tuint32 values_size = values != NULL ? simple8brle_serialized_total_size(values) : 0;\n\n\tif (num_values == 0)\n\t\treturn NULL;\n\n\tif (validity_bitmap != NULL)\n\t\tvalidity_bitmap_size = simple8brle_serialized_total_size(validity_bitmap);\n\n\tcompressed_size = sizeof(BoolCompressed) + values_size + validity_bitmap_size;\n\n\tif (!AllocSizeIsValid(compressed_size))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),\n\t\t\t\t errmsg(\"compressed size exceeds the maximum allowed (%d)\", (int) MaxAllocSize)));\n\n\tcompressed_data = palloc(compressed_size);\n\tcompressed = (BoolCompressed *) compressed_data;\n\tSET_VARSIZE(&compressed->vl_len_, compressed_size);\n\n\tcompressed->compression_algorithm = COMPRESSION_ALGORITHM_BOOL;\n\tcompressed->has_nulls = validity_bitmap_size != 0 ? 1 : 0;\n\n\tcompressed_data += sizeof(*compressed);\n\tcompressed_data = bytes_serialize_simple8b_and_advance(compressed_data, values_size, values);\n\n\tif (compressed->has_nulls == 1 && validity_bitmap != NULL)\n\t{\n\t\tCheckCompressedData(validity_bitmap->num_elements == num_values);\n\t\tbytes_serialize_simple8b_and_advance(compressed_data,\n\t\t\t\t\t\t\t\t\t\t\t validity_bitmap_size,\n\t\t\t\t\t\t\t\t\t\t\t validity_bitmap);\n\t}\n\n\treturn compressed;\n}\n\nstatic void\ndecompression_iterator_init(BoolDecompressionIterator *iter, void *compressed, Oid element_type,\n\t\t\t\t\t\t\tbool forward)\n{\n\tStringInfoData si = { .data = compressed, .len = VARSIZE(compressed) };\n\tBoolCompressed *header = consumeCompressedData(&si, sizeof(BoolCompressed));\n\tSimple8bRleSerialized *values = bytes_deserialize_simple8b_and_advance(&si);\n\n\tAssert(header->has_nulls == 0 || header->has_nulls == 1);\n\tAssert(element_type == BOOLOID);\n\n\tconst bool has_nulls = header->has_nulls == 1;\n\n\tCheckCompressedData(has_nulls == 0 || has_nulls == 1);\n\n\t*iter = (BoolDecompressionIterator){\n\t\t.base = { .compression_algorithm = COMPRESSION_ALGORITHM_BOOL,\n\t\t\t\t  .forward = forward,\n\t\t\t\t  .element_type = element_type,\n\t\t\t\t  .try_next = (forward ? bool_decompression_iterator_try_next_forward :\n\t\t\t\t\t\t\t\t\t\t bool_decompression_iterator_try_next_reverse) },\n\t\t.values = { 0 },\n\t\t.validity_bitmap = { 0 },\n\t\t.position = 0,\n\t};\n\n\titer->values = simple8brle_bitmap_decompress(values);\n\n\tif (has_nulls)\n\t{\n\t\tSimple8bRleSerialized *validity_bitmap = bytes_deserialize_simple8b_and_advance(&si);\n\t\titer->validity_bitmap = simple8brle_bitmap_decompress(validity_bitmap);\n\t\tCheckCompressedData(iter->validity_bitmap.num_elements == iter->values.num_elements);\n\t}\n\n\tif (!forward)\n\t{\n\t\titer->position = iter->values.num_elements - 1;\n\t}\n}\n"
  },
  {
    "path": "tsl/src/compression/algorithms/bool_compress.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n/*\n * bool_compress is used to encode boolean values using the simple8b_rle algorithm.\n *\n * The bool compressor differs from the other compressors in that it does store a value\n * even for nulls, which is the last value seen befere the null. With this the bool\n * compressor always creates a compressed block even for nulls only.\n *\n * The boolean compressor represents the boolean values in a batch with two parallel\n * bitmaps, value bitmap and validity bitmap, like in the Arrow representation.\n * These bitmaps are compressed with our common bit-packing algorithm.\n *\n * The validity bitmap stores a 0 for a null value and a 1 for a non-null value as\n * required by the Arrow specification. This is the opposite of what the other compression\n * algorithms do in their nulls bitmaps.\n */\n\n#include <postgres.h>\n#include <fmgr.h>\n#include <lib/stringinfo.h>\n\n#include \"compression/compression.h\"\n\ntypedef struct BoolCompressor BoolCompressor;\ntypedef struct BoolCompressed BoolCompressed;\ntypedef struct BoolDecompressionIterator BoolDecompressionIterator;\n\n/*\n * Compressor framework functions and definitions for the bool_compress algorithm.\n */\n\nextern BoolCompressor *bool_compressor_alloc(void);\nextern void bool_compressor_append_null(BoolCompressor *compressor);\nextern void bool_compressor_append_value(BoolCompressor *compressor, bool next_val);\nextern void *bool_compressor_finish(BoolCompressor *compressor);\nextern bool bool_compressed_has_nulls(const CompressedDataHeader *header);\n\nextern DecompressResult bool_decompression_iterator_try_next_forward(DecompressionIterator *iter);\n\nextern DecompressionIterator *bool_decompression_iterator_from_datum_forward(Datum bool_compressed,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t Oid element_type);\n\nextern DecompressResult bool_decompression_iterator_try_next_reverse(DecompressionIterator *iter);\n\nextern DecompressionIterator *bool_decompression_iterator_from_datum_reverse(Datum bool_compressed,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t Oid element_type);\n\nextern ArrowArray *bool_decompress_all(Datum compressed, Oid element_type, MemoryContext dest_mctx);\n\nextern void bool_compressed_send(CompressedDataHeader *header, StringInfo buffer);\n\nextern Datum bool_compressed_recv(StringInfo buf);\n\nextern Compressor *bool_compressor_for_type(Oid element_type);\n\n#define BOOL_COMPRESS_ALGORITHM_DEFINITION                                                         \\\n\t{                                                                                              \\\n\t\t.iterator_init_forward = bool_decompression_iterator_from_datum_forward,                   \\\n\t\t.iterator_init_reverse = bool_decompression_iterator_from_datum_reverse,                   \\\n\t\t.decompress_all = bool_decompress_all, .compressed_data_send = bool_compressed_send,       \\\n\t\t.compressed_data_recv = bool_compressed_recv,                                              \\\n\t\t.compressor_for_type = bool_compressor_for_type,                                           \\\n\t\t.compressed_data_storage = TOAST_STORAGE_EXTERNAL,                                         \\\n\t}\n\n/*\n * Cross-module functions for the bool_compress algorithm.\n */\n\nextern Datum tsl_bool_compressor_append(PG_FUNCTION_ARGS);\nextern Datum tsl_bool_compressor_finish(PG_FUNCTION_ARGS);\n"
  },
  {
    "path": "tsl/src/compression/algorithms/datum_serialize.c",
    "content": "\n/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/htup_details.h>\n#include <access/tupmacs.h>\n#include <catalog/namespace.h>\n#include <catalog/pg_type.h>\n#include <libpq/pqformat.h>\n#include <utils/builtins.h>\n#include <utils/datum.h>\n#include <utils/lsyscache.h>\n#include <utils/sortsupport.h>\n#include <utils/syscache.h>\n#include <utils/typcache.h>\n\n#include <compat/compat.h>\n#include \"datum_serialize.h\"\n#include \"src/utils.h\"\n#include <compression/compression.h>\n\ntypedef struct DatumSerializer\n{\n\tOid type_oid;\n\tbool type_by_val;\n\tint16 type_len;\n\tchar type_align;\n\tchar type_storage;\n\tOid type_send;\n\tOid type_out;\n\n\t/* lazy load */\n\tbool send_info_set;\n\tFmgrInfo send_flinfo;\n\tbool use_binary_send;\n} DatumSerializer;\n\nDatumSerializer *\ncreate_datum_serializer(Oid type_oid)\n{\n\tDatumSerializer *res = palloc(sizeof(*res));\n\t/* we use the syscache and not the type cache here b/c we need the\n\t * send/recv in/out functions that aren't in type cache */\n\tForm_pg_type type;\n\tHeapTuple tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));\n\tif (!HeapTupleIsValid(tup))\n\t\telog(ERROR, \"cache lookup failed for type %u\", type_oid);\n\ttype = (Form_pg_type) GETSTRUCT(tup);\n\n\t*res = (DatumSerializer){\n\t\t.type_oid = type_oid,\n\t\t.type_by_val = type->typbyval,\n\t\t.type_len = type->typlen,\n\t\t.type_align = type->typalign,\n\t\t.type_storage = type->typstorage,\n\t\t.type_send = type->typsend,\n\t\t.type_out = type->typoutput,\n\t\t.use_binary_send = OidIsValid(type->typsend),\n\t};\n\n\tReleaseSysCache(tup);\n\treturn res;\n}\n\nbool\ndatum_serializer_value_may_be_toasted(DatumSerializer *serializer)\n{\n\treturn serializer->type_len == -1;\n}\n\nstatic inline void\nload_send_fn(DatumSerializer *serializer)\n{\n\tif (serializer->send_info_set)\n\t\treturn;\n\n\tserializer->send_info_set = true;\n\n\tif (serializer->use_binary_send)\n\t\tfmgr_info(serializer->type_send, &serializer->send_flinfo);\n\telse\n\t\tfmgr_info(serializer->type_out, &serializer->send_flinfo);\n}\n\n#define TYPE_IS_PACKABLE(typlen, typstorage) ((typlen) == -1 && (typstorage) != 'p')\n\n/* Inspired by datum_compute_size in rangetypes.c */\nSize\ndatum_get_bytes_size(DatumSerializer *serializer, Size start_offset, Datum val)\n{\n\tSize data_length = start_offset;\n\n\tif (serializer->type_len == -1)\n\t{\n\t\t/* varlena */\n\t\tPointer ptr = DatumGetPointer(val);\n\n\t\tif (VARATT_IS_EXTERNAL(ptr))\n\t\t{\n\t\t\t/*\n\t\t\t * Throw error, because we should never get a toasted datum.\n\t\t\t * Caller should have detoasted it.\n\t\t\t */\n\t\t\telog(ERROR, \"datum should be detoasted before passed to datum_get_bytes_size\");\n\t\t}\n\t}\n\n\tif (TYPE_IS_PACKABLE(serializer->type_len, serializer->type_storage) &&\n\t\tVARATT_CAN_MAKE_SHORT(DatumGetPointer(val)))\n\t{\n\t\t/*\n\t\t * we're anticipating converting to a short varlena header, so adjust\n\t\t * length and don't count any alignment (the case where the Datum is already\n\t\t * in short format is handled by att_align_datum)\n\t\t */\n\t\tdata_length += VARATT_CONVERTED_SHORT_SIZE(DatumGetPointer(val));\n\t}\n\telse\n\t{\n\t\tdata_length =\n\t\t\tatt_align_datum(data_length, serializer->type_align, serializer->type_len, val);\n\t\tdata_length = att_addlength_datum(data_length, serializer->type_len, val);\n\t}\n\n\treturn data_length;\n}\n\nBinaryStringEncoding\ndatum_serializer_binary_string_encoding(DatumSerializer *serializer)\n{\n\treturn (serializer->use_binary_send ? BINARY_ENCODING : TEXT_ENCODING);\n}\n\nstatic void\ncheck_allowed_data_len(Size data_length, Size max_size)\n{\n\tif (max_size < data_length)\n\t\telog(ERROR, \"trying to serialize more data than was allocated\");\n}\n\nstatic inline char *\nalign_and_zero(char *ptr, char type_align, Size *max_size)\n{\n\tchar *new_pos = (char *) att_align_nominal(ptr, type_align);\n\tif (new_pos != ptr)\n\t{\n\t\tSize padding = new_pos - ptr;\n\t\tcheck_allowed_data_len(padding, *max_size);\n\t\tmemset(ptr, 0, padding);\n\t\t*max_size = *max_size - padding;\n\t}\n\treturn new_pos;\n}\n\n/* Inspired by datum_write in rangetypes.c. This reduces the max_size by the data length before\n * exiting */\nchar *\ndatum_to_bytes_and_advance(DatumSerializer *serializer, char *start, Size *max_size, Datum datum)\n{\n\tSize data_length;\n\n\tif (serializer->type_by_val)\n\t{\n\t\t/* pass-by-value */\n\t\tstart = align_and_zero(start, serializer->type_align, max_size);\n\t\tdata_length = serializer->type_len;\n\t\tcheck_allowed_data_len(data_length, *max_size);\n\n\t\t/* Data length should be set to something sensible, otherwise an error\n\t\t * will be raised inside store_att_byval, so we assert here to get a\n\t\t * stack. */\n\t\tAssert(data_length > 0 && data_length <= 8);\n\t\tstore_att_byval(start, datum, data_length);\n\t}\n\telse if (serializer->type_len == -1)\n\t{\n\t\t/* varlena */\n\t\tPointer val = DatumGetPointer(datum);\n\n\t\tif (VARATT_IS_EXTERNAL(val))\n\t\t{\n\t\t\t/*\n\t\t\t * Throw error, because we should never get a toast datum.\n\t\t\t *  Caller should have detoasted it.\n\t\t\t */\n\t\t\telog(ERROR, \"datum should be detoasted before passed to datum_to_bytes_and_advance\");\n\t\t\tdata_length = 0; /* keep compiler quiet */\n\t\t}\n\t\telse if (VARATT_IS_SHORT(val))\n\t\t{\n\t\t\t/* no alignment for short varlenas */\n\t\t\tdata_length = VARSIZE_SHORT(val);\n\t\t\tcheck_allowed_data_len(data_length, *max_size);\n\t\t\tmemcpy(start, val, data_length);\n\t\t}\n\t\telse if (TYPE_IS_PACKABLE(serializer->type_len, serializer->type_storage) &&\n\t\t\t\t VARATT_CAN_MAKE_SHORT(val))\n\t\t{\n\t\t\t/* convert to short varlena -- no alignment */\n\t\t\tdata_length = VARATT_CONVERTED_SHORT_SIZE(val);\n\t\t\tcheck_allowed_data_len(data_length, *max_size);\n\t\t\tSET_VARSIZE_SHORT(start, data_length);\n\t\t\tmemcpy(start + 1, VARDATA(val), data_length - 1);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* full 4-byte header varlena */\n\t\t\tstart = align_and_zero(start, serializer->type_align, max_size);\n\t\t\tdata_length = VARSIZE(val);\n\t\t\tcheck_allowed_data_len(data_length, *max_size);\n\t\t\tmemcpy(start, val, data_length);\n\t\t}\n\t}\n\telse if (serializer->type_len == -2)\n\t{\n\t\t/* cstring ... never needs alignment */\n\t\tAssert(serializer->type_align == 'c');\n\t\tdata_length = strlen(DatumGetCString(datum)) + 1;\n\t\tcheck_allowed_data_len(data_length, *max_size);\n\t\tmemcpy(start, DatumGetPointer(datum), data_length);\n\t}\n\telse\n\t{\n\t\t/* fixed-length pass-by-reference */\n\t\tstart = align_and_zero(start, serializer->type_align, max_size);\n\t\tAssert(serializer->type_len > 0);\n\t\tdata_length = serializer->type_len;\n\t\tcheck_allowed_data_len(data_length, *max_size);\n\t\tmemcpy(start, DatumGetPointer(datum), data_length);\n\t}\n\n\tstart += data_length;\n\t*max_size = *max_size - data_length;\n\n\treturn start;\n}\n\ntypedef struct DatumDeserializer\n{\n\tbool type_by_val;\n\tint16 type_len;\n\tchar type_align;\n\tchar type_storage;\n\n\tOid type_recv;\n\n\tOid type_in;\n\tOid type_io_param;\n\tint32 type_mod;\n\t/* lazy load */\n\tbool recv_info_set;\n\tFmgrInfo recv_flinfo;\n\tbool use_binary_recv;\n} DatumDeserializer;\n\nDatumDeserializer *\ncreate_datum_deserializer(Oid type_oid)\n{\n\tDatumDeserializer *res = palloc(sizeof(*res));\n\t/* we use the syscache and not the type cache here b/c we need the\n\t * send/recv in/out functions that aren't in type cache */\n\tForm_pg_type type;\n\tHeapTuple tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));\n\tif (!HeapTupleIsValid(tup))\n\t\telog(ERROR, \"cache lookup failed for type %u\", type_oid);\n\ttype = (Form_pg_type) GETSTRUCT(tup);\n\n\t*res = (DatumDeserializer){\n\t\t.type_by_val = type->typbyval,\n\t\t.type_len = type->typlen,\n\t\t.type_align = type->typalign,\n\t\t.type_storage = type->typstorage,\n\t\t.type_recv = type->typreceive,\n\t\t.type_in = type->typinput,\n\t\t.type_io_param = getTypeIOParam(tup),\n\t\t.type_mod = type->typtypmod,\n\t};\n\n\tReleaseSysCache(tup);\n\treturn res;\n}\n\nstatic inline void\nload_recv_fn(DatumDeserializer *des, bool use_binary)\n{\n\tif (des->recv_info_set && des->use_binary_recv == use_binary)\n\t\treturn;\n\n\tdes->recv_info_set = true;\n\tdes->use_binary_recv = use_binary;\n\n\tif (des->use_binary_recv)\n\t\tfmgr_info(des->type_recv, &des->recv_flinfo);\n\telse\n\t\tfmgr_info(des->type_in, &des->recv_flinfo);\n}\n\n/* Loosely based on `range_deserialize` in rangetypes.c */\nDatum\nbytes_to_datum_and_advance(DatumDeserializer *deserializer, const char **ptr)\n{\n\tDatum res;\n\n\t/* att_align_pointer can handle the case where an unaligned short-varlen follows any other\n\t * varlen by detecting padding. padding bytes _must always_ be set to 0, while the first byte of\n\t * a varlen header is _never_ 0. This means that if the next byte is non-zero, it must be the\n\t * start of a short-varlen, otherwise we need to align the pointer.\n\t */\n\n\t*ptr =\n\t\t(Pointer) att_align_pointer(*ptr, deserializer->type_align, deserializer->type_len, *ptr);\n\tif (deserializer->type_len == -1)\n\t{\n\t\t/*\n\t\t * Check for potentially corrupt varlena headers since we're reading them\n\t\t * directly from compressed data. We can only have a plain datum\n\t\t * with 1-byte or 4-byte header here, no TOAST or compressed data.\n\t\t */\n\t\tCheckCompressedData(VARATT_IS_4B_U(*ptr) || (VARATT_IS_1B(*ptr) && !VARATT_IS_1B_E(*ptr)));\n\n\t\t/*\n\t\t * Full varsize must be larger or equal than the header size so that the\n\t\t * calculation of size without header doesn't overflow.\n\t\t */\n\t\tCheckCompressedData((VARATT_IS_1B(*ptr) && VARSIZE_1B(*ptr) >= VARHDRSZ_SHORT) ||\n\t\t\t\t\t\t\t(VARSIZE_4B(*ptr) > VARHDRSZ));\n\t}\n\tres = ts_fetch_att(*ptr, deserializer->type_by_val, deserializer->type_len);\n\t*ptr = att_addlength_pointer(*ptr, deserializer->type_len, *ptr);\n\treturn res;\n}\n\nvoid\ntype_append_to_binary_string(Oid type_oid, StringInfo buffer)\n{\n\tForm_pg_type type_tuple;\n\tHeapTuple tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));\n\tchar *namespace_name;\n\tif (!HeapTupleIsValid(tup))\n\t\telog(ERROR, \"cache lookup failed for type %u\", type_oid);\n\n\ttype_tuple = (Form_pg_type) GETSTRUCT(tup);\n\n\tnamespace_name = get_namespace_name(type_tuple->typnamespace);\n\n\tpq_sendstring(buffer, namespace_name);\n\tpq_sendstring(buffer, NameStr(type_tuple->typname));\n\n\tReleaseSysCache(tup);\n}\n\nOid\nbinary_string_get_type(StringInfo buffer)\n{\n\tconst char *element_type_namespace = pq_getmsgstring(buffer);\n\tconst char *element_type_name = pq_getmsgstring(buffer);\n\tOid namespace_oid;\n\tOid type_oid;\n\n\tnamespace_oid = LookupExplicitNamespace(element_type_namespace, false);\n\n\ttype_oid = GetSysCacheOid2(TYPENAMENSP,\n\t\t\t\t\t\t\t   Anum_pg_type_oid,\n\t\t\t\t\t\t\t   PointerGetDatum(element_type_name),\n\t\t\t\t\t\t\t   ObjectIdGetDatum(namespace_oid));\n\tCheckCompressedData(OidIsValid(type_oid));\n\n\treturn type_oid;\n}\n\nvoid\ndatum_append_to_binary_string(DatumSerializer *serializer, BinaryStringEncoding encoding,\n\t\t\t\t\t\t\t  StringInfo buffer, Datum datum)\n{\n\tload_send_fn(serializer);\n\n\tif (encoding == MESSAGE_SPECIFIES_ENCODING)\n\t\tpq_sendbyte(buffer, serializer->use_binary_send);\n\telse if (encoding != datum_serializer_binary_string_encoding(serializer))\n\t\telog(ERROR, \"incorrect encoding chosen in datum_append_to_binary_string\");\n\n\tif (serializer->use_binary_send)\n\t{\n\t\tbytea *output = SendFunctionCall(&serializer->send_flinfo, datum);\n\t\tpq_sendint32(buffer, VARSIZE_ANY_EXHDR(output));\n\t\tpq_sendbytes(buffer, VARDATA(output), VARSIZE_ANY_EXHDR(output));\n\t}\n\telse\n\t{\n\t\tchar *output = OutputFunctionCall(&serializer->send_flinfo, datum);\n\t\tpq_sendstring(buffer, output);\n\t}\n}\n\nDatum\nbinary_string_to_datum(DatumDeserializer *deserializer, BinaryStringEncoding encoding,\n\t\t\t\t\t   StringInfo buffer)\n{\n\tDatum res;\n\tbool use_binary_recv = false;\n\n\tswitch (encoding)\n\t{\n\t\tcase BINARY_ENCODING:\n\t\t\tuse_binary_recv = true;\n\t\t\tbreak;\n\t\tcase TEXT_ENCODING:\n\t\t\tuse_binary_recv = false;\n\t\t\tbreak;\n\t\tcase MESSAGE_SPECIFIES_ENCODING:\n\t\t\tuse_binary_recv = pq_getmsgbyte(buffer) != 0;\n\t\t\tbreak;\n\t}\n\n\tload_recv_fn(deserializer, use_binary_recv);\n\n\tif (use_binary_recv)\n\t{\n\t\tuint32 data_size = pq_getmsgint32(buffer);\n\t\tconst char *bytes = pq_getmsgbytes(buffer, data_size);\n\t\tStringInfoData d = {\n\t\t\t.data = (char *) bytes,\n\t\t\t.len = data_size,\n\t\t\t.maxlen = data_size,\n\t\t};\n\t\tres = ReceiveFunctionCall(&deserializer->recv_flinfo,\n\t\t\t\t\t\t\t\t  &d,\n\t\t\t\t\t\t\t\t  deserializer->type_io_param,\n\t\t\t\t\t\t\t\t  deserializer->type_mod);\n\t}\n\telse\n\t{\n\t\tconst char *string = pq_getmsgstring(buffer);\n\t\tres = InputFunctionCall(&deserializer->recv_flinfo,\n\t\t\t\t\t\t\t\t(char *) string,\n\t\t\t\t\t\t\t\tdeserializer->type_io_param,\n\t\t\t\t\t\t\t\tdeserializer->type_mod);\n\t}\n\treturn res;\n}\n"
  },
  {
    "path": "tsl/src/compression/algorithms/datum_serialize.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <lib/stringinfo.h>\n\n/* SERIALIZATION */\ntypedef struct DatumSerializer DatumSerializer;\nDatumSerializer *create_datum_serializer(Oid type);\n\nbool datum_serializer_value_may_be_toasted(DatumSerializer *serializer);\n\ntypedef enum\n{\n\tBINARY_ENCODING = 0,\n\tTEXT_ENCODING,\n\tMESSAGE_SPECIFIES_ENCODING,\n} BinaryStringEncoding;\n\n/* Get  the encoding type used by the serializer: either BINARY_ENCODING or TEXT_ENCODING */\nBinaryStringEncoding datum_serializer_binary_string_encoding(DatumSerializer *serializer);\n\n/* serialize to bytes in memory. */\nSize datum_get_bytes_size(DatumSerializer *serializer, Size start_offset, Datum val);\nchar *datum_to_bytes_and_advance(DatumSerializer *serializer, char *start, Size *max_size,\n\t\t\t\t\t\t\t\t Datum datum);\n\n/* serialize to a binary string (for send functions) */\nvoid type_append_to_binary_string(Oid type_oid, StringInfo buffer);\nvoid datum_append_to_binary_string(DatumSerializer *serializer, BinaryStringEncoding encoding,\n\t\t\t\t\t\t\t\t   StringInfo buffer, Datum datum);\n\n/* DESERIALIZATION */\ntypedef struct DatumDeserializer DatumDeserializer;\nDatumDeserializer *create_datum_deserializer(Oid type);\n\n/* deserialization from bytes in memory */\nDatum bytes_to_datum_and_advance(DatumDeserializer *deserializer, const char **ptr);\n\n/* deserialization from binary strings (for recv functions) */\nDatum binary_string_to_datum(DatumDeserializer *deserializer, BinaryStringEncoding encoding,\n\t\t\t\t\t\t\t StringInfo buffer);\nOid binary_string_get_type(StringInfo buffer);\n"
  },
  {
    "path": "tsl/src/compression/algorithms/deltadelta.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include \"deltadelta.h\"\n\n#include <access/htup_details.h>\n#include <catalog/pg_aggregate.h>\n#include <catalog/pg_type.h>\n#include <common/base64.h>\n#include <funcapi.h>\n#include <lib/stringinfo.h>\n#include <stdbool.h>\n#include <utils/builtins.h>\n#include <utils/date.h>\n#include <utils/lsyscache.h>\n#include <utils/syscache.h>\n#include <utils/timestamp.h>\n\n#include <utils.h>\n\n#include \"compression/arrow_c_data_interface.h\"\n#include \"compression/compression.h\"\n#include \"guc.h\"\n#include \"simple8b_rle.h\"\n#include \"simple8b_rle_bitmap.h\"\n\nstatic uint64 zig_zag_encode(uint64 value);\nstatic uint64 zig_zag_decode(uint64 value);\n\ntypedef struct DeltaDeltaCompressed\n{\n\tCompressedDataHeaderFields;\n\tuint8 has_nulls; /* 1 if this has a NULLs bitmap after deltas, 0 otherwise */\n\tuint8 padding[2];\n\tuint64 last_value;\n\tuint64 last_delta;\n\tchar delta_deltas[FLEXIBLE_ARRAY_MEMBER];\n} DeltaDeltaCompressed;\n\nstatic void\npg_attribute_unused() assertions(void)\n{\n\tDeltaDeltaCompressed test_val = { .vl_len_ = { 0 } };\n\t/* make sure no padding bytes make it to disk */\n\tStaticAssertStmt(sizeof(DeltaDeltaCompressed) ==\n\t\t\t\t\t\t sizeof(test_val.vl_len_) + sizeof(test_val.compression_algorithm) +\n\t\t\t\t\t\t\t sizeof(test_val.has_nulls) + sizeof(test_val.padding) +\n\t\t\t\t\t\t\t sizeof(test_val.last_value) + sizeof(test_val.last_delta),\n\t\t\t\t\t \"DeltaDeltaCompressed wrong size\");\n\tStaticAssertStmt(sizeof(DeltaDeltaCompressed) == 24, \"DeltaDeltaCompressed wrong size\");\n}\n\ntypedef struct DeltaDeltaDecompressionIterator\n{\n\tDecompressionIterator base;\n\tuint64 prev_val;\n\tuint64 prev_delta;\n\tSimple8bRleDecompressionIterator delta_deltas;\n\tSimple8bRleDecompressionIterator nulls;\n\tbool has_nulls;\n} DeltaDeltaDecompressionIterator;\n\ntypedef struct DeltaDeltaCompressor\n{\n\tuint64 prev_val;\n\tuint64 prev_delta;\n\tSimple8bRleCompressor delta_delta;\n\tSimple8bRleCompressor nulls;\n\tbool has_nulls;\n} DeltaDeltaCompressor;\n\ntypedef struct ExtendedCompressor\n{\n\tCompressor base;\n\tDeltaDeltaCompressor *internal;\n} ExtendedCompressor;\n\nbool\ndeltadelta_compressed_has_nulls(const CompressedDataHeader *header)\n{\n\tconst DeltaDeltaCompressed *ddc = (const DeltaDeltaCompressed *) header;\n\treturn ddc->has_nulls;\n}\n\nstatic void\ndeltadelta_compressor_append_bool(Compressor *compressor, Datum val)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tif (extended->internal == NULL)\n\t\textended->internal = delta_delta_compressor_alloc();\n\n\tdelta_delta_compressor_append_value(extended->internal, DatumGetBool(val) ? 1 : 0);\n}\n\nstatic void\ndeltadelta_compressor_append_int16(Compressor *compressor, Datum val)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tif (extended->internal == NULL)\n\t\textended->internal = delta_delta_compressor_alloc();\n\n\tdelta_delta_compressor_append_value(extended->internal, DatumGetInt16(val));\n}\n\nstatic void\ndeltadelta_compressor_append_int32(Compressor *compressor, Datum val)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tif (extended->internal == NULL)\n\t\textended->internal = delta_delta_compressor_alloc();\n\n\tdelta_delta_compressor_append_value(extended->internal, DatumGetInt32(val));\n}\n\nstatic void\ndeltadelta_compressor_append_int64(Compressor *compressor, Datum val)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tif (extended->internal == NULL)\n\t\textended->internal = delta_delta_compressor_alloc();\n\n\tdelta_delta_compressor_append_value(extended->internal, DatumGetInt64(val));\n}\n\nstatic void\ndeltadelta_compressor_append_date(Compressor *compressor, Datum val)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tif (extended->internal == NULL)\n\t\textended->internal = delta_delta_compressor_alloc();\n\n\tdelta_delta_compressor_append_value(extended->internal, DatumGetDateADT(val));\n}\n\nstatic void\ndeltadelta_compressor_append_timestamp(Compressor *compressor, Datum val)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tif (extended->internal == NULL)\n\t\textended->internal = delta_delta_compressor_alloc();\n\n\tdelta_delta_compressor_append_value(extended->internal, DatumGetTimestamp(val));\n}\n\nstatic void\ndeltadelta_compressor_append_timestamptz(Compressor *compressor, Datum val)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tif (extended->internal == NULL)\n\t\textended->internal = delta_delta_compressor_alloc();\n\n\tdelta_delta_compressor_append_value(extended->internal, DatumGetTimestampTz(val));\n}\n\nstatic void\ndeltadelta_compressor_append_null_value(Compressor *compressor)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tif (extended->internal == NULL)\n\t\textended->internal = delta_delta_compressor_alloc();\n\n\tdelta_delta_compressor_append_null(extended->internal);\n}\n\nstatic void *\ndeltadelta_compressor_finish_and_reset(Compressor *compressor)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tvoid *compressed = delta_delta_compressor_finish(extended->internal);\n\tpfree(extended->internal);\n\textended->internal = NULL;\n\treturn compressed;\n}\n\nconst Compressor deltadelta_bool_compressor = {\n\t.append_val = deltadelta_compressor_append_bool,\n\t.append_null = deltadelta_compressor_append_null_value,\n\t.is_full = NULL,\n\t.finish = deltadelta_compressor_finish_and_reset,\n};\n\nconst Compressor deltadelta_uint16_compressor = {\n\t.append_val = deltadelta_compressor_append_int16,\n\t.append_null = deltadelta_compressor_append_null_value,\n\t.is_full = NULL,\n\t.finish = deltadelta_compressor_finish_and_reset,\n};\nconst Compressor deltadelta_uint32_compressor = {\n\t.append_val = deltadelta_compressor_append_int32,\n\t.append_null = deltadelta_compressor_append_null_value,\n\t.is_full = NULL,\n\t.finish = deltadelta_compressor_finish_and_reset,\n};\nconst Compressor deltadelta_uint64_compressor = {\n\t.append_val = deltadelta_compressor_append_int64,\n\t.append_null = deltadelta_compressor_append_null_value,\n\t.is_full = NULL,\n\t.finish = deltadelta_compressor_finish_and_reset,\n};\n\nconst Compressor deltadelta_date_compressor = {\n\t.append_val = deltadelta_compressor_append_date,\n\t.append_null = deltadelta_compressor_append_null_value,\n\t.is_full = NULL,\n\t.finish = deltadelta_compressor_finish_and_reset,\n};\n\nconst Compressor deltadelta_timestamp_compressor = {\n\t.append_val = deltadelta_compressor_append_timestamp,\n\t.append_null = deltadelta_compressor_append_null_value,\n\t.is_full = NULL,\n\t.finish = deltadelta_compressor_finish_and_reset,\n};\n\nconst Compressor deltadelta_timestamptz_compressor = {\n\t.append_val = deltadelta_compressor_append_timestamptz,\n\t.append_null = deltadelta_compressor_append_null_value,\n\t.is_full = NULL,\n\t.finish = deltadelta_compressor_finish_and_reset,\n};\n\nCompressor *\ndelta_delta_compressor_for_type(Oid element_type)\n{\n\tExtendedCompressor *compressor = palloc(sizeof(*compressor));\n\tswitch (element_type)\n\t{\n\t\tcase BOOLOID:\n\t\t\t*compressor = (ExtendedCompressor){ .base = deltadelta_bool_compressor };\n\t\t\treturn &compressor->base;\n\t\tcase INT2OID:\n\t\t\t*compressor = (ExtendedCompressor){ .base = deltadelta_uint16_compressor };\n\t\t\treturn &compressor->base;\n\t\tcase INT4OID:\n\t\t\t*compressor = (ExtendedCompressor){ .base = deltadelta_uint32_compressor };\n\t\t\treturn &compressor->base;\n\t\tcase INT8OID:\n\t\t\t*compressor = (ExtendedCompressor){ .base = deltadelta_uint64_compressor };\n\t\t\treturn &compressor->base;\n\t\tcase DATEOID:\n\t\t\t*compressor = (ExtendedCompressor){ .base = deltadelta_date_compressor };\n\t\t\treturn &compressor->base;\n\t\tcase TIMESTAMPOID:\n\t\t\t*compressor = (ExtendedCompressor){ .base = deltadelta_timestamp_compressor };\n\t\t\treturn &compressor->base;\n\t\tcase TIMESTAMPTZOID:\n\t\t\t*compressor = (ExtendedCompressor){ .base = deltadelta_timestamptz_compressor };\n\t\t\treturn &compressor->base;\n\t\tdefault:\n\t\t\telog(ERROR,\n\t\t\t\t \"invalid type for delta-delta compressor \\\"%s\\\"\",\n\t\t\t\t format_type_be(element_type));\n\t}\n\n\tpg_unreachable();\n}\n\nDatum\ntsl_deltadelta_compressor_append(PG_FUNCTION_ARGS)\n{\n\tMemoryContext old_context;\n\tMemoryContext agg_context;\n\tDeltaDeltaCompressor *compressor =\n\t\t(DeltaDeltaCompressor *) (PG_ARGISNULL(0) ? NULL : PG_GETARG_POINTER(0));\n\n\tif (!AggCheckCallContext(fcinfo, &agg_context))\n\t{\n\t\t/* cannot be called directly because of internal-type argument */\n\t\telog(ERROR, \"tsl_deltadelta_compressor_append called in non-aggregate context\");\n\t}\n\n\told_context = MemoryContextSwitchTo(agg_context);\n\n\tif (compressor == NULL)\n\t{\n\t\tcompressor = delta_delta_compressor_alloc();\n\t\tif (PG_NARGS() > 2)\n\t\t\telog(ERROR, \"append expects two arguments\");\n\t}\n\n\tif (PG_ARGISNULL(1))\n\t\tdelta_delta_compressor_append_null(compressor);\n\telse\n\t{\n\t\tint64 next_val = PG_GETARG_INT64(1);\n\t\tdelta_delta_compressor_append_value(compressor, next_val);\n\t}\n\n\tMemoryContextSwitchTo(old_context);\n\tPG_RETURN_POINTER(compressor);\n}\n\nDeltaDeltaCompressor *\ndelta_delta_compressor_alloc(void)\n{\n\tDeltaDeltaCompressor *compressor = palloc0(sizeof(*compressor));\n\tsimple8brle_compressor_init(&compressor->delta_delta);\n\tsimple8brle_compressor_init(&compressor->nulls);\n\treturn compressor;\n}\n\nstatic void *\ndelta_delta_set_header_and_advance(uint64 last_value, uint64 last_delta, bool has_nulls,\n\t\t\t\t\t\t\t\t   size_t compressed_size, void *dest)\n{\n\tDeltaDeltaCompressed *compressed = (DeltaDeltaCompressed *) dest;\n\tSET_VARSIZE(&compressed->vl_len_, compressed_size);\n\tcompressed->compression_algorithm = COMPRESSION_ALGORITHM_DELTADELTA;\n\tcompressed->last_value = last_value;\n\tcompressed->last_delta = last_delta;\n\tcompressed->has_nulls = has_nulls ? 1 : 0;\n\tcompressed->padding[0] = 0;\n\tcompressed->padding[1] = 0;\n\treturn (char *) compressed + sizeof(*compressed);\n}\n\nsize_t\ndelta_delta_compressor_compressed_size(DeltaDeltaCompressor *compressor, size_t *nulls_size_out)\n{\n\tsize_t compressed_size = sizeof(DeltaDeltaCompressed);\n\tsize_t nulls_size_actual;\n\n\t/* If there are no elements, the compressed size is 0 even if there are nulls */\n\tif (compressor->delta_delta.num_elements == 0)\n\t{\n\t\tif (nulls_size_out != NULL)\n\t\t\t*nulls_size_out = 0;\n\t\treturn 0;\n\t}\n\n\tcompressed_size += simple8brle_compressor_compressed_const_size(&compressor->delta_delta);\n\n\tif (compressor->has_nulls)\n\t{\n\t\tnulls_size_actual = simple8brle_compressor_compressed_const_size(&compressor->nulls);\n\t\tcompressed_size += nulls_size_actual;\n\t\tif (nulls_size_out != NULL)\n\t\t\t*nulls_size_out = nulls_size_actual;\n\t}\n\telse if (nulls_size_out != NULL)\n\t\t*nulls_size_out = 0;\n\n\treturn compressed_size;\n}\n\nvoid *\ndelta_delta_compressor_finish(DeltaDeltaCompressor *compressor)\n{\n\tsize_t total_size = delta_delta_compressor_compressed_size(compressor, NULL);\n\tchar *compressed = NULL;\n\n\tif (total_size == 0)\n\t\treturn NULL;\n\n\tcompressed = palloc(total_size);\n\tdelta_delta_compressor_finish_into(compressor, compressed);\n\treturn compressed;\n}\n\nvoid *\ndelta_delta_compressor_finish_into(DeltaDeltaCompressor *compressor, void *dest)\n{\n\tsize_t data_size;\n\tsize_t nulls_size;\n\tsize_t compressed_size;\n\tchar *result = (char *) dest;\n\n\t/* The compressed size includes the header and the nulls */\n\tcompressed_size = delta_delta_compressor_compressed_size(compressor, &nulls_size);\n\tif (compressed_size == 0)\n\t\treturn dest;\n\n\t/* Check if the data size is valid */\n\tdata_size = compressed_size - sizeof(DeltaDeltaCompressed) - nulls_size;\n\tAssert(compressed_size > (sizeof(DeltaDeltaCompressed) + nulls_size));\n\n\tresult = delta_delta_set_header_and_advance(compressor->prev_val,\n\t\t\t\t\t\t\t\t\t\t\t\tcompressor->prev_delta,\n\t\t\t\t\t\t\t\t\t\t\t\tcompressor->has_nulls,\n\t\t\t\t\t\t\t\t\t\t\t\tcompressed_size,\n\t\t\t\t\t\t\t\t\t\t\t\tdest);\n\n\tresult = simple8brle_compressor_finish_into(&compressor->delta_delta, result, data_size);\n\n\tif (compressor->has_nulls)\n\t{\n\t\tAssert(nulls_size > 0);\n\t\tresult = simple8brle_compressor_finish_into(&compressor->nulls, result, nulls_size);\n\t}\n\n\treturn result;\n}\n\nDatum\ntsl_deltadelta_compressor_finish(PG_FUNCTION_ARGS)\n{\n\tDeltaDeltaCompressor *compressor =\n\t\tPG_ARGISNULL(0) ? NULL : (DeltaDeltaCompressor *) PG_GETARG_POINTER(0);\n\tvoid *compressed;\n\tif (compressor == NULL)\n\t\tPG_RETURN_NULL();\n\n\tcompressed = delta_delta_compressor_finish(compressor);\n\tif (compressed == NULL)\n\t\tPG_RETURN_NULL();\n\tPG_RETURN_POINTER(compressed);\n}\n\nvoid\ndelta_delta_compressor_append_null(DeltaDeltaCompressor *compressor)\n{\n\tcompressor->has_nulls = true;\n\tsimple8brle_compressor_append(&compressor->nulls, 1);\n}\n\nvoid\ndelta_delta_compressor_append_value(DeltaDeltaCompressor *compressor, int64 next_val)\n{\n\tuint64 delta;\n\tuint64 delta_delta;\n\tuint64 encoded;\n\n\t/*\n\t * We perform all arithmetic using unsigned values due to C's overflow rules:\n\t * signed integer overflow is undefined behavior, so if we have a very large delta,\n\t * this code is without meaning, while unsigned overflow is 2's complement, so even\n\t * very large delta work the same as any other\n\t */\n\n\t/* step 1: delta of deltas */\n\tdelta = ((uint64) next_val) - compressor->prev_val;\n\tdelta_delta = delta - compressor->prev_delta;\n\n\tcompressor->prev_val = next_val;\n\tcompressor->prev_delta = delta;\n\n\t/* step 2: ZigZag encode */\n\tencoded = zig_zag_encode(delta_delta);\n\n\t/* step 3: simple8b/RTE */\n\tsimple8brle_compressor_append(&compressor->delta_delta, encoded);\n\tsimple8brle_compressor_append(&compressor->nulls, 0);\n}\n\n/**********************************************************************************/\n/**********************************************************************************/\n\nstatic void\nint64_decompression_iterator_init_forward(DeltaDeltaDecompressionIterator *iter, void *compressed,\n\t\t\t\t\t\t\t\t\t\t  Oid element_type)\n{\n\tStringInfoData si = { .data = compressed, .len = VARSIZE(compressed) };\n\tCheckCompressedData(VARSIZE(compressed) >= sizeof(DeltaDeltaCompressed));\n\tCheckCompressedData(((DeltaDeltaCompressed *) compressed)->compression_algorithm ==\n\t\t\t\t\t\tCOMPRESSION_ALGORITHM_DELTADELTA);\n\tDeltaDeltaCompressed *header = consumeCompressedData(&si, sizeof(DeltaDeltaCompressed));\n\n\tSimple8bRleSerialized *deltas = bytes_deserialize_simple8b_and_advance(&si);\n\n\tconst bool has_nulls = header->has_nulls == 1;\n\n\tCheckCompressedData(has_nulls == 0 || has_nulls == 1);\n\n\t*iter = (DeltaDeltaDecompressionIterator){\n\t\t.base = {\n\t\t\t.compression_algorithm = COMPRESSION_ALGORITHM_DELTADELTA,\n\t\t\t.forward = true,\n\t\t\t.element_type = element_type,\n\t\t\t.try_next = delta_delta_decompression_iterator_try_next_forward,\n\t\t},\n\t\t.prev_val = 0,\n\t\t.prev_delta = 0,\n\t\t.has_nulls = has_nulls,\n\t};\n\n\tsimple8brle_decompression_iterator_init_forward(&iter->delta_deltas, deltas);\n\n\tif (has_nulls)\n\t{\n\t\tSimple8bRleSerialized *nulls = bytes_deserialize_simple8b_and_advance(&si);\n\t\tsimple8brle_decompression_iterator_init_forward(&iter->nulls, nulls);\n\t}\n}\n\nstatic void\nint64_decompression_iterator_init_reverse(DeltaDeltaDecompressionIterator *iter, void *compressed,\n\t\t\t\t\t\t\t\t\t\t  Oid element_type)\n{\n\tStringInfoData si = { .data = compressed, .len = VARSIZE(compressed) };\n\tCheckCompressedData(VARSIZE(compressed) >= sizeof(DeltaDeltaCompressed));\n\tCheckCompressedData(((DeltaDeltaCompressed *) compressed)->compression_algorithm ==\n\t\t\t\t\t\tCOMPRESSION_ALGORITHM_DELTADELTA);\n\tDeltaDeltaCompressed *header = consumeCompressedData(&si, sizeof(DeltaDeltaCompressed));\n\tSimple8bRleSerialized *deltas = bytes_deserialize_simple8b_and_advance(&si);\n\n\tAssert(header->has_nulls == 0 || header->has_nulls == 1);\n\n\t*iter = (DeltaDeltaDecompressionIterator){\n\t\t.base = {\n\t\t\t.compression_algorithm = COMPRESSION_ALGORITHM_DELTADELTA,\n\t\t\t.forward = false,\n\t\t\t.element_type = element_type,\n\t\t\t.try_next = delta_delta_decompression_iterator_try_next_reverse,\n\t\t},\n\t\t.prev_val = header->last_value,\n\t\t.prev_delta = header->last_delta,\n\t\t.has_nulls = header->has_nulls,\n\t};\n\n\tsimple8brle_decompression_iterator_init_reverse(&iter->delta_deltas, deltas);\n\n\tif (header->has_nulls)\n\t{\n\t\tSimple8bRleSerialized *nulls = bytes_deserialize_simple8b_and_advance(&si);\n\t\tsimple8brle_decompression_iterator_init_reverse(&iter->nulls, nulls);\n\t}\n}\n\nstatic inline DecompressResult\nconvert_from_internal(DecompressResultInternal res_internal, Oid element_type)\n{\n\tif (res_internal.is_done || res_internal.is_null)\n\t{\n\t\treturn (DecompressResult){\n\t\t\t.is_done = res_internal.is_done,\n\t\t\t.is_null = res_internal.is_null,\n\t\t};\n\t}\n\n\tswitch (element_type)\n\t{\n\t\tcase BOOLOID:\n\t\t\treturn (DecompressResult){\n\t\t\t\t.val = BoolGetDatum(res_internal.val),\n\t\t\t};\n\t\tcase INT8OID:\n\t\t\treturn (DecompressResult){\n\t\t\t\t.val = Int64GetDatum(res_internal.val),\n\t\t\t};\n\t\tcase INT4OID:\n\t\t\treturn (DecompressResult){\n\t\t\t\t.val = Int32GetDatum(res_internal.val),\n\t\t\t};\n\t\tcase INT2OID:\n\t\t\treturn (DecompressResult){\n\t\t\t\t.val = Int16GetDatum(res_internal.val),\n\t\t\t};\n\t\tcase DATEOID:\n\t\t\treturn (DecompressResult){\n\t\t\t\t.val = DateADTGetDatum(res_internal.val),\n\t\t\t};\n\t\tcase TIMESTAMPTZOID:\n\t\t\treturn (DecompressResult){\n\t\t\t\t.val = TimestampTzGetDatum(res_internal.val),\n\t\t\t};\n\t\tcase TIMESTAMPOID:\n\t\t\treturn (DecompressResult){\n\t\t\t\t.val = TimestampGetDatum(res_internal.val),\n\t\t\t};\n\t\tdefault:\n\t\t\telog(ERROR,\n\t\t\t\t \"invalid type requested from deltadelta decompression \\\"%s\\\"\",\n\t\t\t\t format_type_be(element_type));\n\t}\n\n\tpg_unreachable();\n}\n\nstatic DecompressResultInternal\ndelta_delta_decompression_iterator_try_next_forward_internal(DeltaDeltaDecompressionIterator *iter)\n{\n\tSimple8bRleDecompressResult result;\n\tuint64 delta_delta;\n\n\t/* check for a null value */\n\tif (iter->has_nulls)\n\t{\n\t\tSimple8bRleDecompressResult result =\n\t\t\tsimple8brle_decompression_iterator_try_next_forward(&iter->nulls);\n\t\tif (result.is_done)\n\t\t\treturn (DecompressResultInternal){\n\t\t\t\t.is_done = true,\n\t\t\t};\n\n\t\tif (result.val != 0)\n\t\t{\n\t\t\tCheckCompressedData(result.val == 1);\n\t\t\treturn (DecompressResultInternal){\n\t\t\t\t.is_null = true,\n\t\t\t};\n\t\t}\n\t}\n\n\tresult = simple8brle_decompression_iterator_try_next_forward(&iter->delta_deltas);\n\n\tif (result.is_done)\n\t\treturn (DecompressResultInternal){\n\t\t\t.is_done = true,\n\t\t};\n\n\tdelta_delta = zig_zag_decode(result.val);\n\n\titer->prev_delta += delta_delta;\n\titer->prev_val += iter->prev_delta;\n\n\treturn (DecompressResultInternal){\n\t\t.val = iter->prev_val,\n\t\t.is_null = false,\n\t\t.is_done = false,\n\t};\n}\n\nDecompressResult\ndelta_delta_decompression_iterator_try_next_forward(DecompressionIterator *iter)\n{\n\tAssert(iter->compression_algorithm == COMPRESSION_ALGORITHM_DELTADELTA && iter->forward);\n\treturn convert_from_internal(delta_delta_decompression_iterator_try_next_forward_internal(\n\t\t\t\t\t\t\t\t\t (DeltaDeltaDecompressionIterator *) iter),\n\t\t\t\t\t\t\t\t iter->element_type);\n}\n\n#define ELEMENT_TYPE uint64\n#include \"simple8b_rle_decompress_all.h\"\n#undef ELEMENT_TYPE\n\n/* Functions for bulk decompression. */\n#define ELEMENT_TYPE uint16\n#include \"deltadelta_impl.c\"\n#undef ELEMENT_TYPE\n\n#define ELEMENT_TYPE uint32\n#include \"deltadelta_impl.c\"\n#undef ELEMENT_TYPE\n\n#define ELEMENT_TYPE uint64\n#include \"deltadelta_impl.c\"\n#undef ELEMENT_TYPE\n\nArrowArray *\ndelta_delta_decompress_all(Datum compressed_data, Oid element_type, MemoryContext dest_mctx)\n{\n\tswitch (element_type)\n\t{\n\t\tcase INT8OID:\n\t\tcase TIMESTAMPOID:\n\t\tcase TIMESTAMPTZOID:\n\t\t\treturn delta_delta_decompress_all_uint64(compressed_data, dest_mctx);\n\t\tcase INT4OID:\n\t\tcase DATEOID:\n\t\t\treturn delta_delta_decompress_all_uint32(compressed_data, dest_mctx);\n\t\tcase INT2OID:\n\t\t\treturn delta_delta_decompress_all_uint16(compressed_data, dest_mctx);\n\t\tdefault:\n\t\t\telog(ERROR,\n\t\t\t\t \"type '%s' is not supported for deltadelta decompression\",\n\t\t\t\t format_type_be(element_type));\n\t\t\tpg_unreachable();\n\t}\n}\n\n/* Functions for reverse iterator. */\nstatic DecompressResultInternal\ndelta_delta_decompression_iterator_try_next_reverse_internal(DeltaDeltaDecompressionIterator *iter)\n{\n\tSimple8bRleDecompressResult result;\n\tuint64 val;\n\tuint64 delta_delta;\n\t/* check for a null value */\n\tif (iter->has_nulls)\n\t{\n\t\tSimple8bRleDecompressResult result =\n\t\t\tsimple8brle_decompression_iterator_try_next_reverse(&iter->nulls);\n\t\tif (result.is_done)\n\t\t\treturn (DecompressResultInternal){\n\t\t\t\t.is_done = true,\n\t\t\t};\n\n\t\tif (result.val != 0)\n\t\t{\n\t\t\tAssert(result.val == 1);\n\t\t\treturn (DecompressResultInternal){\n\t\t\t\t.is_null = true,\n\t\t\t};\n\t\t}\n\t}\n\n\tresult = simple8brle_decompression_iterator_try_next_reverse(&iter->delta_deltas);\n\n\tif (result.is_done)\n\t\treturn (DecompressResultInternal){\n\t\t\t.is_done = true,\n\t\t};\n\n\tval = iter->prev_val;\n\n\tdelta_delta = zig_zag_decode(result.val);\n\titer->prev_val -= iter->prev_delta;\n\titer->prev_delta -= delta_delta;\n\n\treturn (DecompressResultInternal){\n\t\t.val = val,\n\t};\n}\n\nDecompressResult\ndelta_delta_decompression_iterator_try_next_reverse(DecompressionIterator *iter)\n{\n\tAssert(iter->compression_algorithm == COMPRESSION_ALGORITHM_DELTADELTA && !iter->forward);\n\treturn convert_from_internal(delta_delta_decompression_iterator_try_next_reverse_internal(\n\t\t\t\t\t\t\t\t\t (DeltaDeltaDecompressionIterator *) iter),\n\t\t\t\t\t\t\t\t iter->element_type);\n}\n\nDecompressionIterator *\ndelta_delta_decompression_iterator_from_datum_forward(Datum deltadelta_compressed, Oid element_type)\n{\n\tDeltaDeltaDecompressionIterator *iterator = palloc(sizeof(*iterator));\n\tint64_decompression_iterator_init_forward(iterator,\n\t\t\t\t\t\t\t\t\t\t\t  (void *) PG_DETOAST_DATUM(deltadelta_compressed),\n\t\t\t\t\t\t\t\t\t\t\t  element_type);\n\treturn &iterator->base;\n}\n\nDecompressionIterator *\ndelta_delta_decompression_iterator_from_datum_reverse(Datum deltadelta_compressed, Oid element_type)\n{\n\tDeltaDeltaDecompressionIterator *iterator = palloc(sizeof(*iterator));\n\tint64_decompression_iterator_init_reverse(iterator,\n\t\t\t\t\t\t\t\t\t\t\t  (void *) PG_DETOAST_DATUM(deltadelta_compressed),\n\t\t\t\t\t\t\t\t\t\t\t  element_type);\n\treturn &iterator->base;\n}\n\n/**********************************************************************************/\n/**********************************************************************************/\nvoid\ndeltadelta_compressed_send(CompressedDataHeader *header, StringInfo buffer)\n{\n\tconst DeltaDeltaCompressed *data = (DeltaDeltaCompressed *) header;\n\tAssert(header->compression_algorithm == COMPRESSION_ALGORITHM_DELTADELTA);\n\tpq_sendbyte(buffer, data->has_nulls);\n\tpq_sendint64(buffer, data->last_value);\n\tpq_sendint64(buffer, data->last_delta);\n\tsimple8brle_serialized_send(buffer, (Simple8bRleSerialized *) data->delta_deltas);\n\tif (data->has_nulls)\n\t{\n\t\tSimple8bRleSerialized *nulls =\n\t\t\t(Simple8bRleSerialized *) (((char *) data->delta_deltas) +\n\t\t\t\t\t\t\t\t\t   simple8brle_serialized_total_size(\n\t\t\t\t\t\t\t\t\t\t   (Simple8bRleSerialized *) data->delta_deltas));\n\t\tsimple8brle_serialized_send(buffer, nulls);\n\t}\n}\nDatum\ndeltadelta_compressed_recv(StringInfo buffer)\n{\n\tuint8 has_nulls;\n\tuint64 last_value;\n\tuint64 last_delta;\n\tSimple8bRleSerialized *delta_delta_values;\n\tSimple8bRleSerialized *nulls = NULL;\n\tDeltaDeltaCompressed *compressed;\n\tvoid *buf_ptr = NULL;\n\tint delta_size = 0;\n\tint nulls_size = 0;\n\tsize_t compressed_size = 0;\n\n\tsize_t allocated_size = 2 * sizeof(Simple8bRleSerialized) + sizeof(DeltaDeltaCompressed) +\n\t\t\t\t\t\t\t(buffer->len - buffer->cursor);\n\tcompressed = palloc(allocated_size);\n\n\thas_nulls = pq_getmsgbyte(buffer);\n\tCheckCompressedData(has_nulls == 0 || has_nulls == 1);\n\n\tlast_value = pq_getmsgint64(buffer);\n\tlast_delta = pq_getmsgint64(buffer);\n\n\t/* Leave space for the header, but we don't yet know the size of the compressed data */\n\tbuf_ptr = (char *) compressed + sizeof(DeltaDeltaCompressed);\n\n\t/* Calculate the size of the delta delta values based on the number of bytes read */\n\tdelta_size = buffer->cursor;\n\tbuf_ptr = simple8brle_serialized_recv_into(buffer, buf_ptr, &delta_delta_values);\n\tdelta_size = buffer->cursor - delta_size;\n\n\tif (has_nulls)\n\t{\n\t\t/* Calculate the size of the nulls based on the number of bytes read */\n\t\tnulls_size = buffer->cursor;\n\t\tbuf_ptr = simple8brle_serialized_recv_into(buffer, buf_ptr, &nulls);\n\t\tnulls_size = buffer->cursor - nulls_size;\n\t\tCheckCompressedData(delta_delta_values->num_elements < nulls->num_elements);\n\t}\n\n\tcompressed_size = sizeof(DeltaDeltaCompressed) + delta_size + nulls_size;\n\n\tif (!AllocSizeIsValid(compressed_size))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),\n\t\t\t\t errmsg(\"compressed size exceeds the maximum allowed (%d)\", (int) MaxAllocSize)));\n\n\t/* Set header but don't change the buffer pointer */\n\tdelta_delta_set_header_and_advance(last_value,\n\t\t\t\t\t\t\t\t\t   last_delta,\n\t\t\t\t\t\t\t\t\t   has_nulls,\n\t\t\t\t\t\t\t\t\t   compressed_size,\n\t\t\t\t\t\t\t\t\t   compressed);\n\n\tPG_RETURN_POINTER(compressed);\n}\n\n/**********************************************************************************/\n/**********************************************************************************/\n\nstatic pg_attribute_always_inline uint64\nzig_zag_encode(uint64 value)\n{\n\t// (((uint64)value) << 1) ^ (uint64)(value >> 63);\n\t/* since shift is underspecified, we use (value < 0 ? 0xFFFFFFFFFFFFFFFFull : 0)\n\t * which compiles to the correct asm, and is well defined\n\t */\n\treturn (value << 1) ^ (((int64) value) < 0 ? 0xFFFFFFFFFFFFFFFFULL : 0);\n}\n\nstatic pg_attribute_always_inline uint64\nzig_zag_decode(uint64 value)\n{\n\t/* ZigZag turns negative numbers into odd ones, and positive numbers into even ones*/\n\treturn (value >> 1) ^ (uint64) - (int64) (value & 1);\n}\n"
  },
  {
    "path": "tsl/src/compression/algorithms/deltadelta.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n/*\n * Deltadelta is used to encode integers or integer-like objects (e.g. timestamps). It's input is a\n * series of integers. first convert that series to a series of delta-of-deltas between\n * consecutive integers, while storing the first value as well. Given the first value\n * and the series of delta-of-delta values, it is easy to reconstruct the original series of\n * integers (assume first delta is 0).\n *\n * We now describe how to compress the delta-of-deltas:\n * First we zigzag encodes the delta-of-deltas\n * Second, we simple8b_rle encode the zig-zag encoding\n */\n\n#include <postgres.h>\n#include <fmgr.h>\n#include <lib/stringinfo.h>\n\n#include \"compression/compression.h\"\n\ntypedef struct DeltaDeltaCompressor DeltaDeltaCompressor;\ntypedef struct DeltaDeltaCompressed DeltaDeltaCompressed;\ntypedef struct DeltaDeltaDecompressionIterator DeltaDeltaDecompressionIterator;\n\nextern bool deltadelta_compressed_has_nulls(const CompressedDataHeader *header);\nextern Compressor *delta_delta_compressor_for_type(Oid element_type);\nextern DeltaDeltaCompressor *delta_delta_compressor_alloc(void);\nextern void delta_delta_compressor_append_null(DeltaDeltaCompressor *compressor);\nextern void delta_delta_compressor_append_value(DeltaDeltaCompressor *compressor, int64 next_val);\nextern void *delta_delta_compressor_finish(DeltaDeltaCompressor *compressor);\nextern size_t delta_delta_compressor_compressed_size(DeltaDeltaCompressor *compressor,\n\t\t\t\t\t\t\t\t\t\t\t\t\t size_t *nulls_size /* out */);\nextern void *delta_delta_compressor_finish_into(DeltaDeltaCompressor *compressor, void *dest);\n\nextern DecompressionIterator *\ndelta_delta_decompression_iterator_from_datum_forward(Datum deltadelta_compressed,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  Oid element_type);\nextern DecompressionIterator *\ndelta_delta_decompression_iterator_from_datum_reverse(Datum deltadelta_compressed,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  Oid element_type);\nextern DecompressResult\ndelta_delta_decompression_iterator_try_next_forward(DecompressionIterator *iter);\n\nextern ArrowArray *delta_delta_decompress_all(Datum compressed_data, Oid element_type,\n\t\t\t\t\t\t\t\t\t\t\t  MemoryContext dest_mctx);\n\nextern DecompressResult\ndelta_delta_decompression_iterator_try_next_reverse(DecompressionIterator *iter);\n\nextern void deltadelta_compressed_send(CompressedDataHeader *header, StringInfo buffer);\nextern Datum deltadelta_compressed_recv(StringInfo buf);\n\nextern Datum tsl_deltadelta_compressor_append(PG_FUNCTION_ARGS);\nextern Datum tsl_deltadelta_compressor_finish(PG_FUNCTION_ARGS);\n\n#define DELTA_DELTA_ALGORITHM_DEFINITION                                                           \\\n\t{                                                                                              \\\n\t\t.iterator_init_forward = delta_delta_decompression_iterator_from_datum_forward,            \\\n\t\t.iterator_init_reverse = delta_delta_decompression_iterator_from_datum_reverse,            \\\n\t\t.decompress_all = delta_delta_decompress_all,                                              \\\n\t\t.compressed_data_send = deltadelta_compressed_send,                                        \\\n\t\t.compressed_data_recv = deltadelta_compressed_recv,                                        \\\n\t\t.compressor_for_type = delta_delta_compressor_for_type,                                    \\\n\t\t.compressed_data_storage = TOAST_STORAGE_EXTERNAL,                                         \\\n\t}\n"
  },
  {
    "path": "tsl/src/compression/algorithms/deltadelta_impl.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * Decompress the entire batch of deltadelta-compressed rows into an Arrow array.\n * Specialized for each supported data type.\n */\n\n#define FUNCTION_NAME_HELPER(X, Y) X##_##Y\n#define FUNCTION_NAME(X, Y) FUNCTION_NAME_HELPER(X, Y)\n\nstatic ArrowArray *\nFUNCTION_NAME(delta_delta_decompress_all, ELEMENT_TYPE)(Datum compressed, MemoryContext dest_mctx)\n{\n\tStringInfoData si = { .data = DatumGetPointer(compressed), .len = VARSIZE(compressed) };\n\tDeltaDeltaCompressed *header = consumeCompressedData(&si, sizeof(DeltaDeltaCompressed));\n\tSimple8bRleSerialized *deltas_compressed = bytes_deserialize_simple8b_and_advance(&si);\n\n\tconst bool has_nulls = header->has_nulls == 1;\n\n\tAssert(header->has_nulls == 0 || header->has_nulls == 1);\n\n\t/*\n\t * Can't use element type here because of zig-zag encoding. The deltas are\n\t * computed in uint64, so we can get a delta that is actually larger than\n\t * the element type. We can't just truncate the delta either, because it\n\t * will lead to broken decompression results. The test case is in\n\t * test_delta4().\n\t */\n\tuint32 num_deltas;\n\tconst uint64 *deltas_zigzag = simple8brle_decompress_all_uint64(deltas_compressed, &num_deltas);\n\n\tSimple8bRleBitmap nulls = { 0 };\n\tif (has_nulls)\n\t{\n\t\tSimple8bRleSerialized *nulls_compressed = bytes_deserialize_simple8b_and_advance(&si);\n\t\tnulls = simple8brle_bitmap_decompress(nulls_compressed);\n\t}\n\n\t/*\n\t * Pad the number of elements to multiple of 64 bytes if needed, so that we\n\t * can work in 64-byte blocks.\n\t */\n#define INNER_LOOP_SIZE_LOG2 3\n#define INNER_LOOP_SIZE (1 << INNER_LOOP_SIZE_LOG2)\n\tconst uint32 n_total = has_nulls ? nulls.num_elements : num_deltas;\n\tconst uint32 n_total_padded = pad_to_multiple(INNER_LOOP_SIZE, n_total);\n\tconst uint32 n_notnull = num_deltas;\n\tconst uint32 n_notnull_padded = pad_to_multiple(INNER_LOOP_SIZE, n_notnull);\n\tAssert(n_total_padded >= n_total);\n\tAssert(n_notnull_padded >= n_notnull);\n\tAssert(n_total >= n_notnull);\n\tAssert(n_total <= GLOBAL_MAX_ROWS_PER_COMPRESSION);\n\n\t/*\n\t * We need additional padding at the end of buffer, because the code that\n\t * converts the elements to postgres Datum always reads in 8 bytes.\n\t */\n\tconst int buffer_bytes = n_total_padded * sizeof(ELEMENT_TYPE) + 8;\n\tELEMENT_TYPE *restrict decompressed_values = MemoryContextAlloc(dest_mctx, buffer_bytes);\n\n\t/* Now fill the data w/o nulls. */\n\tELEMENT_TYPE current_delta = 0;\n\tELEMENT_TYPE current_element = 0;\n\t/*\n\t * Manual unrolling speeds up this loop by about 10%. clang vectorizes\n\t * the zig_zag_decode part, but not the double-prefix-sum part.\n\t *\n\t * Also tried using SIMD prefix sum from here twice:\n\t * https://en.algorithmica.org/hpc/algorithms/prefix/, it's slower.\n\t *\n\t * Also tried zig-zag decoding in a separate loop, seems to be slightly\n\t * slower, around the noise threshold.\n\t */\n\tAssert(n_notnull_padded % INNER_LOOP_SIZE == 0);\n\tfor (uint32 outer = 0; outer < n_notnull_padded; outer += INNER_LOOP_SIZE)\n\t{\n\t\tfor (uint32 inner = 0; inner < INNER_LOOP_SIZE; inner++)\n\t\t{\n\t\t\tcurrent_delta += zig_zag_decode(deltas_zigzag[outer + inner]);\n\t\t\tcurrent_element += current_delta;\n\t\t\tdecompressed_values[outer + inner] = current_element;\n\t\t}\n\t}\n#undef INNER_LOOP_SIZE_LOG2\n#undef INNER_LOOP_SIZE\n\n\tuint64 *restrict validity_bitmap = NULL;\n\tif (has_nulls)\n\t{\n\t\t/* Now move the data to account for nulls, and fill the validity bitmap. */\n\t\tconst int validity_bitmap_bytes = sizeof(uint64) * ((n_total + 64 - 1) / 64);\n\t\tvalidity_bitmap = MemoryContextAlloc(dest_mctx, validity_bitmap_bytes);\n\n\t\t/*\n\t\t * First, mark all data as valid, we will fill the nulls later if needed.\n\t\t * Note that the validity bitmap size is a multiple of 64 bits. We have to\n\t\t * fill the tail bits with zeros, because the corresponding elements are not\n\t\t * valid.\n\t\t *\n\t\t */\n\t\tmemset(validity_bitmap, 0xFF, validity_bitmap_bytes);\n\t\tif (n_total % 64)\n\t\t{\n\t\t\tconst uint64 tail_mask = ~0ULL >> (64 - n_total % 64);\n\t\t\tvalidity_bitmap[n_total / 64] &= tail_mask;\n\t\t}\n\n\t\t/*\n\t\t * The number of not-null elements we have must be consistent with the\n\t\t * nulls bitmap.\n\t\t */\n\t\tCheckCompressedData(n_notnull + simple8brle_bitmap_num_ones(&nulls) == n_total);\n\n\t\tint current_notnull_element = n_notnull - 1;\n\t\tfor (int i = n_total - 1; i >= 0; i--)\n\t\t{\n\t\t\tAssert(i >= current_notnull_element);\n\n\t\t\tif (simple8brle_bitmap_get_at(&nulls, i))\n\t\t\t{\n\t\t\t\tarrow_set_row_validity(validity_bitmap, i, false);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tAssert(current_notnull_element >= 0);\n\t\t\t\tdecompressed_values[i] = decompressed_values[current_notnull_element];\n\t\t\t\tcurrent_notnull_element--;\n\t\t\t}\n\t\t}\n\n\t\tAssert(current_notnull_element == -1);\n\t}\n\n\t/* Return the result. */\n\tArrowArray *result = MemoryContextAllocZero(dest_mctx, sizeof(ArrowArray) + sizeof(void *) * 2);\n\tconst void **buffers = (const void **) &result[1];\n\tbuffers[0] = validity_bitmap;\n\tbuffers[1] = decompressed_values;\n\tresult->n_buffers = 2;\n\tresult->buffers = buffers;\n\tresult->length = n_total;\n\tresult->null_count = n_total - n_notnull;\n\treturn result;\n}\n\n#undef FUNCTION_NAME\n#undef FUNCTION_NAME_HELPER\n"
  },
  {
    "path": "tsl/src/compression/algorithms/dictionary.c",
    "content": "\n/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/htup_details.h>\n#include <access/tupmacs.h>\n#include <catalog/namespace.h>\n#include <catalog/pg_aggregate.h>\n#include <catalog/pg_type.h>\n#include <common/base64.h>\n#include <funcapi.h>\n#include <lib/stringinfo.h>\n#include <utils/builtins.h>\n#include <utils/datum.h>\n#include <utils/lsyscache.h>\n#include <utils/syscache.h>\n#include <utils/typcache.h>\n#include <utils/uuid.h>\n\n#include \"array.h\"\n#include \"compression/arrow_c_data_interface.h\"\n#include \"compression/compression.h\"\n#include \"datum_serialize.h\"\n#include \"dictionary.h\"\n#include \"dictionary_hash.h\"\n#include \"guc.h\"\n#include \"simple8b_rle.h\"\n#include \"simple8b_rle_bitarray.h\"\n#include \"simple8b_rle_bitmap.h\"\n\n/*\n * A compression bitmap is stored as\n *     bool has_nulls\n *     padding\n *     Oid element_type: the element stored by this compressed dictionary\n *     uint32 num_distinct: the number of distinct values\n *     simple8b_rle dictionary indexes: array of mappings from row to index into dictionary items\n * ArrayCompressed simple8b_rle nulls (optional) ArrayCompressed dictionary items\n */\ntypedef struct DictionaryCompressed\n{\n\tCompressedDataHeaderFields;\n\tuint8 has_nulls;\n\tuint8 padding[2];\n\tOid element_type;\n\tuint32 num_distinct;\n\t/* 8-byte alignment sentinel for the following fields */\n\tuint64 alignment_sentinel[FLEXIBLE_ARRAY_MEMBER];\n} DictionaryCompressed;\n\nbool\ndictionary_compressed_has_nulls(const CompressedDataHeader *header)\n{\n\tconst DictionaryCompressed *dc = (const DictionaryCompressed *) header;\n\treturn dc->has_nulls;\n}\n\nstatic void\npg_attribute_unused() assertions(void)\n{\n\tDictionaryCompressed test_val;\n\t/* make sure no padding bytes make it to disk */\n\tStaticAssertStmt(sizeof(DictionaryCompressed) ==\n\t\t\t\t\t\t sizeof(test_val.vl_len_) + sizeof(test_val.compression_algorithm) +\n\t\t\t\t\t\t\t sizeof(test_val.has_nulls) + sizeof(test_val.padding) +\n\t\t\t\t\t\t\t sizeof(test_val.element_type) + sizeof(test_val.num_distinct),\n\t\t\t\t\t \"CompressedDictionary wrong size\");\n\tStaticAssertStmt(sizeof(DictionaryCompressed) == 16, \"CompressedDictionary wrong size\");\n}\n\nstruct DictionaryDecompressionIterator\n{\n\tDecompressionIterator base;\n\tconst DictionaryCompressed *compressed;\n\tDatum *values;\n\tSimple8bRleDecompressionIterator bitmap;\n\tSimple8bRleDecompressionIterator nulls;\n\tbool has_nulls;\n};\n\n//////////////////\n/// Compressor ///\n//////////////////\n\ntypedef struct DictionaryCompressor\n{\n\tdictionary_hash *dictionary_items;\n\tuint32 next_index;\n\tuint32 dict_val_size;\n\tOid type;\n\tint16 typlen;\n\tbool typbyval;\n\tchar typalign;\n\tbool has_nulls;\n\tDatumSerializer *serializer;\n\tSimple8bRleCompressor dictionary_indexes;\n\tSimple8bRleCompressor nulls;\n} DictionaryCompressor;\n\ntypedef struct ExtendedCompressor\n{\n\tCompressor base;\n\tDictionaryCompressor *internal;\n\tOid element_type;\n} ExtendedCompressor;\n\nstatic void\ndictionary_compressor_append_datum(Compressor *compressor, Datum val)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tif (extended->internal == NULL)\n\t\textended->internal = dictionary_compressor_alloc(extended->element_type);\n\n\tdictionary_compressor_append(extended->internal, val);\n}\n\nstatic void\ndictionary_compressor_append_null_value(Compressor *compressor)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tif (extended->internal == NULL)\n\t\textended->internal = dictionary_compressor_alloc(extended->element_type);\n\n\tdictionary_compressor_append_null(extended->internal);\n}\n\nstatic bool\ndictionary_compressor_is_full(Compressor *compressor, Datum val)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tif (extended->internal == NULL)\n\t\textended->internal = dictionary_compressor_alloc(extended->element_type);\n\n\tSize datum_size_and_align;\n\tDictionaryCompressor *dict_comp = (DictionaryCompressor *) extended->internal;\n\tif (datum_serializer_value_may_be_toasted(dict_comp->serializer))\n\t\tval = PointerGetDatum(PG_DETOAST_DATUM_PACKED(val));\n\n\tdatum_size_and_align =\n\t\tdatum_get_bytes_size(dict_comp->serializer, dict_comp->dict_val_size, val) -\n\t\tdict_comp->dict_val_size;\n\n\t/* If we can't fit new datum in the max size, we are full */\n\treturn (datum_size_and_align + dict_comp->dict_val_size) > MAX_ARRAY_COMPRESSOR_SIZE_BYTES;\n}\n\nstatic void *\ndictionary_compressor_finish_and_reset(Compressor *compressor)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tvoid *compressed = dictionary_compressor_finish(extended->internal);\n\tpfree(extended->internal);\n\textended->internal = NULL;\n\treturn compressed;\n}\n\nconst Compressor dictionary_compressor = {\n\t.append_val = dictionary_compressor_append_datum,\n\t.append_null = dictionary_compressor_append_null_value,\n\t.is_full = dictionary_compressor_is_full,\n\t.finish = dictionary_compressor_finish_and_reset,\n};\n\nCompressor *\ndictionary_compressor_for_type(Oid element_type)\n{\n\tExtendedCompressor *compressor = palloc(sizeof(*compressor));\n\t*compressor = (ExtendedCompressor){\n\t\t.base = dictionary_compressor,\n\t\t.element_type = element_type,\n\t};\n\treturn &compressor->base;\n}\n\nDictionaryCompressor *\ndictionary_compressor_alloc(Oid type)\n{\n\tDictionaryCompressor *compressor = palloc(sizeof(*compressor));\n\tTypeCacheEntry *tentry =\n\t\tlookup_type_cache(type, TYPECACHE_EQ_OPR_FINFO | TYPECACHE_HASH_PROC_FINFO);\n\n\tcompressor->next_index = 0;\n\tcompressor->dict_val_size = 0;\n\tcompressor->has_nulls = false;\n\tcompressor->type = type;\n\tcompressor->typlen = tentry->typlen;\n\tcompressor->typbyval = tentry->typbyval;\n\tcompressor->typalign = tentry->typalign;\n\n\tcompressor->dictionary_items = dictionary_hash_alloc(tentry);\n\tcompressor->serializer = create_datum_serializer(type);\n\n\tsimple8brle_compressor_init(&compressor->dictionary_indexes);\n\tsimple8brle_compressor_init(&compressor->nulls);\n\treturn compressor;\n}\n\nvoid\ndictionary_compressor_append_null(DictionaryCompressor *compressor)\n{\n\tcompressor->has_nulls = true;\n\tsimple8brle_compressor_append(&compressor->nulls, 1);\n}\n\nvoid\ndictionary_compressor_append(DictionaryCompressor *compressor, Datum val)\n{\n\tbool found;\n\tDictionaryHashItem *dict_item;\n\n\tAssert(compressor != NULL);\n\n\tif (datum_serializer_value_may_be_toasted(compressor->serializer))\n\t\tval = PointerGetDatum(PG_DETOAST_DATUM_PACKED(val));\n\n\tdict_item = dictionary_insert(compressor->dictionary_items, val, &found);\n\n\tif (!found)\n\t{\n\t\t// per_val->bitmap = roaring_dictionary_create();\n\t\tdict_item->index = compressor->next_index;\n\t\tdict_item->key = datumCopy(val, compressor->typbyval, compressor->typlen);\n\t\tAssert(compressor->next_index <= INT16_MAX - 1);\n\t\tcompressor->next_index += 1;\n\t}\n\n\tSize datum_size_and_align =\n\t\tdatum_get_bytes_size(compressor->serializer, compressor->dict_val_size, val) -\n\t\tcompressor->dict_val_size;\n\n\tcompressor->dict_val_size += datum_size_and_align;\n\n\tsimple8brle_compressor_append(&compressor->dictionary_indexes, dict_item->index);\n\tsimple8brle_compressor_append(&compressor->nulls, 0);\n}\n\ntypedef struct DictionaryCompressorSerializationInfo\n{\n\tSize bitmaps_size;\n\tSize nulls_size;\n\tSize dictionary_size;\n\tSize total_size;\n\tuint32 num_distinct;\n\tSimple8bRleSerialized *dictionary_compressed_indexes;\n\tSimple8bRleSerialized *compressed_nulls;\n\tDatum *value_array; /* same as dictionary_serialization_info just as a regular array */\n\tArrayCompressorSerializationInfo *dictionary_serialization_info;\n\tbool is_all_null;\n} DictionaryCompressorSerializationInfo;\n\nstatic DictionaryCompressorSerializationInfo\ncompressor_get_serialization_info(DictionaryCompressor *compressor)\n{\n\tSimple8bRleSerialized *dict_indexes =\n\t\tsimple8brle_compressor_finish(&compressor->dictionary_indexes);\n\tSimple8bRleSerialized *nulls = simple8brle_compressor_finish(&compressor->nulls);\n\tdictionary_iterator dictionary_item_iterator;\n\n\tArrayCompressor *array_comp = array_compressor_alloc(compressor->type);\n\n\t/* the total size is header size + bitmaps size + nulls? + data sizesize */\n\tDictionaryCompressorSerializationInfo sizes = { .dictionary_compressed_indexes = dict_indexes,\n\t\t\t\t\t\t\t\t\t\t\t\t\t.compressed_nulls = nulls,\n\t\t\t\t\t\t\t\t\t\t\t\t\t.value_array = palloc(compressor->next_index *\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  sizeof(Datum)) };\n\tSize header_size = sizeof(DictionaryCompressed);\n\n\tif (sizes.dictionary_compressed_indexes == NULL)\n\t\treturn (DictionaryCompressorSerializationInfo){ .is_all_null = true };\n\n\tsizes.bitmaps_size = simple8brle_serialized_total_size(dict_indexes);\n\tsizes.total_size = MAXALIGN(header_size) + sizes.bitmaps_size;\n\tif (compressor->has_nulls)\n\t\tsizes.nulls_size = simple8brle_serialized_total_size(nulls);\n\tsizes.total_size += sizes.nulls_size;\n\n\tdictionary_start_iterate(compressor->dictionary_items, &dictionary_item_iterator);\n\tsizes.num_distinct = 0;\n\tfor (DictionaryHashItem *dict_item =\n\t\t\t dictionary_iterate(compressor->dictionary_items, &dictionary_item_iterator);\n\t\t dict_item != NULL;\n\t\t dict_item = dictionary_iterate(compressor->dictionary_items, &dictionary_item_iterator))\n\t{\n\t\tsizes.value_array[dict_item->index] = dict_item->key;\n\t\tsizes.num_distinct += 1;\n\t}\n\tfor (uint32 i = 0; i < sizes.num_distinct; i++)\n\t{\n\t\tarray_compressor_append(array_comp, sizes.value_array[i]);\n\t}\n\tsizes.dictionary_serialization_info = array_compressor_get_serialization_info(array_comp);\n\tsizes.dictionary_size =\n\t\tarray_compression_serialization_size(sizes.dictionary_serialization_info);\n\tsizes.total_size += sizes.dictionary_size;\n\n\tif (!AllocSizeIsValid(sizes.total_size))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),\n\t\t\t\t errmsg(\"compressed size exceeds the maximum allowed (%d)\", (int) MaxAllocSize)));\n\treturn sizes;\n}\n\nstatic DictionaryCompressed *\ndictionary_compressed_from_serialization_info(DictionaryCompressorSerializationInfo sizes,\n\t\t\t\t\t\t\t\t\t\t\t  Oid element_type)\n{\n\tchar *data = palloc0(sizes.total_size);\n\tDictionaryCompressed *bitmap = (DictionaryCompressed *) data;\n\tSET_VARSIZE(bitmap->vl_len_, sizes.total_size);\n\n\tbitmap->compression_algorithm = COMPRESSION_ALGORITHM_DICTIONARY;\n\tbitmap->element_type = element_type;\n\tbitmap->has_nulls = sizes.nulls_size > 0 ? 1 : 0;\n\tbitmap->num_distinct = sizes.num_distinct;\n\n\tdata = data + sizeof(DictionaryCompressed);\n\tdata = bytes_serialize_simple8b_and_advance(data,\n\t\t\t\t\t\t\t\t\t\t\t\tsizes.bitmaps_size,\n\t\t\t\t\t\t\t\t\t\t\t\tsizes.dictionary_compressed_indexes);\n\n\tif (bitmap->has_nulls)\n\t\tdata = bytes_serialize_simple8b_and_advance(data, sizes.nulls_size, sizes.compressed_nulls);\n\n\tdata = bytes_serialize_array_compressor_and_advance(data,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsizes.dictionary_size,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsizes.dictionary_serialization_info);\n\n\tAssert((Size) (data - (char *) bitmap) == sizes.total_size);\n\treturn bitmap;\n}\n\nstatic void dictionary_decompression_iterator_init(DictionaryDecompressionIterator *iter,\n\t\t\t\t\t\t\t\t\t\t\t\t   const char *data, bool scan_forward,\n\t\t\t\t\t\t\t\t\t\t\t\t   Oid element_type);\n\n/* there are more efficient ways to do this that use\n * DictionaryCompressorSerializationInfo, but they are not worth implementing\n * yet\n */\nstatic ArrayCompressed *\ndictionary_compressed_to_array_compressed(DictionaryCompressed *compressed)\n{\n\tArrayCompressor *compressor = array_compressor_alloc(compressed->element_type);\n\tDictionaryDecompressionIterator iterator;\n\tdictionary_decompression_iterator_init(&iterator,\n\t\t\t\t\t\t\t\t\t\t   (void *) compressed,\n\t\t\t\t\t\t\t\t\t\t   true,\n\t\t\t\t\t\t\t\t\t\t   compressed->element_type);\n\n\tfor (DecompressResult res = dictionary_decompression_iterator_try_next_forward(&iterator.base);\n\t\t !res.is_done;\n\t\t res = dictionary_decompression_iterator_try_next_forward(&iterator.base))\n\t{\n\t\tif (res.is_null)\n\t\t\tarray_compressor_append_null(compressor);\n\t\telse\n\t\t\tarray_compressor_append(compressor, res.val);\n\t}\n\n\treturn array_compressor_finish(compressor);\n}\n\nvoid *\ndictionary_compressor_finish(DictionaryCompressor *compressor)\n{\n\tuint64 average_element_size;\n\tuint64 expected_array_size;\n\tDictionaryCompressed *compressed;\n\tDictionaryCompressorSerializationInfo sizes = compressor_get_serialization_info(compressor);\n\tif (sizes.is_all_null)\n\t\treturn NULL;\n\n\tAssert(0 != sizes.num_distinct);\n\n\t/* calculate what the expected size would have be if we recompressed this as\n\t * an array, if this is smaller than the current size, recompress as an array.\n\t */\n\taverage_element_size = sizes.dictionary_size / sizes.num_distinct;\n\texpected_array_size = average_element_size * sizes.dictionary_compressed_indexes->num_elements;\n\tcompressed = dictionary_compressed_from_serialization_info(sizes, compressor->type);\n\tif (expected_array_size < sizes.total_size)\n\t\treturn dictionary_compressed_to_array_compressed(compressed);\n\n\treturn compressed;\n}\n\n////////////////////\n/// Decompressor ///\n////////////////////\n\nstatic void\ndictionary_decompression_iterator_init(DictionaryDecompressionIterator *iter, const char *_data,\n\t\t\t\t\t\t\t\t\t   bool scan_forward, Oid element_type)\n{\n\tStringInfoData si = { .data = (char *) _data, .len = VARSIZE(_data) };\n\tconst DictionaryCompressed *bitmap = consumeCompressedData(&si, sizeof(DictionaryCompressed));\n\n\tSimple8bRleSerialized *s8_bitmap;\n\tDecompressionIterator *dictionary_iterator;\n\n\t*iter = (DictionaryDecompressionIterator){\n\t\t.base = {\n\t\t\t.compression_algorithm = COMPRESSION_ALGORITHM_DICTIONARY,\n\t\t\t.forward = scan_forward,\n\t\t\t.element_type = element_type,\n\t\t\t.try_next = (scan_forward ? dictionary_decompression_iterator_try_next_forward : dictionary_decompression_iterator_try_next_reverse),\n\t\t},\n\t\t.compressed = bitmap,\n\t\t.values = palloc(sizeof(Datum) * bitmap->num_distinct),\n\t\t.has_nulls = bitmap->has_nulls == 1,\n\t};\n\n\ts8_bitmap = bytes_deserialize_simple8b_and_advance(&si);\n\n\tif (scan_forward)\n\t\tsimple8brle_decompression_iterator_init_forward(&iter->bitmap, s8_bitmap);\n\telse\n\t\tsimple8brle_decompression_iterator_init_reverse(&iter->bitmap, s8_bitmap);\n\n\tif (iter->has_nulls)\n\t{\n\t\tSimple8bRleSerialized *s8_null = bytes_deserialize_simple8b_and_advance(&si);\n\t\tif (scan_forward)\n\t\t\tsimple8brle_decompression_iterator_init_forward(&iter->nulls, s8_null);\n\t\telse\n\t\t\tsimple8brle_decompression_iterator_init_reverse(&iter->nulls, s8_null);\n\t}\n\n\tdictionary_iterator = array_decompression_iterator_alloc_forward(&si,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t bitmap->element_type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t /* has_nulls */ false);\n\n\tfor (uint32 i = 0; i < bitmap->num_distinct; i++)\n\t{\n\t\tDecompressResult res = array_decompression_iterator_try_next_forward(dictionary_iterator);\n\t\tAssert(!res.is_null);\n\t\tAssert(!res.is_done);\n\t\titer->values[i] = res.val;\n\t}\n\tAssert(array_decompression_iterator_try_next_forward(dictionary_iterator).is_done);\n}\n\nstatic ArrowArray *tsl_bool_dictionary_decompress_all(Datum compressed, Oid element_type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  MemoryContext dest_mctx);\nstatic ArrowArray *tsl_text_dictionary_decompress_all(Datum compressed, Oid element_type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  MemoryContext dest_mctx);\nstatic ArrowArray *tsl_uuid_dictionary_decompress_all(Datum compressed, Oid element_type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  MemoryContext dest_mctx);\n\n/* Pass through to the specialized functions below for BOOL, TEXT and UUID */\nArrowArray *\ntsl_dictionary_decompress_all(Datum compressed, Oid element_type, MemoryContext dest_mctx)\n{\n\tswitch (element_type)\n\t{\n\t\tcase BOOLOID:\n\t\t\treturn tsl_bool_dictionary_decompress_all(compressed, element_type, dest_mctx);\n\t\tcase TEXTOID:\n\t\t\treturn tsl_text_dictionary_decompress_all(compressed, element_type, dest_mctx);\n\t\tcase UUIDOID:\n\t\t\treturn tsl_uuid_dictionary_decompress_all(compressed, element_type, dest_mctx);\n\t\tdefault:\n\t\t\telog(ERROR, \"unsupported dictionary type %u for bulk decompression\", element_type);\n\t\t\tbreak;\n\t}\n\treturn NULL;\n}\n\nstatic ArrowArray *\ntsl_bool_dictionary_decompress_all(Datum compressed, Oid element_type, MemoryContext dest_mctx)\n{\n\tAssert(element_type == BOOLOID);\n\n\tcompressed = PointerGetDatum(PG_DETOAST_DATUM(compressed));\n\tStringInfoData si = { .data = DatumGetPointer(compressed), .len = VARSIZE(compressed) };\n\tconst DictionaryCompressed *header = consumeCompressedData(&si, sizeof(DictionaryCompressed));\n\n\tAssert(header->compression_algorithm == COMPRESSION_ALGORITHM_DICTIONARY);\n\tCheckCompressedData(header->element_type == BOOLOID);\n\n\tSimple8bRleSerialized *indices_serialized = bytes_deserialize_simple8b_and_advance(&si);\n\n\tSimple8bRleSerialized *nulls_serialized = NULL;\n\tif (header->has_nulls)\n\t{\n\t\tnulls_serialized = bytes_deserialize_simple8b_and_advance(&si);\n\t}\n\n\tconst uint32 n_notnull = indices_serialized->num_elements;\n\tconst uint32 n_total = header->has_nulls ? nulls_serialized->num_elements : n_notnull;\n\tconst uint32 n_padded_bits = n_total + 63;\n\tconst uint32 n_padded_bytes = n_padded_bits / 8;\n\n\tuint64 *validity_bitmap = NULL;\n\tuint64 *values = MemoryContextAllocZero(dest_mctx, n_padded_bytes);\n\n\tMemoryContext old_context = MemoryContextSwitchTo(dest_mctx);\n\t/* Decompress the nulls */\n\tSimple8bRleBitArray validity_bits =\n\t\tsimple8brle_bitarray_decompress(nulls_serialized, /* inverted*/ true);\n\tvalidity_bitmap = validity_bits.data;\n\tMemoryContextSwitchTo(old_context);\n\n\tif (header->has_nulls)\n\t{\n\t\tCheckCompressedData(validity_bits.num_ones == n_notnull);\n\t\tCheckCompressedData(validity_bits.num_elements == n_total);\n\t}\n\n\t/* Decompress the values using the iterator based decompressor */\n\t{\n\t\tint position = 0;\n\t\tDecompressionIterator *iter =\n\t\t\ttsl_dictionary_decompression_iterator_from_datum_forward(compressed, BOOLOID);\n\t\tfor (DecompressResult r = dictionary_decompression_iterator_try_next_forward(iter);\n\t\t\t !r.is_done;\n\t\t\t r = dictionary_decompression_iterator_try_next_forward(iter))\n\t\t{\n\t\t\tif (!r.is_null)\n\t\t\t{\n\t\t\t\tbool data = DatumGetBool(r.val) == true;\n\t\t\t\tif (data)\n\t\t\t\t{\n\t\t\t\t\tarrow_set_row_validity(values, position, true);\n\t\t\t\t}\n\t\t\t}\n\t\t\t++position;\n\t\t}\n\t}\n\n\tArrowArray *result =\n\t\tMemoryContextAllocZero(dest_mctx, sizeof(ArrowArray) + (sizeof(void *) * 2));\n\tconst void **buffers = (const void **) &result[1];\n\tbuffers[0] = validity_bitmap;\n\tbuffers[1] = values;\n\tresult->n_buffers = 2;\n\tresult->buffers = buffers;\n\tresult->length = n_total;\n\tresult->null_count = n_total - n_notnull;\n\treturn result;\n}\n\n#define ELEMENT_TYPE int16\n#include \"simple8b_rle_decompress_all.h\"\n#undef ELEMENT_TYPE\n\nstatic ArrowArray *\ntsl_uuid_dictionary_decompress_all(Datum compressed, Oid element_type, MemoryContext dest_mctx)\n{\n\tAssert(element_type == UUIDOID);\n\n\tcompressed = PointerGetDatum(PG_DETOAST_DATUM(compressed));\n\tStringInfoData si = { .data = DatumGetPointer(compressed), .len = VARSIZE_ANY(compressed) };\n\tconst DictionaryCompressed *header = consumeCompressedData(&si, sizeof(DictionaryCompressed));\n\n\tAssert(header->compression_algorithm == COMPRESSION_ALGORITHM_DICTIONARY);\n\tCheckCompressedData(header->element_type == UUIDOID);\n\n\tSimple8bRleSerialized *indices_serialized = bytes_deserialize_simple8b_and_advance(&si);\n\n\tSimple8bRleSerialized *nulls_serialized = NULL;\n\tif (header->has_nulls)\n\t{\n\t\tnulls_serialized = bytes_deserialize_simple8b_and_advance(&si);\n\t}\n\n\tconst uint32 n_notnull = indices_serialized->num_elements;\n\tconst uint32 n_total = header->has_nulls ? nulls_serialized->num_elements : n_notnull;\n\tconst uint32 n_bytes = n_total * 16;\n\n\tuint64 *restrict validity_bitmap = NULL;\n\tuint64 *restrict values = MemoryContextAllocZero(dest_mctx, n_bytes);\n\n\tMemoryContext old_context = MemoryContextSwitchTo(dest_mctx);\n\t/* Decompress the nulls */\n\tSimple8bRleBitArray validity_bits =\n\t\tsimple8brle_bitarray_decompress(nulls_serialized, /* inverted*/ true);\n\tvalidity_bitmap = validity_bits.data;\n\tMemoryContextSwitchTo(old_context);\n\n\tif (header->has_nulls)\n\t{\n\t\tCheckCompressedData(validity_bits.num_ones == n_notnull);\n\t\tCheckCompressedData(validity_bits.num_elements == n_total);\n\t}\n\n\t/* create a context so I can throw away all temp data in one step */\n\tMemoryContext temp_context = AllocSetContextCreate(CurrentMemoryContext,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   \"tsl_uuid_dictionary_decompress_all\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t   ALLOCSET_DEFAULT_SIZES);\n\n\t/* This is the padding requirement of simple8brle_decompress_all. */\n\tconst uint32 n_padded = n_total + 63;\n\n\tint16 *restrict indices = MemoryContextAlloc(temp_context, sizeof(int16) * n_padded);\n\n\tconst uint32 n_decompressed =\n\t\tsimple8brle_decompress_all_buf_int16(indices_serialized, indices, n_padded);\n\tCheckCompressedData(n_decompressed == n_notnull);\n\n\t/* Don't care about the sizes stored in the Array, just skip over them. */\n\tSimple8bRleSerialized *sizes_serialized = bytes_deserialize_simple8b_and_advance(&si);\n\tCheckCompressedData(sizes_serialized->num_elements == header->num_distinct);\n\n\t/* Verify that the remaining size has enough space for the values */\n\tCheckCompressedData((uint32) ((si.len - si.cursor) / 16) >= header->num_distinct);\n\tuint64 *restrict dict_values = (uint64 *) (si.data + si.cursor);\n\n\t{\n\t\tint position = 0;\n\t\tfor (uint32 i = 0; i < n_total; ++i)\n\t\t{\n\t\t\tif (arrow_row_is_valid(validity_bitmap, i))\n\t\t\t{\n\t\t\t\tint16 idx = indices[position];\n\t\t\t\t/* Check that the dictionary indices that we've just read are not out of bounds. */\n\t\t\t\tCheckCompressedData(idx >= 0 && idx < (int16) header->num_distinct);\n\n\t\t\t\t/* Use assignment as both sides are coming from palloc, so it is guaranteed to be\n\t\t\t\t * aligned */\n\t\t\t\tvalues[i * 2] = dict_values[idx * 2];\n\t\t\t\tvalues[i * 2 + 1] = dict_values[idx * 2 + 1];\n\t\t\t\tposition++;\n\t\t\t}\n\t\t}\n\t}\n\n\tMemoryContextDelete(temp_context);\n\n\tArrowArray *result =\n\t\tMemoryContextAllocZero(dest_mctx, sizeof(ArrowArray) + (sizeof(void *) * 2));\n\tconst void **buffers = (const void **) &result[1];\n\tbuffers[0] = validity_bitmap;\n\tbuffers[1] = values;\n\tresult->n_buffers = 2;\n\tresult->buffers = buffers;\n\tresult->length = n_total;\n\tresult->null_count = n_total - n_notnull;\n\treturn result;\n}\n\nstatic ArrowArray *\ntsl_text_dictionary_decompress_all(Datum compressed, Oid element_type, MemoryContext dest_mctx)\n{\n\tAssert(element_type == TEXTOID);\n\n\tcompressed = PointerGetDatum(PG_DETOAST_DATUM(compressed));\n\n\tStringInfoData si = { .data = DatumGetPointer(compressed), .len = VARSIZE(compressed) };\n\n\tconst DictionaryCompressed *header = consumeCompressedData(&si, sizeof(DictionaryCompressed));\n\n\tAssert(header->compression_algorithm == COMPRESSION_ALGORITHM_DICTIONARY);\n\tCheckCompressedData(header->element_type == TEXTOID);\n\n\tSimple8bRleSerialized *indices_serialized = bytes_deserialize_simple8b_and_advance(&si);\n\n\tSimple8bRleSerialized *nulls_serialized = NULL;\n\tif (header->has_nulls)\n\t{\n\t\tnulls_serialized = bytes_deserialize_simple8b_and_advance(&si);\n\t}\n\n\tconst uint32 n_notnull = indices_serialized->num_elements;\n\tconst uint32 n_total = header->has_nulls ? nulls_serialized->num_elements : n_notnull;\n\tCheckCompressedData(n_total >= n_notnull);\n\tconst uint32 n_padded =\n\t\tn_total + 63; /* This is the padding requirement of simple8brle_decompress_all. */\n\tint16 *restrict indices = MemoryContextAlloc(dest_mctx, sizeof(int16) * n_padded);\n\n\tconst uint32 n_decompressed =\n\t\tsimple8brle_decompress_all_buf_int16(indices_serialized, indices, n_padded);\n\tCheckCompressedData(n_decompressed == n_notnull);\n\n\t/* Check that the dictionary indices that we've just read are not out of bounds. */\n\tCheckCompressedData(header->num_distinct <= GLOBAL_MAX_ROWS_PER_COMPRESSION);\n\t/* We use signed indexes as recommended by the Arrow spec. */\n\tCheckCompressedData(header->num_distinct <= INT16_MAX);\n\tbool have_incorrect_index = false;\n\tfor (uint32 i = 0; i < n_notnull; i++)\n\t{\n\t\thave_incorrect_index = have_incorrect_index || indices[i] >= (int16) header->num_distinct;\n\t}\n\tCheckCompressedData(!have_incorrect_index);\n\n\t/* Decompress the actual values in the dictionary. */\n\tArrowArray *dict =\n\t\ttext_array_decompress_all_serialized_no_header(&si, /* has_nulls = */ false, dest_mctx);\n\tCheckCompressedData(header->num_distinct == dict->length);\n\n\tuint64 *restrict validity_bitmap = NULL;\n\tif (header->has_nulls)\n\t{\n\t\t/* Fill validity and indices of the array elements, reshuffling for nulls if needed. */\n\t\tconst int validity_bitmap_bytes = sizeof(uint64) * pad_to_multiple(64, n_total) / 64;\n\t\tvalidity_bitmap = MemoryContextAlloc(dest_mctx, validity_bitmap_bytes);\n\n\t\t/*\n\t\t * First, mark all data as valid, we will fill the nulls later if needed.\n\t\t * Note that the validity bitmap size is a multiple of 64 bits. We have to\n\t\t * fill the tail bits with zeros, because the corresponding elements are not\n\t\t * valid.\n\t\t *\n\t\t */\n\t\tmemset(validity_bitmap, 0xFF, validity_bitmap_bytes);\n\t\tif (n_total % 64)\n\t\t{\n\t\t\tconst uint64 tail_mask = ~0ULL >> (64 - n_total % 64);\n\t\t\tvalidity_bitmap[n_total / 64] &= tail_mask;\n\t\t}\n\n\t\t/*\n\t\t * We have decompressed the data with nulls skipped, reshuffle it\n\t\t * according to the nulls bitmap.\n\t\t */\n\t\tSimple8bRleBitmap nulls = simple8brle_bitmap_decompress(nulls_serialized);\n\t\tCheckCompressedData(n_notnull + simple8brle_bitmap_num_ones(&nulls) == n_total);\n\n\t\t/* current_notnull_element needs to go below 0, so use signed type */\n\t\tint64 current_notnull_element = n_notnull - 1;\n\t\tfor (int64 i = n_total - 1; i >= 0; i--)\n\t\t{\n\t\t\tAssert(i >= current_notnull_element);\n\n\t\t\tif (simple8brle_bitmap_get_at(&nulls, i))\n\t\t\t{\n\t\t\t\tarrow_set_row_validity(validity_bitmap, i, false);\n\t\t\t\tindices[i] = 0;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tAssert(current_notnull_element >= 0);\n\t\t\t\tindices[i] = indices[current_notnull_element];\n\t\t\t\tcurrent_notnull_element--;\n\t\t\t}\n\t\t}\n\n\t\tAssert(current_notnull_element == -1);\n\t}\n\n\tArrowArray *result =\n\t\tMemoryContextAllocZero(dest_mctx, sizeof(ArrowArray) + (sizeof(void *) * 2));\n\tconst void **buffers = (const void **) &result[1];\n\tbuffers[0] = validity_bitmap;\n\tbuffers[1] = indices;\n\tresult->n_buffers = 2;\n\tresult->buffers = buffers;\n\tresult->length = n_total;\n\tresult->null_count = n_total - n_notnull;\n\tresult->dictionary = dict;\n\treturn result;\n}\n\nDecompressionIterator *\ntsl_dictionary_decompression_iterator_from_datum_forward(Datum dictionary_compressed,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t Oid element_type)\n{\n\tDictionaryDecompressionIterator *iterator = palloc(sizeof(*iterator));\n\tdictionary_decompression_iterator_init(iterator,\n\t\t\t\t\t\t\t\t\t\t   (void *) PG_DETOAST_DATUM(dictionary_compressed),\n\t\t\t\t\t\t\t\t\t\t   true,\n\t\t\t\t\t\t\t\t\t\t   element_type);\n\treturn &iterator->base;\n}\n\nDecompressionIterator *\ntsl_dictionary_decompression_iterator_from_datum_reverse(Datum dictionary_compressed,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t Oid element_type)\n{\n\tDictionaryDecompressionIterator *iterator = palloc(sizeof(*iterator));\n\tdictionary_decompression_iterator_init(iterator,\n\t\t\t\t\t\t\t\t\t\t   (void *) PG_DETOAST_DATUM(dictionary_compressed),\n\t\t\t\t\t\t\t\t\t\t   false,\n\t\t\t\t\t\t\t\t\t\t   element_type);\n\treturn &iterator->base;\n}\n\nDecompressResult\ndictionary_decompression_iterator_try_next_forward(DecompressionIterator *iter_base)\n{\n\tDictionaryDecompressionIterator *iter;\n\tSimple8bRleDecompressResult result;\n\n\tAssert(iter_base->compression_algorithm == COMPRESSION_ALGORITHM_DICTIONARY &&\n\t\t   iter_base->forward);\n\titer = (DictionaryDecompressionIterator *) iter_base;\n\n\tif (iter->has_nulls)\n\t{\n\t\tSimple8bRleDecompressResult null =\n\t\t\tsimple8brle_decompression_iterator_try_next_forward(&iter->nulls);\n\t\tif (null.is_done)\n\t\t\treturn (DecompressResult){\n\t\t\t\t.is_done = true,\n\t\t\t};\n\n\t\tif ((null.val & 1) != 0)\n\t\t{\n\t\t\treturn (DecompressResult){\n\t\t\t\t.is_null = true,\n\t\t\t};\n\t\t}\n\t}\n\n\tresult = simple8brle_decompression_iterator_try_next_forward(&iter->bitmap);\n\tif (result.is_done)\n\t\treturn (DecompressResult){\n\t\t\t.is_done = true,\n\t\t};\n\n\tCheckCompressedData(result.val < iter->compressed->num_distinct);\n\treturn (DecompressResult){\n\t\t.val = iter->values[result.val],\n\t\t.is_null = false,\n\t\t.is_done = false,\n\t};\n}\n\nDecompressResult\ndictionary_decompression_iterator_try_next_reverse(DecompressionIterator *iter_base)\n{\n\tDictionaryDecompressionIterator *iter;\n\tSimple8bRleDecompressResult result;\n\n\tAssert(iter_base->compression_algorithm == COMPRESSION_ALGORITHM_DICTIONARY &&\n\t\t   !iter_base->forward);\n\titer = (DictionaryDecompressionIterator *) iter_base;\n\n\tif (iter->has_nulls)\n\t{\n\t\tSimple8bRleDecompressResult null =\n\t\t\tsimple8brle_decompression_iterator_try_next_reverse(&iter->nulls);\n\t\tif (null.is_done)\n\t\t\treturn (DecompressResult){\n\t\t\t\t.is_done = true,\n\t\t\t};\n\n\t\tif ((null.val & 1) != 0)\n\t\t{\n\t\t\treturn (DecompressResult){\n\t\t\t\t.is_null = true,\n\t\t\t};\n\t\t}\n\t}\n\n\tresult = simple8brle_decompression_iterator_try_next_reverse(&iter->bitmap);\n\tif (result.is_done)\n\t\treturn (DecompressResult){\n\t\t\t.is_done = true,\n\t\t};\n\n\tAssert(result.val < iter->compressed->num_distinct);\n\treturn (DecompressResult){\n\t\t.val = iter->values[result.val],\n\t\t.is_null = false,\n\t\t.is_done = false,\n\t};\n}\n\n/////////////////////\n/// SQL Functions ///\n/////////////////////\n\nDatum\ntsl_dictionary_compressor_append(PG_FUNCTION_ARGS)\n{\n\tDictionaryCompressor *compressor =\n\t\t(DictionaryCompressor *) (PG_ARGISNULL(0) ? NULL : PG_GETARG_POINTER(0));\n\tMemoryContext agg_context;\n\tMemoryContext old_context;\n\n\tif (!AggCheckCallContext(fcinfo, &agg_context))\n\t{\n\t\t/* cannot be called directly because of internal-type argument */\n\t\telog(ERROR, \"tsl_dictionary_compressor_append called in non-aggregate context\");\n\t}\n\n\told_context = MemoryContextSwitchTo(agg_context);\n\n\tif (compressor == NULL)\n\t{\n\t\tOid type_to_compress = get_fn_expr_argtype(fcinfo->flinfo, 1);\n\t\tcompressor = dictionary_compressor_alloc(type_to_compress);\n\t}\n\tif (PG_ARGISNULL(1))\n\t\tdictionary_compressor_append_null(compressor);\n\telse\n\t\tdictionary_compressor_append(compressor, PG_GETARG_DATUM(1));\n\n\tMemoryContextSwitchTo(old_context);\n\tPG_RETURN_POINTER(compressor);\n}\n\nDatum\ntsl_dictionary_compressor_finish(PG_FUNCTION_ARGS)\n{\n\tDictionaryCompressor *compressor =\n\t\t(DictionaryCompressor *) (PG_ARGISNULL(0) ? NULL : PG_GETARG_POINTER(0));\n\tvoid *compressed;\n\tif (compressor == NULL)\n\t\tPG_RETURN_NULL();\n\n\tcompressed = dictionary_compressor_finish(compressor);\n\tif (compressed == NULL)\n\t\tPG_RETURN_NULL();\n\n\tPG_RETURN_POINTER(compressed);\n}\n\n/////////////////////\n/// I/O Functions ///\n/////////////////////\n\nvoid\ndictionary_compressed_send(CompressedDataHeader *header, StringInfo buffer)\n{\n\tuint32 data_size;\n\tuint32 size;\n\tconst DictionaryCompressed *compressed_header;\n\tconst char *compressed_data;\n\n\tAssert(header->compression_algorithm == COMPRESSION_ALGORITHM_DICTIONARY);\n\tcompressed_header = (DictionaryCompressed *) header;\n\n\tcompressed_data = (char *) compressed_header;\n\n\tcompressed_data += sizeof(*compressed_header);\n\n\tdata_size = VARSIZE(compressed_header);\n\tdata_size -= sizeof(*compressed_header);\n\n\tpq_sendbyte(buffer, compressed_header->has_nulls == true);\n\n\ttype_append_to_binary_string(compressed_header->element_type, buffer);\n\n\tsize = simple8brle_serialized_total_size((void *) compressed_data);\n\tsimple8brle_serialized_send(buffer, (void *) compressed_data);\n\tcompressed_data += size;\n\tdata_size -= size;\n\n\tif (compressed_header->has_nulls)\n\t{\n\t\tuint32 size = simple8brle_serialized_total_size((void *) compressed_data);\n\t\tsimple8brle_serialized_send(buffer, (void *) compressed_data);\n\t\tcompressed_data += size;\n\t\tdata_size -= size;\n\t}\n\n\tarray_compressed_data_send(buffer,\n\t\t\t\t\t\t\t   compressed_data,\n\t\t\t\t\t\t\t   data_size,\n\t\t\t\t\t\t\t   compressed_header->element_type,\n\t\t\t\t\t\t\t   false);\n}\n\nDatum\ndictionary_compressed_recv(StringInfo buffer)\n{\n\tDictionaryCompressorSerializationInfo info = { 0 };\n\tuint8 has_nulls;\n\tOid element_type;\n\n\thas_nulls = pq_getmsgbyte(buffer);\n\tCheckCompressedData(has_nulls == 0 || has_nulls == 1);\n\n\telement_type = binary_string_get_type(buffer);\n\tinfo.dictionary_compressed_indexes = simple8brle_serialized_recv(buffer);\n\tinfo.bitmaps_size = simple8brle_serialized_total_size(info.dictionary_compressed_indexes);\n\tinfo.total_size = MAXALIGN(sizeof(DictionaryCompressed)) + info.bitmaps_size;\n\n\tif (has_nulls)\n\t{\n\t\tinfo.compressed_nulls = simple8brle_serialized_recv(buffer);\n\t\tinfo.nulls_size = simple8brle_serialized_total_size(info.compressed_nulls);\n\t\tinfo.total_size += info.nulls_size;\n\t}\n\n\tinfo.dictionary_serialization_info = array_compressed_data_recv(buffer, element_type);\n\n\tCheckCompressedData(info.dictionary_serialization_info != NULL);\n\n\tinfo.dictionary_size = array_compression_serialization_size(info.dictionary_serialization_info);\n\tinfo.total_size += info.dictionary_size;\n\tinfo.num_distinct =\n\t\tarray_compression_serialization_num_elements(info.dictionary_serialization_info);\n\n\tif (!AllocSizeIsValid(info.total_size))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),\n\t\t\t\t errmsg(\"compressed size exceeds the maximum allowed (%d)\", (int) MaxAllocSize)));\n\n\treturn PointerGetDatum(dictionary_compressed_from_serialization_info(info, element_type));\n}\n"
  },
  {
    "path": "tsl/src/compression/algorithms/dictionary.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n/*\n * The Dictionary compressions scheme can store any type of data but is optimized for\n * low-cardinality data sets. The dictionary of distinct items is stored as an `array` compressed\n * object. The row->dictionary item mapping is stored as a series of integer-based indexes into the\n * dictionary array ordered by row number (called dictionary_indexes; compressed using\n * `simple8b_rle`).\n */\n\n#include <postgres.h>\n#include \"compression/compression.h\"\n#include <lib/stringinfo.h>\n\n#include <fmgr.h>\n\ntypedef struct DictionaryCompressor DictionaryCompressor;\ntypedef struct DictionaryCompressed DictionaryCompressed;\ntypedef struct DictionaryDecompressionIterator DictionaryDecompressionIterator;\n\nextern bool dictionary_compressed_has_nulls(const CompressedDataHeader *header);\nextern Compressor *dictionary_compressor_for_type(Oid element_type);\nextern DictionaryCompressor *dictionary_compressor_alloc(Oid type_to_compress);\nextern void dictionary_compressor_append_null(DictionaryCompressor *compressor);\nextern void dictionary_compressor_append(DictionaryCompressor *compressor, Datum val);\nextern void *dictionary_compressor_finish(DictionaryCompressor *compressor);\n\nextern DecompressionIterator *\ntsl_dictionary_decompression_iterator_from_datum_forward(Datum dictionary_compressed,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t Oid element_type);\nextern DecompressResult\ndictionary_decompression_iterator_try_next_forward(DecompressionIterator *iter);\n\nextern DecompressionIterator *\ntsl_dictionary_decompression_iterator_from_datum_reverse(Datum dictionary_compressed,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t Oid element_type);\nextern DecompressResult\ndictionary_decompression_iterator_try_next_reverse(DecompressionIterator *iter);\n\nextern void dictionary_compressed_send(CompressedDataHeader *header, StringInfo buffer);\nextern Datum dictionary_compressed_recv(StringInfo buf);\n\nextern Datum tsl_dictionary_compressor_append(PG_FUNCTION_ARGS);\nextern Datum tsl_dictionary_compressor_finish(PG_FUNCTION_ARGS);\n\n/* Pass through to the specialized functions below for BOOL and TEXT */\nArrowArray *tsl_dictionary_decompress_all(Datum compressed, Oid element_type,\n\t\t\t\t\t\t\t\t\t\t  MemoryContext dest_mctx);\n\n#define DICTIONARY_ALGORITHM_DEFINITION                                                            \\\n\t{                                                                                              \\\n\t\t.iterator_init_forward = tsl_dictionary_decompression_iterator_from_datum_forward,         \\\n\t\t.iterator_init_reverse = tsl_dictionary_decompression_iterator_from_datum_reverse,         \\\n\t\t.compressed_data_send = dictionary_compressed_send,                                        \\\n\t\t.compressed_data_recv = dictionary_compressed_recv,                                        \\\n\t\t.compressor_for_type = dictionary_compressor_for_type,                                     \\\n\t\t.compressed_data_storage = TOAST_STORAGE_EXTENDED,                                         \\\n\t\t.decompress_all = tsl_dictionary_decompress_all,                                           \\\n\t}\n"
  },
  {
    "path": "tsl/src/compression/algorithms/dictionary_hash.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n/*\n * The Dictionary compressions scheme can store any type of data but is optimized for\n * low-cardinality data sets. The dictionary of distinct items is stored as an `array` compressed\n * object. The row->dictionary item mapping is stored as a series of integer-based indexes into the\n * dictionary array ordered by row number (called dictionary_indexes; compressed using\n * `simple8b_rle`).\n */\n#include <postgres.h>\n#include <funcapi.h>\n#include <utils/typcache.h>\n\n#include \"compat/compat.h\"\n\ntypedef struct HashMeta\n{\n\tFunctionCallInfo hash_info;\n\tFunctionCallInfo eq_info;\n} HashMeta;\n\ntypedef struct DictionaryHashItem\n{\n\tDatum key;\n\t/* hash entry status */\n\tuint32 hash;\n\tuint16 status;\n\tuint16 index;\n} DictionaryHashItem;\n\ntypedef struct dictionary_hash dictionary_hash;\nstatic uint32 datum_hash(dictionary_hash *tb, Datum key);\nstatic bool datum_eq(dictionary_hash *tb, Datum a, Datum b);\n\n#define SH_PREFIX dictionary\n#define SH_ELEMENT_TYPE DictionaryHashItem\n#define SH_KEY_TYPE Datum\n#define SH_KEY key\n#define SH_HASH_KEY(tb, key) datum_hash(tb, key)\n#define SH_EQUAL(tb, a, b) datum_eq(tb, a, b)\n#define SH_STORE_HASH\n#define SH_GET_HASH(tb, entry) entry->hash\n#define SH_SCOPE static inline\n#define SH_DEFINE\n#define SH_DECLARE\n#include \"lib/simplehash.h\"\n\nstatic uint32\ndatum_hash(dictionary_hash *tb, Datum key)\n{\n\tHashMeta *meta = (HashMeta *) tb->private_data;\n\tFunctionCallInfo fcinfo = meta->hash_info;\n\tDatum value;\n\n\tFC_SET_ARG(fcinfo, 0, key);\n\tfcinfo->isnull = false;\n\n\tvalue = FunctionCallInvoke(fcinfo);\n\tAssert(!fcinfo->isnull);\n\n\treturn DatumGetUInt32(value);\n}\n\nstatic bool\ndatum_eq(dictionary_hash *tb, Datum a, Datum b)\n{\n\tHashMeta *meta = (HashMeta *) tb->private_data;\n\tFunctionCallInfo fcinfo = meta->eq_info;\n\tDatum value;\n\n\tFC_SET_ARG(fcinfo, 0, a);\n\tFC_SET_ARG(fcinfo, 1, b);\n\tfcinfo->isnull = false;\n\n\tvalue = FunctionCallInvoke(fcinfo);\n\tAssert(!fcinfo->isnull);\n\n\treturn DatumGetBool(value);\n}\n\nstatic dictionary_hash *\ndictionary_hash_alloc(TypeCacheEntry *tentry)\n{\n\tHashMeta *meta = palloc(sizeof(*meta));\n\tOid collation = InvalidOid;\n\tcollation = tentry->typcollation;\n\n\tif (tentry->hash_proc_finfo.fn_addr == NULL || tentry->eq_opr_finfo.fn_addr == NULL)\n\t\telog(ERROR,\n\t\t\t \"invalid type for dictionary compression, type must have both a hash function and \"\n\t\t\t \"equality function\");\n\n\t/* May be more correct to get collation defined on the column, which may be different than the\n\t * collation defined on the type (what we're currently using). We need to think about\n\t * backwards compatibility, and different collations. Should only affect compression ratios\n\t * anyway.\n\t */\n\tmeta->eq_info = HEAP_FCINFO(2);\n\tInitFunctionCallInfoData(*meta->eq_info, &tentry->eq_opr_finfo, 2, collation, NULL, NULL);\n\n\tmeta->hash_info = HEAP_FCINFO(2);\n\tInitFunctionCallInfoData(*meta->hash_info, &tentry->hash_proc_finfo, 1, collation, NULL, NULL);\n\n\treturn dictionary_create(CurrentMemoryContext, 10, meta);\n}\n"
  },
  {
    "path": "tsl/src/compression/algorithms/float_utils.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\nstatic inline uint32\nfloat_get_bits(float in)\n{\n\tuint32 out;\n\tStaticAssertStmt(sizeof(float) == sizeof(uint32), \"float is not IEEE double wide float\");\n\t/* yes, this is the correct way to extract the bits of a floating point number in C */\n\tmemcpy(&out, &in, sizeof(uint32));\n\treturn out;\n}\n\nstatic pg_attribute_always_inline float\nbits_get_float(uint32 bits)\n{\n\tfloat out;\n\tStaticAssertStmt(sizeof(float) == sizeof(uint32), \"float is not IEEE double wide float\");\n\t/* yes, this is the correct way to extract the bits of a floating point number in C */\n\tmemcpy(&out, &bits, sizeof(uint32));\n\treturn out;\n}\n\nstatic inline uint64\ndouble_get_bits(double in)\n{\n\tuint64 out;\n\tStaticAssertStmt(sizeof(uint64) == sizeof(double), \"double is not IEEE double wide float\");\n\t/* yes, this is the correct way to extract the bits of a floating point number in C */\n\tmemcpy(&out, &in, sizeof(uint64));\n\treturn out;\n}\n\nstatic pg_attribute_always_inline double\nbits_get_double(uint64 bits)\n{\n\tdouble out;\n\tStaticAssertStmt(sizeof(uint64) == sizeof(double), \"double is not IEEE double wide float\");\n\t/* yes, this is the correct way to extract the bits of a floating point number in C */\n\tmemcpy(&out, &bits, sizeof(double));\n\treturn out;\n}\n"
  },
  {
    "path": "tsl/src/compression/algorithms/gorilla.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/htup_details.h>\n#include <catalog/pg_type.h>\n#include <common/base64.h>\n#include <funcapi.h>\n#include <lib/stringinfo.h>\n#include <libpq/pqformat.h>\n#include <port/pg_bitutils.h>\n#include <utils/builtins.h>\n#include <utils/memutils.h>\n\n#include \"gorilla.h\"\n\n#include \"compression/arrow_c_data_interface.h\"\n#include \"compression/compression.h\"\n#include \"float_utils.h\"\n#include \"guc.h\"\n#include \"simple8b_rle.h\"\n#include \"simple8b_rle_bitmap.h\"\n\n#include \"adts/bit_array.h\"\n\n/*\n * Gorilla compressed data is stored as\n *     uint16 compression_algorithm: id number for the compression scheme\n *     uint8 has_nulls: 1 if we store a NULLs bitmap after the data, otherwise 0\n *     uint8 bits_used_in_last_xor_bucket: number of bits used in the last bucket\n *     uint64 last_val: the last double stored, as bits\n *     simple8b_rle tag0: array of first tag bits (as in gorilla), also stores nelems\n *     simple8b_rle tag1: array of second tag bits (as in gorilla)\n *     BitArray leading_zeros: array of leading zeroes before the xor (as in gorilla)\n *     simple8b_rle num_bits_used: number of bits used for each xor (as in gorilla)\n *     BitArray xors: array xor values (as in gorilla)\n *     simple8b_rle nulls: 1 if the value is NULL, else 0\n */\n\ntypedef struct GorillaCompressed\n{\n\tCompressedDataHeaderFields;\n\tuint8 has_nulls; /* we only use one bit for has_nulls, the rest can be reused */\n\tuint8 bits_used_in_last_xor_bucket;\n\tuint8 bits_used_in_last_leading_zeros_bucket;\n\tuint32 num_leading_zeroes_buckets;\n\tuint32 num_xor_buckets;\n\tuint64 last_value;\n} GorillaCompressed;\n\n#define BITS_PER_LEADING_ZEROS 6\n\n/* expanded version of the compressed data */\ntypedef struct CompressedGorillaData\n{\n\tconst GorillaCompressed *header;\n\tSimple8bRleSerialized *tag0s;\n\tSimple8bRleSerialized *tag1s;\n\tBitArray leading_zeros;\n\tSimple8bRleSerialized *num_bits_used_per_xor;\n\tBitArray xors;\n\tSimple8bRleSerialized *nulls; /* NULL if no nulls */\n} CompressedGorillaData;\n\nbool\ngorilla_compressed_has_nulls(const CompressedDataHeader *header)\n{\n\tconst GorillaCompressed *gc = (const GorillaCompressed *) header;\n\treturn gc->has_nulls;\n}\n\nstatic void\npg_attribute_unused() assertions(void)\n{\n\tGorillaCompressed test_val = { .vl_len_ = { 0 } };\n\t/* make sure no padding bytes make it to disk */\n\tStaticAssertStmt(sizeof(GorillaCompressed) ==\n\t\t\t\t\t\t sizeof(test_val.vl_len_) + sizeof(test_val.compression_algorithm) +\n\t\t\t\t\t\t\t sizeof(test_val.has_nulls) +\n\t\t\t\t\t\t\t sizeof(test_val.bits_used_in_last_xor_bucket) +\n\t\t\t\t\t\t\t sizeof(test_val.bits_used_in_last_leading_zeros_bucket) +\n\t\t\t\t\t\t\t sizeof(test_val.num_leading_zeroes_buckets) +\n\t\t\t\t\t\t\t sizeof(test_val.num_xor_buckets) + sizeof(test_val.last_value),\n\t\t\t\t\t \"Gorilla wrong size\");\n\tStaticAssertStmt(sizeof(GorillaCompressed) == 24, \"Gorilla wrong size\");\n}\n\ntypedef struct GorillaCompressor\n{\n\t// NOTE it is a small win to replace these next two with specialized RLE bitmaps\n\tSimple8bRleCompressor tag0s;\n\tSimple8bRleCompressor tag1s;\n\tBitArray leading_zeros;\n\tSimple8bRleCompressor bits_used_per_xor;\n\tBitArray xors;\n\tSimple8bRleCompressor nulls;\n\n\tuint64 prev_val;\n\tuint8 prev_leading_zeroes;\n\tuint8 prev_trailing_zeros;\n\tbool has_nulls;\n} GorillaCompressor;\n\ntypedef struct ExtendedCompressor\n{\n\tCompressor base;\n\tGorillaCompressor *internal;\n} ExtendedCompressor;\n\ntypedef struct GorillaDecompressionIterator\n{\n\tDecompressionIterator base;\n\tCompressedGorillaData gorilla_data;\n\tSimple8bRleDecompressionIterator tag0s;\n\tSimple8bRleDecompressionIterator tag1s;\n\tBitArrayIterator leading_zeros;\n\tSimple8bRleDecompressionIterator num_bits_used;\n\tBitArrayIterator xors;\n\tSimple8bRleDecompressionIterator nulls;\n\tuint64 prev_val;\n\tuint8 prev_leading_zeroes;\n\tuint8 prev_xor_bits_used;\n\tbool has_nulls;\n} GorillaDecompressionIterator;\n\n/********************\n ***  Compressor  ***\n ********************/\n\nstatic void\ngorilla_compressor_append_float(Compressor *compressor, Datum val)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tuint64 value = float_get_bits(DatumGetFloat4(val));\n\tif (extended->internal == NULL)\n\t\textended->internal = gorilla_compressor_alloc();\n\n\tgorilla_compressor_append_value(extended->internal, value);\n}\n\nstatic void\ngorilla_compressor_append_double(Compressor *compressor, Datum val)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tuint64 value = double_get_bits(DatumGetFloat8(val));\n\tif (extended->internal == NULL)\n\t\textended->internal = gorilla_compressor_alloc();\n\n\tgorilla_compressor_append_value(extended->internal, value);\n}\n\nstatic void\ngorilla_compressor_append_int16(Compressor *compressor, Datum val)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tif (extended->internal == NULL)\n\t\textended->internal = gorilla_compressor_alloc();\n\n\tgorilla_compressor_append_value(extended->internal, (uint16) DatumGetInt16(val));\n}\n\nstatic void\ngorilla_compressor_append_int32(Compressor *compressor, Datum val)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tif (extended->internal == NULL)\n\t\textended->internal = gorilla_compressor_alloc();\n\n\tgorilla_compressor_append_value(extended->internal, (uint32) DatumGetInt32(val));\n}\n\nstatic void\ngorilla_compressor_append_int64(Compressor *compressor, Datum val)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tif (extended->internal == NULL)\n\t\textended->internal = gorilla_compressor_alloc();\n\n\tgorilla_compressor_append_value(extended->internal, DatumGetInt64(val));\n}\n\nstatic void\ngorilla_compressor_append_null_value(Compressor *compressor)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tif (extended->internal == NULL)\n\t\textended->internal = gorilla_compressor_alloc();\n\n\tgorilla_compressor_append_null(extended->internal);\n}\n\nstatic void *\ngorilla_compressor_finish_and_reset(Compressor *compressor)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tvoid *compressed = gorilla_compressor_finish(extended->internal);\n\tpfree(extended->internal);\n\textended->internal = NULL;\n\treturn compressed;\n}\n\nconst Compressor gorilla_float_compressor = {\n\t.append_val = gorilla_compressor_append_float,\n\t.append_null = gorilla_compressor_append_null_value,\n\t.is_full = NULL,\n\t.finish = gorilla_compressor_finish_and_reset,\n};\n\nconst Compressor gorilla_double_compressor = {\n\t.append_val = gorilla_compressor_append_double,\n\t.append_null = gorilla_compressor_append_null_value,\n\t.is_full = NULL,\n\t.finish = gorilla_compressor_finish_and_reset,\n};\nconst Compressor gorilla_uint16_compressor = {\n\t.append_val = gorilla_compressor_append_int16,\n\t.append_null = gorilla_compressor_append_null_value,\n\t.is_full = NULL,\n\t.finish = gorilla_compressor_finish_and_reset,\n};\nconst Compressor gorilla_uint32_compressor = {\n\t.append_val = gorilla_compressor_append_int32,\n\t.append_null = gorilla_compressor_append_null_value,\n\t.is_full = NULL,\n\t.finish = gorilla_compressor_finish_and_reset,\n};\nconst Compressor gorilla_uint64_compressor = {\n\t.append_val = gorilla_compressor_append_int64,\n\t.append_null = gorilla_compressor_append_null_value,\n\t.is_full = NULL,\n\t.finish = gorilla_compressor_finish_and_reset,\n};\n\nCompressor *\ngorilla_compressor_for_type(Oid element_type)\n{\n\tExtendedCompressor *compressor = palloc(sizeof(*compressor));\n\tswitch (element_type)\n\t{\n\t\tcase FLOAT4OID:\n\t\t\t*compressor = (ExtendedCompressor){ .base = gorilla_float_compressor };\n\t\t\treturn &compressor->base;\n\t\tcase FLOAT8OID:\n\t\t\t*compressor = (ExtendedCompressor){ .base = gorilla_double_compressor };\n\t\t\treturn &compressor->base;\n\t\tcase INT2OID:\n\t\t\t*compressor = (ExtendedCompressor){ .base = gorilla_uint16_compressor };\n\t\t\treturn &compressor->base;\n\t\tcase INT4OID:\n\t\t\t*compressor = (ExtendedCompressor){ .base = gorilla_uint32_compressor };\n\t\t\treturn &compressor->base;\n\t\tcase INT8OID:\n\t\t\t*compressor = (ExtendedCompressor){ .base = gorilla_uint64_compressor };\n\t\t\treturn &compressor->base;\n\t\tdefault:\n\t\t\telog(ERROR,\n\t\t\t\t \"invalid type for Gorilla compression \\\"%s\\\"\",\n\t\t\t\t format_type_be(element_type));\n\t}\n\tpg_unreachable();\n}\n\nGorillaCompressor *\ngorilla_compressor_alloc(void)\n{\n\tGorillaCompressor *compressor = palloc(sizeof(*compressor));\n\tsimple8brle_compressor_init(&compressor->tag0s);\n\tsimple8brle_compressor_init(&compressor->tag1s);\n\t/*\n\t * The number of leading zeros takes about 5 bits to encode, and changes\n\t * maybe every 100 rows, so use this as a conservative estimate.\n\t */\n\tbit_array_init(&compressor->leading_zeros,\n\t\t\t\t   /* expected_bits = */ (GLOBAL_MAX_ROWS_PER_COMPRESSION * 5) / 100);\n\tsimple8brle_compressor_init(&compressor->bits_used_per_xor);\n\t/*\n\t * We typically see about 12 bits or 4 decimal digits per row for the \"xors\"\n\t * part in gorilla compression.\n\t */\n\tbit_array_init(&compressor->xors,\n\t\t\t\t   /* expected_bits = */ GLOBAL_MAX_ROWS_PER_COMPRESSION * 12);\n\tsimple8brle_compressor_init(&compressor->nulls);\n\tcompressor->has_nulls = false;\n\tcompressor->prev_leading_zeroes = 0;\n\tcompressor->prev_trailing_zeros = 0;\n\tcompressor->prev_val = 0;\n\treturn compressor;\n}\n\n/* This function is used for testing only. */\nDatum\ntsl_gorilla_compressor_append(PG_FUNCTION_ARGS)\n{\n\tMemoryContext old_context;\n\tMemoryContext agg_context;\n\tCompressor *compressor = (Compressor *) (PG_ARGISNULL(0) ? NULL : PG_GETARG_POINTER(0));\n\n\tif (!AggCheckCallContext(fcinfo, &agg_context))\n\t{\n\t\t/* cannot be called directly because of internal-type argument */\n\t\telog(ERROR, \"tsl_gorilla_compressor_append called in non-aggregate context\");\n\t}\n\n\told_context = MemoryContextSwitchTo(agg_context);\n\n\tif (compressor == NULL)\n\t{\n\t\tcompressor = gorilla_compressor_for_type(get_fn_expr_argtype(fcinfo->flinfo, 1));\n\t}\n\n\tif (PG_ARGISNULL(1))\n\t\tcompressor->append_null(compressor);\n\telse\n\t{\n\t\tcompressor->append_val(compressor, PG_GETARG_DATUM(1));\n\t}\n\n\tMemoryContextSwitchTo(old_context);\n\tPG_RETURN_POINTER(compressor);\n}\n\n/* This function is used for testing only. */\nDatum\ntsl_gorilla_compressor_finish(PG_FUNCTION_ARGS)\n{\n\tCompressor *compressor = (Compressor *) (PG_ARGISNULL(0) ? NULL : PG_GETARG_POINTER(0));\n\n\tif (compressor == NULL)\n\t\tPG_RETURN_NULL();\n\n\tvoid *compressed = compressor->finish(compressor);\n\n\tif (compressed == NULL)\n\t\tPG_RETURN_NULL();\n\n\tPG_RETURN_POINTER(compressed);\n}\n\nvoid\ngorilla_compressor_append_null(GorillaCompressor *compressor)\n{\n\tsimple8brle_compressor_append(&compressor->nulls, 1);\n\tcompressor->has_nulls = true;\n}\n\nvoid\ngorilla_compressor_append_value(GorillaCompressor *compressor, uint64 val)\n{\n\tbool has_values;\n\tuint64 xor = compressor->prev_val ^ val;\n\tsimple8brle_compressor_append(&compressor->nulls, 0);\n\n\t/* for the first value we store the bitsize even if the xor is all zeroes,\n\t * this ensures that the bits-per-xor isn't empty, and that we can calculate\n\t * the remaining offsets correctly.\n\t */\n\thas_values = !simple8brle_compressor_is_empty(&compressor->bits_used_per_xor);\n\n\tif (has_values && xor == 0)\n\t\tsimple8brle_compressor_append(&compressor->tag0s, 0);\n\telse\n\t{\n\t\t/* leftmost/rightmost 1 is not well-defined when all the bits in the number\n\t\t * are 0; the C implementations of these functions will ERROR, while the\n\t\t * assembly versions may return any value. We special-case 0 to to use\n\t\t * values for leading and trailing-zeroes that we know will work.\n\t\t */\n\t\tint leading_zeros = xor != 0 ? 63 - pg_leftmost_one_pos64(xor) : 63;\n\t\tint trailing_zeros = xor != 0 ? pg_rightmost_one_pos64(xor) : 1;\n\t\t/*   This can easily get stuck with a bad value for trailing_zeroes, leading to a bad\n\t\t * compressed size. We use a new trailing_zeroes if the delta is too large, but the\n\t\t *   threshold (12) was picked in a completely unprincipled manner.\n\t\t *   Needs benchmarking to determine an ideal threshold.\n\t\t */\n\t\tbool reuse_bitsizes = has_values && leading_zeros >= compressor->prev_leading_zeroes &&\n\t\t\t\t\t\t\t  trailing_zeros >= compressor->prev_trailing_zeros &&\n\t\t\t\t\t\t\t  ((leading_zeros - compressor->prev_leading_zeroes) +\n\t\t\t\t\t\t\t\t   (trailing_zeros - compressor->prev_trailing_zeros) <=\n\t\t\t\t\t\t\t   12);\n\t\tuint8 num_bits_used;\n\n\t\tsimple8brle_compressor_append(&compressor->tag0s, 1);\n\t\tsimple8brle_compressor_append(&compressor->tag1s, reuse_bitsizes ? 0 : 1);\n\t\tif (!reuse_bitsizes)\n\t\t{\n\t\t\tcompressor->prev_leading_zeroes = leading_zeros;\n\t\t\tcompressor->prev_trailing_zeros = trailing_zeros;\n\t\t\tnum_bits_used = 64 - (leading_zeros + trailing_zeros);\n\n\t\t\tbit_array_append(&compressor->leading_zeros, BITS_PER_LEADING_ZEROS, leading_zeros);\n\t\t\tsimple8brle_compressor_append(&compressor->bits_used_per_xor, num_bits_used);\n\t\t}\n\n\t\tnum_bits_used = 64 - (compressor->prev_leading_zeroes + compressor->prev_trailing_zeros);\n\t\tbit_array_append(&compressor->xors, num_bits_used, xor >> compressor->prev_trailing_zeros);\n\t}\n\tcompressor->prev_val = val;\n}\n\nstatic GorillaCompressed *\ncompressed_gorilla_data_serialize(CompressedGorillaData *input)\n{\n\tSize tags0s_size = simple8brle_serialized_total_size(input->tag0s);\n\tSize tags1s_size = simple8brle_serialized_total_size(input->tag1s);\n\tSize leading_zeros_size = bit_array_data_bytes_used(&input->leading_zeros);\n\tSize bits_used_per_xor_size = simple8brle_serialized_total_size(input->num_bits_used_per_xor);\n\tSize xors_size = bit_array_data_bytes_used(&input->xors);\n\tSize nulls_size = 0;\n\n\tSize compressed_size;\n\tchar *data;\n\tGorillaCompressed *compressed;\n\tif (input->header->has_nulls)\n\t\tnulls_size = simple8brle_serialized_total_size(input->nulls);\n\n\tcompressed_size = sizeof(GorillaCompressed) + tags0s_size + tags1s_size + leading_zeros_size +\n\t\t\t\t\t  bits_used_per_xor_size + xors_size;\n\tif (input->header->has_nulls)\n\t\tcompressed_size += nulls_size;\n\n\tif (!AllocSizeIsValid(compressed_size))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),\n\t\t\t\t errmsg(\"compressed size exceeds the maximum allowed (%d)\", (int) MaxAllocSize)));\n\n\tdata = palloc0(compressed_size);\n\tcompressed = (GorillaCompressed *) data;\n\tSET_VARSIZE(&compressed->vl_len_, compressed_size);\n\tAssert(compressed_size % 4 == 0);\n\n\tcompressed->last_value = input->header->last_value;\n\tcompressed->compression_algorithm = COMPRESSION_ALGORITHM_GORILLA;\n\tcompressed->has_nulls = input->header->has_nulls;\n\tdata += sizeof(GorillaCompressed);\n\n\tdata = bytes_serialize_simple8b_and_advance(data, tags0s_size, input->tag0s);\n\tdata = bytes_serialize_simple8b_and_advance(data, tags1s_size, input->tag1s);\n\tdata = bytes_store_bit_array_and_advance(data,\n\t\t\t\t\t\t\t\t\t\t\t leading_zeros_size,\n\t\t\t\t\t\t\t\t\t\t\t &input->leading_zeros,\n\t\t\t\t\t\t\t\t\t\t\t &compressed->num_leading_zeroes_buckets,\n\t\t\t\t\t\t\t\t\t\t\t &compressed->bits_used_in_last_leading_zeros_bucket);\n\tdata = bytes_serialize_simple8b_and_advance(data,\n\t\t\t\t\t\t\t\t\t\t\t\tbits_used_per_xor_size,\n\t\t\t\t\t\t\t\t\t\t\t\tinput->num_bits_used_per_xor);\n\tdata = bytes_store_bit_array_and_advance(data,\n\t\t\t\t\t\t\t\t\t\t\t xors_size,\n\t\t\t\t\t\t\t\t\t\t\t &input->xors,\n\t\t\t\t\t\t\t\t\t\t\t &compressed->num_xor_buckets,\n\t\t\t\t\t\t\t\t\t\t\t &compressed->bits_used_in_last_xor_bucket);\n\n\tif (input->header->has_nulls)\n\t\tdata = bytes_serialize_simple8b_and_advance(data, nulls_size, input->nulls);\n\treturn compressed;\n}\n\nvoid *\ngorilla_compressor_finish(GorillaCompressor *compressor)\n{\n\tGorillaCompressed header = {\n\t\t.compression_algorithm = COMPRESSION_ALGORITHM_GORILLA,\n\t\t.has_nulls = compressor->has_nulls ? 1 : 0,\n\t\t.last_value = compressor->prev_val,\n\t};\n\tCompressedGorillaData data = { .header = &header };\n\tdata.tag0s = simple8brle_compressor_finish(&compressor->tag0s);\n\tif (data.tag0s == NULL)\n\t\treturn NULL;\n\n\tdata.tag1s = simple8brle_compressor_finish(&compressor->tag1s);\n\tAssert(data.tag1s != NULL);\n\tdata.leading_zeros = compressor->leading_zeros;\n\t/* if all elements in the compressed are the same, there will be no xors,\n\t * and thus bits_used_per_xor will be empty. Since we need to store the header\n\t * to get the sizing right, we force at least one bits_used_per_xor to be created\n\t * in append, above\n\t */\n\tdata.num_bits_used_per_xor = simple8brle_compressor_finish(&compressor->bits_used_per_xor);\n\tAssert(data.num_bits_used_per_xor != NULL);\n\tdata.xors = compressor->xors;\n\tdata.nulls = simple8brle_compressor_finish(&compressor->nulls);\n\tAssert(compressor->has_nulls || data.nulls != NULL);\n\n\treturn compressed_gorilla_data_serialize(&data);\n}\n\n/*******************************\n ***  DecompressionIterator  ***\n *******************************/\n\ninline static void\nbytes_attach_bit_array_and_advance(BitArray *dst, StringInfo si, uint32 num_buckets,\n\t\t\t\t\t\t\t\t   uint8 bits_in_last_bucket)\n{\n\tbit_array_wrap_internal(dst,\n\t\t\t\t\t\t\tnum_buckets,\n\t\t\t\t\t\t\tbits_in_last_bucket,\n\t\t\t\t\t\t\t(uint64 *) (si->data + si->cursor));\n\tconsumeCompressedData(si, bit_array_data_bytes_used(dst));\n}\n\nstatic void\ncompressed_gorilla_data_init_from_stringinfo(CompressedGorillaData *expanded, StringInfo si)\n{\n\texpanded->header = (GorillaCompressed *) consumeCompressedData(si, sizeof(GorillaCompressed));\n\n\tif (expanded->header->compression_algorithm != COMPRESSION_ALGORITHM_GORILLA)\n\t\telog(ERROR, \"unknown compression algorithm\");\n\n\tbool has_nulls = expanded->header->has_nulls == 1;\n\n\texpanded->tag0s = bytes_deserialize_simple8b_and_advance(si);\n\texpanded->tag1s = bytes_deserialize_simple8b_and_advance(si);\n\n\tbytes_attach_bit_array_and_advance(&expanded->leading_zeros,\n\t\t\t\t\t\t\t\t\t   si,\n\t\t\t\t\t\t\t\t\t   expanded->header->num_leading_zeroes_buckets,\n\t\t\t\t\t\t\t\t\t   expanded->header->bits_used_in_last_leading_zeros_bucket);\n\n\texpanded->num_bits_used_per_xor = bytes_deserialize_simple8b_and_advance(si);\n\n\tbytes_attach_bit_array_and_advance(&expanded->xors,\n\t\t\t\t\t\t\t\t\t   si,\n\t\t\t\t\t\t\t\t\t   expanded->header->num_xor_buckets,\n\t\t\t\t\t\t\t\t\t   expanded->header->bits_used_in_last_xor_bucket);\n\n\tif (has_nulls)\n\t\texpanded->nulls = bytes_deserialize_simple8b_and_advance(si);\n\telse\n\t\texpanded->nulls = NULL;\n}\n\nstatic void\ncompressed_gorilla_data_init_from_pointer(CompressedGorillaData *expanded,\n\t\t\t\t\t\t\t\t\t\t  const GorillaCompressed *compressed)\n{\n\tStringInfoData si = { .data = (char *) compressed, .len = VARSIZE(compressed) };\n\tcompressed_gorilla_data_init_from_stringinfo(expanded, &si);\n}\n\nstatic void\ncompressed_gorilla_data_init_from_datum(CompressedGorillaData *data, Datum gorilla_compressed)\n{\n\tcompressed_gorilla_data_init_from_pointer(data,\n\t\t\t\t\t\t\t\t\t\t\t  (GorillaCompressed *) PG_DETOAST_DATUM(\n\t\t\t\t\t\t\t\t\t\t\t\t  gorilla_compressed));\n}\n\nstatic void\ngorilla_iterator_init_from_expanded_forward(GorillaDecompressionIterator *iterator,\n\t\t\t\t\t\t\t\t\t\t\tOid element_type)\n{\n\titerator->base.compression_algorithm = COMPRESSION_ALGORITHM_GORILLA;\n\titerator->base.forward = true;\n\titerator->base.element_type = element_type;\n\titerator->base.try_next = gorilla_decompression_iterator_try_next_forward;\n\titerator->prev_val = 0;\n\titerator->prev_leading_zeroes = 0;\n\titerator->prev_xor_bits_used = 0;\n\n\tsimple8brle_decompression_iterator_init_forward(&iterator->tag0s, iterator->gorilla_data.tag0s);\n\tsimple8brle_decompression_iterator_init_forward(&iterator->tag1s, iterator->gorilla_data.tag1s);\n\tbit_array_iterator_init(&iterator->leading_zeros, &iterator->gorilla_data.leading_zeros);\n\tsimple8brle_decompression_iterator_init_forward(&iterator->num_bits_used,\n\t\t\t\t\t\t\t\t\t\t\t\t\titerator->gorilla_data.num_bits_used_per_xor);\n\tbit_array_iterator_init(&iterator->xors, &iterator->gorilla_data.xors);\n\n\titerator->has_nulls = iterator->gorilla_data.nulls != NULL;\n\tif (iterator->has_nulls)\n\t\tsimple8brle_decompression_iterator_init_forward(&iterator->nulls,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\titerator->gorilla_data.nulls);\n}\n\nDecompressionIterator *\ngorilla_decompression_iterator_from_datum_forward(Datum gorilla_compressed, Oid element_type)\n{\n\tGorillaDecompressionIterator *iterator = palloc(sizeof(*iterator));\n\tcompressed_gorilla_data_init_from_datum(&iterator->gorilla_data, gorilla_compressed);\n\tgorilla_iterator_init_from_expanded_forward(iterator, element_type);\n\treturn &iterator->base;\n}\n\nstatic inline DecompressResult\nconvert_from_internal(DecompressResultInternal res_internal, Oid element_type)\n{\n\tif (res_internal.is_done || res_internal.is_null)\n\t{\n\t\treturn (DecompressResult){\n\t\t\t.is_done = res_internal.is_done,\n\t\t\t.is_null = res_internal.is_null,\n\t\t};\n\t}\n\n\tswitch (element_type)\n\t{\n\t\tcase FLOAT8OID:\n\t\t\treturn (DecompressResult){\n\t\t\t\t.val = Float8GetDatum(bits_get_double(res_internal.val)),\n\t\t\t};\n\t\tcase FLOAT4OID:\n\t\t\treturn (DecompressResult){\n\t\t\t\t.val = Float4GetDatum(bits_get_float(res_internal.val)),\n\t\t\t};\n\t\tcase INT8OID:\n\t\t\treturn (DecompressResult){\n\t\t\t\t.val = Int64GetDatum(res_internal.val),\n\t\t\t};\n\t\tcase INT4OID:\n\t\t\treturn (DecompressResult){\n\t\t\t\t.val = Int32GetDatum(res_internal.val),\n\t\t\t};\n\t\tcase INT2OID:\n\t\t\treturn (DecompressResult){\n\t\t\t\t.val = Int16GetDatum(res_internal.val),\n\t\t\t};\n\t\tdefault:\n\t\t\telog(ERROR, \"invalid type requested from gorilla decompression\");\n\t}\n\tpg_unreachable();\n}\n\nstatic DecompressResultInternal\ngorilla_decompression_iterator_try_next_forward_internal(GorillaDecompressionIterator *iter)\n{\n\tSimple8bRleDecompressResult tag0;\n\tSimple8bRleDecompressResult tag1;\n\tuint64 xor ;\n\n\tif (iter->has_nulls)\n\t{\n\t\tSimple8bRleDecompressResult null =\n\t\t\tsimple8brle_decompression_iterator_try_next_forward(&iter->nulls);\n\t\t/* Could slightly improve performance here by not returning a tail of non-null bits */\n\t\tif (null.is_done)\n\t\t{\n\t\t\treturn (DecompressResultInternal){\n\t\t\t\t.is_done = true,\n\t\t\t};\n\t\t}\n\n\t\tif ((null.val & 1) != 0)\n\t\t{\n\t\t\treturn (DecompressResultInternal){\n\t\t\t\t.is_null = true,\n\t\t\t};\n\t\t}\n\t}\n\n\ttag0 = simple8brle_decompression_iterator_try_next_forward(&iter->tag0s);\n\t/* if we don't have a null bitset, this will determine when we're done */\n\tif (tag0.is_done)\n\t{\n\t\tCheckCompressedData(!iter->has_nulls);\n\t\treturn (DecompressResultInternal){\n\t\t\t.is_done = true,\n\t\t};\n\t}\n\n\tif (tag0.val == 0)\n\t{\n\t\treturn (DecompressResultInternal){\n\t\t\t.val = iter->prev_val,\n\t\t};\n\t}\n\n\ttag1 = simple8brle_decompression_iterator_try_next_forward(&iter->tag1s);\n\tCheckCompressedData(!tag1.is_done);\n\n\tif (tag1.val != 0)\n\t{\n\t\tSimple8bRleDecompressResult num_xor_bits;\n\t\t/* get new xor sizes */\n\t\titer->prev_leading_zeroes =\n\t\t\tbit_array_iter_next(&iter->leading_zeros, BITS_PER_LEADING_ZEROS);\n\t\tCheckCompressedData(iter->prev_leading_zeroes <= 64);\n\n\t\tnum_xor_bits = simple8brle_decompression_iterator_try_next_forward(&iter->num_bits_used);\n\t\tCheckCompressedData(!num_xor_bits.is_done);\n\t\titer->prev_xor_bits_used = num_xor_bits.val;\n\t\tCheckCompressedData(iter->prev_xor_bits_used <= 64);\n\n\t\t/*\n\t\t * More than 64 significant bits don't make sense. Exactly 64 we get for\n\t\t * the first encoded number.\n\t\t */\n\t\tCheckCompressedData(iter->prev_xor_bits_used + iter->prev_leading_zeroes <= 64);\n\t}\n\n\t/*\n\t * Zero significant bits would mean that the previous number is repeated,\n\t * but this should have been encoded with tag0 = 0.\n\t * This also might fail if we haven't seen the tag1 = 1 for the first number\n\t * and didn't initialize the bit widths.\n\t */\n\tCheckCompressedData(iter->prev_xor_bits_used + iter->prev_leading_zeroes > 0);\n\n\txor = bit_array_iter_next(&iter->xors, iter->prev_xor_bits_used);\n\txor <<= 64 - (iter->prev_leading_zeroes + iter->prev_xor_bits_used);\n\titer->prev_val ^= xor;\n\n\treturn (DecompressResultInternal){\n\t\t.val = iter->prev_val,\n\t};\n}\n\nDecompressResult\ngorilla_decompression_iterator_try_next_forward(DecompressionIterator *iter_base)\n{\n\tAssert(iter_base->compression_algorithm == COMPRESSION_ALGORITHM_GORILLA && iter_base->forward);\n\treturn convert_from_internal(gorilla_decompression_iterator_try_next_forward_internal(\n\t\t\t\t\t\t\t\t\t (GorillaDecompressionIterator *) iter_base),\n\t\t\t\t\t\t\t\t iter_base->element_type);\n}\n\n/****************************************\n *** reversed  DecompressionIterator  ***\n ****************************************/\n\n/*\n * conceptually, the bits from the gorilla algorithm can be thought of like\n *      tag0:   1 1 1 1 1 1 1 1 1 1 1\n *      tag1:  1 0 0 0 0 1 0 0 0 0 1\n *     nbits: 0    4         5      3\n *       xor:   1 2 3 4 5 a b c d e Q\n * that is, tag1 represents the transition between one value in the number of\n * leading/used bits arrays, and thus can be transversed in any order, whenever\n * we see a `1`, we switch from using are current numbers to the \"next\" in\n * whichever iteration order we're following. When transversing in reverse order\n * there is a little subtlety in that we run out of lengths before we run out of\n * tag1 bits (there's an implicit leading `0`), but at that point we've run out\n * of values anyway, so it does not matter.\n */\n\nDecompressionIterator *\ngorilla_decompression_iterator_from_datum_reverse(Datum gorilla_compressed, Oid element_type)\n{\n\tGorillaDecompressionIterator *iter = palloc(sizeof(*iter));\n\tSimple8bRleDecompressResult num_xor_bits;\n\n\titer->base.compression_algorithm = COMPRESSION_ALGORITHM_GORILLA;\n\titer->base.forward = false;\n\titer->base.element_type = element_type;\n\titer->base.try_next = gorilla_decompression_iterator_try_next_reverse;\n\tcompressed_gorilla_data_init_from_datum(&iter->gorilla_data, gorilla_compressed);\n\n\tsimple8brle_decompression_iterator_init_reverse(&iter->tag0s, iter->gorilla_data.tag0s);\n\tsimple8brle_decompression_iterator_init_reverse(&iter->tag1s, iter->gorilla_data.tag1s);\n\tbit_array_iterator_init_rev(&iter->leading_zeros, &iter->gorilla_data.leading_zeros);\n\tsimple8brle_decompression_iterator_init_reverse(&iter->num_bits_used,\n\t\t\t\t\t\t\t\t\t\t\t\t\titer->gorilla_data.num_bits_used_per_xor);\n\tbit_array_iterator_init_rev(&iter->xors, &iter->gorilla_data.xors);\n\n\titer->has_nulls = iter->gorilla_data.nulls != NULL;\n\tif (iter->has_nulls)\n\t\tsimple8brle_decompression_iterator_init_reverse(&iter->nulls, iter->gorilla_data.nulls);\n\n\t/* we need to know how many bits are used, even if the last value didn't store them */\n\titer->prev_leading_zeroes =\n\t\tbit_array_iter_next_rev(&iter->leading_zeros, BITS_PER_LEADING_ZEROS);\n\tnum_xor_bits = simple8brle_decompression_iterator_try_next_reverse(&iter->num_bits_used);\n\tAssert(!num_xor_bits.is_done);\n\titer->prev_xor_bits_used = num_xor_bits.val;\n\titer->prev_val = iter->gorilla_data.header->last_value;\n\treturn &iter->base;\n}\n\nstatic DecompressResultInternal\ngorilla_decompression_iterator_try_next_reverse_internal(GorillaDecompressionIterator *iter)\n{\n\tSimple8bRleDecompressResult tag0;\n\tSimple8bRleDecompressResult tag1;\n\tuint64 val;\n\tuint64 xor ;\n\n\tif (iter->has_nulls)\n\t{\n\t\tSimple8bRleDecompressResult null =\n\t\t\tsimple8brle_decompression_iterator_try_next_reverse(&iter->nulls);\n\n\t\tif (null.is_done)\n\t\t\treturn (DecompressResultInternal){\n\t\t\t\t.is_done = true,\n\t\t\t};\n\n\t\tif ((null.val & 1) != 0)\n\t\t{\n\t\t\treturn (DecompressResultInternal){\n\t\t\t\t.is_null = true,\n\t\t\t};\n\t\t}\n\t}\n\n\tval = iter->prev_val;\n\n\ttag0 = simple8brle_decompression_iterator_try_next_reverse(&iter->tag0s);\n\t/* if we don't have a null bitset, this will determine when we're done */\n\tif (tag0.is_done)\n\t\treturn (DecompressResultInternal){\n\t\t\t.is_done = true,\n\t\t};\n\n\tif (tag0.val == 0)\n\t\treturn (DecompressResultInternal){\n\t\t\t.val = val,\n\t\t};\n\n\txor = bit_array_iter_next_rev(&iter->xors, iter->prev_xor_bits_used);\n\n\tif (iter->prev_leading_zeroes + iter->prev_xor_bits_used < 64)\n\t\txor <<= 64 - (iter->prev_leading_zeroes + iter->prev_xor_bits_used);\n\titer->prev_val ^= xor;\n\n\ttag1 = simple8brle_decompression_iterator_try_next_reverse(&iter->tag1s);\n\n\tif (tag1.val != 0)\n\t{\n\t\t/* get new xor sizes */\n\t\tSimple8bRleDecompressResult num_xor_bits =\n\t\t\tsimple8brle_decompression_iterator_try_next_reverse(&iter->num_bits_used);\n\t\t/* there're an implicit leading 0 to num_xor_bits and prev_leading_zeroes,\n\t\t */\n\t\tif (num_xor_bits.is_done)\n\t\t{\n\t\t\titer->prev_xor_bits_used = 0;\n\t\t\titer->prev_leading_zeroes = 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\titer->prev_xor_bits_used = num_xor_bits.val;\n\t\t\titer->prev_leading_zeroes =\n\t\t\t\tbit_array_iter_next_rev(&iter->leading_zeros, BITS_PER_LEADING_ZEROS);\n\t\t}\n\t}\n\n\treturn (DecompressResultInternal){\n\t\t.val = val,\n\t};\n}\n\nDecompressResult\ngorilla_decompression_iterator_try_next_reverse(DecompressionIterator *iter_base)\n{\n\tAssert(iter_base->compression_algorithm == COMPRESSION_ALGORITHM_GORILLA &&\n\t\t   !iter_base->forward);\n\treturn convert_from_internal(gorilla_decompression_iterator_try_next_reverse_internal(\n\t\t\t\t\t\t\t\t\t (GorillaDecompressionIterator *) iter_base),\n\t\t\t\t\t\t\t\t iter_base->element_type);\n}\n\n#define MAX_NUM_LEADING_ZEROS_PADDED_N64 (((GLOBAL_MAX_ROWS_PER_COMPRESSION + 63) / 64) * 64)\n\n/*\n * Decompress packed 6bit values in lanes that contain a round number of both\n * packed and unpacked bytes -- 4 6-bit values are packed into 3 8-bit values.\n */\nstatic uint8 *\nunpack_leading_zeros_array(BitArray *bitarray, uint32 *_n)\n{\n#define LANE_INPUTS 3\n#define LANE_OUTPUTS 4\n\tStaticAssertExpr(BITS_PER_LEADING_ZEROS * LANE_OUTPUTS == 8 * LANE_INPUTS,\n\t\t\t\t\t \"the numbers of input and output lanes do not add up\");\n\n\t/*\n\t * We have four bytes of padding after leading zeros, so we don't care if\n\t * the reads of final bytes run into them and we unpack some nonsense. This\n\t * means we can always work in full lanes.\n\t *\n\t * We do have to check that the result fits into the maximum number of rows,\n\t * because we get the length from user input.\n\t */\n\tconst uint32 n_bytes_packed = bitarray->buckets.num_elements * sizeof(uint64);\n\tconst uint32 n_lanes = (n_bytes_packed + LANE_INPUTS - 1) / LANE_INPUTS;\n\tconst uint32 n_outputs = n_lanes * LANE_OUTPUTS;\n\tCheckCompressedData(n_outputs <= MAX_NUM_LEADING_ZEROS_PADDED_N64);\n\tuint8 *restrict dest = palloc(n_outputs);\n\n\tfor (uint32 lane = 0; lane < n_lanes; lane++)\n\t{\n\t\tuint8 *restrict lane_dest = &dest[lane * LANE_OUTPUTS];\n\t\tconst uint8 *lane_src = &((uint8 *) bitarray->buckets.data)[lane * LANE_INPUTS];\n\t\tfor (uint32 output_in_lane = 0; output_in_lane < LANE_OUTPUTS; output_in_lane++)\n\t\t{\n\t\t\tconst int startbit_abs = output_in_lane * BITS_PER_LEADING_ZEROS;\n\t\t\tconst int startbit_rel = startbit_abs % 8;\n\t\t\tconst int offs = 8 - startbit_rel;\n\n\t\t\tconst uint8 this_input = lane_src[startbit_abs / 8];\n\t\t\tconst uint8 next_input = lane_src[(startbit_abs + BITS_PER_LEADING_ZEROS - 1) / 8];\n\n\t\t\tuint8 output = this_input >> startbit_rel;\n\t\t\toutput |= ((uint64) next_input) << offs;\n\t\t\toutput &= (1ULL << BITS_PER_LEADING_ZEROS) - 1ULL;\n\n\t\t\tlane_dest[output_in_lane] = output;\n\t\t}\n\t}\n#undef LANE_INPUTS\n#undef LANE_OUTPUTS\n\n\t*_n = n_outputs;\n\treturn dest;\n}\n\n/* Bulk gorilla decompression, specialized for supported data types. */\n\n#define ELEMENT_TYPE uint8\n#include \"simple8b_rle_decompress_all.h\"\n#undef ELEMENT_TYPE\n\n#define ELEMENT_TYPE uint32\n#include \"gorilla_impl.c\"\n#undef ELEMENT_TYPE\n\n#define ELEMENT_TYPE uint64\n#include \"gorilla_impl.c\"\n#undef ELEMENT_TYPE\n\nArrowArray *\ngorilla_decompress_all(Datum datum, Oid element_type, MemoryContext dest_mctx)\n{\n\tCompressedGorillaData gorilla_data;\n\tcompressed_gorilla_data_init_from_datum(&gorilla_data, datum);\n\n\tswitch (element_type)\n\t{\n\t\tcase FLOAT8OID:\n\t\t\treturn gorilla_decompress_all_uint64(&gorilla_data, dest_mctx);\n\t\tcase FLOAT4OID:\n\t\t\treturn gorilla_decompress_all_uint32(&gorilla_data, dest_mctx);\n\t\tdefault:\n\t\t\telog(ERROR,\n\t\t\t\t \"type '%s' is not supported for gorilla decompression\",\n\t\t\t\t format_type_be(element_type));\n\t\t\tpg_unreachable();\n\t}\n}\n\n/*************\n ***  I/O  ***\n **************/\n\nvoid\ngorilla_compressed_send(CompressedDataHeader *header, StringInfo buf)\n{\n\tCompressedGorillaData data;\n\tconst GorillaCompressed *compressed = (GorillaCompressed *) header;\n\tAssert(header->compression_algorithm == COMPRESSION_ALGORITHM_GORILLA);\n\n\tcompressed_gorilla_data_init_from_pointer(&data, compressed);\n\tpq_sendbyte(buf, data.header->has_nulls);\n\tpq_sendint64(buf, data.header->last_value);\n\tsimple8brle_serialized_send(buf, data.tag0s);\n\tsimple8brle_serialized_send(buf, data.tag1s);\n\tbit_array_send(buf, &data.leading_zeros);\n\tsimple8brle_serialized_send(buf, data.num_bits_used_per_xor);\n\tbit_array_send(buf, &data.xors);\n\tif (data.header->has_nulls)\n\t\tsimple8brle_serialized_send(buf, data.nulls);\n}\n\nDatum\ngorilla_compressed_recv(StringInfo buf)\n{\n\tGorillaCompressed header = { .vl_len_ = { 0 } };\n\tCompressedGorillaData data = {\n\t\t.header = &header,\n\t};\n\n\theader.has_nulls = pq_getmsgbyte(buf);\n\tCheckCompressedData(header.has_nulls == 0 || header.has_nulls == 1);\n\n\theader.last_value = pq_getmsgint64(buf);\n\tdata.tag0s = simple8brle_serialized_recv(buf);\n\tdata.tag1s = simple8brle_serialized_recv(buf);\n\tdata.leading_zeros = bit_array_recv(buf);\n\tdata.num_bits_used_per_xor = simple8brle_serialized_recv(buf);\n\tdata.xors = bit_array_recv(buf);\n\n\tif (header.has_nulls)\n\t\tdata.nulls = simple8brle_serialized_recv(buf);\n\n\tPG_RETURN_POINTER(compressed_gorilla_data_serialize(&data));\n}\n"
  },
  {
    "path": "tsl/src/compression/algorithms/gorilla.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n/*\n *  The Gorilla algorithm compresses floats and is modeled after the Facebook Gorilla paper:\n *  \"Gorilla: A Fast, Scalable, In-Memory Time Series Database\" by Tuomas Pelkonen et. al.\n *\n * How it works: Given a series of floats, first convert that series to a series of xors between\n * consecutive floats (as uint64), while storing the first value as well. Given the first value\n * and the series of xor values, getting the floats back is trivial. So, our goal becomes to\n * compress the series of xors.\n *\n * The logic for compressing xors is as follows:\n *\n * The compression depends on the observation that a lot of xors will be mostly 0s, and that the\n * section of the xor that is non-zero will be similar in consecutive xors. So the algorithm tries\n * to record only the section of the xors that are non-zero. And records state about which section\n * of the xor its storing rarely (reusing this information from the previous xor if possible).  The\n * state of which section of the xor is recorded is kept in two variables: leading-zeroes and\n * number_of_bits_used. Thus we record only number_of_bits_used bits of the xor (shifted\n * appropriately).\n *\n * The algorithm keeps state for the number of leading-zeroes and number_of_bits_used from xor to\n * xor. A boolean array called tag1 is used to indicate that these state variables need to change\n * for the next xor. A boolean array called Tag0 is used to indicate that the xor is 0.\n *\n * The state is as follows:\n *  * two separate series of boolean bit values called tag0, tag1. (simple8brle compressed)\n *  * a series of 6-bit bit-array values called leading-zeroes (not-compressed)\n *  * a series of 64-bit ints of num_bits_used (simple8brle compressed)\n *  * a series of variable-bit-length bits stored in an bit array called xors (not-compressed)\n *\n * Pseudocode:\n * if the xor == 0:\n *   append a 0 to tag0.\n *   You are done.\n * else: (xor != 0)\n *   append a 1 to tag0.\n *   Figure out the number of leading-zeroes and number_of_bits_used necessary to store the next\n *   xor. Then, Make a decision whether to reuse the leading-zero and number of bits from the\n *   current state. (the decision is based on whether it's possible to fit the next xor in as well\n *   as a heuristic for whether it's cheaper to switch to use less number_of_bits_used)\n *\n *   if (reusing previous state)\n *     append 0 to tag1.\n *     append number_of_bits_used bits to the xors bit array. These bits consist\n *     of the next xor shifted by the appropriate amount.\n *     You are done.\n *   else: (you are changing state)\n *     append 1 to tag1\n *     append the new number of leading-zeroes to the leading-zeroes array\n *     append the new number for number_of_bits_used to the num_bits used array\n *     append new number_of_bits_used bits to the xors bit array.\n *     These bits consist of the next xor shifted by the appropriate amount.\n *     you are done.\n *\n */\n\n#include <postgres.h>\n#include <c.h>\n#include <fmgr.h>\n#include <lib/stringinfo.h>\n\n#include \"compression/compression.h\"\n\ntypedef struct GorillaCompressor GorillaCompressor;\ntypedef struct GorillaCompressed GorillaCompressed;\ntypedef struct GorillaDecompressionIterator GorillaDecompressionIterator;\n\nextern bool gorilla_compressed_has_nulls(const CompressedDataHeader *header);\nextern Compressor *gorilla_compressor_for_type(Oid element_type);\n\nextern GorillaCompressor *gorilla_compressor_alloc(void);\nextern void gorilla_compressor_append_null(GorillaCompressor *compressor);\nextern void gorilla_compressor_append_value(GorillaCompressor *compressor, uint64 val);\nextern void *gorilla_compressor_finish(GorillaCompressor *compressor);\n\nextern DecompressionIterator *\ngorilla_decompression_iterator_from_datum_forward(Datum gorilla_compressed, Oid element_type);\nextern DecompressResult\ngorilla_decompression_iterator_try_next_forward(DecompressionIterator *iter);\n\nextern DecompressionIterator *\ngorilla_decompression_iterator_from_datum_reverse(Datum gorilla_compressed, Oid element_type);\nextern DecompressResult\ngorilla_decompression_iterator_try_next_reverse(DecompressionIterator *iter);\nextern ArrowArray *gorilla_decompress_all(Datum datum, Oid element_type, MemoryContext dest_mctx);\n\nextern void gorilla_compressed_send(CompressedDataHeader *header, StringInfo buffer);\nextern Datum gorilla_compressed_recv(StringInfo buf);\n\nextern Datum tsl_gorilla_compressor_append(PG_FUNCTION_ARGS);\nextern Datum tsl_gorilla_compressor_finish(PG_FUNCTION_ARGS);\n\n#define GORILLA_ALGORITHM_DEFINITION                                                               \\\n\t{                                                                                              \\\n\t\t.iterator_init_forward = gorilla_decompression_iterator_from_datum_forward,                \\\n\t\t.iterator_init_reverse = gorilla_decompression_iterator_from_datum_reverse,                \\\n\t\t.decompress_all = gorilla_decompress_all, .compressed_data_send = gorilla_compressed_send, \\\n\t\t.compressed_data_recv = gorilla_compressed_recv,                                           \\\n\t\t.compressor_for_type = gorilla_compressor_for_type,                                        \\\n\t\t.compressed_data_storage = TOAST_STORAGE_EXTERNAL,                                         \\\n\t}\n"
  },
  {
    "path": "tsl/src/compression/algorithms/gorilla_impl.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * Decompress the entire batch of gorilla-compressed rows into an Arrow array.\n * Specialized for each supported data type.\n */\n\n#define FUNCTION_NAME_HELPER(X, Y) X##_##Y\n#define FUNCTION_NAME(X, Y) FUNCTION_NAME_HELPER(X, Y)\n\nstatic ArrowArray *\nFUNCTION_NAME(gorilla_decompress_all, ELEMENT_TYPE)(CompressedGorillaData *gorilla_data,\n\t\t\t\t\t\t\t\t\t\t\t\t\tMemoryContext dest_mctx)\n{\n\tconst bool has_nulls = gorilla_data->nulls != NULL;\n\tconst uint32 n_total =\n\t\thas_nulls ? gorilla_data->nulls->num_elements : gorilla_data->tag0s->num_elements;\n\tCheckCompressedData(n_total <= GLOBAL_MAX_ROWS_PER_COMPRESSION);\n\n\t/*\n\t * Pad the number of elements to multiple of 64 bytes if needed, so that we\n\t * can work in 64-byte blocks.\n\t */\n\tconst uint32 n_total_padded =\n\t\t((n_total * sizeof(ELEMENT_TYPE) + 63) / 64) * 64 / sizeof(ELEMENT_TYPE);\n\tAssert(n_total_padded >= n_total);\n\n\t/*\n\t * We need additional padding at the end of buffer, because the code that\n\t * converts the elements to postres Datum always reads in 8 bytes.\n\t */\n\tconst int buffer_bytes = n_total_padded * sizeof(ELEMENT_TYPE) + 8;\n\tELEMENT_TYPE *restrict decompressed_values = MemoryContextAlloc(dest_mctx, buffer_bytes);\n\n\tconst uint32 n_notnull = gorilla_data->tag0s->num_elements;\n\tCheckCompressedData(n_total >= n_notnull);\n\n\t/* Unpack the basic compressed data parts. */\n\tconst Simple8bRleBitmap tag0s = simple8brle_bitmap_prefixsums(gorilla_data->tag0s);\n\tconst Simple8bRleBitmap tag1s = simple8brle_bitmap_prefixsums(gorilla_data->tag1s);\n\n\tBitArray leading_zeros_bitarray = gorilla_data->leading_zeros;\n\tBitArrayIterator leading_zeros_iterator;\n\tbit_array_iterator_init(&leading_zeros_iterator, &leading_zeros_bitarray);\n\n\tuint32 num_leading_zeros_padded;\n\tconst uint8 *all_leading_zeros =\n\t\tunpack_leading_zeros_array(&gorilla_data->leading_zeros, &num_leading_zeros_padded);\n\n\tuint32 num_bit_widths;\n\tconst uint8 *bit_widths =\n\t\tsimple8brle_decompress_all_uint8(gorilla_data->num_bits_used_per_xor, &num_bit_widths);\n\n\tBitArray xors_bitarray = gorilla_data->xors;\n\tBitArrayIterator xors_iterator;\n\tbit_array_iterator_init(&xors_iterator, &xors_bitarray);\n\n\t/*\n\t * Now decompress the non-null data.\n\t *\n\t * 1) unpack only the different elements (tag0 = 1) based on the tag1 array.\n\t *\n\t * 1a) Sanity check: the number of bit widths we have matches the\n\t * number of 1s in the tag1s array.\n\t */\n\tCheckCompressedData(simple8brle_bitmap_num_ones(&tag1s) == num_bit_widths);\n\tCheckCompressedData(simple8brle_bitmap_num_ones(&tag1s) <= num_leading_zeros_padded);\n\n\t/*\n\t * 1b) Sanity check: the first tag1 must be 1, so that we initialize the bit\n\t * widths.\n\t */\n\tCheckCompressedData(simple8brle_bitmap_prefix_sum(&tag1s, 0) == 1);\n\n\t/*\n\t * 1c) Sanity check: can't have more different elements than notnull elements.\n\t */\n\tconst uint16 n_different = tag1s.num_elements;\n\tCheckCompressedData(n_different <= n_notnull);\n\n\t/*\n\t * 1d) Unpack.\n\t *\n\t * Note that the bit widths change often, so there's no sense in\n\t * having a fast path for stretches of tag1 == 0.\n\t */\n\tELEMENT_TYPE prev = 0;\n\tfor (uint16 i = 0; i < n_different; i++)\n\t{\n\t\tconst uint8 current_xor_bits = bit_widths[simple8brle_bitmap_prefix_sum(&tag1s, i) - 1];\n\t\tconst uint8 current_leading_zeros =\n\t\t\tall_leading_zeros[simple8brle_bitmap_prefix_sum(&tag1s, i) - 1];\n\n\t\t/*\n\t\t * Truncate the shift here not to cause UB on the corrupt data.\n\t\t */\n\t\tconst uint8 shift = (64 - (current_xor_bits + current_leading_zeros)) & 63;\n\n\t\tconst uint64 current_xor = bit_array_iter_next(&xors_iterator, current_xor_bits);\n\t\tprev ^= current_xor << shift;\n\t\tdecompressed_values[i] = prev;\n\t}\n\n\t/*\n\t * 2) Fill out the stretches of repeated elements, encoded with tag0 = 0.\n\t *\n\t * 2a) Sanity check: number of different elements according to tag0s must be\n\t * the same as number of different elements according to tag1s, so that the\n\t * current_element doesn't underrun.\n\t */\n\tCheckCompressedData(simple8brle_bitmap_num_ones(&tag0s) == n_different);\n\n\t/*\n\t * 2b) Sanity check: tag0s[0] == 1 -- the first element of the sequence is\n\t * always \"different from the previous one\".\n\t */\n\tCheckCompressedData(simple8brle_bitmap_prefix_sum(&tag0s, 0) == 1);\n\n\t/*\n\t * 2b) Fill the repeated elements.\n\t */\n\tfor (int i = n_notnull - 1; i >= 0; i--)\n\t{\n\t\tdecompressed_values[i] = decompressed_values[simple8brle_bitmap_prefix_sum(&tag0s, i) - 1];\n\t}\n\n\tuint64 *restrict validity_bitmap = NULL;\n\tif (has_nulls)\n\t{\n\t\t/*\n\t\t * We have unpacked the non-null data. Now reshuffle it to account for nulls,\n\t\t * and fill the validity bitmap.\n\t\t */\n\t\tconst int validity_bitmap_bytes = sizeof(uint64) * ((n_total + 64 - 1) / 64);\n\t\tvalidity_bitmap = MemoryContextAlloc(dest_mctx, validity_bitmap_bytes);\n\n\t\t/*\n\t\t * First, mark all data as valid, we will fill the nulls later if needed.\n\t\t * Note that the validity bitmap size is a multiple of 64 bits. We have to\n\t\t * fill the tail bits with zeros, because the corresponding elements are not\n\t\t * valid.\n\t\t *\n\t\t */\n\t\tmemset(validity_bitmap, 0xFF, validity_bitmap_bytes);\n\t\tif (n_total % 64)\n\t\t{\n\t\t\tconst uint64 tail_mask = ~0ULL >> (64 - n_total % 64);\n\t\t\tvalidity_bitmap[n_total / 64] &= tail_mask;\n\t\t}\n\n\t\t/*\n\t\t * We have decompressed the data with nulls skipped, reshuffle it\n\t\t * according to the nulls bitmap.\n\t\t */\n\t\tconst Simple8bRleBitmap nulls = simple8brle_bitmap_decompress(gorilla_data->nulls);\n\t\tCheckCompressedData(n_notnull + simple8brle_bitmap_num_ones(&nulls) == n_total);\n\n\t\tint current_notnull_element = n_notnull - 1;\n\t\tfor (int i = n_total - 1; i >= 0; i--)\n\t\t{\n\t\t\tAssert(i >= current_notnull_element);\n\n\t\t\tif (simple8brle_bitmap_get_at(&nulls, i))\n\t\t\t{\n\t\t\t\tarrow_set_row_validity(validity_bitmap, i, false);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tAssert(current_notnull_element >= 0);\n\t\t\t\tdecompressed_values[i] = decompressed_values[current_notnull_element];\n\t\t\t\tcurrent_notnull_element--;\n\t\t\t}\n\t\t}\n\n\t\tAssert(current_notnull_element == -1);\n\t}\n\n\t/* Return the result. */\n\tArrowArray *result = MemoryContextAllocZero(dest_mctx, sizeof(ArrowArray) + sizeof(void *) * 2);\n\tconst void **buffers = (const void **) &result[1];\n\tbuffers[0] = validity_bitmap;\n\tbuffers[1] = decompressed_values;\n\tresult->n_buffers = 2;\n\tresult->buffers = buffers;\n\tresult->length = n_total;\n\tresult->null_count = n_total - n_notnull;\n\treturn result;\n}\n\n#undef FUNCTION_NAME\n#undef FUNCTION_NAME_HELPER\n"
  },
  {
    "path": "tsl/src/compression/algorithms/null.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include \"null.h\"\n#include \"fmgr.h\"\n\ntypedef struct NullCompressed\n{\n\tCompressedDataHeaderFields;\n} NullCompressed;\n\nextern DecompressionIterator *\nnull_decompression_iterator_from_datum_forward(Datum bool_compressed, Oid element_type)\n{\n\telog(ERROR, \"null decompression iterator not implemented\");\n\treturn NULL;\n}\n\nextern DecompressionIterator *\nnull_decompression_iterator_from_datum_reverse(Datum bool_compressed, Oid element_type)\n{\n\telog(ERROR, \"null decompression iterator not implemented\");\n\treturn NULL;\n}\n\nextern void\nnull_compressed_send(CompressedDataHeader *header, StringInfo buffer)\n{\n\telog(ERROR, \"null compression doesn't implement send\");\n}\n\nextern Datum\nnull_compressed_recv(StringInfo buffer)\n{\n\t/* Sanity checks for invalid buffer */\n\tif (buffer->len == 0)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"compressed data is invalid to be a null compressed block\")));\n\tif (buffer->data == NULL)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"compressed data is NULL\")));\n\tPG_RETURN_POINTER(null_compressor_get_dummy_block());\n}\n\nextern Compressor *\nnull_compressor_for_type(Oid element_type)\n{\n\telog(ERROR, \"null compressor not implemented\");\n\treturn NULL;\n}\n\nextern void *\nnull_compressor_get_dummy_block(void)\n{\n\tNullCompressed *compressed = palloc(sizeof(NullCompressed));\n\tSize compressed_size = sizeof(NullCompressed);\n\tcompressed->compression_algorithm = COMPRESSION_ALGORITHM_NULL;\n\tSET_VARSIZE(&compressed->vl_len_, compressed_size);\n\treturn compressed;\n}\n"
  },
  {
    "path": "tsl/src/compression/algorithms/null.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n/*\n * The NULL compression algorithm is a no-op compression algorithm that is only\n * used to signal that all values in a compressed block are NULLs. The compression\n * interface functions are only defined to comply with the framework, but they\n * are not implemented and return an ERROR. Calling these function is a software\n * bug.\n */\n\n#include <postgres.h>\n#include <fmgr.h>\n#include <lib/stringinfo.h>\n\n#include \"compression/compression.h\"\n\n/*\n * Compressor framework functions and definitions for the null algorithm.\n */\n\nextern DecompressionIterator *null_decompression_iterator_from_datum_forward(Datum bool_compressed,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t Oid element_type);\n\nextern DecompressionIterator *null_decompression_iterator_from_datum_reverse(Datum bool_compressed,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t Oid element_type);\n\nextern void null_compressed_send(CompressedDataHeader *header, StringInfo buffer);\n\nextern Datum null_compressed_recv(StringInfo buffer);\n\nextern Compressor *null_compressor_for_type(Oid element_type);\n\nextern void *null_compressor_get_dummy_block(void);\n\n#define NULL_COMPRESS_ALGORITHM_DEFINITION                                                         \\\n\t{                                                                                              \\\n\t\t.iterator_init_forward = null_decompression_iterator_from_datum_forward,                   \\\n\t\t.iterator_init_reverse = null_decompression_iterator_from_datum_reverse,                   \\\n\t\t.decompress_all = NULL, .compressed_data_send = null_compressed_send,                      \\\n\t\t.compressed_data_recv = null_compressed_recv,                                              \\\n\t\t.compressor_for_type = null_compressor_for_type,                                           \\\n\t\t.compressed_data_storage = TOAST_STORAGE_EXTERNAL,                                         \\\n\t}\n"
  },
  {
    "path": "tsl/src/compression/algorithms/simple8b_rle.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <c.h>\n#include <fmgr.h>\n#include <lib/stringinfo.h>\n#include <libpq/pqformat.h>\n\n#include <adts/bit_array.h>\n\n#include \"compat/compat.h\"\n#include <adts/uint64_vec.h>\n\n/* This is defined as a header file as it is expected to be used as a primitive\n * for \"real\" compression algorithms, not used directly on SQL data. Also, due to inlining.\n *\n *\n * From Vo Ngoc Anh, Alistair Moffat: Index compression using 64-bit words. Softw., Pract. Exper.\n * 40(2): 131-147 (2010)\n *\n * Simple 8b RLE is a block based encoding/compression scheme for integers. Each block is made up of\n * one selector and one 64-bit data value. The interpretation of the data value is based on the\n * selector values. Selectors 1-14 indicate that the data value is a bit packing of integers, where\n * each integer takes up a constant number of bits. The value of the constant-number-of-bits is set\n * according to the table below. Selector 15 indicates that the block encodes a single \"run\" of RLE,\n * where the data element is a bit packing of the run count and run value.\n *\n *\n *  Selector value: 0 |  1  2  3  4  5  6  7  8  9 10 11 12 13 14 | 15 (RLE)\n *  Integers coded: 0 | 64 32 21 16 12 10  9  8  6  5  4  3  2  1 | up to 2^28\n *  Bits/integer:   0 |  1  2  3  4  5  6  7  8 10 12 16 21 32 64 | 36 bits\n *  Wasted bits:    0 |  0  0  1  0  4  4  1  0  4  4  0  1  0  0 |   N/A\n *\n *  a 0 selector is currently unused\n */\n\n/************** Constants *****************/\n#define SIMPLE8B_BITSIZE 64\n#define SIMPLE8B_MAXCODE 15\n#define SIMPLE8B_MINCODE 1\n\n#define SIMPLE8B_RLE_SELECTOR SIMPLE8B_MAXCODE\n#define SIMPLE8B_RLE_MAX_VALUE_BITS 36\n#define SIMPLE8B_RLE_MAX_COUNT_BITS (SIMPLE8B_BITSIZE - SIMPLE8B_RLE_MAX_VALUE_BITS)\n#define SIMPLE8B_RLE_MAX_VALUE_MASK ((1ULL << SIMPLE8B_RLE_MAX_VALUE_BITS) - 1)\n#define SIMPLE8B_RLE_MAX_COUNT_MASK ((1ULL << SIMPLE8B_RLE_MAX_COUNT_BITS) - 1)\n\n#define SIMPLE8B_BITS_PER_SELECTOR 4\n#define SIMPLE8B_SELECTORS_PER_SELECTOR_SLOT 16\n#define SIMPLE8B_MAX_BUFFERED 256\n\n/* clang-format off */\n/* selector value:                        0,  1,  2,  3,  4,  5,  6, 7, 8,  9, 10, 11, 12, 13, 14, RLE */\n#define SIMPLE8B_NUM_ELEMENTS ((uint8[]){ 0, 64, 32, 21, 16, 12, 10, 9, 8,  6,  5,  4,  3,  2,  1, 0  })\n#define SIMPLE8B_BIT_LENGTH   ((uint8[]){ 0,  1,  2,  3,  4,  5,  6, 7, 8, 10, 12, 16, 21, 32, 64, 36 })\n/* Map bit lengths directly to selector values */\n#define SIMPLE8B_SELECTOR_FOR_BIT_WIDTH ((uint8[]){                        \\\n/*   0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, */     \\\n     1,  1,  2,  3,  4,  5,  6,  7,  8,  9,  9, 10, 10, 11, 11, 11,        \\\n/*  16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, */     \\\n    11, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,        \\\n/*  32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, */     \\\n    13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,        \\\n/*  48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64 */  \\\n    14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14     \\\n})\n/* clang-format on */\n\n/* The full block selector is used when a value occupies the full 64bit block, see above. */\n#define SIMPLE8B_FULL_BLOCK_SELECTOR 14\n\n/********************\n ***  Public API  ***\n ********************/\n\ntypedef struct Simple8bRleSerialized\n{\n\t/* the slots are padded with 0 to fill out the last slot, so there may be up\n\t * to 59 extra values stored, to counteract this, we store how many values\n\t * there should be on output.\n\t * We currently disallow more than 2^32 values per compression, since we're\n\t * going to limit the amount of rows stored per-compressed-row anyway.\n\t */\n\tuint32 num_elements;\n\t/* we store nslots as a uint32 since we'll need to fit this in a varlen, and\n\t * we cannot store more than 2^32 bytes anyway\n\t */\n\tuint32 num_blocks;\n\tuint64 slots[FLEXIBLE_ARRAY_MEMBER];\n} Simple8bRleSerialized;\n\nstatic void\npg_attribute_unused() simple8brle_size_assertions(void)\n{\n\tSimple8bRleSerialized test_val = { 0 };\n\t/* ensure no padding bits make it to disk */\n\tStaticAssertStmt(sizeof(Simple8bRleSerialized) ==\n\t\t\t\t\t\t sizeof(test_val.num_elements) + sizeof(test_val.num_blocks),\n\t\t\t\t\t \"simple8b_rle_oob wrong size\");\n\tStaticAssertStmt(sizeof(Simple8bRleSerialized) == 8, \"simple8b_rle_oob wrong size\");\n}\n\ntypedef struct Simple8bRleBlock\n{\n\tuint64 data;\n\tuint32 num_elements_compressed;\n\tuint8 selector;\n} Simple8bRleBlock;\n\ntypedef struct Simple8bRleBuffer\n{\n\tuint64 data;\n\tuint32 repcount;\n\t/* The saved_repcount is placed here to help cache locality when we\n\t * need to do a partial flush and need to restore the repcount. */\n\tuint32 saved_repcount;\n} Simple8bRleBuffer;\n\ntypedef struct Simple8bRleCompressor\n{\n\t/* The total number of elements that have been compressed. */\n\tuint32 num_elements;\n\n\t/* The number of elements that have been buffered in the uncompressed_buffer. */\n\tuint32 num_buffered_elements;\n\n\t/* The last value that has been compressed. */\n\tuint64 last_value;\n\n\t/* The buffer of uncompressed elements. */\n\tSimple8bRleBuffer uncompressed_buffer[SIMPLE8B_MAX_BUFFERED];\n\n\tBitArray selectors;\n\tuint64_vec compressed_data;\n\n} Simple8bRleCompressor;\n\ntypedef struct Simple8bRleDecompressionIterator\n{\n\tBitArray selector_data;\n\tBitArrayIterator selectors;\n\tSimple8bRleBlock current_block;\n\n\tconst uint64 *compressed_data;\n\tint32 num_blocks;\n\tint32 current_compressed_pos;\n\tint32 current_in_compressed_pos;\n\n\tuint32 num_elements;\n\tuint32 num_elements_returned;\n} Simple8bRleDecompressionIterator;\n\ntypedef struct Simple8bRleDecompressResult\n{\n\tuint64 val;\n\tbool is_done;\n} Simple8bRleDecompressResult;\n\nstatic inline void simple8brle_compressor_init(Simple8bRleCompressor *compressor);\nstatic inline Simple8bRleSerialized *\nsimple8brle_compressor_finish(Simple8bRleCompressor *compressor);\nstatic inline char *simple8brle_compressor_finish_into(Simple8bRleCompressor *compressor,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   char *dest, size_t expected_size);\nstatic inline void simple8brle_compressor_append(Simple8bRleCompressor *compressor, uint64 val);\nstatic inline bool simple8brle_compressor_is_empty(Simple8bRleCompressor *compressor);\n\nstatic inline void\nsimple8brle_decompression_iterator_init_forward(Simple8bRleDecompressionIterator *iter,\n\t\t\t\t\t\t\t\t\t\t\t\tSimple8bRleSerialized *compressed);\nstatic inline void\nsimple8brle_decompression_iterator_init_reverse(Simple8bRleDecompressionIterator *iter,\n\t\t\t\t\t\t\t\t\t\t\t\tSimple8bRleSerialized *compressed);\nstatic pg_attribute_always_inline Simple8bRleDecompressResult\nsimple8brle_decompression_iterator_try_next_forward(Simple8bRleDecompressionIterator *iter);\nstatic pg_attribute_always_inline Simple8bRleDecompressResult\nsimple8brle_decompression_iterator_try_next_reverse(Simple8bRleDecompressionIterator *iter);\n\nstatic inline void simple8brle_serialized_send(StringInfo buffer,\n\t\t\t\t\t\t\t\t\t\t\t   const Simple8bRleSerialized *data);\nstatic inline char *bytes_serialize_simple8b_and_advance(char *dest, size_t expected_size,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t const Simple8bRleSerialized *data);\nstatic inline Simple8bRleSerialized *bytes_deserialize_simple8b_and_advance(StringInfo si);\nstatic inline size_t simple8brle_serialized_slot_size(const Simple8bRleSerialized *data);\nstatic inline size_t simple8brle_serialized_total_size(const Simple8bRleSerialized *data);\n\n/*\n * Calculate the size of the compressed data with the assumption that all uncompressed\n * data is flushed and pushed already.\n */\nstatic inline size_t\nsimple8brle_compressor_compressed_size(const Simple8bRleCompressor *compressor);\n\n/*\n * Calculate the size of the compressed data without modifying the compressor and without\n * making assumptions about the compressor state.\n */\nstatic inline size_t\nsimple8brle_compressor_compressed_const_size(const Simple8bRleCompressor *compressor);\n\n/*********************\n ***  Private API  ***\n *********************/\n\ntypedef struct Simple8bRlePartiallyCompressedData\n{\n\tSimple8bRleBlock block;\n\tconst uint64 *data;\n\tuint32 data_size;\n} Simple8bRlePartiallyCompressedData;\n\n/* compressor */\nstatic inline void simple8brle_compressor_partial_flush(Simple8bRleCompressor *compressor);\nstatic inline void simple8brle_compressor_full_flush(Simple8bRleCompressor *compressor);\n\n/* block */\nstatic inline Simple8bRleBlock simple8brle_block_create(uint8 selector, uint64 data);\nstatic inline uint64 simple8brle_block_get_element(Simple8bRleBlock block,\n\t\t\t\t\t\t\t\t\t\t\t\t   uint32 position_in_value);\n\n/* utils */\nstatic inline bool simple8brle_selector_is_rle(uint8 selector);\nstatic inline uint64 simple8brle_selector_get_bitmask(uint8 selector);\nstatic inline uint32 simple8brle_bits_for_value(uint64 v);\nstatic inline uint32 simple8brle_rledata_repeatcount(uint64 rledata);\nstatic inline uint64 simple8brle_rledata_value(uint64 rledata);\nstatic uint32 simple8brle_num_selector_slots_for_num_blocks(uint32 num_blocks);\n\n/*******************************\n ***  Simple8bRleSerialized  ***\n *******************************/\n\nstatic inline Simple8bRleSerialized *\nsimple8brle_serialized_recv(StringInfo buffer)\n{\n\tuint32 i;\n\tuint32 num_elements = pq_getmsgint32(buffer);\n\tCheckCompressedData(num_elements <= GLOBAL_MAX_ROWS_PER_COMPRESSION);\n\tuint32 num_blocks = pq_getmsgint32(buffer);\n\tCheckCompressedData(num_blocks <= GLOBAL_MAX_ROWS_PER_COMPRESSION);\n\tuint32 num_selector_slots = simple8brle_num_selector_slots_for_num_blocks(num_blocks);\n\tSimple8bRleSerialized *data;\n\tSize compressed_size =\n\t\tsizeof(Simple8bRleSerialized) + (num_blocks + num_selector_slots) * sizeof(uint64);\n\tif (!AllocSizeIsValid(compressed_size))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),\n\t\t\t\t errmsg(\"compressed size exceeds the maximum allowed (%d)\", (int) MaxAllocSize)));\n\n\tdata = palloc(compressed_size);\n\tdata->num_elements = num_elements;\n\tdata->num_blocks = num_blocks;\n\n\tfor (i = 0; i < num_blocks + num_selector_slots; i++)\n\t\tdata->slots[i] = pq_getmsgint64(buffer);\n\n\treturn data;\n}\n\nstatic inline void *\nsimple8brle_serialized_recv_into(StringInfo buffer, void *dest, Simple8bRleSerialized **data_out)\n{\n\tuint32 i;\n\tuint32 num_elements = pq_getmsgint32(buffer);\n\tCheckCompressedData(num_elements <= GLOBAL_MAX_ROWS_PER_COMPRESSION);\n\tuint32 num_blocks = pq_getmsgint32(buffer);\n\tCheckCompressedData(num_blocks <= GLOBAL_MAX_ROWS_PER_COMPRESSION);\n\tuint32 num_selector_slots = simple8brle_num_selector_slots_for_num_blocks(num_blocks);\n\n\tSize compressed_size =\n\t\tsizeof(Simple8bRleSerialized) + (num_blocks + num_selector_slots) * sizeof(uint64);\n\tif (!AllocSizeIsValid(compressed_size))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),\n\t\t\t\t errmsg(\"compressed size exceeds the maximum allowed (%d)\", (int) MaxAllocSize)));\n\n\t*data_out = (Simple8bRleSerialized *) dest;\n\t(*data_out)->num_elements = num_elements;\n\t(*data_out)->num_blocks = num_blocks;\n\n\tfor (i = 0; i < num_blocks + num_selector_slots; i++)\n\t\t(*data_out)->slots[i] = pq_getmsgint64(buffer);\n\n\treturn (char *) *data_out + compressed_size;\n}\n\nstatic void\nsimple8brle_serialized_send(StringInfo buffer, const Simple8bRleSerialized *data)\n{\n\tAssert(NULL != data);\n\tuint32 num_selector_slots = simple8brle_num_selector_slots_for_num_blocks(data->num_blocks);\n\tuint32 i;\n\tpq_sendint32(buffer, data->num_elements);\n\tpq_sendint32(buffer, data->num_blocks);\n\tfor (i = 0; i < data->num_blocks + num_selector_slots; i++)\n\t\tpq_sendint64(buffer, data->slots[i]);\n}\n\nstatic char *\nbytes_serialize_simple8b_and_advance(char *dest, size_t expected_size,\n\t\t\t\t\t\t\t\t\t const Simple8bRleSerialized *data)\n{\n\tsize_t size = simple8brle_serialized_total_size(data);\n\n\tif (expected_size != size)\n\t\telog(ERROR, \"the size to serialize does not match simple8brle\");\n\n\tmemcpy(dest, data, size);\n\treturn dest + size;\n}\n\nstatic Simple8bRleSerialized *\nbytes_deserialize_simple8b_and_advance(StringInfo si)\n{\n\tSimple8bRleSerialized *serialized = consumeCompressedData(si, sizeof(Simple8bRleSerialized));\n\tconsumeCompressedData(si, simple8brle_serialized_slot_size(serialized));\n\n\tCheckCompressedData(serialized->num_elements <= GLOBAL_MAX_ROWS_PER_COMPRESSION);\n\tCheckCompressedData(serialized->num_elements > 0);\n\tCheckCompressedData(serialized->num_blocks > 0);\n\tCheckCompressedData(serialized->num_elements >= serialized->num_blocks);\n\n\treturn serialized;\n}\n\nstatic size_t\nsimple8brle_serialized_slot_size(const Simple8bRleSerialized *data)\n{\n\tif (data == NULL)\n\t\treturn 0;\n\n\tint total_slots =\n\t\tdata->num_blocks + simple8brle_num_selector_slots_for_num_blocks(data->num_blocks);\n\n\tCheckCompressedData(total_slots > 0);\n\tCheckCompressedData((uint32) total_slots < PG_INT32_MAX / sizeof(uint64));\n\n\treturn total_slots * sizeof(uint64);\n}\n\nstatic size_t\nsimple8brle_serialized_total_size(const Simple8bRleSerialized *data)\n{\n\tAssert(data != NULL);\n\treturn sizeof(*data) + simple8brle_serialized_slot_size(data);\n}\n\n/*******************************\n ***  Simple8bRleCompressor  ***\n *******************************/\n\nstatic void\nsimple8brle_compressor_init(Simple8bRleCompressor *compressor)\n{\n\t*compressor = (Simple8bRleCompressor){\n\t\t.num_elements = 0,\n\t\t.num_buffered_elements = 0,\n\t\t.last_value = 0,\n\t};\n\n\t/* Allocate the size according to the batch target */\n\tuint64_vec_init(&compressor->compressed_data,\n\t\t\t\t\tCurrentMemoryContext,\n\t\t\t\t\tTARGET_COMPRESSED_BATCH_SIZE);\n\tbit_array_init(&compressor->selectors,\n\t\t\t\t   /* expected_bits = */ (TARGET_COMPRESSED_BATCH_SIZE *\n\t\t\t\t\t\t\t\t\t\t  SIMPLE8B_BITS_PER_SELECTOR));\n}\n\nstatic inline void\nsimple8brle_compressor_partial_flush(Simple8bRleCompressor *compressor)\n{\n\tAssert(compressor->num_buffered_elements > 0);\n\n\tuint8 bit_width = 0;\n\tuint64 mask = 0;\n\tint16 max_pack = 0;\n\tuint8 selector = 0;\n\tint32 num_buffered_elements = compressor->num_buffered_elements;\n\n\tint32 i;\n\tfor (i = 0; i < num_buffered_elements; i++)\n\t{\n\t\tSimple8bRleBuffer *restrict buffer_base = &compressor->uncompressed_buffer[i];\n\t\tAssert(buffer_base[0].repcount > 0);\n\n\t\tuint8 first_bit_width = simple8brle_bits_for_value(buffer_base[0].data);\n\t\tselector = SIMPLE8B_SELECTOR_FOR_BIT_WIDTH[first_bit_width];\n\t\tbit_width = SIMPLE8B_BIT_LENGTH[selector];\n\n\t\t/* The current element times the bit width is large enough to flush. */\n\t\tif (bit_width * buffer_base[0].repcount >= SIMPLE8B_BITSIZE)\n\t\t{\n\t\t\tuint64 val = buffer_base[0].data;\n\t\t\tuint64 repcount = buffer_base[0].repcount;\n\n\t\t\t/* We can only RLE elements if they are small enough to fit in the\n\t\t\t * data part of the RLE block.\n\t\t\t */\n\t\t\tif (val <= SIMPLE8B_RLE_MAX_VALUE_MASK)\n\t\t\t{\n\t\t\t\t/* Flush the value as RLE */\n\t\t\t\tuint64 rle_block = (repcount << SIMPLE8B_RLE_MAX_VALUE_BITS) | val;\n\n\t\t\t\tbit_array_append(&compressor->selectors,\n\t\t\t\t\t\t\t\t SIMPLE8B_BITS_PER_SELECTOR,\n\t\t\t\t\t\t\t\t SIMPLE8B_RLE_SELECTOR);\n\t\t\t\tuint64_vec_append(&compressor->compressed_data, rle_block);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t/* Otherwise we need to flush each element as a full 64 bit block */\n\t\t\telse\n\t\t\t{\n\t\t\t\t/* Each repeated element needs to be flushed as separate blocks */\n\t\t\t\tfor (uint32 k = 0; k < repcount; k++)\n\t\t\t\t{\n\t\t\t\t\tbit_array_append(&compressor->selectors,\n\t\t\t\t\t\t\t\t\t SIMPLE8B_BITS_PER_SELECTOR,\n\t\t\t\t\t\t\t\t\t SIMPLE8B_FULL_BLOCK_SELECTOR);\n\t\t\t\t\tuint64_vec_append(&compressor->compressed_data, val);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tint32 num_packed = buffer_base[0].repcount;\n\t\tmax_pack = SIMPLE8B_NUM_ELEMENTS[selector];\n\t\tmask = simple8brle_selector_get_bitmask(selector);\n\n\t\tfor (int32 j = 1; (i + j) < num_buffered_elements && num_packed < max_pack; ++j)\n\t\t{\n\t\t\tAssert(buffer_base[j].repcount > 0);\n\t\t\tuint64 val = buffer_base[j].data;\n\n\t\t\twhile (val > mask)\n\t\t\t{\n\t\t\t\t/* We bumped into a value that doesn't fit, need to expand selector,\n\t\t\t\t * but we need to make sure we don't leave gaps in the packed data. */\n\t\t\t\t++selector;\n\t\t\t\tmask = simple8brle_selector_get_bitmask(selector);\n\t\t\t\tif (num_packed >= SIMPLE8B_NUM_ELEMENTS[selector])\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tmax_pack = SIMPLE8B_NUM_ELEMENTS[selector];\n\t\t\tnum_packed += buffer_base[j].repcount;\n\t\t}\n\n\t\t/* Final calculations after selector is determined */\n\t\tbit_width = SIMPLE8B_BIT_LENGTH[selector];\n\n\t\tint32 num_buffer_taken = 0;\n\t\tuint64 packed_value = 0;\n\t\tnum_packed = 0;\n\n\t\t/*\n\t\t * We need to be smart with the repcounts. The case when the repeated values\n\t\t * can fit an entire block is already handled above. Here we move the values\n\t\t * from the current buffer entry to the packed value, thus we decrease the\n\t\t * repcounts one by one.\n\t\t */\n\t\twhile (num_packed < max_pack && (i + num_buffer_taken) < num_buffered_elements)\n\t\t{\n\t\t\tSimple8bRleBuffer *restrict current_entry = &buffer_base[num_buffer_taken];\n\n\t\t\tcurrent_entry->saved_repcount = current_entry->repcount;\n\t\t\tuint64 val = current_entry->data;\n\t\t\tuint32 entry_repcount = current_entry->repcount;\n\n\t\t\twhile (entry_repcount > 0 && num_packed < max_pack)\n\t\t\t{\n\t\t\t\tpacked_value |= (val & mask) << (num_packed * bit_width);\n\t\t\t\t++num_packed;\n\t\t\t\t--entry_repcount;\n\t\t\t}\n\n\t\t\t/* Write back the updated repcount. */\n\t\t\tcurrent_entry->repcount = entry_repcount;\n\n\t\t\tif (entry_repcount > 0)\n\t\t\t\tbreak;\n\t\t\t++num_buffer_taken;\n\t\t}\n\n\t\tif (num_packed == max_pack)\n\t\t{\n\t\t\t/* Flush the packed value. */\n\t\t\tbit_array_append(&compressor->selectors, SIMPLE8B_BITS_PER_SELECTOR, selector);\n\t\t\tuint64_vec_append(&compressor->compressed_data, packed_value);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/*\n\t\t\t * If we flushed some values from the uncompressed buffer, but there are\n\t\t\t * some that didn't fit, we need to move them to the beginning of the buffer.\n\t\t\t * We need to restore the repcounts to their original values first.\n\t\t\t */\n\t\t\tfor (int32 j = 0; j < num_buffer_taken; j++)\n\t\t\t{\n\t\t\t\tbuffer_base[j].repcount = buffer_base[j].saved_repcount;\n\t\t\t}\n\n\t\t\tuint32 remaining = num_buffered_elements - i;\n\n\t\t\tif (remaining > 0)\n\t\t\t{\n\t\t\t\tmemcpy(compressor->uncompressed_buffer,\n\t\t\t\t\t   &compressor->uncompressed_buffer[i],\n\t\t\t\t\t   remaining * sizeof(Simple8bRleBuffer));\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t/* If no elements are remaining, we can reset the buffer */\n\t\t\t\tcompressor->num_buffered_elements = 0;\n\t\t\t}\n\t\t\t/* Update the number of buffered elements */\n\t\t\tcompressor->num_buffered_elements = remaining;\n\t\t\tbreak;\n\t\t}\n\n\t\ti += (num_buffer_taken - 1);\n\t}\n\n\t/* If all elements were processed, reset buffer. */\n\tif (i == num_buffered_elements)\n\t{\n\t\tcompressor->num_buffered_elements = 0;\n\t}\n}\n\nstatic inline void\nsimple8brle_compressor_full_flush(Simple8bRleCompressor *compressor)\n{\n\tAssert(compressor->num_buffered_elements > 0);\n\n\tuint8 bit_width = 0;\n\tuint64 mask = 0;\n\tint16 max_pack = 0;\n\tuint8 selector = 0;\n\tint32 num_buffered_elements = compressor->num_buffered_elements;\n\n\tfor (int32 i = 0; i < num_buffered_elements; i++)\n\t{\n\t\tSimple8bRleBuffer *restrict buffer_base = &compressor->uncompressed_buffer[i];\n\t\tAssert(buffer_base[0].repcount > 0);\n\n\t\tuint8 first_bit_width = simple8brle_bits_for_value(buffer_base[0].data);\n\t\tselector = SIMPLE8B_SELECTOR_FOR_BIT_WIDTH[first_bit_width];\n\t\tbit_width = SIMPLE8B_BIT_LENGTH[selector];\n\n\t\t/* The current element times the bit width is large enough to flush. */\n\t\tif (bit_width * buffer_base[0].repcount >= SIMPLE8B_BITSIZE)\n\t\t{\n\t\t\tuint64 val = buffer_base[0].data;\n\t\t\tuint64 repcount = buffer_base[0].repcount;\n\n\t\t\t/* We can only RLE elements if they are small enough to fit in the\n\t\t\t * data part of the RLE block.\n\t\t\t */\n\t\t\tif (val <= SIMPLE8B_RLE_MAX_VALUE_MASK)\n\t\t\t{\n\t\t\t\t/* Flush the value as RLE */\n\t\t\t\tuint64 rle_block = (repcount << SIMPLE8B_RLE_MAX_VALUE_BITS) | val;\n\n\t\t\t\tbit_array_append(&compressor->selectors,\n\t\t\t\t\t\t\t\t SIMPLE8B_BITS_PER_SELECTOR,\n\t\t\t\t\t\t\t\t SIMPLE8B_RLE_SELECTOR);\n\t\t\t\tuint64_vec_append(&compressor->compressed_data, rle_block);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t/* Otherwise we need to flush each element as a full 64 bit block */\n\t\t\telse\n\t\t\t{\n\t\t\t\t/* Each repeated element needs to be flushed as separate blocks */\n\t\t\t\tfor (uint32 k = 0; k < repcount; k++)\n\t\t\t\t{\n\t\t\t\t\tbit_array_append(&compressor->selectors,\n\t\t\t\t\t\t\t\t\t SIMPLE8B_BITS_PER_SELECTOR,\n\t\t\t\t\t\t\t\t\t SIMPLE8B_FULL_BLOCK_SELECTOR);\n\t\t\t\t\tuint64_vec_append(&compressor->compressed_data, val);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tint32 num_packed = buffer_base[0].repcount;\n\t\tmax_pack = SIMPLE8B_NUM_ELEMENTS[selector];\n\t\tmask = simple8brle_selector_get_bitmask(selector);\n\n\t\tfor (int32 j = 1; (i + j) < num_buffered_elements && num_packed < max_pack; ++j)\n\t\t{\n\t\t\tAssert(buffer_base[j].repcount > 0);\n\t\t\tuint64 val = buffer_base[j].data;\n\n\t\t\twhile (val > mask)\n\t\t\t{\n\t\t\t\t/* We bumped into a value that doesn't fit, need to expand selector,\n\t\t\t\t * but we need to make sure we don't leave gaps in the packed data. */\n\t\t\t\t++selector;\n\t\t\t\tmask = simple8brle_selector_get_bitmask(selector);\n\t\t\t\tif (num_packed >= SIMPLE8B_NUM_ELEMENTS[selector])\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tmax_pack = SIMPLE8B_NUM_ELEMENTS[selector];\n\t\t\tnum_packed += buffer_base[j].repcount;\n\t\t}\n\n\t\t/* Final calculations after selector is determined */\n\t\tbit_width = SIMPLE8B_BIT_LENGTH[selector];\n\n\t\tint32 num_buffer_taken = 0;\n\t\tuint64 packed_value = 0;\n\t\tnum_packed = 0;\n\n\t\t/*\n\t\t * This is the main difference between the partial and full flush.\n\t\t * Here we don't need to be smart with the repcounts, because we are\n\t\t * flushing a full block no matter what. No elements can remain in the\n\t\t * uncompressed buffer.\n\t\t * */\n\t\twhile (num_packed < max_pack && (i + num_buffer_taken) < num_buffered_elements)\n\t\t{\n\t\t\tuint64 val = buffer_base[num_buffer_taken].data;\n\t\t\twhile (buffer_base[num_buffer_taken].repcount > 0 && num_packed < max_pack)\n\t\t\t{\n\t\t\t\tpacked_value |= (val & mask) << (num_packed * bit_width);\n\t\t\t\t++num_packed;\n\t\t\t\tbuffer_base[num_buffer_taken].repcount--;\n\t\t\t}\n\t\t\tif (buffer_base[num_buffer_taken].repcount > 0)\n\t\t\t\tbreak;\n\t\t\t++num_buffer_taken;\n\t\t}\n\n\t\t/* Flush the packed value */\n\t\tbit_array_append(&compressor->selectors, SIMPLE8B_BITS_PER_SELECTOR, selector);\n\t\tuint64_vec_append(&compressor->compressed_data, packed_value);\n\n\t\ti += (num_buffer_taken - 1);\n\t}\n\n\tcompressor->num_buffered_elements = 0;\n}\n\nstatic void\nsimple8brle_compressor_append(Simple8bRleCompressor *compressor, uint64 val)\n{\n\tAssert(compressor != NULL);\n\n\tif (unlikely(compressor->num_buffered_elements >= SIMPLE8B_MAX_BUFFERED))\n\t{\n\t\tsimple8brle_compressor_partial_flush(compressor);\n\t}\n\n\tuint32 num_buffered = compressor->num_buffered_elements;\n\n\t/* Check for RLE against last element. This saves a few cycles instead of looking\n\t * at the last buffered entry. */\n\tif (likely(num_buffered > 0))\n\t{\n\t\tif (likely(compressor->last_value == val))\n\t\t{\n\t\t\tSimple8bRleBuffer *restrict last_entry =\n\t\t\t\t&compressor->uncompressed_buffer[num_buffered - 1];\n\n\t\t\tif (likely(val == last_entry->data))\n\t\t\t{\n\t\t\t\t/* Increment count, no new buffer entry needed */\n\t\t\t\tlast_entry->repcount++;\n\t\t\t\tcompressor->num_elements++;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\t/* New unique value - buffer it. */\n\tSimple8bRleBuffer *restrict new_entry = &compressor->uncompressed_buffer[num_buffered];\n\n\tnew_entry->data = val;\n\tnew_entry->repcount = 1;\n\n\tcompressor->num_buffered_elements = num_buffered + 1;\n\tcompressor->num_elements++;\n\tcompressor->last_value = val;\n}\n\nstatic bool\nsimple8brle_compressor_is_empty(Simple8bRleCompressor *compressor)\n{\n\treturn compressor->num_elements == 0;\n}\n\nstatic size_t\nsimple8brle_compressor_compressed_size(const Simple8bRleCompressor *compressor)\n{\n\t/* we store 16 selectors per selector_slot, and one selector_slot per compressed_data_slot.\n\t * use num_compressed_data_slots / 16 + 1 to ensure that rounding doesn't truncate our slots\n\t * and that we always have a 0 slot at the end.\n\t */\n\treturn sizeof(Simple8bRleSerialized) +\n\t\t   compressor->compressed_data.num_elements * sizeof(*compressor->compressed_data.data) +\n\t\t   bit_array_data_bytes_used(&compressor->selectors);\n}\n\nstatic size_t\nsimple8brle_compressor_compressed_const_size(const Simple8bRleCompressor *compressor)\n{\n\t/* Allocate temp space where the temp_compressor will put the data. Prefer static\n\t * allocation to avoid palloc overhead, since this is only used for size calculation.\n\t */\n#define TEMP_DATA_SIZE TARGET_COMPRESSED_BATCH_SIZE\n#define TEMP_SELECTORS_SIZE (TEMP_DATA_SIZE / SIMPLE8B_SELECTORS_PER_SELECTOR_SLOT)\n\n\tuint64 temp_data_static[TEMP_DATA_SIZE];\n\tuint64 temp_selectors_static[TEMP_SELECTORS_SIZE];\n\tuint64 temp_data_count = TEMP_DATA_SIZE;\n\tuint64 temp_selectors_count = TEMP_SELECTORS_SIZE;\n\n\tuint64 *temp_data = temp_data_static;\n\tuint64 *temp_selectors = temp_selectors_static;\n\tbool use_static = true;\n\n\tSimple8bRleCompressor temp_compressor = *compressor;\n\n\t/* Replace the data and selectors with the temp space.*/\n\ttemp_compressor.compressed_data.data = temp_data;\n\ttemp_compressor.compressed_data.num_elements = 0;\n\ttemp_compressor.compressed_data.max_elements = TEMP_DATA_SIZE;\n\ttemp_compressor.selectors.buckets.data = temp_selectors;\n\ttemp_compressor.selectors.buckets.num_elements = 0;\n\ttemp_compressor.selectors.buckets.max_elements = TEMP_SELECTORS_SIZE;\n\ttemp_compressor.selectors.bits_used_in_last_bucket = 0;\n\n\t/* If the compressor is empty, we can return 0, similar to finish. */\n\tif (temp_compressor.num_elements == 0)\n\t\treturn 0;\n\n\t/*\n\t * If the compressor has no uncompressed data, we can use the original size calculation.\n\t * Note that after every append, it is guaranteed that we have at least one uncompressed\n\t * element. Not having uncompressed elements can only happen if the compressor is empty\n\t * or finish was called, so we can use the original size calculation.\n\t */\n\tif (compressor->num_buffered_elements == 0)\n\t\treturn simple8brle_compressor_compressed_size(compressor);\n\n\t/* Because the buffering allows RLE entries to be stored, the worst case scenario for\n\t * the buffer size is determined by the repcounts in the buffered elements.\n\t */\n\tuint32 actual_buffered = 0;\n\tfor (uint32 i = 0; i < compressor->num_buffered_elements; i++)\n\t{\n\t\tactual_buffered += compressor->uncompressed_buffer[i].repcount;\n\t}\n\n\tif (actual_buffered > temp_data_count)\n\t{\n\t\t/* We need to increase the temp buffer and allocate it dynamically.\n\t\t * This can only happen if the TARGET_COMPRESSED_BATCH_SIZE is smaller\n\t\t * than the actual buffered elements, which is unlikely.\n\t\t */\n\t\ttemp_data_count = actual_buffered;\n\t\ttemp_data = palloc(temp_data_count * sizeof(uint64));\n\n\t\t/* Similarly the selectors */\n\t\ttemp_selectors_count = (actual_buffered + 15) / 16;\n\t\ttemp_selectors = palloc(temp_selectors_count * sizeof(uint64));\n\n\t\tuse_static = false;\n\t}\n\n\t/* Flush the compressor to ensure all uncompressed data is compressed into the temp_compressor.\n\t */\n\tsimple8brle_compressor_full_flush(&temp_compressor);\n\n\tsize_t num_data_blocks =\n\t\tcompressor->compressed_data.num_elements + temp_compressor.compressed_data.num_elements;\n\n\t/* Add up the size of the compressed data blocks and the header. */\n\tsize_t result = sizeof(Simple8bRleSerialized) +\n\t\t\t\t\tnum_data_blocks * sizeof(*temp_compressor.compressed_data.data);\n\n\t/* Add up the selector bits. */\n\tsize_t selector_bits = num_data_blocks * SIMPLE8B_BITS_PER_SELECTOR;\n\tresult += ((selector_bits + 63) / 64) * sizeof(uint64);\n\n#undef TEMP_DATA_SIZE\n#undef TEMP_SELECTORS_SIZE\n\n\tif (!use_static)\n\t{\n\t\t/* If we allocated dynamically, we need to free the memory. */\n\t\tpfree(temp_data);\n\t\tpfree(temp_selectors);\n\t}\n\n\treturn result;\n}\n\nstatic inline uint32\nsimple8brle_compressor_num_selectors(Simple8bRleCompressor *compressor)\n{\n\tAssert(bit_array_num_bits(&compressor->selectors) % SIMPLE8B_BITS_PER_SELECTOR == 0);\n\treturn bit_array_num_bits(&compressor->selectors) / SIMPLE8B_BITS_PER_SELECTOR;\n}\n\nstatic Simple8bRleSerialized *\nsimple8brle_compressor_finish(Simple8bRleCompressor *compressor)\n{\n\tsize_t size_left;\n\tsize_t selector_size;\n\tsize_t compressed_size;\n\tSimple8bRleSerialized *compressed;\n\tuint64 bits;\n\n\tif (compressor->num_elements == 0)\n\t\treturn NULL;\n\n\t/* Flush any remaining state */\n\tif (compressor->num_buffered_elements > 0)\n\t{\n\t\tsimple8brle_compressor_full_flush(compressor);\n\t}\n\n\tAssert(compressor->num_buffered_elements == 0);\n\n\tcompressed_size = simple8brle_compressor_compressed_size(compressor);\n\t/* we use palloc0 despite initializing the entire structure,\n\t * to ensure padding bits are zeroed, and that there's a 0 selector at the end.\n\t * It would be more efficient to ensure there are no padding bits in the struct,\n\t * and initialize everything ourselves\n\t */\n\tcompressed = palloc0(compressed_size);\n\tAssert(bit_array_num_buckets(&compressor->selectors) > 0);\n\tAssert(compressor->compressed_data.num_elements > 0);\n\tAssert(compressor->compressed_data.num_elements ==\n\t\t   simple8brle_compressor_num_selectors(compressor));\n\n\t*compressed = (Simple8bRleSerialized){\n\t\t.num_elements = compressor->num_elements,\n\t\t.num_blocks = compressor->compressed_data.num_elements,\n\t};\n\n\tsize_left = compressed_size - sizeof(*compressed);\n\tAssert(size_left >= bit_array_data_bytes_used(&compressor->selectors));\n\tselector_size = bit_array_output(&compressor->selectors, compressed->slots, size_left, &bits);\n\n\tsize_left -= selector_size;\n\tAssert(size_left ==\n\t\t   (compressor->compressed_data.num_elements * sizeof(*compressor->compressed_data.data)));\n\tAssert(compressor->selectors.buckets.num_elements ==\n\t\t   simple8brle_num_selector_slots_for_num_blocks(compressor->compressed_data.num_elements));\n\n\tmemcpy(compressed->slots + compressor->selectors.buckets.num_elements,\n\t\t   compressor->compressed_data.data,\n\t\t   size_left);\n\n\treturn compressed;\n}\n\nstatic char *\nsimple8brle_compressor_finish_into(Simple8bRleCompressor *compressor, char *dest,\n\t\t\t\t\t\t\t\t   size_t expected_size)\n{\n\tsize_t size_left;\n\tsize_t selector_size;\n\tsize_t compressed_size;\n\tchar *end_ptr;\n\tSimple8bRleSerialized *compressed;\n\tuint64 bits;\n\n\tif (compressor->num_elements == 0)\n\t\treturn NULL;\n\n\tEnsure(dest != NULL, \"dest is NULL\");\n\n\t/* Flush any remaining state */\n\tif (compressor->num_buffered_elements > 0)\n\t{\n\t\tsimple8brle_compressor_full_flush(compressor);\n\t}\n\n\tAssert(compressor->num_buffered_elements == 0);\n\n\tcompressed_size = simple8brle_compressor_compressed_size(compressor);\n\tEnsure(expected_size == compressed_size,\n\t\t   \"expected_size: %zu, compressed_size: %zu\",\n\t\t   expected_size,\n\t\t   compressed_size);\n\n\tcompressed = (Simple8bRleSerialized *) dest;\n\tAssert(bit_array_num_buckets(&compressor->selectors) > 0);\n\tAssert(compressor->compressed_data.num_elements > 0);\n\tAssert(compressor->compressed_data.num_elements ==\n\t\t   simple8brle_compressor_num_selectors(compressor));\n\n\t*compressed = (Simple8bRleSerialized){\n\t\t.num_elements = compressor->num_elements,\n\t\t.num_blocks = compressor->compressed_data.num_elements,\n\t};\n\n\tsize_left = compressed_size - sizeof(*compressed);\n\tAssert(size_left >= bit_array_data_bytes_used(&compressor->selectors));\n\tselector_size = bit_array_output(&compressor->selectors, compressed->slots, size_left, &bits);\n\n\tsize_left -= selector_size;\n\tAssert(size_left ==\n\t\t   (compressor->compressed_data.num_elements * sizeof(*compressor->compressed_data.data)));\n\tAssert(compressor->selectors.buckets.num_elements ==\n\t\t   simple8brle_num_selector_slots_for_num_blocks(compressor->compressed_data.num_elements));\n\n\tmemcpy(compressed->slots + compressor->selectors.buckets.num_elements,\n\t\t   compressor->compressed_data.data,\n\t\t   size_left);\n\n\tend_ptr = (char *) (compressed->slots + compressor->selectors.buckets.num_elements) + size_left;\n\tAssert(end_ptr == dest + expected_size);\n\n\treturn end_ptr;\n}\n\n/******************************************\n ***  Simple8bRleDecompressionIterator  ***\n ******************************************/\n\nstatic void\nsimple8brle_decompression_iterator_init_common(Simple8bRleDecompressionIterator *iter,\n\t\t\t\t\t\t\t\t\t\t\t   Simple8bRleSerialized *compressed)\n{\n\tuint32 num_selector_slots =\n\t\tsimple8brle_num_selector_slots_for_num_blocks(compressed->num_blocks);\n\n\t*iter = (Simple8bRleDecompressionIterator){\n\t\t.compressed_data = compressed->slots + num_selector_slots,\n\t\t.num_blocks = compressed->num_blocks,\n\t\t.current_compressed_pos = 0,\n\t\t.current_in_compressed_pos = 0,\n\t\t.num_elements = compressed->num_elements,\n\t\t.num_elements_returned = 0,\n\t};\n\n\tbit_array_wrap(&iter->selector_data,\n\t\t\t\t   compressed->slots,\n\t\t\t\t   compressed->num_blocks * SIMPLE8B_BITS_PER_SELECTOR);\n}\n\nstatic void\nsimple8brle_decompression_iterator_init_forward(Simple8bRleDecompressionIterator *iter,\n\t\t\t\t\t\t\t\t\t\t\t\tSimple8bRleSerialized *compressed)\n{\n\tsimple8brle_decompression_iterator_init_common(iter, compressed);\n\tbit_array_iterator_init(&iter->selectors, &iter->selector_data);\n}\n\nstatic uint32\nsimple8brle_decompression_iterator_max_elements(Simple8bRleDecompressionIterator *iter,\n\t\t\t\t\t\t\t\t\t\t\t\tconst Simple8bRleSerialized *compressed)\n{\n\tBitArrayIterator selectors;\n\tuint32 max_stored = 0;\n\tuint32 i;\n\n\tAssert(compressed->num_blocks > 0);\n\n\tbit_array_iterator_init(&selectors, iter->selectors.array);\n\tfor (i = 0; i < compressed->num_blocks; i++)\n\t{\n\t\tuint8 selector = bit_array_iter_next(&selectors, SIMPLE8B_BITS_PER_SELECTOR);\n\t\tif (selector == 0)\n\t\t\telog(ERROR, \"invalid selector 0\");\n\n\t\tif (simple8brle_selector_is_rle(selector) && iter->compressed_data)\n\t\t{\n\t\t\tAssert(simple8brle_rledata_repeatcount(iter->compressed_data[i]) > 0);\n\t\t\tmax_stored += simple8brle_rledata_repeatcount(iter->compressed_data[i]);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tAssert(selector < SIMPLE8B_MAXCODE);\n\t\t\tmax_stored += SIMPLE8B_NUM_ELEMENTS[selector];\n\t\t}\n\t}\n\treturn max_stored;\n}\n\nstatic void\nsimple8brle_decompression_iterator_init_reverse(Simple8bRleDecompressionIterator *iter,\n\t\t\t\t\t\t\t\t\t\t\t\tSimple8bRleSerialized *compressed)\n{\n\tint32 skipped_in_last;\n\tsimple8brle_decompression_iterator_init_common(iter, compressed);\n\tbit_array_iterator_init_rev(&iter->selectors, &iter->selector_data);\n\tskipped_in_last = simple8brle_decompression_iterator_max_elements(iter, compressed) -\n\t\t\t\t\t  compressed->num_elements;\n\n\tAssert(NULL != iter->compressed_data);\n\n\titer->current_block =\n\t\tsimple8brle_block_create(bit_array_iter_next_rev(&iter->selectors,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t SIMPLE8B_BITS_PER_SELECTOR),\n\t\t\t\t\t\t\t\t iter->compressed_data[compressed->num_blocks - 1]);\n\titer->current_in_compressed_pos =\n\t\titer->current_block.num_elements_compressed - 1 - skipped_in_last;\n\titer->current_compressed_pos = compressed->num_blocks - 2;\n\treturn;\n}\n\n/* returning a struct produces noticeably better assembly on x86_64 than returning\n * is_done and is_null via pointers; it uses two registers instead of any memory reads.\n * Since it is also easier to read, we prefer it here.\n */\nstatic Simple8bRleDecompressResult\nsimple8brle_decompression_iterator_try_next_forward(Simple8bRleDecompressionIterator *iter)\n{\n\tuint64 uncompressed;\n\tif (iter->num_elements_returned >= iter->num_elements)\n\t\treturn (Simple8bRleDecompressResult){\n\t\t\t.is_done = true,\n\t\t};\n\n\tif ((uint32) iter->current_in_compressed_pos >= iter->current_block.num_elements_compressed)\n\t{\n\t\tCheckCompressedData(iter->current_compressed_pos < iter->num_blocks);\n\n\t\titer->current_block =\n\t\t\tsimple8brle_block_create(bit_array_iter_next(&iter->selectors,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t SIMPLE8B_BITS_PER_SELECTOR),\n\t\t\t\t\t\t\t\t\t iter->compressed_data[iter->current_compressed_pos]);\n\t\tCheckCompressedData(iter->current_block.selector != 0);\n\t\tCheckCompressedData(iter->current_block.num_elements_compressed <=\n\t\t\t\t\t\t\tGLOBAL_MAX_ROWS_PER_COMPRESSION);\n\t\titer->current_compressed_pos += 1;\n\t\titer->current_in_compressed_pos = 0;\n\t}\n\n\tuncompressed =\n\t\tsimple8brle_block_get_element(iter->current_block, iter->current_in_compressed_pos);\n\titer->num_elements_returned += 1;\n\titer->current_in_compressed_pos += 1;\n\n\treturn (Simple8bRleDecompressResult){\n\t\t.val = uncompressed,\n\t};\n}\n\nstatic Simple8bRleDecompressResult\nsimple8brle_decompression_iterator_try_next_reverse(Simple8bRleDecompressionIterator *iter)\n{\n\tuint64 uncompressed;\n\tif (iter->num_elements_returned >= iter->num_elements)\n\t\treturn (Simple8bRleDecompressResult){\n\t\t\t.is_done = true,\n\t\t};\n\n\tif (iter->current_in_compressed_pos < 0)\n\t{\n\t\titer->current_block =\n\t\t\tsimple8brle_block_create(bit_array_iter_next_rev(&iter->selectors,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t SIMPLE8B_BITS_PER_SELECTOR),\n\t\t\t\t\t\t\t\t\t iter->compressed_data[iter->current_compressed_pos]);\n\t\titer->current_in_compressed_pos = iter->current_block.num_elements_compressed - 1;\n\t\titer->current_compressed_pos -= 1;\n\t}\n\n\tuncompressed =\n\t\tsimple8brle_block_get_element(iter->current_block, iter->current_in_compressed_pos);\n\titer->num_elements_returned += 1;\n\titer->current_in_compressed_pos -= 1;\n\n\treturn (Simple8bRleDecompressResult){\n\t\t.val = uncompressed,\n\t};\n}\n\n/**************************\n ***  Simple8bRleBlock  ***\n **************************/\n\nstatic pg_attribute_always_inline Simple8bRleBlock\nsimple8brle_block_create(uint8 selector, uint64 data)\n{\n\tSimple8bRleBlock block = (Simple8bRleBlock){\n\t\t.selector = selector,\n\t\t.data = data,\n\t};\n\n\tif (simple8brle_selector_is_rle(block.selector))\n\t{\n\t\tblock.num_elements_compressed = simple8brle_rledata_repeatcount(block.data);\n\t}\n\telse\n\t{\n\t\tblock.num_elements_compressed = SIMPLE8B_NUM_ELEMENTS[block.selector];\n\t}\n\n\treturn block;\n}\n\nstatic inline uint64\nsimple8brle_block_get_element(Simple8bRleBlock block, uint32 position_in_value)\n{\n\t/* we're using 0 for end-of-stream, but haven't decided what to use it for */\n\tif (block.selector == 0)\n\t{\n\t\telog(ERROR, \"end of compressed integer stream\");\n\t}\n\telse if (simple8brle_selector_is_rle(block.selector))\n\t{\n\t\t/* decode rle-encoded integers */\n\t\tuint64 repeated_value = simple8brle_rledata_value(block.data);\n\t\tCheckCompressedData(simple8brle_rledata_repeatcount(block.data) > 0);\n\t\tAssert(simple8brle_rledata_repeatcount(block.data) > position_in_value);\n\t\treturn repeated_value;\n\t}\n\telse\n\t{\n\t\tuint64 compressed_value = block.data;\n\t\tuint32 bits_per_val = SIMPLE8B_BIT_LENGTH[block.selector];\n\t\t/* decode bit-packed integers*/\n\t\tAssert(position_in_value < SIMPLE8B_NUM_ELEMENTS[block.selector]);\n\t\tcompressed_value >>= bits_per_val * position_in_value;\n\t\tcompressed_value &= simple8brle_selector_get_bitmask(block.selector);\n\t\treturn compressed_value;\n\t}\n\n\tpg_unreachable();\n}\n\n/***************************\n ***  Utility Functions  ***\n ***************************/\n\nstatic pg_attribute_always_inline bool\nsimple8brle_selector_is_rle(uint8 selector)\n{\n\treturn selector == SIMPLE8B_RLE_SELECTOR;\n}\n\nstatic pg_attribute_always_inline uint32\nsimple8brle_rledata_repeatcount(uint64 rledata)\n{\n\treturn (uint32) ((rledata >> SIMPLE8B_RLE_MAX_VALUE_BITS) & SIMPLE8B_RLE_MAX_COUNT_MASK);\n}\n\nstatic pg_attribute_always_inline uint64\nsimple8brle_rledata_value(uint64 rledata)\n{\n\treturn rledata & SIMPLE8B_RLE_MAX_VALUE_MASK;\n}\n\nstatic pg_attribute_always_inline uint64\nsimple8brle_selector_get_bitmask(uint8 selector)\n{\n\tuint8 bitLen = SIMPLE8B_BIT_LENGTH[selector];\n\tAssert(bitLen != 0);\n\tuint64 result = ((~0ULL) >> (64 - bitLen));\n\treturn result;\n}\n\nstatic pg_attribute_always_inline uint32\nsimple8brle_num_selector_slots_for_num_blocks(uint32 num_blocks)\n{\n\treturn (num_blocks / SIMPLE8B_SELECTORS_PER_SELECTOR_SLOT) +\n\t\t   (num_blocks % SIMPLE8B_SELECTORS_PER_SELECTOR_SLOT != 0 ? 1 : 0);\n}\n\n#ifdef HAVE_BUILTIN_CLZLL\nstatic inline uint32\nsimple8brle_bits_for_value(uint64 v)\n{\n\tif (v == 0)\n\t\treturn 0;\n\treturn 64 - __builtin_clzll(v);\n}\n#else\nstatic inline uint32\nsimple8brle_bits_for_value(uint64 v)\n{\n\tuint32 r = 0;\n\tif (v >= (1U << 31))\n\t{\n\t\tv >>= 32;\n\t\tr += 32;\n\t}\n\tif (v >= (1U << 15))\n\t{\n\t\tv >>= 16;\n\t\tr += 16;\n\t}\n\tif (v >= (1U << 7))\n\t{\n\t\tv >>= 8;\n\t\tr += 8;\n\t}\n\tif (v >= (1U << 3))\n\t{\n\t\tv >>= 4;\n\t\tr += 4;\n\t}\n\tif (v >= (1U << 1))\n\t{\n\t\tv >>= 2;\n\t\tr += 2;\n\t}\n\tif (v >= (1U << 0))\n\t{\n\t\tv >>= 1;\n\t\tr += 1;\n\t}\n\treturn r;\n}\n#endif\n"
  },
  {
    "path": "tsl/src/compression/algorithms/simple8b_rle_bitarray.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include \"simple8b_rle.h\"\n\n/*\n * This is a specialization of Simple8bRLE decoder for encoded 1 bit values\n * as they are used to store NULL flags in the compression methods as well as\n * the values for bool compression.\n *\n * Note that in the bool compression we store a validity map instead of a NULL\n * map, which is the same except the bits are inverted.\n *\n * The goal of this decoder is to support the following use cases:\n *\n *  1. Decompress the validity map of the bool compression method.\n *  2. Decompress the values of the bool compression method.\n *  3. Decompress the NULL map of the other compression methods into a validity\n *     map in the ArrowArray. In this case the bits will be inverted.\n *\n * The reason we don't use the Simple8bRleBitmap is that the end result is an\n * array of bits and not bools.\n *\n * The complication comes from the RLE encoding of Simple8b while in the Arrow\n * validity bitmaps we have a straight array of bits.\n */\n\ntypedef struct Simple8bRleBitArray\n{\n\tuint64 *data;\n\tuint32 num_elements;\n\tuint32 num_blocks;\n\tuint16 num_ones;\n} Simple8bRleBitArray;\n\nstatic Simple8bRleBitArray\nsimple8brle_bitarray_decompress(Simple8bRleSerialized *compressed, bool inverted)\n{\n\tSimple8bRleBitArray result = { 0 };\n\tif (!compressed)\n\t{\n\t\treturn result;\n\t}\n\n\tCheckCompressedData(compressed->num_elements <= GLOBAL_MAX_ROWS_PER_COMPRESSION);\n\tCheckCompressedData(compressed->num_blocks <= GLOBAL_MAX_ROWS_PER_COMPRESSION);\n\n\tconst uint32 num_elements = compressed->num_elements;\n\n\tconst uint32 num_selector_slots =\n\t\tsimple8brle_num_selector_slots_for_num_blocks(compressed->num_blocks);\n\tconst uint64 *compressed_data = compressed->slots + num_selector_slots;\n\n\tconst uint32 num_elements_padded = ((num_elements + 63) / 64 + 1) * 64;\n\tconst uint32 num_blocks = compressed->num_blocks;\n\n\tresult.data = palloc0(num_elements_padded / 64 * sizeof(uint64));\n\tresult.num_elements = num_elements;\n\tresult.num_blocks = num_blocks;\n\n\tuint64 *restrict current_output_ptr = result.data;\n\tuint32 decompressed_index = 0;\n\tuint32 bit_position = 0;\n\n\tfor (uint32 block_index = 0; block_index < num_blocks; block_index++)\n\t{\n\t\tconst uint32 selector_slot = block_index / SIMPLE8B_SELECTORS_PER_SELECTOR_SLOT;\n\t\tconst uint32 selector_pos_in_slot = block_index % SIMPLE8B_SELECTORS_PER_SELECTOR_SLOT;\n\t\tconst uint64 slot_value = compressed->slots[selector_slot];\n\t\tconst uint8 selector_shift = selector_pos_in_slot * SIMPLE8B_BITS_PER_SELECTOR;\n\t\tconst uint64 selector_mask = 0xFULL << selector_shift;\n\t\tconst uint8 selector_value = (slot_value & selector_mask) >> selector_shift;\n\t\tAssert(selector_value < 16);\n\n\t\tuint64 block_data = compressed_data[block_index];\n\n\t\tif (simple8brle_selector_is_rle(selector_value))\n\t\t{\n\t\t\t/*\n\t\t\t * RLE block.\n\t\t\t */\n\t\t\tuint32 repeat_count = simple8brle_rledata_repeatcount(block_data);\n\t\t\tCheckCompressedData(repeat_count <= GLOBAL_MAX_ROWS_PER_COMPRESSION);\n\n\t\t\t/*\n\t\t\t * We might get an incorrect value from the corrupt data. Explicitly\n\t\t\t * truncate it to 0/1 in case the bool is not a standard bool type\n\t\t\t * which would have done it for us.\n\t\t\t */\n\t\t\tconst bool repeated_value = simple8brle_rledata_value(block_data) & 1;\n\t\t\tconst bool bit_value = repeated_value ^ inverted;\n\n\t\t\tCheckCompressedData(decompressed_index + repeat_count <= num_elements);\n\n\t\t\tif (bit_value)\n\t\t\t{\n\t\t\t\tresult.num_ones += repeat_count;\n\n\t\t\t\t/* Repeated 'ones' repeat_count times */\n\t\t\t\tif ((repeat_count + bit_position) >= 64)\n\t\t\t\t{\n\t\t\t\t\t/* Head: Fill the remaining bits in the current word if not aligned */\n\t\t\t\t\tif (bit_position > 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tuint64_t head_bits = 64 - bit_position;\n\t\t\t\t\t\tuint64_t head_mask = (1ULL << head_bits) - 1;\n\t\t\t\t\t\t*current_output_ptr |= (head_mask << bit_position);\n\t\t\t\t\t\trepeat_count -= head_bits;\n\t\t\t\t\t\tdecompressed_index += head_bits;\n\t\t\t\t\t\tcurrent_output_ptr++;\n\t\t\t\t\t\tbit_position = 0;\n\t\t\t\t\t}\n\n\t\t\t\t\t/* Middle: Fill complete words */\n\t\t\t\t\tuint64_t full_words = repeat_count / 64;\n\t\t\t\t\tfor (uint64_t j = 0; j < full_words; j++)\n\t\t\t\t\t{\n\t\t\t\t\t\t*current_output_ptr = 0xFFFFFFFFFFFFFFFF;\n\t\t\t\t\t\tcurrent_output_ptr++;\n\t\t\t\t\t}\n\n\t\t\t\t\tdecompressed_index += full_words * 64;\n\t\t\t\t\trepeat_count -= full_words * 64;\n\t\t\t\t}\n\n\t\t\t\t/* Tail: Handle remaining bits (less than 64) */\n\t\t\t\tif (repeat_count > 0)\n\t\t\t\t{\n\t\t\t\t\tAssert(repeat_count < 64);\n\t\t\t\t\tuint64_t tail_mask = (1ULL << (repeat_count & 63)) - 1;\n\t\t\t\t\t*current_output_ptr |= (tail_mask << bit_position);\n\n\t\t\t\t\tdecompressed_index += repeat_count;\n\t\t\t\t\tbit_position = (bit_position + repeat_count) % 64;\n\t\t\t\t\tif (bit_position == 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tcurrent_output_ptr++;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tcurrent_output_ptr = result.data + (decompressed_index / 64);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tdecompressed_index += repeat_count;\n\t\t\t\tbit_position = decompressed_index % 64;\n\t\t\t\tcurrent_output_ptr = result.data + (decompressed_index / 64);\n\t\t\t}\n\t\t\tAssert(decompressed_index <= num_elements);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/*\n\t\t\t * Bit-packed block. Since this is a bitmap, this block has 64 bits\n\t\t\t * packed. The last block might contain less than maximal possible\n\t\t\t * number of elements, but we have 64 bytes of padding on the right\n\t\t\t * so we don't care.\n\t\t\t */\n\t\t\tCheckCompressedData(selector_value == 1);\n\n\t\t\tAssert(SIMPLE8B_BIT_LENGTH[selector_value] == 1);\n\t\t\tAssert(SIMPLE8B_NUM_ELEMENTS[selector_value] == 64);\n\n\t\t\t/*\n\t\t\t * We should require at least one element from the block. Previous\n\t\t\t * blocks might have had incorrect lengths, so this is not an\n\t\t\t * assertion.\n\t\t\t */\n\t\t\tCheckCompressedData(decompressed_index < num_elements);\n\t\t\tCheckCompressedData(decompressed_index + 64 < num_elements_padded);\n\n\t\t\t/* Have to zero out the unused bits, so that the popcnt works properly. */\n\t\t\tconst int elements_this_block = Min(64, num_elements - decompressed_index);\n\t\t\tAssert(elements_this_block <= 64);\n\t\t\tAssert(elements_this_block > 0);\n\n\t\t\tblock_data = block_data ^ -(uint64_t) inverted;\n\t\t\tblock_data &= (~0ULL) >> (64 - elements_this_block);\n\n\t\t\tif (bit_position == 0)\n\t\t\t{\n\t\t\t\t/* The decoding is on exact 64bit boundaries */\n\t\t\t\t*current_output_ptr = block_data;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t/* We need to split the word */\n\t\t\t\tuint64_t bits_remaining_in_word = 64 - bit_position;\n\n\t\t\t\t/* First part goes at the end of the current word */\n\t\t\t\t*current_output_ptr |= (block_data << bit_position);\n\n\t\t\t\t/* Second part goes at the beginning of the next word */\n\t\t\t\t*(current_output_ptr + 1) |= block_data >> bits_remaining_in_word;\n\t\t\t}\n\n#ifdef HAVE__BUILTIN_POPCOUNT\n\t\t\tresult.num_ones += __builtin_popcountll(block_data);\n#else\n\t\t\tfor (uint16 i = 0; i < 64; i++)\n\t\t\t\tresult.num_ones += ((block_data >> i) & 1);\n#endif\n\t\t\tdecompressed_index += 64;\n\t\t\tbit_position = decompressed_index % 64;\n\t\t\tcurrent_output_ptr = result.data + (decompressed_index / 64);\n\t\t}\n\t}\n\n\t/*\n\t * We might have unpacked more because we work in full blocks, but at least\n\t * we shouldn't have unpacked less.\n\t */\n\tCheckCompressedData(decompressed_index >= num_elements);\n\tAssert(decompressed_index <= num_elements_padded);\n\n\t/*\n\t * Might happen if we have stray ones in the higher unused bits of the last\n\t * block.\n\t */\n\tCheckCompressedData(result.num_ones <= num_elements);\n\treturn result;\n}\n"
  },
  {
    "path": "tsl/src/compression/algorithms/simple8b_rle_bitmap.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n/*\n * This is a specialization of Simple8bRLE decoder for bitmaps, i.e. where the\n * elements are only 0 and 1. It also counts the number of ones.\n */\n\n#include \"simple8b_rle.h\"\n\ntypedef struct Simple8bRleBitmap\n{\n\t/* Either the bools or prefix sums, depending on the decompression method. */\n\tconst void *data;\n\n\tuint16 num_elements;\n\tuint16 num_ones;\n} Simple8bRleBitmap;\n\npg_attribute_always_inline static bool\nsimple8brle_bitmap_get_at(const Simple8bRleBitmap *bitmap, uint16 i)\n{\n\t/* We have some padding on the right but we shouldn't overrun it. */\n\tAssert(i < ((bitmap->num_elements + 63) / 64 + 1) * 64);\n\n\treturn ((const bool *) bitmap->data)[i];\n}\n\npg_attribute_always_inline static uint16\nsimple8brle_bitmap_prefix_sum(const Simple8bRleBitmap *bitmap, uint16 i)\n{\n\tAssert(i < ((bitmap->num_elements + 63) / 64 + 1) * 64);\n\treturn ((const uint16 *) bitmap->data)[i];\n}\n\npg_attribute_always_inline static uint16\nsimple8brle_bitmap_num_ones(const Simple8bRleBitmap *bitmap)\n{\n\treturn bitmap->num_ones;\n}\n\n/*\n * Calculate prefix sum of bits instead of bitmap itself, because it's more\n * useful for gorilla decompression. Can be unused by other users of this\n * header.\n */\nstatic Simple8bRleBitmap simple8brle_bitmap_prefixsums(Simple8bRleSerialized *compressed)\n\tpg_attribute_unused();\n\nstatic Simple8bRleBitmap\nsimple8brle_bitmap_prefixsums(Simple8bRleSerialized *compressed)\n{\n\tCheckCompressedData(compressed->num_elements <= GLOBAL_MAX_ROWS_PER_COMPRESSION);\n\tCheckCompressedData(compressed->num_blocks <= GLOBAL_MAX_ROWS_PER_COMPRESSION);\n\n\tconst uint32 num_elements = compressed->num_elements;\n\n\tconst uint32 num_selector_slots =\n\t\tsimple8brle_num_selector_slots_for_num_blocks(compressed->num_blocks);\n\tconst uint64 *compressed_data = compressed->slots + num_selector_slots;\n\n\t/*\n\t * Pad to next multiple of 64 bytes on the right, so that we can simplify the\n\t * decompression loop and the get() function. Note that for get() we need at\n\t * least one byte of padding, hence the next multiple.\n\t */\n\tconst uint32 num_elements_padded = ((num_elements + 63) / 64 + 1) * 64;\n\tconst uint32 num_blocks = compressed->num_blocks;\n\n\tuint16 *restrict prefix_sums = palloc(sizeof(uint16) * num_elements_padded);\n\n\tuint32 num_ones = 0;\n\tuint32 decompressed_index = 0;\n\tfor (uint32 block_index = 0; block_index < num_blocks; block_index++)\n\t{\n\t\tconst uint32 selector_slot = block_index / SIMPLE8B_SELECTORS_PER_SELECTOR_SLOT;\n\t\tconst uint32 selector_pos_in_slot = block_index % SIMPLE8B_SELECTORS_PER_SELECTOR_SLOT;\n\t\tconst uint64 slot_value = compressed->slots[selector_slot];\n\t\tconst uint8 selector_shift = selector_pos_in_slot * SIMPLE8B_BITS_PER_SELECTOR;\n\t\tconst uint64 selector_mask = 0xFULL << selector_shift;\n\t\tconst uint8 selector_value = (slot_value & selector_mask) >> selector_shift;\n\t\tAssert(selector_value < 16);\n\n\t\tuint64 block_data = compressed_data[block_index];\n\n\t\tif (simple8brle_selector_is_rle(selector_value))\n\t\t{\n\t\t\t/*\n\t\t\t * RLE block.\n\t\t\t */\n\t\t\tconst uint32 n_block_values = simple8brle_rledata_repeatcount(block_data);\n\t\t\tCheckCompressedData(n_block_values <= GLOBAL_MAX_ROWS_PER_COMPRESSION);\n\n\t\t\t/*\n\t\t\t * We might get an incorrect value from the corrupt data. Explicitly\n\t\t\t * truncate it to 0/1 in case the bool is not a standard bool type\n\t\t\t * which would have done it for us.\n\t\t\t */\n\t\t\tconst bool repeated_value = simple8brle_rledata_value(block_data) & 1;\n\n\t\t\tCheckCompressedData(decompressed_index + n_block_values <= num_elements);\n\n\t\t\tif (repeated_value)\n\t\t\t{\n\t\t\t\tfor (uint32 i = 0; i < n_block_values; i++)\n\t\t\t\t{\n\t\t\t\t\tprefix_sums[decompressed_index + i] = num_ones + i + 1;\n\t\t\t\t}\n\n\t\t\t\tnum_ones += n_block_values;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tfor (uint32 i = 0; i < n_block_values; i++)\n\t\t\t\t{\n\t\t\t\t\tprefix_sums[decompressed_index + i] = num_ones;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdecompressed_index += n_block_values;\n\t\t\tAssert(decompressed_index <= num_elements);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/*\n\t\t\t * Bit-packed block. Since this is a bitmap, this block has 64 bits\n\t\t\t * packed. The last block might contain less than maximal possible\n\t\t\t * number of elements, but we have 64 bytes of padding on the right\n\t\t\t * so we don't care.\n\t\t\t */\n\t\t\tCheckCompressedData(selector_value == 1);\n\n\t\t\tAssert(SIMPLE8B_BIT_LENGTH[selector_value] == 1);\n\t\t\tAssert(SIMPLE8B_NUM_ELEMENTS[selector_value] == 64);\n\n\t\t\t/*\n\t\t\t * We should require at least one element from the block. Previous\n\t\t\t * blocks might have had incorrect lengths, so this is not an\n\t\t\t * assertion.\n\t\t\t */\n\t\t\tCheckCompressedData(decompressed_index < num_elements);\n\n\t\t\t/* Have to zero out the unused bits, so that the popcnt works properly. */\n\t\t\tconst int elements_this_block = Min(64, num_elements - decompressed_index);\n\t\t\tAssert(elements_this_block <= 64);\n\t\t\tAssert(elements_this_block > 0);\n\t\t\tblock_data &= (~0ULL) >> (64 - elements_this_block);\n\n\t\t\t/*\n\t\t\t * The number of block elements should fit within padding. Previous\n\t\t\t * blocks might have had incorrect lengths, so this is not an\n\t\t\t * assertion.\n\t\t\t */\n\t\t\tCheckCompressedData(decompressed_index + 64 < num_elements_padded);\n\n#ifdef HAVE__BUILTIN_POPCOUNT\n\t\t\tfor (uint16 i = 0; i < 64; i++)\n\t\t\t{\n\t\t\t\tconst uint16 word_prefix_sum =\n\t\t\t\t\t__builtin_popcountll(block_data & ((~0ULL) >> (63 - i)));\n\t\t\t\tprefix_sums[decompressed_index + i] = num_ones + word_prefix_sum;\n\t\t\t}\n\t\t\tnum_ones += __builtin_popcountll(block_data);\n#else\n\t\t\t/*\n\t\t\t * Unfortunately, we have to have this fallback for Windows.\n\t\t\t */\n\t\t\tfor (uint16 i = 0; i < 64; i++)\n\t\t\t{\n\t\t\t\tconst bool this_bit = (block_data >> i) & 1;\n\t\t\t\tnum_ones += this_bit;\n\t\t\t\tprefix_sums[decompressed_index + i] = num_ones;\n\t\t\t}\n#endif\n\t\t\tdecompressed_index += 64;\n\t\t}\n\t}\n\n\t/*\n\t * We might have unpacked more because we work in full blocks, but at least\n\t * we shouldn't have unpacked less.\n\t */\n\tCheckCompressedData(decompressed_index >= num_elements);\n\tAssert(decompressed_index <= num_elements_padded);\n\n\t/*\n\t * Might happen if we have stray ones in the higher unused bits of the last\n\t * block.\n\t */\n\tCheckCompressedData(num_ones <= num_elements);\n\n\t/*\n\t * Check that the number of ones actually fits into the uint16 counters\n\t * we're using.\n\t */\n\tCheckCompressedData(((uint16) num_ones) == num_ones);\n\n\tSimple8bRleBitmap result = {\n\t\t.data = prefix_sums,\n\t\t.num_elements = num_elements,\n\t\t.num_ones = num_ones,\n\t};\n\n\treturn result;\n}\n\nstatic Simple8bRleBitmap\nsimple8brle_bitmap_decompress(Simple8bRleSerialized *compressed)\n{\n\tCheckCompressedData(compressed->num_elements <= GLOBAL_MAX_ROWS_PER_COMPRESSION);\n\tCheckCompressedData(compressed->num_blocks <= GLOBAL_MAX_ROWS_PER_COMPRESSION);\n\n\tconst uint32 num_elements = compressed->num_elements;\n\n\tconst uint32 num_selector_slots =\n\t\tsimple8brle_num_selector_slots_for_num_blocks(compressed->num_blocks);\n\tconst uint64 *compressed_data = compressed->slots + num_selector_slots;\n\n\t/*\n\t * Pad to next multiple of 64 bytes on the right, so that we can simplify the\n\t * decompression loop and the get() function. Note that for get() we need at\n\t * least one byte of padding, hence the next multiple.\n\t */\n\tconst uint32 num_elements_padded = ((num_elements + 63) / 64 + 1) * 64;\n\tconst uint32 num_blocks = compressed->num_blocks;\n\n\tbool *restrict bitmap_bools_ = palloc(sizeof(bool) * num_elements_padded);\n\tuint32 num_ones = 0;\n\tuint32 decompressed_index = 0;\n\tfor (uint32 block_index = 0; block_index < num_blocks; block_index++)\n\t{\n\t\tconst uint32 selector_slot = block_index / SIMPLE8B_SELECTORS_PER_SELECTOR_SLOT;\n\t\tconst uint32 selector_pos_in_slot = block_index % SIMPLE8B_SELECTORS_PER_SELECTOR_SLOT;\n\t\tconst uint64 slot_value = compressed->slots[selector_slot];\n\t\tconst uint8 selector_shift = selector_pos_in_slot * SIMPLE8B_BITS_PER_SELECTOR;\n\t\tconst uint64 selector_mask = 0xFULL << selector_shift;\n\t\tconst uint8 selector_value = (slot_value & selector_mask) >> selector_shift;\n\t\tAssert(selector_value < 16);\n\n\t\tuint64 block_data = compressed_data[block_index];\n\n\t\tif (simple8brle_selector_is_rle(selector_value))\n\t\t{\n\t\t\t/*\n\t\t\t * RLE block.\n\t\t\t */\n\t\t\tconst uint32 n_block_values = simple8brle_rledata_repeatcount(block_data);\n\t\t\tCheckCompressedData(n_block_values <= GLOBAL_MAX_ROWS_PER_COMPRESSION);\n\n\t\t\t/*\n\t\t\t * We might get an incorrect value from the corrupt data. Explicitly\n\t\t\t * truncate it to 0/1 in case the bool is not a standard bool type\n\t\t\t * which would have done it for us.\n\t\t\t */\n\t\t\tconst bool repeated_value = simple8brle_rledata_value(block_data) & 1;\n\n\t\t\tCheckCompressedData(decompressed_index + n_block_values <= num_elements);\n\n\t\t\tif (repeated_value)\n\t\t\t{\n\t\t\t\tfor (uint32 i = 0; i < n_block_values; i++)\n\t\t\t\t{\n\t\t\t\t\tbitmap_bools_[decompressed_index + i] = true;\n\t\t\t\t}\n\n\t\t\t\tnum_ones += n_block_values;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tfor (uint32 i = 0; i < n_block_values; i++)\n\t\t\t\t{\n\t\t\t\t\tbitmap_bools_[decompressed_index + i] = false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdecompressed_index += n_block_values;\n\t\t\tAssert(decompressed_index <= num_elements);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/*\n\t\t\t * Bit-packed block. Since this is a bitmap, this block has 64 bits\n\t\t\t * packed. The last block might contain less than maximal possible\n\t\t\t * number of elements, but we have 64 bytes of padding on the right\n\t\t\t * so we don't care.\n\t\t\t */\n\t\t\tCheckCompressedData(selector_value == 1);\n\n\t\t\tAssert(SIMPLE8B_BIT_LENGTH[selector_value] == 1);\n\t\t\tAssert(SIMPLE8B_NUM_ELEMENTS[selector_value] == 64);\n\n\t\t\t/*\n\t\t\t * We should require at least one element from the block. Previous\n\t\t\t * blocks might have had incorrect lengths, so this is not an\n\t\t\t * assertion.\n\t\t\t */\n\t\t\tCheckCompressedData(decompressed_index < num_elements);\n\n\t\t\t/* Have to zero out the unused bits, so that the popcnt works properly. */\n\t\t\tconst int elements_this_block = Min(64, num_elements - decompressed_index);\n\t\t\tAssert(elements_this_block <= 64);\n\t\t\tAssert(elements_this_block > 0);\n\t\t\tblock_data &= (~0ULL) >> (64 - elements_this_block);\n\n\t\t\t/*\n\t\t\t * The number of block elements should fit within padding. Previous\n\t\t\t * blocks might have had incorrect lengths, so this is not an\n\t\t\t * assertion.\n\t\t\t */\n\t\t\tCheckCompressedData(decompressed_index + 64 < num_elements_padded);\n\n#ifdef HAVE__BUILTIN_POPCOUNT\n\t\t\tnum_ones += __builtin_popcountll(block_data);\n#endif\n\t\t\tfor (uint16 i = 0; i < 64; i++)\n\t\t\t{\n\t\t\t\tconst bool this_bit = (block_data >> i) & 1;\n\t\t\t\tbitmap_bools_[decompressed_index + i] = this_bit;\n#ifndef HAVE__BUILTIN_POPCOUNT\n\t\t\t\tnum_ones += this_bit;\n#endif\n\t\t\t}\n\t\t\tdecompressed_index += 64;\n\t\t}\n\t}\n\n\t/*\n\t * We might have unpacked more because we work in full blocks, but at least\n\t * we shouldn't have unpacked less.\n\t */\n\tCheckCompressedData(decompressed_index >= num_elements);\n\tAssert(decompressed_index <= num_elements_padded);\n\n\t/*\n\t * Might happen if we have stray ones in the higher unused bits of the last\n\t * block.\n\t */\n\tCheckCompressedData(num_ones <= num_elements);\n\n\tSimple8bRleBitmap result = {\n\t\t.data = bitmap_bools_,\n\t\t.num_elements = num_elements,\n\t\t.num_ones = num_ones,\n\t};\n\n\t/* Sanity check. */\n#ifdef USE_ASSERT_CHECKING\n\tuint32 num_ones_2 = 0;\n\tfor (uint32 i = 0; i < num_elements; i++)\n\t{\n\t\tnum_ones_2 += simple8brle_bitmap_get_at(&result, i);\n\t}\n\tAssert(num_ones_2 == num_ones);\n#endif\n\n\treturn result;\n}\n"
  },
  {
    "path": "tsl/src/compression/algorithms/simple8b_rle_decompress_all.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#define FUNCTION_NAME_HELPER(X, Y) X##_##Y\n#define FUNCTION_NAME(X, Y) FUNCTION_NAME_HELPER(X, Y)\n\n/*\n * Specialization of bulk simple8brle decompression for a data type specified by\n * ELEMENT_TYPE macro.\n *\n * The buffer must have a padding of 63 elements after the last one, because\n * decompression is performed always in full blocks.\n */\nstatic uint32\nFUNCTION_NAME(simple8brle_decompress_all_buf,\n\t\t\t  ELEMENT_TYPE)(Simple8bRleSerialized *compressed,\n\t\t\t\t\t\t\tELEMENT_TYPE *restrict decompressed_values, uint32 n_buffer_elements)\n{\n\tconst uint32 n_total_values = compressed->num_elements;\n\n\t/*\n\t * Caller must have allocated a properly sized buffer, see the comment above.\n\t */\n\tAssert(n_buffer_elements >= n_total_values + 63);\n\n\tconst uint32 num_selector_slots =\n\t\tsimple8brle_num_selector_slots_for_num_blocks(compressed->num_blocks);\n\tconst uint32 num_blocks = compressed->num_blocks;\n\n\t/*\n\t * Unpack the selector slots to get the selector values. Best done separately,\n\t * so that this loop can be vectorized.\n\t */\n\tAssert(num_blocks <= GLOBAL_MAX_ROWS_PER_COMPRESSION);\n\tuint8 selector_values[GLOBAL_MAX_ROWS_PER_COMPRESSION];\n\tconst uint64 *slots = compressed->slots;\n\tfor (uint32 block_index = 0; block_index < num_blocks; block_index++)\n\t{\n\t\tconst uint32 selector_slot = block_index / SIMPLE8B_SELECTORS_PER_SELECTOR_SLOT;\n\t\tconst uint32 selector_pos_in_slot = block_index % SIMPLE8B_SELECTORS_PER_SELECTOR_SLOT;\n\t\tconst uint64 slot_value = slots[selector_slot];\n\t\tconst uint8 selector_shift = selector_pos_in_slot * SIMPLE8B_BITS_PER_SELECTOR;\n\t\tconst uint64 selector_mask = 0xFULL << selector_shift;\n\t\tconst uint8 selector_value = (slot_value & selector_mask) >> selector_shift;\n\t\tselector_values[block_index] = selector_value;\n\t}\n\n\t/*\n\t * Now decompress the individual blocks.\n\t */\n\tuint32 decompressed_index = 0;\n\tconst uint64 *blocks = compressed->slots + num_selector_slots;\n\tfor (uint32 block_index = 0; block_index < num_blocks; block_index++)\n\t{\n\t\tconst uint8 selector_value = selector_values[block_index];\n\t\tconst uint64 block_data = blocks[block_index];\n\n\t\t/* We don't see RLE blocks so often in the real data, <1% of blocks. */\n\t\tif (unlikely(simple8brle_selector_is_rle(selector_value)))\n\t\t{\n\t\t\tconst uint16 n_block_values = simple8brle_rledata_repeatcount(block_data);\n\t\t\tCheckCompressedData(n_block_values <= n_buffer_elements);\n\t\t\tCheckCompressedData(decompressed_index <= n_buffer_elements - n_block_values);\n\n\t\t\tconst uint64 repeated_value_raw = simple8brle_rledata_value(block_data);\n\t\t\tconst ELEMENT_TYPE repeated_value_converted = repeated_value_raw;\n\t\t\tCheckCompressedData(repeated_value_raw == (uint64) repeated_value_converted);\n\n\t\t\tfor (uint16 i = 0; i < n_block_values; i++)\n\t\t\t{\n\t\t\t\tdecompressed_values[decompressed_index + i] = repeated_value_converted;\n\t\t\t}\n\n\t\t\tdecompressed_index += n_block_values;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* Bit-packed blocks. Generate separate code for each block type. */\n#define UNPACK_BLOCK(X)                                                                            \\\n\tcase (X):                                                                                      \\\n\t{                                                                                              \\\n\t\t/*                                                                                         \\\n\t\t * Error out it if the bit width is higher than that of the destination                    \\\n\t\t * type. We could just skip it, but this way the result of e.g. gorilla                    \\\n\t\t * decompression will be closer to what the row-by-row decompression                       \\\n\t\t * produces, which is easier for testing.                                                  \\\n\t\t */                                                                                        \\\n\t\tconst uint8 bits_per_value = SIMPLE8B_BIT_LENGTH[X];                                       \\\n\t\tCheckCompressedData(bits_per_value <= sizeof(ELEMENT_TYPE) * 8);                           \\\n                                                                                                   \\\n\t\t/*                                                                                         \\\n\t\t * The last block might have less values than normal, but we have                          \\\n\t\t * padding at the end so we can unpack them all always for simpler                         \\\n\t\t * code. We still have to check if they fit, because the incoming data                     \\\n\t\t * might be incorrect.                                                                     \\\n\t\t */                                                                                        \\\n\t\tconst uint16 n_block_values = SIMPLE8B_NUM_ELEMENTS[X];                                    \\\n\t\tCheckCompressedData(n_block_values <= n_buffer_elements);                                  \\\n\t\tCheckCompressedData(decompressed_index <= n_buffer_elements - n_block_values);             \\\n                                                                                                   \\\n\t\tconst uint64 bitmask = simple8brle_selector_get_bitmask(X);                                \\\n                                                                                                   \\\n\t\tfor (uint16 i = 0; i < n_block_values; i++)                                                \\\n\t\t{                                                                                          \\\n\t\t\tconst ELEMENT_TYPE value = (block_data >> (bits_per_value * i)) & bitmask;             \\\n\t\t\tdecompressed_values[decompressed_index + i] = value;                                   \\\n\t\t}                                                                                          \\\n\t\tdecompressed_index += n_block_values;                                                      \\\n\t\tbreak;                                                                                     \\\n\t}\n\n\t\t\tswitch (selector_value)\n\t\t\t{\n\t\t\t\tUNPACK_BLOCK(1);\n\t\t\t\tUNPACK_BLOCK(2);\n\t\t\t\tUNPACK_BLOCK(3);\n\t\t\t\tUNPACK_BLOCK(4);\n\t\t\t\tUNPACK_BLOCK(5);\n\t\t\t\tUNPACK_BLOCK(6);\n\t\t\t\tUNPACK_BLOCK(7);\n\t\t\t\tUNPACK_BLOCK(8);\n\t\t\t\tUNPACK_BLOCK(9);\n\t\t\t\tUNPACK_BLOCK(10);\n\t\t\t\tUNPACK_BLOCK(11);\n\t\t\t\tUNPACK_BLOCK(12);\n\t\t\t\tUNPACK_BLOCK(13);\n\t\t\t\tUNPACK_BLOCK(14);\n\t\t\t\tdefault:\n\t\t\t\t\t/*\n\t\t\t\t\t * Can only get 0 here in case the data is corrupt. Doesn't\n\t\t\t\t\t * harm to report it right away, because this loop can't be\n\t\t\t\t\t * vectorized.\n\t\t\t\t\t */\n\t\t\t\t\tCheckCompressedData(false);\n\t\t\t}\n#undef UNPACK_BLOCK\n\t\t}\n\t}\n\n\t/*\n\t * We can decompress more than expected because we work in full blocks,\n\t * but if we decompressed less, this means broken data. Better to report it\n\t * not to have an uninitialized tail.\n\t */\n\tCheckCompressedData(decompressed_index >= n_total_values);\n\tAssert(decompressed_index <= n_buffer_elements);\n\n\treturn n_total_values;\n}\n\n/*\n * The same function as above, but does palloc instead of taking the buffer as\n * an input. We mark it as possibly unused because it is used not for every\n * element type we have.\n */\nstatic ELEMENT_TYPE *FUNCTION_NAME(simple8brle_decompress_all,\n\t\t\t\t\t\t\t\t   ELEMENT_TYPE)(Simple8bRleSerialized *compressed, uint32 *n_)\n\tpg_attribute_unused();\n\nstatic ELEMENT_TYPE *\nFUNCTION_NAME(simple8brle_decompress_all, ELEMENT_TYPE)(Simple8bRleSerialized *compressed,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tuint32 *n_)\n{\n\tconst uint32 n_total_values = compressed->num_elements;\n\tAssert(n_total_values <= GLOBAL_MAX_ROWS_PER_COMPRESSION);\n\n\t/*\n\t * We need a quite significant padding of 63 elements, not bytes, after the\n\t * last element, because we work in Simple8B blocks which can contain up to\n\t * 64 elements.\n\t */\n\tconst uint32 n_buffer_elements = n_total_values + 63;\n\n\tELEMENT_TYPE *restrict decompressed_values = palloc(sizeof(ELEMENT_TYPE) * n_buffer_elements);\n\n\t*n_ = FUNCTION_NAME(simple8brle_decompress_all_buf,\n\t\t\t\t\t\tELEMENT_TYPE)(compressed, decompressed_values, n_buffer_elements);\n\n\treturn decompressed_values;\n}\n"
  },
  {
    "path": "tsl/src/compression/algorithms/uuid_compress.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include \"uuid_compress.h\"\n#include \"adts/uint64_vec.h\"\n#include \"common/hashfn.h\"\n#include \"compression/arrow_c_data_interface.h\"\n#include \"compression/compression.h\"\n#include \"deltadelta.h\"\n#include \"dictionary.h\"\n#include \"guc.h\"\n#include \"lib/hyperloglog.h\"\n#include \"null.h\"\n#include \"simple8b_rle.h\"\n#include \"utils/palloc.h\"\n\n#ifdef TS_USE_UMASH\n#include \"import/umash.h\"\n#endif\n\ntypedef union UuidType\n{\n\tuint64 components[2];\n\tpg_uuid_t uuid;\n} UuidType;\n\ntypedef enum\n{\n\tUUID_COMPRESS_SUBTYPE_DELTADELTA = 0,\n\tUUID_COMPRESS_SUBTYPE_COUNT = 1, /* Must be last */\n} UuidCompressSubtype;\n\ntypedef struct UuidCompressed\n{\n\tCompressedDataHeaderFields; /* this uses 5 bytes */\n\tuint8 subtype;\n\tuint16 num_nulls;\n\tuint32 timestamp_size;\n\tuint32 rand_b_and_variant_size;\n\t/* 8-byte alignment sentinel for the following fields */\n\tuint64 alignment_sentinel[FLEXIBLE_ARRAY_MEMBER];\n} UuidCompressed;\n\nstatic void\npg_attribute_unused() assertions(void)\n{\n\tStaticAssertStmt(sizeof(UuidCompressed) == 16, \"UuidCompressed wrong size\");\n\tStaticAssertStmt(offsetof(UuidCompressed, alignment_sentinel) % MAXIMUM_ALIGNOF == 0,\n\t\t\t\t\t \"variable sized data must be 8-byte aligned\");\n}\n\ntypedef struct UuidDecompressionIterator\n{\n\tDecompressionIterator base;\n\tint32 position;\t\t\t /* position within the total */\n\tint32 total_elements;\t /* total number of entries plus nulls */\n\tArrowArray *uuid_buffer; /* init does a decompress_all to get this */\n} UuidDecompressionIterator;\n\n/*\n * HyperLogLog parameters.\n *\n * The bit width is set such that the error rate is acceptable and also allocate\n * little memory. 8 uses 256 bytes and the error rate is 6.5%\n */\n#define HLL_BIT_WIDTH 8\n#define HLL_ERROR_RATE 0.065\n#define HLL_MIN_CARDINALITY 20\n\n/*\n * The UUID compressor is using delta delta compression for the first 8\n * bytes of the UUID and stores the rest as a uint64_vec. At the same time\n * it keeps track of the cardinality of values. if the cardinality\n * indicates that we are better off with the dictionary compressor, we will\n * recompress it at the end.\n */\ntypedef struct UuidCompressor\n{\n\t/* Delta-delta encoding for timestamp, version and rand_a. */\n\tDeltaDeltaCompressor *timestamp;\n\n\t/* We store the rand_b and variant parts together as a uint64_vec\n\t * to avoid having to store two separate bitmaps.\n\t */\n\tuint64_vec rand_b_and_variant;\n\n\t/* HLL state to estimate the cardinality. This is used to check if\n\t * we are better off with recompressing the data as a dictionary.\n\t */\n\thyperLogLogState cardinality;\n\n\t/* Number of nulls in the data. */\n\tuint16 num_nulls;\n\n\t/* Number of elements in the timestamp part. */\n\tuint16 num_values;\n} UuidCompressor;\n\ntypedef struct ExtendedCompressor\n{\n\tCompressor base;\n\tUuidCompressor *internal;\n} ExtendedCompressor;\n\n/*\n * Local helpers\n */\nstatic void uuid_compressor_append_uuid(Compressor *compressor, Datum val);\nstatic void uuid_compressor_append_null_value(Compressor *compressor);\nstatic void *uuid_compressor_finish_and_reset(Compressor *compressor);\nstatic void decompression_iterator_init(UuidDecompressionIterator *iter, void *compressed,\n\t\t\t\t\t\t\t\t\t\tOid element_type, bool forward);\n\nconst Compressor uuid_compressor_initializer = {\n\t.append_val = uuid_compressor_append_uuid,\n\t.append_null = uuid_compressor_append_null_value,\n\t.is_full = NULL,\n\t.finish = uuid_compressor_finish_and_reset,\n};\n\n/*\n * Compressor framework functions and definitions for the uuid_compress algorithm.\n */\n\nextern UuidCompressor *\nuuid_compressor_alloc(void)\n{\n\tUuidCompressor *compressor = palloc0(sizeof(*compressor));\n\tcompressor->timestamp = delta_delta_compressor_alloc();\n\tuint64_vec_init(&compressor->rand_b_and_variant,\n\t\t\t\t\tCurrentMemoryContext,\n\t\t\t\t\tTARGET_COMPRESSED_BATCH_SIZE);\n\tinitHyperLogLog(&compressor->cardinality, HLL_BIT_WIDTH);\n\treturn compressor;\n}\n\nextern void\nuuid_compressor_append_null(UuidCompressor *compressor)\n{\n\tdelta_delta_compressor_append_null(compressor->timestamp);\n\tcompressor->num_nulls++;\n}\n\n#ifdef TS_USE_UMASH\nstatic inline uint32\nuuid_compress_hash(pg_uuid_t *uuid)\n{\n\tstatic struct umash_params params = { 0 };\n\tif (params.poly[0][0] == 0)\n\t{\n\t\tumash_params_derive(&params, 0x12345abcdef67890ULL, NULL);\n\t\tAssert(params.poly[0][0] != 0);\n\t}\n\n\tuint64 h = umash_full(&params,\n\t\t\t\t\t\t  /* seed = */ ~0ULL,\n\t\t\t\t\t\t  /* which = */ 0,\n\t\t\t\t\t\t  uuid->data,\n\t\t\t\t\t\t  16);\n\n\treturn (uint32) (h ^ (h >> 32));\n}\n#else\nstatic inline uint32\nuuid_compress_hash(pg_uuid_t *uuid)\n{\n\treturn hash_bytes((unsigned char *) uuid->data, sizeof(*uuid));\n}\n#endif\n\nextern void\nuuid_compressor_append_value(UuidCompressor *compressor, pg_uuid_t next_val)\n{\n\tuint64_t components[2];\n\tmemcpy(components, next_val.data, sizeof(components));\n\n\t/* The first component is the timestamp, version and rand_a. */\n\tuint64_t timestamp = pg_ntoh64(components[0]);\n\t/* The second part is the rand_b and variant. */\n\tuint64_t rand_b_and_variant = components[1];\n\n\tdelta_delta_compressor_append_value(compressor->timestamp, timestamp);\n\tuint64_vec_append(&compressor->rand_b_and_variant, rand_b_and_variant);\n\n\tuint32 h = uuid_compress_hash(&next_val);\n\taddHyperLogLog(&compressor->cardinality, h);\n\tcompressor->num_values++;\n}\n\nstatic size_t\nuuid_compressor_estimate_dictionary_storage(UuidCompressor *compressor,\n\t\t\t\t\t\t\t\t\t\t\tsize_t nulls_compressed_size)\n{\n\tdouble cardinality = (double) compressor->rand_b_and_variant.num_elements;\n\tdouble cardinality_and_error = cardinality;\n\n\t/* Don't use HLL if there are too few elements to estimate the cardinality. */\n\tif (cardinality > HLL_MIN_CARDINALITY)\n\t{\n\t\tcardinality = estimateHyperLogLog(&compressor->cardinality);\n\t\tcardinality_and_error = cardinality * (1.0 - HLL_ERROR_RATE);\n\t}\n\n\tint array_index_bytes = ((int) cardinality_and_error * 5 + 63) / 64 * 8;\n\n\tdouble estimated_dictionary_storage =\n\t\t/* 16 bytes per values in dictionary/array/values */\n\t\tcardinality_and_error * 16 +\n\t\t/* a single RLE block for the sizes in dictionary/array/sizes */\n\t\t16 +\n\t\t/* no nulls in dictionary/array/nulls */\n\t\t0 +\n\t\t/* 5 bits on average for the indexes in dictionary/array/indexes */\n\t\tarray_index_bytes +\n\t\t/* storing nulls is the same as in the delta-delta compressor */\n\t\tnulls_compressed_size;\n\n\treturn estimated_dictionary_storage;\n}\n\nextern void *\nuuid_compressor_finish(UuidCompressor *compressor)\n{\n\tif (compressor == NULL)\n\t\treturn NULL;\n\n\tif (compressor->num_values == 0)\n\t\treturn NULL;\n\n\tsize_t nulls_compressed_size = 0;\n\tsize_t timestamp_compressed_size =\n\t\tdelta_delta_compressor_compressed_size(compressor->timestamp, &nulls_compressed_size);\n\tsize_t estimated_dictionary_storage =\n\t\tuuid_compressor_estimate_dictionary_storage(compressor, nulls_compressed_size);\n\tsize_t rand_b_and_variant_compressed_size =\n\t\tcompressor->rand_b_and_variant.num_elements * sizeof(uint64_t);\n\tAssert(compressor->rand_b_and_variant.num_elements == compressor->num_values);\n\tsize_t total_compressed_size =\n\t\tsizeof(UuidCompressed) + timestamp_compressed_size + rand_b_and_variant_compressed_size;\n\n\t/* TODO: this is temporary: to iterate over the delta-delta compressed data\n\t * we need to finalize the compression, so even if we knew that the dictionary\n\t * compression is better we still need to allocate, finish and memcpy the\n\t * entries. This is clearly a waste. To solve this we will need an interface\n\t * to iterate over compressed data without finalizing it.\n\t */\n\tchar *compressed_data = palloc(total_compressed_size);\n\tUuidCompressed *compressed = (UuidCompressed *) compressed_data;\n\tSET_VARSIZE(&compressed->vl_len_, total_compressed_size);\n\tcompressed->compression_algorithm = COMPRESSION_ALGORITHM_UUID;\n\tcompressed->num_nulls = compressor->num_nulls;\n\tEnsure(compressed->num_nulls == compressor->num_nulls,\n\t\t   \"unexpected number of nulls, it doesn't fit into the header\");\n\tcompressed->subtype = UUID_COMPRESS_SUBTYPE_DELTADELTA;\n\tcompressed->timestamp_size = timestamp_compressed_size;\n\tcompressed->rand_b_and_variant_size = rand_b_and_variant_compressed_size;\n\n\tcompressed_data += sizeof(*compressed);\n\tchar *timestamp_compressed_data = compressed_data;\n\tcompressed_data =\n\t\tdelta_delta_compressor_finish_into(compressor->timestamp, timestamp_compressed_data);\n\t/* Make sure delta-delta took exactly the size it said it will */\n\tAssert(compressed_data - timestamp_compressed_data == (long) timestamp_compressed_size);\n\tmemcpy(compressed_data,\n\t\t   compressor->rand_b_and_variant.data,\n\t\t   rand_b_and_variant_compressed_size);\n\n\tif (total_compressed_size > estimated_dictionary_storage)\n\t{\n\t\t/* Recompress as dictionary */\n\t\tDictionaryCompressor *dict_compressor = dictionary_compressor_alloc(UUIDOID);\n\t\tDecompressionIterator *iter =\n\t\t\tdelta_delta_decompression_iterator_from_datum_forward(PointerGetDatum(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  timestamp_compressed_data),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  INT8OID);\n\t\tuint32 value_position = 0;\n\t\tfor (DecompressResult r = delta_delta_decompression_iterator_try_next_forward(iter);\n\t\t\t !r.is_done;\n\t\t\t r = delta_delta_decompression_iterator_try_next_forward(iter))\n\t\t{\n\t\t\tif (r.is_null)\n\t\t\t{\n\t\t\t\tdictionary_compressor_append_null(dict_compressor);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tUuidType uuid_type;\n\t\t\t\tuuid_type.components[0] = pg_hton64(DatumGetInt64(r.val));\n\t\t\t\tuuid_type.components[1] = compressor->rand_b_and_variant.data[value_position];\n\t\t\t\tdictionary_compressor_append(dict_compressor, UUIDPGetDatum(&uuid_type.uuid));\n\t\t\t\t++value_position;\n\t\t\t}\n\t\t}\n\n\t\tvoid *dict_compressed = dictionary_compressor_finish(dict_compressor);\n\t\tif (VARSIZE(dict_compressed) < total_compressed_size)\n\t\t{\n\t\t\t/* We are better off with the dictionary compression, inline with the estimated size */\n\t\t\tpfree(compressed);\n\t\t\tcompressed = dict_compressed;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* We are better off with the original compression, contrary to the estimated size.\n\t\t\t * This is OK, as the estimate is probabilistic.\n\t\t\t */\n\t\t\tpfree(dict_compressed);\n\t\t}\n\t\tpfree(dict_compressor);\n\t\tpfree(iter);\n\t}\n\n\treturn compressed;\n}\n\nextern bool\nuuid_compressed_has_nulls(const CompressedDataHeader *header)\n{\n\tconst UuidCompressed *uc = (const UuidCompressed *) header;\n\treturn uc->num_nulls > 0;\n}\n\nextern DecompressResult\nuuid_decompression_iterator_try_next_forward(DecompressionIterator *iter)\n{\n\tAssert(iter->compression_algorithm == COMPRESSION_ALGORITHM_UUID && iter->forward);\n\tAssert(iter->element_type == UUIDOID);\n\n\tUuidDecompressionIterator *uuid_iter = (UuidDecompressionIterator *) iter;\n\n\tAssert(uuid_iter->uuid_buffer != NULL);\n\tAssert(uuid_iter->uuid_buffer->buffers != NULL);\n\tAssert(uuid_iter->uuid_buffer->buffers[1] != NULL);\n\n\tconst uint64 *validity_bitmap = uuid_iter->uuid_buffer->buffers[0];\n\tUuidType *uuid_values = (UuidType *) uuid_iter->uuid_buffer->buffers[1];\n\n\tif (uuid_iter->position >= uuid_iter->total_elements)\n\t\treturn (DecompressResult){\n\t\t\t.is_done = true,\n\t\t};\n\n\t/* check nulls */\n\tif (validity_bitmap != NULL)\n\t{\n\t\tif (!arrow_row_is_valid(validity_bitmap, uuid_iter->position))\n\t\t{\n\t\t\tuuid_iter->position++;\n\t\t\treturn (DecompressResult){\n\t\t\t\t.is_null = true,\n\t\t\t};\n\t\t}\n\t}\n\n\tUuidType *current_uuid = &uuid_values[uuid_iter->position];\n\tuuid_iter->position++;\n\n\treturn (DecompressResult){\n\t\t.val = PointerGetDatum(current_uuid),\n\t};\n}\n\nextern DecompressionIterator *\nuuid_decompression_iterator_from_datum_forward(Datum uuid_compressed, Oid element_type)\n{\n\tUuidDecompressionIterator *iterator = palloc0(sizeof(*iterator));\n\tCheckCompressedData(DatumGetPointer(uuid_compressed) != NULL);\n\tdecompression_iterator_init(iterator,\n\t\t\t\t\t\t\t\t(void *) PG_DETOAST_DATUM(uuid_compressed),\n\t\t\t\t\t\t\t\telement_type,\n\t\t\t\t\t\t\t\ttrue);\n\treturn &iterator->base;\n}\n\nextern DecompressResult\nuuid_decompression_iterator_try_next_reverse(DecompressionIterator *iter)\n{\n\tAssert(iter->compression_algorithm == COMPRESSION_ALGORITHM_UUID && !iter->forward);\n\tAssert(iter->element_type == UUIDOID);\n\n\tUuidDecompressionIterator *uuid_iter = (UuidDecompressionIterator *) iter;\n\n\tAssert(uuid_iter->uuid_buffer != NULL);\n\tAssert(uuid_iter->uuid_buffer->buffers != NULL);\n\tAssert(uuid_iter->uuid_buffer->buffers[1] != NULL);\n\n\tconst uint64 *validity_bitmap = uuid_iter->uuid_buffer->buffers[0];\n\tUuidType *uuid_values = (UuidType *) uuid_iter->uuid_buffer->buffers[1];\n\n\tif (uuid_iter->position < 0)\n\t\treturn (DecompressResult){\n\t\t\t.is_done = true,\n\t\t};\n\n\t/* check nulls */\n\tif (validity_bitmap != NULL)\n\t{\n\t\tif (!arrow_row_is_valid(validity_bitmap, uuid_iter->position))\n\t\t{\n\t\t\tuuid_iter->position--;\n\t\t\treturn (DecompressResult){\n\t\t\t\t.is_null = true,\n\t\t\t};\n\t\t}\n\t}\n\n\tAssert(uuid_iter->position >= 0);\n\tUuidType *current_uuid = &uuid_values[uuid_iter->position];\n\tuuid_iter->position--;\n\n\treturn (DecompressResult){\n\t\t.val = PointerGetDatum(current_uuid),\n\t};\n}\n\nextern DecompressionIterator *\nuuid_decompression_iterator_from_datum_reverse(Datum uuid_compressed, Oid element_type)\n{\n\tUuidDecompressionIterator *iterator = palloc(sizeof(*iterator));\n\tCheckCompressedData(DatumGetPointer(uuid_compressed) != NULL);\n\tdecompression_iterator_init(iterator,\n\t\t\t\t\t\t\t\t(void *) PG_DETOAST_DATUM(uuid_compressed),\n\t\t\t\t\t\t\t\telement_type,\n\t\t\t\t\t\t\t\tfalse);\n\treturn &iterator->base;\n}\n\nextern void\nuuid_compressed_send(CompressedDataHeader *header, StringInfo buffer)\n{\n\tconst UuidCompressed *data = (UuidCompressed *) header;\n\tAssert(header->compression_algorithm == COMPRESSION_ALGORITHM_UUID);\n\n\tpq_sendbyte(buffer, data->subtype);\n\tpq_sendint16(buffer, data->num_nulls);\n\tpq_sendint32(buffer, data->timestamp_size);\n\tpq_sendint32(buffer, data->rand_b_and_variant_size);\n\n\tchar *ptr = (char *) data->alignment_sentinel;\n\tdeltadelta_compressed_send((CompressedDataHeader *) ptr, buffer);\n\tptr += data->timestamp_size;\n\tuint64 *rand_b_and_variant = (uint64 *) ptr;\n\tuint32 num_elements = data->rand_b_and_variant_size / sizeof(uint64);\n\tfor (uint32 i = 0; i < num_elements; i++)\n\t\tpq_sendint64(buffer, rand_b_and_variant[i]);\n}\n\nextern Datum\nuuid_compressed_recv(StringInfo buffer)\n{\n\tsize_t total_compressed_sized = 0;\n\tuint8 subtype = pq_getmsgbyte(buffer);\n\tCheckCompressedData(subtype < UUID_COMPRESS_SUBTYPE_COUNT);\n\tuint16 num_nulls = pq_getmsgint(buffer, 2);\n\tuint32 timestamp_size = pq_getmsgint32(buffer);\n\tuint32 rand_b_and_variant_size = pq_getmsgint32(buffer);\n\n\t/* The timestamp_size must accommodate the delta-delta header (24 bytes) */\n\tCheckCompressedData(timestamp_size > 24);\n\tCheckCompressedData(rand_b_and_variant_size > 0);\n\tCheckCompressedData(rand_b_and_variant_size % sizeof(uint64) == 0);\n\tCheckCompressedData(timestamp_size % sizeof(uint64) == 0);\n\tCheckCompressedData(rand_b_and_variant_size / sizeof(uint64) < GLOBAL_MAX_ROWS_PER_COMPRESSION);\n\n\t/* A good enough to catch totally bogus timestamp_size values, the actual limit is slightly\n\t * tighter */\n\tCheckCompressedData(timestamp_size < (GLOBAL_MAX_ROWS_PER_COMPRESSION * (sizeof(uint64) + 1)));\n\n\tuint32 total_values = num_nulls + rand_b_and_variant_size / sizeof(uint64);\n\tCheckCompressedData(total_values < GLOBAL_MAX_ROWS_PER_COMPRESSION);\n\n\ttotal_compressed_sized = sizeof(UuidCompressed) + timestamp_size + rand_b_and_variant_size;\n\tCheckCompressedData(total_compressed_sized <= MaxAllocSize);\n\n\tchar *result = palloc(total_compressed_sized);\n\tUuidCompressed *compressed = (UuidCompressed *) result;\n\tcompressed->subtype = subtype;\n\tcompressed->num_nulls = num_nulls;\n\tcompressed->timestamp_size = timestamp_size;\n\tcompressed->rand_b_and_variant_size = rand_b_and_variant_size;\n\tSET_VARSIZE(&compressed->vl_len_, total_compressed_sized);\n\tcompressed->compression_algorithm = COMPRESSION_ALGORITHM_UUID;\n\n\tDatum delta_delta_compressed = deltadelta_compressed_recv(buffer);\n\tsize_t delta_delta_compressed_size = VARSIZE(delta_delta_compressed);\n\tCheckCompressedData(delta_delta_compressed_size == timestamp_size);\n\n\tmemcpy(result + sizeof(UuidCompressed),\n\t\t   DatumGetPointer(delta_delta_compressed),\n\t\t   delta_delta_compressed_size);\n\tuint64 *rand_b_and_variant =\n\t\t(uint64 *) (result + sizeof(UuidCompressed) + delta_delta_compressed_size);\n\tuint32 num_elements = rand_b_and_variant_size / sizeof(uint64);\n\tfor (uint32 i = 0; i < num_elements; i++)\n\t\trand_b_and_variant[i] = pq_getmsgint64(buffer);\n\n\tPG_RETURN_POINTER(result);\n}\n\nextern Compressor *\nuuid_compressor_for_type(Oid element_type)\n{\n\tExtendedCompressor *compressor = palloc(sizeof(*compressor));\n\tswitch (element_type)\n\t{\n\t\tcase UUIDOID:\n\t\t\t*compressor = (ExtendedCompressor){ .base = uuid_compressor_initializer };\n\t\t\treturn &compressor->base;\n\t\tdefault:\n\t\t\telog(ERROR, \"invalid type for uuid compressor \\\"%s\\\"\", format_type_be(element_type));\n\t}\n\n\tpg_unreachable();\n}\n\n/*\n * Cross-module functions for the uuid_compress algorithm.\n */\nextern Datum\ntsl_uuid_compressor_append(PG_FUNCTION_ARGS)\n{\n\tMemoryContext old_context;\n\tMemoryContext agg_context;\n\tUuidCompressor *compressor = (UuidCompressor *) (PG_ARGISNULL(0) ? NULL : PG_GETARG_POINTER(0));\n\n\tif (!AggCheckCallContext(fcinfo, &agg_context))\n\t{\n\t\t/* cannot be called directly because of internal-type argument */\n\t\telog(ERROR, \"tsl_uuid_compressor_append called in non-aggregate context\");\n\t}\n\n\told_context = MemoryContextSwitchTo(agg_context);\n\n\tif (compressor == NULL)\n\t{\n\t\tcompressor = uuid_compressor_alloc();\n\t\tif (PG_NARGS() > 2)\n\t\t\telog(ERROR, \"append expects two arguments\");\n\t}\n\n\tif (PG_ARGISNULL(1))\n\t\tuuid_compressor_append_null(compressor);\n\telse\n\t{\n\t\tpg_uuid_t *uuid = DatumGetUUIDP(PG_GETARG_DATUM(1));\n\t\tEnsure(uuid != NULL, \"invalid UUID\");\n\t\tuuid_compressor_append_value(compressor, *uuid);\n\t}\n\n\tMemoryContextSwitchTo(old_context);\n\tPG_RETURN_POINTER(compressor);\n}\n\nextern Datum\ntsl_uuid_compressor_finish(PG_FUNCTION_ARGS)\n{\n\tUuidCompressor *compressor = PG_ARGISNULL(0) ? NULL : (UuidCompressor *) PG_GETARG_POINTER(0);\n\tvoid *compressed;\n\tif (compressor == NULL)\n\t\tPG_RETURN_NULL();\n\n\tcompressed = uuid_compressor_finish(compressor);\n\tif (compressed == NULL)\n\t\tPG_RETURN_NULL();\n\tPG_RETURN_POINTER(compressed);\n}\n\nextern ArrowArray *\nuuid_decompress_all(Datum compressed, Oid element_type, MemoryContext dest_mctx)\n{\n\tAssert(element_type == UUIDOID);\n\n\tMemoryContext old_context;\n\tArrowArray *timestamp_array = NULL;\n\tCheckCompressedData(DatumGetPointer(compressed) != NULL);\n\n\tvoid *detoasted = PG_DETOAST_DATUM(compressed);\n\tStringInfoData si = { .data = detoasted, .len = VARSIZE_ANY(compressed) };\n\tUuidCompressed *header = consumeCompressedData(&si, sizeof(UuidCompressed));\n\tchar *timestamp_compressed_data = NULL;\n\tchar *rand_b_and_variant_compressed_data;\n\n\tCheckCompressedData(header->compression_algorithm == COMPRESSION_ALGORITHM_UUID);\n\tCheckCompressedData(header->subtype == UUID_COMPRESS_SUBTYPE_DELTADELTA);\n\n\t/* The timestamp_size must accommodate the delta-delta header (24 bytes) */\n\tCheckCompressedData(header->timestamp_size > 24);\n\tCheckCompressedData(header->rand_b_and_variant_size > 0);\n\tCheckCompressedData(header->rand_b_and_variant_size % sizeof(uint64) == 0);\n\tCheckCompressedData(header->timestamp_size % sizeof(uint64) == 0);\n\n\t/* A good enough to catch totally bogus timestamp_size values, the actual limit is slightly\n\t * tighter */\n\tCheckCompressedData(header->timestamp_size <\n\t\t\t\t\t\t(GLOBAL_MAX_ROWS_PER_COMPRESSION * (sizeof(uint64) + 1)));\n\n\ttimestamp_compressed_data = consumeCompressedData(&si, header->timestamp_size);\n\trand_b_and_variant_compressed_data =\n\t\tconsumeCompressedData(&si, header->rand_b_and_variant_size);\n\n\tint32 num_values = (int32) (header->rand_b_and_variant_size / sizeof(uint64));\n\tint32 total_elements = (int32) header->num_nulls + num_values;\n\tCheckCompressedData(num_values > 0);\n\tCheckCompressedData(num_values < GLOBAL_MAX_ROWS_PER_COMPRESSION);\n\n\told_context = MemoryContextSwitchTo(dest_mctx);\n\n\t/* Make sure we use a 128bit aligned buffer */\n\tchar *unaligned = (char *) palloc((total_elements * sizeof(UuidType)) + 15);\n\tUuidType *uuid_buffer = (UuidType *) TYPEALIGN(16, unaligned);\n\n\t/*\n\t * Note : the validity bits from the delta delta compression is directly usable\n\t *        in the result array, but the decompressed ingegers will be replaced\n\t *        with the UUIDs\n\t */\n\ttimestamp_array = delta_delta_decompress_all(PointerGetDatum(timestamp_compressed_data),\n\t\t\t\t\t\t\t\t\t\t\t\t INT8OID,\n\t\t\t\t\t\t\t\t\t\t\t\t CurrentMemoryContext);\n\n\tCheckCompressedData(timestamp_array->length == total_elements);\n\tCheckCompressedData(timestamp_array->null_count == header->num_nulls);\n\n\tuint64 *rand_b_and_variant = (uint64 *) palloc(header->rand_b_and_variant_size);\n\tmemcpy(rand_b_and_variant, rand_b_and_variant_compressed_data, header->rand_b_and_variant_size);\n\n\tuint64 *validity_bitmap = NULL;\n\tuint64 *timestamp_values = (uint64 *) timestamp_array->buffers[1];\n\n\tif (header->num_nulls > 0)\n\t{\n\t\tAssert(timestamp_array->buffers[0] != NULL);\n\t\tvalidity_bitmap = (uint64 *) timestamp_array->buffers[0];\n\n\t\tint value_position = 0;\n\t\tfor (int i = 0; i < total_elements; i++)\n\t\t{\n\t\t\tif (arrow_row_is_valid(validity_bitmap, i))\n\t\t\t{\n\t\t\t\tAssert(value_position < num_values);\n\t\t\t\tuuid_buffer[i].components[0] = pg_ntoh64(timestamp_values[i]);\n\t\t\t\tuuid_buffer[i].components[1] = rand_b_and_variant[value_position];\n\t\t\t\t++value_position;\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t{\n\t\tfor (int i = 0; i < total_elements; i++)\n\t\t{\n\t\t\tuuid_buffer[i].components[0] = pg_ntoh64(timestamp_values[i]);\n\t\t\tuuid_buffer[i].components[1] = rand_b_and_variant[i];\n\t\t}\n\t}\n\n\t/*\n\t * At this point I combined the uncompressed data in the rand_b_and_variant array\n\t * and the freshly decompressed data from the delta delta bulk decompressison.\n\t * I now free the temp data. Note that `timestamp_values` is the same pointer\n\t * as timestamp_array->buffers[1] , the second buffer in the ArrowArray.\n\t * This will be replaced with the uuid array.\n\t * */\n\tpfree(rand_b_and_variant);\n\tpfree(timestamp_values);\n\tMemoryContextSwitchTo(old_context);\n\n\t/*\n\t * This is the only fixup needed for the ArrowArray. Everything else\n\t * is valid data as generated by delta_delta_decompress_all()\n\t */\n\ttimestamp_array->buffers[1] = (uint8 *) uuid_buffer;\n\treturn timestamp_array;\n}\n\n/*\n * Local helpers\n */\nstatic void\nuuid_compressor_append_uuid(Compressor *compressor, Datum val)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tif (extended->internal == NULL)\n\t\textended->internal = uuid_compressor_alloc();\n\n\tpg_uuid_t *uuid = DatumGetUUIDP(val);\n\tEnsure(uuid != NULL, \"invalid UUID\");\n\tuuid_compressor_append_value(extended->internal, *uuid);\n}\n\nstatic void\nuuid_compressor_append_null_value(Compressor *compressor)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tif (extended->internal == NULL)\n\t\textended->internal = uuid_compressor_alloc();\n\n\tuuid_compressor_append_null(extended->internal);\n}\n\nstatic void *\nuuid_compressor_finish_and_reset(Compressor *compressor)\n{\n\tExtendedCompressor *extended = (ExtendedCompressor *) compressor;\n\tvoid *compressed = NULL;\n\tif (extended != NULL && extended->internal != NULL)\n\t{\n\t\tcompressed = uuid_compressor_finish(extended->internal);\n\t\tpfree(extended->internal);\n\t\textended->internal = NULL;\n\t}\n\treturn compressed;\n}\n\nstatic void\ndecompression_iterator_init(UuidDecompressionIterator *iter, void *compressed, Oid element_type,\n\t\t\t\t\t\t\tbool forward)\n{\n\tAssert(element_type == UUIDOID);\n\n\tArrowArray *arrow_array =\n\t\tuuid_decompress_all(PointerGetDatum(compressed), element_type, CurrentMemoryContext);\n\tint32 total_elements = arrow_array->length;\n\n\t*iter = (UuidDecompressionIterator){\n\t\t.base = { .compression_algorithm = COMPRESSION_ALGORITHM_UUID,\n\t\t\t\t  .forward = forward,\n\t\t\t\t  .element_type = element_type,\n\t\t\t\t  .try_next = (forward ? uuid_decompression_iterator_try_next_forward :\n\t\t\t\t\t\t\t\t\t\t uuid_decompression_iterator_try_next_reverse) },\n\t\t.position = (forward ? 0 : total_elements - 1),\n\t\t.total_elements = total_elements,\n\t\t.uuid_buffer = arrow_array,\n\t};\n}\n\nstatic void\npg_attribute_unused() silence_unused_warning(void)\n{\n\tsimple8brle_serialized_recv(NULL);\n}\n"
  },
  {
    "path": "tsl/src/compression/algorithms/uuid_compress.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n/*\n * uuid_compress is used to encode UUID values where there are 4 distinct parts\n * that are encoded separately. The UUID being encoded is optimised for the v7\n * UUID format. The four parts are:\n *\n *  - timestamp : (delta-delta encoding)\n *  - the version number and variant number concatenated : (simple8b_rle)\n *  - rand_a : is encoded as an array of bits\n *  - rand_b : is encoded as an array of bits\n */\n\n#include <postgres.h>\n#include \"compression/compression.h\"\n#include <fmgr.h>\n#include <lib/stringinfo.h>\n#include <utils/uuid.h>\n\ntypedef struct UuidCompressor UuidCompressor;\ntypedef struct UuidCompressed UuidCompressed;\ntypedef struct UuidDecompressionIterator UuidDecompressionIterator;\n\n/*\n * Compressor framework functions and definitions for the uuid_compress algorithm.\n */\n\nextern UuidCompressor *uuid_compressor_alloc(void);\nextern void uuid_compressor_append_null(UuidCompressor *compressor);\nextern void uuid_compressor_append_value(UuidCompressor *compressor, pg_uuid_t next_val);\nextern void *uuid_compressor_finish(UuidCompressor *compressor);\nextern bool uuid_compressed_has_nulls(const CompressedDataHeader *header);\n\nextern DecompressResult uuid_decompression_iterator_try_next_forward(DecompressionIterator *iter);\n\nextern DecompressionIterator *uuid_decompression_iterator_from_datum_forward(Datum uuid_compressed,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t Oid element_type);\n\nextern DecompressResult uuid_decompression_iterator_try_next_reverse(DecompressionIterator *iter);\n\nextern DecompressionIterator *uuid_decompression_iterator_from_datum_reverse(Datum uuid_compressed,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t Oid element_type);\n\nextern void uuid_compressed_send(CompressedDataHeader *header, StringInfo buffer);\n\nextern ArrowArray *uuid_decompress_all(Datum compressed, Oid element_type, MemoryContext dest_mctx);\n\nextern Datum uuid_compressed_recv(StringInfo buf);\n\nextern Compressor *uuid_compressor_for_type(Oid element_type);\n\n#define UUID_COMPRESS_ALGORITHM_DEFINITION                                                         \\\n\t{                                                                                              \\\n\t\t.iterator_init_forward = uuid_decompression_iterator_from_datum_forward,                   \\\n\t\t.iterator_init_reverse = uuid_decompression_iterator_from_datum_reverse,                   \\\n\t\t.decompress_all = uuid_decompress_all, .compressed_data_send = uuid_compressed_send,       \\\n\t\t.compressed_data_recv = uuid_compressed_recv,                                              \\\n\t\t.compressor_for_type = uuid_compressor_for_type,                                           \\\n\t\t.compressed_data_storage = TOAST_STORAGE_EXTERNAL,                                         \\\n\t}\n\n/*\n * Cross-module functions for the uuid_compress algorithm.\n */\n\nextern Datum tsl_uuid_compressor_append(PG_FUNCTION_ARGS);\nextern Datum tsl_uuid_compressor_finish(PG_FUNCTION_ARGS);\n"
  },
  {
    "path": "tsl/src/compression/api.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/* This file contains the implementation for SQL utility functions that\n *  compress and decompress chunks\n */\n#include <postgres.h>\n#include <access/tableam.h>\n#include <access/xact.h>\n#include <catalog/dependency.h>\n#include <catalog/index.h>\n#include <catalog/indexing.h>\n#include <catalog/pg_am.h>\n#include <commands/event_trigger.h>\n#include <commands/tablecmds.h>\n#include <commands/trigger.h>\n#include <libpq-fe.h>\n#include <miscadmin.h>\n#include <nodes/makefuncs.h>\n#include <nodes/parsenodes.h>\n#include <nodes/pg_list.h>\n#include <parser/parse_func.h>\n#include <postgres_ext.h>\n#include <storage/lmgr.h>\n#include <storage/lockdefs.h>\n#include <tcop/utility.h>\n#include <trigger.h>\n#include <utils/builtins.h>\n#include <utils/elog.h>\n#include <utils/fmgrprotos.h>\n#include <utils/inval.h>\n\n#include \"compat/compat.h\"\n#include \"annotations.h\"\n#include \"api.h\"\n#include \"cache.h\"\n#include \"chunk.h\"\n#include \"compression.h\"\n#include \"compression_storage.h\"\n#include \"create.h\"\n#include \"debug_point.h\"\n#include \"error_utils.h\"\n#include \"errors.h\"\n#include \"hypercube.h\"\n#include \"hypertable.h\"\n#include \"hypertable_cache.h\"\n#include \"nodes/columnar_scan/columnar_scan.h\"\n#include \"recompress.h\"\n#include \"scan_iterator.h\"\n#include \"scanner.h\"\n#include \"ts_catalog/array_utils.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"ts_catalog/chunk_column_stats.h\"\n#include \"ts_catalog/compression_chunk_size.h\"\n#include \"ts_catalog/compression_settings.h\"\n#include \"ts_catalog/continuous_agg.h\"\n#include \"utils.h\"\n#include \"wal_utils.h\"\n\ntypedef struct CompressChunkCxt\n{\n\tHypertable *srcht;\n\tChunk *srcht_chunk;\t\t /* chunk from srcht */\n\tHypertable *compress_ht; /*compressed table for srcht */\n} CompressChunkCxt;\n\nstatic Oid get_compressed_chunk_index_for_recompression(Chunk *uncompressed_chunk);\n\nstatic Node *\ncreate_dummy_query()\n{\n\tRawStmt *query = NULL;\n\tquery = makeNode(RawStmt);\n\tquery->stmt = (Node *) makeNode(SelectStmt);\n\treturn (Node *) query;\n}\n\nvoid\ncompression_chunk_size_catalog_insert(int32 src_chunk_id, const RelationSize *src_size,\n\t\t\t\t\t\t\t\t\t  int32 compress_chunk_id, const RelationSize *compress_size,\n\t\t\t\t\t\t\t\t\t  int64 rowcnt_pre_compression, int64 rowcnt_post_compression,\n\t\t\t\t\t\t\t\t\t  int64 rowcnt_frozen)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tRelation rel;\n\tTupleDesc desc;\n\tCatalogSecurityContext sec_ctx;\n\n\tDatum values[Natts_compression_chunk_size];\n\tbool nulls[Natts_compression_chunk_size] = { false };\n\n\trel = table_open(catalog_get_table_id(catalog, COMPRESSION_CHUNK_SIZE), RowExclusiveLock);\n\tdesc = RelationGetDescr(rel);\n\n\tmemset(values, 0, sizeof(values));\n\n\tvalues[AttrNumberGetAttrOffset(Anum_compression_chunk_size_chunk_id)] =\n\t\tInt32GetDatum(src_chunk_id);\n\tvalues[AttrNumberGetAttrOffset(Anum_compression_chunk_size_compressed_chunk_id)] =\n\t\tInt32GetDatum(compress_chunk_id);\n\tvalues[AttrNumberGetAttrOffset(Anum_compression_chunk_size_uncompressed_heap_size)] =\n\t\tInt64GetDatum(src_size->heap_size);\n\tvalues[AttrNumberGetAttrOffset(Anum_compression_chunk_size_uncompressed_toast_size)] =\n\t\tInt64GetDatum(src_size->toast_size);\n\tvalues[AttrNumberGetAttrOffset(Anum_compression_chunk_size_uncompressed_index_size)] =\n\t\tInt64GetDatum(src_size->index_size);\n\tvalues[AttrNumberGetAttrOffset(Anum_compression_chunk_size_compressed_heap_size)] =\n\t\tInt64GetDatum(compress_size->heap_size);\n\tvalues[AttrNumberGetAttrOffset(Anum_compression_chunk_size_compressed_toast_size)] =\n\t\tInt64GetDatum(compress_size->toast_size);\n\tvalues[AttrNumberGetAttrOffset(Anum_compression_chunk_size_compressed_index_size)] =\n\t\tInt64GetDatum(compress_size->index_size);\n\tvalues[AttrNumberGetAttrOffset(Anum_compression_chunk_size_numrows_pre_compression)] =\n\t\tInt64GetDatum(rowcnt_pre_compression);\n\tvalues[AttrNumberGetAttrOffset(Anum_compression_chunk_size_numrows_post_compression)] =\n\t\tInt64GetDatum(rowcnt_post_compression);\n\tvalues[AttrNumberGetAttrOffset(Anum_compression_chunk_size_numrows_frozen_immediately)] =\n\t\tInt64GetDatum(rowcnt_frozen);\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_insert_values(rel, desc, values, nulls);\n\tts_catalog_restore_user(&sec_ctx);\n\ttable_close(rel, RowExclusiveLock);\n}\n\nstatic int\ncompression_chunk_size_catalog_update_merged(int32 chunk_id, const RelationSize *size,\n\t\t\t\t\t\t\t\t\t\t\t int32 merge_chunk_id, const RelationSize *merge_size,\n\t\t\t\t\t\t\t\t\t\t\t int64 merge_rowcnt_pre_compression,\n\t\t\t\t\t\t\t\t\t\t\t int64 merge_rowcnt_post_compression)\n{\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(COMPRESSION_CHUNK_SIZE, RowExclusiveLock, CurrentMemoryContext);\n\tbool updated = false;\n\n\titerator.ctx.index =\n\t\tcatalog_get_index(ts_catalog_get(), COMPRESSION_CHUNK_SIZE, COMPRESSION_CHUNK_SIZE_PKEY);\n\tts_scan_iterator_scan_key_init(&iterator,\n\t\t\t\t\t\t\t\t   Anum_compression_chunk_size_pkey_chunk_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(chunk_id));\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tDatum values[Natts_compression_chunk_size];\n\t\tbool replIsnull[Natts_compression_chunk_size] = { false };\n\t\tbool repl[Natts_compression_chunk_size] = { false };\n\t\tbool should_free;\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\t\tHeapTuple new_tuple;\n\t\theap_deform_tuple(tuple, ts_scanner_get_tupledesc(ti), values, replIsnull);\n\n\t\t/* Increment existing sizes with sizes from uncompressed chunk. */\n\t\tvalues[AttrNumberGetAttrOffset(Anum_compression_chunk_size_uncompressed_heap_size)] =\n\t\t\tInt64GetDatum(size->heap_size +\n\t\t\t\t\t\t  DatumGetInt64(values[AttrNumberGetAttrOffset(\n\t\t\t\t\t\t\t  Anum_compression_chunk_size_uncompressed_heap_size)]));\n\t\trepl[AttrNumberGetAttrOffset(Anum_compression_chunk_size_uncompressed_heap_size)] = true;\n\t\tvalues[AttrNumberGetAttrOffset(Anum_compression_chunk_size_uncompressed_toast_size)] =\n\t\t\tInt64GetDatum(size->toast_size +\n\t\t\t\t\t\t  DatumGetInt64(values[AttrNumberGetAttrOffset(\n\t\t\t\t\t\t\t  Anum_compression_chunk_size_uncompressed_toast_size)]));\n\t\trepl[AttrNumberGetAttrOffset(Anum_compression_chunk_size_uncompressed_toast_size)] = true;\n\t\tvalues[AttrNumberGetAttrOffset(Anum_compression_chunk_size_uncompressed_index_size)] =\n\t\t\tInt64GetDatum(size->index_size +\n\t\t\t\t\t\t  DatumGetInt64(values[AttrNumberGetAttrOffset(\n\t\t\t\t\t\t\t  Anum_compression_chunk_size_uncompressed_index_size)]));\n\t\trepl[AttrNumberGetAttrOffset(Anum_compression_chunk_size_uncompressed_index_size)] = true;\n\t\tvalues[AttrNumberGetAttrOffset(Anum_compression_chunk_size_compressed_heap_size)] =\n\t\t\tInt64GetDatum(merge_size->heap_size);\n\t\trepl[AttrNumberGetAttrOffset(Anum_compression_chunk_size_compressed_heap_size)] = true;\n\t\tvalues[AttrNumberGetAttrOffset(Anum_compression_chunk_size_compressed_toast_size)] =\n\t\t\tInt64GetDatum(merge_size->toast_size);\n\t\trepl[AttrNumberGetAttrOffset(Anum_compression_chunk_size_compressed_toast_size)] = true;\n\t\tvalues[AttrNumberGetAttrOffset(Anum_compression_chunk_size_compressed_index_size)] =\n\t\t\tInt64GetDatum(merge_size->index_size);\n\t\trepl[AttrNumberGetAttrOffset(Anum_compression_chunk_size_compressed_index_size)] = true;\n\t\tvalues[AttrNumberGetAttrOffset(Anum_compression_chunk_size_numrows_pre_compression)] =\n\t\t\tInt64GetDatum(merge_rowcnt_pre_compression +\n\t\t\t\t\t\t  DatumGetInt64(values[AttrNumberGetAttrOffset(\n\t\t\t\t\t\t\t  Anum_compression_chunk_size_numrows_pre_compression)]));\n\t\trepl[AttrNumberGetAttrOffset(Anum_compression_chunk_size_numrows_pre_compression)] = true;\n\t\tvalues[AttrNumberGetAttrOffset(Anum_compression_chunk_size_numrows_post_compression)] =\n\t\t\tInt64GetDatum(merge_rowcnt_post_compression +\n\t\t\t\t\t\t  DatumGetInt64(values[AttrNumberGetAttrOffset(\n\t\t\t\t\t\t\t  Anum_compression_chunk_size_numrows_post_compression)]));\n\t\trepl[AttrNumberGetAttrOffset(Anum_compression_chunk_size_numrows_post_compression)] = true;\n\n\t\tnew_tuple =\n\t\t\theap_modify_tuple(tuple, ts_scanner_get_tupledesc(ti), values, replIsnull, repl);\n\t\tts_catalog_update(ti->scanrel, new_tuple);\n\t\theap_freetuple(new_tuple);\n\n\t\tif (should_free)\n\t\t\theap_freetuple(tuple);\n\n\t\tupdated = true;\n\t\tbreak;\n\t}\n\n\tts_scan_iterator_end(&iterator);\n\tts_scan_iterator_close(&iterator);\n\treturn updated;\n}\n\nstatic void\nget_hypertable_or_cagg_name(Hypertable *ht, Name objname)\n{\n\tContinuousAggHypertableStatus status = ts_continuous_agg_hypertable_status(ht->fd.id);\n\tif (status == HypertableIsNotContinuousAgg || status == HypertableIsRawTable)\n\t\tnamestrcpy(objname, NameStr(ht->fd.table_name));\n\telse if (status == HypertableIsMaterialization)\n\t{\n\t\tContinuousAgg *cagg = ts_continuous_agg_find_by_mat_hypertable_id(ht->fd.id, false);\n\t\tnamestrcpy(objname, NameStr(cagg->data.user_view_name));\n\t}\n\telse\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t errmsg(\"unexpected hypertable status for %s %d\",\n\t\t\t\t\t\tNameStr(ht->fd.table_name),\n\t\t\t\t\t\tstatus)));\n\t}\n}\n\nstatic void\ncompresschunkcxt_init(CompressChunkCxt *cxt, Cache *hcache, Oid hypertable_relid, Oid chunk_relid)\n{\n\tHypertable *srcht = ts_hypertable_cache_get_entry(hcache, hypertable_relid, CACHE_FLAG_NONE);\n\tHypertable *compress_ht;\n\tChunk *srcchunk;\n\n\tts_hypertable_permissions_check(srcht->main_table_relid, GetUserId());\n\n\tif (!TS_HYPERTABLE_HAS_COMPRESSION_TABLE(srcht))\n\t{\n\t\tNameData cagg_ht_name;\n\t\tget_hypertable_or_cagg_name(srcht, &cagg_ht_name);\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"columnstore not enabled on \\\"%s\\\"\", NameStr(cagg_ht_name)),\n\t\t\t\t errdetail(\"It is not possible to convert chunks to columnstore on a hypertable or\"\n\t\t\t\t\t\t   \" continuous aggregate that does not have columnstore enabled.\"),\n\t\t\t\t errhint(\"Enable columnstore using ALTER TABLE/MATERIALIZED VIEW with\"\n\t\t\t\t\t\t \" the timescaledb.enable_columnstore option.\")));\n\t}\n\tcompress_ht = ts_hypertable_get_by_id(srcht->fd.compressed_hypertable_id);\n\tif (compress_ht == NULL)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_TS_HYPERTABLE_NOT_EXIST),\n\t\t\t\t errmsg(\"missing columnstore-enabled hypertable\")));\n\t/* user has to be the owner of the compression table too */\n\tts_hypertable_permissions_check(compress_ht->main_table_relid, GetUserId());\n\n\tif (!srcht->space) /* something is wrong */\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR), errmsg(\"missing hyperspace for hypertable\")));\n\t/* refetch the srcchunk with all attributes filled in */\n\tsrcchunk = ts_chunk_get_by_relid(chunk_relid, true);\n\tts_chunk_validate_chunk_status_for_operation(srcchunk, CHUNK_COMPRESS, true);\n\tcxt->srcht = srcht;\n\tcxt->compress_ht = compress_ht;\n\tcxt->srcht_chunk = srcchunk;\n}\n\nstatic Chunk *\nfind_chunk_to_merge_into(Hypertable *ht, Chunk *current_chunk)\n{\n\tint64 max_chunk_interval, current_chunk_interval = 0, compressed_chunk_interval = 0;\n\tChunk *previous_chunk;\n\tPoint *p;\n\n\tconst Dimension *time_dim = hyperspace_get_open_dimension(ht->space, 0);\n\n\tif (!time_dim || time_dim->fd.compress_interval_length == 0)\n\t\treturn NULL;\n\n\tAssert(current_chunk->cube->num_slices > 0);\n\tAssert(current_chunk->cube->slices[0]->fd.dimension_id == time_dim->fd.id);\n\n\tmax_chunk_interval = time_dim->fd.compress_interval_length;\n\n\tp = ts_point_create(current_chunk->cube->num_slices);\n\n\t/* First coordinate is the time coordinates and we want it to fall into previous chunk\n\t * hence we reduce it by 1\n\t */\n\tp->coordinates[p->num_coords++] = current_chunk->cube->slices[0]->fd.range_start - 1;\n\tcurrent_chunk_interval = current_chunk->cube->slices[0]->fd.range_end -\n\t\t\t\t\t\t\t current_chunk->cube->slices[0]->fd.range_start;\n\n\tfor (int i = p->num_coords; i < current_chunk->cube->num_slices; i++)\n\t{\n\t\tp->coordinates[p->num_coords++] = current_chunk->cube->slices[i]->fd.range_start;\n\t}\n\n\tprevious_chunk = ts_hypertable_find_chunk_for_point(ht, p, ExclusiveLock);\n\n\t/* If there is no previous adjacent chunk along the time dimension or\n\t * if it hasn't been compressed yet, we can't merge.\n\t */\n\tif (!previous_chunk || !OidIsValid(previous_chunk->fd.compressed_chunk_id))\n\t\treturn NULL;\n\n\tAssert(previous_chunk->cube->num_slices > 0);\n\tAssert(previous_chunk->cube->slices[0]->fd.dimension_id == time_dim->fd.id);\n\n\tcompressed_chunk_interval = previous_chunk->cube->slices[0]->fd.range_end -\n\t\t\t\t\t\t\t\tprevious_chunk->cube->slices[0]->fd.range_start;\n\n\t/* If the slices do not match (except on time dimension), we cannot merge the chunks. */\n\tif (previous_chunk->cube->num_slices != current_chunk->cube->num_slices)\n\t\treturn NULL;\n\n\tfor (int i = 1; i < previous_chunk->cube->num_slices; i++)\n\t{\n\t\tif (previous_chunk->cube->slices[i]->fd.id != current_chunk->cube->slices[i]->fd.id)\n\t\t{\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\t/* If the compressed chunk is full, we can't merge any more. */\n\tif (compressed_chunk_interval == 0 ||\n\t\tcompressed_chunk_interval + current_chunk_interval > max_chunk_interval)\n\t\treturn NULL;\n\n\t/* Get reloid of the previous compressed chunk via settings */\n\tCompressionSettings *prev_comp_settings = ts_compression_settings_get(previous_chunk->table_id);\n\tCompressionSettings *ht_comp_settings = ts_compression_settings_get(ht->main_table_relid);\n\tif (!ts_compression_settings_equal_with_defaults(ht_comp_settings, prev_comp_settings))\n\t\treturn NULL;\n\n\t/* We don't support merging chunks with sequence numbers */\n\tif (get_attnum(prev_comp_settings->fd.compress_relid,\n\t\t\t\t   COMPRESSION_COLUMN_METADATA_SEQUENCE_NUM_NAME) != InvalidAttrNumber)\n\t\treturn NULL;\n\n\treturn previous_chunk;\n}\n\n/* Check if compression order is violated by merging in a new chunk\n * Because data merged in uses higher sequence numbers than any data already in the chunk,\n * the only way the order is guaranteed can be if we know the data we are merging in would come\n * after the existing data according to the compression order. This is true if the data being merged\n * in has timestamps greater than the existing data and the first column in the order by is time\n * ASC.\n *\n * The CompressChunkCxt references the chunk we are merging and mergable_chunk is the chunk we\n * are merging into.\n */\nstatic bool\ncheck_is_chunk_order_violated_by_merge(CompressChunkCxt *cxt, const Dimension *time_dim,\n\t\t\t\t\t\t\t\t\t   Chunk *mergable_chunk)\n{\n\tconst DimensionSlice *mergable_slice =\n\t\tts_hypercube_get_slice_by_dimension_id(mergable_chunk->cube, time_dim->fd.id);\n\tif (!mergable_slice)\n\t\telog(ERROR, \"mergeable chunk has no time dimension slice\");\n\tconst DimensionSlice *compressed_slice =\n\t\tts_hypercube_get_slice_by_dimension_id(cxt->srcht_chunk->cube, time_dim->fd.id);\n\tif (!compressed_slice)\n\t\telog(ERROR, \"columnstore chunk has no time dimension slice\");\n\t/*\n\t * Ensure the compressed chunk is AFTER the chunk that\n\t * it is being merged into. This is already guaranteed by previous checks.\n\t */\n\tEnsure(mergable_slice->fd.range_end == compressed_slice->fd.range_start,\n\t\t   \"chunk being merged is not after the chunk that is being merged into\");\n\tCompressionSettings *ht_settings =\n\t\tts_compression_settings_get(mergable_chunk->hypertable_relid);\n\n\tchar *attname = get_attname(cxt->srcht->main_table_relid, time_dim->column_attno, false);\n\tint index = ts_array_position(ht_settings->fd.orderby, attname);\n\n\t/* Primary dimension column should be first compress_orderby column. */\n\tif (index != 1)\n\t\treturn true;\n\n\treturn false;\n}\n\nstatic Oid\ncompress_chunk_impl(Oid hypertable_relid, Oid chunk_relid)\n{\n\tOid result_chunk_id = chunk_relid;\n\tCompressChunkCxt cxt = { 0 };\n\tChunk *compress_ht_chunk, *mergable_chunk;\n\tCache *hcache;\n\tRelationSize before_size, after_size;\n\tCompressionStats cstat;\n\tbool new_compressed_chunk = false;\n\n\thcache = ts_hypertable_cache_pin();\n\tcompresschunkcxt_init(&cxt, hcache, hypertable_relid, chunk_relid);\n\n\t/* acquire locks on src and compress hypertable and src chunk */\n\tereport(DEBUG1,\n\t\t\t(errmsg(\"acquiring locks for converting to columnstore \\\"%s.%s\\\"\",\n\t\t\t\t\tget_namespace_name(get_rel_namespace(chunk_relid)),\n\t\t\t\t\tget_rel_name(chunk_relid))));\n\tLockRelationOid(cxt.srcht->main_table_relid, AccessShareLock);\n\tLockRelationOid(cxt.compress_ht->main_table_relid, AccessShareLock);\n\tLockRelationOid(cxt.srcht_chunk->table_id, ExclusiveLock);\n\n\t/* acquire locks on catalog tables to keep till end of txn */\n\tLockRelationOid(catalog_get_table_id(ts_catalog_get(), CHUNK), RowExclusiveLock);\n\tereport(DEBUG1,\n\t\t\t(errmsg(\"locks acquired for converting to columnstore \\\"%s.%s\\\"\",\n\t\t\t\t\tget_namespace_name(get_rel_namespace(chunk_relid)),\n\t\t\t\t\tget_rel_name(chunk_relid))));\n\n\tDEBUG_WAITPOINT(\"compress_chunk_impl_start\");\n\n\t/*\n\t * Re-read the state of the chunk after all locks have been acquired and ensure\n\t * it is still uncompressed. Another process running in parallel might have\n\t * already performed the compression while we were waiting for the locks to be\n\t * acquired.\n\t */\n\tChunk *chunk_state_after_lock = ts_chunk_get_by_relid(chunk_relid, true);\n\n\t/* Throw error if chunk has invalid status for operation */\n\tts_chunk_validate_chunk_status_for_operation(chunk_state_after_lock, CHUNK_COMPRESS, true);\n\n\t/* get compression properties for hypertable */\n\tmergable_chunk = find_chunk_to_merge_into(cxt.srcht, cxt.srcht_chunk);\n\tif (!mergable_chunk)\n\t{\n\t\t/*\n\t\t * Set up a dummy parsetree since we're calling AlterTableInternal\n\t\t * inside create_compress_chunk(). We can use anything here because we\n\t\t * are not calling EventTriggerDDLCommandEnd but we use a parse tree\n\t\t * type that CreateCommandTag can handle to avoid spurious printouts\n\t\t * in the event that EventTriggerDDLCommandEnd is called.\n\t\t */\n\t\tEventTriggerAlterTableStart(create_dummy_query());\n\t\t/* create compressed chunk and a new table */\n\t\tcompress_ht_chunk = create_compress_chunk(cxt.compress_ht, cxt.srcht_chunk, InvalidOid);\n\t\t/* Associate compressed chunk with main chunk. */\n\t\tts_chunk_set_compressed_chunk(cxt.srcht_chunk, compress_ht_chunk->fd.id);\n\t\tnew_compressed_chunk = true;\n\t\tereport(DEBUG1,\n\t\t\t\t(errmsg(\"new columnstore chunk \\\"%s.%s\\\" created\",\n\t\t\t\t\t\tNameStr(compress_ht_chunk->fd.schema_name),\n\t\t\t\t\t\tNameStr(compress_ht_chunk->fd.table_name))));\n\n\t\tEventTriggerAlterTableEnd();\n\t}\n\telse\n\t{\n\t\t/* use an existing compressed chunk to compress into */\n\t\tcompress_ht_chunk = ts_chunk_get_by_id(mergable_chunk->fd.compressed_chunk_id, true);\n\t\tresult_chunk_id = mergable_chunk->table_id;\n\t\tereport(DEBUG1,\n\t\t\t\t(errmsg(\"merge into existing columnstore chunk \\\"%s.%s\\\"\",\n\t\t\t\t\t\tNameStr(compress_ht_chunk->fd.schema_name),\n\t\t\t\t\t\tNameStr(compress_ht_chunk->fd.table_name))));\n\t}\n\n\t/* Since the compressed relation is created in the same transaction as the tuples that will be\n\t * written by the compressor, we can insert the tuple directly in frozen state. This is the same\n\t * logic as performed in COPY INSERT FROZEN.\n\t *\n\t * Note: Tuples inserted with HEAP_INSERT_FROZEN become immediately visible to all transactions\n\t * (they violate the MVCC pattern). So, this flag can only be used when creating the compressed\n\t * chunk in the same transaction as the compressed tuples are inserted.\n\t *\n\t * If this isn't the case, then tuples can be seen multiple times by parallel readers - once in\n\t * the uncompressed part of the hypertable (since they are not deleted in the transaction) and\n\t * once in the compressed part of the hypertable since the MVCC semantic is violated due to the\n\t * flag.\n\t *\n\t * In contrast, when the compressed chunk part is created in the same transaction as the tuples\n\t * are written, the compressed chunk (i.e., the catalog entry) becomes visible to other\n\t * transactions only after the transaction that performs the compression is committed and\n\t * the uncompressed chunk is truncated.\n\t */\n\tint insert_options = new_compressed_chunk ? HEAP_INSERT_FROZEN : 0;\n\n\tbefore_size = ts_relation_size_impl(cxt.srcht_chunk->table_id);\n\n\tcstat = compress_chunk(cxt.srcht_chunk->table_id, compress_ht_chunk->table_id, insert_options);\n\tafter_size = ts_relation_size_impl(compress_ht_chunk->table_id);\n\n\tif (cxt.srcht->range_space)\n\t\tts_chunk_column_stats_calculate(cxt.srcht, cxt.srcht_chunk);\n\n\tif (new_compressed_chunk)\n\t{\n\t\tcompression_chunk_size_catalog_insert(cxt.srcht_chunk->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t  &before_size,\n\t\t\t\t\t\t\t\t\t\t\t  compress_ht_chunk->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t  &after_size,\n\t\t\t\t\t\t\t\t\t\t\t  cstat.rowcnt_pre_compression,\n\t\t\t\t\t\t\t\t\t\t\t  cstat.rowcnt_post_compression,\n\t\t\t\t\t\t\t\t\t\t\t  cstat.rowcnt_frozen);\n\n\t\t/* Detect and emit warning if poor compression ratio is found */\n\t\tfloat compression_ratio = ((float) before_size.total_size / after_size.total_size);\n\t\tfloat POOR_COMPRESSION_THRESHOLD = 1.0;\n\t\tif (ts_guc_enable_compression_ratio_warnings &&\n\t\t\tcompression_ratio < POOR_COMPRESSION_THRESHOLD)\n\t\t\tereport(WARNING,\n\t\t\t\t\terrcode(ERRCODE_WARNING),\n\t\t\t\t\terrmsg(\"poor compression ratio detected for chunk \\\"%s\\\"'\",\n\t\t\t\t\t\t   get_rel_name(chunk_relid)),\n\t\t\t\t\terrdetail(\"Chunk \\\"%s\\\" has a poor compression ratio: %.2f. Size before \"\n\t\t\t\t\t\t\t  \"compression: \" INT64_FORMAT\n\t\t\t\t\t\t\t  \" bytes. Size after compression: \" INT64_FORMAT \" bytes\",\n\t\t\t\t\t\t\t  get_rel_name(chunk_relid),\n\t\t\t\t\t\t\t  compression_ratio,\n\t\t\t\t\t\t\t  before_size.total_size,\n\t\t\t\t\t\t\t  after_size.total_size),\n\t\t\t\t\terrhint(\"Changing compression settings for \\\"%s\\\" can improve compression rate\",\n\t\t\t\t\t\t\tget_rel_name(hypertable_relid)));\n\t}\n\telse\n\t{\n\t\tcompression_chunk_size_catalog_update_merged(mergable_chunk->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t &before_size,\n\t\t\t\t\t\t\t\t\t\t\t\t\t compress_ht_chunk->fd.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t &after_size,\n\t\t\t\t\t\t\t\t\t\t\t\t\t cstat.rowcnt_pre_compression,\n\t\t\t\t\t\t\t\t\t\t\t\t\t cstat.rowcnt_post_compression);\n\n\t\tconst Dimension *time_dim = hyperspace_get_open_dimension(cxt.srcht->space, 0);\n\t\tAssert(time_dim != NULL);\n\n\t\tbool chunk_unordered =\n\t\t\tcheck_is_chunk_order_violated_by_merge(&cxt, time_dim, mergable_chunk);\n\n\t\tts_chunk_merge_on_dimension(cxt.srcht, mergable_chunk, cxt.srcht_chunk, time_dim->fd.id);\n\n\t\tif (chunk_unordered)\n\t\t{\n\t\t\tts_chunk_set_unordered(mergable_chunk);\n\t\t\ttsl_compress_chunk_wrapper(mergable_chunk, true, false);\n\t\t}\n\t}\n\n\tts_cache_release(&hcache);\n\treturn result_chunk_id;\n}\n\nstatic void\ndecompress_chunk_impl(Chunk *uncompressed_chunk, bool if_compressed)\n{\n\tCache *hcache;\n\tHypertable *uncompressed_hypertable =\n\t\tts_hypertable_cache_get_cache_and_entry(uncompressed_chunk->hypertable_relid,\n\t\t\t\t\t\t\t\t\t\t\t\tCACHE_FLAG_NONE,\n\t\t\t\t\t\t\t\t\t\t\t\t&hcache);\n\tHypertable *compressed_hypertable;\n\tChunk *compressed_chunk;\n\n\tts_hypertable_permissions_check(uncompressed_hypertable->main_table_relid, GetUserId());\n\n\tif (TS_HYPERTABLE_IS_INTERNAL_COMPRESSION_TABLE(uncompressed_hypertable))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t errmsg(\n\t\t\t\t\t \"convert_to_rowstore must not be called on the internal columnstore chunk\")));\n\n\tcompressed_hypertable =\n\t\tts_hypertable_get_by_id(uncompressed_hypertable->fd.compressed_hypertable_id);\n\tif (compressed_hypertable == NULL)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_TS_HYPERTABLE_NOT_EXIST),\n\t\t\t\t errmsg(\"missing columnstore-enabled hypertable\")));\n\n\tif (uncompressed_chunk->fd.hypertable_id != uncompressed_hypertable->fd.id)\n\t\telog(ERROR, \"hypertable and chunk do not match\");\n\n\tif (uncompressed_chunk->fd.compressed_chunk_id == INVALID_CHUNK_ID)\n\t{\n\t\tts_cache_release(&hcache);\n\t\tereport((if_compressed ? NOTICE : ERROR),\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"chunk \\\"%s\\\" is not converted to columnstore\",\n\t\t\t\t\t\tget_rel_name(uncompressed_chunk->table_id))));\n\t\treturn;\n\t}\n\n\twrite_logical_replication_msg_decompression_start();\n\n\tts_chunk_validate_chunk_status_for_operation(uncompressed_chunk, CHUNK_DECOMPRESS, true);\n\tcompressed_chunk = ts_chunk_get_by_id(uncompressed_chunk->fd.compressed_chunk_id, true);\n\n\tereport(DEBUG1,\n\t\t\t(errmsg(\"acquiring locks for converting to rowstore \\\"%s.%s\\\"\",\n\t\t\t\t\tNameStr(uncompressed_chunk->fd.schema_name),\n\t\t\t\t\tNameStr(uncompressed_chunk->fd.table_name))));\n\t/* acquire locks on src and compress hypertable and src chunk */\n\tLockRelationOid(uncompressed_hypertable->main_table_relid, AccessShareLock);\n\tLockRelationOid(compressed_hypertable->main_table_relid, AccessShareLock);\n\n\t/*\n\t * Acquire an ExclusiveLock on the uncompressed and the compressed\n\t * chunk (the chunks can still be accessed by reads).\n\t *\n\t * The lock on the compressed chunk is needed because it gets deleted\n\t * after decompression. The lock on the uncompressed chunk is needed\n\t * to avoid deadlocks (e.g., caused by later lock upgrades or parallel\n\t * started chunk compressions).\n\t *\n\t * Note: Also the function decompress_chunk() will request an\n\t *       ExclusiveLock on the compressed and on the uncompressed\n\t *       chunk. See the comments in function about the concurrency of\n\t *       operations.\n\t */\n\tLockRelationOid(uncompressed_chunk->table_id, ExclusiveLock);\n\tLockRelationOid(compressed_chunk->table_id, ExclusiveLock);\n\n\t/* acquire locks on catalog tables to keep till end of txn */\n\tLockRelationOid(catalog_get_table_id(ts_catalog_get(), CHUNK), RowExclusiveLock);\n\tereport(DEBUG1,\n\t\t\t(errmsg(\"locks acquired for converting to rowstore \\\"%s.%s\\\"\",\n\t\t\t\t\tNameStr(uncompressed_chunk->fd.schema_name),\n\t\t\t\t\tNameStr(uncompressed_chunk->fd.table_name))));\n\n\tDEBUG_WAITPOINT(\"decompress_chunk_impl_start\");\n\n\t/*\n\t * Re-read the state of the chunk after all locks have been acquired and ensure\n\t * it is still compressed. Another process running in parallel might have\n\t * already performed the decompression while we were waiting for the locks to be\n\t * acquired.\n\t */\n\tChunk *chunk_state_after_lock = ts_chunk_get_by_id(uncompressed_chunk->fd.id, true);\n\n\t/* Throw error if chunk has invalid status for operation */\n\tts_chunk_validate_chunk_status_for_operation(chunk_state_after_lock, CHUNK_DECOMPRESS, true);\n\n\tdecompress_chunk(compressed_chunk->table_id, uncompressed_chunk->table_id);\n\n\t/* Delete the compressed chunk */\n\tts_compression_chunk_size_delete(uncompressed_chunk->fd.id);\n\tts_chunk_clear_compressed_chunk(uncompressed_chunk);\n\tts_compression_settings_delete(uncompressed_chunk->table_id);\n\n\t/*\n\t * Lock the compressed chunk that is going to be deleted. At this point,\n\t * the reference to the compressed chunk is already removed from the\n\t * catalog but we need to block readers from accessing this chunk\n\t * until the catalog changes are visible to them.\n\t *\n\t * Note: Calling performMultipleDeletions in chunk_index_tuple_delete\n\t * also requests an AccessExclusiveLock on the compressed_chunk. However,\n\t * this call makes the lock on the chunk explicit.\n\t */\n\tLockRelationOid(uncompressed_chunk->table_id, AccessExclusiveLock);\n\tLockRelationOid(compressed_chunk->table_id, AccessExclusiveLock);\n\tts_chunk_drop(compressed_chunk, DROP_RESTRICT, -1);\n\tts_cache_release(&hcache);\n\twrite_logical_replication_msg_decompression_end();\n}\n\nstatic bool\nrecompress_chunk_impl(Chunk *chunk, Oid *uncompressed_chunk_id, bool recompress)\n{\n\tCompressionSettings *chunk_settings = ts_compression_settings_get(chunk->table_id);\n\tbool recompressed = false;\n\n\tif (!chunk_settings || !chunk_settings->fd.orderby)\n\t{\n\t\telog(NOTICE,\n\t\t\t \"in-memory recompression is disabled due to no order by, \"\n\t\t\t \"performing decompress/compress on chunk \\\"%s.%s\\\"\",\n\t\t\t NameStr(chunk->fd.schema_name),\n\t\t\t NameStr(chunk->fd.table_name));\n\t\treturn false;\n\t}\n\telse if (!get_compressed_chunk_index_for_recompression(chunk))\n\t{\n\t\telog(NOTICE,\n\t\t\t \"in-memory recompression is disabled due to no compressed chunk index, \"\n\t\t\t \"performing decompress/compress on chunk \\\"%s.%s\\\"\",\n\t\t\t NameStr(chunk->fd.schema_name),\n\t\t\t NameStr(chunk->fd.table_name));\n\t\treturn false;\n\t}\n\n\t/*\n\t * Choose recompression strategy based on chunk state and settings:\n\t * 1. Segmentwise recompression: For partial chunks\n\t * 2. In-memory recompression: For other compressed chunks with matching settings\n\t * 3. Fallback to decompress/compress: When neither strategy is applicable\n\t */\n\n\tif (ts_chunk_is_partial(chunk))\n\t{\n\t\tif (!ts_guc_enable_segmentwise_recompression)\n\t\t{\n\t\t\tereport(NOTICE,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"segmentwise in-memory recompression functionality disabled, \"\n\t\t\t\t\t\t\t\"set timescaledb.enable_segmentwise_recompression to on\")));\n\t\t\treturn false;\n\t\t}\n\n\t\t*uncompressed_chunk_id = recompress_chunk_segmentwise_impl(chunk);\n\t\trecompressed = true;\n\t}\n\telse\n\t{\n\t\tif (!ts_guc_enable_in_memory_recompression)\n\t\t{\n\t\t\tereport(DEBUG1,\n\t\t\t\t\t(errcode(ERRCODE_WARNING),\n\t\t\t\t\t errmsg(\"in-memory recompression functionality disabled, \"\n\t\t\t\t\t\t\t\"set timescaledb.enable_in_memory_recompression to on\")));\n\t\t\treturn false;\n\t\t}\n\n\t\trecompressed = recompress_chunk_in_memory_impl(chunk);\n\t}\n\n\treturn recompressed;\n}\n/*\n * Create a new compressed chunk using existing table with compressed data.\n *\n * chunk_relid - non-compressed chunk relid\n * chunk_table - table containing compressed data\n */\nDatum\ntsl_create_compressed_chunk(PG_FUNCTION_ARGS)\n{\n\tOid chunk_relid = PG_GETARG_OID(0);\n\tOid chunk_table = PG_GETARG_OID(1);\n\tRelationSize uncompressed_size = { .heap_size = PG_GETARG_INT64(2),\n\t\t\t\t\t\t\t\t\t   .toast_size = PG_GETARG_INT64(3),\n\t\t\t\t\t\t\t\t\t   .index_size = PG_GETARG_INT64(4) };\n\tRelationSize compressed_size = { .heap_size = PG_GETARG_INT64(5),\n\t\t\t\t\t\t\t\t\t .toast_size = PG_GETARG_INT64(6),\n\t\t\t\t\t\t\t\t\t .index_size = PG_GETARG_INT64(7) };\n\tint64 numrows_pre_compression = PG_GETARG_INT64(8);\n\tint64 numrows_post_compression = PG_GETARG_INT64(9);\n\tChunk *chunk;\n\tChunk *compress_ht_chunk;\n\tCache *hcache;\n\tCompressChunkCxt cxt;\n\tbool chunk_was_compressed;\n\n\tAssert(!PG_ARGISNULL(0));\n\tAssert(!PG_ARGISNULL(1));\n\n\tts_feature_flag_check(FEATURE_HYPERTABLE_COMPRESSION);\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\tchunk = ts_chunk_get_by_relid(chunk_relid, true);\n\thcache = ts_hypertable_cache_pin();\n\tcompresschunkcxt_init(&cxt, hcache, chunk->hypertable_relid, chunk_relid);\n\n\t/* Acquire locks on src and compress hypertable and src chunk */\n\tLockRelationOid(cxt.srcht->main_table_relid, AccessShareLock);\n\tLockRelationOid(cxt.compress_ht->main_table_relid, AccessShareLock);\n\tLockRelationOid(cxt.srcht_chunk->table_id, ShareLock);\n\n\t/* Acquire locks on catalog tables to keep till end of txn */\n\tLockRelationOid(catalog_get_table_id(ts_catalog_get(), CHUNK), RowExclusiveLock);\n\n\t/*\n\t * Set up a dummy parsetree since we're calling AlterTableInternal inside\n\t * create_compress_chunk(). We can use anything here because we are not\n\t * calling EventTriggerDDLCommandEnd but we use a parse tree type that\n\t * CreateCommandTag can handle to avoid spurious printouts.\n\t */\n\tEventTriggerAlterTableStart(create_dummy_query());\n\t/* Create compressed chunk using existing table */\n\tcompress_ht_chunk = create_compress_chunk(cxt.compress_ht, cxt.srcht_chunk, chunk_table);\n\tEventTriggerAlterTableEnd();\n\n\t/* Insert empty stats to compression_chunk_size */\n\tcompression_chunk_size_catalog_insert(cxt.srcht_chunk->fd.id,\n\t\t\t\t\t\t\t\t\t\t  &uncompressed_size,\n\t\t\t\t\t\t\t\t\t\t  compress_ht_chunk->fd.id,\n\t\t\t\t\t\t\t\t\t\t  &compressed_size,\n\t\t\t\t\t\t\t\t\t\t  numrows_pre_compression,\n\t\t\t\t\t\t\t\t\t\t  numrows_post_compression,\n\t\t\t\t\t\t\t\t\t\t  0);\n\n\tchunk_was_compressed = ts_chunk_is_compressed(cxt.srcht_chunk);\n\tts_chunk_set_compressed_chunk(cxt.srcht_chunk, compress_ht_chunk->fd.id);\n\tif (!chunk_was_compressed && ts_table_has_tuples(cxt.srcht_chunk->table_id, AccessShareLock))\n\t{\n\t\t/* The chunk was not compressed before it had the compressed chunk\n\t\t * attached to it, and it contains rows, so we set it to be partial.\n\t\t */\n\t\tts_chunk_set_partial(cxt.srcht_chunk);\n\t}\n\tts_cache_release(&hcache);\n\n\tPG_RETURN_OID(chunk_relid);\n}\n\nDatum\ntsl_compress_chunk(PG_FUNCTION_ARGS)\n{\n\tOid uncompressed_chunk_id = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\tbool if_not_compressed = PG_ARGISNULL(1) ? true : PG_GETARG_BOOL(1);\n\tbool recompress = PG_ARGISNULL(2) ? false : PG_GETARG_BOOL(2);\n\n\tts_feature_flag_check(FEATURE_HYPERTABLE_COMPRESSION);\n\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\tChunk *chunk = ts_chunk_get_by_relid(uncompressed_chunk_id, true);\n\n\tuncompressed_chunk_id = tsl_compress_chunk_wrapper(chunk, if_not_compressed, recompress);\n\n\tPG_RETURN_OID(uncompressed_chunk_id);\n}\n\nOid\ntsl_compress_chunk_wrapper(Chunk *chunk, bool if_not_compressed, bool recompress)\n{\n\tOid uncompressed_chunk_id = chunk->table_id;\n\n\twrite_logical_replication_msg_compression_start();\n\n\tif (ts_chunk_needs_compression(chunk))\n\t{\n\t\tuncompressed_chunk_id = compress_chunk_impl(chunk->hypertable_relid, chunk->table_id);\n\t}\n\telse if (recompress || ts_chunk_needs_recompression(chunk))\n\t{\n\t\t/* Try in-memory recompression first and then fall back to decompress/recompress */\n\t\tbool recompressed = recompress_chunk_impl(chunk, &uncompressed_chunk_id, recompress);\n\t\tif (!recompressed)\n\t\t{\n\t\t\t/* TODO: move away from manual decompression/compression */\n\t\t\telog(DEBUG1,\n\t\t\t\t \"falling back to compress/decompress, performing full \"\n\t\t\t\t \"recompression on chunk \\\"%s.%s\\\"\",\n\t\t\t\t NameStr(chunk->fd.schema_name),\n\t\t\t\t NameStr(chunk->fd.table_name));\n\t\t\tdecompress_chunk_impl(chunk, false);\n\t\t\tcompress_chunk_impl(chunk->hypertable_relid, chunk->table_id);\n\t\t}\n\t}\n\telse\n\t{\n\t\tereport((if_not_compressed ? NOTICE : ERROR),\n\t\t\t\t(errcode(ERRCODE_DUPLICATE_OBJECT),\n\t\t\t\t errmsg(\"chunk \\\"%s\\\" is already converted to columnstore\",\n\t\t\t\t\t\tget_rel_name(chunk->table_id))));\n\t}\n\n\twrite_logical_replication_msg_compression_end();\n\treturn uncompressed_chunk_id;\n}\n\nDatum\ntsl_decompress_chunk(PG_FUNCTION_ARGS)\n{\n\tOid uncompressed_chunk_id = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\tbool if_compressed = PG_ARGISNULL(1) ? true : PG_GETARG_BOOL(1);\n\tint32 chunk_id;\n\n\tts_feature_flag_check(FEATURE_HYPERTABLE_COMPRESSION);\n\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\tChunk *uncompressed_chunk = ts_chunk_get_by_relid(uncompressed_chunk_id, true);\n\tchunk_id = uncompressed_chunk->fd.id;\n\n\tHypertable *ht = ts_hypertable_get_by_id(uncompressed_chunk->fd.hypertable_id);\n\tts_hypertable_permissions_check(ht->main_table_relid, GetUserId());\n\n\tif (!ht->fd.compressed_hypertable_id)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_TS_HYPERTABLE_NOT_EXIST),\n\t\t\t\t errmsg(\"missing columnstore-enabled hypertable\")));\n\n\tif (!ts_chunk_is_compressed(uncompressed_chunk))\n\t{\n\t\tereport((if_compressed ? NOTICE : ERROR),\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"chunk \\\"%s\\\" is not converted to columnstore\",\n\t\t\t\t\t\tget_rel_name(uncompressed_chunk_id))));\n\n\t\tPG_RETURN_NULL();\n\t}\n\telse\n\t\tdecompress_chunk_impl(uncompressed_chunk, if_compressed);\n\n\t/*\n\t * Post decompression regular DML can happen into this chunk. So, we update\n\t * chunk_column_stats entries for this chunk to min/max entries now.\n\t */\n\tts_chunk_column_stats_reset_by_chunk_id(chunk_id);\n\n\tPG_RETURN_OID(uncompressed_chunk_id);\n}\n\nstatic bool\ncan_use_in_memory_rebuild(Chunk *chunk)\n{\n\tCompressionSettings *chunk_settings = ts_compression_settings_get(chunk->table_id);\n\n\t/* check if we can allow in-memory recompression to rebuild columnstore */\n\tif (!chunk_settings || !chunk_settings->fd.orderby)\n\t{\n\t\telog(DEBUG1, \"in-memory rebuild columnstore is disabled due to no order by\");\n\t\treturn false;\n\t}\n\tif (!get_compressed_chunk_index_for_recompression(chunk))\n\t{\n\t\telog(DEBUG1, \"in-memory rebuild columnstore is disabled due to no compressed chunk index.\");\n\t\treturn false;\n\t}\n\tif (!ts_guc_enable_in_memory_recompression)\n\t{\n\t\telog(DEBUG1, \"timescaledb.enable_in_memory_recompression is disabled\");\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nDatum\ntsl_rebuild_columnstore(PG_FUNCTION_ARGS)\n{\n\tOid chunk_relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\n\tts_feature_flag_check(FEATURE_HYPERTABLE_COMPRESSION);\n\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\n\tif (!OidIsValid(chunk_relid))\n\t\tereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"invalid chunk OID\")));\n\n\tChunk *chunk = ts_chunk_get_by_relid(chunk_relid, true);\n\n\tif (!ts_chunk_is_compressed(chunk) || ts_chunk_is_frozen(chunk))\n\t{\n\t\tereport(NOTICE,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"chunk \\\"%s.%s\\\" is uncompressed or frozen, skipping\",\n\t\t\t\t\t\tNameStr(chunk->fd.schema_name),\n\t\t\t\t\t\tNameStr(chunk->fd.table_name))));\n\t\tPG_RETURN_VOID();\n\t}\n\n\t/* Try rebuild with in-memory recompression, fall back to decompress/compress if needed */\n\tif (!can_use_in_memory_rebuild(chunk) || !recompress_chunk_in_memory_impl(chunk))\n\t{\n\t\telog(DEBUG1,\n\t\t\t \"falling back to decompress/compress, performing full \"\n\t\t\t \"rebuild on chunk \\\"%s.%s\\\"\",\n\t\t\t NameStr(chunk->fd.schema_name),\n\t\t\t NameStr(chunk->fd.table_name));\n\t\tdecompress_chunk_impl(chunk, false);\n\t\tcompress_chunk_impl(chunk->hypertable_relid, chunk->table_id);\n\t}\n\n\tPG_RETURN_VOID();\n}\n\n/*\n * This is hacky but it doesn't matter. We just want to check for the existence of such an index\n * on the compressed chunk.\n */\nextern Datum\ntsl_get_compressed_chunk_index_for_recompression(PG_FUNCTION_ARGS)\n{\n\tts_feature_flag_check(FEATURE_HYPERTABLE_COMPRESSION);\n\tOid uncompressed_chunk_id = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\n\tChunk *uncompressed_chunk = ts_chunk_get_by_relid(uncompressed_chunk_id, true);\n\n\tOid index_oid = get_compressed_chunk_index_for_recompression(uncompressed_chunk);\n\n\tif (OidIsValid(index_oid))\n\t{\n\t\tPG_RETURN_OID(index_oid);\n\t}\n\telse\n\t\tPG_RETURN_NULL();\n}\n\nstatic Oid\nget_compressed_chunk_index_for_recompression(Chunk *uncompressed_chunk)\n{\n\tChunk *compressed_chunk = ts_chunk_get_by_id(uncompressed_chunk->fd.compressed_chunk_id, true);\n\tRelation uncompressed_chunk_rel = table_open(uncompressed_chunk->table_id, AccessShareLock);\n\tRelation compressed_chunk_rel = table_open(compressed_chunk->table_id, AccessShareLock);\n\n\tCompressionSettings *settings = ts_compression_settings_get(uncompressed_chunk->table_id);\n\n\tCatalogIndexState indstate = CatalogOpenIndexes(compressed_chunk_rel);\n\tOid index_oid = get_compressed_chunk_index(indstate, settings);\n\tCatalogCloseIndexes(indstate);\n\ttable_close(compressed_chunk_rel, NoLock);\n\ttable_close(uncompressed_chunk_rel, NoLock);\n\n\treturn index_oid;\n}\n\nChunk *\ntsl_compression_chunk_create(Hypertable *compressed_ht, Chunk *src_chunk)\n{\n\t/* Create a new compressed chunk */\n\treturn create_compress_chunk(compressed_ht, src_chunk, InvalidOid);\n}\n\nDatum\ntsl_estimate_compressed_batch_size(PG_FUNCTION_ARGS)\n{\n\tOid relid = PG_GETARG_OID(0);\n\n\tts_feature_flag_check(FEATURE_HYPERTABLE_COMPRESSION);\n\n\tfloat8 approx_batch_size = (float8) ts_columnar_estimate_compressed_batch_size(relid);\n\n\tPG_RETURN_FLOAT8(approx_batch_size);\n}\n"
  },
  {
    "path": "tsl/src/compression/api.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <fmgr.h>\n#include <utils.h>\n\n#include \"chunk.h\"\n\nextern Datum tsl_create_compressed_chunk(PG_FUNCTION_ARGS);\nextern Datum tsl_compress_chunk(PG_FUNCTION_ARGS);\nextern Datum tsl_decompress_chunk(PG_FUNCTION_ARGS);\nextern Datum tsl_rebuild_columnstore(PG_FUNCTION_ARGS);\nextern Oid tsl_compress_chunk_wrapper(Chunk *chunk, bool if_not_compressed, bool recompress);\nextern Chunk *tsl_compression_chunk_create(Hypertable *compressed_ht, Chunk *src_chunk);\n\nextern Datum tsl_get_compressed_chunk_index_for_recompression(\n\tPG_FUNCTION_ARGS); // arg is oid of uncompressed chunk\n\nextern void compression_chunk_size_catalog_insert(int32 src_chunk_id, const RelationSize *src_size,\n\t\t\t\t\t\t\t\t\t\t\t\t  int32 compress_chunk_id,\n\t\t\t\t\t\t\t\t\t\t\t\t  const RelationSize *compress_size,\n\t\t\t\t\t\t\t\t\t\t\t\t  int64 rowcnt_pre_compression,\n\t\t\t\t\t\t\t\t\t\t\t\t  int64 rowcnt_post_compression,\n\t\t\t\t\t\t\t\t\t\t\t\t  int64 rowcnt_frozen);\nextern Datum tsl_estimate_compressed_batch_size(PG_FUNCTION_ARGS);\n"
  },
  {
    "path": "tsl/src/compression/arrow_c_data_interface.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n/*\n * This header describes the Arrow C data interface which is a well-known\n * standard for in-memory interchange of columnar data.\n *\n * https://arrow.apache.org/docs/format/CDataInterface.html\n *\n * Citing from the above link:\n *\n * Arrow C data interface defines a very small, stable set of C definitions that\n * can be easily copied in any project’s source code and used for columnar data\n * interchange in the Arrow format. For non-C/C++ languages and runtimes, it\n * should be almost as easy to translate the C definitions into the\n * corresponding C FFI declarations.\n *\n * Applications and libraries can therefore work with Arrow memory without\n * necessarily using Arrow libraries or reinventing the wheel.\n */\n\n#ifndef ARROW_C_DATA_INTERFACE\n#define ARROW_C_DATA_INTERFACE\n\n#define ARROW_FLAG_DICTIONARY_ORDERED 1\n#define ARROW_FLAG_NULLABLE 2\n#define ARROW_FLAG_MAP_KEYS_SORTED 4\n\ntypedef struct ArrowArray\n{\n\t/*\n\t * Mandatory. The logical length of the array (i.e. its number of items).\n\t */\n\tint64 length;\n\n\t/*\n\t * Mandatory. The number of null items in the array. MAY be -1 if not yet\n\t * computed.\n\t */\n\tint64 null_count;\n\n\t/*\n\t * Mandatory. The logical offset inside the array (i.e. the number of\n\t * items from the physical start of the buffers). MUST be 0 or positive.\n\t *\n\t * Producers MAY specify that they will only produce 0-offset arrays to\n\t * ease implementation of consumer code. Consumers MAY decide not to\n\t * support non-0-offset arrays, but they should document this limitation.\n\t */\n\tint64 offset;\n\n\t/*\n\t * Mandatory. The number of physical buffers backing this array. The\n\t * number of buffers is a function of the data type, as described in the\n\t * Columnar format specification.\n\t *\n\t * Buffers of children arrays are not included.\n\t */\n\tint64 n_buffers;\n\n\t/*\n\t * Mandatory. The number of children this type has.\n\t */\n\tint64 n_children;\n\n\t/*\n\t * Mandatory. A C array of pointers to the start of each physical buffer\n\t * backing this array. Each void* pointer is the physical start of a\n\t * contiguous buffer. There must be ArrowArray.n_buffers pointers.\n\t *\n\t * The producer MUST ensure that each contiguous buffer is large enough to\n\t * represent length + offset values encoded according to the Columnar\n\t * format specification.\n\t *\n\t * It is recommended, but not required, that the memory addresses of the\n\t * buffers be aligned at least according to the type of primitive data\n\t * that they contain. Consumers MAY decide not to support unaligned\n\t * memory.\n\t *\n\t * The buffer pointers MAY be null only in two situations:\n\t *\n\t * - for the null bitmap buffer, if ArrowArray.null_count is 0;\n\t *\n\t * - for any buffer, if the size in bytes of the corresponding buffer would\n\t * be 0.\n\t *\n\t * Buffers of children arrays are not included.\n\t */\n\tconst void **buffers;\n\n\tstruct ArrowArray **children;\n\tstruct ArrowArray *dictionary;\n\n\t/*\n\t * Mandatory. A pointer to a producer-provided release callback.\n\t *\n\t * See below for memory management and release callback semantics.\n\t */\n\tvoid (*release)(struct ArrowArray *);\n\n\t/* Opaque producer-specific data */\n\tvoid *private_data;\n} ArrowArray;\n\n/*\n * We don't use the schema but have to define it for completeness because we're\n * defining the ARROW_C_DATA_INTERFACE macro.\n */\nstruct ArrowSchema\n{\n\tconst char *format;\n\tconst char *name;\n\tconst char *metadata;\n\tint64 flags;\n\tint64 n_children;\n\tstruct ArrowSchema **children;\n\tstruct ArrowSchema *dictionary;\n\n\tvoid (*release)(struct ArrowSchema *);\n\tvoid *private_data;\n};\n\n/*\n * The include guard ARROW_C_DATA_INTERFACE is required by the Arrow docs to\n * avoid redefinition of the Arrow structs in the third-party headers, but the\n * following functions are not part of Arrow C Data Interface, so they are not\n * under the guard. We still need some kind of guard for them, so we also have\n * pragma once above.\n */\n#endif\n\nstatic pg_attribute_always_inline bool\narrow_row_is_valid(const uint64 *bitmap, size_t row_number)\n{\n\tif (likely(bitmap == NULL))\n\t{\n\t\treturn true;\n\t}\n\n\tconst size_t qword_index = row_number / 64;\n\tconst size_t bit_index = row_number % 64;\n\tconst uint64 mask = 1ull << bit_index;\n\treturn bitmap[qword_index] & mask;\n}\n\n/*\n * Same as above but for two bitmaps, this is a typical situation when we have\n * validity bitmap + filter result.\n */\nstatic pg_attribute_always_inline bool\narrow_row_both_valid(const uint64 *bitmap1, const uint64 *bitmap2, size_t row_number)\n{\n\tif (likely(bitmap1 == NULL))\n\t{\n\t\treturn arrow_row_is_valid(bitmap2, row_number);\n\t}\n\n\tif (likely(bitmap2 == NULL))\n\t{\n\t\treturn arrow_row_is_valid(bitmap1, row_number);\n\t}\n\n\tconst size_t qword_index = row_number / 64;\n\tconst size_t bit_index = row_number % 64;\n\tconst uint64 mask = 1ull << bit_index;\n\treturn (bitmap1[qword_index] & bitmap2[qword_index]) & mask;\n}\n\nstatic pg_attribute_always_inline void\narrow_set_row_validity(uint64 *bitmap, size_t row_number, bool value)\n{\n\tconst size_t qword_index = row_number / 64;\n\tconst size_t bit_index = row_number % 64;\n\tconst uint64 mask = 1ull << bit_index;\n\tconst uint64 new_bit = (value ? 1ull : 0ull) << bit_index;\n\n\tbitmap[qword_index] = (bitmap[qword_index] & ~mask) | new_bit;\n\n\tAssert(arrow_row_is_valid(bitmap, row_number) == value);\n}\n\n/*\n * Combine the validity bitmaps into the given storage. Can return one of the\n * input filters if the others are NULL.\n */\nstatic inline pg_nodiscard const uint64 *\narrow_combine_validity(size_t num_words, uint64 *restrict storage, const uint64 *filter1,\n\t\t\t\t\t   const uint64 *filter2, const uint64 *filter3)\n{\n\tAssert(num_words != 0);\n\tAssert(storage != filter1);\n\tAssert(storage != filter2);\n\tAssert(storage != filter3);\n\n\t/*\n\t * Any and all of the filters can be null. For simplicity, move the non-null\n\t * filters to the leading positions.\n\t */\n\tconst uint64 *tmp;\n#define SWAP(X, Y)                                                                                 \\\n\ttmp = (X);                                                                                     \\\n\t(X) = (Y);                                                                                     \\\n\t(Y) = tmp;\n\n\tif (filter1 == NULL)\n\t{\n\t\t/*\n\t\t * We have at least one NULL that goes to the last position.\n\t\t */\n\t\tSWAP(filter1, filter3);\n\n\t\tif (filter1 == NULL)\n\t\t{\n\t\t\t/*\n\t\t\t * We have another NULL that goes to the second position.\n\t\t\t */\n\t\t\tSWAP(filter1, filter2);\n\t\t}\n\t}\n\telse\n\t{\n\t\tif (filter2 == NULL)\n\t\t{\n\t\t\t/*\n\t\t\t * We have at least one NULL that goes to the last position.\n\t\t\t */\n\t\t\tSWAP(filter2, filter3);\n\t\t}\n\t}\n#undef SWAP\n\n\tAssert(filter2 == NULL || filter1 != NULL);\n\tAssert(filter3 == NULL || filter2 != NULL);\n\n\tif (filter2 == NULL)\n\t{\n\t\t/* Either have one non-null filter, or all of them are null. */\n\t\treturn filter1;\n\t}\n\n\tif (filter3 == NULL)\n\t{\n\t\t/* Have two non-null filters. */\n\t\tfor (size_t i = 0; i < num_words; i++)\n\t\t{\n\t\t\tstorage[i] = filter1[i] & filter2[i];\n\t\t}\n\t}\n\telse\n\t{\n\t\t/* Have three non-null filters. */\n\t\tfor (size_t i = 0; i < num_words; i++)\n\t\t{\n\t\t\tstorage[i] = filter1[i] & filter2[i] & filter3[i];\n\t\t}\n\t}\n\n\treturn storage;\n}\n\n/*\n * Do the &= operation on bitmaps. The right argument can be NULL.\n */\nstatic inline void\narrow_validity_and(int num_words, uint64 *restrict left, const uint64 *right)\n{\n\tif (right == NULL)\n\t{\n\t\treturn;\n\t}\n\n\tfor (int i = 0; i < num_words; i++)\n\t{\n\t\tleft[i] &= right[i];\n\t}\n}\n\n/*\n * Increase the `source_value` to be an even multiple of `pad_to`.\n */\nstatic inline uint64\npad_to_multiple(uint64 pad_to, uint64 source_value)\n{\n\treturn ((source_value + pad_to - 1) / pad_to) * pad_to;\n}\n\nstatic inline int\narrow_num_valid(const uint64 *bitmap, size_t total_rows)\n{\n\tAssert(total_rows != 0);\n\n\tif (bitmap == NULL)\n\t{\n\t\treturn total_rows;\n\t}\n\n\tuint64 num_valid = 0;\n#ifdef HAVE__BUILTIN_POPCOUNT\n\tconst uint64 words = pad_to_multiple(64, total_rows) / 64;\n\tfor (uint64 i = 0; i < words; i++)\n\t{\n\t\tnum_valid += __builtin_popcountll(bitmap[i]);\n\t}\n#else\n\tfor (size_t i = 0; i < total_rows; i++)\n\t{\n\t\tnum_valid += arrow_row_is_valid(bitmap, i);\n\t}\n#endif\n\treturn num_valid;\n}\n"
  },
  {
    "path": "tsl/src/compression/batch_metadata_builder.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include \"funcapi.h\" /* for PGFunction, FmgrInfo */\n\ntypedef struct RowCompressor RowCompressor;\n\nenum BatchMetadataBuilderType\n{\n\tMETADATA_BUILDER_MINMAX,\n\tMETADATA_BUILDER_BLOOM1,\n};\n\ntypedef struct BatchMetadataBuilder\n{\n\tvoid (*update_row)(void *builder, TupleTableSlot *slot);\n\tvoid (*insert_to_compressed_row)(void *builder, RowCompressor *compressor);\n\tvoid (*reset)(void *builder, RowCompressor *compressor);\n\tenum BatchMetadataBuilderType builder_type;\n} BatchMetadataBuilder;\n\nBatchMetadataBuilder *batch_metadata_builder_minmax_create(Oid type, Oid collation,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   AttrNumber attnum, int min_attr_offset,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   int max_attr_offset);\n\nBatchMetadataBuilder *batch_metadata_builder_bloom1_create(int num_columns, const Oid *type_oids,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   const AttrNumber *attnums,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   int bloom_attr_offset);\n\n/* Hasher interface common to bloom filters, used to compute the hash without updating the bloom\n * filter */\ntypedef struct Bloom1Hasher\n{\n\tuint64 (*hash_values)(void *hasher, const NullableDatum *values);\n\tint num_columns;\n} Bloom1Hasher;\n\nBloom1Hasher *bloom1_hasher_create(const Oid *type_oids, int num_columns);\n\n/* Shared utilities between metadata builders */\nint batch_metadata_builder_bloom1_varlena_size(void);\nuint64 batch_metadata_builder_bloom1_calculate_hash(PGFunction hash_function, FmgrInfo *finfo,\n\t\t\t\t\t\t\t\t\t\t\t\t\tDatum needle);\nvoid batch_metadata_builder_bloom1_update_bloom_filter_with_hash(void *varlena_ptr, uint64 hash);\nvoid batch_metadata_builder_bloom1_insert_bloom_filter_to_compressed_row(void *bloom_varlena,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t int16 bloom_attr_offset,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t RowCompressor *compressor);\n\n/* Returns true if the hash is maybe present in a bloom filter, if the bloom filter data is\n * NULL, it returns true, because we cannot be sure if the hash is present or not. */\nextern bool bloom1_contains_hash(Datum bloom_datum, uint64 hash);\n"
  },
  {
    "path": "tsl/src/compression/batch_metadata_builder_bloom1.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include \"postgres.h\"\n\n#include <access/detoast.h>\n#include <catalog/pg_collation_d.h>\n#include <common/hashfn.h>\n#include <funcapi.h>\n#include <utils/builtins.h>\n#include <utils/typcache.h>\n\n#include <math.h>\n\n#include \"sparse_index_bloom1.h\"\n\n#include \"arrow_c_data_interface.h\"\n#include \"batch_metadata_builder.h\"\n#include \"city_combine.h\"\n#include \"compression.h\"\n#include \"guc.h\"\n\n#ifdef TS_USE_UMASH\n#include \"import/umash.h\"\n#endif\n\n/*\n * There is a tradeoff between making the bloom filters selective enough but\n * not too big. Testing on some real words datasets, the optimal value seems to\n * be about 2% false positives. We also want to be able to reduce the filter to\n * up to 64 bits, so that it fits in the main table. Hence the optimal number of\n * hashes. This number actually gives a slightly different false positive rate,\n * so this is what we ultimately use.\n * Calculator: https://hur.st/bloomfilter/?p=0.02&m=64\n */\n\n#define BLOOM1_FALSE_POSITIVES 0.022\n#define BLOOM1_HASHES 6\n\n/*\n * Limit the bits belonging to the particular elements to a small contiguous\n * region. This improves memory locality when building the bloom filter.\n */\n#define BLOOM1_BLOCK_BITS 256\n\n/* The NULL marker is used to preserve NULLs in the composite hash. The value is coming from Golden\n * ratio constant so it is unlikely to degrade the collision resistance of the bloom filter.\n * The purpose of the NULL marker is two fold: 1) to distinguish NULLs from actual values so\n * we don't get the same hash for ('foo',NULL) and (NULL, 'foo') and 2) with having a specific\n * marker, we can support IS NULL predicates on columns in addition to equality.\n * */\n#define NULL_MARKER 0x9E3779B97F4A7C15ULL\ntypedef struct Bloom1HasherInternal\n{\n\tBloom1Hasher functions;\n\tPGFunction hash_functions[MAX_BLOOM_FILTER_COLUMNS];\n\tFmgrInfo *hash_function_finfos[MAX_BLOOM_FILTER_COLUMNS];\n} Bloom1HasherInternal;\n\ntypedef struct Bloom1MetadataBuilder\n{\n\tBatchMetadataBuilder functions;\n\tint16 bloom_attr_offset;\n\tint allocated_varlena_bytes;\n\tstruct varlena *bloom_varlena;\n\tAttrNumber input_columns[MAX_BLOOM_FILTER_COLUMNS];\n\tBloom1HasherInternal hasher;\n} Bloom1MetadataBuilder;\n\nstatic void bloom1_hasher_init(Bloom1HasherInternal *hasher, const Oid *type_oids, int num_columns);\n\n/*\n * Low-bias invertible hash function from this article:\n * http://web.archive.org/web/20250406022607/https://nullprogram.com/blog/2018/07/31/\n */\nstatic inline uint64\nbloom1_hash64(uint64 x)\n{\n\tx ^= x >> 32;\n\tx *= 0xd6e8feb86659fd93U;\n\tx ^= x >> 32;\n\tx *= 0xd6e8feb86659fd93U;\n\tx ^= x >> 32;\n\treturn x;\n}\n\nstatic Datum\nbloom1_hash_8(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_UINT64(bloom1_hash64(PG_GETARG_INT64(0)));\n}\n\nstatic Datum\nbloom1_hash_4(PG_FUNCTION_ARGS)\n{\n\tPG_RETURN_UINT64(bloom1_hash64(PG_GETARG_INT32(0)));\n}\n\n#ifdef TS_USE_UMASH\nstatic struct umash_params *\nhashing_params()\n{\n\tstatic struct umash_params params = { 0 };\n\tif (params.poly[0][0] == 0)\n\t{\n\t\tumash_params_derive(&params, 0x12345abcdef67890ULL, NULL);\n\t\tAssert(params.poly[0][0] != 0);\n\t}\n\n\treturn &params;\n}\n\nstatic Datum\nbloom1_hash_varlena(PG_FUNCTION_ARGS)\n{\n\tstruct varlena *needle = PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));\n\tconst int length = VARSIZE_ANY_EXHDR(needle);\n\tconst char *data = VARDATA_ANY(needle);\n\tPG_RETURN_UINT64(umash_full(hashing_params(),\n\t\t\t\t\t\t\t\t/* seed = */ ~0ULL,\n\t\t\t\t\t\t\t\t/* which = */ 0,\n\t\t\t\t\t\t\t\tdata,\n\t\t\t\t\t\t\t\tlength));\n}\n\nstatic Datum\nbloom1_hash_16(PG_FUNCTION_ARGS)\n{\n\tDatum datum = PG_GETARG_DATUM(0);\n\tPG_RETURN_UINT64(umash_full(hashing_params(),\n\t\t\t\t\t\t\t\t/* seed = */ ~0ULL,\n\t\t\t\t\t\t\t\t/* which = */ 0,\n\t\t\t\t\t\t\t\tDatumGetPointer(datum),\n\t\t\t\t\t\t\t\t16));\n}\n#endif\n\n/*\n * Get the hash function we use for building a bloom filter for a particular\n * type. Returns NULL if not supported.\n * The signature of the returned function matches the Postgres extended hashing\n * functions like hashtextextended().\n * It's possible, though impractical, for a hash function to be implemented in a\n * procedural language, not in C. In this case, we need the proper FmgrInfo to\n * call it. We fetch it from the type cache. For our custom functions, it is NULL.\n */\nPGFunction\nbloom1_get_hash_function(Oid type, FmgrInfo **finfo)\n{\n\t*finfo = NULL;\n\n\t/*\n\t * By default, we use the Postgres extended hashing functions, so that we\n\t * can use bloom filters for any types.\n\t * We request also the opfamily info and the equality operator, because\n\t * otherwise the Postgres type cache code fails obtusely on types with\n\t * improper opclasses. It picks up the btree opclass from a binary compatible\n\t * type (see GetDefaultOpClass), then an equality operator from this opclass,\n\t * and then refuses to return the hash functions because the hash opclass has\n\t * a different equality operator. The problem is that this happens over two\n\t * consecutive calls to lookup_type_cache(), so the first invocation of our\n\t * function says that we have a hash, and the second says that we don't.\n\t */\n\tTypeCacheEntry *entry = lookup_type_cache(type,\n\t\t\t\t\t\t\t\t\t\t\t  TYPECACHE_EQ_OPR | TYPECACHE_BTREE_OPFAMILY |\n\t\t\t\t\t\t\t\t\t\t\t\t  TYPECACHE_HASH_EXTENDED_PROC_FINFO);\n\t/*\n\t * For some types we use our custom hash functions. We only do it for the\n\t * builtin Postgres types to be on the safe side, and also simplify the\n\t * testing by creating bad hash functions from SQL tests. If you change this,\n\t * you might have to change the bad hash testing in compress_bloom_sparse.sql.\n\t */\n\tswitch (entry->hash_extended_proc)\n\t{\n#ifdef TS_USE_UMASH\n\t\tcase F_HASHTEXTEXTENDED:\n\t\t\treturn bloom1_hash_varlena;\n\n\t\tcase F_UUID_HASH_EXTENDED:\n\t\t\treturn bloom1_hash_16;\n#endif\n\t\tcase F_HASHINT8EXTENDED:\n\t\t\treturn bloom1_hash_8;\n\n\t\tcase F_HASHINT4EXTENDED:\n#if PG18_GE\n\t\t/*\n\t\t * PG18 added a custom hashing function for date type.\n\t\t * For backwards compatibility, we need to continue using\n\t\t * our own custom function which was used for < PG18.\n\t\t *\n\t\t * https://github.com/postgres/postgres/commit/23d0b484\n\t\t */\n\t\tcase F_HASHDATEEXTENDED:\n#endif\n\t\t\treturn bloom1_hash_4;\n\t\tdefault:\n\t\t\t/*\n\t\t\t * Use the Postgres hash function. We might require the finfo, for\n\t\t\t * example for functions defined in procedural languages.\n\t\t\t */\n\t\t\t*finfo = &entry->hash_extended_proc_finfo;\n\t\t\treturn entry->hash_extended_proc_finfo.fn_addr;\n\t}\n}\n\nstatic void\nbloom1_reset(void *builder_, RowCompressor *compressor)\n{\n\tBloom1MetadataBuilder *builder = (Bloom1MetadataBuilder *) builder_;\n\tAssert(builder->functions.builder_type == METADATA_BUILDER_BLOOM1);\n\n\tstruct varlena *bloom = builder->bloom_varlena;\n\tmemset(bloom, 0, builder->allocated_varlena_bytes);\n\tSET_VARSIZE(bloom, builder->allocated_varlena_bytes);\n\n\tcompressor->compressed_is_null[builder->bloom_attr_offset] = true;\n\tcompressor->compressed_values[builder->bloom_attr_offset] = 0;\n}\n\nstatic char *\nbloom1_words_buf(struct varlena *bloom)\n{\n\treturn VARDATA_ANY(bloom);\n}\n\nstatic int\nbloom1_num_bits(const struct varlena *bloom)\n{\n\treturn 8 * VARSIZE_ANY_EXHDR(bloom);\n}\n\nvoid\nbatch_metadata_builder_bloom1_insert_bloom_filter_to_compressed_row(void *bloom_varlena,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tint16 bloom_attr_offset,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tRowCompressor *compressor)\n{\n\tstruct varlena *bloom = (struct varlena *) bloom_varlena;\n\tchar *restrict words_buf = bloom1_words_buf(bloom);\n\n\tconst int orig_num_bits = bloom1_num_bits(bloom);\n\tAssert(orig_num_bits % 8 == 0);\n\tAssert(orig_num_bits % 64 == 0);\n\tconst int orig_bits_set = pg_popcount(words_buf, orig_num_bits / 8);\n\n\tif (unlikely(orig_bits_set == 0 || orig_bits_set == orig_num_bits))\n\t{\n\t\t/*\n\t\t * 1) All elements turned out to be null, don't save the empty filter in\n\t\t * that case. The compressed batch will be compressed using the NULL\n\t\t * compression algorithm, so actually checking the rows will be efficient\n\t\t * enough.\n\t\t *\n\t\t * 2) All bits are set, this filter is useless. Shouldn't really happen,\n\t\t * but technically possible, and the following calculations will\n\t\t * segfault in this case.\n\t\t */\n\t\tcompressor->compressed_is_null[bloom_attr_offset] = true;\n\t\tcompressor->compressed_values[bloom_attr_offset] = PointerGetDatum(NULL);\n\t\treturn;\n\t}\n\n\t/*\n\t * Our filters are sized for the maximum expected number of the unique\n\t * elements, so in practice they can be very sparse if the actual number of\n\t * the unique elements is less. The TOAST compression doesn't handle even\n\t * the sparse filters very well. Apply a simple compression technique: split\n\t * the filter in half and bitwise OR the halves. Repeat this until we reach\n\t * the filter bit length that gives the desired false positive ratio.\n\t * The desired filter bit length is given by m1, we will now estimate it\n\t * based on the estimated current number of elements in the bloom filter (1)\n\t * and the ideal number of elements for a bloom filter of given size (2).\n\t * (1) n = log(1 - t/m0) / (k * log(1 - 1/m0)),\n\t * (2) n = -m1 * log(1 - p ^ (1/k)) / k.\n\t */\n\tconst double m0 = orig_num_bits;\n\tconst double k = BLOOM1_HASHES;\n\tconst double p = BLOOM1_FALSE_POSITIVES;\n\tconst double t = orig_bits_set;\n\tconst double m1 = -log(1 - t / m0) / (log(1 - 1 / m0) * log(1 - pow(p, 1 / k)));\n\n\t/*\n\t * Compute powers of two corresponding to the current and desired filter\n\t * bit length.\n\t */\n\tconst int starting_pow2 = ceil(log2(m0));\n\tAssert(pow(2, starting_pow2) == m0);\n\t/* We don't want to go under 64 bytes. */\n\tconst int final_pow2 = MAX(6, ceil(log2(m1)));\n\tAssert(final_pow2 >= 6);\n\n\t/*\n\t * Fold filter in half, applying bitwise OR, until we reach the desired\n\t * filter bit length.\n\t */\n\tfor (int current_pow2 = starting_pow2; current_pow2 > final_pow2; current_pow2--)\n\t{\n\t\tconst int half_words = 1 << (current_pow2 - 3 /* 8-bit byte */ - 1 /* half */);\n\t\tAssert(half_words > 0);\n\t\tconst char *words_tail = &words_buf[half_words];\n\t\tfor (int i = 0; i < half_words; i++)\n\t\t{\n\t\t\twords_buf[i] |= words_tail[i];\n\t\t}\n\t}\n\n\t/*\n\t * If we have resized the filter, update the nominal size of the varlena\n\t * object.\n\t */\n\tif (final_pow2 < starting_pow2)\n\t{\n\t\tSET_VARSIZE(bloom,\n\t\t\t\t\t(char *) bloom1_words_buf(bloom) + (1 << (final_pow2 - 3 /* 8-bit byte */)) -\n\t\t\t\t\t\t(char *) bloom);\n\t}\n\n\tAssert(bloom1_num_bits(bloom) % (sizeof(*words_buf) * 8) == 0);\n\tAssert(bloom1_num_bits(bloom) % 64 == 0);\n\n\tcompressor->compressed_is_null[bloom_attr_offset] = false;\n\tcompressor->compressed_values[bloom_attr_offset] = PointerGetDatum(bloom);\n}\n\nstatic void\nbloom1_insert_to_compressed_row(void *builder_, RowCompressor *compressor)\n{\n\tBloom1MetadataBuilder *builder = (Bloom1MetadataBuilder *) builder_;\n\tbatch_metadata_builder_bloom1_insert_bloom_filter_to_compressed_row(builder->bloom_varlena,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbuilder->bloom_attr_offset,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcompressor);\n}\n\n/*\n * Call a hash function that uses a postgres \"extended hash\" signature.\n */\nuint64\nbatch_metadata_builder_bloom1_calculate_hash(PGFunction hash_function, FmgrInfo *finfo,\n\t\t\t\t\t\t\t\t\t\t\t Datum needle)\n{\n\tLOCAL_FCINFO(hashfcinfo, 2);\n\t*hashfcinfo = (FunctionCallInfoBaseData){ 0 };\n\n\t/*\n\t * Our hashing is not collation-sensitive, but the Postgres hashing functions\n\t * might refuse to work if the collation is not deterministic, so make them\n\t * happy.\n\t */\n\thashfcinfo->fncollation = C_COLLATION_OID;\n\n\thashfcinfo->nargs = 2;\n\thashfcinfo->args[0].value = needle;\n\thashfcinfo->args[0].isnull = false;\n\n\t/*\n\t * Seed. Note that on 32-bit systems it is by-reference.\n\t */\n\tconst int64 seed = 0;\n\thashfcinfo->args[1].value = Int64GetDatumFast(seed);\n\thashfcinfo->args[1].isnull = false;\n\n\t/*\n\t * Needed for hash functions defined in procedural languages, not C. While\n\t * unlikely, we shouldn't segfault. The finfo is cached in the type cache.\n\t */\n\thashfcinfo->flinfo = finfo;\n\n\treturn DatumGetUInt64(hash_function(hashfcinfo));\n}\n\n/*\n * The offset of nth bit we're going to set.\n */\nstatic inline uint32\nbloom1_get_one_offset(uint64 value_hash, uint32 index)\n{\n\tconst uint32 low = value_hash & ~(uint32) 0;\n\tconst uint32 high = (value_hash >> 32) & ~(uint32) 0;\n\n\t/*\n\t * Add a quadratic component to lessen degradation in the unlikely case when\n\t * 'high' is a multiple of block bits.\n\t */\n\treturn low + (index * high + index * index) % BLOOM1_BLOCK_BITS;\n}\n\nvoid\nbatch_metadata_builder_bloom1_update_bloom_filter_with_hash(void *varlena_ptr, uint64 hash)\n{\n\tAssert(varlena_ptr != NULL);\n\tstruct varlena *bloom_varlena = (struct varlena *) varlena_ptr;\n\n\tchar *restrict words_buf = bloom1_words_buf(bloom_varlena);\n\tconst uint32 num_bits = bloom1_num_bits(bloom_varlena);\n\n\t/*\n\t * These calculations are a little inconvenient, but I had to switch to\n\t * another buffer word size already, so for now I'm keeping the code generic\n\t * relative to this size.\n\t */\n\tconst uint32 num_word_bits = sizeof(*words_buf) * 8;\n\tAssert(num_bits % num_word_bits == 0);\n\tconst uint32 log2_word_bits = pg_leftmost_one_pos32(num_word_bits);\n\tAssert(num_word_bits == (1ULL << log2_word_bits));\n\n\tconst uint32 word_mask = num_word_bits - 1;\n\tAssert((word_mask >> num_word_bits) == 0);\n\n\tconst uint32 absolute_mask = num_bits - 1;\n\tfor (int i = 0; i < BLOOM1_HASHES; i++)\n\t{\n\t\tconst uint32 absolute_bit_index = bloom1_get_one_offset(hash, i) & absolute_mask;\n\t\tconst uint32 word_index = absolute_bit_index >> log2_word_bits;\n\t\tconst uint32 word_bit_index = absolute_bit_index & word_mask;\n\t\twords_buf[word_index] |= 1ULL << word_bit_index;\n\t}\n}\n\nstatic uint64\nbloom1_hash_values(void *hasher_, const NullableDatum *values)\n{\n\tBloom1HasherInternal *hasher = (Bloom1HasherInternal *) hasher_;\n\tint num_columns = hasher->functions.num_columns;\n\n\tuint64 accumulated = 0;\n\n\tif (values[0].isnull)\n\t\taccumulated = NULL_MARKER;\n\telse\n\t\taccumulated = batch_metadata_builder_bloom1_calculate_hash(hasher->hash_functions[0],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   hasher->hash_function_finfos[0],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   values[0].value);\n\n\tfor (int i = 1; i < num_columns; i++)\n\t{\n\t\tif (values[i].isnull)\n\t\t{\n\t\t\taccumulated = city_hash_combine(accumulated, NULL_MARKER);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tuint64 h = batch_metadata_builder_bloom1_calculate_hash(hasher->hash_functions[i],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\thasher->hash_function_finfos[i],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tvalues[i].value);\n\t\t\taccumulated = city_hash_combine(accumulated, h);\n\t\t}\n\t}\n\n\treturn accumulated;\n}\n\nstatic void\nbloom1_update_row(void *builder_, TupleTableSlot *slot)\n{\n\tBloom1MetadataBuilder *builder = (Bloom1MetadataBuilder *) builder_;\n\tBloom1Hasher *hasher = &builder->hasher.functions;\n\tint num_columns = hasher->num_columns;\n\tNullableDatum values[MAX_BLOOM_FILTER_COLUMNS];\n\n\tfor (int i = 0; i < num_columns; i++)\n\t{\n\t\tvalues[i].value = slot_getattr(slot, builder->input_columns[i], &values[i].isnull);\n\t}\n\n\t/* For single-column blooms, skip NULLs to match old bloom1_update_null (no-op) behavior. */\n\tif (num_columns == 1 && values[0].isnull)\n\t\treturn;\n\n\tuint64 hash = hasher->hash_values(hasher, values);\n\tbatch_metadata_builder_bloom1_update_bloom_filter_with_hash(builder->bloom_varlena, hash);\n}\n\n/*\n * We cache some information across function calls in this context.\n */\ntypedef struct Bloom1ContainsContext\n{\n\tOid element_type;\n\tint16 element_typlen;\n\tbool element_typbyval;\n\tchar element_typalign;\n\n\t/* This is per-row, here for convenience. */\n\tstruct varlena *current_row_bloom;\n\n\tBloom1HasherInternal bloom_hasher;\n} Bloom1ContainsContext;\n\nstatic Bloom1ContainsContext *\nbloom1_contains_context_prepare(FunctionCallInfo fcinfo, bool use_element_type)\n{\n\tBloom1ContainsContext *context = (Bloom1ContainsContext *) fcinfo->flinfo->fn_extra;\n\tif (context == NULL)\n\t{\n\t\tEnsure(PG_NARGS() == 2, \"bloom1_contains called with wrong number of arguments\");\n\n\t\tcontext = MemoryContextAllocZero(fcinfo->flinfo->fn_mcxt, sizeof(*context));\n\n\t\tcontext->element_type = get_fn_expr_argtype(fcinfo->flinfo, 1);\n\t\tif (use_element_type)\n\t\t{\n\t\t\tcontext->element_type = get_element_type(context->element_type);\n\t\t\tEnsure(OidIsValid(context->element_type),\n\t\t\t\t   \"cannot determine array element type for bloom1_contains_any\");\n\t\t}\n\n\t\tOid type_oids[MAX_BLOOM_FILTER_COLUMNS];\n\t\tint num_columns;\n\n\t\tif (context->element_type == RECORDOID)\n\t\t{\n\t\t\tHeapTupleHeader tuple = DatumGetHeapTupleHeader(PG_GETARG_DATUM(1));\n\t\t\tOid tupType = HeapTupleHeaderGetTypeId(tuple);\n\t\t\tint32 tupTypmod = HeapTupleHeaderGetTypMod(tuple);\n\t\t\tTupleDesc tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);\n\n\t\t\tnum_columns = tupdesc->natts;\n\t\t\tif (num_columns > MAX_BLOOM_FILTER_COLUMNS)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_DATA_EXCEPTION),\n\t\t\t\t\t\t errmsg(\"composite bloom filter supports at most %d columns, got %d\",\n\t\t\t\t\t\t\t\tMAX_BLOOM_FILTER_COLUMNS,\n\t\t\t\t\t\t\t\tnum_columns)));\n\n\t\t\tfor (int i = 0; i < num_columns; i++)\n\t\t\t\ttype_oids[i] = TupleDescAttr(tupdesc, i)->atttypid;\n\t\t\tReleaseTupleDesc(tupdesc);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ttype_oids[0] = context->element_type;\n\t\t\tnum_columns = 1;\n\t\t}\n\n\t\tbloom1_hasher_init(&context->bloom_hasher, type_oids, num_columns);\n\n\t\tget_typlenbyvalalign(context->element_type,\n\t\t\t\t\t\t\t &context->element_typlen,\n\t\t\t\t\t\t\t &context->element_typbyval,\n\t\t\t\t\t\t\t &context->element_typalign);\n\n\t\tfcinfo->flinfo->fn_extra = context;\n\t}\n\n\tif (PG_ARGISNULL(0))\n\t{\n\t\tcontext->current_row_bloom = NULL;\n\t}\n\telse\n\t{\n\t\tcontext->current_row_bloom = PG_GETARG_VARLENA_P(0);\n\t}\n\n\treturn context;\n}\n\nstatic inline bool\nbloom1_contains_hash_internal(const char *words_buf, uint32 num_bits, uint64 hash)\n{\n\tAssert(words_buf != NULL);\n\n\t/* Must be a power of two. */\n\tCheckCompressedData(num_bits == (1ULL << pg_leftmost_one_pos32(num_bits)));\n\n\t/* Must be >= 64 bits. */\n\tCheckCompressedData(num_bits >= 64);\n\n\tconst uint32 num_word_bits = sizeof(*words_buf) * 8;\n\tAssert(num_bits % num_word_bits == 0);\n\tconst uint32 log2_word_bits = pg_leftmost_one_pos32(num_word_bits);\n\tAssert(num_word_bits == (1ULL << log2_word_bits));\n\n\tconst uint32 word_mask = num_word_bits - 1;\n\tAssert((word_mask >> num_word_bits) == 0);\n\n\tconst uint32 absolute_mask = num_bits - 1;\n\tfor (int i = 0; i < BLOOM1_HASHES; i++)\n\t{\n\t\tconst uint32 absolute_bit_index = bloom1_get_one_offset(hash, i) & absolute_mask;\n\t\tconst uint32 word_index = absolute_bit_index >> log2_word_bits;\n\t\tconst uint32 word_bit_index = absolute_bit_index & word_mask;\n\t\tif ((words_buf[word_index] & (1ULL << word_bit_index)) == 0)\n\t\t{\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n\n/*\n * Checks whether the given element can be present in the given bloom filter.\n * This is what we use in predicate pushdown. The SQL signature is:\n * _timescaledb_functions.bloom1_contains(bloom1, anyelement)\n */\nDatum\nbloom1_contains(PG_FUNCTION_ARGS)\n{\n\t/*\n\t * A null value cannot match the equality condition, although this probably\n\t * should be optimized away by the planner.\n\t */\n\tif (PG_ARGISNULL(1))\n\t{\n\t\tPG_RETURN_BOOL(false);\n\t}\n\n\tBloom1ContainsContext *context =\n\t\tbloom1_contains_context_prepare(fcinfo, /* use_element_type = */ false);\n\n\t/*\n\t * This function is not strict, because if we don't have a bloom filter, this\n\t * means the condition can potentially be true.\n\t */\n\tstruct varlena *bloom = context->current_row_bloom;\n\tif (bloom == NULL)\n\t{\n\t\tPG_RETURN_BOOL(true);\n\t}\n\n\tuint64 hash = 0;\n\tNullableDatum values[MAX_BLOOM_FILTER_COLUMNS];\n\tmemset(values, 0, sizeof(values));\n\n\tif (context->bloom_hasher.functions.num_columns > 1)\n\t{\n\t\tHeapTupleHeader tuple = DatumGetHeapTupleHeader(PG_GETARG_DATUM(1));\n\t\tfor (int i = 0; i < context->bloom_hasher.functions.num_columns; i++)\n\t\t\tvalues[i].value = GetAttributeByNum(tuple, i + 1, &values[i].isnull);\n\t}\n\telse\n\t{\n\t\tvalues[0].value = PG_GETARG_DATUM(1);\n\t\tvalues[0].isnull = false;\n\t}\n\n\thash = bloom1_hash_values(&context->bloom_hasher, values);\n\tPG_RETURN_BOOL(bloom1_contains_hash(PointerGetDatum(bloom), hash));\n}\n\n#define ST_SORT sort_hashes\n#define ST_ELEMENT_TYPE uint64\n#define ST_COMPARE(a, b) ((*(a) > *(b)) - (*(a) < *(b)))\n#define ST_SCOPE static\n#define ST_DEFINE\n#include <lib/sort_template.h>\n\n/*\n * Checks whether any element of the given array can be present in the given\n * bloom filter. This is used for predicate pushdown for x = any(array[...]).\n * The SQL signature is:\n * _timescaledb_functions.bloom1_contains_any(bloom1, anyarray)\n */\nDatum\nbloom1_contains_any(PG_FUNCTION_ARGS)\n{\n\tBloom1ContainsContext *context =\n\t\tbloom1_contains_context_prepare(fcinfo, /* use_element_type = */ true);\n\n\t/*\n\t * This function is not strict, because if we don't have a bloom filter, this\n\t * means the condition can potentially be true.\n\t */\n\tstruct varlena *bloom = context->current_row_bloom;\n\tif (bloom == NULL)\n\t{\n\t\tPG_RETURN_BOOL(true);\n\t}\n\n\t/*\n\t * A null value cannot match the equality condition, although this probably\n\t * should be optimized away by the planner.\n\t */\n\tif (PG_ARGISNULL(1))\n\t{\n\t\tPG_RETURN_BOOL(false);\n\t}\n\n\tint num_items;\n\tDatum *items;\n\tbool *nulls;\n\tdeconstruct_array(PG_GETARG_ARRAYTYPE_P(1),\n\t\t\t\t\t  context->element_type,\n\t\t\t\t\t  context->element_typlen,\n\t\t\t\t\t  context->element_typbyval,\n\t\t\t\t\t  context->element_typalign,\n\t\t\t\t\t  &items,\n\t\t\t\t\t  &nulls,\n\t\t\t\t\t  &num_items);\n\n\tif (num_items == 0)\n\t{\n\t\tPG_RETURN_BOOL(false);\n\t}\n\n\t/*\n\t * Calculate the per-item base hashes that will be used for computing the\n\t * individual bloom filter bit offsets. We can reuse the \"items\" space to\n\t * avoid more allocations, but have to allocate as a fallback on 32-bit\n\t * systems.\n\t */\n#if FLOAT8PASSBYVAL\n\tuint64 *item_base_hashes = (uint64 *) items;\n#else\n\tuint64 *item_base_hashes = palloc(sizeof(uint64) * num_items);\n#endif\n\n\tFmgrInfo *finfo = context->bloom_hasher.hash_function_finfos[0];\n\tPGFunction hash_fn = context->bloom_hasher.hash_functions[0];\n\n\tint valid = 0;\n\tfor (int i = 0; i < num_items; i++)\n\t{\n\t\tif (nulls[i])\n\t\t{\n\t\t\t/*\n\t\t\t * A null value cannot match the equality condition.\n\t\t\t */\n\t\t\tcontinue;\n\t\t}\n\n\t\titem_base_hashes[valid++] =\n\t\t\tbatch_metadata_builder_bloom1_calculate_hash(hash_fn, finfo, items[i]);\n\t}\n\n\tif (valid == 0)\n\t{\n\t\t/*\n\t\t * No non-null elements.\n\t\t */\n\t\tPG_RETURN_BOOL(false);\n\t}\n\n\t/*\n\t * Sort the hashes for cache-friendly probing.\n\t */\n\tsort_hashes(item_base_hashes, valid);\n\n\t/*\n\t * Get the bloom filter parameters.\n\t */\n\tconst char *words_buf = bloom1_words_buf(bloom);\n\tconst uint32 num_bits = bloom1_num_bits(bloom);\n\n\t/* Must be a power of two. */\n\tCheckCompressedData(num_bits == (1ULL << pg_leftmost_one_pos32(num_bits)));\n\n\t/* Must be >= 64 bits. */\n\tCheckCompressedData(num_bits >= 64);\n\n\tconst uint32 num_word_bits = sizeof(*words_buf) * 8;\n\tAssert(num_bits % num_word_bits == 0);\n\tconst uint32 log2_word_bits = pg_leftmost_one_pos32(num_word_bits);\n\tAssert(num_word_bits == (1ULL << log2_word_bits));\n\n\tconst uint32 word_mask = num_word_bits - 1;\n\tAssert((word_mask >> num_word_bits) == 0);\n\n\tconst uint32 absolute_mask = num_bits - 1;\n\n\t/* Probe the bloom filter. */\n\tfor (int item_index = 0; item_index < valid; item_index++)\n\t{\n\t\tconst uint64 base_hash = item_base_hashes[item_index];\n\t\tbool match = true;\n\t\tfor (int i = 0; i < BLOOM1_HASHES; i++)\n\t\t{\n\t\t\tconst uint32 absolute_bit_index = bloom1_get_one_offset(base_hash, i) & absolute_mask;\n\t\t\tconst uint32 word_index = absolute_bit_index >> log2_word_bits;\n\t\t\tconst uint32 word_bit_index = absolute_bit_index & word_mask;\n\t\t\tif ((words_buf[word_index] & (1ULL << word_bit_index)) == 0)\n\t\t\t{\n\t\t\t\tmatch = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (match)\n\t\t{\n\t\t\tPG_RETURN_BOOL(true);\n\t\t}\n\t}\n\n\tPG_RETURN_BOOL(false);\n}\n\nstatic int\nbloom1_varlena_alloc_size(uint32 num_bits)\n{\n\t/*\n\t * We are not supposed to go below 64 bits because we work in 64-bit words.\n\t */\n\tAssert(num_bits % 64 == 0);\n\tAssert(num_bits > 0);\n\n\t/*\n\t * We must not go over varlena size limit.\n\t */\n\tAssert(num_bits / 8 <= (1ULL << 30) - 1);\n\n\treturn VARHDRSZ + num_bits / 8;\n}\n\nint\nbatch_metadata_builder_bloom1_varlena_size(void)\n{\n\t/*\n\t * Better make the bloom filter size a power of two, because we compress the\n\t * sparse filters using division in half. The formula for the lowest\n\t * enclosing power of two is pow(2, floor(log2(x * 2 - 1))).\n\t */\n\tconst int expected_elements = TARGET_COMPRESSED_BATCH_SIZE * 16;\n\tconst int lowest_power = pg_leftmost_one_pos32(expected_elements * 2 - 1);\n\n\t/*\n\t * The total number of elements must fit into uint32, since that's what we\n\t * use for addressing the elements.\n\t */\n\tAssert(lowest_power < 32);\n\n\tconst int desired_bits = 1ULL << lowest_power;\n\treturn bloom1_varlena_alloc_size(desired_bits);\n}\n\nstatic void\nbloom1_hasher_init(Bloom1HasherInternal *hasher, const Oid *type_oids, int num_columns)\n{\n\t*hasher = (Bloom1HasherInternal){\n\t\t.functions =\n\t\t\t(Bloom1Hasher){\n\t\t\t\t.hash_values = bloom1_hash_values,\n\t\t\t\t.num_columns = num_columns,\n\t\t\t},\n\t};\n\n\tfor (int i = 0; i < num_columns; i++)\n\t{\n\t\thasher->hash_functions[i] =\n\t\t\tbloom1_get_hash_function(type_oids[i], &hasher->hash_function_finfos[i]);\n\t\tif (hasher->hash_functions[i] == NULL)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_FUNCTION),\n\t\t\t\t\t errmsg(\"the argument type %s lacks an extended hash function\",\n\t\t\t\t\t\t\tformat_type_be(type_oids[i]))));\n\t}\n}\n\nBloom1Hasher *\nbloom1_hasher_create(const Oid *type_oids, int num_columns)\n{\n\tBloom1HasherInternal *hasher = palloc(sizeof(*hasher));\n\tbloom1_hasher_init(hasher, type_oids, num_columns);\n\treturn &hasher->functions;\n}\n\nBatchMetadataBuilder *\nbatch_metadata_builder_bloom1_create(int num_columns, const Oid *type_oids,\n\t\t\t\t\t\t\t\t\t const AttrNumber *attnums, int bloom_attr_offset)\n{\n\tAssert(num_columns >= 1 && num_columns <= MAX_BLOOM_FILTER_COLUMNS);\n\n\tconst int varlena_bytes = batch_metadata_builder_bloom1_varlena_size();\n\n\tBloom1MetadataBuilder *builder = palloc(sizeof(*builder));\n\t*builder = (Bloom1MetadataBuilder){\n\t\t.functions =\n\t\t\t(BatchMetadataBuilder){\n\t\t\t\t.update_row = bloom1_update_row,\n\t\t\t\t.insert_to_compressed_row = bloom1_insert_to_compressed_row,\n\t\t\t\t.reset = bloom1_reset,\n\t\t\t\t.builder_type = METADATA_BUILDER_BLOOM1,\n\t\t\t},\n\t\t.bloom_attr_offset = bloom_attr_offset,\n\t\t.allocated_varlena_bytes = varlena_bytes,\n\t};\n\n\tmemcpy(builder->input_columns, attnums, num_columns * sizeof(AttrNumber));\n\n\t/* Initialize the embedded hasher */\n\tbloom1_hasher_init(&builder->hasher, type_oids, num_columns);\n\n\t/*\n\t * Initialize the bloom filter.\n\t */\n\tbuilder->bloom_varlena = palloc0(varlena_bytes);\n\tSET_VARSIZE(builder->bloom_varlena, varlena_bytes);\n\n\treturn &builder->functions;\n}\n\n#ifndef NDEBUG\n\nstatic int\nbloom1_estimate_ndistinct(struct varlena *bloom)\n{\n\tconst double m = bloom1_num_bits(bloom);\n\tconst double t = pg_popcount(bloom1_words_buf(bloom), m / 8);\n\tconst double k = BLOOM1_HASHES;\n\treturn log(1 - t / m) / (k * log(1 - 1 / m));\n}\n\n/*\n * We're slightly modifying this Postgres macro to avoid a warning about signed\n * vs unsigned comparison.\n */\n#define TS_VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer)                                            \\\n\t((int) VARATT_EXTERNAL_GET_EXTSIZE(toast_pointer) <                                            \\\n\t (int) ((toast_pointer).va_rawsize - VARHDRSZ))\n\nTS_FUNCTION_INFO_V1(ts_bloom1_debug_info);\n\n/*\n * A function to output various debugging info about a bloom filter.\n *\n * Usage hints in the tests.\n */\nDatum\nts_bloom1_debug_info(PG_FUNCTION_ARGS)\n{\n\t/* Build a tuple descriptor for our result type */\n\tTupleDesc tuple_desc;\n\tif (get_call_result_type(fcinfo, NULL, &tuple_desc) != TYPEFUNC_COMPOSITE)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"function returning record called in context \"\n\t\t\t\t\t\t\"that cannot accept type record\")));\n\n\t/* Output columns of this function. */\n\tenum\n\t{\n\t\tout_toast_header = 0,\n\t\tout_toasted_bytes,\n\t\tout_compressed_bytes,\n\t\tout_detoasted_bytes,\n\t\tout_bits_total,\n\t\tout_bits_set,\n\t\tout_estimated_elements,\n\t\t_out_columns\n\t};\n\tDatum values[_out_columns] = { 0 };\n\tbool nulls[_out_columns] = { 0 };\n\n\tDatum toasted = PG_GETARG_DATUM(0);\n\tvalues[out_toast_header] = Int32GetDatum(((varattrib_1b *) toasted)->va_header);\n\tvalues[out_toasted_bytes] = Int32GetDatum(VARSIZE_ANY_EXHDR(toasted));\n\n\tstruct varlena *detoasted = PG_DETOAST_DATUM(toasted);\n\tvalues[out_detoasted_bytes] = Int32GetDatum(VARSIZE_ANY_EXHDR(detoasted));\n\n\tconst int bits_total = bloom1_num_bits(detoasted);\n\tvalues[out_bits_total] = Int32GetDatum(bits_total);\n\n\tconst char *words = bloom1_words_buf(detoasted);\n\n\tvalues[out_bits_set] = Int32GetDatum(pg_popcount(words, bits_total / 8));\n\n\tvalues[out_estimated_elements] = Int32GetDatum(bloom1_estimate_ndistinct(detoasted));\n\n\tif (VARATT_IS_EXTERNAL_ONDISK(toasted))\n\t{\n\t\tstruct varatt_external toast_pointer;\n\t\tVARATT_EXTERNAL_GET_POINTER(toast_pointer, toasted);\n\n\t\tif (TS_VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer))\n\t\t{\n\t\t\tvalues[out_compressed_bytes] = VARATT_EXTERNAL_GET_EXTSIZE(toast_pointer);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tnulls[out_compressed_bytes] = true;\n\t\t}\n\t}\n\telse if (VARATT_IS_COMPRESSED(toasted))\n\t{\n\t\tvalues[out_compressed_bytes] = VARDATA_COMPRESSED_GET_EXTSIZE(toasted);\n\t}\n\telse\n\t{\n\t\tnulls[out_compressed_bytes] = true;\n\t}\n\n\tPG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tuple_desc, values, nulls)));\n}\n\nTS_FUNCTION_INFO_V1(ts_bloom1_debug_hash);\n\n/*\n * A debug function to inspect the actual hash value used for the bloom filter,\n * e.g. to find the very even hashes with many low bits equal to zero.\n */\nDatum\nts_bloom1_debug_hash(PG_FUNCTION_ARGS)\n{\n\tOid type_oid = get_fn_expr_argtype(fcinfo->flinfo, 0);\n\tFmgrInfo *finfo = NULL;\n\tPGFunction fn = bloom1_get_hash_function(type_oid, &finfo);\n\tEnsure(fn != NULL, \"cannot find our hash function\");\n\n\tAssert(!PG_ARGISNULL(0));\n\tDatum needle = PG_GETARG_DATUM(0);\n\tPG_RETURN_UINT64(batch_metadata_builder_bloom1_calculate_hash(fn, finfo, needle));\n}\n\nTS_FUNCTION_INFO_V1(ts_bloom1_composite_debug_hash);\n\nDatum\nts_bloom1_composite_debug_hash(PG_FUNCTION_ARGS)\n{\n\tif (PG_ARGISNULL(0))\n\t\tPG_RETURN_NULL();\n\n\tHeapTupleHeader tuple = DatumGetHeapTupleHeader(PG_GETARG_DATUM(0));\n\tOid tupType = HeapTupleHeaderGetTypeId(tuple);\n\tint32 tupTypmod = HeapTupleHeaderGetTypMod(tuple);\n\tTupleDesc tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);\n\n\tint num_fields = tupdesc->natts;\n\tif (num_fields < 2 || num_fields > MAX_BLOOM_FILTER_COLUMNS)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"composite bloom requires 2-%d fields, got %d\",\n\t\t\t\t\t\tMAX_BLOOM_FILTER_COLUMNS,\n\t\t\t\t\t\tnum_fields)));\n\n\tOid type_oids[MAX_BLOOM_FILTER_COLUMNS];\n\tfor (int i = 0; i < num_fields; i++)\n\t\ttype_oids[i] = TupleDescAttr(tupdesc, i)->atttypid;\n\tReleaseTupleDesc(tupdesc);\n\n\tBloom1Hasher *hasher = bloom1_hasher_create(type_oids, num_fields);\n\n\tNullableDatum values[MAX_BLOOM_FILTER_COLUMNS];\n\tfor (int i = 0; i < num_fields; i++)\n\t\tvalues[i].value = GetAttributeByNum(tuple, i + 1, &values[i].isnull);\n\n\tuint64 hash = hasher->hash_values(hasher, values);\n\tPG_RETURN_INT64((int64) hash);\n}\n\n#endif // #ifndef NDEBUG\n\nchar const *bloom1_column_prefix = NULL;\n\n/* this function will be reused in a later PR when we push down pre-calculated\n * hash value checks from the planner */\nbool\nbloom1_contains_hash(Datum bloom_datum, uint64 hash)\n{\n\tstruct varlena *bloom = DatumGetByteaPP(bloom_datum);\n\tif (bloom == NULL)\n\t\treturn true; /* No bloom = might match */\n\n\tconst char *words_buf = VARDATA_ANY(bloom);\n\tconst uint32 num_bits = 8 * VARSIZE_ANY_EXHDR(bloom);\n\n\t/* Validate bloom structure */\n\tCheckCompressedData(num_bits == (1ULL << pg_leftmost_one_pos32(num_bits)));\n\tCheckCompressedData(num_bits >= 64);\n\n\treturn bloom1_contains_hash_internal(words_buf, num_bits, hash);\n}\n"
  },
  {
    "path": "tsl/src/compression/batch_metadata_builder_minmax.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n#include <libpq/pqformat.h>\n#include <utils/builtins.h>\n#include <utils/datum.h>\n#include <utils/sortsupport.h>\n#include <utils/typcache.h>\n\n#include \"batch_metadata_builder_minmax.h\"\n\n#include \"compression.h\"\n\nstatic void minmax_update_row(void *builder_, TupleTableSlot *slot);\nstatic void minmax_insert_to_compressed_row(void *builder_, RowCompressor *compressor);\nstatic void minmax_reset(void *builder_, RowCompressor *compressor);\n\nBatchMetadataBuilder *\nbatch_metadata_builder_minmax_create(Oid type_oid, Oid collation, AttrNumber attnum,\n\t\t\t\t\t\t\t\t\t int min_attr_offset, int max_attr_offset)\n{\n\tBatchMetadataBuilderMinMax *builder = palloc(sizeof(*builder));\n\tTypeCacheEntry *type = lookup_type_cache(type_oid, TYPECACHE_LT_OPR);\n\n\tif (!OidIsValid(type->lt_opr))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_UNDEFINED_FUNCTION),\n\t\t\t\t errmsg(\"could not identify an less-than operator for type %s\",\n\t\t\t\t\t\tformat_type_be(type_oid))));\n\n\t*builder = (BatchMetadataBuilderMinMax){\n\t\t.functions =\n\t\t\t(BatchMetadataBuilder){\n\t\t\t\t.update_row = minmax_update_row,\n\t\t\t\t.insert_to_compressed_row = minmax_insert_to_compressed_row,\n\t\t\t\t.reset = minmax_reset,\n\t\t\t\t.builder_type = METADATA_BUILDER_MINMAX,\n\t\t\t},\n\t\t.type_oid = type_oid,\n\t\t.attnum = attnum,\n\t\t.empty = true,\n\t\t.has_null = false,\n\t\t.type_by_val = type->typbyval,\n\t\t.type_len = type->typlen,\n\t\t.min_metadata_attr_offset = min_attr_offset,\n\t\t.max_metadata_attr_offset = max_attr_offset,\n\t};\n\n\tbuilder->ssup.ssup_cxt = CurrentMemoryContext;\n\tbuilder->ssup.ssup_collation = collation;\n\tbuilder->ssup.ssup_nulls_first = false;\n\n\tPrepareSortSupportFromOrderingOp(type->lt_opr, &builder->ssup);\n\n\treturn &builder->functions;\n}\n\nvoid\nminmax_update_row(void *builder_, TupleTableSlot *slot)\n{\n\tBatchMetadataBuilderMinMax *builder = (BatchMetadataBuilderMinMax *) builder_;\n\tAssert(builder->functions.builder_type == METADATA_BUILDER_MINMAX);\n\tbool is_null;\n\tDatum val = slot_getattr(slot, builder->attnum, &is_null);\n\tif (is_null)\n\t{\n\t\tbuilder->has_null = true;\n\t\treturn;\n\t}\n\tint cmp;\n\n\tif (builder->empty)\n\t{\n\t\tbuilder->min = datumCopy(val, builder->type_by_val, builder->type_len);\n\t\tbuilder->max = datumCopy(val, builder->type_by_val, builder->type_len);\n\t\tbuilder->empty = false;\n\t\treturn;\n\t}\n\n\tcmp = ApplySortComparator(builder->min, false, val, false, &builder->ssup);\n\tif (cmp > 0)\n\t{\n\t\tif (!builder->type_by_val)\n\t\t\tpfree(DatumGetPointer(builder->min));\n\t\tbuilder->min = datumCopy(val, builder->type_by_val, builder->type_len);\n\t}\n\n\tcmp = ApplySortComparator(builder->max, false, val, false, &builder->ssup);\n\tif (cmp < 0)\n\t{\n\t\tif (!builder->type_by_val)\n\t\t\tpfree(DatumGetPointer(builder->max));\n\t\tbuilder->max = datumCopy(val, builder->type_by_val, builder->type_len);\n\t}\n}\n\nstatic void\nminmax_reset(void *builder_, RowCompressor *compressor)\n{\n\tBatchMetadataBuilderMinMax *builder = (BatchMetadataBuilderMinMax *) builder_;\n\tif (!builder->empty)\n\t{\n\t\tif (!builder->type_by_val)\n\t\t{\n\t\t\tpfree(DatumGetPointer(builder->min));\n\t\t\tpfree(DatumGetPointer(builder->max));\n\t\t}\n\t\tbuilder->min = 0;\n\t\tbuilder->max = 0;\n\t}\n\tbuilder->empty = true;\n\tbuilder->has_null = false;\n\n\tcompressor->compressed_is_null[builder->max_metadata_attr_offset] = true;\n\tcompressor->compressed_is_null[builder->min_metadata_attr_offset] = true;\n\tcompressor->compressed_values[builder->min_metadata_attr_offset] = 0;\n\tcompressor->compressed_values[builder->max_metadata_attr_offset] = 0;\n}\n\nDatum\nbatch_metadata_builder_minmax_min(void *builder_)\n{\n\tBatchMetadataBuilderMinMax *builder = (BatchMetadataBuilderMinMax *) builder_;\n\tif (builder->empty)\n\t\telog(ERROR, \"trying to get min from an empty builder\");\n\tif (builder->type_len == -1)\n\t{\n\t\tDatum unpacked = PointerGetDatum(PG_DETOAST_DATUM_PACKED(builder->min));\n\t\tif (builder->min != unpacked)\n\t\t\tpfree(DatumGetPointer(builder->min));\n\t\tbuilder->min = unpacked;\n\t}\n\treturn builder->min;\n}\n\nDatum\nbatch_metadata_builder_minmax_max(void *builder_)\n{\n\tBatchMetadataBuilderMinMax *builder = (BatchMetadataBuilderMinMax *) builder_;\n\tif (builder->empty)\n\t\telog(ERROR, \"trying to get max from an empty builder\");\n\tif (builder->type_len == -1)\n\t{\n\t\tDatum unpacked = PointerGetDatum(PG_DETOAST_DATUM_PACKED(builder->max));\n\t\tif (builder->max != unpacked)\n\t\t\tpfree(DatumGetPointer(builder->max));\n\t\tbuilder->max = unpacked;\n\t}\n\treturn builder->max;\n}\n\nbool\nbatch_metadata_builder_minmax_empty(void *builder_)\n{\n\tBatchMetadataBuilderMinMax *builder = (BatchMetadataBuilderMinMax *) builder_;\n\treturn builder->empty;\n}\n\nstatic void\nminmax_insert_to_compressed_row(void *builder_, RowCompressor *compressor)\n{\n\tBatchMetadataBuilderMinMax *builder = (BatchMetadataBuilderMinMax *) builder_;\n\tAssert(builder->min_metadata_attr_offset >= 0);\n\tAssert(builder->max_metadata_attr_offset >= 0);\n\n\tif (!batch_metadata_builder_minmax_empty(builder))\n\t{\n\t\tcompressor->compressed_is_null[builder->min_metadata_attr_offset] = false;\n\t\tcompressor->compressed_is_null[builder->max_metadata_attr_offset] = false;\n\n\t\tcompressor->compressed_values[builder->min_metadata_attr_offset] =\n\t\t\tbatch_metadata_builder_minmax_min(builder);\n\t\tcompressor->compressed_values[builder->max_metadata_attr_offset] =\n\t\t\tbatch_metadata_builder_minmax_max(builder);\n\t}\n\telse\n\t{\n\t\tcompressor->compressed_is_null[builder->min_metadata_attr_offset] = true;\n\t\tcompressor->compressed_is_null[builder->max_metadata_attr_offset] = true;\n\t}\n}\n"
  },
  {
    "path": "tsl/src/compression/batch_metadata_builder_minmax.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <fmgr.h>\n#include <lib/stringinfo.h>\n#include <utils/sortsupport.h>\n\n#include \"batch_metadata_builder.h\"\n\ntypedef struct BatchMetadataBuilderMinMax\n{\n\tBatchMetadataBuilder functions;\n\n\tOid type_oid;\n\tAttrNumber attnum;\n\tbool empty;\n\tbool has_null;\n\n\tSortSupportData ssup;\n\tbool type_by_val;\n\tint16 type_len;\n\tDatum min;\n\tDatum max;\n\n\tint16 min_metadata_attr_offset;\n\tint16 max_metadata_attr_offset;\n} BatchMetadataBuilderMinMax;\n\ntypedef struct BatchMetadataBuilderMinMax BatchMetadataBuilderMinMax;\n\ntypedef struct RowCompressor RowCompressor;\n\n/*\n * This is exposed only for the old unit tests. Ideally they should be replaced\n * with functional tests inspecting the compressed chunk table, and this\n * test-only interface should be removed.\n */\nDatum batch_metadata_builder_minmax_min(void *builder_);\nDatum batch_metadata_builder_minmax_max(void *builder_);\nbool batch_metadata_builder_minmax_empty(void *builder_);\n"
  },
  {
    "path": "tsl/src/compression/city_combine.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/* The code below is taken from the CityHash project: https://github.com/google/cityhash\n * specifically from here: https://github.com/google/cityhash/blob/master/src/city.h#L101\n */\n\n/*\n * Copyright (c) 2011 Google, Inc.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * CityHash, by Geoff Pike and Jyrki Alakuijala\n *\n * http://code.google.com/p/cityhash/\n */\n\n#pragma once\n\n#include <stdint.h>\n\nstatic inline uint64_t\ncity_hash_combine(uint64_t accumulated_hash, uint64_t new_hash)\n{\n\tconst uint64_t kMul = 0x9ddfea08eb382d69ULL;\n\tuint64_t a = (accumulated_hash ^ new_hash) * kMul;\n\ta ^= (a >> 47);\n\tuint64_t b = (new_hash ^ a) * kMul;\n\tb ^= (b >> 47);\n\tb *= kMul;\n\treturn b;\n}\n"
  },
  {
    "path": "tsl/src/compression/compression.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/attmap.h>\n#include <access/attnum.h>\n#include <access/detoast.h>\n#include <access/skey.h>\n#include <access/tupdesc.h>\n#include <catalog/heap.h>\n#include <catalog/indexing.h>\n#include <catalog/pg_am.h>\n#include <common/base64.h>\n#include <funcapi.h>\n#include <libpq/pqformat.h>\n#include <storage/predicate.h>\n#include <utils/datum.h>\n#include <utils/elog.h>\n#include <utils/lsyscache.h>\n#include <utils/palloc.h>\n#include <utils/rel.h>\n#include <utils/snapmgr.h>\n#include <utils/syscache.h>\n#include <utils/typcache.h>\n\n#include \"compat/compat.h\"\n\n#include \"algorithms/array.h\"\n#include \"algorithms/bool_compress.h\"\n#include \"algorithms/deltadelta.h\"\n#include \"algorithms/dictionary.h\"\n#include \"algorithms/gorilla.h\"\n#include \"algorithms/null.h\"\n#include \"algorithms/uuid_compress.h\"\n#include \"batch_metadata_builder.h\"\n#include \"chunk_insert_state.h\"\n#include \"compression.h\"\n#include \"compression/sparse_index_bloom1.h\"\n#include \"continuous_aggs/insert.h\"\n#include \"create.h\"\n#include \"custom_type_cache.h\"\n#include \"debug_assert.h\"\n#include \"debug_point.h\"\n#include \"guc.h\"\n#include \"nodes/modify_hypertable.h\"\n#include \"ts_catalog/array_utils.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"ts_catalog/compression_settings.h\"\n#include <nodes/columnar_scan/vector_quals.h>\n\n/*\n * Timing parameters for truncate locking heuristics.\n * These are the same as used by Postgres for truncate locking during lazy vacuum.\n * https://github.com/postgres/postgres/blob/4a0650d359c5981270039eeb634c3b7427aa0af5/src/backend/access/heap/vacuumlazy.c#L82\n */\n#define COMPRESS_TRUNCATE_LOCK_WAIT_INTERVAL 50 /* ms */\n#define COMPRESS_TRUNCATE_LOCK_TIMEOUT 5000\t\t/* ms */\n\nStaticAssertDecl(GLOBAL_MAX_ROWS_PER_COMPRESSION >= TARGET_COMPRESSED_BATCH_SIZE,\n\t\t\t\t \"max row numbers must be harmonized\");\nStaticAssertDecl(GLOBAL_MAX_ROWS_PER_COMPRESSION <= INT16_MAX,\n\t\t\t\t \"dictionary compression uses signed int16 indexes\");\n\nstatic const CompressionAlgorithmDefinition definitions[_END_COMPRESSION_ALGORITHMS] = {\n\t[COMPRESSION_ALGORITHM_ARRAY] = ARRAY_ALGORITHM_DEFINITION,\n\t[COMPRESSION_ALGORITHM_DICTIONARY] = DICTIONARY_ALGORITHM_DEFINITION,\n\t[COMPRESSION_ALGORITHM_GORILLA] = GORILLA_ALGORITHM_DEFINITION,\n\t[COMPRESSION_ALGORITHM_DELTADELTA] = DELTA_DELTA_ALGORITHM_DEFINITION,\n\t[COMPRESSION_ALGORITHM_BOOL] = BOOL_COMPRESS_ALGORITHM_DEFINITION,\n\t[COMPRESSION_ALGORITHM_NULL] = NULL_COMPRESS_ALGORITHM_DEFINITION,\n\t[COMPRESSION_ALGORITHM_UUID] = UUID_COMPRESS_ALGORITHM_DEFINITION,\n};\n\nstatic NameData compression_algorithm_name[] = {\n\t[_INVALID_COMPRESSION_ALGORITHM] = { \"INVALID\" },\n\t[COMPRESSION_ALGORITHM_ARRAY] = { \"ARRAY\" },\n\t[COMPRESSION_ALGORITHM_DICTIONARY] = { \"DICTIONARY\" },\n\t[COMPRESSION_ALGORITHM_GORILLA] = { \"GORILLA\" },\n\t[COMPRESSION_ALGORITHM_DELTADELTA] = { \"DELTADELTA\" },\n\t[COMPRESSION_ALGORITHM_BOOL] = { \"BOOL\" },\n\t[COMPRESSION_ALGORITHM_NULL] = { \"NULL\" },\n\t[COMPRESSION_ALGORITHM_UUID] = { \"UUID\" },\n};\n\nName\ncompression_get_algorithm_name(CompressionAlgorithm alg)\n{\n\treturn &compression_algorithm_name[alg];\n}\n\nstatic Compressor *\ncompressor_for_type(Oid type)\n{\n\tCompressionAlgorithm algorithm = compression_get_default_algorithm(type);\n\tif (algorithm >= _END_COMPRESSION_ALGORITHMS)\n\t\telog(ERROR, \"invalid compression algorithm %d\", algorithm);\n\n\treturn definitions[algorithm].compressor_for_type(type);\n}\n\nDecompressionInitializer\ntsl_get_decompression_iterator_init(CompressionAlgorithm algorithm, bool reverse)\n{\n\tif (algorithm >= _END_COMPRESSION_ALGORITHMS)\n\t\telog(ERROR, \"invalid compression algorithm %d\", algorithm);\n\n\tif (reverse)\n\t\treturn definitions[algorithm].iterator_init_reverse;\n\telse\n\t\treturn definitions[algorithm].iterator_init_forward;\n}\n\nDecompressAllFunction\ntsl_get_decompress_all_function(CompressionAlgorithm algorithm, Oid type)\n{\n\tif (algorithm >= _END_COMPRESSION_ALGORITHMS)\n\t\telog(ERROR, \"invalid compression algorithm %d\", algorithm);\n\n\tif (type != TEXTOID && type != BOOLOID && type != UUIDOID &&\n\t\t(algorithm == COMPRESSION_ALGORITHM_DICTIONARY || algorithm == COMPRESSION_ALGORITHM_ARRAY))\n\t{\n\t\t/* Bulk decompression of array and dictionary is only supported for\n\t\t * text, bool and uuid */\n\t\treturn NULL;\n\t}\n\n\treturn definitions[algorithm].decompress_all;\n}\n\nstatic Tuplesortstate *compress_chunk_sort_relation(CompressionSettings *settings, Relation in_rel);\nstatic void row_compressor_process_ordered_slot(RowCompressor *row_compressor, TupleTableSlot *slot,\n\t\t\t\t\t\t\t\t\t\t\t\tBulkWriter *writer);\nstatic void row_compressor_update_group(RowCompressor *row_compressor, TupleTableSlot *row);\nstatic bool row_compressor_new_row_is_in_new_group(RowCompressor *row_compressor,\n\t\t\t\t\t\t\t\t\t\t\t\t   TupleTableSlot *row);\nstatic void create_per_compressed_column(RowDecompressor *decompressor);\nstatic void row_compressor_append_row(RowCompressor *row_compressor, TupleTableSlot *row);\nstatic void row_compressor_flush(RowCompressor *row_compressor, BulkWriter *writer,\n\t\t\t\t\t\t\t\t bool changed_groups);\n\n/********************\n ** compress_chunk **\n ********************/\n\nstatic CompressedDataHeader *\nget_compressed_data_header(Datum data)\n{\n\tCompressedDataHeader *header = (CompressedDataHeader *) PG_DETOAST_DATUM(data);\n\n\tif (header->compression_algorithm >= _END_COMPRESSION_ALGORITHMS)\n\t\telog(ERROR, \"invalid compression algorithm %d\", header->compression_algorithm);\n\n\treturn header;\n}\n\n/* Truncate the relation WITHOUT applying triggers. This is the\n * main difference with ExecuteTruncate. Triggers aren't applied\n * because the data remains, just in compressed form. Also don't\n * restart sequences. Use the transactional branch through ExecuteTruncate.\n */\nstatic void\ntruncate_relation(Oid table_oid)\n{\n\tList *fks = heap_truncate_find_FKs(list_make1_oid(table_oid));\n\t/* Take an access exclusive lock now. Note that this may very well\n\t *  be a lock upgrade. */\n\tRelation rel = table_open(table_oid, AccessExclusiveLock);\n\tOid toast_relid;\n\n\t/* Chunks should never have fks into them, but double check */\n\tif (fks != NIL)\n\t\telog(ERROR, \"found a FK into a chunk while truncating\");\n\n\tCheckTableForSerializableConflictIn(rel);\n\n#if PG16_LT\n\tRelationSetNewRelfilenode(rel, rel->rd_rel->relpersistence);\n#else\n\tRelationSetNewRelfilenumber(rel, rel->rd_rel->relpersistence);\n#endif\n\n\ttoast_relid = rel->rd_rel->reltoastrelid;\n\n\ttable_close(rel, NoLock);\n\n\tif (OidIsValid(toast_relid))\n\t{\n\t\trel = table_open(toast_relid, AccessExclusiveLock);\n#if PG16_LT\n\t\tRelationSetNewRelfilenode(rel, rel->rd_rel->relpersistence);\n#else\n\t\tRelationSetNewRelfilenumber(rel, rel->rd_rel->relpersistence);\n#endif\n\t\ttable_close(rel, NoLock);\n\t}\n\n\tReindexParams params = { 0 };\n\tReindexParams *options = &params;\n\treindex_relation_compat(NULL, table_oid, REINDEX_REL_PROCESS_TOAST, options);\n\trel = table_open(table_oid, AccessExclusiveLock);\n\tCommandCounterIncrement();\n\ttable_close(rel, NoLock);\n}\n\n/* Handle the all rows deletion of a given relation */\nstatic void\nRelationDeleteAllRows(Relation rel, Snapshot snap)\n{\n\tTupleTableSlot *slot = table_slot_create(rel, NULL);\n\tTableScanDesc scan = table_beginscan(rel, snap, 0, NULL);\n\n\twhile (table_scan_getnextslot(scan, ForwardScanDirection, slot))\n\t{\n\t\tsimple_table_tuple_delete(rel, &(slot->tts_tid), snap);\n\t}\n\ttable_endscan(scan);\n\tExecDropSingleTupleTableSlot(slot);\n}\n\n/*\n * Delete the relation WITHOUT applying triggers. This will be used when\n * `enable_delete_after_compression = true` instead of truncating the relation.\n * Also don't restart sequences.\n */\nstatic void\ndelete_relation_rows(Oid table_oid)\n{\n\tRelation rel = table_open(table_oid, RowExclusiveLock);\n\tSnapshot snap = RegisterSnapshot(GetLatestSnapshot());\n\n\t/* Delete the rows in the table */\n\tRelationDeleteAllRows(rel, snap);\n\n\t/* Delete the rows in the toast table */\n\tif (OidIsValid(rel->rd_rel->reltoastrelid))\n\t{\n\t\tRelation toast_rel = table_open(rel->rd_rel->reltoastrelid, RowExclusiveLock);\n\t\tRelationDeleteAllRows(toast_rel, snap);\n\t\ttable_close(toast_rel, NoLock);\n\t}\n\n\ttable_close(rel, NoLock);\n\tUnregisterSnapshot(snap);\n}\n\n/*\n * Use reltuples as an estimate for the number of rows that will get compressed. This value\n * might be way off the mark in case analyze hasn't happened in quite a while on this input\n * chunk. But that's the best guesstimate to start off with.\n *\n * We will report progress for every 10% of reltuples compressed. If rel or reltuples is not valid\n * or it's just too low then we just assume reporting every 100K tuples for now.\n */\n#define RELTUPLES_REPORT_DEFAULT 100000\nstatic int64\ncalculate_reltuples_to_report(float4 reltuples)\n{\n\tint64 report_reltuples = RELTUPLES_REPORT_DEFAULT;\n\n\tif (reltuples > 0)\n\t{\n\t\treport_reltuples = (int64) (0.1 * reltuples);\n\t\t/* either analyze has not been done or table doesn't have a lot of rows */\n\t\tif (report_reltuples < RELTUPLES_REPORT_DEFAULT)\n\t\t\treport_reltuples = RELTUPLES_REPORT_DEFAULT;\n\t}\n\n\treturn report_reltuples;\n}\n\nCompressionStats\ncompress_chunk(Oid in_table, Oid out_table, int insert_options)\n{\n\tint n_keys;\n\tListCell *lc;\n\tScanDirection indexscan_direction = NoMovementScanDirection;\n\tRelation matched_index_rel = NULL;\n\tTupleTableSlot *slot;\n\tIndexScanDesc index_scan;\n\tHeapTuple in_table_tp = NULL, index_tp = NULL;\n\tForm_pg_attribute in_table_attr_tp, index_attr_tp;\n\tCompressionStats cstat;\n\tCompressionSettings *settings = ts_compression_settings_get_by_compress_relid(out_table);\n\n\tint64 report_reltuples;\n\n\t/* We want to prevent other compressors from compressing this table,\n\t * and we want to prevent INSERTs or UPDATEs which could mess up our compression.\n\t * We may as well allow readers to keep reading the uncompressed data while\n\t * we are compressing, so we only take an ExclusiveLock instead of AccessExclusive.\n\t */\n\tRelation in_rel = table_open(in_table, ExclusiveLock);\n\t/* We are _just_ INSERTing into the out_table so in principle we could take\n\t * a RowExclusive lock, and let other operations read and write this table\n\t * as we work. However, we currently compress each table as a oneshot, so\n\t * we're taking the stricter lock to prevent accidents.\n\t *\n\t * Putting RowExclusiveMode behind a GUC so we can try this out with\n\t * rollups during compression.\n\t */\n\tint out_rel_lockmode = ExclusiveLock;\n\tif (ts_guc_enable_rowlevel_compression_locking)\n\t{\n\t\tout_rel_lockmode = RowExclusiveLock;\n\t}\n\tRelation out_rel = relation_open(out_table, out_rel_lockmode);\n\tBulkWriter writer = bulk_writer_build(out_rel, insert_options);\n\n\t/* Sanity check we are dealing with relations */\n\tEnsure(in_rel->rd_rel->relkind == RELKIND_RELATION, \"compress_chunk called on non-relation\");\n\tEnsure(out_rel->rd_rel->relkind == RELKIND_RELATION, \"compress_chunk called on non-relation\");\n\n\tPushActiveSnapshot(GetTransactionSnapshot());\n\n\t/* Before calling row compressor relation should be segmented and sorted as configured\n\t * by compress_segmentby and compress_orderby.\n\t * Cost of sorting can be mitigated if we find an existing BTREE index defined for\n\t * uncompressed chunk otherwise expensive tuplesort will come into play.\n\t *\n\t * The following code is trying to find an existing index that\n\t * matches the configuration so that we can skip sequential scan and\n\t * tuplesort.\n\t *\n\t */\n\tif (ts_guc_enable_compression_indexscan)\n\t{\n\t\tList *in_rel_index_oids = RelationGetIndexList(in_rel);\n\t\tforeach (lc, in_rel_index_oids)\n\t\t{\n\t\t\tOid index_oid = lfirst_oid(lc);\n\t\t\tRelation index_rel = index_open(index_oid, AccessShareLock);\n\t\t\tIndexInfo *index_info = BuildIndexInfo(index_rel);\n\n\t\t\tif (index_info->ii_Predicate != 0)\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * Can't use partial indexes for compression because they refer\n\t\t\t\t * only to a subset of all rows.\n\t\t\t\t */\n\t\t\t\tindex_close(index_rel, AccessShareLock);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tint previous_direction = NoMovementScanDirection;\n\t\t\tint current_direction = NoMovementScanDirection;\n\n\t\t\tn_keys =\n\t\t\t\tts_array_length(settings->fd.segmentby) + ts_array_length(settings->fd.orderby);\n\n\t\t\tif (n_keys <= index_info->ii_NumIndexKeyAttrs && index_info->ii_Am == BTREE_AM_OID)\n\t\t\t{\n\t\t\t\tint i;\n\t\t\t\tfor (i = 0; i < n_keys; i++)\n\t\t\t\t{\n\t\t\t\t\tconst char *attname;\n\t\t\t\t\tint16 position;\n\t\t\t\t\tbool is_orderby_asc = true;\n\t\t\t\t\tbool is_null_first = false;\n\n\t\t\t\t\tif (i < ts_array_length(settings->fd.segmentby))\n\t\t\t\t\t{\n\t\t\t\t\t\tposition = i + 1;\n\t\t\t\t\t\tattname = ts_array_get_element_text(settings->fd.segmentby, position);\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tposition = i - ts_array_length(settings->fd.segmentby) + 1;\n\t\t\t\t\t\tattname = ts_array_get_element_text(settings->fd.orderby, position);\n\t\t\t\t\t\tis_orderby_asc =\n\t\t\t\t\t\t\t!ts_array_get_element_bool(settings->fd.orderby_desc, position);\n\t\t\t\t\t\tis_null_first =\n\t\t\t\t\t\t\tts_array_get_element_bool(settings->fd.orderby_nullsfirst, position);\n\t\t\t\t\t}\n\t\t\t\t\tint16 att_num = get_attnum(in_table, attname);\n\n\t\t\t\t\tint16 option = index_rel->rd_indoption[i];\n\t\t\t\t\tbool index_orderby_asc = ((option & INDOPTION_DESC) == 0);\n\t\t\t\t\tbool index_null_first = ((option & INDOPTION_NULLS_FIRST) != 0);\n\n\t\t\t\t\tif (att_num == 0 || index_info->ii_IndexAttrNumbers[i] != att_num)\n\t\t\t\t\t{\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tin_table_tp = SearchSysCacheAttNum(in_table, att_num);\n\t\t\t\t\tif (!HeapTupleIsValid(in_table_tp))\n\t\t\t\t\t\telog(ERROR,\n\t\t\t\t\t\t\t \"table \\\"%s\\\" does not have column \\\"%s\\\"\",\n\t\t\t\t\t\t\t get_rel_name(in_table),\n\t\t\t\t\t\t\t attname);\n\n\t\t\t\t\tindex_tp = SearchSysCacheAttNum(index_oid, i + 1);\n\t\t\t\t\tif (!HeapTupleIsValid(index_tp))\n\t\t\t\t\t\telog(ERROR,\n\t\t\t\t\t\t\t \"index \\\"%s\\\" does not have column \\\"%s\\\"\",\n\t\t\t\t\t\t\t get_rel_name(index_oid),\n\t\t\t\t\t\t\t attname);\n\n\t\t\t\t\tin_table_attr_tp = (Form_pg_attribute) GETSTRUCT(in_table_tp);\n\t\t\t\t\tindex_attr_tp = (Form_pg_attribute) GETSTRUCT(index_tp);\n\n\t\t\t\t\tif (index_orderby_asc == is_orderby_asc && index_null_first == is_null_first &&\n\t\t\t\t\t\tin_table_attr_tp->attcollation == index_attr_tp->attcollation)\n\t\t\t\t\t{\n\t\t\t\t\t\tcurrent_direction = ForwardScanDirection;\n\t\t\t\t\t}\n\t\t\t\t\telse if (index_orderby_asc != is_orderby_asc &&\n\t\t\t\t\t\t\t index_null_first != is_null_first &&\n\t\t\t\t\t\t\t in_table_attr_tp->attcollation == index_attr_tp->attcollation)\n\t\t\t\t\t{\n\t\t\t\t\t\tcurrent_direction = BackwardScanDirection;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tcurrent_direction = NoMovementScanDirection;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tReleaseSysCache(in_table_tp);\n\t\t\t\t\tin_table_tp = NULL;\n\t\t\t\t\tReleaseSysCache(index_tp);\n\t\t\t\t\tindex_tp = NULL;\n\t\t\t\t\tif (previous_direction == NoMovementScanDirection)\n\t\t\t\t\t{\n\t\t\t\t\t\tprevious_direction = current_direction;\n\t\t\t\t\t}\n\t\t\t\t\telse if (previous_direction != current_direction)\n\t\t\t\t\t{\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (n_keys == i && (previous_direction == current_direction &&\n\t\t\t\t\t\t\t\t\tcurrent_direction != NoMovementScanDirection))\n\t\t\t\t{\n\t\t\t\t\tmatched_index_rel = index_rel;\n\t\t\t\t\tindexscan_direction = current_direction;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tif (HeapTupleIsValid(in_table_tp))\n\t\t\t\t\t{\n\t\t\t\t\t\tReleaseSysCache(in_table_tp);\n\t\t\t\t\t\tin_table_tp = NULL;\n\t\t\t\t\t}\n\t\t\t\t\tif (HeapTupleIsValid(index_tp))\n\t\t\t\t\t{\n\t\t\t\t\t\tReleaseSysCache(index_tp);\n\t\t\t\t\t\tindex_tp = NULL;\n\t\t\t\t\t}\n\t\t\t\t\tindex_close(index_rel, AccessShareLock);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tindex_close(index_rel, AccessShareLock);\n\t\t\t}\n\t\t}\n\t}\n\n\tRowCompressor row_compressor;\n\n\tAssert(settings->fd.compress_relid == RelationGetRelid(out_rel));\n\trow_compressor_init(&row_compressor,\n\t\t\t\t\t\tsettings,\n\t\t\t\t\t\tRelationGetDescr(in_rel),\n\t\t\t\t\t\tRelationGetDescr(out_rel));\n\n\tif (matched_index_rel != NULL)\n\t{\n\t\tint64 nrows_processed = 0;\n\n\t\telog(ts_guc_debug_compression_path_info ? INFO : DEBUG1,\n\t\t\t \"using index \\\"%s\\\" to scan rows for converting to columnstore\",\n\t\t\t get_rel_name(matched_index_rel->rd_id));\n\n\t\tindex_scan =\n\t\t\tindex_beginscan_compat(in_rel, matched_index_rel, GetActiveSnapshot(), NULL, 0, 0);\n\t\tslot = table_slot_create(in_rel, NULL);\n\t\tindex_rescan(index_scan, NULL, 0, NULL, 0);\n\t\treport_reltuples = calculate_reltuples_to_report(in_rel->rd_rel->reltuples);\n\t\twhile (index_getnext_slot(index_scan, indexscan_direction, slot))\n\t\t{\n\t\t\trow_compressor_process_ordered_slot(&row_compressor, slot, &writer);\n\t\t\tif ((++nrows_processed % report_reltuples) == 0)\n\t\t\t\telog(DEBUG2,\n\t\t\t\t\t \"converted \" INT64_FORMAT \" rows to columnstore from \\\"%s\\\"\",\n\t\t\t\t\t nrows_processed,\n\t\t\t\t\t RelationGetRelationName(in_rel));\n\t\t}\n\n\t\tif (row_compressor.rows_compressed_into_current_value > 0)\n\t\t\trow_compressor_flush(&row_compressor, &writer, true);\n\n\t\telog(DEBUG1,\n\t\t\t \"finished converting \" INT64_FORMAT \" rows to columnstore from \\\"%s\\\"\",\n\t\t\t nrows_processed,\n\t\t\t RelationGetRelationName(in_rel));\n\n\t\tExecDropSingleTupleTableSlot(slot);\n\t\tindex_endscan(index_scan);\n\t\tindex_close(matched_index_rel, AccessShareLock);\n\t}\n\telse\n\t{\n\t\telog(ts_guc_debug_compression_path_info ? INFO : DEBUG1,\n\t\t\t \"using tuplesort to scan rows from \\\"%s\\\" for converting to columnstore\",\n\t\t\t RelationGetRelationName(in_rel));\n\n\t\tTuplesortstate *sorted_rel = compress_chunk_sort_relation(settings, in_rel);\n\t\trow_compressor_append_sorted_rows(&row_compressor, sorted_rel, in_rel, &writer);\n\t\ttuplesort_end(sorted_rel);\n\t}\n\n\trow_compressor_close(&row_compressor);\n\tbulk_writer_close(&writer);\n\n\tif (ts_guc_enable_delete_after_compression)\n\t{\n\t\tereport(NOTICE,\n\t\t\t\t(errcode(ERRCODE_WARNING_DEPRECATED_FEATURE),\n\t\t\t\t errmsg(\"timescaledb.enable_delete_after_compression is deprecated and will be \"\n\t\t\t\t\t\t\"removed in a future version. Please use \"\n\t\t\t\t\t\t\"timescaledb.compress_truncate_behaviour instead.\")));\n\t\tdelete_relation_rows(in_table);\n\t\tDEBUG_WAITPOINT(\"compression_done_after_delete_uncompressed\");\n\t}\n\telse\n\t{\n\t\tint lock_retry = 0;\n\t\tswitch (ts_guc_compress_truncate_behaviour)\n\t\t{\n\t\t\tcase COMPRESS_TRUNCATE_ONLY:\n\t\t\t\tDEBUG_WAITPOINT(\"compression_done_before_truncate_uncompressed\");\n\t\t\t\ttruncate_relation(in_table);\n\t\t\t\tDEBUG_WAITPOINT(\"compression_done_after_truncate_uncompressed\");\n\t\t\t\tbreak;\n\t\t\tcase COMPRESS_TRUNCATE_OR_DELETE:\n\t\t\t\tDEBUG_WAITPOINT(\"compression_done_before_truncate_or_delete_uncompressed\");\n\t\t\t\twhile (true)\n\t\t\t\t{\n\t\t\t\t\tif (ConditionalLockRelation(in_rel, AccessExclusiveLock))\n\t\t\t\t\t{\n\t\t\t\t\t\ttruncate_relation(in_table);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\t/*\n\t\t\t\t\t * Check for interrupts while trying to (re-)acquire the exclusive\n\t\t\t\t\t * lock.\n\t\t\t\t\t */\n\t\t\t\t\tCHECK_FOR_INTERRUPTS();\n\n\t\t\t\t\tif (++lock_retry >\n\t\t\t\t\t\t(COMPRESS_TRUNCATE_LOCK_TIMEOUT / COMPRESS_TRUNCATE_LOCK_WAIT_INTERVAL))\n\t\t\t\t\t{\n\t\t\t\t\t\t/*\n\t\t\t\t\t\t * We failed to establish the lock in the specified number of\n\t\t\t\t\t\t * retries. This means we give up truncating and fallback to delete\n\t\t\t\t\t\t */\n\t\t\t\t\t\tdelete_relation_rows(in_table);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\t(void) WaitLatch(MyLatch,\n\t\t\t\t\t\t\t\t\t WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,\n\t\t\t\t\t\t\t\t\t COMPRESS_TRUNCATE_LOCK_WAIT_INTERVAL,\n\t\t\t\t\t\t\t\t\t WAIT_EVENT_VACUUM_TRUNCATE);\n\t\t\t\t\tResetLatch(MyLatch);\n\t\t\t\t}\n\t\t\t\tDEBUG_WAITPOINT(\"compression_done_after_truncate_or_delete_uncompressed\");\n\t\t\t\tbreak;\n\t\t\tcase COMPRESS_TRUNCATE_DISABLED:\n\t\t\t\tdelete_relation_rows(in_table);\n\t\t\t\tDEBUG_WAITPOINT(\"compression_done_after_delete_uncompressed\");\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\ttable_close(out_rel, NoLock);\n\ttable_close(in_rel, NoLock);\n\n\tPopActiveSnapshot();\n\n\tcstat.rowcnt_pre_compression = row_compressor.rowcnt_pre_compression;\n\tcstat.rowcnt_post_compression = row_compressor.num_compressed_rows;\n\n\tif ((insert_options & HEAP_INSERT_FROZEN) == HEAP_INSERT_FROZEN)\n\t\tcstat.rowcnt_frozen = row_compressor.num_compressed_rows;\n\telse\n\t\tcstat.rowcnt_frozen = 0;\n\n\treturn cstat;\n}\n\nTuplesortstate *\ncompression_create_tuplesort_state(CompressionSettings *settings, Relation rel)\n{\n\tTupleDesc tupdesc = RelationGetDescr(rel);\n\tint num_segmentby = ts_array_length(settings->fd.segmentby);\n\tint num_orderby = ts_array_length(settings->fd.orderby);\n\tint n_keys = num_segmentby + num_orderby;\n\tAttrNumber *sort_keys = palloc(sizeof(*sort_keys) * n_keys);\n\tOid *sort_operators = palloc(sizeof(*sort_operators) * n_keys);\n\tOid *sort_collations = palloc(sizeof(*sort_collations) * n_keys);\n\tbool *nulls_first = palloc(sizeof(*nulls_first) * n_keys);\n\tint n;\n\n\tfor (n = 0; n < n_keys; n++)\n\t{\n\t\tconst char *attname;\n\t\tint position;\n\t\tif (n < num_segmentby)\n\t\t{\n\t\t\tposition = n + 1;\n\t\t\tattname = ts_array_get_element_text(settings->fd.segmentby, position);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tposition = n - num_segmentby + 1;\n\t\t\tattname = ts_array_get_element_text(settings->fd.orderby, position);\n\t\t}\n\t\tcompress_chunk_populate_sort_info_for_column(settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t RelationGetRelid(rel),\n\t\t\t\t\t\t\t\t\t\t\t\t\t attname,\n\t\t\t\t\t\t\t\t\t\t\t\t\t &sort_keys[n],\n\t\t\t\t\t\t\t\t\t\t\t\t\t &sort_operators[n],\n\t\t\t\t\t\t\t\t\t\t\t\t\t &sort_collations[n],\n\t\t\t\t\t\t\t\t\t\t\t\t\t &nulls_first[n]);\n\t}\n\n\t/* Make a copy of the tuple descriptor so that it is allocated on the same\n\t * memory context as the tuple sort instead of pointing into the relcache\n\t * entry that could be blown away. */\n\treturn tuplesort_begin_heap(CreateTupleDescCopy(tupdesc),\n\t\t\t\t\t\t\t\tn_keys,\n\t\t\t\t\t\t\t\tsort_keys,\n\t\t\t\t\t\t\t\tsort_operators,\n\t\t\t\t\t\t\t\tsort_collations,\n\t\t\t\t\t\t\t\tnulls_first,\n\t\t\t\t\t\t\t\tmaintenance_work_mem,\n\t\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\t\tfalse /*=randomAccess*/);\n}\n\nstatic Tuplesortstate *\ncompress_chunk_sort_relation(CompressionSettings *settings, Relation in_rel)\n{\n\tPushActiveSnapshot(GetLatestSnapshot());\n\tTuplesortstate *tuplesortstate;\n\tTableScanDesc scan;\n\tTupleTableSlot *slot;\n\ttuplesortstate = compression_create_tuplesort_state(settings, in_rel);\n\tscan = table_beginscan(in_rel, GetActiveSnapshot(), 0, NULL);\n\tslot = table_slot_create(in_rel, NULL);\n\n\twhile (table_scan_getnextslot(scan, ForwardScanDirection, slot))\n\t{\n\t\tif (!TTS_EMPTY(slot))\n\t\t{\n\t\t\t/*    This may not be the most efficient way to do things.\n\t\t\t *     Since we use begin_heap() the tuplestore expects tupleslots,\n\t\t\t *      so ISTM that the options are this or maybe putdatum().\n\t\t\t */\n\t\t\ttuplesort_puttupleslot(tuplesortstate, slot);\n\t\t}\n\t}\n\n\ttable_endscan(scan);\n\n\tExecDropSingleTupleTableSlot(slot);\n\n\ttuplesort_performsort(tuplesortstate);\n\tPopActiveSnapshot();\n\treturn tuplesortstate;\n}\n\nvoid\ncompress_chunk_populate_sort_info_for_column(const CompressionSettings *settings, Oid table,\n\t\t\t\t\t\t\t\t\t\t\t const char *attname, AttrNumber *att_nums,\n\t\t\t\t\t\t\t\t\t\t\t Oid *sort_operator, Oid *collation, bool *nulls_first)\n{\n\tHeapTuple tp;\n\tForm_pg_attribute att_tup;\n\tTypeCacheEntry *tentry;\n\n\ttp = SearchSysCacheAttName(table, attname);\n\tif (!HeapTupleIsValid(tp))\n\t\telog(ERROR, \"table \\\"%s\\\" does not have column \\\"%s\\\"\", get_rel_name(table), attname);\n\n\tatt_tup = (Form_pg_attribute) GETSTRUCT(tp);\n\t/* Other validation checks beyond just existence of a valid comparison operator could be useful\n\t */\n\n\t*att_nums = att_tup->attnum;\n\t*collation = att_tup->attcollation;\n\n\ttentry = lookup_type_cache(att_tup->atttypid, TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);\n\n\tif (ts_array_is_member(settings->fd.segmentby, attname))\n\t{\n\t\t*nulls_first = false;\n\t\t*sort_operator = tentry->lt_opr;\n\t}\n\telse\n\t{\n\t\tAssert(ts_array_is_member(settings->fd.orderby, attname));\n\t\tint position = ts_array_position(settings->fd.orderby, attname);\n\t\t*nulls_first = ts_array_get_element_bool(settings->fd.orderby_nullsfirst, position);\n\n\t\tif (ts_array_get_element_bool(settings->fd.orderby_desc, position))\n\t\t\t*sort_operator = tentry->gt_opr;\n\t\telse\n\t\t\t*sort_operator = tentry->lt_opr;\n\t}\n\n\tif (!OidIsValid(*sort_operator))\n\t\telog(ERROR,\n\t\t\t \"no valid sort operator for column \\\"%s\\\" of type \\\"%s\\\"\",\n\t\t\t attname,\n\t\t\t format_type_be(att_tup->atttypid));\n\n\tReleaseSysCache(tp);\n}\n\n/*\n * Find segment by index on compressed chunk needed when doing index scans\n * over compressed data\n */\nOid\nget_compressed_chunk_index(ResultRelInfo *resultRelInfo, const CompressionSettings *settings)\n{\n\tint num_segmentby_columns = ts_array_length(settings->fd.segmentby);\n\tint num_orderby_columns = ts_array_length(settings->fd.orderby);\n\n\tfor (int i = 0; i < resultRelInfo->ri_NumIndices; i++)\n\t{\n\t\tbool matches = true;\n\t\tRelation index_relation = resultRelInfo->ri_IndexRelationDescs[i];\n\t\tIndexInfo *index_info = resultRelInfo->ri_IndexRelationInfo[i];\n\n\t\t/* The index must include all segment by columns and at least two metadata columns.\n\t\t * Default index we build includes all segmentby columns and metadata columns (min and max,\n\t\t * in that order) for all orderby columns.*/\n\t\tif (index_info->ii_NumIndexKeyAttrs != num_segmentby_columns + (num_orderby_columns * 2))\n\t\t\tcontinue;\n\n\t\tfor (int j = 0; j < num_segmentby_columns - 1; j++)\n\t\t{\n\t\t\tAttrNumber attno = index_relation->rd_index->indkey.values[j];\n\t\t\tconst char *attname = get_attname(index_relation->rd_index->indrelid, attno, false);\n\n\t\t\tif (!ts_array_is_member(settings->fd.segmentby, attname))\n\t\t\t{\n\t\t\t\tmatches = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (!matches)\n\t\t\tcontinue;\n\n\t\treturn RelationGetRelid(index_relation);\n\t}\n\n\treturn InvalidOid;\n}\n\nstatic void\nbuild_column_map(const CompressionSettings *settings, const TupleDesc in_desc,\n\t\t\t\t const TupleDesc out_desc, PerColumn **pcolumns, int16 **pmap,\n\t\t\t\t List **pmetadata_builders)\n{\n\tOid compressed_data_type_oid = ts_custom_type_cache_get(CUSTOM_TYPE_COMPRESSED_DATA)->type_oid;\n\n\tPerColumn *columns = palloc0(sizeof(PerColumn) * in_desc->natts);\n\tint16 *map = palloc0(sizeof(int16) * in_desc->natts);\n\tList *metadata_builders = NIL;\n\n\tSparseIndexSettings *parsed_settings = NULL;\n\tif (settings && settings->fd.index)\n\t\tparsed_settings = ts_convert_to_sparse_index_settings(settings->fd.index);\n\n\tif (parsed_settings != NULL && ts_guc_enable_composite_bloom_indexes)\n\t{\n\t\tListCell *lc;\n\t\tforeach (lc, parsed_settings->objects)\n\t\t{\n\t\t\tSparseIndexSettingsObject *obj = lfirst(lc);\n\t\t\tList *column_names = ts_get_column_names_from_parsed_object(obj);\n\t\t\tint num_columns = list_length(column_names);\n\t\t\tif (num_columns < 2)\n\t\t\t\tcontinue;\n\n\t\t\tOid type_oids[MAX_BLOOM_FILTER_COLUMNS];\n\t\t\tAttrNumber attnums[MAX_BLOOM_FILTER_COLUMNS];\n\t\t\tint col_idx = 0;\n\t\t\tListCell *name_cell;\n\t\t\tforeach (name_cell, column_names)\n\t\t\t{\n\t\t\t\tconst char *col_name = (const char *) lfirst(name_cell);\n\t\t\t\tAttrNumber attnum = get_attnum(settings->fd.relid, col_name);\n\t\t\t\tEnsure(AttributeNumberIsValid(attnum), \"could not find column '%s'\", col_name);\n\t\t\t\tattnums[col_idx] = attnum;\n\t\t\t\ttype_oids[col_idx] = get_atttype(settings->fd.relid, attnum);\n\t\t\t\tcol_idx++;\n\t\t\t}\n\n\t\t\tconst char *bloom_col_name =\n\t\t\t\tcompressed_column_metadata_name_list_v2(bloom1_column_prefix, column_names);\n\t\t\tAttrNumber bloom_attr_number = get_attnum(settings->fd.compress_relid, bloom_col_name);\n\t\t\tif (!AttributeNumberIsValid(bloom_attr_number))\n\t\t\t\tcontinue;\n\n\t\t\tint bloom_attr_offset = AttrNumberGetAttrOffset(bloom_attr_number);\n\t\t\tmetadata_builders = lappend(metadata_builders,\n\t\t\t\t\t\t\t\t\t\tbatch_metadata_builder_bloom1_create(num_columns,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t type_oids,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t attnums,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t bloom_attr_offset));\n\t\t}\n\t}\n\tts_free_sparse_index_settings(parsed_settings);\n\n\tif (settings != NULL && OidIsValid(settings->fd.compress_relid))\n\t{\n\t\tfor (int i = 0; i < in_desc->natts; i++)\n\t\t{\n\t\t\tForm_pg_attribute attr = TupleDescAttr(in_desc, i);\n\n\t\t\tif (attr->attisdropped)\n\t\t\t\tcontinue;\n\n\t\t\tPerColumn *column = &columns[AttrNumberGetAttrOffset(attr->attnum)];\n\t\t\tAttrNumber compressed_colnum =\n\t\t\t\tget_attnum(settings->fd.compress_relid, NameStr(attr->attname));\n\t\t\tForm_pg_attribute compressed_column_attr =\n\t\t\t\tTupleDescAttr(out_desc, AttrNumberGetAttrOffset(compressed_colnum));\n\t\t\tmap[AttrNumberGetAttrOffset(attr->attnum)] = AttrNumberGetAttrOffset(compressed_colnum);\n\n\t\t\tbool is_segmentby = ts_array_is_member(settings->fd.segmentby, NameStr(attr->attname));\n\t\t\tbool is_orderby = ts_array_is_member(settings->fd.orderby, NameStr(attr->attname));\n\n\t\t\tif (!is_segmentby)\n\t\t\t{\n\t\t\t\tif (compressed_column_attr->atttypid != compressed_data_type_oid)\n\t\t\t\t\telog(ERROR,\n\t\t\t\t\t\t \"expected column '%s' to be a compressed data type\",\n\t\t\t\t\t\t NameStr(attr->attname));\n\n\t\t\t\tAttrNumber segment_min_attr_number =\n\t\t\t\t\tcompressed_column_metadata_attno(settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t settings->fd.relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t attr->attnum,\n\t\t\t\t\t\t\t\t\t\t\t\t\t settings->fd.compress_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t \"min\");\n\t\t\t\tAttrNumber segment_max_attr_number =\n\t\t\t\t\tcompressed_column_metadata_attno(settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t settings->fd.relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t attr->attnum,\n\t\t\t\t\t\t\t\t\t\t\t\t\t settings->fd.compress_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t \"max\");\n\t\t\t\tint16 segment_min_attr_offset = segment_min_attr_number - 1;\n\t\t\t\tint16 segment_max_attr_offset = segment_max_attr_number - 1;\n\n\t\t\t\tbool has_minmax_metadata = false;\n\t\t\t\tif (segment_min_attr_number != InvalidAttrNumber ||\n\t\t\t\t\tsegment_max_attr_number != InvalidAttrNumber)\n\t\t\t\t{\n\t\t\t\t\thas_minmax_metadata = true;\n\t\t\t\t\tEnsure(segment_min_attr_number != InvalidAttrNumber,\n\t\t\t\t\t\t   \"could not find the min metadata column\");\n\t\t\t\t\tEnsure(segment_max_attr_number != InvalidAttrNumber,\n\t\t\t\t\t\t   \"could not find the min metadata column\");\n\t\t\t\t\tmetadata_builders =\n\t\t\t\t\t\tlappend(metadata_builders,\n\t\t\t\t\t\t\t\tbatch_metadata_builder_minmax_create(attr->atttypid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t attr->attcollation,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t attr->attnum,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t segment_min_attr_offset,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t segment_max_attr_offset));\n\t\t\t\t}\n\n\t\t\t\tEnsure(!is_orderby || has_minmax_metadata,\n\t\t\t\t\t   \"orderby columns must have minmax metadata\");\n\n\t\t\t\tconst AttrNumber bloom_attr_number =\n\t\t\t\t\tcompressed_column_metadata_attno(settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t settings->fd.relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t attr->attnum,\n\t\t\t\t\t\t\t\t\t\t\t\t\t settings->fd.compress_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t bloom1_column_prefix);\n\t\t\t\tif (AttributeNumberIsValid(bloom_attr_number))\n\t\t\t\t{\n\t\t\t\t\tOid type_oid = attr->atttypid;\n\t\t\t\t\tAttrNumber attnum = attr->attnum;\n\t\t\t\t\tconst int bloom_attr_offset = AttrNumberGetAttrOffset(bloom_attr_number);\n\t\t\t\t\tmetadata_builders =\n\t\t\t\t\t\tlappend(metadata_builders,\n\t\t\t\t\t\t\t\tbatch_metadata_builder_bloom1_create(1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t &type_oid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t &attnum,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t bloom_attr_offset));\n\t\t\t\t}\n\n\t\t\t\t*column = (PerColumn){\n\t\t\t\t\t.compressor = compressor_for_type(attr->atttypid),\n\t\t\t\t\t.segmentby_column_index = -1,\n\t\t\t\t};\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (attr->atttypid != compressed_column_attr->atttypid)\n\t\t\t\t\telog(ERROR,\n\t\t\t\t\t\t \"expected segment by column \\\"%s\\\" to be same type as uncompressed column\",\n\t\t\t\t\t\t NameStr(attr->attname));\n\t\t\t\tint16 index = ts_array_position(settings->fd.segmentby, NameStr(attr->attname));\n\t\t\t\t*column = (PerColumn){\n\t\t\t\t\t.segment_info = segment_info_new(attr),\n\t\t\t\t\t.segmentby_column_index = index,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t}\n\n\t*pcolumns = columns;\n\t*pmap = map;\n\t*pmetadata_builders = metadata_builders;\n}\n\n/* Check if we contain any compressors which need allocation limit checking */\nstatic bool\ncheck_for_limited_size_compressors(PerColumn *pcolumns, int16 natts)\n{\n\tfor (int i = 0; i < natts; i++)\n\t{\n\t\tif (pcolumns[i].compressor && pcolumns[i].compressor->is_full)\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nvoid\ntsl_compressor_set_invalidation(RowCompressor *compressor, Hypertable *ht, Oid chunk_relid)\n{\n\tconst Dimension *time_dim = hyperspace_get_open_dimension(ht->space, 0);\n\tEnsure(time_dim, \"Hypertable must have an open dimension\");\n\tAttrNumber attnum = get_attnum(chunk_relid, NameStr(time_dim->fd.column_name));\n\n\tcompressor->invalidation = palloc0(sizeof(InvalidationSettings));\n\tcompressor->invalidation->hypertable_id = ht->fd.id;\n\tcompressor->invalidation->chunk_relid = chunk_relid;\n\tcompressor->invalidation->invalidation_column_offset = AttrNumberGetAttrOffset(attnum);\n}\n\nvoid\ntsl_compressor_add_slot(RowCompressor *compressor, BulkWriter *bulk_writer, TupleTableSlot *slot)\n{\n\tif (compressor->sort_state)\n\t{\n\t\ttuplesort_puttupleslot(compressor->sort_state, slot);\n\t\tcompressor->tuples_to_sort++;\n\n\t\tif (compressor->tuple_sort_limit &&\n\t\t\tcompressor->tuples_to_sort >= compressor->tuple_sort_limit)\n\t\t\ttsl_compressor_flush(compressor, bulk_writer);\n\t}\n\telse\n\t{\n\t\trow_compressor_process_ordered_slot(compressor, slot, bulk_writer);\n\t}\n}\n\nvoid\ntsl_compressor_flush(RowCompressor *compressor, BulkWriter *bulk_writer)\n{\n\tif (compressor->sort_state)\n\t{\n\t\tif (compressor->tuples_to_sort)\n\t\t{\n\t\t\ttuplesort_performsort(compressor->sort_state);\n\n\t\t\tTupleTableSlot *slot = MakeTupleTableSlot(compressor->in_desc, &TTSOpsMinimalTuple);\n\n\t\t\twhile (tuplesort_gettupleslot(compressor->sort_state,\n\t\t\t\t\t\t\t\t\t\t  true /*=forward*/,\n\t\t\t\t\t\t\t\t\t\t  false /*=copy*/,\n\t\t\t\t\t\t\t\t\t\t  slot,\n\t\t\t\t\t\t\t\t\t\t  NULL /*=abbrev*/))\n\t\t\t\trow_compressor_process_ordered_slot(compressor, slot, bulk_writer);\n\n\t\t\tif (compressor->rows_compressed_into_current_value > 0)\n\t\t\t\trow_compressor_flush(compressor, bulk_writer, true);\n\n\t\t\tExecDropSingleTupleTableSlot(slot);\n\t\t\ttuplesort_reset(compressor->sort_state);\n\t\t\tcompressor->tuples_to_sort = 0;\n\t\t}\n\t}\n\telse\n\t{\n\t\tif (compressor->rows_compressed_into_current_value > 0)\n\t\t\trow_compressor_flush(compressor, bulk_writer, false);\n\t}\n}\n\nvoid\ntsl_compressor_free(RowCompressor *compressor, BulkWriter *bulk_writer)\n{\n\tif (compressor->sort_state)\n\t\ttuplesort_end(compressor->sort_state);\n\tif (compressor->invalidation)\n\t\tpfree(compressor->invalidation);\n\ttsl_compressor_flush(compressor, bulk_writer);\n\trow_compressor_close(compressor);\n\tbulk_writer_close(bulk_writer);\n\ttable_close(bulk_writer->out_rel, NoLock);\n}\n\n/*\n * Initialize a RowCompressor for compressing tuples\n *\n * When `sort` is true, the compressor will buffer all the tuples in a\n * Tuplesortstate and sort them before flushing to the output relation.\n */\nRowCompressor *\ntsl_compressor_init(Relation in_rel, BulkWriter **bulk_writer, bool sort, int sort_limit)\n{\n\tRowCompressor *compressor = palloc0(sizeof(RowCompressor));\n\tCompressionSettings *settings = ts_compression_settings_get(in_rel->rd_id);\n\tRelation out_rel = table_open(settings->fd.compress_relid, RowExclusiveLock);\n\t*bulk_writer = bulk_writer_alloc(out_rel, 0);\n\trow_compressor_init(compressor, settings, RelationGetDescr(in_rel), RelationGetDescr(out_rel));\n\n\tif (sort)\n\t{\n\t\tcompressor->sort_state = compression_create_tuplesort_state(settings, in_rel);\n\t\tcompressor->tuple_sort_limit = sort_limit;\n\t}\n\n\treturn compressor;\n}\n\n/********************\n ** row_compressor **\n ********************/\nvoid\nrow_compressor_init(RowCompressor *row_compressor, const CompressionSettings *settings,\n\t\t\t\t\tconst TupleDesc noncompressed_tupdesc, const TupleDesc compressed_tupdesc)\n{\n\tName count_metadata_name = DatumGetName(\n\t\tDirectFunctionCall1(namein, CStringGetDatum(COMPRESSION_COLUMN_METADATA_COUNT_NAME)));\n\tAttrNumber count_metadata_column_num =\n\t\tget_attnum(settings->fd.compress_relid, NameStr(*count_metadata_name));\n\n\tif (count_metadata_column_num == InvalidAttrNumber)\n\t\telog(ERROR,\n\t\t\t \"missing metadata column '%s' in columnstore table\",\n\t\t\t COMPRESSION_COLUMN_METADATA_COUNT_NAME);\n\n\t*row_compressor = (RowCompressor){\n\t\t.per_row_ctx = AllocSetContextCreate(CurrentMemoryContext,\n\t\t\t\t\t\t\t\t\t\t\t \"compress chunk per-row\",\n\t\t\t\t\t\t\t\t\t\t\t ALLOCSET_DEFAULT_SIZES),\n\t\t.in_desc = CreateTupleDescCopyConstr(noncompressed_tupdesc),\n\t\t.out_desc = CreateTupleDescCopyConstr(compressed_tupdesc),\n\t\t.n_input_columns = noncompressed_tupdesc->natts,\n\t\t.count_metadata_column_offset = AttrNumberGetAttrOffset(count_metadata_column_num),\n\t\t.compressed_values = palloc(sizeof(Datum) * compressed_tupdesc->natts),\n\t\t.compressed_is_null = palloc(sizeof(bool) * compressed_tupdesc->natts),\n\t\t.rows_compressed_into_current_value = 0,\n\t\t.rowcnt_pre_compression = 0,\n\t\t.num_compressed_rows = 0,\n\t\t.first_iteration = true,\n\t\t.sort_state = NULL,\n\t};\n\n\tmemset(row_compressor->compressed_is_null, 1, sizeof(bool) * compressed_tupdesc->natts);\n\n\tbuild_column_map(settings,\n\t\t\t\t\t noncompressed_tupdesc,\n\t\t\t\t\t compressed_tupdesc,\n\t\t\t\t\t &row_compressor->per_column,\n\t\t\t\t\t &row_compressor->uncompressed_col_to_compressed_col,\n\t\t\t\t\t &row_compressor->metadata_builders);\n\n\t/* If we have dictionary or array compressors, we have to check compressor size so we don't end\n\t * up going over allocation limit */\n\trow_compressor->needs_fullness_check =\n\t\tcheck_for_limited_size_compressors(row_compressor->per_column,\n\t\t\t\t\t\t\t\t\t\t   row_compressor->n_input_columns);\n}\n\nvoid\nrow_compressor_append_sorted_rows(RowCompressor *row_compressor, Tuplesortstate *sorted_rel,\n\t\t\t\t\t\t\t\t  Relation in_rel, BulkWriter *writer)\n{\n\tTupleTableSlot *slot = MakeTupleTableSlot(row_compressor->in_desc, &TTSOpsMinimalTuple);\n\tint64 nrows_processed = 0;\n\tint64 report_reltuples;\n\n\treport_reltuples = calculate_reltuples_to_report(in_rel->rd_rel->reltuples);\n\n\twhile (tuplesort_gettupleslot(sorted_rel,\n\t\t\t\t\t\t\t\t  true /*=forward*/,\n\t\t\t\t\t\t\t\t  false /*=copy*/,\n\t\t\t\t\t\t\t\t  slot,\n\t\t\t\t\t\t\t\t  NULL /*=abbrev*/))\n\t{\n\t\trow_compressor_process_ordered_slot(row_compressor, slot, writer);\n\t\tif ((++nrows_processed % report_reltuples) == 0)\n\t\t\telog(DEBUG2,\n\t\t\t\t \"compressed \" INT64_FORMAT \" rows from \\\"%s\\\"\",\n\t\t\t\t nrows_processed,\n\t\t\t\t RelationGetRelationName(in_rel));\n\t}\n\n\tif (row_compressor->rows_compressed_into_current_value > 0)\n\t\trow_compressor_flush(row_compressor, writer, true);\n\telog(DEBUG1,\n\t\t \"finished compressing \" INT64_FORMAT \" rows from \\\"%s\\\"\",\n\t\t nrows_processed,\n\t\t RelationGetRelationName(in_rel));\n\n\tExecDropSingleTupleTableSlot(slot);\n}\n\nstatic bool\nrow_compressor_is_full(RowCompressor *row_compressor, TupleTableSlot *row)\n{\n\tif (row_compressor->rows_compressed_into_current_value >=\n\t\t(uint32) ts_guc_compression_batch_size_limit)\n\t\treturn true;\n\n\tif (!ts_guc_compression_enable_compressor_batch_limit)\n\t\treturn false;\n\n\tif (!row_compressor->needs_fullness_check)\n\t\treturn false;\n\n\t/* Check with every column compressor if they can add the next value to current batch */\n\tint col;\n\tfor (col = 0; col < row_compressor->n_input_columns; col++)\n\t{\n\t\tCompressor *compressor = row_compressor->per_column[col].compressor;\n\t\tbool is_null;\n\t\tDatum val;\n\n\t\t/* No compressor or the compressor has no check, just skip */\n\t\tif (compressor == NULL || compressor->is_full == NULL)\n\t\t\tcontinue;\n\n\t\tval = slot_getattr(row, AttrOffsetGetAttrNumber(col), &is_null);\n\t\tif (!is_null)\n\t\t{\n\t\t\tif (compressor->is_full(compressor, val))\n\t\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nvoid\nrow_compressor_append_ordered_slot(RowCompressor *row_compressor, TupleTableSlot *slot)\n{\n\tMemoryContext old_ctx;\n\tslot_getallattrs(slot);\n\told_ctx = MemoryContextSwitchTo(row_compressor->per_row_ctx);\n\tif (row_compressor->first_iteration)\n\t{\n\t\trow_compressor_update_group(row_compressor, slot);\n\t\trow_compressor->first_iteration = false;\n\t}\n\tbool changed_groups = row_compressor_new_row_is_in_new_group(row_compressor, slot);\n\tbool compressed_row_is_full = row_compressor_is_full(row_compressor, slot);\n\n\tEnsure(!changed_groups, \"row is in different group\");\n\tEnsure(!compressed_row_is_full, \"batch is full\");\n\trow_compressor_append_row(row_compressor, slot);\n\tMemoryContextSwitchTo(old_ctx);\n}\n\nstatic void\nrow_compressor_process_ordered_slot(RowCompressor *row_compressor, TupleTableSlot *slot,\n\t\t\t\t\t\t\t\t\tBulkWriter *writer)\n{\n\tMemoryContext old_ctx;\n\tslot_getallattrs(slot);\n\told_ctx = MemoryContextSwitchTo(row_compressor->per_row_ctx);\n\tif (row_compressor->first_iteration)\n\t{\n\t\trow_compressor_update_group(row_compressor, slot);\n\t\trow_compressor->first_iteration = false;\n\t}\n\tbool changed_groups = row_compressor_new_row_is_in_new_group(row_compressor, slot);\n\tbool compressed_row_is_full = row_compressor_is_full(row_compressor, slot);\n\tif (compressed_row_is_full || changed_groups)\n\t{\n\t\tif (row_compressor->rows_compressed_into_current_value > 0)\n\t\t\trow_compressor_flush(row_compressor, writer, changed_groups);\n\t\tif (changed_groups)\n\t\t\trow_compressor_update_group(row_compressor, slot);\n\t}\n\n\trow_compressor_append_row(row_compressor, slot);\n\tMemoryContextSwitchTo(old_ctx);\n}\n\nstatic void\nrow_compressor_update_group(RowCompressor *row_compressor, TupleTableSlot *row)\n{\n\tint col;\n\t/* save original memory context */\n\tconst MemoryContext oldcontext = CurrentMemoryContext;\n\n\tAssert(row_compressor->rows_compressed_into_current_value == 0);\n\tAssert(row_compressor->n_input_columns <= row->tts_nvalid);\n\n\tMemoryContextSwitchTo(row_compressor->per_row_ctx->parent);\n\tfor (col = 0; col < row_compressor->n_input_columns; col++)\n\t{\n\t\tPerColumn *column = &row_compressor->per_column[col];\n\t\tDatum val;\n\t\tbool is_null;\n\n\t\tif (column->segment_info == NULL)\n\t\t\tcontinue;\n\n\t\tAssert(column->compressor == NULL);\n\n\t\t/* Performance Improvement: We should just use array access here; everything is guaranteed\n\t\t   to be fetched */\n\t\tval = slot_getattr(row, AttrOffsetGetAttrNumber(col), &is_null);\n\t\tsegment_info_update(column->segment_info, val, is_null);\n\t}\n\t/* switch to original memory context */\n\tMemoryContextSwitchTo(oldcontext);\n}\n\nstatic bool\nrow_compressor_new_row_is_in_new_group(RowCompressor *row_compressor, TupleTableSlot *row)\n{\n\tint col;\n\tfor (col = 0; col < row_compressor->n_input_columns; col++)\n\t{\n\t\tPerColumn *column = &row_compressor->per_column[col];\n\t\tDatum datum = CharGetDatum(0);\n\t\tbool is_null;\n\n\t\tif (column->segment_info == NULL)\n\t\t\tcontinue;\n\n\t\tAssert(column->compressor == NULL);\n\n\t\tdatum = slot_getattr(row, AttrOffsetGetAttrNumber(col), &is_null);\n\n\t\tif (!segment_info_datum_is_in_group(column->segment_info, datum, is_null))\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nvoid\nrow_compressor_append_row(RowCompressor *row_compressor, TupleTableSlot *row)\n{\n\tint col;\n\tfor (col = 0; col < row_compressor->n_input_columns; col++)\n\t{\n\t\tCompressor *compressor = row_compressor->per_column[col].compressor;\n\t\tbool is_null;\n\t\tDatum val;\n\n\t\t/* if there is no compressor, this must be a segmenter, so just skip */\n\t\tif (compressor == NULL)\n\t\t\tcontinue;\n\n\t\t/* Performance Improvement: Since we call getallatts at the beginning, slot_getattr is\n\t\t * useless overhead here, and we should just access the array directly.\n\t\t */\n\t\tval = slot_getattr(row, AttrOffsetGetAttrNumber(col), &is_null);\n\t\tif (is_null)\n\t\t\tcompressor->append_null(compressor);\n\t\telse\n\t\t\tcompressor->append_val(compressor, val);\n\t}\n\n\tListCell *lc;\n\tforeach (lc, row_compressor->metadata_builders)\n\t{\n\t\tBatchMetadataBuilder *builder = lfirst(lc);\n\t\tbuilder->update_row(builder, row);\n\t}\n\n\trow_compressor->rows_compressed_into_current_value += 1;\n}\n\nHeapTuple\nrow_compressor_build_tuple(RowCompressor *row_compressor)\n{\n\tMemoryContext old_cxt = MemoryContextSwitchTo(row_compressor->per_row_ctx);\n\n\tfor (int col = 0; col < row_compressor->n_input_columns; col++)\n\t{\n\t\tPerColumn *column = &row_compressor->per_column[col];\n\t\tCompressor *compressor;\n\t\tint16 compressed_col;\n\t\tif (column->compressor == NULL && column->segment_info == NULL)\n\t\t\tcontinue;\n\n\t\tcompressor = column->compressor;\n\t\tcompressed_col = row_compressor->uncompressed_col_to_compressed_col[col];\n\n\t\tAssert(compressed_col >= 0);\n\n\t\tif (compressor != NULL)\n\t\t{\n\t\t\tvoid *compressed_data;\n\t\t\tAssert(column->segment_info == NULL);\n\n\t\t\tcompressed_data = compressor->finish(compressor);\n\t\t\tif (compressed_data == NULL)\n\t\t\t{\n\t\t\t\tif (ts_guc_enable_null_compression &&\n\t\t\t\t\trow_compressor->rows_compressed_into_current_value > 0)\n\t\t\t\t\tcompressed_data = null_compressor_get_dummy_block();\n\t\t\t}\n\t\t\trow_compressor->compressed_is_null[compressed_col] = compressed_data == NULL;\n\n\t\t\tif (compressed_data != NULL)\n\t\t\t\trow_compressor->compressed_values[compressed_col] =\n\t\t\t\t\tPointerGetDatum(compressed_data);\n\t\t}\n\t\telse if (column->segment_info != NULL)\n\t\t{\n\t\t\trow_compressor->compressed_values[compressed_col] = column->segment_info->val;\n\t\t\trow_compressor->compressed_is_null[compressed_col] = column->segment_info->is_null;\n\t\t}\n\t}\n\n\tListCell *lc;\n\tforeach (lc, row_compressor->metadata_builders)\n\t{\n\t\tBatchMetadataBuilder *builder = (BatchMetadataBuilder *) lfirst(lc);\n\t\tbuilder->insert_to_compressed_row(builder, row_compressor);\n\t}\n\n\trow_compressor->compressed_values[row_compressor->count_metadata_column_offset] =\n\t\tInt32GetDatum(row_compressor->rows_compressed_into_current_value);\n\trow_compressor->compressed_is_null[row_compressor->count_metadata_column_offset] = false;\n\n\tMemoryContextSwitchTo(old_cxt);\n\n\t/* Build the tuple on the callers memory context */\n\treturn heap_form_tuple(row_compressor->out_desc,\n\t\t\t\t\t\t   row_compressor->compressed_values,\n\t\t\t\t\t\t   row_compressor->compressed_is_null);\n}\n\nvoid\nrow_compressor_clear_batch(RowCompressor *row_compressor, bool changed_groups)\n{\n\tMemoryContext old_cxt = MemoryContextSwitchTo(row_compressor->per_row_ctx);\n\n\t/* free the compressed values now that we're done with them (the old compressor is freed in\n\t * finish()) */\n\tfor (int col = 0; col < row_compressor->n_input_columns; col++)\n\t{\n\t\tPerColumn *column = &row_compressor->per_column[col];\n\t\tint16 compressed_col;\n\t\tif (column->compressor == NULL && column->segment_info == NULL)\n\t\t\tcontinue;\n\n\t\tcompressed_col = row_compressor->uncompressed_col_to_compressed_col[col];\n\t\tAssert(compressed_col >= 0);\n\t\tif (row_compressor->compressed_is_null[compressed_col])\n\t\t\tcontinue;\n\n\t\t/* don't free the segment-bys if we've overflowed the row, we still need them */\n\t\tif (column->segment_info != NULL && !changed_groups)\n\t\t\tcontinue;\n\n\t\tif (column->compressor != NULL || !column->segment_info->typ_by_val)\n\t\t\tpfree(DatumGetPointer(row_compressor->compressed_values[compressed_col]));\n\n\t\trow_compressor->compressed_values[compressed_col] = 0;\n\t\trow_compressor->compressed_is_null[compressed_col] = true;\n\t}\n\n\tListCell *lc;\n\tforeach (lc, row_compressor->metadata_builders)\n\t{\n\t\tBatchMetadataBuilder *builder = (BatchMetadataBuilder *) lfirst(lc);\n\t\tbuilder->reset(builder, row_compressor);\n\t}\n\n\trow_compressor->rowcnt_pre_compression += row_compressor->rows_compressed_into_current_value;\n\trow_compressor->num_compressed_rows++;\n\trow_compressor->rows_compressed_into_current_value = 0;\n\n\tMemoryContextSwitchTo(old_cxt);\n\tMemoryContextReset(row_compressor->per_row_ctx);\n}\n\nstatic void\nrow_compressor_flush(RowCompressor *row_compressor, BulkWriter *writer, bool changed_groups)\n{\n\tHeapTuple compressed_tuple = row_compressor_build_tuple(row_compressor);\n\tMemoryContext old_cxt = MemoryContextSwitchTo(row_compressor->per_row_ctx);\n\n\t/* invalidate continuous aggregate range */\n\tif (row_compressor->invalidation)\n\t{\n\t\tInvalidationSettings *settings = row_compressor->invalidation;\n\t\tAttrNumber dim_attnum = AttrOffsetGetAttrNumber(settings->invalidation_column_offset);\n\n\t\tBatchMetadataBuilderMinMax *minmax_builder = NULL;\n\t\tListCell *lc;\n\t\tforeach (lc, row_compressor->metadata_builders)\n\t\t{\n\t\t\tBatchMetadataBuilder *builder = (BatchMetadataBuilder *) lfirst(lc);\n\t\t\tif (builder->builder_type == METADATA_BUILDER_MINMAX)\n\t\t\t{\n\t\t\t\tBatchMetadataBuilderMinMax *mm = (BatchMetadataBuilderMinMax *) builder;\n\t\t\t\tif (mm->attnum == dim_attnum)\n\t\t\t\t{\n\t\t\t\t\tminmax_builder = mm;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tAssert(minmax_builder != NULL);\n\t\tDatum min = row_compressor->compressed_values[minmax_builder->min_metadata_attr_offset];\n\t\tDatum max = row_compressor->compressed_values[minmax_builder->max_metadata_attr_offset];\n\t\tint64 start = ts_time_value_to_internal(min, minmax_builder->type_oid);\n\t\tint64 end = ts_time_value_to_internal(max, minmax_builder->type_oid);\n\t\tcontinuous_agg_invalidate_range(settings->hypertable_id, settings->chunk_relid, start, end);\n\t}\n\n\tAssert(writer->bistate != NULL);\n\theap_insert(writer->out_rel,\n\t\t\t\tcompressed_tuple,\n\t\t\t\twriter->mycid,\n\t\t\t\twriter->insert_options /*=options*/,\n\t\t\t\twriter->bistate);\n\tif (writer->indexstate->ri_NumIndices > 0)\n\t{\n\t\tts_catalog_index_insert(writer->indexstate, compressed_tuple);\n\t}\n\n\theap_freetuple(compressed_tuple);\n\n\tif (row_compressor->on_flush)\n\t\trow_compressor->on_flush(row_compressor,\n\t\t\t\t\t\t\t\t row_compressor->rows_compressed_into_current_value);\n\n\tMemoryContextSwitchTo(old_cxt);\n\trow_compressor_clear_batch(row_compressor, changed_groups);\n}\n\nvoid\nrow_compressor_reset(RowCompressor *row_compressor)\n{\n\trow_compressor->first_iteration = true;\n}\n\nvoid\nrow_compressor_close(RowCompressor *row_compressor)\n{\n\tpfree(row_compressor->compressed_is_null);\n\tpfree(row_compressor->compressed_values);\n\tpfree(row_compressor->per_column);\n\tpfree(row_compressor->uncompressed_col_to_compressed_col);\n\tFreeTupleDesc(row_compressor->out_desc);\n}\n\n/******************\n ** segment_info **\n ******************/\n\nSegmentInfo *\nsegment_info_new(Form_pg_attribute column_attr)\n{\n\tTypeCacheEntry *tce = lookup_type_cache(column_attr->atttypid, TYPECACHE_EQ_OPR_FINFO);\n\n\tif (!OidIsValid(tce->eq_opr_finfo.fn_oid))\n\t\telog(ERROR, \"no equality function for column \\\"%s\\\"\", NameStr(column_attr->attname));\n\n\tSegmentInfo *segment_info = palloc(sizeof(*segment_info));\n\n\t*segment_info = (SegmentInfo){\n\t\t.typlen = column_attr->attlen,\n\t\t.typ_by_val = column_attr->attbyval,\n\t};\n\n\tfmgr_info_cxt(tce->eq_opr_finfo.fn_oid, &segment_info->eq_fn, CurrentMemoryContext);\n\n\tsegment_info->eq_fcinfo = HEAP_FCINFO(2);\n\tsegment_info->collation = column_attr->attcollation;\n\tInitFunctionCallInfoData(*segment_info->eq_fcinfo,\n\t\t\t\t\t\t\t &segment_info->eq_fn /*=Flinfo*/,\n\t\t\t\t\t\t\t 2 /*=Nargs*/,\n\t\t\t\t\t\t\t column_attr->attcollation /*=Collation*/,\n\t\t\t\t\t\t\t NULL, /*=Context*/\n\t\t\t\t\t\t\t NULL  /*=ResultInfo*/\n\t);\n\n\treturn segment_info;\n}\n\nvoid\nsegment_info_update(SegmentInfo *segment_info, Datum val, bool is_null)\n{\n\tsegment_info->is_null = is_null;\n\tif (is_null)\n\t\tsegment_info->val = 0;\n\telse\n\t\tsegment_info->val = datumCopy(val, segment_info->typ_by_val, segment_info->typlen);\n}\n\nbool\nsegment_info_datum_is_in_group(SegmentInfo *segment_info, Datum datum, bool is_null)\n{\n\tDatum data_is_eq;\n\tFunctionCallInfo eq_fcinfo;\n\t/* if one of the datums is null and the other isn't, we must be in a new group */\n\tif (segment_info->is_null != is_null)\n\t\treturn false;\n\n\t/* they're both null */\n\tif (segment_info->is_null)\n\t\treturn true;\n\n\t/* neither is null, call the eq function */\n\teq_fcinfo = segment_info->eq_fcinfo;\n\n\tFC_SET_ARG(eq_fcinfo, 0, segment_info->val);\n\tFC_SET_ARG(eq_fcinfo, 1, datum);\n\n\tdata_is_eq = FunctionCallInvoke(eq_fcinfo);\n\n\tif (eq_fcinfo->isnull)\n\t\treturn false;\n\n\treturn DatumGetBool(data_is_eq);\n}\n\n/*\n * Build a map from compressed attribute numbers to non-compressed attribute\n * numbers.\n */\nstatic AttrMap *\nbuild_decompress_attrmap(const TupleDesc noncompressed_desc, const TupleDesc compressed_desc,\n\t\t\t\t\t\t AttrNumber *count_meta_attnum)\n{\n\tAttrMap *attrMap;\n\tint outnatts;\n\tint innatts;\n\tint i;\n\tint nextindesc = -1;\n\n\toutnatts = compressed_desc->natts;\n\tinnatts = noncompressed_desc->natts;\n\n\tattrMap = make_attrmap(outnatts);\n\tfor (i = 0; i < outnatts; i++)\n\t{\n\t\tForm_pg_attribute outatt = TupleDescAttr(compressed_desc, i);\n\t\tchar *attname;\n\t\tint j;\n\n\t\tif (outatt->attisdropped)\n\t\t\tcontinue;\n\n\t\tattname = NameStr(outatt->attname);\n\n\t\tif (strcmp(attname, COMPRESSION_COLUMN_METADATA_COUNT_NAME) == 0)\n\t\t{\n\t\t\t*count_meta_attnum = outatt->attnum;\n\t\t\t/* No point in mapping this attribute since meta columns are not\n\t\t\t * present in the non-compressed relation and will not be found\n\t\t\t * below anyway. */\n\t\t\tcontinue;\n\t\t}\n\t\telse if (strncmp(attname,\n\t\t\t\t\t\t COMPRESSION_COLUMN_METADATA_PREFIX,\n\t\t\t\t\t\t strlen(COMPRESSION_COLUMN_METADATA_PREFIX)) == 0)\n\t\t{\n\t\t\t/* We can skip other meta attributes as well */\n\t\t\tcontinue;\n\t\t}\n\n\t\tfor (j = 0; j < innatts; j++)\n\t\t{\n\t\t\tForm_pg_attribute inatt;\n\n\t\t\tnextindesc++;\n\t\t\tif (nextindesc >= innatts)\n\t\t\t\tnextindesc = 0;\n\n\t\t\tinatt = TupleDescAttr(noncompressed_desc, nextindesc);\n\t\t\tif (inatt->attisdropped)\n\t\t\t\tcontinue;\n\t\t\tif (strcmp(attname, NameStr(inatt->attname)) == 0)\n\t\t\t{\n\t\t\t\tattrMap->attnums[i] = inatt->attnum;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\treturn attrMap;\n}\n\nBulkWriter\nbulk_writer_build(Relation out_rel, int insert_options)\n{\n\tBulkWriter writer = {\n\t\t.out_rel = out_rel,\n\t\t.indexstate = CatalogOpenIndexes(out_rel),\n\t\t.mycid = GetCurrentCommandId(true),\n\t\t.bistate = GetBulkInsertState(),\n\t\t.estate = CreateExecutorState(),\n\t\t.insert_options = insert_options,\n\t};\n\n\treturn writer;\n}\n\nBulkWriter *\nbulk_writer_alloc(Relation out_rel, int insert_options)\n{\n\tBulkWriter *writer = palloc(sizeof(BulkWriter));\n\twriter->out_rel = out_rel;\n\twriter->indexstate = CatalogOpenIndexes(out_rel);\n\twriter->mycid = GetCurrentCommandId(true);\n\twriter->bistate = GetBulkInsertState();\n\twriter->estate = CreateExecutorState();\n\twriter->insert_options = insert_options;\n\n\treturn writer;\n}\n\nvoid\nbulk_writer_close(BulkWriter *writer)\n{\n\tFreeBulkInsertState(writer->bistate);\n\tif (writer->indexstate)\n\t\tCatalogCloseIndexes(writer->indexstate);\n\tFreeExecutorState(writer->estate);\n}\n\n/**********************\n ** decompress_chunk **\n **********************/\n\nRowDecompressor\nbuild_decompressor(const TupleDesc in_desc, const TupleDesc out_desc)\n{\n\tAttrNumber count_meta_attnum = InvalidAttrNumber;\n\tAttrMap *attrmap = build_decompress_attrmap(out_desc, in_desc, &count_meta_attnum);\n\n\tAssert(AttributeNumberIsValid(count_meta_attnum));\n\n\t/*\n\t * Use a value that is lower than the typical target batch size, so that we\n\t * properly test the reallocation logic.\n\t */\n\tconst int default_allocated_slots = 300;\n\n\tRowDecompressor decompressor = {\n\t\t.count_compressed_attindex = AttrNumberGetAttrOffset(count_meta_attnum),\n\t\t.in_desc = CreateTupleDescCopyConstr(in_desc),\n\t\t.out_desc = CreateTupleDescCopyConstr(out_desc),\n\t\t.compressed_datums = palloc(sizeof(Datum) * in_desc->natts),\n\t\t.compressed_is_nulls = palloc(sizeof(bool) * in_desc->natts),\n\n\t\t/* cache memory used to store the decompressed datums/is_null for form_tuple */\n\t\t.decompressed_datums = palloc(sizeof(Datum) * out_desc->natts),\n\t\t.decompressed_is_nulls = palloc(sizeof(bool) * out_desc->natts),\n\t\t.per_compressed_row_ctx = AllocSetContextCreate(CurrentMemoryContext,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"decompress chunk per-compressed row\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tALLOCSET_DEFAULT_SIZES),\n\t\t.decompressed_slots = (TupleTableSlot **) palloc0(sizeof(void *) * default_allocated_slots),\n\t\t.decompressed_slots_capacity = default_allocated_slots,\n\t\t.attrmap = attrmap,\n\t};\n\n\tcreate_per_compressed_column(&decompressor);\n\n\t/*\n\t * We need to make sure decompressed_is_nulls is in a defined state. While this\n\t * will get written for normal columns it will not get written for dropped columns\n\t * since dropped columns don't exist in the compressed chunk so we initialize\n\t * with true here.\n\t */\n\tmemset(decompressor.decompressed_is_nulls, true, out_desc->natts);\n\n\tdetoaster_init(&decompressor.detoaster, CurrentMemoryContext);\n\n\treturn decompressor;\n}\n\nvoid\nrow_decompressor_reset(RowDecompressor *decompressor)\n{\n\tMemoryContextReset(decompressor->per_compressed_row_ctx);\n\tdecompressor->unprocessed_tuples = 0;\n\tdecompressor->batches_decompressed = 0;\n\tdecompressor->tuples_decompressed = 0;\n}\n\nvoid\nrow_decompressor_close(RowDecompressor *decompressor)\n{\n\tMemoryContextDelete(decompressor->per_compressed_row_ctx);\n\tdetoaster_close(&decompressor->detoaster);\n\tfree_attrmap(decompressor->attrmap);\n\tFreeTupleDesc(decompressor->in_desc);\n\tFreeTupleDesc(decompressor->out_desc);\n\tpfree(decompressor->compressed_datums);\n\tpfree(decompressor->compressed_is_nulls);\n\tpfree(decompressor->decompressed_datums);\n\tpfree(decompressor->decompressed_is_nulls);\n\tpfree((void *) decompressor->decompressed_slots);\n\tpfree(decompressor->per_compressed_cols);\n}\n\nvoid\ndecompress_chunk(Oid in_table, Oid out_table)\n{\n\t/*\n\t * Locks are taken in the order uncompressed table then compressed table\n\t * for consistency with compress_chunk.\n\t * We are _just_ INSERTing into the out_table so in principle we could take\n\t * a RowExclusive lock, and let other operations read and write this table\n\t * as we work. However, we currently compress each table as a oneshot, so\n\t * we're taking the stricter lock to prevent accidents.\n\t * We want to prevent other decompressors from decompressing this table,\n\t * and we want to prevent INSERTs or UPDATEs which could mess up our decompression.\n\t * We may as well allow readers to keep reading the compressed data while\n\t * we are decompressing, so we only take an ExclusiveLock instead of AccessExclusive.\n\t */\n\tRelation out_rel = table_open(out_table, ExclusiveLock);\n\tRelation in_rel = table_open(in_table, ExclusiveLock);\n\tint64 nrows_processed = 0;\n\n\tPushActiveSnapshot(GetLatestSnapshot());\n\tBulkWriter writer = bulk_writer_build(out_rel, 0);\n\tRowDecompressor decompressor =\n\t\tbuild_decompressor(RelationGetDescr(in_rel), RelationGetDescr(out_rel));\n\tTupleTableSlot *slot = table_slot_create(in_rel, NULL);\n\tTableScanDesc scan = table_beginscan(in_rel, GetActiveSnapshot(), 0, (ScanKey) NULL);\n\tint64 report_reltuples = calculate_reltuples_to_report(in_rel->rd_rel->reltuples);\n\n\twhile (table_scan_getnextslot(scan, ForwardScanDirection, slot))\n\t{\n\t\tbool should_free;\n\t\tHeapTuple tuple = ExecFetchSlotHeapTuple(slot, false, &should_free);\n\n\t\theap_deform_tuple(tuple,\n\t\t\t\t\t\t  decompressor.in_desc,\n\t\t\t\t\t\t  decompressor.compressed_datums,\n\t\t\t\t\t\t  decompressor.compressed_is_nulls);\n\n\t\tif (should_free)\n\t\t\theap_freetuple(tuple);\n\n\t\trow_decompressor_decompress_row_to_table(&decompressor, &writer);\n\n\t\tif ((++nrows_processed % report_reltuples) == 0)\n\t\t\telog(DEBUG2,\n\t\t\t\t \"decompressed \" INT64_FORMAT \" rows from \\\"%s\\\"\",\n\t\t\t\t nrows_processed,\n\t\t\t\t RelationGetRelationName(in_rel));\n\t}\n\n\telog(DEBUG1,\n\t\t \"finished decompressing \" INT64_FORMAT \" rows from \\\"%s\\\"\",\n\t\t nrows_processed,\n\t\t RelationGetRelationName(in_rel));\n\ttable_endscan(scan);\n\tExecDropSingleTupleTableSlot(slot);\n\trow_decompressor_close(&decompressor);\n\tbulk_writer_close(&writer);\n\n\ttable_close(out_rel, NoLock);\n\ttable_close(in_rel, NoLock);\n\n\tPopActiveSnapshot();\n}\n\nstatic void\ncreate_per_compressed_column(RowDecompressor *decompressor)\n{\n\tOid compressed_data_type_oid = ts_custom_type_cache_get(CUSTOM_TYPE_COMPRESSED_DATA)->type_oid;\n\tAssert(OidIsValid(compressed_data_type_oid));\n\n\tdecompressor->per_compressed_cols =\n\t\tpalloc(sizeof(*decompressor->per_compressed_cols) * decompressor->in_desc->natts);\n\n\tAssert(OidIsValid(compressed_data_type_oid));\n\n\tfor (int col = 0; col < decompressor->in_desc->natts; col++)\n\t{\n\t\tOid decompressed_type;\n\t\tbool is_compressed;\n\t\tint16 decompressed_column_offset;\n\t\tPerCompressedColumn *per_compressed_col = &decompressor->per_compressed_cols[col];\n\t\tForm_pg_attribute compressed_attr = TupleDescAttr(decompressor->in_desc, col);\n\t\tchar *col_name = NameStr(compressed_attr->attname);\n\n\t\t/* find the mapping from compressed column to uncompressed column, setting\n\t\t * the index of columns that don't have an uncompressed version\n\t\t * (such as metadata) to -1\n\t\t * Assumption: column names are the same on compressed and\n\t\t *       uncompressed chunk.\n\t\t */\n\t\tAttrNumber decompressed_colnum = decompressor->attrmap->attnums[col];\n\n\t\tif (!AttributeNumberIsValid(decompressed_colnum))\n\t\t{\n\t\t\t*per_compressed_col = (PerCompressedColumn){\n\t\t\t\t.decompressed_column_offset = -1,\n\t\t\t};\n\t\t\tcontinue;\n\t\t}\n\n\t\tdecompressed_column_offset = AttrNumberGetAttrOffset(decompressed_colnum);\n\n\t\tdecompressed_type =\n\t\t\tTupleDescAttr(decompressor->out_desc, decompressed_column_offset)->atttypid;\n\n\t\t/* determine if the data is compressed or not */\n\t\tis_compressed = compressed_attr->atttypid == compressed_data_type_oid;\n\t\tif (!is_compressed && compressed_attr->atttypid != decompressed_type)\n\t\t\telog(ERROR,\n\t\t\t\t \"compressed table type '%s' does not match decompressed table type '%s' for \"\n\t\t\t\t \"segment-by column \\\"%s\\\"\",\n\t\t\t\t format_type_be(compressed_attr->atttypid),\n\t\t\t\t format_type_be(decompressed_type),\n\t\t\t\t col_name);\n\n\t\t*per_compressed_col = (PerCompressedColumn){\n\t\t\t.decompressed_column_offset = decompressed_column_offset,\n\t\t\t.is_compressed = is_compressed,\n\t\t\t.decompressed_type = decompressed_type,\n\t\t};\n\t}\n}\n\nstatic void\ninit_iterator(RowDecompressor *decompressor, CompressedDataHeader *header, int input_column)\n{\n\tAssert(decompressor->in_desc->natts > input_column);\n\tPerCompressedColumn *column_info = &decompressor->per_compressed_cols[input_column];\n\n\t/* Special compression block with the NULL compression algorithm,\n\t * tells that all values in the compressed block are NULLs.\n\t */\n\tif (header->compression_algorithm == COMPRESSION_ALGORITHM_NULL)\n\t{\n\t\tcolumn_info->iterator = NULL;\n\t\tdecompressor->compressed_is_nulls[input_column] = true;\n\t\tdecompressor->decompressed_is_nulls[column_info->decompressed_column_offset] = true;\n\t\treturn;\n\t}\n\n\tcolumn_info->iterator =\n\t\tdefinitions[header->compression_algorithm]\n\t\t\t.iterator_init_forward(PointerGetDatum(header), column_info->decompressed_type);\n}\n\nstatic void\ninit_batch(RowDecompressor *decompressor, AttrNumber *attnos, int num_attnos)\n{\n\t/*\n\t * Set segmentbys and compressed columns with default value.\n\t */\n\tfor (int input_column = 0; input_column < decompressor->in_desc->natts; input_column++)\n\t{\n\t\tPerCompressedColumn *column_info = &decompressor->per_compressed_cols[input_column];\n\t\tconst int output_index = column_info->decompressed_column_offset;\n\n\t\t/* Metadata column. */\n\t\tif (output_index < 0)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Segmentby column. */\n\t\tif (!column_info->is_compressed)\n\t\t{\n\t\t\tdecompressor->decompressed_datums[output_index] =\n\t\t\t\tdecompressor->compressed_datums[input_column];\n\t\t\tdecompressor->decompressed_is_nulls[output_index] =\n\t\t\t\tdecompressor->compressed_is_nulls[input_column];\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Compressed column with default value. */\n\t\tif (decompressor->compressed_is_nulls[input_column])\n\t\t{\n\t\t\tcolumn_info->iterator = NULL;\n\t\t\tdecompressor->decompressed_datums[output_index] =\n\t\t\t\tgetmissingattr(decompressor->out_desc,\n\t\t\t\t\t\t\t   output_index + 1,\n\t\t\t\t\t\t\t   &decompressor->decompressed_is_nulls[output_index]);\n\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Only initialize required columns if specified. */\n\t\tbool found = num_attnos == 0;\n\t\tfor (int i = 0; i < num_attnos; i++)\n\t\t{\n\t\t\tif (output_index == AttrNumberGetAttrOffset(attnos[i]))\n\t\t\t{\n\t\t\t\tfound = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (!found)\n\t\t{\n\t\t\tcolumn_info->iterator = NULL;\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Normal compressed column. */\n\t\tDatum compressed_datum = PointerGetDatum(\n\t\t\tdetoaster_detoast_attr_copy((struct varlena *) DatumGetPointer(\n\t\t\t\t\t\t\t\t\t\t\tdecompressor->compressed_datums[input_column]),\n\t\t\t\t\t\t\t\t\t\t&decompressor->detoaster,\n\t\t\t\t\t\t\t\t\t\tCurrentMemoryContext));\n\t\tCompressedDataHeader *header = get_compressed_data_header(compressed_datum);\n\n\t\tinit_iterator(decompressor, header, input_column);\n\t}\n}\n\n/*\n * Decompresses the current compressed batch into decompressed_slots, and returns\n * the number of rows in batch.\n */\nint\ndecompress_batch(RowDecompressor *decompressor)\n{\n\tif (decompressor->unprocessed_tuples)\n\t\treturn decompressor->unprocessed_tuples;\n\n\tMemoryContext old_ctx = MemoryContextSwitchTo(decompressor->per_compressed_row_ctx);\n\n\tinit_batch(decompressor, NULL, 0);\n\n\t/*\n\t * Set the number of batch rows from count metadata column.\n\t */\n\tconst int n_batch_rows =\n\t\tDatumGetInt32(decompressor->compressed_datums[decompressor->count_compressed_attindex]);\n\tCheckCompressedData(n_batch_rows > 0);\n\tCheckCompressedData(n_batch_rows <= GLOBAL_MAX_ROWS_PER_COMPRESSION);\n\n\t/*\n\t * Ensure decompressed_slots array is large enough for this batch.\n\t */\n\tif (n_batch_rows > decompressor->decompressed_slots_capacity)\n\t{\n\t\tint new_capacity = decompressor->decompressed_slots_capacity * 2;\n\n\t\tif (new_capacity > GLOBAL_MAX_ROWS_PER_COMPRESSION)\n\t\t{\n\t\t\tnew_capacity = GLOBAL_MAX_ROWS_PER_COMPRESSION;\n\t\t}\n\n\t\tif (new_capacity < n_batch_rows)\n\t\t{\n\t\t\tnew_capacity = n_batch_rows;\n\t\t}\n\n\t\tAssert(new_capacity <= GLOBAL_MAX_ROWS_PER_COMPRESSION);\n\n\t\tMemoryContextSwitchTo(old_ctx);\n\t\tdecompressor->decompressed_slots =\n\t\t\t(TupleTableSlot **) repalloc(decompressor->decompressed_slots,\n\t\t\t\t\t\t\t\t\t\t sizeof(void *) * new_capacity);\n\t\tmemset(decompressor->decompressed_slots + decompressor->decompressed_slots_capacity,\n\t\t\t   0,\n\t\t\t   sizeof(void *) * (new_capacity - decompressor->decompressed_slots_capacity));\n\t\tdecompressor->decompressed_slots_capacity = new_capacity;\n\t\tMemoryContextSwitchTo(decompressor->per_compressed_row_ctx);\n\t}\n\n\t/*\n\t * Decompress all compressed columns for each row of the batch.\n\t */\n\tfor (int current_row = 0; current_row < n_batch_rows; current_row++)\n\t{\n\t\tfor (int col = 0; col < decompressor->in_desc->natts; col++)\n\t\t{\n\t\t\tPerCompressedColumn *column_info = &decompressor->per_compressed_cols[col];\n\t\t\tif (column_info->iterator == NULL)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tAssert(column_info->is_compressed);\n\n\t\t\tconst int output_index = column_info->decompressed_column_offset;\n\t\t\tconst DecompressResult value = column_info->iterator->try_next(column_info->iterator);\n\t\t\tCheckCompressedData(!value.is_done);\n\t\t\tdecompressor->decompressed_datums[output_index] = value.val;\n\t\t\tdecompressor->decompressed_is_nulls[output_index] = value.is_null;\n\t\t}\n\n\t\t/*\n\t\t * Form the heap tuple for this decompressed rows and save it for later\n\t\t * processing.\n\t\t */\n\t\tif (decompressor->decompressed_slots[current_row] == NULL)\n\t\t{\n\t\t\tMemoryContextSwitchTo(old_ctx);\n\t\t\tdecompressor->decompressed_slots[current_row] =\n\t\t\t\tMakeSingleTupleTableSlot(decompressor->out_desc, &TTSOpsHeapTuple);\n\t\t\tMemoryContextSwitchTo(decompressor->per_compressed_row_ctx);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tExecClearTuple(decompressor->decompressed_slots[current_row]);\n\t\t}\n\n\t\tTupleTableSlot *decompressed_slot = decompressor->decompressed_slots[current_row];\n\n\t\tHeapTuple decompressed_tuple = heap_form_tuple(decompressor->out_desc,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   decompressor->decompressed_datums,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   decompressor->decompressed_is_nulls);\n\n\t\tExecStoreHeapTuple(decompressed_tuple, decompressed_slot, /* should_free = */ false);\n\t}\n\n\t/*\n\t * Verify that all other columns have ended, i.e. their length is consistent\n\t * with the count metadata column.\n\t */\n\tfor (int col = 0; col < decompressor->in_desc->natts; col++)\n\t{\n\t\tPerCompressedColumn *column_info = &decompressor->per_compressed_cols[col];\n\t\tif (column_info->iterator == NULL)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\t\tAssert(column_info->is_compressed);\n\t\tconst DecompressResult value = column_info->iterator->try_next(column_info->iterator);\n\t\tCheckCompressedData(value.is_done);\n\t}\n\tMemoryContextSwitchTo(old_ctx);\n\n\tdecompressor->batches_decompressed++;\n\tdecompressor->tuples_decompressed += n_batch_rows;\n\n\tdecompressor->unprocessed_tuples = n_batch_rows;\n\n\treturn n_batch_rows;\n}\n\n/*\n * Decompresses a single row from current compressed batch\n * into decompressed_values and decompressed_is_nulls based on the\n * attnos provided.\n *\n * Returns true if the row was decompressed or false if it finished the batch.\n */\nbool\ndecompress_batch_next_row(RowDecompressor *decompressor, AttrNumber *attnos, int num_attnos)\n{\n\tMemoryContext old_ctx = MemoryContextSwitchTo(decompressor->per_compressed_row_ctx);\n\n\tif (decompressor->unprocessed_tuples > 0)\n\t{\n\t\tdecompressor->unprocessed_tuples--;\n\t\tif (decompressor->unprocessed_tuples == 0)\n\t\t{\n\t\t\tMemoryContextSwitchTo(old_ctx);\n\t\t\treturn false;\n\t\t}\n\t}\n\telse\n\t{\n\t\tdecompressor->batches_decompressed++;\n\t\tinit_batch(decompressor, attnos, num_attnos);\n\n\t\t/*\n\t\t * Set the number of batch rows from count metadata column.\n\t\t */\n\t\tdecompressor->unprocessed_tuples =\n\t\t\tDatumGetInt32(decompressor->compressed_datums[decompressor->count_compressed_attindex]);\n\t\tCheckCompressedData(decompressor->unprocessed_tuples > 0);\n\t\tCheckCompressedData(decompressor->unprocessed_tuples <= GLOBAL_MAX_ROWS_PER_COMPRESSION);\n\t}\n\n\tfor (int col = 0; col < decompressor->in_desc->natts; col++)\n\t{\n\t\tPerCompressedColumn *column_info = &decompressor->per_compressed_cols[col];\n\t\tif (column_info->iterator == NULL)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\t\tAssert(column_info->is_compressed);\n\n\t\tconst int output_index = column_info->decompressed_column_offset;\n\t\tconst DecompressResult value = column_info->iterator->try_next(column_info->iterator);\n\t\tAssert(!value.is_done);\n\t\tdecompressor->decompressed_datums[output_index] = value.val;\n\t\tdecompressor->decompressed_is_nulls[output_index] = value.is_null;\n\t}\n\n\tdecompressor->tuples_decompressed++;\n\n\tMemoryContextSwitchTo(old_ctx);\n\n\treturn true;\n}\n\n/* Decompress single column using vectorized decompression */\nArrowArray *\ndecompress_single_column(RowDecompressor *decompressor, AttrNumber attno, bool *single_value)\n{\n\tint16 target_col = -1;\n\tPerCompressedColumn *column_info = NULL;\n\n\tfor (int col = 0; col < decompressor->in_desc->natts; col++)\n\t{\n\t\tcolumn_info = &decompressor->per_compressed_cols[col];\n\t\tif (!column_info->is_compressed)\n\t\t\tcontinue;\n\n\t\tif (column_info->decompressed_column_offset == AttrNumberGetAttrOffset(attno))\n\t\t{\n\t\t\ttarget_col = col;\n\t\t\tbreak;\n\t\t}\n\t}\n\tAssert(column_info && target_col > -1);\n\n\tif (decompressor->compressed_is_nulls[target_col])\n\t{\n\t\t/* Compressed column has a default value, handle it by generating\n\t\t * a single-value ArrowArray based on the default value. This will have to\n\t\t * be handled specially because of the assumption that the whole row has\n\t\t * this default value.\n\t\t */\n\t\t*single_value = true;\n\t\tbool isnull;\n\t\tDatum default_datum = getmissingattr(decompressor->out_desc, attno, &isnull);\n\n\t\treturn make_single_value_arrow(column_info->decompressed_type, default_datum, isnull);\n\t}\n\n\t*single_value = false;\n\n\tDatum compressed_datum = PointerGetDatum(\n\t\tdetoaster_detoast_attr_copy((struct varlena *) DatumGetPointer(\n\t\t\t\t\t\t\t\t\t\tdecompressor->compressed_datums[target_col]),\n\t\t\t\t\t\t\t\t\t&decompressor->detoaster,\n\t\t\t\t\t\t\t\t\tCurrentMemoryContext));\n\tCompressedDataHeader *header = get_compressed_data_header(compressed_datum);\n\n\t/* Handle NULL compression algorithm */\n\tif (header->compression_algorithm == COMPRESSION_ALGORITHM_NULL)\n\t{\n\t\t*single_value = true;\n\t\treturn make_single_value_arrow(column_info->decompressed_type, (Datum) NULL, true);\n\t}\n\n\tDecompressAllFunction decompress_all =\n\t\ttsl_get_decompress_all_function(header->compression_algorithm,\n\t\t\t\t\t\t\t\t\t\tcolumn_info->decompressed_type);\n\n\tAssert(decompress_all);\n\n\treturn decompress_all(compressed_datum,\n\t\t\t\t\t\t  column_info->decompressed_type,\n\t\t\t\t\t\t  decompressor->per_compressed_row_ctx);\n}\n\nint\nrow_decompressor_decompress_row_to_table(RowDecompressor *decompressor, BulkWriter *writer)\n{\n\tconst int n_batch_rows = decompress_batch(decompressor);\n\n\tMemoryContext old_ctx = MemoryContextSwitchTo(decompressor->per_compressed_row_ctx);\n\n\t/* Insert all decompressed rows into table using the bulk insert API. */\n\ttable_multi_insert(writer->out_rel,\n\t\t\t\t\t   decompressor->decompressed_slots,\n\t\t\t\t\t   n_batch_rows,\n\t\t\t\t\t   writer->mycid,\n\t\t\t\t\t   /* options = */ 0,\n\t\t\t\t\t   writer->bistate);\n\n\t/*\n\t * Now, update the indexes. If we have several indexes, we want to first\n\t * insert the entire batch into one index, then into another, and so on.\n\t * Working with one index at a time gives better data access locality,\n\t * which reduces the load on shared buffers cache.\n\t * The normal Postgres code inserts each row into all indexes, so to do it\n\t * the other way around, we create a temporary ResultRelInfo that only\n\t * references one index. Then we loop over indexes, and for each index we\n\t * set it to this temporary ResultRelInfo, and insert all rows into this\n\t * single index.\n\t */\n\tif (writer->indexstate->ri_NumIndices > 0)\n\t{\n\t\tResultRelInfo indexstate_copy = *writer->indexstate;\n\t\tRelation single_index_relation;\n\t\tIndexInfo *single_index_info;\n\t\tindexstate_copy.ri_NumIndices = 1;\n\t\tindexstate_copy.ri_IndexRelationDescs = &single_index_relation;\n\t\tindexstate_copy.ri_IndexRelationInfo = &single_index_info;\n\t\tfor (int i = 0; i < writer->indexstate->ri_NumIndices; i++)\n\t\t{\n\t\t\tsingle_index_relation = writer->indexstate->ri_IndexRelationDescs[i];\n\t\t\tsingle_index_info = writer->indexstate->ri_IndexRelationInfo[i];\n\t\t\tfor (int row = 0; row < n_batch_rows; row++)\n\t\t\t{\n\t\t\t\tTupleTableSlot *decompressed_slot = decompressor->decompressed_slots[row];\n\t\t\t\tEState *estate = writer->estate;\n\t\t\t\tExprContext *econtext = GetPerTupleExprContext(estate);\n\n\t\t\t\t/* Arrange for econtext's scan tuple to be the tuple under test */\n\t\t\t\tecontext->ecxt_scantuple = decompressed_slot;\n\t\t\t\tExecInsertIndexTuplesCompat(&indexstate_copy,\n\t\t\t\t\t\t\t\t\t\t\tdecompressed_slot,\n\t\t\t\t\t\t\t\t\t\t\testate,\n\t\t\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\t\t\t\t\tNIL,\n\t\t\t\t\t\t\t\t\t\t\tfalse);\n\t\t\t}\n\t\t}\n\t}\n\n\tMemoryContextSwitchTo(old_ctx);\n\trow_decompressor_reset(decompressor);\n\n\treturn n_batch_rows;\n}\n\nvoid\nrow_decompressor_decompress_row_to_tuplesort(RowDecompressor *decompressor,\n\t\t\t\t\t\t\t\t\t\t\t Tuplesortstate *tuplesortstate)\n{\n\tconst int n_batch_rows = decompress_batch(decompressor);\n\n\tMemoryContext old_ctx = MemoryContextSwitchTo(decompressor->per_compressed_row_ctx);\n\n\tfor (int i = 0; i < n_batch_rows; i++)\n\t{\n\t\ttuplesort_puttupleslot(tuplesortstate, decompressor->decompressed_slots[i]);\n\t}\n\n\tMemoryContextSwitchTo(old_ctx);\n\trow_decompressor_reset(decompressor);\n}\n\n/********************/\n/*** SQL Bindings ***/\n/********************/\n\nDatum\ntsl_compressed_data_decompress_forward(PG_FUNCTION_ARGS)\n{\n\tCompressedDataHeader *header;\n\tFuncCallContext *funcctx;\n\tMemoryContext oldcontext;\n\tDecompressionIterator *iter;\n\tDecompressResult res;\n\n\tif (PG_ARGISNULL(0))\n\t\tPG_RETURN_NULL();\n\n\tif (SRF_IS_FIRSTCALL())\n\t{\n\t\tfuncctx = SRF_FIRSTCALL_INIT();\n\t\toldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);\n\n\t\theader = get_compressed_data_header(PG_GETARG_DATUM(0));\n\n\t\titer = definitions[header->compression_algorithm]\n\t\t\t\t   .iterator_init_forward(PointerGetDatum(header),\n\t\t\t\t\t\t\t\t\t\t  get_fn_expr_argtype(fcinfo->flinfo, 1));\n\n\t\tfuncctx->user_fctx = iter;\n\t\tMemoryContextSwitchTo(oldcontext);\n\t}\n\n\tfuncctx = SRF_PERCALL_SETUP();\n\n\titer = funcctx->user_fctx;\n\tres = iter->try_next(iter);\n\n\tif (res.is_done)\n\t\tSRF_RETURN_DONE(funcctx);\n\n\tif (res.is_null)\n\t\tSRF_RETURN_NEXT_NULL(funcctx);\n\n\tSRF_RETURN_NEXT(funcctx, res.val);\n}\n\nDatum\ntsl_compressed_data_decompress_reverse(PG_FUNCTION_ARGS)\n{\n\tCompressedDataHeader *header;\n\tFuncCallContext *funcctx;\n\tMemoryContext oldcontext;\n\tDecompressionIterator *iter;\n\tDecompressResult res;\n\n\tif (PG_ARGISNULL(0))\n\t\tPG_RETURN_NULL();\n\n\tif (SRF_IS_FIRSTCALL())\n\t{\n\t\tfuncctx = SRF_FIRSTCALL_INIT();\n\t\toldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);\n\n\t\theader = get_compressed_data_header(PG_GETARG_DATUM(0));\n\n\t\titer = definitions[header->compression_algorithm]\n\t\t\t\t   .iterator_init_reverse(PointerGetDatum(header),\n\t\t\t\t\t\t\t\t\t\t  get_fn_expr_argtype(fcinfo->flinfo, 1));\n\n\t\tfuncctx->user_fctx = iter;\n\t\tMemoryContextSwitchTo(oldcontext);\n\t}\n\n\tfuncctx = SRF_PERCALL_SETUP();\n\n\titer = funcctx->user_fctx;\n\tres = iter->try_next(iter);\n\n\tif (res.is_done)\n\t\tSRF_RETURN_DONE(funcctx);\n\n\tif (res.is_null)\n\t\tSRF_RETURN_NEXT_NULL(funcctx);\n\n\tSRF_RETURN_NEXT(funcctx, res.val);\n\t;\n}\n\n/*\n * compressed_data_to_array(compressed_data, element_type) -> anyarray\n */\nDatum\ntsl_compressed_data_to_array(PG_FUNCTION_ARGS)\n{\n\tDatum compressed_data;\n\tOid element_type;\n\tArrayType *result;\n\n\tif (PG_ARGISNULL(0))\n\t\tPG_RETURN_NULL();\n\n\tcompressed_data = PG_GETARG_DATUM(0);\n\n\t/* Get element type from the second argument's type */\n\telement_type = get_fn_expr_argtype(fcinfo->flinfo, 1);\n\n\tif (!OidIsValid(element_type))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"could not determine element type\")));\n\n\t/* Initial allocation - will grow as needed */\n\tint capacity = TARGET_COMPRESSED_BATCH_SIZE;\n\tint count = 0;\n\tDatum *values = palloc(sizeof(Datum) * capacity);\n\tbool *nulls = palloc(sizeof(bool) * capacity);\n\n\t/* Get type info for array construction */\n\tint16 typlen;\n\tbool typbyval;\n\tchar typalign;\n\tget_typlenbyvalalign(element_type, &typlen, &typbyval, &typalign);\n\n\tCompressedDataHeader *header;\n\tDecompressionIterator *iter;\n\tDecompressResult res;\n\t/* Get compressed data header and validate */\n\theader = get_compressed_data_header(compressed_data);\n\n\t/* Initialize the decompression iterator */\n\titer = definitions[header->compression_algorithm].iterator_init_forward(PointerGetDatum(header),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\telement_type);\n\n\t/* Iterate through all compressed values */\n\tfor (;;)\n\t{\n\t\tres = iter->try_next(iter);\n\n\t\tif (res.is_done)\n\t\t\tbreak;\n\n\t\t/* Grow arrays if needed */\n\t\tif (count >= capacity)\n\t\t{\n\t\t\tcapacity *= 2;\n\t\t\tvalues = repalloc(values, sizeof(Datum) * capacity);\n\t\t\tnulls = repalloc(nulls, sizeof(bool) * capacity);\n\t\t}\n\n\t\tvalues[count] = res.val;\n\t\tnulls[count] = res.is_null;\n\t\tcount++;\n\t}\n\n\t/* Construct and return the PostgreSQL array */\n\tint dims[1];\n\tint lbs[1];\n\n\tdims[0] = count;\n\tlbs[0] = 1; /* 1-based indexing */\n\n\tresult = construct_md_array(values,\n\t\t\t\t\t\t\t\tnulls,\n\t\t\t\t\t\t\t\t1, /* ndims */\n\t\t\t\t\t\t\t\tdims,\n\t\t\t\t\t\t\t\tlbs,\n\t\t\t\t\t\t\t\telement_type,\n\t\t\t\t\t\t\t\ttyplen,\n\t\t\t\t\t\t\t\ttypbyval,\n\t\t\t\t\t\t\t\ttypalign);\n\tPG_RETURN_ARRAYTYPE_P(result);\n}\n\n/*\n * compressed_data_column_size(compressed_data, element_type) -> anyarray\n */\nDatum\ntsl_compressed_data_column_size(PG_FUNCTION_ARGS)\n{\n\tif (PG_ARGISNULL(0))\n\t\tPG_RETURN_NULL();\n\n\tDatum compressed_data = PG_GETARG_DATUM(0);\n\n\t/* Get element type from the second argument's type */\n\tOid element_type = get_fn_expr_argtype(fcinfo->flinfo, 1);\n\n\tif (!OidIsValid(element_type))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"could not determine element type\")));\n\n\tint16 typlen;\n\tbool typbyval pg_attribute_unused();\n\tchar typalign;\n\tget_typlenbyvalalign(element_type, &typlen, &typbyval, &typalign);\n\n\tCompressedDataHeader *header;\n\tDecompressionIterator *iter;\n\tDecompressResult res;\n\t/* Get compressed data header and validate */\n\theader = get_compressed_data_header(compressed_data);\n\n\tif (header->compression_algorithm == COMPRESSION_ALGORITHM_NULL)\n\t{\n\t\t/* All values are NULL, so size is 0 */\n\t\tPG_RETURN_INT32(0);\n\t}\n\n\t/* Initialize the decompression iterator */\n\titer = definitions[header->compression_algorithm].iterator_init_forward(PointerGetDatum(header),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\telement_type);\n\n\tint32 column_size = 0;\n\t/* Iterate through all compressed values */\n\tfor (;;)\n\t{\n\t\tres = iter->try_next(iter);\n\n\t\tif (res.is_done)\n\t\t\tbreak;\n\n\t\t/* similar to pg_column_size implementation */\n\t\tif (!res.is_null)\n\t\t{\n\t\t\tif (typlen == -1)\n\t\t\t\tcolumn_size += toast_datum_size(res.val);\n\t\t\telse if (typlen == -2)\n\t\t\t\tcolumn_size += strlen(DatumGetCString(res.val)) + 1;\n\t\t\telse\n\t\t\t\tcolumn_size += typlen;\n\n\t\t\tcolumn_size = att_align_nominal(column_size, typalign);\n\t\t}\n\t}\n\n\tPG_RETURN_INT32(column_size);\n}\n\nDatum\ntsl_compressed_data_send(PG_FUNCTION_ARGS)\n{\n\tCompressedDataHeader *header = get_compressed_data_header(PG_GETARG_DATUM(0));\n\tStringInfoData buf;\n\n\tpq_begintypsend(&buf);\n\tpq_sendbyte(&buf, header->compression_algorithm);\n\n\tif (header->compression_algorithm != COMPRESSION_ALGORITHM_NULL)\n\t{\n\t\tdefinitions[header->compression_algorithm].compressed_data_send(header, &buf);\n\t}\n\n\tPG_RETURN_BYTEA_P(pq_endtypsend(&buf));\n}\n\nDatum\ntsl_compressed_data_recv(PG_FUNCTION_ARGS)\n{\n\tStringInfo buf = (StringInfo) PG_GETARG_POINTER(0);\n\tCompressedDataHeader header = { .vl_len_ = { 0 } };\n\n\theader.compression_algorithm = pq_getmsgbyte(buf);\n\n\tif (header.compression_algorithm >= _END_COMPRESSION_ALGORITHMS)\n\t\telog(ERROR, \"invalid compression algorithm %d\", header.compression_algorithm);\n\n\treturn definitions[header.compression_algorithm].compressed_data_recv(buf);\n}\n\nextern Datum\ntsl_compressed_data_in(PG_FUNCTION_ARGS)\n{\n\tconst char *input = PG_GETARG_CSTRING(0);\n\tsize_t input_len = strlen(input);\n\tint decoded_len;\n#if PG18_GE\n\t/* With version 18 pointer type changed to uint8\n\t * for better readability.\n\t *\n\t * https://github.com/postgres/postgres/commit/b28c59a6\n\t */\n\tuint8 *decoded;\n#else\n\tchar *decoded;\n#endif\n\tStringInfoData data;\n\tDatum result;\n\n\tif (input_len > PG_INT32_MAX)\n\t\telog(ERROR, \"input too long\");\n\n\tdecoded_len = pg_b64_dec_len(input_len);\n\tdecoded = palloc(decoded_len + 1);\n\tdecoded_len = pg_b64_decode(input, input_len, decoded, decoded_len);\n\n\tif (decoded_len < 0)\n\t\telog(ERROR, \"could not decode base64-encoded compressed data\");\n\n\tdecoded[decoded_len] = '\\0';\n\tdata = (StringInfoData){\n\t\t.data = (char *) decoded,\n\t\t.len = decoded_len,\n\t\t.maxlen = decoded_len,\n\t};\n\n\tresult = DirectFunctionCall1(tsl_compressed_data_recv, PointerGetDatum(&data));\n\n\tPG_RETURN_DATUM(result);\n}\n\nextern Datum\ntsl_compressed_data_out(PG_FUNCTION_ARGS)\n{\n\tDatum bytes_data = DirectFunctionCall1(tsl_compressed_data_send, PG_GETARG_DATUM(0));\n\tbytea *bytes = DatumGetByteaP(bytes_data);\n\tint raw_len = VARSIZE_ANY_EXHDR(bytes);\n#if PG18_GE\n\t/* With version 18 pointer type changed to uint8\n\t * for better readability.\n\t *\n\t * https://github.com/postgres/postgres/commit/b28c59a6\n\t */\n\tconst uint8 *raw_data = (uint8 *) VARDATA(bytes);\n#else\n\tconst char *raw_data = VARDATA(bytes);\n#endif\n\tint encoded_len = pg_b64_enc_len(raw_len);\n\tchar *encoded = palloc(encoded_len + 1);\n\tencoded_len = pg_b64_encode(raw_data, raw_len, encoded, encoded_len);\n\n\tif (encoded_len < 0)\n\t\telog(ERROR, \"could not base64-encode compressed data\");\n\n\tencoded[encoded_len] = '\\0';\n\n\tPG_RETURN_CSTRING(encoded);\n}\n\n/* create_hypertable record attribute numbers */\nenum Anum_compressed_info\n{\n\tAnum_compressed_info_algorithm = 1,\n\tAnum_compressed_info_has_nulls,\n\t_Anum_compressed_info_max,\n};\n\n#define Natts_compressed_info (_Anum_compressed_info_max - 1)\n\nextern Datum\ntsl_compressed_data_info(PG_FUNCTION_ARGS)\n{\n\tconst CompressedDataHeader *header = get_compressed_data_header(PG_GETARG_DATUM(0));\n\tTupleDesc tupdesc;\n\tHeapTuple tuple;\n\tbool has_nulls = false;\n\n\tif (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"function returning record called in \"\n\t\t\t\t\t\t\"context that cannot accept type record\")));\n\n\tswitch (header->compression_algorithm)\n\t{\n\t\tcase COMPRESSION_ALGORITHM_GORILLA:\n\t\t\thas_nulls = gorilla_compressed_has_nulls(header);\n\t\t\tbreak;\n\t\tcase COMPRESSION_ALGORITHM_DICTIONARY:\n\t\t\thas_nulls = dictionary_compressed_has_nulls(header);\n\t\t\tbreak;\n\t\tcase COMPRESSION_ALGORITHM_DELTADELTA:\n\t\t\thas_nulls = deltadelta_compressed_has_nulls(header);\n\t\t\tbreak;\n\t\tcase COMPRESSION_ALGORITHM_ARRAY:\n\t\t\thas_nulls = array_compressed_has_nulls(header);\n\t\t\tbreak;\n\t\tcase COMPRESSION_ALGORITHM_BOOL:\n\t\t\thas_nulls = bool_compressed_has_nulls(header);\n\t\t\tbreak;\n\t\tcase COMPRESSION_ALGORITHM_NULL:\n\t\t\thas_nulls = true;\n\t\t\tbreak;\n\t\tcase COMPRESSION_ALGORITHM_UUID:\n\t\t\thas_nulls = uuid_compressed_has_nulls(header);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\telog(ERROR, \"unknown compression algorithm %d\", header->compression_algorithm);\n\t\t\tbreak;\n\t}\n\n\ttupdesc = BlessTupleDesc(tupdesc);\n\n\tDatum values[Natts_compressed_info];\n\tbool nulls[Natts_compressed_info] = { false };\n\n\tvalues[AttrNumberGetAttrOffset(Anum_compressed_info_algorithm)] =\n\t\tNameGetDatum(compression_get_algorithm_name(header->compression_algorithm));\n\tvalues[AttrNumberGetAttrOffset(Anum_compressed_info_has_nulls)] = BoolGetDatum(has_nulls);\n\ttuple = heap_form_tuple(tupdesc, values, nulls);\n\n\treturn HeapTupleGetDatum(tuple);\n}\n\nextern Datum\ntsl_compressed_data_has_nulls(PG_FUNCTION_ARGS)\n{\n\tconst CompressedDataHeader *header = get_compressed_data_header(PG_GETARG_DATUM(0));\n\tbool has_nulls = false;\n\n\tswitch (header->compression_algorithm)\n\t{\n\t\tcase COMPRESSION_ALGORITHM_GORILLA:\n\t\t\thas_nulls = gorilla_compressed_has_nulls(header);\n\t\t\tbreak;\n\t\tcase COMPRESSION_ALGORITHM_DICTIONARY:\n\t\t\thas_nulls = dictionary_compressed_has_nulls(header);\n\t\t\tbreak;\n\t\tcase COMPRESSION_ALGORITHM_DELTADELTA:\n\t\t\thas_nulls = deltadelta_compressed_has_nulls(header);\n\t\t\tbreak;\n\t\tcase COMPRESSION_ALGORITHM_ARRAY:\n\t\t\thas_nulls = array_compressed_has_nulls(header);\n\t\t\tbreak;\n\t\tcase COMPRESSION_ALGORITHM_BOOL:\n\t\t\thas_nulls = bool_compressed_has_nulls(header);\n\t\t\tbreak;\n\t\tcase COMPRESSION_ALGORITHM_NULL:\n\t\t\thas_nulls = true;\n\t\t\tbreak;\n\t\tcase COMPRESSION_ALGORITHM_UUID:\n\t\t\thas_nulls = uuid_compressed_has_nulls(header);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\telog(ERROR, \"unknown compression algorithm %d\", header->compression_algorithm);\n\t\t\tbreak;\n\t}\n\n\treturn BoolGetDatum(has_nulls);\n}\n\nextern CompressionStorage\ncompression_get_toast_storage(CompressionAlgorithm algorithm)\n{\n\tif (algorithm == _INVALID_COMPRESSION_ALGORITHM || algorithm >= _END_COMPRESSION_ALGORITHMS)\n\t\telog(ERROR, \"invalid compression algorithm %d\", algorithm);\n\treturn definitions[algorithm].compressed_data_storage;\n}\n\n/*\n * Return a default compression algorithm suitable\n * for the type. The actual algorithm used for a\n * type might be different though since the compressor\n * can deviate from the default. The actual algorithm\n * used for a specific batch can only be determined\n * by reading the batch header.\n */\nextern CompressionAlgorithm\ncompression_get_default_algorithm(Oid typeoid)\n{\n\tswitch (typeoid)\n\t{\n\t\tcase INT4OID:\n\t\tcase INT2OID:\n\t\tcase INT8OID:\n\t\tcase DATEOID:\n\t\tcase TIMESTAMPOID:\n\t\tcase TIMESTAMPTZOID:\n\t\t\treturn COMPRESSION_ALGORITHM_DELTADELTA;\n\n\t\tcase FLOAT4OID:\n\t\tcase FLOAT8OID:\n\t\t\treturn COMPRESSION_ALGORITHM_GORILLA;\n\n\t\tcase NUMERICOID:\n\t\t\treturn COMPRESSION_ALGORITHM_ARRAY;\n\n\t\tcase BOOLOID:\n\t\t\tif (ts_guc_enable_bool_compression)\n\t\t\t\treturn COMPRESSION_ALGORITHM_BOOL;\n\t\t\telse\n\t\t\t\treturn COMPRESSION_ALGORITHM_ARRAY;\n\n\t\tcase UUIDOID:\n\t\t\tif (ts_guc_enable_uuid_compression)\n\t\t\t\treturn COMPRESSION_ALGORITHM_UUID;\n\t\t\telse\n\t\t\t\treturn COMPRESSION_ALGORITHM_DICTIONARY;\n\n\t\tdefault:\n\t\t{\n\t\t\t/* use dictionary if possible, otherwise use array */\n\t\t\tTypeCacheEntry *tentry =\n\t\t\t\tlookup_type_cache(typeoid, TYPECACHE_EQ_OPR_FINFO | TYPECACHE_HASH_PROC_FINFO);\n\t\t\tif (tentry->hash_proc_finfo.fn_addr == NULL || tentry->eq_opr_finfo.fn_addr == NULL)\n\t\t\t\treturn COMPRESSION_ALGORITHM_ARRAY;\n\t\t\treturn COMPRESSION_ALGORITHM_DICTIONARY;\n\t\t}\n\t}\n}\n\nconst CompressionAlgorithmDefinition *\nalgorithm_definition(CompressionAlgorithm algo)\n{\n\tAssert(algo > 0 && algo < _END_COMPRESSION_ALGORITHMS);\n\treturn &definitions[algo];\n}\n"
  },
  {
    "path": "tsl/src/compression/compression.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <access/attnum.h>\n#include <catalog/indexing.h>\n#include <executor/tuptable.h>\n#include <fmgr.h>\n#include <lib/stringinfo.h>\n#include <nodes/execnodes.h>\n#include <utils/relcache.h>\n\ntypedef struct BulkInsertStateData *BulkInsertState;\n\n#include \"batch_metadata_builder_minmax.h\"\n#include \"hypertable.h\"\n#include \"nodes/columnar_scan/detoaster.h\"\n#include \"ts_catalog/compression_settings.h\"\n\n/*\n * Compressed data starts with a specialized varlen type starting with the usual\n * varlen header, and followed by a version specifying which compression\n * algorithm was used. This allows us to share the same code across different\n * SQL datatypes. Currently we only allow 127 versions, as we may want to use\n * variable-width integer type in the event we have more than a non-trivial\n * number of compression algorithms.\n */\n#define CompressedDataHeaderFields                                                                 \\\n\tchar vl_len_[4];                                                                               \\\n\tuint8 compression_algorithm\n\ntypedef struct CompressedDataHeader\n{\n\tCompressedDataHeaderFields;\n} CompressedDataHeader;\n\n/* On 32-bit architectures, 64-bit values are boxed when returned as datums. To avoid\nthis overhead we have this type and corresponding iterators for efficiency. The iterators\nare private to the compression algorithms for now. */\ntypedef uint64 DecompressDataInternal;\n\ntypedef struct DecompressResultInternal\n{\n\tDecompressDataInternal val;\n\tbool is_null;\n\tbool is_done;\n} DecompressResultInternal;\n\n/* This type returns datums and is used as our main interface */\ntypedef struct DecompressResult\n{\n\tDatum val;\n\tbool is_null;\n\tbool is_done;\n} DecompressResult;\n\ntypedef struct FormData_hypertable ChunkCompressionSettings;\n\ntypedef struct Compressor Compressor;\nstruct Compressor\n{\n\tvoid (*append_null)(Compressor *compressord);\n\tvoid (*append_val)(Compressor *compressor, Datum val);\n\tbool (*is_full)(Compressor *compressor, Datum val);\n\tvoid *(*finish)(Compressor *data);\n};\n\ntypedef struct ArrowArray ArrowArray;\n\ntypedef struct DecompressionIterator\n{\n\tuint8 compression_algorithm;\n\tbool forward;\n\n\tOid element_type;\n\tDecompressResult (*try_next)(struct DecompressionIterator *);\n} DecompressionIterator;\n\ntypedef struct SegmentInfo\n{\n\tDatum val;\n\tFmgrInfo eq_fn;\n\tFunctionCallInfo eq_fcinfo;\n\tint16 typlen;\n\tbool is_null;\n\tbool typ_by_val;\n\tOid collation;\n} SegmentInfo;\n\n/* this struct holds information about a segmentby column,\n * and additionally stores the offset for this column in\n * the chunk. */\ntypedef struct CompressedSegmentInfo\n{\n\tSegmentInfo *segment_info;\n\tint16 chunk_offset;\n} CompressedSegmentInfo;\n\ntypedef struct PerCompressedColumn\n{\n\tOid decompressed_type;\n\n\t/* the compressor to use for compressed columns, always NULL for segmenters\n\t * only use if is_compressed\n\t */\n\tDecompressionIterator *iterator;\n\n\t/* is this a compressed column or a segment-by column */\n\tbool is_compressed;\n\n\t/*\n\t * the index in the decompressed table of the data -1,\n\t * if the data is metadata not found in the decompressed table\n\t */\n\tint16 decompressed_column_offset;\n} PerCompressedColumn;\n\ntypedef struct BulkWriter\n{\n\tRelation out_rel;\n\tCatalogIndexState indexstate;\n\tEState *estate;\n\tCommandId mycid;\n\tBulkInsertState bistate;\n\tint insert_options; /* heap insert options */\n} BulkWriter;\n\ntypedef struct RowDecompressor\n{\n\tPerCompressedColumn *per_compressed_cols;\n\tint16 count_compressed_attindex;\n\n\tTupleDesc in_desc;\n\n\tTupleDesc out_desc;\n\tDatum *compressed_datums;\n\tbool *compressed_is_nulls;\n\n\tDatum *decompressed_datums;\n\tbool *decompressed_is_nulls;\n\n\tMemoryContext per_compressed_row_ctx;\n\tint64 batches_decompressed;\n\tint64 tuples_decompressed;\n\n\tTupleTableSlot **decompressed_slots;\n\tint decompressed_slots_capacity;\n\tint unprocessed_tuples;\n\tAttrMap *attrmap;\n\n\tDetoaster detoaster;\n} RowDecompressor;\n\n/*\n * TOAST_STORAGE_EXTENDED for out of line storage.\n * TOAST_STORAGE_EXTERNAL for out of line storage + native PG toast compression\n * used when you want to enable postgres native toast\n * compression on the output of the compression algorithm.\n */\ntypedef enum\n{\n\tTOAST_STORAGE_EXTERNAL,\n\tTOAST_STORAGE_EXTENDED\n} CompressionStorage;\n\ntypedef DecompressionIterator *(*DecompressionInitializer)(Datum, Oid);\ntypedef ArrowArray *(*DecompressAllFunction)(Datum compressed, Oid element_type,\n\t\t\t\t\t\t\t\t\t\t\t MemoryContext dest_mctx);\n\ntypedef struct CompressionAlgorithmDefinition\n{\n\tDecompressionInitializer iterator_init_forward;\n\tDecompressionInitializer iterator_init_reverse;\n\tDecompressAllFunction decompress_all;\n\tvoid (*compressed_data_send)(CompressedDataHeader *, StringInfo);\n\tDatum (*compressed_data_recv)(StringInfo);\n\n\tCompressor *(*compressor_for_type)(Oid element_type);\n\tCompressionStorage compressed_data_storage;\n} CompressionAlgorithmDefinition;\n\ntypedef enum CompressionAlgorithm\n{\n\t/* Not a real algorithm, if this does get used, it's a bug in the code */\n\t_INVALID_COMPRESSION_ALGORITHM = 0,\n\n\tCOMPRESSION_ALGORITHM_ARRAY,\n\tCOMPRESSION_ALGORITHM_DICTIONARY,\n\tCOMPRESSION_ALGORITHM_GORILLA,\n\tCOMPRESSION_ALGORITHM_DELTADELTA,\n\tCOMPRESSION_ALGORITHM_BOOL,\n\tCOMPRESSION_ALGORITHM_NULL,\n\tCOMPRESSION_ALGORITHM_UUID,\n\n\t/* When adding an algorithm also add a static assert statement below */\n\t/* end of real values */\n\t_END_COMPRESSION_ALGORITHMS,\n\t_MAX_NUM_COMPRESSION_ALGORITHMS = 128,\n} CompressionAlgorithm;\n\ntypedef struct CompressionStats\n{\n\tint64 rowcnt_pre_compression;\n\tint64 rowcnt_post_compression;\n\tint64 rowcnt_frozen;\n} CompressionStats;\n\ntypedef struct PerColumn\n{\n\t/* the compressor to use for regular columns, NULL for segmenters */\n\tCompressor *compressor;\n\n\t/* segment info; only used if compressor is NULL */\n\tSegmentInfo *segment_info;\n\tint16 segmentby_column_index;\n} PerColumn;\n\ntypedef struct InvalidationSettings\n{\n\tint32 hypertable_id;\n\tOid chunk_relid;\n\tAttrNumber invalidation_column_offset;\n} InvalidationSettings;\n\ntypedef struct RowCompressor\n{\n\t/* memory context reset per-row is stored */\n\tMemoryContext per_row_ctx;\n\n\t/* The descriptor of the uncompressed tuple we're processing */\n\tTupleDesc in_desc;\n\t/* The descriptor of the compressed tuple we're generating */\n\tTupleDesc out_desc;\n\n\t/* in theory we could have more input columns than outputted ones, so we\n\t   store the number of inputs/compressors separately */\n\tint n_input_columns;\n\n\t/* info about each column */\n\tstruct PerColumn *per_column;\n\t/* do we have to check if compressors can accept more data */\n\tbool needs_fullness_check;\n\n\t/* the order of columns in the compressed data need not match the order in the\n\t * uncompressed. This array maps each attribute offset in the uncompressed\n\t * data to the corresponding one in the compressed\n\t */\n\tint16 *uncompressed_col_to_compressed_col;\n\tint16 count_metadata_column_offset;\n\n\t/* for continuous aggregate invalidation */\n\tInvalidationSettings *invalidation;\n\n\t/* the number of uncompressed rows compressed into the current compressed row */\n\tuint32 rows_compressed_into_current_value;\n\n\t/* cached arrays used to build the HeapTuple */\n\tDatum *compressed_values;\n\tbool *compressed_is_null;\n\tint64 rowcnt_pre_compression;\n\tint64 num_compressed_rows;\n\t/* flag for checking if we are working on the first tuple */\n\tbool first_iteration;\n\n\t/* Callback called on every flush. The ntuples argument is the number of\n\t * tuples flushed. Typically used for progress reporting. */\n\tvoid (*on_flush)(struct RowCompressor *rowcompress, uint64 ntuples);\n\n\tTuplesortstate *sort_state;\n\tint64 tuples_to_sort;\t/* number of tuples to sort with tuplesort */\n\tint64 tuple_sort_limit; /* number of tuples to flush the compressor on */\n\n\tList *metadata_builders; /* List of BatchMetadataBuilder */\n} RowCompressor;\n\n/*\n * BatchFilter is used for filtering batches before decompressing.\n * The columns will either be segmentby columns or the corresponding\n * metadata columns of orderby columns.\n */\ntypedef struct BatchFilter\n{\n\t/* Column which we use for filtering */\n\tNameData column_name;\n\t/* Filter operation used */\n\tStrategyNumber strategy;\n\t/* Collation to be used by the operator */\n\tOid collation;\n\t/* Operator code used */\n\tRegProcedure opcode;\n\t/* Value to compare with */\n\tConst *value;\n\t/* IS NULL or IS NOT NULL */\n\tbool is_null_check;\n\tbool is_null;\n\tbool is_array_op;\n} BatchFilter;\n\nextern Datum tsl_compressed_data_decompress_forward(PG_FUNCTION_ARGS);\nextern Datum tsl_compressed_data_decompress_reverse(PG_FUNCTION_ARGS);\nextern Datum tsl_compressed_data_send(PG_FUNCTION_ARGS);\nextern Datum tsl_compressed_data_recv(PG_FUNCTION_ARGS);\nextern Datum tsl_compressed_data_in(PG_FUNCTION_ARGS);\nextern Datum tsl_compressed_data_out(PG_FUNCTION_ARGS);\nextern Datum tsl_compressed_data_info(PG_FUNCTION_ARGS);\nextern Datum tsl_compressed_data_has_nulls(PG_FUNCTION_ARGS);\nextern Datum tsl_compressed_data_column_size(PG_FUNCTION_ARGS);\nextern Datum tsl_compressed_data_to_array(PG_FUNCTION_ARGS);\n\nstatic void\npg_attribute_unused() assert_num_compression_algorithms_sane(void)\n{\n\t/* make sure not too many compression algorithms   */\n\tStaticAssertStmt(_END_COMPRESSION_ALGORITHMS <= _MAX_NUM_COMPRESSION_ALGORITHMS,\n\t\t\t\t\t \"Too many compression algorithms, make sure a decision on variable-length \"\n\t\t\t\t\t \"version field has been made.\");\n\n\t/* existing indexes that MUST NEVER CHANGE */\n\tStaticAssertStmt(COMPRESSION_ALGORITHM_ARRAY == 1, \"algorithm index has changed\");\n\tStaticAssertStmt(COMPRESSION_ALGORITHM_DICTIONARY == 2, \"algorithm index has changed\");\n\tStaticAssertStmt(COMPRESSION_ALGORITHM_GORILLA == 3, \"algorithm index has changed\");\n\tStaticAssertStmt(COMPRESSION_ALGORITHM_DELTADELTA == 4, \"algorithm index has changed\");\n\tStaticAssertStmt(COMPRESSION_ALGORITHM_BOOL == 5, \"algorithm index has changed\");\n\tStaticAssertStmt(COMPRESSION_ALGORITHM_NULL == 6, \"algorithm index has changed\");\n\tStaticAssertStmt(COMPRESSION_ALGORITHM_UUID == 7, \"algorithm index has changed\");\n\n\t/*\n\t * This should change when adding a new algorithm after adding the new\n\t * algorithm to the assert list above. This statement prevents adding a\n\t * new algorithm without updating the asserts above\n\t */\n\tStaticAssertStmt(_END_COMPRESSION_ALGORITHMS == 8,\n\t\t\t\t\t \"number of algorithms have changed, the asserts should be updated\");\n}\n\nextern Name compression_get_algorithm_name(CompressionAlgorithm alg);\nextern CompressionStorage compression_get_toast_storage(CompressionAlgorithm algo);\nextern CompressionAlgorithm compression_get_default_algorithm(Oid typeoid);\n\nextern CompressionStats compress_chunk(Oid in_table, Oid out_table, int insert_options);\nextern void decompress_chunk(Oid in_table, Oid out_table);\n\nextern DecompressionIterator *(*tsl_get_decompression_iterator_init(\n\tCompressionAlgorithm algorithm, bool reverse))(Datum, Oid element_type);\n\nextern DecompressAllFunction tsl_get_decompress_all_function(CompressionAlgorithm algorithm,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t Oid type);\n\ntypedef struct Chunk Chunk;\ntypedef struct ChunkInsertState ChunkInsertState;\nextern void decompress_batches_for_insert(ChunkInsertState *cis, TupleTableSlot *slot);\nextern void init_decompress_state_for_insert(ChunkInsertState *cis, TupleTableSlot *slot);\ntypedef struct ModifyHypertableState ModifyHypertableState;\nextern bool decompress_target_segments(ModifyHypertableState *ht_state);\n/* CompressSingleRowState methods */\nstruct CompressSingleRowState;\ntypedef struct CompressSingleRowState CompressSingleRowState;\n\nextern CompressSingleRowState *compress_row_init(int srcht_id, Relation in_rel, Relation out_rel);\nextern SegmentInfo *segment_info_new(Form_pg_attribute column_attr);\nextern bool segment_info_datum_is_in_group(SegmentInfo *segment_info, Datum datum, bool is_null);\nextern TupleTableSlot *compress_row_exec(CompressSingleRowState *cr, TupleTableSlot *slot);\nextern void compress_row_end(CompressSingleRowState *cr);\nextern void compress_row_destroy(CompressSingleRowState *cr);\nextern int row_decompressor_decompress_row_to_table(RowDecompressor *row_decompressor,\n\t\t\t\t\t\t\t\t\t\t\t\t\tBulkWriter *writer);\nextern void row_decompressor_decompress_row_to_tuplesort(RowDecompressor *row_decompressor,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t Tuplesortstate *tuplesortstate);\nextern void compress_chunk_populate_sort_info_for_column(const CompressionSettings *settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t Oid table, const char *attname,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t AttrNumber *att_nums, Oid *sort_operator,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t Oid *collation, bool *nulls_first);\nextern Tuplesortstate *compression_create_tuplesort_state(CompressionSettings *settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  Relation rel);\nextern void row_compressor_init(RowCompressor *row_compressor, const CompressionSettings *settings,\n\t\t\t\t\t\t\t\tconst TupleDesc noncompressed_tupdesc,\n\t\t\t\t\t\t\t\tconst TupleDesc compressed_tupdesc);\n\nextern RowCompressor *tsl_compressor_init(Relation in_rel, BulkWriter **bulk_writer, bool sort,\n\t\t\t\t\t\t\t\t\t\t  int tuple_sort_limit);\nextern void tsl_compressor_set_invalidation(RowCompressor *compressor, Hypertable *ht,\n\t\t\t\t\t\t\t\t\t\t\tOid chunk_relid);\nextern void tsl_compressor_add_slot(RowCompressor *compressor, BulkWriter *bulk_writer,\n\t\t\t\t\t\t\t\t\tTupleTableSlot *slot);\nextern void tsl_compressor_flush(RowCompressor *compressor, BulkWriter *bulk_writer);\nextern void tsl_compressor_free(RowCompressor *compressor, BulkWriter *bulk_writer);\n\nextern void row_compressor_reset(RowCompressor *row_compressor);\nextern void row_compressor_close(RowCompressor *row_compressor);\nextern HeapTuple row_compressor_build_tuple(RowCompressor *row_compressor);\nextern void row_compressor_clear_batch(RowCompressor *row_compressor, bool changed_groups);\nextern void row_compressor_append_ordered_slot(RowCompressor *row_compressor, TupleTableSlot *slot);\nextern void row_compressor_append_sorted_rows(RowCompressor *row_compressor,\n\t\t\t\t\t\t\t\t\t\t\t  Tuplesortstate *sorted_rel, Relation in_rel,\n\t\t\t\t\t\t\t\t\t\t\t  BulkWriter *writer);\nextern Oid get_compressed_chunk_index(ResultRelInfo *resultRelInfo,\n\t\t\t\t\t\t\t\t\t  const CompressionSettings *settings);\n\nextern void segment_info_update(SegmentInfo *segment_info, Datum val, bool is_null);\n\nextern BulkWriter bulk_writer_build(Relation out_rel, int insert_options);\nextern BulkWriter *bulk_writer_alloc(Relation out_rel, int insert_options);\nextern void bulk_writer_close(BulkWriter *writer);\nextern RowDecompressor build_decompressor(const TupleDesc in_desc, const TupleDesc out_desc);\n\nextern void row_decompressor_reset(RowDecompressor *decompressor);\nextern void row_decompressor_close(RowDecompressor *decompressor);\nextern enum CompressionAlgorithms compress_get_default_algorithm(Oid typeoid);\nextern int decompress_batch(RowDecompressor *decompressor);\nextern bool decompress_batch_next_row(RowDecompressor *decompressor, AttrNumber *attnos,\n\t\t\t\t\t\t\t\t\t  int num_attnos);\nextern ArrowArray *decompress_single_column(RowDecompressor *decompressor, AttrNumber attno,\n\t\t\t\t\t\t\t\t\t\t\tbool *single_value);\n/*\n * A convenience macro to throw an error about the corrupted compressed data, if\n * the argument is false. When fuzzing is enabled, we don't show the message not\n * to pollute the logs.\n */\n#ifndef TS_COMPRESSION_FUZZING\n#define CORRUPT_DATA_MESSAGE(X)                                                                    \\\n\t(errmsg(\"the compressed data is corrupt\"), errdetail(\"%s\", X), errcode(ERRCODE_DATA_CORRUPTED))\n#else\n#define CORRUPT_DATA_MESSAGE(X) (errcode(ERRCODE_DATA_CORRUPTED))\n#endif\n\n#define CheckCompressedData(X)                                                                     \\\n\tif (unlikely(!(X)))                                                                            \\\n\tereport(ERROR, CORRUPT_DATA_MESSAGE(#X))\n\ninline static void *\nconsumeCompressedData(StringInfo si, int bytes)\n{\n\tCheckCompressedData(bytes >= 0);\n\tCheckCompressedData(si->cursor + bytes >= si->cursor); /* Check for overflow. */\n\tCheckCompressedData(si->cursor + bytes <= si->len);\n\n\tvoid *result = si->data + si->cursor;\n\tsi->cursor += bytes;\n\treturn result;\n}\n\nconst CompressionAlgorithmDefinition *algorithm_definition(CompressionAlgorithm algo);\n\nstruct decompress_batches_stats\n{\n\tint64 batches_deleted;\n\tint64 batches_decompressed;\n\tint64 batches_scanned;\n\tint64 batches_checked_by_bloom;\n\tint64 batches_pruned_by_bloom;\n\tint64 batches_without_bloom;\n\tint64 batches_bloom_false_positives;\n\tint64 tuples_decompressed;\n\tint64 tuples_deleted;\n\tint64 batches_filtered_compressed;\n\tint64 batches_filtered_decompressed;\n};\n"
  },
  {
    "path": "tsl/src/compression/compression_dml.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/genam.h>\n#include <access/sdir.h>\n#include <access/tableam.h>\n#include <access/valid.h>\n#include <catalog/pg_am.h>\n#include <nodes/nodeFuncs.h>\n#include <optimizer/optimizer.h>\n#include <parser/parse_coerce.h>\n#include <parser/parse_relation.h>\n#include <parser/parsetree.h>\n#include <utils/datum.h>\n#include <utils/lsyscache.h>\n#include <utils/relcache.h>\n#include <utils/snapmgr.h>\n#include <utils/typcache.h>\n\n#include <compat/compat.h>\n#include \"foreach_ptr.h\"\n#include <chunk_insert_state.h>\n#include <compression/arrow_c_data_interface.h>\n#include <compression/compression.h>\n#include <compression/compression_dml.h>\n#include <compression/create.h>\n#include <compression/sparse_index_bloom1.h>\n#include <compression/wal_utils.h>\n#include <continuous_aggs/insert.h>\n#include <expression_utils.h>\n#include <indexing.h>\n#include <nodes/columnar_scan/vector_dict.h>\n#include <nodes/columnar_scan/vector_predicates.h>\n#include <nodes/modify_hypertable.h>\n#include <ts_catalog/array_utils.h>\n\n/*\n * Context for tracking continuous aggregate invalidation during direct batch delete.\n * When batches are deleted without decompression, we need to track the\n * time range covered by deleted batches to properly invalidate any\n * continuous aggregates.\n */\ntypedef struct InvalidationContext\n{\n\tint32 hypertable_id;\n\tOid chunk_relid;\n\tOid time_type_oid;\n\tAttrNumber min_time_attno; /* compressed chunk column for time min */\n\tAttrNumber max_time_attno; /* compressed chunk column for time max */\n} InvalidationContext;\n\ntypedef BatchQualSummary(BatchMatcher)(RowDecompressor *decompressor, ScanKeyData *scankeys,\n\t\t\t\t\t\t\t\t\t   int num_scankeys, tuple_filtering_constraints *constraints,\n\t\t\t\t\t\t\t\t\t   bool check_full_match, bool *skip_current_tuple);\n\nstatic struct decompress_batches_stats\ndecompress_batches_scan(Relation in_rel, Relation out_rel, Relation index_rel, Snapshot snapshot,\n\t\t\t\t\t\tbool *skip_current_tuple, bool delete_only, List *is_nulls,\n\t\t\t\t\t\tInvalidationContext *invalidation_ctx, CachedDecompressionState *cdst,\n\t\t\t\t\t\tTupleTableSlot *insert_slot);\n\nstatic BatchQualSummary batch_matches(RowDecompressor *decompressor, ScanKeyData *scankeys,\n\t\t\t\t\t\t\t\t\t  int num_scankeys, tuple_filtering_constraints *constraints,\n\t\t\t\t\t\t\t\t\t  bool check_full_match, bool *skip_current_tuple);\nstatic BatchQualSummary batch_matches_vectorized(RowDecompressor *decompressor,\n\t\t\t\t\t\t\t\t\t\t\t\t ScanKeyData *scankeys, int num_scankeys,\n\t\t\t\t\t\t\t\t\t\t\t\t tuple_filtering_constraints *constraints,\n\t\t\t\t\t\t\t\t\t\t\t\t bool check_full_match, bool *skip_current_tuple);\nstatic void process_predicates(Chunk *ch, CompressionSettings *settings, List *predicates,\n\t\t\t\t\t\t\t   ScanKeyData **mem_scankeys, int *num_mem_scankeys,\n\t\t\t\t\t\t\t   List **heap_filters, List **index_filters, List **is_null,\n\t\t\t\t\t\t\t   List **bloom_filters);\nstatic Relation find_matching_index(Relation comp_chunk_rel, List **index_filters,\n\t\t\t\t\t\t\t\t\tList **heap_filters);\nstatic tuple_filtering_constraints *get_batch_keys_for_unique_constraints(Relation relation);\nstatic BatchFilter *make_batchfilter(char *column_name, StrategyNumber strategy, Oid collation,\n\t\t\t\t\t\t\t\t\t RegProcedure opcode, Const *value, bool is_null_check,\n\t\t\t\t\t\t\t\t\t bool is_null, bool is_array_op);\nstatic void report_error(TM_Result result);\n\nstatic bool key_column_is_null(tuple_filtering_constraints *constraints, Relation chunk_rel,\n\t\t\t\t\t\t\t   Oid ht_relid, TupleTableSlot *slot);\nstatic bool can_delete_without_decompression(ModifyHypertableState *ht_state,\n\t\t\t\t\t\t\t\t\t\t\t CompressionSettings *settings, Chunk *chunk,\n\t\t\t\t\t\t\t\t\t\t\t List *predicates);\nstatic bool can_vectorize_constraint_checks(tuple_filtering_constraints *constraints,\n\t\t\t\t\t\t\t\t\t\t\tCompressionSettings *settings, Relation chunk_rel,\n\t\t\t\t\t\t\t\t\t\t\tOid ht_relid, ScanKeyWithAttnos *mem_scankeys);\nstatic void update_scankeys(ScanKeyWithAttnos *scankeys, TupleTableSlot *slot, int null_flags);\nstatic void init_upsert_bloom_state(ChunkInsertState *cis);\nstatic Bitmapset *get_arbiter_index_attnums(ChunkInsertState *cis);\n\nstatic AttrNumber\nTupleDescGetAttrNumber(TupleDesc desc, const char *name)\n{\n\tfor (int i = 0; i < desc->natts; i++)\n\t{\n\t\tif (strcmp(name, NameStr(TupleDescAttr(desc, i)->attname)) == 0)\n\t\t\treturn TupleDescAttr(desc, i)->attnum;\n\t}\n\n\treturn InvalidAttrNumber;\n}\n\ntypedef struct MatchedBloom\n{\n\tchar *column_name;\n\tBitmapset *attnums;\n\tAttrNumber compressed_attnum;\n\tint num_cols;\n} MatchedBloom;\n\n/*\n * Pre-computed bloom filter check for UPDATE/DELETE batch pruning.\n * The hash is computed once in process_predicates() and checked\n * per batch in decompress_batches_scan() via bloom1_contains_hash().\n */\ntypedef struct BloomFilterCheck\n{\n\tAttrNumber bloom_attno; /* attnum of bloom metadata column in compressed chunk */\n\tuint64 hash;\t\t\t/* pre-computed hash of the search value(s) */\n\tint num_columns;\t\t/* number of columns in the bloom filter (for sort order) */\n} BloomFilterCheck;\n\n/*\n * Comparator for list_sort(): order BloomFilterCheck by column count\n * in descending order, assuming this reflects the selectivity order.\n */\nstatic int\nbloom_filter_check_cmp(const ListCell *a, const ListCell *b)\n{\n\tBloomFilterCheck *ca = lfirst(a);\n\tBloomFilterCheck *cb = lfirst(b);\n\t/* Descending order: more columns first */\n\treturn cb->num_columns - ca->num_columns;\n}\n\n/*\n * Collects equality predicates during process_predicates() for the\n * post-loop bloom matching pass. Type OIDs are resolved via\n * get_atttype() at hash computation time.\n */\ntypedef struct EqualityPredicate\n{\n\tAttrNumber attno; /* column attno in uncompressed chunk */\n\tDatum constvalue; /* the constant value from WHERE col = <value> */\n} EqualityPredicate;\n\n/*\n * Get arbiter index column attnums from the arbiter index list.\n */\nstatic Bitmapset *\nget_arbiter_index_attnums(ChunkInsertState *cis)\n{\n\tAssert(cis != NULL);\n\tAssert(cis->result_relation_info != NULL);\n\tList *arbiterIndexes = cis->result_relation_info->ri_onConflictArbiterIndexes;\n\tif (arbiterIndexes == NIL)\n\t\treturn NULL;\n\n\tOid arbiter_oid = linitial_oid(arbiterIndexes);\n\tRelation index_rel = index_open(arbiter_oid, AccessShareLock);\n\n\tBitmapset *attnums = NULL;\n\tfor (int i = 0; i < index_rel->rd_index->indnkeyatts; i++)\n\t{\n\t\tAttrNumber attno = index_rel->rd_index->indkey.values[i];\n\t\tif (!AttributeNumberIsValid(attno))\n\t\t{\n\t\t\t/* Expression index - can't use bloom optimization */\n\t\t\tindex_close(index_rel, AccessShareLock);\n\t\t\treturn NULL;\n\t\t}\n\t\tattnums = bms_add_member(attnums, attno);\n\t}\n\n\tindex_close(index_rel, AccessShareLock);\n\treturn attnums;\n}\n\n/*\n * Per-chunk initialization of UPSERT bloom state. Called once per chunk in\n * init_decompress_state_for_insert(), inside the has_primary_or_unique_index block. The result is\n * cached in CachedDecompressionState via ChunkInsertState in subspace_store.\n *\n * It assumes cdst->compression_settings is already looked up for the chunk.\n *\n * Discovers which bloom columns match arbiter index columns, that is, being a subset of the\n * conflict columns. Builds the mapping from bloom columns to INSERT tuple attnums, and resolves\n * bloom column names to compressed chunk attnums. The chosen bloom filter is stored in the\n * CachedDecompressionState struct.\n */\nstatic void\ninit_upsert_bloom_state(ChunkInsertState *cis)\n{\n\tBitmapset *conflict_attnums = get_arbiter_index_attnums(cis);\n\tCachedDecompressionState *cdst = cis->cached_decompression_state;\n\tAssert(cdst != NULL);\n\tif (cdst == NULL || conflict_attnums == NULL)\n\t\treturn;\n\n\tCompressionSettings *settings = cdst->compression_settings;\n\tAssert(settings != NULL);\n\tif (settings == NULL || settings->fd.index == NULL)\n\t\treturn;\n\n\tOid compressed_relid = settings->fd.compress_relid;\n\n\tSparseIndexSettings *parsed = ts_convert_to_sparse_index_settings(settings->fd.index);\n\tAssert(parsed != NULL);\n\tif (parsed == NULL)\n\t\treturn;\n\n\t/* Map the bloom column names to hypertable attnums, because the bloom columns\n\t * will be built based on the insert tuple attnums which are the hypertable attnums. */\n\tTsBmsList per_column_attnos =\n\t\tts_resolve_columns_to_attnos_from_parsed_settings(parsed, cis->hypertable_relid);\n\n\tAssert(list_length(per_column_attnos) == list_length(parsed->objects));\n\tAssert(list_length(per_column_attnos) > 0);\n\tMatchedBloom best_match = { .num_cols = 0 };\n\n\t/** Parallel iteration over objects and their resolved attnums. */\n\tListCell *obj_cell;\n\tListCell *attno_cell;\n\tforboth (obj_cell, parsed->objects, attno_cell, per_column_attnos)\n\t{\n\t\tSparseIndexSettingsObject *obj = lfirst(obj_cell);\n\t\tBitmapset *bloom_attnos = lfirst(attno_cell);\n\n\t\t/* Check if bloom type */\n\t\tList *type_values = ts_get_values_by_key_from_parsed_object(obj, \"type\");\n\t\tif (type_values == NIL || strcmp((char *) linitial(type_values), \"bloom\") != 0)\n\t\t\tcontinue;\n\n\t\t/* Check if bloom columns are a subset of the conflict columns */\n\t\tif (!bms_is_subset(bloom_attnos, conflict_attnums))\n\t\t\tcontinue;\n\n\t\tint num_cols = bms_num_members(bloom_attnos);\n\n\t\t/* Only keep the best match (most columns) */\n\t\tif (num_cols <= best_match.num_cols)\n\t\t\tcontinue;\n\n\t\t/* Get column name for this bloom */\n\t\tList *column_names = ts_get_column_names_from_parsed_object(obj);\n\t\tchar *col_name =\n\t\t\tcompressed_column_metadata_name_list_v2(bloom1_column_prefix, column_names);\n\n\t\t/* Verify bloom column exists in the compressed chunk */\n\t\tAttrNumber compressed_attnum = get_attnum(compressed_relid, col_name);\n\t\tAssert(AttributeNumberIsValid(compressed_attnum));\n\t\tif (!AttributeNumberIsValid(compressed_attnum))\n\t\t\tcontinue;\n\n\t\t/* New best match */\n\t\tbest_match.column_name = col_name;\n\t\tbest_match.attnums = bms_copy(bloom_attnos);\n\t\tbest_match.compressed_attnum = compressed_attnum;\n\t\tbest_match.num_cols = num_cols;\n\t}\n\n\t/* Create builder for the best match, having the largest number of columns */\n\tif (best_match.num_cols > 0)\n\t{\n\t\tOid type_oids[MAX_BLOOM_FILTER_COLUMNS];\n\t\tcdst->bloom_column_name = best_match.column_name;\n\t\tcdst->bloom_insert_attnums = best_match.attnums;\n\t\tcdst->upsert_bloom_attnum = best_match.compressed_attnum;\n\n\t\tint col_idx = 0;\n\t\tint attnum = -1;\n\t\twhile ((attnum = bms_next_member(best_match.attnums, attnum)) >= 0)\n\t\t\ttype_oids[col_idx++] = get_atttype(cis->hypertable_relid, attnum);\n\n\t\tif (ts_guc_enable_sparse_index_bloom)\n\t\t\tcdst->bloom_hasher = bloom1_hasher_create(type_oids, best_match.num_cols);\n\t}\n\n\tts_bmslist_free(per_column_attnos);\n\tts_free_sparse_index_settings(parsed);\n}\n\nvoid\ninit_decompress_state_for_insert(ChunkInsertState *cis, TupleTableSlot *slot)\n{\n\tif (!cis->chunk_compressed || cis->cached_decompression_state != NULL)\n\t{\n\t\t/*\n\t\t * If the chunk is not compressed or the decompression state has\n\t\t * already been initialized, there is nothing to do here.\n\t\t */\n\t\treturn;\n\t}\n\n\tCachedDecompressionState *cdst = NULL;\n\n\tMemoryContext old_context = MemoryContextSwitchTo(cis->mctx);\n\tcdst = palloc0(sizeof(CachedDecompressionState));\n\tcis->cached_decompression_state = cdst;\n\tcdst->has_primary_or_unique_index = ts_indexing_relation_has_primary_or_unique_index(cis->rel);\n\n\tif (cdst->has_primary_or_unique_index)\n\t{\n\t\ttuple_filtering_constraints *constraints = get_batch_keys_for_unique_constraints(cis->rel);\n\t\tif (constraints->covered)\n\t\t\tconstraints->on_conflict = cis->onConflictAction;\n\n\t\tcdst->constraints = constraints;\n\n\t\tCompressionSettings *compression_settings =\n\t\t\tts_compression_settings_get(RelationGetRelid(cis->rel));\n\t\tAssert(compression_settings && OidIsValid(compression_settings->fd.compress_relid));\n\t\tcdst->compression_settings = compression_settings;\n\n\t\tRelation in_rel = relation_open(compression_settings->fd.compress_relid, RowExclusiveLock);\n\n\t\tBitmapset *columns_with_null_check = NULL;\n\t\tBitmapset *key_columns = constraints->key_columns;\n\t\tBitmapset *index_columns = NULL;\n\t\tRelation index_rel = NULL;\n\n\t\tif (ts_guc_enable_dml_decompression_tuple_filtering)\n\t\t{\n\t\t\tcdst->mem_scankeys.scankeys =\n\t\t\t\tbuild_mem_scankeys_from_slot(cis->hypertable_relid,\n\t\t\t\t\t\t\t\t\t\t\t compression_settings,\n\t\t\t\t\t\t\t\t\t\t\t cis->rel,\n\t\t\t\t\t\t\t\t\t\t\t constraints,\n\t\t\t\t\t\t\t\t\t\t\t slot,\n\t\t\t\t\t\t\t\t\t\t\t &cdst->mem_scankeys.num_scankeys,\n\t\t\t\t\t\t\t\t\t\t\t &cdst->mem_scankeys.attnos);\n\n\t\t\tcdst->constraints->vectorized_filtering =\n\t\t\t\tcan_vectorize_constraint_checks(constraints,\n\t\t\t\t\t\t\t\t\t\t\t\tcompression_settings,\n\t\t\t\t\t\t\t\t\t\t\t\tcis->rel,\n\t\t\t\t\t\t\t\t\t\t\t\tcis->hypertable_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t&cdst->mem_scankeys);\n\n\t\t\tcdst->index_scankeys.scankeys =\n\t\t\t\tbuild_index_scankeys_using_slot(cis->hypertable_relid,\n\t\t\t\t\t\t\t\t\t\t\t\tin_rel,\n\t\t\t\t\t\t\t\t\t\t\t\tcis->rel,\n\t\t\t\t\t\t\t\t\t\t\t\tconstraints->key_columns,\n\t\t\t\t\t\t\t\t\t\t\t\tslot,\n\t\t\t\t\t\t\t\t\t\t\t\t&index_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t&index_columns,\n\t\t\t\t\t\t\t\t\t\t\t\t&cdst->index_scankeys.num_scankeys,\n\t\t\t\t\t\t\t\t\t\t\t\t&cdst->index_scankeys.attnos);\n\n\t\t\tif (cis->onConflictAction != ONCONFLICT_NONE)\n\t\t\t{\n\t\t\t\tinit_upsert_bloom_state(cis);\n\t\t\t}\n\t\t}\n\n\t\tif (index_rel)\n\t\t{\n\t\t\t/*\n\t\t\t * Prepare the heap scan keys for all\n\t\t\t * key columns not found in the index\n\t\t\t */\n\t\t\tkey_columns = bms_difference(constraints->key_columns, index_columns);\n\t\t}\n\n\t\tcdst->heap_scankeys.scankeys = build_heap_scankeys(cis->hypertable_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   in_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   cis->rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   compression_settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   key_columns,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   &columns_with_null_check,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   slot,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   &cdst->heap_scankeys.num_scankeys,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   &cdst->heap_scankeys.attnos);\n\n\t\tif (index_rel)\n\t\t{\n\t\t\tcdst->index_relid = RelationGetRelid(index_rel);\n\t\t\tcolumns_with_null_check = NULL;\n\t\t\tindex_close(index_rel, AccessShareLock);\n\t\t}\n\n\t\tcdst->columns_with_null_check = columns_with_null_check;\n\t\ttable_close(in_rel, NoLock);\n\t}\n\n\tMemoryContextSwitchTo(old_context);\n}\n\nstatic void\nupdate_scankeys(ScanKeyWithAttnos *scankeys, TupleTableSlot *slot, int null_flags)\n{\n\tif (scankeys->num_scankeys == 0)\n\t{\n\t\treturn;\n\t}\n\n\tfor (int i = 0; i < scankeys->num_scankeys; i++)\n\t{\n\t\tbool isnull = false;\n\t\tDatum value = slot_getattr(slot, scankeys->attnos[i], &isnull);\n\t\tif (isnull)\n\t\t{\n\t\t\tscankeys->scankeys[i].sk_flags = null_flags;\n\t\t\tscankeys->scankeys[i].sk_argument = UnassignedDatum;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tscankeys->scankeys[i].sk_flags = 0;\n\t\t\tscankeys->scankeys[i].sk_argument = value;\n\t\t}\n\t}\n}\n\nvoid\ndecompress_batches_for_insert(ChunkInsertState *cis, TupleTableSlot *slot)\n{\n\t/*\n\t * This is supposed to be called with the actual tuple that is being\n\t * inserted, so it cannot be empty.\n\t */\n\tAssert(!TTS_EMPTY(slot));\n\n\tRelation out_rel = cis->rel;\n\tCachedDecompressionState *cdst = cis->cached_decompression_state;\n\tAssert(cdst != NULL);\n\n\tif (!cdst->has_primary_or_unique_index)\n\t{\n\t\t/*\n\t\t * If there are no unique constraints there is nothing to do here.\n\t\t */\n\t\treturn;\n\t}\n\n\tif (!ts_guc_enable_dml_decompression)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"inserting into compressed chunk with unique constraints disabled\"),\n\t\t\t\t errhint(\"Set timescaledb.enable_dml_decompression to TRUE.\")));\n\n\tif (key_column_is_null(cdst->constraints, cis->rel, cis->hypertable_relid, slot))\n\t{\n\t\t/* When any key column is NULL and NULLs are distinct there is no\n\t\t * decompression to be done as the tuple will not conflict with any\n\t\t * existing tuples.\n\t\t */\n\t\treturn;\n\t}\n\n\tAssert(cdst->compression_settings->fd.relid == RelationGetRelid(out_rel));\n\tRelation in_rel =\n\t\trelation_open(cdst->compression_settings->fd.compress_relid, RowExclusiveLock);\n\n\t/* the scan keys used for in memory tests of the decompressed tuples */\n\tbool skip_current_tuple = false;\n\tstruct decompress_batches_stats stats = { 0 };\n\n\tRelation index_rel = NULL;\n\tif (OidIsValid(cdst->index_relid))\n\t{\n\t\tindex_rel = index_open(cdst->index_relid, AccessShareLock);\n\t}\n\n\tupdate_scankeys(&cdst->index_scankeys, slot, SK_ISNULL | SK_SEARCHNULL);\n\tupdate_scankeys(&cdst->heap_scankeys, slot, SK_ISNULL | SK_SEARCHNULL);\n\tupdate_scankeys(&cdst->mem_scankeys, slot, SK_ISNULL);\n\n\tif (ts_guc_debug_compression_path_info)\n\t{\n\t\telog(INFO,\n\t\t\t \"Using %s scan with scan keys: index %d, heap %d, memory %d. \",\n\t\t\t OidIsValid(cdst->index_relid) ? \"index\" : \"table\",\n\t\t\t cdst->index_scankeys.num_scankeys,\n\t\t\t cdst->heap_scankeys.num_scankeys,\n\t\t\t cdst->mem_scankeys.num_scankeys);\n\t}\n\n\t/*\n\t * Using latest snapshot to scan the heap since we are doing this to build\n\t * the index on the uncompressed chunks in order to do speculative insertion\n\t * which is always built from all tuples (even in higher levels of isolation).\n\t */\n\tPushActiveSnapshot(GetLatestSnapshot());\n\tstats = decompress_batches_scan(in_rel,\n\t\t\t\t\t\t\t\t\tout_rel,\n\t\t\t\t\t\t\t\t\tindex_rel,\n\t\t\t\t\t\t\t\t\tGetActiveSnapshot(),\n\t\t\t\t\t\t\t\t\t&skip_current_tuple,\n\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\tNIL,\n\t\t\t\t\t\t\t\t\tNULL /* no CAgg invalidation for inserts */,\n\t\t\t\t\t\t\t\t\tcdst,\n\t\t\t\t\t\t\t\t\tslot);\n\tif (index_rel)\n\t\tindex_close(index_rel, AccessShareLock);\n\tPopActiveSnapshot();\n\n\tif (skip_current_tuple)\n\t{\n\t\tcis->skip_current_tuple = true;\n\t}\n\n\tcis->counters->batches_deleted += stats.batches_deleted;\n\tcis->counters->batches_filtered_decompressed += stats.batches_filtered_decompressed;\n\tcis->counters->batches_decompressed += stats.batches_decompressed;\n\tcis->counters->tuples_decompressed += stats.tuples_decompressed;\n\tcis->counters->batches_scanned += stats.batches_scanned;\n\tcis->counters->batches_checked_by_bloom += stats.batches_checked_by_bloom;\n\tcis->counters->batches_pruned_by_bloom += stats.batches_pruned_by_bloom;\n\tcis->counters->batches_without_bloom += stats.batches_without_bloom;\n\tcis->counters->batches_bloom_false_positives += stats.batches_bloom_false_positives;\n\tcis->counters->batches_filtered_compressed += stats.batches_filtered_compressed;\n\n\tCommandCounterIncrement();\n\ttable_close(in_rel, NoLock);\n}\n\n/*\n * This method will:\n *  1. Evaluate WHERE clauses and check if SEGMENT BY columns\n *     are specified or not.\n *  2. Build scan keys for SEGMENT BY columns.\n *  3. Move scanned rows to staging area.\n *  4. Update catalog table to change status of moved chunk.\n *\n *  Returns true if it decompresses any data.\n */\nstatic bool\ndecompress_batches_for_update_delete(ModifyHypertableState *ht_state, Chunk *chunk,\n\t\t\t\t\t\t\t\t\t List *predicates, EState *estate, bool has_joins)\n{\n\t/* process each chunk with its corresponding predicates */\n\n\tList *heap_filters = NIL;\n\tList *index_filters = NIL;\n\tList *is_null = NIL;\n\tListCell *lc = NULL;\n\tRelation chunk_rel;\n\tRelation comp_chunk_rel;\n\tRelation matching_index_rel = NULL;\n\tBatchFilter *filter;\n\n\tScanKeyData *scankeys = NULL;\n\tBitmapset *null_columns = NULL;\n\tint num_scankeys = 0;\n\tScanKeyData *index_scankeys = NULL;\n\tint num_index_scankeys = 0;\n\tstruct decompress_batches_stats stats = { 0 };\n\tint num_mem_scankeys = 0;\n\tScanKeyData *mem_scankeys = NULL;\n\tList *bloom_filters = NIL;\n\n\tCompressionSettings *settings = ts_compression_settings_get(chunk->table_id);\n\tbool delete_only = ht_state->mt->operation == CMD_DELETE && !has_joins &&\n\t\t\t\t\t   can_delete_without_decompression(ht_state, settings, chunk, predicates);\n\tInvalidationContext invalidation_ctx = { 0 };\n\n\t/*\n\t * Set up CAgg invalidation context if we're doing direct batch delete\n\t * on a hypertable with continuous aggregates.\n\t */\n\tif (delete_only && ht_state->has_continuous_aggregate)\n\t{\n\t\tconst Dimension *time_dim = hyperspace_get_open_dimension(ht_state->ht->space, 0);\n\t\tAttrNumber chunk_time_attno =\n\t\t\tget_attnum(chunk->table_id, NameStr(time_dim->fd.column_name));\n\n\t\tinvalidation_ctx.hypertable_id = ht_state->ht->fd.id;\n\t\tinvalidation_ctx.chunk_relid = chunk->table_id;\n\t\tinvalidation_ctx.time_type_oid = time_dim->fd.column_type;\n\t\tinvalidation_ctx.min_time_attno =\n\t\t\tcompressed_column_metadata_attno(settings,\n\t\t\t\t\t\t\t\t\t\t\t chunk->table_id,\n\t\t\t\t\t\t\t\t\t\t\t chunk_time_attno,\n\t\t\t\t\t\t\t\t\t\t\t settings->fd.compress_relid,\n\t\t\t\t\t\t\t\t\t\t\t \"min\");\n\t\tinvalidation_ctx.max_time_attno =\n\t\t\tcompressed_column_metadata_attno(settings,\n\t\t\t\t\t\t\t\t\t\t\t chunk->table_id,\n\t\t\t\t\t\t\t\t\t\t\t chunk_time_attno,\n\t\t\t\t\t\t\t\t\t\t\t settings->fd.compress_relid,\n\t\t\t\t\t\t\t\t\t\t\t \"max\");\n\t}\n\n\tprocess_predicates(chunk,\n\t\t\t\t\t   settings,\n\t\t\t\t\t   predicates,\n\t\t\t\t\t   &mem_scankeys,\n\t\t\t\t\t   &num_mem_scankeys,\n\t\t\t\t\t   &heap_filters,\n\t\t\t\t\t   &index_filters,\n\t\t\t\t\t   &is_null,\n\t\t\t\t\t   &bloom_filters);\n\n\tchunk_rel = table_open(chunk->table_id, RowExclusiveLock);\n\tcomp_chunk_rel = table_open(settings->fd.compress_relid, RowExclusiveLock);\n\n\tif (index_filters)\n\t{\n\t\tmatching_index_rel = find_matching_index(comp_chunk_rel, &index_filters, &heap_filters);\n\t}\n\n\tif (heap_filters)\n\t{\n\t\tscankeys = build_update_delete_scankeys(comp_chunk_rel,\n\t\t\t\t\t\t\t\t\t\t\t\theap_filters,\n\t\t\t\t\t\t\t\t\t\t\t\t&num_scankeys,\n\t\t\t\t\t\t\t\t\t\t\t\t&null_columns,\n\t\t\t\t\t\t\t\t\t\t\t\t&delete_only);\n\t}\n\n\tif (matching_index_rel)\n\t{\n\t\tindex_scankeys =\n\t\t\tbuild_index_scankeys(matching_index_rel, index_filters, &num_index_scankeys);\n\t}\n\n\tCachedDecompressionState temp_cdst = { 0 };\n\ttemp_cdst.index_scankeys.scankeys = index_scankeys;\n\ttemp_cdst.index_scankeys.num_scankeys = num_index_scankeys;\n\ttemp_cdst.heap_scankeys.scankeys = scankeys;\n\ttemp_cdst.heap_scankeys.num_scankeys = num_scankeys;\n\ttemp_cdst.mem_scankeys.scankeys = mem_scankeys;\n\ttemp_cdst.mem_scankeys.num_scankeys = num_mem_scankeys;\n\ttemp_cdst.constraints = NULL;\n\ttemp_cdst.columns_with_null_check = null_columns;\n\ttemp_cdst.bloom_filters = bloom_filters;\n\n\tPushActiveSnapshot(GetTransactionSnapshot());\n\tstats = decompress_batches_scan(comp_chunk_rel,\n\t\t\t\t\t\t\t\t\tchunk_rel,\n\t\t\t\t\t\t\t\t\tmatching_index_rel,\n\t\t\t\t\t\t\t\t\tGetActiveSnapshot(),\n\t\t\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\t\t\tdelete_only,\n\t\t\t\t\t\t\t\t\tis_null,\n\t\t\t\t\t\t\t\t\tht_state->has_continuous_aggregate ? &invalidation_ctx : NULL,\n\t\t\t\t\t\t\t\t\t&temp_cdst,\n\t\t\t\t\t\t\t\t\tNULL);\n\n\t/* close the selected index */\n\tif (matching_index_rel)\n\t\tindex_close(matching_index_rel, AccessShareLock);\n\n\tPopActiveSnapshot();\n\n\t/*\n\t * tuples from compressed chunk has been decompressed and moved\n\t * to staging area, thus mark this chunk as partially compressed\n\t */\n\tif (stats.batches_decompressed > 0)\n\t\tts_chunk_set_partial(chunk);\n\n\ttable_close(chunk_rel, NoLock);\n\ttable_close(comp_chunk_rel, NoLock);\n\n\tforeach (lc, heap_filters)\n\t{\n\t\tfilter = lfirst(lc);\n\t\tpfree(filter);\n\t}\n\tforeach (lc, index_filters)\n\t{\n\t\tfilter = lfirst(lc);\n\t\tpfree(filter);\n\t}\n\n\tlist_free_deep(bloom_filters);\n\n\tht_state->batches_deleted += stats.batches_deleted;\n\tht_state->batches_filtered_decompressed += stats.batches_filtered_decompressed;\n\tht_state->batches_decompressed += stats.batches_decompressed;\n\tht_state->tuples_decompressed += stats.tuples_decompressed;\n\tht_state->tuples_deleted += stats.tuples_deleted;\n\tht_state->batches_scanned += stats.batches_scanned;\n\tht_state->batches_checked_by_bloom += stats.batches_checked_by_bloom;\n\tht_state->batches_pruned_by_bloom += stats.batches_pruned_by_bloom;\n\tht_state->batches_without_bloom += stats.batches_without_bloom;\n\tht_state->batches_bloom_false_positives += stats.batches_bloom_false_positives;\n\tht_state->batches_filtered_compressed += stats.batches_filtered_compressed;\n\n\treturn stats.batches_decompressed > 0;\n}\n\ntypedef struct DecompressBatchScanData\n{\n\tTableScanDesc scan;\n\tIndexScanDesc index_scan;\n} DecompressBatchScanData;\n\ntypedef struct DecompressBatchScanData *DecompressBatchScanDesc;\n\nstatic DecompressBatchScanDesc\ndecompress_batch_beginscan(Relation in_rel, Relation index_rel, Snapshot snapshot, int num_scankeys,\n\t\t\t\t\t\t   ScanKeyData *scankeys)\n{\n\tDecompressBatchScanDesc scan;\n\tscan = (DecompressBatchScanDesc) palloc(sizeof(DecompressBatchScanData));\n\n\tif (index_rel)\n\t{\n\t\tscan->index_scan =\n\t\t\tindex_beginscan_compat(in_rel, index_rel, snapshot, NULL, num_scankeys, 0);\n\t\tindex_rescan(scan->index_scan, scankeys, num_scankeys, NULL, 0);\n\t\tscan->scan = NULL;\n\t}\n\telse\n\t{\n\t\tscan->scan = table_beginscan(in_rel, snapshot, num_scankeys, scankeys);\n\t\tscan->index_scan = NULL;\n\t}\n\n\treturn scan;\n}\n\nstatic bool\ndecompress_batch_scan_getnext_slot(DecompressBatchScanDesc scan, ScanDirection direction,\n\t\t\t\t\t\t\t\t   struct TupleTableSlot *slot)\n{\n\tif (scan == NULL)\n\t{\n\t\treturn false;\n\t}\n\telse if (scan->index_scan)\n\t{\n\t\treturn index_getnext_slot(scan->index_scan, direction, slot);\n\t}\n\telse if (scan->scan)\n\t{\n\t\treturn table_scan_getnextslot(scan->scan, direction, slot);\n\t}\n\telse\n\t{\n\t\treturn false;\n\t}\n}\n\nstatic void\ndecompress_batch_endscan(DecompressBatchScanDesc scan)\n{\n\tif (scan == NULL)\n\t{\n\t\treturn;\n\t}\n\telse if (scan->index_scan)\n\t{\n\t\tindex_endscan(scan->index_scan);\n\t}\n\telse if (scan->scan)\n\t{\n\t\ttable_endscan(scan->scan);\n\t}\n\n\tpfree(scan);\n}\n\n/*\n * This method will:\n *  1.Scan the index created with SEGMENT BY columns or the entire compressed chunk\n *  2.Fetch matching rows and decompress the row\n *  3.Delete this row from compressed chunk\n *  4.Insert decompressed rows to uncompressed chunk\n *\n *  Returns whether we decompressed anything.\n *\n */\nstatic struct decompress_batches_stats\ndecompress_batches_scan(Relation in_rel, Relation out_rel, Relation index_rel, Snapshot snapshot,\n\t\t\t\t\t\tbool *skip_current_tuple, bool delete_only, List *is_nulls,\n\t\t\t\t\t\tInvalidationContext *invalidation_ctx, CachedDecompressionState *cdst,\n\t\t\t\t\t\tTupleTableSlot *insert_slot)\n{\n\tHeapTuple compressed_tuple;\n\tBulkWriter writer;\n\tRowDecompressor decompressor;\n\tbool decompressor_initialized = false;\n\tbool valid = false;\n\tTM_Result result;\n\tDecompressBatchScanDesc scan = NULL;\n\tScanKeyData *index_scankeys = cdst->index_scankeys.scankeys;\n\tint num_index_scankeys = cdst->index_scankeys.num_scankeys;\n\tScanKeyData *heap_scankeys = cdst->heap_scankeys.scankeys;\n\tint num_heap_scankeys = cdst->heap_scankeys.num_scankeys;\n\tScanKeyData *mem_scankeys = cdst->mem_scankeys.scankeys;\n\tint num_mem_scankeys = cdst->mem_scankeys.num_scankeys;\n\ttuple_filtering_constraints *constraints = cdst->constraints;\n\tBitmapset *null_columns = cdst->columns_with_null_check;\n\n\tBatchMatcher *batch_matcher =\n\t\tconstraints && constraints->vectorized_filtering ? batch_matches_vectorized : batch_matches;\n\tAttrNumber meta_count_attno = InvalidAttrNumber;\n\n\tstruct decompress_batches_stats stats = { 0 };\n\n\t/* TODO: Optimization by reusing the index scan while working on a single chunk */\n\n\tif (index_rel)\n\t{\n\t\tscan = decompress_batch_beginscan(in_rel,\n\t\t\t\t\t\t\t\t\t\t  index_rel,\n\t\t\t\t\t\t\t\t\t\t  snapshot,\n\t\t\t\t\t\t\t\t\t\t  num_index_scankeys,\n\t\t\t\t\t\t\t\t\t\t  index_scankeys);\n\t}\n\telse\n\t{\n\t\tscan = decompress_batch_beginscan(in_rel, NULL, snapshot, num_heap_scankeys, heap_scankeys);\n\t}\n\tTupleTableSlot *slot = table_slot_create(in_rel, NULL);\n\n\twhile (decompress_batch_scan_getnext_slot(scan, ForwardScanDirection, slot))\n\t{\n\t\tstats.batches_scanned++;\n\n\t\t/* Deconstruct the tuple */\n\t\tAssert(slot->tts_ops->get_heap_tuple);\n\t\tcompressed_tuple = slot->tts_ops->get_heap_tuple(slot);\n\n\t\tif (index_rel && num_heap_scankeys)\n\t\t{\n\t\t\t/* filter tuple based on compress_orderby columns */\n\t\t\tvalid = false;\n#if PG16_LT\n\t\t\tHeapKeyTest(compressed_tuple,\n\t\t\t\t\t\tRelationGetDescr(in_rel),\n\t\t\t\t\t\tnum_heap_scankeys,\n\t\t\t\t\t\theap_scankeys,\n\t\t\t\t\t\tvalid);\n#else\n\t\t\tvalid = HeapKeyTest(compressed_tuple,\n\t\t\t\t\t\t\t\tRelationGetDescr(in_rel),\n\t\t\t\t\t\t\t\tnum_heap_scankeys,\n\t\t\t\t\t\t\t\theap_scankeys);\n#endif\n\t\t\tif (!valid)\n\t\t\t{\n\t\t\t\tstats.batches_filtered_compressed++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tint attrno = bms_next_member(null_columns, -1);\n\t\tint pos = 0;\n\t\tbool is_null_condition = 0;\n\t\tbool seg_col_is_null = false;\n\t\tbool complete_batch_delete;\n\t\tvalid = true;\n\t\t/*\n\t\t * Since the heap scan API does not support SK_SEARCHNULL we have to check\n\t\t * for NULL values manually when those are part of the constraints.\n\t\t */\n\t\tfor (; attrno >= 0; attrno = bms_next_member(null_columns, attrno))\n\t\t{\n\t\t\tis_null_condition = is_nulls && list_nth_int(is_nulls, pos);\n\t\t\tseg_col_is_null = slot_attisnull(slot, attrno);\n\t\t\tif ((seg_col_is_null && !is_null_condition) || (!seg_col_is_null && is_null_condition))\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * if segment by column in the scanned tuple has non null value\n\t\t\t\t * and IS NULL is specified, OR segment by column has null value\n\t\t\t\t * and IS NOT NULL is specified then skip this tuple\n\t\t\t\t */\n\t\t\t\tvalid = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tpos++;\n\t\t}\n\n\t\tif (!valid)\n\t\t{\n\t\t\tstats.batches_filtered_compressed++;\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* To track false positives */\n\t\tbool bloom_passed = false;\n\n\t\t/*\n\t\t * Bloom filter pruning for UPDATE/DELETE. Pre-computed hashes\n\t\t * are checked against bloom metadata via slot_getattr().\n\t\t */\n\t\tif (cdst->bloom_filters != NIL)\n\t\t{\n\t\t\tbool bloom_pruned = false;\n\t\t\tforeach_ptr(BloomFilterCheck, check, cdst->bloom_filters)\n\t\t\t{\n\t\t\t\tbool isnull;\n\t\t\t\tDatum bloom_datum = slot_getattr(slot, check->bloom_attno, &isnull);\n\t\t\t\tstats.batches_checked_by_bloom++;\n\t\t\t\tif (!isnull && !bloom1_contains_hash(bloom_datum, check->hash))\n\t\t\t\t{\n\t\t\t\t\tbloom_pruned = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (isnull)\n\t\t\t\t\tstats.batches_without_bloom++;\n\t\t\t}\n\t\t\tif (bloom_pruned)\n\t\t\t{\n\t\t\t\tstats.batches_pruned_by_bloom++;\n\t\t\t\tstats.batches_filtered_compressed++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tbloom_passed = true;\n\t\t}\n\n\t\tif (!decompressor_initialized)\n\t\t{\n\t\t\tdecompressor = build_decompressor(RelationGetDescr(in_rel), RelationGetDescr(out_rel));\n\t\t\tdecompressor_initialized = true;\n\t\t\twriter = bulk_writer_build(out_rel, 0);\n\t\t\tmeta_count_attno = TupleDescGetAttrNumber(decompressor.in_desc,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  COMPRESSION_COLUMN_METADATA_COUNT_NAME);\n\t\t\tAssert(meta_count_attno != InvalidAttrNumber);\n\t\t}\n\n\t\theap_deform_tuple(compressed_tuple,\n\t\t\t\t\t\t  decompressor.in_desc,\n\t\t\t\t\t\t  decompressor.compressed_datums,\n\t\t\t\t\t\t  decompressor.compressed_is_nulls);\n\n\t\t/* Bloom pre-filtering for UPSERT conflict detection */\n\t\tif (insert_slot != NULL && cdst->bloom_hasher != NULL)\n\t\t{\n\t\t\tDatum bloom_datum =\n\t\t\t\tdecompressor.compressed_datums[AttrNumberGetAttrOffset(cdst->upsert_bloom_attnum)];\n\t\t\tbool bloom_isnull =\n\t\t\t\tdecompressor\n\t\t\t\t\t.compressed_is_nulls[AttrNumberGetAttrOffset(cdst->upsert_bloom_attnum)];\n\n\t\t\tif (!bloom_isnull)\n\t\t\t{\n\t\t\t\tNullableDatum values[MAX_BLOOM_FILTER_COLUMNS];\n\t\t\t\tint col_idx = 0;\n\t\t\t\tint attnum = -1;\n\t\t\t\twhile ((attnum = bms_next_member(cdst->bloom_insert_attnums, attnum)) >= 0)\n\t\t\t\t{\n\t\t\t\t\tvalues[col_idx].value =\n\t\t\t\t\t\tslot_getattr(insert_slot, attnum, &values[col_idx].isnull);\n\t\t\t\t\tcol_idx++;\n\t\t\t\t}\n\t\t\t\tuint64 hash = cdst->bloom_hasher->hash_values(cdst->bloom_hasher, values);\n\n\t\t\t\tstats.batches_checked_by_bloom++;\n\t\t\t\tif (!bloom1_contains_hash(bloom_datum, hash))\n\t\t\t\t{\n\t\t\t\t\trow_decompressor_reset(&decompressor);\n\t\t\t\t\tstats.batches_pruned_by_bloom++;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tbloom_passed = true;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tstats.batches_without_bloom++;\n\t\t\t}\n\t\t}\n\n\t\t/* If there are no in-memory quals, all rows pass */\n\t\tBatchQualSummary summary = AllRowsPass;\n\t\tif (num_mem_scankeys)\n\t\t{\n\t\t\tsummary = batch_matcher(&decompressor,\n\t\t\t\t\t\t\t\t\tmem_scankeys,\n\t\t\t\t\t\t\t\t\tnum_mem_scankeys,\n\t\t\t\t\t\t\t\t\tconstraints,\n\t\t\t\t\t\t\t\t\tdelete_only, /* need to check full batch for direct DELETEs */\n\t\t\t\t\t\t\t\t\tskip_current_tuple);\n\n\t\t\t/* If no rows pass, complete batch gets filtered */\n\t\t\tif (summary == NoRowsPass)\n\t\t\t{\n\t\t\t\tif (bloom_passed)\n\t\t\t\t\tstats.batches_bloom_false_positives++;\n\t\t\t\trow_decompressor_reset(&decompressor);\n\t\t\t\tstats.batches_filtered_decompressed++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\tcomplete_batch_delete = (delete_only && summary == AllRowsPass);\n\n\t\trow_decompressor_reset(&decompressor);\n\n\t\tif (skip_current_tuple && *skip_current_tuple)\n\t\t{\n\t\t\trow_decompressor_close(&decompressor);\n\t\t\tbulk_writer_close(&writer);\n\t\t\tdecompress_batch_endscan(scan);\n\t\t\tExecDropSingleTupleTableSlot(slot);\n\t\t\treturn stats;\n\t\t}\n\n\t\tif (!complete_batch_delete)\n\t\t{\n\t\t\twrite_logical_replication_msg_decompression_start();\n\t\t}\n\n\t\tTM_FailureData tmfd;\n\t\tresult = table_tuple_delete(in_rel,\n\t\t\t\t\t\t\t\t\t&compressed_tuple->t_self,\n\t\t\t\t\t\t\t\t\tGetCurrentCommandId(true),\n\t\t\t\t\t\t\t\t\tsnapshot,\n\t\t\t\t\t\t\t\t\tInvalidSnapshot,\n\t\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\t\t&tmfd,\n\t\t\t\t\t\t\t\t\tfalse);\n\n\t\t/* skip reporting error if isolation level is < Repeatable Read\n\t\t * since somebody decompressed the data concurrently, we need to take\n\t\t * that data into account as well when in Read Committed level\n\t\t */\n\t\tif (result == TM_Deleted && !IsolationUsesXactSnapshot())\n\t\t{\n\t\t\twrite_logical_replication_msg_decompression_end();\n\t\t\tstats.batches_decompressed++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (result != TM_Ok)\n\t\t{\n\t\t\twrite_logical_replication_msg_decompression_end();\n\t\t\trow_decompressor_close(&decompressor);\n\t\t\tbulk_writer_close(&writer);\n\t\t\tdecompress_batch_endscan(scan);\n\t\t\treport_error(result);\n\t\t\treturn stats;\n\t\t}\n\t\t/* If all rows pass, complete batch can be deleted */\n\t\tif (complete_batch_delete)\n\t\t{\n\t\t\tstats.batches_deleted++;\n\t\t\tstats.tuples_deleted += DatumGetInt32(\n\t\t\t\tdecompressor.compressed_datums[AttrNumberGetAttrOffset(meta_count_attno)]);\n\n\t\t\t/* Track time range for continuous aggregate invalidation if needed */\n\t\t\tif (invalidation_ctx)\n\t\t\t{\n\t\t\t\tDatum min_time_datum = decompressor.compressed_datums[AttrNumberGetAttrOffset(\n\t\t\t\t\tinvalidation_ctx->min_time_attno)];\n\t\t\t\tDatum max_time_datum = decompressor.compressed_datums[AttrNumberGetAttrOffset(\n\t\t\t\t\tinvalidation_ctx->max_time_attno)];\n\t\t\t\tint64 batch_min =\n\t\t\t\t\tts_time_value_to_internal(min_time_datum, invalidation_ctx->time_type_oid);\n\t\t\t\tint64 batch_max =\n\t\t\t\t\tts_time_value_to_internal(max_time_datum, invalidation_ctx->time_type_oid);\n\n\t\t\t\tcontinuous_agg_invalidate_range(invalidation_ctx->hypertable_id,\n\t\t\t\t\t\t\t\t\t\t\t\tinvalidation_ctx->chunk_relid,\n\t\t\t\t\t\t\t\t\t\t\t\tbatch_min,\n\t\t\t\t\t\t\t\t\t\t\t\tbatch_max);\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tstats.tuples_decompressed +=\n\t\t\t\trow_decompressor_decompress_row_to_table(&decompressor, &writer);\n\t\t\tstats.batches_decompressed++;\n\t\t\twrite_logical_replication_msg_decompression_end();\n\t\t}\n\t}\n\tExecDropSingleTupleTableSlot(slot);\n\tdecompress_batch_endscan(scan);\n\tif (decompressor_initialized)\n\t{\n\t\trow_decompressor_close(&decompressor);\n\t\tbulk_writer_close(&writer);\n\t}\n\n\tif (ts_guc_debug_compression_path_info)\n\t{\n\t\telog(INFO,\n\t\t\t \"Number of compressed rows fetched from %s: \" INT64_FORMAT \". \"\n\t\t\t \"Number of compressed rows filtered%s: \" INT64_FORMAT \".\",\n\t\t\t index_rel ? \"index\" : \"table scan\",\n\t\t\t stats.batches_scanned,\n\t\t\t index_rel ? \" by heap filters\" : \"\",\n\t\t\t stats.batches_filtered_compressed);\n\t}\n\n\treturn stats;\n}\n\nstatic BatchQualSummary\nbatch_matches(RowDecompressor *decompressor, ScanKeyData *scankeys, int num_scankeys,\n\t\t\t  tuple_filtering_constraints *constraints, bool check_full_match,\n\t\t\t  bool *skip_current_tuple)\n{\n\tAttrNumber *attnos = palloc0(sizeof(AttrNumber) * num_scankeys);\n\tfor (int i = 0; i < num_scankeys; i++)\n\t{\n\t\tattnos[i] = scankeys[i].sk_attno;\n\t}\n\n\tbool next_tuple = decompress_batch_next_row(decompressor, attnos, num_scankeys);\n\tScanKey key;\n\tbool match;\n\n\t/* Default values are set like this because of binary operations\n\t * used to calculate these flags.\n\t */\n\tbool match_any = false;\n\tbool match_all = true;\n\n\twhile (next_tuple)\n\t{\n\t\tmatch = true;\n\t\tfor (int i = 0; i < num_scankeys; i++)\n\t\t{\n\t\t\tkey = &scankeys[i];\n\n\t\t\tif (key->sk_flags & SK_ISNULL)\n\t\t\t{\n\t\t\t\tif (!decompressor->decompressed_is_nulls[AttrNumberGetAttrOffset(key->sk_attno)])\n\t\t\t\t{\n\t\t\t\t\tmatch = false;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\telse if (decompressor->decompressed_is_nulls[AttrNumberGetAttrOffset(key->sk_attno)])\n\t\t\t{\n\t\t\t\tmatch = false;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (!DatumGetBool(\n\t\t\t\t\tFunctionCall2Coll(&key->sk_func,\n\t\t\t\t\t\t\t\t\t  key->sk_collation,\n\t\t\t\t\t\t\t\t\t  decompressor->decompressed_datums[AttrNumberGetAttrOffset(\n\t\t\t\t\t\t\t\t\t\t  key->sk_attno)],\n\t\t\t\t\t\t\t\t\t  key->sk_argument)))\n\t\t\t{\n\t\t\t\tmatch = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tmatch_any |= match;\n\t\tmatch_all &= match;\n\n\t\tif (match)\n\t\t{\n\t\t\tmatch_any = true;\n\t\t\tif (constraints)\n\t\t\t{\n\t\t\t\tif (constraints->on_conflict == ONCONFLICT_NONE)\n\t\t\t\t{\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_UNIQUE_VIOLATION),\n\t\t\t\t\t\t\t errmsg(\"duplicate key value violates unique constraint \\\"%s\\\"\",\n\t\t\t\t\t\t\t\t\tget_rel_name(constraints->index_relid))\n\n\t\t\t\t\t\t\t\t ));\n\t\t\t\t}\n\t\t\t\tif (constraints->on_conflict == ONCONFLICT_NOTHING && skip_current_tuple)\n\t\t\t\t{\n\t\t\t\t\t*skip_current_tuple = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!check_full_match)\n\t\t\t\treturn SomeRowsPass;\n\t\t}\n\t\tnext_tuple = decompress_batch_next_row(decompressor, attnos, num_scankeys);\n\t}\n\n\tif (match_all)\n\t\treturn AllRowsPass;\n\n\tif (match_any)\n\t\treturn SomeRowsPass;\n\n\treturn NoRowsPass;\n}\n\nstatic void\napply_validity_bitmap(const ArrowArray *arrow, uint64 *restrict result)\n{\n\tconst uint64 *validity = (const uint64 *) arrow->buffers[0];\n\tif (validity)\n\t{\n\t\tconst size_t n_vector_result_words = (arrow->length + 63) / 64;\n\t\tfor (size_t i = 0; i < n_vector_result_words; i++)\n\t\t{\n\t\t\tresult[i] &= validity[i];\n\t\t}\n\t}\n\telse\n\t{\n\t\tAssert(arrow->null_count == 0);\n\t}\n}\n\nstatic BatchQualSummary\nbatch_matches_vectorized(RowDecompressor *decompressor, ScanKeyData *scankeys, int num_scankeys,\n\t\t\t\t\t\t tuple_filtering_constraints *constraints, bool check_full_match,\n\t\t\t\t\t\t bool *skip_current_tuple)\n{\n\tconst int n_rows =\n\t\tDatumGetInt32(decompressor->compressed_datums[decompressor->count_compressed_attindex]);\n\tconst int bitmap_bytes = sizeof(uint64) * ((n_rows + 63) / 64);\n\tuint64 *restrict result =\n\t\tMemoryContextAlloc(decompressor->per_compressed_row_ctx, bitmap_bytes);\n\tuint64 dict_result[(GLOBAL_MAX_ROWS_PER_COMPRESSION + 63) / 64];\n\tmemset(result, 0xFF, bitmap_bytes);\n\tbool single_value = false;\n\tbool batch_failed = false;\n\n\t/* batch_matches() calls decompress_batch_next_row() which increments\n\t * the decompressor's batched_decompressed variable. To match that\n\t * behaviour we need to bump it here.\n\t */\n\tdecompressor->batches_decompressed++;\n\n\tfor (int sk = 0; sk < num_scankeys; sk++)\n\t{\n\t\tArrowArray *arrow =\n\t\t\tdecompress_single_column(decompressor, scankeys[sk].sk_attno, &single_value);\n\n\t\t/* Handle null check */\n\t\tif (scankeys[sk].sk_flags & SK_ISNULL)\n\t\t{\n\t\t\tif (single_value)\n\t\t\t{\n\t\t\t\tuint64 single_value_result = 1;\n\t\t\t\tvector_nulltest(arrow, IS_NULL, &single_value_result);\n\t\t\t\tif (!(single_value_result & 1))\n\t\t\t\t{\n\t\t\t\t\tbatch_failed = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tvector_nulltest(arrow, IS_NULL, result);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tVectorPredicate *predicate = get_vector_const_predicate(scankeys[sk].sk_func.fn_oid);\n\n\t\tif (single_value)\n\t\t{\n\t\t\t/*\n\t\t\t * For single-value columns (default values), use a separate bitmap\n\t\t\t * to avoid corrupting the main result. The predicate and validity\n\t\t\t * bitmap operate on a 1-element arrow, which would clear bits 1-63\n\t\t\t * of result[0] if applied directly.\n\t\t\t */\n\t\t\tuint64 single_value_result = 1;\n\t\t\tpredicate(arrow, scankeys[sk].sk_argument, &single_value_result);\n\t\t\tapply_validity_bitmap(arrow, &single_value_result);\n\t\t\tif (!(single_value_result & 1))\n\t\t\t{\n\t\t\t\tbatch_failed = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Handle non-dictionary compressed data */\n\t\tif (!arrow->dictionary)\n\t\t{\n\t\t\tpredicate(arrow, scankeys[sk].sk_argument, result);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* Handle dictionary compressed data by decompressing the dictionary\n\t\t\t * first and then translating the results to actual results */\n\t\t\tconst size_t dict_rows = arrow->dictionary->length;\n\t\t\tconst size_t dict_result_words = (dict_rows + 63) / 64;\n\t\t\tmemset(dict_result, 0xFF, dict_result_words * 8);\n\t\t\tpredicate(arrow->dictionary, scankeys[sk].sk_argument, dict_result);\n\t\t\ttranslate_bitmap_from_dictionary(arrow, dict_result, result);\n\t\t}\n\n\t\tapply_validity_bitmap(arrow, result);\n\t}\n\n\tif (batch_failed)\n\t{\n\t\treturn NoRowsPass;\n\t}\n\n\tBatchQualSummary summary = get_vector_qual_summary(result, n_rows);\n\n\tif (summary != NoRowsPass)\n\t{\n\t\tif (constraints)\n\t\t{\n\t\t\tif (constraints->on_conflict == ONCONFLICT_NONE)\n\t\t\t{\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_UNIQUE_VIOLATION),\n\t\t\t\t\t\t errmsg(\"duplicate key value violates unique constraint \\\"%s\\\"\",\n\t\t\t\t\t\t\t\tget_rel_name(constraints->index_relid))\n\n\t\t\t\t\t\t\t ));\n\t\t\t}\n\t\t\tif (constraints->on_conflict == ONCONFLICT_NOTHING && skip_current_tuple)\n\t\t\t{\n\t\t\t\t*skip_current_tuple = true;\n\t\t\t}\n\t\t}\n\t\treturn summary;\n\t}\n\n\treturn summary;\n}\n\n/*\n * Traverse the plan tree to look for Scan nodes on uncompressed chunks.\n * Once Scan node is found check if chunk is compressed, if so then\n * decompress those segments which match the filter conditions if present.\n */\n\nstruct decompress_chunk_context\n{\n\tList *relids;\n\tModifyHypertableState *ht_state;\n\t/* indicates decompression actually occurred */\n\tbool batches_decompressed;\n\tbool has_joins;\n};\n\nstatic bool decompress_chunk_walker(PlanState *ps, struct decompress_chunk_context *ctx);\n\nbool\ndecompress_target_segments(ModifyHypertableState *ht_state)\n{\n\tModifyTableState *ps =\n\t\tlinitial_node(ModifyTableState, castNode(CustomScanState, ht_state)->custom_ps);\n\n\tstruct decompress_chunk_context ctx = {\n\t\t.ht_state = ht_state,\n\t\t.relids = castNode(ModifyTable, ps->ps.plan)->resultRelations,\n\t};\n\tAssert(ctx.relids);\n\n\tdecompress_chunk_walker(&ps->ps, &ctx);\n\treturn ctx.batches_decompressed;\n}\n\nstatic bool\ndecompress_chunk_walker(PlanState *ps, struct decompress_chunk_context *ctx)\n{\n\tRangeTblEntry *rte = NULL;\n\tbool needs_decompression = false;\n\tbool should_rescan = false;\n\tbool batches_decompressed = false;\n\tList *predicates = NIL;\n\tChunk *current_chunk;\n\tif (ps == NULL)\n\t\treturn false;\n\n\tswitch (nodeTag(ps))\n\t{\n\t\t/* Note: IndexOnlyScans will never be selected for target\n\t\t * tables because system columns are necessary in order to modify the\n\t\t * data and those columns cannot be a part of the index\n\t\t */\n\t\tcase T_IndexScanState:\n\t\t{\n\t\t\t/* Get the index quals on the original table and also include\n\t\t\t * any filters that are used for filtering heap tuples\n\t\t\t */\n\t\t\tpredicates = list_union(((IndexScan *) ps->plan)->indexqualorig, ps->plan->qual);\n\t\t\tneeds_decompression = true;\n\t\t\tbreak;\n\t\t}\n\t\tcase T_BitmapHeapScanState:\n\t\t\tpredicates = list_union(((BitmapHeapScan *) ps->plan)->bitmapqualorig, ps->plan->qual);\n\t\t\tneeds_decompression = true;\n\t\t\tshould_rescan = true;\n\t\t\tbreak;\n\t\tcase T_SeqScanState:\n\t\tcase T_SampleScanState:\n\t\tcase T_TidScanState:\n\t\tcase T_TidRangeScanState:\n\t\t{\n\t\t\tpredicates = list_copy(ps->plan->qual);\n\t\t\tneeds_decompression = true;\n\t\t\tbreak;\n\t\t}\n\t\tcase T_NestLoopState:\n\t\tcase T_MergeJoinState:\n\t\tcase T_HashJoinState:\n\t\t{\n\t\t\tctx->has_joins = true;\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\tif (needs_decompression)\n\t{\n\t\t/*\n\t\t * We are only interested in chunk scans of chunks that are the\n\t\t * target of the DML statement not chunk scan on joined hypertables\n\t\t * even when it is a self join\n\t\t */\n\t\tint scanrelid = ((Scan *) ps->plan)->scanrelid;\n\t\tif (list_member_int(ctx->relids, scanrelid))\n\t\t{\n\t\t\trte = rt_fetch(scanrelid, ps->state->es_range_table);\n\t\t\tcurrent_chunk = ts_chunk_get_by_relid(rte->relid, false);\n\t\t\tif (current_chunk && ts_chunk_is_compressed(current_chunk))\n\t\t\t{\n\t\t\t\tif (!ts_guc_enable_dml_decompression)\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t\t errmsg(\"UPDATE/DELETE is disabled on compressed chunks\"),\n\t\t\t\t\t\t\t errhint(\"Set timescaledb.enable_dml_decompression to TRUE.\")));\n\n\t\t\t\tbatches_decompressed = decompress_batches_for_update_delete(ctx->ht_state,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcurrent_chunk,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tpredicates,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tps->state,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tctx->has_joins);\n\t\t\t\tctx->batches_decompressed |= batches_decompressed;\n\n\t\t\t\t/* This is a workaround specifically for bitmap heap scans:\n\t\t\t\t * during node initialization, initialize the scan state with the active snapshot\n\t\t\t\t * but since we are inserting data to be modified during the same query, they end up\n\t\t\t\t * missing that data by using a snapshot which doesn't account for this decompressed\n\t\t\t\t * data. To circumvent this issue, we change the internal scan state to use the\n\t\t\t\t * transaction snapshot and execute a rescan so the scan state is set correctly and\n\t\t\t\t * includes the new data.\n\t\t\t\t *\n\t\t\t\t * From PG17 this has changed since the scan state is not initialized with\n\t\t\t\t * the node.\n\t\t\t\t */\n\t\t\t\tif (should_rescan)\n\t\t\t\t{\n\t\t\t\t\tScanState *ss = ((ScanState *) ps);\n\t\t\t\t\tif (ss && ss->ss_currentScanDesc)\n\t\t\t\t\t{\n\t\t\t\t\t\tss->ss_currentScanDesc->rs_snapshot = GetActiveSnapshot();\n\t\t\t\t\t\ttable_rescan(ss->ss_currentScanDesc, NULL);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (predicates)\n\t\tpfree(predicates);\n\n\treturn planstate_tree_walker(ps, decompress_chunk_walker, ctx);\n}\n\n/*\n * For insert into compressed chunks with unique index determine the\n * columns which can be used for INSERT batch filtering.\n * The passed in relation is the uncompressed chunk.\n *\n * In case of multiple unique indexes we have to return the shared columns.\n * For expression indexes we ignore the columns with expressions, for partial\n * indexes we ignore predicate.\n *\n */\nstatic tuple_filtering_constraints *\nget_batch_keys_for_unique_constraints(Relation relation)\n{\n\ttuple_filtering_constraints *constraints = palloc0(sizeof(tuple_filtering_constraints));\n\tconstraints->on_conflict = ONCONFLICT_UPDATE;\n\tconstraints->nullsnotdistinct = false;\n\tListCell *lc;\n\n\t/* Fast path if definitely no indexes */\n\tif (!RelationGetForm(relation)->relhasindex)\n\t\treturn constraints;\n\n\tList *indexoidlist = RelationGetIndexList(relation);\n\n\t/* Fall out if no indexes (but relhasindex was set) */\n\tif (indexoidlist == NIL)\n\t\treturn constraints;\n\n\tforeach (lc, indexoidlist)\n\t{\n\t\tOid indexOid = lfirst_oid(lc);\n\t\tRelation indexDesc = index_open(indexOid, AccessShareLock);\n\n\t\t/*\n\t\t * We are only interested in unique indexes. PRIMARY KEY indexes also have\n\t\t * indisunique set to true so we do not need to check for them separately.\n\t\t */\n\t\tif (!indexDesc->rd_index->indislive || !indexDesc->rd_index->indisvalid ||\n\t\t\t!indexDesc->rd_index->indisunique)\n\t\t{\n\t\t\tindex_close(indexDesc, AccessShareLock);\n\t\t\tcontinue;\n\t\t}\n\n\t\tBitmapset *idx_attrs = NULL;\n\t\t/*\n\t\t * Collect attributes of current index.\n\t\t * For covering indexes we need to ignore the included columns.\n\t\t */\n\t\tfor (int i = 0; i < indexDesc->rd_index->indnkeyatts; i++)\n\t\t{\n\t\t\tAttrNumber attno = indexDesc->rd_index->indkey.values[i];\n\t\t\t/* We are not interested in expression columns which will have attno = 0 */\n\t\t\tif (!attno)\n\t\t\t\tcontinue;\n\n\t\t\tAssert(AttrNumberIsForUserDefinedAttr(attno));\n\t\t\tidx_attrs = bms_add_member(idx_attrs, attno);\n\t\t}\n\t\tindex_close(indexDesc, AccessShareLock);\n\n\t\tif (!constraints->key_columns)\n\t\t{\n\t\t\t/* First iteration */\n\t\t\tconstraints->key_columns = bms_copy(idx_attrs);\n\t\t\t/*\n\t\t\t * We only optimize unique constraint checks for non-partial and\n\t\t\t * non-expression indexes. For partial and expression indexes we\n\t\t\t * can still do batch filtering, just not make decisions about\n\t\t\t * constraint violations.\n\t\t\t */\n\t\t\tconstraints->covered = indexDesc->rd_indexprs == NIL && indexDesc->rd_indpred == NIL;\n\t\t\tconstraints->index_relid = indexDesc->rd_id;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* more than one unique constraint */\n\t\t\tconstraints->key_columns = bms_intersect(idx_attrs, constraints->key_columns);\n\t\t\tconstraints->covered = false;\n\t\t}\n\n\t\t/* If any of the unique indexes have NULLS NOT DISTINCT set, we proceed\n\t\t * with checking the constraints with decompression */\n\t\tconstraints->nullsnotdistinct |= indexDesc->rd_index->indnullsnotdistinct;\n\n\t\t/* When multiple unique indexes are present, in theory there could be no shared\n\t\t * columns even though that is very unlikely as they will probably at least share\n\t\t * the partitioning columns. But since we are looking at chunk indexes here that\n\t\t * is not guaranteed.\n\t\t */\n\t\tif (!constraints->key_columns)\n\t\t\treturn constraints;\n\t}\n\n\treturn constraints;\n}\n\n/*\n * This method will evaluate the predicates, extract\n * left and right operands, check if any of the operands\n * can be used for batch filtering and if so, it will\n * create a BatchFilter object and add it to the corresponding\n * list.\n * Any segmentby filter is put into index_filters list other\n * filters are put into heap_filters list.\n */\nstatic void\nprocess_predicates(Chunk *ch, CompressionSettings *settings, List *predicates,\n\t\t\t\t   ScanKeyData **mem_scankeys, int *num_mem_scankeys, List **heap_filters,\n\t\t\t\t   List **index_filters, List **is_null, List **bloom_filters)\n{\n\tListCell *lc;\n\tif (ts_guc_enable_dml_decompression_tuple_filtering)\n\t{\n\t\t*mem_scankeys = palloc0(sizeof(ScanKeyData) * list_length(predicates));\n\t}\n\t*num_mem_scankeys = 0;\n\tList *eq_preds = NIL;\n\n\t/*\n\t * We dont want to forward boundParams from the execution state here\n\t * as we dont want to constify join params in the predicates.\n\t * Constifying JOIN params would not be safe as we don't redo\n\t * this part in rescan.\n\t */\n\tPlannerGlobal glob = { .boundParams = NULL };\n\tPlannerInfo root = { .glob = &glob };\n\n\tforeach (lc, predicates)\n\t{\n\t\tNode *node = copyObject(lfirst(lc));\n\t\tVar *var;\n\t\tExpr *expr;\n\t\tOid collation, opno;\n\t\tRegProcedure opcode;\n\t\tchar *column_name;\n\n\t\tswitch (nodeTag(node))\n\t\t{\n\t\t\tcase T_OpExpr:\n\t\t\t{\n\t\t\t\tOpExpr *opexpr = castNode(OpExpr, node);\n\t\t\t\tcollation = opexpr->inputcollid;\n\t\t\t\tConst *arg_value;\n\n\t\t\t\tif (!ts_extract_expr_args(&opexpr->xpr, &var, &expr, &opno, &opcode))\n\t\t\t\t\tcontinue;\n\n\t\t\t\tif (!IsA(expr, Const))\n\t\t\t\t{\n\t\t\t\t\texpr = (Expr *) estimate_expression_value(&root, (Node *) expr);\n\n\t\t\t\t\tif (!IsA(expr, Const))\n\t\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\targ_value = castNode(Const, expr);\n\n\t\t\t\tcolumn_name = get_attname(ch->table_id, var->varattno, false);\n\t\t\t\tTypeCacheEntry *tce = lookup_type_cache(var->vartype, TYPECACHE_BTREE_OPFAMILY);\n\t\t\t\tint op_strategy = get_op_opfamily_strategy(opno, tce->btree_opf);\n\n\t\t\t\tif (ts_array_is_member(settings->fd.segmentby, column_name))\n\t\t\t\t{\n\t\t\t\t\tswitch (op_strategy)\n\t\t\t\t\t{\n\t\t\t\t\t\tcase BTEqualStrategyNumber:\n\t\t\t\t\t\tcase BTLessStrategyNumber:\n\t\t\t\t\t\tcase BTLessEqualStrategyNumber:\n\t\t\t\t\t\tcase BTGreaterStrategyNumber:\n\t\t\t\t\t\tcase BTGreaterEqualStrategyNumber:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t/* save segment by column name and its corresponding value specified in\n\t\t\t\t\t\t\t * WHERE */\n\t\t\t\t\t\t\t*index_filters = lappend(*index_filters,\n\t\t\t\t\t\t\t\t\t\t\t\t\t make_batchfilter(column_name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  op_strategy,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  collation,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  opcode,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  arg_value,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  false, /* is_null_check */\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  false, /* is_null */\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  false\t /* is_array_op */\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  ));\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t*heap_filters = lappend(*heap_filters,\n\t\t\t\t\t\t\t\t\t\t\t\t\tmake_batchfilter(column_name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t op_strategy,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t collation,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t opcode,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t arg_value,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t false, /* is_null_check */\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t false, /* is_null */\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t false\t/* is_array_op */\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t ));\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t/*\n\t\t\t\t * Segmentby columns are checked as part of batch scan so no need to redo the check.\n\t\t\t\t */\n\t\t\t\tif (ts_guc_enable_dml_decompression_tuple_filtering)\n\t\t\t\t{\n\t\t\t\t\tScanKeyEntryInitialize(&(*mem_scankeys)[(*num_mem_scankeys)++],\n\t\t\t\t\t\t\t\t\t\t   arg_value->constisnull ? SK_ISNULL : 0,\n\t\t\t\t\t\t\t\t\t\t   var->varattno,\n\t\t\t\t\t\t\t\t\t\t   op_strategy,\n\t\t\t\t\t\t\t\t\t\t   arg_value->consttype,\n\t\t\t\t\t\t\t\t\t\t   arg_value->constcollid,\n\t\t\t\t\t\t\t\t\t\t   opcode,\n\t\t\t\t\t\t\t\t\t\t   arg_value->constisnull ? 0 : arg_value->constvalue);\n\t\t\t\t}\n\n\t\t\t\t/*\n\t\t\t\t * Collect equality predicates for the post-loop\n\t\t\t\t * bloom filter matching pass.\n\t\t\t\t */\n\t\t\t\tif (op_strategy == BTEqualStrategyNumber && !arg_value->constisnull &&\n\t\t\t\t\tts_guc_enable_dml_bloom_filter)\n\t\t\t\t{\n\t\t\t\t\tEqualityPredicate *ep = palloc(sizeof(EqualityPredicate));\n\t\t\t\t\tep->attno = var->varattno;\n#ifdef USE_FLOAT8_BYVAL\n\t\t\t\t\tep->constvalue = arg_value->constvalue;\n#else\n\t\t\t\t\tif (arg_value->constbyval)\n\t\t\t\t\t\tep->constvalue = Int64GetDatum(arg_value->constvalue);\n\t\t\t\t\telse\n\t\t\t\t\t\tep->constvalue = datumCopy(arg_value->constvalue,\n\t\t\t\t\t\t\t\t\t\t\t\t   arg_value->constbyval,\n\t\t\t\t\t\t\t\t\t\t\t\t   arg_value->constlen);\n#endif\n\n\t\t\t\t\teq_preds = lappend(eq_preds, ep);\n\t\t\t\t}\n\n\t\t\t\tint min_attno = compressed_column_metadata_attno(settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t settings->fd.relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t var->varattno,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t settings->fd.compress_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"min\");\n\t\t\t\tif (min_attno == InvalidAttrNumber)\n\t\t\t\t\tcontinue;\n\n\t\t\t\tint max_attno = compressed_column_metadata_attno(settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t ch->table_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t var->varattno,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t settings->fd.compress_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"max\");\n\t\t\t\tif (max_attno == InvalidAttrNumber)\n\t\t\t\t\tcontinue;\n\n\t\t\t\t/* Need both min and max metadata attributes to build heap filters */\n\n\t\t\t\tswitch (op_strategy)\n\t\t\t\t{\n\t\t\t\t\tcase BTEqualStrategyNumber:\n\t\t\t\t\t{\n\t\t\t\t\t\t/* orderby col = value implies min <= value and max >= value */\n\t\t\t\t\t\t*heap_filters =\n\t\t\t\t\t\t\tlappend(*heap_filters,\n\t\t\t\t\t\t\t\t\tmake_batchfilter(get_attname(settings->fd.compress_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t min_attno,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t false),\n\t\t\t\t\t\t\t\t\t\t\t\t\t BTLessEqualStrategyNumber,\n\t\t\t\t\t\t\t\t\t\t\t\t\t collation,\n\t\t\t\t\t\t\t\t\t\t\t\t\t opcode,\n\t\t\t\t\t\t\t\t\t\t\t\t\t arg_value,\n\t\t\t\t\t\t\t\t\t\t\t\t\t false, /* is_null_check */\n\t\t\t\t\t\t\t\t\t\t\t\t\t false, /* is_null */\n\t\t\t\t\t\t\t\t\t\t\t\t\t false\t/* is_array_op */\n\t\t\t\t\t\t\t\t\t\t\t\t\t ));\n\t\t\t\t\t\t*heap_filters =\n\t\t\t\t\t\t\tlappend(*heap_filters,\n\t\t\t\t\t\t\t\t\tmake_batchfilter(get_attname(settings->fd.compress_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t max_attno,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t false),\n\t\t\t\t\t\t\t\t\t\t\t\t\t BTGreaterEqualStrategyNumber,\n\t\t\t\t\t\t\t\t\t\t\t\t\t collation,\n\t\t\t\t\t\t\t\t\t\t\t\t\t opcode,\n\t\t\t\t\t\t\t\t\t\t\t\t\t arg_value,\n\t\t\t\t\t\t\t\t\t\t\t\t\t false, /* is_null_check */\n\t\t\t\t\t\t\t\t\t\t\t\t\t false, /* is_null */\n\t\t\t\t\t\t\t\t\t\t\t\t\t false\t/* is_array_op */\n\t\t\t\t\t\t\t\t\t\t\t\t\t ));\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t\tcase BTLessStrategyNumber:\n\t\t\t\t\tcase BTLessEqualStrategyNumber:\n\t\t\t\t\t{\n\t\t\t\t\t\t/* orderby col <[=] value implies min <[=] value */\n\t\t\t\t\t\t*heap_filters =\n\t\t\t\t\t\t\tlappend(*heap_filters,\n\t\t\t\t\t\t\t\t\tmake_batchfilter(get_attname(settings->fd.compress_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t min_attno,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t false),\n\t\t\t\t\t\t\t\t\t\t\t\t\t op_strategy,\n\t\t\t\t\t\t\t\t\t\t\t\t\t collation,\n\t\t\t\t\t\t\t\t\t\t\t\t\t opcode,\n\t\t\t\t\t\t\t\t\t\t\t\t\t arg_value,\n\t\t\t\t\t\t\t\t\t\t\t\t\t false, /* is_null_check */\n\t\t\t\t\t\t\t\t\t\t\t\t\t false, /* is_null */\n\t\t\t\t\t\t\t\t\t\t\t\t\t false\t/* is_array_op */\n\t\t\t\t\t\t\t\t\t\t\t\t\t ));\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t\tcase BTGreaterStrategyNumber:\n\t\t\t\t\tcase BTGreaterEqualStrategyNumber:\n\t\t\t\t\t{\n\t\t\t\t\t\t/* orderby col >[=] value implies max >[=] value */\n\t\t\t\t\t\t*heap_filters =\n\t\t\t\t\t\t\tlappend(*heap_filters,\n\t\t\t\t\t\t\t\t\tmake_batchfilter(get_attname(settings->fd.compress_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t max_attno,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t false),\n\t\t\t\t\t\t\t\t\t\t\t\t\t op_strategy,\n\t\t\t\t\t\t\t\t\t\t\t\t\t collation,\n\t\t\t\t\t\t\t\t\t\t\t\t\t opcode,\n\t\t\t\t\t\t\t\t\t\t\t\t\t arg_value,\n\t\t\t\t\t\t\t\t\t\t\t\t\t false, /* is_null_check */\n\t\t\t\t\t\t\t\t\t\t\t\t\t false, /* is_null */\n\t\t\t\t\t\t\t\t\t\t\t\t\t false\t/* is_array_op */\n\t\t\t\t\t\t\t\t\t\t\t\t\t ));\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\t/* Do nothing for unknown operator strategies. */\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t\tcase T_ScalarArrayOpExpr:\n\t\t\t{\n\t\t\t\tScalarArrayOpExpr *sa_expr = castNode(ScalarArrayOpExpr, node);\n\t\t\t\tif (!ts_extract_expr_args(&sa_expr->xpr, &var, &expr, &opno, &opcode))\n\t\t\t\t\tcontinue;\n\n\t\t\t\tif (!IsA(expr, Const))\n\t\t\t\t{\n\t\t\t\t\texpr = (Expr *) estimate_expression_value(&root, (Node *) expr);\n\t\t\t\t\tif (!IsA(expr, Const))\n\t\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tConst *arg_value = castNode(Const, expr);\n\t\t\t\tcollation = sa_expr->inputcollid;\n\n\t\t\t\tcolumn_name = get_attname(ch->table_id, var->varattno, false);\n\t\t\t\tTypeCacheEntry *tce = lookup_type_cache(var->vartype, TYPECACHE_BTREE_OPFAMILY);\n\t\t\t\tint op_strategy = get_op_opfamily_strategy(opno, tce->btree_opf);\n\t\t\t\tif (ts_array_is_member(settings->fd.segmentby, column_name))\n\t\t\t\t{\n\t\t\t\t\tswitch (op_strategy)\n\t\t\t\t\t{\n\t\t\t\t\t\tcase BTEqualStrategyNumber:\n\t\t\t\t\t\tcase BTLessStrategyNumber:\n\t\t\t\t\t\tcase BTLessEqualStrategyNumber:\n\t\t\t\t\t\tcase BTGreaterStrategyNumber:\n\t\t\t\t\t\tcase BTGreaterEqualStrategyNumber:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t/* save segment by column name and its corresponding value specified in\n\t\t\t\t\t\t\t * WHERE */\n\t\t\t\t\t\t\t*index_filters = lappend(*index_filters,\n\t\t\t\t\t\t\t\t\t\t\t\t\t make_batchfilter(column_name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  op_strategy,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  collation,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  opcode,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  arg_value,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  false, /* is_null_check */\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  false, /* is_null */\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  true\t /* is_array_op */\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  ));\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t*heap_filters = lappend(*heap_filters,\n\t\t\t\t\t\t\t\t\t\t\t\t\tmake_batchfilter(column_name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t op_strategy,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t collation,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t opcode,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t arg_value,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t false, /* is_null_check */\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t false, /* is_null */\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t true\t/* is_array_op */\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t ));\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase T_NullTest:\n\t\t\t{\n\t\t\t\tNullTest *ntest = (NullTest *) node;\n\t\t\t\tif (IsA(ntest->arg, Var))\n\t\t\t\t{\n\t\t\t\t\tvar = (Var *) ntest->arg;\n\t\t\t\t\t/* ignore system-defined attributes */\n\t\t\t\t\tif (var->varattno <= 0)\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tcolumn_name = get_attname(ch->table_id, var->varattno, false);\n\t\t\t\t\tif (ts_array_is_member(settings->fd.segmentby, column_name))\n\t\t\t\t\t{\n\t\t\t\t\t\t*index_filters =\n\t\t\t\t\t\t\tlappend(*index_filters,\n\t\t\t\t\t\t\t\t\tmake_batchfilter(column_name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t InvalidStrategy,\n\t\t\t\t\t\t\t\t\t\t\t\t\t InvalidOid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t InvalidOid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t\t true, /* is_null_check */\n\t\t\t\t\t\t\t\t\t\t\t\t\t ntest->nulltesttype == IS_NULL, /* is_null */\n\t\t\t\t\t\t\t\t\t\t\t\t\t false /* is_array_op */\n\t\t\t\t\t\t\t\t\t\t\t\t\t ));\n\t\t\t\t\t\tif (ntest->nulltesttype == IS_NULL)\n\t\t\t\t\t\t\t*is_null = lappend_int(*is_null, 1);\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\t*is_null = lappend_int(*is_null, 0);\n\t\t\t\t\t}\n\t\t\t\t\t/* We cannot optimize filtering decompression using ORDERBY\n\t\t\t\t\t * metadata and null check qualifiers. We could possibly do that by checking the\n\t\t\t\t\t * compressed data in combination with the ORDERBY nulls first setting and\n\t\t\t\t\t * verifying that the first or last tuple of a segment contains a NULL value.\n\t\t\t\t\t * This is left for future optimization */\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/*\n\t * Bloom filter matching pass: iterate all bloom configs from\n\t * SparseIndexSettings, check which ones are fully covered by\n\t * equality predicates, compute hashes, and collect matches.\n\t * Results are sorted by selectivity (most columns first).\n\t */\n\tif (eq_preds != NIL && settings->fd.index != NULL && ts_guc_enable_dml_bloom_filter)\n\t{\n\t\tSparseIndexSettings *parsed = ts_convert_to_sparse_index_settings(settings->fd.index);\n\t\tTsBmsList per_column_attnos =\n\t\t\tts_resolve_columns_to_attnos_from_parsed_settings(parsed, ch->table_id);\n\n\t\t/* Build a Bitmapset of equality predicate attnums for bms_is_subset() */\n\t\tBitmapset *eq_pred_attnos = NULL;\n\t\tforeach_ptr(EqualityPredicate, ep, eq_preds) eq_pred_attnos =\n\t\t\tbms_add_member(eq_pred_attnos, ep->attno);\n\n\t\tListCell *obj_cell;\n\t\tListCell *attno_cell;\n\t\tforboth (obj_cell, parsed->objects, attno_cell, per_column_attnos)\n\t\t{\n\t\t\tSparseIndexSettingsObject *obj = lfirst(obj_cell);\n\t\t\tBitmapset *bloom_attnos = lfirst(attno_cell);\n\n\t\t\t/* Check if bloom type */\n\t\t\tList *type_values = ts_get_values_by_key_from_parsed_object(obj, \"type\");\n\t\t\tif (type_values == NIL || strcmp((char *) linitial(type_values), \"bloom\") != 0)\n\t\t\t\tcontinue;\n\n\t\t\tint num_columns = bms_num_members(bloom_attnos);\n\n\t\t\t/* Skip composite blooms if the GUC is off */\n\t\t\tif (num_columns > 1 && !ts_guc_enable_composite_bloom_indexes)\n\t\t\t\tcontinue;\n\n\t\t\t/* Check if ALL bloom columns have equality predicates */\n\t\t\tif (!bms_is_subset(bloom_attnos, eq_pred_attnos))\n\t\t\t\tcontinue;\n\n\t\t\t/*\n\t\t\t * All columns covered. Collect type OIDs and values in\n\t\t\t * ascending attnum order (via bms_next_member) to match\n\t\t\t * the order used during compression.\n\t\t\t */\n\t\t\tOid type_oids[MAX_BLOOM_FILTER_COLUMNS];\n\t\t\tNullableDatum values[MAX_BLOOM_FILTER_COLUMNS];\n\t\t\tint col_idx = 0;\n\t\t\tint attnum = -1;\n\t\t\twhile ((attnum = bms_next_member(bloom_attnos, attnum)) >= 0)\n\t\t\t{\n\t\t\t\ttype_oids[col_idx] = get_atttype(ch->table_id, attnum);\n\n\t\t\t\t/* Find the matching equality predicate for this attnum */\n\t\t\t\tforeach_ptr(EqualityPredicate, ep, eq_preds)\n\t\t\t\t{\n\t\t\t\t\tif (ep->attno == attnum)\n\t\t\t\t\t{\n\t\t\t\t\t\tvalues[col_idx].value = ep->constvalue;\n\t\t\t\t\t\tvalues[col_idx].isnull = false;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcol_idx++;\n\t\t\t}\n\n\t\t\tBloom1Hasher *hasher = bloom1_hasher_create(type_oids, num_columns);\n\t\t\tuint64 hash = hasher->hash_values(hasher, values);\n\n\t\t\t/* Resolve bloom metadata column attno in compressed chunk */\n\t\t\tList *column_names = ts_get_column_names_from_parsed_object(obj);\n\t\t\tchar *bloom_col_name =\n\t\t\t\tcompressed_column_metadata_name_list_v2(bloom1_column_prefix, column_names);\n\t\t\tAttrNumber bloom_attno = get_attnum(settings->fd.compress_relid, bloom_col_name);\n\n\t\t\tif (AttributeNumberIsValid(bloom_attno))\n\t\t\t{\n\t\t\t\tBloomFilterCheck *check = palloc(sizeof(BloomFilterCheck));\n\t\t\t\tcheck->bloom_attno = bloom_attno;\n\t\t\t\tcheck->hash = hash;\n\t\t\t\tcheck->num_columns = num_columns;\n\t\t\t\t*bloom_filters = lappend(*bloom_filters, check);\n\t\t\t}\n\n\t\t\tpfree(hasher);\n\t\t}\n\n\t\tbms_free(eq_pred_attnos);\n\t\tts_bmslist_free(per_column_attnos);\n\t\tts_free_sparse_index_settings(parsed);\n\t\tlist_free_deep(eq_preds);\n\t}\n\n\t/*\n\t * Sort bloom filters by number of columns (descending) assuming they\n\t * are more selective.\n\t */\n\tif (list_length(*bloom_filters) > 1)\n\t\tlist_sort(*bloom_filters, bloom_filter_check_cmp);\n}\n\nstatic BatchFilter *\nmake_batchfilter(char *column_name, StrategyNumber strategy, Oid collation, RegProcedure opcode,\n\t\t\t\t Const *value, bool is_null_check, bool is_null, bool is_array_op)\n{\n\tBatchFilter *segment_filter = palloc0(sizeof(*segment_filter));\n\n\t*segment_filter = (BatchFilter){\n\t\t.strategy = strategy,\n\t\t.collation = collation,\n\t\t.opcode = opcode,\n\t\t.value = value,\n\t\t.is_null_check = is_null_check,\n\t\t.is_null = is_null,\n\t\t.is_array_op = is_array_op,\n\t};\n\tnamestrcpy(&segment_filter->column_name, column_name);\n\n\treturn segment_filter;\n}\n\n/*\n * A compressed chunk can have multiple indexes. For a given list\n * of columns in index_filters, find the matching index which has\n * the most columns based on index_filters and adjust the filters\n * if necessary.\n * Return matching index if found else return NULL.\n *\n * Note: This method will find the best matching index based on\n * number of filters it matches. If an index matches all the filters,\n * it will be chosen. Otherwise, it will try to select the index\n * which has most matches. If there are multiple indexes have\n * the same number of matches, it will pick the first one it finds.\n * For example\n * for a given condition like \"WHERE X = 10 AND Y = 8\"\n * if there are multiple indexes like\n * 1. index (a,b,c,x)\n * 2. index (a,x,y)\n * 3. index (x)\n * In this case 2nd index is returned. If that one didn't exist,\n * it would return the 1st index.\n */\nstatic Relation\nfind_matching_index(Relation comp_chunk_rel, List **index_filters, List **heap_filters)\n{\n\tList *index_oids;\n\tListCell *lc;\n\tint total_filters = list_length(*index_filters);\n\tint max_match_count = 0;\n\tRelation result_rel = NULL;\n\n\t/* get list of indexes defined on compressed chunk */\n\tindex_oids = RelationGetIndexList(comp_chunk_rel);\n\tforeach (lc, index_oids)\n\t{\n\t\tint match_count = 0;\n\t\tRelation index_rel = index_open(lfirst_oid(lc), AccessShareLock);\n\t\tIndexInfo *index_info = BuildIndexInfo(index_rel);\n\n\t\t/* Can't use partial or expression indexes */\n\t\tif (index_info->ii_Predicate != NIL || index_info->ii_Expressions != NIL)\n\t\t{\n\t\t\tindex_close(index_rel, AccessShareLock);\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Can only use Btree indexes */\n\t\tif (index_info->ii_Am != BTREE_AM_OID)\n\t\t{\n\t\t\tindex_close(index_rel, AccessShareLock);\n\t\t\tcontinue;\n\t\t}\n\n\t\tListCell *li;\n\t\tforeach (li, *index_filters)\n\t\t{\n\t\t\tfor (int i = 0; i < index_rel->rd_index->indnatts; i++)\n\t\t\t{\n\t\t\t\tAttrNumber attnum = index_rel->rd_index->indkey.values[i];\n\t\t\t\tchar *attname = get_attname(RelationGetRelid(comp_chunk_rel), attnum, false);\n\t\t\t\tBatchFilter *sf = lfirst(li);\n\t\t\t\t/* ensure column exists in index relation */\n\t\t\t\tif (!strcmp(attname, NameStr(sf->column_name)))\n\t\t\t\t{\n\t\t\t\t\tmatch_count++;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (match_count == total_filters)\n\t\t{\n\t\t\t/* found index which has all columns specified in WHERE */\n\t\t\tif (result_rel)\n\t\t\t\tindex_close(result_rel, AccessShareLock);\n\t\t\tif (ts_guc_debug_compression_path_info)\n\t\t\t\telog(INFO, \"Index \\\"%s\\\" is used for scan. \", RelationGetRelationName(index_rel));\n\t\t\treturn index_rel;\n\t\t}\n\n\t\tif (match_count > max_match_count)\n\t\t{\n\t\t\tmax_match_count = match_count;\n\t\t\tresult_rel = index_rel;\n\t\t\tcontinue;\n\t\t}\n\t\tindex_close(index_rel, AccessShareLock);\n\t}\n\n\t/* No matching index whatsoever */\n\tif (!result_rel)\n\t{\n\t\t*heap_filters = list_concat(*heap_filters, *index_filters);\n\t\t*index_filters = list_truncate(*index_filters, 0);\n\t\treturn NULL;\n\t}\n\n\t/* We found an index which matches partially.\n\t * It can be used but we need to transfer the unmatched\n\t * filters from index_filters to heap filters.\n\t */\n\tfor (int i = 0; i < list_length(*index_filters); i++)\n\t{\n\t\tBatchFilter *sf = list_nth(*index_filters, i);\n\t\tbool match = false;\n\t\tfor (int j = 0; j < result_rel->rd_index->indnatts; j++)\n\t\t{\n\t\t\tAttrNumber attnum = result_rel->rd_index->indkey.values[j];\n\t\t\tchar *attname = get_attname(RelationGetRelid(comp_chunk_rel), attnum, false);\n\t\t\t/* ensure column exists in index relation */\n\t\t\tif (!strcmp(attname, NameStr(sf->column_name)))\n\t\t\t{\n\t\t\t\tmatch = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (!match)\n\t\t{\n\t\t\t*heap_filters = lappend(*heap_filters, sf);\n\t\t\t*index_filters = list_delete_nth_cell(*index_filters, i);\n\t\t}\n\t}\n\tif (ts_guc_debug_compression_path_info)\n\t\telog(INFO, \"Index \\\"%s\\\" is used for scan. \", RelationGetRelationName(result_rel));\n\treturn result_rel;\n}\n\nstatic void\nreport_error(TM_Result result)\n{\n\tswitch (result)\n\t{\n\t\tcase TM_Deleted:\n\t\t{\n\t\t\tif (IsolationUsesXactSnapshot())\n\t\t\t{\n\t\t\t\t/* For Repeatable Read isolation level report error */\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),\n\t\t\t\t\t\t errmsg(\"could not serialize access due to concurrent update\")));\n\t\t\t}\n\t\t}\n\t\tbreak;\n\t\t/*\n\t\t * If another transaction is updating the compressed data,\n\t\t * we have to abort the transaction to keep consistency.\n\t\t */\n\t\tcase TM_Updated:\n\t\t{\n\t\t\telog(ERROR, \"tuple concurrently updated\");\n\t\t}\n\t\tbreak;\n\t\tcase TM_Invisible:\n\t\t{\n\t\t\telog(ERROR, \"attempted to lock invisible tuple\");\n\t\t}\n\t\tbreak;\n\t\tcase TM_Ok:\n\t\t\tbreak;\n\t\tdefault:\n\t\t{\n\t\t\telog(ERROR, \"unexpected tuple operation result: %d\", result);\n\t\t}\n\t\tbreak;\n\t}\n}\n\n/*\n * If key_columns are including all unique constraint columns and NULLS\n * are not DISTINCT any NULL value in the key columns allows us to skip\n * finding matching batches as it will not create a constraint violation.\n */\nstatic bool\nkey_column_is_null(tuple_filtering_constraints *constraints, Relation chunk_rel, Oid ht_relid,\n\t\t\t\t   TupleTableSlot *slot)\n{\n\tif (!constraints->covered || constraints->nullsnotdistinct)\n\t\treturn false;\n\n\tAttrNumber chunk_attno = -1;\n\twhile ((chunk_attno = bms_next_member(constraints->key_columns, chunk_attno)) > 0)\n\t{\n\t\t/*\n\t\t * slot has the physical layout of the hypertable, so we need to\n\t\t * get the attribute number of the hypertable for the column.\n\t\t */\n\t\tconst NameData *attname = attnumAttName(chunk_rel, chunk_attno);\n\n\t\tAttrNumber ht_attno = get_attnum(ht_relid, NameStr(*attname));\n\t\tif (slot_attisnull(slot, ht_attno))\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nstatic bool\ncan_delete_without_decompression(ModifyHypertableState *ht_state, CompressionSettings *settings,\n\t\t\t\t\t\t\t\t Chunk *chunk, List *predicates)\n{\n\tListCell *lc;\n\n\tif (!ts_guc_enable_compressed_direct_batch_delete)\n\t\treturn false;\n\n\t/*\n\t * If there is a RETURNING clause we skip the optimization to delete compressed batches directly\n\t */\n\tif (ht_state->mt->returningLists)\n\t\treturn false;\n\n\t/*\n\t * If there are any DELETE row triggers on the hypertable we skip the optimization\n\t * to delete compressed batches directly.\n\t */\n\tModifyTableState *ps =\n\t\tlinitial_node(ModifyTableState, castNode(CustomScanState, ht_state)->custom_ps);\n\tif (ps->rootResultRelInfo->ri_TrigDesc)\n\t{\n\t\tTriggerDesc *trigdesc = ps->rootResultRelInfo->ri_TrigDesc;\n\t\tif (trigdesc->trig_delete_before_row || trigdesc->trig_delete_after_row ||\n\t\t\ttrigdesc->trig_delete_instead_row)\n\t\t{\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tforeach (lc, predicates)\n\t{\n\t\tNode *node = lfirst(lc);\n\t\tVar *var;\n\t\tExpr *arg_value;\n\t\tOid opno;\n\n\t\tif (ts_extract_expr_args((Expr *) node, &var, &arg_value, &opno, NULL))\n\t\t{\n\t\t\tif (!IsA(arg_value, Const))\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tchar *column_name = get_attname(chunk->table_id, var->varattno, false);\n\t\t\t/* Can do direct DELETE if we are dealing with segmentby columns */\n\t\t\tif (ts_array_is_member(settings->fd.segmentby, column_name))\n\t\t\t\tcontinue;\n\n\t\t\t/* Can do direct DELETE if we are using in-memory filtering but\n\t\t\t * only if we can actually create scankeys for filtering\n\t\t\t */\n\t\t\tif (ts_guc_enable_dml_decompression_tuple_filtering)\n\t\t\t{\n\t\t\t\tswitch (nodeTag(node))\n\t\t\t\t{\n\t\t\t\t\tcase T_ScalarArrayOpExpr:\n\t\t\t\t\tcase T_NullTest:\n\t\t\t\t\t\treturn false;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nstatic bool\ncan_vectorize_constraint_checks(tuple_filtering_constraints *constraints,\n\t\t\t\t\t\t\t\tCompressionSettings *settings, Relation chunk_rel, Oid ht_relid,\n\t\t\t\t\t\t\t\tScanKeyWithAttnos *mem_scankeys)\n{\n\tAttrNumber chunk_attno = -1;\n\tOid typoid, collid;\n\tint32 typmod;\n\n\tif (mem_scankeys == NULL || mem_scankeys->num_scankeys == 0)\n\t\treturn false;\n\n\t/* We can only vectorize if a vectorized check is available for all scankeys */\n\tfor (int sk = 0; sk < mem_scankeys->num_scankeys; sk++)\n\t{\n\t\t/*\n\t\t * Here we cannot check for NULL flags even if that is\n\t\t * handled separately, because this code is called from\n\t\t * the `init_decompress_state_for_insert` which sets the\n\t\t * flag based on the first record to be inserted and the\n\t\t * value may change for the subsequent records.\n\t\t *\n\t\t * The `fn_oid` doesn't get updated so it is valid to check\n\t\t * it here.\n\t\t */\n\t\tScanKeyData *scankey = &mem_scankeys->scankeys[sk];\n\t\tif (get_vector_const_predicate(scankey->sk_func.fn_oid) == NULL)\n\t\t\treturn false;\n\t}\n\n\twhile ((chunk_attno = bms_next_member(constraints->key_columns, chunk_attno)) > 0)\n\t{\n\t\t/*\n\t\t * slot has the physical layout of the hypertable, so we need to\n\t\t * get the attribute number of the hypertable for the column.\n\t\t */\n\t\tchar *attname = get_attname(chunk_rel->rd_id, chunk_attno, false);\n\n\t\t/* Ignore segmentby columns, they aren't compressed */\n\t\tif (ts_array_is_member(settings->fd.segmentby, attname))\n\t\t\tcontinue;\n\n\t\tget_atttypetypmodcoll(chunk_rel->rd_id, chunk_attno, &typoid, &typmod, &collid);\n\n\t\t/* No bulk decompression function, no vectorized filtering */\n\t\tif (tsl_get_decompress_all_function(compression_get_default_algorithm(typoid), typoid) ==\n\t\t\tNULL)\n\t\t\treturn false;\n\n\t\t/* For text types, check for non-deterministic collation which\n\t\t * prevents vectorized filtering */\n\t\tif (typoid == TEXTOID && OidIsValid(collid) && !get_collation_isdeterministic(collid))\n\t\t\treturn false;\n\t}\n\n\treturn true;\n}\n"
  },
  {
    "path": "tsl/src/compression/compression_dml.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <access/skey.h>\n#include <nodes/nodes.h>\n\n#include \"ts_catalog/compression_settings.h\"\n\ntypedef struct tuple_filtering_constraints\n{\n\t/*\n\t * All key column heap attribute numbers on uncompressed chunk.\n\t * We shouldn't be dealing with system columns so no need to\n\t * add/subtract FirstLowInvalidHeapAttributeNumber from these.\n\t */\n\tBitmapset *key_columns;\n\t/*\n\t * The covered flag is set to true if we have a single constraint that is covered\n\t * by all the columns present in the Bitmapset.\n\t */\n\tbool covered;\n\t/* further fields only valid when covered is true */\n\tOnConflictAction on_conflict;\n\tOid index_relid; /* used for better error messages */\n\tbool nullsnotdistinct;\n\tbool vectorized_filtering;\n} tuple_filtering_constraints;\n\nbool slot_key_test(TupleTableSlot *slot, ScanKey skey);\n\nScanKeyData *build_mem_scankeys_from_slot(Oid ht_relid, CompressionSettings *settings,\n\t\t\t\t\t\t\t\t\t\t  Relation out_rel,\n\t\t\t\t\t\t\t\t\t\t  tuple_filtering_constraints *constraints,\n\t\t\t\t\t\t\t\t\t\t  TupleTableSlot *slot, int *num_scankeys,\n\t\t\t\t\t\t\t\t\t\t  AttrNumber **slot_attnos);\nScanKeyData *build_index_scankeys(Relation index_rel, List *index_filters, int *num_scankeys);\nScanKeyData *build_index_scankeys_using_slot(Oid hypertable_relid, Relation in_rel,\n\t\t\t\t\t\t\t\t\t\t\t Relation out_rel, Bitmapset *key_columns,\n\t\t\t\t\t\t\t\t\t\t\t TupleTableSlot *slot, Relation *result_index_rel,\n\t\t\t\t\t\t\t\t\t\t\t Bitmapset **index_columns, int *num_scan_keys,\n\t\t\t\t\t\t\t\t\t\t\t AttrNumber **slot_attnos);\nScanKeyData *build_heap_scankeys(Oid hypertable_relid, Relation in_rel, Relation out_rel,\n\t\t\t\t\t\t\t\t CompressionSettings *settings, Bitmapset *key_columns,\n\t\t\t\t\t\t\t\t Bitmapset **null_columns, TupleTableSlot *slot, int *num_scankeys,\n\t\t\t\t\t\t\t\t AttrNumber **slot_attnos);\nScanKeyData *build_update_delete_scankeys(Relation in_rel, List *heap_filters, int *num_scankeys,\n\t\t\t\t\t\t\t\t\t\t  Bitmapset **null_columns, bool *delete_only);\n"
  },
  {
    "path": "tsl/src/compression/compression_scankey.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <catalog/pg_am.h>\n#include <parser/parse_coerce.h>\n#include <parser/parse_relation.h>\n#include <utils/typcache.h>\n\n#include \"compression.h\"\n#include \"compression_dml.h\"\n#include \"create.h\"\n#include \"ts_catalog/array_utils.h\"\n\nstatic Oid deduce_filter_subtype(BatchFilter *filter, Oid att_typoid);\nstatic bool create_segment_filter_scankey(Relation in_rel, char *segment_filter_col_name,\n\t\t\t\t\t\t\t\t\t\t  StrategyNumber strategy, Oid subtype, Oid opcode,\n\t\t\t\t\t\t\t\t\t\t  ScanKeyData *scankeys, int *num_scankeys,\n\t\t\t\t\t\t\t\t\t\t  Bitmapset **null_columns, Datum value, bool is_null_check,\n\t\t\t\t\t\t\t\t\t\t  bool is_array_op);\n\n/*\n * Test ScanKey against a slot.\n *\n * Unlike HeapKeyTest, this function takes into account SK_ISNULL\n * and works correctly when looking for null values.\n */\nbool\nslot_key_test(TupleTableSlot *compressed_slot, ScanKey key)\n{\n\t/* No need to get the datum if we are only checking for NULLs */\n\tif (key->sk_flags & SK_ISNULL)\n\t{\n\t\treturn slot_attisnull(compressed_slot, key->sk_attno);\n\t}\n\n\tDatum val;\n\tbool is_null;\n\tval = slot_getattr(compressed_slot, key->sk_attno, &is_null);\n\n\tif (is_null)\n\t\treturn false;\n\n\treturn DatumGetBool(FunctionCall2Coll(&key->sk_func, key->sk_collation, val, key->sk_argument));\n}\n\n/*\n * Build scankeys for decompressed tuple to check if it is part of the batch.\n *\n * The key_columns are the columns of the uncompressed chunk.\n */\nScanKeyData *\nbuild_mem_scankeys_from_slot(Oid ht_relid, CompressionSettings *settings, Relation out_rel,\n\t\t\t\t\t\t\t tuple_filtering_constraints *constraints, TupleTableSlot *slot,\n\t\t\t\t\t\t\t int *num_scankeys, AttrNumber **slot_attnos)\n{\n\tScanKeyData *scankeys = NULL;\n\tint key_index = 0;\n\tTupleDesc out_desc = RelationGetDescr(out_rel);\n\n\tif (bms_is_empty(constraints->key_columns))\n\t{\n\t\t*num_scankeys = key_index;\n\t\treturn scankeys;\n\t}\n\n\tint max_key_columns = bms_num_members(constraints->key_columns);\n\tscankeys = palloc(sizeof(ScanKeyData) * max_key_columns);\n\t*slot_attnos = palloc0(sizeof(AttrNumber) * max_key_columns);\n\n\tAttrNumber attno = -1;\n\twhile ((attno = bms_next_member(constraints->key_columns, attno)) > 0)\n\t{\n\t\tbool isnull;\n\n\t\t/*\n\t\t * slot has the physical layout of the hypertable, so we need to\n\t\t * get the attribute number of the hypertable for the column.\n\t\t */\n\t\tchar *attname = get_attname(out_rel->rd_id, attno, false);\n\n\t\t/*\n\t\t * We can skip any segmentby columns here since they have already been\n\t\t * checked during batch filtering.\n\t\t */\n\t\tif (ts_array_is_member(settings->fd.segmentby, attname))\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\tAttrNumber ht_attno = get_attnum(ht_relid, attname);\n\t\tDatum value = slot_getattr(slot, ht_attno, &isnull);\n\t\t(*slot_attnos)[key_index] = ht_attno;\n\n\t\tOid atttypid = TupleDescAttr(out_desc, AttrNumberGetAttrOffset(attno))->atttypid;\n\t\tTypeCacheEntry *tce = lookup_type_cache(atttypid, TYPECACHE_BTREE_OPFAMILY);\n\n\t\t/*\n\t\t * Should never happen since the column is part of unique constraint\n\t\t * and should therefore have the required opfamily\n\t\t */\n\t\tif (!OidIsValid(tce->btree_opf))\n\t\t\telog(ERROR, \"no btree opfamily for type \\\"%s\\\"\", format_type_be(atttypid));\n\n\t\tOid opr = get_opfamily_member(tce->btree_opf, atttypid, atttypid, BTEqualStrategyNumber);\n\n\t\t/*\n\t\t * Fall back to btree operator input type when it is binary compatible with\n\t\t * the column type and no operator for column type could be found.\n\t\t */\n\t\tif (!OidIsValid(opr) && IsBinaryCoercible(atttypid, tce->btree_opintype))\n\t\t{\n\t\t\topr = get_opfamily_member(tce->btree_opf,\n\t\t\t\t\t\t\t\t\t  tce->btree_opintype,\n\t\t\t\t\t\t\t\t\t  tce->btree_opintype,\n\t\t\t\t\t\t\t\t\t  BTEqualStrategyNumber);\n\t\t}\n\n\t\tif (!OidIsValid(opr))\n\t\t\telog(ERROR, \"no operator found for type \\\"%s\\\"\", format_type_be(atttypid));\n\n\t\tScanKeyEntryInitialize(&scankeys[key_index++],\n\t\t\t\t\t\t\t   isnull ? SK_ISNULL : 0,\n\t\t\t\t\t\t\t   attno,\n\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t   atttypid,\n\t\t\t\t\t\t\t   TupleDescAttr(out_desc, AttrNumberGetAttrOffset(attno))\n\t\t\t\t\t\t\t\t   ->attcollation,\n\t\t\t\t\t\t\t   get_opcode(opr),\n\t\t\t\t\t\t\t   isnull ? 0 : value);\n\t}\n\n\t*num_scankeys = key_index;\n\treturn scankeys;\n}\n\n/*\n * Build scankeys for decompression of specific batches. key_columns references the\n * columns of the uncompressed chunk.\n */\nScanKeyData *\nbuild_heap_scankeys(Oid hypertable_relid, Relation in_rel, Relation out_rel,\n\t\t\t\t\tCompressionSettings *settings, Bitmapset *key_columns, Bitmapset **null_columns,\n\t\t\t\t\tTupleTableSlot *slot, int *num_scankeys, AttrNumber **slot_attnos)\n{\n\tint key_index = 0;\n\tScanKeyData *scankeys = NULL;\n\n\tif (!bms_is_empty(key_columns))\n\t{\n\t\tint max_key_columns = bms_num_members(key_columns) * 2;\n\t\tscankeys = palloc0(max_key_columns * sizeof(ScanKeyData));\n\t\t*slot_attnos = palloc0(max_key_columns * sizeof(AttrNumber));\n\t\tAttrNumber attno = -1;\n\t\twhile ((attno = bms_next_member(key_columns, attno)) > 0)\n\t\t{\n\t\t\tchar *attname = get_attname(out_rel->rd_id, attno, false);\n\t\t\tbool isnull;\n\t\t\tAttrNumber ht_attno = get_attnum(hypertable_relid, attname);\n\n\t\t\t/*\n\t\t\t * This is a not very precise but easy assertion to detect attno\n\t\t\t * mismatch at least in some cases. The mismatch might happen if the\n\t\t\t * hypertable and chunk layout are different because of dropped\n\t\t\t * columns, and we're using a wrong slot type here.\n\t\t\t */\n\t\t\tPG_USED_FOR_ASSERTS_ONLY Oid ht_atttype = get_atttype(hypertable_relid, ht_attno);\n\t\t\tPG_USED_FOR_ASSERTS_ONLY Oid slot_atttype =\n\t\t\t\tTupleDescAttr(slot->tts_tupleDescriptor, AttrNumberGetAttrOffset(ht_attno))\n\t\t\t\t\t->atttypid;\n\t\t\tAssert(ht_atttype == slot_atttype);\n\n\t\t\tDatum value = slot_getattr(slot, ht_attno, &isnull);\n\n\t\t\t/*\n\t\t\t * There are 3 possible scenarios we have to consider\n\t\t\t * when dealing with columns which are part of unique\n\t\t\t * constraints.\n\t\t\t *\n\t\t\t * 1. Column is segmentby-Column\n\t\t\t * In this case we can add a single ScanKey with an\n\t\t\t * equality check for the value.\n\t\t\t * 2. Column is orderby-Column\n\t\t\t * In this we can add 2 ScanKeys with range constraints\n\t\t\t * utilizing batch metadata.\n\t\t\t * 3. Column is neither segmentby nor orderby\n\t\t\t * In this case we cannot utilize this column for\n\t\t\t * batch filtering as the values are compressed and\n\t\t\t * we have no metadata.\n\t\t\t */\n\t\t\tif (ts_array_is_member(settings->fd.segmentby, attname))\n\t\t\t{\n\t\t\t\tif (create_segment_filter_scankey(in_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t  attname,\n\t\t\t\t\t\t\t\t\t\t\t\t  BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t\t\t\t\t  InvalidOid,\n\t\t\t\t\t\t\t\t\t\t\t\t  InvalidOid,\n\t\t\t\t\t\t\t\t\t\t\t\t  scankeys,\n\t\t\t\t\t\t\t\t\t\t\t\t  &key_index,\n\t\t\t\t\t\t\t\t\t\t\t\t  null_columns,\n\t\t\t\t\t\t\t\t\t\t\t\t  value,\n\t\t\t\t\t\t\t\t\t\t\t\t  isnull,\n\t\t\t\t\t\t\t\t\t\t\t\t  false))\n\t\t\t\t{\n\t\t\t\t\t(*slot_attnos)[key_index - 1] = ht_attno;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (ts_array_is_member(settings->fd.orderby, attname))\n\t\t\t{\n\t\t\t\t/* Cannot optimize orderby columns with NULL values since those\n\t\t\t\t * are not visible in metadata\n\t\t\t\t */\n\t\t\t\tif (isnull)\n\t\t\t\t\tcontinue;\n\n\t\t\t\tint16 index = ts_array_position(settings->fd.orderby, attname);\n\n\t\t\t\tif (create_segment_filter_scankey(in_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t  column_segment_min_name(index),\n\t\t\t\t\t\t\t\t\t\t\t\t  BTLessEqualStrategyNumber,\n\t\t\t\t\t\t\t\t\t\t\t\t  InvalidOid,\n\t\t\t\t\t\t\t\t\t\t\t\t  InvalidOid,\n\t\t\t\t\t\t\t\t\t\t\t\t  scankeys,\n\t\t\t\t\t\t\t\t\t\t\t\t  &key_index,\n\t\t\t\t\t\t\t\t\t\t\t\t  null_columns,\n\t\t\t\t\t\t\t\t\t\t\t\t  value,\n\t\t\t\t\t\t\t\t\t\t\t\t  false,\n\t\t\t\t\t\t\t\t\t\t\t\t  false /* is_null_check */\n\t\t\t\t\t\t\t\t\t\t\t\t  ))\n\t\t\t\t{\n\t\t\t\t\t(*slot_attnos)[key_index - 1] = ht_attno;\n\t\t\t\t}\n\n\t\t\t\tif (create_segment_filter_scankey(in_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t  column_segment_max_name(index),\n\t\t\t\t\t\t\t\t\t\t\t\t  BTGreaterEqualStrategyNumber,\n\t\t\t\t\t\t\t\t\t\t\t\t  InvalidOid,\n\t\t\t\t\t\t\t\t\t\t\t\t  InvalidOid,\n\t\t\t\t\t\t\t\t\t\t\t\t  scankeys,\n\t\t\t\t\t\t\t\t\t\t\t\t  &key_index,\n\t\t\t\t\t\t\t\t\t\t\t\t  null_columns,\n\t\t\t\t\t\t\t\t\t\t\t\t  value,\n\t\t\t\t\t\t\t\t\t\t\t\t  false,\n\t\t\t\t\t\t\t\t\t\t\t\t  false /* is_null_check */\n\t\t\t\t\t\t\t\t\t\t\t\t  ))\n\t\t\t\t{\n\t\t\t\t\t(*slot_attnos)[key_index - 1] = ht_attno;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t*num_scankeys = key_index;\n\treturn scankeys;\n}\n\n/*\n * This method will build scan keys required to do index\n * scans on compressed chunks.\n */\nScanKeyData *\nbuild_index_scankeys(Relation index_rel, List *index_filters, int *num_scankeys)\n{\n\tListCell *lc;\n\tBatchFilter *filter = NULL;\n\t*num_scankeys = list_length(index_filters);\n\tScanKeyData *scankey = palloc0(sizeof(ScanKeyData) * (*num_scankeys));\n\tint idx = 0;\n\tint flags;\n\n\t/* Order scankeys based on index attribute order */\n\tfor (int idx_attno = 1; idx_attno <= index_rel->rd_index->indnkeyatts && idx < *num_scankeys;\n\t\t idx_attno++)\n\t{\n\t\tAttrNumber attno = index_rel->rd_index->indkey.values[AttrNumberGetAttrOffset(idx_attno)];\n\t\tchar *attname = get_attname(index_rel->rd_index->indrelid, attno, false);\n\t\tOid typoid = attnumTypeId(index_rel, idx_attno);\n\t\tforeach (lc, index_filters)\n\t\t{\n\t\t\tfilter = lfirst(lc);\n\t\t\tif (!strcmp(attname, NameStr(filter->column_name)))\n\t\t\t{\n\t\t\t\tflags = 0;\n\t\t\t\tif (filter->is_null_check)\n\t\t\t\t{\n\t\t\t\t\tflags = SK_ISNULL | (filter->is_null ? SK_SEARCHNULL : SK_SEARCHNOTNULL);\n\t\t\t\t}\n\t\t\t\tif (filter->is_array_op)\n\t\t\t\t{\n\t\t\t\t\tflags |= SK_SEARCHARRAY;\n\t\t\t\t}\n\n\t\t\t\tScanKeyEntryInitialize(&scankey[idx++],\n\t\t\t\t\t\t\t\t\t   flags,\n\t\t\t\t\t\t\t\t\t   idx_attno,\n\t\t\t\t\t\t\t\t\t   filter->strategy,\n\t\t\t\t\t\t\t\t\t   deduce_filter_subtype(filter, typoid), /* subtype */\n\t\t\t\t\t\t\t\t\t   filter->collation,\n\t\t\t\t\t\t\t\t\t   filter->opcode,\n\t\t\t\t\t\t\t\t\t   filter->value ? filter->value->constvalue : 0);\n\t\t\t}\n\t\t}\n\t}\n\n\tAssert(idx == *num_scankeys);\n\treturn scankey;\n}\n\n/* This method is used to find matching index on compressed chunk\n * and build scan keys from the slot data\n */\nScanKeyData *\nbuild_index_scankeys_using_slot(Oid hypertable_relid, Relation in_rel, Relation out_rel,\n\t\t\t\t\t\t\t\tBitmapset *key_columns, TupleTableSlot *slot,\n\t\t\t\t\t\t\t\tRelation *result_index_rel, Bitmapset **index_columns,\n\t\t\t\t\t\t\t\tint *num_scan_keys, AttrNumber **slot_attnos)\n{\n\tList *index_oids;\n\tListCell *lc;\n\tScanKeyData *scankeys = NULL;\n\t/* get list of indexes defined on compressed chunk */\n\tindex_oids = RelationGetIndexList(in_rel);\n\t*num_scan_keys = 0;\n\n\tforeach (lc, index_oids)\n\t{\n\t\tRelation index_rel = index_open(lfirst_oid(lc), AccessShareLock);\n\t\tIndexInfo *index_info = BuildIndexInfo(index_rel);\n\n\t\t/* Can't use partial or expression indexes */\n\t\tif (index_info->ii_Predicate != NIL || index_info->ii_Expressions != NIL)\n\t\t{\n\t\t\tindex_close(index_rel, AccessShareLock);\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Can only use Btree indexes */\n\t\tif (index_info->ii_Am != BTREE_AM_OID)\n\t\t{\n\t\t\tindex_close(index_rel, AccessShareLock);\n\t\t\tcontinue;\n\t\t}\n\n\t\t/*\n\t\t * Must have at least two attributes, index we are looking for contains\n\t\t * at least one segmentby column and a sequence number.\n\t\t */\n\t\tif (index_rel->rd_index->indnatts < 2)\n\t\t{\n\t\t\tindex_close(index_rel, AccessShareLock);\n\t\t\tcontinue;\n\t\t}\n\n\t\tscankeys = palloc0((index_rel->rd_index->indnatts) * sizeof(ScanKeyData));\n\t\t*slot_attnos = palloc0((index_rel->rd_index->indnatts) * sizeof(AttrNumber));\n\n\t\t/*\n\t\t * \tUsing only key attributes to exclude covering columns\n\t\t * \tonly interested in filtering here\n\t\t */\n\t\tfor (int i = 0; i < index_rel->rd_index->indnkeyatts; i++)\n\t\t{\n\t\t\tAttrNumber idx_attnum = AttrOffsetGetAttrNumber(i);\n\t\t\tAttrNumber in_attnum = index_rel->rd_index->indkey.values[i];\n\t\t\tconst NameData *attname = attnumAttName(in_rel, in_attnum);\n\t\t\tAttrNumber column_attno = get_attnum(out_rel->rd_id, NameStr(*attname));\n\n\t\t\t/* Make sure we find columns in key columns in order to select the right index */\n\t\t\tif (!bms_is_member(column_attno, key_columns))\n\t\t\t{\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tbool isnull;\n\t\t\tAttrNumber ht_attno = get_attnum(hypertable_relid, NameStr(*attname));\n\t\t\tDatum value = slot_getattr(slot, ht_attno, &isnull);\n\t\t\t(*slot_attnos)[*num_scan_keys] = ht_attno;\n\n\t\t\tOid atttypid = attnumTypeId(index_rel, idx_attnum);\n\n\t\t\tTypeCacheEntry *tce = lookup_type_cache(atttypid, TYPECACHE_BTREE_OPFAMILY);\n\t\t\tif (!OidIsValid(tce->btree_opf))\n\t\t\t\telog(ERROR, \"no btree opfamily for type \\\"%s\\\"\", format_type_be(atttypid));\n\n\t\t\tOid opr =\n\t\t\t\tget_opfamily_member(tce->btree_opf, atttypid, atttypid, BTEqualStrategyNumber);\n\n\t\t\t/*\n\t\t\t * Fall back to btree operator input type when it is binary compatible with\n\t\t\t * the column type and no operator for column type could be found.\n\t\t\t */\n\t\t\tif (!OidIsValid(opr) && IsBinaryCoercible(atttypid, tce->btree_opintype))\n\t\t\t{\n\t\t\t\topr = get_opfamily_member(tce->btree_opf,\n\t\t\t\t\t\t\t\t\t\t  tce->btree_opintype,\n\t\t\t\t\t\t\t\t\t\t  tce->btree_opintype,\n\t\t\t\t\t\t\t\t\t\t  BTEqualStrategyNumber);\n\t\t\t}\n\n\t\t\t/* No operator could be found so we can't create the scankey. */\n\t\t\tif (!OidIsValid(opr))\n\t\t\t\tcontinue;\n\n\t\t\tOid opcode = get_opcode(opr);\n\t\t\tEnsure(OidIsValid(opcode),\n\t\t\t\t   \"no opcode found for column operator of a hypertable column\");\n\n\t\t\t*index_columns = bms_add_member(*index_columns, column_attno);\n\t\t\tScanKeyEntryInitialize(&scankeys[(*num_scan_keys)++],\n\t\t\t\t\t\t\t\t   isnull ? SK_ISNULL | SK_SEARCHNULL : 0, /* flags */\n\t\t\t\t\t\t\t\t   idx_attnum,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   InvalidOid, /* No strategy subtype. */\n\t\t\t\t\t\t\t\t   attnumCollationId(index_rel, idx_attnum),\n\t\t\t\t\t\t\t\t   opcode,\n\t\t\t\t\t\t\t\t   isnull ? 0 : value);\n\t\t}\n\n\t\tif (*num_scan_keys > 0)\n\t\t{\n\t\t\t*result_index_rel = index_rel;\n\t\t\tbreak;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tindex_close(index_rel, AccessShareLock);\n\t\t\tpfree(scankeys);\n\t\t\tscankeys = NULL;\n\t\t}\n\t}\n\n\treturn scankeys;\n}\n\n/*\n * This method will build scan keys for predicates including\n * SEGMENT BY column with attribute number from compressed chunk\n * if condition is like <segmentbycol> = <const value>, else\n * OUT param null_columns is saved with column attribute number.\n */\nScanKeyData *\nbuild_update_delete_scankeys(Relation in_rel, List *heap_filters, int *num_scankeys,\n\t\t\t\t\t\t\t Bitmapset **null_columns, bool *delete_only)\n{\n\tListCell *lc;\n\tBatchFilter *filter;\n\tint key_index = 0;\n\n\tScanKeyData *scankeys = palloc0(heap_filters->length * sizeof(ScanKeyData));\n\n\tforeach (lc, heap_filters)\n\t{\n\t\tfilter = lfirst(lc);\n\t\tAttrNumber attno = get_attnum(in_rel->rd_id, NameStr(filter->column_name));\n\t\tOid typoid = get_atttype(in_rel->rd_id, attno);\n\t\tif (attno == InvalidAttrNumber)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_COLUMN),\n\t\t\t\t\t errmsg(\"column \\\"%s\\\" of relation \\\"%s\\\" does not exist\",\n\t\t\t\t\t\t\tNameStr(filter->column_name),\n\t\t\t\t\t\t\tRelationGetRelationName(in_rel))));\n\n\t\tbool added = create_segment_filter_scankey(in_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t   NameStr(filter->column_name),\n\t\t\t\t\t\t\t\t\t\t\t\t   filter->strategy,\n\t\t\t\t\t\t\t\t\t\t\t\t   deduce_filter_subtype(filter, typoid),\n\t\t\t\t\t\t\t\t\t\t\t\t   filter->opcode,\n\t\t\t\t\t\t\t\t\t\t\t\t   scankeys,\n\t\t\t\t\t\t\t\t\t\t\t\t   &key_index,\n\t\t\t\t\t\t\t\t\t\t\t\t   null_columns,\n\t\t\t\t\t\t\t\t\t\t\t\t   filter->value ? filter->value->constvalue : 0,\n\t\t\t\t\t\t\t\t\t\t\t\t   filter->is_null_check,\n\t\t\t\t\t\t\t\t\t\t\t\t   filter->is_array_op);\n\t\t/*\n\t\t * When we plan to DELETE directly on compressed chunks we\n\t\t * need to ensure all query constraints could be applied\n\t\t * to the compressed scan and disable direct DELETE when\n\t\t * we are skipping filters.\n\t\t */\n\t\tif (*delete_only && !added)\n\t\t\t*delete_only = false;\n\t}\n\t*num_scankeys = key_index;\n\treturn scankeys;\n}\n\nstatic bool\ncreate_segment_filter_scankey(Relation in_rel, char *segment_filter_col_name,\n\t\t\t\t\t\t\t  StrategyNumber strategy, Oid subtype, Oid opcode,\n\t\t\t\t\t\t\t  ScanKeyData *scankeys, int *num_scankeys, Bitmapset **null_columns,\n\t\t\t\t\t\t\t  Datum value, bool is_null_check, bool is_array_op)\n{\n\tAttrNumber cmp_attno = get_attnum(in_rel->rd_id, segment_filter_col_name);\n\tAssert(cmp_attno != InvalidAttrNumber);\n\t/* This should never happen but if it does happen, we can't generate a scan key for\n\t * the filter column so just skip it */\n\tif (cmp_attno == InvalidAttrNumber)\n\t\treturn false;\n\n\tint flags = is_array_op ? SK_SEARCHARRAY : 0;\n\n\t/*\n\t * In PG versions <= 14 NULL values are always considered distinct\n\t * from other NULL values and therefore NULLABLE multi-columnn\n\t * unique constraints might expose unexpected behaviour in the\n\t * presence of NULL values.\n\t * Since SK_SEARCHNULL is not supported by heap scans we cannot\n\t * build a ScanKey for NOT NULL and instead have to do those\n\t * checks manually.\n\t */\n\tif (is_null_check)\n\t{\n\t\t*null_columns = bms_add_member(*null_columns, cmp_attno);\n\t\treturn false;\n\t}\n\n\tOid opr;\n\t/*\n\t * All btree operators will have a valid strategy here. For\n\t * non-btree operators e.g. <> we directly take the opcode\n\t * here. We could do the same for btree in certain cases\n\t * but some filters get transformed to min/max filters and\n\t * won't keep the initial opcode so we would need to disambiguate\n\t * between them.\n\t */\n\tif (strategy == InvalidStrategy)\n\t{\n\t\topr = opcode;\n\t}\n\telse\n\t{\n\t\tOid atttypid = TupleDescAttr(in_rel->rd_att, AttrNumberGetAttrOffset(cmp_attno))->atttypid;\n\n\t\tTypeCacheEntry *tce = lookup_type_cache(atttypid, TYPECACHE_BTREE_OPFAMILY);\n\t\tif (!OidIsValid(tce->btree_opf))\n\t\t\telog(ERROR, \"no btree opfamily for type \\\"%s\\\"\", format_type_be(atttypid));\n\n\t\topr = get_opfamily_member(tce->btree_opf, atttypid, atttypid, strategy);\n\n\t\t/*\n\t\t * Fall back to btree operator input type when it is binary compatible with\n\t\t * the column type and no operator for column type could be found.\n\t\t */\n\t\tif (!OidIsValid(opr) && IsBinaryCoercible(atttypid, tce->btree_opintype))\n\t\t{\n\t\t\topr = get_opfamily_member(tce->btree_opf,\n\t\t\t\t\t\t\t\t\t  tce->btree_opintype,\n\t\t\t\t\t\t\t\t\t  tce->btree_opintype,\n\t\t\t\t\t\t\t\t\t  strategy);\n\t\t}\n\n\t\t/* No operator could be found so we can't create the scankey. */\n\t\tif (!OidIsValid(opr))\n\t\t\treturn false;\n\n\t\topr = get_opcode(opr);\n\t}\n\n\t/* We should never end up here but: no opcode, no optimization */\n\tif (!OidIsValid(opr))\n\t\treturn false;\n\n\tScanKeyEntryInitialize(&scankeys[(*num_scankeys)++],\n\t\t\t\t\t\t   flags,\n\t\t\t\t\t\t   cmp_attno,\n\t\t\t\t\t\t   strategy,\n\t\t\t\t\t\t   subtype,\n\t\t\t\t\t\t   TupleDescAttr(in_rel->rd_att, AttrNumberGetAttrOffset(cmp_attno))\n\t\t\t\t\t\t\t   ->attcollation,\n\t\t\t\t\t\t   opr,\n\t\t\t\t\t\t   value);\n\n\treturn true;\n}\n\n/*\n * Get the subtype for an indexscan from the provided filter. We also\n * need to handle array constants appropriately.\n */\nstatic Oid\ndeduce_filter_subtype(BatchFilter *filter, Oid att_typoid)\n{\n\tOid subtype = InvalidOid;\n\n\tif (!filter->value)\n\t\treturn InvalidOid;\n\n\t/*\n\t * Check if the filter type is different from the att type. If yes, the\n\t * subtype needs to be set appropriately.\n\t */\n\tif (att_typoid != filter->value->consttype)\n\t{\n\t\t/* For an array type get its element type */\n\t\tif (filter->is_array_op)\n\t\t\tsubtype = get_element_type(filter->value->consttype);\n\t\telse\n\t\t\tsubtype = filter->value->consttype;\n\t}\n\n\treturn subtype;\n}\n"
  },
  {
    "path": "tsl/src/compression/compression_storage.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * This file contains functions for manipulating compression related\n * internal storage objects like creating the underlying tables and\n * setting storage options.\n */\n\n#include <postgres.h>\n#include <access/reloptions.h>\n#include <access/xact.h>\n#include <catalog/indexing.h>\n#include <catalog/objectaccess.h>\n#include <catalog/objectaddress.h>\n#include <catalog/pg_class.h>\n#include <catalog/pg_constraint.h>\n#include <catalog/toasting.h>\n#include <commands/tablecmds.h>\n#include <commands/tablespace.h>\n#include <nodes/makefuncs.h>\n#include <utils/builtins.h>\n#include <utils/lsyscache.h>\n#include <utils/syscache.h>\n\n#include \"compression.h\"\n#include \"compression_storage.h\"\n#include \"create.h\"\n#include \"custom_type_cache.h\"\n#include \"extension_constants.h\"\n#include \"guc.h\"\n#include \"hypertable.h\"\n#include \"ts_catalog/array_utils.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"ts_catalog/compression_settings.h\"\n#include \"utils.h\"\n\n#define PRINT_COMPRESSION_TABLE_NAME(buf, prefix, hypertable_id)                                   \\\n\tdo                                                                                             \\\n\t{                                                                                              \\\n\t\tint ret = snprintf(buf, NAMEDATALEN, prefix, hypertable_id);                               \\\n\t\tif (ret < 0 || ret > NAMEDATALEN)                                                          \\\n\t\t{                                                                                          \\\n\t\t\tereport(ERROR,                                                                         \\\n\t\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),                                              \\\n\t\t\t\t\t errmsg(\"bad compression hypertable internal name\")));                         \\\n\t\t}                                                                                          \\\n\t} while (0);\n\nstatic void set_toast_tuple_target_on_chunk(Oid compressed_table_id);\nstatic void set_statistics_on_compressed_chunk(Oid compressed_table_id);\n\nint32\ncompression_hypertable_create(Hypertable *ht, Oid owner, Oid tablespace_oid)\n{\n\tObjectAddress tbladdress;\n\tchar relnamebuf[NAMEDATALEN];\n\tCatalogSecurityContext sec_ctx;\n\tOid compress_relid;\n\n\tCreateStmt *create;\n\tRangeVar *compress_rel;\n\tint32 compress_hypertable_id;\n\n\tAssert(!TS_HYPERTABLE_HAS_COMPRESSION_TABLE(ht));\n\n\tcreate = makeNode(CreateStmt);\n\tcreate->tableElts = NIL;\n\tcreate->inhRelations = NIL;\n\tcreate->ofTypename = NULL;\n\tcreate->constraints = NIL;\n\tcreate->options = NULL;\n\tcreate->oncommit = ONCOMMIT_NOOP;\n\tcreate->tablespacename = get_tablespace_name(tablespace_oid);\n\tcreate->if_not_exists = false;\n\n\t/* Invalid tablespace_oid <=> NULL tablespace name */\n\tAssert(!OidIsValid(tablespace_oid) == (create->tablespacename == NULL));\n\n\t/* create the compression table */\n\t/* NewRelationCreateToastTable calls CommandCounterIncrement */\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tcompress_hypertable_id = ts_catalog_table_next_seq_id(ts_catalog_get(), HYPERTABLE);\n\tPRINT_COMPRESSION_TABLE_NAME(relnamebuf, \"_compressed_hypertable_%d\", compress_hypertable_id);\n\tcompress_rel = makeRangeVar(pstrdup(INTERNAL_SCHEMA_NAME), pstrdup(relnamebuf), -1);\n\n\tcreate->relation = compress_rel;\n\ttbladdress = DefineRelation(create, RELKIND_RELATION, owner, NULL, NULL);\n\tCommandCounterIncrement();\n\tcompress_relid = tbladdress.objectId;\n\tts_copy_relation_acl(ht->main_table_relid, compress_relid, owner);\n\tts_catalog_restore_user(&sec_ctx);\n\tts_hypertable_create_compressed(compress_relid, compress_hypertable_id);\n\n\treturn compress_hypertable_id;\n}\n\nOid\ncompression_chunk_create(Chunk *src_chunk, Chunk *chunk, List *column_defs, Oid tablespace_oid,\n\t\t\t\t\t\t CompressionSettings *settings)\n{\n\tObjectAddress tbladdress;\n\tCatalogSecurityContext sec_ctx;\n\tDatum toast_options;\n#if PG18_LT\n\tchar *validnsps[] = HEAP_RELOPT_NAMESPACES;\n#else\n\tconst char *const validnsps[] = HEAP_RELOPT_NAMESPACES;\n#endif\n\n\tOid owner = ts_rel_get_owner(chunk->hypertable_relid);\n\n\tCreateStmt *create;\n\tRangeVar *compress_rel;\n\n\tcreate = makeNode(CreateStmt);\n\tcreate->tableElts = column_defs;\n\tcreate->inhRelations = NIL;\n\tcreate->ofTypename = NULL;\n\tcreate->constraints = NIL;\n\tcreate->options = NULL;\n\tcreate->oncommit = ONCOMMIT_NOOP;\n\tcreate->tablespacename = get_tablespace_name(tablespace_oid);\n\tcreate->if_not_exists = false;\n\n\t/* Invalid tablespace_oid <=> NULL tablespace name */\n\tAssert(!OidIsValid(tablespace_oid) == (create->tablespacename == NULL));\n\n\t/* create the compression table */\n\t/* NewRelationCreateToastTable calls CommandCounterIncrement */\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tcompress_rel = makeRangeVar(NameStr(chunk->fd.schema_name), NameStr(chunk->fd.table_name), -1);\n\n\tcreate->relation = compress_rel;\n\t/* Inherit the persistence (LOGGED or UNLOGGED) from the uncompressed chunk */\n\tcreate->relation->relpersistence = get_rel_persistence(src_chunk->table_id);\n\n\ttbladdress = DefineRelation(create, RELKIND_RELATION, owner, NULL, NULL);\n\tCommandCounterIncrement();\n\tchunk->table_id = tbladdress.objectId;\n\tts_copy_relation_acl(chunk->hypertable_relid, chunk->table_id, owner);\n\ttoast_options =\n\t\ttransformRelOptions((Datum) 0, create->options, \"toast\", validnsps, true, false);\n\t(void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true);\n\tNewRelationCreateToastTable(chunk->table_id, toast_options);\n\n\tmodify_compressed_toast_table_storage(settings, column_defs, chunk->table_id);\n\tset_statistics_on_compressed_chunk(chunk->table_id);\n\tset_toast_tuple_target_on_chunk(chunk->table_id);\n\tts_catalog_restore_user(&sec_ctx);\n\n\tcreate_compressed_chunk_indexes(chunk, settings);\n\n\treturn chunk->table_id;\n}\n\nstatic void\nset_toast_tuple_target_on_chunk(Oid compressed_table_id)\n{\n\tDefElem def_elem = {\n\t\t.type = T_DefElem,\n\t\t.defname = \"toast_tuple_target\",\n\t\t.arg = (Node *) makeInteger(ts_guc_debug_toast_tuple_target),\n\t\t.defaction = DEFELEM_SET,\n\t\t.location = -1,\n\t};\n\tAlterTableCmd cmd = {\n\t\t.type = T_AlterTableCmd,\n\t\t.subtype = AT_SetRelOptions,\n\t\t.def = (Node *) list_make1(&def_elem),\n\t};\n\n\tAlterTableInternal(compressed_table_id, list_make1(&cmd), true);\n}\n\nstatic void\nset_statistics_on_compressed_chunk(Oid compressed_table_id)\n{\n\tRelation table_rel = table_open(compressed_table_id, ShareUpdateExclusiveLock);\n\tRelation attrelation = table_open(AttributeRelationId, RowExclusiveLock);\n\tTupleDesc table_desc = RelationGetDescr(table_rel);\n\tOid compressed_data_type = ts_custom_type_cache_get(CUSTOM_TYPE_COMPRESSED_DATA)->type_oid;\n\n\tfor (int i = 0; i < table_desc->natts; i++)\n\t{\n\t\tForm_pg_attribute attrtuple;\n\t\tHeapTuple tuple;\n\t\tForm_pg_attribute col_attr = TupleDescAttr(table_desc, i);\n\t\tDatum repl_val[Natts_pg_attribute] = { 0 };\n\t\tbool repl_null[Natts_pg_attribute] = { false };\n\t\tbool repl_repl[Natts_pg_attribute] = { false };\n\n\t\t/* skip system columns */\n\t\tif (col_attr->attnum <= 0)\n\t\t\tcontinue;\n\n\t\ttuple = SearchSysCacheCopyAttName(RelationGetRelid(table_rel), NameStr(col_attr->attname));\n\n\t\tif (!HeapTupleIsValid(tuple))\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_COLUMN),\n\t\t\t\t\t errmsg(\"column \\\"%s\\\" of compressed table \\\"%s\\\" does not exist\",\n\t\t\t\t\t\t\tNameStr(col_attr->attname),\n\t\t\t\t\t\t\tRelationGetRelationName(table_rel))));\n\n\t\tattrtuple = (Form_pg_attribute) GETSTRUCT(tuple);\n\n\t\t/* The planner should never look at compressed column statistics because\n\t\t * it will not understand them. Statistics on the other columns,\n\t\t * segmentbys and metadata, are very important, so we increase their\n\t\t * target.\n\t\t */\n\t\tif (col_attr->atttypid == compressed_data_type)\n\t\t\trepl_val[AttrNumberGetAttrOffset(Anum_pg_attribute_attstattarget)] = Int16GetDatum(0);\n\t\telse\n\t\t\trepl_val[AttrNumberGetAttrOffset(Anum_pg_attribute_attstattarget)] =\n\t\t\t\tInt16GetDatum(1000);\n\t\trepl_repl[AttrNumberGetAttrOffset(Anum_pg_attribute_attstattarget)] = true;\n\n\t\ttuple =\n\t\t\theap_modify_tuple(tuple, RelationGetDescr(attrelation), repl_val, repl_null, repl_repl);\n\t\tCatalogTupleUpdate(attrelation, &tuple->t_self, tuple);\n\n\t\tInvokeObjectPostAlterHook(RelationRelationId,\n\t\t\t\t\t\t\t\t  RelationGetRelid(table_rel),\n\t\t\t\t\t\t\t\t  attrtuple->attnum);\n\t\theap_freetuple(tuple);\n\t}\n\n\ttable_close(attrelation, NoLock);\n\ttable_close(table_rel, NoLock);\n}\n\n/* modify storage attributes for toast table columns attached to the\n * compression table\n */\nvoid\nmodify_compressed_toast_table_storage(CompressionSettings *settings, List *coldefs,\n\t\t\t\t\t\t\t\t\t  Oid compress_relid)\n{\n\tListCell *lc;\n\tList *cmds = NIL;\n\tOid compresseddata_oid = ts_custom_type_cache_get(CUSTOM_TYPE_COMPRESSED_DATA)->type_oid;\n\n\tforeach (lc, coldefs)\n\t{\n\t\tColumnDef *cd = lfirst_node(ColumnDef, lc);\n\t\tAttrNumber attno = get_attnum(compress_relid, cd->colname);\n\t\tif (attno != InvalidAttrNumber && get_atttype(compress_relid, attno) == compresseddata_oid)\n\t\t{\n\t\t\t/*\n\t\t\t * All columns that pass the datatype check are columns\n\t\t\t * that are also present in the uncompressed hypertable.\n\t\t\t * Metadata columns are missing from the uncompressed\n\t\t\t * hypertable but they do not have compresseddata datatype\n\t\t\t * and therefore would be skipped.\n\t\t\t */\n\t\t\tattno = get_attnum(settings->fd.relid, cd->colname);\n\t\t\tAssert(attno != InvalidAttrNumber);\n\t\t\tOid typid = get_atttype(settings->fd.relid, attno);\n\t\t\tCompressionStorage stor =\n\t\t\t\tcompression_get_toast_storage(compression_get_default_algorithm(typid));\n\t\t\tif (stor != TOAST_STORAGE_EXTERNAL)\n\t\t\t/* external is default storage for toast columns */\n\t\t\t{\n\t\t\t\tAlterTableCmd *cmd = makeNode(AlterTableCmd);\n\t\t\t\tcmd->subtype = AT_SetStorage;\n\t\t\t\tcmd->name = pstrdup(cd->colname);\n\t\t\t\tAssert(stor == TOAST_STORAGE_EXTENDED);\n\t\t\t\tcmd->def = (Node *) makeString(\"extended\");\n\t\t\t\tcmds = lappend(cmds, cmd);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (cmds != NIL)\n\t{\n\t\tAlterTableInternal(compress_relid, cmds, false);\n\t}\n}\n\nvoid\ncreate_compressed_chunk_indexes(Chunk *chunk, CompressionSettings *settings)\n{\n\tIndexStmt stmt = {\n\t\t.type = T_IndexStmt,\n\t\t.accessMethod = DEFAULT_INDEX_TYPE,\n\t\t.idxname = NULL,\n\t\t.relation = makeRangeVar(NameStr(chunk->fd.schema_name), NameStr(chunk->fd.table_name), 0),\n\t\t.tableSpace = get_tablespace_name(get_rel_tablespace(chunk->table_id)),\n\t};\n\n\tNameData index_name;\n\tObjectAddress index_addr;\n\tHeapTuple index_tuple;\n\tList *indexcols = NIL;\n\n\tStringInfoData buf;\n\tinitStringInfo(&buf);\n\n\tif (settings->fd.segmentby)\n\t{\n\t\tDatum datum;\n\t\tbool isnull;\n\t\tArrayIterator it = array_create_iterator(settings->fd.segmentby, 0, NULL);\n\t\twhile (array_iterate(it, &datum, &isnull))\n\t\t{\n\t\t\tIndexElem *segment_elem = makeNode(IndexElem);\n\t\t\tsegment_elem->name = TextDatumGetCString(datum);\n\t\t\tappendStringInfoString(&buf, segment_elem->name);\n\t\t\tappendStringInfoString(&buf, \", \");\n\t\t\tindexcols = lappend(indexcols, segment_elem);\n\t\t}\n\t}\n\n\tSortByDir ordering;\n\tSortByNulls nulls_ordering;\n\n\tStringInfoData orderby_buf;\n\tinitStringInfo(&orderby_buf);\n\tfor (int i = 1; i <= ts_array_length(settings->fd.orderby); i++)\n\t{\n\t\tresetStringInfo(&orderby_buf);\n\t\t/* Add min metadata column */\n\t\tIndexElem *orderby_min_elem = makeNode(IndexElem);\n\t\torderby_min_elem->name = column_segment_min_name(i);\n\t\tif (ts_array_get_element_bool(settings->fd.orderby_desc, i))\n\t\t{\n\t\t\tappendStringInfoString(&orderby_buf, \" DESC\");\n\t\t\tordering = SORTBY_DESC;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tappendStringInfoString(&orderby_buf, \" ASC\");\n\t\t\tordering = SORTBY_ASC;\n\t\t}\n\t\torderby_min_elem->ordering = ordering;\n\n\t\tif (ts_array_get_element_bool(settings->fd.orderby_nullsfirst, i))\n\t\t{\n\t\t\tif (orderby_min_elem->ordering != SORTBY_DESC)\n\t\t\t{\n\t\t\t\tappendStringInfoString(&orderby_buf, \" NULLS FIRST\");\n\t\t\t\tnulls_ordering = SORTBY_NULLS_FIRST;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tnulls_ordering = SORTBY_NULLS_DEFAULT;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (orderby_min_elem->ordering != SORTBY_DESC)\n\t\t\t{\n\t\t\t\tnulls_ordering = SORTBY_NULLS_DEFAULT;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tappendStringInfoString(&orderby_buf, \" NULLS LAST\");\n\t\t\t\tnulls_ordering = SORTBY_NULLS_LAST;\n\t\t\t}\n\t\t}\n\t\torderby_min_elem->nulls_ordering = nulls_ordering;\n\t\tappendStringInfoString(&buf, orderby_min_elem->name);\n\t\tappendStringInfoString(&buf, orderby_buf.data);\n\t\tappendStringInfoString(&buf, \", \");\n\t\tindexcols = lappend(indexcols, orderby_min_elem);\n\n\t\t/* Add max metadata column */\n\t\tIndexElem *orderby_max_elem = makeNode(IndexElem);\n\t\torderby_max_elem->name = column_segment_max_name(i);\n\t\torderby_max_elem->ordering = orderby_min_elem->ordering;\n\t\torderby_max_elem->nulls_ordering = orderby_min_elem->nulls_ordering;\n\t\tappendStringInfoString(&buf, orderby_max_elem->name);\n\t\tappendStringInfoString(&buf, orderby_buf.data);\n\t\tappendStringInfoString(&buf, \", \");\n\t\tindexcols = lappend(indexcols, orderby_max_elem);\n\t}\n\n\tstmt.indexParams = indexcols;\n\tindex_addr = DefineIndexCompat(chunk->table_id,\n\t\t\t\t\t\t\t\t   &stmt,\n\t\t\t\t\t\t\t\t   InvalidOid, /* IndexRelationId */\n\t\t\t\t\t\t\t\t   InvalidOid, /* parentIndexId */\n\t\t\t\t\t\t\t\t   InvalidOid, /* parentConstraintId */\n\t\t\t\t\t\t\t\t   -1,\t\t   /* total_parts */\n\t\t\t\t\t\t\t\t   false,\t   /* is_alter_table */\n\t\t\t\t\t\t\t\t   false,\t   /* check_rights */\n\t\t\t\t\t\t\t\t   false,\t   /* check_not_in_use */\n\t\t\t\t\t\t\t\t   false,\t   /* skip_build */\n\t\t\t\t\t\t\t\t   false);\t   /* quiet */\n\tindex_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(index_addr.objectId));\n\n\tif (!HeapTupleIsValid(index_tuple))\n\t\telog(ERROR, \"cache lookup failed for index relid %u\", index_addr.objectId);\n\tindex_name = ((Form_pg_class) GETSTRUCT(index_tuple))->relname;\n\n\telog(DEBUG1,\n\t\t \"adding index %s ON %s.%s USING BTREE(%s)\",\n\t\t NameStr(index_name),\n\t\t NameStr(chunk->fd.schema_name),\n\t\t NameStr(chunk->fd.table_name),\n\t\t buf.data);\n\n\tReleaseSysCache(index_tuple);\n}\n"
  },
  {
    "path": "tsl/src/compression/compression_storage.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * This file contains functions for manipulating compression related\n * internal storage objects like creating the underlying tables and\n * setting storage options.\n */\n\n#include <postgres.h>\n\n#include \"chunk.h\"\n#include \"hypertable.h\"\n\nint32 compression_hypertable_create(Hypertable *ht, Oid owner, Oid tablespace_oid);\nOid compression_chunk_create(Chunk *src_chunk, Chunk *chunk, List *column_defs, Oid tablespace_oid,\n\t\t\t\t\t\t\t CompressionSettings *settings);\nvoid modify_compressed_toast_table_storage(CompressionSettings *settings, List *coldefs,\n\t\t\t\t\t\t\t\t\t\t   Oid compress_relid);\nvoid create_compressed_chunk_indexes(Chunk *chunk, CompressionSettings *settings);\n"
  },
  {
    "path": "tsl/src/compression/create.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/heapam.h>\n#include <access/reloptions.h>\n#include <access/tupdesc.h>\n#include <access/xact.h>\n#include <catalog/index.h>\n#include <catalog/indexing.h>\n#include <catalog/objectaccess.h>\n#include <catalog/pg_am_d.h>\n#include <catalog/pg_constraint.h>\n#include <catalog/pg_constraint_d.h>\n#include <catalog/pg_type.h>\n#include <catalog/toasting.h>\n#include <commands/alter.h>\n#include <commands/defrem.h>\n#include <commands/tablecmds.h>\n#include <commands/tablespace.h>\n#include <common/md5.h>\n#include <executor/spi.h>\n#include <miscadmin.h>\n#include <nodes/makefuncs.h>\n#include <parser/parse_type.h>\n#include <storage/lmgr.h>\n#include <tcop/utility.h>\n#include <utils/array.h>\n#include <utils/builtins.h>\n#include <utils/datum.h>\n#include <utils/guc.h>\n#include <utils/rel.h>\n#include <utils/syscache.h>\n#include <utils/typcache.h>\n\n#include \"compat/compat.h\"\n#include \"bgw_policy/policies_v2.h\"\n#include \"chunk.h\"\n#include \"chunk_index.h\"\n#include \"compression.h\"\n#include \"compression/compression_storage.h\"\n#include \"compression/sparse_index_bloom1.h\"\n#include \"create.h\"\n#include \"custom_type_cache.h\"\n#include \"dimension.h\"\n#include \"foreach_ptr.h\"\n#include \"guc.h\"\n#include \"hypertable_cache.h\"\n#include \"jsonb_utils.h\"\n#include \"trigger.h\"\n#include \"ts_catalog/array_utils.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"ts_catalog/compression_settings.h\"\n#include \"ts_catalog/continuous_agg.h\"\n#include \"utils.h\"\n#include \"with_clause/alter_table_with_clause.h\"\n#include \"with_clause/create_table_with_clause.h\"\n\n#include \"bgw_policy/compression_api.h\"\n\n#ifdef USE_ASSERT_CHECKING\nstatic const char *sparse_index_types[] = { \"min\", \"max\" };\n\nstatic bool\nis_sparse_index_type(const char *type)\n{\n\tfor (size_t i = 0; i < sizeof(sparse_index_types) / sizeof(sparse_index_types[0]); i++)\n\t{\n\t\tif (strcmp(sparse_index_types[i], type) == 0)\n\t\t{\n\t\t\treturn true;\n\t\t}\n\t}\n\n\tif (strcmp(bloom1_column_prefix, type) == 0)\n\t{\n\t\treturn true;\n\t}\n\n\tif (ts_guc_read_legacy_bloom1_v1 && strcmp(\"bloom1\", type) == 0)\n\t{\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n#endif\n\nstatic void validate_hypertable_for_compression(Hypertable *ht);\nstatic List *build_columndefs(CompressionSettings *settings, Oid src_reloid);\nstatic ColumnDef *build_columndef_singlecolumn(const char *colname, Oid typid);\nstatic void compression_settings_set_manually_for_create(Hypertable *ht,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t CompressionSettings *settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t WithClauseResult *with_clause_options);\nstatic void compression_settings_set_manually_for_alter(Hypertable *ht,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tCompressionSettings *settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tWithClauseResult *with_clause_options);\nstatic void create_default_composite_bloom(IndexInfo *index_info, Hypertable *ht,\n\t\t\t\t\t\t\t\t\t\t   CompressionSettings *settings,\n\t\t\t\t\t\t\t\t\t\t   JsonbParseState *parse_state,\n\t\t\t\t\t\t\t\t\t\t   TsBmsList *sparse_index_columns, bool *has_object);\n\nstatic char *\ncompression_column_segment_metadata_name(const char *type, int16 column_index)\n{\n\tAssert(is_sparse_index_type(type));\n\n\tchar *buf = palloc(sizeof(char) * NAMEDATALEN);\n\n\tAssert(column_index > 0);\n\tint ret =\n\t\tsnprintf(buf, NAMEDATALEN, COMPRESSION_COLUMN_METADATA_PATTERN_V1, type, column_index);\n\tif (ret < 0 || ret > NAMEDATALEN)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR), errmsg(\"bad segment metadata column name\")));\n\t}\n\treturn buf;\n}\n\n/*\n * Validate that compression settings don't exceed PostgreSQL's INDEX_MAX_KEYS limit.\n *\n * Compression creates an implicit index on the compressed chunk with:\n * - 1 index key per segmentby column\n * - 2 index keys per orderby column (for min/max metadata)\n */\nstatic void\nvalidate_compression_index_key_limit(CompressionSettings *settings)\n{\n\tint num_segmentby_keys = ts_array_length(settings->fd.segmentby);\n\tint num_orderby_keys = 2 * ts_array_length(settings->fd.orderby);\n\tif ((num_segmentby_keys + num_orderby_keys) > INDEX_MAX_KEYS)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_TOO_MANY_COLUMNS),\n\t\t\t\t errmsg(\"too many segmentby and orderby columns\"),\n\t\t\t\t errdetail(\"Combined segmentby keys (%d) and orderby keys (%d) cannot exceed %d\",\n\t\t\t\t\t\t   num_segmentby_keys,\n\t\t\t\t\t\t   num_orderby_keys,\n\t\t\t\t\t\t   INDEX_MAX_KEYS)));\n}\n\nchar *\ncolumn_segment_min_name(int16 column_index)\n{\n\treturn compression_column_segment_metadata_name(\"min\", column_index);\n}\n\nchar *\ncolumn_segment_max_name(int16 column_index)\n{\n\treturn compression_column_segment_metadata_name(\"max\", column_index);\n}\n\n/*\n * Get metadata name for a given column name and metadata type, format version 2.\n * We can't reference the attribute numbers, because they can change after\n * drop/restore if we had any dropped columns.\n * We might have to truncate the column names to fit into the NAMEDATALEN here,\n * in this case we disambiguate them with their md5 hash.\n */\nchar *\ncompressed_column_metadata_name_v2(const char *metadata_type, const char **column_names,\n\t\t\t\t\t\t\t\t   int num_columns)\n{\n\tAssert(is_sparse_index_type(metadata_type));\n\tAssert(strlen(metadata_type) <= 6);\n\tAssert(column_names != NULL);\n\tAssert(num_columns > 0);\n\tAssert(num_columns <= MAX_BLOOM_FILTER_COLUMNS);\n\n\tint len = 0;\n\tStringInfoData buf = { 0 };\n\tinitStringInfo(&buf);\n\n\tfor (int i = 0; i < num_columns; i++)\n\t{\n\t\tAssert(column_names[i] != NULL);\n#ifdef USE_ASSERT_CHECKING\n\t\tint col_len = strlen(column_names[i]);\n#endif\n\t\tAssert(col_len > 0 && col_len < NAMEDATALEN);\n\t\tif (i > 0)\n\t\t\tappendStringInfoChar(&buf, '_');\n\t\tappendStringInfo(&buf, \"%s\", column_names[i]);\n\t}\n\n\tlen = buf.len;\n\n\t/*\n\t * We have to fit the name into NAMEDATALEN - 1 which is 63 bytes:\n\t * 12 (_ts_meta_v2_) + 6 (metadata_type) + [1 (_) + x (column_name)]x num_columns  + 1 (_) + 4\n\t * (hash) = 63; x = 63 - 24 = 39.\n\t */\n\n\tchar *result;\n\tif (len > 39)\n\t{\n\t\tconst char *errstr = NULL;\n\t\tchar hash[33];\n\t\tEnsure(pg_md5_hash(buf.data, len, hash, &errstr), \"md5 computation failure\");\n\t\tresult = psprintf(\"_ts_meta_v2_%.6s_%.4s_%.39s\", metadata_type, hash, buf.data);\n\t}\n\telse\n\t{\n\t\tresult = psprintf(\"_ts_meta_v2_%.6s_%.39s\", metadata_type, buf.data);\n\t}\n\tAssert(strlen(result) < NAMEDATALEN);\n\treturn result;\n}\n\nchar *\ncompressed_column_metadata_name_list_v2(const char *metadata_type, List *column_names_list)\n{\n\tint num_column_names = list_length(column_names_list);\n\tEnsure(num_column_names > 0, \"list of column names must be non-empty\");\n\tEnsure(num_column_names <= MAX_BLOOM_FILTER_COLUMNS,\n\t\t   \"list of column names must be less than or equal to %d, got %d\",\n\t\t   MAX_BLOOM_FILTER_COLUMNS,\n\t\t   num_column_names);\n\n\tconst char *column_names[MAX_BLOOM_FILTER_COLUMNS];\n\tListCell *cell = NULL;\n\tint i = 0;\n\tforeach (cell, column_names_list)\n\t{\n\t\tcolumn_names[i] = (const char *) lfirst(cell);\n\t\ti++;\n\t}\n\n\treturn compressed_column_metadata_name_v2(metadata_type, column_names, num_column_names);\n}\n\nint\ncompressed_column_metadata_attno(const CompressionSettings *settings, Oid chunk_reloid,\n\t\t\t\t\t\t\t\t AttrNumber chunk_attno, Oid compressed_reloid,\n\t\t\t\t\t\t\t\t char const *metadata_type)\n{\n\tAssert(is_sparse_index_type(metadata_type));\n\n\tchar *attname = get_attname(chunk_reloid, chunk_attno, /* missing_ok = */ false);\n\tint16 orderby_pos = ts_array_position(settings->fd.orderby, attname);\n\n\tif (orderby_pos != 0 &&\n\t\t(strcmp(metadata_type, \"min\") == 0 || strcmp(metadata_type, \"max\") == 0))\n\t{\n\t\tchar *metadata_name = compression_column_segment_metadata_name(metadata_type, orderby_pos);\n\t\treturn get_attnum(compressed_reloid, metadata_name);\n\t}\n\n\tchar *metadata_name =\n\t\tcompressed_column_metadata_name_v2(metadata_type, (const char **) &attname, 1);\n\treturn get_attnum(compressed_reloid, metadata_name);\n}\n\n/*\n * The heuristic for whether we should use the bloom filter sparse index.\n */\nstatic bool\nshould_create_bloom_sparse_index(Oid atttypid, TypeCacheEntry *type, Oid src_reloid)\n{\n\t/*\n\t * The index must be enabled by the GUC.\n\t */\n\tif (!ts_guc_enable_sparse_index_bloom)\n\t{\n\t\treturn false;\n\t}\n\n\t/*\n\t * The type must be hashable. For some types we use our own hash functions\n\t * which have better characteristics.\n\t */\n\tFmgrInfo *finfo = NULL;\n\tif (bloom1_get_hash_function(atttypid, &finfo) == NULL)\n\t{\n\t\treturn false;\n\t}\n\n\t/*\n\t * For time types, we expect:\n\t * 1) range queries, not equality,\n\t * 2) correlation with the orderby columns, e.g. creation time correlates\n\t *    with the update time that is used as orderby.\n\t * This makes minmax indexes more suitable than bloom filters.\n\t */\n\tif (atttypid == TIMESTAMPTZOID || atttypid == TIMESTAMPOID || atttypid == TIMEOID ||\n\t\tatttypid == TIMETZOID || atttypid == DATEOID)\n\t{\n\t\treturn false;\n\t}\n\n\t/*\n\t * For fractional arithmetic types, equality queries are unlikely.\n\t */\n\tif (atttypid == FLOAT4OID || atttypid == FLOAT8OID || atttypid == NUMERICOID)\n\t{\n\t\treturn false;\n\t}\n\n\t/*\n\t * Bloom filters for 1k elements with 2% false positive rate require about\n\t * one byte per element, so there's no point in using them for smaller data\n\t * types that typically compress to less than that.\n\t */\n\tif (type->typlen > 0 && type->typlen < 4)\n\t{\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n * Create a column definition for a sparse index column. The attributes passed is a\n * List of Form_pg_attribute elements. Min and max indices only use\n * the first element. Bloom filters may use multiple columns.\n */\nstatic ColumnDef *\ncreate_sparse_index_column_def(List *attributes, const char *metadata_type)\n{\n\tAssert(is_sparse_index_type(metadata_type));\n\tColumnDef *column_def = NULL;\n\tList *column_names = NIL;\n\n\t/* At least one valid attribute must be present */\n\tAssert(attributes != NULL);\n\tAssert(list_length(attributes) > 0);\n\tAssert(list_length(attributes) <= MAX_BLOOM_FILTER_COLUMNS);\n\n\tconst bool is_bloom = strcmp(metadata_type, bloom1_column_prefix) == 0;\n\n\t{\n\t\t/* Populate the column names array */\n\t\tListCell *cell = NULL;\n\t\tint i = 0;\n\t\tforeach (cell, attributes)\n\t\t{\n\t\t\tForm_pg_attribute attr = (Form_pg_attribute) lfirst(cell);\n\t\t\tEnsure(i < MAX_BLOOM_FILTER_COLUMNS,\n\t\t\t\t   \"too many columns for bloom filter, got %d, max %d, name: %s\",\n\t\t\t\t   i + 1,\n\t\t\t\t   MAX_BLOOM_FILTER_COLUMNS,\n\t\t\t\t   NameStr(attr->attname));\n\t\t\tcolumn_names = lappend(column_names, NameStr(attr->attname));\n\t\t\ti++;\n\t\t}\n\t}\n\n\tif (is_bloom)\n\t{\n\t\t/*\n\t\t * The types must be hashable. For some types we use our own hash functions\n\t\t * which have better characteristics.\n\t\t */\n\t\tListCell *cell = NULL;\n\t\tforeach (cell, attributes)\n\t\t{\n\t\t\tForm_pg_attribute attr = (Form_pg_attribute) lfirst(cell);\n\t\t\tFmgrInfo *finfo = NULL;\n\t\t\tif (bloom1_get_hash_function(attr->atttypid, &finfo) == NULL)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_FUNCTION),\n\t\t\t\t\t\t errmsg(\"invalid bloom filter column type %s, name: %s\",\n\t\t\t\t\t\t\t\tformat_type_be(attr->atttypid),\n\t\t\t\t\t\t\t\tNameStr(attr->attname)),\n\t\t\t\t\t\t errdetail(\"Could not identify a hashing function for the type.\")));\n\t\t}\n\n\t\tcolumn_def =\n\t\t\tmakeColumnDef(compressed_column_metadata_name_list_v2(metadata_type, column_names),\n\t\t\t\t\t\t  ts_custom_type_cache_get(CUSTOM_TYPE_BLOOM1)->type_oid,\n\t\t\t\t\t\t  /* typmod = */ -1,\n\t\t\t\t\t\t  /* collation = */ 0);\n\n\t\t/*\n\t\t * We have our custom compression for bloom filters, and the\n\t\t * result is almost incompressible with lz4 (~2%), so disable it.\n\t\t */\n\t\tcolumn_def->storage = TYPSTORAGE_EXTERNAL;\n\n\t\t/* Composite bloom filters are more selective, try to store them inline. */\n\t\tif (list_length(column_names) > 1)\n\t\t{\n\t\t\tcolumn_def->storage = TYPSTORAGE_MAIN;\n\t\t}\n\t}\n\telse /* either min or max */\n\t{\n\t\tForm_pg_attribute attr = (Form_pg_attribute) lfirst(list_head(attributes));\n\t\tTypeCacheEntry *type = lookup_type_cache(attr->atttypid, TYPECACHE_LT_OPR);\n\n\t\t/*\n\t\t * a comparison operator if required for min max operations\n\t\t */\n\t\tif (!OidIsValid(type->lt_opr))\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_FUNCTION),\n\t\t\t\t\t errmsg(\"invalid minmax column type %s\", format_type_be(attr->atttypid)),\n\t\t\t\t\t errdetail(\"Could not identify a less-than operator for the type.\")));\n\n\t\tcolumn_def =\n\t\t\tmakeColumnDef(compressed_column_metadata_name_list_v2(metadata_type, column_names),\n\t\t\t\t\t\t  attr->atttypid,\n\t\t\t\t\t\t  attr->atttypmod,\n\t\t\t\t\t\t  attr->attcollation);\n\t\tif (attr->attstorage != TYPSTORAGE_PLAIN)\n\t\t{\n\t\t\tcolumn_def->storage = TYPSTORAGE_MAIN;\n\t\t}\n\t}\n\n\treturn column_def;\n}\n\n/*\n * return the columndef list for compressed hypertable.\n * we do this by getting the source hypertable's attrs,\n * 1.  validate the segmentby cols and orderby cols exists in this list and\n * 2. create the columndefs for the new compressed hypertable\n *     segmentby_cols have same datatype as the original table\n *     all other cols have COMPRESSEDDATA_TYPE type\n */\nstatic List *\nbuild_columndefs(CompressionSettings *settings, Oid src_reloid)\n{\n\tOid compresseddata_oid = ts_custom_type_cache_get(CUSTOM_TYPE_COMPRESSED_DATA)->type_oid;\n\tArrayType *segmentby = settings->fd.segmentby;\n\tList *compressed_column_defs = NIL;\n\tList *segmentby_column_defs = NIL;\n\tJsonb *sparse_cfg = settings->fd.index;\n\tSparseIndexSettings *parsed_settings =\n\t\tsparse_cfg ? ts_convert_to_sparse_index_settings(sparse_cfg) : NULL;\n\tBitmapset *all_composite_bloom_obj_ids = NULL;\n\tList *per_column_settings = ts_get_per_column_compression_settings(parsed_settings);\n\n\tRelation rel = table_open(src_reloid, AccessShareLock);\n\n\tTupleDesc tupdesc = rel->rd_att;\n\n\tint num_sparse_index_objects =\n\t\tparsed_settings != NULL ? list_length(parsed_settings->objects) : 0;\n\tList **composite_attr_lists = NULL;\n\tif (num_sparse_index_objects > 0)\n\t{\n\t\t/* Allocate an array of Lists that contain Form_pg_attribute elements for each sparse index\n\t\t * configuration object. Minmax and single bloom filter configuration objects will have a\n\t\t * single element list.\n\t\t */\n\t\tcomposite_attr_lists = palloc0(sizeof(List *) * num_sparse_index_objects);\n\t}\n\n\tfor (int attoffset = 0; attoffset < tupdesc->natts; attoffset++)\n\t{\n\t\tForm_pg_attribute attr = TupleDescAttr(tupdesc, attoffset);\n\t\tif (attr->attisdropped)\n\t\t\tcontinue;\n\t\tif (strncmp(NameStr(attr->attname),\n\t\t\t\t\tCOMPRESSION_COLUMN_METADATA_PREFIX,\n\t\t\t\t\tstrlen(COMPRESSION_COLUMN_METADATA_PREFIX)) == 0)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_RESERVED_NAME),\n\t\t\t\t\t errmsg(\"cannot convert tables with reserved column prefix '%s'\",\n\t\t\t\t\t\t\tCOMPRESSION_COLUMN_METADATA_PREFIX)));\n\n\t\tbool is_segmentby = ts_array_is_member(segmentby, NameStr(attr->attname));\n\t\tif (is_segmentby)\n\t\t{\n\t\t\tsegmentby_column_defs = lappend(segmentby_column_defs,\n\t\t\t\t\t\t\t\t\t\t\tmakeColumnDef(NameStr(attr->attname),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  attr->atttypid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  attr->atttypmod,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  attr->attcollation));\n\t\t\tcontinue;\n\t\t}\n\n\t\tPerColumnCompressionSettings *per_column_setting =\n\t\t\tper_column_settings ?\n\t\t\t\tts_get_per_column_compression_settings_by_column_name(per_column_settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  NameStr(attr->attname)) :\n\t\t\t\tNULL;\n\n\t\tif (per_column_setting != NULL && composite_attr_lists != NULL)\n\t\t{\n\t\t\tif (per_column_setting->minmax_obj_id != -1 &&\n\t\t\t\tper_column_setting->minmax_obj_id < num_sparse_index_objects)\n\t\t\t{\n\t\t\t\t/* Minmax index configuration objects will have a single element list */\n\t\t\t\tAssert(list_length(composite_attr_lists[per_column_setting->minmax_obj_id]) == 0);\n\t\t\t\tcomposite_attr_lists[per_column_setting->minmax_obj_id] =\n\t\t\t\t\tlappend(composite_attr_lists[per_column_setting->minmax_obj_id], attr);\n\t\t\t}\n\n\t\t\tif (per_column_setting->single_bloom_obj_id != -1 &&\n\t\t\t\tper_column_setting->single_bloom_obj_id < num_sparse_index_objects)\n\t\t\t{\n\t\t\t\t/* Single bloom filter configuration objects will have a single element list */\n\t\t\t\tAssert(list_length(composite_attr_lists[per_column_setting->single_bloom_obj_id]) ==\n\t\t\t\t\t   0);\n\t\t\t\tcomposite_attr_lists[per_column_setting->single_bloom_obj_id] =\n\t\t\t\t\tlappend(composite_attr_lists[per_column_setting->single_bloom_obj_id], attr);\n\t\t\t}\n\n\t\t\tif (per_column_setting->composite_bloom_index_obj_ids != NULL)\n\t\t\t{\n\t\t\t\t/* The bitmapset tells which sparse index configuration objects the current\n\t\t\t\t * column participates in. Iterate over the bitmapset and add an entry\n\t\t\t\t * to the composite_attr_lists. */\n\t\t\t\tint i = -1;\n\t\t\t\twhile ((i = bms_next_member(per_column_setting->composite_bloom_index_obj_ids,\n\t\t\t\t\t\t\t\t\t\t\ti)) >= 0)\n\t\t\t\t{\n\t\t\t\t\tcomposite_attr_lists[i] = lappend(composite_attr_lists[i], attr);\n\t\t\t\t}\n\n\t\t\t\t/* capture all composite bloom index objects */\n\t\t\t\tall_composite_bloom_obj_ids =\n\t\t\t\t\tbms_union(all_composite_bloom_obj_ids,\n\t\t\t\t\t\t\t  per_column_setting->composite_bloom_index_obj_ids);\n\t\t\t}\n\t\t}\n\n\t\t/*\n\t\t * This is either an orderby or a normal compressed column. We want to\n\t\t * have metadata for some of them.  Put the metadata columns before the\n\t\t * respective compressed column, because they are accessed before\n\t\t * decompression.\n\t\t */\n\t\tconst bool is_orderby = ts_array_is_member(settings->fd.orderby, NameStr(attr->attname));\n\t\tif (is_orderby)\n\t\t{\n\t\t\tint index = ts_array_position(settings->fd.orderby, NameStr(attr->attname));\n\t\t\tTypeCacheEntry *type = lookup_type_cache(attr->atttypid, TYPECACHE_LT_OPR);\n\n\t\t\t/*\n\t\t\t * We must be able to create the metadata for the orderby columns,\n\t\t\t * because it is required for sorting.\n\t\t\t */\n\t\t\tif (!OidIsValid(type->lt_opr))\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_FUNCTION),\n\t\t\t\t\t\t errmsg(\"invalid ordering column type %s\", format_type_be(attr->atttypid)),\n\t\t\t\t\t\t errdetail(\"Could not identify a less-than operator for the type.\")));\n\n\t\t\t/* segment_meta min and max columns */\n\t\t\tColumnDef *def = makeColumnDef(column_segment_min_name(index),\n\t\t\t\t\t\t\t\t\t\t   attr->atttypid,\n\t\t\t\t\t\t\t\t\t\t   attr->atttypmod,\n\t\t\t\t\t\t\t\t\t\t   attr->attcollation);\n\t\t\tdef->storage = TYPSTORAGE_PLAIN;\n\t\t\tcompressed_column_defs = lappend(compressed_column_defs, def);\n\t\t\tdef = makeColumnDef(column_segment_max_name(index),\n\t\t\t\t\t\t\t\tattr->atttypid,\n\t\t\t\t\t\t\t\tattr->atttypmod,\n\t\t\t\t\t\t\t\tattr->attcollation);\n\t\t\tdef->storage = TYPSTORAGE_PLAIN;\n\t\t\tcompressed_column_defs = lappend(compressed_column_defs, def);\n\t\t}\n\t\telse if (per_column_setting != NULL && composite_attr_lists != NULL)\n\t\t{\n\t\t\t/* check sparse index columndefs is applicable */\n\t\t\tbool is_bloom = per_column_setting->single_bloom_obj_id != -1;\n\t\t\tbool is_minmax = per_column_setting->minmax_obj_id != -1;\n\n\t\t\t/*\n\t\t\t * We allow only one sparse index per column. Columns used in the ORDER BY\n\t\t\t * clause implicitly have a minmax index and adding a bloom filter on them is not\n\t\t\t * allowed.\n\t\t\t *\n\t\t\t * The parser is expected to enforce this constraint earlier, but we check again\n\t\t\t * here as a safeguard.\n\t\t\t */\n\t\t\tEnsure((!is_bloom || !is_minmax),\n\t\t\t\t   \"Should not create bloom filter for minmax column \\\"%s\\\"\",\n\t\t\t\t   NameStr(attr->attname));\n\n\t\t\t/* build sparse index columndefs if applicable */\n\t\t\tif (is_bloom)\n\t\t\t{\n\t\t\t\tif (!ts_guc_enable_sparse_index_bloom)\n\t\t\t\t{\n\t\t\t\t\tereport(WARNING,\n\t\t\t\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t\t\t\t errmsg(\"Creating bloom sparse index is disabled\"),\n\t\t\t\t\t\t\t errhint(\"Either set \\\"enable_sparse_index_bloom\\\" to true or remove \"\n\t\t\t\t\t\t\t\t\t \"the bloom filter indexes from \\\"sparse_index\\\" configuration \"\n\t\t\t\t\t\t\t\t\t \"of the hypertable.\")));\n\t\t\t\t}\n\t\t\t\t/*\n\t\t\t\t * Add bloom filter sparse index for this column.\n\t\t\t\t */\n\t\t\t\tColumnDef *bloom_column_def =\n\t\t\t\t\tcreate_sparse_index_column_def(composite_attr_lists[per_column_setting\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t->single_bloom_obj_id],\n\t\t\t\t\t\t\t\t\t\t\t\t   bloom1_column_prefix);\n\n\t\t\t\tcompressed_column_defs = lappend(compressed_column_defs, bloom_column_def);\n\t\t\t}\n\t\t\telse if (is_minmax)\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * Add minmax sparse index for this column.\n\t\t\t\t */\n\t\t\t\tColumnDef *def =\n\t\t\t\t\tcreate_sparse_index_column_def(composite_attr_lists[per_column_setting\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t->minmax_obj_id],\n\t\t\t\t\t\t\t\t\t\t\t\t   \"min\");\n\t\t\t\tcompressed_column_defs = lappend(compressed_column_defs, def);\n\n\t\t\t\tdef = create_sparse_index_column_def(composite_attr_lists[per_column_setting\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  ->minmax_obj_id],\n\t\t\t\t\t\t\t\t\t\t\t\t\t \"max\");\n\t\t\t\tcompressed_column_defs = lappend(compressed_column_defs, def);\n\t\t\t}\n\t\t}\n\t\tcompressed_column_defs = lappend(compressed_column_defs,\n\t\t\t\t\t\t\t\t\t\t makeColumnDef(NameStr(attr->attname),\n\t\t\t\t\t\t\t\t\t\t\t\t\t   compresseddata_oid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   /* typmod = */ -1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   /* collOid = */ InvalidOid));\n\t}\n\n\t/* add the composite bloom columns */\n\tif (composite_attr_lists != NULL && per_column_settings != NULL)\n\t{\n\t\t/* iterate over the all_composite_bloom_obj_ids bitmapset */\n\t\tint i = -1;\n\t\twhile ((i = bms_next_member(all_composite_bloom_obj_ids, i)) >= 0)\n\t\t{\n\t\t\tAssert(i < num_sparse_index_objects);\n\t\t\tAssert(composite_attr_lists[i] != NULL);\n\t\t\tList *attr_list = composite_attr_lists[i];\n\t\t\tif (attr_list != NULL)\n\t\t\t{\n\t\t\t\tColumnDef *def = create_sparse_index_column_def(attr_list, bloom1_column_prefix);\n\t\t\t\tcompressed_column_defs = lappend(compressed_column_defs, def);\n\t\t\t}\n\t\t}\n\t}\n\n\t/*\n\t * Add the metadata columns. Count is always accessed, so put it first.\n\t */\n\tList *all_column_defs = list_make1(makeColumnDef(COMPRESSION_COLUMN_METADATA_COUNT_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t INT4OID,\n\t\t\t\t\t\t\t\t\t\t\t\t\t -1 /* typemod */,\n\t\t\t\t\t\t\t\t\t\t\t\t\t 0 /*collation*/));\n\n\t/*\n\t * Then, put all segmentby columns. They are likely to be used in filters\n\t * before decompression.\n\t */\n\tall_column_defs = list_concat(all_column_defs, segmentby_column_defs);\n\n\t/*\n\t * Then, put all the compressed columns.\n\t */\n\tall_column_defs = list_concat(all_column_defs, compressed_column_defs);\n\n\ttable_close(rel, AccessShareLock);\n\n\treturn all_column_defs;\n}\n\n/* use this api for the case when you add a single column to a table that already has\n * compression setup\n * such as ALTER TABLE xyz ADD COLUMN .....\n */\nstatic ColumnDef *\nbuild_columndef_singlecolumn(const char *colname, Oid typid)\n{\n\tOid compresseddata_oid = ts_custom_type_cache_get(CUSTOM_TYPE_COMPRESSED_DATA)->type_oid;\n\n\tif (strncmp(colname,\n\t\t\t\tCOMPRESSION_COLUMN_METADATA_PREFIX,\n\t\t\t\tstrlen(COMPRESSION_COLUMN_METADATA_PREFIX)) == 0)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_RESERVED_NAME),\n\t\t\t\t errmsg(\"cannot convert tables with reserved column prefix '%s'\",\n\t\t\t\t\t\tCOMPRESSION_COLUMN_METADATA_PREFIX)));\n\n\treturn makeColumnDef(colname, compresseddata_oid, -1 /*typmod*/, 0 /*collation*/);\n}\n\n/*\n * Create compress chunk for specific table.\n *\n * If table_id is InvalidOid, create a new table.\n *\n */\nChunk *\ncreate_compress_chunk(Hypertable *compress_ht, Chunk *src_chunk, Oid table_id)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tCatalogSecurityContext sec_ctx;\n\tChunk *compress_chunk;\n\tint namelen;\n\tOid tablespace_oid;\n\n\tAssert(compress_ht->space->num_dimensions == 0);\n\n\t/* Create a new catalog entry for chunk based on uncompressed chunk */\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tcompress_chunk =\n\t\tts_chunk_create_base(ts_catalog_table_next_seq_id(catalog, CHUNK), 0, RELKIND_RELATION);\n\tts_catalog_restore_user(&sec_ctx);\n\n\tcompress_chunk->fd.hypertable_id = compress_ht->fd.id;\n\tcompress_chunk->hypertable_relid = compress_ht->main_table_relid;\n\tnamestrcpy(&compress_chunk->fd.schema_name, INTERNAL_SCHEMA_NAME);\n\n\tif (OidIsValid(table_id))\n\t{\n\t\tRelation table_rel = table_open(table_id, AccessShareLock);\n\t\tstrncpy(NameStr(compress_chunk->fd.table_name),\n\t\t\t\tRelationGetRelationName(table_rel),\n\t\t\t\tNAMEDATALEN);\n\t\ttable_close(table_rel, AccessShareLock);\n\t}\n\telse\n\t{\n\t\t/* Fail if we overflow the name limit */\n\t\tnamelen = snprintf(NameStr(compress_chunk->fd.table_name),\n\t\t\t\t\t\t   NAMEDATALEN,\n\t\t\t\t\t\t   \"compress%s_%d_chunk\",\n\t\t\t\t\t\t   NameStr(compress_ht->fd.associated_table_prefix),\n\t\t\t\t\t\t   compress_chunk->fd.id);\n\n\t\tif (namelen >= NAMEDATALEN)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t\t errmsg(\"invalid name \\\"%s\\\" for compressed chunk\",\n\t\t\t\t\t\t\tNameStr(compress_chunk->fd.table_name)),\n\t\t\t\t\t errdetail(\"The associated table prefix is too long.\")));\n\t}\n\n\t/* Insert chunk */\n\tts_chunk_insert_lock(compress_chunk, RowExclusiveLock);\n\n\t/* Create the actual table relation for the chunk\n\t * Note that we have to pick the tablespace here as the compressed ht doesn't have dimensions\n\t * on which to base this decision. We simply pick the same tablespace as the uncompressed chunk\n\t * for now.\n\t */\n\ttablespace_oid = get_rel_tablespace(src_chunk->table_id);\n\tCompressionSettings *settings = ts_compression_settings_get(src_chunk->hypertable_relid);\n\n\t/*\n\t * On hypertables created with CREATE TABLE ... WITH we enable compression\n\t * by default but do not create CompressionSettings immediately assuming\n\t * that we have more information available when the first compression\n\t * is actually triggered allowing us to generate better compression\n\t * settings.\n\t */\n\n\tif (!settings)\n\t{\n\t\tsettings = ts_compression_settings_create(src_chunk->hypertable_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t  InvalidOid,\n\t\t\t\t\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t  NULL);\n\t}\n\n\tHypertable *ht = ts_hypertable_get_by_id(src_chunk->fd.hypertable_id);\n\tcompression_settings_set_defaults(ht, settings, ts_alter_table_with_clause_parse(NIL));\n\n\tif (OidIsValid(table_id))\n\t\tcompress_chunk->table_id = table_id;\n\telse\n\t{\n\t\tList *column_defs = build_columndefs(settings, src_chunk->table_id);\n\t\tcompress_chunk->table_id = compression_chunk_create(src_chunk,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcompress_chunk,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcolumn_defs,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttablespace_oid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsettings);\n\t}\n\n\tif (!OidIsValid(compress_chunk->table_id))\n\t\telog(ERROR, \"could not create columnstore chunk table\");\n\n\t/* Materialize current compression settings for this chunk */\n\tts_compression_settings_materialize(settings, src_chunk->table_id, compress_chunk->table_id);\n\n\t/* if the src chunk is not in the default tablespace, the compressed indexes\n\t * should also be in a non-default tablespace. IN the usual case, this is inferred\n\t * from the hypertable's and chunk's tablespace info. We do not propagate\n\t * attach_tablespace settings to the compressed hypertable. So we have to explicitly\n\t * pass the tablespace information here\n\t */\n\tts_chunk_index_create_all(compress_chunk->fd.hypertable_id,\n\t\t\t\t\t\t\t  compress_chunk->hypertable_relid,\n\t\t\t\t\t\t\t  compress_chunk->fd.id,\n\t\t\t\t\t\t\t  compress_chunk->table_id,\n\t\t\t\t\t\t\t  tablespace_oid);\n\n\treturn compress_chunk;\n}\n\n/* Add  the hypertable time column to the end of the orderby list if\n * it's not already in the orderby or segmentby. */\nstatic OrderBySettings\nadd_time_to_order_by_if_not_included(OrderBySettings obs, ArrayType *segmentby, Hypertable *ht)\n{\n\tconst Dimension *time_dim;\n\tconst char *time_col_name;\n\tbool found = false;\n\n\ttime_dim = hyperspace_get_open_dimension(ht->space, 0);\n\tif (!time_dim)\n\t\treturn obs;\n\n\ttime_col_name = get_attname(ht->main_table_relid, time_dim->column_attno, false);\n\n\tif (ts_array_is_member(obs.orderby, time_col_name))\n\t\tfound = true;\n\n\tif (ts_array_is_member(segmentby, time_col_name))\n\t\tfound = true;\n\n\tif (!found)\n\t{\n\t\t/* Add time DESC NULLS FIRST to order by settings */\n\t\tobs.orderby = ts_array_add_element_text(obs.orderby, pstrdup(time_col_name));\n\t\tobs.orderby_desc = ts_array_add_element_bool(obs.orderby_desc, true);\n\t\tobs.orderby_nullsfirst = ts_array_add_element_bool(obs.orderby_nullsfirst, true);\n\t}\n\treturn obs;\n}\n\n/* returns list of constraints that need to be cloned on the compressed hypertable\n * This is limited to foreign key constraints now\n */\nstatic void\nvalidate_existing_constraints(Hypertable *ht, CompressionSettings *settings)\n{\n\tRelation pg_constr;\n\tSysScanDesc scan;\n\tScanKeyData scankey;\n\tHeapTuple tuple;\n\n\tArrayType *arr;\n\n\tAssert(ht->main_table_relid == settings->fd.relid);\n\tpg_constr = table_open(ConstraintRelationId, AccessShareLock);\n\n\tScanKeyInit(&scankey,\n\t\t\t\tAnum_pg_constraint_conrelid,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_OIDEQ,\n\t\t\t\tObjectIdGetDatum(settings->fd.relid));\n\n\tscan = systable_beginscan(pg_constr, ConstraintRelidTypidNameIndexId, true, NULL, 1, &scankey);\n\twhile (HeapTupleIsValid(tuple = systable_getnext(scan)))\n\t{\n\t\tForm_pg_constraint form = (Form_pg_constraint) GETSTRUCT(tuple);\n\n\t\t/*\n\t\t * We check primary, unique, and exclusion constraints.\n\t\t */\n\t\tif (form->contype == CONSTRAINT_CHECK || form->contype == CONSTRAINT_TRIGGER\n#if PG17_GE\n\t\t\t|| form->contype == CONSTRAINT_NOTNULL\n\t\t/* CONSTRAINT_NOTNULL introduced in PG17, see b0e96f311985 */\n#endif\n\t\t)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\t\telse if (form->contype == CONSTRAINT_EXCLUSION)\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"constraint %s is not supported for converting to columnstore\",\n\t\t\t\t\t\t\tNameStr(form->conname)),\n\t\t\t\t\t errhint(\"Exclusion constraints are not supported on hypertables that are \"\n\t\t\t\t\t\t\t \"converted to columnstore.\")));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tint j, numkeys;\n\t\t\tint16 *attnums;\n\t\t\tbool is_null;\n\n\t\t\t/* Extract the conkey array, ie, attnums of PK's columns */\n\t\t\tDatum adatum = heap_getattr(tuple,\n\t\t\t\t\t\t\t\t\t\tAnum_pg_constraint_conkey,\n\t\t\t\t\t\t\t\t\t\tRelationGetDescr(pg_constr),\n\t\t\t\t\t\t\t\t\t\t&is_null);\n\t\t\tif (is_null)\n\t\t\t{\n\t\t\t\tOid oid = heap_getattr(tuple,\n\t\t\t\t\t\t\t\t\t   Anum_pg_constraint_oid,\n\t\t\t\t\t\t\t\t\t   RelationGetDescr(pg_constr),\n\t\t\t\t\t\t\t\t\t   &is_null);\n\t\t\t\telog(ERROR, \"null conkey for constraint %u\", oid);\n\t\t\t}\n\n\t\t\tarr = DatumGetArrayTypeP(adatum); /* ensure not toasted */\n\t\t\tnumkeys = ts_array_length(arr);\n\t\t\tattnums = (int16 *) ARR_DATA_PTR(arr);\n\t\t\tfor (j = 0; j < numkeys; j++)\n\t\t\t{\n\t\t\t\tconst char *attname = get_attname(settings->fd.relid, attnums[j], false);\n\n\t\t\t\t/* is colno a segment-by or order_by column */\n\t\t\t\tif (!form->conindid && (settings->fd.segmentby && settings->fd.orderby) &&\n\t\t\t\t\t!ts_array_is_member(settings->fd.segmentby, attname) &&\n\t\t\t\t\t!ts_array_is_member(settings->fd.orderby, attname))\n\t\t\t\t\tereport(WARNING,\n\t\t\t\t\t\t\t(errmsg(\"column \\\"%s\\\" should be used for segmenting or ordering\",\n\t\t\t\t\t\t\t\t\tattname)));\n\t\t\t}\n\t\t}\n\t}\n\n\tsystable_endscan(scan);\n\ttable_close(pg_constr, AccessShareLock);\n}\n\n/*\n * Validate existing indexes on the hypertable. Note that there can be indexes\n * that do not have a corresponding constraint.\n *\n * We pass in a list of indexes that we should ignore since these are checked\n * by the constraint checking above.\n */\nstatic void\nvalidate_existing_indexes(Hypertable *ht, CompressionSettings *settings)\n{\n\tRelation pg_index;\n\tHeapTuple htup;\n\tScanKeyData skey;\n\tSysScanDesc indscan;\n\n\tScanKeyInit(&skey,\n\t\t\t\tAnum_pg_index_indrelid,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_OIDEQ,\n\t\t\t\tObjectIdGetDatum(ht->main_table_relid));\n\n\tpg_index = table_open(IndexRelationId, AccessShareLock);\n\tindscan = systable_beginscan(pg_index, IndexIndrelidIndexId, true, NULL, 1, &skey);\n\n\twhile (HeapTupleIsValid(htup = systable_getnext(indscan)))\n\t{\n\t\tForm_pg_index index = (Form_pg_index) GETSTRUCT(htup);\n\n\t\t/* We can ignore indexes that are being dropped, invalid indexes,\n\t\t * exclusion indexes, and any indexes checked by the constraint\n\t\t * checking. We can also skip checks below if the index is not a\n\t\t * unique index. */\n\t\tif (!index->indislive || !index->indisvalid || index->indisexclusion || !index->indisunique)\n\t\t\tcontinue;\n\n\t\t/* Now we check that all columns of the unique index are part of the\n\t\t * segmentby columns. */\n\t\tfor (int i = 0; i < index->indnkeyatts; i++)\n\t\t{\n\t\t\tint attno = index->indkey.values[i];\n\t\t\tif (attno == 0)\n\t\t\t\tcontinue; /* skip check for expression column */\n\t\t\tconst char *attname = get_attname(ht->main_table_relid, attno, false);\n\t\t\tif ((settings->fd.segmentby && settings->fd.orderby) &&\n\t\t\t\t!ts_array_is_member(settings->fd.segmentby, attname) &&\n\t\t\t\t!ts_array_is_member(settings->fd.orderby, attname))\n\t\t\t\tereport(WARNING,\n\t\t\t\t\t\t(errmsg(\"column \\\"%s\\\" should be used for segmenting or ordering\",\n\t\t\t\t\t\t\t\tattname)));\n\t\t}\n\t}\n\tsystable_endscan(indscan);\n\ttable_close(pg_index, AccessShareLock);\n}\n\nstatic void\ndrop_existing_compression_table(Hypertable *ht)\n{\n\tif (ts_chunk_exists_with_compression(ht->fd.id))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot drop columnstore-enabled hypertable with columnstore chunks\")));\n\n\tHypertable *compressed = ts_hypertable_get_by_id(ht->fd.compressed_hypertable_id);\n\tif (compressed == NULL)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t errmsg(\"columnstore-enabled hypertable not found\"),\n\t\t\t\t errdetail(\"columnstore was enabled on \\\"%s\\\", but its internal\"\n\t\t\t\t\t\t   \" columnstore hypertable could not be found.\",\n\t\t\t\t\t\t   NameStr(ht->fd.table_name))));\n\n\t/* need to drop the old compressed hypertable in case the segment by columns changed (and\n\t * thus the column types of compressed hypertable need to change) */\n\tts_hypertable_drop(compressed, DROP_RESTRICT);\n\tts_hypertable_unset_compressed(ht);\n}\n\nstatic bool\ndisable_compression(Hypertable *ht, WithClauseResult *with_clause_options)\n{\n\tif (!TS_HYPERTABLE_HAS_COMPRESSION_ENABLED(ht))\n\t\t/* compression is not enabled, so just return */\n\t\treturn false;\n\n\tif (ts_chunk_exists_with_compression(ht->fd.id))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot disable columnstore on hypertable with columnstore chunks\")));\n\n\tif (TS_HYPERTABLE_HAS_COMPRESSION_TABLE(ht))\n\t\tdrop_existing_compression_table(ht);\n\telse\n\t{\n\t\tts_hypertable_unset_compressed(ht);\n\t}\n\n\tts_compression_settings_delete(ht->main_table_relid);\n\n\treturn true;\n}\n\n/* Add column to internal compression table */\nstatic void\nadd_column_to_compression_table(Oid relid, CompressionSettings *settings, ColumnDef *coldef)\n{\n\tAlterTableCmd *addcol_cmd;\n\n\t/* create altertable stmt to add column to the compressed hypertable */\n\t// Assert(TS_HYPERTABLE_IS_INTERNAL_COMPRESSION_TABLE(compress_ht));\n\taddcol_cmd = makeNode(AlterTableCmd);\n\taddcol_cmd->subtype = AT_AddColumn;\n\taddcol_cmd->def = (Node *) coldef;\n\taddcol_cmd->missing_ok = false;\n\n\t/* alter the table and add column */\n\tts_alter_table_with_event_trigger(relid, NULL, list_make1(addcol_cmd), true);\n\tmodify_compressed_toast_table_storage(settings, list_make1(coldef), relid);\n}\n\n/* Drop column from internal compression table, drop the bloom filter columns as well and\n * update the compression settings for the chunk */\nstatic void\ndrop_column_from_compression_table(CompressionSettings *comp_settings, char *name)\n{\n\tOid relid = comp_settings->fd.compress_relid;\n\tAlterTableCmd *cmd;\n\tList *cmds = NIL;\n\tJsonb *jb = comp_settings->fd.index;\n\n\t/* create altertable stmt to drop column from the compressed hypertable */\n\tcmd = makeNode(AlterTableCmd);\n\tcmd->subtype = AT_DropColumn;\n\tcmd->name = name;\n\tcmd->missing_ok = true;\n\tcmds = list_make1(cmd);\n\n\tif (jb)\n\t{\n\t\tSparseIndexSettings *parsed_settings = ts_convert_to_sparse_index_settings(jb);\n\t\tif (parsed_settings)\n\t\t{\n\t\t\tbool removed_any = false;\n\n\t\t\tListCell *obj_cell = NULL;\n\t\t\tforeach (obj_cell, parsed_settings->objects)\n\t\t\t{\n\t\t\t\tbool removed = false;\n\t\t\t\tconst char *bloom_column_name = NULL;\n\t\t\t\tSparseIndexSettingsObject *obj = (SparseIndexSettingsObject *) lfirst(obj_cell);\n\t\t\t\tforeach_ptr(SparseIndexSettingsPair, pair, obj->pairs)\n\t\t\t\t{\n\t\t\t\t\tif (strcmp(pair->key, ts_sparse_index_common_keys[SparseIndexKeyCol]) != 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tforeach_ptr(const char, value, pair->values)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (strcmp(value, name) == 0)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tremoved = true;\n\t\t\t\t\t\t\tAssert(list_length(pair->values) <= MAX_BLOOM_FILTER_COLUMNS);\n\n\t\t\t\t\t\t\tbloom_column_name =\n\t\t\t\t\t\t\t\tcompressed_column_metadata_name_list_v2(bloom1_column_prefix,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tpair->values);\n\t\t\t\t\t\t\tAssert(bloom_column_name != NULL);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (removed)\n\t\t\t\t\t{\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t/* if the column was removed, we need to remove the object from the list */\n\t\t\t\tif (removed)\n\t\t\t\t{\n\t\t\t\t\tremoved_any = true;\n\t\t\t\t\tif (bloom_column_name)\n\t\t\t\t\t{\n\t\t\t\t\t\tcmd = makeNode(AlterTableCmd);\n\t\t\t\t\t\tcmd->subtype = AT_DropColumn;\n\t\t\t\t\t\tcmd->name = pstrdup(bloom_column_name);\n\t\t\t\t\t\tcmd->missing_ok = true;\n\t\t\t\t\t\tcmds = lappend(cmds, cmd);\n\t\t\t\t\t}\n\t\t\t\t\tparsed_settings->objects =\n\t\t\t\t\t\tforeach_delete_current(parsed_settings->objects, obj_cell);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (removed_any)\n\t\t\t{\n\t\t\t\tjb = ts_convert_from_sparse_index_settings(parsed_settings);\n\t\t\t\tcomp_settings->fd.index = jb;\n\t\t\t\tts_compression_settings_update(comp_settings);\n\t\t\t}\n\t\t\tts_free_sparse_index_settings(parsed_settings);\n\t\t}\n\t}\n\n\t/* alter the table and drop column */\n\tts_alter_table_with_event_trigger(relid, NULL, cmds, true);\n}\n\nstatic bool\nupdate_compress_chunk_time_interval(Hypertable *ht, WithClauseResult *with_clause_options)\n{\n\tconst Dimension *time_dim = hyperspace_get_open_dimension(ht->space, 0);\n\tif (!time_dim)\n\t\treturn false;\n\n\tInterval *compress_interval =\n\t\tts_compress_hypertable_parse_chunk_time_interval(with_clause_options, ht);\n\tif (!compress_interval)\n\t{\n\t\treturn false;\n\t}\n\tint64 compress_interval_usec =\n\t\tts_interval_value_to_internal(IntervalPGetDatum(compress_interval), INTERVALOID);\n\tif (compress_interval_usec % time_dim->fd.interval_length > 0)\n\t\telog(WARNING,\n\t\t\t \"compress chunk interval is not a multiple of chunk interval, you should use a \"\n\t\t\t \"factor of chunk interval to merge as much as possible\");\n\treturn ts_hypertable_set_compress_interval(ht, compress_interval_usec);\n}\n\n/*\n * enables compression for the passed in table by\n * creating a compression hypertable with special properties\n * Note: caller should check security permissions\n *\n * Return true if compression was enabled, false otherwise.\n *\n * Steps:\n * 1. Check existing constraints on the table -> can we support them with compression?\n * 2. Create internal compression table + mark hypertable as compression enabled\n * 3. Add catalog entries to hypertable_compression to record compression settings.\n * 4. Copy constraints to internal compression table\n */\nbool\ntsl_process_compress_table(Hypertable *ht, WithClauseResult *with_clause_options)\n{\n\tint32 compress_htid;\n\tbool compress_disable = !with_clause_options[AlterTableFlagColumnstore].is_default &&\n\t\t\t\t\t\t\t!DatumGetBool(with_clause_options[AlterTableFlagColumnstore].parsed);\n\tCompressionSettings *settings;\n\n\tts_feature_flag_check(FEATURE_HYPERTABLE_COMPRESSION);\n\n\tvalidate_hypertable_for_compression(ht);\n\n\t/* Lock the uncompressed ht in exclusive mode and keep till end of txn */\n\tLockRelationOid(ht->main_table_relid, AccessExclusiveLock);\n\n\t/* reload info after lock */\n\tht = ts_hypertable_get_by_id(ht->fd.id);\n\n\tif (compress_disable)\n\t{\n\t\treturn disable_compression(ht, with_clause_options);\n\t}\n\n\tif (!with_clause_options[AlterTableFlagCompressChunkTimeInterval].is_default)\n\t{\n\t\tupdate_compress_chunk_time_interval(ht, with_clause_options);\n\t}\n\n\tsettings = ts_compression_settings_get(ht->main_table_relid);\n\tif (!settings)\n\t{\n\t\tsettings = ts_compression_settings_create(ht->main_table_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t  InvalidOid,\n\t\t\t\t\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t  NULL);\n\t}\n\n\tcompression_settings_set_manually_for_alter(ht, settings, with_clause_options);\n\n\tif (!TS_HYPERTABLE_HAS_COMPRESSION_TABLE(ht))\n\t{\n\t\t/* take explicit locks on catalog tables and keep them till end of txn */\n\t\tLockRelationOid(catalog_get_table_id(ts_catalog_get(), HYPERTABLE), RowExclusiveLock);\n\n\t\t/* Check if we can create a compressed hypertable with existing\n\t\t * constraints and indexes. */\n\t\tvalidate_existing_constraints(ht, settings);\n\t\tvalidate_existing_indexes(ht, settings);\n\n\t\tOid ownerid = ts_rel_get_owner(ht->main_table_relid);\n\t\tOid tablespace_oid = get_rel_tablespace(ht->main_table_relid);\n\t\tcompress_htid = compression_hypertable_create(ht, ownerid, tablespace_oid);\n\t\tts_hypertable_set_compressed(ht, compress_htid);\n\t}\n\n\t/*\n\t * Check for suboptimal compressed chunk merging configuration\n\t *\n\t * When compress_chunk_time_interval is configured to merge chunks during compression the\n\t * primary dimension should be the first compress_orderby column otherwise chunk merging will\n\t * require decompression.\n\t */\n\tDimension *dim = ts_hyperspace_get_mutable_dimension(ht->space, DIMENSION_TYPE_OPEN, 0);\n\tif (dim && dim->fd.compress_interval_length &&\n\t\tts_array_position(settings->fd.orderby, NameStr(dim->fd.column_name)) != 1)\n\t{\n\t\tereport(WARNING,\n\t\t\t\t(errcode(ERRCODE_WARNING),\n\t\t\t\t errmsg(\"compress_chunk_time_interval configured and primary dimension not \"\n\t\t\t\t\t\t\"first column in compress_orderby\"),\n\t\t\t\t errhint(\"consider setting \\\"%s\\\" as first compress_orderby column\",\n\t\t\t\t\t\t NameStr(dim->fd.column_name))));\n\t}\n\n\t/* do not release any locks, will get released by xact end */\n\treturn true;\n}\n\n/*\n * Verify uncompressed hypertable is compatible with conpression\n */\nstatic void\nvalidate_hypertable_for_compression(Hypertable *ht)\n{\n\tif (TS_HYPERTABLE_IS_INTERNAL_COMPRESSION_TABLE(ht))\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot compress internal columnstore hypertable\")));\n\t}\n\n\t/*check row security settings for the table */\n\tif (ts_has_row_security(ht->main_table_relid))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"columnstore cannot be used on table with row security\")));\n\n\tRelation rel = table_open(ht->main_table_relid, AccessShareLock);\n\tTupleDesc tupdesc = RelationGetDescr(rel);\n\n\t/*\n\t * This is only a rough estimate and the actual row size might be different.\n\t * We use this only to show a warning when the row size is close to the\n\t * maximum row size.\n\t */\n\tSize row_size = MAXALIGN(SizeofHeapTupleHeader);\n\trow_size += 8;\t/* sequence_num */\n\trow_size += 4;\t/* count */\n\trow_size += 16; /* min/max */\n\tfor (int attno = 0; attno < tupdesc->natts; attno++)\n\t{\n\t\tForm_pg_attribute attr = TupleDescAttr(tupdesc, attno);\n\n\t\tif (attr->attisdropped)\n\t\t\tcontinue;\n\n\t\trow_size += 18; /* assume 18 bytes for each compressed column (varlena) */\n\n\t\tif (strncmp(NameStr(attr->attname),\n\t\t\t\t\tCOMPRESSION_COLUMN_METADATA_PREFIX,\n\t\t\t\t\tstrlen(COMPRESSION_COLUMN_METADATA_PREFIX)) == 0)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_RESERVED_NAME),\n\t\t\t\t\t errmsg(\"cannot convert tables with reserved column prefix '%s' to columnstore\",\n\t\t\t\t\t\t\tCOMPRESSION_COLUMN_METADATA_PREFIX)));\n\t}\n\n\tif (row_size > MaxHeapTupleSize)\n\t{\n\t\tereport(WARNING,\n\t\t\t\t(errmsg(\"compressed row size might exceed maximum row size\"),\n\t\t\t\t errdetail(\"Estimated row size of columnstore-enabled hypertable is %zu. This \"\n\t\t\t\t\t\t   \"exceeds the \"\n\t\t\t\t\t\t   \"maximum size of %zu and can cause conversion of chunks to columnstore \"\n\t\t\t\t\t\t   \"to fail.\",\n\t\t\t\t\t\t   row_size,\n\t\t\t\t\t\t   MaxHeapTupleSize)));\n\t}\n\n\t/*\n\t * Check that all triggers are ok for compressed tables.\n\t */\n\tRelation pg_trigger = table_open(TriggerRelationId, AccessShareLock);\n\tHeapTuple tuple;\n\n\tScanKeyData key;\n\tScanKeyInit(&key,\n\t\t\t\tAnum_pg_trigger_tgrelid,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_OIDEQ,\n\t\t\t\tObjectIdGetDatum(ht->main_table_relid));\n\n\tSysScanDesc scan = systable_beginscan(pg_trigger, TriggerRelidNameIndexId, true, NULL, 1, &key);\n\n\twhile (HeapTupleIsValid(tuple = systable_getnext(scan)))\n\t{\n\t\tbool oldtable_isnull;\n\t\tForm_pg_trigger trigrec = (Form_pg_trigger) GETSTRUCT(tuple);\n\n\t\t/*\n\t\t * We currently don't support transition tables for DELETE triggers\n\t\t * on compressed tables because deleting a complete segment will not build a\n\t\t * transition table for the delete.\n\t\t */\n\t\tfastgetattr(tuple, Anum_pg_trigger_tgoldtable, pg_trigger->rd_att, &oldtable_isnull);\n\t\tif (!oldtable_isnull && !TRIGGER_FOR_ROW(trigrec->tgtype) &&\n\t\t\tTRIGGER_FOR_DELETE(trigrec->tgtype))\n\t\t\tereport(ERROR,\n\t\t\t\t\terrcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\terrmsg(\"DELETE triggers with transition tables not supported\"));\n\t}\n\n\tsystable_endscan(scan);\n\ttable_close(pg_trigger, AccessShareLock);\n\ttable_close(rel, AccessShareLock);\n}\n\n/*\n * Get the default segment by value for a hypertable\n */\nstatic ArrayType *\ncompression_setting_segmentby_get_default(const Hypertable *ht)\n{\n\tStringInfoData command;\n\tStringInfoData result;\n\tint res;\n\tArrayType *column_res = NULL;\n\tDatum datum;\n\ttext *message;\n\tbool isnull;\n\tMemoryContext upper = CurrentMemoryContext;\n\tMemoryContext old;\n\tint32 confidence = -1;\n\tOid default_segmentby_fn = ts_guc_default_segmentby_fn_oid();\n\n\tif (!OidIsValid(default_segmentby_fn))\n\t{\n\t\telog(LOG_SERVER_ONLY,\n\t\t\t \"segment_by default: hypertable=\\\"%s\\\" columns=\\\"\\\" function: \\\"\\\" confidence=-1\",\n\t\t\t get_rel_name(ht->main_table_relid));\n\t\treturn NULL;\n\t}\n\n\t/* Lock down search_path */\n\tint save_nestlevel = NewGUCNestLevel();\n\tRestrictSearchPath();\n\n\tinitStringInfo(&command);\n\tappendStringInfo(&command,\n\t\t\t\t\t \"SELECT \"\n\t\t\t\t\t \" (SELECT array_agg(x)  \"\n\t\t\t\t\t \" FROM jsonb_array_elements_text(seg_by->'columns') t(x))::text[], \"\n\t\t\t\t\t \" seg_by->>'message', \"\n\t\t\t\t\t \" (seg_by->>'confidence')::int \"\n\t\t\t\t\t \"FROM %s.%s(%d) seg_by\",\n\t\t\t\t\t quote_identifier(get_namespace_name(get_func_namespace(default_segmentby_fn))),\n\t\t\t\t\t quote_identifier(get_func_name(default_segmentby_fn)),\n\t\t\t\t\t ht->main_table_relid);\n\n\tif (SPI_connect() != SPI_OK_CONNECT)\n\t\telog(ERROR, \"could not connect to SPI\");\n\n\tres = SPI_execute(command.data, true /* read_only */, 0 /*count*/);\n\n\tif (res < 0)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t (errmsg(\"could not get the default segment by for a hypertable \\\"%s\\\"\",\n\t\t\t\t\t\t get_rel_name(ht->main_table_relid)))));\n\n\told = MemoryContextSwitchTo(upper);\n\tdatum = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);\n\tif (!isnull)\n\t\tcolumn_res = DatumGetArrayTypePCopy(datum);\n\tMemoryContextSwitchTo(old);\n\n\tdatum = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 2, &isnull);\n\n\tif (!isnull)\n\t{\n\t\tmessage = DatumGetTextPP(datum);\n\t\telog(LOG_SERVER_ONLY,\n\t\t\t \"there was some uncertainty picking the default segment by for the hypertable: %s\",\n\t\t\t text_to_cstring(message));\n\t}\n\n\tdatum = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 3, &isnull);\n\tif (!isnull)\n\t{\n\t\tconfidence = DatumGetInt32(datum);\n\t}\n\n\tpfree(command.data);\n\n\t/* Reset search path since this can be executed as part of a larger transaction */\n\tAtEOXact_GUC(false, save_nestlevel);\n\n\tres = SPI_finish();\n\tif (res != SPI_OK_FINISH)\n\t\telog(ERROR, \"SPI_finish failed: %s\", SPI_result_code_string(res));\n\n\tinitStringInfo(&result);\n\tts_array_append_stringinfo(column_res, &result);\n\n\telog(LOG_SERVER_ONLY,\n\t\t \"segment_by default: hypertable=\\\"%s\\\" columns=\\\"%s\\\" function: \\\"%s.%s\\\" confidence=%d\",\n\t\t get_rel_name(ht->main_table_relid),\n\t\t result.data,\n\t\t get_namespace_name(get_func_namespace(default_segmentby_fn)),\n\t\t get_func_name(default_segmentby_fn),\n\t\t confidence);\n\tpfree(result.data);\n\treturn column_res;\n}\n\n/*\n * Get the default segment by value for a hypertable\n */\nstatic OrderBySettings\ncompression_setting_orderby_get_default(Hypertable *ht, ArrayType *segmentby)\n{\n\tStringInfoData command;\n\tint res;\n\ttext *column_res = NULL;\n\tDatum datum;\n\ttext *message;\n\tbool isnull;\n\tMemoryContext upper = CurrentMemoryContext;\n\tMemoryContext old;\n\tchar *orderby;\n\tint32 confidence = -1;\n\n\tOid types[] = { TEXTARRAYOID };\n\tDatum values[] = { PointerGetDatum(segmentby) };\n\tchar nulls[] = { segmentby == NULL ? 'n' : 'v' };\n\tOid orderby_fn = ts_guc_default_orderby_fn_oid();\n\n\tif (!OidIsValid(orderby_fn))\n\t{\n\t\t/* fallback to original logic */\n\t\tOrderBySettings obs = (OrderBySettings){ 0 };\n\t\tobs = add_time_to_order_by_if_not_included(obs, segmentby, ht);\n\t\telog(LOG_SERVER_ONLY,\n\t\t\t \"order_by default: hypertable=\\\"%s\\\" function=\\\"\\\" confidence=-1\",\n\t\t\t get_rel_name(ht->main_table_relid));\n\t\treturn obs;\n\t}\n\n\t/* Lock down search_path */\n\tint save_nestlevel = NewGUCNestLevel();\n\tRestrictSearchPath();\n\n\tinitStringInfo(&command);\n\tappendStringInfo(&command,\n\t\t\t\t\t \"SELECT \"\n\t\t\t\t\t \" (SELECT string_agg(x, ', ') FROM \"\n\t\t\t\t\t \"jsonb_array_elements_text(seg_by->'clauses') \"\n\t\t\t\t\t \"t(x))::text, \"\n\t\t\t\t\t \" seg_by->>'message', \"\n\t\t\t\t\t \" (seg_by->>'confidence')::int \"\n\t\t\t\t\t \"FROM %s.%s(%d, coalesce($1, array[]::text[])) seg_by\",\n\t\t\t\t\t quote_identifier(get_namespace_name(get_func_namespace(orderby_fn))),\n\t\t\t\t\t quote_identifier(get_func_name(orderby_fn)),\n\t\t\t\t\t ht->main_table_relid);\n\n\tif (SPI_connect() != SPI_OK_CONNECT)\n\t\telog(ERROR, \"could not connect to SPI\");\n\n\tres = SPI_execute_with_args(command.data,\n\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\ttypes,\n\t\t\t\t\t\t\t\tvalues,\n\t\t\t\t\t\t\t\tnulls,\n\t\t\t\t\t\t\t\ttrue /* read_only */,\n\t\t\t\t\t\t\t\t0 /*count*/);\n\tif (res < 0)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t (errmsg(\"could not get the default order by for a hypertable \\\"%s\\\"\",\n\t\t\t\t\t\t get_rel_name(ht->main_table_relid)))));\n\n\told = MemoryContextSwitchTo(upper);\n\tdatum = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);\n\n\tif (!isnull)\n\t\tcolumn_res = DatumGetTextPCopy(datum);\n\tMemoryContextSwitchTo(old);\n\n\tdatum = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 2, &isnull);\n\n\tif (!isnull)\n\t{\n\t\tmessage = DatumGetTextPP(datum);\n\t\telog(LOG_SERVER_ONLY,\n\t\t\t \"there was some uncertainty picking the default order by for the hypertable: %s\",\n\t\t\t text_to_cstring(message));\n\t}\n\tdatum = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 3, &isnull);\n\tif (!isnull)\n\t{\n\t\tconfidence = DatumGetInt32(datum);\n\t}\n\n\t/* Reset search path since this can be executed as part of a larger transaction */\n\tAtEOXact_GUC(false, save_nestlevel);\n\n\tpfree(command.data);\n\n\tres = SPI_finish();\n\tif (res != SPI_OK_FINISH)\n\t\telog(ERROR, \"SPI_finish failed: %s\", SPI_result_code_string(res));\n\n\tif (column_res != NULL)\n\t\torderby = TextDatumGetCString(PointerGetDatum(column_res));\n\telse\n\t\torderby = \"\";\n\n\telog(LOG_SERVER_ONLY,\n\t\t \"order_by default: hypertable=\\\"%s\\\" clauses=\\\"%s\\\" function=\\\"%s.%s\\\" confidence=%d\",\n\t\t get_rel_name(ht->main_table_relid),\n\t\t orderby,\n\t\t get_namespace_name(get_func_namespace(orderby_fn)),\n\t\t get_func_name(orderby_fn),\n\t\t confidence);\n\n\tif (*orderby == '\\0')\n\t{\n\t\treturn (OrderBySettings){ 0 };\n\t}\n\n\treturn ts_compress_parse_order_collist(orderby, ht);\n}\n\n/* sparse indexes will only be set by default if there was no configuration */\nstatic bool\ncan_set_default_sparse_index(CompressionSettings *settings)\n{\n\treturn (settings->fd.index == NULL) ||\n\t\t   !ts_jsonb_has_key_value_str_field(settings->fd.index,\n\t\t\t\t\t\t\t\t\t\t\t ts_sparse_index_common_keys[SparseIndexKeySource],\n\t\t\t\t\t\t\t\t\t\t\t ts_sparse_index_source_names\n\t\t\t\t\t\t\t\t\t\t\t\t [_SparseIndexSourceEnumConfig]);\n}\n\nstatic void\ncreate_default_composite_bloom(IndexInfo *index_info, Hypertable *ht, CompressionSettings *settings,\n\t\t\t\t\t\t\t   JsonbParseState *parse_state, TsBmsList *sparse_index_columns,\n\t\t\t\t\t\t\t   bool *has_object)\n{\n\tint num_cols = index_info->ii_NumIndexKeyAttrs;\n\n\t/* Allocate bloom config for the number of columns in the index */\n\tBloomFilterConfig bloom_config;\n\tbloom_config.base.type = _SparseIndexTypeEnumBloom;\n\tbloom_config.base.source = _SparseIndexSourceEnumDefault;\n\tbloom_config.columns = palloc0(num_cols * sizeof(SparseIndexColumn));\n\n\t/* Extract columns, filtering out segmentby columns.\n\t * Note: orderby columns are not filtered out here because they can\n\t * be in composite bloom filters.\n\t */\n\tint valid_columns = 0;\n\n\t/*\n\t * The index must be enabled by the GUC.\n\t */\n\tif (!ts_guc_enable_sparse_index_bloom)\n\t{\n\t\treturn;\n\t}\n\n\t/* Bitmapset of column attnums */\n\tBitmapset *attnums_bitmap = NULL;\n\n\t/* Check the total width of the hashable columns */\n\tint total_width = 0;\n\n\tfor (int i = 0; i < num_cols; i++)\n\t{\n\t\tAttrNumber attno = index_info->ii_IndexAttrNumbers[i];\n\n\t\t/* Skip expression indexes */\n\t\tif (attno == InvalidAttrNumber)\n\t\t\tcontinue;\n\n\t\tchar *attname = get_attname(ht->main_table_relid, attno, false);\n\n\t\t/* Skip segmentby columns but continue processing other columns */\n\t\tif (ts_array_is_member(settings->fd.segmentby, attname))\n\t\t\tcontinue;\n\n\t\tOid atttypid = get_atttype(ht->main_table_relid, attno);\n\n\t\t/* Check if hashable */\n\t\tFmgrInfo *finfo = NULL;\n\t\tif (bloom1_get_hash_function(atttypid, &finfo) == NULL)\n\t\t\tcontinue;\n\n\t\tTypeCacheEntry *type = lookup_type_cache(atttypid, TYPECACHE_HASH_EXTENDED_PROC);\n\t\ttotal_width += (type->typlen > 0 ? type->typlen : 4);\n\n\t\t/* Equality queries are unlikely for floating-point types, so we skip them. */\n\t\tif (atttypid == FLOAT4OID || atttypid == FLOAT8OID)\n\t\t\tcontinue;\n\n\t\t/* Add to bloom config */\n\t\tbloom_config.columns[valid_columns].attnum = attno;\n\t\tbloom_config.columns[valid_columns].name = attname;\n\t\tbloom_config.columns[valid_columns].type = atttypid;\n\t\tvalid_columns++;\n\n\t\tattnums_bitmap = bms_add_member(attnums_bitmap, attno);\n\t}\n\n\t/* Need at least 2 valid columns for composite bloom and the total width must be at least 4\n\t * bytes. */\n\tif (valid_columns < 2 || total_width < 4)\n\t{\n\t\tpfree(bloom_config.columns);\n\t\tbms_free(attnums_bitmap);\n\t\treturn;\n\t}\n\n\t/* Check if this exact bloom already exists */\n\tif (ts_bmslist_contains_set(*sparse_index_columns, attnums_bitmap))\n\t{\n\t\tpfree(bloom_config.columns);\n\t\tbms_free(attnums_bitmap);\n\t\treturn;\n\t}\n\n\tbloom_config.num_columns = valid_columns;\n\n\t/* Column names must be in attnum order for metadata column naming */\n\tqsort(bloom_config.columns,\n\t\t  bloom_config.num_columns,\n\t\t  sizeof(SparseIndexColumn),\n\t\t  ts_qsort_attrnumber_cmp);\n\n\t/* Add the bloom's column set to the list */\n\t*sparse_index_columns = ts_bmslist_add_set(*sparse_index_columns, attnums_bitmap);\n\n\t/* Convert to JSONB and add to array */\n\tts_convert_sparse_index_config_to_jsonb(parse_state, &bloom_config.base);\n\t*has_object = true;\n\n\tpfree(bloom_config.columns);\n}\n\nstatic Jsonb *\ncompression_setting_sparse_index_get_default(Hypertable *ht, CompressionSettings *settings)\n{\n\tbool has_object = false;\n\tTsBmsList sparse_index_columns = ts_bmslist_create();\n\tJsonbParseState *parse_state = NULL;\n\n\t/*\n\t * Sparse indexes are only created automatically if they are not set in compression settings\n\t */\n\tif (!ts_guc_auto_sparse_indexes || !can_set_default_sparse_index(settings))\n\t\treturn NULL;\n\n\t/*\n\t * Check which columns have btree indexes. We will create sparse minmax\n\t * indexes for them in compressed chunk.\n\t */\n\tRelation rel = table_open(ht->main_table_relid, AccessShareLock);\n\n\tListCell *lc;\n\tList *index_oids = RelationGetIndexList(rel);\n\n\tpushJsonbValue(&parse_state, WJB_BEGIN_ARRAY, NULL);\n\tforeach (lc, index_oids)\n\t{\n\t\tOid index_oid = lfirst_oid(lc);\n\t\tRelation index_rel = index_open(index_oid, AccessShareLock);\n\t\tIndexInfo *index_info = BuildIndexInfo(index_rel);\n\t\tindex_close(index_rel, NoLock);\n\n\t\t/*\n\t\t * We want to create the sparse minmax index, if it can satisfy the same\n\t\t * kinds of queries as the uncompressed index. The simplest case is btree\n\t\t * which can satisfy equality and comparison tests, same as sparse minmax.\n\t\t *\n\t\t * If an uncompressed column has an index, we want to create a\n\t\t * sparse index for it as well. A sparse index can't satisfy ordering\n\t\t * queries, but at least we can use a bloom index to satisfy equality\n\t\t * queries. Create it when we have uncompressed index types that can\n\t\t * also satisfy equality.\n\t\t */\n\t\tif (index_info->ii_Am != BTREE_AM_OID && index_info->ii_Am != HASH_AM_OID &&\n\t\t\tindex_info->ii_Am != BRIN_AM_OID)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\tint num_cols = index_info->ii_NumIndexKeyAttrs;\n\t\tif (ts_guc_enable_sparse_index_bloom && num_cols >= 2 &&\n\t\t\tnum_cols <= MAX_BLOOM_FILTER_COLUMNS)\n\t\t{\n\t\t\tcreate_default_composite_bloom(index_info,\n\t\t\t\t\t\t\t\t\t\t   ht,\n\t\t\t\t\t\t\t\t\t\t   settings,\n\t\t\t\t\t\t\t\t\t\t   parse_state,\n\t\t\t\t\t\t\t\t\t\t   &sparse_index_columns,\n\t\t\t\t\t\t\t\t\t\t   &has_object);\n\t\t}\n\n\t\tfor (int i = 0; i < num_cols; i++)\n\t\t{\n\t\t\tchar *attname;\n\t\t\tOid atttypid;\n\t\t\tMinmaxIndexColumnConfig minmax_config;\n\t\t\tBloomFilterConfig bloom_config;\n\t\t\tSparseIndexConfigBase *config = NULL;\n\t\t\tTypeCacheEntry *type;\n\t\t\tconst int attno = index_info->ii_IndexAttrNumbers[i];\n\t\t\tif (attno == InvalidAttrNumber)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tattname = get_attname(ht->main_table_relid, attno, false);\n\t\t\t/* do not create sparse index for orderby columns */\n\t\t\tif (ts_array_is_member(settings->fd.orderby, attname) ||\n\t\t\t\tts_array_is_member(settings->fd.segmentby, attname) ||\n\t\t\t\tts_bmslist_contains_items(sparse_index_columns, &attno, 1))\n\t\t\t\tcontinue;\n\n\t\t\tatttypid = get_atttype(ht->main_table_relid, attno);\n\n\t\t\ttype = lookup_type_cache(atttypid, TYPECACHE_LT_OPR | TYPECACHE_HASH_EXTENDED_PROC);\n\n\t\t\t/* construct sparse index config */\n\t\t\tif (ts_guc_enable_sparse_index_bloom &&\n\t\t\t\tshould_create_bloom_sparse_index(atttypid, type, ht->main_table_relid))\n\t\t\t{\n\t\t\t\tconfig = &bloom_config.base;\n\t\t\t\tconfig->type = _SparseIndexTypeEnumBloom;\n\t\t\t\tbloom_config.num_columns = 1;\n\t\t\t\tbloom_config.columns = palloc(1 * sizeof(SparseIndexColumn));\n\t\t\t\tbloom_config.columns[0].attnum = attno;\n\t\t\t\tbloom_config.columns[0].name = attname;\n\t\t\t\tbloom_config.columns[0].type = atttypid;\n\t\t\t}\n\t\t\telse if (OidIsValid(type->lt_opr))\n\t\t\t{\n\t\t\t\tconfig = &minmax_config.base;\n\t\t\t\tconfig->type = _SparseIndexTypeEnumMinmax;\n\t\t\t\tminmax_config.col = attname;\n\t\t\t}\n\t\t\telse\n\t\t\t\tcontinue;\n\n\t\t\tconfig->source = _SparseIndexSourceEnumDefault;\n\n\t\t\t/* convert to json object */\n\t\t\tts_convert_sparse_index_config_to_jsonb(parse_state, config);\n\t\t\tsparse_index_columns = ts_bmslist_add_member(sparse_index_columns, &attno, 1);\n\t\t\thas_object = true;\n\t\t}\n\t}\n\ttable_close(rel, AccessShareLock);\n\tts_bmslist_free(sparse_index_columns);\n\treturn has_object ? JsonbValueToJsonb(pushJsonbValue(&parse_state, WJB_END_ARRAY, NULL)) : NULL;\n}\n\nvoid\ncompression_settings_set_defaults(Hypertable *ht, CompressionSettings *settings,\n\t\t\t\t\t\t\t\t  WithClauseResult *with_clause_options)\n{\n\t/* orderby arrays should always be in sync either all NULL or none */\n\tAssert(\n\t\t(settings->fd.orderby && settings->fd.orderby_desc && settings->fd.orderby_nullsfirst) ||\n\t\t(!settings->fd.orderby && !settings->fd.orderby_desc && !settings->fd.orderby_nullsfirst));\n\n\tbool add_orderby_sparse_index = false;\n\t/* get default settings which will be stored at chunk level */\n\tif (!(settings->fd.orderby) && with_clause_options[AlterTableFlagOrderBy].is_default)\n\t{\n\t\tif (!settings->fd.segmentby && with_clause_options[AlterTableFlagSegmentBy].is_default)\n\t\t{\n\t\t\tsettings->fd.segmentby = compression_setting_segmentby_get_default(ht);\n\t\t}\n\t\tsettings->fd.index = ts_remove_orderby_sparse_index(settings);\n\t\tOrderBySettings obs = compression_setting_orderby_get_default(ht, settings->fd.segmentby);\n\t\tsettings->fd.orderby = obs.orderby;\n\t\tsettings->fd.orderby_desc = obs.orderby_desc;\n\t\tsettings->fd.orderby_nullsfirst = obs.orderby_nullsfirst;\n\t\tadd_orderby_sparse_index = settings->fd.index != NULL;\n\t}\n\n\tif (ts_guc_auto_sparse_indexes && can_set_default_sparse_index(settings))\n\t{\n\t\tsettings->fd.index = compression_setting_sparse_index_get_default(ht, settings);\n\t\tsettings->fd.index = ts_add_orderby_sparse_index(settings);\n\t}\n\telse if (add_orderby_sparse_index)\n\t{\n\t\tsettings->fd.index = ts_add_orderby_sparse_index(settings);\n\t}\n\n\t/* should always be valid, but call as a sanity check */\n\tvalidate_compression_index_key_limit(settings);\n}\n\nstatic void\ncompression_settings_set_manually_for_alter(Hypertable *ht, CompressionSettings *settings,\n\t\t\t\t\t\t\t\t\t\t\tWithClauseResult *with_clause_options)\n{\n\t/* orderby arrays should always be in sync either all NULL or none */\n\tAssert(\n\t\t(settings->fd.orderby && settings->fd.orderby_desc && settings->fd.orderby_nullsfirst) ||\n\t\t(!settings->fd.orderby && !settings->fd.orderby_desc && !settings->fd.orderby_nullsfirst));\n\n\tif (with_clause_options[AlterTableFlagSegmentBy].is_default &&\n\t\twith_clause_options[AlterTableFlagOrderBy].is_default &&\n\t\twith_clause_options[AlterTableFlagIndex].is_default)\n\t\treturn;\n\n\tbool add_orderby_sparse_index = false;\n\tif (!with_clause_options[AlterTableFlagSegmentBy].is_default)\n\t{\n\t\tsettings->fd.segmentby =\n\t\t\tts_compress_hypertable_parse_segment_by(with_clause_options[AlterTableFlagSegmentBy],\n\t\t\t\t\t\t\t\t\t\t\t\t\tht);\n\t}\n\n\tif (!with_clause_options[AlterTableFlagOrderBy].is_default)\n\t{\n\t\tsettings->fd.index = ts_remove_orderby_sparse_index(settings);\n\t\tOrderBySettings obs =\n\t\t\tts_compress_hypertable_parse_order_by(with_clause_options[AlterTableFlagOrderBy], ht);\n\t\tobs = add_time_to_order_by_if_not_included(obs, settings->fd.segmentby, ht);\n\t\tsettings->fd.orderby = obs.orderby;\n\t\tsettings->fd.orderby_desc = obs.orderby_desc;\n\t\tsettings->fd.orderby_nullsfirst = obs.orderby_nullsfirst;\n\t\tadd_orderby_sparse_index = settings->fd.index != NULL;\n\t}\n\n\tif (!with_clause_options[AlterTableFlagIndex].is_default)\n\t{\n\t\tif (!settings->fd.orderby)\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"cannot set sparse index option without orderby option\"),\n\t\t\t\t\t errdetail(\"Either set both options or remove both options to trigger default \"\n\t\t\t\t\t\t\t   \"values\")));\n\t\t}\n\t\tsettings->fd.index =\n\t\t\tts_compress_hypertable_parse_index(with_clause_options[AlterTableFlagIndex], ht);\n\t\tsettings->fd.index = ts_add_orderby_sparse_index(settings);\n\t}\n\telse if (add_orderby_sparse_index)\n\t{\n\t\tsettings->fd.index = ts_add_orderby_sparse_index(settings);\n\t}\n\n\tvalidate_compression_index_key_limit(settings);\n\n\t/* update manual settings */\n\tts_compression_settings_update(settings);\n}\n\nstatic void\ncompression_settings_set_manually_for_create(Hypertable *ht, CompressionSettings *settings,\n\t\t\t\t\t\t\t\t\t\t\t WithClauseResult *with_clause_options)\n{\n\tif (with_clause_options[CreateTableFlagSegmentBy].is_default &&\n\t\twith_clause_options[CreateTableFlagOrderBy].is_default &&\n\t\twith_clause_options[CreateTableFlagIndex].is_default)\n\t\treturn;\n\n\tbool add_orderby_sparse_index = false;\n\tif (!with_clause_options[CreateTableFlagSegmentBy].is_default)\n\t{\n\t\tsettings->fd.segmentby =\n\t\t\tts_compress_hypertable_parse_segment_by(with_clause_options[CreateTableFlagSegmentBy],\n\t\t\t\t\t\t\t\t\t\t\t\t\tht);\n\t}\n\n\tif (!with_clause_options[CreateTableFlagOrderBy].is_default)\n\t{\n\t\tsettings->fd.index = ts_remove_orderby_sparse_index(settings);\n\t\tOrderBySettings obs =\n\t\t\tts_compress_hypertable_parse_order_by(with_clause_options[CreateTableFlagOrderBy], ht);\n\t\tobs = add_time_to_order_by_if_not_included(obs, settings->fd.segmentby, ht);\n\t\tsettings->fd.orderby = obs.orderby;\n\t\tsettings->fd.orderby_desc = obs.orderby_desc;\n\t\tsettings->fd.orderby_nullsfirst = obs.orderby_nullsfirst;\n\t\tadd_orderby_sparse_index = settings->fd.index != NULL;\n\t}\n\n\tif (!with_clause_options[CreateTableFlagIndex].is_default)\n\t{\n\t\tif (!settings->fd.orderby)\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"cannot set sparse index option without orderby option\"),\n\t\t\t\t\t errdetail(\"Either set both options or remove both options to trigger default \"\n\t\t\t\t\t\t\t   \"values\")));\n\t\t}\n\t\tsettings->fd.index =\n\t\t\tts_compress_hypertable_parse_index(with_clause_options[CreateTableFlagIndex], ht);\n\t\tsettings->fd.index = ts_add_orderby_sparse_index(settings);\n\t}\n\telse if (add_orderby_sparse_index)\n\t{\n\t\tsettings->fd.index = ts_add_orderby_sparse_index(settings);\n\t}\n\n\tvalidate_compression_index_key_limit(settings);\n\n\t/* update manual settings */\n\tts_compression_settings_update(settings);\n}\n\n/* Add a column to a table that has compression enabled\n * This function specifically adds the column to the internal compression table.\n */\nvoid\ntsl_process_compress_table_add_column(Hypertable *ht, ColumnDef *orig_def)\n{\n\tts_feature_flag_check(FEATURE_HYPERTABLE_COMPRESSION);\n\tif (!TS_HYPERTABLE_HAS_COMPRESSION_TABLE(ht))\n\t{\n\t\treturn;\n\t}\n\n\tList *chunks = ts_chunk_get_by_hypertable_id(ht->fd.compressed_hypertable_id);\n\tListCell *lc;\n\tOid coloid = LookupTypeNameOid(NULL, orig_def->typeName, false);\n\n\tforeach (lc, chunks)\n\t{\n\t\tChunk *chunk = lfirst(lc);\n\t\t/* don't add column if it already exists */\n\t\tif (get_attnum(chunk->table_id, orig_def->colname) != InvalidAttrNumber)\n\t\t{\n\t\t\treturn;\n\t\t}\n\t\tColumnDef *coldef = build_columndef_singlecolumn(orig_def->colname, coloid);\n\t\tCompressionSettings *settings =\n\t\t\tts_compression_settings_get_by_compress_relid(chunk->table_id);\n\t\tadd_column_to_compression_table(chunk->table_id, settings, coldef);\n\t}\n}\n\n/* Drop a column from a table that has compression enabled\n * This function specifically removes it from the internal compression table\n * and removes it from metadata.\n * Removing orderby or segmentby columns is not supported.\n */\nvoid\ntsl_process_compress_table_drop_column(Hypertable *ht, char *name)\n{\n\tAssert(TS_HYPERTABLE_HAS_COMPRESSION_TABLE(ht) || TS_HYPERTABLE_HAS_COMPRESSION_ENABLED(ht));\n\n\tts_feature_flag_check(FEATURE_HYPERTABLE_COMPRESSION);\n\n\tCompressionSettings *settings = ts_compression_settings_get(ht->main_table_relid);\n\tEnsure(settings != NULL,\n\t\t   \"compression settings not found for hypertable \\\"%s\\\"\",\n\t\t   get_rel_name(ht->main_table_relid));\n\n\tJsonb *jb = settings->fd.index;\n\n\t/* check if the column is a segmentby or orderby column */\n\tif (settings && (ts_array_is_member(settings->fd.segmentby, name) ||\n\t\t\t\t\t ts_array_is_member(settings->fd.orderby, name)))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot drop orderby or segmentby column from a hypertable with \"\n\t\t\t\t\t\t\"columnstore enabled\")));\n\n\tList *chunks = ts_chunk_get_by_hypertable_id(ht->fd.compressed_hypertable_id);\n\tListCell *lc;\n\tint num_chunks = list_length(chunks);\n\tCompressionSettings **chunk_settings = palloc(sizeof(CompressionSettings *) * num_chunks);\n\n\tint i = 0;\n\tforeach (lc, chunks)\n\t{\n\t\tChunk *chunk = lfirst(lc);\n\t\tCompressionSettings *settings =\n\t\t\tts_compression_settings_get_by_compress_relid(chunk->table_id);\n\t\tchunk_settings[i++] = settings;\n\t\tif (ts_array_is_member(settings->fd.segmentby, name) ||\n\t\t\tts_array_is_member(settings->fd.orderby, name))\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"cannot drop orderby or segmentby column from a chunk with \"\n\t\t\t\t\t\t\t\"columnstore enabled\")));\n\t}\n\n\tif (TS_HYPERTABLE_HAS_COMPRESSION_TABLE(ht))\n\t{\n\t\tfor (int i = 0; i < num_chunks; i++)\n\t\t{\n\t\t\tCompressionSettings *comp_settings = chunk_settings[i];\n\t\t\tdrop_column_from_compression_table(comp_settings, name);\n\t\t}\n\t}\n\n\t/* update the compression settings for the main table */\n\tif (jb)\n\t{\n\t\tSparseIndexSettings *parsed_settings = ts_convert_to_sparse_index_settings(jb);\n\t\tif (parsed_settings)\n\t\t{\n\t\t\tbool removed_any = false;\n\t\t\tListCell *obj_cell = NULL;\n\t\t\tforeach (obj_cell, parsed_settings->objects)\n\t\t\t{\n\t\t\t\tbool removed = false;\n\t\t\t\tSparseIndexSettingsObject *obj = (SparseIndexSettingsObject *) lfirst(obj_cell);\n\t\t\t\tAssert(obj != NULL);\n\t\t\t\tforeach_ptr(SparseIndexSettingsPair, pair, obj->pairs)\n\t\t\t\t{\n\t\t\t\t\tif (strcmp(pair->key, ts_sparse_index_common_keys[SparseIndexKeyCol]) != 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tforeach_ptr(const char, value, pair->values)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (strcmp(value, name) == 0)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tremoved = true;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (removed)\n\t\t\t\t\t{\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t/* if the column was removed, we need to remove the object from the list */\n\t\t\t\tif (removed)\n\t\t\t\t{\n\t\t\t\t\tremoved_any = true;\n\t\t\t\t\tparsed_settings->objects =\n\t\t\t\t\t\tforeach_delete_current(parsed_settings->objects, obj_cell);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (removed_any)\n\t\t\t{\n\t\t\t\tjb = ts_convert_from_sparse_index_settings(parsed_settings);\n\t\t\t\tsettings->fd.index = jb;\n\t\t\t\tts_compression_settings_update(settings);\n\t\t\t}\n\t\t\tts_free_sparse_index_settings(parsed_settings);\n\t\t}\n\t}\n}\n\n/* Rename a column on a hypertable that has compression enabled.\n *\n * This function renames the existing column in the internal compression table.\n * We assume that there is a 1-1 mapping between the original chunk and\n * compressed chunk column names and that the names are identical.\n * Also update any metadata associated with the column.\n */\nvoid\ntsl_process_compress_table_rename_column(Hypertable *ht, const RenameStmt *stmt)\n{\n\tAssert(stmt->relationType == OBJECT_TABLE && stmt->renameType == OBJECT_COLUMN);\n\tAssert(TS_HYPERTABLE_HAS_COMPRESSION_ENABLED(ht));\n\n\tstruct RenameFromTo\n\t{\n\t\tchar *from;\n\t\tchar *to;\n\t};\n\n\tif (strncmp(stmt->newname,\n\t\t\t\tCOMPRESSION_COLUMN_METADATA_PREFIX,\n\t\t\t\tstrlen(COMPRESSION_COLUMN_METADATA_PREFIX)) == 0)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_RESERVED_NAME),\n\t\t\t\t errmsg(\"cannot convert tables with reserved column prefix '%s' to columnstore\",\n\t\t\t\t\t\tCOMPRESSION_COLUMN_METADATA_PREFIX)));\n\n\tif (!TS_HYPERTABLE_HAS_COMPRESSION_TABLE(ht))\n\t{\n\t\treturn;\n\t}\n\n\tRenameStmt *compressed_col_stmt = (RenameStmt *) copyObject(stmt);\n\tRenameStmt *compressed_index_stmt = (RenameStmt *) copyObject(stmt);\n\tList *chunks = ts_chunk_get_by_hypertable_id(ht->fd.compressed_hypertable_id);\n\tCompressionSettings *ht_settings = NULL;\n\tListCell *lc;\n\n\tforeach (lc, chunks)\n\t{\n\t\tChunk *chunk = lfirst(lc);\n\t\tcompressed_col_stmt->relation =\n\t\t\tmakeRangeVar(NameStr(chunk->fd.schema_name), NameStr(chunk->fd.table_name), -1);\n\t\tExecRenameStmt(compressed_col_stmt);\n\n\t\tList *rename_from_to = NIL;\n\t\tCompressionSettings *settings = ts_compression_settings_get(chunk->table_id);\n\t\tif (!settings || settings->fd.index == NULL)\n\t\t{\n\t\t\t/* only lookup ht settings if we haven't already */\n\t\t\tif (!ht_settings)\n\t\t\t{\n\t\t\t\tht_settings = ts_compression_settings_get(ht->main_table_relid);\n\t\t\t}\n\t\t\tsettings = ht_settings;\n\t\t}\n\n\t\t/* check the minmax and single bloom index columns no matter what the compression settings\n\t\t * says */\n\t\t{\n\t\t\t/* handle minmax index */\n\t\t\tstruct RenameFromTo *from_to =\n\t\t\t\t(struct RenameFromTo *) palloc(sizeof(struct RenameFromTo));\n\t\t\tfrom_to->from =\n\t\t\t\tcompressed_column_metadata_name_v2(\"min\", (const char **) &stmt->subname, 1);\n\t\t\tfrom_to->to =\n\t\t\t\tcompressed_column_metadata_name_v2(\"min\", (const char **) &stmt->newname, 1);\n\t\t\trename_from_to = lappend(rename_from_to, from_to);\n\t\t\tfrom_to = (struct RenameFromTo *) palloc(sizeof(struct RenameFromTo));\n\t\t\tfrom_to->from =\n\t\t\t\tcompressed_column_metadata_name_v2(\"max\", (const char **) &stmt->subname, 1);\n\t\t\tfrom_to->to =\n\t\t\t\tcompressed_column_metadata_name_v2(\"max\", (const char **) &stmt->newname, 1);\n\t\t\trename_from_to = lappend(rename_from_to, from_to);\n\t\t}\n\n\t\t{\n\t\t\t/* handle single bloom index */\n\t\t\tstruct RenameFromTo *from_to =\n\t\t\t\t(struct RenameFromTo *) palloc(sizeof(struct RenameFromTo));\n\t\t\tfrom_to->from = compressed_column_metadata_name_v2(bloom1_column_prefix,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   (const char **) &stmt->subname,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   1);\n\t\t\tfrom_to->to = compressed_column_metadata_name_v2(bloom1_column_prefix,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t (const char **) &stmt->newname,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t 1);\n\t\t\trename_from_to = lappend(rename_from_to, from_to);\n\t\t}\n\n\t\tif (settings && settings->fd.index != NULL)\n\t\t{\n\t\t\tSparseIndexSettings *parsed_settings =\n\t\t\t\tts_convert_to_sparse_index_settings(settings->fd.index);\n\t\t\tList *per_column_settings = ts_get_per_column_compression_settings(parsed_settings);\n\t\t\tPerColumnCompressionSettings *per_column_setting =\n\t\t\t\tper_column_settings ?\n\t\t\t\t\tts_get_per_column_compression_settings_by_column_name(per_column_settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  stmt->subname) :\n\t\t\t\t\tNULL;\n\n\t\t\tif (per_column_setting != NULL)\n\t\t\t{\n\t\t\t\tif (per_column_setting->composite_bloom_index_obj_ids != NULL)\n\t\t\t\t{\n\t\t\t\t\t/* one column may participate in multiple composite bloom indices, so we need to\n\t\t\t\t\t * handle all of them */\n\t\t\t\t\tint i = -1;\n\t\t\t\t\tstruct RenameFromTo *from_to = NULL;\n\n\t\t\t\t\twhile ((i = bms_next_member(per_column_setting->composite_bloom_index_obj_ids,\n\t\t\t\t\t\t\t\t\t\t\t\ti)) >= 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tSparseIndexSettingsObject *obj =\n\t\t\t\t\t\t\t(SparseIndexSettingsObject *) list_nth(parsed_settings->objects, i);\n\t\t\t\t\t\tAssert(obj != NULL);\n\t\t\t\t\t\tList *column_names = ts_get_column_names_from_parsed_object(obj);\n\t\t\t\t\t\tAssert(column_names != NULL);\n\t\t\t\t\t\tAssert(list_length(column_names) > 1);\n\t\t\t\t\t\tAssert(list_length(column_names) <= MAX_BLOOM_FILTER_COLUMNS);\n\t\t\t\t\t\tchar *new_name[MAX_BLOOM_FILTER_COLUMNS] = { NULL };\n\t\t\t\t\t\tint j = 0;\n\t\t\t\t\t\tListCell *cell = NULL;\n\t\t\t\t\t\tforeach (cell, column_names)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tconst char *column_name = (const char *) lfirst(cell);\n\t\t\t\t\t\t\tif (strcmp(column_name, stmt->subname) == 0)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tnew_name[j] = stmt->newname;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tnew_name[j] = pstrdup(column_name);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tj++;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t/* handle composite bloom index */\n\t\t\t\t\t\tfrom_to = (struct RenameFromTo *) palloc(sizeof(struct RenameFromTo));\n\t\t\t\t\t\tfrom_to->from =\n\t\t\t\t\t\t\tcompressed_column_metadata_name_list_v2(bloom1_column_prefix,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcolumn_names);\n\t\t\t\t\t\tfrom_to->to = compressed_column_metadata_name_v2(bloom1_column_prefix,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t (const char **) new_name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t list_length(column_names));\n\t\t\t\t\t\trename_from_to = lappend(rename_from_to, from_to);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tts_free_sparse_index_settings(parsed_settings);\n\t\t}\n\n\t\tcompressed_index_stmt->relation = compressed_col_stmt->relation;\n\t\tif (rename_from_to != NULL)\n\t\t{\n\t\t\tListCell *cell = NULL;\n\t\t\tforeach (cell, rename_from_to)\n\t\t\t{\n\t\t\t\tstruct RenameFromTo *from_to = (struct RenameFromTo *) lfirst(cell);\n\t\t\t\tAssert(from_to != NULL);\n\t\t\t\tAssert(from_to->from != NULL);\n\t\t\t\tAssert(from_to->to != NULL);\n\t\t\t\tif (get_attnum(chunk->table_id, from_to->from) == InvalidAttrNumber)\n\t\t\t\t{\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tcompressed_index_stmt->subname = from_to->from;\n\t\t\t\tcompressed_index_stmt->newname = from_to->to;\n\t\t\t\tExecRenameStmt(compressed_index_stmt);\n\t\t\t}\n\n\t\t\tlist_free_deep(rename_from_to);\n\t\t}\n\t}\n}\n\n/*\n * Enables compression for a hypertable without creating initial configuration\n *\n * This is used when creating a hypertable with CREATE TABLE ... WITH (timescaledb.hypertable)\n */\nvoid\ntsl_columnstore_setup(Hypertable *ht, WithClauseResult *with_clause_options)\n{\n\tLockRelationOid(catalog_get_table_id(ts_catalog_get(), HYPERTABLE), RowExclusiveLock);\n\tOid ownerid = ts_rel_get_owner(ht->main_table_relid);\n\tOid tablespace_oid = get_rel_tablespace(ht->main_table_relid);\n\tCompressionSettings *settings = ts_compression_settings_create(ht->main_table_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   InvalidOid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   NULL);\n\n\tcompression_settings_set_manually_for_create(ht, settings, with_clause_options);\n\tint compress_htid = compression_hypertable_create(ht, ownerid, tablespace_oid);\n\tts_hypertable_set_compressed(ht, compress_htid);\n\n\t/* Add default compression policy when compression is enabled via CREATE TABLE WITH */\n\t/* Use the chunk interval as the compression interval */\n\tconst Dimension *time_dim = hyperspace_get_open_dimension(ht->space, 0);\n\tif (time_dim != NULL)\n\t{\n\t\tOid compress_after_type = ts_dimension_get_partition_type(time_dim);\n\t\tDatum compress_after_datum;\n\t\tif (IS_TIMESTAMP_TYPE(compress_after_type) || IS_UUID_TYPE(compress_after_type))\n\t\t\tcompress_after_type = INTERVALOID;\n\n\t\tcompress_after_datum =\n\t\t\tts_internal_to_interval_value(time_dim->fd.interval_length, compress_after_type);\n\n\t\tpolicy_compression_add_internal(\n\t\t\tht->main_table_relid,\n\t\t\tcompress_after_datum,\n\t\t\tcompress_after_type,\n\t\t\tNULL,\t\t\t\t\t\t\t\t   /* created_before */\n\t\t\tDEFAULT_COMPRESSION_SCHEDULE_INTERVAL, /* default_schedule_interval\n\t\t\t\t\t\t\t\t\t\t\t\t\t*/\n\t\t\ttrue,\t\t\t\t\t\t\t\t   /* user_defined_schedule_interval */\n\t\t\ttrue,\t\t\t\t\t\t\t\t   /* if_not_exists */\n\t\t\tfalse,\t\t\t\t\t\t\t\t   /* fixed_schedule */\n\t\t\tGetCurrentTimestamp() + USECS_PER_DAY, /* initial_start */\n\t\t\tNULL /* timezone */);\n\t}\n}\n"
  },
  {
    "path": "tsl/src/compression/create.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <nodes/parsenodes.h>\n\n#include \"hypertable.h\"\n#include \"with_clause/with_clause_parser.h\"\n\n#define COMPRESSION_COLUMN_METADATA_PREFIX \"_ts_meta_\"\n#define COMPRESSION_COLUMN_METADATA_COUNT_NAME COMPRESSION_COLUMN_METADATA_PREFIX \"count\"\n#define COMPRESSION_COLUMN_METADATA_SEQUENCE_NUM_NAME                                              \\\n\tCOMPRESSION_COLUMN_METADATA_PREFIX \"sequence_num\"\n\n#define COMPRESSION_COLUMN_METADATA_PATTERN_V1 \"_ts_meta_%s_%d\"\n\nbool tsl_process_compress_table(Hypertable *ht, WithClauseResult *with_clause_options);\nvoid tsl_process_compress_table_add_column(Hypertable *ht, ColumnDef *orig_def);\nvoid tsl_process_compress_table_drop_column(Hypertable *ht, char *name);\nvoid tsl_process_compress_table_rename_column(Hypertable *ht, const RenameStmt *stmt);\nChunk *create_compress_chunk(Hypertable *compress_ht, Chunk *src_chunk, Oid table_id);\n\nchar *column_segment_min_name(int16 column_index);\nchar *column_segment_max_name(int16 column_index);\nchar *compressed_column_metadata_name_v2(const char *metadata_type, const char **column_names,\n\t\t\t\t\t\t\t\t\t\t int num_columns);\nchar *compressed_column_metadata_name_list_v2(const char *metadata_type, List *column_names_list);\n\ntypedef struct CompressionSettings CompressionSettings;\nint compressed_column_metadata_attno(const CompressionSettings *settings, Oid chunk_reloid,\n\t\t\t\t\t\t\t\t\t AttrNumber chunk_attno, Oid compressed_reloid,\n\t\t\t\t\t\t\t\t\t char const *metadata_type);\n\nvoid tsl_columnstore_setup(Hypertable *ht, WithClauseResult *with_clause_options);\n\nvoid compression_settings_set_defaults(Hypertable *ht, CompressionSettings *settings,\n\t\t\t\t\t\t\t\t\t   WithClauseResult *with_clause_options);\n"
  },
  {
    "path": "tsl/src/compression/recompress.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n#include \"debug_point.h\"\n#include <parser/parse_coerce.h>\n#include <parser/parse_relation.h>\n#include <utils/inval.h>\n#include <utils/lsyscache.h>\n#include <utils/rel.h>\n#include <utils/relcache.h>\n#include <utils/snapmgr.h>\n#include <utils/syscache.h>\n#include <utils/typcache.h>\n\n#include \"api.h\"\n#include \"compression.h\"\n#include \"compression_dml.h\"\n#include \"create.h\"\n#include \"debug_assert.h\"\n#include \"guc.h\"\n#include \"indexing.h\"\n#include \"recompress.h\"\n#include \"ts_catalog/array_utils.h\"\n#include \"ts_catalog/chunk_column_stats.h\"\n#include \"ts_catalog/compression_chunk_size.h\"\n#include \"ts_catalog/compression_settings.h\"\n#include \"with_clause/alter_table_with_clause.h\"\n\n/*\n * Timing parameters for spin locking heuristics.\n * These are the same as used by Postgres for truncate locking during lazy vacuum.\n * https://github.com/postgres/postgres/blob/4a0650d359c5981270039eeb634c3b7427aa0af5/src/backend/access/heap/vacuumlazy.c#L82\n */\n#define RECOMPRESS_EXCLUSIVE_LOCK_WAIT_INTERVAL 50 /* ms */\n#ifdef TS_DEBUG\n/* Lock timeout reduced for the sake of faster testing. */\n#define RECOMPRESS_EXCLUSIVE_LOCK_TIMEOUT 100 /* ms */\n#else\n#define RECOMPRESS_EXCLUSIVE_LOCK_TIMEOUT 5000 /* ms */\n#endif\n\nstatic bool fetch_uncompressed_chunk_into_tuplesort(Tuplesortstate *tuplesortstate,\n\t\t\t\t\t\t\t\t\t\t\t\t\tRelation uncompressed_chunk_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\tSnapshot snapshot);\nstatic bool delete_tuple_for_recompression(Relation rel, ItemPointer tid, Snapshot snapshot);\nstatic void update_current_segment(CompressedSegmentInfo *current_segment, Datum *values,\n\t\t\t\t\t\t\t\t   bool *isnulls, int nsegmentby_cols);\nstatic void create_segmentby_scankeys(CompressionSettings *settings, Relation index_rel,\n\t\t\t\t\t\t\t\t\t  Relation compressed_chunk_rel, ScanKeyData *index_scankeys);\nstatic void create_orderby_scankeys(CompressionSettings *settings, Relation index_rel,\n\t\t\t\t\t\t\t\t\tRelation compressed_chunk_rel, ScanKeyData *orderby_scankeys);\nstatic void update_segmentby_scankeys(Datum *values, bool *isnulls, int num_segmentby,\n\t\t\t\t\t\t\t\t\t  ScanKey index_scankeys);\nstatic void update_orderby_scankeys(Datum *values, bool *isnulls, int num_segmentby,\n\t\t\t\t\t\t\t\t\tint num_orderby, ScanKey orderby_scankeys);\nstatic enum Batch_match_result match_tuple_batch(TupleTableSlot *compressed_slot, int num_orderby,\n\t\t\t\t\t\t\t\t\t\t\t\t ScanKey orderby_scankeys, bool *nulls_first);\nstatic bool check_changed_group(CompressedSegmentInfo *current_segment, Datum *values,\n\t\t\t\t\t\t\t\tbool *isnulls, int nsegmentby_cols);\nstatic void recompress_segment(Tuplesortstate *tuplesortstate, Relation compressed_chunk_rel,\n\t\t\t\t\t\t\t   RowCompressor *row_compressor, BulkWriter *writer);\nstatic void try_updating_chunk_status(Chunk *uncompressed_chunk, Relation uncompressed_chunk_rel);\n\n/*\n * Recompress an existing chunk by decompressing the batches\n * that are affected by the addition of newer data. The existing\n * compressed chunk will not be recreated but modified in place.\n *\n * 0 uncompressed_chunk_id REGCLASS\n * 1 if_not_compressed BOOL = false\n */\nDatum\ntsl_recompress_chunk_segmentwise(PG_FUNCTION_ARGS)\n{\n\tOid uncompressed_chunk_id = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\tbool if_not_compressed = PG_ARGISNULL(1) ? true : PG_GETARG_BOOL(1);\n\n\tts_feature_flag_check(FEATURE_HYPERTABLE_COMPRESSION);\n\tTS_PREVENT_FUNC_IF_READ_ONLY();\n\tChunk *chunk = ts_chunk_get_by_relid(uncompressed_chunk_id, true);\n\n\tif (!ts_chunk_is_partial(chunk))\n\t{\n\t\tint elevel = if_not_compressed ? NOTICE : ERROR;\n\t\tereport(elevel,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"nothing to recompress in chunk %s.%s\",\n\t\t\t\t\t\tNameStr(chunk->fd.schema_name),\n\t\t\t\t\t\tNameStr(chunk->fd.table_name))));\n\t}\n\telse\n\t{\n\t\tif (!ts_guc_enable_segmentwise_recompression)\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"segmentwise recompression functionality disabled, \"\n\t\t\t\t\t\t\t\"enable it by first setting \"\n\t\t\t\t\t\t\t\"timescaledb.enable_segmentwise_recompression to on\")));\n\t\t}\n\t\tCompressionSettings *settings = ts_compression_settings_get(uncompressed_chunk_id);\n\t\tif (!settings->fd.orderby)\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"segmentwise recompression cannot be applied for \"\n\t\t\t\t\t\t\t\"compression with no \"\n\t\t\t\t\t\t\t\"order by\")));\n\t\t}\n\t\tuncompressed_chunk_id = recompress_chunk_segmentwise_impl(chunk);\n\t}\n\n\tPG_RETURN_OID(uncompressed_chunk_id);\n}\n\nstatic RecompressContext *\ncompress_chunk_populate_recompress_ctx(CompressionSettings *settings,\n\t\t\t\t\t\t\t\t\t   Relation uncompressed_chunk_rel,\n\t\t\t\t\t\t\t\t\t   Relation compressed_chunk_rel, Relation index_rel,\n\t\t\t\t\t\t\t\t\t   const bool for_uncompressed)\n{\n\tRecompressContext *recompress_ctx;\n\tint n;\n\tint position;\n\tconst char *attname;\n\tAttrNumber col_attno;\n\tRelation chunk_rel = for_uncompressed ? uncompressed_chunk_rel : compressed_chunk_rel;\n\t/* Initialize sort info structure */\n\trecompress_ctx = palloc0(sizeof(RecompressContext));\n\n\t/* Calculate array sizes */\n\trecompress_ctx->num_segmentby = ts_array_length(settings->fd.segmentby);\n\trecompress_ctx->num_orderby = ts_array_length(settings->fd.orderby);\n\trecompress_ctx->n_keys = recompress_ctx->num_segmentby + recompress_ctx->num_orderby;\n\n\t/* Allocate arrays */\n\trecompress_ctx->sort_keys = palloc(sizeof(*recompress_ctx->sort_keys) * recompress_ctx->n_keys);\n\trecompress_ctx->sort_operators =\n\t\tpalloc(sizeof(*recompress_ctx->sort_operators) * recompress_ctx->n_keys);\n\trecompress_ctx->sort_collations =\n\t\tpalloc(sizeof(*recompress_ctx->sort_collations) * recompress_ctx->n_keys);\n\trecompress_ctx->nulls_first =\n\t\tpalloc(sizeof(*recompress_ctx->nulls_first) * recompress_ctx->n_keys);\n\trecompress_ctx->current_segment =\n\t\tpalloc0(sizeof(CompressedSegmentInfo) * recompress_ctx->n_keys);\n\n\t/* Populate sort information for each column */\n\tfor (n = 0; n < recompress_ctx->n_keys; n++)\n\t{\n\t\tif (n < recompress_ctx->num_segmentby)\n\t\t{\n\t\t\tposition = n + 1;\n\t\t\tattname = ts_array_get_element_text(settings->fd.segmentby, position);\n\t\t\tcol_attno = get_attnum(chunk_rel->rd_id, attname);\n\t\t\trecompress_ctx->current_segment[n].chunk_offset = AttrNumberGetAttrOffset(col_attno);\n\t\t\trecompress_ctx->current_segment[n].segment_info =\n\t\t\t\tsegment_info_new(TupleDescAttr(RelationGetDescr(chunk_rel),\n\t\t\t\t\t\t\t\t\t\t\t   recompress_ctx->current_segment[n].chunk_offset));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tposition = n - recompress_ctx->num_segmentby + 1;\n\t\t\tattname = ts_array_get_element_text(settings->fd.orderby, position);\n\t\t\tcol_attno = get_attnum(chunk_rel->rd_id, attname);\n\t\t\trecompress_ctx->current_segment[n].chunk_offset = AttrNumberGetAttrOffset(col_attno);\n\t\t}\n\t\tcompress_chunk_populate_sort_info_for_column(settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t RelationGetRelid(uncompressed_chunk_rel),\n\t\t\t\t\t\t\t\t\t\t\t\t\t attname,\n\t\t\t\t\t\t\t\t\t\t\t\t\t &recompress_ctx->sort_keys[n],\n\t\t\t\t\t\t\t\t\t\t\t\t\t &recompress_ctx->sort_operators[n],\n\t\t\t\t\t\t\t\t\t\t\t\t\t &recompress_ctx->sort_collations[n],\n\t\t\t\t\t\t\t\t\t\t\t\t\t &recompress_ctx->nulls_first[n]);\n\t}\n\n\t/* Allocate scankeys */\n\trecompress_ctx->index_scankeys = palloc(sizeof(ScanKeyData) * recompress_ctx->num_segmentby);\n\trecompress_ctx->orderby_scankeys =\n\t\tpalloc(sizeof(ScanKeyData) * recompress_ctx->num_orderby * 2);\n\n\t/* Populate scankeys */\n\tcreate_segmentby_scankeys(settings,\n\t\t\t\t\t\t\t  index_rel,\n\t\t\t\t\t\t\t  compressed_chunk_rel,\n\t\t\t\t\t\t\t  recompress_ctx->index_scankeys);\n\tcreate_orderby_scankeys(settings,\n\t\t\t\t\t\t\tindex_rel,\n\t\t\t\t\t\t\tcompressed_chunk_rel,\n\t\t\t\t\t\t\trecompress_ctx->orderby_scankeys);\n\n\treturn recompress_ctx;\n}\n\nstatic void\nfree_chunk_recompress_ctx(RecompressContext *recompress_ctx)\n{\n\tif (recompress_ctx == NULL)\n\t\treturn;\n\n\tif (recompress_ctx->sort_keys)\n\t\tpfree(recompress_ctx->sort_keys);\n\tif (recompress_ctx->sort_operators)\n\t\tpfree(recompress_ctx->sort_operators);\n\tif (recompress_ctx->sort_collations)\n\t\tpfree(recompress_ctx->sort_collations);\n\tif (recompress_ctx->nulls_first)\n\t\tpfree(recompress_ctx->nulls_first);\n\tif (recompress_ctx->current_segment)\n\t\tpfree(recompress_ctx->current_segment);\n\tif (recompress_ctx->index_scankeys)\n\t\tpfree(recompress_ctx->index_scankeys);\n\tif (recompress_ctx->orderby_scankeys)\n\t\tpfree(recompress_ctx->orderby_scankeys);\n\tpfree(recompress_ctx);\n}\n\nOid\nrecompress_chunk_segmentwise_impl(Chunk *uncompressed_chunk)\n{\n\tOid uncompressed_chunk_id = uncompressed_chunk->table_id;\n\n\t/*\n\t * only proceed if status in (3, 9, 11)\n\t * 1: compressed\n\t * 2: compressed_unordered\n\t * 4: frozen\n\t * 8: compressed_partial\n\t */\n\tif (!ts_chunk_is_compressed(uncompressed_chunk) && ts_chunk_is_partial(uncompressed_chunk))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"unexpected chunk status %d in chunk %s.%s\",\n\t\t\t\t\t\tuncompressed_chunk->fd.status,\n\t\t\t\t\t\tNameStr(uncompressed_chunk->fd.schema_name),\n\t\t\t\t\t\tNameStr(uncompressed_chunk->fd.table_name))));\n\n\t/* need it to find the segby cols from the catalog */\n\tChunk *compressed_chunk = ts_chunk_get_by_id(uncompressed_chunk->fd.compressed_chunk_id, true);\n\tCompressionSettings *settings = ts_compression_settings_get(uncompressed_chunk->table_id);\n\n\t/* We should not do segment-wise recompression with empty orderby, see #7748\n\t */\n\tEnsure(settings->fd.orderby, \"empty order by, cannot recompress segmentwise\");\n\n\tereport(DEBUG1,\n\t\t\t(errmsg(\"acquiring locks for recompression: \\\"%s.%s\\\"\",\n\t\t\t\t\tNameStr(uncompressed_chunk->fd.schema_name),\n\t\t\t\t\tNameStr(uncompressed_chunk->fd.table_name))));\n\n\tLOCKMODE recompression_lockmode =\n\t\tts_guc_enable_exclusive_locking_recompression ? ExclusiveLock : ShareUpdateExclusiveLock;\n\t/* lock both chunks, compressed and uncompressed */\n\tRelation uncompressed_chunk_rel =\n\t\ttable_open(uncompressed_chunk->table_id, recompression_lockmode);\n\tRelation compressed_chunk_rel = table_open(compressed_chunk->table_id, recompression_lockmode);\n\n\tbool has_unique_constraints =\n\t\tts_indexing_relation_has_primary_or_unique_index(uncompressed_chunk_rel);\n\tint count;\n\tLOCKTAG locktag;\n\tSET_LOCKTAG_RELATION(locktag, MyDatabaseId, uncompressed_chunk_id);\n\n\t/*\n\t * Recompression does not block inserts but it can interfere with\n\t * constraint checking since it moves uncompressed tuples from\n\t * uncompressed chunk to compressed chunk but the INSERTs check\n\t * tuples in the opposite order.\n\t *\n\t * If there are unique constraints and multiple INSERTs happening at start\n\t * we want to just bail out so not to cause wasted work and bloat.\n\t */\n\tif (has_unique_constraints)\n\t{\n\t\tGetLockConflicts(&locktag, ExclusiveLock, &count);\n\n\t\tif (count > 1)\n\t\t{\n\t\t\telog(WARNING,\n\t\t\t\t \"skipping recompression of chunk %s.%s due to unique constraints and concurrent \"\n\t\t\t\t \"DML\",\n\t\t\t\t NameStr(uncompressed_chunk->fd.schema_name),\n\t\t\t\t NameStr(uncompressed_chunk->fd.table_name));\n\n\t\t\ttable_close(uncompressed_chunk_rel, NoLock);\n\t\t\ttable_close(compressed_chunk_rel, NoLock);\n\n\t\t\tPG_RETURN_OID(uncompressed_chunk_id);\n\t\t}\n\t}\n\n\tHypertable *ht = ts_hypertable_get_by_id(uncompressed_chunk->fd.hypertable_id);\n\tif (ht->range_space)\n\t\tts_chunk_column_stats_calculate(ht, uncompressed_chunk);\n\n\tTupleDesc compressed_rel_tupdesc = RelationGetDescr(compressed_chunk_rel);\n\tTupleDesc uncompressed_rel_tupdesc = RelationGetDescr(uncompressed_chunk_rel);\n\t/******************** row decompressor **************/\n\n\tRowDecompressor decompressor = build_decompressor(RelationGetDescr(compressed_chunk_rel),\n\t\t\t\t\t\t\t\t\t\t\t\t\t  RelationGetDescr(uncompressed_chunk_rel));\n\n\t/********** row compressor *******************/\n\tRowCompressor row_compressor;\n\tAssert(settings->fd.compress_relid == RelationGetRelid(compressed_chunk_rel));\n\trow_compressor_init(&row_compressor,\n\t\t\t\t\t\tsettings,\n\t\t\t\t\t\tRelationGetDescr(uncompressed_chunk_rel),\n\t\t\t\t\t\tRelationGetDescr(compressed_chunk_rel));\n\n\tBulkWriter writer = bulk_writer_build(compressed_chunk_rel, 0);\n\tOid index_oid = get_compressed_chunk_index(writer.indexstate, settings);\n\n\t/* For chunks with no segmentby settings, we can still do segmentwise recompression\n\t * The entire chunk is treated as a single segment\n\t */\n\telog(ts_guc_debug_compression_path_info ? INFO : DEBUG1,\n\t\t \"Using index \\\"%s\\\" for recompression\",\n\t\t get_rel_name(index_oid));\n\n\tLOCKMODE index_lockmode =\n\t\tts_guc_enable_exclusive_locking_recompression ? ExclusiveLock : RowExclusiveLock;\n\tRelation index_rel = index_open(index_oid, index_lockmode);\n\tereport(DEBUG1,\n\t\t\t(errmsg(\"locks acquired for recompression: \\\"%s.%s\\\"\",\n\t\t\t\t\tNameStr(uncompressed_chunk->fd.schema_name),\n\t\t\t\t\tNameStr(uncompressed_chunk->fd.table_name))));\n\n\t/* Need to populate recompress context of an uncompressed chunk */\n\tRecompressContext *recompress_ctx =\n\t\tcompress_chunk_populate_recompress_ctx(settings,\n\t\t\t\t\t\t\t\t\t\t\t   uncompressed_chunk_rel,\n\t\t\t\t\t\t\t\t\t\t\t   compressed_chunk_rel,\n\t\t\t\t\t\t\t\t\t\t\t   index_rel,\n\t\t\t\t\t\t\t\t\t\t\t   true);\n\t/* Used for sorting and iterating over all the uncompressed tuples that have\n\t * to be recompressed. These tuples are sorted based on the segmentby and\n\t * orderby settings.\n\t */\n\tTuplesortstate *input_tuplesortstate = tuplesort_begin_heap(uncompressed_rel_tupdesc,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\trecompress_ctx->n_keys,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\trecompress_ctx->sort_keys,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\trecompress_ctx->sort_operators,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\trecompress_ctx->sort_collations,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\trecompress_ctx->nulls_first,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tmaintenance_work_mem,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfalse);\n\n\t/* Used for gathering and resorting the tuples that should be recompressed together.\n\t * Since we are working on a per-segment level here, we only need to sort them\n\t * based on the orderby settings.\n\t */\n\tTuplesortstate *recompress_tuplesortstate =\n\t\ttuplesort_begin_heap(uncompressed_rel_tupdesc,\n\t\t\t\t\t\t\t recompress_ctx->num_orderby,\n\t\t\t\t\t\t\t &recompress_ctx->sort_keys[recompress_ctx->num_segmentby],\n\t\t\t\t\t\t\t &recompress_ctx->sort_operators[recompress_ctx->num_segmentby],\n\t\t\t\t\t\t\t &recompress_ctx->sort_collations[recompress_ctx->num_segmentby],\n\t\t\t\t\t\t\t &recompress_ctx->nulls_first[recompress_ctx->num_segmentby],\n\t\t\t\t\t\t\t maintenance_work_mem,\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t false);\n\n\t/************** snapshot ****************************/\n\tSnapshot snapshot = RegisterSnapshot(GetTransactionSnapshot());\n\n\tTupleTableSlot *uncompressed_slot =\n\t\tMakeTupleTableSlot(uncompressed_rel_tupdesc, &TTSOpsMinimalTuple);\n\tTupleTableSlot *compressed_slot = table_slot_create(compressed_chunk_rel, NULL);\n\n\tDatum *values = palloc(sizeof(Datum) * recompress_ctx->n_keys);\n\tbool *isnulls = palloc(sizeof(bool) * recompress_ctx->n_keys);\n\n\tHeapTuple compressed_tuple;\n\tIndexScanDesc index_scan = index_beginscan_compat(compressed_chunk_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  index_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  snapshot,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  recompress_ctx->num_segmentby,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  0);\n\n\tbool found_tuple = fetch_uncompressed_chunk_into_tuplesort(input_tuplesortstate,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   uncompressed_chunk_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   snapshot);\n\tif (!found_tuple)\n\t\tgoto finish;\n\ttuplesort_performsort(input_tuplesortstate);\n\n\tfor (found_tuple = tuplesort_gettupleslot(input_tuplesortstate,\n\t\t\t\t\t\t\t\t\t\t\t  true /*=forward*/,\n\t\t\t\t\t\t\t\t\t\t\t  false /*=copy*/,\n\t\t\t\t\t\t\t\t\t\t\t  uncompressed_slot,\n\t\t\t\t\t\t\t\t\t\t\t  NULL /*=abbrev*/);\n\t\t found_tuple;)\n\t{\n\t\tCHECK_FOR_INTERRUPTS();\n\n\t\tfor (int i = 0; i < recompress_ctx->n_keys; i++)\n\t\t{\n\t\t\tvalues[i] = slot_getattr(uncompressed_slot,\n\t\t\t\t\t\t\t\t\t AttrOffsetGetAttrNumber(\n\t\t\t\t\t\t\t\t\t\t recompress_ctx->current_segment[i].chunk_offset),\n\t\t\t\t\t\t\t\t\t &isnulls[i]);\n\t\t}\n\n\t\tupdate_current_segment(recompress_ctx->current_segment,\n\t\t\t\t\t\t\t   values,\n\t\t\t\t\t\t\t   isnulls,\n\t\t\t\t\t\t\t   recompress_ctx->num_segmentby);\n\n\t\t/* Build scankeys based on uncompressed tuple values */\n\t\tupdate_segmentby_scankeys(values,\n\t\t\t\t\t\t\t\t  isnulls,\n\t\t\t\t\t\t\t\t  recompress_ctx->num_segmentby,\n\t\t\t\t\t\t\t\t  recompress_ctx->index_scankeys);\n\n\t\tupdate_orderby_scankeys(values,\n\t\t\t\t\t\t\t\tisnulls,\n\t\t\t\t\t\t\t\trecompress_ctx->num_segmentby,\n\t\t\t\t\t\t\t\trecompress_ctx->num_orderby,\n\t\t\t\t\t\t\t\trecompress_ctx->orderby_scankeys);\n\n\t\tindex_rescan(index_scan,\n\t\t\t\t\t recompress_ctx->index_scankeys,\n\t\t\t\t\t recompress_ctx->num_segmentby,\n\t\t\t\t\t NULL,\n\t\t\t\t\t 0);\n\n\t\tbool done_with_segment = false;\n\t\tbool tuples_for_recompression = false;\n\t\tenum Batch_match_result result;\n\n\t\twhile (index_getnext_slot(index_scan, ForwardScanDirection, compressed_slot))\n\t\t{\n\t\t\t/* Check if the uncompressed tuple is before, inside, or after the compressed batch */\n\t\t\tresult = match_tuple_batch(compressed_slot,\n\t\t\t\t\t\t\t\t\t   recompress_ctx->num_orderby,\n\t\t\t\t\t\t\t\t\t   recompress_ctx->orderby_scankeys,\n\t\t\t\t\t\t\t\t\t   &recompress_ctx->nulls_first[recompress_ctx->num_segmentby]);\n\n\t\t\t/* If the tuple is before the batch, add it for recompression\n\t\t\t * also keep adding uncompressed tuples while they are:\n\t\t\t * - any left\n\t\t\t * - before the current batch\n\t\t\t * - in the same segment group\n\t\t\t */\n\t\t\twhile (result == Tuple_before)\n\t\t\t{\n\t\t\t\ttuples_for_recompression = true;\n\t\t\t\ttuplesort_puttupleslot(recompress_tuplesortstate, uncompressed_slot);\n\t\t\t\t/* If we happen to hit the end of uncompressed tuples or tuple changed segment group\n\t\t\t\t * we are done with the segment group\n\t\t\t\t */\n\t\t\t\tfound_tuple = tuplesort_gettupleslot(input_tuplesortstate,\n\t\t\t\t\t\t\t\t\t\t\t\t\t true /*=forward*/,\n\t\t\t\t\t\t\t\t\t\t\t\t\t false /*=copy*/,\n\t\t\t\t\t\t\t\t\t\t\t\t\t uncompressed_slot,\n\t\t\t\t\t\t\t\t\t\t\t\t\t NULL /*=abbrev*/);\n\n\t\t\t\tif (!found_tuple)\n\t\t\t\t{\n\t\t\t\t\tdone_with_segment = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tfor (int i = 0; i < recompress_ctx->n_keys; i++)\n\t\t\t\t{\n\t\t\t\t\tvalues[i] = slot_getattr(uncompressed_slot,\n\t\t\t\t\t\t\t\t\t\t\t AttrOffsetGetAttrNumber(\n\t\t\t\t\t\t\t\t\t\t\t\t recompress_ctx->current_segment[i].chunk_offset),\n\t\t\t\t\t\t\t\t\t\t\t &isnulls[i]);\n\t\t\t\t}\n\n\t\t\t\tdone_with_segment = check_changed_group(recompress_ctx->current_segment,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tvalues,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tisnulls,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\trecompress_ctx->num_segmentby);\n\t\t\t\tif (done_with_segment)\n\t\t\t\t\tbreak;\n\n\t\t\t\tupdate_orderby_scankeys(values,\n\t\t\t\t\t\t\t\t\t\tisnulls,\n\t\t\t\t\t\t\t\t\t\trecompress_ctx->num_segmentby,\n\t\t\t\t\t\t\t\t\t\trecompress_ctx->num_orderby,\n\t\t\t\t\t\t\t\t\t\trecompress_ctx->orderby_scankeys);\n\t\t\t\tresult =\n\t\t\t\t\tmatch_tuple_batch(compressed_slot,\n\t\t\t\t\t\t\t\t\t  recompress_ctx->num_orderby,\n\t\t\t\t\t\t\t\t\t  recompress_ctx->orderby_scankeys,\n\t\t\t\t\t\t\t\t\t  &recompress_ctx->nulls_first[recompress_ctx->num_segmentby]);\n\t\t\t}\n\n\t\t\t/* If we are done with segment, recompress everything we have so far\n\t\t\t * and break out of this segment index scan\n\t\t\t */\n\t\t\tif (done_with_segment)\n\t\t\t{\n\t\t\t\ttuples_for_recompression = false;\n\t\t\t\trecompress_segment(recompress_tuplesortstate,\n\t\t\t\t\t\t\t\t   uncompressed_chunk_rel,\n\t\t\t\t\t\t\t\t   &row_compressor,\n\t\t\t\t\t\t\t\t   &writer);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t/* If the tuple matches the batch, add the batch for recompression */\n\t\t\t/* Potential optimization: merge uncompressed tuples and decompressed tuples\n\t\t\t * into the tuplesortstate since they are both already sorted\n\t\t\t */\n\t\t\tif (result == Tuple_match)\n\t\t\t{\n\t\t\t\ttuples_for_recompression = true;\n\t\t\t\tbool should_free;\n\n\t\t\t\tcompressed_tuple = ExecFetchSlotHeapTuple(compressed_slot, false, &should_free);\n\n\t\t\t\theap_deform_tuple(compressed_tuple,\n\t\t\t\t\t\t\t\t  compressed_rel_tupdesc,\n\t\t\t\t\t\t\t\t  decompressor.compressed_datums,\n\t\t\t\t\t\t\t\t  decompressor.compressed_is_nulls);\n\n\t\t\t\trow_decompressor_decompress_row_to_tuplesort(&decompressor,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t recompress_tuplesortstate);\n\n\t\t\t\tif (!delete_tuple_for_recompression(compressed_chunk_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t&(compressed_slot->tts_tid),\n\t\t\t\t\t\t\t\t\t\t\t\t\tsnapshot))\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),\n\t\t\t\t\t\t\t errmsg(\"aborting recompression due to concurrent updates on \"\n\t\t\t\t\t\t\t\t\t\"compressed data, retrying with next policy run\")));\n\t\t\t\tCommandCounterIncrement();\n\n\t\t\t\tif (should_free)\n\t\t\t\t\theap_freetuple(compressed_tuple);\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t/* At this point, tuple is after the batch\n\t\t\t * If there are tuples added for recompression, do it\n\t\t\t * and continue to the next batch\n\t\t\t */\n\t\t\tif (tuples_for_recompression)\n\t\t\t{\n\t\t\t\ttuples_for_recompression = false;\n\t\t\t\trecompress_segment(recompress_tuplesortstate,\n\t\t\t\t\t\t\t\t   uncompressed_chunk_rel,\n\t\t\t\t\t\t\t\t   &row_compressor,\n\t\t\t\t\t\t\t\t   &writer);\n\t\t\t}\n\t\t}\n\n\t\t/* End if we are finished with all uncompressed tuples */\n\t\tif (!found_tuple)\n\t\t{\n\t\t\tbreak;\n\t\t}\n\n\t\t/* Reset index scan if we are done with with this segment */\n\t\tif (done_with_segment)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* We are done with existing batches for this segment group\n\t\t * Everything after this point goes into new batches\n\t\t * until we hit a new segment group or exhaust the uncompressed tuples\n\t\t */\n\t\twhile (!check_changed_group(recompress_ctx->current_segment,\n\t\t\t\t\t\t\t\t\tvalues,\n\t\t\t\t\t\t\t\t\tisnulls,\n\t\t\t\t\t\t\t\t\trecompress_ctx->num_segmentby))\n\t\t{\n\t\t\ttuples_for_recompression = true;\n\t\t\ttuplesort_puttupleslot(recompress_tuplesortstate, uncompressed_slot);\n\t\t\tfound_tuple = tuplesort_gettupleslot(input_tuplesortstate,\n\t\t\t\t\t\t\t\t\t\t\t\t true /*=forward*/,\n\t\t\t\t\t\t\t\t\t\t\t\t false /*=copy*/,\n\t\t\t\t\t\t\t\t\t\t\t\t uncompressed_slot,\n\t\t\t\t\t\t\t\t\t\t\t\t NULL /*=abbrev*/);\n\t\t\tif (!found_tuple)\n\t\t\t{\n\t\t\t\ttuples_for_recompression = false;\n\t\t\t\trecompress_segment(recompress_tuplesortstate,\n\t\t\t\t\t\t\t\t   uncompressed_chunk_rel,\n\t\t\t\t\t\t\t\t   &row_compressor,\n\t\t\t\t\t\t\t\t   &writer);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tfor (int i = 0; i < recompress_ctx->num_segmentby; i++)\n\t\t\t{\n\t\t\t\tvalues[i] = slot_getattr(uncompressed_slot,\n\t\t\t\t\t\t\t\t\t\t AttrOffsetGetAttrNumber(\n\t\t\t\t\t\t\t\t\t\t\t recompress_ctx->current_segment[i].chunk_offset),\n\t\t\t\t\t\t\t\t\t\t &isnulls[i]);\n\t\t\t}\n\t\t}\n\n\t\tif (tuples_for_recompression)\n\t\t{\n\t\t\trecompress_segment(recompress_tuplesortstate,\n\t\t\t\t\t\t\t   uncompressed_chunk_rel,\n\t\t\t\t\t\t\t   &row_compressor,\n\t\t\t\t\t\t\t   &writer);\n\t\t}\n\t}\n\nfinish:\n\trow_compressor_close(&row_compressor);\n\tbulk_writer_close(&writer);\n\tExecDropSingleTupleTableSlot(uncompressed_slot);\n\tExecDropSingleTupleTableSlot(compressed_slot);\n\tindex_endscan(index_scan);\n\tUnregisterSnapshot(snapshot);\n\tindex_close(index_rel, NoLock);\n\trow_decompressor_close(&decompressor);\n\n\ttuplesort_end(input_tuplesortstate);\n\ttuplesort_end(recompress_tuplesortstate);\n\n\tfree_chunk_recompress_ctx(recompress_ctx);\n\n\t/* If we can quickly upgrade the lock, lets try updating the chunk status to fully\n\t * compressed. But we need to check if there are any uncompressed tuples in the\n\t * relation since somebody might have inserted new tuples while we were recompressing.\n\t */\n\tif (ConditionalLockRelation(uncompressed_chunk_rel, ExclusiveLock))\n\t{\n\t\ttry_updating_chunk_status(uncompressed_chunk, uncompressed_chunk_rel);\n\t}\n\telse if (has_unique_constraints)\n\t{\n\t\t/*\n\t\t * This can be problematic since we cannot acquire ExclusiveLock meaning its\n\t\t * possible there are inserts going which need to check unique constraints.\n\t\t * Due to the reverse direction of tuple movement, concurrent recompression\n\t\t * and speculative insertion could potentially cause false negatives during\n\t\t * constraint checking. For now, our best option here is to bail.\n\t\t *\n\t\t * We use a spin lock to wait for the ExclusiveLock or bail out if we can't get it in time.\n\t\t */\n\n\t\tint lock_retry = 0;\n\t\twhile (true)\n\t\t{\n\t\t\tif (ConditionalLockRelation(uncompressed_chunk_rel, ExclusiveLock))\n\t\t\t{\n\t\t\t\ttry_updating_chunk_status(uncompressed_chunk, uncompressed_chunk_rel);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * Check for interrupts while trying to (re-)acquire the exclusive\n\t\t\t * lock.\n\t\t\t */\n\t\t\tCHECK_FOR_INTERRUPTS();\n\n\t\t\tif (++lock_retry >\n\t\t\t\t(RECOMPRESS_EXCLUSIVE_LOCK_TIMEOUT / RECOMPRESS_EXCLUSIVE_LOCK_WAIT_INTERVAL))\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * We failed to establish the lock in the specified number of\n\t\t\t\t * retries. This means we give up trying to get the exclusive lock are abort the\n\t\t\t\t * recompression operation\n\t\t\t\t */\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),\n\t\t\t\t\t\t errmsg(\"aborting recompression due to concurrent DML on uncompressed \"\n\t\t\t\t\t\t\t\t\"data, retrying with next policy run\")));\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t(void) WaitLatch(MyLatch,\n\t\t\t\t\t\t\t WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,\n\t\t\t\t\t\t\t RECOMPRESS_EXCLUSIVE_LOCK_WAIT_INTERVAL,\n\t\t\t\t\t\t\t WAIT_EVENT_VACUUM_TRUNCATE);\n\t\t\tResetLatch(MyLatch);\n\t\t\tDEBUG_WAITPOINT(\"chunk_recompress_after_latch\");\n\t\t}\n\t}\n\n\ttable_close(uncompressed_chunk_rel, NoLock);\n\ttable_close(compressed_chunk_rel, NoLock);\n\n\tPG_RETURN_OID(uncompressed_chunk_id);\n}\n\n/*\n * perform_recompression expects appropriate permissions and checks have already been done.\n * Relations must have appropriate locks and the CompressionSettings of compressed_chunk and\n * new_compressed_chunk should match\n */\nstatic void\nperform_recompression(RecompressContext *recompress_ctx, Relation compressed_chunk_rel,\n\t\t\t\t\t  Relation uncompressed_chunk_rel, Relation index_rel,\n\t\t\t\t\t  CompressionSettings *new_settings, Relation new_compressed_chunk_rel)\n{\n\tRowDecompressor decompressor;\n\tTuplesortstate *tuplesortstate;\n\tRowCompressor row_compressor;\n\tBulkWriter writer;\n\tTupleTableSlot *compressed_slot;\n\tbool first_iteration = true;\n\tIndexScanDesc index_scan;\n\tHeapTuple compressed_tuple;\n\n\tPushActiveSnapshot(GetTransactionSnapshot());\n\n\tdecompressor = build_decompressor(RelationGetDescr(compressed_chunk_rel),\n\t\t\t\t\t\t\t\t\t  RelationGetDescr(uncompressed_chunk_rel));\n\n\ttuplesortstate = tuplesort_begin_heap(RelationGetDescr(uncompressed_chunk_rel),\n\t\t\t\t\t\t\t\t\t\t  recompress_ctx->n_keys,\n\t\t\t\t\t\t\t\t\t\t  recompress_ctx->sort_keys,\n\t\t\t\t\t\t\t\t\t\t  recompress_ctx->sort_operators,\n\t\t\t\t\t\t\t\t\t\t  recompress_ctx->sort_collations,\n\t\t\t\t\t\t\t\t\t\t  recompress_ctx->nulls_first,\n\t\t\t\t\t\t\t\t\t\t  maintenance_work_mem,\n\t\t\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t\t\t  false);\n\n\trow_compressor_init(&row_compressor,\n\t\t\t\t\t\tnew_settings,\n\t\t\t\t\t\tRelationGetDescr(uncompressed_chunk_rel),\n\t\t\t\t\t\tRelationGetDescr(new_compressed_chunk_rel));\n\n\twriter = bulk_writer_build(new_compressed_chunk_rel, 0);\n\tcompressed_slot = table_slot_create(compressed_chunk_rel, NULL);\n\tDatum *values = palloc(sizeof(Datum) * recompress_ctx->num_segmentby);\n\tbool *isnulls = palloc(sizeof(bool) * recompress_ctx->num_segmentby);\n\n\t/*\n\t * we use the compressed chunk's index to scan so that we get the compressed tuples sorted\n\t * by segment-by and order-by minmax\n\t */\n\tindex_scan =\n\t\tindex_beginscan_compat(compressed_chunk_rel, index_rel, GetActiveSnapshot(), NULL, 0, 0);\n\tindex_scan->xs_want_itup = true;\n\tindex_rescan(index_scan, NULL, 0, NULL, 0);\n\n\twhile (index_getnext_slot(index_scan, ForwardScanDirection, compressed_slot))\n\t{\n\t\tfor (int i = 0; i < recompress_ctx->num_segmentby; i++)\n\t\t{\n\t\t\tvalues[i] = index_getattr(index_scan->xs_itup,\n\t\t\t\t\t\t\t\t\t  AttrOffsetGetAttrNumber(i),\n\t\t\t\t\t\t\t\t\t  index_scan->xs_itupdesc,\n\t\t\t\t\t\t\t\t\t  &isnulls[i]);\n\t\t}\n\n\t\tif (first_iteration)\n\t\t{\n\t\t\tupdate_current_segment(recompress_ctx->current_segment,\n\t\t\t\t\t\t\t\t   values,\n\t\t\t\t\t\t\t\t   isnulls,\n\t\t\t\t\t\t\t\t   recompress_ctx->num_segmentby);\n\t\t\tfirst_iteration = false;\n\t\t}\n\t\telse if (check_changed_group(recompress_ctx->current_segment,\n\t\t\t\t\t\t\t\t\t values,\n\t\t\t\t\t\t\t\t\t isnulls,\n\t\t\t\t\t\t\t\t\t recompress_ctx->num_segmentby))\n\t\t{\n\t\t\trecompress_segment(tuplesortstate, uncompressed_chunk_rel, &row_compressor, &writer);\n\t\t\tupdate_current_segment(recompress_ctx->current_segment,\n\t\t\t\t\t\t\t\t   values,\n\t\t\t\t\t\t\t\t   isnulls,\n\t\t\t\t\t\t\t\t   recompress_ctx->num_segmentby);\n\t\t}\n\n\t\tbool should_free;\n\n\t\tcompressed_tuple = ExecFetchSlotHeapTuple(compressed_slot, false, &should_free);\n\n\t\theap_deform_tuple(compressed_tuple,\n\t\t\t\t\t\t  RelationGetDescr(compressed_chunk_rel),\n\t\t\t\t\t\t  decompressor.compressed_datums,\n\t\t\t\t\t\t  decompressor.compressed_is_nulls);\n\n\t\trow_decompressor_decompress_row_to_tuplesort(&decompressor, tuplesortstate);\n\n\t\tif (should_free)\n\t\t\theap_freetuple(compressed_tuple);\n\t}\n\n\trecompress_segment(tuplesortstate, uncompressed_chunk_rel, &row_compressor, &writer);\n\n\trow_compressor_close(&row_compressor);\n\tbulk_writer_close(&writer);\n\tExecDropSingleTupleTableSlot(compressed_slot);\n\tindex_endscan(index_scan);\n\trow_decompressor_close(&decompressor);\n\ttuplesort_end(tuplesortstate);\n\tPopActiveSnapshot();\n}\n\n/*\n * Perform per segment in-memory recompression of a compressed chunk.\n */\nbool\nrecompress_chunk_in_memory_impl(Chunk *uncompressed_chunk)\n{\n\tif (uncompressed_chunk == NULL)\n\t\tereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"chunk cannot be NULL\")));\n\n\tEnsure(ts_guc_enable_in_memory_recompression, \"in-memory recompression functionality disabled\");\n\n\tif (!ts_chunk_is_compressed(uncompressed_chunk) || ts_chunk_is_frozen(uncompressed_chunk))\n\t\treturn false;\n\n\tChunk *compressed_chunk = ts_chunk_get_by_id(uncompressed_chunk->fd.compressed_chunk_id, true);\n\tEnsure(compressed_chunk != NULL,\n\t\t   \"compressed chunk not found for chunk \\\"%s\\\"\",\n\t\t   get_rel_name(uncompressed_chunk->table_id));\n\n\tCompressionSettings *settings = ts_compression_settings_get(uncompressed_chunk->table_id);\n\tEnsure(settings != NULL,\n\t\t   \"compression settings not found for chunk \\\"%s\\\"\",\n\t\t   get_rel_name(uncompressed_chunk->table_id));\n\n\tEnsure(settings->fd.orderby, \"empty order by, cannot recompress in-memory\");\n\n\tLOCKMODE lockmode = ExclusiveLock;\n\tRelation uncompressed_chunk_rel = table_open(uncompressed_chunk->table_id, lockmode);\n\tRelation compressed_chunk_rel = table_open(compressed_chunk->table_id, lockmode);\n\n\t/* Check new chunk will have the same compression settings */\n\tHypertable *ht = ts_hypertable_get_by_id(uncompressed_chunk->fd.hypertable_id);\n\tCompressionSettings *check_new_settings =\n\t\tts_compression_settings_get(uncompressed_chunk->hypertable_relid);\n\tcompression_settings_set_defaults(ht,\n\t\t\t\t\t\t\t\t\t  check_new_settings,\n\t\t\t\t\t\t\t\t\t  ts_alter_table_with_clause_parse(NIL));\n\n\tif (!ts_array_equal(settings->fd.segmentby, check_new_settings->fd.segmentby))\n\t{\n\t\ttable_close(uncompressed_chunk_rel, lockmode);\n\t\ttable_close(compressed_chunk_rel, lockmode);\n\t\treturn false;\n\t}\n\n\t/* Check that the compressed chunk's index exist. TODO: Add support for this scenario */\n\tCatalogIndexState indstate = CatalogOpenIndexes(compressed_chunk_rel);\n\tOid index_oid = get_compressed_chunk_index(indstate, settings);\n\tCatalogCloseIndexes(indstate);\n\n\tif (!OidIsValid(index_oid))\n\t{\n\t\ttable_close(uncompressed_chunk_rel, lockmode);\n\t\ttable_close(compressed_chunk_rel, lockmode);\n\t\treturn false;\n\t}\n\n\tRelation index_rel = index_open(index_oid, lockmode);\n\tRecompressContext *recompress_ctx =\n\t\tcompress_chunk_populate_recompress_ctx(settings,\n\t\t\t\t\t\t\t\t\t\t\t   uncompressed_chunk_rel,\n\t\t\t\t\t\t\t\t\t\t\t   compressed_chunk_rel,\n\t\t\t\t\t\t\t\t\t\t\t   index_rel,\n\t\t\t\t\t\t\t\t\t\t\t   false);\n\n\t/* Delete old compression settings before creating new compressed chunk to avoid conflict */\n\tts_compression_settings_delete(uncompressed_chunk->table_id);\n\tHypertable *compressed_ht = ts_hypertable_get_by_id(ht->fd.compressed_hypertable_id);\n\tChunk *new_compressed_chunk =\n\t\tcreate_compress_chunk(compressed_ht, uncompressed_chunk, InvalidOid);\n\t/* The old compression settings were deleted above to avoid catalog conflicts. */\n\tCompressionSettings *new_settings = ts_compression_settings_get(uncompressed_chunk->table_id);\n\tRelation new_compressed_chunk_rel = table_open(new_compressed_chunk->table_id, lockmode);\n\n\tEnsure(ts_compression_settings_equal(new_settings, check_new_settings),\n\t\t   \"compression settings mismatch during recompression of \\\"%s.%s\\\"\",\n\t\t   NameStr(uncompressed_chunk->fd.schema_name),\n\t\t   NameStr(uncompressed_chunk->fd.table_name));\n\n\tperform_recompression(recompress_ctx,\n\t\t\t\t\t\t  compressed_chunk_rel,\n\t\t\t\t\t\t  uncompressed_chunk_rel,\n\t\t\t\t\t\t  index_rel,\n\t\t\t\t\t\t  new_settings,\n\t\t\t\t\t\t  new_compressed_chunk_rel);\n\n\tfree_chunk_recompress_ctx(recompress_ctx);\n\tindex_close(index_rel, NoLock);\n\ttable_close(uncompressed_chunk_rel, NoLock);\n\ttable_close(compressed_chunk_rel, NoLock);\n\ttable_close(new_compressed_chunk_rel, NoLock);\n\n\tLockRelationOid(uncompressed_chunk->table_id, AccessExclusiveLock);\n\tLockRelationOid(compressed_chunk->table_id, AccessExclusiveLock);\n\tts_chunk_drop(compressed_chunk, DROP_RESTRICT, -1);\n\tif (ts_chunk_clear_status(uncompressed_chunk, CHUNK_STATUS_COMPRESSED_UNORDERED))\n\t\tereport(DEBUG1,\n\t\t\t\t(errmsg(\"cleared chunk status for recompression: \\\"%s.%s\\\"\",\n\t\t\t\t\t\tNameStr(uncompressed_chunk->fd.schema_name),\n\t\t\t\t\t\tNameStr(uncompressed_chunk->fd.table_name))));\n\tts_chunk_set_compressed_chunk(uncompressed_chunk, new_compressed_chunk->fd.id);\n\n\t/* recompress successful */\n\treturn true;\n}\n\nstatic void\nupdate_scankey(ScanKey index_scankey, Datum val, bool is_null)\n{\n\tindex_scankey->sk_flags = is_null ? SK_ISNULL | SK_SEARCHNULL : 0;\n\tindex_scankey->sk_argument = val;\n}\n\nstatic void\nupdate_segmentby_scankeys(Datum *values, bool *isnulls, int num_segmentby, ScanKey index_scankeys)\n{\n\tfor (int i = 0; i < num_segmentby; i++)\n\t{\n\t\tupdate_scankey(&index_scankeys[i], values[i], isnulls[i]);\n\t}\n}\n\nstatic void\nupdate_orderby_scankeys(Datum *values, bool *isnulls, int num_segmentby, int num_orderby,\n\t\t\t\t\t\tScanKey orderby_scankeys)\n{\n\tint min_index, max_index;\n\tfor (int i = 0; i < num_orderby; i++)\n\t{\n\t\tmin_index = i * 2;\n\t\tmax_index = min_index + 1;\n\t\tupdate_scankey(&orderby_scankeys[min_index],\n\t\t\t\t\t   values[num_segmentby + i],\n\t\t\t\t\t   isnulls[num_segmentby + i]);\n\t\tupdate_scankey(&orderby_scankeys[max_index],\n\t\t\t\t\t   values[num_segmentby + i],\n\t\t\t\t\t   isnulls[num_segmentby + i]);\n\t}\n}\n\nstatic enum Batch_match_result\nhandle_null_scan(int key_flags, bool nulls_first, enum Batch_match_result result)\n{\n\tif (key_flags & SK_ISNULL)\n\t\treturn nulls_first ? Tuple_before : Tuple_after;\n\n\treturn result;\n}\n\nstatic enum Batch_match_result\nmatch_tuple_batch(TupleTableSlot *compressed_slot, int num_orderby, ScanKey orderby_scankeys,\n\t\t\t\t  bool *nulls_first)\n{\n\tScanKey key;\n\tfor (int i = 0; i < num_orderby; i++)\n\t{\n\t\tkey = &orderby_scankeys[i * 2];\n\t\tif (!slot_key_test(compressed_slot, key))\n\t\t\treturn handle_null_scan(key->sk_flags, nulls_first[i], Tuple_before);\n\n\t\tkey = &orderby_scankeys[i * 2 + 1];\n\t\tif (!slot_key_test(compressed_slot, key))\n\t\t\treturn handle_null_scan(key->sk_flags, nulls_first[i], Tuple_after);\n\t}\n\n\treturn Tuple_match;\n}\n\nstatic bool\nfetch_uncompressed_chunk_into_tuplesort(Tuplesortstate *tuplesortstate,\n\t\t\t\t\t\t\t\t\t\tRelation uncompressed_chunk_rel, Snapshot snapshot)\n{\n\tbool matching_exist = false;\n\n\tTableScanDesc scan = table_beginscan(uncompressed_chunk_rel, snapshot, 0, 0);\n\tTupleTableSlot *slot = table_slot_create(uncompressed_chunk_rel, NULL);\n\n\twhile (table_scan_getnextslot(scan, ForwardScanDirection, slot))\n\t{\n\t\tmatching_exist = true;\n\t\tslot_getallattrs(slot);\n\t\ttuplesort_puttupleslot(tuplesortstate, slot);\n\t\tif (!delete_tuple_for_recompression(uncompressed_chunk_rel, &slot->tts_tid, snapshot))\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),\n\t\t\t\t\t errmsg(\"aborting recompression due to concurrent updates on \"\n\t\t\t\t\t\t\t\"uncompressed data, retrying with next policy run\")));\n\t}\n\tExecDropSingleTupleTableSlot(slot);\n\ttable_endscan(scan);\n\n\treturn matching_exist;\n}\n\n/* Sort the tuples and recompress them */\nstatic void\nrecompress_segment(Tuplesortstate *tuplesortstate, Relation compressed_chunk_rel,\n\t\t\t\t   RowCompressor *row_compressor, BulkWriter *writer)\n{\n\ttuplesort_performsort(tuplesortstate);\n\trow_compressor_reset(row_compressor);\n\trow_compressor_append_sorted_rows(row_compressor, tuplesortstate, compressed_chunk_rel, writer);\n\ttuplesort_reset(tuplesortstate);\n\tCommandCounterIncrement();\n}\n\nstatic void\nupdate_current_segment(CompressedSegmentInfo *current_segment, Datum *values, bool *isnulls,\n\t\t\t\t\t   int nsegmentby_cols)\n{\n\tfor (int i = 0; i < nsegmentby_cols; i++)\n\t{\n\t\t/* new segment, need to do per-segment processing */\n\t\tsegment_info_update(current_segment[i].segment_info, values[i], isnulls[i]);\n\t}\n}\n\nstatic bool\ncheck_changed_group(CompressedSegmentInfo *current_segment, Datum *values, bool *isnulls,\n\t\t\t\t\tint nsegmentby_cols)\n{\n\tfor (int i = 0; i < nsegmentby_cols; i++)\n\t{\n\t\tif (!segment_info_datum_is_in_group(current_segment[i].segment_info, values[i], isnulls[i]))\n\t\t\treturn true;\n\t}\n\treturn false;\n}\n\nstatic void\ninit_scankey(ScanKey sk, AttrNumber attnum, Oid atttypid, Oid attcollid, StrategyNumber strategy)\n{\n\tTypeCacheEntry *tce = lookup_type_cache(atttypid, TYPECACHE_BTREE_OPFAMILY);\n\tif (!OidIsValid(tce->btree_opf))\n\t\telog(ERROR, \"no btree opfamily for type \\\"%s\\\"\", format_type_be(atttypid));\n\n\tOid opr = get_opfamily_member(tce->btree_opf, atttypid, atttypid, strategy);\n\n\t/*\n\t * Fall back to btree operator input type when it is binary compatible with\n\t * the column type and no operator for column type could be found.\n\t */\n\tif (!OidIsValid(opr) && IsBinaryCoercible(atttypid, tce->btree_opintype))\n\t{\n\t\topr =\n\t\t\tget_opfamily_member(tce->btree_opf, tce->btree_opintype, tce->btree_opintype, strategy);\n\t}\n\n\tif (!OidIsValid(opr))\n\t\telog(ERROR, \"no operator for type \\\"%s\\\"\", format_type_be(atttypid));\n\n\topr = get_opcode(opr);\n\tif (!OidIsValid(opr))\n\t\telog(ERROR, \"no opcode for type \\\"%s\\\"\", format_type_be(atttypid));\n\n\tScanKeyEntryInitialize(sk,\n\t\t\t\t\t\t   0, /* flags */\n\t\t\t\t\t\t   attnum,\n\t\t\t\t\t\t   strategy,\n\t\t\t\t\t\t   InvalidOid, /* No strategy subtype. */\n\t\t\t\t\t\t   attcollid,\n\t\t\t\t\t\t   opr,\n\t\t\t\t\t\t   UnassignedDatum);\n}\n\nstatic void\ncreate_segmentby_scankeys(CompressionSettings *settings, Relation index_rel,\n\t\t\t\t\t\t  Relation compressed_chunk_rel, ScanKeyData *index_scankeys)\n{\n\tint num_segmentby = ts_array_length(settings->fd.segmentby);\n\n\tfor (int i = 0; i < num_segmentby; i++)\n\t{\n\t\tAttrNumber idx_attnum = AttrOffsetGetAttrNumber(i);\n\t\tAttrNumber in_attnum = index_rel->rd_index->indkey.values[i];\n\t\tconst NameData PG_USED_FOR_ASSERTS_ONLY *attname =\n\t\t\tattnumAttName(compressed_chunk_rel, in_attnum);\n\t\tAssert(strcmp(NameStr(*attname),\n\t\t\t\t\t  ts_array_get_element_text(settings->fd.segmentby, i + 1)) == 0);\n\n\t\tinit_scankey(&index_scankeys[i],\n\t\t\t\t\t idx_attnum,\n\t\t\t\t\t attnumTypeId(index_rel, idx_attnum),\n\t\t\t\t\t attnumCollationId(index_rel, idx_attnum),\n\t\t\t\t\t BTEqualStrategyNumber);\n\t}\n}\n\nstatic void\ncreate_orderby_scankeys(CompressionSettings *settings, Relation index_rel,\n\t\t\t\t\t\tRelation compressed_chunk_rel, ScanKeyData *orderby_scankeys)\n{\n\tint position;\n\tint num_orderby = ts_array_length(settings->fd.orderby);\n\t/* Create two scankeys per orderby column, for min and max metadata columns respectively */\n\tfor (int i = 0; i < num_orderby * 2; i = i + 2)\n\t{\n\t\tposition = (i / 2) + 1;\n\t\tAttrNumber first_attno =\n\t\t\tget_attnum(compressed_chunk_rel->rd_id, column_segment_min_name(position));\n\t\tStrategyNumber first_strategy = BTLessEqualStrategyNumber;\n\t\tAttrNumber second_attno =\n\t\t\tget_attnum(compressed_chunk_rel->rd_id, column_segment_max_name(position));\n\t\tStrategyNumber second_strategy = BTGreaterEqualStrategyNumber;\n\n\t\tAssert(first_attno != InvalidAttrNumber);\n\t\tAssert(second_attno != InvalidAttrNumber);\n\n\t\tbool is_desc = ts_array_get_element_bool(settings->fd.orderby_desc, position);\n\n\t\t/* If we are using DESC order, swap the order of metadata scankeys\n\t\t * since we rely on the order to determine whether a tuple is before or after\n\t\t * the compressed batch and the index is also ordered in that way.\n\t\t */\n\t\tif (is_desc)\n\t\t{\n\t\t\tAttrNumber temp_attno = first_attno;\n\t\t\tStrategyNumber temp_strategy = first_strategy;\n\t\t\tfirst_attno = second_attno;\n\t\t\tfirst_strategy = second_strategy;\n\t\t\tsecond_attno = temp_attno;\n\t\t\tsecond_strategy = temp_strategy;\n\t\t}\n\t\tinit_scankey(&orderby_scankeys[i],\n\t\t\t\t\t first_attno,\n\t\t\t\t\t attnumTypeId(compressed_chunk_rel, first_attno),\n\t\t\t\t\t attnumCollationId(compressed_chunk_rel, first_attno),\n\t\t\t\t\t first_strategy);\n\t\tinit_scankey(&orderby_scankeys[i + 1],\n\t\t\t\t\t second_attno,\n\t\t\t\t\t attnumTypeId(compressed_chunk_rel, second_attno),\n\t\t\t\t\t attnumCollationId(compressed_chunk_rel, second_attno),\n\t\t\t\t\t second_strategy);\n\t}\n}\n\n/* Deleting a tuple for recompression if we can.\n * If there is an unexpected result, we should just abort the operation completely.\n * There are potential optimizations that can be done here in certain scenarios.\n */\nstatic bool\ndelete_tuple_for_recompression(Relation rel, ItemPointer tid, Snapshot snapshot)\n{\n\tTM_Result result;\n\tTM_FailureData tmfd;\n\n\tresult =\n\t\ttable_tuple_delete(rel,\n\t\t\t\t\t\t   tid,\n\t\t\t\t\t\t   GetCurrentCommandId(true),\n\t\t\t\t\t\t   snapshot,\n\t\t\t\t\t\t   InvalidSnapshot,\n\t\t\t\t\t\t   true /* for now, just wait for commit/abort, that might let us proceed */\n\t\t\t\t\t\t   ,\n\t\t\t\t\t\t   &tmfd,\n\t\t\t\t\t\t   true /* changingPart */);\n\n\treturn result == TM_Ok;\n}\n\n/* Check if we can update the chunk status to fully compressed after segmentwise recompression\n * We can only do this if there were no concurrent DML operations, so we check to see if there are\n * any uncompressed tuples in the chunk after compression.\n * If there aren't, we can update the chunk status\n *\n * Note: Caller is expected to have an ExclusiveLock on the uncompressed_chunk\n */\nstatic void\ntry_updating_chunk_status(Chunk *uncompressed_chunk, Relation uncompressed_chunk_rel)\n{\n\tPushActiveSnapshot(GetLatestSnapshot());\n\tTableScanDesc scan = table_beginscan(uncompressed_chunk_rel, GetActiveSnapshot(), 0, 0);\n\tScanDirection scan_dir = BackwardScanDirection;\n\tTupleTableSlot *slot = table_slot_create(uncompressed_chunk_rel, NULL);\n\n\t/* Doing a backwards scan with assumption that newly inserted tuples\n\t * are most likely at the end of the heap.\n\t */\n\tbool has_tuples = false;\n\tif (table_scan_getnextslot(scan, scan_dir, slot))\n\t{\n\t\thas_tuples = true;\n\t}\n\n\tExecDropSingleTupleTableSlot(slot);\n\ttable_endscan(scan);\n\tPopActiveSnapshot();\n\n\tif (!has_tuples)\n\t{\n\t\t/*\n\t\t * Only clear PARTIAL. Segmentwise recompression only processes\n\t\t * segments that have new uncompressed data, so segments without new\n\t\t * data are left as-is. Any overlapping batches in those segments\n\t\t * remain as is, so the UNORDERED flag must be preserved.\n\t\t */\n\t\tif (ts_chunk_clear_status(uncompressed_chunk, CHUNK_STATUS_COMPRESSED_PARTIAL))\n\t\t\tereport(DEBUG1,\n\t\t\t\t\t(errmsg(\"cleared chunk status for recompression: \\\"%s.%s\\\"\",\n\t\t\t\t\t\t\tNameStr(uncompressed_chunk->fd.schema_name),\n\t\t\t\t\t\t\tNameStr(uncompressed_chunk->fd.table_name))));\n\n\t\t/* changed chunk status, so invalidate any plans involving this chunk */\n\t\tCacheInvalidateRelcacheByRelid(uncompressed_chunk->table_id);\n\t}\n}\n"
  },
  {
    "path": "tsl/src/compression/recompress.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <fmgr.h>\n#include <utils.h>\n\n#include \"chunk.h\"\n\n/* Structure to hold recompression sort information */\ntypedef struct RecompressContext\n{\n\tint num_segmentby;\n\tint num_orderby;\n\tint n_keys;\n\tAttrNumber *sort_keys;\n\tOid *sort_operators;\n\tOid *sort_collations;\n\tbool *nulls_first;\n\tCompressedSegmentInfo *current_segment;\n\tScanKeyData *index_scankeys;\n\tScanKeyData *orderby_scankeys;\n} RecompressContext;\n\nextern Datum tsl_recompress_chunk_segmentwise(PG_FUNCTION_ARGS);\n\nOid recompress_chunk_segmentwise_impl(Chunk *chunk);\nbool recompress_chunk_in_memory_impl(Chunk *uncompressed_chunk);\n\n/* Result of matching an uncompressed tuple against a compressed batch */\nenum Batch_match_result\n{\n\tTuple_before = 1,\n\tTuple_match,\n\tTuple_after,\n\t_Batch_match_result_max,\n};\n"
  },
  {
    "path": "tsl/src/compression/sparse_index_bloom1.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include \"postgres.h\"\n\nDatum bloom1_contains(PG_FUNCTION_ARGS);\n\nDatum bloom1_contains_any(PG_FUNCTION_ARGS);\n\nPGFunction bloom1_get_hash_function(Oid type, FmgrInfo **finfo);\n\nextern char const *bloom1_column_prefix;\n\n/*\n * We used to have two possible hashes depending on the build configuration,\n * which were incompatible with each other. They both used the \"bloom1\" column\n * prefix. This could lead to false negatives if a database was updated to a\n * different build of the TimescaleDB extension. Now these hashing configuration\n * use different prefixes. The bloom filter is still constructed according to\n * the \"bloom1\" rules.\n */\n#ifdef TS_USE_UMASH\n#define default_bloom1_column_prefix \"bloomh\"\n#else\n#define default_bloom1_column_prefix \"bloomg\"\n#endif\n"
  },
  {
    "path": "tsl/src/compression/wal_utils.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <replication/message.h>\n\n#include \"guc.h\"\n\n/*\n * Utils to insert markers into the WAL log which demarcate compression\n * and decompression operations.\n * The primary purpose is to be able to discern between user-driven DML\n * operations (caused by statements which INSERT/UPDATE/DELETE data), and\n * compression-driven DML (moving data to/from compressed chunks).\n */\n\n#define COMPRESSION_MARKER_START \"::timescaledb-compression-start\"\n#define COMPRESSION_MARKER_END \"::timescaledb-compression-end\"\n#define DECOMPRESSION_MARKER_START \"::timescaledb-decompression-start\"\n#define DECOMPRESSION_MARKER_END \"::timescaledb-decompression-end\"\n\nstatic inline bool\nis_compression_wal_markers_enabled()\n{\n\treturn ts_guc_enable_compression_wal_markers && XLogLogicalInfoActive();\n}\n\nstatic inline void\nwrite_logical_replication_msg_compression_start()\n{\n\tif (is_compression_wal_markers_enabled())\n\t{\n\t\tLogLogicalMessageCompat(COMPRESSION_MARKER_START, \"\", 0, true, true);\n\t}\n}\n\nstatic inline void\nwrite_logical_replication_msg_compression_end()\n{\n\tif (is_compression_wal_markers_enabled())\n\t{\n\t\tLogLogicalMessageCompat(COMPRESSION_MARKER_END, \"\", 0, true, true);\n\t}\n}\n\nstatic inline void\nwrite_logical_replication_msg_decompression_start()\n{\n\tif (is_compression_wal_markers_enabled())\n\t{\n\t\tLogLogicalMessageCompat(DECOMPRESSION_MARKER_START, \"\", 0, true, true);\n\t}\n}\n\nstatic inline void\nwrite_logical_replication_msg_decompression_end()\n{\n\tif (is_compression_wal_markers_enabled())\n\t{\n\t\tLogLogicalMessageCompat(DECOMPRESSION_MARKER_END, \"\", 0, true, true);\n\t}\n}\n"
  },
  {
    "path": "tsl/src/continuous_aggs/CMakeLists.txt",
    "content": "set(SOURCES\n    ${CMAKE_CURRENT_SOURCE_DIR}/common.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/create.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/finalize.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/insert.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/invalidation_threshold.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/invalidation.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/materialize.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/options.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/planner.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/refresh.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/utils.c)\ntarget_sources(${TSL_LIBRARY_NAME} PRIVATE ${SOURCES})\n"
  },
  {
    "path": "tsl/src/continuous_aggs/README.md",
    "content": "# Continuous Aggregates #\n\nA continuous aggregate is a special kind of materialized view for\naggregates that can be partially and continuously refreshed, either\nmanually or automated by a policy that runs in the background. Unlike\na regular materialized view, a continuous aggregate doesn't require\ncomplete re-materialization on every refresh. Instead, it is possible\nto refresh a subset of the continuous aggregate at relatively low\ncost, thus enabling continuous aggregation as new data is written or\nold data is updated and/or backfilled.\n\nTo enable continuous aggregation, a continuous aggregate stores\npartial aggregations for every time bucket in an internal\nhypertable. The advantage of this configuration is that each time\nbucket can be recomputed individually, without requiring updates to\nother buckets, and buckets can be combined to form more granular\naggregates (e.g., hourly buckets can be combined to form daily\nbuckets). Finalization of the partial buckets happens automatically at\nquery time. Although such finalization gives slightly higher querying\ntimes, it is offset by more efficient refreshes that only recompute\nthe buckets that have been \"invalidated\" by changes in the raw data.\n\nA continuous aggregate policy automates the refreshing, allowing the\naggregate to stay up-to-date without manual intervention. A policy can\nbe configured to only refresh the most recent data (e.g., just the\nlast hour's worth of data) or ensure that the continuous aggregate is\nalways up-to-date with the underlying source data. Policies that focus\non recent data allow older parts of the continuous aggregate to stay\nthe same or be governed by manual refreshes.\n\n## Bookkeeping and Internal State ##\n\nTimescaleDB does bookkeeping for each continuous aggregate to know\nwhich buckets of the aggregates require refreshing. Whenever a\nmodification happens to the source data, an invalidation for the\nmodified region is written to an invalidation log. However,\ninvalidations are not written after the *invalidation threshold*,\nwhich tracks the latest bucket materialized thus far. This threshold\nallows write amplification to be kept to a minimum by not writing\ninvalidations for \"hot\" time buckets that are assumed to still have\ndata being written to them.\n\nThus, to store, maintain, and query aggregations, continuous\naggregates consist of the following objects:\n\n1. A user view, which queries and finalizes the aggregations and is\n   also the object that users interact with.\n2. A partial view, which is used to materialize new data.\n3. A direct view, which holds the original query that users specified.\n4. An internal materialization hypertable, containing the materialized\n   data as partial aggregates for each time bucket.\n5. An invalidation threshold, which is a timestamp that tracks the\n   latest materialization. Invalidations that occur before this\n   timestamp will be logged, while invalidations after it will not be\n   logged.\n6. A trigger on the source hypertable that writes invalidations to the\n   hypertable invalidation log at transaction end, based on INSERT,\n   UPDATE, and DELETE statements that mutate the data.\n7. A hypertable invalidation log that tracks invalidated regions of\n   data for each hypertable. Entries in this log contain time ranges\n   that need to be re-materialized across all the hypertable's\n   continuous aggregates.\n8. A materialization invalidation log. Once a refresh runs on a given\n   continuous aggregate, this log tracks how invalidations from the\n   hypertable invalidation log are processed against the refresh\n   window for the refreshed continuous aggregate. Thus, a single\n   invalidation in the hypertable invalidation log becomes one entry\n   per continuous aggregate in the materialization invalidation log.\n\n## The materialized hypertable ##\n\nThe materialized hypertable does not store the aggregate's output, but\nrather the partial aggregate state. For instance, in case of an\naverage, each bucket stores the sum and count in an internal binary\nform. The partial aggregates are what gives continuous aggregates\nflexibility; buckets can be individually updated and multiple partial\naggregates can be combined to form new partials. Future enhancements\nmay allow aggregating at different time resolutions using the the same\nunderlying continuous aggregate.\n\n## The Invalidation Log and Threshold ##\n\nThere are two mechanisms for collecting hypertable invalidations:\n1. Using ModifyHypertable hooks for INSERT / UPDATE / DELETE\n2. Using a hook in our custom COPY implementation\n\n### Invalidation Log Table\n\nMutating transactions must record their mutations in the invalidation\nlog, so that a refresh knows to re-materialize the invalidated\nrange.\n\nTo reduce the extra writes by invalidations, only one invalidation range\n(lowest and highest modified value) is written at the end of a\nmutating transaction. As a result, a refresh might materialize more\ndata than necessary, but the insert incurs a smaller overhead\ninstead. Write amplification is further reduced by never writing\ninvalidations after the invalidation threshold, which can be\nconfigured to lag behind the time bucket that sees the most writes.\n\nWhenever a refresh occurs across a time range that is newer than the\ncurrent invalidation threshold, the threshold must first be moved to\nthe end of the refreshed region so that new invalidations are recorded\nin the region after the refresh. However, mutations in the refreshed\nregion can also happen concurrently with the refresh, so, in order to\nnot lose any invalidations, the invalidation threshold must be moved\nin its own transaction before the new region is materialized.\n\nThus, every refresh may happen across two transactions; first one that\nmoves the invalidation threshold (if necessary) and a second one that\ndoes the actual materialization of new data.\n\nThe second transaction of the refresh will only materialize regions\nthat are recorded as invalid in the invalidation log. Thus, the\ninitial state of a continuous aggregate is to have an entry in the\ninvalidation log that invalidates the entire range of the\naggregate. During the refresh, the log is processed and invalidations\nare cut against the given refresh window, leaving only invalidation\nentries that are outside the refresh window. Subsequently, if the\nrefresh window does not match any invalidations, there is nothing to\nrefresh either.\n\n## Distribution of functions across files\n\nEach source file has an associated header file that should be included\nto use functions from the corresponding file.\n\n<dl>\n<dt>`common.c`</dt>\n<dd>This file contains the functions common in all scenarios of creating a continuous aggregates.</dd>\n\n<dt>`create.c`</dt>\n<dd>This file contains the functions that are directly responsible for the creation of the continuous aggregates,\nlike creating hypertable, catalog_entry, view, etc.</dd>\n\n<dt>`finalize.c`</dt>\n<dd>This file contains the specific functions for the case when continous aggregates are created in old format.</dd>\n\n<dt>`materialize.c`</dt>\n<dd>This file contains the functions directly dealing with the materialization of the continuous aggregates.</dd>\n\n<dt>`invalidation.c`</dt>\n<dd>Functions related to invalidation processing for continuous aggregates.</dd>\n</dl>\n\n"
  },
  {
    "path": "tsl/src/continuous_aggs/common.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include \"common.h\"\n\n#include <utils/acl.h>\n#include <utils/date.h>\n#include <utils/timestamp.h>\n#include <utils/uuid.h>\n\n#include \"extension.h\"\n#include \"guc.h\"\n\nstatic Const *check_time_bucket_argument(Node *arg, char *position, bool process_checks);\nstatic void caggtimebucketinfo_init(ContinuousAggTimeBucketInfo *src, int32 hypertable_id,\n\t\t\t\t\t\t\t\t\tOid hypertable_oid, AttrNumber hypertable_partition_colno,\n\t\t\t\t\t\t\t\t\tOid hypertable_partition_coltype,\n\t\t\t\t\t\t\t\t\tint64 hypertable_partition_col_interval,\n\t\t\t\t\t\t\t\t\tint32 parent_mat_hypertable_id);\nstatic void process_additional_timebucket_parameter(ContinuousAggBucketFunction *bf, Const *arg,\n\t\t\t\t\t\t\t\t\t\t\t\t\tbool *custom_origin);\nstatic void process_timebucket_parameters(FuncExpr *fe, ContinuousAggBucketFunction *bf,\n\t\t\t\t\t\t\t\t\t\t  bool process_checks, bool is_cagg_create,\n\t\t\t\t\t\t\t\t\t\t  AttrNumber htpartcolno);\nstatic void caggtimebucket_validate(ContinuousAggTimeBucketInfo *tbinfo, List *groupClause,\n\t\t\t\t\t\t\t\t\tList *targetList, List *rtable, bool is_cagg_create);\nstatic bool cagg_query_supported(const Query *query, StringInfo hint, StringInfo detail);\nstatic Datum get_bucket_width_datum(ContinuousAggTimeBucketInfo bucket_info);\nstatic int64 get_bucket_width(ContinuousAggTimeBucketInfo bucket_info);\nstatic FuncExpr *build_conversion_call(Oid type, FuncExpr *boundary);\nstatic FuncExpr *build_boundary_call(int32 ht_id, Oid type);\nstatic Const *cagg_boundary_make_lower_bound(Oid type);\nstatic Node *build_union_query_quals(int32 ht_id, Oid partcoltype, Oid opno, int varno,\n\t\t\t\t\t\t\t\t\t AttrNumber attno);\nstatic RangeTblEntry *makeRangeTblEntry(Query *subquery, const char *aliasname);\nstatic bool time_bucket_info_has_fixed_width(const ContinuousAggBucketFunction *bf);\n\n#define INTERNAL_TO_DATE_FUNCTION \"to_date\"\n#define INTERNAL_TO_TSTZ_FUNCTION \"to_timestamp\"\n#define INTERNAL_TO_TS_FUNCTION \"to_timestamp_without_timezone\"\n#define BOUNDARY_FUNCTION \"cagg_watermark\"\n\nstatic Const *\ncheck_time_bucket_argument(Node *arg, char *position, bool process_checks)\n{\n\tif (IsA(arg, NamedArgExpr))\n\t\targ = (Node *) castNode(NamedArgExpr, arg)->arg;\n\n\tNode *expr = eval_const_expressions(NULL, arg);\n\n\tif (process_checks && !IsA(expr, Const))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"only immutable expressions allowed in time bucket function\"),\n\t\t\t\t errhint(\"Use an immutable expression as %s argument to the time bucket function.\",\n\t\t\t\t\t\t position)));\n\n\treturn castNode(Const, expr);\n}\n\n/*\n * Initialize caggtimebucket.\n */\nstatic void\ncaggtimebucketinfo_init(ContinuousAggTimeBucketInfo *src, int32 hypertable_id, Oid hypertable_oid,\n\t\t\t\t\t\tAttrNumber hypertable_partition_colno, Oid hypertable_partition_coltype,\n\t\t\t\t\t\tint64 hypertable_partition_col_interval, int32 parent_mat_hypertable_id)\n{\n\tsrc->htid = hypertable_id;\n\tsrc->parent_mat_hypertable_id = parent_mat_hypertable_id;\n\tsrc->htoid = hypertable_oid;\n\tsrc->htoidparent = InvalidOid;\n\tsrc->htpartcolno = hypertable_partition_colno;\n\tsrc->htpartcoltype = hypertable_partition_coltype;\n\tsrc->htpartcol_interval_len = hypertable_partition_col_interval;\n\n\t/* Initialize bucket function data structure */\n\tsrc->bf = palloc0(sizeof(ContinuousAggBucketFunction));\n\tsrc->bf->bucket_function = InvalidOid;\n\tsrc->bf->bucket_width_type = InvalidOid;\n\n\t/* Time based buckets */\n\tsrc->bf->bucket_time_width = NULL;\t\t\t\t/* not specified by default */\n\tsrc->bf->bucket_time_timezone = NULL;\t\t\t/* not specified by default */\n\tsrc->bf->bucket_time_offset = NULL;\t\t\t\t/* not specified by default */\n\tTIMESTAMP_NOBEGIN(src->bf->bucket_time_origin); /* origin is not specified by default */\n\n\t/* Integer based buckets */\n\tsrc->bf->bucket_integer_width = 0;\t/* invalid value */\n\tsrc->bf->bucket_integer_offset = 0; /* invalid value */\n}\n\n/*\n * Check if the supplied OID belongs to a valid bucket function\n * for continuous aggregates.\n */\nbool\nfunction_allowed_in_cagg_definition(Oid funcid)\n{\n\tFuncInfo *finfo = ts_func_cache_get_bucketing_func(funcid);\n\tif (finfo == NULL)\n\t\treturn false;\n\n\tif (finfo->allowed_in_cagg_definition)\n\t\treturn true;\n\n\treturn false;\n}\n\n/*\n * When a view is created (StoreViewQuery), 2 dummy rtable entries corresponding to \"old\" and\n * \"new\" are prepended to the rtable list. We remove these and adjust the varnos to recreate\n * the user or direct view query.\n */\nvoid\nRemoveRangeTableEntries(Query *query)\n{\n#if PG16_LT\n\tList *rtable = query->rtable;\n\tAssert(list_length(rtable) >= 3);\n\trtable = list_delete_first(rtable);\n\tquery->rtable = list_delete_first(rtable);\n\tOffsetVarNodes((Node *) query, -2, 0);\n\tAssert(list_length(query->rtable) >= 1);\n#endif\n}\n\n/*\n * Extract the final view from the UNION ALL query.\n *\n * q1 is the query on the materialization hypertable with the finalize call\n * q2 is the query on the raw hypertable which was supplied in the initial CREATE VIEW statement\n * returns q1 from:\n * SELECT * from (  SELECT * from q1 where <coale_qual>\n *                  UNION ALL\n *                  SELECT * from q2 where existing_qual and <coale_qual>\n * where coale_qual is: time < ----> (or >= )\n * COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark( <htid>)),\n * '-infinity'::timestamp with time zone)\n * The WHERE clause of the final view is removed.\n */\nQuery *\ndestroy_union_query(Query *q)\n{\n\tAssert(q->commandType == CMD_SELECT &&\n\t\t   ((SetOperationStmt *) q->setOperations)->op == SETOP_UNION &&\n\t\t   ((SetOperationStmt *) q->setOperations)->all == true);\n\n\t/* Get RTE of the left-hand side of UNION ALL. */\n\tRangeTblEntry *rte = linitial(q->rtable);\n\tAssert(rte->rtekind == RTE_SUBQUERY);\n\n\tQuery *query = copyObject(rte->subquery);\n\n\t/* Delete the WHERE clause from the final view. */\n\tquery->jointree->quals = NULL;\n\n\treturn query;\n}\n\n/*\n * Handle additional parameter of the timebucket function such as timezone, offset, or origin\n */\nstatic void\nprocess_additional_timebucket_parameter(ContinuousAggBucketFunction *bf, Const *arg,\n\t\t\t\t\t\t\t\t\t\tbool *custom_origin)\n{\n\tchar *tz_name;\n\tswitch (exprType((Node *) arg))\n\t{\n\t\t/* Timezone as text */\n\t\tcase TEXTOID:\n\t\t\ttz_name = TextDatumGetCString(arg->constvalue);\n\t\t\tif (!ts_is_valid_timezone_name(tz_name))\n\t\t\t{\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t\t errmsg(\"invalid timezone name \\\"%s\\\"\", tz_name)));\n\t\t\t}\n\n\t\t\tbf->bucket_time_timezone = tz_name;\n\t\t\tbreak;\n\t\tcase INTERVALOID:\n\t\t\t/* Bucket offset as interval */\n\t\t\tbf->bucket_time_offset = DatumGetIntervalP(arg->constvalue);\n\t\t\tbreak;\n\t\tcase DATEOID:\n\t\t\t/* Bucket origin as Date */\n\t\t\tif (!arg->constisnull)\n\t\t\t\tbf->bucket_time_origin =\n\t\t\t\t\tdate2timestamptz_opt_overflow(DatumGetDateADT(arg->constvalue), NULL);\n\t\t\t*custom_origin = true;\n\t\t\tbreak;\n\t\tcase TIMESTAMPOID:\n\t\t\t/* Bucket origin as Timestamp */\n\t\t\tbf->bucket_time_origin = DatumGetTimestamp(arg->constvalue);\n\t\t\t*custom_origin = true;\n\t\t\tbreak;\n\t\tcase TIMESTAMPTZOID:\n\t\t\t/* Bucket origin as TimestampTZ */\n\t\t\tbf->bucket_time_origin = DatumGetTimestampTz(arg->constvalue);\n\t\t\t*custom_origin = true;\n\t\t\tbreak;\n\t\tcase INT2OID:\n\t\t\t/* Bucket offset as smallint */\n\t\t\tbf->bucket_integer_offset = DatumGetInt16(arg->constvalue);\n\t\t\tbreak;\n\t\tcase INT4OID:\n\t\t\t/* Bucket offset as int */\n\t\t\tbf->bucket_integer_offset = DatumGetInt32(arg->constvalue);\n\t\t\tbreak;\n\t\tcase INT8OID:\n\t\t\t/* Bucket offset as bigint */\n\t\t\tbf->bucket_integer_offset = DatumGetInt64(arg->constvalue);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t\t errmsg(\"unable to handle time_bucket parameter of type: %s\",\n\t\t\t\t\t\t\tformat_type_be(exprType((Node *) arg)))));\n\t\t\tpg_unreachable();\n\t}\n}\n\n/*\n * Process the FuncExpr node to fill the bucket function data structure. The other\n * parameters are used when `process_check` is true that means we need to raise errors\n * when invalid parameters are passed to the time bucket function when creating a cagg.\n */\nstatic void\nprocess_timebucket_parameters(FuncExpr *fe, ContinuousAggBucketFunction *bf, bool process_checks,\n\t\t\t\t\t\t\t  bool is_cagg_create, AttrNumber htpartcolno)\n{\n\tNode *width_arg;\n\tNode *col_arg;\n\tbool custom_origin = false;\n\tTIMESTAMP_NOBEGIN(bf->bucket_time_origin);\n\tint nargs;\n\n\t/* Only column allowed : time_bucket('1day', <column> ) */\n\tcol_arg = lsecond(fe->args);\n\n\t/* Could be a named argument */\n\tif (IsA(col_arg, NamedArgExpr))\n\t\tcol_arg = (Node *) castNode(NamedArgExpr, col_arg)->arg;\n\n\tif (process_checks && htpartcolno != InvalidAttrNumber &&\n\t\t(!(IsA(col_arg, Var)) || castNode(Var, col_arg)->varattno != htpartcolno))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"time bucket function must reference the primary hypertable \"\n\t\t\t\t\t\t\"dimension column\")));\n\n\tnargs = list_length(fe->args);\n\tAssert(nargs >= 2 && nargs <= 5);\n\n\t/*\n\t * Process the third argument of the time bucket function. This could be `timezone`, `offset`,\n\t * or `origin`.\n\t *\n\t * Time bucket function variations with 3 and 5 arguments:\n\t *   - time_bucket(width SMALLINT, ts SMALLINT,    offset SMALLINT)\n\t *   - time_bucket(width INTEGER,  ts INTEGER,     offset INTEGER)\n\t *   - time_bucket(width BIGINT,   ts BIGINT,      offset BIGINT)\n\t *   - time_bucket(width INTERVAL, ts DATE,        offset INTERVAL)\n\t *   - time_bucket(width INTERVAL, ts DATE,        origin DATE)\n\t *   - time_bucket(width INTERVAL, ts TIMESTAMPTZ, offset INTERVAL)\n\t *   - time_bucket(width INTERVAL, ts TIMESTAMPTZ, origin TIMESTAMPTZ)\n\t *   - time_bucket(width INTERVAL, ts TIMESTAMPTZ, timezone TEXT, origin TIMESTAMPTZ,\n\t *                 offset INTERVAL)\n\t *   - time_bucket(width INTERVAL, ts TIMESTAMP,   offset INTERVAL)\n\t *   - time_bucket(width INTERVAL, ts TIMESTAMP,   origin TIMESTAMP)\n\t */\n\tif (nargs >= 3)\n\t{\n\t\tConst *arg = check_time_bucket_argument(lthird(fe->args), \"third\", process_checks);\n\t\tprocess_additional_timebucket_parameter(bf, arg, &custom_origin);\n\t}\n\n\t/*\n\t * Process the fourth and fifth arguments of the time bucket function. This could be `origin` or\n\t * `offset`.\n\t *\n\t * Time bucket function variation with 5 arguments:\n\t *   - time_bucket(width INTERVAL, ts TIMESTAMPTZ, timezone TEXT, origin TIMESTAMPTZ,\n\t *                 offset INTERVAL)\n\t */\n\tif (nargs >= 4)\n\t{\n\t\tConst *arg = check_time_bucket_argument(lfourth(fe->args), \"fourth\", process_checks);\n\t\tprocess_additional_timebucket_parameter(bf, arg, &custom_origin);\n\t}\n\n\tif (nargs == 5)\n\t{\n\t\tConst *arg = check_time_bucket_argument(lfifth(fe->args), \"fifth\", process_checks);\n\t\tprocess_additional_timebucket_parameter(bf, arg, &custom_origin);\n\t}\n\n\tif (process_checks && custom_origin && TIMESTAMP_NOT_FINITE(bf->bucket_time_origin))\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid origin value: infinity\")));\n\t}\n\n\t/*\n\t * We constify width expression here so any immutable expression will be allowed.\n\t * Otherwise it would make it harder to create caggs for hypertables with e.g. int8\n\t * partitioning column as int constants default to int4 and so expression would\n\t * have a cast and not be a Const.\n\t */\n\twidth_arg = linitial(fe->args);\n\n\tif (IsA(width_arg, NamedArgExpr))\n\t\twidth_arg = (Node *) castNode(NamedArgExpr, width_arg)->arg;\n\n\twidth_arg = eval_const_expressions(NULL, width_arg);\n\tif (IsA(width_arg, Const))\n\t{\n\t\tConst *width = castNode(Const, width_arg);\n\t\tbf->bucket_width_type = width->consttype;\n\n\t\tif (width->constisnull)\n\t\t{\n\t\t\tif (process_checks && is_cagg_create)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t\t errmsg(\"invalid bucket width for time bucket function\")));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (width->consttype == INTERVALOID)\n\t\t\t{\n\t\t\t\tbf->bucket_time_width = DatumGetIntervalP(width->constvalue);\n\t\t\t}\n\n\t\t\tif (!IS_TIME_BUCKET_INFO_TIME_BASED(bf))\n\t\t\t{\n\t\t\t\tbf->bucket_integer_width =\n\t\t\t\t\tts_interval_value_to_internal(width->constvalue, width->consttype);\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t{\n\t\tif (process_checks)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"only immutable expressions allowed in time bucket function\"),\n\t\t\t\t\t errhint(\"Use an immutable expression as first argument to the time bucket \"\n\t\t\t\t\t\t\t \"function.\")));\n\t}\n\n\tbf->bucket_function = fe->funcid;\n\tbf->bucket_time_based = ts_continuous_agg_bucket_on_interval(bf->bucket_function);\n\tbf->bucket_fixed_interval = time_bucket_info_has_fixed_width(bf);\n}\n\n/*\n * Check if the group-by clauses has exactly 1 time_bucket(.., <col>) where\n * <col> is the hypertable's partitioning column and other invariants. Then fill\n * the `bucket_width` and other fields of `tbinfo`.\n */\nstatic void\ncaggtimebucket_validate(ContinuousAggTimeBucketInfo *tbinfo, List *groupClause, List *targetList,\n\t\t\t\t\t\tList *rtable, bool is_cagg_create)\n{\n\tListCell *l;\n\tbool found = false;\n\n\t/* Make sure tbinfo was initialized. This assumption is used below. */\n\tAssert(tbinfo->bf->bucket_integer_width == 0);\n\tAssert(tbinfo->bf->bucket_time_timezone == NULL);\n\tAssert(TIMESTAMP_NOT_FINITE(tbinfo->bf->bucket_time_origin));\n\n\tList *group_exprs = get_sortgrouplist_exprs(groupClause, targetList);\n\n#if PG18_GE\n\t/* PG18 introduced RTEs for group clauses so\n\t * we can just use rtable to look for GROUP BY expressions.\n\t *\n\t * https://github.com/postgres/postgres/commit/247dea89\n\t */\n\tList *group_rte_exprs = NIL;\n\tforeach (l, rtable)\n\t{\n\t\tRangeTblEntry *rte = (RangeTblEntry *) lfirst(l);\n\n\t\tif (rte->rtekind == RTE_GROUP)\n\t\t\tgroup_rte_exprs = list_concat(group_rte_exprs, rte->groupexprs);\n\t}\n\n\tgroup_exprs = group_rte_exprs;\n#endif\n\n\tforeach (l, group_exprs)\n\t{\n\t\tExpr *expr = (Expr *) lfirst(l);\n\n\t\tif (IsA(expr, FuncExpr))\n\t\t{\n\t\t\tFuncExpr *fe = castNode(FuncExpr, expr);\n\n\t\t\t/* Filter any non bucketing functions */\n\t\t\tFuncInfo *finfo = ts_func_cache_get_bucketing_func(fe->funcid);\n\t\t\tif (finfo == NULL || !finfo->is_bucketing_func)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t/* Do we have a bucketing function that is not allowed in the CAgg definition?\n\t\t\t *\n\t\t\t * This is only validated upon creation. If an older TSDB version has allowed us to use\n\t\t\t * the function and it's now removed from the list of allowed functions, we should not\n\t\t\t * error out (e.g., materialized_only setting is changed on a CAgg that uses the\n\t\t\t * deprecated time_bucket_ng function). */\n\t\t\tif (!function_allowed_in_cagg_definition(fe->funcid))\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (found)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t errmsg(\"continuous aggregate view cannot contain\"\n\t\t\t\t\t\t\t\t\" multiple time bucket functions\")));\n\t\t\telse\n\t\t\t\tfound = true;\n\n\t\t\tprocess_timebucket_parameters(fe,\n\t\t\t\t\t\t\t\t\t\t  tbinfo->bf,\n\t\t\t\t\t\t\t\t\t\t  true,\n\t\t\t\t\t\t\t\t\t\t  is_cagg_create,\n\t\t\t\t\t\t\t\t\t\t  tbinfo->htpartcolno);\n\t\t}\n\t}\n\n\tif (tbinfo->bf->bucket_time_offset != NULL &&\n\t\tTIMESTAMP_NOT_FINITE(tbinfo->bf->bucket_time_origin) == false)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"using offset and origin in a time_bucket function at the same time is not \"\n\t\t\t\t\t\t\"supported\")));\n\t}\n\n\tif (!time_bucket_info_has_fixed_width(tbinfo->bf))\n\t{\n\t\t/* Variable-sized buckets can be used only with intervals. */\n\t\tAssert(tbinfo->bf->bucket_time_width != NULL);\n\t\tAssert(IS_TIME_BUCKET_INFO_TIME_BASED(tbinfo->bf));\n\n\t\tif ((tbinfo->bf->bucket_time_width->month != 0) &&\n\t\t\t((tbinfo->bf->bucket_time_width->day != 0) ||\n\t\t\t (tbinfo->bf->bucket_time_width->time != 0)))\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"invalid interval specified\"),\n\t\t\t\t\t errhint(\"Use either months or days and hours, but not months, days and hours \"\n\t\t\t\t\t\t\t \"together\")));\n\t\t}\n\t}\n\n\tif (!found)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"continuous aggregate view must include a valid time bucket function\")));\n}\n\n/*\n * Check query and extract error details and error hints.\n *\n * Returns:\n *   True if the query is supported, false otherwise with hints and errors\n *   added.\n */\nstatic bool\ncagg_query_supported(const Query *query, StringInfo hint, StringInfo detail)\n{\n\tif (!query->jointree->fromlist)\n\t{\n\t\tappendStringInfoString(hint, \"FROM clause missing in the query\");\n\t\treturn false;\n\t}\n\tif (query->commandType != CMD_SELECT)\n\t{\n\t\tappendStringInfoString(hint, \"Use a SELECT query in the continuous aggregate view.\");\n\t\treturn false;\n\t}\n\n\tif (query->hasWindowFuncs)\n\t{\n\t\tif (ts_guc_enable_cagg_window_functions)\n\t\t{\n\t\t\telog(WARNING,\n\t\t\t\t \"window function support is experimental and may result in unexpected results \"\n\t\t\t\t \"depending on the functions used.\");\n\t\t}\n\t\telse\n\t\t{\n\t\t\tappendStringInfoString(detail, \"Window function support not enabled.\");\n\t\t\tappendStringInfoString(hint,\n\t\t\t\t\t\t\t\t   \"Enable experimental window function support by setting \"\n\t\t\t\t\t\t\t\t   \"timescaledb.enable_cagg_window_functions.\");\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tif (query->hasDistinctOn || query->distinctClause)\n\t{\n\t\tappendStringInfoString(detail,\n\t\t\t\t\t\t\t   \"DISTINCT / DISTINCT ON queries are not supported by continuous \"\n\t\t\t\t\t\t\t   \"aggregates.\");\n\t\treturn false;\n\t}\n\n\tif (query->limitOffset || query->limitCount)\n\t{\n\t\tappendStringInfoString(detail,\n\t\t\t\t\t\t\t   \"LIMIT and LIMIT OFFSET are not supported in queries defining \"\n\t\t\t\t\t\t\t   \"continuous aggregates.\");\n\t\tappendStringInfoString(hint,\n\t\t\t\t\t\t\t   \"Use LIMIT and LIMIT OFFSET in SELECTS from the continuous \"\n\t\t\t\t\t\t\t   \"aggregate view instead.\");\n\t\treturn false;\n\t}\n\n\tif (query->hasRecursive || query->hasSubLinks || query->cteList)\n\t{\n\t\tappendStringInfoString(detail,\n\t\t\t\t\t\t\t   \"CTEs and subqueries are not supported by \"\n\t\t\t\t\t\t\t   \"continuous aggregates.\");\n\t\treturn false;\n\t}\n\n\tif (query->hasForUpdate || query->hasModifyingCTE)\n\t{\n\t\tappendStringInfoString(detail,\n\t\t\t\t\t\t\t   \"Data modification is not allowed in continuous aggregate view \"\n\t\t\t\t\t\t\t   \"definitions.\");\n\t\treturn false;\n\t}\n\n\tif (query->hasRowSecurity)\n\t{\n\t\tappendStringInfoString(detail,\n\t\t\t\t\t\t\t   \"Row level security is not supported by continuous aggregate \"\n\t\t\t\t\t\t\t   \"views.\");\n\t\treturn false;\n\t}\n\n\tif (query->groupingSets)\n\t{\n\t\tappendStringInfoString(detail,\n\t\t\t\t\t\t\t   \"GROUP BY GROUPING SETS, ROLLUP and CUBE are not supported by \"\n\t\t\t\t\t\t\t   \"continuous aggregates\");\n\t\tappendStringInfoString(hint,\n\t\t\t\t\t\t\t   \"Define multiple continuous aggregates with different grouping \"\n\t\t\t\t\t\t\t   \"levels.\");\n\t\treturn false;\n\t}\n\n\tif (query->setOperations)\n\t{\n\t\tappendStringInfoString(detail,\n\t\t\t\t\t\t\t   \"UNION, EXCEPT & INTERSECT are not supported by continuous \"\n\t\t\t\t\t\t\t   \"aggregates\");\n\t\treturn false;\n\t}\n\n\tif (!query->groupClause)\n\t{\n\t\t/*\n\t\t * Query can have aggregate without group by , so look\n\t\t * for groupClause.\n\t\t */\n\t\tappendStringInfoString(hint,\n\t\t\t\t\t\t\t   \"Include at least one aggregate function\"\n\t\t\t\t\t\t\t   \" and a GROUP BY clause with time bucket.\");\n\t\treturn false;\n\t}\n\n\treturn true; /* Query was OK and is supported. */\n}\n\nstatic Datum\nget_bucket_width_datum(ContinuousAggTimeBucketInfo bucket_info)\n{\n\tDatum width = UnassignedDatum;\n\n\tswitch (bucket_info.bf->bucket_width_type)\n\t{\n\t\tcase INT8OID:\n\t\tcase INT4OID:\n\t\tcase INT2OID:\n\t\t\twidth = ts_internal_to_interval_value(bucket_info.bf->bucket_integer_width,\n\t\t\t\t\t\t\t\t\t\t\t\t  bucket_info.bf->bucket_width_type);\n\t\t\tbreak;\n\t\tcase INTERVALOID:\n\t\t\twidth = IntervalPGetDatum(bucket_info.bf->bucket_time_width);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tAssert(false);\n\t}\n\n\treturn width;\n}\n\nstatic int64\nget_bucket_width(ContinuousAggTimeBucketInfo bucket_info)\n{\n\tint64 width = 0;\n\n\t/* Calculate the width. */\n\tswitch (bucket_info.bf->bucket_width_type)\n\t{\n\t\tcase INT8OID:\n\t\tcase INT4OID:\n\t\tcase INT2OID:\n\t\t\twidth = bucket_info.bf->bucket_integer_width;\n\t\t\tbreak;\n\t\tcase INTERVALOID:\n\t\t{\n\t\t\t/*\n\t\t\t * Original interval should not be changed, hence create a local copy\n\t\t\t * for this check.\n\t\t\t */\n\t\t\tInterval interval = *bucket_info.bf->bucket_time_width;\n\n\t\t\t/*\n\t\t\t * epoch will treat year as 365.25 days. This leads to the unexpected\n\t\t\t * result that year is not multiple of day or month, which is perceived\n\t\t\t * as a bug. For that reason, we treat all months as 30 days regardless of year\n\t\t\t */\n\t\t\tif (interval.month && !interval.day && !interval.time)\n\t\t\t{\n\t\t\t\tinterval.day = interval.month * DAYS_PER_MONTH;\n\t\t\t\tinterval.month = 0;\n\t\t\t}\n\t\t\t/* Convert Interval to int64 */\n\t\t\twidth = ts_interval_value_to_internal(IntervalPGetDatum(&interval), INTERVALOID);\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tAssert(false);\n\t}\n\n\treturn width;\n}\n\nContinuousAggTimeBucketInfo\ncagg_validate_query(const Query *query, const char *cagg_schema, const char *cagg_name,\n\t\t\t\t\tconst bool is_cagg_create)\n{\n\tContinuousAggTimeBucketInfo bucket_info = { 0 };\n\tContinuousAggTimeBucketInfo bucket_info_parent = { 0 };\n\tHypertable *ht = NULL, *ht_parent = NULL;\n\tRangeTblEntry *rte = NULL;\n\tStringInfoData hint;\n\tStringInfoData detail;\n\tbool is_hierarchical = false;\n\tQuery *prev_query = NULL;\n\tContinuousAgg *cagg_parent = NULL;\n\n\tinitStringInfo(&hint);\n\tinitStringInfo(&detail);\n\n\tif (!cagg_query_supported(query, &hint, &detail))\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"invalid continuous aggregate query\"),\n\t\t\t\t hint.len > 0 ? errhint(\"%s\", hint.data) : 0,\n\t\t\t\t detail.len > 0 ? errdetail(\"%s\", detail.data) : 0));\n\t}\n\n\tint num_hypertables = 0;\n\tListCell *lc;\n\tforeach (lc, query->rtable)\n\t{\n\t\tRangeTblEntry *inner_rte = lfirst_node(RangeTblEntry, lc);\n\n\t\tif (inner_rte->rtekind == RTE_RELATION)\n\t\t{\n\t\t\tbool is_hypertable = ts_is_hypertable(inner_rte->relid) ||\n\t\t\t\t\t\t\t\t ts_continuous_agg_find_by_relid(inner_rte->relid);\n\n\t\t\tif (is_hypertable)\n\t\t\t{\n\t\t\t\tnum_hypertables++;\n\t\t\t\tif (rte == NULL)\n\t\t\t\t\trte = copyObject(inner_rte);\n\t\t\t}\n\n\t\t\tif (is_hypertable && inner_rte->inh == false)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t errmsg(\"invalid continuous aggregate view\"),\n\t\t\t\t\t\t errdetail(\n\t\t\t\t\t\t\t \"FROM ONLY on hypertables is not allowed in continuous aggregate.\")));\n\t\t}\n\n\t\t/* Only inner joins are allowed. */\n\t\tif (inner_rte->jointype != JOIN_INNER && inner_rte->jointype != JOIN_LEFT)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"only INNER or LEFT joins are supported in continuous aggregates\")));\n\n\t\t/* Subquery only using LATERAL */\n\t\tif (inner_rte->subquery && !inner_rte->lateral)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"invalid continuous aggregate view\"),\n\t\t\t\t\t errdetail(\"Sub-queries are not supported in FROM clause.\")));\n\n\t\t/* TABLESAMPLE not allowed */\n\t\tif (inner_rte->tablesample)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"invalid continuous aggregate view\"),\n\t\t\t\t\t errdetail(\"TABLESAMPLE is not supported in continuous aggregate.\")));\n\t}\n\n\tif (num_hypertables > 1)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"invalid continuous aggregate view\"),\n\t\t\t\t errdetail(\"Only one hypertable is allowed in continuous aggregate view.\")));\n\n\tif (rte == NULL)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"invalid continuous aggregate view\"),\n\t\t\t\t errdetail(\"At least one hypertable should be used in the view definition.\")));\n\t}\n\n\tconst Dimension *part_dimension = NULL;\n\tint32 parent_mat_hypertable_id = INVALID_HYPERTABLE_ID;\n\tCache *hcache = ts_hypertable_cache_pin();\n\n\tif (rte->relkind == RELKIND_RELATION)\n\t{\n\t\tht = ts_hypertable_cache_get_entry(hcache, rte->relid, CACHE_FLAG_MISSING_OK);\n\n\t\tif (!ht)\n\t\t{\n\t\t\tts_cache_release(&hcache);\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_TS_HYPERTABLE_NOT_EXIST),\n\t\t\t\t\t errmsg(\"table \\\"%s\\\" is not a hypertable\", get_rel_name(rte->relid))));\n\t\t}\n\t}\n\telse\n\t{\n\t\tcagg_parent = ts_continuous_agg_find_by_relid(rte->relid);\n\n\t\tif (!cagg_parent)\n\t\t{\n\t\t\tts_cache_release(&hcache);\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"invalid continuous aggregate query\"),\n\t\t\t\t\t errhint(\"Continuous aggregate needs to query hypertable or another \"\n\t\t\t\t\t\t\t \"continuous aggregate.\")));\n\t\t}\n\n\t\tparent_mat_hypertable_id = cagg_parent->data.mat_hypertable_id;\n\t\tht = ts_hypertable_cache_get_entry_by_id(hcache, cagg_parent->data.mat_hypertable_id);\n\n\t\t/* If parent cagg is hierarchical then we should get the matht otherwise the rawht. */\n\t\tif (ContinuousAggIsHierarchical(cagg_parent))\n\t\t\tht_parent =\n\t\t\t\tts_hypertable_cache_get_entry_by_id(hcache, cagg_parent->data.mat_hypertable_id);\n\t\telse\n\t\t\tht_parent =\n\t\t\t\tts_hypertable_cache_get_entry_by_id(hcache, cagg_parent->data.raw_hypertable_id);\n\n\t\t/* Get the querydef for the source cagg. */\n\t\tis_hierarchical = true;\n\t\tprev_query = ts_continuous_agg_get_query(cagg_parent);\n\t}\n\n\t/*\n\t * Check if user can refresh continuous aggregate\n\t * We only check for SELECT on the hypertable here but there\n\t * could be other permissions needed depending on the query.\n\t * For WITH DATA this is not a problem since we try a refresh\n\t * immediately but for WITH NO DATA the refresh might still\n\t * fail due to other permissions being needed.\n\t */\n\tAclResult aclresult = pg_class_aclcheck(ht->main_table_relid, GetUserId(), ACL_SELECT);\n\tif (aclresult != ACLCHECK_OK)\n\t{\n\t\t/* User doesn't have permission */\n\t\taclcheck_error(aclresult,\n\t\t\t\t\t   get_relkind_objtype(get_rel_relkind(ht->main_table_relid)),\n\t\t\t\t\t   get_rel_name(ht->main_table_relid));\n\t}\n\n\tif (TS_HYPERTABLE_IS_INTERNAL_COMPRESSION_TABLE(ht))\n\t{\n\t\tts_cache_release(&hcache);\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"hypertable is an internal compressed hypertable\")));\n\t}\n\n\tif (rte->relkind == RELKIND_RELATION)\n\t{\n\t\tContinuousAggHypertableStatus status = ts_continuous_agg_hypertable_status(ht->fd.id);\n\n\t\t/* Prevent create a CAGG over an existing materialization hypertable. */\n\t\tif (status == HypertableIsMaterialization || status == HypertableIsMaterializationAndRaw)\n\t\t{\n\t\t\tconst ContinuousAgg *cagg =\n\t\t\t\tts_continuous_agg_find_by_mat_hypertable_id(ht->fd.id, false);\n\t\t\tAssert(cagg != NULL);\n\n\t\t\tts_cache_release(&hcache);\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"hypertable is a continuous aggregate materialization table\"),\n\t\t\t\t\t errdetail(\"Materialization hypertable \\\"%s.%s\\\".\",\n\t\t\t\t\t\t\t   NameStr(ht->fd.schema_name),\n\t\t\t\t\t\t\t   NameStr(ht->fd.table_name)),\n\t\t\t\t\t errhint(\"Do you want to use continuous aggregate \\\"%s.%s\\\" instead?\",\n\t\t\t\t\t\t\t NameStr(cagg->data.user_view_schema),\n\t\t\t\t\t\t\t NameStr(cagg->data.user_view_name))));\n\t\t}\n\t}\n\n\t/* Get primary partitioning column information. */\n\tpart_dimension = hyperspace_get_open_dimension(ht->space, 0);\n\n\t/*\n\t * NOTE: if we ever allow custom partitioning functions we'll need to\n\t *       change part_dimension->fd.column_type to partitioning_type\n\t *       below, along with any other fallout.\n\t */\n\tif (part_dimension == NULL || part_dimension->partitioning != NULL)\n\t{\n\t\tts_cache_release(&hcache);\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"custom partitioning functions not supported\"\n\t\t\t\t\t\t\" with continuous aggregates\")));\n\t}\n\n\tif (IS_INTEGER_TYPE(ts_dimension_get_partition_type(part_dimension)) &&\n\t\trte->relkind == RELKIND_RELATION)\n\t{\n\t\tconst char *funcschema = NameStr(part_dimension->fd.integer_now_func_schema);\n\t\tconst char *funcname = NameStr(part_dimension->fd.integer_now_func);\n\n\t\tif (strlen(funcschema) == 0 || strlen(funcname) == 0)\n\t\t{\n\t\t\tts_cache_release(&hcache);\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"custom time function required on hypertable \\\"%s\\\"\",\n\t\t\t\t\t\t\tget_rel_name(ht->main_table_relid)),\n\t\t\t\t\t errdetail(\"An integer-based hypertable requires a custom time function to \"\n\t\t\t\t\t\t\t   \"support continuous aggregates.\"),\n\t\t\t\t\t errhint(\"Set a custom time function on the hypertable.\")));\n\t\t}\n\t}\n\n\tcaggtimebucketinfo_init(&bucket_info,\n\t\t\t\t\t\t\tht->fd.id,\n\t\t\t\t\t\t\tht->main_table_relid,\n\t\t\t\t\t\t\tpart_dimension->column_attno,\n\t\t\t\t\t\t\tpart_dimension->fd.column_type,\n\t\t\t\t\t\t\tpart_dimension->fd.interval_length,\n\t\t\t\t\t\t\tparent_mat_hypertable_id);\n\n\tif (is_hierarchical)\n\t{\n\t\tconst Dimension *part_dimension_parent = hyperspace_get_open_dimension(ht_parent->space, 0);\n\n\t\tcaggtimebucketinfo_init(&bucket_info_parent,\n\t\t\t\t\t\t\t\tht_parent->fd.id,\n\t\t\t\t\t\t\t\tht_parent->main_table_relid,\n\t\t\t\t\t\t\t\tpart_dimension_parent->column_attno,\n\t\t\t\t\t\t\t\tpart_dimension_parent->fd.column_type,\n\t\t\t\t\t\t\t\tpart_dimension_parent->fd.interval_length,\n\t\t\t\t\t\t\t\tINVALID_HYPERTABLE_ID);\n\t}\n\n\tts_cache_release(&hcache);\n\n\t/*\n\t * We need a GROUP By clause with time_bucket on the partitioning\n\t * column of the hypertable\n\t */\n\tAssert(query->groupClause);\n\tcaggtimebucket_validate(&bucket_info,\n\t\t\t\t\t\t\tquery->groupClause,\n\t\t\t\t\t\t\tquery->targetList,\n\t\t\t\t\t\t\tquery->rtable,\n\t\t\t\t\t\t\tis_cagg_create);\n\n\t/* Check row security settings for the table. */\n\tif (ts_has_row_security(rte->relid))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot create continuous aggregate on hypertable with row security\")));\n\n\t/* At this point, we should have a valid bucket function. Otherwise, we have errored out before.\n\t */\n\tEnsure(OidIsValid(bucket_info.bf->bucket_function), \"unable to find valid bucket function\");\n\n\t/* Ignore time_bucket_ng in this check, since offset and origin were allowed in the past */\n\tFuncInfo *func_info = ts_func_cache_get_bucketing_func(bucket_info.bf->bucket_function);\n\tEnsure(func_info != NULL, \"bucket function is not found in function cache\");\n\n\t/* hierarchical cagg validations */\n\tif (is_hierarchical)\n\t{\n\t\tint64 bucket_width = 0, bucket_width_parent = 0;\n\t\tbool is_greater_or_equal_than_parent = true, is_multiple_of_parent = true;\n\n\t\tAssert(prev_query->groupClause);\n\t\tcaggtimebucket_validate(&bucket_info_parent,\n\t\t\t\t\t\t\t\tprev_query->groupClause,\n\t\t\t\t\t\t\t\tprev_query->targetList,\n\t\t\t\t\t\t\t\tprev_query->rtable,\n\t\t\t\t\t\t\t\tis_cagg_create);\n\n\t\t/* Cannot create cagg with fixed bucket on top of variable bucket. */\n\t\tif (time_bucket_info_has_fixed_width(bucket_info_parent.bf) == false &&\n\t\t\ttime_bucket_info_has_fixed_width(bucket_info.bf) == true)\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"cannot create continuous aggregate with fixed-width bucket on top of \"\n\t\t\t\t\t\t\t\"one using variable-width bucket\"),\n\t\t\t\t\t errdetail(\"Continuous aggregate with a fixed time bucket width (e.g. 61 days) \"\n\t\t\t\t\t\t\t   \"cannot be created on top of one using variable time bucket width \"\n\t\t\t\t\t\t\t   \"(e.g. 1 month).\\n\"\n\t\t\t\t\t\t\t   \"The variance can lead to the fixed width one not being a multiple \"\n\t\t\t\t\t\t\t   \"of the variable width one.\")));\n\t\t}\n\n\t\t/* Get bucket widths for validation. */\n\t\tbucket_width = get_bucket_width(bucket_info);\n\t\tbucket_width_parent = get_bucket_width(bucket_info_parent);\n\n\t\tAssert(bucket_width != 0);\n\t\tAssert(bucket_width_parent != 0);\n\n\t\t/* Check if the current bucket is greater or equal than the parent. */\n\t\tis_greater_or_equal_than_parent = (bucket_width >= bucket_width_parent);\n\n\t\t/* Check if buckets are multiple. */\n\t\tif (bucket_width_parent != 0)\n\t\t{\n\t\t\tif (bucket_width_parent > bucket_width && bucket_width != 0)\n\t\t\t\tis_multiple_of_parent = ((bucket_width_parent % bucket_width) == 0);\n\t\t\telse\n\t\t\t\tis_multiple_of_parent = ((bucket_width % bucket_width_parent) == 0);\n\t\t}\n\n\t\t/* Proceed with validation errors. */\n\t\tif (!is_greater_or_equal_than_parent || !is_multiple_of_parent)\n\t\t{\n\t\t\tchar *message = NULL;\n\n\t\t\t/* New bucket should be multiple of the parent. */\n\t\t\tif (!is_multiple_of_parent)\n\t\t\t\tmessage = \"multiple of\";\n\n\t\t\t/* New bucket should be greater than the parent. */\n\t\t\tif (!is_greater_or_equal_than_parent)\n\t\t\t\tmessage = \"greater or equal than\";\n\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"cannot create continuous aggregate with incompatible bucket width\"),\n\t\t\t\t\t errdetail(\"Time bucket width of \\\"%s.%s\\\" [%s] should be %s the time \"\n\t\t\t\t\t\t\t   \"bucket width of \\\"%s.%s\\\" [%s].\",\n\t\t\t\t\t\t\t   cagg_schema,\n\t\t\t\t\t\t\t   cagg_name,\n\t\t\t\t\t\t\t   ts_datum_to_string(get_bucket_width_datum(bucket_info),\n\t\t\t\t\t\t\t\t\t\t\t\t  bucket_info.bf->bucket_width_type),\n\t\t\t\t\t\t\t   message,\n\t\t\t\t\t\t\t   NameStr(cagg_parent->data.user_view_schema),\n\t\t\t\t\t\t\t   NameStr(cagg_parent->data.user_view_name),\n\t\t\t\t\t\t\t   ts_datum_to_string(get_bucket_width_datum(bucket_info_parent),\n\t\t\t\t\t\t\t\t\t\t\t\t  bucket_info_parent.bf->bucket_width_type))));\n\t\t}\n\n\t\t/* Test compatible time origin values */\n\t\tif (bucket_info.bf->bucket_time_origin != bucket_info_parent.bf->bucket_time_origin)\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\n\t\t\t\t\t\t \"cannot create continuous aggregate with different bucket origin values\"),\n\t\t\t\t\t errdetail(\"Time origin of \\\"%s.%s\\\" [%s] and \\\"%s.%s\\\" [%s] should be the \"\n\t\t\t\t\t\t\t   \"same.\",\n\t\t\t\t\t\t\t   cagg_schema,\n\t\t\t\t\t\t\t   cagg_name,\n\t\t\t\t\t\t\t   ts_datum_to_string(TimestampTzGetDatum(\n\t\t\t\t\t\t\t\t\t\t\t\t\t  bucket_info.bf->bucket_time_origin),\n\t\t\t\t\t\t\t\t\t\t\t\t  TIMESTAMPTZOID),\n\t\t\t\t\t\t\t   NameStr(cagg_parent->data.user_view_schema),\n\t\t\t\t\t\t\t   NameStr(cagg_parent->data.user_view_name),\n\t\t\t\t\t\t\t   ts_datum_to_string(TimestampTzGetDatum(\n\t\t\t\t\t\t\t\t\t\t\t\t\t  bucket_info_parent.bf->bucket_time_origin),\n\t\t\t\t\t\t\t\t\t\t\t\t  TIMESTAMPTZOID))));\n\t\t}\n\n\t\t/* Test compatible time offset values */\n\t\tif (bucket_info.bf->bucket_time_offset != NULL ||\n\t\t\tbucket_info_parent.bf->bucket_time_offset != NULL)\n\t\t{\n\t\t\tbool bucket_offset_isnull = bucket_info.bf->bucket_time_offset == NULL;\n\t\t\tbool bucket_offset_parent_isnull = bucket_info_parent.bf->bucket_time_offset == NULL;\n\n\t\t\tDatum offset_datum = IntervalPGetDatum(bucket_info.bf->bucket_time_offset);\n\t\t\tDatum offset_datum_parent =\n\t\t\t\tIntervalPGetDatum(bucket_info_parent.bf->bucket_time_offset);\n\n\t\t\tbool both_buckets_are_equal = false;\n\t\t\tbool both_buckets_have_offset = !bucket_offset_isnull && !bucket_offset_parent_isnull;\n\n\t\t\tif (both_buckets_have_offset)\n\t\t\t{\n\t\t\t\tboth_buckets_are_equal = DatumGetBool(\n\t\t\t\t\tDirectFunctionCall2(interval_eq, offset_datum, offset_datum_parent));\n\t\t\t}\n\n\t\t\tif (!both_buckets_are_equal)\n\t\t\t{\n\t\t\t\tchar *offset =\n\t\t\t\t\t!bucket_offset_isnull ?\n\t\t\t\t\t\tDatumGetCString(DirectFunctionCall1(interval_out, offset_datum)) :\n\t\t\t\t\t\t\"NULL\";\n\t\t\t\tchar *offset_parent =\n\t\t\t\t\t!bucket_offset_parent_isnull ?\n\t\t\t\t\t\tDatumGetCString(DirectFunctionCall1(interval_out, offset_datum_parent)) :\n\t\t\t\t\t\t\"NULL\";\n\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t errmsg(\"cannot create continuous aggregate with different bucket offset \"\n\t\t\t\t\t\t\t\t\"values\"),\n\t\t\t\t\t\t errdetail(\"Time origin of \\\"%s.%s\\\" [%s] and \\\"%s.%s\\\" [%s] should be the \"\n\t\t\t\t\t\t\t\t   \"same.\",\n\t\t\t\t\t\t\t\t   cagg_schema,\n\t\t\t\t\t\t\t\t   cagg_name,\n\t\t\t\t\t\t\t\t   offset,\n\t\t\t\t\t\t\t\t   NameStr(cagg_parent->data.user_view_schema),\n\t\t\t\t\t\t\t\t   NameStr(cagg_parent->data.user_view_name),\n\t\t\t\t\t\t\t\t   offset_parent)));\n\t\t\t}\n\t\t}\n\n\t\t/* Test compatible integer offset values */\n\t\tif (bucket_info.bf->bucket_integer_offset != bucket_info_parent.bf->bucket_integer_offset)\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\n\t\t\t\t\t\t \"cannot create continuous aggregate with different bucket offset values\"),\n\t\t\t\t\t errdetail(\"Integer offset of \\\"%s.%s\\\" [\" INT64_FORMAT\n\t\t\t\t\t\t\t   \"] and \\\"%s.%s\\\" [\" INT64_FORMAT \"] should be the same.\",\n\t\t\t\t\t\t\t   cagg_schema,\n\t\t\t\t\t\t\t   cagg_name,\n\t\t\t\t\t\t\t   bucket_info.bf->bucket_integer_offset,\n\t\t\t\t\t\t\t   NameStr(cagg_parent->data.user_view_schema),\n\t\t\t\t\t\t\t   NameStr(cagg_parent->data.user_view_name),\n\t\t\t\t\t\t\t   bucket_info_parent.bf->bucket_integer_offset)));\n\t\t}\n\t}\n\n\tif (is_hierarchical)\n\t\tbucket_info.htoidparent = cagg_parent->relid;\n\n\treturn bucket_info;\n}\n\n/*\n * Get oid of function to convert from our internal representation\n * to postgres representation.\n */\nOid\ncagg_get_boundary_converter_funcoid(Oid typoid)\n{\n\tchar *function_name;\n\tOid argtyp[] = { INT8OID };\n\n\tswitch (typoid)\n\t{\n\t\tcase DATEOID:\n\t\t\tfunction_name = INTERNAL_TO_DATE_FUNCTION;\n\t\t\tbreak;\n\t\tcase TIMESTAMPOID:\n\t\t\tfunction_name = INTERNAL_TO_TS_FUNCTION;\n\t\t\tbreak;\n\t\tcase TIMESTAMPTZOID:\n\t\t\tfunction_name = INTERNAL_TO_TSTZ_FUNCTION;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\t/*\n\t\t\t * This should never be reached and unsupported datatypes\n\t\t\t * should be caught at much earlier stages.\n\t\t\t */\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t\t errmsg(\"no converter function defined for datatype: %s\",\n\t\t\t\t\t\t\tformat_type_be(typoid))));\n\t\t\tpg_unreachable();\n\t}\n\n\tList *func_name = list_make2(makeString(FUNCTIONS_SCHEMA_NAME), makeString(function_name));\n\tOid converter_oid = LookupFuncName(func_name, lengthof(argtyp), argtyp, false);\n\n\tAssert(OidIsValid(converter_oid));\n\n\treturn converter_oid;\n}\n\nstatic FuncExpr *\nbuild_conversion_call(Oid type, FuncExpr *boundary)\n{\n\t/*\n\t * If the partitioning column type is not integer we need to convert\n\t * to proper representation.\n\t */\n\tswitch (type)\n\t{\n\t\tcase INT2OID:\n\t\tcase INT4OID:\n\t\t{\n\t\t\t/* Since the boundary function returns int8 we need to cast to proper type here. */\n\t\t\tOid cast_oid = ts_get_cast_func(INT8OID, type);\n\n\t\t\treturn makeFuncExpr(cast_oid,\n\t\t\t\t\t\t\t\ttype,\n\t\t\t\t\t\t\t\tlist_make1(boundary),\n\t\t\t\t\t\t\t\tInvalidOid,\n\t\t\t\t\t\t\t\tInvalidOid,\n\t\t\t\t\t\t\t\tCOERCE_IMPLICIT_CAST);\n\t\t}\n\t\tcase INT8OID:\n\t\t\t/* Nothing to do for int8. */\n\t\t\treturn boundary;\n\t\tcase DATEOID:\n\t\tcase TIMESTAMPOID:\n\t\tcase TIMESTAMPTZOID:\n\t\t{\n\t\t\t/*\n\t\t\t * date/timestamp/timestamptz need to be converted since\n\t\t\t * we store them differently from postgres format.\n\t\t\t */\n\t\t\tOid converter_oid = cagg_get_boundary_converter_funcoid(type);\n\t\t\treturn makeFuncExpr(converter_oid,\n\t\t\t\t\t\t\t\ttype,\n\t\t\t\t\t\t\t\tlist_make1(boundary),\n\t\t\t\t\t\t\t\tInvalidOid,\n\t\t\t\t\t\t\t\tInvalidOid,\n\t\t\t\t\t\t\t\tCOERCE_EXPLICIT_CALL);\n\t\t}\n\t\tcase UUIDOID:\n\t\t{\n\t\t\t/*\n\t\t\t * UUID needs two-step conversion: first convert the internal int8\n\t\t\t * representation to timestamptz, then convert timestamptz to a\n\t\t\t * boundary UUID via to_uuidv7_boundary().\n\t\t\t */\n\t\t\tOid tstz_converter_oid = cagg_get_boundary_converter_funcoid(TIMESTAMPTZOID);\n\t\t\tFuncExpr *tstz_boundary = makeFuncExpr(tstz_converter_oid,\n\t\t\t\t\t\t\t\t\t\t\t\t   TIMESTAMPTZOID,\n\t\t\t\t\t\t\t\t\t\t\t\t   list_make1(boundary),\n\t\t\t\t\t\t\t\t\t\t\t\t   InvalidOid,\n\t\t\t\t\t\t\t\t\t\t\t\t   InvalidOid,\n\t\t\t\t\t\t\t\t\t\t\t\t   COERCE_EXPLICIT_CALL);\n\n\t\t\tOid uuid_argtyp[] = { TIMESTAMPTZOID };\n\t\t\tList *uuid_func_name = list_make2(makeString(ts_extension_schema_name()),\n\t\t\t\t\t\t\t\t\t\t\t  makeString(\"to_uuidv7_boundary\"));\n\t\t\tOid uuid_converter_oid =\n\t\t\t\tLookupFuncName(uuid_func_name, lengthof(uuid_argtyp), uuid_argtyp, false);\n\n\t\t\treturn makeFuncExpr(uuid_converter_oid,\n\t\t\t\t\t\t\t\tUUIDOID,\n\t\t\t\t\t\t\t\tlist_make1(tstz_boundary),\n\t\t\t\t\t\t\t\tInvalidOid,\n\t\t\t\t\t\t\t\tInvalidOid,\n\t\t\t\t\t\t\t\tCOERCE_EXPLICIT_CALL);\n\t\t}\n\n\t\tdefault:\n\t\t\t/*\n\t\t\t * All valid types should be handled above, this should\n\t\t\t * never be reached and error handling at earlier stages\n\t\t\t * should catch this.\n\t\t\t */\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t\t errmsg(\"unsupported datatype for continuous aggregates: %s\",\n\t\t\t\t\t\t\tformat_type_be(type))));\n\t\t\tpg_unreachable();\n\t}\n}\n\n/*\n * Return the Oid of the cagg_watermark function\n */\nOid\nget_watermark_function_oid(void)\n{\n\tOid argtyp[] = { INT4OID };\n\n\tOid boundary_func_oid =\n\t\tLookupFuncName(list_make2(makeString(FUNCTIONS_SCHEMA_NAME), makeString(BOUNDARY_FUNCTION)),\n\t\t\t\t\t   lengthof(argtyp),\n\t\t\t\t\t   argtyp,\n\t\t\t\t\t   false);\n\n\treturn boundary_func_oid;\n}\n\n/*\n * Build function call that returns boundary for a hypertable\n * wrapped in type conversion calls when required.\n */\nstatic FuncExpr *\nbuild_boundary_call(int32 ht_id, Oid type)\n{\n\tFuncExpr *boundary;\n\tOid boundary_func_oid = get_watermark_function_oid();\n\tList *func_args =\n\t\tlist_make1(makeConst(INT4OID, -1, InvalidOid, 4, Int32GetDatum(ht_id), false, true));\n\n\tboundary = makeFuncExpr(boundary_func_oid,\n\t\t\t\t\t\t\tINT8OID,\n\t\t\t\t\t\t\tfunc_args,\n\t\t\t\t\t\t\tInvalidOid,\n\t\t\t\t\t\t\tInvalidOid,\n\t\t\t\t\t\t\tCOERCE_EXPLICIT_CALL);\n\n\treturn build_conversion_call(type, boundary);\n}\n\n/*\n * Create Const of proper type for lower bound of watermark when\n * watermark has not been set yet.\n */\nstatic Const *\ncagg_boundary_make_lower_bound(Oid type)\n{\n\tDatum value;\n\tint16 typlen;\n\tbool typbyval;\n\n\tif (type == UUIDOID)\n\t{\n\t\t/*\n\t\t * For UUID, create an all-zeros UUID as the lower bound. This is\n\t\t * smaller than any valid UUIDv7 and serves as the \"beginning of time\"\n\t\t * fallback when the watermark is NULL.\n\t\t */\n\t\tpg_uuid_t *uuid = (pg_uuid_t *) palloc0(sizeof(pg_uuid_t));\n\t\treturn makeConst(UUIDOID, -1, InvalidOid, UUID_LEN, UUIDPGetDatum(uuid), false, false);\n\t}\n\n\tget_typlenbyval(type, &typlen, &typbyval);\n\tvalue = ts_time_datum_get_nobegin_or_min(type);\n\n\treturn makeConst(type, -1, InvalidOid, typlen, value, false, typbyval);\n}\n\nstatic Node *\nbuild_union_query_quals(int32 ht_id, Oid partcoltype, Oid opno, int varno, AttrNumber attno)\n{\n\tVar *var = makeVar(varno, attno, partcoltype, -1, InvalidOid, InvalidOid);\n\tFuncExpr *boundary = build_boundary_call(ht_id, partcoltype);\n\n\tCoalesceExpr *coalesce = makeNode(CoalesceExpr);\n\tcoalesce->coalescetype = partcoltype;\n\tcoalesce->coalescecollid = InvalidOid;\n\tcoalesce->args = list_make2(boundary, cagg_boundary_make_lower_bound(partcoltype));\n\n\treturn (Node *) make_opclause(opno,\n\t\t\t\t\t\t\t\t  BOOLOID,\n\t\t\t\t\t\t\t\t  false,\n\t\t\t\t\t\t\t\t  (Expr *) var,\n\t\t\t\t\t\t\t\t  (Expr *) coalesce,\n\t\t\t\t\t\t\t\t  InvalidOid,\n\t\t\t\t\t\t\t\t  InvalidOid);\n}\n\nstatic RangeTblEntry *\nmakeRangeTblEntry(Query *query, const char *aliasname)\n{\n\tRangeTblEntry *rte = makeNode(RangeTblEntry);\n\tListCell *lc;\n\n\trte->rtekind = RTE_SUBQUERY;\n\trte->relid = InvalidOid;\n\trte->subquery = query;\n\trte->alias = makeAlias(aliasname, NIL);\n\trte->eref = copyObject(rte->alias);\n\n\tforeach (lc, query->targetList)\n\t{\n\t\tTargetEntry *tle = lfirst_node(TargetEntry, lc);\n\t\tif (!tle->resjunk)\n\t\t\trte->eref->colnames = lappend(rte->eref->colnames, makeString(pstrdup(tle->resname)));\n\t}\n\n\trte->lateral = false;\n\trte->inh = false; /* never true for subqueries */\n\trte->inFromCl = false;\n\n\treturn rte;\n}\n\n/*\n * Build union query combining the materialized data with data from the raw data hypertable.\n *\n * q1 is the query on the materialization hypertable with the finalize call\n * q2 is the query on the raw hypertable which was supplied in the initial CREATE VIEW statement\n * returns a query as\n * SELECT * from (  SELECT * from q1 where <coale_qual>\n *                  UNION ALL\n *                  SELECT * from q2 where existing_qual and <coale_qual>\n * where coale_qual is: time < ----> (or >= )\n *\n * COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(<htid>)),\n * '-infinity'::timestamp with time zone)\n *\n * See build_union_query_quals for COALESCE clauses.\n */\nQuery *\nbuild_union_query(ContinuousAggTimeBucketInfo *tbinfo, int matpartcolno, Query *q1, Query *q2,\n\t\t\t\t  int materialize_htid)\n{\n\tListCell *lc1, *lc2;\n\tList *col_types = NIL;\n\tList *col_typmods = NIL;\n\tList *col_collations = NIL;\n\tList *tlist = NIL;\n\tList *sortClause = NIL;\n\tint varno;\n\tNode *q2_quals = NULL;\n\n\tAssert(list_length(q1->targetList) <= list_length(q2->targetList));\n\n\tq1 = copyObject(q1);\n\tq2 = copyObject(q2);\n\n\tif (q1->sortClause)\n\t\tsortClause = copyObject(q1->sortClause);\n\n\t/*\n\t * For UUID-partitioned hypertables, the materialization table's partition\n\t * column is TIMESTAMPTZ (the output of time_bucket on UUID), while the raw\n\t * table's partition column is UUID. We need different types and operators\n\t * for q1 (materialized data) and q2 (raw data).\n\t */\n\tOid q1_partcoltype = tbinfo->htpartcoltype;\n\tOid q2_partcoltype = tbinfo->htpartcoltype;\n\n\tif (tbinfo->htpartcoltype == UUIDOID)\n\t\tq1_partcoltype = TIMESTAMPTZOID;\n\n\tTypeCacheEntry *tce_q1 = lookup_type_cache(q1_partcoltype, TYPECACHE_LT_OPR);\n\tTypeCacheEntry *tce_q2 = lookup_type_cache(q2_partcoltype, TYPECACHE_LT_OPR);\n\n\tvarno = list_length(q1->rtable);\n\tq1->jointree->quals = build_union_query_quals(materialize_htid,\n\t\t\t\t\t\t\t\t\t\t\t\t  q1_partcoltype,\n\t\t\t\t\t\t\t\t\t\t\t\t  tce_q1->lt_opr,\n\t\t\t\t\t\t\t\t\t\t\t\t  varno,\n\t\t\t\t\t\t\t\t\t\t\t\t  matpartcolno);\n\t/*\n\t * If there is join in CAgg definition then adjust varno\n\t * to get time column from the hypertable in the join.\n\t */\n\tvarno = list_length(q2->rtable);\n\n\tif (list_length(q2->rtable) > 1)\n\t{\n\t\tint nvarno = 1;\n\t\tforeach (lc2, q2->rtable)\n\t\t{\n\t\t\tRangeTblEntry *rte = lfirst_node(RangeTblEntry, lc2);\n\t\t\tif (rte->rtekind == RTE_RELATION)\n\t\t\t{\n\t\t\t\t/* look for hypertable or parent hypertable in RangeTableEntry list */\n\t\t\t\tif (rte->relid == tbinfo->htoid || rte->relid == tbinfo->htoidparent)\n\t\t\t\t{\n\t\t\t\t\tvarno = nvarno;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tnvarno++;\n\t\t}\n\t}\n\n\tq2_quals = build_union_query_quals(materialize_htid,\n\t\t\t\t\t\t\t\t\t   q2_partcoltype,\n\t\t\t\t\t\t\t\t\t   get_negator(tce_q2->lt_opr),\n\t\t\t\t\t\t\t\t\t   varno,\n\t\t\t\t\t\t\t\t\t   tbinfo->htpartcolno);\n\n\tq2->jointree->quals = make_and_qual(q2->jointree->quals, q2_quals);\n\n\tQuery *query = makeNode(Query);\n\tSetOperationStmt *setop = makeNode(SetOperationStmt);\n\tRangeTblEntry *rte_q1 = makeRangeTblEntry(q1, \"*SELECT* 1\");\n\tRangeTblEntry *rte_q2 = makeRangeTblEntry(q2, \"*SELECT* 2\");\n\tRangeTblRef *ref_q1 = makeNode(RangeTblRef);\n\tRangeTblRef *ref_q2 = makeNode(RangeTblRef);\n\n\tquery->commandType = CMD_SELECT;\n\tquery->rtable = list_make2(rte_q1, rte_q2);\n\tquery->setOperations = (Node *) setop;\n\n\tsetop->op = SETOP_UNION;\n\tsetop->all = true;\n\tref_q1->rtindex = 1;\n\tref_q2->rtindex = 2;\n\tsetop->larg = (Node *) ref_q1;\n\tsetop->rarg = (Node *) ref_q2;\n\n\tforboth (lc1, q1->targetList, lc2, q2->targetList)\n\t{\n\t\tTargetEntry *tle = lfirst_node(TargetEntry, lc1);\n\t\tTargetEntry *tle2 = lfirst_node(TargetEntry, lc2);\n\t\tTargetEntry *tle_union;\n\t\tVar *expr;\n\n\t\tif (!tle->resjunk)\n\t\t{\n\t\t\tcol_types = lappend_int(col_types, exprType((Node *) tle->expr));\n\t\t\tcol_typmods = lappend_int(col_typmods, exprTypmod((Node *) tle->expr));\n\t\t\tcol_collations = lappend_int(col_collations, exprCollation((Node *) tle->expr));\n\n\t\t\texpr = makeVarFromTargetEntry(1, tle);\n\t\t\t/*\n\t\t\t * We need to use resname from q2 because that is the query from the\n\t\t\t * initial CREATE VIEW statement so the VIEW can be updated in place.\n\t\t\t */\n\t\t\ttle_union = makeTargetEntry((Expr *) copyObject(expr),\n\t\t\t\t\t\t\t\t\t\tlist_length(tlist) + 1,\n\t\t\t\t\t\t\t\t\t\ttle2->resname,\n\t\t\t\t\t\t\t\t\t\tfalse);\n\t\t\ttle_union->resorigtbl = expr->varno;\n\t\t\ttle_union->resorigcol = expr->varattno;\n\t\t\ttle_union->ressortgroupref = tle->ressortgroupref;\n\n\t\t\ttlist = lappend(tlist, tle_union);\n\t\t}\n\t}\n\n\tquery->targetList = tlist;\n\tquery->jointree = makeFromExpr(NIL, NULL);\n\n\tif (sortClause)\n\t{\n\t\tquery->sortClause = sortClause;\n\t}\n\n\tsetop->colTypes = col_types;\n\tsetop->colTypmods = col_typmods;\n\tsetop->colCollations = col_collations;\n\n\treturn query;\n}\n\n/*\n * Returns true if the time bucket size is fixed\n */\nstatic bool\ntime_bucket_info_has_fixed_width(const ContinuousAggBucketFunction *bf)\n{\n\tif (!IS_TIME_BUCKET_INFO_TIME_BASED(bf))\n\t{\n\t\treturn true;\n\t}\n\telse\n\t{\n\t\t/* Historically, we treat all buckets with timezones as variable. Buckets with only days are\n\t\t * treated as fixed. */\n\t\treturn bf->bucket_time_width->month == 0 && bf->bucket_time_timezone == NULL;\n\t}\n}\n\nContinuousAgg *\ncagg_get_by_relid_or_fail(const Oid cagg_relid)\n{\n\tContinuousAgg *cagg;\n\n\tif (!OidIsValid(cagg_relid))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(\"invalid continuous aggregate\")));\n\n\tcagg = ts_continuous_agg_find_by_relid(cagg_relid);\n\n\tif (NULL == cagg)\n\t{\n\t\tconst char *relname = get_rel_name(cagg_relid);\n\n\t\tif (relname == NULL)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_TABLE),\n\t\t\t\t\t (errmsg(\"continuous aggregate does not exist\"))));\n\t\telse\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t (errmsg(\"relation \\\"%s\\\" is not a continuous aggregate\", relname))));\n\t}\n\n\treturn cagg;\n}\n\n/* Get time bucket function info based on the view definition */\nContinuousAggBucketFunction *\nts_cagg_get_bucket_function_info(Oid view_oid)\n{\n\tRelation view_rel = relation_open(view_oid, AccessShareLock);\n\tQuery *query = copyObject(get_view_query(view_rel));\n\trelation_close(view_rel, NoLock);\n\n\tAssert(query != NULL);\n\tAssert(query->commandType == CMD_SELECT);\n\n\tContinuousAggBucketFunction *bf = palloc0(sizeof(ContinuousAggBucketFunction));\n\n\tListCell *l;\n\tforeach (l, query->groupClause)\n\t{\n\t\tSortGroupClause *sgc = lfirst_node(SortGroupClause, l);\n\t\tTargetEntry *tle = get_sortgroupclause_tle(sgc, query->targetList);\n\n\t\tExpr *expr = tle->expr;\n#if PG18_GE\n\t\t/* PG18 introduced RTEs for group clauses so\n\t\t * we can use rtable to look up GROUP BY expressions.\n\t\t *\n\t\t * https://github.com/postgres/postgres/commit/247dea89\n\t\t */\n\t\tif (IsA(expr, Var))\n\t\t{\n\t\t\tVar *var = castNode(Var, tle->expr);\n\t\t\tAssert((int) var->varno <= list_length(query->rtable));\n\t\t\tRangeTblEntry *rte = list_nth(query->rtable, var->varno - 1);\n\t\t\tAssert(rte->rtekind == RTE_GROUP);\n\t\t\tAssert(var->varattno > 0);\n\t\t\tExpr *node = list_nth(rte->groupexprs, var->varattno - 1);\n\t\t\tif (IsA(node, FuncExpr))\n\t\t\t\texpr = node;\n\t\t}\n#endif\n\n\t\tif (IsA(expr, FuncExpr))\n\t\t{\n\t\t\tFuncExpr *fe = castNode(FuncExpr, expr);\n\n\t\t\t/* Filter any non bucketing functions */\n\t\t\tFuncInfo *finfo = ts_func_cache_get_bucketing_func(fe->funcid);\n\t\t\tif (finfo == NULL)\n\t\t\t\tcontinue;\n\n\t\t\tAssert(finfo->is_bucketing_func);\n\n\t\t\tprocess_timebucket_parameters(fe, bf, false, false, InvalidAttrNumber);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn bf;\n}\n\n/*\n * This function is responsible to return a list of column names used in\n * GROUP BY clause of the cagg query. It behaves a bit different depending\n * of the type of the Continuous Aggregate.\n *\n * Retrieve the \"direct view query\" and find the GROUP BY clause and\n * \"time_bucket\" clause. We use the \"direct view query\" because in the\n * \"user view query\" we removed the re-aggregation in the part that query\n * the materialization hypertable so we don't have a GROUP BY clause\n * anymore.\n *\n * Get the column name from the GROUP BY clause because all the column\n * names are the same in all underlying objects (user view, direct view,\n * partial view and materialization hypertable).\n */\nList *\ncagg_find_groupingcols(ContinuousAgg *agg, Hypertable *mat_ht)\n{\n\tList *retlist = NIL;\n\tListCell *lc;\n\tQuery *cagg_view_query = ts_continuous_agg_get_query(agg);\n\tOid mat_relid = mat_ht->main_table_relid;\n\tQuery *finalize_query;\n\n#if PG16_LT\n\t/* The view rule has dummy old and new range table entries as the 1st and 2nd entries */\n\tAssert(list_length(cagg_view_query->rtable) >= 2);\n#endif\n\n\tif (cagg_view_query->setOperations)\n\t{\n\t\t/*\n\t\t * This corresponds to the union view.\n\t\t *   PG16_LT the 3rd RTE entry has the SELECT 1 query from the union view.\n\t\t *   PG16_GE the 1st RTE entry has the SELECT 1 query from the union view\n\t\t */\n#if PG16_LT\n\t\tRangeTblEntry *finalize_query_rte = lthird(cagg_view_query->rtable);\n#else\n\t\tRangeTblEntry *finalize_query_rte = linitial(cagg_view_query->rtable);\n#endif\n\t\tif (finalize_query_rte->rtekind != RTE_SUBQUERY)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_TS_UNEXPECTED),\n\t\t\t\t\t errmsg(\"unexpected rte type for view %d\", finalize_query_rte->rtekind)));\n\n\t\tfinalize_query = finalize_query_rte->subquery;\n\t}\n\telse\n\t{\n\t\tfinalize_query = cagg_view_query;\n\t}\n\n\tforeach (lc, finalize_query->groupClause)\n\t{\n\t\tSortGroupClause *cagg_gc = (SortGroupClause *) lfirst(lc);\n\t\tTargetEntry *cagg_tle = get_sortgroupclause_tle(cagg_gc, finalize_query->targetList);\n\n\t\t/* \"resname\" is the same as \"mat column names\" */\n\t\tif (!cagg_tle->resjunk && cagg_tle->resname)\n\t\t\tretlist = lappend(retlist, get_attname(mat_relid, cagg_tle->resno, false));\n\t}\n\treturn retlist;\n}\n"
  },
  {
    "path": "tsl/src/continuous_aggs/common.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include <access/reloptions.h>\n#include <access/xact.h>\n#include <catalog/pg_aggregate.h>\n#include <catalog/pg_type.h>\n#include <catalog/toasting.h>\n#include <commands/tablecmds.h>\n#include <commands/tablespace.h>\n#include <miscadmin.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodeFuncs.h>\n#include <nodes/parsenodes.h>\n#include <nodes/pg_list.h>\n#include <optimizer/optimizer.h>\n#include <parser/parse_func.h>\n#include <parser/parse_oper.h>\n#include <parser/parsetree.h>\n#include <rewrite/rewriteHandler.h>\n#include <rewrite/rewriteManip.h>\n#include <utils/builtins.h>\n#include <utils/syscache.h>\n#include <utils/typcache.h>\n\n#include \"errors.h\"\n#include \"func_cache.h\"\n#include \"hypertable_cache.h\"\n#include \"timezones.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"ts_catalog/continuous_agg.h\"\n\n#define DEFAULT_MATPARTCOLUMN_NAME \"time_partition_col\"\n#define CAGG_INVALIDATION_THRESHOLD_NAME \"invalidation threshold watermark\"\n#define CAGG_INVALIDATION_WRONG_GREATEST_VALUE ((int64) -210866803200000001)\n\ntypedef struct FinalizeQueryInfo\n{\n\tList *final_seltlist;\t/* select target list for finalize query */\n\tNode *final_havingqual; /* having qual for finalize query */\n\tQuery *final_userquery; /* user query used to compute the finalize_query */\n} FinalizeQueryInfo;\n\ntypedef struct MaterializationHypertableColumnInfo\n{\n\tList *matcollist;\t\t /* column defns for materialization tbl*/\n\tList *partial_seltlist;\t /* tlist entries for populating the materialization table columns */\n\tList *partial_grouplist; /* group clauses used for populating the materialization table */\n\tList *mat_groupcolname_list; /* names of columns that are populated by the group-by clause\n\t\t\t\t\t\t\t\t\tcorrespond to the partial_grouplist.\n\t\t\t\t\t\t\t\t\ttime_bucket column is not included here: it is the\n\t\t\t\t\t\t\t\t\tmatpartcolname */\n\tint matpartcolno;\t\t\t /*index of partitioning column in matcollist */\n\tchar *matpartcolname;\t\t /*name of the partition column */\n} MaterializationHypertableColumnInfo;\n\ntypedef struct ContinuousAggTimeBucketInfo\n{\n\tint32 htid;\t\t\t\t\t\t/* hypertable id */\n\tint32 parent_mat_hypertable_id; /* parent materialization hypertable id */\n\tOid htoid;\t\t\t\t\t\t/* hypertable oid */\n\tOid htoidparent;\t\t\t\t/* parent hypertable oid in case of hierarchical */\n\tAttrNumber htpartcolno;\t\t\t/* primary partitioning column of raw hypertable */\n\t\t\t\t\t\t\t\t\t/* This should also be the column used by time_bucket */\n\tOid htpartcoltype;\t\t\t\t/* The collation type */\n\tint64 htpartcol_interval_len;\t/* interval length setting for primary partitioning column */\n\n\t/* General bucket information */\n\tContinuousAggBucketFunction *bf;\n} ContinuousAggTimeBucketInfo;\n\ntypedef enum ContinuousAggRefreshCallContext\n{\n\tCAGG_REFRESH_CREATION,\n\tCAGG_REFRESH_WINDOW,\n\tCAGG_REFRESH_POLICY,\n\tCAGG_REFRESH_POLICY_BATCHED\n} ContinuousAggRefreshCallContext;\n\ntypedef struct ContinuousAggRefreshContext\n{\n\tContinuousAggRefreshCallContext callctx;\n\tint32 processing_batch;\n\tint32 number_of_batches;\n} ContinuousAggRefreshContext;\n\n#define IS_TIME_BUCKET_INFO_TIME_BASED(bucket_function)                                            \\\n\t(bucket_function->bucket_width_type == INTERVALOID)\n\n#define CAGG_MAKEQUERY(selquery, srcquery)                                                         \\\n\tdo                                                                                             \\\n\t{                                                                                              \\\n\t\t(selquery) = makeNode(Query);                                                              \\\n\t\t(selquery)->commandType = CMD_SELECT;                                                      \\\n\t\t(selquery)->querySource = (srcquery)->querySource;                                         \\\n\t\t(selquery)->queryId = (srcquery)->queryId;                                                 \\\n\t\t(selquery)->canSetTag = (srcquery)->canSetTag;                                             \\\n\t\t(selquery)->utilityStmt = copyObject((srcquery)->utilityStmt);                             \\\n\t\t(selquery)->resultRelation = 0;                                                            \\\n\t\t(selquery)->hasAggs = true;                                                                \\\n\t\t(selquery)->hasRowSecurity = false;                                                        \\\n\t\t(selquery)->rtable = NULL;                                                                 \\\n\t} while (0);\n\nextern ContinuousAggTimeBucketInfo cagg_validate_query(const Query *query, const char *cagg_schema,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   const char *cagg_name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   const bool is_cagg_create);\nextern Query *destroy_union_query(Query *q);\nextern void RemoveRangeTableEntries(Query *query);\nextern Query *build_union_query(ContinuousAggTimeBucketInfo *tbinfo, int matpartcolno, Query *q1,\n\t\t\t\t\t\t\t\tQuery *q2, int materialize_htid);\nextern bool function_allowed_in_cagg_definition(Oid funcid);\nextern Oid get_watermark_function_oid(void);\nextern Oid cagg_get_boundary_converter_funcoid(Oid typoid);\n\nextern ContinuousAgg *cagg_get_by_relid_or_fail(const Oid cagg_relid);\nextern List *cagg_find_groupingcols(ContinuousAgg *agg, Hypertable *mat_ht);\n\nstatic inline int64\ncagg_get_time_min(const ContinuousAgg *cagg)\n{\n\tif (cagg->bucket_function->bucket_fixed_interval == false)\n\t{\n\t\t/*\n\t\t * To determine inscribed/circumscribed refresh window for variable-sized\n\t\t * buckets we should be able to calculate time_bucket(window.begin) and\n\t\t * time_bucket(window.end). This, however, is not possible in general case.\n\t\t * As an example, the minimum date is 4714-11-24 BC, which is before any\n\t\t * reasonable default `origin` value. Thus for variable-sized buckets\n\t\t * instead of minimum date we use -infinity since time_bucket(-infinity)\n\t\t * is well-defined as -infinity.\n\t\t *\n\t\t * For more details see:\n\t\t * - ts_compute_inscribed_bucketed_refresh_window_variable()\n\t\t * - ts_compute_circumscribed_bucketed_refresh_window_variable()\n\t\t */\n\t\treturn ts_time_get_nobegin_or_min(cagg->partition_type);\n\t}\n\n\t/* For fixed-sized buckets return min (start of time) */\n\treturn ts_time_get_min(cagg->partition_type);\n}\n\nContinuousAggBucketFunction *ts_cagg_get_bucket_function_info(Oid view_oid);\n"
  },
  {
    "path": "tsl/src/continuous_aggs/create.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * This file contains the code for processing continuous aggregate\n * DDL statements which are of the form:\n *\n * CREATE MATERIALIZED VIEW <name> WITH (ts_continuous = [option] )\n * AS  <select query>\n * The entry point for the code is\n * tsl_process_continuous_agg_viewstmt\n * The bulk of the code that creates the underlying tables/views etc. is in\n * cagg_create.\n */\n#include <postgres.h>\n\n#include <access/reloptions.h>\n#include <access/sysattr.h>\n#include <access/xact.h>\n#include <access/xlogutils.h>\n#include <catalog/index.h>\n#include <catalog/indexing.h>\n#include <catalog/pg_namespace.h>\n#include <catalog/pg_type.h>\n#include <catalog/toasting.h>\n#include <commands/defrem.h>\n#include <commands/tablecmds.h>\n#include <commands/tablespace.h>\n#include <commands/view.h>\n#include <executor/spi.h>\n#include <fmgr.h>\n#include <miscadmin.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodeFuncs.h>\n#include <nodes/nodes.h>\n#include <nodes/parsenodes.h>\n#include <nodes/pg_list.h>\n#include <optimizer/clauses.h>\n#include <optimizer/optimizer.h>\n#include <optimizer/prep.h>\n#include <optimizer/tlist.h>\n#include <parser/analyze.h>\n#include <parser/parse_func.h>\n#include <parser/parse_oper.h>\n#include <parser/parse_relation.h>\n#include <parser/parse_type.h>\n#include <parser/parsetree.h>\n#include <replication/logical.h>\n#include <replication/slot.h>\n#include <storage/lwlocknames.h>\n#include <utils/acl.h>\n#include <utils/builtins.h>\n#include <utils/catcache.h>\n#include <utils/elog.h>\n#include <utils/pg_lsn.h>\n#include <utils/rel.h>\n#include <utils/resowner.h>\n#include <utils/ruleutils.h>\n#include <utils/snapshot.h>\n#include <utils/syscache.h>\n#include <utils/typcache.h>\n\n#include \"common.h\"\n#include \"config.h\"\n#include \"create.h\"\n#include \"finalize.h\"\n#include \"invalidation_threshold.h\"\n\n#include \"debug_assert.h\"\n#include \"dimension.h\"\n#include \"extension_constants.h\"\n#include \"guc.h\"\n#include \"hypertable.h\"\n#include \"hypertable_cache.h\"\n#include \"invalidation.h\"\n#include \"refresh.h\"\n#include \"time_utils.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"ts_catalog/continuous_agg.h\"\n#include \"ts_catalog/continuous_aggs_watermark.h\"\n#include \"with_clause/create_materialized_view_with_clause.h\"\n\nstatic void create_cagg_catalog_entry(int32 matht_id, int32 rawht_id, const char *user_schema,\n\t\t\t\t\t\t\t\t\t  const char *user_view, const char *partial_schema,\n\t\t\t\t\t\t\t\t\t  const char *partial_view, bool materialized_only,\n\t\t\t\t\t\t\t\t\t  const char *direct_schema, const char *direct_view,\n\t\t\t\t\t\t\t\t\t  const int32 parent_mat_hypertable_id);\nstatic void create_bucket_function_catalog_entry(int32 matht_id, Oid bucket_function,\n\t\t\t\t\t\t\t\t\t\t\t\t const char *bucket_width, const char *origin,\n\t\t\t\t\t\t\t\t\t\t\t\t const char *offset, const char *timezone,\n\t\t\t\t\t\t\t\t\t\t\t\t const bool bucket_fixed_width);\nstatic void cagg_create_hypertable(int32 hypertable_id, Oid mat_tbloid, const char *matpartcolname,\n\t\t\t\t\t\t\t\t   int64 mat_tbltimecol_interval);\nstatic void mattablecolumninfo_add_mattable_index(MaterializationHypertableColumnInfo *matcolinfo,\n\t\t\t\t\t\t\t\t\t\t\t\t  Hypertable *ht);\nstatic ObjectAddress create_view_for_query(Query *selquery, RangeVar *viewrel);\nstatic void fixup_userview_query_tlist(Query *userquery, List *tlist_aliases);\nstatic void cagg_create(const CreateTableAsStmt *create_stmt, ViewStmt *stmt, Query *panquery,\n\t\t\t\t\t\tContinuousAggTimeBucketInfo *bucket_info,\n\t\t\t\t\t\tWithClauseResult *with_clause_options);\n\n#define MATPARTCOL_INTERVAL_FACTOR 10\n\nstatic void\nmakeMaterializedTableName(char *buf, const char *prefix, int hypertable_id)\n{\n\tint ret = snprintf(buf, NAMEDATALEN, prefix, hypertable_id);\n\tif (ret < 0 || ret > NAMEDATALEN)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR), errmsg(\"bad materialization internal name\")));\n\t}\n}\n\n/*\n * Create a entry for the materialization table in table CONTINUOUS_AGGS.\n */\nstatic void\ncreate_cagg_catalog_entry(int32 matht_id, int32 rawht_id, const char *user_schema,\n\t\t\t\t\t\t  const char *user_view, const char *partial_schema,\n\t\t\t\t\t\t  const char *partial_view, bool materialized_only,\n\t\t\t\t\t\t  const char *direct_schema, const char *direct_view,\n\t\t\t\t\t\t  const int32 parent_mat_hypertable_id)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tRelation rel;\n\tTupleDesc desc;\n\tNameData user_schnm, user_viewnm, partial_schnm, partial_viewnm, direct_schnm, direct_viewnm;\n\tDatum values[Natts_continuous_agg];\n\tbool nulls[Natts_continuous_agg] = { false };\n\tCatalogSecurityContext sec_ctx;\n\n\tnamestrcpy(&user_schnm, user_schema);\n\tnamestrcpy(&user_viewnm, user_view);\n\tnamestrcpy(&partial_schnm, partial_schema);\n\tnamestrcpy(&partial_viewnm, partial_view);\n\tnamestrcpy(&direct_schnm, direct_schema);\n\tnamestrcpy(&direct_viewnm, direct_view);\n\trel = table_open(catalog_get_table_id(catalog, CONTINUOUS_AGG), RowExclusiveLock);\n\tdesc = RelationGetDescr(rel);\n\n\tmemset(values, 0, sizeof(values));\n\tvalues[AttrNumberGetAttrOffset(Anum_continuous_agg_mat_hypertable_id)] = matht_id;\n\tvalues[AttrNumberGetAttrOffset(Anum_continuous_agg_raw_hypertable_id)] = rawht_id;\n\n\tif (parent_mat_hypertable_id == INVALID_HYPERTABLE_ID)\n\t\tnulls[AttrNumberGetAttrOffset(Anum_continuous_agg_parent_mat_hypertable_id)] = true;\n\telse\n\t{\n\t\tvalues[AttrNumberGetAttrOffset(Anum_continuous_agg_parent_mat_hypertable_id)] =\n\t\t\tparent_mat_hypertable_id;\n\t}\n\n\tvalues[AttrNumberGetAttrOffset(Anum_continuous_agg_user_view_schema)] =\n\t\tNameGetDatum(&user_schnm);\n\tvalues[AttrNumberGetAttrOffset(Anum_continuous_agg_user_view_name)] =\n\t\tNameGetDatum(&user_viewnm);\n\tvalues[AttrNumberGetAttrOffset(Anum_continuous_agg_partial_view_schema)] =\n\t\tNameGetDatum(&partial_schnm);\n\tvalues[AttrNumberGetAttrOffset(Anum_continuous_agg_partial_view_name)] =\n\t\tNameGetDatum(&partial_viewnm);\n\tvalues[AttrNumberGetAttrOffset(Anum_continuous_agg_direct_view_schema)] =\n\t\tNameGetDatum(&direct_schnm);\n\tvalues[AttrNumberGetAttrOffset(Anum_continuous_agg_direct_view_name)] =\n\t\tNameGetDatum(&direct_viewnm);\n\tvalues[AttrNumberGetAttrOffset(Anum_continuous_agg_materialize_only)] =\n\t\tBoolGetDatum(materialized_only);\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_insert_values(rel, desc, values, nulls);\n\tts_catalog_restore_user(&sec_ctx);\n\ttable_close(rel, RowExclusiveLock);\n}\n\n/*\n * Create a entry for the materialization table in table\n * CONTINUOUS_AGGS_BUCKET_FUNCTION.\n */\nstatic void\ncreate_bucket_function_catalog_entry(int32 matht_id, Oid bucket_function, const char *bucket_width,\n\t\t\t\t\t\t\t\t\t const char *bucket_origin, const char *bucket_offset,\n\t\t\t\t\t\t\t\t\t const char *bucket_timezone, const bool bucket_fixed_width)\n{\n\tCatalog *catalog = ts_catalog_get();\n\tRelation rel;\n\tTupleDesc desc;\n\tDatum values[Natts_continuous_aggs_bucket_function];\n\tbool nulls[Natts_continuous_aggs_bucket_function] = { false };\n\tCatalogSecurityContext sec_ctx;\n\n\tAssert(OidIsValid(bucket_function));\n\tAssert(bucket_width != NULL);\n\n\trel = table_open(catalog_get_table_id(catalog, CONTINUOUS_AGGS_BUCKET_FUNCTION),\n\t\t\t\t\t RowExclusiveLock);\n\tdesc = RelationGetDescr(rel);\n\n\tmemset(values, 0, sizeof(values));\n\n\t/* Hypertable ID */\n\tvalues[AttrNumberGetAttrOffset(Anum_continuous_aggs_bucket_function_mat_hypertable_id)] =\n\t\tmatht_id;\n\n\t/* Bucket function */\n\tvalues[AttrNumberGetAttrOffset(Anum_continuous_aggs_bucket_function_function)] =\n\t\tCStringGetTextDatum(format_procedure_qualified(bucket_function));\n\n\t/* Bucket width */\n\tvalues[AttrNumberGetAttrOffset(Anum_continuous_aggs_bucket_function_bucket_width)] =\n\t\tCStringGetTextDatum(bucket_width);\n\n\t/* Bucket origin */\n\tif (bucket_origin != NULL)\n\t{\n\t\tvalues[AttrNumberGetAttrOffset(Anum_continuous_aggs_bucket_function_bucket_origin)] =\n\t\t\tCStringGetTextDatum(bucket_origin);\n\t}\n\telse\n\t{\n\t\tnulls[AttrNumberGetAttrOffset(Anum_continuous_aggs_bucket_function_bucket_origin)] = true;\n\t}\n\n\t/* Bucket offset */\n\tif (bucket_offset != NULL)\n\t{\n\t\tvalues[AttrNumberGetAttrOffset(Anum_continuous_aggs_bucket_function_bucket_offset)] =\n\t\t\tCStringGetTextDatum(bucket_offset);\n\t}\n\telse\n\t{\n\t\tnulls[AttrNumberGetAttrOffset(Anum_continuous_aggs_bucket_function_bucket_offset)] = true;\n\t}\n\n\t/* Bucket timezone */\n\tif (bucket_timezone != NULL)\n\t{\n\t\tvalues[AttrNumberGetAttrOffset(Anum_continuous_aggs_bucket_function_bucket_timezone)] =\n\t\t\tCStringGetTextDatum(bucket_timezone);\n\t}\n\telse\n\t{\n\t\tnulls[AttrNumberGetAttrOffset(Anum_continuous_aggs_bucket_function_bucket_timezone)] = true;\n\t}\n\n\t/* Bucket fixed width */\n\tvalues[AttrNumberGetAttrOffset(Anum_continuous_aggs_bucket_function_bucket_fixed_width)] =\n\t\tBoolGetDatum(bucket_fixed_width);\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_insert_values(rel, desc, values, nulls);\n\tts_catalog_restore_user(&sec_ctx);\n\ttable_close(rel, RowExclusiveLock);\n}\n\n/*\n * Create hypertable for the table referred by mat_tbloid\n * matpartcolname - partition column for hypertable\n * timecol_interval - is the partitioning column's interval for hypertable partition\n */\nstatic void\ncagg_create_hypertable(int32 hypertable_id, Oid mat_tbloid, const char *matpartcolname,\n\t\t\t\t\t   int64 mat_tbltimecol_interval)\n{\n\tbool created;\n\tint flags = 0;\n\tNameData mat_tbltimecol;\n\tDimensionInfo *time_dim_info;\n\tChunkSizingInfo *chunk_sizing_info;\n\tnamestrcpy(&mat_tbltimecol, matpartcolname);\n\ttime_dim_info = ts_dimension_info_create_open(mat_tbloid,\n\t\t\t\t\t\t\t\t\t\t\t\t  &mat_tbltimecol,\n\t\t\t\t\t\t\t\t\t\t\t\t  Int64GetDatum(mat_tbltimecol_interval),\n\t\t\t\t\t\t\t\t\t\t\t\t  INT8OID,\n\t\t\t\t\t\t\t\t\t\t\t\t  InvalidOid);\n\t/*\n\t * Ideally would like to change/expand the API so setting the column name manually is\n\t * unnecessary, but not high priority.\n\t */\n\tchunk_sizing_info = ts_chunk_sizing_info_get_default_disabled(mat_tbloid);\n\tchunk_sizing_info->colname = matpartcolname;\n\tcreated = ts_hypertable_create_from_info(mat_tbloid,\n\t\t\t\t\t\t\t\t\t\t\t hypertable_id,\n\t\t\t\t\t\t\t\t\t\t\t flags,\n\t\t\t\t\t\t\t\t\t\t\t time_dim_info,\n\t\t\t\t\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t\t\t\t\t chunk_sizing_info);\n\tif (!created)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t errmsg(\"could not create materialization hypertable\")));\n}\n\n/*\n * Add additional indexes to materialization table for the columns derived from\n * the group-by column list of the partial select query.\n * If partial select query has:\n * GROUP BY timebucket_expr, <grpcol1, grpcol2, grpcol3 ...>\n * index on mattable is <grpcol1, timebucketcol>, <grpcol2, timebucketcol> ... and so on.\n * i.e. #indexes =(  #grp-cols - 1)\n */\nstatic void\nmattablecolumninfo_add_mattable_index(MaterializationHypertableColumnInfo *matcolinfo,\n\t\t\t\t\t\t\t\t\t  Hypertable *ht)\n{\n\tIndexStmt stmt = {\n\t\t.type = T_IndexStmt,\n\t\t.accessMethod = DEFAULT_INDEX_TYPE,\n\t\t.idxname = NULL,\n\t\t.relation = makeRangeVar(NameStr(ht->fd.schema_name), NameStr(ht->fd.table_name), 0),\n\t\t.tableSpace = get_tablespace_name(get_rel_tablespace(ht->main_table_relid)),\n\t};\n\tIndexElem timeelem = { .type = T_IndexElem,\n\t\t\t\t\t\t   .name = matcolinfo->matpartcolname,\n\t\t\t\t\t\t   .ordering = SORTBY_DESC };\n\tListCell *le = NULL;\n\tforeach (le, matcolinfo->mat_groupcolname_list)\n\t{\n\t\tNameData indxname;\n\t\tObjectAddress indxaddr;\n\t\tHeapTuple indxtuple;\n\t\tchar *grpcolname = (char *) lfirst(le);\n\t\tIndexElem grpelem = { .type = T_IndexElem, .name = grpcolname };\n\t\tstmt.indexParams = list_make2(&grpelem, &timeelem);\n\t\tindxaddr = DefineIndexCompat(ht->main_table_relid,\n\t\t\t\t\t\t\t\t\t &stmt,\n\t\t\t\t\t\t\t\t\t InvalidOid, /* indexRelationId */\n\t\t\t\t\t\t\t\t\t InvalidOid, /* parentIndexId */\n\t\t\t\t\t\t\t\t\t InvalidOid, /* parentConstraintId */\n\t\t\t\t\t\t\t\t\t -1,\t\t /* total_parts */\n\t\t\t\t\t\t\t\t\t false,\t\t /* is_alter_table */\n\t\t\t\t\t\t\t\t\t false,\t\t /* check_rights */\n\t\t\t\t\t\t\t\t\t false,\t\t /* check_not_in_use */\n\t\t\t\t\t\t\t\t\t false,\t\t /* skip_build */\n\t\t\t\t\t\t\t\t\t false);\t /* quiet */\n\t\tindxtuple = SearchSysCache1(RELOID, ObjectIdGetDatum(indxaddr.objectId));\n\n\t\tif (!HeapTupleIsValid(indxtuple))\n\t\t\telog(ERROR, \"cache lookup failed for index relid %u\", indxaddr.objectId);\n\t\tindxname = ((Form_pg_class) GETSTRUCT(indxtuple))->relname;\n\t\telog(DEBUG1,\n\t\t\t \"adding index %s ON %s.%s USING BTREE(%s, %s)\",\n\t\t\t NameStr(indxname),\n\t\t\t NameStr(ht->fd.schema_name),\n\t\t\t NameStr(ht->fd.table_name),\n\t\t\t grpcolname,\n\t\t\t matcolinfo->matpartcolname);\n\t\tReleaseSysCache(indxtuple);\n\t}\n}\n\n/*\n * Create the materialization hypertable root by faking up a\n * CREATE TABLE parsetree and passing it to DefineRelation.\n * Reuse the information from ViewStmt:\n *   Remove the options on the into clause that we will not honour\n *   Modify the relname to ts_internal_<name>\n *\n *  Parameters:\n *    mat_rel: relation information for the materialization table\n *    bucket_info: bucket information used for setting up the\n *                 hypertable partitioning (`chunk_interval_size`).\n *    tablespace_name: Name of the tablespace for the materialization table.\n *    mataddress: return the ObjectAddress\n *\n *  RETURNS: hypertable id of the materialization table\n */\nstatic int32\ncreate_materialization_table(MaterializationHypertableColumnInfo *matcolinfo, int32 hypertable_id,\n\t\t\t\t\t\t\t RangeVar *mat_rel, ContinuousAggTimeBucketInfo *bucket_info,\n\t\t\t\t\t\t\t bool create_addl_index, IntoClause *into, int64 matpartcol_interval,\n\t\t\t\t\t\t\t ObjectAddress *mataddress)\n{\n\tOid uid, saved_uid;\n\tint sec_ctx;\n\tchar *matpartcolname = matcolinfo->matpartcolname;\n\tCreateStmt *create;\n\tDatum toast_options;\n#if PG18_LT\n\tchar *validnsps[] = HEAP_RELOPT_NAMESPACES;\n#else\n\tconst char *const validnsps[] = HEAP_RELOPT_NAMESPACES;\n#endif\n\tint32 mat_htid;\n\tOid mat_relid;\n\tCache *hcache;\n\tHypertable *mat_ht = NULL, *orig_ht = NULL;\n\tOid owner = GetUserId();\n\n\tcreate = makeNode(CreateStmt);\n\tcreate->relation = mat_rel;\n\tcreate->tableElts = matcolinfo->matcollist;\n\tcreate->inhRelations = NIL;\n\tcreate->ofTypename = NULL;\n\tcreate->constraints = NIL;\n\tcreate->options = NULL;\n\tcreate->oncommit = ONCOMMIT_NOOP;\n\tcreate->tablespacename = into->tableSpaceName;\n\tcreate->accessMethod = into->accessMethod;\n\tcreate->if_not_exists = false;\n\n\t/*  Create the materialization table.  */\n\tSWITCH_TO_TS_USER(mat_rel->schemaname, uid, saved_uid, sec_ctx);\n\t*mataddress = DefineRelation(create, RELKIND_RELATION, owner, NULL, NULL);\n\tCommandCounterIncrement();\n\tmat_relid = mataddress->objectId;\n\n\t/* NewRelationCreateToastTable calls CommandCounterIncrement. */\n\ttoast_options =\n\t\ttransformRelOptions(UnassignedDatum, create->options, \"toast\", validnsps, true, false);\n\t(void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true);\n\tNewRelationCreateToastTable(mat_relid, toast_options);\n\tRESTORE_USER(uid, saved_uid, sec_ctx);\n\n\tcagg_create_hypertable(hypertable_id, mat_relid, matpartcolname, matpartcol_interval);\n\n\t/* Retrieve the hypertable id from the cache. */\n\tmat_ht = ts_hypertable_cache_get_cache_and_entry(mat_relid, CACHE_FLAG_NONE, &hcache);\n\tmat_htid = mat_ht->fd.id;\n\n\t/* Create additional index on the group-by columns for the materialization table. */\n\tif (create_addl_index)\n\t\tmattablecolumninfo_add_mattable_index(matcolinfo, mat_ht);\n\n\t/*\n\t * Initialize the invalidation log for the cagg. Initially, everything is\n\t * invalid. Add an infinite invalidation for the continuous\n\t * aggregate. This is the initial state of the aggregate before any\n\t * refreshes.\n\t */\n\torig_ht = ts_hypertable_cache_get_entry(hcache, bucket_info->htoid, CACHE_FLAG_NONE);\n\tcontinuous_agg_invalidate_mat_ht(orig_ht, mat_ht, TS_TIME_NOBEGIN, TS_TIME_NOEND);\n\tts_cache_release(&hcache);\n\treturn mat_htid;\n}\n\n/*\n * Use the userview query to create the partial query to populate\n * the materialization columns and remove HAVING clause and ORDER BY.\n */\nstatic Query *\nget_partial_select_query(MaterializationHypertableColumnInfo *mattblinfo, Query *userview_query)\n{\n\tQuery *partial_selquery = NULL;\n\n\tpartial_selquery = copyObject(userview_query);\n\t/* Partial view should always include the time dimension column */\n\tpartial_selquery->targetList = mattblinfo->partial_seltlist;\n\tpartial_selquery->groupClause = mattblinfo->partial_grouplist;\n\n\treturn partial_selquery;\n}\n\n/*\n * Create a view for the query using the SELECt stmt sqlquery\n * and view name from RangeVar viewrel.\n */\nstatic ObjectAddress\ncreate_view_for_query(Query *selquery, RangeVar *viewrel)\n{\n\tOid uid, saved_uid;\n\tint sec_ctx;\n\tObjectAddress address;\n\tCreateStmt *create;\n\tList *selcollist = NIL;\n\tOid owner = GetUserId();\n\tListCell *lc;\n\tforeach (lc, selquery->targetList)\n\t{\n\t\tTargetEntry *tle = (TargetEntry *) lfirst(lc);\n\t\tif (!tle->resjunk)\n\t\t{\n\t\t\tColumnDef *col = makeColumnDef(tle->resname,\n\t\t\t\t\t\t\t\t\t\t   exprType((Node *) tle->expr),\n\t\t\t\t\t\t\t\t\t\t   exprTypmod((Node *) tle->expr),\n\t\t\t\t\t\t\t\t\t\t   exprCollation((Node *) tle->expr));\n\t\t\tselcollist = lappend(selcollist, col);\n\t\t}\n\t}\n\n\tcreate = makeNode(CreateStmt);\n\tcreate->relation = viewrel;\n\tcreate->tableElts = selcollist;\n\tcreate->inhRelations = NIL;\n\tcreate->ofTypename = NULL;\n\tcreate->constraints = NIL;\n\tcreate->options = NULL;\n\tcreate->oncommit = ONCOMMIT_NOOP;\n\tcreate->tablespacename = NULL;\n\tcreate->if_not_exists = false;\n\n\t/*\n\t * Create the view. Viewname is in viewrel.\n\t */\n\tSWITCH_TO_TS_USER(viewrel->schemaname, uid, saved_uid, sec_ctx);\n\taddress = DefineRelation(create, RELKIND_VIEW, owner, NULL, NULL);\n\tCommandCounterIncrement();\n\tStoreViewQuery(address.objectId, selquery, false);\n\tCommandCounterIncrement();\n\tRESTORE_USER(uid, saved_uid, sec_ctx);\n\treturn address;\n}\n\n/*\n * Assign aliases to the targetlist in the query according to the\n * column_names provided in the CREATE VIEW statement.\n */\nstatic void\nfixup_userview_query_tlist(Query *userquery, List *tlist_aliases)\n{\n\tif (tlist_aliases != NIL)\n\t{\n\t\tListCell *lc;\n\t\tListCell *alist_item = list_head(tlist_aliases);\n\t\tforeach (lc, userquery->targetList)\n\t\t{\n\t\t\tTargetEntry *tle = (TargetEntry *) lfirst(lc);\n\n\t\t\t/* Junk columns don't get aliases. */\n\t\t\tif (tle->resjunk)\n\t\t\t\tcontinue;\n\t\t\ttle->resname = pstrdup(strVal(lfirst(alist_item)));\n\t\t\talist_item = lnext(tlist_aliases, alist_item);\n\t\t\tif (alist_item == NULL)\n\t\t\t\tbreak; /* done assigning aliases */\n\t\t}\n\n\t\tif (alist_item != NULL)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_SYNTAX_ERROR), errmsg(\"too many column names specified\")));\n\t}\n}\n\n/*\n * Modifies the passed in ViewStmt to do the following\n * a) Create a hypertable for the continuous agg materialization.\n * b) create a view that references the underlying\n * materialization table instead of the original table used in\n * the CREATE VIEW stmt.\n * Example:\n * CREATE VIEW mcagg ...\n * AS  select a, min(b)+max(d) from foo group by a,timebucket(a);\n *\n * Step 1. create a materialiation table which stores the partials for the\n * aggregates and the grouping columns + internal columns.\n * So we have a table like _materialization_hypertable\n * with columns:\n *( a, col1, col2, col3, internal-columns)\n * where col1 =  partialize(min(b)), col2= partialize(max(d)),\n * col3= timebucket(a))\n *\n * Step 2: Create a view with modified select query\n * CREATE VIEW mcagg\n * as\n * select a, finalize( col1) + finalize(col2))\n * from _materialization_hypertable\n * group by a, col3\n *\n * Step 3: Create a view to populate the materialization table\n * create view ts_internal_mcagg_view\n * as\n * select a, partialize(min(b)), partialize(max(d)), timebucket(a), <internal-columns>\n * from foo\n * group by <internal-columns> , a , timebucket(a);\n *\n * Notes: ViewStmt->query is the raw parse tree\n * panquery is the output of running parse_anlayze( ViewStmt->query)\n\n * Since 1.7, we support real time aggregation.\n * If real time aggregation is off i.e. materialized only, the mcagg view is as described in Step 2.\n * If it is turned on\n * we build a union query that selects from the internal mat view and the raw hypertable\n *     (see build_union_query for details)\n * CREATE VIEW mcagg\n * as\n * SELECT * from\n *        ( SELECT a, finalize(col1) + finalize(col2) from ts_internal_mcagg_view\n *                 ---> query from Step 2 with additional where clause\n *          WHERE timecol < materialization threshold\n *          group by <internal-columns> , a , timebucket(a);\n *          UNION ALL\n *          SELECT a, min(b)+max(d) from foo ---> original view stmt\n *                              ----> with additional where clause\n *          WHERE timecol >= materialization threshold\n *          GROUP BY a, time_bucket(a)\n *        )\n */\nstatic void\ncagg_create(const CreateTableAsStmt *create_stmt, ViewStmt *stmt, Query *panquery,\n\t\t\tContinuousAggTimeBucketInfo *bucket_info, WithClauseResult *with_clause_options)\n{\n\tObjectAddress mataddress;\n\tchar relnamebuf[NAMEDATALEN];\n\tFinalizeQueryInfo finalqinfo;\n\tCatalogSecurityContext sec_ctx;\n\tbool is_create_mattbl_index;\n\n\tQuery *final_selquery;\n\tQuery *partial_selquery;\t/* query to populate the mattable*/\n\tQuery *orig_userview_query; /* copy of the original user query for dummy view */\n\tOid nspid;\n\tRangeVar *part_rel = NULL, *mat_rel = NULL, *dum_rel = NULL;\n\tint32 materialize_hypertable_id;\n\tbool materialized_only =\n\t\tDatumGetBool(with_clause_options[CreateMaterializedViewFlagMaterializedOnly].parsed);\n\n\tint64 matpartcol_interval = 0;\n\tif (!with_clause_options[CreateMaterializedViewFlagChunkTimeInterval].is_default)\n\t{\n\t\tmatpartcol_interval = interval_to_usec(DatumGetIntervalP(\n\t\t\twith_clause_options[CreateMaterializedViewFlagChunkTimeInterval].parsed));\n\t}\n\telse\n\t{\n\t\tmatpartcol_interval = bucket_info->htpartcol_interval_len;\n\n\t\t/* Apply the factor just for non-Hierachical CAggs */\n\t\tif (bucket_info->parent_mat_hypertable_id == INVALID_HYPERTABLE_ID)\n\t\t\tmatpartcol_interval *= MATPARTCOL_INTERVAL_FACTOR;\n\t}\n\n\t/*\n\t * Assign the column_name aliases in CREATE VIEW to the query.\n\t * No other modifications to panquery.\n\t */\n\tfixup_userview_query_tlist(panquery, stmt->aliases);\n\tMaterializationHypertableColumnInfo mattblinfo = { .partial_grouplist =\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   copyObject(panquery->groupClause) };\n\tfinalizequery_init(&finalqinfo, panquery, &mattblinfo);\n\n\t/*\n\t * Invalidate all options on the stmt before using it\n\t * The options are valid only for internal use (ts_continuous).\n\t */\n\tstmt->options = NULL;\n\n\t/*\n\t * Step 1: create the materialization table.\n\t */\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tmaterialize_hypertable_id = ts_catalog_table_next_seq_id(ts_catalog_get(), HYPERTABLE);\n\tts_catalog_restore_user(&sec_ctx);\n\tmakeMaterializedTableName(relnamebuf, \"_materialized_hypertable_%d\", materialize_hypertable_id);\n\tmat_rel = makeRangeVar(pstrdup(INTERNAL_SCHEMA_NAME), pstrdup(relnamebuf), -1);\n\tis_create_mattbl_index =\n\t\tDatumGetBool(with_clause_options[CreateMaterializedViewFlagCreateGroupIndexes].parsed);\n\tcreate_materialization_table(&mattblinfo,\n\t\t\t\t\t\t\t\t materialize_hypertable_id,\n\t\t\t\t\t\t\t\t mat_rel,\n\t\t\t\t\t\t\t\t bucket_info,\n\t\t\t\t\t\t\t\t is_create_mattbl_index,\n\t\t\t\t\t\t\t\t create_stmt->into,\n\t\t\t\t\t\t\t\t matpartcol_interval,\n\t\t\t\t\t\t\t\t &mataddress);\n\n\t/*\n\t * Step 2: Create view with select finalize from materialization table.\n\t */\n\tfinal_selquery = finalizequery_get_select_query(&finalqinfo,\n\t\t\t\t\t\t\t\t\t\t\t\t\tmattblinfo.matcollist,\n\t\t\t\t\t\t\t\t\t\t\t\t\t&mataddress,\n\t\t\t\t\t\t\t\t\t\t\t\t\tmat_rel->relname);\n\n\tif (!materialized_only)\n\t\tfinal_selquery = build_union_query(bucket_info,\n\t\t\t\t\t\t\t\t\t\t   mattblinfo.matpartcolno,\n\t\t\t\t\t\t\t\t\t\t   final_selquery,\n\t\t\t\t\t\t\t\t\t\t   panquery,\n\t\t\t\t\t\t\t\t\t\t   materialize_hypertable_id);\n\n\t/* Copy view acl to materialization hypertable. */\n\tObjectAddress view_address = create_view_for_query(final_selquery, stmt->view);\n\tts_copy_relation_acl(view_address.objectId, mataddress.objectId, GetUserId());\n\n\t/*\n\t * Step 3: create the internal view with select partialize(..).\n\t */\n\tpartial_selquery = get_partial_select_query(&mattblinfo, panquery);\n\n\tmakeMaterializedTableName(relnamebuf, \"_partial_view_%d\", materialize_hypertable_id);\n\tpart_rel = makeRangeVar(pstrdup(INTERNAL_SCHEMA_NAME), pstrdup(relnamebuf), -1);\n\tcreate_view_for_query(partial_selquery, part_rel);\n\n\t/*\n\t * Additional miscellaneous steps.\n\t */\n\n\t/*\n\t * Create a dummy view to store the user supplied view query.\n\t * This is to get PG to display the view correctly without\n\t * having to replicate the PG source code for make_viewdef.\n\t */\n\torig_userview_query = copyObject(panquery);\n\tmakeMaterializedTableName(relnamebuf, \"_direct_view_%d\", materialize_hypertable_id);\n\tdum_rel = makeRangeVar(pstrdup(INTERNAL_SCHEMA_NAME), pstrdup(relnamebuf), -1);\n\tcreate_view_for_query(orig_userview_query, dum_rel);\n\n\t/* Step 4: Add catalog table entry for the objects we just created. */\n\tnspid = RangeVarGetCreationNamespace(stmt->view);\n\n\tcreate_cagg_catalog_entry(materialize_hypertable_id,\n\t\t\t\t\t\t\t  bucket_info->htid,\n\t\t\t\t\t\t\t  get_namespace_name(nspid), /*schema name for user view */\n\t\t\t\t\t\t\t  stmt->view->relname,\n\t\t\t\t\t\t\t  part_rel->schemaname,\n\t\t\t\t\t\t\t  part_rel->relname,\n\t\t\t\t\t\t\t  materialized_only,\n\t\t\t\t\t\t\t  dum_rel->schemaname,\n\t\t\t\t\t\t\t  dum_rel->relname,\n\t\t\t\t\t\t\t  bucket_info->parent_mat_hypertable_id);\n\n\tchar *bucket_origin = NULL;\n\tchar *bucket_offset = NULL;\n\tchar *bucket_width = NULL;\n\n\tif (IS_TIME_BUCKET_INFO_TIME_BASED(bucket_info->bf))\n\t{\n\t\t/* Bucketing on time */\n\t\tAssert(bucket_info->bf->bucket_time_width != NULL);\n\t\tbucket_width = DatumGetCString(\n\t\t\tDirectFunctionCall1(interval_out,\n\t\t\t\t\t\t\t\tIntervalPGetDatum(bucket_info->bf->bucket_time_width)));\n\n\t\tif (!TIMESTAMP_NOT_FINITE(bucket_info->bf->bucket_time_origin))\n\t\t{\n\t\t\tbucket_origin = DatumGetCString(\n\t\t\t\tDirectFunctionCall1(timestamptz_out,\n\t\t\t\t\t\t\t\t\tTimestampTzGetDatum(bucket_info->bf->bucket_time_origin)));\n\t\t}\n\n\t\tif (bucket_info->bf->bucket_time_offset != NULL)\n\t\t{\n\t\t\tbucket_offset = DatumGetCString(\n\t\t\t\tDirectFunctionCall1(interval_out,\n\t\t\t\t\t\t\t\t\tIntervalPGetDatum(bucket_info->bf->bucket_time_offset)));\n\t\t}\n\t}\n\telse\n\t{\n\t\t/* Bucketing on integers */\n\t\tbucket_width = palloc0(MAXINT8LEN + 1);\n\t\tpg_lltoa(bucket_info->bf->bucket_integer_width, bucket_width);\n\n\t\t/* Integer buckets with origin are not supported, so noting to do. */\n\t\tAssert(bucket_origin == NULL);\n\n\t\tif (bucket_info->bf->bucket_integer_offset != 0)\n\t\t{\n\t\t\tbucket_offset = palloc0(MAXINT8LEN + 1);\n\t\t\tpg_lltoa(bucket_info->bf->bucket_integer_offset, bucket_offset);\n\t\t}\n\t}\n\n\tcreate_bucket_function_catalog_entry(materialize_hypertable_id,\n\t\t\t\t\t\t\t\t\t\t bucket_info->bf->bucket_function,\n\t\t\t\t\t\t\t\t\t\t bucket_width,\n\t\t\t\t\t\t\t\t\t\t bucket_origin,\n\t\t\t\t\t\t\t\t\t\t bucket_offset,\n\t\t\t\t\t\t\t\t\t\t bucket_info->bf->bucket_time_timezone,\n\t\t\t\t\t\t\t\t\t\t bucket_info->bf->bucket_fixed_interval);\n}\n\nDDLResult\ntsl_process_continuous_agg_viewstmt(Node *node, const char *query_string, void *pstmt,\n\t\t\t\t\t\t\t\t\tWithClauseResult *with_clause_options)\n{\n\tconst CreateTableAsStmt *stmt = castNode(CreateTableAsStmt, node);\n\tContinuousAggTimeBucketInfo timebucket_exprinfo;\n\tOid nspid;\n\tViewStmt viewstmt = {\n\t\t.type = T_ViewStmt,\n\t\t.view = stmt->into->rel,\n\t\t.query = (Node *) stmt->into->viewQuery,\n\t\t.options = stmt->into->options,\n\t\t.aliases = stmt->into->colNames,\n\t};\n\tContinuousAgg *cagg;\n\tHypertable *mat_ht;\n\tOid relid;\n\tchar *schema_name;\n\n\tts_feature_flag_check(FEATURE_CAGG);\n\n\tnspid = RangeVarGetCreationNamespace(stmt->into->rel);\n\trelid = get_relname_relid(stmt->into->rel->relname, nspid);\n\n\tif (OidIsValid(relid))\n\t{\n\t\tif (stmt->if_not_exists)\n\t\t{\n\t\t\tereport(NOTICE,\n\t\t\t\t\t(errcode(ERRCODE_DUPLICATE_TABLE),\n\t\t\t\t\t errmsg(\"continuous aggregate \\\"%s\\\" already exists, skipping\",\n\t\t\t\t\t\t\tstmt->into->rel->relname)));\n\t\t\treturn DDL_DONE;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_DUPLICATE_TABLE),\n\t\t\t\t\t errmsg(\"continuous aggregate \\\"%s\\\" already exists\", stmt->into->rel->relname),\n\t\t\t\t\t errhint(\"Drop or rename the existing continuous aggregate first or use \"\n\t\t\t\t\t\t\t \"another name.\")));\n\t\t}\n\t}\n\n\tif (!with_clause_options[CreateMaterializedViewFlagColumnstore].is_default)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot enable compression while creating a continuous aggregate\"),\n\t\t\t\t errhint(\"Use ALTER MATERIALIZED VIEW to enable compression.\")));\n\t}\n\n\tschema_name = get_namespace_name(nspid);\n\ttimebucket_exprinfo = cagg_validate_query((Query *) stmt->into->viewQuery,\n\t\t\t\t\t\t\t\t\t\t\t  schema_name,\n\t\t\t\t\t\t\t\t\t\t\t  stmt->into->rel->relname,\n\t\t\t\t\t\t\t\t\t\t\t  true);\n\tcagg_create(stmt, &viewstmt, (Query *) stmt->query, &timebucket_exprinfo, with_clause_options);\n\n\t/* Insert the MIN of the time dimension type for the new watermark */\n\tCommandCounterIncrement();\n\n\trelid = get_relname_relid(stmt->into->rel->relname, nspid);\n\tEnsure(OidIsValid(relid),\n\t\t   \"relation \\\"%s\\\".\\\"%s\\\" not found\",\n\t\t   schema_name,\n\t\t   stmt->into->rel->relname);\n\n\tcagg = ts_continuous_agg_find_by_relid(relid);\n\tEnsure(NULL != cagg,\n\t\t   \"continuous aggregate \\\"%s\\\".\\\"%s\\\" not found\",\n\t\t   schema_name,\n\t\t   stmt->into->rel->relname);\n\n\tmat_ht = ts_hypertable_get_by_id(cagg->data.mat_hypertable_id);\n\tEnsure(NULL != mat_ht, \"materialization hypertable %d not found\", cagg->data.mat_hypertable_id);\n\tts_cagg_watermark_insert(mat_ht, 0, true);\n\n\tinvalidation_threshold_initialize(cagg);\n\n\tif (!stmt->into->skipData)\n\t{\n\t\tInternalTimeRange refresh_window = {\n\t\t\t.type = InvalidOid,\n\t\t};\n\n\t\t/*\n\t\t * We are creating a refresh window here in a similar way to how it's\n\t\t * done in continuous_agg_refresh. We do not call the PG function\n\t\t * directly since we want to be able to suppress the output in that\n\t\t * function and adding a 'verbose' parameter to is not useful for a\n\t\t * user.\n\t\t */\n\t\trefresh_window.type = cagg->partition_type;\n\n\t\t/*\n\t\t * To determine inscribed/circumscribed refresh window for variable-sized\n\t\t * buckets we should be able to calculate time_bucket(window.begin) and\n\t\t * time_bucket(window.end). This, however, is not possible in general case.\n\t\t * As an example, the minimum date is 4714-11-24 BC, which is before any\n\t\t * reasonable default `origin` value. Thus for variable-sized buckets\n\t\t * instead of minimum date we use -infinity since time_bucket(-infinity)\n\t\t * is well-defined as -infinity.\n\t\t *\n\t\t * For more details see:\n\t\t * - ts_compute_inscribed_bucketed_refresh_window_variable()\n\t\t * - ts_compute_circumscribed_bucketed_refresh_window_variable()\n\t\t */\n\t\trefresh_window.start = cagg_get_time_min(cagg);\n\t\trefresh_window.end = ts_time_get_noend_or_max(refresh_window.type);\n\n\t\tContinuousAggRefreshContext context = { .callctx = CAGG_REFRESH_CREATION };\n\t\tcontinuous_agg_refresh_internal(cagg,\n\t\t\t\t\t\t\t\t\t\t&refresh_window,\n\t\t\t\t\t\t\t\t\t\tcontext,\n\t\t\t\t\t\t\t\t\t\ttrue,  /* start_isnull */\n\t\t\t\t\t\t\t\t\t\ttrue,  /* end_isnull */\n\t\t\t\t\t\t\t\t\t\ttrue,  /* bucketing_refresh_window */\n\t\t\t\t\t\t\t\t\t\tfalse, /* force */\n\t\t\t\t\t\t\t\t\t\ttrue,  /* process_hypertable_invalidations */\n\t\t\t\t\t\t\t\t\t\tfalse /*extend_last_bucket*/);\n\t}\n\n\treturn DDL_DONE;\n}\n\n/*\n * Flip the view definition of an existing continuous aggregate from\n * real-time to materialized-only or vice versa depending on the current state.\n */\nvoid\ncagg_flip_realtime_view_definition(ContinuousAgg *agg, Hypertable *mat_ht)\n{\n\tint sec_ctx;\n\tOid uid, saved_uid;\n\tQuery *result_view_query;\n\n\t/* User view query of the user defined CAGG. */\n\tOid user_view_oid = ts_get_relation_relid(NameStr(agg->data.user_view_schema),\n\t\t\t\t\t\t\t\t\t\t\t  NameStr(agg->data.user_view_name),\n\t\t\t\t\t\t\t\t\t\t\t  false);\n\tRelation user_view_rel = relation_open(user_view_oid, AccessShareLock);\n\tQuery *user_query = copyObject(get_view_query(user_view_rel));\n\t/* Keep lock until end of transaction. */\n\trelation_close(user_view_rel, NoLock);\n\tRemoveRangeTableEntries(user_query);\n\n\t/* Direct view query of the original user view definition at CAGG creation. */\n\tOid direct_view_oid = ts_get_relation_relid(NameStr(agg->data.direct_view_schema),\n\t\t\t\t\t\t\t\t\t\t\t\tNameStr(agg->data.direct_view_name),\n\t\t\t\t\t\t\t\t\t\t\t\tfalse);\n\tRelation direct_view_rel = relation_open(direct_view_oid, AccessShareLock);\n\tQuery *direct_query = copyObject(get_view_query(direct_view_rel));\n\t/* Keep lock until end of transaction. */\n\trelation_close(direct_view_rel, NoLock);\n\tRemoveRangeTableEntries(direct_query);\n\n\tContinuousAggTimeBucketInfo timebucket_exprinfo =\n\t\tcagg_validate_query(direct_query,\n\t\t\t\t\t\t\tNameStr(agg->data.user_view_schema),\n\t\t\t\t\t\t\tNameStr(agg->data.user_view_name),\n\t\t\t\t\t\t\tfalse);\n\n\t/* Flip */\n\tagg->data.materialized_only = !agg->data.materialized_only;\n\tif (agg->data.materialized_only)\n\t{\n\t\tresult_view_query = destroy_union_query(user_query);\n\t}\n\telse\n\t{\n\t\t/* Get primary partitioning column information of time bucketing. */\n\t\tconst Dimension *mat_part_dimension = hyperspace_get_open_dimension(mat_ht->space, 0);\n\t\tresult_view_query = build_union_query(&timebucket_exprinfo,\n\t\t\t\t\t\t\t\t\t\t\t  mat_part_dimension->column_attno,\n\t\t\t\t\t\t\t\t\t\t\t  user_query,\n\t\t\t\t\t\t\t\t\t\t\t  direct_query,\n\t\t\t\t\t\t\t\t\t\t\t  mat_ht->fd.id);\n\t}\n\n\tSWITCH_TO_TS_USER(NameStr(agg->data.user_view_schema), uid, saved_uid, sec_ctx);\n\tStoreViewQuery(user_view_oid, result_view_query, true);\n\tCommandCounterIncrement();\n\tRESTORE_USER(uid, saved_uid, sec_ctx);\n}\n\n/*\n * Sync target list column names with the relation's pg_attribute names.\n */\nstatic void\nsync_target_list_names(List *targetList, TupleDesc desc)\n{\n\tListCell *lc;\n\tint i = 0;\n\tforeach (lc, targetList)\n\t{\n\t\tTargetEntry *tle = lfirst_node(TargetEntry, lc);\n\t\tif (tle->resjunk)\n\t\t\tbreak;\n\t\tFormData_pg_attribute *attr = TupleDescAttr(desc, i);\n\t\ttle->resname = NameStr(attr->attname);\n\t\t++i;\n\t}\n}\n\nvoid\ncagg_rename_view_columns(ContinuousAgg *agg)\n{\n\tint sec_ctx;\n\tOid uid, saved_uid;\n\n\t/*\n\t * This function is called from the process_rename start handler after\n\t * ExecRenameStmt has already been called on the user view, direct view,\n\t * partial view, and materialization table. All pg_attribute entries now\n\t * have the new column names.\n\t *\n\t * PostgreSQL's ExecRenameStmt only renames pg_attribute — it does NOT\n\t * update the stored query trees in pg_rewrite. We update the stored\n\t * query trees for the direct view and the user view so that subsequent\n\t * operations (build_union_query, destroy_union_query) see correct names.\n\t */\n\n\t/* --- Update direct view's stored query --- */\n\tOid direct_view_oid = ts_get_relation_relid(NameStr(agg->data.direct_view_schema),\n\t\t\t\t\t\t\t\t\t\t\t\tNameStr(agg->data.direct_view_name),\n\t\t\t\t\t\t\t\t\t\t\t\tfalse);\n\tRelation direct_view_rel = relation_open(direct_view_oid, AccessShareLock);\n\tQuery *direct_query = copyObject(get_view_query(direct_view_rel));\n\tRemoveRangeTableEntries(direct_query);\n\n\tsync_target_list_names(direct_query->targetList, RelationGetDescr(direct_view_rel));\n\n\tSWITCH_TO_TS_USER(NameStr(agg->data.user_view_schema), uid, saved_uid, sec_ctx);\n\tStoreViewQuery(direct_view_oid, direct_query, true);\n\tCommandCounterIncrement();\n\tRESTORE_USER(uid, saved_uid, sec_ctx);\n\n\t/*\n\t * Do not close the relation before StoreViewQuery since it can otherwise\n\t * release the memory for attr->attname, causing a segfault.\n\t */\n\trelation_close(direct_view_rel, NoLock);\n\n\t/* --- Update user view's stored query --- */\n\tOid user_view_oid = ts_get_relation_relid(NameStr(agg->data.user_view_schema),\n\t\t\t\t\t\t\t\t\t\t\t  NameStr(agg->data.user_view_name),\n\t\t\t\t\t\t\t\t\t\t\t  false);\n\tRelation user_view_rel = relation_open(user_view_oid, AccessShareLock);\n\tQuery *user_query = copyObject(get_view_query(user_view_rel));\n\tRemoveRangeTableEntries(user_query);\n\n\tTupleDesc user_desc = RelationGetDescr(user_view_rel);\n\tsync_target_list_names(user_query->targetList, user_desc);\n\n\t/*\n\t * When materialized_only is false the user view query is a UNION ALL\n\t * with subqueries whose target lists also carry column names. Update\n\t * those so that destroy_union_query(), which extracts a subquery,\n\t * produces a query with current names.\n\t */\n\tif (user_query->setOperations)\n\t{\n\t\tListCell *lc;\n\t\tforeach (lc, user_query->rtable)\n\t\t{\n\t\t\tRangeTblEntry *rte = lfirst_node(RangeTblEntry, lc);\n\t\t\tif (rte->rtekind == RTE_SUBQUERY && rte->subquery)\n\t\t\t\tsync_target_list_names(rte->subquery->targetList, user_desc);\n\t\t}\n\t}\n\n\tSWITCH_TO_TS_USER(NameStr(agg->data.user_view_schema), uid, saved_uid, sec_ctx);\n\tStoreViewQuery(user_view_oid, user_query, true);\n\tCommandCounterIncrement();\n\tRESTORE_USER(uid, saved_uid, sec_ctx);\n\n\trelation_close(user_view_rel, NoLock);\n}\n"
  },
  {
    "path": "tsl/src/continuous_aggs/create.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include \"ts_catalog/continuous_agg.h\"\n#include \"with_clause/with_clause_parser.h\"\n#include <process_utility.h>\n\nDDLResult tsl_process_continuous_agg_viewstmt(Node *node, const char *query_string, void *pstmt,\n\t\t\t\t\t\t\t\t\t\t\t  WithClauseResult *with_clause_options);\n\nextern void cagg_flip_realtime_view_definition(ContinuousAgg *agg, Hypertable *mat_ht);\nextern void cagg_rename_view_columns(ContinuousAgg *agg);\n"
  },
  {
    "path": "tsl/src/continuous_aggs/finalize.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include \"finalize.h\"\n\n#include <parser/parse_relation.h>\n\n#include \"common.h\"\n#include \"create.h\"\n\n/* Static function prototypes */\nstatic Var *mattablecolumninfo_addentry(MaterializationHypertableColumnInfo *out, Node *input,\n\t\t\t\t\t\t\t\t\t\tList *rtable, int original_query_resno, bool *skip_adding);\nstatic inline void makeMaterializeColumnName(char *colbuf, const char *type,\n\t\t\t\t\t\t\t\t\t\t\t int original_query_resno, int colno);\n\nstatic inline void\nmakeMaterializeColumnName(char *colbuf, const char *type, int original_query_resno, int colno)\n{\n\tint ret = snprintf(colbuf, NAMEDATALEN, \"%s_%d_%d\", type, original_query_resno, colno);\n\tif (ret < 0 || ret >= NAMEDATALEN)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR), errmsg(\"bad materialization table column name\")));\n}\n\n/*\n * Init the finalize query data structure.\n * Parameters:\n * orig_query - the original query from user view that is being used as template for the finalize\n * query tlist_aliases - aliases for the view select list materialization table columns are created\n * . This will be returned in  the mattblinfo\n *\n * DO NOT modify orig_query. Make a copy if needed.\n * SIDE_EFFECT: the data structure in mattblinfo is modified as a side effect by adding new\n * materialize table columns and partialize exprs.\n */\nvoid\nfinalizequery_init(FinalizeQueryInfo *inp, Query *orig_query,\n\t\t\t\t   MaterializationHypertableColumnInfo *mattblinfo)\n{\n\tListCell *lc;\n\tint resno = 1;\n\n\tinp->final_userquery = copyObject(orig_query);\n\tinp->final_seltlist = NIL;\n\tinp->final_havingqual = NULL;\n\n\t/*\n\t * We want all the entries in the targetlist (resjunk or not)\n\t * in the materialization  table definition so we include group-by/having clause etc.\n\t * We have to do 3 things here:\n\t * 1) create a column for mat table\n\t * 2) partialize_expr to populate it, and\n\t * 3) modify the target entry to be a finalize_expr\n\t *    that selects from the materialization table.\n\t */\n\tforeach (lc, orig_query->targetList)\n\t{\n\t\tTargetEntry *tle = (TargetEntry *) lfirst(lc);\n\t\tTargetEntry *modte = copyObject(tle);\n\n\t\tif (!orig_query->sortClause)\n\t\t\tmodte->ressortgroupref = 0;\n\n\t\t/*\n\t\t * We need columns for non-aggregate targets.\n\t\t * If it is not a resjunk OR appears in the grouping clause.\n\t\t */\n\t\tif (tle->resjunk == false || tle->ressortgroupref > 0)\n\t\t{\n\t\t\tVar *var;\n\t\t\tbool skip_adding = false;\n\t\t\tvar = mattablecolumninfo_addentry(mattblinfo,\n\t\t\t\t\t\t\t\t\t\t\t  (Node *) tle,\n\t\t\t\t\t\t\t\t\t\t\t  orig_query->rtable,\n\t\t\t\t\t\t\t\t\t\t\t  resno,\n\t\t\t\t\t\t\t\t\t\t\t  &skip_adding);\n\n\t\t\t/* Skip adding this column for finalized form. */\n\t\t\tif (skip_adding)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t/* Fix the expression for the target entry. */\n\t\t\tmodte->expr = (Expr *) var;\n\t\t}\n\t\t/*\n\t\t * Construct the targetlist for the query on the\n\t\t * materialization table. The TL maps 1-1 with the original query:\n\t\t * e.g select a, min(b)+max(d) from foo group by a,timebucket(a);\n\t\t * becomes\n\t\t * select <a-col>,\n\t\t * ts_internal_cagg_final(..b-col ) + ts_internal_cagg_final(..d-col)\n\t\t * from mattbl\n\t\t * group by a-col, timebucket(a-col)\n\t\t */\n\n\t\t/*\n\t\t * We copy the modte target entries, resnos should be the same for\n\t\t * final_selquery and origquery. So tleSortGroupReffor the targetentry\n\t\t * can be reused, only table info needs to be modified.\n\t\t */\n\t\tAssert(modte->resno >= resno);\n\t\tresno++;\n\t\tif (IsA(modte->expr, Var))\n\t\t{\n\t\t\tmodte->resorigcol = ((Var *) modte->expr)->varattno;\n\t\t}\n\t\tinp->final_seltlist = lappend(inp->final_seltlist, modte);\n\t}\n}\n\n/*\n * Create select query with the finalize aggregates\n * for the materialization table.\n * matcollist - column list for mat table\n * mattbladdress - materialization table ObjectAddress\n * This is the function responsible for creating the final\n * structures for selecting from the materialized hypertable\n * created for the Cagg which is\n * select * from _timescaldeb_internal._materialized_hypertable_<xxx>\n */\nQuery *\nfinalizequery_get_select_query(FinalizeQueryInfo *inp, List *matcollist,\n\t\t\t\t\t\t\t   ObjectAddress *mattbladdress, char *relname)\n{\n\tQuery *final_selquery = NULL;\n\n\tCAGG_MAKEQUERY(final_selquery, inp->final_userquery);\n\tfinal_selquery->hasAggs = false;\n\n\t/* New RangeTblEntry for the materialization hypertable */\n\tRangeTblEntry *rte = makeNode(RangeTblEntry);\n\trte->inFromCl = true;\n\trte->inh = true;\n\trte->rellockmode = 1;\n\trte->eref = makeAlias(relname, NIL);\n\n\trte->relid = mattbladdress->objectId;\n\trte->rtekind = RTE_RELATION;\n\trte->relkind = RELKIND_RELATION;\n\trte->tablesample = NULL;\n#if PG16_LT\n\trte->requiredPerms |= ACL_SELECT;\n\trte->insertedCols = NULL;\n\trte->updatedCols = NULL;\n#else\n\tRTEPermissionInfo *perminfo = addRTEPermissionInfo(&final_selquery->rteperminfos, rte);\n\tperminfo->selectedCols = NULL;\n\tperminfo->relid = mattbladdress->objectId;\n\tperminfo->requiredPerms |= ACL_SELECT;\n\tperminfo->insertedCols = NULL;\n\tperminfo->updatedCols = NULL;\n#endif\n\n\t/* Aliases for column names for the materialization hypertable. */\n\tListCell *lc;\n\tint attno = 0;\n\tforeach (lc, matcollist)\n\t{\n\t\tColumnDef *cdef = lfirst_node(ColumnDef, lc);\n\t\trte->eref->colnames = lappend(rte->eref->colnames, makeString(cdef->colname));\n\t\tattno = list_length(rte->eref->colnames) - FirstLowInvalidHeapAttributeNumber;\n#if PG16_LT\n\t\trte->selectedCols = bms_add_member(rte->selectedCols, attno);\n#else\n\t\tperminfo->selectedCols = bms_add_member(perminfo->selectedCols, attno);\n#endif\n\t}\n\n\t/* Fixup targetlist with the correct rel information. */\n\tforeach (lc, inp->final_seltlist)\n\t{\n\t\tTargetEntry *tle = lfirst_node(TargetEntry, lc);\n\t\t/*\n\t\t * In case when this is a cagg with joins, the Var from the normal table\n\t\t * already has resorigtbl populated and we need to use that to resolve\n\t\t * the Var. Hence only modify the tle when resorigtbl is unset\n\t\t * which means it is Var of the Hypertable\n\t\t */\n\t\tif (IsA(tle->expr, Var) && !OidIsValid(tle->resorigtbl))\n\t\t{\n\t\t\ttle->resorigtbl = rte->relid;\n\t\t\ttle->resorigcol = castNode(Var, tle->expr)->varattno;\n\t\t}\n\t}\n\n\tRangeTblRef *rtr = makeNode(RangeTblRef);\n\trtr->rtindex = 1;\n\n\tfinal_selquery->rtable = list_make1(rte);\n\tfinal_selquery->jointree = makeFromExpr(list_make1(rtr), NULL);\n\tfinal_selquery->targetList = inp->final_seltlist;\n\tfinal_selquery->sortClause = inp->final_userquery->sortClause;\n\n\treturn final_selquery;\n}\n\n/*\n * Add Information required to create and populate the materialization table columns\n * creating a columndef for the materialization table.\n *\n * Notes: make sure the materialization table columns do not save\n *        values computed by mutable function.\n *\n * Notes on TargetEntry fields:\n * - (resname != NULL) means it's projected in our case\n * - (ressortgroupref > 0) means part of GROUP BY, which can be projected or not, depending of the\n *                         value of the resjunk\n * - (resjunk == true) applies for GROUP BY columns that are not projected\n *\n */\nstatic Var *\nmattablecolumninfo_addentry(MaterializationHypertableColumnInfo *out, Node *input, List *rtable,\n\t\t\t\t\t\t\tint original_query_resno, bool *skip_adding)\n{\n\tint matcolno = list_length(out->matcollist) + 1;\n\tchar colbuf[NAMEDATALEN];\n\tchar *colname;\n\tTargetEntry *part_te = NULL;\n\tColumnDef *col;\n\tVar *var;\n\tOid coltype = InvalidOid, colcollation = InvalidOid;\n\tint32 coltypmod;\n\n\t*skip_adding = false;\n\n\tif (contain_mutable_functions(input))\n\t{\n\t\tereport(WARNING,\n\t\t\t\t(errmsg(\"using non-immutable functions in continuous aggregate view may lead to \"\n\t\t\t\t\t\t\"inconsistent results on rematerialization\")));\n\t}\n\n\tswitch (nodeTag(input))\n\t{\n\t\tcase T_TargetEntry:\n\t\t{\n\t\t\tTargetEntry *tle = (TargetEntry *) input;\n\t\t\tbool timebkt_chk = false;\n\n\t\t\tif (IsA(tle->expr, FuncExpr))\n\t\t\t\ttimebkt_chk = function_allowed_in_cagg_definition(((FuncExpr *) tle->expr)->funcid);\n\n#if PG18_GE\n\t\t\t/* PG18 introduced RTEs for group clauses so\n\t\t\t * we use rtable to look up GROUP BY expressions.\n\t\t\t *\n\t\t\t * https://github.com/postgres/postgres/commit/247dea89\n\t\t\t */\n\t\t\tif (IsA(tle->expr, Var))\n\t\t\t{\n\t\t\t\tVar *var = castNode(Var, tle->expr);\n\t\t\t\tAssert((int) var->varno <= list_length(rtable));\n\t\t\t\tRangeTblEntry *rte = list_nth(rtable, var->varno - 1);\n\t\t\t\tAssert(var->varattno > 0);\n\t\t\t\t/*\n\t\t\t\t * Var might not be part of the GROUP BY clause\n\t\t\t\t * eg for functionally dependent columns on tables with primary key\n\t\t\t\t */\n\t\t\t\tif (rte->rtekind == RTE_GROUP)\n\t\t\t\t{\n\t\t\t\t\tNode *node = list_nth(rte->groupexprs, var->varattno - 1);\n\t\t\t\t\tif (IsA(node, FuncExpr))\n\t\t\t\t\t{\n\t\t\t\t\t\tif (contain_mutable_functions(node))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tereport(WARNING,\n\t\t\t\t\t\t\t\t\t(errmsg(\"using non-immutable functions in continuous aggregate \"\n\t\t\t\t\t\t\t\t\t\t\t\"view may lead to \"\n\t\t\t\t\t\t\t\t\t\t\t\"inconsistent results on rematerialization\")));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tFuncExpr *expr = (FuncExpr *) node;\n\t\t\t\t\t\ttimebkt_chk =\n\t\t\t\t\t\t\tfunction_allowed_in_cagg_definition(((FuncExpr *) expr)->funcid);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n#endif\n\n\t\t\tif (tle->resname)\n\t\t\t\tcolname = pstrdup(tle->resname);\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (timebkt_chk)\n\t\t\t\t\tcolname = DEFAULT_MATPARTCOLUMN_NAME;\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tmakeMaterializeColumnName(colbuf, \"grp\", original_query_resno, matcolno);\n\t\t\t\t\tcolname = colbuf;\n\n\t\t\t\t\t/* For finalized form we skip adding extra group by columns. */\n\t\t\t\t\t*skip_adding = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (timebkt_chk)\n\t\t\t{\n\t\t\t\ttle->resname = pstrdup(colname);\n\t\t\t\tout->matpartcolno = matcolno;\n\t\t\t\tout->matpartcolname = pstrdup(colname);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * Add indexes only for columns that are part of the GROUP BY clause\n\t\t\t\t * and for finals form.\n\t\t\t\t * We skip adding it because we'll not add the extra group by columns\n\t\t\t\t * to the materialization hypertable anymore.\n\t\t\t\t */\n\t\t\t\tif (!*skip_adding && tle->ressortgroupref > 0)\n\t\t\t\t\tout->mat_groupcolname_list =\n\t\t\t\t\t\tlappend(out->mat_groupcolname_list, pstrdup(colname));\n\t\t\t}\n\n\t\t\tcoltype = exprType((Node *) tle->expr);\n\t\t\tcoltypmod = exprTypmod((Node *) tle->expr);\n\t\t\tcolcollation = exprCollation((Node *) tle->expr);\n\t\t\tcol = makeColumnDef(colname, coltype, coltypmod, colcollation);\n\t\t\tpart_te = (TargetEntry *) copyObject(input);\n\n\t\t\t/* Keep original resjunk if not time bucket. */\n\t\t\tif (timebkt_chk)\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * Need to project all the partial entries so that\n\t\t\t\t * materialization table is filled.\n\t\t\t\t */\n\t\t\t\tpart_te->resjunk = false;\n\t\t\t}\n\n\t\t\tpart_te->resno = matcolno;\n\n\t\t\tif (timebkt_chk)\n\t\t\t{\n\t\t\t\tcol->is_not_null = true;\n\t\t\t}\n\n\t\t\tif (part_te->resname == NULL)\n\t\t\t{\n\t\t\t\tpart_te->resname = pstrdup(colname);\n\t\t\t}\n\t\t}\n\t\tbreak;\n\n\t\tcase T_Var:\n\t\t{\n\t\t\tmakeMaterializeColumnName(colbuf, \"var\", original_query_resno, matcolno);\n\t\t\tcolname = colbuf;\n\n\t\t\tcoltype = exprType(input);\n\t\t\tcoltypmod = exprTypmod(input);\n\t\t\tcolcollation = exprCollation(input);\n\t\t\tcol = makeColumnDef(colname, coltype, coltypmod, colcollation);\n\t\t\tpart_te = makeTargetEntry((Expr *) input, matcolno, pstrdup(colname), false);\n\n\t\t\t/* Need to project all the partial entries so that materialization table is filled. */\n\t\t\tpart_te->resjunk = false;\n\t\t\tpart_te->resno = matcolno;\n\t\t}\n\t\tbreak;\n\n\t\tdefault:\n\t\t\telog(ERROR, \"invalid node type %d\", nodeTag(input));\n\t\t\tbreak;\n\t}\n\tAssert(list_length(out->matcollist) <= list_length(out->partial_seltlist));\n\tAssert(col != NULL);\n\tAssert(part_te != NULL);\n\n\tif (!*skip_adding)\n\t{\n\t\tout->matcollist = lappend(out->matcollist, col);\n\t}\n\n\tout->partial_seltlist = lappend(out->partial_seltlist, part_te);\n\n\tvar = makeVar(1, matcolno, coltype, coltypmod, colcollation, 0);\n\treturn var;\n}\n"
  },
  {
    "path": "tsl/src/continuous_aggs/finalize.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include <catalog/pg_aggregate.h>\n#include <catalog/pg_collation.h>\n#include <catalog/pg_type.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodeFuncs.h>\n#include <nodes/pg_list.h>\n#include <parser/parse_func.h>\n#include <utils/builtins.h>\n#include <utils/regproc.h>\n#include <utils/syscache.h>\n\n#include \"common.h\"\n#include \"ts_catalog/catalog.h\"\n\nextern Query *finalize_query_get_select_query(FinalizeQueryInfo *inp, List *matcollist,\n\t\t\t\t\t\t\t\t\t\t\t  ObjectAddress *mattbladdress);\nextern void finalizequery_init(FinalizeQueryInfo *inp, Query *orig_query,\n\t\t\t\t\t\t\t   MaterializationHypertableColumnInfo *mattblinfo);\nextern Query *finalizequery_get_select_query(FinalizeQueryInfo *inp, List *matcollist,\n\t\t\t\t\t\t\t\t\t\t\t ObjectAddress *mattbladdress, char *relname);\n"
  },
  {
    "path": "tsl/src/continuous_aggs/insert.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <utils/hsearch.h>\n#include <utils/snapmgr.h>\n\n#include \"compat/compat.h\"\n\n#include \"continuous_aggs/insert.h\"\n#include \"debug_point.h\"\n#include \"guc.h\"\n#include \"invalidation.h\"\n#include \"partitioning.h\"\n\n/*\n * When tuples in a hypertable that has a continuous aggregate are modified, the\n * lowest modified value and the greatest modified value must be tracked over\n * the course of a transaction or statement. At the end of the statement these\n * values will be inserted into the proper cache invalidation log table for\n * their associated hypertable if they are below the speculative materialization\n * watermark (or, if in REPEATABLE_READ isolation level or higher, they will be\n * inserted no matter what as we cannot see if a materialization transaction has\n * started and moved the watermark during our transaction in that case).\n *\n * We accomplish this at the transaction level by keeping a hash table of each\n * hypertable that has been modified in the transaction and the lowest and\n * greatest modified values. The hashtable will be updated via ModifyHypertable\n * for every row that is inserted, updated or deleted.\n * We use a hashtable because we need to keep track of this on a per hypertable\n * basis and multiple can have tuples modified during a single transaction.\n * (And if we move to per-chunk cache-invalidation it makes it even easier).\n *\n */\ntypedef struct ContinuousAggsCacheInvalEntry\n{\n\tOid chunk_relid;\n\tint32 hypertable_id;\n\tDimension hypertable_open_dimension;\n\tAttrNumber open_dimension_attno;\n\tbool value_is_set;\n\tint64 lowest_modified_value;\n\tint64 greatest_modified_value;\n} ContinuousAggsCacheInvalEntry;\n\ntypedef struct ContinuousAggsCacheHyperInvalThresholdEntry\n{\n\tint32 hypertable_id;\n\tint64 watermark;\n} ContinuousAggsCacheHyperInvalThresholdEntry;\n\nstatic int64 get_lowest_invalidated_time_for_hypertable(int32 hypertable_id);\nstatic inline int64 cache_get_lowest_invalidated_time_for_hypertable(int32 hypertable_id);\n\n#define CA_CACHE_INVAL_INIT_HTAB_SIZE 64\n\nstatic HTAB *continuous_aggs_cache_inval_htab = NULL;\nstatic HTAB *continuous_aggs_cache_hyper_inval_threshold_htab = NULL;\n\nstatic MemoryContext continuous_aggs_invalidation_mctx = NULL;\n\nstatic inline void cache_inval_entry_init(ContinuousAggsCacheInvalEntry *cache_entry,\n\t\t\t\t\t\t\t\t\t\t  int32 hypertable_id, Oid chunk_relid);\nstatic inline ContinuousAggsCacheInvalEntry *get_cache_inval_entry(int32 hypertable_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   Oid chunk_relid);\nstatic void cache_inval_cleanup(void);\nstatic void cache_inval_htab_write(void);\nstatic void continuous_agg_xact_invalidation_callback(XactEvent event, void *arg);\nstatic ScanTupleResult invalidation_tuple_found(TupleInfo *ti, void *min);\n\nstatic void\ncache_inval_init()\n{\n\tHASHCTL ctl;\n\n\tAssert(continuous_aggs_invalidation_mctx == NULL);\n\n\tcontinuous_aggs_invalidation_mctx = AllocSetContextCreate(TopTransactionContext,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  \"ContinuousAggsInvalidationCtx\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  ALLOCSET_DEFAULT_SIZES);\n\n\tmemset(&ctl, 0, sizeof(ctl));\n\tctl.keysize = sizeof(Oid);\n\tctl.entrysize = sizeof(ContinuousAggsCacheInvalEntry);\n\tctl.hcxt = continuous_aggs_invalidation_mctx;\n\n\tcontinuous_aggs_cache_inval_htab = hash_create(\"TS Continuous Aggs Cache Inval\",\n\t\t\t\t\t\t\t\t\t\t\t\t   CA_CACHE_INVAL_INIT_HTAB_SIZE,\n\t\t\t\t\t\t\t\t\t\t\t\t   &ctl,\n\t\t\t\t\t\t\t\t\t\t\t\t   HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);\n\n\tmemset(&ctl, 0, sizeof(ctl));\n\tctl.keysize = sizeof(int32);\n\tctl.entrysize = sizeof(ContinuousAggsCacheHyperInvalThresholdEntry);\n\tctl.hcxt = continuous_aggs_invalidation_mctx;\n\n\tcontinuous_aggs_cache_hyper_inval_threshold_htab =\n\t\thash_create(\"TS Continuous Aggs Hypertable Invalidation Threshold\",\n\t\t\t\t\tCA_CACHE_INVAL_INIT_HTAB_SIZE,\n\t\t\t\t\t&ctl,\n\t\t\t\t\tHASH_ELEM | HASH_BLOBS | HASH_CONTEXT);\n}\n\nstatic void\nupdate_cache_from_tuple(ContinuousAggsCacheInvalEntry *cache_entry, HeapTuple tuple,\n\t\t\t\t\t\tTupleDesc tupdesc)\n{\n\tDatum datum;\n\tbool isnull;\n\tOid dimtype;\n\tDimension *d = &cache_entry->hypertable_open_dimension;\n\tAttrNumber col = cache_entry->open_dimension_attno;\n\n\tAssert(d->type == DIMENSION_TYPE_OPEN);\n\n\tdatum = heap_getattr(tuple, col, tupdesc, &isnull);\n\t/*\n\t * Even though there are NOT NULL constraints on time columns checking these happens\n\t * after invalidation processing so we skip nulls here to allow for normal postgres\n\t * error handling for these NULL values.\n\t */\n\tif (isnull)\n\t\treturn;\n\n\tdimtype = ts_dimension_get_partition_type(d);\n\tint64 timeval = ts_time_value_to_internal(datum, dimtype);\n\n\tcache_entry->value_is_set = true;\n\tif (timeval < cache_entry->lowest_modified_value)\n\t\tcache_entry->lowest_modified_value = timeval;\n\tif (timeval > cache_entry->greatest_modified_value)\n\t\tcache_entry->greatest_modified_value = timeval;\n}\n\nstatic inline void\ncache_inval_entry_init(ContinuousAggsCacheInvalEntry *cache_entry, int32 hypertable_id,\n\t\t\t\t\t   Oid chunk_relid)\n{\n\tCache *ht_cache = ts_hypertable_cache_pin();\n\tHypertable *ht = ts_hypertable_cache_get_entry_by_id(ht_cache, hypertable_id);\n\tEnsure(ht, \"could not find hypertable with id %d\", hypertable_id);\n\n\tconst Dimension *open_dim = hyperspace_get_open_dimension(ht->space, 0);\n\tEnsure(open_dim, \"hypertable %d has no open partitioning dimension\", hypertable_id);\n\n\tcache_entry->chunk_relid = chunk_relid;\n\tcache_entry->hypertable_id = hypertable_id;\n\tcache_entry->hypertable_open_dimension = *open_dim;\n\tcache_entry->open_dimension_attno = get_attnum(chunk_relid, NameStr(open_dim->fd.column_name));\n\tcache_entry->value_is_set = false;\n\tcache_entry->lowest_modified_value = INVAL_POS_INFINITY;\n\tcache_entry->greatest_modified_value = INVAL_NEG_INFINITY;\n\tts_cache_release(&ht_cache);\n}\n\nstatic inline ContinuousAggsCacheInvalEntry *\nget_cache_inval_entry(int32 hypertable_id, Oid chunk_relid)\n{\n\tContinuousAggsCacheInvalEntry *cache_entry;\n\tbool found;\n\n\tif (!continuous_aggs_cache_inval_htab)\n\t\tcache_inval_init();\n\n\tcache_entry = (ContinuousAggsCacheInvalEntry *)\n\t\thash_search(continuous_aggs_cache_inval_htab, &chunk_relid, HASH_ENTER, &found);\n\n\tif (!found)\n\t\tcache_inval_entry_init(cache_entry, hypertable_id, chunk_relid);\n\n\treturn cache_entry;\n}\n\n/*\n * Used by direct compress invalidation\n */\nvoid\ncontinuous_agg_invalidate_range(int32 hypertable_id, Oid chunk_relid, int64 start, int64 end)\n{\n\tContinuousAggsCacheInvalEntry *cache_entry = get_cache_inval_entry(hypertable_id, chunk_relid);\n\n\tcache_entry->value_is_set = true;\n\tAssert(start <= end);\n\tif (start < cache_entry->lowest_modified_value)\n\t\tcache_entry->lowest_modified_value = start;\n\tif (end > cache_entry->greatest_modified_value)\n\t\tcache_entry->greatest_modified_value = end;\n}\n\nvoid\ncontinuous_agg_dml_invalidate(int32 hypertable_id, Relation chunk_rel, HeapTuple chunk_tuple,\n\t\t\t\t\t\t\t  HeapTuple chunk_newtuple, bool update)\n{\n\tContinuousAggsCacheInvalEntry *cache_entry =\n\t\tget_cache_inval_entry(hypertable_id, chunk_rel->rd_id);\n\n\tupdate_cache_from_tuple(cache_entry, chunk_tuple, RelationGetDescr(chunk_rel));\n\n\tif (!update)\n\t\treturn;\n\n\t/* on update we need to invalidate the new time value as well as the old one */\n\tupdate_cache_from_tuple(cache_entry, chunk_newtuple, RelationGetDescr(chunk_rel));\n}\n\nstatic inline void\ncache_inval_entry_write(ContinuousAggsCacheInvalEntry *entry)\n{\n\tint64 liv;\n\n\tif (!entry->value_is_set)\n\t\treturn;\n\n\t/* The materialization worker uses a READ COMMITTED isolation level by default. Therefore, if we\n\t * use a stronger isolation level, the isolation threshold could update without us seeing the\n\t * new value. In order to prevent serialization errors, we always append invalidation entries in\n\t * the case when we're using a strong enough isolation level that we won't see the new\n\t * threshold. The materializer can handle invalidations that are beyond the threshold\n\t * gracefully.\n\t */\n\tif (IsolationUsesXactSnapshot())\n\t{\n\t\tinvalidation_hyper_log_add_entry(entry->hypertable_id,\n\t\t\t\t\t\t\t\t\t\t entry->lowest_modified_value,\n\t\t\t\t\t\t\t\t\t\t entry->greatest_modified_value);\n\t\treturn;\n\t}\n\n\tliv = cache_get_lowest_invalidated_time_for_hypertable(entry->hypertable_id);\n\n\tif (entry->lowest_modified_value < liv)\n\t\tinvalidation_hyper_log_add_entry(entry->hypertable_id,\n\t\t\t\t\t\t\t\t\t\t entry->lowest_modified_value,\n\t\t\t\t\t\t\t\t\t\t entry->greatest_modified_value);\n};\n\nstatic void\ncache_inval_cleanup(void)\n{\n\tAssert(continuous_aggs_cache_inval_htab != NULL);\n\tAssert(continuous_aggs_cache_hyper_inval_threshold_htab != NULL);\n\thash_destroy(continuous_aggs_cache_inval_htab);\n\thash_destroy(continuous_aggs_cache_hyper_inval_threshold_htab);\n\tMemoryContextDelete(continuous_aggs_invalidation_mctx);\n\n\tcontinuous_aggs_cache_inval_htab = NULL;\n\tcontinuous_aggs_cache_hyper_inval_threshold_htab = NULL;\n\tcontinuous_aggs_invalidation_mctx = NULL;\n};\n\nstatic void\ncache_inval_htab_write(void)\n{\n\tHASH_SEQ_STATUS hash_seq;\n\tContinuousAggsCacheInvalEntry *current_entry;\n\tCatalog *catalog;\n\n\tif (hash_get_num_entries(continuous_aggs_cache_inval_htab) == 0)\n\t\treturn;\n\n\tcatalog = ts_catalog_get();\n\n\t/* The invalidation threshold must remain locked until the end of\n\t * the transaction to ensure the materializer will see our updates,\n\t * so we explicitly lock it here\n\t */\n\tLockRelationOid(catalog_get_table_id(catalog, CONTINUOUS_AGGS_INVALIDATION_THRESHOLD),\n\t\t\t\t\tAccessShareLock);\n\n\thash_seq_init(&hash_seq, continuous_aggs_cache_inval_htab);\n\twhile ((current_entry = hash_seq_search(&hash_seq)) != NULL)\n\t\tcache_inval_entry_write(current_entry);\n};\n\n/*\n * We use TopTransactionContext for our cached invalidations.\n * We need to make sure cache_inval_cleanup() is always called after cache_inval_htab_write().\n * We need this memory context to survive the transaction lifetime so that cache_inval_cleanup()\n * does not attempt to tear down memory that has already been freed due to a transaction ending.\n *\n * The order of operations in postgres can be this:\n * CallXactCallbacks(XACT_EVENT_PRE_PREPARE);\n * ...\n * CallXactCallbacks(XACT_EVENT_PREPARE);\n * ...\n * MemoryContextDelete(TopTransactionContext);\n *\n * or that:\n * CallXactCallbacks(XACT_EVENT_PRE_COMMIT);\n * ...\n * CallXactCallbacks(XACT_EVENT_COMMIT);\n * ...\n * MemoryContextDelete(TopTransactionContext);\n *\n * In the case of a 2PC transaction, we need to make sure to apply the invalidations at\n * XACT_EVENT_PRE_PREPARE time, before TopTransactionContext is torn down by PREPARE TRANSACTION.\n * Otherwise, we are unable to call cache_inval_cleanup() without corrupting the memory. For\n * this reason, we also deallocate at XACT_EVENT_PREPARE time.\n *\n * For local transactions we apply the invalidations at XACT_EVENT_PRE_COMMIT time.\n * Similar care is taken of parallel workers and aborting transactions.\n */\nstatic void\ncontinuous_agg_xact_invalidation_callback(XactEvent event, void *arg)\n{\n\t/* Return quickly if we never initialize the hashtable */\n\tif (!continuous_aggs_cache_inval_htab)\n\t\treturn;\n\n\tswitch (event)\n\t{\n\t\tcase XACT_EVENT_PRE_PREPARE:\n\t\tcase XACT_EVENT_PRE_COMMIT:\n\t\tcase XACT_EVENT_PARALLEL_PRE_COMMIT:\n\t\t\tcache_inval_htab_write();\n\t\t\tbreak;\n\t\tcase XACT_EVENT_PREPARE:\n\t\tcase XACT_EVENT_COMMIT:\n\t\tcase XACT_EVENT_PARALLEL_COMMIT:\n\t\tcase XACT_EVENT_ABORT:\n\t\tcase XACT_EVENT_PARALLEL_ABORT:\n\t\t\tcache_inval_cleanup();\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n}\n\nvoid\n_continuous_aggs_cache_inval_init(void)\n{\n\tRegisterXactCallback(continuous_agg_xact_invalidation_callback, NULL);\n}\n\nvoid\n_continuous_aggs_cache_inval_fini(void)\n{\n\tUnregisterXactCallback(continuous_agg_xact_invalidation_callback, NULL);\n}\n\nstatic ScanTupleResult\ninvalidation_tuple_found(TupleInfo *ti, void *min)\n{\n\tbool isnull;\n\tDatum watermark =\n\t\tslot_getattr(ti->slot, Anum_continuous_aggs_invalidation_threshold_watermark, &isnull);\n\n\tAssert(!isnull);\n\n\tif (DatumGetInt64(watermark) < *((int64 *) min))\n\t\t*((int64 *) min) = DatumGetInt64(watermark);\n\n\tDEBUG_WAITPOINT(\"invalidation_tuple_found_done\");\n\n\t/*\n\t * Return SCAN_CONTINUE because we check for multiple tuples as an error\n\t * condition.\n\t */\n\treturn SCAN_CONTINUE;\n}\n\nstatic int64\nget_lowest_invalidated_time_for_hypertable(int32 hypertable_id)\n{\n\tint64 min_val = INVAL_POS_INFINITY;\n\tCatalog *catalog = ts_catalog_get();\n\tScanKeyData scankey[1];\n\tScannerCtx scanctx;\n\n\tPushActiveSnapshot(GetLatestSnapshot());\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_continuous_aggs_invalidation_threshold_pkey_hypertable_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(hypertable_id));\n\tscanctx = (ScannerCtx){\n\t\t.table = catalog_get_table_id(catalog, CONTINUOUS_AGGS_INVALIDATION_THRESHOLD),\n\t\t.index = catalog_get_index(catalog,\n\t\t\t\t\t\t\t\t   CONTINUOUS_AGGS_INVALIDATION_THRESHOLD,\n\t\t\t\t\t\t\t\t   CONTINUOUS_AGGS_INVALIDATION_THRESHOLD_PKEY),\n\t\t.nkeys = 1,\n\t\t.scankey = scankey,\n\t\t.tuple_found = &invalidation_tuple_found,\n\t\t.filter = NULL,\n\t\t.data = &min_val,\n\t\t.lockmode = AccessShareLock,\n\t\t.scandirection = ForwardScanDirection,\n\t\t.result_mctx = NULL,\n\n\t\t/* We need to define a custom snapshot for this scan. The default snapshot (SNAPSHOT_SELF)\n\t\t   reads data of all committed transactions, even if they have started after our scan. If a\n\t\t   parallel session updates the scanned value and commits during a scan, we end up in a\n\t\t   situation where we see the old and the new value. This causes ts_scanner_scan_one() to\n\t\t   fail. */\n\t\t.snapshot = GetActiveSnapshot(),\n\t};\n\n\t/* If we don't find any invalidation threshold watermark, then we've never done any\n\t * materialization we'll treat this as if the invalidation timestamp is at min value, since the\n\t * first materialization needs to scan the entire table anyway; the invalidations are redundant.\n\t */\n\tif (!ts_scanner_scan_one(&scanctx, false, CAGG_INVALIDATION_THRESHOLD_NAME))\n\t\tmin_val = INVAL_NEG_INFINITY;\n\tPopActiveSnapshot();\n\n\treturn min_val;\n}\n\nstatic inline int64\ncache_get_lowest_invalidated_time_for_hypertable(int32 hypertable_id)\n{\n\tContinuousAggsCacheHyperInvalThresholdEntry *hyper_inval_cache_entry;\n\tbool found;\n\n\thyper_inval_cache_entry = (ContinuousAggsCacheHyperInvalThresholdEntry *)\n\t\thash_search(continuous_aggs_cache_hyper_inval_threshold_htab,\n\t\t\t\t\t&hypertable_id,\n\t\t\t\t\tHASH_ENTER,\n\t\t\t\t\t&found);\n\tif (!found)\n\t{\n\t\thyper_inval_cache_entry->hypertable_id = hypertable_id;\n\t\thyper_inval_cache_entry->watermark =\n\t\t\tget_lowest_invalidated_time_for_hypertable(hypertable_id);\n\t}\n\n\treturn hyper_inval_cache_entry->watermark;\n}\n"
  },
  {
    "path": "tsl/src/continuous_aggs/insert.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\nextern void _continuous_aggs_cache_inval_init(void);\nextern void _continuous_aggs_cache_inval_fini(void);\nextern void continuous_agg_invalidate_range(int32 hypertable_id, Oid chunk_relid, int64 start,\n\t\t\t\t\t\t\t\t\t\t\tint64 end);\nextern void continuous_agg_dml_invalidate(int32 hypertable_id, Relation chunk_rel,\n\t\t\t\t\t\t\t\t\t\t  HeapTuple chunk_tuple, HeapTuple chunk_newtuple,\n\t\t\t\t\t\t\t\t\t\t  bool update);\n"
  },
  {
    "path": "tsl/src/continuous_aggs/invalidation.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n\n#include <access/htup.h>\n#include <access/htup_details.h>\n#include <access/xact.h>\n#include <extension.h>\n#include <fmgr.h>\n#include <funcapi.h>\n#include <hypertable_cache.h>\n#include <miscadmin.h>\n#include <nodes/makefuncs.h>\n#include <nodes/memnodes.h>\n#include <parser/parse_func.h>\n#include <scan_iterator.h>\n#include <scanner.h>\n#include <storage/lockdefs.h>\n#include <time_bucket.h>\n#include <time_utils.h>\n#include <utils.h>\n#include <utils/builtins.h>\n#include <utils/elog.h>\n#include <utils/lsyscache.h>\n#include <utils/memutils.h>\n#include <utils/palloc.h>\n#include <utils/snapmgr.h>\n#include <utils/tuplestore.h>\n\n#include \"cache.h\"\n#include \"continuous_aggs/invalidation_threshold.h\"\n#include \"continuous_aggs/materialize.h\"\n#include \"guc.h\"\n#include \"invalidation.h\"\n#include \"refresh.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"ts_catalog/continuous_agg.h\"\n\n/*\n * Invalidation processing for continuous aggregates.\n *\n * Invalidations track the regions/intervals [start, end] of a continuous\n * aggregate that are out-of-date relative to the source hypertable on top of\n * which the aggregate is defined. When a continuous aggregate is out-of-date\n * across one or more regions, it can be refreshed with a window covering\n * those regions in order to bring it up-to-date with the source data again.\n *\n * Invalidations are generated by mutations on the source hypertable (INSERT,\n * DELETE, UPDATE, TRUNCATE, drop_chunks, etc.) and are initially written to a\n * hypertable invalidation log [hypertable_id, start, end].\n *\n * When a continuous aggregate is refreshed, invalidations are moved from the\n * hypertable invalidation log to a continuous aggregate invalidation log,\n * where each original entry creates one new entry per continuous aggregate\n * [cagg_id, start, end]. Thus, if one continuous aggregate is refreshed but\n * not others, then only the invalidations for the refreshed aggregate are\n * processed.\n *\n * Simplified, invalidations move through the following stages:\n *\n * insert_trigger => hypertable_inval_log => cagg_inval_log => refreshing\n *\n * Thus, invalidations are generated by mutations and are processed and used\n * as input for refreshing the a continuous aggregate.\n *\n * Invalidations can overlap or be duplicates. Therefore, invalidations are\n * merged during processing to reduce the number of entries in the logs. This\n * typically happens during a refresh of a continuous aggregate, which also\n * cuts invalidations along the refresh window. The cutting will leave some\n * parts of entries in the invalidation log while the entries that fall\n * within the refresh window are stored in an invalidation store and used for\n * refreshing:\n *\n *       |-------------|    refresh window\n *\n *   |-----|  |---| |----|  invalidations\n *\n *             =>\n *\n *   |---|             |--| invalidations that remain in the log\n *\n *       |-|  |---| |--|    invalidations that are used for refreshing\n *\n * The invalidation store will spill to disk in case of many invalidations so\n * it won't blow up memory usage. If there are no invalidations in the store\n * after processing, then the continuous aggregate is up-to-date in the region\n * defined by the refresh window.\n */\n\n/*\n * Processing state used while processing the materialization invalidation log\n * and refreshing the continuous aggregate.\n */\ntypedef struct ContinuousAggInvalidationState\n{\n\tconst ContinuousAgg *cagg;\n\tMemoryContext per_tuple_mctx;\n\tRelation cagg_log_rel;\n\tRelation cagg_queue_rel;\n\tSnapshot snapshot;\n\tTuplestorestate *invalidations;\n} ContinuousAggInvalidationState;\n\n/*\n * Processing state used while moving invalidations from hypertable\n * invalidation log to materialization invalidation log.\n */\ntypedef struct HypertableInvalidationState\n{\n\tint32 hypertable_id;\n\tOid dimtype; /* Type of the underlying hypertable's bucketed attribute */\n\tconst ContinuousAggInfo *all_caggs;\n\tMemoryContext per_tuple_mctx;\n\tRelation cagg_log_rel;\n\tSnapshot snapshot;\n} HypertableInvalidationState;\n\ntypedef enum ContinuousAggTableType\n{\n\tHYPER_INVALIDATION_LOG,\n\tCAGG_INVALIDATION_LOG,\n\tCAGG_MATERIALIZATION_RANGES,\n} ContinuousAggTableType;\n\nstatic Relation open_cagg_table(ContinuousAggTableType type, LOCKMODE lockmode);\nstatic void hypertable_invalidation_scan_init(ScanIterator *iterator, int32 hyper_id,\n\t\t\t\t\t\t\t\t\t\t\t  LOCKMODE lockmode);\nstatic HeapTuple create_materialization_ranges_tup(TupleDesc tupdesc, int32 cagg_hyper_id,\n\t\t\t\t\t\t\t\t\t\t\t\t   const InternalTimeRange range);\nstatic void check_materialization_ranges_overlap(const ContinuousAgg *cagg,\n\t\t\t\t\t\t\t\t\t\t\t\t const InternalTimeRange refresh_window);\nstatic void insert_new_cagg_materialization_ranges(const ContinuousAggInvalidationState *state,\n\t\t\t\t\t\t\t\t\t\t\t\t   const InternalTimeRange refresh_window,\n\t\t\t\t\t\t\t\t\t\t\t\t   int32 cagg_hyper_id);\nstatic bool save_invalidation_for_refresh(const ContinuousAggInvalidationState *state,\n\t\t\t\t\t\t\t\t\t\t  const Invalidation *invalidation);\nstatic void set_remainder_after_cut(Invalidation *remainder, int32 hyper_id,\n\t\t\t\t\t\t\t\t\tint64 lowest_modified_value, int64 greatest_modified_value);\nstatic void invalidation_entry_reset(Invalidation *entry);\nstatic void\ninvalidation_entry_set_from_hyper_invalidation(Invalidation *entry, const TupleInfo *ti,\n\t\t\t\t\t\t\t\t\t\t\t   int32 hyper_id, Oid dimtype,\n\t\t\t\t\t\t\t\t\t\t\t   const ContinuousAggBucketFunction *bucket_function);\nstatic void\ninvalidation_entry_set_from_cagg_invalidation(Invalidation *entry, const TupleInfo *ti, Oid dimtype,\n\t\t\t\t\t\t\t\t\t\t\t  const ContinuousAggBucketFunction *bucket_function);\nstatic bool invalidations_can_be_merged(const Invalidation *a, const Invalidation *b);\nstatic bool invalidation_entry_try_merge(Invalidation *entry, const Invalidation *newentry);\nstatic void insert_new_cagg_invalidation(const HypertableInvalidationState *state,\n\t\t\t\t\t\t\t\t\t\t const Invalidation *entry, int32 cagg_hyper_id);\nstatic void move_invalidations_from_hyper_to_cagg_log(const HypertableInvalidationState *state);\nstatic void cagg_invalidations_scan_by_hypertable_init(ScanIterator *iterator, int32 cagg_hyper_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   LOCKMODE lockmode);\nstatic Invalidation cut_cagg_invalidation(const ContinuousAggInvalidationState *state,\n\t\t\t\t\t\t\t\t\t\t  const InternalTimeRange *refresh_window,\n\t\t\t\t\t\t\t\t\t\t  const Invalidation *entry);\nstatic Invalidation cut_cagg_invalidation_and_compute_remainder(\n\tconst ContinuousAggInvalidationState *state, const InternalTimeRange *refresh_window,\n\tconst Invalidation *mergedentry, const Invalidation *current_remainder);\nstatic void clear_cagg_invalidations_for_refresh(const ContinuousAggInvalidationState *state,\n\t\t\t\t\t\t\t\t\t\t\t\t const InternalTimeRange *refresh_window,\n\t\t\t\t\t\t\t\t\t\t\t\t bool force);\nstatic void cagg_invalidation_state_init(ContinuousAggInvalidationState *state,\n\t\t\t\t\t\t\t\t\t\t const ContinuousAgg *cagg);\nstatic void cagg_invalidation_state_cleanup(const ContinuousAggInvalidationState *state);\n\nstatic Relation\nopen_cagg_table(ContinuousAggTableType type, LOCKMODE lockmode)\n{\n\tstatic const CatalogTable logmappings[] = {\n\t\t[HYPER_INVALIDATION_LOG] = CONTINUOUS_AGGS_HYPERTABLE_INVALIDATION_LOG,\n\t\t[CAGG_INVALIDATION_LOG] = CONTINUOUS_AGGS_MATERIALIZATION_INVALIDATION_LOG,\n\t\t[CAGG_MATERIALIZATION_RANGES] = CONTINUOUS_AGGS_MATERIALIZATION_RANGES,\n\t};\n\tCatalog *catalog = ts_catalog_get();\n\tOid relid = catalog_get_table_id(catalog, logmappings[type]);\n\n\treturn table_open(relid, lockmode);\n}\n\nstatic void\nhypertable_invalidation_scan_init(ScanIterator *iterator, int32 hyper_id, LOCKMODE lockmode)\n{\n\t*iterator = ts_scan_iterator_create(CONTINUOUS_AGGS_HYPERTABLE_INVALIDATION_LOG,\n\t\t\t\t\t\t\t\t\t\tlockmode,\n\t\t\t\t\t\t\t\t\t\tCurrentMemoryContext);\n\titerator->ctx.index = catalog_get_index(ts_catalog_get(),\n\t\t\t\t\t\t\t\t\t\t\tCONTINUOUS_AGGS_HYPERTABLE_INVALIDATION_LOG,\n\t\t\t\t\t\t\t\t\t\t\tCONTINUOUS_AGGS_HYPERTABLE_INVALIDATION_LOG_IDX);\n\tts_scan_iterator_scan_key_init(\n\t\titerator,\n\t\tAnum_continuous_aggs_hypertable_invalidation_log_idx_hypertable_id,\n\t\tBTEqualStrategyNumber,\n\t\tF_INT4EQ,\n\t\tInt32GetDatum(hyper_id));\n}\n\nHeapTuple\ncreate_invalidation_tup(const TupleDesc tupdesc, int32 cagg_hyper_id, int64 start, int64 end)\n{\n\tDatum values[Natts_continuous_aggs_materialization_invalidation_log] = { 0 };\n\tbool isnull[Natts_continuous_aggs_materialization_invalidation_log] = { false };\n\n\tvalues[AttrNumberGetAttrOffset(\n\t\tAnum_continuous_aggs_materialization_invalidation_log_materialization_id)] =\n\t\tInt32GetDatum(cagg_hyper_id);\n\tvalues[AttrNumberGetAttrOffset(\n\t\tAnum_continuous_aggs_materialization_invalidation_log_lowest_modified_value)] =\n\t\tInt64GetDatum(start);\n\tvalues[AttrNumberGetAttrOffset(\n\t\tAnum_continuous_aggs_materialization_invalidation_log_greatest_modified_value)] =\n\t\tInt64GetDatum(end);\n\n\treturn heap_form_tuple(tupdesc, values, isnull);\n}\n\n/*\n * Add an entry to the continuous aggregate invalidation log.\n */\nvoid\ninvalidation_cagg_log_add_entry(int32 cagg_hyper_id, int64 start, int64 end)\n{\n\tRelation rel = open_cagg_table(CAGG_INVALIDATION_LOG, RowExclusiveLock);\n\tCatalogSecurityContext sec_ctx;\n\tHeapTuple tuple;\n\n\tAssert(start <= end);\n\ttuple = create_invalidation_tup(RelationGetDescr(rel), cagg_hyper_id, start, end);\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_insert_only(rel, tuple);\n\tts_catalog_restore_user(&sec_ctx);\n\theap_freetuple(tuple);\n\ttable_close(rel, NoLock);\n}\n\nvoid\ninvalidation_hyper_log_add_entry(int32 hyper_id, int64 start, int64 end)\n{\n\tRelation rel = open_cagg_table(HYPER_INVALIDATION_LOG, RowExclusiveLock);\n\tCatalogSecurityContext sec_ctx;\n\tDatum values[Natts_continuous_aggs_hypertable_invalidation_log];\n\tbool nulls[Natts_continuous_aggs_hypertable_invalidation_log] = { false };\n\n\tAssert(start <= end);\n\tvalues[AttrNumberGetAttrOffset(\n\t\tAnum_continuous_aggs_hypertable_invalidation_log_hypertable_id)] = Int32GetDatum(hyper_id);\n\tvalues[AttrNumberGetAttrOffset(\n\t\tAnum_continuous_aggs_hypertable_invalidation_log_lowest_modified_value)] =\n\t\tInt64GetDatum(start);\n\tvalues[AttrNumberGetAttrOffset(\n\t\tAnum_continuous_aggs_hypertable_invalidation_log_greatest_modified_value)] =\n\t\tInt64GetDatum(end);\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_insert_values(rel, RelationGetDescr(rel), values, nulls);\n\tts_catalog_restore_user(&sec_ctx);\n\ttable_close(rel, NoLock);\n\telog(DEBUG1,\n\t\t \"hypertable log for hypertable %d added entry [\" INT64_FORMAT \", \" INT64_FORMAT \"]\",\n\t\t hyper_id,\n\t\t start,\n\t\t end);\n}\n\n/*\n * Invalidate one or more continuous aggregates.\n *\n * Add an invalidation in the given range. The invalidation is added either to\n * the hypertable invalidation log or the continuous aggregate invalidation\n * log depending on the type of the given hypertable. If the hypertable is a\n * \"raw\" hypertable (i.e., one that has one or more continuous aggregates), the\n * entry is added to the hypertable invalidation log and will invalidate all\n * the associated continuous aggregates. If the hypertable is instead an\n * materialized hypertable, the entry is added to the cagg invalidation log\n * and only invalidates the continuous aggregate owning that materialized\n * hypertable.\n */\nvoid\ncontinuous_agg_invalidate_raw_ht(const Hypertable *raw_ht, int64 start, int64 end)\n{\n\tAssert(raw_ht != NULL);\n\n\tinvalidation_hyper_log_add_entry(raw_ht->fd.id, start, end);\n}\n\nvoid\ncontinuous_agg_invalidate_mat_ht(const Hypertable *raw_ht, const Hypertable *mat_ht, int64 start,\n\t\t\t\t\t\t\t\t int64 end)\n{\n\tAssert((raw_ht != NULL) && (mat_ht != NULL));\n\n\tinvalidation_cagg_log_add_entry(mat_ht->fd.id, start, end);\n}\n\nstatic HeapTuple\ncreate_materialization_ranges_tup(TupleDesc tupdesc, int32 cagg_hyper_id,\n\t\t\t\t\t\t\t\t  const InternalTimeRange range)\n{\n\tDatum values[Natts_continuous_aggs_materialization_ranges] = { 0 };\n\tbool isnull[Natts_continuous_aggs_materialization_ranges] = { false };\n\n\tvalues[AttrNumberGetAttrOffset(\n\t\tAnum_continuous_aggs_materialization_ranges_materialization_id)] =\n\t\tInt32GetDatum(cagg_hyper_id);\n\tvalues[AttrNumberGetAttrOffset(\n\t\tAnum_continuous_aggs_materialization_ranges_lowest_modified_value)] =\n\t\tInt64GetDatum(range.start);\n\tvalues[AttrNumberGetAttrOffset(\n\t\tAnum_continuous_aggs_materialization_ranges_greatest_modified_value)] =\n\t\tInt64GetDatum(range.end);\n\n\treturn heap_form_tuple(tupdesc, values, isnull);\n}\n\n/*\n * Check if a new materialization range overlaps with any existing range for the\n * same materialization_id. two ranges [s1, e1) and [s2, e2) overlap iff\n * s1 < e2 AND e1 > s2.\n */\nstatic void\ncheck_materialization_ranges_overlap(const ContinuousAgg *cagg,\n\t\t\t\t\t\t\t\t\t const InternalTimeRange refresh_window)\n{\n\tScanIterator iterator = ts_scan_iterator_create(CONTINUOUS_AGGS_MATERIALIZATION_RANGES,\n\t\t\t\t\t\t\t\t\t\t\t\t\tAccessShareLock,\n\t\t\t\t\t\t\t\t\t\t\t\t\tCurrentMemoryContext);\n\n\titerator.ctx.index = catalog_get_index(ts_catalog_get(),\n\t\t\t\t\t\t\t\t\t\t   CONTINUOUS_AGGS_MATERIALIZATION_RANGES,\n\t\t\t\t\t\t\t\t\t\t   CONTINUOUS_AGGS_MATERIALIZATION_RANGES_IDX);\n\n\tts_scan_iterator_scan_key_init(\n\t\t&iterator,\n\t\tAnum_continuous_aggs_materialization_ranges_idx_materialization_id,\n\t\tBTEqualStrategyNumber,\n\t\tF_INT4EQ,\n\t\tInt32GetDatum(cagg->data.mat_hypertable_id));\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tbool isnull;\n\t\tint64 existing_start = DatumGetInt64(\n\t\t\tslot_getattr(ti->slot,\n\t\t\t\t\t\t Anum_continuous_aggs_materialization_ranges_lowest_modified_value,\n\t\t\t\t\t\t &isnull));\n\t\tAssert(!isnull);\n\t\tint64 existing_end = DatumGetInt64(\n\t\t\tslot_getattr(ti->slot,\n\t\t\t\t\t\t Anum_continuous_aggs_materialization_ranges_greatest_modified_value,\n\t\t\t\t\t\t &isnull));\n\t\tAssert(!isnull);\n\n\t\tif (refresh_window.start < existing_end && refresh_window.end > existing_start)\n\t\t{\n\t\t\tts_scan_iterator_close(&iterator);\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"materialization range [%s, %s) overlaps with existing range [%s, %s)\"\n\t\t\t\t\t\t\t\" in materialization_ranges table for continuous aggregate \"\n\t\t\t\t\t\t\t\"\\\"%s\\\".\\\"%s\\\"\",\n\t\t\t\t\t\t\tts_internal_to_time_string(refresh_window.start, cagg->partition_type),\n\t\t\t\t\t\t\tts_internal_to_time_string(refresh_window.end, cagg->partition_type),\n\t\t\t\t\t\t\tts_internal_to_time_string(existing_start, cagg->partition_type),\n\t\t\t\t\t\t\tts_internal_to_time_string(existing_end, cagg->partition_type),\n\t\t\t\t\t\t\tNameStr(cagg->data.user_view_schema),\n\t\t\t\t\t\t\tNameStr(cagg->data.user_view_name)),\n\t\t\t\t\t errdetail(\"A concurrent refresh is working on this range or a previously\"\n\t\t\t\t\t\t\t   \" failed refresh left an overlapping range in the\"\n\t\t\t\t\t\t\t   \" materialization_ranges table.\")));\n\t\t}\n\t}\n\n\tts_scan_iterator_close(&iterator);\n}\n\nstatic void\ninsert_new_cagg_materialization_ranges(const ContinuousAggInvalidationState *state,\n\t\t\t\t\t\t\t\t\t   const InternalTimeRange refresh_window, int32 cagg_hyper_id)\n{\n\tCatalogSecurityContext sec_ctx;\n\tTupleDesc tupdesc = RelationGetDescr(state->cagg_queue_rel);\n\tHeapTuple tuple;\n\n\tcheck_materialization_ranges_overlap(state->cagg, refresh_window);\n\n\ttuple = create_materialization_ranges_tup(tupdesc, cagg_hyper_id, refresh_window);\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_insert_only(state->cagg_queue_rel, tuple);\n\tts_catalog_restore_user(&sec_ctx);\n\theap_freetuple(tuple);\n}\n\ntypedef enum InvalidationResult\n{\n\tINVAL_NOMATCH,\n\tINVAL_DELETE,\n\tINVAL_CUT,\n} InvalidationResult;\n\nstatic inline bool\nIsValidInvalidation(const Invalidation *invalidation)\n{\n\tAssert(invalidation->lowest_modified_value <= invalidation->greatest_modified_value);\n\treturn invalidation->hyper_id != INVALID_HYPERTABLE_ID &&\n\t\t   invalidation->lowest_modified_value <= invalidation->greatest_modified_value;\n}\n\nstatic bool\nsave_invalidation_for_refresh(const ContinuousAggInvalidationState *state,\n\t\t\t\t\t\t\t  const Invalidation *invalidation)\n{\n\tif (!IsValidInvalidation(invalidation))\n\t\treturn false;\n\n\tint32 cagg_hyper_id = state->cagg->data.mat_hypertable_id;\n\tTupleDesc tupdesc = RelationGetDescr(state->cagg_log_rel);\n\tHeapTuple refresh_tup = create_invalidation_tup(tupdesc,\n\t\t\t\t\t\t\t\t\t\t\t\t\tcagg_hyper_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\tinvalidation->lowest_modified_value,\n\t\t\t\t\t\t\t\t\t\t\t\t\tinvalidation->greatest_modified_value);\n\ttuplestore_puttuple(state->invalidations, refresh_tup);\n\theap_freetuple(refresh_tup);\n\n\tInternalTimeRange refresh_window = {\n\t\t.type = state->cagg->partition_type,\n\t\t.start = invalidation->lowest_modified_value,\n\t\t/* Invalidations are inclusive at the end, while refresh windows aren't, so add one to the\n\t\t   end of the invalidated region */\n\t\t.end = ts_time_saturating_add(invalidation->greatest_modified_value,\n\t\t\t\t\t\t\t\t\t  1,\n\t\t\t\t\t\t\t\t\t  state->cagg->partition_type),\n\t};\n\n\tInternalTimeRange bucketed_refresh_window =\n\t\tcompute_circumscribed_bucketed_refresh_window(state->cagg,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  &refresh_window,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  state->cagg->bucket_function);\n\n\tinsert_new_cagg_materialization_ranges(state, bucketed_refresh_window, cagg_hyper_id);\n\n\treturn true;\n}\n\nstatic void\nset_remainder_after_cut(Invalidation *remainder, int32 hyper_id, int64 lowest_modified_value,\n\t\t\t\t\t\tint64 greatest_modified_value)\n{\n\tMemSet(remainder, 0, sizeof(*remainder));\n\tremainder->hyper_id = hyper_id;\n\tremainder->lowest_modified_value = lowest_modified_value;\n\tremainder->greatest_modified_value = greatest_modified_value;\n}\n\n/*\n * Try to cut an invalidation against the refresh window.\n *\n * If an invalidation entry overlaps with the refresh window, it needs\n * additional processing: it is either cut, deleted, or left unmodified.\n *\n * The part(s) of the invalidation that are outside the refresh window after\n * the cut will remain in the log. The part of the invalidation that fits\n * within the window is returned as the \"remainder\".\n *\n * Note that the refresh window is exclusive in the end while invalidations\n * are inclusive.\n */\nstatic InvalidationResult\ncut_invalidation_along_refresh_window(const ContinuousAggInvalidationState *state,\n\t\t\t\t\t\t\t\t\t  const Invalidation *invalidation,\n\t\t\t\t\t\t\t\t\t  const InternalTimeRange *refresh_window,\n\t\t\t\t\t\t\t\t\t  Invalidation *remainder)\n{\n\tint32 cagg_hyper_id = state->cagg->data.mat_hypertable_id;\n\tTupleDesc tupdesc = RelationGetDescr(state->cagg_log_rel);\n\tInvalidationResult result = INVAL_NOMATCH;\n\tHeapTuple lower = NULL;\n\tHeapTuple upper = NULL;\n\n\tAssert(remainder != NULL);\n\n\t/* Entry is completely enclosed by the refresh window */\n\tif (invalidation->lowest_modified_value >= refresh_window->start &&\n\t\tinvalidation->greatest_modified_value < refresh_window->end)\n\t{\n\t\t/*\n\t\t * Entry completely enclosed so can be deleted:\n\t\t *\n\t\t * [---------------)\n\t\t *     [+++++]\n\t\t */\n\n\t\tresult = INVAL_DELETE;\n\t\tset_remainder_after_cut(remainder,\n\t\t\t\t\t\t\t\tcagg_hyper_id,\n\t\t\t\t\t\t\t\tinvalidation->lowest_modified_value,\n\t\t\t\t\t\t\t\tinvalidation->greatest_modified_value);\n\t}\n\telse\n\t{\n\t\tif (invalidation->lowest_modified_value < refresh_window->start &&\n\t\t\tinvalidation->greatest_modified_value >= refresh_window->start)\n\t\t{\n\t\t\t/*\n\t\t\t * Need to cut in right end:\n\t\t\t *\n\t\t\t *     [------)\n\t\t\t * [++++++]\n\t\t\t *\n\t\t\t * [++]\n\t\t\t */\n\t\t\tlower = create_invalidation_tup(tupdesc,\n\t\t\t\t\t\t\t\t\t\t\tcagg_hyper_id,\n\t\t\t\t\t\t\t\t\t\t\tinvalidation->lowest_modified_value,\n\t\t\t\t\t\t\t\t\t\t\trefresh_window->start - 1);\n\t\t\tset_remainder_after_cut(remainder,\n\t\t\t\t\t\t\t\t\tcagg_hyper_id,\n\t\t\t\t\t\t\t\t\trefresh_window->start,\n\t\t\t\t\t\t\t\t\t/* Refresh window not exclusive at end */\n\t\t\t\t\t\t\t\t\tMIN(refresh_window->end - 1,\n\t\t\t\t\t\t\t\t\t\tinvalidation->greatest_modified_value));\n\t\t\tresult = INVAL_CUT;\n\t\t}\n\n\t\tif (invalidation->lowest_modified_value < refresh_window->end &&\n\t\t\tinvalidation->greatest_modified_value >= refresh_window->end)\n\t\t{\n\t\t\t/*\n\t\t\t * If the invalidation is already cut on the left above, the reminder is set and\n\t\t\t * will be reset here. The assert prevents from losing information from the reminder.\n\t\t\t */\n\t\t\tAssert((result == INVAL_CUT &&\n\t\t\t\t\tremainder->lowest_modified_value == refresh_window->start) ||\n\t\t\t\t   result == INVAL_NOMATCH);\n\n\t\t\t/*\n\t\t\t * Need to cut in left end:\n\t\t\t *\n\t\t\t * [------)\n\t\t\t *    [++++++++]\n\t\t\t *\n\t\t\t *        [++++]\n\t\t\t */\n\t\t\tupper = create_invalidation_tup(tupdesc,\n\t\t\t\t\t\t\t\t\t\t\tcagg_hyper_id,\n\t\t\t\t\t\t\t\t\t\t\trefresh_window->end,\n\t\t\t\t\t\t\t\t\t\t\tinvalidation->greatest_modified_value);\n\t\t\tset_remainder_after_cut(remainder,\n\t\t\t\t\t\t\t\t\tcagg_hyper_id,\n\t\t\t\t\t\t\t\t\tMAX(invalidation->lowest_modified_value, refresh_window->start),\n\t\t\t\t\t\t\t\t\t/* Refresh window exclusive at end */\n\t\t\t\t\t\t\t\t\trefresh_window->end - 1);\n\t\t\tresult = INVAL_CUT;\n\t\t}\n\t}\n\n\t/* Insert any modifications into the cagg invalidation log */\n\tif (result == INVAL_CUT)\n\t{\n\t\tCatalogSecurityContext sec_ctx;\n\t\tHeapTuple other_range = NULL;\n\n\t\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\n\t\t/* We'd like to do one update (unless the TID is not set), and\n\t\t * optionally one insert. We pick one of the tuples for an update, and\n\t\t * the other one will be an insert. */\n\t\tif (lower || upper)\n\t\t{\n\t\t\tHeapTuple tup = lower ? lower : upper;\n\t\t\tother_range = lower ? upper : lower;\n\n\t\t\t/* If the TID is set, we are updating an existing tuple, i.e., we\n\t\t\t * are processing and entry in the cagg log itself. Otherwise, we\n\t\t\t * are processing the hypertable invalidation log and need to\n\t\t\t * insert a new entry. */\n\t\t\tif (ItemPointerIsValid(&invalidation->tid))\n\t\t\t{\n\t\t\t\tItemPointerData tid = invalidation->tid;\n\t\t\t\tts_catalog_update_tid_only(state->cagg_log_rel, &tid, tup);\n\t\t\t}\n\t\t\telse\n\t\t\t\tts_catalog_insert_only(state->cagg_log_rel, tup);\n\n\t\t\theap_freetuple(tup);\n\t\t}\n\n\t\tif (other_range)\n\t\t{\n\t\t\tts_catalog_insert_only(state->cagg_log_rel, other_range);\n\t\t\theap_freetuple(other_range);\n\t\t}\n\n\t\tts_catalog_restore_user(&sec_ctx);\n\t}\n\n\treturn result;\n}\n\nstatic void\ninvalidation_entry_reset(Invalidation *entry)\n{\n\tMemSet(entry, 0, sizeof(Invalidation));\n}\n\n/*\n * Expand an invalidation to bucket boundaries.\n *\n * Since a refresh always materializes full buckets, we can safely expand an\n * invalidation to bucket boundaries and in the process merge a lot more\n * invalidations.\n */\nvoid\ninvalidation_expand_to_bucket_boundaries(Invalidation *inv, Oid time_type_oid,\n\t\t\t\t\t\t\t\t\t\t const ContinuousAggBucketFunction *bucket_function)\n{\n\tconst int64 time_dimension_min = ts_time_get_min(time_type_oid);\n\tconst int64 time_dimension_max = ts_time_get_max(time_type_oid);\n\tint64 min_bucket_start;\n\tint64 max_bucket_end;\n\n\tif (bucket_function->bucket_fixed_interval == false)\n\t{\n\t\tts_compute_circumscribed_bucketed_refresh_window_variable(&inv->lowest_modified_value,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  &inv->greatest_modified_value,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  bucket_function);\n\t\t/* ts_compute_circumscribed_bucketed_refresh_window_variable returns the start of the\n\t\t * next bucket as the end (exclusive). Since invalidations are inclusive at both ends,\n\t\t * subtract 1 to get the last value of the current bucket (inclusive).\n\t\t * Don't adjust infinity values. */\n\t\tif (inv->greatest_modified_value != INVAL_POS_INFINITY &&\n\t\t\tinv->greatest_modified_value != INVAL_NEG_INFINITY)\n\t\t{\n\t\t\tinv->greatest_modified_value = int64_saturating_sub(inv->greatest_modified_value, 1);\n\t\t}\n\t\treturn;\n\t}\n\n\tint64 bucket_width = ts_continuous_agg_fixed_bucket_width(bucket_function);\n\tAssert(bucket_width > 0);\n\n\tNullableDatum offset = INIT_NULL_DATUM;\n\tNullableDatum origin = INIT_NULL_DATUM;\n\tfill_bucket_offset_origin(bucket_function, time_type_oid, &offset, &origin);\n\n\t/* Compute the start of the \"first\" bucket for the type. The min value\n\t * must be at the start of the \"first\" bucket or somewhere in the\n\t * bucket. If the min value falls on the exact start of the bucket we are\n\t * good. Otherwise, we need to move to the next full bucket. */\n\tmin_bucket_start = ts_time_saturating_add(time_dimension_min, bucket_width - 1, time_type_oid);\n\tmin_bucket_start = ts_time_bucket_by_type(bucket_width, min_bucket_start, time_type_oid);\n\n\t/* Compute the end of the \"last\" bucket for the time type. Remember that\n\t * invalidations are inclusive, so the \"greatest\" value should be the last\n\t * value of the last full bucket. Either the max value is already the last\n\t * value of the last bucket, or we need to return the last value of the\n\t * previous full bucket.  */\n\tmax_bucket_end = ts_time_bucket_by_type(bucket_width, time_dimension_max, time_type_oid);\n\n\t/* Check if the max value was already the last value of the last bucket */\n\tif (ts_time_saturating_add(max_bucket_end, bucket_width - 1, time_type_oid) ==\n\t\ttime_dimension_max)\n\t{\n\t\tmax_bucket_end = time_dimension_max;\n\t}\n\telse\n\t{\n\t\t/* The last bucket was partial. To get the end of previous bucket, we\n\t\t * need to move one step down from the partial last bucket. */\n\t\tmax_bucket_end = ts_time_saturating_sub(max_bucket_end, 1, time_type_oid);\n\t}\n\n\tif (inv->lowest_modified_value < min_bucket_start)\n\t\t/* Below the min bucket, so treat as invalid to -infinity. */\n\t\tinv->lowest_modified_value = INVAL_NEG_INFINITY;\n\telse if (inv->lowest_modified_value > max_bucket_end)\n\t\t/* Above the max bucket, so treat as invalid to +infinity. */\n\t\tinv->lowest_modified_value = INVAL_POS_INFINITY;\n\telse\n\t\tinv->lowest_modified_value = ts_time_bucket_by_type_extended(bucket_width,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t inv->lowest_modified_value,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t time_type_oid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t offset,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t origin);\n\n\tif (inv->greatest_modified_value < min_bucket_start)\n\t\t/* Below the min bucket, so treat as invalid to -infinity. */\n\t\tinv->greatest_modified_value = INVAL_NEG_INFINITY;\n\telse if (inv->greatest_modified_value > max_bucket_end)\n\t\t/* Above the max bucket, so treat as invalid to +infinity. */\n\t\tinv->greatest_modified_value = INVAL_POS_INFINITY;\n\telse\n\t{\n\t\tinv->greatest_modified_value = ts_time_bucket_by_type_extended(bucket_width,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   inv->greatest_modified_value,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   time_type_oid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   offset,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   origin);\n\t\tinv->greatest_modified_value =\n\t\t\tts_time_saturating_add(inv->greatest_modified_value, bucket_width - 1, time_type_oid);\n\t}\n}\n\n/*\n * Macro to set an Invalidation from a tuple. The tuple can either have the\n * format of the hypertable invalidation log or the continuous aggregate\n * invalidation log (as determined by the type parameter).\n */\n#define INVALIDATION_ENTRY_SET(entry, ti, hypertable_id, type)                                     \\\n\tdo                                                                                             \\\n\t{                                                                                              \\\n\t\tbool should_free;                                                                          \\\n\t\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);                    \\\n\t\ttype form;                                                                                 \\\n\t\tform = (type) GETSTRUCT(tuple);                                                            \\\n\t\t(entry)->hyper_id = form->hypertable_id;                                                   \\\n\t\t(entry)->lowest_modified_value = form->lowest_modified_value;                              \\\n\t\t(entry)->greatest_modified_value = form->greatest_modified_value;                          \\\n\t\t(entry)->is_modified = false;                                                              \\\n\t\tItemPointerCopy(&tuple->t_self, &(entry)->tid);                                            \\\n                                                                                                   \\\n\t\tif (should_free)                                                                           \\\n\t\t\theap_freetuple(tuple);                                                                 \\\n\t} while (0);\n\nstatic void\ninvalidation_entry_set_from_hyper_invalidation(Invalidation *entry, const TupleInfo *ti,\n\t\t\t\t\t\t\t\t\t\t\t   int32 hyper_id, Oid dimtype,\n\t\t\t\t\t\t\t\t\t\t\t   const ContinuousAggBucketFunction *bucket_function)\n{\n\tINVALIDATION_ENTRY_SET(entry,\n\t\t\t\t\t\t   ti,\n\t\t\t\t\t\t   hypertable_id,\n\t\t\t\t\t\t   Form_continuous_aggs_hypertable_invalidation_log);\n\t/* Since hypertable invalidations are moved to the continuous aggregate\n\t * invalidation log, a different hypertable ID must be set (the ID of the\n\t * materialized hypertable). */\n\tentry->hyper_id = hyper_id;\n\tinvalidation_expand_to_bucket_boundaries(entry, dimtype, bucket_function);\n}\n\nstatic void\ninvalidation_entry_set_from_cagg_invalidation(Invalidation *entry, const TupleInfo *ti, Oid dimtype,\n\t\t\t\t\t\t\t\t\t\t\t  const ContinuousAggBucketFunction *bucket_function)\n{\n\tINVALIDATION_ENTRY_SET(entry,\n\t\t\t\t\t\t   ti,\n\t\t\t\t\t\t   materialization_id,\n\t\t\t\t\t\t   Form_continuous_aggs_materialization_invalidation_log);\n\n\t/* It isn't strictly necessary to expand the invalidation to bucket\n\t * boundaries here since all invalidations were already expanded when\n\t * copied from the hypertable invalidation log. However, since\n\t * invalidation expansion wasn't implemented in early 2.0.x versions of\n\t * the extension, there might be unexpanded entries in the cagg\n\t * invalidation log for some users. Therefore we try to expand\n\t * invalidation entries also here, although in most cases it would do\n\t * nothing. */\n\tinvalidation_expand_to_bucket_boundaries(entry, dimtype, bucket_function);\n}\n\n/*\n * Check if two invalidations can be merged into one.\n *\n * Since invalidations are inclusive in both ends, two adjacent invalidations\n * can be merged.\n */\nstatic bool\ninvalidations_can_be_merged(const Invalidation *a, const Invalidation *b)\n{\n\t/* To account for adjacency, expand one window 1 step in each\n\t * direction. This makes adjacent invalidations overlapping. */\n\tint64 a_start = int64_saturating_sub(a->lowest_modified_value, 1);\n\tint64 a_end = int64_saturating_add(a->greatest_modified_value, 1);\n\n\treturn a_end >= b->lowest_modified_value && a_start <= b->greatest_modified_value;\n}\n\n/*\n * Try to merge two invalidations into one.\n *\n * Returns true if the invalidations were merged, otherwise false.\n *\n * Given that we scan ordered on lowest_modified_value, the previous and\n * current invalidation can overlap in two ways (generalized):\n *\n * |------|\n *    |++++++++|\n *\n * |-------------|\n *    |++++++++|\n *\n * The closest non-overlapping case is (note that adjacent invalidations can\n * be merged since they are inclusive in both ends):\n *\n * |--|\n *     |++++++++|\n *\n */\nstatic bool\ninvalidation_entry_try_merge(Invalidation *entry, const Invalidation *newentry)\n{\n\tif (!IsValidInvalidation(newentry))\n\t\treturn false;\n\n\t/* Quick exit if no overlap */\n\tif (!invalidations_can_be_merged(entry, newentry))\n\t\treturn false;\n\n\t/* Check if the new entry expands beyond the old one (first case above) */\n\tif (entry->greatest_modified_value < newentry->greatest_modified_value)\n\t{\n\t\tentry->greatest_modified_value = newentry->greatest_modified_value;\n\t\tentry->is_modified = true;\n\t}\n\n\treturn true;\n}\n\nstatic void\ninsert_new_cagg_invalidation(const HypertableInvalidationState *state, const Invalidation *entry,\n\t\t\t\t\t\t\t int32 cagg_hyper_id)\n{\n\tCatalogSecurityContext sec_ctx;\n\tTupleDesc tupdesc = RelationGetDescr(state->cagg_log_rel);\n\tHeapTuple tuple = create_invalidation_tup(tupdesc,\n\t\t\t\t\t\t\t\t\t\t\t  cagg_hyper_id,\n\t\t\t\t\t\t\t\t\t\t\t  entry->lowest_modified_value,\n\t\t\t\t\t\t\t\t\t\t\t  entry->greatest_modified_value);\n\n\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\tts_catalog_insert_only(state->cagg_log_rel, tuple);\n\tts_catalog_restore_user(&sec_ctx);\n\theap_freetuple(tuple);\n}\n\n/*\n * Process invalidations in the hypertable invalidation log.\n *\n * Copy and delete all entries from the hypertable invalidation log.\n *\n * Note that each entry gets one copy per continuous aggregate in the cagg\n * invalidation log (unless it was merged or matched the refresh\n * window). These copied entries are later used to track invalidations across\n * refreshes on a per-cagg basis.\n *\n * After this function has run, there are no entries left in the hypertable\n * invalidation log.\n */\nstatic void\nmove_invalidations_from_hyper_to_cagg_log(const HypertableInvalidationState *state)\n{\n\tconst ContinuousAggInfo *all_caggs = state->all_caggs;\n\tint32 hyper_id = state->hypertable_id;\n\tint32 last_cagg_hyper_id;\n\tListCell *lc1, *lc2;\n\n\tlast_cagg_hyper_id = llast_int(all_caggs->mat_hypertable_ids);\n\n\t/* We use a per-tuple memory context in the scan loop since we could be\n\t * processing a lot of invalidations (basically an unbounded\n\t * amount). Initialize it here by resetting it. */\n\tMemoryContextReset(state->per_tuple_mctx);\n\n\t/*\n\t * Looping over all continuous aggregates in the outer loop ensures all\n\t * tuples for a specific continuous aggregate is inserted consecutively in\n\t * the cagg invalidation log. This creates better locality for scanning\n\t * the invalidations later.\n\t */\n\tforboth (lc1, all_caggs->mat_hypertable_ids, lc2, all_caggs->bucket_functions)\n\t{\n\t\tint32 cagg_hyper_id = lfirst_int(lc1);\n\t\tconst ContinuousAggBucketFunction *bucket_function = lfirst(lc2);\n\n\t\tInvalidation mergedentry;\n\t\tScanIterator iterator;\n\n\t\tinvalidation_entry_reset(&mergedentry);\n\t\thypertable_invalidation_scan_init(&iterator, hyper_id, RowExclusiveLock);\n\t\titerator.ctx.snapshot = state->snapshot;\n\n\t\t/* Scan all invalidations */\n\t\tts_scanner_foreach(&iterator)\n\t\t{\n\t\t\tTupleInfo *ti;\n\t\t\tMemoryContext oldmctx;\n\t\t\tInvalidation logentry;\n\n\t\t\toldmctx = MemoryContextSwitchTo(state->per_tuple_mctx);\n\t\t\tti = ts_scan_iterator_tuple_info(&iterator);\n\n\t\t\tinvalidation_entry_set_from_hyper_invalidation(&logentry,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   ti,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   cagg_hyper_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   state->dimtype,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   bucket_function);\n\n\t\t\tif (!IsValidInvalidation(&mergedentry))\n\t\t\t{\n\t\t\t\tmergedentry = logentry;\n\t\t\t\tmergedentry.hyper_id = cagg_hyper_id;\n\t\t\t}\n\t\t\telse if (!invalidation_entry_try_merge(&mergedentry, &logentry))\n\t\t\t{\n\t\t\t\tinsert_new_cagg_invalidation(state, &mergedentry, cagg_hyper_id);\n\t\t\t\tmergedentry = logentry;\n\t\t\t}\n\n\t\t\tif (cagg_hyper_id == last_cagg_hyper_id)\n\t\t\t{\n\t\t\t\tCatalogSecurityContext sec_ctx;\n\n\t\t\t\t/* The invalidation has been processed for all caggs, so the\n\t\t\t\t * only thing left is to delete it from the source hypertable\n\t\t\t\t * invalidation log. */\n\t\t\t\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\t\t\t\tts_catalog_delete_tid_only(ti->scanrel, &logentry.tid);\n\t\t\t\tts_catalog_restore_user(&sec_ctx);\n\t\t\t}\n\n\t\t\tMemoryContextSwitchTo(oldmctx);\n\t\t\tMemoryContextReset(state->per_tuple_mctx);\n\t\t}\n\n\t\tts_scan_iterator_close(&iterator);\n\n\t\t/* Handle the last merged invalidation */\n\t\tif (IsValidInvalidation(&mergedentry))\n\t\t\tinsert_new_cagg_invalidation(state, &mergedentry, cagg_hyper_id);\n\t}\n}\n\nstatic void\ncagg_invalidations_scan_by_hypertable_init(ScanIterator *iterator, int32 cagg_hyper_id,\n\t\t\t\t\t\t\t\t\t\t   LOCKMODE lockmode)\n{\n\t*iterator = ts_scan_iterator_create(CONTINUOUS_AGGS_MATERIALIZATION_INVALIDATION_LOG,\n\t\t\t\t\t\t\t\t\t\tlockmode,\n\t\t\t\t\t\t\t\t\t\tCurrentMemoryContext);\n\titerator->ctx.index = catalog_get_index(ts_catalog_get(),\n\t\t\t\t\t\t\t\t\t\t\tCONTINUOUS_AGGS_MATERIALIZATION_INVALIDATION_LOG,\n\t\t\t\t\t\t\t\t\t\t\tCONTINUOUS_AGGS_MATERIALIZATION_INVALIDATION_LOG_IDX);\n\tts_scan_iterator_scan_key_init(\n\t\titerator,\n\t\tAnum_continuous_aggs_materialization_invalidation_log_idx_materialization_id,\n\t\tBTEqualStrategyNumber,\n\t\tF_INT4EQ,\n\t\tInt32GetDatum(cagg_hyper_id));\n}\n\n/*\n * Cut an invalidation and return the part, if any, that remains within the\n * refresh window.\n */\nstatic Invalidation\ncut_cagg_invalidation(const ContinuousAggInvalidationState *state,\n\t\t\t\t\t  const InternalTimeRange *refresh_window, const Invalidation *entry)\n{\n\tInvalidationResult result;\n\tInvalidation remainder;\n\tItemPointerData tid = entry->tid;\n\n\tinvalidation_entry_reset(&remainder);\n\n\tresult = cut_invalidation_along_refresh_window(state, entry, refresh_window, &remainder);\n\n\tswitch (result)\n\t{\n\t\tcase INVAL_NOMATCH:\n\t\t\t/* If no cutting was done (i.e., the invalidation was outside the\n\t\t\t * refresh window), but the invalidation was previously merged\n\t\t\t * (expanded) with another invalidation, then we still need to\n\t\t\t * update it. */\n\t\t\tif (entry->is_modified)\n\t\t\t{\n\t\t\t\tHeapTuple tuple = create_invalidation_tup(RelationGetDescr(state->cagg_log_rel),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  entry->hyper_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  entry->lowest_modified_value,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  entry->greatest_modified_value);\n\t\t\t\tts_catalog_update_tid_only(state->cagg_log_rel, &tid, tuple);\n\t\t\t\theap_freetuple(tuple);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase INVAL_DELETE:\n\t\t\tts_catalog_delete_tid_only(state->cagg_log_rel, &tid);\n\t\t\tbreak;\n\t\tcase INVAL_CUT:\n\t\t\t/* Nothing to do */\n\t\t\tbreak;\n\t}\n\n\treturn remainder;\n}\n\nstatic Invalidation\ncut_cagg_invalidation_and_compute_remainder(const ContinuousAggInvalidationState *state,\n\t\t\t\t\t\t\t\t\t\t\tconst InternalTimeRange *refresh_window,\n\t\t\t\t\t\t\t\t\t\t\tconst Invalidation *mergedentry,\n\t\t\t\t\t\t\t\t\t\t\tconst Invalidation *current_remainder)\n{\n\tInvalidation new_remainder;\n\tInvalidation remainder = *current_remainder;\n\n\t/* The previous and current invalidation could not be merged. We\n\t * need to cut the prev invalidation against the refresh window */\n\tnew_remainder = cut_cagg_invalidation(state, refresh_window, mergedentry);\n\n\tif (!IsValidInvalidation(&remainder))\n\t\tremainder = new_remainder;\n\telse if (IsValidInvalidation(&new_remainder) &&\n\t\t\t !invalidation_entry_try_merge(&remainder, &new_remainder))\n\t{\n\t\tsave_invalidation_for_refresh(state, &remainder);\n\t\tremainder = new_remainder;\n\t}\n\n\treturn remainder;\n}\n\n/*\n * Clear all cagg invalidations that match a refresh window.\n *\n * This function clears all invalidations in the cagg invalidation log that\n * matches a window, and adds the invalidation segments covered by the window\n * to the invalidation store (tuple store) in the state argument. The\n * remaining segments that are added to the invalidation store are regions\n * that require materialization.\n *\n * An invalidation entry that gets processed is either completely enclosed\n * (covered) by the refresh window, or it partially overlaps. In the former\n * case, the invalidation entry is removed and for the latter case it is\n * cut. Thus, an entry can either disappear, reduce in size, or be cut in two.\n *\n * Note that the refresh window is inclusive at the start and exclusive at the\n * end. This function also assumes that invalidations are scanned in order of\n * lowest_modified_value.\n */\nstatic void\nclear_cagg_invalidations_for_refresh(const ContinuousAggInvalidationState *state,\n\t\t\t\t\t\t\t\t\t const InternalTimeRange *refresh_window, bool force)\n{\n\tScanIterator iterator;\n\tInvalidation mergedentry;\n\tInvalidation remainder;\n\n\tinvalidation_entry_reset(&mergedentry);\n\tinvalidation_entry_reset(&remainder);\n\tcagg_invalidations_scan_by_hypertable_init(&iterator,\n\t\t\t\t\t\t\t\t\t\t\t   state->cagg->data.mat_hypertable_id,\n\t\t\t\t\t\t\t\t\t\t\t   RowExclusiveLock);\n\titerator.ctx.data = (void *) &state;\n\titerator.ctx.snapshot = state->snapshot;\n\tScanTupLock scantuplock = {\n\t\t.waitpolicy = LockWaitBlock,\n\t\t.lockmode = LockTupleExclusive,\n\t\t.lockflags = TUPLE_LOCK_FLAG_FIND_LAST_VERSION,\n\t};\n\titerator.ctx.tuplock = &scantuplock;\n\titerator.ctx.flags = SCANNER_F_KEEPLOCK;\n\n\tMemoryContextReset(state->per_tuple_mctx);\n\n\t/*\n\t * Force refresh within the entire window.\n\t *\n\t * At this point the refresh window has already been inscribed to bucket\n\t * boundaries by the caller, so [start, end) covers exactly the set of\n\t * buckets to materialize.\n\t *\n\t * Synthesize an invalidation covering [start, end-1] (inclusive) and use\n\t * it as the initial remainder.  We use end-1 because greatest_modified_value\n\t * is inclusive while refresh_window->end is exclusive.\n\t *\n\t * By seeding the remainder with this forced entry, any cagg invalidation\n\t * log entries whose inside parts overlap the window will be merged into it\n\t * in the scan loop below rather than being saved as separate entries.\n\t * The single merged remainder is then saved once at the end of this function.\n\t */\n\tif (force)\n\t{\n\t\tremainder.hyper_id = state->cagg->data.mat_hypertable_id;\n\t\tremainder.lowest_modified_value = refresh_window->start;\n\t\tremainder.greatest_modified_value =\n\t\t\tts_time_saturating_sub(refresh_window->end, 1, refresh_window->type);\n\t\tremainder.is_modified = false;\n\t\tItemPointerSetInvalid(&remainder.tid);\n\t}\n\n\t/* Process all invalidations for the continuous aggregate */\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tMemoryContext oldmctx;\n\t\tInvalidation logentry;\n\n\t\toldmctx = MemoryContextSwitchTo(state->per_tuple_mctx);\n\t\tinvalidation_entry_set_from_cagg_invalidation(&logentry,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  ti,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  state->cagg->partition_type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  state->cagg->bucket_function);\n\n\t\tif (!IsValidInvalidation(&mergedentry))\n\t\t\tmergedentry = logentry;\n\t\telse if (invalidation_entry_try_merge(&mergedentry, &logentry))\n\t\t{\n\t\t\t/*\n\t\t\t * The previous and current invalidation were merged into\n\t\t\t * one entry (i.e., they overlapped or were adjacent).\n\t\t\t */\n\t\t\tts_catalog_delete_tid_only(state->cagg_log_rel, &logentry.tid);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tremainder = cut_cagg_invalidation_and_compute_remainder(state,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\trefresh_window,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t&mergedentry,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t&remainder);\n\t\t\tmergedentry = logentry;\n\t\t}\n\n\t\tMemoryContextSwitchTo(oldmctx);\n\t\tMemoryContextReset(state->per_tuple_mctx);\n\t}\n\n\tts_scan_iterator_close(&iterator);\n\n\t/* Handle the last (merged) invalidation */\n\tif (IsValidInvalidation(&mergedentry))\n\t\tremainder = cut_cagg_invalidation_and_compute_remainder(state,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\trefresh_window,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t&mergedentry,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t&remainder);\n\n\t/* Handle the last (merged) remainder */\n\tsave_invalidation_for_refresh(state, &remainder);\n}\n\nstatic void\ncagg_invalidation_state_init(ContinuousAggInvalidationState *state, const ContinuousAgg *cagg)\n{\n\tstate->cagg = cagg;\n\tstate->cagg_log_rel = open_cagg_table(CAGG_INVALIDATION_LOG, RowExclusiveLock);\n\tstate->cagg_queue_rel = open_cagg_table(CAGG_MATERIALIZATION_RANGES, RowExclusiveLock);\n\tstate->per_tuple_mctx = AllocSetContextCreate(CurrentMemoryContext,\n\t\t\t\t\t\t\t\t\t\t\t\t  \"Materialization invalidations\",\n\t\t\t\t\t\t\t\t\t\t\t\t  ALLOCSET_DEFAULT_SIZES);\n\tstate->snapshot = RegisterSnapshot(GetTransactionSnapshot());\n}\n\nstatic void\ncagg_invalidation_state_cleanup(const ContinuousAggInvalidationState *state)\n{\n\ttable_close(state->cagg_log_rel, NoLock);\n\ttable_close(state->cagg_queue_rel, NoLock);\n\tUnregisterSnapshot(state->snapshot);\n\tMemoryContextDelete(state->per_tuple_mctx);\n}\n\nstatic void\nhypertable_invalidation_state_init(HypertableInvalidationState *state, int32 hypertable_id,\n\t\t\t\t\t\t\t\t   Oid dimtype, const ContinuousAggInfo *all_caggs)\n{\n\tstate->hypertable_id = hypertable_id;\n\tstate->dimtype = dimtype;\n\tstate->all_caggs = all_caggs;\n\tstate->cagg_log_rel = open_cagg_table(CAGG_INVALIDATION_LOG, RowExclusiveLock);\n\tstate->per_tuple_mctx = AllocSetContextCreate(CurrentMemoryContext,\n\t\t\t\t\t\t\t\t\t\t\t\t  \"Hypertable invalidations\",\n\t\t\t\t\t\t\t\t\t\t\t\t  ALLOCSET_DEFAULT_SIZES);\n\tstate->snapshot = RegisterSnapshot(GetTransactionSnapshot());\n}\n\nstatic void\nhypertable_invalidation_state_cleanup(const HypertableInvalidationState *state)\n{\n\ttable_close(state->cagg_log_rel, NoLock);\n\tUnregisterSnapshot(state->snapshot);\n\tMemoryContextDelete(state->per_tuple_mctx);\n}\n\n/*\n * Move invalidations for a single hypertable from hypertable invalidation log\n * to materialization invalidation log. This will move *all* hypertable\n * invalidations for the hypertable to the associated continuous aggregates.\n */\nvoid\ninvalidation_process_hypertable_log(int32 hypertable_id, Oid dimtype)\n{\n\tHypertableInvalidationState state;\n\tconst ContinuousAggInfo all_caggs = ts_continuous_agg_get_all_caggs_info(hypertable_id);\n\n\thypertable_invalidation_state_init(&state, hypertable_id, dimtype, &all_caggs);\n\tmove_invalidations_from_hyper_to_cagg_log(&state);\n\thypertable_invalidation_state_cleanup(&state);\n}\n\nInvalidationStore *\ninvalidation_process_cagg_log(const ContinuousAgg *cagg, const InternalTimeRange *refresh_window,\n\t\t\t\t\t\t\t  long max_materializations, ContinuousAggRefreshContext context,\n\t\t\t\t\t\t\t  bool force)\n{\n\tContinuousAggInvalidationState state;\n\tInvalidationStore *store = NULL;\n\tlong count;\n\n\tcagg_invalidation_state_init(&state, cagg);\n\tstate.invalidations = tuplestore_begin_heap(false, false, work_mem);\n\tclear_cagg_invalidations_for_refresh(&state, refresh_window, force);\n\tcount = tuplestore_tuple_count(state.invalidations);\n\n\tif (count == 0)\n\t{\n\t\ttuplestore_end(state.invalidations);\n\t}\n\telse\n\t{\n\t\tstore = palloc(sizeof(InvalidationStore));\n\t\tstore->tupstore = state.invalidations;\n\t\tstore->tupdesc = CreateTupleDescCopy(RelationGetDescr(state.cagg_log_rel));\n\t}\n\n\tcagg_invalidation_state_cleanup(&state);\n\n\treturn store;\n}\n\nvoid\ninvalidation_store_free(InvalidationStore *store)\n{\n\tFreeTupleDesc(store->tupdesc);\n\ttuplestore_end(store->tupstore);\n\tpfree(store);\n}\n"
  },
  {
    "path": "tsl/src/continuous_aggs/invalidation.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include \"continuous_aggs/materialize.h\"\n#include \"ts_catalog/continuous_agg.h\"\n\n/*\n * Invalidation.\n *\n * A common representation of an invalidation that works across both the\n * hypertable invalidation log and the continuous aggregate invalidation log.\n */\ntypedef struct Invalidation\n{\n\tint32 hyper_id;\n\tint64 lowest_modified_value;\n\tint64 greatest_modified_value;\n\tbool is_modified;\n\tItemPointerData tid;\n} Invalidation;\n\n#define INVAL_NEG_INFINITY PG_INT64_MIN\n#define INVAL_POS_INFINITY PG_INT64_MAX\n\ntypedef struct InvalidationStore\n{\n\tTuplestorestate *tupstore;\n\tTupleDesc tupdesc;\n} InvalidationStore;\n\ntypedef struct Hypertable Hypertable;\n\nextern void invalidation_cagg_log_add_entry(int32 cagg_hyper_id, int64 start, int64 end);\nextern void invalidation_hyper_log_add_entry(int32 hyper_id, int64 start, int64 end);\nextern void continuous_agg_invalidate_raw_ht(const Hypertable *raw_ht, int64 start, int64 end);\nextern void continuous_agg_invalidate_mat_ht(const Hypertable *raw_ht, const Hypertable *mat_ht,\n\t\t\t\t\t\t\t\t\t\t\t int64 start, int64 end);\nextern Datum continuous_agg_process_hypertable_invalidations(PG_FUNCTION_ARGS);\nextern void invalidation_process_hypertable_log(int32 hypertable_id, Oid dimtype);\n\nextern InvalidationStore *invalidation_process_cagg_log(const ContinuousAgg *cagg,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst InternalTimeRange *refresh_window,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlong max_materializations,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tContinuousAggRefreshContext context,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbool force);\n\nextern void invalidation_store_free(InvalidationStore *store);\nextern void\ninvalidation_expand_to_bucket_boundaries(Invalidation *inv, Oid time_type_oid,\n\t\t\t\t\t\t\t\t\t\t const ContinuousAggBucketFunction *bucket_function);\nextern HeapTuple create_invalidation_tup(const TupleDesc tupdesc, int32 cagg_hyper_id, int64 start,\n\t\t\t\t\t\t\t\t\t\t int64 end);\n"
  },
  {
    "path": "tsl/src/continuous_aggs/invalidation_threshold.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/htup.h>\n#include <access/htup_details.h>\n#include <access/tableam.h>\n#include <access/xact.h>\n#include <nodes/memnodes.h>\n#include <storage/lmgr.h>\n#include <storage/lockdefs.h>\n#include <utils/builtins.h>\n#include <utils/memutils.h>\n#include <utils/snapmgr.h>\n\n#include <compat/compat.h>\n#include \"ts_catalog/catalog.h\"\n#include <scan_iterator.h>\n#include <scanner.h>\n#include <time_bucket.h>\n#include <time_utils.h>\n\n#include \"continuous_aggs/materialize.h\"\n#include \"continuous_aggs/refresh.h\"\n#include \"debug_point.h\"\n#include \"invalidation_threshold.h\"\n#include \"ts_catalog/continuous_agg.h\"\n#include <utils.h>\n\n/*\n * Invalidation threshold.\n *\n * The invalidation threshold acts as a dampener on a hypertable to make sure\n * that invalidations written during inserts won't cause too much write\n * amplification in \"hot\" regions---typically the \"head\" of the table. The\n * presumption is that most inserts happen at recent time intervals, and those\n * intervals will be invalid until writes move out of them. Therefore, it\n * isn't worth writing invalidations in that region since it is presumed\n * out-of-date anyway. Further, although it is possible to refresh a\n * continuous aggregate in those \"hot\" regions, it will lead to partially\n * filled buckets. Thus, refreshing those intervals is discouraged since the\n * aggregate will be immediately out-of-date until the buckets are filled. The\n * invalidation threshold is, in other words, used as a marker that lags\n * behind the head of the hypertable, where invalidations are written before\n * the threshold but not after it.\n *\n * The invalidation threshold is moved forward (and only forward) by refreshes\n * on continuous aggregates when it covers a window that stretches beyond the\n * current threshold. The invalidation threshold needs to be moved in its own\n * transaction, with exclusive access, before the refresh starts to\n * materialize data. This is to avoid losing any invalidations that occur\n * between the start of the transaction that moves the threshold and its end\n * (when the new threshold becomes visible).\n *\n * ______________________________________________\n * |_______________________________________|_____| recent data\n *                                        ^\n *      invalidations written here        |  no invalidations\n *                                        |\n *                               invalidation threshold\n *\n * Transactions that use an isolation level stronger than READ COMMITTED will\n * not be able to \"see\" changes to the invalidation threshold that may have\n * been made while they were running. Therefore, they always create records\n * in the hypertable invalidation log. See the cache_inval_entry_write()\n * implementation in tsl/src/continuous_aggs/insert.c\n */\n\ntypedef struct InvalidationThresholdData\n{\n\tconst ContinuousAgg *cagg;\n\tconst InternalTimeRange *refresh_window;\n\tint64 computed_invalidation_threshold;\n} InvalidationThresholdData;\n\ntypedef struct InvalidationThresholdGetData\n{\n\tint32 hypertable_id;\n\tint64 threshold;\n} InvalidationThresholdGetData;\n\nstatic ScanTupleResult\ninvalidation_threshold_scan_get(TupleInfo *ti, void *const data)\n{\n\tInvalidationThresholdGetData *the_data = (InvalidationThresholdGetData *) data;\n\tbool isnull;\n\tDatum datum;\n\n\tif (ti->lockresult == TM_Updated || ti->lockresult == TM_Deleted)\n\t\treturn SCAN_RESTART_WITH_NEW_SNAPSHOT;\n\n\tEnsure(ti->lockresult == TM_Ok,\n\t\t   \"unable to lock invalidation threshold tuple for hypertable %d (lock result %d)\",\n\t\t   the_data->hypertable_id,\n\t\t   ti->lockresult);\n\n\tdatum = slot_getattr(ti->slot, Anum_continuous_aggs_invalidation_threshold_watermark, &isnull);\n\n\tEnsure(!isnull, \"invalidation threshold for hypertable %d is null\", the_data->hypertable_id);\n\tthe_data->threshold = DatumGetInt64(datum);\n\n\treturn SCAN_DONE;\n}\n\nstatic ScanTupleResult\ninvalidation_threshold_scan_update(TupleInfo *ti, void *const data)\n{\n\tDEBUG_WAITPOINT(\"invalidation_threshold_scan_update_enter\");\n\n\tInvalidationThresholdData *invthresh = (InvalidationThresholdData *) data;\n\n\t/* If the tuple was modified concurrently, retry the operation and use a new snapshot\n\t * to see the updated tuple. */\n\tif (ti->lockresult == TM_Updated)\n\t\treturn SCAN_RESTART_WITH_NEW_SNAPSHOT;\n\n\tif (ti->lockresult != TM_Ok)\n\t{\n\t\telog(ERROR,\n\t\t\t \"unable to lock invalidation threshold tuple for hypertable %d (lock result %d)\",\n\t\t\t invthresh->cagg->data.raw_hypertable_id,\n\t\t\t ti->lockresult);\n\n\t\tpg_unreachable();\n\t}\n\n\tbool isnull;\n\tDatum datum =\n\t\tslot_getattr(ti->slot, Anum_continuous_aggs_invalidation_threshold_watermark, &isnull);\n\n\t/* NULL should never happen because we always initialize the threshold with the MIN\n\t * value of the partition type */\n\tEnsure(!isnull,\n\t\t   \"invalidation threshold for hypertable %d is null\",\n\t\t   invthresh->cagg->data.raw_hypertable_id);\n\n\tint64 current_invalidation_threshold = DatumGetInt64(datum);\n\n\t/* Compute new invalidation threshold. Note that this computation caps the\n\t * threshold at the end of the last bucket that holds data in the\n\t * underlying hypertable. */\n\tinvthresh->computed_invalidation_threshold =\n\t\tinvalidation_threshold_compute(invthresh->cagg, invthresh->refresh_window);\n\n\tif (invthresh->computed_invalidation_threshold > current_invalidation_threshold)\n\t{\n\t\tbool nulls[Natts_continuous_agg];\n\t\tDatum values[Natts_continuous_agg];\n\t\tbool do_replace[Natts_continuous_agg] = { false };\n\t\tbool should_free;\n\t\tHeapTuple tuple = ts_scanner_fetch_heap_tuple(ti, false, &should_free);\n\t\tHeapTuple new_tuple;\n\t\tTupleDesc tupdesc = ts_scanner_get_tupledesc(ti);\n\n\t\theap_deform_tuple(tuple, tupdesc, values, nulls);\n\n\t\tdo_replace[AttrNumberGetAttrOffset(Anum_continuous_aggs_invalidation_threshold_watermark)] =\n\t\t\ttrue;\n\t\tvalues[AttrNumberGetAttrOffset(Anum_continuous_aggs_invalidation_threshold_watermark)] =\n\t\t\tInt64GetDatum(invthresh->computed_invalidation_threshold);\n\n\t\tnew_tuple = heap_modify_tuple(tuple, tupdesc, values, nulls, do_replace);\n\n\t\tts_catalog_update(ti->scanrel, new_tuple);\n\t\theap_freetuple(new_tuple);\n\n\t\tif (should_free)\n\t\t\theap_freetuple(tuple);\n\t}\n\telse\n\t{\n\t\telog(DEBUG1,\n\t\t\t \"hypertable %d existing watermark >= new invalidation threshold \" INT64_FORMAT\n\t\t\t \" \" INT64_FORMAT,\n\t\t\t invthresh->cagg->data.raw_hypertable_id,\n\t\t\t current_invalidation_threshold,\n\t\t\t invthresh->computed_invalidation_threshold);\n\t\tinvthresh->computed_invalidation_threshold = current_invalidation_threshold;\n\t}\n\n\treturn SCAN_CONTINUE;\n}\n\n/*\n * Get the invalidation threshold for the hypertable.\n *\n * This will also lock the row.\n */\nint64\ninvalidation_threshold_get(int32 hypertable_id)\n{\n\tInvalidationThresholdGetData data = { .hypertable_id = hypertable_id };\n\tScanKeyData scankey[1];\n\tCatalog *catalog = ts_catalog_get();\n\tScanTupLock scantuplock = {\n\t\t.waitpolicy = LockWaitBlock,\n\t\t.lockmode = LockTupleExclusive,\n\t};\n\tPushActiveSnapshot(GetLatestSnapshot());\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, CONTINUOUS_AGGS_INVALIDATION_THRESHOLD),\n\t\t.index = catalog_get_index(catalog,\n\t\t\t\t\t\t\t\t   CONTINUOUS_AGGS_INVALIDATION_THRESHOLD,\n\t\t\t\t\t\t\t\t   CONTINUOUS_AGGS_INVALIDATION_THRESHOLD_PKEY),\n\t\t.nkeys = 1,\n\t\t.scankey = scankey,\n\t\t.data = &data,\n\t\t.tuple_found = invalidation_threshold_scan_get,\n\t\t.lockmode = RowShareLock,\n\t\t.scandirection = ForwardScanDirection,\n\t\t.result_mctx = CurrentMemoryContext,\n\t\t.tuplock = &scantuplock,\n\t\t.flags = SCANNER_F_KEEPLOCK,\n\t\t.snapshot = GetActiveSnapshot(),\n\t};\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_continuous_aggs_invalidation_threshold_hypertable_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(hypertable_id));\n\n\tbool found = ts_scanner_scan_one(&scanctx, false, CAGG_INVALIDATION_THRESHOLD_NAME);\n\tEnsure(found, \"invalidation threshold for hypertable %d not found\", hypertable_id);\n\tPopActiveSnapshot();\n\treturn data.threshold;\n}\n\n/*\n * Set a new invalidation threshold.\n *\n * The threshold is only updated if the new threshold is greater than the old\n * one.\n *\n * On success, the new threshold is returned, otherwise the existing threshold\n * is returned instead.\n */\nint64\ninvalidation_threshold_set_or_get(const ContinuousAgg *cagg,\n\t\t\t\t\t\t\t\t  const InternalTimeRange *refresh_window)\n{\n\tbool found = false;\n\tScanKeyData scankey[1];\n\tCatalog *catalog = ts_catalog_get();\n\tScanTupLock scantuplock = {\n\t\t.waitpolicy = LockWaitBlock,\n\t\t.lockmode = LockTupleExclusive,\n\t};\n\tInvalidationThresholdData updatectx = {\n\t\t.cagg = cagg,\n\t\t.refresh_window = refresh_window,\n\t};\n\tPushActiveSnapshot(GetLatestSnapshot());\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, CONTINUOUS_AGGS_INVALIDATION_THRESHOLD),\n\t\t.index = catalog_get_index(catalog,\n\t\t\t\t\t\t\t\t   CONTINUOUS_AGGS_INVALIDATION_THRESHOLD,\n\t\t\t\t\t\t\t\t   CONTINUOUS_AGGS_INVALIDATION_THRESHOLD_PKEY),\n\t\t.nkeys = 1,\n\t\t.scankey = scankey,\n\t\t.data = &updatectx,\n\t\t.tuple_found = invalidation_threshold_scan_update,\n\t\t.lockmode = RowExclusiveLock,\n\t\t.scandirection = ForwardScanDirection,\n\t\t.result_mctx = CurrentMemoryContext,\n\t\t.tuplock = &scantuplock,\n\t\t.flags = SCANNER_F_KEEPLOCK,\n\t\t/* We update the threshold value using this scanner. Since the scanner uses SnapshotSelf\n\t\t * per default, the updated tuple would become immediately visible to the scanner (the\n\t\t * snapshot includes \"changes made by the current command\") and ts_scanner_scan_one()\n\t\t * would fail due to the second found tuple. A normal MVCC snapshot is used to prevent\n\t\t * the update is immediately seen by the scanner. */\n\t\t.snapshot = GetActiveSnapshot(),\n\t};\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_continuous_aggs_invalidation_threshold_hypertable_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(cagg->data.raw_hypertable_id));\n\n\tfound = ts_scanner_scan_one(&scanctx, false, CAGG_INVALIDATION_THRESHOLD_NAME);\n\tEnsure(found,\n\t\t   \"invalidation threshold for hypertable %d not found\",\n\t\t   cagg->data.raw_hypertable_id);\n\tPopActiveSnapshot();\n\n\treturn updatectx.computed_invalidation_threshold;\n}\n\n/*\n * Compute a new invalidation threshold.\n *\n * The new invalidation threshold returned is the end of the given refresh\n * window, unless it ends at \"infinity\" in which case the threshold is capped\n * at the end of the last bucket materialized.\n */\nint64\ninvalidation_threshold_compute(const ContinuousAgg *cagg, const InternalTimeRange *refresh_window)\n{\n\tbool max_refresh = false;\n\tHypertable *ht = ts_hypertable_get_by_id(cagg->data.raw_hypertable_id);\n\n\tif (IS_TIMESTAMP_TYPE(refresh_window->type))\n\t\tmax_refresh = TS_TIME_IS_END(refresh_window->end, refresh_window->type) ||\n\t\t\t\t\t  TS_TIME_IS_NOEND(refresh_window->end, refresh_window->type);\n\telse\n\t\tmax_refresh = TS_TIME_IS_MAX(refresh_window->end, refresh_window->type);\n\n\tif (max_refresh)\n\t{\n\t\tbool isnull;\n\t\tint64 maxval = ts_hypertable_get_open_dim_max_value(ht, 0, &isnull);\n\n\t\tif (isnull)\n\t\t{\n\t\t\t/* No data in hypertable */\n\t\t\treturn cagg_get_time_min(cagg);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (cagg->bucket_function->bucket_fixed_interval == false)\n\t\t\t{\n\t\t\t\treturn ts_compute_beginning_of_the_next_bucket_variable(maxval,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcagg->bucket_function);\n\t\t\t}\n\n\t\t\tint64 bucket_width = ts_continuous_agg_fixed_bucket_width(cagg->bucket_function);\n\t\t\tAssert(bucket_width > 0);\n\t\t\tNullableDatum offset = INIT_NULL_DATUM;\n\t\t\tNullableDatum origin = INIT_NULL_DATUM;\n\t\t\tfill_bucket_offset_origin(cagg->bucket_function,\n\t\t\t\t\t\t\t\t\t  refresh_window->type,\n\t\t\t\t\t\t\t\t\t  &offset,\n\t\t\t\t\t\t\t\t\t  &origin);\n\t\t\tint64 bucket_start = ts_time_bucket_by_type_extended(bucket_width,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t maxval,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t refresh_window->type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t offset,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t origin);\n\t\t\t/* Add one bucket to get to the end of the last bucket */\n\t\t\treturn ts_time_saturating_add(bucket_start, bucket_width, refresh_window->type);\n\t\t}\n\t}\n\n\treturn refresh_window->end;\n}\n\n/*\n * Initialize the invalidation threshold.\n *\n * The initial value of the invalidation threshold should be the MIN\n * value for the Continuous Aggregate partition type.\n */\nvoid\ninvalidation_threshold_initialize(const ContinuousAgg *cagg)\n{\n\tbool found = false;\n\tScanKeyData scankey[1];\n\tCatalog *catalog = ts_catalog_get();\n\tScannerCtx scanctx = {\n\t\t.table = catalog_get_table_id(catalog, CONTINUOUS_AGGS_INVALIDATION_THRESHOLD),\n\t\t.index = catalog_get_index(catalog,\n\t\t\t\t\t\t\t\t   CONTINUOUS_AGGS_INVALIDATION_THRESHOLD,\n\t\t\t\t\t\t\t\t   CONTINUOUS_AGGS_INVALIDATION_THRESHOLD_PKEY),\n\t\t.nkeys = 1,\n\t\t.scankey = scankey,\n\t\t.lockmode = ShareUpdateExclusiveLock,\n\t\t.scandirection = ForwardScanDirection,\n\t\t.result_mctx = CurrentMemoryContext,\n\t\t.flags = SCANNER_F_KEEPLOCK,\n\t};\n\n\tScanKeyInit(&scankey[0],\n\t\t\t\tAnum_continuous_aggs_invalidation_threshold_hypertable_id,\n\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\tF_INT4EQ,\n\t\t\t\tInt32GetDatum(cagg->data.raw_hypertable_id));\n\n\tfound = ts_scanner_scan_one(&scanctx, false, CAGG_INVALIDATION_THRESHOLD_NAME);\n\n\tif (!found)\n\t{\n\t\tRelation rel =\n\t\t\ttable_open(catalog_get_table_id(catalog, CONTINUOUS_AGGS_INVALIDATION_THRESHOLD),\n\t\t\t\t\t   ShareUpdateExclusiveLock);\n\t\tTupleDesc desc = RelationGetDescr(rel);\n\t\tDatum values[Natts_continuous_aggs_invalidation_threshold];\n\t\tbool nulls[Natts_continuous_aggs_invalidation_threshold] = { false };\n\t\tCatalogSecurityContext sec_ctx;\n\t\t/* get the MIN value for the partition type */\n\t\tint64 min_value = cagg_get_time_min(cagg);\n\n\t\tvalues[AttrNumberGetAttrOffset(Anum_continuous_aggs_invalidation_threshold_hypertable_id)] =\n\t\t\tInt32GetDatum(cagg->data.raw_hypertable_id);\n\n\t\tvalues[AttrNumberGetAttrOffset(Anum_continuous_aggs_invalidation_threshold_watermark)] =\n\t\t\tInt64GetDatum(min_value);\n\n\t\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\t\tts_catalog_insert_values(rel, desc, values, nulls);\n\t\tts_catalog_restore_user(&sec_ctx);\n\t\ttable_close(rel, NoLock);\n\t}\n}\n"
  },
  {
    "path": "tsl/src/continuous_aggs/invalidation_threshold.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\ntypedef struct InternalTimeRange InternalTimeRange;\ntypedef struct ContinuousAgg ContinuousAgg;\ntypedef struct Hypertable Hypertable;\n\nextern int64 invalidation_threshold_get(int32 hypertable_id);\nextern int64 invalidation_threshold_set_or_get(const ContinuousAgg *cagg,\n\t\t\t\t\t\t\t\t\t\t\t   const InternalTimeRange *refresh_window);\nextern int64 invalidation_threshold_compute(const ContinuousAgg *cagg,\n\t\t\t\t\t\t\t\t\t\t\tconst InternalTimeRange *refresh_window);\nextern void invalidation_threshold_initialize(const ContinuousAgg *cagg);\n"
  },
  {
    "path": "tsl/src/continuous_aggs/materialize.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n\n#include <executor/spi.h>\n#include <fmgr.h>\n#include <lib/stringinfo.h>\n#include <utils/builtins.h>\n#include <utils/date.h>\n#include <utils/guc.h>\n#include <utils/palloc.h>\n#include <utils/rel.h>\n#include <utils/relcache.h>\n#include <utils/snapmgr.h>\n#include <utils/timestamp.h>\n\n#include \"compat/compat.h\"\n#include \"debug_assert.h\"\n#include \"guc.h\"\n#include \"materialize.h\"\n#include \"scan_iterator.h\"\n#include \"scanner.h\"\n#include \"time_utils.h\"\n#include \"ts_catalog/array_utils.h\"\n#include \"ts_catalog/compression_settings.h\"\n#include \"ts_catalog/continuous_agg.h\"\n#include \"ts_catalog/continuous_aggs_watermark.h\"\n\n/*********************\n * utility functions *\n *********************/\nstatic TimeRange internal_time_range_to_time_range(InternalTimeRange internal);\nstatic Datum internal_to_time_value_or_infinite(int64 internal, Oid time_type,\n\t\t\t\t\t\t\t\t\t\t\t\tbool *is_infinite_out);\nstatic List *cagg_find_aggref_and_var_cols(ContinuousAgg *cagg, Hypertable *mat_ht);\nstatic char *build_merge_insert_columns(List *strings, const char *separator, const char *prefix);\nstatic char *build_merge_join_clause(List *column_names);\nstatic char *build_merge_update_clause(List *column_names);\n\n/***************************\n * materialization support *\n ***************************/\ntypedef enum MaterializationPlanType\n{\n\tPLAN_TYPE_INSERT,\n\tPLAN_TYPE_DELETE,\n\tPLAN_TYPE_EXISTS,\n\tPLAN_TYPE_MERGE,\n\tPLAN_TYPE_MERGE_DELETE,\n\tPLAN_TYPE_RANGES_SELECT,\n\tPLAN_TYPE_RANGES_DELETE,\n\tPLAN_TYPE_RANGES_PENDING,\n\t_MAX_MATERIALIZATION_PLAN_TYPES\n} MaterializationPlanType;\n\ntypedef struct MaterializationContext\n{\n\tHypertable *mat_ht;\n\tconst ContinuousAgg *cagg;\n\tSchemaAndName partial_view;\n\tSchemaAndName materialization_table;\n\tNameData *time_column_name;\n\tTimeRange materialization_range;\n\tInternalTimeRange internal_materialization_range;\n\tItemPointer tupleid;\n\tint nargs;\n} MaterializationContext;\n\ntypedef char *(*MaterializationCreateStatement)(MaterializationContext *context);\n\ntypedef struct MaterializationPlan\n{\n\tSPIPlanPtr plan;\n\tbool read_only;\n\tbool catalog_security_context;\n\tint nargs;\n\tMaterializationCreateStatement create_statement;\n\tconst char *error_message;\n\tconst char *progress_message;\n} MaterializationPlan;\n\nstatic char *build_order_by_clause(MaterializationContext *context);\nstatic char *create_materialization_insert_statement(MaterializationContext *context);\nstatic char *create_materialization_delete_statement(MaterializationContext *context);\nstatic char *create_materialization_exists_statement(MaterializationContext *context);\nstatic char *create_materialization_merge_statement(MaterializationContext *context);\nstatic char *create_materialization_merge_delete_statement(MaterializationContext *context);\nstatic char *create_materialization_ranges_select_statement(MaterializationContext *context);\nstatic char *create_materialization_ranges_delete_statement(MaterializationContext *context);\nstatic char *create_materialization_ranges_pending_statement(MaterializationContext *context);\n\nstatic MaterializationPlan materialization_plans[_MAX_MATERIALIZATION_PLAN_TYPES + 1] = {\n\t[PLAN_TYPE_INSERT] = { .nargs = 2,\n\t\t\t\t\t\t   .create_statement = create_materialization_insert_statement,\n\t\t\t\t\t\t   .error_message =\n\t\t\t\t\t\t\t   \"could not insert old values into materialization table \\\"%s.%s\\\"\",\n\t\t\t\t\t\t   .progress_message = \"inserted \" UINT64_FORMAT\n\t\t\t\t\t\t\t\t\t\t\t   \" row(s) into materialization table \\\"%s.%s\\\"\" },\n\t[PLAN_TYPE_DELETE] = { .nargs = 2,\n\t\t\t\t\t\t   .create_statement = create_materialization_delete_statement,\n\t\t\t\t\t\t   .error_message =\n\t\t\t\t\t\t\t   \"could not delete old values from materialization table \\\"%s.%s\\\"\",\n\t\t\t\t\t\t   .progress_message = \"deleted \" UINT64_FORMAT\n\t\t\t\t\t\t\t\t\t\t\t   \" row(s) from materialization table \\\"%s.%s\\\"\" },\n\t[PLAN_TYPE_EXISTS] = { .read_only = true,\n\t\t\t\t\t\t   .nargs = 2,\n\t\t\t\t\t\t   .create_statement = create_materialization_exists_statement,\n\t\t\t\t\t\t   .error_message = \"could not check the materialization table \\\"%s.%s\\\"\" },\n\t[PLAN_TYPE_MERGE] = { .nargs = 2,\n\t\t\t\t\t\t  .create_statement = create_materialization_merge_statement,\n\t\t\t\t\t\t  .error_message =\n\t\t\t\t\t\t\t  \"could not merge old values into materialization table \\\"%s.%s\\\"\",\n\t\t\t\t\t\t  .progress_message = \"merged \" UINT64_FORMAT\n\t\t\t\t\t\t\t\t\t\t\t  \" row(s) into materialization table \\\"%s.%s\\\"\" },\n\t[PLAN_TYPE_MERGE_DELETE] = { .nargs = 2,\n\t\t\t\t\t\t\t\t .create_statement = create_materialization_merge_delete_statement,\n\t\t\t\t\t\t\t\t .error_message = \"could not delete old values from \"\n\t\t\t\t\t\t\t\t\t\t\t\t  \"materialization table \\\"%s.%s\\\"\",\n\t\t\t\t\t\t\t\t .progress_message =\n\t\t\t\t\t\t\t\t\t \"deleted \" UINT64_FORMAT\n\t\t\t\t\t\t\t\t\t \" row(s) from materialization table \\\"%s.%s\\\"\" },\n\t[PLAN_TYPE_RANGES_SELECT] = { .catalog_security_context = true,\n\t\t\t\t\t\t\t\t  .nargs = 3,\n\t\t\t\t\t\t\t\t  .create_statement =\n\t\t\t\t\t\t\t\t\t  create_materialization_ranges_select_statement,\n\t\t\t\t\t\t\t\t  .error_message = \"could not select invalidation entries for \"\n\t\t\t\t\t\t\t\t\t\t\t\t   \"materialization table \\\"%s.%s\\\"\" },\n\t[PLAN_TYPE_RANGES_DELETE] = { .catalog_security_context = true,\n\t\t\t\t\t\t\t\t  .nargs = 1,\n\t\t\t\t\t\t\t\t  .create_statement =\n\t\t\t\t\t\t\t\t\t  create_materialization_ranges_delete_statement,\n\t\t\t\t\t\t\t\t  .error_message = \"could not delete invalidation entries for \"\n\t\t\t\t\t\t\t\t\t\t\t\t   \"materialization table \\\"%s.%s\\\"\" },\n\t[PLAN_TYPE_RANGES_PENDING] = { .read_only = true,\n\t\t\t\t\t\t\t\t   .nargs = 3,\n\t\t\t\t\t\t\t\t   .create_statement =\n\t\t\t\t\t\t\t\t\t   create_materialization_ranges_pending_statement,\n\t\t\t\t\t\t\t\t   .error_message = \"could not select pending materialization \"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"ranges \\\"%s.%s\\\"\" },\n};\n\nstatic Oid *create_materialization_plan_argtypes(MaterializationContext *context,\n\t\t\t\t\t\t\t\t\t\t\t\t MaterializationPlanType plan_type, int nargs);\nstatic MaterializationPlan *create_materialization_plan(MaterializationContext *context,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tMaterializationPlanType plan_type);\nstatic void create_materialization_plan_args(MaterializationContext *context,\n\t\t\t\t\t\t\t\t\t\t\t MaterializationPlanType plan_type, Datum **values,\n\t\t\t\t\t\t\t\t\t\t\t char **nulls);\nstatic uint64 execute_materialization_plan(MaterializationContext *context,\n\t\t\t\t\t\t\t\t\t\t   MaterializationPlanType plan_type);\nstatic void free_materialization_plan(MaterializationContext *context,\n\t\t\t\t\t\t\t\t\t  MaterializationPlanType plan_type);\nstatic void free_materialization_plans(MaterializationContext *context);\n\nstatic void update_watermark(MaterializationContext *context);\nstatic void execute_materializations(MaterializationContext *context);\n\n/* API to update materializations from refresh code */\nvoid\ncontinuous_agg_update_materialization(Hypertable *mat_ht, const ContinuousAgg *cagg,\n\t\t\t\t\t\t\t\t\t  SchemaAndName partial_view,\n\t\t\t\t\t\t\t\t\t  SchemaAndName materialization_table,\n\t\t\t\t\t\t\t\t\t  const NameData *time_column_name,\n\t\t\t\t\t\t\t\t\t  InternalTimeRange materialization_range)\n{\n\tMaterializationContext context = {\n\t\t.mat_ht = mat_ht,\n\t\t.cagg = cagg,\n\t\t.partial_view = partial_view,\n\t\t.materialization_table = materialization_table,\n\t\t.time_column_name = (NameData *) time_column_name,\n\t\t.materialization_range = internal_time_range_to_time_range(materialization_range),\n\t\t.internal_materialization_range = materialization_range,\n\t};\n\n\t/* Lock down search_path */\n\tint save_nestlevel = NewGUCNestLevel();\n\tRestrictSearchPath();\n\n\t/* pin the start of new_materialization to the end of new_materialization,\n\t * we are not allowed to materialize beyond that point\n\t */\n\tif (materialization_range.start > materialization_range.end)\n\t\tmaterialization_range.start = materialization_range.end;\n\n\t/* Then insert the materializations */\n\tcontext.materialization_range = internal_time_range_to_time_range(materialization_range);\n\texecute_materializations(&context);\n\n\t/* Restore search_path */\n\tAtEOXact_GUC(false, save_nestlevel);\n}\n\n/* API to check for pending materialization ranges */\nbool\ncontinuous_agg_has_pending_materializations(const ContinuousAgg *cagg,\n\t\t\t\t\t\t\t\t\t\t\tInternalTimeRange materialization_range)\n{\n\tMaterializationContext context = {\n\t\t.cagg = cagg,\n\t\t.internal_materialization_range = materialization_range,\n\t};\n\n\t/* Lock down search_path */\n\tint save_nestlevel = NewGUCNestLevel();\n\tRestrictSearchPath();\n\n\tif (materialization_range.start > materialization_range.end)\n\t\tmaterialization_range.start = materialization_range.end;\n\n\tPushActiveSnapshot(GetLatestSnapshot());\n\tbool has_pending_materializations =\n\t\t(execute_materialization_plan(&context, PLAN_TYPE_RANGES_PENDING) > 0);\n\n\tfree_materialization_plan(&context, PLAN_TYPE_RANGES_PENDING);\n\tPopActiveSnapshot();\n\n\t/* Restore search_path */\n\tAtEOXact_GUC(false, save_nestlevel);\n\n\treturn has_pending_materializations;\n}\n\nstatic Datum\ntime_range_internal_to_min_time_value(Oid type)\n{\n\tswitch (type)\n\t{\n\t\tcase TIMESTAMPOID:\n\t\t\treturn TimestampGetDatum(DT_NOBEGIN);\n\t\tcase TIMESTAMPTZOID:\n\t\t\treturn TimestampTzGetDatum(DT_NOBEGIN);\n\t\tcase DATEOID:\n\t\t\treturn DateADTGetDatum(DATEVAL_NOBEGIN);\n\t\tdefault:\n\t\t\treturn ts_internal_to_time_value(PG_INT64_MIN, type);\n\t}\n}\n\nstatic Datum\ntime_range_internal_to_max_time_value(Oid type)\n{\n\tswitch (type)\n\t{\n\t\tcase TIMESTAMPOID:\n\t\t\treturn TimestampGetDatum(DT_NOEND);\n\t\tcase TIMESTAMPTZOID:\n\t\t\treturn TimestampTzGetDatum(DT_NOEND);\n\t\tcase DATEOID:\n\t\t\treturn DateADTGetDatum(DATEVAL_NOEND);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\treturn ts_internal_to_time_value(PG_INT64_MAX, type);\n\t}\n}\n\nstatic Datum\ninternal_to_time_value_or_infinite(int64 internal, Oid time_type, bool *is_infinite_out)\n{\n\t/* MIN and MAX can occur due to NULL thresholds, or due to a lack of invalidations. Since our\n\t * regular conversion function errors in those cases, and we want to use those as markers for an\n\t * open threshold in one direction, we special case this here*/\n\tif (internal == PG_INT64_MIN)\n\t{\n\t\tif (is_infinite_out != NULL)\n\t\t\t*is_infinite_out = true;\n\t\treturn time_range_internal_to_min_time_value(time_type);\n\t}\n\telse if (internal == PG_INT64_MAX)\n\t{\n\t\tif (is_infinite_out != NULL)\n\t\t\t*is_infinite_out = true;\n\t\treturn time_range_internal_to_max_time_value(time_type);\n\t}\n\telse\n\t{\n\t\tif (is_infinite_out != NULL)\n\t\t\t*is_infinite_out = false;\n\t\treturn ts_internal_to_time_value(internal, time_type);\n\t}\n}\n\nstatic TimeRange\ninternal_time_range_to_time_range(InternalTimeRange internal)\n{\n\tTimeRange range;\n\trange.type = internal.type;\n\n\trange.start = internal_to_time_value_or_infinite(internal.start, internal.type, NULL);\n\trange.end = internal_to_time_value_or_infinite(internal.end, internal.type, NULL);\n\n\treturn range;\n}\n\nstatic List *\ncagg_find_aggref_and_var_cols(ContinuousAgg *cagg, Hypertable *mat_ht)\n{\n\tList *retlist = NIL;\n\tListCell *lc;\n\tQuery *cagg_view_query = ts_continuous_agg_get_query(cagg);\n\n\tforeach (lc, cagg_view_query->targetList)\n\t{\n\t\tTargetEntry *tle = castNode(TargetEntry, lfirst(lc));\n\n\t\tif (!tle->resjunk && (tle->ressortgroupref == 0 ||\n\t\t\t\t\t\t\t  get_sortgroupref_clause_noerr(tle->ressortgroupref,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcagg_view_query->groupClause) == NULL))\n\t\t\tretlist = lappend(retlist, get_attname(mat_ht->main_table_relid, tle->resno, false));\n\t}\n\n\treturn retlist;\n}\n\nstatic char *\nbuild_merge_insert_columns(List *strings, const char *separator, const char *prefix)\n{\n\tStringInfoData ret;\n\tinitStringInfo(&ret);\n\n\tAssert(strings != NIL);\n\n\tListCell *lc;\n\tforeach (lc, strings)\n\t{\n\t\tchar *grpcol = (char *) lfirst(lc);\n\t\tif (ret.len > 0)\n\t\t\tappendStringInfoString(&ret, separator);\n\n\t\tif (prefix)\n\t\t\tappendStringInfoString(&ret, prefix);\n\t\tappendStringInfoString(&ret, quote_identifier(grpcol));\n\t}\n\n\telog(DEBUG2, \"%s: %s\", __func__, ret.data);\n\treturn ret.data;\n}\n\nstatic char *\nbuild_merge_join_clause(List *column_names)\n{\n\tStringInfoData ret;\n\tinitStringInfo(&ret);\n\n\tAssert(column_names != NIL);\n\n\tListCell *lc;\n\tforeach (lc, column_names)\n\t{\n\t\tchar *column = (char *) lfirst(lc);\n\n\t\tif (ret.len > 0)\n\t\t\tappendStringInfoString(&ret, \" AND \");\n\n\t\tappendStringInfoString(&ret, \"P.\");\n\t\tappendStringInfoString(&ret, quote_identifier(column));\n\t\tappendStringInfoString(&ret, \" IS NOT DISTINCT FROM M.\");\n\t\tappendStringInfoString(&ret, quote_identifier(column));\n\t}\n\n\telog(DEBUG2, \"%s: %s\", __func__, ret.data);\n\treturn ret.data;\n}\n\nstatic char *\nbuild_merge_update_clause(List *column_names)\n{\n\tStringInfoData ret;\n\tinitStringInfo(&ret);\n\n\tAssert(column_names != NIL);\n\n\tListCell *lc;\n\tforeach (lc, column_names)\n\t{\n\t\tchar *column = (char *) lfirst(lc);\n\n\t\tif (ret.len > 0)\n\t\t\tappendStringInfoString(&ret, \", \");\n\n\t\tappendStringInfoString(&ret, quote_identifier(column));\n\t\tappendStringInfoString(&ret, \" = P.\");\n\t\tappendStringInfoString(&ret, quote_identifier(column));\n\t}\n\n\telog(DEBUG2, \"%s: %s\", __func__, ret.data);\n\treturn ret.data;\n}\n\n/* Build ORDER BY clause based on segmentby + orderby compression settings */\nstatic char *\nbuild_order_by_clause(MaterializationContext *context)\n{\n\t/* Don't build ORDER BY clause if compression is not enabled */\n\tif (!TS_HYPERTABLE_HAS_COMPRESSION_ENABLED(context->mat_ht))\n\t\treturn \"\"; /* No ORDER BY if no compression */\n\n\tCompressionSettings *settings = ts_compression_settings_get(context->mat_ht->main_table_relid);\n\n\tint num_segmentby = ts_array_length(settings->fd.segmentby);\n\tint num_orderby = ts_array_length(settings->fd.orderby);\n\n\tStringInfo ret = makeStringInfo();\n\tappendStringInfoString(ret, \"ORDER BY \");\n\n\t/* process segmentby settings */\n\tfor (int i = 1; i <= num_segmentby; i++)\n\t{\n\t\tif (i > 1)\n\t\t\tappendStringInfoString(ret, \", \");\n\t\tappendStringInfoString(ret,\n\t\t\t\t\t\t\t   quote_identifier(\n\t\t\t\t\t\t\t\t   ts_array_get_element_text(settings->fd.segmentby, i)));\n\t}\n\n\t/* process orderby settings */\n\tfor (int i = 1; i <= num_orderby; i++)\n\t{\n\t\tbool is_orderby_desc = ts_array_get_element_bool(settings->fd.orderby_desc, i);\n\t\tbool is_null_first = ts_array_get_element_bool(settings->fd.orderby_nullsfirst, i);\n\n\t\tif (num_segmentby > 0 || i > 1)\n\t\t\tappendStringInfoString(ret, \", \");\n\t\tappendStringInfoString(ret,\n\t\t\t\t\t\t\t   quote_identifier(\n\t\t\t\t\t\t\t\t   ts_array_get_element_text(settings->fd.orderby, i)));\n\t\tif (is_orderby_desc)\n\t\t\tappendStringInfoString(ret, \" DESC\");\n\t\telse\n\t\t\tappendStringInfoString(ret, \" ASC\");\n\t\tif (is_null_first)\n\t\t\tappendStringInfoString(ret, \" NULLS FIRST\");\n\t\telse\n\t\t\tappendStringInfoString(ret, \" NULLS LAST\");\n\t}\n\n\telog(DEBUG2, \"%s: %s\", __func__, ret->data);\n\treturn ret->data;\n}\n\nstatic inline bool\nhas_direct_compress_on_cagg_refresh_enabled(MaterializationContext *context)\n{\n\treturn ts_guc_enable_direct_compress_on_cagg_refresh &&\n\t\t   TS_HYPERTABLE_HAS_COMPRESSION_ENABLED(context->mat_ht);\n}\n\n/* Create INSERT statement */\nstatic char *\ncreate_materialization_insert_statement(MaterializationContext *context)\n{\n\t/* If direct compress on cagg refresh is enabled, build ORDER BY clause based on segmentby and\n\t * orderby settings. This is necessary because we set\n\t * `timescaledb.enable_direct_compress_insert_client_sorted=on` in order to send ordered data to\n\t * the compressor. */\n\tchar *orderby =\n\t\thas_direct_compress_on_cagg_refresh_enabled(context) ? build_order_by_clause(context) : \"\";\n\n\tStringInfoData query;\n\tinitStringInfo(&query);\n\tappendStringInfo(&query,\n\t\t\t\t\t \"INSERT INTO %s.%s SELECT * FROM %s.%s AS I \"\n\t\t\t\t\t \"WHERE I.%s >= $1 AND I.%s < $2 %s;\",\n\t\t\t\t\t quote_identifier(NameStr(*context->materialization_table.schema)),\n\t\t\t\t\t quote_identifier(NameStr(*context->materialization_table.name)),\n\t\t\t\t\t quote_identifier(NameStr(*context->partial_view.schema)),\n\t\t\t\t\t quote_identifier(NameStr(*context->partial_view.name)),\n\t\t\t\t\t quote_identifier(NameStr(*context->time_column_name)),\n\t\t\t\t\t quote_identifier(NameStr(*context->time_column_name)),\n\t\t\t\t\t orderby);\n\treturn query.data;\n}\n\n/* Create DELETE statement */\nstatic char *\ncreate_materialization_delete_statement(MaterializationContext *context)\n{\n\tStringInfoData query;\n\tinitStringInfo(&query);\n\tappendStringInfo(&query,\n\t\t\t\t\t \"DELETE FROM %s.%s AS D \"\n\t\t\t\t\t \"WHERE D.%s >= $1 AND D.%s < $2;\",\n\t\t\t\t\t quote_identifier(NameStr(*context->materialization_table.schema)),\n\t\t\t\t\t quote_identifier(NameStr(*context->materialization_table.name)),\n\t\t\t\t\t quote_identifier(NameStr(*context->time_column_name)),\n\t\t\t\t\t quote_identifier(NameStr(*context->time_column_name)));\n\treturn query.data;\n}\n\n/* Create SELECT EXISTS statement */\nstatic char *\ncreate_materialization_exists_statement(MaterializationContext *context)\n{\n\tStringInfoData query;\n\tinitStringInfo(&query);\n\tappendStringInfo(&query,\n\t\t\t\t\t \"SELECT 1 FROM %s.%s AS M \"\n\t\t\t\t\t \"WHERE M.%s >= $1 AND M.%s < $2 \"\n\t\t\t\t\t \"LIMIT 1;\",\n\t\t\t\t\t quote_identifier(NameStr(*context->materialization_table.schema)),\n\t\t\t\t\t quote_identifier(NameStr(*context->materialization_table.name)),\n\t\t\t\t\t quote_identifier(NameStr(*context->time_column_name)),\n\t\t\t\t\t quote_identifier(NameStr(*context->time_column_name)));\n\treturn query.data;\n}\n\n/* Create MERGE statement */\nstatic char *\ncreate_materialization_merge_statement(MaterializationContext *context)\n{\n\tList *grp_colnames = cagg_find_groupingcols((ContinuousAgg *) context->cagg, context->mat_ht);\n\tList *agg_colnames =\n\t\tcagg_find_aggref_and_var_cols((ContinuousAgg *) context->cagg, context->mat_ht);\n\tList *all_columns = NIL;\n\n\t/* Concat both lists into a single one*/\n\tall_columns = list_concat(all_columns, grp_colnames);\n\tall_columns = list_concat(all_columns, agg_colnames);\n\n\tStringInfoData merge_update;\n\tinitStringInfo(&merge_update);\n\tchar *merge_update_clause = build_merge_update_clause(all_columns);\n\n\t/* It make no sense but is possible to create a cagg only with time bucket (without\n\t * aggregate functions) */\n\tif (merge_update_clause != NULL)\n\t{\n\t\tappendStringInfo(&merge_update,\n\t\t\t\t\t\t \"  WHEN MATCHED AND ROW(M.*) IS DISTINCT FROM ROW(P.*) THEN \"\n\t\t\t\t\t\t \"    UPDATE SET %s \",\n\t\t\t\t\t\t merge_update_clause);\n\t}\n\n\tStringInfoData query;\n\tinitStringInfo(&query);\n\n\t/* MERGE statement to UPDATE affected buckets and INSERT new ones */\n\tappendStringInfo(&query,\n\t\t\t\t\t \"WITH partial AS ( \"\n\t\t\t\t\t \"  SELECT * \"\n\t\t\t\t\t \"  FROM %s.%s \"\n\t\t\t\t\t \"  WHERE %s >= $1 AND %s < $2 \"\n\t\t\t\t\t \") \"\n\t\t\t\t\t \"MERGE INTO %s.%s M \"\n\t\t\t\t\t \"USING partial P ON %s AND M.%s >= $1 AND M.%s < $2 \"\n\t\t\t\t\t \"  %s \" /* UPDATE */\n\t\t\t\t\t \"  WHEN NOT MATCHED THEN \"\n\t\t\t\t\t \"    INSERT (%s) VALUES (%s) \",\n\n\t\t\t\t\t /* partial VIEW */\n\t\t\t\t\t quote_identifier(NameStr(*context->partial_view.schema)),\n\t\t\t\t\t quote_identifier(NameStr(*context->partial_view.name)),\n\n\t\t\t\t\t /* partial WHERE */\n\t\t\t\t\t quote_identifier(NameStr(*context->time_column_name)),\n\t\t\t\t\t quote_identifier(NameStr(*context->time_column_name)),\n\n\t\t\t\t\t /* materialization hypertable */\n\t\t\t\t\t quote_identifier(NameStr(*context->materialization_table.schema)),\n\t\t\t\t\t quote_identifier(NameStr(*context->materialization_table.name)),\n\n\t\t\t\t\t /* MERGE JOIN condition */\n\t\t\t\t\t build_merge_join_clause(grp_colnames),\n\n\t\t\t\t\t /* extra MERGE JOIN condition with primary dimension */\n\t\t\t\t\t quote_identifier(NameStr(*context->time_column_name)),\n\t\t\t\t\t quote_identifier(NameStr(*context->time_column_name)),\n\n\t\t\t\t\t /* UPDATE */\n\t\t\t\t\t merge_update.data,\n\n\t\t\t\t\t /* INSERT */\n\t\t\t\t\t build_merge_insert_columns(all_columns, \", \", NULL),\n\t\t\t\t\t build_merge_insert_columns(all_columns, \", \", \"P.\"));\n\treturn query.data;\n}\n\n/* Create DELETE after MERGE query statement */\nstatic char *\ncreate_materialization_merge_delete_statement(MaterializationContext *context)\n{\n\tStringInfoData query;\n\tinitStringInfo(&query);\n\tList *grp_colnames = cagg_find_groupingcols((ContinuousAgg *) context->cagg, context->mat_ht);\n\n\tappendStringInfo(&query,\n\t\t\t\t\t \"DELETE \"\n\t\t\t\t\t \"FROM %s.%s M \"\n\t\t\t\t\t \"WHERE M.%s >= $1 AND M.%s < $2 \"\n\t\t\t\t\t \"AND NOT EXISTS (\"\n\t\t\t\t\t \" SELECT FROM %s.%s P \"\n\t\t\t\t\t \" WHERE %s AND P.%s >= $1 AND P.%s < $2) \",\n\n\t\t\t\t\t /* materialization hypertable */\n\t\t\t\t\t quote_identifier(NameStr(*context->materialization_table.schema)),\n\t\t\t\t\t quote_identifier(NameStr(*context->materialization_table.name)),\n\n\t\t\t\t\t /* materialization hypertable WHERE */\n\t\t\t\t\t quote_identifier(NameStr(*context->time_column_name)),\n\t\t\t\t\t quote_identifier(NameStr(*context->time_column_name)),\n\n\t\t\t\t\t /* partial VIEW */\n\t\t\t\t\t quote_identifier(NameStr(*context->partial_view.schema)),\n\t\t\t\t\t quote_identifier(NameStr(*context->partial_view.name)),\n\n\t\t\t\t\t /* MERGE JOIN condition */\n\t\t\t\t\t build_merge_join_clause(grp_colnames),\n\n\t\t\t\t\t /* partial WHERE */\n\t\t\t\t\t quote_identifier(NameStr(*context->time_column_name)),\n\t\t\t\t\t quote_identifier(NameStr(*context->time_column_name)));\n\treturn query.data;\n}\n\nstatic char *\ncreate_materialization_ranges_select_statement(MaterializationContext *context)\n{\n\tStringInfoData query;\n\tinitStringInfo(&query);\n\n\tappendStringInfo(&query,\n\t\t\t\t\t \"SELECT ctid, lowest_modified_value, greatest_modified_value \"\n\t\t\t\t\t \"FROM _timescaledb_catalog.continuous_aggs_materialization_ranges \"\n\t\t\t\t\t \"WHERE materialization_id = $1 \"\n\t\t\t\t\t \"AND greatest_modified_value >= lowest_modified_value \"\n\t\t\t\t\t \"AND lowest_modified_value >= $2 \"\n\t\t\t\t\t \"AND greatest_modified_value <= $3 \"\n\t\t\t\t\t \"AND pg_catalog.int8range(lowest_modified_value, greatest_modified_value) && \"\n\t\t\t\t\t \"pg_catalog.int8range($2, $3) \"\n\t\t\t\t\t \"ORDER BY lowest_modified_value ASC \"\n\t\t\t\t\t \"LIMIT 1 \"\n\t\t\t\t\t \"FOR UPDATE SKIP LOCKED \");\n\n\treturn query.data;\n}\n\nstatic char *\ncreate_materialization_ranges_delete_statement(MaterializationContext *context)\n{\n\tStringInfoData query;\n\tinitStringInfo(&query);\n\n\tappendStringInfo(&query,\n\t\t\t\t\t \"DELETE \"\n\t\t\t\t\t \"FROM _timescaledb_catalog.continuous_aggs_materialization_ranges \"\n\t\t\t\t\t \"WHERE ctid = $1\");\n\n\treturn query.data;\n}\n\nstatic char *\ncreate_materialization_ranges_pending_statement(MaterializationContext *context)\n{\n\tStringInfoData query;\n\tinitStringInfo(&query);\n\n\tappendStringInfo(&query,\n\t\t\t\t\t \"SELECT * \"\n\t\t\t\t\t \"FROM _timescaledb_catalog.continuous_aggs_materialization_ranges \"\n\t\t\t\t\t \"WHERE materialization_id = $1 \"\n\t\t\t\t\t \"AND greatest_modified_value >= lowest_modified_value \"\n\t\t\t\t\t \"AND lowest_modified_value >= $2 \"\n\t\t\t\t\t \"AND greatest_modified_value <= $3 \"\n\t\t\t\t\t \"AND pg_catalog.int8range(lowest_modified_value, greatest_modified_value) && \"\n\t\t\t\t\t \"pg_catalog.int8range($2, $3) \"\n\t\t\t\t\t \"LIMIT 1 \");\n\n\treturn query.data;\n}\n\nstatic Oid *\ncreate_materialization_plan_argtypes(MaterializationContext *context,\n\t\t\t\t\t\t\t\t\t MaterializationPlanType plan_type, int nargs)\n{\n\tOid *argtypes = (Oid *) palloc(nargs * sizeof(Oid));\n\n\tswitch (plan_type)\n\t{\n\t\tcase PLAN_TYPE_RANGES_SELECT: /* 3 arguments */\n\t\tcase PLAN_TYPE_RANGES_PENDING:\n\t\t\targtypes[0] = INT4OID; /* materialization_id */\n\t\t\targtypes[1] = INT8OID;\n\t\t\targtypes[2] = INT8OID;\n\t\t\tbreak;\n\n\t\tcase PLAN_TYPE_RANGES_DELETE: /* 1 argument1 */\n\t\t\targtypes[0] = TIDOID;\t  /* ctid */\n\t\t\tbreak;\n\n\t\tdefault: /* 2 arguments */\n\t\t\targtypes[0] = context->materialization_range.type;\n\t\t\targtypes[1] = context->materialization_range.type;\n\t\t\tbreak;\n\t}\n\n\treturn argtypes;\n}\n\nstatic MaterializationPlan *\ncreate_materialization_plan(MaterializationContext *context, MaterializationPlanType plan_type)\n{\n\tAssert(plan_type >= PLAN_TYPE_INSERT);\n\tAssert(plan_type < _MAX_MATERIALIZATION_PLAN_TYPES);\n\n\tMaterializationPlan *materialization = &materialization_plans[plan_type];\n\n\tif (materialization->plan == NULL)\n\t{\n\t\tchar *query = materialization->create_statement(context);\n\t\tOid *argtypes =\n\t\t\tcreate_materialization_plan_argtypes(context, plan_type, materialization->nargs);\n\n\t\telog(DEBUG2, \"%s: %s\", __func__, query);\n\t\tmaterialization->plan = SPI_prepare(query, materialization->nargs, argtypes);\n\t\tif (materialization->plan == NULL)\n\t\t\telog(ERROR, \"%s: SPI_prepare failed: %s\", __func__, query);\n\n\t\tSPI_keepplan(materialization->plan);\n\t\tpfree(query);\n\t\tpfree(argtypes);\n\t}\n\n\treturn materialization;\n}\n\nstatic void\ncreate_materialization_plan_args(MaterializationContext *context, MaterializationPlanType plan_type,\n\t\t\t\t\t\t\t\t Datum **values, char **nulls)\n{\n\tswitch (plan_type)\n\t{\n\t\tcase PLAN_TYPE_RANGES_SELECT: /* 3 arguments */\n\t\tcase PLAN_TYPE_RANGES_PENDING:\n\t\t{\n\t\t\t/* read the maximum of one bucket before the window start and after the window end to\n\t\t\t * prevent pickup large pending ranges */\n\t\t\tconst int64 bucket_width =\n\t\t\t\tts_continuous_agg_bucket_width(context->cagg->bucket_function);\n\t\t\tconst int64 start_adjusted =\n\t\t\t\tcontext->internal_materialization_range.start_isnull ?\n\t\t\t\t\tcontext->internal_materialization_range.start :\n\t\t\t\t\tts_time_saturating_sub(context->internal_materialization_range.start,\n\t\t\t\t\t\t\t\t\t\t   bucket_width,\n\t\t\t\t\t\t\t\t\t\t   context->cagg->partition_type);\n\t\t\tconst int64 end_adjusted =\n\t\t\t\tcontext->internal_materialization_range.end_isnull ?\n\t\t\t\t\tcontext->internal_materialization_range.end :\n\t\t\t\t\tts_time_saturating_add(context->internal_materialization_range.end,\n\t\t\t\t\t\t\t\t\t\t   bucket_width,\n\t\t\t\t\t\t\t\t\t\t   context->cagg->partition_type);\n\n\t\t\t(*values)[0] = Int32GetDatum(context->cagg->data.mat_hypertable_id);\n\t\t\t(*values)[1] = Int64GetDatum(start_adjusted);\n\t\t\t(*values)[2] = Int64GetDatum(end_adjusted);\n\t\t\t(*nulls)[0] = false;\n\t\t\t(*nulls)[1] = false;\n\t\t\t(*nulls)[2] = false;\n\t\t\tbreak;\n\t\t}\n\n\t\tcase PLAN_TYPE_RANGES_DELETE: /* 1 argument */\n\t\t{\n\t\t\t(*values)[0] = ItemPointerGetDatum(context->tupleid);\n\t\t\t(*nulls)[0] = false;\n\t\t\tbreak;\n\t\t}\n\n\t\tdefault: /* 2 arguments */\n\t\t{\n\t\t\t(*values)[0] = context->materialization_range.start;\n\t\t\t(*values)[1] = context->materialization_range.end;\n\t\t\t(*nulls)[0] = false;\n\t\t\t(*nulls)[1] = false;\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nstatic uint64\nexecute_materialization_plan(MaterializationContext *context, MaterializationPlanType plan_type)\n{\n\tMaterializationPlan *materialization = create_materialization_plan(context, plan_type);\n\n\tDatum *values = (Datum *) palloc(materialization->nargs * sizeof(Datum));\n\tchar *nulls = (char *) palloc(materialization->nargs * sizeof(char));\n\n\tcreate_materialization_plan_args(context, plan_type, &values, &nulls);\n\n\tCatalogSecurityContext sec_ctx;\n\tif (materialization->catalog_security_context)\n\t\tts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);\n\n\tint res = SPI_execute_plan(materialization->plan, values, nulls, materialization->read_only, 0);\n\n\tif (materialization->catalog_security_context)\n\t\tts_catalog_restore_user(&sec_ctx);\n\n\tif (res < 0)\n\t{\n\t\tEnsure(materialization->error_message,\n\t\t\t   \"materialization plan error message not set for plan type %d\",\n\t\t\t   plan_type);\n\t\telog(ERROR,\n\t\t\t materialization->error_message,\n\t\t\t NameStr(*context->materialization_table.schema),\n\t\t\t NameStr(*context->materialization_table.name));\n\t}\n\telse if (materialization->progress_message)\n\t{\n\t\telog(LOG,\n\t\t\t materialization->progress_message,\n\t\t\t SPI_processed,\n\t\t\t NameStr(*context->materialization_table.schema),\n\t\t\t NameStr(*context->materialization_table.name));\n\t}\n\n\tif (SPI_processed > 0 && plan_type == PLAN_TYPE_RANGES_SELECT)\n\t{\n\t\tbool isnull;\n\t\tDatum dat;\n\n\t\tAssert(SPI_processed == 1);\n\n\t\t/* ctid */\n\t\tdat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);\n\t\tcontext->tupleid = DatumGetItemPointer(dat);\n\n\t\t/* lowest_modified_value */\n\t\tdat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 2, &isnull);\n\t\tcontext->materialization_range.start =\n\t\t\tinternal_to_time_value_or_infinite(DatumGetInt64(dat),\n\t\t\t\t\t\t\t\t\t\t\t   context->materialization_range.type,\n\t\t\t\t\t\t\t\t\t\t\t   NULL);\n\n\t\t/* greatest_modified_value */\n\t\tdat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 3, &isnull);\n\t\tcontext->materialization_range.end =\n\t\t\tinternal_to_time_value_or_infinite(DatumGetInt64(dat),\n\t\t\t\t\t\t\t\t\t\t\t   context->materialization_range.type,\n\t\t\t\t\t\t\t\t\t\t\t   NULL);\n\t}\n\n\tpfree(values);\n\tpfree(nulls);\n\n\treturn SPI_processed;\n}\n\nstatic void\nfree_materialization_plan(MaterializationContext *context, MaterializationPlanType plan_type)\n{\n\tMaterializationPlan *materialization = &materialization_plans[plan_type];\n\n\tif (materialization->plan != NULL)\n\t{\n\t\tSPI_freeplan(materialization->plan);\n\t\tmaterialization->plan = NULL;\n\t}\n}\n\nstatic void\nfree_materialization_plans(MaterializationContext *context)\n{\n\tfor (int plan_type = PLAN_TYPE_INSERT; plan_type < _MAX_MATERIALIZATION_PLAN_TYPES; plan_type++)\n\t{\n\t\tfree_materialization_plan(context, plan_type);\n\t}\n}\n\nstatic void\nupdate_watermark(MaterializationContext *context)\n{\n\tint res;\n\tStringInfoData command;\n\tOid types[] = { context->materialization_range.type };\n\tDatum values[] = { context->materialization_range.start };\n\tchar nulls[] = { false };\n\n\tinitStringInfo(&command);\n\tappendStringInfo(&command,\n\t\t\t\t\t \"SELECT %s FROM %s.%s AS I \"\n\t\t\t\t\t \"WHERE I.%s >= $1 \"\n\t\t\t\t\t \"ORDER BY 1 DESC LIMIT 1;\",\n\t\t\t\t\t quote_identifier(NameStr(*context->time_column_name)),\n\t\t\t\t\t quote_identifier(NameStr(*context->materialization_table.schema)),\n\t\t\t\t\t quote_identifier(NameStr(*context->materialization_table.name)),\n\t\t\t\t\t quote_identifier(NameStr(*context->time_column_name)));\n\n\telog(DEBUG2, \"%s: %s\", __func__, command.data);\n\tres = SPI_execute_with_args(command.data,\n\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\ttypes,\n\t\t\t\t\t\t\t\tvalues,\n\t\t\t\t\t\t\t\tnulls,\n\t\t\t\t\t\t\t\tfalse /* read_only */,\n\t\t\t\t\t\t\t\t0 /* count */);\n\n\tif (res < 0)\n\t\telog(ERROR, \"%s: could not get the last bucket of the materialized data\", __func__);\n\n\tEnsure(SPI_gettypeid(SPI_tuptable->tupdesc, 1) == context->materialization_range.type,\n\t\t   \"partition types for result (%d) and dimension (%d) do not match\",\n\t\t   SPI_gettypeid(SPI_tuptable->tupdesc, 1),\n\t\t   context->materialization_range.type);\n\n\tif (SPI_processed > 0)\n\t{\n\t\tbool isnull;\n\t\tDatum maxdat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);\n\n\t\tif (!isnull)\n\t\t{\n\t\t\tint64 watermark =\n\t\t\t\tts_time_value_to_internal(maxdat, context->materialization_range.type);\n\t\t\tts_cagg_watermark_update(context->mat_ht, watermark, isnull, false);\n\t\t}\n\t}\n}\n\nstatic void\nexecute_materializations(MaterializationContext *context)\n{\n\tvolatile uint64 rows_processed = 0;\n\tbool prev_enable_direct_compress_insert = ts_guc_enable_direct_compress_insert;\n\tbool prev_enable_direct_compress_insert_client_sorted =\n\t\tts_guc_enable_direct_compress_insert_client_sorted;\n\n\tif (has_direct_compress_on_cagg_refresh_enabled(context))\n\t{\n\t\t/* Force the direct compress on INSERT */\n\t\tSetConfigOption(\"timescaledb.enable_direct_compress_insert\",\n\t\t\t\t\t\t\"on\",\n\t\t\t\t\t\tPGC_USERSET,\n\t\t\t\t\t\tPGC_S_SESSION);\n\n\t\tSetConfigOption(\"timescaledb.enable_direct_compress_insert_client_sorted\",\n\t\t\t\t\t\t\"on\",\n\t\t\t\t\t\tPGC_USERSET,\n\t\t\t\t\t\tPGC_S_SESSION);\n\t}\n\n\tPG_TRY();\n\t{\n\t\twhile (execute_materialization_plan(context, PLAN_TYPE_RANGES_SELECT) > 0)\n\t\t{\n\t\t\t/* MERGE statement is supported only for non-compressed CAggs */\n\t\t\tif (ts_guc_enable_merge_on_cagg_refresh &&\n\t\t\t\t!TS_HYPERTABLE_HAS_COMPRESSION_ENABLED(context->mat_ht))\n\t\t\t{\n\t\t\t\t/* Fallback to INSERT materializations if there are no rows to change on it */\n\t\t\t\tif (execute_materialization_plan(context, PLAN_TYPE_EXISTS) == 0)\n\t\t\t\t{\n\t\t\t\t\telog(DEBUG2,\n\t\t\t\t\t\t \"no rows to merge on materialization table \\\"%s.%s\\\", falling back to \"\n\t\t\t\t\t\t \"INSERT\",\n\t\t\t\t\t\t NameStr(*context->materialization_table.schema),\n\t\t\t\t\t\t NameStr(*context->materialization_table.name));\n\t\t\t\t\trows_processed = execute_materialization_plan(context, PLAN_TYPE_INSERT);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\trows_processed += execute_materialization_plan(context, PLAN_TYPE_MERGE);\n\t\t\t\t\trows_processed += execute_materialization_plan(context, PLAN_TYPE_MERGE_DELETE);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\trows_processed += execute_materialization_plan(context, PLAN_TYPE_DELETE);\n\t\t\t\trows_processed += execute_materialization_plan(context, PLAN_TYPE_INSERT);\n\t\t\t}\n\n\t\t\t/* Delete the pending range entry */\n\t\t\trows_processed += execute_materialization_plan(context, PLAN_TYPE_RANGES_DELETE);\n\t\t}\n\n\t\t/* Free all cached plans */\n\t\tfree_materialization_plans(context);\n\t}\n\tPG_CATCH();\n\t{\n\t\t/* Make sure all cached plans in the session be released before rethrowing the error */\n\t\tfree_materialization_plans(context);\n\t\tPG_RE_THROW();\n\t}\n\tPG_END_TRY();\n\n\t/* Get the max(time_dimension) of the materialized data */\n\tif (rows_processed > 0)\n\t{\n\t\tupdate_watermark(context);\n\t}\n\n\t/* Restore previous GUC values */\n\tSetConfigOption(\"timescaledb.enable_direct_compress_insert\",\n\t\t\t\t\tprev_enable_direct_compress_insert ? \"on\" : \"off\",\n\t\t\t\t\tPGC_USERSET,\n\t\t\t\t\tPGC_S_SESSION);\n\tSetConfigOption(\"timescaledb.enable_direct_compress_insert_client_sorted\",\n\t\t\t\t\tprev_enable_direct_compress_insert_client_sorted ? \"on\" : \"off\",\n\t\t\t\t\tPGC_USERSET,\n\t\t\t\t\tPGC_S_SESSION);\n}\n"
  },
  {
    "path": "tsl/src/continuous_aggs/materialize.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include \"common.h\"\n#include \"ts_catalog/continuous_agg.h\"\n#include <fmgr.h>\n#include <nodes/pg_list.h>\n\ntypedef struct SchemaAndName\n{\n\tName schema;\n\tName name;\n} SchemaAndName;\n\n/***********************\n * Time ranges\n ***********************/\n\ntypedef struct TimeRange\n{\n\tOid type;\n\tDatum start;\n\tDatum end;\n} TimeRange;\n\ntypedef struct InternalTimeRange\n{\n\tOid type;\n\tint64 start; /* inclusive */\n\tint64 end;\t /* exclusive */\n\tbool start_isnull;\n\tbool end_isnull;\n} InternalTimeRange;\n\nvoid continuous_agg_update_materialization(Hypertable *mat_ht, const ContinuousAgg *cagg,\n\t\t\t\t\t\t\t\t\t\t   SchemaAndName partial_view,\n\t\t\t\t\t\t\t\t\t\t   SchemaAndName materialization_table,\n\t\t\t\t\t\t\t\t\t\t   const NameData *time_column_name,\n\t\t\t\t\t\t\t\t\t\t   InternalTimeRange materialization_range);\nbool continuous_agg_has_pending_materializations(const ContinuousAgg *cagg,\n\t\t\t\t\t\t\t\t\t\t\t\t InternalTimeRange materialization_range);\n"
  },
  {
    "path": "tsl/src/continuous_aggs/options.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n\n#include <access/xact.h>\n#include <catalog/namespace.h>\n#include <commands/view.h>\n#include <miscadmin.h>\n#include <nodes/makefuncs.h>\n#include <optimizer/optimizer.h>\n#include <rewrite/rewriteManip.h>\n#include <utils/builtins.h>\n\n#include \"cache.h\"\n#include \"compression/create.h\"\n#include \"continuous_aggs/common.h\"\n#include \"continuous_aggs/create.h\"\n#include \"errors.h\"\n#include \"hypertable_cache.h\"\n#include \"options.h\"\n#include \"scan_iterator.h\"\n#include \"ts_catalog/array_utils.h\"\n#include \"ts_catalog/continuous_agg.h\"\n#include \"with_clause/alter_table_with_clause.h\"\n#include \"with_clause/create_materialized_view_with_clause.h\"\n\nstatic void cagg_update_materialized_only(ContinuousAgg *agg, bool materialized_only);\nstatic List *cagg_get_compression_params(ContinuousAgg *agg, Hypertable *mat_ht,\n\t\t\t\t\t\t\t\t\t\t WithClauseResult *with_clause_options);\nstatic void cagg_alter_compression(ContinuousAgg *agg, Hypertable *mat_ht, List *compress_defelems);\n\nstatic void\ncagg_update_materialized_only(ContinuousAgg *agg, bool materialized_only)\n{\n\tScanIterator iterator =\n\t\tts_scan_iterator_create(CONTINUOUS_AGG, RowExclusiveLock, CurrentMemoryContext);\n\titerator.ctx.index = catalog_get_index(ts_catalog_get(), CONTINUOUS_AGG, CONTINUOUS_AGG_PKEY);\n\n\tts_scan_iterator_scan_key_init(&iterator,\n\t\t\t\t\t\t\t\t   Anum_continuous_agg_pkey_mat_hypertable_id,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t\t\t   Int32GetDatum(agg->data.mat_hypertable_id));\n\n\tts_scanner_foreach(&iterator)\n\t{\n\t\tTupleInfo *ti = ts_scan_iterator_tuple_info(&iterator);\n\t\tbool nulls[Natts_continuous_agg];\n\t\tDatum values[Natts_continuous_agg];\n\t\tbool doReplace[Natts_continuous_agg] = { false };\n\t\tbool should_free;\n\t\tHeapTuple tuple = ts_scan_iterator_fetch_heap_tuple(&iterator, false, &should_free);\n\t\tHeapTuple new_tuple;\n\t\tTupleDesc tupdesc = ts_scan_iterator_tupledesc(&iterator);\n\n\t\theap_deform_tuple(tuple, tupdesc, values, nulls);\n\n\t\tdoReplace[AttrNumberGetAttrOffset(Anum_continuous_agg_materialize_only)] = true;\n\t\tvalues[AttrNumberGetAttrOffset(Anum_continuous_agg_materialize_only)] =\n\t\t\tBoolGetDatum(materialized_only);\n\n\t\tnew_tuple = heap_modify_tuple(tuple, tupdesc, values, nulls, doReplace);\n\n\t\tts_catalog_update(ti->scanrel, new_tuple);\n\t\theap_freetuple(new_tuple);\n\n\t\tif (should_free)\n\t\t\theap_freetuple(tuple);\n\n\t\tbreak;\n\t}\n\tts_scan_iterator_close(&iterator);\n}\n\n/* get the compression parameters for cagg. The parameters are\n * derived from the cagg view definition.\n * Computes:\n * compress_orderby = time_bucket column from cagg query followed by remaining grouping columns\n */\nstatic List *\ncagg_get_compression_params(ContinuousAgg *agg, Hypertable *mat_ht,\n\t\t\t\t\t\t\tWithClauseResult *with_clause_options)\n{\n\tconst Dimension *mat_ht_dim = hyperspace_get_open_dimension(mat_ht->space, 0);\n\tStringInfoData info;\n\tinitStringInfo(&info);\n\tArrayType *segmentby_columns = NULL;\n\n\t/* add time column as first entry */\n\tappendStringInfoString(&info, quote_identifier(NameStr(mat_ht_dim->fd.column_name)));\n\n\tif (with_clause_options[AlterTableFlagSegmentBy].parsed)\n\t{\n\t\tsegmentby_columns =\n\t\t\tts_compress_hypertable_parse_segment_by(with_clause_options[AlterTableFlagSegmentBy],\n\t\t\t\t\t\t\t\t\t\t\t\t\tmat_ht);\n\t}\n\n\tList *grp_colnames = cagg_find_groupingcols(agg, mat_ht);\n\tif (grp_colnames)\n\t{\n\t\tListCell *lc;\n\t\tforeach (lc, grp_colnames)\n\t\t{\n\t\t\tchar *grpcol = (char *) lfirst(lc);\n\t\t\t/* skip time dimension since we put it as first entry */\n\t\t\tif (namestrcmp((Name) & (mat_ht_dim->fd.column_name), grpcol) == 0)\n\t\t\t\tcontinue;\n\n\t\t\tif (segmentby_columns && ts_array_is_member(segmentby_columns, grpcol))\n\t\t\t\tcontinue;\n\n\t\t\tif (info.len > 0)\n\t\t\t\tappendStringInfoString(&info, \",\");\n\t\t\tappendStringInfoString(&info, quote_identifier(grpcol));\n\t\t}\n\t}\n\n\tDefElem *ordby = makeDefElemExtended(EXTENSION_NAMESPACE,\n\t\t\t\t\t\t\t\t\t\t \"compress_orderby\",\n\t\t\t\t\t\t\t\t\t\t (Node *) makeString(info.data),\n\t\t\t\t\t\t\t\t\t\t DEFELEM_UNSPEC,\n\t\t\t\t\t\t\t\t\t\t -1);\n\n\treturn list_make1(ordby);\n}\n\n/* forwards compression related changes via an alter statement to the underlying HT */\nstatic void\ncagg_alter_compression(ContinuousAgg *agg, Hypertable *mat_ht, List *compress_defelems)\n{\n\tAssert(mat_ht != NULL);\n\tWithClauseResult *with_clause_options = ts_alter_table_with_clause_parse(compress_defelems);\n\n\tif (with_clause_options[AlterTableFlagColumnstore].parsed)\n\t{\n\t\tList *default_compress_defelems =\n\t\t\tcagg_get_compression_params(agg, mat_ht, with_clause_options);\n\t\tWithClauseResult *default_with_clause_options =\n\t\t\tts_alter_table_with_clause_parse(default_compress_defelems);\n\t\t/* Merge defaults if there's any. */\n\t\tfor (int i = 0; i < AlterTableFlagsMax; i++)\n\t\t{\n\t\t\tif (with_clause_options[i].is_default && !default_with_clause_options[i].is_default)\n\t\t\t{\n\t\t\t\twith_clause_options[i] = default_with_clause_options[i];\n\t\t\t\telog(NOTICE,\n\t\t\t\t\t \"defaulting %s to %s\",\n\t\t\t\t\t with_clause_options[i].definition->arg_names[0],\n\t\t\t\t\t ts_with_clause_result_deparse_value(&with_clause_options[i]));\n\t\t\t}\n\t\t}\n\t}\n\n\ttsl_process_compress_table(mat_ht, with_clause_options);\n}\n\nvoid\ncontinuous_agg_update_options(ContinuousAgg *agg, WithClauseResult *with_clause_options)\n{\n\tif (!with_clause_options[CreateMaterializedViewFlagContinuous].is_default)\n\t\telog(ERROR, \"cannot disable continuous aggregates\");\n\n\tif (!with_clause_options[CreateMaterializedViewFlagMaterializedOnly].is_default)\n\t{\n\t\tbool materialized_only =\n\t\t\tDatumGetBool(with_clause_options[CreateMaterializedViewFlagMaterializedOnly].parsed);\n\n\t\tCache *hcache = ts_hypertable_cache_pin();\n\t\tHypertable *mat_ht =\n\t\t\tts_hypertable_cache_get_entry_by_id(hcache, agg->data.mat_hypertable_id);\n\n\t\tif (materialized_only == agg->data.materialized_only)\n\t\t{\n\t\t\t/* nothing changed, so just return */\n\t\t\tts_cache_release(&hcache);\n\t\t\treturn;\n\t\t}\n\n\t\tAssert(mat_ht != NULL);\n\n\t\tcagg_flip_realtime_view_definition(agg, mat_ht);\n\t\tcagg_update_materialized_only(agg, materialized_only);\n\t\tts_cache_release(&hcache);\n\t}\n\n\tif (!with_clause_options[CreateMaterializedViewFlagChunkTimeInterval].is_default)\n\t{\n\t\tCache *hcache = ts_hypertable_cache_pin();\n\t\tHypertable *mat_ht =\n\t\t\tts_hypertable_cache_get_entry_by_id(hcache, agg->data.mat_hypertable_id);\n\n\t\tint64 interval = interval_to_usec(DatumGetIntervalP(\n\t\t\twith_clause_options[CreateMaterializedViewFlagChunkTimeInterval].parsed));\n\t\tDimension *dim = ts_hyperspace_get_mutable_dimension(mat_ht->space, DIMENSION_TYPE_OPEN, 0);\n\n\t\tts_dimension_set_chunk_interval(dim, interval);\n\t\tts_cache_release(&hcache);\n\t}\n\n\tList *compression_options = ts_continuous_agg_get_compression_defelems(with_clause_options);\n\n\tif (list_length(compression_options) > 0)\n\t{\n\t\tCache *hcache = ts_hypertable_cache_pin();\n\t\tHypertable *mat_ht =\n\t\t\tts_hypertable_cache_get_entry_by_id(hcache, agg->data.mat_hypertable_id);\n\t\tAssert(mat_ht != NULL);\n\n\t\tcagg_alter_compression(agg, mat_ht, compression_options);\n\t\tts_cache_release(&hcache);\n\t}\n\n\tif (!with_clause_options[CreateMaterializedViewFlagCreateGroupIndexes].is_default)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot alter create_group_indexes option for continuous aggregates\")));\n\t}\n}\n"
  },
  {
    "path": "tsl/src/continuous_aggs/options.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include \"ts_catalog/continuous_agg.h\"\n#include \"with_clause/with_clause_parser.h\"\n\nextern void continuous_agg_update_options(ContinuousAgg *cagg,\n\t\t\t\t\t\t\t\t\t\t  WithClauseResult *with_clause_options);\n"
  },
  {
    "path": "tsl/src/continuous_aggs/planner.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n\n#include <nodes/makefuncs.h>\n#include <nodes/nodeFuncs.h>\n#include <nodes/nodes.h>\n#include <nodes/pg_list.h>\n#include <parser/parse_func.h>\n\n#include \"continuous_aggs/common.h\"\n#include \"planner.h\"\n#include \"ts_catalog/continuous_aggs_watermark.h\"\n\n/*\n * The watermark function of a CAgg query is embedded into further functions. It\n * has the following structure:\n *\n * 1. a coalesce expression\n * 2. an optional to timestamp conversion function (date, timestamp, timestamptz)\n * 3. the actual watermark function\n *\n * For example:\n *        ... COALESCE(to_timestamp(cagg_watermark(59)), XXX) ...\n *        ... COALESCE(cagg_watermark(59), XXX) ...\n *\n * We use the following data structure while walking to the query to analyze the query\n * and collect references to the needed functions. The data structure contains:\n *\n *   (a) values (e.g., function Oids) which are needed to analyze the query\n *   (b) values that are changed during walking through the query\n *       (e.g., references to parent functions)\n *   (c) result data like the watermark functions and their parent functions\n */\ntypedef struct\n{\n\t/* (a) Values initialized after creating the context */\n\tList *to_timestamp_func_oids; // List of Oids of the timestamp conversion functions\n\n\t/* (b) Values changed while walking through the query */\n\tCoalesceExpr *parent_coalesce_expr; // the current parent coalesce_expr\n\tFuncExpr *parent_to_timestamp_func; // the current parent timestamp function\n\n\t/* (c) Result values */\n\tList *watermark_parent_functions; // List of parent functions of a watermark (1) and (2)\n\tList *watermark_functions;\t\t  // List of watermark functions (3)\n\tList *relids;\t\t\t\t\t  // List of used relids by the query\n\tbool valid_query;\t\t\t\t  // Is the query valid a valid CAgg query or not\n} ConstifyWatermarkContext;\n\n/* Oid of the watermark function. It can be stored into a static variable because it will not\n * change over the lifetime of a backend session, so we can lookup it only once.\n */\nstatic Oid watermark_function_oid = InvalidOid;\n\n/*\n * Walk through the elements of the query and detect the watermark functions and their\n * parent functions.\n */\nstatic bool\nconstify_cagg_watermark_walker(Node *node, ConstifyWatermarkContext *context)\n{\n\tif (node == NULL)\n\t\treturn false;\n\n\tif (IsA(node, FuncExpr))\n\t{\n\t\tFuncExpr *funcExpr = castNode(FuncExpr, node);\n\n\t\t/* Handle watermark function */\n\t\tif (watermark_function_oid == funcExpr->funcid)\n\t\t{\n\t\t\t/* The watermark function takes exactly one argument */\n\t\t\tAssert(list_length(funcExpr->args) == 1);\n\n\t\t\t/* No coalesce expression found so far or function parameter is not constant, we are not\n\t\t\t * interested in this expression */\n\t\t\tif (context->parent_coalesce_expr == NULL || !IsA(linitial(funcExpr->args), Const) ||\n\t\t\t\t(castNode(Const, linitial(funcExpr->args))->constisnull))\n\t\t\t{\n\t\t\t\tcontext->valid_query = false;\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tcontext->watermark_functions = lappend(context->watermark_functions, funcExpr);\n\n\t\t\t/* Only on time based hypertables, we have a to_timestamp function */\n\t\t\tif (context->parent_to_timestamp_func != NULL)\n\t\t\t{\n\t\t\t\t/* to_timestamp functions take only one parameter. This should be a reference to our\n\t\t\t\t * function */\n\t\t\t\tAssert(linitial(context->parent_to_timestamp_func->args) == node);\n\n\t\t\t\tcontext->watermark_parent_functions =\n\t\t\t\t\tlappend(context->watermark_parent_functions, context->parent_to_timestamp_func);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t/* For non int64 partitioned tables, the watermark function is wrapped into a cast\n\t\t\t\t * for example: COALESCE((_timescaledb_functions.cagg_watermark(11))::integer,\n\t\t\t\t * '-2147483648'::integer))\n\t\t\t\t */\n\t\t\t\tNode *coalesce_arg = linitial(context->parent_coalesce_expr->args);\n\t\t\t\tif (coalesce_arg != node)\n\t\t\t\t{\n\t\t\t\t\t/* Check if the watermark function is wrapped into a cast function */\n\t\t\t\t\tif (!IsA(coalesce_arg, FuncExpr) || ((FuncExpr *) coalesce_arg)->args == NIL ||\n\t\t\t\t\t\tlinitial(((FuncExpr *) coalesce_arg)->args) != node)\n\t\t\t\t\t{\n\t\t\t\t\t\tcontext->valid_query = false;\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\n\t\t\t\t\tcontext->watermark_parent_functions =\n\t\t\t\t\t\tlappend(context->watermark_parent_functions, coalesce_arg);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tcontext->watermark_parent_functions =\n\t\t\t\t\t\tlappend(context->watermark_parent_functions, context->parent_coalesce_expr);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t/* Capture the timestamp conversion function */\n\t\tif (list_member_oid(context->to_timestamp_func_oids, funcExpr->funcid))\n\t\t{\n\t\t\tFuncExpr *old_func_expr = context->parent_to_timestamp_func;\n\t\t\tcontext->parent_to_timestamp_func = funcExpr;\n\t\t\tbool result = expression_tree_walker(node, constify_cagg_watermark_walker, context);\n\t\t\tcontext->parent_to_timestamp_func = old_func_expr;\n\n\t\t\treturn result;\n\t\t}\n\t}\n\telse if (IsA(node, Query))\n\t{\n\t\t/* Recurse into subselects */\n\t\tQuery *query = castNode(Query, node);\n\t\treturn query_tree_walker(query,\n\t\t\t\t\t\t\t\t constify_cagg_watermark_walker,\n\t\t\t\t\t\t\t\t context,\n\t\t\t\t\t\t\t\t QTW_EXAMINE_RTES_BEFORE);\n\t}\n\telse if (IsA(node, CoalesceExpr))\n\t{\n\t\t/* Capture the CoalesceExpr */\n\t\tCoalesceExpr *parent_coalesce_expr = context->parent_coalesce_expr;\n\t\tcontext->parent_coalesce_expr = castNode(CoalesceExpr, node);\n\t\tbool result = expression_tree_walker(node, constify_cagg_watermark_walker, context);\n\t\tcontext->parent_coalesce_expr = parent_coalesce_expr;\n\n\t\treturn result;\n\t}\n\telse if (IsA(node, RangeTblEntry))\n\t{\n\t\t/* Collect the Oid of the used range tables */\n\t\tRangeTblEntry *rte = (RangeTblEntry *) node;\n\n\t\tif (rte->rtekind == RTE_RELATION)\n\t\t{\n\t\t\tcontext->relids = list_append_unique_oid(context->relids, rte->relid);\n\t\t}\n\n\t\t/* allow range_table_walker to continue */\n\t\treturn false;\n\t}\n\n\treturn expression_tree_walker(node, constify_cagg_watermark_walker, context);\n}\n\n/*\n * The entry of the watermark HTAB.\n */\ntypedef struct WatermarkConstEntry\n{\n\tint32 key;\n\tConst *watermark_constant;\n} WatermarkConstEntry;\n\n/* The query can contain multiple watermarks (i.e., two hierarchal real-time CAggs)\n * We maintain a hash map (hypertable id -> constant) to ensure we use the same constant\n * for the same watermark across the while query.\n */\nstatic HTAB *pg_nodiscard\ninit_watermark_map()\n{\n\tstruct HASHCTL hctl = {\n\t\t.keysize = sizeof(int32),\n\t\t.entrysize = sizeof(WatermarkConstEntry),\n\t\t.hcxt = CurrentMemoryContext,\n\t};\n\n\t/* Use 4 initial elements to have enough space for normal and hierarchical CAggs */\n\treturn hash_create(\"Watermark const values\", 4, &hctl, HASH_ELEM | HASH_CONTEXT | HASH_BLOBS);\n}\n\n/*\n * Get a constant value for our watermark function. The constant is cached\n * in a hash map to ensure we use the same constant for invocations of the\n * watermark function with the same parameter across the whole query.\n */\nstatic Const *\nget_watermark_const(HTAB *watermarks, int32 watermark_hypertable_id, List *range_table_oids)\n{\n\tbool found;\n\tWatermarkConstEntry *watermark_const =\n\t\thash_search(watermarks, &watermark_hypertable_id, HASH_ENTER, &found);\n\n\tif (!found)\n\t{\n\t\t/*\n\t\t * Check that the argument of the watermark function is also a range table of the query. We\n\t\t * only constify the value when this condition is true. Only in this case, the query will be\n\t\t * removed from the query cache by PostgreSQL when an invalidation for the watermark\n\t\t * hypertable is processed (see CacheInvalidateRelcacheByRelid).\n\t\t */\n\t\tOid ht_relid = ts_hypertable_id_to_relid(watermark_hypertable_id, true);\n\n\t\tif (!OidIsValid(ht_relid))\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"invalid materialized hypertable ID: %d\", watermark_hypertable_id)));\n\t\t}\n\n\t\t/* Given table is not a part of our range tables */\n\t\tif (!list_member_oid(range_table_oids, ht_relid))\n\t\t{\n\t\t\twatermark_const->watermark_constant = NULL;\n\t\t\treturn NULL;\n\t\t}\n\n\t\t/* Not found, create a new constant */\n\t\tint64 watermark = ts_cagg_watermark_get(watermark_hypertable_id);\n\t\tConst *const_watermark = makeConst(INT8OID,\n\t\t\t\t\t\t\t\t\t\t   -1,\n\t\t\t\t\t\t\t\t\t\t   InvalidOid,\n\t\t\t\t\t\t\t\t\t\t   sizeof(int64),\n\t\t\t\t\t\t\t\t\t\t   Int64GetDatum(watermark),\n\t\t\t\t\t\t\t\t\t\t   false,\n\t\t\t\t\t\t\t\t\t\t   FLOAT8PASSBYVAL);\n\t\twatermark_const->watermark_constant = const_watermark;\n\t}\n\n\treturn watermark_const->watermark_constant;\n}\n\n/*\n * Replace the collected references to the watermark function in the context variable\n * with constant values.\n */\nstatic void\nreplace_watermark_with_const(ConstifyWatermarkContext *context)\n{\n\tAssert(context != NULL);\n\tAssert(context->valid_query);\n\n\t/* We need to have at least one watermark value */\n\tif (list_length(context->watermark_functions) < 1)\n\t\treturn;\n\n\tHTAB *watermarks = init_watermark_map();\n\n\t/* The list of watermark function should have the same length as the parent functions. In\n\t * other words, each watermark function should have exactly one parent function. */\n\tAssert(list_length(context->watermark_parent_functions) ==\n\t\t   list_length(context->watermark_functions));\n\n\t/* Iterate over the function parents and the actual watermark functions. Get a\n\t * const value for each function and replace the reference to the watermark function\n\t * in the function parent.\n\t */\n\tListCell *parent_lc, *watermark_lc;\n\tforboth (parent_lc,\n\t\t\t context->watermark_parent_functions,\n\t\t\t watermark_lc,\n\t\t\t context->watermark_functions)\n\t{\n\t\tFuncExpr *watermark_function = lfirst(watermark_lc);\n\t\tAssert(watermark_function_oid == watermark_function->funcid);\n\t\tConst *arg = (Const *) linitial(watermark_function->args);\n\t\tint32 watermark_hypertable_id = DatumGetInt32(arg->constvalue);\n\n\t\tConst *watermark_const =\n\t\t\tget_watermark_const(watermarks, watermark_hypertable_id, context->relids);\n\n\t\t/* No constant created, it means the hypertable id used by the watermark function is not a\n\t\t * range table and no invalidations would be processed. So, not replacing the function\n\t\t * invocation. */\n\t\tif (watermark_const == NULL)\n\t\t\tcontinue;\n\n\t\t/* Replace cagg_watermark FuncExpr node by a Const node */\n\t\tif (IsA(lfirst(parent_lc), FuncExpr))\n\t\t{\n\t\t\tFuncExpr *parent_func_expr = castNode(FuncExpr, lfirst(parent_lc));\n\t\t\tlinitial(parent_func_expr->args) = (Node *) watermark_const;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* Check that the assumed parent function is our parent function */\n\t\t\tCoalesceExpr *parent_coalesce_expr = castNode(CoalesceExpr, lfirst(parent_lc));\n\t\t\tlinitial(parent_coalesce_expr->args) = (Node *) watermark_const;\n\t\t}\n\t}\n\n\t/* Clean up the hash map */\n\thash_destroy(watermarks);\n}\n\n/*\n * Constify all references to the CAgg watermark function if the query is a union query on a CAgg\n */\nvoid\nconstify_cagg_watermark(Query *parse)\n{\n\tif (parse == NULL)\n\t\treturn;\n\n\t/* process only SELECT queries */\n\tif (parse->commandType != CMD_SELECT)\n\t\treturn;\n\n\tNode *node = (Node *) parse;\n\n\tConstifyWatermarkContext context = { 0 };\n\tcontext.valid_query = true;\n\n\tif (!OidIsValid(watermark_function_oid))\n\t{\n\t\twatermark_function_oid = get_watermark_function_oid();\n\n\t\tEnsure(OidIsValid(watermark_function_oid), \"unable to determine watermark function Oid\");\n\t}\n\n\t/* Get Oid of all used timestamp converter functions.\n\t *\n\t * The watermark function can be invoked by a timestamp conversion function.\n\t * For example: to_timestamp(cagg_watermark(XX)). We collect the Oid of all these\n\t * converter functions in the list to_timestamp_func_oids.\n\t */\n\tcontext.to_timestamp_func_oids = NIL;\n\n\tcontext.to_timestamp_func_oids =\n\t\tlappend_oid(context.to_timestamp_func_oids, cagg_get_boundary_converter_funcoid(DATEOID));\n\n\tcontext.to_timestamp_func_oids = lappend_oid(context.to_timestamp_func_oids,\n\t\t\t\t\t\t\t\t\t\t\t\t cagg_get_boundary_converter_funcoid(TIMESTAMPOID));\n\n\tcontext.to_timestamp_func_oids =\n\t\tlappend_oid(context.to_timestamp_func_oids,\n\t\t\t\t\tcagg_get_boundary_converter_funcoid(TIMESTAMPTZOID));\n\n\t/* Walk through the query and collect function information */\n\tconstify_cagg_watermark_walker(node, &context);\n\n\t/* Replace watermark functions with const value if the query might belong to the CAgg query */\n\tif (context.valid_query)\n\t\treplace_watermark_with_const(&context);\n}\n\n/*\n * Push down ORDER BY and LIMIT into subqueries of UNION for realtime\n * continuous aggregates when sorting by time.\n *\n * This is only enabled on PG16 and above because the internal structure is different\n * in previous versions.\n */\nvoid\ncagg_sort_pushdown(Query *parse, int *cursor_opts)\n{\n\tListCell *lc;\n\n\t/* We dont optimize aggregations on top of caggs for now. */\n\tif (parse->groupClause)\n\t\treturn;\n\n\t/* Nothing to do if we have no valid sort clause */\n\tif (list_length(parse->rtable) != 1 || list_length(parse->sortClause) != 1 ||\n\t\t!OidIsValid(linitial_node(SortGroupClause, parse->sortClause)->sortop))\n\t\treturn;\n\n\tCache *cache = ts_hypertable_cache_pin();\n\n\tforeach (lc, parse->rtable)\n\t{\n\t\tRangeTblEntry *rte = lfirst(lc);\n\n\t\t/*\n\t\t * Realtime cagg view will have 2 rtable entries, one for the materialized data and one for\n\t\t * the not yet materialized data.\n\t\t */\n\t\tif (rte->rtekind != RTE_SUBQUERY || rte->relkind != RELKIND_VIEW ||\n\t\t\tlist_length(rte->subquery->rtable) != 2)\n\t\t\tcontinue;\n\n\t\tContinuousAgg *cagg = ts_continuous_agg_find_by_relid(rte->relid);\n\n\t\t/*\n\t\t * This optimization only applies to realtime caggs.\n\t\t */\n\t\tif (!cagg || cagg->data.materialized_only)\n\t\t\tcontinue;\n\n\t\tHypertable *ht = ts_hypertable_cache_get_entry_by_id(cache, cagg->data.mat_hypertable_id);\n\t\tDimension const *dim = hyperspace_get_open_dimension(ht->space, 0);\n\n\t\t/* We should only encounter hypertables with an open dimension */\n\t\tif (!dim)\n\t\t\tcontinue;\n\n\t\tSortGroupClause *sort = linitial_node(SortGroupClause, parse->sortClause);\n\t\tTargetEntry *tle = get_sortgroupref_tle(sort->tleSortGroupRef, parse->targetList);\n\n\t\t/*\n\t\t * We only pushdown ORDER BY when it's single column\n\t\t * ORDER BY on the time column.\n\t\t */\n\t\tAttrNumber time_col = dim->column_attno;\n\t\tif (!IsA(tle->expr, Var) || castNode(Var, tle->expr)->varattno != time_col)\n\t\t\tcontinue;\n\n\t\tRangeTblEntry *mat_rte = linitial_node(RangeTblEntry, rte->subquery->rtable);\n\t\tRangeTblEntry *rt_rte = lsecond_node(RangeTblEntry, rte->subquery->rtable);\n\n\t\tmat_rte->subquery->sortClause = list_copy(parse->sortClause);\n\t\trt_rte->subquery->sortClause = list_copy(parse->sortClause);\n\n\t\tTargetEntry *mat_tle = list_nth(mat_rte->subquery->targetList, time_col - 1);\n\t\tTargetEntry *rt_tle = list_nth(rt_rte->subquery->targetList, time_col - 1);\n\n\t\tSortGroupClause *cagg_group = linitial(rt_rte->subquery->groupClause);\n\t\tcagg_group = list_nth(rt_rte->subquery->groupClause, rt_tle->ressortgroupref - 1);\n\t\tcagg_group->sortop = sort->sortop;\n\t\tcagg_group->nulls_first = sort->nulls_first;\n#if PG18_GE\n\t\t/* Track sort order\n\t\t * https://github.com/postgres/postgres/commit/0d2aa4d4\n\t\t */\n\t\tcagg_group->reverse_sort = sort->reverse_sort;\n#endif\n\n\t\tlinitial_node(SortGroupClause, rt_rte->subquery->sortClause)->tleSortGroupRef =\n\t\t\trt_tle->ressortgroupref;\n\t\tmat_tle->ressortgroupref =\n\t\t\tlinitial_node(SortGroupClause, mat_rte->subquery->sortClause)->tleSortGroupRef;\n\n\t\tOid placeholder;\n\t\tCompareType strategy;\n\t\tget_ordering_op_properties(sort->sortop, &placeholder, &placeholder, &strategy);\n\n\t\t/*\n\t\t * If this is DESC order and the sortop is the commutator of the cagg_group sortop,\n\t\t * we can align the sortop of the cagg_group with the sortop of the sort clause, which\n\t\t * will allow us to have the GroupAggregate node to produce the correct order and avoid\n\t\t * having to resort.\n\t\t */\n\t\tif (strategy == BTGreaterStrategyNumber)\n\t\t{\n\t\t\trte->subquery->rtable = list_make2(rt_rte, mat_rte);\n\t\t}\n\n\t\t/*\n\t\t * We have to prevent parallelism when we do this optimization because\n\t\t * the subplans of the Append have to be processed sequentially.\n\t\t */\n\t\t*cursor_opts = *cursor_opts & ~CURSOR_OPT_PARALLEL_OK;\n\t\tparse->sortClause = NIL;\n\t\trte->subquery->sortClause = NIL;\n\t}\n\tts_cache_release(&cache);\n}\n"
  },
  {
    "path": "tsl/src/continuous_aggs/planner.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#ifndef TIMESCALEDB_TSL_CONTINUOUS_AGGS_PLANNER_H\n#define TIMESCALEDB_TSL_CONTINUOUS_AGGS_PLANNER_H\n\n#include \"planner/planner.h\"\n\nvoid constify_cagg_watermark(Query *parse);\nvoid cagg_sort_pushdown(Query *parse, int *cursor_opts);\n\n#endif\n"
  },
  {
    "path": "tsl/src/continuous_aggs/refresh.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n\n#include <access/xact.h>\n#include <executor/spi.h>\n#include <fmgr.h>\n#include <miscadmin.h>\n#include <storage/lmgr.h>\n#include <utils/acl.h>\n#include <utils/builtins.h>\n#include <utils/date.h>\n#include <utils/fmgrprotos.h>\n#include <utils/guc.h>\n#include <utils/lsyscache.h>\n#include <utils/snapmgr.h>\n\n#include \"bgw_policy/policies_v2.h\"\n#include \"debug_point.h\"\n#include \"dimension.h\"\n#include \"dimension_slice.h\"\n#include \"guc.h\"\n#include \"hypertable.h\"\n#include \"invalidation.h\"\n#include \"invalidation_threshold.h\"\n#include \"jsonb_utils.h\"\n#include \"materialize.h\"\n#include \"process_utility.h\"\n#include \"refresh.h\"\n#include \"time_bucket.h\"\n#include \"time_utils.h\"\n#include \"ts_catalog/catalog.h\"\n#include \"ts_catalog/continuous_agg.h\"\n\n#define CAGG_REFRESH_LOG_LEVEL                                                                     \\\n\t(context.callctx == CAGG_REFRESH_POLICY || context.callctx == CAGG_REFRESH_POLICY_BATCHED ?    \\\n\t\t LOG :                                                                                     \\\n\t\t DEBUG1)\n\ntypedef struct ContinuousAggRefreshState\n{\n\tContinuousAgg cagg;\n\tHypertable *cagg_ht;\n\tInternalTimeRange refresh_window;\n\tSchemaAndName partial_view;\n\tbool bucketing_refresh_window;\n} ContinuousAggRefreshState;\n\nstatic Hypertable *cagg_get_hypertable_or_fail(int32 hypertable_id);\nstatic InternalTimeRange get_largest_bucketed_window(Oid timetype, int64 bucket_width);\nstatic InternalTimeRange\ncompute_inscribed_bucketed_refresh_window(const ContinuousAgg *cagg,\n\t\t\t\t\t\t\t\t\t\t  const InternalTimeRange *const refresh_window,\n\t\t\t\t\t\t\t\t\t\t  const int64 bucket_width);\nstatic void continuous_agg_refresh_init(ContinuousAggRefreshState *refresh,\n\t\t\t\t\t\t\t\t\t\tconst ContinuousAgg *cagg,\n\t\t\t\t\t\t\t\t\t\tconst InternalTimeRange *refresh_window,\n\t\t\t\t\t\t\t\t\t\tbool bucketing_refresh_window);\nstatic void continuous_agg_refresh_execute(const ContinuousAggRefreshState *refresh,\n\t\t\t\t\t\t\t\t\t\t   const InternalTimeRange *bucketed_refresh_window);\nstatic void log_refresh_window(int elevel, const ContinuousAgg *cagg,\n\t\t\t\t\t\t\t   const InternalTimeRange *refresh_window,\n\t\t\t\t\t\t\t   ContinuousAggRefreshContext context);\nstatic void continuous_agg_refresh_execute_wrapper(const InternalTimeRange *bucketed_refresh_window,\n\t\t\t\t\t\t\t\t\t\t\t\t   const ContinuousAggRefreshContext context,\n\t\t\t\t\t\t\t\t\t\t\t\t   const long iteration, void *arg1_refresh);\nstatic void continuous_agg_refresh_with_window(const ContinuousAgg *cagg,\n\t\t\t\t\t\t\t\t\t\t\t   const InternalTimeRange *refresh_window,\n\t\t\t\t\t\t\t\t\t\t\t   const InvalidationStore *invalidations,\n\n\t\t\t\t\t\t\t\t\t\t\t   const ContinuousAggRefreshContext context,\n\t\t\t\t\t\t\t\t\t\t\t   bool bucketing_refresh_window);\nstatic void emit_up_to_date_notice(const ContinuousAgg *cagg,\n\t\t\t\t\t\t\t\t   const ContinuousAggRefreshContext context);\nstatic bool process_cagg_invalidations_and_refresh(const ContinuousAgg *cagg,\n\t\t\t\t\t\t\t\t\t\t\t\t   const InternalTimeRange *refresh_window,\n\t\t\t\t\t\t\t\t\t\t\t\t   const ContinuousAggRefreshContext context,\n\t\t\t\t\t\t\t\t\t\t\t\t   bool bucketing_refresh_window, bool force);\nstatic Hypertable *\ncagg_get_hypertable_or_fail(int32 hypertable_id)\n{\n\tHypertable *ht = ts_hypertable_get_by_id(hypertable_id);\n\n\tif (NULL == ht)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INTERNAL_ERROR),\n\t\t\t\t errmsg(\"invalid continuous aggregate state\"),\n\t\t\t\t errdetail(\"A continuous aggregate references a hypertable that does not exist.\")));\n\n\treturn ht;\n}\n\n/*\n * Compute the largest possible bucketed window given the time type and\n * internal restrictions.\n *\n * The largest bucketed window is governed by restrictions set by the type and\n * internal, TimescaleDB-specific legacy details (see get_max_window above for\n * further explanation).\n */\nstatic InternalTimeRange\nget_largest_bucketed_window(Oid timetype, int64 bucket_width)\n{\n\tInternalTimeRange maxwindow = {\n\t\t.type = timetype,\n\t\t.start = ts_time_get_min(timetype),\n\t\t.end = ts_time_get_end_or_max(timetype),\n\t};\n\tInternalTimeRange maxbuckets = {\n\t\t.type = timetype,\n\t};\n\n\t/* For the MIN value, the corresponding bucket either falls on the exact\n\t * MIN or it will be below it. Therefore, we add (bucket_width - 1) to\n\t * move to the next bucket to be within the allowed range. */\n\tmaxwindow.start = ts_time_saturating_add(maxwindow.start, bucket_width - 1, timetype);\n\tmaxbuckets.start = ts_time_bucket_by_type(bucket_width, maxwindow.start, timetype);\n\tmaxbuckets.end = ts_time_get_end_or_max(timetype);\n\n\treturn maxbuckets;\n}\n\n/*\n * Adjust the refresh window to align with inscribed buckets, so it includes buckets, which are\n * fully covered by the refresh window.\n *\n * Bucketing refresh window is necessary for a continuous aggregate refresh, which can refresh only\n * entire buckets. The result of the function is a bucketed window, where its start is at the start\n * of the first bucket, which is  fully inside the refresh window, and its end is at the end of the\n * last fully covered bucket.\n *\n * Example1, the window needs to shrink:\n *    [---------)      - given refresh window\n * .|....|....|....|.  - buckets\n *       [----)        - inscribed bucketed window\n *\n * Example2, the window is already aligned:\n *       [----)        - given refresh window\n * .|....|....|....|.  - buckets\n *       [----)        - inscribed bucketed window\n *\n * This function is called for the continuous aggregate policy and manual refresh. In such case\n * excluding buckets, which are not fully covered by the refresh window, avoids refreshing a bucket,\n * where part of its data were dropped by a retention policy. See #2198 for details.\n */\nstatic InternalTimeRange\ncompute_inscribed_bucketed_refresh_window(const ContinuousAgg *cagg,\n\t\t\t\t\t\t\t\t\t\t  const InternalTimeRange *const refresh_window,\n\t\t\t\t\t\t\t\t\t\t  const int64 bucket_width)\n{\n\tAssert(cagg != NULL);\n\tAssert(cagg->bucket_function != NULL);\n\n\tInternalTimeRange result = *refresh_window;\n\tInternalTimeRange largest_bucketed_window =\n\t\tget_largest_bucketed_window(refresh_window->type, bucket_width);\n\n\t/* Get offset and origin for bucket function */\n\tNullableDatum offset = INIT_NULL_DATUM;\n\tNullableDatum origin = INIT_NULL_DATUM;\n\tfill_bucket_offset_origin(cagg->bucket_function, refresh_window->type, &offset, &origin);\n\n\t/* Defined offset and origin in one function is not supported */\n\tAssert(offset.isnull == true || origin.isnull == true);\n\n\tif (refresh_window->start <= largest_bucketed_window.start)\n\t{\n\t\tresult.start = largest_bucketed_window.start;\n\t}\n\telse\n\t{\n\t\t/* The start time needs to be aligned with the first fully enclosed bucket.\n\t\t * So the original window start is moved to next bucket, except if the start is\n\t\t * already aligned with a bucket, thus 1 is subtracted to avoid moving into next\n\t\t * bucket in the aligned case. */\n\t\tint64 included_bucket =\n\t\t\tts_time_saturating_add(refresh_window->start, bucket_width - 1, refresh_window->type);\n\t\t/* Get the start of the included bucket. */\n\t\tresult.start = ts_time_bucket_by_type_extended(bucket_width,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   included_bucket,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   refresh_window->type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   offset,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   origin);\n\t}\n\n\tif (refresh_window->end >= largest_bucketed_window.end)\n\t{\n\t\tresult.end = largest_bucketed_window.end;\n\t}\n\telse\n\t{\n\t\t/* The window is reduced to the beginning of the bucket, which contains the exclusive\n\t\t * end of the refresh window. */\n\t\tresult.end = ts_time_bucket_by_type_extended(bucket_width,\n\t\t\t\t\t\t\t\t\t\t\t\t\t refresh_window->end,\n\t\t\t\t\t\t\t\t\t\t\t\t\t refresh_window->type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t offset,\n\t\t\t\t\t\t\t\t\t\t\t\t\t origin);\n\t}\n\treturn result;\n}\n\n/*\n * Get the offset as Datum value of an integer based bucket\n */\nstatic Datum\nint_bucket_offset_to_datum(Oid type, const ContinuousAggBucketFunction *bucket_function)\n{\n\tAssert(bucket_function->bucket_time_based == false);\n\n\tswitch (type)\n\t{\n\t\tcase INT2OID:\n\t\t\treturn Int16GetDatum(bucket_function->bucket_integer_offset);\n\t\tcase INT4OID:\n\t\t\treturn Int32GetDatum(bucket_function->bucket_integer_offset);\n\t\tcase INT8OID:\n\t\t\treturn Int64GetDatum(bucket_function->bucket_integer_offset);\n\t\tdefault:\n\t\t\telog(ERROR, \"invalid integer time_bucket type \\\"%s\\\"\", format_type_be(type));\n\t\t\tpg_unreachable();\n\t}\n}\n\n/*\n * Get a NullableDatum for offset and origin based on the CAgg information\n */\nvoid\nfill_bucket_offset_origin(const ContinuousAggBucketFunction *bucket_function, Oid type,\n\t\t\t\t\t\t  NullableDatum *offset, NullableDatum *origin)\n{\n\tAssert(bucket_function != NULL);\n\tAssert(offset != NULL);\n\tAssert(origin != NULL);\n\tAssert(offset->isnull);\n\tAssert(origin->isnull);\n\n\tif (bucket_function->bucket_time_based)\n\t{\n\t\tif (bucket_function->bucket_time_offset != NULL)\n\t\t{\n\t\t\toffset->isnull = false;\n\t\t\toffset->value = IntervalPGetDatum(bucket_function->bucket_time_offset);\n\t\t}\n\n\t\tif (TIMESTAMP_NOT_FINITE(bucket_function->bucket_time_origin) == false)\n\t\t{\n\t\t\torigin->isnull = false;\n\t\t\tif (type == DATEOID)\n\t\t\t{\n\t\t\t\t/* Date was converted into a timestamp in process_additional_timebucket_parameter(),\n\t\t\t\t * build a Date again */\n\t\t\t\torigin->value =\n\t\t\t\t\tDirectFunctionCall1(timestamp_date,\n\t\t\t\t\t\t\t\t\t\tTimestampGetDatum(bucket_function->bucket_time_origin));\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\torigin->value = TimestampGetDatum(bucket_function->bucket_time_origin);\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t{\n\t\tif (bucket_function->bucket_integer_offset != 0)\n\t\t{\n\t\t\toffset->isnull = false;\n\t\t\toffset->value = int_bucket_offset_to_datum(type, bucket_function);\n\t\t}\n\t}\n}\n\n/*\n * Adjust the refresh window to align with circumscribed buckets, so it includes buckets, which\n * fully cover the refresh window.\n *\n * Bucketing refresh window is necessary for a continuous aggregate refresh, which can refresh only\n * entire buckets. The result of the function is a bucketed window, where its start is at the start\n * of a bucket, which contains the start of the refresh window, and its end is at the end of a\n * bucket, which contains the end of the refresh window.\n *\n * Example1, the window needs to expand:\n *    [---------)      - given refresh window\n * .|....|....|....|.  - buckets\n *  [--------------)   - circumscribed bucketed window\n *\n * Example2, the window is already aligned:\n *       [----)        - given refresh window\n * .|....|....|....|.  - buckets\n *       [----)        - inscribed bucketed window\n *\n * This function is called for an invalidation window before refreshing it and after the\n * invalidation window was adjusted to be fully inside a refresh window. In the case of a\n * continuous aggregate policy or manual refresh, the refresh window is the inscribed bucketed\n * window.\n *\n * The circumscribed behaviour is also used for a refresh on drop, when the refresh is called during\n * dropping chunks manually or as part of retention policy.\n */\nInternalTimeRange\ncompute_circumscribed_bucketed_refresh_window(const ContinuousAgg *cagg,\n\t\t\t\t\t\t\t\t\t\t\t  const InternalTimeRange *const refresh_window,\n\t\t\t\t\t\t\t\t\t\t\t  const ContinuousAggBucketFunction *bucket_function)\n{\n\tAssert(cagg != NULL);\n\tAssert(cagg->bucket_function != NULL);\n\n\tif (bucket_function->bucket_fixed_interval == false)\n\t{\n\t\tInternalTimeRange result = *refresh_window;\n\t\tts_compute_circumscribed_bucketed_refresh_window_variable(&result.start,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  &result.end,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  bucket_function);\n\t\treturn result;\n\t}\n\n\t/* Interval is fixed */\n\tint64 bucket_width = ts_continuous_agg_fixed_bucket_width(bucket_function);\n\tAssert(bucket_width > 0);\n\n\tInternalTimeRange result = *refresh_window;\n\tInternalTimeRange largest_bucketed_window =\n\t\tget_largest_bucketed_window(refresh_window->type, bucket_width);\n\n\t/* Get offset and origin for bucket function */\n\tNullableDatum offset = INIT_NULL_DATUM;\n\tNullableDatum origin = INIT_NULL_DATUM;\n\tfill_bucket_offset_origin(cagg->bucket_function, refresh_window->type, &offset, &origin);\n\n\t/* Defined offset and origin in one function is not supported */\n\tAssert(offset.isnull == true || origin.isnull == true);\n\n\tif (refresh_window->start <= largest_bucketed_window.start)\n\t{\n\t\tresult.start = largest_bucketed_window.start;\n\t}\n\telse\n\t{\n\t\t/* For alignment with a bucket, which includes the start of the refresh window, we just\n\t\t * need to get start of the bucket. */\n\t\tresult.start = ts_time_bucket_by_type_extended(bucket_width,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   refresh_window->start,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   refresh_window->type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   offset,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   origin);\n\t}\n\n\tif (refresh_window->end >= largest_bucketed_window.end)\n\t{\n\t\tresult.end = largest_bucketed_window.end;\n\t}\n\telse\n\t{\n\t\tint64 exclusive_end;\n\t\tint64 bucketed_end;\n\n\t\tAssert(refresh_window->end > result.start);\n\n\t\t/* The end of the window is non-inclusive so subtract one before\n\t\t * bucketing in case we're already at the end of the bucket (we don't\n\t\t * want to add an extra bucket).  */\n\t\texclusive_end = ts_time_saturating_sub(refresh_window->end, 1, refresh_window->type);\n\t\tbucketed_end = ts_time_bucket_by_type_extended(bucket_width,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   exclusive_end,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   refresh_window->type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   offset,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   origin);\n\n\t\t/* We get the time value for the start of the bucket, so need to add\n\t\t * bucket_width to get the end of it. */\n\t\tresult.end = ts_time_saturating_add(bucketed_end, bucket_width, refresh_window->type);\n\t}\n\treturn result;\n}\n\n/*\n * Initialize the refresh state for a continuous aggregate.\n *\n * The state holds information for executing a refresh of a continuous aggregate.\n */\nstatic void\ncontinuous_agg_refresh_init(ContinuousAggRefreshState *refresh, const ContinuousAgg *cagg,\n\t\t\t\t\t\t\tconst InternalTimeRange *refresh_window, bool bucketing_refresh_window)\n{\n\tMemSet(refresh, 0, sizeof(*refresh));\n\trefresh->cagg = *cagg;\n\trefresh->cagg_ht = cagg_get_hypertable_or_fail(cagg->data.mat_hypertable_id);\n\trefresh->refresh_window = *refresh_window;\n\trefresh->bucketing_refresh_window = bucketing_refresh_window;\n\trefresh->partial_view.schema = &refresh->cagg.data.partial_view_schema;\n\trefresh->partial_view.name = &refresh->cagg.data.partial_view_name;\n}\n\n/*\n * Execute a refresh.\n *\n * The refresh will materialize the area given by the refresh window in the\n * refresh state.\n */\nstatic void\ncontinuous_agg_refresh_execute(const ContinuousAggRefreshState *refresh,\n\t\t\t\t\t\t\t   const InternalTimeRange *bucketed_refresh_window)\n{\n\tSchemaAndName cagg_hypertable_name = {\n\t\t.schema = &refresh->cagg_ht->fd.schema_name,\n\t\t.name = &refresh->cagg_ht->fd.table_name,\n\t};\n\tconst Dimension *time_dim = hyperspace_get_open_dimension(refresh->cagg_ht->space, 0);\n\n\tAssert(time_dim != NULL);\n\n\tcontinuous_agg_update_materialization(refresh->cagg_ht,\n\t\t\t\t\t\t\t\t\t\t  &refresh->cagg,\n\t\t\t\t\t\t\t\t\t\t  refresh->partial_view,\n\t\t\t\t\t\t\t\t\t\t  cagg_hypertable_name,\n\t\t\t\t\t\t\t\t\t\t  &time_dim->fd.column_name,\n\t\t\t\t\t\t\t\t\t\t  *bucketed_refresh_window);\n}\n\nstatic void\nlog_refresh_window(int elevel, const ContinuousAgg *cagg, const InternalTimeRange *refresh_window,\n\t\t\t\t   ContinuousAggRefreshContext context)\n{\n\tconst char *msg = \"continuous aggregate refresh (individual invalidation) on\";\n\tif (context.callctx == CAGG_REFRESH_POLICY_BATCHED)\n\t\telog(elevel,\n\t\t\t \"%s \\\"%s\\\" in window [ %s, %s ] (batch %d of %d)\",\n\t\t\t msg,\n\t\t\t NameStr(cagg->data.user_view_name),\n\t\t\t ts_internal_to_time_string(refresh_window->start, refresh_window->type),\n\t\t\t ts_internal_to_time_string(refresh_window->end, refresh_window->type),\n\t\t\t context.processing_batch,\n\t\t\t context.number_of_batches);\n\telse\n\t\telog(elevel,\n\t\t\t \"%s \\\"%s\\\" in window [ %s, %s ]\",\n\t\t\t msg,\n\t\t\t NameStr(cagg->data.user_view_name),\n\t\t\t ts_internal_to_time_string(refresh_window->start, refresh_window->type),\n\t\t\t ts_internal_to_time_string(refresh_window->end, refresh_window->type));\n}\n\ntypedef void (*scan_refresh_ranges_funct_t)(const InternalTimeRange *bucketed_refresh_window,\n\t\t\t\t\t\t\t\t\t\t\tconst ContinuousAggRefreshContext context,\n\t\t\t\t\t\t\t\t\t\t\tconst long iteration, /* 0 is first range */\n\t\t\t\t\t\t\t\t\t\t\tvoid *arg1);\n\nstatic void\ncontinuous_agg_refresh_execute_wrapper(const InternalTimeRange *bucketed_refresh_window,\n\t\t\t\t\t\t\t\t\t   const ContinuousAggRefreshContext context,\n\t\t\t\t\t\t\t\t\t   const long iteration, void *arg1_refresh)\n{\n\tconst ContinuousAggRefreshState *refresh = (const ContinuousAggRefreshState *) arg1_refresh;\n\t(void) iteration;\n\n\tlog_refresh_window(CAGG_REFRESH_LOG_LEVEL, &refresh->cagg, bucketed_refresh_window, context);\n\tcontinuous_agg_refresh_execute(refresh, bucketed_refresh_window);\n}\n\nstatic long\ncontinuous_agg_scan_refresh_window_ranges(const ContinuousAgg *cagg,\n\t\t\t\t\t\t\t\t\t\t  const InternalTimeRange *refresh_window,\n\t\t\t\t\t\t\t\t\t\t  const InvalidationStore *invalidations,\n\t\t\t\t\t\t\t\t\t\t  const ContinuousAggRefreshContext context,\n\t\t\t\t\t\t\t\t\t\t  scan_refresh_ranges_funct_t exec_func, void *func_arg1)\n{\n\tTupleTableSlot *slot;\n\tlong count = 0;\n\tContinuousAggRefreshState *refresh = (ContinuousAggRefreshState *) func_arg1;\n\n\tslot = MakeSingleTupleTableSlot(invalidations->tupdesc, &TTSOpsMinimalTuple);\n\n\twhile (tuplestore_gettupleslot(invalidations->tupstore,\n\t\t\t\t\t\t\t\t   true /* forward */,\n\t\t\t\t\t\t\t\t   false /* copy */,\n\t\t\t\t\t\t\t\t   slot))\n\t{\n\t\tbool isnull;\n\t\tDatum start = slot_getattr(\n\t\t\tslot,\n\t\t\tAnum_continuous_aggs_materialization_invalidation_log_lowest_modified_value,\n\t\t\t&isnull);\n\t\tDatum end = slot_getattr(\n\t\t\tslot,\n\t\t\tAnum_continuous_aggs_materialization_invalidation_log_greatest_modified_value,\n\t\t\t&isnull);\n\n\t\tInternalTimeRange invalidation = {\n\t\t\t.type = refresh_window->type,\n\t\t\t.start = DatumGetInt64(start),\n\t\t\t/* Invalidations are inclusive at the end, while refresh windows\n\t\t\t * aren't, so add one to the end of the invalidated region */\n\t\t\t.end = ts_time_saturating_add(DatumGetInt64(end), 1, refresh_window->type),\n\t\t};\n\n\t\tInternalTimeRange bucketed_refresh_window = {\n\t\t\t.type = invalidation.type,\n\t\t\t.start = invalidation.start,\n\t\t\t.end = invalidation.end,\n\t\t};\n\n\t\tif (refresh->bucketing_refresh_window)\n\t\t{\n\t\t\tbucketed_refresh_window =\n\t\t\t\tcompute_circumscribed_bucketed_refresh_window(cagg,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  &invalidation,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  cagg->bucket_function);\n\t\t}\n\t\t(*exec_func)(&bucketed_refresh_window, context, count, func_arg1);\n\n\t\tcount++;\n\t}\n\n\tExecDropSingleTupleTableSlot(slot);\n\n\treturn count;\n}\n\n/*\n * Execute refreshes based on the processed invalidations.\n *\n * The given refresh window covers a set of buckets, some of which are\n * out-of-date (invalid) and some which are up-to-date (valid). Invalid\n * buckets that are adjacent form larger ranges, as shown below.\n *\n * Refresh window:  [-----------------------------------------)\n * Invalid ranges:           [-----] [-]   [--] [-] [---]\n * Merged range:             [---------------------------)\n *\n * The maximum number of individual (non-mergeable) ranges are\n * #buckets_in_window/2 (i.e., every other bucket is invalid).\n *\n * Since it might not be efficient to materialize a lot buckets separately\n * when there are many invalid (non-adjecent) buckets/ranges, we put a limit\n * on the number of individual materializations we do. This limit is\n * determined by the MATERIALIZATIONS_PER_REFRESH_WINDOW setting.\n *\n * Thus, if the refresh window covers a large number of buckets, but only a\n * few of them are invalid, it is likely beneficial to materialized these\n * separately to avoid materializing a lot of buckets that are already\n * up-to-date. But if the number of invalid buckets/ranges go above the\n * threshold, we materialize all of them in one go using the \"merged range\",\n * as illustrated above.\n */\nstatic void\ncontinuous_agg_refresh_with_window(const ContinuousAgg *cagg,\n\t\t\t\t\t\t\t\t   const InternalTimeRange *refresh_window,\n\t\t\t\t\t\t\t\t   const InvalidationStore *invalidations,\n\t\t\t\t\t\t\t\t   const ContinuousAggRefreshContext context,\n\t\t\t\t\t\t\t\t   bool bucketing_refresh_window)\n{\n\tContinuousAggRefreshState refresh;\n\n\tcontinuous_agg_refresh_init(&refresh, cagg, refresh_window, bucketing_refresh_window);\n\n\tlong count pg_attribute_unused();\n\tcount = continuous_agg_scan_refresh_window_ranges(cagg,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  refresh_window,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  invalidations,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  context,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  continuous_agg_refresh_execute_wrapper,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  (void *) &refresh /* arg1 */);\n\tAssert(count);\n}\n\n#define REFRESH_FUNCTION_NAME \"refresh_continuous_aggregate()\"\n/*\n * Refresh a continuous aggregate across the given window.\n */\nDatum\ncontinuous_agg_refresh(PG_FUNCTION_ARGS)\n{\n\tOid cagg_relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\tbool force = PG_ARGISNULL(3) ? false : PG_GETARG_BOOL(3);\n\tJsonb *options = PG_ARGISNULL(4) ? NULL : PG_GETARG_JSONB_P(4);\n\tbool process_hypertable_invalidations = true;\n\tContinuousAgg *cagg;\n\tInternalTimeRange refresh_window = {\n\t\t.type = InvalidOid,\n\t};\n\n\tts_feature_flag_check(FEATURE_CAGG);\n\n\tif (options)\n\t{\n\t\tbool found;\n\t\tbool value = ts_jsonb_get_bool_field(options,\n\t\t\t\t\t\t\t\t\t\t\t POL_REFRESH_CONF_KEY_PROCESS_HYPERTABLE_INVALIDATIONS,\n\t\t\t\t\t\t\t\t\t\t\t &found);\n\t\tprocess_hypertable_invalidations = !found || value;\n\t}\n\n\tcagg = cagg_get_by_relid_or_fail(cagg_relid);\n\trefresh_window.type = cagg->partition_type;\n\n\tif (!PG_ARGISNULL(1))\n\t\trefresh_window.start = ts_time_value_from_arg(PG_GETARG_DATUM(1),\n\t\t\t\t\t\t\t\t\t\t\t\t\t  get_fn_expr_argtype(fcinfo->flinfo, 1),\n\t\t\t\t\t\t\t\t\t\t\t\t\t  refresh_window.type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  true);\n\telse\n\t\t/* get min time for a cagg depending of the primary partition type */\n\t\trefresh_window.start = cagg_get_time_min(cagg);\n\n\tif (!PG_ARGISNULL(2))\n\t\trefresh_window.end = ts_time_value_from_arg(PG_GETARG_DATUM(2),\n\t\t\t\t\t\t\t\t\t\t\t\t\tget_fn_expr_argtype(fcinfo->flinfo, 2),\n\t\t\t\t\t\t\t\t\t\t\t\t\trefresh_window.type,\n\t\t\t\t\t\t\t\t\t\t\t\t\ttrue);\n\telse\n\t\trefresh_window.end = ts_time_get_noend_or_max(refresh_window.type);\n\n\tContinuousAggRefreshContext context = { .callctx = CAGG_REFRESH_WINDOW };\n\tcontinuous_agg_refresh_internal(cagg,\n\t\t\t\t\t\t\t\t\t&refresh_window,\n\t\t\t\t\t\t\t\t\tcontext,\n\t\t\t\t\t\t\t\t\tPG_ARGISNULL(1),\n\t\t\t\t\t\t\t\t\tPG_ARGISNULL(2),\n\t\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\t\tforce,\n\t\t\t\t\t\t\t\t\tprocess_hypertable_invalidations,\n\t\t\t\t\t\t\t\t\tfalse /*extend_last_bucket*/);\n\n\tPG_RETURN_VOID();\n}\n\nstatic void\nemit_up_to_date_notice(const ContinuousAgg *cagg, const ContinuousAggRefreshContext context)\n{\n\tswitch (context.callctx)\n\t{\n\t\tcase CAGG_REFRESH_WINDOW:\n\t\tcase CAGG_REFRESH_CREATION:\n\t\t\telog(NOTICE,\n\t\t\t\t \"continuous aggregate \\\"%s\\\" is already up-to-date\",\n\t\t\t\t NameStr(cagg->data.user_view_name));\n\t\t\tbreak;\n\t\tcase CAGG_REFRESH_POLICY:\n\t\tcase CAGG_REFRESH_POLICY_BATCHED:\n\t\t\tbreak;\n\t}\n}\n\nstatic bool\nprocess_cagg_invalidations_and_refresh(const ContinuousAgg *cagg,\n\t\t\t\t\t\t\t\t\t   const InternalTimeRange *refresh_window,\n\t\t\t\t\t\t\t\t\t   const ContinuousAggRefreshContext context,\n\t\t\t\t\t\t\t\t\t   bool bucketing_refresh_window, bool force)\n{\n\tInvalidationStore *invalidations;\n\tOid hyper_relid = ts_hypertable_id_to_relid(cagg->data.mat_hypertable_id, false);\n\n\t/* Lock the continuous aggregate's materialized hypertable to protect against\n\t * concurrent invalidation log processing.\n\t *\n\t * It will produce rows in the `continuous_aggs_materialization_ranges` table\n\t * to be materialized later either serially or in parallel for non-overlap\n\t * refresh ranges.\n\t *\n\t * This is supposed to be a short transaction and in the future we can consider\n\t * relaxing this lock.\n\t */\n\tLockRelationOid(hyper_relid, ShareUpdateExclusiveLock);\n\tinvalidations = invalidation_process_cagg_log(cagg,\n\t\t\t\t\t\t\t\t\t\t\t\t  refresh_window,\n\t\t\t\t\t\t\t\t\t\t\t\t  ts_guc_cagg_max_individual_materializations,\n\t\t\t\t\t\t\t\t\t\t\t\t  context,\n\t\t\t\t\t\t\t\t\t\t\t\t  force);\n\n\tDEBUG_WAITPOINT(\"before_process_cagg_invalidations_for_refresh_lock\");\n\n\tSPI_commit_and_chain();\n\n\tDEBUG_WAITPOINT(\"after_process_cagg_invalidations_for_refresh_lock\");\n\n\tif (invalidations != NULL)\n\t{\n\t\tif (context.callctx == CAGG_REFRESH_CREATION)\n\t\t{\n\t\t\tAssert(OidIsValid(cagg->relid));\n\t\t\tereport(NOTICE,\n\t\t\t\t\t(errmsg(\"refreshing continuous aggregate \\\"%s\\\"\", get_rel_name(cagg->relid)),\n\t\t\t\t\t errhint(\"Use WITH NO DATA if you do not want to refresh the continuous \"\n\t\t\t\t\t\t\t \"aggregate on creation.\")));\n\t\t}\n\n\t\tcontinuous_agg_refresh_with_window(cagg,\n\t\t\t\t\t\t\t\t\t\t   refresh_window,\n\t\t\t\t\t\t\t\t\t\t   invalidations,\n\t\t\t\t\t\t\t\t\t\t   context,\n\t\t\t\t\t\t\t\t\t\t   bucketing_refresh_window);\n\t\tif (invalidations)\n\t\t\tinvalidation_store_free(invalidations);\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nvoid\ncontinuous_agg_refresh_internal(const ContinuousAgg *cagg,\n\t\t\t\t\t\t\t\tconst InternalTimeRange *refresh_window_arg,\n\t\t\t\t\t\t\t\tconst ContinuousAggRefreshContext context, const bool start_isnull,\n\t\t\t\t\t\t\t\tconst bool end_isnull, bool bucketing_refresh_window, bool force,\n\t\t\t\t\t\t\t\tbool process_hypertable_invalidations, bool extend_last_bucket)\n{\n\tint32 mat_id = cagg->data.mat_hypertable_id;\n\tInternalTimeRange refresh_window = *refresh_window_arg;\n\tint64 invalidation_threshold;\n\tbool nonatomic = ts_process_utility_is_context_nonatomic();\n\n\t/* Reset the saved ProcessUtilityContext value promptly before\n\t * calling Prevent* checks so the potential unsupported (atomic)\n\t * value won't linger there in case of ereport exit.\n\t */\n\tts_process_utility_context_reset();\n\n\tPreventCommandIfReadOnly(REFRESH_FUNCTION_NAME);\n\n\t/* Prevent running refresh if we're in a transaction block since a refresh\n\t * can run two transactions and might take a long time to release locks if\n\t * there's a lot to materialize. Strictly, it is optional to prohibit\n\t * transaction blocks since there will be only one transaction if the\n\t * invalidation threshold needs no update. However, materialization might\n\t * still take a long time and it is probably best for consistency to always\n\t * prevent transaction blocks.  */\n\tPreventInTransactionBlock(nonatomic, REFRESH_FUNCTION_NAME);\n\n\t/*\n\t * We don't cagg refresh to fail because of decompression limit. So disable\n\t * the decompression limit for the duration of the refresh.\n\t */\n\tconst char *old_decompression_limit =\n\t\tGetConfigOption(\"timescaledb.max_tuples_decompressed_per_dml_transaction\", false, false);\n\tSetConfigOption(\"timescaledb.max_tuples_decompressed_per_dml_transaction\",\n\t\t\t\t\t\"0\",\n\t\t\t\t\tPGC_USERSET,\n\t\t\t\t\tPGC_S_SESSION);\n\n\t/* Connect to SPI manager due to the underlying SPI calls */\n\tint rc = SPI_connect_ext(SPI_OPT_NONATOMIC);\n\tif (rc != SPI_OK_CONNECT)\n\t\telog(ERROR, \"SPI_connect failed: %s\", SPI_result_code_string(rc));\n\n\t/* Lock down search_path */\n\tint save_nestlevel = NewGUCNestLevel();\n\tRestrictSearchPath();\n\n\t/* Like regular materialized views, require owner to refresh. */\n\tif (!object_ownercheck(RelationRelationId, cagg->relid, GetUserId()))\n\t\taclcheck_error(ACLCHECK_NOT_OWNER,\n\t\t\t\t\t   get_relkind_objtype(get_rel_relkind(cagg->relid)),\n\t\t\t\t\t   get_rel_name(cagg->relid));\n\n\t/* No bucketing when open ended */\n\tif (bucketing_refresh_window && !(start_isnull && end_isnull))\n\t{\n\t\tif (cagg->bucket_function->bucket_fixed_interval == false)\n\t\t{\n\t\t\trefresh_window = *refresh_window_arg;\n\t\t\tts_compute_inscribed_bucketed_refresh_window_variable(&refresh_window.start,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  &refresh_window.end,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  cagg->bucket_function);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tint64 bucket_width = ts_continuous_agg_fixed_bucket_width(cagg->bucket_function);\n\t\t\tAssert(bucket_width > 0);\n\t\t\trefresh_window =\n\t\t\t\tcompute_inscribed_bucketed_refresh_window(cagg, refresh_window_arg, bucket_width);\n\t\t}\n\t}\n\n\tif (refresh_window.start >= refresh_window.end)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"refresh window too small\"),\n\t\t\t\t errdetail(\"The refresh window must cover at least one bucket of data.\"),\n\t\t\t\t errhint(\"Align the refresh window with the bucket\"\n\t\t\t\t\t\t \" time zone or use at least two buckets.\")));\n\n\t/* If there is no other policy defined after this, the inscribed bucket calculated above\n\t * is correct. However, in the case of concurrent policies, if this isn't the last\n\t * policy defined then we should extend the end of the window to include the partial\n\t * bucket. This is done to ensure concurrent policies that are 'adjacent' don't skip a\n\t * bucket We don't need to do this when the CAgg is created WITH DATA, or manually\n\t * refreshed\n\t */\n\tif (extend_last_bucket && !(start_isnull && end_isnull))\n\t{\n\t\tif (cagg->bucket_function->bucket_fixed_interval == false)\n\t\t{\n\t\t\trefresh_window.end =\n\t\t\t\tts_compute_beginning_of_the_next_bucket_variable(refresh_window.end,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t cagg->bucket_function);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tint64 bucket_width = ts_continuous_agg_fixed_bucket_width(cagg->bucket_function);\n\t\t\trefresh_window.end =\n\t\t\t\tts_time_saturating_add(refresh_window.end, bucket_width - 1, refresh_window.type);\n\t\t}\n\t}\n\n\t/*\n\t * Perform the refresh across two transactions.\n\t *\n\t * The first transaction moves the invalidation threshold (if needed) and\n\t * copies over invalidations from the hypertable log to the cagg\n\t * invalidation log. Doing the threshold and copying as part of the first\n\t * transaction ensures that the threshold and new invalidations will be\n\t * visible as soon as possible to concurrent refreshes and that we keep\n\t * locks for only a short period.\n\t *\n\t * The second transaction processes the cagg invalidation log and then\n\t * performs the actual refresh (materialization of data). This transaction\n\t * serializes around a lock on the materialized hypertable for the\n\t * continuous aggregate that gets refreshed.\n\t */\n\n\t/* Set the new invalidation threshold. Note that this only updates the\n\t * threshold if the new value is greater than the old one. Otherwise, the\n\t * existing threshold is returned. */\n\tinvalidation_threshold = invalidation_threshold_set_or_get(cagg, &refresh_window);\n\n\t/* We must also cap the refresh window at the invalidation threshold. If\n\t * we process invalidations after the threshold, the continuous aggregates\n\t * won't be refreshed when the threshold is moved forward in the\n\t * future. The invalidation threshold should already be aligned on bucket\n\t * boundary. */\n\tif (refresh_window.end > invalidation_threshold)\n\t\trefresh_window.end = invalidation_threshold;\n\n\t/* Capping the end might have made the window 0, or negative, so nothing to refresh in that\n\t * case.\n\t *\n\t * For variable width buckets we use a refresh_window.start value that is lower than the\n\t * -infinity value (ts_time_get_nobegin < ts_time_get_min). Therefore, the first check in the\n\t * following if statement is not enough. If the invalidation_threshold returns the min_value for\n\t * the data type, we end up with [nobegin, min_value] which is an invalid time interval.\n\t * Therefore, we have also to check if the invalidation_threshold is defined. If not, no refresh\n\t * is needed.  */\n\tif ((refresh_window.start >= refresh_window.end) ||\n\t\t(IS_TIMESTAMP_TYPE(refresh_window.type) &&\n\t\t invalidation_threshold == ts_time_get_min(refresh_window.type)))\n\t{\n\t\temit_up_to_date_notice(cagg, context);\n\n\t\t/* Restore search_path */\n\t\tAtEOXact_GUC(false, save_nestlevel);\n\n\t\trc = SPI_finish();\n\t\tif (rc != SPI_OK_FINISH)\n\t\t\telog(ERROR, \"SPI_finish failed: %s\", SPI_result_code_string(rc));\n\n\t\treturn;\n\t}\n\n\tif (process_hypertable_invalidations)\n\t{\n\t\t/*\n\t\t * If we are using trigger-based invalidations, we can process the\n\t\t * invalidations for the associated hypertable only and later read the\n\t\t * invalidations for other hypertables, but when using WAL-based\n\t\t * invalidation we need to process all of the hypertables that are\n\t\t * currently using WAL.\n\t\t *\n\t\t * We want to prevent any changes to how invalidations are collected\n\t\t * in the meantime since changing the invalidation collection method\n\t\t * while this is running might cause problems and miss invalidations.\n\t\t *\n\t\t * Concurrency on the replication slot is controlled using some\n\t\t * special sauce in ReplicationSlotAcquire(), which is called inside\n\t\t * pg_logical_slot_get_changes_guts().\n\t\t *\n\t\t * This will currently generate an error rather than blocking on the\n\t\t * lock, so we need to add a separate lock to ensure a blocking\n\t\t * behaviour.\n\t\t */\n\t\tinvalidation_process_hypertable_log(cagg->data.raw_hypertable_id, refresh_window.type);\n\t}\n\n\t/* Commit and Start a new transaction */\n\tSPI_commit_and_chain();\n\n\tcagg = ts_continuous_agg_find_by_mat_hypertable_id(mat_id, false);\n\n\tbool refreshed = process_cagg_invalidations_and_refresh(cagg,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t&refresh_window,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontext,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbucketing_refresh_window,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tforce);\n\n\t/* check if we have any pending materializations in our refresh window range,\n\t * if so, we need to process them\n\t * Note that we use the original refresh window range here, not the one that has been processed\n\t * by the refresh function*/\n\trefresh_window = *refresh_window_arg;\n\tbool has_pending_materializations =\n\t\tcontinuous_agg_has_pending_materializations(cagg, refresh_window);\n\n\tif (has_pending_materializations)\n\t{\n\t\tContinuousAggRefreshState refresh;\n\t\tcontinuous_agg_refresh_init(&refresh, cagg, &refresh_window, bucketing_refresh_window);\n\n#ifdef TS_DEBUG\n\t\telog(NOTICE,\n\t\t\t \"continuous aggregate \\\"%s\\\" has pending materializations in window [ %s, %s ]\",\n\t\t\t NameStr(cagg->data.user_view_name),\n\t\t\t ts_internal_to_time_string(refresh_window.start, refresh_window.type),\n\t\t\t ts_internal_to_time_string(refresh_window.end, refresh_window.type));\n#endif\n\n\t\tInternalTimeRange invalidation = {\n\t\t\t.type = refresh_window.type,\n\t\t\t.start = refresh_window.start,\n\t\t\t/* Invalidations are inclusive at the end, while refresh windows\n\t\t\t * aren't, so add one to the end of the invalidated region */\n\t\t\t.end = ts_time_saturating_add(refresh_window.end, 1, refresh_window.type),\n\t\t};\n\n\t\tInternalTimeRange bucketed_refresh_window = {\n\t\t\t.type = invalidation.type,\n\t\t\t.start = invalidation.start,\n\t\t\t.end = invalidation.end,\n\t\t};\n\n\t\tif (bucketing_refresh_window)\n\t\t{\n\t\t\tbucketed_refresh_window =\n\t\t\t\tcompute_circumscribed_bucketed_refresh_window(cagg,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  &invalidation,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  cagg->bucket_function);\n\t\t}\n\n\t\tcontinuous_agg_refresh_execute(&refresh, &bucketed_refresh_window);\n\t}\n\n\tif (!refreshed && !has_pending_materializations)\n\t\temit_up_to_date_notice(cagg, context);\n\n\tDEBUG_WAITPOINT(\"after_process_cagg_materializations\");\n\n\t/* Restore search_path */\n\tAtEOXact_GUC(false, save_nestlevel);\n\n\tSetConfigOption(\"timescaledb.max_tuples_decompressed_per_dml_transaction\",\n\t\t\t\t\told_decompression_limit,\n\t\t\t\t\tPGC_USERSET,\n\t\t\t\t\tPGC_S_SESSION);\n\n\trc = SPI_finish();\n\tif (rc != SPI_OK_FINISH)\n\t\telog(ERROR, \"SPI_finish failed: %s\", SPI_result_code_string(rc));\n}\n\nstatic void\ndebug_refresh_window(const ContinuousAgg *cagg, const InternalTimeRange *refresh_window,\n\t\t\t\t\t const char *msg)\n{\n\telog(DEBUG1,\n\t\t \"%s \\\"%s\\\" in window [ %s, %s ] internal [ \" INT64_FORMAT \", \" INT64_FORMAT\n\t\t \" ] minimum [ %s ]\",\n\t\t msg,\n\t\t NameStr(cagg->data.user_view_name),\n\t\t ts_internal_to_time_string(refresh_window->start, refresh_window->type),\n\t\t ts_internal_to_time_string(refresh_window->end, refresh_window->type),\n\t\t refresh_window->start,\n\t\t refresh_window->end,\n\t\t ts_datum_to_string(Int64GetDatum(ts_time_get_min(refresh_window->type)),\n\t\t\t\t\t\t\trefresh_window->type));\n}\n\nList *\ncontinuous_agg_split_refresh_window(ContinuousAgg *cagg, InternalTimeRange *original_refresh_window,\n\t\t\t\t\t\t\t\t\tint32 buckets_per_batch, bool refresh_newest_first)\n{\n\t/* Do not produce batches when the number of buckets per batch is zero (disabled) */\n\tif (buckets_per_batch == 0)\n\t{\n\t\treturn NIL;\n\t}\n\n\tInternalTimeRange refresh_window = {\n\t\t.type = original_refresh_window->type,\n\t\t.start = original_refresh_window->start,\n\t\t.start_isnull = original_refresh_window->start_isnull,\n\t\t.end = original_refresh_window->end,\n\t\t.end_isnull = original_refresh_window->end_isnull,\n\t};\n\n\tdebug_refresh_window(cagg, &refresh_window, \"begin\");\n\n\tconst Hypertable *ht = cagg_get_hypertable_or_fail(cagg->data.raw_hypertable_id);\n\tconst Dimension *time_dim = hyperspace_get_open_dimension(ht->space, 0);\n\n\t/*\n\t * Cap the refresh window to the min and max time of the hypertable\n\t *\n\t * In order to don't produce unnecessary batches we need to check if the start and end of the\n\t * refresh window is NULL then get the min/max slice from the original hypertable\n\t *\n\t */\n\tif (refresh_window.start_isnull)\n\t{\n\t\tdebug_refresh_window(cagg, &refresh_window, \"START IS NULL\");\n\t\tDimensionSlice *slice = ts_dimension_slice_nth_earliest_slice(time_dim->fd.id, 1);\n\n\t\t/* If still there's no MIN slice range start then return no batches */\n\t\tif (NULL == slice || TS_TIME_IS_MIN(slice->fd.range_start, refresh_window.type) ||\n\t\t\tTS_TIME_IS_NOBEGIN(slice->fd.range_start, refresh_window.type))\n\t\t{\n\t\t\telog(DEBUG1,\n\t\t\t\t \"no min slice range start for continuous aggregate \\\"%s.%s\\\", falling back to \"\n\t\t\t\t \"single batch processing\",\n\t\t\t\t NameStr(cagg->data.user_view_schema),\n\t\t\t\t NameStr(cagg->data.user_view_name));\n\t\t\treturn NIL;\n\t\t}\n\t\trefresh_window.start = slice->fd.range_start;\n\t\trefresh_window.start_isnull = false;\n\t}\n\n\tif (refresh_window.end_isnull)\n\t{\n\t\tdebug_refresh_window(cagg, &refresh_window, \"END IS NULL\");\n\t\tDimensionSlice *slice = ts_dimension_slice_nth_latest_slice(time_dim->fd.id, 1);\n\n\t\t/* If still there's no MAX slice range start then return no batches */\n\t\tif (NULL == slice || TS_TIME_IS_MAX(slice->fd.range_end, refresh_window.type) ||\n\t\t\tTS_TIME_IS_NOEND(slice->fd.range_end, refresh_window.type))\n\t\t{\n\t\t\telog(DEBUG1,\n\t\t\t\t \"no min slice range start for continuous aggregate \\\"%s.%s\\\", falling back to \"\n\t\t\t\t \"single batch processing\",\n\t\t\t\t NameStr(cagg->data.user_view_schema),\n\t\t\t\t NameStr(cagg->data.user_view_name));\n\t\t\treturn NIL;\n\t\t}\n\t\trefresh_window.end = slice->fd.range_end;\n\t\trefresh_window.end_isnull = false;\n\t}\n\n\t/* Compute the inscribed bucket for the capped refresh window range */\n\tconst int64 bucket_width = ts_continuous_agg_bucket_width(cagg->bucket_function);\n\tif (cagg->bucket_function->bucket_fixed_interval == false)\n\t{\n\t\tts_compute_inscribed_bucketed_refresh_window_variable(&refresh_window.start,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  &refresh_window.end,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  cagg->bucket_function);\n\t}\n\telse\n\t{\n\t\trefresh_window =\n\t\t\tcompute_inscribed_bucketed_refresh_window(cagg, &refresh_window, bucket_width);\n\t}\n\n\t/* Check if the refresh size is large enough to produce bathes, if not then return no batches */\n\tconst int64 refresh_window_size = i64abs(refresh_window.end - refresh_window.start);\n\tconst int64 batch_size = (bucket_width * buckets_per_batch);\n\n\tif (refresh_window_size <= batch_size)\n\t{\n\t\tOid type = IS_TIMESTAMP_TYPE(refresh_window.type) ? INTERVALOID : refresh_window.type;\n\t\tDatum refresh_size_interval = ts_internal_to_interval_value(refresh_window_size, type);\n\t\tDatum batch_size_interval = ts_internal_to_interval_value(batch_size, type);\n\n\t\telog(DEBUG1,\n\t\t\t \"refresh window size (%s) is smaller than or equal to batch size (%s), falling back \"\n\t\t\t \"to single batch processing\",\n\t\t\t ts_datum_to_string(refresh_size_interval, type),\n\t\t\t ts_datum_to_string(batch_size_interval, type));\n\t\treturn NIL;\n\t}\n\n\tdebug_refresh_window(cagg, &refresh_window, \"before produce batches\");\n\n\t/*\n\t * Produce the batches to be processed\n\t *\n\t * The refresh window is split into multiple batches of size `batch_size` each. The batches are\n\t * produced in reverse order so that the first range produced is the last range to be processed.\n\t *\n\t * The batches are produced in reverse order because the most recent data should be the first to\n\t * be processed and be visible for the users.\n\t *\n\t * It takes in account the invalidation logs (hypertable and materialization hypertable) to\n\t * avoid producing wholes that have no data to be processed.\n\t *\n\t * The logic is something like the following:\n\t * 1. Get dimension slices from the original hypertables\n\t * 2. Get either hypertable and materialization hypertable invalidation logs\n\t * 3. Produce the batches in reverse order\n\t * 4. Check if the produced batch overlaps either with dimension slices #1 and invalidation logs\n\t * #2\n\t * 5. If the batch overlaps with both then it's a valid batch to be processed\n\t * 6. If the batch overlaps with only one of them then it's not a valid batch to be processed\n\t * 7. If the batch does not overlap with any of them then it's not a valid batch to be processed\n\t */\n\tconst char *query_str_template = \" \\\n\t\tWITH dimension_slices AS ( \\\n\t\t\tSELECT \\\n\t\t\t\trange_start AS start, \\\n\t\t\t\trange_end AS end \\\n\t\t\tFROM \\\n\t\t\t\t_timescaledb_catalog.dimension_slice \\\n\t\t\t\tJOIN _timescaledb_catalog.dimension ON dimension.id = dimension_slice.dimension_id \\\n\t\t\tWHERE \\\n\t\t\t\thypertable_id = $1 \\\n\t\t\t\tAND dimension_id = $2 \\\n\t\t\t\tAND range_end >= range_start \\\n\t\t\tORDER BY \\\n\t\t\t\t%s \\\n\t\t), \\\n\t\tinvalidation_logs AS ( \\\n\t\t\tSELECT \\\n\t\t\t\tlowest_modified_value, \\\n\t\t\t\tgreatest_modified_value \\\n\t\t\tFROM \\\n\t\t\t\t_timescaledb_catalog.continuous_aggs_materialization_invalidation_log \\\n\t\t\tWHERE \\\n\t\t\t\tmaterialization_id = $3 \\\n\t\t\t\tAND greatest_modified_value >= lowest_modified_value \\\n\t\t\tUNION ALL \\\n\t\t\tSELECT \\\n\t\t\t\tpg_catalog.min(lowest_modified_value) AS lowest_modified_value, \\\n\t\t\t\tpg_catalog.max(greatest_modified_value) AS greatest_modified_value \\\n\t\t\tFROM \\\n\t\t\t\t_timescaledb_catalog.continuous_aggs_hypertable_invalidation_log \\\n\t\t\tWHERE \\\n\t\t\t\thypertable_id = $1 \\\n\t\t\t\tAND greatest_modified_value >= lowest_modified_value \\\n\t\t) \\\n\t\tSELECT \\\n\t\t\trefresh_start AS start, \\\n\t\t\tLEAST($6::numeric, refresh_start::numeric + $4::numeric)::bigint AS end \\\n\t\tFROM \\\n\t\t\tpg_catalog.generate_series($5, $6, $4) AS refresh_start \\\n\t\tWHERE \\\n\t\t\tEXISTS ( \\\n\t\t\t    SELECT FROM dimension_slices \\\n\t\t\t\tWHERE \\\n\t\t\t\t\tpg_catalog.int8range(refresh_start, LEAST($6::numeric, refresh_start::numeric + $4::numeric)::bigint) \\\n\t\t\t\t\tOPERATOR(pg_catalog.&&) \\\n\t\t\t\t\tpg_catalog.int8range(dimension_slices.start, dimension_slices.end) \\\n\t\t\t) \\\n\t\t\tAND EXISTS ( \\\n\t\t\t\tSELECT FROM \\\n\t\t\t\t\tinvalidation_logs \\\n\t\t\t\tWHERE \\\n\t\t\t\t\tpg_catalog.int8range(refresh_start, LEAST($6::numeric, refresh_start::numeric + $4::numeric)::bigint) \\\n\t\t\t\t\tOPERATOR(pg_catalog.&&) \\\n\t\t\t\t\tpg_catalog.int8range(lowest_modified_value, greatest_modified_value) \\\n\t\t\t\t\tAND lowest_modified_value IS NOT NULL \\\n\t\t\t\t\tAND (greatest_modified_value IS NOT NULL AND greatest_modified_value != $7) \\\n\t\t\t) \\\n\t\tORDER BY \\\n\t\t\trefresh_start %s;\";\n\n\tconst char *query_str = psprintf(query_str_template,\n\t\t\t\t\t\t\t\t\t refresh_newest_first ? \"range_end DESC\" : \"range_start ASC\",\n\t\t\t\t\t\t\t\t\t refresh_newest_first ? \"DESC\" : \"ASC\");\n\n\t/* List of InternalTimeRange elements to be returned */\n\tList *refresh_window_list = NIL;\n\n\t/* Prepare for SPI call */\n\tint res;\n\tOid types[] = { INT4OID, INT4OID, INT4OID, INT8OID, INT8OID, INT8OID, INT8OID };\n\tDatum values[] = { Int32GetDatum(ht->fd.id),\n\t\t\t\t\t   Int32GetDatum(time_dim->fd.id),\n\t\t\t\t\t   Int32GetDatum(cagg->data.mat_hypertable_id),\n\t\t\t\t\t   Int64GetDatum(batch_size),\n\t\t\t\t\t   Int64GetDatum(refresh_window.start),\n\t\t\t\t\t   Int64GetDatum(refresh_window.end),\n\t\t\t\t\t   Int64GetDatum(CAGG_INVALIDATION_WRONG_GREATEST_VALUE) };\n\tchar nulls[] = { false, false, false, false, false, false, false };\n\tMemoryContext oldcontext = CurrentMemoryContext;\n\n\tif (SPI_connect() != SPI_OK_CONNECT)\n\t\telog(ERROR, \"could not connect to SPI\");\n\n\t/* Lock down search_path */\n\tint save_nestlevel = NewGUCNestLevel();\n\tRestrictSearchPath();\n\n\tres = SPI_execute_with_args(query_str,\n\t\t\t\t\t\t\t\t7,\n\t\t\t\t\t\t\t\ttypes,\n\t\t\t\t\t\t\t\tvalues,\n\t\t\t\t\t\t\t\tnulls,\n\t\t\t\t\t\t\t\tfalse /* read_only */,\n\t\t\t\t\t\t\t\t0 /* count */);\n\n\tif (res < 0)\n\t\telog(ERROR, \"%s: could not produce batches for the policy cagg refresh\", __func__);\n\n\tif (SPI_processed == 1)\n\t{\n\t\telog(DEBUG1,\n\t\t\t \"only one batch produced for continuous aggregate \\\"%s.%s\\\", falling back to single \"\n\t\t\t \"batch processing\",\n\t\t\t NameStr(cagg->data.user_view_schema),\n\t\t\t NameStr(cagg->data.user_view_name));\n\n\t\t/* Restore search_path */\n\t\tAtEOXact_GUC(false, save_nestlevel);\n\n\t\tres = SPI_finish();\n\t\tif (res != SPI_OK_FINISH)\n\t\t\telog(ERROR, \"SPI_finish failed: %s\", SPI_result_code_string(res));\n\n\t\treturn NIL;\n\t}\n\n\t/* Build the batches list */\n\tfor (uint64 batch = 0; batch < SPI_processed; batch++)\n\t{\n\t\tbool range_start_isnull, range_end_isnull;\n\t\tDatum range_start =\n\t\t\tSPI_getbinval(SPI_tuptable->vals[batch], SPI_tuptable->tupdesc, 1, &range_start_isnull);\n\t\tDatum range_end =\n\t\t\tSPI_getbinval(SPI_tuptable->vals[batch], SPI_tuptable->tupdesc, 2, &range_end_isnull);\n\n\t\t/* We need to allocate the list in the old memory context because here we're in the SPI\n\t\t * context */\n\t\tMemoryContext saved_context = MemoryContextSwitchTo(oldcontext);\n\t\tInternalTimeRange *range = palloc0(sizeof(InternalTimeRange));\n\t\trange->start = DatumGetInt64(range_start);\n\t\trange->start_isnull = range_start_isnull;\n\t\trange->end = DatumGetInt64(range_end);\n\t\trange->end_isnull = range_end_isnull;\n\t\trange->type = original_refresh_window->type;\n\n\t\t/* For variable-length buckets, circumscribe the batch to bucket boundaries.\n\t\t * The batch size calculation uses a 30-day approximation for months, so we need\n\t\t * to expand batches to cover complete buckets.*/\n\t\tif (cagg->bucket_function->bucket_fixed_interval == false)\n\t\t{\n\t\t\tts_compute_circumscribed_bucketed_refresh_window_variable(&range->start,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  &range->end,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  cagg->bucket_function);\n\t\t}\n\t\t/*\n\t\t * To make sure that the first range (or last range in case of refreshing from oldest to\n\t\t * newest) is aligned with the end of the refresh window we need to set the end to the\n\t\t * maximum value of the time type if the original refresh window end is NULL.\n\t\t */\n\t\tif (((batch == 0 && refresh_newest_first) ||\n\t\t\t (batch == (SPI_processed - 1) && !refresh_newest_first)) &&\n\t\t\toriginal_refresh_window->end_isnull)\n\t\t{\n\t\t\trange->end = ts_time_get_noend_or_max(range->type);\n\t\t\trange->end_isnull = true;\n\t\t}\n\n\t\t/*\n\t\t * To make sure that the last range (or first range in case of refreshing from oldest to\n\t\t * newest) is aligned with the start of the refresh window we need to set the start to the\n\t\t * maximum value of the time type if the original refresh window start is NULL.\n\t\t */\n\t\tif (((batch == (SPI_processed - 1) && refresh_newest_first) ||\n\t\t\t (batch == 0 && !refresh_newest_first)) &&\n\t\t\toriginal_refresh_window->start_isnull)\n\t\t{\n\t\t\trange->start = cagg_get_time_min(cagg);\n\t\t\trange->start_isnull = true;\n\t\t}\n\n\t\trefresh_window_list = lappend(refresh_window_list, range);\n\t\tMemoryContextSwitchTo(saved_context);\n\n\t\tdebug_refresh_window(cagg, range, \"batch produced\");\n\t}\n\n\t/* Restore search_path */\n\tAtEOXact_GUC(false, save_nestlevel);\n\n\tres = SPI_finish();\n\tif (res != SPI_OK_FINISH)\n\t\telog(ERROR, \"SPI_finish failed: %s\", SPI_result_code_string(res));\n\n\tif (refresh_window_list == NIL)\n\t{\n\t\telog(DEBUG1,\n\t\t\t \"no valid batches produced for continuous aggregate \\\"%s.%s\\\", falling back to single \"\n\t\t\t \"batch processing\",\n\t\t\t NameStr(cagg->data.user_view_schema),\n\t\t\t NameStr(cagg->data.user_view_name));\n\t}\n\n\treturn refresh_window_list;\n}\n"
  },
  {
    "path": "tsl/src/continuous_aggs/refresh.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <fmgr.h>\n\n#include \"invalidation.h\"\n#include \"materialize.h\"\n#include \"ts_catalog/continuous_agg.h\"\n\nextern Datum continuous_agg_refresh(PG_FUNCTION_ARGS);\nextern void\ncontinuous_agg_refresh_internal(const ContinuousAgg *cagg, const InternalTimeRange *refresh_window,\n\t\t\t\t\t\t\t\tconst ContinuousAggRefreshContext context, const bool start_isnull,\n\t\t\t\t\t\t\t\tconst bool end_isnull, bool bucketing_refresh_window, bool force,\n\t\t\t\t\t\t\t\tbool process_hypertable_invalidations, bool extend_last_bucket);\nextern List *continuous_agg_split_refresh_window(ContinuousAgg *cagg,\n\t\t\t\t\t\t\t\t\t\t\t\t InternalTimeRange *original_refresh_window,\n\t\t\t\t\t\t\t\t\t\t\t\t int32 buckets_per_batch,\n\t\t\t\t\t\t\t\t\t\t\t\t bool refresh_newest_first);\nInternalTimeRange\ncompute_circumscribed_bucketed_refresh_window(const ContinuousAgg *cagg,\n\t\t\t\t\t\t\t\t\t\t\t  const InternalTimeRange *const refresh_window,\n\t\t\t\t\t\t\t\t\t\t\t  const ContinuousAggBucketFunction *bucket_function);\n\nextern void fill_bucket_offset_origin(const ContinuousAggBucketFunction *bucket_function, Oid type,\n\t\t\t\t\t\t\t\t\t  NullableDatum *offset, NullableDatum *origin);\n"
  },
  {
    "path": "tsl/src/continuous_aggs/utils.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n\n#include <commands/view.h>\n#include <storage/lmgr.h>\n#include <utils/acl.h>\n#include <utils/regproc.h>\n#include <utils/snapmgr.h>\n#include <utils/timestamp.h>\n\n#include \"extension.h\"\n#include \"guc.h\"\n#include \"time_bucket.h\"\n#include \"utils.h\"\n\nenum\n{\n\tAnum_cagg_validate_query_valid = 1,\n\tAnum_cagg_validate_query_error_level,\n\tAnum_cagg_validate_query_error_code,\n\tAnum_cagg_validate_query_error_message,\n\tAnum_cagg_validate_query_error_detail,\n\tAnum_cagg_validate_query_error_hint,\n\t_Anum_cagg_validate_query_max\n};\n\n#define Natts_cagg_validate_query (_Anum_cagg_validate_query_max - 1)\n#define ORIGIN_PARAMETER_NAME \"origin\"\n\nstatic Datum\ncreate_cagg_validate_query_datum(TupleDesc tupdesc, const bool is_valid_query, ErrorData *edata)\n{\n\tNullableDatum datums[Natts_cagg_validate_query] = { { 0 } };\n\tHeapTuple tuple;\n\n\ttupdesc = BlessTupleDesc(tupdesc);\n\n\tts_datum_set_bool(Anum_cagg_validate_query_valid, datums, is_valid_query, false);\n\tts_datum_set_text_from_cstring(Anum_cagg_validate_query_error_level,\n\t\t\t\t\t\t\t\t   datums,\n\t\t\t\t\t\t\t\t   edata->elevel > 0 ? error_severity(edata->elevel) : NULL);\n\tts_datum_set_text_from_cstring(Anum_cagg_validate_query_error_code,\n\t\t\t\t\t\t\t\t   datums,\n\t\t\t\t\t\t\t\t   edata->sqlerrcode > 0 ? unpack_sql_state(edata->sqlerrcode) :\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   NULL);\n\tts_datum_set_text_from_cstring(Anum_cagg_validate_query_error_message,\n\t\t\t\t\t\t\t\t   datums,\n\t\t\t\t\t\t\t\t   edata->message ? edata->message : NULL);\n\tts_datum_set_text_from_cstring(Anum_cagg_validate_query_error_detail,\n\t\t\t\t\t\t\t\t   datums,\n\t\t\t\t\t\t\t\t   edata->detail ? edata->detail : NULL);\n\tts_datum_set_text_from_cstring(Anum_cagg_validate_query_error_hint,\n\t\t\t\t\t\t\t\t   datums,\n\t\t\t\t\t\t\t\t   edata->hint ? edata->hint : NULL);\n\n\tAssert(tupdesc->natts == Natts_cagg_validate_query);\n\ttuple = ts_heap_form_tuple(tupdesc, datums);\n\treturn HeapTupleGetDatum(tuple);\n}\n\nDatum\ncontinuous_agg_validate_query(PG_FUNCTION_ARGS)\n{\n\ttext *query_text = PG_GETARG_TEXT_P(0);\n\tchar *sql;\n\tvolatile bool is_valid_query = false;\n\tDatum datum_sql;\n\tTupleDesc tupdesc;\n\tErrorData *edata;\n\tMemoryContext oldcontext = CurrentMemoryContext;\n\n\t/* Change $1, $2 ... placeholders to NULL constant. This is necessary to make parser happy */\n\tsql = text_to_cstring(query_text);\n\telog(DEBUG1, \"sql: %s\", sql);\n\n\tdatum_sql = CStringGetTextDatum(sql);\n\tdatum_sql = DirectFunctionCall4Coll(textregexreplace,\n\t\t\t\t\t\t\t\t\t\tC_COLLATION_OID,\n\t\t\t\t\t\t\t\t\t\tdatum_sql,\n\t\t\t\t\t\t\t\t\t\tCStringGetTextDatum(\"\\\\$[0-9]+\"),\n\t\t\t\t\t\t\t\t\t\tCStringGetTextDatum(\"NULL\"),\n\t\t\t\t\t\t\t\t\t\tCStringGetTextDatum(\"g\"));\n\tsql = text_to_cstring(DatumGetTextP(datum_sql));\n\telog(DEBUG1, \"sql: %s\", sql);\n\n\tif (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)\n\t\telog(ERROR, \"function returning record called in context that cannot accept type record\");\n\n\tPG_TRY();\n\t{\n\t\tList *tree;\n\t\tNode *node;\n\t\tRawStmt *rawstmt;\n\t\tParseState *pstate;\n\t\tQuery *query;\n\n\t\tedata = (ErrorData *) palloc0(sizeof(ErrorData));\n\t\tedata->message = NULL;\n\t\tedata->detail = NULL;\n\t\tedata->hint = NULL;\n\n\t\ttree = pg_parse_query(sql);\n\n\t\tif (tree == NIL)\n\t\t{\n\t\t\tedata->elevel = ERROR;\n\t\t\tedata->sqlerrcode = ERRCODE_INTERNAL_ERROR;\n\t\t\tedata->message = \"failed to parse query\";\n\t\t}\n\t\telse if (list_length(tree) > 1)\n\t\t{\n\t\t\tedata->elevel = WARNING;\n\t\t\tedata->sqlerrcode = ERRCODE_FEATURE_NOT_SUPPORTED;\n\t\t\tedata->message = \"multiple statements are not supported\";\n\t\t}\n\t\telse\n\t\t{\n\t\t\tnode = linitial(tree);\n\t\t\trawstmt = (RawStmt *) node;\n\t\t\tpstate = make_parsestate(NULL);\n\n\t\t\tAssert(IsA(node, RawStmt));\n\n\t\t\tif (!IsA(rawstmt->stmt, SelectStmt))\n\t\t\t{\n\t\t\t\tedata->elevel = WARNING;\n\t\t\t\tedata->sqlerrcode = ERRCODE_FEATURE_NOT_SUPPORTED;\n\t\t\t\tedata->message = \"only select statements are supported\";\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tpstate->p_sourcetext = sql;\n\t\t\t\tquery = transformTopLevelStmt(pstate, rawstmt);\n\t\t\t\tfree_parsestate(pstate);\n\n\t\t\t\t(void) cagg_validate_query(query, \"public\", \"cagg_validate\", false);\n\t\t\t\tis_valid_query = true;\n\t\t\t}\n\t\t}\n\t}\n\tPG_CATCH();\n\t{\n\t\tMemoryContextSwitchTo(oldcontext);\n\t\tedata = CopyErrorData();\n\t\tFlushErrorState();\n\t}\n\tPG_END_TRY();\n\n\tPG_RETURN_DATUM(create_cagg_validate_query_datum(tupdesc, is_valid_query, edata));\n}\n\n/* Get the Oid of the direct view of the CAgg. We cannot use the TimescaleDB internal\n * functions such as ts_continuous_agg_find_by_mat_hypertable_id() at this point since this\n * function can be called during an extension upgrade and ts_catalog_get() does not work.\n */\nstatic Oid\nget_direct_view_oid(int32 mat_hypertable_id)\n{\n\tRangeVar *ts_cagg = makeRangeVar(CATALOG_SCHEMA_NAME,\n\t\t\t\t\t\t\t\t\t CONTINUOUS_AGG_TABLE_NAME,\n\t\t\t\t\t\t\t\t\t -1 /* taken location unknown */);\n\tRelation cagg_rel = relation_openrv_extended(ts_cagg, AccessShareLock, /* missing ok */ true);\n\n\tRangeVar *ts_cagg_idx =\n\t\tmakeRangeVar(CATALOG_SCHEMA_NAME, TS_CAGG_CATALOG_IDX, -1 /* taken location unknown */);\n\tRelation cagg_idx_rel =\n\t\trelation_openrv_extended(ts_cagg_idx, AccessShareLock, /* missing ok */ true);\n\n\t/* Prepare relation scan */\n\tTupleTableSlot *slot = table_slot_create(cagg_rel, NULL);\n\tScanKeyData scankeys[1];\n\tScanKeyEntryInitialize(&scankeys[0],\n\t\t\t\t\t\t   0,\n\t\t\t\t\t\t   1,\n\t\t\t\t\t\t   BTEqualStrategyNumber,\n\t\t\t\t\t\t   InvalidOid,\n\t\t\t\t\t\t   InvalidOid,\n\t\t\t\t\t\t   F_INT4EQ,\n\t\t\t\t\t\t   Int32GetDatum(mat_hypertable_id));\n\n\t/* Prepare index scan */\n\tPushActiveSnapshot(GetTransactionSnapshot());\n\tIndexScanDesc indexscan =\n\t\tindex_beginscan_compat(cagg_rel, cagg_idx_rel, GetActiveSnapshot(), NULL, 1, 0);\n\tindex_rescan(indexscan, scankeys, 1, NULL, 0);\n\n\t/* Read tuple from relation */\n\tbool got_next_slot = index_getnext_slot(indexscan, ForwardScanDirection, slot);\n\tif (!got_next_slot)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid materialized hypertable ID: %d\", mat_hypertable_id)));\n\tbool is_null = false;\n\n\t/* We use the view_schema and view_name to get data from the system catalog. Even char pointers\n\t * are passed to the catalog, it calls namehashfast() internally, which assumes that a char of\n\t * NAMEDATALEN is allocated. */\n\tNameData view_schema_name;\n\tNameData view_name_name;\n\n\t/* We need to get the attribute names dynamically since this function can be called during an\n\t * upgrade and the fixed attribution numbers (i.e., Anum_continuous_agg_direct_view_schema) can\n\t * be different. */\n\tAttrNumber direct_view_schema_attr = get_attnum(cagg_rel->rd_id, \"direct_view_schema\");\n\tEnsure(direct_view_schema_attr != InvalidAttrNumber,\n\t\t   \"unable to get attribute number for direct_view_schema\");\n\n\tAttrNumber direct_view_name_attr = get_attnum(cagg_rel->rd_id, \"direct_view_name\");\n\tEnsure(direct_view_name_attr != InvalidAttrNumber,\n\t\t   \"unable to get attribute number for direct_view_name\");\n\n\tchar *view_schema = DatumGetCString(slot_getattr(slot, direct_view_schema_attr, &is_null));\n\tEnsure(!is_null, \"unable to get view schema for oid %d\", mat_hypertable_id);\n\tAssert(view_schema != NULL);\n\tnamestrcpy(&view_schema_name, view_schema);\n\n\tchar *view_name = DatumGetCString(slot_getattr(slot, direct_view_name_attr, &is_null));\n\tEnsure(!is_null, \"unable to get view name for oid %d\", mat_hypertable_id);\n\tAssert(view_name != NULL);\n\tnamestrcpy(&view_name_name, view_name);\n\n\tgot_next_slot = index_getnext_slot(indexscan, ForwardScanDirection, slot);\n\tEnsure(!got_next_slot, \"found duplicate definitions for CAgg mat_ht %d\", mat_hypertable_id);\n\n\t/* End relation scan */\n\tindex_endscan(indexscan);\n\tExecDropSingleTupleTableSlot(slot);\n\trelation_close(cagg_rel, AccessShareLock);\n\trelation_close(cagg_idx_rel, AccessShareLock);\n\tPopActiveSnapshot();\n\n\t/* Get Oid of user view */\n\tOid direct_view_oid =\n\t\tts_get_relation_relid(NameStr(view_schema_name), NameStr(view_name_name), false);\n\tAssert(OidIsValid(direct_view_oid));\n\treturn direct_view_oid;\n}\n\nenum\n{\n\tAnum_cagg_bucket_function_oid = 1,\n\tAnum_cagg_bucket_function_width,\n\tAnum_cagg_bucket_function_origin,\n\tAnum_cagg_bucket_function_offset,\n\tAnum_cagg_bucket_function_timezone,\n\tAnum_cagg_bucket_function_fixed_width,\n\t_Anum_cagg_bucket_function_max\n};\n\n#define Natts_cagg_bucket_function (_Anum_cagg_bucket_function_max - 1)\n\nstatic Datum\ncreate_cagg_get_bucket_function_datum(TupleDesc tupdesc, ContinuousAggBucketFunction *bf)\n{\n\tNullableDatum datums[Natts_cagg_bucket_function] = { { 0 } };\n\tHeapTuple tuple;\n\n\tchar *bucket_origin = NULL;\n\tchar *bucket_offset = NULL;\n\tchar *bucket_width = NULL;\n\n\tif (IS_TIME_BUCKET_INFO_TIME_BASED(bf))\n\t{\n\t\t/* Bucketing on time */\n\t\tAssert(bf->bucket_time_width != NULL);\n\t\tbucket_width = DatumGetCString(\n\t\t\tDirectFunctionCall1(interval_out, IntervalPGetDatum(bf->bucket_time_width)));\n\n\t\tif (!TIMESTAMP_NOT_FINITE(bf->bucket_time_origin))\n\t\t{\n\t\t\tbucket_origin = DatumGetCString(\n\t\t\t\tDirectFunctionCall1(timestamptz_out, TimestampTzGetDatum(bf->bucket_time_origin)));\n\t\t}\n\n\t\tif (bf->bucket_time_offset != NULL)\n\t\t{\n\t\t\tbucket_offset = DatumGetCString(\n\t\t\t\tDirectFunctionCall1(interval_out, IntervalPGetDatum(bf->bucket_time_offset)));\n\t\t}\n\t}\n\telse\n\t{\n\t\t/* Bucketing on integers */\n\t\tbucket_width = palloc0((MAXINT8LEN + 1) * sizeof(char));\n\t\tpg_lltoa(bf->bucket_integer_width, bucket_width);\n\n\t\t/* Integer buckets with origin are not supported, so nothing to do. */\n\t\tAssert(bucket_origin == NULL);\n\n\t\tif (bf->bucket_integer_offset != 0)\n\t\t{\n\t\t\tbucket_offset = palloc0((MAXINT8LEN + 1) * sizeof(char));\n\t\t\tpg_lltoa(bf->bucket_integer_offset, bucket_offset);\n\t\t}\n\t}\n\n\ttupdesc = BlessTupleDesc(tupdesc);\n\n\tts_datum_set_objectid(Anum_cagg_bucket_function_oid, datums, bf->bucket_function);\n\tts_datum_set_text_from_cstring(Anum_cagg_bucket_function_width, datums, bucket_width);\n\tts_datum_set_text_from_cstring(Anum_cagg_bucket_function_origin, datums, bucket_origin);\n\tts_datum_set_text_from_cstring(Anum_cagg_bucket_function_offset, datums, bucket_offset);\n\tts_datum_set_text_from_cstring(Anum_cagg_bucket_function_timezone,\n\t\t\t\t\t\t\t\t   datums,\n\t\t\t\t\t\t\t\t   bf->bucket_time_timezone);\n\tts_datum_set_bool(Anum_cagg_bucket_function_fixed_width,\n\t\t\t\t\t  datums,\n\t\t\t\t\t  bf->bucket_fixed_interval,\n\t\t\t\t\t  false);\n\n\tAssert(tupdesc->natts == Natts_cagg_validate_query);\n\ttuple = ts_heap_form_tuple(tupdesc, datums);\n\n\treturn HeapTupleGetDatum(tuple);\n}\n\n/*\n * Get the bucket function information for the given materialized hypertable id.\n *\n * When running `cagg_get_bucket_function_info` the function returns the following fields:\n * - oid: The Oid of the bucket function\n * - width: The width of the bucket function\n * - origin: The origin of the bucket function\n * - offset: The offset of the bucket function\n * - timezone: The timezone of the bucket function\n * - fixed_width: Is the bucket width fixed\n *\n * When running `cagg_get_bucket_function` the function returns the following fields:\n * - oid: The Oid of the bucket function\n */\nstatic Datum\ncagg_get_bucket_function_datum(int32 mat_hypertable_id, FunctionCallInfo fcinfo)\n{\n\tOid direct_view_oid = get_direct_view_oid(mat_hypertable_id);\n\tTupleDesc tupdesc;\n\n\tif (fcinfo != NULL && get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)\n\t\telog(ERROR, \"function returning record called in context that cannot accept type record\");\n\n\tContinuousAggBucketFunction *bf = ts_cagg_get_bucket_function_info(direct_view_oid);\n\n\tif (!OidIsValid(bf->bucket_function))\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"time_bucket function not found in CAgg definition for \"\n\t\t\t\t\t\t\"mat_ht_id: %d\",\n\t\t\t\t\t\tmat_hypertable_id)));\n\t\tpg_unreachable();\n\t}\n\n\tif (fcinfo != NULL)\n\t\treturn create_cagg_get_bucket_function_datum(tupdesc, bf);\n\n\treturn ObjectIdGetDatum(bf->bucket_function);\n}\n\n/*\n * This function returns the `time_bucket` function Oid in the user view definition\n * of a given materialization hupertable.\n *\n * NOTE: this function is deprecated and should be removed in the future, use\n * `cagg_get_bucket_function_info` instead.\n */\nDatum\ncontinuous_agg_get_bucket_function(PG_FUNCTION_ARGS)\n{\n\t/* Return the oid of the bucket function */\n\tPG_RETURN_DATUM(cagg_get_bucket_function_datum(PG_GETARG_INT32(0), NULL));\n}\n\n/*\n * This function returns all information about the `time_bucket` function of a given\n * materialization hypertable using the user view definition stored in Postgres catalog.\n */\nDatum\ncontinuous_agg_get_bucket_function_info(PG_FUNCTION_ARGS)\n{\n\t/* Return all bucket function info */\n\tPG_RETURN_DATUM(cagg_get_bucket_function_datum(PG_GETARG_INT32(0), fcinfo));\n}\n\nDatum\ncontinuous_agg_get_grouping_columns(PG_FUNCTION_ARGS)\n{\n\tList *cagg_group_cols = NIL;\n\tListCell *lc = NULL;\n\tOid cagg_relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\tArrayBuildState *astate = NULL;\n\tContinuousAgg *cagg = cagg_get_by_relid_or_fail(cagg_relid);\n\tCache *hcache = ts_hypertable_cache_pin();\n\tHypertable *mat_ht = ts_hypertable_cache_get_entry_by_id(hcache, cagg->data.mat_hypertable_id);\n\tAssert(mat_ht != NULL);\n\n\tcagg_group_cols = cagg_find_groupingcols(cagg, mat_ht);\n\n\tforeach (lc, cagg_group_cols)\n\t{\n\t\tchar *group_col = lfirst(lc);\n\t\tastate = accumArrayResult(astate,\n\t\t\t\t\t\t\t\t  CStringGetTextDatum(group_col),\n\t\t\t\t\t\t\t\t  false,\n\t\t\t\t\t\t\t\t  TEXTOID,\n\t\t\t\t\t\t\t\t  CurrentMemoryContext);\n\t}\n\tts_cache_release(&hcache);\n\tPG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext));\n}\n"
  },
  {
    "path": "tsl/src/continuous_aggs/utils.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <catalog/pg_collation.h>\n#include <funcapi.h>\n#include <parser/analyze.h>\n#include <parser/parser.h>\n#include <tcop/tcopprot.h>\n\n#include \"compat/compat.h\"\n#include \"common.h\"\n\nextern Datum continuous_agg_validate_query(PG_FUNCTION_ARGS);\nextern Datum continuous_agg_get_bucket_function(PG_FUNCTION_ARGS);\nextern Datum continuous_agg_get_bucket_function_info(PG_FUNCTION_ARGS);\nextern Datum continuous_agg_get_grouping_columns(PG_FUNCTION_ARGS);\n"
  },
  {
    "path": "tsl/src/import/CMakeLists.txt",
    "content": "set(SOURCES)\n\nif(USE_UMASH)\n  list(APPEND SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/umash.c)\nendif()\n\nif(SOURCES)\n  # Disable clang-tidy for imported code\n  add_library(target_no_static_code_analysis OBJECT ${SOURCES})\n  set_target_properties(target_no_static_code_analysis PROPERTIES C_CLANG_TIDY\n                                                                  \"\")\n  target_sources(${TSL_LIBRARY_NAME}\n                 PRIVATE $<TARGET_OBJECTS:target_no_static_code_analysis>)\nendif()\n"
  },
  {
    "path": "tsl/src/import/ts_like_match.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * This file contains source code that was copied and/or modified from\n * the PostgreSQL database, which is licensed under the open-source\n * PostgreSQL License. Please see the NOTICE at the top level\n * directory for a copy of the PostgreSQL License.\n *\n * This is a copy of backend/utils/adt/like_match.c from PG 15.0, git commit sha\n * 2a7ce2e2ce474504a707ec03e128fde66cfb8b48.\n * It has one modification: the check_stack_depth() check is moved to happen\n * before recursion to simplify the non-recursive code path.\n */\n\n/*--------------------\n *\tMatch text and pattern, return LIKE_TRUE, LIKE_FALSE, or LIKE_ABORT.\n *\n *\tLIKE_TRUE: they match\n *\tLIKE_FALSE: they don't match\n *\tLIKE_ABORT: not only don't they match, but the text is too short.\n *\n * If LIKE_ABORT is returned, then no suffix of the text can match the\n * pattern either, so an upper-level % scan can stop scanning now.\n *--------------------\n */\n\n#ifdef MATCH_LOWER\n#define GETCHAR(t) MATCH_LOWER(t)\n#else\n#define GETCHAR(t) (t)\n#endif\n\nstatic int\nMatchText(const char *t, int tlen, const char *p, int plen)\n{\n\t/* Fast path for match-everything pattern */\n\tif (plen == 1 && *p == '%')\n\t\treturn LIKE_TRUE;\n\n\t/*\n\t * In this loop, we advance by char when matching wildcards (and thus on\n\t * recursive entry to this function we are properly char-synced). On other\n\t * occasions it is safe to advance by byte, as the text and pattern will\n\t * be in lockstep. This allows us to perform all comparisons between the\n\t * text and pattern on a byte by byte basis, even for multi-byte\n\t * encodings.\n\t */\n\twhile (tlen > 0 && plen > 0)\n\t{\n\t\tif (*p == '\\\\')\n\t\t{\n\t\t\t/* Next pattern byte must match literally, whatever it is */\n\t\t\tNextByte(p, plen);\n\t\t\t/* ... and there had better be one, per SQL standard */\n\t\t\tif (plen <= 0)\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE),\n\t\t\t\t\t\t errmsg(\"LIKE pattern must not end with escape character\")));\n\t\t\tif (GETCHAR(*p) != GETCHAR(*t))\n\t\t\t\treturn LIKE_FALSE;\n\t\t}\n\t\telse if (*p == '%')\n\t\t{\n\t\t\tchar firstpat;\n\n\t\t\t/*\n\t\t\t * % processing is essentially a search for a text position at\n\t\t\t * which the remainder of the text matches the remainder of the\n\t\t\t * pattern, using a recursive call to check each potential match.\n\t\t\t *\n\t\t\t * If there are wildcards immediately following the %, we can skip\n\t\t\t * over them first, using the idea that any sequence of N _'s and\n\t\t\t * one or more %'s is equivalent to N _'s and one % (ie, it will\n\t\t\t * match any sequence of at least N text characters).  In this way\n\t\t\t * we will always run the recursive search loop using a pattern\n\t\t\t * fragment that begins with a literal character-to-match, thereby\n\t\t\t * not recursing more than we have to.\n\t\t\t */\n\t\t\tNextByte(p, plen);\n\n\t\t\twhile (plen > 0)\n\t\t\t{\n\t\t\t\tif (*p == '%')\n\t\t\t\t\tNextByte(p, plen);\n\t\t\t\telse if (*p == '_')\n\t\t\t\t{\n\t\t\t\t\t/* If not enough text left to match the pattern, ABORT */\n\t\t\t\t\tif (tlen <= 0)\n\t\t\t\t\t\treturn LIKE_ABORT;\n\t\t\t\t\tNextChar(t, tlen);\n\t\t\t\t\tNextByte(p, plen);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\tbreak; /* Reached a non-wildcard pattern char */\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * If we're at end of pattern, match: we have a trailing % which\n\t\t\t * matches any remaining text string.\n\t\t\t */\n\t\t\tif (plen <= 0)\n\t\t\t\treturn LIKE_TRUE;\n\n\t\t\t/*\n\t\t\t * Otherwise, scan for a text position at which we can match the\n\t\t\t * rest of the pattern.  The first remaining pattern char is known\n\t\t\t * to be a regular or escaped literal character, so we can compare\n\t\t\t * the first pattern byte to each text byte to avoid recursing\n\t\t\t * more than we have to.  This fact also guarantees that we don't\n\t\t\t * have to consider a match to the zero-length substring at the\n\t\t\t * end of the text.\n\t\t\t */\n\t\t\tif (*p == '\\\\')\n\t\t\t{\n\t\t\t\tif (plen < 2)\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE),\n\t\t\t\t\t\t\t errmsg(\"LIKE pattern must not end with escape character\")));\n\t\t\t\tfirstpat = GETCHAR(p[1]);\n\t\t\t}\n\t\t\telse\n\t\t\t\tfirstpat = GETCHAR(*p);\n\n\t\t\twhile (tlen > 0)\n\t\t\t{\n\t\t\t\tif (GETCHAR(*t) == firstpat)\n\t\t\t\t{\n\t\t\t\t\t/* Since this function recurses, it could be driven to stack overflow */\n\t\t\t\t\tcheck_stack_depth();\n\n\t\t\t\t\tint matched = MatchText(t, tlen, p, plen);\n\n\t\t\t\t\tif (matched != LIKE_FALSE)\n\t\t\t\t\t\treturn matched; /* TRUE or ABORT */\n\t\t\t\t}\n\n\t\t\t\tNextChar(t, tlen);\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * End of text with no match, so no point in trying later places\n\t\t\t * to start matching this pattern.\n\t\t\t */\n\t\t\treturn LIKE_ABORT;\n\t\t}\n\t\telse if (*p == '_')\n\t\t{\n\t\t\t/* _ matches any single character, and we know there is one */\n\t\t\tNextChar(t, tlen);\n\t\t\tNextByte(p, plen);\n\t\t\tcontinue;\n\t\t}\n\t\telse if (GETCHAR(*p) != GETCHAR(*t))\n\t\t{\n\t\t\t/* non-wildcard pattern char fails to match text char */\n\t\t\treturn LIKE_FALSE;\n\t\t}\n\n\t\t/*\n\t\t * Pattern and text match, so advance.\n\t\t *\n\t\t * It is safe to use NextByte instead of NextChar here, even for\n\t\t * multi-byte character sets, because we are not following immediately\n\t\t * after a wildcard character. If we are in the middle of a multibyte\n\t\t * character, we must already have matched at least one byte of the\n\t\t * character from both text and pattern; so we cannot get out-of-sync\n\t\t * on character boundaries.  And we know that no backend-legal\n\t\t * encoding allows ASCII characters such as '%' to appear as non-first\n\t\t * bytes of characters, so we won't mistakenly detect a new wildcard.\n\t\t */\n\t\tNextByte(t, tlen);\n\t\tNextByte(p, plen);\n\t}\n\n\tif (tlen > 0)\n\t\treturn LIKE_FALSE; /* end of pattern, but not of text */\n\n\t/*\n\t * End of text, but perhaps not of pattern.  Match iff the remaining\n\t * pattern can match a zero-length string, ie, it's zero or more %'s.\n\t */\n\twhile (plen > 0 && *p == '%')\n\t\tNextByte(p, plen);\n\tif (plen <= 0)\n\t\treturn LIKE_TRUE;\n\n\t/*\n\t * End of text with no match, so no point in trying later places to start\n\t * matching this pattern.\n\t */\n\treturn LIKE_ABORT;\n} /* MatchText() */\n\n#ifdef CHAREQ\n#undef CHAREQ\n#endif\n\n#undef NextChar\n#undef CopyAdvChar\n#undef MatchText\n\n#undef GETCHAR\n\n#ifdef MATCH_LOWER\n#undef MATCH_LOWER\n\n#endif\n"
  },
  {
    "path": "tsl/src/import/umash.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * This file contains source code that was copied and/or modified from\n * the UMASH hash implementation at https://github.com/backtrace-labs/umash.\n *\n * This is a copy of umash.c, git commit sha\n * fc4c5b6ca1f06c308e96c43aa080bd766238e092.\n */\n\n#include \"umash.h\"\n\n/*\n * UMASH is distributed under the MIT license.\n *\n * SPDX-License-Identifier: MIT\n *\n * Copyright 2020-2022 Backtrace I/O, Inc.\n * Copyright 2022 Paul Khuong\n * Copyright 2022 Dougall Johnson\n *\n * Permission is hereby granted, free of charge, to any person\n * obtaining a copy of this software and associated documentation\n * files (the \"Software\"), to deal in the Software without\n * restriction, including without limitation the rights to use, copy,\n * modify, merge, publish, distribute, sublicense, and/or sell copies\n * of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n#if !defined(UMASH_TEST_ONLY) && !defined(NDEBUG)\n#define NDEBUG\n#endif\n\n/**\n * -DUMASH_LONG_INPUTS=0 to disable the routine specialised for long\n * inputs, and -DUMASH_LONG_INPUTS=1 to enable it.  If the variable\n * isn't defined, we try to probe for `umash_long.inc`: that's where\n * the long input routines are defined.\n */\n#ifndef UMASH_LONG_INPUTS\n#ifdef __has_include\n#if __has_include(\"umash_long.inc\")\n#define UMASH_LONG_INPUTS 1\n#endif /* __has_include() */\n#endif /* __has_include */\n\n#ifndef UMASH_LONG_INPUTS\n#define UMASH_LONG_INPUTS 0\n#endif /* !UMASH_LONG_INPUTS */\n#endif /* !UMASH_LONG_INPUTS */\n\n/*\n * Default to dynamically dispatching implementations on x86-64\n * (there's nothing to dispatch on aarch64).\n */\n#ifndef UMASH_DYNAMIC_DISPATCH\n#ifdef __x86_64__\n#define UMASH_DYNAMIC_DISPATCH 1\n#else\n#define UMASH_DYNAMIC_DISPATCH 0\n#endif\n#endif\n\n/*\n * Enable inline assembly by default when building with recent GCC or\n * compatible compilers.  It should always be safe to disable this\n * option, although there may be a performance cost.\n */\n#ifndef UMASH_INLINE_ASM\n\n#if defined(__clang__)\n/*\n * We need clang 8+ for output flags, and 10+ for relaxed vector\n * constraints.\n */\n#if __clang_major__ >= 10\n#define UMASH_INLINE_ASM 1\n#else\n#define UMASH_INLINE_ASM 0\n#endif /* __clang_major__ */\n\n#elif defined(__GNUC__)\n#if __GNUC__ >= 6\n#define UMASH_INLINE_ASM 1\n#else\n#define UMASH_INLINE_ASM 0\n#endif /* __GNUC__ */\n\n#else\n#define UMASH_INLINE_ASM 0\n#endif\n\n#endif\n\n#if defined __has_attribute\n#if __has_attribute(nonstring)\n#define TS_NONSTRING __attribute__((nonstring))\n#else\n#define TS_NONSTRING\n#endif\n#else\n#define TS_NONSTRING\n#endif\n\n#include <assert.h>\n#include <string.h>\n\n#ifdef __PCLMUL__\n/* If we have access to x86 PCLMUL (and some basic SSE). */\n#include <immintrin.h>\n\n/* We only use 128-bit vector, as pairs of 64-bit integers. */\ntypedef __m128i v128;\n\n#define V128_ZERO { 0 };\n\nstatic inline v128\nv128_create(uint64_t lo, uint64_t hi)\n{\n\treturn _mm_set_epi64x(hi, lo);\n}\n\n/* Shift each 64-bit lane left by one bit. */\nstatic inline v128\nv128_shift(v128 x)\n{\n\treturn _mm_add_epi64(x, x);\n}\n\n/* Computes the 128-bit carryless product of x and y. */\nstatic inline v128\nv128_clmul(uint64_t x, uint64_t y)\n{\n\treturn _mm_clmulepi64_si128(_mm_cvtsi64_si128(x), _mm_cvtsi64_si128(y), 0);\n}\n\n/* Computes the 128-bit carryless product of the high and low halves of x. */\nstatic inline v128\nv128_clmul_cross(v128 x)\n{\n\treturn _mm_clmulepi64_si128(x, x, 1);\n}\n\n#elif defined(__ARM_FEATURE_CRYPTO)\n\n#include <arm_neon.h>\n\ntypedef uint64x2_t v128;\n\n#define V128_ZERO { 0 };\n\nstatic inline v128\nv128_create(uint64_t lo, uint64_t hi)\n{\n\treturn vcombine_u64(vcreate_u64(lo), vcreate_u64(hi));\n}\n\nstatic inline v128\nv128_shift(v128 x)\n{\n\treturn vshlq_n_u64(x, 1);\n}\n\nstatic inline v128\nv128_clmul(uint64_t x, uint64_t y)\n{\n\treturn vreinterpretq_u64_p128(vmull_p64(x, y));\n}\n\nstatic inline v128\nv128_clmul_cross(v128 x)\n{\n\tv128 swapped = vextq_u64(x, x, 1);\n#if UMASH_INLINE_ASM\n\t/* Keep the result out of GPRs. */\n\t__asm__(\"\" : \"+w\"(swapped));\n#endif\n\n\treturn v128_clmul(vgetq_lane_u64(x, 0), vgetq_lane_u64(swapped, 0));\n}\n\n#else\n\n#error \\\n    \"Unsupported platform: umash requires CLMUL (-mpclmul) on x86-64, or crypto (-march=...+crypto) extensions on aarch64.\"\n#endif\n\n/*\n * #define UMASH_STAP_PROBE=1 to insert probe points in public UMASH\n * functions.\n *\n * This functionality depends on Systemtap's SDT header file.\n */\n#if defined(UMASH_STAP_PROBE) && UMASH_STAP_PROBE\n#include <sys/sdt.h>\n#else\n#define DTRACE_PROBE1(lib, name, a0)\n#define DTRACE_PROBE2(lib, name, a0, a1)\n#define DTRACE_PROBE3(lib, name, a0, a1, a2)\n#define DTRACE_PROBE4(lib, name, a0, a1, a2, a3)\n#endif\n\n/*\n * #define UMASH_SECTION=\"special_section\" to emit all UMASH symbols\n * in the `special_section` ELF section.\n */\n#if defined(UMASH_SECTION) && defined(__GNUC__)\n#define FN __attribute__((__section__(UMASH_SECTION)))\n#else\n#define FN\n#endif\n\n/*\n * Defining UMASH_TEST_ONLY switches to a debug build with internal\n * symbols exposed.\n */\n#ifdef UMASH_TEST_ONLY\n#define TEST_DEF FN\n#include \"t/umash_test_only.h\"\n#else\n#define TEST_DEF static FN\n#endif\n\n#ifdef __GNUC__\n#define LIKELY(X) __builtin_expect(!!(X), 1)\n#define UNLIKELY(X) __builtin_expect(!!(X), 0)\n#define HOT __attribute__((__hot__))\n#define COLD __attribute__((__cold__))\n#else\n#define LIKELY(X) X\n#define UNLIKELY(X) X\n#define HOT\n#define COLD\n#endif\n\n#define ARRAY_SIZE(ARR) (sizeof(ARR) / sizeof(ARR[0]))\n\n#define BLOCK_SIZE (sizeof(uint64_t) * UMASH_OH_PARAM_COUNT)\n\n/*\n * We derive independent short hashes by offsetting the constant array\n * by four u64s.  In theory, any positive even number works, but this\n * is the constant we used in an earlier incarnation, and it works.\n */\n#define OH_SHORT_HASH_SHIFT 4\n\n/* Incremental UMASH consumes 16 bytes at a time. */\n#define INCREMENTAL_GRANULARITY 16\n\n/**\n * Modular arithmetic utilities.\n *\n * The code below uses GCC extensions.  It should be possible to add\n * support for other compilers.\n */\n\n#if !defined(__x86_64__) || !UMASH_INLINE_ASM\nstatic inline void\nmul128(uint64_t x, uint64_t y, uint64_t *hi, uint64_t *lo)\n{\n\t__uint128_t product = x;\n\n\tproduct *= y;\n\t*hi = product >> 64;\n\t*lo = product;\n\treturn;\n}\n#else\nstatic inline void\nmul128(uint64_t x, uint64_t y, uint64_t *hi, uint64_t *lo)\n{\n\tuint64_t mulhi, mullo;\n\n\t__asm__(\"mul %3\" : \"=a\"(mullo), \"=d\"(mulhi) : \"%a\"(x), \"r\"(y) : \"cc\");\n\t*hi = mulhi;\n\t*lo = mullo;\n\treturn;\n}\n#endif\n\nTEST_DEF inline uint64_t\nadd_mod_fast(uint64_t x, uint64_t y)\n{\n\tunsigned long long sum;\n\n\t/* If `sum` overflows, `sum + 8` does not. */\n\treturn (__builtin_uaddll_overflow(x, y, &sum) ? sum + 8 : sum);\n}\n\nstatic FN COLD uint64_t\nadd_mod_slow_slow_path(uint64_t sum, uint64_t fixup)\n{\n\t/* Reduce sum, mod 2**64 - 8. */\n\tsum = (sum >= (uint64_t)-8) ? sum + 8 : sum;\n\t/* sum < 2**64 - 8, so this doesn't overflow. */\n\tsum += fixup;\n\t/* Reduce again. */\n\tsum = (sum >= (uint64_t)-8) ? sum + 8 : sum;\n\treturn sum;\n}\n\nTEST_DEF inline uint64_t\nadd_mod_slow(uint64_t x, uint64_t y)\n{\n\tunsigned long long sum;\n\tuint64_t fixup = 0;\n\n\t/* x + y \\equiv sum + fixup */\n\tif (__builtin_uaddll_overflow(x, y, &sum))\n\t\tfixup = 8;\n\n\t/*\n\t * We must ensure `sum + fixup < 2**64 - 8`.\n\t *\n\t * We want a conditional branch here, but not in the\n\t * overflowing add: overflows happen roughly half the time on\n\t * pseudorandom inputs, but `sum < 2**64 - 16` is almost\n\t * always true, for pseudorandom `sum`.\n\t */\n\tif (LIKELY(sum < (uint64_t)-16))\n\t\treturn sum + fixup;\n\n#ifdef UMASH_INLINE_ASM\n\t/*\n\t * Some compilers like to compile the likely branch above with\n\t * conditional moves or predication.  Insert a compiler barrier\n\t * in the slow path here to force a branch.\n\t */\n\t__asm__(\"\" : \"+r\"(sum));\n#endif\n\treturn add_mod_slow_slow_path(sum, fixup);\n}\n\nTEST_DEF inline uint64_t\nmul_mod_fast(uint64_t m, uint64_t x)\n{\n\tuint64_t hi, lo;\n\n\tmul128(m, x, &hi, &lo);\n\treturn add_mod_fast(lo, 8 * hi);\n}\n\nTEST_DEF inline uint64_t\nhorner_double_update(uint64_t acc, uint64_t m0, uint64_t m1, uint64_t x, uint64_t y)\n{\n\n\tacc = add_mod_fast(acc, x);\n\treturn add_mod_slow(mul_mod_fast(m0, acc), mul_mod_fast(m1, y));\n}\n\n/**\n * Salsa20 stream generator, used to derive struct umash_param.\n *\n * Slightly prettified version of D. J. Bernstein's public domain NaCL\n * (version 20110121), without paying any attention to constant time\n * execution or any other side-channel.\n */\nstatic inline uint32_t\nrotate(uint32_t u, int c)\n{\n\n\treturn (u << c) | (u >> (32 - c));\n}\n\nstatic inline uint32_t\nload_littleendian(const void *buf)\n{\n\tuint32_t ret = 0;\n\tuint8_t x[4];\n\n\tmemcpy(x, buf, sizeof(x));\n\tfor (size_t i = 0; i < 4; i++)\n\t\tret |= (uint32_t)x[i] << (8 * i);\n\n\treturn ret;\n}\n\nstatic inline void\nstore_littleendian(void *dst, uint32_t u)\n{\n\n\tfor (size_t i = 0; i < 4; i++) {\n\t\tuint8_t lo = u;\n\n\t\tmemcpy(dst, &lo, 1);\n\t\tu >>= 8;\n\t\tdst = (char *)dst + 1;\n\t}\n\n\treturn;\n}\n\nstatic FN void\ncore_salsa20(char *out, const uint8_t in[static 16], const uint8_t key[static 32],\n    const uint8_t constant[16])\n{\n\tenum { ROUNDS = 20 };\n\tuint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;\n\tuint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;\n\n\tj0 = x0 = load_littleendian(constant + 0);\n\tj1 = x1 = load_littleendian(key + 0);\n\tj2 = x2 = load_littleendian(key + 4);\n\tj3 = x3 = load_littleendian(key + 8);\n\tj4 = x4 = load_littleendian(key + 12);\n\tj5 = x5 = load_littleendian(constant + 4);\n\tj6 = x6 = load_littleendian(in + 0);\n\tj7 = x7 = load_littleendian(in + 4);\n\tj8 = x8 = load_littleendian(in + 8);\n\tj9 = x9 = load_littleendian(in + 12);\n\tj10 = x10 = load_littleendian(constant + 8);\n\tj11 = x11 = load_littleendian(key + 16);\n\tj12 = x12 = load_littleendian(key + 20);\n\tj13 = x13 = load_littleendian(key + 24);\n\tj14 = x14 = load_littleendian(key + 28);\n\tj15 = x15 = load_littleendian(constant + 12);\n\n\tfor (size_t i = 0; i < ROUNDS; i += 2) {\n\t\tx4 ^= rotate(x0 + x12, 7);\n\t\tx8 ^= rotate(x4 + x0, 9);\n\t\tx12 ^= rotate(x8 + x4, 13);\n\t\tx0 ^= rotate(x12 + x8, 18);\n\t\tx9 ^= rotate(x5 + x1, 7);\n\t\tx13 ^= rotate(x9 + x5, 9);\n\t\tx1 ^= rotate(x13 + x9, 13);\n\t\tx5 ^= rotate(x1 + x13, 18);\n\t\tx14 ^= rotate(x10 + x6, 7);\n\t\tx2 ^= rotate(x14 + x10, 9);\n\t\tx6 ^= rotate(x2 + x14, 13);\n\t\tx10 ^= rotate(x6 + x2, 18);\n\t\tx3 ^= rotate(x15 + x11, 7);\n\t\tx7 ^= rotate(x3 + x15, 9);\n\t\tx11 ^= rotate(x7 + x3, 13);\n\t\tx15 ^= rotate(x11 + x7, 18);\n\t\tx1 ^= rotate(x0 + x3, 7);\n\t\tx2 ^= rotate(x1 + x0, 9);\n\t\tx3 ^= rotate(x2 + x1, 13);\n\t\tx0 ^= rotate(x3 + x2, 18);\n\t\tx6 ^= rotate(x5 + x4, 7);\n\t\tx7 ^= rotate(x6 + x5, 9);\n\t\tx4 ^= rotate(x7 + x6, 13);\n\t\tx5 ^= rotate(x4 + x7, 18);\n\t\tx11 ^= rotate(x10 + x9, 7);\n\t\tx8 ^= rotate(x11 + x10, 9);\n\t\tx9 ^= rotate(x8 + x11, 13);\n\t\tx10 ^= rotate(x9 + x8, 18);\n\t\tx12 ^= rotate(x15 + x14, 7);\n\t\tx13 ^= rotate(x12 + x15, 9);\n\t\tx14 ^= rotate(x13 + x12, 13);\n\t\tx15 ^= rotate(x14 + x13, 18);\n\t}\n\n\tx0 += j0;\n\tx1 += j1;\n\tx2 += j2;\n\tx3 += j3;\n\tx4 += j4;\n\tx5 += j5;\n\tx6 += j6;\n\tx7 += j7;\n\tx8 += j8;\n\tx9 += j9;\n\tx10 += j10;\n\tx11 += j11;\n\tx12 += j12;\n\tx13 += j13;\n\tx14 += j14;\n\tx15 += j15;\n\n\tstore_littleendian(out + 0, x0);\n\tstore_littleendian(out + 4, x1);\n\tstore_littleendian(out + 8, x2);\n\tstore_littleendian(out + 12, x3);\n\tstore_littleendian(out + 16, x4);\n\tstore_littleendian(out + 20, x5);\n\tstore_littleendian(out + 24, x6);\n\tstore_littleendian(out + 28, x7);\n\tstore_littleendian(out + 32, x8);\n\tstore_littleendian(out + 36, x9);\n\tstore_littleendian(out + 40, x10);\n\tstore_littleendian(out + 44, x11);\n\tstore_littleendian(out + 48, x12);\n\tstore_littleendian(out + 52, x13);\n\tstore_littleendian(out + 56, x14);\n\tstore_littleendian(out + 60, x15);\n\treturn;\n}\n\nTEST_DEF void\nsalsa20_stream(\n    void *dst, size_t len, const uint8_t nonce[static 8], const uint8_t key[static 32])\n{\n\tstatic const uint8_t TS_NONSTRING sigma[16] = \"expand 32-byte k\";\n\tuint8_t in[16];\n\n\tif (len == 0)\n\t\treturn;\n\n\tmemcpy(in, nonce, 8);\n\tmemset(in + 8, 0, 8);\n\n\twhile (len >= 64) {\n\t\tunsigned int u;\n\n\t\tcore_salsa20(dst, in, key, sigma);\n\t\tu = 1;\n\t\tfor (size_t i = 8; i < 16; i++) {\n\t\t\tu += in[i];\n\t\t\tin[i] = u;\n\t\t\tu >>= 8;\n\t\t}\n\n\t\tdst = (char *)dst + 64;\n\t\tlen -= 64;\n\t}\n\n\tif (len > 0) {\n\t\tchar block[64];\n\n\t\tcore_salsa20(block, in, key, sigma);\n\t\tmemcpy(dst, block, len);\n\t}\n\n\treturn;\n}\n\n#if defined(UMASH_TEST_ONLY) || UMASH_LONG_INPUTS\n#include \"umash_long.inc\"\n#endif\n\n/**\n * OH block compression.\n */\nTEST_DEF struct umash_oh\noh_varblock(const uint64_t *params, uint64_t tag, const void *block, size_t n_bytes)\n{\n\tstruct umash_oh ret;\n\tv128 acc = V128_ZERO;\n\n\t/* The final block processes `remaining > 0` bytes. */\n\tsize_t remaining = 1 + ((n_bytes - 1) % sizeof(v128));\n\tsize_t end_full_pairs = (n_bytes - remaining) / sizeof(uint64_t);\n\tconst void *last_ptr = (const char *)block + n_bytes - sizeof(v128);\n\tsize_t i;\n\n\tfor (i = 0; i < end_full_pairs; i += 2) {\n\t\tv128 x, k;\n\n\t\tmemcpy(&x, block, sizeof(x));\n\t\tblock = (const char *)block + sizeof(x);\n\n\t\tmemcpy(&k, &params[i], sizeof(k));\n\t\tx ^= k;\n\t\tacc ^= v128_clmul_cross(x);\n\t}\n\n\tmemcpy(&ret, &acc, sizeof(ret));\n\n\t/* Compress the final (potentially partial) pair. */\n\t{\n\t\tuint64_t x, y, enh_hi, enh_lo;\n\n\t\tmemcpy(&x, last_ptr, sizeof(x));\n\t\tlast_ptr = (const char *)last_ptr + sizeof(x);\n\t\tmemcpy(&y, last_ptr, sizeof(y));\n\n\t\tx += params[i];\n\t\ty += params[i + 1];\n\t\tmul128(x, y, &enh_hi, &enh_lo);\n\t\tenh_hi += tag;\n\n\t\tret.bits[0] ^= enh_lo;\n\t\tret.bits[1] ^= enh_hi ^ enh_lo;\n\t}\n\n\treturn ret;\n}\n\nTEST_DEF void\noh_varblock_fprint(struct umash_oh dst[static restrict 2],\n    const uint64_t *restrict params, uint64_t tag, const void *restrict block,\n    size_t n_bytes)\n{\n\tv128 acc = V128_ZERO; /* Base umash */\n\tv128 acc_shifted = V128_ZERO; /* Accumulates shifted values */\n\tv128 lrc;\n\t/* The final block processes `remaining > 0` bytes. */\n\tsize_t remaining = 1 + ((n_bytes - 1) % sizeof(v128));\n\tsize_t end_full_pairs = (n_bytes - remaining) / sizeof(uint64_t);\n\tconst void *last_ptr = (const char *)block + n_bytes - sizeof(v128);\n\tsize_t i;\n\n\tlrc = v128_create(params[UMASH_OH_PARAM_COUNT], params[UMASH_OH_PARAM_COUNT + 1]);\n\tfor (i = 0; i < end_full_pairs; i += 2) {\n\t\tv128 x, k;\n\n\t\tmemcpy(&x, block, sizeof(x));\n\t\tblock = (const char *)block + sizeof(x);\n\n\t\tmemcpy(&k, &params[i], sizeof(k));\n\n\t\tx ^= k;\n\t\tlrc ^= x;\n\n\t\tx = v128_clmul_cross(x);\n\n\t\tacc ^= x;\n\t\tif (i + 2 >= end_full_pairs)\n\t\t\tbreak;\n\n\t\tacc_shifted ^= x;\n\t\tacc_shifted = v128_shift(acc_shifted);\n\t}\n\n\t/*\n\t * Update the LRC for the last chunk before treating it\n\t * specially.\n\t */\n\t{\n\t\tv128 x, k;\n\n\t\tmemcpy(&x, last_ptr, sizeof(x));\n\t\tmemcpy(&k, &params[end_full_pairs], sizeof(k));\n\n\t\tlrc ^= x ^ k;\n\t}\n\n\tacc_shifted ^= acc;\n\tacc_shifted = v128_shift(acc_shifted);\n\n\tacc_shifted ^= v128_clmul_cross(lrc);\n\n\tmemcpy(&dst[0], &acc, sizeof(dst[0]));\n\tmemcpy(&dst[1], &acc_shifted, sizeof(dst[1]));\n\n\t{\n\t\tuint64_t x, y, kx, ky, enh_hi, enh_lo;\n\n\t\tmemcpy(&x, last_ptr, sizeof(x));\n\t\tlast_ptr = (const char *)last_ptr + sizeof(x);\n\t\tmemcpy(&y, last_ptr, sizeof(y));\n\n\t\tkx = x + params[end_full_pairs];\n\t\tky = y + params[end_full_pairs + 1];\n\n\t\tmul128(kx, ky, &enh_hi, &enh_lo);\n\t\tenh_hi += tag;\n\n\t\tenh_hi ^= enh_lo;\n\t\tdst[0].bits[0] ^= enh_lo;\n\t\tdst[0].bits[1] ^= enh_hi;\n\n\t\tdst[1].bits[0] ^= enh_lo;\n\t\tdst[1].bits[1] ^= enh_hi;\n\t}\n\n\treturn;\n}\n\n/**\n * Returns `then` if `cond` is true, `otherwise` if false.\n *\n * This noise helps compiler emit conditional moves.\n */\nstatic inline const void *\nselect_ptr(bool cond, const void *then, const void *otherwise)\n{\n\tconst char *ret;\n\n#if UMASH_INLINE_ASM\n\t/* Force strict evaluation of both arguments. */\n\t__asm__(\"\" ::\"r\"(then), \"r\"(otherwise));\n#endif\n\n\tret = (cond) ? then : otherwise;\n\n#if UMASH_INLINE_ASM\n\t/* And also force the result to be materialised with a blackhole. */\n\t__asm__(\"\" : \"+r\"(ret));\n#endif\n\treturn ret;\n}\n\n/**\n * Short UMASH (<= 8 bytes).\n */\nTEST_DEF inline uint64_t\nvec_to_u64(const void *data, size_t n_bytes)\n{\n\tconst char zeros[2] = { 0 };\n\tuint32_t hi, lo;\n\n\t/*\n\t * If there are at least 4 bytes to read, read the first 4 in\n\t * `lo`, and the last 4 in `hi`.  This covers the whole range,\n\t * since `n_bytes` is at most 8.\n\t */\n\tif (LIKELY(n_bytes >= sizeof(lo))) {\n\t\tmemcpy(&lo, data, sizeof(lo));\n\t\tmemcpy(&hi, (const char *)data + n_bytes - sizeof(hi), sizeof(hi));\n\t} else {\n\t\t/* 0 <= n_bytes < 4.  Decode the size in binary. */\n\t\tuint16_t word;\n\t\tuint8_t byte;\n\n\t\t/*\n\t\t * If the size is odd, load the first byte in `byte`;\n\t\t * otherwise, load in a zero.\n\t\t */\n\t\tmemcpy(&byte, select_ptr(n_bytes & 1, data, zeros), 1);\n\t\tlo = byte;\n\n\t\t/*\n\t\t * If the size is 2 or 3, load the last two bytes in `word`;\n\t\t * otherwise, load in a zero.\n\t\t */\n\t\tmemcpy(&word,\n\t\t    select_ptr(n_bytes & 2, (const char *)data + n_bytes - 2, zeros), 2);\n\t\t/*\n\t\t * We have now read `bytes[0 ... n_bytes - 1]`\n\t\t * exactly once without overwriting any data.\n\t\t */\n\t\thi = word;\n\t}\n\n\t/*\n\t * Mix `hi` with the `lo` bits: SplitMix64 seems to have\n\t * trouble with the top 4 bits.\n\t */\n\treturn ((uint64_t)hi << 32) | (lo + hi);\n}\n\nTEST_DEF uint64_t\numash_short(const uint64_t *params, uint64_t seed, const void *data, size_t n_bytes)\n{\n\tuint64_t h;\n\n\tseed += params[n_bytes];\n\th = vec_to_u64(data, n_bytes);\n\th ^= h >> 30;\n\th *= 0xbf58476d1ce4e5b9ULL;\n\th = (h ^ seed) ^ (h >> 27);\n\th *= 0x94d049bb133111ebULL;\n\th ^= h >> 31;\n\treturn h;\n}\n\nstatic FN struct umash_fp\numash_fp_short(const uint64_t *params, uint64_t seed, const void *data, size_t n_bytes)\n{\n\tstruct umash_fp ret;\n\tuint64_t h;\n\n\tret.hash[0] = seed + params[n_bytes];\n\tret.hash[1] = seed + params[n_bytes + OH_SHORT_HASH_SHIFT];\n\n\th = vec_to_u64(data, n_bytes);\n\th ^= h >> 30;\n\th *= 0xbf58476d1ce4e5b9ULL;\n\th ^= h >> 27;\n\n#define TAIL(i)                                       \\\n\tdo {                                          \\\n\t\tret.hash[i] ^= h;                     \\\n\t\tret.hash[i] *= 0x94d049bb133111ebULL; \\\n\t\tret.hash[i] ^= ret.hash[i] >> 31;     \\\n\t} while (0)\n\n\tTAIL(0);\n\tTAIL(1);\n#undef TAIL\n\n\treturn ret;\n}\n\n/**\n * Rotates `x` left by `n` bits.\n */\nstatic inline uint64_t\nrotl64(uint64_t x, int n)\n{\n\n\treturn (x << n) | (x >> (64 - n));\n}\n\nTEST_DEF inline uint64_t\nfinalize(uint64_t x)\n{\n\n\treturn (x ^ rotl64(x, 8)) ^ rotl64(x, 33);\n}\n\nTEST_DEF uint64_t\numash_medium(const uint64_t multipliers[static 2], const uint64_t *oh, uint64_t seed,\n    const void *data, size_t n_bytes)\n{\n\tuint64_t enh_hi, enh_lo;\n\n\t{\n\t\tuint64_t x, y;\n\n\t\tmemcpy(&x, data, sizeof(x));\n\t\tmemcpy(&y, (const char *)data + n_bytes - sizeof(y), sizeof(y));\n\t\tx += oh[0];\n\t\ty += oh[1];\n\n\t\tmul128(x, y, &enh_hi, &enh_lo);\n\t\tenh_hi += seed ^ n_bytes;\n\t}\n\n\tenh_hi ^= enh_lo;\n\treturn finalize(horner_double_update(\n\t    /*acc=*/0, multipliers[0], multipliers[1], enh_lo, enh_hi));\n}\n\nstatic FN struct umash_fp\numash_fp_medium(const uint64_t multipliers[static 2][2], const uint64_t *oh,\n    uint64_t seed, const void *data, size_t n_bytes)\n{\n\tstruct umash_fp ret;\n\tconst uint64_t offset = seed ^ n_bytes;\n\tuint64_t enh_hi, enh_lo;\n\tunion {\n\t\tv128 v;\n\t\tuint64_t u64[2];\n\t} mixed_lrc;\n\tuint64_t lrc[2] = { oh[UMASH_OH_PARAM_COUNT], oh[UMASH_OH_PARAM_COUNT + 1] };\n\tuint64_t x, y;\n\tuint64_t a, b;\n\n\t/* Expand the 9-16 bytes to 16. */\n\tmemcpy(&x, data, sizeof(x));\n\tmemcpy(&y, (const char *)data + n_bytes - sizeof(y), sizeof(y));\n\n\ta = oh[0];\n\tb = oh[1];\n\n\tlrc[0] ^= x ^ a;\n\tlrc[1] ^= y ^ b;\n\tmixed_lrc.v = v128_clmul(lrc[0], lrc[1]);\n\n\ta += x;\n\tb += y;\n\n\tmul128(a, b, &enh_hi, &enh_lo);\n\tenh_hi += offset;\n\tenh_hi ^= enh_lo;\n\n\tret.hash[0] = finalize(horner_double_update(\n\t    /*acc=*/0, multipliers[0][0], multipliers[0][1], enh_lo, enh_hi));\n\n\tret.hash[1] = finalize(horner_double_update(/*acc=*/0, multipliers[1][0],\n\t    multipliers[1][1], enh_lo ^ mixed_lrc.u64[0], enh_hi ^ mixed_lrc.u64[1]));\n\n\treturn ret;\n}\n\nTEST_DEF uint64_t\numash_long(const uint64_t multipliers[static 2], const uint64_t *oh, uint64_t seed,\n    const void *data, size_t n_bytes)\n{\n\tuint64_t acc = 0;\n\n\t/*\n\t * umash_long.inc defines this variable when the long input\n\t * routine is enabled.\n\t */\n#ifdef UMASH_MULTIPLE_BLOCKS_THRESHOLD\n\tif (UNLIKELY(n_bytes >= UMASH_MULTIPLE_BLOCKS_THRESHOLD)) {\n\t\tsize_t n_block = n_bytes / BLOCK_SIZE;\n\t\tconst void *remaining;\n\n\t\tn_bytes %= BLOCK_SIZE;\n\t\tremaining = (const char *)data + (n_block * BLOCK_SIZE);\n\t\tacc = umash_multiple_blocks(acc, multipliers, oh, seed, data, n_block);\n\n\t\tdata = remaining;\n\t\tif (n_bytes == 0)\n\t\t\tgoto finalize;\n\n\t\tgoto last_block;\n\t}\n#else\n\t/* Avoid warnings about the unused labels. */\n\tif (0) {\n\t\tgoto last_block;\n\t\tgoto finalize;\n\t}\n#endif\n\n\twhile (n_bytes > BLOCK_SIZE) {\n\t\tstruct umash_oh compressed;\n\n\t\tcompressed = oh_varblock(oh, seed, data, BLOCK_SIZE);\n\t\tdata = (const char *)data + BLOCK_SIZE;\n\t\tn_bytes -= BLOCK_SIZE;\n\n\t\tacc = horner_double_update(acc, multipliers[0], multipliers[1],\n\t\t    compressed.bits[0], compressed.bits[1]);\n\t}\n\nlast_block:\n\t/* Do the final block. */\n\t{\n\t\tstruct umash_oh compressed;\n\n\t\tseed ^= (uint8_t)n_bytes;\n\t\tcompressed = oh_varblock(oh, seed, data, n_bytes);\n\t\tacc = horner_double_update(acc, multipliers[0], multipliers[1],\n\t\t    compressed.bits[0], compressed.bits[1]);\n\t}\n\nfinalize:\n\treturn finalize(acc);\n}\n\nTEST_DEF struct umash_fp\numash_fp_long(const uint64_t multipliers[static 2][2], const uint64_t *oh, uint64_t seed,\n    const void *data, size_t n_bytes)\n{\n\tstruct umash_oh compressed[2];\n\tstruct umash_fp ret;\n\tuint64_t acc[2] = { 0, 0 };\n\n#ifdef UMASH_MULTIPLE_BLOCKS_THRESHOLD\n\tif (UNLIKELY(n_bytes >= UMASH_MULTIPLE_BLOCKS_THRESHOLD)) {\n\t\tstruct umash_fp poly = { .hash = { 0, 0 } };\n\t\tsize_t n_block = n_bytes / BLOCK_SIZE;\n\t\tconst void *remaining;\n\n\t\tn_bytes %= BLOCK_SIZE;\n\t\tremaining = (const char *)data + (n_block * BLOCK_SIZE);\n\t\tpoly = umash_fprint_multiple_blocks(\n\t\t    poly, multipliers, oh, seed, data, n_block);\n\n\t\tacc[0] = poly.hash[0];\n\t\tacc[1] = poly.hash[1];\n\n\t\tdata = remaining;\n\t\tif (n_bytes == 0)\n\t\t\tgoto finalize;\n\n\t\tgoto last_block;\n\t}\n#else\n\t/* Avoid warnings about the unused labels. */\n\tif (0) {\n\t\tgoto last_block;\n\t\tgoto finalize;\n\t}\n#endif\n\n\twhile (n_bytes > BLOCK_SIZE) {\n\t\toh_varblock_fprint(compressed, oh, seed, data, BLOCK_SIZE);\n\n#define UPDATE(i)                                                                   \\\n\tacc[i] = horner_double_update(acc[i], multipliers[i][0], multipliers[i][1], \\\n\t    compressed[i].bits[0], compressed[i].bits[1])\n\n\t\tUPDATE(0);\n\t\tUPDATE(1);\n#undef UPDATE\n\n\t\tdata = (const char *)data + BLOCK_SIZE;\n\t\tn_bytes -= BLOCK_SIZE;\n\t}\n\nlast_block:\n\toh_varblock_fprint(compressed, oh, seed ^ (uint8_t)n_bytes, data, n_bytes);\n\n#define FINAL(i)                                                                      \\\n\tdo {                                                                          \\\n\t\tacc[i] = horner_double_update(acc[i], multipliers[i][0],              \\\n\t\t    multipliers[i][1], compressed[i].bits[0], compressed[i].bits[1]); \\\n\t} while (0)\n\n\tFINAL(0);\n\tFINAL(1);\n#undef FINAL\n\nfinalize:\n\tret.hash[0] = finalize(acc[0]);\n\tret.hash[1] = finalize(acc[1]);\n\treturn ret;\n}\n\nstatic FN bool\nvalue_is_repeated(const uint64_t *values, size_t n, uint64_t needle)\n{\n\n\tfor (size_t i = 0; i < n; i++) {\n\t\tif (values[i] == needle)\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nFN bool\numash_params_prepare(struct umash_params *params)\n{\n\tstatic const uint64_t modulo = (1UL << 61) - 1;\n\t/*\n\t * The polynomial parameters have two redundant fields (for\n\t * the pre-squared multipliers).  Use them as our source of\n\t * extra entropy if needed.\n\t */\n\tuint64_t buf[] = { params->poly[0][0], params->poly[1][0] };\n\tsize_t buf_idx = 0;\n\n#define GET_RANDOM(DST)                         \\\n\tdo {                                    \\\n\t\tif (buf_idx >= ARRAY_SIZE(buf)) \\\n\t\t\treturn false;           \\\n                                                \\\n\t\t(DST) = buf[buf_idx++];         \\\n\t} while (0)\n\n\t/* Check the polynomial multipliers: we don't want 0s. */\n\tfor (size_t i = 0; i < ARRAY_SIZE(params->poly); i++) {\n\t\tuint64_t f = params->poly[i][1];\n\n\t\twhile (true) {\n\t\t\t/*\n\t\t\t * Zero out bits and use rejection sampling to\n\t\t\t * guarantee uniformity.\n\t\t\t */\n\t\t\tf &= (1UL << 61) - 1;\n\t\t\tif (f != 0 && f < modulo)\n\t\t\t\tbreak;\n\n\t\t\tGET_RANDOM(f);\n\t\t}\n\n\t\t/* We can work in 2**64 - 8 and reduce after the fact. */\n\t\tparams->poly[i][0] = mul_mod_fast(f, f) % modulo;\n\t\tparams->poly[i][1] = f;\n\t}\n\n\t/* Avoid repeated OH noise values. */\n\tfor (size_t i = 0; i < ARRAY_SIZE(params->oh); i++) {\n\t\twhile (value_is_repeated(params->oh, i, params->oh[i]))\n\t\t\tGET_RANDOM(params->oh[i]);\n\t}\n\n\treturn true;\n}\n\nFN void\numash_params_derive(struct umash_params *params, uint64_t bits, const void *key)\n{\n\tuint8_t umash_key[32] TS_NONSTRING = \"Do not use UMASH VS adversaries.\";\n\n\tif (key != NULL)\n\t\tmemcpy(umash_key, key, sizeof(umash_key));\n\n\twhile (true) {\n\t\tuint8_t nonce[8];\n\n\t\tfor (size_t i = 0; i < 8; i++)\n\t\t\tnonce[i] = bits >> (8 * i);\n\n\t\tsalsa20_stream(params, sizeof(*params), nonce, umash_key);\n\t\tif (umash_params_prepare(params))\n\t\t\treturn;\n\n\t\t/*\n\t\t * This should practically never fail, so really\n\t\t * shouldn't happen multiple times.  If it does, an\n\t\t * infinite loop is as good as anything else.\n\t\t */\n\t\tbits++;\n\t}\n}\n\n/*\n * Updates the polynomial state at the end of a block.\n */\nstatic FN void\nsink_update_poly(struct umash_sink *sink)\n{\n\tuint64_t oh0, oh1;\n\n\toh0 = sink->oh_acc.bits[0];\n\toh1 = sink->oh_acc.bits[1];\n\tsink->poly_state[0].acc = horner_double_update(sink->poly_state[0].acc,\n\t    sink->poly_state[0].mul[0], sink->poly_state[0].mul[1], oh0, oh1);\n\n\tsink->oh_acc = (struct umash_oh) { .bits = { 0 } };\n\tif (sink->hash_wanted == 0)\n\t\treturn;\n\n\toh0 = sink->oh_twisted.acc.bits[0];\n\toh1 = sink->oh_twisted.acc.bits[1];\n\tsink->poly_state[1].acc = horner_double_update(sink->poly_state[1].acc,\n\t    sink->poly_state[1].mul[0], sink->poly_state[1].mul[1], oh0, oh1);\n\n\tsink->oh_twisted =\n\t    (struct umash_twisted_oh) { .lrc = { sink->oh[UMASH_OH_PARAM_COUNT],\n\t\t\t\t\t    sink->oh[UMASH_OH_PARAM_COUNT + 1] } };\n\treturn;\n}\n\n/*\n * Updates the OH state with 16 bytes of data.  If `final` is true, we\n * are definitely consuming the last chunk in the input.\n */\nstatic FN void\nsink_consume_buf(\n    struct umash_sink *sink, const char buf[static INCREMENTAL_GRANULARITY], bool final)\n{\n\tconst size_t buf_begin = sizeof(sink->buf) - INCREMENTAL_GRANULARITY;\n\tconst size_t param = sink->oh_iter;\n\tconst uint64_t k0 = sink->oh[param];\n\tconst uint64_t k1 = sink->oh[param + 1];\n\tuint64_t x, y;\n\n\t/* Use GPR loads to avoid forwarding stalls.  */\n\tmemcpy(&x, buf, sizeof(x));\n\tmemcpy(&y, buf + sizeof(x), sizeof(y));\n\n\t/* All but the last 16-byte chunk of each block goes through PH. */\n\tif (sink->oh_iter < UMASH_OH_PARAM_COUNT - 2 && !final) {\n\t\tv128 acc, h, twisted_acc, prev;\n\t\tuint64_t m0, m1;\n\n\t\tm0 = x ^ k0;\n\t\tm1 = y ^ k1;\n\n\t\tmemcpy(&acc, &sink->oh_acc, sizeof(acc));\n\t\th = v128_clmul(m0, m1);\n\t\tacc ^= h;\n\t\tmemcpy(&sink->oh_acc, &acc, sizeof(acc));\n\n\t\tif (sink->hash_wanted == 0)\n\t\t\tgoto next;\n\n\t\tsink->oh_twisted.lrc[0] ^= m0;\n\t\tsink->oh_twisted.lrc[1] ^= m1;\n\n\t\tmemcpy(&twisted_acc, &sink->oh_twisted.acc, sizeof(twisted_acc));\n\t\tmemcpy(&prev, sink->oh_twisted.prev, sizeof(prev));\n\n\t\ttwisted_acc ^= prev;\n\t\ttwisted_acc = v128_shift(twisted_acc);\n\t\tmemcpy(&sink->oh_twisted.acc, &twisted_acc, sizeof(twisted_acc));\n\t\tmemcpy(&sink->oh_twisted.prev, &h, sizeof(h));\n\t} else {\n\t\t/* The last chunk is combined with the size tag with ENH. */\n\t\tuint64_t tag = sink->seed ^ (uint8_t)(sink->block_size + sink->bufsz);\n\t\tuint64_t enh_hi, enh_lo;\n\n\t\tmul128(x + k0, y + k1, &enh_hi, &enh_lo);\n\t\tenh_hi += tag;\n\t\tenh_hi ^= enh_lo;\n\n\t\tif (sink->hash_wanted != 0) {\n\t\t\tunion {\n\t\t\t\tv128 vec;\n\t\t\t\tuint64_t h[2];\n\t\t\t} lrc_hash;\n\t\t\tuint64_t lrc0, lrc1;\n\t\t\tuint64_t oh0, oh1;\n\t\t\tuint64_t oh_twisted0, oh_twisted1;\n\n\t\t\tlrc0 = sink->oh_twisted.lrc[0] ^ x ^ k0;\n\t\t\tlrc1 = sink->oh_twisted.lrc[1] ^ y ^ k1;\n\t\t\tlrc_hash.vec = v128_clmul(lrc0, lrc1);\n\n\t\t\toh_twisted0 = sink->oh_twisted.acc.bits[0];\n\t\t\toh_twisted1 = sink->oh_twisted.acc.bits[1];\n\n\t\t\toh0 = sink->oh_acc.bits[0];\n\t\t\toh1 = sink->oh_acc.bits[1];\n\t\t\toh0 ^= oh_twisted0;\n\t\t\toh0 <<= 1;\n\t\t\toh1 ^= oh_twisted1;\n\t\t\toh1 <<= 1;\n\n\t\t\toh0 ^= lrc_hash.h[0];\n\t\t\toh1 ^= lrc_hash.h[1];\n\t\t\tsink->oh_twisted.acc.bits[0] = oh0 ^ enh_lo;\n\t\t\tsink->oh_twisted.acc.bits[1] = oh1 ^ enh_hi;\n\t\t}\n\n\t\tsink->oh_acc.bits[0] ^= enh_lo;\n\t\tsink->oh_acc.bits[1] ^= enh_hi;\n\t}\n\nnext:\n\tmemmove(&sink->buf, buf, buf_begin);\n\tsink->block_size += sink->bufsz;\n\tsink->bufsz = 0;\n\tsink->oh_iter += 2;\n\n\tif (sink->oh_iter == UMASH_OH_PARAM_COUNT || final) {\n\t\tsink_update_poly(sink);\n\t\tsink->block_size = 0;\n\t\tsink->oh_iter = 0;\n\t}\n\n\treturn;\n}\n\n/**\n * Hashes full 256-byte blocks into a sink that just dumped its OH\n * state in the toplevel polynomial hash and reset the block state.\n */\nstatic FN size_t\nblock_sink_update(struct umash_sink *sink, const void *data, size_t n_bytes)\n{\n\tsize_t consumed = 0;\n\n\tassert(n_bytes >= BLOCK_SIZE);\n\tassert(sink->bufsz == 0);\n\tassert(sink->block_size == 0);\n\tassert(sink->oh_iter == 0);\n\n#ifdef UMASH_MULTIPLE_BLOCKS_THRESHOLD\n\tif (UNLIKELY(n_bytes > UMASH_MULTIPLE_BLOCKS_THRESHOLD)) {\n\t\t/*\n\t\t * We leave the last block (partial or not) for the\n\t\t * caller: incremental hashing must save some state\n\t\t * at the end of a block.\n\t\t */\n\t\tsize_t n_blocks = (n_bytes - 1) / BLOCK_SIZE;\n\n\t\tif (sink->hash_wanted != 0) {\n\t\t\tconst uint64_t multipliers[2][2] = {\n\t\t\t\t[0][0] = sink->poly_state[0].mul[0],\n\t\t\t\t[0][1] = sink->poly_state[0].mul[1],\n\t\t\t\t[1][0] = sink->poly_state[1].mul[0],\n\t\t\t\t[1][1] = sink->poly_state[1].mul[1],\n\t\t\t};\n\t\t\tstruct umash_fp poly = {\n\t\t\t\t.hash[0] = sink->poly_state[0].acc,\n\t\t\t\t.hash[1] = sink->poly_state[1].acc,\n\t\t\t};\n\n\t\t\tpoly = umash_fprint_multiple_blocks(\n\t\t\t    poly, multipliers, sink->oh, sink->seed, data, n_blocks);\n\n\t\t\tsink->poly_state[0].acc = poly.hash[0];\n\t\t\tsink->poly_state[1].acc = poly.hash[1];\n\t\t} else {\n\t\t\tsink->poly_state[0].acc = umash_multiple_blocks(\n\t\t\t    sink->poly_state[0].acc, sink->poly_state[0].mul, sink->oh,\n\t\t\t    sink->seed, data, n_blocks);\n\t\t}\n\n\t\treturn n_blocks * BLOCK_SIZE;\n\t}\n#endif\n\n\twhile (n_bytes > BLOCK_SIZE) {\n\t\t/*\n\t\t * Is this worth unswitching?  Not obviously, given\n\t\t * the amount of work in one OH block.\n\t\t */\n\t\tif (sink->hash_wanted != 0) {\n\t\t\tstruct umash_oh hashes[2];\n\n\t\t\toh_varblock_fprint(\n\t\t\t    hashes, sink->oh, sink->seed, data, BLOCK_SIZE);\n\t\t\tsink->oh_acc = hashes[0];\n\t\t\tsink->oh_twisted.acc = hashes[1];\n\t\t} else {\n\t\t\tsink->oh_acc =\n\t\t\t    oh_varblock(sink->oh, sink->seed, data, BLOCK_SIZE);\n\t\t}\n\n\t\tsink_update_poly(sink);\n\t\tconsumed += BLOCK_SIZE;\n\t\tdata = (const char *)data + BLOCK_SIZE;\n\t\tn_bytes -= BLOCK_SIZE;\n\t}\n\n\treturn consumed;\n}\n\nFN void\numash_sink_update(struct umash_sink *sink, const void *data, size_t n_bytes)\n{\n\tconst size_t buf_begin = sizeof(sink->buf) - INCREMENTAL_GRANULARITY;\n\tsize_t remaining = INCREMENTAL_GRANULARITY - sink->bufsz;\n\n\tDTRACE_PROBE4(libumash, umash_sink_update, sink, remaining, data, n_bytes);\n\n\tif (n_bytes < remaining) {\n\t\tmemcpy(&sink->buf[buf_begin + sink->bufsz], data, n_bytes);\n\t\tsink->bufsz += n_bytes;\n\t\treturn;\n\t}\n\n\tmemcpy(&sink->buf[buf_begin + sink->bufsz], data, remaining);\n\tdata = (const char *)data + remaining;\n\tn_bytes -= remaining;\n\t/* We know we're hashing at least 16 bytes. */\n\tsink->large_umash = true;\n\tsink->bufsz = INCREMENTAL_GRANULARITY;\n\n\t/*\n\t * We can't compress a 16-byte buffer until we know whether\n\t * data is coming: the last 16-byte chunk goes to `NH` instead\n\t * of `PH`.  We could try to detect when the buffer is the\n\t * last chunk in a block and immediately go to `NH`, but it\n\t * seems more robust to always let the stores settle before we\n\t * read them, just in case the combination is bad for forwarding.\n\t */\n\tif (n_bytes == 0)\n\t\treturn;\n\n\tsink_consume_buf(sink, sink->buf + buf_begin, /*final=*/false);\n\n\twhile (n_bytes > INCREMENTAL_GRANULARITY) {\n\t\tsize_t consumed;\n\n\t\tif (sink->oh_iter == 0 && n_bytes > BLOCK_SIZE) {\n\t\t\tconsumed = block_sink_update(sink, data, n_bytes);\n\t\t\tassert(consumed >= BLOCK_SIZE);\n\n\t\t\t/*\n\t\t\t * Save the tail of the data we just consumed\n\t\t\t * in `sink->buf[0 ... buf_begin - 1]`: the\n\t\t\t * final digest may need those bytes for its\n\t\t\t * redundant read.\n\t\t\t */\n\t\t\tmemcpy(sink->buf,\n\t\t\t    (const char *)data + (consumed - INCREMENTAL_GRANULARITY),\n\t\t\t    buf_begin);\n\t\t} else {\n\t\t\tconsumed = INCREMENTAL_GRANULARITY;\n\t\t\tsink->bufsz = INCREMENTAL_GRANULARITY;\n\t\t\tsink_consume_buf(sink, data, /*final=*/false);\n\t\t}\n\n\t\tn_bytes -= consumed;\n\t\tdata = (const char *)data + consumed;\n\t}\n\n\tmemcpy(&sink->buf[buf_begin], data, n_bytes);\n\tsink->bufsz = n_bytes;\n\treturn;\n}\n\nFN uint64_t\numash_full(const struct umash_params *params, uint64_t seed, int which, const void *data,\n    size_t n_bytes)\n{\n\n\tDTRACE_PROBE4(libumash, umash_full, params, which, data, n_bytes);\n\n\t/*\n\t * We don't (yet) implement code that only evaluates the\n\t * second hash.  We don't currently use that logic, and it's\n\t * about to become a bit more complex, so let's just go for a\n\t * full fingerprint and take what we need.\n\t *\n\t * umash_full is also rarely used that way: usually we want\n\t * either the main hash, or the full fingerprint.\n\t */\n\tif (UNLIKELY(which != 0)) {\n\t\tstruct umash_fp fp;\n\n\t\tfp = umash_fprint(params, seed, data, n_bytes);\n\t\treturn fp.hash[1];\n\t}\n\n\t/*\n\t * It's not that short inputs are necessarily more likely, but\n\t * we want to make sure they fall through correctly to\n\t * minimise latency.\n\t */\n\tif (LIKELY(n_bytes <= sizeof(v128))) {\n\t\tif (LIKELY(n_bytes <= sizeof(uint64_t)))\n\t\t\treturn umash_short(params->oh, seed, data, n_bytes);\n\n\t\treturn umash_medium(params->poly[0], params->oh, seed, data, n_bytes);\n\t}\n\n\treturn umash_long(params->poly[0], params->oh, seed, data, n_bytes);\n}\n\nFN struct umash_fp\numash_fprint(\n    const struct umash_params *params, uint64_t seed, const void *data, size_t n_bytes)\n{\n\n\tDTRACE_PROBE3(libumash, umash_fprint, params, data, n_bytes);\n\tif (LIKELY(n_bytes <= sizeof(v128))) {\n\t\tif (LIKELY(n_bytes <= sizeof(uint64_t)))\n\t\t\treturn umash_fp_short(params->oh, seed, data, n_bytes);\n\n\t\treturn umash_fp_medium(params->poly, params->oh, seed, data, n_bytes);\n\t}\n\n\treturn umash_fp_long(params->poly, params->oh, seed, data, n_bytes);\n}\n\nFN void\numash_init(struct umash_state *state, const struct umash_params *params, uint64_t seed,\n    int which)\n{\n\n\twhich = (which == 0) ? 0 : 1;\n\tDTRACE_PROBE3(libumash, umash_init, state, params, which);\n\n\tstate->sink = (struct umash_sink) {\n\t\t.poly_state[0] = {\n\t\t\t.mul = {\n\t\t\t\tparams->poly[0][0],\n\t\t\t\tparams->poly[0][1],\n\t\t\t},\n\t\t},\n\t\t.poly_state[1]= {\n\t\t\t.mul = {\n\t\t\t\tparams->poly[1][0],\n\t\t\t\tparams->poly[1][1],\n\t\t\t},\n\t\t},\n\t\t.oh = params->oh,\n\t\t.hash_wanted = which,\n\t\t.oh_twisted.lrc = { params->oh[UMASH_OH_PARAM_COUNT],\n\t\t\tparams->oh[UMASH_OH_PARAM_COUNT + 1] },\n\t\t.seed = seed,\n\t};\n\n\treturn;\n}\n\nFN void\numash_fp_init(\n    struct umash_fp_state *state, const struct umash_params *params, uint64_t seed)\n{\n\n\tDTRACE_PROBE2(libumash, umash_fp_init, state, params);\n\n\tstate->sink = (struct umash_sink) {\n\t\t.poly_state[0] = {\n\t\t\t.mul = {\n\t\t\t\tparams->poly[0][0],\n\t\t\t\tparams->poly[0][1],\n\t\t\t},\n\t\t},\n\t\t.poly_state[1]= {\n\t\t\t.mul = {\n\t\t\t\tparams->poly[1][0],\n\t\t\t\tparams->poly[1][1],\n\t\t\t},\n\t\t},\n\t\t.oh = params->oh,\n\t\t.hash_wanted = 2,\n\t\t.oh_twisted.lrc = { params->oh[UMASH_OH_PARAM_COUNT],\n\t\t\tparams->oh[UMASH_OH_PARAM_COUNT + 1] },\n\t\t.seed = seed,\n\t};\n\n\treturn;\n}\n\n/**\n * Pumps any last block out of the incremental state.\n */\nstatic FN void\ndigest_flush(struct umash_sink *sink)\n{\n\n\tif (sink->bufsz > 0)\n\t\tsink_consume_buf(sink, &sink->buf[sink->bufsz], /*final=*/true);\n\treturn;\n}\n\n/**\n * Finalizes a digest out of `sink`'s current state.\n *\n * The `sink` must be `digest_flush`ed if it is a `large_umash`.\n *\n * @param index 0 to return the first (only, if hashing) value, 1 for the\n *   second independent value for fingerprinting.\n */\nstatic FN uint64_t\ndigest(const struct umash_sink *sink, int index)\n{\n\tconst size_t buf_begin = sizeof(sink->buf) - INCREMENTAL_GRANULARITY;\n\tconst size_t shift = (index == 0) ? 0 : OH_SHORT_HASH_SHIFT;\n\n\tif (sink->large_umash)\n\t\treturn finalize(sink->poly_state[index].acc);\n\n\tif (sink->bufsz <= sizeof(uint64_t))\n\t\treturn umash_short(\n\t\t    &sink->oh[shift], sink->seed, &sink->buf[buf_begin], sink->bufsz);\n\n\treturn umash_medium(sink->poly_state[index].mul, sink->oh, sink->seed,\n\t    &sink->buf[buf_begin], sink->bufsz);\n}\n\nstatic FN struct umash_fp\nfp_digest_sink(const struct umash_sink *sink)\n{\n\tstruct umash_sink copy;\n\tstruct umash_fp ret;\n\tconst size_t buf_begin = sizeof(sink->buf) - INCREMENTAL_GRANULARITY;\n\n\tif (sink->large_umash) {\n\t\tcopy = *sink;\n\t\tdigest_flush(&copy);\n\t\tsink = &copy;\n\t} else if (sink->bufsz <= sizeof(uint64_t)) {\n\t\treturn umash_fp_short(\n\t\t    sink->oh, sink->seed, &sink->buf[buf_begin], sink->bufsz);\n\t} else {\n\t\tconst struct umash_params *params;\n\n\t\t/*\n\t\t * Back out the params struct from our pointer to its\n\t\t * `oh` member.\n\t\t */\n\t\tparams = (const void *)((const char *)sink->oh -\n\t\t    __builtin_offsetof(struct umash_params, oh));\n\t\treturn umash_fp_medium(params->poly, sink->oh, sink->seed,\n\t\t    &sink->buf[buf_begin], sink->bufsz);\n\t}\n\n\tfor (size_t i = 0; i < ARRAY_SIZE(ret.hash); i++)\n\t\tret.hash[i] = digest(sink, i);\n\n\treturn ret;\n}\n\nFN uint64_t\numash_digest(const struct umash_state *state)\n{\n\tstruct umash_sink copy;\n\tconst struct umash_sink *sink = &state->sink;\n\n\tDTRACE_PROBE1(libumash, umash_digest, state);\n\n\tif (sink->hash_wanted == 1) {\n\t\tstruct umash_fp fp;\n\n\t\tfp = fp_digest_sink(sink);\n\t\treturn fp.hash[1];\n\t}\n\n\tif (sink->large_umash) {\n\t\tcopy = *sink;\n\t\tdigest_flush(&copy);\n\t\tsink = &copy;\n\t}\n\n\treturn digest(sink, 0);\n}\n\nFN struct umash_fp\numash_fp_digest(const struct umash_fp_state *state)\n{\n\n\tDTRACE_PROBE1(libumash, umash_fp_digest, state);\n\treturn fp_digest_sink(&state->sink);\n}\n"
  },
  {
    "path": "tsl/src/import/umash.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * This file contains source code that was copied and/or modified from\n * the UMASH hash implementation at https://github.com/backtrace-labs/umash.\n *\n * This is a copy of umash.h, git commit sha\n * fc4c5b6ca1f06c308e96c43aa080bd766238e092.\n */\n\n/*\n * UMASH is distributed under the MIT license.\n *\n * SPDX-License-Identifier: MIT\n *\n * Copyright 2020-2022 Backtrace I/O, Inc.\n * Copyright 2022 Paul Khuong\n * Copyright 2022 Dougall Johnson\n *\n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the\n * \"Software\"), to deal in the Software without restriction, including\n * without limitation the rights to use, copy, modify, merge, publish,\n * distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to\n * the following conditions:\n *\n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n#ifndef UMASH_H\n#define UMASH_H\n#include <stdbool.h>\n#include <stddef.h>\n#include <stdint.h>\n\n#ifndef TS_USE_UMASH\n#error \"UMASH usage is disabled, but the header is included\"\n#endif\n\n/**\n * # UMASH: a non-cryptographic hash function with collision bounds\n *\n * SPDX-License-Identifier: MIT\n * Copyright 2020-2022 Backtrace I/O, Inc.\n * Copyright 2022 Paul Khuong\n *\n * UMASH is a fast (9-22 ns latency for inputs of 1-64 bytes and 22\n * GB/s peak throughput, on a 2.5 GHz Intel 8175M) 64-bit hash\n * function with mathematically proven collision bounds: it is\n * [ceil(s / 4096) * 2^{-55}]-almost-universal for inputs of s or\n * fewer bytes.\n *\n * When that's not enough, UMASH can also generate a pair of 64-bit\n * hashes in a single traversal.  The resulting fingerprint reduces\n * the collision probability to less than [ceil(s / 2^{26})^2 * 2^{-83}];\n * the probability that two distinct inputs receive the same\n * fingerprint is less 2^{-83} for inputs up to 64 MB, and less than\n * 2^{-70} as long as the inputs are shorter than 5 GB each.  This\n * expectation is taken over the randomly generated `umash_params`.\n * If an attacker can infer the contents of these parameters, the\n * bounds do not apply.\n *\n * ## Initialisation\n *\n * In order to use `UMASH`, one must first generate a `struct\n * umash_params`; each such param defines a distinct `UMASH` function\n * (a pair of such functions, in fact).  Ideally, one would fill\n * a struct with random bytes and call`umash_params_prepare`.\n *\n * - `umash_params_prepare`: attempts to convert the contents of\n *   randomly filled `struct umash_params` into a valid UMASH\n *   parameter struct (key).  When the input consists of uniformly\n *   generated random bytes, the probability of failure is\n *   astronomically small.\n *\n * - `umash_params_derive`: deterministically constructs a `struct\n *   umash_params` from a 64-bit seed and an optional 32-byte secret.\n *   The seed and secret are expanded into random bytes with Salsa20;\n *   the resulting `umash_params` should be practically random, as\n *   long the seed or secret are unknown.\n *\n * ## Batch hashing and fingerprinting\n *\n * Once we have a `struct umash_params`, we can use `umash_full` or\n * `umash_fprint` like regular hash functions.\n *\n * - `umash_full` can compute either of the two UMASH functions\n *   described by a `struct umash_params`.  Its `seed` argument will\n *   change the output, but is not associated with any collision\n *   bound.\n *\n * - `umash_fprint` computes both `UMASH` functions described by a\n *   `struct umash_params`.  `umash_fp::hash[0]` corresponds to\n *   calling `umash_full` with the same arguments and `which = 0`;\n *   `umash_fp::hash[1]` corresponds to `which = 1`.\n *\n * ## Incremental hashing and fingerprinting\n *\n * We can also compute UMASH values by feeding bytes incrementally.\n * The result is guaranteed to the same as if we had buffered all the\n * bytes and called `umash_full` or `umash_fprint`.\n *\n * - `umash_init` initialises a `struct umash_state` with the same\n *   parameters one would pass to `umash_full`.\n *\n * - `umash_digest` computes the value `umash_full` would return\n *   were it passed the arguments that were given to `umash_init`,\n *   and the bytes \"fed\" into the `umash_state`.\n *\n * - `umash_fp_init` initialises a `struct umash_fp_state` with the\n *   same parameters one would pass to `umash_fprint`.\n *\n * - `umash_fp_digest` computes the value `umash_fprint` would return\n *   for the bytes \"fed\" into the `umash_fp_state`.\n *\n * In both cases, one passes a pointer to `struct umash_state::sink`\n * or `struct umash_fp_state::sink` to callees that wish to feed bytes\n * into the `umash_state` or `umash_fp_state`.\n *\n * - `umash_sink_update` feeds a byte range to the `umash_sink`\n *   initialised by calling `umash_init` or `umash_fp_init`.  The sink\n *   does not take ownership of anything and the input bytes may be\n *   overwritten or freed as soon as `umash_sink_update` returns.\n */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nenum { UMASH_OH_PARAM_COUNT = 32, UMASH_OH_TWISTING_COUNT = 2 };\n\n/**\n * A single UMASH params struct stores the parameters for a pair of\n * independent `UMASH` functions.\n */\nstruct umash_params {\n\t/*\n\t * Each uint64_t[2] array consists of {f^2, f}, where f is a\n\t * random multiplier in mod 2**61 - 1.\n\t */\n\tuint64_t poly[2][2];\n\t/*\n\t * The second (twisted) OH function uses an additional\n\t * 128-bit constant stored in the last two elements.\n\t */\n\tuint64_t oh[UMASH_OH_PARAM_COUNT + UMASH_OH_TWISTING_COUNT];\n};\n\n/**\n * A fingerprint consists of two independent `UMASH` hash values.\n */\nstruct umash_fp {\n\tuint64_t hash[2];\n};\n\n/**\n * This struct holds the state for incremental UMASH hashing or\n * fingerprinting.\n *\n * A sink owns no allocation, and simply borrows a pointer to its\n * `umash_params`.  It can be byte-copied to snapshot its state.\n *\n * The layout works best with alignment to 64 bytes, but does not\n * require it.\n */\nstruct umash_sink {\n\t/*\n\t * We incrementally maintain two states when fingerprinting.\n\t * When hashing, only the first `poly_state` and `oh_acc`\n\t * entries are active.\n\t */\n\tstruct {\n\t\tuint64_t mul[2]; /* Multiplier, and multiplier^2. */\n\t\tuint64_t acc; /* Current Horner accumulator. */\n\t} poly_state[2];\n\n\t/*\n\t * We write new bytes to the second half, and keep the previous\n\t * 16 byte chunk in the first half.\n\t *\n\t * We may temporarily have a full 16-byte buffer in the second half:\n\t * we must know if the first 16 byte chunk is the first of many, or\n\t * the whole input.\n\t */\n\tchar buf[2 * 16];\n\n\t/* The next 64 bytes are accessed in the `OH` inner loop. */\n\n\t/* key->oh. */\n\tconst uint64_t *oh;\n\n\t/* oh_iter tracks where we are in the inner loop, times 2. */\n\tuint32_t oh_iter;\n\tuint8_t bufsz; /* Write pointer in `buf + 16`. */\n\tuint8_t block_size; /* Current OH block size, excluding `bufsz`. */\n\tbool large_umash; /* True once we definitely have >= 16 bytes. */\n\t/*\n\t * 0 if we're computing the first umash, 1 for the second, and\n\t * 2 for a fingerprint.\n\t *\n\t * In practice, we treat 1 and 2 the same (always compute a\n\t * full fingerprint), and return only the second half if we\n\t * only want that half.\n\t */\n\tuint8_t hash_wanted;\n\n\t/* Accumulators for the current OH value. */\n\tstruct umash_oh {\n\t\tuint64_t bits[2];\n\t} oh_acc;\n\tstruct umash_twisted_oh {\n\t\tuint64_t lrc[2];\n\t\tuint64_t prev[2];\n\t\tstruct umash_oh acc;\n\t} oh_twisted;\n\n\tuint64_t seed;\n};\n\n/**\n * The `umash_state` struct wraps a sink in a type-safe interface: we\n * don't want to try and extract a fingerprint from a sink configured\n * for hashing.\n */\nstruct umash_state {\n\tstruct umash_sink sink;\n};\n\n/**\n * Similarly, the `umash_fp_state` struct wraps a sink from which we\n * should extract a fingerprint.\n */\nstruct umash_fp_state {\n\tstruct umash_sink sink;\n};\n\n/**\n * Converts a `umash_params` struct filled with random values into\n * something usable by the UMASH functions below.\n *\n * When it succeeds, this function is idempotent.  Failure happens\n * with probability < 2**-110 is `params` is filled with uniformly\n * distributed random bits.  That's an astronomically unlikely event,\n * and most likely signals an issue with the caller's (pseudo-)random\n * number generator.\n *\n * @return false on failure, probably because the input was not random.\n */\nbool umash_params_prepare(struct umash_params *params);\n\n/**\n * Deterministically derives a `umash_params` struct from `bits` and\n * `key`.  The `bits` values do not have to be particularly well\n * distributed, and can be generated sequentially.\n *\n * @param key a pointer to exactly 32 secret bytes.  NULL will be\n *   replaced with \"Do not use UMASH VS adversaries.\", the default\n *   UMASH secret.\n */\nvoid umash_params_derive(struct umash_params *, uint64_t bits, const void *key);\n\n/**\n * Updates a `umash_sink` to take into account `data[0 ... n_bytes)`.\n */\nvoid umash_sink_update(struct umash_sink *, const void *data, size_t n_bytes);\n\n/**\n * Computes the UMASH hash of `data[0 ... n_bytes)`.\n *\n * Randomly generated `param` lead to independent UMASH values and\n * associated worst-case collision bounds; changing the `seed` comes\n * with no guarantee.\n *\n * @param which 0 to compute the first UMASH defined by `params`, 1\n *   for the second.\n */\nuint64_t umash_full(const struct umash_params *params, uint64_t seed, int which,\n\tconst void *data, size_t n_bytes);\n\n/**\n * Computes the UMASH fingerprint of `data[0 ... n_bytes)`.\n *\n * Randomly generated `param` lead to independent UMASH values and\n * associated worst-case collision bounds; changing the `seed` comes\n * with no guarantee.\n */\nstruct umash_fp umash_fprint(\n\tconst struct umash_params *params, uint64_t seed, const void *data, size_t n_bytes);\n\n/**\n * Prepares a `umash_state` for computing the `which`th UMASH function in\n * `params`.\n */\nvoid umash_init(\n\tstruct umash_state *, const struct umash_params *params, uint64_t seed, int which);\n\n/**\n * Returns the UMASH value for the bytes that have been\n * `umash_sink_update`d into the state.\n */\nuint64_t umash_digest(const struct umash_state *);\n\n/**\n * Prepares a `umash_fp_state` for computing the UMASH fingerprint in\n * `params`.\n */\nvoid umash_fp_init(\n\tstruct umash_fp_state *, const struct umash_params *params, uint64_t seed);\n\n/**\n * Returns the UMASH fingerprint for the bytes that have been\n * `umash_sink_update`d into the state.\n */\nstruct umash_fp umash_fp_digest(const struct umash_fp_state *);\n\n#ifdef __cplusplus\n}\n#endif\n#endif /* !UMASH_H */\n"
  },
  {
    "path": "tsl/src/init.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n#include <fmgr.h>\n#include <storage/ipc.h>\n\n#include \"bgw_policy/compression_api.h\"\n#include \"bgw_policy/continuous_aggregate_api.h\"\n#include \"bgw_policy/job.h\"\n#include \"bgw_policy/job_api.h\"\n#include \"bgw_policy/policies_v2.h\"\n#include \"bgw_policy/process_hyper_inval_api.h\"\n#include \"bgw_policy/reorder_api.h\"\n#include \"bgw_policy/retention_api.h\"\n#include \"chunk.h\"\n#include \"chunk_api.h\"\n#include \"compression/algorithms/array.h\"\n#include \"compression/algorithms/bool_compress.h\"\n#include \"compression/algorithms/deltadelta.h\"\n#include \"compression/algorithms/dictionary.h\"\n#include \"compression/algorithms/gorilla.h\"\n#include \"compression/algorithms/uuid_compress.h\"\n#include \"compression/api.h\"\n#include \"compression/compression.h\"\n#include \"compression/create.h\"\n#include \"compression/recompress.h\"\n#include \"compression/sparse_index_bloom1.h\"\n#include \"continuous_aggs/create.h\"\n#include \"continuous_aggs/insert.h\"\n#include \"continuous_aggs/invalidation.h\"\n#include \"continuous_aggs/options.h\"\n#include \"continuous_aggs/refresh.h\"\n#include \"continuous_aggs/utils.h\"\n#include \"cross_module_fn.h\"\n#include \"export.h\"\n#include \"hypertable.h\"\n#include \"license_guc.h\"\n#include \"nodes/columnar_index_scan/columnar_index_scan.h\"\n#include \"nodes/columnar_scan/planner.h\"\n#include \"nodes/gapfill/gapfill_functions.h\"\n#include \"nodes/skip_scan/skip_scan.h\"\n#include \"nodes/vector_agg/plan.h\"\n#include \"planner.h\"\n#include \"process_utility.h\"\n#include \"reorder.h\"\n\n#ifdef PG_MODULE_MAGIC\nPG_MODULE_MAGIC;\n#endif\n\n#ifdef APACHE_ONLY\n#error \"cannot compile the TSL for ApacheOnly mode\"\n#endif\n\n#if PG16_LT\nextern void PGDLLEXPORT _PG_init(void);\n#endif\n\n/*\n * Cross module function initialization.\n *\n * During module start we set ts_cm_functions to point at the tsl version of the\n * function registry.\n *\n * NOTE: To ensure that your cross-module function has a correct default, you\n * must also add it to ts_cm_functions_default in cross_module_fn.c in the\n * Apache codebase.\n */\nCrossModuleFunctions tsl_cm_functions = {\n\n\t.create_upper_paths_hook = tsl_create_upper_paths_hook,\n\t.set_rel_pathlist_dml = tsl_set_rel_pathlist_dml,\n\t.set_rel_pathlist_query = tsl_set_rel_pathlist_query,\n\t.sort_transform_replace_pathkeys = tsl_sort_transform_replace_pathkeys,\n\n\t/* bgw policies */\n\t.policy_compression_add = policy_compression_add,\n\t.policy_compression_remove = policy_compression_remove,\n\t.policy_recompression_proc = policy_recompression_proc,\n\t.policy_compression_check = policy_compression_check,\n\t.policy_refresh_cagg_add = policy_refresh_cagg_add,\n\t.policy_refresh_cagg_proc = policy_refresh_cagg_proc,\n\t.policy_refresh_cagg_check = policy_refresh_cagg_check,\n\t.policy_refresh_cagg_remove = policy_refresh_cagg_remove,\n\t.policy_process_hyper_inval_add = policy_process_hyper_inval_add,\n\t.policy_process_hyper_inval_proc = policy_process_hyper_inval_proc,\n\t.policy_process_hyper_inval_check = policy_process_hyper_inval_check,\n\t.policy_process_hyper_inval_remove = policy_process_hyper_inval_remove,\n\t.policy_reorder_add = policy_reorder_add,\n\t.policy_reorder_proc = policy_reorder_proc,\n\t.policy_reorder_check = policy_reorder_check,\n\t.policy_reorder_remove = policy_reorder_remove,\n\t.policy_retention_add = policy_retention_add,\n\t.policy_retention_proc = policy_retention_proc,\n\t.policy_retention_check = policy_retention_check,\n\t.policy_retention_remove = policy_retention_remove,\n\n\t.job_add = job_add,\n\t.job_alter = job_alter,\n\t.job_alter_set_hypertable_id = job_alter_set_hypertable_id,\n\t.job_delete = job_delete,\n\t.job_run = job_run,\n\t.job_execute = job_execute,\n\n\t/* gapfill */\n\t.gapfill_marker = gapfill_marker,\n\t.gapfill_int16_time_bucket = gapfill_int16_time_bucket,\n\t.gapfill_int32_time_bucket = gapfill_int32_time_bucket,\n\t.gapfill_int64_time_bucket = gapfill_int64_time_bucket,\n\t.gapfill_date_time_bucket = gapfill_date_time_bucket,\n\t.gapfill_timestamp_time_bucket = gapfill_timestamp_time_bucket,\n\t.gapfill_timestamptz_time_bucket = gapfill_timestamptz_time_bucket,\n\t.gapfill_timestamptz_timezone_time_bucket = gapfill_timestamptz_timezone_time_bucket,\n\n\t.reorder_chunk = tsl_reorder_chunk,\n\t.move_chunk = tsl_move_chunk,\n\n\t.policies_add = policies_add,\n\t.policies_remove = policies_remove,\n\t.policies_remove_all = policies_remove_all,\n\t.policies_alter = policies_alter,\n\t.policies_show = policies_show,\n\n\t/* Vectorized queries */\n\t.tsl_postprocess_plan = tsl_postprocess_plan,\n\n\t/* Continuous Aggregates */\n\t.process_cagg_viewstmt = tsl_process_continuous_agg_viewstmt,\n\t.continuous_agg_refresh = continuous_agg_refresh,\n\t.continuous_agg_invalidate_raw_ht = continuous_agg_invalidate_raw_ht,\n\t.continuous_agg_invalidate_mat_ht = continuous_agg_invalidate_mat_ht,\n\t.continuous_agg_dml_invalidate = continuous_agg_dml_invalidate,\n\t.continuous_agg_update_options = continuous_agg_update_options,\n\t.continuous_agg_validate_query = continuous_agg_validate_query,\n\t.continuous_agg_get_bucket_function = continuous_agg_get_bucket_function,\n\t.continuous_agg_get_bucket_function_info = continuous_agg_get_bucket_function_info,\n\t.continuous_agg_get_grouping_columns = continuous_agg_get_grouping_columns,\n\n\t/* Compression */\n\t.compressed_data_decompress_forward = tsl_compressed_data_decompress_forward,\n\t.compressed_data_decompress_reverse = tsl_compressed_data_decompress_reverse,\n\t.compressed_data_column_size = tsl_compressed_data_column_size,\n\t.compressed_data_to_array = tsl_compressed_data_to_array,\n\t.compressed_data_send = tsl_compressed_data_send,\n\t.compressed_data_recv = tsl_compressed_data_recv,\n\t.compressed_data_in = tsl_compressed_data_in,\n\t.compressed_data_out = tsl_compressed_data_out,\n\t.compressed_data_info = tsl_compressed_data_info,\n\t.compressed_data_has_nulls = tsl_compressed_data_has_nulls,\n\t.deltadelta_compressor_append = tsl_deltadelta_compressor_append,\n\t.deltadelta_compressor_finish = tsl_deltadelta_compressor_finish,\n\t.gorilla_compressor_append = tsl_gorilla_compressor_append,\n\t.gorilla_compressor_finish = tsl_gorilla_compressor_finish,\n\t.dictionary_compressor_append = tsl_dictionary_compressor_append,\n\t.dictionary_compressor_finish = tsl_dictionary_compressor_finish,\n\t.array_compressor_append = tsl_array_compressor_append,\n\t.array_compressor_finish = tsl_array_compressor_finish,\n\t.bool_compressor_append = tsl_bool_compressor_append,\n\t.bool_compressor_finish = tsl_bool_compressor_finish,\n\t.uuid_compressor_append = tsl_uuid_compressor_append,\n\t.uuid_compressor_finish = tsl_uuid_compressor_finish,\n\t.bloom1_contains = bloom1_contains,\n\t.bloom1_contains_any = bloom1_contains_any,\n\t.bloom1_get_hash_function = bloom1_get_hash_function,\n\t.process_compress_table = tsl_process_compress_table,\n\t.process_altertable_cmd = tsl_process_altertable_cmd,\n\t.process_rename_cmd = tsl_process_rename_cmd,\n\t.compress_chunk = tsl_compress_chunk,\n\t.decompress_chunk = tsl_decompress_chunk,\n\t.rebuild_columnstore = tsl_rebuild_columnstore,\n\t.decompress_batches_for_insert = decompress_batches_for_insert,\n\t.init_decompress_state_for_insert = init_decompress_state_for_insert,\n\t.decompress_target_segments = decompress_target_segments,\n\t.columnstore_setup = tsl_columnstore_setup,\n\t.compressor_init = tsl_compressor_init,\n\t.compressor_set_invalidation = tsl_compressor_set_invalidation,\n\t.compressor_add_slot = tsl_compressor_add_slot,\n\t.compressor_flush = tsl_compressor_flush,\n\t.compressor_free = tsl_compressor_free,\n\t.compression_chunk_create = tsl_compression_chunk_create,\n\t.show_chunk = chunk_show,\n\t.create_compressed_chunk = tsl_create_compressed_chunk,\n\t.create_chunk = chunk_create,\n\t.chunk_freeze_chunk = chunk_freeze_chunk,\n\t.chunk_unfreeze_chunk = chunk_unfreeze_chunk,\n\t.recompress_chunk_segmentwise = tsl_recompress_chunk_segmentwise,\n\t.get_compressed_chunk_index_for_recompression =\n\t\ttsl_get_compressed_chunk_index_for_recompression,\n\t.preprocess_query_tsl = tsl_preprocess_query,\n\t.merge_chunks = chunk_merge_chunks,\n\t.split_chunk = chunk_split_chunk,\n\t.detach_chunk = chunk_detach,\n\t.attach_chunk = chunk_attach,\n\t.estimate_compressed_batch_size = tsl_estimate_compressed_batch_size,\n};\n\nstatic void\nts_module_cleanup_on_pg_exit(int code, Datum arg)\n{\n\t_continuous_aggs_cache_inval_fini();\n}\n\nTS_FUNCTION_INFO_V1(ts_module_init);\n/*\n * Module init function, sets ts_cm_functions to point at tsl_cm_functions\n */\nPGDLLEXPORT Datum\nts_module_init(PG_FUNCTION_ARGS)\n{\n\tbool register_proc_exit = PG_GETARG_BOOL(0);\n\tts_cm_functions = &tsl_cm_functions;\n\n\t_continuous_aggs_cache_inval_init();\n\t_columnar_index_scan_init();\n\t_columnar_scan_init();\n\t_skip_scan_init();\n\t_vector_agg_init();\n\n\t/* Register a cleanup function to be called when the backend exits */\n\tif (register_proc_exit)\n\t{\n\t\ton_proc_exit(ts_module_cleanup_on_pg_exit, 0);\n\n\t\t/*\n\t\t * We also register some GUCs here which are impossible to register in\n\t\t * the Apache module, because the default value is only known in the TSL\n\t\t * module. It is done in this branch to avoid being called multiple\n\t\t * times in the parallel workers.\n\t\t */\n\n\t\t/*\n\t\t * The read-only GUC to query the current metadata column prefix used\n\t\t * for bloom filter sparse indexes. It can be different depending on the\n\t\t * hashing schema we use, that is determined at build time. In debug\n\t\t * builds, it can be changed for testing.\n\t\t */\n\t\tbloom1_column_prefix = default_bloom1_column_prefix;\n\t\tDefineCustomStringVariable(MAKE_EXTOPTION(\"bloom1_column_prefix\"),\n\t\t\t\t\t\t\t\t   \"bloom filter column prefix\",\n\t\t\t\t\t\t\t\t   \"The prefix used for the metadata columns storing the sparse \"\n\t\t\t\t\t\t\t\t   \"bloom filter indexes.\",\n\t\t\t\t\t\t\t\t   (char **) &bloom1_column_prefix,\n\t\t\t\t\t\t\t\t   default_bloom1_column_prefix,\n#ifndef NDEBUG\n\t\t\t\t\t\t\t\t   PGC_USERSET,\n#else\n\t\t\t\t\t\t\t\t   PGC_INTERNAL,\n#endif\n\t\t\t\t\t\t\t\t   0,\n\t\t\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t\t\t   NULL,\n\t\t\t\t\t\t\t\t   NULL);\n\t}\n\n\tPG_RETURN_BOOL(true);\n}\n\n/* Informative functions */\n\nPGDLLEXPORT void\n_PG_init(void)\n{\n\t/*\n\t * In a normal backend, we disable loading the tsl until after the main\n\t * timescale library is loaded, after which we enable it from the loader.\n\t * In parallel workers the restore shared libraries function will load the\n\t * libraries itself, and we bypass the loader, so we need to ensure that\n\t * timescale is aware it can use the tsl if needed. It is always safe to\n\t * do this here, because if we reach this point, we must have already\n\t * loaded the tsl, so we no longer need to worry about its load order\n\t * relative to the other libraries.\n\t */\n\tts_license_enable_module_loading();\n}\n"
  },
  {
    "path": "tsl/src/nodes/CMakeLists.txt",
    "content": "set(SOURCES)\ntarget_sources(${TSL_LIBRARY_NAME} PRIVATE ${SOURCES})\nadd_subdirectory(columnar_index_scan)\nadd_subdirectory(columnar_scan)\nadd_subdirectory(gapfill)\nadd_subdirectory(skip_scan)\nadd_subdirectory(vector_agg)\n"
  },
  {
    "path": "tsl/src/nodes/README.md",
    "content": "# TimescaleDB Optimizations\n\nTimescaleDB has a number of optimizations to improve performance of\nquery execution.\n\n- [Skip scan](skip_scan/README.md) optimize queries involving `DISTINCT`\n- [Gapfill](gapfill/README.md) supports gapfilling time-series using LOCF and interpolation\n"
  },
  {
    "path": "tsl/src/nodes/columnar_index_scan/CMakeLists.txt",
    "content": "# Add all *.c to sources in upperlevel directory\nset(SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/columnar_index_scan.c\n            ${CMAKE_CURRENT_SOURCE_DIR}/columnar_index_scan_exec.c)\ntarget_sources(${TSL_LIBRARY_NAME} PRIVATE ${SOURCES})\n"
  },
  {
    "path": "tsl/src/nodes/columnar_index_scan/columnar_index_scan.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <access/sysattr.h>\n#include <nodes/bitmapset.h>\n#include <nodes/extensible.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodeFuncs.h>\n#include <nodes/plannodes.h>\n#include <optimizer/optimizer.h>\n#include <parser/parsetree.h>\n#include <utils/fmgroids.h>\n#include <utils/lsyscache.h>\n\n#include \"columnar_index_scan.h\"\n#include \"compression/create.h\"\n#include \"expression_utils.h\"\n#include \"func_cache.h\"\n#include \"guc.h\"\n#include \"nodes/columnar_scan/columnar_scan.h\"\n#include \"nodes/columnar_scan/planner.h\"\n#include \"ts_catalog/array_utils.h\"\n#include \"ts_catalog/compression_settings.h\"\n#include \"utils.h\"\n\nstatic CustomScanMethods columnar_index_scan_plan_methods = {\n\t.CustomName = COLUMNAR_INDEX_SCAN_NAME,\n\t.CreateCustomScanState = columnar_index_scan_state_create,\n};\n\nvoid\n_columnar_index_scan_init(void)\n{\n\tTryRegisterCustomScanMethods(&columnar_index_scan_plan_methods);\n}\n\n/*\n * Check if an aggregate function can use compressed chunk sparse index.\n *\n * Currently supported aggregates are min, max, first, and last.\n */\nstatic bool\nis_supported_aggregate(Aggref *aggref, Var **arg_var_out, const char **meta_type_out)\n{\n\tif (aggref->args == NIL)\n\t\treturn false;\n\n\t/* Get the argument - must be a Var (possibly with implicit coercions) */\n\tTargetEntry *arg_te = linitial_node(TargetEntry, aggref->args);\n\n\tNode *arg_expr = strip_implicit_coercions((Node *) arg_te->expr);\n\tif (!IsA(arg_expr, Var))\n\t\treturn false;\n\n\tVar *var = castNode(Var, arg_expr);\n\n\t/* Reject system columns except tableoid */\n\tif (var->varattno <= 0 && var->varattno != TableOidAttributeNumber)\n\t\treturn false;\n\n\tswitch (aggref->aggfnoid)\n\t{\n\t\tcase F_MIN_ANYARRAY:\n\t\tcase F_MIN_ANYENUM:\n\t\tcase F_MIN_BPCHAR:\n#if PG18_GE\n\t\tcase F_MIN_BYTEA:\n#endif\n\t\tcase F_MIN_DATE:\n\t\tcase F_MIN_FLOAT4:\n\t\tcase F_MIN_FLOAT8:\n\t\tcase F_MIN_INET:\n\t\tcase F_MIN_INT2:\n\t\tcase F_MIN_INT4:\n\t\tcase F_MIN_INT8:\n\t\tcase F_MIN_INTERVAL:\n\t\tcase F_MIN_MONEY:\n\t\tcase F_MIN_NUMERIC:\n\t\tcase F_MIN_OID:\n\t\tcase F_MIN_PG_LSN:\n#if PG18_GE\n\t\tcase F_MIN_RECORD:\n#endif\n\t\tcase F_MIN_TEXT:\n\t\tcase F_MIN_TID:\n\t\tcase F_MIN_TIME:\n\t\tcase F_MIN_TIMESTAMP:\n\t\tcase F_MIN_TIMESTAMPTZ:\n\t\tcase F_MIN_TIMETZ:\n\t\tcase F_MIN_XID8:\n\t\t\t*meta_type_out = \"min\";\n\t\t\t*arg_var_out = var;\n\t\t\treturn true;\n\t\tcase F_MAX_ANYARRAY:\n\t\tcase F_MAX_ANYENUM:\n\t\tcase F_MAX_BPCHAR:\n#if PG18_GE\n\t\tcase F_MAX_BYTEA:\n#endif\n\t\tcase F_MAX_DATE:\n\t\tcase F_MAX_FLOAT4:\n\t\tcase F_MAX_FLOAT8:\n\t\tcase F_MAX_INET:\n\t\tcase F_MAX_INT2:\n\t\tcase F_MAX_INT4:\n\t\tcase F_MAX_INT8:\n\t\tcase F_MAX_INTERVAL:\n\t\tcase F_MAX_MONEY:\n\t\tcase F_MAX_NUMERIC:\n\t\tcase F_MAX_OID:\n\t\tcase F_MAX_PG_LSN:\n#if PG18_GE\n\t\tcase F_MAX_RECORD:\n#endif\n\t\tcase F_MAX_TEXT:\n\t\tcase F_MAX_TID:\n\t\tcase F_MAX_TIME:\n\t\tcase F_MAX_TIMESTAMP:\n\t\tcase F_MAX_TIMESTAMPTZ:\n\t\tcase F_MAX_TIMETZ:\n\t\tcase F_MAX_XID8:\n\t\t\t*meta_type_out = \"max\";\n\t\t\t*arg_var_out = var;\n\t\t\treturn true;\n\t\tdefault:\n\t\t\t/* Check for first()/last() with both args referencing same column */\n\t\t\tif (!OidIsValid(ts_first_func_oid) || !OidIsValid(ts_last_func_oid))\n\t\t\t\tts_func_cache_init();\n\n\t\t\tif (aggref->aggfnoid == ts_first_func_oid || aggref->aggfnoid == ts_last_func_oid)\n\t\t\t{\n\t\t\t\tif (list_length(aggref->args) != 2)\n\t\t\t\t\treturn false;\n\t\t\t\tTargetEntry *tle2 = castNode(TargetEntry, lsecond(aggref->args));\n\t\t\t\tNode *arg2_expr = strip_implicit_coercions((Node *) tle2->expr);\n\t\t\t\tif (!equal(var, arg2_expr))\n\t\t\t\t\treturn false;\n\t\t\t\t*meta_type_out = (aggref->aggfnoid == ts_first_func_oid) ? \"min\" : \"max\";\n\t\t\t\t*arg_var_out = var;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tbreak;\n\t}\n\n\treturn false;\n}\n\n/*\n * Check if all quals reference only segmentby columns.\n *\n * This function is only relevant for partial chunks.  For fully compressed\n * chunks, segmentby quals are pushed to the compressed scan and removed from\n * plan->qual, so plan->qual is NIL and this function is never called.\n * For partial chunks, segmentby quals remain on plan->qual because they must\n * also be checked against the uncompressed portion, but they have already been\n * pushed to the compressed scan as well.  ColumnarIndexScan only reads\n * compressed metadata, so these quals are already handled.\n *\n * Uses the decompression_map and is_segmentby_column lists from the\n * ColumnarScan's custom_private to build a set of custom_scan_tlist\n * positions that are segmentby columns, then checks each qual's Vars\n * against that set.\n *\n * Returns false for quals containing no Vars (e.g. constified tableoid),\n * since those constant expressions are not handled by the compressed scan\n * and must still be evaluated.  In practice these only appear on partial\n * chunks from constraint exclusion checks (e.g. tableoid comparisons for\n * hypertable expansion) and should be uncommon.\n */\nstatic bool\nquals_only_reference_segmentby(List *quals, CustomScan *cscan)\n{\n\tList *decompression_map = list_nth(cscan->custom_private, DCP_DecompressionMap);\n\tList *is_segmentby = list_nth(cscan->custom_private, DCP_IsSegmentbyColumn);\n\n\tBitmapset *segmentby_positions = NULL;\n\tListCell *lc_map, *lc_seg;\n\tforboth (lc_map, decompression_map, lc_seg, is_segmentby)\n\t{\n\t\tint tlist_pos = lfirst_int(lc_map);\n\t\tif (lfirst_int(lc_seg) && tlist_pos > 0)\n\t\t\tsegmentby_positions = bms_add_member(segmentby_positions, tlist_pos);\n\t}\n\n\tbool result = true;\n\tListCell *lc;\n\tforeach (lc, quals)\n\t{\n\t\tList *vars = pull_var_clause(lfirst(lc), 0);\n\t\tif (vars == NIL)\n\t\t{\n\t\t\tresult = false;\n\t\t\tbreak;\n\t\t}\n\n\t\tListCell *lc2;\n\t\tforeach (lc2, vars)\n\t\t{\n\t\t\tVar *var = lfirst_node(Var, lc2);\n\t\t\tif (!bms_is_member(var->varattno, segmentby_positions))\n\t\t\t{\n\t\t\t\tresult = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!result)\n\t\t\tbreak;\n\t}\n\n\tbms_free(segmentby_positions);\n\treturn result;\n}\n\n/*\n * Check if the ColumnarScan's vectorized quals are empty (no filters applied).\n */\nstatic bool\ncolumnar_scan_has_no_vector_quals(CustomScan *cscan)\n{\n\t/*\n\t * The vectorized quals are stored in custom_exprs. If the list is empty\n\t * or the first element is NIL, there are no vectorized quals.\n\t */\n\tif (cscan->custom_exprs == NIL)\n\t\treturn true;\n\tif (linitial(cscan->custom_exprs) == NIL)\n\t\treturn true;\n\treturn false;\n}\n\n/*\n * Find the resno in the leaf plan's targetlist that corresponds to a given\n * compressed chunk attribute number.\n */\nstatic AttrNumber\nfind_resno_by_compressed_attno(Plan *leaf_plan, AttrNumber compressed_attno)\n{\n\tListCell *lc;\n\tforeach (lc, leaf_plan->targetlist)\n\t{\n\t\tTargetEntry *tle = lfirst_node(TargetEntry, lc);\n\t\tif (IsA(tle->expr, Var))\n\t\t{\n\t\t\tVar *var = castNode(Var, tle->expr);\n\t\t\tif (var->varattno == compressed_attno)\n\t\t\t\treturn tle->resno;\n\t\t}\n\t}\n\treturn InvalidAttrNumber;\n}\n\n/*\n * Context for validate_entries_walker.\n */\ntypedef struct ValidateContext\n{\n\tOid uncompressed_relid;\n\tOid compressed_relid;\n\tIndex uncompressed_scanrelid;\n\tIndex compressed_scanrelid;\n\tCompressionSettings *settings;\n\tPlan *leaf_plan;\n\tList *custom_scan_tlist;\n\tList *output_map;\n\tAttrNumber next_resno;\n} ValidateContext;\n\nstatic void\nadd_scan_output(ValidateContext *ctx, AttrNumber child_resno, Index tlist_varno,\n\t\t\t\tAttrNumber tlist_attno, Oid col_type, int32 col_typmod, Oid col_collid)\n{\n\tVar *tlist_var = makeVar(tlist_varno, tlist_attno, col_type, col_typmod, col_collid, 0);\n\tctx->custom_scan_tlist =\n\t\tlappend(ctx->custom_scan_tlist,\n\t\t\t\tmakeTargetEntry((Expr *) tlist_var, ctx->next_resno++, NULL, false));\n\tctx->output_map = lappend(ctx->output_map, makeInteger(child_resno));\n}\n\n/*\n * Expression tree walker that validates Var and Aggref nodes in the resolved\n * Agg targetlist. Builds custom_scan_tlist and output_map entries in DFS order.\n * Returns true (abort) if any node cannot be handled.\n *\n * Aggrefs are validated but NOT recursed into — their args reference the\n * child plan being replaced, so we only care about the aggregate itself.\n */\nstatic bool\nvalidate_entries_walker(Node *node, void *context)\n{\n\tValidateContext *ctx = (ValidateContext *) context;\n\n\tif (node == NULL)\n\t\treturn false;\n\n\tif (IsA(node, Var))\n\t{\n\t\tVar *var = (Var *) node;\n\n\t\tif (var->varattno == TableOidAttributeNumber)\n\t\t{\n\t\t\tadd_scan_output(ctx,\n\t\t\t\t\t\t\tTableOidAttributeNumber,\n\t\t\t\t\t\t\tctx->uncompressed_scanrelid,\n\t\t\t\t\t\t\tTableOidAttributeNumber,\n\t\t\t\t\t\t\tOIDOID,\n\t\t\t\t\t\t\t-1,\n\t\t\t\t\t\t\tInvalidOid);\n\t\t\treturn false;\n\t\t}\n\n\t\tif (var->varattno <= 0)\n\t\t\treturn true;\n\n\t\tchar *col_name = get_attname(ctx->uncompressed_relid, var->varattno, false);\n\n\t\tif (ctx->settings == NULL || !ts_array_is_member(ctx->settings->fd.segmentby, col_name))\n\t\t\treturn true;\n\n\t\tAttrNumber compressed_attno = get_attnum(ctx->compressed_relid, col_name);\n\t\tif (compressed_attno == InvalidAttrNumber)\n\t\t\treturn true;\n\n\t\tAttrNumber child_resno = find_resno_by_compressed_attno(ctx->leaf_plan, compressed_attno);\n\t\tif (child_resno == InvalidAttrNumber)\n\t\t\treturn true;\n\n\t\tadd_scan_output(ctx,\n\t\t\t\t\t\tchild_resno,\n\t\t\t\t\t\tctx->uncompressed_scanrelid,\n\t\t\t\t\t\tvar->varattno,\n\t\t\t\t\t\tvar->vartype,\n\t\t\t\t\t\tvar->vartypmod,\n\t\t\t\t\t\tvar->varcollid);\n\t\treturn false;\n\t}\n\n\tif (IsA(node, Aggref))\n\t{\n\t\tAggref *aggref = (Aggref *) node;\n\n\t\t/* No DISTINCT, ORDER BY, or FILTER on the Aggref */\n\t\tif (aggref->aggdistinct != NIL || aggref->aggorder != NIL || aggref->aggfilter != NULL)\n\t\t\treturn true;\n\n\t\tif (aggref->aggfnoid == F_COUNT_)\n\t\t{\n\t\t\tAttrNumber meta_count_attno =\n\t\t\t\tget_attnum(ctx->compressed_relid, COMPRESSION_COLUMN_METADATA_COUNT_NAME);\n\t\t\tif (meta_count_attno == InvalidAttrNumber)\n\t\t\t\treturn true;\n\n\t\t\tAttrNumber child_resno =\n\t\t\t\tfind_resno_by_compressed_attno(ctx->leaf_plan, meta_count_attno);\n\t\t\tif (child_resno == InvalidAttrNumber)\n\t\t\t\treturn true;\n\n\t\t\tadd_scan_output(ctx,\n\t\t\t\t\t\t\tchild_resno,\n\t\t\t\t\t\t\tctx->compressed_scanrelid,\n\t\t\t\t\t\t\tmeta_count_attno,\n\t\t\t\t\t\t\tINT4OID,\n\t\t\t\t\t\t\t-1,\n\t\t\t\t\t\t\tInvalidOid);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tVar *arg_var = NULL;\n\t\t\tconst char *meta_type = NULL;\n\t\t\tif (!is_supported_aggregate(aggref, &arg_var, &meta_type))\n\t\t\t\treturn true;\n\n\t\t\tif (arg_var->varattno == TableOidAttributeNumber)\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * tableoid is constant within a single chunk scan, so\n\t\t\t\t * min(tableoid) = max(tableoid) = tableoid.  Output the\n\t\t\t\t * tableoid sentinel and let the Agg reduce it.\n\t\t\t\t */\n\t\t\t\tadd_scan_output(ctx,\n\t\t\t\t\t\t\t\tTableOidAttributeNumber,\n\t\t\t\t\t\t\t\tctx->uncompressed_scanrelid,\n\t\t\t\t\t\t\t\tTableOidAttributeNumber,\n\t\t\t\t\t\t\t\tOIDOID,\n\t\t\t\t\t\t\t\t-1,\n\t\t\t\t\t\t\t\tInvalidOid);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tAttrNumber meta_attno = compressed_column_metadata_attno(ctx->settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t ctx->uncompressed_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t arg_var->varattno,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t ctx->compressed_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t meta_type);\n\t\t\t\tif (meta_attno == InvalidAttrNumber)\n\t\t\t\t\treturn true;\n\n\t\t\t\tAttrNumber child_resno = find_resno_by_compressed_attno(ctx->leaf_plan, meta_attno);\n\t\t\t\tif (child_resno == InvalidAttrNumber)\n\t\t\t\t\treturn true;\n\n\t\t\t\tadd_scan_output(ctx,\n\t\t\t\t\t\t\t\tchild_resno,\n\t\t\t\t\t\t\t\tctx->uncompressed_scanrelid,\n\t\t\t\t\t\t\t\targ_var->varattno,\n\t\t\t\t\t\t\t\tget_atttype(ctx->compressed_relid, meta_attno),\n\t\t\t\t\t\t\t\t-1,\n\t\t\t\t\t\t\t\targ_var->varcollid);\n\t\t\t}\n\t\t}\n\n\t\t/* Don't recurse into Aggref children */\n\t\treturn false;\n\t}\n\n\treturn expression_tree_walker(node, validate_entries_walker, context);\n}\n\n/*\n * Context for rewrite_agg_tlist_mutator.\n */\ntypedef struct RewriteContext\n{\n\tAgg *agg;\n\tList *custom_scan_tlist;\n\tAttrNumber next_resno;\n} RewriteContext;\n\n/*\n * Expression tree mutator that rewrites Var and Aggref nodes in the Agg's\n * targetlist to reference ColumnarIndexScan output columns. Type information\n * for rewritten aggregate arguments is read from the pre-built custom_scan_tlist.\n */\nstatic Node *\nrewrite_agg_tlist_mutator(Node *node, void *context)\n{\n\tRewriteContext *ctx = (RewriteContext *) context;\n\n\tif (node == NULL)\n\t\treturn NULL;\n\n\tif (IsA(node, Var) && ((Var *) node)->varno == OUTER_VAR)\n\t{\n\t\tVar *var = (Var *) node;\n\t\tAttrNumber resno = ctx->next_resno++;\n\n\t\t/* Update grpColIdx for GROUP BY Vars */\n\t\tfor (int k = 0; k < ctx->agg->numCols; k++)\n\t\t{\n\t\t\tif (ctx->agg->grpColIdx[k] == var->varattno)\n\t\t\t\tctx->agg->grpColIdx[k] = resno;\n\t\t}\n\n\t\treturn (Node *) makeVar(OUTER_VAR,\n\t\t\t\t\t\t\t\tresno,\n\t\t\t\t\t\t\t\tvar->vartype,\n\t\t\t\t\t\t\t\tvar->vartypmod,\n\t\t\t\t\t\t\t\tvar->varcollid,\n\t\t\t\t\t\t\t\tvar->varlevelsup);\n\t}\n\n\tif (IsA(node, Aggref))\n\t{\n\t\tAggref *orig = (Aggref *) node;\n\t\tAttrNumber resno = ctx->next_resno++;\n\n\t\tif (orig->aggfnoid == F_COUNT_)\n\t\t{\n\t\t\t/*\n\t\t\t * Rewrite count(*) → sum(_ts_meta_count).\n\t\t\t *\n\t\t\t * sum(int4) uses int4_sum as transition function with INT8\n\t\t\t * transition type, same as count(*)'s int8inc. Both use\n\t\t\t * int8pl as combine function, so the Finalize Agg above\n\t\t\t * (which still has the original count(*) aggfnoid) can\n\t\t\t * combine partial states from either without changes.\n\t\t\t *\n\t\t\t * For AGGSPLIT_SIMPLE (no Finalize), wrap in COALESCE to\n\t\t\t * preserve count(*)'s 0-for-empty semantics since sum()\n\t\t\t * returns NULL for no rows. For AGGSPLIT_INITIAL_SERIAL,\n\t\t\t * the Finalize's strict combine function (int8pl) and\n\t\t\t * count's initial value (0) handle NULL partial states.\n\t\t\t */\n\t\t\tAggref *aggref = copyObject(orig);\n\t\t\taggref->aggfnoid = F_SUM_INT4;\n\t\t\taggref->aggtranstype = INT8OID;\n\t\t\taggref->aggstar = false;\n\t\t\taggref->aggargtypes = list_make1_oid(INT4OID);\n\t\t\tVar *arg_var = makeVar(OUTER_VAR, resno, INT4OID, -1, InvalidOid, 0);\n\t\t\taggref->args = list_make1(makeTargetEntry((Expr *) arg_var, 1, NULL, false));\n\n\t\t\tif (ctx->agg->aggsplit == AGGSPLIT_SIMPLE)\n\t\t\t{\n\t\t\t\tCoalesceExpr *coalesce = makeNode(CoalesceExpr);\n\t\t\t\tcoalesce->coalescetype = aggref->aggtype;\n\t\t\t\tcoalesce->args = list_make2(aggref,\n\t\t\t\t\t\t\t\t\t\t\tmakeConst(INT8OID,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  -1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  InvalidOid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  sizeof(int64),\n\t\t\t\t\t\t\t\t\t\t\t\t\t  Int64GetDatum(0),\n\t\t\t\t\t\t\t\t\t\t\t\t\t  false,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  true));\n\t\t\t\tcoalesce->location = -1;\n\t\t\t\treturn (Node *) coalesce;\n\t\t\t}\n\n\t\t\treturn (Node *) aggref;\n\t\t}\n\n\t\t/* min/max/first/last: rewrite args to point to metadata column */\n\t\tAggref *aggref = copyObject(orig);\n\t\tTargetEntry *scan_tle = list_nth_node(TargetEntry, ctx->custom_scan_tlist, resno - 1);\n\t\tVar *scan_var = castNode(Var, scan_tle->expr);\n\t\tVar *new_arg = makeVar(OUTER_VAR,\n\t\t\t\t\t\t\t   resno,\n\t\t\t\t\t\t\t   scan_var->vartype,\n\t\t\t\t\t\t\t   scan_var->vartypmod,\n\t\t\t\t\t\t\t   scan_var->varcollid,\n\t\t\t\t\t\t\t   0);\n\t\tTargetEntry *new_arg_te = makeTargetEntry((Expr *) new_arg, 1, NULL, false);\n\n\t\tif (list_length(aggref->args) == 2)\n\t\t{\n\t\t\t/* first/last: both args point to same metadata column */\n\t\t\tVar *new_arg2 = copyObject(new_arg);\n\t\t\tTargetEntry *new_arg_te2 = makeTargetEntry((Expr *) new_arg2, 2, NULL, false);\n\t\t\taggref->args = list_make2(new_arg_te, new_arg_te2);\n\t\t}\n\t\telse\n\t\t{\n\t\t\taggref->args = list_make1(new_arg_te);\n\t\t}\n\n\t\treturn (Node *) aggref;\n\t}\n\n\treturn expression_tree_mutator(node, rewrite_agg_tlist_mutator, context);\n}\n\n/*\n * Create a ColumnarIndexScan plan node as a pure scan node that replaces the ColumnarScan\n * beneath the Agg. The Agg stays in the plan with rewritten Aggrefs:\n *   count(*)        → sum(_ts_meta_count)\n *   min(col)/max(col) → same aggfnoid, arg rewritten to metadata column\n *   first(col,col)/last(col,col) → same aggfnoid, both args rewritten\n *\n * Expressions that combine aggregates (e.g. 2*count(*), min(x)+max(y)) are\n * supported — the walker/mutator handles Var and Aggref nodes at any depth.\n */\nstatic Plan *\ncolumnar_index_scan_plan_create(Agg *agg, CustomScan *cscan, List *rtable)\n{\n\tPlan *compressed_scan_subtree = linitial(cscan->custom_plans);\n\n\tPlan *leaf_plan = compressed_scan_subtree;\n\tif (IsA(leaf_plan, Sort))\n\t\tleaf_plan = leaf_plan->lefttree;\n\n\tScan *compressed_scan = (Scan *) leaf_plan;\n\tRangeTblEntry *compressed_rte = rt_fetch(compressed_scan->scanrelid, rtable);\n\tOid compressed_relid = compressed_rte->relid;\n\n\tOid uncompressed_relid = rt_fetch(cscan->scan.scanrelid, rtable)->relid;\n\tCompressionSettings *settings = ts_compression_settings_get(uncompressed_relid);\n\n\t/*\n\t * Validation pass: walk the resolved targetlist to validate all Var and\n\t * Aggref leaf nodes. Builds custom_scan_tlist and output_map directly.\n\t * No modifications to the Agg happen here, so bailing out is safe.\n\t */\n\tValidateContext validate_ctx = {\n\t\t.uncompressed_relid = uncompressed_relid,\n\t\t.compressed_relid = compressed_relid,\n\t\t.uncompressed_scanrelid = cscan->scan.scanrelid,\n\t\t.compressed_scanrelid = compressed_scan->scanrelid,\n\t\t.settings = settings,\n\t\t.leaf_plan = leaf_plan,\n\t\t.custom_scan_tlist = NIL,\n\t\t.output_map = NIL,\n\t\t.next_resno = 1,\n\t};\n\n\tPlan *childplan = agg->plan.lefttree;\n\tNode *resolved_targetlist =\n\t\tts_resolve_outer_special_vars((Node *) agg->plan.targetlist, childplan);\n\tif (validate_entries_walker(resolved_targetlist, &validate_ctx))\n\t\treturn NULL;\n\n\tif (agg->plan.qual)\n\t{\n\t\tNode *resolved_qual = ts_resolve_outer_special_vars((Node *) agg->plan.qual, childplan);\n\t\tif (validate_entries_walker(resolved_qual, &validate_ctx))\n\t\t\treturn NULL;\n\t}\n\n\tList *custom_scan_tlist = validate_ctx.custom_scan_tlist;\n\tList *output_map = validate_ctx.output_map;\n\n\tif (custom_scan_tlist == NIL)\n\t\treturn NULL;\n\n\t/*\n\t * Rewrite pass: walk the Agg's targetlist with a mutator that rewrites\n\t * Var and Aggref nodes to reference ColumnarIndexScan output columns.\n\t */\n\tRewriteContext rewrite_ctx = {\n\t\t.agg = agg,\n\t\t.custom_scan_tlist = custom_scan_tlist,\n\t\t.next_resno = 1,\n\t};\n\n\tagg->plan.targetlist =\n\t\tcastNode(List, rewrite_agg_tlist_mutator((Node *) agg->plan.targetlist, &rewrite_ctx));\n\n\tif (agg->plan.qual)\n\t\tagg->plan.qual =\n\t\t\tcastNode(List, rewrite_agg_tlist_mutator((Node *) agg->plan.qual, &rewrite_ctx));\n\n\t/* Build ColumnarIndexScan CustomScan */\n\tCustomScan *columnar_index_scan = (CustomScan *) makeNode(CustomScan);\n\tcolumnar_index_scan->custom_plans = list_make1(compressed_scan_subtree);\n\tcolumnar_index_scan->methods = &columnar_index_scan_plan_methods;\n\n\tcolumnar_index_scan->scan.scanrelid = cscan->scan.scanrelid;\n\n\tcolumnar_index_scan->custom_scan_tlist = custom_scan_tlist;\n\tcolumnar_index_scan->scan.plan.targetlist =\n\t\tts_build_trivial_custom_output_targetlist(custom_scan_tlist);\n\n\t/* Copy cost/parallel/param fields from the ColumnarScan */\n\tcolumnar_index_scan->scan.plan.plan_rows = cscan->scan.plan.plan_rows;\n\tcolumnar_index_scan->scan.plan.plan_width = cscan->scan.plan.plan_width;\n\tcolumnar_index_scan->scan.plan.startup_cost = cscan->scan.plan.startup_cost;\n\tcolumnar_index_scan->scan.plan.total_cost = cscan->scan.plan.total_cost;\n\n\tcolumnar_index_scan->scan.plan.parallel_aware = false;\n\tcolumnar_index_scan->scan.plan.parallel_safe = cscan->scan.plan.parallel_safe;\n\tcolumnar_index_scan->scan.plan.async_capable = false;\n\n\tcolumnar_index_scan->scan.plan.plan_node_id = cscan->scan.plan.plan_node_id;\n\n\tcolumnar_index_scan->scan.plan.initPlan = cscan->scan.plan.initPlan;\n\tcolumnar_index_scan->scan.plan.extParam = bms_copy(cscan->scan.plan.extParam);\n\tcolumnar_index_scan->scan.plan.allParam = bms_copy(cscan->scan.plan.allParam);\n\n\tcolumnar_index_scan->custom_private = list_make1(output_map);\n\n\t/* Set ColumnarIndexScan as the Agg's child */\n\tagg->plan.lefttree = (Plan *) columnar_index_scan;\n\n\treturn (Plan *) agg;\n}\n\n/*\n * Callback for ts_plan_tree_walker: try to insert a ColumnarIndexScan node between an Agg\n * and its ColumnarScan child.\n */\nstatic Plan *\ninsert_columnar_index_scan(Plan *plan, void *context)\n{\n\tList *rtable = (List *) context;\n\n\tif (plan->type != T_Agg)\n\t\treturn plan;\n\n\tAgg *agg = castNode(Agg, plan);\n\n\t/*\n\t * We are looking for the partial step (AGGSPLIT_INITIAL_SERIAL) of\n\t * partial/finalize aggregate or non-partial aggregates (AGGSPLIT_SIMPLE).\n\t */\n\tif (agg->aggsplit != AGGSPLIT_INITIAL_SERIAL && agg->aggsplit != AGGSPLIT_SIMPLE)\n\t\treturn plan;\n\n\tPlan *childplan = agg->plan.lefttree;\n\n\t/*\n\t * The child must be a ColumnarScan.\n\t */\n\tif (!ts_is_columnar_scan_plan(childplan))\n\t\treturn plan;\n\n\tCustomScan *cscan = castNode(CustomScan, childplan);\n\n\t/*\n\t * No Postgres quals on the ColumnarScan, or quals only on segmentby\n\t * columns. Segmentby filters are pushed to the compressed scan so\n\t * ColumnarIndexScan can skip them.\n\t */\n\tif (childplan->qual != NIL && !quals_only_reference_segmentby(childplan->qual, cscan))\n\t\treturn plan;\n\n\t/*\n\t * No vectorized quals on the ColumnarScan.\n\t */\n\tif (!columnar_scan_has_no_vector_quals(cscan))\n\t\treturn plan;\n\n\tPlan *result = columnar_index_scan_plan_create(agg, cscan, rtable);\n\tif (result == NULL)\n\t\treturn plan;\n\n\treturn result;\n}\n\n/*\n * Where possible, replace Agg -> ColumnarScan with Agg -> ColumnarIndexScan.\n * The Agg stays in the plan with rewritten Aggrefs; ColumnarIndexScan replaces\n * the ColumnarScan as a pure scan node over compressed metadata.\n */\nPlan *\ntry_insert_columnar_index_scan_node(Plan *plan, List *rtable)\n{\n\treturn ts_plan_tree_walker(plan, insert_columnar_index_scan, rtable);\n}\n"
  },
  {
    "path": "tsl/src/nodes/columnar_index_scan/columnar_index_scan.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#define COLUMNAR_INDEX_SCAN_NAME \"ColumnarIndexScan\"\n\n#include <postgres.h>\n#include <nodes/plannodes.h>\n\nextern void _columnar_index_scan_init(void);\nextern Plan *try_insert_columnar_index_scan_node(Plan *plan, List *rtable);\nextern Node *columnar_index_scan_state_create(CustomScan *cscan);\n"
  },
  {
    "path": "tsl/src/nodes/columnar_index_scan/columnar_index_scan_exec.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n\n#include <access/sysattr.h>\n#include <executor/executor.h>\n#include <nodes/extensible.h>\n#include <nodes/plannodes.h>\n#include <utils/rel.h>\n\n#include \"columnar_index_scan.h\"\n\ntypedef struct ColumnarIndexScanState\n{\n\tCustomScanState custom;\n\tint num_outputs;\n\tAttrNumber *child_resnos; /* one per output column */\n} ColumnarIndexScanState;\n\nstatic void\ncolumnar_index_scan_begin(CustomScanState *node, EState *estate, int eflags)\n{\n\tColumnarIndexScanState *state = (ColumnarIndexScanState *) node;\n\tCustomScan *cscan = castNode(CustomScan, node->ss.ps.plan);\n\n\t/*\n\t * Parse output_map from custom_private: a flat list of Integer values,\n\t * one child_resno per output column.\n\t */\n\tList *output_map = linitial(cscan->custom_private);\n\tint num_outputs = list_length(output_map);\n\tstate->num_outputs = num_outputs;\n\tstate->child_resnos = palloc(sizeof(AttrNumber) * num_outputs);\n\n\tListCell *lc;\n\tint i = 0;\n\tforeach (lc, output_map)\n\t{\n\t\tstate->child_resnos[i++] = intVal(lfirst(lc));\n\t}\n\n\tAssert(list_length(cscan->custom_plans) == 1);\n\tnode->custom_ps = list_make1(ExecInitNode(linitial(cscan->custom_plans), estate, eflags));\n}\n\nstatic TupleTableSlot *\ncolumnar_index_scan_exec(CustomScanState *node)\n{\n\tColumnarIndexScanState *state = (ColumnarIndexScanState *) node;\n\tPlanState *child_ps = linitial(node->custom_ps);\n\tTupleTableSlot *result_slot = node->ss.ps.ps_ResultTupleSlot;\n\tExecClearTuple(result_slot);\n\n\tTupleTableSlot *child_slot = ExecProcNode(child_ps);\n\tif (TupIsNull(child_slot))\n\t\treturn NULL;\n\n\t/*\n\t * Copy values from the child slot to the output slot using the output_map.\n\t * Each output column i gets its value from child_resnos[i].\n\t */\n\tDatum *values = result_slot->tts_values;\n\tbool *nulls = result_slot->tts_isnull;\n\n\tfor (int i = 0; i < state->num_outputs; i++)\n\t{\n\t\tif (state->child_resnos[i] == TableOidAttributeNumber)\n\t\t{\n\t\t\tvalues[i] = ObjectIdGetDatum(node->ss.ss_currentRelation->rd_id);\n\t\t\tnulls[i] = false;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tvalues[i] = slot_getattr(child_slot, state->child_resnos[i], &nulls[i]);\n\t\t}\n\t}\n\n\treturn ExecStoreVirtualTuple(result_slot);\n}\n\nstatic void\ncolumnar_index_scan_end(CustomScanState *node)\n{\n\tExecEndNode(linitial(node->custom_ps));\n}\n\nstatic void\ncolumnar_index_scan_rescan(CustomScanState *node)\n{\n\tExecReScan(linitial(node->custom_ps));\n}\n\nstatic struct CustomExecMethods exec_methods = {\n\t.CustomName = COLUMNAR_INDEX_SCAN_NAME,\n\t.BeginCustomScan = columnar_index_scan_begin,\n\t.ExecCustomScan = columnar_index_scan_exec,\n\t.EndCustomScan = columnar_index_scan_end,\n\t.ReScanCustomScan = columnar_index_scan_rescan,\n\t.ExplainCustomScan = NULL,\n};\n\nNode *\ncolumnar_index_scan_state_create(CustomScan *cscan)\n{\n\tColumnarIndexScanState *state =\n\t\t(ColumnarIndexScanState *) newNode(sizeof(ColumnarIndexScanState), T_CustomScanState);\n\tstate->custom.methods = &exec_methods;\n\treturn (Node *) state;\n}\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/CMakeLists.txt",
    "content": "# Add all *.c to sources in upperlevel directory\nset(SOURCES\n    ${CMAKE_CURRENT_SOURCE_DIR}/batch_array.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/batch_queue_heap.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/batch_queue_fifo.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/compressed_batch.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/columnar_scan.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/detoaster.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/exec.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/planner.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/pred_text.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/pred_vector_array.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/qual_pushdown.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/vector_predicates.c)\ntarget_sources(${TSL_LIBRARY_NAME} PRIVATE ${SOURCES})\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/batch_array.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include \"nodes/columnar_scan/batch_array.h\"\n#include \"compression/compression.h\"\n#include \"nodes/columnar_scan/compressed_batch.h\"\n/*\n * Create states to hold information for up to n batches.\n */\nvoid\nbatch_array_init(BatchArray *array, int nbatches, int ncolumns_per_batch)\n{\n\tAssert(nbatches >= 0);\n\n\tarray->n_batch_states = nbatches;\n\tarray->n_columns_per_batch = ncolumns_per_batch;\n\tarray->unused_batch_states = bms_add_range(NULL, 0, nbatches - 1);\n\tarray->n_batch_state_bytes =\n\t\tsizeof(DecompressBatchState) + sizeof(CompressedColumnValues) * ncolumns_per_batch;\n\tarray->batch_states = palloc0(array->n_batch_state_bytes * nbatches);\n\tAssert(bms_num_members(array->unused_batch_states) == array->n_batch_states);\n}\n\n/*\n * Destroy batch states.\n */\nvoid\nbatch_array_destroy(BatchArray *array)\n{\n\tfor (int i = 0; i < array->n_batch_states; i++)\n\t{\n\t\tDecompressBatchState *batch_state = batch_array_get_at(array, i);\n\t\tcompressed_batch_destroy(batch_state);\n\t}\n\n\tpfree(array->batch_states);\n\tarray->batch_states = NULL;\n}\n\n/*\n * Enhance the capacity of existing batch states.\n */\nstatic void\nbatch_array_enlarge(BatchArray *array, int new_number)\n{\n\tAssert(new_number > array->n_batch_states);\n\n\t/* Request additional memory */\n\tarray->batch_states = repalloc(array->batch_states, array->n_batch_state_bytes * new_number);\n\n\t/* Zero out the tail. The batch states are initialized on first use. */\n\tmemset(((char *) array->batch_states) + (array->n_batch_state_bytes * array->n_batch_states),\n\t\t   0x0,\n\t\t   array->n_batch_state_bytes * (new_number - array->n_batch_states));\n\n\t/* Register the new states as unused */\n\tarray->unused_batch_states =\n\t\tbms_add_range(array->unused_batch_states, array->n_batch_states, new_number - 1);\n\n\tAssert(bms_num_members(array->unused_batch_states) == new_number - array->n_batch_states);\n\n\t/* Update number of available batch states */\n\tarray->n_batch_states = new_number;\n}\n\n/*\n * Mark a DecompressBatchState as unused\n */\nvoid\nbatch_array_clear_at(BatchArray *array, int batch_index)\n{\n\tAssert(batch_index >= 0);\n\tAssert(batch_index < array->n_batch_states);\n\n\tDecompressBatchState *batch_state = batch_array_get_at(array, batch_index);\n\n\t/* Reset batch state */\n\tcompressed_batch_discard_tuples(batch_state);\n\n\tarray->unused_batch_states = bms_add_member(array->unused_batch_states, batch_index);\n}\n\nvoid\nbatch_array_clear_all(BatchArray *array)\n{\n\tfor (int i = 0; i < array->n_batch_states; i++)\n\t\tbatch_array_clear_at(array, i);\n\n\tAssert(bms_num_members(array->unused_batch_states) == array->n_batch_states);\n}\n\n/*\n * Get the next free and unused batch state and mark as used\n */\nint\nbatch_array_get_unused_slot(BatchArray *array)\n{\n\tif (bms_is_empty(array->unused_batch_states))\n\t\tbatch_array_enlarge(array, array->n_batch_states * 2);\n\n\tAssert(!bms_is_empty(array->unused_batch_states));\n\n\tint next_unused_batch = bms_next_member(array->unused_batch_states, -1);\n\n\tAssert(next_unused_batch >= 0);\n\tAssert(next_unused_batch < array->n_batch_states);\n\n\tAssert(TupIsNull(compressed_batch_current_tuple(batch_array_get_at(array, next_unused_batch))));\n\n\tarray->unused_batch_states = bms_del_member(array->unused_batch_states, next_unused_batch);\n\n\treturn next_unused_batch;\n}\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/batch_array.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <nodes/bitmapset.h>\n#include <stdbool.h>\n\n/* The value for an invalid batch id */\n#define INVALID_BATCH_ID -1\n\ntypedef struct BatchArray\n{\n\t/* Batch states */\n\tint n_batch_states; /* Number of batch states */\n\t/*\n\t * The batch states. It's void* because they have a variable length\n\t * column array, so normal indexing can't be used. Use the batch_array_get_at\n\t * accessor instead.\n\t */\n\tvoid *batch_states;\n\tint n_batch_state_bytes;\n\tint n_columns_per_batch;\n\tBitmapset *unused_batch_states; /* The unused batch states */\n} BatchArray;\n/*\n * Create states to hold information for up to n batches\n */\nvoid batch_array_init(BatchArray *array, int nbatches, int ncolumns_per_batch);\n\nvoid batch_array_destroy(BatchArray *array);\n\nextern int batch_array_get_unused_slot(BatchArray *array);\n\ninline static struct DecompressBatchState *\nbatch_array_get_at(const BatchArray *array, int batch_index)\n{\n\t/*\n\t * Since we're accessing batch states through a \"char\" pointer, use\n\t * \"restrict\" to tell the compiler that it doesn't alias with anything.\n\t * Might be important in hot loops.\n\t */\n\treturn (struct DecompressBatchState *) ((char *restrict) array->batch_states +\n\t\t\t\t\t\t\t\t\t\t\tarray->n_batch_state_bytes * batch_index);\n}\n\nextern void batch_array_clear_at(BatchArray *array, int batch_index);\nextern void batch_array_clear_all(BatchArray *array);\n\ninline static bool\nbatch_array_has_active_batches(const BatchArray *array)\n{\n\treturn bms_num_members(array->unused_batch_states) != array->n_batch_states;\n}\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/batch_queue.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#ifndef TIMESCALEDB_BATCH_QUEUE_H\n#define TIMESCALEDB_BATCH_QUEUE_H\n\n#include <postgres.h>\n#include <executor/tuptable.h>\n\n#include \"decompress_context.h\"\n\n/* Initial amount of batch states */\n#define INITIAL_BATCH_CAPACITY 16\n\nstruct BatchQueue;\n\ntypedef struct BatchQueueFunctions\n{\n\tvoid (*free)(struct BatchQueue *);\n\tbool (*needs_next_batch)(struct BatchQueue *);\n\tvoid (*pop)(struct BatchQueue *, DecompressContext *);\n\tvoid (*push_batch)(struct BatchQueue *, DecompressContext *, TupleTableSlot *);\n\tvoid (*reset)(struct BatchQueue *);\n\tTupleTableSlot *(*top_tuple)(struct BatchQueue *);\n} BatchQueueFunctions;\n\ntypedef struct BatchQueue\n{\n\tBatchArray batch_array;\n\tconst BatchQueueFunctions *funcs;\n} BatchQueue;\n\n#include \"batch_queue_fifo.h\"\n#include \"batch_queue_heap.h\"\n\n#endif /* TIMESCALEDB_BATCH_QUEUE_H */\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/batch_queue_fifo.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n#include \"batch_queue_fifo.h\"\n#include \"batch_array.h\"\n\n/*\n * Create a FIFO batch queue.\n *\n * Pass the function struct as argument to ensure it is a pointer to the\n * struct that is inlined at the place this function is called. This is mostly\n * to be able to later Assert() that the struct pointer is pointing to the\n * expected function struct.\n */\nBatchQueue *\nbatch_queue_fifo_create(int num_compressed_cols, const BatchQueueFunctions *funcs)\n{\n\tBatchQueue *bq = palloc0(sizeof(BatchQueue));\n\n\tbatch_array_init(&bq->batch_array, INITIAL_BATCH_CAPACITY, num_compressed_cols);\n\tbq->funcs = funcs;\n\n\treturn bq;\n}\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/batch_queue_fifo.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include \"batch_queue.h\"\n#include \"compressed_batch.h\"\n\nstatic inline void\nbatch_queue_fifo_free(BatchQueue *bq)\n{\n\tbatch_array_destroy(&bq->batch_array);\n\tpfree(bq);\n}\n\nstatic inline bool\nbatch_queue_fifo_needs_next_batch(BatchQueue *bq)\n{\n\treturn TupIsNull(compressed_batch_current_tuple(batch_array_get_at(&bq->batch_array, 0)));\n}\n\nstatic inline void\nbatch_queue_fifo_pop(BatchQueue *bq, DecompressContext *dcontext)\n{\n\tDecompressBatchState *batch_state = batch_array_get_at(&bq->batch_array, 0);\n\tif (TupIsNull(compressed_batch_current_tuple(batch_state)))\n\t{\n\t\t/* Allow this function to be called on the initial empty queue. */\n\t\treturn;\n\t}\n\n\tcompressed_batch_advance(dcontext, batch_state);\n}\n\nstatic inline void\nbatch_queue_fifo_push_batch(BatchQueue *bq, DecompressContext *dcontext,\n\t\t\t\t\t\t\tTupleTableSlot *compressed_slot)\n{\n\tBatchArray *batch_array = &bq->batch_array;\n\tDecompressBatchState *batch_state = batch_array_get_at(batch_array, 0);\n\tAssert(TupIsNull(compressed_batch_current_tuple(batch_array_get_at(batch_array, 0))));\n\tcompressed_batch_set_compressed_tuple(dcontext, batch_state, compressed_slot);\n\tcompressed_batch_advance(dcontext, batch_state);\n}\n\nstatic inline void\nbatch_queue_fifo_reset(BatchQueue *bq)\n{\n\tbatch_array_clear_all(&bq->batch_array);\n}\n\nstatic inline TupleTableSlot *\nbatch_queue_fifo_top_tuple(BatchQueue *bq)\n{\n\treturn compressed_batch_current_tuple(batch_array_get_at(&bq->batch_array, 0));\n}\n\nstatic const struct BatchQueueFunctions BatchQueueFunctionsFifo = {\n\t.free = batch_queue_fifo_free,\n\t.needs_next_batch = batch_queue_fifo_needs_next_batch,\n\t.pop = batch_queue_fifo_pop,\n\t.push_batch = batch_queue_fifo_push_batch,\n\t.reset = batch_queue_fifo_reset,\n\t.top_tuple = batch_queue_fifo_top_tuple,\n};\n\nextern BatchQueue *batch_queue_fifo_create(int num_compressed_cols,\n\t\t\t\t\t\t\t\t\t\t   const BatchQueueFunctions *funcs);\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/batch_queue_heap.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n#include <lib/binaryheap.h>\n#include <nodes/bitmapset.h>\n\n#include \"compression/compression.h\"\n#include \"nodes/columnar_scan/batch_array.h\"\n#include \"nodes/columnar_scan/batch_queue.h\"\n#include \"nodes/columnar_scan/compressed_batch.h\"\n\ntypedef struct\n{\n\tDatum value;\n\tbool null;\n} HeapEntryColumn;\n\ntypedef struct BatchQueueHeap\n{\n\tBatchQueue queue;\n\tbinaryheap *merge_heap; /* Binary heap of slot indices */\n\n\t/*\n\t * Requested sort order of the heap.\n\t */\n\tint nkeys;\n\tSortSupport sortkeys;\n\n\t/*\n\t * This is the actual entries of the heap we're going to compare. We're using\n\t * these minimal structures for better memory locality instead of addressing\n\t * the entire compressed batches. Would be even better to put them into the\n\t * heap inline, but unfortunately the Postgres binary heap doesn't support\n\t * this.\n\t *\n\t * For each batch, we have nkeys of HeapEntryColumn values, which contain\n\t * the latest decompressed values.\n\t */\n\tHeapEntryColumn *heap_entries;\n\n\t/*\n\t * We use this to check when we have to ask for the next input batch.\n\t */\n\tTupleTableSlot *last_batch_first_tuple_slot;\n\tHeapEntryColumn *last_batch_first_tuple_entry;\n} BatchQueueHeap;\n\n/*\n * Compare heap entries for two batches. This function is used for comparing the\n * first tuple of the last batch to the current top tuple, so it is only called\n * once per input tuple, and optimized specializations for it are less important.\n */\nstatic int32\ncompare_entries(HeapEntryColumn *entryA, HeapEntryColumn *entryB, const SortSupport sortkeys,\n\t\t\t\tint nkeys)\n{\n\tfor (int key = 0; key < nkeys; key++)\n\t{\n\t\tint compare = ApplySortComparator(entryA[key].value,\n\t\t\t\t\t\t\t\t\t\t  entryA[key].null,\n\t\t\t\t\t\t\t\t\t\t  entryB[key].value,\n\t\t\t\t\t\t\t\t\t\t  entryB[key].null,\n\t\t\t\t\t\t\t\t\t\t  &sortkeys[key]);\n\n\t\tif (compare != 0)\n\t\t{\n\t\t\tINVERT_COMPARE_RESULT(compare);\n\t\t\treturn compare;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n/*\n * Compare top tuples of two given batch array slots. We support specializations\n * for comparison of the first tuple, like tuplesort.\n */\nstatic pg_attribute_always_inline int32\ncompare_heap_pos_impl(Datum a, Datum b, void *arg,\n\t\t\t\t\t  int32 (*apply_first_datum_comparator)(Datum, bool, Datum, bool, SortSupport))\n{\n\tBatchQueueHeap *queue = (BatchQueueHeap *) arg;\n\tPG_USED_FOR_ASSERTS_ONLY BatchArray *batch_array = &queue->queue.batch_array;\n\tint batchA = DatumGetInt32(a);\n\tAssert(batchA <= batch_array->n_batch_states);\n\n\tint batchB = DatumGetInt32(b);\n\tAssert(batchB <= batch_array->n_batch_states);\n\n\tconst int nkeys = queue->nkeys;\n\tSortSupport sortkeys = queue->sortkeys;\n\n\tHeapEntryColumn *entryA = &queue->heap_entries[batchA * nkeys];\n\tHeapEntryColumn *entryB = &queue->heap_entries[batchB * nkeys];\n\n\tint compare = apply_first_datum_comparator(entryA[0].value,\n\t\t\t\t\t\t\t\t\t\t\t   entryA[0].null,\n\t\t\t\t\t\t\t\t\t\t\t   entryB[0].value,\n\t\t\t\t\t\t\t\t\t\t\t   entryB[0].null,\n\t\t\t\t\t\t\t\t\t\t\t   &sortkeys[0]);\n\tif (compare != 0)\n\t{\n\t\tINVERT_COMPARE_RESULT(compare);\n\t\treturn compare;\n\t}\n\n\tfor (int key = 1; key < nkeys; key++)\n\t{\n\t\tint compare = ApplySortComparator(entryA[key].value,\n\t\t\t\t\t\t\t\t\t\t  entryA[key].null,\n\t\t\t\t\t\t\t\t\t\t  entryB[key].value,\n\t\t\t\t\t\t\t\t\t\t  entryB[key].null,\n\t\t\t\t\t\t\t\t\t\t  &sortkeys[key]);\n\n\t\tif (compare != 0)\n\t\t{\n\t\t\tINVERT_COMPARE_RESULT(compare);\n\t\t\treturn compare;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int32\ncompare_heap_pos_generic(Datum a, Datum b, void *arg)\n{\n\treturn compare_heap_pos_impl(a, b, arg, ApplySortComparator);\n}\n\nstatic int32\ncompare_heap_pos_int32(Datum a, Datum b, void *arg)\n{\n\treturn compare_heap_pos_impl(a, b, arg, ApplyInt32SortComparator);\n}\n\n#if SIZEOF_DATUM >= 8\nstatic int32\ncompare_heap_pos_signed(Datum a, Datum b, void *arg)\n{\n\treturn compare_heap_pos_impl(a, b, arg, ApplySignedSortComparator);\n}\n#endif\n\n/* Add a new datum to the heap and perform an automatic resizing if needed. In contrast to\n * the binaryheap_add_unordered() function, the capacity of the heap is automatically\n * increased if needed.\n */\nstatic pg_nodiscard binaryheap *\nbinaryheap_add_unordered_autoresize(binaryheap *heap, Datum d)\n{\n\t/* Resize heap if needed */\n\tif (heap->bh_size >= heap->bh_space)\n\t{\n\t\theap->bh_space = heap->bh_space * 2;\n\t\tSize new_size = offsetof(binaryheap, bh_nodes) + (sizeof(Datum) * heap->bh_space);\n\t\theap = (binaryheap *) repalloc(heap, new_size);\n\t}\n\n\t/* Insert new element */\n\tbinaryheap_add(heap, d);\n\n\treturn heap;\n}\n\nstatic void\nbatch_queue_heap_pop(BatchQueue *bq, DecompressContext *dcontext)\n{\n\tBatchQueueHeap *queue = (BatchQueueHeap *) bq;\n\tBatchArray *batch_array = &bq->batch_array;\n\n\tif (binaryheap_empty(queue->merge_heap))\n\t{\n\t\t/* Allow this function to be called on the initial empty heap. */\n\t\treturn;\n\t}\n\n\tconst int top_batch_index = DatumGetInt32(binaryheap_first(queue->merge_heap));\n\tDecompressBatchState *top_batch = batch_array_get_at(batch_array, top_batch_index);\n\n\tcompressed_batch_advance(dcontext, top_batch);\n\n\tTupleTableSlot *top_tuple = compressed_batch_current_tuple(top_batch);\n\tif (TupIsNull(top_tuple))\n\t{\n\t\t/* Batch is exhausted, recycle batch_state */\n\t\t(void) binaryheap_remove_first(queue->merge_heap);\n\t\tbatch_array_clear_at(batch_array, top_batch_index);\n\t}\n\telse\n\t{\n\t\t/*\n\t\t * Update the heap entries for this batch with the current decompressed\n\t\t * tuple values.\n\t\t */\n\t\tfor (int key = 0; key < queue->nkeys; key++)\n\t\t{\n\t\t\tSortSupport sortKey = &queue->sortkeys[key];\n\t\t\tconst AttrNumber attr = AttrNumberGetAttrOffset(sortKey->ssup_attno);\n\t\t\t/*\n\t\t\t * We're working with virtual tuple slots so no need for slot_getattr().\n\t\t\t */\n\t\t\tAssert(TTS_IS_VIRTUAL(top_tuple));\n\t\t\tqueue->heap_entries[(top_batch_index * queue->nkeys) + key].value =\n\t\t\t\ttop_tuple->tts_values[attr];\n\t\t\tqueue->heap_entries[(top_batch_index * queue->nkeys) + key].null =\n\t\t\t\ttop_tuple->tts_isnull[attr];\n\t\t}\n\n\t\t/* Place this batch on the heap according to its new decompressed tuple. */\n\t\tbinaryheap_replace_first(queue->merge_heap, Int32GetDatum(top_batch_index));\n\t}\n}\n\nstatic bool\nbatch_queue_heap_needs_next_batch(BatchQueue *_queue)\n{\n\tBatchQueueHeap *queue = (BatchQueueHeap *) _queue;\n\n\tif (binaryheap_empty(queue->merge_heap))\n\t{\n\t\treturn true;\n\t}\n\n\tconst int top_batch_index = DatumGetInt32(binaryheap_first(queue->merge_heap));\n\tconst int comparison_result =\n\t\tcompare_entries(&queue->heap_entries[queue->nkeys * top_batch_index],\n\t\t\t\t\t\tqueue->last_batch_first_tuple_entry,\n\t\t\t\t\t\tqueue->sortkeys,\n\t\t\t\t\t\tqueue->nkeys);\n\n\t/*\n\t * The invariant we have to preserve is that either:\n\t * 1) the current top tuple sorts before the first tuple of the last\n\t *    added batch,\n\t * 2) the input has ended.\n\t * Since the incoming batches arrive in the order of their first tuple,\n\t * if this invariant holds, then the current top tuple is found inside the\n\t * heap.\n\t * If it doesn't hold, the top tuple might be in the next incoming batches,\n\t * and we have to continue adding them.\n\t */\n\treturn comparison_result <= 0;\n}\n\nstatic void\nbatch_queue_heap_push_batch(BatchQueue *_queue, DecompressContext *dcontext,\n\t\t\t\t\t\t\tTupleTableSlot *compressed_slot)\n{\n\tBatchQueueHeap *queue = (BatchQueueHeap *) _queue;\n\tBatchArray *batch_array = &queue->queue.batch_array;\n\n\tAssert(!TupIsNull(compressed_slot));\n\n\tconst int old_size = batch_array->n_batch_states;\n\tconst int new_batch_index = batch_array_get_unused_slot(batch_array);\n\tif (batch_array->n_batch_states != old_size)\n\t{\n\t\tqueue->heap_entries =\n\t\t\trepalloc(queue->heap_entries,\n\t\t\t\t\t sizeof(HeapEntryColumn) * queue->nkeys * batch_array->n_batch_states);\n\t}\n\tDecompressBatchState *batch_state = batch_array_get_at(batch_array, new_batch_index);\n\n\tcompressed_batch_set_compressed_tuple(dcontext, batch_state, compressed_slot);\n\tcompressed_batch_save_first_tuple(dcontext, batch_state, queue->last_batch_first_tuple_slot);\n\n\t/*\n\t * Update the heap entries for the first tuple of the last batch.\n\t */\n\tfor (int key = 0; key < queue->nkeys; key++)\n\t{\n\t\tSortSupport sortKey = &queue->sortkeys[key];\n\t\tconst AttrNumber attr = AttrNumberGetAttrOffset(sortKey->ssup_attno);\n\t\t/*\n\t\t * We're working with virtual tuple slots so no need for slot_getattr().\n\t\t */\n\t\tAssert(TTS_IS_VIRTUAL(queue->last_batch_first_tuple_slot));\n\t\tqueue->last_batch_first_tuple_entry[key].value =\n\t\t\tqueue->last_batch_first_tuple_slot->tts_values[attr];\n\t\tqueue->last_batch_first_tuple_entry[key].null =\n\t\t\tqueue->last_batch_first_tuple_slot->tts_isnull[attr];\n\t}\n\n\tTupleTableSlot *current_tuple = compressed_batch_current_tuple(batch_state);\n\tif (TupIsNull(current_tuple))\n\t{\n\t\t/* Might happen if there are no tuples in the batch that pass the quals. */\n\t\tbatch_array_clear_at(batch_array, new_batch_index);\n\t\treturn;\n\t}\n\n\t/*\n\t * Update the heap entries for this batch with the first decompressed tuple\n\t * values.\n\t */\n\tfor (int key = 0; key < queue->nkeys; key++)\n\t{\n\t\tSortSupport sortKey = &queue->sortkeys[key];\n\t\tconst AttrNumber attr = AttrNumberGetAttrOffset(sortKey->ssup_attno);\n\t\t/*\n\t\t * We're working with virtual tuple slots so no need for slot_getattr().\n\t\t */\n\t\tAssert(TTS_IS_VIRTUAL(current_tuple));\n\t\tqueue->heap_entries[(new_batch_index * queue->nkeys) + key].value =\n\t\t\tcurrent_tuple->tts_values[attr];\n\t\tqueue->heap_entries[(new_batch_index * queue->nkeys) + key].null =\n\t\t\tcurrent_tuple->tts_isnull[attr];\n\t}\n\n\t/*\n\t * Put the batch on the heap.\n\t */\n\tqueue->merge_heap = binaryheap_add_unordered_autoresize(queue->merge_heap, new_batch_index);\n}\n\nstatic TupleTableSlot *\nbatch_queue_heap_top_tuple(BatchQueue *bq)\n{\n\tBatchQueueHeap *bqh = (BatchQueueHeap *) bq;\n\tBatchArray *batch_array = &bq->batch_array;\n\n\tif (binaryheap_empty(bqh->merge_heap))\n\t{\n\t\treturn NULL;\n\t}\n\n\tconst int top_batch_index = DatumGetInt32(binaryheap_first(bqh->merge_heap));\n\tDecompressBatchState *top_batch = batch_array_get_at(batch_array, top_batch_index);\n\tTupleTableSlot *top_tuple = compressed_batch_current_tuple(top_batch);\n\tAssert(!TupIsNull(top_tuple));\n\treturn top_tuple;\n}\n\nstatic void\nbatch_queue_heap_reset(BatchQueue *bq)\n{\n\tBatchQueueHeap *bqh = (BatchQueueHeap *) bq;\n\tbinaryheap_reset(bqh->merge_heap);\n}\n\n/*\n * Free the binary heap.\n */\nstatic void\nbatch_queue_heap_free(BatchQueue *_queue)\n{\n\tBatchQueueHeap *queue = (BatchQueueHeap *) _queue;\n\tBatchArray *batch_array = &queue->queue.batch_array;\n\n\telog(DEBUG3, \"heap has capacity of %d\", queue->merge_heap->bh_space);\n\telog(DEBUG3, \"created batch states %d\", batch_array->n_batch_states);\n\tbatch_array_clear_all(batch_array);\n\tpfree(queue->heap_entries);\n\tbinaryheap_free(queue->merge_heap);\n\tqueue->merge_heap = NULL;\n\tpfree(queue->sortkeys);\n\tExecDropSingleTupleTableSlot(queue->last_batch_first_tuple_slot);\n\tpfree(queue->last_batch_first_tuple_entry);\n\tbatch_array_destroy(batch_array);\n\tpfree(queue);\n}\n\nconst struct BatchQueueFunctions BatchQueueFunctionsHeap = {\n\t.free = batch_queue_heap_free,\n\t.needs_next_batch = batch_queue_heap_needs_next_batch,\n\t.pop = batch_queue_heap_pop,\n\t.push_batch = batch_queue_heap_push_batch,\n\t.reset = batch_queue_heap_reset,\n\t.top_tuple = batch_queue_heap_top_tuple,\n};\n\nstatic SortSupport\nbuild_batch_sorted_merge_info(const List *sortinfo, int *nkeys)\n{\n\tAssert(sortinfo != NULL);\n\n\tList *sort_col_idx = linitial(sortinfo);\n\tList *sort_ops = lsecond(sortinfo);\n\tList *sort_collations = lthird(sortinfo);\n\tList *sort_nulls = lfourth(sortinfo);\n\n\t*nkeys = list_length(linitial((sortinfo)));\n\n\tAssert(list_length(sort_col_idx) == list_length(sort_ops));\n\tAssert(list_length(sort_ops) == list_length(sort_collations));\n\tAssert(list_length(sort_collations) == list_length(sort_nulls));\n\tAssert(*nkeys > 0);\n\n\tSortSupportData *sortkeys = palloc0(sizeof(SortSupportData) * *nkeys);\n\n\t/* Inspired by nodeMergeAppend.c */\n\tfor (int i = 0; i < *nkeys; i++)\n\t{\n\t\tSortSupportData *sortkey = &sortkeys[i];\n\n\t\tsortkey->ssup_cxt = CurrentMemoryContext;\n\t\tsortkey->ssup_collation = list_nth_oid(sort_collations, i);\n\t\tsortkey->ssup_nulls_first = list_nth_oid(sort_nulls, i);\n\t\tsortkey->ssup_attno = list_nth_oid(sort_col_idx, i);\n\n\t\t/*\n\t\t * It isn't feasible to perform abbreviated key conversion, since\n\t\t * tuples are pulled into mergestate's binary heap as needed.  It\n\t\t * would likely be counter-productive to convert tuples into an\n\t\t * abbreviated representation as they're pulled up, so opt out of that\n\t\t * additional optimization entirely.\n\t\t */\n\t\tsortkey->abbreviate = false;\n\t\tPrepareSortSupportFromOrderingOp(list_nth_oid(sort_ops, i), sortkey);\n\t}\n\n\treturn sortkeys;\n}\n\nBatchQueue *\nbatch_queue_heap_create(int num_compressed_cols, const List *sortinfo,\n\t\t\t\t\t\tconst TupleDesc result_tupdesc, const BatchQueueFunctions *funcs)\n{\n\tBatchQueueHeap *queue = palloc0(sizeof(BatchQueueHeap));\n\n\tbatch_array_init(&queue->queue.batch_array, INITIAL_BATCH_CAPACITY, num_compressed_cols);\n\n\tqueue->sortkeys = build_batch_sorted_merge_info(sortinfo, &queue->nkeys);\n\n\tqueue->heap_entries = palloc(sizeof(HeapEntryColumn) * queue->nkeys * INITIAL_BATCH_CAPACITY);\n\n\t/*\n\t * Choose a specialization for faster comparison of the first column. This is\n\t * the approach that tuplesort uses, see e.g. qsort_tuple_signed().\n\t * The ssup_datum_unsigned_cmp is used only for abbreviated keys which the\n\t * batch sorted merge doesn't use, so we use a generic comparator in this\n\t * case.\n\t */\n\tbinaryheap_comparator comparator = compare_heap_pos_generic;\n\tif (queue->sortkeys[0].comparator == ssup_datum_int32_cmp)\n\t{\n\t\tcomparator = compare_heap_pos_int32;\n\t}\n#if SIZEOF_DATUM >= 8\n\telse if (queue->sortkeys[0].comparator == ssup_datum_signed_cmp)\n\t{\n\t\tcomparator = compare_heap_pos_signed;\n\t}\n#endif\n\n\tqueue->merge_heap = binaryheap_allocate(INITIAL_BATCH_CAPACITY, comparator, queue);\n\tqueue->last_batch_first_tuple_slot = MakeSingleTupleTableSlot(result_tupdesc, &TTSOpsVirtual);\n\tqueue->last_batch_first_tuple_entry = palloc(sizeof(HeapEntryColumn) * queue->nkeys);\n\tqueue->queue.funcs = funcs;\n\n\treturn &queue->queue;\n}\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/batch_queue_heap.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include \"batch_queue.h\"\n\nextern BatchQueue *batch_queue_heap_create(int num_compressed_cols, const List *sortinfo,\n\t\t\t\t\t\t\t\t\t\t   const TupleDesc result_tupdesc,\n\t\t\t\t\t\t\t\t\t\t   const BatchQueueFunctions *funcs);\n\nextern const struct BatchQueueFunctions BatchQueueFunctionsHeap;\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/columnar_scan.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n#include \"chunk.h\"\n#include \"hypertable_cache.h\"\n#include <catalog/pg_operator.h>\n#include <math.h>\n#include <miscadmin.h>\n#include <nodes/bitmapset.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodeFuncs.h>\n#include <optimizer/cost.h>\n#include <optimizer/optimizer.h>\n#include <optimizer/pathnode.h>\n#include <optimizer/paths.h>\n#include <parser/parse_relation.h>\n#include <parser/parsetree.h>\n#include <planner/planner.h>\n#include <storage/lockdefs.h>\n#include <utils/builtins.h>\n#include <utils/lsyscache.h>\n#include <utils/syscache.h>\n#include <utils/typcache.h>\n\n#include <planner.h>\n\n#include \"compat/compat.h\"\n#include \"compression/compression.h\"\n#include \"compression/create.h\"\n#include \"cross_module_fn.h\"\n#include \"custom_type_cache.h\"\n#include \"debug_assert.h\"\n#include \"import/allpaths.h\"\n#include \"import/planner.h\"\n#include \"nodes/columnar_scan/columnar_scan.h\"\n#include \"nodes/columnar_scan/planner.h\"\n#include \"nodes/columnar_scan/qual_pushdown.h\"\n#include \"ts_catalog/array_utils.h\"\n#include \"utils.h\"\n\nstatic CustomPathMethods columnar_scan_path_methods = {\n\t.CustomName = \"ColumnarScan\",\n\t.PlanCustomPath = columnar_scan_plan_create,\n};\n\ntypedef struct SortInfo\n{\n\tList *required_compressed_pathkeys;\n\tList *required_eq_classes;\n\tbool needs_sequence_num;\n\tbool use_compressed_sort; /* sort can be pushed below ColumnarScan */\n\tbool use_batch_sorted_merge;\n\tbool reverse;\n\n\tList *decompressed_sort_pathkeys;\n\tQualCost decompressed_sort_pathkeys_cost;\n} SortInfo;\n\nstatic RangeTblEntry *columnar_scan_make_rte(Oid compressed_relid, LOCKMODE lockmode, Query *parse);\nstatic void create_compressed_scan_paths(PlannerInfo *root, RelOptInfo *compressed_rel,\n\t\t\t\t\t\t\t\t\t\t const CompressionInfo *compression_info,\n\t\t\t\t\t\t\t\t\t\t const SortInfo *sort_info);\n\nstatic ColumnarScanPath *columnar_scan_path_create(PlannerInfo *root, const CompressionInfo *info,\n\t\t\t\t\t\t\t\t\t\t\t\t   Path *compressed_path);\n\nstatic void columnar_scan_add_plannerinfo(PlannerInfo *root, CompressionInfo *info,\n\t\t\t\t\t\t\t\t\t\t  const Chunk *chunk, RelOptInfo *chunk_rel,\n\t\t\t\t\t\t\t\t\t\t  bool needs_sequence_num);\n\nstatic SortInfo build_sortinfo(PlannerInfo *root, const Chunk *chunk, RelOptInfo *chunk_rel,\n\t\t\t\t\t\t\t   const CompressionInfo *info, List *pathkeys);\n\nstatic Bitmapset *find_const_segmentby(RelOptInfo *chunk_rel, const CompressionInfo *info);\n\nstatic EquivalenceClass *\nappend_ec_for_seqnum(PlannerInfo *root, const CompressionInfo *info, const SortInfo *sort_info,\n\t\t\t\t\t Var *var, Oid sortop, bool nulls_first)\n{\n\tMemoryContext oldcontext = MemoryContextSwitchTo(root->planner_cxt);\n\n\tOid opfamily, opcintype, equality_op;\n\tCompareType strategy;\n\tList *opfamilies;\n\tEquivalenceClass *newec = makeNode(EquivalenceClass);\n\tEquivalenceMember *em = makeNode(EquivalenceMember);\n\n\t/* Find the operator in pg_amop --- failure shouldn't happen */\n\tif (!get_ordering_op_properties(sortop, &opfamily, &opcintype, &strategy))\n\t\telog(ERROR, \"operator %u is not a valid ordering operator\", sortop);\n\n\t/*\n\t * EquivalenceClasses need to contain opfamily lists based on the family\n\t * membership of mergejoinable equality operators, which could belong to\n\t * more than one opfamily.  So we have to look up the opfamily's equality\n\t * operator and get its membership.\n\t */\n\tequality_op = get_opfamily_member(opfamily, opcintype, opcintype, BTEqualStrategyNumber);\n\tif (!OidIsValid(equality_op)) /* shouldn't happen */\n\t\telog(ERROR,\n\t\t\t \"missing operator %d(%u,%u) in opfamily %u\",\n\t\t\t BTEqualStrategyNumber,\n\t\t\t opcintype,\n\t\t\t opcintype,\n\t\t\t opfamily);\n\topfamilies = get_mergejoin_opfamilies(equality_op);\n\tif (!opfamilies) /* certainly should find some */\n\t\telog(ERROR, \"could not find opfamilies for equality operator %u\", equality_op);\n\n\tem->em_expr = (Expr *) var;\n\tem->em_relids = bms_make_singleton(info->compressed_rel->relid);\n#if PG16_LT\n\tem->em_nullable_relids = NULL;\n#endif\n\tem->em_is_const = false;\n\tem->em_is_child = false;\n\tem->em_datatype = INT4OID;\n\n\tnewec->ec_opfamilies = list_copy(opfamilies);\n\tnewec->ec_collation = 0;\n\tnewec->ec_members = list_make1(em);\n\tnewec->ec_sources = NIL;\n\tnewec->ec_derives_list = NIL;\n\tnewec->ec_relids = bms_make_singleton(info->compressed_rel->relid);\n\tnewec->ec_has_const = false;\n\tnewec->ec_has_volatile = false;\n#if PG16_LT\n\tnewec->ec_below_outer_join = false;\n#endif\n\tnewec->ec_broken = false;\n\tnewec->ec_sortref = 0;\n\tnewec->ec_min_security = UINT_MAX;\n\tnewec->ec_max_security = 0;\n\tnewec->ec_merged = NULL;\n\n\tinfo->compressed_rel->eclass_indexes =\n\t\tbms_add_member(info->compressed_rel->eclass_indexes, list_length(root->eq_classes));\n\troot->eq_classes = lappend(root->eq_classes, newec);\n\n\tMemoryContextSwitchTo(oldcontext);\n\n\treturn newec;\n}\n\nstatic EquivalenceClass *\nappend_ec_for_metadata_col(PlannerInfo *root, const CompressionInfo *info, Expr *expr, PathKey *pk,\n\t\t\t\t\t\t   Oid em_datatype)\n{\n\tMemoryContext oldcontext = MemoryContextSwitchTo(root->planner_cxt);\n\tEquivalenceMember *em = makeNode(EquivalenceMember);\n\n\tem->em_expr = expr;\n\tem->em_relids = bms_make_singleton(info->compressed_rel->relid);\n\tem->em_is_const = false;\n\tem->em_is_child = false;\n\tem->em_datatype = em_datatype;\n\tEquivalenceClass *ec = makeNode(EquivalenceClass);\n\tec->ec_opfamilies = pk->pk_eclass->ec_opfamilies;\n\tec->ec_collation = pk->pk_eclass->ec_collation;\n\tec->ec_members = list_make1(em);\n\tec->ec_sources = list_copy(pk->pk_eclass->ec_sources);\n\tec->ec_derives_list = list_copy(pk->pk_eclass->ec_derives_list);\n\tec->ec_relids = bms_make_singleton(info->compressed_rel->relid);\n\tec->ec_has_const = pk->pk_eclass->ec_has_const;\n\tec->ec_has_volatile = pk->pk_eclass->ec_has_volatile;\n#if PG16_LT\n\tec->ec_below_outer_join = pk->pk_eclass->ec_below_outer_join;\n#endif\n\tec->ec_broken = pk->pk_eclass->ec_broken;\n\tec->ec_sortref = pk->pk_eclass->ec_sortref;\n\tec->ec_min_security = pk->pk_eclass->ec_min_security;\n\tec->ec_max_security = pk->pk_eclass->ec_max_security;\n\tec->ec_merged = pk->pk_eclass->ec_merged;\n\troot->eq_classes = lappend(root->eq_classes, ec);\n\tMemoryContextSwitchTo(oldcontext);\n\tinfo->compressed_rel->eclass_indexes =\n\t\tbms_add_member(info->compressed_rel->eclass_indexes, root->eq_classes->length - 1);\n\n\treturn ec;\n}\n\nstatic List *\nbuild_compressed_scan_pathkeys(const SortInfo *sort_info, PlannerInfo *root, List *chunk_pathkeys,\n\t\t\t\t\t\t\t   const CompressionInfo *info)\n{\n\tVar *var;\n\tint varattno;\n\tList *required_compressed_pathkeys = NIL;\n\tListCell *lc = NULL;\n\tPathKey *pk;\n\n\t/*\n\t * all segmentby columns need to be prefix of pathkeys\n\t * except those with equality constraint in baserestrictinfo\n\t */\n\tif (info->num_segmentby_columns > 0)\n\t{\n\t\tTimescaleDBPrivate *compressed_fdw_private =\n\t\t\t(TimescaleDBPrivate *) info->compressed_rel->fdw_private;\n\t\t/*\n\t\t * We don't need any sorting for the segmentby columns that are equated\n\t\t * to a constant. The respective constant ECs are excluded from\n\t\t * canonical pathkeys, so we won't see these columns here. Count them as\n\t\t * seen from the start, so that we arrive at the proper counts of seen\n\t\t * segmentby columns in the end.\n\t\t */\n\t\tfor (lc = list_head(chunk_pathkeys); lc; lc = lnext(chunk_pathkeys, lc))\n\t\t{\n\t\t\tPathKey *pk = lfirst(lc);\n\t\t\tEquivalenceMember *compressed_em = NULL;\n\t\t\tListCell *ec_em_pair_cell;\n\t\t\tforeach (ec_em_pair_cell, compressed_fdw_private->compressed_ec_em_pairs)\n\t\t\t{\n\t\t\t\tList *pair = lfirst(ec_em_pair_cell);\n\t\t\t\tif (linitial(pair) == pk->pk_eclass)\n\t\t\t\t{\n\t\t\t\t\tcompressed_em = lsecond(pair);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * We should exit the loop after we've seen all required segmentby\n\t\t\t * columns. If we haven't seen them all, but the next pathkey\n\t\t\t * already refers a compressed column, it is a bug. See\n\t\t\t * build_sortinfo().\n\t\t\t */\n\t\t\tif (!compressed_em)\n\t\t\t\tbreak;\n\n\t\t\trequired_compressed_pathkeys = lappend(required_compressed_pathkeys, pk);\n\t\t}\n\t}\n\n\t/*\n\t * If pathkeys contains non-segmentby columns the rest of the ordering\n\t * requirements will be satisfied by ordering by sequence_num.\n\t */\n\tif (sort_info->needs_sequence_num)\n\t{\n\t\t/* TODO: split up legacy sequence number path and non-sequence number path into dedicated\n\t\t * functions. */\n\t\tif (info->has_seq_num)\n\t\t{\n\t\t\tbool nulls_first;\n\t\t\tOid sortop;\n\t\t\tvarattno = get_attnum(info->compressed_rte->relid,\n\t\t\t\t\t\t\t\t  COMPRESSION_COLUMN_METADATA_SEQUENCE_NUM_NAME);\n\t\t\tvar = makeVar(info->compressed_rel->relid, varattno, INT4OID, -1, InvalidOid, 0);\n\n\t\t\tif (sort_info->reverse)\n\t\t\t{\n\t\t\t\tsortop = get_commutator(Int4LessOperator);\n\t\t\t\tnulls_first = true;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tsortop = Int4LessOperator;\n\t\t\t\tnulls_first = false;\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * Create the EquivalenceClass for the sequence number column of this\n\t\t\t * compressed chunk, so that we can build the PathKey that refers to it.\n\t\t\t */\n\t\t\tEquivalenceClass *ec =\n\t\t\t\tappend_ec_for_seqnum(root, info, sort_info, var, sortop, nulls_first);\n\n\t\t\t/* Find the operator in pg_amop --- failure shouldn't happen. */\n\t\t\tOid opfamily, opcintype;\n\t\t\tCompareType strategy;\n\t\t\tif (!get_ordering_op_properties(sortop, &opfamily, &opcintype, &strategy))\n\t\t\t\telog(ERROR, \"operator %u is not a valid ordering operator\", sortop);\n\n\t\t\tpk = make_canonical_pathkey(root, ec, opfamily, strategy, nulls_first);\n\n\t\t\trequired_compressed_pathkeys = lappend(required_compressed_pathkeys, pk);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* If there are no segmentby pathkeys, start from the beginning of the list */\n\t\t\tif (info->num_segmentby_columns == 0)\n\t\t\t{\n\t\t\t\tlc = list_head(chunk_pathkeys);\n\t\t\t}\n\t\t\tAssert(lc != NULL);\n\t\t\tExpr *expr;\n\t\t\tchar *column_name;\n\t\t\tfor (; lc != NULL; lc = lnext(chunk_pathkeys, lc))\n\t\t\t{\n\t\t\t\tpk = lfirst(lc);\n\t\t\t\tEquivalenceMember *chunk_em = ts_find_em_for_rel(pk->pk_eclass, info->chunk_rel);\n\n\t\t\t\tAssert(chunk_em);\n\t\t\t\texpr = chunk_em->em_expr;\n\t\t\t\t/*\n\t\t\t\t * Use em_datatype from the original equivalence member as the\n\t\t\t\t * opcintype. For polymorphic types like anyenum,\n\t\t\t\t * canonicalize_ec_expression will not add a RelabelType (it\n\t\t\t\t * replaces polymorphic req_type with the concrete type), so we\n\t\t\t\t * must explicitly pass the correct em_datatype to the metadata\n\t\t\t\t * column EC.\n\t\t\t\t */\n\t\t\t\tOid opcintype = chunk_em->em_datatype;\n\t\t\t\tOid collation = exprCollation((Node *) expr);\n\t\t\t\texpr = (Expr *) strip_implicit_coercions((Node *) expr);\n\t\t\t\tvar = castNode(Var, expr);\n\t\t\t\tAssert(var->varattno > 0);\n\n\t\t\t\tcolumn_name = get_attname(info->chunk_rte->relid, var->varattno, false);\n\t\t\t\tint16 orderby_index = ts_array_position(info->settings->fd.orderby, column_name);\n\t\t\t\tvarattno =\n\t\t\t\t\tget_attnum(info->compressed_rte->relid, column_segment_min_name(orderby_index));\n\t\t\t\tAssert(orderby_index != 0);\n\t\t\t\tbool orderby_desc =\n\t\t\t\t\tts_array_get_element_bool(info->settings->fd.orderby_desc, orderby_index);\n\t\t\t\tbool orderby_nullsfirst =\n\t\t\t\t\tts_array_get_element_bool(info->settings->fd.orderby_nullsfirst, orderby_index);\n\n\t\t\t\tbool nulls_first;\n\t\t\t\tCompareType strategy;\n\n\t\t\t\tif (sort_info->reverse)\n\t\t\t\t{\n\t\t\t\t\tstrategy = orderby_desc ? BTLessStrategyNumber : BTGreaterStrategyNumber;\n\t\t\t\t\tnulls_first = !orderby_nullsfirst;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tstrategy = orderby_desc ? BTGreaterStrategyNumber : BTLessStrategyNumber;\n\t\t\t\t\tnulls_first = orderby_nullsfirst;\n\t\t\t\t}\n\n\t\t\t\tVar *metadata_var = makeVar(info->compressed_rel->relid,\n\t\t\t\t\t\t\t\t\t\t\tvarattno,\n\t\t\t\t\t\t\t\t\t\t\tvar->vartype,\n\t\t\t\t\t\t\t\t\t\t\tvar->vartypmod,\n\t\t\t\t\t\t\t\t\t\t\tvar->varcollid,\n\t\t\t\t\t\t\t\t\t\t\tvar->varlevelsup);\n\t\t\t\tExpr *min_expr =\n\t\t\t\t\tcanonicalize_ec_expression((Expr *) metadata_var, opcintype, collation);\n\t\t\t\tEquivalenceClass *min_ec =\n\t\t\t\t\tappend_ec_for_metadata_col(root, info, min_expr, pk, opcintype);\n\t\t\t\tPathKey *min =\n\t\t\t\t\tmake_canonical_pathkey(root, min_ec, pk->pk_opfamily, strategy, nulls_first);\n\t\t\t\trequired_compressed_pathkeys = lappend(required_compressed_pathkeys, min);\n\n\t\t\t\tvarattno =\n\t\t\t\t\tget_attnum(info->compressed_rte->relid, column_segment_max_name(orderby_index));\n\t\t\t\tmetadata_var = makeVar(info->compressed_rel->relid,\n\t\t\t\t\t\t\t\t\t   varattno,\n\t\t\t\t\t\t\t\t\t   var->vartype,\n\t\t\t\t\t\t\t\t\t   var->vartypmod,\n\t\t\t\t\t\t\t\t\t   var->varcollid,\n\t\t\t\t\t\t\t\t\t   var->varlevelsup);\n\t\t\t\tExpr *max_expr =\n\t\t\t\t\tcanonicalize_ec_expression((Expr *) metadata_var, opcintype, collation);\n\t\t\t\tEquivalenceClass *max_ec =\n\t\t\t\t\tappend_ec_for_metadata_col(root, info, max_expr, pk, opcintype);\n\t\t\t\tPathKey *max =\n\t\t\t\t\tmake_canonical_pathkey(root, max_ec, pk->pk_opfamily, strategy, nulls_first);\n\n\t\t\t\trequired_compressed_pathkeys = lappend(required_compressed_pathkeys, max);\n\t\t\t}\n\t\t}\n\t}\n\treturn required_compressed_pathkeys;\n}\n\nColumnarScanPath *\ncopy_columnar_scan_path(ColumnarScanPath *src)\n{\n\tAssert(ts_is_columnar_scan_path(&src->custom_path.path));\n\n\tColumnarScanPath *dst = palloc(sizeof(ColumnarScanPath));\n\tmemcpy(dst, src, sizeof(ColumnarScanPath));\n\n\treturn dst;\n}\n\n/*\n * Maps the attno of the min metadata column in the compressed chunk to the\n * attno of the corresponding max metadata column. Zero if none or not applicable.\n */\ntypedef struct SelectivityEstimationContext\n{\n\tAttrNumber *min_to_max;\n\tAttrNumber *max_to_min;\n\n\tList *vars;\n} SelectivityEstimationContext;\n\n/*\n * Collect the Vars referencing the \"min\" metadata columns into the context->vars.\n */\nstatic bool\nmin_metadata_vars_collector(Node *orig_node, SelectivityEstimationContext *context)\n{\n\tif (orig_node == NULL)\n\t{\n\t\t/*\n\t\t * An expression node can have a NULL field and the mutator will be\n\t\t * still called for it, so we have to handle this.\n\t\t */\n\t\treturn false;\n\t}\n\n\tif (!IsA(orig_node, Var))\n\t{\n\t\t/*\n\t\t * Recurse.\n\t\t */\n\t\treturn expression_tree_walker(orig_node, min_metadata_vars_collector, context);\n\t}\n\n\tVar *orig_var = castNode(Var, orig_node);\n\tif (orig_var->varattno <= 0)\n\t{\n\t\t/*\n\t\t * We don't handle special variables. Not sure how it could happen though.\n\t\t */\n\t\treturn false;\n\t}\n\n\tAttrNumber replaced_attno = context->min_to_max[orig_var->varattno];\n\tif (replaced_attno == InvalidAttrNumber)\n\t{\n\t\t/*\n\t\t * No replacement for this column.\n\t\t */\n\t\treturn false;\n\t}\n\n\tcontext->vars = lappend(context->vars, orig_var);\n\treturn false;\n}\n\nstatic void\nset_compressed_baserel_size_estimates(PlannerInfo *root, RelOptInfo *rel,\n\t\t\t\t\t\t\t\t\t  CompressionInfo *compression_info)\n{\n\t/*\n\t * We need some custom selectivity estimation code for the compressed chunk\n\t * table, because some pushed down filters require special handling.\n\t *\n\t * An equality condition can be pushed down to the minmax sparse index\n\t * condition, and becomes x_min <= const and const <= x_max. Postgres\n\t * treats the part of this condition as independent, which leads to\n\t * significant overestimates when x has high cardinality, and therefore\n\t * not using the Index Scan. This stems from the fact that Postgres doesn't\n\t * know that x_max is always just very slightly more than x_min for the\n\t * given compressed batch.\n\n\t * To work around this, temporarily replace all conditions on x_min with\n\t * conditions on x_max before feeding them to the Postgres clauselist\n\t * selectivity functions. Since the range of x_min to x_max for a given\n\t * batch is small relative to the range of x in the entire chunk, this\n\t * should not introduce much error, but at the same time allow Postgres to\n\t * see the correlation.\n\t *\n\t * We do this here for the entire baserestrictinfo and not per-rinfo as we\n\t * add them during filter pushdown, because the Postgres clauselist\n\t * selectivity estimator must see the entire clause list to detect the range\n\t * conditions.\n\t *\n\t * First, build the correspondence of min metadata attno -> max metadata\n\t * attno for all minmax metadata.\n\t */\n\tconst int storage_elements = 2 * (compression_info->compressed_rel->max_attr + 1);\n\tAttrNumber *storage = palloc0(storage_elements * sizeof(*storage));\n\tSelectivityEstimationContext context = {\n\t\t.min_to_max = &storage[0],\n\t\t.max_to_min = &storage[compression_info->compressed_rel->max_attr],\n\t};\n\n\tfor (int uncompressed_attno = 1; uncompressed_attno <= compression_info->chunk_rel->max_attr;\n\t\t uncompressed_attno++)\n\t{\n\t\tif (get_rte_attribute_is_dropped(compression_info->chunk_rte, uncompressed_attno))\n\t\t{\n\t\t\t/* Skip the dropped column. */\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst char *attname = get_attname(compression_info->chunk_rte->relid,\n\t\t\t\t\t\t\t\t\t\t  uncompressed_attno,\n\t\t\t\t\t\t\t\t\t\t  /* missing_ok = */ false);\n\t\tconst int16 orderby_pos =\n\t\t\tts_array_position(compression_info->settings->fd.orderby, attname);\n\n\t\tif (orderby_pos == 0)\n\t\t{\n\t\t\t/*\n\t\t\t * This reasoning is only applicable to orderby columns, where each\n\t\t\t * batch is a thin slice of the entire range of the column. It also does\n\t\t\t * not have many intersections, because the compressed batches mostly\n\t\t\t * follow the total order of orderby columns, that is relaxed for the\n\t\t\t * last orderby  columns or unordered chunks.This does not necessarily\n\t\t\t * hold for non-orderby columns that can also have a sparse index.\n\t\t\t */\n\t\t\tcontinue;\n\t\t}\n\n\t\tAttrNumber min_attno =\n\t\t\tcompressed_column_metadata_attno(compression_info->settings,\n\t\t\t\t\t\t\t\t\t\t\t compression_info->chunk_rte->relid,\n\t\t\t\t\t\t\t\t\t\t\t uncompressed_attno,\n\t\t\t\t\t\t\t\t\t\t\t compression_info->compressed_rte->relid,\n\t\t\t\t\t\t\t\t\t\t\t \"min\");\n\t\tAttrNumber max_attno =\n\t\t\tcompressed_column_metadata_attno(compression_info->settings,\n\t\t\t\t\t\t\t\t\t\t\t compression_info->chunk_rte->relid,\n\t\t\t\t\t\t\t\t\t\t\t uncompressed_attno,\n\t\t\t\t\t\t\t\t\t\t\t compression_info->compressed_rte->relid,\n\t\t\t\t\t\t\t\t\t\t\t \"max\");\n\n\t\tif (min_attno == InvalidAttrNumber || max_attno == InvalidAttrNumber)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\tAssert(&context.min_to_max[min_attno] < &storage[storage_elements]);\n\t\tAssert(&context.max_to_min[max_attno] < &storage[storage_elements]);\n\n\t\tcontext.min_to_max[min_attno] = max_attno;\n\t\tcontext.max_to_min[max_attno] = min_attno;\n\t}\n\n\t/*\n\t * Then, replace all conditions on min metadata column with conditions on\n\t * max metadata column.\n\t */\n\tListCell *lc;\n\tforeach (lc, rel->baserestrictinfo)\n\t{\n\t\tRestrictInfo *orig_restrictinfo = castNode(RestrictInfo, lfirst(lc));\n\t\tNode *orig_clause = (Node *) orig_restrictinfo->clause;\n\t\texpression_tree_walker(orig_clause, min_metadata_vars_collector, &context);\n\t}\n\n\t/*\n\t * Temporarily replace \"min\" with \"max\" in-place to save on memory allocations.\n\t */\n\tforeach (lc, context.vars)\n\t{\n\t\tVar *var = castNode(Var, lfirst(lc));\n\n\t\tAssert(var->varattno != InvalidAttrNumber);\n\t\tAssert(context.min_to_max[var->varattno] != InvalidAttrNumber);\n\t\tAssert(context.max_to_min[context.min_to_max[var->varattno]] == var->varattno);\n\n\t\tvar->varattno = context.min_to_max[var->varattno];\n\t}\n\n\t/*\n\t * Compute selectivity with the updated filters.\n\t */\n\tset_baserel_size_estimates(root, rel);\n\n\t/*\n\t * Replace the Vars back.\n\t */\n\tforeach (lc, context.vars)\n\t{\n\t\tVar *var = castNode(Var, lfirst(lc));\n\t\tvar->varattno = context.max_to_min[var->varattno];\n\t}\n\n\tpfree(storage);\n}\n\nstatic CompressionInfo *\nbuild_compressioninfo(PlannerInfo *root, const Hypertable *ht, const Chunk *chunk,\n\t\t\t\t\t  RelOptInfo *chunk_rel)\n{\n\tAppendRelInfo *appinfo;\n\tCompressionInfo *info = palloc0(sizeof(CompressionInfo));\n\n\tinfo->compresseddata_oid = ts_custom_type_cache_get(CUSTOM_TYPE_COMPRESSED_DATA)->type_oid;\n\n\tinfo->chunk_rel = chunk_rel;\n\tinfo->chunk_rte = planner_rt_fetch(chunk_rel->relid, root);\n\tinfo->settings = ts_compression_settings_get(chunk->table_id);\n\n\tif (chunk_rel->reloptkind == RELOPT_OTHER_MEMBER_REL)\n\t{\n\t\tappinfo = ts_get_appendrelinfo(root, chunk_rel->relid, false);\n\t\tRangeTblEntry *rte = planner_rt_fetch(appinfo->parent_relid, root);\n\t\tif (rte->rtekind == RTE_RELATION)\n\t\t{\n\t\t\tinfo->ht_rte = rte;\n\t\t\tinfo->ht_rel = root->simple_rel_array[appinfo->parent_relid];\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* In UNION queries referencing chunks directly, the parent rel can be a subquery */\n\t\t\tAssert(rte->rtekind == RTE_SUBQUERY);\n\t\t\tinfo->single_chunk = true;\n\t\t\tinfo->ht_rte = info->chunk_rte;\n\t\t\tinfo->ht_rel = info->chunk_rel;\n\t\t}\n\t}\n\telse\n\t{\n\t\tAssert(chunk_rel->reloptkind == RELOPT_BASEREL);\n\t\tinfo->single_chunk = true;\n\t\tinfo->ht_rte = info->chunk_rte;\n\t\tinfo->ht_rel = info->chunk_rel;\n\t}\n\n\tinfo->hypertable_id = ht->fd.id;\n\n\tinfo->num_orderby_columns = ts_array_length(info->settings->fd.orderby);\n\tinfo->num_segmentby_columns = ts_array_length(info->settings->fd.segmentby);\n\n\tif (info->num_segmentby_columns)\n\t{\n\t\tArrayIterator it = array_create_iterator(info->settings->fd.segmentby, 0, NULL);\n\t\tDatum datum;\n\t\tbool isnull;\n\t\twhile (array_iterate(it, &datum, &isnull))\n\t\t{\n\t\t\tEnsure(!isnull, \"NULL element in catalog array\");\n\t\t\tAttrNumber chunk_attno = get_attnum(info->chunk_rte->relid, TextDatumGetCString(datum));\n\t\t\tinfo->chunk_segmentby_attnos =\n\t\t\t\tbms_add_member(info->chunk_segmentby_attnos, chunk_attno);\n\t\t}\n\t}\n\n\tinfo->has_seq_num =\n\t\tget_attnum(info->settings->fd.compress_relid,\n\t\t\t\t   COMPRESSION_COLUMN_METADATA_SEQUENCE_NUM_NAME) != InvalidAttrNumber;\n\n\tinfo->chunk_const_segmentby = find_const_segmentby(chunk_rel, info);\n\n\t/*\n\t * If the chunk is member of hypertable expansion or a UNION, find its\n\t * parent relation ids. We will use it later to filter out some parameterized\n\t * paths.\n\t */\n\tif (chunk_rel->reloptkind == RELOPT_OTHER_MEMBER_REL)\n\t{\n\t\tinfo->parent_relids = find_childrel_parents(root, chunk_rel);\n\t}\n\n\tinfo->chunk_status = chunk->fd.status;\n\n\treturn info;\n}\n\n/*\n * Estimate the average count of elements in the compressed batch based on the\n * Postgres statistics for _ts_meta_count column.\n * Returns TARGET_COMPRESSED_BATCH_SIZE when no pg_statistic entry exists.\n */\ndouble\nts_columnar_estimate_compressed_batch_size(const Oid relid)\n{\n\tAttrNumber attnum = get_attnum(relid, \"_ts_meta_count\");\n\tif (attnum == InvalidAttrNumber)\n\t\treturn TARGET_COMPRESSED_BATCH_SIZE;\n\n\t/* fetch statistics */\n\tHeapTuple statsTuple = SearchSysCache3(STATRELATTINH,\n\t\t\t\t\t\t\t\t\t\t   ObjectIdGetDatum(relid),\n\t\t\t\t\t\t\t\t\t\t   Int16GetDatum(attnum),\n\t\t\t\t\t\t\t\t\t\t   BoolGetDatum(false));\n\tif (!HeapTupleIsValid(statsTuple))\n\t{\n\t\treturn TARGET_COMPRESSED_BATCH_SIZE;\n\t}\n\n\tdouble mcv_sum = 0.0;\n\tdouble mcv_freq = 0.0;\n\n\t/* exact MCV contribution */\n\tAttStatsSlot mcvslot;\n\tif (get_attstatsslot(&mcvslot,\n\t\t\t\t\t\t statsTuple,\n\t\t\t\t\t\t STATISTIC_KIND_MCV,\n\t\t\t\t\t\t InvalidOid,\n\t\t\t\t\t\t ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS))\n\t{\n\t\tfor (int i = 0; i < mcvslot.nvalues; i++)\n\t\t{\n\t\t\tdouble val = (double) DatumGetInt32(mcvslot.values[i]);\n\t\t\tdouble freq = (double) mcvslot.numbers[i];\n\t\t\tmcv_sum += val * freq;\n\t\t\tmcv_freq += freq;\n\t\t}\n\t\tfree_attstatsslot(&mcvslot);\n\t}\n\n\tdouble hist_sum = 0.0;\n\n\t/* histogram contribution */\n\tAttStatsSlot histslot;\n\tif (get_attstatsslot(&histslot,\n\t\t\t\t\t\t statsTuple,\n\t\t\t\t\t\t STATISTIC_KIND_HISTOGRAM,\n\t\t\t\t\t\t InvalidOid,\n\t\t\t\t\t\t ATTSTATSSLOT_VALUES))\n\t{\n\t\tint buckets = histslot.nvalues - 1;\n\n\t\tif (buckets > 0 && mcv_freq < 1.0)\n\t\t{\n\t\t\tfor (int i = 0; i < buckets; i++)\n\t\t\t{\n\t\t\t\tdouble lo = (double) DatumGetInt32(histslot.values[i]);\n\t\t\t\tdouble hi = (double) DatumGetInt32(histslot.values[i + 1]);\n\t\t\t\thist_sum += (lo + hi) / 2.0;\n\t\t\t}\n\t\t\thist_sum *= (1.0 - mcv_freq) / buckets;\n\t\t}\n\t\tfree_attstatsslot(&histslot);\n\t}\n\n\tReleaseSysCache(statsTuple);\n\n\tconst double final_result = mcv_sum + hist_sum;\n\tif (final_result == 0)\n\t{\n\t\t/*\n\t\t * For tables with few rows, the statistics tuple will contain all zero\n\t\t * values. We shouldn't return zero in this case to avoid weird behavior.\n\t\t */\n\t\treturn TARGET_COMPRESSED_BATCH_SIZE;\n\t}\n\n\treturn final_result;\n}\n\n/*\n * calculate cost for ColumnarScanPath\n *\n * since we have to read whole batch before producing tuple\n * we put cost of 1 tuple of compressed_scan as startup cost\n */\nstatic void\ncost_columnar_scan(PlannerInfo *root, const CompressionInfo *compression_info, Path *path,\n\t\t\t\t   Path *compressed_path)\n{\n\t/* startup_cost is cost before fetching first tuple */\n\tconst double compressed_rows = Max(1, compressed_path->rows);\n\tpath->startup_cost =\n\t\tcompressed_path->startup_cost +\n\t\t(compressed_path->total_cost - compressed_path->startup_cost) / compressed_rows;\n\n\t/* total_cost is cost for fetching all tuples */\n\tpath->rows = compressed_path->rows * compression_info->compressed_batch_size;\n\tpath->total_cost = compressed_path->total_cost + path->rows * cpu_tuple_cost;\n\n#if PG18_GE\n\t/* PG18 changes the way we handle disabled nodes so we\n\t * need to take those into account as well.\n\t *\n\t * https://github.com/postgres/postgres/commit/e2225346\n\t */\n\tpath->disabled_nodes = compressed_path->disabled_nodes;\n#endif\n}\n\n/* Smoothstep function S1 (the h01 cubic Hermite spline). */\nstatic double\nsmoothstep(double x, double start, double end)\n{\n\tx = (x - start) / (end - start);\n\n\tif (x < 0)\n\t{\n\t\tx = 0;\n\t}\n\telse if (x > 1)\n\t{\n\t\tx = 1;\n\t}\n\n\treturn x * x * (3.0F - 2.0F * x);\n}\n\n/*\n * If the query 'order by' is prefix of the compression 'order by' (or equal), we can exploit\n * the ordering of the individual batches to create a total ordered result without resorting\n * the tuples. This speeds up all queries that use this ordering (because no sort node is\n * needed). In particular, queries that use a LIMIT are speed-up because only the top elements\n * of the affected batches needs to be decompressed. Without the optimization, the entire batches\n * are decompressed, sorted, and then the top elements are taken from the result.\n *\n * The idea is to do something similar to the MergeAppend node; a BinaryHeap is used\n * to merge the per segment by column sorted individual batches into a sorted result. So, we end\n * up which a data flow which looks as follows:\n *\n * ColumnarScan\n *   * Decompress Batch 1\n *   * Decompress Batch 2\n *   * Decompress Batch 3\n *       [....]\n *   * Decompress Batch N\n *\n * Using the presorted batches, we are able to open these batches dynamically. If we don't presort\n * them, we would have to open all batches at the same time. This would be similar to the work the\n * MergeAppend does, but this is not needed in our case and we could reduce the size of the heap and\n * the amount of parallel open batches.\n *\n * The algorithm works as follows:\n *\n *   (1) A sort node is placed below the decompress scan node and on top of the scan\n *       on the compressed chunk. This sort node uses the min/max values of the 'order by'\n *       columns from the metadata of the batch to get them into an order which can be\n *       used to merge them.\n *\n *       [Scan on compressed chunk] -> [Sort on min/max values] -> [Decompress and merge]\n *\n *       For example, the batches are sorted on the min value of the 'order by' metadata\n *       column: [0, 3] [0, 5] [3, 7] [6, 10]\n *\n *   (2) The decompress chunk node initializes a binary heap, opens the first batch and\n *       decompresses the first tuple from the batch. The tuple is put on the heap. In addition\n *       the opened batch is marked as the most recent batch (MRB).\n *\n *   (3) As soon as a tuple is requested from the heap, the following steps are performed:\n *       (3a) If the heap is empty, we are done.\n *       (3b) The top tuple from the heap is taken. It is checked if this tuple is from the\n *            MRB. If this is the case, the next batch is opened, the first tuple is decompressed,\n *            placed on the heap and this batch is marked as MRB. This is repeated until the\n *            top tuple from the heap is not from the MRB. After the top tuple is not from the\n *            MRB, all batches (and one ahead) which might contain the most recent tuple are\n *            opened and placed on the heap.\n *\n *            In the example above, the first three batches are opened because the first two\n *            batches might contain tuples with a value of 0.\n *       (3c) The top element from the heap is removed, the next tuple from the batch is\n *            decompressed (if present) and placed on the heap.\n *       (3d) The former top tuple of the heap is returned.\n *\n * This function calculate the costs for retrieving the decompressed in-order\n * using a binary heap.\n */\nstatic void\ncost_batch_sorted_merge(PlannerInfo *root, const CompressionInfo *compression_info,\n\t\t\t\t\t\tColumnarScanPath *dcpath, Path *compressed_path)\n{\n\tPath sort_path; /* dummy for result of cost_sort */\n\n\t/*\n\t * Don't disable the compressed batch sorted merge plan with the enable_sort\n\t * GUC. We have a separate GUC for it, and this way you can try to force the\n\t * batch sorted merge plan by disabling sort.\n\t */\n\tconst bool old_enable_sort = enable_sort;\n\tenable_sort = true;\n\tcost_sort(&sort_path,\n\t\t\t  root,\n\t\t\t  dcpath->required_compressed_pathkeys,\n#if PG18_GE\n\t\t\t  compressed_path->disabled_nodes,\n#endif\n\t\t\t  compressed_path->total_cost,\n\t\t\t  compressed_path->rows,\n\t\t\t  compressed_path->pathtarget->width,\n\t\t\t  0.0,\n\t\t\t  work_mem,\n\t\t\t  -1);\n\tenable_sort = old_enable_sort;\n\n\t/*\n\t * In compressed batch sorted merge, for each distinct segmentby value we\n\t * have to keep the corresponding latest batch open. Estimate the number of\n\t * these batches with the usual Postgres estimator for grouping cardinality.\n\t */\n\tList *segmentby_groupexprs = NIL;\n\tfor (int segmentby_attno = bms_next_member(compression_info->chunk_segmentby_attnos, -1);\n\t\t segmentby_attno > 0;\n\t\t segmentby_attno =\n\t\t\t bms_next_member(compression_info->chunk_segmentby_attnos, segmentby_attno))\n\t{\n\t\tchar *colname = get_attname(compression_info->chunk_rte->relid,\n\t\t\t\t\t\t\t\t\tsegmentby_attno,\n\t\t\t\t\t\t\t\t\t/* missing_ok = */ false);\n\t\tAttrNumber compressed_attno = get_attnum(compression_info->compressed_rte->relid, colname);\n\t\tEnsure(compressed_attno != InvalidAttrNumber,\n\t\t\t   \"segmentby column %s not found in compressed chunk %d\",\n\t\t\t   colname,\n\t\t\t   compression_info->compressed_rte->relid);\n\t\tVar *var = palloc(sizeof(Var));\n\t\t*var = (Var){ .xpr.type = T_Var,\n\t\t\t\t\t  .varno = compression_info->compressed_rel->relid,\n\t\t\t\t\t  .varattno = compressed_attno };\n\t\tsegmentby_groupexprs = lappend(segmentby_groupexprs, var);\n\t}\n\tconst double open_batches_estimated =\n\t\testimate_num_groups(root, segmentby_groupexprs, dcpath->custom_path.path.rows, NULL, NULL);\n\tAssert(open_batches_estimated > 0);\n\n\t/*\n\t * We can't have more open batches than the total number of compressed rows,\n\t * so clamp it for sanity of the following calculations.\n\t */\n\tconst double open_batches_clamped = Min(open_batches_estimated, sort_path.rows);\n\n\t/*\n\t * Keeping a lot of batches open might use a lot of memory. The batch sorted\n\t * merge can't offload anything to disk, so we just penalize it heavily if\n\t * we expect it to go over the work_mem. First, estimate the amount of\n\t * memory we'll need. We do this on the basis of uncompressed chunk width,\n\t * as if we had to materialize entire decompressed batches. This might\n\t * be less precise when bulk decompression is not used, because we\n\t * materialize only the compressed data which is smaller. But it accounts\n\t * for projections, which is probably more important than precision, because\n\t * we often read a small subset of columns in analytical queries. The\n\t * compressed chunk is never projected so we can't use it for that.\n\t */\n\tconst double work_mem_bytes = work_mem * 1024.0;\n\tconst double needed_memory_bytes = open_batches_clamped *\n\t\t\t\t\t\t\t\t\t   compression_info->compressed_batch_size *\n\t\t\t\t\t\t\t\t\t   dcpath->custom_path.path.pathtarget->width;\n\n\t/*\n\t * Next, calculate the cost penalty. It is a smooth step, starting at 75% of\n\t * work_mem, and ending at 125%. We want to effectively disable this plan\n\t * if it doesn't fit into the available memory, so the penalty should be\n\t * comparable to disable_cost but still less than it, so that the\n\t * manual disables still have priority.\n\t */\n\tconst double work_mem_penalty =\n\t\t0.1 * disable_cost *\n\t\tsmoothstep(needed_memory_bytes, 0.75 * work_mem_bytes, 1.25 * work_mem_bytes);\n\tAssert(work_mem_penalty >= 0);\n\n\t/*\n\t * startup_cost is cost before fetching first tuple. Batch sorted merge has\n\t * to load at least the number of batches we expect to be open\n\t * simultaneously, before it can produce the first row.\n\t */\n\tconst double sort_path_cost_for_startup =\n\t\tsort_path.startup_cost +\n\t\t((sort_path.total_cost - sort_path.startup_cost) * (open_batches_clamped / sort_path.rows));\n\tAssert(sort_path_cost_for_startup >= 0);\n\tdcpath->custom_path.path.startup_cost = sort_path_cost_for_startup + work_mem_penalty;\n\n\t/*\n\t * Finally, to run this path to completion, we have to complete the\n\t * underlying sort path, and return all uncompressed rows. Getting one\n\t * uncompressed row involves replacing the top row in the heap, which costs\n\t * O(log(heap size)). The constant multiplier is found empirically by\n\t * benchmarking the queries returning 1 - 1e9 tuples, with segmentby\n\t * cardinality 1 to 1e4, and adjusting the cost so that the fastest plan is\n\t * used. The \"+ 1\" under the logarithm is to avoid zero uncompressed row cost\n\t * when we expect to have only 1 batch open.\n\t */\n\tconst double sort_path_cost_rest = sort_path.total_cost - sort_path_cost_for_startup;\n\tAssert(sort_path_cost_rest >= 0);\n\tconst double uncompressed_row_cost = 1.5 * log(open_batches_clamped + 1) * cpu_tuple_cost;\n\tAssert(uncompressed_row_cost > 0);\n\tdcpath->custom_path.path.total_cost = dcpath->custom_path.path.startup_cost +\n\t\t\t\t\t\t\t\t\t\t  sort_path_cost_rest +\n\t\t\t\t\t\t\t\t\t\t  dcpath->custom_path.path.rows * uncompressed_row_cost;\n\n#if PG18_GE\n\t/* PG18 changes the way we handle disabled nodes so we\n\t * need to take those into account as well.\n\t *\n\t * https://github.com/postgres/postgres/commit/e2225346\n\t */\n\tdcpath->custom_path.path.disabled_nodes = sort_path.disabled_nodes;\n#endif\n}\n\n/*\n * This function adds per-chunk sorted paths for compressed chunks if beneficial. This has two\n * advantages:\n *\n *  (1) Make ChunkAppend possible. If at least one chunk of a hypertable is uncompressed, PostgreSQL\n * will generate a MergeAppend path in generate_orderedappend_paths() / create_merge_append_path()\n * due to the existing pathkeys of the index on the uncompressed chunk. If all chunks are\n * compressed, no path keys are present and no MergeAppend path is generated by PostgreSQL. In that\n * case, the ChunkAppend optimization cannot be used because MergeAppend path can be promoted in\n * ts_chunk_append_path_create(). Adding a sorted path with pathkeys makes ChunkAppend possible for\n * these queries.\n *\n *  (2) Sorting on a per-chunk basis and merging / appending these results could be faster than\n * sorting the whole input. Especially limit queries that use an ORDER BY that is compatible with\n * the partitioning of the hypertable could be inefficiently executed otherwise. For example, an\n * expensive query plan with a sort node on top of the append node could be chosen. Due to the sort\n * node at the high level in the query plan and the missing ChunkAppend node (see (1)), all chunks\n * are decompressed (instead of only the actually needed ones).\n *\n * If existing index pathkeys do not match query pathkeys and sort cannot be pushed down\n * into compressed index, for example \"SELECT * FROM ... ORDER BY (segcol, time DESC, some_col)\" if\n * compressed index is (segcol, time DESC), we should  allow SortPath over (ColumnarScan\n * <- IndexScan) for such cases, i.e. should consider IndexScan compressed paths along with SeqScan\n * compressed paths. IndexScans with useful index conditions can be cheaper than SeqScans.\n *\n * The logic is inspired by PostgreSQL's add_paths_with_pathkeys_for_rel() function.\n *\n * Note: This function adds only non-partial paths. In parallel plans PostgreSQL prefers sorting\n * directly under the gather (merge) node and the per-chunk sorting are not used in parallel plans.\n * To save planning time, we therefore refrain from adding them.\n */\nstatic Path *\nmake_chunk_sorted_path(PlannerInfo *root, RelOptInfo *chunk_rel, Path *path, Path *compressed_path,\n\t\t\t\t\t   const SortInfo *sort_info)\n{\n\t/*\n\t * Don't have a useful sorting after decompression.\n\t */\n\tif (sort_info->decompressed_sort_pathkeys == NIL)\n\t{\n\t\treturn NULL;\n\t}\n\n\tAssert(ts_is_columnar_scan_path(path));\n\n\t/*\n\t * We should be given an unsorted ColumnarScan path.\n\t */\n\tAssert(path->pathkeys == NIL);\n\n\t/*\n\t * Create the sorted path for these useful_pathkeys. Copy the decompress\n\t * chunk path because the original can be recycled in add_path, and our\n\t * sorted path must be independent.\n\t */\n\tColumnarScanPath *path_copy = copy_columnar_scan_path((ColumnarScanPath *) path);\n\n\t/*\n\t * Create the Sort path.\n\t */\n\tPath *sorted_path = (Path *) create_sort_path(root,\n\t\t\t\t\t\t\t\t\t\t\t\t  chunk_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t  (Path *) path_copy,\n\t\t\t\t\t\t\t\t\t\t\t\t  sort_info->decompressed_sort_pathkeys,\n\t\t\t\t\t\t\t\t\t\t\t\t  root->limit_tuples);\n\n\t/* Set in \"create_sort_path\" in PG18GE, have to set separately for PG17LE.\n\t * Need to preserve info for sort over parametrized index paths. */\n\tsorted_path->param_info = path->param_info;\n\n\t/*\n\t * Now, we need another dumb workaround for Postgres problems. When creating\n\t * a sort plan, it performs a linear search of equivalence member of a\n\t * pathkey's equivalence class, that matches the sorted relation (see\n\t * prepare_sort_from_pathkeys()). This is effectively quadratic in the\n\t * number of chunks, and becomes a real CPU sink after we pass 1k chunks.\n\t * Try to reflect this in the costs, because in some cases a chunk-wise sort\n\t * might be avoided, e.g. Limit 1 over MergeAppend over chunk-wise Sort can\n\t * be just as well replaced with a Limit 1 over Sort over Append of chunks,\n\t * that is just marginally costlier.\n\t *\n\t * We can't easily know the number of chunks in the query here, so add some\n\t * startup cost that is quadratic in the current chunk index, which\n\t * hopefully should be a good enough replacement.\n\t */\n\tconst int parent_relindex = bms_next_member(chunk_rel->top_parent_relids, -1);\n\tif (parent_relindex)\n\t{\n\t\tconst int chunk_index = chunk_rel->relid - parent_relindex;\n\t\tsorted_path->startup_cost += cpu_operator_cost * chunk_index * chunk_index;\n\t\tsorted_path->total_cost += cpu_operator_cost * chunk_index * chunk_index;\n\t}\n\n\treturn sorted_path;\n}\n\nstatic List *build_on_single_compressed_path(PlannerInfo *root, const Chunk *chunk,\n\t\t\t\t\t\t\t\t\t\t\t RelOptInfo *chunk_rel, Path *compressed_path,\n\t\t\t\t\t\t\t\t\t\t\t bool add_uncompressed_part,\n\t\t\t\t\t\t\t\t\t\t\t List *uncompressed_table_pathlist,\n\t\t\t\t\t\t\t\t\t\t\t const SortInfo *sort_info,\n\t\t\t\t\t\t\t\t\t\t\t const CompressionInfo *compression_info);\n\nvoid\nts_columnar_scan_generate_paths(PlannerInfo *root, RelOptInfo *chunk_rel, const Hypertable *ht,\n\t\t\t\t\t\t\t\tconst Chunk *chunk)\n{\n\t/*\n\t * For UPDATE/DELETE commands, the executor decompresses and brings the rows into\n\t * the uncompressed chunk. Therefore, it's necessary to add the scan on the\n\t * uncompressed portion.\n\t */\n\tbool add_uncompressed_part = ts_chunk_is_partial(chunk);\n\tif (ts_chunk_is_compressed(chunk) && ts_cm_functions->decompress_target_segments &&\n\t\t!add_uncompressed_part)\n\t{\n\t\tfor (PlannerInfo *proot = root->parent_root; proot != NULL && !add_uncompressed_part;\n\t\t\t proot = proot->parent_root)\n\t\t{\n\t\t\t/*\n\t\t\t * We could additionally check and compare that the relation involved in the subquery\n\t\t\t * and the DML target relation are one and the same. But these kinds of queries\n\t\t\t * should be rare.\n\t\t\t */\n\t\t\tif (proot->parse->commandType == CMD_UPDATE || proot->parse->commandType == CMD_DELETE\n#if PG15_GE\n\t\t\t\t|| proot->parse->commandType == CMD_MERGE\n#endif\n\t\t\t)\n\t\t\t{\n\t\t\t\tadd_uncompressed_part = true;\n\t\t\t}\n\t\t}\n\t}\n\n\tCompressionInfo *compression_info = build_compressioninfo(root, ht, chunk, chunk_rel);\n\n\t/* double check we don't end up here on single chunk queries with ONLY */\n\tAssert(compression_info->chunk_rel->reloptkind == RELOPT_OTHER_MEMBER_REL ||\n\t\t   (compression_info->chunk_rel->reloptkind == RELOPT_BASEREL &&\n\t\t\tts_rte_is_marked_for_expansion(compression_info->chunk_rte)));\n\n\tSortInfo sort_info =\n\t\tbuild_sortinfo(root, chunk, chunk_rel, compression_info, root->query_pathkeys);\n\n\tAssert(chunk->fd.compressed_chunk_id > 0);\n\n\tList *uncompressed_table_pathlist = chunk_rel->pathlist;\n\tList *uncompressed_table_parallel_pathlist = chunk_rel->partial_pathlist;\n\tchunk_rel->pathlist = NIL;\n\tchunk_rel->partial_pathlist = NIL;\n\n\t/* add RangeTblEntry and RelOptInfo for compressed chunk */\n\tcolumnar_scan_add_plannerinfo(root,\n\t\t\t\t\t\t\t\t  compression_info,\n\t\t\t\t\t\t\t\t  chunk,\n\t\t\t\t\t\t\t\t  chunk_rel,\n\t\t\t\t\t\t\t\t  sort_info.needs_sequence_num);\n\n\tif (sort_info.use_compressed_sort)\n\t{\n\t\tsort_info.required_compressed_pathkeys =\n\t\t\tbuild_compressed_scan_pathkeys(&sort_info,\n\t\t\t\t\t\t\t\t\t\t   root,\n\t\t\t\t\t\t\t\t\t\t   root->query_pathkeys,\n\t\t\t\t\t\t\t\t\t\t   compression_info);\n\t}\n\n\tRelOptInfo *compressed_rel = compression_info->compressed_rel;\n\n\tcompressed_rel->consider_parallel = chunk_rel->consider_parallel;\n\n\t/* translate chunk_rel->baserestrictinfo */\n\tif (ts_guc_enable_columnar_scan_filter_pushdown)\n\t{\n\t\tcolumnar_scan_filter_pushdown(root,\n\t\t\t\t\t\t\t\t\t  compression_info->settings,\n\t\t\t\t\t\t\t\t\t  chunk_rel,\n\t\t\t\t\t\t\t\t\t  compressed_rel,\n\t\t\t\t\t\t\t\t\t  add_uncompressed_part);\n\t}\n\t/*\n\t * Estimate the size of the compressed chunk table.\n\t */\n\tset_compressed_baserel_size_estimates(root, compressed_rel, compression_info);\n\n\t/*\n\t * Estimate the size of the compressed batch from Postgres\n\t * statistics.\n\t */\n\tcompression_info->compressed_batch_size =\n\t\tts_columnar_estimate_compressed_batch_size(compression_info->compressed_rte->relid);\n\n\t/*\n\t * Estimate the size of decompressed chunk based on the compressed chunk.\n\t *\n\t * The tuple estimates derived from pg_class will be empty, so we have to\n\t * compute that based on the compressed relation as well. Wrong estimates\n\t * there lead to wrong join order choice and wrong low cost for Sort over\n\t * Append, and also different MergeAppend costs on Postgres before 17 due to\n\t * a bug there.\n\t */\n\tconst double new_row_estimate = compressed_rel->rows * compression_info->compressed_batch_size;\n\tconst double new_tuples_estimate =\n\t\tcompressed_rel->tuples * compression_info->compressed_batch_size;\n\tif (!compression_info->single_chunk)\n\t{\n\t\t/*\n\t\t * Adjust the hypertable estimate by the diff of new and old chunk\n\t\t * estimate.\n\t\t */\n\t\tAppendRelInfo *chunk_info = ts_get_appendrelinfo(root, chunk_rel->relid, false);\n\t\tconst Index ht_relid = chunk_info->parent_relid;\n\t\tRelOptInfo *hypertable_rel = root->simple_rel_array[ht_relid];\n\t\tconst double delta = new_row_estimate - chunk_rel->rows;\n\t\thypertable_rel->rows += delta;\n\t\t/*\n\t\t * For appendrel, set tuples to the same value as rows,\n\t\t * like set_append_rel_size() does.\n\t\t */\n\t\thypertable_rel->tuples += delta;\n\t}\n\tchunk_rel->rows = new_row_estimate;\n\tchunk_rel->tuples = new_tuples_estimate;\n\n\t/*\n\t * Create the paths for the compressed chunk table.\n\t */\n\tcreate_compressed_scan_paths(root, compressed_rel, compression_info, &sort_info);\n\n\t/* create non-parallel paths */\n\tListCell *compressed_cell;\n\tforeach (compressed_cell, compressed_rel->pathlist)\n\t{\n\t\tPath *compressed_path = lfirst(compressed_cell);\n\t\tList *decompressed_paths = build_on_single_compressed_path(root,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   chunk,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   chunk_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   compressed_path,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   add_uncompressed_part,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   uncompressed_table_pathlist,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   &sort_info,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   compression_info);\n\n\t\t/*\n\t\t * We want to consider startup costs so that IndexScan is preferred to\n\t\t * sorted SeqScan when we may have a chance to use SkipScan. We consider\n\t\t * startup costs for LIMIT queries, and SkipScan is basically a\n\t\t * \"LIMIT 1\" query run \"ndistinct\" times. At this point we don't have\n\t\t * all information to check if SkipScan can be used, but we can narrow\n\t\t * it down.\n\t\t */\n\t\tif (!chunk_rel->consider_startup && IsA(compressed_path, IndexPath))\n\t\t{\n\t\t\t/* Candidate for SELECT DISTINCT SkipScan */\n\t\t\tif (list_length(root->distinct_pathkeys) == 1\n\t\t\t\t/* Candidate for DISTINCT aggregate SkipScan */\n\t\t\t\t|| (root->numOrderedAggs >= 1 && list_length(root->group_pathkeys) == 1))\n\t\t\t{\n\t\t\t\tchunk_rel->consider_startup = true;\n\t\t\t}\n\t\t}\n\n\t\t/*\n\t\t * Add the paths to the chunk relation.\n\t\t */\n\t\tListCell *decompressed_cell;\n\t\tforeach (decompressed_cell, decompressed_paths)\n\t\t{\n\t\t\tPath *path = lfirst(decompressed_cell);\n\t\t\tadd_path(chunk_rel, path);\n\t\t}\n\t}\n\n\t/* create parallel paths */\n\tList *uncompressed_paths_with_parallel =\n\t\tlist_concat(uncompressed_table_parallel_pathlist, uncompressed_table_pathlist);\n\tforeach (compressed_cell, compressed_rel->partial_pathlist)\n\t{\n\t\tPath *compressed_path = lfirst(compressed_cell);\n\t\t/* Partial parameterized paths are not supported */\n\t\tAssert(bms_is_empty(PATH_REQ_OUTER(compressed_path)));\n\t\tList *decompressed_paths = build_on_single_compressed_path(root,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   chunk,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   chunk_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   compressed_path,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   add_uncompressed_part,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   uncompressed_paths_with_parallel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   &sort_info,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   compression_info);\n\t\t/*\n\t\t * Add the paths to the chunk relation.\n\t\t */\n\t\tListCell *decompressed_cell;\n\t\tforeach (decompressed_cell, decompressed_paths)\n\t\t{\n\t\t\tPath *path = lfirst(decompressed_cell);\n\t\t\tadd_partial_path(chunk_rel, path);\n\t\t}\n\t}\n\n\t/* the chunk_rel now owns the paths, remove them from the compressed_rel so they can't be freed\n\t * if it's planned */\n\tcompressed_rel->pathlist = NIL;\n\tcompressed_rel->partial_pathlist = NIL;\n\n\t/*\n\t * Remove the compressed_rel from planner arrays to prevent it from being\n\t * referenced again.\n\t */\n\troot->simple_rel_array[compressed_rel->relid] = NULL;\n\troot->append_rel_array[compressed_rel->relid] = NULL;\n\n\t/* We should never get in the situation with no viable paths. */\n\tEnsure(chunk_rel->pathlist, \"could not create decompression path\");\n}\n\n/*\n * Add various decompression paths that are possible based on the given\n * compressed path.\n */\nstatic List *\nbuild_on_single_compressed_path(PlannerInfo *root, const Chunk *chunk, RelOptInfo *chunk_rel,\n\t\t\t\t\t\t\t\tPath *compressed_path, bool add_uncompressed_part,\n\t\t\t\t\t\t\t\tList *uncompressed_table_pathlist, const SortInfo *sort_info,\n\t\t\t\t\t\t\t\tconst CompressionInfo *compression_info)\n{\n\t/*\n\t * We skip any BitmapScan parameterized paths here as supporting\n\t * those would require fixing up the internal scan. Since we\n\t * currently do not do this BitmapScans would be generated\n\t * when we have a parameterized path on a compressed column\n\t * that would have invalid references due to our\n\t * EquivalenceClasses.\n\t */\n\tif (IsA(compressed_path, BitmapHeapPath) && compressed_path->param_info)\n\t\treturn NIL;\n\n\t/*\n\t * Filter out all paths that try to JOIN the compressed chunk on the\n\t * hypertable or the uncompressed chunk\n\t * Ideally, we wouldn't create these paths in the first place.\n\t * However, create_join_clause code is called by PG while generating paths for the\n\t * compressed_rel via generate_implied_equalities_for_column.\n\t * create_join_clause ends up creating rinfo's between compressed_rel and ht because\n\t * PG does not know that compressed_rel is related to ht in anyway.\n\t * The parent-child relationship between chunk_rel and ht is known\n\t * to PG and so it does not try to create meaningless rinfos for that case.\n\t */\n\tif (compressed_path->param_info != NULL)\n\t{\n\t\tif (bms_is_member(chunk_rel->relid, compressed_path->param_info->ppi_req_outer))\n\t\t\treturn NIL;\n\n\t\t/* check if this is path made with references between\n\t\t * compressed_rel + hypertable or a nesting subquery.\n\t\t * The latter can happen in the case of UNION queries. see github 2917. This\n\t\t * happens since PG is not aware that the nesting\n\t\t * subquery that references the hypertable is a parent of compressed_rel as well.\n\t\t */\n\t\tif (bms_overlap(compression_info->parent_relids,\n\t\t\t\t\t\tcompressed_path->param_info->ppi_req_outer))\n\t\t{\n\t\t\treturn NIL;\n\t\t}\n\t}\n\n\tPath *chunk_path_no_sort =\n\t\t(Path *) columnar_scan_path_create(root, compression_info, compressed_path);\n\tList *decompressed_paths = list_make1(chunk_path_no_sort);\n\n\t/*\n\t * Create a path for the batch sorted merge optimization. This optimization\n\t * performs a sorted merge of the involved batches by using a binary heap\n\t * and preserving the compression order. This optimization is only\n\t * considered if we can't push down the sort to the compressed chunk. If we\n\t * can push down the sort, the batches can be directly consumed in this\n\t * order and we don't need to use this optimization.\n\t */\n\tif (sort_info->use_batch_sorted_merge && ts_guc_enable_decompression_sorted_merge)\n\t{\n\t\tAssert(!sort_info->use_compressed_sort);\n\n\t\tColumnarScanPath *path_copy =\n\t\t\tcopy_columnar_scan_path((ColumnarScanPath *) chunk_path_no_sort);\n\n\t\tpath_copy->reverse = sort_info->reverse;\n\t\tpath_copy->batch_sorted_merge = true;\n\n\t\t/*\n\t\t * The segment by optimization is only enabled if it can deliver the tuples in the\n\t\t * same order as the query requested it. So, we can just copy the pathkeys of the\n\t\t * query here.\n\t\t */\n\t\tpath_copy->custom_path.path.pathkeys = sort_info->decompressed_sort_pathkeys;\n\t\tcost_batch_sorted_merge(root, compression_info, path_copy, compressed_path);\n\n\t\tif (ts_guc_debug_require_batch_sorted_merge == DRO_Force)\n\t\t{\n\t\t\tpath_copy->custom_path.path.startup_cost = cpu_tuple_cost;\n\t\t\tpath_copy->custom_path.path.total_cost = 2 * cpu_tuple_cost;\n\t\t}\n\n\t\tdecompressed_paths = lappend(decompressed_paths, path_copy);\n\t}\n\telse if (ts_guc_debug_require_batch_sorted_merge == DRO_Require ||\n\t\t\t ts_guc_debug_require_batch_sorted_merge == DRO_Force)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"debug: batch sorted merge is required but not possible at planning \"\n\t\t\t\t\t\t\"time\")));\n\t}\n\n\t/*\n\t * If we can push down the sort below the ColumnarScan node, we set the\n\t * pathkeys of the decompress node to the decompressed_sort_pathkeys. We\n\t * will determine whether to put an actual sort between the decompression\n\t * node and the scan during plan creation.\n\t */\n\tif (sort_info->use_compressed_sort)\n\t{\n\t\tif (pathkeys_contained_in(sort_info->required_compressed_pathkeys,\n\t\t\t\t\t\t\t\t  compressed_path->pathkeys))\n\t\t{\n\t\t\t/*\n\t\t\t * The compressed path already has the required ordering. Modify\n\t\t\t * in place the no-sorting path we just created above.\n\t\t\t */\n\t\t\tColumnarScanPath *path = (ColumnarScanPath *) chunk_path_no_sort;\n\t\t\tpath->reverse = sort_info->reverse;\n\t\t\tpath->needs_sequence_num = sort_info->needs_sequence_num;\n\t\t\tpath->required_compressed_pathkeys = sort_info->required_compressed_pathkeys;\n\t\t\tpath->custom_path.path.pathkeys = sort_info->decompressed_sort_pathkeys;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/*\n\t\t\t * We must sort the underlying compressed path to get the\n\t\t\t * required ordering. Make a copy of no-sorting path and modify\n\t\t\t * it accordingly\n\t\t\t */\n\t\t\tColumnarScanPath *path_copy =\n\t\t\t\tcopy_columnar_scan_path((ColumnarScanPath *) chunk_path_no_sort);\n\t\t\tpath_copy->reverse = sort_info->reverse;\n\t\t\tpath_copy->needs_sequence_num = sort_info->needs_sequence_num;\n\t\t\tpath_copy->required_compressed_pathkeys = sort_info->required_compressed_pathkeys;\n\t\t\tpath_copy->custom_path.path.pathkeys = sort_info->decompressed_sort_pathkeys;\n\n\t\t\t/*\n\t\t\t * Add costing for a sort. The standard Postgres pattern is to add the cost during\n\t\t\t * path creation, but not add the sort path itself, that's done during plan\n\t\t\t * creation. Examples of this in: create_merge_append_path &\n\t\t\t * create_merge_append_plan\n\t\t\t */\n\t\t\tPath sort_path; /* dummy for result of cost_sort */\n\n\t\t\tcost_sort(&sort_path,\n\t\t\t\t\t  root,\n\t\t\t\t\t  sort_info->required_compressed_pathkeys,\n#if PG18_GE\n\t\t\t\t\t  compressed_path->disabled_nodes,\n#endif\n\t\t\t\t\t  compressed_path->total_cost,\n\t\t\t\t\t  compressed_path->rows,\n\t\t\t\t\t  compressed_path->pathtarget->width,\n\t\t\t\t\t  0.0,\n\t\t\t\t\t  work_mem,\n\t\t\t\t\t  -1);\n\n\t\t\tcost_columnar_scan(root, compression_info, &path_copy->custom_path.path, &sort_path);\n\n\t\t\tdecompressed_paths = lappend(decompressed_paths, path_copy);\n\t\t}\n\t}\n\n\t/*\n\t * Also try explicit sort after decompression, if we couldn't push down the\n\t * sort. Don't do this for parallel plans, because in this case it is\n\t * typically done with Sort under Gather node. This splits the Sort in\n\t * per-worker buckets, so splitting the buckets further per-chunk is less\n\t * important.\n\t */\n\tif (!sort_info->use_compressed_sort && chunk_path_no_sort->parallel_workers == 0)\n\t{\n\t\tPath *sort_above_chunk =\n\t\t\tmake_chunk_sorted_path(root, chunk_rel, chunk_path_no_sort, compressed_path, sort_info);\n\t\tif (sort_above_chunk != NULL)\n\t\t{\n\t\t\tdecompressed_paths = lappend(decompressed_paths, sort_above_chunk);\n\t\t}\n\t}\n\n\tif (!add_uncompressed_part)\n\t{\n\t\t/*\n\t\t * If the chunk has only the compressed part, we're done.\n\t\t */\n\t\treturn decompressed_paths;\n\t}\n\n\t/*\n\t * This is a partially compressed chunk, we have to combine data from\n\t * compressed and uncompressed chunk.\n\t */\n\tList *combined_paths = NIL;\n\n\t/*\n\t * All decompressed paths we've built have the same parameterization since\n\t * we're building on a single compressed path. We only inherit the\n\t * parameterization from it and don't add our own.\n\t */\n\tBitmapset *req_outer = PATH_REQ_OUTER(chunk_path_no_sort);\n\n\t/*\n\t * Look up the uncompressed chunk paths. We might need an unordered path\n\t * (SeqScan) and an ordered path (e.g. IndexScan).\n\t */\n\tPath *unordered_uncompressed_path = get_cheapest_path_for_pathkeys(uncompressed_table_pathlist,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   NIL,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   req_outer,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   TOTAL_COST,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   false);\n\tEnsure(unordered_uncompressed_path != NULL,\n\t\t   \"couldn't find a scan path for uncompressed chunk table\");\n\n\tPath *ordered_uncompressed_path = NULL;\n\tif (sort_info->decompressed_sort_pathkeys != NIL)\n\t{\n\t\tordered_uncompressed_path =\n\t\t\tget_cheapest_path_for_pathkeys(uncompressed_table_pathlist,\n\t\t\t\t\t\t\t\t\t\t   sort_info->decompressed_sort_pathkeys,\n\t\t\t\t\t\t\t\t\t\t   req_outer,\n\t\t\t\t\t\t\t\t\t\t   TOTAL_COST,\n\t\t\t\t\t\t\t\t\t\t   false);\n\t}\n\n\t/*\n\t * All children of an append path are required to have the same parameterization\n\t * so we reparameterize here when we couldn't get a path with the parameterization\n\t * we need. Reparameterization should always succeed here since uncompressed_path\n\t * should always be a scan.\n\t */\n\tif (!bms_equal(req_outer, PATH_REQ_OUTER(unordered_uncompressed_path)))\n\t{\n\t\tunordered_uncompressed_path =\n\t\t\treparameterize_path(root, unordered_uncompressed_path, req_outer, 1.0);\n\t\tEnsure(unordered_uncompressed_path != NULL,\n\t\t\t   \"couldn't reparameterize a scan path for uncompressed chunk table\");\n\t}\n\tif (ordered_uncompressed_path != NULL &&\n\t\t!bms_equal(req_outer, PATH_REQ_OUTER(ordered_uncompressed_path)))\n\t{\n\t\tordered_uncompressed_path =\n\t\t\treparameterize_path(root, ordered_uncompressed_path, req_outer, 1.0);\n\t}\n\n\t/*\n\t * Create plain Append, potentially parallel. It only makes sense for the\n\t * unsorted input paths.\n\t */\n\t{\n\t\tconst int workers = Max(chunk_path_no_sort->parallel_workers,\n\t\t\t\t\t\t\t\tunordered_uncompressed_path->parallel_workers);\n\n\t\tList *parallel_paths = NIL;\n\t\tList *sequential_paths = NIL;\n\n\t\tif (chunk_path_no_sort->parallel_workers > 0)\n\t\t{\n\t\t\tparallel_paths = lappend(parallel_paths, chunk_path_no_sort);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tsequential_paths = lappend(sequential_paths, chunk_path_no_sort);\n\t\t}\n\n\t\tif (unordered_uncompressed_path->parallel_workers > 0)\n\t\t{\n\t\t\tparallel_paths = lappend(parallel_paths, unordered_uncompressed_path);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tsequential_paths = lappend(sequential_paths, unordered_uncompressed_path);\n\t\t}\n\n\t\tPath *plain_append = (Path *) create_append_path(root,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t chunk_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t sequential_paths,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t parallel_paths,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t /* pathkeys = */ NIL,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t req_outer,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t workers,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t workers > 0,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t chunk_path_no_sort->rows +\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t unordered_uncompressed_path->rows);\n\n\t\tcombined_paths = lappend(combined_paths, plain_append);\n\t}\n\n\tif (sort_info->decompressed_sort_pathkeys == NIL)\n\t{\n\t\t/*\n\t\t * No sorting requested, so we're done after creating the plain Append\n\t\t * above.\n\t\t */\n\t\treturn combined_paths;\n\t}\n\n\t/*\n\t * We require sorting, try MergeAppend.\n\t */\n\tPath *uncompressed_path_for_merge = ordered_uncompressed_path;\n\tif (uncompressed_path_for_merge == NULL || IsA(uncompressed_path_for_merge, SortPath))\n\t{\n\t\t/*\n\t\t * Don't use explicit Sort as MergeAppend child, because the\n\t\t * MergeAppend adds the required sorting anyway. With the explicit\n\t\t * Sort it still works but performs the pathkey lookups twice, which\n\t\t * leads to planning performance regression.\n\t\t */\n\t\tuncompressed_path_for_merge = unordered_uncompressed_path;\n\t}\n\tif (uncompressed_path_for_merge->parallel_workers > 0)\n\t{\n\t\t/*\n\t\t * MergeAppend can't be parallel.\n\t\t */\n\t\treturn combined_paths;\n\t}\n\n\t/*\n\t * For Merge Append, we consider:\n\t * 1) explicit sorting over decompressed path,\n\t * 2) compressed sort pushdown path,\n\t * 3) batch sorted merge path.\n\t * We have to make a cost-based decision between them (i.e. batch sorted\n\t * merge might be more expensive due to memory requirements).\n\t */\n\tListCell *lc;\n\tforeach (lc, decompressed_paths)\n\t{\n\t\tPath *decompression_path = lfirst(lc);\n\t\tif (decompression_path->parallel_workers > 0)\n\t\t{\n\t\t\t/*\n\t\t\t * MergeAppend can't be parallel.\n\t\t\t */\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (decompression_path == chunk_path_no_sort)\n\t\t{\n\t\t\t/*\n\t\t\t * We can't use the unsorted decompression path directly because it\n\t\t\t * doesn't have the sort projection cost workaround.\n\t\t\t */\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!bms_is_empty(chunk_rel->lateral_relids) || !bms_is_empty(req_outer))\n\t\t{\n\t\t\t/*\n\t\t\t * Parametrized MergeAppend paths are not supported.\n\t\t\t */\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (IsA(decompression_path, SortPath))\n\t\t{\n\t\t\t/*\n\t\t\t * We have to remove the explicit Sort, otherwise it will lead to\n\t\t\t * planning time regression because of double call of\n\t\t\t * prepare_sort_from_pathkeys() in MergeAppend plan creation. Still,\n\t\t\t * we have to use the copy of ColumnarScan path that we created\n\t\t\t * for explicit sorting, because it has the sort projection cost\n\t\t\t * workaround.\n\t\t\t */\n\t\t\tdecompression_path = castNode(SortPath, decompression_path)->subpath;\n\t\t}\n\n\t\tPath *merge_append =\n\t\t\t(Path *) create_merge_append_path(root,\n\t\t\t\t\t\t\t\t\t\t\t  chunk_rel,\n\t\t\t\t\t\t\t\t\t\t\t  list_make2(decompression_path,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t uncompressed_path_for_merge),\n\t\t\t\t\t\t\t\t\t\t\t  sort_info->decompressed_sort_pathkeys,\n\t\t\t\t\t\t\t\t\t\t\t  req_outer);\n\t\tcombined_paths = lappend(combined_paths, merge_append);\n\t}\n\n\treturn combined_paths;\n}\n\n/*\n * Add a var for a particular column to the reltarget. attrs_used is a bitmap\n * of which columns we already have in reltarget. We do not add the columns that\n * are already there, and update it after adding something.\n */\nstatic void\ncompressed_reltarget_add_var_for_column(RelOptInfo *compressed_rel, Oid compressed_relid,\n\t\t\t\t\t\t\t\t\t\tconst char *column_name, Bitmapset **attrs_used)\n{\n\tAttrNumber attnum = get_attnum(compressed_relid, column_name);\n\tAssert(attnum > 0);\n\n\tif (bms_is_member(attnum, *attrs_used))\n\t{\n\t\t/* This column is already in reltarget, we don't need duplicates. */\n\t\treturn;\n\t}\n\n\t*attrs_used = bms_add_member(*attrs_used, attnum);\n\n\tOid typid, collid;\n\tint32 typmod;\n\tget_atttypetypmodcoll(compressed_relid, attnum, &typid, &typmod, &collid);\n\tcompressed_rel->reltarget->exprs =\n\t\tlappend(compressed_rel->reltarget->exprs,\n\t\t\t\tmakeVar(compressed_rel->relid, attnum, typid, typmod, collid, 0));\n}\n\n/* copy over the vars from the chunk_rel->reltarget to the compressed_rel->reltarget\n * altering the fields that need it\n */\nstatic void\ncompressed_rel_setup_reltarget(RelOptInfo *compressed_rel, CompressionInfo *info,\n\t\t\t\t\t\t\t   bool needs_sequence_num)\n{\n\tbool have_whole_row_var = false;\n\tBitmapset *attrs_used = NULL;\n\n\tOid compressed_relid = info->compressed_rte->relid;\n\n\t/*\n\t * We have to decompress three kinds of columns:\n\t * 1) output targetlist of the relation,\n\t * 2) columns required for the quals (WHERE),\n\t * 3) columns required for joins.\n\t */\n\tList *exprs = list_copy(info->chunk_rel->reltarget->exprs);\n\tListCell *lc;\n\tforeach (lc, info->chunk_rel->baserestrictinfo)\n\t{\n\t\texprs = lappend(exprs, ((RestrictInfo *) lfirst(lc))->clause);\n\t}\n\tforeach (lc, info->chunk_rel->joininfo)\n\t{\n\t\texprs = lappend(exprs, ((RestrictInfo *) lfirst(lc))->clause);\n\t}\n\n\t/*\n\t * Now go over the required expressions we prepared above, and add the\n\t * required columns to the compressed reltarget.\n\t */\n\tinfo->compressed_rel->reltarget->exprs = NIL;\n\tforeach (lc, exprs)\n\t{\n\t\tListCell *lc2;\n\t\tList *chunk_vars = pull_var_clause(lfirst(lc), PVC_RECURSE_PLACEHOLDERS);\n\t\tforeach (lc2, chunk_vars)\n\t\t{\n\t\t\tchar *column_name;\n\t\t\tVar *chunk_var = castNode(Var, lfirst(lc2));\n\n\t\t\t/* skip vars that aren't from the uncompressed chunk */\n\t\t\tif ((Index) chunk_var->varno != info->chunk_rel->relid)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * If there's a system column or whole-row reference, add a whole-\n\t\t\t * row reference, and we're done.\n\t\t\t */\n\t\t\tif (chunk_var->varattno <= 0)\n\t\t\t{\n\t\t\t\thave_whole_row_var = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tcolumn_name = get_attname(info->chunk_rte->relid, chunk_var->varattno, false);\n\t\t\tcompressed_reltarget_add_var_for_column(compressed_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\tcompressed_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\tcolumn_name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t&attrs_used);\n\n\t\t\t/* if the column is an orderby, add it's metadata columns too */\n\t\t\tint16 index = ts_array_position(info->settings->fd.orderby, column_name);\n\t\t\tif (index != 0)\n\t\t\t{\n\t\t\t\tcompressed_reltarget_add_var_for_column(compressed_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcompressed_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcolumn_segment_min_name(index),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t&attrs_used);\n\t\t\t\tcompressed_reltarget_add_var_for_column(compressed_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcompressed_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcolumn_segment_max_name(index),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t&attrs_used);\n\t\t\t}\n\t\t}\n\t}\n\n\t/* always add the count column */\n\tcompressed_reltarget_add_var_for_column(compressed_rel,\n\t\t\t\t\t\t\t\t\t\t\tcompressed_relid,\n\t\t\t\t\t\t\t\t\t\t\tCOMPRESSION_COLUMN_METADATA_COUNT_NAME,\n\t\t\t\t\t\t\t\t\t\t\t&attrs_used);\n\n\t/* add the sequence number or orderby metadata columns if we try to order by them*/\n\tif (needs_sequence_num)\n\t{\n\t\tif (info->has_seq_num)\n\t\t{\n\t\t\tcompressed_reltarget_add_var_for_column(compressed_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\tcompressed_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\tCOMPRESSION_COLUMN_METADATA_SEQUENCE_NUM_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t\t&attrs_used);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfor (int i = 1; i <= ts_array_length(info->settings->fd.orderby); i++)\n\t\t\t{\n\t\t\t\tcompressed_reltarget_add_var_for_column(compressed_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcompressed_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcolumn_segment_min_name(i),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t&attrs_used);\n\t\t\t\tcompressed_reltarget_add_var_for_column(compressed_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcompressed_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcolumn_segment_max_name(i),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t&attrs_used);\n\t\t\t}\n\t\t}\n\t}\n\n\t/*\n\t * It doesn't make sense to request a whole-row var from the compressed\n\t * chunk scan. If it is requested, just fetch the rest of columns. The\n\t * whole-row var will be created by the projection of ColumnarScan node.\n\t */\n\tif (have_whole_row_var)\n\t{\n\t\tfor (int i = 1; i <= info->chunk_rel->max_attr; i++)\n\t\t{\n\t\t\tchar *column_name = get_attname(info->chunk_rte->relid,\n\t\t\t\t\t\t\t\t\t\t\ti,\n\t\t\t\t\t\t\t\t\t\t\t/* missing_ok = */ false);\n\t\t\tAttrNumber chunk_attno = get_attnum(info->chunk_rte->relid, column_name);\n\t\t\tif (chunk_attno == InvalidAttrNumber)\n\t\t\t{\n\t\t\t\t/* Skip the dropped column. */\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tAttrNumber compressed_attno = get_attnum(info->compressed_rte->relid, column_name);\n\t\t\tif (compressed_attno == InvalidAttrNumber)\n\t\t\t{\n\t\t\t\telog(ERROR,\n\t\t\t\t\t \"column '%s' not found in the compressed chunk '%s'\",\n\t\t\t\t\t column_name,\n\t\t\t\t\t get_rel_name(info->compressed_rte->relid));\n\t\t\t}\n\n\t\t\tif (bms_is_member(compressed_attno, attrs_used))\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tcompressed_reltarget_add_var_for_column(compressed_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\tcompressed_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\tcolumn_name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t&attrs_used);\n\t\t}\n\t}\n}\n\nstatic Bitmapset *\ncolumnar_scan_adjust_child_relids(Bitmapset *src, int chunk_relid, int compressed_chunk_relid)\n{\n\tBitmapset *result = NULL;\n\tif (src != NULL)\n\t{\n\t\tresult = bms_copy(src);\n\t\tresult = bms_del_member(result, chunk_relid);\n\t\tresult = bms_add_member(result, compressed_chunk_relid);\n\t}\n\treturn result;\n}\n\n/* based on adjust_appendrel_attrs_mutator handling of RestrictInfo */\nstatic Node *\nchunk_joininfo_mutator(Node *node, CompressionInfo *context)\n{\n\tif (node == NULL)\n\t\treturn NULL;\n\n\tif (IsA(node, Var))\n\t{\n\t\tVar *var = castNode(Var, node);\n\t\tVar *compress_var = copyObject(var);\n\t\tchar *column_name;\n\t\tAttrNumber compressed_attno;\n\t\tif ((Index) var->varno != context->chunk_rel->relid)\n\t\t\treturn (Node *) var;\n\n\t\tcolumn_name = get_attname(context->chunk_rte->relid, var->varattno, false);\n\t\tcompressed_attno = get_attnum(context->compressed_rte->relid, column_name);\n\t\tcompress_var->varno = context->compressed_rel->relid;\n\t\tcompress_var->varattno = compressed_attno;\n\n\t\treturn (Node *) compress_var;\n\t}\n\telse if (IsA(node, RestrictInfo))\n\t{\n\t\tRestrictInfo *oldinfo = (RestrictInfo *) node;\n\t\tRestrictInfo *newinfo = makeNode(RestrictInfo);\n\n\t\t/* Copy all flat-copiable fields */\n\t\tmemcpy(newinfo, oldinfo, sizeof(RestrictInfo));\n\n\t\t/* Recursively fix the clause itself */\n\t\tnewinfo->clause = (Expr *) chunk_joininfo_mutator((Node *) oldinfo->clause, context);\n\n\t\t/* and the modified version, if an OR clause */\n\t\tnewinfo->orclause = (Expr *) chunk_joininfo_mutator((Node *) oldinfo->orclause, context);\n\n\t\t/* adjust relid sets too */\n\t\tnewinfo->clause_relids = columnar_scan_adjust_child_relids(oldinfo->clause_relids,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   context->chunk_rel->relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   context->compressed_rel->relid);\n\t\tnewinfo->required_relids =\n\t\t\tcolumnar_scan_adjust_child_relids(oldinfo->required_relids,\n\t\t\t\t\t\t\t\t\t\t\t  context->chunk_rel->relid,\n\t\t\t\t\t\t\t\t\t\t\t  context->compressed_rel->relid);\n\t\tnewinfo->outer_relids = columnar_scan_adjust_child_relids(oldinfo->outer_relids,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  context->chunk_rel->relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  context->compressed_rel->relid);\n#if PG16_LT\n\t\tnewinfo->nullable_relids =\n\t\t\tcolumnar_scan_adjust_child_relids(oldinfo->nullable_relids,\n\t\t\t\t\t\t\t\t\t\t\t  context->chunk_rel->relid,\n\t\t\t\t\t\t\t\t\t\t\t  context->compressed_rel->relid);\n#endif\n\t\tnewinfo->left_relids = columnar_scan_adjust_child_relids(oldinfo->left_relids,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t context->chunk_rel->relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t context->compressed_rel->relid);\n\t\tnewinfo->right_relids = columnar_scan_adjust_child_relids(oldinfo->right_relids,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  context->chunk_rel->relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  context->compressed_rel->relid);\n\n\t\tnewinfo->eval_cost.startup = -1;\n\t\tnewinfo->norm_selec = -1;\n\t\tnewinfo->outer_selec = -1;\n\t\tnewinfo->left_em = NULL;\n\t\tnewinfo->right_em = NULL;\n\t\tnewinfo->scansel_cache = NIL;\n\t\tnewinfo->left_bucketsize = -1;\n\t\tnewinfo->right_bucketsize = -1;\n\t\tnewinfo->left_mcvfreq = -1;\n\t\tnewinfo->right_mcvfreq = -1;\n\t\treturn (Node *) newinfo;\n\t}\n\treturn expression_tree_mutator(node, chunk_joininfo_mutator, context);\n}\n\n/* Check if the expression references a compressed column in compressed chunk. */\nstatic bool\nhas_compressed_vars_walker(Node *node, CompressionInfo *info)\n{\n\tif (node == NULL)\n\t{\n\t\treturn false;\n\t}\n\n\tif (IsA(node, Var))\n\t{\n\t\tVar *var = castNode(Var, node);\n\t\tif ((Index) var->varno != info->compressed_rel->relid)\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tif (var->varattno <= 0)\n\t\t{\n\t\t\t/*\n\t\t\t * Shouldn't see a system var here, might be a whole row var?\n\t\t\t * In any case, we can't push it down to the compressed scan level.\n\t\t\t */\n\t\t\treturn true;\n\t\t}\n\n\t\tif (bms_is_member(var->varattno, info->compressed_attnos_in_compressed_chunk))\n\t\t{\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\treturn expression_tree_walker(node, has_compressed_vars_walker, info);\n}\n\nstatic bool\nhas_compressed_vars(RestrictInfo *ri, CompressionInfo *info)\n{\n\treturn expression_tree_walker((Node *) ri->clause, has_compressed_vars_walker, info);\n}\n\n/* translate chunk_rel->joininfo for compressed_rel\n * this is necessary for create_index_path which gets join clauses from\n * rel->joininfo and sets up parameterized paths (in rel->ppilist).\n * ppi_clauses is finally used to add any additional filters on the\n * indexpath when creating a plan in create_indexscan_plan.\n * Otherwise we miss additional filters that need to be applied after\n * the index plan is executed (github issue 1558)\n */\nstatic void\ncompressed_rel_setup_joininfo(RelOptInfo *compressed_rel, CompressionInfo *info)\n{\n\tRelOptInfo *chunk_rel = info->chunk_rel;\n\tListCell *lc;\n\tList *compress_joininfo = NIL;\n\tforeach (lc, chunk_rel->joininfo)\n\t{\n\t\tRestrictInfo *ri = (RestrictInfo *) lfirst(lc);\n\n\t\tRestrictInfo *adjusted = (RestrictInfo *) chunk_joininfo_mutator((Node *) ri, info);\n\t\tAssert(IsA(adjusted, RestrictInfo));\n\n\t\tif (has_compressed_vars(adjusted, info))\n\t\t{\n\t\t\t/*\n\t\t\t * We can't check clauses that refer to compressed columns during\n\t\t\t * the compressed scan.\n\t\t\t */\n\t\t\tcontinue;\n\t\t}\n\n\t\tcompress_joininfo = lappend(compress_joininfo, adjusted);\n\t}\n\tcompressed_rel->joininfo = compress_joininfo;\n}\n\ntypedef struct EMCreationContext\n{\n\tOid uncompressed_relid;\n\tOid compressed_relid;\n\tIndex uncompressed_relid_idx;\n\tIndex compressed_relid_idx;\n\tCompressionSettings *settings;\n} EMCreationContext;\n\nstatic Node *\ncreate_var_for_compressed_equivalence_member(Var *var, const EMCreationContext *context,\n\t\t\t\t\t\t\t\t\t\t\t const char *attname)\n{\n\t/* based on adjust_appendrel_attrs_mutator */\n\tAssert((Index) var->varno == context->uncompressed_relid_idx);\n\tAssert(var->varattno > 0);\n\n\tvar = (Var *) copyObject(var);\n\n\tif (var->varlevelsup == 0)\n\t{\n\t\tvar->varno = context->compressed_relid_idx;\n\t\tvar->varattno = get_attnum(context->compressed_relid, attname);\n\t\tvar->varnosyn = var->varno;\n\t\tvar->varattnosyn = var->varattno;\n\n\t\treturn (Node *) var;\n\t}\n\n\treturn NULL;\n}\n\n/* This function is inspired by the Postgres add_child_rel_equivalences. */\nstatic bool\nadd_segmentby_to_equivalence_class(PlannerInfo *root, EquivalenceClass *cur_ec,\n\t\t\t\t\t\t\t\t   CompressionInfo *info, EMCreationContext *context)\n{\n\tTimescaleDBPrivate *compressed_fdw_private =\n\t\t(TimescaleDBPrivate *) info->compressed_rel->fdw_private;\n\tAssert(compressed_fdw_private != NULL);\n\n\tEquivalenceMember *cur_em;\n#if PG18_GE\n\t/* Use specialized iterator to include child ems.\n\t *\n\t * https://github.com/postgres/postgres/commit/d69d45a5\n\t */\n\tEquivalenceMemberIterator it;\n\n\tsetup_eclass_member_iterator(&it, cur_ec, bms_make_singleton(info->chunk_rel->relid));\n\twhile ((cur_em = eclass_member_iterator_next(&it)) != NULL)\n\t{\n#else\n\tListCell *lc;\n\tforeach (lc, cur_ec->ec_members)\n\t{\n\t\tcur_em = (EquivalenceMember *) lfirst(lc);\n#endif\n\t\tNode *node;\n\t\tExpr *child_expr;\n\t\tRelids new_relids;\n\t\tVar *var;\n\t\tAssert(!bms_overlap(cur_em->em_relids, info->compressed_rel->relids));\n\n\t\t/* only consider EquivalenceMembers that are Vars, possibly with RelabelType, of the\n\t\t * uncompressed chunk */\n\t\tnode = strip_implicit_coercions((Node *) cur_em->em_expr);\n\t\tif (!(node && IsA(node, Var)))\n\t\t\tcontinue;\n\t\tvar = castNode(Var, node);\n\n\t\t/*\n\t\t * We want to base our equivalence member on the hypertable equivalence\n\t\t * member, not on the uncompressed chunk one. We can't just check for\n\t\t * em_is_child though because the hypertable might be a child itself and not\n\t\t * a top-level EquivalenceMember. This is mostly relevant for PG16+ where\n\t\t * we have to specify a parent for the newly created equivalence member.\n\t\t */\n\t\tif ((Index) var->varno != info->ht_rel->relid)\n\t\t\tcontinue;\n\n\t\tif (var->varattno <= 0)\n\t\t{\n\t\t\t/*\n\t\t\t * We can have equivalence members that refer to special variables,\n\t\t\t * but these variables can't be segmentby, so we're not interested\n\t\t\t * in them here.\n\t\t\t */\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* given that the em is a var of the uncompressed chunk, the relid of the chunk should\n\t\t * be set on the em */\n\t\tAssert(bms_is_member(info->ht_rel->relid, cur_em->em_relids));\n\t\tAssert(OidIsValid(info->ht_rte->relid));\n\n\t\tconst char *attname = get_attname(info->ht_rte->relid, var->varattno, false);\n\n\t\tif (!ts_array_is_member(context->settings->fd.segmentby, attname))\n\t\t\tcontinue;\n\n\t\tchild_expr = (Expr *) create_var_for_compressed_equivalence_member(var, context, attname);\n\t\tif (child_expr == NULL)\n\t\t\tcontinue;\n\n\t\t/* #8681: coerce compressed var to current equivalence member type/collation,\n\t\t *  in case we dug the \"cur_em->em_expr\" var from under RelabelTypes\n\t\t */\n\t\tchild_expr =\n\t\t\tcanonicalize_ec_expression(child_expr, cur_em->em_datatype, cur_ec->ec_collation);\n\n\t\t/*\n\t\t * Transform em_relids to match.  Note we do *not* do\n\t\t * pull_varnos(child_expr) here, as for example the\n\t\t * transformation might have substituted a constant, but we\n\t\t * don't want the child member to be marked as constant.\n\t\t */\n\t\tnew_relids = bms_copy(cur_em->em_relids);\n\t\tnew_relids = bms_del_member(new_relids, info->ht_rel->relid);\n\t\tnew_relids = bms_add_members(new_relids, info->compressed_rel->relids);\n\n\t\t/* copied from add_eq_member */\n\t\t{\n\t\t\tEquivalenceMember *em = makeNode(EquivalenceMember);\n\n\t\t\tem->em_expr = child_expr;\n\t\t\tem->em_relids = new_relids;\n\t\t\tem->em_is_const = false;\n\t\t\tem->em_is_child = true;\n\t\t\tem->em_datatype = cur_em->em_datatype;\n#if PG16_GE\n\t\t\tem->em_jdomain = cur_em->em_jdomain;\n\t\t\tem->em_parent = cur_em;\n#endif\n\n#if PG16_LT\n\t\t\t/*\n\t\t\t * For versions less than PG16, transform and set em_nullable_relids similar to\n\t\t\t * em_relids. Note that this code assumes parent and child relids are singletons.\n\t\t\t */\n\t\t\tRelids new_nullable_relids = cur_em->em_nullable_relids;\n\t\t\tif (bms_is_member(info->ht_rel->relid, new_nullable_relids))\n\t\t\t{\n\t\t\t\tnew_nullable_relids = bms_copy(new_nullable_relids);\n\t\t\t\tnew_nullable_relids = bms_del_member(new_nullable_relids, info->ht_rel->relid);\n\t\t\t\tnew_nullable_relids =\n\t\t\t\t\tbms_add_members(new_nullable_relids, info->compressed_rel->relids);\n\t\t\t}\n\t\t\tem->em_nullable_relids = new_nullable_relids;\n#endif\n\n\t\t\t/*\n\t\t\t * In some cases the new EC member is likely to be accessed soon, so\n\t\t\t * it would make sense to add it to the front, but we cannot do that\n\t\t\t * here. If we do that, the compressed chunk EM might get picked as\n\t\t\t * SortGroupExpr by cost_incremental_sort, and estimate_num_groups\n\t\t\t * will assert that the rel is simple rel, but it will fail because\n\t\t\t * the compressed chunk rel is a deadrel. Anyway, it wouldn't make\n\t\t\t * sense to estimate the group numbers by one append member,\n\t\t\t * probably Postgres expects to see the parent relation first in the\n\t\t\t * EMs.\n\t\t\t */\n#if PG18_LT\n\t\t\tcur_ec->ec_members = lappend(cur_ec->ec_members, em);\n\t\t\tcur_ec->ec_relids = bms_add_members(cur_ec->ec_relids, info->compressed_rel->relids);\n#else\n\t\t\tts_add_child_eq_member(root, cur_ec, em, info->compressed_rel->relid);\n#endif\n\n\t\t\t/*\n\t\t\t * Cache the matching EquivalenceClass and EquivalenceMember for\n\t\t\t * segmentby column for future use, if we want to build a path that\n\t\t\t * sorts on it. Sorting is defined by PathKeys, which refer to\n\t\t\t * EquivalenceClasses, so it's a convenient form.\n\t\t\t */\n\t\t\tcompressed_fdw_private->compressed_ec_em_pairs =\n\t\t\t\tlappend(compressed_fdw_private->compressed_ec_em_pairs, list_make2(cur_ec, em));\n\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\nstatic void\ncompressed_rel_setup_equivalence_classes(PlannerInfo *root, CompressionInfo *info)\n{\n\tEMCreationContext context = {\n\t\t.uncompressed_relid = info->ht_rte->relid,\n\t\t.compressed_relid = info->compressed_rte->relid,\n\n\t\t.uncompressed_relid_idx = info->ht_rel->relid,\n\t\t.compressed_relid_idx = info->compressed_rel->relid,\n\t\t.settings = info->settings,\n\t};\n\n\tAssert(info->chunk_rte->relid != info->compressed_rel->relid);\n\tAssert(info->chunk_rel->relid != info->compressed_rel->relid);\n\t/* based on add_child_rel_equivalences */\n\tint i = -1;\n\tAssert(root->ec_merging_done);\n\t/* use chunk rel's eclass_indexes to avoid traversing all\n\t * the root's eq_classes\n\t */\n\twhile ((i = bms_next_member(info->chunk_rel->eclass_indexes, i)) >= 0)\n\t{\n\t\tEquivalenceClass *cur_ec = (EquivalenceClass *) list_nth(root->eq_classes, i);\n\t\t/*\n\t\t * If this EC contains a volatile expression, then generating child\n\t\t * EMs would be downright dangerous, so skip it.  We rely on a\n\t\t * volatile EC having only one EM.\n\t\t */\n\t\tif (cur_ec->ec_has_volatile)\n\t\t\tcontinue;\n\n\t\t/* if the compressed rel is already part of this EC,\n\t\t * we don't need to re-add it\n\t\t */\n\t\tif (bms_overlap(cur_ec->ec_relids, info->compressed_rel->relids))\n\t\t\tcontinue;\n\n\t\tbool em_added = add_segmentby_to_equivalence_class(root, cur_ec, info, &context);\n\t\t/* Record this EC index for the compressed rel */\n\t\tif (em_added)\n\t\t\tinfo->compressed_rel->eclass_indexes =\n\t\t\t\tbms_add_member(info->compressed_rel->eclass_indexes, i);\n\t}\n\tinfo->compressed_rel->has_eclass_joins = info->chunk_rel->has_eclass_joins;\n}\n\n/*\n * create RangeTblEntry and RelOptInfo for the compressed chunk\n * and add it to PlannerInfo\n */\nstatic void\ncolumnar_scan_add_plannerinfo(PlannerInfo *root, CompressionInfo *info, const Chunk *chunk,\n\t\t\t\t\t\t\t  RelOptInfo *chunk_rel, bool needs_sequence_num)\n{\n\tIndex compressed_index = root->simple_rel_array_size;\n\n\t/*\n\t * Add the compressed chunk to the baserel cache. Note that it belongs to\n\t * a different hypertable, the internal compression table.\n\t *\n\t * Ensure we do not grab a slice lock because that will assign a transaction ID that could\n\t * unnecessarily block other operations.\n\t */\n\tconst Chunk *compressed_chunk = ts_chunk_get_by_relid_locked(info->settings->fd.compress_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t AccessShareLock,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t true);\n\tts_add_baserel_cache_entry_for_chunk(info->settings->fd.compress_relid,\n\t\t\t\t\t\t\t\t\t\t ts_planner_get_hypertable(compressed_chunk\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   ->hypertable_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   CACHE_FLAG_NONE));\n\n\texpand_planner_arrays(root, 1);\n\tinfo->compressed_rte = columnar_scan_make_rte(info->settings->fd.compress_relid,\n\t\t\t\t\t\t\t\t\t\t\t\t  info->chunk_rte->rellockmode,\n\t\t\t\t\t\t\t\t\t\t\t\t  root->parse);\n\troot->simple_rte_array[compressed_index] = info->compressed_rte;\n\n\troot->parse->rtable = lappend(root->parse->rtable, info->compressed_rte);\n\n\troot->simple_rel_array[compressed_index] = NULL;\n\n\tRelOptInfo *compressed_rel = build_simple_rel(root, compressed_index, NULL);\n\n#if PG16_GE\n\t/*\n\t * When initially creating the RTE we add a RTEPerminfo entry for the\n\t * RTE but that is only to make build_simple_rel happy.\n\t * Asserts in the permission check code will fail with an RTEPerminfo\n\t * with no permissions to check so we remove it again here as we don't\n\t * want permission checks on the compressed chunks when querying\n\t * hypertables with compressed data.\n\t */\n\troot->parse->rteperminfos = list_delete_last(root->parse->rteperminfos);\n\tinfo->compressed_rte->perminfoindex = 0;\n#endif\n\n\t/* github issue :1558\n\t * set up top_parent_relids for this rel as the same as the\n\t * original hypertable, otherwise eq classes are not computed correctly\n\t * in generate_join_implied_equalities (called by\n\t * get_baserel_parampathinfo <- create_index_paths)\n\t */\n\tAssert(info->single_chunk || chunk_rel->top_parent_relids != NULL);\n\tcompressed_rel->top_parent_relids = bms_copy(chunk_rel->top_parent_relids);\n\tcompressed_rel->lateral_relids = bms_copy(chunk_rel->lateral_relids);\n\n\troot->simple_rel_array[compressed_index] = compressed_rel;\n\tinfo->compressed_rel = compressed_rel;\n\n\tRelation r = table_open(info->compressed_rte->relid, AccessShareLock);\n\n\tfor (int i = 0; i < r->rd_att->natts; i++)\n\t{\n\t\tForm_pg_attribute attr = TupleDescAttr(r->rd_att, i);\n\n\t\tif (attr->attisdropped || attr->atttypid != info->compresseddata_oid)\n\t\t\tcontinue;\n\n\t\tinfo->compressed_attnos_in_compressed_chunk =\n\t\t\tbms_add_member(info->compressed_attnos_in_compressed_chunk, attr->attnum);\n\t}\n\ttable_close(r, NoLock);\n\n\tcompressed_rel_setup_reltarget(compressed_rel, info, needs_sequence_num);\n\tcompressed_rel_setup_equivalence_classes(root, info);\n\t/* translate chunk_rel->joininfo for compressed_rel */\n\tcompressed_rel_setup_joininfo(compressed_rel, info);\n\n\t/*\n\t * Force parallel plan creation, see compute_parallel_worker().\n\t * This is not compatible with ts_classify_relation(), but on the other hand\n\t * the compressed chunk rel shouldn't exist anywhere outside of the\n\t * decompression planning, it is removed at the end.\n\t *\n\t * This is not needed for direct select from a single chunk, in which case\n\t * the chunk reloptkind will be RELOPT_BASEREL\n\t */\n\tif (chunk_rel->reloptkind == RELOPT_OTHER_MEMBER_REL)\n\t{\n\t\tcompressed_rel->reloptkind = RELOPT_OTHER_MEMBER_REL;\n\n\t\t/*\n\t\t * We have to minimally initialize the append relation info for the\n\t\t * compressed chunks, so that the generate_implied_equalities() works.\n\t\t * Only the parent hypertable relindex is needed.\n\t\t */\n\t\troot->append_rel_array[compressed_rel->relid] = makeNode(AppendRelInfo);\n\t\troot->append_rel_array[compressed_rel->relid]->parent_relid = info->ht_rel->relid;\n\t}\n}\n\nstatic ColumnarScanPath *\ncolumnar_scan_path_create(PlannerInfo *root, const CompressionInfo *compression_info,\n\t\t\t\t\t\t  Path *compressed_path)\n{\n\tColumnarScanPath *path;\n\n\tpath = (ColumnarScanPath *) newNode(sizeof(ColumnarScanPath), T_CustomPath);\n\n\tpath->info = compression_info;\n\n\tpath->custom_path.path.pathtype = T_CustomScan;\n\tpath->custom_path.path.parent = compression_info->chunk_rel;\n\tpath->custom_path.path.pathtarget = compression_info->chunk_rel->reltarget;\n\n\tif (compressed_path->param_info != NULL)\n\t{\n\t\t/*\n\t\t * Note that we have to separately generate the parameterized path info\n\t\t * for decompressed chunk path. The compressed parameterized path only\n\t\t * checks the clauses on segmentby columns, not on the compressed\n\t\t * columns.\n\t\t */\n\t\tpath->custom_path.path.param_info =\n\t\t\tget_baserel_parampathinfo(root,\n\t\t\t\t\t\t\t\t\t  compression_info->chunk_rel,\n\t\t\t\t\t\t\t\t\t  compressed_path->param_info->ppi_req_outer);\n\t\tAssert(path->custom_path.path.param_info != NULL);\n\t}\n\telse\n\t{\n\t\tpath->custom_path.path.param_info = NULL;\n\t}\n\n\t/*\n\t * Setting this flags means that Postgres can change the result targetlist\n\t * after the plan creation. This node can cope with this because it performs\n\t * the usual Postgres projection to produce the result tuple from the scan\n\t * tuple. The decompression-specific code works before that, and produces\n\t * the scan tuple based on the compressed tuple. The scan tuple descriptor\n\t * is based either on custom_scan_tlist or scanrelid, and the decompression\n\t * map is based on the compressed tuple, so they are not dependent on the\n\t * result targetlist, and we can allow it to be changed later. This allows\n\t * us to avoid a separate Result node, for a small performance saving.\n\t */\n\tpath->custom_path.flags = CUSTOMPATH_SUPPORT_PROJECTION;\n\n\tpath->custom_path.methods = &columnar_scan_path_methods;\n\tpath->batch_sorted_merge = false;\n\n\t/*\n\t * ColumnarScan doesn't manage any parallelism itself.\n\t */\n\tpath->custom_path.path.parallel_aware = false;\n\n\t/*\n\t * It can be applied per parallel worker, if its underlying scan is parallel.\n\t */\n\tpath->custom_path.path.parallel_safe = compressed_path->parallel_safe;\n\tpath->custom_path.path.parallel_workers = compressed_path->parallel_workers;\n\n\tpath->custom_path.custom_paths = list_make1(compressed_path);\n\tpath->reverse = false;\n\tpath->chunk_status = compression_info->chunk_status;\n\tpath->required_compressed_pathkeys = NIL;\n\tcost_columnar_scan(root, compression_info, &path->custom_path.path, compressed_path);\n\n\treturn path;\n}\n\n/* NOTE: this needs to be called strictly after all restrictinfos have been added\n *       to the compressed rel\n */\n\nstatic void\ncreate_compressed_scan_paths(PlannerInfo *root, RelOptInfo *compressed_rel,\n\t\t\t\t\t\t\t const CompressionInfo *compression_info, const SortInfo *sort_info)\n{\n\tPath *compressed_path;\n\tRelids required_outer = compressed_rel->lateral_relids;\n\n\t/* Must have same lateral relids as the chunk hypertable */\n\tAssert(bms_equal(required_outer, compression_info->chunk_rel->lateral_relids));\n\n\t/* clamp total_table_pages to 10 pages since this is the\n\t * minimum estimate for number of pages.\n\t * Add the value to any existing estimates\n\t */\n\troot->total_table_pages += Max(compressed_rel->pages, 10);\n\n\t/* create non parallel scan path */\n\tcompressed_path = create_seqscan_path(root, compressed_rel, required_outer, 0);\n\tadd_path(compressed_rel, compressed_path);\n\n\t/*\n\t * Create parallel seq scan path.\n\t * We marked the compressed rel as RELOPT_OTHER_MEMBER_REL when creating it,\n\t * so we should get a nonzero number of parallel workers even for small\n\t * tables, so that they don't prevent parallelism in the entire append plan.\n\t * See compute_parallel_workers(). This also applies to the creation of\n\t * index paths below.\n\t *\n\t * Parameterized rels that depend on an outer rel are not allowed to form partial\n\t * sequential scan paths\n\t */\n\tif (compressed_rel->consider_parallel && required_outer == NULL)\n\t{\n\t\tint parallel_workers = compute_parallel_worker(compressed_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   compressed_rel->pages,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   -1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   max_parallel_workers_per_gather);\n\n\t\tif (parallel_workers > 0)\n\t\t{\n\t\t\tadd_partial_path(compressed_rel,\n\t\t\t\t\t\t\t create_seqscan_path(root, compressed_rel, NULL, parallel_workers));\n\t\t}\n\t}\n\n\tif (sort_info->use_compressed_sort)\n\t{\n\t\t/*\n\t\t * If we can push down sort below decompression we temporarily switch\n\t\t * out root->query_pathkeys to allow matching to pathkeys produces by\n\t\t * decompression\n\t\t */\n\t\tList *orig_pathkeys = root->query_pathkeys;\n\t\tList *orig_eq_classes = root->eq_classes;\n\t\tBitmapset *orig_eclass_indexes = compression_info->compressed_rel->eclass_indexes;\n\t\troot->query_pathkeys = sort_info->required_compressed_pathkeys;\n\n\t\t/* We can optimize iterating over EquivalenceClasses by reducing them to\n\t\t * the subset which are from the compressed chunk. This only works if we don't\n\t\t * have joins based on equivalence classes involved since those\n\t\t * use eclass_indexes which is not valid with this optimization.\n\t\t *\n\t\t * Clauseless joins work fine since they don't rely on eclass_indexes.\n\t\t */\n\t\tif (!compression_info->chunk_rel->has_eclass_joins)\n\t\t{\n\t\t\tint i = -1;\n\t\t\tList *required_eq_classes = NIL;\n\t\t\twhile ((i = bms_next_member(compression_info->compressed_rel->eclass_indexes, i)) >= 0)\n\t\t\t{\n\t\t\t\tEquivalenceClass *cur_ec = (EquivalenceClass *) list_nth(root->eq_classes, i);\n\t\t\t\trequired_eq_classes = lappend(required_eq_classes, cur_ec);\n\t\t\t}\n\t\t\troot->eq_classes = required_eq_classes;\n\t\t\tcompression_info->compressed_rel->eclass_indexes = NULL;\n\t\t}\n\n\t\tcheck_index_predicates(root, compressed_rel);\n\t\tcreate_index_paths(root, compressed_rel);\n\t\troot->query_pathkeys = orig_pathkeys;\n\t\troot->eq_classes = orig_eq_classes;\n\t\tcompression_info->compressed_rel->eclass_indexes = orig_eclass_indexes;\n\t}\n\telse\n\t{\n\t\tcheck_index_predicates(root, compressed_rel);\n\t\tcreate_index_paths(root, compressed_rel);\n\t}\n}\n\n/*\n * create RangeTblEntry for compressed chunk\n */\nstatic RangeTblEntry *\ncolumnar_scan_make_rte(Oid compressed_relid, LOCKMODE lockmode, Query *parse)\n{\n\tRangeTblEntry *rte = makeNode(RangeTblEntry);\n\tRelation r = table_open(compressed_relid, lockmode);\n\tint varattno;\n\n\trte->rtekind = RTE_RELATION;\n\trte->relid = compressed_relid;\n\trte->relkind = r->rd_rel->relkind;\n\trte->rellockmode = lockmode;\n\trte->eref = makeAlias(RelationGetRelationName(r), NULL);\n\n\t/*\n\t * inlined from buildRelationAliases()\n\t * alias handling has been stripped because we won't\n\t * need alias handling at this level\n\t */\n\tfor (varattno = 0; varattno < r->rd_att->natts; varattno++)\n\t{\n\t\tForm_pg_attribute attr = TupleDescAttr(r->rd_att, varattno);\n\t\t/* Always insert an empty string for a dropped column */\n\t\tconst char *attrname = attr->attisdropped ? \"\" : NameStr(attr->attname);\n\t\trte->eref->colnames = lappend(rte->eref->colnames, makeString(pstrdup(attrname)));\n\t}\n\n\t/*\n\t * Drop the rel refcount, but keep the access lock till end of transaction\n\t * so that the table can't be deleted or have its schema modified\n\t * underneath us.\n\t */\n\ttable_close(r, NoLock);\n\n\t/*\n\t * Set flags and access permissions.\n\t *\n\t * The initial default on access checks is always check-for-READ-access,\n\t * which is the right thing for all except target tables.\n\t */\n\trte->lateral = false;\n\trte->inh = false;\n\trte->inFromCl = false;\n\n#if PG16_LT\n\trte->requiredPerms = 0;\n\trte->checkAsUser = InvalidOid; /* not set-uid by default, either */\n\trte->selectedCols = NULL;\n\trte->insertedCols = NULL;\n\trte->updatedCols = NULL;\n#else\n\t/* Add empty perminfo for the new RTE to make build_simple_rel happy. */\n\taddRTEPermissionInfo(&parse->rteperminfos, rte);\n#endif\n\n\treturn rte;\n}\n\n/*\n * Find segmentby columns that are equated to a constant by a toplevel\n * baserestrictinfo.\n *\n * This will detect Var = Const and Var = Param and set the corresponding bit\n * in CompressionInfo->chunk_const_segmentby.\n */\nstatic Bitmapset *\nfind_const_segmentby(RelOptInfo *chunk_rel, const CompressionInfo *info)\n{\n\tBitmapset *segmentby_columns = NULL;\n\n\tif (chunk_rel->baserestrictinfo != NIL)\n\t{\n\t\tListCell *lc_ri;\n\t\tforeach (lc_ri, chunk_rel->baserestrictinfo)\n\t\t{\n\t\t\tRestrictInfo *ri = lfirst(lc_ri);\n\n\t\t\tif (IsA(ri->clause, OpExpr) && list_length(castNode(OpExpr, ri->clause)->args) == 2)\n\t\t\t{\n\t\t\t\tOpExpr *op = castNode(OpExpr, ri->clause);\n\t\t\t\tNode *lnode, *rnode;\n\t\t\t\tVar *var;\n\t\t\t\tExpr *other;\n\n\t\t\t\tif (op->opretset)\n\t\t\t\t\tcontinue;\n\n\t\t\t\tlnode = strip_implicit_coercions(linitial(op->args));\n\t\t\t\trnode = strip_implicit_coercions(lsecond(op->args));\n\n\t\t\t\tAssert(lnode && rnode);\n\t\t\t\tif (IsA(lnode, Var))\n\t\t\t\t{\n\t\t\t\t\tvar = castNode(Var, lnode);\n\t\t\t\t\tother = lsecond(op->args);\n\t\t\t\t}\n\t\t\t\telse if (IsA(rnode, Var))\n\t\t\t\t{\n\t\t\t\t\tvar = castNode(Var, rnode);\n\t\t\t\t\tother = linitial(op->args);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\tcontinue;\n\n\t\t\t\tif ((Index) var->varno != chunk_rel->relid || var->varattno <= 0)\n\t\t\t\t\tcontinue;\n\n\t\t\t\tif (IsA(other, Const) || IsA(other, Param))\n\t\t\t\t{\n\t\t\t\t\tTypeCacheEntry *tce = lookup_type_cache(var->vartype, TYPECACHE_EQ_OPR);\n\n\t\t\t\t\tif (op->opno != tce->eq_opr)\n\t\t\t\t\t{\n\t\t\t\t\t\t/* Issue #9066: check if our OpExpr is still an equality (Var = Const)\n\t\t\t\t\t\t * for Var and Const of different types\n\t\t\t\t\t\t */\n#if PG18_GE\n\t\t\t\t\t\tList *opinfos = get_op_index_interpretation(op->opno);\n#else\n\t\t\t\t\t\tList *opinfos = get_op_btree_interpretation(op->opno);\n#endif\n\t\t\t\t\t\tListCell *lc;\n\t\t\t\t\t\tbool equality = false;\n\t\t\t\t\t\tforeach (lc, opinfos)\n\t\t\t\t\t\t{\n#if PG18_GE\n\t\t\t\t\t\t\tOpIndexInterpretation *opinfo = (OpIndexInterpretation *) lfirst(lc);\n\t\t\t\t\t\t\tif (opinfo->cmptype == COMPARE_EQ)\n#else\n\t\t\t\t\t\t\tOpBtreeInterpretation *opinfo = (OpBtreeInterpretation *) lfirst(lc);\n\t\t\t\t\t\t\tif (opinfo->strategy == BTEqualStrategyNumber)\n#endif\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tOid mixed_type_eqop = get_opfamily_member(opinfo->opfamily_id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  var->vartype,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  exprType((Node *) other),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  BTEqualStrategyNumber);\n\t\t\t\t\t\t\t\tif (op->opno == mixed_type_eqop)\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tequality = true;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!equality)\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (bms_is_member(var->varattno, info->chunk_segmentby_attnos))\n\t\t\t\t\t\tsegmentby_columns = bms_add_member(segmentby_columns, var->varattno);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn segmentby_columns;\n}\n\n/*\n * Returns whether the pathkeys starting at the given offset match the compression\n * orderby, and whether the order is reverse.\n */\nstatic bool\nmatch_pathkeys_to_compression_orderby(List *pathkeys, List *chunk_em_exprs,\n\t\t\t\t\t\t\t\t\t  int starting_pathkey_offset,\n\t\t\t\t\t\t\t\t\t  const CompressionInfo *compression_info, bool *out_reverse)\n{\n\tint compressed_pk_index = 0;\n\tfor (int i = starting_pathkey_offset; i < list_length(pathkeys); i++)\n\t{\n\t\tcompressed_pk_index++;\n\t\tPathKey *pk = list_nth_node(PathKey, pathkeys, i);\n\t\tNode *node = strip_implicit_coercions((Node *) list_nth(chunk_em_exprs, i));\n\n\t\tif (node == NULL || !IsA(node, Var))\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tVar *var = castNode(Var, node);\n\n\t\tif (var->varattno <= 0)\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tchar *column_name = get_attname(compression_info->chunk_rte->relid, var->varattno, false);\n\t\tint orderby_index = ts_array_position(compression_info->settings->fd.orderby, column_name);\n\n\t\tif (orderby_index != compressed_pk_index)\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tbool orderby_desc =\n\t\t\tts_array_get_element_bool(compression_info->settings->fd.orderby_desc, orderby_index);\n\t\tbool orderby_nullsfirst =\n\t\t\tts_array_get_element_bool(compression_info->settings->fd.orderby_nullsfirst,\n\t\t\t\t\t\t\t\t\t  orderby_index);\n\n\t\t/*\n\t\t * In PG18+: pk_cmptype is either COMPARE_LT (for ASC) or COMPARE_GT (for DESC)\n\t\t * For previous PG versions we have compatibility macros to make these new names available.\n\t\t */\n\t\tbool this_pathkey_reverse = false;\n\t\tif (pk->pk_cmptype == COMPARE_LT)\n\t\t{\n\t\t\tif (!orderby_desc && orderby_nullsfirst == pk->pk_nulls_first)\n\t\t\t{\n\t\t\t\tthis_pathkey_reverse = false;\n\t\t\t}\n\t\t\telse if (orderby_desc && orderby_nullsfirst != pk->pk_nulls_first)\n\t\t\t{\n\t\t\t\tthis_pathkey_reverse = true;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\telse if (pk->pk_cmptype == COMPARE_GT)\n\t\t{\n\t\t\tif (orderby_desc && orderby_nullsfirst == pk->pk_nulls_first)\n\t\t\t{\n\t\t\t\tthis_pathkey_reverse = false;\n\t\t\t}\n\t\t\telse if (!orderby_desc && orderby_nullsfirst != pk->pk_nulls_first)\n\t\t\t{\n\t\t\t\tthis_pathkey_reverse = true;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\t/*\n\t\t * first pathkey match determines if this is forward or backward scan\n\t\t * any further pathkey items need to have same direction\n\t\t */\n\t\tif (compressed_pk_index == 1)\n\t\t{\n\t\t\t*out_reverse = this_pathkey_reverse;\n\t\t}\n\t\telse if (this_pathkey_reverse != *out_reverse)\n\t\t{\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/*\n * Check if we can push down the sort below the ColumnarScan node and fill\n * SortInfo accordingly\n *\n * The following conditions need to be true for pushdown:\n *  - all segmentby columns need to be prefix of pathkeys or have equality constraint\n *  - the rest of pathkeys needs to match compress_orderby\n *\n * If query pathkeys is shorter than segmentby + compress_orderby pushdown can still be done\n */\nstatic SortInfo\nbuild_sortinfo(PlannerInfo *root, const Chunk *chunk, RelOptInfo *chunk_rel,\n\t\t\t   const CompressionInfo *compression_info, List *pathkeys)\n{\n\tVar *var;\n\tchar *column_name;\n\tListCell *lc;\n\tSortInfo sort_info = { 0 };\n\n\tif (pathkeys == NIL)\n\t{\n\t\treturn sort_info;\n\t}\n\n\t/*\n\t * Translate the pathkeys to chunk expressions, creating a List of them\n\t * parallel to the pathkeys list, with NULL entries if we didn't find a\n\t * match.\n\t */\n\tList *chunk_em_exprs = NIL;\n\tforeach (lc, pathkeys)\n\t{\n\t\tPathKey *pk = lfirst(lc);\n\t\tEquivalenceClass *ec = pk->pk_eclass;\n\t\tExpr *em_expr = NULL;\n\t\tif (!ec->ec_has_volatile)\n\t\t{\n\t\t\tem_expr = ts_find_em_expr_for_rel(pk->pk_eclass, compression_info->chunk_rel);\n\t\t}\n\t\tchunk_em_exprs = lappend(chunk_em_exprs, em_expr);\n\t}\n\tAssert(list_length(chunk_em_exprs) == list_length(pathkeys));\n\n\t/* Find the pathkeys we can use for explicitly sorting after decompression. */\n\tList *sort_pathkey_exprs = NIL;\n\tList *sort_pathkeys = NIL;\n\tfor (int i = 0; i < list_length(chunk_em_exprs); i++)\n\t{\n\t\tPathKey *pk = list_nth_node(PathKey, pathkeys, i);\n\t\tExpr *chunk_em_expr = (Expr *) list_nth(chunk_em_exprs, i);\n\t\tif (chunk_em_expr == NULL)\n\t\t{\n\t\t\tbreak;\n\t\t}\n\n\t\tsort_pathkeys = lappend(sort_pathkeys, pk);\n\t\tsort_pathkey_exprs = lappend(sort_pathkey_exprs, chunk_em_expr);\n\t}\n\n\tif (sort_pathkeys == NIL)\n\t{\n\t\treturn sort_info;\n\t}\n\n\tsort_info.decompressed_sort_pathkeys = sort_pathkeys;\n\tcost_qual_eval(&sort_info.decompressed_sort_pathkeys_cost, sort_pathkey_exprs, root);\n\n\t/*\n\t * Next, check if we can push the sort down to the compressed part.\n\t *\n\t * Batch sorted merge optimization is enabled for unordered chunks\n\t * because we do merge the batches at execution time which\n\t * only relies that the batches themselves are sorted which is\n\t * always the case.\n\t */\n\n\t/* all segmentby columns need to be prefix of pathkeys */\n\tint i = 0;\n\tif (compression_info->num_segmentby_columns > 0)\n\t{\n\t\tBitmapset *segmentby_columns;\n\n\t\t/*\n\t\t * initialize segmentby with equality constraints from baserestrictinfo because\n\t\t * those columns dont need to be prefix of pathkeys\n\t\t */\n\t\tsegmentby_columns = bms_copy(compression_info->chunk_const_segmentby);\n\n\t\t/*\n\t\t * loop over pathkeys until we find one that is not a segmentby column\n\t\t * we keep looping even if we found all segmentby columns in case a\n\t\t * columns appears both in baserestrictinfo and in ORDER BY clause\n\t\t */\n\t\tfor (i = 0; i < list_length(pathkeys); i++)\n\t\t{\n\t\t\tAssert(bms_num_members(segmentby_columns) <= compression_info->num_segmentby_columns);\n\n\t\t\tNode *node = strip_implicit_coercions((Node *) list_nth(chunk_em_exprs, i));\n\n\t\t\tif (node == NULL || !IsA(node, Var))\n\t\t\t\tbreak;\n\t\t\tvar = castNode(Var, node);\n\n\t\t\tif (var->varattno <= 0)\n\t\t\t\tbreak;\n\n\t\t\tcolumn_name = get_attname(compression_info->chunk_rte->relid, var->varattno, false);\n\t\t\tif (!ts_array_is_member(compression_info->settings->fd.segmentby, column_name))\n\t\t\t\tbreak;\n\n\t\t\tsegmentby_columns = bms_add_member(segmentby_columns, var->varattno);\n\t\t}\n\n\t\t/*\n\t\t * Pathkeys satisfied by sorting the compressed data on segmentby columns.\n\t\t * Can use compressed sort on segmentby cols for unordered chunks as well.\n\t\t */\n\t\tif (i == list_length(pathkeys))\n\t\t{\n\t\t\tsort_info.use_compressed_sort = true;\n\t\t\treturn sort_info;\n\t\t}\n\n\t\t/*\n\t\t * If pathkeys still has items, but we didn't find all segmentby columns,\n\t\t * we cannot satisfy these pathkeys by sorting the compressed chunk table.\n\t\t */\n\t\tif (i != list_length(pathkeys) &&\n\t\t\tbms_num_members(segmentby_columns) != compression_info->num_segmentby_columns)\n\t\t{\n\t\t\t/*\n\t\t\t * If we didn't have any segmentby columns in pathkeys, try batch sorted merge\n\t\t\t * instead.\n\t\t\t */\n\t\t\tif (i == 0)\n\t\t\t{\n\t\t\t\tsort_info.use_batch_sorted_merge =\n\t\t\t\t\tmatch_pathkeys_to_compression_orderby(pathkeys,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  chunk_em_exprs,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  /* starting_pathkey_offset = */ 0,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  compression_info,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  &sort_info.reverse);\n\t\t\t}\n\t\t\treturn sort_info;\n\t\t}\n\t}\n\n\t/*\n\t * Cannot push down sort on non-segmentby columns\n\t * if the chunk has batches overlapping on orderby columns\n\t */\n\tif (ts_chunk_is_unordered(chunk))\n\t\treturn sort_info;\n\n\t/*\n\t * Pathkeys includes columns past segmentby columns, so we need sequence_num\n\t * in the targetlist for ordering.\n\t */\n\tsort_info.needs_sequence_num = true;\n\n\t/*\n\t * loop over the rest of pathkeys\n\t * this needs to exactly match the configured compress_orderby\n\t */\n\tsort_info.use_compressed_sort = match_pathkeys_to_compression_orderby(pathkeys,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  chunk_em_exprs,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  i,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  compression_info,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  &sort_info.reverse);\n\n\treturn sort_info;\n}\n\n/* Check if the provided path is a ColumnarScanPath */\nbool\nts_is_columnar_scan_path(Path *path)\n{\n\treturn IsA(path, CustomPath) &&\n\t\t   castNode(CustomPath, path)->methods == &columnar_scan_path_methods;\n}\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/columnar_scan.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <nodes/bitmapset.h>\n#include <nodes/extensible.h>\n\n#include \"chunk.h\"\n#include \"hypertable.h\"\n#include \"ts_catalog/compression_settings.h\"\n\ntypedef struct CompressionInfo\n{\n\tRelOptInfo *chunk_rel;\n\tRelOptInfo *compressed_rel;\n\tRelOptInfo *ht_rel;\n\tRangeTblEntry *chunk_rte;\n\tRangeTblEntry *compressed_rte;\n\tRangeTblEntry *ht_rte;\n\tOid compresseddata_oid;\n\n\tCompressionSettings *settings;\n\n\tint hypertable_id;\n\tList *hypertable_compression_info;\n\n\tint num_orderby_columns;\n\tint num_segmentby_columns;\n\n\t/* chunk attribute numbers that are segmentby columns */\n\tBitmapset *chunk_segmentby_attnos;\n\t/*\n\t * Chunk segmentby attribute numbers that are equated to a constant by a\n\t * baserestrictinfo.\n\t */\n\tBitmapset *chunk_const_segmentby;\n\t/* compressed chunk attribute numbers for columns that are compressed */\n\tBitmapset *compressed_attnos_in_compressed_chunk;\n\n\tbool single_chunk;\t  /* query on explicit chunk */\n\tbool has_seq_num;\t  /* legacy sequence number support */\n\tRelids parent_relids; /* relids of the parent hypertable and UNION */\n\n\t/* Compressed batch size estimated from statistics. */\n\tdouble compressed_batch_size;\n\n\tint32 chunk_status;\n} CompressionInfo;\n\ntypedef struct ColumnarScanPath\n{\n\tCustomPath custom_path;\n\tconst CompressionInfo *info;\n\n\tList *required_compressed_pathkeys;\n\tbool needs_sequence_num;\n\tbool reverse;\n\tbool batch_sorted_merge;\n\tint32 chunk_status;\n} ColumnarScanPath;\n\nvoid ts_columnar_scan_generate_paths(PlannerInfo *root, RelOptInfo *rel, const Hypertable *ht,\n\t\t\t\t\t\t\t\t\t const Chunk *chunk);\n\nextern bool ts_is_columnar_scan_path(Path *path);\nextern bool ts_is_columnar_scan_plan(Plan *plan);\n\nextern double ts_columnar_estimate_compressed_batch_size(const Oid relid);\n\nColumnarScanPath *copy_columnar_scan_path(ColumnarScanPath *src);\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/compressed_batch.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n\n#include <executor/tuptable.h>\n#include <nodes/bitmapset.h>\n#include <utils/builtins.h>\n#include <utils/date.h>\n#include <utils/timestamp.h>\n#include <utils/uuid.h>\n\n#include \"compression/arrow_c_data_interface.h\"\n#include \"compression/compression.h\"\n#include \"debug_assert.h\"\n#include \"guc.h\"\n#include \"nodes/columnar_scan/compressed_batch.h\"\n#include \"nodes/columnar_scan/vector_dict.h\"\n#include \"nodes/columnar_scan/vector_predicates.h\"\n#include \"nodes/columnar_scan/vector_quals.h\"\n\n/*\n * Create a single-value ArrowArray of an arithmetic type. This is a specialized\n * function because arithmetic types have a particular layout of ArrowArrays.\n */\nstatic ArrowArray *\nmake_single_value_arrow_arithmetic(Oid arithmetic_type, Datum datum, bool isnull)\n{\n\tstruct ArrowWithBuffers\n\t{\n\t\tArrowArray arrow;\n\t\tuint64 arrow_buffers_array_storage[2];\n\t\tuint64 validity_buffer[1];\n\t\t/* The value buffer has 64-byte padding as required by Arrow. */\n\t\tuint64 values_buffer[8];\n\t};\n\n\tstruct ArrowWithBuffers *with_buffers = palloc0(sizeof(struct ArrowWithBuffers));\n\tArrowArray *arrow = &with_buffers->arrow;\n\tarrow->length = 1;\n\tarrow->buffers = (const void **) with_buffers->arrow_buffers_array_storage;\n\tarrow->n_buffers = 2;\n\tarrow->buffers[0] = with_buffers->validity_buffer;\n\tarrow->buffers[1] = with_buffers->values_buffer;\n\n\tif (isnull)\n\t{\n\t\t/*\n\t\t * The validity bitmap was initialized to invalid on allocation, and\n\t\t * the Datum might be invalid if the value is null (important on i386\n\t\t * where it might be pass-by-reference), so don't read it.\n\t\t */\n\t\tarrow->null_count = 1;\n\t\treturn arrow;\n\t}\n\n\tarrow_set_row_validity((uint64 *) arrow->buffers[0], 0, true);\n\n#define FOR_TYPE(PGTYPE, CTYPE, FROMDATUM)                                                         \\\n\tcase PGTYPE:                                                                                   \\\n\t\t*((CTYPE *) arrow->buffers[1]) = FROMDATUM(datum);                                         \\\n\t\tbreak\n\n\tswitch (arithmetic_type)\n\t{\n\t\tFOR_TYPE(INT8OID, int64, DatumGetInt64);\n\t\tFOR_TYPE(INT4OID, int32, DatumGetInt32);\n\t\tFOR_TYPE(INT2OID, int16, DatumGetInt16);\n\t\tFOR_TYPE(FLOAT8OID, float8, DatumGetFloat8);\n\t\tFOR_TYPE(FLOAT4OID, float4, DatumGetFloat4);\n\t\tFOR_TYPE(TIMESTAMPTZOID, TimestampTz, DatumGetTimestampTz);\n\t\tFOR_TYPE(TIMESTAMPOID, Timestamp, DatumGetTimestamp);\n\t\tFOR_TYPE(DATEOID, DateADT, DatumGetDateADT);\n\t\tFOR_TYPE(UUIDOID, pg_uuid_t, *DatumGetUUIDP);\n\t\tcase BOOLOID:\n\t\t\tarrow_set_row_validity((uint64 *) arrow->buffers[1], 0, DatumGetBool(datum));\n\t\t\tbreak;\n\t\tdefault:\n\t\t\telog(ERROR, \"unexpected column type '%s'\", format_type_be(arithmetic_type));\n\t\t\tpg_unreachable();\n\t}\n\n#undef FOR_TYPE\n\n\treturn arrow;\n}\n\n/*\n * Create a single-value ArrowArray of text. This is a specialized function\n * because the text ArrowArray has a specialized layout.\n */\nstatic ArrowArray *\nmake_single_value_arrow_text(Datum datum, bool isnull)\n{\n\tstruct ArrowWithBuffers\n\t{\n\t\tArrowArray arrow;\n\t\tuint64 arrow_buffers_array_storage[3];\n\t\tuint64 validity_buffer[1];\n\t\tuint32 offsets_buffer[2];\n\t\t/* The value buffer has 64-byte padding as required by Arrow. */\n\t\tuint64 values_buffer[8];\n\t};\n\n\tstruct ArrowWithBuffers *with_buffers = palloc0(sizeof(struct ArrowWithBuffers));\n\tArrowArray *arrow = &with_buffers->arrow;\n\tarrow->length = 1;\n\tarrow->buffers = (const void **) with_buffers->arrow_buffers_array_storage;\n\tarrow->n_buffers = 3;\n\tarrow->buffers[0] = with_buffers->validity_buffer;\n\tarrow->buffers[1] = with_buffers->offsets_buffer;\n\tarrow->buffers[2] = with_buffers->values_buffer;\n\n\tif (isnull)\n\t{\n\t\t/*\n\t\t * The validity bitmap was initialized to invalid on allocation, and\n\t\t * the Datum might be invalid if the value is null (important on i386\n\t\t * where it might be pass-by-reference), so don't read it.\n\t\t */\n\t\tarrow->null_count = 1;\n\t\treturn arrow;\n\t}\n\n\tarrow_set_row_validity((uint64 *) arrow->buffers[0], 0, true);\n\n\ttext *detoasted = PG_DETOAST_DATUM(datum);\n\t((uint32 *) arrow->buffers[1])[1] = VARSIZE_ANY_EXHDR(detoasted);\n\tarrow->buffers[2] = VARDATA(detoasted);\n\treturn arrow;\n}\n\n/*\n * Create a single value ArrowArray from Postgres Datum. This is used to run\n * the usual vectorized predicates on compressed columns with default values.\n */\nArrowArray *\nmake_single_value_arrow(Oid pgtype, Datum datum, bool isnull)\n{\n\tif (pgtype == TEXTOID)\n\t{\n\t\treturn make_single_value_arrow_text(datum, isnull);\n\t}\n\n\treturn make_single_value_arrow_arithmetic(pgtype, datum, isnull);\n}\n\nint\nget_max_varlena_bytes(ArrowArray *text_array)\n{\n\tif (text_array->dictionary != NULL)\n\t{\n\t\tEnsure(text_array->dictionary->dictionary == NULL,\n\t\t\t   \"got dictionary-encoded dictionary for a text arrow array\");\n\t\treturn get_max_varlena_bytes(text_array->dictionary);\n\t}\n\n\tint maxbytes = 0;\n\tuint32 *offsets = (uint32 *) text_array->buffers[1];\n\tfor (int i = 0; i < text_array->length; i++)\n\t{\n\t\tconst int curbytes = offsets[i + 1] - offsets[i];\n\t\tif (curbytes > maxbytes)\n\t\t{\n\t\t\tmaxbytes = curbytes;\n\t\t}\n\t}\n\n\treturn VARHDRSZ + maxbytes;\n}\n\nstatic void\ndecompress_scalar_column(CompressedColumnValues *column, Datum value, bool isnull)\n{\n\tcolumn->decompression_type = DT_Scalar;\n\tcolumn->buffers[0] = DatumGetPointer(BoolGetDatum(isnull));\n\tcolumn->buffers[1] = DatumGetPointer(value);\n\n\t*column->output_isnull = isnull;\n\t*column->output_value = value;\n}\n\nstatic void\ndecompress_column(DecompressContext *dcontext, DecompressBatchState *batch_state,\n\t\t\t\t  TupleTableSlot *compressed_slot, int i)\n{\n\tCompressionColumnDescription *column_description = &dcontext->compressed_chunk_columns[i];\n\tCompressedColumnValues *column_values = &batch_state->compressed_columns[i];\n\tcolumn_values->arrow = NULL;\n\tconst int value_bytes = get_typlen(column_description->typid);\n\tAssert(value_bytes != 0);\n\n\tbool isnull;\n\tDatum value = slot_getattr(compressed_slot, column_description->compressed_scan_attno, &isnull);\n\n\tif (isnull)\n\t{\n\t\t/*\n\t\t * The column will have a default value for the entire batch,\n\t\t * set it now.\n\t\t *\n\t\t * We might use a custom targetlist-based scan tuple which has no\n\t\t * default values, so the default values are fetched from the\n\t\t * uncompressed chunk tuple descriptor.\n\t\t */\n\t\tbool isnull;\n\t\tDatum value = getmissingattr(dcontext->uncompressed_chunk_tdesc,\n\t\t\t\t\t\t\t\t\t column_description->uncompressed_chunk_attno,\n\t\t\t\t\t\t\t\t\t &isnull);\n\t\tdecompress_scalar_column(column_values, value, isnull);\n\t\treturn;\n\t}\n\n\t/* Detoast the compressed datum. */\n\tvalue = PointerGetDatum(detoaster_detoast_attr_copy((struct varlena *) DatumGetPointer(value),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t&dcontext->detoaster,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbatch_state->per_batch_context));\n\n\tCompressedDataHeader *header = (CompressedDataHeader *) value;\n\n\t/* First check if this is a block of NULL values. */\n\tif (header->compression_algorithm == COMPRESSION_ALGORITHM_NULL)\n\t{\n\t\tdecompress_scalar_column(column_values, (Datum) NULL, /* isnull = */ true);\n\t\treturn;\n\t}\n\n\t/* Decompress the entire batch if it is supported. */\n\tArrowArray *arrow = NULL;\n\tif (dcontext->enable_bulk_decompression && column_description->bulk_decompression_supported)\n\t{\n\t\tif (dcontext->bulk_decompression_context == NULL)\n\t\t{\n\t\t\tdcontext->bulk_decompression_context = create_bulk_decompression_mctx(\n\t\t\t\tMemoryContextGetParent(batch_state->per_batch_context));\n\t\t}\n\n\t\tDecompressAllFunction decompress_all =\n\t\t\ttsl_get_decompress_all_function(header->compression_algorithm,\n\t\t\t\t\t\t\t\t\t\t\tcolumn_description->typid);\n\t\tAssert(decompress_all != NULL);\n\n\t\tMemoryContext context_before_decompression =\n\t\t\tMemoryContextSwitchTo(dcontext->bulk_decompression_context);\n\n\t\tarrow = decompress_all(PointerGetDatum(header),\n\t\t\t\t\t\t\t   column_description->typid,\n\t\t\t\t\t\t\t   batch_state->per_batch_context);\n\n\t\tMemoryContextSwitchTo(context_before_decompression);\n\n\t\tMemoryContextReset(dcontext->bulk_decompression_context);\n\t}\n\n\tif (arrow == NULL)\n\t{\n\t\t/* As a fallback, decompress row-by-row. */\n\t\tcolumn_values->decompression_type = DT_Iterator;\n\t\tMemoryContext old_context = MemoryContextSwitchTo(batch_state->per_batch_context);\n\t\tcolumn_values->buffers[0] =\n\t\t\ttsl_get_decompression_iterator_init(header->compression_algorithm,\n\t\t\t\t\t\t\t\t\t\t\t\tdcontext->reverse)(PointerGetDatum(header),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   column_description->typid);\n\t\tMemoryContextSwitchTo(old_context);\n\t\treturn;\n\t}\n\n\t/* Should have been filled from the count metadata column. */\n\tAssert(batch_state->total_batch_rows != 0);\n\tif (batch_state->total_batch_rows != arrow->length)\n\t{\n\t\telog(ERROR, \"compressed column out of sync with batch counter\");\n\t}\n\n\tcolumn_values->arrow = arrow;\n\n\tif (value_bytes > 0)\n\t{\n\t\t/* Fixed-width column. */\n\t\tcolumn_values->decompression_type = value_bytes;\n\t\tcolumn_values->buffers[0] = arrow->buffers[0];\n\t\tcolumn_values->buffers[1] = arrow->buffers[1];\n\t\tcolumn_values->buffers[2] = NULL;\n\t\tcolumn_values->buffers[3] = NULL;\n\n\t\tif (column_description->typid == BOOLOID)\n\t\t{\n\t\t\t/* The bool columns have a dedicated storage format. */\n\t\t\tcolumn_values->decompression_type = DT_ArrowBits;\n\t\t}\n\t}\n\telse\n\t{\n\t\t/*\n\t\t * Text column. Pre-allocate memory for its text Datum in the\n\t\t * decompressed scan slot. We can't put direct references to Arrow\n\t\t * memory there, because it doesn't have the varlena headers that\n\t\t * Postgres expects for text.\n\t\t */\n\t\tconst int maxbytes = get_max_varlena_bytes(arrow);\n\t\t*column_values->output_value =\n\t\t\tPointerGetDatum(MemoryContextAlloc(batch_state->per_batch_context, maxbytes));\n\n\t\t/*\n\t\t * Set up the datum conversion based on whether we use the dictionary.\n\t\t */\n\t\tif (arrow->dictionary == NULL)\n\t\t{\n\t\t\tcolumn_values->decompression_type = DT_ArrowText;\n\t\t\tcolumn_values->buffers[0] = arrow->buffers[0];\n\t\t\tcolumn_values->buffers[1] = arrow->buffers[1];\n\t\t\tcolumn_values->buffers[2] = arrow->buffers[2];\n\t\t\tcolumn_values->buffers[3] = NULL;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tcolumn_values->decompression_type = DT_ArrowTextDict;\n\t\t\tcolumn_values->buffers[0] = arrow->buffers[0];\n\t\t\tcolumn_values->buffers[1] = arrow->dictionary->buffers[1];\n\t\t\tcolumn_values->buffers[2] = arrow->dictionary->buffers[2];\n\t\t\tcolumn_values->buffers[3] = arrow->buffers[1];\n\t\t}\n\t}\n}\n\n/*\n * Get the arrow array for the compressed batch via the VectorQualState.\n *\n * This is a ColumnarScan-specific implementation of the\n * VectorQualState->get_arrow_array() function used to interface with the\n * vector qual code across different scan nodes.\n */\nconst ArrowArray *\ncompressed_batch_get_arrow_array(VectorQualState *vqstate, Expr *expr, bool *is_default_value)\n{\n\tCompressedBatchVectorQualState *cbvqstate = (CompressedBatchVectorQualState *) vqstate;\n\tDecompressContext *dcontext = cbvqstate->dcontext;\n\tDecompressBatchState *batch_state = cbvqstate->batch_state;\n\tCompressionColumnDescription *column_description = NULL;\n\tTupleTableSlot *compressed_slot = vqstate->slot;\n\tconst Var *var = castNode(Var, expr);\n\tint column_index = 0;\n\n\tfor (; column_index < dcontext->num_data_columns; column_index++)\n\t{\n\t\tcolumn_description = &dcontext->compressed_chunk_columns[column_index];\n\t\tif (var->varno == INDEX_VAR)\n\t\t{\n\t\t\t/*\n\t\t\t * Reference into custom scan tlist, happens when we are using a\n\t\t\t * non-default custom scan tuple.\n\t\t\t */\n\t\t\tif (column_description->custom_scan_attno == var->varattno)\n\t\t\t{\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/*\n\t\t\t * Reference into uncompressed chunk tuple.\n\t\t\t *\n\t\t\t * Note that this is somewhat redundant, because this branch is\n\t\t\t * taken when we do not use a custom scan tuple, and in this case\n\t\t\t * the custom scan attno is the same as the uncompressed chunk attno,\n\t\t\t * so the above branch would do as well. This difference might\n\t\t\t * become relevant in the future, if we stop outputting the\n\t\t\t * columns that are needed only for the vectorized quals.\n\t\t\t */\n\t\t\tif (column_description->uncompressed_chunk_attno == var->varattno)\n\t\t\t{\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\tEnsure(column_index < dcontext->num_data_columns,\n\t\t   \"decompressed column %d not found in batch\",\n\t\t   var->varattno);\n\tAssert(column_description != NULL);\n\tAssert(column_description->typid == var->vartype);\n\n\tCompressedColumnValues *column_values = &batch_state->compressed_columns[column_index];\n\n\tif (column_values->decompression_type == DT_Invalid)\n\t{\n\t\t/*\n\t\t * We decompress the compressed columns on demand, so that we can\n\t\t * skip decompressing some columns if the entire batch doesn't pass\n\t\t * the quals.\n\t\t */\n\t\tdecompress_column(dcontext, batch_state, compressed_slot, column_index);\n\t\tAssert(column_values->decompression_type != DT_Invalid);\n\t}\n\n\tEnsure(column_values->decompression_type != DT_Iterator,\n\t\t   \"expected arrow array but got iterator for column index %d\",\n\t\t   column_index);\n\n\t/*\n\t * Prepare to compute the vector predicate. We have to handle the\n\t * default values in a special way because they don't produce the usual\n\t * decompressed ArrowArrays.\n\t */\n\tconst ArrowArray *vector = column_values->arrow;\n\tif (column_values->arrow == NULL)\n\t{\n\t\t/*\n\t\t * The compressed column had a default value. We can't fall back to\n\t\t * the non-vectorized quals now, so build a single-value ArrowArray\n\t\t * with this default value, check if it passes the predicate, and apply\n\t\t * it to the entire batch.\n\t\t */\n\t\tAssert(column_values->decompression_type == DT_Scalar);\n\n\t\t/*\n\t\t * We saved the actual default value into the decompressed scan slot\n\t\t * above, so pull it from there.\n\t\t */\n\t\tvector = make_single_value_arrow(column_description->typid,\n\t\t\t\t\t\t\t\t\t\t PointerGetDatum(column_values->buffers[1]),\n\t\t\t\t\t\t\t\t\t\t DatumGetBool(PointerGetDatum(column_values->buffers[0])));\n\n\t\t/*\n\t\t * We start from an all-valid bitmap, because the predicate is\n\t\t * AND-ed to it.\n\t\t */\n\t\t*is_default_value = true;\n\t}\n\telse\n\t\t*is_default_value = false;\n\n\treturn vector;\n}\n\nstatic void\ncompute_plain_qual(VectorQualState *vqstate, TupleTableSlot *slot, Node *qual,\n\t\t\t\t   uint64 *restrict result)\n{\n\t/*\n\t * Some predicates can be evaluated to a Const at run time.\n\t */\n\tif (IsA(qual, Const))\n\t{\n\t\tConst *c = castNode(Const, qual);\n\t\tif (c->constisnull || !DatumGetBool(c->constvalue))\n\t\t{\n\t\t\t/*\n\t\t\t * Some predicates are evaluated to a null Const, like a\n\t\t\t * strict comparison with stable expression that evaluates to null.\n\t\t\t * No rows pass.\n\t\t\t */\n\t\t\tconst size_t n_batch_result_words = (vqstate->num_results + 63) / 64;\n\t\t\tfor (size_t i = 0; i < n_batch_result_words; i++)\n\t\t\t{\n\t\t\t\tresult[i] = 0;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/*\n\t\t\t * This is a constant true qual, every row passes and we can\n\t\t\t * just ignore it. No idea how it can happen though.\n\t\t\t */\n\t\t\tAssert(false);\n\t\t}\n\t\treturn;\n\t}\n\n\t/*\n\t * For now, we support NullTest, \"Var ? Const\" predicates,\n\t * boolean Variables, the negation of boolean variables\n\t * and ScalarArrayOperations.\n\t */\n\tList *args = NULL;\n\tRegProcedure vector_const_opcode = InvalidOid;\n\tScalarArrayOpExpr *saop = NULL;\n\tOpExpr *opexpr = NULL;\n\tNullTest *nulltest = NULL;\n\tBooleanTest *booltest = NULL;\n\tVar *bool_var = NULL;\n\tbool negate_bool_var = false;\n\tif (IsA(qual, NullTest))\n\t{\n\t\tnulltest = castNode(NullTest, qual);\n\t\targs = list_make1(nulltest->arg);\n\t}\n\telse if (IsA(qual, ScalarArrayOpExpr))\n\t{\n\t\tsaop = castNode(ScalarArrayOpExpr, qual);\n\t\targs = saop->args;\n\t\tvector_const_opcode = get_opcode(saop->opno);\n\t}\n\telse if (IsA(qual, Var))\n\t{\n\t\tbool_var = castNode(Var, qual);\n\t\tEnsure(bool_var->vartype == BOOLOID, \"expected boolean Var\");\n\t\targs = list_make1(bool_var);\n\t}\n\telse if (IsA(qual, BoolExpr))\n\t{\n\t\tBoolExpr *boolexpr = castNode(BoolExpr, qual);\n\t\tEnsure(boolexpr->boolop == NOT_EXPR, \"expected NOT BoolExpr\");\n\t\tEnsure(list_length(boolexpr->args) == 1, \"expected one argument in NOT BoolExpr\");\n\t\tEnsure(IsA(linitial(boolexpr->args), Var), \"expected Var in NOT BoolExpr\");\n\n\t\tbool_var = castNode(Var, linitial(boolexpr->args));\n\t\tEnsure(bool_var->vartype == BOOLOID, \"expected boolean Var\");\n\n\t\t/*\n\t\t * We can vectorize boolean variables like 'COL = false' which is\n\t\t * transformed to BoolExpr(NOT_EXPR, Var).\n\t\t */\n\t\tnegate_bool_var = true;\n\t\tbool_var = castNode(Var, linitial(boolexpr->args));\n\t\targs = list_make1(bool_var);\n\t}\n\telse if (IsA(qual, BooleanTest))\n\t{\n\t\tbooltest = castNode(BooleanTest, qual);\n\t\tEnsure(IsA(booltest->arg, Var), \"expected Var in BooleanTest\");\n\t\targs = list_make1(booltest->arg);\n\t}\n\telse\n\t{\n\t\tEnsure(IsA(qual, OpExpr), \"expected OpExpr\");\n\t\topexpr = castNode(OpExpr, qual);\n\t\targs = opexpr->args;\n\t\tvector_const_opcode = get_opcode(opexpr->opno);\n\t}\n\n\t/*\n\t * Find the compressed column referred to by the Var.\n\t */\n\tExpr *expr = linitial(args);\n\tuint64 default_value_predicate_result[1];\n\tuint64 *predicate_result = result;\n\tbool default_value = false;\n\tconst ArrowArray *vector = vqstate->get_arrow_array(vqstate, expr, &default_value);\n\n\tif (default_value)\n\t{\n\t\t/*\n\t\t * We start from an all-valid bitmap, because the predicate is\n\t\t * AND-ed to it.\n\t\t */\n\t\tdefault_value_predicate_result[0] = 1;\n\t\tpredicate_result = default_value_predicate_result;\n\t}\n\n\tif (nulltest)\n\t{\n\t\tvector_nulltest(vector, nulltest->nulltesttype, predicate_result);\n\t}\n\telse if (bool_var)\n\t{\n\t\tvector_booleantest(vector, (negate_bool_var ? IS_FALSE : IS_TRUE), predicate_result);\n\t}\n\telse if (booltest)\n\t{\n\t\tvector_booleantest(vector, booltest->booltesttype, predicate_result);\n\t}\n\telse\n\t{\n\t\t/*\n\t\t * Find the vector_const predicate.\n\t\t */\n\t\tVectorPredicate *vector_const_predicate = get_vector_const_predicate(vector_const_opcode);\n\t\tAssert(vector_const_predicate != NULL);\n\n\t\tEnsure(IsA(lsecond(args), Const),\n\t\t\t   \"failed to evaluate runtime constant in vectorized filter\");\n\n\t\t/*\n\t\t * The vectorizable predicates should be STRICT, so we shouldn't see null\n\t\t * constants here. However, a null constant can still come from a scalar\n\t\t * array expression like x = any(null::int[]). Such a predicate fails\n\t\t * for all rows.\n\t\t */\n\t\tConst *constnode = castNode(Const, lsecond(args));\n\t\tEnsure(saop != NULL || !constnode->constisnull,\n\t\t\t   \"vectorized predicate called for a null value\");\n\t\tif (constnode->constisnull)\n\t\t{\n\t\t\tconst size_t n_batch_result_words = (vqstate->num_results + 63) / 64;\n\t\t\tfor (size_t i = 0; i < n_batch_result_words; i++)\n\t\t\t{\n\t\t\t\tresult[i] = 0;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t/*\n\t\t * If the data is dictionary-encoded, we are going to compute the\n\t\t * predicate on dictionary and then translate the results.\n\t\t */\n\t\tconst ArrowArray *vector_nodict = NULL;\n\t\tuint64 *restrict predicate_result_nodict = NULL;\n\t\tuint64 dict_result[(GLOBAL_MAX_ROWS_PER_COMPRESSION + 63) / 64];\n\t\tif (vector->dictionary)\n\t\t{\n\t\t\tconst size_t dict_rows = vector->dictionary->length;\n\t\t\tconst size_t dict_result_words = (dict_rows + 63) / 64;\n\t\t\tmemset(dict_result, 0xFF, dict_result_words * 8);\n\t\t\tpredicate_result_nodict = dict_result;\n\t\t\tvector_nodict = vector->dictionary;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tpredicate_result_nodict = predicate_result;\n\t\t\tvector_nodict = vector;\n\t\t}\n\n\t\t/*\n\t\t * At last, compute the predicate.\n\t\t */\n\t\tif (saop)\n\t\t{\n\t\t\tvector_array_predicate(vector_const_predicate,\n\t\t\t\t\t\t\t\t   saop->useOr,\n\t\t\t\t\t\t\t\t   vector_nodict,\n\t\t\t\t\t\t\t\t   constnode->constvalue,\n\t\t\t\t\t\t\t\t   predicate_result_nodict);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tvector_const_predicate(vector_nodict, constnode->constvalue, predicate_result_nodict);\n\t\t}\n\n\t\t/*\n\t\t * If the vector is dictionary-encoded, we have just computed the\n\t\t * predicate for dictionary and now have to translate it.\n\t\t */\n\t\tif (vector->dictionary)\n\t\t{\n\t\t\ttranslate_bitmap_from_dictionary(vector, predicate_result_nodict, predicate_result);\n\t\t}\n\n\t\t/*\n\t\t * Account for nulls which shouldn't pass the predicate. Note that the\n\t\t * vector here might have only one row, in contrast with the number of\n\t\t * rows in the batch, if the column has a default value in this batch.\n\t\t */\n\t\tconst size_t n_vector_result_words = (vector->length + 63) / 64;\n\t\tAssert((predicate_result != default_value_predicate_result) ||\n\t\t\t   n_vector_result_words == 1); /* to placate Coverity. */\n\t\tconst uint64 *validity = (const uint64 *) vector->buffers[0];\n\t\tif (validity)\n\t\t{\n\t\t\tfor (size_t i = 0; i < n_vector_result_words; i++)\n\t\t\t{\n\t\t\t\tpredicate_result[i] &= validity[i];\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tAssert(vector->null_count == 0);\n\t\t}\n\t}\n\n\t/* Translate the result if the column had a default value. */\n\tif (default_value)\n\t{\n\t\tif (!(default_value_predicate_result[0] & 1))\n\t\t{\n\t\t\t/*\n\t\t\t * We had a default value for the compressed column, and it\n\t\t\t * didn't pass the predicate, so the entire batch didn't pass.\n\t\t\t */\n\t\t\tconst size_t n_batch_result_words = (vqstate->num_results + 63) / 64;\n\t\t\tfor (size_t i = 0; i < n_batch_result_words; i++)\n\t\t\t{\n\t\t\t\tresult[i] = 0;\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void compute_one_qual(VectorQualState *vqstate, TupleTableSlot *compressed_slot, Node *qual,\n\t\t\t\t\t\t\t uint64 *restrict result);\nstatic void\ncompute_qual_conjunction(VectorQualState *vqstate, TupleTableSlot *compressed_slot, List *quals,\n\t\t\t\t\t\t uint64 *restrict result)\n{\n\tListCell *lc;\n\tforeach (lc, quals)\n\t{\n\t\tcompute_one_qual(vqstate, compressed_slot, lfirst(lc), result);\n\t\tif (get_vector_qual_summary(result, vqstate->num_results) == NoRowsPass)\n\t\t{\n\t\t\t/*\n\t\t\t * Exit early if no rows pass already. This might allow us to avoid\n\t\t\t * reading the columns required for the subsequent quals.\n\t\t\t */\n\t\t\treturn;\n\t\t}\n\t}\n}\n\nstatic void\ncompute_qual_disjunction(VectorQualState *vqstate, TupleTableSlot *compressed_slot, List *quals,\n\t\t\t\t\t\t uint64 *restrict result)\n{\n\tconst size_t n_rows = vqstate->num_results;\n\tconst size_t n_result_words = (n_rows + 63) / 64;\n\tuint64 *or_result =\n\t\tMemoryContextAlloc(vqstate->per_vector_mcxt, (sizeof(uint64) * n_result_words));\n\tfor (size_t i = 0; i < n_result_words; i++)\n\t{\n\t\tor_result[i] = 0;\n\t}\n\n\tuint64 *one_qual_result =\n\t\tMemoryContextAlloc(vqstate->per_vector_mcxt, (sizeof(uint64) * n_result_words));\n\n\tListCell *lc;\n\tforeach (lc, quals)\n\t{\n\t\tfor (size_t i = 0; i < n_result_words; i++)\n\t\t{\n\t\t\tone_qual_result[i] = (uint64) -1;\n\t\t}\n\t\tcompute_one_qual(vqstate, compressed_slot, lfirst(lc), one_qual_result);\n\t\tfor (size_t i = 0; i < n_result_words; i++)\n\t\t{\n\t\t\tor_result[i] |= one_qual_result[i];\n\t\t}\n\n\t\tif (get_vector_qual_summary(or_result, n_rows) == AllRowsPass)\n\t\t{\n\t\t\t/*\n\t\t\t * We can sometimes avoing reading the columns required for the\n\t\t\t * rest of conditions if we break out early here.\n\t\t\t */\n\t\t\treturn;\n\t\t}\n\t}\n\n\tfor (size_t i = 0; i < n_result_words; i++)\n\t{\n\t\tresult[i] &= or_result[i];\n\t}\n}\n\nstatic void\ncompute_one_qual(VectorQualState *vqstate, TupleTableSlot *compressed_slot, Node *qual,\n\t\t\t\t uint64 *restrict result)\n{\n\tif (!IsA(qual, BoolExpr))\n\t{\n\t\tcompute_plain_qual(vqstate, compressed_slot, qual, result);\n\t\treturn;\n\t}\n\n\tBoolExpr *boolexpr = castNode(BoolExpr, qual);\n\tif (boolexpr->boolop == AND_EXPR)\n\t{\n\t\tcompute_qual_conjunction(vqstate, compressed_slot, boolexpr->args, result);\n\t\treturn;\n\t}\n\n\t/*\n\t * Postgres removes NOT for operators we can vectorize, so we don't support\n\t * NOT in general, except when the column is boolean and the expression is\n\t * like 'Col = false'. In this case, the NOT is present and we can vectorize\n\t * it.\n\t *\n\t * Apart from these cases, only OR is left.\n\t */\n\tif (boolexpr->boolop == NOT_EXPR)\n\t{\n\t\tif (list_length(boolexpr->args) == 1 && IsA(linitial(boolexpr->args), Var))\n\t\t{\n\t\t\tcompute_plain_qual(vqstate, compressed_slot, qual, result);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tEnsure(boolexpr->boolop == OR_EXPR, \"expected OR\");\n\tcompute_qual_disjunction(vqstate, compressed_slot, boolexpr->args, result);\n}\n\n/*\n * Compute the vectorized filters. Returns true if we have any passing rows. If not,\n * it means the entire batch is filtered out, and we use this for further\n * optimizations.\n */\nBatchQualSummary\nvector_qual_compute(VectorQualState *vqstate)\n{\n\t/*\n\t * Allocate the bitmap that will hold the vectorized qual results. We will\n\t * initialize it to all ones and AND the individual quals to it.\n\t */\n\tconst size_t n_rows = vqstate->num_results;\n\tconst int bitmap_bytes = sizeof(uint64) * ((n_rows + 63) / 64);\n\tvqstate->vector_qual_result = MemoryContextAlloc(vqstate->per_vector_mcxt, bitmap_bytes);\n\tmemset(vqstate->vector_qual_result, 0xFF, bitmap_bytes);\n\n\tif (n_rows % 64 != 0)\n\t{\n\t\t/*\n\t\t * We have to zero out the bits for past-the-end elements in the last\n\t\t * bitmap word. Since all predicates are ANDed to the result bitmap,\n\t\t * we can do it here once instead of doing it in each predicate.\n\t\t */\n\t\tconst uint64 mask = ((uint64) -1) >> (64 - vqstate->num_results % 64);\n\t\tvqstate->vector_qual_result[vqstate->num_results / 64] = mask;\n\t}\n\n\t/*\n\t * Compute the quals.\n\t */\n\tcompute_qual_conjunction(vqstate,\n\t\t\t\t\t\t\t vqstate->slot,\n\t\t\t\t\t\t\t vqstate->vectorized_quals_constified,\n\t\t\t\t\t\t\t vqstate->vector_qual_result);\n\n\treturn get_vector_qual_summary(vqstate->vector_qual_result, n_rows);\n}\n\n/*\n * Scrolls the compressed batch to the end, discarding any tuples left in it.\n * This makes the batch ready to accept the next compressed tuple, but without\n * de-initializing its expensive reusable parts such as memory context and tuple\n * slots. This is used when vectorized quals don't pass for the entire batch,\n * and also in batch array to free the batch state for reuse.\n */\nvoid\ncompressed_batch_discard_tuples(DecompressBatchState *batch_state)\n{\n\tbatch_state->next_batch_row = batch_state->total_batch_rows;\n\tbatch_state->vector_qual_result = NULL;\n\n\tif (batch_state->per_batch_context != NULL)\n\t{\n\t\tExecClearTuple(&batch_state->decompressed_scan_slot_data.base);\n\t\tMemoryContextReset(batch_state->per_batch_context);\n\t}\n\telse\n\t{\n\t\t/*\n\t\t * Check that we have a valid zero-initialized batch here.\n\t\t */\n\t\tAssert(IsA(&batch_state->decompressed_scan_slot_data, Invalid));\n\t\tAssert(batch_state->decompressed_scan_slot_data.base.tts_ops == NULL);\n\t}\n}\n\n/*\n * Initializes the zero-initialized batch state. We do this on demand, because\n * it involves the creation of memory context and tuple slots, which are\n * relatively expensive.\n */\nstatic void\ncompressed_batch_lazy_init(DecompressContext *dcontext, DecompressBatchState *batch_state)\n{\n\t/* Init memory context */\n\tbatch_state->per_batch_context = create_per_batch_mctx(dcontext);\n\tAssert(batch_state->per_batch_context != NULL);\n\n\t/* Get a reference to the decompressed scan TupleTableSlot */\n\tTupleTableSlot *decompressed_slot = dcontext->custom_scan_slot;\n\n\t/*\n\t * This code follows Postgres' MakeTupleTableSlot().\n\t */\n\tTupleTableSlot *slot = &batch_state->decompressed_scan_slot_data.base;\n\tAssert(IsA(slot, Invalid));\n\tAssert(slot->tts_ops == NULL);\n\n\tslot->type = T_TupleTableSlot;\n\tslot->tts_flags = TTS_FLAG_EMPTY | TTS_FLAG_FIXED;\n\n\t/*\n\t * The decompressed slot and the respective tuple descriptor are owned by\n\t * DecompressContext and live throughout the entire decompression process,\n\t * so here we can reuse a plain pointer to the tuple descriptor without\n\t * additional reference counting.\n\t */\n\tslot->tts_tupleDescriptor = decompressed_slot->tts_tupleDescriptor;\n\n\tslot->tts_mcxt = CurrentMemoryContext;\n\tslot->tts_nvalid = 0;\n\tslot->tts_values = palloc0(MAXALIGN(slot->tts_tupleDescriptor->natts * sizeof(Datum)) +\n\t\t\t\t\t\t\t   MAXALIGN(slot->tts_tupleDescriptor->natts * sizeof(bool)));\n\tslot->tts_isnull = (bool *) ((char *) slot->tts_values) +\n\t\t\t\t\t   MAXALIGN(slot->tts_tupleDescriptor->natts * sizeof(Datum));\n\n\t/*\n\t * Have to initially set nulls to true, because this is the uncompressed chunk\n\t * tuple, and some of its columns might be not even decompressed. The tuple\n\t * slot functions will get confused by them, because they expect a non-null\n\t * value for attributes not marked as null.\n\t */\n\tmemset(slot->tts_isnull, true, slot->tts_tupleDescriptor->natts * sizeof(bool));\n\n\t/*\n\t * ColumnarScan produces virtual tuple slots.\n\t */\n\t*((const TupleTableSlotOps **) &slot->tts_ops) = &TTSOpsVirtual;\n\tslot->tts_ops->init(slot);\n}\n\n/*\n * Initialize the batch decompression state with the new compressed  tuple.\n */\nvoid\ncompressed_batch_set_compressed_tuple(DecompressContext *dcontext,\n\t\t\t\t\t\t\t\t\t  DecompressBatchState *batch_state,\n\t\t\t\t\t\t\t\t\t  TupleTableSlot *compressed_slot)\n{\n\tAssert(TupIsNull(compressed_batch_current_tuple(batch_state)));\n\n\t/*\n\t * The batch states are initialized on demand, because creating the memory\n\t * context and the tuple table slots is expensive.\n\t */\n\tif (batch_state->per_batch_context == NULL)\n\t{\n\t\tcompressed_batch_lazy_init(dcontext, batch_state);\n\t}\n\tTupleTableSlot *decompressed_tuple = compressed_batch_current_tuple(batch_state);\n\tAssert(decompressed_tuple != NULL);\n\n\tbatch_state->total_batch_rows = 0;\n\tbatch_state->next_batch_row = 0;\n\n\tMemoryContextReset(batch_state->per_batch_context);\n\n\tfor (int i = 0; i < dcontext->num_columns_with_metadata; i++)\n\t{\n\t\tCompressionColumnDescription *column_description = &dcontext->compressed_chunk_columns[i];\n\n\t\tswitch (column_description->type)\n\t\t{\n\t\t\tcase COMPRESSED_COLUMN:\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * We decompress the compressed columns on demand, so that we can\n\t\t\t\t * skip decompressing some columns if the entire batch doesn't pass\n\t\t\t\t * the quals. Skip them for now.\n\t\t\t\t */\n\t\t\t\tAssert(i < dcontext->num_data_columns);\n\t\t\t\tCompressedColumnValues *column_values = &batch_state->compressed_columns[i];\n\t\t\t\tcolumn_values->decompression_type = DT_Invalid;\n\t\t\t\tcolumn_values->arrow = NULL;\n\t\t\t\tconst AttrNumber attr =\n\t\t\t\t\tAttrNumberGetAttrOffset(column_description->custom_scan_attno);\n\t\t\t\tcolumn_values->output_value = &decompressed_tuple->tts_values[attr];\n\t\t\t\tcolumn_values->output_isnull = &decompressed_tuple->tts_isnull[attr];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase SEGMENTBY_COLUMN:\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * A segmentby column is not going to change during one batch,\n\t\t\t\t * and our output tuples are read-only, so it's enough to only\n\t\t\t\t * save it once per batch, which we do here.\n\t\t\t\t */\n\t\t\t\tAssert(i < dcontext->num_data_columns);\n\t\t\t\tCompressedColumnValues *column_values = &batch_state->compressed_columns[i];\n\n\t\t\t\tbool isnull;\n\t\t\t\tDatum value = slot_getattr(compressed_slot,\n\t\t\t\t\t\t\t\t\t\t   column_description->compressed_scan_attno,\n\t\t\t\t\t\t\t\t\t\t   &isnull);\n\n\t\t\t\t/*\n\t\t\t\t * Note that if it's not a by-value type, we should copy it into\n\t\t\t\t * the slot context.\n\t\t\t\t */\n\t\t\t\tif (!column_description->by_value && !isnull && DatumGetPointer(value) != NULL)\n\t\t\t\t{\n\t\t\t\t\tif (column_description->value_bytes < 0)\n\t\t\t\t\t{\n\t\t\t\t\t\t/* This is a varlena type. */\n\t\t\t\t\t\tvalue = PointerGetDatum(\n\t\t\t\t\t\t\tdetoaster_detoast_attr_copy((struct varlena *) value,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t&dcontext->detoaster,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbatch_state->per_batch_context));\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\t/* This is a fixed-length by-reference type. */\n\t\t\t\t\t\tvoid *tmp = MemoryContextAlloc(batch_state->per_batch_context,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   column_description->value_bytes);\n\t\t\t\t\t\tmemcpy(tmp, DatumGetPointer(value), column_description->value_bytes);\n\t\t\t\t\t\tvalue = PointerGetDatum(tmp);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst AttrNumber attr =\n\t\t\t\t\tAttrNumberGetAttrOffset(column_description->custom_scan_attno);\n\t\t\t\tcolumn_values->output_value = &decompressed_tuple->tts_values[attr];\n\t\t\t\tcolumn_values->output_isnull = &decompressed_tuple->tts_isnull[attr];\n\t\t\t\tdecompress_scalar_column(column_values, value, isnull);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase COUNT_COLUMN:\n\t\t\t{\n\t\t\t\tbool isnull;\n\t\t\t\tDatum value = slot_getattr(compressed_slot,\n\t\t\t\t\t\t\t\t\t\t   column_description->compressed_scan_attno,\n\t\t\t\t\t\t\t\t\t\t   &isnull);\n\t\t\t\t/* count column should never be NULL */\n\t\t\t\tAssert(!isnull);\n\t\t\t\tint count_value = DatumGetInt32(value);\n\t\t\t\tif (count_value <= 0)\n\t\t\t\t{\n\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t(errmsg(\"the compressed data is corrupt: got a segment with length %d\",\n\t\t\t\t\t\t\t\t\tcount_value)));\n\t\t\t\t}\n\n\t\t\t\tAssert(batch_state->total_batch_rows == 0);\n\t\t\t\tCheckCompressedData(count_value <= UINT16_MAX);\n\t\t\t\tbatch_state->total_batch_rows = count_value;\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase SEQUENCE_NUM_COLUMN:\n\t\t\t\t/*\n\t\t\t\t * nothing to do here for sequence number\n\t\t\t\t * we only needed this for sorting in node below\n\t\t\t\t */\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tCompressedBatchVectorQualState cbvqstate = {\n\t\t.vqstate = {\n\t\t\t.vectorized_quals_constified = dcontext->vectorized_quals_constified,\n\t\t\t.num_results = batch_state->total_batch_rows,\n\t\t\t.per_vector_mcxt = batch_state->per_batch_context,\n\t\t\t.slot = compressed_slot,\n\t\t\t.get_arrow_array = compressed_batch_get_arrow_array,\n\t\t},\n\t\t.batch_state = batch_state,\n\t\t.dcontext = dcontext,\n\t};\n\tVectorQualState *vqstate = &cbvqstate.vqstate;\n\n\tBatchQualSummary vector_qual_summary =\n\t\tvqstate->vectorized_quals_constified != NIL ? vector_qual_compute(vqstate) : AllRowsPass;\n\n\tbatch_state->vector_qual_result = vqstate->vector_qual_result;\n\n\tif (vector_qual_summary == NoRowsPass && !dcontext->batch_sorted_merge)\n\t{\n\t\t/*\n\t\t * The entire batch doesn't pass the vectorized quals, so we might be\n\t\t * able to avoid reading and decompressing other columns. Scroll it to\n\t\t * the end.\n\t\t * Note that this optimization can't work with \"batch sorted merge\",\n\t\t * because the latter always has to read the first row of the batch for\n\t\t * its sorting needs, so it always has to read and decompress all\n\t\t * columns. This can be improved by only decompressing the columns\n\t\t * needed for sorting.\n\t\t */\n\t\tcompressed_batch_discard_tuples(batch_state);\n\n\t\tInstrCountTuples2(dcontext->ps, 1);\n\t\tInstrCountFiltered1(dcontext->ps, batch_state->total_batch_rows);\n\t}\n\telse\n\t{\n\t\t/*\n\t\t * We have some rows in the batch that pass the vectorized filters, so\n\t\t * we have to decompress the rest of the compressed columns.\n\t\t */\n\t\tconst int num_data_columns = dcontext->num_data_columns;\n\t\tfor (int i = 0; i < num_data_columns; i++)\n\t\t{\n\t\t\tCompressedColumnValues *column_values = &batch_state->compressed_columns[i];\n\t\t\tif (column_values->decompression_type == DT_Invalid)\n\t\t\t{\n\t\t\t\tdecompress_column(dcontext, batch_state, compressed_slot, i);\n\t\t\t\tAssert(column_values->decompression_type != DT_Invalid);\n\t\t\t}\n\t\t}\n\n\t\t/*\n\t\t * If all rows pass, no need to test the vector qual for each row. This\n\t\t * is a common case for time range conditions.\n\t\t */\n\t\tif (vector_qual_summary == AllRowsPass)\n\t\t{\n\t\t\tbatch_state->vector_qual_result = NULL;\n\t\t\tvqstate->vector_qual_result = NULL;\n\t\t}\n\t}\n}\n\n/*\n * Construct the next tuple in the decompressed scan slot.\n * Doesn't check the quals.\n */\nstatic void\nmake_next_tuple(DecompressBatchState *batch_state, uint16 arrow_row, int num_data_columns)\n{\n\tTupleTableSlot *decompressed_scan_slot = &batch_state->decompressed_scan_slot_data.base;\n\n\tAssert(batch_state->total_batch_rows > 0);\n\tAssert(batch_state->next_batch_row < batch_state->total_batch_rows);\n\n\tcompressed_columns_to_postgres_data(batch_state->compressed_columns,\n\t\t\t\t\t\t\t\t\t\tnum_data_columns,\n\t\t\t\t\t\t\t\t\t\tarrow_row);\n\n\t/*\n\t * It's a virtual tuple slot, so no point in clearing/storing it\n\t * per each row, we can just update the values in-place. This saves\n\t * some CPU. We have to store it after ExecQual returns false (the tuple\n\t * didn't pass the filter), or after a new batch. The standard protocol\n\t * is to clear and set the tuple slot for each row, but our output tuple\n\t * slots are read-only, and the memory is owned by this node, so it is\n\t * safe to violate this protocol.\n\t */\n\tAssert(TTS_IS_VIRTUAL(decompressed_scan_slot));\n\tif (TTS_EMPTY(decompressed_scan_slot))\n\t{\n\t\tExecStoreVirtualTuple(decompressed_scan_slot);\n\t}\n}\n\nstatic bool\nvector_qual(DecompressBatchState *batch_state, uint16 arrow_row)\n{\n\tAssert(batch_state->total_batch_rows > 0);\n\tAssert(batch_state->next_batch_row < batch_state->total_batch_rows);\n\n\tif (!batch_state->vector_qual_result)\n\t{\n\t\treturn true;\n\t}\n\n\treturn arrow_row_is_valid(batch_state->vector_qual_result, arrow_row);\n}\n\nstatic bool\npostgres_qual(DecompressContext *dcontext, DecompressBatchState *batch_state)\n{\n\tTupleTableSlot *decompressed_scan_slot = &batch_state->decompressed_scan_slot_data.base;\n\tAssert(IsA(decompressed_scan_slot, TupleTableSlot));\n\tAssert(!TupIsNull(decompressed_scan_slot));\n\n\tif (dcontext->ps == NULL || dcontext->ps->qual == NULL)\n\t{\n\t\treturn true;\n\t}\n\n\t/* Perform the usual Postgres selection. */\n\tExprContext *econtext = dcontext->ps->ps_ExprContext;\n\tecontext->ecxt_scantuple = decompressed_scan_slot;\n\tResetExprContext(econtext);\n\treturn ExecQual(dcontext->ps->qual, econtext);\n}\n\n/*\n * Decompress the next tuple from the batch indicated by batch state. The result is stored\n * in batch_state->decompressed_scan_slot. The slot will be empty if the batch\n * is entirely processed.\n */\nvoid\ncompressed_batch_advance(DecompressContext *dcontext, DecompressBatchState *batch_state)\n{\n\tAssert(batch_state->total_batch_rows > 0);\n\n\tTupleTableSlot *decompressed_scan_slot = &batch_state->decompressed_scan_slot_data.base;\n\n\tconst bool reverse = dcontext->reverse;\n\tconst int num_data_columns = dcontext->num_data_columns;\n\n\tfor (; batch_state->next_batch_row < batch_state->total_batch_rows;\n\t\t batch_state->next_batch_row++)\n\t{\n\t\tconst uint16 output_row = batch_state->next_batch_row;\n\t\tconst uint16 arrow_row =\n\t\t\tunlikely(reverse) ? batch_state->total_batch_rows - 1 - output_row : output_row;\n\n\t\tif (!vector_qual(batch_state, arrow_row))\n\t\t{\n\t\t\t/*\n\t\t\t * This row doesn't pass the vectorized quals. Advance the iterated\n\t\t\t * compressed columns if we have any.\n\t\t\t */\n\t\t\tfor (int i = 0; i < num_data_columns; i++)\n\t\t\t{\n\t\t\t\tCompressedColumnValues *column_values = &batch_state->compressed_columns[i];\n\t\t\t\tif (column_values->decompression_type == DT_Iterator)\n\t\t\t\t{\n\t\t\t\t\tDecompressionIterator *iterator =\n\t\t\t\t\t\t(DecompressionIterator *) column_values->buffers[0];\n\t\t\t\t\titerator->try_next(iterator);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tInstrCountFiltered1(dcontext->ps, 1);\n\t\t\tcontinue;\n\t\t}\n\n\t\tmake_next_tuple(batch_state, arrow_row, num_data_columns);\n\n\t\tif (!postgres_qual(dcontext, batch_state))\n\t\t{\n\t\t\t/*\n\t\t\t * The tuple didn't pass the qual, fetch the next one in the next\n\t\t\t * iteration.\n\t\t\t */\n\t\t\tInstrCountFiltered1(dcontext->ps, 1);\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* The tuple passed the qual. */\n\t\tbatch_state->next_batch_row++;\n\t\treturn;\n\t}\n\n\t/*\n\t * Reached end of batch. Check that the columns that we're decompressing\n\t * row-by-row have also ended.\n\t */\n\tAssert(batch_state->next_batch_row == batch_state->total_batch_rows);\n\tfor (int i = 0; i < num_data_columns; i++)\n\t{\n\t\tCompressedColumnValues *column_values = &batch_state->compressed_columns[i];\n\t\tif (column_values->decompression_type == DT_Iterator)\n\t\t{\n\t\t\tDecompressionIterator *iterator = (DecompressionIterator *) column_values->buffers[0];\n\t\t\tDecompressResult result = iterator->try_next(iterator);\n\t\t\tif (!result.is_done)\n\t\t\t{\n\t\t\t\telog(ERROR, \"compressed column out of sync with batch counter\");\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Clear old slot state */\n\tExecClearTuple(decompressed_scan_slot);\n}\n\n/*\n * Before loading the first matching tuple from the batch, also save the very\n * first one into the given slot, even if it doesn't pass the quals. This is\n * needed for batch sorted merge.\n */\nvoid\ncompressed_batch_save_first_tuple(DecompressContext *dcontext, DecompressBatchState *batch_state,\n\t\t\t\t\t\t\t\t  TupleTableSlot *first_tuple_slot)\n{\n\tAssert(batch_state->next_batch_row == 0);\n\tAssert(batch_state->total_batch_rows > 0);\n\tAssert(TupIsNull(compressed_batch_current_tuple(batch_state)));\n\n\t/*\n\t * Check that we have decompressed all columns even if the vector quals\n\t * didn't pass for the entire batch. We need them because we're asked\n\t * to save the first tuple. This doesn't actually happen yet, because the\n\t * vectorized decompression is disabled with sorted merge.\n\t */\n#ifdef USE_ASSERT_CHECKING\n\tconst int num_data_columns = dcontext->num_data_columns;\n\tfor (int i = 0; i < num_data_columns; i++)\n\t{\n\t\tCompressedColumnValues *column_values = &batch_state->compressed_columns[i];\n\t\tAssert(column_values->decompression_type != DT_Invalid);\n\t}\n#endif\n\n\t/* Make the first tuple and save it. */\n\tAssert(batch_state->next_batch_row == 0);\n\tconst uint16 arrow_row = dcontext->reverse ? batch_state->total_batch_rows - 1 : 0;\n\tmake_next_tuple(batch_state, arrow_row, dcontext->num_data_columns);\n\tExecCopySlot(first_tuple_slot, &batch_state->decompressed_scan_slot_data.base);\n\n\t/*\n\t * Check the quals and advance, so that the batch is in the correct state\n\t * for the subsequent calls (matching tuple is in decompressed scan slot).\n\t */\n\tconst bool qual_passed =\n\t\tvector_qual(batch_state, arrow_row) && postgres_qual(dcontext, batch_state);\n\tbatch_state->next_batch_row++;\n\n\tif (!qual_passed)\n\t{\n\t\tInstrCountFiltered1(dcontext->ps, 1);\n\t\tcompressed_batch_advance(dcontext, batch_state);\n\t}\n}\n\n/*\n * Frees all resources used by the compressed batch.\n *\n * If the batch is intended to be reused, use compressed_batch_discard_tuples()\n * instead.\n */\nvoid\ncompressed_batch_destroy(DecompressBatchState *batch_state)\n{\n\tAssert(batch_state != NULL);\n\n\tif (batch_state->per_batch_context != NULL)\n\t{\n\t\tMemoryContextDelete(batch_state->per_batch_context);\n\t\tbatch_state->per_batch_context = NULL;\n\t}\n\n\tif (batch_state->decompressed_scan_slot_data.base.tts_values != NULL)\n\t{\n\t\t/*\n\t\t * Can be separately NULL in the current simplified prototype for\n\t\t * vectorized aggregation, but ideally it should change together with\n\t\t * per-batch context.\n\t\t */\n\t\tpfree(batch_state->decompressed_scan_slot_data.base.tts_values);\n\t\tbatch_state->decompressed_scan_slot_data.base.tts_values = NULL;\n\t}\n}\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/compressed_batch.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include \"compression/compression.h\"\n#include \"nodes/columnar_scan/decompress_context.h\"\n#include \"nodes/columnar_scan/vector_quals.h\"\n#include <executor/tuptable.h>\n\ntypedef struct ArrowArray ArrowArray;\n\n/* How to obtain the decompressed datum for individual row. */\ntypedef enum\n{\n\t/*\n\t * The decompressed value is a boolean and is stored as a vector of bits.\n\t */\n\tDT_ArrowBits = -5,\n\n\tDT_ArrowTextDict = -4,\n\n\tDT_ArrowText = -3,\n\n\t/*\n\t * The decompressed value is already in the decompressed slot. This is used\n\t * for segmentby and compressed columns with default value in batch.\n\t */\n\tDT_Scalar = -2,\n\n\tDT_Iterator = -1,\n\n\tDT_Invalid = 0,\n\n\t/*\n\t * Any positive number is also valid for the decompression type. It means\n\t * arrow array of a fixed-size by-value type, with size in bytes given by\n\t * the number.\n\t */\n} DecompressionType;\n\ntypedef struct CompressedColumnValues\n{\n\t/* How to obtain the decompressed datum for individual row. */\n\tDecompressionType decompression_type;\n\n\t/* Where to put the decompressed datum. */\n\tDatum *output_value;\n\tbool *output_isnull;\n\n\t/*\n\t * The flattened source buffers for getting the decompressed datum.\n\t * Depending on decompression type, they are as follows:\n\t * scalar:          isnull, value\n\t * iterator:        iterator\n\t * arrow fixed:     validity, value\n\t * arrow text:      validity, uint32* offsets, void* bodies\n\t * arrow dict text: validity, uint32* dict offsets, void* dict bodies, int16* indices\n\t */\n\tconst void *restrict buffers[4];\n\n\t/*\n\t * The source arrow array, if any. We don't use it for building the\n\t * individual rows, and use the flattened buffers instead to lessen the\n\t * amount of indirections. However, it is used for vectorized filters.\n\t */\n\tArrowArray *arrow;\n} CompressedColumnValues;\n\n/*\n * All the information needed to decompress a batch.\n */\ntypedef struct DecompressBatchState\n{\n\t/*\n\t * The slot for the decompressed tuple.\n\t *\n\t * We embed it into the batch state as the first member (data inheritance),\n\t * so that it's easier to pass out to parent nodes, while following the usual\n\t * Postgres interface of passing the tuple table slots.\n\t * We use &batch_state->decompressed_scan_slot_data.base everywhere where we\n\t * need the TupleTableSlot*, and some parent nodes can cast this pointer to\n\t * DecompressBatchState* to use our custom interfaces.\n\t *\n\t * The slot itself follows the TTSVirtualOps tuple slot protocol, because Postgres\n\t * expression executor has special fast path for virtual tuples, and we don't\n\t * really need the custom tuple slot protocol for anything. One potential use\n\t * case for it would be late decompression by implementing custom slot_getattr().\n\t * It was actually implemented and didn't show any benefits in the preliminary\n\t * testing, compared to what we already achieve with lazy decompression after\n\t * vectorized filters. One reason is that the Postgres expression compiler\n\t * can be eager in requesting materialization. For example, it would call\n\t * slot_getattr up to the last attribute used by every filter in a qualifier,\n\t * before running any qualifiers. This might be possible to configure, but\n\t * the area needs more research.\n\t *\n\t * See the PR #6628 for context.\n\t */\n\tVirtualTupleTableSlot decompressed_scan_slot_data;\n\n\tuint16 total_batch_rows;\n\tuint16 next_batch_row;\n\tMemoryContext per_batch_context;\n\n\t/*\n\t * Arrow-style bitmap that says whether the vector quals passed for a given\n\t * row. Indexed same as arrow arrays, w/o accounting for the reverse scan\n\t * direction. Initialized to all ones, i.e. all rows pass.\n\t */\n\tconst uint64 *restrict vector_qual_result;\n\n\t/*\n\t * This follows DecompressContext.compressed_chunk_columns, but does not\n\t * include the trailing metadata columns, but only the leading data columns.\n\t * These columns are compressed and segmentby columns, their total number is\n\t * given by DecompressContext.num_data_columns.\n\t */\n\tCompressedColumnValues compressed_columns[FLEXIBLE_ARRAY_MEMBER];\n} DecompressBatchState;\n\nextern void compressed_batch_set_compressed_tuple(DecompressContext *dcontext,\n\t\t\t\t\t\t\t\t\t\t\t\t  DecompressBatchState *batch_state,\n\t\t\t\t\t\t\t\t\t\t\t\t  TupleTableSlot *compressed_slot);\n\nextern void compressed_batch_advance(DecompressContext *dcontext,\n\t\t\t\t\t\t\t\t\t DecompressBatchState *batch_state);\n\nextern void compressed_batch_save_first_tuple(DecompressContext *dcontext,\n\t\t\t\t\t\t\t\t\t\t\t  DecompressBatchState *batch_state,\n\t\t\t\t\t\t\t\t\t\t\t  TupleTableSlot *first_tuple_slot);\n\n/*\n * Initialize the batch memory context and bulk decompression context.\n *\n * We use Generation context here because the AllocSet has a hardcoded threshold\n * of 8kB per allocation, after which it allocates directly through malloc. We\n * want to make the blocks as big as possible, but below the malloc's mmap\n * threshold. For small queries, these contexts are basically single-shot and\n * the page faults after an mmap slow them down significantly. The threshold\n * should be 128 kiB according to the docs, but I'm seeing 64 kiB in testing.\n *\n * If bulk decompression is not used, use the default size for batch context.\n * This reduces memory usage and improves performance with batch sorted merge.\n */\n#define create_bulk_decompression_mctx(parent_mctx)                                                \\\n\tGenerationContextCreate(parent_mctx,                                                           \\\n\t\t\t\t\t\t\t\"DecompressBatchState bulk decompression\",                             \\\n\t\t\t\t\t\t\t0,                                                                     \\\n\t\t\t\t\t\t\t64 * 1024,                                                             \\\n\t\t\t\t\t\t\t64 * 1024);\n\n#define create_per_batch_mctx(dcontext)                                                            \\\n\tGenerationContextCreate(CurrentMemoryContext,                                                  \\\n\t\t\t\t\t\t\t\"DecompressBatchState per-batch\",                                      \\\n\t\t\t\t\t\t\t0,                                                                     \\\n\t\t\t\t\t\t\tdcontext->enable_bulk_decompression ? 64 * 1024 : 8 * 1024,            \\\n\t\t\t\t\t\t\tdcontext->enable_bulk_decompression ? 64 * 1024 : 8 * 1024);\n\nextern void compressed_batch_destroy(DecompressBatchState *batch_state);\n\nextern void compressed_batch_discard_tuples(DecompressBatchState *batch_state);\n\n/*\n * Returns the current decompressed tuple in the compressed batch.\n */\ninline static TupleTableSlot *\ncompressed_batch_current_tuple(DecompressBatchState *batch_state)\n{\n\tif (IsA(&batch_state->decompressed_scan_slot_data, Invalid))\n\t{\n\t\t/*\n\t\t * For convenience, we want a zero-initialized batch to be a valid\n\t\t * \"empty\" state, but unfortunately a zero-initialized TupleTableSlotData\n\t\t * is not a valid tuple slot, so here we have to work around this mismatch.\n\t\t */\n\t\tAssert(batch_state->decompressed_scan_slot_data.base.tts_ops == NULL);\n\t\tAssert(batch_state->per_batch_context == NULL);\n\t\treturn NULL;\n\t}\n\n\tAssert(batch_state->per_batch_context != NULL);\n\treturn &batch_state->decompressed_scan_slot_data.base;\n}\n\n/*\n * VectorQualState for a compressed batch used to pass\n * ColumnarScan-specific data to vector qual functions that are shared\n * across scan nodes.\n */\ntypedef struct CompressedBatchVectorQualState\n{\n\tVectorQualState vqstate;\n\tDecompressBatchState *batch_state;\n\tDecompressContext *dcontext;\n} CompressedBatchVectorQualState;\n\nconst ArrowArray *compressed_batch_get_arrow_array(VectorQualState *vqstate, Expr *expr,\n\t\t\t\t\t\t\t\t\t\t\t\t   bool *is_default_value);\nint get_max_varlena_bytes(ArrowArray *text_array);\n\ninline static void\nstore_text_datum(CompressedColumnValues *column_values, int arrow_row)\n{\n\tconst uint32 start = ((uint32 *) column_values->buffers[1])[arrow_row];\n\tconst int32 value_bytes = ((uint32 *) column_values->buffers[1])[arrow_row + 1] - start;\n\tAssert(value_bytes >= 0);\n\n\tconst int total_bytes = value_bytes + VARHDRSZ;\n\tAssert(DatumGetPointer(*column_values->output_value) != NULL);\n\tSET_VARSIZE(*column_values->output_value, total_bytes);\n\tmemcpy(VARDATA(*column_values->output_value),\n\t\t   &((uint8 *) column_values->buffers[2])[start],\n\t\t   value_bytes);\n}\n\nstatic pg_attribute_always_inline void\ncompressed_columns_to_postgres_data(CompressedColumnValues *columns, int num_data_columns,\n\t\t\t\t\t\t\t\t\tuint16 arrow_row)\n{\n\tfor (int i = 0; i < num_data_columns; i++)\n\t{\n\t\tCompressedColumnValues *column_values = &columns[i];\n\t\tswitch ((int) column_values->decompression_type)\n\t\t{\n\t\t\tcase DT_Iterator:\n\t\t\t{\n\t\t\t\tDecompressionIterator *iterator =\n\t\t\t\t\t(DecompressionIterator *) column_values->buffers[0];\n\t\t\t\tDecompressResult result = iterator->try_next(iterator);\n\n\t\t\t\tif (result.is_done)\n\t\t\t\t{\n\t\t\t\t\telog(ERROR, \"compressed column out of sync with batch counter\");\n\t\t\t\t}\n\n\t\t\t\t*column_values->output_isnull = result.is_null;\n\t\t\t\t*column_values->output_value = result.val;\n\t\t\t\tbreak;\n\t\t\t}\n#ifndef USE_FLOAT8_BYVAL\n\t\t\tcase 8:\n#endif\n\t\t\tcase 16:\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * Fixed-width by-reference type that doesn't fit into a Datum.\n\t\t\t\t * For now this only happens for 8-byte types on 32-bit systems,\n\t\t\t\t * but eventually we could also use it for bigger by-value types\n\t\t\t\t * such as UUID.\n\t\t\t\t */\n\t\t\t\tconst uint8 value_bytes = column_values->decompression_type;\n\t\t\t\tconst char *src = column_values->buffers[1];\n\t\t\t\t*column_values->output_value = PointerGetDatum(&src[value_bytes * arrow_row]);\n\t\t\t\t*column_values->output_isnull =\n\t\t\t\t\t!arrow_row_is_valid(column_values->buffers[0], arrow_row);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase DT_ArrowBits:\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * The DT_ArrowBits type is a special case, because the value is\n\t\t\t\t * stored as an Array of bits.\n\t\t\t\t */\n\t\t\t\t*column_values->output_value =\n\t\t\t\t\tBoolGetDatum(arrow_row_is_valid(column_values->buffers[1], arrow_row));\n\t\t\t\t*column_values->output_isnull =\n\t\t\t\t\t!arrow_row_is_valid(column_values->buffers[0], arrow_row);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 2:\n\t\t\tcase 4:\n#ifdef USE_FLOAT8_BYVAL\n\t\t\tcase 8:\n#endif\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * Fixed-width by-value type that fits into a Datum.\n\t\t\t\t *\n\t\t\t\t * The conversion of Datum to more narrow types will truncate\n\t\t\t\t * the higher bytes, so we don't care if we read some garbage\n\t\t\t\t * into them, and can always read 8 bytes. These are unaligned\n\t\t\t\t * reads, so technically we have to do memcpy.\n\t\t\t\t */\n\t\t\t\tconst uint8 value_bytes = column_values->decompression_type;\n\t\t\t\tAssert(value_bytes <= SIZEOF_DATUM);\n\t\t\t\tconst char *src = column_values->buffers[1];\n\t\t\t\tmemcpy(column_values->output_value, &src[value_bytes * arrow_row], SIZEOF_DATUM);\n\t\t\t\t*column_values->output_isnull =\n\t\t\t\t\t!arrow_row_is_valid(column_values->buffers[0], arrow_row);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase DT_ArrowText:\n\t\t\t{\n\t\t\t\tstore_text_datum(column_values, arrow_row);\n\t\t\t\t*column_values->output_isnull =\n\t\t\t\t\t!arrow_row_is_valid(column_values->buffers[0], arrow_row);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase DT_ArrowTextDict:\n\t\t\t{\n\t\t\t\tconst int16 index = ((int16 *) column_values->buffers[3])[arrow_row];\n\t\t\t\tstore_text_datum(column_values, index);\n\t\t\t\t*column_values->output_isnull =\n\t\t\t\t\t!arrow_row_is_valid(column_values->buffers[0], arrow_row);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\t/* A compressed column with default value, do nothing. */\n\t\t\t\tAssert(column_values->decompression_type == DT_Scalar);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/decompress_context.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#ifndef TIMESCALEDB_DECOMPRESS_CONTEXT_H\n#define TIMESCALEDB_DECOMPRESS_CONTEXT_H\n\n#include <postgres.h>\n#include <access/attnum.h>\n#include <executor/tuptable.h>\n#include <nodes/execnodes.h>\n#include <nodes/pg_list.h>\n\n#include \"batch_array.h\"\n#include \"detoaster.h\"\n\ntypedef enum CompressionColumnType\n{\n\tSEGMENTBY_COLUMN,\n\tCOMPRESSED_COLUMN,\n\tCOUNT_COLUMN,\n\tSEQUENCE_NUM_COLUMN,\n} CompressionColumnType;\n\ntypedef struct CompressionColumnDescription\n{\n\tCompressionColumnType type;\n\tOid typid;\n\tint16 value_bytes;\n\tbool by_value;\n\n\t/*\n\t * Attno of the decompressed column in the scan tuple of ColumnarScan node.\n\t * Negative values are special columns that do not have a representation in\n\t * the decompressed chunk, but are still used for decompression. The `type`\n\t * field is set accordingly for these columns.\n\t */\n\tAttrNumber custom_scan_attno;\n\n\t/*\n\t * Attno of this column in the uncompressed chunks. We use it to fetch the\n\t * default value from the uncompressed chunk tuple descriptor.\n\t */\n\tAttrNumber uncompressed_chunk_attno;\n\n\t/*\n\t * Attno of the compressed column in the input compressed chunk scan.\n\t */\n\tAttrNumber compressed_scan_attno;\n\n\tbool bulk_decompression_supported;\n} CompressionColumnDescription;\n\ntypedef struct DecompressContext\n{\n\t/*\n\t * Note that this array contains only those columns that are decompressed\n\t * (output_attno != 0), and the order is different from the compressed chunk\n\t * tuple order: first go the actual data columns, and after that the metadata\n\t * columns.\n\t */\n\tCompressionColumnDescription *compressed_chunk_columns;\n\n\t/*\n\t * This includes all decompressed columns (output_attno != 0), including the\n\t * metadata columns.\n\t */\n\tint num_columns_with_metadata;\n\n\t/* This excludes the metadata columns. */\n\tint num_data_columns;\n\n\tList *vectorized_quals_constified;\n\tbool reverse;\n\tbool batch_sorted_merge; /* Batch sorted merge optimization enabled. */\n\tbool enable_bulk_decompression;\n\n\t/*\n\t * Scratch space for bulk decompression which might need a lot of temporary\n\t * data.\n\t */\n\tMemoryContext bulk_decompression_context;\n\n\tTupleTableSlot *custom_scan_slot;\n\n\t/*\n\t * The scan tuple descriptor might be different from the uncompressed chunk\n\t * one, and it doesn't have the default column values in that case, so we\n\t * have to fetch the default values from the uncompressed chunk tuple\n\t * descriptor which we store here.\n\t */\n\tTupleDesc uncompressed_chunk_tdesc;\n\n\tPlanState *ps; /* Set for filtering and instrumentation */\n\n\tDetoaster detoaster;\n\n\tint32 chunk_status;\n\n} DecompressContext;\n\n#endif /* TIMESCALEDB_DECOMPRESS_CONTEXT_H */\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/detoaster.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include \"detoaster.h\"\n\n#include <access/detoast.h>\n#include <access/genam.h>\n#include <access/heaptoast.h>\n#include <access/relscan.h>\n#include <access/skey.h>\n#include <access/stratnum.h>\n#include <access/table.h>\n#include <access/tableam.h>\n#include <access/toast_internals.h>\n#include <utils/expandeddatum.h>\n#include <utils/fmgroids.h>\n#include <utils/rel.h>\n#include <utils/relcache.h>\n\n#include <compat/compat.h>\n#include \"debug_assert.h\"\n#include <compression/compression.h>\n\n/* We redefine this postgres macro to fix a warning about signed integer comparison. */\n#define TS_VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer)                                            \\\n\t(((int32) VARATT_EXTERNAL_GET_EXTSIZE(toast_pointer)) < (toast_pointer).va_rawsize - VARHDRSZ)\n\n/*\n * Fetch a TOAST slice from a heap table.\n *\n * This function is a modified copy of heap_fetch_toast_slice(). The difference\n * is that it holds the open toast relation, index and other intermediate data\n * for detoasting in the Detoaster struct, to allow them to be reused over many\n * input tuples.\n */\nstatic void\nts_fetch_toast(Detoaster *detoaster, struct varatt_external *toast_pointer, struct varlena *result)\n{\n\tconst Oid valueid = toast_pointer->va_valueid;\n\n\t/*\n\t * Open the toast relation and its indexes\n\t */\n\tif (detoaster->toastrel == NULL)\n\t{\n\t\tMemoryContext old_mctx = MemoryContextSwitchTo(detoaster->mctx);\n\t\tdetoaster->toastrel = table_open(toast_pointer->va_toastrelid, AccessShareLock);\n\n\t\tint num_indexes;\n\t\tRelation *toastidxs;\n\t\t/* Look for the valid index of toast relation */\n\t\tconst int validIndex =\n\t\t\ttoast_open_indexes(detoaster->toastrel, AccessShareLock, &toastidxs, &num_indexes);\n\t\tdetoaster->index = toastidxs[validIndex];\n\t\tfor (int i = 0; i < num_indexes; i++)\n\t\t{\n\t\t\tif (i != validIndex)\n\t\t\t{\n\t\t\t\tindex_close(toastidxs[i], AccessShareLock);\n\t\t\t}\n\t\t}\n\n\t\t/* Set up a scan key to fetch from the index. */\n\t\tScanKeyInit(&detoaster->toastkey,\n\t\t\t\t\t(AttrNumber) 1,\n\t\t\t\t\tBTEqualStrategyNumber,\n\t\t\t\t\tF_OIDEQ,\n\t\t\t\t\tObjectIdGetDatum(valueid));\n\n\t\t/* Prepare for scan */\n#if PG18_GE\n\t\tdetoaster->SnapshotToast = *get_toast_snapshot();\n#else\n\t\tinit_toast_snapshot(&detoaster->SnapshotToast);\n#endif\n\t\tdetoaster->toastscan = systable_beginscan_ordered(detoaster->toastrel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  detoaster->index,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  &detoaster->SnapshotToast,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  &detoaster->toastkey);\n\t\tMemoryContextSwitchTo(old_mctx);\n\t}\n\telse\n\t{\n\t\tEnsure(detoaster->toastrel->rd_id == toast_pointer->va_toastrelid,\n\t\t\t   \"unexpected toast pointer relid %d, expected %d\",\n\t\t\t   toast_pointer->va_toastrelid,\n\t\t\t   detoaster->toastrel->rd_id);\n\t\tdetoaster->toastkey.sk_argument = ObjectIdGetDatum(valueid);\n\t\tindex_rescan(detoaster->toastscan->iscan, &detoaster->toastkey, 1, NULL, 0);\n\t}\n\n\tTupleDesc toasttupDesc = detoaster->toastrel->rd_att;\n\n\t///////////////////////////////////////////////\n\n\t/*\n\t * Read the chunks by index\n\t *\n\t * The index is on (valueid, chunkidx) so they will come in order\n\t */\n\tconst int32 attrsize = VARATT_EXTERNAL_GET_EXTSIZE(*toast_pointer);\n\tconst int32 totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;\n\tconst int startchunk = 0;\n\tconst int endchunk = (attrsize - 1) / TOAST_MAX_CHUNK_SIZE;\n\tAssert(endchunk <= totalchunks);\n\tHeapTuple ttup;\n\tint32 expectedchunk = startchunk;\n\twhile ((ttup = systable_getnext_ordered(detoaster->toastscan, ForwardScanDirection)) != NULL)\n\t{\n\t\tint32 curchunk;\n\t\tPointer chunk;\n\t\tbool isnull;\n\t\tchar *chunkdata;\n\t\tint32 chunksize;\n\t\tint32 expected_size;\n\t\tint32 chcpystrt;\n\t\tint32 chcpyend;\n\n\t\t/*\n\t\t * Have a chunk, extract the sequence number and the data\n\t\t */\n\t\tcurchunk = DatumGetInt32(fastgetattr(ttup, 2, toasttupDesc, &isnull));\n\t\tAssert(!isnull);\n\t\tchunk = DatumGetPointer(fastgetattr(ttup, 3, toasttupDesc, &isnull));\n\t\tAssert(!isnull);\n\t\tif (!VARATT_IS_EXTENDED(chunk))\n\t\t{\n\t\t\tchunksize = VARSIZE(chunk) - VARHDRSZ;\n\t\t\tchunkdata = VARDATA(chunk);\n\t\t}\n\t\telse if (VARATT_IS_SHORT(chunk))\n\t\t{\n\t\t\t/* could happen due to heap_form_tuple doing its thing */\n\t\t\tchunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT;\n\t\t\tchunkdata = VARDATA_SHORT(chunk);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* should never happen */\n\t\t\telog(ERROR,\n\t\t\t\t \"found toasted toast chunk for toast value %u in %s\",\n\t\t\t\t valueid,\n\t\t\t\t RelationGetRelationName(detoaster->toastrel));\n\t\t\tchunksize = 0; /* keep compiler quiet */\n\t\t\tchunkdata = NULL;\n\t\t}\n\n\t\t/*\n\t\t * Some checks on the data we've found\n\t\t */\n\t\tif (curchunk != expectedchunk)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_DATA_CORRUPTED),\n\t\t\t\t\t errmsg_internal(\"unexpected chunk number %d (expected %d) for toast value %u \"\n\t\t\t\t\t\t\t\t\t \"in %s\",\n\t\t\t\t\t\t\t\t\t curchunk,\n\t\t\t\t\t\t\t\t\t expectedchunk,\n\t\t\t\t\t\t\t\t\t valueid,\n\t\t\t\t\t\t\t\t\t RelationGetRelationName(detoaster->toastrel))));\n\t\tif (curchunk > endchunk)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_DATA_CORRUPTED),\n\t\t\t\t\t errmsg_internal(\"unexpected chunk number %d (out of range %d..%d) for toast \"\n\t\t\t\t\t\t\t\t\t \"value %u in %s\",\n\t\t\t\t\t\t\t\t\t curchunk,\n\t\t\t\t\t\t\t\t\t startchunk,\n\t\t\t\t\t\t\t\t\t endchunk,\n\t\t\t\t\t\t\t\t\t valueid,\n\t\t\t\t\t\t\t\t\t RelationGetRelationName(detoaster->toastrel))));\n\t\texpected_size = curchunk < totalchunks - 1 ?\n\t\t\t\t\t\t\tTOAST_MAX_CHUNK_SIZE :\n\t\t\t\t\t\t\tattrsize - ((totalchunks - 1) * TOAST_MAX_CHUNK_SIZE);\n\t\tif (chunksize != expected_size)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_DATA_CORRUPTED),\n\t\t\t\t\t errmsg_internal(\"unexpected chunk size %d (expected %d) in chunk %d of %d for \"\n\t\t\t\t\t\t\t\t\t \"toast value %u in %s\",\n\t\t\t\t\t\t\t\t\t chunksize,\n\t\t\t\t\t\t\t\t\t expected_size,\n\t\t\t\t\t\t\t\t\t curchunk,\n\t\t\t\t\t\t\t\t\t totalchunks,\n\t\t\t\t\t\t\t\t\t valueid,\n\t\t\t\t\t\t\t\t\t RelationGetRelationName(detoaster->toastrel))));\n\n\t\t/*\n\t\t * Copy the data into proper place in our result\n\t\t */\n\t\tchcpystrt = 0;\n\t\tchcpyend = chunksize - 1;\n\t\tif (curchunk == startchunk)\n\t\t\tchcpystrt = 0;\n\t\tif (curchunk == endchunk)\n\t\t\tchcpyend = (attrsize - 1) % TOAST_MAX_CHUNK_SIZE;\n\n\t\tmemcpy(VARDATA(result) + (curchunk * TOAST_MAX_CHUNK_SIZE) + chcpystrt,\n\t\t\t   chunkdata + chcpystrt,\n\t\t\t   (chcpyend - chcpystrt) + 1);\n\n\t\texpectedchunk++;\n\t}\n\n\t/*\n\t * Final checks that we successfully fetched the datum\n\t */\n\tif (expectedchunk != (endchunk + 1))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_DATA_CORRUPTED),\n\t\t\t\t errmsg_internal(\"missing chunk number %d for toast value %u in %s\",\n\t\t\t\t\t\t\t\t expectedchunk,\n\t\t\t\t\t\t\t\t valueid,\n\t\t\t\t\t\t\t\t RelationGetRelationName(detoaster->toastrel))));\n}\n\n/*\n * The memory context is used to store intermediate data, and is supposed to\n * live over the calls to detoaster_detoast_attr_copy().\n * That function itself can be called in a short-lived memory context.\n */\nvoid\ndetoaster_init(Detoaster *detoaster, MemoryContext mctx)\n{\n\tdetoaster->toastrel = NULL;\n\tdetoaster->mctx = mctx;\n}\n\nvoid\ndetoaster_close(Detoaster *detoaster)\n{\n\t/* Close toast table */\n\tif (detoaster->toastrel != NULL)\n\t{\n\t\tsystable_endscan_ordered(detoaster->toastscan);\n\t\ttable_close(detoaster->toastrel, AccessShareLock);\n\t\tindex_close(detoaster->index, AccessShareLock);\n\t\tdetoaster->toastrel = NULL;\n\t\tdetoaster->index = NULL;\n\t}\n}\n\n/*\n * Copy of Postgres' toast_fetch_datum(): Reconstruct an in memory Datum from\n * the chunks saved in the toast relation.\n */\nstatic struct varlena *\nts_toast_fetch_datum(struct varlena *attr, Detoaster *detoaster, MemoryContext dest_mctx)\n{\n\tstruct varlena *result;\n\tstruct varatt_external toast_pointer;\n\tint32 attrsize;\n\n\tif (!VARATT_IS_EXTERNAL_ONDISK(attr))\n\t\telog(ERROR, \"toast_fetch_datum shouldn't be called for non-ondisk datums\");\n\n\t/* Must copy to access aligned fields */\n\tVARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);\n\n\tattrsize = VARATT_EXTERNAL_GET_EXTSIZE(toast_pointer);\n\n\tresult = (struct varlena *) MemoryContextAlloc(dest_mctx, attrsize + VARHDRSZ);\n\n\tif (TS_VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer))\n\t\tSET_VARSIZE_COMPRESSED(result, attrsize + VARHDRSZ);\n\telse\n\t\tSET_VARSIZE(result, attrsize + VARHDRSZ);\n\n\tif (attrsize == 0)\n\t\treturn result; /* Probably shouldn't happen, but just in\n\t\t\t\t\t\t* case. */\n\n\t/* Fetch all chunks */\n\tts_fetch_toast(detoaster, &toast_pointer, result);\n\n\treturn result;\n}\n\n#include <access/toast_compression.h>\n\nstatic struct varlena *\nts_toast_decompress_datum(struct varlena *attr)\n{\n\tToastCompressionId cmid;\n\n\tAssert(VARATT_IS_COMPRESSED(attr));\n\n\t/*\n\t * Fetch the compression method id stored in the compression header and\n\t * decompress the data using the appropriate decompression routine.\n\t */\n\tcmid = TOAST_COMPRESS_METHOD(attr);\n\tswitch (cmid)\n\t{\n\t\tcase TOAST_PGLZ_COMPRESSION_ID:\n\t\t\treturn pglz_decompress_datum(attr);\n\t\tcase TOAST_LZ4_COMPRESSION_ID:\n\t\t\treturn lz4_decompress_datum(attr);\n\t\tdefault:\n\t\t\telog(ERROR, \"invalid compression method id %d\", cmid);\n\t\t\treturn NULL; /* keep compiler quiet */\n\t}\n}\n\n/*\n * Modification of Postgres' detoast_attr() where we use the stateful Detoaster\n * and skip some cases that don't occur for the toasted compressed data. Even if\n * the data is inline and no detoasting is needed, copies it into the destination\n * memory context.\n */\nstruct varlena *\ndetoaster_detoast_attr_copy(struct varlena *attr, Detoaster *detoaster, MemoryContext dest_mctx)\n{\n\tif (!VARATT_IS_EXTENDED(attr))\n\t{\n\t\t/*\n\t\t * This case is unlikely because the compressed data is almost always\n\t\t * toasted and not inline, but we still have to copy the data into the\n\t\t * destination memory context. The source compressed tuple may have\n\t\t * independent unknown lifetime.\n\t\t */\n\t\tSize len = VARSIZE(attr);\n\t\tstruct varlena *result = (struct varlena *) MemoryContextAlloc(dest_mctx, len);\n\t\tmemcpy(result, attr, len);\n\t\treturn result;\n\t}\n\n\tif (VARATT_IS_EXTERNAL_ONDISK(attr))\n\t{\n\t\t/*\n\t\t * This is an externally stored datum --- fetch it back from there.\n\t\t */\n\t\tattr = ts_toast_fetch_datum(attr, detoaster, dest_mctx);\n\n\t\t/* If it's compressed, decompress it */\n\t\tif (VARATT_IS_COMPRESSED(attr))\n\t\t{\n\t\t\tstruct varlena *tmp = attr;\n\n\t\t\tMemoryContext old_context = MemoryContextSwitchTo(dest_mctx);\n\t\t\tattr = ts_toast_decompress_datum(tmp);\n\t\t\tMemoryContextSwitchTo(old_context);\n\n\t\t\tpfree(tmp);\n\t\t}\n\n\t\treturn attr;\n\t}\n\n\t/*\n\t * Can't get indirect TOAST here (out-of-line Datum that's stored in memory),\n\t * because we're reading from the compressed chunk table.\n\t */\n\tEnsure(!VARATT_IS_EXTERNAL_INDIRECT(attr), \"got indirect TOAST for compressed data\");\n\n\t/*\n\t * Compressed data doesn't have an expanded representation.\n\t */\n\tEnsure(!VARATT_IS_EXTERNAL_EXPANDED(attr), \"got expanded TOAST for compressed data\");\n\n\tif (VARATT_IS_COMPRESSED(attr))\n\t{\n\t\t/*\n\t\t * This is a compressed value stored inline in the main tuple. It rarely\n\t\t * occurs in practice, because we set a low toast_tuple_target = 128\n\t\t * for the compressed chunks, but is still technically possible.\n\t\t *\n\t\t * Note that the attr comes from the compressed tuple slot here, so we\n\t\t * don't have to free it unlike the above case of decompression.\n\t\t */\n\t\tMemoryContext old_context = MemoryContextSwitchTo(dest_mctx);\n\t\tattr = ts_toast_decompress_datum(attr);\n\t\tMemoryContextSwitchTo(old_context);\n\n\t\treturn attr;\n\t}\n\n\t/*\n\t * The only option left is a short-header varlena --- convert to 4-byte\n\t * header format.\n\t */\n\tEnsure(VARATT_IS_SHORT(attr), \"got unexpected TOAST type for compressed data\");\n\n\t/*\n\t * Check that the size of datum is not less than the size of header, which\n\t * could lead to data_size of UINT64_MAX. This is possible in case of\n\t * TOAST data corruption. Postgres doesn't specifically check for this,\n\t * because in any case it will be detected by the subsequent palloc call,\n\t * but we do it to silence the Coverity warning.\n\t */\n\tCheckCompressedData(VARSIZE_SHORT(attr) >= VARHDRSZ_SHORT);\n\tSize data_size = VARSIZE_SHORT(attr) - VARHDRSZ_SHORT;\n\tSize new_size = data_size + VARHDRSZ;\n\tstruct varlena *new_attr;\n\n\tnew_attr = (struct varlena *) MemoryContextAlloc(dest_mctx, new_size);\n\tSET_VARSIZE(new_attr, new_size);\n\tmemcpy(VARDATA(new_attr), VARDATA_SHORT(attr), data_size);\n\tattr = new_attr;\n\n\treturn attr;\n}\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/detoaster.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#pragma once\n\n#include <postgres.h>\n\n#include <access/genam.h>\n#include <access/relscan.h>\n#include <access/skey.h>\n#include <utils/snapshot.h>\n\ntypedef struct RelationData *Relation;\n\ntypedef struct Detoaster\n{\n\tMemoryContext mctx;\n\tRelation toastrel;\n\tRelation index;\n\tSnapshotData SnapshotToast;\n\tScanKeyData toastkey;\n\tSysScanDesc toastscan;\n} Detoaster;\n\nvoid detoaster_init(Detoaster *detoaster, MemoryContext mctx);\nvoid detoaster_close(Detoaster *detoaster);\nstruct varlena *detoaster_detoast_attr_copy(struct varlena *attr, Detoaster *detoaster,\n\t\t\t\t\t\t\t\t\t\t\tMemoryContext dest_mctx);\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/exec.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <access/sysattr.h>\n#include <executor/executor.h>\n#include <miscadmin.h>\n#include <nodes/bitmapset.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodeFuncs.h>\n#include <optimizer/optimizer.h>\n#include <parser/parsetree.h>\n#include <rewrite/rewriteManip.h>\n#include <tcop/tcopprot.h>\n#include <utils/datum.h>\n#include <utils/memutils.h>\n#include <utils/typcache.h>\n\n#include \"compat/compat.h\"\n#include \"compression/arrow_c_data_interface.h\"\n#include \"compression/compression.h\"\n#include \"guc.h\"\n#include \"import/ts_explain.h\"\n#include \"nodes/columnar_scan/batch_array.h\"\n#include \"nodes/columnar_scan/batch_queue.h\"\n#include \"nodes/columnar_scan/columnar_scan.h\"\n#include \"nodes/columnar_scan/compressed_batch.h\"\n#include \"nodes/columnar_scan/exec.h\"\n#include \"nodes/columnar_scan/planner.h\"\n#include \"ts_catalog/array_utils.h\"\n\n#if PG18_GE\n#include <commands/explain_format.h>\n#include <commands/explain_state.h>\n#endif\n\nstatic void columnar_scan_begin(CustomScanState *node, EState *estate, int eflags);\nstatic void columnar_scan_end(CustomScanState *node);\nstatic void columnar_scan_rescan(CustomScanState *node);\nstatic void columnar_scan_explain(CustomScanState *node, List *ancestors, ExplainState *es);\n\nstatic CustomExecMethods columnar_scan_state_methods = {\n\t.BeginCustomScan = columnar_scan_begin,\n\t.ExecCustomScan = NULL, /* To be determined later. */\n\t.EndCustomScan = columnar_scan_end,\n\t.ReScanCustomScan = columnar_scan_rescan,\n\t.ExplainCustomScan = columnar_scan_explain,\n};\n\n/*\n * Build the sortkeys data structure from the list structure in the\n * custom_private field of the custom scan. This sort info is used to sort\n * binary heap used for batch sorted merge.\n */\n\nNode *\ncolumnar_scan_state_create(CustomScan *cscan)\n{\n\tColumnarScanState *chunk_state;\n\n\tchunk_state = (ColumnarScanState *) newNode(sizeof(ColumnarScanState), T_CustomScanState);\n\n\tchunk_state->exec_methods = columnar_scan_state_methods;\n\tchunk_state->csstate.methods = &chunk_state->exec_methods;\n\n\tAssert(IsA(cscan->custom_private, List));\n\tAssert(list_length(cscan->custom_private) == DCP_Count);\n\tList *settings = list_nth(cscan->custom_private, DCP_Settings);\n\tchunk_state->decompression_map = list_nth(cscan->custom_private, DCP_DecompressionMap);\n\tchunk_state->is_segmentby_column = list_nth(cscan->custom_private, DCP_IsSegmentbyColumn);\n\tchunk_state->bulk_decompression_column =\n\t\tlist_nth(cscan->custom_private, DCP_BulkDecompressionColumn);\n\tchunk_state->sortinfo = list_nth(cscan->custom_private, DCP_SortInfo);\n\n\tchunk_state->custom_scan_tlist = cscan->custom_scan_tlist;\n\n\tAssert(IsA(settings, IntList));\n\tAssert(list_length(settings) == DCS_Count);\n\tchunk_state->hypertable_id = list_nth_int(settings, DCS_HypertableId);\n\tchunk_state->chunk_relid = list_nth_int(settings, DCS_ChunkRelid);\n\tchunk_state->decompress_context.reverse = list_nth_int(settings, DCS_Reverse);\n\tchunk_state->decompress_context.batch_sorted_merge =\n\t\tlist_nth_int(settings, DCS_BatchSortedMerge);\n\tchunk_state->decompress_context.chunk_status = list_nth_int(settings, DCS_ChunkStatus);\n\tchunk_state->decompress_context.enable_bulk_decompression =\n\t\tlist_nth_int(settings, DCS_EnableBulkDecompression);\n\tchunk_state->has_row_marks = list_nth_int(settings, DCS_HasRowMarks);\n\n\tAssert(IsA(cscan->custom_exprs, List));\n\tAssert(list_length(cscan->custom_exprs) == 1);\n\tchunk_state->vectorized_quals_original = linitial(cscan->custom_exprs);\n\tAssert(list_length(chunk_state->decompression_map) ==\n\t\t   list_length(chunk_state->is_segmentby_column));\n\n\treturn (Node *) chunk_state;\n}\n\ntypedef struct ConstifyTableOidContext\n{\n\tIndex chunk_index;\n\tOid chunk_relid;\n\tbool made_changes;\n} ConstifyTableOidContext;\n\nstatic Node *\nconstify_tableoid_walker(Node *node, ConstifyTableOidContext *ctx)\n{\n\tif (node == NULL)\n\t\treturn NULL;\n\n\tif (IsA(node, Var))\n\t{\n\t\tVar *var = castNode(Var, node);\n\n\t\tif ((Index) var->varno != ctx->chunk_index)\n\t\t\treturn node;\n\n\t\tif (var->varattno == TableOidAttributeNumber)\n\t\t{\n\t\t\tctx->made_changes = true;\n\t\t\treturn (\n\t\t\t\tNode *) makeConst(OIDOID, -1, InvalidOid, 4, (Datum) ctx->chunk_relid, false, true);\n\t\t}\n\n\t\t/*\n\t\t * we doublecheck system columns here because projection will\n\t\t * segfault if any system columns get through\n\t\t */\n\t\tif (var->varattno < SelfItemPointerAttributeNumber)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),\n\t\t\t\t\t errmsg(\"transparent decompression only supports tableoid system column\")));\n\n\t\treturn node;\n\t}\n\n\treturn expression_tree_mutator(node, constify_tableoid_walker, (void *) ctx);\n}\n\nstatic List *\nconstify_tableoid(List *node, Index chunk_index, Oid chunk_relid)\n{\n\tConstifyTableOidContext ctx = {\n\t\t.chunk_index = chunk_index,\n\t\t.chunk_relid = chunk_relid,\n\t\t.made_changes = false,\n\t};\n\n\tList *result = (List *) constify_tableoid_walker((Node *) node, &ctx);\n\tif (ctx.made_changes)\n\t{\n\t\treturn result;\n\t}\n\n\treturn node;\n}\n\npg_attribute_always_inline static TupleTableSlot *\ncolumnar_scan_exec_impl(ColumnarScanState *chunk_state, const BatchQueueFunctions *funcs);\n\nstatic TupleTableSlot *\ncolumnar_scan_exec_fifo(CustomScanState *node)\n{\n\tColumnarScanState *chunk_state = (ColumnarScanState *) node;\n\tAssert(!chunk_state->decompress_context.batch_sorted_merge);\n\treturn columnar_scan_exec_impl(chunk_state, &BatchQueueFunctionsFifo);\n}\n\nstatic TupleTableSlot *\ncolumnar_scan_exec_heap(CustomScanState *node)\n{\n\tColumnarScanState *chunk_state = (ColumnarScanState *) node;\n\tAssert(chunk_state->decompress_context.batch_sorted_merge);\n\treturn columnar_scan_exec_impl(chunk_state, &BatchQueueFunctionsHeap);\n}\n\n/*\n * Complete initialization of the supplied CustomScanState.\n *\n * Standard fields have been initialized by ExecInitCustomScan,\n * but any private fields should be initialized here.\n */\nstatic void\ncolumnar_scan_begin(CustomScanState *node, EState *estate, int eflags)\n{\n\tColumnarScanState *chunk_state = (ColumnarScanState *) node;\n\tDecompressContext *dcontext = &chunk_state->decompress_context;\n\tCustomScan *cscan = castNode(CustomScan, node->ss.ps.plan);\n\tPlan *compressed_scan = linitial(cscan->custom_plans);\n\tAssert(list_length(cscan->custom_plans) == 1);\n\n\tPlanState *ps = &node->ss.ps;\n\tif (ps->ps_ProjInfo)\n\t{\n\t\t/*\n\t\t * if we are projecting we need to constify tableoid references here\n\t\t * because decompressed tuple are virtual tuples and don't have\n\t\t * system columns.\n\t\t *\n\t\t * We do the constify in executor because even after plan creation\n\t\t * our targetlist might still get modified by parent nodes pushing\n\t\t * down targetlist.\n\t\t */\n\t\tList *tlist = ps->plan->targetlist;\n\t\tList *modified_tlist =\n\t\t\tconstify_tableoid(tlist, cscan->scan.scanrelid, chunk_state->chunk_relid);\n\n\t\tif (modified_tlist != tlist)\n\t\t{\n\t\t\tps->ps_ProjInfo =\n\t\t\t\tExecBuildProjectionInfo(modified_tlist,\n\t\t\t\t\t\t\t\t\t\tps->ps_ExprContext,\n\t\t\t\t\t\t\t\t\t\tps->ps_ResultTupleSlot,\n\t\t\t\t\t\t\t\t\t\tps,\n\t\t\t\t\t\t\t\t\t\tnode->ss.ss_ScanTupleSlot->tts_tupleDescriptor);\n\t\t}\n\t}\n\n\t/*\n\t * Sort keys should only be present at the level of this node when batch\n\t * sorted merge is used.\n\t * In other cases of sort pushdown, sorting is performed by the underlying\n\t * compressed scan.\n\t */\n\tAssert(dcontext->batch_sorted_merge == true || list_length(chunk_state->sortinfo) == 0);\n\n\t/*\n\t * Init the underlying compressed scan.\n\t */\n\tnode->custom_ps = lappend(node->custom_ps, ExecInitNode(compressed_scan, estate, eflags));\n\n\t/*\n\t * Count the actual data columns we have to decompress, skipping the\n\t * metadata columns. We only need the metadata columns when initializing the\n\t * compressed batch, so they are not saved in the compressed batch itself,\n\t * it tracks only the data columns. We put the metadata columns to the end\n\t * of the array to have the same column indexes in compressed batch state\n\t * and in decompression context.\n\t */\n\tint num_data_columns = 0;\n\tint num_columns_with_metadata = 0;\n\n\tListCell *dest_cell;\n\tListCell *is_segmentby_cell;\n\n\tforboth (dest_cell,\n\t\t\t chunk_state->decompression_map,\n\t\t\t is_segmentby_cell,\n\t\t\t chunk_state->is_segmentby_column)\n\t{\n\t\tAttrNumber output_attno = lfirst_int(dest_cell);\n\t\tif (output_attno == 0)\n\t\t{\n\t\t\t/* We are asked not to decompress this column, skip it. */\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (output_attno > 0)\n\t\t{\n\t\t\t/*\n\t\t\t * Not a metadata column.\n\t\t\t */\n\t\t\tnum_data_columns++;\n\t\t}\n\n\t\tnum_columns_with_metadata++;\n\t}\n\n\tAssert(num_data_columns <= num_columns_with_metadata);\n\tdcontext->num_data_columns = num_data_columns;\n\tdcontext->num_columns_with_metadata = num_columns_with_metadata;\n\tdcontext->compressed_chunk_columns =\n\t\tpalloc0(sizeof(CompressionColumnDescription) * num_columns_with_metadata);\n\tdcontext->custom_scan_slot = node->ss.ss_ScanTupleSlot;\n\tdcontext->uncompressed_chunk_tdesc = RelationGetDescr(node->ss.ss_currentRelation);\n\tdcontext->ps = &node->ss.ps;\n\n\tTupleDesc desc = dcontext->custom_scan_slot->tts_tupleDescriptor;\n\n\t/*\n\t * Compressed columns go in front, and the rest go to the back, so we have\n\t * separate indices for them.\n\t */\n\tint current_compressed = 0;\n\tint current_not_compressed = num_data_columns;\n\tfor (int compressed_index = 0; compressed_index < list_length(chunk_state->decompression_map);\n\t\t compressed_index++)\n\t{\n\t\tCompressionColumnDescription column = {\n\t\t\t.compressed_scan_attno = AttrOffsetGetAttrNumber(compressed_index),\n\t\t\t.custom_scan_attno = list_nth_int(chunk_state->decompression_map, compressed_index),\n\t\t\t.bulk_decompression_supported =\n\t\t\t\tlist_nth_int(chunk_state->bulk_decompression_column, compressed_index)\n\t\t};\n\n\t\tif (column.custom_scan_attno == 0)\n\t\t{\n\t\t\t/* We are asked not to decompress this column, skip it. */\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (column.custom_scan_attno > 0)\n\t\t{\n\t\t\t/* normal column that is also present in decompressed chunk */\n\t\t\tForm_pg_attribute attribute =\n\t\t\t\tTupleDescAttr(desc, AttrNumberGetAttrOffset(column.custom_scan_attno));\n\n\t\t\tcolumn.typid = attribute->atttypid;\n\t\t\tget_typlenbyval(column.typid, &column.value_bytes, &column.by_value);\n\n\t\t\tif (list_nth_int(chunk_state->is_segmentby_column, compressed_index))\n\t\t\t\tcolumn.type = SEGMENTBY_COLUMN;\n\t\t\telse\n\t\t\t\tcolumn.type = COMPRESSED_COLUMN;\n\n\t\t\tif (cscan->custom_scan_tlist == NIL)\n\t\t\t{\n\t\t\t\tcolumn.uncompressed_chunk_attno = column.custom_scan_attno;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tVar *var =\n\t\t\t\t\tcastNode(Var,\n\t\t\t\t\t\t\t castNode(TargetEntry,\n\t\t\t\t\t\t\t\t\t  list_nth(cscan->custom_scan_tlist,\n\t\t\t\t\t\t\t\t\t\t\t   AttrNumberGetAttrOffset(column.custom_scan_attno)))\n\t\t\t\t\t\t\t\t ->expr);\n\t\t\t\tcolumn.uncompressed_chunk_attno = var->varattno;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* metadata columns */\n\t\t\tswitch (column.custom_scan_attno)\n\t\t\t{\n\t\t\t\tcase COLUMNAR_SCAN_COUNT_ID:\n\t\t\t\t\tcolumn.type = COUNT_COLUMN;\n\t\t\t\t\tbreak;\n\t\t\t\tcase COLUMNAR_SCAN_SEQUENCE_NUM_ID:\n\t\t\t\t\tcolumn.type = SEQUENCE_NUM_COLUMN;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\telog(ERROR, \"Invalid column attno \\\"%d\\\"\", column.custom_scan_attno);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (column.custom_scan_attno > 0)\n\t\t{\n\t\t\t/* Data column. */\n\t\t\tAssert(current_compressed < num_data_columns);\n\t\t\tdcontext->compressed_chunk_columns[current_compressed++] = column;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* Metadata column. */\n\t\t\tAssert(current_not_compressed < num_columns_with_metadata);\n\t\t\tdcontext->compressed_chunk_columns[current_not_compressed++] = column;\n\t\t}\n\t}\n\n\tAssert(current_compressed == num_data_columns);\n\tAssert(current_not_compressed == num_columns_with_metadata);\n\n\t/*\n\t * Choose which batch queue we are going to use: heap for batch sorted\n\t * merge, and one-element FIFO for normal decompression.\n\t */\n\tif (dcontext->batch_sorted_merge)\n\t{\n\t\tchunk_state->batch_queue =\n\t\t\tbatch_queue_heap_create(num_data_columns,\n\t\t\t\t\t\t\t\t\tchunk_state->sortinfo,\n\t\t\t\t\t\t\t\t\tdcontext->custom_scan_slot->tts_tupleDescriptor,\n\t\t\t\t\t\t\t\t\t&BatchQueueFunctionsHeap);\n\t\tchunk_state->exec_methods.ExecCustomScan = columnar_scan_exec_heap;\n\t}\n\telse\n\t{\n\t\tchunk_state->batch_queue =\n\t\t\tbatch_queue_fifo_create(num_data_columns, &BatchQueueFunctionsFifo);\n\t\tchunk_state->exec_methods.ExecCustomScan = columnar_scan_exec_fifo;\n\t}\n\n\tif ((ts_guc_debug_require_batch_sorted_merge == DRO_Require ||\n\t\t ts_guc_debug_require_batch_sorted_merge == DRO_Force) &&\n\t\t!dcontext->batch_sorted_merge)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"debug: batch sorted merge is required but not used\")));\n\t}\n\n\tif (ts_guc_debug_require_batch_sorted_merge == DRO_Forbid && dcontext->batch_sorted_merge)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"debug: batch sorted merge is used when it is forbidden\")));\n\t}\n\n\t/* Constify stable expressions in vectorized predicates. */\n\tPlannerGlobal glob = {\n\t\t.boundParams = node->ss.ps.state->es_param_list_info,\n\t};\n\tPlannerInfo root = {\n\t\t.glob = &glob,\n\t};\n\tListCell *lc;\n\tforeach (lc, chunk_state->vectorized_quals_original)\n\t{\n\t\tNode *constified = estimate_expression_value(&root, (Node *) lfirst(lc));\n\n\t\tdcontext->vectorized_quals_constified =\n\t\t\tlappend(dcontext->vectorized_quals_constified, constified);\n\t}\n\n\tdetoaster_init(&dcontext->detoaster, CurrentMemoryContext);\n}\n\n/*\n * The exec function for the ColumnarScan node. It takes the explicit queue\n * functions pointer as an optimization, to allow these functions to be\n * inlined in the FIFO case. This is important because this is a part of a\n * relatively hot loop.\n */\npg_attribute_always_inline static TupleTableSlot *\ncolumnar_scan_exec_impl(ColumnarScanState *chunk_state, const BatchQueueFunctions *bqfuncs)\n{\n\tDecompressContext *dcontext = &chunk_state->decompress_context;\n\tBatchQueue *bq = chunk_state->batch_queue;\n\n\tAssert(bq->funcs == bqfuncs);\n\n\tbqfuncs->pop(bq, dcontext);\n\n\twhile (bqfuncs->needs_next_batch(bq))\n\t{\n\t\tTupleTableSlot *subslot = ExecProcNode(linitial(chunk_state->csstate.custom_ps));\n\t\tif (TupIsNull(subslot))\n\t\t{\n\t\t\t/* Won't have more compressed tuples. */\n\t\t\tbreak;\n\t\t}\n\n\t\tbqfuncs->push_batch(bq, dcontext, subslot);\n\t}\n\tTupleTableSlot *result_slot = bqfuncs->top_tuple(bq);\n\n\tif (TupIsNull(result_slot))\n\t{\n\t\treturn NULL;\n\t}\n\n\tif (chunk_state->has_row_marks)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"locking compressed tuples is not supported\")));\n\t}\n\n\tif (chunk_state->csstate.ss.ps.ps_ProjInfo)\n\t{\n\t\tExprContext *econtext = chunk_state->csstate.ss.ps.ps_ExprContext;\n\t\tecontext->ecxt_scantuple = result_slot;\n\t\treturn ExecProject(chunk_state->csstate.ss.ps.ps_ProjInfo);\n\t}\n\n\treturn result_slot;\n}\n\nstatic void\ncolumnar_scan_rescan(CustomScanState *node)\n{\n\tColumnarScanState *chunk_state = (ColumnarScanState *) node;\n\tBatchQueue *bq = chunk_state->batch_queue;\n\n\tbq->funcs->reset(bq);\n\n\tif (node->ss.ps.chgParam != NULL)\n\t\tUpdateChangedParamSet(linitial(node->custom_ps), node->ss.ps.chgParam);\n\n\tExecReScan(linitial(node->custom_ps));\n}\n\n/* End the decompress operation and free the requested resources */\nstatic void\ncolumnar_scan_end(CustomScanState *node)\n{\n\tColumnarScanState *chunk_state = (ColumnarScanState *) node;\n\tBatchQueue *bq = chunk_state->batch_queue;\n\n\tbq->funcs->free(bq);\n\tExecEndNode(linitial(node->custom_ps));\n\n\tdetoaster_close(&chunk_state->decompress_context.detoaster);\n}\n\n/*\n * Output additional information for EXPLAIN of a custom-scan plan node.\n */\nstatic void\ncolumnar_scan_explain(CustomScanState *node, List *ancestors, ExplainState *es)\n{\n\tColumnarScanState *chunk_state = (ColumnarScanState *) node;\n\tDecompressContext *dcontext = &chunk_state->decompress_context;\n\n\tts_show_scan_qual(chunk_state->vectorized_quals_original,\n\t\t\t\t\t  \"Vectorized Filter\",\n\t\t\t\t\t  &node->ss.ps,\n\t\t\t\t\t  ancestors,\n\t\t\t\t\t  es);\n\n\tif (!node->ss.ps.plan->qual && chunk_state->vectorized_quals_original)\n\t{\n\t\t/*\n\t\t * The normal explain won't show this if there are no normal quals but\n\t\t * only the vectorized ones.\n\t\t */\n\t\tts_show_instrumentation_count(\"Rows Removed by Filter\", 1, &node->ss.ps, es);\n\t}\n\n\tif (es->analyze && es->verbose &&\n\t\t(node->ss.ps.instrument->ntuples2 > 0 || es->format != EXPLAIN_FORMAT_TEXT))\n\t{\n\t\tExplainPropertyFloat(\"Batches Removed by Filter\",\n\t\t\t\t\t\t\t NULL,\n\t\t\t\t\t\t\t node->ss.ps.instrument->ntuples2,\n\t\t\t\t\t\t\t 0,\n\t\t\t\t\t\t\t es);\n\t}\n\n\tif (es->verbose || es->format != EXPLAIN_FORMAT_TEXT)\n\t{\n\t\t/* Display any statuses in addition to COMPRESSED */\n\t\tif (dcontext->chunk_status > CHUNK_STATUS_COMPRESSED)\n\t\t{\n\t\t\tStringInfoData status_text;\n\t\t\tinitStringInfo(&status_text);\n\t\t\tArrayType *arr =\n\t\t\t\tDatumGetArrayTypeP(DirectFunctionCall1(ts_chunk_status_text,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   Int32GetDatum(dcontext->chunk_status -\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t CHUNK_STATUS_COMPRESSED)));\n\t\t\tts_array_append_stringinfo(arr, &status_text);\n\t\t\tpfree(arr);\n\t\t\tExplainPropertyText(\"Chunk Status\", status_text.data, es);\n\t\t}\n\n\t\tif (dcontext->batch_sorted_merge)\n\t\t{\n\t\t\tExplainPropertyBool(\"Batch Sorted Merge\", dcontext->batch_sorted_merge, es);\n\t\t}\n\n\t\tif (dcontext->reverse)\n\t\t{\n\t\t\tExplainPropertyBool(\"Reverse\", dcontext->reverse, es);\n\t\t}\n\n\t\tif (es->analyze)\n\t\t{\n\t\t\tExplainPropertyBool(\"Bulk Decompression\",\n\t\t\t\t\t\t\t\tchunk_state->decompress_context.enable_bulk_decompression,\n\t\t\t\t\t\t\t\tes);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/exec.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include \"batch_queue.h\"\n#include \"decompress_context.h\"\n#include <nodes/extensible.h>\n\n#define COLUMNAR_SCAN_COUNT_ID -9\n#define COLUMNAR_SCAN_SEQUENCE_NUM_ID -10\n\ntypedef struct ColumnarScanState\n{\n\tCustomScanState csstate;\n\tList *decompression_map;\n\tList *is_segmentby_column;\n\tList *bulk_decompression_column;\n\tList *custom_scan_tlist;\n\tbool has_row_marks;\n\n\tDecompressContext decompress_context;\n\n\tint hypertable_id;\n\tOid chunk_relid;\n\n\tBatchQueue *batch_queue;\n\tCustomExecMethods exec_methods;\n\n\tList *sortinfo;\n\n\t/*\n\t * For some predicates, we have more efficient implementation that work on\n\t * the entire compressed batch in one go. They go to this list, and the rest\n\t * goes into the usual ss.ps.qual. Note that we constify stable functions\n\t * in these predicates at execution time, but have to keep the original\n\t * version for EXPLAIN. We also need special handling for quals that\n\t * evaluate to constant false, hence the flag.\n\t */\n\tList *vectorized_quals_original;\n} ColumnarScanState;\n\nextern Node *columnar_scan_state_create(CustomScan *cscan);\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/planner.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <access/sysattr.h>\n#include <catalog/pg_namespace.h>\n#include <catalog/pg_operator.h>\n#include <nodes/bitmapset.h>\n#include <nodes/extensible.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodeFuncs.h>\n#include <nodes/nodes.h>\n#include <optimizer/cost.h>\n#include <optimizer/optimizer.h>\n#include <optimizer/paths.h>\n#include <optimizer/plancat.h>\n#include <optimizer/restrictinfo.h>\n#include <optimizer/tlist.h>\n#include <parser/parse_relation.h>\n#include <parser/parsetree.h>\n#include <utils/builtins.h>\n#include <utils/typcache.h>\n\n#include \"compression/compression.h\"\n#include \"compression/create.h\"\n#include \"custom_type_cache.h\"\n#include \"guc.h\"\n#include \"import/list.h\"\n#include \"import/planner.h\"\n#include \"nodes/chunk_append/transform.h\"\n#include \"nodes/columnar_scan/columnar_scan.h\"\n#include \"nodes/columnar_scan/exec.h\"\n#include \"nodes/columnar_scan/planner.h\"\n#include \"nodes/columnar_scan/vector_quals.h\"\n#include \"nodes/vector_agg/exec.h\"\n#include \"ts_catalog/array_utils.h\"\n#include \"vector_predicates.h\"\n\nstatic CustomScanMethods columnar_scan_plan_methods = {\n\t.CustomName = \"ColumnarScan\",\n\t.CreateCustomScanState = columnar_scan_state_create,\n};\n\n/* Check if the provided plan is a ColumnarScanPlan */\nbool\nts_is_columnar_scan_plan(Plan *plan)\n{\n\treturn IsA(plan, CustomScan) &&\n\t\t   castNode(CustomScan, plan)->methods == &columnar_scan_plan_methods;\n}\n\nvoid\n_columnar_scan_init(void)\n{\n\tTryRegisterCustomScanMethods(&columnar_scan_plan_methods);\n}\n\nstatic void\ncheck_for_system_columns(Bitmapset *attrs_used)\n{\n\tint bit = bms_next_member(attrs_used, -1);\n\tif (bit > 0 && bit + FirstLowInvalidHeapAttributeNumber < 0)\n\t{\n\t\t/* we support tableoid so skip that */\n\t\tif (bit == TableOidAttributeNumber - FirstLowInvalidHeapAttributeNumber)\n\t\t\tbit = bms_next_member(attrs_used, bit);\n\n\t\tif (bit > 0 && bit + FirstLowInvalidHeapAttributeNumber < 0)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),\n\t\t\t\t\t errmsg(\"transparent decompression only supports tableoid system column\")));\n\t}\n}\n\ntypedef struct\n{\n\tbool bulk_decompression_possible;\n\tint custom_scan_attno;\n} UncompressedColumnInfo;\n\ntypedef struct\n{\n\t/* Can be negative if it's a metadata column, zero if not decompressed. */\n\tint uncompressed_chunk_attno;\n\tbool bulk_decompression_possible;\n\tbool is_segmentby;\n} CompressedColumnInfo;\n\n/*\n * Scratch space for mapping out the decompressed columns.\n */\ntypedef struct\n{\n\tPlannerInfo *root;\n\n\tColumnarScanPath *decompress_path;\n\n\tBitmapset *uncompressed_attrs_needed;\n\n\t/*\n\t * If we produce at least some columns that support bulk decompression.\n\t */\n\tbool have_bulk_decompression_columns;\n\n\t/*\n\t * Maps the uncompressed chunk attno to the respective column compression\n\t * info. This lives only during planning so that we can understand on which\n\t * columns we can apply vectorized quals, and which uncompressed attno goes\n\t * to which custom scan attno (it's not the same if we're using a custom\n\t * scan targetlist).\n\t */\n\tUncompressedColumnInfo *uncompressed_attno_info;\n\n\t/*\n\t * Maps the compressed chunk attno to the respective column compression info.\n\t */\n\tCompressedColumnInfo *compressed_attno_info;\n\n\t/*\n\t * We might use a custom scan targetlist for ColumnarScan node if it\n\t * allows us to avoid projection.\n\t */\n\tList *custom_scan_targetlist;\n\n\t/*\n\t * Next, we have basically the same data as the above, but expressed as\n\t * several Lists, to allow passing them through the custom plan settings.\n\t */\n\n\t/*\n\t * decompression_map maps targetlist entries of the compressed scan to\n\t * custom scan attnos. Negative values are metadata columns in the compressed\n\t * scan that do not have a representation in the uncompressed chunk, but are\n\t * still used for decompression.\n\t */\n\tList *decompression_map;\n\n\t/*\n\t * This Int list is parallel to the compressed scan targetlist, just like\n\t * the above one. The value is true if a given targetlist entry is a\n\t * segmentby column, false otherwise. Has the same length as the above list.\n\t * We have to use the parallel lists and not a list of structs, because the\n\t * Plans have to be copyable by the Postgres _copy functions, and we can't\n\t * do that for a custom struct.\n\t */\n\tList *is_segmentby_column;\n\n\t/*\n\t * Same structure as above, says whether we support bulk decompression for this\n\t * column.\n\t */\n\tList *bulk_decompression_column;\n\n} DecompressionMapContext;\n\nstatic bool *\nbuild_vector_attrs_array(const UncompressedColumnInfo *colinfo, const CompressionInfo *info)\n{\n\tconst AttrNumber arrlen = info->chunk_rel->max_attr + 1;\n\tbool *vector_attrs = palloc(sizeof(bool) * arrlen);\n\n\tfor (AttrNumber attno = 0; attno < arrlen; attno++)\n\t{\n\t\tvector_attrs[attno] = colinfo[attno].bulk_decompression_possible;\n\t}\n\n\treturn vector_attrs;\n}\n\n/*\n * Try to make the custom scan targetlist that follows the order of the\n * pathtarget. This would allow us to avoid a projection from scan tuple to\n * output tuple.\n * Returns NIL if it's not possible, e.g. if there are whole-row variables or\n * variables that are used for quals but not for output.\n */\nstatic List *\nfollow_uncompressed_output_tlist(const DecompressionMapContext *context)\n{\n\tList *result = NIL;\n\tBitmapset *uncompressed_attrs_found = NULL;\n\tconst CompressionInfo *info = context->decompress_path->info;\n\tconst PathTarget *pathtarget = context->decompress_path->custom_path.path.pathtarget;\n\tint custom_scan_attno = 1;\n\tfor (int i = 0; i < list_length(pathtarget->exprs); i++)\n\t{\n\t\tExpr *expr = list_nth(pathtarget->exprs, i);\n\t\tif (!IsA(expr, Var))\n\t\t{\n\t\t\t/*\n\t\t\t * The pathtarget has some non-var expressions, so we won't be able\n\t\t\t * to build a matching decompressed scan targetlist.\n\t\t\t */\n\t\t\treturn NIL;\n\t\t}\n\n\t\tVar *var = castNode(Var, expr);\n\n\t\t/* This should produce uncompressed chunk columns. */\n\t\tAssert((Index) var->varno == info->chunk_rel->relid);\n\n\t\tconst int uncompressed_chunk_attno = var->varattno;\n\t\tif (uncompressed_chunk_attno <= 0)\n\t\t{\n\t\t\t/*\n\t\t\t * The pathtarget has some special vars so we won't be able to\n\t\t\t * build a matching decompressed scan targetlist.\n\t\t\t */\n\t\t\treturn NIL;\n\t\t}\n\n\t\tchar *attname = get_attname(info->chunk_rte->relid,\n\t\t\t\t\t\t\t\t\tuncompressed_chunk_attno,\n\t\t\t\t\t\t\t\t\t/* missing_ok = */ false);\n\n\t\tTargetEntry *target_entry = makeTargetEntry((Expr *) copyObject(var),\n\t\t\t\t\t\t\t\t\t\t\t\t\t/* resno = */ custom_scan_attno,\n\t\t\t\t\t\t\t\t\t\t\t\t\t/* resname = */ attname,\n\t\t\t\t\t\t\t\t\t\t\t\t\t/* resjunk = */ false);\n\t\ttarget_entry->ressortgroupref =\n\t\t\tpathtarget->sortgrouprefs ? pathtarget->sortgrouprefs[i] : 0;\n\t\tresult = lappend(result, target_entry);\n\n\t\tuncompressed_attrs_found =\n\t\t\tbms_add_member(uncompressed_attrs_found,\n\t\t\t\t\t\t   uncompressed_chunk_attno - FirstLowInvalidHeapAttributeNumber);\n\n\t\tcustom_scan_attno++;\n\t}\n\n\tif (!bms_equal(uncompressed_attrs_found, context->uncompressed_attrs_needed))\n\t{\n\t\t/*\n\t\t * There are some variables that are not in the pathtarget that are used\n\t\t * for quals. We still have to have them in the scan tuple in this case.\n\t\t * Note that while we could possibly relax this at execution time for\n\t\t * vectorized quals, the requirement that the qual var be found in the\n\t\t * scan targetlist is a Postgres one.\n\t\t */\n\t\treturn NIL;\n\t}\n\n\treturn result;\n}\n\n/*\n * Given the compressed output targetlist and the bitmapset of the needed\n * columns, determine which compressed chunk column become which uncompressed\n * chunk column.\n *\n * Note that the uncompressed_attrs_needed bitmap is offset by the\n * FirstLowInvalidHeapAttributeNumber, similar to RelOptInfo.attr_needed. This\n * allows to encode the requirement for system columns, which have negative\n * attnos.\n */\nstatic void\nbuild_decompression_map(DecompressionMapContext *context, List *compressed_output_tlist)\n{\n\tColumnarScanPath *path = context->decompress_path;\n\tconst CompressionInfo *info = path->info;\n\t/*\n\t * Track which normal and metadata columns we were able to find in the\n\t * targetlist.\n\t */\n\tbool missing_count = true;\n\tBitmapset *uncompressed_attrs_found = NULL;\n\tBitmapset *selectedCols = NULL;\n\n#if PG16_LT\n\tselectedCols = info->ht_rte->selectedCols;\n#else\n\tif (info->ht_rte->perminfoindex > 0)\n\t{\n\t\tRTEPermissionInfo *perminfo =\n\t\t\tgetRTEPermissionInfo(context->root->parse->rteperminfos, info->ht_rte);\n\t\tselectedCols = perminfo->selectedCols;\n\t}\n#endif\n\t/*\n\t * TODO this way to determine which columns are used is actually wrong, see\n\t * https://github.com/timescale/timescaledb/issues/4195#issuecomment-1104238863\n\t * Left as is for now, because changing it uncovers a whole new story with\n\t * ctid.\n\t */\n\tcheck_for_system_columns(selectedCols);\n\n\t/*\n\t * We allow tableoid system column, it won't be in the targetlist but will\n\t * be added at decompression time. Always mark it as found.\n\t */\n\tif (bms_is_member(TableOidAttributeNumber - FirstLowInvalidHeapAttributeNumber,\n\t\t\t\t\t  context->uncompressed_attrs_needed))\n\t{\n\t\tuncompressed_attrs_found =\n\t\t\tbms_add_member(uncompressed_attrs_found,\n\t\t\t\t\t\t   TableOidAttributeNumber - FirstLowInvalidHeapAttributeNumber);\n\t}\n\n\tListCell *lc;\n\n\tcontext->uncompressed_attno_info =\n\t\tpalloc0(sizeof(*context->uncompressed_attno_info) * (info->chunk_rel->max_attr + 1));\n\n\tcontext->compressed_attno_info =\n\t\tpalloc0(sizeof(*context->compressed_attno_info) * (info->compressed_rel->max_attr + 1));\n\n\t/*\n\t * Go over the scan targetlist and determine to which output column each\n\t * scan column goes, saving other additional info as we do that.\n\t */\n\tcontext->have_bulk_decompression_columns = false;\n\tcontext->decompression_map = NIL;\n\tforeach (lc, compressed_output_tlist)\n\t{\n\t\tTargetEntry *target = (TargetEntry *) lfirst(lc);\n\t\tif (!IsA(target->expr, Var))\n\t\t{\n\t\t\telog(ERROR, \"compressed scan targetlist entries must be Vars\");\n\t\t}\n\n\t\tVar *var = castNode(Var, target->expr);\n\t\tAssert((Index) var->varno == info->compressed_rel->relid);\n\t\tAttrNumber compressed_chunk_attno = var->varattno;\n\n\t\tif (compressed_chunk_attno == InvalidAttrNumber)\n\t\t{\n\t\t\t/*\n\t\t\t * We shouldn't have whole-row vars in the compressed scan tlist,\n\t\t\t * they are going to be built by final projection of ColumnarScan\n\t\t\t * custom scan.\n\t\t\t * See compressed_rel_setup_reltarget().\n\t\t\t */\n\t\t\telog(ERROR, \"compressed scan targetlist must not have whole-row vars\");\n\t\t}\n\n\t\tconst char *column_name = get_attname(info->compressed_rte->relid,\n\t\t\t\t\t\t\t\t\t\t\t  compressed_chunk_attno,\n\t\t\t\t\t\t\t\t\t\t\t  /* missing_ok = */ false);\n\t\tAttrNumber uncompressed_chunk_attno = get_attnum(info->chunk_rte->relid, column_name);\n\n\t\tAttrNumber destination_attno = 0;\n\t\tif (uncompressed_chunk_attno != InvalidAttrNumber)\n\t\t{\n\t\t\t/*\n\t\t\t * Normal column, not a metadata column.\n\t\t\t */\n\t\t\tAssert(uncompressed_chunk_attno != InvalidAttrNumber);\n\n\t\t\tif (bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,\n\t\t\t\t\t\t\t  context->uncompressed_attrs_needed))\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * attno = 0 means whole-row var. Output all the columns.\n\t\t\t\t */\n\t\t\t\tdestination_attno = uncompressed_chunk_attno;\n\t\t\t\tuncompressed_attrs_found =\n\t\t\t\t\tbms_add_member(uncompressed_attrs_found,\n\t\t\t\t\t\t\t\t   uncompressed_chunk_attno - FirstLowInvalidHeapAttributeNumber);\n\t\t\t}\n\t\t\telse if (bms_is_member(uncompressed_chunk_attno - FirstLowInvalidHeapAttributeNumber,\n\t\t\t\t\t\t\t\t   context->uncompressed_attrs_needed))\n\t\t\t{\n\t\t\t\tdestination_attno = uncompressed_chunk_attno;\n\t\t\t\tuncompressed_attrs_found =\n\t\t\t\t\tbms_add_member(uncompressed_attrs_found,\n\t\t\t\t\t\t\t\t   uncompressed_chunk_attno - FirstLowInvalidHeapAttributeNumber);\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/*\n\t\t\t * Metadata column.\n\t\t\t * We always need count column, and sometimes a sequence number\n\t\t\t * column. We don't output them, but use them for decompression,\n\t\t\t * hence the special negative destination attnos.\n\t\t\t * The min/max metadata columns are normally not required for output\n\t\t\t * or decompression, they are used only as filter for the compressed\n\t\t\t * scan, so we skip them here.\n\t\t\t */\n\t\t\tAssert(strncmp(column_name,\n\t\t\t\t\t\t   COMPRESSION_COLUMN_METADATA_PREFIX,\n\t\t\t\t\t\t   strlen(COMPRESSION_COLUMN_METADATA_PREFIX)) == 0);\n\n\t\t\tif (strcmp(column_name, COMPRESSION_COLUMN_METADATA_COUNT_NAME) == 0)\n\t\t\t{\n\t\t\t\tdestination_attno = COLUMNAR_SCAN_COUNT_ID;\n\t\t\t\tmissing_count = false;\n\t\t\t}\n\t\t}\n\n\t\tconst bool is_segment = ts_array_is_member(info->settings->fd.segmentby, column_name);\n\n\t\t/*\n\t\t * Determine if we can use bulk decompression for this column.\n\t\t */\n\t\tOid typoid = get_atttype(info->chunk_rte->relid, uncompressed_chunk_attno);\n\t\tconst bool bulk_decompression_possible =\n\t\t\t!is_segment && destination_attno > 0 &&\n\t\t\ttsl_get_decompress_all_function(compression_get_default_algorithm(typoid), typoid) !=\n\t\t\t\tNULL;\n\t\tcontext->have_bulk_decompression_columns |= bulk_decompression_possible;\n\n\t\t/*\n\t\t * Save information about decompressed columns in uncompressed chunk\n\t\t * for planning of vectorized filters.\n\t\t */\n\t\tif (uncompressed_chunk_attno != InvalidAttrNumber)\n\t\t{\n\t\t\tcontext->uncompressed_attno_info[uncompressed_chunk_attno] = (UncompressedColumnInfo){\n\t\t\t\t.bulk_decompression_possible = bulk_decompression_possible,\n\t\t\t\t.custom_scan_attno = InvalidAttrNumber,\n\t\t\t};\n\t\t}\n\n\t\tcontext->compressed_attno_info[compressed_chunk_attno] = (CompressedColumnInfo){\n\t\t\t.bulk_decompression_possible = bulk_decompression_possible,\n\t\t\t.uncompressed_chunk_attno = destination_attno,\n\t\t\t.is_segmentby = is_segment,\n\t\t};\n\t}\n\n\t/*\n\t * Check that we have found all the needed columns in the compressed targetlist.\n\t * We can't conveniently check that we have all columns for all-row vars, so\n\t * skip attno 0 in this check.\n\t */\n\tBitmapset *attrs_not_found =\n\t\tbms_difference(context->uncompressed_attrs_needed, uncompressed_attrs_found);\n\tint bit = bms_next_member(attrs_not_found, 0 - FirstLowInvalidHeapAttributeNumber);\n\tif (bit >= 0)\n\t{\n\t\telog(ERROR,\n\t\t\t \"column '%s' (%d) not found in the targetlist for compressed chunk '%s'\",\n\t\t\t get_attname(info->chunk_rte->relid,\n\t\t\t\t\t\t bit + FirstLowInvalidHeapAttributeNumber,\n\t\t\t\t\t\t /* missing_ok = */ true),\n\t\t\t bit + FirstLowInvalidHeapAttributeNumber,\n\t\t\t get_rel_name(info->compressed_rte->relid));\n\t}\n\n\tif (missing_count)\n\t{\n\t\telog(ERROR, \"the count column was not found in the compressed targetlist\");\n\t}\n\n\t/*\n\t * If possible, try to make the custom scan targetlist same as the required\n\t * output targetlist, so that we can avoid a projection there.\n\t */\n\tcontext->custom_scan_targetlist = follow_uncompressed_output_tlist(context);\n\tif (context->custom_scan_targetlist != NIL)\n\t{\n\t\t/*\n\t\t * The decompression will produce a custom scan tuple, set the custom\n\t\t * scan attnos accordingly.\n\t\t */\n\t\tint custom_scan_attno = 1;\n\t\tforeach (lc, context->custom_scan_targetlist)\n\t\t{\n\t\t\tconst int uncompressed_chunk_attno =\n\t\t\t\tcastNode(Var, castNode(TargetEntry, lfirst(lc))->expr)->varattno;\n\t\t\tcontext->uncompressed_attno_info[uncompressed_chunk_attno].custom_scan_attno =\n\t\t\t\tcustom_scan_attno;\n\t\t\tcustom_scan_attno++;\n\t\t}\n\t}\n\telse\n\t{\n\t\t/*\n\t\t * The decompression will produce the uncompressed chunk tuple, set the\n\t\t * custom scan attnos accordingly.\n\t\t * Note that we might have dropped columns here, but we can set these\n\t\t * attnos for them just as well, they won't be decompressed anyway\n\t\t * because they are not in the compressed scan output.\n\t\t */\n\t\tfor (int i = 1; i <= info->chunk_rel->max_attr; i++)\n\t\t{\n\t\t\tUncompressedColumnInfo *uncompressed_info = &context->uncompressed_attno_info[i];\n\t\t\tuncompressed_info->custom_scan_attno = i;\n\t\t}\n\t}\n\n\t/*\n\t * Finally, we have to convert the decompression information we've build\n\t * into several lists so that it can be passed through the custom path\n\t * settings.\n\t */\n\tforeach (lc, compressed_output_tlist)\n\t{\n\t\tTargetEntry *target = (TargetEntry *) lfirst(lc);\n\t\tVar *var = castNode(Var, target->expr);\n\t\tAssert((Index) var->varno == info->compressed_rel->relid);\n\t\tconst AttrNumber compressed_chunk_attno = var->varattno;\n\t\tAssert(compressed_chunk_attno != InvalidAttrNumber);\n\t\tCompressedColumnInfo *compressed_info =\n\t\t\t&context->compressed_attno_info[compressed_chunk_attno];\n\n\t\t/*\n\t\t * Note that the decompressed custom scan targetlist might follow\n\t\t * neither its output targetlist (when we need more columns for filters)\n\t\t * nor the uncompressed chunk tuple. So here we have to do this\n\t\t * additional conversion.\n\t\t */\n\t\tint compressed_column_destination;\n\t\tif (compressed_info->uncompressed_chunk_attno <= 0)\n\t\t{\n\t\t\tcompressed_column_destination = compressed_info->uncompressed_chunk_attno;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tUncompressedColumnInfo *uncompressed_info =\n\t\t\t\t&context->uncompressed_attno_info[compressed_info->uncompressed_chunk_attno];\n\t\t\tcompressed_column_destination = uncompressed_info->custom_scan_attno;\n\t\t}\n\n\t\tcontext->decompression_map =\n\t\t\tlappend_int(context->decompression_map, compressed_column_destination);\n\t\tcontext->is_segmentby_column =\n\t\t\tlappend_int(context->is_segmentby_column, compressed_info->is_segmentby);\n\t\tcontext->bulk_decompression_column =\n\t\t\tlappend_int(context->bulk_decompression_column,\n\t\t\t\t\t\tcompressed_info->bulk_decompression_possible);\n\t}\n}\n\n/* replace vars that reference the compressed table with ones that reference the\n * uncompressed one. Based on replace_nestloop_params\n */\nstatic Node *\nreplace_compressed_vars(Node *node, const CompressionInfo *info)\n{\n\tif (node == NULL)\n\t\treturn NULL;\n\n\tif (IsA(node, Var))\n\t{\n\t\tVar *var = (Var *) node;\n\t\tVar *new_var;\n\t\tchar *colname;\n\n\t\t/* constify tableoid in quals */\n\t\tif ((Index) var->varno == info->chunk_rel->relid &&\n\t\t\tvar->varattno == TableOidAttributeNumber)\n\t\t\treturn (Node *)\n\t\t\t\tmakeConst(OIDOID, -1, InvalidOid, 4, (Datum) info->chunk_rte->relid, false, true);\n\n\t\t/* Upper-level Vars should be long gone at this point */\n\t\tAssert(var->varlevelsup == 0);\n\t\t/* If not to be replaced, we can just return the Var unmodified */\n\t\tif ((Index) var->varno != info->compressed_rel->relid)\n\t\t\treturn node;\n\n\t\t/* Create a decompressed Var to replace the compressed one */\n\t\tcolname = get_attname(info->compressed_rte->relid, var->varattno, false);\n\t\tnew_var = makeVar(info->chunk_rel->relid,\n\t\t\t\t\t\t  get_attnum(info->chunk_rte->relid, colname),\n\t\t\t\t\t\t  var->vartype,\n\t\t\t\t\t\t  var->vartypmod,\n\t\t\t\t\t\t  var->varcollid,\n\t\t\t\t\t\t  var->varlevelsup);\n\n\t\tif (!AttributeNumberIsValid(new_var->varattno))\n\t\t\telog(ERROR, \"cannot find column %s on decompressed chunk\", colname);\n\n\t\t/* And return the replacement var */\n\t\treturn (Node *) new_var;\n\t}\n\tif (IsA(node, PlaceHolderVar))\n\t\telog(ERROR, \"ignoring placeholders\");\n\n\treturn expression_tree_mutator(node, replace_compressed_vars, (void *) info);\n}\n\n/*\n * Find the resno of the given attribute in the provided target list\n */\nstatic AttrNumber\nfind_attr_pos_in_tlist(List *targetlist, AttrNumber pos)\n{\n\tListCell *lc;\n\n\tAssert(targetlist != NIL);\n\tAssert(pos > 0 && pos != InvalidAttrNumber);\n\n\tforeach (lc, targetlist)\n\t{\n\t\tTargetEntry *target = (TargetEntry *) lfirst(lc);\n\n\t\tif (!IsA(target->expr, Var))\n\t\t\telog(ERROR, \"compressed scan targetlist entries must be Vars\");\n\n\t\tVar *var = castNode(Var, target->expr);\n\t\tAttrNumber compressed_attno = var->varattno;\n\n\t\tif (compressed_attno == pos)\n\t\t\treturn target->resno;\n\t}\n\n\telog(ERROR, \"Unable to locate var %d in targetlist\", pos);\n\tpg_unreachable();\n}\n\nstatic bool\nis_not_runtime_constant_walker(Node *node, void *context)\n{\n\tif (node == NULL)\n\t{\n\t\treturn false;\n\t}\n\n\tswitch (nodeTag(node))\n\t{\n\t\tcase T_Var:\n\t\tcase T_PlaceHolderVar:\n\t\t\t/*\n\t\t\t * We might want to support these nodes to have vectorizable join\n\t\t\t * clauses (T_Var) or join clauses referencing a variable that is\n\t\t\t * above outer join (T_PlaceHolderVar). We don't support them at the\n\t\t\t * moment.\n\t\t\t */\n\t\t\treturn true;\n\t\tcase T_Param:\n\t\t\t/*\n\t\t\t * We support external query parameters (e.g. from parameterized\n\t\t\t * prepared statements), because they are constant for the duration\n\t\t\t * of the query.\n\t\t\t *\n\t\t\t * Join and initplan parameters are passed as PARAM_EXEC and require\n\t\t\t * support in the Rescan functions of the custom scan node. We don't\n\t\t\t * support them at the moment.\n\t\t\t */\n\t\t\treturn castNode(Param, node)->paramkind != PARAM_EXTERN;\n\t\tdefault:\n\t\t\tif (check_functions_in_node(node,\n\t\t\t\t\t\t\t\t\t\tcontains_volatile_functions_checker,\n\t\t\t\t\t\t\t\t\t\t/* context = */ NULL))\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn expression_tree_walker(node,\n\t\t\t\t\t\t\t\t\t\t  is_not_runtime_constant_walker,\n\t\t\t\t\t\t\t\t\t\t  /* context = */ NULL);\n\t}\n}\n\n/*\n * Check if the given node is a run-time constant, i.e. it doesn't contain\n * volatile functions or variables or parameters. This means we can evaluate\n * it at run time, allowing us to apply the vectorized comparison operators\n * that have the form \"Var op Const\". This applies for example to filter\n * expressions like `time > now() - interval '1 hour'`.\n * Note that we do the same evaluation when doing run time chunk exclusion, but\n * there is no good way to pass the evaluated clauses to the underlying nodes\n * like this ColumnarScan node.\n *\n * Similar checks are performed for sparse index pushdown.\n */\nstatic bool\nis_not_runtime_constant(Node *node)\n{\n\tbool result = is_not_runtime_constant_walker(node, /* context = */ NULL);\n\treturn result;\n}\n\n/*\n * Try to check if the current qual is vectorizable, and if needed make a\n * commuted copy. If not, return NULL.\n */\nNode *\nvector_qual_make(Node *qual, const VectorQualInfo *vqinfo)\n{\n\t/*\n\t * We can vectorize BoolExpr (AND/OR/NOT).\n\t */\n\tif (IsA(qual, BoolExpr))\n\t{\n\t\tBoolExpr *boolexpr = castNode(BoolExpr, qual);\n\n\t\tif (boolexpr->boolop == NOT_EXPR)\n\t\t{\n\t\t\t/*\n\t\t\t * NOT should be removed by Postgres for all operators we can\n\t\t\t * vectorize (see prepqual.c) except when the where clause is\n\t\t\t * something like 'COL = false' for bool columns. In this case, we\n\t\t\t * have to check if it was transformed to BoolExpr(NOT_EXPR, Var) so\n\t\t\t * we can vectorize it, provided that the column supports bulk\n\t\t\t * decompression.\n\t\t\t */\n\t\t\tif (list_length(boolexpr->args) == 1 && IsA(linitial(boolexpr->args), Var))\n\t\t\t{\n\t\t\t\tif (!vqinfo->vector_attrs[castNode(Var, linitial(boolexpr->args))->varattno])\n\t\t\t\t{\n\t\t\t\t\treturn NULL;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t}\n\n\t\tbool need_copy = false;\n\t\tList *vectorized_args = NIL;\n\t\tListCell *lc;\n\t\tforeach (lc, boolexpr->args)\n\t\t{\n\t\t\tNode *arg = lfirst(lc);\n\t\t\tNode *vectorized_arg = vector_qual_make(arg, vqinfo);\n\t\t\tif (vectorized_arg == NULL)\n\t\t\t{\n\t\t\t\treturn NULL;\n\t\t\t}\n\n\t\t\tif (vectorized_arg != arg)\n\t\t\t{\n\t\t\t\tneed_copy = true;\n\t\t\t}\n\n\t\t\tvectorized_args = lappend(vectorized_args, vectorized_arg);\n\t\t}\n\n\t\tif (!need_copy)\n\t\t{\n\t\t\treturn (Node *) boolexpr;\n\t\t}\n\n\t\tBoolExpr *boolexpr_copy = (BoolExpr *) copyObject(boolexpr);\n\t\tboolexpr_copy->args = vectorized_args;\n\t\treturn (Node *) boolexpr_copy;\n\t}\n\n\t/*\n\t * Among the simple predicates, we vectorize some \"Var op Const\" binary\n\t * predicates, scalar array operations with these predicates, boolean variables\n\t * and null test.\n\t */\n\tNullTest *nulltest = NULL;\n\tOpExpr *opexpr = NULL;\n\tScalarArrayOpExpr *saop = NULL;\n\tNode *arg1 = NULL;\n\tNode *arg2 = NULL;\n\tOid opno = InvalidOid;\n\tVar *var = NULL;\n\tif (IsA(qual, OpExpr))\n\t{\n\t\topexpr = castNode(OpExpr, qual);\n\t\topno = opexpr->opno;\n\t\tif (list_length(opexpr->args) != 2)\n\t\t{\n\t\t\treturn NULL;\n\t\t}\n\t\targ1 = (Node *) linitial(opexpr->args);\n\t\targ2 = (Node *) lsecond(opexpr->args);\n\t}\n\telse if (IsA(qual, ScalarArrayOpExpr))\n\t{\n\t\tsaop = castNode(ScalarArrayOpExpr, qual);\n\t\topno = saop->opno;\n\t\tAssert(list_length(saop->args) == 2);\n\t\targ1 = (Node *) linitial(saop->args);\n\t\targ2 = (Node *) lsecond(saop->args);\n\t}\n\telse if (IsA(qual, NullTest))\n\t{\n\t\tnulltest = castNode(NullTest, qual);\n\t\targ1 = (Node *) nulltest->arg;\n\t}\n\telse if (IsA(qual, BooleanTest))\n\t{\n\t\tBooleanTest *booltest = castNode(BooleanTest, qual);\n\t\tif (IsA(booltest->arg, Var))\n\t\t{\n\t\t\tvar = castNode(Var, booltest->arg);\n\t\t\tif (!vqinfo->vector_attrs[var->varattno])\n\t\t\t{\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t\treturn (Node *) booltest;\n\t\t}\n\t\telse\n\t\t{\n\t\t\treturn NULL;\n\t\t}\n\t}\n\telse if (IsA(qual, Var) && (castNode(Var, qual))->vartype == BOOLOID)\n\t{\n\t\t/* We can vectorize boolean variables if bulk decompression is possible. */\n\t\tvar = castNode(Var, qual);\n\t\tif (!vqinfo->vector_attrs[var->varattno])\n\t\t{\n\t\t\treturn NULL;\n\t\t}\n\t\treturn (Node *) var;\n\t}\n\telse\n\t{\n\t\treturn NULL;\n\t}\n\n\tif (opexpr && IsA(arg2, Var))\n\t{\n\t\t/*\n\t\t * Try to commute the operator if we have Var on the right.\n\t\t */\n\t\topno = get_commutator(opno);\n\t\tif (!OidIsValid(opno))\n\t\t{\n\t\t\treturn NULL;\n\t\t}\n\n\t\topexpr = (OpExpr *) copyObject(opexpr);\n\t\topexpr->opno = opno;\n\t\t/*\n\t\t * opfuncid is a cache, we can set it to InvalidOid like the\n\t\t * CommuteOpExpr() does.\n\t\t */\n\t\topexpr->opfuncid = InvalidOid;\n\t\topexpr->args = list_make2(arg2, arg1);\n\t\tNode *tmp = arg1;\n\t\targ1 = arg2;\n\t\targ2 = tmp;\n\t}\n\n\t/*\n\t * We can vectorize the operation where the left side is a Var.\n\t */\n\tif (!IsA(arg1, Var))\n\t{\n\t\treturn NULL;\n\t}\n\n\tvar = castNode(Var, arg1);\n\tif ((Index) var->varno != vqinfo->rti)\n\t{\n\t\t/*\n\t\t * We have a Var from other relation (join clause), can't vectorize it\n\t\t * at the moment.\n\t\t */\n\t\treturn NULL;\n\t}\n\n\tif (var->varattno <= 0)\n\t{\n\t\t/*\n\t\t * Can't vectorize operators with special variables such as whole-row var.\n\t\t */\n\t\treturn NULL;\n\t}\n\n\t/*\n\t * ExecQual is performed before ExecProject and operates on the decompressed\n\t * scan slot, so the qual attnos are the uncompressed chunk attnos.\n\t */\n\tif (!vqinfo->vector_attrs[var->varattno])\n\t{\n\t\t/* This column doesn't support bulk decompression. */\n\t\treturn NULL;\n\t}\n\n\tif (nulltest)\n\t{\n\t\t/*\n\t\t * The checks we've done to this point is all that is required for null\n\t\t * test.\n\t\t */\n\t\treturn (Node *) nulltest;\n\t}\n\n\t/*\n\t * We can vectorize the operation where the right side is a constant or can\n\t * be evaluated to a constant at run time (e.g. contains stable functions).\n\t */\n\tAssert(arg2);\n\tif (is_not_runtime_constant(arg2))\n\t{\n\t\treturn NULL;\n\t}\n\n\tOid opcode = get_opcode(opno);\n\tif (!get_vector_const_predicate(opcode))\n\t{\n\t\treturn NULL;\n\t}\n\n\tif (OidIsValid(var->varcollid) && !get_collation_isdeterministic(var->varcollid))\n\t{\n\t\t/*\n\t\t * Can't vectorize string equality with a nondeterministic collation.\n\t\t * Not sure if we have to check the collation of Const as well, but it\n\t\t * will be known only at planning time. Currently we don't check it at\n\t\t * all. Also this is untested because we don't have nondeterministic\n\t\t * collations in all test configurations.\n\t\t */\n\t\treturn NULL;\n\t}\n\n\tif (opexpr)\n\t{\n\t\t/*\n\t\t * The checks we've done to this point is all that is required for\n\t\t * OpExpr.\n\t\t */\n\t\treturn (Node *) opexpr;\n\t}\n\n\t/*\n\t * The only option that is left is a ScalarArrayOpExpr.\n\t */\n\tAssert(saop != NULL);\n\n\t/*\n\t * The planner can decide to build a hash table. It's still somewhat slower\n\t * than our vectorized lookups for array lengths <= 32.\n\t */\n\tif (saop->hashfuncid)\n\t{\n\t\tif (!IsA(arg2, Const))\n\t\t{\n\t\t\t/*\n\t\t\t * The planner as of PG 17 only uses hashing for plan-time constants,\n\t\t\t * but double-check.\n\t\t\t */\n\t\t\treturn NULL;\n\t\t}\n\n\t\tConst *c = castNode(Const, arg2);\n\t\tif (c->constisnull)\n\t\t{\n\t\t\t/*\n\t\t\t * Shouldn't happen, but not controlled by us.\n\t\t\t */\n\t\t\treturn NULL;\n\t\t}\n\n\t\tDatum arrdatum = c->constvalue;\n\t\tArrayType *arr = (ArrayType *) DatumGetPointer(arrdatum);\n\t\tconst int nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));\n\t\tif (nitems > 32)\n\t\t{\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\treturn (Node *) saop;\n}\n\n/*\n * Find the scan qualifiers that can be vectorized and put them into a separate\n * list.\n */\nstatic void\nfind_vectorized_quals(DecompressionMapContext *context, ColumnarScanPath *path, List *qual_list,\n\t\t\t\t\t  List **vectorized, List **nonvectorized)\n{\n\tVectorQualInfo vqi = {\n\t\t.maxattno = path->info->chunk_rel->max_attr,\n\t\t.vector_attrs = build_vector_attrs_array(context->uncompressed_attno_info, path->info),\n\t\t.rti = path->info->chunk_rel->relid,\n\t};\n\n\tListCell *lc;\n\tforeach (lc, qual_list)\n\t{\n\t\tNode *source_qual = lfirst(lc);\n\n\t\t/*\n\t\t * We can't vectorize the stable cross-type operators (for example\n\t\t * timestamp > timestamptz), so try to cast the constant to the same\n\t\t * type to convert it to the same-type operator. We do the same thing\n\t\t * for chunk exclusion.\n\t\t */\n\t\tNode *transformed_comparison =\n\t\t\t(Node *) ts_transform_cross_datatype_comparison((Expr *) source_qual);\n\t\tNode *vectorized_qual = vector_qual_make(transformed_comparison, &vqi);\n\t\tif (vectorized_qual)\n\t\t{\n\t\t\t*vectorized = lappend(*vectorized, vectorized_qual);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t*nonvectorized = lappend(*nonvectorized, source_qual);\n\t\t}\n\t}\n\n\tpfree(vqi.vector_attrs);\n}\n\n/*\n * Copy of the Postgres' static function from createplan.c.\n *\n * Some places in this file build Sort nodes that don't have a directly\n * corresponding Path node.  The cost of the sort is, or should have been,\n * included in the cost of the Path node we're working from, but since it's\n * not split out, we have to re-figure it using cost_sort().  This is just\n * to label the Sort node nicely for EXPLAIN.\n *\n * limit_tuples is as for cost_sort (in particular, pass -1 if no limit)\n */\nstatic void\nts_label_sort_with_costsize(PlannerInfo *root, Sort *plan, double limit_tuples)\n{\n\tPlan *lefttree = plan->plan.lefttree;\n\tPath sort_path; /* dummy for result of cost_sort */\n\n\t/*\n\t * This function shouldn't have to deal with IncrementalSort plans because\n\t * they are only created from corresponding Path nodes.\n\t */\n\tAssert(IsA(plan, Sort));\n\n\tcost_sort(&sort_path,\n\t\t\t  root,\n\t\t\t  NIL,\n#if PG18_GE\n\t\t\t  lefttree->disabled_nodes,\n#endif\n\t\t\t  lefttree->total_cost,\n\t\t\t  lefttree->plan_rows,\n\t\t\t  lefttree->plan_width,\n\t\t\t  0.0,\n\t\t\t  work_mem,\n\t\t\t  limit_tuples);\n\tplan->plan.startup_cost = sort_path.startup_cost;\n\tplan->plan.total_cost = sort_path.total_cost;\n\tplan->plan.plan_rows = lefttree->plan_rows;\n\tplan->plan.plan_width = lefttree->plan_width;\n\tplan->plan.parallel_aware = false;\n\tplan->plan.parallel_safe = lefttree->parallel_safe;\n}\n\n/*\n * Find a variable of the given relation somewhere in the expression tree.\n * Currently we use this to find the Var argument of time_bucket, when we prepare\n * the batch sorted merge parameters after using the monotonous sorting transform\n * optimization.\n */\nstatic Var *\nfind_var_subexpression(void *expr, Index varno)\n{\n\tList *varlist = pull_var_clause((Node *) expr, 0);\n\tif (list_length(varlist) == 1)\n\t{\n\t\tVar *var = (Var *) linitial(varlist);\n\t\tif ((Index) var->varno == (Index) varno)\n\t\t{\n\t\t\treturn var;\n\t\t}\n\n\t\treturn NULL;\n\t}\n\n\treturn NULL;\n}\n\nPlan *\ncolumnar_scan_plan_create(PlannerInfo *root, RelOptInfo *rel, CustomPath *path,\n\t\t\t\t\t\t  List *output_targetlist, List *clauses, List *custom_plans)\n{\n\tColumnarScanPath *dcpath = (ColumnarScanPath *) path;\n\tCustomScan *decompress_plan = makeNode(CustomScan);\n\tScan *compressed_scan = linitial(custom_plans);\n\tPath *compressed_path = linitial(path->custom_paths);\n\tList *settings;\n\tListCell *lc;\n\n\tAssert(list_length(custom_plans) == 1);\n\tAssert(list_length(path->custom_paths) == 1);\n\n\tdecompress_plan->flags = path->flags;\n\tdecompress_plan->methods = &columnar_scan_plan_methods;\n\tdecompress_plan->scan.scanrelid = dcpath->info->chunk_rel->relid;\n\n\tif (IsA(compressed_path, IndexPath))\n\t{\n\t\t/*\n\t\t * Check if any of the decompressed scan clauses are redundant with\n\t\t * the compressed index scan clauses. Note that we can't use\n\t\t * is_redundant_derived_clause() here, because it can't work with\n\t\t * IndexClause's, so we use some custom code based on it.\n\t\t */\n\t\tIndexPath *ipath = castNode(IndexPath, compressed_path);\n\t\tforeach (lc, clauses)\n\t\t{\n\t\t\tRestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);\n\n\t\t\tListCell *indexclause_cell = NULL;\n\t\t\tif (rinfo->parent_ec != NULL)\n\t\t\t{\n\t\t\t\tforeach (indexclause_cell, ipath->indexclauses)\n\t\t\t\t{\n\t\t\t\t\tIndexClause *indexclause = lfirst(indexclause_cell);\n\t\t\t\t\tRestrictInfo *index_rinfo = indexclause->rinfo;\n\t\t\t\t\tif (index_rinfo->parent_ec == rinfo->parent_ec)\n\t\t\t\t\t{\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (indexclause_cell != NULL)\n\t\t\t{\n\t\t\t\t/* We already have an index clause derived from same EquivalenceClass. */\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * We don't have this clause in the underlying index scan, add it\n\t\t\t * to the decompressed scan.\n\t\t\t */\n\t\t\tdecompress_plan->scan.plan.qual =\n\t\t\t\tlappend(decompress_plan->scan.plan.qual, rinfo->clause);\n\t\t}\n\t}\n\telse\n\t{\n\t\tforeach (lc, clauses)\n\t\t{\n\t\t\tRestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);\n\t\t\tdecompress_plan->scan.plan.qual =\n\t\t\t\tlappend(decompress_plan->scan.plan.qual, rinfo->clause);\n\t\t}\n\t}\n\n\tdecompress_plan->scan.plan.qual =\n\t\t(List *) replace_compressed_vars((Node *) decompress_plan->scan.plan.qual, dcpath->info);\n\n\t/*\n\t * Try to use a physical tlist if possible. There's no reason to do the\n\t * extra work of projecting the result of compressed chunk scan, because\n\t * ColumnarScan can choose only the needed columns itself.\n\t * Note that Postgres uses the CP_EXACT_TLIST option when planning the child\n\t * paths of the Custom path, so we won't automatically get a physical tlist\n\t * here.\n\t */\n\tbool target_list_compressed_is_physical = false;\n\tif (compressed_path->pathtype == T_IndexOnlyScan)\n\t{\n\t\tcompressed_scan->plan.targetlist = ((IndexPath *) compressed_path)->indexinfo->indextlist;\n\t}\n\telse\n\t{\n\t\tList *physical_tlist = build_physical_tlist(root, dcpath->info->compressed_rel);\n\t\t/* Can be null if the relation has dropped columns. */\n\t\tif (physical_tlist)\n\t\t{\n\t\t\tcompressed_scan->plan.targetlist = physical_tlist;\n\t\t\ttarget_list_compressed_is_physical = true;\n\t\t}\n\t}\n\n\t/*\n\t * Determine which columns we have to decompress.\n\t * output_targetlist is sometimes empty, e.g. for a direct select from\n\t * chunk. We have a ProjectionPath above ColumnarScan in this case, and\n\t * the targetlist for this path is not built by the planner\n\t * (CP_IGNORE_TLIST). This is why we have to examine rel pathtarget.\n\t * Looking at the targetlist is not enough, we also have to decompress the\n\t * columns participating in quals and in pathkeys.\n\t */\n\tBitmapset *uncompressed_attrs_needed = NULL;\n\tpull_varattnos((Node *) decompress_plan->scan.plan.qual,\n\t\t\t\t   dcpath->info->chunk_rel->relid,\n\t\t\t\t   &uncompressed_attrs_needed);\n\tpull_varattnos((Node *) dcpath->custom_path.path.pathtarget->exprs,\n\t\t\t\t   dcpath->info->chunk_rel->relid,\n\t\t\t\t   &uncompressed_attrs_needed);\n\n\t/*\n\t * Determine which compressed column goes to which output column.\n\t */\n\tDecompressionMapContext context = { .root = root,\n\t\t\t\t\t\t\t\t\t\t.decompress_path = dcpath,\n\t\t\t\t\t\t\t\t\t\t.uncompressed_attrs_needed = uncompressed_attrs_needed };\n\tbuild_decompression_map(&context, compressed_scan->plan.targetlist);\n\n\t/* Build heap sort info for batch sorted merge. */\n\tList *sort_options = NIL;\n\n\tif (dcpath->batch_sorted_merge)\n\t{\n\t\t/*\n\t\t * 'order by' of the query and the 'order by' of the compressed batches\n\t\t * match, so we will we use a heap to merge the batches. For the heap we\n\t\t * need a compare function that determines the heap order. This function\n\t\t * is constructed here.\n\t\t *\n\t\t * Batch sorted merge is done over the decompressed chunk scan tuple, so\n\t\t * we must match the pathkeys to the decompressed chunk tupdesc.\n\t\t */\n\n\t\tint numsortkeys = list_length(dcpath->custom_path.path.pathkeys);\n\n\t\tList *sort_col_idx = NIL;\n\t\tList *sort_ops = NIL;\n\t\tList *sort_collations = NIL;\n\t\tList *sort_nulls = NIL;\n\n\t\t/*\n\t\t */\n\t\tListCell *lc;\n\t\tforeach (lc, dcpath->custom_path.path.pathkeys)\n\t\t{\n\t\t\tPathKey *pk = lfirst(lc);\n\t\t\tEquivalenceClass *ec = pk->pk_eclass;\n\n\t\t\t/*\n\t\t\t * Find the equivalence member that belongs to decompressed relation.\n\t\t\t */\n\t\t\tEquivalenceMember *em;\n\t\t\tListCell *membercell = NULL;\n#if PG18_GE\n\t\t\t/* In PG18, iterating over child ems requires you to\n\t\t\t * use child relids with a special iterator. Here we gather\n\t\t\t * them by collecting them from childmembers array.\n\t\t\t *\n\t\t\t * https://github.com/postgres/postgres/commit/d69d45a5\n\t\t\t */\n\t\t\tEquivalenceMemberIterator it;\n\n\t\t\tsetup_eclass_member_iterator(&it, ec, dcpath->custom_path.path.parent->relids);\n\t\t\twhile ((em = eclass_member_iterator_next(&it)) != NULL)\n\t\t\t{\n\t\t\t\t/* Setting up so that the check below doesn't complain */\n\t\t\t\tmembercell = &list_make_int_cell(1);\n#else\n\t\t\tforeach (membercell, ec->ec_members)\n\t\t\t{\n\t\t\t\tem = lfirst(membercell);\n#endif\n\n\t\t\t\tif (em->em_is_const)\n\t\t\t\t{\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tint em_relid;\n\t\t\t\tif (!bms_get_singleton_member(em->em_relids, &em_relid))\n\t\t\t\t{\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif ((Index) em_relid != dcpath->info->chunk_rel->relid)\n\t\t\t\t{\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t/*\n\t\t\t\t * The equivalence member expression might be a monotonous\n\t\t\t\t * expression of the decompressed relation Var, so recurse to\n\t\t\t\t * find it.\n\t\t\t\t */\n\t\t\t\tVar *var = find_var_subexpression(em->em_expr, em_relid);\n\t\t\t\tEnsure(var != NULL,\n\t\t\t\t\t   \"non-Var pathkey not expected for compressed batch sorted merge\");\n\n\t\t\t\tAssert((Index) var->varno == (Index) em_relid);\n\n\t\t\t\t/*\n\t\t\t\t * Convert its varattno which is the varattno of the\n\t\t\t\t * uncompressed chunk tuple, to the decompressed scan tuple\n\t\t\t\t * varattno.\n\t\t\t\t */\n\t\t\t\tconst int decompressed_scan_attno =\n\t\t\t\t\tcontext.uncompressed_attno_info[var->varattno].custom_scan_attno;\n\t\t\t\tAssert(decompressed_scan_attno > 0);\n\n\t\t\t\t/*\n\t\t\t\t * Look up the correct sort operator from the PathKey's slightly\n\t\t\t\t * abstracted representation.\n\t\t\t\t */\n\t\t\t\tOid sortop = get_opfamily_member(pk->pk_opfamily,\n\t\t\t\t\t\t\t\t\t\t\t\t var->vartype,\n\t\t\t\t\t\t\t\t\t\t\t\t var->vartype,\n\t\t\t\t\t\t\t\t\t\t\t\t pk->pk_cmptype);\n\t\t\t\tif (!OidIsValid(sortop)) /* should not happen */\n\t\t\t\t\telog(ERROR,\n\t\t\t\t\t\t \"missing operator %d(%u,%u) in opfamily %u\",\n\t\t\t\t\t\t pk->pk_cmptype,\n\t\t\t\t\t\t var->vartype,\n\t\t\t\t\t\t var->vartype,\n\t\t\t\t\t\t pk->pk_opfamily);\n\n\t\t\t\tsort_col_idx = lappend_oid(sort_col_idx, decompressed_scan_attno);\n\t\t\t\tsort_collations = lappend_oid(sort_collations, var->varcollid);\n\t\t\t\tsort_nulls = lappend_oid(sort_nulls, pk->pk_nulls_first);\n\t\t\t\tsort_ops = lappend_oid(sort_ops, sortop);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tEnsure(membercell != NULL,\n\t\t\t\t   \"could not find matching decompressed chunk column for batch sorted merge \"\n\t\t\t\t   \"pathkey\");\n\t\t}\n\n\t\tsort_options = list_make4(sort_col_idx, sort_ops, sort_collations, sort_nulls);\n\n\t\t/*\n\t\t * Build a sort node for the compressed batches. The sort function is\n\t\t * derived from the sort function of the pathkeys, except that it refers\n\t\t * to the min and max metadata columns of the batches. We have already\n\t\t * verified that the pathkeys match the compression order_by, so this\n\t\t * mapping is possible.\n\t\t */\n\t\tAttrNumber *sortColIdx = palloc(sizeof(AttrNumber) * numsortkeys);\n\t\tOid *sortOperators = palloc(sizeof(Oid) * numsortkeys);\n\t\tOid *collations = palloc(sizeof(Oid) * numsortkeys);\n\t\tbool *nullsFirst = palloc(sizeof(bool) * numsortkeys);\n\t\tfor (int i = 0; i < numsortkeys; i++)\n\t\t{\n\t\t\tOid sortop = list_nth_oid(sort_ops, i);\n\n\t\t\t/* Find the operator in pg_amop --- failure shouldn't happen */\n\t\t\tOid opfamily, opcintype;\n\t\t\tCompareType strategy;\n\t\t\tif (!get_ordering_op_properties(list_nth_oid(sort_ops, i),\n\t\t\t\t\t\t\t\t\t\t\t&opfamily,\n\t\t\t\t\t\t\t\t\t\t\t&opcintype,\n\t\t\t\t\t\t\t\t\t\t\t&strategy))\n\t\t\t\telog(ERROR, \"operator %u is not a valid ordering operator\", sortOperators[i]);\n\n\t\t\t/*\n\t\t\t * This way to determine the matching metadata column works, because\n\t\t\t * we have already verified that the pathkeys match the compression\n\t\t\t * orderby.\n\t\t\t */\n\t\t\tAssert(strategy == BTLessStrategyNumber || strategy == BTGreaterStrategyNumber);\n\t\t\tchar *meta_col_name = strategy == BTLessStrategyNumber ?\n\t\t\t\t\t\t\t\t\t  column_segment_min_name(i + 1) :\n\t\t\t\t\t\t\t\t\t  column_segment_max_name(i + 1);\n\n\t\t\tAttrNumber attr_position =\n\t\t\t\tget_attnum(dcpath->info->compressed_rte->relid, meta_col_name);\n\n\t\t\tif (attr_position == InvalidAttrNumber)\n\t\t\t\telog(ERROR, \"couldn't find metadata column \\\"%s\\\"\", meta_col_name);\n\n\t\t\t/*\n\t\t\t * If the the compressed target list is not based on the layout of\n\t\t\t * the uncompressed chunk (see comment for physical_tlist above),\n\t\t\t * adjust the position of the attribute.\n\t\t\t */\n\t\t\tif (target_list_compressed_is_physical)\n\t\t\t\tsortColIdx[i] = attr_position;\n\t\t\telse\n\t\t\t\tsortColIdx[i] =\n\t\t\t\t\tfind_attr_pos_in_tlist(compressed_scan->plan.targetlist, attr_position);\n\n\t\t\tsortOperators[i] = sortop;\n\t\t\tcollations[i] = list_nth_oid(sort_collations, i);\n\t\t\tnullsFirst[i] = list_nth_oid(sort_nulls, i);\n\t\t}\n\n\t\t/* Now build the compressed batches sort node */\n\t\tSort *sort = ts_make_sort((Plan *) compressed_scan,\n\t\t\t\t\t\t\t\t  numsortkeys,\n\t\t\t\t\t\t\t\t  sortColIdx,\n\t\t\t\t\t\t\t\t  sortOperators,\n\t\t\t\t\t\t\t\t  collations,\n\t\t\t\t\t\t\t\t  nullsFirst);\n\n\t\tts_label_sort_with_costsize(root, sort, /* limit_tuples = */ -1.0);\n\n\t\tdecompress_plan->custom_plans = list_make1(sort);\n\t}\n\telse\n\t{\n\t\t/*\n\t\t * Add a sort if the compressed scan is not ordered appropriately.\n\t\t */\n\t\tif (!pathkeys_contained_in(dcpath->required_compressed_pathkeys, compressed_path->pathkeys))\n\t\t{\n\t\t\tList *compressed_pks = dcpath->required_compressed_pathkeys;\n\t\t\tSort *sort = ts_make_sort_from_pathkeys((Plan *) compressed_scan,\n\t\t\t\t\t\t\t\t\t\t\t\t\tcompressed_pks,\n\t\t\t\t\t\t\t\t\t\t\t\t\tbms_make_singleton(compressed_scan->scanrelid));\n\n\t\t\tts_label_sort_with_costsize(root, sort, /* limit_tuples = */ -1.0);\n\n\t\t\tdecompress_plan->custom_plans = list_make1(sort);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tdecompress_plan->custom_plans = custom_plans;\n\t\t}\n\t}\n\n\tAssert(list_length(custom_plans) == 1);\n\n\tconst bool enable_bulk_decompression = !dcpath->batch_sorted_merge &&\n\t\t\t\t\t\t\t\t\t\t   ts_guc_enable_bulk_decompression &&\n\t\t\t\t\t\t\t\t\t\t   context.have_bulk_decompression_columns;\n\n\t/*\n\t * For some predicates, we have more efficient implementation that work on\n\t * the entire compressed batch in one go. They go to this list, and the rest\n\t * goes into the usual scan.plan.qual.\n\t */\n\tList *vectorized_quals = NIL;\n\tif (enable_bulk_decompression)\n\t{\n\t\tList *nonvectorized_quals = NIL;\n\t\tfind_vectorized_quals(&context,\n\t\t\t\t\t\t\t  dcpath,\n\t\t\t\t\t\t\t  decompress_plan->scan.plan.qual,\n\t\t\t\t\t\t\t  &vectorized_quals,\n\t\t\t\t\t\t\t  &nonvectorized_quals);\n\n\t\tdecompress_plan->scan.plan.qual = nonvectorized_quals;\n\t}\n\n#ifdef TS_DEBUG\n\tif (ts_guc_debug_require_vector_qual == DRO_Forbid && list_length(vectorized_quals) > 0)\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t errmsg(\"debug: encountered vector quals when they are disabled\")));\n\t}\n\telse if (ts_guc_debug_require_vector_qual == DRO_Require)\n\t{\n\t\tif (list_length(decompress_plan->scan.plan.qual) > 0)\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t\t errmsg(\"debug: encountered non-vector quals when they are disabled\")));\n\t\t}\n\t\tif (list_length(vectorized_quals) == 0)\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t\t errmsg(\"debug: did not encounter vector quals when they are required\")));\n\t\t}\n\t}\n#endif\n\n\tsettings = ts_new_list(T_IntList, DCS_Count);\n\tlfirst_int(list_nth_cell(settings, DCS_HypertableId)) = dcpath->info->hypertable_id;\n\tlfirst_int(list_nth_cell(settings, DCS_ChunkRelid)) = dcpath->info->chunk_rte->relid;\n\tlfirst_int(list_nth_cell(settings, DCS_Reverse)) = dcpath->reverse;\n\tlfirst_int(list_nth_cell(settings, DCS_BatchSortedMerge)) = dcpath->batch_sorted_merge;\n\tlfirst_int(list_nth_cell(settings, DCS_EnableBulkDecompression)) = enable_bulk_decompression;\n\tlfirst_int(list_nth_cell(settings, DCS_HasRowMarks)) = root->parse->rowMarks != NIL;\n\tlfirst_int(list_nth_cell(settings, DCS_ChunkStatus)) = dcpath->chunk_status;\n\n\t/*\n\t * Vectorized quals must go into custom_exprs, because Postgres has to see\n\t * them and perform the varno adjustments on them when flattening the\n\t * subqueries.\n\t */\n\tdecompress_plan->custom_exprs = list_make1(vectorized_quals);\n\n\tdecompress_plan->custom_private = ts_new_list(T_List, DCP_Count);\n\tlfirst(list_nth_cell(decompress_plan->custom_private, DCP_Settings)) = settings;\n\tlfirst(list_nth_cell(decompress_plan->custom_private, DCP_DecompressionMap)) =\n\t\tcontext.decompression_map;\n\tlfirst(list_nth_cell(decompress_plan->custom_private, DCP_IsSegmentbyColumn)) =\n\t\tcontext.is_segmentby_column;\n\tlfirst(list_nth_cell(decompress_plan->custom_private, DCP_BulkDecompressionColumn)) =\n\t\tcontext.bulk_decompression_column;\n\tlfirst(list_nth_cell(decompress_plan->custom_private, DCP_SortInfo)) = sort_options;\n\n\t/*\n\t * We might be using a custom scan tuple if it allows us to avoid the\n\t * projection. Otherwise, this tlist is NIL and we'll be using the\n\t * uncompressed tuple as the custom scan tuple.\n\t */\n\tdecompress_plan->custom_scan_tlist = context.custom_scan_targetlist;\n\n\t/*\n\t * Note that we cannot decide here that we require a projection. It is\n\t * decided at Path stage, now we must produce the requested targetlist.\n\t */\n\tdecompress_plan->scan.plan.targetlist = output_targetlist;\n\n\treturn &decompress_plan->scan.plan;\n}\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/planner.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\ntypedef enum\n{\n\tDCS_HypertableId = 0,\n\tDCS_ChunkRelid = 1,\n\tDCS_Reverse = 2,\n\tDCS_BatchSortedMerge = 3,\n\tDCS_EnableBulkDecompression = 4,\n\tDCS_HasRowMarks = 5,\n\tDCS_ChunkStatus = 6,\n\tDCS_Count\n} ColumnarScanSettingsIndex;\n\ntypedef enum\n{\n\tDCP_Settings = 0,\n\tDCP_DecompressionMap = 1,\n\tDCP_IsSegmentbyColumn = 2,\n\tDCP_BulkDecompressionColumn = 3,\n\tDCP_SortInfo = 4,\n\tDCP_Count\n} ColumnarScanPrivateIndex;\n\nextern Plan *columnar_scan_plan_create(PlannerInfo *root, RelOptInfo *rel, CustomPath *path,\n\t\t\t\t\t\t\t\t\t   List *output_targetlist, List *clauses, List *custom_plans);\n\nextern void _columnar_scan_init(void);\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/pred_text.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include \"pred_text.h\"\n\n#include <miscadmin.h>\n\n#include \"compat/compat.h\"\n\n#if PG16_GE\n#include <varatt.h>\n#endif\n\nstatic void\nvector_const_text_comparison(const ArrowArray *arrow, const Datum constdatum, bool needequal,\n\t\t\t\t\t\t\t uint64 *restrict result)\n{\n\tAssert(!arrow->dictionary);\n\n\ttext *consttext = (text *) DatumGetPointer(constdatum);\n\tconst size_t textlen = VARSIZE_ANY_EXHDR(consttext);\n\tconst uint8 *cstring = (uint8 *) VARDATA_ANY(consttext);\n\tconst uint32 *offsets = (uint32 *) arrow->buffers[1];\n\tconst uint8 *values = (uint8 *) arrow->buffers[2];\n\n\tconst size_t n = arrow->length;\n\tfor (size_t outer = 0; outer < n / 64; outer++)\n\t{\n\t\tuint64 word = 0;\n\t\tfor (size_t inner = 0; inner < 64; inner++)\n\t\t{\n\t\t\tconst size_t row = (outer * 64) + inner;\n\t\t\tconst size_t bit_index = inner;\n#define INNER_LOOP                                                                                 \\\n\tconst uint32 start = offsets[row];                                                             \\\n\tconst uint32 end = offsets[row + 1];                                                           \\\n\tAssert(end >= start);                                                                          \\\n\tconst uint32 veclen = end - start;                                                             \\\n\tbool isequal = veclen != textlen ?                                                             \\\n\t\t\t\t\t   false :                                                                     \\\n\t\t\t\t\t   (strncmp((char *) &values[start], (char *) cstring, textlen) == 0);         \\\n\tword |= ((uint64) (isequal == needequal)) << bit_index;\n\n\t\t\tINNER_LOOP\n\t\t}\n\t\tresult[outer] &= word;\n\t}\n\n\tif (n % 64)\n\t{\n\t\tuint64 word = 0;\n\t\tfor (size_t row = (n / 64) * 64; row < n; row++)\n\t\t{\n\t\t\tconst size_t bit_index = row % 64;\n\t\t\tINNER_LOOP\n\t\t}\n\t\tresult[n / 64] &= word;\n\t}\n\n#undef INNER_LOOP\n}\n\nvoid\nvector_const_texteq(const ArrowArray *arrow, const Datum constdatum, uint64 *restrict result)\n{\n\tvector_const_text_comparison(arrow, constdatum, /* needequal = */ true, result);\n}\n\nvoid\nvector_const_textne(const ArrowArray *arrow, const Datum constdatum, uint64 *restrict result)\n{\n\tvector_const_text_comparison(arrow, constdatum, /* needequal = */ false, result);\n}\n\n/*\n * Generate specializations for LIKE functions based on database encoding. This\n * follows the Postgres code from backend/utils/adt/like.c, version 15.0,\n * commit sha 2a7ce2e2ce474504a707ec03e128fde66cfb8b48.\n * The copy of PG code begins here.\n * ----------------------------------------------------------------------------\n */\n\n#define LIKE_TRUE 1\n#define LIKE_FALSE 0\n#define LIKE_ABORT (-1)\n\n/* setup to compile like_match.c for UTF8 encoding, using fast NextChar */\n#define NextByte(p, plen) ((p)++, (plen)--)\n#define NextChar(p, plen)                                                                          \\\n\tdo                                                                                             \\\n\t{                                                                                              \\\n\t\t(p)++;                                                                                     \\\n\t\t(plen)--;                                                                                  \\\n\t} while ((plen) > 0 && (*(p) & 0xC0) == 0x80)\n#define MatchText UTF8_MatchText\n\n#include \"import/ts_like_match.c\"\n\n/*\n * ----------------------------------------------------------------------------\n * The copy of PG code ends here.\n */\n\nstatic void\nvector_const_like_impl(const ArrowArray *arrow, const Datum constdatum, uint64 *restrict result,\n\t\t\t\t\t   int (*match)(const char *, int, const char *, int), bool should_match)\n{\n\tAssert(!arrow->dictionary);\n\n\ttext *consttext = (text *) DatumGetPointer(constdatum);\n\tconst size_t textlen = VARSIZE_ANY_EXHDR(consttext);\n\tconst char *restrict cstring = VARDATA_ANY(consttext);\n\tconst uint32 *offsets = (uint32 *) arrow->buffers[1];\n\tconst char *restrict values = arrow->buffers[2];\n\n\tconst size_t n = arrow->length;\n\tfor (size_t outer = 0; outer < n / 64; outer++)\n\t{\n\t\tuint64 word = 0;\n\t\tfor (size_t inner = 0; inner < 64; inner++)\n\t\t{\n\t\t\tconst size_t row = (outer * 64) + inner;\n\t\t\tconst size_t bit_index = inner;\n\t\t\t/*\n\t\t\t * The inner loop could have been an inline function, but it would have 5\n\t\t\t * parameters and one of them in/out, so a macro probably has better\n\t\t\t * readability.\n\t\t\t */\n#define INNER_LOOP                                                                                 \\\n\tconst uint32 start = offsets[row];                                                             \\\n\tconst uint32 end = offsets[row + 1];                                                           \\\n\tAssert(end >= start);                                                                          \\\n\tconst uint32 veclen = end - start;                                                             \\\n\tint result = match(&values[start], veclen, cstring, textlen);                                  \\\n\tbool valid = (result == LIKE_TRUE) == should_match;                                            \\\n\tword |= ((uint64) valid) << bit_index;\n\n\t\t\tINNER_LOOP\n\t\t}\n\t\tresult[outer] &= word;\n\t}\n\n\tif (n % 64)\n\t{\n\t\tuint64 word = 0;\n\t\tfor (size_t row = (n / 64) * 64; row < n; row++)\n\t\t{\n\t\t\tconst size_t bit_index = row % 64;\n\t\t\tINNER_LOOP\n\t\t}\n\t\tresult[n / 64] &= word;\n\t}\n\n#undef INNER_LOOP\n}\n\nvoid\nvector_const_textlike_utf8(const ArrowArray *arrow, const Datum constdatum, uint64 *restrict result)\n{\n\tvector_const_like_impl(arrow, constdatum, result, UTF8_MatchText, /* should_match = */ true);\n}\n\nvoid\nvector_const_textnlike_utf8(const ArrowArray *arrow, const Datum constdatum,\n\t\t\t\t\t\t\tuint64 *restrict result)\n{\n\tvector_const_like_impl(arrow, constdatum, result, UTF8_MatchText, /* should_match = */ false);\n}\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/pred_text.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include \"compression/arrow_c_data_interface.h\"\n\nextern void vector_const_texteq(const ArrowArray *arrow, const Datum constdatum,\n\t\t\t\t\t\t\t\tuint64 *restrict result);\n\nextern void vector_const_textne(const ArrowArray *arrow, const Datum constdatum,\n\t\t\t\t\t\t\t\tuint64 *restrict result);\n\nextern void vector_const_textlike_utf8(const ArrowArray *arrow, const Datum constdatum,\n\t\t\t\t\t\t\t\t\t   uint64 *restrict result);\n\nextern void vector_const_textnlike_utf8(const ArrowArray *arrow, const Datum constdatum,\n\t\t\t\t\t\t\t\t\t\tuint64 *restrict result);\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/pred_vector_array.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n\n#include \"compression/arrow_c_data_interface.h\"\n#include \"compression/compression.h\"\n#include \"guc.h\"\n#include \"src/utils.h\"\n#include \"vector_predicates.h\"\n\n/*\n * Vectorized implementation of ScalarArrayOpExpr. Applies scalar_predicate for\n * vector and each element of array, combines the result according to \"is_or\"\n * flag. Written along the lines of ExecEvalScalarArrayOp().\n */\nvoid\nvector_array_predicate(VectorPredicate *vector_const_predicate, bool is_or,\n\t\t\t\t\t   const ArrowArray *vector, Datum array, uint64 *restrict final_result)\n{\n\tconst size_t n_rows = vector->length;\n\tconst size_t result_words = (n_rows + 63) / 64;\n\n\tuint64 *restrict array_result = final_result;\n\t/*\n\t * For OR, we need an intermediate storage to accumulate the results\n\t * from all elements.\n\t * For AND, we can apply predicate for each element to the final result.\n\t */\n\tuint64 array_result_storage[(GLOBAL_MAX_ROWS_PER_COMPRESSION + 63) / 64];\n\tif (is_or)\n\t{\n\t\tarray_result = array_result_storage;\n\t\tfor (size_t i = 0; i < result_words; i++)\n\t\t{\n\t\t\tarray_result_storage[i] = 0;\n\t\t}\n\t}\n\n\tArrayType *arr = DatumGetArrayTypeP(array);\n\n\tint16 typlen;\n\tbool typbyval;\n\tchar typalign;\n\tget_typlenbyvalalign(ARR_ELEMTYPE(arr), &typlen, &typbyval, &typalign);\n\n\tconst char *array_data = (const char *) ARR_DATA_PTR(arr);\n\tconst size_t nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));\n\tconst uint64 *array_null_bitmap = (uint64 *) ARR_NULLBITMAP(arr);\n\n\tfor (size_t array_index = 0; array_index < nitems; array_index++)\n\t{\n\t\tif (array_null_bitmap != NULL && !arrow_row_is_valid(array_null_bitmap, array_index))\n\t\t{\n\t\t\t/*\n\t\t\t * This array element is NULL. We can't avoid NULLS when evaluating\n\t\t\t * the stable functions at run time, so we have to support them.\n\t\t\t * This is a predicate, not a generic scalar array operation, so\n\t\t\t * thankfully we return a non-nullable bool.\n\t\t\t * For ANY: null | true = true, null | false = null, so this means\n\t\t\t * we can skip the null element and continue evaluation.\n\t\t\t * For ALL: null & true = null, null & false = false, so this means\n\t\t\t * that for each row the condition goes to false, and we don't have\n\t\t\t * to evaluate the next elements.\n\t\t\t */\n\t\t\tif (is_or)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfor (size_t word = 0; word < result_words; word++)\n\t\t\t{\n\t\t\t\tfinal_result[word] = 0;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tDatum constvalue = ts_fetch_att(array_data, typbyval, typlen);\n\t\tarray_data = att_addlength_pointer(array_data, typlen, array_data);\n\t\tarray_data = (const char *) att_align_nominal(array_data, typalign);\n\n\t\t/*\n\t\t * For OR, we also need an intermediate storage for predicate result\n\t\t * for each array element, since the predicates AND their result.\n\t\t *\n\t\t * For AND, we can and apply predicate for each array element to the\n\t\t * final result.\n\t\t */\n\t\tuint64 single_result_storage[(GLOBAL_MAX_ROWS_PER_COMPRESSION + 63) / 64];\n\t\tuint64 *restrict single_result;\n\t\tif (is_or)\n\t\t{\n\t\t\tsingle_result = single_result_storage;\n\t\t\tfor (size_t outer = 0; outer < result_words; outer++)\n\t\t\t{\n\t\t\t\tsingle_result[outer] = ~0ULL;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tsingle_result = array_result;\n\t\t}\n\n\t\tvector_const_predicate(vector, constvalue, single_result);\n\n\t\tif (is_or)\n\t\t{\n\t\t\tfor (size_t outer = 0; outer < result_words; outer++)\n\t\t\t{\n\t\t\t\tarray_result[outer] |= single_result[outer];\n\t\t\t}\n\t\t}\n\n\t\t/*\n\t\t * The bitmaps are small, no more than 15 qwords for our maximal\n\t\t * compressed batch size of 1000 rows, so we can check for early exit\n\t\t * after every row.\n\t\t */\n\t\tBatchQualSummary summary = get_vector_qual_summary(array_result, n_rows);\n\t\tif (summary == (is_or ? AllRowsPass : NoRowsPass))\n\t\t{\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (is_or)\n\t{\n\t\tfor (size_t outer = 0; outer < result_words; outer++)\n\t\t{\n\t\t\t/*\n\t\t\t * The tail bits corresponding to past-the-end rows when n % 64 != 0\n\t\t\t * should be already zeroed out in the final_result.\n\t\t\t */\n\t\t\tfinal_result[outer] &= array_result[outer];\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/pred_vector_const_arithmetic_all.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * Define all supported \"vector ? const\" predicates for arithmetic types.\n */\n\n/* int8 functions. */\n#define VECTOR_CTYPE int64\n#define CONST_CTYPE int64\n#define CONST_CONVERSION(X) DatumGetInt64(X)\n#define PG_PREDICATE(X)                                                                            \\\n\tF_INT8##X: case F_TIMESTAMPTZ_##X:                                                             \\\n\tcase F_TIMESTAMP_##X\n\n#include \"pred_vector_const_arithmetic_type_pair.c\"\n\n/* int84 functions. */\n#define VECTOR_CTYPE int64\n#define CONST_CTYPE int32\n#define CONST_CONVERSION(X) DatumGetInt32(X)\n#define PG_PREDICATE(X) F_INT84##X\n\n#include \"pred_vector_const_arithmetic_type_pair.c\"\n\n/* int82 functions. */\n#define VECTOR_CTYPE int64\n#define CONST_CTYPE int16\n#define CONST_CONVERSION(X) DatumGetInt16(X)\n#define PG_PREDICATE(X) F_INT82##X\n\n#include \"pred_vector_const_arithmetic_type_pair.c\"\n\n/* int48 functions. */\n#define VECTOR_CTYPE int32\n#define CONST_CTYPE int64\n#define CONST_CONVERSION(X) DatumGetInt64(X)\n#define PG_PREDICATE(X) F_INT48##X\n\n#include \"pred_vector_const_arithmetic_type_pair.c\"\n\n/* int4 functions. */\n#define VECTOR_CTYPE int32\n#define CONST_CTYPE int32\n#define CONST_CONVERSION(X) DatumGetInt32(X)\n#define PG_PREDICATE(X) F_INT4##X\n\n#include \"pred_vector_const_arithmetic_type_pair.c\"\n\n/* int42 functions. */\n#define VECTOR_CTYPE int32\n#define CONST_CTYPE int16\n#define CONST_CONVERSION(X) DatumGetInt16(X)\n#define PG_PREDICATE(X) F_INT42##X\n\n#include \"pred_vector_const_arithmetic_type_pair.c\"\n\n/* int28 functions. */\n#define VECTOR_CTYPE int16\n#define CONST_CTYPE int64\n#define CONST_CONVERSION(X) DatumGetInt64(X)\n#define PG_PREDICATE(X) F_INT28##X\n\n#include \"pred_vector_const_arithmetic_type_pair.c\"\n\n/* int24 functions. */\n#define VECTOR_CTYPE int16\n#define CONST_CTYPE int32\n#define CONST_CONVERSION(X) DatumGetInt32(X)\n#define PG_PREDICATE(X) F_INT24##X\n\n#include \"pred_vector_const_arithmetic_type_pair.c\"\n/* int2 functions. */\n#define VECTOR_CTYPE int16\n#define CONST_CTYPE int16\n#define CONST_CONVERSION(X) DatumGetInt16(X)\n#define PG_PREDICATE(X) F_INT2##X\n\n#include \"pred_vector_const_arithmetic_type_pair.c\"\n\n/* float8 functions. */\n#define VECTOR_CTYPE float8\n#define CONST_CTYPE float8\n#define CONST_CONVERSION(X) DatumGetFloat8(X)\n#define PG_PREDICATE(X) F_FLOAT8##X\n\n#include \"pred_vector_const_arithmetic_type_pair.c\"\n\n/* float84 functions. */\n#define VECTOR_CTYPE float8\n#define CONST_CTYPE float4\n#define CONST_CONVERSION(X) DatumGetFloat4(X)\n#define PG_PREDICATE(X) F_FLOAT84##X\n\n#include \"pred_vector_const_arithmetic_type_pair.c\"\n\n/* float48 functions. */\n#define VECTOR_CTYPE float4\n#define CONST_CTYPE float8\n#define CONST_CONVERSION(X) DatumGetFloat8(X)\n#define PG_PREDICATE(X) F_FLOAT48##X\n\n#include \"pred_vector_const_arithmetic_type_pair.c\"\n\n/* float4 functions. */\n#define VECTOR_CTYPE float4\n#define CONST_CTYPE float4\n#define CONST_CONVERSION(X) DatumGetFloat4(X)\n#define PG_PREDICATE(X) F_FLOAT4##X\n\n#include \"pred_vector_const_arithmetic_type_pair.c\"\n\n/* date functions. */\n#define VECTOR_CTYPE DateADT\n#define CONST_CTYPE DateADT\n#define CONST_CONVERSION(X) DatumGetDateADT(X)\n#define PG_PREDICATE(X) F_DATE_##X\n\n#include \"pred_vector_const_arithmetic_type_pair.c\"\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/pred_vector_const_arithmetic_single.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * Compute a vector-const predicate and AND it to the filter bitmap.\n * Specialized for particular arithmetic data types and predicate.\n * Marked as noinline for the ease of debugging. Inlining it shouldn't be\n * beneficial because it's a big self-contained loop.\n */\n\n#define PG_PREDICATE_HELPER(X) PG_PREDICATE(X)\n\n#define FUNCTION_NAME_HELPER(X, Y, Z) predicate_##X##_##Y##_vector_##Z##_const\n#define FUNCTION_NAME(X, Y, Z) FUNCTION_NAME_HELPER(X, Y, Z)\n\n#ifdef GENERATE_DISPATCH_TABLE\ncase PG_PREDICATE_HELPER(PREDICATE_NAME):\n\treturn FUNCTION_NAME(PREDICATE_NAME, VECTOR_CTYPE, CONST_CTYPE);\n#else\n\nstatic pg_noinline void\nFUNCTION_NAME(PREDICATE_NAME, VECTOR_CTYPE,\n\t\t\t  CONST_CTYPE)(const ArrowArray *arrow, const Datum constdatum, uint64 *restrict result)\n{\n\tconst size_t n = arrow->length;\n\n\t/* Now run the predicate itself. */\n\tconst CONST_CTYPE constvalue = CONST_CONVERSION(constdatum);\n\tconst VECTOR_CTYPE *vector = (const VECTOR_CTYPE *) arrow->buffers[1];\n\n\tfor (size_t outer = 0; outer < n / 64; outer++)\n\t{\n\t\t/* no need to check the values if the result is already invalid */\n\t\tif (result[outer] == 0)\n\t\t\tcontinue;\n\n\t\tuint64 word = 0;\n\t\tfor (size_t inner = 0; inner < 64; inner++)\n\t\t{\n\t\t\tconst bool valid = PREDICATE_EXPRESSION(vector[outer * 64 + inner], constvalue);\n\t\t\tword |= ((uint64) valid) << inner;\n\t\t}\n\t\tresult[outer] &= word;\n\t}\n\n\tif (n % 64)\n\t{\n\t\tuint64 tail_word = 0;\n\t\tfor (size_t i = (n / 64) * 64; i < n; i++)\n\t\t{\n\t\t\tconst bool valid = PREDICATE_EXPRESSION(vector[i], constvalue);\n\t\t\ttail_word |= ((uint64) valid) << (i % 64);\n\t\t}\n\t\tresult[n / 64] &= tail_word;\n\t}\n}\n\n#endif\n\n#undef PG_PREDICATE_HELPER\n\n#undef FUNCTION_NAME\n#undef FUNCTION_NAME_HELPER\n\n#undef PREDICATE_EXPRESSION\n#undef PREDICATE_NAME\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/pred_vector_const_arithmetic_type_pair.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * Vector-const predicates for one pair of arithmetic types. For NaN comparison,\n * Postgres has its own nonstandard rules different from the IEEE floats.\n */\n\n#define PREDICATE_NAME GE\n#define PREDICATE_EXPRESSION(X, Y) (isnan((double) (X)) || (!isnan((double) (Y)) && (X) >= (Y)))\n#include \"pred_vector_const_arithmetic_single.c\"\n\n#define PREDICATE_NAME LE\n#define PREDICATE_EXPRESSION(X, Y) (isnan((double) (Y)) || (!isnan((double) (X)) && (X) <= (Y)))\n#include \"pred_vector_const_arithmetic_single.c\"\n\n#define PREDICATE_NAME LT\n#define PREDICATE_EXPRESSION(X, Y) (!isnan((double) (X)) && (isnan((double) (Y)) || (X) < (Y)))\n#include \"pred_vector_const_arithmetic_single.c\"\n\n#define PREDICATE_NAME GT\n#define PREDICATE_EXPRESSION(X, Y) (!isnan((double) (Y)) && (isnan((double) (X)) || (X) > (Y)))\n#include \"pred_vector_const_arithmetic_single.c\"\n\n#define PREDICATE_NAME EQ\n#define PREDICATE_EXPRESSION(X, Y) (isnan((double) (X)) ? isnan((double) (Y)) : ((X) == (Y)))\n#include \"pred_vector_const_arithmetic_single.c\"\n\n#define PREDICATE_NAME NE\n#define PREDICATE_EXPRESSION(X, Y) (isnan((double) (X)) ? !isnan((double) (Y)) : ((X) != (Y)))\n#include \"pred_vector_const_arithmetic_single.c\"\n\n#undef VECTOR_CTYPE\n#undef CONST_CTYPE\n#undef CONST_CONVERSION\n#undef PG_PREDICATE\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/qual_pushdown.c",
    "content": "/* * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodeFuncs.h>\n#include <optimizer/optimizer.h>\n#include <optimizer/restrictinfo.h>\n#include <parser/parse_func.h>\n#include <parser/parsetree.h>\n#include <utils/builtins.h>\n#include <utils/typcache.h>\n\n#include \"columnar_scan.h\"\n#include \"compression/batch_metadata_builder.h\"\n#include \"compression/create.h\"\n#include \"compression/sparse_index_bloom1.h\"\n#include \"custom_type_cache.h\"\n#include \"guc.h\"\n#include \"ts_catalog/array_utils.h\"\n\n#include \"qual_pushdown.h\"\n\ntypedef struct QualPushdownContext\n{\n\tRelOptInfo *chunk_rel;\n\tRelOptInfo *compressed_rel;\n\tRangeTblEntry *chunk_rte;\n\tRangeTblEntry *compressed_rte;\n\tCompressionSettings *settings;\n\n\t/*\n\t * This is actually the result, not the static input context like above, but\n\t * there's no way to separate this properly using the expression tree mutator\n\t * interface.\n\t */\n\tbool can_pushdown;\n\tbool needs_recheck;\n} QualPushdownContext;\nstatic QualPushdownContext\ncopy_context(const QualPushdownContext *source)\n{\n\tQualPushdownContext copy;\n\tcopy = *source;\n\tcopy.can_pushdown = true;\n\tcopy.needs_recheck = false;\n\treturn copy;\n}\n\nstatic Node *qual_pushdown_mutator(Node *node, QualPushdownContext *context);\n\n/*\n * Result of validating an OpExpr as a potential bloom filter candidate.\n * Does NOT make decisions about which operand to use.\n */\ntypedef struct HashableEqualityInfo\n{\n\tVar *left_var;\t\t /* NULL if left is not a Var on chunk_rel */\n\tVar *right_var;\t\t /* NULL if right is not a Var on chunk_rel */\n\tExpr *left_expr;\t /* Original left operand (after unwrapping RelabelType) */\n\tExpr *right_expr;\t /* Original right operand (after unwrapping RelabelType) */\n\tOid opno;\t\t\t /* Original operator OID */\n\tbool left_hashable;\t /* Is operator in left_var's hash opfamily? */\n\tbool right_hashable; /* Is operator in right_var's hash opfamily? */\n\tbool valid;\t\t\t /* Is this a valid hashable equality? */\n} HashableEqualityInfo;\n\nstatic HashableEqualityInfo validate_hashable_equality(OpExpr *opexpr,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   QualPushdownContext *context);\nstatic Var *extract_var_for_bloom1(OpExpr *opexpr, QualPushdownContext *context, Expr **value_out,\n\t\t\t\t\t\t\t\t   Oid *op_oid_out);\n\nstatic Var *extract_var_for_composite_bloom(OpExpr *opexpr, QualPushdownContext *context,\n\t\t\t\t\t\t\t\t\t\t\tExpr **value_out, Oid *op_oid_out);\nstatic void pushdown_composite_blooms(PlannerInfo *root, QualPushdownContext *context);\n\nvoid\ncolumnar_scan_filter_pushdown(PlannerInfo *root, CompressionSettings *settings,\n\t\t\t\t\t\t\t  RelOptInfo *chunk_rel, RelOptInfo *compressed_rel, bool chunk_partial)\n{\n\tListCell *lc;\n\tList *decompress_clauses = NIL;\n\tQualPushdownContext base_context = {\n\t\t.chunk_rel = chunk_rel,\n\t\t.compressed_rel = compressed_rel,\n\t\t.chunk_rte = planner_rt_fetch(chunk_rel->relid, root),\n\t\t.compressed_rte = planner_rt_fetch(compressed_rel->relid, root),\n\t\t.settings = settings,\n\t};\n\n\t/*\n\t * Collect composite bloom candidates first.\n\t * This looks at ALL equality predicates together to find composite bloom matches\n\t * and push down the composite bloom filters.\n\t */\n\tif (ts_guc_enable_sparse_index_bloom && settings != NULL && settings->fd.index != NULL &&\n\t\tts_guc_enable_composite_bloom_indexes)\n\t{\n\t\tpushdown_composite_blooms(root, &base_context);\n\t}\n\n\tforeach (lc, chunk_rel->baserestrictinfo)\n\t{\n\t\tRestrictInfo *ri = lfirst(lc);\n\n\t\tQualPushdownContext clause_context = copy_context(&base_context);\n\t\tNode *pushed_down = qual_pushdown_mutator((Node *) ri->clause, &clause_context);\n\n\t\tif (clause_context.can_pushdown)\n\t\t{\n\t\t\t/*\n\t\t\t * We have to call eval_const_expressions after pushing down\n\t\t\t * the quals, to normalize the bool expressions. Namely, we might add an\n\t\t\t * AND boolexpr on minmax metadata columns, but the normal form is not\n\t\t\t * allowed to have nested AND boolexprs. They break some functions like\n\t\t\t * generate_bitmap_or_paths().\n\t\t\t */\n\t\t\tpushed_down = eval_const_expressions(root, pushed_down);\n\n\t\t\tif (IsA(pushed_down, BoolExpr) && castNode(BoolExpr, pushed_down)->boolop == AND_EXPR)\n\t\t\t{\n\t\t\t\t/* have to separate out and expr into different restrict infos */\n\t\t\t\tListCell *lc_and;\n\t\t\t\tBoolExpr *bool_expr = castNode(BoolExpr, pushed_down);\n\t\t\t\tforeach (lc_and, bool_expr->args)\n\t\t\t\t{\n\t\t\t\t\tcompressed_rel->baserestrictinfo =\n\t\t\t\t\t\tlappend(compressed_rel->baserestrictinfo,\n\t\t\t\t\t\t\t\tmake_simple_restrictinfo(root, lfirst(lc_and)));\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t\tcompressed_rel->baserestrictinfo =\n\t\t\t\t\tlappend(compressed_rel->baserestrictinfo,\n\t\t\t\t\t\t\tmake_simple_restrictinfo(root, (Expr *) pushed_down));\n\t\t}\n\n\t\t/*\n\t\t * We need to check the restriction clause on the decompress node if the clause can't be\n\t\t * pushed down or needs re-checking.\n\t\t */\n\t\tif (!clause_context.can_pushdown || clause_context.needs_recheck || chunk_partial)\n\t\t{\n\t\t\tdecompress_clauses = lappend(decompress_clauses, ri);\n\t\t}\n\t}\n\tchunk_rel->baserestrictinfo = decompress_clauses;\n}\n\nstatic OpExpr *\nmake_segment_meta_opexpr(QualPushdownContext *context, Oid opno, AttrNumber meta_column_attno,\n\t\t\t\t\t\t Var *uncompressed_var, Expr *compare_to_expr, StrategyNumber strategy)\n{\n\tVar *meta_var = makeVar(context->compressed_rel->relid,\n\t\t\t\t\t\t\tmeta_column_attno,\n\t\t\t\t\t\t\tuncompressed_var->vartype,\n\t\t\t\t\t\t\t-1,\n\t\t\t\t\t\t\tInvalidOid,\n\t\t\t\t\t\t\t0);\n\n\treturn (OpExpr *) make_opclause(opno,\n\t\t\t\t\t\t\t\t\tBOOLOID,\n\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\t(Expr *) meta_var,\n\t\t\t\t\t\t\t\t\tcopyObject(compare_to_expr),\n\t\t\t\t\t\t\t\t\tInvalidOid,\n\t\t\t\t\t\t\t\t\tuncompressed_var->varcollid);\n}\n\nstatic void\nexpr_fetch_minmax_metadata(QualPushdownContext *context, Expr *expr, AttrNumber *min_attno,\n\t\t\t\t\t\t   AttrNumber *max_attno)\n{\n\t*min_attno = InvalidAttrNumber;\n\t*max_attno = InvalidAttrNumber;\n\n\tif (!IsA(expr, Var))\n\t\treturn;\n\n\tVar *var = castNode(Var, expr);\n\n\t/*\n\t * Not on the chunk we expect. This doesn't really happen because we don't\n\t * push down the join quals, only the baserestrictinfo.\n\t */\n\tif ((Index) var->varno != context->chunk_rel->relid)\n\t\treturn;\n\n\t/* ignore system attributes or whole row references */\n\tif (var->varattno <= 0)\n\t\treturn;\n\n\t*min_attno = compressed_column_metadata_attno(context->settings,\n\t\t\t\t\t\t\t\t\t\t\t\t  context->chunk_rte->relid,\n\t\t\t\t\t\t\t\t\t\t\t\t  var->varattno,\n\t\t\t\t\t\t\t\t\t\t\t\t  context->compressed_rte->relid,\n\t\t\t\t\t\t\t\t\t\t\t\t  \"min\");\n\t*max_attno = compressed_column_metadata_attno(context->settings,\n\t\t\t\t\t\t\t\t\t\t\t\t  context->chunk_rte->relid,\n\t\t\t\t\t\t\t\t\t\t\t\t  var->varattno,\n\t\t\t\t\t\t\t\t\t\t\t\t  context->compressed_rte->relid,\n\t\t\t\t\t\t\t\t\t\t\t\t  \"max\");\n}\n\nstatic void *\npushdown_op_to_segment_meta_min_max(QualPushdownContext *context, OpExpr *orig_opexpr)\n{\n\t/*\n\t * This always requires rechecking the decompressed data.\n\t */\n\tcontext->needs_recheck = true;\n\n\tList *expr_args = orig_opexpr->args;\n\tAssert(list_length(expr_args) == 2);\n\tExpr *orig_leftop = linitial(expr_args);\n\tExpr *orig_rightop = lsecond(expr_args);\n\n\tif (IsA(orig_leftop, RelabelType))\n\t\torig_leftop = ((RelabelType *) orig_leftop)->arg;\n\tif (IsA(orig_rightop, RelabelType))\n\t\torig_rightop = ((RelabelType *) orig_rightop)->arg;\n\n\t/* Find the side that has var with segment meta set expr to the other side */\n\tOid op_oid = orig_opexpr->opno;\n\tAttrNumber min_attno;\n\tAttrNumber max_attno;\n\texpr_fetch_minmax_metadata(context, orig_leftop, &min_attno, &max_attno);\n\tif (min_attno == InvalidAttrNumber || max_attno == InvalidAttrNumber)\n\t{\n\t\t/* No metadata for the left operand, try to commute the operator. */\n\t\top_oid = get_commutator(op_oid);\n\t\tExpr *tmp = orig_leftop;\n\t\torig_leftop = orig_rightop;\n\t\torig_rightop = tmp;\n\n\t\texpr_fetch_minmax_metadata(context, orig_leftop, &min_attno, &max_attno);\n\t}\n\n\tif (min_attno == InvalidAttrNumber || max_attno == InvalidAttrNumber)\n\t{\n\t\t/* No metadata for either operand. */\n\t\tcontext->can_pushdown = false;\n\t\treturn orig_opexpr;\n\t}\n\n\tVar *var_with_segment_meta = castNode(Var, orig_leftop);\n\n\t/* May be able to allow non-strict operations as well.\n\t * Next steps: Think through edge cases, either allow and write tests or figure out why we must\n\t * block strict operations\n\t */\n\tif (!OidIsValid(op_oid) || !op_strict(op_oid))\n\t{\n\t\tcontext->can_pushdown = false;\n\t\treturn orig_opexpr;\n\t}\n\n\t/* If the collation to be used by the OP doesn't match the column's collation do not push down\n\t * as the materialized min/max value do not match the semantics of what we need here */\n\tOid op_collation = orig_opexpr->inputcollid;\n\tif (var_with_segment_meta->varcollid != op_collation)\n\t{\n\t\tcontext->can_pushdown = false;\n\t\treturn orig_opexpr;\n\t}\n\n\tTypeCacheEntry *tce =\n\t\tlookup_type_cache(var_with_segment_meta->vartype, TYPECACHE_BTREE_OPFAMILY);\n\n\tconst int strategy = get_op_opfamily_strategy(op_oid, tce->btree_opf);\n\tif (strategy == InvalidStrategy)\n\t{\n\t\tcontext->can_pushdown = false;\n\t\treturn orig_opexpr;\n\t}\n\n\t/*\n\t * Check if the righthand expression is safe to push down. We cannot combine\n\t * it with the original operator if there can be false negatives.\n\t */\n\tQualPushdownContext tmp_context = copy_context(context);\n\tExpr *pushed_down_rightop = (Expr *) qual_pushdown_mutator((Node *) orig_rightop, &tmp_context);\n\tif (!tmp_context.can_pushdown || tmp_context.needs_recheck)\n\t{\n\t\tcontext->can_pushdown = false;\n\t\treturn orig_opexpr;\n\t}\n\tAssert(pushed_down_rightop != NULL);\n\n\tconst Oid expr_type_id = exprType((Node *) pushed_down_rightop);\n\n\tswitch (strategy)\n\t{\n\t\tcase BTEqualStrategyNumber:\n\t\t{\n\t\t\t/* var = expr implies min < expr and max > expr */\n\t\t\tOid opno_le = get_opfamily_member(tce->btree_opf,\n\t\t\t\t\t\t\t\t\t\t\t  tce->type_id,\n\t\t\t\t\t\t\t\t\t\t\t  expr_type_id,\n\t\t\t\t\t\t\t\t\t\t\t  BTLessEqualStrategyNumber);\n\t\t\tOid opno_ge = get_opfamily_member(tce->btree_opf,\n\t\t\t\t\t\t\t\t\t\t\t  tce->type_id,\n\t\t\t\t\t\t\t\t\t\t\t  expr_type_id,\n\t\t\t\t\t\t\t\t\t\t\t  BTGreaterEqualStrategyNumber);\n\n\t\t\tif (!OidIsValid(opno_le) || !OidIsValid(opno_ge))\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * Shouldn't be possible if we managed to create the min/max\n\t\t\t\t * sparse index, but defend against catalog corruption.\n\t\t\t\t */\n\t\t\t\tcontext->can_pushdown = false;\n\t\t\t\treturn orig_opexpr;\n\t\t\t}\n\n\t\t\treturn make_andclause(\n\t\t\t\tlist_make2(make_segment_meta_opexpr(context,\n\t\t\t\t\t\t\t\t\t\t\t\t\topno_le,\n\t\t\t\t\t\t\t\t\t\t\t\t\tmin_attno,\n\t\t\t\t\t\t\t\t\t\t\t\t\tvar_with_segment_meta,\n\t\t\t\t\t\t\t\t\t\t\t\t\tpushed_down_rightop,\n\t\t\t\t\t\t\t\t\t\t\t\t\tBTLessEqualStrategyNumber),\n\t\t\t\t\t\t   make_segment_meta_opexpr(context,\n\t\t\t\t\t\t\t\t\t\t\t\t\topno_ge,\n\t\t\t\t\t\t\t\t\t\t\t\t\tmax_attno,\n\t\t\t\t\t\t\t\t\t\t\t\t\tvar_with_segment_meta,\n\t\t\t\t\t\t\t\t\t\t\t\t\tpushed_down_rightop,\n\t\t\t\t\t\t\t\t\t\t\t\t\tBTGreaterEqualStrategyNumber)));\n\t\t}\n\t\tcase BTLessStrategyNumber:\n\t\tcase BTLessEqualStrategyNumber:\n\t\t\t/* var < expr  implies min < expr */\n\t\t\t{\n\t\t\t\tOid opno =\n\t\t\t\t\tget_opfamily_member(tce->btree_opf, tce->type_id, expr_type_id, strategy);\n\n\t\t\t\tif (!OidIsValid(opno))\n\t\t\t\t{\n\t\t\t\t\t/*\n\t\t\t\t\t * Shouldn't be possible if we managed to create the min/max\n\t\t\t\t\t * sparse index, but defend against catalog corruption.\n\t\t\t\t\t */\n\t\t\t\t\tcontext->can_pushdown = false;\n\t\t\t\t\treturn orig_opexpr;\n\t\t\t\t}\n\n\t\t\t\treturn (Expr *) make_segment_meta_opexpr(context,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t opno,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t min_attno,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t var_with_segment_meta,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t pushed_down_rightop,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t strategy);\n\t\t\t}\n\n\t\tcase BTGreaterStrategyNumber:\n\t\tcase BTGreaterEqualStrategyNumber:\n\t\t\t/* var > expr  implies max > expr */\n\t\t\t{\n\t\t\t\tOid opno =\n\t\t\t\t\tget_opfamily_member(tce->btree_opf, tce->type_id, expr_type_id, strategy);\n\n\t\t\t\tif (!OidIsValid(opno))\n\t\t\t\t{\n\t\t\t\t\t/*\n\t\t\t\t\t * Shouldn't be possible if we managed to create the min/max\n\t\t\t\t\t * sparse index, but defend against catalog corruption.\n\t\t\t\t\t */\n\t\t\t\t\tcontext->can_pushdown = false;\n\t\t\t\t\treturn orig_opexpr;\n\t\t\t\t}\n\n\t\t\t\treturn (Expr *) make_segment_meta_opexpr(context,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t opno,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t max_attno,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t var_with_segment_meta,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t pushed_down_rightop,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t strategy);\n\t\t\t}\n\t\tdefault:\n\t\t\tcontext->can_pushdown = false;\n\t\t\treturn orig_opexpr;\n\t}\n}\n\nstatic void\nexpr_fetch_bloom1_metadata(QualPushdownContext *context, Expr *expr, AttrNumber *bloom1_attno)\n{\n\t*bloom1_attno = InvalidAttrNumber;\n\n\tif (!IsA(expr, Var))\n\t\treturn;\n\n\tVar *var = castNode(Var, expr);\n\n\t/*\n\t * Not on the chunk we expect. This doesn't really happen because we don't\n\t * push down the join quals, only the baserestrictinfo.\n\t */\n\tif ((Index) var->varno != context->chunk_rel->relid)\n\t\treturn;\n\n\t/* ignore system attributes or whole row references */\n\tif (var->varattno <= 0)\n\t\treturn;\n\n\t*bloom1_attno = compressed_column_metadata_attno(context->settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t context->chunk_rte->relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t var->varattno,\n\t\t\t\t\t\t\t\t\t\t\t\t\t context->compressed_rte->relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t bloom1_column_prefix);\n\n\tif (*bloom1_attno == InvalidAttrNumber && ts_guc_read_legacy_bloom1_v1)\n\t{\n\t\t/*\n\t\t * The version 1 of bloom1 indexes is disabled by default because its\n\t\t * hashing was dependent on build options leading to corrupt indexes,\n\t\t * but can be enabled manually.\n\t\t */\n\t\t*bloom1_attno = compressed_column_metadata_attno(context->settings,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t context->chunk_rte->relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t var->varattno,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t context->compressed_rte->relid,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \"bloom1\");\n\t}\n}\n\n/*\n * Validate an OpExpr as a hashable equality predicate.\n *\n * Does NOT:\n * - Decide which operand is \"column\" vs \"value\"\n * - Check bloom metadata\n * - Check collation (Caller's responsibility - depends on which Var is chosen)\n * - Commute the operator\n *\n * DOES validate:\n * - OpExpr structure\n * - Var identification on chunk_rel\n * - Hash operator validity for each Var\n *\n * Returns info about both operands with validation flags.\n * Caller decides which Var to use and validates collation.\n */\nstatic HashableEqualityInfo\nvalidate_hashable_equality(OpExpr *opexpr, QualPushdownContext *context)\n{\n\tAssert(opexpr != NULL);\n\tAssert(context != NULL);\n\n\tHashableEqualityInfo info = { 0 };\n\tinfo.valid = false;\n\tinfo.left_hashable = false;\n\tinfo.right_hashable = false;\n\n\tif (list_length(opexpr->args) != 2)\n\t\treturn info;\n\n\tExpr *left = linitial(opexpr->args);\n\tExpr *right = lsecond(opexpr->args);\n\n\t/* Unwrap RelabelType */\n\tif (IsA(left, RelabelType))\n\t\tleft = ((RelabelType *) left)->arg;\n\tif (IsA(right, RelabelType))\n\t\tright = ((RelabelType *) right)->arg;\n\n\tinfo.left_expr = left;\n\tinfo.right_expr = right;\n\tinfo.opno = opexpr->opno;\n\n\t/* Must have valid operator OID */\n\tif (!OidIsValid(info.opno))\n\t\treturn info;\n\n\t/* Identify Vars on our relation and validate hash operator for each */\n\tif (IsA(left, Var))\n\t{\n\t\tVar *left_var = (Var *) left;\n\t\tif ((Index) left_var->varno == context->chunk_rel->relid && left_var->varattno > 0)\n\t\t{\n\t\t\tinfo.left_var = left_var;\n\n\t\t\t/* Check if operator is hashable equality for this type */\n\t\t\tTypeCacheEntry *tce = lookup_type_cache(left_var->vartype, TYPECACHE_HASH_OPFAMILY);\n\t\t\tif (OidIsValid(tce->hash_opf))\n\t\t\t{\n\t\t\t\tint strategy = get_op_opfamily_strategy(info.opno, tce->hash_opf);\n\t\t\t\tif (strategy == HTEqualStrategyNumber)\n\t\t\t\t\tinfo.left_hashable = true;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (IsA(right, Var))\n\t{\n\t\tVar *right_var = (Var *) right;\n\t\tif ((Index) right_var->varno == context->chunk_rel->relid && right_var->varattno > 0)\n\t\t{\n\t\t\tinfo.right_var = right_var;\n\n\t\t\t/* Check if operator is hashable equality for this type */\n\t\t\tTypeCacheEntry *tce = lookup_type_cache(right_var->vartype, TYPECACHE_HASH_OPFAMILY);\n\t\t\tif (OidIsValid(tce->hash_opf))\n\t\t\t{\n\t\t\t\tint strategy = get_op_opfamily_strategy(info.opno, tce->hash_opf);\n\t\t\t\tif (strategy == HTEqualStrategyNumber)\n\t\t\t\t\tinfo.right_hashable = true;\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Must have at least one Var on our relation */\n\tif (info.left_var == NULL && info.right_var == NULL)\n\t\treturn info;\n\n\t/* Must have at least one Var that passes hashable equality check */\n\tif (!info.left_hashable && !info.right_hashable)\n\t\treturn info;\n\n\tinfo.valid = true;\n\treturn info;\n}\n\n/*\n * Extract Var for single-column bloom filter pushdown.\n * Uses bloom metadata presence to decide which operand to use.\n *\n * This handles cases like:\n * - bloom_col = 5              (left has bloom)\n * - 5 = bloom_col              (right has bloom, commute)\n * - bloom_col = segmentby_col  (left has bloom, caller validates segmentby)\n * - segmentby_col = bloom_col  (right has bloom, commute, caller validates)\n * - bloom_col1 = bloom_col2    (left has bloom, caller validates bloom_col2: FAILS)\n *\n * Returns the Var that has single-column bloom metadata, along with\n * the value expression and (possibly commuted) operator.\n */\nstatic Var *\nextract_var_for_bloom1(OpExpr *opexpr, QualPushdownContext *context, Expr **value_out,\n\t\t\t\t\t   Oid *op_oid_out)\n{\n\tAssert(value_out != NULL);\n\tAssert(op_oid_out != NULL);\n\tAssert(opexpr != NULL);\n\tAssert(context != NULL);\n\n\t*value_out = NULL;\n\t*op_oid_out = InvalidOid;\n\n\t/* Validate the expression */\n\tHashableEqualityInfo info = validate_hashable_equality(opexpr, context);\n\tif (!info.valid)\n\t\treturn NULL;\n\n\t/* Try to find a Var with bloom metadata that passes hash operator validation. */\n\tVar *chosen_var = NULL;\n\tExpr *value_expr_tmp = NULL;\n\tOid op_oid_tmp;\n\tAttrNumber bloom1_attno = InvalidAttrNumber;\n\n\tif (info.left_var != NULL && info.left_hashable)\n\t{\n\t\texpr_fetch_bloom1_metadata(context, (Expr *) info.left_var, &bloom1_attno);\n\t\tif (bloom1_attno != InvalidAttrNumber)\n\t\t{\n\t\t\t/* Left has bloom metadata and valid hash operator. */\n\t\t\tchosen_var = info.left_var;\n\t\t\tvalue_expr_tmp = info.right_expr;\n\t\t\top_oid_tmp = info.opno;\n\t\t}\n\t}\n\n\t/* If left didn't qualify, try right. */\n\tif (chosen_var == NULL && info.right_var != NULL && info.right_hashable)\n\t{\n\t\texpr_fetch_bloom1_metadata(context, (Expr *) info.right_var, &bloom1_attno);\n\t\tif (bloom1_attno != InvalidAttrNumber)\n\t\t{\n\t\t\t/* Right has bloom metadata and valid hash operator. Need commutation. */\n\t\t\tchosen_var = info.right_var;\n\t\t\tvalue_expr_tmp = info.left_expr;\n\t\t\top_oid_tmp = get_commutator(info.opno);\n\t\t}\n\t}\n\n\tif (chosen_var == NULL)\n\t{\n\t\t/* No Var with both bloom metadata and valid hash operator */\n\t\treturn NULL;\n\t}\n\n\t/* Validate collation for the chosen Var */\n\tOid op_collation = opexpr->inputcollid;\n\tif (chosen_var->varcollid != op_collation)\n\t{\n\t\t/* Collation mismatch - bloom filter hash won't match operator hash */\n\t\treturn NULL;\n\t}\n\n\t/* Cannot use non-deterministic collations */\n\tif (OidIsValid(op_collation) && !get_collation_isdeterministic(op_collation))\n\t\treturn NULL;\n\n\t*value_out = value_expr_tmp;\n\t*op_oid_out = op_oid_tmp;\n\treturn chosen_var;\n}\n\nstatic void *\npushdown_op_to_segment_meta_bloom1(QualPushdownContext *context, OpExpr *orig_opexpr)\n{\n\t/*\n\t * This always requires rechecking the decompressed data.\n\t */\n\tcontext->needs_recheck = true;\n\n\t/*\n\t * Use single-column bloom helper to find Var with bloom metadata.\n\t * Helper returns first Var with bloom metadata.\n\t */\n\tExpr *orig_rightop = NULL;\n\tOid op_oid;\n\tVar *var = extract_var_for_bloom1(orig_opexpr, context, &orig_rightop, &op_oid);\n\n\tif (var == NULL)\n\t{\n\t\tcontext->can_pushdown = false;\n\t\treturn orig_opexpr;\n\t}\n\n\t/* Get bloom metadata. */\n\tAttrNumber bloom1_attno = InvalidAttrNumber;\n\texpr_fetch_bloom1_metadata(context, (Expr *) var, &bloom1_attno);\n\tAssert(bloom1_attno != InvalidAttrNumber);\n\n\t/*\n\t * The hash equality operators are supposed to be strict.\n\t */\n\tAssert(op_strict(op_oid));\n\n\t/*\n\t * Check if the righthand expression is safe to push down. We cannot combine\n\t * it with the original operator if there can be false negatives.\n\t */\n\tQualPushdownContext tmp_context = copy_context(context);\n\tExpr *pushed_down_rightop = (Expr *) qual_pushdown_mutator((Node *) orig_rightop, &tmp_context);\n\tif (!tmp_context.can_pushdown || tmp_context.needs_recheck)\n\t{\n\t\tcontext->can_pushdown = false;\n\t\treturn orig_opexpr;\n\t}\n\tAssert(pushed_down_rightop != NULL);\n\n\t/*\n\t * We can have cross-type equality operator, but in this case the our hashes\n\t * or Postgres hashes for the respective types are guaranteed to have the\n\t * same result for both types, so we don't need any type conversion here.\n\t * The only special case is composite types. The right-hand constant would\n\t * have the anonymous type \"record\" and would be compared polymorphically\n\t * at runtime with the record_eq() function. However, this type doesn't have\n\t * an extended hash function. Just refuse to work with it.\n\t */\n\tconst Oid compared_type = exprType((Node *) pushed_down_rightop);\n\tif (compared_type == RECORDOID)\n\t{\n\t\tcontext->can_pushdown = false;\n\t\treturn orig_opexpr;\n\t}\n\n\t/*\n\t * var = expr implies bloom1_contains(var_bloom, expr).\n\t */\n\tVar *bloom_var = makeVar(context->compressed_rel->relid,\n\t\t\t\t\t\t\t bloom1_attno,\n\t\t\t\t\t\t\t ts_custom_type_cache_get(CUSTOM_TYPE_BLOOM1)->type_oid,\n\t\t\t\t\t\t\t -1,\n\t\t\t\t\t\t\t InvalidOid,\n\t\t\t\t\t\t\t 0);\n\n\tOid func = LookupFuncName(list_make2(makeString(\"_timescaledb_functions\"),\n\t\t\t\t\t\t\t\t\t\t makeString(\"bloom1_contains\")),\n\t\t\t\t\t\t\t  /* nargs = */ -1,\n\t\t\t\t\t\t\t  /* argtypes = */ (void *) -1,\n\t\t\t\t\t\t\t  /* missing_ok = */ false);\n\n\treturn (Expr *) makeFuncExpr(func,\n\t\t\t\t\t\t\t\t BOOLOID,\n\t\t\t\t\t\t\t\t list_make2(bloom_var, pushed_down_rightop),\n\t\t\t\t\t\t\t\t /* funccollid = */ InvalidOid,\n\t\t\t\t\t\t\t\t /* inputcollid = */ InvalidOid,\n\t\t\t\t\t\t\t\t COERCE_EXPLICIT_CALL);\n}\n\n/*\n * Try to transform x = any(array[]) into bloom1_contains_any(bloom_x, array[]).\n */\nstatic void *\npushdown_saop_bloom1(QualPushdownContext *context, ScalarArrayOpExpr *orig_saop)\n{\n\t/*\n\t * This always requires rechecking the decompressed data.\n\t */\n\tcontext->needs_recheck = true;\n\n\tif (!orig_saop->useOr)\n\t{\n\t\tcontext->can_pushdown = false;\n\t\treturn orig_saop;\n\t}\n\n\tList *expr_args = orig_saop->args;\n\tAssert(list_length(expr_args) == 2);\n\tExpr *orig_leftop = linitial(expr_args);\n\tExpr *orig_rightop = lsecond(expr_args);\n\n\tif (IsA(orig_leftop, RelabelType))\n\t\torig_leftop = ((RelabelType *) orig_leftop)->arg;\n\tif (IsA(orig_rightop, RelabelType))\n\t\torig_rightop = ((RelabelType *) orig_rightop)->arg;\n\n\t/*\n\t * For scalar array operation, we expect a var on the left side.\n\t */\n\tAttrNumber bloom1_attno = InvalidAttrNumber;\n\texpr_fetch_bloom1_metadata(context, orig_leftop, &bloom1_attno);\n\tif (bloom1_attno == InvalidAttrNumber)\n\t{\n\t\t/* No metadata for left operand. */\n\t\tcontext->can_pushdown = false;\n\t\treturn orig_saop;\n\t}\n\n\tVar *var_with_segment_meta = castNode(Var, orig_leftop);\n\n\t/*\n\t * Play it safe and don't push down if the operator collation doesn't match\n\t * the column collation.\n\t */\n\tOid op_collation = orig_saop->inputcollid;\n\tif (var_with_segment_meta->varcollid != op_collation)\n\t{\n\t\tcontext->can_pushdown = false;\n\t\treturn orig_saop;\n\t}\n\n\t/*\n\t * We cannot use bloom filters for non-deterministic collations.\n\t */\n\tif (OidIsValid(op_collation) && !get_collation_isdeterministic(op_collation))\n\t{\n\t\tcontext->can_pushdown = false;\n\t\treturn orig_saop;\n\t}\n\n\t/*\n\t * We only support hashable equality operators.\n\t */\n\tconst Oid op_oid = orig_saop->opno;\n\tTypeCacheEntry *tce =\n\t\tlookup_type_cache(var_with_segment_meta->vartype, TYPECACHE_HASH_OPFAMILY);\n\tconst int strategy = get_op_opfamily_strategy(op_oid, tce->hash_opf);\n\tif (strategy != HTEqualStrategyNumber)\n\t{\n\t\tcontext->can_pushdown = false;\n\t\treturn orig_saop;\n\t}\n\n\t/*\n\t * The hash equality operators are supposed to be strict.\n\t */\n\tAssert(op_strict(op_oid));\n\n\t/*\n\t * Check if the righthand expression is safe to push down. We cannot combine\n\t * it with the original operator if there can be false negatives.\n\t */\n\tQualPushdownContext tmp_context = copy_context(context);\n\tExpr *pushed_down_rightop = (Expr *) qual_pushdown_mutator((Node *) orig_rightop, &tmp_context);\n\tif (!tmp_context.can_pushdown || tmp_context.needs_recheck)\n\t{\n\t\tcontext->can_pushdown = false;\n\t\treturn orig_saop;\n\t}\n\tAssert(pushed_down_rightop != NULL);\n\n\t/*\n\t * var = any(array) implies bloom1_contains_any(var_bloom, array).\n\t */\n\tVar *bloom_var = makeVar(context->compressed_rel->relid,\n\t\t\t\t\t\t\t bloom1_attno,\n\t\t\t\t\t\t\t ts_custom_type_cache_get(CUSTOM_TYPE_BLOOM1)->type_oid,\n\t\t\t\t\t\t\t -1,\n\t\t\t\t\t\t\t InvalidOid,\n\t\t\t\t\t\t\t 0);\n\n\tOid func = LookupFuncName(list_make2(makeString(\"_timescaledb_functions\"),\n\t\t\t\t\t\t\t\t\t\t makeString(\"bloom1_contains_any\")),\n\t\t\t\t\t\t\t  /* nargs = */ -1,\n\t\t\t\t\t\t\t  /* argtypes = */ (void *) -1,\n\t\t\t\t\t\t\t  /* missing_ok = */ false);\n\n\treturn makeFuncExpr(func,\n\t\t\t\t\t\tBOOLOID,\n\t\t\t\t\t\tlist_make2(bloom_var, pushed_down_rightop),\n\t\t\t\t\t\t/* funccollid = */ InvalidOid,\n\t\t\t\t\t\t/* inputcollid = */ InvalidOid,\n\t\t\t\t\t\tCOERCE_EXPLICIT_CALL);\n}\n\n/*\n * Extract Var for composite bloom filter pushdown.\n * Uses segmentby membership as a heuristic for Var-to-Var cases.\n * Does NOT check bloom metadata (composite bloom is checked later by name matching).\n *\n * This handles cases like:\n * - col = 5                (obvious)\n * - 5 = col                (commute)\n * - col = segmentby_col    (prefer non-segmentby col)\n * - col1 = col2            (prefer non-segmentby, but caller validates value)\n *\n * IMPORTANT: For col1 = col2 where both are non-segmentby, returns col1 but\n * the caller (pushdown_composite_blooms) will reject col2 during value validation.\n * Composite bloom requires value expressions to be constants, params, or segmentby Vars.\n *\n * Returns a Var along with the value expression and (possibly commuted) operator.\n */\nstatic Var *\nextract_var_for_composite_bloom(OpExpr *opexpr, QualPushdownContext *context, Expr **value_out,\n\t\t\t\t\t\t\t\tOid *op_oid_out)\n{\n\tAssert(opexpr != NULL);\n\tAssert(context != NULL);\n\tAssert(value_out != NULL);\n\tAssert(op_oid_out != NULL);\n\n\t*value_out = NULL;\n\t*op_oid_out = InvalidOid;\n\n\t/* Validate the expression */\n\tHashableEqualityInfo info = validate_hashable_equality(opexpr, context);\n\tif (!info.valid)\n\t\treturn NULL;\n\n\t/* Only one side is a Var. */\n\tVar *chosen_var = NULL;\n\tExpr *value_expr_tmp = NULL;\n\tOid op_oid_tmp;\n\tbool value_is_segmentby = false;\n\n\tif (info.left_var != NULL && info.right_var == NULL)\n\t{\n\t\tchosen_var = info.left_var;\n\t\tvalue_expr_tmp = info.right_expr;\n\t\top_oid_tmp = info.opno;\n\t}\n\telse if (info.right_var != NULL && info.left_var == NULL)\n\t{\n\t\tchosen_var = info.right_var;\n\t\tvalue_expr_tmp = info.left_expr;\n\t\top_oid_tmp = get_commutator(info.opno);\n\t}\n\telse if (info.left_var != NULL && info.right_var != NULL)\n\t{\n\t\t/*\n\t\t * Both are Vars. Prefer non-segmentby Var (segmentby columns cannot\n\t\t * be in bloom filters), but also require valid hash operator.\n\t\t */\n\t\tbool left_is_segmentby = false;\n\t\tbool right_is_segmentby = false;\n\n\t\tif (context->settings && context->settings->fd.segmentby)\n\t\t{\n\t\t\tchar *left_attname =\n\t\t\t\tget_attname(context->chunk_rte->relid, info.left_var->varattno, false);\n\t\t\tleft_is_segmentby = ts_array_is_member(context->settings->fd.segmentby, left_attname);\n\n\t\t\tchar *right_attname =\n\t\t\t\tget_attname(context->chunk_rte->relid, info.right_var->varattno, false);\n\t\t\tright_is_segmentby = ts_array_is_member(context->settings->fd.segmentby, right_attname);\n\t\t}\n\n\t\t/* Try candidates in preference order: non-segmentby+hashable, then segmentby+hashable */\n\t\tif (!right_is_segmentby && info.right_hashable)\n\t\t{\n\t\t\t/* Right is non-segmentby and hashable - prefer it */\n\t\t\tchosen_var = info.right_var;\n\t\t\tvalue_expr_tmp = info.left_expr;\n\t\t\top_oid_tmp = get_commutator(info.opno);\n\t\t\tvalue_is_segmentby = left_is_segmentby;\n\t\t}\n\t\telse if (!left_is_segmentby && info.left_hashable)\n\t\t{\n\t\t\t/* Left is non-segmentby and hashable - use it */\n\t\t\tchosen_var = info.left_var;\n\t\t\tvalue_expr_tmp = info.right_expr;\n\t\t\top_oid_tmp = info.opno;\n\t\t\tvalue_is_segmentby = right_is_segmentby;\n\t\t}\n\t\telse if (info.left_hashable)\n\t\t{\n\t\t\t/* Left is hashable (may be segmentby) - use it as fallback */\n\t\t\tchosen_var = info.left_var;\n\t\t\tvalue_expr_tmp = info.right_expr;\n\t\t\top_oid_tmp = info.opno;\n\t\t\tvalue_is_segmentby = right_is_segmentby;\n\t\t}\n\t\telse if (info.right_hashable)\n\t\t{\n\t\t\t/* Right is hashable (may be segmentby) - use it as last resort */\n\t\t\tchosen_var = info.right_var;\n\t\t\tvalue_expr_tmp = info.left_expr;\n\t\t\top_oid_tmp = get_commutator(info.opno);\n\t\t\tvalue_is_segmentby = left_is_segmentby;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* Neither Var has valid hash operator */\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\t/* Validate the chosen Var and value expression */\n\tif (chosen_var != NULL)\n\t{\n\t\t/* Check collation */\n\t\tOid op_collation = opexpr->inputcollid;\n\t\tif (chosen_var->varcollid != op_collation)\n\t\t{\n\t\t\t/* Collation mismatch. */\n\t\t\treturn NULL;\n\t\t}\n\n\t\t/* Cannot use non-deterministic collations */\n\t\tif (OidIsValid(op_collation) && !get_collation_isdeterministic(op_collation))\n\t\t\treturn NULL;\n\n\t\t/*\n\t\t * Reject non-segmentby Vars in value expression.\n\t\t * For composite bloom, value expressions must be pushable (const, param, or segmentby Var).\n\t\t * Non-segmentby Vars need decompression and cannot be used in bloom checks.\n\t\t *\n\t\t * Note: value_is_segmentby is only set when both sides are Vars (both-Vars case).\n\t\t * In that case, value_expr_tmp is always a Var on chunk_rel. If value_is_segmentby\n\t\t * is false in the both-Vars case, the value Var is non-segmentby and must be rejected.\n\t\t * In the single-Var case, value_expr_tmp is not a Var on chunk_rel, so this check\n\t\t * doesn't apply (value_is_segmentby remains false but value_expr_tmp is not a Var).\n\t\t */\n\t\tif (IsA(value_expr_tmp, Var))\n\t\t{\n\t\t\tVar *value_var = (Var *) value_expr_tmp;\n\t\t\t/* Only check segmentby for Vars on our relation */\n\t\t\tif ((Index) value_var->varno == context->chunk_rel->relid && value_var->varattno > 0 &&\n\t\t\t\t!value_is_segmentby)\n\t\t\t{\n\t\t\t\t/* Value is a non-segmentby Var on our relation - cannot be pushed */\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t}\n\n\t\t*value_out = value_expr_tmp;\n\t\t*op_oid_out = op_oid_tmp;\n\t\treturn chosen_var;\n\t}\n\n\treturn NULL;\n}\n\n/*\n * Scan baserestrictinfo for composite bloom opportunities.\n * For each applicable composite bloom, generate bloom1_contains(bloom, ROW(...)).\n *\n * Scans predicates first, then parses settings only if needed.\n */\nstatic void\npushdown_composite_blooms(PlannerInfo *root, QualPushdownContext *context)\n{\n\tAssert(root != NULL);\n\tAssert(context != NULL);\n\n\t/* We need settings to generate composite blooms. */\n\tCompressionSettings *settings = context->settings;\n\tif (settings == NULL || settings->fd.index == NULL)\n\t{\n\t\treturn;\n\t}\n\n\t/* We need at least two baserestrictinfo to have a chance to push down a composite bloom filter.\n\t */\n\tif (list_length(context->chunk_rel->baserestrictinfo) < 2)\n\t{\n\t\treturn;\n\t}\n\n\tListCell *lc;\n\tBitmapset *var_attnos = NULL;\n\n\t/* Build map: chunk_attno -> value_expr for equality predicates. */\n\t/* Note that we may have more than one predicate for the same attribute\n\t * (e.g. col1 = 1 AND col1 = 2) which is a contradiction and we should\n\t * be able to detect and optimize for this, meaning that this predicate\n\t * will always be false. This is a TODO.\n\t *\n\t * For now, we will just use the last one, which will filter out some\n\t * chunks which is better than nothing.\n\t */\n\tAttrNumber max_attno = context->chunk_rel->max_attr;\n\tExpr **attno_to_value = palloc0((max_attno + 1) * sizeof(Expr *));\n\n\t/* Determine vars with equality predicates. */\n\tforeach (lc, context->chunk_rel->baserestrictinfo)\n\t{\n\t\tRestrictInfo *ri = lfirst_node(RestrictInfo, lc);\n\t\tif (!IsA(ri->clause, OpExpr))\n\t\t\tcontinue;\n\n\t\tExpr *value = NULL;\n\t\tOid op_oid = InvalidOid;\n\t\tVar *var =\n\t\t\textract_var_for_composite_bloom(castNode(OpExpr, ri->clause), context, &value, &op_oid);\n\t\tif (var != NULL && value != NULL)\n\t\t{\n\t\t\tvar_attnos = bms_add_member(var_attnos, var->varattno);\n\t\t\tattno_to_value[var->varattno] = value;\n\t\t}\n\t}\n\n\t/* Check if not enough vars with equality predicates. */\n\tif (bms_num_members(var_attnos) < 2)\n\t\treturn;\n\n\t/* Parse settings to get per-column compression settings. */\n\tSparseIndexSettings *parsed = ts_convert_to_sparse_index_settings(settings->fd.index);\n\tif (parsed == NULL)\n\t{\n\t\tbms_free(var_attnos);\n\t\tpfree(attno_to_value);\n\t\treturn;\n\t}\n\n\t/* For each sparse index object, resolve the columns to attribute numbers. */\n\tTsBmsList per_column_attnos =\n\t\tts_resolve_columns_to_attnos_from_parsed_settings(parsed, context->chunk_rte->relid);\n\n\t/* This bitmap tells which sparse index objects are candidates for composite bloom filters. */\n\tBitmapset *composite_filter_candidates_ids = NULL;\n\n\t/* Iterate over the resolved columns and check if they match the vars with equality predicates.\n\t */\n\tListCell *attno_cell = NULL;\n\tint sparse_index_obj_id = -1;\n\tforeach (attno_cell, per_column_attnos)\n\t{\n\t\tsparse_index_obj_id++;\n\t\tBitmapset *attnos = lfirst(attno_cell);\n\t\t/* Only care about sparse indices with at least 2 columns. */\n\t\tif (bms_num_members(attnos) < 2)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\t\tif (bms_is_subset(attnos, var_attnos))\n\t\t{\n\t\t\t/* This sparse index object matches the vars with equality predicates. */\n\t\t\tSparseIndexSettingsObject *obj = list_nth(parsed->objects, sparse_index_obj_id);\n\t\t\tAssert(obj != NULL);\n\t\t\tif (obj == NULL)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t/* Get the index type from the parsed object. */\n\t\t\tList *index_type =\n\t\t\t\tts_get_values_by_key_from_parsed_object(obj,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tts_sparse_index_common_keys\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t[SparseIndexKeyType] /* \"type\" */);\n\t\t\tAssert(index_type != NIL && list_length(index_type) == 1);\n\t\t\tif (index_type == NIL || list_length(index_type) != 1)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t/* Check that it is a bloom index. */\n\t\t\tconst char *index_type_str = lfirst(list_head(index_type));\n\t\t\tif (strcmp(index_type_str,\n\t\t\t\t\t   ts_sparse_index_type_names[_SparseIndexTypeEnumBloom] /* \"bloom\" */) != 0)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tAssert(list_length(ts_get_column_names_from_parsed_object(obj)) >= 2);\n\t\t\tcomposite_filter_candidates_ids =\n\t\t\t\tbms_add_member(composite_filter_candidates_ids, sparse_index_obj_id);\n\t\t}\n\t}\n\n\t/* Check if there are composite filter candidates. */\n\tif (bms_is_empty(composite_filter_candidates_ids))\n\t{\n\t\tpfree(attno_to_value);\n\t\tbms_free(var_attnos);\n\t\tts_bmslist_free(per_column_attnos);\n\t\tts_free_sparse_index_settings(parsed);\n\t\treturn;\n\t}\n\n\t/* For each composite filter candidate, build the composite bloom filter. */\n\tint candidate_filter_id = -1;\n\twhile ((candidate_filter_id =\n\t\t\t\tbms_next_member(composite_filter_candidates_ids, candidate_filter_id)) >= 0)\n\t{\n\t\tSparseIndexSettingsObject *obj = list_nth(parsed->objects, candidate_filter_id);\n\t\tAssert(obj != NULL);\n\t\t/* The column attnos generated from the parsed object is a list indexed by the object id.*/\n\t\tBitmapset *column_attnos = list_nth(per_column_attnos, candidate_filter_id);\n\t\tAssert(bms_num_members(column_attnos) >= 2);\n\n\t\t/* Iterate over the attnos for the current candidate filter an check if this is a valid\n\t\t * predicate to push down. This is a safety check and hope that the composite bloom filter\n\t\t * will also be valid to push down by induction.\n\t\t */\n\t\tList *pushed_value_exprs = NIL;\n\t\tbool all_valid = true;\n\t\tint col_attno = -1;\n\t\twhile ((col_attno = bms_next_member(column_attnos, col_attno)) >= 0)\n\t\t{\n\t\t\tExpr *value_expr = attno_to_value[col_attno];\n\t\t\tAssert(value_expr != NULL);\n\n\t\t\t/* Validate the value expression via qual_pushdown_mutator. */\n\t\t\tQualPushdownContext tmp_context = copy_context(context);\n\t\t\tExpr *pushed_value = (Expr *) qual_pushdown_mutator((Node *) value_expr, &tmp_context);\n\n\t\t\tif (!tmp_context.can_pushdown || tmp_context.needs_recheck)\n\t\t\t{\n\t\t\t\tall_valid = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tpushed_value_exprs = lappend(pushed_value_exprs, pushed_value);\n\t\t}\n\t\tif (!all_valid)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\tList *column_names = ts_get_column_names_from_parsed_object(obj);\n\t\tAssert(list_length(column_names) >= 2 &&\n\t\t\t   list_length(column_names) <= MAX_BLOOM_FILTER_COLUMNS);\n\n\t\t/* Check if this chunk has the composite bloom column */\n\t\tchar *composite_col_name =\n\t\t\tcompressed_column_metadata_name_list_v2(bloom1_column_prefix, column_names);\n\t\tAttrNumber composite_attno = get_attnum(context->compressed_rte->relid, composite_col_name);\n\t\tpfree(composite_col_name);\n\t\tif (!AttributeNumberIsValid(composite_attno))\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Build bloom1_contains(composite_bloom, ROW(...)) */\n\t\tVar *bloom_var = makeVar(context->compressed_rel->relid,\n\t\t\t\t\t\t\t\t composite_attno,\n\t\t\t\t\t\t\t\t ts_custom_type_cache_get(CUSTOM_TYPE_BLOOM1)->type_oid,\n\t\t\t\t\t\t\t\t -1,\n\t\t\t\t\t\t\t\t InvalidOid,\n\t\t\t\t\t\t\t\t 0);\n\n\t\t/* If all pushed-down values are Const, pre-hash at planning time. */\n\t\tFuncExpr *bloom_check = NULL;\n\n\t\t/* Build ROW(val1, val2, ...) expression using pushed-down values */\n\t\tRowExpr *row_expr = makeNode(RowExpr);\n\t\trow_expr->args = pushed_value_exprs;\n\t\trow_expr->row_typeid = RECORDOID;\n\t\trow_expr->row_format = COERCE_IMPLICIT_CAST;\n\t\trow_expr->colnames = NIL;\n\t\trow_expr->location = -1;\n\n\t\tOid func_oid = LookupFuncName(list_make2(makeString(\"_timescaledb_functions\"),\n\t\t\t\t\t\t\t\t\t\t\t\t makeString(\"bloom1_contains\")),\n\t\t\t\t\t\t\t\t\t  -1,\n\t\t\t\t\t\t\t\t\t  (void *) -1,\n\t\t\t\t\t\t\t\t\t  false);\n\n\t\tbloom_check = makeFuncExpr(func_oid,\n\t\t\t\t\t\t\t\t   BOOLOID,\n\t\t\t\t\t\t\t\t   list_make2(bloom_var, row_expr),\n\t\t\t\t\t\t\t\t   InvalidOid,\n\t\t\t\t\t\t\t\t   InvalidOid,\n\t\t\t\t\t\t\t\t   COERCE_EXPLICIT_CALL);\n\n\t\t/* Add to baserestrictinfo. */\n\t\tcontext->compressed_rel->baserestrictinfo =\n\t\t\tlappend(context->compressed_rel->baserestrictinfo,\n\t\t\t\t\tmake_simple_restrictinfo(root, (Expr *) bloom_check));\n\t}\n\n\t/* Cleanup */\n\tbms_free(var_attnos);\n\tbms_free(composite_filter_candidates_ids);\n\tts_free_sparse_index_settings(parsed);\n\tts_bmslist_free(per_column_attnos);\n\tpfree(attno_to_value);\n}\n\n/*\n * Deconstruct a Const of array type into a list of the array values.\n */\nstatic List *\ndeconstruct_array_const(Const *array_const)\n{\n\t/*\n\t * No way to represent that as a list (NIL is an empty array), so has to be\n\t * handled by the caller.\n\t */\n\tAssert(!array_const->constisnull);\n\n\tOid array_type = array_const->consttype;\n\tDatum array_datum = array_const->constvalue;\n\n\tOid element_type = get_element_type(array_type);\n\tAssert(OidIsValid(element_type));\n\n\tint16 typlen;\n\tbool typbyval;\n\tchar typalign;\n\tget_typlenbyvalalign(element_type, &typlen, &typbyval, &typalign);\n\n\tint nelems;\n\tDatum *elem_values;\n\tbool *elem_nulls;\n\tdeconstruct_array(DatumGetArrayTypeP(array_datum),\n\t\t\t\t\t  element_type,\n\t\t\t\t\t  typlen,\n\t\t\t\t\t  typbyval,\n\t\t\t\t\t  typalign,\n\t\t\t\t\t  &elem_values,\n\t\t\t\t\t  &elem_nulls,\n\t\t\t\t\t  &nelems);\n\n\tList *const_list = NIL;\n\tfor (int i = 0; i < nelems; i++)\n\t{\n\t\tConst *elem_const = makeConst(element_type,\n\t\t\t\t\t\t\t\t\t  array_const->consttypmod,\n\t\t\t\t\t\t\t\t\t  array_const->constcollid,\n\t\t\t\t\t\t\t\t\t  typlen,\n\t\t\t\t\t\t\t\t\t  elem_values[i],\n\t\t\t\t\t\t\t\t\t  elem_nulls[i],\n\t\t\t\t\t\t\t\t\t  typbyval);\n\t\tconst_list = lappend(const_list, elem_const);\n\t}\n\n\treturn const_list;\n}\n\n/*\n * Push down the scalar array operation by transforming it into a series of\n * OR/AND clauses.\n */\nstatic Expr *\npushdown_saop_boolexpr(QualPushdownContext *context, ScalarArrayOpExpr *saop)\n{\n\tvoid *scalar_arg = linitial(saop->args);\n\tvoid *array_arg = list_nth(saop->args, 1);\n\tList *array_elements;\n\tif (IsA(array_arg, Const) && !castNode(Const, array_arg)->constisnull)\n\t{\n\t\tarray_elements = deconstruct_array_const(castNode(Const, array_arg));\n\t}\n\telse if (IsA(array_arg, ArrayExpr))\n\t{\n\t\tarray_elements = castNode(ArrayExpr, array_arg)->elements;\n\t}\n\telse\n\t{\n\t\t/*\n\t\t * We can encounter an array-type Param here, and maybe something else.\n\t\t * This function has to deconstruct the array into elements now, so\n\t\t * these types of array argument are not suitable.\n\t\t */\n\t\tcontext->can_pushdown = false;\n\t\treturn (Expr *) saop;\n\t}\n\n\t/*\n\t * This will be the operation on the scalar value and an individual array\n\t * element.\n\t */\n\tOpExpr *opexpr = makeNode(OpExpr);\n\topexpr->opno = saop->opno;\n\topexpr->opfuncid = saop->opfuncid;\n\topexpr->opresulttype = BOOLOID;\n\topexpr->inputcollid = saop->inputcollid;\n\n\t/*\n\t * Try to apply the above operation for each array element.\n\t */\n\tList *pushed_down_ops = NIL;\n\tListCell *lc;\n\tforeach (lc, array_elements)\n\t{\n\t\topexpr->args = list_make2(scalar_arg, lfirst(lc));\n\n\t\tQualPushdownContext tmp_context = copy_context(context);\n\t\tvoid *transformed = qual_pushdown_mutator((Node *) opexpr, &tmp_context);\n\n\t\t/*\n\t\t * If the scalar array operation uses AND, it's correct and useful to\n\t\t * push down the check only for some array elements.\n\t\t *\n\t\t * For OR, we must be able to push down the checks for every element.\n\t\t */\n\t\tif (!tmp_context.can_pushdown)\n\t\t{\n\t\t\tif (saop->useOr)\n\t\t\t{\n\t\t\t\tcontext->can_pushdown = false;\n\t\t\t\treturn (Expr *) saop;\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * If we pushed down the clause only partially, we have to mark that\n\t\t\t * it needs rechecking, even when the individual parts don't.\n\t\t\t */\n\t\t\tcontext->needs_recheck = true;\n\t\t\tcontinue;\n\t\t}\n\t\tcontext->needs_recheck |= tmp_context.needs_recheck;\n\t\tpushed_down_ops = lappend(pushed_down_ops, transformed);\n\t}\n\n\t/*\n\t * We can have no pushed down clauses if:\n\t * 1) we had an AND scalar array operation, but failed to push down every\n\t * individual clause.\n\t * 2) we had an empty array argument, apparently it's not simplified by\n\t * Postgres' eval_const_expressions().\n\t */\n\tif (pushed_down_ops == NIL)\n\t{\n\t\tcontext->can_pushdown = false;\n\t\treturn (Expr *) saop;\n\t}\n\n\tif (list_length(pushed_down_ops) == 1)\n\t\treturn linitial(pushed_down_ops);\n\n\tif (saop->useOr)\n\t{\n\t\treturn make_orclause(pushed_down_ops);\n\t}\n\telse\n\t{\n\t\treturn make_andclause(pushed_down_ops);\n\t}\n}\n\nstatic bool\ncontain_volatile_functions_checker(Oid func_id, void *context)\n{\n\treturn (func_volatile(func_id) == PROVOLATILE_VOLATILE);\n}\n\n/*\n * Push down the given expression node.\n *\n * This is used as a mutator for expression_tree_mutator().\n *\n * We return the original node if we cannot push it down, to be consistent with\n * the expression_tree_mutator behavior. The caller must check\n * context.can_pushdown.\n */\nstatic Node *\nqual_pushdown_mutator(Node *orig_node, QualPushdownContext *context)\n{\n\tif (orig_node == NULL)\n\t{\n\t\t/*\n\t\t * An expression node can have a NULL field and the mutator will be\n\t\t * still called for it, so we have to handle this.\n\t\t */\n\t\treturn NULL;\n\t}\n\n\tif (!context->can_pushdown)\n\t{\n\t\t/*\n\t\t * Stop early if we already know we can't push down this filter.\n\t\t */\n\t\treturn orig_node;\n\t}\n\n\tif (check_functions_in_node(orig_node,\n\t\t\t\t\t\t\t\tcontain_volatile_functions_checker,\n\t\t\t\t\t\t\t\t/* context = */ NULL))\n\t{\n\t\t/* pushdown is not safe for volatile expressions */\n\t\tcontext->can_pushdown = false;\n\t\treturn orig_node;\n\t}\n\n\tswitch (nodeTag(orig_node))\n\t{\n\t\tcase T_Var:\n\t\t{\n\t\t\tVar *var = castNode(Var, orig_node);\n\t\t\tAssert((Index) var->varno == context->chunk_rel->relid);\n\n\t\t\tif (var->varattno <= 0)\n\t\t\t{\n\t\t\t\t/* Can't do this for system columns such as whole-row var. */\n\t\t\t\tcontext->can_pushdown = false;\n\t\t\t\treturn orig_node;\n\t\t\t}\n\n\t\t\tchar *attname = get_attname(context->chunk_rte->relid, var->varattno, false);\n\t\t\t/* we can only push down quals for segmentby columns */\n\t\t\tif (!ts_array_is_member(context->settings->fd.segmentby, attname))\n\t\t\t{\n\t\t\t\tcontext->can_pushdown = false;\n\t\t\t\treturn orig_node;\n\t\t\t}\n\n\t\t\tvar = copyObject(var);\n\t\t\tvar->varno = context->compressed_rel->relid;\n\t\t\tvar->varattno = get_attnum(context->compressed_rte->relid, attname);\n\n\t\t\treturn (Node *) var;\n\t\t}\n\t\tcase T_OpExpr:\n\t\t{\n\t\t\tOpExpr *opexpr = (OpExpr *) orig_node;\n\n\t\t\t/*\n\t\t\t * It might be possible to push down the OpExpr as is, if it\n\t\t\t * references only the segmentby columns. Check this case first.\n\t\t\t *\n\t\t\t * Note that we can't push down the entire operator if we pushed\n\t\t\t * down both sides inexactly, i.e. they require recheck. This means\n\t\t\t * we can have false positives there, and combining false positives\n\t\t\t * with the original operator could lead to false negatives, which\n\t\t\t * would be a bug. Consider for example (x = 1) = (y = 1) in case\n\t\t\t * where both sides are false, but there's a false posistive for the\n\t\t\t * pushed down version of the left side but not the right side.\n\t\t\t */\n\t\t\tQualPushdownContext tmp_context = copy_context(context);\n\t\t\tvoid *pushed_down =\n\t\t\t\texpression_tree_mutator((Node *) orig_node, qual_pushdown_mutator, &tmp_context);\n\t\t\tif (tmp_context.can_pushdown && !tmp_context.needs_recheck)\n\t\t\t{\n\t\t\t\treturn pushed_down;\n\t\t\t}\n\n\t\t\tif (opexpr->opresulttype != BOOLOID)\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * The following pushdown options only support operators that\n\t\t\t\t * return bool.\n\t\t\t\t */\n\t\t\t\tcontext->can_pushdown = false;\n\t\t\t\treturn orig_node;\n\t\t\t}\n\n\t\t\tif (list_length(opexpr->args) != 2)\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * The following pushdown options only support operators with\n\t\t\t\t * two operands.\n\t\t\t\t */\n\t\t\t\tcontext->can_pushdown = false;\n\t\t\t\treturn orig_node;\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * Try bloom1 sparse index.\n\t\t\t */\n\t\t\tif (ts_guc_enable_sparse_index_bloom)\n\t\t\t{\n\t\t\t\ttmp_context = copy_context(context);\n\t\t\t\tpushed_down = pushdown_op_to_segment_meta_bloom1(&tmp_context, opexpr);\n\t\t\t\tif (tmp_context.can_pushdown)\n\t\t\t\t{\n\t\t\t\t\tcontext->needs_recheck |= tmp_context.needs_recheck;\n\t\t\t\t\treturn pushed_down;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * Try minmax sparse index.\n\t\t\t */\n\t\t\ttmp_context = copy_context(context);\n\t\t\tpushed_down = pushdown_op_to_segment_meta_min_max(&tmp_context, opexpr);\n\t\t\tif (tmp_context.can_pushdown)\n\t\t\t{\n\t\t\t\tcontext->needs_recheck |= tmp_context.needs_recheck;\n\t\t\t\treturn pushed_down;\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * No other options to push down the OpExpr.\n\t\t\t */\n\t\t\tcontext->can_pushdown = false;\n\t\t\treturn orig_node;\n\t\t}\n\t\tcase T_ScalarArrayOpExpr:\n\t\t{\n\t\t\t/*\n\t\t\t * It can be possible to push down the scalar array operation as is,\n\t\t\t * if it references only the segmentby columns. Check this case\n\t\t\t * first.\n\t\t\t *\n\t\t\t * See the comment for OpExpr about needs_recheck handling.\n\t\t\t */\n\t\t\tQualPushdownContext tmp_context = copy_context(context);\n\t\t\tvoid *pushed_down =\n\t\t\t\texpression_tree_mutator((Node *) orig_node, qual_pushdown_mutator, &tmp_context);\n\t\t\tif (tmp_context.can_pushdown && !tmp_context.needs_recheck)\n\t\t\t{\n\t\t\t\treturn pushed_down;\n\t\t\t}\n\n\t\t\tScalarArrayOpExpr *saop = castNode(ScalarArrayOpExpr, orig_node);\n\n\t\t\t/*\n\t\t\t * Try to transform x = any(array[]) into\n\t\t\t * bloom1_contains_any(bloom_x, array[]).\n\t\t\t */\n\t\t\tif (ts_guc_enable_sparse_index_bloom)\n\t\t\t{\n\t\t\t\ttmp_context = *context;\n\t\t\t\tpushed_down = pushdown_saop_bloom1(&tmp_context, saop);\n\t\t\t\tif (tmp_context.can_pushdown)\n\t\t\t\t{\n\t\t\t\t\tcontext->needs_recheck |= tmp_context.needs_recheck;\n\t\t\t\t\treturn pushed_down;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * Generic code for scalar array operation pushdown that transforms\n\t\t\t * them into a series of OR/AND clauses.\n\t\t\t */\n\t\t\ttmp_context = *context;\n\t\t\tpushed_down = pushdown_saop_boolexpr(&tmp_context, saop);\n\t\t\tif (tmp_context.can_pushdown)\n\t\t\t{\n\t\t\t\tcontext->needs_recheck |= tmp_context.needs_recheck;\n\t\t\t\treturn pushed_down;\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * No other ways to push it down, so consider it failed.\n\t\t\t */\n\t\t\tcontext->can_pushdown = false;\n\t\t\treturn orig_node;\n\t\t}\n\t\tcase T_BoolExpr:\n\t\t{\n\t\t\tBoolExpr *orig_boolexpr = castNode(BoolExpr, orig_node);\n\t\t\tList *pushed_down_args = NIL;\n\t\t\tListCell *lc;\n\t\t\tforeach (lc, orig_boolexpr->args)\n\t\t\t{\n\t\t\t\tQualPushdownContext tmp_context = *context;\n\t\t\t\tvoid *pushed_down = qual_pushdown_mutator(lfirst(lc), &tmp_context);\n\n\t\t\t\t/*\n\t\t\t\t * If the bool operation uses AND, it's correct and useful to\n\t\t\t\t * push down only some arguments.\n\t\t\t\t *\n\t\t\t\t * For OR, we must be able to push down every argument.\n\t\t\t\t */\n\t\t\t\tif (!tmp_context.can_pushdown)\n\t\t\t\t{\n\t\t\t\t\tif (orig_boolexpr->boolop != AND_EXPR)\n\t\t\t\t\t{\n\t\t\t\t\t\tcontext->can_pushdown = false;\n\t\t\t\t\t\treturn orig_node;\n\t\t\t\t\t}\n\n\t\t\t\t\t/*\n\t\t\t\t\t * If we pushed down the expression only partially, it means\n\t\t\t\t\t * we'll have to recheck it even if individual parts don't\n\t\t\t\t\t * require rechecking.\n\t\t\t\t\t */\n\t\t\t\t\tcontext->needs_recheck = true;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tcontext->needs_recheck |= tmp_context.needs_recheck;\n\t\t\t\tpushed_down_args = lappend(pushed_down_args, pushed_down);\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * We might have no pushed down arguments if we had an AND bool\n\t\t\t * operation, but failed to push down every individual argument.\n\t\t\t */\n\t\t\tif (pushed_down_args == NIL)\n\t\t\t{\n\t\t\t\tcontext->can_pushdown = false;\n\t\t\t\treturn orig_node;\n\t\t\t}\n\n\t\t\tBoolExpr *boolexpr_copy = makeNode(BoolExpr);\n\t\t\t*boolexpr_copy = *orig_boolexpr;\n\t\t\tboolexpr_copy->args = pushed_down_args;\n\t\t\treturn (Node *) boolexpr_copy;\n\t\t}\n\n\t\t/*\n\t\t * These nodes do not influence the pushdown by themselves, so we\n\t\t * recurse.\n\t\t */\n\t\tcase T_FuncExpr:\n\t\tcase T_CoerceViaIO:\n\t\tcase T_RelabelType:\n\t\tcase T_List:\n\t\tcase T_Const:\n\t\tcase T_NullTest:\n\t\tcase T_Param:\n\t\tcase T_SQLValueFunction:\n\t\tcase T_CaseExpr:\n\t\tcase T_CaseWhen:\n\t\tcase T_ArrayExpr:\n\t\t{\n\t\t\tNode *pushed_down =\n\t\t\t\texpression_tree_mutator((Node *) orig_node, qual_pushdown_mutator, context);\n\t\t\treturn pushed_down;\n\t\t}\n\n\t\t/*\n\t\t * We don't know how to work with other nodes.\n\t\t */\n\t\tdefault:\n\t\t\tcontext->can_pushdown = false;\n\t\t\treturn orig_node;\n\t}\n}\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/qual_pushdown.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\nvoid columnar_scan_filter_pushdown(PlannerInfo *root, CompressionSettings *settings,\n\t\t\t\t\t\t\t\t   RelOptInfo *chunk_rel, RelOptInfo *compressed_rel,\n\t\t\t\t\t\t\t\t   bool chunk_partial);\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/vector_dict.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n#include \"compression/arrow_c_data_interface.h\"\n\n/*\n * When we have a dictionary-encoded Arrow Array, and have run a predicate on\n * the dictionary, this function is used to translate the dictionary predicate\n * result to the final predicate result.\n */\nstatic void\ntranslate_bitmap_from_dictionary(const ArrowArray *arrow, const uint64 *dict_result,\n\t\t\t\t\t\t\t\t uint64 *restrict final_result)\n{\n\tAssert(arrow->dictionary != NULL);\n\n\tconst size_t n = arrow->length;\n\tconst int16 *indices = (int16 *) arrow->buffers[1];\n\tfor (size_t outer = 0; outer < n / 64; outer++)\n\t{\n\t\tuint64 word = 0;\n\t\tfor (size_t inner = 0; inner < 64; inner++)\n\t\t{\n\t\t\tconst size_t row = (outer * 64) + inner;\n\t\t\tconst size_t bit_index = inner;\n#define INNER_LOOP                                                                                 \\\n\tconst int16 index = indices[row];                                                              \\\n\tconst bool valid = arrow_row_is_valid(dict_result, index);                                     \\\n\tword |= ((uint64) valid) << bit_index;\n\n\t\t\tINNER_LOOP\n\t\t}\n\t\tfinal_result[outer] &= word;\n\t}\n\n\tif (n % 64)\n\t{\n\t\tuint64 word = 0;\n\t\tfor (size_t row = (n / 64) * 64; row < n; row++)\n\t\t{\n\t\t\tconst size_t bit_index = row % 64;\n\n\t\t\tINNER_LOOP\n\t\t}\n\t\tfinal_result[n / 64] &= word;\n\t}\n#undef INNER_LOOP\n}\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/vector_predicates.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * Functions for working with vectorized predicates.\n */\n\n#include <postgres.h>\n\n#include <mb/pg_wchar.h>\n#include <utils/date.h>\n#include <utils/fmgroids.h>\n#include <utils/uuid.h>\n\n#include \"compression/arrow_c_data_interface.h\"\n\n#include \"vector_predicates.h\"\n\n#include \"compat/compat.h\"\n#include \"compression/compression.h\"\n#include \"debug_assert.h\"\n\n/*\n * We include all implementations of vector-const predicates here. No separate\n * declarations for them to reduce the amount of macro template magic.\n */\n#include \"pred_vector_const_arithmetic_all.c\"\n\n#include \"pred_text.h\"\n\n/*\n * Look up the vectorized implementation for a Postgres predicate, specified by\n * its Oid in pg_proc. Note that this Oid is different from the opcode.\n */\nVectorPredicate *\nget_vector_const_predicate(Oid pg_predicate)\n{\n\tswitch (pg_predicate)\n\t{\n#define GENERATE_DISPATCH_TABLE\n#include \"pred_vector_const_arithmetic_all.c\"\n#undef GENERATE_DISPATCH_TABLE\n\n\t\tcase F_TEXTEQ:\n\t\t\treturn vector_const_texteq;\n\n\t\tcase F_TEXTNE:\n\t\t\treturn vector_const_textne;\n\n\t\tcase F_BOOLEQ:\n\t\t\treturn vector_booleq;\n\n\t\tcase F_UUID_EQ:\n\t\t\treturn vector_uuideq;\n\n\t\tcase F_UUID_NE:\n\t\t\treturn vector_uuidne;\n\n\t\tdefault:\n\t\t\t/*\n\t\t\t * More checks below, this branch is to placate the static analyzers.\n\t\t\t */\n\t\t\tbreak;\n\t}\n\n\tif (GetDatabaseEncoding() == PG_UTF8)\n\t{\n\t\t/* We have some simple LIKE vectorization for case-sensitive UTF8. */\n\t\tswitch (pg_predicate)\n\t\t{\n\t\t\tcase F_TEXTLIKE:\n\t\t\t\treturn vector_const_textlike_utf8;\n\t\t\tcase F_TEXTNLIKE:\n\t\t\t\treturn vector_const_textnlike_utf8;\n\t\t\tdefault:\n\t\t\t\t/*\n\t\t\t\t * This branch is to placate the static analyzers.\n\t\t\t\t */\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\nvoid\nvector_nulltest(const ArrowArray *arrow, int test_type, uint64 *restrict result)\n{\n\tconst bool should_be_null = test_type == IS_NULL;\n\n\tconst uint16 bitmap_words = (arrow->length + 63) / 64;\n\tconst uint64 *validity = (const uint64 *) arrow->buffers[0];\n\tfor (uint16 i = 0; i < bitmap_words; i++)\n\t{\n\t\tconst uint64 validity_word = validity != NULL ? validity[i] : ~0ULL;\n\t\tif (should_be_null)\n\t\t{\n\t\t\tresult[i] &= ~validity_word;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tresult[i] &= validity_word;\n\t\t}\n\t}\n}\n\nvoid\nvector_booleq(const ArrowArray *arrow, Datum arg, uint64 *restrict result)\n{\n\tbool check_val = DatumGetBool(arg);\n\tif (check_val)\n\t{\n\t\tvector_booleantest(arrow, IS_TRUE, result);\n\t}\n\telse\n\t{\n\t\tvector_booleantest(arrow, IS_FALSE, result);\n\t}\n}\n\ntypedef union UUIDBuffer\n{\n\tuint64 components[2];\n\tpg_uuid_t uuid;\n#ifdef HAVE_INT128\n\tint128 i128;\n#endif\n} UUIDBuffer;\n\n#define VECTOR_CTYPE UUIDBuffer\n#define CONST_CTYPE UUIDBuffer\n#define CONST_CONVERSION(X) *((UUIDBuffer *) DatumGetPointer(X))\n#define PREDICATE_NAME NE\n#ifdef HAVE_INT128\n#define PREDICATE_EXPRESSION(X, Y) (((X).i128) != ((Y).i128))\n#else\n#define PREDICATE_EXPRESSION(X, Y)                                                                 \\\n\t((X.components[0]) != (Y.components[0]) || (X.components[1]) != (Y.components[1]))\n#endif\n#include \"pred_vector_const_arithmetic_single.c\"\n\nvoid\nvector_uuidne(const ArrowArray *arrow, Datum arg, uint64 *restrict result)\n{\n\tpg_uuid_t *uuid = DatumGetUUIDP(arg);\n\t/*\n\t * No assumptions are being made about the alignment of the argument UUID,\n\t * so we copy the values to a local variable. This is because uuid is defined as typalign 'c'.\n\t */\n\tUUIDBuffer arg_values;\n\tmemcpy(&arg_values.uuid, uuid, sizeof(pg_uuid_t));\n\tpredicate_NE_UUIDBuffer_vector_UUIDBuffer_const(arrow, PointerGetDatum(&arg_values), result);\n}\n\n#undef VECTOR_CTYPE\n#undef CONST_CTYPE\n#undef CONST_CONVERSION\n#undef PG_PREDICATE\n\n#define VECTOR_CTYPE UUIDBuffer\n#define CONST_CTYPE UUIDBuffer\n#define CONST_CONVERSION(X) *((UUIDBuffer *) DatumGetPointer(X))\n#define PREDICATE_NAME EQ\n#ifdef HAVE_INT128\n#define PREDICATE_EXPRESSION(X, Y) (((X).i128) == ((Y).i128))\n#else\n#define PREDICATE_EXPRESSION(X, Y)                                                                 \\\n\t((X.components[0]) == (Y.components[0]) && (X.components[1]) == (Y.components[1]))\n#endif\n#include \"pred_vector_const_arithmetic_single.c\"\n\nvoid\nvector_uuideq(const ArrowArray *arrow, Datum arg, uint64 *restrict result)\n{\n\tpg_uuid_t *uuid = DatumGetUUIDP(arg);\n\t/*\n\t * No assumptions are being made about the alignment of the argument UUID,\n\t * so we copy the values to a local variable. This is because uuid is defined as typalign 'c'.\n\t */\n\tUUIDBuffer arg_values;\n\tmemcpy(&arg_values.uuid, uuid, sizeof(pg_uuid_t));\n\tpredicate_EQ_UUIDBuffer_vector_UUIDBuffer_const(arrow, PointerGetDatum(&arg_values), result);\n}\n\n#undef VECTOR_CTYPE\n#undef CONST_CTYPE\n#undef CONST_CONVERSION\n#undef PG_PREDICATE\n\nvoid\nvector_booleantest(const ArrowArray *arrow, int test_type, uint64 *restrict result)\n{\n\tconst uint16 bitmap_words = (arrow->length + 63) / 64;\n\tconst uint64 *restrict validity = (const uint64 *) arrow->buffers[0];\n\tconst uint64 *restrict values = (const uint64 *) arrow->buffers[1];\n\n\tswitch (test_type)\n\t{\n\t\tcase IS_TRUE:\n\t\t{\n\t\t\tif (validity)\n\t\t\t{\n\t\t\t\tfor (uint16 i = 0; i < bitmap_words; i++)\n\t\t\t\t{\n\t\t\t\t\tresult[i] &= validity[i] & values[i];\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tfor (uint16 i = 0; i < bitmap_words; i++)\n\t\t\t\t{\n\t\t\t\t\tresult[i] &= values[i];\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase IS_NOT_TRUE:\n\t\t{\n\t\t\tif (validity)\n\t\t\t{\n\t\t\t\tfor (uint16 i = 0; i < bitmap_words; i++)\n\t\t\t\t{\n\t\t\t\t\tresult[i] &= (~validity[i] | ~values[i]);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tfor (uint16 i = 0; i < bitmap_words; i++)\n\t\t\t\t{\n\t\t\t\t\tresult[i] &= ~values[i];\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase IS_FALSE:\n\t\t{\n\t\t\tif (validity)\n\t\t\t{\n\t\t\t\tfor (uint16 i = 0; i < bitmap_words; i++)\n\t\t\t\t{\n\t\t\t\t\tresult[i] &= validity[i] & ~values[i];\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tfor (uint16 i = 0; i < bitmap_words; i++)\n\t\t\t\t{\n\t\t\t\t\tresult[i] &= ~values[i];\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase IS_NOT_FALSE:\n\t\t{\n\t\t\tif (validity)\n\t\t\t{\n\t\t\t\tfor (uint16 i = 0; i < bitmap_words; i++)\n\t\t\t\t{\n\t\t\t\t\tresult[i] &= (~validity[i] | values[i]);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tfor (uint16 i = 0; i < bitmap_words; i++)\n\t\t\t\t{\n\t\t\t\t\tresult[i] &= values[i];\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase IS_UNKNOWN:\n\t\t{\n\t\t\tif (validity)\n\t\t\t{\n\t\t\t\tfor (uint16 i = 0; i < bitmap_words; i++)\n\t\t\t\t{\n\t\t\t\t\tresult[i] &= ~validity[i];\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t/* No validity, so all rows are valid and all result rows are filtered out. */\n\t\t\t\tmemset(result, 0, bitmap_words * sizeof(uint64));\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase IS_NOT_UNKNOWN:\n\t\t{\n\t\t\tif (validity)\n\t\t\t{\n\t\t\t\tfor (uint16 i = 0; i < bitmap_words; i++)\n\t\t\t\t{\n\t\t\t\t\tresult[i] &= validity[i];\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tAssert(false);\n\t\t\tbreak;\n\t}\n}\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/vector_predicates.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * Functions for working with vectorized predicates.\n */\n#pragma once\n\ntypedef void(VectorPredicate)(const ArrowArray *, Datum, uint64 *restrict);\n\nVectorPredicate *get_vector_const_predicate(Oid pg_predicate);\n\nvoid vector_array_predicate(VectorPredicate *vector_const_predicate, bool is_or,\n\t\t\t\t\t\t\tconst ArrowArray *vector, Datum array, uint64 *restrict final_result);\n\nvoid vector_nulltest(const ArrowArray *arrow, int test_type, uint64 *restrict result);\n\n/* this implements the vectorized BooleanTest, where NULLs are handled in a special way:\n * for example IS_NOT_TRUE(NULL) is true and IS_NOT_FALSE(NULL) is true, plus there are\n * NULL test types, like IS_UNKNOWN and IS_NOT_UNKNOWN.\n */\nvoid vector_booleantest(const ArrowArray *arrow, int test_type, uint64 *restrict result);\nvoid vector_booleq(const ArrowArray *arrow, Datum arg, uint64 *restrict result);\nvoid vector_uuideq(const ArrowArray *arrow, Datum arg, uint64 *restrict result);\nvoid vector_uuidne(const ArrowArray *arrow, Datum arg, uint64 *restrict result);\n\ntypedef enum BatchQualSummary\n{\n\tAllRowsPass,\n\tNoRowsPass,\n\tSomeRowsPass\n} BatchQualSummary;\n\nstatic pg_attribute_always_inline BatchQualSummary\nget_vector_qual_summary(const uint64 *qual_result, size_t n_rows)\n{\n\tbool any_rows_pass = false;\n\tbool all_rows_pass = true;\n\tfor (size_t i = 0; i < n_rows / 64; i++)\n\t{\n\t\tany_rows_pass = any_rows_pass || (qual_result[i] != 0);\n\t\tall_rows_pass = all_rows_pass && (~qual_result[i] == 0);\n\t}\n\n\tif (n_rows % 64 != 0)\n\t{\n\t\tconst uint64 last_word_mask = ~0ULL >> (64 - n_rows % 64);\n\t\tany_rows_pass |= (qual_result[n_rows / 64] & last_word_mask) != 0;\n\t\tall_rows_pass &= ((~qual_result[n_rows / 64]) & last_word_mask) == 0;\n\t}\n\n\tAssert(!(all_rows_pass && !any_rows_pass));\n\n\tif (!any_rows_pass)\n\t{\n\t\treturn NoRowsPass;\n\t}\n\n\tif (all_rows_pass)\n\t{\n\t\treturn AllRowsPass;\n\t}\n\n\treturn SomeRowsPass;\n}\n"
  },
  {
    "path": "tsl/src/nodes/columnar_scan/vector_quals.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <executor/tuptable.h>\n#include <nodes/primnodes.h>\n\n#include \"compression/arrow_c_data_interface.h\"\n#include \"vector_predicates.h\"\n\n/*\n * VectorQualInfo provides planner time information for extracting\n * vectorizable quals from regular quals.\n */\ntypedef struct VectorQualInfo\n{\n\t/*\n\t * The range-table index of the relation to compute vectorized quals\n\t * for.\n\t */\n\tIndex rti;\n\n\tbool reverse;\n\n\t/*\n\t * Arrays indexed by uncompressed attno indicating whether an\n\t * attribute/column is a vectorizable type and/or a segmentby attribute.\n\t *\n\t * Note: array lengths are maxattno + 1.\n\t */\n\tbool *vector_attrs;\n\tbool *segmentby_attrs;\n\n\t/* Max attribute number found in arrays above */\n\tAttrNumber maxattno;\n} VectorQualInfo;\n\n/*\n * VectorQualState keeps the necessary state needed for the computation of\n * vectorized filters in scan nodes.\n */\ntypedef struct VectorQualState\n{\n\tList *vectorized_quals_constified;\n\tuint16 num_results;\n\tuint64 *vector_qual_result;\n\tMemoryContext per_vector_mcxt;\n\tTupleTableSlot *slot;\n\n\t/*\n\t * Interface function to be provided by scan node.\n\t *\n\t * Given a (compressed) tuple/slot, and a column reference (Var), get the\n\t * corresponding arrow array.\n\t *\n\t * Scan-node specific context data can be provided by wrapping this struct\n\t * in a larger one.\n\t */\n\tconst ArrowArray *(*get_arrow_array)(struct VectorQualState *vqstate, Expr *expr,\n\t\t\t\t\t\t\t\t\t\t bool *is_default_value);\n} VectorQualState;\n\nextern Node *vector_qual_make(Node *qual, const VectorQualInfo *vqinfo);\nextern BatchQualSummary vector_qual_compute(VectorQualState *vqstate);\nextern ArrowArray *make_single_value_arrow(Oid pgtype, Datum datum, bool isnull);\n"
  },
  {
    "path": "tsl/src/nodes/gapfill/CMakeLists.txt",
    "content": "set(SOURCES\n    ${CMAKE_CURRENT_SOURCE_DIR}/gapfill_functions.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/gapfill_plan.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/gapfill_exec.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/locf.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/interpolate.c)\ntarget_sources(${TSL_LIBRARY_NAME} PRIVATE ${SOURCES})\n"
  },
  {
    "path": "tsl/src/nodes/gapfill/README.md",
    "content": "# Gap filling\n\nThis module implements first level support for gap fill queries, including\nsupport for LOCF (last observation carried forward) and interpolation, without\nrequiring to join against `generate_series`. This makes it easier to join\ntimeseries with different or irregular sampling intervals.\n\n## Design\n\nThis introduces a new gapfill customscan node that is inserted above the\naggregation node of a query. The node will inject tuples for time intervals\nwithout data. The node requires data to be sorted by time, but it will inject\nsort nodes in the plan to ensure data is sorted correctly if the query order\ndoes not match the required order.\n\nThe time_bucket_gapfill functions only serves to trigger injecting the gapfill\ncustomscan node in the planner all the tuple injecting happens in the gapfill\nnode and time_bucket_gapfill just calls plain time_bucket.\n\nThe locf and interpolate function calls serve as markers in the plan to\ntrigger locf or interpolate behaviour. In the targetlist of the gapfill node\nthose functions will be toplevel function calls.\n\nThe gapfill state transitions are described in gapfill_internal.h\n\n## Usage\n\nGapfill query\n```\nSELECT\n  time_bucket_gapfill(1,time,0,6) AS time,\n  min(value) AS value\nFROM (values (0,1),(5,6)) v(time,value)\nGROUP BY 1 ORDER BY 1;\n```\n\nGapfill query with LOCF\n```\nSELECT\n  time_bucket_gapfill(1,time,0,6) AS time,\n  locf(min(value)) AS value\nFROM (values (0,1),(5,6)) v(time,value)\nGROUP BY 1 ORDER BY 1;\n```\n\nGapfill query with interpolation\n```\nSELECT\n  time_bucket_gapfill(1,time,0,6) AS time,\n  interpolate(min(value)) AS value\nFROM (values (0,1),(5,6)) v(time,value)\nGROUP BY 1 ORDER BY 1;\n```\n\n"
  },
  {
    "path": "tsl/src/nodes/gapfill/gapfill.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <nodes/pathnodes.h>\n#include <nodes/primnodes.h>\n\n#define GAPFILL_FUNCTION \"time_bucket_gapfill\"\n#define GAPFILL_LOCF_FUNCTION \"locf\"\n#define GAPFILL_INTERPOLATE_FUNCTION \"interpolate\"\n\n/*\n * Indices into CustomScan->custom_private for GapFill node.\n */\ntypedef enum GapfillPrivateIndex\n{\n\tGFP_GapfillFunc = 0, /* FuncExpr: time_bucket_gapfill call */\n\tGFP_GroupClause = 1, /* List: parse->groupClause */\n\tGFP_JoinTree = 2,\t /* FromExpr: parse->jointree */\n\tGFP_Args = 3,\t\t /* List: gapfill function arguments */\n\tGFP_Count\n} GapfillPrivateIndex;\n\nvoid plan_add_gapfill(PlannerInfo *root, RelOptInfo *group_rel);\nvoid gapfill_adjust_window_targetlist(PlannerInfo *root, RelOptInfo *input_rel,\n\t\t\t\t\t\t\t\t\t  RelOptInfo *output_rel);\n\ntypedef struct GapFillPath\n{\n\tCustomPath cpath;\n\tFuncExpr *func; /* time_bucket_gapfill function call */\n} GapFillPath;\n"
  },
  {
    "path": "tsl/src/nodes/gapfill/gapfill_exec.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <access/attnum.h>\n#include <access/htup_details.h>\n#include <c.h>\n#include <catalog/pg_cast.h>\n#include <catalog/pg_collation.h>\n#include <catalog/pg_type.h>\n#include <miscadmin.h>\n#include <nodes/extensible.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodeFuncs.h>\n#include <nodes/primnodes.h>\n#include <optimizer/clauses.h>\n#include <optimizer/optimizer.h>\n#include <utils/builtins.h>\n#include <utils/date.h>\n#include <utils/datum.h>\n#include <utils/lsyscache.h>\n#include <utils/memutils.h>\n#include <utils/syscache.h>\n#include <utils/timestamp.h>\n#include <utils/typcache.h>\n\n#include <compat/compat.h>\n#include \"gapfill.h\"\n#include \"gapfill_internal.h\"\n#include \"interpolate.h\"\n#include \"locf.h\"\n#include \"time_bucket.h\"\n#include <annotations.h>\n\ntypedef enum GapFillBoundary\n{\n\tGAPFILL_START,\n\tGAPFILL_END,\n} GapFillBoundary;\n\ntypedef union GapFillColumnStateUnion\n{\n\tGapFillColumnState *base;\n\tGapFillGroupColumnState *group;\n\tGapFillInterpolateColumnState *interpolate;\n\tGapFillLocfColumnState *locf;\n} GapFillColumnStateUnion;\n\n#define foreach_column(column, index, state)                                                       \\\n\tAssert((state)->ncolumns > 0);                                                                 \\\n\tfor ((index) = 0, (column) = (state)->columns[index];                                          \\\n\t\t (index) < (state)->ncolumns && ((column) = (state)->columns[index], true);                \\\n\t\t (index)++)\n\nstatic void gapfill_begin(CustomScanState *node, EState *estate, int eflags);\nstatic void gapfill_end(CustomScanState *node);\nstatic void gapfill_rescan(CustomScanState *node);\nstatic TupleTableSlot *gapfill_exec(CustomScanState *node);\n\nstatic void gapfill_state_reset_group(GapFillState *state, TupleTableSlot *slot);\nstatic TupleTableSlot *gapfill_state_gaptuple_create(GapFillState *state, int64 time);\nstatic bool gapfill_state_is_new_group(GapFillState *state, TupleTableSlot *slot);\nstatic void gapfill_state_set_next(GapFillState *state, TupleTableSlot *subslot);\nstatic TupleTableSlot *gapfill_state_return_subplan_slot(GapFillState *state);\nstatic TupleTableSlot *gapfill_fetch_next_tuple(GapFillState *state);\nstatic void gapfill_state_initialize_columns(GapFillState *state, List *exec_tlist);\nstatic GapFillColumnState *gapfill_column_state_create(GapFillColumnType ctype, Oid typeid);\nstatic bool gapfill_is_group_column(GapFillState *state, TargetEntry *tle);\nstatic TargetEntry *gapfill_get_fixed_agg_expr_column(GapFillState *state, TargetEntry *tle);\n\nstatic CustomExecMethods gapfill_state_methods = {\n\t.BeginCustomScan = gapfill_begin,\n\t.ExecCustomScan = gapfill_exec,\n\t.EndCustomScan = gapfill_end,\n\t.ReScanCustomScan = gapfill_rescan,\n};\n\n/*\n * convert Datum to int64 according to type\n * internally we store all times as int64 in the\n * same format postgres does\n */\nint64\ngapfill_datum_get_internal(Datum value, Oid type)\n{\n\tswitch (type)\n\t{\n\t\tcase INT2OID:\n\t\t\treturn DatumGetInt16(value);\n\t\tcase DATEOID:\n\t\tcase INT4OID:\n\t\t\treturn DatumGetInt32(value);\n\t\tcase TIMESTAMPOID:\n\t\tcase TIMESTAMPTZOID:\n\t\tcase INT8OID:\n\t\t\treturn DatumGetInt64(value);\n\t\tdefault:\n\n\t\t\t/*\n\t\t\t * should never happen since time_bucket_gapfill is not defined\n\t\t\t * for other datatypes\n\t\t\t */\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"unsupported datatype for time_bucket_gapfill: %s\",\n\t\t\t\t\t\t\tformat_type_be(type))));\n\t\t\tpg_unreachable();\n\t\t\tbreak;\n\t}\n}\n\n/*\n * convert int64 to Datum according to type\n * internally we store all times as int64 in the\n * same format postgres does\n */\nstatic inline Datum\ngapfill_internal_get_datum(int64 value, Oid type)\n{\n\tswitch (type)\n\t{\n\t\tcase INT2OID:\n\t\t\treturn Int16GetDatum(value);\n\t\tcase DATEOID:\n\t\tcase INT4OID:\n\t\t\treturn Int32GetDatum(value);\n\t\tcase TIMESTAMPOID:\n\t\tcase TIMESTAMPTZOID:\n\t\tcase INT8OID:\n\t\t\treturn Int64GetDatum(value);\n\t\tdefault:\n\n\t\t\t/*\n\t\t\t * should never happen since time_bucket_gapfill is not defined\n\t\t\t * for other datatypes\n\t\t\t */\n\t\t\tAssert(false);\n\t\t\treturn Int64GetDatum(0);\n\t}\n}\n\nstatic Expr *\nget_start_arg(GapFillState *state)\n{\n\tif (!state->have_timezone)\n\t\treturn lthird(state->args);\n\telse\n\t\treturn lfourth(state->args);\n}\n\nstatic Expr *\nget_finish_arg(GapFillState *state)\n{\n\tif (!state->have_timezone)\n\t\treturn lfourth(state->args);\n\telse\n\t\treturn lfifth(state->args);\n}\n\nstatic Expr *\nget_timezone_arg(GapFillState *state)\n{\n\tAssert(state->have_timezone);\n\treturn lthird(state->args);\n}\n\nstatic inline int64\ngapfill_period_get_internal(Oid timetype, Oid argtype, Datum arg, Interval **interval)\n{\n\tswitch (timetype)\n\t{\n\t\tcase DATEOID:\n\t\tcase TIMESTAMPOID:\n\t\tcase TIMESTAMPTZOID:\n\t\t\tAssert(INTERVALOID == argtype);\n\t\t\tInterval *interval_arg = DatumGetIntervalP(arg);\n\t\t\tif (interval_arg->time < 0 || interval_arg->day < 0 || interval_arg->month < 0 ||\n\t\t\t\tinterval_arg->time + interval_arg->day + interval_arg->month == 0)\n\t\t\t{\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t\t errmsg(\"invalid time_bucket_gapfill argument: bucket_width must be \"\n\t\t\t\t\t\t\t\t\"greater than 0\")));\n\t\t\t}\n\n\t\t\t*interval = interval_arg;\n\t\t\treturn 0;\n\n\t\t\tbreak;\n\t\tcase INT2OID:\n\t\t\tAssert(INT2OID == argtype);\n\t\t\treturn DatumGetInt16(arg);\n\t\tcase INT4OID:\n\t\t\tAssert(INT4OID == argtype);\n\t\t\treturn DatumGetInt32(arg);\n\t\tcase INT8OID:\n\t\t\tAssert(INT8OID == argtype);\n\t\t\treturn DatumGetInt64(arg);\n\t\tdefault:\n\n\t\t\t/*\n\t\t\t * should never happen since time_bucket_gapfill is not defined\n\t\t\t * for other datatypes\n\t\t\t */\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"unsupported datatype for time_bucket_gapfill: %s\",\n\t\t\t\t\t\t\tformat_type_be(timetype))));\n\t\t\tpg_unreachable();\n\t\t\tbreak;\n\t}\n}\n\n/*\n * Create a GapFill node from this plan. This is the full execution\n * state that replaces the plan node as the plan moves from planning to\n * execution.\n */\nNode *\ngapfill_state_create(CustomScan *cscan)\n{\n\tGapFillState *state = (GapFillState *) newNode(sizeof(GapFillState), T_CustomScanState);\n\n\tstate->csstate.methods = &gapfill_state_methods;\n\tstate->subplan = linitial(cscan->custom_plans);\n\tstate->args = list_nth(cscan->custom_private, GFP_Args);\n\tstate->have_timezone = list_length(state->args) == 5;\n\n\treturn (Node *) state;\n}\n\nstatic bool\nis_const_null(Expr *expr)\n{\n\treturn IsA(expr, Const) && castNode(Const, expr)->constisnull;\n}\n\n/*\n * lookup cast func oid in pg_cast\n *\n * throws an error if no cast can be found\n */\nstatic Oid\nget_cast_func(Oid source, Oid target)\n{\n\tOid result = InvalidOid;\n\tHeapTuple casttup;\n\n\tcasttup = SearchSysCache2(CASTSOURCETARGET, ObjectIdGetDatum(source), ObjectIdGetDatum(target));\n\tif (HeapTupleIsValid(casttup))\n\t{\n\t\tForm_pg_cast castform = (Form_pg_cast) GETSTRUCT(casttup);\n\n\t\tresult = castform->castfunc;\n\t\tReleaseSysCache(casttup);\n\t}\n\n\tif (!OidIsValid(result))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"could not find cast from %s to %s\",\n\t\t\t\t\t\tformat_type_be(source),\n\t\t\t\t\t\tformat_type_be(target))));\n\n\treturn result;\n}\n\n/*\n * returns true if v1 and v2 reference the same object\n */\nstatic bool\nvar_equal(Var *v1, Var *v2)\n{\n\treturn v1->varno == v2->varno && v1->varattno == v2->varattno && v1->vartype == v2->vartype;\n}\n\nstatic bool\nis_simple_expr_walker(Node *node, void *context)\n{\n\tif (node == NULL)\n\t\treturn false;\n\n\t/*\n\t * since expression_tree_walker does early exit on true\n\t * logic is reverted and return value of true means expression\n\t * is not simple, this is reverted in parent\n\t */\n\tswitch (nodeTag(node))\n\t{\n\t\t\t/*\n\t\t\t * whitelist expression types we deem safe to execute in a\n\t\t\t * separate expression context\n\t\t\t */\n\t\tcase T_Const:\n\t\tcase T_FuncExpr:\n\t\tcase T_NamedArgExpr:\n\t\tcase T_OpExpr:\n\t\tcase T_DistinctExpr:\n\t\tcase T_NullIfExpr:\n\t\tcase T_ScalarArrayOpExpr:\n\t\tcase T_BoolExpr:\n\t\tcase T_CoerceViaIO:\n\t\tcase T_CaseExpr:\n\t\tcase T_CaseWhen:\n\t\t\tbreak;\n\t\tcase T_Param:\n\t\t\tif (castNode(Param, node)->paramkind != PARAM_EXTERN)\n\t\t\t\treturn true;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\treturn true;\n\t}\n\treturn expression_tree_walker(node, is_simple_expr_walker, context);\n}\n\n/*\n * check if expression is simple expression and contains only simple\n * subexpressions\n */\nstatic bool\nis_simple_expr(Expr *node)\n{\n\t/*\n\t * since expression_tree_walker does early exit on true and we use that to\n\t * skip processing on first non-simple expression we invert return value\n\t * from expression_tree_walker here\n\t */\n\treturn !is_simple_expr_walker((Node *) node, NULL);\n}\n\n/*\n * align a value with the bucket boundary\n * even though we use int64 as our internal representation we cannot call\n * ts_int64_bucket here because int variants of time_bucket align differently\n * then non-int variants because the bucket start is on monday for the latter\n */\nstatic int64\nalign_with_time_bucket(GapFillState *state, Expr *expr)\n{\n\tCustomScan *cscan = castNode(CustomScan, state->csstate.ss.ps.plan);\n\tFuncExpr *time_bucket = copyObject(list_nth(cscan->custom_private, GFP_GapfillFunc));\n\tDatum value;\n\tbool isnull;\n\n\tif (!is_simple_expr(expr))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\n\t\t\t\t\t \"invalid time_bucket_gapfill argument: start must be a simple expression\")));\n\n\tif (state->have_timezone)\n\t{\n\t\tif (IsA(get_timezone_arg(state), Const) &&\n\t\t\tcastNode(Const, get_timezone_arg(state))->constisnull)\n\t\t{\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"invalid time_bucket_gapfill argument: timezone cannot be NULL\")));\n\t\t}\n\n\t\ttime_bucket->args =\n\t\t\tlist_make3(linitial(time_bucket->args), expr, lthird(time_bucket->args));\n\t}\n\telse\n\t{\n\t\ttime_bucket->args = list_make2(linitial(time_bucket->args), expr);\n\t}\n\tvalue = gapfill_exec_expr(state, state->scanslot, (Expr *) time_bucket, &isnull);\n\n\t/* start expression must not evaluate to NULL */\n\tif (isnull)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid time_bucket_gapfill argument: start cannot be NULL\"),\n\t\t\t\t errhint(\"Specify start and finish as arguments or in the WHERE clause.\")));\n\n\treturn gapfill_datum_get_internal(value, state->gapfill_typid);\n}\n\nstatic int64\nget_boundary_expr_value(GapFillState *state, GapFillBoundary boundary, Expr *expr)\n{\n\tDatum arg_value;\n\tbool isnull;\n\n\t/*\n\t * add an explicit cast here if types do not match\n\t */\n\tif (exprType((Node *) expr) != state->gapfill_typid)\n\t{\n\t\tOid cast_oid = get_cast_func(exprType((Node *) expr), state->gapfill_typid);\n\n\t\texpr = (Expr *) makeFuncExpr(cast_oid,\n\t\t\t\t\t\t\t\t\t state->gapfill_typid,\n\t\t\t\t\t\t\t\t\t list_make1(expr),\n\t\t\t\t\t\t\t\t\t InvalidOid,\n\t\t\t\t\t\t\t\t\t InvalidOid,\n\t\t\t\t\t\t\t\t\t 0);\n\t}\n\n\targ_value = gapfill_exec_expr(state, state->scanslot, expr, &isnull);\n\n\tif (isnull)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid time_bucket_gapfill argument: %s cannot be NULL\",\n\t\t\t\t\t\tboundary == GAPFILL_START ? \"start\" : \"finish\"),\n\t\t\t\t errhint(\"Specify start and finish as arguments or in the WHERE clause.\")));\n\n\treturn gapfill_datum_get_internal(arg_value, state->gapfill_typid);\n}\n\ntypedef struct CollectBoundaryContext\n{\n\tList *quals;\n\tVar *ts_var;\n} CollectBoundaryContext;\n\n/*\n * expression references our gapfill time column and could be\n * a boundary expression, more thorough check is in\n * infer_gapfill_boundary\n */\nstatic bool\nis_boundary_expr(Node *node, CollectBoundaryContext *context)\n{\n\tOpExpr *op;\n\tNode *left, *right;\n\n\tif (!IsA(node, OpExpr))\n\t\treturn false;\n\n\top = castNode(OpExpr, node);\n\n\tif (op->args->length != 2)\n\t\treturn false;\n\n\tleft = linitial(op->args);\n\tright = llast(op->args);\n\n\t/* Var OP Var is not useful here because we are not yet at a point\n\t * where we could evaluate them */\n\tif (IsA(left, Var) && IsA(right, Var))\n\t\treturn false;\n\n\tif (IsA(left, Var) && var_equal(castNode(Var, left), context->ts_var))\n\t\treturn true;\n\n\tif (IsA(right, Var) && var_equal(castNode(Var, right), context->ts_var))\n\t\treturn true;\n\n\treturn false;\n}\n\nstatic bool\ncollect_boundary_walker(Node *node, CollectBoundaryContext *context)\n{\n\tNode *quals = NULL;\n\n\tif (node == NULL)\n\t\treturn false;\n\n\tif (IsA(node, FromExpr))\n\t{\n\t\tquals = castNode(FromExpr, node)->quals;\n\t}\n\telse if (IsA(node, JoinExpr))\n\t{\n\t\tJoinExpr *j = castNode(JoinExpr, node);\n\n\t\t/* don't descend into outer join */\n\t\tif (IS_OUTER_JOIN(j->jointype))\n\t\t\treturn false;\n\n\t\tquals = j->quals;\n\t}\n\n\tif (quals)\n\t{\n\t\tListCell *lc;\n\n\t\tforeach (lc, castNode(List, quals))\n\t\t{\n\t\t\tif (is_boundary_expr(lfirst(lc), context))\n\t\t\t\tcontext->quals = lappend(context->quals, lfirst(lc));\n\t\t}\n\t}\n\n\treturn expression_tree_walker(node, collect_boundary_walker, context);\n}\n\n/*\n * traverse jointree to look for expressions referencing\n * the time column of our gapfill call\n */\nstatic List *\ncollect_boundary_expressions(Node *node, Var *ts_var)\n{\n\tCollectBoundaryContext context = { .quals = NIL, .ts_var = ts_var };\n\n\tcollect_boundary_walker(node, &context);\n\n\treturn context.quals;\n}\n\nstatic int64\ninfer_gapfill_boundary(GapFillState *state, GapFillBoundary boundary)\n{\n\tCustomScan *cscan = castNode(CustomScan, state->csstate.ss.ps.plan);\n\tFuncExpr *func = list_nth(cscan->custom_private, GFP_GapfillFunc);\n\tFromExpr *jt = list_nth(cscan->custom_private, GFP_JoinTree);\n\tListCell *lc;\n\tVar *ts_var;\n\tTypeCacheEntry *tce = lookup_type_cache(state->gapfill_typid, TYPECACHE_BTREE_OPFAMILY);\n\tint strategy;\n\tOid lefttype, righttype;\n\tList *quals;\n\n\tint64 boundary_value = 0;\n\tbool boundary_found = false;\n\n\t/*\n\t * if the second argument to time_bucket_gapfill is not a column reference\n\t * we cannot match WHERE clause to the time column\n\t */\n\tif (!IsA(lsecond(func->args), Var))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid time_bucket_gapfill argument: ts needs to refer to a single \"\n\t\t\t\t\t\t\"column if no start or finish is supplied\"),\n\t\t\t\t errhint(\"Specify start and finish as arguments or in the WHERE clause.\")));\n\n\tts_var = castNode(Var, lsecond(func->args));\n\n\tquals = collect_boundary_expressions((Node *) jt, ts_var);\n\n\tforeach (lc, quals)\n\t{\n\t\tOpExpr *opexpr = lfirst_node(OpExpr, lc);\n\t\tVar *var;\n\t\tExpr *expr;\n\t\tOid op;\n\t\tint64 value;\n\n\t\tif (IsA(linitial(opexpr->args), Var))\n\t\t{\n\t\t\tvar = linitial(opexpr->args);\n\t\t\texpr = lsecond(opexpr->args);\n\t\t\top = opexpr->opno;\n\t\t}\n\t\telse if (IsA(lsecond(opexpr->args), Var))\n\t\t{\n\t\t\tvar = lsecond(opexpr->args);\n\t\t\texpr = linitial(opexpr->args);\n\t\t\top = get_commutator(opexpr->opno);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* collect_boundary_expressions has filtered those out already */\n\t\t\tAssert(false);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!op_in_opfamily(op, tce->btree_opf))\n\t\t\tcontinue;\n\n\t\t/*\n\t\t * only allow simple expressions because Params have not been set up\n\t\t * at this stage and Vars will not work either because we execute in\n\t\t * separate execution context\n\t\t */\n\t\tif (!is_simple_expr(expr) || !var_equal(ts_var, var))\n\t\t\tcontinue;\n\n\t\tget_op_opfamily_properties(op, tce->btree_opf, false, &strategy, &lefttype, &righttype);\n\n\t\tif (boundary == GAPFILL_START && strategy != BTGreaterStrategyNumber &&\n\t\t\tstrategy != BTGreaterEqualStrategyNumber)\n\t\t\tcontinue;\n\t\tif (boundary == GAPFILL_END && strategy != BTLessStrategyNumber &&\n\t\t\tstrategy != BTLessEqualStrategyNumber)\n\t\t\tcontinue;\n\n\t\tvalue = get_boundary_expr_value(state, boundary, expr);\n\n\t\t/*\n\t\t * if the boundary expression operator does not match the operator\n\t\t * used by the gapfill node we adjust the value by 1 here\n\t\t *\n\t\t * the operators for the gapfill node are >= for start and < for end\n\t\t * column > value becomes start >= value + 1 column <= value becomes\n\t\t * end < value + 1\n\t\t */\n\t\tif (strategy == BTGreaterStrategyNumber || strategy == BTLessEqualStrategyNumber)\n\t\t\tvalue += 1;\n\n\t\tif (!boundary_found)\n\t\t{\n\t\t\tboundary_found = true;\n\t\t\tboundary_value = value;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (boundary == GAPFILL_START)\n\t\t\t\tboundary_value = Max(boundary_value, value);\n\t\t\telse\n\t\t\t\tboundary_value = Min(boundary_value, value);\n\t\t}\n\t}\n\n\tif (boundary_found)\n\t\treturn boundary_value;\n\n\tereport(ERROR,\n\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t errmsg(\"missing time_bucket_gapfill argument: could not infer %s from WHERE clause\",\n\t\t\t\t\tboundary == GAPFILL_START ? \"start\" : \"finish\"),\n\t\t\t errhint(\"Specify start and finish as arguments or in the WHERE clause.\")));\n\tpg_unreachable();\n}\n\nstatic Const *\nmake_const_value_for_gapfill_internal(Oid typid, int64 value)\n{\n\tTypeCacheEntry *tce = lookup_type_cache(typid, 0);\n\tDatum d = gapfill_internal_get_datum(value, typid);\n\n\treturn makeConst(typid, -1, InvalidOid, tce->typlen, d, false, tce->typbyval);\n}\n\nstatic void\ngapfill_advance_timestamp(GapFillState *state)\n{\n\tDatum next;\n\n\tswitch (state->gapfill_typid)\n\t{\n\t\tcase DATEOID:\n\t\t\tnext = DirectFunctionCall2(date_pl_interval,\n\t\t\t\t\t\t\t\t\t   DateADTGetDatum(state->gapfill_start),\n\t\t\t\t\t\t\t\t\t   IntervalPGetDatum(state->next_offset));\n\t\t\tnext = DirectFunctionCall1(timestamp_date, next);\n\t\t\tstate->next_timestamp = DatumGetDateADT(next);\n\t\t\tbreak;\n\t\tcase TIMESTAMPOID:\n\t\t\tnext = DirectFunctionCall2(timestamp_pl_interval,\n\t\t\t\t\t\t\t\t\t   TimestampGetDatum(state->gapfill_start),\n\t\t\t\t\t\t\t\t\t   IntervalPGetDatum(state->next_offset));\n\t\t\tstate->next_timestamp = DatumGetTimestamp(next);\n\t\t\tbreak;\n\t\tcase TIMESTAMPTZOID:\n\t\t\t/*\n\t\t\t * To be consistent with time_bucket we do UTC bucketing unless\n\t\t\t * a different timezone got explicitly passed to the function\n\t\t\t * and we are bucketing by non-fixed intervals.\n\t\t\t */\n\t\t\tif (state->have_timezone &&\n\t\t\t\t(state->next_offset->day != 0 || state->next_offset->month != 0))\n\t\t\t{\n\t\t\t\tbool isnull;\n\t\t\t\t/* TODO: optimize by constifying and caching the datum if possible */\n\t\t\t\tDatum tzname =\n\t\t\t\t\tgapfill_exec_expr(state, state->scanslot, get_timezone_arg(state), &isnull);\n\t\t\t\tAssert(!isnull);\n\n\t\t\t\t/* Convert to local timestamp */\n\t\t\t\tnext = DirectFunctionCall2(timestamptz_zone,\n\t\t\t\t\t\t\t\t\t\t   tzname,\n\t\t\t\t\t\t\t\t\t\t   TimestampTzGetDatum(state->gapfill_start));\n\n\t\t\t\t/* Add interval */\n\t\t\t\tnext = DirectFunctionCall2(timestamp_pl_interval,\n\t\t\t\t\t\t\t\t\t\t   next,\n\t\t\t\t\t\t\t\t\t\t   IntervalPGetDatum(state->next_offset));\n\n\t\t\t\t/* Convert back to specified timezone */\n\t\t\t\tnext = DirectFunctionCall2(timestamp_zone, tzname, next);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tnext = DirectFunctionCall2(timestamp_pl_interval,\n\t\t\t\t\t\t\t\t\t\t   TimestampTzGetDatum(state->gapfill_start),\n\t\t\t\t\t\t\t\t\t\t   IntervalPGetDatum(state->next_offset));\n\t\t\t}\n\t\t\tstate->next_timestamp = DatumGetTimestampTz(next);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tstate->next_timestamp += state->gapfill_period;\n\t\t\tbreak;\n\t}\n\t/* Advance the interval offset if necessary */\n\tif (state->gapfill_interval)\n\t{\n\t\tDatum tspan = DirectFunctionCall2(interval_pl,\n\t\t\t\t\t\t\t\t\t\t  IntervalPGetDatum(state->gapfill_interval),\n\t\t\t\t\t\t\t\t\t\t  IntervalPGetDatum(state->next_offset));\n\t\tstate->next_offset = DatumGetIntervalP(tspan);\n\t}\n}\n\n/*\n * Initialize the scan state\n */\nstatic void\ngapfill_begin(CustomScanState *node, EState *estate, int eflags)\n{\n\tGapFillState *state = (GapFillState *) node;\n\tCustomScan *cscan = castNode(CustomScan, state->csstate.ss.ps.plan);\n\n\t/*\n\t * this is the time_bucket_gapfill call from the plan which is used to\n\t * extract arguments and to align gapfill_start\n\t */\n\tFuncExpr *func = list_nth(cscan->custom_private, GFP_GapfillFunc);\n\tTupleDesc tupledesc = state->csstate.ss.ps.ps_ResultTupleSlot->tts_tupleDescriptor;\n\tList *targetlist = copyObject(state->csstate.ss.ps.plan->targetlist);\n\tbool isnull;\n\tDatum arg_value;\n\n\tstate->gapfill_typid = func->funcresulttype;\n\tstate->state = FETCHED_NONE;\n\tstate->subslot = MakeSingleTupleTableSlot(tupledesc, &TTSOpsVirtual);\n\tstate->scanslot = MakeSingleTupleTableSlot(tupledesc, &TTSOpsVirtual);\n\n\t/* bucket_width */\n\tif (!is_simple_expr(linitial(state->args)))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid time_bucket_gapfill argument: bucket_width must be a simple \"\n\t\t\t\t\t\t\"expression\")));\n\n\targ_value = gapfill_exec_expr(state, NULL, linitial(state->args), &isnull);\n\tif (isnull)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid time_bucket_gapfill argument: bucket_width cannot be NULL\")));\n\n\tstate->gapfill_period = gapfill_period_get_internal(func->funcresulttype,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\texprType(linitial(state->args)),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\targ_value,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t&state->gapfill_interval);\n\n\t/*\n\t * this would error when trying to align start and stop to bucket_width as well below\n\t * but checking this explicitly here will make a nicer error message\n\t */\n\tif (state->gapfill_period <= 0 && !state->gapfill_interval)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\n\t\t\t\t\t \"invalid time_bucket_gapfill argument: bucket_width must be greater than 0\")));\n\n\t/*\n\t * check if gapfill start was left out so we have to infer from WHERE\n\t * clause\n\t */\n\tif (is_const_null(get_start_arg(state)))\n\t{\n\t\tint64 start = infer_gapfill_boundary(state, GAPFILL_START);\n\t\tConst *expr = make_const_value_for_gapfill_internal(state->gapfill_typid, start);\n\n\t\tstate->gapfill_start = align_with_time_bucket(state, (Expr *) expr);\n\t}\n\telse\n\t{\n\t\t/*\n\t\t * pass gapfill start through time_bucket so it is aligned with bucket\n\t\t * start\n\t\t */\n\t\tstate->gapfill_start = align_with_time_bucket(state, get_start_arg(state));\n\t}\n\tstate->next_timestamp = state->gapfill_start;\n\tstate->next_offset = state->gapfill_interval;\n\n\t/* gap fill end */\n\tif (is_const_null(get_finish_arg(state)))\n\t\tstate->gapfill_end = infer_gapfill_boundary(state, GAPFILL_END);\n\telse\n\t{\n\t\tif (!is_simple_expr(get_finish_arg(state)))\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"invalid time_bucket_gapfill argument: finish must be a simple \"\n\t\t\t\t\t\t\t\"expression\")));\n\t\targ_value = gapfill_exec_expr(state, NULL, get_finish_arg(state), &isnull);\n\n\t\t/*\n\t\t * the default value for finish is NULL but this is checked above,\n\t\t * when a non-Const is passed here that evaluates to NULL we bail\n\t\t */\n\t\tif (isnull)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"invalid time_bucket_gapfill argument: finish cannot be NULL\"),\n\t\t\t\t\t errhint(\"Specify start and finish as arguments or in the WHERE clause.\")));\n\n\t\tstate->gapfill_end = gapfill_datum_get_internal(arg_value, func->funcresulttype);\n\t}\n\n\tgapfill_state_initialize_columns(state, targetlist);\n\n\t/*\n\t * Build ProjectionInfo that will be used for gap filled tuples only.\n\t */\n\tstate->pi = ExecBuildProjectionInfo(targetlist,\n\t\t\t\t\t\t\t\t\t\tstate->csstate.ss.ps.ps_ExprContext,\n\t\t\t\t\t\t\t\t\t\tMakeSingleTupleTableSlot(tupledesc, &TTSOpsVirtual),\n\t\t\t\t\t\t\t\t\t\t&state->csstate.ss.ps,\n\t\t\t\t\t\t\t\t\t\tNULL);\n\n\tstate->csstate.custom_ps = list_make1(ExecInitNode(state->subplan, estate, eflags));\n}\n\n/*\n * This is the main loop of the node it is called whenever the upper node\n * wants to consume a new tuple. Returning NULL signals that the tuples\n * are exhausted. All gapfill state transitions happen in this function.\n */\nstatic TupleTableSlot *\ngapfill_exec(CustomScanState *node)\n{\n\tGapFillState *state = (GapFillState *) node;\n\tTupleTableSlot *slot = NULL;\n\n\twhile (true)\n\t{\n\t\tCHECK_FOR_INTERRUPTS();\n\n\t\t/* fetch next tuple from subplan */\n\t\tif (FETCHED_NONE == state->state)\n\t\t{\n\t\t\tslot = gapfill_fetch_next_tuple(state);\n\t\t\tif (slot)\n\t\t\t{\n\t\t\t\tif (state->multigroup && gapfill_state_is_new_group(state, slot))\n\t\t\t\t\tstate->state = FETCHED_NEXT_GROUP;\n\t\t\t\telse\n\t\t\t\t\tstate->state = FETCHED_ONE;\n\n\t\t\t\tgapfill_state_set_next(state, slot);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * if GROUP BY has non time_bucket_gapfill columns but the\n\t\t\t\t * query has not initialized the groups there is nothing we\n\t\t\t\t * can do here\n\t\t\t\t */\n\t\t\t\tif (state->multigroup && !state->groups_initialized)\n\t\t\t\t\treturn NULL;\n\t\t\t\telse\n\t\t\t\t\tstate->state = FETCHED_LAST;\n\t\t\t}\n\t\t}\n\n\t\t/* return any subplan tuples before gapfill_start */\n\t\tif (FETCHED_ONE == state->state && state->subslot_time < state->gapfill_start)\n\t\t{\n\t\t\tstate->state = FETCHED_NONE;\n\t\t\treturn gapfill_state_return_subplan_slot(state);\n\t\t}\n\n\t\t/* if we have tuple from subplan check if it needs to be inserted now */\n\t\tif (FETCHED_ONE == state->state && state->subslot_time == state->next_timestamp)\n\t\t{\n\t\t\tstate->state = FETCHED_NONE;\n\t\t\tgapfill_advance_timestamp(state);\n\t\t\treturn gapfill_state_return_subplan_slot(state);\n\t\t}\n\n\t\t/* if we are within gapfill boundaries we need to insert tuple */\n\t\tif (state->next_timestamp < state->gapfill_end)\n\t\t{\n\t\t\tAssert(state->state != FETCHED_NONE);\n\t\t\tslot = gapfill_state_gaptuple_create(state, state->next_timestamp);\n\t\t\tgapfill_advance_timestamp(state);\n\t\t\treturn slot;\n\t\t}\n\n\t\t/* return any remaining subplan tuples after gapfill_end */\n\t\tif (FETCHED_ONE == state->state)\n\t\t{\n\t\t\tstate->state = FETCHED_NONE;\n\t\t\treturn gapfill_state_return_subplan_slot(state);\n\t\t}\n\n\t\t/*\n\t\t * Done with current group, prepare for next\n\t\t */\n\t\tif (FETCHED_NEXT_GROUP == state->state)\n\t\t{\n\t\t\tstate->state = FETCHED_ONE;\n\t\t\tstate->next_timestamp = state->gapfill_start;\n\t\t\tgapfill_state_reset_group(state, state->subslot);\n\t\t\tcontinue;\n\t\t}\n\n\t\treturn NULL;\n\t}\n}\n\nstatic void\ngapfill_end(CustomScanState *node)\n{\n\tif (node->custom_ps != NIL)\n\t{\n\t\tExecEndNode(linitial(node->custom_ps));\n\t}\n}\n\nstatic void\ngapfill_rescan(CustomScanState *node)\n{\n\tGapFillState *state = (GapFillState *) node;\n\n\tif (node->custom_ps != NIL)\n\t{\n\t\tif (node->ss.ps.chgParam != NULL)\n\t\t\tUpdateChangedParamSet(linitial(node->custom_ps), node->ss.ps.chgParam);\n\t\tExecReScan(linitial(node->custom_ps));\n\t}\n\n\tstate->state = FETCHED_NONE;\n\tstate->next_timestamp = state->gapfill_start;\n\tstate->next_offset = state->gapfill_interval;\n\n\tif (state->multigroup)\n\t\tstate->groups_initialized = false;\n\n\t/* Reset column states for locf and interpolate */\n\tfor (int i = 0; i < state->ncolumns; i++)\n\t{\n\t\tGapFillColumnState *column = state->columns[i];\n\t\tswitch (column->ctype)\n\t\t{\n\t\t\tcase LOCF_COLUMN:\n\t\t\t\tgapfill_locf_group_change((GapFillLocfColumnState *) column);\n\t\t\t\tbreak;\n\t\t\tcase INTERPOLATE_COLUMN:\n\t\t\t{\n\t\t\t\tGapFillInterpolateColumnState *ic = (GapFillInterpolateColumnState *) column;\n\t\t\t\tic->prev.isnull = true;\n\t\t\t\tic->next.isnull = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase GROUP_COLUMN:\n\t\t\tcase DERIVED_COLUMN:\n\t\t\t{\n\t\t\t\tGapFillGroupColumnState *gc = (GapFillGroupColumnState *) column;\n\t\t\t\tgc->isnull = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nstatic void\ngapfill_state_reset_group(GapFillState *state, TupleTableSlot *slot)\n{\n\tGapFillColumnStateUnion column;\n\tint i;\n\tDatum value;\n\tbool isnull;\n\n\tforeach_column(column.base, i, state)\n\t{\n\t\tvalue = slot_getattr(slot, AttrOffsetGetAttrNumber(i), &isnull);\n\t\tswitch (column.base->ctype)\n\t\t{\n\t\t\tcase INTERPOLATE_COLUMN:\n\t\t\t\tgapfill_interpolate_group_change(column.interpolate,\n\t\t\t\t\t\t\t\t\t\t\t\t state->subslot_time,\n\t\t\t\t\t\t\t\t\t\t\t\t value,\n\t\t\t\t\t\t\t\t\t\t\t\t isnull);\n\t\t\t\tbreak;\n\t\t\tcase LOCF_COLUMN:\n\t\t\t\tgapfill_locf_group_change(column.locf);\n\t\t\t\tbreak;\n\t\t\tcase GROUP_COLUMN:\n\t\t\tcase DERIVED_COLUMN:\n\t\t\t\tcolumn.group->isnull = isnull;\n\t\t\t\tif (!isnull)\n\t\t\t\t\tcolumn.group->value =\n\t\t\t\t\t\tdatumCopy(value, column.base->typbyval, column.base->typlen);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\tstate->next_offset = state->gapfill_interval;\n}\n\n/*\n * Create generated tuple according to column state\n */\nstatic TupleTableSlot *\ngapfill_state_gaptuple_create(GapFillState *state, int64 time)\n{\n\tTupleTableSlot *slot = state->scanslot;\n\tGapFillColumnStateUnion column;\n\tint i;\n\n\tExecClearTuple(slot);\n\n\t/*\n\t * we need to fill in group columns first because locf and interpolation\n\t * might reference those columns when doing out of bounds lookup\n\t */\n\tforeach_column(column.base, i, state)\n\t{\n\t\tswitch (column.base->ctype)\n\t\t{\n\t\t\tcase TIME_COLUMN:\n\t\t\t\tslot->tts_values[i] = gapfill_internal_get_datum(time, state->gapfill_typid);\n\t\t\t\tslot->tts_isnull[i] = false;\n\t\t\t\tbreak;\n\t\t\tcase GROUP_COLUMN:\n\t\t\tcase DERIVED_COLUMN:\n\t\t\t\tslot->tts_values[i] = column.group->value;\n\t\t\t\tslot->tts_isnull[i] = column.group->isnull;\n\t\t\t\tbreak;\n\t\t\tcase NULL_COLUMN:\n\t\t\t\tslot->tts_isnull[i] = true;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/*\n\t * mark slot as containing data so it can be used in locf and interpolate\n\t * lookup expressions\n\t */\n\tExecStoreVirtualTuple(slot);\n\n\tforeach_column(column.base, i, state)\n\t{\n\t\tswitch (column.base->ctype)\n\t\t{\n\t\t\tcase LOCF_COLUMN:\n\t\t\t\t/* We may execute lookup expression over a generated tuple which fills the gap */\n\t\t\t\tgapfill_locf_calculate(column.locf,\n\t\t\t\t\t\t\t\t\t   state,\n\t\t\t\t\t\t\t\t\t   slot,\n\t\t\t\t\t\t\t\t\t   time,\n\t\t\t\t\t\t\t\t\t   &slot->tts_values[i],\n\t\t\t\t\t\t\t\t\t   &slot->tts_isnull[i]);\n\t\t\t\tbreak;\n\t\t\tcase INTERPOLATE_COLUMN:\n\t\t\t\tgapfill_interpolate_calculate(column.interpolate,\n\t\t\t\t\t\t\t\t\t\t\t  state,\n\t\t\t\t\t\t\t\t\t\t\t  time,\n\t\t\t\t\t\t\t\t\t\t\t  &slot->tts_values[i],\n\t\t\t\t\t\t\t\t\t\t\t  &slot->tts_isnull[i]);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tResetExprContext(state->pi->pi_exprContext);\n\tstate->pi->pi_exprContext->ecxt_scantuple = slot;\n\treturn ExecProject(state->pi);\n}\n\n/*\n * Returns true if tuple in the TupleTableSlot belongs to the next\n * aggregation group\n */\nstatic bool\ngapfill_state_is_new_group(GapFillState *state, TupleTableSlot *slot)\n{\n\tGapFillColumnStateUnion column;\n\tint i;\n\tDatum value;\n\tbool isnull;\n\n\t/* groups not initialized yet */\n\tif (!state->groups_initialized)\n\t{\n\t\tstate->groups_initialized = true;\n\t\tgapfill_state_reset_group(state, slot);\n\t\treturn false;\n\t}\n\n\tforeach_column(column.base, i, state)\n\t{\n\t\tif (column.base->ctype == GROUP_COLUMN)\n\t\t{\n\t\t\tvalue = slot_getattr(slot, AttrOffsetGetAttrNumber(i), &isnull);\n\t\t\tif (isnull && column.group->isnull)\n\t\t\t\tcontinue;\n\t\t\tif (isnull != column.group->isnull)\n\t\t\t\treturn true;\n\t\t\t/* We need to use FunctionCall2Coll here since equality comparison\n\t\t\t * functions can try to access flinfo (see arrayfuncs.c). */\n\t\t\tif (!DatumGetBool(FunctionCall2Coll(&column.group->eq_func,\n\t\t\t\t\t\t\t\t\t\t\t\tcolumn.group->collation,\n\t\t\t\t\t\t\t\t\t\t\t\tvalue,\n\t\t\t\t\t\t\t\t\t\t\t\tcolumn.group->value)))\n\t\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/*\n * Returns subslot tuple and adjusts column state accordingly\n */\nstatic TupleTableSlot *\ngapfill_state_return_subplan_slot(GapFillState *state)\n{\n\tGapFillColumnStateUnion column;\n\tCustomScanState *node = castNode(CustomScanState, state);\n\tint i;\n\tDatum value;\n\tbool isnull;\n\n\tforeach_column(column.base, i, state)\n\t{\n\t\tswitch (column.base->ctype)\n\t\t{\n\t\t\tcase LOCF_COLUMN:\n\t\t\t\tvalue = slot_getattr(state->subslot, AttrOffsetGetAttrNumber(i), &isnull);\n\t\t\t\t/* We may execute lookup expression over an input tuple from the subplan to override\n\t\t\t\t * NULL value when NULLs are treated as missing. Use the correct tuple for the\n\t\t\t\t * purpose.\n\t\t\t\t */\n\t\t\t\tif (isnull && column.locf->treat_null_as_missing)\n\t\t\t\t\tgapfill_locf_calculate(column.locf,\n\t\t\t\t\t\t\t\t\t\t   state,\n\t\t\t\t\t\t\t\t\t\t   state->subslot,\n\t\t\t\t\t\t\t\t\t\t   state->subslot_time,\n\t\t\t\t\t\t\t\t\t\t   &state->subslot->tts_values[i],\n\t\t\t\t\t\t\t\t\t\t   &state->subslot->tts_isnull[i]);\n\t\t\t\telse\n\t\t\t\t\tgapfill_locf_tuple_returned(column.locf, value, isnull);\n\t\t\t\tbreak;\n\t\t\tcase INTERPOLATE_COLUMN:\n\t\t\t\tvalue = slot_getattr(state->subslot, AttrOffsetGetAttrNumber(i), &isnull);\n\t\t\t\tgapfill_interpolate_tuple_returned(column.interpolate,\n\t\t\t\t\t\t\t\t\t\t\t\t   state->subslot_time,\n\t\t\t\t\t\t\t\t\t\t\t\t   value,\n\t\t\t\t\t\t\t\t\t\t\t\t   isnull);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (node->ss.ps.ps_ProjInfo)\n\t{\n\t\tExprContext *econtext = node->ss.ps.ps_ExprContext;\n\t\tResetExprContext(econtext);\n\t\tecontext->ecxt_scantuple = state->subslot;\n\t\treturn ExecProject(node->ss.ps.ps_ProjInfo);\n\t}\n\n\treturn state->subslot;\n}\n\nstatic void\ngapfill_state_set_next(GapFillState *state, TupleTableSlot *subslot)\n{\n\tGapFillColumnStateUnion column;\n\tint i;\n\tDatum value;\n\tbool isnull;\n\n\t/*\n\t * if this tuple is for next group we dont update column state yet\n\t * updating of column state happens in gapfill_state_reset_group instead\n\t */\n\tif (FETCHED_NEXT_GROUP == state->state)\n\t\treturn;\n\n\tforeach_column(column.base, i, state)\n\t{\n\t\t/* nothing to do here for locf */\n\t\tif (INTERPOLATE_COLUMN == column.base->ctype)\n\t\t{\n\t\t\tvalue = slot_getattr(subslot, AttrOffsetGetAttrNumber(i), &isnull);\n\t\t\tgapfill_interpolate_tuple_fetched(column.interpolate,\n\t\t\t\t\t\t\t\t\t\t\t  state->subslot_time,\n\t\t\t\t\t\t\t\t\t\t\t  value,\n\t\t\t\t\t\t\t\t\t\t\t  isnull);\n\t\t}\n\t}\n}\n\nstatic TupleTableSlot *\ngapfill_fetch_next_tuple(GapFillState *state)\n{\n\tDatum time_value;\n\tbool isnull;\n\tPlanState *subplan = linitial(castNode(CustomScanState, state)->custom_ps);\n\tTupleTableSlot *subslot = ExecProcNode(subplan);\n\n\tif (TupIsNull(subslot))\n\t\treturn NULL;\n\n\t/* we cannot simply treat an arbitrary source slot as virtual,\n\t * instead we must copy the data into our own slot in order to be able to\n\t * modify it\n\t */\n\tExecCopySlot(state->subslot, subslot);\n\ttime_value = slot_getattr(subslot, AttrOffsetGetAttrNumber(state->time_index), &isnull);\n\tif (isnull)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"invalid time_bucket_gapfill argument: ts cannot be NULL\")));\n\n\tstate->subslot_time = gapfill_datum_get_internal(time_value, state->gapfill_typid);\n\n\treturn state->subslot;\n}\n\n/*\n * Initialize column meta data\n */\nstatic void\ngapfill_state_initialize_columns(GapFillState *state, List *exec_tlist)\n{\n\tTupleDesc tupledesc = state->csstate.ss.ps.ps_ResultTupleSlot->tts_tupleDescriptor;\n\tCustomScan *cscan = castNode(CustomScan, state->csstate.ss.ps.plan);\n\tTargetEntry *tle;\n\tExpr *expr;\n\tint i;\n\n\tstate->ncolumns = tupledesc->natts;\n\tstate->columns = (GapFillColumnState **) palloc(state->ncolumns * sizeof(GapFillColumnState *));\n\n\tfor (i = 0; i < state->ncolumns; i++)\n\t{\n\t\ttle = list_nth(cscan->custom_scan_tlist, i);\n\t\texpr = tle->expr;\n\n\t\tif (tle->ressortgroupref && gapfill_is_group_column(state, tle))\n\t\t{\n\t\t\t/*\n\t\t\t * if there is time_bucket_gapfill function call this is our time\n\t\t\t * column\n\t\t\t */\n\t\t\tif (IsA(expr, FuncExpr) && strncmp(get_func_name(castNode(FuncExpr, expr)->funcid),\n\t\t\t\t\t\t\t\t\t\t\t   GAPFILL_FUNCTION,\n\t\t\t\t\t\t\t\t\t\t\t   NAMEDATALEN) == 0)\n\t\t\t{\n\t\t\t\tstate->columns[i] =\n\t\t\t\t\tgapfill_column_state_create(TIME_COLUMN, TupleDescAttr(tupledesc, i)->atttypid);\n\t\t\t\tstate->time_index = i;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t/* otherwise this is a normal group column */\n\t\t\tstate->columns[i] =\n\t\t\t\tgapfill_column_state_create(GROUP_COLUMN, TupleDescAttr(tupledesc, i)->atttypid);\n\t\t\tstate->multigroup = true;\n\t\t\tstate->groups_initialized = false;\n\t\t\tcontinue;\n\t\t}\n\t\telse if (IsA(expr, FuncExpr))\n\t\t{\n\t\t\t/* locf and interpolate will be toplevel function calls in the gapfill node */\n\t\t\tif (strncmp(get_func_name(castNode(FuncExpr, expr)->funcid),\n\t\t\t\t\t\tGAPFILL_LOCF_FUNCTION,\n\t\t\t\t\t\tNAMEDATALEN) == 0)\n\t\t\t{\n\t\t\t\tstate->columns[i] =\n\t\t\t\t\tgapfill_column_state_create(LOCF_COLUMN, TupleDescAttr(tupledesc, i)->atttypid);\n\t\t\t\tgapfill_locf_initialize((GapFillLocfColumnState *) state->columns[i],\n\t\t\t\t\t\t\t\t\t\tstate,\n\t\t\t\t\t\t\t\t\t\t(FuncExpr *) expr);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (strncmp(get_func_name(castNode(FuncExpr, expr)->funcid),\n\t\t\t\t\t\tGAPFILL_INTERPOLATE_FUNCTION,\n\t\t\t\t\t\tNAMEDATALEN) == 0)\n\t\t\t{\n\t\t\t\tstate->columns[i] =\n\t\t\t\t\tgapfill_column_state_create(INTERPOLATE_COLUMN,\n\t\t\t\t\t\t\t\t\t\t\t\tTupleDescAttr(tupledesc, i)->atttypid);\n\t\t\t\tgapfill_interpolate_initialize((GapFillInterpolateColumnState *) state->columns[i],\n\t\t\t\t\t\t\t\t\t\t\t   state,\n\t\t\t\t\t\t\t\t\t\t\t   (FuncExpr *) expr);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\t/*\n\t\t * any column that does not have an aggregation function and is not\n\t\t * an explicit GROUP BY column has to be derived from a GROUP BY\n\t\t * column so we treat those similar to GROUP BY column for gapfill\n\t\t * purposes.\n\t\t */\n\t\tbool column_contains_aggs = contain_agg_clause((Node *) expr);\n\t\tif (!column_contains_aggs && contain_var_clause((Node *) expr))\n\t\t{\n\t\t\tstate->columns[i] =\n\t\t\t\tgapfill_column_state_create(DERIVED_COLUMN, TupleDescAttr(tupledesc, i)->atttypid);\n\t\t\tstate->multigroup = true;\n\t\t\tstate->groups_initialized = false;\n\t\t\tcontinue;\n\t\t}\n\n\t\t/*\n\t\t * For every column with Aggrefs we take the original expression tree from the\n\t\t * subplan and replace Aggref nodes with Const NULL nodes. This is\n\t\t * necessary because the expression might be evaluated below the\n\t\t * aggregation so we need to pull up expression from subplan into\n\t\t * projection for gapfilled tuples so expressions like COALESCE work\n\t\t * correctly for gapfilled tuples.\n\t\t */\n\t\tif (column_contains_aggs)\n\t\t{\n\t\t\tTargetEntry *agg_expr_tle = gapfill_get_fixed_agg_expr_column(state, tle);\n\t\t\tAssert(agg_expr_tle);\n\t\t\tNode *entry = copyObject((Node *) agg_expr_tle);\n\n\t\t\t/* Fix for #4894 when we have expressions like (agg + group_expr):\n\t\t\t * after getting fixed entry where aggs are replaced with NULLs\n\t\t\t * and group expressions are replaced with exec group columns,\n\t\t\t * check whether this column contains group columns and needs to be DERIVED or NULL.\n\t\t\t */\n\t\t\tif (contain_var_clause(entry))\n\t\t\t{\n\t\t\t\tstate->columns[i] =\n\t\t\t\t\tgapfill_column_state_create(DERIVED_COLUMN,\n\t\t\t\t\t\t\t\t\t\t\t\tTupleDescAttr(tupledesc, i)->atttypid);\n\t\t\t\tstate->multigroup = true;\n\t\t\t\tstate->groups_initialized = false;\n\t\t\t}\n\t\t\telse\n\t\t\t\tstate->columns[i] =\n\t\t\t\t\tgapfill_column_state_create(NULL_COLUMN, TupleDescAttr(tupledesc, i)->atttypid);\n\n\t\t\tlfirst(list_nth_cell(exec_tlist, i)) = entry;\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* column with no special action from gap fill node */\n\t\tstate->columns[i] =\n\t\t\tgapfill_column_state_create(NULL_COLUMN, TupleDescAttr(tupledesc, i)->atttypid);\n\t}\n}\n\n/*\n * Create GapFillColumnState object, set proper type and fill in datatype information\n */\nstatic GapFillColumnState *\ngapfill_column_state_create(GapFillColumnType ctype, Oid typeid)\n{\n\tTypeCacheEntry *tce;\n\tint tc_flags = 0;\n\tGapFillColumnState *column;\n\tsize_t size;\n\n\tswitch (ctype)\n\t{\n\t\tcase GROUP_COLUMN:\n\t\t\ttc_flags |= TYPECACHE_EQ_OPR;\n\t\t\tTS_FALLTHROUGH;\n\t\tcase DERIVED_COLUMN:\n\t\t\tsize = sizeof(GapFillGroupColumnState);\n\t\t\tbreak;\n\t\tcase LOCF_COLUMN:\n\t\t\tsize = sizeof(GapFillLocfColumnState);\n\t\t\tbreak;\n\t\tcase INTERPOLATE_COLUMN:\n\t\t\tsize = sizeof(GapFillInterpolateColumnState);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tsize = sizeof(GapFillColumnState);\n\t\t\tbreak;\n\t}\n\ttce = lookup_type_cache(typeid, tc_flags);\n\n\tcolumn = palloc0(size);\n\tcolumn->ctype = ctype;\n\tcolumn->typid = tce->type_id;\n\tcolumn->typbyval = tce->typbyval;\n\tcolumn->typlen = tce->typlen;\n\n\tif (ctype == GROUP_COLUMN)\n\t{\n\t\tGapFillGroupColumnState *gcolumn = (GapFillGroupColumnState *) column;\n\t\tOid eq_opr_func = get_opcode(tce->eq_opr);\n\t\tfmgr_info_cxt(eq_opr_func, &gcolumn->eq_func, CurrentMemoryContext);\n\t\tgcolumn->collation = tce->typcollation;\n\t}\n\n\treturn column;\n}\n\n/*\n * check if the target entry is a GROUP BY column, we need\n * this check because ressortgroupref will be nonzero for\n * ORDER BY and GROUP BY columns but we are only interested\n * in actual GROUP BY columns\n */\nstatic bool\ngapfill_is_group_column(GapFillState *state, TargetEntry *tle)\n{\n\tListCell *lc;\n\tCustomScan *cscan = castNode(CustomScan, state->csstate.ss.ps.plan);\n\tList *groups = list_nth(cscan->custom_private, GFP_GroupClause);\n\n\tforeach (lc, groups)\n\t{\n\t\tif (tle->ressortgroupref == ((SortGroupClause *) lfirst(lc))->tleSortGroupRef)\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n/*\n * If the target entry contains an aggregate, it has been fixed in \"custom_exprs\"\n * so that the aggregate is replaced with NULL\n * and any group expressions are replaced with exec group vars.\n * We will get the fixed aggregate expression here and use it in exec tlist.\n */\nstatic TargetEntry *\ngapfill_get_fixed_agg_expr_column(GapFillState *state, TargetEntry *tle)\n{\n\tListCell *lc;\n\tCustomScan *cscan = castNode(CustomScan, state->csstate.ss.ps.plan);\n\tList *mutated_agg_exprs_list = castNode(List, cscan->custom_exprs);\n\tAssert(list_length(mutated_agg_exprs_list) == 1);\n\tList *mutated_agg_exprs = castNode(List, linitial(mutated_agg_exprs_list));\n\n\tforeach (lc, mutated_agg_exprs)\n\t{\n\t\tTargetEntry *mutated_agg_expr_tle = castNode(TargetEntry, lfirst(lc));\n\t\tif (tle->resno == mutated_agg_expr_tle->resno)\n\t\t\treturn mutated_agg_expr_tle;\n\t}\n\treturn NULL;\n}\n\n/*\n * Execute expression and return result of expression\n */\nDatum\ngapfill_exec_expr(GapFillState *state, TupleTableSlot *ecxt_slot, Expr *expr, bool *isnull)\n{\n\tExprState *exprstate = ExecInitExpr(expr, &state->csstate.ss.ps);\n\tExprContext *exprcontext = GetPerTupleExprContext(state->csstate.ss.ps.state);\n\n\texprcontext->ecxt_scantuple = ecxt_slot;\n\n\treturn ExecEvalExprSwitchContext(exprstate, exprcontext, isnull);\n}\n\n/*\n * Adjust attribute number of all Var nodes in an expression to have the\n * proper index into the gap filled tuple. This is necessary to make column\n * references in correlated subqueries in lookup queries work.\n */\nExpr *\ngapfill_adjust_varnos(GapFillState *state, Expr *expr)\n{\n\tListCell *lc_var, *lc_tle;\n\tList *vars = pull_var_clause((Node *) expr, 0);\n\tList *tlist = castNode(CustomScan, state->csstate.ss.ps.plan)->custom_scan_tlist;\n\n\tforeach (lc_var, vars)\n\t{\n\t\tVar *var = lfirst(lc_var);\n\n\t\tforeach (lc_tle, tlist)\n\t\t{\n\t\t\tTargetEntry *tle = lfirst(lc_tle);\n\n\t\t\t/*\n\t\t\t * subqueries in aggregate queries can only reference columns so\n\t\t\t * we only need to look for targetlist toplevel column references\n\t\t\t */\n\t\t\tif (IsA(tle->expr, Var) && castNode(Var, tle->expr)->varattno == var->varattno)\n\t\t\t{\n\t\t\t\tvar->varattno = tle->resno;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\treturn expr;\n}\n"
  },
  {
    "path": "tsl/src/nodes/gapfill/gapfill_functions.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n#include <fmgr.h>\n#include <utils/lsyscache.h>\n\n#include \"gapfill_functions.h\"\n#include \"time_bucket.h\"\n\nDatum\ngapfill_marker(PG_FUNCTION_ARGS)\n{\n\tif (PG_ARGISNULL(0))\n\t\tPG_RETURN_NULL();\n\telse\n\t\tPG_RETURN_DATUM(PG_GETARG_DATUM(0));\n}\n\n#define GAPFILL_TIMEBUCKET_WRAPPER(datatype)                                                       \\\n\tDatum gapfill_##datatype##_time_bucket(PG_FUNCTION_ARGS)                    \\\n\t{                                                                                              \\\n\t\t/*                                                                                         \\\n\t\t * since time_bucket is STRICT and time_bucket_gapfill                                     \\\n\t\t * is not we need to add explicit checks for NULL here                                     \\\n\t\t */                                                                                        \\\n\t\tif (PG_ARGISNULL(0) || PG_ARGISNULL(1))                                                    \\\n\t\t\tPG_RETURN_NULL();                                                                      \\\n\t\treturn DirectFunctionCall2(ts_##datatype##_bucket,                                         \\\n\t\t\t\t\t\t\t\t   PG_GETARG_DATUM(0),                                             \\\n\t\t\t\t\t\t\t\t   PG_GETARG_DATUM(1));                                            \\\n\t}\n\nGAPFILL_TIMEBUCKET_WRAPPER(int16);\nGAPFILL_TIMEBUCKET_WRAPPER(int32);\nGAPFILL_TIMEBUCKET_WRAPPER(int64);\nGAPFILL_TIMEBUCKET_WRAPPER(date);\nGAPFILL_TIMEBUCKET_WRAPPER(timestamp);\nGAPFILL_TIMEBUCKET_WRAPPER(timestamptz);\n\nDatum\ngapfill_timestamptz_timezone_time_bucket(PG_FUNCTION_ARGS)\n{\n\t/*\n\t * since time_bucket is STRICT and time_bucket_gapfill\n\t * is not we need to add explicit checks for NULL here\n\t */\n\tif (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2))\n\t\tPG_RETURN_NULL();\n\treturn DirectFunctionCall3(ts_timestamptz_timezone_bucket,\n\t\t\t\t\t\t\t   PG_GETARG_DATUM(0),\n\t\t\t\t\t\t\t   PG_GETARG_DATUM(1),\n\t\t\t\t\t\t\t   PG_GETARG_DATUM(2));\n}\n"
  },
  {
    "path": "tsl/src/nodes/gapfill/gapfill_functions.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n/*\n * Functions used by gapfill which are exported to SQL.\n * Shell functions for these are defined in src/gapfill.c\n */\n\n#include <postgres.h>\n#include <fmgr.h>\n\nextern Datum gapfill_marker(PG_FUNCTION_ARGS);\nextern Datum gapfill_int16_time_bucket(PG_FUNCTION_ARGS);\nextern Datum gapfill_int32_time_bucket(PG_FUNCTION_ARGS);\nextern Datum gapfill_int64_time_bucket(PG_FUNCTION_ARGS);\nextern Datum gapfill_timestamp_time_bucket(PG_FUNCTION_ARGS);\nextern Datum gapfill_timestamptz_time_bucket(PG_FUNCTION_ARGS);\nextern Datum gapfill_timestamptz_timezone_time_bucket(PG_FUNCTION_ARGS);\nextern Datum gapfill_date_time_bucket(PG_FUNCTION_ARGS);\n"
  },
  {
    "path": "tsl/src/nodes/gapfill/gapfill_internal.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <datatype/timestamp.h>\n#include <nodes/execnodes.h>\n\n/*\n * GapFillFetchState describes the state of subslot in GapFillState:\n * FETCHED_NONE: no tuple in subslot\n * FETCHED_ONE: valid tuple in subslot\n * FETCHED_LAST: no tuple in subslot and no more tuples from subplan\n * FETCHED_NEXT_GROUP: tuple in subslot belongs to next aggregation group\n *\n * The start state is FETCHED_NONE and the end state is FETCHED_LAST\n *\n *  State transition with single group by time\n *\n *                                     no tuple returned\n *  FETCHED_NONE --> fetch_next_tuple -------------------> FETCHED_LAST\n *            ^               |\n *            |               | tuple found\n *            |               |\n *            |               v\n *            └----------FETCHED_ONE\n *       tuple returned\n *\n *  State transition with multiple groups\n *\n *                                     no tuple returned\n *  FETCHED_NONE --> fetch_next_tuple -------------------> FETCHED_LAST\n *            ^               |\n *            |               | tuple found\n *            |               v\n *            |       check_group_changed -------> FETCHED_NEXT_GROUP\n *            |               |             yes        |\n *            |               | no                     |\n *            |               |                        |\n *            |               v                        |\n *            └----------FETCHED_ONE <-----------------\n *       tuple returned\n */\ntypedef enum GapFillFetchState\n{\n\tFETCHED_NONE,\n\tFETCHED_ONE,\n\tFETCHED_NEXT_GROUP,\n\tFETCHED_LAST,\n} GapFillFetchState;\n\n/*\n * NULL_COLUMN: column with no special action from gapfill e.g. min(value)\n * TIME_COLUMN: column with time_bucket_gapfill call\n * GROUP_COLUMN: any column appearing in GROUP BY clause\n * DERIVED_COLUMN: column not appearing in GROUP BY but dependent on GROUP BY column\n * LOCF_COLUMN: column with locf call\n * INTERPOLATE_COLUMN: column with interpolate call\n */\ntypedef enum GapFillColumnType\n{\n\tNULL_COLUMN,\n\tTIME_COLUMN,\n\tGROUP_COLUMN,\n\tDERIVED_COLUMN,\n\tLOCF_COLUMN,\n\tINTERPOLATE_COLUMN\n} GapFillColumnType;\n\ntypedef struct GapFillColumnState\n{\n\tGapFillColumnType ctype;\n\tOid typid;\n\tbool typbyval;\n\tint16 typlen;\n} GapFillColumnState;\n\ntypedef struct GapFillGroupColumnState\n{\n\tGapFillColumnState base;\n\tDatum value;\n\tbool isnull;\n\tOid collation;\n\tFmgrInfo eq_func;\n} GapFillGroupColumnState;\n\ntypedef struct GapFillState\n{\n\tCustomScanState csstate;\n\tPlan *subplan;\n\n\tOid gapfill_typid;\n\t/* arguments of the gapfill function call */\n\tList *args;\n\tbool have_timezone;\n\tint64 gapfill_start;\n\tint64 gapfill_end;\n\t/* bucket width for fixed-size buckets */\n\tint64 gapfill_period;\n\t/* bucket width when bucketing by month */\n\tInterval *gapfill_interval;\n\n\tint64 next_timestamp;\n\t/* interval offset for next_timestamp from gapfill_start */\n\tInterval *next_offset;\n\tint64 subslot_time; /* time of tuple in subslot */\n\n\tint time_index;\t\t\t /* position of time column */\n\tTupleTableSlot *subslot; /* TupleTableSlot storing data from subplan */\n\n\tbool multigroup; /* multiple groupings */\n\tbool groups_initialized;\n\n\tint ncolumns;\n\tGapFillColumnState **columns;\n\n\tProjectionInfo *pi;\n\tTupleTableSlot *scanslot;\n\tGapFillFetchState state;\n} GapFillState;\n\nNode *gapfill_state_create(CustomScan *);\nExpr *gapfill_adjust_varnos(GapFillState *state, Expr *expr);\nDatum gapfill_exec_expr(GapFillState *state, TupleTableSlot *, Expr *expr, bool *isnull);\nint64 gapfill_datum_get_internal(Datum, Oid);\n"
  },
  {
    "path": "tsl/src/nodes/gapfill/gapfill_plan.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <nodes/execnodes.h>\n#include <nodes/extensible.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodeFuncs.h>\n#include <optimizer/clauses.h>\n#include <optimizer/optimizer.h>\n#include <optimizer/pathnode.h>\n#include <optimizer/paths.h>\n#include <optimizer/tlist.h>\n#include <parser/parse_func.h>\n#include <utils/lsyscache.h>\n\n#include \"compat/compat.h\"\n\n#include \"gapfill.h\"\n#include \"gapfill_internal.h\"\n#include \"import/list.h\"\n#include \"utils.h\"\n\nstatic CustomScanMethods gapfill_plan_methods = {\n\t.CustomName = \"GapFill\",\n\t.CreateCustomScanState = gapfill_state_create,\n};\n\ntypedef struct gapfill_walker_context\n{\n\tunion\n\t{\n\t\tNode *node;\n\t\tExpr *expr;\n\t\tFuncExpr *func;\n\t\tWindowFunc *window;\n\t} call;\n\tint count;\n} gapfill_walker_context;\n\n/*\n * Replace Aggref with const NULL\n */\nstatic Node *\ngapfill_aggref_mutator(Node *node, void *context)\n{\n\tif (node == NULL)\n\t\treturn NULL;\n\n\tif (IsA(node, Aggref))\n\t\treturn (Node *)\n\t\t\tmakeConst(((Aggref *) node)->aggtype, -1, InvalidOid, -2, UnassignedDatum, true, false);\n\n\treturn expression_tree_mutator(node, gapfill_aggref_mutator, context);\n}\n\n/*\n * Check if an expression is NOT a runtime constant.\n *\n */\nstatic bool\ncontains_nonconstant_walker(Node *node, void *context)\n{\n\tif (node == NULL)\n\t\treturn false;\n\n\tif (IsA(node, Var))\n\t{\n\t\treturn true;\n\t}\n\n\tif (IsA(node, SubLink))\n\t{\n\t\treturn true;\n\t}\n\n\tif (IsA(node, PlaceHolderVar))\n\t{\n\t\treturn true;\n\t}\n\n\tif (IsA(node, Param))\n\t{\n\t\tParam *param = castNode(Param, node);\n\t\t/* PARAM_EXTERN are external query parameters, constant for query duration */\n\t\treturn param->paramkind != PARAM_EXTERN;\n\t}\n\n\tif (check_functions_in_node(node,\n\t\t\t\t\t\t\t\tcontains_volatile_functions_checker,\n\t\t\t\t\t\t\t\t/* context = */ NULL))\n\t{\n\t\treturn true;\n\t}\n\n\treturn expression_tree_walker(node, contains_nonconstant_walker, context);\n}\n\nstatic bool\ncontains_nonconstant_expr(Node *node)\n{\n\treturn contains_nonconstant_walker(node, NULL);\n}\n\n/*\n * FuncExpr is time_bucket_gapfill function call\n */\nstatic inline bool\nis_gapfill_function_call(FuncExpr *call)\n{\n\tchar *func_name = get_func_name(call->funcid);\n\treturn strncmp(func_name, GAPFILL_FUNCTION, NAMEDATALEN) == 0;\n}\n\n/*\n * FuncExpr is locf or interpolate function call\n */\nstatic inline bool\nis_marker_function_call(FuncExpr *call)\n{\n\tchar *func_name = get_func_name(call->funcid);\n\treturn strncmp(func_name, GAPFILL_LOCF_FUNCTION, NAMEDATALEN) == 0 ||\n\t\t   strncmp(func_name, GAPFILL_INTERPOLATE_FUNCTION, NAMEDATALEN) == 0;\n}\n\n/*\n * Find time_bucket_gapfill function call\n */\nstatic bool\ngapfill_function_walker(Node *node, gapfill_walker_context *context)\n{\n\tif (node == NULL)\n\t\treturn false;\n\n\tif (IsA(node, FuncExpr) && is_gapfill_function_call(castNode(FuncExpr, node)))\n\t{\n\t\tcontext->call.node = node;\n\t\tcontext->count++;\n\t}\n\n\treturn expression_tree_walker(node, gapfill_function_walker, context);\n}\n\n/*\n * Find locf/interpolate function call\n */\nstatic bool\nmarker_function_walker(Node *node, gapfill_walker_context *context)\n{\n\tif (node == NULL)\n\t\treturn false;\n\n\tif (IsA(node, FuncExpr) && is_marker_function_call(castNode(FuncExpr, node)))\n\t{\n\t\tcontext->call.node = node;\n\t\tcontext->count++;\n\t}\n\n\treturn expression_tree_walker(node, marker_function_walker, context);\n}\n\n/*\n * Find window function calls\n */\nstatic bool\nwindow_function_walker(Node *node, gapfill_walker_context *context)\n{\n\tif (node == NULL)\n\t\treturn false;\n\n\tif (IsA(node, WindowFunc))\n\t{\n\t\tcontext->call.node = node;\n\t\tcontext->count++;\n\t}\n\n\treturn expression_tree_walker(node, window_function_walker, context);\n}\n\n/*\n * check if ordering matches the order we need:\n * all groups need to be part of order\n * pathkeys must consist of group elements only\n * last element of pathkeys needs to be time_bucket_gapfill ASC\n */\nstatic bool\ngapfill_correct_order(PlannerInfo *root, Path *subpath, FuncExpr *func)\n{\n\tint num_groupby_pathkeys;\n#if PG16_LT\n\tnum_groupby_pathkeys = list_length(root->group_pathkeys);\n#else\n\t/* In PG16 group_pathkeys can contain additional pathkeys\n\t * used for optimization on ordered aggregates.\n\t * We only want to deal with group by elements only here.\n\t */\n\tnum_groupby_pathkeys = root->num_groupby_pathkeys;\n#endif\n\n\tif (list_length(subpath->pathkeys) != num_groupby_pathkeys)\n\t\treturn false;\n\n\tif (list_length(subpath->pathkeys) > 0)\n\t{\n\t\tPathKey *pk = llast(subpath->pathkeys);\n\t\tEquivalenceMember *em = linitial(pk->pk_eclass->ec_members);\n\n\t\t/* time_bucket_gapfill is last element */\n\t\tif (pk->pk_cmptype == COMPARE_LT && IsA(em->em_expr, FuncExpr) &&\n\t\t\t((FuncExpr *) em->em_expr)->funcid == func->funcid)\n\t\t{\n\t\t\tint i;\n\t\t\t/* check all groupby pathkeys are part of subpath pathkeys */\n\t\t\tfor (i = 0; i < num_groupby_pathkeys; i++)\n\t\t\t{\n\t\t\t\tif (!list_member(subpath->pathkeys, list_nth(root->group_pathkeys, i)))\n\t\t\t\t\treturn false;\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/* Create a gapfill plan node in the form of a CustomScan node. The\n * purpose of this plan node is to insert tuples for missing groups.\n *\n * Note that CustomScan nodes cannot be extended (by struct embedding) because\n * they might be copied, therefore we pass any extra info in the custom_private\n * field.\n *\n * The gapfill plan takes the original Agg node and imposes itself on top of the\n * Agg node. During execution, the gapfill node will produce the new tuples.\n */\nstatic Plan *\ngapfill_plan_create(PlannerInfo *root, RelOptInfo *rel, CustomPath *path, List *tlist,\n\t\t\t\t\tList *clauses, List *custom_plans)\n{\n\tGapFillPath *gfpath = (GapFillPath *) path;\n\tCustomScan *cscan = makeNode(CustomScan);\n\tList *args = list_copy(gfpath->func->args);\n\n\tcscan->scan.scanrelid = 0;\n\tcscan->scan.plan.targetlist = tlist;\n\tcscan->custom_plans = custom_plans;\n\tcscan->custom_scan_tlist = tlist;\n\n\t/* When we have original target entries like (agg + group_expr)\n\t * we will replace agg with NULL and put resulting expression into exec-fixed \"targetlist\",\n\t * but we need to fix \"group_expr\" to refer to exec targetlist group column.\n\t * Only then we can safely put (NULL + group_column_exec) entry into exec-fixed targetlist.\n\t */\n\tList *mutated_agg_exprs = NIL;\n\tif (contain_agg_clause((Node *) tlist))\n\t{\n\t\tTargetEntry *tle;\n\t\tListCell *lc;\n\t\tforeach (lc, tlist)\n\t\t{\n\t\t\ttle = lfirst(lc);\n\t\t\tif (contain_agg_clause((Node *) tle))\n\t\t\t{\n\t\t\t\tNode *entry = copyObject((Node *) tle);\n\t\t\t\tentry = gapfill_aggref_mutator(entry, NULL);\n\t\t\t\tmutated_agg_exprs = lappend(mutated_agg_exprs, entry);\n\t\t\t}\n\t\t}\n\t}\n\tcscan->custom_exprs = list_make1(mutated_agg_exprs);\n\n\tcscan->flags = path->flags;\n\tcscan->methods = &gapfill_plan_methods;\n\n\tcscan->custom_private = ts_new_list(T_List, GFP_Count);\n\tlfirst(list_nth_cell(cscan->custom_private, GFP_GapfillFunc)) = gfpath->func;\n\tlfirst(list_nth_cell(cscan->custom_private, GFP_GroupClause)) = root->parse->groupClause;\n\tlfirst(list_nth_cell(cscan->custom_private, GFP_JoinTree)) = root->parse->jointree;\n\tlfirst(list_nth_cell(cscan->custom_private, GFP_Args)) = args;\n\n\treturn &cscan->scan.plan;\n}\n\nstatic CustomPathMethods gapfill_path_methods = {\n\t.CustomName = \"GapFill\",\n\t.PlanCustomPath = gapfill_plan_create,\n};\n\nstatic bool\ngapfill_expression_walker(Expr *node, bool (*walker)(Node *, gapfill_walker_context *),\n\t\t\t\t\t\t  gapfill_walker_context *context)\n{\n\tcontext->count = 0;\n\tcontext->call.node = NULL;\n\n\treturn (*walker)((Node *) node, context);\n}\n\n/*\n * Build expression lists for the gapfill node and the node below.\n * All marker functions will be top-level function calls in the\n * resulting gapfill node targetlist and will not be included in\n * the subpath expression list\n */\nstatic void\ngapfill_build_pathtarget(PathTarget *pt_upper, PathTarget *pt_path, PathTarget *pt_subpath)\n{\n\tListCell *lc;\n\tint i = -1;\n\n\tforeach (lc, pt_upper->exprs)\n\t{\n\t\tExpr *expr = lfirst(lc);\n\t\tgapfill_walker_context context;\n\t\ti++;\n\n\t\t/* check for locf/interpolate calls */\n\t\tgapfill_expression_walker(expr, marker_function_walker, &context);\n\t\tif (context.count > 1)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"multiple interpolate/locf function calls per resultset column not \"\n\t\t\t\t\t\t\t\"supported\")));\n\n\t\tif (context.count == 1)\n\t\t{\n\t\t\t/*\n\t\t\t * marker needs to be toplevel for now unless we have a projection capable\n\t\t\t * node above gapfill node\n\t\t\t */\n\t\t\tif (expr != context.call.expr && !contain_window_function((Node *) expr))\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t errmsg(\"%s must be toplevel function call\",\n\t\t\t\t\t\t\t\tget_func_name(context.call.func->funcid))));\n\n\t\t\t/* if there is an aggregation it needs to be a child of the marker function */\n\t\t\tif (contain_agg_clause((Node *) expr) &&\n\t\t\t\t!contain_agg_clause(linitial(context.call.func->args)))\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t errmsg(\"aggregate functions must be below %s\",\n\t\t\t\t\t\t\t\tget_func_name(context.call.func->funcid))));\n\n\t\t\tif (contain_window_function(context.call.node))\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t errmsg(\"window functions must not be below %s\",\n\t\t\t\t\t\t\t\tget_func_name(context.call.func->funcid))));\n\n\t\t\tadd_column_to_pathtarget(pt_path, context.call.expr, pt_upper->sortgrouprefs[i]);\n\t\t\tadd_column_to_pathtarget(pt_subpath,\n\t\t\t\t\t\t\t\t\t linitial(context.call.func->args),\n\t\t\t\t\t\t\t\t\t pt_upper->sortgrouprefs[i]);\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* check for plain window function calls without locf/interpolate */\n\t\tgapfill_expression_walker(expr, window_function_walker, &context);\n\t\tif (context.count > 1)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"multiple window function calls per column not supported\")));\n\n\t\tif (context.count == 1)\n\t\t{\n\t\t\t/*\n\t\t\t * window functions without arguments like rank() don't need to\n\t\t\t * appear in the target list below WindowAgg node\n\t\t\t */\n\t\t\tif (context.call.window->args != NIL)\n\t\t\t{\n\t\t\t\tListCell *lc_arg;\n\n\t\t\t\t/*\n\t\t\t\t * check arguments past first argument dont have Vars\n\t\t\t\t */\n\t\t\t\tfor (lc_arg =\n\t\t\t\t\t\t lnext(context.call.window->args, list_head(context.call.window->args));\n\t\t\t\t\t lc_arg != NULL;\n\t\t\t\t\t lc_arg = lnext(context.call.window->args, lc_arg))\n\t\t\t\t{\n\t\t\t\t\tif (contain_var_clause(lfirst(lc_arg)))\n\t\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t\t\t errmsg(\"window functions with multiple column \"\n\t\t\t\t\t\t\t\t\t\t\"references not supported\")));\n\t\t\t\t}\n\n\t\t\t\tif (contain_var_clause(linitial(context.call.window->args)))\n\t\t\t\t{\n\t\t\t\t\tadd_column_to_pathtarget(pt_path,\n\t\t\t\t\t\t\t\t\t\t\t linitial(context.call.window->args),\n\t\t\t\t\t\t\t\t\t\t\t pt_upper->sortgrouprefs[i]);\n\t\t\t\t\tadd_column_to_pathtarget(pt_subpath,\n\t\t\t\t\t\t\t\t\t\t\t linitial(context.call.window->args),\n\t\t\t\t\t\t\t\t\t\t\t pt_upper->sortgrouprefs[i]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/*\n\t\t\t * no locf/interpolate or window functions found so we can\n\t\t\t * use expression verbatim\n\t\t\t */\n\t\t\tadd_column_to_pathtarget(pt_path, expr, pt_upper->sortgrouprefs[i]);\n\t\t\tadd_column_to_pathtarget(pt_subpath, expr, pt_upper->sortgrouprefs[i]);\n\t\t}\n\t}\n}\n\n/*\n * Create a Gapfill Path node.\n *\n * The gap fill node needs rows to be sorted by time ASC\n * so we insert sort paths if the query order does not match\n * that\n */\nstatic Path *\ngapfill_path_create(PlannerInfo *root, Path *subpath, FuncExpr *func)\n{\n\tGapFillPath *path;\n\n\tpath = (GapFillPath *) newNode(sizeof(GapFillPath), T_CustomPath);\n\tpath->cpath.path.pathtype = T_CustomScan;\n\tpath->cpath.methods = &gapfill_path_methods;\n\n\t/*\n\t * parallel_safe must be false because it is not safe to execute this node\n\t * in parallel, but it is safe for child nodes to be parallel\n\t */\n\tAssert(!path->cpath.path.parallel_safe);\n\tpath->cpath.path.rows = subpath->rows;\n\tpath->cpath.path.parent = subpath->parent;\n\tpath->cpath.path.param_info = subpath->param_info;\n\tpath->cpath.flags = 0;\n\tpath->cpath.path.pathkeys = subpath->pathkeys;\n\n\tpath->cpath.path.pathtarget = create_empty_pathtarget();\n\tsubpath->pathtarget = create_empty_pathtarget();\n\tgapfill_build_pathtarget(root->upper_targets[UPPERREL_FINAL],\n\t\t\t\t\t\t\t path->cpath.path.pathtarget,\n\t\t\t\t\t\t\t subpath->pathtarget);\n\n\tif (!gapfill_correct_order(root, subpath, func))\n\t{\n\t\tList *new_order = NIL;\n\t\tPathKey *pk_func = NULL;\n\t\tint num_groupby_pathkeys;\n#if PG16_LT\n\t\tnum_groupby_pathkeys = list_length(root->group_pathkeys);\n#else\n\t\t/* In PG16 group_pathkeys can contain additional pathkeys\n\t\t * used for optimization on ordered aggregates.\n\t\t * We only want to deal with group by elements only here.\n\t\t */\n\t\tnum_groupby_pathkeys = root->num_groupby_pathkeys;\n#endif\n\t\tint i;\n\t\t/* subpath does not have correct order */\n\t\tfor (i = 0; i < num_groupby_pathkeys; i++)\n\t\t{\n\t\t\tPathKey *pk = list_nth(root->group_pathkeys, i);\n\t\t\tEquivalenceMember *em = linitial(pk->pk_eclass->ec_members);\n\n\t\t\tif (!pk_func && IsA(em->em_expr, FuncExpr) &&\n\t\t\t\t((FuncExpr *) em->em_expr)->funcid == func->funcid)\n\t\t\t{\n\t\t\t\tif (pk->pk_cmptype == COMPARE_LT)\n\t\t\t\t\tpk_func = pk;\n\t\t\t\telse\n\t\t\t\t\tpk_func = make_canonical_pathkey(root,\n\t\t\t\t\t\t\t\t\t\t\t\t\t pk->pk_eclass,\n\t\t\t\t\t\t\t\t\t\t\t\t\t pk->pk_opfamily,\n\t\t\t\t\t\t\t\t\t\t\t\t\t BTLessStrategyNumber,\n\t\t\t\t\t\t\t\t\t\t\t\t\t pk->pk_nulls_first);\n\t\t\t}\n\t\t\telse\n\t\t\t\tnew_order = lappend(new_order, pk);\n\t\t}\n\t\tif (!pk_func)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"no top level time_bucket_gapfill in group by clause\")));\n\n\t\tnew_order = lappend(new_order, pk_func);\n\t\tsubpath = (Path *)\n\t\t\tcreate_sort_path(root, subpath->parent, subpath, new_order, root->limit_tuples);\n\t}\n\n\tpath->cpath.path.startup_cost = subpath->startup_cost;\n\tpath->cpath.path.total_cost = subpath->total_cost;\n\tpath->cpath.path.pathkeys = subpath->pathkeys;\n\tpath->cpath.custom_paths = list_make1(subpath);\n\tpath->func = func;\n\n\treturn &path->cpath.path;\n}\n\n/*\n * Prepend GapFill node to every group_rel path.\n * The implementation assumes that TimescaleDB planning hook is called only once\n * per grouping.\n */\nvoid\nplan_add_gapfill(PlannerInfo *root, RelOptInfo *group_rel)\n{\n\tListCell *lc;\n\tQuery *parse = root->parse;\n\tgapfill_walker_context context = { .call.node = NULL, .count = 0 };\n\n\tif (CMD_SELECT != parse->commandType || parse->groupClause == NIL)\n\t\treturn;\n\n\t/*\n\t * Look for time_bucket_gapfill function call in the target list, which\n\t * will succeed on every call to plan_add_gapfill, thus it will lead to\n\t * incorrect query plan if plan_add_gapfill is called more than once per\n\t * grouping.\n\t */\n\tgapfill_function_walker((Node *) parse->targetList, &context);\n\n\tif (context.count == 0)\n\t\treturn;\n\n\tif (context.count > 1)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"multiple time_bucket_gapfill calls not allowed\")));\n\n\t/*\n\t * Check for non-constant timezone parameter. Gapfill needs a consistent\n\t * timezone to generate gap timestamps, so column references and\n\t * subqueries are not supported.\n\t *\n\t * The timezone variant has 5 arguments after PostgreSQL fills in\n\t * defaults: (bucket_width, ts, timezone, start, finish). The\n\t * non-timezone variant has 4: (bucket_width, ts, start, finish).\n\t */\n\tFuncExpr *func = context.call.func;\n\tint nargs = list_length(func->args);\n\tif (nargs == 5)\n\t{\n\t\tExpr *tz_arg = lthird(func->args);\n\t\tif (contains_nonconstant_expr((Node *) tz_arg))\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"time_bucket_gapfill does not support non-constant timezone\"),\n\t\t\t\t\t errhint(\"Use a constant timezone value.\")));\n\t}\n\tList *copy = group_rel->pathlist;\n\tgroup_rel->pathlist = NIL;\n\tgroup_rel->cheapest_total_path = NULL;\n\tgroup_rel->cheapest_startup_path = NULL;\n\tgroup_rel->cheapest_unique_path = NULL;\n\n\t/*\n\t * cheapest_parameterized_paths will be rebuilt by set_cheapest()\n\t * after this hook returns. We must not delete ppilist as it contains\n\t * ParamPathInfo entries needed for parameterized paths (e.g. LATERAL).\n\t */\n\tlist_free(group_rel->cheapest_parameterized_paths);\n\tgroup_rel->cheapest_parameterized_paths = NULL;\n\n\tforeach (lc, copy)\n\t{\n\t\tadd_path(group_rel, gapfill_path_create(root, lfirst(lc), context.call.func));\n\t}\n\tlist_free(copy);\n}\n\nstatic inline bool\nis_gapfill_path(Path *path)\n{\n\treturn IsA(path, CustomPath) && castNode(CustomPath, path)->methods == &gapfill_path_methods;\n}\n\n/*\n * Since we construct the targetlist for the gapfill node from the\n * final targetlist we need to adjust any intermediate targetlists\n * between toplevel window agg node and gapfill node. This adjustment\n * is only necessary if multiple WindowAgg nodes are present.\n * In that case we need to adjust the targetlists of nodes between\n * toplevel WindowAgg node and Gapfill node\n *\n * Gapfill plan with multiple WindowAgg nodes:\n *\n *  WindowAgg\n *    ->  WindowAgg\n *          ->  Custom Scan (GapFill)\n *                ->  Sort\n *                      Sort Key: (time_bucket_gapfill(1, \"time\"))\n *                      ->  HashAggregate\n *                            Group Key: time_bucket_gapfill(1, \"time\")\n *                            ->  Seq Scan on metrics_int\n *\n */\nvoid\ngapfill_adjust_window_targetlist(PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *output_rel)\n{\n\tListCell *lc;\n\n\tif (!is_gapfill_path(linitial(input_rel->pathlist)))\n\t\treturn;\n\n\tforeach (lc, output_rel->pathlist)\n\t{\n\t\tWindowAggPath *toppath = lfirst(lc);\n\n\t\t/*\n\t\t * the toplevel WindowAggPath has the highest index. If winref is\n\t\t * 1 we only have one WindowAggPath if its greater then 1 then there\n\t\t * are multiple WindowAgg nodes.\n\t\t *\n\t\t * we skip toplevel WindowAggPath because targetlist of toplevel WindowAggPath\n\t\t * is our starting point for building gapfill targetlist so we don't need to\n\t\t * adjust the toplevel targetlist\n\t\t */\n\t\tif (IsA(toppath, WindowAggPath) && toppath->winclause->winref > 1)\n\t\t{\n\t\t\tWindowAggPath *path;\n\n\t\t\tfor (path = (WindowAggPath *) toppath->subpath; IsA(path, WindowAggPath);\n\t\t\t\t path = (WindowAggPath *) path->subpath)\n\t\t\t{\n\t\t\t\tPathTarget *pt_top = toppath->path.pathtarget;\n\t\t\t\tPathTarget *pt;\n\t\t\t\tListCell *lc_expr;\n\t\t\t\tint i = -1;\n\n\t\t\t\tpt = create_empty_pathtarget();\n\t\t\t\t/*\n\t\t\t\t * for each child we build targetlist based on top path\n\t\t\t\t * targetlist\n\t\t\t\t */\n\t\t\t\tforeach (lc_expr, pt_top->exprs)\n\t\t\t\t{\n\t\t\t\t\tgapfill_walker_context context;\n\t\t\t\t\ti++;\n\n\t\t\t\t\tgapfill_expression_walker(lfirst(lc_expr), window_function_walker, &context);\n\n\t\t\t\t\t/*\n\t\t\t\t\t * we error out on multiple window functions per resultset column\n\t\t\t\t\t * when building gapfill node targetlist so we only assert here\n\t\t\t\t\t */\n\t\t\t\t\tAssert(context.count <= 1);\n\n\t\t\t\t\tif (context.count == 1)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (context.call.window->winref <= path->winclause->winref)\n\t\t\t\t\t\t\t/*\n\t\t\t\t\t\t\t * window function of current level or below\n\t\t\t\t\t\t\t * so we can put in verbatim\n\t\t\t\t\t\t\t */\n\t\t\t\t\t\t\tadd_column_to_pathtarget(pt, lfirst(lc_expr), pt_top->sortgrouprefs[i]);\n\t\t\t\t\t\telse if (context.call.window->args != NIL)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tListCell *lc_arg;\n\t\t\t\t\t\t\tif (list_length(context.call.window->args) > 1)\n\t\t\t\t\t\t\t\t/*\n\t\t\t\t\t\t\t\t * check arguments past first argument dont have Vars\n\t\t\t\t\t\t\t\t */\n\t\t\t\t\t\t\t\tfor (lc_arg = lnext(context.call.window->args,\n\t\t\t\t\t\t\t\t\t\t\t\t\tlist_head(context.call.window->args));\n\t\t\t\t\t\t\t\t\t lc_arg != NULL;\n\t\t\t\t\t\t\t\t\t lc_arg = lnext(context.call.window->args, lc_arg))\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tif (contain_var_clause(lfirst(lc_arg)))\n\t\t\t\t\t\t\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t\t\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t\t\t\t\t\t\t\t errmsg(\"window functions with multiple column \"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"references not supported\")));\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (contain_var_clause(linitial(context.call.window->args)))\n\t\t\t\t\t\t\t\tadd_column_to_pathtarget(pt,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t linitial(context.call.window->args),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t pt_top->sortgrouprefs[i]);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\tadd_column_to_pathtarget(pt, lfirst(lc_expr), pt_top->sortgrouprefs[i]);\n\t\t\t\t}\n\t\t\t\tpath->path.pathtarget = pt;\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "tsl/src/nodes/gapfill/interpolate.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <access/htup_details.h>\n#include <catalog/pg_type.h>\n#include <utils/builtins.h>\n#include <utils/datum.h>\n#include <utils/numeric.h>\n#include <utils/typcache.h>\n\n#include \"compat/compat.h\"\n#include \"gapfill_internal.h\"\n#include \"interpolate.h\"\n\n#define INTERPOLATE(x, x0, x1, y0, y1) (((y0) * ((x1) - (x)) + (y1) * ((x) - (x0))) / ((x1) - (x0)))\n\n/*\n * gapfill_interpolate_initialize gets called when plan is initialized for every interpolate column\n */\nvoid\ngapfill_interpolate_initialize(GapFillInterpolateColumnState *interpolate, GapFillState *state,\n\t\t\t\t\t\t\t   FuncExpr *function)\n{\n\tinterpolate->prev.isnull = true;\n\tinterpolate->next.isnull = true;\n\tif (list_length(function->args) > 1)\n\t\tinterpolate->lookup_before = gapfill_adjust_varnos(state, lsecond(function->args));\n\tif (list_length(function->args) > 2)\n\t\tinterpolate->lookup_after = gapfill_adjust_varnos(state, lthird(function->args));\n}\n\n/*\n * gapfill_interpolate_group_change gets called when a new aggregation group becomes active\n */\nvoid\ngapfill_interpolate_group_change(GapFillInterpolateColumnState *column, int64 time, Datum value,\n\t\t\t\t\t\t\t\t bool isnull)\n{\n\tcolumn->prev.isnull = true;\n\tcolumn->next.isnull = isnull;\n\tif (!isnull)\n\t{\n\t\tcolumn->next.time = time;\n\t\tcolumn->next.value = datumCopy(value, column->base.typbyval, column->base.typlen);\n\t}\n}\n\n/*\n * gapfill_interpolate_tuple_fetched gets called when a new tuple is fetched from subplan\n */\nvoid\ngapfill_interpolate_tuple_fetched(GapFillInterpolateColumnState *column, int64 time, Datum value,\n\t\t\t\t\t\t\t\t  bool isnull)\n{\n\tcolumn->next.isnull = isnull;\n\tif (!isnull)\n\t{\n\t\tcolumn->next.time = time;\n\t\tcolumn->next.value = datumCopy(value, column->base.typbyval, column->base.typlen);\n\t}\n}\n\n/*\n * gapfill_interpolate_tuple_returned gets called when subplan tuple is returned\n */\nvoid\ngapfill_interpolate_tuple_returned(GapFillInterpolateColumnState *column, int64 time, Datum value,\n\t\t\t\t\t\t\t\t   bool isnull)\n{\n\tcolumn->next.isnull = true;\n\tcolumn->prev.isnull = isnull;\n\tif (!isnull)\n\t{\n\t\tcolumn->prev.time = time;\n\t\tcolumn->prev.value = datumCopy(value, column->base.typbyval, column->base.typlen);\n\t}\n}\n\n/*\n * Do out of bounds lookup for interpolation\n */\nstatic void\ngapfill_fetch_sample(GapFillState *state, GapFillInterpolateColumnState *column,\n\t\t\t\t\t GapFillInterpolateSample *sample, Expr *lookup)\n{\n\tHeapTupleHeader th;\n\tHeapTupleData tuple;\n\tTupleDesc tupdesc;\n\tDatum value;\n\tbool isnull;\n\tDatum datum = gapfill_exec_expr(state, state->scanslot, lookup, &isnull);\n\n\tif (isnull)\n\t{\n\t\tsample->isnull = true;\n\t\treturn;\n\t}\n\n\tth = DatumGetHeapTupleHeader(datum);\n\tif (HeapTupleHeaderGetNatts(th) != 2)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"interpolate RECORD arguments must have 2 elements\")));\n\n\t/* Extract type information from the tuple itself */\n\tAssert(RECORDOID == HeapTupleHeaderGetTypeId(th));\n\ttupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(th), HeapTupleHeaderGetTypMod(th));\n\n\t/* Build a temporary HeapTuple control structure */\n\ttuple.t_len = HeapTupleHeaderGetDatumLength(th);\n\tItemPointerSetInvalid(&(tuple.t_self));\n\ttuple.t_tableOid = InvalidOid;\n\ttuple.t_data = th;\n\n\t/* check first element in record matches timestamp datatype */\n\tif (TupleDescAttr(tupdesc, 0)->atttypid != state->columns[state->time_index]->typid)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"first argument of interpolate returned record must match used timestamp \"\n\t\t\t\t\t\t\"datatype\"),\n\t\t\t\t errdetail(\"Returned type %s does not match expected type %s.\",\n\t\t\t\t\t\t   format_type_be(TupleDescAttr(tupdesc, 0)->atttypid),\n\t\t\t\t\t\t   format_type_be(column->base.typid))));\n\n\t/* check second element in record matches interpolate datatype */\n\tif (TupleDescAttr(tupdesc, 1)->atttypid != column->base.typid)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"second argument of interpolate returned record must match used \"\n\t\t\t\t\t\t\"interpolate datatype\"),\n\t\t\t\t errdetail(\"Returned type %s does not match expected type %s.\",\n\t\t\t\t\t\t   format_type_be(TupleDescAttr(tupdesc, 1)->atttypid),\n\t\t\t\t\t\t   format_type_be(column->base.typid))));\n\n\tvalue = heap_getattr(&tuple, 1, tupdesc, &sample->isnull);\n\tif (!sample->isnull)\n\t{\n\t\tsample->time = gapfill_datum_get_internal(value, state->gapfill_typid);\n\n\t\tvalue = heap_getattr(&tuple, 2, tupdesc, &sample->isnull);\n\t\tif (!sample->isnull)\n\t\t\tsample->value = datumCopy(value, column->base.typbyval, column->base.typlen);\n\t}\n\n\tReleaseTupleDesc(tupdesc);\n}\n\n/* Calculate the interpolation using numerics, returning the result as a numeric datum */\nstatic Datum\ninterpolate_numeric(int64 x_i, int64 x0_i, int64 x1_i, Datum y0, Datum y1)\n{\n\tDatum x0 = DirectFunctionCall1(int8_numeric, Int64GetDatum(x0_i));\n\tDatum x1 = DirectFunctionCall1(int8_numeric, Int64GetDatum(x1_i));\n\tDatum x = DirectFunctionCall1(int8_numeric, Int64GetDatum(x_i));\n\n\tDatum x1_sub_x = DirectFunctionCall2(numeric_sub, x1, x);\n\tDatum x_sub_x0 = DirectFunctionCall2(numeric_sub, x, x0);\n\tDatum y0_mul_x1_sub_x = DirectFunctionCall2(numeric_mul, y0, x1_sub_x);\n\tDatum y1_mul_x_sub_x0 = DirectFunctionCall2(numeric_mul, y1, x_sub_x0);\n\n\tDatum numerator = DirectFunctionCall2(numeric_add, y0_mul_x1_sub_x, y1_mul_x_sub_x0);\n\tDatum denominator = DirectFunctionCall2(numeric_sub, x1, x0);\n\n\treturn DirectFunctionCall2(numeric_div, numerator, denominator);\n}\n\n/*\n * gapfill_interpolate_calculate gets called for every gapfilled tuple to calculate values\n *\n * Calculate linear interpolation value\n * y = (y0(x1-x) + y1(x-x0))/(x1-x0)\n */\nvoid\ngapfill_interpolate_calculate(GapFillInterpolateColumnState *column, GapFillState *state,\n\t\t\t\t\t\t\t  int64 time, Datum *value, bool *isnull)\n{\n\tint64 x, x0, x1;\n\tDatum y0, y1;\n\n\t/* only evaluate expr for first tuple */\n\tif (column->prev.isnull && column->lookup_before && time == state->gapfill_start)\n\t\tgapfill_fetch_sample(state, column, &column->prev, column->lookup_before);\n\n\tif (column->next.isnull && column->lookup_after &&\n\t\t(FETCHED_LAST == state->state || FETCHED_NEXT_GROUP == state->state))\n\t\tgapfill_fetch_sample(state, column, &column->next, column->lookup_after);\n\n\t*isnull = column->prev.isnull || column->next.isnull;\n\tif (*isnull)\n\t\treturn;\n\n\ty0 = column->prev.value;\n\ty1 = column->next.value;\n\n\tx = time;\n\tx0 = column->prev.time;\n\tx1 = column->next.time;\n\n\tswitch (column->base.typid)\n\t{\n\t\t/* All integer types must use numeric-based interpolation calculations since they are\n\t\t * multiplied by int64 and this could cause an overflow. numerics also interpolate better\n\t\t * because the answer is rounded and not truncated. We can't use float8 because that\n\t\t doesn't handle really big ints exactly. We can't use the Postgres INT128 implementation\n\t\t because it doesn't support division. */\n\t\tcase INT2OID:\n\t\t\t*value =\n\t\t\t\tDirectFunctionCall1(numeric_int2,\n\t\t\t\t\t\t\t\t\tinterpolate_numeric(x,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tx0,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tx1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tDirectFunctionCall1(int2_numeric, y0),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tDirectFunctionCall1(int2_numeric, y1)));\n\t\t\tbreak;\n\t\tcase INT4OID:\n\t\t\t*value =\n\t\t\t\tDirectFunctionCall1(numeric_int4,\n\t\t\t\t\t\t\t\t\tinterpolate_numeric(x,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tx0,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tx1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tDirectFunctionCall1(int4_numeric, y0),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tDirectFunctionCall1(int4_numeric, y1)));\n\t\t\tbreak;\n\t\tcase INT8OID:\n\t\t\t*value =\n\t\t\t\tDirectFunctionCall1(numeric_int8,\n\t\t\t\t\t\t\t\t\tinterpolate_numeric(x,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tx0,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tx1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tDirectFunctionCall1(int8_numeric, y0),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tDirectFunctionCall1(int8_numeric, y1)));\n\t\t\tbreak;\n\t\tcase FLOAT4OID:\n\t\t\t/* Shortcircuit calculation when y0 == y1 for float because otherwise\n\t\t\t * output will be unstable for certain values due to float rounding. */\n\t\t\tif (DatumGetFloat4(y0) == DatumGetFloat4(y1))\n\t\t\t\t*value = y0;\n\t\t\telse\n\t\t\t\t*value =\n\t\t\t\t\tFloat4GetDatum(INTERPOLATE(x, x0, x1, DatumGetFloat4(y0), DatumGetFloat4(y1)));\n\t\t\tbreak;\n\t\tcase FLOAT8OID:\n\t\t\t/* Shortcircuit calculation when y0 == y1 for float because otherwise\n\t\t\t * output will be unstable for certain values due to float rounding. */\n\t\t\tif (DatumGetFloat8(y0) == DatumGetFloat8(y1))\n\t\t\t\t*value = y0;\n\t\t\telse\n\t\t\t\t*value =\n\t\t\t\t\tFloat8GetDatum(INTERPOLATE(x, x0, x1, DatumGetFloat8(y0), DatumGetFloat8(y1)));\n\t\t\tbreak;\n\t\tdefault:\n\n\t\t\t/*\n\t\t\t * should never happen since interpolate is not defined for other\n\t\t\t * datatypes\n\t\t\t */\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"unsupported datatype for interpolate: %s\",\n\t\t\t\t\t\t\tformat_type_be(column->base.typid))));\n\t\t\tpg_unreachable();\n\t\t\tbreak;\n\t}\n}\n"
  },
  {
    "path": "tsl/src/nodes/gapfill/interpolate.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include \"gapfill_internal.h\"\n\ntypedef struct GapFillInterpolateSample\n{\n\tint64 time;\n\tDatum value;\n\tbool isnull;\n} GapFillInterpolateSample;\n\ntypedef struct GapFillInterpolateColumnState\n{\n\tGapFillColumnState base;\n\tExpr *lookup_before;\n\tExpr *lookup_after;\n\tGapFillInterpolateSample prev;\n\tGapFillInterpolateSample next;\n} GapFillInterpolateColumnState;\n\nvoid gapfill_interpolate_initialize(GapFillInterpolateColumnState *, GapFillState *, FuncExpr *);\nvoid gapfill_interpolate_group_change(GapFillInterpolateColumnState *, int64, Datum, bool);\nvoid gapfill_interpolate_tuple_fetched(GapFillInterpolateColumnState *, int64, Datum, bool);\nvoid gapfill_interpolate_tuple_returned(GapFillInterpolateColumnState *, int64, Datum, bool);\nvoid gapfill_interpolate_calculate(GapFillInterpolateColumnState *, GapFillState *, int64, Datum *,\n\t\t\t\t\t\t\t\t   bool *);\n"
  },
  {
    "path": "tsl/src/nodes/gapfill/locf.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <catalog/pg_type.h>\n#include <utils/datum.h>\n\n#include \"gapfill_internal.h\"\n#include \"locf.h\"\n\n/*\n * gapfill_locf_initialize gets called when plan is initialized for every locf column\n */\nvoid\ngapfill_locf_initialize(GapFillLocfColumnState *locf, GapFillState *state, FuncExpr *function)\n{\n\tlocf->isnull = true;\n\n\t/* check if out of boundary lookup expression was supplied */\n\tif (list_length(function->args) > 1)\n\t\tlocf->lookup_last = gapfill_adjust_varnos(state, lsecond(function->args));\n\n\t/* check if treat_null_as_missing was supplied */\n\tif (list_length(function->args) > 2)\n\t{\n\t\tConst *treat_null_as_missing = lthird(function->args);\n\t\tif (!IsA(treat_null_as_missing, Const) || treat_null_as_missing->consttype != BOOLOID)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\n\t\t\t\t\t\t \"invalid locf argument: treat_null_as_missing must be a BOOL literal\")));\n\t\tif (!treat_null_as_missing->constisnull)\n\t\t\tlocf->treat_null_as_missing = DatumGetBool(treat_null_as_missing->constvalue);\n\t}\n}\n\n/*\n * gapfill_locf_group_change gets called when a new aggregation group becomes active\n */\nvoid\ngapfill_locf_group_change(GapFillLocfColumnState *locf)\n{\n\tlocf->isnull = true;\n}\n\n/*\n * gapfill_locf_tuple_returned gets called when subplan tuple is returned\n */\nvoid\ngapfill_locf_tuple_returned(GapFillLocfColumnState *locf, Datum value, bool isnull)\n{\n\tlocf->isnull = isnull;\n\tif (!isnull)\n\t\tlocf->value = datumCopy(value, locf->base.typbyval, locf->base.typlen);\n}\n\n/*\n * gapfill_locf_calculate gets called for every gapfilled tuple to calculate values\n */\nvoid\ngapfill_locf_calculate(GapFillLocfColumnState *locf, GapFillState *state, TupleTableSlot *ecxt_slot,\n\t\t\t\t\t   int64 time, Datum *value, bool *isnull)\n{\n\t/* only evaluate expr for first tuple */\n\tif (locf->isnull && locf->lookup_last && time == state->gapfill_start)\n\t\tlocf->value = gapfill_exec_expr(state, ecxt_slot, locf->lookup_last, &locf->isnull);\n\n\t*value = locf->value;\n\t*isnull = locf->isnull;\n}\n"
  },
  {
    "path": "tsl/src/nodes/gapfill/locf.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include \"gapfill_internal.h\"\n\ntypedef struct GapFillLocfColumnState\n{\n\tGapFillColumnState base;\n\tExpr *lookup_last;\n\tDatum value;\n\tbool isnull;\n\tbool treat_null_as_missing;\n} GapFillLocfColumnState;\n\nvoid gapfill_locf_initialize(GapFillLocfColumnState *, GapFillState *, FuncExpr *);\nvoid gapfill_locf_group_change(GapFillLocfColumnState *);\nvoid gapfill_locf_tuple_returned(GapFillLocfColumnState *, Datum, bool);\nvoid gapfill_locf_calculate(GapFillLocfColumnState *, GapFillState *, TupleTableSlot *, int64,\n\t\t\t\t\t\t\tDatum *, bool *);\n"
  },
  {
    "path": "tsl/src/nodes/skip_scan/CMakeLists.txt",
    "content": "set(SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/planner.c\n            ${CMAKE_CURRENT_SOURCE_DIR}/exec.c)\ntarget_sources(${TSL_LIBRARY_NAME} PRIVATE ${SOURCES})\n"
  },
  {
    "path": "tsl/src/nodes/skip_scan/README.md",
    "content": "# SkipScan #\n\nThis module implements SkipScan; an optimization for `SELECT DISTINCT ON`.\nUsually for `SELECT DISTINCT ON` Postgres will plan either a `UNIQUE` over a\nsorted path, or some form of aggregate. In either case, it needs to scan the\nentire table, even in cases where there are only a few unique values.\n\nA skip scan optimizes this case when we have an ordered index. Instead of\nscanning the entire table and deduplicating after, the scan remembers the last\nvalue returned, and searches the index for the next value after that one. This\nmeans that for a table with `k` keys, with `u` distinct values, a skip scan runs\nin time `u * log(k)` as opposed to scanning then deduplicating, which takes time\n`k`. We can write the number of unique values `u` as of function of `k` by\ndividing by the number of repeats `r` i.e. `u = k/r` this means that a skip scan\nwill be faster if each key is repeated more than a logarithmic number of times,\ni.e. if `r > log(k)` then `u * log(k) < k/log(k) * log(k) < k`.\n\n\n## Implementation ##\n\nWe plan our skip scan with a tree something like\n\n```SQL\nCustom Scan (SkipScan) on table\n   ->  Index Scan using table_key_idx on table\n       Index Cond: (key > NULL)\n```\n\nAfter each iteration through the `SkipScan` we replace the `key > NULL` with\na `key > [next value we are returning]` and restart the underlying `IndexScan`.\nThere are some subtleties around `NULL` handling, see the source file for more\ndetail.\n\n\n## Planning Heuristics ##\n\nTo plan our SkipScan we look for a compatible plan, for instance\n\n```SQL\nUnique\n   ->  Index Scan\n```\n\nor\n\n```SQL\nUnique\n   ->  Merge Append\n         ->  Index Scan\n         ...\n```\n\ngiven such a plan, we know the index is sorted in an order with the distinct\nkey(s) first, so we can add quals to the `IndexScan` representing the previous\nkey returned, and thus skip over the repeated values. The `Unique` node tells us\nwhich columns are relevant.\n\nWe use this to create plans that look like\n\n```SQL\nUnique\n  ->  Custom Scan (SkipScan) on skip_scan\n        ->  Index Scan using skip_scan_dev_name_idx on skip_scan\n```\n\nor\n\n```SQL\nUnique\n  ->  Merge Append\n        Sort Key: _hyper_2_1_chunk.dev_name\n        ->  Custom Scan (SkipScan) on _hyper_2_1_chunk\n              ->  Index Scan using _hyper_2_1_chunk_idx on _hyper_2_1_chunk\n        ->  Custom Scan (SkipScan) on _hyper_2_2_chunk\n              ->  Index Scan using _hyper_2_2_chunk_idx on _hyper_2_2_chunk\n```\n\nrespectively. While we could remove the top-level Unique node for the single\nchunk/normal table case we keep it so we don't need to support projection\nas postgres won't modify the SkipScan targetlist that way.\n\n## Postgres-Native Skip Scan ##\n\nUpstream postgres is also working on a skip scan implementation, see e.g.\nhttps://commitfest.postgresql.org/32/1741/\nAs when this document was first written, it is not yet merged. Their strategy\ninvolves integrating this functionality into the btree searching code,\nand will be available in PG15 at the earliest. The two\nimplementations should not interfere with eachother.\n"
  },
  {
    "path": "tsl/src/nodes/skip_scan/exec.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * SkipScan is an optimized form of SELECT DISTINCT ON (column)\n * Conceptually, a SkipScan is a regular IndexScan with an additional skip-qual like\n *     WHERE column > [previous value of column]\n *\n * Implementing this qual is complicated by two factors:\n *   1. The first time through the SkipScan there is no previous value for the\n *      DISTINCT column.\n *   2. NULL values don't behave nicely with ordering operators.\n *\n * To get around these issues, we have to special case those two cases. All in\n * all, the SkipScan's state machine evolves according to the following flowchart\n *\n *                        start\n *                          |\n *          +================================+\n *          | search for NULL if NULLS FIRST |\n *          +================================+\n *                  |\n *                  v\n *  +=====================+          +==============================+\n *  | search for non-NULL |--found-->| search for values after prev |\n *  +=====================+  value   +==============================+\n *                  |                   |\n *                  v                   v\n *           +================================+\n *           | search for NULL if NULLS LAST  |\n *           +================================+\n *                          |\n *                          v\n *                    /===========\\\n *                    |   DONE    |\n *                    \\===========/\n *\n *\n *\n * N-key SkipScan needs to do 2^N null check stages when using the above scheme,\n * made even more complicated with having to change searches for previous keys.\n *\n * So we made a decision to support multikey SkipScan in NOT NULL mode only.\n *\n * For N-key SkipScan we search with these predicates when current key = K:\n * (key_1 = prev_1),...,(key_K > prev_K),(key_K+1 IS NOT NULL)...(key_N IS NOT NULL)\n *\n * As all skip keys are NOT NULL, \"IS NOT NULL\" fetches the tuple with no previous value.\n *\n * We start the search with K=1 i.e. with these predicates:\n * (key_1 IS NOT NULL),...,(key_N IS NOT NULL).\n *\n * When a tuple is fetched we set  K=N as we can fill all previous values, search is now:\n * (key_1 = prev_1),...,(key_N > prev_N)\n *\n * When no tuple is fetched and K>1 we can relax the search and move to previous key (K-1):\n * (key_1 = prev_1),...,(key_K-1 > prev_K-1),(key_K IS NOT NULL)...(key_N IS NOT NULL)\n *\n * When no tuple is fetched and K=1, we are done.\n *\n * Multikey SkipScan flowchart:\n *                   start (K=1)\n *                      |         +---------+\n *                      |         |         |\n *                      v         v         |\n *      +=================================+ |\n *      |   search for NOT NULL after K   | |\n *      +=================================+ |\n *            |                          |  |\n *            | found value              |  |\n *            v                          |  |\n *  +==============================+     |  |\n *  | search for values after prev |     |  |\n *  +==============================+     |  |\n *                       |               |  |\n *                       |   no value    |  |\n *                       v               v  |\n *                 +======================+ |\n *                 | K=1         | K>1      |\n *                 v             v          |\n *            /===========\\   +=========+   |\n *            |   DONE    |   | K = K-1 |---+\n *            \\===========/   +=========+\n *\n */\n\n#include <postgres.h>\n#include <access/genam.h>\n#include <access/nbtree.h>\n#include <nodes/extensible.h>\n#include <nodes/pg_list.h>\n#include <utils/datum.h>\n\n#include \"guc.h\"\n#include \"nodes/columnar_scan/columnar_scan.h\"\n#include \"nodes/columnar_scan/exec.h\"\n#include \"nodes/skip_scan/skip_scan.h\"\n\ntypedef enum SkipScanStage\n{\n\tSS_BEGIN = 0,\n\tSS_NULLS_FIRST,\n\tSS_NOT_NULL,\n\tSS_VALUES,\n\tSS_NULLS_LAST,\n\tSS_PREV_KEY,\n\tSS_END,\n} SkipScanStage;\n\ntypedef struct SkipKeyData\n{\n\tScanKey skip_key;\n\n\t/* Comparison value filled in at runtime */\n\tDatum prev_datum;\n\tbool prev_is_null;\n\n\t/* Info about the type we are performing DISTINCT on */\n\tbool distinct_by_val;\n\tint distinct_col_attnum;\n\tint distinct_typ_len;\n\tint sk_attno;\n\tSkipKeyNullStatus nulls;\n} SkipKeyData;\n\ntypedef struct SkipScanState\n{\n\tCustomScanState cscan_state;\n\tIndexScanDesc *scan_desc;\n\tMemoryContext ctx;\n\n\t/* Interior Index(Only)Scan the SkipScan runs over */\n\tScanState *idx;\n\n\t/* Pointers into the Index(Only)Scan */\n\tint *num_scan_keys;\n\tScanKey *scan_keys;\n\n\tint num_skip_keys;\n\tSkipKeyData *skip_keys;\n\n\t/* Skip key with \">\" qual, coming after \"=\" skip quals for multikey SkipScan */\n\tint current_key;\n\n\t/* For Multikey SkipScan we keep copies of \"sk_func\" for \"=\" and \">\" for keys 1..N-1\n\t * to be swapped during execution.\n\t */\n\tFmgrInfo *eq_funcs;\n\t/* Will be filled after IndexScan scankeys have been initialized */\n\tFmgrInfo *comp_funcs;\n\tStrategyNumber *comp_strategies;\n\n\tSkipScanStage stage;\n\n\t/* rescan required before getting next tuple */\n\tbool needs_rescan;\n\n\t/* child_plan node is the input for skip scan plan node.\n\t * if skip scan is directly over index scan, child_plan = idx_scan\n\t * if skip scan is over compressed chunk,\n\t * idx_scan = compressed index scan,\n\t * child_plan = decompressed input into skip scan\n\t */\n\tPlan *child_plan;\n\tvoid *idx_scan;\n} SkipScanState;\n\nstatic bool has_nulls_first(SkipScanState *state);\nstatic bool has_nulls_last(SkipScanState *state);\nstatic void skip_scan_rescan_index(SkipScanState *state);\nstatic void skip_scan_switch_stage(SkipScanState *state, SkipScanStage new_stage);\n\nstatic void\nskip_scan_begin(CustomScanState *node, EState *estate, int eflags)\n{\n\tSkipScanState *state = (SkipScanState *) node;\n\tstate->ctx = AllocSetContextCreate(estate->es_query_cxt, \"skipscan\", ALLOCSET_DEFAULT_SIZES);\n\n\tnode->custom_ps = list_make1((ScanState *) ExecInitNode(state->child_plan, estate, eflags));\n\tScanState *child_state = linitial(node->custom_ps);\n\tif (state->child_plan == state->idx_scan)\n\t{\n\t\tstate->idx = child_state;\n\t}\n\telse if (IsA(child_state, CustomScanState))\n\t{\n\t\tAssert(ts_is_columnar_scan_plan(state->child_plan));\n\t\tstate->idx = linitial(castNode(CustomScanState, child_state)->custom_ps);\n\t}\n\telse\n\t\telog(ERROR, \"unknown subscan type in SkipScan\");\n\n\tif (IsA(state->idx_scan, IndexScan))\n\t{\n\t\tIndexScanState *idx = castNode(IndexScanState, state->idx);\n\t\tstate->scan_keys = &idx->iss_ScanKeys;\n\t\tstate->num_scan_keys = &idx->iss_NumScanKeys;\n\t\tstate->scan_desc = &idx->iss_ScanDesc;\n\t}\n\telse if (IsA(state->idx_scan, IndexOnlyScan))\n\t{\n\t\tIndexOnlyScanState *idx = castNode(IndexOnlyScanState, state->idx);\n\t\tstate->scan_keys = &idx->ioss_ScanKeys;\n\t\tstate->num_scan_keys = &idx->ioss_NumScanKeys;\n\t\tstate->scan_desc = &idx->ioss_ScanDesc;\n\t}\n\telse\n\t\telog(ERROR, \"unknown subscan type in SkipScan\");\n\n\t/* scankeys are not setup for explain only */\n\tif (eflags & EXEC_FLAG_EXPLAIN_ONLY)\n\t\treturn;\n\n\t/* find position of our skip key\n\t * skip key is put as first key for the respective column in sort_indexquals\n\t */\n\tScanKey scankeydata = *state->scan_keys;\n\tint j = 0;\n\tfor (int i = 0; i < *state->num_scan_keys; i++)\n\t{\n\t\tif (scankeydata[i].sk_flags == SK_ISNULL &&\n\t\t\tscankeydata[i].sk_attno == state->skip_keys[j].sk_attno)\n\t\t{\n\t\t\tSkipKeyData *skipkeydata = &state->skip_keys[j++];\n\t\t\tskipkeydata->skip_key = &scankeydata[i];\n\t\t\t/* Set up \">\" sk_func swaps for skip keys 1..N-1 */\n\t\t\tif (j < state->num_skip_keys)\n\t\t\t{\n\t\t\t\tstate->comp_strategies[j - 1] = scankeydata[i].sk_strategy;\n\t\t\t\tfmgr_info_copy(&state->comp_funcs[j - 1],\n\t\t\t\t\t\t\t   &scankeydata[i].sk_func,\n\t\t\t\t\t\t\t   CurrentMemoryContext);\n\t\t\t}\n\t\t\tif (j == state->num_skip_keys)\n\t\t\t\tbreak;\n\t\t}\n\t}\n\tif (j < state->num_skip_keys)\n\t\telog(ERROR, \"ScanKey for skip qual not found\");\n\n\t/* when we fetch the 1st tuple we update all skip keys from 0 to N */\n\tstate->current_key = 0;\n}\n\nstatic bool\nhas_nulls_first(SkipScanState *state)\n{\n\treturn state->skip_keys[0].nulls == SK_NULLS_FIRST;\n}\n\nstatic bool\nhas_nulls_last(SkipScanState *state)\n{\n\treturn state->skip_keys[0].nulls == SK_NULLS_LAST;\n}\n\nstatic void\nskip_scan_rescan_index(SkipScanState *state)\n{\n\t/* if the scan in the child scan has not been\n\t * setup yet which is true before the first tuple\n\t * has been retrieved from child scan we cannot\n\t * trigger rescan but since the child scan\n\t * has not been initialized it will pick up\n\t * any ScanKey changes we did */\n\tif (*state->scan_desc)\n\t{\n\t\tindex_rescan(*state->scan_desc,\n\t\t\t\t\t *state->scan_keys,\n\t\t\t\t\t *state->num_scan_keys,\n\t\t\t\t\t NULL /*orderbys*/,\n\t\t\t\t\t 0 /*norderbys*/);\n\n\t\t/* Discard current compressed index tuple as we are ready to move to the next compressed\n\t\t * tuple via SkipScan */\n\t\tScanState *child = linitial(state->cscan_state.custom_ps);\n\t\tif (ts_is_columnar_scan_plan(state->child_plan))\n\t\t{\n\t\t\tColumnarScanState *ds = (ColumnarScanState *) child;\n\t\t\tTupleTableSlot *slot = ds->batch_queue->funcs->top_tuple(ds->batch_queue);\n\t\t\tif (slot)\n\t\t\t{\n\t\t\t\tcompressed_batch_discard_tuples((DecompressBatchState *) slot);\n\t\t\t}\n\t\t}\n\t}\n\tstate->needs_rescan = false;\n}\n\n/*\n * Update skip scankey flags according to stage\n */\nstatic void\nskip_scan_switch_stage(SkipScanState *state, SkipScanStage new_stage)\n{\n\tAssert(new_stage > state->stage || state->num_skip_keys > 1);\n\n\tswitch (new_stage)\n\t{\n\t\tcase SS_NOT_NULL:\n\t\t\tfor (int i = 0; i < state->num_skip_keys; i++)\n\t\t\t{\n\t\t\t\tstate->skip_keys[i].skip_key->sk_flags = SK_ISNULL | SK_SEARCHNOTNULL;\n\t\t\t\tstate->skip_keys[i].skip_key->sk_argument = 0;\n\t\t\t}\n\t\t\tstate->current_key = 0;\n\t\t\tstate->needs_rescan = true;\n\t\t\tbreak;\n\n\t\tcase SS_PREV_KEY:\n\t\t\t/* Done searching with \">\" for this key: set this key to NOT NULL i.e. any value,\n\t\t\t * set previous \"=\" key to search with \">\".\n\t\t\t */\n\t\t\tstate->skip_keys[state->current_key].skip_key->sk_flags = SK_ISNULL | SK_SEARCHNOTNULL;\n\t\t\tstate->current_key--;\n\t\t\tstate->skip_keys[state->current_key].skip_key->sk_flags = 0;\n\t\t\tfmgr_info_copy(&state->skip_keys[state->current_key].skip_key->sk_func,\n\t\t\t\t\t\t   &state->comp_funcs[state->current_key],\n\t\t\t\t\t\t   CurrentMemoryContext);\n\t\t\tstate->skip_keys[state->current_key].skip_key->sk_strategy =\n\t\t\t\tstate->comp_strategies[state->current_key];\n\t\t\tstate->needs_rescan = true;\n\t\t\tbreak;\n\n\t\tcase SS_VALUES:\n\t\t\tfor (int i = 0; i < state->num_skip_keys; i++)\n\t\t\t{\n\t\t\t\tstate->skip_keys[i].skip_key->sk_flags = 0;\n\t\t\t\t/* reset all \">\" back to \"=\" from the current key to N-1 */\n\t\t\t\tif (i >= state->current_key && i < state->num_skip_keys - 1)\n\t\t\t\t{\n\t\t\t\t\tfmgr_info_copy(&state->skip_keys[i].skip_key->sk_func,\n\t\t\t\t\t\t\t\t   &state->eq_funcs[i],\n\t\t\t\t\t\t\t\t   CurrentMemoryContext);\n\t\t\t\t\tstate->skip_keys[i].skip_key->sk_strategy = BTEqualStrategyNumber;\n\t\t\t\t}\n\t\t\t}\n\t\t\tstate->current_key = state->num_skip_keys - 1;\n\t\t\tstate->needs_rescan = true;\n\t\t\tbreak;\n\n\t\tcase SS_NULLS_LAST:\n\t\tcase SS_NULLS_FIRST:\n\t\t\tstate->skip_keys[0].skip_key->sk_flags = SK_ISNULL | SK_SEARCHNULL;\n\t\t\tstate->skip_keys[0].skip_key->sk_argument = 0;\n\t\t\tstate->needs_rescan = true;\n\t\t\tbreak;\n\n\t\tcase SS_BEGIN:\n\t\tcase SS_END:\n\t\t\tbreak;\n\t}\n\n\tstate->stage = new_stage;\n}\n\nstatic void\nskip_scan_update_key(SkipScanState *state, TupleTableSlot *slot)\n{\n\tfor (int i = state->current_key; i < state->num_skip_keys; i++)\n\t{\n\t\tif (!state->skip_keys[i].prev_is_null && !state->skip_keys[i].distinct_by_val)\n\t\t{\n\t\t\tAssert(state->stage == SS_VALUES || state->num_skip_keys > 1);\n\t\t\tpfree(DatumGetPointer(state->skip_keys[i].prev_datum));\n\t\t}\n\n\t\tMemoryContext old_ctx = MemoryContextSwitchTo(state->ctx);\n\t\tstate->skip_keys[i].prev_datum = slot_getattr(slot,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  state->skip_keys[i].distinct_col_attnum,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  &state->skip_keys[i].prev_is_null);\n\t\tif (state->skip_keys[i].prev_is_null)\n\t\t{\n\t\t\tstate->skip_keys[i].skip_key->sk_flags = SK_ISNULL;\n\t\t\tstate->skip_keys[i].skip_key->sk_argument = 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tstate->skip_keys[i].prev_datum = datumCopy(state->skip_keys[i].prev_datum,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   state->skip_keys[i].distinct_by_val,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   state->skip_keys[i].distinct_typ_len);\n\t\t\tstate->skip_keys[i].skip_key->sk_argument = state->skip_keys[i].prev_datum;\n\t\t}\n\t\tMemoryContextSwitchTo(old_ctx);\n\t}\n\t/* we need to do a rescan whenever we modify the ScanKey */\n\tstate->needs_rescan = true;\n}\n\nstatic TupleTableSlot *\nskip_scan_exec(CustomScanState *node)\n{\n\tSkipScanState *state = (SkipScanState *) node;\n\tTupleTableSlot *result;\n\tScanState *child_state;\n\n\t/*\n\t * We are not supporting projection here since no plan\n\t * we generate will need it as our SkipScan node will\n\t * always be below Unique need so our targetlist\n\t * will not get modified by postgres.\n\t */\n\tAssert(!node->ss.ps.ps_ProjInfo);\n\n\twhile (true)\n\t{\n\t\tif (state->needs_rescan)\n\t\t\tskip_scan_rescan_index(state);\n\n\t\tswitch (state->stage)\n\t\t{\n\t\t\tcase SS_BEGIN:\n\t\t\t\tif (has_nulls_first(state))\n\t\t\t\t\tskip_scan_switch_stage(state, SS_NULLS_FIRST);\n\t\t\t\telse\n\t\t\t\t\tskip_scan_switch_stage(state, SS_NOT_NULL);\n\n\t\t\t\tbreak;\n\n\t\t\tcase SS_NULLS_FIRST:\n\t\t\t\tchild_state = linitial(state->cscan_state.custom_ps);\n\t\t\t\tresult = child_state->ps.ExecProcNode(&child_state->ps);\n\n\t\t\t\t/*\n\t\t\t\t * if we found a NULL value we return it, otherwise\n\t\t\t\t * we restart the scan looking for non-NULL\n\t\t\t\t */\n\t\t\t\tskip_scan_switch_stage(state, SS_NOT_NULL);\n\t\t\t\tif (!TupIsNull(result))\n\t\t\t\t\treturn result;\n\n\t\t\t\tbreak;\n\n\t\t\tcase SS_NOT_NULL:\n\t\t\tcase SS_PREV_KEY:\n\t\t\tcase SS_VALUES:\n\t\t\t\tchild_state = linitial(state->cscan_state.custom_ps);\n\t\t\t\tresult = child_state->ps.ExecProcNode(&child_state->ps);\n\n\t\t\t\tif (!TupIsNull(result))\n\t\t\t\t{\n\t\t\t\t\t/*\n\t\t\t\t\t * if we found a tuple we update the skip scan key\n\t\t\t\t\t * and look for values greater than the value we just\n\t\t\t\t\t * found. If this is the first non-NULL value we\n\t\t\t\t\t * also switch stage to look for values greater than\n\t\t\t\t\t * that in subsequent calls.\n\t\t\t\t\t */\n\t\t\t\t\tskip_scan_update_key(state, result);\n\t\t\t\t\tif (state->stage == SS_NOT_NULL || state->stage == SS_PREV_KEY)\n\t\t\t\t\t\tskip_scan_switch_stage(state, SS_VALUES);\n\n\t\t\t\t\treturn result;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t/*\n\t\t\t\t\t * if there are no more values that satisfy\n\t\t\t\t\t * the skip constraint we are either done\n\t\t\t\t\t * for NULLS FIRST ordering or need to check\n\t\t\t\t\t * for NULLs if we have NULLS LAST ordering\n\t\t\t\t\t *\n\t\t\t\t\t * Or we can move back one key for multikey SkipScan to relax the search,\n\t\t\t\t\t * i.e. make current key NOT NULL (any value) and change previous search from\n\t\t\t\t\t * \"=\" to \">\"\n\t\t\t\t\t */\n\t\t\t\t\tif (has_nulls_last(state))\n\t\t\t\t\t\tskip_scan_switch_stage(state, SS_NULLS_LAST);\n\t\t\t\t\telse if (state->current_key > 0)\n\t\t\t\t\t\tskip_scan_switch_stage(state, SS_PREV_KEY);\n\t\t\t\t\telse\n\t\t\t\t\t\tskip_scan_switch_stage(state, SS_END);\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase SS_NULLS_LAST:\n\t\t\t\tchild_state = linitial(state->cscan_state.custom_ps);\n\t\t\t\tresult = child_state->ps.ExecProcNode(&child_state->ps);\n\t\t\t\tskip_scan_switch_stage(state, SS_END);\n\t\t\t\treturn result;\n\t\t\t\tbreak;\n\n\t\t\tcase SS_END:\n\t\t\t\treturn NULL;\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nstatic void\nskip_scan_end(CustomScanState *node)\n{\n\tSkipScanState *state = (SkipScanState *) node;\n\tScanState *child_state = linitial(state->cscan_state.custom_ps);\n\tExecEndNode(&child_state->ps);\n}\n\nstatic void\nskip_scan_rescan(CustomScanState *node)\n{\n\tSkipScanState *state = (SkipScanState *) node;\n\t/* reset stage so we can assert in skip_scan_switch_stage that stage always moves forward */\n\tstate->stage = SS_BEGIN;\n\n\t/* Switching state here instead of in the main loop\n\t * means we dont have to call skip_scan_rescan_index\n\t * as ExecReScan on the child scan takes care of that. */\n\tif (has_nulls_first(state))\n\t\tskip_scan_switch_stage(state, SS_NULLS_FIRST);\n\telse\n\t\tskip_scan_switch_stage(state, SS_NOT_NULL);\n\n\tfor (int i = 0; i < state->num_skip_keys; i++)\n\t{\n\t\tstate->skip_keys[i].prev_is_null = true;\n\t\tstate->skip_keys[i].prev_datum = 0;\n\t}\n\n\tstate->needs_rescan = false;\n\tScanState *child_state = linitial(state->cscan_state.custom_ps);\n\tExecReScan(&child_state->ps);\n\tMemoryContextReset(state->ctx);\n}\n\nstatic CustomExecMethods skip_scan_state_methods = {\n\t.CustomName = \"SkipScanState\",\n\t.BeginCustomScan = skip_scan_begin,\n\t.EndCustomScan = skip_scan_end,\n\t.ExecCustomScan = skip_scan_exec,\n\t.ReScanCustomScan = skip_scan_rescan,\n};\n\nNode *\ntsl_skip_scan_state_create(CustomScan *cscan)\n{\n\tSkipScanState *state = (SkipScanState *) newNode(sizeof(SkipScanState), T_CustomScanState);\n\n\tstate->child_plan = linitial(cscan->custom_plans);\n\tif (ts_is_columnar_scan_plan(state->child_plan))\n\t{\n\t\tCustomScan *csplan = castNode(CustomScan, state->child_plan);\n\t\tstate->idx_scan = linitial(csplan->custom_plans);\n\t}\n\telse\n\t{\n\t\tstate->idx_scan = state->child_plan;\n\t}\n\tstate->stage = SS_BEGIN;\n\n\t/* set up N skipkeyinfos for N skip keys */\n\tList *skinfos = (List *) linitial(cscan->custom_private);\n\tstate->num_skip_keys = list_length(skinfos);\n\tstate->skip_keys = palloc(sizeof(SkipKeyData) * state->num_skip_keys);\n\n\tListCell *lc;\n\tint i = 0;\n\tforeach (lc, skinfos)\n\t{\n\t\tList *skipkeyinfo = (List *) lfirst(lc);\n\n\t\tstate->skip_keys[i].distinct_col_attnum = list_nth_int(skipkeyinfo, SK_DistinctColAttno);\n\t\tstate->skip_keys[i].distinct_by_val = list_nth_int(skipkeyinfo, SK_DistinctByVal);\n\t\tstate->skip_keys[i].distinct_typ_len = list_nth_int(skipkeyinfo, SK_DistinctTypeLen);\n\t\tstate->skip_keys[i].nulls = list_nth_int(skipkeyinfo, SK_NullStatus);\n\t\tAssert(state->num_skip_keys == 1 || state->skip_keys[i].nulls == SK_NOT_NULL);\n\t\tstate->skip_keys[i].sk_attno = list_nth_int(skipkeyinfo, SK_IndexKeyAttno);\n\n\t\tstate->skip_keys[i].prev_is_null = true;\n\t\ti++;\n\t}\n\n\tstate->eq_funcs = NULL;\n\tstate->comp_funcs = NULL;\n\tstate->comp_strategies = NULL;\n\n\t/* set up N-1 equality ops for N skip keys if N>1 */\n\tif (state->num_skip_keys > 1)\n\t{\n\t\t/* Should have a list of N-1 equality op Oids for N skip keys if N>1 */\n\t\tAssert(list_length(cscan->custom_private) == 2);\n\t\tList *eqoids = (List *) lsecond(cscan->custom_private);\n\n\t\tstate->eq_funcs = palloc(sizeof(FmgrInfo) * (state->num_skip_keys - 1));\n\t\tstate->comp_funcs = palloc(sizeof(FmgrInfo) * (state->num_skip_keys - 1));\n\t\tstate->comp_strategies = palloc(sizeof(StrategyNumber) * (state->num_skip_keys - 1));\n\n\t\tint i = 0;\n\t\t/* Set up \"=\" sk_funcs for keys 1..N-1 */\n\t\tforeach (lc, eqoids)\n\t\t{\n\t\t\tOid eqoid = lfirst_oid(lc);\n\t\t\tAssert(OidIsValid(eqoid));\n\t\t\tfmgr_info(eqoid, &state->eq_funcs[i++]);\n\t\t}\n\t\tAssert(i == state->num_skip_keys - 1);\n\t}\n\n\tstate->cscan_state.methods = &skip_scan_state_methods;\n\treturn (Node *) state;\n}\n"
  },
  {
    "path": "tsl/src/nodes/skip_scan/planner.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n#include <access/sysattr.h>\n#include <nodes/extensible.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodeFuncs.h>\n#include <nodes/pathnodes.h>\n#include <optimizer/clauses.h>\n#include <optimizer/cost.h>\n#include <optimizer/optimizer.h>\n#include <optimizer/pathnode.h>\n#include <optimizer/paths.h>\n#include <optimizer/planmain.h>\n#include <optimizer/prep.h>\n#include <optimizer/restrictinfo.h>\n#include <optimizer/tlist.h>\n#include <parser/parse_coerce.h>\n#include <parser/parsetree.h>\n#include <rewrite/rewriteManip.h>\n#include <utils/syscache.h>\n#include <utils/typcache.h>\n\n#include \"compat/compat.h\"\n#include \"guc.h\"\n#include \"nodes/chunk_append/chunk_append.h\"\n#include \"nodes/columnar_scan/columnar_scan.h\"\n#include \"nodes/constraint_aware_append/constraint_aware_append.h\"\n#include \"nodes/skip_scan/skip_scan.h\"\n#include <import/planner.h>\n\n#include <math.h>\n\ntypedef struct SkipKeyInfo\n{\n\t/* Index clause which we'll use to skip past elements we've already seen */\n\tRestrictInfo *skip_clause;\n\n\t/* Is this key guaranteed to be not null? */\n\tbool notnull;\n\n\t/* attribute number of the distinct column on the table/chunk which provides comparison value\n\t * for Skip qual */\n\tAttrNumber distinct_attno;\n\n\t/* attribute number of the Skip qual comparison column on the indexed table/chunk\n\t * \"indexed_column_attno = distinct_attno\" for (SkipScan <- Index Scan) scenario,\n\t * it can be different for (SkipScan <- ColumnarScan <- compressed Index Scan) scenario,\n\t * in that case \"indexed_column_attno\" is the attribute number of the compressed chunk column\n\t * corresponding to the distinct column \"distinct_attno\" on the decompressed chunk consumed by\n\t * SkipScan\n\t */\n\tAttrNumber indexed_column_attno;\n\n\t/* The column offset on the index we are calling DISTINCT on */\n\tAttrNumber scankey_attno;\n\tint distinct_typ_len;\n\tbool distinct_by_val;\n\n\t/* InvalidOid for the last skip key, always invalid for one-key SkipScan\n\t * For N-key SkipScan default quals are (sk1 = p1), (sk2 = p2), .. (sk_n > p_n),\n\t * we'll switch to (sk_i > p_i) when no more values for (sk_i+1 > p_i+1),\n\t * so we will store \"=\" along with \">\" comparator for keys 1..N-1.\n\t */\n\tOid eqcomp;\n} SkipKeyInfo;\n\ntypedef struct SkipScanPath\n{\n\tCustomPath cpath;\n\tIndexPath *index_path;\n\n\t/* List of skip column attributes for each skip key */\n\tList *skipkeyinfo;\n\n\t/* Vars referencing the distinct columns on the relation */\n\tList *dvars;\n} SkipScanPath;\n\ntypedef struct DistinctPathInfo\n{\n\tUpperRelationKind stage; /* What kind of Upper distinct path we are dealing with */\n\tPath *unique_path;\t\t /* If not NULL, valid Upper distinct path */\n\tList *\n\t\tdistinct_expr; /* If not NULL, list of valid distinct expressions for Upper distinct path */\n} DistinctPathInfo;\n\nstatic int get_idx_key(IndexOptInfo *idxinfo, AttrNumber attno);\nstatic List *sort_indexquals(IndexOptInfo *indexinfo, List *quals);\nstatic OpExpr *fix_indexqual(IndexOptInfo *index, RestrictInfo *rinfo, AttrNumber scankey_attno);\nstatic bool build_skip_qual(PlannerInfo *root, SkipKeyInfo *skinfo, IndexPath *index_path, Var *var,\n\t\t\t\t\t\t\tbool build_eqop);\nstatic List *build_subpath(PlannerInfo *root, List *subpaths, DistinctPathInfo *dpinfo,\n\t\t\t\t\t\t   List *top_pathkeys);\nstatic Var *get_distinct_var(PlannerInfo *root, Expr *tlexpr, IndexPath *index_path,\n\t\t\t\t\t\t\t Path *child_path, SkipKeyInfo *skinfo);\nstatic TargetEntry *tlist_member_match_var(Var *var, List *targetlist);\n\n/**************************\n * SkipScan Plan Creation *\n **************************/\n\nstatic CustomScanMethods skip_scan_plan_methods = {\n\t.CustomName = \"SkipScan\",\n\t.CreateCustomScanState = tsl_skip_scan_state_create,\n};\n\nvoid\n_skip_scan_init(void)\n{\n\tTryRegisterCustomScanMethods(&skip_scan_plan_methods);\n}\n\nstatic Plan *\nsetup_index_plan(CustomScan *skip_plan, Plan *child_plan)\n{\n\tPlan *plan = child_plan;\n\tif (IsA(child_plan, IndexScan))\n\t{\n\t\tskip_plan->scan = castNode(IndexScan, child_plan)->scan;\n\t}\n\telse if (IsA(child_plan, IndexOnlyScan))\n\t{\n\t\tskip_plan->scan = castNode(IndexOnlyScan, child_plan)->scan;\n\t}\n\telse if (ts_is_columnar_scan_plan(child_plan))\n\t{\n\t\tCustomScan *csplan = castNode(CustomScan, plan);\n\t\tskip_plan->scan = csplan->scan;\n\t\tplan = linitial(csplan->custom_plans);\n\t}\n\telse\n\t\telog(ERROR,\n\t\t\t \"unsupported subplan type for SkipScan: %s\",\n\t\t\t ts_get_node_name((Node *) child_plan));\n\n\treturn plan;\n}\n\nstatic Plan *\nskip_scan_plan_create(PlannerInfo *root, RelOptInfo *relopt, CustomPath *best_path, List *tlist,\n\t\t\t\t\t  List *clauses, List *custom_plans)\n{\n\tSkipScanPath *path = (SkipScanPath *) best_path;\n\tCustomScan *skip_plan = makeNode(CustomScan);\n\tIndexPath *index_path = path->index_path;\n\n\tPlan *child_plan = linitial(custom_plans);\n\tPlan *plan = setup_index_plan(skip_plan, child_plan);\n\n\tskip_plan->scan.plan.targetlist = tlist;\n\tskip_plan->custom_scan_tlist = list_copy(tlist);\n\tskip_plan->scan.plan.qual = NIL;\n\tskip_plan->scan.plan.type = T_CustomScan;\n\tskip_plan->methods = &skip_scan_plan_methods;\n\tskip_plan->custom_plans = custom_plans;\n\n\t/* Setup for SkipScan debug info */\n\tStringInfoData debuginfo;\n\tRangeTblEntry *indexed_rte = NULL;\n\tchar *sep = \"\";\n\tif (ts_guc_debug_skip_scan_info)\n\t{\n\t\tinitStringInfo(&debuginfo);\n\t\tRelOptInfo *indexed_rel = index_path->path.parent;\n\t\tindexed_rte = planner_rt_fetch(indexed_rel->relid, root);\n\t\tOid indrelid = InvalidOid;\n\t\tif (IsA(plan, IndexScan))\n\t\t{\n\t\t\tIndexScan *idx_plan = castNode(IndexScan, plan);\n\t\t\tindrelid = idx_plan->indexid;\n\t\t}\n\t\telse if (IsA(plan, IndexOnlyScan))\n\t\t{\n\t\t\tIndexOnlyScan *idx_plan = castNode(IndexOnlyScan, plan);\n\t\t\tindrelid = idx_plan->indexid;\n\t\t}\n\t\tappendStringInfo(&debuginfo, \"SkipScan used on %s(\", get_rel_name(indrelid));\n\t}\n\n\tListCell *lc, *lv;\n\t/* List of N-1 equality op Oids for N-key skipscan, stays NIL for one-key skipscan */\n\tList *eqcomps = NIL;\n\t/* List of N skipkeyinfo Int lists for N-key skipscan */\n\tList *skinfos = NIL;\n\tforboth (lc, path->skipkeyinfo, lv, path->dvars)\n\t{\n\t\tSkipKeyInfo *skinfo = (SkipKeyInfo *) lfirst(lc);\n\t\tVar *dvar = castNode(Var, lfirst(lv));\n\t\tOpExpr *op =\n\t\t\tfix_indexqual(index_path->indexinfo, skinfo->skip_clause, skinfo->scankey_attno);\n\t\tif (OidIsValid(skinfo->eqcomp))\n\t\t\teqcomps = lappend_oid(eqcomps, skinfo->eqcomp);\n\n\t\tif (IsA(plan, IndexScan))\n\t\t{\n\t\t\tIndexScan *idx_plan = castNode(IndexScan, plan);\n\t\t\t/* we prepend skip qual here so sort_indexquals will put it as first qual for that\n\t\t\t * column */\n\t\t\tidx_plan->indexqual =\n\t\t\t\tsort_indexquals(index_path->indexinfo, lcons(op, idx_plan->indexqual));\n\t\t}\n\t\telse if (IsA(plan, IndexOnlyScan))\n\t\t{\n\t\t\tIndexOnlyScan *idx_plan = castNode(IndexOnlyScan, plan);\n\t\t\t/* we prepend skip qual here so sort_indexquals will put it as first qual for that\n\t\t\t * column */\n\t\t\tidx_plan->indexqual =\n\t\t\t\tsort_indexquals(index_path->indexinfo, lcons(op, idx_plan->indexqual));\n\t\t}\n\t\telse\n\t\t\telog(ERROR,\n\t\t\t\t \"unsupported subplan type for SkipScan: %s\",\n\t\t\t\t ts_get_node_name((Node *) plan));\n\n\t\t/* get position of distinct column in tuples produced by child scan */\n\t\tTargetEntry *tle = tlist_member_match_var(dvar, child_plan->targetlist);\n\n\t\tSkipKeyNullStatus sknulls;\n\t\tif (skinfo->notnull)\n\t\t\tsknulls = SK_NOT_NULL;\n\t\telse\n\t\t{\n\t\t\tbool nulls_first = index_path->indexinfo->nulls_first[skinfo->scankey_attno - 1];\n\t\t\tif (index_path->indexscandir == BackwardScanDirection)\n\t\t\t\tnulls_first = !nulls_first;\n\t\t\tsknulls = (nulls_first ? SK_NULLS_FIRST : SK_NULLS_LAST);\n\t\t}\n\t\tskinfos = lappend(skinfos,\n\t\t\t\t\t\t  list_make5_int(tle->resno,\n\t\t\t\t\t\t\t\t\t\t skinfo->distinct_by_val,\n\t\t\t\t\t\t\t\t\t\t skinfo->distinct_typ_len,\n\t\t\t\t\t\t\t\t\t\t sknulls,\n\t\t\t\t\t\t\t\t\t\t skinfo->scankey_attno));\n\t\t/* Debug info about skip key */\n\t\tif (ts_guc_debug_skip_scan_info)\n\t\t{\n\t\t\tchar *attname = get_attname(indexed_rte->relid, skinfo->indexed_column_attno, false);\n\t\t\tchar *sknullstext;\n\t\t\tswitch (sknulls)\n\t\t\t{\n\t\t\t\tcase SK_NOT_NULL:\n\t\t\t\t\tsknullstext = \"NOT NULL\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase SK_NULLS_FIRST:\n\t\t\t\t\tsknullstext = \"NULLS FIRST\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase SK_NULLS_LAST:\n\t\t\t\t\tsknullstext = \"NULLS LAST\";\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tAssert(false);\n\t\t\t}\n\t\t\tappendStringInfo(&debuginfo, \"%s%s %s\", sep, attname, sknullstext);\n\t\t\tsep = \", \";\n\t\t}\n\t}\n\n\tif (ts_guc_debug_skip_scan_info)\n\t{\n\t\tappendStringInfoString(&debuginfo, \")\");\n\t\telog(INFO, \"%s\", debuginfo.data);\n\t}\n\n\tskip_plan->custom_private = lappend(skip_plan->custom_private, skinfos);\n\t/* Don't need equality ops for one-key skipscan */\n\tif (eqcomps != NIL)\n\t{\n\t\tAssert(list_length(skinfos) > 1);\n\t\tskip_plan->custom_private = lappend(skip_plan->custom_private, eqcomps);\n\t}\n\n\treturn &skip_plan->scan.plan;\n}\n\n/*************************\n * SkipScanPath Creation *\n *************************/\nstatic CustomPathMethods skip_scan_path_methods = {\n\t.CustomName = \"SkipScanPath\",\n\t.PlanCustomPath = skip_scan_plan_create,\n};\n\n#if PG16_GE\ntypedef struct FindAggrefsContext\n{\n\tList *aggrefs; /* all non-nested Aggrefs found in a node */\n} FindAggrefsContext;\n\nstatic bool\nfind_aggrefs_walker(Node *node, FindAggrefsContext *context)\n{\n\tif (node == NULL)\n\t\treturn false;\n\tif (IsA(node, Aggref))\n\t{\n\t\tcontext->aggrefs = lappend(context->aggrefs, node);\n\t\t/* don't recurse inside Aggrefs */\n\t\treturn false;\n\t}\n\n\treturn expression_tree_walker(node, find_aggrefs_walker, context);\n}\n#endif\n\nstatic Expr *\nget_distint_clause_expr(PlannerInfo *root, SortGroupClause *distinct_clause)\n{\n\tNode *expr = get_sortgroupclause_expr(distinct_clause, root->parse->targetList);\n\n\t/* we ignore any columns that can be constified to allow for cases like DISTINCT 'abc',\n\t * column */\n\tif (IsA(estimate_expression_value(root, expr), Const))\n\t\treturn NULL;\n\n\t/* We ignore binary-compatible relabeling */\n\tExpr *tlexpr = (Expr *) expr;\n\twhile (tlexpr && IsA(tlexpr, RelabelType))\n\t\ttlexpr = ((RelabelType *) tlexpr)->arg;\n\n\tif (!IsA(tlexpr, Var))\n\t\treturn NULL;\n\n\treturn tlexpr;\n}\n\n/* We can get upper path Distinct expression once for upper path,\n * rather than repeat this check for each child path of an upper path input\n */\nstatic List *\nget_upper_distinct_expr(PlannerInfo *root, UpperRelationKind stage)\n{\n\tListCell *lc;\n\tExpr *tlexpr = NULL;\n\tList *result = NULL;\n\n\tif (stage == UPPERREL_DISTINCT && root->parse->distinctClause)\n\t{\n\t\t/* Obtain Distinct key from the target list, we ruled out numkeys > 1 cases before.\n\t\t * Examples of queries with 1 Distinct key but multiple target entries:\n\t\t * SELECT dev, dev FROM t; SELECT 1, dev FROM t; SELECT dev, time FROM t WHERE time = 100;\n\t\t */\n\t\tSortGroupClause *distinct_clause = NULL;\n#if PG16_GE\n\t\tforeach (lc, root->processed_distinctClause)\n\t\t{\n\t\t\tdistinct_clause = (SortGroupClause *) lfirst(lc);\n\t\t\ttlexpr = get_distint_clause_expr(root, distinct_clause);\n\t\t\tif (tlexpr)\n\t\t\t\tresult = lappend(result, tlexpr);\n\t\t\telse\n\t\t\t\treturn NULL;\n\t\t}\n#else\n\t\tif (root->distinct_pathkeys)\n\t\t{\n\t\t\tforeach (lc, root->distinct_pathkeys)\n\t\t\t{\n\t\t\t\tPathKey *pathkey = (PathKey *) lfirst(lc);\n\t\t\t\tif (pathkey->pk_eclass->ec_sortref)\n\t\t\t\t{\n\t\t\t\t\tforeach (lc, root->parse->distinctClause)\n\t\t\t\t\t{\n\t\t\t\t\t\tSortGroupClause *clause = lfirst_node(SortGroupClause, lc);\n\t\t\t\t\t\tif (clause->tleSortGroupRef == pathkey->pk_eclass->ec_sortref)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdistinct_clause = clause;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (!distinct_clause)\n\t\t\t\t\t\treturn NULL;\n\t\t\t\t}\n\t\t\t\t/* We can get PathKey with ec_sortref = 0 in PG15\n\t\t\t\t * when False filter is not pushed into a relation with distinct column (i.e. it's\n\t\t\t\t * on top of a join), so need to support this case in PG15\n\t\t\t\t */\n\t\t\t\telse\n\t\t\t\t\treturn NULL;\n\n\t\t\t\ttlexpr = get_distint_clause_expr(root, distinct_clause);\n\t\t\t\tif (tlexpr)\n\t\t\t\t\tresult = lappend(result, tlexpr);\n\t\t\t\telse\n\t\t\t\t\treturn NULL;\n\t\t\t}\n\t\t}\n\t\t/* In PG16+ we use LIMIT instead of UpperUniquePath for (numkeys = 0),\n\t\t * but in PG15- we would still create UpperUniquePath for (numkeys = 0), so handle this case\n\t\t * here\n\t\t */\n\t\telse\n\t\t{\n\t\t\tforeach (lc, root->parse->distinctClause)\n\t\t\t{\n\t\t\t\tdistinct_clause = lfirst_node(SortGroupClause, lc);\n\t\t\t\ttlexpr = get_distint_clause_expr(root, distinct_clause);\n\t\t\t\tif (tlexpr)\n\t\t\t\t\tresult = lappend(result, tlexpr);\n\t\t\t\telse\n\t\t\t\t\treturn NULL;\n\t\t\t}\n\t\t}\n#endif\n\t}\n#if PG16_GE\n\telse if (stage == UPPERREL_GROUP_AGG)\n\t{\n\t\t/* Find all non-nested Aggrefs in the query target list */\n\t\tFindAggrefsContext agg_ctx = { .aggrefs = NULL };\n\t\tfind_aggrefs_walker((Node *) root->parse->targetList, &agg_ctx);\n\n\t\tforeach (lc, agg_ctx.aggrefs)\n\t\t{\n\t\t\tAggref *agg = lfirst_node(Aggref, lc);\n\t\t\t/* Only distinct aggs with 1 sorted argument are eligible*/\n\t\t\tif (agg->aggdistinct && agg->aggpresorted && list_length(agg->args) == 1)\n\t\t\t{\n\t\t\t\tTargetEntry *tle = (TargetEntry *) linitial(agg->args);\n\n\t\t\t\tExpr *expr = tle->expr;\n\t\t\t\t/* We ignore binary-compatible relabeling */\n\t\t\t\twhile (expr && IsA(expr, RelabelType))\n\t\t\t\t\texpr = ((RelabelType *) expr)->arg;\n\n\t\t\t\t/* Distinct agg over a Const is OK */\n\t\t\t\tif (IsA(estimate_expression_value(root, (Node *) expr), Const))\n\t\t\t\t\tcontinue;\n\n\t\t\t\t/* Don't support no-var arguments */\n\t\t\t\tif (!IsA(expr, Var))\n\t\t\t\t\treturn NULL;\n\n\t\t\t\t/* Don't support multiple distinct aggs over different columns */\n\t\t\t\tif (tlexpr && !tlist_member_match_var((Var *) tlexpr, agg->args))\n\t\t\t\t\treturn NULL;\n\n\t\t\t\t/* If Distinct agg path has a groupby column, it needs to match Distinct agg column\n\t\t\t\t */\n\t\t\t\tif (root->processed_groupClause)\n\t\t\t\t{\n\t\t\t\t\t/* Should have bailed out on gby exprs > 1 earlier\n\t\t\t\t\t * Only 1-key SkipScan is supported for distinct aggregates\n\t\t\t\t\t */\n\t\t\t\t\tAssert(list_length(root->processed_groupClause) == 1);\n\t\t\t\t\tSortGroupClause *sortcl =\n\t\t\t\t\t\t(SortGroupClause *) linitial(root->processed_groupClause);\n\t\t\t\t\tExpr *gbykey = (Expr *) get_sortgroupclause_expr(sortcl, root->processed_tlist);\n\t\t\t\t\tif (!equal(gbykey, expr))\n\t\t\t\t\t\treturn NULL;\n\t\t\t\t}\n\n\t\t\t\t/* Found a valid distinct agg over a valid Var */\n\t\t\t\tif (!tlexpr)\n\t\t\t\t{\n\t\t\t\t\ttlexpr = expr;\n\t\t\t\t\tresult = lappend(result, tlexpr);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t}\n\t}\n#endif\n\treturn result;\n}\n\nstatic void\nobtain_upper_distinct_path(PlannerInfo *root, RelOptInfo *output_rel, DistinctPathInfo *dpinfo)\n{\n\tListCell *lc;\n\n\t/*\n\t * look for Unique Path so we dont have to repeat some of\n\t * the calculations done by postgres and can also assume\n\t * that the DISTINCT clause is eligible for sort based\n\t * DISTINCT\n\t */\n\tif (dpinfo->stage == UPPERREL_DISTINCT)\n\t{\n\t\tif (!ts_guc_enable_skip_scan)\n\t\t\treturn;\n\n\t\tforeach (lc, output_rel->pathlist)\n\t\t{\n\t\t\tif (IsA(lfirst(lc), UpperUniquePath))\n\t\t\t{\n\t\t\t\tUpperUniquePath *unique = (UpperUniquePath *) lfirst_node(UpperUniquePath, lc);\n\n\t\t\t\t/* We can handle DISTINCT on more than one key if all keys are guaranteed not-nulls.\n\t\t\t\t * To do so, we break down the SkipScan into subproblems: first\n\t\t\t\t * find the minimal tuple then for each prefix find all unique suffix\n\t\t\t\t * tuples. For instance, if we are searching over (int, int), we would\n\t\t\t\t * first find (0, 0) then find (0, N) for all N in the domain, then\n\t\t\t\t * find (1, N), then (2, N), etc\n\t\t\t\t */\n\t\t\t\tif (!ts_guc_enable_multikey_skip_scan && unique->numkeys > 1)\n\t\t\t\t\treturn;\n\n#if PG16_GE\n\t\t\t\t/* since PG16+ we no longer create UpperUniquePath with 0 numkeys,\n\t\t\t\t * we create LIMIT path instead, so shouldn't be here with 0 numkeys\n\t\t\t\t */\n\t\t\t\tAssert(unique->numkeys >= 1);\n#endif\n\t\t\t\tdpinfo->unique_path = (Path *) unique;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\t/* Sorted inputs for Distinct aggs weren't supported until PG16 */\n#if PG16_GE\n\t/* Look for Aggpath with eligible Distinct aggregates */\n\telse if (dpinfo->stage == UPPERREL_GROUP_AGG)\n\t{\n\t\tif (!ts_guc_enable_skip_scan_for_distinct_aggregates)\n\t\t\treturn;\n\n\t\t/* Cannot apply SkipScan to distinct aggregates with more than one key */\n\t\tif (list_length(root->group_pathkeys) > 1)\n\t\t\treturn;\n\n\t\tforeach (lc, output_rel->pathlist)\n\t\t{\n\t\t\tif (IsA(lfirst(lc), AggPath))\n\t\t\t{\n\t\t\t\tAggPath *unique = (AggPath *) lfirst_node(AggPath, lc);\n\n\t\t\t\t/* If Distinct agg path has a group key, it must match Distinct aggregate input sort\n\t\t\t\t * key, otherwise cannot apply SkipScan\n\t\t\t\t */\n\t\t\t\tif (unique->path.pathkeys &&\n\t\t\t\t\t!pathkeys_contained_in(unique->path.pathkeys, unique->subpath->pathkeys))\n\t\t\t\t{\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tdpinfo->unique_path = (Path *) lfirst_node(AggPath, lc);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n#endif\n\telse\n\t\treturn;\n\n\tif (!dpinfo->unique_path)\n\t\treturn;\n\n\t/* Check if we have valid distinct expression to source from the underlying index */\n\tdpinfo->distinct_expr = get_upper_distinct_expr(root, dpinfo->stage);\n\tif (!dpinfo->distinct_expr)\n\t{\n\t\tdpinfo->unique_path = NULL;\n\t\treturn;\n\t}\n\n\t/* Need to make a copy of the unique path here because add_path() in the\n\t * pathlist loop below might prune it if the new unique path\n\t * (SkipScanPath) dominates the old one. When the unique path is pruned,\n\t * the pointer will no longer be valid in the next iteration of the\n\t * pathlist loop. Fortunately, the Path object is not deeply freed, so a\n\t * shallow copy is enough. */\n\tif (dpinfo->stage == UPPERREL_DISTINCT)\n\t{\n\t\tUpperUniquePath *unique = makeNode(UpperUniquePath);\n\t\tmemcpy(unique, lfirst_node(UpperUniquePath, lc), sizeof(UpperUniquePath));\n\t\tdpinfo->unique_path = (Path *) unique;\n\t}\n\telse if (dpinfo->stage == UPPERREL_GROUP_AGG)\n\t{\n\t\tAggPath *dist_agg_path = makeNode(AggPath);\n\t\tmemcpy(dist_agg_path, lfirst_node(AggPath, lc), sizeof(AggPath));\n\t\tdpinfo->unique_path = (Path *) dist_agg_path;\n\t}\n}\n\nstatic SkipScanPath *skip_scan_path_create(PlannerInfo *root, Path *child_path,\n\t\t\t\t\t\t\t\t\t\t   DistinctPathInfo *dpinfo);\n\n/*\n * Create SkipScan paths based on existing Unique paths.\n * For a Unique path on a simple relation like the following\n *\n *  Unique\n *    ->  Index Scan using skip_scan_dev_name_idx on skip_scan\n *\n * a SkipScan path like this will be created:\n *\n *  Unique\n *    ->  Custom Scan (SkipScan) on skip_scan\n *          ->  Index Scan using skip_scan_dev_name_idx on skip_scan\n *\n * For a Unique path on a hypertable with multiple chunks like the following\n *\n *  Unique\n *    ->  Merge Append\n *          Sort Key: _hyper_2_1_chunk.dev_name\n *          ->  Index Scan using _hyper_2_1_chunk_idx on _hyper_2_1_chunk\n *          ->  Index Scan using _hyper_2_2_chunk_idx on _hyper_2_2_chunk\n *\n * a SkipScan path like this will be created:\n *\n *  Unique\n *    ->  Merge Append\n *          Sort Key: _hyper_2_1_chunk.dev_name\n *          ->  Custom Scan (SkipScan) on _hyper_2_1_chunk\n *                ->  Index Scan using _hyper_2_1_chunk_idx on _hyper_2_1_chunk\n *          ->  Custom Scan (SkipScan) on _hyper_2_2_chunk\n *                ->  Index Scan using _hyper_2_2_chunk_idx on _hyper_2_2_chunk\n */\nvoid\ntsl_skip_scan_paths_add(PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *output_rel,\n\t\t\t\t\t\tUpperRelationKind stage)\n{\n\tDistinctPathInfo dpinfo = {\n\t\t.stage = stage,\n\t\t.unique_path = NULL,\n\t\t.distinct_expr = NULL,\n\t};\n\n\tobtain_upper_distinct_path(root, output_rel, &dpinfo);\n\tif (!dpinfo.unique_path)\n\t\treturn;\n\n\tAssert(IsA(dpinfo.unique_path, UpperUniquePath) || IsA(dpinfo.unique_path, AggPath));\n\tListCell *lc;\n\tforeach (lc, input_rel->pathlist)\n\t{\n\t\tbool has_caa = false;\n\n\t\tPath *subpath = lfirst(lc);\n\n\t\tList *top_pathkeys = NULL;\n\n\t\t/* Unique path has to be sorted on at least DISTINCT ON key */\n\t\tif (IsA(dpinfo.unique_path, UpperUniquePath))\n\t\t{\n\t\t\tif (!pathkeys_contained_in(dpinfo.unique_path->pathkeys, subpath->pathkeys))\n\t\t\t\tcontinue;\n\t\t}\n\t\t/* AggPath with distinct aggs may not be sorted, but the input into distinct aggs needs to\n\t\t * be sorted */\n\t\telse if (IsA(dpinfo.unique_path, AggPath))\n\t\t{\n\t\t\tif (!subpath->pathkeys ||\n\t\t\t\t!pathkeys_contained_in(dpinfo.unique_path->pathkeys, subpath->pathkeys))\n\t\t\t\tcontinue;\n\t\t\t/* Need to check sortedness for inputs of Distinct aggs, so we'll keep track of the\n\t\t\t * input pathkeys  */\n\t\t\ttop_pathkeys = subpath->pathkeys;\n\t\t}\n\n\t\t/* If path is a ProjectionPath we strip it off for processing\n\t\t * but also add a ProjectionPath on top of the SKipScanPaths\n\t\t * later.\n\t\t */\n\t\tProjectionPath *proj = NULL;\n\t\tif (IsA(subpath, ProjectionPath))\n\t\t{\n\t\t\tproj = castNode(ProjectionPath, subpath);\n\t\t\tsubpath = proj->subpath;\n\t\t}\n\n\t\t/* Path might be wrapped in a ConstraintAwareAppendPath if this\n\t\t * is a MergeAppend that could benefit from runtime exclusion.\n\t\t * We treat this similar to ProjectionPath and add it back\n\t\t * later\n\t\t */\n\t\tif (ts_is_constraint_aware_append_path(subpath))\n\t\t{\n\t\t\tsubpath = linitial(castNode(CustomPath, subpath)->custom_paths);\n\t\t\tAssert(IsA(subpath, MergeAppendPath));\n\t\t\thas_caa = true;\n\t\t}\n\n\t\tif (IsA(subpath, IndexPath) || ts_is_columnar_scan_path(subpath))\n\t\t{\n\t\t\tsubpath = (Path *) skip_scan_path_create(root, subpath, &dpinfo);\n\t\t\tif (!subpath)\n\t\t\t\tcontinue;\n\t\t}\n\t\telse if (IsA(subpath, MergeAppendPath))\n\t\t{\n\t\t\tMergeAppendPath *merge_path = castNode(MergeAppendPath, subpath);\n\n\t\t\tList *new_paths = build_subpath(root, merge_path->subpaths, &dpinfo, top_pathkeys);\n\n\t\t\t/* build_subpath returns NULL when no SkipScanPath was created */\n\t\t\tif (!new_paths)\n\t\t\t\tcontinue;\n\n\t\t\tsubpath = (Path *) create_merge_append_path(root,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tmerge_path->path.parent,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tnew_paths,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tmerge_path->path.pathkeys,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tNULL);\n\t\t\tsubpath->pathtarget = copy_pathtarget(merge_path->path.pathtarget);\n\t\t}\n\t\t/* We may have Append over one input which will be removed from the plan later.\n\t\t * Consider it when it is sorted correctly. #7778\n\t\t */\n\t\telse if (IsA(subpath, AppendPath))\n\t\t{\n\t\t\tAppendPath *append_path = castNode(AppendPath, subpath);\n\n\t\t\tif (list_length(append_path->subpaths) > 1)\n\t\t\t\tcontinue;\n\n\t\t\tList *new_paths = build_subpath(root, append_path->subpaths, &dpinfo, top_pathkeys);\n\n\t\t\t/* build_subpath returns NULL when no SkipScanPath was created */\n\t\t\tif (!new_paths)\n\t\t\t\tcontinue;\n\n\t\t\tsubpath = (Path *) create_append_path(root,\n\t\t\t\t\t\t\t\t\t\t\t\t  append_path->path.parent,\n\t\t\t\t\t\t\t\t\t\t\t\t  new_paths,\n\t\t\t\t\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t  append_path->path.pathkeys,\n\t\t\t\t\t\t\t\t\t\t\t\t  NULL,\n\t\t\t\t\t\t\t\t\t\t\t\t  append_path->path.parallel_workers,\n\t\t\t\t\t\t\t\t\t\t\t\t  append_path->path.parallel_aware,\n\t\t\t\t\t\t\t\t\t\t\t\t  -1);\n\t\t\tsubpath->pathtarget = copy_pathtarget(append_path->path.pathtarget);\n\t\t}\n\t\telse if (ts_is_chunk_append_path(subpath))\n\t\t{\n\t\t\tChunkAppendPath *ca = (ChunkAppendPath *) subpath;\n\t\t\tList *new_paths = build_subpath(root, ca->cpath.custom_paths, &dpinfo, top_pathkeys);\n\t\t\t/* ChunkAppend should never be wrapped in ConstraintAwareAppendPath */\n\t\t\tAssert(!has_caa);\n\n\t\t\t/* build_subpath returns NULL when no SkipScanPath was created */\n\t\t\tif (!new_paths)\n\t\t\t\tcontinue;\n\n\t\t\t/* We copy the existing ChunkAppendPath here because we don't have all the\n\t\t\t * information used for creating the original one and we don't want to\n\t\t\t * duplicate all the checks done when creating the original one.\n\t\t\t */\n\t\t\tsubpath = (Path *) ts_chunk_append_path_copy(ca, new_paths, ca->cpath.path.pathtarget);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* add ConstraintAwareAppendPath if the original path had one */\n\t\tif (has_caa)\n\t\t\tsubpath = ts_constraint_aware_append_path_create(root, subpath);\n\n\t\tPath *new_unique = NULL;\n\n\t\tif (IsA(dpinfo.unique_path, UpperUniquePath))\n\t\t{\n\t\t\tUpperUniquePath *unique = (UpperUniquePath *) dpinfo.unique_path;\n\t\t\tnew_unique = (Path *) create_upper_unique_path(root,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   output_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   subpath,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   unique->numkeys,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   unique->path.rows);\n\t\t\tnew_unique->pathtarget = unique->path.pathtarget;\n\n\t\t\tif (proj)\n\t\t\t\tnew_unique =\n\t\t\t\t\t(Path *) create_projection_path(root,\n\t\t\t\t\t\t\t\t\t\t\t\t\toutput_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t\tnew_unique,\n\t\t\t\t\t\t\t\t\t\t\t\t\tcopy_pathtarget(new_unique->pathtarget));\n\t\t}\n\t\telse if (IsA(dpinfo.unique_path, AggPath))\n\t\t{\n\t\t\tAggPath *dist_agg_path = (AggPath *) dpinfo.unique_path;\n\t\t\tif (proj)\n\t\t\t{\n\t\t\t\tproj->subpath = subpath;\n\t\t\t\tsubpath = (Path *) proj;\n\t\t\t}\n\t\t\tAggClauseCosts agg_costs;\n\t\t\tMemSet(&agg_costs, 0, sizeof(AggClauseCosts));\n\t\t\tget_agg_clause_costs(root, dist_agg_path->aggsplit, &agg_costs);\n\t\t\tnew_unique = (Path *) create_agg_path(root,\n\t\t\t\t\t\t\t\t\t\t\t\t  output_rel,\n\t\t\t\t\t\t\t\t\t\t\t\t  subpath,\n\t\t\t\t\t\t\t\t\t\t\t\t  dist_agg_path->path.pathtarget,\n\t\t\t\t\t\t\t\t\t\t\t\t  dist_agg_path->aggstrategy,\n\t\t\t\t\t\t\t\t\t\t\t\t  dist_agg_path->aggsplit,\n\t\t\t\t\t\t\t\t\t\t\t\t  dist_agg_path->groupClause,\n\t\t\t\t\t\t\t\t\t\t\t\t  dist_agg_path->qual,\n\t\t\t\t\t\t\t\t\t\t\t\t  (const AggClauseCosts *) &agg_costs,\n\t\t\t\t\t\t\t\t\t\t\t\t  dist_agg_path->numGroups);\n\t\t}\n\n\t\tadd_path(output_rel, new_unique);\n\t}\n}\n\n#if PG17_LT\nstatic bool\nattr_is_notnull(Oid relid, AttrNumber attno)\n{\n\tHeapTuple tp = SearchSysCache2(ATTNUM, ObjectIdGetDatum(relid), Int16GetDatum(attno));\n\tif (!HeapTupleIsValid(tp))\n\t\treturn false;\n\tForm_pg_attribute att_tup = (Form_pg_attribute) GETSTRUCT(tp);\n\tbool result = att_tup->attnotnull;\n\tReleaseSysCache(tp);\n\treturn result;\n}\n#endif\n\n/* Check if skip key is guaranteed not-null */\nstatic void\ncheck_notnull_skipkey(SkipKeyInfo *skinfo, Path *child_path, IndexPath *index_path)\n{\n\tListCell *l;\n\t/* Quickly look through index clauses on this skip key */\n\tforeach (l, index_path->indexclauses)\n\t{\n\t\tIndexClause *ic = (IndexClause *) lfirst(l);\n\t\t/* index quals are ordered by indexcol, nothing to see if we've passed our indexcol */\n\t\tif (ic->indexcol > skinfo->scankey_attno - 1)\n\t\t\tbreak;\n\n\t\t/* We may have row comparison with skip key not being a leading col,\n\t\t * like (col, skipcol) > (3, 5), but it can allow NULL skipcols to pass if (col>3) is true,\n\t\t * so for row comparisons we will only look at leading \"indexcol\" and not at \"indexcols\".\n\t\t */\n\t\tif (ic->indexcol == skinfo->scankey_attno - 1)\n\t\t{\n\t\t\t/* Any simple index qual but \"isNull\" filters out nulls,\n\t\t\t * including \"lossy\" index quals extracted from index clauses.\n\t\t\t */\n\t\t\tListCell *lc;\n\t\t\tforeach (lc, ic->indexquals)\n\t\t\t{\n\t\t\t\tRestrictInfo *iqual = (RestrictInfo *) lfirst(lc);\n\t\t\t\tif (!(IsA(iqual->clause, NullTest) &&\n\t\t\t\t\t  ((NullTest *) iqual->clause)->nulltesttype == IS_NULL))\n\t\t\t\t{\n\t\t\t\t\tskinfo->notnull = true;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Otherwise look at all non-indexqual index filters on the key (like (key+1)>5) to see if they\n\t * filter out NULLs */\n\tRelOptInfo *indexed_rel = index_path->path.parent;\n\tforeach (l, index_path->indexinfo->indrestrictinfo)\n\t{\n\t\tRestrictInfo *ri = castNode(RestrictInfo, lfirst(l));\n\t\tBitmapset *clause_attnos = NULL;\n\t\tpull_varattnos((Node *) ri->clause, indexed_rel->relid, &clause_attnos);\n\t\tif (bms_is_member(skinfo->indexed_column_attno - FirstLowInvalidHeapAttributeNumber,\n\t\t\t\t\t\t  clause_attnos))\n\t\t{\n\t\t\tif (!contain_nonstrict_functions((Node *) ri->clause))\n\t\t\t{\n\t\t\t\tskinfo->notnull = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Failing that, look at filters not pushed down into index (like col1+col2>1) to see if they\n\t * filter out NULLs */\n\tRelOptInfo *child_rel = child_path->parent;\n\tforeach (l, child_rel->baserestrictinfo)\n\t{\n\t\tRestrictInfo *ri = castNode(RestrictInfo, lfirst(l));\n\t\tBitmapset *clause_attnos = NULL;\n\t\tpull_varattnos((Node *) ri->clause, child_rel->relid, &clause_attnos);\n\t\tif (bms_is_member(skinfo->distinct_attno - FirstLowInvalidHeapAttributeNumber,\n\t\t\t\t\t\t  clause_attnos))\n\t\t{\n\t\t\tif (!contain_nonstrict_functions((Node *) ri->clause))\n\t\t\t{\n\t\t\t\tskinfo->notnull = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic IndexPath *\nget_compressed_index_path(ColumnarScanPath *dcpath)\n{\n\tPath *compressed_path = linitial(dcpath->custom_path.custom_paths);\n\tif (IsA(compressed_path, IndexPath))\n\t{\n\t\tIndexPath *index_path = castNode(IndexPath, compressed_path);\n\t\tif (!pathkeys_contained_in(dcpath->required_compressed_pathkeys, compressed_path->pathkeys))\n\t\t\treturn NULL;\n\n\t\treturn index_path;\n\t}\n\treturn NULL;\n}\n\nstatic SkipScanPath *\nskip_scan_path_create(PlannerInfo *root, Path *child_path, DistinctPathInfo *dpinfo)\n{\n\tIndexPath *index_path = NULL;\n\n\tif (IsA(child_path, IndexPath))\n\t{\n\t\tindex_path = castNode(IndexPath, child_path);\n\t}\n\telse if (ts_is_columnar_scan_path(child_path))\n\t{\n\t\tif (!ts_guc_enable_compressed_skip_scan)\n\t\t\treturn NULL;\n\n\t\tColumnarScanPath *dcpath = (ColumnarScanPath *) child_path;\n\t\tindex_path = get_compressed_index_path(dcpath);\n\t}\n\tif (!index_path)\n\t\treturn NULL;\n\n\t/* cannot use SkipScan with non-orderable index or IndexPath without pathkeys */\n\tif (!index_path->path.pathkeys || !index_path->indexinfo->sortopfamily)\n\t\treturn NULL;\n\n\t/* orderbyops are not compatible with skipscan */\n\tif (index_path->indexorderbys != NIL)\n\t\treturn NULL;\n\n\tSkipScanPath *skip_scan_path = (SkipScanPath *) newNode(sizeof(SkipScanPath), T_CustomPath);\n\tskip_scan_path->cpath.path.pathtype = T_CustomScan;\n\tskip_scan_path->cpath.path.pathkeys = child_path->pathkeys;\n\tskip_scan_path->cpath.path.pathtarget = child_path->pathtarget;\n\tskip_scan_path->cpath.path.param_info = child_path->param_info;\n\tskip_scan_path->cpath.path.parent = child_path->parent;\n\tskip_scan_path->cpath.custom_paths = list_make1(child_path);\n\tskip_scan_path->cpath.methods = &skip_scan_path_methods;\n\n\t/* While add_path may pfree paths with higher costs\n\t * it will never free IndexPaths and only ever do a shallow\n\t * free so reusing the IndexPath here is safe. */\n\tskip_scan_path->index_path = index_path;\n\n\tListCell *lc;\n\tint sk_no = 0;\n\tint num_skipkeys = list_length(dpinfo->distinct_expr);\n\tforeach (lc, dpinfo->distinct_expr)\n\t{\n\t\tExpr *dexpr = (Expr *) lfirst(lc);\n\n\t\t/* Placeholder for skip key attributes */\n\t\tSkipKeyInfo *skinfo = palloc(sizeof(SkipKeyInfo));\n\t\tVar *dvar = get_distinct_var(root, dexpr, index_path, child_path, skinfo);\n\t\tif (!dvar)\n\t\t{\n\t\t\tpfree(skinfo);\n\t\t\treturn NULL;\n\t\t}\n\n\t\t/* build skip qual this may fail if we cannot look up the operator */\n\t\tif (!build_skip_qual(root, skinfo, index_path, dvar, (++sk_no) < num_skipkeys))\n\t\t{\n\t\t\tpfree(skinfo);\n\t\t\treturn NULL;\n\t\t}\n\t\tif (!skinfo->notnull)\n\t\t\tcheck_notnull_skipkey(skinfo, child_path, index_path);\n\n\t\t/* Multikey SkipScan is only supported in not-null mode */\n\t\tif (!skinfo->notnull && num_skipkeys > 1)\n\t\t\treturn NULL;\n\n\t\tskip_scan_path->dvars = lappend(skip_scan_path->dvars, dvar);\n\t\tskip_scan_path->skipkeyinfo = lappend(skip_scan_path->skipkeyinfo, skinfo);\n\t}\n\n\t/* We have valid SkipScanPath: now we can cost it */\n\tdouble startup = child_path->startup_cost;\n\tdouble total = child_path->total_cost;\n\tdouble rows = child_path->rows;\n\tdouble indexscan_rows = index_path->path.rows;\n\n\t/* Also true for SkipScan over compressed chunks as can't have more distinct segmentby values\n\t * than number of batches */\n\tint ndistinct = indexscan_rows;\n\t/* For SELECT DISTINCT path, #rows can cap \"ndistinct\",\n\t * but for Distinct aggregates #rows = 1 usually, i.e. we can't cap \"ndistinct\" in this case.\n\t */\n\tif (dpinfo->stage == UPPERREL_DISTINCT)\n\t\tndistinct = Min(ndistinct, dpinfo->unique_path->rows);\n\n\t/* If we are on a chunk rather than on a PG table, we want to get \"ndistinct\" for this chunk,\n\t * as Unique path rows may combine rows from each chunk and may not represent a true\n\t * \"ndistinct\". Consider a hypertable with 1000 chunks, each chunk has the same 1 distinct\n\t * value, Unique path will add them up and we will get \"ndistinct\" = 1000 instead of 1. If\n\t * Unique path has \"ndistinct=1\" we can't go any smaller so will just accept this number.\n\t */\n\tif (ndistinct > 1)\n\t{\n\t\tndistinct =\n\t\t\tMax(1, floor(estimate_num_groups(root, skip_scan_path->dvars, ndistinct, NULL, NULL)));\n\t}\n\tskip_scan_path->cpath.path.rows = ndistinct;\n\n\t/* Addressing #8107: filters on the indexed data which are not index quals\n\t * will require sequential scanning of indexed tuples until finding a tuple passing the filter.\n\t * For some highly selective filters it may mean scanning a lot of tuples, sometimes the entire\n\t * input. SeqScan may perform better than IndexScan for such filters. We need to account for\n\t * such filters in the cost model i.e. cost the number of tuples to scan before passing the\n\t * filter.\n\t */\n\tList *clauses_needing_scan = NULL;\n\t/* If a filter is not pushed down into compessed indexed data, it's a filter for which we will\n\t * need to scan and decompress until filter is passed */\n\tif ((Path *) index_path != child_path)\n\t\tclauses_needing_scan = child_path->parent->baserestrictinfo;\n\telse\n\t{\n\t\t/* For uncompressed index data we need a finer check for which filters on the indexed data\n\t\t * are index quals or not */\n\t\tListCell *lc;\n\t\tforeach (lc, index_path->indexinfo->indrestrictinfo)\n\t\t{\n\t\t\tRestrictInfo *ri = (RestrictInfo *) lfirst(lc);\n\t\t\tbool match_found = false;\n\t\t\tListCell *l;\n\t\t\tforeach (l, index_path->indexclauses)\n\t\t\t{\n\t\t\t\tIndexClause *ic = (IndexClause *) lfirst(l);\n\t\t\t\tif (ri == ic->rinfo)\n\t\t\t\t{\n\t\t\t\t\tmatch_found = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\t/* This is a filter which is not an index qual: will have to scan indexed data until\n\t\t\t * this filter is passed */\n\t\t\tif (!match_found)\n\t\t\t\tclauses_needing_scan = lappend(clauses_needing_scan, ri);\n\t\t}\n\t}\n\n\t/* Heuristic for accounting for previous key shifts in multikey SkipScan\n\t * In general, we will have (k_1*k_2*..*k_N + k_1*..*k_N-1 + ... + k_1) shifts\n\t * where numdistinct = k_1*k_2*..*k_N and for each key \"k_i\" we have \"k_i+1\" distinct values.\n\t *\n\t * Worst case scenario is when k_1 = numdistinct and the rest =1, then we'll have (numdistinct *\n\t * N) shifts. If it's uniform for each key i.e. numdistinct = k^N we'll have less than\n\t * (numdistinct * 2) shifts. We pick something in the middle to avoid evaluating k_i for each\n\t * key.\n\t */\n\tint numkeys_multiplier = 1.0 + (num_skipkeys - 1) * 0.5;\n\n\t/* We calculate SkipScan cost as ndistinct * startup_cost + (ndistinct/rows) * total_cost\n\t * ndistinct * startup_cost is to account for the rescans we have to do and since startup\n\t * cost for indexes does not include page access cost we add a fraction of the total cost\n\t * accounting for the number of rows we expect to fetch.\n\t * If the row estimate for the scan is 1 we assume that the estimate got clamped to 1\n\t * and no rows would be returned by this scan and this chunk will most likely be excluded\n\t * by runtime exclusion. Otherwise the cost for this path would be highly inflated due\n\t * to (ndistinct / rows) * total leading to SkipScan not being chosen for queries on\n\t * hypertables with a lot of excluded chunks.\n\t *\n\t * This is the cost of (SkipScan <- IndexScan) scenario\n\t */\n\tif (clauses_needing_scan == NULL && (Path *) index_path == child_path)\n\t{\n\t\tskip_scan_path->cpath.path.startup_cost = startup;\n\t\tif (indexscan_rows > 1)\n\t\t\tskip_scan_path->cpath.path.total_cost =\n\t\t\t\tndistinct * startup * numkeys_multiplier + (ndistinct / rows) * total;\n\t\telse\n\t\t\tskip_scan_path->cpath.path.total_cost = startup;\n\t}\n\t/* For (SkipScan <- ColumnarScan <- compressed IndexScan) scenario\n\t * we will estimate cost as (ndistinct * costs( child_path LIMIT 1 OFFSET x))\n\t * i.e. as if we computed \"ndistinct\" LIMIT 1 queries on the \"child_path\" after initial setup.\n\t * If there is no qual above IndexScan, then OFFSET=0 (we don't need to scan tuples to pass qual\n\t * before returning the 1st tuple), otherwise OFFSET = (1 / qual_selectivity - 1), i.e. we have\n\t * to skip OFFSET tuples until we get the one which passes the qual.\n\t */\n\telse\n\t{\n\t\tint64 offset_until_qual_pass = 0;\n\t\tif (clauses_needing_scan != NULL)\n\t\t{\n\t\t\t/* Avoid division by qual_selectivity = 0.0 */\n\t\t\tSelectivity qual_selectivity =\n\t\t\t\tMax(1.0 / (rows + 1),\n\t\t\t\t\tclauselist_selectivity(root, clauses_needing_scan, 0, JOIN_INNER, NULL));\n\t\t\toffset_until_qual_pass = Max(0, floor(1 / qual_selectivity));\n\t\t}\n\t\tadjust_limit_rows_costs(&rows, &startup, &total, offset_until_qual_pass, 1);\n\n\t\tskip_scan_path->cpath.path.startup_cost = startup;\n\t\tif (indexscan_rows > 1)\n\t\t\tskip_scan_path->cpath.path.total_cost =\n\t\t\t\tstartup + (total - startup) * ndistinct * numkeys_multiplier;\n\t\telse\n\t\t\tskip_scan_path->cpath.path.total_cost = startup;\n\t}\n\n\t/* Finally, adjust SkipScan run costs with GUC multiplier (1.0 by default), to give users more\n\t * control over choosing SkipScan */\n\tskip_scan_path->cpath.path.total_cost =\n\t\tstartup +\n\t\t(skip_scan_path->cpath.path.total_cost - startup) * ts_guc_skip_scan_run_cost_multiplier;\n\n\treturn skip_scan_path;\n}\n\n/* Extract the Var to use for the SkipScan and do attno mapping if required. */\nstatic Var *\nget_distinct_var(PlannerInfo *root, Expr *tlexpr, IndexPath *index_path, Path *child_path,\n\t\t\t\t SkipKeyInfo *skinfo)\n{\n\tRelOptInfo *rel = child_path->parent;\n\tRelOptInfo *indexed_rel = index_path->path.parent;\n\n\tAssert(tlexpr && IsA(tlexpr, Var));\n\tVar *var = castNode(Var, tlexpr);\n\n\tRangeTblEntry *ht_rte = planner_rt_fetch(var->varno, root);\n\n\t/* check whether a skip var is declared NOT NULL\n\t *  it's enough to check hypertable for NOT NULL\n\t *  as NOT NULL constraint will be propagated to and checked on all chunks\n\t */\n#if PG17_LT\n\tskinfo->notnull = attr_is_notnull(ht_rte->relid, var->varattno);\n#else\n\tRelOptInfo *baserel = ((Index) var->varno == rel->relid ? rel : rel->parent);\n\tskinfo->notnull = bms_is_member(var->varattno, baserel->notnullattnums);\n#endif\n\n\t/* If we are dealing with a hypertable Var extracted from distinctClause will point to\n\t * the parent hypertable while the IndexPath will be on a Chunk.\n\t * For a normal PG table they point to the same relation and we are done here. */\n\tif ((Index) var->varno == rel->relid)\n\t{\n\t\t/* Get attribute number for distinct column on a normal PG table */\n\t\tskinfo->indexed_column_attno = var->varattno;\n\t\treturn var;\n\t}\n\n\tRangeTblEntry *chunk_rte = planner_rt_fetch(rel->relid, root);\n\tRangeTblEntry *indexed_rte =\n\t\t(indexed_rel == rel ? chunk_rte : planner_rt_fetch(indexed_rel->relid, root));\n\n\t/* Check for hypertable */\n\tif (!ts_is_hypertable(ht_rte->relid) || !bms_is_member(var->varno, rel->top_parent_relids))\n\t\treturn NULL;\n\n\tchar *attname = get_attname(ht_rte->relid, var->varattno, false);\n\tvar = copyObject(var);\n\tvar->varattno = get_attnum(chunk_rte->relid, attname);\n\n\t/* Get attribute number for distinct column on a compressed chunk */\n\tif (ts_is_columnar_scan_path(child_path))\n\t{\n\t\t/* distinct column has to be a segmentby column */\n\t\tColumnarScanPath *dcpath = (ColumnarScanPath *) child_path;\n\t\tif (!bms_is_member(var->varattno, dcpath->info->chunk_segmentby_attnos))\n\t\t{\n\t\t\treturn NULL;\n\t\t}\n\t\tskinfo->indexed_column_attno = get_attnum(indexed_rte->relid, attname);\n\t}\n\t/* Get attribute number for distinct column on an uncompressed chunk */\n\telse\n\t{\n\t\tskinfo->indexed_column_attno = var->varattno;\n\t}\n\n\tvar->varno = rel->relid;\n\n\treturn var;\n}\n\n/*\n * Creates SkipScanPath for each path of subpaths that is an IndexPath\n * If no subpath can be changed to SkipScanPath returns NULL\n * otherwise returns list of new paths\n */\nstatic List *\nbuild_subpath(PlannerInfo *root, List *subpaths, DistinctPathInfo *dpinfo, List *top_pathkeys)\n{\n\tbool has_skip_path = false;\n\tList *new_paths = NIL;\n\tListCell *lc;\n\n\tforeach (lc, subpaths)\n\t{\n\t\tPath *child = lfirst(lc);\n\t\tif (IsA(child, IndexPath) || ts_is_columnar_scan_path(child))\n\t\t{\n\t\t\tif (top_pathkeys && !pathkeys_contained_in(top_pathkeys, child->pathkeys))\n\t\t\t\tcontinue;\n\n\t\t\tSkipScanPath *skip_path = skip_scan_path_create(root, child, dpinfo);\n\n\t\t\tif (skip_path)\n\t\t\t{\n\t\t\t\tchild = (Path *) skip_path;\n\t\t\t\thas_skip_path = true;\n\t\t\t}\n\t\t}\n\n\t\tnew_paths = lappend(new_paths, child);\n\t}\n\n\tif (!has_skip_path && new_paths)\n\t{\n\t\tpfree(new_paths);\n\t\treturn NIL;\n\t}\n\n\treturn new_paths;\n}\n\nstatic bool\nbuild_skip_qual(PlannerInfo *root, SkipKeyInfo *skinfo, IndexPath *index_path, Var *var,\n\t\t\t\tbool build_eqop)\n{\n\tIndexOptInfo *info = index_path->indexinfo;\n\tOid column_type = exprType((Node *) var);\n\tOid column_collation = get_typcollation(column_type);\n\tTypeCacheEntry *tce = lookup_type_cache(column_type, 0);\n\tbool need_coerce = false;\n\n\t/*\n\t * Skipscan is not applicable for the following case:\n\t * We might have a path with an index that produces the correct pathkeys for the target ordering\n\t * without actually including all the columns of the ORDER BY. If the path uses an index that\n\t * does not include the distinct column, we cannot use it for skipscan and have to discard this\n\t * path from skipscan generation. This happens, for instance, when we have an order by clause\n\t * (like ORDER BY a, b) with constraints in the WHERE clause (like WHERE a = <constant>) . \"a\"\n\t * can now be removed from the Pathkeys (since it is a constant) and the query can be satisfied\n\t * by using an index on just column \"b\".\n\t *\n\t * Example query:\n\t * SELECT DISTINCT ON (a) * FROM test WHERE a in (2) ORDER BY a ASC, time DESC;\n\t * Since a is always 2 due to the WHERE clause we can create the correct ordering for the\n\t * ORDER BY with an index that does not include the a column and only includes the time column.\n\t */\n\tint idx_key = get_idx_key(index_path->indexinfo, skinfo->indexed_column_attno);\n\tif (idx_key < 0)\n\t\treturn false;\n\n\t/* sk_attno of the skip qual */\n\tskinfo->scankey_attno = idx_key + 1;\n\n\tskinfo->distinct_attno = var->varattno;\n\tskinfo->distinct_by_val = tce->typbyval;\n\tskinfo->distinct_typ_len = tce->typlen;\n\n\tint16 strategy = info->reverse_sort[idx_key] ? BTLessStrategyNumber : BTGreaterStrategyNumber;\n\tif (index_path->indexscandir == BackwardScanDirection)\n\t{\n\t\tstrategy =\n\t\t\t(strategy == BTLessStrategyNumber) ? BTGreaterStrategyNumber : BTLessStrategyNumber;\n\t}\n\tOid opcintype = info->opcintype[idx_key];\n\n\tOid comparator =\n\t\tget_opfamily_member(info->sortopfamily[idx_key], column_type, column_type, strategy);\n\n\tOid eqop = InvalidOid;\n\tif (build_eqop)\n\t\teqop = get_opfamily_member(info->sortopfamily[idx_key],\n\t\t\t\t\t\t\t\t   column_type,\n\t\t\t\t\t\t\t\t   column_type,\n\t\t\t\t\t\t\t\t   BTEqualStrategyNumber);\n\n\t/* If there is no exact operator match for the column type we have here check\n\t * if we can coerce to the type of the operator class. */\n\tif (!OidIsValid(comparator))\n\t{\n\t\tif (IsBinaryCoercible(column_type, opcintype))\n\t\t{\n\t\t\tcomparator =\n\t\t\t\tget_opfamily_member(info->sortopfamily[idx_key], opcintype, opcintype, strategy);\n\t\t\tif (!OidIsValid(comparator))\n\t\t\t\treturn false;\n\n\t\t\tif (build_eqop)\n\t\t\t\teqop = get_opfamily_member(info->sortopfamily[idx_key],\n\t\t\t\t\t\t\t\t\t\t   opcintype,\n\t\t\t\t\t\t\t\t\t\t   opcintype,\n\t\t\t\t\t\t\t\t\t\t   BTEqualStrategyNumber);\n\t\t\tneed_coerce = true;\n\t\t}\n\t\telse\n\t\t\treturn false; /* cannot use this index */\n\t}\n\n\tConst *prev_val = makeNullConst(need_coerce ? opcintype : column_type, -1, column_collation);\n\tExpr *current_val = (Expr *) makeVar(info->rel->relid /*varno*/,\n\t\t\t\t\t\t\t\t\t\t skinfo->indexed_column_attno /*varattno*/,\n\t\t\t\t\t\t\t\t\t\t column_type /*vartype*/,\n\t\t\t\t\t\t\t\t\t\t -1 /*vartypmod*/,\n\t\t\t\t\t\t\t\t\t\t column_collation /*varcollid*/,\n\t\t\t\t\t\t\t\t\t\t 0 /*varlevelsup*/);\n\n\tif (need_coerce)\n\t{\n\t\tCoerceViaIO *coerce = makeNode(CoerceViaIO);\n\t\tcoerce->arg = current_val;\n\t\tcoerce->resulttype = opcintype;\n\t\tcoerce->resultcollid = column_collation;\n\t\tcoerce->coerceformat = COERCE_IMPLICIT_CAST;\n\t\tcoerce->location = -1;\n\n\t\tcurrent_val = &coerce->xpr;\n\t}\n\n\tExpr *comparison_expr = make_opclause(comparator,\n\t\t\t\t\t\t\t\t\t\t  BOOLOID /*opresulttype*/,\n\t\t\t\t\t\t\t\t\t\t  false /*opretset*/,\n\t\t\t\t\t\t\t\t\t\t  current_val /*leftop*/,\n\t\t\t\t\t\t\t\t\t\t  &prev_val->xpr /*rightop*/,\n\t\t\t\t\t\t\t\t\t\t  InvalidOid /*opcollid*/,\n\t\t\t\t\t\t\t\t\t\t  info->indexcollations[idx_key] /*inputcollid*/);\n\tset_opfuncid(castNode(OpExpr, comparison_expr));\n\tskinfo->eqcomp = (build_eqop ? get_opcode(eqop) : InvalidOid);\n\n\tskinfo->skip_clause = make_simple_restrictinfo(root, comparison_expr);\n\n\treturn true;\n}\n\nstatic int\nget_idx_key(IndexOptInfo *idxinfo, AttrNumber attno)\n{\n\tfor (int i = 0; i < idxinfo->nkeycolumns; i++)\n\t{\n\t\tif (attno == idxinfo->indexkeys[i])\n\t\t\treturn i;\n\t}\n\treturn -1;\n}\n\n/* Sort quals according to index column order.\n * ScanKeys need to be sorted by the position of the index column\n * they are referencing but since we don't want to adjust actual\n * ScanKey array we presort qual list when creating plan.\n */\nstatic List *\nsort_indexquals(IndexOptInfo *indexinfo, List *quals)\n{\n\tList *indexclauses[INDEX_MAX_KEYS] = { 0 };\n\tList *ordered_list = NIL;\n\tListCell *lc;\n\tint i;\n\n\tforeach (lc, quals)\n\t{\n\t\tBitmapset *bms = NULL;\n\t\tpull_varattnos(lfirst(lc), INDEX_VAR, &bms);\n\t\tAssert(bms_num_members(bms) >= 1);\n\n\t\ti = bms_next_member(bms, -1) + FirstLowInvalidHeapAttributeNumber - 1;\n\t\tindexclauses[i] = lappend(indexclauses[i], lfirst(lc));\n\t}\n\n\tfor (i = 0; i < indexinfo->nkeycolumns; i++)\n\t{\n\t\tif (indexclauses[i] != NIL)\n\t\t\tordered_list = list_concat(ordered_list, indexclauses[i]);\n\t}\n\n\treturn ordered_list;\n}\n\nstatic OpExpr *\nfix_indexqual(IndexOptInfo *index, RestrictInfo *rinfo, AttrNumber scankey_attno)\n{\n\t/* technically our placeholder col > NULL is unsatisfiable, and in some instances\n\t * the planner will realize this and use is as an excuse to remove other quals.\n\t * in order to prevent this, we prepare this qual ourselves.\n\t */\n\n\t/* fix_indexqual_references */\n\tOpExpr *op = copyObject(castNode(OpExpr, rinfo->clause));\n\tAssert(list_length(op->args) == 2);\n\tAssert(bms_equal(rinfo->left_relids, index->rel->relids));\n\n\t/* fix_indexqual_operand */\n\tAssert(index->indexkeys[scankey_attno - 1] != 0);\n\tVar *node = linitial_node(Var, pull_var_clause(linitial(op->args), 0));\n\n\tAssert((Index) ((Var *) node)->varno == index->rel->relid &&\n\t\t   ((Var *) node)->varattno == index->indexkeys[scankey_attno - 1]);\n\n\tVar *result = (Var *) copyObject(node);\n\tresult->varno = INDEX_VAR;\n\tresult->varattno = scankey_attno;\n\n\tlinitial(op->args) = result;\n\n\treturn op;\n}\n\n/*\n * tlist_member_match_var\n *    Same as tlist_member, except that we match the provided Var on the basis\n *    of varno/varattno/varlevelsup/vartype only, rather than full equal().\n *\n * This is needed in some cases where we can't be sure of an exact typmod\n * match.  For safety, though, we insist on vartype match.\n *\n * static function copied from src/backend/optimizer/util/tlist.c\n */\nstatic TargetEntry *\ntlist_member_match_var(Var *var, List *targetlist)\n{\n\tListCell *temp;\n\n\tforeach (temp, targetlist)\n\t{\n\t\tTargetEntry *tlentry = (TargetEntry *) lfirst(temp);\n\t\tVar *tlvar = (Var *) tlentry->expr;\n\n\t\tif (!tlvar || !IsA(tlvar, Var))\n\t\t\tcontinue;\n\t\tif (var->varno == tlvar->varno && var->varattno == tlvar->varattno &&\n\t\t\tvar->varlevelsup == tlvar->varlevelsup && var->vartype == tlvar->vartype)\n\t\t\treturn tlentry;\n\t}\n\treturn NULL;\n}\n"
  },
  {
    "path": "tsl/src/nodes/skip_scan/skip_scan.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <nodes/plannodes.h>\n\ntypedef enum SkipKeyNullStatus\n{\n\tSK_NOT_NULL = 0,\n\tSK_NULLS_FIRST,\n\tSK_NULLS_LAST\n} SkipKeyNullStatus;\n\ntypedef enum\n{\n\tSK_DistinctColAttno = 0,\n\tSK_DistinctByVal = 1,\n\tSK_DistinctTypeLen = 2,\n\tSK_NullStatus = 3,\n\tSK_IndexKeyAttno = 4\n} SkipScanPrivateIndex;\n\nextern void tsl_skip_scan_paths_add(PlannerInfo *root, RelOptInfo *input_rel,\n\t\t\t\t\t\t\t\t\tRelOptInfo *output_rel, UpperRelationKind stage);\nextern Node *tsl_skip_scan_state_create(CustomScan *cscan);\nextern void _skip_scan_init(void);\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/CMakeLists.txt",
    "content": "add_subdirectory(function)\nadd_subdirectory(hashing)\nset(SOURCES\n    ${CMAKE_CURRENT_SOURCE_DIR}/exec.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/grouping_policy_batch.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/grouping_policy_hash.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/plan.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/plan_columnar_scan.c)\ntarget_sources(${TSL_LIBRARY_NAME} PRIVATE ${SOURCES})\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/exec.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n\n#include <commands/explain.h>\n#include <executor/executor.h>\n#include <executor/tuptable.h>\n#include <fmgr.h>\n#include <funcapi.h>\n#include <nodes/extensible.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodeFuncs.h>\n#include <nodes/pg_list.h>\n#include <optimizer/optimizer.h>\n\n#include \"nodes/vector_agg/exec.h\"\n\n#include \"compat/compat.h\"\n#include \"compression/arrow_c_data_interface.h\"\n#include \"nodes/columnar_scan/columnar_scan.h\"\n#include \"nodes/columnar_scan/compressed_batch.h\"\n#include \"nodes/columnar_scan/exec.h\"\n#include \"nodes/columnar_scan/vector_quals.h\"\n#include \"nodes/vector_agg.h\"\n#include \"nodes/vector_agg/filter_word_iterator.h\"\n#include \"nodes/vector_agg/plan.h\"\n#include \"nodes/vector_agg/vector_slot.h\"\n\n#if PG18_GE\n#include \"commands/explain_format.h\"\n#include \"commands/explain_state.h\"\n#endif\n\nstatic CompressedBatchVectorQualState\ncompressed_batch_init_vector_quals(DecompressContext *dcontext, List *quals, TupleTableSlot *slot);\n\nstatic int\nget_input_offset(const DecompressContext *dcontext, const Var *var)\n{\n\tconst CompressionColumnDescription *value_column_description = NULL;\n\tfor (int i = 0; i < dcontext->num_data_columns; i++)\n\t{\n\t\tconst CompressionColumnDescription *current_column = &dcontext->compressed_chunk_columns[i];\n\t\tif (current_column->uncompressed_chunk_attno == var->varattno)\n\t\t{\n\t\t\tvalue_column_description = current_column;\n\t\t\tbreak;\n\t\t}\n\t}\n\tEnsure(value_column_description != NULL, \"aggregated compressed column not found\");\n\n\tAssert(value_column_description->type == COMPRESSED_COLUMN ||\n\t\t   value_column_description->type == SEGMENTBY_COLUMN);\n\n\tconst int index = value_column_description - dcontext->compressed_chunk_columns;\n\treturn index;\n}\n\n/*\n * Workspace for converting the results of a Postgres function into a columnar\n * format.\n */\ntypedef struct\n{\n\tDecompressionType type;\n\n\tuint64 *restrict validity;\n\n\tint allocated_body_bytes;\n\tuint8 *restrict body_buffer;\n\n\tuint32 *restrict offset_buffer;\n\tuint32 current_offset;\n} ColumnarResult;\n\nstatic void\ncolumnar_result_init_for_type(ColumnarResult *columnar_result,\n\t\t\t\t\t\t\t  DecompressBatchState const *batch_state, Oid typeoid)\n{\n\tint16 typlen;\n\tbool typbyval;\n\tget_typlenbyval(typeoid, &typlen, &typbyval);\n\tif (typeoid == BOOLOID)\n\t{\n\t\tcolumnar_result->type = DT_ArrowBits;\n\t}\n\telse if (typlen == -1)\n\t{\n\t\tcolumnar_result->type = DT_ArrowText;\n\t}\n\telse\n\t{\n\t\tAssert(typlen > 0);\n\t\tcolumnar_result->type = typlen;\n\t}\n\n\tconst int nrows = batch_state->total_batch_rows;\n\tconst size_t num_validity_words = (nrows + 63) / 64;\n\tif (columnar_result->type == DT_ArrowBits)\n\t{\n\t\tcolumnar_result->allocated_body_bytes = sizeof(uint64) * num_validity_words;\n\t}\n\telse if (columnar_result->type == DT_ArrowText)\n\t{\n\t\t/*\n\t\t * Arrow variable-length types require n + 1 offsets to store the end\n\t\t * position of the last element. Pad to 64 bytes per Arrow spec.\n\t\t */\n\t\tcolumnar_result->offset_buffer =\n\t\t\tMemoryContextAllocZero(batch_state->per_batch_context,\n\t\t\t\t\t\t\t\t   pad_to_multiple(64,\n\t\t\t\t\t\t\t\t\t\t\t\t   sizeof(*columnar_result->offset_buffer) *\n\t\t\t\t\t\t\t\t\t\t\t\t\t   (nrows + 1)));\n\t\tcolumnar_result->allocated_body_bytes = pad_to_multiple(64, 10);\n\t}\n\telse\n\t{\n\t\tAssert(columnar_result->type > 0);\n\t\tcolumnar_result->allocated_body_bytes =\n\t\t\tpad_to_multiple(64, 1 + columnar_result->type * nrows);\n\t}\n\n\tcolumnar_result->body_buffer = MemoryContextAllocZero(batch_state->per_batch_context,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  columnar_result->allocated_body_bytes);\n}\n\nstatic pg_attribute_always_inline void\ncolumnar_result_set_row(ColumnarResult *columnar_result, DecompressBatchState const *batch_state,\n\t\t\t\t\t\tint row, Datum datum, bool isnull)\n{\n\tconst int nrows = batch_state->total_batch_rows;\n\tAssert(row < nrows);\n\n\tif (isnull)\n\t{\n\t\tif (columnar_result->validity == NULL)\n\t\t{\n\t\t\tconst int num_validity_words = (nrows + 63) / 64;\n\t\t\tcolumnar_result->validity =\n\t\t\t\tMemoryContextAlloc(batch_state->per_batch_context,\n\t\t\t\t\t\t\t\t   num_validity_words * sizeof(*columnar_result->validity));\n\t\t\tmemset(columnar_result->validity,\n\t\t\t\t   -1,\n\t\t\t\t   num_validity_words * sizeof(*columnar_result->validity));\n\t\t\tif (nrows % 64 != 0)\n\t\t\t{\n\t\t\t\tconst uint64 tail_mask = ~0ULL >> (64 - nrows % 64);\n\t\t\t\tcolumnar_result->validity[nrows / 64] &= tail_mask;\n\t\t\t}\n\t\t}\n\n\t\tarrow_set_row_validity(columnar_result->validity, row, false);\n\n\t\treturn;\n\t}\n\n\tswitch ((int) columnar_result->type)\n\t{\n\t\tcase DT_ArrowBits:\n\t\t{\n\t\t\tarrow_set_row_validity((uint64 *restrict) columnar_result->body_buffer,\n\t\t\t\t\t\t\t\t   row,\n\t\t\t\t\t\t\t\t   DatumGetBool(datum));\n\t\t\tbreak;\n\t\t}\n\t\tcase DT_ArrowText:\n\t\t{\n\t\t\tconst int result_bytes = VARSIZE_ANY_EXHDR(datum);\n\t\t\tconst int required_body_bytes =\n\t\t\t\tpad_to_multiple(64, columnar_result->current_offset + result_bytes);\n\t\t\tif (required_body_bytes > columnar_result->allocated_body_bytes)\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * We reallocate based on how many rows in the batch we have\n\t\t\t\t * left, not to overshoot too much. At the same time, we\n\t\t\t\t * shouldn't reallocate too often either. The parameters were\n\t\t\t\t * tuned manually on a few real data sets until this balance\n\t\t\t\t * looked somewhat acceptable.\n\t\t\t\t */\n\t\t\t\tconst int new_body_bytes =\n\t\t\t\t\trequired_body_bytes * Min(10, Max(1.2, 1.2 * nrows / ((float) row + 1))) + 1;\n\t\t\t\tAssert(new_body_bytes >= required_body_bytes);\n\t\t\t\tcolumnar_result->body_buffer =\n\t\t\t\t\trepalloc(columnar_result->body_buffer, new_body_bytes);\n\t\t\t\tcolumnar_result->allocated_body_bytes = new_body_bytes;\n\t\t\t}\n\n\t\t\tmemcpy(&columnar_result->body_buffer[columnar_result->current_offset],\n\t\t\t\t   VARDATA_ANY(datum),\n\t\t\t\t   result_bytes);\n\t\t\tcolumnar_result->offset_buffer[row] = columnar_result->current_offset;\n\t\t\tcolumnar_result->current_offset += result_bytes;\n\t\t\tbreak;\n\t\t}\n\t\tcase 2:\n\t\tcase 4:\n#ifdef USE_FLOAT8_BYVAL\n\t\tcase 8:\n#endif\n\t\t\tmemcpy(row * columnar_result->type + (uint8 *restrict) columnar_result->body_buffer,\n\t\t\t\t   &datum,\n\t\t\t\t   sizeof(Datum));\n\t\t\tbreak;\n#ifndef USE_FLOAT8_BYVAL\n\t\tcase 8:\n#endif\n\t\tcase 16:\n\t\t\tmemcpy(row * columnar_result->type + (uint8 *restrict) columnar_result->body_buffer,\n\t\t\t\t   DatumGetPointer(datum),\n\t\t\t\t   columnar_result->type);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\telog(ERROR, \"wrong arrow result type %d\", columnar_result->type);\n\t}\n}\n\nstatic CompressedColumnValues\ncolumnar_result_finalize(ColumnarResult *columnar_result, DecompressBatchState const *batch_state)\n{\n\tconst int nrows = batch_state->total_batch_rows;\n\n\tArrowArray *arrow_result = NULL;\n\tif (columnar_result->type == DT_ArrowBits)\n\t{\n\t\tarrow_result = MemoryContextAllocZero(batch_state->per_batch_context,\n\t\t\t\t\t\t\t\t\t\t\t  sizeof(ArrowArray) + 2 * sizeof(void *));\n\t\tarrow_result->buffers = (void *) &arrow_result[1];\n\t\tarrow_result->buffers[1] = columnar_result->body_buffer;\n\t}\n\telse if (columnar_result->type == DT_ArrowText)\n\t{\n\t\tcolumnar_result->offset_buffer[nrows] = columnar_result->current_offset;\n\n\t\tarrow_result = MemoryContextAllocZero(batch_state->per_batch_context,\n\t\t\t\t\t\t\t\t\t\t\t  sizeof(ArrowArray) + 3 * sizeof(void *));\n\t\tarrow_result->buffers = (void *) &arrow_result[1];\n\t\tarrow_result->buffers[1] = columnar_result->offset_buffer;\n\t\tarrow_result->buffers[2] = columnar_result->body_buffer;\n\t}\n\telse\n\t{\n\t\tAssert(columnar_result->type > 0);\n\n\t\tarrow_result = MemoryContextAllocZero(batch_state->per_batch_context,\n\t\t\t\t\t\t\t\t\t\t\t  sizeof(ArrowArray) + 2 * sizeof(void *));\n\t\tarrow_result->buffers = (void *) &arrow_result[1];\n\t\tarrow_result->buffers[1] = columnar_result->body_buffer;\n\t}\n\n\tarrow_result->length = nrows;\n\n\tarrow_result->buffers[0] = columnar_result->validity;\n\tarrow_result->null_count =\n\t\tarrow_result->length - arrow_num_valid(arrow_result->buffers[0], nrows);\n\n\tCompressedColumnValues result = {\n\t\t.decompression_type = columnar_result->type,\n\t\t.buffers = { arrow_result->buffers[0],\n\t\t\t\t\t arrow_result->buffers[1],\n\t\t\t\t\t columnar_result->type == DT_ArrowText ? arrow_result->buffers[2] : NULL },\n\t\t.arrow = arrow_result,\n\t};\n\treturn result;\n}\n\nstatic pg_noinline CompressedColumnValues\nvector_slot_evaluate_function(DecompressContext *dcontext, TupleTableSlot *slot,\n\t\t\t\t\t\t\t  uint64 const *filter, List *args, Oid funcoid, Oid inputcollid)\n{\n\tconst DecompressBatchState *batch_state = (const DecompressBatchState *) slot;\n\n\tconst int nargs = list_length(args);\n\n\tFmgrInfo flinfo;\n\tfmgr_info(funcoid, &flinfo);\n\tFunctionCallInfo fcinfo = palloc0(SizeForFunctionCallInfo(nargs));\n\tInitFunctionCallInfoData(*fcinfo, &flinfo, nargs, inputcollid, NULL, NULL);\n\n\tCompressedColumnValues *arg_values = palloc0(nargs * sizeof(*arg_values));\n\tbool have_null_bitmap = false;\n\tbool have_null_scalars = false;\n\tListCell *lc;\n\tforeach (lc, args)\n\t{\n\t\tconst int i = foreach_current_index(lc);\n\t\tCompressedColumnValues arg_value =\n\t\t\tvector_slot_evaluate_expression(dcontext, slot, filter, lfirst(lc));\n\t\tEnsure(arg_value.decompression_type != DT_Invalid, \"got DT_Invalid for argument %d\", i);\n\n\t\thave_null_bitmap =\n\t\t\t(arg_value.arrow != NULL && arg_value.arrow->null_count > 0) || have_null_bitmap;\n\n\t\targ_value.output_value = &fcinfo->args[i].value;\n\t\targ_value.output_isnull = &fcinfo->args[i].isnull;\n\n\t\tif (arg_value.decompression_type == DT_ArrowText ||\n\t\t\targ_value.decompression_type == DT_ArrowTextDict)\n\t\t{\n\t\t\tconst int maxbytes = get_max_varlena_bytes(arg_value.arrow);\n\t\t\t*arg_value.output_value =\n\t\t\t\tPointerGetDatum(MemoryContextAlloc(batch_state->per_batch_context, maxbytes));\n\t\t}\n\t\telse if (arg_value.decompression_type == DT_Scalar)\n\t\t{\n\t\t\t/*\n\t\t\t * The values of the scalar columns have to be stored once at\n\t\t\t * initialization, they won't be updated per-row.\n\t\t\t */\n\t\t\t*arg_value.output_value = PointerGetDatum(arg_value.buffers[1]);\n\t\t\t*arg_value.output_isnull = DatumGetBool(PointerGetDatum(arg_value.buffers[0]));\n\n\t\t\thave_null_scalars = *arg_value.output_isnull || have_null_scalars;\n\t\t}\n\n\t\targ_values[i] = arg_value;\n\t}\n\n\t/*\n\t * We only evaluate strict functions, so if we have a scalar null argument,\n\t * return a scalar null.\n\t */\n\tif (have_null_scalars)\n\t{\n\t\tpfree(fcinfo);\n\t\tpfree(arg_values);\n\t\treturn (CompressedColumnValues){ .decompression_type = DT_Scalar,\n\t\t\t\t\t\t\t\t\t\t .buffers[0] = DatumGetPointer(BoolGetDatum(true)) };\n\t}\n\n\t/*\n\t * Our Postgres function is strict, so we should avoid calling it on null\n\t * inputs.\n\t */\n\tconst int nrows = batch_state->total_batch_rows;\n\tconst size_t num_validity_words = (nrows + 63) / 64;\n\tuint64 *input_validity = NULL;\n\tif (have_null_bitmap || filter != NULL)\n\t{\n\t\tuint64 *restrict combined_validity =\n\t\t\tMemoryContextAlloc(batch_state->per_batch_context,\n\t\t\t\t\t\t\t   sizeof(*combined_validity) * num_validity_words);\n\t\tmemset(combined_validity, -1, num_validity_words * sizeof(*combined_validity));\n\t\tarrow_validity_and(num_validity_words, combined_validity, filter);\n\t\tfor (int i = 0; i < nargs; i++)\n\t\t{\n\t\t\tarrow_validity_and(num_validity_words, combined_validity, arg_values[i].buffers[0]);\n\t\t}\n\t\tinput_validity = combined_validity;\n\t}\n\n\t/*\n\t * Call the Postgres function on every row. Here as well, we have to deal\n\t * with very selective filters and avoid evaluating the functions on long\n\t * consecutive ranges of filtered out rows, to improve the performance.\n\t */\n\tColumnarResult columnar_result = { 0 };\n\tcolumnar_result_init_for_type(&columnar_result, batch_state, get_func_rettype(funcoid));\n\tMemoryContext function_call_context =\n\t\tAllocSetContextCreate(CurrentMemoryContext, \"bulk function call\", ALLOCSET_DEFAULT_SIZES);\n\tMemoryContext old = MemoryContextSwitchTo(function_call_context);\n\tFilterWordIterator iter = filter_word_iterator_init(nrows, input_validity);\n\tint last_processed_row = 0;\n\tfor (;;)\n\t{\n\t\t/*\n\t\t * The Arrow format requires the offsets to monotonically increase even\n\t\t * for the invalid rows.\n\t\t */\n\t\tif (columnar_result.offset_buffer != NULL)\n\t\t{\n\t\t\tfor (int row = last_processed_row; row < iter.start_row; row++)\n\t\t\t{\n\t\t\t\tcolumnar_result.offset_buffer[row] = columnar_result.current_offset;\n\t\t\t}\n\t\t}\n\t\tif (!filter_word_iterator_is_valid(&iter))\n\t\t{\n\t\t\tbreak;\n\t\t}\n\t\tfor (int row = iter.start_row; row < iter.end_row; row++)\n\t\t{\n\t\t\t/*\n\t\t\t * The Arrow format requires the offsets to monotonically increase even\n\t\t\t * for the invalid rows.\n\t\t\t */\n\t\t\tif (columnar_result.offset_buffer != NULL)\n\t\t\t{\n\t\t\t\tcolumnar_result.offset_buffer[row] = columnar_result.current_offset;\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * Do not evaluate the function on null inputs because it is strict.\n\t\t\t */\n\t\t\tif (!arrow_row_is_valid(input_validity, row))\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tcompressed_columns_to_postgres_data(arg_values, nargs, row);\n\n\t\t\tconst Datum datum = FunctionCallInvoke(fcinfo);\n\n\t\t\t/*\n\t\t\t * A strict function can still return a null for a non-null argument.\n\t\t\t */\n\t\t\tconst bool isnull = fcinfo->isnull;\n\n\t\t\tcolumnar_result_set_row(&columnar_result, batch_state, row, datum, isnull);\n\n\t\t\tMemoryContextReset(function_call_context);\n\t\t}\n\n\t\tlast_processed_row = iter.end_row;\n\t\tfilter_word_iterator_advance(&iter);\n\t}\n\tMemoryContextSwitchTo(old);\n\tMemoryContextDelete(function_call_context);\n\n\t/*\n\t * Figure out the validity bitmap of the result rows. Besides the null\n\t * inputs, the function itself can return nulls for some rows.\n\t */\n\tif (columnar_result.validity != NULL)\n\t{\n\t\tarrow_validity_and(num_validity_words, columnar_result.validity, input_validity);\n\t}\n\telse\n\t{\n\t\tcolumnar_result.validity = (uint64 *) input_validity;\n\t}\n\n\tpfree(fcinfo);\n\tpfree(arg_values);\n\n\treturn columnar_result_finalize(&columnar_result, batch_state);\n}\n\n/*\n * Return the arrow array or the datum (in case of single scalar value) for a\n * given expression as a CompressedColumnValues struct.\n */\nCompressedColumnValues\nvector_slot_evaluate_expression(DecompressContext *dcontext, TupleTableSlot *slot,\n\t\t\t\t\t\t\t\tuint64 const *filter, const Expr *argument)\n{\n\tconst DecompressBatchState *batch_state = (const DecompressBatchState *) slot;\n\tswitch (((Node *) argument)->type)\n\t{\n\t\tcase T_Const:\n\t\t{\n\t\t\tconst Const *c = (const Const *) argument;\n\t\t\tCompressedColumnValues result = { .decompression_type = DT_Scalar,\n\t\t\t\t\t\t\t\t\t\t\t  .buffers[1] = DatumGetPointer(c->constvalue),\n\t\t\t\t\t\t\t\t\t\t\t  .buffers[0] =\n\t\t\t\t\t\t\t\t\t\t\t\t  DatumGetPointer(BoolGetDatum(c->constisnull)) };\n\t\t\treturn result;\n\t\t}\n\t\tcase T_Var:\n\t\t{\n\t\t\tconst Var *var = (const Var *) argument;\n\t\t\tconst uint16 offset = get_input_offset(dcontext, var);\n\t\t\tconst CompressedColumnValues *values = &batch_state->compressed_columns[offset];\n\t\t\tEnsure(values->decompression_type != DT_Invalid,\n\t\t\t\t   \"got DT_Invalid decompression type at offset %d\",\n\t\t\t\t   offset);\n\t\t\treturn *values;\n\t\t}\n\t\tcase T_OpExpr:\n\t\t{\n\t\t\tconst OpExpr *o = (const OpExpr *) argument;\n\t\t\treturn vector_slot_evaluate_function(dcontext,\n\t\t\t\t\t\t\t\t\t\t\t\t slot,\n\t\t\t\t\t\t\t\t\t\t\t\t filter,\n\t\t\t\t\t\t\t\t\t\t\t\t o->args,\n\t\t\t\t\t\t\t\t\t\t\t\t o->opfuncid,\n\t\t\t\t\t\t\t\t\t\t\t\t o->inputcollid);\n\t\t}\n\t\tcase T_FuncExpr:\n\t\t{\n\t\t\tconst FuncExpr *f = (const FuncExpr *) argument;\n\t\t\treturn vector_slot_evaluate_function(dcontext,\n\t\t\t\t\t\t\t\t\t\t\t\t slot,\n\t\t\t\t\t\t\t\t\t\t\t\t filter,\n\t\t\t\t\t\t\t\t\t\t\t\t f->args,\n\t\t\t\t\t\t\t\t\t\t\t\t f->funcid,\n\t\t\t\t\t\t\t\t\t\t\t\t f->inputcollid);\n\t\t}\n\t\tdefault:\n\t\t\tEnsure(false,\n\t\t\t\t   \"wrong node type %s for vector expression\",\n\t\t\t\t   ts_get_node_name((Node *) argument));\n\t\t\treturn (CompressedColumnValues){ .decompression_type = DT_Invalid };\n\t}\n}\n\nstatic void\nvector_agg_begin(CustomScanState *node, EState *estate, int eflags)\n{\n\tCustomScan *cscan = castNode(CustomScan, node->ss.ps.plan);\n\tnode->custom_ps =\n\t\tlappend(node->custom_ps, ExecInitNode(linitial(cscan->custom_plans), estate, eflags));\n\n\tVectorAggState *vector_agg_state = (VectorAggState *) node;\n\tvector_agg_state->input_ended = false;\n\n\t/*\n\t * Set up the helper structures used to evaluate stable expressions in\n\t * vectorized FILTER clauses.\n\t */\n\tPlannerGlobal glob = {\n\t\t.boundParams = node->ss.ps.state->es_param_list_info,\n\t};\n\tPlannerInfo root = {\n\t\t.glob = &glob,\n\t};\n\n\t/*\n\t * The aggregated targetlist with Aggrefs is in the custom scan targetlist\n\t * of the custom scan node that is performing the vectorized aggregation.\n\t * We do this to avoid projections at this node, because the postgres\n\t * projection functions complain when they see an Aggref in a custom\n\t * node output targetlist.\n\t * The output targetlist, in turn, consists of just the INDEX_VAR references\n\t * into the custom_scan_tlist.\n\t * Now, iterate through the aggregated targetlist to collect aggregates and\n\t * output grouping columns.\n\t */\n\tList *aggregated_tlist =\n\t\tcastNode(CustomScan, vector_agg_state->custom.ss.ps.plan)->custom_scan_tlist;\n\tconst int tlist_length = list_length(aggregated_tlist);\n\n\t/*\n\t * First, count how many grouping columns and aggregate functions we have.\n\t */\n\tint agg_functions_counter = 0;\n\tint grouping_column_counter = 0;\n\tfor (int i = 0; i < tlist_length; i++)\n\t{\n\t\tTargetEntry *tlentry = list_nth_node(TargetEntry, aggregated_tlist, i);\n\t\tif (IsA(tlentry->expr, Aggref))\n\t\t{\n\t\t\tagg_functions_counter++;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* This is a grouping column. */\n\t\t\tgrouping_column_counter++;\n\t\t}\n\t}\n\tAssert(agg_functions_counter + grouping_column_counter == tlist_length);\n\n\t/*\n\t * Allocate the storage for definitions of aggregate function and grouping\n\t * columns.\n\t */\n\tvector_agg_state->num_agg_defs = agg_functions_counter;\n\tvector_agg_state->agg_defs =\n\t\tpalloc0(sizeof(*vector_agg_state->agg_defs) * vector_agg_state->num_agg_defs);\n\n\tvector_agg_state->num_grouping_columns = grouping_column_counter;\n\tvector_agg_state->grouping_columns = palloc0(sizeof(*vector_agg_state->grouping_columns) *\n\t\t\t\t\t\t\t\t\t\t\t\t vector_agg_state->num_grouping_columns);\n\n\t/*\n\t * Loop through the aggregated targetlist again and fill the definitions.\n\t */\n\tagg_functions_counter = 0;\n\tgrouping_column_counter = 0;\n\tfor (int i = 0; i < tlist_length; i++)\n\t{\n\t\tTargetEntry *tlentry = list_nth_node(TargetEntry, aggregated_tlist, i);\n\t\tif (IsA(tlentry->expr, Aggref))\n\t\t{\n\t\t\t/* This is an aggregate function. */\n\t\t\tVectorAggDef *def = &vector_agg_state->agg_defs[agg_functions_counter++];\n\t\t\tdef->output_offset = i;\n\n\t\t\tAggref *aggref = castNode(Aggref, tlentry->expr);\n\n\t\t\tVectorAggFunctions *func = get_vector_aggregate(aggref->aggfnoid, aggref->inputcollid);\n\t\t\tAssert(func != NULL);\n\t\t\tdef->func = *func;\n\n\t\t\tif (list_length(aggref->args) > 0)\n\t\t\t{\n\t\t\t\tAssert(list_length(aggref->args) == 1);\n\n\t\t\t\t/* The aggregate should be a partial aggregate */\n\t\t\t\tAssert(aggref->aggsplit == AGGSPLIT_INITIAL_SERIAL);\n\n\t\t\t\tdef->argument = castNode(TargetEntry, linitial(aggref->args))->expr;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tdef->argument = NULL;\n\t\t\t}\n\n\t\t\tif (aggref->aggfilter != NULL)\n\t\t\t{\n\t\t\t\tNode *constified = estimate_expression_value(&root, (Node *) aggref->aggfilter);\n\t\t\t\tdef->filter_clauses = list_make1(constified);\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* This is a grouping column. */\n\n\t\t\tGroupingColumn *col = &vector_agg_state->grouping_columns[grouping_column_counter++];\n\t\t\tcol->expr = tlentry->expr;\n\t\t\tcol->output_offset = i;\n\n\t\t\tTupleDesc tdesc = NULL;\n\t\t\tOid type = InvalidOid;\n\t\t\tTypeFuncClass type_class = get_expr_result_type((Node *) tlentry->expr, &type, &tdesc);\n\t\t\tEnsure(type_class == TYPEFUNC_SCALAR,\n\t\t\t\t   \"wrong grouping column type class %d\",\n\t\t\t\t   type_class);\n\t\t\tget_typlenbyval(type, &col->value_bytes, &col->by_value);\n\t\t}\n\t}\n\n\t/*\n\t * Create the grouping policy chosen at plan time.\n\t */\n\tconst VectorAggGroupingType grouping_type =\n\t\tintVal(list_nth(cscan->custom_private, VASI_GroupingType));\n\tif (grouping_type == VAGT_Batch)\n\t{\n\t\t/*\n\t\t * Per-batch grouping.\n\t\t */\n\t\tvector_agg_state->grouping =\n\t\t\tcreate_grouping_policy_batch(vector_agg_state->num_agg_defs,\n\t\t\t\t\t\t\t\t\t\t vector_agg_state->agg_defs,\n\t\t\t\t\t\t\t\t\t\t vector_agg_state->num_grouping_columns,\n\t\t\t\t\t\t\t\t\t\t vector_agg_state->grouping_columns);\n\t}\n\telse\n\t{\n\t\t/*\n\t\t * Hash grouping.\n\t\t */\n\t\tvector_agg_state->grouping =\n\t\t\tcreate_grouping_policy_hash(vector_agg_state->num_agg_defs,\n\t\t\t\t\t\t\t\t\t\tvector_agg_state->agg_defs,\n\t\t\t\t\t\t\t\t\t\tvector_agg_state->num_grouping_columns,\n\t\t\t\t\t\t\t\t\t\tvector_agg_state->grouping_columns,\n\t\t\t\t\t\t\t\t\t\tgrouping_type);\n\t}\n}\n\nstatic void\nvector_agg_end(CustomScanState *node)\n{\n\tExecEndNode(linitial(node->custom_ps));\n}\n\nstatic void\nvector_agg_rescan(CustomScanState *node)\n{\n\tif (node->ss.ps.chgParam != NULL)\n\t\tUpdateChangedParamSet(linitial(node->custom_ps), node->ss.ps.chgParam);\n\n\tExecReScan(linitial(node->custom_ps));\n\n\tVectorAggState *state = (VectorAggState *) node;\n\tstate->input_ended = false;\n\n\tstate->grouping->gp_reset(state->grouping);\n}\n\n/*\n * Get the next slot to aggregate for a compressed batch.\n *\n * Implements \"get next slot\" on top of ColumnarScan. Note that compressed\n * tuples are read directly from the ColumnarScan child node, which means\n * that the processing normally done in ColumnarScan is actually done here\n * (batch processing and filtering).\n *\n * Returns an TupleTableSlot that implements a compressed batch.\n */\nstatic TupleTableSlot *\ncompressed_batch_get_next_slot(VectorAggState *vector_agg_state)\n{\n\tColumnarScanState *decompress_state =\n\t\t(ColumnarScanState *) linitial(vector_agg_state->custom.custom_ps);\n\tDecompressContext *dcontext = &decompress_state->decompress_context;\n\tBatchQueue *batch_queue = decompress_state->batch_queue;\n\tDecompressBatchState *batch_state = batch_array_get_at(&batch_queue->batch_array, 0);\n\n\tdo\n\t{\n\t\t/*\n\t\t * We discard the previous compressed batch here and not earlier,\n\t\t * because the grouping column values returned by the batch grouping\n\t\t * policy are owned by the compressed batch memory context. This is done\n\t\t * to avoid generic value copying in the grouping policy to simplify its\n\t\t * code.\n\t\t */\n\t\tcompressed_batch_discard_tuples(batch_state);\n\n\t\tTupleTableSlot *compressed_slot =\n\t\t\tExecProcNode(linitial(decompress_state->csstate.custom_ps));\n\n\t\tif (TupIsNull(compressed_slot))\n\t\t{\n\t\t\tvector_agg_state->input_ended = true;\n\t\t\treturn NULL;\n\t\t}\n\n\t\tif (dcontext->ps->instrument)\n\t\t{\n\t\t\t/*\n\t\t\t * Ensure proper EXPLAIN output for the underlying ColumnarScan\n\t\t\t * node.\n\t\t\t *\n\t\t\t * This value is normally updated by InstrStopNode(), and is\n\t\t\t * required so that the calculations in InstrEndLoop() run properly.\n\t\t\t * We have to call it manually because we run the underlying\n\t\t\t * ColumnarScan manually and not as a normal Postgres node.\n\t\t\t */\n\t\t\tdcontext->ps->instrument->running = true;\n\t\t}\n\n\t\tcompressed_batch_set_compressed_tuple(dcontext, batch_state, compressed_slot);\n\n\t\t/* If the entire batch is filtered out, then immediately read the next\n\t\t * one */\n\t} while (batch_state->next_batch_row >= batch_state->total_batch_rows);\n\n\t/*\n\t * Count rows filtered out by vectorized filters for EXPLAIN. Normally\n\t * this is done in tuple-by-tuple interface of ColumnarScan, so that\n\t * it doesn't say it filtered out more rows that were returned (e.g.\n\t * with LIMIT). Here we always work in full batches. The batches that\n\t * were fully filtered out, and their rows, were already counted in\n\t * compressed_batch_set_compressed_tuple().\n\t */\n\tconst int not_filtered_rows =\n\t\tarrow_num_valid(batch_state->vector_qual_result, batch_state->total_batch_rows);\n\tInstrCountFiltered1(dcontext->ps, batch_state->total_batch_rows - not_filtered_rows);\n\tif (dcontext->ps->instrument)\n\t{\n\t\t/*\n\t\t * Ensure proper EXPLAIN output for the underlying ColumnarScan\n\t\t * node.\n\t\t *\n\t\t * This value is normally updated by InstrStopNode(), and is\n\t\t * required so that the calculations in InstrEndLoop() run properly.\n\t\t * We have to call it manually because we run the underlying\n\t\t * ColumnarScan manually and not as a normal Postgres node.\n\t\t */\n\t\tdcontext->ps->instrument->tuplecount += not_filtered_rows;\n\t}\n\n\treturn &batch_state->decompressed_scan_slot_data.base;\n}\n\n/*\n * Initialize vector quals for a compressed batch.\n *\n * Used to implement vectorized aggregate function filter clause.\n */\nstatic CompressedBatchVectorQualState\ncompressed_batch_init_vector_quals(DecompressContext *dcontext, List *quals, TupleTableSlot *slot)\n{\n\tDecompressBatchState *batch_state = (DecompressBatchState *) slot;\n\n\treturn (CompressedBatchVectorQualState) {\n\t\t\t\t.vqstate = {\n\t\t\t\t\t.vectorized_quals_constified = quals,\n\t\t\t\t\t.num_results = batch_state->total_batch_rows,\n\t\t\t\t\t.per_vector_mcxt = batch_state->per_batch_context,\n\t\t\t\t\t.slot = slot,\n\t\t\t\t\t.get_arrow_array = compressed_batch_get_arrow_array,\n\t\t\t\t},\n\t\t\t\t.batch_state = batch_state,\n\t\t\t\t.dcontext = dcontext,\n\t\t\t};\n}\n\nstatic TupleTableSlot *\nvector_agg_exec(CustomScanState *node)\n{\n\tVectorAggState *vector_agg_state = (VectorAggState *) node;\n\n\tColumnarScanState *decompress_state =\n\t\t(ColumnarScanState *) linitial(vector_agg_state->custom.custom_ps);\n\tDecompressContext *dcontext = &decompress_state->decompress_context;\n\n\tExprContext *econtext = node->ss.ps.ps_ExprContext;\n\tResetExprContext(econtext);\n\n\tTupleTableSlot *aggregated_slot = vector_agg_state->custom.ss.ps.ps_ResultTupleSlot;\n\tExecClearTuple(aggregated_slot);\n\n\t/*\n\t * If we have more partial aggregation results, continue returning them.\n\t */\n\tGroupingPolicy *grouping = vector_agg_state->grouping;\n\tMemoryContext old_context = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);\n\tbool have_partial = grouping->gp_do_emit(grouping, aggregated_slot);\n\tMemoryContextSwitchTo(old_context);\n\tif (have_partial)\n\t{\n\t\t/* The grouping policy produced a partial aggregation result. */\n\t\treturn ExecStoreVirtualTuple(aggregated_slot);\n\t}\n\n\t/*\n\t * Have no more partial aggregation results but might still have input.\n\t * Reset the grouping policy and start a new cycle of partial aggregation.\n\t */\n\tgrouping->gp_reset(grouping);\n\n\t/*\n\t * If the partial aggregation results have ended, and the input has ended,\n\t * we're done.\n\t */\n\tif (vector_agg_state->input_ended)\n\t{\n\t\treturn NULL;\n\t}\n\n\t/*\n\t * Now we loop through the input compressed tuples, until they end or until\n\t * the grouping policy asks us to emit partials.\n\t */\n\twhile (!grouping->gp_should_emit(grouping))\n\t{\n\t\t/*\n\t\t * Get the next slot to aggregate. It will be either a compressed\n\t\t * batch or an arrow tuple table slot. Both hold arrow arrays of data\n\t\t * that can be vectorized.\n\t\t */\n\t\tTupleTableSlot *slot = vector_agg_state->get_next_slot(vector_agg_state);\n\n\t\t/*\n\t\t * Exit if there is no more data. Note that it is not possible to do\n\t\t * the standard TupIsNull() check here because the compressed batch's\n\t\t * implementation of TupleTableSlot never clears the empty flag bit\n\t\t * (TTS_EMPTY), so it will always look empty. Therefore, look at the\n\t\t * \"input_ended\" flag instead.\n\t\t */\n\t\tif (vector_agg_state->input_ended)\n\t\t\tbreak;\n\n\t\t/*\n\t\t * Compute the vectorized filters for the aggregate function FILTER\n\t\t * clauses.\n\t\t */\n\t\tconst int naggs = vector_agg_state->num_agg_defs;\n\t\tfor (int i = 0; i < naggs; i++)\n\t\t{\n\t\t\tVectorAggDef *agg_def = &vector_agg_state->agg_defs[i];\n\t\t\tuint64 *filter_clause_result = NULL;\n\t\t\tif (agg_def->filter_clauses != NIL)\n\t\t\t{\n\t\t\t\tCompressedBatchVectorQualState vqstate =\n\t\t\t\t\tcompressed_batch_init_vector_quals(dcontext, agg_def->filter_clauses, slot);\n\t\t\t\tif (vector_qual_compute(&vqstate.vqstate) != AllRowsPass)\n\t\t\t\t{\n\t\t\t\t\tfilter_clause_result = vqstate.vqstate.vector_qual_result;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tDecompressBatchState *batch_state = (DecompressBatchState *) slot;\n\t\t\tif (filter_clause_result != NULL)\n\t\t\t{\n\t\t\t\tconst int num_validity_words = (batch_state->total_batch_rows + 63) / 64;\n\t\t\t\tarrow_validity_and(num_validity_words,\n\t\t\t\t\t\t\t\t   filter_clause_result,\n\t\t\t\t\t\t\t\t   batch_state->vector_qual_result);\n\t\t\t\tagg_def->effective_batch_filter = filter_clause_result;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tagg_def->effective_batch_filter = batch_state->vector_qual_result;\n\t\t\t}\n\t\t}\n\n\t\t/*\n\t\t * Finally, pass the compressed batch to the grouping policy.\n\t\t */\n\t\tgrouping->gp_add_batch(grouping, dcontext, slot);\n\t}\n\n\t/*\n\t * If we have partial aggregation results, start returning them.\n\t */\n\told_context = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);\n\thave_partial = grouping->gp_do_emit(grouping, aggregated_slot);\n\tMemoryContextSwitchTo(old_context);\n\tif (have_partial)\n\t{\n\t\t/* Have partial aggregation results. */\n\t\treturn ExecStoreVirtualTuple(aggregated_slot);\n\t}\n\n\tif (vector_agg_state->input_ended)\n\t{\n\t\t/*\n\t\t * Have no partial aggregation results and the input has ended, so we're\n\t\t * done. We can get here only if we had no input at all, otherwise the\n\t\t * grouping policy would have produced some partials above.\n\t\t */\n\t\treturn NULL;\n\t}\n\n\t/*\n\t * We cannot get here. This would mean we still have input, and the\n\t * grouping policy asked us to stop but couldn't produce any partials.\n\t */\n\tAssert(false);\n\tpg_unreachable();\n\treturn NULL;\n}\n\nstatic void\nvector_agg_explain(CustomScanState *node, List *ancestors, ExplainState *es)\n{\n\tVectorAggState *state = (VectorAggState *) node;\n\tif (es->verbose || es->format != EXPLAIN_FORMAT_TEXT)\n\t{\n\t\tExplainPropertyText(\"Grouping Policy\", state->grouping->gp_explain(state->grouping), es);\n\t}\n}\n\nstatic struct CustomExecMethods exec_methods = {\n\t.CustomName = VECTOR_AGG_NODE_NAME,\n\t.BeginCustomScan = vector_agg_begin,\n\t.ExecCustomScan = vector_agg_exec,\n\t.EndCustomScan = vector_agg_end,\n\t.ReScanCustomScan = vector_agg_rescan,\n\t.ExplainCustomScan = vector_agg_explain,\n};\n\nNode *\nvector_agg_state_create(CustomScan *cscan)\n{\n\tVectorAggState *state = (VectorAggState *) newNode(sizeof(VectorAggState), T_CustomScanState);\n\tAssert(ts_is_columnar_scan_plan((Plan *) linitial(cscan->custom_plans)));\n\n\tstate->custom.methods = &exec_methods;\n\n\t/*\n\t * Initialize VectorAggState to process vector slots from different\n\t * subnodes.\n\t *\n\t * When the child is ColumnarScan, VectorAgg doesn't read the slot from\n\t * the child node. Instead, it bypasses ColumnarScan and reads\n\t * compressed tuples directly from the grandchild. It therefore needs to\n\t * handle batch decompression and vectorized qual filtering itself, in its\n\t * own \"get next slot\" implementation.\n\t *\n\t * The vector qual init functions are needed to implement vectorized\n\t * aggregate function FILTER clauses for arrow tuple table slots and\n\t * compressed batches, respectively.\n\t */\n\tstate->get_next_slot = compressed_batch_get_next_slot;\n\n\treturn (Node *) state;\n}\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/exec.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#pragma once\n\n#include <postgres.h>\n\n#include \"nodes/columnar_scan/compressed_batch.h\"\n#include <nodes/execnodes.h>\n\n#include \"function/functions.h\"\n#include \"grouping_policy.h\"\n\ntypedef struct VectorAggDef\n{\n\tVectorAggFunctions func;\n\tExpr *argument;\n\tint output_offset;\n\tList *filter_clauses;\n\n\t/*\n\t * This filter bitmap ANDs the batch filter and the aggregate function\n\t * FILTER clause, if present.\n\t */\n\tuint64 const *effective_batch_filter;\n} VectorAggDef;\n\ntypedef struct GroupingColumn\n{\n\tExpr *expr;\n\n\tint output_offset;\n\n\tint16 value_bytes;\n\tbool by_value;\n} GroupingColumn;\n\ntypedef struct VectorAggState\n{\n\tCustomScanState custom;\n\n\tint num_agg_defs;\n\tVectorAggDef *agg_defs;\n\n\tint num_grouping_columns;\n\tGroupingColumn *grouping_columns;\n\n\t/*\n\t * We can't call the underlying scan after it has ended, or it will be\n\t * restarted. This is the behavior of Postgres heap scans. So we have to\n\t * track whether it has ended to avoid this.\n\t */\n\tbool input_ended;\n\n\tGroupingPolicy *grouping;\n\n\t/*\n\t * Function for getting the next slot from the child node depending on\n\t * child node type.\n\t */\n\tTupleTableSlot *(*get_next_slot)(struct VectorAggState *vector_agg_state);\n} VectorAggState;\n\nextern Node *vector_agg_state_create(CustomScan *cscan);\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/filter_word_iterator.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n/*\n * Iterator that skips the bitmap words that are fully zero. It helps us do less\n * work when most of the batch rows are filtered out.\n */\ntypedef struct\n{\n\tconst int nrows;\n\tconst int past_the_end_word;\n\tuint64 const *const filter;\n\n\tint start_word;\n\tint end_word;\n\n\tint start_row;\n\tint end_row;\n} FilterWordIterator;\n\ninline static void\nfilter_word_iterator_advance(FilterWordIterator *iter)\n{\n\tif (iter->filter == NULL)\n\t{\n\t\titer->start_word = iter->end_word;\n\t\titer->end_word = iter->past_the_end_word;\n\t\titer->start_row = iter->end_row;\n\t\titer->end_row = iter->nrows;\n\t\treturn;\n\t}\n\n\t/*\n\t * Skip the bitmap words which are zero.\n\t */\n\tfor (iter->start_word = iter->end_word;\n\t\t iter->start_word < iter->past_the_end_word && iter->filter[iter->start_word] == 0;\n\t\t iter->start_word++)\n\t\t;\n\n\tif (iter->start_word >= iter->past_the_end_word)\n\t{\n\t\t/*\n\t\t * Finished. The start_row shouldn't be used because the iterator is\n\t\t * invalid now, but set it to past-the-end for consistency.\n\t\t */\n\t\titer->start_row = iter->nrows;\n\t\treturn;\n\t}\n\n\t/*\n\t * Collect the consecutive bitmap words which are nonzero.\n\t */\n\tfor (iter->end_word = iter->start_word + 1;\n\t\t iter->end_word < iter->past_the_end_word && iter->filter[iter->end_word] != 0;\n\t\t iter->end_word++)\n\t\t;\n\n\tAssert(iter->end_word > iter->start_word);\n\n\t/*\n\t * Now we have the [start, end] range of bitmap words that are\n\t * nonzero.\n\t *\n\t * Determine starting and ending rows, also skipping the starting\n\t * and trailing zero bits at the ends of the range.\n\t */\n\titer->start_row =\n\t\titer->start_word * 64 + pg_rightmost_one_pos64(iter->filter[iter->start_word]);\n\tAssert(iter->start_row <= iter->nrows);\n\n\t/*\n\t * The bits for past-the-end rows must be set to zero, so this\n\t * calculation should yield no more than n.\n\t */\n\titer->end_row =\n\t\t(iter->end_word - 1) * 64 + pg_leftmost_one_pos64(iter->filter[iter->end_word - 1]) + 1;\n\tAssert(iter->end_row <= iter->nrows);\n}\n\ninline static FilterWordIterator\nfilter_word_iterator_init(int nrows, uint64 const *filter)\n{\n\tFilterWordIterator iter = { .nrows = nrows,\n\t\t\t\t\t\t\t\t.past_the_end_word = (nrows - 1) / 64 + 1,\n\t\t\t\t\t\t\t\t.filter = filter };\n\n\tfilter_word_iterator_advance(&iter);\n\n\treturn iter;\n}\n\ninline static bool\nfilter_word_iterator_is_valid(FilterWordIterator const *iter)\n{\n\treturn iter->start_word < iter->past_the_end_word;\n}\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/function/CMakeLists.txt",
    "content": "set(SOURCES\n    ${CMAKE_CURRENT_SOURCE_DIR}/functions.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/minmax_templates.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/int24_sum_templates.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/sum_float_templates.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/float48_accum_templates.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/int24_avg_accum_templates.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/int128_accum_templates.c)\ntarget_sources(${TSL_LIBRARY_NAME} PRIVATE ${SOURCES})\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/function/agg_many_vector_helper.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * A generic implementation of adding the given batch to many aggregate function\n * states with given offsets. Used for hash aggregation, and builds on the\n * FUNCTION_NAME(one) function, which adds one passing non-null row to the given\n * aggregate function state.\n */\nstatic pg_attribute_always_inline void\nFUNCTION_NAME(many_vector_impl)(void *restrict agg_states, const uint32 *offsets,\n\t\t\t\t\t\t\t\tconst uint64 *filter, int start_row, int end_row,\n\t\t\t\t\t\t\t\tconst ArrowArray *vector, MemoryContext agg_extra_mctx)\n{\n\tFUNCTION_NAME(state) *restrict states = (FUNCTION_NAME(state) *) agg_states;\n\tconst CTYPE *values = vector->buffers[1];\n\tMemoryContext old = MemoryContextSwitchTo(agg_extra_mctx);\n\tfor (int row = start_row; row < end_row; row++)\n\t{\n\t\tconst CTYPE value = values[row];\n\t\tFUNCTION_NAME(state) *restrict state = &states[offsets[row]];\n\t\tif (arrow_row_is_valid(filter, row))\n\t\t{\n\t\t\tAssert(offsets[row] != 0);\n\t\t\tFUNCTION_NAME(one)(state, value);\n\t\t}\n\t}\n\tMemoryContextSwitchTo(old);\n}\n\nstatic pg_noinline void\nFUNCTION_NAME(many_vector_all_valid)(void *restrict agg_states, const uint32 *offsets,\n\t\t\t\t\t\t\t\t\t int start_row, int end_row, const ArrowArray *vector,\n\t\t\t\t\t\t\t\t\t MemoryContext agg_extra_mctx)\n{\n\tFUNCTION_NAME(many_vector_impl)\n\t(agg_states, offsets, NULL, start_row, end_row, vector, agg_extra_mctx);\n}\n\nstatic void\nFUNCTION_NAME(many_vector)(void *restrict agg_states, const uint32 *offsets, const uint64 *filter,\n\t\t\t\t\t\t   int start_row, int end_row, const ArrowArray *vector,\n\t\t\t\t\t\t   MemoryContext agg_extra_mctx)\n{\n\tif (filter == NULL)\n\t{\n\t\tFUNCTION_NAME(many_vector_all_valid)\n\t\t(agg_states, offsets, start_row, end_row, vector, agg_extra_mctx);\n\t}\n\telse\n\t{\n\t\tFUNCTION_NAME(many_vector_impl)\n\t\t(agg_states, offsets, filter, start_row, end_row, vector, agg_extra_mctx);\n\t}\n}\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/function/agg_scalar_helper.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * A generic function for aggregating a constant input. We use a very simple\n * implementation here, because aggregating a segmentby column or a column with\n * default value is a relatively rare case, but it requires a fully custom\n * implementation otherwise.\n */\nstatic void\nFUNCTION_NAME(scalar)(void *agg_state, Datum constvalue, bool constisnull, int n,\n\t\t\t\t\t  MemoryContext agg_extra_mctx)\n{\n\tif (constisnull)\n\t{\n\t\treturn;\n\t}\n\n\tconst CTYPE value = DATUM_TO_CTYPE(constvalue);\n\n\tMemoryContext old = MemoryContextSwitchTo(agg_extra_mctx);\n\tfor (int i = 0; i < n; i++)\n\t{\n\t\tFUNCTION_NAME(one)(agg_state, value);\n\t}\n\tMemoryContextSwitchTo(old);\n}\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/function/agg_vector_validity_helper.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * Generate a separate implementation of aggregating an ArrowArray for the\n * common cases where we have no nulls and/or all rows pass the filter. It\n * avoids branches so can be more easily vectorized.\n */\n\nstatic pg_attribute_always_inline void\nFUNCTION_NAME(vector_impl_arrow)(void *agg_state, const ArrowArray *vector, const uint64 *filter,\n\t\t\t\t\t\t\t\t MemoryContext agg_extra_mctx)\n{\n\tconst int n = vector->length;\n\tconst CTYPE *values = vector->buffers[1];\n\tFUNCTION_NAME(vector_impl)(agg_state, n, values, filter, agg_extra_mctx);\n}\n\nstatic pg_noinline void\nFUNCTION_NAME(vector_all_valid)(void *agg_state, const ArrowArray *vector,\n\t\t\t\t\t\t\t\tMemoryContext agg_extra_mctx)\n{\n\tFUNCTION_NAME(vector_impl_arrow)(agg_state, vector, NULL, agg_extra_mctx);\n}\n\nstatic pg_noinline void\nFUNCTION_NAME(vector_one_validity)(void *agg_state, const ArrowArray *vector, const uint64 *filter,\n\t\t\t\t\t\t\t\t   MemoryContext agg_extra_mctx)\n{\n\tFUNCTION_NAME(vector_impl_arrow)(agg_state, vector, filter, agg_extra_mctx);\n}\n\nstatic void\nFUNCTION_NAME(vector)(void *agg_state, const ArrowArray *vector, const uint64 *filter,\n\t\t\t\t\t  MemoryContext agg_extra_mctx)\n{\n\tif (filter == NULL)\n\t{\n\t\t/* All rows are valid and we don't have to check any validity bitmaps. */\n\t\tFUNCTION_NAME(vector_all_valid)(agg_state, vector, agg_extra_mctx);\n\t}\n\telse\n\t{\n\t\t/* Have to check only one combined validity bitmap. */\n\t\tFUNCTION_NAME(vector_one_validity)(agg_state, vector, filter, agg_extra_mctx);\n\t}\n}\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/function/float48_accum_single.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * Vectorized implementation of a Postgres float{4,8}_accum() transition\n * function for a single type. They use the same Youngs-Cramer state, but for\n * AVG we can skip calculating the Sxx variable.\n */\n\n#ifdef GENERATE_DISPATCH_TABLE\n/*\n * Forward declaration for the vectorized aggregate function definition.\n */\nextern VectorAggFunctions FUNCTION_NAME(argdef);\n\n/*\n * Helper macros to generate the cases for the given argument type. We support\n * different aggregate functions based on whether we calculate the Sxx variable.\n */\n#ifdef NEED_SXX\n#define ACCUM_CASE_HELPER(PG_TYPE)                                                                 \\\n\tcase F_STDDEV_##PG_TYPE:                                                                       \\\n\tcase F_STDDEV_SAMP_##PG_TYPE:                                                                  \\\n\tcase F_STDDEV_POP_##PG_TYPE:                                                                   \\\n\tcase F_VARIANCE_##PG_TYPE:                                                                     \\\n\tcase F_VAR_SAMP_##PG_TYPE:                                                                     \\\n\tcase F_VAR_POP_##PG_TYPE:\n#else\n#define ACCUM_CASE_HELPER(PG_TYPE) case F_AVG_##PG_TYPE:\n#endif\n\n#define ACCUM_CASE(PG_TYPE) ACCUM_CASE_HELPER(PG_TYPE)\n\n/*\n * The actual case label.\n */\nACCUM_CASE(PG_TYPE)\nreturn &FUNCTION_NAME(argdef);\n#else\n\n/*\n * State of Youngs-Cramer algorithm, see the comments for float8_accum()\n * Postgres function.\n */\ntypedef struct\n{\n\tdouble N;\n\tdouble Sx;\n#ifdef NEED_SXX\n\tdouble Sxx;\n#endif\n} FUNCTION_NAME(state);\n\nstatic void\nFUNCTION_NAME(init)(void *restrict agg_states, int n)\n{\n\tFUNCTION_NAME(state) *states = (FUNCTION_NAME(state) *) agg_states;\n\tfor (int i = 0; i < n; i++)\n\t{\n\t\tstates[i] = (FUNCTION_NAME(state)){ 0 };\n\t}\n}\n\nstatic void\nFUNCTION_NAME(emit)(void *agg_state, Datum *out_result, bool *out_isnull)\n{\n\tFUNCTION_NAME(state) *state = (FUNCTION_NAME(state) *) agg_state;\n\n\tconst size_t nbytes = 3 * sizeof(float8) + ARR_OVERHEAD_NONULLS(/* ndims = */ 1);\n\tArrayType *result = palloc(nbytes);\n\tSET_VARSIZE(result, nbytes);\n\tresult->ndim = 1;\n\tresult->dataoffset = 0;\n\tresult->elemtype = FLOAT8OID;\n\tARR_DIMS(result)[0] = 3;\n\tARR_LBOUND(result)[0] = 1;\n\n\t/*\n\t * The array elements are stored by value, regardless of if the float8\n\t * itself is by-value on this platform.\n\t */\n\t((float8 *) ARR_DATA_PTR(result))[0] = state->N;\n\t((float8 *) ARR_DATA_PTR(result))[1] = state->Sx;\n\t((float8 *) ARR_DATA_PTR(result))[2] =\n\t\t/*\n\t\t * Sxx should be NaN if any of the inputs are infinite or NaN. This is\n\t\t * checked by float8_combine even if it's not used for the actual\n\t\t * calculations.\n\t\t */\n\t\t0. * state->Sx\n#ifdef NEED_SXX\n\t\t+ state->Sxx\n#endif\n\t\t;\n\n\t*out_result = PointerGetDatum(result);\n\t*out_isnull = false;\n}\n\n/*\n * Youngs-Cramer update for rows after the first.\n */\nstatic pg_attribute_always_inline void\nFUNCTION_NAME(update)(const uint64 *filter, const CTYPE *values, int row, double *N, double *Sx\n#ifdef NEED_SXX\n\t\t\t\t\t  ,\n\t\t\t\t\t  double *Sxx\n#endif\n)\n{\n\tconst CTYPE newval = values[row];\n\tif (!arrow_row_is_valid(filter, row))\n\t{\n\t\treturn;\n\t}\n\n\t/*\n\t * This code follows the Postgres float8_accum() transition function, see\n\t * the comments there.\n\t */\n\tconst double newN = *N + 1.0;\n\tconst double newSx = *Sx + newval;\n#ifdef NEED_SXX\n\tAssert(*N > 0.0);\n\tconst double tmp = newval * newN - newSx;\n\t*Sxx += tmp * tmp / (*N * newN);\n#endif\n\n\t*N = newN;\n\t*Sx = newSx;\n}\n\n/*\n * Combine two Youngs-Cramer states following the float8_combine() function.\n */\nstatic pg_attribute_always_inline void\nFUNCTION_NAME(combine)(double *inout_N, double *inout_Sx,\n#ifdef NEED_SXX\n\t\t\t\t\t   double *inout_Sxx,\n#endif\n\t\t\t\t\t   double N2, double Sx2\n#ifdef NEED_SXX\n\t\t\t\t\t   ,\n\t\t\t\t\t   double Sxx2\n#endif\n)\n{\n\tconst double N1 = *inout_N;\n\tconst double Sx1 = *inout_Sx;\n#ifdef NEED_SXX\n\tconst double Sxx1 = *inout_Sxx;\n#endif\n\n\tif (unlikely(N1 == 0))\n\t{\n\t\t*inout_N = N2;\n\t\t*inout_Sx = Sx2;\n#ifdef NEED_SXX\n\t\t*inout_Sxx = Sxx2;\n#endif\n\t\treturn;\n\t}\n\n\tif (unlikely(N2 == 0))\n\t{\n\t\t*inout_N = N1;\n\t\t*inout_Sx = Sx1;\n#ifdef NEED_SXX\n\t\t*inout_Sxx = Sxx1;\n#endif\n\t\treturn;\n\t}\n\n\tconst double combinedN = N1 + N2;\n\tconst double combinedSx = Sx1 + Sx2;\n#ifdef NEED_SXX\n\tconst double tmp = Sx1 / N1 - Sx2 / N2;\n\tconst double combinedSxx = Sxx1 + Sxx2 + N1 * N2 * tmp * tmp / combinedN;\n#endif\n\n\t*inout_N = combinedN;\n\t*inout_Sx = combinedSx;\n#ifdef NEED_SXX\n\t*inout_Sxx = combinedSxx;\n#endif\n}\n\n#ifdef NEED_SXX\n#define UPDATE(filter, values, row, N, Sx, Sxx)                                                    \\\n\tFUNCTION_NAME(update)(filter, values, row, N, Sx, Sxx)\n#define COMBINE(inout_N, inout_Sx, inout_Sxx, N2, Sx2, Sxx2)                                       \\\n\tFUNCTION_NAME(combine)(inout_N, inout_Sx, inout_Sxx, N2, Sx2, Sxx2)\n#else\n#define UPDATE(filter, values, row, N, Sx, Sxx) FUNCTION_NAME(update)(filter, values, row, N, Sx)\n#define COMBINE(inout_N, inout_Sx, inout_Sxx, N2, Sx2, Sxx2)                                       \\\n\tFUNCTION_NAME(combine)(inout_N, inout_Sx, N2, Sx2)\n#endif\n\nstatic pg_attribute_always_inline void\nFUNCTION_NAME(vector_impl)(void *agg_state, size_t n, const CTYPE *values, const uint64 *filter,\n\t\t\t\t\t\t   MemoryContext agg_extra_mctx)\n{\n\t/*\n\t * Vector registers can be up to 512 bits wide.\n\t */\n#define UNROLL_SIZE ((int) (512 / 8 / sizeof(CTYPE)))\n\n\t/*\n\t * Each inner iteration works with its own accumulators to avoid data\n\t * dependencies.\n\t */\n\tdouble Narray[UNROLL_SIZE] = { 0 };\n\tdouble Sxarray[UNROLL_SIZE] = { 0 };\n#ifdef NEED_SXX\n\tdouble Sxxarray[UNROLL_SIZE] = { 0 };\n#endif\n\n\tsize_t row = 0;\n\n#ifdef NEED_SXX\n\t/*\n\t * Initialize each state with the first matching row. We do this separately\n\t * to make the actual update function branchless, namely the computation of\n\t * Sxx which works differently for the first row.\n\t */\n\tfor (size_t inner = 0; inner < UNROLL_SIZE; inner++)\n\t{\n\t\tfor (; row < n; row++)\n\t\t{\n\t\t\tconst CTYPE newval = values[row];\n\t\t\tif (arrow_row_is_valid(filter, row))\n\t\t\t{\n\t\t\t\tNarray[inner] = 1;\n\t\t\t\tSxarray[inner] = newval;\n\t\t\t\tSxxarray[inner] = 0 * newval;\n\t\t\t\trow++;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t/*\n\t * Scroll to the row that is a multiple of UNROLL_SIZE. This is the correct\n\t * row at which to enter the unrolled loop below.\n\t */\n\tfor (size_t inner = row % UNROLL_SIZE; inner > 0 && inner < UNROLL_SIZE && row < n;\n\t\t inner++, row++)\n\t{\n\t\tUPDATE(filter, values, row, &Narray[inner], &Sxarray[inner], &Sxxarray[inner]);\n\t}\n#endif\n\n\t/*\n\t * Unrolled loop.\n\t */\n\tAssert(row % UNROLL_SIZE == 0 || row == n);\n\tfor (; row < UNROLL_SIZE * (n / UNROLL_SIZE); row += UNROLL_SIZE)\n\t{\n\t\tfor (size_t inner = 0; inner < UNROLL_SIZE; inner++)\n\t\t{\n\t\t\tUPDATE(filter, values, row + inner, &Narray[inner], &Sxarray[inner], &Sxxarray[inner]);\n\t\t}\n\t}\n\n\t/*\n\t * Process the odd tail.\n\t */\n\tfor (; row < n; row++)\n\t{\n\t\tconst size_t inner = row % UNROLL_SIZE;\n\t\tUPDATE(filter, values, row, &Narray[inner], &Sxarray[inner], &Sxxarray[inner]);\n\t}\n\n\t/*\n\t * Merge all intermediate states into the first one.\n\t */\n\tfor (int i = 1; i < UNROLL_SIZE; i++)\n\t{\n\t\tCOMBINE(&Narray[0], &Sxarray[0], &Sxxarray[0], Narray[i], Sxarray[i], Sxxarray[i]);\n\t}\n#undef UNROLL_SIZE\n\n\t/*\n\t * Merge the total computed state into the aggregate function state.\n\t */\n\tFUNCTION_NAME(state) *state = (FUNCTION_NAME(state) *) agg_state;\n\tCOMBINE(&state->N, &state->Sx, &state->Sxx, Narray[0], Sxarray[0], Sxxarray[0]);\n}\n\nstatic pg_attribute_always_inline void\nFUNCTION_NAME(one)(void *restrict agg_state, const CTYPE value)\n{\n\tFUNCTION_NAME(state) *state = (FUNCTION_NAME(state) *) agg_state;\n\t/*\n\t * This code follows the Postgres float8_accum() transition function, see\n\t * the comments there.\n\t */\n\tconst double newN = state->N + 1.0;\n\tconst double newSx = state->Sx + value;\n#ifdef NEED_SXX\n\tif (state->N > 0.0)\n\t{\n\t\tconst double tmp = value * newN - newSx;\n\t\tstate->Sxx += tmp * tmp / (state->N * newN);\n\t}\n\telse\n\t{\n\t\tstate->Sxx = 0 * value;\n\t}\n#endif\n\n\tstate->N = newN;\n\tstate->Sx = newSx;\n}\n\n#include \"agg_many_vector_helper.c\"\n#include \"agg_scalar_helper.c\"\n#include \"agg_vector_validity_helper.c\"\n\nVectorAggFunctions FUNCTION_NAME(argdef) = {\n\t.state_bytes = sizeof(FUNCTION_NAME(state)),\n\t.agg_init = FUNCTION_NAME(init),\n\t.agg_emit = FUNCTION_NAME(emit),\n\t.agg_scalar = FUNCTION_NAME(scalar),\n\t.agg_vector = FUNCTION_NAME(vector),\n\t.agg_many_vector = FUNCTION_NAME(many_vector),\n};\n#undef UPDATE\n#undef COMBINE\n\n#endif\n\n#undef PG_TYPE\n#undef CTYPE\n#undef DATUM_TO_CTYPE\n#undef CTYPE_TO_DATUM\n\n#undef ACCUM_CASE\n#undef ACCUM_CASE_HELPER\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/function/float48_accum_templates.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * Common parts for aggregate functions that use the float{4,8}_accum transition.\n */\n\n#include <postgres.h>\n\n#include <catalog/pg_type_d.h>\n#include <utils/array.h>\n#include <utils/float.h>\n#include <utils/fmgroids.h>\n#include <utils/fmgrprotos.h>\n\n#include \"functions.h\"\n#include \"template_helper.h\"\n#include <compression/arrow_c_data_interface.h>\n\n#ifndef GENERATE_DISPATCH_TABLE\n\n#endif\n\n/*\n * Templated parts for vectorized avg(float).\n */\n#define AGG_NAME accum_no_squares\n#include \"float48_accum_types.c\"\n\n/*\n * Templated parts for vectorized functions that use the Sxx state (stddev etc).\n */\n#define AGG_NAME accum_with_squares\n#define NEED_SXX\n#include \"float48_accum_types.c\"\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/function/float48_accum_types.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * Functions handled by *accum() aggregate functions states, implementation for\n * all types. They use the same Youngs-Cramer state, but for AVG we can skip\n * calculating the Sxx variable.\n */\n\n#define PG_TYPE FLOAT4\n#define CTYPE float\n#define CTYPE_TO_DATUM Float4GetDatum\n#define DATUM_TO_CTYPE DatumGetFloat4\n#include \"float48_accum_single.c\"\n\n#define PG_TYPE FLOAT8\n#define CTYPE double\n#define CTYPE_TO_DATUM Float8GetDatum\n#define DATUM_TO_CTYPE DatumGetFloat8\n#include \"float48_accum_single.c\"\n\n#undef AGG_NAME\n#undef NEED_SXX\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/function/functions.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <limits.h>\n\n#include <postgres.h>\n\n#include <common/int.h>\n#include <utils/date.h>\n#include <utils/float.h>\n#include <utils/fmgroids.h>\n#include <utils/fmgrprotos.h>\n\n#include \"functions.h\"\n\n#include \"compat/compat.h\"\n\n/*\n * For PG18+: provide the old lc_collate_is_c() API using pg_locale_t flags.\n */\n\n#if PG18_GE\n\n#include \"utils/pg_locale.h\"\n\nstatic inline bool\nlc_collate_is_c(Oid collation)\n{\n\treturn pg_newlocale_from_collation(collation)->collate_is_c;\n}\n\n#else\n\n#include <utils/pg_locale.h>\n\n#endif\n\n/*\n * Aggregate function count(*).\n */\ntypedef struct\n{\n\tint64 count;\n} CountState;\n\nstatic void\ncount_init(void *restrict agg_states, int n)\n{\n\tCountState *states = (CountState *) agg_states;\n\tfor (int i = 0; i < n; i++)\n\t{\n\t\tstates[i].count = 0;\n\t}\n}\n\nstatic void\ncount_emit(void *agg_state, Datum *out_result, bool *out_isnull)\n{\n\tCountState *state = (CountState *) agg_state;\n\t*out_result = Int64GetDatum(state->count);\n\t*out_isnull = false;\n}\n\nstatic void\ncount_star_scalar(void *agg_state, Datum constvalue, bool constisnull, int n,\n\t\t\t\t  MemoryContext agg_extra_mctx)\n{\n\tCountState *state = (CountState *) agg_state;\n\tstate->count += n;\n}\n\nstatic pg_attribute_always_inline void\ncount_star_many_scalar_impl(void *restrict agg_states, const uint32 *offsets, const uint64 *filter,\n\t\t\t\t\t\t\tint start_row, int end_row, Datum constvalue, bool constisnull,\n\t\t\t\t\t\t\tMemoryContext agg_extra_mctx)\n{\n\tCountState *states = (CountState *) agg_states;\n\tfor (int row = start_row; row < end_row; row++)\n\t{\n\t\tif (arrow_row_is_valid(filter, row))\n\t\t{\n\t\t\tstates[offsets[row]].count++;\n\t\t}\n\t}\n}\n\nstatic pg_noinline void\ncount_star_many_scalar_nofilter(void *restrict agg_states, const uint32 *offsets, int start_row,\n\t\t\t\t\t\t\t\tint end_row, Datum constvalue, bool constisnull,\n\t\t\t\t\t\t\t\tMemoryContext agg_extra_mctx)\n{\n\tcount_star_many_scalar_impl(agg_states,\n\t\t\t\t\t\t\t\toffsets,\n\t\t\t\t\t\t\t\tNULL,\n\t\t\t\t\t\t\t\tstart_row,\n\t\t\t\t\t\t\t\tend_row,\n\t\t\t\t\t\t\t\tconstvalue,\n\t\t\t\t\t\t\t\tconstisnull,\n\t\t\t\t\t\t\t\tagg_extra_mctx);\n}\n\nstatic void\ncount_star_many_scalar(void *restrict agg_states, const uint32 *offsets, const uint64 *filter,\n\t\t\t\t\t   int start_row, int end_row, Datum constvalue, bool constisnull,\n\t\t\t\t\t   MemoryContext agg_extra_mctx)\n{\n\tif (filter == NULL)\n\t{\n\t\tcount_star_many_scalar_nofilter(agg_states,\n\t\t\t\t\t\t\t\t\t\toffsets,\n\t\t\t\t\t\t\t\t\t\tstart_row,\n\t\t\t\t\t\t\t\t\t\tend_row,\n\t\t\t\t\t\t\t\t\t\tconstvalue,\n\t\t\t\t\t\t\t\t\t\tconstisnull,\n\t\t\t\t\t\t\t\t\t\tagg_extra_mctx);\n\t}\n\telse\n\t{\n\t\tcount_star_many_scalar_impl(agg_states,\n\t\t\t\t\t\t\t\t\toffsets,\n\t\t\t\t\t\t\t\t\tfilter,\n\t\t\t\t\t\t\t\t\tstart_row,\n\t\t\t\t\t\t\t\t\tend_row,\n\t\t\t\t\t\t\t\t\tconstvalue,\n\t\t\t\t\t\t\t\t\tconstisnull,\n\t\t\t\t\t\t\t\t\tagg_extra_mctx);\n\t}\n}\n\nVectorAggFunctions count_star_agg = {\n\t.state_bytes = sizeof(CountState),\n\t.agg_init = count_init,\n\t.agg_scalar = count_star_scalar,\n\t.agg_emit = count_emit,\n\t.agg_many_scalar = count_star_many_scalar,\n};\n\n/*\n * Aggregate function count(x).\n */\nstatic void\ncount_any_scalar(void *agg_state, Datum constvalue, bool constisnull, int n,\n\t\t\t\t MemoryContext agg_extra_mctx)\n{\n\tif (constisnull)\n\t{\n\t\treturn;\n\t}\n\n\tCountState *state = (CountState *) agg_state;\n\tstate->count += n;\n}\n\nstatic void\ncount_any_vector(void *agg_state, const ArrowArray *vector, const uint64 *filter,\n\t\t\t\t MemoryContext agg_extra_mctx)\n{\n\tCountState *state = (CountState *) agg_state;\n\tconst int n = vector->length;\n\t/* First, process the full words. */\n\tfor (int i = 0; i < n / 64; i++)\n\t{\n\t\tconst uint64 filter_word = filter ? filter[i] : ~0ULL;\n\n#ifdef HAVE__BUILTIN_POPCOUNT\n\t\tstate->count += __builtin_popcountll(filter_word);\n#else\n\t\t/*\n\t\t * Unfortunately, we have to have this fallback for Windows.\n\t\t */\n\t\tfor (uint16 i = 0; i < 64; i++)\n\t\t{\n\t\t\tconst bool this_bit = (filter_word >> i) & 1;\n\t\t\tstate->count += this_bit;\n\t\t}\n#endif\n\t}\n\n\t/*\n\t * The tail word needs special handling because not all rows there are valid\n\t * (some are past-the-end) even when the bitmap is null.\n\t */\n\tfor (int i = 64 * (n / 64); i < n; i++)\n\t{\n\t\tstate->count += arrow_row_is_valid(filter, i);\n\t}\n}\n\nstatic void\ncount_any_many_vector(void *restrict agg_states, const uint32 *offsets, const uint64 *filter,\n\t\t\t\t\t  int start_row, int end_row, const ArrowArray *vector,\n\t\t\t\t\t  MemoryContext agg_extra_mctx)\n{\n\tfor (int row = start_row; row < end_row; row++)\n\t{\n\t\tCountState *state = (offsets[row] + (CountState *) agg_states);\n\t\tif (arrow_row_is_valid(filter, row))\n\t\t{\n\t\t\tstate->count++;\n\t\t}\n\t}\n}\n\nVectorAggFunctions count_any_agg = {\n\t.state_bytes = sizeof(CountState),\n\t.agg_init = count_init,\n\t.agg_emit = count_emit,\n\t.agg_scalar = count_any_scalar,\n\t.agg_vector = count_any_vector,\n\t.agg_many_vector = count_any_many_vector,\n};\n\n/*\n * Return the vector aggregate definition corresponding to the given\n * PG aggregate function Oid and collation.\n *\n * The collation parameter is used for text aggregates (min/max) which use\n * memcmp for comparison. This only produces correct results for C collation,\n * so we cannot use vectorized aggregation for non-C collations.\n */\nVectorAggFunctions *\nget_vector_aggregate(Oid aggfnoid, Oid collation)\n{\n\tswitch (aggfnoid)\n\t{\n\t\tcase F_COUNT_:\n\t\t\treturn &count_star_agg;\n\t\tcase F_COUNT_ANY:\n\t\t\treturn &count_any_agg;\n#define GENERATE_DISPATCH_TABLE 1\n#include \"float48_accum_templates.c\"\n#include \"int128_accum_templates.c\"\n#include \"int24_avg_accum_templates.c\"\n#include \"int24_sum_templates.c\"\n#include \"minmax_templates.c\"\n#include \"sum_float_templates.c\"\n#undef GENERATE_DISPATCH_TABLE\n\t\tdefault:\n\t\t\treturn NULL;\n\t}\n}\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/function/functions.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#pragma once\n\n#include <compression/arrow_c_data_interface.h>\n\n/*\n * Function table for a vectorized implementation of an aggregate function.\n *\n * One state of aggregate function corresponds to one set of rows that are\n * supposed to be grouped together (e.g. for one grouping key).\n *\n * There are functions for adding a compressed batch to one aggregate function\n * state (no grouping keys), and to multiple aggregate function states laid out\n * contiguously in memory.\n */\ntypedef struct\n{\n\t/* Size of the aggregate function state. */\n\tsize_t state_bytes;\n\n\t/*\n\t * Initialize the n aggregate function states stored contiguously at the\n\t * given pointer.\n\t */\n\tvoid (*agg_init)(void *restrict agg_states, int n);\n\n\t/* Aggregate a given arrow array. */\n\tvoid (*agg_vector)(void *restrict agg_state, const ArrowArray *vector, const uint64 *filter,\n\t\t\t\t\t   MemoryContext agg_extra_mctx);\n\n\t/* Aggregate a scalar value, like segmentby or column with default value. */\n\tvoid (*agg_scalar)(void *restrict agg_state, Datum constvalue, bool constisnull, int n,\n\t\t\t\t\t   MemoryContext agg_extra_mctx);\n\n\t/*\n\t * Add the rows of the given arrow array to aggregate function states given\n\t * by the respective offsets.\n\t */\n\tvoid (*agg_many_vector)(void *restrict agg_states, const uint32 *offsets, const uint64 *filter,\n\t\t\t\t\t\t\tint start_row, int end_row, const ArrowArray *vector,\n\t\t\t\t\t\t\tMemoryContext agg_extra_mctx);\n\n\t/*\n\t * Same as above, but for a scalar argument. This is mostly important for\n\t * count(*) and can be NULL.\n\t */\n\tvoid (*agg_many_scalar)(void *restrict agg_states, const uint32 *offsets, const uint64 *filter,\n\t\t\t\t\t\t\tint start_row, int end_row, Datum constvalue, bool constisnull,\n\t\t\t\t\t\t\tMemoryContext agg_extra_mctx);\n\n\t/* Emit a partial aggregation result. */\n\tvoid (*agg_emit)(void *restrict agg_state, Datum *out_result, bool *out_isnull);\n} VectorAggFunctions;\n\nVectorAggFunctions *get_vector_aggregate(Oid aggfnoid, Oid collation);\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/function/int128_accum_single.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * Vectorized implementation of a single transition function that uses the\n * Int128AggState transition state. Note that the serialization format differs\n * based on whether the sum of squares is needed.\n */\n#ifdef GENERATE_DISPATCH_TABLE\nextern VectorAggFunctions FUNCTION_NAME(argdef);\nAGG_CASES\nreturn &FUNCTION_NAME(argdef);\n#else\n\ntypedef struct\n{\n\tint64 N;\n\tint128 sumX;\n#ifdef NEED_SUMX2\n\tint128 sumX2;\n#endif\n} FUNCTION_NAME(state);\n\nstatic void\nFUNCTION_NAME(init)(void *restrict agg_states, int n)\n{\n\tFUNCTION_NAME(state) *states = (FUNCTION_NAME(state) *) agg_states;\n\tfor (int i = 0; i < n; i++)\n\t{\n\t\tstates[i].N = 0;\n\t\tstates[i].sumX = 0;\n#ifdef NEED_SUMX2\n\t\tstates[i].sumX2 = 0;\n#endif\n\t}\n}\n\nstatic void\nFUNCTION_NAME(emit)(void *agg_state, Datum *out_result, bool *out_isnull)\n{\n\tFUNCTION_NAME(state) *state = (FUNCTION_NAME(state) *) agg_state;\n\n\tPgInt128AggState result = {\n\t\t.N = state->N,\n\t\t.sumX = state->sumX,\n#ifdef NEED_SUMX2\n\t\t.sumX2 = state->sumX2,\n#endif\n\t};\n\n\t/*\n\t * The serialization functions insist on being called in aggregate context,\n\t * but thankfully don't use it in any way so we can use this dummy.\n\t */\n\tAggState agg_context = { .ss.ps.type = T_AggState };\n\tLOCAL_FCINFO(fcinfo, 1);\n\tInitFunctionCallInfoData(*fcinfo, NULL, 1, InvalidOid, (Node *) &agg_context, NULL);\n\n\tfcinfo->args[0].value = PointerGetDatum(&result);\n\tfcinfo->args[0].isnull = false;\n\n#ifdef NEED_SUMX2\n\t*out_result = numeric_poly_serialize(fcinfo);\n#else\n\t*out_result = int8_avg_serialize(fcinfo);\n#endif\n\n\t*out_isnull = false;\n}\n\nstatic pg_attribute_always_inline void\nFUNCTION_NAME(vector_impl)(void *agg_state, int n, const CTYPE *values, const uint64 *filter,\n\t\t\t\t\t\t   MemoryContext agg_extra_mctx)\n{\n\tint64 N = 0;\n\tint128 sumX = 0;\n#ifdef NEED_SUMX2\n\tint128 sumX2 = 0;\n#endif\n\tfor (int row = 0; row < n; row++)\n\t{\n\t\tconst bool row_ok = arrow_row_is_valid(filter, row);\n\t\tconst CTYPE value = values[row];\n\t\tN += row_ok;\n\t\tsumX += value * row_ok;\n#ifdef NEED_SUMX2\n\t\tsumX2 += ((int128) value) * ((int128) value) * row_ok;\n#endif\n\t}\n\n\tFUNCTION_NAME(state) *state = (FUNCTION_NAME(state) *) agg_state;\n\tstate->N += N;\n\tstate->sumX += sumX;\n#ifdef NEED_SUMX2\n\tstate->sumX2 += sumX2;\n#endif\n}\n\nstatic pg_attribute_always_inline void\nFUNCTION_NAME(one)(void *restrict agg_state, const CTYPE value)\n{\n\tFUNCTION_NAME(state) *state = (FUNCTION_NAME(state) *) agg_state;\n\tstate->N++;\n\tstate->sumX += value;\n#ifdef NEED_SUMX2\n\tstate->sumX2 += ((int128) value) * ((int128) value);\n#endif\n}\n\n#include \"agg_many_vector_helper.c\"\n#include \"agg_scalar_helper.c\"\n#include \"agg_vector_validity_helper.c\"\n\nVectorAggFunctions FUNCTION_NAME(argdef) = {\n\t.state_bytes = sizeof(FUNCTION_NAME(state)),\n\t.agg_init = FUNCTION_NAME(init),\n\t.agg_emit = FUNCTION_NAME(emit),\n\t.agg_scalar = FUNCTION_NAME(scalar),\n\t.agg_vector = FUNCTION_NAME(vector),\n\t.agg_many_vector = FUNCTION_NAME(many_vector),\n};\n\n#endif\n\n#undef PG_TYPE\n#undef CTYPE\n#undef DATUM_TO_CTYPE\n#undef AGG_CASES\n#undef AGG_NAME\n#undef NEED_SUMX2\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/function/int128_accum_templates.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * Vectorized transition functions that use the Int128AggState transition state.\n */\n\n#include <postgres.h>\n\n#include <nodes/execnodes.h>\n#include <utils/fmgroids.h>\n#include <utils/fmgrprotos.h>\n\n#include \"functions.h\"\n#include \"template_helper.h\"\n#include <compression/arrow_c_data_interface.h>\n\n#ifdef HAVE_INT128\n#ifndef GENERATE_DISPATCH_TABLE\n/*\n * The PG aggregation state that we have to serialize. Copied from numeric.c.\n */\ntypedef struct\n{\n\tbool calcSumX2; /* if true, calculate sumX2 */\n\tint64 N;\t\t/* count of processed numbers */\n\tint128 sumX;\t/* sum of processed numbers */\n\tint128 sumX2;\t/* sum of squares of processed numbers */\n} PgInt128AggState;\n#endif\n\n/*\n * Vectorized implementation of int8_avg_accum() function.\n */\n#define AGG_NAME accum_no_squares\n#define AGG_CASES                                                                                  \\\n\tcase PG_AGG_OID_HELPER(SUM, PG_TYPE):                                                          \\\n\tcase PG_AGG_OID_HELPER(AVG, PG_TYPE):\n\n#define PG_TYPE INT8\n#define CTYPE int64\n#define DATUM_TO_CTYPE DatumGetInt64\n#include \"int128_accum_single.c\"\n\n/*\n * Vectorized implementation of int2_accum() function.\n */\n#define NEED_SUMX2\n#define AGG_NAME accum_with_squares\n#define AGG_CASES                                                                                  \\\n\tcase PG_AGG_OID_HELPER(STDDEV, PG_TYPE):                                                       \\\n\tcase PG_AGG_OID_HELPER(STDDEV_SAMP, PG_TYPE):                                                  \\\n\tcase PG_AGG_OID_HELPER(STDDEV_POP, PG_TYPE):                                                   \\\n\tcase PG_AGG_OID_HELPER(VARIANCE, PG_TYPE):                                                     \\\n\tcase PG_AGG_OID_HELPER(VAR_SAMP, PG_TYPE):                                                     \\\n\tcase PG_AGG_OID_HELPER(VAR_POP, PG_TYPE):\n\n#define PG_TYPE INT2\n#define CTYPE int16\n#define DATUM_TO_CTYPE DatumGetInt16\n#include \"int128_accum_single.c\"\n\n/*\n * Vectorized implementation of int4_accum() function.\n */\n#define NEED_SUMX2\n#define AGG_NAME accum_with_squares\n#define AGG_CASES                                                                                  \\\n\tcase PG_AGG_OID_HELPER(STDDEV, PG_TYPE):                                                       \\\n\tcase PG_AGG_OID_HELPER(STDDEV_SAMP, PG_TYPE):                                                  \\\n\tcase PG_AGG_OID_HELPER(STDDEV_POP, PG_TYPE):                                                   \\\n\tcase PG_AGG_OID_HELPER(VARIANCE, PG_TYPE):                                                     \\\n\tcase PG_AGG_OID_HELPER(VAR_SAMP, PG_TYPE):                                                     \\\n\tcase PG_AGG_OID_HELPER(VAR_POP, PG_TYPE):\n\n#define PG_TYPE INT4\n#define CTYPE int32\n#define DATUM_TO_CTYPE DatumGetInt32\n#include \"int128_accum_single.c\"\n\n#endif\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/function/int24_avg_accum_single.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#ifdef GENERATE_DISPATCH_TABLE\nextern VectorAggFunctions FUNCTION_NAME(argdef);\ncase PG_AGG_OID_HELPER(AGG_NAME, PG_TYPE):\n\treturn &FUNCTION_NAME(argdef);\n#else\n\nstatic pg_attribute_always_inline void\nFUNCTION_NAME(vector_impl)(void *agg_state, int n, const CTYPE *values, const uint64 *filter,\n\t\t\t\t\t\t   MemoryContext agg_extra_mctx)\n{\n\tint64 batch_count = 0;\n\tint64 batch_sum = 0;\n\tfor (int row = 0; row < n; row++)\n\t{\n\t\tconst bool row_ok = arrow_row_is_valid(filter, row);\n\t\tbatch_count += row_ok;\n\t\tbatch_sum += values[row] * row_ok;\n\t}\n\n\tInt24AvgAccumState *state = (Int24AvgAccumState *) agg_state;\n\tstate->count += batch_count;\n\tstate->sum += batch_sum;\n}\n\ntypedef Int24AvgAccumState FUNCTION_NAME(state);\n\nstatic pg_attribute_always_inline void\nFUNCTION_NAME(one)(void *restrict agg_state, const CTYPE value)\n{\n\tFUNCTION_NAME(state) *state = (FUNCTION_NAME(state) *) agg_state;\n\tstate->count++;\n\tstate->sum += value;\n}\n\n#include \"agg_many_vector_helper.c\"\n#include \"agg_scalar_helper.c\"\n#include \"agg_vector_validity_helper.c\"\n\nVectorAggFunctions FUNCTION_NAME(argdef) = {\n\t.state_bytes = sizeof(Int24AvgAccumState),\n\t.agg_init = int24_avg_accum_init,\n\t.agg_emit = int24_avg_accum_emit,\n\t.agg_scalar = FUNCTION_NAME(scalar),\n\t.agg_vector = FUNCTION_NAME(vector),\n\t.agg_many_vector = FUNCTION_NAME(many_vector),\n};\n\n#endif\n\n#undef PG_TYPE\n#undef CTYPE\n#undef DATUM_TO_CTYPE\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/function/int24_avg_accum_templates.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * Vectorized implementation for int{2,4}_avg_accum transition functions.\n */\n\n#include <postgres.h>\n\n#include <catalog/pg_type_d.h>\n#include <utils/array.h>\n#include <utils/fmgroids.h>\n#include <utils/fmgrprotos.h>\n\n#include \"functions.h\"\n#include \"template_helper.h\"\n#include <compression/arrow_c_data_interface.h>\n\n#ifndef GENERATE_DISPATCH_TABLE\ntypedef struct\n{\n\tint64 count;\n\tint64 sum;\n} Int24AvgAccumState;\n\nstatic void\nint24_avg_accum_init(void *restrict agg_states, int n)\n{\n\tInt24AvgAccumState *states = (Int24AvgAccumState *) agg_states;\n\tfor (int i = 0; i < n; i++)\n\t{\n\t\tstates[i].count = 0;\n\t\tstates[i].sum = 0;\n\t}\n}\n\nstatic void\nint24_avg_accum_emit(void *agg_state, Datum *out_result, bool *out_isnull)\n{\n\tInt24AvgAccumState *state = (Int24AvgAccumState *) agg_state;\n\n\tconst size_t nbytes = 2 * sizeof(int64) + ARR_OVERHEAD_NONULLS(/* ndims = */ 1);\n\tArrayType *result = palloc(nbytes);\n\tSET_VARSIZE(result, nbytes);\n\tresult->ndim = 1;\n\tresult->dataoffset = 0;\n\tresult->elemtype = INT8OID;\n\tARR_DIMS(result)[0] = 2;\n\tARR_LBOUND(result)[0] = 1;\n\n\t/*\n\t * The array elements are stored by value, regardless of if the int8 itself\n\t * is by-value on this platform.\n\t */\n\t((int64 *) ARR_DATA_PTR(result))[0] = state->count;\n\t((int64 *) ARR_DATA_PTR(result))[1] = state->sum;\n\n\t*out_result = PointerGetDatum(result);\n\t*out_isnull = false;\n}\n#endif\n\n#define AGG_NAME AVG\n\n#define PG_TYPE INT2\n#define CTYPE int16\n#define DATUM_TO_CTYPE DatumGetInt16\n#include \"int24_avg_accum_single.c\"\n\n#define PG_TYPE INT4\n#define CTYPE int32\n#define DATUM_TO_CTYPE DatumGetInt32\n#include \"int24_avg_accum_single.c\"\n\n#undef AGG_NAME\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/function/int24_sum_single.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#ifdef GENERATE_DISPATCH_TABLE\nextern VectorAggFunctions FUNCTION_NAME(argdef);\ncase PG_AGG_OID_HELPER(AGG_NAME, PG_TYPE):\n\treturn &FUNCTION_NAME(argdef);\n#else\n\nstatic pg_attribute_always_inline void\nFUNCTION_NAME(vector_impl)(void *agg_state, int n, const CTYPE *values, const uint64 *filter,\n\t\t\t\t\t\t   MemoryContext agg_extra_mctx)\n{\n\tInt24SumState *state = (Int24SumState *) agg_state;\n\n\t/*\n\t * We accumulate the sum as int64, so we can sum INT_MAX = 2^31 - 1\n\t * at least 2^31 times without incurring an overflow of the int64\n\t * accumulator. The same is true for negative numbers. The\n\t * compressed batch size is currently capped at 1000 rows, but even\n\t * if it's changed in the future, it's unlikely that we support\n\t * batches larger than 65536 rows, not to mention 2^31. Therefore,\n\t * we don't need to check for overflows within the loop, which would\n\t * slow down the calculation.\n\t */\n\tAssert(n <= INT_MAX);\n\n\t/*\n\t * Note that we use a simplest loop here, there are many possibilities of\n\t * optimizing this function (for example, this loop is not unrolled by\n\t * clang-16).\n\t */\n\tint64 batch_sum = 0;\n\tbool have_result = false;\n\tfor (int row = 0; row < n; row++)\n\t{\n\t\tconst bool row_ok = arrow_row_is_valid(filter, row);\n\t\tbatch_sum += values[row] * row_ok;\n\t\thave_result = have_result || row_ok;\n\t}\n\n\tif (unlikely(pg_add_s64_overflow(state->result, batch_sum, &state->result)))\n\t{\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg(\"bigint out of range\")));\n\t}\n\n\tstate->isvalid = state->isvalid || have_result;\n}\n\nstatic pg_attribute_always_inline void\nFUNCTION_NAME(one)(void *restrict agg_state, const CTYPE value)\n{\n\tInt24SumState *state = (Int24SumState *) agg_state;\n\tstate->result += value;\n\tstate->isvalid = true;\n}\n\ntypedef Int24SumState FUNCTION_NAME(state);\n\n#include \"agg_many_vector_helper.c\"\n#include \"agg_scalar_helper.c\"\n#include \"agg_vector_validity_helper.c\"\n\nVectorAggFunctions FUNCTION_NAME(argdef) = {\n\t.state_bytes = sizeof(Int24SumState),\n\t.agg_init = int_sum_init,\n\t.agg_emit = int_sum_emit,\n\t.agg_scalar = FUNCTION_NAME(scalar),\n\t.agg_vector = FUNCTION_NAME(vector),\n\t.agg_many_vector = FUNCTION_NAME(many_vector),\n};\n#endif\n\n#undef PG_TYPE\n#undef CTYPE\n#undef DATUM_TO_CTYPE\n#undef CTYPE_TO_DATUM\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/function/int24_sum_templates.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * Common parts for vectorized sum(int).\n */\n\n#include <limits.h>\n\n#include <postgres.h>\n\n#include <common/int.h>\n#include <utils/fmgroids.h>\n#include <utils/fmgrprotos.h>\n\n#include \"functions.h\"\n#include \"template_helper.h\"\n#include <compression/arrow_c_data_interface.h>\n\n#ifndef GENERATE_DISPATCH_TABLE\ntypedef struct\n{\n\tint64 result;\n\tbool isvalid;\n} Int24SumState;\n\nstatic void\nint_sum_init(void *restrict agg_states, int n)\n{\n\tInt24SumState *states = (Int24SumState *) agg_states;\n\tfor (int i = 0; i < n; i++)\n\t{\n\t\tstates[i].result = 0;\n\t\tstates[i].isvalid = false;\n\t}\n}\n\nstatic void\nint_sum_emit(void *agg_state, Datum *out_result, bool *out_isnull)\n{\n\tInt24SumState *state = (Int24SumState *) agg_state;\n\t*out_result = Int64GetDatum(state->result);\n\t*out_isnull = !state->isvalid;\n}\n#endif\n\n/*\n * Templated parts for vectorized sum(int).\n */\n#define AGG_NAME SUM\n\n#define PG_TYPE INT4\n#define CTYPE int32\n#define DATUM_TO_CTYPE DatumGetInt32\n#include \"int24_sum_single.c\"\n\n#define PG_TYPE INT2\n#define CTYPE int16\n#define DATUM_TO_CTYPE DatumGetInt16\n#include \"int24_sum_single.c\"\n\n#undef AGG_NAME\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/function/minmax_arithmetic_single.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#ifdef GENERATE_DISPATCH_TABLE\nextern VectorAggFunctions FUNCTION_NAME(argdef);\ncase PG_AGG_OID_HELPER(AGG_NAME, PG_TYPE):\n\treturn &FUNCTION_NAME(argdef);\n#else\nstatic pg_attribute_always_inline void\nFUNCTION_NAME(vector_impl)(void *agg_state, int n, const CTYPE *values, const uint64 *filter,\n\t\t\t\t\t\t   MemoryContext agg_extra_mctx)\n{\n\tMinMaxState *state = (MinMaxState *) agg_state;\n\n\tCTYPE outer_result = state->isvalid ? DATUM_TO_CTYPE(state->value) : 0;\n\tbool outer_isvalid = state->isvalid;\n\tfor (int row = 0; row < n; row++)\n\t{\n\t\tconst CTYPE new_value = values[row];\n\t\tconst bool new_value_ok = arrow_row_is_valid(filter, row);\n\n\t\t/*\n\t\t * Note that we have to properly handle NaNs and Infinities for floats.\n\t\t */\n\t\tconst bool do_replace =\n\t\t\tnew_value_ok && (unlikely(!outer_isvalid) || PREDICATE(outer_result, new_value));\n\n\t\touter_result = do_replace ? new_value : outer_result;\n\t\touter_isvalid = outer_isvalid || do_replace;\n\t}\n\n\tstate->isvalid = outer_isvalid;\n\n\t/* Note that float8 Datum is by-reference on 32-bit systems. */\n\tMemoryContext old = MemoryContextSwitchTo(agg_extra_mctx);\n\tstate->value = CTYPE_TO_DATUM(outer_result);\n\tMemoryContextSwitchTo(old);\n}\n\ntypedef MinMaxState FUNCTION_NAME(state);\n\nstatic pg_attribute_always_inline void\nFUNCTION_NAME(one)(void *restrict agg_state, const CTYPE value)\n{\n\tFUNCTION_NAME(state) *state = (FUNCTION_NAME(state) *) agg_state;\n\tif (!state->isvalid || PREDICATE(DATUM_TO_CTYPE(state->value), value) || isnan((double) value))\n\t{\n\t\t/*\n\t\t * Note that float8 Datum is by-reference on 32-bit systems, and this\n\t\t * function is called in the extra aggregate data memory context.\n\t\t */\n\t\tstate->value = CTYPE_TO_DATUM(value);\n\t\tstate->isvalid = true;\n\t}\n}\n\n#include \"agg_many_vector_helper.c\"\n#include \"agg_scalar_helper.c\"\n#include \"agg_vector_validity_helper.c\"\n\nVectorAggFunctions FUNCTION_NAME(argdef) = {\n\t.state_bytes = sizeof(MinMaxState),\n\t.agg_init = minmax_init,\n\t.agg_emit = minmax_emit,\n\t.agg_scalar = FUNCTION_NAME(scalar),\n\t.agg_vector = FUNCTION_NAME(vector),\n\t.agg_many_vector = FUNCTION_NAME(many_vector),\n};\n#endif\n\n#undef PG_TYPE\n#undef CTYPE\n#undef DATUM_TO_CTYPE\n#undef CTYPE_TO_DATUM\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/function/minmax_arithmetic_types.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#define PG_TYPE INT2\n#define CTYPE int16\n#define DATUM_TO_CTYPE DatumGetInt16\n#define CTYPE_TO_DATUM Int16GetDatum\n#include \"minmax_arithmetic_single.c\"\n\n#define PG_TYPE INT4\n#define CTYPE int32\n#define DATUM_TO_CTYPE DatumGetInt32\n#define CTYPE_TO_DATUM Int32GetDatum\n#include \"minmax_arithmetic_single.c\"\n\n#define PG_TYPE INT8\n#define CTYPE int64\n#define DATUM_TO_CTYPE DatumGetInt64\n#define CTYPE_TO_DATUM Int64GetDatum\n#include \"minmax_arithmetic_single.c\"\n\n#define PG_TYPE FLOAT4\n#define CTYPE float\n#define DATUM_TO_CTYPE DatumGetFloat4\n#define CTYPE_TO_DATUM Float4GetDatum\n#include \"minmax_arithmetic_single.c\"\n\n#define PG_TYPE FLOAT8\n#define CTYPE double\n#define DATUM_TO_CTYPE DatumGetFloat8\n#define CTYPE_TO_DATUM Float8GetDatum\n#include \"minmax_arithmetic_single.c\"\n\n#define PG_TYPE TIMESTAMP\n#define CTYPE Timestamp\n#define DATUM_TO_CTYPE DatumGetTimestamp\n#define CTYPE_TO_DATUM TimestampGetDatum\n#include \"minmax_arithmetic_single.c\"\n\n#define PG_TYPE TIMESTAMPTZ\n#define CTYPE TimestampTz\n#define DATUM_TO_CTYPE DatumGetTimestampTz\n#define CTYPE_TO_DATUM TimestampTzGetDatum\n#include \"minmax_arithmetic_single.c\"\n\n#define PG_TYPE DATE\n#define CTYPE DateADT\n#define DATUM_TO_CTYPE DatumGetDateADT\n#define CTYPE_TO_DATUM DateADTGetDatum\n#include \"minmax_arithmetic_single.c\"\n\n#undef PREDICATE\n#undef AGG_NAME\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/function/minmax_templates.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include <postgres.h>\n\n#include <port/pg_bitutils.h>\n#include <utils/date.h>\n#include <utils/fmgroids.h>\n#include <utils/fmgrprotos.h>\n#include <utils/timestamp.h>\n\n#include \"functions.h\"\n#include \"template_helper.h\"\n#include <compression/arrow_c_data_interface.h>\n\n#include \"compat/compat.h\"\n\n#if PG16_GE\n#include <varatt.h>\n#endif\n\n/*\n * Common parts for vectorized min(), max().\n */\n#ifndef GENERATE_DISPATCH_TABLE\ntypedef struct\n{\n\tbool isvalid;\n\tDatum value;\n} MinMaxState;\n\nstatic void\nminmax_init(void *restrict agg_states, int n)\n{\n\tMinMaxState *states = (MinMaxState *) agg_states;\n\tfor (int i = 0; i < n; i++)\n\t{\n\t\tstates[i].isvalid = false;\n\t\tstates[i].value = 0;\n\t}\n}\n\nstatic void\nminmax_emit(void *agg_state, Datum *out_result, bool *out_isnull)\n{\n\tMinMaxState *state = (MinMaxState *) agg_state;\n\t*out_result = state->value;\n\t*out_isnull = !state->isvalid;\n}\n#endif\n\n/*\n * Templated parts for vectorized min(), max().\n *\n * NaN handled similar to equivalent PG functions.\n */\n#define AGG_NAME MIN\n#define PREDICATE(CURRENT, NEW)                                                                    \\\n\t(unlikely(!isnan((double) (NEW))) && (isnan((double) (CURRENT)) || (CURRENT) > (NEW)))\n#include \"minmax_arithmetic_types.c\"\n\n#define AGG_NAME MAX\n#define PREDICATE(CURRENT, NEW)                                                                    \\\n\t(unlikely(!isnan((double) (CURRENT))) && (isnan((double) (NEW)) || (CURRENT) < (NEW)))\n#include \"minmax_arithmetic_types.c\"\n\n#ifndef GENERATE_DISPATCH_TABLE\n\n/*\n * Common parts for vectorized min(text), max(text).\n */\n\ntypedef struct\n{\n\tuint32 capacity;\n\tstruct varlena *data;\n} MinMaxBytesState;\n\ntypedef struct BytesView\n{\n\tconst uint8 *data;\n\tuint32 len;\n} BytesView;\n\nstatic void\nminmax_bytes_init(void *restrict agg_states, int n)\n{\n\tMinMaxBytesState *restrict states = (MinMaxBytesState *) agg_states;\n\tfor (int i = 0; i < n; i++)\n\t{\n\t\tstates[i].capacity = 0;\n\t\tstates[i].data = NULL;\n\t}\n}\n\nstatic void\nminmax_bytes_emit(void *agg_state, Datum *out_result, bool *out_isnull)\n{\n\tMinMaxBytesState *state = (MinMaxBytesState *) agg_state;\n\t*out_isnull = state->capacity == 0;\n\t*out_result = PointerGetDatum(state->data);\n}\n#endif\n\n/*\n * Templated parts for vectorized min(text), max(text).\n */\n#define PG_TYPE TEXT\n#define AGG_NAME MIN\n#define PREDICATE(CURRENT, NEW) ((CURRENT) > (NEW))\n#include \"minmax_text.c\"\n\n#define PG_TYPE TEXT\n#define AGG_NAME MAX\n#define PREDICATE(CURRENT, NEW) ((CURRENT) < (NEW))\n#include \"minmax_text.c\"\n\n#undef AGG_NAME\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/function/minmax_text.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#ifdef GENERATE_DISPATCH_TABLE\nextern VectorAggFunctions FUNCTION_NAME(argdef);\ncase PG_AGG_OID_HELPER(AGG_NAME, PG_TYPE):\n\t/*\n\t * Text min/max uses memcmp for comparison, which only produces correct\n\t * ordering for C collation. For non-C collations, return NULL to fall\n\t * back to the Postgres aggregation.\n\t */\n\tif (lc_collate_is_c(collation))\n\t\treturn &FUNCTION_NAME(argdef);\n\treturn NULL;\n#else\n\ntypedef MinMaxBytesState FUNCTION_NAME(state);\n\nstatic pg_attribute_always_inline void\nFUNCTION_NAME(one)(void *restrict agg_state, const BytesView new_value)\n{\n\tFUNCTION_NAME(state) *restrict state = (FUNCTION_NAME(state) *) agg_state;\n\n\t/*\n\t * If current value is null, we replace it with the new value, otherwise we\n\t * have to check the predicate.\n\t */\n\tbool replace = state->capacity == 0;\n\tif (likely(!replace))\n\t{\n\t\tconst uint32 current_len = VARSIZE(state->data) - VARHDRSZ;\n\t\tconst int result =\n\t\t\tmemcmp(VARDATA(state->data), new_value.data, Min(current_len, new_value.len));\n\t\tif (result == 0)\n\t\t{\n\t\t\treplace = PREDICATE(current_len, new_value.len);\n\t\t}\n\t\telse\n\t\t{\n\t\t\treplace = PREDICATE(result, 0);\n\t\t}\n\t}\n\n\tif (replace)\n\t{\n\t\tconst uint32 new_vardata_bytes = new_value.len + VARHDRSZ;\n\t\tif (new_vardata_bytes > state->capacity)\n\t\t{\n\t\t\t/*\n\t\t\t * Reallocate to closest power of two to amortize the costs. Varlena\n\t\t\t * is limited to 2^30 - 1 bytes.\n\t\t\t */\n\t\t\tAssert(new_vardata_bytes < INT32_MAX / 2);\n\t\t\tconst int lowest_power = pg_leftmost_one_pos32(new_vardata_bytes * 2 - 1);\n\t\t\tconst int new_capacity = 1ULL << lowest_power;\n\t\t\tstate->data = palloc(new_capacity);\n\t\t\tstate->capacity = new_capacity;\n\t\t}\n\t\tSET_VARSIZE(state->data, new_vardata_bytes);\n\t\tmemcpy(VARDATA(state->data), new_value.data, new_value.len);\n\n\t\tAssert(state->capacity > 0);\n\t\tAssert(VARSIZE(state->data) <= state->capacity);\n\t}\n}\n\nstatic void\nFUNCTION_NAME(vector)(void *agg_state, const ArrowArray *arrow, const uint64 *filter,\n\t\t\t\t\t  MemoryContext agg_extra_mctx)\n{\n\tFUNCTION_NAME(state) *restrict state = (FUNCTION_NAME(state) *) agg_state;\n\n\tconst int16 *body_offset_indexes = arrow->dictionary ? arrow->buffers[1] : NULL;\n\tconst uint8 *bodies = arrow->dictionary ? arrow->dictionary->buffers[2] : arrow->buffers[2];\n\tconst uint32 *body_offsets =\n\t\tarrow->dictionary ? arrow->dictionary->buffers[1] : arrow->buffers[1];\n\n\tconst int n = arrow->length;\n\tMemoryContext old = MemoryContextSwitchTo(agg_extra_mctx);\n\tfor (int row = 0; row < n; row++)\n\t{\n\t\tconst int body_offset_index = body_offset_indexes == NULL ? row : body_offset_indexes[row];\n\t\tconst int body_offset = body_offsets[body_offset_index];\n\t\tconst int body_bytes = body_offsets[body_offset_index + 1] - body_offset;\n\t\tconst BytesView value = { .data = &bodies[body_offset], .len = body_bytes };\n\n\t\tif (arrow_row_is_valid(filter, row))\n\t\t{\n\t\t\tFUNCTION_NAME(one)(state, value);\n\t\t}\n\t}\n\tMemoryContextSwitchTo(old);\n}\n\nstatic void\nFUNCTION_NAME(many_vector)(void *restrict agg_states, const uint32 *state_indices,\n\t\t\t\t\t\t   const uint64 *filter, int start_row, int end_row,\n\t\t\t\t\t\t   const ArrowArray *arrow, MemoryContext agg_extra_mctx)\n{\n\tFUNCTION_NAME(state) *restrict states = (FUNCTION_NAME(state) *) agg_states;\n\tconst int16 *body_offset_indexes = arrow->dictionary ? arrow->buffers[1] : NULL;\n\tconst uint8 *bodies = arrow->dictionary ? arrow->dictionary->buffers[2] : arrow->buffers[2];\n\tconst uint32 *body_offsets =\n\t\tarrow->dictionary ? arrow->dictionary->buffers[1] : arrow->buffers[1];\n\n\tMemoryContext old = MemoryContextSwitchTo(agg_extra_mctx);\n\tfor (int row = start_row; row < end_row; row++)\n\t{\n\t\tFUNCTION_NAME(state) *restrict state = &states[state_indices[row]];\n\n\t\tconst int body_offset_index = body_offset_indexes == NULL ? row : body_offset_indexes[row];\n\t\tconst int body_offset = body_offsets[body_offset_index];\n\t\tconst int body_bytes = body_offsets[body_offset_index + 1] - body_offset;\n\t\tconst BytesView value = { .data = &bodies[body_offset], .len = body_bytes };\n\n\t\tif (arrow_row_is_valid(filter, row))\n\t\t{\n\t\t\tAssert(state_indices[row] != 0);\n\t\t\tFUNCTION_NAME(one)(state, value);\n\t\t}\n\t}\n\tMemoryContextSwitchTo(old);\n}\n\nstatic void\nFUNCTION_NAME(scalar)(void *agg_state, Datum constvalue, bool constisnull, int n,\n\t\t\t\t\t  MemoryContext agg_extra_mctx)\n{\n\tif (constisnull)\n\t{\n\t\treturn;\n\t}\n\n\tBytesView value = { .data = (const uint8 *) VARDATA_ANY(constvalue),\n\t\t\t\t\t\t.len = VARSIZE_ANY_EXHDR(constvalue) };\n\tMemoryContext old = MemoryContextSwitchTo(agg_extra_mctx);\n\tFUNCTION_NAME(one)(agg_state, value);\n\tMemoryContextSwitchTo(old);\n}\n\nVectorAggFunctions FUNCTION_NAME(argdef) = {\n\t.state_bytes = sizeof(MinMaxBytesState),\n\t.agg_init = minmax_bytes_init,\n\t.agg_emit = minmax_bytes_emit,\n\t.agg_scalar = FUNCTION_NAME(scalar),\n\t.agg_vector = FUNCTION_NAME(vector),\n\t.agg_many_vector = FUNCTION_NAME(many_vector),\n};\n#endif\n\n#undef PG_TYPE\n#undef CTYPE\n#undef DATUM_TO_CTYPE\n#undef CTYPE_TO_DATUM\n\n#undef PREDICATE\n#undef AGG_NAME\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/function/sum_float_single.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#ifdef GENERATE_DISPATCH_TABLE\nextern VectorAggFunctions FUNCTION_NAME(argdef);\ncase PG_AGG_OID_HELPER(AGG_NAME, PG_TYPE):\n\treturn &FUNCTION_NAME(argdef);\n#else\n\nStaticAssertDecl(sizeof(CTYPE) == sizeof(MASKTYPE), \"CTYPE and MASKTYPE must be the same size\");\n\nstatic void\nFUNCTION_NAME(emit)(void *agg_state, Datum *out_result, bool *out_isnull)\n{\n\tFloatSumState *state = (FloatSumState *) agg_state;\n\tconst CTYPE result_casted = state->result;\n\t*out_result = CTYPE_TO_DATUM(result_casted);\n\t*out_isnull = !state->isvalid;\n}\n\nstatic pg_attribute_always_inline void\nFUNCTION_NAME(vector_impl)(void *agg_state, int n, const CTYPE *values, const uint64 *filter,\n\t\t\t\t\t\t   MemoryContext agg_extra_mctx)\n{\n\t/*\n\t * Vector registers can be up to 512 bits wide.\n\t */\n#define UNROLL_SIZE ((int) (512 / 8 / sizeof(CTYPE)))\n\n\tbool have_result_accu[UNROLL_SIZE] = { 0 };\n\tdouble sum_accu[UNROLL_SIZE] = { 0 };\n\tfor (int outer = 0; outer < UNROLL_SIZE * (n / UNROLL_SIZE); outer += UNROLL_SIZE)\n\t{\n\t\tfor (int inner = 0; inner < UNROLL_SIZE; inner++)\n\t\t{\n\t\t\tconst int row = outer + inner;\n\t\t\tdouble *dest = &sum_accu[inner];\n\t\t\tbool *have_result = &have_result_accu[inner];\n\t\t\t/*\n\t\t\t * We're using a trick with bitmasking the numbers that don't\n\t\t\t * pass the filter, to allow for branchless code generation. This is\n\t\t\t * analogous to integer version where we just multiply the integers\n\t\t\t * by bool, but for floats we can't use multiplication because of\n\t\t\t * infinities and NaNs.\n\t\t\t */\n#define INNER_LOOP                                                                                 \\\n\tconst bool row_valid = arrow_row_is_valid(filter, row);                                        \\\n\tunion                                                                                          \\\n\t{                                                                                              \\\n\t\tCTYPE f;                                                                                   \\\n\t\tMASKTYPE m;                                                                                \\\n\t} u = { .f = values[row] };                                                                    \\\n\tu.m &= row_valid ? ~(MASKTYPE) 0 : (MASKTYPE) 0;                                               \\\n\t*dest += u.f;                                                                                  \\\n\t*have_result = *have_result || row_valid;\n\n\t\t\tINNER_LOOP\n\t\t}\n\t}\n\n\tfor (int row = UNROLL_SIZE * (n / UNROLL_SIZE); row < n; row++)\n\t{\n\t\tdouble *dest = &sum_accu[0];\n\t\tbool *have_result = &have_result_accu[0];\n\t\tINNER_LOOP\n\t}\n\n\tfor (int i = 1; i < UNROLL_SIZE; i++)\n\t{\n\t\tsum_accu[0] += sum_accu[i];\n\t\thave_result_accu[0] = have_result_accu[0] || have_result_accu[i];\n\t}\n#undef UNROLL_SIZE\n#undef INNER_LOOP\n\n\tFloatSumState *state = (FloatSumState *) agg_state;\n\tstate->isvalid = state->isvalid || have_result_accu[0];\n\tstate->result += sum_accu[0];\n}\n\ntypedef FloatSumState FUNCTION_NAME(state);\n\nstatic pg_attribute_always_inline void\nFUNCTION_NAME(one)(void *restrict agg_state, const CTYPE value)\n{\n\tFUNCTION_NAME(state) *state = (FUNCTION_NAME(state) *) agg_state;\n\tstate->isvalid = true;\n\tstate->result += value;\n}\n\n#include \"agg_many_vector_helper.c\"\n#include \"agg_scalar_helper.c\"\n#include \"agg_vector_validity_helper.c\"\n\nVectorAggFunctions FUNCTION_NAME(argdef) = {\n\t.state_bytes = sizeof(FloatSumState),\n\t.agg_init = float_sum_init,\n\t.agg_emit = FUNCTION_NAME(emit),\n\t.agg_scalar = FUNCTION_NAME(scalar),\n\t.agg_vector = FUNCTION_NAME(vector),\n\t.agg_many_vector = FUNCTION_NAME(many_vector),\n};\n\n#endif\n\n#undef PG_TYPE\n#undef CTYPE\n#undef MASKTYPE\n#undef DATUM_TO_CTYPE\n#undef CTYPE_TO_DATUM\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/function/sum_float_templates.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * Common parts for vectorized sum(float).\n */\n\n#include <postgres.h>\n#include \"functions.h\"\n#include \"template_helper.h\"\n#include <compression/arrow_c_data_interface.h>\n#include <utils/fmgroids.h>\n#include <utils/fmgrprotos.h>\n\n#ifndef GENERATE_DISPATCH_TABLE\ntypedef struct\n{\n\tdouble result;\n\tbool isvalid;\n} FloatSumState;\n\nstatic void\nfloat_sum_init(void *restrict agg_states, int n)\n{\n\tFloatSumState *states = (FloatSumState *) agg_states;\n\tfor (int i = 0; i < n; i++)\n\t{\n\t\tstates[i].result = 0;\n\t\tstates[i].isvalid = false;\n\t}\n}\n#endif\n\n/*\n * Templated parts for vectorized sum(float).\n */\n#define AGG_NAME SUM\n\n#define PG_TYPE FLOAT4\n#define CTYPE float\n#define MASKTYPE uint32\n#define CTYPE_TO_DATUM Float4GetDatum\n#define DATUM_TO_CTYPE DatumGetFloat4\n#include \"sum_float_single.c\"\n\n#define PG_TYPE FLOAT8\n#define CTYPE double\n#define MASKTYPE uint64\n#define CTYPE_TO_DATUM Float8GetDatum\n#define DATUM_TO_CTYPE DatumGetFloat8\n#include \"sum_float_single.c\"\n\n#undef AGG_NAME\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/function/template_helper.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#define PG_AGG_OID(AGG_NAME, ARGUMENT_TYPE) F_##AGG_NAME##_##ARGUMENT_TYPE\n#define PG_AGG_OID_HELPER(X, Y) PG_AGG_OID(X, Y)\n\n#define FUNCTION_NAME_HELPER2(X, Y, Z) X##_##Y##_##Z\n#define FUNCTION_NAME_HELPER(X, Y, Z) FUNCTION_NAME_HELPER2(X, Y, Z)\n#define FUNCTION_NAME(Z) FUNCTION_NAME_HELPER(AGG_NAME, PG_TYPE, Z)\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/grouping_policy.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <executor/tuptable.h>\n\ntypedef struct GroupingPolicy GroupingPolicy;\n\ntypedef struct DecompressContext DecompressContext;\n\ntypedef struct TupleTableSlot TupleTableSlot;\n\ntypedef struct VectorAggDef VectorAggDef;\n\ntypedef struct GroupingColumn GroupingColumn;\n\n/*\n * This is a common interface for grouping policies which define how the rows\n * are grouped for aggregation -- e.g. there can be an implementation for no\n * grouping, grouping by compression segmentby columns, grouping over sorted\n * input (GroupAggregate), grouping using a hash table, and so on.\n */\ntypedef struct GroupingPolicy\n{\n\t/*\n\t * Used for rescans in the Postgres sense.\n\t */\n\tvoid (*gp_reset)(GroupingPolicy *gp);\n\n\t/*\n\t * Aggregate a single compressed batch.\n\t */\n\tvoid (*gp_add_batch)(GroupingPolicy *gp, DecompressContext *dcontext,\n\t\t\t\t\t\t TupleTableSlot *vector_slot);\n\n\t/*\n\t * Is a partial aggregation result ready?\n\t */\n\tbool (*gp_should_emit)(GroupingPolicy *gp);\n\n\t/*\n\t * Emit a partial aggregation result into the result slot.\n\t */\n\tbool (*gp_do_emit)(GroupingPolicy *gp, TupleTableSlot *aggregated_slot);\n\n\t/*\n\t * Destroy the grouping policy.\n\t */\n\tvoid (*gp_destroy)(GroupingPolicy *gp);\n\n\t/*\n\t * Description of this grouping policy for the EXPLAIN output.\n\t */\n\tchar *(*gp_explain)(GroupingPolicy *gp);\n} GroupingPolicy;\n\n/*\n * The various types of grouping we might use, as determined at planning time.\n * The hashed subtypes are all implemented by hash grouping policy.\n */\ntypedef enum\n{\n\tVAGT_Invalid,\n\tVAGT_Batch,\n\tVAGT_HashSingleFixed2,\n\tVAGT_HashSingleFixed4,\n\tVAGT_HashSingleFixed8,\n\tVAGT_HashSingleText,\n\tVAGT_HashSerialized,\n} VectorAggGroupingType;\n\nextern GroupingPolicy *create_grouping_policy_batch(int num_agg_defs, VectorAggDef *agg_defs,\n\t\t\t\t\t\t\t\t\t\t\t\t\tint num_grouping_columns,\n\t\t\t\t\t\t\t\t\t\t\t\t\tGroupingColumn *grouping_columns);\n\nextern GroupingPolicy *create_grouping_policy_hash(int num_agg_defs, VectorAggDef *agg_defs,\n\t\t\t\t\t\t\t\t\t\t\t\t   int num_grouping_columns,\n\t\t\t\t\t\t\t\t\t\t\t\t   GroupingColumn *grouping_columns,\n\t\t\t\t\t\t\t\t\t\t\t\t   VectorAggGroupingType grouping_type);\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/grouping_policy_batch.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * This grouping policy aggregates entire compressed batches. It can be used to\n * aggregate with no grouping, or to produce partial aggregates per each batch\n * to group by segmentby columns.\n */\n\n#include <postgres.h>\n\n#include <access/attnum.h>\n#include <executor/tuptable.h>\n#include <nodes/pg_list.h>\n\n#include \"grouping_policy.h\"\n\n#include \"nodes/vector_agg/exec.h\"\n#include \"nodes/vector_agg/vector_slot.h\"\n\ntypedef struct\n{\n\tGroupingPolicy funcs;\n\n\tint num_agg_defs;\n\tVectorAggDef *agg_defs;\n\n\t/*\n\t * Temporary storage for combined bitmap of batch filter and aggregate\n\t * argument validity.\n\t */\n\tuint64 *tmp_filter;\n\tuint64 num_tmp_filter_words;\n\n\tvoid **agg_states;\n\n\tint num_grouping_columns;\n\tGroupingColumn *grouping_columns;\n\n\tDatum *output_grouping_values;\n\tbool *output_grouping_isnull;\n\tbool have_results;\n\n\t/*\n\t * A memory context for aggregate functions to allocate additional data,\n\t * i.e. if they store strings or float8 datum on 32-bit systems, or they\n\t * have variable-length state like the exact distinct function or the\n\t * statistical sketches.\n\t * Valid until the grouping policy is reset.\n\t */\n\tMemoryContext agg_extra_mctx;\n} GroupingPolicyBatch;\n\nstatic const GroupingPolicy grouping_policy_batch_functions;\n\nGroupingPolicy *\ncreate_grouping_policy_batch(int num_agg_defs, VectorAggDef *agg_defs, int num_grouping_columns,\n\t\t\t\t\t\t\t GroupingColumn *output_grouping_columns)\n{\n\tGroupingPolicyBatch *policy = palloc0(sizeof(GroupingPolicyBatch));\n\tpolicy->funcs = grouping_policy_batch_functions;\n\n\tpolicy->num_grouping_columns = num_grouping_columns;\n\tpolicy->grouping_columns = output_grouping_columns;\n\n\tpolicy->num_agg_defs = num_agg_defs;\n\tpolicy->agg_defs = agg_defs;\n\n\tpolicy->agg_extra_mctx =\n\t\tAllocSetContextCreate(CurrentMemoryContext, \"agg extra\", ALLOCSET_DEFAULT_SIZES);\n\n\tpolicy->agg_states = (void **) palloc(sizeof(*policy->agg_states) * policy->num_agg_defs);\n\tfor (int i = 0; i < policy->num_agg_defs; i++)\n\t{\n\t\tVectorAggDef *agg_def = &policy->agg_defs[i];\n\t\tpolicy->agg_states[i] = palloc(agg_def->func.state_bytes);\n\t}\n\n\tpolicy->output_grouping_values =\n\t\t(Datum *) palloc0(MAXALIGN(num_grouping_columns * sizeof(Datum)) +\n\t\t\t\t\t\t  MAXALIGN(num_grouping_columns * sizeof(bool)));\n\tpolicy->output_grouping_isnull = (bool *) ((char *) policy->output_grouping_values +\n\t\t\t\t\t\t\t\t\t\t\t   MAXALIGN(num_grouping_columns * sizeof(Datum)));\n\n\treturn &policy->funcs;\n}\n\nstatic void\ngp_batch_reset(GroupingPolicy *obj)\n{\n\tGroupingPolicyBatch *policy = (GroupingPolicyBatch *) obj;\n\n\tMemoryContextReset(policy->agg_extra_mctx);\n\n\tconst int naggs = policy->num_agg_defs;\n\tfor (int i = 0; i < naggs; i++)\n\t{\n\t\tVectorAggDef *agg_def = &policy->agg_defs[i];\n\t\tvoid *agg_state = policy->agg_states[i];\n\t\tagg_def->func.agg_init(agg_state, 1);\n\t}\n\n\tconst int ngrp = policy->num_grouping_columns;\n\tfor (int i = 0; i < ngrp; i++)\n\t{\n\t\tpolicy->output_grouping_values[i] = 0;\n\t\tpolicy->output_grouping_isnull[i] = true;\n\t}\n\n\tpolicy->have_results = false;\n}\n\nstatic void\ncompute_single_aggregate(GroupingPolicyBatch *policy, DecompressContext *dcontext,\n\t\t\t\t\t\t TupleTableSlot *vector_slot, VectorAggDef *agg_def, void *agg_state,\n\t\t\t\t\t\t MemoryContext agg_extra_mctx)\n{\n\t/*\n\t * We have functions with one argument, and one function with no arguments\n\t * (count(*)). Collect the arguments.\n\t */\n\tconst ArrowArray *arg_arrow = NULL;\n\tconst uint64 *arg_validity_bitmap = NULL;\n\tDatum arg_datum = 0;\n\tbool arg_isnull = true;\n\tif (agg_def->argument != NULL)\n\t{\n\t\tconst CompressedColumnValues values =\n\t\t\tvector_slot_evaluate_expression(dcontext,\n\t\t\t\t\t\t\t\t\t\t\tvector_slot,\n\t\t\t\t\t\t\t\t\t\t\tagg_def->effective_batch_filter,\n\t\t\t\t\t\t\t\t\t\t\tagg_def->argument);\n\n\t\tAssert(values.decompression_type != DT_Invalid);\n\t\tEnsure(values.decompression_type != DT_Iterator, \"expected arrow array but got iterator\");\n\n\t\tif (values.arrow != NULL)\n\t\t{\n\t\t\targ_arrow = values.arrow;\n\t\t\targ_validity_bitmap = values.buffers[0];\n\t\t}\n\t\telse\n\t\t{\n\t\t\tAssert(values.decompression_type == DT_Scalar);\n\t\t\targ_isnull = DatumGetBool(PointerGetDatum(values.buffers[0]));\n\t\t\targ_datum = PointerGetDatum(values.buffers[1]);\n\t\t}\n\t}\n\n\t/*\n\t * Compute the combined validity bitmap that includes the argument validity.\n\t */\n\tDecompressBatchState *batch_state = (DecompressBatchState *) vector_slot;\n\tconst size_t num_words = (batch_state->total_batch_rows + 63) / 64;\n\tconst uint64 *combined_validity = arrow_combine_validity(num_words,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t policy->tmp_filter,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t agg_def->effective_batch_filter,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t arg_validity_bitmap,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t NULL);\n\n\t/*\n\t * Now call the function.\n\t */\n\tif (arg_arrow != NULL)\n\t{\n\t\t/* Arrow argument. */\n\t\tagg_def->func.agg_vector(agg_state, arg_arrow, combined_validity, agg_extra_mctx);\n\t}\n\telse\n\t{\n\t\t/*\n\t\t * Scalar argument, or count(*). Have to also count the valid rows in\n\t\t * the batch.\n\t\t *\n\t\t * The batches that are fully filtered out by vectorized quals should\n\t\t * have been skipped by the caller, but we also have to check for the\n\t\t * case when no rows match the aggregate FILTER clause.\n\t\t */\n\t\tconst int n = arrow_num_valid(combined_validity, batch_state->total_batch_rows);\n\t\tif (n > 0)\n\t\t{\n\t\t\tagg_def->func.agg_scalar(agg_state, arg_datum, arg_isnull, n, agg_extra_mctx);\n\t\t}\n\t}\n}\n\nstatic void\ngp_batch_add_batch(GroupingPolicy *gp, DecompressContext *dcontext, TupleTableSlot *vector_slot)\n{\n\tGroupingPolicyBatch *policy = (GroupingPolicyBatch *) gp;\n\tuint16 total_batch_rows = 0;\n\tconst uint64 *vector_qual_result = vector_slot_get_qual_result(vector_slot, &total_batch_rows);\n\n\t/*\n\t * Allocate the temporary filter array for computing the combined results of\n\t * batch filter, aggregate filter and column validity.\n\t */\n\tconst size_t num_words = (total_batch_rows + 63) / 64;\n\tif (num_words > policy->num_tmp_filter_words)\n\t{\n\t\tconst size_t new_words = (num_words * 2) + 1;\n\t\tif (policy->tmp_filter != NULL)\n\t\t{\n\t\t\tpfree(policy->tmp_filter);\n\t\t}\n\n\t\tpolicy->tmp_filter = palloc(sizeof(*policy->tmp_filter) * new_words);\n\t\tpolicy->num_tmp_filter_words = new_words;\n\t}\n\n\t/*\n\t * Compute the aggregates.\n\t */\n\tconst int naggs = policy->num_agg_defs;\n\tfor (int i = 0; i < naggs; i++)\n\t{\n\t\tVectorAggDef *agg_def = &policy->agg_defs[i];\n\t\tvoid *agg_state = policy->agg_states[i];\n\t\tcompute_single_aggregate(policy,\n\t\t\t\t\t\t\t\t dcontext,\n\t\t\t\t\t\t\t\t vector_slot,\n\t\t\t\t\t\t\t\t agg_def,\n\t\t\t\t\t\t\t\t agg_state,\n\t\t\t\t\t\t\t\t policy->agg_extra_mctx);\n\t}\n\n\t/*\n\t * Save the values of the grouping columns.\n\t */\n\tconst int ngrp = policy->num_grouping_columns;\n\tfor (int i = 0; i < ngrp; i++)\n\t{\n\t\tGroupingColumn *col = &policy->grouping_columns[i];\n\t\tAssert(col->output_offset >= 0);\n\n\t\tconst CompressedColumnValues values =\n\t\t\tvector_slot_evaluate_expression(dcontext, vector_slot, vector_qual_result, col->expr);\n\t\tAssert(values.decompression_type == DT_Scalar);\n\n\t\t/*\n\t\t * By sheer luck, we can avoid generically copying the Datum here,\n\t\t * because if we have any output grouping columns in this policy, it\n\t\t * means we're grouping by segmentby, and these values will be valid\n\t\t * until the next call to the vector agg node.\n\t\t */\n\t\tpolicy->output_grouping_values[i] = PointerGetDatum(values.buffers[1]);\n\t\tpolicy->output_grouping_isnull[i] = DatumGetBool(PointerGetDatum(values.buffers[0]));\n\t}\n\n\tpolicy->have_results = true;\n}\n\nstatic bool\ngp_batch_should_emit(GroupingPolicy *gp)\n{\n\tGroupingPolicyBatch *policy = (GroupingPolicyBatch *) gp;\n\n\t/*\n\t * If we're grouping by segmentby columns, we have to output partials for\n\t * every batch.\n\t */\n\treturn policy->num_grouping_columns > 0 && policy->have_results;\n}\n\nstatic bool\ngp_batch_do_emit(GroupingPolicy *gp, TupleTableSlot *aggregated_slot)\n{\n\tGroupingPolicyBatch *policy = (GroupingPolicyBatch *) gp;\n\n\tif (!policy->have_results)\n\t{\n\t\treturn false;\n\t}\n\n\tconst int naggs = policy->num_agg_defs;\n\tfor (int i = 0; i < naggs; i++)\n\t{\n\t\tVectorAggDef *agg_def = &policy->agg_defs[i];\n\t\tvoid *agg_state = policy->agg_states[i];\n\t\tagg_def->func.agg_emit(agg_state,\n\t\t\t\t\t\t\t   &aggregated_slot->tts_values[agg_def->output_offset],\n\t\t\t\t\t\t\t   &aggregated_slot->tts_isnull[agg_def->output_offset]);\n\t}\n\n\tconst int ngrp = policy->num_grouping_columns;\n\tfor (int i = 0; i < ngrp; i++)\n\t{\n\t\tGroupingColumn *col = &policy->grouping_columns[i];\n\t\tAssert(col->output_offset >= 0);\n\n\t\taggregated_slot->tts_values[col->output_offset] = policy->output_grouping_values[i];\n\t\taggregated_slot->tts_isnull[col->output_offset] = policy->output_grouping_isnull[i];\n\t}\n\n\t/*\n\t * We only have one partial aggregation result for this policy.\n\t */\n\tpolicy->have_results = false;\n\n\treturn true;\n}\n\nstatic char *\ngp_batch_explain(GroupingPolicy *gp)\n{\n\tGroupingPolicyBatch *policy = (GroupingPolicyBatch *) gp;\n\n\t/*\n\t * If we're grouping by segmentby columns, we have to output partials for\n\t * every batch.\n\t */\n\treturn policy->num_grouping_columns > 0 ? \"per compressed batch\" : \"all compressed batches\";\n}\n\nstatic const GroupingPolicy grouping_policy_batch_functions = {\n\t.gp_reset = gp_batch_reset,\n\t.gp_add_batch = gp_batch_add_batch,\n\t.gp_should_emit = gp_batch_should_emit,\n\t.gp_do_emit = gp_batch_do_emit,\n\t.gp_explain = gp_batch_explain,\n};\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/grouping_policy_hash.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * This grouping policy groups the rows using a hash table. Currently it only\n * supports a single fixed-size by-value compressed column that fits into a Datum.\n */\n\n#include <postgres.h>\n\n#include <access/attnum.h>\n#include <access/tupdesc.h>\n#include <executor/tuptable.h>\n#include <nodes/pg_list.h>\n\n#include \"grouping_policy.h\"\n\n#include \"guc.h\"\n#include \"nodes/vector_agg/exec.h\"\n#include \"nodes/vector_agg/filter_word_iterator.h\"\n#include \"nodes/vector_agg/vector_slot.h\"\n\n#include \"grouping_policy_hash.h\"\n\n#ifdef USE_FLOAT8_BYVAL\n#define DEBUG_LOG(MSG, ...) elog(DEBUG3, MSG, __VA_ARGS__)\n#else\n/*\n * On 32-bit platforms we'd have to use the cross-platform int width printf\n * specifiers which are really unreadable.\n */\n#define DEBUG_LOG(...)\n#endif\n\nextern HashingStrategy single_fixed_2_strategy;\nextern HashingStrategy single_fixed_4_strategy;\nextern HashingStrategy single_fixed_8_strategy;\n#ifdef TS_USE_UMASH\nextern HashingStrategy single_text_strategy;\nextern HashingStrategy serialized_strategy;\n#endif\n\nstatic const GroupingPolicy grouping_policy_hash_functions;\n\nGroupingPolicy *\ncreate_grouping_policy_hash(int num_agg_defs, VectorAggDef *agg_defs, int num_grouping_columns,\n\t\t\t\t\t\t\tGroupingColumn *grouping_columns, VectorAggGroupingType grouping_type)\n{\n\tGroupingPolicyHash *policy = palloc0(sizeof(GroupingPolicyHash));\n\tpolicy->funcs = grouping_policy_hash_functions;\n\n\tpolicy->num_grouping_columns = num_grouping_columns;\n\tpolicy->grouping_columns = grouping_columns;\n\n\tpolicy->agg_extra_mctx =\n\t\tAllocSetContextCreate(CurrentMemoryContext, \"agg extra\", ALLOCSET_DEFAULT_SIZES);\n\n\t/*\n\t * This should match the expected grouping cardinality. Here we use a value\n\t * noticeably lower than the batch size, so that the reallocation logic is\n\t * triggered in more cases and better tested.\n\t */\n\tpolicy->num_allocated_per_key_agg_states = 300;\n\n\tpolicy->num_agg_defs = num_agg_defs;\n\tpolicy->agg_defs = agg_defs;\n\n\tpolicy->per_agg_per_key_states =\n\t\t(void **) palloc(sizeof(*policy->per_agg_per_key_states) * policy->num_agg_defs);\n\tfor (int i = 0; i < policy->num_agg_defs; i++)\n\t{\n\t\tconst VectorAggDef *agg_def = &policy->agg_defs[i];\n\t\tpolicy->per_agg_per_key_states[i] =\n\t\t\tpalloc(agg_def->func.state_bytes * policy->num_allocated_per_key_agg_states);\n\t}\n\n\tpolicy->current_batch_grouping_column_values =\n\t\tpalloc(sizeof(CompressedColumnValues) * num_grouping_columns);\n\n\tswitch (grouping_type)\n\t{\n#ifdef TS_USE_UMASH\n\t\tcase VAGT_HashSerialized:\n\t\t\tpolicy->hashing = serialized_strategy;\n\t\t\tbreak;\n\t\tcase VAGT_HashSingleText:\n\t\t\tpolicy->hashing = single_text_strategy;\n\t\t\tbreak;\n#endif\n\t\tcase VAGT_HashSingleFixed8:\n\t\t\tpolicy->hashing = single_fixed_8_strategy;\n\t\t\tbreak;\n\t\tcase VAGT_HashSingleFixed4:\n\t\t\tpolicy->hashing = single_fixed_4_strategy;\n\t\t\tbreak;\n\t\tcase VAGT_HashSingleFixed2:\n\t\t\tpolicy->hashing = single_fixed_2_strategy;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tEnsure(false, \"failed to determine the hashing strategy\");\n\t\t\tbreak;\n\t}\n\n\tpolicy->hashing.key_body_mctx = policy->agg_extra_mctx;\n\n\tpolicy->hashing.init(&policy->hashing, policy);\n\n\treturn &policy->funcs;\n}\n\nstatic void\ngp_hash_reset(GroupingPolicy *obj)\n{\n\tGroupingPolicyHash *policy = (GroupingPolicyHash *) obj;\n\n\tMemoryContextReset(policy->agg_extra_mctx);\n\n\tpolicy->returning_results = false;\n\n\tpolicy->hashing.reset(&policy->hashing);\n\n\tpolicy->stat_input_valid_rows = 0;\n\tpolicy->stat_input_total_rows = 0;\n\tpolicy->stat_bulk_filtered_rows = 0;\n\tpolicy->stat_consecutive_keys = 0;\n}\n\nstatic void\ncompute_single_aggregate(GroupingPolicyHash *policy, DecompressContext *dcontext,\n\t\t\t\t\t\t TupleTableSlot *vector_slot, const VectorAggDef *agg_def, void *agg_states)\n{\n\tconst uint32 *offsets = policy->key_index_for_row;\n\tMemoryContext agg_extra_mctx = policy->agg_extra_mctx;\n\n\t/*\n\t * We have functions with one argument, and one function with no arguments\n\t * (count(*)). Collect the arguments.\n\t */\n\tconst ArrowArray *arg_arrow = NULL;\n\tconst uint64 *arg_validity_bitmap = NULL;\n\tDatum arg_datum = 0;\n\tbool arg_isnull = true;\n\tif (agg_def->argument != NULL)\n\t{\n\t\tconst CompressedColumnValues values =\n\t\t\tvector_slot_evaluate_expression(dcontext,\n\t\t\t\t\t\t\t\t\t\t\tvector_slot,\n\t\t\t\t\t\t\t\t\t\t\tagg_def->effective_batch_filter,\n\t\t\t\t\t\t\t\t\t\t\tagg_def->argument);\n\n\t\tAssert(values.decompression_type != DT_Invalid);\n\t\tEnsure(values.decompression_type != DT_Iterator, \"expected arrow array but got iterator\");\n\n\t\tif (values.arrow != NULL)\n\t\t{\n\t\t\targ_arrow = values.arrow;\n\t\t\targ_validity_bitmap = values.buffers[0];\n\t\t}\n\t\telse\n\t\t{\n\t\t\tAssert(values.decompression_type == DT_Scalar);\n\t\t\targ_isnull = DatumGetBool(PointerGetDatum(values.buffers[0]));\n\t\t\targ_datum = PointerGetDatum(values.buffers[1]);\n\t\t}\n\t}\n\n\t/*\n\t * Compute the combined validity bitmap that includes the argument validity.\n\t */\n\tDecompressBatchState *batch_state = (DecompressBatchState *) vector_slot;\n\tconst size_t num_words = (batch_state->total_batch_rows + 63) / 64;\n\tconst uint64 *combined_validity = arrow_combine_validity(num_words,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t policy->tmp_filter,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t agg_def->effective_batch_filter,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t arg_validity_bitmap,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t NULL);\n\n\t/*\n\t * Now call the function, skipping the sequences of rows that didn't pass\n\t * the filters.\n\t */\n\tfor (FilterWordIterator iter =\n\t\t\t filter_word_iterator_init(batch_state->total_batch_rows, combined_validity);\n\t\t filter_word_iterator_is_valid(&iter);\n\t\t filter_word_iterator_advance(&iter))\n\t{\n\t\tif (arg_arrow != NULL)\n\t\t{\n\t\t\t/* Arrow argument. */\n\t\t\tagg_def->func.agg_many_vector(agg_states,\n\t\t\t\t\t\t\t\t\t\t  offsets,\n\t\t\t\t\t\t\t\t\t\t  combined_validity,\n\t\t\t\t\t\t\t\t\t\t  iter.start_row,\n\t\t\t\t\t\t\t\t\t\t  iter.end_row,\n\t\t\t\t\t\t\t\t\t\t  arg_arrow,\n\t\t\t\t\t\t\t\t\t\t  agg_extra_mctx);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/*\n\t\t\t * Scalar argument, or count(*). The latter has an optimized\n\t\t\t * implementation.\n\t\t\t */\n\t\t\tif (agg_def->func.agg_many_scalar != NULL)\n\t\t\t{\n\t\t\t\tagg_def->func.agg_many_scalar(agg_states,\n\t\t\t\t\t\t\t\t\t\t\t  offsets,\n\t\t\t\t\t\t\t\t\t\t\t  combined_validity,\n\t\t\t\t\t\t\t\t\t\t\t  iter.start_row,\n\t\t\t\t\t\t\t\t\t\t\t  iter.end_row,\n\t\t\t\t\t\t\t\t\t\t\t  arg_datum,\n\t\t\t\t\t\t\t\t\t\t\t  arg_isnull,\n\t\t\t\t\t\t\t\t\t\t\t  agg_extra_mctx);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tfor (int i = iter.start_row; i < iter.end_row; i++)\n\t\t\t\t{\n\t\t\t\t\tif (!arrow_row_is_valid(combined_validity, i))\n\t\t\t\t\t{\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tvoid *state = (offsets[i] * agg_def->func.state_bytes + (char *) agg_states);\n\t\t\t\t\tagg_def->func.agg_scalar(state, arg_datum, arg_isnull, 1, agg_extra_mctx);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void\ngp_hash_add_batch(GroupingPolicy *gp, DecompressContext *dcontext, TupleTableSlot *vector_slot)\n{\n\tGroupingPolicyHash *policy = (GroupingPolicyHash *) gp;\n\tuint16 nrows;\n\tconst uint64 *restrict filter = vector_slot_get_qual_result(vector_slot, &nrows);\n\n\tAssert(!policy->returning_results);\n\n\t/*\n\t * Initialize the array for storing the aggregate state offsets corresponding\n\t * to a given batch row. We don't need the offsets for the previous batch\n\t * that are currently stored there, so we don't need to use repalloc.\n\t */\n\tif ((size_t) nrows > policy->num_key_index_for_row)\n\t{\n\t\tif (policy->key_index_for_row != NULL)\n\t\t{\n\t\t\tpfree(policy->key_index_for_row);\n\t\t}\n\t\tpolicy->num_key_index_for_row = nrows;\n\t\tpolicy->key_index_for_row =\n\t\t\tpalloc(sizeof(policy->key_index_for_row[0]) * policy->num_key_index_for_row);\n\t}\n\tmemset(policy->key_index_for_row, 0, nrows * sizeof(policy->key_index_for_row[0]));\n\n\t/*\n\t * Allocate the temporary filter array for computing the combined results of\n\t * batch filter, aggregate filter and column validity.\n\t */\n\tconst size_t num_words = (nrows + 63) / 64;\n\tif (num_words > policy->num_tmp_filter_words)\n\t{\n\t\tpolicy->tmp_filter = palloc(sizeof(*policy->tmp_filter) * (num_words * 2 + 1));\n\t\tpolicy->num_tmp_filter_words = (num_words * 2 + 1);\n\t}\n\n\t/*\n\t * Arrange the input compressed columns in the order of grouping columns.\n\t */\n\tfor (int i = 0; i < policy->num_grouping_columns; i++)\n\t{\n\t\tconst GroupingColumn *def = &policy->grouping_columns[i];\n\n\t\tpolicy->current_batch_grouping_column_values[i] =\n\t\t\tvector_slot_evaluate_expression(dcontext, vector_slot, filter, def->expr);\n\t}\n\n\t/*\n\t * Call the per-batch initialization function of the hashing strategy.\n\t */\n\tpolicy->hashing.prepare_for_batch(policy, vector_slot);\n\n\t/*\n\t * Remember which grouping keys have already existed, and which we\n\t * have to initialize. State index zero is invalid.\n\t */\n\tconst uint32 last_initialized_key_index = policy->hashing.last_used_key_index;\n\tAssert(last_initialized_key_index <= policy->num_allocated_per_key_agg_states);\n\n\t/*\n\t * Add the grouping keys to the hash table, skipping the sequences\n\t * of rows that are filtered out by the batch filter.\n\t */\n\tint stats_matched_rows = 0;\n\tfor (FilterWordIterator iter = filter_word_iterator_init(nrows, filter);\n\t\t filter_word_iterator_is_valid(&iter);\n\t\t filter_word_iterator_advance(&iter))\n\t{\n\t\tstats_matched_rows += iter.end_row - iter.start_row;\n\t\tAssert((size_t) iter.end_row <= policy->num_key_index_for_row);\n\t\tpolicy->hashing.fill_offsets(policy, vector_slot, iter.start_row, iter.end_row);\n\t}\n\n\tpolicy->stat_input_total_rows += nrows;\n\tpolicy->stat_input_valid_rows += arrow_num_valid(filter, nrows);\n\tpolicy->stat_bulk_filtered_rows += nrows - stats_matched_rows;\n\n\t/*\n\t * Process the aggregate function states. We are processing single aggregate\n\t * function for the entire batch to improve the memory locality.\n\t */\n\tconst uint64 new_aggstate_rows = Max(policy->hashing.last_used_key_index + 1,\n\t\t\t\t\t\t\t\t\t\t policy->num_allocated_per_key_agg_states * 2 + 1);\n\tconst int num_fns = policy->num_agg_defs;\n\tfor (int agg_index = 0; agg_index < num_fns; agg_index++)\n\t{\n\t\tconst VectorAggDef *agg_def = &policy->agg_defs[agg_index];\n\n\t\t/*\n\t\t * If we added new keys for this batch, initialize the states for these\n\t\t * keys for this aggregate function.\n\t\t */\n\t\tif (policy->hashing.last_used_key_index > last_initialized_key_index)\n\t\t{\n\t\t\t/*\n\t\t\t * If the aggregate function states don't fit into the existing\n\t\t\t * storage, reallocate it. We will record the allocated size later,\n\t\t\t * and before that, the allocation needs to be done for every\n\t\t\t * aggregate function.\n\t\t\t */\n\t\t\tif (policy->hashing.last_used_key_index >= policy->num_allocated_per_key_agg_states)\n\t\t\t{\n\t\t\t\tpolicy->per_agg_per_key_states[agg_index] =\n\t\t\t\t\trepalloc(policy->per_agg_per_key_states[agg_index],\n\t\t\t\t\t\t\t new_aggstate_rows * agg_def->func.state_bytes);\n\t\t\t}\n\n\t\t\tvoid *first_uninitialized_state =\n\t\t\t\tagg_def->func.state_bytes * (last_initialized_key_index + 1) +\n\t\t\t\t(char *) policy->per_agg_per_key_states[agg_index];\n\t\t\tagg_def->func.agg_init(first_uninitialized_state,\n\t\t\t\t\t\t\t\t   policy->hashing.last_used_key_index -\n\t\t\t\t\t\t\t\t\t   last_initialized_key_index);\n\t\t}\n\n\t\t/*\n\t\t * Add this batch to the states of this aggregate function.\n\t\t */\n\t\tcompute_single_aggregate(policy,\n\t\t\t\t\t\t\t\t dcontext,\n\t\t\t\t\t\t\t\t vector_slot,\n\t\t\t\t\t\t\t\t agg_def,\n\t\t\t\t\t\t\t\t policy->per_agg_per_key_states[agg_index]);\n\t}\n\n\t/*\n\t * If we got new grouping keys in this batch, this means we had to\n\t * reallocate the aggregate function states for them, and now have to record\n\t * the new allocated size.\n\t */\n\tif (policy->hashing.last_used_key_index >= policy->num_allocated_per_key_agg_states)\n\t{\n\t\tAssert(new_aggstate_rows > policy->num_allocated_per_key_agg_states);\n\t\tpolicy->num_allocated_per_key_agg_states = new_aggstate_rows;\n\t}\n}\n\nstatic bool\ngp_hash_should_emit(GroupingPolicy *gp)\n{\n\tGroupingPolicyHash *policy = (GroupingPolicyHash *) gp;\n\n\tif (policy->hashing.last_used_key_index > UINT32_MAX - GLOBAL_MAX_ROWS_PER_COMPRESSION)\n\t{\n\t\t/*\n\t\t * The max valid key index is UINT32_MAX, so we have to spill if the next\n\t\t * batch can possibly lead to key index overflow.\n\t\t */\n\t\treturn true;\n\t}\n\n\t/*\n\t * Don't grow the hash table cardinality too much, otherwise we become bound\n\t * by memory reads. In general, when this first stage of grouping doesn't\n\t * significantly reduce the cardinality, it becomes pure overhead and the\n\t * work will be done by the final Postgres aggregation, so we should bail\n\t * out early here.\n\t */\n\treturn policy->hashing.get_size_bytes(&policy->hashing) > 512 * 1024;\n}\n\nstatic bool\ngp_hash_do_emit(GroupingPolicy *gp, TupleTableSlot *aggregated_slot)\n{\n\tGroupingPolicyHash *policy = (GroupingPolicyHash *) gp;\n\n\tif (!policy->returning_results)\n\t{\n\t\tpolicy->returning_results = true;\n\t\tpolicy->last_returned_key = 1;\n\n\t\tconst float keys = policy->hashing.last_used_key_index;\n\t\tif (keys > 0)\n\t\t{\n\t\t\tDEBUG_LOG(\"spill after \" UINT64_FORMAT \" input, \" UINT64_FORMAT \" valid, \" UINT64_FORMAT\n\t\t\t\t\t  \" bulk filtered, \" UINT64_FORMAT \" cons, %.0f keys, \"\n\t\t\t\t\t  \"%f ratio, %ld curctx bytes, %ld aggstate bytes\",\n\t\t\t\t\t  policy->stat_input_total_rows,\n\t\t\t\t\t  policy->stat_input_valid_rows,\n\t\t\t\t\t  policy->stat_bulk_filtered_rows,\n\t\t\t\t\t  policy->stat_consecutive_keys,\n\t\t\t\t\t  keys,\n\t\t\t\t\t  policy->stat_input_valid_rows / keys,\n\t\t\t\t\t  MemoryContextMemAllocated(CurrentMemoryContext, false),\n\t\t\t\t\t  MemoryContextMemAllocated(policy->agg_extra_mctx, false));\n\t\t}\n\t}\n\telse\n\t{\n\t\tpolicy->last_returned_key++;\n\t}\n\n\tconst uint32 current_key = policy->last_returned_key;\n\tconst uint32 keys_end = policy->hashing.last_used_key_index + 1;\n\tif (current_key >= keys_end)\n\t{\n\t\tpolicy->returning_results = false;\n\t\treturn false;\n\t}\n\n\tconst int naggs = policy->num_agg_defs;\n\tfor (int i = 0; i < naggs; i++)\n\t{\n\t\tconst VectorAggDef *agg_def = &policy->agg_defs[i];\n\t\tvoid *agg_states = policy->per_agg_per_key_states[i];\n\t\tvoid *agg_state = current_key * agg_def->func.state_bytes + (char *) agg_states;\n\t\tagg_def->func.agg_emit(agg_state,\n\t\t\t\t\t\t\t   &aggregated_slot->tts_values[agg_def->output_offset],\n\t\t\t\t\t\t\t   &aggregated_slot->tts_isnull[agg_def->output_offset]);\n\t}\n\n\tpolicy->hashing.emit_key(policy, current_key, aggregated_slot);\n\n\tDEBUG_PRINT(\"%p: output key index %d\\n\", policy, current_key);\n\n\treturn true;\n}\n\nstatic char *\ngp_hash_explain(GroupingPolicy *gp)\n{\n\tGroupingPolicyHash *policy = (GroupingPolicyHash *) gp;\n\treturn psprintf(\"hashed with %s key\", policy->hashing.explain_name);\n}\n\nstatic const GroupingPolicy grouping_policy_hash_functions = {\n\t.gp_reset = gp_hash_reset,\n\t.gp_add_batch = gp_hash_add_batch,\n\t.gp_should_emit = gp_hash_should_emit,\n\t.gp_do_emit = gp_hash_do_emit,\n\t.gp_explain = gp_hash_explain,\n};\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/grouping_policy_hash.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\n#include <nodes/pg_list.h>\n\n#include \"grouping_policy.h\"\n\n#include \"nodes/columnar_scan/compressed_batch.h\"\n\n#include \"hashing/hashing_strategy.h\"\n\ntypedef struct GroupingPolicyHash GroupingPolicyHash;\n\n/*\n * Hash grouping policy.\n *\n * The grouping and aggregation is performed as follows:\n *\n * 0) The grouping policy keeps track of the unique grouping keys seen in\n * the input rows, and the states of aggregate functions for each key. This\n * spans multiple input compressed batches, and is reset after the partial\n * aggregation results are emitted.\n *\n * 1) For each row of the new compressed batch, we obtain an index that\n * uniquely identifies its grouping key. This is done by matching the row's\n * grouping columns to the hash table recording the unique grouping keys and\n * their respective indexes. It is performed in bulk for all rows of the batch,\n * to improve memory locality. The details of this are managed by the hashing\n * strategy.\n *\n * 2) The key indexes are used to locate the aggregate function states\n * corresponding to a given row's key, and update it. This is done in bulk for all\n * rows of the batch, and for each aggregate function separately, to generate\n * simpler and potentially vectorizable code, and improve memory locality.\n *\n * 3) After the input has ended, or if the memory limit is reached, the partial\n * results are emitted into the output slot. This is done in the order of unique\n * grouping key indexes, thereby preserving the incoming key order. This\n * guarantees that this policy works correctly even in a Partial GroupAggregate\n * node, even though it's not optimal performance-wise. We only support the\n * direct order of records in batch though, not reverse. This is checked at\n * planning time.\n */\ntypedef struct GroupingPolicyHash\n{\n\t/*\n\t * We're using data inheritance from the GroupingPolicy.\n\t */\n\tGroupingPolicy funcs;\n\n\t/*\n\t * Aggregate function definitions.\n\t */\n\tint num_agg_defs;\n\tconst VectorAggDef *restrict agg_defs;\n\n\t/*\n\t * Grouping column definitions.\n\t */\n\tint num_grouping_columns;\n\tconst GroupingColumn *restrict grouping_columns;\n\n\t/*\n\t * The values of the grouping columns picked from the compressed batch and\n\t * arranged in the order of grouping column definitions.\n\t */\n\tCompressedColumnValues *restrict current_batch_grouping_column_values;\n\n\t/*\n\t * Hashing strategy that is responsible for mapping the rows to the unique\n\t * indexes of their grouping keys.\n\t */\n\tHashingStrategy hashing;\n\n\t/*\n\t * Temporary storage of unique indexes of keys corresponding to a given row\n\t * of the compressed batch that is currently being aggregated. We keep it in\n\t * the policy because it is potentially too big to keep on stack, and we\n\t * don't want to reallocate it for each batch.\n\t */\n\tuint32 *restrict key_index_for_row;\n\tuint64 num_key_index_for_row;\n\n\t/*\n\t * The temporary filter bitmap we use to combine the results of the\n\t * vectorized filters in WHERE, validity of the aggregate function argument,\n\t * and the aggregate FILTER clause. It is then used by the aggregate\n\t * function implementation to filter out the rows that don't pass.\n\t */\n\tuint64 *tmp_filter;\n\tuint64 num_tmp_filter_words;\n\n\t/*\n\t * Aggregate function states. Each element is an array of states for the\n\t * respective function from agg_defs. These arrays are indexed by the unique\n\t * grouping key indexes. The key index 0 is invalid, so the corresponding\n\t * states are unused.\n\t * The states of each aggregate function are stored separately and\n\t * contiguously, to achieve better memory locality when updating them.\n\t */\n\tvoid **per_agg_per_key_states;\n\tuint64 num_allocated_per_key_agg_states;\n\n\t/*\n\t * A memory context for aggregate functions to allocate additional data,\n\t * i.e. if they store strings or float8 datum on 32-bit systems. Valid until\n\t * the grouping policy is reset.\n\t */\n\tMemoryContext agg_extra_mctx;\n\n\t/*\n\t * Whether we are in the mode of returning the partial aggregation results.\n\t * If we are, track the index of the last returned grouping key.\n\t */\n\tbool returning_results;\n\tuint32 last_returned_key;\n\n\t/*\n\t * Some statistics for debugging.\n\t */\n\tuint64 stat_input_total_rows;\n\tuint64 stat_input_valid_rows;\n\tuint64 stat_bulk_filtered_rows;\n\tuint64 stat_consecutive_keys;\n} GroupingPolicyHash;\n\n// #define DEBUG_PRINT(...) fprintf(stderr, __VA_ARGS__)\n#ifndef DEBUG_PRINT\n#define DEBUG_PRINT(...)\n#endif\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/hashing/CMakeLists.txt",
    "content": "set(SOURCES\n    ${CMAKE_CURRENT_SOURCE_DIR}/hash_strategy_single_fixed_2.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/hash_strategy_single_fixed_4.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/hash_strategy_single_fixed_8.c\n    ${CMAKE_CURRENT_SOURCE_DIR}/hash_strategy_common.c)\n\nif(USE_UMASH)\n  list(APPEND SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/hash_strategy_single_text.c\n       ${CMAKE_CURRENT_SOURCE_DIR}/hash_strategy_serialized.c)\nendif()\n\ntarget_sources(${TSL_LIBRARY_NAME} PRIVATE ${SOURCES})\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/hashing/batch_hashing_params.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#pragma once\n\n#include \"nodes/vector_agg/grouping_policy_hash.h\"\n#include \"nodes/vector_agg/vector_slot.h\"\n\n/*\n * The data required to map the rows of the given compressed batch to the unique\n * indexes of grouping keys, using a hash table.\n */\ntypedef struct BatchHashingParams\n{\n\tconst uint64 *batch_filter;\n\tCompressedColumnValues single_grouping_column;\n\n\tint num_grouping_columns;\n\tconst CompressedColumnValues *grouping_column_values;\n\n\tGroupingPolicyHash *policy;\n\tHashingStrategy *restrict hashing;\n\n\tuint32 *restrict result_key_indexes;\n} BatchHashingParams;\n\nstatic pg_attribute_always_inline BatchHashingParams\nbuild_batch_hashing_params(GroupingPolicyHash *policy, TupleTableSlot *vector_slot)\n{\n\tuint16 nrows;\n\tBatchHashingParams params = {\n\t\t.policy = policy,\n\t\t.hashing = &policy->hashing,\n\t\t.batch_filter = vector_slot_get_qual_result(vector_slot, &nrows),\n\t\t.num_grouping_columns = policy->num_grouping_columns,\n\t\t.grouping_column_values = policy->current_batch_grouping_column_values,\n\t\t.result_key_indexes = policy->key_index_for_row,\n\t};\n\n\tAssert(policy->num_grouping_columns > 0);\n\tif (policy->num_grouping_columns == 1)\n\t{\n\t\tparams.single_grouping_column = policy->current_batch_grouping_column_values[0];\n\t}\n\n\treturn params;\n}\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/hashing/hash64.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n/*\n * We can use crc32 as a hash function, it has bad properties but takes only one\n * cycle, which is why it is sometimes used in the existing hash table\n * implementations. When we don't have the crc32 instruction, use the SplitMix64\n * finalizer.\n */\nstatic pg_attribute_always_inline uint64\nhash64_splitmix(uint64 x)\n{\n\tx ^= x >> 30;\n\tx *= 0xbf58476d1ce4e5b9U;\n\tx ^= x >> 27;\n\tx *= 0x94d049bb133111ebU;\n\tx ^= x >> 31;\n\treturn x;\n}\n\n#ifdef USE_SSE42_CRC32C\n#include <nmmintrin.h>\nstatic pg_attribute_always_inline uint64\nhash64_crc(uint64 x)\n{\n\treturn _mm_crc32_u64(~0ULL, x);\n}\n\n#define HASH64 hash64_crc\n#else\n#define HASH64 hash64_splitmix\n#endif\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/hashing/hash_strategy_common.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include \"hashing_strategy.h\"\n\n#include \"nodes/vector_agg/exec.h\"\n#include \"nodes/vector_agg/grouping_policy_hash.h\"\n\n/*\n * Allocate enough storage for keys, given that each row of the new compressed\n * batch might turn out to be a new grouping key. We do this separately to avoid\n * allocations in the hot loop that fills the hash table.\n */\nvoid\nhash_strategy_output_key_alloc(GroupingPolicyHash *policy, uint16 nrows)\n{\n\tHashingStrategy *hashing = &policy->hashing;\n\tconst uint32 num_possible_keys = hashing->last_used_key_index + 1 + nrows;\n\n\tif (num_possible_keys > hashing->num_allocated_output_keys)\n\t{\n\t\thashing->num_allocated_output_keys = num_possible_keys * 2 + 1;\n\t\tconst size_t new_bytes = sizeof(Datum) * hashing->num_allocated_output_keys;\n\t\tif (hashing->output_keys == NULL)\n\t\t{\n\t\t\thashing->output_keys = palloc(new_bytes);\n\t\t}\n\t\telse\n\t\t{\n\t\t\thashing->output_keys = repalloc(hashing->output_keys, new_bytes);\n\t\t}\n\t}\n}\n\n/*\n * Emit a single-column grouping key with the given index into the aggregated\n * slot.\n */\nvoid\nhash_strategy_output_key_single_emit(GroupingPolicyHash *policy, uint32 current_key,\n\t\t\t\t\t\t\t\t\t TupleTableSlot *aggregated_slot)\n{\n\tHashingStrategy *hashing = &policy->hashing;\n\tAssert(policy->num_grouping_columns == 1);\n\n\tconst GroupingColumn *col = &policy->grouping_columns[0];\n\taggregated_slot->tts_values[col->output_offset] = hashing->output_keys[current_key];\n\taggregated_slot->tts_isnull[col->output_offset] = current_key == hashing->null_key_index;\n}\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/hashing/hash_strategy_impl.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n#include \"batch_hashing_params.h\"\n#include \"nodes/vector_agg/vector_slot.h\"\n\n/*\n * The hash table maps the value of the grouping key to its unique index.\n * We don't store any extra information here, because we're accessing the memory\n * of the hash table randomly, and want it to be as small as possible to fit the\n * caches.\n */\ntypedef struct FUNCTION_NAME(entry)\n{\n\t/* Key index 0 is invalid. */\n\tuint32 key_index;\n\n\tuint8 status;\n\n\tHASH_TABLE_KEY_TYPE hash_table_key;\n} FUNCTION_NAME(entry);\n\n#define SH_PREFIX KEY_VARIANT\n#define SH_ELEMENT_TYPE FUNCTION_NAME(entry)\n#define SH_KEY_TYPE HASH_TABLE_KEY_TYPE\n#define SH_KEY hash_table_key\n#define SH_HASH_KEY(tb, key) KEY_HASH(key)\n#define SH_EQUAL(tb, a, b) KEY_EQUAL(a, b)\n#define SH_SCOPE static inline\n#define SH_DECLARE\n#define SH_DEFINE\n#include <lib/simplehash.h>\n\nstruct FUNCTION_NAME(hash);\n\nstatic uint64\nFUNCTION_NAME(get_size_bytes)(HashingStrategy *hashing)\n{\n\tstruct FUNCTION_NAME(hash) *hash = (struct FUNCTION_NAME(hash) *) hashing->table;\n\treturn hash->members * sizeof(FUNCTION_NAME(entry));\n}\n\nstatic void\nFUNCTION_NAME(hash_strategy_init)(HashingStrategy *hashing, GroupingPolicyHash *policy)\n{\n\thashing->table =\n\t\tFUNCTION_NAME(create)(CurrentMemoryContext, policy->num_allocated_per_key_agg_states, NULL);\n\n\tFUNCTION_NAME(key_hashing_init)(hashing);\n}\n\nstatic void\nFUNCTION_NAME(hash_strategy_reset)(HashingStrategy *hashing)\n{\n\tstruct FUNCTION_NAME(hash) *table = (struct FUNCTION_NAME(hash) *) hashing->table;\n\tFUNCTION_NAME(reset)(table);\n\n\thashing->last_used_key_index = 0;\n\n\thashing->null_key_index = 0;\n\n\t/*\n\t * Have to reset this because it's in the key body context which is also\n\t * reset here.\n\t */\n\thashing->tmp_key_storage = NULL;\n\thashing->num_tmp_key_storage_bytes = 0;\n}\n\nstatic void\nFUNCTION_NAME(hash_strategy_prepare_for_batch)(GroupingPolicyHash *policy,\n\t\t\t\t\t\t\t\t\t\t\t   TupleTableSlot *vector_slot)\n{\n\tuint16 nrows = 0;\n\tvector_slot_get_qual_result(vector_slot, &nrows);\n\thash_strategy_output_key_alloc(policy, nrows);\n\tFUNCTION_NAME(key_hashing_prepare_for_batch)(policy, vector_slot);\n}\n\n/*\n * Fill the unique key indexes for all rows of the batch, using a hash table.\n */\nstatic pg_attribute_always_inline void\nFUNCTION_NAME(fill_offsets_impl)(BatchHashingParams params, int start_row, int end_row)\n{\n\tHashingStrategy *restrict hashing = params.hashing;\n\n\tuint32 *restrict indexes = params.result_key_indexes;\n\n\tstruct FUNCTION_NAME(hash) *restrict table = hashing->table;\n\n\tHASH_TABLE_KEY_TYPE prev_hash_table_key = { 0 };\n\tuint32 previous_key_index = 0;\n\tfor (int row = start_row; row < end_row; row++)\n\t{\n\t\tif (!arrow_row_is_valid(params.batch_filter, row))\n\t\t{\n\t\t\t/* The row doesn't pass the filter. */\n\t\t\tDEBUG_PRINT(\"%p: row %d doesn't pass batch filter\\n\", hashing, row);\n\t\t\tcontinue;\n\t\t}\n\n\t\t/*\n\t\t * Get the key for the given row. For some hashing strategies, the key\n\t\t * that is used for the hash table is different from actual values of\n\t\t * the grouping columns, termed \"output key\" here.\n\t\t */\n\t\tbool key_valid = false;\n\t\tOUTPUT_KEY_TYPE output_key = { 0 };\n\t\tHASH_TABLE_KEY_TYPE hash_table_key = { 0 };\n\t\tFUNCTION_NAME(key_hashing_get_key)(params, row, &output_key, &hash_table_key, &key_valid);\n\n\t\tif (unlikely(!key_valid))\n\t\t{\n\t\t\t/* The key is null. */\n\t\t\tif (hashing->null_key_index == 0)\n\t\t\t{\n\t\t\t\thashing->null_key_index = ++hashing->last_used_key_index;\n\t\t\t}\n\t\t\tindexes[row] = hashing->null_key_index;\n\t\t\tDEBUG_PRINT(\"%p: row %d null key index %d\\n\", hashing, row, hashing->null_key_index);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (likely(previous_key_index != 0) && KEY_EQUAL(hash_table_key, prev_hash_table_key))\n\t\t{\n\t\t\t/*\n\t\t\t * In real data sets, we often see consecutive rows with the\n\t\t\t * same value of a grouping column, so checking for this case\n\t\t\t * improves performance. For multi-column keys, this is unlikely,\n\t\t\t * but we currently often have suboptimal plans that use this policy\n\t\t\t * as a GroupAggregate, so we still use this as an easy optimization\n\t\t\t * for that case.\n\t\t\t */\n\t\t\tindexes[row] = previous_key_index;\n#ifndef NDEBUG\n\t\t\tparams.policy->stat_consecutive_keys++;\n#endif\n\t\t\tDEBUG_PRINT(\"%p: row %d consecutive key index %d\\n\", hashing, row, previous_key_index);\n\t\t\tcontinue;\n\t\t}\n\n\t\t/*\n\t\t * Find the key using the hash table.\n\t\t */\n\t\tbool found = false;\n\t\tFUNCTION_NAME(entry) *restrict entry = FUNCTION_NAME(insert)(table, hash_table_key, &found);\n\t\tif (!found)\n\t\t{\n\t\t\t/*\n\t\t\t * New key, have to store it persistently.\n\t\t\t */\n\t\t\tconst uint32 index = ++hashing->last_used_key_index;\n\t\t\tentry->key_index = index;\n\t\t\tFUNCTION_NAME(key_hashing_store_new)(hashing, index, output_key);\n\t\t\tDEBUG_PRINT(\"%p: row %d new key index %d\\n\", hashing, row, index);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tDEBUG_PRINT(\"%p: row %d old key index %d\\n\", hashing, row, entry->key_index);\n\t\t}\n\t\tindexes[row] = entry->key_index;\n\n\t\tprevious_key_index = entry->key_index;\n\t\tprev_hash_table_key = entry->hash_table_key;\n\t}\n}\n\nstatic void\nFUNCTION_NAME(fill_offsets)(GroupingPolicyHash *policy, TupleTableSlot *vector_slot, int start_row,\n\t\t\t\t\t\t\tint end_row)\n{\n\tAssert((size_t) end_row <= policy->num_key_index_for_row);\n\n\tBatchHashingParams params = build_batch_hashing_params(policy, vector_slot);\n\n\tFUNCTION_NAME(fill_offsets_impl)(params, start_row, end_row);\n}\n\nHashingStrategy FUNCTION_NAME(strategy) = {\n\t.emit_key = FUNCTION_NAME(emit_key),\n\t.explain_name = EXPLAIN_NAME,\n\t.fill_offsets = FUNCTION_NAME(fill_offsets),\n\t.get_size_bytes = FUNCTION_NAME(get_size_bytes),\n\t.init = FUNCTION_NAME(hash_strategy_init),\n\t.prepare_for_batch = FUNCTION_NAME(hash_strategy_prepare_for_batch),\n\t.reset = FUNCTION_NAME(hash_strategy_reset),\n};\n\n#undef EXPLAIN_NAME\n#undef KEY_VARIANT\n#undef KEY_EQUAL\n#undef OUTPUT_KEY_TYPE\n#undef HASH_TABLE_KEY_TYPE\n#undef USE_DICT_HASHING\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/hashing/hash_strategy_impl_single_fixed_key.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * Key handling function for a single fixed-size grouping key.\n */\n\n#include \"batch_hashing_params.h\"\n\nstatic void\nFUNCTION_NAME(key_hashing_init)(HashingStrategy *hashing)\n{\n}\n\nstatic void\nFUNCTION_NAME(key_hashing_prepare_for_batch)(GroupingPolicyHash *policy,\n\t\t\t\t\t\t\t\t\t\t\t TupleTableSlot *vector_slot)\n{\n}\n\nstatic pg_attribute_always_inline void\nFUNCTION_NAME(key_hashing_get_key)(BatchHashingParams params, int row,\n\t\t\t\t\t\t\t\t   void *restrict output_key_ptr, void *restrict hash_table_key_ptr,\n\t\t\t\t\t\t\t\t   bool *restrict valid)\n{\n\tOUTPUT_KEY_TYPE *restrict output_key = (OUTPUT_KEY_TYPE *) output_key_ptr;\n\tHASH_TABLE_KEY_TYPE *restrict hash_table_key = (HASH_TABLE_KEY_TYPE *) hash_table_key_ptr;\n\n\tif (unlikely(params.single_grouping_column.decompression_type == DT_Scalar))\n\t{\n\t\t*output_key =\n\t\t\tDATUM_TO_OUTPUT_KEY(PointerGetDatum(params.single_grouping_column.buffers[1]));\n\t\t*valid = !DatumGetBool(PointerGetDatum(params.single_grouping_column.buffers[0]));\n\t}\n\telse if (params.single_grouping_column.decompression_type == sizeof(OUTPUT_KEY_TYPE))\n\t{\n\t\tconst OUTPUT_KEY_TYPE *values = params.single_grouping_column.buffers[1];\n\t\t*valid = arrow_row_is_valid(params.single_grouping_column.buffers[0], row);\n\t\t*output_key = values[row];\n\t}\n\telse\n\t{\n\t\tpg_unreachable();\n\t}\n\n\t/*\n\t * For the fixed-size hash grouping, we use the output key as the hash table\n\t * key as well.\n\t */\n\t*hash_table_key = *output_key;\n}\n\nstatic pg_attribute_always_inline void\nFUNCTION_NAME(key_hashing_store_new)(HashingStrategy *restrict hashing, uint32 new_key_index,\n\t\t\t\t\t\t\t\t\t OUTPUT_KEY_TYPE output_key)\n{\n\thashing->output_keys[new_key_index] = OUTPUT_KEY_TO_DATUM(output_key);\n}\n\nstatic void\nFUNCTION_NAME(emit_key)(GroupingPolicyHash *policy, uint32 current_key,\n\t\t\t\t\t\tTupleTableSlot *aggregated_slot)\n{\n\thash_strategy_output_key_single_emit(policy, current_key, aggregated_slot);\n}\n\n#undef DATUM_TO_OUTPUT_KEY\n#undef OUTPUT_KEY_TO_DATUM\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/hashing/hash_strategy_serialized.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * Implementation of column hashing for multiple serialized columns.\n */\n\n#include <postgres.h>\n\n#include <common/hashfn.h>\n\n#include \"compression/arrow_c_data_interface.h\"\n#include \"nodes/columnar_scan/compressed_batch.h\"\n#include \"nodes/vector_agg/exec.h\"\n#include \"nodes/vector_agg/grouping_policy_hash.h\"\n#include \"template_helper.h\"\n\n#include \"batch_hashing_params.h\"\n\n#include \"umash_fingerprint_key.h\"\n\n#define EXPLAIN_NAME \"serialized\"\n#define KEY_VARIANT serialized\n#define OUTPUT_KEY_TYPE text *\n\nstatic void\nserialized_key_hashing_init(HashingStrategy *hashing)\n{\n\thashing->umash_params = umash_key_hashing_init();\n}\n\nstatic void\nserialized_key_hashing_prepare_for_batch(GroupingPolicyHash *policy, TupleTableSlot *vector_slot)\n{\n}\n\nstatic pg_attribute_always_inline bool\nbyte_bitmap_row_is_valid(const uint8 *bitmap, size_t row_number)\n{\n\tconst size_t byte_index = row_number / 8;\n\tconst size_t bit_index = row_number % 8;\n\tconst uint8 mask = ((uint8) 1) << bit_index;\n\treturn bitmap[byte_index] & mask;\n}\n\nstatic pg_attribute_always_inline void\nbyte_bitmap_set_row_validity(uint8 *bitmap, size_t row_number, bool value)\n{\n\tconst size_t byte_index = row_number / 8;\n\tconst size_t bit_index = row_number % 8;\n\tconst uint8 mask = ((uint8) 1) << bit_index;\n\tconst uint8 new_bit = ((uint8) value) << bit_index;\n\n\tbitmap[byte_index] = (bitmap[byte_index] & ~mask) | new_bit;\n\n\tAssert(byte_bitmap_row_is_valid(bitmap, row_number) == value);\n}\n\nstatic pg_attribute_always_inline void\nserialized_key_hashing_get_key(BatchHashingParams params, int row, void *restrict output_key_ptr,\n\t\t\t\t\t\t\t   void *restrict hash_table_key_ptr, bool *restrict valid)\n{\n\tHashingStrategy *hashing = params.hashing;\n\n\ttext **restrict output_key = (text **) output_key_ptr;\n\tHASH_TABLE_KEY_TYPE *restrict hash_table_key = (HASH_TABLE_KEY_TYPE *) hash_table_key_ptr;\n\n\tconst int num_columns = params.num_grouping_columns;\n\n\tconst size_t bitmap_bytes = (num_columns + 7) / 8;\n\n\t/*\n\t * Loop through the grouping columns to determine the length of the key. We\n\t * need that to allocate memory to store it.\n\t *\n\t * The key has the null bitmap at the beginning.\n\t */\n\tsize_t num_bytes = bitmap_bytes;\n\tfor (int column_index = 0; column_index < num_columns; column_index++)\n\t{\n\t\tconst CompressedColumnValues *column_values = &params.grouping_column_values[column_index];\n\n\t\tif (column_values->decompression_type == DT_Scalar)\n\t\t{\n\t\t\tif (!DatumGetBool(PointerGetDatum(column_values->buffers[0])))\n\t\t\t{\n\t\t\t\tconst GroupingColumn *def = &params.policy->grouping_columns[column_index];\n\t\t\t\tif (def->value_bytes > 0)\n\t\t\t\t{\n\t\t\t\t\tnum_bytes += def->value_bytes;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t/*\n\t\t\t\t\t * The default value always has a long varlena header, but\n\t\t\t\t\t * we are going to use short if it fits.\n\t\t\t\t\t */\n\t\t\t\t\tconst int32 value_bytes = VARSIZE_ANY_EXHDR(column_values->buffers[1]);\n\t\t\t\t\tif (value_bytes + VARHDRSZ_SHORT <= VARATT_SHORT_MAX)\n\t\t\t\t\t{\n\t\t\t\t\t\t/* Short varlena, unaligned. */\n\t\t\t\t\t\tconst int total_bytes = value_bytes + VARHDRSZ_SHORT;\n\t\t\t\t\t\tnum_bytes += total_bytes;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\t/* Long varlena, requires alignment. */\n\t\t\t\t\t\tconst int total_bytes = value_bytes + VARHDRSZ;\n\t\t\t\t\t\tnum_bytes = TYPEALIGN(4, num_bytes) + total_bytes;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst bool is_valid = arrow_row_is_valid(column_values->buffers[0], row);\n\t\tif (!is_valid)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (column_values->decompression_type > 0)\n\t\t{\n\t\t\tnum_bytes += column_values->decompression_type;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (column_values->decompression_type == DT_ArrowBits)\n\t\t{\n\t\t\tnum_bytes += 1;\n\t\t\tcontinue;\n\t\t}\n\n\t\tAssert(column_values->decompression_type == DT_ArrowText ||\n\t\t\t   column_values->decompression_type == DT_ArrowTextDict);\n\t\tAssert((column_values->decompression_type == DT_ArrowTextDict) ==\n\t\t\t   (column_values->buffers[3] != NULL));\n\n\t\tconst uint32 data_row = (column_values->decompression_type == DT_ArrowTextDict) ?\n\t\t\t\t\t\t\t\t\t((int16 *) column_values->buffers[3])[row] :\n\t\t\t\t\t\t\t\t\trow;\n\t\tconst uint32 start = ((uint32 *) column_values->buffers[1])[data_row];\n\t\tconst int32 value_bytes = ((uint32 *) column_values->buffers[1])[data_row + 1] - start;\n\n\t\tif (value_bytes + VARHDRSZ_SHORT <= VARATT_SHORT_MAX)\n\t\t{\n\t\t\t/* Short varlena, unaligned. */\n\t\t\tconst int total_bytes = value_bytes + VARHDRSZ_SHORT;\n\t\t\tnum_bytes += total_bytes;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* Long varlena, requires alignment. */\n\t\t\tconst int total_bytes = value_bytes + VARHDRSZ;\n\t\t\tnum_bytes = TYPEALIGN(4, num_bytes) + total_bytes;\n\t\t}\n\t}\n\n\t/*\n\t * The key has short or long varlena header. This is a little tricky, we\n\t * decide the header length after we have counted all the columns, but we\n\t * put it at the beginning. Technically it could change the length because\n\t * of the alignment. In practice, we only use alignment by 4 bytes for long\n\t * varlena strings, and if we have at least one long varlena string column,\n\t * the key is also going to use the long varlena header which is 4 bytes, so\n\t * the alignment is not affected. If we use the short varlena header for the\n\t * key, it necessarily means that there were no long varlena columns and\n\t * therefore no alignment is needed.\n\t */\n\tconst bool key_uses_short_header = num_bytes + VARHDRSZ_SHORT <= VARATT_SHORT_MAX;\n\tnum_bytes += key_uses_short_header ? VARHDRSZ_SHORT : VARHDRSZ;\n\n\t/*\n\t * Use temporary storage for the new key, reallocate if it's too small.\n\t */\n\tif (num_bytes > hashing->num_tmp_key_storage_bytes)\n\t{\n\t\tif (hashing->tmp_key_storage != NULL)\n\t\t{\n\t\t\tpfree(hashing->tmp_key_storage);\n\t\t}\n\t\thashing->tmp_key_storage = MemoryContextAlloc(hashing->key_body_mctx, num_bytes);\n\t\thashing->num_tmp_key_storage_bytes = num_bytes;\n\t}\n\tuint8 *restrict serialized_key_storage = hashing->tmp_key_storage;\n\n\t/*\n\t * Build the actual grouping key.\n\t */\n\tuint32 offset = 0;\n\toffset += key_uses_short_header ? VARHDRSZ_SHORT : VARHDRSZ;\n\n\t/*\n\t * We must always save the validity bitmap, even when there are no\n\t * null words, so that the key is uniquely deserializable. Otherwise a key\n\t * with some nulls might collide with a key with no nulls.\n\t */\n\tuint8 *restrict serialized_key_validity_bitmap = &serialized_key_storage[offset];\n\toffset += bitmap_bytes;\n\n\t/*\n\t * Loop through the grouping columns again and add their values to the\n\t * grouping key.\n\t */\n\tfor (int column_index = 0; column_index < num_columns; column_index++)\n\t{\n\t\tconst CompressedColumnValues *column_values = &params.grouping_column_values[column_index];\n\n\t\tif (column_values->decompression_type == DT_Scalar)\n\t\t{\n\t\t\tconst bool is_valid = !DatumGetBool(PointerGetDatum(column_values->buffers[0]));\n\t\t\tDatum value = PointerGetDatum(column_values->buffers[1]);\n\t\t\tbyte_bitmap_set_row_validity(serialized_key_validity_bitmap, column_index, is_valid);\n\t\t\tif (is_valid)\n\t\t\t{\n\t\t\t\tconst GroupingColumn *def = &params.policy->grouping_columns[column_index];\n\t\t\t\tif (def->by_value)\n\t\t\t\t{\n\t\t\t\t\tmemcpy(&serialized_key_storage[offset], &value, def->value_bytes);\n\n\t\t\t\t\toffset += def->value_bytes;\n\t\t\t\t}\n\t\t\t\telse if (def->value_bytes > 0)\n\t\t\t\t{\n\t\t\t\t\tmemcpy(&serialized_key_storage[offset],\n\t\t\t\t\t\t   DatumGetPointer(value),\n\t\t\t\t\t\t   def->value_bytes);\n\n\t\t\t\t\toffset += def->value_bytes;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t/*\n\t\t\t\t\t * The default value always has a long varlena header, but\n\t\t\t\t\t * we are going to use short if it fits.\n\t\t\t\t\t */\n\t\t\t\t\tconst int32 value_bytes = VARSIZE_ANY_EXHDR(value);\n\t\t\t\t\tif (value_bytes + VARHDRSZ_SHORT <= VARATT_SHORT_MAX)\n\t\t\t\t\t{\n\t\t\t\t\t\t/* Short varlena, no alignment. */\n\t\t\t\t\t\tconst int32 total_bytes = value_bytes + VARHDRSZ_SHORT;\n\t\t\t\t\t\tSET_VARSIZE_SHORT(&serialized_key_storage[offset], total_bytes);\n\t\t\t\t\t\toffset += VARHDRSZ_SHORT;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\t/* Long varlena, requires alignment. Zero out the alignment bytes. */\n\t\t\t\t\t\tmemset(&serialized_key_storage[offset], 0, 4);\n\t\t\t\t\t\toffset = TYPEALIGN(4, offset);\n\t\t\t\t\t\tconst int32 total_bytes = value_bytes + VARHDRSZ;\n\t\t\t\t\t\tSET_VARSIZE(&serialized_key_storage[offset], total_bytes);\n\t\t\t\t\t\toffset += VARHDRSZ;\n\t\t\t\t\t}\n\n\t\t\t\t\tmemcpy(&serialized_key_storage[offset], VARDATA_ANY(value), value_bytes);\n\n\t\t\t\t\toffset += value_bytes;\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst bool is_valid = arrow_row_is_valid(column_values->buffers[0], row);\n\t\tbyte_bitmap_set_row_validity(serialized_key_validity_bitmap, column_index, is_valid);\n\n\t\tif (!is_valid)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (column_values->decompression_type > 0)\n\t\t{\n\t\t\tAssert(offset <= UINT_MAX - column_values->decompression_type);\n\n\t\t\tswitch ((int) column_values->decompression_type)\n\t\t\t{\n\t\t\t\tcase 2:\n\t\t\t\t\tmemcpy(&serialized_key_storage[offset],\n\t\t\t\t\t\t   row + (int16 *) column_values->buffers[1],\n\t\t\t\t\t\t   2);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 4:\n\t\t\t\t\tmemcpy(&serialized_key_storage[offset],\n\t\t\t\t\t\t   row + (int32 *) column_values->buffers[1],\n\t\t\t\t\t\t   4);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 8:\n\t\t\t\t\tmemcpy(&serialized_key_storage[offset],\n\t\t\t\t\t\t   row + (int64 *) column_values->buffers[1],\n\t\t\t\t\t\t   8);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 16:\n\t\t\t\t\tmemcpy(&serialized_key_storage[offset],\n\t\t\t\t\t\t   (row * 2) + (int64 *) column_values->buffers[1],\n\t\t\t\t\t\t   16);\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tpg_unreachable();\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\toffset += column_values->decompression_type;\n\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (column_values->decompression_type == DT_ArrowBits)\n\t\t{\n\t\t\tserialized_key_storage[offset] = arrow_row_is_valid(column_values->buffers[1], row);\n\t\t\toffset += 1;\n\t\t\tcontinue;\n\t\t}\n\n\t\tAssert(column_values->decompression_type == DT_ArrowText ||\n\t\t\t   column_values->decompression_type == DT_ArrowTextDict);\n\n\t\tconst uint32 data_row = column_values->decompression_type == DT_ArrowTextDict ?\n\t\t\t\t\t\t\t\t\t((int16 *) column_values->buffers[3])[row] :\n\t\t\t\t\t\t\t\t\trow;\n\t\tconst uint32 start = ((uint32 *) column_values->buffers[1])[data_row];\n\t\tconst int32 value_bytes = ((uint32 *) column_values->buffers[1])[data_row + 1] - start;\n\n\t\tif (value_bytes + VARHDRSZ_SHORT <= VARATT_SHORT_MAX)\n\t\t{\n\t\t\t/* Short varlena, unaligned. */\n\t\t\tconst int32 total_bytes = value_bytes + VARHDRSZ_SHORT;\n\t\t\tSET_VARSIZE_SHORT(&serialized_key_storage[offset], total_bytes);\n\t\t\toffset += VARHDRSZ_SHORT;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* Long varlena, requires alignment. Zero out the alignment bytes. */\n\t\t\tmemset(&serialized_key_storage[offset], 0, 4);\n\t\t\toffset = TYPEALIGN(4, offset);\n\t\t\tconst int32 total_bytes = value_bytes + VARHDRSZ;\n\t\t\tSET_VARSIZE(&serialized_key_storage[offset], total_bytes);\n\t\t\toffset += VARHDRSZ;\n\t\t}\n\t\tmemcpy(&serialized_key_storage[offset],\n\t\t\t   &((uint8 *) column_values->buffers[2])[start],\n\t\t\t   value_bytes);\n\n\t\toffset += value_bytes;\n\t}\n\n\tAssert(offset == num_bytes);\n\n\tif (key_uses_short_header)\n\t{\n\t\tSET_VARSIZE_SHORT(serialized_key_storage, offset);\n\t}\n\telse\n\t{\n\t\tSET_VARSIZE(serialized_key_storage, offset);\n\t}\n\n\tDEBUG_PRINT(\"key is %d bytes: \", offset);\n\tfor (size_t i = 0; i < offset; i++)\n\t{\n\t\tDEBUG_PRINT(\"%.2x.\", serialized_key_storage[i]);\n\t}\n\tDEBUG_PRINT(\"\\n\");\n\n\t*output_key = (text *) serialized_key_storage;\n\n\tAssert(VARSIZE_ANY(*output_key) == num_bytes);\n\n\t/*\n\t * The multi-column key is always considered non-null, and the null flags\n\t * for the individual columns are stored in a bitmap that is part of the\n\t * key.\n\t */\n\t*valid = true;\n\n\tconst struct umash_fp fp = umash_fprint(params.hashing->umash_params,\n\t\t\t\t\t\t\t\t\t\t\t/* seed = */ ~0ULL,\n\t\t\t\t\t\t\t\t\t\t\tserialized_key_storage,\n\t\t\t\t\t\t\t\t\t\t\tnum_bytes);\n\t*hash_table_key = umash_fingerprint_get_key(fp);\n}\n\nstatic pg_attribute_always_inline void\nserialized_key_hashing_store_new(HashingStrategy *restrict hashing, uint32 new_key_index,\n\t\t\t\t\t\t\t\t text *output_key)\n{\n\t/*\n\t * We will store this key so we have to consume the temporary storage that\n\t * was used for it. The subsequent keys will need to allocate new memory.\n\t */\n\tAssert(hashing->tmp_key_storage == (void *) output_key);\n\thashing->tmp_key_storage = NULL;\n\thashing->num_tmp_key_storage_bytes = 0;\n\n\thashing->output_keys[new_key_index] = PointerGetDatum(output_key);\n}\n\nstatic void\nserialized_emit_key(GroupingPolicyHash *policy, uint32 current_key, TupleTableSlot *aggregated_slot)\n{\n\tconst HashingStrategy *hashing = &policy->hashing;\n\tconst int num_key_columns = policy->num_grouping_columns;\n\tconst Datum serialized_key_datum = hashing->output_keys[current_key];\n\tconst uint8 *serialized_key = (const uint8 *) VARDATA_ANY(serialized_key_datum);\n\tPG_USED_FOR_ASSERTS_ONLY const int key_data_bytes = VARSIZE_ANY_EXHDR(serialized_key_datum);\n\tconst uint8 *restrict ptr = serialized_key;\n\n\t/*\n\t * We have the column validity bitmap at the beginning of the key.\n\t */\n\tconst int bitmap_bytes = (num_key_columns + 7) / 8;\n\tAssert(bitmap_bytes <= key_data_bytes);\n\tconst uint8 *restrict key_validity_bitmap = serialized_key;\n\tptr += bitmap_bytes;\n\n\tDEBUG_PRINT(\"emit key #%d, with header %ld without %d bytes: \",\n\t\t\t\tcurrent_key,\n\t\t\t\tVARSIZE_ANY(serialized_key_datum),\n\t\t\t\tkey_data_bytes);\n\tfor (size_t i = 0; i < VARSIZE_ANY(serialized_key_datum); i++)\n\t{\n\t\tDEBUG_PRINT(\"%.2x.\", ((const uint8 *) serialized_key_datum)[i]);\n\t}\n\tDEBUG_PRINT(\"\\n\");\n\n\tfor (int column_index = 0; column_index < num_key_columns; column_index++)\n\t{\n\t\tconst GroupingColumn *col = &policy->grouping_columns[column_index];\n\t\tconst bool isnull = !byte_bitmap_row_is_valid(key_validity_bitmap, column_index);\n\n\t\taggregated_slot->tts_isnull[col->output_offset] = isnull;\n\n\t\tif (isnull)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\tDatum *output = &aggregated_slot->tts_values[col->output_offset];\n\t\tif (col->value_bytes > 0)\n\t\t{\n\t\t\tif (col->by_value)\n\t\t\t{\n\t\t\t\tAssert(col->by_value);\n\t\t\t\tAssert((size_t) col->value_bytes <= sizeof(Datum));\n\t\t\t\t*output = 0;\n\t\t\t\tmemcpy(output, ptr, col->value_bytes);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t*output = PointerGetDatum(ptr);\n\t\t\t}\n\n\t\t\tptr += col->value_bytes;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tAssert(col->value_bytes == -1);\n\t\t\tAssert(!col->by_value);\n\t\t\tif (VARATT_IS_SHORT(ptr))\n\t\t\t{\n\t\t\t\t*output = PointerGetDatum(ptr);\n\t\t\t\tptr += VARSIZE_SHORT(ptr);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tptr = (const uint8 *) TYPEALIGN(4, ptr);\n\t\t\t\t*output = PointerGetDatum(ptr);\n\t\t\t\tptr += VARSIZE(ptr);\n\t\t\t}\n\t\t}\n\t}\n\n\tAssert(ptr == serialized_key + key_data_bytes);\n}\n\n#include \"hash_strategy_impl.c\"\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/hashing/hash_strategy_single_fixed_2.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * Implementation of column hashing for a single fixed size 2-byte column.\n */\n\n#include <postgres.h>\n\n#include \"compression/arrow_c_data_interface.h\"\n#include \"hash64.h\"\n#include \"nodes/columnar_scan/compressed_batch.h\"\n#include \"nodes/vector_agg/exec.h\"\n#include \"nodes/vector_agg/grouping_policy_hash.h\"\n#include \"template_helper.h\"\n\n#define EXPLAIN_NAME \"single 2-byte\"\n#define KEY_VARIANT single_fixed_2\n#define OUTPUT_KEY_TYPE int16\n#define HASH_TABLE_KEY_TYPE OUTPUT_KEY_TYPE\n#define DATUM_TO_OUTPUT_KEY DatumGetInt16\n#define OUTPUT_KEY_TO_DATUM Int16GetDatum\n\n#include \"hash_strategy_impl_single_fixed_key.c\"\n\n#define KEY_EQUAL(a, b) a == b\n#define KEY_HASH(X) HASH64(X)\n\n#include \"hash_strategy_impl.c\"\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/hashing/hash_strategy_single_fixed_4.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * Implementation of column hashing for a single fixed size 4-byte column.\n */\n\n#include <postgres.h>\n\n#include \"compression/arrow_c_data_interface.h\"\n#include \"hash64.h\"\n#include \"nodes/columnar_scan/compressed_batch.h\"\n#include \"nodes/vector_agg/exec.h\"\n#include \"nodes/vector_agg/grouping_policy_hash.h\"\n#include \"template_helper.h\"\n\n#define EXPLAIN_NAME \"single 4-byte\"\n#define KEY_VARIANT single_fixed_4\n#define OUTPUT_KEY_TYPE int32\n#define HASH_TABLE_KEY_TYPE int32\n#define DATUM_TO_OUTPUT_KEY DatumGetInt32\n#define OUTPUT_KEY_TO_DATUM Int32GetDatum\n\n#include \"hash_strategy_impl_single_fixed_key.c\"\n\n#define KEY_EQUAL(a, b) a == b\n#define KEY_HASH(X) HASH64(X)\n\n#include \"hash_strategy_impl.c\"\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/hashing/hash_strategy_single_fixed_8.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * Implementation of column hashing for a single fixed size 8-byte column.\n */\n\n#include <postgres.h>\n\n#include \"compression/arrow_c_data_interface.h\"\n#include \"hash64.h\"\n#include \"nodes/columnar_scan/compressed_batch.h\"\n#include \"nodes/vector_agg/exec.h\"\n#include \"nodes/vector_agg/grouping_policy_hash.h\"\n#include \"template_helper.h\"\n\n#define EXPLAIN_NAME \"single 8-byte\"\n#define KEY_VARIANT single_fixed_8\n#define OUTPUT_KEY_TYPE int64\n#define HASH_TABLE_KEY_TYPE int64\n#define DATUM_TO_OUTPUT_KEY DatumGetInt64\n#define OUTPUT_KEY_TO_DATUM Int64GetDatum\n\n#include \"hash_strategy_impl_single_fixed_key.c\"\n\n#define KEY_EQUAL(a, b) a == b\n#define KEY_HASH(X) HASH64(X)\n\n#include \"hash_strategy_impl.c\"\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/hashing/hash_strategy_single_text.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * Implementation of column hashing for a single text column.\n */\n\n#include <postgres.h>\n\n#include <common/hashfn.h>\n\n#include \"compression/arrow_c_data_interface.h\"\n#include \"nodes/columnar_scan/compressed_batch.h\"\n#include \"nodes/vector_agg/exec.h\"\n#include \"nodes/vector_agg/grouping_policy_hash.h\"\n#include \"template_helper.h\"\n\n#include \"batch_hashing_params.h\"\n\n#include \"umash_fingerprint_key.h\"\n\n#define EXPLAIN_NAME \"single text\"\n#define KEY_VARIANT single_text\n#define OUTPUT_KEY_TYPE BytesView\n\nstatic void\nsingle_text_key_hashing_init(HashingStrategy *hashing)\n{\n\thashing->umash_params = umash_key_hashing_init();\n}\n\ntypedef struct BytesView\n{\n\tconst uint8 *data;\n\tuint32 len;\n} BytesView;\n\nstatic BytesView\nget_bytes_view(CompressedColumnValues *column_values, int arrow_row)\n{\n\tconst uint32 start = ((uint32 *) column_values->buffers[1])[arrow_row];\n\tconst int32 value_bytes = ((uint32 *) column_values->buffers[1])[arrow_row + 1] - start;\n\tAssert(value_bytes >= 0);\n\n\treturn (BytesView){ .len = value_bytes, .data = &((uint8 *) column_values->buffers[2])[start] };\n}\n\nstatic pg_attribute_always_inline void\nsingle_text_key_hashing_get_key(BatchHashingParams params, int row, void *restrict output_key_ptr,\n\t\t\t\t\t\t\t\tvoid *restrict hash_table_key_ptr, bool *restrict valid)\n{\n\tAssert(params.policy->num_grouping_columns == 1);\n\n\tBytesView *restrict output_key = (BytesView *) output_key_ptr;\n\tHASH_TABLE_KEY_TYPE *restrict hash_table_key = (HASH_TABLE_KEY_TYPE *) hash_table_key_ptr;\n\n\tif (unlikely(params.single_grouping_column.decompression_type == DT_Scalar))\n\t{\n\t\t*valid = !DatumGetBool(PointerGetDatum(params.single_grouping_column.buffers[0]));\n\t\tif (*valid)\n\t\t{\n\t\t\toutput_key->len = VARSIZE_ANY_EXHDR(params.single_grouping_column.buffers[1]);\n\t\t\toutput_key->data =\n\t\t\t\t(const uint8 *) VARDATA_ANY(params.single_grouping_column.buffers[1]);\n\t\t}\n\t\telse\n\t\t{\n\t\t\toutput_key->len = 0;\n\t\t\toutput_key->data = NULL;\n\t\t}\n\t}\n\telse if (params.single_grouping_column.decompression_type == DT_ArrowText)\n\t{\n\t\t/*\n\t\t * The Arrow format requires the offsets to be monotonically increasing\n\t\t * even for null rows, so this is safe.\n\t\t */\n\t\t*output_key = get_bytes_view(&params.single_grouping_column, row);\n\t\t*valid = arrow_row_is_valid(params.single_grouping_column.buffers[0], row);\n\t}\n\telse if (params.single_grouping_column.decompression_type == DT_ArrowTextDict)\n\t{\n\t\tconst int16 index = ((int16 *) params.single_grouping_column.buffers[3])[row];\n\t\t*output_key = get_bytes_view(&params.single_grouping_column, index);\n\t\t*valid = arrow_row_is_valid(params.single_grouping_column.buffers[0], row);\n\t}\n\telse\n\t{\n\t\tpg_unreachable();\n\t}\n\n\tDEBUG_PRINT(\"%p consider key row %d key index %d is %d bytes: \",\n\t\t\t\tparams.hashing,\n\t\t\t\trow,\n\t\t\t\tparams.hashing->last_used_key_index + 1,\n\t\t\t\toutput_key->len);\n\tfor (size_t i = 0; i < output_key->len; i++)\n\t{\n\t\tDEBUG_PRINT(\"%.2x.\", output_key->data[i]);\n\t}\n\tDEBUG_PRINT(\"\\n\");\n\n\tconst struct umash_fp fp = umash_fprint(params.policy->hashing.umash_params,\n\t\t\t\t\t\t\t\t\t\t\t/* seed = */ ~0ULL,\n\t\t\t\t\t\t\t\t\t\t\toutput_key->data,\n\t\t\t\t\t\t\t\t\t\t\toutput_key->len);\n\t*hash_table_key = umash_fingerprint_get_key(fp);\n}\n\nstatic pg_attribute_always_inline void\nsingle_text_key_hashing_store_new(HashingStrategy *restrict hashing, uint32 new_key_index,\n\t\t\t\t\t\t\t\t  BytesView output_key)\n{\n\tconst int total_bytes = output_key.len + VARHDRSZ;\n\ttext *restrict stored = (text *) MemoryContextAlloc(hashing->key_body_mctx, total_bytes);\n\tSET_VARSIZE(stored, total_bytes);\n\tmemcpy(VARDATA(stored), output_key.data, output_key.len);\n\thashing->output_keys[new_key_index] = PointerGetDatum(stored);\n}\n\n/*\n * We use the standard single-key key output functions.\n */\nstatic void\nsingle_text_emit_key(GroupingPolicyHash *policy, uint32 current_key,\n\t\t\t\t\t TupleTableSlot *aggregated_slot)\n{\n\treturn hash_strategy_output_key_single_emit(policy, current_key, aggregated_slot);\n}\n\nstatic void\nsingle_text_key_hashing_prepare_for_batch(GroupingPolicyHash *policy, TupleTableSlot *vector_slot)\n{\n}\n\n#include \"hash_strategy_impl.c\"\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/hashing/hashing_strategy.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\ntypedef struct GroupingPolicyHash GroupingPolicyHash;\n\ntypedef struct HashingStrategy HashingStrategy;\n\ntypedef struct DecompressBatchState DecompressBatchState;\n\ntypedef struct TupleTableSlot TupleTableSlot;\n\n/*\n * The hashing strategy manages the details of how the grouping keys are stored\n * in a hash table.\n */\ntypedef struct HashingStrategy\n{\n\tchar *explain_name;\n\tvoid (*init)(HashingStrategy *hashing, GroupingPolicyHash *policy);\n\tvoid (*reset)(HashingStrategy *hashing);\n\tuint64 (*get_size_bytes)(HashingStrategy *hashing);\n\tvoid (*prepare_for_batch)(GroupingPolicyHash *policy, TupleTableSlot *vector_slot);\n\tvoid (*fill_offsets)(GroupingPolicyHash *policy, TupleTableSlot *vector_slot, int start_row,\n\t\t\t\t\t\t int end_row);\n\tvoid (*emit_key)(GroupingPolicyHash *policy, uint32 current_key,\n\t\t\t\t\t TupleTableSlot *aggregated_slot);\n\n\t/*\n\t * The hash table we use for grouping. It matches each grouping key to its\n\t * unique integer index.\n\t */\n\tvoid *table;\n\n\t/*\n\t * For each unique grouping key, we store the values of the grouping columns.\n\t * This is stored separately from hash table keys, because they might not\n\t * have the full column values, and also storing them contiguously here\n\t * leads to better memory access patterns when emitting the results.\n\t * The details of the key storage are managed by the hashing strategy. The\n\t * by-reference keys can use a separate memory context for dense storage.\n\t */\n\tDatum *restrict output_keys;\n\tuint64 num_allocated_output_keys;\n\tMemoryContext key_body_mctx;\n\n\t/*\n\t * The last used index of an unique grouping key. Key index 0 is invalid.\n\t */\n\tuint32 last_used_key_index;\n\n\t/*\n\t * In single-column grouping, we store the null key outside of the hash\n\t * table, and its index is given by this value. Key index 0 is invalid.\n\t * This is done to avoid having an \"is null\" flag in the hash table entries,\n\t * to reduce the hash table size.\n\t */\n\tuint32 null_key_index;\n\n#ifdef TS_USE_UMASH\n\t/*\n\t * UMASH fingerprinting parameters.\n\t */\n\tstruct umash_params *umash_params;\n#endif\n\n\t/*\n\t * Temporary key storages. Some hashing strategies need to put the key in a\n\t * separate memory area, we don't want to alloc/free it on each row.\n\t */\n\tuint8 *tmp_key_storage;\n\tuint64 num_tmp_key_storage_bytes;\n} HashingStrategy;\n\nvoid hash_strategy_output_key_alloc(GroupingPolicyHash *policy, uint16 nrows);\nvoid hash_strategy_output_key_single_emit(GroupingPolicyHash *policy, uint32 current_key,\n\t\t\t\t\t\t\t\t\t\t  TupleTableSlot *aggregated_slot);\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/hashing/template_helper.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#define FUNCTION_NAME_HELPER2(X, Y) X##_##Y\n#define FUNCTION_NAME_HELPER(X, Y) FUNCTION_NAME_HELPER2(X, Y)\n#define FUNCTION_NAME(Y) FUNCTION_NAME_HELPER(KEY_VARIANT, Y)\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/hashing/umash_fingerprint_key.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n/*\n * Helpers to use the umash fingerprint as a hash table key in our hashing\n * strategies for vectorized grouping.\n */\n\n#include \"import/umash.h\"\n\n/*\n * The struct is packed so that the hash table entry fits into 16\n * bytes with the uint32 key index that goes before.\n */\nstruct umash_fingerprint_key\n{\n\tuint32 hash;\n\tuint64 rest;\n} pg_attribute_packed();\n\n#define HASH_TABLE_KEY_TYPE struct umash_fingerprint_key\n#define KEY_HASH(X) (X.hash)\n#define KEY_EQUAL(a, b) (a.hash == b.hash && a.rest == b.rest)\n\nstatic inline struct umash_fingerprint_key\numash_fingerprint_get_key(struct umash_fp fp)\n{\n\tconst struct umash_fingerprint_key key = {\n\t\t.hash = fp.hash[0] & (~(uint32) 0),\n\t\t.rest = fp.hash[1],\n\t};\n\treturn key;\n}\n\nstatic inline struct umash_params *\numash_key_hashing_init()\n{\n\tstruct umash_params *params = palloc0(sizeof(struct umash_params));\n\tumash_params_derive(params, 0xabcdef1234567890ull, NULL);\n\treturn params;\n}\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/plan.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/attnum.h>\n#include <commands/explain.h>\n#include <executor/executor.h>\n#include <funcapi.h>\n#include <nodes/extensible.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodeFuncs.h>\n#include <nodes/plannodes.h>\n#include <optimizer/planner.h>\n#include <parser/parsetree.h>\n#include <utils/fmgroids.h>\n\n#include \"plan.h\"\n\n#include \"exec.h\"\n#include \"expression_utils.h\"\n#include \"import/list.h\"\n#include \"nodes/chunk_append/chunk_append.h\"\n#include \"nodes/columnar_scan/columnar_scan.h\"\n#include \"nodes/columnar_scan/vector_quals.h\"\n#include \"nodes/modify_hypertable.h\"\n#include \"nodes/vector_agg.h\"\n#include \"utils.h\"\n\nstatic struct CustomScanMethods scan_methods = { .CustomName = VECTOR_AGG_NODE_NAME,\n\t\t\t\t\t\t\t\t\t\t\t\t .CreateCustomScanState = vector_agg_state_create };\n\nvoid\n_vector_agg_init(void)\n{\n\tTryRegisterCustomScanMethods(&scan_methods);\n}\n\nbool\nts_is_vector_agg_plan(Plan *plan)\n{\n\treturn IsA(plan, CustomScan) && castNode(CustomScan, plan)->methods == &scan_methods;\n}\n\n/*\n * Create a vectorized aggregation node to replace the given partial aggregation\n * node.\n */\nstatic Plan *\nvector_agg_plan_create(Plan *childplan, Agg *agg, List *resolved_targetlist,\n\t\t\t\t\t   VectorAggGroupingType grouping_type)\n{\n\tCustomScan *vector_agg = (CustomScan *) makeNode(CustomScan);\n\tvector_agg->custom_plans = list_make1(childplan);\n\tvector_agg->methods = &scan_methods;\n\n\tvector_agg->custom_scan_tlist = resolved_targetlist;\n\n\t/*\n\t * Note that this is being called from the post-planning hook, and therefore\n\t * after set_plan_refs(). The meaning of output targetlists is different from\n\t * the previous planning stages, and they contain special varnos referencing\n\t * the scan targetlists.\n\t */\n\tvector_agg->scan.plan.targetlist =\n\t\tts_build_trivial_custom_output_targetlist(vector_agg->custom_scan_tlist);\n\n\t/*\n\t * Copy the costs from the normal aggregation node, so that they show up in\n\t * the EXPLAIN output. They are not used for any other purposes, because\n\t * this hook is called after the planning is finished.\n\t */\n\tvector_agg->scan.plan.plan_rows = agg->plan.plan_rows;\n\tvector_agg->scan.plan.plan_width = agg->plan.plan_width;\n\tvector_agg->scan.plan.startup_cost = agg->plan.startup_cost;\n\tvector_agg->scan.plan.total_cost = agg->plan.total_cost;\n\n\tvector_agg->scan.plan.parallel_aware = false;\n\tvector_agg->scan.plan.parallel_safe = childplan->parallel_safe;\n\tvector_agg->scan.plan.async_capable = false;\n\n\tvector_agg->scan.plan.plan_node_id = agg->plan.plan_node_id;\n\n\tAssert(agg->plan.qual == NIL);\n\n\tvector_agg->scan.plan.initPlan = agg->plan.initPlan;\n\n\tvector_agg->scan.plan.extParam = bms_copy(agg->plan.extParam);\n\tvector_agg->scan.plan.allParam = bms_copy(agg->plan.allParam);\n\n\tvector_agg->custom_private = ts_new_list(T_List, VASI_Count);\n\tlfirst(list_nth_cell(vector_agg->custom_private, VASI_GroupingType)) =\n\t\tmakeInteger(grouping_type);\n\n\treturn (Plan *) vector_agg;\n}\n\n/*\n * Whether we have an in-memory columnar representation for a given type.\n */\nstatic bool\nis_vector_type(Oid typeoid)\n{\n\tswitch (typeoid)\n\t{\n\t\tcase BOOLOID:\n\t\tcase FLOAT4OID:\n\t\tcase FLOAT8OID:\n\t\tcase INT2OID:\n\t\tcase INT4OID:\n\t\tcase INT8OID:\n\t\tcase TEXTOID:\n\t\tcase TIMESTAMPOID:\n\t\tcase TIMESTAMPTZOID:\n\t\tcase DATEOID:\n\t\tcase UUIDOID:\n\t\tcase INTERVALOID:\n\t\t\treturn true;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\nstatic bool is_vector_expr(const VectorQualInfo *vqinfo, Expr *expr);\n\n/*\n * Whether we can evaluate this function as part of the columnar pipeline.\n */\nstatic bool\nis_vector_function(const VectorQualInfo *vqinfo, List *args, Oid funcoid, Oid resulttype,\n\t\t\t\t   Oid inputcollid)\n{\n\tif (!is_vector_type(resulttype))\n\t{\n\t\treturn false;\n\t}\n\n\tListCell *lc;\n\tforeach (lc, args)\n\t{\n\t\tif (!is_vector_expr(vqinfo, (Expr *) lfirst(lc)))\n\t\t{\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tif (!func_strict(funcoid))\n\t{\n\t\treturn false;\n\t}\n\n\tif (func_volatile(funcoid) == PROVOLATILE_VOLATILE)\n\t{\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n * Whether the expression can be used for vectorized processing: must be a Var\n * that refers to either a bulk-decompressed or a segmentby column.\n */\nstatic bool\nis_vector_expr(const VectorQualInfo *vqinfo, Expr *expr)\n{\n\t/*\n\t * Skip NULLs for uniform handling of the optional nodes.\n\t */\n\tif (expr == NULL)\n\t{\n\t\treturn true;\n\t}\n\n\tswitch (((Node *) expr)->type)\n\t{\n\t\tcase T_Const:\n\t\t{\n\t\t\tConst *c = (Const *) expr;\n\t\t\treturn is_vector_type(c->consttype);\n\t\t}\n\n\t\tcase T_FuncExpr:\n\t\t{\n\t\t\t/* Can vectorize some functions! */\n\t\t\tFuncExpr *f = castNode(FuncExpr, expr);\n\t\t\treturn is_vector_function(vqinfo,\n\t\t\t\t\t\t\t\t\t  f->args,\n\t\t\t\t\t\t\t\t\t  f->funcid,\n\t\t\t\t\t\t\t\t\t  f->funcresulttype,\n\t\t\t\t\t\t\t\t\t  f->inputcollid);\n\t\t}\n\n\t\tcase T_OpExpr:\n\t\t{\n\t\t\tOpExpr *o = castNode(OpExpr, expr);\n\t\t\treturn is_vector_function(vqinfo,\n\t\t\t\t\t\t\t\t\t  o->args,\n\t\t\t\t\t\t\t\t\t  o->opfuncid,\n\t\t\t\t\t\t\t\t\t  o->opresulttype,\n\t\t\t\t\t\t\t\t\t  o->inputcollid);\n\t\t}\n\n\t\tcase T_Var:\n\t\t{\n\t\t\tVar *var = castNode(Var, expr);\n\n\t\t\tif (var->varattno <= 0)\n\t\t\t{\n\t\t\t\t/* Can't work with special attributes like tableoid. */\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tAssert(var->varattno <= vqinfo->maxattno);\n\n\t\t\tconst bool is_vector = vqinfo->vector_attrs && vqinfo->vector_attrs[var->varattno];\n\n\t\t\t/*\n\t\t\t * The segmentby columns are considered vectorizable, but their type might not actually\n\t\t\t * have a columnar representation. Theoretically this can work because they are always\n\t\t\t * represented as DT_Scalar, but in practice this is poorly tested and of limited\n\t\t\t * utility, so we consider such columns not to be vectorizable at the moment.\n\t\t\t */\n\t\t\treturn is_vector && is_vector_type(var->vartype);\n\t\t}\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\n/*\n * Whether we can vectorize this particular aggregate.\n */\nstatic bool\ncan_vectorize_aggref(const VectorQualInfo *vqi, Aggref *aggref)\n{\n\tif (aggref->aggdirectargs != NIL)\n\t{\n\t\t/* Can't process ordered-set aggregates with direct arguments. */\n\t\treturn false;\n\t}\n\n\tif (aggref->aggorder != NIL)\n\t{\n\t\t/* Can't process aggregates with an ORDER BY clause. */\n\t\treturn false;\n\t}\n\n\tif (aggref->aggdistinct != NIL)\n\t{\n\t\t/* Can't process aggregates with DISTINCT clause. */\n\t\treturn false;\n\t}\n\n\tif (aggref->aggfilter != NULL)\n\t{\n\t\t/* Can process aggregates with filter clause if it's vectorizable. */\n\t\tNode *aggfilter_vectorized = vector_qual_make((Node *) aggref->aggfilter, vqi);\n\t\tif (aggfilter_vectorized == NULL)\n\t\t{\n\t\t\treturn false;\n\t\t}\n\t\taggref->aggfilter = (Expr *) aggfilter_vectorized;\n\t}\n\n\tif (get_vector_aggregate(aggref->aggfnoid, aggref->inputcollid) == NULL)\n\t{\n\t\t/*\n\t\t * We don't have a vectorized implementation for this particular\n\t\t * aggregate function.\n\t\t */\n\t\treturn false;\n\t}\n\n\tif (aggref->args == NIL)\n\t{\n\t\t/* This must be count(*), we can vectorize it. */\n\t\treturn true;\n\t}\n\n\t/* The function must have one argument, check it. */\n\tAssert(list_length(aggref->args) == 1);\n\tTargetEntry *argument = castNode(TargetEntry, linitial(aggref->args));\n\n\treturn is_vector_expr(vqi, argument->expr);\n}\n\n/*\n * What vectorized grouping strategy we can use for the given grouping columns.\n */\nstatic VectorAggGroupingType\nget_vectorized_grouping_type(const VectorQualInfo *vqinfo, Agg *agg, List *resolved_targetlist)\n{\n\t/*\n\t * The Agg->numCols value can be less than the number of the non-aggregated\n\t * vars in the aggregated targetlist, if some of them are equated to a\n\t * constant. This behavior started with PG 16. This case is not very\n\t * important, so we treat all non-aggregated columns as grouping columns to\n\t * keep the vectorized aggregation node simple.\n\t */\n\tint num_grouping_columns = 0;\n\tbool all_segmentby = true;\n\n\tOid single_grouping_var_type = InvalidOid;\n\tint16 typlen = 0;\n\tbool typbyval = false;\n\n\tListCell *lc;\n\tforeach (lc, resolved_targetlist)\n\t{\n\t\tTargetEntry *target_entry = lfirst_node(TargetEntry, lc);\n\t\tif (IsA(target_entry->expr, Aggref))\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\tnum_grouping_columns++;\n\n\t\tif (!is_vector_expr(vqinfo, target_entry->expr))\n\t\t{\n\t\t\treturn VAGT_Invalid;\n\t\t}\n\n\t\t/*\n\t\t * Detect whether we're only grouping by segmentby columns, in which\n\t\t * case we can use the whole-batch grouping strategy. Probably this\n\t\t * could be extended to allow arbitrary expressions referencing only the\n\t\t * segmentby columns.\n\t\t */\n\t\tif (IsA(target_entry->expr, Var))\n\t\t{\n\t\t\tVar *var = castNode(Var, target_entry->expr);\n\t\t\tall_segmentby &= vqinfo->segmentby_attrs[var->varattno];\n\t\t}\n\t\telse\n\t\t{\n\t\t\tall_segmentby = false;\n\t\t}\n\n\t\t/*\n\t\t * If we have a single grouping column, record it for the additional\n\t\t * checks later.\n\t\t */\n\t\tif (num_grouping_columns != 1)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\tTupleDesc tdesc = NULL;\n\t\tTypeFuncClass type_class =\n\t\t\tget_expr_result_type((Node *) target_entry->expr, &single_grouping_var_type, &tdesc);\n\t\tif (type_class != TYPEFUNC_SCALAR)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\tget_typlenbyval(single_grouping_var_type, &typlen, &typbyval);\n\t\tEnsure(typlen != 0, \"invalid zero typlen for type %d\", single_grouping_var_type);\n\t}\n\n\tAssert(num_grouping_columns >= agg->numCols);\n\n\t/*\n\t * We support vectorized aggregation without grouping.\n\t */\n\tif (num_grouping_columns == 0)\n\t{\n\t\treturn VAGT_Batch;\n\t}\n\n\t/*\n\t * We support grouping by any number of columns if all of them are segmentby.\n\t */\n\tif (all_segmentby)\n\t{\n\t\treturn VAGT_Batch;\n\t}\n\n\t/*\n\t * We support hashed vectorized grouping by one fixed-size by-value\n\t * compressed column.\n\t * We can use our hash table for GroupAggregate as well, because it preserves\n\t * the input order of the keys, but only for the direct order, not reverse.\n\t */\n\tif (num_grouping_columns == 1 && typlen != 0)\n\t{\n\t\tif (typbyval)\n\t\t{\n\t\t\tswitch (typlen)\n\t\t\t{\n\t\t\t\tcase 1:\n#ifdef TS_USE_UMASH\n\t\t\t\t\tAssert(single_grouping_var_type == BOOLOID);\n\t\t\t\t\treturn VAGT_HashSerialized;\n#else\n\t\t\t\t\treturn VAGT_Invalid;\n#endif\n\t\t\t\tcase 2:\n\t\t\t\t\treturn VAGT_HashSingleFixed2;\n\t\t\t\tcase 4:\n\t\t\t\t\treturn VAGT_HashSingleFixed4;\n\t\t\t\tcase 8:\n\t\t\t\t\treturn VAGT_HashSingleFixed8;\n\t\t\t\tdefault:\n\t\t\t\t\tEnsure(false, \"invalid fixed size %d of a vector type\", typlen);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n#ifdef TS_USE_UMASH\n\t\t/*\n\t\t * We also have the UUID type which is by-reference and has a\n\t\t * columnar in-memory representation, but no specialized single-column\n\t\t * vectorized grouping support. It can use the serialized grouping\n\t\t * strategy.\n\t\t */\n\t\telse if (single_grouping_var_type == TEXTOID)\n\t\t{\n\t\t\treturn VAGT_HashSingleText;\n\t\t}\n#endif\n\t}\n\n#ifdef TS_USE_UMASH\n\t/*\n\t * Use hashing of serialized keys when we have many grouping columns.\n\t */\n\treturn VAGT_HashSerialized;\n#else\n\treturn VAGT_Invalid;\n#endif\n}\n\ntypedef struct HasVectorAggContext\n{\n\tbool has_agg;\n\tbool has_vector_agg;\n} HasVectorAggContext;\n\nstatic Plan *\nhas_vector_agg(Plan *plan, void *context)\n{\n\tHasVectorAggContext *ctx = (HasVectorAggContext *) context;\n\tif (IsA(plan, Agg))\n\t{\n\t\tctx->has_agg = true;\n\t}\n\telse if (ts_is_vector_agg_plan(plan))\n\t{\n\t\tctx->has_vector_agg = true;\n\t}\n\treturn plan;\n}\n\n/*\n * Whether we have a vectorized aggregation node and any aggregate node at all\n * in the plan tree. This is used for testing.\n */\nbool\nhas_vector_agg_node(Plan *plan, bool *has_some_agg)\n{\n\tHasVectorAggContext context = { .has_agg = false, .has_vector_agg = false };\n\tts_plan_tree_walker(plan, has_vector_agg, &context);\n\t*has_some_agg = context.has_agg;\n\treturn context.has_vector_agg;\n}\n\n/*\n * Check if a VectorAgg is possible on top of the given child plan.\n *\n * If the child plan is compatible, also initialize the VectorQualInfo struct\n * for aggregation FILTER clauses.\n *\n * Returns true if the scan node is a supported child, otherwise false.\n */\nstatic bool\nvectoragg_plan_possible(Plan *childplan, VectorQualInfo *vqi)\n{\n\tif (!IsA(childplan, CustomScan))\n\t\treturn false;\n\n\tif (childplan->qual != NIL)\n\t{\n\t\t/* Can't do vectorized aggregation if we have Postgres quals. */\n\t\treturn false;\n\t}\n\n\tif (ts_is_columnar_scan_plan(childplan))\n\t{\n\t\tvectoragg_plan_columnar_scan(childplan, vqi);\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nstatic Node *\nmark_partial_aggref_mutator(Node *node, void *context)\n{\n\tif (node == NULL)\n\t\treturn NULL;\n\n\tif (IsA(node, Aggref))\n\t{\n\t\tmark_partial_aggref(castNode(Aggref, node), AGGSPLIT_INITIAL_SERIAL);\n\t\treturn node;\n\t}\n\n\treturn expression_tree_mutator(node, mark_partial_aggref_mutator, context);\n}\n\ntypedef struct MakeFinalizeAggContext\n{\n\tAgg *agg;\n\tList *vector_agg_targetlist;\n} MakeFinalizeAggContext;\n\nstatic Node *\nmake_finalize_agg_mutator(Node *node, void *context)\n{\n\tif (node == NULL)\n\t\treturn NULL;\n\n\tif (IsA(node, TargetEntry))\n\t{\n\t\tTargetEntry *tle = castNode(TargetEntry, node);\n\t\tMakeFinalizeAggContext *ctx = (MakeFinalizeAggContext *) context;\n\n\t\tif (IsA(tle->expr, Var))\n\t\t{\n\t\t\tVar *var = castNode(Var, tle->expr);\n\t\t\tAssert(var->varno == OUTER_VAR);\n\t\t\tAttrNumber old_attno = var->varattno;\n\t\t\tvar->varattno = tle->resno;\n\t\t\tfor (int k = 0; k < ctx->agg->numCols; k++)\n\t\t\t{\n\t\t\t\tif (ctx->agg->grpColIdx[k] == old_attno)\n\t\t\t\t\tctx->agg->grpColIdx[k] = tle->resno;\n\t\t\t}\n\t\t\treturn node;\n\t\t}\n\n\t\tif (IsA(tle->expr, Aggref))\n\t\t{\n\t\t\tAggref *aggref = castNode(Aggref, tle->expr);\n\n\t\t\t/*\n\t\t\t * Look up the VectorAgg output type for this column, which is\n\t\t\t * the transition type set by mark_partial_aggref above.\n\t\t\t */\n\t\t\tTargetEntry *vag_tle = list_nth(ctx->vector_agg_targetlist, tle->resno - 1);\n\t\t\tOid var_type = exprType((Node *) vag_tle->expr);\n\n\t\t\tmark_partial_aggref(aggref, AGGSPLIT_FINAL_DESERIAL);\n\n\t\t\tVar *var = makeVar(OUTER_VAR, tle->resno, var_type, -1, aggref->aggcollid, 0);\n\t\t\taggref->args = list_make1(makeTargetEntry((Expr *) var, 1, NULL, false));\n\t\t\treturn node;\n\t\t}\n\t}\n\n\treturn expression_tree_mutator(node, make_finalize_agg_mutator, context);\n}\n\nstatic Plan *insert_vector_agg(Plan *plan, void *context);\n\nPlan *\ntry_insert_vector_agg_node(Plan *plan)\n{\n\treturn ts_plan_tree_walker(plan, insert_vector_agg, NULL);\n}\n\nstatic Plan *\ninsert_vector_agg(Plan *plan, void *context)\n{\n\tif (!IsA(plan, Agg))\n\t{\n\t\treturn plan;\n\t}\n\n\tAgg *agg = castNode(Agg, plan);\n\n\tif (agg->aggsplit != AGGSPLIT_INITIAL_SERIAL && agg->aggsplit != AGGSPLIT_SIMPLE)\n\t{\n\t\t/* Can only vectorize partial or non-partial aggregation node. */\n\t\treturn plan;\n\t}\n\n\tif (agg->groupingSets != NIL)\n\t{\n\t\t/* No GROUPING SETS support. */\n\t\treturn plan;\n\t}\n\n\tif (agg->plan.qual != NIL)\n\t{\n\t\t/*\n\t\t * No HAVING support. Probably we can't have it in this node in any case,\n\t\t * because we only replace the partial aggregation nodes which can't\n\t\t * check the HAVING clause.\n\t\t */\n\t\treturn plan;\n\t}\n\tif (agg->plan.lefttree == NULL)\n\t{\n\t\t/*\n\t\t * Not sure what this would mean, but check for it just to be on the\n\t\t * safe side because we can effectively see any possible plan here.\n\t\t */\n\t\treturn plan;\n\t}\n\n\tPlan *childplan = agg->plan.lefttree;\n\tVectorQualInfo vqi;\n\tMemSet(&vqi, 0, sizeof(VectorQualInfo));\n\n\t/*\n\t * Build supplementary info to determine whether we can vectorize the\n\t * aggregate FILTER clauses.\n\t */\n\tif (!vectoragg_plan_possible(childplan, &vqi))\n\t{\n\t\t/* Not a compatible vectoragg child node */\n\t\treturn plan;\n\t}\n\n\t/*\n\t * To make it easier to examine the variables participating in the aggregation,\n\t * the subsequent checks are performed on the aggregated targetlist with\n\t * all variables resolved to uncompressed chunk variables.\n\t */\n\tList *resolved_targetlist =\n\t\tcastNode(List, ts_resolve_outer_special_vars((Node *) agg->plan.targetlist, childplan));\n\n\tconst VectorAggGroupingType grouping_type =\n\t\tget_vectorized_grouping_type(&vqi, agg, resolved_targetlist);\n\tif (grouping_type == VAGT_Invalid)\n\t{\n\t\t/* The grouping is not vectorizable. */\n\t\treturn plan;\n\t}\n\n\t/*\n\t * The hash grouping strategies do not preserve the input key order when the\n\t * reverse ordering is requested, so in this case they cannot work in\n\t * GroupAggregate mode.\n\t */\n\tif (grouping_type != VAGT_Batch && agg->aggstrategy != AGG_HASHED)\n\t{\n\t\tif (vqi.reverse)\n\t\t{\n\t\t\treturn plan;\n\t\t}\n\t}\n\n\t/* Now check the output targetlist. */\n\tListCell *lc;\n\tforeach (lc, resolved_targetlist)\n\t{\n\t\tTargetEntry *target_entry = lfirst_node(TargetEntry, lc);\n\t\tif (IsA(target_entry->expr, Aggref))\n\t\t{\n\t\t\tAggref *aggref = castNode(Aggref, target_entry->expr);\n\t\t\tif (!can_vectorize_aggref(&vqi, aggref))\n\t\t\t{\n\t\t\t\t/* Aggregate function not vectorizable. */\n\t\t\t\treturn plan;\n\t\t\t}\n\t\t}\n\t}\n\n\t/*\n\t * Finally, all requirements are satisfied and we can vectorize this\n\t * aggregation node.\n\t */\n\tPlan *vector_agg_plan =\n\t\tvector_agg_plan_create(childplan, agg, resolved_targetlist, grouping_type);\n\n\tif (agg->aggsplit == AGGSPLIT_SIMPLE)\n\t{\n\t\t/*\n\t\t * Convert a non-partial aggregation into a two-phase partial + finalize\n\t\t * aggregation with VectorAgg performing the partial step.\n\t\t */\n\t\tCustomScan *vector_agg = castNode(CustomScan, vector_agg_plan);\n\t\tvector_agg->custom_scan_tlist =\n\t\t\t(List *) expression_tree_mutator((Node *) vector_agg->custom_scan_tlist,\n\t\t\t\t\t\t\t\t\t\t\t mark_partial_aggref_mutator,\n\t\t\t\t\t\t\t\t\t\t\t NULL);\n\n\t\t/*\n\t\t * Rebuild the plan output targetlist to reflect the updated types.\n\t\t * VectorAgg returns ps_ResultTupleSlot whose TupleDesc is derived from\n\t\t * plan.targetlist, so it must match the actual partial aggregate output\n\t\t * types for correct tuple materialization on all platforms.\n\t\t */\n\t\tvector_agg->scan.plan.targetlist =\n\t\t\tts_build_trivial_custom_output_targetlist(vector_agg->custom_scan_tlist);\n\n\t\t/*\n\t\t * Set up the parent Agg to finalize the partial results from VectorAgg.\n\t\t */\n\t\tagg->aggsplit = AGGSPLIT_FINAL_DESERIAL;\n\t\tagg->plan.lefttree = vector_agg_plan;\n\n\t\tMakeFinalizeAggContext finalize_ctx = {\n\t\t\t.agg = agg,\n\t\t\t.vector_agg_targetlist = vector_agg->scan.plan.targetlist,\n\t\t};\n\t\tagg->plan.targetlist = (List *) expression_tree_mutator((Node *) agg->plan.targetlist,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tmake_finalize_agg_mutator,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t&finalize_ctx);\n\t\tagg->plan.qual = (List *) expression_tree_mutator((Node *) agg->plan.qual,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  make_finalize_agg_mutator,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  &finalize_ctx);\n\n\t\treturn (Plan *) agg;\n\t}\n\n\treturn vector_agg_plan;\n}\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/plan.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n#include <nodes/plannodes.h>\n#include <utils/relcache.h>\n\n#include \"nodes/columnar_scan/vector_quals.h\"\n\n/*\n * The indexes of settings that we have to pass through the custom_private list.\n */\ntypedef enum\n{\n\tVASI_GroupingType = 0,\n\tVASI_Count\n} VectorAggSettingsIndex;\n\nextern void _vector_agg_init(void);\nextern void vectoragg_plan_columnar_scan(Plan *childplan, VectorQualInfo *vqi);\nPlan *try_insert_vector_agg_node(Plan *plan);\nbool has_vector_agg_node(Plan *plan, bool *has_some_agg);\nbool ts_is_vector_agg_plan(Plan *plan);\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/plan_columnar_scan.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n#include <nodes/pathnodes.h>\n#include <nodes/plannodes.h>\n\n#include \"nodes/columnar_scan/planner.h\"\n#include \"plan.h\"\n\n/*\n * Whether the given compressed column index corresponds to a vector variable.\n */\nstatic bool\nis_vector_compressed_column(const CustomScan *custom, int compressed_column_index,\n\t\t\t\t\t\t\tbool *out_is_segmentby)\n{\n\tList *bulk_decompression_column = list_nth(custom->custom_private, DCP_BulkDecompressionColumn);\n\tconst bool bulk_decompression_enabled_for_column =\n\t\tlist_nth_int(bulk_decompression_column, compressed_column_index);\n\n\t/*\n\t * Bulk decompression can be disabled for all columns in the ColumnarScan\n\t * node settings, we can't do vectorized aggregation for compressed columns\n\t * in that case. For segmentby columns it's still possible.\n\t */\n\tList *settings = linitial(custom->custom_private);\n\tconst bool bulk_decompression_enabled_globally =\n\t\tlist_nth_int(settings, DCS_EnableBulkDecompression);\n\n\t/*\n\t * Check if this column is a segmentby.\n\t */\n\tList *is_segmentby_column = list_nth(custom->custom_private, DCP_IsSegmentbyColumn);\n\tconst bool is_segmentby = list_nth_int(is_segmentby_column, compressed_column_index);\n\tif (out_is_segmentby)\n\t{\n\t\t*out_is_segmentby = is_segmentby;\n\t}\n\n\t/*\n\t * We support vectorized aggregation either for segmentby columns or for\n\t * columns with bulk decompression enabled.\n\t */\n\tif (!is_segmentby &&\n\t\t!(bulk_decompression_enabled_for_column && bulk_decompression_enabled_globally))\n\t{\n\t\t/* Vectorized aggregation not possible for this particular column. */\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n * Map the custom scan attribute number to the uncompressed chunk attribute\n * number.\n */\nstatic int\ncustom_scan_to_uncompressed_chunk_attno(List *custom_scan_tlist, int custom_scan_attno)\n{\n\tif (custom_scan_tlist == NIL)\n\t{\n\t\treturn custom_scan_attno;\n\t}\n\n\tVar *var =\n\t\tcastNode(Var,\n\t\t\t\t castNode(TargetEntry,\n\t\t\t\t\t\t  list_nth(custom_scan_tlist, AttrNumberGetAttrOffset(custom_scan_attno)))\n\t\t\t\t\t ->expr);\n\treturn var->varattno;\n}\n\nvoid\nvectoragg_plan_columnar_scan(Plan *childplan, VectorQualInfo *vqi)\n{\n\tconst CustomScan *custom = castNode(CustomScan, childplan);\n\n\tvqi->rti = custom->scan.scanrelid;\n\n\t/*\n\t * Now, we have to translate the decompressed varno into the compressed\n\t * column index, to check if the column supports bulk decompression.\n\t */\n\tList *decompression_map = list_nth(custom->custom_private, DCP_DecompressionMap);\n\n\t/*\n\t * There's no easy way to determine maximum attribute number for uncompressed\n\t * chunk at this stage, so we'll have to go through all the compressed columns\n\t * for this.\n\t */\n\tint maxattno = 0;\n\tfor (int compressed_column_index = 0; compressed_column_index < list_length(decompression_map);\n\t\t compressed_column_index++)\n\t{\n\t\tconst int custom_scan_attno = list_nth_int(decompression_map, compressed_column_index);\n\t\tif (custom_scan_attno <= 0)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst int uncompressed_chunk_attno =\n\t\t\tcustom_scan_to_uncompressed_chunk_attno(custom->custom_scan_tlist, custom_scan_attno);\n\n\t\tif (uncompressed_chunk_attno > maxattno)\n\t\t{\n\t\t\tmaxattno = uncompressed_chunk_attno;\n\t\t}\n\t}\n\n\tvqi->maxattno = maxattno;\n\tvqi->vector_attrs = (bool *) palloc0(sizeof(bool) * (maxattno + 1));\n\tvqi->segmentby_attrs = (bool *) palloc0(sizeof(bool) * (maxattno + 1));\n\n\tfor (int compressed_column_index = 0; compressed_column_index < list_length(decompression_map);\n\t\t compressed_column_index++)\n\t{\n\t\tconst int custom_scan_attno = list_nth_int(decompression_map, compressed_column_index);\n\t\tif (custom_scan_attno <= 0)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst int uncompressed_chunk_attno =\n\t\t\tcustom_scan_to_uncompressed_chunk_attno(custom->custom_scan_tlist, custom_scan_attno);\n\n\t\tvqi->vector_attrs[uncompressed_chunk_attno] =\n\t\t\tis_vector_compressed_column(custom,\n\t\t\t\t\t\t\t\t\t\tcompressed_column_index,\n\t\t\t\t\t\t\t\t\t\t&vqi->segmentby_attrs[uncompressed_chunk_attno]);\n\t}\n\n\tList *settings = linitial(custom->custom_private);\n\tvqi->reverse = list_nth_int(settings, DCS_Reverse);\n}\n"
  },
  {
    "path": "tsl/src/nodes/vector_agg/vector_slot.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <access/attnum.h>\n#include <access/tupdesc.h>\n\n#include <nodes/columnar_scan/compressed_batch.h>\n\n/*\n * Vector slot functions.\n *\n * These functions provide a common interface for arrow slots and compressed\n * batches.\n *\n */\n\n/*\n * Get the result vectorized filter bitmap.\n */\nstatic inline const uint64 *\nvector_slot_get_qual_result(const TupleTableSlot *slot, uint16 *num_rows)\n{\n\tconst DecompressBatchState *batch_state = (const DecompressBatchState *) slot;\n\t*num_rows = batch_state->total_batch_rows;\n\treturn batch_state->vector_qual_result;\n}\n\n/*\n * Return the arrow array or the datum (in case of single scalar value) for a\n * given attribute as a CompressedColumnValues struct.\n */\nCompressedColumnValues vector_slot_evaluate_expression(DecompressContext *dcontext,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   TupleTableSlot *slot, uint64 const *filter,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   const Expr *argument);\n"
  },
  {
    "path": "tsl/src/planner.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n\n#include <catalog/pg_trigger.h>\n#include <commands/extension.h>\n#include <foreign/fdwapi.h>\n#include <nodes/nodeFuncs.h>\n#include <nodes/parsenodes.h>\n#include <optimizer/pathnode.h>\n#include <optimizer/paths.h>\n#include <parser/parsetree.h>\n\n#include \"compat/compat.h\"\n#include \"chunk.h\"\n#include \"chunkwise_agg.h\"\n#include \"continuous_aggs/planner.h\"\n#include \"guc.h\"\n#include \"hypertable.h\"\n#include \"nodes/columnar_index_scan/columnar_index_scan.h\"\n#include \"nodes/columnar_scan/columnar_scan.h\"\n#include \"nodes/gapfill/gapfill.h\"\n#include \"nodes/skip_scan/skip_scan.h\"\n#include \"nodes/vector_agg/plan.h\"\n#include \"planner.h\"\n\n#include <math.h>\n\n#define OSM_EXTENSION_NAME \"timescaledb_osm\"\n\nstatic bool\ninvolves_hypertable(PlannerInfo *root, RelOptInfo *parent)\n{\n\tfor (int relid = bms_next_member(parent->relids, -1); relid > 0;\n\t\t relid = bms_next_member(parent->relids, relid))\n\t{\n\t\tHypertable *ht;\n\t\tRelOptInfo *child = root->simple_rel_array[relid];\n\t\t/*\n\t\t * RelOptInfo can be null here for join RTEs on PG >= 16. This doesn't\n\t\t * matter because we'll have all the baserels in relids bitmap as well.\n\t\t */\n\t\tif (child != NULL && ts_classify_relation(root, child, &ht) == TS_REL_HYPERTABLE)\n\t\t{\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\nvoid\ntsl_create_upper_paths_hook(PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel,\n\t\t\t\t\t\t\tRelOptInfo *output_rel, TsRelType input_reltype, Hypertable *ht,\n\t\t\t\t\t\t\tvoid *extra)\n{\n\tswitch (stage)\n\t{\n\t\tcase UPPERREL_GROUP_AGG:\n\t\t\tif (input_reltype != TS_REL_HYPERTABLE_CHILD)\n\t\t\t{\n\t\t\t\tplan_add_gapfill(root, output_rel);\n\t\t\t}\n\n\t\t\tif (ts_guc_enable_chunkwise_aggregation && input_rel != NULL &&\n\t\t\t\t!IS_DUMMY_REL(input_rel) && output_rel != NULL &&\n\t\t\t\tinvolves_hypertable(root, input_rel))\n\t\t\t{\n\t\t\t\ttsl_pushdown_partial_agg(root, ht, input_rel, output_rel, extra);\n\t\t\t}\n\n\t\t\tif (root->numOrderedAggs && !IS_DUMMY_REL(input_rel) && output_rel != NULL)\n\t\t\t{\n\t\t\t\ttsl_skip_scan_paths_add(root, input_rel, output_rel, stage);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase UPPERREL_WINDOW:\n\t\t\tif (IsA(linitial(input_rel->pathlist), CustomPath))\n\t\t\t\tgapfill_adjust_window_targetlist(root, input_rel, output_rel);\n\t\t\tbreak;\n\t\tcase UPPERREL_DISTINCT:\n\t\t\ttsl_skip_scan_paths_add(root, input_rel, output_rel, stage);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n}\n\n/*\n * Check if a chunk should be decompressed via a ColumnarScan plan.\n *\n * Check first that it is a compressed chunk. Then, decompress unless it is\n * SELECT * FROM ONLY <chunk>. We check if it is the ONLY case by calling\n * ts_rte_is_marked_for_expansion. Respecting ONLY here is important to not\n * break postgres tools like pg_dump.\n */\nstatic inline bool\nuse_columnar_scan(const RelOptInfo *rel, const RangeTblEntry *rte, const Chunk *chunk)\n{\n\tif (!ts_guc_enable_columnarscan)\n\t\treturn false;\n\n\t/* Check that the chunk is actually compressed */\n\treturn chunk->fd.compressed_chunk_id != INVALID_CHUNK_ID &&\n\t\t   /* Check that it is _not_ SELECT FROM ONLY <chunk> */\n\t\t   (rel->reloptkind != RELOPT_BASEREL || ts_rte_is_marked_for_expansion(rte));\n}\n\nvoid\ntsl_set_rel_pathlist_query(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte,\n\t\t\t\t\t\t   Hypertable *ht)\n{\n\t/* Only interested in queries on relations that are part of hypertables\n\t * with compression enabled, so quick exit if not this case. */\n\tif (ht == NULL || !TS_HYPERTABLE_HAS_COMPRESSION_TABLE(ht))\n\t\treturn;\n\n\t/*\n\t * For a chunk, we can get here via a query on the hypertable that expands\n\t * to the chunk or by direct query on the chunk. In the former case,\n\t * reloptkind will be RELOPT_OTHER_MEMBER_REL (nember of hypertable) or in\n\t * the latter case reloptkind will be RELOPT_BASEREL (standalone rel).\n\t *\n\t * These two cases are checked in ts_planner_chunk_fetch().\n\t */\n\tconst Chunk *chunk = ts_planner_chunk_fetch(root, rel);\n\n\tif (chunk == NULL)\n\t\treturn;\n\n\tif (use_columnar_scan(rel, rte, chunk))\n\t{\n\t\tts_columnar_scan_generate_paths(root, rel, ht, chunk);\n\t}\n}\n\nvoid\ntsl_set_rel_pathlist_dml(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte,\n\t\t\t\t\t\t Hypertable *ht)\n{\n\t/*\n\t * We do not support MERGE command with UPDATE/DELETE merge actions on\n\t * compressed hypertables, because Custom Scan (ModifyHypertable) node is\n\t * not generated in the plan for MERGE command on compressed hypertables\n\t */\n\tif (ht != NULL && TS_HYPERTABLE_HAS_COMPRESSION_TABLE(ht))\n\t{\n\t\tif (root->parse->commandType == CMD_MERGE)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"The MERGE command with UPDATE/DELETE merge actions is not support on \"\n\t\t\t\t\t\t\t\"compressed hypertables\")));\n\n#if !PG17_GE\n\t\t/*\n\t\t * PG16 and earlier: Remove BitmapHeapScan paths for DML on partial chunks.\n\t\t *\n\t\t * On PG16, BitmapHeapScan eagerly initializes its heap scan descriptor\n\t\t * with the original snapshot during plan initialization. When we\n\t\t * decompress rows and call CommandCounterIncrement(), the stale\n\t\t * snapshot cannot see the newly decompressed rows.\n\t\t *\n\t\t * This bug only affects partial chunks because:\n\t\t * - Fully compressed chunks have rel->indexlist = NIL (set in\n\t\t *   timescaledb_get_relation_info_hook), so BitmapHeapScan is not\n\t\t *   available anyway.\n\t\t * - Partial chunks have indexes available, so BitmapHeapScan can be\n\t\t *   chosen, and decompression will add rows that it cannot see.\n\t\t *\n\t\t * PG17+ fixed this via commit 1577081e961 which lazily initializes\n\t\t * the scan descriptor in BitmapHeapNext(), using the current\n\t\t * estate->es_snapshot after CommandCounterIncrement().\n\t\t *\n\t\t * IMPORTANT: PostgreSQL's add_path() prunes dominated paths. If\n\t\t * BitmapHeapPath has lower cost than SeqScan (common with adjusted\n\t\t * cost parameters), SeqScan may have been pruned from the pathlist.\n\t\t * If removing BitmapHeapPath would leave no paths, we must add a\n\t\t * another path as fallback to ensure a valid plan exists.\n\t\t */\n\t\tconst Chunk *chunk = ts_planner_chunk_fetch(root, rel);\n\t\tif (chunk && ts_chunk_is_partial(chunk))\n\t\t{\n\t\t\tListCell *lc;\n\t\t\tList *filtered_paths = NIL;\n\n\t\t\tforeach (lc, rel->pathlist)\n\t\t\t{\n\t\t\t\tPath *path = lfirst(lc);\n\t\t\t\tif (!IsA(path, BitmapHeapPath))\n\t\t\t\t\tfiltered_paths = lappend(filtered_paths, path);\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * If removing BitmapHeapPath left us with no paths, try to add\n\t\t\t * alternative scan paths. This can happen when BitmapHeapPath\n\t\t\t * dominated and pruned other paths due to cost calculations.\n\t\t\t *\n\t\t\t * Prefer IndexScan if available, fall back to SeqScan.\n\t\t\t */\n\t\t\tif (filtered_paths == NIL && rel->pathlist != NIL)\n\t\t\t{\n\t\t\t\t/*\n\t\t\t\t * Try to create index paths. create_index_paths() adds paths\n\t\t\t\t * to rel->pathlist, but it also creates BitmapHeapPath entries\n\t\t\t\t * which we must filter out again.\n\t\t\t\t */\n\t\t\t\trel->pathlist = NIL; /* Clear the BitmapHeapPath */\n\t\t\t\tcreate_index_paths(root, rel);\n\n\t\t\t\t/* Filter out any BitmapHeapPath that create_index_paths added */\n\t\t\t\tforeach (lc, rel->pathlist)\n\t\t\t\t{\n\t\t\t\t\tPath *path = lfirst(lc);\n\t\t\t\t\tif (!IsA(path, BitmapHeapPath))\n\t\t\t\t\t\tfiltered_paths = lappend(filtered_paths, path);\n\t\t\t\t}\n\n\t\t\t\t/*\n\t\t\t\t * If no non-bitmap index paths were created (e.g., enable_indexscan=off),\n\t\t\t\t * add SeqScan as the final fallback.\n\t\t\t\t */\n\t\t\t\tif (filtered_paths == NIL)\n\t\t\t\t{\n\t\t\t\t\tRelids required_outer = rel->lateral_relids;\n\t\t\t\t\tPath *seqpath = create_seqscan_path(root, rel, required_outer, 0);\n\t\t\t\t\tfiltered_paths = lappend(filtered_paths, seqpath);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\trel->pathlist = filtered_paths;\n\n\t\t\t/* Also filter partial_pathlist for parallel plans */\n\t\t\tfiltered_paths = NIL;\n\t\t\tforeach (lc, rel->partial_pathlist)\n\t\t\t{\n\t\t\t\tPath *path = lfirst(lc);\n\t\t\t\tif (!IsA(path, BitmapHeapPath))\n\t\t\t\t\tfiltered_paths = lappend(filtered_paths, path);\n\t\t\t}\n\t\t\trel->partial_pathlist = filtered_paths;\n\t\t}\n\n#endif /* !PG17_GE */\n\t}\n}\n\n/*\n * Run preprocess query optimizations\n */\nvoid\ntsl_preprocess_query(Query *parse, int *cursor_opts)\n{\n\tAssert(parse != NULL);\n\n\t/* Check if constification of watermark values is enabled */\n\tif (ts_guc_enable_cagg_watermark_constify)\n\t{\n\t\tconstify_cagg_watermark(parse);\n\t}\n\n#if PG16_GE\n\t/* Push down ORDER BY and LIMIT for realtime cagg (PG16+ only) */\n\tif (ts_guc_enable_cagg_sort_pushdown)\n\t{\n\t\tcagg_sort_pushdown(parse, cursor_opts);\n\t}\n#endif\n}\n\n/*\n * Replaces pathkeys in tsl-specific custom path types during sort transformation.\n *\n * This hook is called from ts_sort_transform_replace_pathkeys() in sort_transform.c\n * after the basic pathkey replacement has been performed. It handles tsl-specific\n * path types (such as ColumnarScan) that contain additional pathkey fields beyond\n * the standard path.pathkeys field.\n */\nvoid\ntsl_sort_transform_replace_pathkeys(void *path, List *transformed_pathkeys, List *original_pathkeys)\n{\n\tif (!path)\n\t\treturn;\n\tif (ts_is_columnar_scan_path(path))\n\t{\n\t\tColumnarScanPath *dcpath = (ColumnarScanPath *) path;\n\t\tif (compare_pathkeys(dcpath->required_compressed_pathkeys, transformed_pathkeys) ==\n\t\t\tPATHKEYS_EQUAL)\n\t\t{\n\t\t\tdcpath->required_compressed_pathkeys = original_pathkeys;\n\t\t}\n\t}\n}\n\n/*\n * Run plan postprocessing optimizations.\n */\nvoid\ntsl_postprocess_plan(PlannedStmt *stmt)\n{\n\tif (ts_guc_enable_columnarindexscan)\n\t{\n\t\tstmt->planTree = try_insert_columnar_index_scan_node(stmt->planTree, stmt->rtable);\n\t\tstmt->subplans =\n\t\t\t(List *) try_insert_columnar_index_scan_node((Plan *) stmt->subplans, stmt->rtable);\n\t}\n\n\tif (ts_guc_enable_vectorized_aggregation)\n\t{\n\t\tstmt->planTree = try_insert_vector_agg_node(stmt->planTree);\n\t\tstmt->subplans = (List *) try_insert_vector_agg_node((Plan *) stmt->subplans);\n\t}\n\n#ifdef TS_DEBUG\n\tif (ts_guc_debug_require_vector_agg != DRO_Allow)\n\t{\n\t\tbool has_some_agg = false;\n\t\tconst bool has_vector_partial_agg = has_vector_agg_node(stmt->planTree, &has_some_agg);\n\n\t\t/*\n\t\t * For convenience of using this in the tests, we don't complain about\n\t\t * queries that don't have aggregation at all.\n\t\t */\n\t\tif (has_some_agg)\n\t\t{\n\t\t\tif (!has_vector_partial_agg && ts_guc_debug_require_vector_agg == DRO_Require)\n\t\t\t{\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t\t\t errmsg(\"vectorized aggregation node not found when required by the \"\n\t\t\t\t\t\t\t\t\"debug_require_vector_agg GUC\")));\n\t\t\t}\n\n\t\t\tif (has_vector_partial_agg && ts_guc_debug_require_vector_agg == DRO_Forbid)\n\t\t\t{\n\t\t\t\tereport(ERROR,\n\t\t\t\t\t\t(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),\n\t\t\t\t\t\t errmsg(\"vectorized aggregation node found when forbidden by the \"\n\t\t\t\t\t\t\t\t\"debug_require_vector_agg GUC\")));\n\t\t\t}\n\t\t}\n\t}\n#endif\n}\n"
  },
  {
    "path": "tsl/src/planner.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n#include <optimizer/planner.h>\n\n#include \"hypertable.h\"\n#include <planner/planner.h>\n\nvoid tsl_create_upper_paths_hook(PlannerInfo *, UpperRelationKind, RelOptInfo *, RelOptInfo *,\n\t\t\t\t\t\t\t\t TsRelType, Hypertable *, void *);\nvoid tsl_set_rel_pathlist_query(PlannerInfo *, RelOptInfo *, Index, RangeTblEntry *, Hypertable *);\nvoid tsl_set_rel_pathlist_dml(PlannerInfo *, RelOptInfo *, Index, RangeTblEntry *, Hypertable *);\nvoid tsl_sort_transform_replace_pathkeys(void *path, List *transformed_pathkeys,\n\t\t\t\t\t\t\t\t\t\t List *original_pathkeys);\nvoid tsl_preprocess_query(Query *parse, int *cursor_opts);\nvoid tsl_postprocess_plan(PlannedStmt *stmt);\n"
  },
  {
    "path": "tsl/src/process_utility.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#include <postgres.h>\n#include <access/xact.h>\n#include <catalog/namespace.h>\n#include <catalog/pg_trigger.h>\n#include <commands/event_trigger.h>\n#include <commands/tablecmds.h>\n#include <nodes/makefuncs.h>\n#include <nodes/nodes.h>\n#include <nodes/parsenodes.h>\n#include <storage/lockdefs.h>\n\n#include \"compression/create.h\"\n#include \"continuous_aggs/create.h\"\n#include \"guc.h\"\n#include \"hypertable_cache.h\"\n#include \"process_utility.h\"\n#include \"ts_catalog/continuous_agg.h\"\n\n/* AlterTableCmds that need tsl side processing invoke this function\n * we only process AddColumn command right now.\n */\nvoid\ntsl_process_altertable_cmd(Hypertable *ht, const AlterTableCmd *cmd)\n{\n\tswitch (cmd->subtype)\n\t{\n\t\tcase AT_AddColumn:\n#if PG16_LT\n\t\tcase AT_AddColumnRecurse:\n#endif\n\t\t\tif (TS_HYPERTABLE_HAS_COMPRESSION_TABLE(ht) ||\n\t\t\t\tTS_HYPERTABLE_HAS_COMPRESSION_ENABLED(ht))\n\t\t\t{\n\t\t\t\tColumnDef *orig_coldef = castNode(ColumnDef, cmd->def);\n\t\t\t\ttsl_process_compress_table_add_column(ht, orig_coldef);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase AT_DropColumn:\n#if PG16_LT\n\t\tcase AT_DropColumnRecurse:\n#endif\n\t\t\tif (TS_HYPERTABLE_HAS_COMPRESSION_TABLE(ht) ||\n\t\t\t\tTS_HYPERTABLE_HAS_COMPRESSION_ENABLED(ht))\n\t\t\t{\n\t\t\t\ttsl_process_compress_table_drop_column(ht, cmd->name);\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n}\n\nvoid\ntsl_process_rename_cmd(Oid relid, Cache *hcache, const RenameStmt *stmt)\n{\n\tif (stmt->renameType == OBJECT_COLUMN)\n\t{\n\t\t/*\n\t\t * process_rename_column() always sets relid to the materialization\n\t\t * hypertable before calling us, so the cache lookup always succeeds.\n\t\t */\n\t\tHypertable *ht = ts_hypertable_cache_get_entry(hcache, relid, CACHE_FLAG_MISSING_OK);\n\n\t\tif (ht)\n\t\t{\n\t\t\tContinuousAgg *cagg = ts_continuous_agg_find_by_mat_hypertable_id(ht->fd.id, true);\n\t\t\tif (cagg)\n\t\t\t\tcagg_rename_view_columns(cagg);\n\t\t}\n\n\t\tif (ht &&\n\t\t\t(TS_HYPERTABLE_HAS_COMPRESSION_TABLE(ht) || TS_HYPERTABLE_HAS_COMPRESSION_ENABLED(ht)))\n\t\t{\n\t\t\ttsl_process_compress_table_rename_column(ht, stmt);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "tsl/src/process_utility.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <commands/event_trigger.h>\n#include <process_utility.h>\n\nextern void tsl_process_altertable_cmd(Hypertable *ht, const AlterTableCmd *cmd);\nextern void tsl_process_rename_cmd(Oid relid, Cache *hcache, const RenameStmt *stmt);\nextern DDLResult tsl_ddl_command_start(ProcessUtilityArgs *args);\nextern void tsl_ddl_command_end(EventTriggerData *command);\n"
  },
  {
    "path": "tsl/src/reorder.c",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n\n/*\n * This file contains source code that was copied and/or modified from the\n * PostgreSQL database, which is licensed under the open-source PostgreSQL\n * License. Please see the NOTICE at the top level directory for a copy of\n * the PostgreSQL License.\n */\n\n/* see postgres commit ab5e9caa4a3ec4765348a0482e88edcf3f6aab4a */\n\n#include <postgres.h>\n#include <access/amapi.h>\n#include <access/multixact.h>\n#include <access/relscan.h>\n#include <access/rewriteheap.h>\n#include <access/transam.h>\n#include <access/xact.h>\n#include <access/xlog.h>\n#include <catalog/catalog.h>\n#include <catalog/dependency.h>\n#include <catalog/heap.h>\n#include <catalog/index.h>\n#include <catalog/namespace.h>\n#include <catalog/objectaccess.h>\n#include <catalog/pg_am.h>\n#include <catalog/pg_authid.h>\n#include <catalog/pg_tablespace_d.h>\n#include <catalog/toasting.h>\n#include <commands/cluster.h>\n#include <commands/tablecmds.h>\n#include <commands/tablespace.h>\n#include <commands/vacuum.h>\n#include <executor/spi.h>\n#include <miscadmin.h>\n#include <nodes/pg_list.h>\n#include <optimizer/planner.h>\n#include <storage/bufmgr.h>\n#include <storage/lmgr.h>\n#include <storage/lockdefs.h>\n#include <storage/predicate.h>\n#include <storage/smgr.h>\n#include <tcop/tcopprot.h>\n#include <utils/acl.h>\n#include <utils/builtins.h>\n#include <utils/fmgroids.h>\n#include <utils/guc.h>\n#include <utils/inval.h>\n#include <utils/lsyscache.h>\n#include <utils/memutils.h>\n#include <utils/pg_rusage.h>\n#include <utils/relmapper.h>\n#include <utils/snapmgr.h>\n#include <utils/syscache.h>\n#include <utils/tuplesort.h>\n\n#include <access/toast_internals.h>\n\n#include \"chunk.h\"\n#include \"chunk_index.h\"\n#include \"hypertable_cache.h\"\n#include \"import/heapswap.h\"\n#include \"indexing.h\"\n#include \"reorder.h\"\n\nstatic void reorder_rel(Oid tableOid, Oid indexOid, bool verbose, Oid wait_id,\n\t\t\t\t\t\tOid destination_tablespace, Oid index_tablespace);\n\n#define REORDER_ACCESS_EXCLUSIVE_DEADLOCK_TIMEOUT \"101000\"\n\nstatic void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid wait_id,\n\t\t\t\t\t\t\t Oid destination_tablespace, Oid index_tablespace);\nstatic void copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,\n\t\t\t\t\t\t   bool *pSwapToastByContent, TransactionId *pFreezeXid,\n\t\t\t\t\t\t   MultiXactId *pCutoffMulti);\n\nstatic Oid chunk_get_reorder_index(Hypertable *ht, Chunk *chunk, Oid index_relid);\n\nDatum\ntsl_reorder_chunk(PG_FUNCTION_ARGS)\n{\n\tOid chunk_id = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\tOid index_id = PG_ARGISNULL(1) ? InvalidOid : PG_GETARG_OID(1);\n\tbool verbose = PG_ARGISNULL(2) ? false : PG_GETARG_BOOL(2);\n\n\t/* used for debugging purposes only see finish_heap_swaps */\n\tOid wait_id = PG_NARGS() < 4 || PG_ARGISNULL(3) ? InvalidOid : PG_GETARG_OID(3);\n\n\t/*\n\t * Allow reorder in transactions for testing purposes only\n\t */\n\tif (!OidIsValid(wait_id))\n\t\tPreventInTransactionBlock(true, \"reorder\");\n\n\treorder_chunk(chunk_id, index_id, verbose, wait_id, InvalidOid, InvalidOid);\n\tPG_RETURN_VOID();\n}\n\nDatum\ntsl_move_chunk(PG_FUNCTION_ARGS)\n{\n\tOid chunk_id = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);\n\tOid destination_tablespace =\n\t\tPG_ARGISNULL(1) ? InvalidOid : get_tablespace_oid(PG_GETARG_NAME(1)->data, false);\n\tOid index_destination_tablespace =\n\t\tPG_ARGISNULL(2) ? InvalidOid : get_tablespace_oid(PG_GETARG_NAME(2)->data, false);\n\tOid index_id = PG_ARGISNULL(3) ? InvalidOid : PG_GETARG_OID(3);\n\tbool verbose = PG_ARGISNULL(4) ? false : PG_GETARG_BOOL(4);\n\tChunk *chunk;\n\n\t/* used for debugging purposes only see finish_heap_swaps */\n\tOid wait_id = PG_NARGS() < 6 || PG_ARGISNULL(5) ? InvalidOid : PG_GETARG_OID(5);\n\n\t/*\n\t * Allow move in transactions for testing purposes only\n\t */\n\tif (!OidIsValid(wait_id))\n\t\tPreventInTransactionBlock(true, \"move\");\n\n\t/*\n\t * Index_destination_tablespace is currently a required parameter in order\n\t * to avoid situations where there is ambiguity about where indexes should\n\t * be placed based on where the index was created and the new tablespace\n\t * (and avoid interactions with multi-tablespace hypertable functionality).\n\t * Eventually we may want to offer an option to keep indexes in the\n\t * tablespace of their parent if it is specified.\n\t */\n\tif (!OidIsValid(chunk_id) || !OidIsValid(destination_tablespace) ||\n\t\t!OidIsValid(index_destination_tablespace))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"valid chunk, destination_tablespace, and index_destination_tablespaces \"\n\t\t\t\t\t\t\"are required\")));\n\n\tchunk = ts_chunk_get_by_relid(chunk_id, false);\n\n\tif (NULL == chunk)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"\\\"%s\\\" is not a chunk\", get_rel_name(chunk_id))));\n\n\tif (ts_chunk_contains_compressed_data(chunk))\n\t{\n\t\tChunk *chunk_parent = ts_chunk_get_compressed_chunk_parent(chunk);\n\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot directly move internal columnstore data\"),\n\t\t\t\t errdetail(\"Chunk \\\"%s\\\" contains columnstore data for chunk \\\"%s\\\" and cannot be \"\n\t\t\t\t\t\t   \"moved directly.\",\n\t\t\t\t\t\t   get_rel_name(chunk_id),\n\t\t\t\t\t\t   get_rel_name(chunk_parent->table_id)),\n\t\t\t\t errhint(\"Moving chunk \\\"%s\\\" will also move the columnstore data.\",\n\t\t\t\t\t\t get_rel_name(chunk_parent->table_id))));\n\t}\n\n\t/* If chunk is compressed move it by altering tablespace on both chunks */\n\tif (OidIsValid(chunk->fd.compressed_chunk_id))\n\t{\n\t\tChunk *compressed_chunk = ts_chunk_get_by_id(chunk->fd.compressed_chunk_id, true);\n\t\tAlterTableCmd cmd = { .type = T_AlterTableCmd,\n\t\t\t\t\t\t\t  .subtype = AT_SetTableSpace,\n\t\t\t\t\t\t\t  .name = get_tablespace_name(destination_tablespace) };\n\n\t\tif (OidIsValid(index_id))\n\t\t\tereport(NOTICE,\n\t\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t\t errmsg(\"ignoring index parameter\"),\n\t\t\t\t\t errdetail(\"Chunk will not be reordered as it has columnstore data.\")));\n\n\t\tts_alter_table_with_event_trigger(chunk_id, fcinfo->context, list_make1(&cmd), false);\n\t\tts_alter_table_with_event_trigger(compressed_chunk->table_id,\n\t\t\t\t\t\t\t\t\t\t  fcinfo->context,\n\t\t\t\t\t\t\t\t\t\t  list_make1(&cmd),\n\t\t\t\t\t\t\t\t\t\t  false);\n\t\t/* move indexes on original and compressed chunk */\n\t\tts_chunk_index_move_all(chunk_id, index_destination_tablespace);\n\t\tts_chunk_index_move_all(compressed_chunk->table_id, index_destination_tablespace);\n\t}\n\telse\n\t{\n\t\treorder_chunk(chunk_id,\n\t\t\t\t\t  index_id,\n\t\t\t\t\t  verbose,\n\t\t\t\t\t  wait_id,\n\t\t\t\t\t  destination_tablespace,\n\t\t\t\t\t  index_destination_tablespace);\n\t}\n\n\tPG_RETURN_VOID();\n}\n\nvoid\nreorder_chunk(Oid chunk_id, Oid index_id, bool verbose, Oid wait_id, Oid destination_tablespace,\n\t\t\t  Oid index_tablespace)\n{\n\tChunk *chunk;\n\tCache *hcache;\n\tHypertable *ht;\n\n\tif (!OidIsValid(chunk_id))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"must provide a valid chunk to cluster\")));\n\n\tchunk = ts_chunk_get_by_relid(chunk_id, false);\n\n\tif (NULL == chunk)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t errmsg(\"\\\"%s\\\" is not a chunk\", get_rel_name(chunk_id))));\n\n\tht = ts_hypertable_cache_get_cache_and_entry(chunk->hypertable_relid, CACHE_FLAG_NONE, &hcache);\n\n\t/* Our check gives better error messages, but keep the original one too. */\n\tts_hypertable_permissions_check(ht->main_table_relid, GetUserId());\n\n\tif (!object_ownercheck(RelationRelationId, ht->main_table_relid, GetUserId()))\n\t{\n\t\tOid main_table_relid = ht->main_table_relid;\n\n\t\tts_cache_release(&hcache);\n\t\taclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_TABLE, get_rel_name(main_table_relid));\n\t}\n\n\tOid index_relid = chunk_get_reorder_index(ht, chunk, index_id);\n\tif (!OidIsValid(index_relid))\n\t{\n\t\tts_cache_release(&hcache);\n\t\tif (OidIsValid(index_id))\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INVALID_PARAMETER_VALUE),\n\t\t\t\t\t errmsg(\"\\\"%s\\\" is not a valid clustering index for table \\\"%s\\\"\",\n\t\t\t\t\t\t\tget_rel_name(index_id),\n\t\t\t\t\t\t\tget_rel_name(chunk_id))));\n\t\telse\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_UNDEFINED_OBJECT),\n\t\t\t\t\t errmsg(\"there is no previously clustered index for table \\\"%s\\\"\",\n\t\t\t\t\t\t\tget_rel_name(chunk_id))));\n\t}\n\n\tif (OidIsValid(destination_tablespace) && destination_tablespace != MyDatabaseTableSpace)\n\t{\n\t\tAclResult aclresult;\n\n\t\taclresult =\n\t\t\tobject_aclcheck(TableSpaceRelationId, destination_tablespace, GetUserId(), ACL_CREATE);\n\t\tif (aclresult != ACLCHECK_OK)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),\n\t\t\t\t\t errmsg(\"permission denied for tablespace \\\"%s\\\"\",\n\t\t\t\t\t\t\tget_tablespace_name(destination_tablespace))));\n\t\t;\n\t}\n\n\tif (OidIsValid(index_tablespace) && index_tablespace != MyDatabaseTableSpace)\n\t{\n\t\tAclResult aclresult;\n\n\t\taclresult =\n\t\t\tobject_aclcheck(TableSpaceRelationId, index_tablespace, GetUserId(), ACL_CREATE);\n\t\tif (aclresult != ACLCHECK_OK)\n\t\t\tereport(ERROR,\n\t\t\t\t\t(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),\n\t\t\t\t\t errmsg(\"permission denied for tablespace \\\"%s\\\"\",\n\t\t\t\t\t\t\tget_tablespace_name(index_tablespace))));\n\t}\n\n\t/*\n\t * We must mark each chunk index as clustered before calling reorder_rel()\n\t * because it expects indexes that need to be rechecked (due to new\n\t * transaction) to already have that mark set\n\t */\n\tts_chunk_index_mark_clustered(chunk->table_id, index_relid);\n\treorder_rel(chunk->table_id,\n\t\t\t\tindex_relid,\n\t\t\t\tverbose,\n\t\t\t\twait_id,\n\t\t\t\tdestination_tablespace,\n\t\t\t\tindex_tablespace);\n\tts_cache_release(&hcache);\n}\n\nstatic bool\nindex_belongs_to_relation(Oid relid, Oid index_oid)\n{\n\tHeapTuple tuple;\n\tForm_pg_index rd_index;\n\tbool result;\n\n\ttuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid));\n\tif (!HeapTupleIsValid(tuple))\n\t\treturn false;\n\n\trd_index = (Form_pg_index) GETSTRUCT(tuple);\n\tresult = rd_index->indrelid == relid;\n\tReleaseSysCache(tuple);\n\n\treturn result;\n}\n\n/*\n * Find the index to reorder a chunk on based on a possibly NULL indexname\n * returns InvalidOid if no such index is found\n */\nstatic Oid\nchunk_get_reorder_index(Hypertable *ht, Chunk *chunk, Oid index_relid)\n{\n\t/*\n\t * Index search order: 1. Explicitly named index 2. Chunk cluster index 3.\n\t * - index belongs to the chunk\n\t * - index belongs to the hypertable\n\t * - any clustered index on the chunk\n\t * - any clustered index on the hypertable\n\t */\n\n\tif (OidIsValid(index_relid))\n\t{\n\t\tif (index_belongs_to_relation(chunk->table_id, index_relid))\n\t\t\treturn index_relid;\n\n\t\tif (index_belongs_to_relation(ht->main_table_relid, index_relid))\n\t\t{\n\t\t\tRelation chunk_rel = table_open(chunk->table_id, AccessShareLock);\n\t\t\tOid chunk_index_oid =\n\t\t\t\tts_chunk_index_get_by_hypertable_indexrelid(chunk_rel, index_relid);\n\t\t\ttable_close(chunk_rel, NoLock);\n\t\t\treturn chunk_index_oid;\n\t\t}\n\n\t\treturn InvalidOid;\n\t}\n\n\tindex_relid = ts_indexing_find_clustered_index(chunk->table_id);\n\tif (OidIsValid(index_relid))\n\t\treturn index_relid;\n\n\tindex_relid = ts_indexing_find_clustered_index(ht->main_table_relid);\n\tif (OidIsValid(index_relid))\n\t{\n\t\tRelation chunk_rel = table_open(chunk->table_id, AccessShareLock);\n\t\tOid chunk_index_oid = ts_chunk_index_get_by_hypertable_indexrelid(chunk_rel, index_relid);\n\t\ttable_close(chunk_rel, NoLock);\n\t\treturn chunk_index_oid;\n\t}\n\n\treturn InvalidOid;\n}\n\n/* The following functions are based on their equivalents in postgres's cluster.c */\n\n/*\n * reorder_rel\n *\n * This clusters the table by creating a new, clustered table and\n * swapping the relfilenodes of the new table and the old table, so\n * the OID of the original table is preserved.\n *\n * Indexes are rebuilt in the same manner.\n */\nstatic void\nreorder_rel(Oid tableOid, Oid indexOid, bool verbose, Oid wait_id, Oid destination_tablespace,\n\t\t\tOid index_tablespace)\n{\n\tRelation OldHeap;\n\tHeapTuple tuple;\n\tForm_pg_index indexForm;\n\n\tif (!OidIsValid(indexOid))\n\t\telog(ERROR, \"Reorder must specify an index.\");\n\n\t/* Check for user-requested abort. */\n\tCHECK_FOR_INTERRUPTS();\n\n\t/*\n\t * We grab exclusive access to the target rel and index for the duration\n\t * of the transaction.  (This is redundant for the single-transaction\n\t * case, since cluster() already did it.)  The index lock is taken inside\n\t * check_index_is_clusterable.\n\t */\n\tOldHeap = try_relation_open(tableOid, ExclusiveLock);\n\n\t/* If the table has gone away, we can skip processing it */\n\tif (!OldHeap)\n\t{\n\t\tereport(WARNING, (errcode(ERRCODE_WARNING), errmsg(\"table disappeared during reorder\")));\n\t\treturn;\n\t}\n\n\t/*\n\t * Since we may open a new transaction for each relation, we have to check\n\t * that the relation still is what we think it is.\n\t */\n\t/* Check that the user still owns the relation */\n\tif (!object_ownercheck(RelationRelationId, tableOid, GetUserId()))\n\t{\n\t\trelation_close(OldHeap, ExclusiveLock);\n\t\tereport(WARNING, (errcode(ERRCODE_WARNING), errmsg(\"ownership changed during reorder\")));\n\t\treturn;\n\t}\n\n\tif (IsSystemRelation(OldHeap))\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot reorder a system relation\")));\n\n\tif (OldHeap->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"can only reorder a permanent table\")));\n\n\t/* We do not allow reordering on shared catalogs. */\n\tif (OldHeap->rd_rel->relisshared)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"cannot reorder a shared catalog\")));\n\n\tif (OldHeap->rd_rel->relkind != RELKIND_RELATION)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg(\"can only reorder a relation\")));\n\n\t/*\n\t * Check that the index still exists\n\t */\n\tif (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(indexOid)))\n\t{\n\t\tereport(WARNING, (errcode(ERRCODE_WARNING), errmsg(\"index disappeared during reorder\")));\n\t\trelation_close(OldHeap, ExclusiveLock);\n\t\treturn;\n\t}\n\n\t/*\n\t * Check that the index is still the one with indisclustered set.\n\t */\n\ttuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexOid));\n\tif (!HeapTupleIsValid(tuple)) /* probably can't happen */\n\t{\n\t\tereport(WARNING, (errcode(ERRCODE_WARNING), errmsg(\"invalid index heap during reorder\")));\n\t\trelation_close(OldHeap, ExclusiveLock);\n\t\treturn;\n\t}\n\tindexForm = (Form_pg_index) GETSTRUCT(tuple);\n\n\t/*\n\t * We always mark indexes as clustered when we intercept a cluster\n\t * command, if it's not marked as such here, something has gone wrong\n\t */\n\tif (!indexForm->indisclustered)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_ASSERT_FAILURE), errmsg(\"invalid index heap during reorder\")));\n\tReleaseSysCache(tuple);\n\n\t/*\n\t * Also check for active uses of the relation in the current transaction,\n\t * including open scans and pending AFTER trigger events.\n\t */\n\tCheckTableNotInUse(OldHeap, \"CLUSTER\");\n\n\t/* Check heap and index are valid to cluster on */\n\tcheck_index_is_clusterable(OldHeap, indexOid, ExclusiveLock);\n\n\t/* rebuild_relation does all the dirty work */\n\trebuild_relation(OldHeap, indexOid, verbose, wait_id, destination_tablespace, index_tablespace);\n\n\t/* NB: rebuild_relation does table_close() on OldHeap */\n}\n\nstatic void\nreorder_finish_heap_swaps(Oid OIDOldHeap, Oid OIDNewHeap, char relpersistence, List *old_index_oids,\n\t\t\t\t\t\t  List *new_index_oids, bool swap_toast_by_content, bool is_internal,\n\t\t\t\t\t\t  TransactionId frozenXid, MultiXactId cutoffMulti, Oid wait_id)\n{\n\tListCell *old_index_cell;\n\tListCell *new_index_cell;\n\tOid mapped_tables[4];\n\tint config_change;\n\n#ifdef DEBUG\n\n\t/*\n\t * For debug purposes we serialize against wait_id if it exists, this\n\t * allows us to \"pause\" reorder immediately before swapping in the new\n\t * table\n\t */\n\tif (OidIsValid(wait_id))\n\t{\n\t\tRelation waiter = table_open(wait_id, AccessExclusiveLock);\n\n\t\ttable_close(waiter, AccessExclusiveLock);\n\t}\n#endif\n\n\t/*\n\t * There's a risk of deadlock if some other process is also trying to\n\t * upgrade their lock in the same manner as us, at this time. Since our\n\t * transaction has performed a large amount of work, and only needs to be\n\t * run once per chunk, we do not want to abort it due to this deadlock. To\n\t * prevent abort we set our `deadlock_timeout` to a large value in the\n\t * expectation that the other process will timeout and abort first.\n\t * Currently we set `deadlock_timeout` to 1 hour, as this should be longer\n\t * than any other normal process, while still allowing the system to make\n\t * progress in the event of a real deadlock. As this is the last lock we\n\t * grab, and the setting is local to our transaction we do not bother\n\t * changing the guc back.\n\t */\n\tconfig_change = set_config_option(\"deadlock_timeout\",\n\t\t\t\t\t\t\t\t\t  REORDER_ACCESS_EXCLUSIVE_DEADLOCK_TIMEOUT,\n\t\t\t\t\t\t\t\t\t  PGC_SUSET,\n\t\t\t\t\t\t\t\t\t  PGC_S_SESSION,\n\t\t\t\t\t\t\t\t\t  GUC_ACTION_LOCAL,\n\t\t\t\t\t\t\t\t\t  true,\n\t\t\t\t\t\t\t\t\t  0,\n\t\t\t\t\t\t\t\t\t  false);\n\n\tif (config_change == 0)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"deadlock_timeout guc does not exist.\")));\n\telse if (config_change < 0)\n\t\tereport(ERROR,\n\t\t\t\t(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\n\t\t\t\t errmsg(\"could not set deadlock_timeout guc.\")));\n\n\t/* Upgrade to an AccessExclusiveLock for the heap swap */\n\tLockRelationOid(OIDOldHeap, AccessExclusiveLock);\n\n\t/* Swap the contents of the indexes */\n\tAssert(list_length(old_index_oids) == list_length(new_index_oids));\n\tforboth (old_index_cell, old_index_oids, new_index_cell, new_index_oids)\n\t{\n\t\tOid old_index_oid = lfirst_oid(old_index_cell);\n\t\tOid new_index_oid = lfirst_oid(new_index_cell);\n\n\t\tts_swap_relation_files(old_index_oid,\n\t\t\t\t\t\t\t   new_index_oid,\n\t\t\t\t\t\t\t   false,\n\t\t\t\t\t\t\t   swap_toast_by_content,\n\t\t\t\t\t\t\t   true,\n\t\t\t\t\t\t\t   frozenXid,\n\t\t\t\t\t\t\t   cutoffMulti,\n\t\t\t\t\t\t\t   mapped_tables);\n\t}\n\n\t/* Old indexes must be visible for deletion */\n\tCommandCounterIncrement();\n\n\t/*\n\t * Swap the physical files of the target and transient tables, then\n\t * rebuild the target's indexes and throw away the transient table.\n\t */\n\tts_finish_heap_swap(OIDOldHeap,\n\t\t\t\t\t\tOIDNewHeap,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\tswap_toast_by_content,\n\t\t\t\t\t\ttrue,\n\t\t\t\t\t\ttrue,\n\t\t\t\t\t\ttrue,\n\t\t\t\t\t\tfrozenXid,\n\t\t\t\t\t\tcutoffMulti,\n\t\t\t\t\t\trelpersistence);\n}\n/*\n * rebuild_relation: rebuild an existing relation in index or physical order\n *\n * OldHeap: table to rebuild --- must be opened and exclusive-locked!\n * indexOid: index to cluster by, or InvalidOid to rewrite in physical order.\n *\n * NB: this routine closes OldHeap at the right time; caller should not.\n */\nstatic void\nrebuild_relation(Relation OldHeap, Oid indexOid, bool verbose, Oid wait_id,\n\t\t\t\t Oid destination_tablespace, Oid index_tablespace)\n{\n\tOid tableOid = RelationGetRelid(OldHeap);\n\tOid tableSpace = OidIsValid(destination_tablespace) ? destination_tablespace :\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  OldHeap->rd_rel->reltablespace;\n\tOid OIDNewHeap;\n\tList *old_index_oids;\n\tList *new_index_oids;\n\tchar relpersistence;\n\tbool swap_toast_by_content;\n\tTransactionId frozenXid;\n\tMultiXactId cutoffMulti;\n\n\t/* Mark the correct index as clustered */\n\tmark_index_clustered(OldHeap, indexOid, true);\n\n\t/* Remember info about rel before closing OldHeap */\n\trelpersistence = OldHeap->rd_rel->relpersistence;\n\n\t/* Close relcache entry, but keep lock until transaction commit */\n\ttable_close(OldHeap, NoLock);\n\n\t/* Create the transient table that will receive the re-ordered data */\n\tOIDNewHeap =\n\t\tmake_new_heap(tableOid, tableSpace, OldHeap->rd_rel->relam, relpersistence, ExclusiveLock);\n\n\t/* Copy the heap data into the new table in the desired order */\n\tcopy_heap_data(OIDNewHeap,\n\t\t\t\t   tableOid,\n\t\t\t\t   indexOid,\n\t\t\t\t   verbose,\n\t\t\t\t   &swap_toast_by_content,\n\t\t\t\t   &frozenXid,\n\t\t\t\t   &cutoffMulti);\n\n\t/* Create versions of the tables indexes for the new table */\n\tnew_index_oids =\n\t\tts_chunk_index_duplicate(tableOid, OIDNewHeap, &old_index_oids, index_tablespace);\n\n\treorder_finish_heap_swaps(tableOid,\n\t\t\t\t\t\t\t  OIDNewHeap,\n\t\t\t\t\t\t\t  relpersistence,\n\t\t\t\t\t\t\t  old_index_oids,\n\t\t\t\t\t\t\t  new_index_oids,\n\t\t\t\t\t\t\t  swap_toast_by_content,\n\t\t\t\t\t\t\t  true,\n\t\t\t\t\t\t\t  frozenXid,\n\t\t\t\t\t\t\t  cutoffMulti,\n\t\t\t\t\t\t\t  wait_id);\n}\n\n/*\n * Do the physical copying of heap data.\n *\n * There are three output parameters:\n * *pSwapToastByContent is set true if toast tables must be swapped by content.\n * *pFreezeXid receives the TransactionId used as freeze cutoff point.\n * *pCutoffMulti receives the MultiXactId used as a cutoff point.\n */\nstatic void\ncopy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,\n\t\t\t   bool *pSwapToastByContent, TransactionId *pFreezeXid, MultiXactId *pCutoffMulti)\n{\n\tRelation NewHeap, OldHeap, OldIndex;\n\tRelation relRelation;\n\tHeapTuple reltup;\n\tForm_pg_class relform;\n\tTupleDesc PG_USED_FOR_ASSERTS_ONLY oldTupDesc;\n\tTupleDesc newTupDesc;\n\tint natts;\n\tDatum *values;\n\tbool *isnull;\n\tbool use_sort;\n\tdouble num_tuples = 0, tups_vacuumed = 0, tups_recently_dead = 0;\n\tBlockNumber num_pages;\n\tint elevel = verbose ? INFO : DEBUG2;\n\tPGRUsage ru0;\n\tpg_rusage_init(&ru0);\n\n\t/*\n\t * Open the relations we need.\n\t */\n\tNewHeap = table_open(OIDNewHeap, AccessExclusiveLock);\n\tOldHeap = table_open(OIDOldHeap, ExclusiveLock);\n\n\tif (OidIsValid(OIDOldIndex))\n\t\tOldIndex = index_open(OIDOldIndex, ExclusiveLock);\n\telse\n\t\tOldIndex = NULL;\n\n\t/*\n\t * Their tuple descriptors should be exactly alike, but here we only need\n\t * assume that they have the same number of columns.\n\t */\n\toldTupDesc = RelationGetDescr(OldHeap);\n\tnewTupDesc = RelationGetDescr(NewHeap);\n\tAssert(newTupDesc->natts == oldTupDesc->natts);\n\n\t/* Preallocate values/isnull arrays */\n\tnatts = newTupDesc->natts;\n\tvalues = (Datum *) palloc(natts * sizeof(Datum));\n\tisnull = (bool *) palloc(natts * sizeof(bool));\n\n\t/*\n\t * If the OldHeap has a toast table, get lock on the toast table to keep\n\t * it from being vacuumed.  This is needed because autovacuum processes\n\t * toast tables independently of their main tables, with no lock on the\n\t * latter.  If an autovacuum were to start on the toast table after we\n\t * compute our OldestXmin below, it would use a later OldestXmin, and then\n\t * possibly remove as DEAD toast tuples belonging to main tuples we think\n\t * are only RECENTLY_DEAD.  Then we'd fail while trying to copy those\n\t * tuples.\n\t *\n\t * We don't need to open the toast relation here, just lock it.  The lock\n\t * will be held till end of transaction.\n\t */\n\tif (OldHeap->rd_rel->reltoastrelid)\n\t\tLockRelationOid(OldHeap->rd_rel->reltoastrelid, ExclusiveLock);\n\n\t/* use_wal off requires smgr_targblock be initially invalid */\n\tAssert(RelationGetTargetBlock(NewHeap) == InvalidBlockNumber);\n\n\t/*\n\t * If both tables have TOAST tables, perform toast swap by content.  It is\n\t * possible that the old table has a toast table but the new one doesn't,\n\t * if toastable columns have been dropped.  In that case we have to do\n\t * swap by links.  This is okay because swap by content is only essential\n\t * for system catalogs, and we don't support schema changes for them.\n\t */\n\tif (OldHeap->rd_rel->reltoastrelid && NewHeap->rd_rel->reltoastrelid)\n\t{\n\t\t*pSwapToastByContent = true;\n\n\t\t/*\n\t\t * When doing swap by content, any toast pointers written into NewHeap\n\t\t * must use the old toast table's OID, because that's where the toast\n\t\t * data will eventually be found.  Set this up by setting rd_toastoid.\n\t\t * This also tells toast_save_datum() to preserve the toast value\n\t\t * OIDs, which we want so as not to invalidate toast pointers in\n\t\t * system catalog caches, and to avoid making multiple copies of a\n\t\t * single toast value.\n\t\t *\n\t\t * Note that we must hold NewHeap open until we are done writing data,\n\t\t * since the relcache will not guarantee to remember this setting once\n\t\t * the relation is closed.  Also, this technique depends on the fact\n\t\t * that no one will try to read from the NewHeap until after we've\n\t\t * finished writing it and swapping the rels --- otherwise they could\n\t\t * follow the toast pointers to the wrong place.  (It would actually\n\t\t * work for values copied over from the old toast table, but not for\n\t\t * any values that we toast which were previously not toasted.)\n\t\t */\n\t\tNewHeap->rd_toastoid = OldHeap->rd_rel->reltoastrelid;\n\t}\n\telse\n\t\t*pSwapToastByContent = false;\n\n\t/*\n\t * Compute xids used to freeze and weed out dead tuples and multixacts.\n\t * Since we're going to rewrite the whole table anyway, there's no reason\n\t * not to be aggressive about this.\n\t */\n\tstruct VacuumCutoffs cutoffs;\n\tVacuumParams params;\n\n\tmemset(&params, 0, sizeof(VacuumParams));\n\tvacuum_get_cutoffs(OldHeap, &params, &cutoffs);\n\n\t/*\n\t * FreezeXid will become the table's new relfrozenxid, and that mustn't go\n\t * backwards, so take the max.\n\t */\n\t{\n\t\tTransactionId relfrozenxid = OldHeap->rd_rel->relfrozenxid;\n\n\t\tif (TransactionIdIsValid(relfrozenxid) &&\n\t\t\tTransactionIdPrecedes(cutoffs.FreezeLimit, relfrozenxid))\n\t\t\tcutoffs.FreezeLimit = relfrozenxid;\n\t}\n\n\t/*\n\t * MultiXactCutoff, similarly, shouldn't go backwards either.\n\t */\n\t{\n\t\tMultiXactId relminmxid = OldHeap->rd_rel->relminmxid;\n\n\t\tif (MultiXactIdIsValid(relminmxid) &&\n\t\t\tMultiXactIdPrecedes(cutoffs.MultiXactCutoff, relminmxid))\n\t\t\tcutoffs.MultiXactCutoff = relminmxid;\n\t}\n\n\t/* return selected values to caller */\n\t*pFreezeXid = cutoffs.FreezeLimit;\n\t*pCutoffMulti = cutoffs.MultiXactCutoff;\n\n\t/*\n\t * We know how to use a sort to duplicate the ordering of a btree index,\n\t * and will use seqscan-and-sort for that.  Otherwise, always use an\n\t * indexscan for other indexes or plain seqscan if no index is supplied.\n\t */\n\tif (OldIndex != NULL && OldIndex->rd_rel->relam == BTREE_AM_OID)\n\t\tuse_sort = true;\n\telse\n\t\tuse_sort = false;\n\n\t/* Log what we're doing */\n\tif (OldIndex != NULL && !use_sort)\n\t\tereport(elevel,\n\t\t\t\t(errmsg(\"reordering \\\"%s.%s\\\" using index scan on \\\"%s\\\"\",\n\t\t\t\t\t\tget_namespace_name(RelationGetNamespace(OldHeap)),\n\t\t\t\t\t\tRelationGetRelationName(OldHeap),\n\t\t\t\t\t\tRelationGetRelationName(OldIndex))));\n\telse if (use_sort)\n\t\tereport(elevel,\n\t\t\t\t(errmsg(\"reordering \\\"%s.%s\\\" using sequential scan and sort\",\n\t\t\t\t\t\tget_namespace_name(RelationGetNamespace(OldHeap)),\n\t\t\t\t\t\tRelationGetRelationName(OldHeap))));\n\telse\n\t\tereport(ERROR,\n\t\t\t\t(errmsg(\"tried to use a reorder without an index \\\"%s.%s\\\"\",\n\t\t\t\t\t\tget_namespace_name(RelationGetNamespace(OldHeap)),\n\t\t\t\t\t\tRelationGetRelationName(OldHeap))));\n\n\ttable_relation_copy_for_cluster(OldHeap,\n\t\t\t\t\t\t\t\t\tNewHeap,\n\t\t\t\t\t\t\t\t\tOldIndex,\n\t\t\t\t\t\t\t\t\tuse_sort,\n\t\t\t\t\t\t\t\t\tcutoffs.OldestXmin,\n\t\t\t\t\t\t\t\t\t&cutoffs.FreezeLimit,\n\t\t\t\t\t\t\t\t\t&cutoffs.MultiXactCutoff,\n\t\t\t\t\t\t\t\t\t&num_tuples,\n\t\t\t\t\t\t\t\t\t&tups_vacuumed,\n\t\t\t\t\t\t\t\t\t&tups_recently_dead);\n\n\t/* Reset rd_toastoid just to be tidy --- it shouldn't be looked at again */\n\tNewHeap->rd_toastoid = InvalidOid;\n\n\tnum_pages = RelationGetNumberOfBlocks(NewHeap);\n\n\t/* Log what we did */\n\tereport(elevel,\n\t\t\t(errmsg(\"\\\"%s\\\": found %.0f removable, %.0f nonremovable row versions in %u pages\",\n\t\t\t\t\tRelationGetRelationName(OldHeap),\n\t\t\t\t\ttups_vacuumed,\n\t\t\t\t\tnum_tuples,\n\t\t\t\t\tRelationGetNumberOfBlocks(OldHeap)),\n\t\t\t errdetail(\"%.0f dead row versions cannot be removed yet.\\n\"\n\t\t\t\t\t   \"%s.\",\n\t\t\t\t\t   tups_recently_dead,\n\t\t\t\t\t   pg_rusage_show(&ru0))));\n\n\t/* Clean up */\n\tpfree(values);\n\tpfree(isnull);\n\n\tif (OldIndex != NULL)\n\t\tindex_close(OldIndex, NoLock);\n\ttable_close(OldHeap, NoLock);\n\ttable_close(NewHeap, NoLock);\n\n\t/* Update pg_class to reflect the correct values of pages and tuples. */\n\trelRelation = table_open(RelationRelationId, RowExclusiveLock);\n\n\treltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(OIDNewHeap));\n\tif (!HeapTupleIsValid(reltup))\n\t\telog(ERROR, \"cache lookup failed for relation %u\", OIDNewHeap);\n\trelform = (Form_pg_class) GETSTRUCT(reltup);\n\n\trelform->relpages = num_pages;\n\trelform->reltuples = num_tuples;\n\n\t/* Don't update the stats for pg_class.  See swap_relation_files. */\n\tAssert(OIDOldHeap != RelationRelationId);\n\tCacheInvalidateRelcacheByTuple(reltup);\n\n\t/* Clean up. */\n\theap_freetuple(reltup);\n\ttable_close(relRelation, RowExclusiveLock);\n\n\t/* Make the update visible */\n\tCommandCounterIncrement();\n}\n"
  },
  {
    "path": "tsl/src/reorder.h",
    "content": "/*\n * This file and its contents are licensed under the Timescale License.\n * Please see the included NOTICE for copyright information and\n * LICENSE-TIMESCALE for a copy of the license.\n */\n#pragma once\n\n#include <postgres.h>\n\nextern Datum tsl_reorder_chunk(PG_FUNCTION_ARGS);\nextern Datum tsl_move_chunk(PG_FUNCTION_ARGS);\nextern void reorder_chunk(Oid chunk_id, Oid index_id, bool verbose, Oid wait_id,\n\t\t\t\t\t\t  Oid destination_tablespace, Oid index_tablespace);\n"
  },
  {
    "path": "tsl/test/.gitignore",
    "content": "results/\ndump/\nregression.diffs\nregression.out\nunit/testoutputs.tmp\n"
  },
  {
    "path": "tsl/test/CMakeLists.txt",
    "content": "include(\"${PRIMARY_TEST_DIR}/test-defs.cmake\")\n\nset(_local_install_checks)\nset(_install_checks)\n\n# No checks for REGRESS_CHECKS needed here since all the checks are done in the\n# parent CMakeLists.txt.\nif(PG_REGRESS)\n  # This custom target executes one command for each test configuration. It\n  # might be possible to automatically generate this custom target, but for now\n  # the configurations are hard-coded.\n  add_custom_target(\n    regresscheck-t\n    COMMAND\n      ${CMAKE_COMMAND} -E env ${PG_REGRESS_ENV}\n      EXE_DIR=${CMAKE_CURRENT_SOURCE_DIR} TEST_SCHEDULE=${TEST_SCHEDULE}\n      TEST_TIMEOUT=${TEST_TIMEOUT} TEST_PGPORT=${TEST_PGPORT_TEMP_INSTANCE}\n      ${PRIMARY_TEST_DIR}/pg_regress.sh ${PG_REGRESS_OPTS_BASE}\n      ${PG_REGRESS_OPTS_EXTRA} ${PG_REGRESS_OPTS_INOUT}\n      ${PG_REGRESS_OPTS_TEMP_INSTANCE}\n      --temp-config=${TEST_OUTPUT_DIR}/postgresql.conf\n    USES_TERMINAL)\n\n  add_custom_target(\n    regresscheck-t-rerun\n    COMMAND ${PRIMARY_TEST_DIR}/ci_rerun.sh regresscheck-t\n    USES_TERMINAL)\n\n  add_custom_target(\n    regresschecklocal-t\n    COMMAND\n      ${CMAKE_COMMAND} -E env ${PG_REGRESS_ENV}\n      EXE_DIR=${CMAKE_CURRENT_SOURCE_DIR} TEST_PGPORT=${TEST_PGPORT_LOCAL}\n      TEST_SCHEDULE=${TEST_SCHEDULE} TEST_TIMEOUT=${TEST_TIMEOUT}\n      ${PRIMARY_TEST_DIR}/pg_regress.sh ${PG_REGRESS_OPTS_BASE}\n      ${PG_REGRESS_OPTS_EXTRA} ${PG_REGRESS_OPTS_INOUT}\n      ${PG_REGRESS_OPTS_LOCAL_INSTANCE}\n    USES_TERMINAL)\n\n  list(APPEND _local_install_checks regresschecklocal-t)\n  list(APPEND _install_checks regresscheck-t)\n\n  add_custom_target(\n    regresscheck-shared\n    COMMAND\n      ${CMAKE_COMMAND} -E env ${PG_REGRESS_ENV}\n      EXE_DIR=${CMAKE_CURRENT_SOURCE_DIR}/shared\n      TEST_SCHEDULE=${TEST_SCHEDULE_SHARED} TEST_TIMEOUT=${TEST_TIMEOUT}\n      TEST_PGPORT=${TEST_PGPORT_TEMP_INSTANCE} ${PRIMARY_TEST_DIR}/pg_regress.sh\n      ${PG_REGRESS_OPTS_BASE} ${PG_REGRESS_SHARED_OPTS_EXTRA}\n      ${PG_REGRESS_SHARED_OPTS_INOUT} ${PG_REGRESS_OPTS_TEMP_INSTANCE}\n      --temp-config=${TEST_OUTPUT_DIR}/postgresql.conf\n    USES_TERMINAL)\n\n  add_custom_target(\n    regresscheck-shared-rerun\n    COMMAND ${PRIMARY_TEST_DIR}/ci_rerun.sh regresscheck-shared\n    USES_TERMINAL)\n\n  add_custom_target(\n    regresschecklocal-shared\n    COMMAND\n      ${CMAKE_COMMAND} -E env ${PG_REGRESS_ENV}\n      EXE_DIR=${CMAKE_CURRENT_SOURCE_DIR}/shared\n      TEST_SCHEDULE=${TEST_SCHEDULE_SHARED} TEST_TIMEOUT=${TEST_TIMEOUT}\n      TEST_PGPORT=${TEST_PGPORT_LOCAL} ${PRIMARY_TEST_DIR}/pg_regress.sh\n      ${PG_REGRESS_OPTS_BASE} ${PG_REGRESS_SHARED_OPTS_EXTRA}\n      ${PG_REGRESS_SHARED_OPTS_INOUT} ${PG_REGRESS_OPTS_LOCAL_INSTANCE}\n    USES_TERMINAL)\n\n  list(APPEND _install_checks regresscheck-shared)\n  list(APPEND _local_install_checks regresschecklocal-shared)\nelseif(REQUIRE_ALL_TESTS)\n  message(\n    FATAL_ERROR \"All tests were required but 'pg_regress' could not be found\")\nendif()\n\nif(TAP_CHECKS)\n  add_custom_target(\n    provecheck-t\n    COMMAND rm -rf ${CMAKE_CURRENT_BINARY_DIR}/tmp_check\n    COMMAND\n      CONFDIR=${CMAKE_BINARY_DIR}/tsl/test PATH=\"${PG_BINDIR}:$ENV{PATH}\"\n      PG_REGRESS=${PG_REGRESS} SRC_DIR=${PG_SOURCE_DIR}\n      CM_SRC_DIR=${CMAKE_SOURCE_DIR} PG_LIBDIR=${PG_LIBDIR}\n      PG_VERSION_MAJOR=${PG_VERSION_MAJOR} ${PRIMARY_TEST_DIR}/pg_prove.sh\n    USES_TERMINAL)\n  list(APPEND _install_checks provecheck-t)\nelseif(REQUIRE_ALL_TESTS)\n  message(\n    FATAL_ERROR\n      \"All tests were required but TAP_CHECKS was off (see previous messages why)\"\n  )\nendif()\n\nif(PG_ISOLATION_REGRESS)\n  add_custom_target(\n    isolationcheck-t\n    COMMAND\n      ${CMAKE_COMMAND} -E env ${PG_ISOLATION_REGRESS_ENV}\n      EXE_DIR=${CMAKE_CURRENT_SOURCE_DIR}\n      SPECS_DIR=${CMAKE_CURRENT_BINARY_DIR}/isolation/specs\n      TEST_PGPORT=${TEST_PGPORT_TEMP_INSTANCE} ${PRIMARY_TEST_DIR}/pg_regress.sh\n      ${PG_REGRESS_OPTS_BASE} ${PG_ISOLATION_REGRESS_OPTS_EXTRA}\n      ${PG_ISOLATION_REGRESS_OPTS_INOUT} ${PG_REGRESS_OPTS_TEMP_INSTANCE}\n      --temp-config=${TEST_OUTPUT_DIR}/postgresql.conf\n    USES_TERMINAL)\n\n  add_custom_target(\n    isolationcheck-t-rerun\n    COMMAND ${PRIMARY_TEST_DIR}/ci_rerun.sh isolationcheck-t\n    USES_TERMINAL)\n\n  add_custom_target(\n    isolationchecklocal-t\n    COMMAND\n      ${CMAKE_COMMAND} -E env ${PG_ISOLATION_REGRESS_ENV}\n      EXE_DIR=${CMAKE_CURRENT_SOURCE_DIR}\n      SPECS_DIR=${CMAKE_CURRENT_BINARY_DIR}/isolation/specs\n      TEST_PGPORT=${TEST_PGPORT_LOCAL} ${PRIMARY_TEST_DIR}/pg_regress.sh\n      ${PG_REGRESS_OPTS_BASE} ${PG_ISOLATION_REGRESS_OPTS_EXTRA}\n      ${PG_ISOLATION_REGRESS_OPTS_INOUT} ${PG_REGRESS_OPTS_LOCAL_INSTANCE}\n    USES_TERMINAL)\n\n  list(APPEND _local_install_checks isolationchecklocal-t)\n  list(APPEND _install_checks isolationcheck-t)\nelseif(REQUIRE_ALL_TESTS)\n  message(\n    FATAL_ERROR\n      \"All tests were required but 'pg_isolation_regress' could not be found\")\nendif()\n\nadd_subdirectory(shared)\nadd_subdirectory(sql)\nadd_subdirectory(isolation)\nadd_subdirectory(t)\n\n# installchecklocal tests against an existing postgres instance\nif(_local_install_checks)\n  add_custom_target(installchecklocal-t DEPENDS ${_local_install_checks})\n  add_dependencies(installchecklocal installchecklocal-t)\nendif()\n\nif(_install_checks)\n  add_custom_target(installcheck-t DEPENDS ${_install_checks})\n  add_dependencies(installcheck installcheck-t)\nendif()\n\nif(CMAKE_BUILD_TYPE MATCHES Debug OR COMPRESSION_FUZZING)\n  add_subdirectory(src)\nendif()\n"
  },
  {
    "path": "tsl/test/expected/agg_partials_pushdown.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set PREFIX 'EXPLAIN (analyze, verbose, buffers off, costs off, timing off, summary off)'\n-- Make parallel plans predictable\nSET max_parallel_workers_per_gather = 1;\nSET parallel_leader_participation = off;\nCREATE TABLE testtable(filter_1 int, filler_2 int, filler_3 int, time timestamptz NOT NULL, device_id int, v0 int, v1 int, v2 float, v3 float);\nSELECT create_hypertable('testtable', 'time');\n   create_hypertable    \n------------------------\n (1,public,testtable,t)\n\nALTER TABLE testtable SET (timescaledb.compress, timescaledb.compress_orderby='time DESC', timescaledb.compress_segmentby='device_id');\nINSERT INTO testtable(time,device_id,v0,v1,v2,v3)\nSELECT time, device_id, device_id+1,  device_id + 2, device_id + 0.5, NULL\nFROM generate_series('2000-01-01 0:00:00+0'::timestamptz,'2000-01-10 23:55:00+0','1day') gtime(time), generate_series(1,5,1) gdevice(device_id);\nSELECT compress_chunk(c) FROM show_chunks('testtable') c;\n             compress_chunk             \n----------------------------------------\n _timescaledb_internal._hyper_1_1_chunk\n _timescaledb_internal._hyper_1_2_chunk\n\nANALYZE testtable;\n-- Pushdown aggregation to the chunk level\nSELECT count(*), sum(v0), sum(v1), sum(v2), sum(v3) FROM testtable WHERE time >= '2000-01-01 00:00:00+0' AND time <= '2000-02-01 00:00:00+0';\n count | sum | sum | sum | sum \n-------+-----+-----+-----+-----\n    50 | 200 | 250 | 175 |    \n\n:PREFIX\nSELECT count(*), sum(v0), sum(v1), sum(v2), sum(v3) FROM testtable WHERE time >= '2000-01-01 00:00:00+0' AND time <= '2000-02-01 00:00:00+0';\n--- QUERY PLAN ---\n Finalize Aggregate (actual rows=1.00 loops=1)\n   Output: count(*), sum(testtable.v0), sum(testtable.v1), sum(testtable.v2), sum(testtable.v3)\n   ->  Append (actual rows=2.00 loops=1)\n         ->  Custom Scan (VectorAgg) (actual rows=1.00 loops=1)\n               Output: (PARTIAL count(*)), (PARTIAL sum(_hyper_1_1_chunk.v0)), (PARTIAL sum(_hyper_1_1_chunk.v1)), (PARTIAL sum(_hyper_1_1_chunk.v2)), (PARTIAL sum(_hyper_1_1_chunk.v3))\n               Grouping Policy: all compressed batches\n               ->  Custom Scan (ColumnarScan) on _timescaledb_internal._hyper_1_1_chunk (actual rows=25.00 loops=1)\n                     Output: _hyper_1_1_chunk.v0, _hyper_1_1_chunk.v1, _hyper_1_1_chunk.v2, _hyper_1_1_chunk.v3\n                     Vectorized Filter: ((_hyper_1_1_chunk.\"time\" >= 'Fri Dec 31 16:00:00 1999 PST'::timestamp with time zone) AND (_hyper_1_1_chunk.\"time\" <= 'Mon Jan 31 16:00:00 2000 PST'::timestamp with time zone))\n                     Bulk Decompression: true\n                     ->  Seq Scan on _timescaledb_internal.compress_hyper_2_3_chunk (actual rows=5.00 loops=1)\n                           Output: compress_hyper_2_3_chunk._ts_meta_count, compress_hyper_2_3_chunk.device_id, compress_hyper_2_3_chunk.filter_1, compress_hyper_2_3_chunk.filler_2, compress_hyper_2_3_chunk.filler_3, compress_hyper_2_3_chunk._ts_meta_min_1, compress_hyper_2_3_chunk._ts_meta_max_1, compress_hyper_2_3_chunk.\"time\", compress_hyper_2_3_chunk.v0, compress_hyper_2_3_chunk.v1, compress_hyper_2_3_chunk.v2, compress_hyper_2_3_chunk.v3\n                           Filter: ((compress_hyper_2_3_chunk._ts_meta_max_1 >= 'Fri Dec 31 16:00:00 1999 PST'::timestamp with time zone) AND (compress_hyper_2_3_chunk._ts_meta_min_1 <= 'Mon Jan 31 16:00:00 2000 PST'::timestamp with time zone))\n         ->  Custom Scan (VectorAgg) (actual rows=1.00 loops=1)\n               Output: (PARTIAL count(*)), (PARTIAL sum(_hyper_1_2_chunk.v0)), (PARTIAL sum(_hyper_1_2_chunk.v1)), (PARTIAL sum(_hyper_1_2_chunk.v2)), (PARTIAL sum(_hyper_1_2_chunk.v3))\n               Grouping Policy: all compressed batches\n               ->  Custom Scan (ColumnarScan) on _timescaledb_internal._hyper_1_2_chunk (actual rows=25.00 loops=1)\n                     Output: _hyper_1_2_chunk.v0, _hyper_1_2_chunk.v1, _hyper_1_2_chunk.v2, _hyper_1_2_chunk.v3\n                     Bulk Decompression: true\n                     ->  Seq Scan on _timescaledb_internal.compress_hyper_2_4_chunk (actual rows=5.00 loops=1)\n                           Output: compress_hyper_2_4_chunk._ts_meta_count, compress_hyper_2_4_chunk.device_id, compress_hyper_2_4_chunk.filter_1, compress_hyper_2_4_chunk.filler_2, compress_hyper_2_4_chunk.filler_3, compress_hyper_2_4_chunk._ts_meta_min_1, compress_hyper_2_4_chunk._ts_meta_max_1, compress_hyper_2_4_chunk.\"time\", compress_hyper_2_4_chunk.v0, compress_hyper_2_4_chunk.v1, compress_hyper_2_4_chunk.v2, compress_hyper_2_4_chunk.v3\n\n-- Create partially compressed chunk\nINSERT INTO testtable(time,device_id,v0,v1,v2,v3)\nSELECT time, device_id, device_id+1,  device_id + 2, device_id + 0.5, NULL\nFROM generate_series('2000-01-01 0:00:00+0'::timestamptz,'2000-01-10 23:55:00+0','1day') gtime(time), generate_series(1,5,1) gdevice(device_id);\nANALYZE testtable;\n-- Pushdown aggregation to the chunk level\nSELECT count(*), sum(v0), sum(v1), sum(v2), sum(v3) FROM testtable WHERE time >= '2000-01-01 00:00:00+0' AND time <= '2000-02-01 00:00:00+0';\n count | sum | sum | sum | sum \n-------+-----+-----+-----+-----\n   100 | 400 | 500 | 350 |    \n\n:PREFIX\nSELECT count(*), sum(v0), sum(v1), sum(v2), sum(v3) FROM testtable WHERE time >= '2000-01-01 00:00:00+0' AND time <= '2000-02-01 00:00:00+0';\n--- QUERY PLAN ---\n Finalize Aggregate (actual rows=1.00 loops=1)\n   Output: count(*), sum(testtable.v0), sum(testtable.v1), sum(testtable.v2), sum(testtable.v3)\n   ->  Append (actual rows=4.00 loops=1)\n         ->  Custom Scan (VectorAgg) (actual rows=1.00 loops=1)\n               Output: (PARTIAL count(*)), (PARTIAL sum(_hyper_1_1_chunk.v0)), (PARTIAL sum(_hyper_1_1_chunk.v1)), (PARTIAL sum(_hyper_1_1_chunk.v2)), (PARTIAL sum(_hyper_1_1_chunk.v3))\n               Grouping Policy: all compressed batches\n               ->  Custom Scan (ColumnarScan) on _timescaledb_internal._hyper_1_1_chunk (actual rows=25.00 loops=1)\n                     Output: _hyper_1_1_chunk.v0, _hyper_1_1_chunk.v1, _hyper_1_1_chunk.v2, _hyper_1_1_chunk.v3\n                     Vectorized Filter: ((_hyper_1_1_chunk.\"time\" >= 'Fri Dec 31 16:00:00 1999 PST'::timestamp with time zone) AND (_hyper_1_1_chunk.\"time\" <= 'Mon Jan 31 16:00:00 2000 PST'::timestamp with time zone))\n                     Chunk Status: PARTIAL\n                     Bulk Decompression: true\n                     ->  Seq Scan on _timescaledb_internal.compress_hyper_2_3_chunk (actual rows=5.00 loops=1)\n                           Output: compress_hyper_2_3_chunk._ts_meta_count, compress_hyper_2_3_chunk.device_id, compress_hyper_2_3_chunk.filter_1, compress_hyper_2_3_chunk.filler_2, compress_hyper_2_3_chunk.filler_3, compress_hyper_2_3_chunk._ts_meta_min_1, compress_hyper_2_3_chunk._ts_meta_max_1, compress_hyper_2_3_chunk.\"time\", compress_hyper_2_3_chunk.v0, compress_hyper_2_3_chunk.v1, compress_hyper_2_3_chunk.v2, compress_hyper_2_3_chunk.v3\n                           Filter: ((compress_hyper_2_3_chunk._ts_meta_max_1 >= 'Fri Dec 31 16:00:00 1999 PST'::timestamp with time zone) AND (compress_hyper_2_3_chunk._ts_meta_min_1 <= 'Mon Jan 31 16:00:00 2000 PST'::timestamp with time zone))\n         ->  Partial Aggregate (actual rows=1.00 loops=1)\n               Output: PARTIAL count(*), PARTIAL sum(_hyper_1_1_chunk.v0), PARTIAL sum(_hyper_1_1_chunk.v1), PARTIAL sum(_hyper_1_1_chunk.v2), PARTIAL sum(_hyper_1_1_chunk.v3)\n               ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk (actual rows=25.00 loops=1)\n                     Output: _hyper_1_1_chunk.v0, _hyper_1_1_chunk.v1, _hyper_1_1_chunk.v2, _hyper_1_1_chunk.v3\n                     Filter: ((_hyper_1_1_chunk.\"time\" >= 'Fri Dec 31 16:00:00 1999 PST'::timestamp with time zone) AND (_hyper_1_1_chunk.\"time\" <= 'Mon Jan 31 16:00:00 2000 PST'::timestamp with time zone))\n         ->  Custom Scan (VectorAgg) (actual rows=1.00 loops=1)\n               Output: (PARTIAL count(*)), (PARTIAL sum(_hyper_1_2_chunk.v0)), (PARTIAL sum(_hyper_1_2_chunk.v1)), (PARTIAL sum(_hyper_1_2_chunk.v2)), (PARTIAL sum(_hyper_1_2_chunk.v3))\n               Grouping Policy: all compressed batches\n               ->  Custom Scan (ColumnarScan) on _timescaledb_internal._hyper_1_2_chunk (actual rows=25.00 loops=1)\n                     Output: _hyper_1_2_chunk.v0, _hyper_1_2_chunk.v1, _hyper_1_2_chunk.v2, _hyper_1_2_chunk.v3\n                     Chunk Status: PARTIAL\n                     Bulk Decompression: true\n                     ->  Seq Scan on _timescaledb_internal.compress_hyper_2_4_chunk (actual rows=5.00 loops=1)\n                           Output: compress_hyper_2_4_chunk._ts_meta_count, compress_hyper_2_4_chunk.device_id, compress_hyper_2_4_chunk.filter_1, compress_hyper_2_4_chunk.filler_2, compress_hyper_2_4_chunk.filler_3, compress_hyper_2_4_chunk._ts_meta_min_1, compress_hyper_2_4_chunk._ts_meta_max_1, compress_hyper_2_4_chunk.\"time\", compress_hyper_2_4_chunk.v0, compress_hyper_2_4_chunk.v1, compress_hyper_2_4_chunk.v2, compress_hyper_2_4_chunk.v3\n         ->  Partial Aggregate (actual rows=1.00 loops=1)\n               Output: PARTIAL count(*), PARTIAL sum(_hyper_1_2_chunk.v0), PARTIAL sum(_hyper_1_2_chunk.v1), PARTIAL sum(_hyper_1_2_chunk.v2), PARTIAL sum(_hyper_1_2_chunk.v3)\n               ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk (actual rows=25.00 loops=1)\n                     Output: _hyper_1_2_chunk.v0, _hyper_1_2_chunk.v1, _hyper_1_2_chunk.v2, _hyper_1_2_chunk.v3\n\n-- Same query using chunk append\nSELECT count(*), sum(v0), sum(v1), sum(v2), sum(v3) FROM testtable WHERE time >= '2000-01-01 00:00:00+0'::text::timestamptz AND time <= '2000-02-01 00:00:00+0';\n count | sum | sum | sum | sum \n-------+-----+-----+-----+-----\n   100 | 400 | 500 | 350 |    \n\n:PREFIX\nSELECT count(*), sum(v0), sum(v1), sum(v2), sum(v3) FROM testtable WHERE time >= '2000-01-01 00:00:00+0'::text::timestamptz AND time <= '2000-02-01 00:00:00+0';\n--- QUERY PLAN ---\n Finalize Aggregate (actual rows=1.00 loops=1)\n   Output: count(*), sum(testtable.v0), sum(testtable.v1), sum(testtable.v2), sum(testtable.v3)\n   ->  Custom Scan (ChunkAppend) on public.testtable (actual rows=4.00 loops=1)\n         Output: (PARTIAL count(*)), (PARTIAL sum(testtable.v0)), (PARTIAL sum(testtable.v1)), (PARTIAL sum(testtable.v2)), (PARTIAL sum(testtable.v3))\n         Startup Exclusion: true\n         Runtime Exclusion: false\n         Chunks excluded during startup: 0\n         ->  Custom Scan (VectorAgg) (actual rows=1.00 loops=1)\n               Output: (PARTIAL count(*)), (PARTIAL sum(_hyper_1_1_chunk.v0)), (PARTIAL sum(_hyper_1_1_chunk.v1)), (PARTIAL sum(_hyper_1_1_chunk.v2)), (PARTIAL sum(_hyper_1_1_chunk.v3))\n               Grouping Policy: all compressed batches\n               ->  Custom Scan (ColumnarScan) on _timescaledb_internal._hyper_1_1_chunk (actual rows=25.00 loops=1)\n                     Output: _hyper_1_1_chunk.v0, _hyper_1_1_chunk.v1, _hyper_1_1_chunk.v2, _hyper_1_1_chunk.v3\n                     Vectorized Filter: (_hyper_1_1_chunk.\"time\" >= ('2000-01-01 00:00:00+0'::cstring)::timestamp with time zone)\n                     Chunk Status: PARTIAL\n                     Bulk Decompression: true\n                     ->  Seq Scan on _timescaledb_internal.compress_hyper_2_3_chunk (actual rows=5.00 loops=1)\n                           Output: compress_hyper_2_3_chunk._ts_meta_count, compress_hyper_2_3_chunk.device_id, compress_hyper_2_3_chunk.filter_1, compress_hyper_2_3_chunk.filler_2, compress_hyper_2_3_chunk.filler_3, compress_hyper_2_3_chunk._ts_meta_min_1, compress_hyper_2_3_chunk._ts_meta_max_1, compress_hyper_2_3_chunk.\"time\", compress_hyper_2_3_chunk.v0, compress_hyper_2_3_chunk.v1, compress_hyper_2_3_chunk.v2, compress_hyper_2_3_chunk.v3\n                           Filter: (compress_hyper_2_3_chunk._ts_meta_max_1 >= ('2000-01-01 00:00:00+0'::cstring)::timestamp with time zone)\n         ->  Partial Aggregate (actual rows=1.00 loops=1)\n               Output: PARTIAL count(*), PARTIAL sum(_hyper_1_1_chunk.v0), PARTIAL sum(_hyper_1_1_chunk.v1), PARTIAL sum(_hyper_1_1_chunk.v2), PARTIAL sum(_hyper_1_1_chunk.v3)\n               ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk (actual rows=25.00 loops=1)\n                     Output: _hyper_1_1_chunk.v0, _hyper_1_1_chunk.v1, _hyper_1_1_chunk.v2, _hyper_1_1_chunk.v3\n                     Filter: (_hyper_1_1_chunk.\"time\" >= ('2000-01-01 00:00:00+0'::cstring)::timestamp with time zone)\n         ->  Custom Scan (VectorAgg) (actual rows=1.00 loops=1)\n               Output: (PARTIAL count(*)), (PARTIAL sum(_hyper_1_2_chunk.v0)), (PARTIAL sum(_hyper_1_2_chunk.v1)), (PARTIAL sum(_hyper_1_2_chunk.v2)), (PARTIAL sum(_hyper_1_2_chunk.v3))\n               Grouping Policy: all compressed batches\n               ->  Custom Scan (ColumnarScan) on _timescaledb_internal._hyper_1_2_chunk (actual rows=25.00 loops=1)\n                     Output: _hyper_1_2_chunk.v0, _hyper_1_2_chunk.v1, _hyper_1_2_chunk.v2, _hyper_1_2_chunk.v3\n                     Vectorized Filter: (_hyper_1_2_chunk.\"time\" >= ('2000-01-01 00:00:00+0'::cstring)::timestamp with time zone)\n                     Chunk Status: PARTIAL\n                     Bulk Decompression: true\n                     ->  Seq Scan on _timescaledb_internal.compress_hyper_2_4_chunk (actual rows=5.00 loops=1)\n                           Output: compress_hyper_2_4_chunk._ts_meta_count, compress_hyper_2_4_chunk.device_id, compress_hyper_2_4_chunk.filter_1, compress_hyper_2_4_chunk.filler_2, compress_hyper_2_4_chunk.filler_3, compress_hyper_2_4_chunk._ts_meta_min_1, compress_hyper_2_4_chunk._ts_meta_max_1, compress_hyper_2_4_chunk.\"time\", compress_hyper_2_4_chunk.v0, compress_hyper_2_4_chunk.v1, compress_hyper_2_4_chunk.v2, compress_hyper_2_4_chunk.v3\n                           Filter: (compress_hyper_2_4_chunk._ts_meta_max_1 >= ('2000-01-01 00:00:00+0'::cstring)::timestamp with time zone)\n         ->  Partial Aggregate (actual rows=1.00 loops=1)\n               Output: PARTIAL count(*), PARTIAL sum(_hyper_1_2_chunk.v0), PARTIAL sum(_hyper_1_2_chunk.v1), PARTIAL sum(_hyper_1_2_chunk.v2), PARTIAL sum(_hyper_1_2_chunk.v3)\n               ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk (actual rows=25.00 loops=1)\n                     Output: _hyper_1_2_chunk.v0, _hyper_1_2_chunk.v1, _hyper_1_2_chunk.v2, _hyper_1_2_chunk.v3\n                     Filter: (_hyper_1_2_chunk.\"time\" >= ('2000-01-01 00:00:00+0'::cstring)::timestamp with time zone)\n\n-- Perform chunk append startup chunk exclusion - issue 6282\n:PREFIX\nSELECT count(*), sum(v0), sum(v1), sum(v2), sum(v3) FROM testtable WHERE time >= '2000-01-09 00:00:00+0'::text::timestamptz AND time <= '2000-02-01 00:00:00+0'::text::timestamptz;\n--- QUERY PLAN ---\n Finalize Aggregate (actual rows=1.00 loops=1)\n   Output: count(*), sum(testtable.v0), sum(testtable.v1), sum(testtable.v2), sum(testtable.v3)\n   ->  Custom Scan (ChunkAppend) on public.testtable (actual rows=2.00 loops=1)\n         Output: (PARTIAL count(*)), (PARTIAL sum(testtable.v0)), (PARTIAL sum(testtable.v1)), (PARTIAL sum(testtable.v2)), (PARTIAL sum(testtable.v3))\n         Startup Exclusion: true\n         Runtime Exclusion: false\n         Chunks excluded during startup: 2\n         ->  Custom Scan (VectorAgg) (actual rows=1.00 loops=1)\n               Output: (PARTIAL count(*)), (PARTIAL sum(_hyper_1_2_chunk.v0)), (PARTIAL sum(_hyper_1_2_chunk.v1)), (PARTIAL sum(_hyper_1_2_chunk.v2)), (PARTIAL sum(_hyper_1_2_chunk.v3))\n               Grouping Policy: all compressed batches\n               ->  Custom Scan (ColumnarScan) on _timescaledb_internal._hyper_1_2_chunk (actual rows=10.00 loops=1)\n                     Output: _hyper_1_2_chunk.v0, _hyper_1_2_chunk.v1, _hyper_1_2_chunk.v2, _hyper_1_2_chunk.v3\n                     Vectorized Filter: ((_hyper_1_2_chunk.\"time\" >= ('2000-01-09 00:00:00+0'::cstring)::timestamp with time zone) AND (_hyper_1_2_chunk.\"time\" <= ('2000-02-01 00:00:00+0'::cstring)::timestamp with time zone))\n                     Rows Removed by Filter: 15\n                     Chunk Status: PARTIAL\n                     Bulk Decompression: true\n                     ->  Seq Scan on _timescaledb_internal.compress_hyper_2_4_chunk (actual rows=5.00 loops=1)\n                           Output: compress_hyper_2_4_chunk._ts_meta_count, compress_hyper_2_4_chunk.device_id, compress_hyper_2_4_chunk.filter_1, compress_hyper_2_4_chunk.filler_2, compress_hyper_2_4_chunk.filler_3, compress_hyper_2_4_chunk._ts_meta_min_1, compress_hyper_2_4_chunk._ts_meta_max_1, compress_hyper_2_4_chunk.\"time\", compress_hyper_2_4_chunk.v0, compress_hyper_2_4_chunk.v1, compress_hyper_2_4_chunk.v2, compress_hyper_2_4_chunk.v3\n                           Filter: ((compress_hyper_2_4_chunk._ts_meta_max_1 >= ('2000-01-09 00:00:00+0'::cstring)::timestamp with time zone) AND (compress_hyper_2_4_chunk._ts_meta_min_1 <= ('2000-02-01 00:00:00+0'::cstring)::timestamp with time zone))\n         ->  Partial Aggregate (actual rows=1.00 loops=1)\n               Output: PARTIAL count(*), PARTIAL sum(_hyper_1_2_chunk.v0), PARTIAL sum(_hyper_1_2_chunk.v1), PARTIAL sum(_hyper_1_2_chunk.v2), PARTIAL sum(_hyper_1_2_chunk.v3)\n               ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk (actual rows=10.00 loops=1)\n                     Output: _hyper_1_2_chunk.v0, _hyper_1_2_chunk.v1, _hyper_1_2_chunk.v2, _hyper_1_2_chunk.v3\n                     Filter: ((_hyper_1_2_chunk.\"time\" >= ('2000-01-09 00:00:00+0'::cstring)::timestamp with time zone) AND (_hyper_1_2_chunk.\"time\" <= ('2000-02-01 00:00:00+0'::cstring)::timestamp with time zone))\n                     Rows Removed by Filter: 15\n\n-- Force plain / sorted aggregation\nSET enable_hashagg = OFF;\nSELECT count(*), sum(v0), sum(v1), sum(v2), sum(v3) FROM testtable WHERE time >= '2000-01-01 00:00:00+0'::text::timestamptz AND time <= '2000-02-01 00:00:00+0';\n count | sum | sum | sum | sum \n-------+-----+-----+-----+-----\n   100 | 400 | 500 | 350 |    \n\n:PREFIX\nSELECT count(*), sum(v0), sum(v1), sum(v2), sum(v3) FROM testtable WHERE time >= '2000-01-01 00:00:00+0'::text::timestamptz AND time <= '2000-02-01 00:00:00+0';\n--- QUERY PLAN ---\n Finalize Aggregate (actual rows=1.00 loops=1)\n   Output: count(*), sum(testtable.v0), sum(testtable.v1), sum(testtable.v2), sum(testtable.v3)\n   ->  Custom Scan (ChunkAppend) on public.testtable (actual rows=4.00 loops=1)\n         Output: (PARTIAL count(*)), (PARTIAL sum(testtable.v0)), (PARTIAL sum(testtable.v1)), (PARTIAL sum(testtable.v2)), (PARTIAL sum(testtable.v3))\n         Startup Exclusion: true\n         Runtime Exclusion: false\n         Chunks excluded during startup: 0\n         ->  Custom Scan (VectorAgg) (actual rows=1.00 loops=1)\n               Output: (PARTIAL count(*)), (PARTIAL sum(_hyper_1_1_chunk.v0)), (PARTIAL sum(_hyper_1_1_chunk.v1)), (PARTIAL sum(_hyper_1_1_chunk.v2)), (PARTIAL sum(_hyper_1_1_chunk.v3))\n               Grouping Policy: all compressed batches\n               ->  Custom Scan (ColumnarScan) on _timescaledb_internal._hyper_1_1_chunk (actual rows=25.00 loops=1)\n                     Output: _hyper_1_1_chunk.v0, _hyper_1_1_chunk.v1, _hyper_1_1_chunk.v2, _hyper_1_1_chunk.v3\n                     Vectorized Filter: (_hyper_1_1_chunk.\"time\" >= ('2000-01-01 00:00:00+0'::cstring)::timestamp with time zone)\n                     Chunk Status: PARTIAL\n                     Bulk Decompression: true\n                     ->  Seq Scan on _timescaledb_internal.compress_hyper_2_3_chunk (actual rows=5.00 loops=1)\n                           Output: compress_hyper_2_3_chunk._ts_meta_count, compress_hyper_2_3_chunk.device_id, compress_hyper_2_3_chunk.filter_1, compress_hyper_2_3_chunk.filler_2, compress_hyper_2_3_chunk.filler_3, compress_hyper_2_3_chunk._ts_meta_min_1, compress_hyper_2_3_chunk._ts_meta_max_1, compress_hyper_2_3_chunk.\"time\", compress_hyper_2_3_chunk.v0, compress_hyper_2_3_chunk.v1, compress_hyper_2_3_chunk.v2, compress_hyper_2_3_chunk.v3\n                           Filter: (compress_hyper_2_3_chunk._ts_meta_max_1 >= ('2000-01-01 00:00:00+0'::cstring)::timestamp with time zone)\n         ->  Partial Aggregate (actual rows=1.00 loops=1)\n               Output: PARTIAL count(*), PARTIAL sum(_hyper_1_1_chunk.v0), PARTIAL sum(_hyper_1_1_chunk.v1), PARTIAL sum(_hyper_1_1_chunk.v2), PARTIAL sum(_hyper_1_1_chunk.v3)\n               ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk (actual rows=25.00 loops=1)\n                     Output: _hyper_1_1_chunk.v0, _hyper_1_1_chunk.v1, _hyper_1_1_chunk.v2, _hyper_1_1_chunk.v3\n                     Filter: (_hyper_1_1_chunk.\"time\" >= ('2000-01-01 00:00:00+0'::cstring)::timestamp with time zone)\n         ->  Custom Scan (VectorAgg) (actual rows=1.00 loops=1)\n               Output: (PARTIAL count(*)), (PARTIAL sum(_hyper_1_2_chunk.v0)), (PARTIAL sum(_hyper_1_2_chunk.v1)), (PARTIAL sum(_hyper_1_2_chunk.v2)), (PARTIAL sum(_hyper_1_2_chunk.v3))\n               Grouping Policy: all compressed batches\n               ->  Custom Scan (ColumnarScan) on _timescaledb_internal._hyper_1_2_chunk (actual rows=25.00 loops=1)\n                     Output: _hyper_1_2_chunk.v0, _hyper_1_2_chunk.v1, _hyper_1_2_chunk.v2, _hyper_1_2_chunk.v3\n                     Vectorized Filter: (_hyper_1_2_chunk.\"time\" >= ('2000-01-01 00:00:00+0'::cstring)::timestamp with time zone)\n                     Chunk Status: PARTIAL\n                     Bulk Decompression: true\n                     ->  Seq Scan on _timescaledb_internal.compress_hyper_2_4_chunk (actual rows=5.00 loops=1)\n                           Output: compress_hyper_2_4_chunk._ts_meta_count, compress_hyper_2_4_chunk.device_id, compress_hyper_2_4_chunk.filter_1, compress_hyper_2_4_chunk.filler_2, compress_hyper_2_4_chunk.filler_3, compress_hyper_2_4_chunk._ts_meta_min_1, compress_hyper_2_4_chunk._ts_meta_max_1, compress_hyper_2_4_chunk.\"time\", compress_hyper_2_4_chunk.v0, compress_hyper_2_4_chunk.v1, compress_hyper_2_4_chunk.v2, compress_hyper_2_4_chunk.v3\n                           Filter: (compress_hyper_2_4_chunk._ts_meta_max_1 >= ('2000-01-01 00:00:00+0'::cstring)::timestamp with time zone)\n         ->  Partial Aggregate (actual rows=1.00 loops=1)\n               Output: PARTIAL count(*), PARTIAL sum(_hyper_1_2_chunk.v0), PARTIAL sum(_hyper_1_2_chunk.v1), PARTIAL sum(_hyper_1_2_chunk.v2), PARTIAL sum(_hyper_1_2_chunk.v3)\n               ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk (actual rows=25.00 loops=1)\n                     Output: _hyper_1_2_chunk.v0, _hyper_1_2_chunk.v1, _hyper_1_2_chunk.v2, _hyper_1_2_chunk.v3\n                     Filter: (_hyper_1_2_chunk.\"time\" >= ('2000-01-01 00:00:00+0'::cstring)::timestamp with time zone)\n\nRESET enable_hashagg;\n-- Check chunk exclusion for index scans\nSET enable_seqscan = OFF;\nSELECT count(*), sum(v0), sum(v1), sum(v2), sum(v3) FROM testtable WHERE time >= '2000-01-09 00:00:00+0'::text::timestamptz AND time <= '2000-02-01 00:00:00+0'::text::timestamptz;\n count | sum | sum | sum | sum \n-------+-----+-----+-----+-----\n    20 |  80 | 100 |  70 |    \n\n:PREFIX\nSELECT count(*), sum(v0), sum(v1), sum(v2), sum(v3) FROM testtable WHERE time >= '2000-01-09 00:00:00+0'::text::timestamptz AND time <= '2000-02-01 00:00:00+0'::text::timestamptz;\n--- QUERY PLAN ---\n Finalize Aggregate (actual rows=1.00 loops=1)\n   Output: count(*), sum(testtable.v0), sum(testtable.v1), sum(testtable.v2), sum(testtable.v3)\n   ->  Custom Scan (ChunkAppend) on public.testtable (actual rows=2.00 loops=1)\n         Output: (PARTIAL count(*)), (PARTIAL sum(testtable.v0)), (PARTIAL sum(testtable.v1)), (PARTIAL sum(testtable.v2)), (PARTIAL sum(testtable.v3))\n         Startup Exclusion: true\n         Runtime Exclusion: false\n         Chunks excluded during startup: 2\n         ->  Custom Scan (VectorAgg) (actual rows=1.00 loops=1)\n               Output: (PARTIAL count(*)), (PARTIAL sum(_hyper_1_2_chunk.v0)), (PARTIAL sum(_hyper_1_2_chunk.v1)), (PARTIAL sum(_hyper_1_2_chunk.v2)), (PARTIAL sum(_hyper_1_2_chunk.v3))\n               Grouping Policy: all compressed batches\n               ->  Custom Scan (ColumnarScan) on _timescaledb_internal._hyper_1_2_chunk (actual rows=10.00 loops=1)\n                     Output: _hyper_1_2_chunk.v0, _hyper_1_2_chunk.v1, _hyper_1_2_chunk.v2, _hyper_1_2_chunk.v3\n                     Vectorized Filter: ((_hyper_1_2_chunk.\"time\" >= ('2000-01-09 00:00:00+0'::cstring)::timestamp with time zone) AND (_hyper_1_2_chunk.\"time\" <= ('2000-02-01 00:00:00+0'::cstring)::timestamp with time zone))\n                     Rows Removed by Filter: 15\n                     Chunk Status: PARTIAL\n                     Bulk Decompression: true\n                     ->  Index Scan using compress_hyper_2_4_chunk_device_id__ts_meta_min_1__ts_meta__idx on _timescaledb_internal.compress_hyper_2_4_chunk (actual rows=5.00 loops=1)\n                           Output: compress_hyper_2_4_chunk._ts_meta_count, compress_hyper_2_4_chunk.device_id, compress_hyper_2_4_chunk.filter_1, compress_hyper_2_4_chunk.filler_2, compress_hyper_2_4_chunk.filler_3, compress_hyper_2_4_chunk._ts_meta_min_1, compress_hyper_2_4_chunk._ts_meta_max_1, compress_hyper_2_4_chunk.\"time\", compress_hyper_2_4_chunk.v0, compress_hyper_2_4_chunk.v1, compress_hyper_2_4_chunk.v2, compress_hyper_2_4_chunk.v3\n                           Index Cond: ((compress_hyper_2_4_chunk._ts_meta_min_1 <= ('2000-02-01 00:00:00+0'::cstring)::timestamp with time zone) AND (compress_hyper_2_4_chunk._ts_meta_max_1 >= ('2000-01-09 00:00:00+0'::cstring)::timestamp with time zone))\n         ->  Partial Aggregate (actual rows=1.00 loops=1)\n               Output: PARTIAL count(*), PARTIAL sum(_hyper_1_2_chunk.v0), PARTIAL sum(_hyper_1_2_chunk.v1), PARTIAL sum(_hyper_1_2_chunk.v2), PARTIAL sum(_hyper_1_2_chunk.v3)\n               ->  Index Scan using _hyper_1_2_chunk_testtable_time_idx on _timescaledb_internal._hyper_1_2_chunk (actual rows=10.00 loops=1)\n                     Output: _hyper_1_2_chunk.v0, _hyper_1_2_chunk.v1, _hyper_1_2_chunk.v2, _hyper_1_2_chunk.v3\n                     Index Cond: ((_hyper_1_2_chunk.\"time\" >= ('2000-01-09 00:00:00+0'::cstring)::timestamp with time zone) AND (_hyper_1_2_chunk.\"time\" <= ('2000-02-01 00:00:00+0'::cstring)::timestamp with time zone))\n\nRESET enable_seqscan;\n-- Check Append Node under ChunkAppend\nRESET enable_hashagg;\nRESET timescaledb.enable_chunkwise_aggregation;\nCREATE TABLE testtable2 (\n  timecustom BIGINT NOT NULL,\n  device_id TEXT NOT NULL,\n  series_0 DOUBLE PRECISION NULL,\n  series_1 DOUBLE PRECISION NULL,\n  series_2 DOUBLE PRECISION NULL,\n  series_bool BOOLEAN NULL\n);\nCREATE INDEX ON testtable2 (timeCustom DESC NULLS LAST, device_id);\nSELECT * FROM create_hypertable('testtable2', 'timecustom', 'device_id', number_partitions => 2, chunk_time_interval=>_timescaledb_functions.interval_to_usec('1 month'));\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             3 | public      | testtable2 | t\n\nINSERT INTO testtable2 VALUES\n(1257894000000000000, 'dev1', 1.5, 1, 2, true),\n(1257894000000000000, 'dev1', 1.5, 2, NULL, NULL),\n(1257894000000001000, 'dev1', 2.5, 3, NULL, NULL),\n(1257894001000000000, 'dev1', 3.5, 4, NULL, NULL),\n(1257897600000000000, 'dev1', 4.5, 5, NULL, false),\n(1257894002000000000, 'dev1', 5.5, 6, NULL, true),\n(1257894002000000000, 'dev1', 5.5, 7, NULL, false);\nINSERT INTO testtable2(timeCustom, device_id, series_0, series_1) VALUES\n(1257987600000000000, 'dev1', 1.5, 1),\n(1257987600000000000, 'dev1', 1.5, 2),\n(1257894000000000000, 'dev2', 1.5, 1),\n(1257894002000000000, 'dev1', 2.5, 3);\nSELECT timeCustom t, min(series_0) FROM PUBLIC.testtable2 GROUP BY t ORDER BY t DESC NULLS LAST limit 2;\n          t          | min \n---------------------+-----\n 1257987600000000000 | 1.5\n 1257897600000000000 | 4.5\n\n:PREFIX\nSELECT timeCustom t, min(series_0) FROM PUBLIC.testtable2 GROUP BY t ORDER BY t DESC NULLS LAST limit 2;\n--- QUERY PLAN ---\n Limit (actual rows=2.00 loops=1)\n   Output: testtable2.timecustom, (min(testtable2.series_0))\n   ->  Finalize GroupAggregate (actual rows=2.00 loops=1)\n         Output: testtable2.timecustom, min(testtable2.series_0)\n         Group Key: testtable2.timecustom\n         ->  Custom Scan (ChunkAppend) on public.testtable2 (actual rows=3.00 loops=1)\n               Output: testtable2.timecustom, (PARTIAL min(testtable2.series_0))\n               Order: testtable2.timecustom DESC NULLS LAST\n               Startup Exclusion: false\n               Runtime Exclusion: false\n               ->  Partial GroupAggregate (actual rows=1.00 loops=1)\n                     Output: _hyper_3_7_chunk.timecustom, PARTIAL min(_hyper_3_7_chunk.series_0)\n                     Group Key: _hyper_3_7_chunk.timecustom\n                     ->  Index Scan using _hyper_3_7_chunk_testtable2_timecustom_device_id_idx on _timescaledb_internal._hyper_3_7_chunk (actual rows=2.00 loops=1)\n                           Output: _hyper_3_7_chunk.timecustom, _hyper_3_7_chunk.series_0\n               ->  Partial GroupAggregate (actual rows=1.00 loops=1)\n                     Output: _hyper_3_6_chunk.timecustom, PARTIAL min(_hyper_3_6_chunk.series_0)\n                     Group Key: _hyper_3_6_chunk.timecustom\n                     ->  Index Scan using _hyper_3_6_chunk_testtable2_timecustom_device_id_idx on _timescaledb_internal._hyper_3_6_chunk (actual rows=1.00 loops=1)\n                           Output: _hyper_3_6_chunk.timecustom, _hyper_3_6_chunk.series_0\n               ->  Merge Append (actual rows=1.00 loops=1)\n                     Sort Key: testtable2.timecustom DESC NULLS LAST\n                     ->  Partial GroupAggregate (actual rows=1.00 loops=1)\n                           Output: _hyper_3_8_chunk.timecustom, PARTIAL min(_hyper_3_8_chunk.series_0)\n                           Group Key: _hyper_3_8_chunk.timecustom\n                           ->  Index Scan using _hyper_3_8_chunk_testtable2_timecustom_device_id_idx on _timescaledb_internal._hyper_3_8_chunk (actual rows=1.00 loops=1)\n                                 Output: _hyper_3_8_chunk.timecustom, _hyper_3_8_chunk.series_0\n                     ->  Partial GroupAggregate (actual rows=1.00 loops=1)\n                           Output: _hyper_3_5_chunk.timecustom, PARTIAL min(_hyper_3_5_chunk.series_0)\n                           Group Key: _hyper_3_5_chunk.timecustom\n                           ->  Index Scan using _hyper_3_5_chunk_testtable2_timecustom_device_id_idx on _timescaledb_internal._hyper_3_5_chunk (actual rows=4.00 loops=1)\n                                 Output: _hyper_3_5_chunk.timecustom, _hyper_3_5_chunk.series_0\n\n-- Force parallel query\nSELECT set_config(CASE WHEN current_setting('server_version_num')::int < 160000 THEN 'force_parallel_mode' ELSE 'debug_parallel_query' END,'on', false);\n set_config \n------------\n on\n\nSET parallel_setup_cost = 0;\nSET parallel_tuple_cost = 0;\nSELECT timeCustom t, min(series_0) FROM PUBLIC.testtable2 GROUP BY t ORDER BY t DESC NULLS LAST limit 2;\n          t          | min \n---------------------+-----\n 1257987600000000000 | 1.5\n 1257897600000000000 | 4.5\n\n:PREFIX\nSELECT timeCustom t, min(series_0) FROM PUBLIC.testtable2 GROUP BY t ORDER BY t DESC NULLS LAST limit 2;\n--- QUERY PLAN ---\n Gather (actual rows=2.00 loops=1)\n   Output: testtable2.timecustom, (min(testtable2.series_0))\n   Workers Planned: 1\n   Workers Launched: 1\n   Single Copy: true\n   ->  Limit (actual rows=2.00 loops=1)\n         Output: testtable2.timecustom, (min(testtable2.series_0))\n         Worker 0:  actual rows=2.00 loops=1\n         ->  Finalize GroupAggregate (actual rows=2.00 loops=1)\n               Output: testtable2.timecustom, min(testtable2.series_0)\n               Group Key: testtable2.timecustom\n               Worker 0:  actual rows=2.00 loops=1\n               ->  Custom Scan (ChunkAppend) on public.testtable2 (actual rows=3.00 loops=1)\n                     Output: testtable2.timecustom, (PARTIAL min(testtable2.series_0))\n                     Order: testtable2.timecustom DESC NULLS LAST\n                     Startup Exclusion: false\n                     Runtime Exclusion: false\n                     Worker 0:  actual rows=3.00 loops=1\n                     ->  Partial GroupAggregate (actual rows=1.00 loops=1)\n                           Output: _hyper_3_7_chunk.timecustom, PARTIAL min(_hyper_3_7_chunk.series_0)\n                           Group Key: _hyper_3_7_chunk.timecustom\n                           Worker 0:  actual rows=1.00 loops=1\n                           ->  Index Scan using _hyper_3_7_chunk_testtable2_timecustom_device_id_idx on _timescaledb_internal._hyper_3_7_chunk (actual rows=2.00 loops=1)\n                                 Output: _hyper_3_7_chunk.timecustom, _hyper_3_7_chunk.series_0\n                                 Worker 0:  actual rows=2.00 loops=1\n                     ->  Partial GroupAggregate (actual rows=1.00 loops=1)\n                           Output: _hyper_3_6_chunk.timecustom, PARTIAL min(_hyper_3_6_chunk.series_0)\n                           Group Key: _hyper_3_6_chunk.timecustom\n                           Worker 0:  actual rows=1.00 loops=1\n                           ->  Index Scan using _hyper_3_6_chunk_testtable2_timecustom_device_id_idx on _timescaledb_internal._hyper_3_6_chunk (actual rows=1.00 loops=1)\n                                 Output: _hyper_3_6_chunk.timecustom, _hyper_3_6_chunk.series_0\n                                 Worker 0:  actual rows=1.00 loops=1\n                     ->  Merge Append (actual rows=1.00 loops=1)\n                           Sort Key: testtable2.timecustom DESC NULLS LAST\n                           Worker 0:  actual rows=1.00 loops=1\n                           ->  Partial GroupAggregate (actual rows=1.00 loops=1)\n                                 Output: _hyper_3_8_chunk.timecustom, PARTIAL min(_hyper_3_8_chunk.series_0)\n                                 Group Key: _hyper_3_8_chunk.timecustom\n                                 Worker 0:  actual rows=1.00 loops=1\n                                 ->  Index Scan using _hyper_3_8_chunk_testtable2_timecustom_device_id_idx on _timescaledb_internal._hyper_3_8_chunk (actual rows=1.00 loops=1)\n                                       Output: _hyper_3_8_chunk.timecustom, _hyper_3_8_chunk.series_0\n                                       Worker 0:  actual rows=1.00 loops=1\n                           ->  Partial GroupAggregate (actual rows=1.00 loops=1)\n                                 Output: _hyper_3_5_chunk.timecustom, PARTIAL min(_hyper_3_5_chunk.series_0)\n                                 Group Key: _hyper_3_5_chunk.timecustom\n                                 Worker 0:  actual rows=1.00 loops=1\n                                 ->  Index Scan using _hyper_3_5_chunk_testtable2_timecustom_device_id_idx on _timescaledb_internal._hyper_3_5_chunk (actual rows=4.00 loops=1)\n                                       Output: _hyper_3_5_chunk.timecustom, _hyper_3_5_chunk.series_0\n                                       Worker 0:  actual rows=4.00 loops=1\n\n-- Test that we don't process groupingSets\n:PREFIX\nSELECT timeCustom t, min(series_0) FROM PUBLIC.testtable2 GROUP BY ROLLUP(t);\n--- QUERY PLAN ---\n Gather (actual rows=7.00 loops=1)\n   Output: testtable2.timecustom, (min(testtable2.series_0))\n   Workers Planned: 1\n   Workers Launched: 1\n   Single Copy: true\n   ->  MixedAggregate (actual rows=7.00 loops=1)\n         Output: testtable2.timecustom, min(testtable2.series_0)\n         Hash Key: testtable2.timecustom\n         Group Key: ()\n         Worker 0:  actual rows=7.00 loops=1\n         ->  Append (actual rows=11.00 loops=1)\n               Worker 0:  actual rows=11.00 loops=1\n               ->  Seq Scan on _timescaledb_internal._hyper_3_5_chunk (actual rows=7.00 loops=1)\n                     Output: _hyper_3_5_chunk.timecustom, _hyper_3_5_chunk.series_0\n                     Worker 0:  actual rows=7.00 loops=1\n               ->  Seq Scan on _timescaledb_internal._hyper_3_6_chunk (actual rows=1.00 loops=1)\n                     Output: _hyper_3_6_chunk.timecustom, _hyper_3_6_chunk.series_0\n                     Worker 0:  actual rows=1.00 loops=1\n               ->  Seq Scan on _timescaledb_internal._hyper_3_7_chunk (actual rows=2.00 loops=1)\n                     Output: _hyper_3_7_chunk.timecustom, _hyper_3_7_chunk.series_0\n                     Worker 0:  actual rows=2.00 loops=1\n               ->  Seq Scan on _timescaledb_internal._hyper_3_8_chunk (actual rows=1.00 loops=1)\n                     Output: _hyper_3_8_chunk.timecustom, _hyper_3_8_chunk.series_0\n                     Worker 0:  actual rows=1.00 loops=1\n\n-- Check parallel fallback into a non-partial aggregation\nSET timescaledb.enable_chunkwise_aggregation = OFF;\nSET enable_hashagg = OFF;\nSELECT timeCustom t, min(series_0) FROM PUBLIC.testtable2 GROUP BY t ORDER BY t DESC NULLS LAST limit 2;\n          t          | min \n---------------------+-----\n 1257987600000000000 | 1.5\n 1257897600000000000 | 4.5\n\n:PREFIX\nSELECT timeCustom t, min(series_0) FROM PUBLIC.testtable2 GROUP BY t ORDER BY t DESC NULLS LAST limit 2;\n--- QUERY PLAN ---\n Gather (actual rows=2.00 loops=1)\n   Output: testtable2.timecustom, (min(testtable2.series_0))\n   Workers Planned: 1\n   Workers Launched: 1\n   Single Copy: true\n   ->  Limit (actual rows=2.00 loops=1)\n         Output: testtable2.timecustom, (min(testtable2.series_0))\n         Worker 0:  actual rows=2.00 loops=1\n         ->  GroupAggregate (actual rows=2.00 loops=1)\n               Output: testtable2.timecustom, min(testtable2.series_0)\n               Group Key: testtable2.timecustom\n               Worker 0:  actual rows=2.00 loops=1\n               ->  Custom Scan (ChunkAppend) on public.testtable2 (actual rows=4.00 loops=1)\n                     Output: testtable2.timecustom, testtable2.series_0\n                     Order: testtable2.timecustom DESC NULLS LAST\n                     Startup Exclusion: false\n                     Runtime Exclusion: false\n                     Worker 0:  actual rows=4.00 loops=1\n                     ->  Index Scan using _hyper_3_7_chunk_testtable2_timecustom_device_id_idx on _timescaledb_internal._hyper_3_7_chunk (actual rows=2.00 loops=1)\n                           Output: _hyper_3_7_chunk.timecustom, _hyper_3_7_chunk.series_0\n                           Worker 0:  actual rows=2.00 loops=1\n                     ->  Index Scan using _hyper_3_6_chunk_testtable2_timecustom_device_id_idx on _timescaledb_internal._hyper_3_6_chunk (actual rows=1.00 loops=1)\n                           Output: _hyper_3_6_chunk.timecustom, _hyper_3_6_chunk.series_0\n                           Worker 0:  actual rows=1.00 loops=1\n                     ->  Merge Append (actual rows=1.00 loops=1)\n                           Sort Key: testtable2.timecustom DESC NULLS LAST\n                           Worker 0:  actual rows=1.00 loops=1\n                           ->  Index Scan using _hyper_3_8_chunk_testtable2_timecustom_device_id_idx on _timescaledb_internal._hyper_3_8_chunk (actual rows=1.00 loops=1)\n                                 Output: _hyper_3_8_chunk.timecustom, _hyper_3_8_chunk.series_0\n                                 Worker 0:  actual rows=1.00 loops=1\n                           ->  Index Scan using _hyper_3_5_chunk_testtable2_timecustom_device_id_idx on _timescaledb_internal._hyper_3_5_chunk (actual rows=1.00 loops=1)\n                                 Output: _hyper_3_5_chunk.timecustom, _hyper_3_5_chunk.series_0\n                                 Worker 0:  actual rows=1.00 loops=1\n\nRESET timescaledb.enable_chunkwise_aggregation;\nRESET enable_hashagg;\n-- Test aggregation pushdown with MergeAppend node\nCREATE TABLE merge_append_test (start_time timestamptz, sensor_id int, cluster varchar (253), cost_recommendation_memory numeric);\nSELECT * FROM create_hypertable('merge_append_test', 'start_time');\nWARNING:  column type \"character varying\" used for \"cluster\" does not follow best practices\n hypertable_id | schema_name |    table_name     | created \n---------------+-------------+-------------------+---------\n             4 | public      | merge_append_test | t\n\nCREATE INDEX merge_append_test_sensorid ON merge_append_test USING btree (start_time, sensor_id);\nINSERT INTO merge_append_test\nSELECT\n    date_series,\n    1,\n    'production-1',\n   random() * 100\n   FROM generate_series('2023-10-01 00:00:00', '2023-12-01 00:00:00', INTERVAL '1 hour') AS date_series\n;\nINSERT INTO merge_append_test\nSELECT\n    date_series,\n    sensor_id,\n    'production-2',\n   random() * 100\n   FROM generate_series('2023-10-01 00:00:00', '2023-12-01 00:00:00', INTERVAL '1 hour') AS date_series,\ngenerate_series(1, 100, 1) AS sensor_id\n;\nANALYZE merge_append_test;\nSET enable_seqscan = off;\nSET random_page_cost = 0;\nSET cpu_operator_cost = 0;\nSET enable_hashagg = off;\nRESET parallel_setup_cost;\nRESET parallel_tuple_cost;\nSELECT set_config(CASE WHEN current_setting('server_version_num')::int < 160000 THEN 'force_parallel_mode' ELSE 'debug_parallel_query' END, 'off', false);\n set_config \n------------\n off\n\n:PREFIX\nSELECT\n    start_time, sensor_id,\n    SUM(cost_recommendation_memory)\nFROM\n    merge_append_test\nWHERE\n    start_time >= '2023-11-27 00:00:00Z'\n    AND start_time <= '2023-12-01 00:00:00Z'\n    AND sensor_id < 10\n    AND CLUSTER = 'production-2'\nGROUP BY\n    1, 2;\n--- QUERY PLAN ---\n Finalize GroupAggregate (actual rows=873.00 loops=1)\n   Output: merge_append_test.start_time, merge_append_test.sensor_id, sum(merge_append_test.cost_recommendation_memory)\n   Group Key: merge_append_test.start_time, merge_append_test.sensor_id\n   ->  Merge Append (actual rows=873.00 loops=1)\n         Sort Key: merge_append_test.start_time, merge_append_test.sensor_id\n         ->  Partial GroupAggregate (actual rows=648.00 loops=1)\n               Output: _hyper_4_17_chunk.start_time, _hyper_4_17_chunk.sensor_id, PARTIAL sum(_hyper_4_17_chunk.cost_recommendation_memory)\n               Group Key: _hyper_4_17_chunk.start_time, _hyper_4_17_chunk.sensor_id\n               ->  Index Scan using _hyper_4_17_chunk_merge_append_test_sensorid on _timescaledb_internal._hyper_4_17_chunk (actual rows=648.00 loops=1)\n                     Output: _hyper_4_17_chunk.start_time, _hyper_4_17_chunk.sensor_id, _hyper_4_17_chunk.cost_recommendation_memory\n                     Index Cond: ((_hyper_4_17_chunk.start_time >= 'Sun Nov 26 16:00:00 2023 PST'::timestamp with time zone) AND (_hyper_4_17_chunk.start_time <= 'Thu Nov 30 16:00:00 2023 PST'::timestamp with time zone) AND (_hyper_4_17_chunk.sensor_id < 10))\n                     Filter: ((_hyper_4_17_chunk.cluster)::text = 'production-2'::text)\n                     Rows Removed by Filter: 72\n         ->  Partial GroupAggregate (actual rows=225.00 loops=1)\n               Output: _hyper_4_18_chunk.start_time, _hyper_4_18_chunk.sensor_id, PARTIAL sum(_hyper_4_18_chunk.cost_recommendation_memory)\n               Group Key: _hyper_4_18_chunk.start_time, _hyper_4_18_chunk.sensor_id\n               ->  Index Scan using _hyper_4_18_chunk_merge_append_test_sensorid on _timescaledb_internal._hyper_4_18_chunk (actual rows=225.00 loops=1)\n                     Output: _hyper_4_18_chunk.start_time, _hyper_4_18_chunk.sensor_id, _hyper_4_18_chunk.cost_recommendation_memory\n                     Index Cond: ((_hyper_4_18_chunk.start_time >= 'Sun Nov 26 16:00:00 2023 PST'::timestamp with time zone) AND (_hyper_4_18_chunk.start_time <= 'Thu Nov 30 16:00:00 2023 PST'::timestamp with time zone) AND (_hyper_4_18_chunk.sensor_id < 10))\n                     Filter: ((_hyper_4_18_chunk.cluster)::text = 'production-2'::text)\n                     Rows Removed by Filter: 25\n\nRESET enable_seqscan;\nRESET random_page_cost;\nRESET cpu_operator_cost;\nRESET enable_hashagg;\n"
  },
  {
    "path": "tsl/test/expected/attach_chunk.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nGRANT CREATE ON DATABASE :\"TEST_DBNAME\" TO :ROLE_DEFAULT_PERM_USER;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n-- Create test tables and hypertables (reusing from detach_chunk test)\nCREATE TABLE devices(id int PRIMARY KEY);\nINSERT INTO devices VALUES (1), (2), (3);\nCREATE TABLE attach_test(id int, time timestamptz not null, device int, temp float);\nCREATE INDEX attach_test_device_idx ON attach_test (device);\nALTER TABLE attach_test\n    ADD CONSTRAINT attach_test_temp_check CHECK (temp > 0),\n    ADD CONSTRAINT attach_test_device_fkey FOREIGN KEY (device) REFERENCES devices(id),\n    ADD CONSTRAINT attach_test_id_time_unique UNIQUE (id, time);\nSELECT * FROM create_hypertable('attach_test', 'time', 'id', 2);\n hypertable_id | schema_name | table_name  | created \n---------------+-------------+-------------+---------\n             1 | public      | attach_test | t\n\nCREATE TABLE attach_test_ref (\n    id int PRIMARY KEY,\n    ref_id int,\n    ref_time timestamptz,\n    FOREIGN KEY (ref_id, ref_time) REFERENCES attach_test(id, time)\n);\nINSERT INTO attach_test VALUES\n    (1, '2025-06-01 05:00:00+3', 1, 23.4),\n    (2, '2025-06-15 05:00:00+3', 2, 24.5),\n    (3, '2025-06-30 05:00:00+3', 3, 25.6);\n-- Get chunk information for testing\nSELECT\n    chunk_id AS \"CHUNK_ID\",\n    hypertable_id AS \"HYPERTABLE_ID\",\n    schema_name AS \"CHUNK_SCHEMA\",\n    table_name AS \"CHUNK_TABLE\",\n    schema_name || '.' || table_name AS \"CHUNK_NAME\",\n    slices AS \"CHUNK_SLICES\"\nFROM _timescaledb_functions.show_chunk((SELECT show_chunks('attach_test') LIMIT 1)); \\gset\n CHUNK_ID | HYPERTABLE_ID |     CHUNK_SCHEMA      |   CHUNK_TABLE    |               CHUNK_NAME               |                                       CHUNK_SLICES                                       \n----------+---------------+-----------------------+------------------+----------------------------------------+------------------------------------------------------------------------------------------\n        1 |             1 | _timescaledb_internal | _hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk | {\"id\": [-9223372036854775808, 1073741823], \"time\": [1748476800000000, 1749081600000000]}\n\n-- Successful attachment\nCREATE TABLE regular_table_to_attach(id int, time timestamptz not null, device int, temp float);\nCREATE INDEX regular_table_device_idx ON regular_table_to_attach (device);\nALTER TABLE regular_table_to_attach\n    ADD CONSTRAINT attach_test_temp_check CHECK (temp > 0),\n    ADD CONSTRAINT pre_attach_temp_check CHECK (temp < 100),\n    ADD CONSTRAINT pre_attach_id_time_unique UNIQUE (id, time);\nINSERT INTO regular_table_to_attach VALUES (10, '2025-07-05 13:00:00+3', 2, 27.8);\n-- Attach it as a chunk\nCALL attach_chunk('attach_test', 'regular_table_to_attach', '{\"time\": [\"2025-07-01 05:00:00+3\", \"2025-07-07 05:00:00+3\"], \"id\": [-9223372036854775808, 1073741823]}');\n-- Verify data is accessible through the hypertable\nSELECT count(*) > 0 FROM attach_test WHERE time >= '2025-07-5'::timestamptz;\n ?column? \n----------\n t\n\n-- Check the chunk and related metadata are set up correctly\nSELECT id AS \"ATTACHED_CHUNK\" FROM _timescaledb_catalog.chunk\nWHERE hypertable_id = :'HYPERTABLE_ID' AND table_name = 'regular_table_to_attach'; \\gset\n ATTACHED_CHUNK \n----------------\n              4\n\n-- Verify that each constraint/index on a auto-created chunk is also present on the attached chunk\nSELECT hypertable_constraint_name FROM _timescaledb_catalog.chunk_constraint WHERE chunk_id = :'CHUNK_ID'\nEXCEPT SELECT hypertable_constraint_name FROM _timescaledb_catalog.chunk_constraint WHERE chunk_id = :'ATTACHED_CHUNK';\n hypertable_constraint_name \n----------------------------\n\nSELECT * FROM test.show_indexesp('regular_table_to_attach');\n          Table          |                    Index                     |  Columns  | Expr | Unique | Primary | Exclusion | Tablespace \n-------------------------+----------------------------------------------+-----------+------+--------+---------+-----------+------------\n regular_table_to_attach | regular_table_device_idx                     | {device}  |      | f      | f       | f         | \n regular_table_to_attach | pre_attach_id_time_unique                    | {id,time} |      | t      | f       | f         | \n regular_table_to_attach | regular_table_to_attach_attach_test_time_idx | {time}    |      | f      | f       | f         | \n\nSELECT * FROM _timescaledb_catalog.dimension_slice ds\nJOIN _timescaledb_catalog.chunk_constraint cc ON ds.id = cc.dimension_slice_id\nWHERE cc.chunk_id = :'ATTACHED_CHUNK';\n id | dimension_id |     range_start      |    range_end     | chunk_id | dimension_slice_id | constraint_name | hypertable_constraint_name \n----+--------------+----------------------+------------------+----------+--------------------+-----------------+----------------------------\n  2 |            2 | -9223372036854775808 |       1073741823 |        4 |                  2 | constraint_2    | \n  6 |            1 |     1751335200000000 | 1751853600000000 |        4 |                  6 | constraint_6    | \n\n-- Verify that the chunk is a child of the hypertable\nSELECT count(*) > 0 FROM pg_inherits\nWHERE inhrelid = 'regular_table_to_attach'::regclass::oid AND inhparent = 'attach_test'::regclass::oid;\n ?column? \n----------\n t\n\n-- Verify foreign key references work\nSELECT count(*) > 0 FROM pg_constraint\nWHERE contype = 'f' AND confrelid = 'regular_table_to_attach'::regclass::oid;\n ?column? \n----------\n t\n\nSELECT count(*) > 0 FROM pg_constraint\nWHERE contype = 'f' AND conrelid = 'regular_table_to_attach'::regclass::oid;\n ?column? \n----------\n t\n\n-- Verify data is routed to the correct chunk\nSELECT count(*) FROM attach_test WHERE time > '2025-07-01'::timestamptz;\n count \n-------\n     1\n\nINSERT INTO attach_test VALUES (5, '2025-07-4 05:00:00+3', 2, 19.5);\nSELECT count(*) FROM regular_table_to_attach;\n count \n-------\n     2\n\n-- Detach and re-attach a chunk\nCALL detach_chunk(:'CHUNK_NAME');\nCALL attach_chunk('attach_test', :'CHUNK_NAME', :'CHUNK_SLICES');\n-- Store the new chunk id\nSELECT chunk_id AS \"CHUNK_ID\" FROM _timescaledb_functions.show_chunk(:'CHUNK_NAME'); \\gset\n CHUNK_ID \n----------\n        5\n\n-- Verify it's re-attached\nSELECT count(*) > 0 FROM pg_inherits WHERE inhrelid = :'CHUNK_NAME'::regclass::oid;\n ?column? \n----------\n t\n\n-- Verify constraints and indexes are restored\nSELECT * FROM _timescaledb_catalog.chunk_constraint WHERE chunk_id = :'CHUNK_ID';\n chunk_id | dimension_slice_id |        constraint_name         | hypertable_constraint_name \n----------+--------------------+--------------------------------+----------------------------\n        5 |                    | 1_2_attach_test_id_time_unique | attach_test_id_time_unique\n        5 |                    | 5_8_attach_test_device_fkey    | attach_test_device_fkey\n        5 |                  2 | constraint_2                   | \n        5 |                  7 | constraint_7                   | \n\nSELECT * FROM _timescaledb_catalog.dimension_slice ds\nJOIN _timescaledb_catalog.chunk_constraint cc ON ds.id = cc.dimension_slice_id\nWHERE cc.chunk_id = :'CHUNK_ID';\n id | dimension_id |     range_start      |    range_end     | chunk_id | dimension_slice_id | constraint_name | hypertable_constraint_name \n----+--------------+----------------------+------------------+----------+--------------------+-----------------+----------------------------\n  2 |            2 | -9223372036854775808 |       1073741823 |        5 |                  2 | constraint_2    | \n  7 |            1 |     1748476800000000 | 1749081600000000 |        5 |                  7 | constraint_7    | \n\n-- Attach a chunk to another hypertable with a different dimension\nCALL detach_chunk('regular_table_to_attach');\nCREATE TABLE hypertable_with_different_dimension(id int, time timestamptz not null, device int, temp float);\nSELECT * FROM create_hypertable('hypertable_with_different_dimension', 'time');\n hypertable_id | schema_name |             table_name              | created \n---------------+-------------+-------------------------------------+---------\n             2 | public      | hypertable_with_different_dimension | t\n\nCALL attach_chunk('hypertable_with_different_dimension', 'regular_table_to_attach', '{\"time\": [\"2025-07-01 05:00:00+3\", \"2025-07-07 05:00:00+3\"]}');\nCALL detach_chunk('regular_table_to_attach');\nCREATE TABLE not_a_hypertable(id int, time timestamptz not null, device int, temp float);\n-- Error cases\n\\set ON_ERROR_STOP 0\nCALL attach_chunk('attach_test', 'nonexistent_table', '{\"time\": [\"2025-07-01 05:00:00+3\", \"2025-07-07 05:00:00+3\"], \"id\": [-9223372036854775808, 1073741823]}');\nERROR:  relation \"nonexistent_table\" does not exist at character 34\nCALL attach_chunk('not_a_hypertable', 'regular_table_to_attach', '{\"time\": [\"2025-07-01 05:00:00+3\", \"2025-07-07 05:00:00+3\"], \"id\": [-9223372036854775808, 1073741823]}');\nERROR:  table \"not_a_hypertable\" is not a hypertable\nCALL attach_chunk('nonexistent_hypertable', 'regular_table_to_attach', '{\"time\": [\"2025-07-01 05:00:00+3\", \"2025-07-07 05:00:00+3\"], \"id\": [-9223372036854775808, 1073741823]}');\nERROR:  relation \"nonexistent_hypertable\" does not exist at character 19\nCALL attach_chunk(98765, 'regular_table_to_attach', '{\"time\": [\"2025-07-01 05:00:00+3\", \"2025-07-07 05:00:00+3\"], \"id\": [-9223372036854775808, 1073741823]}');\nERROR:  relation with OID 98765 does not exist\nCALL attach_chunk(0, 'regular_table_to_attach', '{\"time\": [\"2025-07-01 05:00:00+3\", \"2025-07-07 05:00:00+3\"], \"id\": [-9223372036854775808, 1073741823]}');\nERROR:  invalid hypertable relation OID\n-- invalid json format\nCALL attach_chunk('attach_test', 'regular_table_to_attach', '\"time\": [\"2025-07-01 05:00:00+3\", \"2025-07-07 05:00:00+3\"], \"id\": [-9223372036854775808, 1073741823]');\nERROR:  invalid input syntax for type json at character 61\n-- incorrect dimension information\nCALL attach_chunk('attach_test', 'regular_table_to_attach', '{\"time\": [\"2025123-07-01 05:00:00+3\", \"2025-07-07 05:00:00+3\"], \"id\": [-9223372036854775808, 1073741823]}');\nERROR:  timestamp out of range: \"2025123-07-01 05:00:00+3\"\nCALL attach_chunk('attach_test', 'regular_table_to_attach', '{\"time\": [\"2025-07-01 05:00:00+3\", \"2025-07-07 05:00:00+3\"], \"incorrect_key\": [-9223372036854775808, 1073741823]}');\nERROR:  invalid hypercube for hypertable \"attach_test\"\nCALL attach_chunk('attach_test', 'regular_table_to_attach', NULL);\nERROR:  invalid dimension slices argument\nCALL attach_chunk('attach_test', 'regular_table_to_attach', :'CHUNK_SLICES');\nERROR:  chunk creation failed due to collision\nCALL attach_chunk('attach_test', :'CHUNK_NAME', :'CHUNK_SLICES');\nERROR:  cannot attach chunk that is already a child of another table\n-- Attach a chunk of another hypertable\nCALL attach_chunk('hypertable_with_different_dimension', :'CHUNK_NAME', :'CHUNK_SLICES');\nERROR:  cannot attach chunk that is already a child of another table\n-- Try to attach a table that's already a child of another table\nCREATE TABLE parent_table(id int);\nCREATE TABLE child_table(time timestamptz not null, device int, temp float) INHERITS (parent_table);\nCALL attach_chunk('attach_test', 'child_table', '{\"time\": [\"2025-07-01 05:00:00+3\", \"2025-07-07 05:00:00+3\"], \"id\": [-9223372036854775808, 1073741823]}');\nERROR:  cannot attach chunk that is already a child of another table\n-- Try to attach a table with incompatible types\nCREATE TABLE incompatible_table(id int, time timestamptz not null, device text, temp float);\nCALL attach_chunk('attach_test', 'incompatible_table', '{\"time\": [\"2025-07-01 05:00:00+3\", \"2025-07-07 05:00:00+3\"], \"id\": [-9223372036854775808, 1073741823]}');\nERROR:  child table \"incompatible_table\" has different type for column \"device\"\n-- Try to attach a table with missing columns\nCREATE TABLE missing_col_table(id int, time timestamptz not null);\nCALL attach_chunk('attach_test', 'missing_col_table', '{\"time\": [\"2025-07-01 05:00:00+3\", \"2025-07-07 05:00:00+3\"], \"id\": [-9223372036854775808, 1073741823]}');\nERROR:  child table is missing column \"device\"\n-- Try to attach a table with extra columns\nCREATE TABLE extra_col_table(id int, time timestamptz not null, device int, temp float, extra_col int);\nCALL attach_chunk('attach_test', 'extra_col_table', '{\"time\": [\"2025-07-01 05:00:00+3\", \"2025-07-07 05:00:00+3\"], \"id\": [-9223372036854775808, 1073741823]}');\nERROR:  table \"extra_col_table\" contains column \"extra_col\" not found in parent \"attach_test\"\n-- Attach by non-owner is not allowed\nset role :ROLE_1;\nCALL attach_chunk('attach_test', 'regular_table_to_attach', '{\"time\": [\"2025-07-01 05:00:00+3\", \"2025-07-07 05:00:00+3\"], \"id\": [-9223372036854775808, 1073741823]}');\nERROR:  must be owner of table regular_table_to_attach\nset role :ROLE_DEFAULT_PERM_USER;\nCALL attach_chunk('attach_test', 'regular_table_to_attach', '{\"time\": [\"2025-06-01 05:00:00+3\", \"2025-06-07 05:00:00+3\"], \"id\": [-9223372036854775808, 1073741823]}');\nERROR:  chunk creation failed due to collision\n-- Attach hypertable as a chunk\nCALL attach_chunk('attach_test', 'hypertable_with_different_dimension', '{\"time\": [\"2025-07-01 05:00:00+3\", \"2025-07-07 05:00:00+3\"], \"id\": [-9223372036854775808, 1073741823]}');\nERROR:  cannot attach hypertable as a chunk\n\\set ON_ERROR_STOP 1\n-- Test rollback behavior\nSELECT count(*) AS \"PRE_ROLLBACK_CHUNKS\" FROM _timescaledb_catalog.chunk WHERE hypertable_id = :'HYPERTABLE_ID'; \\gset\n PRE_ROLLBACK_CHUNKS \n---------------------\n                   3\n\nBEGIN;\nCREATE TABLE rollback_test_table(id int, time timestamptz not null, device int, temp float);\nALTER TABLE rollback_test_table ADD CONSTRAINT attach_test_temp_check CHECK (temp > 0);\nCALL attach_chunk('attach_test', 'rollback_test_table', '{\"time\": [\"2025-07-01 05:00:00+3\", \"2025-07-07 05:00:00+3\"], \"id\": [-9223372036854775808, 1073741823]}');\nROLLBACK;\nSELECT count(*) = :'PRE_ROLLBACK_CHUNKS' FROM _timescaledb_catalog.chunk WHERE hypertable_id = :'HYPERTABLE_ID';\n ?column? \n----------\n t\n\nCALL attach_chunk('attach_test', 'regular_table_to_attach', '{\"time\": [\"2025-07-01 05:00:00\", \"2025-07-07 05:00:00\"], \"id\": [-9223372036854775808, 1073741823]}');\nCALL detach_chunk('regular_table_to_attach');\nCALL attach_chunk('attach_test', 'regular_table_to_attach', '{\"time\": [\"2025-07-01\", \"2025-07-07\"], \"id\": [-9223372036854775808, 1073741823]}');\nCALL detach_chunk('regular_table_to_attach');\nCALL attach_chunk('attach_test', 'regular_table_to_attach', '{\"time\": [\"Tue July 1 05:00:00 2025 GMT+3\", \"Mon July 7 05:00:00 2025 GMT+3\"], \"id\": [-9223372036854775808, 1073741823]}');\nCALL detach_chunk('regular_table_to_attach');\nCALL attach_chunk('attach_test', 'regular_table_to_attach', '{\"time\": [1751356800000000, 1751875200000000], \"id\": [-9223372036854775808, 1073741823]}');\nCALL detach_chunk('regular_table_to_attach');\n-- Attach a table with rows violating chunk constraints\nINSERT INTO regular_table_to_attach VALUES (4, '2024-07-05 13:00:00+3', 2, 27.8);\n\\set ON_ERROR_STOP 0\nCALL attach_chunk('attach_test', 'regular_table_to_attach', '{\"time\": [\"2025-07-01 05:00:00+3\", \"2025-07-07 05:00:00+3\"], \"id\": [-9223372036854775808, 1073741823]}');\nERROR:  dimension constraint for column \"time\" violated by some row\n\\set ON_ERROR_STOP 1\n-- Clean up\nDROP TABLE regular_table_to_attach;\nDROP TABLE attach_test_ref;\nDROP TABLE attach_test;\nDROP TABLE devices CASCADE;\n"
  },
  {
    "path": "tsl/test/expected/bgw_custom.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\nCREATE TABLE custom_log(job_id int, args jsonb, extra text, runner NAME DEFAULT CURRENT_ROLE);\nCREATE OR REPLACE FUNCTION custom_func(jobid int, args jsonb) RETURNS VOID LANGUAGE SQL AS\n$$\n  INSERT INTO custom_log VALUES($1, $2, 'custom_func');\n$$;\nCREATE OR REPLACE FUNCTION custom_func_definer(jobid int, args jsonb) RETURNS VOID LANGUAGE SQL AS\n$$\n  INSERT INTO custom_log VALUES($1, $2, 'security definer');\n$$ SECURITY DEFINER;\nCREATE OR REPLACE PROCEDURE custom_proc(job_id int, args jsonb) LANGUAGE SQL AS\n$$\n  INSERT INTO custom_log VALUES($1, $2, 'custom_proc');\n$$;\n-- procedure with transaction handling\nCREATE OR REPLACE PROCEDURE custom_proc2(job_id int, args jsonb) LANGUAGE PLPGSQL AS\n$$\nBEGIN\n  INSERT INTO custom_log VALUES($1, $2, 'custom_proc2 1 COMMIT ' || (args->>'type'));\n  COMMIT;\n  INSERT INTO custom_log VALUES($1, $2, 'custom_proc2 2 ROLLBACK ' || (args->>'type'));\n  ROLLBACK;\n  INSERT INTO custom_log VALUES($1, $2, 'custom_proc2 3 COMMIT ' || (args->>'type'));\n  COMMIT;\nEND\n$$;\n\\set ON_ERROR_STOP 0\n-- test bad input\nSELECT add_job(NULL, '1h');\nERROR:  function or procedure cannot be NULL\nSELECT add_job(0, '1h');\nERROR:  function or procedure with OID 0 does not exist\n-- this will return an error about Oid 4294967295\n-- while regproc is unsigned int postgres has an implicit cast from int to regproc\nSELECT add_job(-1, '1h');\nERROR:  function or procedure with OID 4294967295 does not exist\nSELECT add_job('invalid_func', '1h');\nERROR:  function \"invalid_func\" does not exist at character 16\nSELECT add_job('custom_func', NULL);\nERROR:  schedule interval cannot be NULL\nSELECT add_job('custom_func', 'invalid interval');\nERROR:  invalid input syntax for type interval: \"invalid interval\" at character 31\nSELECT add_job('custom_func', '1h', job_name := 'this_is_a_really_really_really_long_application_name_to_overflow');\nERROR:  application name too long.\n\\set ON_ERROR_STOP 1\nselect '2000-01-01 00:00:00+00' as time_zero \\gset\nSELECT add_job('custom_func','1h', config:='{\"type\":\"function\"}'::jsonb, initial_start => :'time_zero'::TIMESTAMPTZ);\n add_job \n---------\n    1001\n\nSELECT add_job('custom_proc','1h', config:='{\"type\":\"procedure\"}'::jsonb, initial_start => :'time_zero'::TIMESTAMPTZ);\n add_job \n---------\n    1002\n\nSELECT add_job('custom_proc2','1h', config:= '{\"type\":\"procedure\"}'::jsonb, initial_start => :'time_zero'::TIMESTAMPTZ);\n add_job \n---------\n    1003\n\nSELECT add_job('custom_func', '1h', config:='{\"type\":\"function\"}'::jsonb, initial_start => :'time_zero'::TIMESTAMPTZ);\n add_job \n---------\n    1004\n\nSELECT add_job('custom_func_definer', '1h', config:='{\"type\":\"function\"}'::jsonb, initial_start => :'time_zero'::TIMESTAMPTZ, job_name := 'custom_job_name');\n add_job \n---------\n    1005\n\n-- exclude internal jobs\nSELECT * FROM timescaledb_information.jobs WHERE job_id >= 1000 ORDER BY 1;\n job_id |      application_name      | schedule_interval | max_runtime | max_retries | retry_period | proc_schema |      proc_name      |       owner       | scheduled | fixed_schedule |        config         |          next_start          |        initial_start         | hypertable_schema | hypertable_name | check_schema | check_name \n--------+----------------------------+-------------------+-------------+-------------+--------------+-------------+---------------------+-------------------+-----------+----------------+-----------------------+------------------------------+------------------------------+-------------------+-----------------+--------------+------------\n   1001 | User-Defined Action [1001] | @ 1 hour          | @ 0         |          -1 | @ 5 mins     | public      | custom_func         | default_perm_user | t         | t              | {\"type\": \"function\"}  | Fri Dec 31 16:00:00 1999 PST | Fri Dec 31 16:00:00 1999 PST |                   |                 |              | \n   1002 | User-Defined Action [1002] | @ 1 hour          | @ 0         |          -1 | @ 5 mins     | public      | custom_proc         | default_perm_user | t         | t              | {\"type\": \"procedure\"} | Fri Dec 31 16:00:00 1999 PST | Fri Dec 31 16:00:00 1999 PST |                   |                 |              | \n   1003 | User-Defined Action [1003] | @ 1 hour          | @ 0         |          -1 | @ 5 mins     | public      | custom_proc2        | default_perm_user | t         | t              | {\"type\": \"procedure\"} | Fri Dec 31 16:00:00 1999 PST | Fri Dec 31 16:00:00 1999 PST |                   |                 |              | \n   1004 | User-Defined Action [1004] | @ 1 hour          | @ 0         |          -1 | @ 5 mins     | public      | custom_func         | default_perm_user | t         | t              | {\"type\": \"function\"}  | Fri Dec 31 16:00:00 1999 PST | Fri Dec 31 16:00:00 1999 PST |                   |                 |              | \n   1005 | custom_job_name [1005]     | @ 1 hour          | @ 0         |          -1 | @ 5 mins     | public      | custom_func_definer | default_perm_user | t         | t              | {\"type\": \"function\"}  | Fri Dec 31 16:00:00 1999 PST | Fri Dec 31 16:00:00 1999 PST |                   |                 |              | \n\nSELECT count(*) FROM _timescaledb_catalog.bgw_job WHERE config->>'type' IN ('procedure', 'function');\n count \n-------\n     5\n\n\\set ON_ERROR_STOP 0\n-- test bad input\nCALL run_job(NULL);\nERROR:  job ID cannot be NULL\nCALL run_job(-1);\nERROR:  job -1 not found\n\\set ON_ERROR_STOP 1\nCALL run_job(1001);\nCALL run_job(1002);\nCALL run_job(1003);\nCALL run_job(1004);\nCALL run_job(1005);\nSELECT * FROM custom_log ORDER BY job_id, extra;\n job_id |         args          |              extra              |      runner       \n--------+-----------------------+---------------------------------+-------------------\n   1001 | {\"type\": \"function\"}  | custom_func                     | default_perm_user\n   1002 | {\"type\": \"procedure\"} | custom_proc                     | default_perm_user\n   1003 | {\"type\": \"procedure\"} | custom_proc2 1 COMMIT procedure | default_perm_user\n   1003 | {\"type\": \"procedure\"} | custom_proc2 3 COMMIT procedure | default_perm_user\n   1004 | {\"type\": \"function\"}  | custom_func                     | default_perm_user\n   1005 | {\"type\": \"function\"}  | security definer                | default_perm_user\n\n\\set ON_ERROR_STOP 0\n-- test bad input\nSELECT delete_job(NULL);\n delete_job \n------------\n \n\nSELECT delete_job(-1);\nERROR:  job -1 not found\n\\set ON_ERROR_STOP 1\n-- We keep job 1001 for some additional checks.\nSELECT delete_job(1002);\n delete_job \n------------\n \n\nSELECT delete_job(1003);\n delete_job \n------------\n \n\nSELECT delete_job(1004);\n delete_job \n------------\n \n\nSELECT delete_job(1005);\n delete_job \n------------\n \n\n-- check jobs got removed\nSELECT count(*) FROM timescaledb_information.jobs WHERE job_id >= 1002;\n count \n-------\n     0\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- create a new job with longer id\nSELECT nextval('_timescaledb_catalog.bgw_job_id_seq') as nextval \\gset\nSELECT setval('_timescaledb_catalog.bgw_job_id_seq', 2147483647, false);\n   setval   \n------------\n 2147483647\n\nSELECT add_job('custom_func', '1h', config:='{\"type\":\"function\"}'::jsonb, job_name := 'custom_job_name');\n  add_job   \n------------\n 2147483647\n\n\\set ON_ERROR_STOP 0\n-- test bad input\nSELECT alter_job(NULL, if_exists => false);\nERROR:  job ID cannot be NULL\nSELECT alter_job(-1, if_exists => false);\nERROR:  job -1 not found\nSELECT alter_job(1001, job_name => 'this_is_a_really_really_really_long_application_name_to_overflow');\nERROR:  application name too long.\nSELECT alter_job(2147483647, job_name => 'this_is_a_really_really_really_long_application_name_to_overflow');\nERROR:  application name too long.\n\\set ON_ERROR_STOP 1\n-- test bad input but don't fail\nSELECT alter_job(NULL, if_exists => true);\nNOTICE:  job 0 not found, skipping\n alter_job \n-----------\n \n\nSELECT alter_job(-1, if_exists => true);\nNOTICE:  job -1 not found, skipping\n alter_job \n-----------\n \n\n-- test altering job with NULL config\nSELECT job_id FROM alter_job(1001,scheduled:=false);\n job_id \n--------\n   1001\n\nSELECT scheduled, config FROM timescaledb_information.jobs WHERE job_id = 1001;\n scheduled |        config        \n-----------+----------------------\n f         | {\"type\": \"function\"}\n\n-- test updating job settings\nSELECT job_id FROM alter_job(1001,config:='{\"test\":\"test\"}');\n job_id \n--------\n   1001\n\nSELECT scheduled, config FROM timescaledb_information.jobs WHERE job_id = 1001;\n scheduled |      config      \n-----------+------------------\n f         | {\"test\": \"test\"}\n\nSELECT job_id FROM alter_job(1001,scheduled:=true);\n job_id \n--------\n   1001\n\nSELECT scheduled, config FROM timescaledb_information.jobs WHERE job_id = 1001;\n scheduled |      config      \n-----------+------------------\n t         | {\"test\": \"test\"}\n\nSELECT job_id FROM alter_job(1001,scheduled:=false);\n job_id \n--------\n   1001\n\nSELECT scheduled, config FROM timescaledb_information.jobs WHERE job_id = 1001;\n scheduled |      config      \n-----------+------------------\n f         | {\"test\": \"test\"}\n\n-- test updating the job name\nSELECT job_id, application_name FROM alter_job(1001,job_name:='custom_name_2');\n job_id |   application_name   \n--------+----------------------\n   1001 | custom_name_2 [1001]\n\nSELECT job_id, application_name FROM alter_job(2147483647,job_name:='short_name_to_fit');\n   job_id   |        application_name        \n------------+--------------------------------\n 2147483647 | short_name_to_fit [2147483647]\n\nSELECT application_name FROM timescaledb_information.jobs WHERE job_id >= 1001;\n        application_name        \n--------------------------------\n custom_name_2 [1001]\n short_name_to_fit [2147483647]\n\n-- Done with jobs now, so remove it.\nSELECT delete_job(1001);\n delete_job \n------------\n \n\nSELECT delete_job(2147483647);\n delete_job \n------------\n \n\n-- reset the sequence to its previous value\nSELECT setval('_timescaledb_catalog.bgw_job_id_seq', :nextval, false);\n setval \n--------\n   1006\n\n--test for #2793\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n-- background workers are disabled, so the job will not run --\nSELECT add_job( proc=>'custom_func',\n     schedule_interval=>'1h', initial_start =>'2018-01-01 10:00:00-05') AS job_id_1 \\gset\nSELECT job_id, next_start, scheduled, schedule_interval\nFROM timescaledb_information.jobs WHERE job_id > 1001;\n job_id |          next_start          | scheduled | schedule_interval \n--------+------------------------------+-----------+-------------------\n   1006 | Mon Jan 01 07:00:00 2018 PST | t         | @ 1 hour\n\n\\x\nSELECT * FROM timescaledb_information.job_stats WHERE job_id > 1001;\n-[ RECORD 1 ]----------+-----------------------------\nhypertable_schema      | \nhypertable_name        | \njob_id                 | 1006\nlast_run_started_at    | -infinity\nlast_successful_finish | -infinity\nlast_run_status        | \njob_status             | Scheduled\nlast_run_duration      | \nnext_start             | Mon Jan 01 07:00:00 2018 PST\ntotal_runs             | 0\ntotal_successes        | 0\ntotal_failures         | 0\n\n\\x\nSELECT delete_job(:job_id_1);\n delete_job \n------------\n \n\n-- tests for #3545\nTRUNCATE custom_log;\n-- Nested procedure call\nCREATE OR REPLACE PROCEDURE custom_proc_nested(job_id int, args jsonb) LANGUAGE PLPGSQL AS\n$$\nBEGIN\n  INSERT INTO custom_log VALUES($1, $2, 'custom_proc_nested 1 COMMIT');\n  COMMIT;\n  INSERT INTO custom_log VALUES($1, $2, 'custom_proc_nested 2 ROLLBACK');\n  ROLLBACK;\n  INSERT INTO custom_log VALUES($1, $2, 'custom_proc_nested 3 COMMIT');\n  COMMIT;\nEND\n$$;\nCREATE OR REPLACE PROCEDURE custom_proc3(job_id int, args jsonb) LANGUAGE PLPGSQL AS\n$$\nBEGIN\n    CALL custom_proc_nested(job_id, args);\nEND\n$$;\nCREATE OR REPLACE PROCEDURE custom_proc4(job_id int, args jsonb) LANGUAGE PLPGSQL AS\n$$\nBEGIN\n    INSERT INTO custom_log VALUES($1, $2, 'custom_proc4 1 COMMIT');\n    COMMIT;\n    INSERT INTO custom_log VALUES($1, $2, 'custom_proc4 2 ROLLBACK');\n    ROLLBACK;\n    RAISE EXCEPTION 'forced exception';\n    INSERT INTO custom_log VALUES($1, $2, 'custom_proc4 3 ABORT');\n    COMMIT;\nEND\n$$;\nCREATE OR REPLACE PROCEDURE custom_proc5(job_id int, args jsonb) LANGUAGE PLPGSQL AS\n$$\nBEGIN\n    CALL refresh_continuous_aggregate('conditions_summary_daily', '2021-08-01 00:00', '2021-08-31 00:00');\nEND\n$$;\n-- Remove any default jobs, e.g., telemetry\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE _timescaledb_catalog.bgw_job RESTART IDENTITY CASCADE;\nNOTICE:  truncate cascades to table \"bgw_job_stat\"\nNOTICE:  truncate cascades to table \"bgw_policy_chunk_stats\"\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT add_job('custom_proc2', '1h', config := '{\"type\":\"procedure\"}'::jsonb, initial_start := now()) AS job_id_1 \\gset\nSELECT add_job('custom_proc3', '1h', config := '{\"type\":\"procedure\"}'::jsonb, initial_start := now()) AS job_id_2 \\gset\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- Start Background Workers\nSELECT _timescaledb_functions.start_background_workers();\n start_background_workers \n--------------------------\n t\n\n-- Wait for jobs\nSELECT test.wait_for_job_to_run(:job_id_1, 1);\n wait_for_job_to_run \n---------------------\n t\n\nSELECT test.wait_for_job_to_run(:job_id_2, 1);\n wait_for_job_to_run \n---------------------\n t\n\n-- Check results\nSELECT * FROM custom_log ORDER BY job_id, extra;\n job_id |         args          |              extra              |      runner       \n--------+-----------------------+---------------------------------+-------------------\n   1000 | {\"type\": \"procedure\"} | custom_proc2 1 COMMIT procedure | default_perm_user\n   1000 | {\"type\": \"procedure\"} | custom_proc2 3 COMMIT procedure | default_perm_user\n   1001 | {\"type\": \"procedure\"} | custom_proc_nested 1 COMMIT     | default_perm_user\n   1001 | {\"type\": \"procedure\"} | custom_proc_nested 3 COMMIT     | default_perm_user\n\n-- Delete previous jobs\nSELECT delete_job(:job_id_1);\n delete_job \n------------\n \n\nSELECT delete_job(:job_id_2);\n delete_job \n------------\n \n\nTRUNCATE custom_log;\n-- Forced Exception\nSELECT add_job('custom_proc4', '1h', config := '{\"type\":\"procedure\"}'::jsonb, initial_start := now()) AS job_id_3 \\gset\nSELECT _timescaledb_functions.restart_background_workers();\n restart_background_workers \n----------------------------\n t\n\nSELECT test.wait_for_job_to_run(:job_id_3, 1);\nINFO:  wait_for_job_to_run: job execution failed\n wait_for_job_to_run \n---------------------\n f\n\n-- Check results\nSELECT * FROM custom_log ORDER BY job_id, extra;\n job_id |         args          |         extra         |   runner   \n--------+-----------------------+-----------------------+------------\n   1002 | {\"type\": \"procedure\"} | custom_proc4 1 COMMIT | super_user\n\n-- Delete previous jobs\nSELECT delete_job(:job_id_3);\n delete_job \n------------\n \n\nCREATE TABLE conditions (\n  time TIMESTAMP NOT NULL,\n  location TEXT NOT NULL,\n  location2 char(10) NOT NULL,\n  temperature DOUBLE PRECISION NULL,\n  humidity DOUBLE PRECISION NULL\n) WITH (autovacuum_enabled = FALSE);\nSELECT create_hypertable('conditions', 'time', chunk_time_interval := '15 days'::interval);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n    create_hypertable    \n-------------------------\n (1,public,conditions,t)\n\nALTER TABLE conditions\n  SET (\n    timescaledb.compress,\n    timescaledb.compress_segmentby = 'location',\n    timescaledb.compress_orderby = 'time'\n);\nINSERT INTO conditions\nSELECT generate_series('2021-08-01 00:00'::timestamp, '2021-08-31 00:00'::timestamp, '1 day'), 'POR', 'klick', 55, 75;\n-- Chunk compress stats\nSELECT * FROM _timescaledb_internal.compressed_chunk_stats ORDER BY chunk_name;\n hypertable_schema | hypertable_name |     chunk_schema      |    chunk_name    | compression_status | uncompressed_heap_size | uncompressed_index_size | uncompressed_toast_size | uncompressed_total_size | compressed_heap_size | compressed_index_size | compressed_toast_size | compressed_total_size \n-------------------+-----------------+-----------------------+------------------+--------------------+------------------------+-------------------------+-------------------------+-------------------------+----------------------+-----------------------+-----------------------+-----------------------\n public            | conditions      | _timescaledb_internal | _hyper_1_1_chunk | Uncompressed       |                        |                         |                         |                         |                      |                       |                       |                      \n public            | conditions      | _timescaledb_internal | _hyper_1_2_chunk | Uncompressed       |                        |                         |                         |                         |                      |                       |                       |                      \n public            | conditions      | _timescaledb_internal | _hyper_1_3_chunk | Uncompressed       |                        |                         |                         |                         |                      |                       |                       |                      \n\n-- Compression policy\nSELECT add_compression_policy('conditions', interval '1 day') AS job_id_4 \\gset\nSELECT _timescaledb_functions.restart_background_workers();\n restart_background_workers \n----------------------------\n t\n\nSELECT test.wait_for_job_to_run(:job_id_4, 1);\n wait_for_job_to_run \n---------------------\n t\n\n-- Chunk compress stats\nSELECT * FROM _timescaledb_internal.compressed_chunk_stats ORDER BY chunk_name;\n hypertable_schema | hypertable_name |     chunk_schema      |    chunk_name    | compression_status | uncompressed_heap_size | uncompressed_index_size | uncompressed_toast_size | uncompressed_total_size | compressed_heap_size | compressed_index_size | compressed_toast_size | compressed_total_size \n-------------------+-----------------+-----------------------+------------------+--------------------+------------------------+-------------------------+-------------------------+-------------------------+----------------------+-----------------------+-----------------------+-----------------------\n public            | conditions      | _timescaledb_internal | _hyper_1_1_chunk | Compressed         |                   8192 |                   16384 |                    8192 |                   32768 |                16384 |                 16384 |                  8192 |                 40960\n public            | conditions      | _timescaledb_internal | _hyper_1_2_chunk | Compressed         |                   8192 |                   16384 |                    8192 |                   32768 |                16384 |                 16384 |                  8192 |                 40960\n public            | conditions      | _timescaledb_internal | _hyper_1_3_chunk | Compressed         |                   8192 |                   16384 |                    8192 |                   32768 |                16384 |                 16384 |                  8192 |                 40960\n\n--TEST compression job after inserting data into previously compressed chunk\nINSERT INTO conditions\nSELECT generate_series('2021-08-01 00:00'::timestamp, '2021-08-31 00:00'::timestamp, '1 day'), 'NYC', 'nycity', 40, 40;\nSELECT id, table_name, status from _timescaledb_catalog.chunk\nwhere hypertable_id = (select id from _timescaledb_catalog.hypertable\n                       where table_name = 'conditions')\norder by id;\n id |    table_name    | status \n----+------------------+--------\n  1 | _hyper_1_1_chunk |      9\n  2 | _hyper_1_2_chunk |      9\n  3 | _hyper_1_3_chunk |      9\n\n--running job second time, wait for it to complete\nselect t.schedule_interval FROM alter_job(:job_id_4, next_start=> now() ) t;\n schedule_interval \n-------------------\n @ 12 hours\n\nSELECT _timescaledb_functions.restart_background_workers();\n restart_background_workers \n----------------------------\n t\n\nSELECT test.wait_for_job_to_run(:job_id_4, 2);\n wait_for_job_to_run \n---------------------\n t\n\nSELECT id, table_name, status from _timescaledb_catalog.chunk\nwhere hypertable_id = (select id from _timescaledb_catalog.hypertable\n                       where table_name = 'conditions')\norder by id;\n id |    table_name    | status \n----+------------------+--------\n  1 | _hyper_1_1_chunk |      1\n  2 | _hyper_1_2_chunk |      1\n  3 | _hyper_1_3_chunk |      1\n\n-- Drop the compression job\nSELECT delete_job(:job_id_4);\n delete_job \n------------\n \n\n-- Decompress chunks before create the cagg\nSELECT decompress_chunk(c) FROM show_chunks('conditions') c;\n            decompress_chunk            \n----------------------------------------\n _timescaledb_internal._hyper_1_1_chunk\n _timescaledb_internal._hyper_1_2_chunk\n _timescaledb_internal._hyper_1_3_chunk\n\n-- TEST Continuous Aggregate job\nCREATE MATERIALIZED VIEW conditions_summary_daily\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT location,\n   time_bucket(INTERVAL '1 day', time) AS bucket,\n   AVG(temperature),\n   MAX(temperature),\n   MIN(temperature)\nFROM conditions\nGROUP BY location, bucket\nWITH NO DATA;\n-- Refresh Continous Aggregate by Job\nSELECT add_job('custom_proc5', '1h', config := '{\"type\":\"procedure\"}'::jsonb, initial_start := now()) AS job_id_5 \\gset\nSELECT _timescaledb_functions.restart_background_workers();\n restart_background_workers \n----------------------------\n t\n\nSELECT test.wait_for_job_to_run(:job_id_5, 1);\n wait_for_job_to_run \n---------------------\n t\n\nSELECT count(*) FROM conditions_summary_daily;\n count \n-------\n    62\n\n-- TESTs for alter_job_set_hypertable_id API\nSELECT _timescaledb_functions.alter_job_set_hypertable_id( :job_id_5, NULL);\n alter_job_set_hypertable_id \n-----------------------------\n                        1004\n\nSELECT id, proc_name, hypertable_id\nFROM _timescaledb_catalog.bgw_job WHERE id = :job_id_5;\n  id  |  proc_name   | hypertable_id \n------+--------------+---------------\n 1004 | custom_proc5 |              \n\n-- error case, try to associate with a PG relation\n\\set ON_ERROR_STOP 0\nSELECT _timescaledb_functions.alter_job_set_hypertable_id( :job_id_5, 'custom_log');\nERROR:  relation \"custom_log\" is not a hypertable or continuous aggregate\n\\set ON_ERROR_STOP 1\n-- TEST associate the cagg with the job\nSELECT _timescaledb_functions.alter_job_set_hypertable_id( :job_id_5, 'conditions_summary_daily'::regclass);\n alter_job_set_hypertable_id \n-----------------------------\n                        1004\n\nSELECT id, proc_name, hypertable_id\nFROM _timescaledb_catalog.bgw_job WHERE id = :job_id_5;\n  id  |  proc_name   | hypertable_id \n------+--------------+---------------\n 1004 | custom_proc5 |             3\n\n--verify that job is dropped when cagg is dropped\nDROP MATERIALIZED VIEW conditions_summary_daily;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_3_7_chunk\nSELECT id, proc_name, hypertable_id\nFROM _timescaledb_catalog.bgw_job WHERE id = :job_id_5;\n id | proc_name | hypertable_id \n----+-----------+---------------\n\n-- Cleanup\nDROP TABLE conditions;\nDROP TABLE custom_log;\n-- Stop Background Workers\nSELECT _timescaledb_functions.stop_background_workers();\n stop_background_workers \n-------------------------\n t\n\nSELECT _timescaledb_functions.restart_background_workers();\n restart_background_workers \n----------------------------\n t\n\n\\set ON_ERROR_STOP 0\n-- add test for custom jobs with custom check functions\n-- create the functions/procedures to be used as checking functions\nCREATE OR REPLACE PROCEDURE test_config_check_proc(config jsonb)\nLANGUAGE PLPGSQL\nAS $$\nDECLARE\n  drop_after interval;\nBEGIN\n    SELECT jsonb_object_field_text (config, 'drop_after')::interval INTO STRICT drop_after;\n    IF drop_after IS NULL THEN\n        RAISE EXCEPTION 'Config must be not NULL and have drop_after';\n    END IF ;\nEND\n$$;\nCREATE OR REPLACE FUNCTION test_config_check_func(config jsonb) RETURNS VOID\nAS $$\nDECLARE\n  drop_after interval;\nBEGIN\n    IF config IS NULL THEN\n        RETURN;\n    END IF;\n    SELECT jsonb_object_field_text (config, 'drop_after')::interval INTO STRICT drop_after;\n    IF drop_after IS NULL THEN\n        RAISE EXCEPTION 'Config can be NULL but must have drop_after if not';\n    END IF ;\nEND\n$$ LANGUAGE PLPGSQL;\n-- step 2, create a procedure to run as a custom job\nCREATE OR REPLACE PROCEDURE test_proc_with_check(job_id int, config jsonb)\nLANGUAGE PLPGSQL\nAS $$\nBEGIN\n  RAISE NOTICE 'Will only print this if config passes checks, my config is %', config;\nEND\n$$;\n-- step 3, add the job with the config check function passed as argument\n-- test procedures, should get an unsupported error\nselect add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_config_check_proc'::regproc);\nERROR:  unsupported function type\n-- test functions\nselect add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_config_check_func'::regproc);\nERROR:  Config can be NULL but must have drop_after if not\nselect add_job('test_proc_with_check', '5 secs', config => NULL, check_config => 'test_config_check_func'::regproc);\n add_job \n---------\n    1005\n\nselect add_job('test_proc_with_check', '5 secs', config => '{\"drop_after\": \"chicken\"}', check_config => 'test_config_check_func'::regproc);\nERROR:  invalid input syntax for type interval: \"chicken\"\nselect add_job('test_proc_with_check', '5 secs', config => '{\"drop_after\": \"2 weeks\"}', check_config => 'test_config_check_func'::regproc)\nas job_with_func_check_id \\gset\n--- test alter_job\nselect alter_job(:job_with_func_check_id, config => '{\"drop_after\":\"chicken\"}');\nERROR:  invalid input syntax for type interval: \"chicken\"\nselect config from alter_job(:job_with_func_check_id, config => '{\"drop_after\":\"5 years\"}');\n          config           \n---------------------------\n {\"drop_after\": \"5 years\"}\n\n-- test that jobs with an incorrect check function signature will not be registered\n-- these are all incorrect function signatures\nCREATE OR REPLACE FUNCTION test_config_check_func_0args() RETURNS VOID\nAS $$\nBEGIN\n    RAISE NOTICE 'I take no arguments and will validate anything you give me!';\nEND\n$$ LANGUAGE PLPGSQL;\nCREATE OR REPLACE FUNCTION test_config_check_func_2args(config jsonb, intarg int) RETURNS VOID\nAS $$\nBEGIN\n    RAISE NOTICE 'I take two arguments (jsonb, int) and I should fail to run!';\nEND\n$$ LANGUAGE PLPGSQL;\nCREATE OR REPLACE FUNCTION test_config_check_func_intarg(config int) RETURNS VOID\nAS $$\nBEGIN\n    RAISE NOTICE 'I take one argument which is an integer and I should fail to run!';\nEND\n$$ LANGUAGE PLPGSQL;\n-- -- this should fail, it has an incorrect check function\nselect add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_config_check_func_0args'::regproc);\nERROR:  function or procedure public.test_config_check_func_0args(config jsonb) not found\n-- -- so should this\nselect add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_config_check_func_2args'::regproc);\nERROR:  function or procedure public.test_config_check_func_2args(config jsonb) not found\n-- and this\nselect add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_config_check_func_intarg'::regproc);\nERROR:  function or procedure public.test_config_check_func_intarg(config jsonb) not found\n-- and this fails as it calls a nonexistent function\nselect add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_nonexistent_check_func'::regproc);\nERROR:  function \"test_nonexistent_check_func\" does not exist at character 82\n-- when called with a valid check function and a NULL config no check should occur\nCREATE OR REPLACE FUNCTION test_config_check_func(config jsonb) RETURNS VOID\nAS $$\nBEGIN\n    RAISE NOTICE 'This message will get printed for both NULL and not NULL config';\nEND\n$$ LANGUAGE PLPGSQL;\nSET client_min_messages = NOTICE;\n-- check done for both NULL and non-NULL config\nselect add_job('test_proc_with_check', '5 secs', config => NULL, check_config => 'test_config_check_func'::regproc);\nNOTICE:  This message will get printed for both NULL and not NULL config\n add_job \n---------\n    1007\n\n-- check done\nselect add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_config_check_func'::regproc) as job_id \\gset\nNOTICE:  This message will get printed for both NULL and not NULL config\n-- check function not returning void\nCREATE OR REPLACE FUNCTION test_config_check_func_returns_int(config jsonb) RETURNS INT\nAS $$\nBEGIN\n    raise notice 'I print a message, and then I return least(1,2)';\n    RETURN LEAST(1, 2);\nEND\n$$ LANGUAGE PLPGSQL;\nselect add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_config_check_func_returns_int'::regproc,\ninitial_start => :'time_zero'::timestamptz) as job_id_int \\gset\nNOTICE:  I print a message, and then I return least(1,2)\n-- rename the check function and then call alter_job to register the new name\nALTER FUNCTION test_config_check_func RENAME TO renamed_func;\nselect job_id, schedule_interval, config, check_config from alter_job(:job_id, check_config => 'renamed_func'::regproc, schedule_interval => '1 hour');\nNOTICE:  This message will get printed for both NULL and not NULL config\n job_id | schedule_interval | config |    check_config     \n--------+-------------------+--------+---------------------\n   1008 | @ 1 hour          | {}     | public.renamed_func\n\n-- run alter again, should get a config check\nselect job_id, schedule_interval, config, check_config from alter_job(:job_id, config => '{}');\nNOTICE:  This message will get printed for both NULL and not NULL config\n job_id | schedule_interval | config |    check_config     \n--------+-------------------+--------+---------------------\n   1008 | @ 1 hour          | {}     | public.renamed_func\n\n-- drop the registered check function, verify that alter_job will work and print a warning that\n-- the check is being skipped due to the check function missing\nDROP FUNCTION test_config_check_func_returns_int;\nselect job_id, schedule_interval, config, check_config from alter_job(:job_id_int, config => '{\"field\":\"value\"}');\nWARNING:  function public.test_config_check_func_returns_int(config jsonb) not found, skipping config validation for job 1009\n job_id | schedule_interval |       config       |               check_config                \n--------+-------------------+--------------------+-------------------------------------------\n   1009 | @ 5 secs          | {\"field\": \"value\"} | public.test_config_check_func_returns_int\n\n-- do not drop the current check function but register a new one\nCREATE OR REPLACE FUNCTION substitute_check_func(config jsonb) RETURNS VOID\nAS $$\nBEGIN\n    RAISE NOTICE 'This message is a substitute of the previously printed one';\nEND\n$$ LANGUAGE PLPGSQL;\n-- register the new check\nselect job_id, schedule_interval, config, check_config from alter_job(:job_id, check_config => 'substitute_check_func');\nNOTICE:  This message is a substitute of the previously printed one\n job_id | schedule_interval | config |         check_config         \n--------+-------------------+--------+------------------------------\n   1008 | @ 1 hour          | {}     | public.substitute_check_func\n\nselect job_id, schedule_interval, config, check_config from alter_job(:job_id, config => '{}');\nNOTICE:  This message is a substitute of the previously printed one\n job_id | schedule_interval | config |         check_config         \n--------+-------------------+--------+------------------------------\n   1008 | @ 1 hour          | {}     | public.substitute_check_func\n\nRESET client_min_messages;\n-- test an oid that doesn't exist\nselect add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 17424217::regproc);\nERROR:  function with OID 17424217 does not exist\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- test a function with insufficient privileges\ncreate schema test_schema;\ncreate role user_noexec with login;\ngrant usage on schema test_schema to user_noexec;\nCREATE OR REPLACE FUNCTION test_schema.test_config_check_func_privileges(config jsonb) RETURNS VOID\nAS $$\nBEGIN\n    RAISE NOTICE 'This message will only get printed if privileges suffice';\nEND\n$$ LANGUAGE PLPGSQL;\nrevoke execute on function test_schema.test_config_check_func_privileges from public;\n-- verify the user doesn't have execute permissions on the function\nselect has_function_privilege('user_noexec', 'test_schema.test_config_check_func_privileges(jsonb)', 'execute');\n has_function_privilege \n------------------------\n f\n\n\\c :TEST_DBNAME user_noexec\n-- user_noexec should not have exec permissions on this function\nselect add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'test_schema.test_config_check_func_privileges'::regproc);\nERROR:  permission denied for function \"test_config_check_func_privileges\"\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- check that alter_job rejects a check function with invalid signature\nselect add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'renamed_func',\ninitial_start => :'time_zero'::timestamptz) as job_id_alter \\gset\nNOTICE:  This message will get printed for both NULL and not NULL config\nselect job_id, schedule_interval, config, check_config from alter_job(:job_id_alter, check_config => 'test_config_check_func_0args');\nERROR:  function or procedure public.test_config_check_func_0args(config jsonb) not found\nselect job_id, schedule_interval, config, check_config from alter_job(:job_id_alter);\nNOTICE:  This message will get printed for both NULL and not NULL config\n job_id | schedule_interval | config |    check_config     \n--------+-------------------+--------+---------------------\n   1010 | @ 5 secs          | {}     | public.renamed_func\n\n-- test that we can unregister the check function\nselect job_id, schedule_interval, config, check_config from alter_job(:job_id_alter, check_config => 0);\n job_id | schedule_interval | config | check_config \n--------+-------------------+--------+--------------\n   1010 | @ 5 secs          | {}     | \n\n-- no message printed now\nselect job_id, schedule_interval, config, check_config from alter_job(:job_id_alter, config => '{}');\n job_id | schedule_interval | config | check_config \n--------+-------------------+--------+--------------\n   1010 | @ 5 secs          | {}     | \n\n-- test the case where we have a background job that registers jobs with a check fn\nCREATE OR REPLACE PROCEDURE add_scheduled_jobs_with_check(job_id int, config jsonb) LANGUAGE PLPGSQL AS\n$$\nBEGIN\n    perform add_job('test_proc_with_check', schedule_interval => '10 secs', config => '{}', check_config => 'renamed_func');\nEND\n$$;\nselect add_job('add_scheduled_jobs_with_check', schedule_interval => '1 hour') as last_job_id \\gset\n-- wait for enough time\nSELECT _timescaledb_functions.restart_background_workers();\n restart_background_workers \n----------------------------\n t\n\nSELECT test.wait_for_job_to_run(:last_job_id, 1);\n wait_for_job_to_run \n---------------------\n t\n\nselect total_runs, total_successes, last_run_status from timescaledb_information.job_stats where job_id = :last_job_id;\n total_runs | total_successes | last_run_status \n------------+-----------------+-----------------\n          1 |               1 | Success\n\n-- test coverage for alter_job\n-- registering an invalid oid\nselect alter_job(:job_id_alter, check_config => 123456789::regproc);\nERROR:  function with OID 123456789 does not exist\n-- registering a function with insufficient privileges\n\\c :TEST_DBNAME user_noexec\nselect * from add_job('test_proc_with_check', '5 secs', config => '{}') as job_id_owner \\gset\nselect * from alter_job(:job_id_owner, check_config => 'test_schema.test_config_check_func_privileges'::regproc);\nERROR:  permission denied for function \"test_config_check_func_privileges\"\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nDROP SCHEMA test_schema CASCADE;\nNOTICE:  drop cascades to function test_schema.test_config_check_func_privileges(jsonb)\n-- Delete all jobs with that owner before we can drop the user.\nDELETE FROM _timescaledb_catalog.bgw_job WHERE owner = 'user_noexec'::regrole;\nDROP ROLE user_noexec;\n-- test with aggregate check proc\ncreate function jsonb_add (j1 jsonb, j2 jsonb) returns jsonb\nAS $$\nBEGIN\n    RETURN j1 || j2;\nEND\n$$ LANGUAGE PLPGSQL;\nCREATE AGGREGATE sum_jsb (jsonb)\n(\n    sfunc = jsonb_add,\n    stype = jsonb,\n    initcond = '{}'\n);\n-- for test coverage, check unsupported aggregate type\nselect add_job('test_proc_with_check', '5 secs', config => '{}', check_config => 'sum_jsb'::regproc);\nERROR:  unsupported function type\n-- Cleanup jobs\nTRUNCATE _timescaledb_catalog.bgw_job CASCADE;\nNOTICE:  truncate cascades to table \"bgw_job_stat\"\nNOTICE:  truncate cascades to table \"bgw_policy_chunk_stats\"\n-- github issue 4610\nCREATE TABLE sensor_data\n(\n    time timestamptz not null,\n    sensor_id integer not null,\n    cpu double precision null,\n    temperature double precision null\n);\nSELECT FROM create_hypertable('sensor_data','time');\n--\n\nSELECT '2022-10-06 00:00:00+00' as start_date_sd \\gset\nINSERT INTO sensor_data\n\tSELECT\n\t\ttime + (INTERVAL '1 minute' * random()) AS time,\n\t\tsensor_id,\n\t\trandom() AS cpu,\n\t\trandom()* 100 AS temperature\n\tFROM\n\t\tgenerate_series(:'start_date_sd'::timestamptz - INTERVAL '1 months', :'start_date_sd'::timestamptz - INTERVAL '1 week', INTERVAL '30 minute') AS g1(time),\n\t\tgenerate_series(1, 50, 1 ) AS g2(sensor_id)\n\tORDER BY\n\t\ttime;\n-- enable compression\nALTER TABLE sensor_data SET (timescaledb.compress, timescaledb.compress_orderby = 'time DESC');\n-- create new chunks\nINSERT INTO sensor_data\n\tSELECT\n\t\ttime + (INTERVAL '1 minute' * random()) AS time,\n\t\tsensor_id,\n\t\trandom() AS cpu,\n\t\trandom()* 100 AS temperature\n\tFROM\n\t\tgenerate_series(:'start_date_sd'::timestamptz - INTERVAL '2 months', :'start_date_sd'::timestamptz - INTERVAL '2 week', INTERVAL '60 minute') AS g1(time),\n\t\tgenerate_series(1, 30, 1 ) AS g2(sensor_id)\n\tORDER BY\n\t\ttime;\n-- get the name of a new uncompressed chunk\nSELECT chunk_name AS new_uncompressed_chunk_name\n  FROM timescaledb_information.chunks\n  WHERE hypertable_name = 'sensor_data' AND NOT is_compressed LIMIT 1 \\gset\n-- change compression status so that this chunk is skipped when policy is run\nupdate _timescaledb_catalog.chunk set status=3 where table_name = :'new_uncompressed_chunk_name';\n-- add new compression policy job\nSELECT add_compression_policy('sensor_data', INTERVAL '1' minute) AS compressjob_id \\gset\n-- set recompress to true\nSELECT alter_job(id,config:=jsonb_set(config,'{recompress}', 'true')) FROM _timescaledb_catalog.bgw_job WHERE id = :compressjob_id;\n                                                                                                      alter_job                                                                                                      \n---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1014,\"@ 12 hours\",\"@ 0\",-1,\"@ 1 hour\",t,\"{\"\"recompress\"\": true, \"\"hypertable_id\"\": 4, \"\"compress_after\"\": \"\"@ 1 min\"\"}\",-infinity,_timescaledb_functions.policy_compression_check,f,,,\"Columnstore Policy [1014]\")\n\n-- verify that there are other uncompressed new chunks that need to be compressed\nSELECT count(*) > 1\n  FROM timescaledb_information.chunks\n  WHERE hypertable_name = 'sensor_data' AND NOT is_compressed;\n ?column? \n----------\n t\n\n-- disable notice/warning as the new_uncompressed_chunk_name\n-- is dynamic and it will be printed in those messages.\nSET client_min_messages TO ERROR;\nCALL run_job(:compressjob_id);\nERROR:  columnstore policy failure\nSET client_min_messages TO NOTICE;\n-- check compression status is not changed for the chunk whose status was manually updated\nSELECT status FROM _timescaledb_catalog.chunk where table_name = :'new_uncompressed_chunk_name';\n status \n--------\n      3\n\n-- confirm all the other new chunks are now compressed despite\n-- facing an error when trying to compress :'new_uncompressed_chunk_name'\nSELECT count(*) = 0\n  FROM timescaledb_information.chunks\n  WHERE hypertable_name = 'sensor_data' AND NOT is_compressed;\n ?column? \n----------\n t\n\n-- cleanup\nSELECT _timescaledb_functions.stop_background_workers();\n stop_background_workers \n-------------------------\n t\n\nDROP TABLE sensor_data;\nSELECT _timescaledb_functions.restart_background_workers();\n restart_background_workers \n----------------------------\n t\n\n-- Github issue #5537\n-- Proc that waits until the given job enters the expected state\nCREATE OR REPLACE PROCEDURE wait_for_job_status(job_param_id INTEGER, expected_status TEXT, spins INTEGER=:TEST_SPINWAIT_ITERS)\nLANGUAGE PLPGSQL AS $$\nDECLARE\n  jobstatus TEXT;\nBEGIN\n  FOR i in 1..spins\n  LOOP\n    SELECT job_status FROM timescaledb_information.job_stats WHERE job_id = job_param_id INTO jobstatus;\n    IF jobstatus = expected_status THEN\n      RETURN;\n    END IF;\n    PERFORM pg_sleep(0.1);\n    ROLLBACK;\n  END LOOP;\n  RAISE EXCEPTION 'wait_for_job_status(%): timeout after % tries', job_param_id, spins;\nEND;\n$$;\n-- Proc that sleeps for 1m - to keep the test jobs in running state\nCREATE OR REPLACE PROCEDURE proc_that_sleeps(job_id INT, config JSONB)\nLANGUAGE PLPGSQL AS\n$$\nBEGIN\n    PERFORM pg_sleep(60);\nEND\n$$;\n-- create new jobs and ensure that the second one gets scheduled\n-- before the first one by adjusting the initial_start values\nSELECT add_job('proc_that_sleeps', '1h', initial_start => now()::timestamptz + interval '2s') AS job_id_1 \\gset\nSELECT add_job('proc_that_sleeps', '1h', initial_start => now()::timestamptz - interval '2s') AS job_id_2 \\gset\nSELECT _timescaledb_functions.restart_background_workers();\n restart_background_workers \n----------------------------\n t\n\n-- wait for the jobs to start running job_2 will start running first\nCALL wait_for_job_status(:job_id_2, 'Running');\nCALL wait_for_job_status(:job_id_1, 'Running');\n-- add a new job and wait for it to start\nSELECT add_job('proc_that_sleeps', '1h') AS job_id_3 \\gset\nCALL wait_for_job_status(:job_id_3, 'Running');\n-- verify that none of the jobs crashed\nSELECT job_id, job_status, next_start,\n       total_runs, total_successes, total_failures\n  FROM timescaledb_information.job_stats\n  WHERE job_id IN (:job_id_1, :job_id_2, :job_id_3)\n  ORDER BY job_id;\n job_id | job_status | next_start | total_runs | total_successes | total_failures \n--------+------------+------------+------------+-----------------+----------------\n   1015 | Running    | -infinity  |          1 |               0 |              0\n   1016 | Running    | -infinity  |          1 |               0 |              0\n   1017 | Running    | -infinity  |          1 |               0 |              0\n\nSELECT job_id, err_message\n  FROM timescaledb_information.job_errors\n  WHERE job_id IN (:job_id_1, :job_id_2, :job_id_3);\n job_id | err_message \n--------+-------------\n\n-- cleanup\nSELECT _timescaledb_functions.stop_background_workers();\n stop_background_workers \n-------------------------\n t\n\nCALL wait_for_job_status(:job_id_1, 'Scheduled');\nCALL wait_for_job_status(:job_id_2, 'Scheduled');\nCALL wait_for_job_status(:job_id_3, 'Scheduled');\nSELECT delete_job(:job_id_1);\n delete_job \n------------\n \n\nSELECT delete_job(:job_id_2);\n delete_job \n------------\n \n\nSELECT delete_job(:job_id_3);\n delete_job \n------------\n \n\nCREATE OR REPLACE FUNCTION ts_test_bgw_job_function_call_string(job_id INTEGER) RETURNS text\nAS :MODULE_PATHNAME LANGUAGE C STABLE STRICT;\n\\set ON_ERROR_STOP 0\nSELECT ts_test_bgw_job_function_call_string(999999);\nERROR:  job 999999 not found\n\\set ON_ERROR_STOP 1\nSELECT add_job('custom_func', '1h') AS job_func \\gset\nSELECT add_job('custom_proc', '1h') AS job_proc \\gset\nSELECT ts_test_bgw_job_function_call_string(:job_func);\n  ts_test_bgw_job_function_call_string   \n-----------------------------------------\n SELECT public.custom_func('1018', NULL)\n\nSELECT ts_test_bgw_job_function_call_string(:job_proc);\n ts_test_bgw_job_function_call_string  \n---------------------------------------\n CALL public.custom_proc('1019', NULL)\n\nSELECT delete_job(:job_func);\n delete_job \n------------\n \n\nSELECT delete_job(:job_proc);\n delete_job \n------------\n \n\nSELECT add_job('custom_func', '1h', config => '{\"type\":\"function\"}'::jsonb) AS job_func \\gset\nSELECT add_job('custom_proc', '1h', config => '{\"type\":\"procedure\"}'::jsonb) AS job_proc \\gset\nSELECT ts_test_bgw_job_function_call_string(:job_func);\n           ts_test_bgw_job_function_call_string            \n-----------------------------------------------------------\n SELECT public.custom_func('1020', '{\"type\": \"function\"}')\n\nSELECT ts_test_bgw_job_function_call_string(:job_proc);\n           ts_test_bgw_job_function_call_string           \n----------------------------------------------------------\n CALL public.custom_proc('1021', '{\"type\": \"procedure\"}')\n\n-- Remove the procedure and let's check it fallingback to PROKIND_FUNCTION\nDROP PROCEDURE custom_proc(jobid int, args jsonb);\nSELECT ts_test_bgw_job_function_call_string(:job_proc);\n            ts_test_bgw_job_function_call_string            \n------------------------------------------------------------\n SELECT public.custom_proc('1021', '{\"type\": \"procedure\"}')\n\n\\set ON_ERROR_STOP 0\n-- Mess with pg catalog to don't identify the PROKIND\nBEGIN;\nUPDATE pg_catalog.pg_proc SET prokind = 'X' WHERE oid = 'custom_func(int,jsonb)'::regprocedure;\nSELECT ts_test_bgw_job_function_call_string(:job_func);\nERROR:  unsupported function type: X\nROLLBACK;\n\\set ON_ERROR_STOP 1\nSELECT delete_job(:job_func);\n delete_job \n------------\n \n\nSELECT delete_job(:job_proc);\n delete_job \n------------\n \n\n-- Test work_mem config option in job execution\nCREATE TABLE work_mem_log(job_id int, work_mem_value text);\nCREATE OR REPLACE PROCEDURE log_work_mem(job_id int, config jsonb) LANGUAGE PLPGSQL AS\n$$\nBEGIN\n    INSERT INTO work_mem_log VALUES(job_id, current_setting('work_mem'));\nEND\n$$;\n-- Add job with work_mem in config\nSELECT add_job('log_work_mem', '1h', config => '{\"work_mem\": \"123MB\"}'::jsonb) AS job_work_mem \\gset\n-- Run the job - work_mem should be set to 123MB before execution\nCALL run_job(:job_work_mem);\n-- Verify work_mem was set during job execution\nSELECT * FROM work_mem_log;\n job_id | work_mem_value \n--------+----------------\n   1022 | 123MB\n\n-- Cleanup\nSELECT delete_job(:job_work_mem);\n delete_job \n------------\n \n\nDROP TABLE work_mem_log;\nDROP PROCEDURE log_work_mem;\n"
  },
  {
    "path": "tsl/test/expected/bgw_db_scheduler.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n--\n-- Setup\n--\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(timeout INT = -1, mock_start_time INT = 0) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_db_scheduler_test_run(timeout INT = -1, mock_start_time INT = 0) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_db_scheduler_test_wait_for_scheduler_finish() RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_params_create() RETURNS VOID AS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_params_destroy() RETURNS VOID AS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_test_job_sleep(job_id INT, config JSONB) RETURNS VOID AS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_params_reset_time(set_time BIGINT = 0, wait BOOLEAN = false) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION insert_job(\n       application_name NAME,\n       job_type NAME,\n       schedule_interval INTERVAL,\n       max_runtime INTERVAL,\n       retry_period INTERVAL,\n       owner regrole DEFAULT CURRENT_ROLE::regrole,\n       scheduled BOOL DEFAULT true,\n       fixed_schedule BOOL DEFAULT false\n) RETURNS INT LANGUAGE SQL SECURITY DEFINER AS\n$$\n  INSERT INTO _timescaledb_catalog.bgw_job(application_name,schedule_interval,max_runtime,max_retries,\n  retry_period,proc_name,proc_schema,owner,scheduled,fixed_schedule)\n  VALUES($1,$3,$4,5,$5,$2,'public',$6,$7,$8) RETURNING id;\n$$;\nCREATE OR REPLACE FUNCTION test_toggle_scheduled(job_id INTEGER) RETURNS VOID LANGUAGE SQL SECURITY DEFINER AS\n$$\n  UPDATE _timescaledb_catalog.bgw_job SET scheduled = NOT scheduled WHERE id = $1;\n$$;\n\\set WAIT_ON_JOB 0\n\\set IMMEDIATELY_SET_UNTIL 1\n\\set WAIT_FOR_OTHER_TO_ADVANCE 2\n\\set WAIT_FOR_STANDARD_WAITLATCH 3\nCREATE OR REPLACE FUNCTION ts_bgw_params_mock_wait_returns_immediately(new_val INTEGER) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE FUNCTION get_application_pid(app_name TEXT) RETURNS INTEGER LANGUAGE SQL AS\n$BODY$\n    SELECT pid FROM pg_stat_activity WHERE application_name = app_name;\n$BODY$;\nCREATE FUNCTION wait_application_pid(app_name TEXT, wait_for_start BOOLEAN = true) RETURNS INTEGER LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n    r INTEGER;\nBEGIN\n    --wait up to a second checking each 100ms\n    FOR i in 1..10\n    LOOP\n        SELECT get_application_pid(app_name) INTO r;\n        IF (wait_for_start AND r IS NULL) OR (NOT wait_for_start AND r IS NOT NULL) THEN\n            PERFORM pg_sleep(0.1);\n            PERFORM pg_stat_clear_snapshot();\n        ELSE\n            RETURN r;\n        END IF;\n    END LOOP;\n    RETURN NULL;\nEND\n$BODY$;\nCREATE FUNCTION wait_for_logentry(job_id INTEGER) RETURNS TEXT LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n  app_name TEXT;\n  message TEXT;\nBEGIN\n    SELECT application_name INTO app_name FROM _timescaledb_catalog.bgw_job WHERE id = job_id;\n\n    --wait up to a second checking each 100ms\n    FOR i in 1..10\n    LOOP\n        SELECT msg INTO message FROM bgw_log WHERE application_name = app_name ORDER BY msg_no DESC LIMIT 1;\n        IF FOUND THEN\n          RETURN message;\n        END IF;\n        PERFORM pg_sleep(0.1);\n        PERFORM pg_stat_clear_snapshot();\n    END LOOP;\n    RETURN NULL;\nEND\n$BODY$;\n-- Remove any default jobs, e.g., telemetry\nDELETE FROM _timescaledb_catalog.bgw_job WHERE TRUE;\nTRUNCATE _timescaledb_internal.bgw_job_stat;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nCREATE TABLE public.bgw_log(\n    msg_no INT,\n    mock_time BIGINT,\n    application_name TEXT,\n    msg TEXT\n);\nCREATE VIEW sorted_bgw_log AS\n    SELECT msg_no,\n    \t   application_name,\n    \t   regexp_replace(regexp_replace(msg, '(Wait until|started at|execution time) [0-9]+(\\.[0-9]+)?', '\\1 (RANDOM)', 'g'), 'background worker \"[^\"]+\"','connection') AS msg\n\t   FROM bgw_log ORDER BY mock_time, application_name COLLATE \"C\", msg_no;\nCREATE TABLE public.bgw_dsm_handle_store(\n    handle BIGINT\n);\nINSERT INTO public.bgw_dsm_handle_store VALUES (0);\nSELECT ts_bgw_params_create();\n ts_bgw_params_create \n----------------------\n \n\n--\n-- Test running the scheduler with no jobs\n--\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(50);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\n-- empty\n-- turn on extended display to make the many fields of the table easier to parse\n\\x on\nSELECT * FROM _timescaledb_internal.bgw_job_stat;\n\n\\x off\n-- empty\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n\n--\n-- Test running the scheduler with a job marked as unscheduled\n--\nTRUNCATE bgw_log;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT insert_job('unscheduled', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s',scheduled:= false);\n insert_job \n------------\n       1000\n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(50);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\n-- empty\n\\x on\nSELECT * FROM _timescaledb_internal.bgw_job_stat;\n\n\\x off\nSELECT * FROM timescaledb_information.job_stats;\n hypertable_schema | hypertable_name | job_id | last_run_started_at | last_successful_finish | last_run_status | job_status | last_run_duration | next_start | total_runs | total_successes | total_failures \n-------------------+-----------------+--------+---------------------+------------------------+-----------------+------------+-------------------+------------+------------+-----------------+----------------\n                   |                 |   1000 |                     |                        |                 | Paused     |                   |            |            |                 |               \n\nSELECT test_toggle_scheduled(1000);\n test_toggle_scheduled \n-----------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(50);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\n\\x on\nSELECT * FROM _timescaledb_internal.bgw_job_stat;\n-[ RECORD 1 ]-----------+--------------------------------\njob_id                  | 1000\nlast_start              | Fri Dec 31 16:00:00.05 1999 PST\nlast_finish             | Fri Dec 31 16:00:00.05 1999 PST\nnext_start              | Fri Dec 31 16:00:00.15 1999 PST\nlast_successful_finish  | Fri Dec 31 16:00:00.05 1999 PST\nlast_run_success        | t\ntotal_runs              | 1\ntotal_duration          | @ 0\ntotal_duration_failures | @ 0\ntotal_successes         | 1\ntotal_failures          | 0\ntotal_crashes           | 0\nconsecutive_failures    | 0\nconsecutive_crashes     | 0\nflags                   | 0\n\n\\x off\nSELECT * FROM timescaledb_information.job_stats;\n hypertable_schema | hypertable_name | job_id |       last_run_started_at       |     last_successful_finish      | last_run_status | job_status | last_run_duration |           next_start            | total_runs | total_successes | total_failures \n-------------------+-----------------+--------+---------------------------------+---------------------------------+-----------------+------------+-------------------+---------------------------------+------------+-----------------+----------------\n                   |                 |   1000 | Fri Dec 31 16:00:00.05 1999 PST | Fri Dec 31 16:00:00.05 1999 PST | Success         | Scheduled  |                   | Fri Dec 31 16:00:00.15 1999 PST |          1 |               1 |              0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | unscheduled      | Execute job 1\n\nSELECT delete_job(1000);\n delete_job \n------------\n \n\n--\n-- test deleting job also terminates running jobs\n--\nSELECT add_job('ts_bgw_test_job_sleep','1h') AS job_id \\gset\nSELECT ts_bgw_db_scheduler_test_run();\n ts_bgw_db_scheduler_test_run \n------------------------------\n \n\nSELECT wait_for_logentry(:job_id);\n wait_for_logentry \n-------------------\n Before sleep\n\nSELECT application_name FROM pg_stat_activity WHERE application_name LIKE 'User-Defined Action%';\n      application_name      \n----------------------------\n User-Defined Action [1001]\n\n\\x on\nSELECT job_id, job_status FROM timescaledb_information.job_stats;\n-[ RECORD 1 ]-------\njob_id     | 1001\njob_status | Running\n\n-- Showing non-volatile information from pg_stat_activity for\n-- debugging purposes. Information schema above reads from this view.\nSELECT datname, usename, application_name, state, query, wait_event_type, wait_event\n  FROM pg_stat_activity WHERE application_name LIKE 'User-Defined Action%';\n-[ RECORD 1 ]----+------------------------------------\ndatname          | db_bgw_db_scheduler\nusename          | default_perm_user\napplication_name | User-Defined Action [1001]\nstate            | active\nquery            | CALL public.ts_bgw_test_job_sleep()\nwait_event_type  | Timeout\nwait_event       | PgSleep\n\n\\x off\nSELECT delete_job(:job_id);\n delete_job \n------------\n \n\nSELECT wait_application_pid('User-Defined Action [' || :job_id || ']', false);\n wait_application_pid \n----------------------\n                     \n\nSELECT application_name FROM pg_stat_activity WHERE application_name LIKE 'User-Defined Action%';\n application_name \n------------------\n\n-- wait for scheduler finish\nSELECT ts_bgw_db_scheduler_test_wait_for_scheduler_finish();\n ts_bgw_db_scheduler_test_wait_for_scheduler_finish \n----------------------------------------------------\n \n\n--\n-- Test running a normal job\n--\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE bgw_log;\nALTER SEQUENCE _timescaledb_catalog.bgw_job_id_seq RESTART;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT insert_job('test_job_1', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s');\n insert_job \n------------\n       1000\n\nselect * from _timescaledb_catalog.bgw_job;\n  id  | application_name | schedule_interval |   max_runtime   | max_retries | retry_period | proc_schema |   proc_name    |   owner    | scheduled | fixed_schedule | initial_start | hypertable_id | config | check_schema | check_name | timezone \n------+------------------+-------------------+-----------------+-------------+--------------+-------------+----------------+------------+-----------+----------------+---------------+---------------+--------+--------------+------------+----------\n 1000 | test_job_1       | @ 0.1 secs        | @ 1 min 40 secs |           5 | @ 1 sec      | public      | bgw_test_job_1 | super_user | t         | f              |               |               |        |              |            | \n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n--Tests that the scheduler start a job right away if it's the first time and there is no job_stat entry for it\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, next_start, last_finish as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id |           next_start           |          until_next          | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+--------------------------------+------------------------------+------------------+------------+-----------------+----------------+---------------\n   1000 | Fri Dec 31 16:00:00.1 1999 PST | Fri Dec 31 16:00:00 1999 PST | t                |          1 |               1 |              0 |             0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_1       | Execute job 1\n\n--Test that the scheduler will not run job again if not enough time has passed\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25, 25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1000 | t                |          1 |               1 |              0 |             0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_1       | Execute job 1\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n\n--After enough time has passed the scheduler will run the job again\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(100, 50);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, next_start, last_finish, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id |           next_start           |          last_finish           | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+--------------------------------+--------------------------------+------------------+------------+-----------------+----------------+---------------\n   1000 | Fri Dec 31 16:00:00.2 1999 PST | Fri Dec 31 16:00:00.1 1999 PST | t                |          2 |               2 |              0 |             0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_1       | Execute job 1\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_1       | Execute job 1\n\n--Now it runs it one more time\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(120, 100);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, next_start, last_finish, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id |           next_start           |          last_finish           | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+--------------------------------+--------------------------------+------------------+------------+-----------------+----------------+---------------\n   1000 | Fri Dec 31 16:00:00.3 1999 PST | Fri Dec 31 16:00:00.2 1999 PST | t                |          3 |               3 |              0 |             0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_1       | Execute job 1\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_1       | Execute job 1\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_1       | Execute job 1\n\n--\n-- Test what happens when running a job that throws an error\n--\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE bgw_log;\nTRUNCATE _timescaledb_internal.bgw_job_stat;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nDELETE FROM _timescaledb_catalog.bgw_job;\nSELECT insert_job('test_job_2', 'bgw_test_job_2_error', INTERVAL '100ms', INTERVAL '100s', INTERVAL '100ms') AS test_job_2_id \\gset\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n--Run the first time and error\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1001 | f                |          1 |               0 |              1 |             0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n\nSELECT last_finish, last_successful_finish, last_run_success FROM _timescaledb_internal.bgw_job_stat;\n         last_finish          | last_successful_finish | last_run_success \n------------------------------+------------------------+------------------\n Fri Dec 31 16:00:00 1999 PST | -infinity              | f\n\n--Scheduler runs the job again, sees another error, and increases the wait time\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(125);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1001 | f                |          2 |               0 |              2 |             0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n\n--The job runs and fails again a few more times increasing the wait time each time.\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(225);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1001 | f                |          3 |               0 |              3 |             0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(425);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1001 | f                |          4 |               0 |              4 |             0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n\n--Once the wait time reaches 500ms it stops increasion\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(525);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1001 | f                |          5 |               0 |              5 |             0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                            msg                            \n--------+------------------+-----------------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 reached max_retries after 5 consecutive failures\n      2 | test_job_2       | job 1001 threw an error\n      3 | test_job_2       | Error job 2\n\n-- Get status of failing job `test_job_2` to check it reached `max_retries` and\n-- the new `job_status` now is `Paused`\nSELECT job_id, last_run_status, job_status, total_runs, total_successes, total_failures\nFROM timescaledb_information.job_stats WHERE job_id = :test_job_2_id;\n job_id | last_run_status | job_status | total_runs | total_successes | total_failures \n--------+-----------------+------------+------------+-----------------+----------------\n   1001 | Failed          | Paused     |          5 |               0 |              5\n\n-- Alter job to be rescheduled and run it again\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE bgw_log;\nSELECT scheduled FROM alter_job(:test_job_2_id, scheduled => true) AS discard;\n scheduled \n-----------\n t\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(525);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat WHERE job_id = :test_job_2_id;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1001 | f                |          6 |               0 |              6 |             0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                            msg                            \n--------+------------------+-----------------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 reached max_retries after 6 consecutive failures\n      2 | test_job_2       | job 1001 threw an error\n      3 | test_job_2       | Error job 2\n\n--\n-- Test timeout logic\n--\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE bgw_log;\nTRUNCATE _timescaledb_internal.bgw_job_stat;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nDELETE FROM _timescaledb_catalog.bgw_job;\n--set timeout lower than job length\nSELECT insert_job('test_job_3_long', 'bgw_test_job_3_long', INTERVAL '5000ms', INTERVAL '20ms', INTERVAL '50ms');\n insert_job \n------------\n       1002\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT ts_bgw_params_mock_wait_returns_immediately(:IMMEDIATELY_SET_UNTIL);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\n--Test that the scheduler kills a job that takes too long\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(200);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes, consecutive_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes | consecutive_crashes \n--------+------------------+------------+-----------------+----------------+---------------+---------------------\n   1002 | f                |          1 |               0 |              1 |             0 |                   0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      2 | DB Scheduler     | terminating connection due to timeout\n      3 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      4 | DB Scheduler     | job 1002 failed\n\n--Check that the scheduler does not kill a job with infinite timeout\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE bgw_log;\nTRUNCATE _timescaledb_internal.bgw_job_stat;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nDELETE FROM _timescaledb_catalog.bgw_job;\n--set timeout to 0\nSELECT insert_job('test_job_3_long', 'bgw_test_job_3_long', INTERVAL '5000ms', INTERVAL '0', INTERVAL '10ms');\n insert_job \n------------\n       1003\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(550);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes, consecutive_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes | consecutive_crashes \n--------+------------------+------------+-----------------+----------------+---------------+---------------------\n   1003 | t                |          1 |               1 |              0 |             0 |                   0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_3_long  | Before sleep job 3\n      1 | test_job_3_long  | After sleep job 3\n\nSELECT ts_bgw_params_mock_wait_returns_immediately(:WAIT_ON_JOB);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\n--\n-- Test signal handling\n--\n--Test sending a SIGTERM to a job\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE bgw_log;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nTRUNCATE _timescaledb_internal.bgw_job_stat;\nDELETE FROM _timescaledb_catalog.bgw_job;\nSELECT insert_job('test_job_3_long', 'bgw_test_job_3_long', INTERVAL '5000ms', INTERVAL '100s', INTERVAL '500ms');\n insert_job \n------------\n       1004\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n--escalated priv needed for access to pg_stat_activity\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT ts_bgw_db_scheduler_test_run(300);\n ts_bgw_db_scheduler_test_run \n------------------------------\n \n\nSELECT pg_terminate_backend(wait_application_pid('test_job_3_long'));\n pg_terminate_backend \n----------------------\n t\n\nSELECT ts_bgw_db_scheduler_test_wait_for_scheduler_finish();\n ts_bgw_db_scheduler_test_wait_for_scheduler_finish \n----------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                         msg                         \n--------+------------------+-----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_3_long  | Before sleep job 3\n      1 | test_job_3_long  | terminating connection due to administrator command\n      2 | DB Scheduler     | job 1004 failed\n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1004 | f                |          1 |               0 |              1 |             0\n\n-- Test that the job is able to run again and succeed\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(900);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1004 | t                |          2 |               1 |              1 |             0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                         msg                         \n--------+------------------+-----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_3_long  | Before sleep job 3\n      1 | test_job_3_long  | terminating connection due to administrator command\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      2 | DB Scheduler     | job 1004 failed\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_3_long  | Before sleep job 3\n      1 | test_job_3_long  | After sleep job 3\n\n--Test sending a SIGHUP to a job\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE bgw_log;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nTRUNCATE _timescaledb_internal.bgw_job_stat;\nDELETE FROM _timescaledb_catalog.bgw_job;\nSELECT ts_bgw_params_mock_wait_returns_immediately(:WAIT_FOR_STANDARD_WAITLATCH);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run(-1);\n ts_bgw_db_scheduler_test_run \n------------------------------\n \n\nSHOW timescaledb.shutdown_bgw_scheduler;\n timescaledb.shutdown_bgw_scheduler \n------------------------------------\n off\n\nALTER SYSTEM SET timescaledb.shutdown_bgw_scheduler TO 'on';\nSELECT pg_reload_conf();\n pg_reload_conf \n----------------\n t\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSHOW timescaledb.shutdown_bgw_scheduler;\n timescaledb.shutdown_bgw_scheduler \n------------------------------------\n on\n\nSELECT ts_bgw_db_scheduler_test_wait_for_scheduler_finish();\n ts_bgw_db_scheduler_test_wait_for_scheduler_finish \n----------------------------------------------------\n \n\n-- The number of scheduler restarts is not deterministic during [..]_wait_for_scheduler_finish().\n-- Therefore, we filter these messages to get a deterministic test output.\nSELECT * FROM sorted_bgw_log WHERE msg NOT LIKE '[TESTING] Wait until%';\n msg_no | application_name |                      msg                      \n--------+------------------+-----------------------------------------------\n      0 | DB Scheduler     | bgw scheduler stopped due to shutdown_bgw guc\n\nALTER SYSTEM RESET timescaledb.shutdown_bgw_scheduler;\nSELECT pg_reload_conf();\n pg_reload_conf \n----------------\n t\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSHOW timescaledb.shutdown_bgw_scheduler;\n timescaledb.shutdown_bgw_scheduler \n------------------------------------\n off\n\nSELECT ts_bgw_params_mock_wait_returns_immediately(:WAIT_ON_JOB);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\n--Test that sending SIGTERM to scheduler terminates the jobs as well\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE bgw_log;\nTRUNCATE _timescaledb_internal.bgw_job_stat;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nDELETE FROM _timescaledb_catalog.bgw_job;\nSELECT insert_job('test_job_3_long', 'bgw_test_job_3_long', INTERVAL '5000ms', INTERVAL '100s', INTERVAL '10ms');\n insert_job \n------------\n       1005\n\nSELECT ts_bgw_db_scheduler_test_run(500);\n ts_bgw_db_scheduler_test_run \n------------------------------\n \n\nSELECT wait_application_pid('test_job_3_long') IS NOT NULL ;\n ?column? \n----------\n t\n\nSELECT pg_terminate_backend(wait_application_pid('DB Scheduler Test'));\n pg_terminate_backend \n----------------------\n t\n\nSELECT ts_bgw_db_scheduler_test_wait_for_scheduler_finish();\n ts_bgw_db_scheduler_test_wait_for_scheduler_finish \n----------------------------------------------------\n \n\nSELECT job_id, last_finish, last_run_success, total_runs, total_successes, total_failures, total_crashes, consecutive_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id | last_finish | last_run_success | total_runs | total_successes | total_failures | total_crashes | consecutive_crashes \n--------+-------------+------------------+------------+-----------------+----------------+---------------+---------------------\n   1005 | -infinity   | f                |          1 |               0 |              0 |             1 |                   1\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                         msg                         \n--------+------------------+-----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      2 | DB Scheduler     | terminating connection due to administrator command\n      0 | test_job_3_long  | Before sleep job 3\n      1 | test_job_3_long  | terminating connection due to administrator command\n\n--After a SIGTERM to scheduler and jobs, the jobs are considered crashed and there is a imposed wait of 5 min before a job can be run.\n--See that there is no run again because of the crash-imposed wait (not run with the 10ms retry_period)\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(500);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_finish, next_start, last_run_success, total_runs, total_successes, total_failures, total_crashes, consecutive_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id | last_finish | next_start | last_run_success | total_runs | total_successes | total_failures | total_crashes | consecutive_crashes \n--------+-------------+------------+------------------+------------+-----------------+----------------+---------------+---------------------\n   1005 | -infinity   | -infinity  | f                |          1 |               0 |              0 |             1 |                   1\n\n--But after the 5 min period the job is again run\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(400000);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_finish, next_start, last_run_success, total_runs, total_successes, total_failures, total_crashes, consecutive_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id |          last_finish           |           next_start           | last_run_success | total_runs | total_successes | total_failures | total_crashes | consecutive_crashes \n--------+--------------------------------+--------------------------------+------------------+------------+-----------------+----------------+---------------+---------------------\n   1005 | Fri Dec 31 16:05:00.5 1999 PST | Fri Dec 31 16:05:05.5 1999 PST | t                |          2 |               1 |              0 |             1 |                   0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                         msg                         \n--------+------------------+-----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      2 | DB Scheduler     | terminating connection due to administrator command\n      0 | test_job_3_long  | Before sleep job 3\n      1 | test_job_3_long  | terminating connection due to administrator command\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_3_long  | Before sleep job 3\n      1 | test_job_3_long  | After sleep job 3\n\nCREATE FUNCTION wait_for_timer_to_run(started_at INTEGER, spins INTEGER=:TEST_SPINWAIT_ITERS) RETURNS BOOLEAN LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n\tnum_runs INTEGER;\n\tmessage TEXT;\nBEGIN\n\tselect format('[TESTING] Wait until %%, started at %s', started_at) into message;\n\tFOR i in 1..spins\n\tLOOP\n\tSELECT COUNT(*) from bgw_log where msg LIKE message INTO num_runs;\n\tif (num_runs > 0) THEN\n\t\tRETURN true;\n\tELSE\n\t\tPERFORM pg_sleep(0.1);\n\tEND IF;\n\tEND LOOP;\n\tRETURN false;\nEND\n$BODY$;\nCREATE FUNCTION wait_for_job_3_to_finish(runs INTEGER, spins INTEGER=:TEST_SPINWAIT_ITERS) RETURNS BOOLEAN LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n\tnum_runs INTEGER;\nBEGIN\n\tFOR i in 1..spins\n\tLOOP\n\tSELECT COUNT(*) from bgw_log where msg='After sleep job 3' INTO num_runs;\n\tif (num_runs = runs) THEN\n\t\tRETURN true;\n\tELSE\n\t\tPERFORM pg_sleep(0.1);\n\tEND IF;\n\tEND LOOP;\n\tRETURN false;\nEND\n$BODY$;\n--\n-- Test starting more jobs than availlable workers\n--\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE bgw_log;\nTRUNCATE _timescaledb_internal.bgw_job_stat;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nDELETE FROM _timescaledb_catalog.bgw_job;\nSELECT ts_bgw_params_mock_wait_returns_immediately(:WAIT_FOR_OTHER_TO_ADVANCE);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\n--Our normal limit is 8 jobs (1 already taken up by the launcher, we don't register the test scheduler)\n--so start 8 workers. Make the schedule_INTERVAL long and the retry period short so that the\n--retries happen within the scheduler run time but everything only runs once.\nSELECT\ninsert_job('test_job_3_long_1', 'bgw_test_job_3_long', INTERVAL '5000ms', INTERVAL '100s', INTERVAL '10ms'),\ninsert_job('test_job_3_long_2', 'bgw_test_job_3_long', INTERVAL '5000ms', INTERVAL '100s', INTERVAL '10ms'),\ninsert_job('test_job_3_long_3', 'bgw_test_job_3_long', INTERVAL '5000ms', INTERVAL '100s', INTERVAL '10ms'),\ninsert_job('test_job_3_long_4', 'bgw_test_job_3_long', INTERVAL '5000ms', INTERVAL '100s', INTERVAL '10ms'),\ninsert_job('test_job_3_long_5', 'bgw_test_job_3_long', INTERVAL '5000ms', INTERVAL '100s', INTERVAL '10ms'),\ninsert_job('test_job_3_long_6', 'bgw_test_job_3_long', INTERVAL '5000ms', INTERVAL '100s', INTERVAL '10ms');\n insert_job | insert_job | insert_job | insert_job | insert_job | insert_job \n------------+------------+------------+------------+------------+------------\n       1006 |       1007 |       1008 |       1009 |       1010 |       1011\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT ts_bgw_db_scheduler_test_run(25000); --quit at second 25\n ts_bgw_db_scheduler_test_run \n------------------------------\n \n\n--the first 7 jobs will run right away, but not the last one\nSELECT wait_for_timer_to_run(0);\n wait_for_timer_to_run \n-----------------------\n t\n\nSELECT wait_for_job_3_to_finish(6);\n wait_for_job_3_to_finish \n--------------------------\n t\n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes, consecutive_crashes\nFROM _timescaledb_internal.bgw_job_stat\nORDER BY job_id;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes | consecutive_crashes \n--------+------------------+------------+-----------------+----------------+---------------+---------------------\n   1006 | t                |          1 |               1 |              0 |             0 |                   0\n   1007 | t                |          1 |               1 |              0 |             0 |                   0\n   1008 | t                |          1 |               1 |              0 |             0 |                   0\n   1009 | t                |          1 |               1 |              0 |             0 |                   0\n   1010 | t                |          1 |               1 |              0 |             0 |                   0\n   1011 | t                |          1 |               1 |              0 |             0 |                   0\n\nSELECT ts_bgw_params_reset_time(30000000, true); --set to second 30, which causes a quit.\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_wait_for_scheduler_finish();\n ts_bgw_db_scheduler_test_wait_for_scheduler_finish \n----------------------------------------------------\n \n\n--should have all 8 runs, all with success runs\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes, consecutive_crashes\nFROM _timescaledb_internal.bgw_job_stat\nORDER BY job_id;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes | consecutive_crashes \n--------+------------------+------------+-----------------+----------------+---------------+---------------------\n   1006 | t                |          1 |               1 |              0 |             0 |                   0\n   1007 | t                |          1 |               1 |              0 |             0 |                   0\n   1008 | t                |          1 |               1 |              0 |             0 |                   0\n   1009 | t                |          1 |               1 |              0 |             0 |                   0\n   1010 | t                |          1 |               1 |              0 |             0 |                   0\n   1011 | t                |          1 |               1 |              0 |             0 |                   0\n\nSELECT * FROM sorted_bgw_log WHERE application_name = 'DB Scheduler' ORDER BY application_name, msg_no;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Registered new background worker\n      3 | DB Scheduler     | [TESTING] Registered new background worker\n      4 | DB Scheduler     | [TESTING] Registered new background worker\n      5 | DB Scheduler     | [TESTING] Registered new background worker\n      6 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n\nSELECT ts_bgw_params_destroy();\n ts_bgw_params_destroy \n-----------------------\n \n\n--\n-- Test setting next_start time within a job\n--\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE bgw_log;\nTRUNCATE _timescaledb_internal.bgw_job_stat;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_params_mock_wait_returns_immediately(:WAIT_ON_JOB);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\nDELETE FROM _timescaledb_catalog.bgw_job;\nSELECT insert_job('test_job_4', 'bgw_test_job_4', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s');\n insert_job \n------------\n       1012\n\nselect * from _timescaledb_catalog.bgw_job;\n  id  | application_name | schedule_interval |   max_runtime   | max_retries | retry_period | proc_schema |   proc_name    |   owner    | scheduled | fixed_schedule | initial_start | hypertable_id | config | check_schema | check_name | timezone \n------+------------------+-------------------+-----------------+-------------+--------------+-------------+----------------+------------+-----------+----------------+---------------+---------------+--------+--------------+------------+----------\n 1012 | test_job_4       | @ 0.1 secs        | @ 1 min 40 secs |           5 | @ 1 sec      | public      | bgw_test_job_4 | super_user | t         | f              |               |               |        |              |            | \n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n-- Now run and make sure next_start is 200ms away, not 100ms\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1012 | t                |          1 |               1 |              0 |             0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_4       | Execute job 4\n\n-- Now just make sure that the job actually runs in 200ms\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(200);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\n-- Print next_start and last_finish explicitly, instead of the difference, to make sure the times have changed\n-- since the last run\nSELECT job_id, next_start, last_finish, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id |           next_start           |          last_finish           | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+--------------------------------+--------------------------------+------------------+------------+-----------------+----------------+---------------\n   1012 | Fri Dec 31 16:00:00.4 1999 PST | Fri Dec 31 16:00:00.2 1999 PST | t                |          2 |               2 |              0 |             0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_4       | Execute job 4\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_4       | Execute job 4\n\n-- Test updating jobs list\nTRUNCATE bgw_log;\n\\set ON_ERROR_STOP 0\nSELECT _timescaledb_functions.stop_background_workers();\nERROR:  must be superuser to stop background workers\nSELECT _timescaledb_functions.restart_background_workers();\nERROR:  must be superuser to restart background workers\nSELECT _timescaledb_functions.start_background_workers();\nERROR:  must be superuser to start background workers\n\\set ON_ERROR_STOP 1\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT _timescaledb_functions.stop_background_workers();\n stop_background_workers \n-------------------------\n t\n\nCREATE OR REPLACE FUNCTION ts_test_job_refresh() RETURNS TABLE(\nid INTEGER,\napplication_name NAME,\nschedule_interval INTERVAL,\nmax_runtime INTERVAL,\nmax_retries INT,\nretry_period INTERVAL,\nnext_start TIMESTAMPTZ,\ntimeout_at TIMESTAMPTZ,\nreserved_worker BOOLEAN,\nmay_next_mark_end BOOLEAN\n)\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE FUNCTION verify_refresh_correct() RETURNS BOOLEAN LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n    num_jobs INTEGER;\n    num_jobs_in_list INTEGER;\nBEGIN\n    SELECT COUNT(*) from _timescaledb_catalog.bgw_job INTO num_jobs;\n\tselect COUNT(*) from ts_test_job_refresh() JOIN _timescaledb_catalog.bgw_job USING (id,application_name,schedule_interval,max_runtime,max_retries,retry_period) INTO num_jobs_in_list;\n\tIF (num_jobs = num_jobs_in_list) THEN\n\t\tRETURN true;\n\tEND IF;\n\tRETURN false;\nEND\n$BODY$;\nCREATE FUNCTION wait_for_job_1_to_run(runs INTEGER, spins INTEGER=:TEST_SPINWAIT_ITERS) RETURNS BOOLEAN LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n\tnum_runs INTEGER;\nBEGIN\n\tFOR i in 1..spins\n\tLOOP\n\tSELECT COUNT(*) from bgw_log where msg='Execute job 1' INTO num_runs;\n\tif (num_runs = runs) THEN\n\t\tRETURN true;\n\tELSE\n\t\tPERFORM pg_sleep(0.1);\n\tEND IF;\n\tEND LOOP;\n\tRETURN false;\nEND\n$BODY$;\nselect * from verify_refresh_correct();\n verify_refresh_correct \n------------------------\n t\n\n-- Should return the same table\nselect * from verify_refresh_correct();\n verify_refresh_correct \n------------------------\n t\n\nDELETE FROM _timescaledb_catalog.bgw_job;\n-- Make sure jobs list is empty\nselect count(*) from ts_test_job_refresh();\n count \n-------\n     0\n\nSELECT\ninsert_job('test_1', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s'),\ninsert_job('test_2', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s'),\ninsert_job('test_3', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s');\n insert_job | insert_job | insert_job \n------------+------------+------------\n       1013 |       1014 |       1015\n\nselect * from verify_refresh_correct();\n verify_refresh_correct \n------------------------\n t\n\nDELETE from _timescaledb_catalog.bgw_job where application_name='test_2';\nSELECT insert_job('test_4', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s');\n insert_job \n------------\n       1016\n\nselect * from verify_refresh_correct();\n verify_refresh_correct \n------------------------\n t\n\nDELETE FROM _timescaledb_catalog.bgw_job;\nSELECT insert_job('test_10', 'test_10', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s');\n insert_job \n------------\n       1017\n\nselect * from verify_refresh_correct();\n verify_refresh_correct \n------------------------\n t\n\n-- Should be idempotent\nselect * from verify_refresh_correct();\n verify_refresh_correct \n------------------------\n t\n\nDELETE FROM _timescaledb_catalog.bgw_job;\nSELECT\ninsert_job('another', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s'),\ninsert_job('another1', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s'),\ninsert_job('another2', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s'),\ninsert_job('another3', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s'),\ninsert_job('another4', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s');\n insert_job | insert_job | insert_job | insert_job | insert_job \n------------+------------+------------+------------+------------\n       1018 |       1019 |       1020 |       1021 |       1022\n\nselect * from verify_refresh_correct();\n verify_refresh_correct \n------------------------\n t\n\nDELETE FROM _timescaledb_catalog.bgw_job where application_name='another' OR application_name='another3';\nSELECT insert_job('blah', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s');\n insert_job \n------------\n       1023\n\nselect * from verify_refresh_correct();\n verify_refresh_correct \n------------------------\n t\n\n-- Now test a real scheduler-mock running in a loop and updating the list of jobs\nTRUNCATE _timescaledb_internal.bgw_job_stat;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nDELETE FROM _timescaledb_catalog.bgw_job;\nSELECT ts_bgw_params_mock_wait_returns_immediately(:WAIT_FOR_OTHER_TO_ADVANCE);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run(500);\n ts_bgw_db_scheduler_test_run \n------------------------------\n \n\n-- Wait for scheduler to start up\nSELECT wait_for_timer_to_run(0);\n wait_for_timer_to_run \n-----------------------\n t\n\nSELECT insert_job('another', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s') AS job_id \\gset\n-- call alter_job to trigger cache invalidation\nSELECT alter_job(:job_id,scheduled:=true);\n                                  alter_job                                  \n-----------------------------------------------------------------------------\n (1024,\"@ 0.1 secs\",\"@ 1 min 40 secs\",5,\"@ 1 sec\",t,,-infinity,,f,,,another)\n\nSELECT ts_bgw_params_reset_time(50000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT wait_for_timer_to_run(50000);\n wait_for_timer_to_run \n-----------------------\n t\n\nSELECT wait_for_job_1_to_run(1);\n wait_for_job_1_to_run \n-----------------------\n t\n\nSELECT ts_bgw_params_reset_time(150000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT wait_for_timer_to_run(150000);\n wait_for_timer_to_run \n-----------------------\n t\n\nSELECT wait_for_job_1_to_run(2);\n wait_for_job_1_to_run \n-----------------------\n t\n\n\\x on\nselect * from _timescaledb_internal.bgw_job_stat;\n-[ RECORD 1 ]-----------+--------------------------------\njob_id                  | 1024\nlast_start              | Fri Dec 31 16:00:00.15 1999 PST\nlast_finish             | Fri Dec 31 16:00:00.15 1999 PST\nnext_start              | Fri Dec 31 16:00:00.25 1999 PST\nlast_successful_finish  | Fri Dec 31 16:00:00.15 1999 PST\nlast_run_success        | t\ntotal_runs              | 2\ntotal_duration          | @ 0\ntotal_duration_failures | @ 0\ntotal_successes         | 2\ntotal_failures          | 0\ntotal_crashes           | 0\nconsecutive_failures    | 0\nconsecutive_crashes     | 0\nflags                   | 0\n\n\\x off\nSELECT delete_job(x.id) FROM (select * from _timescaledb_catalog.bgw_job) x;\n delete_job \n------------\n \n\n-- test null handling in delete_job\nSELECT delete_job(NULL);\n delete_job \n------------\n \n\nSELECT ts_bgw_params_reset_time(200000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT wait_for_timer_to_run(200000);\n wait_for_timer_to_run \n-----------------------\n t\n\n-- In the next time interval, nothing should be run because scheduler should have an empty list\nSELECT ts_bgw_params_reset_time(300000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT wait_for_timer_to_run(300000);\n wait_for_timer_to_run \n-----------------------\n t\n\n-- Same for this time interval\nSELECT ts_bgw_params_reset_time(400000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT wait_for_timer_to_run(400000);\n wait_for_timer_to_run \n-----------------------\n t\n\n-- Now add a new job and make sure it gets run before the scheduler dies\nSELECT insert_job('new_job', 'bgw_test_job_1', INTERVAL '10ms', INTERVAL '100s', INTERVAL '1s') AS job_id \\gset\n-- call alter_job to trigger cache invalidation\nSELECT alter_job(:job_id,scheduled:=true);\n                                  alter_job                                   \n------------------------------------------------------------------------------\n (1025,\"@ 0.01 secs\",\"@ 1 min 40 secs\",5,\"@ 1 sec\",t,,-infinity,,f,,,new_job)\n\nSELECT ts_bgw_params_reset_time(450000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\n-- New job should be run once, for a total of 3 runs of this job in the log\nSELECT wait_for_job_1_to_run(3);\n wait_for_job_1_to_run \n-----------------------\n t\n\n-- New job should be run again\nSELECT ts_bgw_params_reset_time(480000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT wait_for_job_1_to_run(4);\n wait_for_job_1_to_run \n-----------------------\n t\n\nSELECT ts_bgw_params_reset_time(500000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | another          | Execute job 1\n      3 | DB Scheduler     | [TESTING] Registered new background worker\n      4 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | another          | Execute job 1\n      5 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      6 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      7 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      8 | DB Scheduler     | [TESTING] Registered new background worker\n      9 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | new_job          | Execute job 1\n     10 | DB Scheduler     | [TESTING] Registered new background worker\n     11 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | new_job          | Execute job 1\n\n\\x on\nSELECT * FROM _timescaledb_internal.bgw_job_stat;\n-[ RECORD 1 ]-----------+--------------------------------\njob_id                  | 1025\nlast_start              | Fri Dec 31 16:00:00.48 1999 PST\nlast_finish             | Fri Dec 31 16:00:00.48 1999 PST\nnext_start              | Fri Dec 31 16:00:00.49 1999 PST\nlast_successful_finish  | Fri Dec 31 16:00:00.48 1999 PST\nlast_run_success        | t\ntotal_runs              | 2\ntotal_duration          | @ 0\ntotal_duration_failures | @ 0\ntotal_successes         | 2\ntotal_failures          | 0\ntotal_crashes           | 0\nconsecutive_failures    | 0\nconsecutive_crashes     | 0\nflags                   | 0\n\n\\x off\n--\n-- Test without retry\n--\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE bgw_log;\nTRUNCATE _timescaledb_internal.bgw_job_stat;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_params_mock_wait_returns_immediately(:WAIT_ON_JOB);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\nDELETE FROM _timescaledb_catalog.bgw_job;\nINSERT INTO _timescaledb_catalog.bgw_job(application_name, schedule_interval, max_runtime, max_retries, retry_period, proc_schema, proc_name) VALUES('bgw_test_job_2_error', INTERVAL '5000ms', INTERVAL '20ms', 0, INTERVAL '20ms', 'public', 'bgw_test_job_2_error') RETURNING id;\n  id  \n------\n 1026\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n-- Run the first time\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes FROM _timescaledb_internal.bgw_job_stat;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1026 | f                |          1 |               0 |              1 |             0\n\nSELECT * FROM sorted_bgw_log;\n msg_no |   application_name   |                            msg                            \n--------+----------------------+-----------------------------------------------------------\n      0 | DB Scheduler         | [TESTING] Registered new background worker\n      1 | DB Scheduler         | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | bgw_test_job_2_error | job 1026 reached max_retries after 1 consecutive failures\n      2 | bgw_test_job_2_error | job 1026 threw an error\n      3 | bgw_test_job_2_error | Error job 2\n      2 | DB Scheduler         | [TESTING] Wait until (RANDOM), started at (RANDOM)\n\nSELECT last_finish, last_successful_finish, last_run_success FROM _timescaledb_internal.bgw_job_stat;\n         last_finish          | last_successful_finish | last_run_success \n------------------------------+------------------------+------------------\n Fri Dec 31 16:00:00 1999 PST | -infinity              | f\n\n-- Run the second time\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(100, 50);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes FROM _timescaledb_internal.bgw_job_stat;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1026 | f                |          1 |               0 |              1 |             0\n\n-- We increase the mock time a lot to ensure the job does not get restarted. However, the amount of scheduler sleep/wakeup cycles\n-- is not deterministic. Therefore, we filter these messages to get a deterministic test output.\nSELECT * FROM sorted_bgw_log WHERE msg NOT LIKE '[TESTING] Wait until%';\n msg_no |   application_name   |                            msg                            \n--------+----------------------+-----------------------------------------------------------\n      0 | DB Scheduler         | [TESTING] Registered new background worker\n      1 | bgw_test_job_2_error | job 1026 reached max_retries after 1 consecutive failures\n      2 | bgw_test_job_2_error | job 1026 threw an error\n      3 | bgw_test_job_2_error | Error job 2\n\nSELECT last_finish, last_successful_finish, last_run_success FROM _timescaledb_internal.bgw_job_stat;\n         last_finish          | last_successful_finish | last_run_success \n------------------------------+------------------------+------------------\n Fri Dec 31 16:00:00 1999 PST | -infinity              | f\n\n-- clean up jobs\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT _timescaledb_functions.stop_background_workers();\n stop_background_workers \n-------------------------\n t\n\n"
  },
  {
    "path": "tsl/test/expected/bgw_db_scheduler_fixed.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n--\n-- Setup\n--\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET timezone TO PST8PDT;\n-- this mock_start_time doesnt seem to be used anywhere\nCREATE OR REPLACE FUNCTION ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(timeout INT = -1, mock_start_time INT = 0) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_db_scheduler_test_run(timeout INT = -1, mock_start_time INT = 0) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_db_scheduler_test_wait_for_scheduler_finish() RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_params_create() RETURNS VOID AS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_params_destroy() RETURNS VOID AS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_test_job_sleep(job_id INT, config JSONB) RETURNS VOID AS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_params_reset_time(set_time BIGINT = 0, wait BOOLEAN = false) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\n-- we use insert_job instead of add_job because we want to be able to set and use max_retries, max_runtime, retry_period which are not part of the add_job api\nCREATE OR REPLACE FUNCTION insert_job(\n       application_name NAME,\n       job_type NAME,\n       schedule_interval INTERVAL,\n       max_runtime INTERVAL,\n       retry_period INTERVAL,\n       owner regrole DEFAULT pg_catalog.quote_ident(current_role)::regrole,\n       scheduled BOOL DEFAULT true,\n       fixed_schedule BOOL DEFAULT true\n)\nRETURNS INT LANGUAGE SQL SECURITY DEFINER AS\n$$\n  INSERT INTO _timescaledb_catalog.bgw_job(application_name,schedule_interval,max_runtime,max_retries,\n  retry_period,proc_name,proc_schema,owner,scheduled,fixed_schedule,initial_start)\n  VALUES($1,$3,$4,5,$5,$2,'public',$6,$7,$8,'2000-01-01 00:00:00+00'::timestamptz) RETURNING id;\n$$;\nCREATE OR REPLACE FUNCTION test_toggle_scheduled(job_id INTEGER) RETURNS VOID LANGUAGE SQL SECURITY DEFINER AS\n$$\n  UPDATE _timescaledb_catalog.bgw_job SET scheduled = NOT scheduled WHERE id = $1;\n$$;\n\\set WAIT_ON_JOB 0\n\\set IMMEDIATELY_SET_UNTIL 1\n\\set WAIT_FOR_OTHER_TO_ADVANCE 2\n\\set WAIT_FOR_STANDARD_WAITLATCH 3\n-- simply sets the wait type\nCREATE OR REPLACE FUNCTION ts_bgw_params_mock_wait_returns_immediately(new_val INTEGER) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE FUNCTION get_application_pid(app_name TEXT) RETURNS INTEGER LANGUAGE SQL AS\n$BODY$\n    SELECT pid FROM pg_stat_activity WHERE application_name = app_name;\n$BODY$;\nCREATE FUNCTION wait_application_pid(app_name TEXT, wait_for_start BOOLEAN = true) RETURNS INTEGER LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n    r INTEGER;\nBEGIN\n    --wait up to a second checking each 100ms\n    FOR i in 1..10\n    LOOP\n        SELECT get_application_pid(app_name) INTO r;\n        IF (wait_for_start AND r IS NULL) OR (NOT wait_for_start AND r IS NOT NULL) THEN\n            PERFORM pg_sleep(0.1);\n            PERFORM pg_stat_clear_snapshot();\n        ELSE\n            RETURN r;\n        END IF;\n    END LOOP;\n    RETURN NULL;\nEND\n$BODY$;\nCREATE FUNCTION wait_for_logentry(job_id INTEGER) RETURNS TEXT LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n  app_name TEXT;\n  message TEXT;\nBEGIN\n    SELECT application_name INTO app_name FROM _timescaledb_catalog.bgw_job WHERE id = job_id;\n\n    --wait up to a second checking each 100ms\n    FOR i in 1..10\n    LOOP\n        SELECT msg INTO message FROM bgw_log WHERE application_name = app_name ORDER BY msg_no DESC LIMIT 1;\n        IF FOUND THEN\n          RETURN message;\n        END IF;\n        PERFORM pg_sleep(0.1);\n        PERFORM pg_stat_clear_snapshot();\n    END LOOP;\n    RETURN NULL;\nEND\n$BODY$;\n-- Remove any default jobs, e.g., telemetry\nDELETE FROM _timescaledb_catalog.bgw_job WHERE TRUE;\nTRUNCATE _timescaledb_internal.bgw_job_stat;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSET timezone TO PST8PDT;\nCREATE TABLE public.bgw_log(\n    msg_no INT,\n    mock_time BIGINT,\n    application_name TEXT,\n    msg TEXT\n);\nCREATE VIEW sorted_bgw_log AS\n    SELECT msg_no,\n    \t   application_name,\n    \t   regexp_replace(regexp_replace(msg, '(Wait until|started at|execution time) [0-9]+(\\.[0-9]+)?', '\\1 (RANDOM)', 'g'), 'background worker \"[^\"]+\"','connection') AS msg\n\t   FROM bgw_log ORDER BY mock_time, application_name COLLATE \"C\", msg_no;\nCREATE TABLE public.bgw_dsm_handle_store(\n    handle BIGINT\n);\nINSERT INTO public.bgw_dsm_handle_store VALUES (0);\nSELECT ts_bgw_params_create();\n ts_bgw_params_create \n----------------------\n \n\n--\n-- Test running the scheduler with no jobs\n--\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(50);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\n-- empty\nSELECT * FROM _timescaledb_internal.bgw_job_stat;\n job_id | last_start | last_finish | next_start | last_successful_finish | last_run_success | total_runs | total_duration | total_duration_failures | total_successes | total_failures | total_crashes | consecutive_failures | consecutive_crashes | flags \n--------+------------+-------------+------------+------------------------+------------------+------------+----------------+-------------------------+-----------------+----------------+---------------+----------------------+---------------------+-------\n\n-- empty\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n\n--\n-- Test running the scheduler with a job marked as unscheduled\n--\nTRUNCATE bgw_log;\n-- this function sets the counter (microseconds) that corresponds to the current time to the\n-- given value (defalut 0, and the default for setting the latch is false)\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT insert_job('unscheduled', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s',scheduled:= false);\n insert_job \n------------\n       1000\n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(50);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\n-- empty\nSELECT * FROM _timescaledb_internal.bgw_job_stat;\n job_id | last_start | last_finish | next_start | last_successful_finish | last_run_success | total_runs | total_duration | total_duration_failures | total_successes | total_failures | total_crashes | consecutive_failures | consecutive_crashes | flags \n--------+------------+-------------+------------+------------------------+------------------+------------+----------------+-------------------------+-----------------+----------------+---------------+----------------------+---------------------+-------\n\nSELECT * FROM timescaledb_information.job_stats;\n hypertable_schema | hypertable_name | job_id | last_run_started_at | last_successful_finish | last_run_status | job_status | last_run_duration | next_start | total_runs | total_successes | total_failures \n-------------------+-----------------+--------+---------------------+------------------------+-----------------+------------+-------------------+------------+------------+-----------------+----------------\n                   |                 |   1000 |                     |                        |                 | Paused     |                   |            |            |                 |               \n\nSELECT test_toggle_scheduled(1000);\n test_toggle_scheduled \n-----------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(50);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM _timescaledb_internal.bgw_job_stat;\n job_id |           last_start            |           last_finish           |           next_start           |     last_successful_finish      | last_run_success | total_runs | total_duration | total_duration_failures | total_successes | total_failures | total_crashes | consecutive_failures | consecutive_crashes | flags \n--------+---------------------------------+---------------------------------+--------------------------------+---------------------------------+------------------+------------+----------------+-------------------------+-----------------+----------------+---------------+----------------------+---------------------+-------\n   1000 | Fri Dec 31 16:00:00.05 1999 PST | Fri Dec 31 16:00:00.05 1999 PST | Fri Dec 31 16:00:00.1 1999 PST | Fri Dec 31 16:00:00.05 1999 PST | t                |          1 | @ 0            | @ 0                     |               1 |              0 |             0 |                    0 |                   0 |     0\n\nSELECT * FROM timescaledb_information.job_stats;\n hypertable_schema | hypertable_name | job_id |       last_run_started_at       |     last_successful_finish      | last_run_status | job_status | last_run_duration |           next_start           | total_runs | total_successes | total_failures \n-------------------+-----------------+--------+---------------------------------+---------------------------------+-----------------+------------+-------------------+--------------------------------+------------+-----------------+----------------\n                   |                 |   1000 | Fri Dec 31 16:00:00.05 1999 PST | Fri Dec 31 16:00:00.05 1999 PST | Success         | Scheduled  |                   | Fri Dec 31 16:00:00.1 1999 PST |          1 |               1 |              0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | unscheduled      | Execute job 1\n\nSELECT delete_job(1000);\n delete_job \n------------\n \n\n--\n-- test deleting job also terminates running jobs\n--\nSELECT add_job('ts_bgw_test_job_sleep','1h') AS job_id \\gset\nSELECT ts_bgw_db_scheduler_test_run();\n ts_bgw_db_scheduler_test_run \n------------------------------\n \n\nSELECT wait_for_logentry(:job_id);\n wait_for_logentry \n-------------------\n Before sleep\n\nSELECT application_name FROM pg_stat_activity WHERE application_name LIKE 'User-Defined Action%';\n      application_name      \n----------------------------\n User-Defined Action [1001]\n\n\\x on\nSELECT job_id, job_status FROM timescaledb_information.job_stats;\n-[ RECORD 1 ]-------\njob_id     | 1001\njob_status | Running\n\n-- Showing non-volatile information from pg_stat_activity for\n-- debugging purposes. Information schema above reads from this view.\nSELECT datname, usename, application_name, state, query, wait_event_type, wait_event\n  FROM pg_stat_activity WHERE application_name LIKE 'User-Defined Action%';\n-[ RECORD 1 ]----+------------------------------------\ndatname          | db_bgw_db_scheduler_fixed\nusename          | default_perm_user\napplication_name | User-Defined Action [1001]\nstate            | active\nquery            | CALL public.ts_bgw_test_job_sleep()\nwait_event_type  | Timeout\nwait_event       | PgSleep\n\n\\x off\nSELECT delete_job(:job_id);\n delete_job \n------------\n \n\nSELECT wait_application_pid('User-Defined Action [' || :job_id || ']', false);\n wait_application_pid \n----------------------\n                     \n\nSELECT application_name FROM pg_stat_activity WHERE application_name LIKE 'User-Defined Action%';\n application_name \n------------------\n\n--\n-- Test running a normal job\n--\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE bgw_log;\nALTER SEQUENCE _timescaledb_catalog.bgw_job_id_seq RESTART;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT insert_job('test_job_1', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s');\n insert_job \n------------\n       1000\n\nselect * from _timescaledb_catalog.bgw_job;\n  id  | application_name | schedule_interval |   max_runtime   | max_retries | retry_period | proc_schema |   proc_name    |   owner    | scheduled | fixed_schedule |        initial_start         | hypertable_id | config | check_schema | check_name | timezone \n------+------------------+-------------------+-----------------+-------------+--------------+-------------+----------------+------------+-----------+----------------+------------------------------+---------------+--------+--------------+------------+----------\n 1000 | test_job_1       | @ 0.1 secs        | @ 1 min 40 secs |           5 | @ 1 sec      | public      | bgw_test_job_1 | super_user | t         | t              | Fri Dec 31 16:00:00 1999 PST |               |        |              |            | \n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSET timezone TO PST8PDT;\n--Tests that the scheduler start a job right away if it's the first time and there is no job_stat entry for it\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, next_start, last_finish as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id |           next_start           |          until_next          | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+--------------------------------+------------------------------+------------------+------------+-----------------+----------------+---------------\n   1000 | Fri Dec 31 16:00:00.1 1999 PST | Fri Dec 31 16:00:00 1999 PST | t                |          1 |               1 |              0 |             0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_1       | Execute job 1\n\n--Test that the scheduler will not run job again if not enough time has passed\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25, 25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1000 | t                |          1 |               1 |              0 |             0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_1       | Execute job 1\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n\n--After enough time has passed the scheduler will run the job again\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(100, 50);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, next_start, last_finish, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id |           next_start           |          last_finish           | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+--------------------------------+--------------------------------+------------------+------------+-----------------+----------------+---------------\n   1000 | Fri Dec 31 16:00:00.2 1999 PST | Fri Dec 31 16:00:00.1 1999 PST | t                |          2 |               2 |              0 |             0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_1       | Execute job 1\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_1       | Execute job 1\n\n--Now it runs it one more time\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(120, 100);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, next_start, last_finish, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id |           next_start           |          last_finish           | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+--------------------------------+--------------------------------+------------------+------------+-----------------+----------------+---------------\n   1000 | Fri Dec 31 16:00:00.3 1999 PST | Fri Dec 31 16:00:00.2 1999 PST | t                |          3 |               3 |              0 |             0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_1       | Execute job 1\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_1       | Execute job 1\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_1       | Execute job 1\n\n--\n-- Test what happens when running a job that throws an error\n--\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE bgw_log;\nTRUNCATE _timescaledb_internal.bgw_job_stat;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nDELETE FROM _timescaledb_catalog.bgw_job;\n-- schedule_interval, max_runtime, retry_period\nSELECT insert_job('test_job_2', 'bgw_test_job_2_error', INTERVAL '800ms', INTERVAL '100s', INTERVAL '200ms');\n insert_job \n------------\n       1001\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSET timezone TO PST8PDT;\n--Run the first time and error\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1001 | f                |          1 |               0 |              1 |             0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n\nSELECT last_finish, last_successful_finish, last_run_success FROM _timescaledb_internal.bgw_job_stat;\n         last_finish          | last_successful_finish | last_run_success \n------------------------------+------------------------+------------------\n Fri Dec 31 16:00:00 1999 PST | -infinity              | f\n\n-- what we aim to verify here is the following:\n-- 1. that the job is run again on its next scheduled slot, if the next_start calculated based\n-- on failure count would surpass it\n-- the next_start on failure is calculated by adding failure_count * retry_period to finish time\n-- maximum backoff is 5 * schedule interval, but for a fixed job, if we surpass the next_scheduled_slot\n-- for it this way, then we execute again at the next scheduled slot instead\n--Scheduler runs the job again, sees another error, and increases the wait time\n-- this retry time is before the next scheduled execution, so the job is allowed to retry before then\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(225); -- will see 2 failures now\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1001 | f                |          2 |               0 |              2 |             0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n\n-- as we have a job on a fixed_schedule, the next_start will not be more than the next scheduled slot\n-- If the calculated next_start is more than the next scheduled execution slot, then\n-- we will execute again at the next scheduled slot.\n-- again this is before the next scheduled slot so the job retries before then\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(425); -- will see 3 failures now\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1001 | f                |          3 |               0 |              3 |             0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n\n-- will see 4 failures now\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(625);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1001 | f                |          4 |               0 |              4 |             0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n\n-- will see 5 failures now because job executes again on its next scheduled slot (800ms after its initial start, which is 0)\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(825);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1001 | f                |          5 |               0 |              5 |             0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                            msg                            \n--------+------------------+-----------------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 threw an error\n      2 | test_job_2       | Error job 2\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | test_job_2       | job 1001 reached max_retries after 5 consecutive failures\n      2 | test_job_2       | job 1001 threw an error\n      3 | test_job_2       | Error job 2\n\n-- Get status of failing job `test_job_2` to check it reached `max_retries` and\n-- the new `job_status` now is `Paused`\nSELECT job_id, last_run_status, job_status, total_runs, total_successes, total_failures\nFROM timescaledb_information.job_stats WHERE job_id = 1001;\n job_id | last_run_status | job_status | total_runs | total_successes | total_failures \n--------+-----------------+------------+------------+-----------------+----------------\n   1001 | Failed          | Paused     |          5 |               0 |              5\n\n-- Alter job to be rescheduled and run it again\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE bgw_log;\nSELECT scheduled FROM alter_job(1001, scheduled => true) AS discard;\n scheduled \n-----------\n t\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(825);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat WHERE job_id = 1001;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1001 | f                |          6 |               0 |              6 |             0\n\n--\n-- Test timeout logic\n--\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE bgw_log;\nTRUNCATE _timescaledb_internal.bgw_job_stat;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nDELETE FROM _timescaledb_catalog.bgw_job;\n--set timeout lower than job length\nSELECT insert_job('test_job_3_long', 'bgw_test_job_3_long', INTERVAL '5000ms', INTERVAL '20ms', INTERVAL '50ms');\n insert_job \n------------\n       1002\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT ts_bgw_params_mock_wait_returns_immediately(:IMMEDIATELY_SET_UNTIL);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\n--Test that the scheduler kills a job that takes too long\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(200);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes, consecutive_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes | consecutive_crashes \n--------+------------------+------------+-----------------+----------------+---------------+---------------------\n   1002 | f                |          1 |               0 |              1 |             0 |                   0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      2 | DB Scheduler     | terminating connection due to timeout\n      3 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      4 | DB Scheduler     | job 1002 failed\n\n--Check that the scheduler does not kill a job with infinite timeout\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE bgw_log;\nTRUNCATE _timescaledb_internal.bgw_job_stat;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nDELETE FROM _timescaledb_catalog.bgw_job;\n--set timeout to 0\nSELECT insert_job('test_job_3_long', 'bgw_test_job_3_long', INTERVAL '5000ms', INTERVAL '0', INTERVAL '10ms');\n insert_job \n------------\n       1003\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(550);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes, consecutive_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes | consecutive_crashes \n--------+------------------+------------+-----------------+----------------+---------------+---------------------\n   1003 | t                |          1 |               1 |              0 |             0 |                   0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_3_long  | Before sleep job 3\n      1 | test_job_3_long  | After sleep job 3\n\nSELECT ts_bgw_params_mock_wait_returns_immediately(:WAIT_ON_JOB);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\n--\n-- Test signal handling\n--\n--Test sending a SIGTERM to a job\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE bgw_log;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nTRUNCATE _timescaledb_internal.bgw_job_stat;\nDELETE FROM _timescaledb_catalog.bgw_job;\nSELECT insert_job('test_job_3_long', 'bgw_test_job_3_long', INTERVAL '5000ms', INTERVAL '100s', INTERVAL '500ms');\n insert_job \n------------\n       1004\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n--escalated priv needed for access to pg_stat_activity\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT ts_bgw_db_scheduler_test_run(300);\n ts_bgw_db_scheduler_test_run \n------------------------------\n \n\nSELECT pg_terminate_backend(wait_application_pid('test_job_3_long'));\n pg_terminate_backend \n----------------------\n t\n\nSELECT ts_bgw_db_scheduler_test_wait_for_scheduler_finish();\n ts_bgw_db_scheduler_test_wait_for_scheduler_finish \n----------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                         msg                         \n--------+------------------+-----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_3_long  | Before sleep job 3\n      1 | test_job_3_long  | terminating connection due to administrator command\n      2 | DB Scheduler     | job 1004 failed\n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1004 | f                |          1 |               0 |              1 |             0\n\n-- Test that the job is able to run again and succeed\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(900);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1004 | t                |          2 |               1 |              1 |             0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                         msg                         \n--------+------------------+-----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_3_long  | Before sleep job 3\n      1 | test_job_3_long  | terminating connection due to administrator command\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      2 | DB Scheduler     | job 1004 failed\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_3_long  | Before sleep job 3\n      1 | test_job_3_long  | After sleep job 3\n\n--Test sending a SIGHUP to a job\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE bgw_log;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nTRUNCATE _timescaledb_internal.bgw_job_stat;\nDELETE FROM _timescaledb_catalog.bgw_job;\nSELECT ts_bgw_params_mock_wait_returns_immediately(:WAIT_FOR_STANDARD_WAITLATCH);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run(-1);\n ts_bgw_db_scheduler_test_run \n------------------------------\n \n\nSHOW timescaledb.shutdown_bgw_scheduler;\n timescaledb.shutdown_bgw_scheduler \n------------------------------------\n off\n\nALTER SYSTEM SET timescaledb.shutdown_bgw_scheduler TO 'on';\nSELECT pg_reload_conf();\n pg_reload_conf \n----------------\n t\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSHOW timescaledb.shutdown_bgw_scheduler;\n timescaledb.shutdown_bgw_scheduler \n------------------------------------\n on\n\nSELECT ts_bgw_db_scheduler_test_wait_for_scheduler_finish();\n ts_bgw_db_scheduler_test_wait_for_scheduler_finish \n----------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                      msg                      \n--------+------------------+-----------------------------------------------\n      0 | DB Scheduler     | bgw scheduler stopped due to shutdown_bgw guc\n\nALTER SYSTEM RESET timescaledb.shutdown_bgw_scheduler;\nSELECT pg_reload_conf();\n pg_reload_conf \n----------------\n t\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSHOW timescaledb.shutdown_bgw_scheduler;\n timescaledb.shutdown_bgw_scheduler \n------------------------------------\n off\n\nSELECT ts_bgw_params_mock_wait_returns_immediately(:WAIT_ON_JOB);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\n--Test that sending SIGTERM to scheduler terminates the jobs as well\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE bgw_log;\nTRUNCATE _timescaledb_internal.bgw_job_stat;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nDELETE FROM _timescaledb_catalog.bgw_job;\nSELECT insert_job('test_job_3_long', 'bgw_test_job_3_long', INTERVAL '5000ms', INTERVAL '100s', INTERVAL '10ms');\n insert_job \n------------\n       1005\n\nSELECT ts_bgw_db_scheduler_test_run(500);\n ts_bgw_db_scheduler_test_run \n------------------------------\n \n\nSELECT wait_application_pid('test_job_3_long') IS NOT NULL ;\n ?column? \n----------\n t\n\nSELECT pg_terminate_backend(wait_application_pid('DB Scheduler Test'));\n pg_terminate_backend \n----------------------\n t\n\nSELECT ts_bgw_db_scheduler_test_wait_for_scheduler_finish();\n ts_bgw_db_scheduler_test_wait_for_scheduler_finish \n----------------------------------------------------\n \n\nSELECT job_id, last_finish, last_run_success, total_runs, total_successes, total_failures, total_crashes, consecutive_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id | last_finish | last_run_success | total_runs | total_successes | total_failures | total_crashes | consecutive_crashes \n--------+-------------+------------------+------------+-----------------+----------------+---------------+---------------------\n   1005 | -infinity   | f                |          1 |               0 |              0 |             1 |                   1\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                         msg                         \n--------+------------------+-----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      2 | DB Scheduler     | terminating connection due to administrator command\n      0 | test_job_3_long  | Before sleep job 3\n      1 | test_job_3_long  | terminating connection due to administrator command\n\n--After a SIGTERM to scheduler and jobs, the jobs are considered crashed and there is a imposed wait of 5 min before a job can be run.\n--See that there is no run again because of the crash-imposed wait (not run with the 10ms retry_period)\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(500);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_finish, next_start, last_run_success, total_runs, total_successes, total_failures, total_crashes, consecutive_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id | last_finish | next_start | last_run_success | total_runs | total_successes | total_failures | total_crashes | consecutive_crashes \n--------+-------------+------------+------------------+------------+-----------------+----------------+---------------+---------------------\n   1005 | -infinity   | -infinity  | f                |          1 |               0 |              0 |             1 |                   1\n\n--But after the 5 min period the job is again run\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(400000);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_finish, next_start, last_run_success, total_runs, total_successes, total_failures, total_crashes, consecutive_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id |          last_finish           |          next_start          | last_run_success | total_runs | total_successes | total_failures | total_crashes | consecutive_crashes \n--------+--------------------------------+------------------------------+------------------+------------+-----------------+----------------+---------------+---------------------\n   1005 | Fri Dec 31 16:05:00.5 1999 PST | Fri Dec 31 16:05:05 1999 PST | t                |          2 |               1 |              0 |             1 |                   0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                         msg                         \n--------+------------------+-----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      2 | DB Scheduler     | terminating connection due to administrator command\n      0 | test_job_3_long  | Before sleep job 3\n      1 | test_job_3_long  | terminating connection due to administrator command\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_3_long  | Before sleep job 3\n      1 | test_job_3_long  | After sleep job 3\n\nCREATE FUNCTION wait_for_timer_to_run(started_at INTEGER, spins INTEGER=:TEST_SPINWAIT_ITERS) RETURNS BOOLEAN LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n\tnum_runs INTEGER;\n\tmessage TEXT;\nBEGIN\n\tselect format('[TESTING] Wait until %%, started at %s', started_at) into message;\n\tFOR i in 1..spins\n\tLOOP\n\tSELECT COUNT(*) from bgw_log where msg LIKE message INTO num_runs;\n\tif (num_runs > 0) THEN\n\t\tRETURN true;\n\tELSE\n\t\tPERFORM pg_sleep(0.1);\n\tEND IF;\n\tEND LOOP;\n\tRETURN false;\nEND\n$BODY$;\nCREATE FUNCTION wait_for_job_3_to_finish(runs INTEGER, spins INTEGER=:TEST_SPINWAIT_ITERS) RETURNS BOOLEAN LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n\tnum_runs INTEGER;\nBEGIN\n\tFOR i in 1..spins\n\tLOOP\n\tSELECT COUNT(*) from bgw_log where msg='After sleep job 3' INTO num_runs;\n\tif (num_runs = runs) THEN\n\t\tRETURN true;\n\tELSE\n\t\tPERFORM pg_sleep(0.1);\n\tEND IF;\n\tEND LOOP;\n\tRETURN false;\nEND\n$BODY$;\n--\n-- Test starting more jobs than availlable workers\n--\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE bgw_log;\nTRUNCATE _timescaledb_internal.bgw_job_stat;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nDELETE FROM _timescaledb_catalog.bgw_job;\nSELECT ts_bgw_params_mock_wait_returns_immediately(:WAIT_FOR_OTHER_TO_ADVANCE);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\n--Our normal limit is 8 jobs (1 already taken up by the launcher, we don't register the test scheduler)\n--so start 8 workers. Make the schedule_INTERVAL long and the retry period short so that the\n--retries happen within the scheduler run time but everything only runs once.\nSELECT\ninsert_job('test_job_3_long_1', 'bgw_test_job_3_long', INTERVAL '5000ms', INTERVAL '100s', INTERVAL '10ms'),\ninsert_job('test_job_3_long_2', 'bgw_test_job_3_long', INTERVAL '5000ms', INTERVAL '100s', INTERVAL '10ms'),\ninsert_job('test_job_3_long_3', 'bgw_test_job_3_long', INTERVAL '5000ms', INTERVAL '100s', INTERVAL '10ms'),\ninsert_job('test_job_3_long_4', 'bgw_test_job_3_long', INTERVAL '5000ms', INTERVAL '100s', INTERVAL '10ms'),\ninsert_job('test_job_3_long_5', 'bgw_test_job_3_long', INTERVAL '5000ms', INTERVAL '100s', INTERVAL '10ms'),\ninsert_job('test_job_3_long_6', 'bgw_test_job_3_long', INTERVAL '5000ms', INTERVAL '100s', INTERVAL '10ms');\n insert_job | insert_job | insert_job | insert_job | insert_job | insert_job \n------------+------------+------------+------------+------------+------------\n       1006 |       1007 |       1008 |       1009 |       1010 |       1011\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT ts_bgw_db_scheduler_test_run(25000); --quit at second 25\n ts_bgw_db_scheduler_test_run \n------------------------------\n \n\n--the first 7 jobs will run right away, but not the last one\nSELECT wait_for_timer_to_run(0);\n wait_for_timer_to_run \n-----------------------\n t\n\nSELECT wait_for_job_3_to_finish(6);\n wait_for_job_3_to_finish \n--------------------------\n t\n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes, consecutive_crashes\nFROM _timescaledb_internal.bgw_job_stat\nORDER BY job_id;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes | consecutive_crashes \n--------+------------------+------------+-----------------+----------------+---------------+---------------------\n   1006 | t                |          1 |               1 |              0 |             0 |                   0\n   1007 | t                |          1 |               1 |              0 |             0 |                   0\n   1008 | t                |          1 |               1 |              0 |             0 |                   0\n   1009 | t                |          1 |               1 |              0 |             0 |                   0\n   1010 | t                |          1 |               1 |              0 |             0 |                   0\n   1011 | t                |          1 |               1 |              0 |             0 |                   0\n\nSELECT ts_bgw_params_reset_time(30000000, true); --set to second 30, which causes a quit.\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_wait_for_scheduler_finish();\n ts_bgw_db_scheduler_test_wait_for_scheduler_finish \n----------------------------------------------------\n \n\n--should have all 8 runs, all with success runs\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes, consecutive_crashes\nFROM _timescaledb_internal.bgw_job_stat\nORDER BY job_id;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes | consecutive_crashes \n--------+------------------+------------+-----------------+----------------+---------------+---------------------\n   1006 | t                |          1 |               1 |              0 |             0 |                   0\n   1007 | t                |          1 |               1 |              0 |             0 |                   0\n   1008 | t                |          1 |               1 |              0 |             0 |                   0\n   1009 | t                |          1 |               1 |              0 |             0 |                   0\n   1010 | t                |          1 |               1 |              0 |             0 |                   0\n   1011 | t                |          1 |               1 |              0 |             0 |                   0\n\nSELECT * FROM sorted_bgw_log WHERE application_name = 'DB Scheduler' ORDER BY application_name, msg_no;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Registered new background worker\n      3 | DB Scheduler     | [TESTING] Registered new background worker\n      4 | DB Scheduler     | [TESTING] Registered new background worker\n      5 | DB Scheduler     | [TESTING] Registered new background worker\n      6 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n\nSELECT ts_bgw_params_destroy();\n ts_bgw_params_destroy \n-----------------------\n \n\n--\n-- Test setting next_start time within a job\n--\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE bgw_log;\nTRUNCATE _timescaledb_internal.bgw_job_stat;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_params_mock_wait_returns_immediately(:WAIT_ON_JOB);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\nDELETE FROM _timescaledb_catalog.bgw_job;\nSELECT insert_job('test_job_4', 'bgw_test_job_4', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s');\n insert_job \n------------\n       1012\n\nselect * from _timescaledb_catalog.bgw_job;\n  id  | application_name | schedule_interval |   max_runtime   | max_retries | retry_period | proc_schema |   proc_name    |   owner    | scheduled | fixed_schedule |        initial_start         | hypertable_id | config | check_schema | check_name | timezone \n------+------------------+-------------------+-----------------+-------------+--------------+-------------+----------------+------------+-----------+----------------+------------------------------+---------------+--------+--------------+------------+----------\n 1012 | test_job_4       | @ 0.1 secs        | @ 1 min 40 secs |           5 | @ 1 sec      | public      | bgw_test_job_4 | super_user | t         | t              | Fri Dec 31 16:00:00 1999 PST |               |        |              |            | \n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n-- Now run and make sure next_start is 200ms away, not 100ms\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1012 | t                |          1 |               1 |              0 |             0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_4       | Execute job 4\n\n-- Now just make sure that the job actually runs in 200ms\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(200);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\n-- Print next_start and last_finish explicitly, instead of the difference, to make sure the times have changed\n-- since the last run\nSELECT job_id, next_start, last_finish, last_run_success, total_runs, total_successes, total_failures, total_crashes\nFROM _timescaledb_internal.bgw_job_stat;\n job_id |           next_start           |          last_finish           | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+--------------------------------+--------------------------------+------------------+------------+-----------------+----------------+---------------\n   1012 | Fri Dec 31 16:00:00.4 1999 PST | Fri Dec 31 16:00:00.2 1999 PST | t                |          2 |               2 |              0 |             0\n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_4       | Execute job 4\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_4       | Execute job 4\n\n-- Test updating jobs list\nTRUNCATE bgw_log;\n\\set ON_ERROR_STOP 0\nSELECT _timescaledb_functions.stop_background_workers();\nERROR:  must be superuser to stop background workers\nSELECT _timescaledb_functions.restart_background_workers();\nERROR:  must be superuser to restart background workers\nSELECT _timescaledb_functions.start_background_workers();\nERROR:  must be superuser to start background workers\n\\set ON_ERROR_STOP 1\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT _timescaledb_functions.stop_background_workers();\n stop_background_workers \n-------------------------\n t\n\nSET timezone TO PST8PDT;\nCREATE OR REPLACE FUNCTION ts_test_job_refresh() RETURNS TABLE(\nid INTEGER,\napplication_name NAME,\nschedule_interval INTERVAL,\nmax_runtime INTERVAL,\nmax_retries INT,\nretry_period INTERVAL,\nnext_start TIMESTAMPTZ,\ntimeout_at TIMESTAMPTZ,\nreserved_worker BOOLEAN,\nmay_next_mark_end BOOLEAN\n)\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE FUNCTION verify_refresh_correct() RETURNS BOOLEAN LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n    num_jobs INTEGER;\n    num_jobs_in_list INTEGER;\nBEGIN\n    SELECT COUNT(*) from _timescaledb_catalog.bgw_job INTO num_jobs;\n\tselect COUNT(*) from ts_test_job_refresh() JOIN _timescaledb_catalog.bgw_job USING (id,application_name,schedule_interval,max_runtime,max_retries,retry_period) INTO num_jobs_in_list;\n\tIF (num_jobs = num_jobs_in_list) THEN\n\t\tRETURN true;\n\tEND IF;\n\tRETURN false;\nEND\n$BODY$;\nCREATE FUNCTION wait_for_job_1_to_run(runs INTEGER, spins INTEGER=:TEST_SPINWAIT_ITERS) RETURNS BOOLEAN LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n\tnum_runs INTEGER;\nBEGIN\n\tFOR i in 1..spins\n\tLOOP\n\tSELECT COUNT(*) from bgw_log where msg='Execute job 1' INTO num_runs;\n\tif (num_runs = runs) THEN\n\t\tRETURN true;\n\tELSE\n\t\tPERFORM pg_sleep(0.1);\n\tEND IF;\n\tEND LOOP;\n\tRETURN false;\nEND\n$BODY$;\nselect * from verify_refresh_correct();\n verify_refresh_correct \n------------------------\n t\n\n-- Should return the same table\nselect * from verify_refresh_correct();\n verify_refresh_correct \n------------------------\n t\n\nDELETE FROM _timescaledb_catalog.bgw_job;\n-- Make sure jobs list is empty\nselect count(*) from ts_test_job_refresh();\n count \n-------\n     0\n\nSELECT\ninsert_job('test_1', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s'),\ninsert_job('test_2', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s'),\ninsert_job('test_3', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s');\n insert_job | insert_job | insert_job \n------------+------------+------------\n       1013 |       1014 |       1015\n\nselect * from verify_refresh_correct();\n verify_refresh_correct \n------------------------\n t\n\nDELETE from _timescaledb_catalog.bgw_job where application_name='test_2';\nSELECT insert_job('test_4', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s');\n insert_job \n------------\n       1016\n\nselect * from verify_refresh_correct();\n verify_refresh_correct \n------------------------\n t\n\nDELETE FROM _timescaledb_catalog.bgw_job;\nSELECT insert_job('test_10', 'test_10', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s');\n insert_job \n------------\n       1017\n\nselect * from verify_refresh_correct();\n verify_refresh_correct \n------------------------\n t\n\n-- Should be idempotent\nselect * from verify_refresh_correct();\n verify_refresh_correct \n------------------------\n t\n\nDELETE FROM _timescaledb_catalog.bgw_job;\nSELECT\ninsert_job('another', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s'),\ninsert_job('another1', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s'),\ninsert_job('another2', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s'),\ninsert_job('another3', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s'),\ninsert_job('another4', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s');\n insert_job | insert_job | insert_job | insert_job | insert_job \n------------+------------+------------+------------+------------\n       1018 |       1019 |       1020 |       1021 |       1022\n\nselect * from verify_refresh_correct();\n verify_refresh_correct \n------------------------\n t\n\nDELETE FROM _timescaledb_catalog.bgw_job where application_name='another' OR application_name='another3';\nSELECT insert_job('blah', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s');\n insert_job \n------------\n       1023\n\nselect * from verify_refresh_correct();\n verify_refresh_correct \n------------------------\n t\n\n-- Now test a real scheduler-mock running in a loop and updating the list of jobs\nTRUNCATE _timescaledb_internal.bgw_job_stat;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nDELETE FROM _timescaledb_catalog.bgw_job;\nSELECT ts_bgw_params_mock_wait_returns_immediately(:WAIT_FOR_OTHER_TO_ADVANCE);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run(500);\n ts_bgw_db_scheduler_test_run \n------------------------------\n \n\n-- Wait for scheduler to start up\nSELECT wait_for_timer_to_run(0);\n wait_for_timer_to_run \n-----------------------\n t\n\nSELECT insert_job('another', 'bgw_test_job_1', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s') AS job_id \\gset\n-- call alter_job to trigger cache invalidation\nSELECT alter_job(:job_id,scheduled:=true);\n                                                 alter_job                                                 \n-----------------------------------------------------------------------------------------------------------\n (1024,\"@ 0.1 secs\",\"@ 1 min 40 secs\",5,\"@ 1 sec\",t,,-infinity,,t,\"Fri Dec 31 16:00:00 1999 PST\",,another)\n\nSELECT ts_bgw_params_reset_time(50000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT wait_for_timer_to_run(50000);\n wait_for_timer_to_run \n-----------------------\n t\n\nSELECT wait_for_job_1_to_run(1);\n wait_for_job_1_to_run \n-----------------------\n t\n\nSELECT ts_bgw_params_reset_time(150000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT wait_for_timer_to_run(150000);\n wait_for_timer_to_run \n-----------------------\n t\n\nSELECT wait_for_job_1_to_run(2);\n wait_for_job_1_to_run \n-----------------------\n t\n\nselect * from _timescaledb_internal.bgw_job_stat;\n job_id |           last_start            |           last_finish           |           next_start           |     last_successful_finish      | last_run_success | total_runs | total_duration | total_duration_failures | total_successes | total_failures | total_crashes | consecutive_failures | consecutive_crashes | flags \n--------+---------------------------------+---------------------------------+--------------------------------+---------------------------------+------------------+------------+----------------+-------------------------+-----------------+----------------+---------------+----------------------+---------------------+-------\n   1024 | Fri Dec 31 16:00:00.15 1999 PST | Fri Dec 31 16:00:00.15 1999 PST | Fri Dec 31 16:00:00.2 1999 PST | Fri Dec 31 16:00:00.15 1999 PST | t                |          2 | @ 0            | @ 0                     |               2 |              0 |             0 |                    0 |                   0 |     0\n\nSELECT delete_job(x.id) FROM (select * from _timescaledb_catalog.bgw_job) x;\n delete_job \n------------\n \n\n-- test null handling in delete_job\nSELECT delete_job(NULL);\n delete_job \n------------\n \n\nSELECT ts_bgw_params_reset_time(200000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT wait_for_timer_to_run(200000);\n wait_for_timer_to_run \n-----------------------\n t\n\n-- In the next time interval, nothing should be run because scheduler should have an empty list\nSELECT ts_bgw_params_reset_time(300000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT wait_for_timer_to_run(300000);\n wait_for_timer_to_run \n-----------------------\n t\n\n-- Same for this time interval\nSELECT ts_bgw_params_reset_time(400000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT wait_for_timer_to_run(400000);\n wait_for_timer_to_run \n-----------------------\n t\n\n-- Now add a new job and make sure it gets run before the scheduler dies\nSELECT insert_job('new_job', 'bgw_test_job_1', INTERVAL '10ms', INTERVAL '100s', INTERVAL '1s') AS job_id \\gset\n-- call alter_job to trigger cache invalidation\nSELECT alter_job(:job_id,scheduled:=true);\n                                                 alter_job                                                  \n------------------------------------------------------------------------------------------------------------\n (1025,\"@ 0.01 secs\",\"@ 1 min 40 secs\",5,\"@ 1 sec\",t,,-infinity,,t,\"Fri Dec 31 16:00:00 1999 PST\",,new_job)\n\nSELECT ts_bgw_params_reset_time(450000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\n-- New job should be run once, for a total of 3 runs of this job in the log\nSELECT wait_for_job_1_to_run(3);\n wait_for_job_1_to_run \n-----------------------\n t\n\n-- New job should be run again\nSELECT ts_bgw_params_reset_time(480000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT wait_for_job_1_to_run(4);\n wait_for_job_1_to_run \n-----------------------\n t\n\nSELECT ts_bgw_params_reset_time(500000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      1 | DB Scheduler     | [TESTING] Registered new background worker\n      2 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | another          | Execute job 1\n      3 | DB Scheduler     | [TESTING] Registered new background worker\n      4 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | another          | Execute job 1\n      5 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      6 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      7 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      8 | DB Scheduler     | [TESTING] Registered new background worker\n      9 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | new_job          | Execute job 1\n     10 | DB Scheduler     | [TESTING] Registered new background worker\n     11 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | new_job          | Execute job 1\n\nSELECT * FROM _timescaledb_internal.bgw_job_stat;\n job_id |           last_start            |           last_finish           |           next_start            |     last_successful_finish      | last_run_success | total_runs | total_duration | total_duration_failures | total_successes | total_failures | total_crashes | consecutive_failures | consecutive_crashes | flags \n--------+---------------------------------+---------------------------------+---------------------------------+---------------------------------+------------------+------------+----------------+-------------------------+-----------------+----------------+---------------+----------------------+---------------------+-------\n   1025 | Fri Dec 31 16:00:00.48 1999 PST | Fri Dec 31 16:00:00.48 1999 PST | Fri Dec 31 16:00:00.49 1999 PST | Fri Dec 31 16:00:00.48 1999 PST | t                |          2 | @ 0            | @ 0                     |               2 |              0 |             0 |                    0 |                   0 |     0\n\n-- clean up jobs\nSELECT _timescaledb_functions.stop_background_workers();\n stop_background_workers \n-------------------------\n t\n\nselect delete_job(:job_id);\n delete_job \n------------\n \n\n-- test the new API with all its parameters: with timezone, without timezone\nTRUNCATE bgw_log;\nTRUNCATE bgw_dsm_handle_store;\nINSERT INTO public.bgw_dsm_handle_store VALUES (0);\nSELECT ts_bgw_params_create();\n ts_bgw_params_create \n----------------------\n \n\nCREATE TABLE test_table_scheduler (\n    time timestamptz not null,\n    a int,\n    b int\n);\nselect '2000-01-01 00:00:00+00' as init \\gset\nselect create_hypertable('test_table_scheduler', 'time', chunk_time_interval => interval '1 month');\n         create_hypertable         \n-----------------------------------\n (1,public,test_table_scheduler,t)\n\nINSERT INTO test_table_scheduler values\n(now() - interval '10 years', 1, 1),\n(now() - interval '8 years', 1, 1),\n(now() - interval '6 years', 1, 1),\n(now() - interval '4 years', 1, 1),\n(now() - interval '2 years', 1, 1),\n(now() - interval '1 years', 2, 2),\n(now() - interval '6 months', 3, 3),\n(now() - interval '3 months', 4, 4);\nCREATE MATERIALIZED VIEW cagg_scheduler(time, avg_a)\nWITH (timescaledb.continuous) AS\nSELECT time_bucket('1 month', time), avg(a)\nFROM test_table_scheduler\nGROUP BY time_bucket('1 month', time)\nWITH NO DATA;\nSELECT set_chunk_time_interval('_timescaledb_internal._materialized_hypertable_2', interval '36500 days');\n set_chunk_time_interval \n-------------------------\n \n\nselect show_chunks('test_table_scheduler');\n              show_chunks               \n----------------------------------------\n _timescaledb_internal._hyper_1_1_chunk\n _timescaledb_internal._hyper_1_2_chunk\n _timescaledb_internal._hyper_1_3_chunk\n _timescaledb_internal._hyper_1_4_chunk\n _timescaledb_internal._hyper_1_5_chunk\n _timescaledb_internal._hyper_1_6_chunk\n _timescaledb_internal._hyper_1_7_chunk\n _timescaledb_internal._hyper_1_8_chunk\n\nalter table test_table_scheduler set (timescaledb.compress, timescaledb.compress_orderby = 'time DESC');\nselect add_retention_policy('test_table_scheduler', interval '2 year', initial_start => :'init'::timestamptz, timezone => 'Europe/Berlin');\n add_retention_policy \n----------------------\n                 1026\n\nselect add_compression_policy('test_table_scheduler', interval '1 year', initial_start => :'init'::timestamptz, timezone => 'Europe/Berlin');\n add_compression_policy \n------------------------\n                   1027\n\nselect add_continuous_aggregate_policy('cagg_scheduler', interval '1 year', interval '2 months', interval '3 weeks',\ninitial_start => :'init'::timestamptz + interval '5 ms', timezone => 'Europe/Athens');\n add_continuous_aggregate_policy \n---------------------------------\n                            1028\n\nselect * from _timescaledb_catalog.bgw_job;\n  id  |              application_name              | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema       |              proc_name              |   owner    | scheduled | fixed_schedule |          initial_start           | hypertable_id |                                     config                                     |      check_schema      |                check_name                 |   timezone    \n------+--------------------------------------------+-------------------+-------------+-------------+--------------+------------------------+-------------------------------------+------------+-----------+----------------+----------------------------------+---------------+--------------------------------------------------------------------------------+------------------------+-------------------------------------------+---------------\n 1026 | Retention Policy [1026]                    | @ 1 day           | @ 5 mins    |          -1 | @ 5 mins     | _timescaledb_functions | policy_retention                    | super_user | t         | t              | Fri Dec 31 16:00:00 1999 PST     |             1 | {\"drop_after\": \"@ 2 years\", \"hypertable_id\": 1}                                | _timescaledb_functions | policy_retention_check                    | Europe/Berlin\n 1027 | Columnstore Policy [1027]                  | @ 12 hours        | @ 0         |          -1 | @ 1 hour     | _timescaledb_functions | policy_compression                  | super_user | t         | t              | Fri Dec 31 16:00:00 1999 PST     |             1 | {\"hypertable_id\": 1, \"compress_after\": \"@ 1 year\"}                             | _timescaledb_functions | policy_compression_check                  | Europe/Berlin\n 1028 | Refresh Continuous Aggregate Policy [1028] | @ 21 days         | @ 0         |          -1 | @ 21 days    | _timescaledb_functions | policy_refresh_continuous_aggregate | super_user | t         | t              | Fri Dec 31 16:00:00.005 1999 PST |             2 | {\"end_offset\": \"@ 2 mons\", \"start_offset\": \"@ 1 year\", \"mat_hypertable_id\": 2} | _timescaledb_functions | policy_refresh_continuous_aggregate_check | Europe/Athens\n\n-- now wait for scheduler to run the policies\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * from _timescaledb_internal.bgw_job_stat;\n job_id |            last_start            |           last_finish            |            next_start            |      last_successful_finish      | last_run_success | total_runs | total_duration | total_duration_failures | total_successes | total_failures | total_crashes | consecutive_failures | consecutive_crashes | flags \n--------+----------------------------------+----------------------------------+----------------------------------+----------------------------------+------------------+------------+----------------+-------------------------+-----------------+----------------+---------------+----------------------+---------------------+-------\n   1026 | Fri Dec 31 16:00:00 1999 PST     | Fri Dec 31 16:00:00 1999 PST     | Sat Jan 01 16:00:00 2000 PST     | Fri Dec 31 16:00:00 1999 PST     | t                |          1 | @ 0            | @ 0                     |               1 |              0 |             0 |                    0 |                   0 |     0\n   1027 | Fri Dec 31 16:00:00 1999 PST     | Fri Dec 31 16:00:00 1999 PST     | Sat Jan 01 04:00:00 2000 PST     | Fri Dec 31 16:00:00 1999 PST     | t                |          1 | @ 0            | @ 0                     |               1 |              0 |             0 |                    0 |                   0 |     0\n   1028 | Fri Dec 31 16:00:00.005 1999 PST | Fri Dec 31 16:00:00.005 1999 PST | Fri Jan 21 16:00:00.005 2000 PST | Fri Dec 31 16:00:00.005 1999 PST | t                |          1 | @ 0            | @ 0                     |               1 |              0 |             0 |                    0 |                   0 |     0\n\nSELECT show_chunks('test_table_scheduler');\n              show_chunks               \n----------------------------------------\n _timescaledb_internal._hyper_1_5_chunk\n _timescaledb_internal._hyper_1_6_chunk\n _timescaledb_internal._hyper_1_7_chunk\n _timescaledb_internal._hyper_1_8_chunk\n\nselect hypertable_schema, hypertable_name, chunk_schema, chunk_name, is_compressed from timescaledb_information.chunks ;\n   hypertable_schema   |      hypertable_name       |     chunk_schema      |    chunk_name     | is_compressed \n-----------------------+----------------------------+-----------------------+-------------------+---------------\n public                | test_table_scheduler       | _timescaledb_internal | _hyper_1_5_chunk  | t\n public                | test_table_scheduler       | _timescaledb_internal | _hyper_1_6_chunk  | f\n public                | test_table_scheduler       | _timescaledb_internal | _hyper_1_7_chunk  | f\n public                | test_table_scheduler       | _timescaledb_internal | _hyper_1_8_chunk  | f\n _timescaledb_internal | _materialized_hypertable_2 | _timescaledb_internal | _hyper_2_10_chunk | f\n\nselect avg_a from cagg_scheduler ORDER BY 1;\n       avg_a        \n--------------------\n 3.0000000000000000\n 4.0000000000000000\n\n-- test the API for add_job too\ncreate or replace procedure job_test_fixed(jobid int, config jsonb) language plpgsql as $$\nbegin\nraise NOTICE 'this is job_test_fixed';\nend\n$$;\n\\set ON_ERROR_STOP 0\nselect add_job('job_test_fixed', interval '7 months', initial_start => :'init'::timestamptz + interval '10 ms');\n add_job \n---------\n    1029\n\nselect add_job('job_test_fixed', interval '7 months', initial_start => :'init'::timestamptz + interval '10 ms', timezone => 'Europe/Athens');\n add_job \n---------\n    1030\n\n-- this will fail because the timezone has a bad value\nselect add_job('job_test_fixed', interval '8 weeks', timezone => 'EuRoPe/AmEriCa');\nERROR:  time zone \"EuRoPe/AmEriCa\" not recognized\nselect add_reorder_policy('test_table_scheduler','test_table_scheduler_time_idx',\ninitial_start => :'init'::timestamptz + interval '15 ms', timezone => 'Europe/Berlin');\n add_reorder_policy \n--------------------\n               1031\n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nselect * from _timescaledb_catalog.bgw_job order by id;\n  id  |              application_name              | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema       |              proc_name              |   owner    | scheduled | fixed_schedule |          initial_start           | hypertable_id |                                     config                                     |      check_schema      |                check_name                 |   timezone    \n------+--------------------------------------------+-------------------+-------------+-------------+--------------+------------------------+-------------------------------------+------------+-----------+----------------+----------------------------------+---------------+--------------------------------------------------------------------------------+------------------------+-------------------------------------------+---------------\n 1026 | Retention Policy [1026]                    | @ 1 day           | @ 5 mins    |          -1 | @ 5 mins     | _timescaledb_functions | policy_retention                    | super_user | t         | t              | Fri Dec 31 16:00:00 1999 PST     |             1 | {\"drop_after\": \"@ 2 years\", \"hypertable_id\": 1}                                | _timescaledb_functions | policy_retention_check                    | Europe/Berlin\n 1027 | Columnstore Policy [1027]                  | @ 12 hours        | @ 0         |          -1 | @ 1 hour     | _timescaledb_functions | policy_compression                  | super_user | t         | t              | Fri Dec 31 16:00:00 1999 PST     |             1 | {\"hypertable_id\": 1, \"compress_after\": \"@ 1 year\"}                             | _timescaledb_functions | policy_compression_check                  | Europe/Berlin\n 1028 | Refresh Continuous Aggregate Policy [1028] | @ 21 days         | @ 0         |          -1 | @ 21 days    | _timescaledb_functions | policy_refresh_continuous_aggregate | super_user | t         | t              | Fri Dec 31 16:00:00.005 1999 PST |             2 | {\"end_offset\": \"@ 2 mons\", \"start_offset\": \"@ 1 year\", \"mat_hypertable_id\": 2} | _timescaledb_functions | policy_refresh_continuous_aggregate_check | Europe/Athens\n 1029 | User-Defined Action [1029]                 | @ 7 mons          | @ 0         |          -1 | @ 5 mins     | public                 | job_test_fixed                      | super_user | t         | t              | Fri Dec 31 16:00:00.01 1999 PST  |               |                                                                                |                        |                                           | \n 1030 | User-Defined Action [1030]                 | @ 7 mons          | @ 0         |          -1 | @ 5 mins     | public                 | job_test_fixed                      | super_user | t         | t              | Fri Dec 31 16:00:00.01 1999 PST  |               |                                                                                |                        |                                           | Europe/Athens\n 1031 | Reorder Policy [1031]                      | @ 360 hours       | @ 0         |          -1 | @ 5 mins     | _timescaledb_functions | policy_reorder                      | super_user | t         | t              | Fri Dec 31 16:00:00.015 1999 PST |             1 | {\"index_name\": \"test_table_scheduler_time_idx\", \"hypertable_id\": 1}            | _timescaledb_functions | policy_reorder_check                      | Europe/Berlin\n\nSELECT\n  job_id,\n  date_trunc('second',last_start) AS last_start,\n  date_trunc('second',last_finish) AS last_finish,\n  date_trunc('second',next_start) AS next_start,\n  date_trunc('second',last_successful_finish) as last_successful_finish\nFROM _timescaledb_internal.bgw_job_stat\nORDER BY job_id;\n job_id |          last_start          |         last_finish          |          next_start          |    last_successful_finish    \n--------+------------------------------+------------------------------+------------------------------+------------------------------\n   1026 | Fri Dec 31 16:00:00 1999 PST | Fri Dec 31 16:00:00 1999 PST | Sat Jan 01 16:00:00 2000 PST | Fri Dec 31 16:00:00 1999 PST\n   1027 | Fri Dec 31 16:00:00 1999 PST | Fri Dec 31 16:00:00 1999 PST | Sat Jan 01 04:00:00 2000 PST | Fri Dec 31 16:00:00 1999 PST\n   1028 | Fri Dec 31 16:00:00 1999 PST | Fri Dec 31 16:00:00 1999 PST | Fri Jan 21 16:00:00 2000 PST | Fri Dec 31 16:00:00 1999 PST\n   1029 | Fri Dec 31 16:00:00 1999 PST | Fri Dec 31 16:00:00 1999 PST | Mon Jul 31 16:00:00 2000 PDT | Fri Dec 31 16:00:00 1999 PST\n   1030 | Fri Dec 31 16:00:00 1999 PST | Fri Dec 31 16:00:00 1999 PST | Mon Jul 31 16:00:00 2000 PDT | Fri Dec 31 16:00:00 1999 PST\n   1031 | Fri Dec 31 16:00:00 1999 PST | Fri Dec 31 16:00:00 1999 PST | Sat Jan 15 16:00:00 2000 PST | Fri Dec 31 16:00:00 1999 PST\n\n-- test ability to switch from one type of schedule to another\nCREATE OR REPLACE PROCEDURE job_test(jobid int, config jsonb) language plpgsql as $$\nBEGIN\nPERFORM pg_sleep(0.5);\nEND\n$$;\nSELECT add_job('job_test', '8 min', fixed_schedule => false) AS jobid_drifting_1 \\gset\nSELECT add_job('job_test', '8 min', fixed_schedule => false) AS jobid_drifting_2 \\gset\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT last_finish AS finish_time_drifting_1 FROM _timescaledb_internal.bgw_job_stat WHERE job_id = :jobid_drifting_1 \\gset\n-- job is on fixed schedule, so changing the timezone and initial start, has no effect on its next start,\n-- which should be 8 min after the finish time\nSELECT next_start AS next_start_drifting_1 FROM alter_job(:jobid_drifting_1, schedule_interval => interval '10 min', timezone => 'Europe/Athens') \\gset\nSELECT :'next_start_drifting_1'::timestamptz - :'finish_time_drifting_1'::timestamptz as diff_interval;\n diff_interval \n---------------\n @ 10 mins\n\n-- this will print a notice about using the current time as initial start\n-- suppress the notice though as it will lead to flaky tests\nset client_min_messages = 'warning';\nSELECT next_start, initial_start FROM alter_job(:jobid_drifting_1, schedule_interval => interval '10 min', fixed_schedule => true, initial_start => '-infinity') \\gset\n-- should be 10 min\nSELECT :'next_start'::timestamptz - :'initial_start'::timestamptz;\n ?column?  \n-----------\n @ 10 mins\n\n-- if job is not on fixed schedule, and we change it to fixed schedule, then user should also provide initial_start.\n-- if they don't, a notice is printed that we're using current time as initial start\nSELECT next_start, initial_start FROM alter_job(:jobid_drifting_2, schedule_interval => interval '10 min', fixed_schedule => true) \\gset\nSELECT :'next_start'::timestamptz - :'initial_start'::timestamptz;\n ?column?  \n-----------\n @ 10 mins\n\nreset client_min_messages;\n-- jobs starting with fixed schedules\nSELECT add_job('job_test', '1 month', initial_start => '2000-01-01 00:03') as jobid_fixed_1 \\gset\n-- wait for the job to run, then check its next_start: (3 minutes = 180mil microseconds)\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(180000005);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT last_finish, next_start from _timescaledb_internal.bgw_job_stat where job_id = :jobid_fixed_1;\n         last_finish          |          next_start          \n------------------------------+------------------------------\n Sat Jan 01 00:03:00 2000 PST | Tue Feb 01 00:03:00 2000 PST\n\nSELECT date_part('hour',next_start)::integer, date_part('minute',next_start)::integer, date_part('second',next_start)::integer FROM alter_job(:jobid_fixed_1, initial_start => '2020-01-01 04:00');\n date_part | date_part | date_part \n-----------+-----------+-----------\n         4 |         0 |         0\n\nSELECT date_part('hour',next_start)::integer, date_part('minute',next_start)::integer, date_part('second',next_start)::integer FROM _timescaledb_internal.bgw_job_stat WHERE job_id = :jobid_fixed_1;\n date_part | date_part | date_part \n-----------+-----------+-----------\n         4 |         0 |         0\n\n-- go from fixed_schedule to drifting schedule\nSELECT ts_bgw_params_destroy();\n ts_bgw_params_destroy \n-----------------------\n \n\nSELECT ts_bgw_params_create();\n ts_bgw_params_create \n----------------------\n \n\nSELECT add_job('job_test', '30 sec', initial_start => '2000-01-01 00:00:23') as jobid_fixed_2 \\gset\n-- wait for the job to run, check the next_start, once it's finished, switch to drifting schedule and\n-- check the next_start again\n-- wait for 30 seconds to pass\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(30000025);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nselect * from _timescaledb_internal.bgw_job_stat WHERE job_id = :jobid_fixed_2;\n job_id |          last_start          |         last_finish          |          next_start          |    last_successful_finish    | last_run_success | total_runs | total_duration | total_duration_failures | total_successes | total_failures | total_crashes | consecutive_failures | consecutive_crashes | flags \n--------+------------------------------+------------------------------+------------------------------+------------------------------+------------------+------------+----------------+-------------------------+-----------------+----------------+---------------+----------------------+---------------------+-------\n   1035 | Sat Jan 01 00:00:23 2000 PST | Sat Jan 01 00:00:23 2000 PST | Sat Jan 01 00:00:53 2000 PST | Sat Jan 01 00:00:23 2000 PST | t                |          1 | @ 0            | @ 0                     |               1 |              0 |             0 |                    0 |                   0 |     0\n\nUPDATE _timescaledb_internal.bgw_job_stat\nSET last_finish = last_finish + interval '10 sec', last_successful_finish = last_successful_finish + interval '10 sec'\nWHERE job_id = :jobid_fixed_2;\n-- next_start is unchanged\nSELECT * FROM _timescaledb_internal.bgw_job_stat WHERE job_id = :jobid_fixed_2;\n job_id |          last_start          |         last_finish          |          next_start          |    last_successful_finish    | last_run_success | total_runs | total_duration | total_duration_failures | total_successes | total_failures | total_crashes | consecutive_failures | consecutive_crashes | flags \n--------+------------------------------+------------------------------+------------------------------+------------------------------+------------------+------------+----------------+-------------------------+-----------------+----------------+---------------+----------------------+---------------------+-------\n   1035 | Sat Jan 01 00:00:23 2000 PST | Sat Jan 01 00:00:33 2000 PST | Sat Jan 01 00:00:53 2000 PST | Sat Jan 01 00:00:33 2000 PST | t                |          1 | @ 0            | @ 0                     |               1 |              0 |             0 |                    0 |                   0 |     0\n\n-- next start is now updated\nSELECT alter_job(:jobid_fixed_2, fixed_schedule => false);\n                                                                alter_job                                                                 \n------------------------------------------------------------------------------------------------------------------------------------------\n (1035,\"@ 30 secs\",\"@ 0\",-1,\"@ 5 mins\",t,,\"Sat Jan 01 00:01:03 2000 PST\",,f,\"Sat Jan 01 00:00:23 2000 PST\",,\"User-Defined Action [1035]\")\n\n"
  },
  {
    "path": "tsl/test/expected/bgw_job_ddl.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- Test for DDL-like functionality\nCREATE VIEW my_jobs AS\nSELECT proc_schema, proc_name, owner\n  FROM _timescaledb_catalog.bgw_job\n WHERE id >= 1000\nORDER BY proc_schema, proc_name, owner;\nGRANT SELECT ON my_jobs TO PUBLIC;\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION insert_job(\n       application_name NAME,\n       job_type NAME,\n       schedule_interval INTERVAL,\n       max_runtime INTERVAL,\n       retry_period INTERVAL,\n       owner regrole DEFAULT CURRENT_ROLE::regrole,\n       scheduled BOOL DEFAULT true,\n       fixed_schedule BOOL DEFAULT false\n) RETURNS INT LANGUAGE SQL SECURITY DEFINER AS\n$$\n  INSERT INTO _timescaledb_catalog.bgw_job(application_name,schedule_interval,max_runtime,max_retries,\n  retry_period,proc_name,proc_schema,owner,scheduled,fixed_schedule)\n  VALUES($1,$3,$4,5,$5,$2,'public',$6,$7,$8) RETURNING id;\n$$;\nCREATE PROCEDURE more_magic(job_id INT, config jsonb) LANGUAGE plpgsql AS $$\nBEGIN\n  RAISE NOTICE 'done';\nEND;\n$$;\nCREATE PROCEDURE some_magic(job_id INT, config jsonb) LANGUAGE plpgsql AS $$\nBEGIN\n  RAISE NOTICE 'done';\nEND;\n$$;\nCREATE USER another_user;\nSET ROLE another_user;\nSELECT insert_job('one_job', 'some_magic', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s') AS job_id_2 \\gset\nSELECT insert_job('another_one', 'more_magic', INTERVAL '100ms', INTERVAL '100s', INTERVAL '1s') AS job_id \\gset\nSELECT * FROM my_jobs;\n proc_schema | proc_name  |    owner     \n-------------+------------+--------------\n public      | more_magic | another_user\n public      | some_magic | another_user\n\n-- Test that reassigning to another user privileges does not work for\n-- a normal user. We test both users with superuser privileges and\n-- default permissions.\n\\set ON_ERROR_STOP 0\nREASSIGN OWNED BY another_user TO :ROLE_SUPERUSER;\nERROR:  permission denied to reassign objects\nREASSIGN OWNED BY another_user TO :ROLE_DEFAULT_PERM_USER;\nERROR:  permission denied to reassign objects\n\\set ON_ERROR_STOP 1\nRESET ROLE;\n-- Test that renaming a user changes keeps the job assigned to that user.\nALTER USER another_user RENAME TO renamed_user;\nSELECT * FROM my_jobs;\n proc_schema | proc_name  |    owner     \n-------------+------------+--------------\n public      | more_magic | renamed_user\n public      | some_magic | renamed_user\n\n-- Test that renaming the procedure also modifies the entry in the\n-- jobs table.\nALTER PROCEDURE more_magic RENAME TO magic;\nSELECT * FROM my_jobs;\n proc_schema | proc_name  |    owner     \n-------------+------------+--------------\n public      | magic      | renamed_user\n public      | some_magic | renamed_user\n\n-- Test that modifying the schema also modifies the entry in the jobs\n-- table.\nCREATE SCHEMA frugal;\nALTER PROCEDURE magic SET SCHEMA frugal;\nALTER PROCEDURE some_magic SET SCHEMA frugal;\nSELECT * FROM my_jobs;\n proc_schema | proc_name  |    owner     \n-------------+------------+--------------\n frugal      | magic      | renamed_user\n frugal      | some_magic | renamed_user\n\n-- Test that renaming the schema will rename the procedure schema\nSTART TRANSACTION;\nALTER SCHEMA frugal RENAME TO wicked;\nSELECT * FROM my_jobs;\n proc_schema | proc_name  |    owner     \n-------------+------------+--------------\n wicked      | magic      | renamed_user\n wicked      | some_magic | renamed_user\n\nROLLBACK;\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\nSELECT * FROM my_jobs;\n proc_schema | proc_name  |    owner     \n-------------+------------+--------------\n frugal      | magic      | renamed_user\n frugal      | some_magic | renamed_user\n\n-- Test that dropping a user owning a job fails.\nDROP USER renamed_user;\nERROR:  role \"renamed_user\" cannot be dropped because some objects depend on it\nDETAIL:  owner of job 1001\n-- Test that dropping the procedure fails since there is a background\n-- job using it.\nDROP PROCEDURE frugal.magic;\nERROR:  cannot drop frugal.magic because background job 1001 depends on it\nHINT:  Use delete_job() to drop the job first.\n-- Test that re-assigning objects owned by an unknown user still fails\nREASSIGN OWNED BY renamed_user, unknown_user TO :ROLE_DEFAULT_PERM_USER;\nERROR:  role \"unknown_user\" does not exist\n-- Test that dropping the schema without CASCADE will error out\nDROP SCHEMA frugal;\nERROR:  cannot drop schema frugal because other objects depend on it\nDETAIL:  function frugal.magic(integer,jsonb) depends on schema frugal\nfunction frugal.some_magic(integer,jsonb) depends on schema frugal\nHINT:  Use DROP ... CASCADE to drop the dependent objects too.\n\\set ON_ERROR_STOP 1\n-- Test that reassigning the owned job actually changes the owner of\n-- the job.\nSTART TRANSACTION;\nREASSIGN OWNED BY renamed_user TO :ROLE_DEFAULT_PERM_USER;\nSELECT * FROM my_jobs;\n proc_schema | proc_name  |       owner       \n-------------+------------+-------------------\n frugal      | magic      | default_perm_user\n frugal      | some_magic | default_perm_user\n\nROLLBACK;\n-- Test that reassigning to postgres works\nREASSIGN OWNED BY renamed_user TO :ROLE_SUPERUSER;\nSELECT * FROM my_jobs;\n proc_schema | proc_name  |   owner    \n-------------+------------+------------\n frugal      | magic      | super_user\n frugal      | some_magic | super_user\n\n-- Dropping the user now should work.\nDROP USER renamed_user;\n-- Dropping using Cascade should work and remove the background worker\n-- entry as well.\nSTART TRANSACTION;\nSELECT * FROM my_jobs;\n proc_schema | proc_name  |   owner    \n-------------+------------+------------\n frugal      | magic      | super_user\n frugal      | some_magic | super_user\n\nDROP PROCEDURE frugal.magic CASCADE;\nNOTICE:  drop cascades to job 1001\nSELECT * FROM my_jobs;\n proc_schema | proc_name  |   owner    \n-------------+------------+------------\n frugal      | some_magic | super_user\n\nROLLBACK;\nDELETE FROM _timescaledb_catalog.bgw_job WHERE id = :job_id;\n-- We should be able to drop the procedure without CASCADE now since\n-- it is not used by any job.\nDROP PROCEDURE frugal.magic;\n-- We should be able to drop the schema with CASCADE despite\n-- containing a procedure used by a background worker, but this should\n-- remove the job from the background worker table.\nSELECT * FROM my_jobs;\n proc_schema | proc_name  |   owner    \n-------------+------------+------------\n frugal      | some_magic | super_user\n\nDROP SCHEMA frugal CASCADE;\nNOTICE:  drop cascades to job 1000\nNOTICE:  drop cascades to function frugal.some_magic(integer,jsonb)\nSELECT * FROM my_jobs;\n proc_schema | proc_name | owner \n-------------+-----------+-------\n\n"
  },
  {
    "path": "tsl/test/expected/bgw_job_stat_history.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nALTER DATABASE :TEST_DBNAME SET timezone TO 'UTC';\n\\c\nCREATE PROCEDURE custom_job_ok(job_id int, config jsonb) LANGUAGE PLPGSQL AS\n$$\nBEGIN\n  RAISE INFO 'custom_job';\nEND\n$$;\nCREATE PROCEDURE custom_job_error(job_id int, config jsonb) LANGUAGE PLPGSQL AS\n$$\nBEGIN\n  PERFORM 1/0;\nEND\n$$;\nCREATE VIEW job_history_summary AS\nSELECT job_id, succeeded, count(*) AS record_count\n  FROM _timescaledb_internal.bgw_job_stat_history\nGROUP BY job_id, succeeded;\nCREATE VIEW recent_job_history_summary AS\nSELECT job_id, succeeded, count(*) AS record_count\n  FROM _timescaledb_internal.bgw_job_stat_history\n WHERE execution_finish > now() - interval '30 days'\nGROUP BY job_id, succeeded;\n-- Do not log all jobs, only FAILED executions\nSHOW timescaledb.enable_job_execution_logging;\n timescaledb.enable_job_execution_logging \n------------------------------------------\n off\n\nSELECT add_job('custom_job_ok', schedule_interval => interval '1 hour', initial_start := now()) AS job_id_1 \\gset\nSELECT add_job('custom_job_error', schedule_interval => interval '1 hour', initial_start := now()) AS job_id_2 \\gset\n-- Start Background Workers\nSELECT _timescaledb_functions.start_background_workers();\n start_background_workers \n--------------------------\n t\n\nSELECT test.wait_for_job_to_run(:job_id_1, 1);\n wait_for_job_to_run \n---------------------\n t\n\nSELECT test.wait_for_job_to_run(:job_id_2, 1);\nINFO:  wait_for_job_to_run: job execution failed\n wait_for_job_to_run \n---------------------\n f\n\n-- only 1 failure\nSELECT count(*), succeeded FROM timescaledb_information.job_history WHERE job_id >= 1000 GROUP BY 2 ORDER BY 2;\n count | succeeded \n-------+-----------\n     1 | f\n\nSELECT proc_schema, proc_name, sqlerrcode, err_message FROM timescaledb_information.job_history WHERE job_id >= 1000 AND succeeded IS FALSE;\n proc_schema |    proc_name     | sqlerrcode |   err_message    \n-------------+------------------+------------+------------------\n public      | custom_job_error | 22012      | division by zero\n\n-- Check current jobs status\nSELECT job_id, job_status, total_runs, total_successes, total_failures\nFROM timescaledb_information.job_stats\nWHERE job_id >= 1000\nORDER BY job_id;\n job_id | job_status | total_runs | total_successes | total_failures \n--------+------------+------------+-----------------+----------------\n   1000 | Scheduled  |          1 |               1 |              0\n   1001 | Scheduled  |          1 |               0 |              1\n\n-- Log all executions\nALTER SYSTEM SET timescaledb.enable_job_execution_logging TO ON;\nSELECT pg_reload_conf();\n pg_reload_conf \n----------------\n t\n\n-- Reconnect to make sure the GUC is set\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT scheduled FROM alter_job(:job_id_1, next_start => now());\n scheduled \n-----------\n t\n\nSELECT scheduled FROM alter_job(:job_id_2, next_start => now());\n scheduled \n-----------\n t\n\nSELECT _timescaledb_functions.restart_background_workers();\n restart_background_workers \n----------------------------\n t\n\nSELECT test.wait_for_job_to_run(:job_id_1, 2);\n wait_for_job_to_run \n---------------------\n t\n\nSELECT test.wait_for_job_to_run(:job_id_2, 2);\nINFO:  wait_for_job_to_run: job execution failed\n wait_for_job_to_run \n---------------------\n f\n\n-- 1 succeeded 2 failures\nSELECT count(*), succeeded FROM timescaledb_information.job_history WHERE job_id >= 1000 GROUP BY 2 ORDER BY 2;\n count | succeeded \n-------+-----------\n     2 | f\n     1 | t\n\n-- Check current jobs status\nSELECT job_id, job_status, total_runs, total_successes, total_failures\nFROM timescaledb_information.job_stats\nWHERE job_id >= 1000\nORDER BY job_id;\n job_id | job_status | total_runs | total_successes | total_failures \n--------+------------+------------+-----------------+----------------\n   1000 | Scheduled  |          2 |               2 |              0\n   1001 | Scheduled  |          2 |               0 |              2\n\n-- Check config changes over time\nSELECT scheduled FROM alter_job(:job_id_1, config => '{\"foo\": 1}'::jsonb);\n scheduled \n-----------\n t\n\nSELECT scheduled FROM alter_job(:job_id_2, config => '{\"bar\": 1}'::jsonb);\n scheduled \n-----------\n t\n\nSELECT scheduled FROM alter_job(:job_id_1, next_start => now());\n scheduled \n-----------\n t\n\nSELECT scheduled FROM alter_job(:job_id_2, next_start => now());\n scheduled \n-----------\n t\n\nSELECT _timescaledb_functions.restart_background_workers();\n restart_background_workers \n----------------------------\n t\n\nSELECT test.wait_for_job_to_run(:job_id_1, 3);\n wait_for_job_to_run \n---------------------\n t\n\nSELECT test.wait_for_job_to_run(:job_id_2, 3);\nINFO:  wait_for_job_to_run: job execution failed\n wait_for_job_to_run \n---------------------\n f\n\n-- Check job execution history\nSELECT job_id, pid IS NOT NULL AS pid, proc_schema, proc_name, succeeded, config, sqlerrcode, err_message\nFROM timescaledb_information.job_history\nWHERE job_id >= 1000\nORDER BY id, job_id;\n job_id | pid | proc_schema |    proc_name     | succeeded |   config   | sqlerrcode |   err_message    \n--------+-----+-------------+------------------+-----------+------------+------------+------------------\n   1001 | t   | public      | custom_job_error | f         |            | 22012      | division by zero\n   1000 | t   | public      | custom_job_ok    | t         |            |            | \n   1001 | t   | public      | custom_job_error | f         |            | 22012      | division by zero\n   1000 | t   | public      | custom_job_ok    | t         | {\"foo\": 1} |            | \n   1001 | t   | public      | custom_job_error | f         | {\"bar\": 1} | 22012      | division by zero\n\n-- Changing the config of one job\nSELECT scheduled FROM alter_job(:job_id_1, config => '{\"foo\": 2, \"bar\": 1}'::jsonb);\n scheduled \n-----------\n t\n\nSELECT scheduled FROM alter_job(:job_id_1, next_start => now());\n scheduled \n-----------\n t\n\nSELECT _timescaledb_functions.restart_background_workers();\n restart_background_workers \n----------------------------\n t\n\nSELECT test.wait_for_job_to_run(:job_id_1, 4);\n wait_for_job_to_run \n---------------------\n t\n\n-- Check job execution history\nSELECT job_id, pid IS NOT NULL AS pid, proc_schema, proc_name, succeeded, config, sqlerrcode, err_message\nFROM timescaledb_information.job_history\nWHERE job_id = :job_id_1\nORDER BY id;\n job_id | pid | proc_schema |   proc_name   | succeeded |        config        | sqlerrcode | err_message \n--------+-----+-------------+---------------+-----------+----------------------+------------+-------------\n   1000 | t   | public      | custom_job_ok | t         |                      |            | \n   1000 | t   | public      | custom_job_ok | t         | {\"foo\": 1}           |            | \n   1000 | t   | public      | custom_job_ok | t         | {\"bar\": 1, \"foo\": 2} |            | \n\n-- Change the job procedure to alter the job configuration during the execution\nCREATE OR REPLACE PROCEDURE custom_job_ok(job_id int, config jsonb) LANGUAGE PLPGSQL AS\n$$\nBEGIN\n  RAISE INFO 'custom_job';\n  PERFORM alter_job(job_id, config => '{\"config_changed_by_job_execution\": 1}'::jsonb);\nEND\n$$;\n-- Run the job\nSELECT scheduled FROM alter_job(:job_id_1, next_start => now());\n scheduled \n-----------\n t\n\nSELECT _timescaledb_functions.restart_background_workers();\n restart_background_workers \n----------------------------\n t\n\nSELECT test.wait_for_job_to_run(:job_id_1, 5);\n wait_for_job_to_run \n---------------------\n t\n\n-- Check job execution history\nSELECT job_id, pid IS NOT NULL AS pid, proc_schema, proc_name, succeeded, config, sqlerrcode, err_message\nFROM timescaledb_information.job_history\nWHERE job_id = :job_id_1\nORDER BY id;\n job_id | pid | proc_schema |   proc_name   | succeeded |                 config                 | sqlerrcode | err_message \n--------+-----+-------------+---------------+-----------+----------------------------------------+------------+-------------\n   1000 | t   | public      | custom_job_ok | t         |                                        |            | \n   1000 | t   | public      | custom_job_ok | t         | {\"foo\": 1}                             |            | \n   1000 | t   | public      | custom_job_ok | t         | {\"bar\": 1, \"foo\": 2}                   |            | \n   1000 | t   | public      | custom_job_ok | t         | {\"config_changed_by_job_execution\": 1} |            | \n\n-- Change the job procedure to alter the job configuration during the execution\nCREATE OR REPLACE PROCEDURE custom_job_ok(job_id int, config jsonb) LANGUAGE PLPGSQL AS\n$$\nBEGIN\n  RAISE INFO 'custom_job';\n  PERFORM alter_job(job_id, config => '{\"change_not_logged\": 1}'::jsonb);\n  COMMIT;\n  PERFORM alter_job(job_id, config => '{\"only_last_change_is_logged\": 1}'::jsonb);\n  COMMIT;\nEND\n$$;\n-- Run the job\nSELECT scheduled FROM alter_job(:job_id_1, next_start => now());\n scheduled \n-----------\n t\n\nSELECT _timescaledb_functions.restart_background_workers();\n restart_background_workers \n----------------------------\n t\n\nSELECT test.wait_for_job_to_run(:job_id_1, 6);\n wait_for_job_to_run \n---------------------\n t\n\n-- Check job execution history\nSELECT job_id, pid IS NOT NULL AS pid, proc_schema, proc_name, succeeded, config, sqlerrcode, err_message\nFROM timescaledb_information.job_history\nWHERE job_id = :job_id_1\nORDER BY id;\n job_id | pid | proc_schema |   proc_name   | succeeded |                 config                 | sqlerrcode | err_message \n--------+-----+-------------+---------------+-----------+----------------------------------------+------------+-------------\n   1000 | t   | public      | custom_job_ok | t         |                                        |            | \n   1000 | t   | public      | custom_job_ok | t         | {\"foo\": 1}                             |            | \n   1000 | t   | public      | custom_job_ok | t         | {\"bar\": 1, \"foo\": 2}                   |            | \n   1000 | t   | public      | custom_job_ok | t         | {\"config_changed_by_job_execution\": 1} |            | \n   1000 | t   | public      | custom_job_ok | t         | {\"only_last_change_is_logged\": 1}      |            | \n\n-- Alter other information about the job\nCREATE PROCEDURE custom_job_alter(job_id int, config jsonb) LANGUAGE PLPGSQL AS\n$$\nBEGIN\n  RAISE LOG 'custom_job_alter';\nEND\n$$;\nSELECT add_job('custom_job_alter', schedule_interval => interval '1 hour', initial_start := now()) AS job_id_3 \\gset\nSELECT _timescaledb_functions.restart_background_workers();\n restart_background_workers \n----------------------------\n t\n\nSELECT test.wait_for_job_to_run(:job_id_3, 1);\n wait_for_job_to_run \n---------------------\n t\n\nSELECT timezone, fixed_schedule, config, schedule_interval\nFROM alter_job(:job_id_3, timezone => 'America/Sao_Paulo', fixed_schedule => false, config => '{\"key\": \"value\"}'::jsonb, schedule_interval => interval '10 min', next_start => now());\n     timezone      | fixed_schedule |      config      | schedule_interval \n-------------------+----------------+------------------+-------------------\n America/Sao_Paulo | f              | {\"key\": \"value\"} | @ 10 mins\n\nSELECT _timescaledb_functions.restart_background_workers();\n restart_background_workers \n----------------------------\n t\n\nSELECT test.wait_for_job_to_run(:job_id_3, 2);\n wait_for_job_to_run \n---------------------\n t\n\n-- Should return two executions, the second will show the changed values\nSELECT job_id, succeeded, data->'job'->>'timezone' AS timezone, data->'job'->>'fixed_schedule' AS fixed_schedule, data->'job'->>'schedule_interval' AS schedule_interval, data->'job'->'config' AS config\nFROM _timescaledb_internal.bgw_job_stat_history\nWHERE job_id = :job_id_3\nORDER BY id;\n job_id | succeeded |     timezone      | fixed_schedule | schedule_interval |      config      \n--------+-----------+-------------------+----------------+-------------------+------------------\n   1002 | t         |                   | true           | 01:00:00          | \n   1002 | t         | America/Sao_Paulo | false          | 00:10:00          | {\"key\": \"value\"}\n\nSELECT delete_job(:job_id_1);\n delete_job \n------------\n \n\nSELECT delete_job(:job_id_2);\n delete_job \n------------\n \n\nSELECT delete_job(:job_id_3);\n delete_job \n------------\n \n\nALTER SYSTEM RESET timescaledb.enable_job_execution_logging;\nSELECT pg_reload_conf();\n pg_reload_conf \n----------------\n t\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- The GUC is PGC_SIGHUP context so only ALTER SYSTEM is allowed\n\\set ON_ERROR_STOP 0\nSHOW timescaledb.enable_job_execution_logging;\n timescaledb.enable_job_execution_logging \n------------------------------------------\n off\n\nSET timescaledb.enable_job_execution_logging TO OFF;\nERROR:  parameter \"timescaledb.enable_job_execution_logging\" cannot be changed now\nSHOW timescaledb.enable_job_execution_logging;\n timescaledb.enable_job_execution_logging \n------------------------------------------\n off\n\nALTER DATABASE :TEST_DBNAME SET timescaledb.enable_job_execution_logging TO ON;\nERROR:  parameter \"timescaledb.enable_job_execution_logging\" cannot be changed now\nSHOW timescaledb.enable_job_execution_logging;\n timescaledb.enable_job_execution_logging \n------------------------------------------\n off\n\n\\set ON_ERROR_STOP 1\nSELECT _timescaledb_functions.stop_background_workers();\n stop_background_workers \n-------------------------\n t\n\n-- Test bgw_job_stat_history retention job\n-- Alter the drop_after interval to be fixed (30 days) to ensure tests are deterministic\nSELECT config AS config FROM _timescaledb_catalog.bgw_job WHERE id = 3 \\gset\nSELECT config FROM alter_job(3, config => jsonb_set(:'config', '{drop_after}', '\"30 days\"'));\n                                         config                                         \n----------------------------------------------------------------------------------------\n {\"drop_after\": \"30 days\", \"max_failures_per_job\": 1000, \"max_successes_per_job\": 1000}\n\n-- These configuration should fail since they are not valid.\n\\set ON_ERROR_STOP 0\nSELECT config FROM alter_job(3, config => :'config'::jsonb - 'drop_after');\nERROR:  drop_after interval not provided\nSELECT config FROM alter_job(3, config => :'config'::jsonb - 'max_successes_per_job');\nERROR:  max_successes_per_job not provided\nSELECT config FROM alter_job(3, config => :'config'::jsonb - 'max_failures_per_job');\nERROR:  max_failures_per_job not provided\nSELECT config FROM alter_job(3, config => jsonb_set(:'config', '{max_successes_per_job}', '0'));\nERROR:  max_successes_per_job has to be at least 10\nSELECT config FROM alter_job(3, config => jsonb_set(:'config', '{max_failures_per_job}', '0'));\nERROR:  max_failures_per_job has to be at least 10\nSELECT config FROM alter_job(3, config => jsonb_set(:'config', '{max_successes_per_job}', '\"none\"'));\nERROR:  invalid input syntax for type integer: \"none\"\nSELECT config FROM alter_job(3, config => jsonb_set(:'config', '{max_failures_per_job}', '\"none\"'));\nERROR:  invalid input syntax for type integer: \"none\"\n\\set ON_ERROR_STOP 1\n-- Test 1\nTRUNCATE _timescaledb_internal.bgw_job_stat_history;\n-- Insert test data: jobs every 15 minutes from 3 months ago to today\n-- Each job runs for 5 minutes (job_id=100, pid=12345)\n-- Fix NOW to ensure the tests are deterministic\nSET timezone TO 'UTC';\nINSERT INTO _timescaledb_internal.bgw_job_stat_history\n(job_id, pid, succeeded, execution_start, execution_finish, data)\nSELECT\n    100 as job_id,\n    12345 as pid,\n    true as succeeded,\n    ts as execution_start,\n    ts + interval '5 minutes' as execution_finish,\n    '{}'::jsonb as data\nFROM generate_series(now() - interval '90 days', now(), interval '15 minutes') as ts;\n-- Check data after insertion\nselect * from job_history_summary;\n job_id | succeeded | record_count \n--------+-----------+--------------\n    100 | t         |         8641\n\n-- Test the retention job (job id 3)\nCALL run_job(3);\n-- Check data after retention\nSELECT * FROM job_history_summary;\n job_id | succeeded | record_count \n--------+-----------+--------------\n    100 | t         |         1000\n\n-- Verify only recent records remain\nSELECT * FROM recent_job_history_summary;\n job_id | succeeded | record_count \n--------+-----------+--------------\n    100 | t         |         1000\n\n-- Cleanup\nTRUNCATE _timescaledb_internal.bgw_job_stat_history;\n-- Test 2: Empty table (no job history)\nCALL run_job(3);\nSELECT * FROM job_history_summary;\n job_id | succeeded | record_count \n--------+-----------+--------------\n\n-- Verify only recent records remain\nSELECT * FROM recent_job_history_summary;\n job_id | succeeded | record_count \n--------+-----------+--------------\n\n-- Test 3: Odd number of entries (5 entries)\nINSERT INTO _timescaledb_internal.bgw_job_stat_history\n(job_id, pid, succeeded, execution_start, execution_finish, data)\nVALUES\n(301, 3001, true, now() - interval '60 days', now() - interval '60 days' + interval '5 minutes', '{}'),\n(302, 3002, true, now() - interval '6 weeks', now() - interval '6 weeks' + interval '5 minutes', '{}'),\n(303, 3003, true, now() - interval '30 days', now() - interval '30 days' + interval '5 minutes', '{}'),\n(301, 3001, true, now() - interval '2 weeks', now() - interval '2 weeks' + interval '5 minutes', '{}'),\n(304, 3004, true, now() - interval '1 week', now() - interval '1 week' + interval '5 minutes', '{}');\nSELECT * FROM job_history_summary;\n job_id | succeeded | record_count \n--------+-----------+--------------\n    301 | t         |            2\n    302 | t         |            1\n    303 | t         |            1\n    304 | t         |            1\n\nCALL run_job(3);\nSELECT * FROM job_history_summary;\n job_id | succeeded | record_count \n--------+-----------+--------------\n    301 | t         |            1\n    303 | t         |            1\n    304 | t         |            1\n\n-- Verify only recent records remain\nSELECT * FROM recent_job_history_summary;\n job_id | succeeded | record_count \n--------+-----------+--------------\n    301 | t         |            1\n    303 | t         |            1\n    304 | t         |            1\n\nTRUNCATE _timescaledb_internal.bgw_job_stat_history;\n-- Test 4: Even number of entries (6 entries)\nINSERT INTO _timescaledb_internal.bgw_job_stat_history\n(job_id, pid, succeeded, execution_start, execution_finish, data)\nVALUES\n(401, 4001, true, now() - interval '90 days', now() - interval '90 days' + interval '5 minutes', '{}'),\n(402, 4002, true, now() - interval '60 days', now() - interval '60 days' + interval '5 minutes', '{}'),\n(403, 4003, true, now() - interval '6 weeks', now() - interval '6 weeks' + interval '5 minutes', '{}'),\n(401, 4001, true, now() - interval '30 days', now() - interval '30 days' + interval '5 minutes', '{}'),\n(404, 4004, true, now() - interval '2 weeks', now() - interval '2 weeks' + interval '5 minutes', '{}'),\n(402, 4002, true, now() - interval '1 week', now() - interval '1 week' + interval '5 minutes', '{}');\nSELECT * FROM job_history_summary;\n job_id | succeeded | record_count \n--------+-----------+--------------\n    403 | t         |            1\n    401 | t         |            2\n    404 | t         |            1\n    402 | t         |            2\n\nCALL run_job(3);\nSELECT * FROM job_history_summary;\n job_id | succeeded | record_count \n--------+-----------+--------------\n    401 | t         |            1\n    404 | t         |            1\n    402 | t         |            1\n\n-- Verify only recent records remain\nSELECT * FROM recent_job_history_summary;\n job_id | succeeded | record_count \n--------+-----------+--------------\n    401 | t         |            1\n    404 | t         |            1\n    402 | t         |            1\n\nTRUNCATE _timescaledb_internal.bgw_job_stat_history;\n-- Test 5: Missing middle job id (gaps in sequence)\nINSERT INTO _timescaledb_internal.bgw_job_stat_history\n(job_id, pid, succeeded, execution_start, execution_finish, data)\nSELECT\n    501 + (row_number() over () % 3) as job_id,\n    5001 + (row_number() over () % 3) as pid,\n    true as succeeded,\n    ts as execution_start,\n    ts + interval '5 minutes' as execution_finish,\n    '{}'::jsonb as data\nFROM generate_series(now() - interval '60 days', now() - interval '1 week', interval '1 week') as ts;\n-- Delete some records to create gaps\nDELETE FROM _timescaledb_internal.bgw_job_stat_history\nWHERE id IN (SELECT id FROM _timescaledb_internal.bgw_job_stat_history ORDER BY id LIMIT 2 OFFSET 2);\nSELECT * FROM job_history_summary;\n job_id | succeeded | record_count \n--------+-----------+--------------\n    503 | t         |            3\n    502 | t         |            2\n    501 | t         |            1\n\nCALL run_job(3);\nSELECT * FROM job_history_summary;\n job_id | succeeded | record_count \n--------+-----------+--------------\n    503 | t         |            1\n    502 | t         |            1\n    501 | t         |            1\n\n-- Verify only recent records remain\nSELECT * FROM recent_job_history_summary;\n job_id | succeeded | record_count \n--------+-----------+--------------\n    503 | t         |            1\n    502 | t         |            1\n    501 | t         |            1\n\nTRUNCATE _timescaledb_internal.bgw_job_stat_history;\n-- Test 6: All records older than retention period\nINSERT INTO _timescaledb_internal.bgw_job_stat_history\n(job_id, pid, succeeded, execution_start, execution_finish, data)\nVALUES\n(601, 6001, true, now() - interval '90 days', now() - interval '90 days' + interval '5 minutes', '{}'),\n(602, 6002, true, now() - interval '60 days', now() - interval '60 days' + interval '5 minutes', '{}'),\n(601, 6001, true, now() - interval '6 weeks', now() - interval '6 weeks' + interval '5 minutes', '{}');\nSELECT * FROM job_history_summary;\n job_id | succeeded | record_count \n--------+-----------+--------------\n    601 | t         |            2\n    602 | t         |            1\n\nCALL run_job(3);\nSELECT * FROM job_history_summary;\n job_id | succeeded | record_count \n--------+-----------+--------------\n    601 | t         |            2\n    602 | t         |            1\n\n-- Verify only recent records remain\nSELECT * FROM recent_job_history_summary;\n job_id | succeeded | record_count \n--------+-----------+--------------\n\nTRUNCATE _timescaledb_internal.bgw_job_stat_history;\n-- Test 7: No records older than retention period\nINSERT INTO _timescaledb_internal.bgw_job_stat_history\n(job_id, pid, succeeded, execution_start, execution_finish, data)\nVALUES\n(701, 7001, true, now() - interval '1 week', now() - interval '1 week' + interval '7 minutes', '{}'),\n(702, 7002, true, now() - interval '6 days', now() - interval '6 days' + interval '7 minutes', '{}'),\n(703, 7003, true, now() - interval '7 days', now() - interval '7 days' + interval '7 minutes', '{}'),\n(701, 7001, true, now() - interval '4 days', now() - interval '4 days' + interval '7 minutes', '{}');\nSELECT * FROM job_history_summary;\n job_id | succeeded | record_count \n--------+-----------+--------------\n    701 | t         |            2\n    702 | t         |            1\n    703 | t         |            1\n\nCALL run_job(3);\nSELECT * FROM job_history_summary;\n job_id | succeeded | record_count \n--------+-----------+--------------\n    701 | t         |            2\n    702 | t         |            1\n    703 | t         |            1\n\n-- Verify only recent records remain\nSELECT * FROM recent_job_history_summary;\n job_id | succeeded | record_count \n--------+-----------+--------------\n    701 | t         |            2\n    702 | t         |            1\n    703 | t         |            1\n\nTRUNCATE _timescaledb_internal.bgw_job_stat_history;\n-- Test 8: No records older than retention period and not more than\n-- the expected number of successes and failures per job.\nSELECT config AS config FROM _timescaledb_catalog.bgw_job WHERE id = 3 \\gset\nSELECT config FROM alter_job(3,\n    config => jsonb_set(jsonb_set(:'config', '{max_failures_per_job}', '15'), '{max_successes_per_job}', '10'));\n                                       config                                       \n------------------------------------------------------------------------------------\n {\"drop_after\": \"30 days\", \"max_failures_per_job\": 15, \"max_successes_per_job\": 10}\n\nINSERT INTO _timescaledb_internal.bgw_job_stat_history\n(job_id, pid, succeeded, execution_start, execution_finish, data)\nVALUES\n(803, 7003, true, now() - interval '7 days', now() - interval '7 days' + interval '7 minutes', '{}'),\n(801, 7001, true, now() - interval '4 days', now() - interval '4 days' + interval '7 minutes', '{}');\nINSERT INTO\n   _timescaledb_internal.bgw_job_stat_history(job_id, pid, succeeded, execution_start, execution_finish, data)\nSELECT 801, 7001, true, now() - format('%s hour', hours)::interval, now() - interval '1 week' + interval '7 minutes', '{}'\nFROM generate_series(1,20) hours;\nINSERT INTO\n   _timescaledb_internal.bgw_job_stat_history(job_id, pid, succeeded, execution_start, execution_finish, data)\nSELECT 802, 7001, false, now() - format('%s minutes', hours)::interval, now() - interval '6 days' + interval '7 minutes', '{}'\nFROM generate_series(1,20) hours;\nSELECT * FROM job_history_summary;\n job_id | succeeded | record_count \n--------+-----------+--------------\n    801 | t         |           21\n    803 | t         |            1\n    802 | f         |           20\n\nCALL run_job(3);\nSELECT * FROM job_history_summary;\n job_id | succeeded | record_count \n--------+-----------+--------------\n    801 | t         |           10\n    803 | t         |            1\n    802 | f         |           15\n\n-- Verify only recent records remain\nSELECT * FROM recent_job_history_summary;\n job_id | succeeded | record_count \n--------+-----------+--------------\n    801 | t         |           10\n    803 | t         |            1\n    802 | f         |           15\n\n-- Cleanup\nTRUNCATE _timescaledb_internal.bgw_job_stat_history;\n-- Test that lock_timeout can be configured\nSELECT config FROM alter_job(3, config => jsonb_set(:'config', '{lock_timeout}', '\"1s\"'));\n                                                    config                                                    \n--------------------------------------------------------------------------------------------------------------\n {\"drop_after\": \"30 days\", \"lock_timeout\": \"1s\", \"max_failures_per_job\": 1000, \"max_successes_per_job\": 1000}\n\nCALL run_job(3);\n-- Test the job_history_bsearch function directly as well\n-- It returns the first element where execution_finish >= search_point or NULL if no such element exists\n\\set NOW '2025-08-15 12:34:00'\n-- No elements in table\nSELECT _timescaledb_functions.job_history_bsearch(:'NOW'::timestamptz - interval '1 month');\n job_history_bsearch \n---------------------\n                    \n\n-- Single element\nINSERT INTO _timescaledb_internal.bgw_job_stat_history\n(id, job_id, pid, succeeded, execution_start, execution_finish, data)\nVALUES\n(5, 601, 6001, true, :'NOW'::timestamptz - interval '2 weeks', :'NOW'::timestamptz - interval '2 weeks' + interval '5 minutes', '{}');\n-- Return the single element\nSELECT _timescaledb_functions.job_history_bsearch(:'NOW'::timestamptz - interval '1 month');\n job_history_bsearch \n---------------------\n                   5\n\n-- Return NULL\nSELECT _timescaledb_functions.job_history_bsearch(:'NOW'::timestamptz - interval '1 week');\n job_history_bsearch \n---------------------\n                    \n\nTRUNCATE _timescaledb_internal.bgw_job_stat_history;\n-- Two elements\nINSERT INTO _timescaledb_internal.bgw_job_stat_history\n(id, job_id, pid, succeeded, execution_start, execution_finish, data)\nVALUES\n(5, 701, 7001, true, :'NOW'::timestamptz - interval '3 weeks', :'NOW'::timestamptz - interval '3 weeks' + interval '5 minutes', '{}'),\n(6, 702, 7002, true, :'NOW'::timestamptz - interval '1 week', :'NOW'::timestamptz - interval '1 week' + interval '5 minutes', '{}');\n-- Returns the first element\nSELECT _timescaledb_functions.job_history_bsearch(:'NOW'::timestamptz - interval '1 month');\n job_history_bsearch \n---------------------\n                   5\n\n-- Returns the second element\nSELECT _timescaledb_functions.job_history_bsearch(:'NOW'::timestamptz - interval '2 weeks');\n job_history_bsearch \n---------------------\n                   6\n\n-- Returns NULL\nSELECT _timescaledb_functions.job_history_bsearch(:'NOW'::timestamptz - interval '3 days');\n job_history_bsearch \n---------------------\n                    \n\nTRUNCATE _timescaledb_internal.bgw_job_stat_history;\n-- Odd number of elements\nINSERT INTO _timescaledb_internal.bgw_job_stat_history\n(id, job_id, pid, succeeded, execution_start, execution_finish, data)\nVALUES\n(5, 801, 8001, true, :'NOW'::timestamptz - interval '5 weeks', :'NOW'::timestamptz - interval '5 weeks' + interval '5 minutes', '{}'),\n(6, 802, 8002, true, :'NOW'::timestamptz - interval '4 weeks', :'NOW'::timestamptz - interval '4 weeks' + interval '5 minutes', '{}'),\n(7, 803, 8003, true, :'NOW'::timestamptz - interval '3 weeks', :'NOW'::timestamptz - interval '3 weeks' + interval '5 minutes', '{}'),\n(8, 804, 8004, true, :'NOW'::timestamptz - interval '2 weeks', :'NOW'::timestamptz - interval '2 weeks' + interval '5 minutes', '{}'),\n(9, 805, 8005, true, :'NOW'::timestamptz - interval '1 week', :'NOW'::timestamptz - interval '1 week' + interval '5 minutes', '{}');\n-- Returns the first element\nSELECT _timescaledb_functions.job_history_bsearch(:'NOW'::timestamptz - interval '6 weeks');\n job_history_bsearch \n---------------------\n                   5\n\n-- Returns the middle element\nSELECT _timescaledb_functions.job_history_bsearch(:'NOW'::timestamptz - interval '3 weeks');\n job_history_bsearch \n---------------------\n                   7\n\n-- Returns one after the middle element\nSELECT _timescaledb_functions.job_history_bsearch(:'NOW'::timestamptz - interval '2 weeks 3 days');\n job_history_bsearch \n---------------------\n                   8\n\n-- Returns NULL\nSELECT _timescaledb_functions.job_history_bsearch(:'NOW'::timestamptz - interval '2 days');\n job_history_bsearch \n---------------------\n                    \n\nTRUNCATE _timescaledb_internal.bgw_job_stat_history;\n-- Even number of elements\nINSERT INTO _timescaledb_internal.bgw_job_stat_history\n(id, job_id, pid, succeeded, execution_start, execution_finish, data)\nVALUES\n(5, 902, 9002, true, :'NOW'::timestamptz - interval '5 weeks', :'NOW'::timestamptz - interval '5 weeks' + interval '5 minutes', '{}'),\n(6, 903, 9003, true, :'NOW'::timestamptz - interval '4 weeks', :'NOW'::timestamptz - interval '4 weeks' + interval '5 minutes', '{}'),\n(7, 904, 9004, true, :'NOW'::timestamptz - interval '3 weeks', :'NOW'::timestamptz - interval '3 weeks' + interval '5 minutes', '{}'),\n(8, 905, 9005, true, :'NOW'::timestamptz - interval '2 weeks', :'NOW'::timestamptz - interval '2 weeks' + interval '5 minutes', '{}'),\n(9, 906, 9006, true, :'NOW'::timestamptz - interval '1 week', :'NOW'::timestamptz - interval '1 week' + interval '5 minutes', '{}'),\n(10, 907, 9007, true, :'NOW'::timestamptz - interval '3 days', :'NOW'::timestamptz - interval '3 days' + interval '5 minutes', '{}');\n-- Returns the first element\nSELECT _timescaledb_functions.job_history_bsearch(:'NOW'::timestamptz - interval '6 weeks');\n job_history_bsearch \n---------------------\n                   5\n\n-- Returns the middle element\nSELECT _timescaledb_functions.job_history_bsearch(:'NOW'::timestamptz - interval '3 weeks');\n job_history_bsearch \n---------------------\n                   7\n\n-- Returns one after the middle element\nSELECT _timescaledb_functions.job_history_bsearch(:'NOW'::timestamptz - interval '2 weeks 3 days');\n job_history_bsearch \n---------------------\n                   8\n\n-- Returns NULL\nSELECT _timescaledb_functions.job_history_bsearch(:'NOW'::timestamptz - interval '2 days');\n job_history_bsearch \n---------------------\n                    \n\nTRUNCATE _timescaledb_internal.bgw_job_stat_history;\n-- With gaps in id\nINSERT INTO _timescaledb_internal.bgw_job_stat_history\n(id, job_id, pid, succeeded, execution_start, execution_finish, data)\nVALUES\n(10, 1001, 10001, true, :'NOW'::timestamptz - interval '5 weeks', :'NOW'::timestamptz - interval '5 weeks' + interval '5 minutes', '{}'),\n(11, 1002, 10002, true, :'NOW'::timestamptz - interval '4 weeks', :'NOW'::timestamptz - interval '4 weeks' + interval '5 minutes', '{}'),\n(13, 1003, 10003, true, :'NOW'::timestamptz - interval '3 weeks', :'NOW'::timestamptz - interval '3 weeks' + interval '5 minutes', '{}'),\n(15, 1004, 10004, true, :'NOW'::timestamptz - interval '2 weeks', :'NOW'::timestamptz - interval '2 weeks' + interval '5 minutes', '{}'),\n(16, 1005, 10005, true, :'NOW'::timestamptz - interval '1 week', :'NOW'::timestamptz - interval '1 week' + interval '5 minutes', '{}');\n-- Returns id before the gap\nSELECT _timescaledb_functions.job_history_bsearch(:'NOW'::timestamptz - interval '3 weeks 3 days') AS result_gap_trigger1;\n result_gap_trigger1 \n---------------------\n                  13\n\n-- Returns id after the gap\nSELECT _timescaledb_functions.job_history_bsearch(:'NOW'::timestamptz - interval '2 weeks 3 days') AS result_gap_trigger2;\n result_gap_trigger2 \n---------------------\n                  15\n\n-- Returns second element\nSELECT _timescaledb_functions.job_history_bsearch(:'NOW'::timestamptz - interval '4 weeks 3 days') AS result_gap_trigger3;\n result_gap_trigger3 \n---------------------\n                  11\n\n-- Final cleanup\nTRUNCATE _timescaledb_internal.bgw_job_stat_history;\n"
  },
  {
    "path": "tsl/test/expected/bgw_job_stat_history_errors.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\\set client_min_messages TO NOTICE;\ncreate or replace procedure job_fail(jobid int, config jsonb) language plpgsql as $$\nbegin\nperform pg_sleep(2);\nraise exception 'raising an exception';\nend\n$$;\n-- very simple case: job that raises an exception\nselect add_job('job_fail', '4 minutes') as jobf_id \\gset\n-- test jobs that try to update concurrently\nCREATE TABLE custom_log (\n    a int,\n    b int,\n    msg text\n);\ninsert into custom_log values (0, 0, 'msg0');\nALTER SYSTEM SET DEFAULT_TRANSACTION_ISOLATION TO 'serializable';\nSELECT pg_reload_conf();\n pg_reload_conf \n----------------\n t\n\n-- Reconnect to make sure the GUC is set\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- test a concurrent update\nCREATE OR REPLACE PROCEDURE custom_proc1(jobid int, config jsonb) LANGUAGE PLPGSQL AS\n$$\nBEGIN\n  UPDATE custom_log set msg = 'msg1' where msg = 'msg0';\n  perform pg_sleep(5);\n  COMMIT;\nEND\n$$;\nCREATE OR REPLACE PROCEDURE custom_proc2(jobid int, config jsonb) LANGUAGE PLPGSQL AS\n$$\nBEGIN\n  UPDATE custom_log set msg = 'msg2' where msg = 'msg0';\n  COMMIT;\nEND\n$$;\nselect add_job('custom_proc1', '2 min', initial_start => now());\n add_job \n---------\n    1001\n\n-- to make sure custom_log is first updated by custom_proc_1\nselect add_job('custom_proc2', '2 min', initial_start => now() + interval '2 seconds');\n add_job \n---------\n    1002\n\nSELECT _timescaledb_functions.start_background_workers();\n start_background_workers \n--------------------------\n t\n\n-- enough time to for job_fail to fail\nselect pg_sleep(5);\n pg_sleep \n----------\n \n\nselect job_id, data->'job'->>'proc_name' as proc_name, data->'error_data'->>'message' as err_message, data->'error_data'->>'sqlerrcode' as sqlerrcode\nfrom _timescaledb_internal.bgw_job_stat_history where job_id = :jobf_id and succeeded is false;\n job_id | proc_name |     err_message      | sqlerrcode \n--------+-----------+----------------------+------------\n   1000 | job_fail  | raising an exception | P0001\n\nselect delete_job(:jobf_id);\n delete_job \n------------\n \n\nselect pg_sleep(5);\n pg_sleep \n----------\n \n\n-- exclude internal jobs\nselect job_id, data->'job'->>'proc_name' as proc_name, data->'error_data'->>'message' as err_message, data->'error_data'->>'sqlerrcode' as sqlerrcode\nfrom _timescaledb_internal.bgw_job_stat_history WHERE job_id >= 1000 and succeeded is false;\n job_id |  proc_name   |                     err_message                     | sqlerrcode \n--------+--------------+-----------------------------------------------------+------------\n   1000 | job_fail     | raising an exception                                | P0001\n   1002 | custom_proc2 | could not serialize access due to concurrent update | 40001\n\nALTER SYSTEM RESET DEFAULT_TRANSACTION_ISOLATION;\nSELECT pg_reload_conf();\n pg_reload_conf \n----------------\n t\n\n-- Reconnect to make sure the GUC is set\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- test the retention job\nSELECT next_start FROM alter_job(3, next_start => '2060-01-01 00:00:00+00'::timestamptz);\n          next_start          \n------------------------------\n Wed Dec 31 16:00:00 2059 PST\n\nDELETE FROM _timescaledb_internal.bgw_job_stat_history;\nINSERT INTO _timescaledb_internal.bgw_job_stat_history(job_id, pid, succeeded, execution_start, execution_finish, data)\nVALUES (123, 12345, false, '2000-01-01 00:00:00+00'::timestamptz, '2000-01-01 00:00:10+00'::timestamptz, '{}'),\n(456, 45678, false, '2000-01-01 00:00:20+00'::timestamptz, '2000-01-01 00:00:40+00'::timestamptz, '{}'),\n-- not older than a month\n(123, 23456, false, '2050-01-01 00:00:00+00'::timestamptz, '2050-01-01 00:00:10+00'::timestamptz, '{}');\n-- 3 rows in the table before policy runs\nSELECT job_id, pid, succeeded, execution_start, execution_finish, data\nFROM _timescaledb_internal.bgw_job_stat_history\nWHERE succeeded IS FALSE;\n job_id |  pid  | succeeded |       execution_start        |       execution_finish       | data \n--------+-------+-----------+------------------------------+------------------------------+------\n    123 | 12345 | f         | Fri Dec 31 16:00:00 1999 PST | Fri Dec 31 16:00:10 1999 PST | {}\n    456 | 45678 | f         | Fri Dec 31 16:00:20 1999 PST | Fri Dec 31 16:00:40 1999 PST | {}\n    123 | 23456 | f         | Fri Dec 31 16:00:00 2049 PST | Fri Dec 31 16:00:10 2049 PST | {}\n\n-- drop all job_stats for the retention job\nDELETE FROM _timescaledb_internal.bgw_job_stat WHERE job_id = 3;\nSELECT FROM alter_job(3, next_start => now());\n--\n\nSELECT _timescaledb_functions.restart_background_workers();\n restart_background_workers \n----------------------------\n t\n\nSELECT test.wait_for_job_to_run(3, 1);\n wait_for_job_to_run \n---------------------\n t\n\n-- only the last row remains\nSELECT job_id, pid, succeeded, execution_start, execution_finish, data\nFROM _timescaledb_internal.bgw_job_stat_history\nWHERE succeeded IS FALSE;\n job_id |  pid  | succeeded |       execution_start        |       execution_finish       | data \n--------+-------+-----------+------------------------------+------------------------------+------\n    123 | 23456 | f         | Fri Dec 31 16:00:00 2049 PST | Fri Dec 31 16:00:10 2049 PST | {}\n\n-- test failure when starting jobs\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT _timescaledb_functions.stop_background_workers();\n stop_background_workers \n-------------------------\n t\n\n-- Job didn't finish yet and Crash detected\nDELETE FROM _timescaledb_internal.bgw_job_stat_history;\nINSERT INTO _timescaledb_internal.bgw_job_stat_history(job_id, pid, succeeded, execution_start, execution_finish, data)\nVALUES (1, NULL, NULL, '2000-01-01 00:00:00+00'::timestamptz, NULL, '{}'), -- Crash server detected\n(2, 2222, false, '2000-01-01 00:00:00+00'::timestamptz, NULL, '{}'), -- Didn't finished yet\n(3, 3333, false, '2000-01-01 00:00:00+00'::timestamptz, '2000-01-01 01:00:00+00'::timestamptz, '{}'), -- Finish with ERROR\n(4, 4444, true, '2000-01-01 00:00:00+00'::timestamptz, '2000-01-01 01:00:00+00'::timestamptz, '{}'); -- Finish with SUCCESS\nSELECT job_id, pid, succeeded, start_time, finish_time, config, err_message\nFROM timescaledb_information.job_history\nORDER BY job_id;\n job_id | pid  | succeeded |          start_time          |         finish_time          | config |             err_message             \n--------+------+-----------+------------------------------+------------------------------+--------+-------------------------------------\n      1 |      |           | Fri Dec 31 16:00:00 1999 PST |                              |        | job crash detected, see server logs\n      2 | 2222 | f         | Fri Dec 31 16:00:00 1999 PST |                              |        | \n      3 | 3333 | f         | Fri Dec 31 16:00:00 1999 PST | Fri Dec 31 17:00:00 1999 PST |        | \n      4 | 4444 | t         | Fri Dec 31 16:00:00 1999 PST | Fri Dec 31 17:00:00 1999 PST |        | \n\nSELECT job_id, pid, start_time, finish_time, err_message\nFROM timescaledb_information.job_errors\nORDER BY job_id;\n job_id | pid  |          start_time          |         finish_time          |             err_message             \n--------+------+------------------------------+------------------------------+-------------------------------------\n      1 |      | Fri Dec 31 16:00:00 1999 PST |                              | job crash detected, see server logs\n      2 | 2222 | Fri Dec 31 16:00:00 1999 PST |                              | \n      3 | 3333 | Fri Dec 31 16:00:00 1999 PST | Fri Dec 31 17:00:00 1999 PST | \n\nDELETE FROM _timescaledb_internal.bgw_job_stat;\nDELETE FROM _timescaledb_internal.bgw_job_stat_history;\nDELETE FROM _timescaledb_catalog.bgw_job CASCADE;\nSELECT _timescaledb_functions.start_background_workers();\n start_background_workers \n--------------------------\n t\n\n\\set VERBOSITY default\n-- Setup Jobs\nDO\n$TEST$\nDECLARE\n  stmt TEXT;\n  njobs INT := 30;\nBEGIN\n  RAISE INFO 'Creating % jobs', njobs;\n  FOR stmt IN\n    SELECT format('CREATE PROCEDURE custom_job%s(job_id int, config jsonb) LANGUAGE PLPGSQL AS $$ BEGIN PERFORM pg_sleep(1); END; $$', i) FROM generate_series(1, njobs) AS i\n  LOOP\n    EXECUTE stmt;\n  END LOOP;\n\n  RAISE INFO 'Scheduling % jobs', njobs;\n  PERFORM add_job(format('custom_job%s', i)::regproc, schedule_interval => interval '1 hour', initial_start := now())\n  FROM generate_series(1, njobs) AS i;\nEND;\n$TEST$;\nINFO:  Creating 30 jobs\nINFO:  Scheduling 30 jobs\nSELECT _timescaledb_functions.restart_background_workers();\n restart_background_workers \n----------------------------\n t\n\n-- Wait for jobs to run\nDO\n$TEST$\nDECLARE\n  njobs INT := 30;\nBEGIN\n  RAISE INFO 'Waiting for the % jobs to run', njobs;\n  SET LOCAL client_min_messages TO WARNING;\n  PERFORM test.wait_for_job_to_run_or_fail(id) FROM _timescaledb_catalog.bgw_job WHERE id >= 1000;\nEND;\n$TEST$;\nINFO:  Waiting for the 30 jobs to run\nSELECT count(*) > 0 FROM timescaledb_information.job_history WHERE succeeded IS FALSE AND err_message ~ 'failed to start job';\n ?column? \n----------\n t\n\nSELECT count(*) > 0 FROM timescaledb_information.job_errors WHERE err_message ~ 'failed to start job';\n ?column? \n----------\n t\n\n\\set VERBOSITY terse\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT _timescaledb_functions.stop_background_workers();\n stop_background_workers \n-------------------------\n t\n\n"
  },
  {
    "path": "tsl/test/expected/bgw_job_stat_history_errors_permissions.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- Table to update concurrently to generate error message\nCREATE TABLE my_table (a int, b int);\nINSERT INTO my_table VALUES (0, 0);\nGRANT ALL ON my_table TO PUBLIC;\nALTER SYSTEM SET DEFAULT_TRANSACTION_ISOLATION TO 'serializable';\nSELECT pg_reload_conf();\n pg_reload_conf \n----------------\n t\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nCREATE OR REPLACE PROCEDURE job_fail(jobid int, config jsonb)\nAS $$\nBEGIN\n    RAISE EXCEPTION 'raising an exception';\nEND\n$$ LANGUAGE plpgsql;\nSELECT add_job('job_fail', '4 minutes', initial_start => now()) as job_fail_id \\gset\nCREATE OR REPLACE PROCEDURE custom_proc1(jobid int, config jsonb) LANGUAGE PLPGSQL AS\n$$\nBEGIN\n  UPDATE my_table SET b = 1 WHERE a = 0;\n  PERFORM pg_sleep(5);\n  COMMIT;\nEND\n$$;\nSELECT add_job('custom_proc1', '2 min', initial_start => now()) as custom_proc1_id \\gset\nSET ROLE :ROLE_DEFAULT_PERM_USER_2;\nCREATE OR REPLACE PROCEDURE custom_proc2(jobid int, config jsonb) LANGUAGE PLPGSQL AS\n$$\nBEGIN\n  UPDATE my_table SET b = 2 WHERE a = 0;\n  PERFORM pg_sleep(5);\n  COMMIT;\nEND\n$$;\n-- to make sure custom_log is first updated by custom_proc_1\nselect add_job('custom_proc2', '2 min', initial_start => now() + interval '1 seconds') as custom_proc2_id \\gset\nSET ROLE :ROLE_SUPERUSER;\nSELECT _timescaledb_functions.start_background_workers();\n start_background_workers \n--------------------------\n t\n\nSELECT pg_sleep(6);\n pg_sleep \n----------\n \n\n\\d timescaledb_information.job_errors\n                View \"timescaledb_information.job_errors\"\n   Column    |           Type           | Collation | Nullable | Default \n-------------+--------------------------+-----------+----------+---------\n job_id      | integer                  |           |          | \n proc_schema | text                     |           |          | \n proc_name   | text                     |           |          | \n pid         | integer                  |           |          | \n start_time  | timestamp with time zone |           |          | \n finish_time | timestamp with time zone |           |          | \n sqlerrcode  | text                     |           |          | \n err_message | text                     |           |          | \n\n-- We add a few entries without a matching job id, so that we get a\n-- null owner. Note that the second entry does not have a message\n-- defined, so it will print a standardized message assuming that the\n-- job crashed.\n\\set start '2000-01-01 00:00:00+00'\n\\set finish '2000-01-01 00:00:10+00'\nINSERT INTO _timescaledb_internal.bgw_job_stat_history(job_id, pid, succeeded, execution_start, execution_finish, data) VALUES\n       (11111, 12345, false, :'start'::timestamptz, :'finish'::timestamptz, '{\"error_data\": {\"message\": \"not an error\"}}'),\n       (22222, 45678, false, :'start'::timestamptz, NULL, '{}'), -- Started and didn't finished yet\n       (33333, NULL, NULL, :'start'::timestamptz, NULL, NULL); -- Crash detected cause not assigned an PID\n-- We check the log as different users and should only see what we\n-- have permissions to see. We only bother about jobs at 1000 or\n-- larger since the standard jobs are flaky.\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT job_id, proc_schema, proc_name, sqlerrcode, err_message\nFROM timescaledb_information.job_errors WHERE job_id >= 1000\nORDER BY job_id;\n job_id | proc_schema |  proc_name   | sqlerrcode |                     err_message                     \n--------+-------------+--------------+------------+-----------------------------------------------------\n   1000 | public      | job_fail     | P0001      | raising an exception\n   1002 | public      | custom_proc2 | 40001      | could not serialize access due to concurrent update\n  11111 |             |              |            | not an error\n  22222 |             |              |            | \n\nSET ROLE :ROLE_DEFAULT_PERM_USER_2;\nSELECT job_id, proc_schema, proc_name, sqlerrcode, err_message\nFROM timescaledb_information.job_errors WHERE job_id >= 1000\nORDER BY job_id;\n job_id | proc_schema |  proc_name   | sqlerrcode |                     err_message                     \n--------+-------------+--------------+------------+-----------------------------------------------------\n   1000 | public      | job_fail     | P0001      | raising an exception\n   1002 | public      | custom_proc2 | 40001      | could not serialize access due to concurrent update\n  11111 |             |              |            | not an error\n  22222 |             |              |            | \n\nSET ROLE :ROLE_SUPERUSER;\nSELECT job_id, proc_schema, proc_name, sqlerrcode, err_message\nFROM timescaledb_information.job_errors WHERE job_id >= 1000\nORDER BY job_id;\n job_id | proc_schema |  proc_name   | sqlerrcode |                     err_message                     \n--------+-------------+--------------+------------+-----------------------------------------------------\n   1000 | public      | job_fail     | P0001      | raising an exception\n   1002 | public      | custom_proc2 | 40001      | could not serialize access due to concurrent update\n  11111 |             |              |            | not an error\n  22222 |             |              |            | \n  33333 |             |              |            | job crash detected, see server logs\n\nSELECT delete_job(:custom_proc2_id);\n delete_job \n------------\n \n\nSELECT delete_job(:custom_proc1_id);\n delete_job \n------------\n \n\nSELECT delete_job(:job_fail_id);\n delete_job \n------------\n \n\nALTER SYSTEM RESET DEFAULT_TRANSACTION_ISOLATION;\nSELECT pg_reload_conf();\n pg_reload_conf \n----------------\n t\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT _timescaledb_functions.stop_background_workers();\n stop_background_workers \n-------------------------\n t\n\n"
  },
  {
    "path": "tsl/test/expected/bgw_policy.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\nCREATE OR REPLACE FUNCTION reorder_called(chunk_id INT) RETURNS BOOL AS $$\n  SELECT indisclustered\n  FROM _timescaledb_catalog.chunk ch\n  JOIN pg_index i on indrelid = format('%I.%I',ch.schema_name,ch.table_name)::regclass\n  WHERE ch.id = chunk_id;\n$$ LANGUAGE SQL;\nCREATE TABLE test_table(time timestamptz, chunk_id int);\nSELECT create_hypertable('test_table', 'time');\n    create_hypertable    \n-------------------------\n (1,public,test_table,t)\n\n-- These inserts should create 5 different chunks\nINSERT INTO test_table VALUES (now() - INTERVAL '3 weeks', 1);\nINSERT INTO test_table VALUES (now(), 2);\nINSERT INTO test_table VALUES (now() - INTERVAL '5 months', 3);\nINSERT INTO test_table VALUES (now() - INTERVAL '3 months', 4);\nINSERT INTO test_table VALUES (now() - INTERVAL '8 months', 5);\nSELECT COUNT(*) FROM _timescaledb_catalog.chunk as c, _timescaledb_catalog.hypertable as ht where c.hypertable_id = ht.id and ht.table_name='test_table';\n count \n-------\n     5\n\n-- Make sure reorder correctly selects chunks to reorder\n-- by starting with oldest chunks\nselect add_reorder_policy('test_table', 'test_table_time_idx') as reorder_job_id \\gset\nselect * from _timescaledb_catalog.bgw_job WHERE id >= 1000 ORDER BY id;\n  id  |   application_name    | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema       |   proc_name    |       owner       | scheduled | fixed_schedule | initial_start | hypertable_id |                          config                           |      check_schema      |      check_name      | timezone \n------+-----------------------+-------------------+-------------+-------------+--------------+------------------------+----------------+-------------------+-----------+----------------+---------------+---------------+-----------------------------------------------------------+------------------------+----------------------+----------\n 1000 | Reorder Policy [1000] | @ 84 hours        | @ 0         |          -1 | @ 5 mins     | _timescaledb_functions | policy_reorder | default_perm_user | t         | f              |               |             1 | {\"index_name\": \"test_table_time_idx\", \"hypertable_id\": 1} | _timescaledb_functions | policy_reorder_check | \n\nselect job_id, chunk_id, num_times_job_run from _timescaledb_internal.bgw_policy_chunk_stats;\n job_id | chunk_id | num_times_job_run \n--------+----------+-------------------\n\n-- Make a manual calls to reorder: make sure the correct chunk is called\n-- Chunk 5 should be first\nCALL run_job(:reorder_job_id);\nselect job_id, chunk_id, num_times_job_run from _timescaledb_internal.bgw_policy_chunk_stats;\n job_id | chunk_id | num_times_job_run \n--------+----------+-------------------\n   1000 |        5 |                 1\n\n-- Confirm that reorder was called on the correct chunk Oid\nSELECT reorder_called(5);\n reorder_called \n----------------\n t\n\n-- Chunk 3 is next\nCALL run_job(:reorder_job_id);\nselect job_id, chunk_id, num_times_job_run from _timescaledb_internal.bgw_policy_chunk_stats;\n job_id | chunk_id | num_times_job_run \n--------+----------+-------------------\n   1000 |        5 |                 1\n   1000 |        3 |                 1\n\nSELECT reorder_called(3);\n reorder_called \n----------------\n t\n\n-- Chunk 4 is next\nCALL run_job(:reorder_job_id);\nselect job_id, chunk_id, num_times_job_run from _timescaledb_internal.bgw_policy_chunk_stats;\n job_id | chunk_id | num_times_job_run \n--------+----------+-------------------\n   1000 |        5 |                 1\n   1000 |        3 |                 1\n   1000 |        4 |                 1\n\nSELECT reorder_called(4);\n reorder_called \n----------------\n t\n\n-- The following calls should not reorder any chunk, because they're all too new\nCALL run_job(:reorder_job_id);\nNOTICE:  no chunks need reordering for hypertable public.test_table\nselect job_id, chunk_id, num_times_job_run from _timescaledb_internal.bgw_policy_chunk_stats;\n job_id | chunk_id | num_times_job_run \n--------+----------+-------------------\n   1000 |        5 |                 1\n   1000 |        3 |                 1\n   1000 |        4 |                 1\n\nCALL run_job(:reorder_job_id);\nNOTICE:  no chunks need reordering for hypertable public.test_table\nselect job_id, chunk_id, num_times_job_run from _timescaledb_internal.bgw_policy_chunk_stats;\n job_id | chunk_id | num_times_job_run \n--------+----------+-------------------\n   1000 |        5 |                 1\n   1000 |        3 |                 1\n   1000 |        4 |                 1\n\nINSERT INTO test_table VALUES (now() - INTERVAL '7 days', 6);\n-- This call should reorder chunk 1\nCALL run_job(:reorder_job_id);\nselect job_id, chunk_id, num_times_job_run from _timescaledb_internal.bgw_policy_chunk_stats;\n job_id | chunk_id | num_times_job_run \n--------+----------+-------------------\n   1000 |        5 |                 1\n   1000 |        3 |                 1\n   1000 |        4 |                 1\n   1000 |        1 |                 1\n\nSELECT reorder_called(1);\n reorder_called \n----------------\n t\n\n-- Should not reorder anything, because all chunks are too new\nCALL run_job(:reorder_job_id);\nNOTICE:  no chunks need reordering for hypertable public.test_table\nselect job_id, chunk_id, num_times_job_run from _timescaledb_internal.bgw_policy_chunk_stats;\n job_id | chunk_id | num_times_job_run \n--------+----------+-------------------\n   1000 |        5 |                 1\n   1000 |        3 |                 1\n   1000 |        4 |                 1\n   1000 |        1 |                 1\n\nselect remove_reorder_policy('test_table');\n remove_reorder_policy \n-----------------------\n \n\n-- Now do drop_chunks test\nselect add_retention_policy('test_table', INTERVAL '4 months', true) as drop_chunks_job_id \\gset\nSELECT count(*) FROM _timescaledb_catalog.chunk as c, _timescaledb_catalog.hypertable as ht where c.hypertable_id = ht.id and ht.table_name='test_table';\n count \n-------\n     6\n\n-- Now simulate drop_chunks running automatically by calling it explicitly\nCALL run_job(:drop_chunks_job_id);\n-- Should have 4 chunks left\nSELECT count(*) FROM _timescaledb_catalog.chunk as c, _timescaledb_catalog.hypertable as ht where c.hypertable_id = ht.id and ht.table_name='test_table' \\gset before_\nselect :before_count=4;\n ?column? \n----------\n t\n\n-- Make sure this second call does nothing\nCALL run_job(:drop_chunks_job_id);\nSELECT count(*) FROM _timescaledb_catalog.chunk as c, _timescaledb_catalog.hypertable as ht where c.hypertable_id = ht.id and ht.table_name='test_table' \\gset after_\n-- Should be true\nselect :before_count=:after_count;\n ?column? \n----------\n t\n\nINSERT INTO test_table VALUES (now() - INTERVAL '2 weeks', 1);\nSELECT count(*) FROM _timescaledb_catalog.chunk as c, _timescaledb_catalog.hypertable as ht where c.hypertable_id = ht.id and ht.table_name='test_table' \\gset before_\n-- This call should also do nothing\nCALL run_job(:drop_chunks_job_id);\nSELECT count(*) FROM _timescaledb_catalog.chunk as c, _timescaledb_catalog.hypertable as ht where c.hypertable_id = ht.id and ht.table_name='test_table' \\gset after_\n-- Should be true\nselect :before_count=:after_count;\n ?column? \n----------\n t\n\nselect remove_retention_policy('test_table');\n remove_retention_policy \n-------------------------\n \n\n-- Now test reorder chunk selection when there is space partitioning\nTRUNCATE test_table;\nSELECT add_dimension('public.test_table', 'chunk_id', 2);\n          add_dimension           \n----------------------------------\n (2,public,test_table,chunk_id,t)\n\nINSERT INTO test_table VALUES (now() - INTERVAL '3 weeks', 1);\nINSERT INTO test_table VALUES (now(), 2);\nINSERT INTO test_table VALUES (now() - INTERVAL '5 months', 3);\nINSERT INTO test_table VALUES (now() - INTERVAL '3 months', 4);\nINSERT INTO test_table VALUES (now() - INTERVAL '3 months', -4);\nINSERT INTO test_table VALUES (now() - INTERVAL '8 months', 5);\nINSERT INTO test_table VALUES (now() - INTERVAL '8 months', -5);\nselect add_reorder_policy('test_table', 'test_table_time_idx') as reorder_job_id \\gset\n-- Should be nothing in the chunk_stats table\nselect count(*) from _timescaledb_internal.bgw_policy_chunk_stats where job_id=:reorder_job_id;\n count \n-------\n     0\n\n-- Make a manual calls to reorder: make sure the correct (oldest) chunk is called\nselect chunk_id from _timescaledb_catalog.dimension_slice as ds, _timescaledb_catalog.chunk_constraint as cc where ds.dimension_id=1 and ds.id=cc.dimension_slice_id ORDER BY ds.range_start LIMIT 1 \\gset oldest_\nCALL run_job(:reorder_job_id);\nselect job_id, chunk_id, num_times_job_run from _timescaledb_internal.bgw_policy_chunk_stats where job_id=:reorder_job_id and chunk_id=:oldest_chunk_id;\n job_id | chunk_id | num_times_job_run \n--------+----------+-------------------\n   1002 |       13 |                 1\n\n-- Confirm that reorder was called on the correct chunk Oid\nSELECT reorder_called(:oldest_chunk_id);\n reorder_called \n----------------\n t\n\n-- Now run reorder again and pick the next oldest chunk\nselect cc.chunk_id from _timescaledb_catalog.dimension_slice as ds, _timescaledb_catalog.chunk_constraint as cc where ds.dimension_id=1 and ds.id=cc.dimension_slice_id and cc.chunk_id NOT IN (select chunk_id from _timescaledb_internal.bgw_policy_chunk_stats) ORDER BY ds.range_start LIMIT 1 \\gset oldest_\nCALL run_job(:reorder_job_id);\nselect job_id, chunk_id, num_times_job_run from _timescaledb_internal.bgw_policy_chunk_stats where job_id=:reorder_job_id and chunk_id=:oldest_chunk_id;\n job_id | chunk_id | num_times_job_run \n--------+----------+-------------------\n   1002 |       14 |                 1\n\n-- Confirm that reorder was called on the correct chunk Oid\nSELECT reorder_called(:oldest_chunk_id);\n reorder_called \n----------------\n t\n\n-- Again\nselect cc.chunk_id from _timescaledb_catalog.dimension_slice as ds, _timescaledb_catalog.chunk_constraint as cc where ds.dimension_id=1 and ds.id=cc.dimension_slice_id and cc.chunk_id NOT IN (select chunk_id from _timescaledb_internal.bgw_policy_chunk_stats) ORDER BY ds.range_start LIMIT 1 \\gset oldest_\nCALL run_job(:reorder_job_id);\nselect job_id, chunk_id, num_times_job_run from _timescaledb_internal.bgw_policy_chunk_stats where job_id=:reorder_job_id and chunk_id=:oldest_chunk_id;\n job_id | chunk_id | num_times_job_run \n--------+----------+-------------------\n   1002 |       10 |                 1\n\nSELECT reorder_called(:oldest_chunk_id);\n reorder_called \n----------------\n t\n\n-- Again\nselect cc.chunk_id from _timescaledb_catalog.dimension_slice as ds, _timescaledb_catalog.chunk_constraint as cc where ds.dimension_id=1 and ds.id=cc.dimension_slice_id and cc.chunk_id NOT IN (select chunk_id from _timescaledb_internal.bgw_policy_chunk_stats) ORDER BY ds.range_start LIMIT 1 \\gset oldest_\nCALL run_job(:reorder_job_id);\nselect job_id, chunk_id, num_times_job_run from _timescaledb_internal.bgw_policy_chunk_stats where job_id=:reorder_job_id and chunk_id=:oldest_chunk_id;\n job_id | chunk_id | num_times_job_run \n--------+----------+-------------------\n   1002 |       11 |                 1\n\nSELECT reorder_called(:oldest_chunk_id);\n reorder_called \n----------------\n t\n\n-- Again\nselect cc.chunk_id from _timescaledb_catalog.dimension_slice as ds, _timescaledb_catalog.chunk_constraint as cc where ds.dimension_id=1 and ds.id=cc.dimension_slice_id and cc.chunk_id NOT IN (select chunk_id from _timescaledb_internal.bgw_policy_chunk_stats) ORDER BY ds.range_start LIMIT 1 \\gset oldest_\nCALL run_job(:reorder_job_id);\nselect job_id, chunk_id, num_times_job_run from _timescaledb_internal.bgw_policy_chunk_stats where job_id=:reorder_job_id and chunk_id=:oldest_chunk_id;\n job_id | chunk_id | num_times_job_run \n--------+----------+-------------------\n   1002 |       12 |                 1\n\nSELECT reorder_called(:oldest_chunk_id);\n reorder_called \n----------------\n t\n\n-- Ran out of chunks, so should be a noop\nCALL run_job(:reorder_job_id);\nNOTICE:  no chunks need reordering for hypertable public.test_table\n-- Corner case: when there are no recent-enough chunks to reorder,\n-- DO NOT reorder any new chunks created by space partitioning.\n-- We only want to reorder when new dimension_slices on time are created.\nINSERT INTO test_table VALUES (now() - INTERVAL '5 months', -5);\nINSERT INTO test_table VALUES (now() - INTERVAL '3 weeks', -5);\nINSERT INTO test_table VALUES (now(), -25);\n-- Should be noop\nCALL run_job(:reorder_job_id);\nNOTICE:  no chunks need reordering for hypertable public.test_table\n-- But if we create a new time dimension, reorder it\nINSERT INTO test_table VALUES (now() - INTERVAL '1 year', 1);\nselect cc.chunk_id from _timescaledb_catalog.dimension_slice as ds, _timescaledb_catalog.chunk_constraint as cc where ds.dimension_id=1 and ds.id=cc.dimension_slice_id and cc.chunk_id NOT IN (select chunk_id from _timescaledb_internal.bgw_policy_chunk_stats) ORDER BY ds.range_start LIMIT 1 \\gset oldest_\nCALL run_job(:reorder_job_id);\nselect job_id, chunk_id, num_times_job_run from _timescaledb_internal.bgw_policy_chunk_stats where job_id=:reorder_job_id and chunk_id=:oldest_chunk_id;\n job_id | chunk_id | num_times_job_run \n--------+----------+-------------------\n   1002 |       16 |                 1\n\nSELECT reorder_called(:oldest_chunk_id);\n reorder_called \n----------------\n t\n\n-- Should be noop again\nCALL run_job(:reorder_job_id);\nNOTICE:  no chunks need reordering for hypertable public.test_table\nCREATE TABLE test_table_int(time bigint, junk int);\nCREATE TABLE test_overflow_smallint(time smallint, junk int);\nSELECT create_hypertable('test_table_int', 'time', chunk_time_interval => 1);\n      create_hypertable      \n-----------------------------\n (2,public,test_table_int,t)\n\nSELECT create_hypertable('test_overflow_smallint', 'time', chunk_time_interval => 1);\n          create_hypertable          \n-------------------------------------\n (3,public,test_overflow_smallint,t)\n\ncreate or replace function dummy_now() returns BIGINT LANGUAGE SQL IMMUTABLE as  'SELECT 1::BIGINT';\ncreate or replace function dummy_now2() returns BIGINT LANGUAGE SQL IMMUTABLE as  'SELECT 2::BIGINT';\ncreate or replace function overflow_now() returns SMALLINT LANGUAGE SQL IMMUTABLE as  'SELECT 32767::SMALLINT';\nCREATE TABLE test_table_perm(time timestamp PRIMARY KEY);\nSELECT create_hypertable('test_table_perm', 'time', chunk_time_interval => 1);\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\nWARNING:  unexpected interval: smaller than one second\n      create_hypertable       \n------------------------------\n (4,public,test_table_perm,t)\n\n\\set ON_ERROR_STOP 0\n-- we cannot add a drop_chunks policy on a table whose open dimension is not time and no now_func is set\nselect add_retention_policy('test_table_int', INTERVAL '4 months', true);\nERROR:  invalid value for parameter drop_after\n\\set ON_ERROR_STOP 1\nINSERT INTO test_table_int VALUES (-2, -2), (-1, -1), (0,0), (1, 1), (2, 2), (3, 3);\n\\c :TEST_DBNAME :ROLE_SUPERUSER;\nCREATE USER unprivileged;\n\\c :TEST_DBNAME unprivileged\n-- should fail as the user has no permissions on the table\n\\set ON_ERROR_STOP 0\nselect set_integer_now_func('test_table_int', 'dummy_now');\nERROR:  must be owner of hypertable \"test_table_int\"\n\\set ON_ERROR_STOP 1\n\\c :TEST_DBNAME :ROLE_SUPERUSER;\nDROP USER unprivileged;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nselect set_integer_now_func('test_table_int', 'dummy_now');\n set_integer_now_func \n----------------------\n \n\nselect * from test_table_int;\n time | junk \n------+------\n   -2 |   -2\n   -1 |   -1\n    0 |    0\n    1 |    1\n    2 |    2\n    3 |    3\n\nSELECT count(*) FROM _timescaledb_catalog.chunk as c, _timescaledb_catalog.hypertable as ht where c.hypertable_id = ht.id and ht.table_name='test_table_int';\n count \n-------\n     6\n\nselect add_retention_policy('test_table_int', 1, true) as drop_chunks_job_id \\gset\n-- Now simulate drop_chunks running automatically by calling it explicitly\nCALL run_job(:drop_chunks_job_id);\nselect * from test_table_int;\n time | junk \n------+------\n    0 |    0\n    1 |    1\n    2 |    2\n    3 |    3\n\n-- Should have 4 chunks left\nSELECT count(*) FROM _timescaledb_catalog.chunk as c, _timescaledb_catalog.hypertable as ht where c.hypertable_id = ht.id and ht.table_name='test_table_int' \\gset before_\nselect :before_count=4;\n ?column? \n----------\n t\n\n-- Make sure this second call does nothing\nCALL run_job(:drop_chunks_job_id);\nSELECT count(*) FROM _timescaledb_catalog.chunk as c, _timescaledb_catalog.hypertable as ht where c.hypertable_id = ht.id and ht.table_name='test_table_int' \\gset after_\n-- Should be true\nselect :before_count=:after_count;\n ?column? \n----------\n t\n\nINSERT INTO test_table_int VALUES (42, 42);\nSELECT count(*) FROM _timescaledb_catalog.chunk as c, _timescaledb_catalog.hypertable as ht where c.hypertable_id = ht.id and ht.table_name='test_table_int' \\gset before_\n-- This call should also do nothing\nCALL run_job(:drop_chunks_job_id);\nSELECT count(*) FROM _timescaledb_catalog.chunk as c, _timescaledb_catalog.hypertable as ht where c.hypertable_id = ht.id and ht.table_name='test_table_int' \\gset after_\n-- Should be true\nselect :before_count=:after_count;\n ?column? \n----------\n t\n\nINSERT INTO test_table_int VALUES (-1, -1);\nSELECT count(*) FROM _timescaledb_catalog.chunk as c, _timescaledb_catalog.hypertable as ht where c.hypertable_id = ht.id and ht.table_name='test_table_int' \\gset add_one_\nselect :before_count+1=:add_one_count;\n ?column? \n----------\n t\n\nCALL run_job(:drop_chunks_job_id);\nSELECT count(*) FROM _timescaledb_catalog.chunk as c, _timescaledb_catalog.hypertable as ht where c.hypertable_id = ht.id and ht.table_name='test_table_int' \\gset after_\n-- (-1,-1) was in droping range so it should be dropped by background job\nselect :before_count=:after_count;\n ?column? \n----------\n t\n\nselect set_integer_now_func('test_table_int', 'dummy_now2', replace_if_exists=>true);\n set_integer_now_func \n----------------------\n \n\nselect * from test_table_int;\n time | junk \n------+------\n    0 |    0\n    1 |    1\n    2 |    2\n    3 |    3\n   42 |   42\n\nCALL run_job(:drop_chunks_job_id);\n-- added one to now() so time entry with value 0 should be dropped now\nSELECT count(*) FROM _timescaledb_catalog.chunk as c, _timescaledb_catalog.hypertable as ht where c.hypertable_id = ht.id and ht.table_name='test_table_int' \\gset after_\nselect :before_count=:after_count+1;\n ?column? \n----------\n t\n\nselect * from test_table_int;\n time | junk \n------+------\n    1 |    1\n    2 |    2\n    3 |    3\n   42 |   42\n\n-- make the now() function invalid -- returns INT and not BIGINT\ndrop function dummy_now2();\ncreate or replace function dummy_now2() returns INT LANGUAGE SQL IMMUTABLE as  'SELECT 2::INT';\n\\set ON_ERROR_STOP 0\nCALL run_job(:drop_chunks_job_id);\nERROR:  invalid integer_now function\n\\set ON_ERROR_STOP 1\n-- test the expected use case of set_integer_now_func\n create function nowstamp() returns bigint language sql STABLE as 'SELECT extract(epoch from now())::BIGINT';\nselect set_integer_now_func('test_table_int', 'nowstamp', replace_if_exists=>true);\n set_integer_now_func \n----------------------\n \n\nCALL run_job(:drop_chunks_job_id);\nSELECT count(*) FROM _timescaledb_catalog.chunk as c, _timescaledb_catalog.hypertable as ht where c.hypertable_id = ht.id and ht.table_name='test_table_int' \\gset after_\nselect :after_count=0;\n ?column? \n----------\n t\n\n-- test the case when now()-interval overflows\nselect set_integer_now_func('test_overflow_smallint', 'overflow_now');\n set_integer_now_func \n----------------------\n \n\nselect add_retention_policy('test_overflow_smallint', -2) as drop_chunks_job_id \\gset\n\\set ON_ERROR_STOP 0\nCALL run_job(:drop_chunks_job_id);\nERROR:  integer time overflow\n\\set ON_ERROR_STOP 1\n-- test the case when partitioning function and now function are set.\ncreate table part_time_now_func(time float8, temp float8);\ncreate or replace function time_partfunc(unixtime float8)\n    returns bigint language plpgsql immutable as\n$body$\ndeclare\n    retval bigint;\nbegin\n\n    retval := unixtime::bigint;\n    raise notice 'time value for % is %', unixtime, retval;\n    return retval;\nend\n$body$;\ncreate or replace function dummy_now() returns bigint language sql immutable as  'select 2::bigint';\nselect create_hypertable('part_time_now_func', 'time', time_partitioning_func => 'time_partfunc', chunk_time_interval=>1);\n        create_hypertable        \n---------------------------------\n (5,public,part_time_now_func,t)\n\ninsert into part_time_now_func values\n(1.1, 23.4), (2.2, 22.3), (3.3, 42.3);\nNOTICE:  time value for 1.1 is 1\nNOTICE:  time value for 1.1 is 1\nNOTICE:  time value for 1.1 is 1\nNOTICE:  time value for 1.1 is 1\nNOTICE:  time value for 2.2 is 2\nNOTICE:  time value for 2.2 is 2\nNOTICE:  time value for 2.2 is 2\nNOTICE:  time value for 2.2 is 2\nNOTICE:  time value for 3.3 is 3\nNOTICE:  time value for 3.3 is 3\nNOTICE:  time value for 3.3 is 3\nNOTICE:  time value for 3.3 is 3\nselect * from part_time_now_func;\n time | temp \n------+------\n  1.1 | 23.4\n  2.2 | 22.3\n  3.3 | 42.3\n\nselect set_integer_now_func('part_time_now_func', 'dummy_now');\n set_integer_now_func \n----------------------\n \n\nselect add_retention_policy('part_time_now_func', 0) as drop_chunks_job_id \\gset\nCALL run_job(:drop_chunks_job_id);\nselect * from part_time_now_func;\n time | temp \n------+------\n  2.2 | 22.3\n  3.3 | 42.3\n\nselect remove_retention_policy('part_time_now_func');\n remove_retention_policy \n-------------------------\n \n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nalter function dummy_now() rename to dummy_now_renamed;\nalter schema public rename to new_public;\nselect * from  _timescaledb_catalog.dimension;\n id | hypertable_id | column_name |         column_type         | aligned | num_slices | partitioning_func_schema | partitioning_func  | interval_length | compress_interval_length | integer_now_func_schema | integer_now_func \n----+---------------+-------------+-----------------------------+---------+------------+--------------------------+--------------------+-----------------+--------------------------+-------------------------+------------------\n  1 |             1 | time        | timestamp with time zone    | t       |            |                          |                    |    604800000000 |                          |                         | \n  2 |             1 | chunk_id    | integer                     | f       |          2 | _timescaledb_functions   | get_partition_hash |                 |                          |                         | \n  5 |             4 | time        | timestamp without time zone | t       |            |                          |                    |               1 |                          |                         | \n  6 |             5 | time        | double precision            | t       |            | new_public               | time_partfunc      |               1 |                          | new_public              | dummy_now\n  3 |             2 | time        | bigint                      | t       |            |                          |                    |               1 |                          | new_public              | nowstamp\n  4 |             3 | time        | smallint                    | t       |            |                          |                    |               1 |                          | new_public              | overflow_now\n\nalter schema new_public rename to public;\n\\c  :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n-- test that the behavior is strict when providing NULL required arguments\ncreate table test_strict (time timestamptz not null, a int, b int);\nselect create_hypertable('test_strict', 'time');\n    create_hypertable     \n--------------------------\n (6,public,test_strict,t)\n\n\\set ON_ERROR_STOP 0\nselect add_reorder_policy('test_table_perm', 'test_table_perm_pkey');\nERROR:  must be owner of hypertable \"test_table_perm\"\nselect remove_reorder_policy('test_table');\nERROR:  must be owner of hypertable \"test_table\"\nselect add_retention_policy('test_table_perm', INTERVAL '4 months', true);\nERROR:  must be owner of hypertable \"test_table_perm\"\nselect remove_retention_policy('test_table');\nERROR:  must be owner of hypertable \"test_table\"\nselect add_retention_policy('test_strict', drop_after => NULL);\nERROR:  need to specify one of \"drop_after\" or \"drop_created_before\"\n\\set ON_ERROR_STOP 1\n-- Check the number of non-internal policies\nSELECT proc_name, count(*)\nFROM _timescaledb_catalog.bgw_job\nWHERE id >= 1000\nGROUP BY proc_name;\n    proc_name     | count \n------------------+-------\n policy_reorder   |     1\n policy_retention |     2\n\n-- test retention with null arguments\nselect add_retention_policy(NULL, NULL);\n add_retention_policy \n----------------------\n                     \n\nselect add_retention_policy(NULL, drop_after => interval '2 days');\n add_retention_policy \n----------------------\n                     \n\n-- this is an optional argument\nselect add_retention_policy('test_strict', drop_after => interval '2 days', if_not_exists => NULL);\n add_retention_policy \n----------------------\n                     \n\nselect add_retention_policy('test_strict', interval '2 days', schedule_interval => NULL);\n add_retention_policy \n----------------------\n                 1006\n\n-- test compression with null arguments\nalter table test_strict set (timescaledb.compress);\nselect add_compression_policy(NULL, compress_after => NULL);\n add_compression_policy \n------------------------\n                       \n\nselect add_compression_policy('test_strict', INTERVAL '2 weeks', if_not_exists => NULL);\n add_compression_policy \n------------------------\n                       \n\nselect add_compression_policy('test_strict', INTERVAL '2 weeks', schedule_interval => NULL);\n add_compression_policy \n------------------------\n                   1007\n\n-- test that we get the default schedule_interval if nothing is specified\ncreate table test_missing_schedint (time timestamptz not null, a int, b int);\nselect create_hypertable('test_missing_schedint', 'time', chunk_time_interval=> '31days'::interval);\n         create_hypertable          \n------------------------------------\n (8,public,test_missing_schedint,t)\n\n-- we expect shedule_interval to be 1 day\nselect add_retention_policy('test_missing_schedint', interval '2 weeks') as retenion_id_missing_schedint \\gset\n-- we expect schedule_interval to be chunk_time_interval/2 for timestamptz time\nalter table test_missing_schedint set (timescaledb.compress);\nselect add_compression_policy('test_missing_schedint', interval '60 days') as compression_id_missing_schedint \\gset\n-- we expect schedule_interval to be 1 day for int time\ncreate table test_missing_schedint_integer (time int not null, a int, b int);\n-- 10 days interval\nselect create_hypertable('test_missing_schedint_integer', 'time', chunk_time_interval => 864000000);\n              create_hypertable              \n---------------------------------------------\n (10,public,test_missing_schedint_integer,t)\n\nalter table test_missing_schedint_integer set (timescaledb.compress);\nselect add_compression_policy('test_missing_schedint_integer', BIGINT '600000') as compression_id_integer \\gset\nselect * from _timescaledb_catalog.bgw_job where id in (:retenion_id_missing_schedint, :compression_id_missing_schedint, :compression_id_integer);\n  id  |     application_name      | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema       |     proc_name      |        owner        | scheduled | fixed_schedule | initial_start | hypertable_id |                       config                        |      check_schema      |        check_name        | timezone \n------+---------------------------+-------------------+-------------+-------------+--------------+------------------------+--------------------+---------------------+-----------+----------------+---------------+---------------+-----------------------------------------------------+------------------------+--------------------------+----------\n 1008 | Retention Policy [1008]   | @ 1 day           | @ 5 mins    |          -1 | @ 5 mins     | _timescaledb_functions | policy_retention   | default_perm_user_2 | t         | f              |               |             8 | {\"drop_after\": \"@ 14 days\", \"hypertable_id\": 8}     | _timescaledb_functions | policy_retention_check   | \n 1009 | Columnstore Policy [1009] | @ 12 hours        | @ 0         |          -1 | @ 1 hour     | _timescaledb_functions | policy_compression | default_perm_user_2 | t         | f              |               |             8 | {\"hypertable_id\": 8, \"compress_after\": \"@ 60 days\"} | _timescaledb_functions | policy_compression_check | \n 1010 | Columnstore Policy [1010] | @ 1 day           | @ 0         |          -1 | @ 1 hour     | _timescaledb_functions | policy_compression | default_perm_user_2 | t         | f              |               |            10 | {\"hypertable_id\": 10, \"compress_after\": 600000}     | _timescaledb_functions | policy_compression_check | \n\n-- test policy check functions with NULL args\n\\set ON_ERROR_STOP 0\nselect add_compression_policy('test_strict', compress_after => NULL);\nERROR:  need to specify one of \"compress_after\" or \"compress_created_before\"\nSELECT _timescaledb_functions.policy_compression_check(NULL);\nERROR:  config must not be NULL\nSELECT _timescaledb_functions.policy_refresh_continuous_aggregate_check(NULL);\nERROR:  config must not be NULL\nSELECT _timescaledb_functions.policy_reorder_check(NULL);\nERROR:  config must not be NULL\nSELECT _timescaledb_functions.policy_retention_check(NULL);\nERROR:  config must not be NULL\n\\set ON_ERROR_STOP 1\n--TEST check if alias for bgw_job table works\nSELECT * from _timescaledb_config.bgw_job WHERE id = :retenion_id_missing_schedint ORDER BY id;\n  id  |    application_name     | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema       |    proc_name     |        owner        | scheduled | fixed_schedule | initial_start | hypertable_id |                     config                      |      check_schema      |       check_name       | timezone \n------+-------------------------+-------------------+-------------+-------------+--------------+------------------------+------------------+---------------------+-----------+----------------+---------------+---------------+-------------------------------------------------+------------------------+------------------------+----------\n 1008 | Retention Policy [1008] | @ 1 day           | @ 5 mins    |          -1 | @ 5 mins     | _timescaledb_functions | policy_retention | default_perm_user_2 | t         | f              |               |             8 | {\"drop_after\": \"@ 14 days\", \"hypertable_id\": 8} | _timescaledb_functions | policy_retention_check | \n\n"
  },
  {
    "path": "tsl/test/expected/bgw_reorder_drop_chunks.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n--\n-- Setup\n--\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(timeout INT = -1) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_db_scheduler_test_run(timeout INT = -1, mock_start_time INT = 0) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_db_scheduler_test_wait_for_scheduler_finish() RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_params_create() RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_params_destroy() RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_params_reset_time(set_time BIGINT = 0, wait BOOLEAN = false) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\n\\set WAIT_ON_JOB 0\n\\set IMMEDIATELY_SET_UNTIL 1\n\\set WAIT_FOR_OTHER_TO_ADVANCE 2\n-- Remove any default jobs, e.g., telemetry\nDELETE FROM _timescaledb_catalog.bgw_job;\nTRUNCATE _timescaledb_internal.bgw_job_stat;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nCREATE TABLE public.bgw_log(\n    msg_no INT,\n    mock_time BIGINT,\n    application_name TEXT,\n    msg TEXT\n);\nCREATE VIEW sorted_bgw_log AS\nSELECT msg_no,\n  mock_time,\n  application_name,\n  regexp_replace(CASE WHEN length(msg) > 80 THEN substring(msg, 1, 80) || '...' ELSE msg END,\n  \t\t '(execution time) [0-9]+(\\.[0-9]+)?', '\\1 (RANDOM)', 'g') AS msg\nFROM bgw_log\nORDER BY mock_time,\n  application_name COLLATE \"C\",\n  msg_no;\nCREATE TABLE public.bgw_dsm_handle_store(\n    handle BIGINT\n);\nINSERT INTO public.bgw_dsm_handle_store VALUES (0);\nSELECT ts_bgw_params_create();\n ts_bgw_params_create \n----------------------\n \n\nSELECT * FROM _timescaledb_catalog.bgw_job;\n id | application_name | schedule_interval | max_runtime | max_retries | retry_period | proc_schema | proc_name | owner | scheduled | fixed_schedule | initial_start | hypertable_id | config | check_schema | check_name | timezone \n----+------------------+-------------------+-------------+-------------+--------------+-------------+-----------+-------+-----------+----------------+---------------+---------------+--------+--------------+------------+----------\n\nSELECT * FROM timescaledb_information.job_stats;\n hypertable_schema | hypertable_name | job_id | last_run_started_at | last_successful_finish | last_run_status | job_status | last_run_duration | next_start | total_runs | total_successes | total_failures \n-------------------+-----------------+--------+---------------------+------------------------+-----------------+------------+-------------------+------------+------------+-----------------+----------------\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n------------------------------\n-- test reorder policy runs --\n------------------------------\nCREATE TABLE test_reorder_table(time int, chunk_id int);\nSELECT create_hypertable('test_reorder_table', 'time', chunk_time_interval => 1);\n        create_hypertable        \n---------------------------------\n (1,public,test_reorder_table,t)\n\n-- These inserts should create 5 different chunks\nINSERT INTO test_reorder_table VALUES (1, 1);\nINSERT INTO test_reorder_table VALUES (2, 2);\nINSERT INTO test_reorder_table VALUES (3, 3);\nINSERT INTO test_reorder_table VALUES (4, 4);\nINSERT INTO test_reorder_table VALUES (5, 5);\nSELECT COUNT(*) FROM _timescaledb_catalog.chunk as c, _timescaledb_catalog.hypertable as ht where c.hypertable_id = ht.id and ht.table_name='test_reorder_table';\n count \n-------\n     5\n\nSELECT count(*) FROM _timescaledb_catalog.bgw_job WHERE proc_schema = '_timescaledb_functions' AND proc_name = 'policy_reorder';\n count \n-------\n     0\n\nselect add_reorder_policy('test_reorder_table', 'test_reorder_table_time_idx') as reorder_job_id \\gset\nSELECT count(*) FROM _timescaledb_catalog.bgw_job WHERE proc_schema = '_timescaledb_functions' AND proc_name = 'policy_reorder';\n count \n-------\n     1\n\n-- job was created\nSELECT * FROM timescaledb_information.jobs WHERE job_id=:reorder_job_id;\n job_id |   application_name    | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema       |   proc_name    |       owner       | scheduled | fixed_schedule |                              config                               | next_start | initial_start | hypertable_schema |  hypertable_name   |      check_schema      |      check_name      \n--------+-----------------------+-------------------+-------------+-------------+--------------+------------------------+----------------+-------------------+-----------+----------------+-------------------------------------------------------------------+------------+---------------+-------------------+--------------------+------------------------+----------------------\n   1000 | Reorder Policy [1000] | @ 4 days          | @ 0         |          -1 | @ 5 mins     | _timescaledb_functions | policy_reorder | default_perm_user | t         | f              | {\"index_name\": \"test_reorder_table_time_idx\", \"hypertable_id\": 1} |            |               | public            | test_reorder_table | _timescaledb_functions | policy_reorder_check\n\n-- no stats\nSELECT job_id, next_start, last_finish as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    ORDER BY job_id;\n job_id | next_start | until_next | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------+------------+------------------+------------+-----------------+----------------+---------------\n\n-- nothing clustered\nSELECT indexrelid::regclass, indisclustered\n    FROM pg_index\n    WHERE indisclustered = true ORDER BY 1;\n indexrelid | indisclustered \n------------+----------------\n\n-- run first time\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | mock_time | application_name |                    msg                     \n--------+-----------+------------------+--------------------------------------------\n      0 |         0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 |         0 | DB Scheduler     | [TESTING] Wait until 25000, started at 0\n\nSELECT * FROM timescaledb_information.jobs WHERE job_id=:reorder_job_id;\n job_id |   application_name    | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema       |   proc_name    |       owner       | scheduled | fixed_schedule |                              config                               |          next_start          | initial_start | hypertable_schema |  hypertable_name   |      check_schema      |      check_name      \n--------+-----------------------+-------------------+-------------+-------------+--------------+------------------------+----------------+-------------------+-----------+----------------+-------------------------------------------------------------------+------------------------------+---------------+-------------------+--------------------+------------------------+----------------------\n   1000 | Reorder Policy [1000] | @ 4 days          | @ 0         |          -1 | @ 5 mins     | _timescaledb_functions | policy_reorder | default_perm_user | t         | f              | {\"index_name\": \"test_reorder_table_time_idx\", \"hypertable_id\": 1} | Fri Dec 31 16:00:00 1999 PST |               | public            | test_reorder_table | _timescaledb_functions | policy_reorder_check\n\n-- job ran once, successfully\nSELECT job_id, next_start, last_finish as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:reorder_job_id;\n job_id |          next_start          |          until_next          | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------------------+------------------------------+------------------+------------+-----------------+----------------+---------------\n   1000 | Fri Dec 31 16:00:00 1999 PST | Fri Dec 31 16:00:00 1999 PST | t                |          1 |               1 |              0 |             0\n\n-- first chunk reordered\nSELECT indexrelid::regclass, indisclustered\n    FROM pg_index\n    WHERE indisclustered = true ORDER BY 1;\n                             indexrelid                             | indisclustered \n--------------------------------------------------------------------+----------------\n _timescaledb_internal._hyper_1_1_chunk_test_reorder_table_time_idx | t\n\n-- second call to scheduler should immediately run reorder again, due to catchup\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | mock_time | application_name |                     msg                      \n--------+-----------+------------------+----------------------------------------------\n      0 |         0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 |         0 | DB Scheduler     | [TESTING] Wait until 25000, started at 0\n      0 |     25000 | DB Scheduler     | [TESTING] Registered new background worker\n      1 |     25000 | DB Scheduler     | [TESTING] Wait until 50000, started at 25000\n\nSELECT * FROM timescaledb_information.jobs WHERE job_id=:reorder_job_id;\n job_id |   application_name    | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema       |   proc_name    |       owner       | scheduled | fixed_schedule |                              config                               |            next_start            | initial_start | hypertable_schema |  hypertable_name   |      check_schema      |      check_name      \n--------+-----------------------+-------------------+-------------+-------------+--------------+------------------------+----------------+-------------------+-----------+----------------+-------------------------------------------------------------------+----------------------------------+---------------+-------------------+--------------------+------------------------+----------------------\n   1000 | Reorder Policy [1000] | @ 4 days          | @ 0         |          -1 | @ 5 mins     | _timescaledb_functions | policy_reorder | default_perm_user | t         | f              | {\"index_name\": \"test_reorder_table_time_idx\", \"hypertable_id\": 1} | Fri Dec 31 16:00:00.025 1999 PST |               | public            | test_reorder_table | _timescaledb_functions | policy_reorder_check\n\n-- two runs\nSELECT job_id, next_start, last_finish as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:reorder_job_id;\n job_id |            next_start            |            until_next            | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+----------------------------------+----------------------------------+------------------+------------+-----------------+----------------+---------------\n   1000 | Fri Dec 31 16:00:00.025 1999 PST | Fri Dec 31 16:00:00.025 1999 PST | t                |          2 |               2 |              0 |             0\n\n-- two chunks clustered\nSELECT indexrelid::regclass, indisclustered\n    FROM pg_index\n    WHERE indisclustered = true ORDER BY 1;\n                             indexrelid                             | indisclustered \n--------------------------------------------------------------------+----------------\n _timescaledb_internal._hyper_1_1_chunk_test_reorder_table_time_idx | t\n _timescaledb_internal._hyper_1_2_chunk_test_reorder_table_time_idx | t\n\n-- third call to scheduler should immediately run reorder again, due to catchup\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(50);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\n-- job info is gone\nSELECT * FROM sorted_bgw_log;\n msg_no | mock_time | application_name |                      msg                      \n--------+-----------+------------------+-----------------------------------------------\n      0 |         0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 |         0 | DB Scheduler     | [TESTING] Wait until 25000, started at 0\n      0 |     25000 | DB Scheduler     | [TESTING] Registered new background worker\n      1 |     25000 | DB Scheduler     | [TESTING] Wait until 50000, started at 25000\n      0 |     50000 | DB Scheduler     | [TESTING] Registered new background worker\n      1 |     50000 | DB Scheduler     | [TESTING] Wait until 100000, started at 50000\n\nSELECT * FROM timescaledb_information.jobs WHERE job_id=:reorder_job_id;\n job_id |   application_name    | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema       |   proc_name    |       owner       | scheduled | fixed_schedule |                              config                               |           next_start            | initial_start | hypertable_schema |  hypertable_name   |      check_schema      |      check_name      \n--------+-----------------------+-------------------+-------------+-------------+--------------+------------------------+----------------+-------------------+-----------+----------------+-------------------------------------------------------------------+---------------------------------+---------------+-------------------+--------------------+------------------------+----------------------\n   1000 | Reorder Policy [1000] | @ 4 days          | @ 0         |          -1 | @ 5 mins     | _timescaledb_functions | policy_reorder | default_perm_user | t         | f              | {\"index_name\": \"test_reorder_table_time_idx\", \"hypertable_id\": 1} | Tue Jan 04 16:00:00.05 2000 PST |               | public            | test_reorder_table | _timescaledb_functions | policy_reorder_check\n\nSELECT *\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:reorder_job_id;\n job_id |           last_start            |           last_finish           |           next_start            |     last_successful_finish      | last_run_success | total_runs | total_duration | total_duration_failures | total_successes | total_failures | total_crashes | consecutive_failures | consecutive_crashes | flags \n--------+---------------------------------+---------------------------------+---------------------------------+---------------------------------+------------------+------------+----------------+-------------------------+-----------------+----------------+---------------+----------------------+---------------------+-------\n   1000 | Fri Dec 31 16:00:00.05 1999 PST | Fri Dec 31 16:00:00.05 1999 PST | Tue Jan 04 16:00:00.05 2000 PST | Fri Dec 31 16:00:00.05 1999 PST | t                |          3 | @ 0            | @ 0                     |               3 |              0 |             0 |                    0 |                   0 |     0\n\n-- three chunks clustered\nSELECT indexrelid::regclass, indisclustered\n    FROM pg_index\n    WHERE indisclustered = true ORDER BY 1;\n                             indexrelid                             | indisclustered \n--------------------------------------------------------------------+----------------\n _timescaledb_internal._hyper_1_1_chunk_test_reorder_table_time_idx | t\n _timescaledb_internal._hyper_1_2_chunk_test_reorder_table_time_idx | t\n _timescaledb_internal._hyper_1_3_chunk_test_reorder_table_time_idx | t\n\n-- running is a nop\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(100);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | mock_time | application_name |                      msg                       \n--------+-----------+------------------+------------------------------------------------\n      0 |         0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 |         0 | DB Scheduler     | [TESTING] Wait until 25000, started at 0\n      0 |     25000 | DB Scheduler     | [TESTING] Registered new background worker\n      1 |     25000 | DB Scheduler     | [TESTING] Wait until 50000, started at 25000\n      0 |     50000 | DB Scheduler     | [TESTING] Registered new background worker\n      1 |     50000 | DB Scheduler     | [TESTING] Wait until 100000, started at 50000\n      0 |    100000 | DB Scheduler     | [TESTING] Wait until 200000, started at 100000\n\nSELECT * FROM timescaledb_information.jobs WHERE job_id=:reorder_job_id;\n job_id |   application_name    | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema       |   proc_name    |       owner       | scheduled | fixed_schedule |                              config                               |           next_start            | initial_start | hypertable_schema |  hypertable_name   |      check_schema      |      check_name      \n--------+-----------------------+-------------------+-------------+-------------+--------------+------------------------+----------------+-------------------+-----------+----------------+-------------------------------------------------------------------+---------------------------------+---------------+-------------------+--------------------+------------------------+----------------------\n   1000 | Reorder Policy [1000] | @ 4 days          | @ 0         |          -1 | @ 5 mins     | _timescaledb_functions | policy_reorder | default_perm_user | t         | f              | {\"index_name\": \"test_reorder_table_time_idx\", \"hypertable_id\": 1} | Tue Jan 04 16:00:00.05 2000 PST |               | public            | test_reorder_table | _timescaledb_functions | policy_reorder_check\n\nSELECT *\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:reorder_job_id;\n job_id |           last_start            |           last_finish           |           next_start            |     last_successful_finish      | last_run_success | total_runs | total_duration | total_duration_failures | total_successes | total_failures | total_crashes | consecutive_failures | consecutive_crashes | flags \n--------+---------------------------------+---------------------------------+---------------------------------+---------------------------------+------------------+------------+----------------+-------------------------+-----------------+----------------+---------------+----------------------+---------------------+-------\n   1000 | Fri Dec 31 16:00:00.05 1999 PST | Fri Dec 31 16:00:00.05 1999 PST | Tue Jan 04 16:00:00.05 2000 PST | Fri Dec 31 16:00:00.05 1999 PST | t                |          3 | @ 0            | @ 0                     |               3 |              0 |             0 |                    0 |                   0 |     0\n\n-- still have 3 chunks clustered\nSELECT indexrelid::regclass, indisclustered\n    FROM pg_index\n    WHERE indisclustered = true ORDER BY 1;\n                             indexrelid                             | indisclustered \n--------------------------------------------------------------------+----------------\n _timescaledb_internal._hyper_1_1_chunk_test_reorder_table_time_idx | t\n _timescaledb_internal._hyper_1_2_chunk_test_reorder_table_time_idx | t\n _timescaledb_internal._hyper_1_3_chunk_test_reorder_table_time_idx | t\n\n--check that views work correctly\nSELECT * FROM timescaledb_information.job_stats;\n hypertable_schema |  hypertable_name   | job_id |       last_run_started_at       |     last_successful_finish      | last_run_status | job_status | last_run_duration |           next_start            | total_runs | total_successes | total_failures \n-------------------+--------------------+--------+---------------------------------+---------------------------------+-----------------+------------+-------------------+---------------------------------+------------+-----------------+----------------\n public            | test_reorder_table |   1000 | Fri Dec 31 16:00:00.05 1999 PST | Fri Dec 31 16:00:00.05 1999 PST | Success         | Scheduled  |                   | Tue Jan 04 16:00:00.05 2000 PST |          3 |               3 |              0\n\n-- test deleting the policy\nSELECT remove_reorder_policy('test_reorder_table');\n remove_reorder_policy \n-----------------------\n \n\nSELECT * FROM timescaledb_information.jobs WHERE job_id=:reorder_job_id;\n job_id | application_name | schedule_interval | max_runtime | max_retries | retry_period | proc_schema | proc_name | owner | scheduled | fixed_schedule | config | next_start | initial_start | hypertable_schema | hypertable_name | check_schema | check_name \n--------+------------------+-------------------+-------------+-------------+--------------+-------------+-----------+-------+-----------+----------------+--------+------------+---------------+-------------------+-----------------+--------------+------------\n\nSELECT job_id, next_start, last_finish as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:reorder_job_id;\n job_id | next_start | until_next | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------+------------+------------------+------------+-----------------+----------------+---------------\n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(125);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | mock_time | application_name |                      msg                       \n--------+-----------+------------------+------------------------------------------------\n      0 |         0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 |         0 | DB Scheduler     | [TESTING] Wait until 25000, started at 0\n      0 |     25000 | DB Scheduler     | [TESTING] Registered new background worker\n      1 |     25000 | DB Scheduler     | [TESTING] Wait until 50000, started at 25000\n      0 |     50000 | DB Scheduler     | [TESTING] Registered new background worker\n      1 |     50000 | DB Scheduler     | [TESTING] Wait until 100000, started at 50000\n      0 |    100000 | DB Scheduler     | [TESTING] Wait until 200000, started at 100000\n      0 |    200000 | DB Scheduler     | [TESTING] Wait until 325000, started at 200000\n\nSELECT * FROM timescaledb_information.jobs WHERE job_id=:reorder_job_id;\n job_id | application_name | schedule_interval | max_runtime | max_retries | retry_period | proc_schema | proc_name | owner | scheduled | fixed_schedule | config | next_start | initial_start | hypertable_schema | hypertable_name | check_schema | check_name \n--------+------------------+-------------------+-------------+-------------+--------------+-------------+-----------+-------+-----------+----------------+--------+------------+---------------+-------------------+-----------------+--------------+------------\n\n-- still only 3 chunks clustered\nSELECT indexrelid::regclass, indisclustered\n    FROM pg_index\n    WHERE indisclustered = true ORDER BY 1;\n                             indexrelid                             | indisclustered \n--------------------------------------------------------------------+----------------\n _timescaledb_internal._hyper_1_1_chunk_test_reorder_table_time_idx | t\n _timescaledb_internal._hyper_1_2_chunk_test_reorder_table_time_idx | t\n _timescaledb_internal._hyper_1_3_chunk_test_reorder_table_time_idx | t\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE bgw_log;\nTRUNCATE _timescaledb_internal.bgw_job_stat;\nDELETE FROM _timescaledb_catalog.bgw_job;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n-----------------------------------\n-- test drop chunks policy runs --\n-----------------------------------\nCREATE TABLE test_drop_chunks_table(time timestamptz, drop_order int);\nSELECT create_hypertable('test_drop_chunks_table', 'time', chunk_time_interval => INTERVAL '1 week');\n          create_hypertable          \n-------------------------------------\n (2,public,test_drop_chunks_table,t)\n\n-- These inserts should create 5 different chunks\nINSERT INTO test_drop_chunks_table VALUES (now() - INTERVAL '2 month',  4);\nINSERT INTO test_drop_chunks_table VALUES (now(),                       5);\nINSERT INTO test_drop_chunks_table VALUES (now() - INTERVAL '6 months', 2);\nINSERT INTO test_drop_chunks_table VALUES (now() - INTERVAL '4 months', 3);\nINSERT INTO test_drop_chunks_table VALUES (now() - INTERVAL '8 months', 1);\nSELECT show_chunks('test_drop_chunks_table');\n               show_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_2_6_chunk\n _timescaledb_internal._hyper_2_7_chunk\n _timescaledb_internal._hyper_2_8_chunk\n _timescaledb_internal._hyper_2_9_chunk\n _timescaledb_internal._hyper_2_10_chunk\n\nSELECT COUNT(*) FROM _timescaledb_catalog.chunk as c, _timescaledb_catalog.hypertable as ht where c.hypertable_id = ht.id and ht.table_name='test_drop_chunks_table';\n count \n-------\n     5\n\nSELECT count(*) FROM _timescaledb_catalog.bgw_job WHERE proc_schema = '_timescaledb_functions' AND proc_name = 'policy_retention';\n count \n-------\n     0\n\nSELECT add_retention_policy('test_drop_chunks_table', INTERVAL '4 months') as drop_chunks_job_id \\gset\nSELECT count(*) FROM _timescaledb_catalog.bgw_job WHERE proc_schema = '_timescaledb_functions' AND proc_name = 'policy_retention';\n count \n-------\n     1\n\nSELECT alter_job(:drop_chunks_job_id, schedule_interval => INTERVAL '1 second');\n                                                                                        alter_job                                                                                         \n------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1001,\"@ 1 sec\",\"@ 5 mins\",-1,\"@ 5 mins\",t,\"{\"\"drop_after\"\": \"\"@ 4 mons\"\", \"\"hypertable_id\"\": 2}\",-infinity,_timescaledb_functions.policy_retention_check,f,,,\"Retention Policy [1001]\")\n\nSELECT * FROM timescaledb_information.jobs WHERE job_id=:drop_chunks_job_id;\n job_id |    application_name     | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema       |    proc_name     |       owner       | scheduled | fixed_schedule |                     config                     | next_start | initial_start | hypertable_schema |    hypertable_name     |      check_schema      |       check_name       \n--------+-------------------------+-------------------+-------------+-------------+--------------+------------------------+------------------+-------------------+-----------+----------------+------------------------------------------------+------------+---------------+-------------------+------------------------+------------------------+------------------------\n   1001 | Retention Policy [1001] | @ 1 sec           | @ 5 mins    |          -1 | @ 5 mins     | _timescaledb_functions | policy_retention | default_perm_user | t         | f              | {\"drop_after\": \"@ 4 mons\", \"hypertable_id\": 2} |            |               | public            | test_drop_chunks_table | _timescaledb_functions | policy_retention_check\n\n-- no stats\nSELECT job_id, next_start, last_finish as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    WHERE job_id=:drop_chunks_job_id;\n job_id | next_start | until_next | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------+------------+------------------+------------+-----------------+----------------+---------------\n\n-- all chunks are there\nSELECT show_chunks('test_drop_chunks_table');\n               show_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_2_6_chunk\n _timescaledb_internal._hyper_2_7_chunk\n _timescaledb_internal._hyper_2_8_chunk\n _timescaledb_internal._hyper_2_9_chunk\n _timescaledb_internal._hyper_2_10_chunk\n\n-- run first time\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | mock_time | application_name |                    msg                     \n--------+-----------+------------------+--------------------------------------------\n      0 |         0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 |         0 | DB Scheduler     | [TESTING] Wait until 25000, started at 0\n\nSELECT * FROM timescaledb_information.jobs WHERE job_id=:drop_chunks_job_id;\n job_id |    application_name     | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema       |    proc_name     |       owner       | scheduled | fixed_schedule |                     config                     |          next_start          | initial_start | hypertable_schema |    hypertable_name     |      check_schema      |       check_name       \n--------+-------------------------+-------------------+-------------+-------------+--------------+------------------------+------------------+-------------------+-----------+----------------+------------------------------------------------+------------------------------+---------------+-------------------+------------------------+------------------------+------------------------\n   1001 | Retention Policy [1001] | @ 1 sec           | @ 5 mins    |          -1 | @ 5 mins     | _timescaledb_functions | policy_retention | default_perm_user | t         | f              | {\"drop_after\": \"@ 4 mons\", \"hypertable_id\": 2} | Fri Dec 31 16:00:01 1999 PST |               | public            | test_drop_chunks_table | _timescaledb_functions | policy_retention_check\n\n-- job ran once, successfully\nSELECT job_id, time_bucket('1m',next_start) AS next_start, time_bucket('1m',last_finish) as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:drop_chunks_job_id;\n job_id |          next_start          |          until_next          | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------------------+------------------------------+------------------+------------+-----------------+----------------+---------------\n   1001 | Fri Dec 31 16:00:00 1999 PST | Fri Dec 31 16:00:00 1999 PST | t                |          1 |               1 |              0 |             0\n\n-- chunks 8 and 10 dropped\nSELECT show_chunks('test_drop_chunks_table');\n              show_chunks               \n----------------------------------------\n _timescaledb_internal._hyper_2_6_chunk\n _timescaledb_internal._hyper_2_7_chunk\n _timescaledb_internal._hyper_2_9_chunk\n\n-- job doesn't run again immediately\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | mock_time | application_name |                     msg                      \n--------+-----------+------------------+----------------------------------------------\n      0 |         0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 |         0 | DB Scheduler     | [TESTING] Wait until 25000, started at 0\n      0 |     25000 | DB Scheduler     | [TESTING] Wait until 50000, started at 25000\n\nSELECT * FROM timescaledb_information.jobs WHERE job_id=:drop_chunks_job_id;\n job_id |    application_name     | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema       |    proc_name     |       owner       | scheduled | fixed_schedule |                     config                     |          next_start          | initial_start | hypertable_schema |    hypertable_name     |      check_schema      |       check_name       \n--------+-------------------------+-------------------+-------------+-------------+--------------+------------------------+------------------+-------------------+-----------+----------------+------------------------------------------------+------------------------------+---------------+-------------------+------------------------+------------------------+------------------------\n   1001 | Retention Policy [1001] | @ 1 sec           | @ 5 mins    |          -1 | @ 5 mins     | _timescaledb_functions | policy_retention | default_perm_user | t         | f              | {\"drop_after\": \"@ 4 mons\", \"hypertable_id\": 2} | Fri Dec 31 16:00:01 1999 PST |               | public            | test_drop_chunks_table | _timescaledb_functions | policy_retention_check\n\n-- still only 1 run\nSELECT job_id, time_bucket('1m',next_start) AS next_start, time_bucket('1m',last_finish) as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:drop_chunks_job_id;\n job_id |          next_start          |          until_next          | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------------------+------------------------------+------------------+------------+-----------------+----------------+---------------\n   1001 | Fri Dec 31 16:00:00 1999 PST | Fri Dec 31 16:00:00 1999 PST | t                |          1 |               1 |              0 |             0\n\n-- same chunks\nSELECT show_chunks('test_drop_chunks_table');\n              show_chunks               \n----------------------------------------\n _timescaledb_internal._hyper_2_6_chunk\n _timescaledb_internal._hyper_2_7_chunk\n _timescaledb_internal._hyper_2_9_chunk\n\n-- a new chunk older than the drop date will be dropped\nINSERT INTO test_drop_chunks_table VALUES (now() - INTERVAL '12 months', 0);\nSELECT show_chunks('test_drop_chunks_table');\n               show_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_2_6_chunk\n _timescaledb_internal._hyper_2_7_chunk\n _timescaledb_internal._hyper_2_9_chunk\n _timescaledb_internal._hyper_2_11_chunk\n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(10000);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | mock_time | application_name |                        msg                        \n--------+-----------+------------------+---------------------------------------------------\n      0 |         0 | DB Scheduler     | [TESTING] Registered new background worker\n      1 |         0 | DB Scheduler     | [TESTING] Wait until 25000, started at 0\n      0 |     25000 | DB Scheduler     | [TESTING] Wait until 50000, started at 25000\n      0 |     50000 | DB Scheduler     | [TESTING] Wait until 1000000, started at 50000\n      1 |   1000000 | DB Scheduler     | [TESTING] Registered new background worker\n      2 |   1000000 | DB Scheduler     | [TESTING] Wait until 10050000, started at 1000000\n\nSELECT * FROM timescaledb_information.jobs WHERE job_id=:drop_chunks_job_id;\n job_id |    application_name     | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema       |    proc_name     |       owner       | scheduled | fixed_schedule |                     config                     |          next_start          | initial_start | hypertable_schema |    hypertable_name     |      check_schema      |       check_name       \n--------+-------------------------+-------------------+-------------+-------------+--------------+------------------------+------------------+-------------------+-----------+----------------+------------------------------------------------+------------------------------+---------------+-------------------+------------------------+------------------------+------------------------\n   1001 | Retention Policy [1001] | @ 1 sec           | @ 5 mins    |          -1 | @ 5 mins     | _timescaledb_functions | policy_retention | default_perm_user | t         | f              | {\"drop_after\": \"@ 4 mons\", \"hypertable_id\": 2} | Fri Dec 31 16:00:02 1999 PST |               | public            | test_drop_chunks_table | _timescaledb_functions | policy_retention_check\n\n-- 2 runs\nSELECT job_id, time_bucket('1m',next_start) AS next_start, time_bucket('1m',last_finish) as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:drop_chunks_job_id;\n job_id |          next_start          |          until_next          | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------------------+------------------------------+------------------+------------+-----------------+----------------+---------------\n   1001 | Fri Dec 31 16:00:00 1999 PST | Fri Dec 31 16:00:00 1999 PST | t                |          2 |               2 |              0 |             0\n\nSELECT show_chunks('test_drop_chunks_table');\n              show_chunks               \n----------------------------------------\n _timescaledb_internal._hyper_2_6_chunk\n _timescaledb_internal._hyper_2_7_chunk\n _timescaledb_internal._hyper_2_9_chunk\n\n--test that views work\nSELECT * FROM timescaledb_information.job_stats;\n hypertable_schema |    hypertable_name     | job_id |     last_run_started_at      |    last_successful_finish    | last_run_status | job_status | last_run_duration |          next_start          | total_runs | total_successes | total_failures \n-------------------+------------------------+--------+------------------------------+------------------------------+-----------------+------------+-------------------+------------------------------+------------+-----------------+----------------\n public            | test_drop_chunks_table |   1001 | Fri Dec 31 16:00:01 1999 PST | Fri Dec 31 16:00:01 1999 PST | Success         | Scheduled  |                   | Fri Dec 31 16:00:02 1999 PST |          2 |               2 |              0\n\n-- Test that add_retention_policy also works with timestamp (without time zone) and date types\n-- and that the policy execution is being logged\n-- Test for date\nCREATE TABLE test_drop_chunks_table_date(time date, drop_order int);\nSELECT create_hypertable('test_drop_chunks_table_date', 'time', chunk_time_interval => INTERVAL '1 week');\n            create_hypertable             \n------------------------------------------\n (3,public,test_drop_chunks_table_date,t)\n\nINSERT INTO test_drop_chunks_table_date VALUES (now() - INTERVAL '2 month',  4);\nINSERT INTO test_drop_chunks_table_date VALUES (now(),                       5);\nINSERT INTO test_drop_chunks_table_date VALUES (now() - INTERVAL '6 months', 2);\nINSERT INTO test_drop_chunks_table_date VALUES (now() - INTERVAL '4 months', 3);\nINSERT INTO test_drop_chunks_table_date VALUES (now() - INTERVAL '8 months', 1);\n-- Clear the job stats and reset timer, this will also clear the bgw_log\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE bgw_log;\nTRUNCATE _timescaledb_internal.bgw_job_stat;\nDELETE FROM _timescaledb_catalog.bgw_job;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n-- Test for timestamp\nCREATE TABLE test_drop_chunks_table_tsntz(time date, drop_order int);\nSELECT create_hypertable('test_drop_chunks_table_tsntz', 'time', chunk_time_interval => INTERVAL '1 week');\n             create_hypertable             \n-------------------------------------------\n (4,public,test_drop_chunks_table_tsntz,t)\n\nINSERT INTO test_drop_chunks_table_tsntz VALUES (now() - INTERVAL '2 month',  4);\nINSERT INTO test_drop_chunks_table_tsntz VALUES (now(),                       5);\nINSERT INTO test_drop_chunks_table_tsntz VALUES (now() - INTERVAL '6 months', 2);\nINSERT INTO test_drop_chunks_table_tsntz VALUES (now() - INTERVAL '4 months', 3);\nINSERT INTO test_drop_chunks_table_tsntz VALUES (now() - INTERVAL '8 months', 1);\n-- Add retention policies for both tables\nSELECT add_retention_policy('test_drop_chunks_table_date', INTERVAL '4 months') as drop_chunks_date_job_id \\gset\nSELECT add_retention_policy('test_drop_chunks_table_tsntz', INTERVAL '4 months') as drop_chunks_tsntz_job_id \\gset\n-- Test that retention policy is being logged\nSELECT alter_job(id,config:=jsonb_set(config,'{verbose_log}', 'true'))\n FROM _timescaledb_catalog.bgw_job WHERE id = :drop_chunks_date_job_id;\n                                                                                                    alter_job                                                                                                    \n-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1002,\"@ 1 day\",\"@ 5 mins\",-1,\"@ 5 mins\",t,\"{\"\"drop_after\"\": \"\"@ 4 mons\"\", \"\"verbose_log\"\": true, \"\"hypertable_id\"\": 3}\",-infinity,_timescaledb_functions.policy_retention_check,f,,,\"Retention Policy [1002]\")\n\nSELECT alter_job(id,config:=jsonb_set(config,'{verbose_log}', 'true'))\n FROM _timescaledb_catalog.bgw_job WHERE id = :drop_chunks_tsntz_job_id;\n                                                                                                    alter_job                                                                                                    \n-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1003,\"@ 1 day\",\"@ 5 mins\",-1,\"@ 5 mins\",t,\"{\"\"drop_after\"\": \"\"@ 4 mons\"\", \"\"verbose_log\"\": true, \"\"hypertable_id\"\": 4}\",-infinity,_timescaledb_functions.policy_retention_check,f,,,\"Retention Policy [1003]\")\n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(1000);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | mock_time |    application_name     |                                         msg                                         \n--------+-----------+-------------------------+-------------------------------------------------------------------------------------\n      0 |         0 | DB Scheduler            | [TESTING] Registered new background worker\n      1 |         0 | DB Scheduler            | [TESTING] Registered new background worker\n      2 |         0 | DB Scheduler            | [TESTING] Wait until 1000000, started at 0\n      0 |         0 | Retention Policy [1002] | applying retention policy to hypertable \"test_drop_chunks_table_date\": dropping ...\n      0 |         0 | Retention Policy [1003] | applying retention policy to hypertable \"test_drop_chunks_table_tsntz\": dropping...\n\n-- test the schedule_interval parameter for policies\nCREATE TABLE test_schedint(time timestamptz, a int, b int);\nselect create_hypertable('test_schedint', 'time');\n     create_hypertable      \n----------------------------\n (5,public,test_schedint,t)\n\ninsert into test_schedint values (now(), 1, 2), (now() + interval '2 seconds', 2, 3);\n-- test the retention policy\nselect add_retention_policy('test_schedint', interval '2 months', schedule_interval => '30 seconds') as polret_schedint \\gset\n-- wait for a bit more than \"schedule_interval\" seconds, then verify the policy has run twice\nselect ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(1000);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nselect total_runs, total_successes, total_failures from timescaledb_information.job_stats where job_id = :polret_schedint;\n total_runs | total_successes | total_failures \n------------+-----------------+----------------\n          1 |               1 |              0\n\nselect ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(30000);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nselect total_runs, total_successes, total_failures from timescaledb_information.job_stats where job_id = :polret_schedint;\n total_runs | total_successes | total_failures \n------------+-----------------+----------------\n          2 |               2 |              0\n\n-- if we wait another 30s, we should see 3 runs of the job\nselect ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(30000);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nselect total_runs, total_successes, total_failures from timescaledb_information.job_stats where job_id = :polret_schedint;\n total_runs | total_successes | total_failures \n------------+-----------------+----------------\n          3 |               3 |              0\n\n-- test the compression policy\nalter table test_schedint set (timescaledb.compress);\nselect add_compression_policy('test_schedint', interval '3 weeks', schedule_interval => '40 seconds') as polcomp_schedint \\gset\nselect ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(1000);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nselect total_runs, total_successes, total_failures from timescaledb_information.job_stats where job_id = :polcomp_schedint;\n total_runs | total_successes | total_failures \n------------+-----------------+----------------\n          1 |               1 |              0\n\nselect ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(40000);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nselect total_runs, total_successes, total_failures from timescaledb_information.job_stats where job_id = :polcomp_schedint;\n total_runs | total_successes | total_failures \n------------+-----------------+----------------\n          2 |               2 |              0\n\n-- if we wait another 40s, we should see 3 runs of the job\nselect ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(40000);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nselect total_runs, total_successes, total_failures from timescaledb_information.job_stats where job_id = :polcomp_schedint;\n total_runs | total_successes | total_failures \n------------+-----------------+----------------\n          3 |               3 |              0\n\n"
  },
  {
    "path": "tsl/test/expected/bgw_scheduler_control.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE FUNCTION ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(INT, INT) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE FUNCTION ts_bgw_params_create() RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE FUNCTION ts_bgw_params_destroy() RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE FUNCTION ts_bgw_params_reset_time(set_time BIGINT, wait BOOLEAN) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nALTER DATABASE :TEST_DBNAME OWNER TO :ROLE_DEFAULT_PERM_USER;\nGRANT EXECUTE ON FUNCTION pg_reload_conf TO :ROLE_DEFAULT_PERM_USER;\nGRANT ALTER SYSTEM, SET ON PARAMETER timescaledb.bgw_log_level TO :ROLE_DEFAULT_PERM_USER;\n-- These are needed to set up the test scheduler\nCREATE TABLE public.bgw_dsm_handle_store(handle BIGINT);\nINSERT INTO public.bgw_dsm_handle_store VALUES (0);\nSELECT ts_bgw_params_create();\n ts_bgw_params_create \n----------------------\n \n\n-- Test scheduler automatically writes to this table by name, so\n-- create it.\nCREATE TABLE public.bgw_log(\n    msg_no INT,\n    mock_time BIGINT,\n    application_name TEXT,\n    msg TEXT\n);\nCREATE VIEW cleaned_bgw_log AS\n    SELECT msg_no, application_name,\n    \t   regexp_replace(regexp_replace(msg, '(Wait until|started at|execution time|database) [0-9]+(\\.[0-9]+)?', '\\1 (RANDOM)', 'g'), 'background worker \"[^\"]+\"','connection') AS msg\n      FROM bgw_log ORDER BY mock_time, application_name COLLATE \"C\", msg_no;\n-- Remove all default jobs\nDELETE FROM _timescaledb_catalog.bgw_job WHERE TRUE;\nTRUNCATE _timescaledb_internal.bgw_job_stat;\n--\n-- Set bgw log level and reload config.\n--\n-- Debug messages should be in log now which it wasn't before.\n--\n-- We change user to make sure that granting SET and ALTER SYSTEM\n-- privileges to the default user actually works.\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nALTER DATABASE :TEST_DBNAME SET timescaledb.bgw_log_level = 'DEBUG1';\nSELECT pg_reload_conf();\n pg_reload_conf \n----------------\n t\n\nRESET ROLE;\nSELECT ts_bgw_params_reset_time(0, false);\n ts_bgw_params_reset_time \n--------------------------\n \n\nINSERT INTO _timescaledb_catalog.bgw_job(\n       application_name,\n       schedule_interval,\n       max_runtime,\n       max_retries,\n       retry_period,\n       proc_schema,\n       proc_name,\n       owner,\n       scheduled,\n       fixed_schedule\n) VALUES (\n\t'test_job_1b',\t\t--application_name\n\tINTERVAL '100ms',\t--schedule_interval\n\tINTERVAL '100s',\t--max_runtime\n\t5,\t\t\t--max_retries\n\tINTERVAL '1s',\t\t--retry_period\n\t'public',\t\t--proc_schema\n\t'bgw_test_job_1',\t--proc_name\n\tCURRENT_ROLE::regrole,\t--owner\n\tTRUE,\t\t\t--scheduled\n\tFALSE\t\t\t--fixed_schedule\n) RETURNING id AS job_id \\gset\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25, 0);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM cleaned_bgw_log;\n msg_no | application_name |                                   msg                                   \n--------+------------------+-------------------------------------------------------------------------\n      0 | DB Scheduler     | extension state changed: unknown to created\n      1 | DB Scheduler     | database scheduler for database (RANDOM) starting\n      2 | DB Scheduler     | launching job 1000 \"test_job_1b\"\n      3 | DB Scheduler     | [TESTING] Registered new background worker\n      4 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | test_job_1b      | Execute job 1\n      1 | test_job_1b      | job 1000 (test_job_1b) exiting with success: execution time (RANDOM) ms\n      5 | DB Scheduler     | scheduler for database (RANDOM) exiting with exit status 0\n\n-- We test that we can set it to FATAL, which removed LOG level\n-- entries from the log.\nALTER DATABASE :TEST_DBNAME SET timescaledb.bgw_log_level = 'FATAL';\nSELECT pg_reload_conf();\n pg_reload_conf \n----------------\n t\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE bgw_log;\nSELECT ts_bgw_params_reset_time(0, false);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25, 0);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM cleaned_bgw_log;\n msg_no | application_name | msg \n--------+------------------+-----\n\n-- We test that we can set it to ERROR.\nALTER DATABASE :TEST_DBNAME SET timescaledb.bgw_log_level = 'ERROR';\nSELECT pg_reload_conf();\n pg_reload_conf \n----------------\n t\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE bgw_log;\nSELECT ts_bgw_params_reset_time(0, false);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25, 0);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM cleaned_bgw_log;\n msg_no | application_name | msg \n--------+------------------+-----\n\n-- Reset the log level and check that normal entries are showing up\n-- again.\nALTER DATABASE :TEST_DBNAME RESET timescaledb.bgw_log_level;\nSELECT pg_reload_conf();\n pg_reload_conf \n----------------\n t\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nTRUNCATE bgw_log;\nSELECT ts_bgw_params_reset_time(0, false);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25, 0);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM cleaned_bgw_log;\n msg_no | application_name |                        msg                         \n--------+------------------+----------------------------------------------------\n      0 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n\nSELECT delete_job(:job_id);\n delete_job \n------------\n \n\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n-- Make sure we can set the variable using ALTER SYSTEM using the\n-- previous grants. We don't bother about checking that it has an\n-- effect here since we already knows it works from the above code.\nALTER SYSTEM SET timescaledb.bgw_log_level TO 'DEBUG2';\nALTER SYSTEM RESET timescaledb.bgw_log_level;\n"
  },
  {
    "path": "tsl/test/expected/bgw_scheduler_restart.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE VIEW tsdb_bgw AS\n       SELECT datname, pid, backend_type, application_name\n         FROM pg_stat_activity\n        WHERE backend_type LIKE '%TimescaleDB%'\n     ORDER BY datname, backend_type, application_name;\n-- Wait for at least one background worker matching pattern to have\n-- started.\nCREATE PROCEDURE wait_for_some_started(\n       min_time double precision,\n       timeout double precision,\n       pattern text\n) AS $$\nDECLARE\n   backend_count int;\nBEGIN\n    FOR i IN 0..(timeout / min_time)::int\n    LOOP\n        PERFORM pg_sleep(min_time);\n        SELECT count(*) INTO backend_count FROM tsdb_bgw WHERE backend_type LIKE pattern;\n        IF backend_count > 0 THEN\n            RETURN;\n        END IF;\n    END LOOP;\n    RAISE EXCEPTION 'backend matching % did not start before timeout', pattern;\nEND;\n$$ LANGUAGE plpgsql;\n-- Wait for the number of background workers matching pattern to be\n-- zero.\nCREATE PROCEDURE wait_for_all_stopped(\n       min_time double precision,\n       timeout double precision,\n       pattern text\n) AS $$\nDECLARE\n   backend_count int;\nBEGIN\n    FOR i IN 0..(timeout / min_time)::int\n    LOOP\n        PERFORM pg_sleep(min_time);\n        SELECT count(*) INTO backend_count FROM tsdb_bgw WHERE backend_type LIKE pattern;\n        IF backend_count = 0 THEN\n            RETURN;\n        END IF;\n    END LOOP;\n    RAISE EXCEPTION 'backend matching % did not start before timeout', pattern;\nEND;\n$$ LANGUAGE plpgsql;\nCREATE PROCEDURE ts_terminate_launcher() AS $$\nSELECT pg_terminate_backend(pid) FROM tsdb_bgw\n WHERE backend_type LIKE '%Launcher%';\n$$ LANGUAGE SQL;\n-- Show the default scheduler restart time\nSHOW timescaledb.bgw_scheduler_restart_time;\n timescaledb.bgw_scheduler_restart_time \n----------------------------------------\n -1\n\n-- Test that it cannot be set to something between -1 and 10\n\\set ON_ERROR_STOP 0\n\\set VERBOSITY default\nALTER SYSTEM SET timescaledb.bgw_scheduler_restart_time TO '5s';\nERROR:  invalid value for parameter \"timescaledb.bgw_scheduler_restart_time\": 5\nDETAIL:  Scheduler restart time must be be either -1 or at least 10 seconds.\n\\set VERBOSITY terse\n\\set ON_ERROR_STOP 1\n-- Set scheduler restart time to a lower value to make the test a\n-- little faster.\nALTER SYSTEM SET timescaledb.bgw_scheduler_restart_time TO '10s';\nALTER SYSTEM SET timescaledb.debug_bgw_scheduler_exit_status TO 1;\nSELECT pg_reload_conf();\n pg_reload_conf \n----------------\n t\n\n-- Reconnect and check the restart time to make sure that it is\n-- correct.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSHOW timescaledb.bgw_scheduler_restart_time;\n timescaledb.bgw_scheduler_restart_time \n----------------------------------------\n 10s\n\n-- Launcher is running, so we need to restart it for the scheduler\n-- restart time to take effect.\nSELECT datname, application_name FROM tsdb_bgw;\n datname |            application_name            \n---------+----------------------------------------\n         | TimescaleDB Background Worker Launcher\n\nCALL ts_terminate_launcher();\n-- It will restart automatically, but we wait for it to start.\nCALL wait_for_some_started(1, 50, '%Launcher%');\n-- Make sure that the new value of the scheduler restart time is\n-- correct or the rest of the tests will fail.\nSHOW timescaledb.bgw_scheduler_restart_time;\n timescaledb.bgw_scheduler_restart_time \n----------------------------------------\n 10s\n\n-- Verify that launcher is running. If it is not, the rest of the test\n-- will fail.\nSELECT datname, application_name FROM tsdb_bgw;\n         datname          |            application_name             \n--------------------------+-----------------------------------------\n db_bgw_scheduler_restart | TimescaleDB Background Worker Scheduler\n                          | TimescaleDB Background Worker Launcher\n\n-- Now we can start the background workers.\nSELECT _timescaledb_functions.start_background_workers();\n start_background_workers \n--------------------------\n t\n\n-- They should start immediately, but let's wait for them to start.\nCALL wait_for_some_started(1, 50, '%Scheduler%');\n-- Check that the schedulers are running. If they are not, the rest of\n-- the test is meaningless.\nSELECT datname, application_name FROM tsdb_bgw;\n         datname          |            application_name             \n--------------------------+-----------------------------------------\n db_bgw_scheduler_restart | TimescaleDB Background Worker Scheduler\n                          | TimescaleDB Background Worker Launcher\n\n-- Kill the schedulers and check that they restart.\nSELECT pg_terminate_backend(pid) FROM tsdb_bgw\n WHERE datname = :'TEST_DBNAME' AND backend_type LIKE '%Scheduler%';\n pg_terminate_backend \n----------------------\n t\n\n-- Wait for scheduler to exit, they should exit immediately.\nCALL wait_for_all_stopped(1, 50, '%Scheduler%');\n-- Check that the schedulers really exited.\nSELECT datname, application_name FROM tsdb_bgw;\n datname |            application_name            \n---------+----------------------------------------\n         | TimescaleDB Background Worker Launcher\n\n-- Wait for scheduler to restart.\nCALL wait_for_some_started(10, 100, '%Scheduler%');\n-- Make sure that the launcher and schedulers are running. Otherwise\n-- the test will fail.\nSELECT datname, application_name FROM tsdb_bgw;\n         datname          |            application_name             \n--------------------------+-----------------------------------------\n db_bgw_scheduler_restart | TimescaleDB Background Worker Scheduler\n                          | TimescaleDB Background Worker Launcher\n\n-- Now, we had a previous bug where killing the launcher at this point\n-- would leave the schedulers running (because the launcher did not\n-- have a handle for them) and when launcher is restarting, it would\n-- start more schedulers, leaving two schedulers per database.\n-- Get the PID of the launcher to be able to compare it after the restart\nSELECT pid AS orig_pid FROM tsdb_bgw WHERE backend_type LIKE '%Launcher%' \\gset\n-- Kill the launcher. Since there are new restarted schedulers, the\n-- handle could not be used to terminate them, and they would be left\n-- running.\nCALL ts_terminate_launcher();\n-- Launcher will restart immediately, but we wait one second to give\n-- it a chance to start.\nCALL wait_for_some_started(1, 50, '%Launcher%');\n-- Check that the launcher is running and that there are exactly one\n-- scheduler per database. Here the old schedulers are killed, so it\n-- will be schedulers with a different PID than the ones before the\n-- launcher was killed, but we are not showing this here.\nSELECT (pid != :orig_pid) AS different_pid,\n       datname,\n       application_name\n  FROM tsdb_bgw;\n different_pid |         datname          |            application_name             \n---------------+--------------------------+-----------------------------------------\n t             | db_bgw_scheduler_restart | TimescaleDB Background Worker Scheduler\n t             |                          | TimescaleDB Background Worker Launcher\n\nALTER SYSTEM RESET timescaledb.bgw_scheduler_restart_time;\nALTER SYSTEM RESET timescaledb.debug_bgw_scheduler_exit_status;\nSELECT pg_reload_conf();\n pg_reload_conf \n----------------\n t\n\nSELECT _timescaledb_functions.stop_background_workers();\n stop_background_workers \n-------------------------\n t\n\n-- We need to restart the launcher as well to read the reset\n-- configuration or it will affect other tests.\nCALL ts_terminate_launcher();\n"
  },
  {
    "path": "tsl/test/expected/bgw_security.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set ROLE_ADMIN :TEST_DBNAME _admin\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE ROLE :ROLE_ADMIN LOGIN;\nGRANT :ROLE_ADMIN TO :ROLE_DEFAULT_PERM_USER;\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE TABLE custom_log (ts integer, msg text);\nGRANT ALL ON custom_log TO PUBLIC;\nCREATE PROCEDURE custom_job(integer, jsonb) AS $$\n  INSERT INTO custom_log values($1, 'custom_job');\n$$ LANGUAGE SQL;\nSET ROLE :ROLE_ADMIN;\nSELECT add_job('custom_job', '1h') AS job_id \\gset\nRESET ROLE;\nSELECT id, proc_name, owner FROM _timescaledb_catalog.bgw_job WHERE id = :job_id;\n  id  | proc_name  |         owner         \n------+------------+-----------------------\n 1000 | custom_job | db_bgw_security_admin\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n-- We should fail to execute and delete the job since we do not own it\n-- or belong to the group that owns it.\n\\set ON_ERROR_STOP 0\nCALL run_job(:job_id);\nERROR:  insufficient permissions to run job 1000\nSELECT delete_job(:job_id);\nERROR:  insufficient permissions to delete job owned by \"db_bgw_security_admin\"\n\\set ON_ERROR_STOP 1\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n-- This should succeed since the role belongs to the job owner group.\nCALL run_job(:job_id);\n-- This should succeed since we belong to the owners role.\nSELECT delete_job(:job_id);\n delete_job \n------------\n \n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nDROP ROLE :ROLE_ADMIN;\n"
  },
  {
    "path": "tsl/test/expected/bgw_telemetry.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- Check that we can use run_job() with the telemetry job, so first\n-- locate the job id for it (should be 1, but who knows, and it is not\n-- important for this test).\nSELECT id AS job_id FROM _timescaledb_catalog.bgw_job\n WHERE proc_schema = '_timescaledb_functions'\n   AND proc_name = 'policy_telemetry' \\gset\n-- It should be possible to run it twice and running it should change\n-- the last_finish time. Since job_stats can be empty to start with,\n-- we run it once first to populate job_stats.\nCALL run_job(:job_id);\nSELECT last_finish AS last_finish\n  FROM _timescaledb_internal.bgw_job_stat\n WHERE job_id = :job_id \\gset\nSELECT pg_sleep(1);\n pg_sleep \n----------\n \n\nCALL run_job(:job_id);\nSELECT last_finish > :'last_finish' AS job_executed,\n       last_run_success\n  FROM _timescaledb_internal.bgw_job_stat\n WHERE job_id = :job_id;\n job_executed | last_run_success \n--------------+------------------\n t            | f\n\n-- Running it as the default user should fail since they do not own\n-- the job. This should be the case also for the telemetry job, which\n-- is a little special.\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n\\set ON_ERROR_STOP 0\nCALL run_job(:job_id);\nERROR:  insufficient permissions to run job 1\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "tsl/test/expected/cagg-15.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- initialize the bgw mock state to prevent the materialization workers from running\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION ts_bgw_params_create() RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION test.continuous_aggs_find_view(cagg REGCLASS) RETURNS VOID\nAS :TSL_MODULE_PATHNAME, 'ts_test_continuous_agg_find_by_view_name' LANGUAGE C;\n\\set WAIT_ON_JOB 0\n\\set IMMEDIATELY_SET_UNTIL 1\n\\set WAIT_FOR_OTHER_TO_ADVANCE 2\n-- remove any default jobs, e.g., telemetry so bgw_job isn't polluted\nDELETE FROM _timescaledb_catalog.bgw_job;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT * FROM _timescaledb_catalog.bgw_job;\n id | application_name | schedule_interval | max_runtime | max_retries | retry_period | proc_schema | proc_name | owner | scheduled | fixed_schedule | initial_start | hypertable_id | config | check_schema | check_name | timezone \n----+------------------+-------------------+-------------+-------------+--------------+-------------+-----------+-------+-----------+----------------+---------------+---------------+--------+--------------+------------+----------\n\n--TEST1 ---\n--basic test with count\ncreate table foo (a integer, b integer, c integer);\nselect table_name from create_hypertable('foo', 'a', chunk_time_interval=> 10);\n table_name \n------------\n foo\n\ninsert into foo values( 3 , 16 , 20);\ninsert into foo values( 1 , 10 , 20);\ninsert into foo values( 1 , 11 , 20);\ninsert into foo values( 1 , 12 , 20);\ninsert into foo values( 1 , 13 , 20);\ninsert into foo values( 1 , 14 , 20);\ninsert into foo values( 2 , 14 , 20);\ninsert into foo values( 2 , 15 , 20);\ninsert into foo values( 2 , 16 , 20);\nCREATE OR REPLACE FUNCTION integer_now_foo() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(a), 0) FROM foo $$;\nSELECT set_integer_now_func('foo', 'integer_now_foo');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW mat_m1(a, countb)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect a, count(b)\nfrom foo\ngroup by time_bucket(1, a), a WITH NO DATA;\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, 2::integer, '12 h'::interval) AS job_id\n\\gset\nSELECT * FROM _timescaledb_catalog.bgw_job;\n  id  |              application_name              | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema       |              proc_name              |       owner       | scheduled | fixed_schedule | initial_start | hypertable_id |                             config                              |      check_schema      |                check_name                 | timezone \n------+--------------------------------------------+-------------------+-------------+-------------+--------------+------------------------+-------------------------------------+-------------------+-----------+----------------+---------------+---------------+-----------------------------------------------------------------+------------------------+-------------------------------------------+----------\n 1000 | Refresh Continuous Aggregate Policy [1000] | @ 12 hours        | @ 0         |          -1 | @ 12 hours   | _timescaledb_functions | policy_refresh_continuous_aggregate | default_perm_user | t         | f              |               |             2 | {\"end_offset\": 2, \"start_offset\": null, \"mat_hypertable_id\": 2} | _timescaledb_functions | policy_refresh_continuous_aggregate_check | \n\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\ninsert into :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect a, count(b),\ntime_bucket(1, a)\nfrom foo\ngroup by time_bucket(1, a) , a ;\nselect * from mat_m1 order by a ;\n a | countb \n---+--------\n 1 |      5\n 2 |      3\n 3 |      1\n\n--check triggers on user hypertable --\nSET ROLE :ROLE_SUPERUSER;\nselect tgname, tgtype, tgenabled , relname from pg_trigger, pg_class\nwhere tgrelid = pg_class.oid and pg_class.relname like 'foo'\norder by tgname;\n tgname | tgtype | tgenabled | relname \n--------+--------+-----------+---------\n\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n-- TEST2 ---\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_2_2_chunk\nSHOW enable_partitionwise_aggregate;\n enable_partitionwise_aggregate \n--------------------------------\n off\n\nSET enable_partitionwise_aggregate = on;\nSELECT * FROM _timescaledb_catalog.bgw_job;\n id | application_name | schedule_interval | max_runtime | max_retries | retry_period | proc_schema | proc_name | owner | scheduled | fixed_schedule | initial_start | hypertable_id | config | check_schema | check_name | timezone \n----+------------------+-------------------+-------------+-------------+--------------+-------------+-----------+-------+-----------+----------------+---------------+---------------+--------+--------------+------------+----------\n\nCREATE TABLE conditions (\n      timec        TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\ninsert into conditions values ( '2010-01-01 09:00:00-08', 'SFO', 55, 45);\ninsert into conditions values ( '2010-01-02 09:00:00-08', 'por', 100, 100);\ninsert into conditions values ( '2010-01-02 09:00:00-08', 'SFO', 65, 45);\ninsert into conditions values ( '2010-01-02 09:00:00-08', 'NYC', 65, 45);\ninsert into conditions values ( '2018-11-01 09:00:00-08', 'NYC', 45, 35);\ninsert into conditions values ( '2018-11-02 09:00:00-08', 'NYC', 35, 15);\nCREATE MATERIALIZED VIEW mat_m1( timec, minl, sumt , sumh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1day', timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by time_bucket('1day', timec) WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\n-- Materialized hypertable for mat_m1 should not be visible in the\n-- hypertables view:\nSELECT hypertable_schema, hypertable_name\nFROM timescaledb_information.hypertables ORDER BY 1,2;\n hypertable_schema | hypertable_name \n-------------------+-----------------\n public            | conditions\n public            | foo\n\nSET ROLE :ROLE_SUPERUSER;\ninsert into  :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect\n time_bucket('1day', timec), min(location), sum(temperature), sum(humidity)\nfrom conditions\ngroup by time_bucket('1day', timec) ;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--should have same results --\nselect timec, minl, sumt, sumh\nfrom mat_m1\norder by timec;\n            timec             | minl | sumt | sumh \n------------------------------+------+------+------\n Thu Dec 31 16:00:00 2009 PST | SFO  |   55 |   45\n Fri Jan 01 16:00:00 2010 PST | NYC  |  230 |  190\n Wed Oct 31 17:00:00 2018 PDT | NYC  |   45 |   35\n Thu Nov 01 17:00:00 2018 PDT | NYC  |   35 |   15\n\nselect time_bucket('1day', timec), min(location), sum(temperature), sum(humidity)\nfrom conditions\ngroup by time_bucket('1day', timec)\norder by 1;\n         time_bucket          | min | sum | sum \n------------------------------+-----+-----+-----\n Thu Dec 31 16:00:00 2009 PST | SFO |  55 |  45\n Fri Jan 01 16:00:00 2010 PST | NYC | 230 | 190\n Wed Oct 31 17:00:00 2018 PDT | NYC |  45 |  35\n Thu Nov 01 17:00:00 2018 PDT | NYC |  35 |  15\n\nSET enable_partitionwise_aggregate = off;\n-- TEST3 --\n-- drop on table conditions should cascade to materialized mat_v1\ndrop table conditions cascade;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to 2 other objects\nCREATE TABLE conditions (\n      timec        TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\ninsert into conditions values ( '2010-01-01 09:00:00-08', 'SFO', 55, 45);\ninsert into conditions values ( '2010-01-02 09:00:00-08', 'por', 100, 100);\ninsert into conditions values ( '2010-01-02 09:00:00-08', 'NYC', 65, 45);\ninsert into conditions values ( '2010-01-02 09:00:00-08', 'SFO', 65, 45);\ninsert into conditions values ( '2010-01-03 09:00:00-08', 'NYC', 45, 55);\ninsert into conditions values ( '2010-01-05 09:00:00-08', 'SFO', 75, 100);\ninsert into conditions values ( '2018-11-01 09:00:00-08', 'NYC', 45, 35);\ninsert into conditions values ( '2018-11-02 09:00:00-08', 'NYC', 35, 15);\ninsert into conditions values ( '2018-11-03 09:00:00-08', 'NYC', 35, 25);\nCREATE MATERIALIZED VIEW mat_m1( timec, minl, sumth, stddevh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec)  WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\nSET ROLE :ROLE_SUPERUSER;\ninsert into  :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect\n time_bucket('1week', timec), min(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec) ;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--should have same results --\nselect timec, minl, sumth, stddevh\nfrom mat_m1\norder by timec;\n            timec             | minl | sumth |     stddevh      \n------------------------------+------+-------+------------------\n Sun Dec 27 16:00:00 2009 PST | NYC  |   620 | 23.8746727726266\n Sun Jan 03 16:00:00 2010 PST | SFO  |   175 |                 \n Sun Oct 28 17:00:00 2018 PDT | NYC  |   190 |               10\n\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+ sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec)\norder by time_bucket('1week', timec);\n         time_bucket          | min | ?column? |      stddev      \n------------------------------+-----+----------+------------------\n Sun Dec 27 16:00:00 2009 PST | NYC |      620 | 23.8746727726266\n Sun Jan 03 16:00:00 2010 PST | SFO |      175 |                 \n Sun Oct 28 17:00:00 2018 PDT | NYC |      190 |               10\n\n-- TEST4 --\n--materialized view with group by clause + expression in SELECT\n-- use previous data from conditions\n--drop only the view.\n-- apply where clause on result of mat_m1 --\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\nCREATE MATERIALIZED VIEW mat_m1( timec, minl, sumth, stddevh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\nwhere location = 'NYC'\ngroup by time_bucket('1week', timec)\n WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\nSET ROLE :ROLE_SUPERUSER;\ninsert into  :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect\n time_bucket('1week', timec), min(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\nwhere location = 'NYC'\ngroup by time_bucket('1week', timec) ;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--should have same results --\nselect timec, minl, sumth, stddevh\nfrom mat_m1\nwhere stddevh is not null\norder by timec;\n            timec             | minl | sumth |     stddevh      \n------------------------------+------+-------+------------------\n Sun Dec 27 16:00:00 2009 PST | NYC  |   210 | 7.07106781186548\n Sun Oct 28 17:00:00 2018 PDT | NYC  |   190 |               10\n\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+ sum(humidity), stddev(humidity)\nfrom conditions\nwhere location = 'NYC'\ngroup by time_bucket('1week', timec)\norder by time_bucket('1week', timec);\n         time_bucket          | min | ?column? |      stddev      \n------------------------------+-----+----------+------------------\n Sun Dec 27 16:00:00 2009 PST | NYC |      210 | 7.07106781186548\n Sun Oct 28 17:00:00 2018 PDT | NYC |      190 |               10\n\n-- TEST5 --\n---------test with having clause ----------------------\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\ncreate materialized view mat_m1( timec, minl, sumth, stddevh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec)\nhaving stddev(humidity) is not null WITH NO DATA;\n;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\nSET ROLE :ROLE_SUPERUSER;\ninsert into  :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect\n time_bucket('1week', timec), min(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec)\nhaving stddev(humidity) is not null;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n-- should have same results --\nselect * from mat_m1\norder by sumth;\n            timec             | minl | sumth |     stddevh      \n------------------------------+------+-------+------------------\n Sun Oct 28 17:00:00 2018 PDT | NYC  |   190 |               10\n Sun Dec 27 16:00:00 2009 PST | NYC  |   620 | 23.8746727726266\n\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec)\nhaving stddev(humidity) is not null\norder by sum(temperature)+sum(humidity);\n         time_bucket          | min | ?column? |      stddev      \n------------------------------+-----+----------+------------------\n Sun Oct 28 17:00:00 2018 PDT | NYC |      190 |               10\n Sun Dec 27 16:00:00 2009 PST | NYC |      620 | 23.8746727726266\n\n-- TEST6 --\n--group by with more than 1 group column\n-- having clause with a mix of columns from select list + others\ndrop table conditions cascade;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to 2 other objects\nCREATE TABLE conditions (\n      timec       TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        numeric NULL,\n      highp       numeric null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\ninsert into conditions\nselect generate_series('2018-12-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'POR', 55, 75, 40, 70;\ninsert into conditions\nselect generate_series('2018-11-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'NYC', 35, 45, 50, 40;\ninsert into conditions\nselect generate_series('2018-11-01 00:00'::timestamp, '2018-12-15 00:00'::timestamp, '1 day'), 'LA', 73, 55, 71, 28;\n--naming with AS clauses\nCREATE MATERIALIZED VIEW mat_naming\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec) as bucket, location as loc, sum(temperature)+sum(humidity) as sumth, stddev(humidity)\nfrom conditions\ngroup by bucket, loc\nhaving min(location) >= 'NYC' and avg(temperature) > 20 WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_naming'\n\\gset\nselect attnum , attname from pg_attribute\nwhere attnum > 0 and attrelid =\n(Select oid from pg_class where relname like :'MAT_TABLE_NAME')\norder by attnum, attname;\n attnum | attname \n--------+---------\n      1 | bucket\n      2 | loc\n      3 | sumth\n      4 | stddev\n\nDROP MATERIALIZED VIEW mat_naming;\n--naming with default names\nCREATE MATERIALIZED VIEW mat_naming\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec), location, sum(temperature)+sum(humidity) as sumth, stddev(humidity)\nfrom conditions\ngroup by 1,2\nhaving min(location) >= 'NYC' and avg(temperature) > 20 WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_naming'\n\\gset\nselect attnum , attname from pg_attribute\nwhere attnum > 0 and attrelid =\n(Select oid from pg_class where relname like :'MAT_TABLE_NAME')\norder by attnum, attname;\n attnum |   attname   \n--------+-------------\n      1 | time_bucket\n      2 | location\n      3 | sumth\n      4 | stddev\n\nDROP MATERIALIZED VIEW mat_naming;\n--naming with view col names\nCREATE MATERIALIZED VIEW mat_naming(bucket, loc, sum_t_h, stdd)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec), location, sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by 1,2\nhaving min(location) >= 'NYC' and avg(temperature) > 20 WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_naming'\n\\gset\nselect attnum , attname from pg_attribute\nwhere attnum > 0 and attrelid =\n(Select oid from pg_class where relname like :'MAT_TABLE_NAME')\norder by attnum, attname;\n attnum | attname \n--------+---------\n      1 | bucket\n      2 | loc\n      3 | sum_t_h\n      4 | stdd\n\nDROP MATERIALIZED VIEW mat_naming;\nCREATE MATERIALIZED VIEW mat_m1(timec, minl, sumth, stddevh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by  time_bucket('1week', timec)\nhaving min(location) >= 'NYC' and avg(temperature) > 20 WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\nselect attnum , attname from pg_attribute\nwhere attnum > 0 and attrelid =\n(Select oid from pg_class where relname like :'MAT_TABLE_NAME')\norder by attnum, attname;\n attnum | attname \n--------+---------\n      1 | timec\n      2 | minl\n      3 | sumth\n      4 | stddevh\n\nSET ROLE :ROLE_SUPERUSER;\ninsert into  :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect\n time_bucket('1week', timec), min(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec)\nhaving min(location) >= 'NYC' and avg(temperature) > 20;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--should have same results --\nselect timec, minl, sumth, stddevh\nfrom mat_m1\norder by timec, minl;\n            timec             | minl | sumth |     stddevh      \n------------------------------+------+-------+------------------\n Sun Dec 16 16:00:00 2018 PST | NYC  |  1470 | 15.5662356498831\n Sun Dec 23 16:00:00 2018 PST | NYC  |  1470 | 15.5662356498831\n Sun Dec 30 16:00:00 2018 PST | NYC  |   210 | 21.2132034355964\n\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by  time_bucket('1week', timec)\nhaving min(location) >= 'NYC' and avg(temperature) > 20 and avg(lowp) > 10\norder by time_bucket('1week', timec), min(location);\n         time_bucket          | min | ?column? |      stddev      \n------------------------------+-----+----------+------------------\n Sun Dec 16 16:00:00 2018 PST | NYC |     1470 | 15.5662356498831\n Sun Dec 23 16:00:00 2018 PST | NYC |     1470 | 15.5662356498831\n Sun Dec 30 16:00:00 2018 PST | NYC |      210 | 21.2132034355964\n\n--check view defintion in information views\nselect view_name, view_definition from timescaledb_information.continuous_aggregates\nwhere view_name::text like 'mat_m1';\n view_name |                                                 view_definition                                                 \n-----------+-----------------------------------------------------------------------------------------------------------------\n mat_m1    |  SELECT time_bucket('@ 7 days'::interval, conditions.timec) AS timec,                                          +\n           |     min(conditions.location) AS minl,                                                                          +\n           |     (sum(conditions.temperature) + sum(conditions.humidity)) AS sumth,                                         +\n           |     stddev(conditions.humidity) AS stddevh                                                                     +\n           |    FROM conditions                                                                                             +\n           |   GROUP BY (time_bucket('@ 7 days'::interval, conditions.timec))                                               +\n           |  HAVING ((min(conditions.location) >= 'NYC'::text) AND (avg(conditions.temperature) > (20)::double precision));\n\n--TEST6 -- select from internal view\nSET ROLE :ROLE_SUPERUSER;\ninsert into :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect * from :\"PART_VIEW_SCHEMA\".:\"PART_VIEW_NAME\";\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--lets drop the view and check\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_13_24_chunk\ndrop table conditions;\nCREATE TABLE conditions (\n      timec       TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\ninsert into conditions\nselect generate_series('2018-12-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'POR', 55, 75, 40, 70, NULL;\ninsert into conditions\nselect generate_series('2018-11-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'NYC', 35, 45, 50, 40, NULL;\ninsert into conditions\nselect generate_series('2018-11-01 00:00'::timestamp, '2018-12-15 00:00'::timestamp, '1 day'), 'LA', 73, 55, NULL, 28, NULL;\nSELECT\n  $$\n  select time_bucket('1week', timec) ,\n  min(location) as col1, sum(temperature)+sum(humidity) as col2, stddev(humidity) as col3, min(allnull) as col4\n  from conditions\n  group by  time_bucket('1week', timec)\n  having min(location) >= 'NYC' and avg(temperature) > 20\n  $$ AS \"QUERY\"\n\\gset\n\\set ECHO errors\npsql:include/cont_agg_equal.sql:8: NOTICE:  materialized view \"mat_test\" does not exist, skipping\n                           ?column?                            | count \n---------------------------------------------------------------+-------\n Number of rows different between view and original (expect 0) |     0\n\nSELECT\n  $$\n  select time_bucket('1week', timec), location,\n  sum(temperature)+sum(humidity) as col2, stddev(humidity) as col3, min(allnull) as col4\n  from conditions\n  group by location, time_bucket('1week', timec)\n  $$ AS \"QUERY\"\n\\gset\n\\set ECHO errors\npsql:include/cont_agg_equal.sql:8: NOTICE:  drop cascades to table _timescaledb_internal._hyper_15_34_chunk\n                           ?column?                            | count \n---------------------------------------------------------------+-------\n Number of rows different between view and original (expect 0) |     0\n\n--TEST7 -- drop tests for view and hypertable\n--DROP tests\n\\set ON_ERROR_STOP 0\nSELECT  h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\",\n       direct_view_name as \"DIR_VIEW_NAME\",\n       direct_view_schema as \"DIR_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_test'\n\\gset\nDROP TABLE :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\";\nERROR:  cannot drop table _timescaledb_internal._materialized_hypertable_16 because other objects depend on it\nDROP VIEW :\"PART_VIEW_SCHEMA\".:\"PART_VIEW_NAME\";\nERROR:  cannot drop the partial/direct view because it is required by a continuous aggregate\nDROP VIEW :\"DIR_VIEW_SCHEMA\".:\"DIR_VIEW_NAME\";\nERROR:  cannot drop the partial/direct view because it is required by a continuous aggregate\n\\set ON_ERROR_STOP 1\n--catalog entry still there;\nSELECT count(*)\nFROM _timescaledb_catalog.continuous_agg ca\nWHERE user_view_name = 'mat_test';\n count \n-------\n     1\n\n--mat table, user_view, direct view and partial view all there\nselect count(*) from pg_class where relname = :'PART_VIEW_NAME';\n count \n-------\n     1\n\nselect count(*) from pg_class where relname = :'MAT_TABLE_NAME';\n count \n-------\n     1\n\nselect count(*) from pg_class where relname = :'DIR_VIEW_NAME';\n count \n-------\n     1\n\nselect count(*) from pg_class where relname = 'mat_test';\n count \n-------\n     1\n\nDROP MATERIALIZED VIEW mat_test;\nNOTICE:  drop cascades to 2 other objects\n--catalog entry should be gone\nSELECT count(*)\nFROM _timescaledb_catalog.continuous_agg ca\nWHERE user_view_name = 'mat_test';\n count \n-------\n     0\n\n--mat table, user_view, direct view and partial view all gone\nselect count(*) from pg_class where relname = :'PART_VIEW_NAME';\n count \n-------\n     0\n\nselect count(*) from pg_class where relname = :'MAT_TABLE_NAME';\n count \n-------\n     0\n\nselect count(*) from pg_class where relname = :'DIR_VIEW_NAME';\n count \n-------\n     0\n\nselect count(*) from pg_class where relname = 'mat_test';\n count \n-------\n     0\n\n--test dropping raw table\nDROP TABLE conditions;\nCREATE TABLE conditions (\n      timec       TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\n--no data in hyper table on purpose so that CASCADE is not required because of chunks\nCREATE MATERIALIZED VIEW mat_drop_test(timec, minl, sumt , sumh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1day', timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by time_bucket('1day', timec) WITH NO DATA;\n\\set ON_ERROR_STOP 0\nDROP TABLE conditions;\nERROR:  cannot drop table conditions because other objects depend on it\n\\set ON_ERROR_STOP 1\n--insert data now\ninsert into conditions\nselect generate_series('2018-12-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'POR', 55, 75, 40, 70, NULL;\ninsert into conditions\nselect generate_series('2018-11-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'NYC', 35, 45, 50, 40, NULL;\ninsert into conditions\nselect generate_series('2018-11-01 00:00'::timestamp, '2018-12-15 00:00'::timestamp, '1 day'), 'LA', 73, 55, NULL, 28, NULL;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_drop_test'\n\\gset\nSET client_min_messages TO NOTICE;\nCALL refresh_continuous_aggregate('mat_drop_test', NULL, NULL);\n--force invalidation\ninsert into conditions\nselect generate_series('2017-11-01 00:00'::timestamp, '2017-12-15 00:00'::timestamp, '1 day'), 'LA', 73, 55, NULL, 28, NULL;\nselect count(*) from _timescaledb_catalog.continuous_aggs_invalidation_threshold;\n count \n-------\n     1\n\nselect count(*) from _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log;\n count \n-------\n     8\n\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to 2 other objects\n--catalog entry should be gone\nSELECT count(*)\nFROM _timescaledb_catalog.continuous_agg ca\nWHERE user_view_name = 'mat_drop_test';\n count \n-------\n     0\n\nselect count(*) from _timescaledb_catalog.continuous_aggs_invalidation_threshold;\n count \n-------\n     0\n\nselect count(*) from _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log;\n count \n-------\n     0\n\nselect count(*) from _timescaledb_catalog.continuous_aggs_materialization_invalidation_log;\n count \n-------\n     0\n\nSELECT * FROM _timescaledb_catalog.bgw_job;\n id | application_name | schedule_interval | max_runtime | max_retries | retry_period | proc_schema | proc_name | owner | scheduled | fixed_schedule | initial_start | hypertable_id | config | check_schema | check_name | timezone \n----+------------------+-------------------+-------------+-------------+--------------+-------------+-----------+-------+-----------+----------------+---------------+---------------+--------+--------------+------------+----------\n\n--mat table, user_view, and partial view all gone\nselect count(*) from pg_class where relname = :'PART_VIEW_NAME';\n count \n-------\n     0\n\nselect count(*) from pg_class where relname = :'MAT_TABLE_NAME';\n count \n-------\n     0\n\nselect count(*) from pg_class where relname = 'mat_drop_test';\n count \n-------\n     0\n\n--TEST With options\nCREATE TABLE conditions (\n      timec       TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\nCREATE MATERIALIZED VIEW mat_with_test(timec, minl, sumt , sumh)\nWITH (timescaledb.continuous,\n      timescaledb.materialized_only=true)\nas\nselect time_bucket('1day', timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by time_bucket('1day', timec), location, humidity, temperature WITH NO DATA;\nSELECT add_continuous_aggregate_policy('mat_with_test', NULL, '5 h'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1001\n\nSELECT alter_job(id, schedule_interval => '1h') FROM _timescaledb_catalog.bgw_job;\n                                                                                                                         alter_job                                                                                                                          \n------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1001,\"@ 1 hour\",\"@ 0\",-1,\"@ 12 hours\",t,\"{\"\"end_offset\"\": \"\"@ 5 hours\"\", \"\"start_offset\"\": null, \"\"mat_hypertable_id\"\": 20}\",-infinity,_timescaledb_functions.policy_refresh_continuous_aggregate_check,f,,,\"Refresh Continuous Aggregate Policy [1001]\")\n\nSELECT schedule_interval FROM _timescaledb_catalog.bgw_job;\n schedule_interval \n-------------------\n @ 1 hour\n\nSELECT alter_job(id, schedule_interval => '2h') FROM _timescaledb_catalog.bgw_job;\n                                                                                                                          alter_job                                                                                                                          \n-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1001,\"@ 2 hours\",\"@ 0\",-1,\"@ 12 hours\",t,\"{\"\"end_offset\"\": \"\"@ 5 hours\"\", \"\"start_offset\"\": null, \"\"mat_hypertable_id\"\": 20}\",-infinity,_timescaledb_functions.policy_refresh_continuous_aggregate_check,f,,,\"Refresh Continuous Aggregate Policy [1001]\")\n\nSELECT schedule_interval FROM _timescaledb_catalog.bgw_job;\n schedule_interval \n-------------------\n @ 2 hours\n\nselect indexname, indexdef from pg_indexes where tablename =\n(SELECT h.table_name\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_with_test')\norder by indexname;\n               indexname               |                                                             indexdef                                                             \n---------------------------------------+----------------------------------------------------------------------------------------------------------------------------------\n _materialized_hypertable_20_timec_idx | CREATE INDEX _materialized_hypertable_20_timec_idx ON _timescaledb_internal._materialized_hypertable_20 USING btree (timec DESC)\n\nDROP MATERIALIZED VIEW mat_with_test;\n--no additional indexes\nCREATE MATERIALIZED VIEW mat_with_test(timec, minl, sumt , sumh)\nWITH (timescaledb.continuous,\n      timescaledb.materialized_only=true,\n      timescaledb.create_group_indexes=false)\nas\nselect time_bucket('1day', timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by time_bucket('1day', timec), location, humidity, temperature WITH NO DATA;\nselect indexname, indexdef from pg_indexes where tablename =\n(SELECT h.table_name\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_with_test');\n               indexname               |                                                             indexdef                                                             \n---------------------------------------+----------------------------------------------------------------------------------------------------------------------------------\n _materialized_hypertable_21_timec_idx | CREATE INDEX _materialized_hypertable_21_timec_idx ON _timescaledb_internal._materialized_hypertable_21 USING btree (timec DESC)\n\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 2 other objects\n--test WITH using a hypertable with an integer time dimension\nCREATE TABLE conditions (\n      timec       INT       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec', chunk_time_interval=> 100);\n table_name \n------------\n conditions\n\nCREATE OR REPLACE FUNCTION integer_now_conditions() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(timec), 0) FROM conditions $$;\nSELECT set_integer_now_func('conditions', 'integer_now_conditions');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW mat_with_test(timec, minl, sumt , sumh)\nWITH (timescaledb.continuous,\n      timescaledb.materialized_only=true)\nas\nselect time_bucket(100, timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by time_bucket(100, timec) WITH NO DATA;\nSELECT add_continuous_aggregate_policy('mat_with_test', NULL, 500::integer, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1002\n\nSELECT alter_job(id, schedule_interval => '2h') FROM _timescaledb_catalog.bgw_job;\n                                                                                                                     alter_job                                                                                                                     \n---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1002,\"@ 2 hours\",\"@ 0\",-1,\"@ 12 hours\",t,\"{\"\"end_offset\"\": 500, \"\"start_offset\"\": null, \"\"mat_hypertable_id\"\": 23}\",-infinity,_timescaledb_functions.policy_refresh_continuous_aggregate_check,f,,,\"Refresh Continuous Aggregate Policy [1002]\")\n\nSELECT schedule_interval FROM _timescaledb_catalog.bgw_job;\n schedule_interval \n-------------------\n @ 2 hours\n\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 2 other objects\n--test space partitions\nCREATE TABLE space_table (\n    time BIGINT,\n    dev  BIGINT,\n    data BIGINT\n);\nSELECT create_hypertable(\n    'space_table',\n    'time',\n    chunk_time_interval => 10,\n    partitioning_column => 'dev',\n    number_partitions => 3);\n     create_hypertable     \n---------------------------\n (24,public,space_table,t)\n\nCREATE OR REPLACE FUNCTION integer_now_space_table() returns BIGINT LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), BIGINT '0') FROM space_table $$;\nSELECT set_integer_now_func('space_table', 'integer_now_space_table');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW space_view\nWITH (timescaledb.continuous,\n      timescaledb.materialized_only=true)\nAS SELECT time_bucket('4', time), COUNT(data)\n   FROM space_table\n   GROUP BY 1 WITH NO DATA;\nINSERT INTO space_table VALUES\n  (0, 1, 1), (0, 2, 1), (1, 1, 1), (1, 2, 1),\n  (10, 1, 1), (10, 2, 1), (11, 1, 1), (11, 2, 1);\nSELECT  h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\",\n       direct_view_name as \"DIR_VIEW_NAME\",\n       direct_view_schema as \"DIR_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'space_view'\n\\gset\nSELECT * FROM :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\n  ORDER BY time_bucket;\n time_bucket | count \n-------------+-------\n\nCALL refresh_continuous_aggregate('space_view', NULL, NULL);\nSELECT * FROM space_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     4\n           8 |     4\n\nSELECT * FROM :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\n  ORDER BY time_bucket;\n time_bucket | count \n-------------+-------\n           0 |     4\n           8 |     4\n\nINSERT INTO space_table VALUES (3, 2, 1);\nCALL refresh_continuous_aggregate('space_view', NULL, NULL);\nSELECT * FROM space_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     5\n           8 |     4\n\nSELECT * FROM :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\n  ORDER BY time_bucket;\n time_bucket | count \n-------------+-------\n           0 |     5\n           8 |     4\n\nINSERT INTO space_table VALUES (2, 3, 1);\nCALL refresh_continuous_aggregate('space_view', NULL, NULL);\nSELECT * FROM space_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     6\n           8 |     4\n\nSELECT * FROM :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\n  ORDER BY time_bucket;\n time_bucket | count \n-------------+-------\n           0 |     6\n           8 |     4\n\nDROP TABLE space_table CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_25_60_chunk\n--\n-- TEST FINALIZEFUNC_EXTRA\n--\n-- create special aggregate to test ffunc_extra\n-- Raise warning with the actual type being passed in\nCREATE OR REPLACE FUNCTION fake_ffunc(a int8, b int, c int, d int, x anyelement)\nRETURNS anyelement AS $$\nBEGIN\n RAISE WARNING 'type % %', pg_typeof(d), pg_typeof(x);\n RETURN x;\nEND;\n$$\nLANGUAGE plpgsql;\nCREATE OR REPLACE FUNCTION fake_sfunc(a int8, b int, c int, d int, x anyelement)\nRETURNS int8 AS $$\nBEGIN\n RETURN b;\nEND; $$\nLANGUAGE plpgsql;\nCREATE AGGREGATE aggregate_to_test_ffunc_extra(int, int, int, anyelement) (\n    SFUNC = fake_sfunc,\n    STYPE = int8,\n    COMBINEFUNC = int8pl,\n    FINALFUNC = fake_ffunc,\n    PARALLEL = SAFE,\n    FINALFUNC_EXTRA\n);\nCREATE TABLE conditions (\n      timec       INT       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec', chunk_time_interval=> 100);\n table_name \n------------\n conditions\n\nCREATE OR REPLACE FUNCTION integer_now_conditions() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(timec), 0) FROM conditions $$;\nSELECT set_integer_now_func('conditions', 'integer_now_conditions');\n set_integer_now_func \n----------------------\n \n\ninsert into conditions\nselect generate_series(0, 200, 10), 'POR', 55, 75, 40, 70, NULL;\nCREATE MATERIALIZED VIEW mat_ffunc_test\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket(100, timec), aggregate_to_test_ffunc_extra(timec, 1, 3, 'test'::text)\nfrom conditions\ngroup by time_bucket(100, timec);\nNOTICE:  refreshing continuous aggregate \"mat_ffunc_test\"\nWARNING:  type integer text\nWARNING:  type integer text\nWARNING:  type integer text\nSELECT * FROM mat_ffunc_test ORDER BY time_bucket;\n time_bucket | aggregate_to_test_ffunc_extra \n-------------+-------------------------------\n           0 | \n         100 | \n         200 | \n\nDROP MATERIALIZED view mat_ffunc_test;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_27_65_chunk\nCREATE MATERIALIZED VIEW mat_ffunc_test\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket(100, timec), aggregate_to_test_ffunc_extra(timec, 4, 5, bigint '123')\nfrom conditions\ngroup by time_bucket(100, timec);\nNOTICE:  refreshing continuous aggregate \"mat_ffunc_test\"\nWARNING:  type integer bigint\nWARNING:  type integer bigint\nWARNING:  type integer bigint\nSELECT * FROM mat_ffunc_test ORDER BY time_bucket;\n time_bucket | aggregate_to_test_ffunc_extra \n-------------+-------------------------------\n           0 |                              \n         100 |                              \n         200 |                              \n\n--refresh mat view test when time_bucket is not projected --\nDROP MATERIALIZED VIEW mat_ffunc_test;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_28_66_chunk\nCREATE MATERIALIZED VIEW mat_refresh_test\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect location, max(humidity)\nfrom conditions\ngroup by time_bucket(100, timec), location WITH NO DATA;\ninsert into conditions\nselect generate_series(0, 50, 10), 'NYC', 55, 75, 40, 70, NULL;\nCALL refresh_continuous_aggregate('mat_refresh_test', NULL, NULL);\nSELECT * FROM mat_refresh_test order by 1,2 ;\n location | max \n----------+-----\n NYC      |  75\n POR      |  75\n POR      |  75\n POR      |  75\n\n-- test for bug when group by is not in project list\nCREATE MATERIALIZED VIEW conditions_grpby_view with (timescaledb.continuous, timescaledb.materialized_only=false) as\nselect time_bucket(100, timec),  sum(humidity)\nfrom conditions\ngroup by time_bucket(100, timec), location;\nNOTICE:  refreshing continuous aggregate \"conditions_grpby_view\"\nselect * from conditions_grpby_view order by 1, 2;\n time_bucket | sum \n-------------+-----\n           0 | 450\n           0 | 750\n         100 | 750\n         200 |  75\n\nCREATE MATERIALIZED VIEW conditions_grpby_view2 with (timescaledb.continuous, timescaledb.materialized_only=false) as\nselect time_bucket(100, timec), sum(humidity)\nfrom conditions\ngroup by time_bucket(100, timec), location\nhaving avg(temperature) > 0;\nNOTICE:  refreshing continuous aggregate \"conditions_grpby_view2\"\nselect * from conditions_grpby_view2 order by 1, 2;\n time_bucket | sum \n-------------+-----\n           0 | 450\n           0 | 750\n         100 | 750\n         200 |  75\n\n-- Test internal functions for continuous aggregates\nSELECT test.continuous_aggs_find_view('mat_refresh_test');\n continuous_aggs_find_view \n---------------------------\n \n\n-- Test pseudotype/enum handling\nCREATE TYPE status_enum AS ENUM (\n  'red',\n  'yellow',\n  'green'\n);\nCREATE TABLE cagg_types (\n  time TIMESTAMPTZ NOT NULL,\n  status status_enum,\n  names NAME[],\n  floats FLOAT[]\n);\nSELECT\n  table_name\nFROM\n  create_hypertable('cagg_types', 'time');\n table_name \n------------\n cagg_types\n\nINSERT INTO cagg_types\nSELECT\n  '2000-01-01',\n  'yellow',\n  '{foo,bar,baz}',\n  '{1,2.5,3}';\nCREATE MATERIALIZED VIEW mat_types WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  time_bucket('1d', time),\n  min(status) AS status,\n  max(names) AS names,\n  min(floats) AS floats\nFROM\n  cagg_types\nGROUP BY\n  1;\nNOTICE:  refreshing continuous aggregate \"mat_types\"\nCALL refresh_continuous_aggregate('mat_types',NULL,NULL);\nNOTICE:  continuous aggregate \"mat_types\" is already up-to-date\nSELECT * FROM mat_types;\n         time_bucket          | status |     names     |  floats   \n------------------------------+--------+---------------+-----------\n Fri Dec 31 16:00:00 1999 PST | yellow | {foo,bar,baz} | {1,2.5,3}\n\n-------------------------------------------------------------------------------------\n-- Test issue #2616 where cagg view contains an experssion with several aggregates in\nCREATE TABLE water_consumption\n(\n    sensor_id   integer      NOT NULL,\n    timestamp   timestamp(0) NOT NULL,\n    water_index integer\n);\nSELECT create_hypertable('water_consumption', 'timestamp', 'sensor_id', 2);\nWARNING:  column type \"timestamp without time zone\" used for \"timestamp\" does not follow best practices\n        create_hypertable        \n---------------------------------\n (34,public,water_consumption,t)\n\nINSERT INTO public.water_consumption (sensor_id, timestamp, water_index) VALUES\n  (1, '2010-11-03 09:42:30', 1030),\n  (1, '2010-11-03 09:42:40', 1032),\n  (1, '2010-11-03 09:42:50', 1035),\n  (1, '2010-11-03 09:43:30', 1040),\n  (1, '2010-11-03 09:43:40', 1045),\n  (1, '2010-11-03 09:43:50', 1050),\n  (1, '2010-11-03 09:44:30', 1052),\n  (1, '2010-11-03 09:44:40', 1057),\n  (1, '2010-11-03 09:44:50', 1060),\n  (1, '2010-11-03 09:45:30', 1063),\n  (1, '2010-11-03 09:45:40', 1067),\n  (1, '2010-11-03 09:45:50', 1070);\n-- The test with the view originally reported in the issue.\nCREATE MATERIALIZED VIEW water_consumption_aggregation_minute\n            WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE)\nAS\nSELECT sensor_id,\n       time_bucket(INTERVAL '1 minute', timestamp) + '1 minute' AS timestamp,\n       (max(water_index) - min(water_index))                    AS water_consumption\nFROM water_consumption\nGROUP BY sensor_id, time_bucket(INTERVAL '1 minute', timestamp)\nWITH NO DATA;\nCALL refresh_continuous_aggregate('water_consumption_aggregation_minute', NULL, NULL);\n-- The results of the view and the query over hypertable should be the same\nSELECT * FROM water_consumption_aggregation_minute ORDER BY water_consumption;\n sensor_id |        timestamp         | water_consumption \n-----------+--------------------------+-------------------\n         1 | Wed Nov 03 09:43:00 2010 |                 5\n         1 | Wed Nov 03 09:46:00 2010 |                 7\n         1 | Wed Nov 03 09:45:00 2010 |                 8\n         1 | Wed Nov 03 09:44:00 2010 |                10\n\nSELECT sensor_id,\n       time_bucket(INTERVAL '1 minute', timestamp) + '1 minute' AS timestamp,\n       (max(water_index) - min(water_index))                    AS water_consumption\nFROM water_consumption\nGROUP BY sensor_id, time_bucket(INTERVAL '1 minute', timestamp)\nORDER BY water_consumption;\n sensor_id |        timestamp         | water_consumption \n-----------+--------------------------+-------------------\n         1 | Wed Nov 03 09:43:00 2010 |                 5\n         1 | Wed Nov 03 09:46:00 2010 |                 7\n         1 | Wed Nov 03 09:45:00 2010 |                 8\n         1 | Wed Nov 03 09:44:00 2010 |                10\n\n-- Simplified test, where the view doesn't contain all group by clauses\nCREATE MATERIALIZED VIEW water_consumption_no_select_bucket\n            WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE)\nAS\nSELECT sensor_id,\n       (max(water_index) - min(water_index))                    AS water_consumption\nFROM water_consumption\nGROUP BY sensor_id, time_bucket(INTERVAL '1 minute', timestamp)\nWITH NO DATA;\nCALL refresh_continuous_aggregate('water_consumption_no_select_bucket', NULL, NULL);\n-- The results of the view and the query over hypertable should be the same\nSELECT * FROM water_consumption_no_select_bucket ORDER BY water_consumption;\n sensor_id | water_consumption \n-----------+-------------------\n         1 |                 5\n         1 |                 7\n         1 |                 8\n         1 |                10\n\nSELECT sensor_id,\n       (max(water_index) - min(water_index))                    AS water_consumption\nFROM water_consumption\nGROUP BY sensor_id, time_bucket(INTERVAL '1 minute', timestamp)\nORDER BY water_consumption;\n sensor_id | water_consumption \n-----------+-------------------\n         1 |                 5\n         1 |                 7\n         1 |                 8\n         1 |                10\n\n-- The test with SELECT matching GROUP BY and placing aggregate expression not the last\nCREATE MATERIALIZED VIEW water_consumption_aggregation_no_addition\n            WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE)\nAS\nSELECT sensor_id,\n       (max(water_index) - min(water_index))                    AS water_consumption,\n       time_bucket(INTERVAL '1 minute', timestamp) AS timestamp\nFROM water_consumption\nGROUP BY sensor_id, time_bucket(INTERVAL '1 minute', timestamp)\nWITH NO DATA;\nCALL refresh_continuous_aggregate('water_consumption_aggregation_no_addition', NULL, NULL);\n-- The results of the view and the query over hypertable should be the same\nSELECT * FROM water_consumption_aggregation_no_addition ORDER BY water_consumption;\n sensor_id | water_consumption |        timestamp         \n-----------+-------------------+--------------------------\n         1 |                 5 | Wed Nov 03 09:42:00 2010\n         1 |                 7 | Wed Nov 03 09:45:00 2010\n         1 |                 8 | Wed Nov 03 09:44:00 2010\n         1 |                10 | Wed Nov 03 09:43:00 2010\n\nSELECT sensor_id,\n       (max(water_index) - min(water_index))                    AS water_consumption,\n       time_bucket(INTERVAL '1 minute', timestamp) AS timestamp\nFROM water_consumption\nGROUP BY sensor_id, time_bucket(INTERVAL '1 minute', timestamp)\nORDER BY water_consumption;\n sensor_id | water_consumption |        timestamp         \n-----------+-------------------+--------------------------\n         1 |                 5 | Wed Nov 03 09:42:00 2010\n         1 |                 7 | Wed Nov 03 09:45:00 2010\n         1 |                 8 | Wed Nov 03 09:44:00 2010\n         1 |                10 | Wed Nov 03 09:43:00 2010\n\nDROP TABLE water_consumption CASCADE;\nNOTICE:  drop cascades to 6 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_35_73_chunk\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_36_74_chunk\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_37_75_chunk\n----\n--- github issue 2655 ---\ncreate table raw_data(time timestamptz, search_query text, cnt integer, cnt2 integer);\nselect create_hypertable('raw_data','time', chunk_time_interval=>'15 days'::interval);\n   create_hypertable    \n------------------------\n (38,public,raw_data,t)\n\ninsert into raw_data select '2000-01-01','Q1';\n--having has exprs that appear in select\nCREATE MATERIALIZED VIEW search_query_count_1m WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\n SELECT  search_query,count(search_query) as count,\n         time_bucket(INTERVAL '1 minute', time) AS bucket\n FROM raw_data\n WHERE search_query is not null AND LENGTH(TRIM(both from search_query))>0\n GROUP BY search_query, bucket HAVING count(search_query) > 3 OR sum(cnt) > 1;\nNOTICE:  refreshing continuous aggregate \"search_query_count_1m\"\n--having has aggregates + grp by columns that appear in select\nCREATE MATERIALIZED VIEW search_query_count_2 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\n SELECT  search_query,count(search_query) as count, sum(cnt),\n         time_bucket(INTERVAL '1 minute', time) AS bucket\n FROM raw_data\n WHERE search_query is not null AND LENGTH(TRIM(both from search_query))>0\n GROUP BY search_query, bucket\nHAVING count(search_query) > 3 OR sum(cnt) > 1 OR\n       ( sum(cnt) + count(cnt)) > 1\n       AND search_query = 'Q1';\nNOTICE:  refreshing continuous aggregate \"search_query_count_2\"\nCREATE MATERIALIZED VIEW search_query_count_3 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\n SELECT  search_query,count(search_query) as count, sum(cnt),\n         time_bucket(INTERVAL '1 minute', time) AS bucket\n FROM raw_data\n WHERE search_query is not null AND LENGTH(TRIM(both from search_query))>0\n GROUP BY cnt +cnt2 , bucket, search_query\n HAVING cnt + cnt2 + sum(cnt) > 2 or count(cnt2) > 10;\nNOTICE:  refreshing continuous aggregate \"search_query_count_3\"\ninsert into raw_data select '2000-01-01 00:00+0','Q1', 1, 100;\ninsert into raw_data select '2000-01-01 00:00+0','Q1', 2, 200;\ninsert into raw_data select '2000-01-01 00:00+0','Q1', 3, 300;\ninsert into raw_data select '2000-01-02 00:00+0','Q2', 10, 10;\ninsert into raw_data select '2000-01-02 00:00+0','Q2', 20, 20;\nCALL refresh_continuous_aggregate('search_query_count_1m', NULL, NULL);\nSELECT * FROM search_query_count_1m ORDER BY 1, 2;\n search_query | count |            bucket            \n--------------+-------+------------------------------\n Q1           |     3 | Fri Dec 31 16:00:00 1999 PST\n Q2           |     2 | Sat Jan 01 16:00:00 2000 PST\n\n--only 1 of these should appear in the result\ninsert into raw_data select '2000-01-02 00:00+0','Q3', 0, 0;\ninsert into raw_data select '2000-01-03 00:00+0','Q4', 20, 20;\nCALL refresh_continuous_aggregate('search_query_count_1m', NULL, NULL);\nSELECT * FROM search_query_count_1m ORDER BY 1, 2;\n search_query | count |            bucket            \n--------------+-------+------------------------------\n Q1           |     3 | Fri Dec 31 16:00:00 1999 PST\n Q2           |     2 | Sat Jan 01 16:00:00 2000 PST\n Q4           |     1 | Sun Jan 02 16:00:00 2000 PST\n\n--refresh search_query_count_2---\nCALL refresh_continuous_aggregate('search_query_count_2', NULL, NULL);\nSELECT * FROM search_query_count_2 ORDER BY 1, 2;\n search_query | count | sum |            bucket            \n--------------+-------+-----+------------------------------\n Q1           |     3 |   6 | Fri Dec 31 16:00:00 1999 PST\n Q2           |     2 |  30 | Sat Jan 01 16:00:00 2000 PST\n Q4           |     1 |  20 | Sun Jan 02 16:00:00 2000 PST\n\n--refresh search_query_count_3---\nCALL refresh_continuous_aggregate('search_query_count_3', NULL, NULL);\nSELECT * FROM search_query_count_3 ORDER BY 1, 2, 3;\n search_query | count | sum |            bucket            \n--------------+-------+-----+------------------------------\n Q1           |     1 |   1 | Fri Dec 31 16:00:00 1999 PST\n Q1           |     1 |   2 | Fri Dec 31 16:00:00 1999 PST\n Q1           |     1 |   3 | Fri Dec 31 16:00:00 1999 PST\n Q2           |     1 |  10 | Sat Jan 01 16:00:00 2000 PST\n Q2           |     1 |  20 | Sat Jan 01 16:00:00 2000 PST\n Q4           |     1 |  20 | Sun Jan 02 16:00:00 2000 PST\n\n--- TEST enable compression on continuous aggregates\nCREATE VIEW cagg_compression_status as\nSELECT ca.mat_hypertable_id AS mat_htid,\n       ca.user_view_name AS cagg_name ,\n       h.schema_name AS mat_schema_name,\n       h.table_name AS mat_table_name,\n       ca.materialized_only\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\n;\nSELECT mat_htid AS \"MAT_HTID\"\n     , mat_schema_name || '.' || mat_table_name AS \"MAT_HTNAME\"\n     , mat_table_name AS \"MAT_TABLE_NAME\"\nFROM cagg_compression_status\nWHERE cagg_name = 'search_query_count_3' \\gset\nALTER MATERIALIZED VIEW search_query_count_3 SET (timescaledb.compress = 'true');\nNOTICE:  defaulting compress_orderby to bucket,search_query\nSELECT cagg_name, mat_table_name\nFROM cagg_compression_status where cagg_name = 'search_query_count_3';\n      cagg_name       |       mat_table_name        \n----------------------+-----------------------------\n search_query_count_3 | _materialized_hypertable_41\n\n\\x\nSELECT * FROM timescaledb_information.compression_settings\nWHERE hypertable_name = :'MAT_TABLE_NAME';\n-[ RECORD 1 ]----------+----------------------------\nhypertable_schema      | _timescaledb_internal\nhypertable_name        | _materialized_hypertable_41\nattname                | bucket\nsegmentby_column_index | \norderby_column_index   | 1\norderby_asc            | t\norderby_nullsfirst     | f\n-[ RECORD 2 ]----------+----------------------------\nhypertable_schema      | _timescaledb_internal\nhypertable_name        | _materialized_hypertable_41\nattname                | search_query\nsegmentby_column_index | \norderby_column_index   | 2\norderby_asc            | t\norderby_nullsfirst     | f\n\n\\x\nSELECT compress_chunk(ch)\nFROM show_chunks('search_query_count_3') ch;\n              compress_chunk              \n------------------------------------------\n _timescaledb_internal._hyper_41_79_chunk\n\nSELECT * from search_query_count_3 ORDER BY 1, 2, 3;\n search_query | count | sum |            bucket            \n--------------+-------+-----+------------------------------\n Q1           |     1 |   1 | Fri Dec 31 16:00:00 1999 PST\n Q1           |     1 |   2 | Fri Dec 31 16:00:00 1999 PST\n Q1           |     1 |   3 | Fri Dec 31 16:00:00 1999 PST\n Q2           |     1 |  10 | Sat Jan 01 16:00:00 2000 PST\n Q2           |     1 |  20 | Sat Jan 01 16:00:00 2000 PST\n Q4           |     1 |  20 | Sun Jan 02 16:00:00 2000 PST\n\n-- insert into a new region of the hypertable and then refresh the cagg\n-- (note we still do not support refreshes into existing regions.\n-- cagg chunks do not map 1-1 to hypertabl regions. They encompass\n-- more data\n-- ).\ninsert into raw_data select '2000-05-01 00:00+0','Q3', 0, 0;\n-- On PG >= 14 the refresh test below will pass because we added support for UPDATE/DELETE on compressed chunks in PR #5339\n\\set ON_ERROR_STOP 0\nCALL refresh_continuous_aggregate('search_query_count_3', NULL, '2000-06-01 00:00+0'::timestamptz);\nCALL refresh_continuous_aggregate('search_query_count_3', '2000-05-01 00:00+0'::timestamptz, '2000-06-01 00:00+0'::timestamptz);\nNOTICE:  continuous aggregate \"search_query_count_3\" is already up-to-date\n\\set ON_ERROR_STOP 1\n--insert row\ninsert into raw_data select '2001-05-10 00:00+0','Q3', 100, 100;\n--this should succeed since it does not refresh any compressed regions in the cagg\nCALL refresh_continuous_aggregate('search_query_count_3', '2001-05-01 00:00+0'::timestamptz, '2001-06-01 00:00+0'::timestamptz);\n--verify watermark and check that chunks are compressed\nSELECT _timescaledb_functions.to_timestamp(w) FROM _timescaledb_functions.cagg_watermark(:'MAT_HTID') w;\n         to_timestamp         \n------------------------------\n Wed May 09 17:01:00 2001 PDT\n\nSELECT chunk_name, range_start, range_end, is_compressed\nFROM timescaledb_information.chunks\nWHERE hypertable_name = :'MAT_TABLE_NAME'\nORDER BY 1;\n     chunk_name     |         range_start          |          range_end           | is_compressed \n--------------------+------------------------------+------------------------------+---------------\n _hyper_41_79_chunk | Fri Dec 24 16:00:00 1999 PST | Mon May 22 17:00:00 2000 PDT | t\n _hyper_41_83_chunk | Sun Mar 18 16:00:00 2001 PST | Wed Aug 15 17:00:00 2001 PDT | f\n\nSELECT * FROM _timescaledb_catalog.continuous_aggs_materialization_invalidation_log\nWHERE materialization_id = :'MAT_HTID' ORDER BY 1, 2,3;\n materialization_id | lowest_modified_value | greatest_modified_value \n--------------------+-----------------------+-------------------------\n                 41 |  -9223372036854775808 |     -210866803200000001\n                 41 |       959817600000000 |         988675199999999\n                 41 |       991353600000000 |     9223372036854775807\n\nSELECT * from search_query_count_3\nWHERE bucket > '2001-01-01'\nORDER BY 1, 2, 3;\n search_query | count | sum |            bucket            \n--------------+-------+-----+------------------------------\n Q3           |     1 | 100 | Wed May 09 17:00:00 2001 PDT\n\n--now disable compression , will error out --\n\\set ON_ERROR_STOP 0\nALTER MATERIALIZED VIEW search_query_count_3 SET (timescaledb.compress = 'false');\nERROR:  cannot disable columnstore on hypertable with columnstore chunks\n\\set ON_ERROR_STOP 1\nSELECT decompress_chunk(format('%I.%I', schema_name, table_name))\nFROM _timescaledb_catalog.chunk\nWHERE hypertable_id = :'MAT_HTID' and status = 1;\n             decompress_chunk             \n------------------------------------------\n _timescaledb_internal._hyper_41_79_chunk\n\n--disable compression on cagg after decompressing all chunks--\nALTER MATERIALIZED VIEW search_query_count_3 SET (timescaledb.compress = 'false');\nSELECT cagg_name, mat_table_name\nFROM cagg_compression_status where cagg_name = 'search_query_count_3';\n      cagg_name       |       mat_table_name        \n----------------------+-----------------------------\n search_query_count_3 | _materialized_hypertable_41\n\nSELECT view_name, materialized_only, compression_enabled\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'search_query_count_3';\n      view_name       | materialized_only | compression_enabled \n----------------------+-------------------+---------------------\n search_query_count_3 | f                 | f\n\n-- TEST caggs on table with more columns than in the cagg view defn --\nCREATE TABLE test_morecols ( time TIMESTAMPTZ NOT NULL,\n                             val1 INTEGER, val2 INTEGER, val3 INTEGER, val4 INTEGER,\n                             val5 INTEGER,  val6 INTEGER, val7 INTEGER, val8 INTEGER);\nSELECT create_hypertable('test_morecols', 'time', chunk_time_interval=> '7 days'::interval);\n      create_hypertable      \n-----------------------------\n (43,public,test_morecols,t)\n\nINSERT INTO test_morecols\nSELECT generate_series('2018-12-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 55, 75, 40, 70, NULL, 100, 200, 200;\nCREATE MATERIALIZED VIEW test_morecols_cagg with (timescaledb.continuous, timescaledb.materialized_only=false)\nAS SELECT time_bucket('30 days',time), avg(val1),  count(val2)\n FROM test_morecols GROUP BY 1;\nNOTICE:  refreshing continuous aggregate \"test_morecols_cagg\"\nALTER MATERIALIZED VIEW test_morecols_cagg SET (timescaledb.compress='true');\nNOTICE:  defaulting compress_orderby to time_bucket\nSELECT compress_chunk(ch) FROM show_chunks('test_morecols_cagg') ch;\n              compress_chunk              \n------------------------------------------\n _timescaledb_internal._hyper_44_89_chunk\n\nSELECT * FROM test_morecols_cagg ORDER BY time_bucket;\n         time_bucket          |         avg         | count \n------------------------------+---------------------+-------\n Fri Nov 23 16:00:00 2018 PST | 55.0000000000000000 |    23\n Sun Dec 23 16:00:00 2018 PST | 55.0000000000000000 |     8\n\nSELECT view_name, materialized_only, compression_enabled\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_morecols_cagg';\n     view_name      | materialized_only | compression_enabled \n--------------------+-------------------+---------------------\n test_morecols_cagg | f                 | t\n\n--should keep compressed option, modify only materialized --\nALTER MATERIALIZED VIEW test_morecols_cagg SET (timescaledb.materialized_only='true');\nSELECT view_name, materialized_only, compression_enabled\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_morecols_cagg';\n     view_name      | materialized_only | compression_enabled \n--------------------+-------------------+---------------------\n test_morecols_cagg | t                 | t\n\nCREATE TABLE issue3248(filler_1 int, filler_2 int, filler_3 int, time timestamptz NOT NULL, device_id int, v0 int, v1 int, v2 float, v3 float);\nCREATE INDEX ON issue3248(time DESC);\nCREATE INDEX ON issue3248(device_id,time DESC);\nSELECT create_hypertable('issue3248','time',create_default_indexes:=false);\n    create_hypertable    \n-------------------------\n (46,public,issue3248,t)\n\nALTER TABLE issue3248 DROP COLUMN filler_1;\nINSERT INTO issue3248(time,device_id,v0,v1,v2,v3)\nSELECT time, device_id, device_id+1,  device_id + 2, device_id + 0.5, NULL\nFROM generate_series('2000-01-01 0:00:00+0'::timestamptz,'2000-01-05 23:55:00+0','8h') gtime(time),\n     generate_series(1,5,1) gdevice(device_id);\nALTER TABLE issue3248 DROP COLUMN filler_2;\nINSERT INTO issue3248(time,device_id,v0,v1,v2,v3)\nSELECT time, device_id, device_id-1, device_id + 2, device_id + 0.5, NULL\nFROM generate_series('2000-01-06 0:00:00+0'::timestamptz,'2000-01-12 23:55:00+0','8h') gtime(time),\n     generate_series(1,5,1) gdevice(device_id);\nALTER TABLE issue3248 DROP COLUMN filler_3;\nINSERT INTO issue3248(time,device_id,v0,v1,v2,v3)\nSELECT time, device_id, device_id, device_id + 2, device_id + 0.5, NULL\nFROM generate_series('2000-01-13 0:00:00+0'::timestamptz,'2000-01-19 23:55:00+0','8h') gtime(time),\n     generate_series(1,5,1) gdevice(device_id);\nANALYZE issue3248;\nCREATE materialized view issue3248_cagg WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS SELECT time_bucket('1h',time), device_id, min(v0), max(v1), avg(v2)\nFROM issue3248 GROUP BY 1,2;\nNOTICE:  refreshing continuous aggregate \"issue3248_cagg\"\nSELECT\n  FROM issue3248 AS m,\n       LATERAL(SELECT m FROM issue3248_cagg WHERE avg IS NULL LIMIT 1) AS lat;\n--\n\n-- test that option create_group_indexes is taken into account\nCREATE TABLE test_group_idx (\ntime timestamptz,\nsymbol int,\nvalue numeric\n);\nselect create_hypertable('test_group_idx', 'time');\n      create_hypertable       \n------------------------------\n (48,public,test_group_idx,t)\n\ninsert into test_group_idx\nselect t, round(random()*10), random()*5\nfrom generate_series('2020-01-01', '2020-02-25', INTERVAL '12 hours') t;\ncreate materialized view cagg_index_true\nwith (timescaledb.continuous, timescaledb.materialized_only=false, timescaledb.create_group_indexes=true) as\nselect\n\ttime_bucket('1 day', \"time\") as bucket,\n\tsum(value),\n\tsymbol\nfrom test_group_idx\ngroup by bucket, symbol;\nNOTICE:  refreshing continuous aggregate \"cagg_index_true\"\ncreate materialized view cagg_index_false\nwith (timescaledb.continuous, timescaledb.materialized_only=false, timescaledb.create_group_indexes=false) as\nselect\n\ttime_bucket('1 day', \"time\") as bucket,\n\tsum(value),\n\tsymbol\nfrom test_group_idx\ngroup by bucket, symbol;\nNOTICE:  refreshing continuous aggregate \"cagg_index_false\"\ncreate materialized view cagg_index_default\nwith (timescaledb.continuous, timescaledb.materialized_only=false) as\nselect\n\ttime_bucket('1 day', \"time\") as bucket,\n\tsum(value),\n\tsymbol\nfrom test_group_idx\ngroup by bucket, symbol;\nNOTICE:  refreshing continuous aggregate \"cagg_index_default\"\n-- see corresponding materialization_hypertables\nselect view_name, materialization_hypertable_name from timescaledb_information.continuous_aggregates ca\nwhere view_name like 'cagg_index_%' ORDER BY view_name;\n     view_name      | materialization_hypertable_name \n--------------------+---------------------------------\n cagg_index_default | _materialized_hypertable_51\n cagg_index_false   | _materialized_hypertable_50\n cagg_index_true    | _materialized_hypertable_49\n\n-- now make sure a group index has been created when explicitly asked for\n\\x on\nselect i.*\nfrom pg_indexes i\njoin pg_class c\n    on schemaname = relnamespace::regnamespace::text\n    and tablename = relname\nwhere tablename in (select materialization_hypertable_name from timescaledb_information.continuous_aggregates\nwhere view_name like 'cagg_index_%')\norder by tablename, indexname;\n-[ RECORD 1 ]-------------------------------------------------------------------------------------------------------------------------------------------------\nschemaname | _timescaledb_internal\ntablename  | _materialized_hypertable_49\nindexname  | _materialized_hypertable_49_bucket_idx\ntablespace | \nindexdef   | CREATE INDEX _materialized_hypertable_49_bucket_idx ON _timescaledb_internal._materialized_hypertable_49 USING btree (bucket DESC)\n-[ RECORD 2 ]-------------------------------------------------------------------------------------------------------------------------------------------------\nschemaname | _timescaledb_internal\ntablename  | _materialized_hypertable_49\nindexname  | _materialized_hypertable_49_symbol_bucket_idx\ntablespace | \nindexdef   | CREATE INDEX _materialized_hypertable_49_symbol_bucket_idx ON _timescaledb_internal._materialized_hypertable_49 USING btree (symbol, bucket DESC)\n-[ RECORD 3 ]-------------------------------------------------------------------------------------------------------------------------------------------------\nschemaname | _timescaledb_internal\ntablename  | _materialized_hypertable_50\nindexname  | _materialized_hypertable_50_bucket_idx\ntablespace | \nindexdef   | CREATE INDEX _materialized_hypertable_50_bucket_idx ON _timescaledb_internal._materialized_hypertable_50 USING btree (bucket DESC)\n-[ RECORD 4 ]-------------------------------------------------------------------------------------------------------------------------------------------------\nschemaname | _timescaledb_internal\ntablename  | _materialized_hypertable_51\nindexname  | _materialized_hypertable_51_bucket_idx\ntablespace | \nindexdef   | CREATE INDEX _materialized_hypertable_51_bucket_idx ON _timescaledb_internal._materialized_hypertable_51 USING btree (bucket DESC)\n-[ RECORD 5 ]-------------------------------------------------------------------------------------------------------------------------------------------------\nschemaname | _timescaledb_internal\ntablename  | _materialized_hypertable_51\nindexname  | _materialized_hypertable_51_symbol_bucket_idx\ntablespace | \nindexdef   | CREATE INDEX _materialized_hypertable_51_symbol_bucket_idx ON _timescaledb_internal._materialized_hypertable_51 USING btree (symbol, bucket DESC)\n\n\\x off\n--\n-- TESTs for removing old CAggs restrictions\n--\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 8 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_29_67_chunk\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_30_68_chunk\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_31_69_chunk\nCREATE TABLE conditions (\n  timec       TIMESTAMPTZ       NOT NULL,\n  location    TEXT              NOT NULL,\n  temperature DOUBLE PRECISION  NULL,\n  humidity    DOUBLE PRECISION  NULL\n);\nSELECT create_hypertable('conditions', 'timec');\n    create_hypertable     \n--------------------------\n (52,public,conditions,t)\n\nINSERT INTO conditions\nVALUES\n  ('2010-01-01 09:00:00-08', 'SFO', 55, 45),\n  ('2010-01-02 09:00:00-08', 'por', 100, 100),\n  ('2010-01-02 09:00:00-08', 'NYC', 65, 45),\n  ('2010-01-02 09:00:00-08', 'SFO', 65, 45),\n  ('2010-01-03 09:00:00-08', 'NYC', 45, 55),\n  ('2010-01-05 09:00:00-08', 'SFO', 75, 100),\n  ('2018-11-01 09:00:00-08', 'NYC', 45, 35),\n  ('2018-11-02 09:00:00-08', 'NYC', 35, 15),\n  ('2018-11-03 09:00:00-08', 'NYC', 35, 25);\n-- aggregate with DISTINCT\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1week', timec),\n  COUNT(location),\n  SUM(DISTINCT temperature)\nFROM conditions\nGROUP BY time_bucket('1week', timec), location;\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\nSELECT * FROM mat_m1 ORDER BY 1, 2, 3;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Dec 27 16:00:00 2009 PST |     1 | 100\n Sun Dec 27 16:00:00 2009 PST |     2 | 110\n Sun Dec 27 16:00:00 2009 PST |     2 | 120\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n Sun Oct 28 17:00:00 2018 PDT |     3 |  80\n\n-- aggregate with FILTER\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1week', timec),\n  SUM(temperature) FILTER (WHERE humidity > 60)\nFROM conditions\nGROUP BY time_bucket('1week', timec), location;\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\nSELECT * FROM mat_m1 ORDER BY 1, 2;\n         time_bucket          | sum \n------------------------------+-----\n Sun Dec 27 16:00:00 2009 PST | 100\n Sun Dec 27 16:00:00 2009 PST |    \n Sun Dec 27 16:00:00 2009 PST |    \n Sun Jan 03 16:00:00 2010 PST |  75\n Sun Oct 28 17:00:00 2018 PDT |    \n\n-- aggregate with filter in having clause\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1week', timec),\n  MAX(temperature)\nFROM conditions\nGROUP BY time_bucket('1week', timec), location\nHAVING SUM(temperature) FILTER (WHERE humidity > 40) > 50;\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\nSELECT * FROM mat_m1 ORDER BY 1, 2;\n         time_bucket          | max \n------------------------------+-----\n Sun Dec 27 16:00:00 2009 PST |  65\n Sun Dec 27 16:00:00 2009 PST |  65\n Sun Dec 27 16:00:00 2009 PST | 100\n Sun Jan 03 16:00:00 2010 PST |  75\n\n-- ordered set aggr\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_55_116_chunk\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1week', timec),\n  MODE() WITHIN GROUP(ORDER BY humidity)\nFROM conditions\nGROUP BY time_bucket('1week', timec);\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\nSELECT * FROM mat_m1 ORDER BY 1;\n         time_bucket          | mode \n------------------------------+------\n Sun Dec 27 16:00:00 2009 PST |   45\n Sun Jan 03 16:00:00 2010 PST |  100\n Sun Oct 28 17:00:00 2018 PDT |   15\n\n-- hypothetical-set aggr\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1week', timec),\n  RANK(60) WITHIN GROUP (ORDER BY humidity),\n  DENSE_RANK(60) WITHIN GROUP (ORDER BY humidity),\n  PERCENT_RANK(60) WITHIN GROUP (ORDER BY humidity)\nFROM conditions\nGROUP BY time_bucket('1week', timec);\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\nSELECT * FROM mat_m1 ORDER BY 1;\n         time_bucket          | rank | dense_rank | percent_rank \n------------------------------+------+------------+--------------\n Sun Dec 27 16:00:00 2009 PST |    5 |          3 |          0.8\n Sun Jan 03 16:00:00 2010 PST |    1 |          1 |            0\n Sun Oct 28 17:00:00 2018 PDT |    4 |          4 |            1\n\n-- userdefined aggregate without combine function\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\nCREATE AGGREGATE newavg (\n  sfunc = int4_avg_accum,\n  basetype = int4,\n  stype = _int8,\n  finalfunc = int8_avg,\n  initcond1 = '{0,0}'\n);\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  SUM(humidity),\n  round(newavg(temperature::int4))\nFROM conditions\nGROUP BY time_bucket('1week', timec), location ORDER BY 1,2;\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\nSELECT * FROM mat_m1 ORDER BY 1, 2;\n sum | round \n-----+-------\n  75 |    38\n  90 |    60\n 100 |    55\n 100 |    75\n 100 |   100\n\n-- ORDER BY in the view definition\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1week', timec),\n  COUNT(location),\n  SUM(temperature)\nFROM conditions\nGROUP BY time_bucket('1week', timec)\nORDER BY sum DESC;\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\n-- CAgg definition for realtime\nSELECT pg_get_viewdef('mat_m1',true);\n                                                                                  pg_get_viewdef                                                                                   \n-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n ( SELECT _materialized_hypertable_59.time_bucket,                                                                                                                                +\n     _materialized_hypertable_59.count,                                                                                                                                           +\n     _materialized_hypertable_59.sum                                                                                                                                              +\n    FROM _timescaledb_internal._materialized_hypertable_59                                                                                                                        +\n   WHERE _materialized_hypertable_59.time_bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(59)), '-infinity'::timestamp with time zone)+\n   ORDER BY _materialized_hypertable_59.sum DESC)                                                                                                                                 +\n UNION ALL                                                                                                                                                                        +\n ( SELECT time_bucket('@ 7 days'::interval, conditions.timec) AS time_bucket,                                                                                                     +\n     count(conditions.location) AS count,                                                                                                                                         +\n     sum(conditions.temperature) AS sum                                                                                                                                           +\n    FROM conditions                                                                                                                                                               +\n   WHERE conditions.timec >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(59)), '-infinity'::timestamp with time zone)                      +\n   GROUP BY (time_bucket('@ 7 days'::interval, conditions.timec))                                                                                                                 +\n   ORDER BY (sum(conditions.temperature)) DESC)                                                                                                                                   +\n   ORDER BY 3 DESC;\n\n-- Ordered result\nSELECT * FROM mat_m1;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Dec 27 16:00:00 2009 PST |     5 | 330\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n\n-- Insert new data and query again to make sure we produce ordered data\nINSERT INTO conditions VALUES ('2018-11-10 09:00:00-08', 'SFO', 10, 10);\nSELECT * FROM mat_m1;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Dec 27 16:00:00 2009 PST |     5 | 330\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n Sun Nov 04 16:00:00 2018 PST |     1 |  10\n\n-- This new row will change the order again\nINSERT INTO conditions VALUES ('2018-11-11 09:00:00-08', 'SFO', 400, 400);\nSELECT * FROM mat_m1;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Nov 04 16:00:00 2018 PST |     2 | 410\n Sun Dec 27 16:00:00 2009 PST |     5 | 330\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n\n-- Merge Append\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM mat_m1;\n--- QUERY PLAN ---\n Merge Append\n   Sort Key: _materialized_hypertable_59.sum DESC\n   ->  Merge Append\n         Sort Key: _materialized_hypertable_59.sum DESC\n         ->  Index Scan Backward using _hyper_59_123_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_123_chunk\n         ->  Index Scan Backward using _hyper_59_124_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_124_chunk\n               Index Cond: (time_bucket < 'Sun Nov 04 16:00:00 2018 PST'::timestamp with time zone)\n   ->  Sort\n         Sort Key: (sum(conditions.temperature)) DESC\n         ->  Finalize HashAggregate\n               Group Key: (time_bucket('@ 7 days'::interval, conditions.timec))\n               ->  Append\n                     ->  Partial HashAggregate\n                           Group Key: time_bucket('@ 7 days'::interval, _hyper_52_111_chunk.timec)\n                           ->  Index Scan Backward using _hyper_52_111_chunk_conditions_timec_idx on _hyper_52_111_chunk\n                                 Index Cond: (timec >= 'Sun Nov 04 16:00:00 2018 PST'::timestamp with time zone)\n                     ->  Partial HashAggregate\n                           Group Key: time_bucket('@ 7 days'::interval, _hyper_52_125_chunk.timec)\n                           ->  Seq Scan on _hyper_52_125_chunk\n\n-- Ordering by another column\nSELECT * FROM mat_m1 ORDER BY count;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n Sun Nov 04 16:00:00 2018 PST |     2 | 410\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Dec 27 16:00:00 2009 PST |     5 | 330\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM mat_m1 ORDER BY count;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _materialized_hypertable_59.count\n   ->  Merge Append\n         Sort Key: _materialized_hypertable_59.sum DESC\n         ->  Merge Append\n               Sort Key: _materialized_hypertable_59.sum DESC\n               ->  Index Scan Backward using _hyper_59_123_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_123_chunk\n               ->  Index Scan Backward using _hyper_59_124_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_124_chunk\n                     Index Cond: (time_bucket < 'Sun Nov 04 16:00:00 2018 PST'::timestamp with time zone)\n         ->  Sort\n               Sort Key: (sum(conditions.temperature)) DESC\n               ->  Finalize HashAggregate\n                     Group Key: (time_bucket('@ 7 days'::interval, conditions.timec))\n                     ->  Append\n                           ->  Partial HashAggregate\n                                 Group Key: time_bucket('@ 7 days'::interval, _hyper_52_111_chunk.timec)\n                                 ->  Index Scan Backward using _hyper_52_111_chunk_conditions_timec_idx on _hyper_52_111_chunk\n                                       Index Cond: (timec >= 'Sun Nov 04 16:00:00 2018 PST'::timestamp with time zone)\n                           ->  Partial HashAggregate\n                                 Group Key: time_bucket('@ 7 days'::interval, _hyper_52_125_chunk.timec)\n                                 ->  Seq Scan on _hyper_52_125_chunk\n\n-- Change the type of cagg\nALTER MATERIALIZED VIEW mat_m1 SET (timescaledb.materialized_only=true);\n-- CAgg definition for materialized only\nSELECT pg_get_viewdef('mat_m1',true);\n                      pg_get_viewdef                       \n-----------------------------------------------------------\n  SELECT _materialized_hypertable_59.time_bucket,         +\n     _materialized_hypertable_59.count,                   +\n     _materialized_hypertable_59.sum                      +\n    FROM _timescaledb_internal._materialized_hypertable_59+\n   ORDER BY _materialized_hypertable_59.sum DESC;\n\n-- Now the query will show only the materialized data, without last two\n-- records inserted into the original hypertable (last two insers above)\nSELECT * FROM mat_m1;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Dec 27 16:00:00 2009 PST |     5 | 330\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n\n-- Merge Append\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM mat_m1;\n--- QUERY PLAN ---\n Merge Append\n   Sort Key: _materialized_hypertable_59.sum DESC\n   ->  Index Scan Backward using _hyper_59_123_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_123_chunk\n   ->  Index Scan Backward using _hyper_59_124_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_124_chunk\n\n-- Ordering by another column\nSELECT * FROM mat_m1 ORDER BY count;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Dec 27 16:00:00 2009 PST |     5 | 330\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM mat_m1 ORDER BY count;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _materialized_hypertable_59.count\n   ->  Merge Append\n         Sort Key: _materialized_hypertable_59.sum DESC\n         ->  Index Scan Backward using _hyper_59_123_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_123_chunk\n         ->  Index Scan Backward using _hyper_59_124_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_124_chunk\n\nSELECT h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\n-- Invalidate old region and refresh again\nDELETE FROM conditions WHERE timec < '2010-01-05 09:00:00-08';\nCALL refresh_continuous_aggregate('mat_m1', NULL, NULL);\n-- Querying the cagg produce ordered records as expected\nSELECT * FROM mat_m1;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Nov 04 16:00:00 2018 PST |     2 | 410\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n\n-- Querying direct the materialization hypertable doesn't\n-- produce ordered records\nSELECT * FROM :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\";\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Nov 04 16:00:00 2018 PST |     2 | 410\n\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to 2 other objects\nCREATE TABLE conditions (\n  timec       TIMESTAMPTZ       NOT NULL,\n  location    TEXT              NOT NULL,\n  temperature DOUBLE PRECISION  NULL,\n  humidity    DOUBLE PRECISION  NULL\n);\nSELECT table_name FROM create_hypertable('conditions', 'timec');\n table_name \n------------\n conditions\n\nINSERT INTO conditions VALUES\n  ('2010-01-01 09:00:00-08', 'SFO', 55, 45),\n  ('2010-01-02 09:00:00-08', 'por', 100, 100),\n  ('2010-01-02 09:00:00-08', 'SFO', 65, 45),\n  ('2010-01-02 09:00:00-08', 'NYC', 65, 45),\n  ('2018-11-01 09:00:00-08', 'NYC', 45, 35),\n  ('2018-11-02 09:00:00-08', 'NYC', 35, 15);\nCREATE MATERIALIZED VIEW conditions_summary_new(timec, minl, sumt, sumh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nAS\nSELECT time_bucket('1day', timec), min(location), sum(temperature), sum(humidity)\nFROM conditions\nGROUP BY time_bucket('1day', timec) WITH NO DATA;\n\\x ON\nSELECT *\nFROM timescaledb_information.continuous_aggregates\nWHERE view_name = 'conditions_summary_new';\n-[ RECORD 1 ]---------------------+---------------------------------------------------------------------\nhypertable_schema                 | public\nhypertable_name                   | conditions\nview_schema                       | public\nview_name                         | conditions_summary_new\nview_owner                        | default_perm_user\nmaterialized_only                 | t\ncompression_enabled               | f\nmaterialization_hypertable_schema | _timescaledb_internal\nmaterialization_hypertable_name   | _materialized_hypertable_61\nview_definition                   |  SELECT time_bucket('@ 1 day'::interval, conditions.timec) AS timec,+\n                                  |     min(conditions.location) AS minl,                               +\n                                  |     sum(conditions.temperature) AS sumt,                            +\n                                  |     sum(conditions.humidity) AS sumh                                +\n                                  |    FROM conditions                                                  +\n                                  |   GROUP BY (time_bucket('@ 1 day'::interval, conditions.timec));\n\n\\x OFF\nCALL refresh_continuous_aggregate('conditions_summary_new', NULL, NULL);\n-- Check and compare number of returned rows\nSELECT count(*) FROM conditions_summary_new;\n count \n-------\n     4\n\n-- Parallel planning test for realtime Continuous Aggregate\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to 2 other objects\nCREATE TABLE conditions (\n  timec       TIMESTAMPTZ       NOT NULL,\n  temperature DOUBLE PRECISION  NULL\n);\nSELECT table_name FROM create_hypertable('conditions', 'timec');\n table_name \n------------\n conditions\n\nINSERT INTO conditions\nSELECT t, 10 FROM generate_series('2023-01-01 00:00-03'::timestamptz, '2023-12-31 23:59-03'::timestamptz, '1 hour'::interval) AS t;\nCREATE MATERIALIZED VIEW conditions_daily WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1 day', timec),\n  SUM(temperature)\nFROM conditions\nGROUP BY 1\nORDER BY 2 DESC;\nNOTICE:  refreshing continuous aggregate \"conditions_daily\"\nSELECT set_config(CASE WHEN current_setting('server_version_num')::int < 160000 THEN 'force_parallel_mode' ELSE 'debug_parallel_query' END,'on', false);\n set_config \n------------\n on\n\nSET max_parallel_workers_per_gather = 4;\nSET parallel_setup_cost = 0;\nSET parallel_tuple_cost = 0;\n-- Parallel planning\nEXPLAIN (BUFFERS OFF, COSTS OFF, TIMING OFF) SELECT * FROM conditions_daily WHERE time_bucket >= '2023-07-01';\n--- QUERY PLAN ---\n Merge Append\n   Sort Key: _materialized_hypertable_63.sum DESC\n   ->  Gather Merge\n         Workers Planned: 2\n         ->  Sort\n               Sort Key: _materialized_hypertable_63.sum DESC\n               ->  Parallel Append\n                     ->  Parallel Index Scan using _hyper_63_185_chunk__materialized_hypertable_63_time_bucket_idx on _hyper_63_185_chunk\n                           Index Cond: ((time_bucket < 'Mon Jan 01 16:00:00 2024 PST'::timestamp with time zone) AND (time_bucket >= 'Sat Jul 01 00:00:00 2023 PDT'::timestamp with time zone))\n                     ->  Parallel Index Scan using _hyper_63_187_chunk__materialized_hypertable_63_time_bucket_idx on _hyper_63_187_chunk\n                           Index Cond: ((time_bucket < 'Mon Jan 01 16:00:00 2024 PST'::timestamp with time zone) AND (time_bucket >= 'Sat Jul 01 00:00:00 2023 PDT'::timestamp with time zone))\n                     ->  Parallel Seq Scan on _hyper_63_184_chunk\n   ->  Sort\n         Sort Key: (sum(_hyper_62_182_chunk.temperature)) DESC\n         ->  HashAggregate\n               Group Key: (time_bucket('@ 1 day'::interval, _hyper_62_182_chunk.timec))\n               ->  Gather\n                     Workers Planned: 1\n                     ->  Result\n                           ->  Parallel Index Scan Backward using _hyper_62_182_chunk_conditions_timec_idx on _hyper_62_182_chunk\n                                 Index Cond: ((timec >= 'Mon Jan 01 16:00:00 2024 PST'::timestamp with time zone) AND (timec >= 'Sat Jul 01 00:00:00 2023 PDT'::timestamp with time zone))\n                                 Filter: (time_bucket('@ 1 day'::interval, timec) >= 'Sat Jul 01 00:00:00 2023 PDT'::timestamp with time zone)\n\n"
  },
  {
    "path": "tsl/test/expected/cagg-16.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- initialize the bgw mock state to prevent the materialization workers from running\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION ts_bgw_params_create() RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION test.continuous_aggs_find_view(cagg REGCLASS) RETURNS VOID\nAS :TSL_MODULE_PATHNAME, 'ts_test_continuous_agg_find_by_view_name' LANGUAGE C;\n\\set WAIT_ON_JOB 0\n\\set IMMEDIATELY_SET_UNTIL 1\n\\set WAIT_FOR_OTHER_TO_ADVANCE 2\n-- remove any default jobs, e.g., telemetry so bgw_job isn't polluted\nDELETE FROM _timescaledb_catalog.bgw_job;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT * FROM _timescaledb_catalog.bgw_job;\n id | application_name | schedule_interval | max_runtime | max_retries | retry_period | proc_schema | proc_name | owner | scheduled | fixed_schedule | initial_start | hypertable_id | config | check_schema | check_name | timezone \n----+------------------+-------------------+-------------+-------------+--------------+-------------+-----------+-------+-----------+----------------+---------------+---------------+--------+--------------+------------+----------\n\n--TEST1 ---\n--basic test with count\ncreate table foo (a integer, b integer, c integer);\nselect table_name from create_hypertable('foo', 'a', chunk_time_interval=> 10);\n table_name \n------------\n foo\n\ninsert into foo values( 3 , 16 , 20);\ninsert into foo values( 1 , 10 , 20);\ninsert into foo values( 1 , 11 , 20);\ninsert into foo values( 1 , 12 , 20);\ninsert into foo values( 1 , 13 , 20);\ninsert into foo values( 1 , 14 , 20);\ninsert into foo values( 2 , 14 , 20);\ninsert into foo values( 2 , 15 , 20);\ninsert into foo values( 2 , 16 , 20);\nCREATE OR REPLACE FUNCTION integer_now_foo() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(a), 0) FROM foo $$;\nSELECT set_integer_now_func('foo', 'integer_now_foo');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW mat_m1(a, countb)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect a, count(b)\nfrom foo\ngroup by time_bucket(1, a), a WITH NO DATA;\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, 2::integer, '12 h'::interval) AS job_id\n\\gset\nSELECT * FROM _timescaledb_catalog.bgw_job;\n  id  |              application_name              | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema       |              proc_name              |       owner       | scheduled | fixed_schedule | initial_start | hypertable_id |                             config                              |      check_schema      |                check_name                 | timezone \n------+--------------------------------------------+-------------------+-------------+-------------+--------------+------------------------+-------------------------------------+-------------------+-----------+----------------+---------------+---------------+-----------------------------------------------------------------+------------------------+-------------------------------------------+----------\n 1000 | Refresh Continuous Aggregate Policy [1000] | @ 12 hours        | @ 0         |          -1 | @ 12 hours   | _timescaledb_functions | policy_refresh_continuous_aggregate | default_perm_user | t         | f              |               |             2 | {\"end_offset\": 2, \"start_offset\": null, \"mat_hypertable_id\": 2} | _timescaledb_functions | policy_refresh_continuous_aggregate_check | \n\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\ninsert into :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect a, count(b),\ntime_bucket(1, a)\nfrom foo\ngroup by time_bucket(1, a) , a ;\nselect * from mat_m1 order by a ;\n a | countb \n---+--------\n 1 |      5\n 2 |      3\n 3 |      1\n\n--check triggers on user hypertable --\nSET ROLE :ROLE_SUPERUSER;\nselect tgname, tgtype, tgenabled , relname from pg_trigger, pg_class\nwhere tgrelid = pg_class.oid and pg_class.relname like 'foo'\norder by tgname;\n tgname | tgtype | tgenabled | relname \n--------+--------+-----------+---------\n\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n-- TEST2 ---\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_2_2_chunk\nSHOW enable_partitionwise_aggregate;\n enable_partitionwise_aggregate \n--------------------------------\n off\n\nSET enable_partitionwise_aggregate = on;\nSELECT * FROM _timescaledb_catalog.bgw_job;\n id | application_name | schedule_interval | max_runtime | max_retries | retry_period | proc_schema | proc_name | owner | scheduled | fixed_schedule | initial_start | hypertable_id | config | check_schema | check_name | timezone \n----+------------------+-------------------+-------------+-------------+--------------+-------------+-----------+-------+-----------+----------------+---------------+---------------+--------+--------------+------------+----------\n\nCREATE TABLE conditions (\n      timec        TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\ninsert into conditions values ( '2010-01-01 09:00:00-08', 'SFO', 55, 45);\ninsert into conditions values ( '2010-01-02 09:00:00-08', 'por', 100, 100);\ninsert into conditions values ( '2010-01-02 09:00:00-08', 'SFO', 65, 45);\ninsert into conditions values ( '2010-01-02 09:00:00-08', 'NYC', 65, 45);\ninsert into conditions values ( '2018-11-01 09:00:00-08', 'NYC', 45, 35);\ninsert into conditions values ( '2018-11-02 09:00:00-08', 'NYC', 35, 15);\nCREATE MATERIALIZED VIEW mat_m1( timec, minl, sumt , sumh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1day', timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by time_bucket('1day', timec) WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\n-- Materialized hypertable for mat_m1 should not be visible in the\n-- hypertables view:\nSELECT hypertable_schema, hypertable_name\nFROM timescaledb_information.hypertables ORDER BY 1,2;\n hypertable_schema | hypertable_name \n-------------------+-----------------\n public            | conditions\n public            | foo\n\nSET ROLE :ROLE_SUPERUSER;\ninsert into  :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect\n time_bucket('1day', timec), min(location), sum(temperature), sum(humidity)\nfrom conditions\ngroup by time_bucket('1day', timec) ;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--should have same results --\nselect timec, minl, sumt, sumh\nfrom mat_m1\norder by timec;\n            timec             | minl | sumt | sumh \n------------------------------+------+------+------\n Thu Dec 31 16:00:00 2009 PST | SFO  |   55 |   45\n Fri Jan 01 16:00:00 2010 PST | NYC  |  230 |  190\n Wed Oct 31 17:00:00 2018 PDT | NYC  |   45 |   35\n Thu Nov 01 17:00:00 2018 PDT | NYC  |   35 |   15\n\nselect time_bucket('1day', timec), min(location), sum(temperature), sum(humidity)\nfrom conditions\ngroup by time_bucket('1day', timec)\norder by 1;\n         time_bucket          | min | sum | sum \n------------------------------+-----+-----+-----\n Thu Dec 31 16:00:00 2009 PST | SFO |  55 |  45\n Fri Jan 01 16:00:00 2010 PST | NYC | 230 | 190\n Wed Oct 31 17:00:00 2018 PDT | NYC |  45 |  35\n Thu Nov 01 17:00:00 2018 PDT | NYC |  35 |  15\n\nSET enable_partitionwise_aggregate = off;\n-- TEST3 --\n-- drop on table conditions should cascade to materialized mat_v1\ndrop table conditions cascade;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to 2 other objects\nCREATE TABLE conditions (\n      timec        TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\ninsert into conditions values ( '2010-01-01 09:00:00-08', 'SFO', 55, 45);\ninsert into conditions values ( '2010-01-02 09:00:00-08', 'por', 100, 100);\ninsert into conditions values ( '2010-01-02 09:00:00-08', 'NYC', 65, 45);\ninsert into conditions values ( '2010-01-02 09:00:00-08', 'SFO', 65, 45);\ninsert into conditions values ( '2010-01-03 09:00:00-08', 'NYC', 45, 55);\ninsert into conditions values ( '2010-01-05 09:00:00-08', 'SFO', 75, 100);\ninsert into conditions values ( '2018-11-01 09:00:00-08', 'NYC', 45, 35);\ninsert into conditions values ( '2018-11-02 09:00:00-08', 'NYC', 35, 15);\ninsert into conditions values ( '2018-11-03 09:00:00-08', 'NYC', 35, 25);\nCREATE MATERIALIZED VIEW mat_m1( timec, minl, sumth, stddevh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec)  WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\nSET ROLE :ROLE_SUPERUSER;\ninsert into  :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect\n time_bucket('1week', timec), min(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec) ;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--should have same results --\nselect timec, minl, sumth, stddevh\nfrom mat_m1\norder by timec;\n            timec             | minl | sumth |     stddevh      \n------------------------------+------+-------+------------------\n Sun Dec 27 16:00:00 2009 PST | NYC  |   620 | 23.8746727726266\n Sun Jan 03 16:00:00 2010 PST | SFO  |   175 |                 \n Sun Oct 28 17:00:00 2018 PDT | NYC  |   190 |               10\n\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+ sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec)\norder by time_bucket('1week', timec);\n         time_bucket          | min | ?column? |      stddev      \n------------------------------+-----+----------+------------------\n Sun Dec 27 16:00:00 2009 PST | NYC |      620 | 23.8746727726266\n Sun Jan 03 16:00:00 2010 PST | SFO |      175 |                 \n Sun Oct 28 17:00:00 2018 PDT | NYC |      190 |               10\n\n-- TEST4 --\n--materialized view with group by clause + expression in SELECT\n-- use previous data from conditions\n--drop only the view.\n-- apply where clause on result of mat_m1 --\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\nCREATE MATERIALIZED VIEW mat_m1( timec, minl, sumth, stddevh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\nwhere location = 'NYC'\ngroup by time_bucket('1week', timec)\n WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\nSET ROLE :ROLE_SUPERUSER;\ninsert into  :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect\n time_bucket('1week', timec), min(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\nwhere location = 'NYC'\ngroup by time_bucket('1week', timec) ;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--should have same results --\nselect timec, minl, sumth, stddevh\nfrom mat_m1\nwhere stddevh is not null\norder by timec;\n            timec             | minl | sumth |     stddevh      \n------------------------------+------+-------+------------------\n Sun Dec 27 16:00:00 2009 PST | NYC  |   210 | 7.07106781186548\n Sun Oct 28 17:00:00 2018 PDT | NYC  |   190 |               10\n\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+ sum(humidity), stddev(humidity)\nfrom conditions\nwhere location = 'NYC'\ngroup by time_bucket('1week', timec)\norder by time_bucket('1week', timec);\n         time_bucket          | min | ?column? |      stddev      \n------------------------------+-----+----------+------------------\n Sun Dec 27 16:00:00 2009 PST | NYC |      210 | 7.07106781186548\n Sun Oct 28 17:00:00 2018 PDT | NYC |      190 |               10\n\n-- TEST5 --\n---------test with having clause ----------------------\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\ncreate materialized view mat_m1( timec, minl, sumth, stddevh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec)\nhaving stddev(humidity) is not null WITH NO DATA;\n;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\nSET ROLE :ROLE_SUPERUSER;\ninsert into  :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect\n time_bucket('1week', timec), min(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec)\nhaving stddev(humidity) is not null;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n-- should have same results --\nselect * from mat_m1\norder by sumth;\n            timec             | minl | sumth |     stddevh      \n------------------------------+------+-------+------------------\n Sun Oct 28 17:00:00 2018 PDT | NYC  |   190 |               10\n Sun Dec 27 16:00:00 2009 PST | NYC  |   620 | 23.8746727726266\n\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec)\nhaving stddev(humidity) is not null\norder by sum(temperature)+sum(humidity);\n         time_bucket          | min | ?column? |      stddev      \n------------------------------+-----+----------+------------------\n Sun Oct 28 17:00:00 2018 PDT | NYC |      190 |               10\n Sun Dec 27 16:00:00 2009 PST | NYC |      620 | 23.8746727726266\n\n-- TEST6 --\n--group by with more than 1 group column\n-- having clause with a mix of columns from select list + others\ndrop table conditions cascade;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to 2 other objects\nCREATE TABLE conditions (\n      timec       TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        numeric NULL,\n      highp       numeric null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\ninsert into conditions\nselect generate_series('2018-12-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'POR', 55, 75, 40, 70;\ninsert into conditions\nselect generate_series('2018-11-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'NYC', 35, 45, 50, 40;\ninsert into conditions\nselect generate_series('2018-11-01 00:00'::timestamp, '2018-12-15 00:00'::timestamp, '1 day'), 'LA', 73, 55, 71, 28;\n--naming with AS clauses\nCREATE MATERIALIZED VIEW mat_naming\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec) as bucket, location as loc, sum(temperature)+sum(humidity) as sumth, stddev(humidity)\nfrom conditions\ngroup by bucket, loc\nhaving min(location) >= 'NYC' and avg(temperature) > 20 WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_naming'\n\\gset\nselect attnum , attname from pg_attribute\nwhere attnum > 0 and attrelid =\n(Select oid from pg_class where relname like :'MAT_TABLE_NAME')\norder by attnum, attname;\n attnum | attname \n--------+---------\n      1 | bucket\n      2 | loc\n      3 | sumth\n      4 | stddev\n\nDROP MATERIALIZED VIEW mat_naming;\n--naming with default names\nCREATE MATERIALIZED VIEW mat_naming\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec), location, sum(temperature)+sum(humidity) as sumth, stddev(humidity)\nfrom conditions\ngroup by 1,2\nhaving min(location) >= 'NYC' and avg(temperature) > 20 WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_naming'\n\\gset\nselect attnum , attname from pg_attribute\nwhere attnum > 0 and attrelid =\n(Select oid from pg_class where relname like :'MAT_TABLE_NAME')\norder by attnum, attname;\n attnum |   attname   \n--------+-------------\n      1 | time_bucket\n      2 | location\n      3 | sumth\n      4 | stddev\n\nDROP MATERIALIZED VIEW mat_naming;\n--naming with view col names\nCREATE MATERIALIZED VIEW mat_naming(bucket, loc, sum_t_h, stdd)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec), location, sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by 1,2\nhaving min(location) >= 'NYC' and avg(temperature) > 20 WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_naming'\n\\gset\nselect attnum , attname from pg_attribute\nwhere attnum > 0 and attrelid =\n(Select oid from pg_class where relname like :'MAT_TABLE_NAME')\norder by attnum, attname;\n attnum | attname \n--------+---------\n      1 | bucket\n      2 | loc\n      3 | sum_t_h\n      4 | stdd\n\nDROP MATERIALIZED VIEW mat_naming;\nCREATE MATERIALIZED VIEW mat_m1(timec, minl, sumth, stddevh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by  time_bucket('1week', timec)\nhaving min(location) >= 'NYC' and avg(temperature) > 20 WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\nselect attnum , attname from pg_attribute\nwhere attnum > 0 and attrelid =\n(Select oid from pg_class where relname like :'MAT_TABLE_NAME')\norder by attnum, attname;\n attnum | attname \n--------+---------\n      1 | timec\n      2 | minl\n      3 | sumth\n      4 | stddevh\n\nSET ROLE :ROLE_SUPERUSER;\ninsert into  :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect\n time_bucket('1week', timec), min(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec)\nhaving min(location) >= 'NYC' and avg(temperature) > 20;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--should have same results --\nselect timec, minl, sumth, stddevh\nfrom mat_m1\norder by timec, minl;\n            timec             | minl | sumth |     stddevh      \n------------------------------+------+-------+------------------\n Sun Dec 16 16:00:00 2018 PST | NYC  |  1470 | 15.5662356498831\n Sun Dec 23 16:00:00 2018 PST | NYC  |  1470 | 15.5662356498831\n Sun Dec 30 16:00:00 2018 PST | NYC  |   210 | 21.2132034355964\n\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by  time_bucket('1week', timec)\nhaving min(location) >= 'NYC' and avg(temperature) > 20 and avg(lowp) > 10\norder by time_bucket('1week', timec), min(location);\n         time_bucket          | min | ?column? |      stddev      \n------------------------------+-----+----------+------------------\n Sun Dec 16 16:00:00 2018 PST | NYC |     1470 | 15.5662356498831\n Sun Dec 23 16:00:00 2018 PST | NYC |     1470 | 15.5662356498831\n Sun Dec 30 16:00:00 2018 PST | NYC |      210 | 21.2132034355964\n\n--check view defintion in information views\nselect view_name, view_definition from timescaledb_information.continuous_aggregates\nwhere view_name::text like 'mat_m1';\n view_name |                                      view_definition                                      \n-----------+-------------------------------------------------------------------------------------------\n mat_m1    |  SELECT time_bucket('@ 7 days'::interval, timec) AS timec,                               +\n           |     min(location) AS minl,                                                               +\n           |     (sum(temperature) + sum(humidity)) AS sumth,                                         +\n           |     stddev(humidity) AS stddevh                                                          +\n           |    FROM conditions                                                                       +\n           |   GROUP BY (time_bucket('@ 7 days'::interval, timec))                                    +\n           |  HAVING ((min(location) >= 'NYC'::text) AND (avg(temperature) > (20)::double precision));\n\n--TEST6 -- select from internal view\nSET ROLE :ROLE_SUPERUSER;\ninsert into :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect * from :\"PART_VIEW_SCHEMA\".:\"PART_VIEW_NAME\";\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--lets drop the view and check\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_13_24_chunk\ndrop table conditions;\nCREATE TABLE conditions (\n      timec       TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\ninsert into conditions\nselect generate_series('2018-12-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'POR', 55, 75, 40, 70, NULL;\ninsert into conditions\nselect generate_series('2018-11-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'NYC', 35, 45, 50, 40, NULL;\ninsert into conditions\nselect generate_series('2018-11-01 00:00'::timestamp, '2018-12-15 00:00'::timestamp, '1 day'), 'LA', 73, 55, NULL, 28, NULL;\nSELECT\n  $$\n  select time_bucket('1week', timec) ,\n  min(location) as col1, sum(temperature)+sum(humidity) as col2, stddev(humidity) as col3, min(allnull) as col4\n  from conditions\n  group by  time_bucket('1week', timec)\n  having min(location) >= 'NYC' and avg(temperature) > 20\n  $$ AS \"QUERY\"\n\\gset\n\\set ECHO errors\npsql:include/cont_agg_equal.sql:8: NOTICE:  materialized view \"mat_test\" does not exist, skipping\n                           ?column?                            | count \n---------------------------------------------------------------+-------\n Number of rows different between view and original (expect 0) |     0\n\nSELECT\n  $$\n  select time_bucket('1week', timec), location,\n  sum(temperature)+sum(humidity) as col2, stddev(humidity) as col3, min(allnull) as col4\n  from conditions\n  group by location, time_bucket('1week', timec)\n  $$ AS \"QUERY\"\n\\gset\n\\set ECHO errors\npsql:include/cont_agg_equal.sql:8: NOTICE:  drop cascades to table _timescaledb_internal._hyper_15_34_chunk\n                           ?column?                            | count \n---------------------------------------------------------------+-------\n Number of rows different between view and original (expect 0) |     0\n\n--TEST7 -- drop tests for view and hypertable\n--DROP tests\n\\set ON_ERROR_STOP 0\nSELECT  h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\",\n       direct_view_name as \"DIR_VIEW_NAME\",\n       direct_view_schema as \"DIR_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_test'\n\\gset\nDROP TABLE :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\";\nERROR:  cannot drop table _timescaledb_internal._materialized_hypertable_16 because other objects depend on it\nDROP VIEW :\"PART_VIEW_SCHEMA\".:\"PART_VIEW_NAME\";\nERROR:  cannot drop the partial/direct view because it is required by a continuous aggregate\nDROP VIEW :\"DIR_VIEW_SCHEMA\".:\"DIR_VIEW_NAME\";\nERROR:  cannot drop the partial/direct view because it is required by a continuous aggregate\n\\set ON_ERROR_STOP 1\n--catalog entry still there;\nSELECT count(*)\nFROM _timescaledb_catalog.continuous_agg ca\nWHERE user_view_name = 'mat_test';\n count \n-------\n     1\n\n--mat table, user_view, direct view and partial view all there\nselect count(*) from pg_class where relname = :'PART_VIEW_NAME';\n count \n-------\n     1\n\nselect count(*) from pg_class where relname = :'MAT_TABLE_NAME';\n count \n-------\n     1\n\nselect count(*) from pg_class where relname = :'DIR_VIEW_NAME';\n count \n-------\n     1\n\nselect count(*) from pg_class where relname = 'mat_test';\n count \n-------\n     1\n\nDROP MATERIALIZED VIEW mat_test;\nNOTICE:  drop cascades to 2 other objects\n--catalog entry should be gone\nSELECT count(*)\nFROM _timescaledb_catalog.continuous_agg ca\nWHERE user_view_name = 'mat_test';\n count \n-------\n     0\n\n--mat table, user_view, direct view and partial view all gone\nselect count(*) from pg_class where relname = :'PART_VIEW_NAME';\n count \n-------\n     0\n\nselect count(*) from pg_class where relname = :'MAT_TABLE_NAME';\n count \n-------\n     0\n\nselect count(*) from pg_class where relname = :'DIR_VIEW_NAME';\n count \n-------\n     0\n\nselect count(*) from pg_class where relname = 'mat_test';\n count \n-------\n     0\n\n--test dropping raw table\nDROP TABLE conditions;\nCREATE TABLE conditions (\n      timec       TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\n--no data in hyper table on purpose so that CASCADE is not required because of chunks\nCREATE MATERIALIZED VIEW mat_drop_test(timec, minl, sumt , sumh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1day', timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by time_bucket('1day', timec) WITH NO DATA;\n\\set ON_ERROR_STOP 0\nDROP TABLE conditions;\nERROR:  cannot drop table conditions because other objects depend on it\n\\set ON_ERROR_STOP 1\n--insert data now\ninsert into conditions\nselect generate_series('2018-12-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'POR', 55, 75, 40, 70, NULL;\ninsert into conditions\nselect generate_series('2018-11-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'NYC', 35, 45, 50, 40, NULL;\ninsert into conditions\nselect generate_series('2018-11-01 00:00'::timestamp, '2018-12-15 00:00'::timestamp, '1 day'), 'LA', 73, 55, NULL, 28, NULL;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_drop_test'\n\\gset\nSET client_min_messages TO NOTICE;\nCALL refresh_continuous_aggregate('mat_drop_test', NULL, NULL);\n--force invalidation\ninsert into conditions\nselect generate_series('2017-11-01 00:00'::timestamp, '2017-12-15 00:00'::timestamp, '1 day'), 'LA', 73, 55, NULL, 28, NULL;\nselect count(*) from _timescaledb_catalog.continuous_aggs_invalidation_threshold;\n count \n-------\n     1\n\nselect count(*) from _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log;\n count \n-------\n     8\n\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to 2 other objects\n--catalog entry should be gone\nSELECT count(*)\nFROM _timescaledb_catalog.continuous_agg ca\nWHERE user_view_name = 'mat_drop_test';\n count \n-------\n     0\n\nselect count(*) from _timescaledb_catalog.continuous_aggs_invalidation_threshold;\n count \n-------\n     0\n\nselect count(*) from _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log;\n count \n-------\n     0\n\nselect count(*) from _timescaledb_catalog.continuous_aggs_materialization_invalidation_log;\n count \n-------\n     0\n\nSELECT * FROM _timescaledb_catalog.bgw_job;\n id | application_name | schedule_interval | max_runtime | max_retries | retry_period | proc_schema | proc_name | owner | scheduled | fixed_schedule | initial_start | hypertable_id | config | check_schema | check_name | timezone \n----+------------------+-------------------+-------------+-------------+--------------+-------------+-----------+-------+-----------+----------------+---------------+---------------+--------+--------------+------------+----------\n\n--mat table, user_view, and partial view all gone\nselect count(*) from pg_class where relname = :'PART_VIEW_NAME';\n count \n-------\n     0\n\nselect count(*) from pg_class where relname = :'MAT_TABLE_NAME';\n count \n-------\n     0\n\nselect count(*) from pg_class where relname = 'mat_drop_test';\n count \n-------\n     0\n\n--TEST With options\nCREATE TABLE conditions (\n      timec       TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\nCREATE MATERIALIZED VIEW mat_with_test(timec, minl, sumt , sumh)\nWITH (timescaledb.continuous,\n      timescaledb.materialized_only=true)\nas\nselect time_bucket('1day', timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by time_bucket('1day', timec), location, humidity, temperature WITH NO DATA;\nSELECT add_continuous_aggregate_policy('mat_with_test', NULL, '5 h'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1001\n\nSELECT alter_job(id, schedule_interval => '1h') FROM _timescaledb_catalog.bgw_job;\n                                                                                                                         alter_job                                                                                                                          \n------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1001,\"@ 1 hour\",\"@ 0\",-1,\"@ 12 hours\",t,\"{\"\"end_offset\"\": \"\"@ 5 hours\"\", \"\"start_offset\"\": null, \"\"mat_hypertable_id\"\": 20}\",-infinity,_timescaledb_functions.policy_refresh_continuous_aggregate_check,f,,,\"Refresh Continuous Aggregate Policy [1001]\")\n\nSELECT schedule_interval FROM _timescaledb_catalog.bgw_job;\n schedule_interval \n-------------------\n @ 1 hour\n\nSELECT alter_job(id, schedule_interval => '2h') FROM _timescaledb_catalog.bgw_job;\n                                                                                                                          alter_job                                                                                                                          \n-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1001,\"@ 2 hours\",\"@ 0\",-1,\"@ 12 hours\",t,\"{\"\"end_offset\"\": \"\"@ 5 hours\"\", \"\"start_offset\"\": null, \"\"mat_hypertable_id\"\": 20}\",-infinity,_timescaledb_functions.policy_refresh_continuous_aggregate_check,f,,,\"Refresh Continuous Aggregate Policy [1001]\")\n\nSELECT schedule_interval FROM _timescaledb_catalog.bgw_job;\n schedule_interval \n-------------------\n @ 2 hours\n\nselect indexname, indexdef from pg_indexes where tablename =\n(SELECT h.table_name\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_with_test')\norder by indexname;\n               indexname               |                                                             indexdef                                                             \n---------------------------------------+----------------------------------------------------------------------------------------------------------------------------------\n _materialized_hypertable_20_timec_idx | CREATE INDEX _materialized_hypertable_20_timec_idx ON _timescaledb_internal._materialized_hypertable_20 USING btree (timec DESC)\n\nDROP MATERIALIZED VIEW mat_with_test;\n--no additional indexes\nCREATE MATERIALIZED VIEW mat_with_test(timec, minl, sumt , sumh)\nWITH (timescaledb.continuous,\n      timescaledb.materialized_only=true,\n      timescaledb.create_group_indexes=false)\nas\nselect time_bucket('1day', timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by time_bucket('1day', timec), location, humidity, temperature WITH NO DATA;\nselect indexname, indexdef from pg_indexes where tablename =\n(SELECT h.table_name\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_with_test');\n               indexname               |                                                             indexdef                                                             \n---------------------------------------+----------------------------------------------------------------------------------------------------------------------------------\n _materialized_hypertable_21_timec_idx | CREATE INDEX _materialized_hypertable_21_timec_idx ON _timescaledb_internal._materialized_hypertable_21 USING btree (timec DESC)\n\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 2 other objects\n--test WITH using a hypertable with an integer time dimension\nCREATE TABLE conditions (\n      timec       INT       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec', chunk_time_interval=> 100);\n table_name \n------------\n conditions\n\nCREATE OR REPLACE FUNCTION integer_now_conditions() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(timec), 0) FROM conditions $$;\nSELECT set_integer_now_func('conditions', 'integer_now_conditions');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW mat_with_test(timec, minl, sumt , sumh)\nWITH (timescaledb.continuous,\n      timescaledb.materialized_only=true)\nas\nselect time_bucket(100, timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by time_bucket(100, timec) WITH NO DATA;\nSELECT add_continuous_aggregate_policy('mat_with_test', NULL, 500::integer, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1002\n\nSELECT alter_job(id, schedule_interval => '2h') FROM _timescaledb_catalog.bgw_job;\n                                                                                                                     alter_job                                                                                                                     \n---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1002,\"@ 2 hours\",\"@ 0\",-1,\"@ 12 hours\",t,\"{\"\"end_offset\"\": 500, \"\"start_offset\"\": null, \"\"mat_hypertable_id\"\": 23}\",-infinity,_timescaledb_functions.policy_refresh_continuous_aggregate_check,f,,,\"Refresh Continuous Aggregate Policy [1002]\")\n\nSELECT schedule_interval FROM _timescaledb_catalog.bgw_job;\n schedule_interval \n-------------------\n @ 2 hours\n\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 2 other objects\n--test space partitions\nCREATE TABLE space_table (\n    time BIGINT,\n    dev  BIGINT,\n    data BIGINT\n);\nSELECT create_hypertable(\n    'space_table',\n    'time',\n    chunk_time_interval => 10,\n    partitioning_column => 'dev',\n    number_partitions => 3);\n     create_hypertable     \n---------------------------\n (24,public,space_table,t)\n\nCREATE OR REPLACE FUNCTION integer_now_space_table() returns BIGINT LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), BIGINT '0') FROM space_table $$;\nSELECT set_integer_now_func('space_table', 'integer_now_space_table');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW space_view\nWITH (timescaledb.continuous,\n      timescaledb.materialized_only=true)\nAS SELECT time_bucket('4', time), COUNT(data)\n   FROM space_table\n   GROUP BY 1 WITH NO DATA;\nINSERT INTO space_table VALUES\n  (0, 1, 1), (0, 2, 1), (1, 1, 1), (1, 2, 1),\n  (10, 1, 1), (10, 2, 1), (11, 1, 1), (11, 2, 1);\nSELECT  h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\",\n       direct_view_name as \"DIR_VIEW_NAME\",\n       direct_view_schema as \"DIR_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'space_view'\n\\gset\nSELECT * FROM :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\n  ORDER BY time_bucket;\n time_bucket | count \n-------------+-------\n\nCALL refresh_continuous_aggregate('space_view', NULL, NULL);\nSELECT * FROM space_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     4\n           8 |     4\n\nSELECT * FROM :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\n  ORDER BY time_bucket;\n time_bucket | count \n-------------+-------\n           0 |     4\n           8 |     4\n\nINSERT INTO space_table VALUES (3, 2, 1);\nCALL refresh_continuous_aggregate('space_view', NULL, NULL);\nSELECT * FROM space_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     5\n           8 |     4\n\nSELECT * FROM :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\n  ORDER BY time_bucket;\n time_bucket | count \n-------------+-------\n           0 |     5\n           8 |     4\n\nINSERT INTO space_table VALUES (2, 3, 1);\nCALL refresh_continuous_aggregate('space_view', NULL, NULL);\nSELECT * FROM space_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     6\n           8 |     4\n\nSELECT * FROM :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\n  ORDER BY time_bucket;\n time_bucket | count \n-------------+-------\n           0 |     6\n           8 |     4\n\nDROP TABLE space_table CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_25_60_chunk\n--\n-- TEST FINALIZEFUNC_EXTRA\n--\n-- create special aggregate to test ffunc_extra\n-- Raise warning with the actual type being passed in\nCREATE OR REPLACE FUNCTION fake_ffunc(a int8, b int, c int, d int, x anyelement)\nRETURNS anyelement AS $$\nBEGIN\n RAISE WARNING 'type % %', pg_typeof(d), pg_typeof(x);\n RETURN x;\nEND;\n$$\nLANGUAGE plpgsql;\nCREATE OR REPLACE FUNCTION fake_sfunc(a int8, b int, c int, d int, x anyelement)\nRETURNS int8 AS $$\nBEGIN\n RETURN b;\nEND; $$\nLANGUAGE plpgsql;\nCREATE AGGREGATE aggregate_to_test_ffunc_extra(int, int, int, anyelement) (\n    SFUNC = fake_sfunc,\n    STYPE = int8,\n    COMBINEFUNC = int8pl,\n    FINALFUNC = fake_ffunc,\n    PARALLEL = SAFE,\n    FINALFUNC_EXTRA\n);\nCREATE TABLE conditions (\n      timec       INT       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec', chunk_time_interval=> 100);\n table_name \n------------\n conditions\n\nCREATE OR REPLACE FUNCTION integer_now_conditions() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(timec), 0) FROM conditions $$;\nSELECT set_integer_now_func('conditions', 'integer_now_conditions');\n set_integer_now_func \n----------------------\n \n\ninsert into conditions\nselect generate_series(0, 200, 10), 'POR', 55, 75, 40, 70, NULL;\nCREATE MATERIALIZED VIEW mat_ffunc_test\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket(100, timec), aggregate_to_test_ffunc_extra(timec, 1, 3, 'test'::text)\nfrom conditions\ngroup by time_bucket(100, timec);\nNOTICE:  refreshing continuous aggregate \"mat_ffunc_test\"\nWARNING:  type integer text\nWARNING:  type integer text\nWARNING:  type integer text\nSELECT * FROM mat_ffunc_test ORDER BY time_bucket;\n time_bucket | aggregate_to_test_ffunc_extra \n-------------+-------------------------------\n           0 | \n         100 | \n         200 | \n\nDROP MATERIALIZED view mat_ffunc_test;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_27_65_chunk\nCREATE MATERIALIZED VIEW mat_ffunc_test\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket(100, timec), aggregate_to_test_ffunc_extra(timec, 4, 5, bigint '123')\nfrom conditions\ngroup by time_bucket(100, timec);\nNOTICE:  refreshing continuous aggregate \"mat_ffunc_test\"\nWARNING:  type integer bigint\nWARNING:  type integer bigint\nWARNING:  type integer bigint\nSELECT * FROM mat_ffunc_test ORDER BY time_bucket;\n time_bucket | aggregate_to_test_ffunc_extra \n-------------+-------------------------------\n           0 |                              \n         100 |                              \n         200 |                              \n\n--refresh mat view test when time_bucket is not projected --\nDROP MATERIALIZED VIEW mat_ffunc_test;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_28_66_chunk\nCREATE MATERIALIZED VIEW mat_refresh_test\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect location, max(humidity)\nfrom conditions\ngroup by time_bucket(100, timec), location WITH NO DATA;\ninsert into conditions\nselect generate_series(0, 50, 10), 'NYC', 55, 75, 40, 70, NULL;\nCALL refresh_continuous_aggregate('mat_refresh_test', NULL, NULL);\nSELECT * FROM mat_refresh_test order by 1,2 ;\n location | max \n----------+-----\n NYC      |  75\n POR      |  75\n POR      |  75\n POR      |  75\n\n-- test for bug when group by is not in project list\nCREATE MATERIALIZED VIEW conditions_grpby_view with (timescaledb.continuous, timescaledb.materialized_only=false) as\nselect time_bucket(100, timec),  sum(humidity)\nfrom conditions\ngroup by time_bucket(100, timec), location;\nNOTICE:  refreshing continuous aggregate \"conditions_grpby_view\"\nselect * from conditions_grpby_view order by 1, 2;\n time_bucket | sum \n-------------+-----\n           0 | 450\n           0 | 750\n         100 | 750\n         200 |  75\n\nCREATE MATERIALIZED VIEW conditions_grpby_view2 with (timescaledb.continuous, timescaledb.materialized_only=false) as\nselect time_bucket(100, timec), sum(humidity)\nfrom conditions\ngroup by time_bucket(100, timec), location\nhaving avg(temperature) > 0;\nNOTICE:  refreshing continuous aggregate \"conditions_grpby_view2\"\nselect * from conditions_grpby_view2 order by 1, 2;\n time_bucket | sum \n-------------+-----\n           0 | 450\n           0 | 750\n         100 | 750\n         200 |  75\n\n-- Test internal functions for continuous aggregates\nSELECT test.continuous_aggs_find_view('mat_refresh_test');\n continuous_aggs_find_view \n---------------------------\n \n\n-- Test pseudotype/enum handling\nCREATE TYPE status_enum AS ENUM (\n  'red',\n  'yellow',\n  'green'\n);\nCREATE TABLE cagg_types (\n  time TIMESTAMPTZ NOT NULL,\n  status status_enum,\n  names NAME[],\n  floats FLOAT[]\n);\nSELECT\n  table_name\nFROM\n  create_hypertable('cagg_types', 'time');\n table_name \n------------\n cagg_types\n\nINSERT INTO cagg_types\nSELECT\n  '2000-01-01',\n  'yellow',\n  '{foo,bar,baz}',\n  '{1,2.5,3}';\nCREATE MATERIALIZED VIEW mat_types WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  time_bucket('1d', time),\n  min(status) AS status,\n  max(names) AS names,\n  min(floats) AS floats\nFROM\n  cagg_types\nGROUP BY\n  1;\nNOTICE:  refreshing continuous aggregate \"mat_types\"\nCALL refresh_continuous_aggregate('mat_types',NULL,NULL);\nNOTICE:  continuous aggregate \"mat_types\" is already up-to-date\nSELECT * FROM mat_types;\n         time_bucket          | status |     names     |  floats   \n------------------------------+--------+---------------+-----------\n Fri Dec 31 16:00:00 1999 PST | yellow | {foo,bar,baz} | {1,2.5,3}\n\n-------------------------------------------------------------------------------------\n-- Test issue #2616 where cagg view contains an experssion with several aggregates in\nCREATE TABLE water_consumption\n(\n    sensor_id   integer      NOT NULL,\n    timestamp   timestamp(0) NOT NULL,\n    water_index integer\n);\nSELECT create_hypertable('water_consumption', 'timestamp', 'sensor_id', 2);\nWARNING:  column type \"timestamp without time zone\" used for \"timestamp\" does not follow best practices\n        create_hypertable        \n---------------------------------\n (34,public,water_consumption,t)\n\nINSERT INTO public.water_consumption (sensor_id, timestamp, water_index) VALUES\n  (1, '2010-11-03 09:42:30', 1030),\n  (1, '2010-11-03 09:42:40', 1032),\n  (1, '2010-11-03 09:42:50', 1035),\n  (1, '2010-11-03 09:43:30', 1040),\n  (1, '2010-11-03 09:43:40', 1045),\n  (1, '2010-11-03 09:43:50', 1050),\n  (1, '2010-11-03 09:44:30', 1052),\n  (1, '2010-11-03 09:44:40', 1057),\n  (1, '2010-11-03 09:44:50', 1060),\n  (1, '2010-11-03 09:45:30', 1063),\n  (1, '2010-11-03 09:45:40', 1067),\n  (1, '2010-11-03 09:45:50', 1070);\n-- The test with the view originally reported in the issue.\nCREATE MATERIALIZED VIEW water_consumption_aggregation_minute\n            WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE)\nAS\nSELECT sensor_id,\n       time_bucket(INTERVAL '1 minute', timestamp) + '1 minute' AS timestamp,\n       (max(water_index) - min(water_index))                    AS water_consumption\nFROM water_consumption\nGROUP BY sensor_id, time_bucket(INTERVAL '1 minute', timestamp)\nWITH NO DATA;\nCALL refresh_continuous_aggregate('water_consumption_aggregation_minute', NULL, NULL);\n-- The results of the view and the query over hypertable should be the same\nSELECT * FROM water_consumption_aggregation_minute ORDER BY water_consumption;\n sensor_id |        timestamp         | water_consumption \n-----------+--------------------------+-------------------\n         1 | Wed Nov 03 09:43:00 2010 |                 5\n         1 | Wed Nov 03 09:46:00 2010 |                 7\n         1 | Wed Nov 03 09:45:00 2010 |                 8\n         1 | Wed Nov 03 09:44:00 2010 |                10\n\nSELECT sensor_id,\n       time_bucket(INTERVAL '1 minute', timestamp) + '1 minute' AS timestamp,\n       (max(water_index) - min(water_index))                    AS water_consumption\nFROM water_consumption\nGROUP BY sensor_id, time_bucket(INTERVAL '1 minute', timestamp)\nORDER BY water_consumption;\n sensor_id |        timestamp         | water_consumption \n-----------+--------------------------+-------------------\n         1 | Wed Nov 03 09:43:00 2010 |                 5\n         1 | Wed Nov 03 09:46:00 2010 |                 7\n         1 | Wed Nov 03 09:45:00 2010 |                 8\n         1 | Wed Nov 03 09:44:00 2010 |                10\n\n-- Simplified test, where the view doesn't contain all group by clauses\nCREATE MATERIALIZED VIEW water_consumption_no_select_bucket\n            WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE)\nAS\nSELECT sensor_id,\n       (max(water_index) - min(water_index))                    AS water_consumption\nFROM water_consumption\nGROUP BY sensor_id, time_bucket(INTERVAL '1 minute', timestamp)\nWITH NO DATA;\nCALL refresh_continuous_aggregate('water_consumption_no_select_bucket', NULL, NULL);\n-- The results of the view and the query over hypertable should be the same\nSELECT * FROM water_consumption_no_select_bucket ORDER BY water_consumption;\n sensor_id | water_consumption \n-----------+-------------------\n         1 |                 5\n         1 |                 7\n         1 |                 8\n         1 |                10\n\nSELECT sensor_id,\n       (max(water_index) - min(water_index))                    AS water_consumption\nFROM water_consumption\nGROUP BY sensor_id, time_bucket(INTERVAL '1 minute', timestamp)\nORDER BY water_consumption;\n sensor_id | water_consumption \n-----------+-------------------\n         1 |                 5\n         1 |                 7\n         1 |                 8\n         1 |                10\n\n-- The test with SELECT matching GROUP BY and placing aggregate expression not the last\nCREATE MATERIALIZED VIEW water_consumption_aggregation_no_addition\n            WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE)\nAS\nSELECT sensor_id,\n       (max(water_index) - min(water_index))                    AS water_consumption,\n       time_bucket(INTERVAL '1 minute', timestamp) AS timestamp\nFROM water_consumption\nGROUP BY sensor_id, time_bucket(INTERVAL '1 minute', timestamp)\nWITH NO DATA;\nCALL refresh_continuous_aggregate('water_consumption_aggregation_no_addition', NULL, NULL);\n-- The results of the view and the query over hypertable should be the same\nSELECT * FROM water_consumption_aggregation_no_addition ORDER BY water_consumption;\n sensor_id | water_consumption |        timestamp         \n-----------+-------------------+--------------------------\n         1 |                 5 | Wed Nov 03 09:42:00 2010\n         1 |                 7 | Wed Nov 03 09:45:00 2010\n         1 |                 8 | Wed Nov 03 09:44:00 2010\n         1 |                10 | Wed Nov 03 09:43:00 2010\n\nSELECT sensor_id,\n       (max(water_index) - min(water_index))                    AS water_consumption,\n       time_bucket(INTERVAL '1 minute', timestamp) AS timestamp\nFROM water_consumption\nGROUP BY sensor_id, time_bucket(INTERVAL '1 minute', timestamp)\nORDER BY water_consumption;\n sensor_id | water_consumption |        timestamp         \n-----------+-------------------+--------------------------\n         1 |                 5 | Wed Nov 03 09:42:00 2010\n         1 |                 7 | Wed Nov 03 09:45:00 2010\n         1 |                 8 | Wed Nov 03 09:44:00 2010\n         1 |                10 | Wed Nov 03 09:43:00 2010\n\nDROP TABLE water_consumption CASCADE;\nNOTICE:  drop cascades to 6 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_35_73_chunk\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_36_74_chunk\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_37_75_chunk\n----\n--- github issue 2655 ---\ncreate table raw_data(time timestamptz, search_query text, cnt integer, cnt2 integer);\nselect create_hypertable('raw_data','time', chunk_time_interval=>'15 days'::interval);\n   create_hypertable    \n------------------------\n (38,public,raw_data,t)\n\ninsert into raw_data select '2000-01-01','Q1';\n--having has exprs that appear in select\nCREATE MATERIALIZED VIEW search_query_count_1m WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\n SELECT  search_query,count(search_query) as count,\n         time_bucket(INTERVAL '1 minute', time) AS bucket\n FROM raw_data\n WHERE search_query is not null AND LENGTH(TRIM(both from search_query))>0\n GROUP BY search_query, bucket HAVING count(search_query) > 3 OR sum(cnt) > 1;\nNOTICE:  refreshing continuous aggregate \"search_query_count_1m\"\n--having has aggregates + grp by columns that appear in select\nCREATE MATERIALIZED VIEW search_query_count_2 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\n SELECT  search_query,count(search_query) as count, sum(cnt),\n         time_bucket(INTERVAL '1 minute', time) AS bucket\n FROM raw_data\n WHERE search_query is not null AND LENGTH(TRIM(both from search_query))>0\n GROUP BY search_query, bucket\nHAVING count(search_query) > 3 OR sum(cnt) > 1 OR\n       ( sum(cnt) + count(cnt)) > 1\n       AND search_query = 'Q1';\nNOTICE:  refreshing continuous aggregate \"search_query_count_2\"\nCREATE MATERIALIZED VIEW search_query_count_3 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\n SELECT  search_query,count(search_query) as count, sum(cnt),\n         time_bucket(INTERVAL '1 minute', time) AS bucket\n FROM raw_data\n WHERE search_query is not null AND LENGTH(TRIM(both from search_query))>0\n GROUP BY cnt +cnt2 , bucket, search_query\n HAVING cnt + cnt2 + sum(cnt) > 2 or count(cnt2) > 10;\nNOTICE:  refreshing continuous aggregate \"search_query_count_3\"\ninsert into raw_data select '2000-01-01 00:00+0','Q1', 1, 100;\ninsert into raw_data select '2000-01-01 00:00+0','Q1', 2, 200;\ninsert into raw_data select '2000-01-01 00:00+0','Q1', 3, 300;\ninsert into raw_data select '2000-01-02 00:00+0','Q2', 10, 10;\ninsert into raw_data select '2000-01-02 00:00+0','Q2', 20, 20;\nCALL refresh_continuous_aggregate('search_query_count_1m', NULL, NULL);\nSELECT * FROM search_query_count_1m ORDER BY 1, 2;\n search_query | count |            bucket            \n--------------+-------+------------------------------\n Q1           |     3 | Fri Dec 31 16:00:00 1999 PST\n Q2           |     2 | Sat Jan 01 16:00:00 2000 PST\n\n--only 1 of these should appear in the result\ninsert into raw_data select '2000-01-02 00:00+0','Q3', 0, 0;\ninsert into raw_data select '2000-01-03 00:00+0','Q4', 20, 20;\nCALL refresh_continuous_aggregate('search_query_count_1m', NULL, NULL);\nSELECT * FROM search_query_count_1m ORDER BY 1, 2;\n search_query | count |            bucket            \n--------------+-------+------------------------------\n Q1           |     3 | Fri Dec 31 16:00:00 1999 PST\n Q2           |     2 | Sat Jan 01 16:00:00 2000 PST\n Q4           |     1 | Sun Jan 02 16:00:00 2000 PST\n\n--refresh search_query_count_2---\nCALL refresh_continuous_aggregate('search_query_count_2', NULL, NULL);\nSELECT * FROM search_query_count_2 ORDER BY 1, 2;\n search_query | count | sum |            bucket            \n--------------+-------+-----+------------------------------\n Q1           |     3 |   6 | Fri Dec 31 16:00:00 1999 PST\n Q2           |     2 |  30 | Sat Jan 01 16:00:00 2000 PST\n Q4           |     1 |  20 | Sun Jan 02 16:00:00 2000 PST\n\n--refresh search_query_count_3---\nCALL refresh_continuous_aggregate('search_query_count_3', NULL, NULL);\nSELECT * FROM search_query_count_3 ORDER BY 1, 2, 3;\n search_query | count | sum |            bucket            \n--------------+-------+-----+------------------------------\n Q1           |     1 |   1 | Fri Dec 31 16:00:00 1999 PST\n Q1           |     1 |   2 | Fri Dec 31 16:00:00 1999 PST\n Q1           |     1 |   3 | Fri Dec 31 16:00:00 1999 PST\n Q2           |     1 |  10 | Sat Jan 01 16:00:00 2000 PST\n Q2           |     1 |  20 | Sat Jan 01 16:00:00 2000 PST\n Q4           |     1 |  20 | Sun Jan 02 16:00:00 2000 PST\n\n--- TEST enable compression on continuous aggregates\nCREATE VIEW cagg_compression_status as\nSELECT ca.mat_hypertable_id AS mat_htid,\n       ca.user_view_name AS cagg_name ,\n       h.schema_name AS mat_schema_name,\n       h.table_name AS mat_table_name,\n       ca.materialized_only\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\n;\nSELECT mat_htid AS \"MAT_HTID\"\n     , mat_schema_name || '.' || mat_table_name AS \"MAT_HTNAME\"\n     , mat_table_name AS \"MAT_TABLE_NAME\"\nFROM cagg_compression_status\nWHERE cagg_name = 'search_query_count_3' \\gset\nALTER MATERIALIZED VIEW search_query_count_3 SET (timescaledb.compress = 'true');\nNOTICE:  defaulting compress_orderby to bucket,search_query\nSELECT cagg_name, mat_table_name\nFROM cagg_compression_status where cagg_name = 'search_query_count_3';\n      cagg_name       |       mat_table_name        \n----------------------+-----------------------------\n search_query_count_3 | _materialized_hypertable_41\n\n\\x\nSELECT * FROM timescaledb_information.compression_settings\nWHERE hypertable_name = :'MAT_TABLE_NAME';\n-[ RECORD 1 ]----------+----------------------------\nhypertable_schema      | _timescaledb_internal\nhypertable_name        | _materialized_hypertable_41\nattname                | bucket\nsegmentby_column_index | \norderby_column_index   | 1\norderby_asc            | t\norderby_nullsfirst     | f\n-[ RECORD 2 ]----------+----------------------------\nhypertable_schema      | _timescaledb_internal\nhypertable_name        | _materialized_hypertable_41\nattname                | search_query\nsegmentby_column_index | \norderby_column_index   | 2\norderby_asc            | t\norderby_nullsfirst     | f\n\n\\x\nSELECT compress_chunk(ch)\nFROM show_chunks('search_query_count_3') ch;\n              compress_chunk              \n------------------------------------------\n _timescaledb_internal._hyper_41_79_chunk\n\nSELECT * from search_query_count_3 ORDER BY 1, 2, 3;\n search_query | count | sum |            bucket            \n--------------+-------+-----+------------------------------\n Q1           |     1 |   1 | Fri Dec 31 16:00:00 1999 PST\n Q1           |     1 |   2 | Fri Dec 31 16:00:00 1999 PST\n Q1           |     1 |   3 | Fri Dec 31 16:00:00 1999 PST\n Q2           |     1 |  10 | Sat Jan 01 16:00:00 2000 PST\n Q2           |     1 |  20 | Sat Jan 01 16:00:00 2000 PST\n Q4           |     1 |  20 | Sun Jan 02 16:00:00 2000 PST\n\n-- insert into a new region of the hypertable and then refresh the cagg\n-- (note we still do not support refreshes into existing regions.\n-- cagg chunks do not map 1-1 to hypertabl regions. They encompass\n-- more data\n-- ).\ninsert into raw_data select '2000-05-01 00:00+0','Q3', 0, 0;\n-- On PG >= 14 the refresh test below will pass because we added support for UPDATE/DELETE on compressed chunks in PR #5339\n\\set ON_ERROR_STOP 0\nCALL refresh_continuous_aggregate('search_query_count_3', NULL, '2000-06-01 00:00+0'::timestamptz);\nCALL refresh_continuous_aggregate('search_query_count_3', '2000-05-01 00:00+0'::timestamptz, '2000-06-01 00:00+0'::timestamptz);\nNOTICE:  continuous aggregate \"search_query_count_3\" is already up-to-date\n\\set ON_ERROR_STOP 1\n--insert row\ninsert into raw_data select '2001-05-10 00:00+0','Q3', 100, 100;\n--this should succeed since it does not refresh any compressed regions in the cagg\nCALL refresh_continuous_aggregate('search_query_count_3', '2001-05-01 00:00+0'::timestamptz, '2001-06-01 00:00+0'::timestamptz);\n--verify watermark and check that chunks are compressed\nSELECT _timescaledb_functions.to_timestamp(w) FROM _timescaledb_functions.cagg_watermark(:'MAT_HTID') w;\n         to_timestamp         \n------------------------------\n Wed May 09 17:01:00 2001 PDT\n\nSELECT chunk_name, range_start, range_end, is_compressed\nFROM timescaledb_information.chunks\nWHERE hypertable_name = :'MAT_TABLE_NAME'\nORDER BY 1;\n     chunk_name     |         range_start          |          range_end           | is_compressed \n--------------------+------------------------------+------------------------------+---------------\n _hyper_41_79_chunk | Fri Dec 24 16:00:00 1999 PST | Mon May 22 17:00:00 2000 PDT | t\n _hyper_41_83_chunk | Sun Mar 18 16:00:00 2001 PST | Wed Aug 15 17:00:00 2001 PDT | f\n\nSELECT * FROM _timescaledb_catalog.continuous_aggs_materialization_invalidation_log\nWHERE materialization_id = :'MAT_HTID' ORDER BY 1, 2,3;\n materialization_id | lowest_modified_value | greatest_modified_value \n--------------------+-----------------------+-------------------------\n                 41 |  -9223372036854775808 |     -210866803200000001\n                 41 |       959817600000000 |         988675199999999\n                 41 |       991353600000000 |     9223372036854775807\n\nSELECT * from search_query_count_3\nWHERE bucket > '2001-01-01'\nORDER BY 1, 2, 3;\n search_query | count | sum |            bucket            \n--------------+-------+-----+------------------------------\n Q3           |     1 | 100 | Wed May 09 17:00:00 2001 PDT\n\n--now disable compression , will error out --\n\\set ON_ERROR_STOP 0\nALTER MATERIALIZED VIEW search_query_count_3 SET (timescaledb.compress = 'false');\nERROR:  cannot disable columnstore on hypertable with columnstore chunks\n\\set ON_ERROR_STOP 1\nSELECT decompress_chunk(format('%I.%I', schema_name, table_name))\nFROM _timescaledb_catalog.chunk\nWHERE hypertable_id = :'MAT_HTID' and status = 1;\n             decompress_chunk             \n------------------------------------------\n _timescaledb_internal._hyper_41_79_chunk\n\n--disable compression on cagg after decompressing all chunks--\nALTER MATERIALIZED VIEW search_query_count_3 SET (timescaledb.compress = 'false');\nSELECT cagg_name, mat_table_name\nFROM cagg_compression_status where cagg_name = 'search_query_count_3';\n      cagg_name       |       mat_table_name        \n----------------------+-----------------------------\n search_query_count_3 | _materialized_hypertable_41\n\nSELECT view_name, materialized_only, compression_enabled\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'search_query_count_3';\n      view_name       | materialized_only | compression_enabled \n----------------------+-------------------+---------------------\n search_query_count_3 | f                 | f\n\n-- TEST caggs on table with more columns than in the cagg view defn --\nCREATE TABLE test_morecols ( time TIMESTAMPTZ NOT NULL,\n                             val1 INTEGER, val2 INTEGER, val3 INTEGER, val4 INTEGER,\n                             val5 INTEGER,  val6 INTEGER, val7 INTEGER, val8 INTEGER);\nSELECT create_hypertable('test_morecols', 'time', chunk_time_interval=> '7 days'::interval);\n      create_hypertable      \n-----------------------------\n (43,public,test_morecols,t)\n\nINSERT INTO test_morecols\nSELECT generate_series('2018-12-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 55, 75, 40, 70, NULL, 100, 200, 200;\nCREATE MATERIALIZED VIEW test_morecols_cagg with (timescaledb.continuous, timescaledb.materialized_only=false)\nAS SELECT time_bucket('30 days',time), avg(val1),  count(val2)\n FROM test_morecols GROUP BY 1;\nNOTICE:  refreshing continuous aggregate \"test_morecols_cagg\"\nALTER MATERIALIZED VIEW test_morecols_cagg SET (timescaledb.compress='true');\nNOTICE:  defaulting compress_orderby to time_bucket\nSELECT compress_chunk(ch) FROM show_chunks('test_morecols_cagg') ch;\n              compress_chunk              \n------------------------------------------\n _timescaledb_internal._hyper_44_89_chunk\n\nSELECT * FROM test_morecols_cagg ORDER BY time_bucket;\n         time_bucket          |         avg         | count \n------------------------------+---------------------+-------\n Fri Nov 23 16:00:00 2018 PST | 55.0000000000000000 |    23\n Sun Dec 23 16:00:00 2018 PST | 55.0000000000000000 |     8\n\nSELECT view_name, materialized_only, compression_enabled\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_morecols_cagg';\n     view_name      | materialized_only | compression_enabled \n--------------------+-------------------+---------------------\n test_morecols_cagg | f                 | t\n\n--should keep compressed option, modify only materialized --\nALTER MATERIALIZED VIEW test_morecols_cagg SET (timescaledb.materialized_only='true');\nSELECT view_name, materialized_only, compression_enabled\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_morecols_cagg';\n     view_name      | materialized_only | compression_enabled \n--------------------+-------------------+---------------------\n test_morecols_cagg | t                 | t\n\nCREATE TABLE issue3248(filler_1 int, filler_2 int, filler_3 int, time timestamptz NOT NULL, device_id int, v0 int, v1 int, v2 float, v3 float);\nCREATE INDEX ON issue3248(time DESC);\nCREATE INDEX ON issue3248(device_id,time DESC);\nSELECT create_hypertable('issue3248','time',create_default_indexes:=false);\n    create_hypertable    \n-------------------------\n (46,public,issue3248,t)\n\nALTER TABLE issue3248 DROP COLUMN filler_1;\nINSERT INTO issue3248(time,device_id,v0,v1,v2,v3)\nSELECT time, device_id, device_id+1,  device_id + 2, device_id + 0.5, NULL\nFROM generate_series('2000-01-01 0:00:00+0'::timestamptz,'2000-01-05 23:55:00+0','8h') gtime(time),\n     generate_series(1,5,1) gdevice(device_id);\nALTER TABLE issue3248 DROP COLUMN filler_2;\nINSERT INTO issue3248(time,device_id,v0,v1,v2,v3)\nSELECT time, device_id, device_id-1, device_id + 2, device_id + 0.5, NULL\nFROM generate_series('2000-01-06 0:00:00+0'::timestamptz,'2000-01-12 23:55:00+0','8h') gtime(time),\n     generate_series(1,5,1) gdevice(device_id);\nALTER TABLE issue3248 DROP COLUMN filler_3;\nINSERT INTO issue3248(time,device_id,v0,v1,v2,v3)\nSELECT time, device_id, device_id, device_id + 2, device_id + 0.5, NULL\nFROM generate_series('2000-01-13 0:00:00+0'::timestamptz,'2000-01-19 23:55:00+0','8h') gtime(time),\n     generate_series(1,5,1) gdevice(device_id);\nANALYZE issue3248;\nCREATE materialized view issue3248_cagg WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS SELECT time_bucket('1h',time), device_id, min(v0), max(v1), avg(v2)\nFROM issue3248 GROUP BY 1,2;\nNOTICE:  refreshing continuous aggregate \"issue3248_cagg\"\nSELECT\n  FROM issue3248 AS m,\n       LATERAL(SELECT m FROM issue3248_cagg WHERE avg IS NULL LIMIT 1) AS lat;\n--\n\n-- test that option create_group_indexes is taken into account\nCREATE TABLE test_group_idx (\ntime timestamptz,\nsymbol int,\nvalue numeric\n);\nselect create_hypertable('test_group_idx', 'time');\n      create_hypertable       \n------------------------------\n (48,public,test_group_idx,t)\n\ninsert into test_group_idx\nselect t, round(random()*10), random()*5\nfrom generate_series('2020-01-01', '2020-02-25', INTERVAL '12 hours') t;\ncreate materialized view cagg_index_true\nwith (timescaledb.continuous, timescaledb.materialized_only=false, timescaledb.create_group_indexes=true) as\nselect\n\ttime_bucket('1 day', \"time\") as bucket,\n\tsum(value),\n\tsymbol\nfrom test_group_idx\ngroup by bucket, symbol;\nNOTICE:  refreshing continuous aggregate \"cagg_index_true\"\ncreate materialized view cagg_index_false\nwith (timescaledb.continuous, timescaledb.materialized_only=false, timescaledb.create_group_indexes=false) as\nselect\n\ttime_bucket('1 day', \"time\") as bucket,\n\tsum(value),\n\tsymbol\nfrom test_group_idx\ngroup by bucket, symbol;\nNOTICE:  refreshing continuous aggregate \"cagg_index_false\"\ncreate materialized view cagg_index_default\nwith (timescaledb.continuous, timescaledb.materialized_only=false) as\nselect\n\ttime_bucket('1 day', \"time\") as bucket,\n\tsum(value),\n\tsymbol\nfrom test_group_idx\ngroup by bucket, symbol;\nNOTICE:  refreshing continuous aggregate \"cagg_index_default\"\n-- see corresponding materialization_hypertables\nselect view_name, materialization_hypertable_name from timescaledb_information.continuous_aggregates ca\nwhere view_name like 'cagg_index_%' ORDER BY view_name;\n     view_name      | materialization_hypertable_name \n--------------------+---------------------------------\n cagg_index_default | _materialized_hypertable_51\n cagg_index_false   | _materialized_hypertable_50\n cagg_index_true    | _materialized_hypertable_49\n\n-- now make sure a group index has been created when explicitly asked for\n\\x on\nselect i.*\nfrom pg_indexes i\njoin pg_class c\n    on schemaname = relnamespace::regnamespace::text\n    and tablename = relname\nwhere tablename in (select materialization_hypertable_name from timescaledb_information.continuous_aggregates\nwhere view_name like 'cagg_index_%')\norder by tablename, indexname;\n-[ RECORD 1 ]-------------------------------------------------------------------------------------------------------------------------------------------------\nschemaname | _timescaledb_internal\ntablename  | _materialized_hypertable_49\nindexname  | _materialized_hypertable_49_bucket_idx\ntablespace | \nindexdef   | CREATE INDEX _materialized_hypertable_49_bucket_idx ON _timescaledb_internal._materialized_hypertable_49 USING btree (bucket DESC)\n-[ RECORD 2 ]-------------------------------------------------------------------------------------------------------------------------------------------------\nschemaname | _timescaledb_internal\ntablename  | _materialized_hypertable_49\nindexname  | _materialized_hypertable_49_symbol_bucket_idx\ntablespace | \nindexdef   | CREATE INDEX _materialized_hypertable_49_symbol_bucket_idx ON _timescaledb_internal._materialized_hypertable_49 USING btree (symbol, bucket DESC)\n-[ RECORD 3 ]-------------------------------------------------------------------------------------------------------------------------------------------------\nschemaname | _timescaledb_internal\ntablename  | _materialized_hypertable_50\nindexname  | _materialized_hypertable_50_bucket_idx\ntablespace | \nindexdef   | CREATE INDEX _materialized_hypertable_50_bucket_idx ON _timescaledb_internal._materialized_hypertable_50 USING btree (bucket DESC)\n-[ RECORD 4 ]-------------------------------------------------------------------------------------------------------------------------------------------------\nschemaname | _timescaledb_internal\ntablename  | _materialized_hypertable_51\nindexname  | _materialized_hypertable_51_bucket_idx\ntablespace | \nindexdef   | CREATE INDEX _materialized_hypertable_51_bucket_idx ON _timescaledb_internal._materialized_hypertable_51 USING btree (bucket DESC)\n-[ RECORD 5 ]-------------------------------------------------------------------------------------------------------------------------------------------------\nschemaname | _timescaledb_internal\ntablename  | _materialized_hypertable_51\nindexname  | _materialized_hypertable_51_symbol_bucket_idx\ntablespace | \nindexdef   | CREATE INDEX _materialized_hypertable_51_symbol_bucket_idx ON _timescaledb_internal._materialized_hypertable_51 USING btree (symbol, bucket DESC)\n\n\\x off\n--\n-- TESTs for removing old CAggs restrictions\n--\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 8 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_29_67_chunk\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_30_68_chunk\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_31_69_chunk\nCREATE TABLE conditions (\n  timec       TIMESTAMPTZ       NOT NULL,\n  location    TEXT              NOT NULL,\n  temperature DOUBLE PRECISION  NULL,\n  humidity    DOUBLE PRECISION  NULL\n);\nSELECT create_hypertable('conditions', 'timec');\n    create_hypertable     \n--------------------------\n (52,public,conditions,t)\n\nINSERT INTO conditions\nVALUES\n  ('2010-01-01 09:00:00-08', 'SFO', 55, 45),\n  ('2010-01-02 09:00:00-08', 'por', 100, 100),\n  ('2010-01-02 09:00:00-08', 'NYC', 65, 45),\n  ('2010-01-02 09:00:00-08', 'SFO', 65, 45),\n  ('2010-01-03 09:00:00-08', 'NYC', 45, 55),\n  ('2010-01-05 09:00:00-08', 'SFO', 75, 100),\n  ('2018-11-01 09:00:00-08', 'NYC', 45, 35),\n  ('2018-11-02 09:00:00-08', 'NYC', 35, 15),\n  ('2018-11-03 09:00:00-08', 'NYC', 35, 25);\n-- aggregate with DISTINCT\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1week', timec),\n  COUNT(location),\n  SUM(DISTINCT temperature)\nFROM conditions\nGROUP BY time_bucket('1week', timec), location;\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\nSELECT * FROM mat_m1 ORDER BY 1, 2, 3;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Dec 27 16:00:00 2009 PST |     1 | 100\n Sun Dec 27 16:00:00 2009 PST |     2 | 110\n Sun Dec 27 16:00:00 2009 PST |     2 | 120\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n Sun Oct 28 17:00:00 2018 PDT |     3 |  80\n\n-- aggregate with FILTER\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1week', timec),\n  SUM(temperature) FILTER (WHERE humidity > 60)\nFROM conditions\nGROUP BY time_bucket('1week', timec), location;\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\nSELECT * FROM mat_m1 ORDER BY 1, 2;\n         time_bucket          | sum \n------------------------------+-----\n Sun Dec 27 16:00:00 2009 PST | 100\n Sun Dec 27 16:00:00 2009 PST |    \n Sun Dec 27 16:00:00 2009 PST |    \n Sun Jan 03 16:00:00 2010 PST |  75\n Sun Oct 28 17:00:00 2018 PDT |    \n\n-- aggregate with filter in having clause\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1week', timec),\n  MAX(temperature)\nFROM conditions\nGROUP BY time_bucket('1week', timec), location\nHAVING SUM(temperature) FILTER (WHERE humidity > 40) > 50;\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\nSELECT * FROM mat_m1 ORDER BY 1, 2;\n         time_bucket          | max \n------------------------------+-----\n Sun Dec 27 16:00:00 2009 PST |  65\n Sun Dec 27 16:00:00 2009 PST |  65\n Sun Dec 27 16:00:00 2009 PST | 100\n Sun Jan 03 16:00:00 2010 PST |  75\n\n-- ordered set aggr\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_55_116_chunk\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1week', timec),\n  MODE() WITHIN GROUP(ORDER BY humidity)\nFROM conditions\nGROUP BY time_bucket('1week', timec);\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\nSELECT * FROM mat_m1 ORDER BY 1;\n         time_bucket          | mode \n------------------------------+------\n Sun Dec 27 16:00:00 2009 PST |   45\n Sun Jan 03 16:00:00 2010 PST |  100\n Sun Oct 28 17:00:00 2018 PDT |   15\n\n-- hypothetical-set aggr\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1week', timec),\n  RANK(60) WITHIN GROUP (ORDER BY humidity),\n  DENSE_RANK(60) WITHIN GROUP (ORDER BY humidity),\n  PERCENT_RANK(60) WITHIN GROUP (ORDER BY humidity)\nFROM conditions\nGROUP BY time_bucket('1week', timec);\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\nSELECT * FROM mat_m1 ORDER BY 1;\n         time_bucket          | rank | dense_rank | percent_rank \n------------------------------+------+------------+--------------\n Sun Dec 27 16:00:00 2009 PST |    5 |          3 |          0.8\n Sun Jan 03 16:00:00 2010 PST |    1 |          1 |            0\n Sun Oct 28 17:00:00 2018 PDT |    4 |          4 |            1\n\n-- userdefined aggregate without combine function\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\nCREATE AGGREGATE newavg (\n  sfunc = int4_avg_accum,\n  basetype = int4,\n  stype = _int8,\n  finalfunc = int8_avg,\n  initcond1 = '{0,0}'\n);\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  SUM(humidity),\n  round(newavg(temperature::int4))\nFROM conditions\nGROUP BY time_bucket('1week', timec), location ORDER BY 1,2;\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\nSELECT * FROM mat_m1 ORDER BY 1, 2;\n sum | round \n-----+-------\n  75 |    38\n  90 |    60\n 100 |    55\n 100 |    75\n 100 |   100\n\n-- ORDER BY in the view definition\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1week', timec),\n  COUNT(location),\n  SUM(temperature)\nFROM conditions\nGROUP BY time_bucket('1week', timec)\nORDER BY sum DESC;\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\n-- CAgg definition for realtime\nSELECT pg_get_viewdef('mat_m1',true);\n                                                                                  pg_get_viewdef                                                                                   \n-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n ( SELECT _materialized_hypertable_59.time_bucket,                                                                                                                                +\n     _materialized_hypertable_59.count,                                                                                                                                           +\n     _materialized_hypertable_59.sum                                                                                                                                              +\n    FROM _timescaledb_internal._materialized_hypertable_59                                                                                                                        +\n   WHERE _materialized_hypertable_59.time_bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(59)), '-infinity'::timestamp with time zone)+\n   ORDER BY _materialized_hypertable_59.sum DESC)                                                                                                                                 +\n UNION ALL                                                                                                                                                                        +\n ( SELECT time_bucket('@ 7 days'::interval, conditions.timec) AS time_bucket,                                                                                                     +\n     count(conditions.location) AS count,                                                                                                                                         +\n     sum(conditions.temperature) AS sum                                                                                                                                           +\n    FROM conditions                                                                                                                                                               +\n   WHERE conditions.timec >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(59)), '-infinity'::timestamp with time zone)                      +\n   GROUP BY (time_bucket('@ 7 days'::interval, conditions.timec))                                                                                                                 +\n   ORDER BY (sum(conditions.temperature)) DESC)                                                                                                                                   +\n   ORDER BY 3 DESC;\n\n-- Ordered result\nSELECT * FROM mat_m1;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Dec 27 16:00:00 2009 PST |     5 | 330\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n\n-- Insert new data and query again to make sure we produce ordered data\nINSERT INTO conditions VALUES ('2018-11-10 09:00:00-08', 'SFO', 10, 10);\nSELECT * FROM mat_m1;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Dec 27 16:00:00 2009 PST |     5 | 330\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n Sun Nov 04 16:00:00 2018 PST |     1 |  10\n\n-- This new row will change the order again\nINSERT INTO conditions VALUES ('2018-11-11 09:00:00-08', 'SFO', 400, 400);\nSELECT * FROM mat_m1;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Nov 04 16:00:00 2018 PST |     2 | 410\n Sun Dec 27 16:00:00 2009 PST |     5 | 330\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n\n-- Merge Append\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM mat_m1;\n--- QUERY PLAN ---\n Merge Append\n   Sort Key: _materialized_hypertable_59.sum DESC\n   ->  Merge Append\n         Sort Key: _materialized_hypertable_59.sum DESC\n         ->  Index Scan Backward using _hyper_59_123_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_123_chunk\n         ->  Index Scan Backward using _hyper_59_124_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_124_chunk\n               Index Cond: (time_bucket < 'Sun Nov 04 16:00:00 2018 PST'::timestamp with time zone)\n   ->  Sort\n         Sort Key: (sum(conditions.temperature)) DESC\n         ->  Finalize HashAggregate\n               Group Key: (time_bucket('@ 7 days'::interval, conditions.timec))\n               ->  Append\n                     ->  Partial HashAggregate\n                           Group Key: time_bucket('@ 7 days'::interval, _hyper_52_111_chunk.timec)\n                           ->  Index Scan Backward using _hyper_52_111_chunk_conditions_timec_idx on _hyper_52_111_chunk\n                                 Index Cond: (timec >= 'Sun Nov 04 16:00:00 2018 PST'::timestamp with time zone)\n                     ->  Partial HashAggregate\n                           Group Key: time_bucket('@ 7 days'::interval, _hyper_52_125_chunk.timec)\n                           ->  Seq Scan on _hyper_52_125_chunk\n\n-- Ordering by another column\nSELECT * FROM mat_m1 ORDER BY count;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n Sun Nov 04 16:00:00 2018 PST |     2 | 410\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Dec 27 16:00:00 2009 PST |     5 | 330\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM mat_m1 ORDER BY count;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _materialized_hypertable_59.count\n   ->  Merge Append\n         Sort Key: _materialized_hypertable_59.sum DESC\n         ->  Merge Append\n               Sort Key: _materialized_hypertable_59.sum DESC\n               ->  Index Scan Backward using _hyper_59_123_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_123_chunk\n               ->  Index Scan Backward using _hyper_59_124_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_124_chunk\n                     Index Cond: (time_bucket < 'Sun Nov 04 16:00:00 2018 PST'::timestamp with time zone)\n         ->  Sort\n               Sort Key: (sum(conditions.temperature)) DESC\n               ->  Finalize HashAggregate\n                     Group Key: (time_bucket('@ 7 days'::interval, conditions.timec))\n                     ->  Append\n                           ->  Partial HashAggregate\n                                 Group Key: time_bucket('@ 7 days'::interval, _hyper_52_111_chunk.timec)\n                                 ->  Index Scan Backward using _hyper_52_111_chunk_conditions_timec_idx on _hyper_52_111_chunk\n                                       Index Cond: (timec >= 'Sun Nov 04 16:00:00 2018 PST'::timestamp with time zone)\n                           ->  Partial HashAggregate\n                                 Group Key: time_bucket('@ 7 days'::interval, _hyper_52_125_chunk.timec)\n                                 ->  Seq Scan on _hyper_52_125_chunk\n\n-- Change the type of cagg\nALTER MATERIALIZED VIEW mat_m1 SET (timescaledb.materialized_only=true);\n-- CAgg definition for materialized only\nSELECT pg_get_viewdef('mat_m1',true);\n                      pg_get_viewdef                       \n-----------------------------------------------------------\n  SELECT time_bucket,                                     +\n     count,                                               +\n     sum                                                  +\n    FROM _timescaledb_internal._materialized_hypertable_59+\n   ORDER BY sum DESC;\n\n-- Now the query will show only the materialized data, without last two\n-- records inserted into the original hypertable (last two insers above)\nSELECT * FROM mat_m1;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Dec 27 16:00:00 2009 PST |     5 | 330\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n\n-- Merge Append\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM mat_m1;\n--- QUERY PLAN ---\n Merge Append\n   Sort Key: _materialized_hypertable_59.sum DESC\n   ->  Index Scan Backward using _hyper_59_123_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_123_chunk\n   ->  Index Scan Backward using _hyper_59_124_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_124_chunk\n\n-- Ordering by another column\nSELECT * FROM mat_m1 ORDER BY count;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Dec 27 16:00:00 2009 PST |     5 | 330\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM mat_m1 ORDER BY count;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _materialized_hypertable_59.count\n   ->  Merge Append\n         Sort Key: _materialized_hypertable_59.sum DESC\n         ->  Index Scan Backward using _hyper_59_123_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_123_chunk\n         ->  Index Scan Backward using _hyper_59_124_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_124_chunk\n\nSELECT h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\n-- Invalidate old region and refresh again\nDELETE FROM conditions WHERE timec < '2010-01-05 09:00:00-08';\nCALL refresh_continuous_aggregate('mat_m1', NULL, NULL);\n-- Querying the cagg produce ordered records as expected\nSELECT * FROM mat_m1;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Nov 04 16:00:00 2018 PST |     2 | 410\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n\n-- Querying direct the materialization hypertable doesn't\n-- produce ordered records\nSELECT * FROM :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\";\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Nov 04 16:00:00 2018 PST |     2 | 410\n\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to 2 other objects\nCREATE TABLE conditions (\n  timec       TIMESTAMPTZ       NOT NULL,\n  location    TEXT              NOT NULL,\n  temperature DOUBLE PRECISION  NULL,\n  humidity    DOUBLE PRECISION  NULL\n);\nSELECT table_name FROM create_hypertable('conditions', 'timec');\n table_name \n------------\n conditions\n\nINSERT INTO conditions VALUES\n  ('2010-01-01 09:00:00-08', 'SFO', 55, 45),\n  ('2010-01-02 09:00:00-08', 'por', 100, 100),\n  ('2010-01-02 09:00:00-08', 'SFO', 65, 45),\n  ('2010-01-02 09:00:00-08', 'NYC', 65, 45),\n  ('2018-11-01 09:00:00-08', 'NYC', 45, 35),\n  ('2018-11-02 09:00:00-08', 'NYC', 35, 15);\nCREATE MATERIALIZED VIEW conditions_summary_new(timec, minl, sumt, sumh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nAS\nSELECT time_bucket('1day', timec), min(location), sum(temperature), sum(humidity)\nFROM conditions\nGROUP BY time_bucket('1day', timec) WITH NO DATA;\n\\x ON\nSELECT *\nFROM timescaledb_information.continuous_aggregates\nWHERE view_name = 'conditions_summary_new';\n-[ RECORD 1 ]---------------------+----------------------------------------------------------\nhypertable_schema                 | public\nhypertable_name                   | conditions\nview_schema                       | public\nview_name                         | conditions_summary_new\nview_owner                        | default_perm_user\nmaterialized_only                 | t\ncompression_enabled               | f\nmaterialization_hypertable_schema | _timescaledb_internal\nmaterialization_hypertable_name   | _materialized_hypertable_61\nview_definition                   |  SELECT time_bucket('@ 1 day'::interval, timec) AS timec,+\n                                  |     min(location) AS minl,                               +\n                                  |     sum(temperature) AS sumt,                            +\n                                  |     sum(humidity) AS sumh                                +\n                                  |    FROM conditions                                       +\n                                  |   GROUP BY (time_bucket('@ 1 day'::interval, timec));\n\n\\x OFF\nCALL refresh_continuous_aggregate('conditions_summary_new', NULL, NULL);\n-- Check and compare number of returned rows\nSELECT count(*) FROM conditions_summary_new;\n count \n-------\n     4\n\n-- Parallel planning test for realtime Continuous Aggregate\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to 2 other objects\nCREATE TABLE conditions (\n  timec       TIMESTAMPTZ       NOT NULL,\n  temperature DOUBLE PRECISION  NULL\n);\nSELECT table_name FROM create_hypertable('conditions', 'timec');\n table_name \n------------\n conditions\n\nINSERT INTO conditions\nSELECT t, 10 FROM generate_series('2023-01-01 00:00-03'::timestamptz, '2023-12-31 23:59-03'::timestamptz, '1 hour'::interval) AS t;\nCREATE MATERIALIZED VIEW conditions_daily WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1 day', timec),\n  SUM(temperature)\nFROM conditions\nGROUP BY 1\nORDER BY 2 DESC;\nNOTICE:  refreshing continuous aggregate \"conditions_daily\"\nSELECT set_config(CASE WHEN current_setting('server_version_num')::int < 160000 THEN 'force_parallel_mode' ELSE 'debug_parallel_query' END,'on', false);\n set_config \n------------\n on\n\nSET max_parallel_workers_per_gather = 4;\nSET parallel_setup_cost = 0;\nSET parallel_tuple_cost = 0;\n-- Parallel planning\nEXPLAIN (BUFFERS OFF, COSTS OFF, TIMING OFF) SELECT * FROM conditions_daily WHERE time_bucket >= '2023-07-01';\n--- QUERY PLAN ---\n Merge Append\n   Sort Key: _materialized_hypertable_63.sum DESC\n   ->  Gather Merge\n         Workers Planned: 2\n         ->  Sort\n               Sort Key: _materialized_hypertable_63.sum DESC\n               ->  Parallel Append\n                     ->  Parallel Index Scan using _hyper_63_185_chunk__materialized_hypertable_63_time_bucket_idx on _hyper_63_185_chunk\n                           Index Cond: ((time_bucket < 'Mon Jan 01 16:00:00 2024 PST'::timestamp with time zone) AND (time_bucket >= 'Sat Jul 01 00:00:00 2023 PDT'::timestamp with time zone))\n                     ->  Parallel Index Scan using _hyper_63_187_chunk__materialized_hypertable_63_time_bucket_idx on _hyper_63_187_chunk\n                           Index Cond: ((time_bucket < 'Mon Jan 01 16:00:00 2024 PST'::timestamp with time zone) AND (time_bucket >= 'Sat Jul 01 00:00:00 2023 PDT'::timestamp with time zone))\n                     ->  Parallel Seq Scan on _hyper_63_184_chunk\n   ->  Sort\n         Sort Key: (sum(_hyper_62_182_chunk.temperature)) DESC\n         ->  HashAggregate\n               Group Key: (time_bucket('@ 1 day'::interval, _hyper_62_182_chunk.timec))\n               ->  Gather\n                     Workers Planned: 1\n                     ->  Result\n                           ->  Parallel Index Scan Backward using _hyper_62_182_chunk_conditions_timec_idx on _hyper_62_182_chunk\n                                 Index Cond: ((timec >= 'Mon Jan 01 16:00:00 2024 PST'::timestamp with time zone) AND (timec >= 'Sat Jul 01 00:00:00 2023 PDT'::timestamp with time zone))\n                                 Filter: (time_bucket('@ 1 day'::interval, timec) >= 'Sat Jul 01 00:00:00 2023 PDT'::timestamp with time zone)\n\n"
  },
  {
    "path": "tsl/test/expected/cagg-17.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- initialize the bgw mock state to prevent the materialization workers from running\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION ts_bgw_params_create() RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION test.continuous_aggs_find_view(cagg REGCLASS) RETURNS VOID\nAS :TSL_MODULE_PATHNAME, 'ts_test_continuous_agg_find_by_view_name' LANGUAGE C;\n\\set WAIT_ON_JOB 0\n\\set IMMEDIATELY_SET_UNTIL 1\n\\set WAIT_FOR_OTHER_TO_ADVANCE 2\n-- remove any default jobs, e.g., telemetry so bgw_job isn't polluted\nDELETE FROM _timescaledb_catalog.bgw_job;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT * FROM _timescaledb_catalog.bgw_job;\n id | application_name | schedule_interval | max_runtime | max_retries | retry_period | proc_schema | proc_name | owner | scheduled | fixed_schedule | initial_start | hypertable_id | config | check_schema | check_name | timezone \n----+------------------+-------------------+-------------+-------------+--------------+-------------+-----------+-------+-----------+----------------+---------------+---------------+--------+--------------+------------+----------\n\n--TEST1 ---\n--basic test with count\ncreate table foo (a integer, b integer, c integer);\nselect table_name from create_hypertable('foo', 'a', chunk_time_interval=> 10);\n table_name \n------------\n foo\n\ninsert into foo values( 3 , 16 , 20);\ninsert into foo values( 1 , 10 , 20);\ninsert into foo values( 1 , 11 , 20);\ninsert into foo values( 1 , 12 , 20);\ninsert into foo values( 1 , 13 , 20);\ninsert into foo values( 1 , 14 , 20);\ninsert into foo values( 2 , 14 , 20);\ninsert into foo values( 2 , 15 , 20);\ninsert into foo values( 2 , 16 , 20);\nCREATE OR REPLACE FUNCTION integer_now_foo() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(a), 0) FROM foo $$;\nSELECT set_integer_now_func('foo', 'integer_now_foo');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW mat_m1(a, countb)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect a, count(b)\nfrom foo\ngroup by time_bucket(1, a), a WITH NO DATA;\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, 2::integer, '12 h'::interval) AS job_id\n\\gset\nSELECT * FROM _timescaledb_catalog.bgw_job;\n  id  |              application_name              | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema       |              proc_name              |       owner       | scheduled | fixed_schedule | initial_start | hypertable_id |                             config                              |      check_schema      |                check_name                 | timezone \n------+--------------------------------------------+-------------------+-------------+-------------+--------------+------------------------+-------------------------------------+-------------------+-----------+----------------+---------------+---------------+-----------------------------------------------------------------+------------------------+-------------------------------------------+----------\n 1000 | Refresh Continuous Aggregate Policy [1000] | @ 12 hours        | @ 0         |          -1 | @ 12 hours   | _timescaledb_functions | policy_refresh_continuous_aggregate | default_perm_user | t         | f              |               |             2 | {\"end_offset\": 2, \"start_offset\": null, \"mat_hypertable_id\": 2} | _timescaledb_functions | policy_refresh_continuous_aggregate_check | \n\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\ninsert into :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect a, count(b),\ntime_bucket(1, a)\nfrom foo\ngroup by time_bucket(1, a) , a ;\nselect * from mat_m1 order by a ;\n a | countb \n---+--------\n 1 |      5\n 2 |      3\n 3 |      1\n\n--check triggers on user hypertable --\nSET ROLE :ROLE_SUPERUSER;\nselect tgname, tgtype, tgenabled , relname from pg_trigger, pg_class\nwhere tgrelid = pg_class.oid and pg_class.relname like 'foo'\norder by tgname;\n tgname | tgtype | tgenabled | relname \n--------+--------+-----------+---------\n\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n-- TEST2 ---\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_2_2_chunk\nSHOW enable_partitionwise_aggregate;\n enable_partitionwise_aggregate \n--------------------------------\n off\n\nSET enable_partitionwise_aggregate = on;\nSELECT * FROM _timescaledb_catalog.bgw_job;\n id | application_name | schedule_interval | max_runtime | max_retries | retry_period | proc_schema | proc_name | owner | scheduled | fixed_schedule | initial_start | hypertable_id | config | check_schema | check_name | timezone \n----+------------------+-------------------+-------------+-------------+--------------+-------------+-----------+-------+-----------+----------------+---------------+---------------+--------+--------------+------------+----------\n\nCREATE TABLE conditions (\n      timec        TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\ninsert into conditions values ( '2010-01-01 09:00:00-08', 'SFO', 55, 45);\ninsert into conditions values ( '2010-01-02 09:00:00-08', 'por', 100, 100);\ninsert into conditions values ( '2010-01-02 09:00:00-08', 'SFO', 65, 45);\ninsert into conditions values ( '2010-01-02 09:00:00-08', 'NYC', 65, 45);\ninsert into conditions values ( '2018-11-01 09:00:00-08', 'NYC', 45, 35);\ninsert into conditions values ( '2018-11-02 09:00:00-08', 'NYC', 35, 15);\nCREATE MATERIALIZED VIEW mat_m1( timec, minl, sumt , sumh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1day', timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by time_bucket('1day', timec) WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\n-- Materialized hypertable for mat_m1 should not be visible in the\n-- hypertables view:\nSELECT hypertable_schema, hypertable_name\nFROM timescaledb_information.hypertables ORDER BY 1,2;\n hypertable_schema | hypertable_name \n-------------------+-----------------\n public            | conditions\n public            | foo\n\nSET ROLE :ROLE_SUPERUSER;\ninsert into  :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect\n time_bucket('1day', timec), min(location), sum(temperature), sum(humidity)\nfrom conditions\ngroup by time_bucket('1day', timec) ;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--should have same results --\nselect timec, minl, sumt, sumh\nfrom mat_m1\norder by timec;\n            timec             | minl | sumt | sumh \n------------------------------+------+------+------\n Thu Dec 31 16:00:00 2009 PST | SFO  |   55 |   45\n Fri Jan 01 16:00:00 2010 PST | NYC  |  230 |  190\n Wed Oct 31 17:00:00 2018 PDT | NYC  |   45 |   35\n Thu Nov 01 17:00:00 2018 PDT | NYC  |   35 |   15\n\nselect time_bucket('1day', timec), min(location), sum(temperature), sum(humidity)\nfrom conditions\ngroup by time_bucket('1day', timec)\norder by 1;\n         time_bucket          | min | sum | sum \n------------------------------+-----+-----+-----\n Thu Dec 31 16:00:00 2009 PST | SFO |  55 |  45\n Fri Jan 01 16:00:00 2010 PST | NYC | 230 | 190\n Wed Oct 31 17:00:00 2018 PDT | NYC |  45 |  35\n Thu Nov 01 17:00:00 2018 PDT | NYC |  35 |  15\n\nSET enable_partitionwise_aggregate = off;\n-- TEST3 --\n-- drop on table conditions should cascade to materialized mat_v1\ndrop table conditions cascade;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to 2 other objects\nCREATE TABLE conditions (\n      timec        TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\ninsert into conditions values ( '2010-01-01 09:00:00-08', 'SFO', 55, 45);\ninsert into conditions values ( '2010-01-02 09:00:00-08', 'por', 100, 100);\ninsert into conditions values ( '2010-01-02 09:00:00-08', 'NYC', 65, 45);\ninsert into conditions values ( '2010-01-02 09:00:00-08', 'SFO', 65, 45);\ninsert into conditions values ( '2010-01-03 09:00:00-08', 'NYC', 45, 55);\ninsert into conditions values ( '2010-01-05 09:00:00-08', 'SFO', 75, 100);\ninsert into conditions values ( '2018-11-01 09:00:00-08', 'NYC', 45, 35);\ninsert into conditions values ( '2018-11-02 09:00:00-08', 'NYC', 35, 15);\ninsert into conditions values ( '2018-11-03 09:00:00-08', 'NYC', 35, 25);\nCREATE MATERIALIZED VIEW mat_m1( timec, minl, sumth, stddevh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec)  WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\nSET ROLE :ROLE_SUPERUSER;\ninsert into  :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect\n time_bucket('1week', timec), min(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec) ;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--should have same results --\nselect timec, minl, sumth, stddevh\nfrom mat_m1\norder by timec;\n            timec             | minl | sumth |     stddevh      \n------------------------------+------+-------+------------------\n Sun Dec 27 16:00:00 2009 PST | NYC  |   620 | 23.8746727726266\n Sun Jan 03 16:00:00 2010 PST | SFO  |   175 |                 \n Sun Oct 28 17:00:00 2018 PDT | NYC  |   190 |               10\n\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+ sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec)\norder by time_bucket('1week', timec);\n         time_bucket          | min | ?column? |      stddev      \n------------------------------+-----+----------+------------------\n Sun Dec 27 16:00:00 2009 PST | NYC |      620 | 23.8746727726266\n Sun Jan 03 16:00:00 2010 PST | SFO |      175 |                 \n Sun Oct 28 17:00:00 2018 PDT | NYC |      190 |               10\n\n-- TEST4 --\n--materialized view with group by clause + expression in SELECT\n-- use previous data from conditions\n--drop only the view.\n-- apply where clause on result of mat_m1 --\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\nCREATE MATERIALIZED VIEW mat_m1( timec, minl, sumth, stddevh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\nwhere location = 'NYC'\ngroup by time_bucket('1week', timec)\n WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\nSET ROLE :ROLE_SUPERUSER;\ninsert into  :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect\n time_bucket('1week', timec), min(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\nwhere location = 'NYC'\ngroup by time_bucket('1week', timec) ;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--should have same results --\nselect timec, minl, sumth, stddevh\nfrom mat_m1\nwhere stddevh is not null\norder by timec;\n            timec             | minl | sumth |     stddevh      \n------------------------------+------+-------+------------------\n Sun Dec 27 16:00:00 2009 PST | NYC  |   210 | 7.07106781186548\n Sun Oct 28 17:00:00 2018 PDT | NYC  |   190 |               10\n\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+ sum(humidity), stddev(humidity)\nfrom conditions\nwhere location = 'NYC'\ngroup by time_bucket('1week', timec)\norder by time_bucket('1week', timec);\n         time_bucket          | min | ?column? |      stddev      \n------------------------------+-----+----------+------------------\n Sun Dec 27 16:00:00 2009 PST | NYC |      210 | 7.07106781186548\n Sun Oct 28 17:00:00 2018 PDT | NYC |      190 |               10\n\n-- TEST5 --\n---------test with having clause ----------------------\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\ncreate materialized view mat_m1( timec, minl, sumth, stddevh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec)\nhaving stddev(humidity) is not null WITH NO DATA;\n;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\nSET ROLE :ROLE_SUPERUSER;\ninsert into  :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect\n time_bucket('1week', timec), min(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec)\nhaving stddev(humidity) is not null;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n-- should have same results --\nselect * from mat_m1\norder by sumth;\n            timec             | minl | sumth |     stddevh      \n------------------------------+------+-------+------------------\n Sun Oct 28 17:00:00 2018 PDT | NYC  |   190 |               10\n Sun Dec 27 16:00:00 2009 PST | NYC  |   620 | 23.8746727726266\n\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec)\nhaving stddev(humidity) is not null\norder by sum(temperature)+sum(humidity);\n         time_bucket          | min | ?column? |      stddev      \n------------------------------+-----+----------+------------------\n Sun Oct 28 17:00:00 2018 PDT | NYC |      190 |               10\n Sun Dec 27 16:00:00 2009 PST | NYC |      620 | 23.8746727726266\n\n-- TEST6 --\n--group by with more than 1 group column\n-- having clause with a mix of columns from select list + others\ndrop table conditions cascade;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to 2 other objects\nCREATE TABLE conditions (\n      timec       TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        numeric NULL,\n      highp       numeric null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\ninsert into conditions\nselect generate_series('2018-12-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'POR', 55, 75, 40, 70;\ninsert into conditions\nselect generate_series('2018-11-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'NYC', 35, 45, 50, 40;\ninsert into conditions\nselect generate_series('2018-11-01 00:00'::timestamp, '2018-12-15 00:00'::timestamp, '1 day'), 'LA', 73, 55, 71, 28;\n--naming with AS clauses\nCREATE MATERIALIZED VIEW mat_naming\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec) as bucket, location as loc, sum(temperature)+sum(humidity) as sumth, stddev(humidity)\nfrom conditions\ngroup by bucket, loc\nhaving min(location) >= 'NYC' and avg(temperature) > 20 WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_naming'\n\\gset\nselect attnum , attname from pg_attribute\nwhere attnum > 0 and attrelid =\n(Select oid from pg_class where relname like :'MAT_TABLE_NAME')\norder by attnum, attname;\n attnum | attname \n--------+---------\n      1 | bucket\n      2 | loc\n      3 | sumth\n      4 | stddev\n\nDROP MATERIALIZED VIEW mat_naming;\n--naming with default names\nCREATE MATERIALIZED VIEW mat_naming\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec), location, sum(temperature)+sum(humidity) as sumth, stddev(humidity)\nfrom conditions\ngroup by 1,2\nhaving min(location) >= 'NYC' and avg(temperature) > 20 WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_naming'\n\\gset\nselect attnum , attname from pg_attribute\nwhere attnum > 0 and attrelid =\n(Select oid from pg_class where relname like :'MAT_TABLE_NAME')\norder by attnum, attname;\n attnum |   attname   \n--------+-------------\n      1 | time_bucket\n      2 | location\n      3 | sumth\n      4 | stddev\n\nDROP MATERIALIZED VIEW mat_naming;\n--naming with view col names\nCREATE MATERIALIZED VIEW mat_naming(bucket, loc, sum_t_h, stdd)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec), location, sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by 1,2\nhaving min(location) >= 'NYC' and avg(temperature) > 20 WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_naming'\n\\gset\nselect attnum , attname from pg_attribute\nwhere attnum > 0 and attrelid =\n(Select oid from pg_class where relname like :'MAT_TABLE_NAME')\norder by attnum, attname;\n attnum | attname \n--------+---------\n      1 | bucket\n      2 | loc\n      3 | sum_t_h\n      4 | stdd\n\nDROP MATERIALIZED VIEW mat_naming;\nCREATE MATERIALIZED VIEW mat_m1(timec, minl, sumth, stddevh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by  time_bucket('1week', timec)\nhaving min(location) >= 'NYC' and avg(temperature) > 20 WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\nselect attnum , attname from pg_attribute\nwhere attnum > 0 and attrelid =\n(Select oid from pg_class where relname like :'MAT_TABLE_NAME')\norder by attnum, attname;\n attnum | attname \n--------+---------\n      1 | timec\n      2 | minl\n      3 | sumth\n      4 | stddevh\n\nSET ROLE :ROLE_SUPERUSER;\ninsert into  :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect\n time_bucket('1week', timec), min(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec)\nhaving min(location) >= 'NYC' and avg(temperature) > 20;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--should have same results --\nselect timec, minl, sumth, stddevh\nfrom mat_m1\norder by timec, minl;\n            timec             | minl | sumth |     stddevh      \n------------------------------+------+-------+------------------\n Sun Dec 16 16:00:00 2018 PST | NYC  |  1470 | 15.5662356498831\n Sun Dec 23 16:00:00 2018 PST | NYC  |  1470 | 15.5662356498831\n Sun Dec 30 16:00:00 2018 PST | NYC  |   210 | 21.2132034355964\n\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by  time_bucket('1week', timec)\nhaving min(location) >= 'NYC' and avg(temperature) > 20 and avg(lowp) > 10\norder by time_bucket('1week', timec), min(location);\n         time_bucket          | min | ?column? |      stddev      \n------------------------------+-----+----------+------------------\n Sun Dec 16 16:00:00 2018 PST | NYC |     1470 | 15.5662356498831\n Sun Dec 23 16:00:00 2018 PST | NYC |     1470 | 15.5662356498831\n Sun Dec 30 16:00:00 2018 PST | NYC |      210 | 21.2132034355964\n\n--check view defintion in information views\nselect view_name, view_definition from timescaledb_information.continuous_aggregates\nwhere view_name::text like 'mat_m1';\n view_name |                                      view_definition                                      \n-----------+-------------------------------------------------------------------------------------------\n mat_m1    |  SELECT time_bucket('@ 7 days'::interval, timec) AS timec,                               +\n           |     min(location) AS minl,                                                               +\n           |     (sum(temperature) + sum(humidity)) AS sumth,                                         +\n           |     stddev(humidity) AS stddevh                                                          +\n           |    FROM conditions                                                                       +\n           |   GROUP BY (time_bucket('@ 7 days'::interval, timec))                                    +\n           |  HAVING ((min(location) >= 'NYC'::text) AND (avg(temperature) > (20)::double precision));\n\n--TEST6 -- select from internal view\nSET ROLE :ROLE_SUPERUSER;\ninsert into :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect * from :\"PART_VIEW_SCHEMA\".:\"PART_VIEW_NAME\";\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--lets drop the view and check\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_13_24_chunk\ndrop table conditions;\nCREATE TABLE conditions (\n      timec       TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\ninsert into conditions\nselect generate_series('2018-12-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'POR', 55, 75, 40, 70, NULL;\ninsert into conditions\nselect generate_series('2018-11-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'NYC', 35, 45, 50, 40, NULL;\ninsert into conditions\nselect generate_series('2018-11-01 00:00'::timestamp, '2018-12-15 00:00'::timestamp, '1 day'), 'LA', 73, 55, NULL, 28, NULL;\nSELECT\n  $$\n  select time_bucket('1week', timec) ,\n  min(location) as col1, sum(temperature)+sum(humidity) as col2, stddev(humidity) as col3, min(allnull) as col4\n  from conditions\n  group by  time_bucket('1week', timec)\n  having min(location) >= 'NYC' and avg(temperature) > 20\n  $$ AS \"QUERY\"\n\\gset\n\\set ECHO errors\npsql:include/cont_agg_equal.sql:8: NOTICE:  materialized view \"mat_test\" does not exist, skipping\n                           ?column?                            | count \n---------------------------------------------------------------+-------\n Number of rows different between view and original (expect 0) |     0\n\nSELECT\n  $$\n  select time_bucket('1week', timec), location,\n  sum(temperature)+sum(humidity) as col2, stddev(humidity) as col3, min(allnull) as col4\n  from conditions\n  group by location, time_bucket('1week', timec)\n  $$ AS \"QUERY\"\n\\gset\n\\set ECHO errors\npsql:include/cont_agg_equal.sql:8: NOTICE:  drop cascades to table _timescaledb_internal._hyper_15_34_chunk\n                           ?column?                            | count \n---------------------------------------------------------------+-------\n Number of rows different between view and original (expect 0) |     0\n\n--TEST7 -- drop tests for view and hypertable\n--DROP tests\n\\set ON_ERROR_STOP 0\nSELECT  h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\",\n       direct_view_name as \"DIR_VIEW_NAME\",\n       direct_view_schema as \"DIR_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_test'\n\\gset\nDROP TABLE :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\";\nERROR:  cannot drop table _timescaledb_internal._materialized_hypertable_16 because other objects depend on it\nDROP VIEW :\"PART_VIEW_SCHEMA\".:\"PART_VIEW_NAME\";\nERROR:  cannot drop the partial/direct view because it is required by a continuous aggregate\nDROP VIEW :\"DIR_VIEW_SCHEMA\".:\"DIR_VIEW_NAME\";\nERROR:  cannot drop the partial/direct view because it is required by a continuous aggregate\n\\set ON_ERROR_STOP 1\n--catalog entry still there;\nSELECT count(*)\nFROM _timescaledb_catalog.continuous_agg ca\nWHERE user_view_name = 'mat_test';\n count \n-------\n     1\n\n--mat table, user_view, direct view and partial view all there\nselect count(*) from pg_class where relname = :'PART_VIEW_NAME';\n count \n-------\n     1\n\nselect count(*) from pg_class where relname = :'MAT_TABLE_NAME';\n count \n-------\n     1\n\nselect count(*) from pg_class where relname = :'DIR_VIEW_NAME';\n count \n-------\n     1\n\nselect count(*) from pg_class where relname = 'mat_test';\n count \n-------\n     1\n\nDROP MATERIALIZED VIEW mat_test;\nNOTICE:  drop cascades to 2 other objects\n--catalog entry should be gone\nSELECT count(*)\nFROM _timescaledb_catalog.continuous_agg ca\nWHERE user_view_name = 'mat_test';\n count \n-------\n     0\n\n--mat table, user_view, direct view and partial view all gone\nselect count(*) from pg_class where relname = :'PART_VIEW_NAME';\n count \n-------\n     0\n\nselect count(*) from pg_class where relname = :'MAT_TABLE_NAME';\n count \n-------\n     0\n\nselect count(*) from pg_class where relname = :'DIR_VIEW_NAME';\n count \n-------\n     0\n\nselect count(*) from pg_class where relname = 'mat_test';\n count \n-------\n     0\n\n--test dropping raw table\nDROP TABLE conditions;\nCREATE TABLE conditions (\n      timec       TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\n--no data in hyper table on purpose so that CASCADE is not required because of chunks\nCREATE MATERIALIZED VIEW mat_drop_test(timec, minl, sumt , sumh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1day', timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by time_bucket('1day', timec) WITH NO DATA;\n\\set ON_ERROR_STOP 0\nDROP TABLE conditions;\nERROR:  cannot drop table conditions because other objects depend on it\n\\set ON_ERROR_STOP 1\n--insert data now\ninsert into conditions\nselect generate_series('2018-12-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'POR', 55, 75, 40, 70, NULL;\ninsert into conditions\nselect generate_series('2018-11-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'NYC', 35, 45, 50, 40, NULL;\ninsert into conditions\nselect generate_series('2018-11-01 00:00'::timestamp, '2018-12-15 00:00'::timestamp, '1 day'), 'LA', 73, 55, NULL, 28, NULL;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_drop_test'\n\\gset\nSET client_min_messages TO NOTICE;\nCALL refresh_continuous_aggregate('mat_drop_test', NULL, NULL);\n--force invalidation\ninsert into conditions\nselect generate_series('2017-11-01 00:00'::timestamp, '2017-12-15 00:00'::timestamp, '1 day'), 'LA', 73, 55, NULL, 28, NULL;\nselect count(*) from _timescaledb_catalog.continuous_aggs_invalidation_threshold;\n count \n-------\n     1\n\nselect count(*) from _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log;\n count \n-------\n     8\n\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to 2 other objects\n--catalog entry should be gone\nSELECT count(*)\nFROM _timescaledb_catalog.continuous_agg ca\nWHERE user_view_name = 'mat_drop_test';\n count \n-------\n     0\n\nselect count(*) from _timescaledb_catalog.continuous_aggs_invalidation_threshold;\n count \n-------\n     0\n\nselect count(*) from _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log;\n count \n-------\n     0\n\nselect count(*) from _timescaledb_catalog.continuous_aggs_materialization_invalidation_log;\n count \n-------\n     0\n\nSELECT * FROM _timescaledb_catalog.bgw_job;\n id | application_name | schedule_interval | max_runtime | max_retries | retry_period | proc_schema | proc_name | owner | scheduled | fixed_schedule | initial_start | hypertable_id | config | check_schema | check_name | timezone \n----+------------------+-------------------+-------------+-------------+--------------+-------------+-----------+-------+-----------+----------------+---------------+---------------+--------+--------------+------------+----------\n\n--mat table, user_view, and partial view all gone\nselect count(*) from pg_class where relname = :'PART_VIEW_NAME';\n count \n-------\n     0\n\nselect count(*) from pg_class where relname = :'MAT_TABLE_NAME';\n count \n-------\n     0\n\nselect count(*) from pg_class where relname = 'mat_drop_test';\n count \n-------\n     0\n\n--TEST With options\nCREATE TABLE conditions (\n      timec       TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\nCREATE MATERIALIZED VIEW mat_with_test(timec, minl, sumt , sumh)\nWITH (timescaledb.continuous,\n      timescaledb.materialized_only=true)\nas\nselect time_bucket('1day', timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by time_bucket('1day', timec), location, humidity, temperature WITH NO DATA;\nSELECT add_continuous_aggregate_policy('mat_with_test', NULL, '5 h'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1001\n\nSELECT alter_job(id, schedule_interval => '1h') FROM _timescaledb_catalog.bgw_job;\n                                                                                                                         alter_job                                                                                                                          \n------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1001,\"@ 1 hour\",\"@ 0\",-1,\"@ 12 hours\",t,\"{\"\"end_offset\"\": \"\"@ 5 hours\"\", \"\"start_offset\"\": null, \"\"mat_hypertable_id\"\": 20}\",-infinity,_timescaledb_functions.policy_refresh_continuous_aggregate_check,f,,,\"Refresh Continuous Aggregate Policy [1001]\")\n\nSELECT schedule_interval FROM _timescaledb_catalog.bgw_job;\n schedule_interval \n-------------------\n @ 1 hour\n\nSELECT alter_job(id, schedule_interval => '2h') FROM _timescaledb_catalog.bgw_job;\n                                                                                                                          alter_job                                                                                                                          \n-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1001,\"@ 2 hours\",\"@ 0\",-1,\"@ 12 hours\",t,\"{\"\"end_offset\"\": \"\"@ 5 hours\"\", \"\"start_offset\"\": null, \"\"mat_hypertable_id\"\": 20}\",-infinity,_timescaledb_functions.policy_refresh_continuous_aggregate_check,f,,,\"Refresh Continuous Aggregate Policy [1001]\")\n\nSELECT schedule_interval FROM _timescaledb_catalog.bgw_job;\n schedule_interval \n-------------------\n @ 2 hours\n\nselect indexname, indexdef from pg_indexes where tablename =\n(SELECT h.table_name\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_with_test')\norder by indexname;\n               indexname               |                                                             indexdef                                                             \n---------------------------------------+----------------------------------------------------------------------------------------------------------------------------------\n _materialized_hypertable_20_timec_idx | CREATE INDEX _materialized_hypertable_20_timec_idx ON _timescaledb_internal._materialized_hypertable_20 USING btree (timec DESC)\n\nDROP MATERIALIZED VIEW mat_with_test;\n--no additional indexes\nCREATE MATERIALIZED VIEW mat_with_test(timec, minl, sumt , sumh)\nWITH (timescaledb.continuous,\n      timescaledb.materialized_only=true,\n      timescaledb.create_group_indexes=false)\nas\nselect time_bucket('1day', timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by time_bucket('1day', timec), location, humidity, temperature WITH NO DATA;\nselect indexname, indexdef from pg_indexes where tablename =\n(SELECT h.table_name\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_with_test');\n               indexname               |                                                             indexdef                                                             \n---------------------------------------+----------------------------------------------------------------------------------------------------------------------------------\n _materialized_hypertable_21_timec_idx | CREATE INDEX _materialized_hypertable_21_timec_idx ON _timescaledb_internal._materialized_hypertable_21 USING btree (timec DESC)\n\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 2 other objects\n--test WITH using a hypertable with an integer time dimension\nCREATE TABLE conditions (\n      timec       INT       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec', chunk_time_interval=> 100);\n table_name \n------------\n conditions\n\nCREATE OR REPLACE FUNCTION integer_now_conditions() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(timec), 0) FROM conditions $$;\nSELECT set_integer_now_func('conditions', 'integer_now_conditions');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW mat_with_test(timec, minl, sumt , sumh)\nWITH (timescaledb.continuous,\n      timescaledb.materialized_only=true)\nas\nselect time_bucket(100, timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by time_bucket(100, timec) WITH NO DATA;\nSELECT add_continuous_aggregate_policy('mat_with_test', NULL, 500::integer, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1002\n\nSELECT alter_job(id, schedule_interval => '2h') FROM _timescaledb_catalog.bgw_job;\n                                                                                                                     alter_job                                                                                                                     \n---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1002,\"@ 2 hours\",\"@ 0\",-1,\"@ 12 hours\",t,\"{\"\"end_offset\"\": 500, \"\"start_offset\"\": null, \"\"mat_hypertable_id\"\": 23}\",-infinity,_timescaledb_functions.policy_refresh_continuous_aggregate_check,f,,,\"Refresh Continuous Aggregate Policy [1002]\")\n\nSELECT schedule_interval FROM _timescaledb_catalog.bgw_job;\n schedule_interval \n-------------------\n @ 2 hours\n\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 2 other objects\n--test space partitions\nCREATE TABLE space_table (\n    time BIGINT,\n    dev  BIGINT,\n    data BIGINT\n);\nSELECT create_hypertable(\n    'space_table',\n    'time',\n    chunk_time_interval => 10,\n    partitioning_column => 'dev',\n    number_partitions => 3);\n     create_hypertable     \n---------------------------\n (24,public,space_table,t)\n\nCREATE OR REPLACE FUNCTION integer_now_space_table() returns BIGINT LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), BIGINT '0') FROM space_table $$;\nSELECT set_integer_now_func('space_table', 'integer_now_space_table');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW space_view\nWITH (timescaledb.continuous,\n      timescaledb.materialized_only=true)\nAS SELECT time_bucket('4', time), COUNT(data)\n   FROM space_table\n   GROUP BY 1 WITH NO DATA;\nINSERT INTO space_table VALUES\n  (0, 1, 1), (0, 2, 1), (1, 1, 1), (1, 2, 1),\n  (10, 1, 1), (10, 2, 1), (11, 1, 1), (11, 2, 1);\nSELECT  h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\",\n       direct_view_name as \"DIR_VIEW_NAME\",\n       direct_view_schema as \"DIR_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'space_view'\n\\gset\nSELECT * FROM :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\n  ORDER BY time_bucket;\n time_bucket | count \n-------------+-------\n\nCALL refresh_continuous_aggregate('space_view', NULL, NULL);\nSELECT * FROM space_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     4\n           8 |     4\n\nSELECT * FROM :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\n  ORDER BY time_bucket;\n time_bucket | count \n-------------+-------\n           0 |     4\n           8 |     4\n\nINSERT INTO space_table VALUES (3, 2, 1);\nCALL refresh_continuous_aggregate('space_view', NULL, NULL);\nSELECT * FROM space_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     5\n           8 |     4\n\nSELECT * FROM :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\n  ORDER BY time_bucket;\n time_bucket | count \n-------------+-------\n           0 |     5\n           8 |     4\n\nINSERT INTO space_table VALUES (2, 3, 1);\nCALL refresh_continuous_aggregate('space_view', NULL, NULL);\nSELECT * FROM space_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     6\n           8 |     4\n\nSELECT * FROM :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\n  ORDER BY time_bucket;\n time_bucket | count \n-------------+-------\n           0 |     6\n           8 |     4\n\nDROP TABLE space_table CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_25_60_chunk\n--\n-- TEST FINALIZEFUNC_EXTRA\n--\n-- create special aggregate to test ffunc_extra\n-- Raise warning with the actual type being passed in\nCREATE OR REPLACE FUNCTION fake_ffunc(a int8, b int, c int, d int, x anyelement)\nRETURNS anyelement AS $$\nBEGIN\n RAISE WARNING 'type % %', pg_typeof(d), pg_typeof(x);\n RETURN x;\nEND;\n$$\nLANGUAGE plpgsql;\nCREATE OR REPLACE FUNCTION fake_sfunc(a int8, b int, c int, d int, x anyelement)\nRETURNS int8 AS $$\nBEGIN\n RETURN b;\nEND; $$\nLANGUAGE plpgsql;\nCREATE AGGREGATE aggregate_to_test_ffunc_extra(int, int, int, anyelement) (\n    SFUNC = fake_sfunc,\n    STYPE = int8,\n    COMBINEFUNC = int8pl,\n    FINALFUNC = fake_ffunc,\n    PARALLEL = SAFE,\n    FINALFUNC_EXTRA\n);\nCREATE TABLE conditions (\n      timec       INT       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec', chunk_time_interval=> 100);\n table_name \n------------\n conditions\n\nCREATE OR REPLACE FUNCTION integer_now_conditions() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(timec), 0) FROM conditions $$;\nSELECT set_integer_now_func('conditions', 'integer_now_conditions');\n set_integer_now_func \n----------------------\n \n\ninsert into conditions\nselect generate_series(0, 200, 10), 'POR', 55, 75, 40, 70, NULL;\nCREATE MATERIALIZED VIEW mat_ffunc_test\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket(100, timec), aggregate_to_test_ffunc_extra(timec, 1, 3, 'test'::text)\nfrom conditions\ngroup by time_bucket(100, timec);\nNOTICE:  refreshing continuous aggregate \"mat_ffunc_test\"\nWARNING:  type integer text\nWARNING:  type integer text\nWARNING:  type integer text\nSELECT * FROM mat_ffunc_test ORDER BY time_bucket;\n time_bucket | aggregate_to_test_ffunc_extra \n-------------+-------------------------------\n           0 | \n         100 | \n         200 | \n\nDROP MATERIALIZED view mat_ffunc_test;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_27_65_chunk\nCREATE MATERIALIZED VIEW mat_ffunc_test\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket(100, timec), aggregate_to_test_ffunc_extra(timec, 4, 5, bigint '123')\nfrom conditions\ngroup by time_bucket(100, timec);\nNOTICE:  refreshing continuous aggregate \"mat_ffunc_test\"\nWARNING:  type integer bigint\nWARNING:  type integer bigint\nWARNING:  type integer bigint\nSELECT * FROM mat_ffunc_test ORDER BY time_bucket;\n time_bucket | aggregate_to_test_ffunc_extra \n-------------+-------------------------------\n           0 |                              \n         100 |                              \n         200 |                              \n\n--refresh mat view test when time_bucket is not projected --\nDROP MATERIALIZED VIEW mat_ffunc_test;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_28_66_chunk\nCREATE MATERIALIZED VIEW mat_refresh_test\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect location, max(humidity)\nfrom conditions\ngroup by time_bucket(100, timec), location WITH NO DATA;\ninsert into conditions\nselect generate_series(0, 50, 10), 'NYC', 55, 75, 40, 70, NULL;\nCALL refresh_continuous_aggregate('mat_refresh_test', NULL, NULL);\nSELECT * FROM mat_refresh_test order by 1,2 ;\n location | max \n----------+-----\n NYC      |  75\n POR      |  75\n POR      |  75\n POR      |  75\n\n-- test for bug when group by is not in project list\nCREATE MATERIALIZED VIEW conditions_grpby_view with (timescaledb.continuous, timescaledb.materialized_only=false) as\nselect time_bucket(100, timec),  sum(humidity)\nfrom conditions\ngroup by time_bucket(100, timec), location;\nNOTICE:  refreshing continuous aggregate \"conditions_grpby_view\"\nselect * from conditions_grpby_view order by 1, 2;\n time_bucket | sum \n-------------+-----\n           0 | 450\n           0 | 750\n         100 | 750\n         200 |  75\n\nCREATE MATERIALIZED VIEW conditions_grpby_view2 with (timescaledb.continuous, timescaledb.materialized_only=false) as\nselect time_bucket(100, timec), sum(humidity)\nfrom conditions\ngroup by time_bucket(100, timec), location\nhaving avg(temperature) > 0;\nNOTICE:  refreshing continuous aggregate \"conditions_grpby_view2\"\nselect * from conditions_grpby_view2 order by 1, 2;\n time_bucket | sum \n-------------+-----\n           0 | 450\n           0 | 750\n         100 | 750\n         200 |  75\n\n-- Test internal functions for continuous aggregates\nSELECT test.continuous_aggs_find_view('mat_refresh_test');\n continuous_aggs_find_view \n---------------------------\n \n\n-- Test pseudotype/enum handling\nCREATE TYPE status_enum AS ENUM (\n  'red',\n  'yellow',\n  'green'\n);\nCREATE TABLE cagg_types (\n  time TIMESTAMPTZ NOT NULL,\n  status status_enum,\n  names NAME[],\n  floats FLOAT[]\n);\nSELECT\n  table_name\nFROM\n  create_hypertable('cagg_types', 'time');\n table_name \n------------\n cagg_types\n\nINSERT INTO cagg_types\nSELECT\n  '2000-01-01',\n  'yellow',\n  '{foo,bar,baz}',\n  '{1,2.5,3}';\nCREATE MATERIALIZED VIEW mat_types WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  time_bucket('1d', time),\n  min(status) AS status,\n  max(names) AS names,\n  min(floats) AS floats\nFROM\n  cagg_types\nGROUP BY\n  1;\nNOTICE:  refreshing continuous aggregate \"mat_types\"\nCALL refresh_continuous_aggregate('mat_types',NULL,NULL);\nNOTICE:  continuous aggregate \"mat_types\" is already up-to-date\nSELECT * FROM mat_types;\n         time_bucket          | status |     names     |  floats   \n------------------------------+--------+---------------+-----------\n Fri Dec 31 16:00:00 1999 PST | yellow | {foo,bar,baz} | {1,2.5,3}\n\n-------------------------------------------------------------------------------------\n-- Test issue #2616 where cagg view contains an experssion with several aggregates in\nCREATE TABLE water_consumption\n(\n    sensor_id   integer      NOT NULL,\n    timestamp   timestamp(0) NOT NULL,\n    water_index integer\n);\nSELECT create_hypertable('water_consumption', 'timestamp', 'sensor_id', 2);\nWARNING:  column type \"timestamp without time zone\" used for \"timestamp\" does not follow best practices\n        create_hypertable        \n---------------------------------\n (34,public,water_consumption,t)\n\nINSERT INTO public.water_consumption (sensor_id, timestamp, water_index) VALUES\n  (1, '2010-11-03 09:42:30', 1030),\n  (1, '2010-11-03 09:42:40', 1032),\n  (1, '2010-11-03 09:42:50', 1035),\n  (1, '2010-11-03 09:43:30', 1040),\n  (1, '2010-11-03 09:43:40', 1045),\n  (1, '2010-11-03 09:43:50', 1050),\n  (1, '2010-11-03 09:44:30', 1052),\n  (1, '2010-11-03 09:44:40', 1057),\n  (1, '2010-11-03 09:44:50', 1060),\n  (1, '2010-11-03 09:45:30', 1063),\n  (1, '2010-11-03 09:45:40', 1067),\n  (1, '2010-11-03 09:45:50', 1070);\n-- The test with the view originally reported in the issue.\nCREATE MATERIALIZED VIEW water_consumption_aggregation_minute\n            WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE)\nAS\nSELECT sensor_id,\n       time_bucket(INTERVAL '1 minute', timestamp) + '1 minute' AS timestamp,\n       (max(water_index) - min(water_index))                    AS water_consumption\nFROM water_consumption\nGROUP BY sensor_id, time_bucket(INTERVAL '1 minute', timestamp)\nWITH NO DATA;\nCALL refresh_continuous_aggregate('water_consumption_aggregation_minute', NULL, NULL);\n-- The results of the view and the query over hypertable should be the same\nSELECT * FROM water_consumption_aggregation_minute ORDER BY water_consumption;\n sensor_id |        timestamp         | water_consumption \n-----------+--------------------------+-------------------\n         1 | Wed Nov 03 09:43:00 2010 |                 5\n         1 | Wed Nov 03 09:46:00 2010 |                 7\n         1 | Wed Nov 03 09:45:00 2010 |                 8\n         1 | Wed Nov 03 09:44:00 2010 |                10\n\nSELECT sensor_id,\n       time_bucket(INTERVAL '1 minute', timestamp) + '1 minute' AS timestamp,\n       (max(water_index) - min(water_index))                    AS water_consumption\nFROM water_consumption\nGROUP BY sensor_id, time_bucket(INTERVAL '1 minute', timestamp)\nORDER BY water_consumption;\n sensor_id |        timestamp         | water_consumption \n-----------+--------------------------+-------------------\n         1 | Wed Nov 03 09:43:00 2010 |                 5\n         1 | Wed Nov 03 09:46:00 2010 |                 7\n         1 | Wed Nov 03 09:45:00 2010 |                 8\n         1 | Wed Nov 03 09:44:00 2010 |                10\n\n-- Simplified test, where the view doesn't contain all group by clauses\nCREATE MATERIALIZED VIEW water_consumption_no_select_bucket\n            WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE)\nAS\nSELECT sensor_id,\n       (max(water_index) - min(water_index))                    AS water_consumption\nFROM water_consumption\nGROUP BY sensor_id, time_bucket(INTERVAL '1 minute', timestamp)\nWITH NO DATA;\nCALL refresh_continuous_aggregate('water_consumption_no_select_bucket', NULL, NULL);\n-- The results of the view and the query over hypertable should be the same\nSELECT * FROM water_consumption_no_select_bucket ORDER BY water_consumption;\n sensor_id | water_consumption \n-----------+-------------------\n         1 |                 5\n         1 |                 7\n         1 |                 8\n         1 |                10\n\nSELECT sensor_id,\n       (max(water_index) - min(water_index))                    AS water_consumption\nFROM water_consumption\nGROUP BY sensor_id, time_bucket(INTERVAL '1 minute', timestamp)\nORDER BY water_consumption;\n sensor_id | water_consumption \n-----------+-------------------\n         1 |                 5\n         1 |                 7\n         1 |                 8\n         1 |                10\n\n-- The test with SELECT matching GROUP BY and placing aggregate expression not the last\nCREATE MATERIALIZED VIEW water_consumption_aggregation_no_addition\n            WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE)\nAS\nSELECT sensor_id,\n       (max(water_index) - min(water_index))                    AS water_consumption,\n       time_bucket(INTERVAL '1 minute', timestamp) AS timestamp\nFROM water_consumption\nGROUP BY sensor_id, time_bucket(INTERVAL '1 minute', timestamp)\nWITH NO DATA;\nCALL refresh_continuous_aggregate('water_consumption_aggregation_no_addition', NULL, NULL);\n-- The results of the view and the query over hypertable should be the same\nSELECT * FROM water_consumption_aggregation_no_addition ORDER BY water_consumption;\n sensor_id | water_consumption |        timestamp         \n-----------+-------------------+--------------------------\n         1 |                 5 | Wed Nov 03 09:42:00 2010\n         1 |                 7 | Wed Nov 03 09:45:00 2010\n         1 |                 8 | Wed Nov 03 09:44:00 2010\n         1 |                10 | Wed Nov 03 09:43:00 2010\n\nSELECT sensor_id,\n       (max(water_index) - min(water_index))                    AS water_consumption,\n       time_bucket(INTERVAL '1 minute', timestamp) AS timestamp\nFROM water_consumption\nGROUP BY sensor_id, time_bucket(INTERVAL '1 minute', timestamp)\nORDER BY water_consumption;\n sensor_id | water_consumption |        timestamp         \n-----------+-------------------+--------------------------\n         1 |                 5 | Wed Nov 03 09:42:00 2010\n         1 |                 7 | Wed Nov 03 09:45:00 2010\n         1 |                 8 | Wed Nov 03 09:44:00 2010\n         1 |                10 | Wed Nov 03 09:43:00 2010\n\nDROP TABLE water_consumption CASCADE;\nNOTICE:  drop cascades to 6 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_35_73_chunk\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_36_74_chunk\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_37_75_chunk\n----\n--- github issue 2655 ---\ncreate table raw_data(time timestamptz, search_query text, cnt integer, cnt2 integer);\nselect create_hypertable('raw_data','time', chunk_time_interval=>'15 days'::interval);\n   create_hypertable    \n------------------------\n (38,public,raw_data,t)\n\ninsert into raw_data select '2000-01-01','Q1';\n--having has exprs that appear in select\nCREATE MATERIALIZED VIEW search_query_count_1m WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\n SELECT  search_query,count(search_query) as count,\n         time_bucket(INTERVAL '1 minute', time) AS bucket\n FROM raw_data\n WHERE search_query is not null AND LENGTH(TRIM(both from search_query))>0\n GROUP BY search_query, bucket HAVING count(search_query) > 3 OR sum(cnt) > 1;\nNOTICE:  refreshing continuous aggregate \"search_query_count_1m\"\n--having has aggregates + grp by columns that appear in select\nCREATE MATERIALIZED VIEW search_query_count_2 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\n SELECT  search_query,count(search_query) as count, sum(cnt),\n         time_bucket(INTERVAL '1 minute', time) AS bucket\n FROM raw_data\n WHERE search_query is not null AND LENGTH(TRIM(both from search_query))>0\n GROUP BY search_query, bucket\nHAVING count(search_query) > 3 OR sum(cnt) > 1 OR\n       ( sum(cnt) + count(cnt)) > 1\n       AND search_query = 'Q1';\nNOTICE:  refreshing continuous aggregate \"search_query_count_2\"\nCREATE MATERIALIZED VIEW search_query_count_3 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\n SELECT  search_query,count(search_query) as count, sum(cnt),\n         time_bucket(INTERVAL '1 minute', time) AS bucket\n FROM raw_data\n WHERE search_query is not null AND LENGTH(TRIM(both from search_query))>0\n GROUP BY cnt +cnt2 , bucket, search_query\n HAVING cnt + cnt2 + sum(cnt) > 2 or count(cnt2) > 10;\nNOTICE:  refreshing continuous aggregate \"search_query_count_3\"\ninsert into raw_data select '2000-01-01 00:00+0','Q1', 1, 100;\ninsert into raw_data select '2000-01-01 00:00+0','Q1', 2, 200;\ninsert into raw_data select '2000-01-01 00:00+0','Q1', 3, 300;\ninsert into raw_data select '2000-01-02 00:00+0','Q2', 10, 10;\ninsert into raw_data select '2000-01-02 00:00+0','Q2', 20, 20;\nCALL refresh_continuous_aggregate('search_query_count_1m', NULL, NULL);\nSELECT * FROM search_query_count_1m ORDER BY 1, 2;\n search_query | count |            bucket            \n--------------+-------+------------------------------\n Q1           |     3 | Fri Dec 31 16:00:00 1999 PST\n Q2           |     2 | Sat Jan 01 16:00:00 2000 PST\n\n--only 1 of these should appear in the result\ninsert into raw_data select '2000-01-02 00:00+0','Q3', 0, 0;\ninsert into raw_data select '2000-01-03 00:00+0','Q4', 20, 20;\nCALL refresh_continuous_aggregate('search_query_count_1m', NULL, NULL);\nSELECT * FROM search_query_count_1m ORDER BY 1, 2;\n search_query | count |            bucket            \n--------------+-------+------------------------------\n Q1           |     3 | Fri Dec 31 16:00:00 1999 PST\n Q2           |     2 | Sat Jan 01 16:00:00 2000 PST\n Q4           |     1 | Sun Jan 02 16:00:00 2000 PST\n\n--refresh search_query_count_2---\nCALL refresh_continuous_aggregate('search_query_count_2', NULL, NULL);\nSELECT * FROM search_query_count_2 ORDER BY 1, 2;\n search_query | count | sum |            bucket            \n--------------+-------+-----+------------------------------\n Q1           |     3 |   6 | Fri Dec 31 16:00:00 1999 PST\n Q2           |     2 |  30 | Sat Jan 01 16:00:00 2000 PST\n Q4           |     1 |  20 | Sun Jan 02 16:00:00 2000 PST\n\n--refresh search_query_count_3---\nCALL refresh_continuous_aggregate('search_query_count_3', NULL, NULL);\nSELECT * FROM search_query_count_3 ORDER BY 1, 2, 3;\n search_query | count | sum |            bucket            \n--------------+-------+-----+------------------------------\n Q1           |     1 |   1 | Fri Dec 31 16:00:00 1999 PST\n Q1           |     1 |   2 | Fri Dec 31 16:00:00 1999 PST\n Q1           |     1 |   3 | Fri Dec 31 16:00:00 1999 PST\n Q2           |     1 |  10 | Sat Jan 01 16:00:00 2000 PST\n Q2           |     1 |  20 | Sat Jan 01 16:00:00 2000 PST\n Q4           |     1 |  20 | Sun Jan 02 16:00:00 2000 PST\n\n--- TEST enable compression on continuous aggregates\nCREATE VIEW cagg_compression_status as\nSELECT ca.mat_hypertable_id AS mat_htid,\n       ca.user_view_name AS cagg_name ,\n       h.schema_name AS mat_schema_name,\n       h.table_name AS mat_table_name,\n       ca.materialized_only\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\n;\nSELECT mat_htid AS \"MAT_HTID\"\n     , mat_schema_name || '.' || mat_table_name AS \"MAT_HTNAME\"\n     , mat_table_name AS \"MAT_TABLE_NAME\"\nFROM cagg_compression_status\nWHERE cagg_name = 'search_query_count_3' \\gset\nALTER MATERIALIZED VIEW search_query_count_3 SET (timescaledb.compress = 'true');\nNOTICE:  defaulting compress_orderby to bucket,search_query\nSELECT cagg_name, mat_table_name\nFROM cagg_compression_status where cagg_name = 'search_query_count_3';\n      cagg_name       |       mat_table_name        \n----------------------+-----------------------------\n search_query_count_3 | _materialized_hypertable_41\n\n\\x\nSELECT * FROM timescaledb_information.compression_settings\nWHERE hypertable_name = :'MAT_TABLE_NAME';\n-[ RECORD 1 ]----------+----------------------------\nhypertable_schema      | _timescaledb_internal\nhypertable_name        | _materialized_hypertable_41\nattname                | bucket\nsegmentby_column_index | \norderby_column_index   | 1\norderby_asc            | t\norderby_nullsfirst     | f\n-[ RECORD 2 ]----------+----------------------------\nhypertable_schema      | _timescaledb_internal\nhypertable_name        | _materialized_hypertable_41\nattname                | search_query\nsegmentby_column_index | \norderby_column_index   | 2\norderby_asc            | t\norderby_nullsfirst     | f\n\n\\x\nSELECT compress_chunk(ch)\nFROM show_chunks('search_query_count_3') ch;\n              compress_chunk              \n------------------------------------------\n _timescaledb_internal._hyper_41_79_chunk\n\nSELECT * from search_query_count_3 ORDER BY 1, 2, 3;\n search_query | count | sum |            bucket            \n--------------+-------+-----+------------------------------\n Q1           |     1 |   1 | Fri Dec 31 16:00:00 1999 PST\n Q1           |     1 |   2 | Fri Dec 31 16:00:00 1999 PST\n Q1           |     1 |   3 | Fri Dec 31 16:00:00 1999 PST\n Q2           |     1 |  10 | Sat Jan 01 16:00:00 2000 PST\n Q2           |     1 |  20 | Sat Jan 01 16:00:00 2000 PST\n Q4           |     1 |  20 | Sun Jan 02 16:00:00 2000 PST\n\n-- insert into a new region of the hypertable and then refresh the cagg\n-- (note we still do not support refreshes into existing regions.\n-- cagg chunks do not map 1-1 to hypertabl regions. They encompass\n-- more data\n-- ).\ninsert into raw_data select '2000-05-01 00:00+0','Q3', 0, 0;\n-- On PG >= 14 the refresh test below will pass because we added support for UPDATE/DELETE on compressed chunks in PR #5339\n\\set ON_ERROR_STOP 0\nCALL refresh_continuous_aggregate('search_query_count_3', NULL, '2000-06-01 00:00+0'::timestamptz);\nCALL refresh_continuous_aggregate('search_query_count_3', '2000-05-01 00:00+0'::timestamptz, '2000-06-01 00:00+0'::timestamptz);\nNOTICE:  continuous aggregate \"search_query_count_3\" is already up-to-date\n\\set ON_ERROR_STOP 1\n--insert row\ninsert into raw_data select '2001-05-10 00:00+0','Q3', 100, 100;\n--this should succeed since it does not refresh any compressed regions in the cagg\nCALL refresh_continuous_aggregate('search_query_count_3', '2001-05-01 00:00+0'::timestamptz, '2001-06-01 00:00+0'::timestamptz);\n--verify watermark and check that chunks are compressed\nSELECT _timescaledb_functions.to_timestamp(w) FROM _timescaledb_functions.cagg_watermark(:'MAT_HTID') w;\n         to_timestamp         \n------------------------------\n Wed May 09 17:01:00 2001 PDT\n\nSELECT chunk_name, range_start, range_end, is_compressed\nFROM timescaledb_information.chunks\nWHERE hypertable_name = :'MAT_TABLE_NAME'\nORDER BY 1;\n     chunk_name     |         range_start          |          range_end           | is_compressed \n--------------------+------------------------------+------------------------------+---------------\n _hyper_41_79_chunk | Fri Dec 24 16:00:00 1999 PST | Mon May 22 17:00:00 2000 PDT | t\n _hyper_41_83_chunk | Sun Mar 18 16:00:00 2001 PST | Wed Aug 15 17:00:00 2001 PDT | f\n\nSELECT * FROM _timescaledb_catalog.continuous_aggs_materialization_invalidation_log\nWHERE materialization_id = :'MAT_HTID' ORDER BY 1, 2,3;\n materialization_id | lowest_modified_value | greatest_modified_value \n--------------------+-----------------------+-------------------------\n                 41 |  -9223372036854775808 |     -210866803200000001\n                 41 |       959817600000000 |         988675199999999\n                 41 |       991353600000000 |     9223372036854775807\n\nSELECT * from search_query_count_3\nWHERE bucket > '2001-01-01'\nORDER BY 1, 2, 3;\n search_query | count | sum |            bucket            \n--------------+-------+-----+------------------------------\n Q3           |     1 | 100 | Wed May 09 17:00:00 2001 PDT\n\n--now disable compression , will error out --\n\\set ON_ERROR_STOP 0\nALTER MATERIALIZED VIEW search_query_count_3 SET (timescaledb.compress = 'false');\nERROR:  cannot disable columnstore on hypertable with columnstore chunks\n\\set ON_ERROR_STOP 1\nSELECT decompress_chunk(format('%I.%I', schema_name, table_name))\nFROM _timescaledb_catalog.chunk\nWHERE hypertable_id = :'MAT_HTID' and status = 1;\n             decompress_chunk             \n------------------------------------------\n _timescaledb_internal._hyper_41_79_chunk\n\n--disable compression on cagg after decompressing all chunks--\nALTER MATERIALIZED VIEW search_query_count_3 SET (timescaledb.compress = 'false');\nSELECT cagg_name, mat_table_name\nFROM cagg_compression_status where cagg_name = 'search_query_count_3';\n      cagg_name       |       mat_table_name        \n----------------------+-----------------------------\n search_query_count_3 | _materialized_hypertable_41\n\nSELECT view_name, materialized_only, compression_enabled\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'search_query_count_3';\n      view_name       | materialized_only | compression_enabled \n----------------------+-------------------+---------------------\n search_query_count_3 | f                 | f\n\n-- TEST caggs on table with more columns than in the cagg view defn --\nCREATE TABLE test_morecols ( time TIMESTAMPTZ NOT NULL,\n                             val1 INTEGER, val2 INTEGER, val3 INTEGER, val4 INTEGER,\n                             val5 INTEGER,  val6 INTEGER, val7 INTEGER, val8 INTEGER);\nSELECT create_hypertable('test_morecols', 'time', chunk_time_interval=> '7 days'::interval);\n      create_hypertable      \n-----------------------------\n (43,public,test_morecols,t)\n\nINSERT INTO test_morecols\nSELECT generate_series('2018-12-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 55, 75, 40, 70, NULL, 100, 200, 200;\nCREATE MATERIALIZED VIEW test_morecols_cagg with (timescaledb.continuous, timescaledb.materialized_only=false)\nAS SELECT time_bucket('30 days',time), avg(val1),  count(val2)\n FROM test_morecols GROUP BY 1;\nNOTICE:  refreshing continuous aggregate \"test_morecols_cagg\"\nALTER MATERIALIZED VIEW test_morecols_cagg SET (timescaledb.compress='true');\nNOTICE:  defaulting compress_orderby to time_bucket\nSELECT compress_chunk(ch) FROM show_chunks('test_morecols_cagg') ch;\n              compress_chunk              \n------------------------------------------\n _timescaledb_internal._hyper_44_89_chunk\n\nSELECT * FROM test_morecols_cagg ORDER BY time_bucket;\n         time_bucket          |         avg         | count \n------------------------------+---------------------+-------\n Fri Nov 23 16:00:00 2018 PST | 55.0000000000000000 |    23\n Sun Dec 23 16:00:00 2018 PST | 55.0000000000000000 |     8\n\nSELECT view_name, materialized_only, compression_enabled\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_morecols_cagg';\n     view_name      | materialized_only | compression_enabled \n--------------------+-------------------+---------------------\n test_morecols_cagg | f                 | t\n\n--should keep compressed option, modify only materialized --\nALTER MATERIALIZED VIEW test_morecols_cagg SET (timescaledb.materialized_only='true');\nSELECT view_name, materialized_only, compression_enabled\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_morecols_cagg';\n     view_name      | materialized_only | compression_enabled \n--------------------+-------------------+---------------------\n test_morecols_cagg | t                 | t\n\nCREATE TABLE issue3248(filler_1 int, filler_2 int, filler_3 int, time timestamptz NOT NULL, device_id int, v0 int, v1 int, v2 float, v3 float);\nCREATE INDEX ON issue3248(time DESC);\nCREATE INDEX ON issue3248(device_id,time DESC);\nSELECT create_hypertable('issue3248','time',create_default_indexes:=false);\n    create_hypertable    \n-------------------------\n (46,public,issue3248,t)\n\nALTER TABLE issue3248 DROP COLUMN filler_1;\nINSERT INTO issue3248(time,device_id,v0,v1,v2,v3)\nSELECT time, device_id, device_id+1,  device_id + 2, device_id + 0.5, NULL\nFROM generate_series('2000-01-01 0:00:00+0'::timestamptz,'2000-01-05 23:55:00+0','8h') gtime(time),\n     generate_series(1,5,1) gdevice(device_id);\nALTER TABLE issue3248 DROP COLUMN filler_2;\nINSERT INTO issue3248(time,device_id,v0,v1,v2,v3)\nSELECT time, device_id, device_id-1, device_id + 2, device_id + 0.5, NULL\nFROM generate_series('2000-01-06 0:00:00+0'::timestamptz,'2000-01-12 23:55:00+0','8h') gtime(time),\n     generate_series(1,5,1) gdevice(device_id);\nALTER TABLE issue3248 DROP COLUMN filler_3;\nINSERT INTO issue3248(time,device_id,v0,v1,v2,v3)\nSELECT time, device_id, device_id, device_id + 2, device_id + 0.5, NULL\nFROM generate_series('2000-01-13 0:00:00+0'::timestamptz,'2000-01-19 23:55:00+0','8h') gtime(time),\n     generate_series(1,5,1) gdevice(device_id);\nANALYZE issue3248;\nCREATE materialized view issue3248_cagg WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS SELECT time_bucket('1h',time), device_id, min(v0), max(v1), avg(v2)\nFROM issue3248 GROUP BY 1,2;\nNOTICE:  refreshing continuous aggregate \"issue3248_cagg\"\nSELECT\n  FROM issue3248 AS m,\n       LATERAL(SELECT m FROM issue3248_cagg WHERE avg IS NULL LIMIT 1) AS lat;\n--\n\n-- test that option create_group_indexes is taken into account\nCREATE TABLE test_group_idx (\ntime timestamptz,\nsymbol int,\nvalue numeric\n);\nselect create_hypertable('test_group_idx', 'time');\n      create_hypertable       \n------------------------------\n (48,public,test_group_idx,t)\n\ninsert into test_group_idx\nselect t, round(random()*10), random()*5\nfrom generate_series('2020-01-01', '2020-02-25', INTERVAL '12 hours') t;\ncreate materialized view cagg_index_true\nwith (timescaledb.continuous, timescaledb.materialized_only=false, timescaledb.create_group_indexes=true) as\nselect\n\ttime_bucket('1 day', \"time\") as bucket,\n\tsum(value),\n\tsymbol\nfrom test_group_idx\ngroup by bucket, symbol;\nNOTICE:  refreshing continuous aggregate \"cagg_index_true\"\ncreate materialized view cagg_index_false\nwith (timescaledb.continuous, timescaledb.materialized_only=false, timescaledb.create_group_indexes=false) as\nselect\n\ttime_bucket('1 day', \"time\") as bucket,\n\tsum(value),\n\tsymbol\nfrom test_group_idx\ngroup by bucket, symbol;\nNOTICE:  refreshing continuous aggregate \"cagg_index_false\"\ncreate materialized view cagg_index_default\nwith (timescaledb.continuous, timescaledb.materialized_only=false) as\nselect\n\ttime_bucket('1 day', \"time\") as bucket,\n\tsum(value),\n\tsymbol\nfrom test_group_idx\ngroup by bucket, symbol;\nNOTICE:  refreshing continuous aggregate \"cagg_index_default\"\n-- see corresponding materialization_hypertables\nselect view_name, materialization_hypertable_name from timescaledb_information.continuous_aggregates ca\nwhere view_name like 'cagg_index_%' ORDER BY view_name;\n     view_name      | materialization_hypertable_name \n--------------------+---------------------------------\n cagg_index_default | _materialized_hypertable_51\n cagg_index_false   | _materialized_hypertable_50\n cagg_index_true    | _materialized_hypertable_49\n\n-- now make sure a group index has been created when explicitly asked for\n\\x on\nselect i.*\nfrom pg_indexes i\njoin pg_class c\n    on schemaname = relnamespace::regnamespace::text\n    and tablename = relname\nwhere tablename in (select materialization_hypertable_name from timescaledb_information.continuous_aggregates\nwhere view_name like 'cagg_index_%')\norder by tablename, indexname;\n-[ RECORD 1 ]-------------------------------------------------------------------------------------------------------------------------------------------------\nschemaname | _timescaledb_internal\ntablename  | _materialized_hypertable_49\nindexname  | _materialized_hypertable_49_bucket_idx\ntablespace | \nindexdef   | CREATE INDEX _materialized_hypertable_49_bucket_idx ON _timescaledb_internal._materialized_hypertable_49 USING btree (bucket DESC)\n-[ RECORD 2 ]-------------------------------------------------------------------------------------------------------------------------------------------------\nschemaname | _timescaledb_internal\ntablename  | _materialized_hypertable_49\nindexname  | _materialized_hypertable_49_symbol_bucket_idx\ntablespace | \nindexdef   | CREATE INDEX _materialized_hypertable_49_symbol_bucket_idx ON _timescaledb_internal._materialized_hypertable_49 USING btree (symbol, bucket DESC)\n-[ RECORD 3 ]-------------------------------------------------------------------------------------------------------------------------------------------------\nschemaname | _timescaledb_internal\ntablename  | _materialized_hypertable_50\nindexname  | _materialized_hypertable_50_bucket_idx\ntablespace | \nindexdef   | CREATE INDEX _materialized_hypertable_50_bucket_idx ON _timescaledb_internal._materialized_hypertable_50 USING btree (bucket DESC)\n-[ RECORD 4 ]-------------------------------------------------------------------------------------------------------------------------------------------------\nschemaname | _timescaledb_internal\ntablename  | _materialized_hypertable_51\nindexname  | _materialized_hypertable_51_bucket_idx\ntablespace | \nindexdef   | CREATE INDEX _materialized_hypertable_51_bucket_idx ON _timescaledb_internal._materialized_hypertable_51 USING btree (bucket DESC)\n-[ RECORD 5 ]-------------------------------------------------------------------------------------------------------------------------------------------------\nschemaname | _timescaledb_internal\ntablename  | _materialized_hypertable_51\nindexname  | _materialized_hypertable_51_symbol_bucket_idx\ntablespace | \nindexdef   | CREATE INDEX _materialized_hypertable_51_symbol_bucket_idx ON _timescaledb_internal._materialized_hypertable_51 USING btree (symbol, bucket DESC)\n\n\\x off\n--\n-- TESTs for removing old CAggs restrictions\n--\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 8 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_29_67_chunk\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_30_68_chunk\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_31_69_chunk\nCREATE TABLE conditions (\n  timec       TIMESTAMPTZ       NOT NULL,\n  location    TEXT              NOT NULL,\n  temperature DOUBLE PRECISION  NULL,\n  humidity    DOUBLE PRECISION  NULL\n);\nSELECT create_hypertable('conditions', 'timec');\n    create_hypertable     \n--------------------------\n (52,public,conditions,t)\n\nINSERT INTO conditions\nVALUES\n  ('2010-01-01 09:00:00-08', 'SFO', 55, 45),\n  ('2010-01-02 09:00:00-08', 'por', 100, 100),\n  ('2010-01-02 09:00:00-08', 'NYC', 65, 45),\n  ('2010-01-02 09:00:00-08', 'SFO', 65, 45),\n  ('2010-01-03 09:00:00-08', 'NYC', 45, 55),\n  ('2010-01-05 09:00:00-08', 'SFO', 75, 100),\n  ('2018-11-01 09:00:00-08', 'NYC', 45, 35),\n  ('2018-11-02 09:00:00-08', 'NYC', 35, 15),\n  ('2018-11-03 09:00:00-08', 'NYC', 35, 25);\n-- aggregate with DISTINCT\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1week', timec),\n  COUNT(location),\n  SUM(DISTINCT temperature)\nFROM conditions\nGROUP BY time_bucket('1week', timec), location;\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\nSELECT * FROM mat_m1 ORDER BY 1, 2, 3;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Dec 27 16:00:00 2009 PST |     1 | 100\n Sun Dec 27 16:00:00 2009 PST |     2 | 110\n Sun Dec 27 16:00:00 2009 PST |     2 | 120\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n Sun Oct 28 17:00:00 2018 PDT |     3 |  80\n\n-- aggregate with FILTER\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1week', timec),\n  SUM(temperature) FILTER (WHERE humidity > 60)\nFROM conditions\nGROUP BY time_bucket('1week', timec), location;\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\nSELECT * FROM mat_m1 ORDER BY 1, 2;\n         time_bucket          | sum \n------------------------------+-----\n Sun Dec 27 16:00:00 2009 PST | 100\n Sun Dec 27 16:00:00 2009 PST |    \n Sun Dec 27 16:00:00 2009 PST |    \n Sun Jan 03 16:00:00 2010 PST |  75\n Sun Oct 28 17:00:00 2018 PDT |    \n\n-- aggregate with filter in having clause\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1week', timec),\n  MAX(temperature)\nFROM conditions\nGROUP BY time_bucket('1week', timec), location\nHAVING SUM(temperature) FILTER (WHERE humidity > 40) > 50;\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\nSELECT * FROM mat_m1 ORDER BY 1, 2;\n         time_bucket          | max \n------------------------------+-----\n Sun Dec 27 16:00:00 2009 PST |  65\n Sun Dec 27 16:00:00 2009 PST |  65\n Sun Dec 27 16:00:00 2009 PST | 100\n Sun Jan 03 16:00:00 2010 PST |  75\n\n-- ordered set aggr\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_55_116_chunk\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1week', timec),\n  MODE() WITHIN GROUP(ORDER BY humidity)\nFROM conditions\nGROUP BY time_bucket('1week', timec);\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\nSELECT * FROM mat_m1 ORDER BY 1;\n         time_bucket          | mode \n------------------------------+------\n Sun Dec 27 16:00:00 2009 PST |   45\n Sun Jan 03 16:00:00 2010 PST |  100\n Sun Oct 28 17:00:00 2018 PDT |   15\n\n-- hypothetical-set aggr\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1week', timec),\n  RANK(60) WITHIN GROUP (ORDER BY humidity),\n  DENSE_RANK(60) WITHIN GROUP (ORDER BY humidity),\n  PERCENT_RANK(60) WITHIN GROUP (ORDER BY humidity)\nFROM conditions\nGROUP BY time_bucket('1week', timec);\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\nSELECT * FROM mat_m1 ORDER BY 1;\n         time_bucket          | rank | dense_rank | percent_rank \n------------------------------+------+------------+--------------\n Sun Dec 27 16:00:00 2009 PST |    5 |          3 |          0.8\n Sun Jan 03 16:00:00 2010 PST |    1 |          1 |            0\n Sun Oct 28 17:00:00 2018 PDT |    4 |          4 |            1\n\n-- userdefined aggregate without combine function\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\nCREATE AGGREGATE newavg (\n  sfunc = int4_avg_accum,\n  basetype = int4,\n  stype = _int8,\n  finalfunc = int8_avg,\n  initcond1 = '{0,0}'\n);\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  SUM(humidity),\n  round(newavg(temperature::int4))\nFROM conditions\nGROUP BY time_bucket('1week', timec), location ORDER BY 1,2;\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\nSELECT * FROM mat_m1 ORDER BY 1, 2;\n sum | round \n-----+-------\n  75 |    38\n  90 |    60\n 100 |    55\n 100 |    75\n 100 |   100\n\n-- ORDER BY in the view definition\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1week', timec),\n  COUNT(location),\n  SUM(temperature)\nFROM conditions\nGROUP BY time_bucket('1week', timec)\nORDER BY sum DESC;\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\n-- CAgg definition for realtime\nSELECT pg_get_viewdef('mat_m1',true);\n                                                                                  pg_get_viewdef                                                                                   \n-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n ( SELECT _materialized_hypertable_59.time_bucket,                                                                                                                                +\n     _materialized_hypertable_59.count,                                                                                                                                           +\n     _materialized_hypertable_59.sum                                                                                                                                              +\n    FROM _timescaledb_internal._materialized_hypertable_59                                                                                                                        +\n   WHERE _materialized_hypertable_59.time_bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(59)), '-infinity'::timestamp with time zone)+\n   ORDER BY _materialized_hypertable_59.sum DESC)                                                                                                                                 +\n UNION ALL                                                                                                                                                                        +\n ( SELECT time_bucket('@ 7 days'::interval, conditions.timec) AS time_bucket,                                                                                                     +\n     count(conditions.location) AS count,                                                                                                                                         +\n     sum(conditions.temperature) AS sum                                                                                                                                           +\n    FROM conditions                                                                                                                                                               +\n   WHERE conditions.timec >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(59)), '-infinity'::timestamp with time zone)                      +\n   GROUP BY (time_bucket('@ 7 days'::interval, conditions.timec))                                                                                                                 +\n   ORDER BY (sum(conditions.temperature)) DESC)                                                                                                                                   +\n   ORDER BY 3 DESC;\n\n-- Ordered result\nSELECT * FROM mat_m1;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Dec 27 16:00:00 2009 PST |     5 | 330\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n\n-- Insert new data and query again to make sure we produce ordered data\nINSERT INTO conditions VALUES ('2018-11-10 09:00:00-08', 'SFO', 10, 10);\nSELECT * FROM mat_m1;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Dec 27 16:00:00 2009 PST |     5 | 330\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n Sun Nov 04 16:00:00 2018 PST |     1 |  10\n\n-- This new row will change the order again\nINSERT INTO conditions VALUES ('2018-11-11 09:00:00-08', 'SFO', 400, 400);\nSELECT * FROM mat_m1;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Nov 04 16:00:00 2018 PST |     2 | 410\n Sun Dec 27 16:00:00 2009 PST |     5 | 330\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n\n-- Merge Append\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM mat_m1;\n--- QUERY PLAN ---\n Merge Append\n   Sort Key: _materialized_hypertable_59.sum DESC\n   ->  Merge Append\n         Sort Key: _materialized_hypertable_59.sum DESC\n         ->  Index Scan Backward using _hyper_59_123_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_123_chunk\n         ->  Index Scan Backward using _hyper_59_124_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_124_chunk\n               Index Cond: (time_bucket < 'Sun Nov 04 16:00:00 2018 PST'::timestamp with time zone)\n   ->  Sort\n         Sort Key: (sum(conditions.temperature)) DESC\n         ->  Finalize HashAggregate\n               Group Key: (time_bucket('@ 7 days'::interval, conditions.timec))\n               ->  Append\n                     ->  Partial HashAggregate\n                           Group Key: time_bucket('@ 7 days'::interval, _hyper_52_111_chunk.timec)\n                           ->  Index Scan Backward using _hyper_52_111_chunk_conditions_timec_idx on _hyper_52_111_chunk\n                                 Index Cond: (timec >= 'Sun Nov 04 16:00:00 2018 PST'::timestamp with time zone)\n                     ->  Partial HashAggregate\n                           Group Key: time_bucket('@ 7 days'::interval, _hyper_52_125_chunk.timec)\n                           ->  Seq Scan on _hyper_52_125_chunk\n\n-- Ordering by another column\nSELECT * FROM mat_m1 ORDER BY count;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n Sun Nov 04 16:00:00 2018 PST |     2 | 410\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Dec 27 16:00:00 2009 PST |     5 | 330\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM mat_m1 ORDER BY count;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _materialized_hypertable_59.count\n   ->  Merge Append\n         Sort Key: _materialized_hypertable_59.sum DESC\n         ->  Merge Append\n               Sort Key: _materialized_hypertable_59.sum DESC\n               ->  Index Scan Backward using _hyper_59_123_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_123_chunk\n               ->  Index Scan Backward using _hyper_59_124_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_124_chunk\n                     Index Cond: (time_bucket < 'Sun Nov 04 16:00:00 2018 PST'::timestamp with time zone)\n         ->  Sort\n               Sort Key: (sum(conditions.temperature)) DESC\n               ->  Finalize HashAggregate\n                     Group Key: (time_bucket('@ 7 days'::interval, conditions.timec))\n                     ->  Append\n                           ->  Partial HashAggregate\n                                 Group Key: time_bucket('@ 7 days'::interval, _hyper_52_111_chunk.timec)\n                                 ->  Index Scan Backward using _hyper_52_111_chunk_conditions_timec_idx on _hyper_52_111_chunk\n                                       Index Cond: (timec >= 'Sun Nov 04 16:00:00 2018 PST'::timestamp with time zone)\n                           ->  Partial HashAggregate\n                                 Group Key: time_bucket('@ 7 days'::interval, _hyper_52_125_chunk.timec)\n                                 ->  Seq Scan on _hyper_52_125_chunk\n\n-- Change the type of cagg\nALTER MATERIALIZED VIEW mat_m1 SET (timescaledb.materialized_only=true);\n-- CAgg definition for materialized only\nSELECT pg_get_viewdef('mat_m1',true);\n                      pg_get_viewdef                       \n-----------------------------------------------------------\n  SELECT time_bucket,                                     +\n     count,                                               +\n     sum                                                  +\n    FROM _timescaledb_internal._materialized_hypertable_59+\n   ORDER BY sum DESC;\n\n-- Now the query will show only the materialized data, without last two\n-- records inserted into the original hypertable (last two insers above)\nSELECT * FROM mat_m1;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Dec 27 16:00:00 2009 PST |     5 | 330\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n\n-- Merge Append\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM mat_m1;\n--- QUERY PLAN ---\n Merge Append\n   Sort Key: _materialized_hypertable_59.sum DESC\n   ->  Index Scan Backward using _hyper_59_123_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_123_chunk\n   ->  Index Scan Backward using _hyper_59_124_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_124_chunk\n\n-- Ordering by another column\nSELECT * FROM mat_m1 ORDER BY count;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Dec 27 16:00:00 2009 PST |     5 | 330\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM mat_m1 ORDER BY count;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _materialized_hypertable_59.count\n   ->  Merge Append\n         Sort Key: _materialized_hypertable_59.sum DESC\n         ->  Index Scan Backward using _hyper_59_123_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_123_chunk\n         ->  Index Scan Backward using _hyper_59_124_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_124_chunk\n\nSELECT h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\n-- Invalidate old region and refresh again\nDELETE FROM conditions WHERE timec < '2010-01-05 09:00:00-08';\nCALL refresh_continuous_aggregate('mat_m1', NULL, NULL);\n-- Querying the cagg produce ordered records as expected\nSELECT * FROM mat_m1;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Nov 04 16:00:00 2018 PST |     2 | 410\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n\n-- Querying direct the materialization hypertable doesn't\n-- produce ordered records\nSELECT * FROM :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\";\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Nov 04 16:00:00 2018 PST |     2 | 410\n\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to 2 other objects\nCREATE TABLE conditions (\n  timec       TIMESTAMPTZ       NOT NULL,\n  location    TEXT              NOT NULL,\n  temperature DOUBLE PRECISION  NULL,\n  humidity    DOUBLE PRECISION  NULL\n);\nSELECT table_name FROM create_hypertable('conditions', 'timec');\n table_name \n------------\n conditions\n\nINSERT INTO conditions VALUES\n  ('2010-01-01 09:00:00-08', 'SFO', 55, 45),\n  ('2010-01-02 09:00:00-08', 'por', 100, 100),\n  ('2010-01-02 09:00:00-08', 'SFO', 65, 45),\n  ('2010-01-02 09:00:00-08', 'NYC', 65, 45),\n  ('2018-11-01 09:00:00-08', 'NYC', 45, 35),\n  ('2018-11-02 09:00:00-08', 'NYC', 35, 15);\nCREATE MATERIALIZED VIEW conditions_summary_new(timec, minl, sumt, sumh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nAS\nSELECT time_bucket('1day', timec), min(location), sum(temperature), sum(humidity)\nFROM conditions\nGROUP BY time_bucket('1day', timec) WITH NO DATA;\n\\x ON\nSELECT *\nFROM timescaledb_information.continuous_aggregates\nWHERE view_name = 'conditions_summary_new';\n-[ RECORD 1 ]---------------------+----------------------------------------------------------\nhypertable_schema                 | public\nhypertable_name                   | conditions\nview_schema                       | public\nview_name                         | conditions_summary_new\nview_owner                        | default_perm_user\nmaterialized_only                 | t\ncompression_enabled               | f\nmaterialization_hypertable_schema | _timescaledb_internal\nmaterialization_hypertable_name   | _materialized_hypertable_61\nview_definition                   |  SELECT time_bucket('@ 1 day'::interval, timec) AS timec,+\n                                  |     min(location) AS minl,                               +\n                                  |     sum(temperature) AS sumt,                            +\n                                  |     sum(humidity) AS sumh                                +\n                                  |    FROM conditions                                       +\n                                  |   GROUP BY (time_bucket('@ 1 day'::interval, timec));\n\n\\x OFF\nCALL refresh_continuous_aggregate('conditions_summary_new', NULL, NULL);\n-- Check and compare number of returned rows\nSELECT count(*) FROM conditions_summary_new;\n count \n-------\n     4\n\n-- Parallel planning test for realtime Continuous Aggregate\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to 2 other objects\nCREATE TABLE conditions (\n  timec       TIMESTAMPTZ       NOT NULL,\n  temperature DOUBLE PRECISION  NULL\n);\nSELECT table_name FROM create_hypertable('conditions', 'timec');\n table_name \n------------\n conditions\n\nINSERT INTO conditions\nSELECT t, 10 FROM generate_series('2023-01-01 00:00-03'::timestamptz, '2023-12-31 23:59-03'::timestamptz, '1 hour'::interval) AS t;\nCREATE MATERIALIZED VIEW conditions_daily WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1 day', timec),\n  SUM(temperature)\nFROM conditions\nGROUP BY 1\nORDER BY 2 DESC;\nNOTICE:  refreshing continuous aggregate \"conditions_daily\"\nSELECT set_config(CASE WHEN current_setting('server_version_num')::int < 160000 THEN 'force_parallel_mode' ELSE 'debug_parallel_query' END,'on', false);\n set_config \n------------\n on\n\nSET max_parallel_workers_per_gather = 4;\nSET parallel_setup_cost = 0;\nSET parallel_tuple_cost = 0;\n-- Parallel planning\nEXPLAIN (BUFFERS OFF, COSTS OFF, TIMING OFF) SELECT * FROM conditions_daily WHERE time_bucket >= '2023-07-01';\n--- QUERY PLAN ---\n Merge Append\n   Sort Key: _materialized_hypertable_63.sum DESC\n   ->  Gather Merge\n         Workers Planned: 2\n         ->  Sort\n               Sort Key: _materialized_hypertable_63.sum DESC\n               ->  Parallel Append\n                     ->  Parallel Index Scan using _hyper_63_185_chunk__materialized_hypertable_63_time_bucket_idx on _hyper_63_185_chunk\n                           Index Cond: ((time_bucket < 'Mon Jan 01 16:00:00 2024 PST'::timestamp with time zone) AND (time_bucket >= 'Sat Jul 01 00:00:00 2023 PDT'::timestamp with time zone))\n                     ->  Parallel Index Scan using _hyper_63_187_chunk__materialized_hypertable_63_time_bucket_idx on _hyper_63_187_chunk\n                           Index Cond: ((time_bucket < 'Mon Jan 01 16:00:00 2024 PST'::timestamp with time zone) AND (time_bucket >= 'Sat Jul 01 00:00:00 2023 PDT'::timestamp with time zone))\n                     ->  Parallel Seq Scan on _hyper_63_184_chunk\n   ->  Sort\n         Sort Key: (sum(_hyper_62_182_chunk.temperature)) DESC\n         ->  HashAggregate\n               Group Key: (time_bucket('@ 1 day'::interval, _hyper_62_182_chunk.timec))\n               ->  Gather\n                     Workers Planned: 1\n                     ->  Result\n                           ->  Parallel Index Scan Backward using _hyper_62_182_chunk_conditions_timec_idx on _hyper_62_182_chunk\n                                 Index Cond: ((timec >= 'Mon Jan 01 16:00:00 2024 PST'::timestamp with time zone) AND (timec >= 'Sat Jul 01 00:00:00 2023 PDT'::timestamp with time zone))\n                                 Filter: (time_bucket('@ 1 day'::interval, timec) >= 'Sat Jul 01 00:00:00 2023 PDT'::timestamp with time zone)\n\n"
  },
  {
    "path": "tsl/test/expected/cagg-18.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- initialize the bgw mock state to prevent the materialization workers from running\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION ts_bgw_params_create() RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION test.continuous_aggs_find_view(cagg REGCLASS) RETURNS VOID\nAS :TSL_MODULE_PATHNAME, 'ts_test_continuous_agg_find_by_view_name' LANGUAGE C;\n\\set WAIT_ON_JOB 0\n\\set IMMEDIATELY_SET_UNTIL 1\n\\set WAIT_FOR_OTHER_TO_ADVANCE 2\n-- remove any default jobs, e.g., telemetry so bgw_job isn't polluted\nDELETE FROM _timescaledb_catalog.bgw_job;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT * FROM _timescaledb_catalog.bgw_job;\n id | application_name | schedule_interval | max_runtime | max_retries | retry_period | proc_schema | proc_name | owner | scheduled | fixed_schedule | initial_start | hypertable_id | config | check_schema | check_name | timezone \n----+------------------+-------------------+-------------+-------------+--------------+-------------+-----------+-------+-----------+----------------+---------------+---------------+--------+--------------+------------+----------\n\n--TEST1 ---\n--basic test with count\ncreate table foo (a integer, b integer, c integer);\nselect table_name from create_hypertable('foo', 'a', chunk_time_interval=> 10);\n table_name \n------------\n foo\n\ninsert into foo values( 3 , 16 , 20);\ninsert into foo values( 1 , 10 , 20);\ninsert into foo values( 1 , 11 , 20);\ninsert into foo values( 1 , 12 , 20);\ninsert into foo values( 1 , 13 , 20);\ninsert into foo values( 1 , 14 , 20);\ninsert into foo values( 2 , 14 , 20);\ninsert into foo values( 2 , 15 , 20);\ninsert into foo values( 2 , 16 , 20);\nCREATE OR REPLACE FUNCTION integer_now_foo() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(a), 0) FROM foo $$;\nSELECT set_integer_now_func('foo', 'integer_now_foo');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW mat_m1(a, countb)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect a, count(b)\nfrom foo\ngroup by time_bucket(1, a), a WITH NO DATA;\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, 2::integer, '12 h'::interval) AS job_id\n\\gset\nSELECT * FROM _timescaledb_catalog.bgw_job;\n  id  |              application_name              | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema       |              proc_name              |       owner       | scheduled | fixed_schedule | initial_start | hypertable_id |                             config                              |      check_schema      |                check_name                 | timezone \n------+--------------------------------------------+-------------------+-------------+-------------+--------------+------------------------+-------------------------------------+-------------------+-----------+----------------+---------------+---------------+-----------------------------------------------------------------+------------------------+-------------------------------------------+----------\n 1000 | Refresh Continuous Aggregate Policy [1000] | @ 12 hours        | @ 0         |          -1 | @ 12 hours   | _timescaledb_functions | policy_refresh_continuous_aggregate | default_perm_user | t         | f              |               |             2 | {\"end_offset\": 2, \"start_offset\": null, \"mat_hypertable_id\": 2} | _timescaledb_functions | policy_refresh_continuous_aggregate_check | \n\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\ninsert into :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect a, count(b),\ntime_bucket(1, a)\nfrom foo\ngroup by time_bucket(1, a) , a ;\nselect * from mat_m1 order by a ;\n a | countb \n---+--------\n 1 |      5\n 2 |      3\n 3 |      1\n\n--check triggers on user hypertable --\nSET ROLE :ROLE_SUPERUSER;\nselect tgname, tgtype, tgenabled , relname from pg_trigger, pg_class\nwhere tgrelid = pg_class.oid and pg_class.relname like 'foo'\norder by tgname;\n tgname | tgtype | tgenabled | relname \n--------+--------+-----------+---------\n\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n-- TEST2 ---\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_2_2_chunk\nSHOW enable_partitionwise_aggregate;\n enable_partitionwise_aggregate \n--------------------------------\n off\n\nSET enable_partitionwise_aggregate = on;\nSELECT * FROM _timescaledb_catalog.bgw_job;\n id | application_name | schedule_interval | max_runtime | max_retries | retry_period | proc_schema | proc_name | owner | scheduled | fixed_schedule | initial_start | hypertable_id | config | check_schema | check_name | timezone \n----+------------------+-------------------+-------------+-------------+--------------+-------------+-----------+-------+-----------+----------------+---------------+---------------+--------+--------------+------------+----------\n\nCREATE TABLE conditions (\n      timec        TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\ninsert into conditions values ( '2010-01-01 09:00:00-08', 'SFO', 55, 45);\ninsert into conditions values ( '2010-01-02 09:00:00-08', 'por', 100, 100);\ninsert into conditions values ( '2010-01-02 09:00:00-08', 'SFO', 65, 45);\ninsert into conditions values ( '2010-01-02 09:00:00-08', 'NYC', 65, 45);\ninsert into conditions values ( '2018-11-01 09:00:00-08', 'NYC', 45, 35);\ninsert into conditions values ( '2018-11-02 09:00:00-08', 'NYC', 35, 15);\nCREATE MATERIALIZED VIEW mat_m1( timec, minl, sumt , sumh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1day', timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by time_bucket('1day', timec) WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\n-- Materialized hypertable for mat_m1 should not be visible in the\n-- hypertables view:\nSELECT hypertable_schema, hypertable_name\nFROM timescaledb_information.hypertables ORDER BY 1,2;\n hypertable_schema | hypertable_name \n-------------------+-----------------\n public            | conditions\n public            | foo\n\nSET ROLE :ROLE_SUPERUSER;\ninsert into  :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect\n time_bucket('1day', timec), min(location), sum(temperature), sum(humidity)\nfrom conditions\ngroup by time_bucket('1day', timec) ;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--should have same results --\nselect timec, minl, sumt, sumh\nfrom mat_m1\norder by timec;\n            timec             | minl | sumt | sumh \n------------------------------+------+------+------\n Thu Dec 31 16:00:00 2009 PST | SFO  |   55 |   45\n Fri Jan 01 16:00:00 2010 PST | NYC  |  230 |  190\n Wed Oct 31 17:00:00 2018 PDT | NYC  |   45 |   35\n Thu Nov 01 17:00:00 2018 PDT | NYC  |   35 |   15\n\nselect time_bucket('1day', timec), min(location), sum(temperature), sum(humidity)\nfrom conditions\ngroup by time_bucket('1day', timec)\norder by 1;\n         time_bucket          | min | sum | sum \n------------------------------+-----+-----+-----\n Thu Dec 31 16:00:00 2009 PST | SFO |  55 |  45\n Fri Jan 01 16:00:00 2010 PST | NYC | 230 | 190\n Wed Oct 31 17:00:00 2018 PDT | NYC |  45 |  35\n Thu Nov 01 17:00:00 2018 PDT | NYC |  35 |  15\n\nSET enable_partitionwise_aggregate = off;\n-- TEST3 --\n-- drop on table conditions should cascade to materialized mat_v1\ndrop table conditions cascade;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to 2 other objects\nCREATE TABLE conditions (\n      timec        TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\ninsert into conditions values ( '2010-01-01 09:00:00-08', 'SFO', 55, 45);\ninsert into conditions values ( '2010-01-02 09:00:00-08', 'por', 100, 100);\ninsert into conditions values ( '2010-01-02 09:00:00-08', 'NYC', 65, 45);\ninsert into conditions values ( '2010-01-02 09:00:00-08', 'SFO', 65, 45);\ninsert into conditions values ( '2010-01-03 09:00:00-08', 'NYC', 45, 55);\ninsert into conditions values ( '2010-01-05 09:00:00-08', 'SFO', 75, 100);\ninsert into conditions values ( '2018-11-01 09:00:00-08', 'NYC', 45, 35);\ninsert into conditions values ( '2018-11-02 09:00:00-08', 'NYC', 35, 15);\ninsert into conditions values ( '2018-11-03 09:00:00-08', 'NYC', 35, 25);\nCREATE MATERIALIZED VIEW mat_m1( timec, minl, sumth, stddevh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec)  WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\nSET ROLE :ROLE_SUPERUSER;\ninsert into  :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect\n time_bucket('1week', timec), min(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec) ;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--should have same results --\nselect timec, minl, sumth, stddevh\nfrom mat_m1\norder by timec;\n            timec             | minl | sumth |     stddevh      \n------------------------------+------+-------+------------------\n Sun Dec 27 16:00:00 2009 PST | NYC  |   620 | 23.8746727726266\n Sun Jan 03 16:00:00 2010 PST | SFO  |   175 |                 \n Sun Oct 28 17:00:00 2018 PDT | NYC  |   190 |               10\n\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+ sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec)\norder by time_bucket('1week', timec);\n         time_bucket          | min | ?column? |      stddev      \n------------------------------+-----+----------+------------------\n Sun Dec 27 16:00:00 2009 PST | NYC |      620 | 23.8746727726266\n Sun Jan 03 16:00:00 2010 PST | SFO |      175 |                 \n Sun Oct 28 17:00:00 2018 PDT | NYC |      190 |               10\n\n-- TEST4 --\n--materialized view with group by clause + expression in SELECT\n-- use previous data from conditions\n--drop only the view.\n-- apply where clause on result of mat_m1 --\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\nCREATE MATERIALIZED VIEW mat_m1( timec, minl, sumth, stddevh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\nwhere location = 'NYC'\ngroup by time_bucket('1week', timec)\n WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\nSET ROLE :ROLE_SUPERUSER;\ninsert into  :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect\n time_bucket('1week', timec), min(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\nwhere location = 'NYC'\ngroup by time_bucket('1week', timec) ;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--should have same results --\nselect timec, minl, sumth, stddevh\nfrom mat_m1\nwhere stddevh is not null\norder by timec;\n            timec             | minl | sumth |     stddevh      \n------------------------------+------+-------+------------------\n Sun Dec 27 16:00:00 2009 PST | NYC  |   210 | 7.07106781186548\n Sun Oct 28 17:00:00 2018 PDT | NYC  |   190 |               10\n\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+ sum(humidity), stddev(humidity)\nfrom conditions\nwhere location = 'NYC'\ngroup by time_bucket('1week', timec)\norder by time_bucket('1week', timec);\n         time_bucket          | min | ?column? |      stddev      \n------------------------------+-----+----------+------------------\n Sun Dec 27 16:00:00 2009 PST | NYC |      210 | 7.07106781186548\n Sun Oct 28 17:00:00 2018 PDT | NYC |      190 |               10\n\n-- TEST5 --\n---------test with having clause ----------------------\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\ncreate materialized view mat_m1( timec, minl, sumth, stddevh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec)\nhaving stddev(humidity) is not null WITH NO DATA;\n;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\nSET ROLE :ROLE_SUPERUSER;\ninsert into  :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect\n time_bucket('1week', timec), min(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec)\nhaving stddev(humidity) is not null;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n-- should have same results --\nselect * from mat_m1\norder by sumth;\n            timec             | minl | sumth |     stddevh      \n------------------------------+------+-------+------------------\n Sun Oct 28 17:00:00 2018 PDT | NYC  |   190 |               10\n Sun Dec 27 16:00:00 2009 PST | NYC  |   620 | 23.8746727726266\n\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec)\nhaving stddev(humidity) is not null\norder by sum(temperature)+sum(humidity);\n         time_bucket          | min | ?column? |      stddev      \n------------------------------+-----+----------+------------------\n Sun Oct 28 17:00:00 2018 PDT | NYC |      190 |               10\n Sun Dec 27 16:00:00 2009 PST | NYC |      620 | 23.8746727726266\n\n-- TEST6 --\n--group by with more than 1 group column\n-- having clause with a mix of columns from select list + others\ndrop table conditions cascade;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to 2 other objects\nCREATE TABLE conditions (\n      timec       TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        numeric NULL,\n      highp       numeric null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\ninsert into conditions\nselect generate_series('2018-12-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'POR', 55, 75, 40, 70;\ninsert into conditions\nselect generate_series('2018-11-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'NYC', 35, 45, 50, 40;\ninsert into conditions\nselect generate_series('2018-11-01 00:00'::timestamp, '2018-12-15 00:00'::timestamp, '1 day'), 'LA', 73, 55, 71, 28;\n--naming with AS clauses\nCREATE MATERIALIZED VIEW mat_naming\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec) as bucket, location as loc, sum(temperature)+sum(humidity) as sumth, stddev(humidity)\nfrom conditions\ngroup by bucket, loc\nhaving min(location) >= 'NYC' and avg(temperature) > 20 WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_naming'\n\\gset\nselect attnum , attname from pg_attribute\nwhere attnum > 0 and attrelid =\n(Select oid from pg_class where relname like :'MAT_TABLE_NAME')\norder by attnum, attname;\n attnum | attname \n--------+---------\n      1 | bucket\n      2 | loc\n      3 | sumth\n      4 | stddev\n\nDROP MATERIALIZED VIEW mat_naming;\n--naming with default names\nCREATE MATERIALIZED VIEW mat_naming\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec), location, sum(temperature)+sum(humidity) as sumth, stddev(humidity)\nfrom conditions\ngroup by 1,2\nhaving min(location) >= 'NYC' and avg(temperature) > 20 WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_naming'\n\\gset\nselect attnum , attname from pg_attribute\nwhere attnum > 0 and attrelid =\n(Select oid from pg_class where relname like :'MAT_TABLE_NAME')\norder by attnum, attname;\n attnum |   attname   \n--------+-------------\n      1 | time_bucket\n      2 | location\n      3 | sumth\n      4 | stddev\n\nDROP MATERIALIZED VIEW mat_naming;\n--naming with view col names\nCREATE MATERIALIZED VIEW mat_naming(bucket, loc, sum_t_h, stdd)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec), location, sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by 1,2\nhaving min(location) >= 'NYC' and avg(temperature) > 20 WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_naming'\n\\gset\nselect attnum , attname from pg_attribute\nwhere attnum > 0 and attrelid =\n(Select oid from pg_class where relname like :'MAT_TABLE_NAME')\norder by attnum, attname;\n attnum | attname \n--------+---------\n      1 | bucket\n      2 | loc\n      3 | sum_t_h\n      4 | stdd\n\nDROP MATERIALIZED VIEW mat_naming;\nCREATE MATERIALIZED VIEW mat_m1(timec, minl, sumth, stddevh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by  time_bucket('1week', timec)\nhaving min(location) >= 'NYC' and avg(temperature) > 20 WITH NO DATA;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\nselect attnum , attname from pg_attribute\nwhere attnum > 0 and attrelid =\n(Select oid from pg_class where relname like :'MAT_TABLE_NAME')\norder by attnum, attname;\n attnum | attname \n--------+---------\n      1 | timec\n      2 | minl\n      3 | sumth\n      4 | stddevh\n\nSET ROLE :ROLE_SUPERUSER;\ninsert into  :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect\n time_bucket('1week', timec), min(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by time_bucket('1week', timec)\nhaving min(location) >= 'NYC' and avg(temperature) > 20;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--should have same results --\nselect timec, minl, sumth, stddevh\nfrom mat_m1\norder by timec, minl;\n            timec             | minl | sumth |     stddevh      \n------------------------------+------+-------+------------------\n Sun Dec 16 16:00:00 2018 PST | NYC  |  1470 | 15.5662356498831\n Sun Dec 23 16:00:00 2018 PST | NYC  |  1470 | 15.5662356498831\n Sun Dec 30 16:00:00 2018 PST | NYC  |   210 | 21.2132034355964\n\nselect time_bucket('1week', timec) ,\nmin(location), sum(temperature)+sum(humidity), stddev(humidity)\nfrom conditions\ngroup by  time_bucket('1week', timec)\nhaving min(location) >= 'NYC' and avg(temperature) > 20 and avg(lowp) > 10\norder by time_bucket('1week', timec), min(location);\n         time_bucket          | min | ?column? |      stddev      \n------------------------------+-----+----------+------------------\n Sun Dec 16 16:00:00 2018 PST | NYC |     1470 | 15.5662356498831\n Sun Dec 23 16:00:00 2018 PST | NYC |     1470 | 15.5662356498831\n Sun Dec 30 16:00:00 2018 PST | NYC |      210 | 21.2132034355964\n\n--check view defintion in information views\nselect view_name, view_definition from timescaledb_information.continuous_aggregates\nwhere view_name::text like 'mat_m1';\n view_name |                                      view_definition                                      \n-----------+-------------------------------------------------------------------------------------------\n mat_m1    |  SELECT time_bucket('@ 7 days'::interval, timec) AS timec,                               +\n           |     min(location) AS minl,                                                               +\n           |     (sum(temperature) + sum(humidity)) AS sumth,                                         +\n           |     stddev(humidity) AS stddevh                                                          +\n           |    FROM conditions                                                                       +\n           |   GROUP BY (time_bucket('@ 7 days'::interval, timec))                                    +\n           |  HAVING ((min(location) >= 'NYC'::text) AND (avg(temperature) > (20)::double precision));\n\n--TEST6 -- select from internal view\nSET ROLE :ROLE_SUPERUSER;\ninsert into :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\nselect * from :\"PART_VIEW_SCHEMA\".:\"PART_VIEW_NAME\";\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--lets drop the view and check\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_13_24_chunk\ndrop table conditions;\nCREATE TABLE conditions (\n      timec       TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\ninsert into conditions\nselect generate_series('2018-12-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'POR', 55, 75, 40, 70, NULL;\ninsert into conditions\nselect generate_series('2018-11-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'NYC', 35, 45, 50, 40, NULL;\ninsert into conditions\nselect generate_series('2018-11-01 00:00'::timestamp, '2018-12-15 00:00'::timestamp, '1 day'), 'LA', 73, 55, NULL, 28, NULL;\nSELECT\n  $$\n  select time_bucket('1week', timec) ,\n  min(location) as col1, sum(temperature)+sum(humidity) as col2, stddev(humidity) as col3, min(allnull) as col4\n  from conditions\n  group by  time_bucket('1week', timec)\n  having min(location) >= 'NYC' and avg(temperature) > 20\n  $$ AS \"QUERY\"\n\\gset\n\\set ECHO errors\npsql:include/cont_agg_equal.sql:8: NOTICE:  materialized view \"mat_test\" does not exist, skipping\n                           ?column?                            | count \n---------------------------------------------------------------+-------\n Number of rows different between view and original (expect 0) |     0\n\nSELECT\n  $$\n  select time_bucket('1week', timec), location,\n  sum(temperature)+sum(humidity) as col2, stddev(humidity) as col3, min(allnull) as col4\n  from conditions\n  group by location, time_bucket('1week', timec)\n  $$ AS \"QUERY\"\n\\gset\n\\set ECHO errors\npsql:include/cont_agg_equal.sql:8: NOTICE:  drop cascades to table _timescaledb_internal._hyper_15_34_chunk\n                           ?column?                            | count \n---------------------------------------------------------------+-------\n Number of rows different between view and original (expect 0) |     0\n\n--TEST7 -- drop tests for view and hypertable\n--DROP tests\n\\set ON_ERROR_STOP 0\nSELECT  h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\",\n       direct_view_name as \"DIR_VIEW_NAME\",\n       direct_view_schema as \"DIR_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_test'\n\\gset\nDROP TABLE :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\";\nERROR:  cannot drop table _timescaledb_internal._materialized_hypertable_16 because other objects depend on it\nDROP VIEW :\"PART_VIEW_SCHEMA\".:\"PART_VIEW_NAME\";\nERROR:  cannot drop the partial/direct view because it is required by a continuous aggregate\nDROP VIEW :\"DIR_VIEW_SCHEMA\".:\"DIR_VIEW_NAME\";\nERROR:  cannot drop the partial/direct view because it is required by a continuous aggregate\n\\set ON_ERROR_STOP 1\n--catalog entry still there;\nSELECT count(*)\nFROM _timescaledb_catalog.continuous_agg ca\nWHERE user_view_name = 'mat_test';\n count \n-------\n     1\n\n--mat table, user_view, direct view and partial view all there\nselect count(*) from pg_class where relname = :'PART_VIEW_NAME';\n count \n-------\n     1\n\nselect count(*) from pg_class where relname = :'MAT_TABLE_NAME';\n count \n-------\n     1\n\nselect count(*) from pg_class where relname = :'DIR_VIEW_NAME';\n count \n-------\n     1\n\nselect count(*) from pg_class where relname = 'mat_test';\n count \n-------\n     1\n\nDROP MATERIALIZED VIEW mat_test;\nNOTICE:  drop cascades to 2 other objects\n--catalog entry should be gone\nSELECT count(*)\nFROM _timescaledb_catalog.continuous_agg ca\nWHERE user_view_name = 'mat_test';\n count \n-------\n     0\n\n--mat table, user_view, direct view and partial view all gone\nselect count(*) from pg_class where relname = :'PART_VIEW_NAME';\n count \n-------\n     0\n\nselect count(*) from pg_class where relname = :'MAT_TABLE_NAME';\n count \n-------\n     0\n\nselect count(*) from pg_class where relname = :'DIR_VIEW_NAME';\n count \n-------\n     0\n\nselect count(*) from pg_class where relname = 'mat_test';\n count \n-------\n     0\n\n--test dropping raw table\nDROP TABLE conditions;\nCREATE TABLE conditions (\n      timec       TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\n--no data in hyper table on purpose so that CASCADE is not required because of chunks\nCREATE MATERIALIZED VIEW mat_drop_test(timec, minl, sumt , sumh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket('1day', timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by time_bucket('1day', timec) WITH NO DATA;\n\\set ON_ERROR_STOP 0\nDROP TABLE conditions;\nERROR:  cannot drop table conditions because other objects depend on it\n\\set ON_ERROR_STOP 1\n--insert data now\ninsert into conditions\nselect generate_series('2018-12-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'POR', 55, 75, 40, 70, NULL;\ninsert into conditions\nselect generate_series('2018-11-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'NYC', 35, 45, 50, 40, NULL;\ninsert into conditions\nselect generate_series('2018-11-01 00:00'::timestamp, '2018-12-15 00:00'::timestamp, '1 day'), 'LA', 73, 55, NULL, 28, NULL;\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_drop_test'\n\\gset\nSET client_min_messages TO NOTICE;\nCALL refresh_continuous_aggregate('mat_drop_test', NULL, NULL);\n--force invalidation\ninsert into conditions\nselect generate_series('2017-11-01 00:00'::timestamp, '2017-12-15 00:00'::timestamp, '1 day'), 'LA', 73, 55, NULL, 28, NULL;\nselect count(*) from _timescaledb_catalog.continuous_aggs_invalidation_threshold;\n count \n-------\n     1\n\nselect count(*) from _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log;\n count \n-------\n     8\n\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to 2 other objects\n--catalog entry should be gone\nSELECT count(*)\nFROM _timescaledb_catalog.continuous_agg ca\nWHERE user_view_name = 'mat_drop_test';\n count \n-------\n     0\n\nselect count(*) from _timescaledb_catalog.continuous_aggs_invalidation_threshold;\n count \n-------\n     0\n\nselect count(*) from _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log;\n count \n-------\n     0\n\nselect count(*) from _timescaledb_catalog.continuous_aggs_materialization_invalidation_log;\n count \n-------\n     0\n\nSELECT * FROM _timescaledb_catalog.bgw_job;\n id | application_name | schedule_interval | max_runtime | max_retries | retry_period | proc_schema | proc_name | owner | scheduled | fixed_schedule | initial_start | hypertable_id | config | check_schema | check_name | timezone \n----+------------------+-------------------+-------------+-------------+--------------+-------------+-----------+-------+-----------+----------------+---------------+---------------+--------+--------------+------------+----------\n\n--mat table, user_view, and partial view all gone\nselect count(*) from pg_class where relname = :'PART_VIEW_NAME';\n count \n-------\n     0\n\nselect count(*) from pg_class where relname = :'MAT_TABLE_NAME';\n count \n-------\n     0\n\nselect count(*) from pg_class where relname = 'mat_drop_test';\n count \n-------\n     0\n\n--TEST With options\nCREATE TABLE conditions (\n      timec       TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\nCREATE MATERIALIZED VIEW mat_with_test(timec, minl, sumt , sumh)\nWITH (timescaledb.continuous,\n      timescaledb.materialized_only=true)\nas\nselect time_bucket('1day', timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by time_bucket('1day', timec), location, humidity, temperature WITH NO DATA;\nSELECT add_continuous_aggregate_policy('mat_with_test', NULL, '5 h'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1001\n\nSELECT alter_job(id, schedule_interval => '1h') FROM _timescaledb_catalog.bgw_job;\n                                                                                                                         alter_job                                                                                                                          \n------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1001,\"@ 1 hour\",\"@ 0\",-1,\"@ 12 hours\",t,\"{\"\"end_offset\"\": \"\"@ 5 hours\"\", \"\"start_offset\"\": null, \"\"mat_hypertable_id\"\": 20}\",-infinity,_timescaledb_functions.policy_refresh_continuous_aggregate_check,f,,,\"Refresh Continuous Aggregate Policy [1001]\")\n\nSELECT schedule_interval FROM _timescaledb_catalog.bgw_job;\n schedule_interval \n-------------------\n @ 1 hour\n\nSELECT alter_job(id, schedule_interval => '2h') FROM _timescaledb_catalog.bgw_job;\n                                                                                                                          alter_job                                                                                                                          \n-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1001,\"@ 2 hours\",\"@ 0\",-1,\"@ 12 hours\",t,\"{\"\"end_offset\"\": \"\"@ 5 hours\"\", \"\"start_offset\"\": null, \"\"mat_hypertable_id\"\": 20}\",-infinity,_timescaledb_functions.policy_refresh_continuous_aggregate_check,f,,,\"Refresh Continuous Aggregate Policy [1001]\")\n\nSELECT schedule_interval FROM _timescaledb_catalog.bgw_job;\n schedule_interval \n-------------------\n @ 2 hours\n\nselect indexname, indexdef from pg_indexes where tablename =\n(SELECT h.table_name\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_with_test')\norder by indexname;\n               indexname               |                                                             indexdef                                                             \n---------------------------------------+----------------------------------------------------------------------------------------------------------------------------------\n _materialized_hypertable_20_timec_idx | CREATE INDEX _materialized_hypertable_20_timec_idx ON _timescaledb_internal._materialized_hypertable_20 USING btree (timec DESC)\n\nDROP MATERIALIZED VIEW mat_with_test;\n--no additional indexes\nCREATE MATERIALIZED VIEW mat_with_test(timec, minl, sumt , sumh)\nWITH (timescaledb.continuous,\n      timescaledb.materialized_only=true,\n      timescaledb.create_group_indexes=false)\nas\nselect time_bucket('1day', timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by time_bucket('1day', timec), location, humidity, temperature WITH NO DATA;\nselect indexname, indexdef from pg_indexes where tablename =\n(SELECT h.table_name\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_with_test');\n               indexname               |                                                             indexdef                                                             \n---------------------------------------+----------------------------------------------------------------------------------------------------------------------------------\n _materialized_hypertable_21_timec_idx | CREATE INDEX _materialized_hypertable_21_timec_idx ON _timescaledb_internal._materialized_hypertable_21 USING btree (timec DESC)\n\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 2 other objects\n--test WITH using a hypertable with an integer time dimension\nCREATE TABLE conditions (\n      timec       INT       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec', chunk_time_interval=> 100);\n table_name \n------------\n conditions\n\nCREATE OR REPLACE FUNCTION integer_now_conditions() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(timec), 0) FROM conditions $$;\nSELECT set_integer_now_func('conditions', 'integer_now_conditions');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW mat_with_test(timec, minl, sumt , sumh)\nWITH (timescaledb.continuous,\n      timescaledb.materialized_only=true)\nas\nselect time_bucket(100, timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by time_bucket(100, timec) WITH NO DATA;\nSELECT add_continuous_aggregate_policy('mat_with_test', NULL, 500::integer, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1002\n\nSELECT alter_job(id, schedule_interval => '2h') FROM _timescaledb_catalog.bgw_job;\n                                                                                                                     alter_job                                                                                                                     \n---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1002,\"@ 2 hours\",\"@ 0\",-1,\"@ 12 hours\",t,\"{\"\"end_offset\"\": 500, \"\"start_offset\"\": null, \"\"mat_hypertable_id\"\": 23}\",-infinity,_timescaledb_functions.policy_refresh_continuous_aggregate_check,f,,,\"Refresh Continuous Aggregate Policy [1002]\")\n\nSELECT schedule_interval FROM _timescaledb_catalog.bgw_job;\n schedule_interval \n-------------------\n @ 2 hours\n\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 2 other objects\n--test space partitions\nCREATE TABLE space_table (\n    time BIGINT,\n    dev  BIGINT,\n    data BIGINT\n);\nSELECT create_hypertable(\n    'space_table',\n    'time',\n    chunk_time_interval => 10,\n    partitioning_column => 'dev',\n    number_partitions => 3);\n     create_hypertable     \n---------------------------\n (24,public,space_table,t)\n\nCREATE OR REPLACE FUNCTION integer_now_space_table() returns BIGINT LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), BIGINT '0') FROM space_table $$;\nSELECT set_integer_now_func('space_table', 'integer_now_space_table');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW space_view\nWITH (timescaledb.continuous,\n      timescaledb.materialized_only=true)\nAS SELECT time_bucket('4', time), COUNT(data)\n   FROM space_table\n   GROUP BY 1 WITH NO DATA;\nINSERT INTO space_table VALUES\n  (0, 1, 1), (0, 2, 1), (1, 1, 1), (1, 2, 1),\n  (10, 1, 1), (10, 2, 1), (11, 1, 1), (11, 2, 1);\nSELECT  h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\",\n       direct_view_name as \"DIR_VIEW_NAME\",\n       direct_view_schema as \"DIR_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'space_view'\n\\gset\nSELECT * FROM :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\n  ORDER BY time_bucket;\n time_bucket | count \n-------------+-------\n\nCALL refresh_continuous_aggregate('space_view', NULL, NULL);\nSELECT * FROM space_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     4\n           8 |     4\n\nSELECT * FROM :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\n  ORDER BY time_bucket;\n time_bucket | count \n-------------+-------\n           0 |     4\n           8 |     4\n\nINSERT INTO space_table VALUES (3, 2, 1);\nCALL refresh_continuous_aggregate('space_view', NULL, NULL);\nSELECT * FROM space_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     5\n           8 |     4\n\nSELECT * FROM :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\n  ORDER BY time_bucket;\n time_bucket | count \n-------------+-------\n           0 |     5\n           8 |     4\n\nINSERT INTO space_table VALUES (2, 3, 1);\nCALL refresh_continuous_aggregate('space_view', NULL, NULL);\nSELECT * FROM space_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     6\n           8 |     4\n\nSELECT * FROM :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\"\n  ORDER BY time_bucket;\n time_bucket | count \n-------------+-------\n           0 |     6\n           8 |     4\n\nDROP TABLE space_table CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_25_60_chunk\n--\n-- TEST FINALIZEFUNC_EXTRA\n--\n-- create special aggregate to test ffunc_extra\n-- Raise warning with the actual type being passed in\nCREATE OR REPLACE FUNCTION fake_ffunc(a int8, b int, c int, d int, x anyelement)\nRETURNS anyelement AS $$\nBEGIN\n RAISE WARNING 'type % %', pg_typeof(d), pg_typeof(x);\n RETURN x;\nEND;\n$$\nLANGUAGE plpgsql;\nCREATE OR REPLACE FUNCTION fake_sfunc(a int8, b int, c int, d int, x anyelement)\nRETURNS int8 AS $$\nBEGIN\n RETURN b;\nEND; $$\nLANGUAGE plpgsql;\nCREATE AGGREGATE aggregate_to_test_ffunc_extra(int, int, int, anyelement) (\n    SFUNC = fake_sfunc,\n    STYPE = int8,\n    COMBINEFUNC = int8pl,\n    FINALFUNC = fake_ffunc,\n    PARALLEL = SAFE,\n    FINALFUNC_EXTRA\n);\nCREATE TABLE conditions (\n      timec       INT       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec', chunk_time_interval=> 100);\n table_name \n------------\n conditions\n\nCREATE OR REPLACE FUNCTION integer_now_conditions() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(timec), 0) FROM conditions $$;\nSELECT set_integer_now_func('conditions', 'integer_now_conditions');\n set_integer_now_func \n----------------------\n \n\ninsert into conditions\nselect generate_series(0, 200, 10), 'POR', 55, 75, 40, 70, NULL;\nCREATE MATERIALIZED VIEW mat_ffunc_test\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket(100, timec), aggregate_to_test_ffunc_extra(timec, 1, 3, 'test'::text)\nfrom conditions\ngroup by time_bucket(100, timec);\nNOTICE:  refreshing continuous aggregate \"mat_ffunc_test\"\nWARNING:  type integer text\nWARNING:  type integer text\nWARNING:  type integer text\nSELECT * FROM mat_ffunc_test ORDER BY time_bucket;\n time_bucket | aggregate_to_test_ffunc_extra \n-------------+-------------------------------\n           0 | \n         100 | \n         200 | \n\nDROP MATERIALIZED view mat_ffunc_test;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_27_65_chunk\nCREATE MATERIALIZED VIEW mat_ffunc_test\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect time_bucket(100, timec), aggregate_to_test_ffunc_extra(timec, 4, 5, bigint '123')\nfrom conditions\ngroup by time_bucket(100, timec);\nNOTICE:  refreshing continuous aggregate \"mat_ffunc_test\"\nWARNING:  type integer bigint\nWARNING:  type integer bigint\nWARNING:  type integer bigint\nSELECT * FROM mat_ffunc_test ORDER BY time_bucket;\n time_bucket | aggregate_to_test_ffunc_extra \n-------------+-------------------------------\n           0 |                              \n         100 |                              \n         200 |                              \n\n--refresh mat view test when time_bucket is not projected --\nDROP MATERIALIZED VIEW mat_ffunc_test;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_28_66_chunk\nCREATE MATERIALIZED VIEW mat_refresh_test\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect location, max(humidity)\nfrom conditions\ngroup by time_bucket(100, timec), location WITH NO DATA;\ninsert into conditions\nselect generate_series(0, 50, 10), 'NYC', 55, 75, 40, 70, NULL;\nCALL refresh_continuous_aggregate('mat_refresh_test', NULL, NULL);\nSELECT * FROM mat_refresh_test order by 1,2 ;\n location | max \n----------+-----\n NYC      |  75\n POR      |  75\n POR      |  75\n POR      |  75\n\n-- test for bug when group by is not in project list\nCREATE MATERIALIZED VIEW conditions_grpby_view with (timescaledb.continuous, timescaledb.materialized_only=false) as\nselect time_bucket(100, timec),  sum(humidity)\nfrom conditions\ngroup by time_bucket(100, timec), location;\nNOTICE:  refreshing continuous aggregate \"conditions_grpby_view\"\nselect * from conditions_grpby_view order by 1, 2;\n time_bucket | sum \n-------------+-----\n           0 | 450\n           0 | 750\n         100 | 750\n         200 |  75\n\nCREATE MATERIALIZED VIEW conditions_grpby_view2 with (timescaledb.continuous, timescaledb.materialized_only=false) as\nselect time_bucket(100, timec), sum(humidity)\nfrom conditions\ngroup by time_bucket(100, timec), location\nhaving avg(temperature) > 0;\nNOTICE:  refreshing continuous aggregate \"conditions_grpby_view2\"\nselect * from conditions_grpby_view2 order by 1, 2;\n time_bucket | sum \n-------------+-----\n           0 | 450\n           0 | 750\n         100 | 750\n         200 |  75\n\n-- Test internal functions for continuous aggregates\nSELECT test.continuous_aggs_find_view('mat_refresh_test');\n continuous_aggs_find_view \n---------------------------\n \n\n-- Test pseudotype/enum handling\nCREATE TYPE status_enum AS ENUM (\n  'red',\n  'yellow',\n  'green'\n);\nCREATE TABLE cagg_types (\n  time TIMESTAMPTZ NOT NULL,\n  status status_enum,\n  names NAME[],\n  floats FLOAT[]\n);\nSELECT\n  table_name\nFROM\n  create_hypertable('cagg_types', 'time');\n table_name \n------------\n cagg_types\n\nINSERT INTO cagg_types\nSELECT\n  '2000-01-01',\n  'yellow',\n  '{foo,bar,baz}',\n  '{1,2.5,3}';\nCREATE MATERIALIZED VIEW mat_types WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  time_bucket('1d', time),\n  min(status) AS status,\n  max(names) AS names,\n  min(floats) AS floats\nFROM\n  cagg_types\nGROUP BY\n  1;\nNOTICE:  refreshing continuous aggregate \"mat_types\"\nCALL refresh_continuous_aggregate('mat_types',NULL,NULL);\nNOTICE:  continuous aggregate \"mat_types\" is already up-to-date\nSELECT * FROM mat_types;\n         time_bucket          | status |     names     |  floats   \n------------------------------+--------+---------------+-----------\n Fri Dec 31 16:00:00 1999 PST | yellow | {foo,bar,baz} | {1,2.5,3}\n\n-------------------------------------------------------------------------------------\n-- Test issue #2616 where cagg view contains an experssion with several aggregates in\nCREATE TABLE water_consumption\n(\n    sensor_id   integer      NOT NULL,\n    timestamp   timestamp(0) NOT NULL,\n    water_index integer\n);\nSELECT create_hypertable('water_consumption', 'timestamp', 'sensor_id', 2);\nWARNING:  column type \"timestamp without time zone\" used for \"timestamp\" does not follow best practices\n        create_hypertable        \n---------------------------------\n (34,public,water_consumption,t)\n\nINSERT INTO public.water_consumption (sensor_id, timestamp, water_index) VALUES\n  (1, '2010-11-03 09:42:30', 1030),\n  (1, '2010-11-03 09:42:40', 1032),\n  (1, '2010-11-03 09:42:50', 1035),\n  (1, '2010-11-03 09:43:30', 1040),\n  (1, '2010-11-03 09:43:40', 1045),\n  (1, '2010-11-03 09:43:50', 1050),\n  (1, '2010-11-03 09:44:30', 1052),\n  (1, '2010-11-03 09:44:40', 1057),\n  (1, '2010-11-03 09:44:50', 1060),\n  (1, '2010-11-03 09:45:30', 1063),\n  (1, '2010-11-03 09:45:40', 1067),\n  (1, '2010-11-03 09:45:50', 1070);\n-- The test with the view originally reported in the issue.\nCREATE MATERIALIZED VIEW water_consumption_aggregation_minute\n            WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE)\nAS\nSELECT sensor_id,\n       time_bucket(INTERVAL '1 minute', timestamp) + '1 minute' AS timestamp,\n       (max(water_index) - min(water_index))                    AS water_consumption\nFROM water_consumption\nGROUP BY sensor_id, time_bucket(INTERVAL '1 minute', timestamp)\nWITH NO DATA;\nCALL refresh_continuous_aggregate('water_consumption_aggregation_minute', NULL, NULL);\n-- The results of the view and the query over hypertable should be the same\nSELECT * FROM water_consumption_aggregation_minute ORDER BY water_consumption;\n sensor_id |        timestamp         | water_consumption \n-----------+--------------------------+-------------------\n         1 | Wed Nov 03 09:43:00 2010 |                 5\n         1 | Wed Nov 03 09:46:00 2010 |                 7\n         1 | Wed Nov 03 09:45:00 2010 |                 8\n         1 | Wed Nov 03 09:44:00 2010 |                10\n\nSELECT sensor_id,\n       time_bucket(INTERVAL '1 minute', timestamp) + '1 minute' AS timestamp,\n       (max(water_index) - min(water_index))                    AS water_consumption\nFROM water_consumption\nGROUP BY sensor_id, time_bucket(INTERVAL '1 minute', timestamp)\nORDER BY water_consumption;\n sensor_id |        timestamp         | water_consumption \n-----------+--------------------------+-------------------\n         1 | Wed Nov 03 09:43:00 2010 |                 5\n         1 | Wed Nov 03 09:46:00 2010 |                 7\n         1 | Wed Nov 03 09:45:00 2010 |                 8\n         1 | Wed Nov 03 09:44:00 2010 |                10\n\n-- Simplified test, where the view doesn't contain all group by clauses\nCREATE MATERIALIZED VIEW water_consumption_no_select_bucket\n            WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE)\nAS\nSELECT sensor_id,\n       (max(water_index) - min(water_index))                    AS water_consumption\nFROM water_consumption\nGROUP BY sensor_id, time_bucket(INTERVAL '1 minute', timestamp)\nWITH NO DATA;\nCALL refresh_continuous_aggregate('water_consumption_no_select_bucket', NULL, NULL);\n-- The results of the view and the query over hypertable should be the same\nSELECT * FROM water_consumption_no_select_bucket ORDER BY water_consumption;\n sensor_id | water_consumption \n-----------+-------------------\n         1 |                 5\n         1 |                 7\n         1 |                 8\n         1 |                10\n\nSELECT sensor_id,\n       (max(water_index) - min(water_index))                    AS water_consumption\nFROM water_consumption\nGROUP BY sensor_id, time_bucket(INTERVAL '1 minute', timestamp)\nORDER BY water_consumption;\n sensor_id | water_consumption \n-----------+-------------------\n         1 |                 5\n         1 |                 7\n         1 |                 8\n         1 |                10\n\n-- The test with SELECT matching GROUP BY and placing aggregate expression not the last\nCREATE MATERIALIZED VIEW water_consumption_aggregation_no_addition\n            WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE)\nAS\nSELECT sensor_id,\n       (max(water_index) - min(water_index))                    AS water_consumption,\n       time_bucket(INTERVAL '1 minute', timestamp) AS timestamp\nFROM water_consumption\nGROUP BY sensor_id, time_bucket(INTERVAL '1 minute', timestamp)\nWITH NO DATA;\nCALL refresh_continuous_aggregate('water_consumption_aggregation_no_addition', NULL, NULL);\n-- The results of the view and the query over hypertable should be the same\nSELECT * FROM water_consumption_aggregation_no_addition ORDER BY water_consumption;\n sensor_id | water_consumption |        timestamp         \n-----------+-------------------+--------------------------\n         1 |                 5 | Wed Nov 03 09:42:00 2010\n         1 |                 7 | Wed Nov 03 09:45:00 2010\n         1 |                 8 | Wed Nov 03 09:44:00 2010\n         1 |                10 | Wed Nov 03 09:43:00 2010\n\nSELECT sensor_id,\n       (max(water_index) - min(water_index))                    AS water_consumption,\n       time_bucket(INTERVAL '1 minute', timestamp) AS timestamp\nFROM water_consumption\nGROUP BY sensor_id, time_bucket(INTERVAL '1 minute', timestamp)\nORDER BY water_consumption;\n sensor_id | water_consumption |        timestamp         \n-----------+-------------------+--------------------------\n         1 |                 5 | Wed Nov 03 09:42:00 2010\n         1 |                 7 | Wed Nov 03 09:45:00 2010\n         1 |                 8 | Wed Nov 03 09:44:00 2010\n         1 |                10 | Wed Nov 03 09:43:00 2010\n\nDROP TABLE water_consumption CASCADE;\nNOTICE:  drop cascades to 6 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_35_73_chunk\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_36_74_chunk\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_37_75_chunk\n----\n--- github issue 2655 ---\ncreate table raw_data(time timestamptz, search_query text, cnt integer, cnt2 integer);\nselect create_hypertable('raw_data','time', chunk_time_interval=>'15 days'::interval);\n   create_hypertable    \n------------------------\n (38,public,raw_data,t)\n\ninsert into raw_data select '2000-01-01','Q1';\n--having has exprs that appear in select\nCREATE MATERIALIZED VIEW search_query_count_1m WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\n SELECT  search_query,count(search_query) as count,\n         time_bucket(INTERVAL '1 minute', time) AS bucket\n FROM raw_data\n WHERE search_query is not null AND LENGTH(TRIM(both from search_query))>0\n GROUP BY search_query, bucket HAVING count(search_query) > 3 OR sum(cnt) > 1;\nNOTICE:  refreshing continuous aggregate \"search_query_count_1m\"\n--having has aggregates + grp by columns that appear in select\nCREATE MATERIALIZED VIEW search_query_count_2 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\n SELECT  search_query,count(search_query) as count, sum(cnt),\n         time_bucket(INTERVAL '1 minute', time) AS bucket\n FROM raw_data\n WHERE search_query is not null AND LENGTH(TRIM(both from search_query))>0\n GROUP BY search_query, bucket\nHAVING count(search_query) > 3 OR sum(cnt) > 1 OR\n       ( sum(cnt) + count(cnt)) > 1\n       AND search_query = 'Q1';\nNOTICE:  refreshing continuous aggregate \"search_query_count_2\"\nCREATE MATERIALIZED VIEW search_query_count_3 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\n SELECT  search_query,count(search_query) as count, sum(cnt),\n         time_bucket(INTERVAL '1 minute', time) AS bucket\n FROM raw_data\n WHERE search_query is not null AND LENGTH(TRIM(both from search_query))>0\n GROUP BY cnt +cnt2 , bucket, search_query\n HAVING cnt + cnt2 + sum(cnt) > 2 or count(cnt2) > 10;\nNOTICE:  refreshing continuous aggregate \"search_query_count_3\"\ninsert into raw_data select '2000-01-01 00:00+0','Q1', 1, 100;\ninsert into raw_data select '2000-01-01 00:00+0','Q1', 2, 200;\ninsert into raw_data select '2000-01-01 00:00+0','Q1', 3, 300;\ninsert into raw_data select '2000-01-02 00:00+0','Q2', 10, 10;\ninsert into raw_data select '2000-01-02 00:00+0','Q2', 20, 20;\nCALL refresh_continuous_aggregate('search_query_count_1m', NULL, NULL);\nSELECT * FROM search_query_count_1m ORDER BY 1, 2;\n search_query | count |            bucket            \n--------------+-------+------------------------------\n Q1           |     3 | Fri Dec 31 16:00:00 1999 PST\n Q2           |     2 | Sat Jan 01 16:00:00 2000 PST\n\n--only 1 of these should appear in the result\ninsert into raw_data select '2000-01-02 00:00+0','Q3', 0, 0;\ninsert into raw_data select '2000-01-03 00:00+0','Q4', 20, 20;\nCALL refresh_continuous_aggregate('search_query_count_1m', NULL, NULL);\nSELECT * FROM search_query_count_1m ORDER BY 1, 2;\n search_query | count |            bucket            \n--------------+-------+------------------------------\n Q1           |     3 | Fri Dec 31 16:00:00 1999 PST\n Q2           |     2 | Sat Jan 01 16:00:00 2000 PST\n Q4           |     1 | Sun Jan 02 16:00:00 2000 PST\n\n--refresh search_query_count_2---\nCALL refresh_continuous_aggregate('search_query_count_2', NULL, NULL);\nSELECT * FROM search_query_count_2 ORDER BY 1, 2;\n search_query | count | sum |            bucket            \n--------------+-------+-----+------------------------------\n Q1           |     3 |   6 | Fri Dec 31 16:00:00 1999 PST\n Q2           |     2 |  30 | Sat Jan 01 16:00:00 2000 PST\n Q4           |     1 |  20 | Sun Jan 02 16:00:00 2000 PST\n\n--refresh search_query_count_3---\nCALL refresh_continuous_aggregate('search_query_count_3', NULL, NULL);\nSELECT * FROM search_query_count_3 ORDER BY 1, 2, 3;\n search_query | count | sum |            bucket            \n--------------+-------+-----+------------------------------\n Q1           |     1 |   1 | Fri Dec 31 16:00:00 1999 PST\n Q1           |     1 |   2 | Fri Dec 31 16:00:00 1999 PST\n Q1           |     1 |   3 | Fri Dec 31 16:00:00 1999 PST\n Q2           |     1 |  10 | Sat Jan 01 16:00:00 2000 PST\n Q2           |     1 |  20 | Sat Jan 01 16:00:00 2000 PST\n Q4           |     1 |  20 | Sun Jan 02 16:00:00 2000 PST\n\n--- TEST enable compression on continuous aggregates\nCREATE VIEW cagg_compression_status as\nSELECT ca.mat_hypertable_id AS mat_htid,\n       ca.user_view_name AS cagg_name ,\n       h.schema_name AS mat_schema_name,\n       h.table_name AS mat_table_name,\n       ca.materialized_only\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\n;\nSELECT mat_htid AS \"MAT_HTID\"\n     , mat_schema_name || '.' || mat_table_name AS \"MAT_HTNAME\"\n     , mat_table_name AS \"MAT_TABLE_NAME\"\nFROM cagg_compression_status\nWHERE cagg_name = 'search_query_count_3' \\gset\nALTER MATERIALIZED VIEW search_query_count_3 SET (timescaledb.compress = 'true');\nNOTICE:  defaulting compress_orderby to bucket,search_query\nSELECT cagg_name, mat_table_name\nFROM cagg_compression_status where cagg_name = 'search_query_count_3';\n      cagg_name       |       mat_table_name        \n----------------------+-----------------------------\n search_query_count_3 | _materialized_hypertable_41\n\n\\x\nSELECT * FROM timescaledb_information.compression_settings\nWHERE hypertable_name = :'MAT_TABLE_NAME';\n-[ RECORD 1 ]----------+----------------------------\nhypertable_schema      | _timescaledb_internal\nhypertable_name        | _materialized_hypertable_41\nattname                | bucket\nsegmentby_column_index | \norderby_column_index   | 1\norderby_asc            | t\norderby_nullsfirst     | f\n-[ RECORD 2 ]----------+----------------------------\nhypertable_schema      | _timescaledb_internal\nhypertable_name        | _materialized_hypertable_41\nattname                | search_query\nsegmentby_column_index | \norderby_column_index   | 2\norderby_asc            | t\norderby_nullsfirst     | f\n\n\\x\nSELECT compress_chunk(ch)\nFROM show_chunks('search_query_count_3') ch;\n              compress_chunk              \n------------------------------------------\n _timescaledb_internal._hyper_41_79_chunk\n\nSELECT * from search_query_count_3 ORDER BY 1, 2, 3;\n search_query | count | sum |            bucket            \n--------------+-------+-----+------------------------------\n Q1           |     1 |   1 | Fri Dec 31 16:00:00 1999 PST\n Q1           |     1 |   2 | Fri Dec 31 16:00:00 1999 PST\n Q1           |     1 |   3 | Fri Dec 31 16:00:00 1999 PST\n Q2           |     1 |  10 | Sat Jan 01 16:00:00 2000 PST\n Q2           |     1 |  20 | Sat Jan 01 16:00:00 2000 PST\n Q4           |     1 |  20 | Sun Jan 02 16:00:00 2000 PST\n\n-- insert into a new region of the hypertable and then refresh the cagg\n-- (note we still do not support refreshes into existing regions.\n-- cagg chunks do not map 1-1 to hypertabl regions. They encompass\n-- more data\n-- ).\ninsert into raw_data select '2000-05-01 00:00+0','Q3', 0, 0;\n-- On PG >= 14 the refresh test below will pass because we added support for UPDATE/DELETE on compressed chunks in PR #5339\n\\set ON_ERROR_STOP 0\nCALL refresh_continuous_aggregate('search_query_count_3', NULL, '2000-06-01 00:00+0'::timestamptz);\nCALL refresh_continuous_aggregate('search_query_count_3', '2000-05-01 00:00+0'::timestamptz, '2000-06-01 00:00+0'::timestamptz);\nNOTICE:  continuous aggregate \"search_query_count_3\" is already up-to-date\n\\set ON_ERROR_STOP 1\n--insert row\ninsert into raw_data select '2001-05-10 00:00+0','Q3', 100, 100;\n--this should succeed since it does not refresh any compressed regions in the cagg\nCALL refresh_continuous_aggregate('search_query_count_3', '2001-05-01 00:00+0'::timestamptz, '2001-06-01 00:00+0'::timestamptz);\n--verify watermark and check that chunks are compressed\nSELECT _timescaledb_functions.to_timestamp(w) FROM _timescaledb_functions.cagg_watermark(:'MAT_HTID') w;\n         to_timestamp         \n------------------------------\n Wed May 09 17:01:00 2001 PDT\n\nSELECT chunk_name, range_start, range_end, is_compressed\nFROM timescaledb_information.chunks\nWHERE hypertable_name = :'MAT_TABLE_NAME'\nORDER BY 1;\n     chunk_name     |         range_start          |          range_end           | is_compressed \n--------------------+------------------------------+------------------------------+---------------\n _hyper_41_79_chunk | Fri Dec 24 16:00:00 1999 PST | Mon May 22 17:00:00 2000 PDT | t\n _hyper_41_83_chunk | Sun Mar 18 16:00:00 2001 PST | Wed Aug 15 17:00:00 2001 PDT | f\n\nSELECT * FROM _timescaledb_catalog.continuous_aggs_materialization_invalidation_log\nWHERE materialization_id = :'MAT_HTID' ORDER BY 1, 2,3;\n materialization_id | lowest_modified_value | greatest_modified_value \n--------------------+-----------------------+-------------------------\n                 41 |  -9223372036854775808 |     -210866803200000001\n                 41 |       959817600000000 |         988675199999999\n                 41 |       991353600000000 |     9223372036854775807\n\nSELECT * from search_query_count_3\nWHERE bucket > '2001-01-01'\nORDER BY 1, 2, 3;\n search_query | count | sum |            bucket            \n--------------+-------+-----+------------------------------\n Q3           |     1 | 100 | Wed May 09 17:00:00 2001 PDT\n\n--now disable compression , will error out --\n\\set ON_ERROR_STOP 0\nALTER MATERIALIZED VIEW search_query_count_3 SET (timescaledb.compress = 'false');\nERROR:  cannot disable columnstore on hypertable with columnstore chunks\n\\set ON_ERROR_STOP 1\nSELECT decompress_chunk(format('%I.%I', schema_name, table_name))\nFROM _timescaledb_catalog.chunk\nWHERE hypertable_id = :'MAT_HTID' and status = 1;\n             decompress_chunk             \n------------------------------------------\n _timescaledb_internal._hyper_41_79_chunk\n\n--disable compression on cagg after decompressing all chunks--\nALTER MATERIALIZED VIEW search_query_count_3 SET (timescaledb.compress = 'false');\nSELECT cagg_name, mat_table_name\nFROM cagg_compression_status where cagg_name = 'search_query_count_3';\n      cagg_name       |       mat_table_name        \n----------------------+-----------------------------\n search_query_count_3 | _materialized_hypertable_41\n\nSELECT view_name, materialized_only, compression_enabled\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'search_query_count_3';\n      view_name       | materialized_only | compression_enabled \n----------------------+-------------------+---------------------\n search_query_count_3 | f                 | f\n\n-- TEST caggs on table with more columns than in the cagg view defn --\nCREATE TABLE test_morecols ( time TIMESTAMPTZ NOT NULL,\n                             val1 INTEGER, val2 INTEGER, val3 INTEGER, val4 INTEGER,\n                             val5 INTEGER,  val6 INTEGER, val7 INTEGER, val8 INTEGER);\nSELECT create_hypertable('test_morecols', 'time', chunk_time_interval=> '7 days'::interval);\n      create_hypertable      \n-----------------------------\n (43,public,test_morecols,t)\n\nINSERT INTO test_morecols\nSELECT generate_series('2018-12-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 55, 75, 40, 70, NULL, 100, 200, 200;\nCREATE MATERIALIZED VIEW test_morecols_cagg with (timescaledb.continuous, timescaledb.materialized_only=false)\nAS SELECT time_bucket('30 days',time), avg(val1),  count(val2)\n FROM test_morecols GROUP BY 1;\nNOTICE:  refreshing continuous aggregate \"test_morecols_cagg\"\nALTER MATERIALIZED VIEW test_morecols_cagg SET (timescaledb.compress='true');\nNOTICE:  defaulting compress_orderby to time_bucket\nSELECT compress_chunk(ch) FROM show_chunks('test_morecols_cagg') ch;\n              compress_chunk              \n------------------------------------------\n _timescaledb_internal._hyper_44_89_chunk\n\nSELECT * FROM test_morecols_cagg ORDER BY time_bucket;\n         time_bucket          |         avg         | count \n------------------------------+---------------------+-------\n Fri Nov 23 16:00:00 2018 PST | 55.0000000000000000 |    23\n Sun Dec 23 16:00:00 2018 PST | 55.0000000000000000 |     8\n\nSELECT view_name, materialized_only, compression_enabled\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_morecols_cagg';\n     view_name      | materialized_only | compression_enabled \n--------------------+-------------------+---------------------\n test_morecols_cagg | f                 | t\n\n--should keep compressed option, modify only materialized --\nALTER MATERIALIZED VIEW test_morecols_cagg SET (timescaledb.materialized_only='true');\nSELECT view_name, materialized_only, compression_enabled\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_morecols_cagg';\n     view_name      | materialized_only | compression_enabled \n--------------------+-------------------+---------------------\n test_morecols_cagg | t                 | t\n\nCREATE TABLE issue3248(filler_1 int, filler_2 int, filler_3 int, time timestamptz NOT NULL, device_id int, v0 int, v1 int, v2 float, v3 float);\nCREATE INDEX ON issue3248(time DESC);\nCREATE INDEX ON issue3248(device_id,time DESC);\nSELECT create_hypertable('issue3248','time',create_default_indexes:=false);\n    create_hypertable    \n-------------------------\n (46,public,issue3248,t)\n\nALTER TABLE issue3248 DROP COLUMN filler_1;\nINSERT INTO issue3248(time,device_id,v0,v1,v2,v3)\nSELECT time, device_id, device_id+1,  device_id + 2, device_id + 0.5, NULL\nFROM generate_series('2000-01-01 0:00:00+0'::timestamptz,'2000-01-05 23:55:00+0','8h') gtime(time),\n     generate_series(1,5,1) gdevice(device_id);\nALTER TABLE issue3248 DROP COLUMN filler_2;\nINSERT INTO issue3248(time,device_id,v0,v1,v2,v3)\nSELECT time, device_id, device_id-1, device_id + 2, device_id + 0.5, NULL\nFROM generate_series('2000-01-06 0:00:00+0'::timestamptz,'2000-01-12 23:55:00+0','8h') gtime(time),\n     generate_series(1,5,1) gdevice(device_id);\nALTER TABLE issue3248 DROP COLUMN filler_3;\nINSERT INTO issue3248(time,device_id,v0,v1,v2,v3)\nSELECT time, device_id, device_id, device_id + 2, device_id + 0.5, NULL\nFROM generate_series('2000-01-13 0:00:00+0'::timestamptz,'2000-01-19 23:55:00+0','8h') gtime(time),\n     generate_series(1,5,1) gdevice(device_id);\nANALYZE issue3248;\nCREATE materialized view issue3248_cagg WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS SELECT time_bucket('1h',time), device_id, min(v0), max(v1), avg(v2)\nFROM issue3248 GROUP BY 1,2;\nNOTICE:  refreshing continuous aggregate \"issue3248_cagg\"\nSELECT\n  FROM issue3248 AS m,\n       LATERAL(SELECT m FROM issue3248_cagg WHERE avg IS NULL LIMIT 1) AS lat;\n--\n\n-- test that option create_group_indexes is taken into account\nCREATE TABLE test_group_idx (\ntime timestamptz,\nsymbol int,\nvalue numeric\n);\nselect create_hypertable('test_group_idx', 'time');\n      create_hypertable       \n------------------------------\n (48,public,test_group_idx,t)\n\ninsert into test_group_idx\nselect t, round(random()*10), random()*5\nfrom generate_series('2020-01-01', '2020-02-25', INTERVAL '12 hours') t;\ncreate materialized view cagg_index_true\nwith (timescaledb.continuous, timescaledb.materialized_only=false, timescaledb.create_group_indexes=true) as\nselect\n\ttime_bucket('1 day', \"time\") as bucket,\n\tsum(value),\n\tsymbol\nfrom test_group_idx\ngroup by bucket, symbol;\nNOTICE:  refreshing continuous aggregate \"cagg_index_true\"\ncreate materialized view cagg_index_false\nwith (timescaledb.continuous, timescaledb.materialized_only=false, timescaledb.create_group_indexes=false) as\nselect\n\ttime_bucket('1 day', \"time\") as bucket,\n\tsum(value),\n\tsymbol\nfrom test_group_idx\ngroup by bucket, symbol;\nNOTICE:  refreshing continuous aggregate \"cagg_index_false\"\ncreate materialized view cagg_index_default\nwith (timescaledb.continuous, timescaledb.materialized_only=false) as\nselect\n\ttime_bucket('1 day', \"time\") as bucket,\n\tsum(value),\n\tsymbol\nfrom test_group_idx\ngroup by bucket, symbol;\nNOTICE:  refreshing continuous aggregate \"cagg_index_default\"\n-- see corresponding materialization_hypertables\nselect view_name, materialization_hypertable_name from timescaledb_information.continuous_aggregates ca\nwhere view_name like 'cagg_index_%' ORDER BY view_name;\n     view_name      | materialization_hypertable_name \n--------------------+---------------------------------\n cagg_index_default | _materialized_hypertable_51\n cagg_index_false   | _materialized_hypertable_50\n cagg_index_true    | _materialized_hypertable_49\n\n-- now make sure a group index has been created when explicitly asked for\n\\x on\nselect i.*\nfrom pg_indexes i\njoin pg_class c\n    on schemaname = relnamespace::regnamespace::text\n    and tablename = relname\nwhere tablename in (select materialization_hypertable_name from timescaledb_information.continuous_aggregates\nwhere view_name like 'cagg_index_%')\norder by tablename, indexname;\n-[ RECORD 1 ]-------------------------------------------------------------------------------------------------------------------------------------------------\nschemaname | _timescaledb_internal\ntablename  | _materialized_hypertable_49\nindexname  | _materialized_hypertable_49_bucket_idx\ntablespace | \nindexdef   | CREATE INDEX _materialized_hypertable_49_bucket_idx ON _timescaledb_internal._materialized_hypertable_49 USING btree (bucket DESC)\n-[ RECORD 2 ]-------------------------------------------------------------------------------------------------------------------------------------------------\nschemaname | _timescaledb_internal\ntablename  | _materialized_hypertable_49\nindexname  | _materialized_hypertable_49_symbol_bucket_idx\ntablespace | \nindexdef   | CREATE INDEX _materialized_hypertable_49_symbol_bucket_idx ON _timescaledb_internal._materialized_hypertable_49 USING btree (symbol, bucket DESC)\n-[ RECORD 3 ]-------------------------------------------------------------------------------------------------------------------------------------------------\nschemaname | _timescaledb_internal\ntablename  | _materialized_hypertable_50\nindexname  | _materialized_hypertable_50_bucket_idx\ntablespace | \nindexdef   | CREATE INDEX _materialized_hypertable_50_bucket_idx ON _timescaledb_internal._materialized_hypertable_50 USING btree (bucket DESC)\n-[ RECORD 4 ]-------------------------------------------------------------------------------------------------------------------------------------------------\nschemaname | _timescaledb_internal\ntablename  | _materialized_hypertable_51\nindexname  | _materialized_hypertable_51_bucket_idx\ntablespace | \nindexdef   | CREATE INDEX _materialized_hypertable_51_bucket_idx ON _timescaledb_internal._materialized_hypertable_51 USING btree (bucket DESC)\n-[ RECORD 5 ]-------------------------------------------------------------------------------------------------------------------------------------------------\nschemaname | _timescaledb_internal\ntablename  | _materialized_hypertable_51\nindexname  | _materialized_hypertable_51_symbol_bucket_idx\ntablespace | \nindexdef   | CREATE INDEX _materialized_hypertable_51_symbol_bucket_idx ON _timescaledb_internal._materialized_hypertable_51 USING btree (symbol, bucket DESC)\n\n\\x off\n--\n-- TESTs for removing old CAggs restrictions\n--\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 8 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_29_67_chunk\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_30_68_chunk\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_31_69_chunk\nCREATE TABLE conditions (\n  timec       TIMESTAMPTZ       NOT NULL,\n  location    TEXT              NOT NULL,\n  temperature DOUBLE PRECISION  NULL,\n  humidity    DOUBLE PRECISION  NULL\n);\nSELECT create_hypertable('conditions', 'timec');\n    create_hypertable     \n--------------------------\n (52,public,conditions,t)\n\nINSERT INTO conditions\nVALUES\n  ('2010-01-01 09:00:00-08', 'SFO', 55, 45),\n  ('2010-01-02 09:00:00-08', 'por', 100, 100),\n  ('2010-01-02 09:00:00-08', 'NYC', 65, 45),\n  ('2010-01-02 09:00:00-08', 'SFO', 65, 45),\n  ('2010-01-03 09:00:00-08', 'NYC', 45, 55),\n  ('2010-01-05 09:00:00-08', 'SFO', 75, 100),\n  ('2018-11-01 09:00:00-08', 'NYC', 45, 35),\n  ('2018-11-02 09:00:00-08', 'NYC', 35, 15),\n  ('2018-11-03 09:00:00-08', 'NYC', 35, 25);\n-- aggregate with DISTINCT\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1week', timec),\n  COUNT(location),\n  SUM(DISTINCT temperature)\nFROM conditions\nGROUP BY time_bucket('1week', timec), location;\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\nSELECT * FROM mat_m1 ORDER BY 1, 2, 3;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Dec 27 16:00:00 2009 PST |     1 | 100\n Sun Dec 27 16:00:00 2009 PST |     2 | 110\n Sun Dec 27 16:00:00 2009 PST |     2 | 120\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n Sun Oct 28 17:00:00 2018 PDT |     3 |  80\n\n-- aggregate with FILTER\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1week', timec),\n  SUM(temperature) FILTER (WHERE humidity > 60)\nFROM conditions\nGROUP BY time_bucket('1week', timec), location;\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\nSELECT * FROM mat_m1 ORDER BY 1, 2;\n         time_bucket          | sum \n------------------------------+-----\n Sun Dec 27 16:00:00 2009 PST | 100\n Sun Dec 27 16:00:00 2009 PST |    \n Sun Dec 27 16:00:00 2009 PST |    \n Sun Jan 03 16:00:00 2010 PST |  75\n Sun Oct 28 17:00:00 2018 PDT |    \n\n-- aggregate with filter in having clause\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1week', timec),\n  MAX(temperature)\nFROM conditions\nGROUP BY time_bucket('1week', timec), location\nHAVING SUM(temperature) FILTER (WHERE humidity > 40) > 50;\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\nSELECT * FROM mat_m1 ORDER BY 1, 2;\n         time_bucket          | max \n------------------------------+-----\n Sun Dec 27 16:00:00 2009 PST |  65\n Sun Dec 27 16:00:00 2009 PST |  65\n Sun Dec 27 16:00:00 2009 PST | 100\n Sun Jan 03 16:00:00 2010 PST |  75\n\n-- ordered set aggr\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_55_116_chunk\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1week', timec),\n  MODE() WITHIN GROUP(ORDER BY humidity)\nFROM conditions\nGROUP BY time_bucket('1week', timec);\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\nSELECT * FROM mat_m1 ORDER BY 1;\n         time_bucket          | mode \n------------------------------+------\n Sun Dec 27 16:00:00 2009 PST |   45\n Sun Jan 03 16:00:00 2010 PST |  100\n Sun Oct 28 17:00:00 2018 PDT |   15\n\n-- hypothetical-set aggr\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1week', timec),\n  RANK(60) WITHIN GROUP (ORDER BY humidity),\n  DENSE_RANK(60) WITHIN GROUP (ORDER BY humidity),\n  PERCENT_RANK(60) WITHIN GROUP (ORDER BY humidity)\nFROM conditions\nGROUP BY time_bucket('1week', timec);\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\nSELECT * FROM mat_m1 ORDER BY 1;\n         time_bucket          | rank | dense_rank | percent_rank \n------------------------------+------+------------+--------------\n Sun Dec 27 16:00:00 2009 PST |    5 |          3 |          0.8\n Sun Jan 03 16:00:00 2010 PST |    1 |          1 |            0\n Sun Oct 28 17:00:00 2018 PDT |    4 |          4 |            1\n\n-- userdefined aggregate without combine function\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\nCREATE AGGREGATE newavg (\n  sfunc = int4_avg_accum,\n  basetype = int4,\n  stype = _int8,\n  finalfunc = int8_avg,\n  initcond1 = '{0,0}'\n);\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  SUM(humidity),\n  round(newavg(temperature::int4))\nFROM conditions\nGROUP BY time_bucket('1week', timec), location ORDER BY 1,2;\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\nSELECT * FROM mat_m1 ORDER BY 1, 2;\n sum | round \n-----+-------\n  75 |    38\n  90 |    60\n 100 |    55\n 100 |    75\n 100 |   100\n\n-- ORDER BY in the view definition\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 2 other objects\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1week', timec),\n  COUNT(location),\n  SUM(temperature)\nFROM conditions\nGROUP BY time_bucket('1week', timec)\nORDER BY sum DESC;\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\n-- CAgg definition for realtime\nSELECT pg_get_viewdef('mat_m1',true);\n                                                                                  pg_get_viewdef                                                                                   \n-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n ( SELECT _materialized_hypertable_59.time_bucket,                                                                                                                                +\n     _materialized_hypertable_59.count,                                                                                                                                           +\n     _materialized_hypertable_59.sum                                                                                                                                              +\n    FROM _timescaledb_internal._materialized_hypertable_59                                                                                                                        +\n   WHERE _materialized_hypertable_59.time_bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(59)), '-infinity'::timestamp with time zone)+\n   ORDER BY _materialized_hypertable_59.sum DESC)                                                                                                                                 +\n UNION ALL                                                                                                                                                                        +\n ( SELECT time_bucket('@ 7 days'::interval, conditions.timec) AS time_bucket,                                                                                                     +\n     count(conditions.location) AS count,                                                                                                                                         +\n     sum(conditions.temperature) AS sum                                                                                                                                           +\n    FROM conditions                                                                                                                                                               +\n   WHERE conditions.timec >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(59)), '-infinity'::timestamp with time zone)                      +\n   GROUP BY (time_bucket('@ 7 days'::interval, conditions.timec))                                                                                                                 +\n   ORDER BY (sum(conditions.temperature)) DESC)                                                                                                                                   +\n   ORDER BY 3 DESC;\n\n-- Ordered result\nSELECT * FROM mat_m1;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Dec 27 16:00:00 2009 PST |     5 | 330\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n\n-- Insert new data and query again to make sure we produce ordered data\nINSERT INTO conditions VALUES ('2018-11-10 09:00:00-08', 'SFO', 10, 10);\nSELECT * FROM mat_m1;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Dec 27 16:00:00 2009 PST |     5 | 330\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n Sun Nov 04 16:00:00 2018 PST |     1 |  10\n\n-- This new row will change the order again\nINSERT INTO conditions VALUES ('2018-11-11 09:00:00-08', 'SFO', 400, 400);\nSELECT * FROM mat_m1;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Nov 04 16:00:00 2018 PST |     2 | 410\n Sun Dec 27 16:00:00 2009 PST |     5 | 330\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n\n-- Merge Append\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM mat_m1;\n--- QUERY PLAN ---\n Merge Append\n   Sort Key: _materialized_hypertable_59.sum DESC\n   ->  Merge Append\n         Sort Key: _materialized_hypertable_59.sum DESC\n         ->  Index Scan Backward using _hyper_59_123_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_123_chunk\n         ->  Index Scan Backward using _hyper_59_124_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_124_chunk\n               Index Cond: (time_bucket < 'Sun Nov 04 16:00:00 2018 PST'::timestamp with time zone)\n   ->  Sort\n         Sort Key: (sum(conditions.temperature)) DESC\n         ->  Finalize HashAggregate\n               Group Key: (time_bucket('@ 7 days'::interval, conditions.timec))\n               ->  Append\n                     ->  Partial HashAggregate\n                           Group Key: time_bucket('@ 7 days'::interval, _hyper_52_111_chunk.timec)\n                           ->  Index Scan Backward using _hyper_52_111_chunk_conditions_timec_idx on _hyper_52_111_chunk\n                                 Index Cond: (timec >= 'Sun Nov 04 16:00:00 2018 PST'::timestamp with time zone)\n                     ->  Partial HashAggregate\n                           Group Key: time_bucket('@ 7 days'::interval, _hyper_52_125_chunk.timec)\n                           ->  Seq Scan on _hyper_52_125_chunk\n\n-- Ordering by another column\nSELECT * FROM mat_m1 ORDER BY count;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n Sun Nov 04 16:00:00 2018 PST |     2 | 410\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Dec 27 16:00:00 2009 PST |     5 | 330\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM mat_m1 ORDER BY count;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _materialized_hypertable_59.count\n   ->  Merge Append\n         Sort Key: _materialized_hypertable_59.sum DESC\n         ->  Merge Append\n               Sort Key: _materialized_hypertable_59.sum DESC\n               ->  Index Scan Backward using _hyper_59_123_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_123_chunk\n               ->  Index Scan Backward using _hyper_59_124_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_124_chunk\n                     Index Cond: (time_bucket < 'Sun Nov 04 16:00:00 2018 PST'::timestamp with time zone)\n         ->  Sort\n               Sort Key: (sum(conditions.temperature)) DESC\n               ->  Finalize HashAggregate\n                     Group Key: (time_bucket('@ 7 days'::interval, conditions.timec))\n                     ->  Append\n                           ->  Partial HashAggregate\n                                 Group Key: time_bucket('@ 7 days'::interval, _hyper_52_111_chunk.timec)\n                                 ->  Index Scan Backward using _hyper_52_111_chunk_conditions_timec_idx on _hyper_52_111_chunk\n                                       Index Cond: (timec >= 'Sun Nov 04 16:00:00 2018 PST'::timestamp with time zone)\n                           ->  Partial HashAggregate\n                                 Group Key: time_bucket('@ 7 days'::interval, _hyper_52_125_chunk.timec)\n                                 ->  Seq Scan on _hyper_52_125_chunk\n\n-- Change the type of cagg\nALTER MATERIALIZED VIEW mat_m1 SET (timescaledb.materialized_only=true);\n-- CAgg definition for materialized only\nSELECT pg_get_viewdef('mat_m1',true);\n                      pg_get_viewdef                       \n-----------------------------------------------------------\n  SELECT time_bucket,                                     +\n     count,                                               +\n     sum                                                  +\n    FROM _timescaledb_internal._materialized_hypertable_59+\n   ORDER BY sum DESC;\n\n-- Now the query will show only the materialized data, without last two\n-- records inserted into the original hypertable (last two insers above)\nSELECT * FROM mat_m1;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Dec 27 16:00:00 2009 PST |     5 | 330\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n\n-- Merge Append\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM mat_m1;\n--- QUERY PLAN ---\n Merge Append\n   Sort Key: _materialized_hypertable_59.sum DESC\n   ->  Index Scan Backward using _hyper_59_123_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_123_chunk\n   ->  Index Scan Backward using _hyper_59_124_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_124_chunk\n\n-- Ordering by another column\nSELECT * FROM mat_m1 ORDER BY count;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Dec 27 16:00:00 2009 PST |     5 | 330\n\nEXPLAIN (BUFFERS OFF, COSTS OFF) SELECT * FROM mat_m1 ORDER BY count;\n--- QUERY PLAN ---\n Sort\n   Sort Key: _materialized_hypertable_59.count\n   ->  Merge Append\n         Sort Key: _materialized_hypertable_59.sum DESC\n         ->  Index Scan Backward using _hyper_59_123_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_123_chunk\n         ->  Index Scan Backward using _hyper_59_124_chunk__materialized_hypertable_59_sum_time_bucket on _hyper_59_124_chunk\n\nSELECT h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_m1'\n\\gset\n-- Invalidate old region and refresh again\nDELETE FROM conditions WHERE timec < '2010-01-05 09:00:00-08';\nCALL refresh_continuous_aggregate('mat_m1', NULL, NULL);\n-- Querying the cagg produce ordered records as expected\nSELECT * FROM mat_m1;\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Nov 04 16:00:00 2018 PST |     2 | 410\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n\n-- Querying direct the materialization hypertable doesn't\n-- produce ordered records\nSELECT * FROM :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\";\n         time_bucket          | count | sum \n------------------------------+-------+-----\n Sun Jan 03 16:00:00 2010 PST |     1 |  75\n Sun Oct 28 17:00:00 2018 PDT |     3 | 115\n Sun Nov 04 16:00:00 2018 PST |     2 | 410\n\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to 2 other objects\nCREATE TABLE conditions (\n  timec       TIMESTAMPTZ       NOT NULL,\n  location    TEXT              NOT NULL,\n  temperature DOUBLE PRECISION  NULL,\n  humidity    DOUBLE PRECISION  NULL\n);\nSELECT table_name FROM create_hypertable('conditions', 'timec');\n table_name \n------------\n conditions\n\nINSERT INTO conditions VALUES\n  ('2010-01-01 09:00:00-08', 'SFO', 55, 45),\n  ('2010-01-02 09:00:00-08', 'por', 100, 100),\n  ('2010-01-02 09:00:00-08', 'SFO', 65, 45),\n  ('2010-01-02 09:00:00-08', 'NYC', 65, 45),\n  ('2018-11-01 09:00:00-08', 'NYC', 45, 35),\n  ('2018-11-02 09:00:00-08', 'NYC', 35, 15);\nCREATE MATERIALIZED VIEW conditions_summary_new(timec, minl, sumt, sumh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nAS\nSELECT time_bucket('1day', timec), min(location), sum(temperature), sum(humidity)\nFROM conditions\nGROUP BY time_bucket('1day', timec) WITH NO DATA;\n\\x ON\nSELECT *\nFROM timescaledb_information.continuous_aggregates\nWHERE view_name = 'conditions_summary_new';\n-[ RECORD 1 ]---------------------+----------------------------------------------------------\nhypertable_schema                 | public\nhypertable_name                   | conditions\nview_schema                       | public\nview_name                         | conditions_summary_new\nview_owner                        | default_perm_user\nmaterialized_only                 | t\ncompression_enabled               | f\nmaterialization_hypertable_schema | _timescaledb_internal\nmaterialization_hypertable_name   | _materialized_hypertable_61\nview_definition                   |  SELECT time_bucket('@ 1 day'::interval, timec) AS timec,+\n                                  |     min(location) AS minl,                               +\n                                  |     sum(temperature) AS sumt,                            +\n                                  |     sum(humidity) AS sumh                                +\n                                  |    FROM conditions                                       +\n                                  |   GROUP BY (time_bucket('@ 1 day'::interval, timec));\n\n\\x OFF\nCALL refresh_continuous_aggregate('conditions_summary_new', NULL, NULL);\n-- Check and compare number of returned rows\nSELECT count(*) FROM conditions_summary_new;\n count \n-------\n     4\n\n-- Parallel planning test for realtime Continuous Aggregate\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to 2 other objects\nCREATE TABLE conditions (\n  timec       TIMESTAMPTZ       NOT NULL,\n  temperature DOUBLE PRECISION  NULL\n);\nSELECT table_name FROM create_hypertable('conditions', 'timec');\n table_name \n------------\n conditions\n\nINSERT INTO conditions\nSELECT t, 10 FROM generate_series('2023-01-01 00:00-03'::timestamptz, '2023-12-31 23:59-03'::timestamptz, '1 hour'::interval) AS t;\nCREATE MATERIALIZED VIEW conditions_daily WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT\n  time_bucket('1 day', timec),\n  SUM(temperature)\nFROM conditions\nGROUP BY 1\nORDER BY 2 DESC;\nNOTICE:  refreshing continuous aggregate \"conditions_daily\"\nSELECT set_config(CASE WHEN current_setting('server_version_num')::int < 160000 THEN 'force_parallel_mode' ELSE 'debug_parallel_query' END,'on', false);\n set_config \n------------\n on\n\nSET max_parallel_workers_per_gather = 4;\nSET parallel_setup_cost = 0;\nSET parallel_tuple_cost = 0;\n-- Parallel planning\nEXPLAIN (BUFFERS OFF, COSTS OFF, TIMING OFF) SELECT * FROM conditions_daily WHERE time_bucket >= '2023-07-01';\n--- QUERY PLAN ---\n Merge Append\n   Sort Key: _materialized_hypertable_63.sum DESC\n   ->  Gather Merge\n         Workers Planned: 2\n         ->  Sort\n               Sort Key: _materialized_hypertable_63.sum DESC\n               ->  Parallel Append\n                     ->  Parallel Index Scan using _hyper_63_185_chunk__materialized_hypertable_63_time_bucket_idx on _hyper_63_185_chunk\n                           Index Cond: ((time_bucket < 'Mon Jan 01 16:00:00 2024 PST'::timestamp with time zone) AND (time_bucket >= 'Sat Jul 01 00:00:00 2023 PDT'::timestamp with time zone))\n                     ->  Parallel Index Scan using _hyper_63_187_chunk__materialized_hypertable_63_time_bucket_idx on _hyper_63_187_chunk\n                           Index Cond: ((time_bucket < 'Mon Jan 01 16:00:00 2024 PST'::timestamp with time zone) AND (time_bucket >= 'Sat Jul 01 00:00:00 2023 PDT'::timestamp with time zone))\n                     ->  Parallel Seq Scan on _hyper_63_184_chunk\n   ->  Sort\n         Sort Key: (sum(_hyper_62_182_chunk.temperature)) DESC\n         ->  HashAggregate\n               Group Key: (time_bucket('@ 1 day'::interval, _hyper_62_182_chunk.timec))\n               ->  Gather\n                     Workers Planned: 1\n                     ->  Result\n                           ->  Parallel Index Scan Backward using _hyper_62_182_chunk_conditions_timec_idx on _hyper_62_182_chunk\n                                 Index Cond: ((timec >= 'Mon Jan 01 16:00:00 2024 PST'::timestamp with time zone) AND (timec >= 'Sat Jul 01 00:00:00 2023 PDT'::timestamp with time zone))\n                                 Filter: (time_bucket('@ 1 day'::interval, timec) >= 'Sat Jul 01 00:00:00 2023 PDT'::timestamp with time zone)\n\n"
  },
  {
    "path": "tsl/test/expected/cagg_bgw-15.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n--\n-- Setup\n--\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(timeout INT = -1, mock_start_time INT = 0) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_db_scheduler_test_run(timeout INT = -1, mock_start_time INT = 0) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_db_scheduler_test_wait_for_scheduler_finish() RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_params_create() RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_params_destroy() RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_params_reset_time(set_time BIGINT = 0, wait BOOLEAN = false) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\n--test that this all works under the community license\nALTER DATABASE :TEST_DBNAME SET timescaledb.license_key='Community';\n--create a function with no permissions to execute\nCREATE FUNCTION get_constant_no_perms() RETURNS INTEGER LANGUAGE SQL IMMUTABLE AS\n$BODY$\n    SELECT 10;\n$BODY$;\nREVOKE EXECUTE ON FUNCTION get_constant_no_perms() FROM PUBLIC;\n\\set WAIT_ON_JOB 0\n\\set IMMEDIATELY_SET_UNTIL 1\n\\set WAIT_FOR_OTHER_TO_ADVANCE 2\nCREATE OR REPLACE FUNCTION ts_bgw_params_mock_wait_returns_immediately(new_val INTEGER) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\n-- Remove any default jobs, e.g., telemetry\nDELETE FROM _timescaledb_catalog.bgw_job WHERE TRUE;\nTRUNCATE _timescaledb_internal.bgw_job_stat;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nCREATE TABLE public.bgw_log(\n    msg_no INT,\n    mock_time BIGINT,\n    application_name TEXT,\n    msg TEXT\n);\nCREATE VIEW sorted_bgw_log AS\n    SELECT msg_no,\n    \t   mock_time,\n    \t   application_name,\n    \t   regexp_replace(regexp_replace(msg, '(Wait until|started at|execution time) [0-9]+(\\.[0-9]+)?', '\\1 (RANDOM)', 'g'), 'background worker \"[^\"]+\"','connection') AS msg\n\t   FROM bgw_log ORDER BY mock_time, application_name COLLATE \"C\", msg_no;\nCREATE TABLE public.bgw_dsm_handle_store(\n    handle BIGINT\n);\nINSERT INTO public.bgw_dsm_handle_store VALUES (0);\nSELECT ts_bgw_params_create();\n ts_bgw_params_create \n----------------------\n \n\nSELECT * FROM _timescaledb_catalog.bgw_job;\n id | application_name | schedule_interval | max_runtime | max_retries | retry_period | proc_schema | proc_name | owner | scheduled | fixed_schedule | initial_start | hypertable_id | config | check_schema | check_name | timezone \n----+------------------+-------------------+-------------+-------------+--------------+-------------+-----------+-------+-----------+----------------+---------------+---------------+--------+--------------+------------+----------\n\nSELECT * FROM timescaledb_information.job_stats;\n hypertable_schema | hypertable_name | job_id | last_run_started_at | last_successful_finish | last_run_status | job_status | last_run_duration | next_start | total_runs | total_successes | total_failures \n-------------------+-----------------+--------+---------------------+------------------------+-----------------+------------+-------------------+------------+------------+-----------------+----------------\n\nSELECT * FROM _timescaledb_catalog.continuous_agg;\n mat_hypertable_id | raw_hypertable_id | parent_mat_hypertable_id | user_view_schema | user_view_name | partial_view_schema | partial_view_name | direct_view_schema | direct_view_name | materialized_only \n-------------------+-------------------+--------------------------+------------------+----------------+---------------------+-------------------+--------------------+------------------+-------------------\n\n-- though user on access node has required GRANTS, this will propagate GRANTS to the connected data nodes\nGRANT CREATE ON SCHEMA public TO :ROLE_DEFAULT_PERM_USER;\nWARNING:  no privileges were granted for \"public\"\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nCREATE TABLE test_continuous_agg_table(time int, data int);\nSELECT create_hypertable('test_continuous_agg_table', 'time', chunk_time_interval => 10);\n           create_hypertable            \n----------------------------------------\n (1,public,test_continuous_agg_table,t)\n\nCREATE OR REPLACE FUNCTION integer_now_test() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), 0) FROM test_continuous_agg_table $$;\nSELECT set_integer_now_func('test_continuous_agg_table', 'integer_now_test');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW test_continuous_agg_view\n    WITH (timescaledb.continuous, timescaledb.materialized_only=true)\n    AS SELECT time_bucket('2', time), SUM(data) as value\n        FROM test_continuous_agg_table\n        GROUP BY 1 WITH NO DATA;\nSELECT add_continuous_aggregate_policy('test_continuous_agg_view', NULL, 4::integer, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1000\n\nSELECT id as raw_table_id FROM _timescaledb_catalog.hypertable WHERE table_name='test_continuous_agg_table' \\gset\n-- min distance from end should be 1\nSELECT mat_hypertable_id, user_view_schema, user_view_name FROM _timescaledb_catalog.continuous_agg;\n mat_hypertable_id | user_view_schema |      user_view_name      \n-------------------+------------------+--------------------------\n                 2 | public           | test_continuous_agg_view\n\nSELECT mat_hypertable_id, bucket_width FROM _timescaledb_catalog.continuous_aggs_bucket_function;\n mat_hypertable_id | bucket_width \n-------------------+--------------\n                 2 | 2\n\nSELECT mat_hypertable_id FROM _timescaledb_catalog.continuous_agg \\gset\nSELECT id AS job_id FROM _timescaledb_catalog.bgw_job where hypertable_id=:mat_hypertable_id \\gset\n-- job was created\nSELECT * FROM _timescaledb_catalog.bgw_job where hypertable_id=:mat_hypertable_id;\n  id  |              application_name              | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema       |              proc_name              |       owner       | scheduled | fixed_schedule | initial_start | hypertable_id |                             config                              |      check_schema      |                check_name                 | timezone \n------+--------------------------------------------+-------------------+-------------+-------------+--------------+------------------------+-------------------------------------+-------------------+-----------+----------------+---------------+---------------+-----------------------------------------------------------------+------------------------+-------------------------------------------+----------\n 1000 | Refresh Continuous Aggregate Policy [1000] | @ 12 hours        | @ 0         |          -1 | @ 12 hours   | _timescaledb_functions | policy_refresh_continuous_aggregate | default_perm_user | t         | f              |               |             2 | {\"end_offset\": 4, \"start_offset\": null, \"mat_hypertable_id\": 2} | _timescaledb_functions | policy_refresh_continuous_aggregate_check | \n\n-- create 10 time buckets\nINSERT INTO test_continuous_agg_table\n    SELECT i, i FROM\n        (SELECT generate_series(0, 10) as i) AS j;\n-- no stats\nSELECT job_id, next_start, last_finish as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    ORDER BY job_id;\n job_id | next_start | until_next | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------+------------+------------------+------------+-----------------+----------------+---------------\n\n-- no data in view\nSELECT * FROM test_continuous_agg_view ORDER BY 1;\n time_bucket | value \n-------------+-------\n\n-- run first time\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | mock_time |              application_name              |                                                        msg                                                        \n--------+-----------+--------------------------------------------+-------------------------------------------------------------------------------------------------------------------\n      0 |         0 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 |         0 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 |         0 | Refresh Continuous Aggregate Policy [1000] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ -2147483648, 6 ]\n      1 |         0 | Refresh Continuous Aggregate Policy [1000] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      2 |         0 | Refresh Continuous Aggregate Policy [1000] | inserted 3 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n\nSELECT * FROM _timescaledb_catalog.bgw_job where id=:job_id;\n  id  |              application_name              | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema       |              proc_name              |       owner       | scheduled | fixed_schedule | initial_start | hypertable_id |                             config                              |      check_schema      |                check_name                 | timezone \n------+--------------------------------------------+-------------------+-------------+-------------+--------------+------------------------+-------------------------------------+-------------------+-----------+----------------+---------------+---------------+-----------------------------------------------------------------+------------------------+-------------------------------------------+----------\n 1000 | Refresh Continuous Aggregate Policy [1000] | @ 12 hours        | @ 0         |          -1 | @ 12 hours   | _timescaledb_functions | policy_refresh_continuous_aggregate | default_perm_user | t         | f              |               |             2 | {\"end_offset\": 4, \"start_offset\": null, \"mat_hypertable_id\": 2} | _timescaledb_functions | policy_refresh_continuous_aggregate_check | \n\n-- job ran once, successfully\nSELECT job_id, next_start-last_finish as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:job_id;\n job_id | until_next | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------+------------------+------------+-----------------+----------------+---------------\n   1000 | @ 12 hours | t                |          1 |               1 |              0 |             0\n\n--clear log for next run of scheduler.\nTRUNCATE public.bgw_log;\nCREATE FUNCTION wait_for_timer_to_run(started_at INTEGER, spins INTEGER=:TEST_SPINWAIT_ITERS) RETURNS BOOLEAN LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n    num_runs INTEGER;\n    message TEXT;\nBEGIN\n    select format('[TESTING] Wait until %%, started at %s', started_at) into message;\n    FOR i in 1..spins\n    LOOP\n    SELECT COUNT(*) from bgw_log where msg LIKE message INTO num_runs;\n    if (num_runs > 0) THEN\n        RETURN true;\n    ELSE\n        PERFORM pg_sleep(0.1);\n    END IF;\n    END LOOP;\n    RETURN false;\nEND\n$BODY$;\n--make sure there is 1 job to start with\nSELECT test.wait_for_job_to_run(:job_id, 1);\n wait_for_job_to_run \n---------------------\n t\n\nSELECT ts_bgw_params_mock_wait_returns_immediately(:WAIT_FOR_OTHER_TO_ADVANCE);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\n--start the scheduler on 0 time\nSELECT ts_bgw_params_reset_time(0, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run(extract(epoch from interval '24 hour')::int * 1000, 0);\n ts_bgw_db_scheduler_test_run \n------------------------------\n \n\nSELECT wait_for_timer_to_run(0);\n wait_for_timer_to_run \n-----------------------\n t\n\n--advance to 12:00 so that it runs one more time; now we know the\n--scheduler has loaded up the job with the old schedule_interval\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '12 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT test.wait_for_job_to_run(:job_id, 2);\n wait_for_job_to_run \n---------------------\n t\n\n--advance clock 1us to make the scheduler realize the job is done\nSELECT ts_bgw_params_reset_time((extract(epoch from interval '12 hour')::bigint * 1000000)+1, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\n--alter the refresh interval and check if next_start is altered\nSELECT alter_job(:job_id, schedule_interval => '1m', retry_period => '1m');\n                                                                                                                           alter_job                                                                                                                            \n----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1000,\"@ 1 min\",\"@ 0\",-1,\"@ 1 min\",t,\"{\"\"end_offset\"\": 4, \"\"start_offset\"\": null, \"\"mat_hypertable_id\"\": 2}\",\"Sat Jan 01 04:01:00 2000 PST\",_timescaledb_functions.policy_refresh_continuous_aggregate_check,f,,,\"Refresh Continuous Aggregate Policy [1000]\")\n\nSELECT job_id, next_start - last_finish as until_next, total_runs\nFROM _timescaledb_internal.bgw_job_stat\nWHERE job_id=:job_id;;\n job_id | until_next | total_runs \n--------+------------+------------\n   1000 | @ 1 min    |          2\n\n--advance to 12:02, job should have run at 12:01\nSELECT ts_bgw_params_reset_time((extract(epoch from interval '12 hour')::bigint * 1000000)+(extract(epoch from interval '2 minute')::bigint * 1000000), true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT test.wait_for_job_to_run(:job_id, 3);\n wait_for_job_to_run \n---------------------\n t\n\n--next run in 1 minute\nSELECT job_id, next_start-last_finish as until_next, total_runs\nFROM _timescaledb_internal.bgw_job_stat\nWHERE job_id=:job_id;\n job_id | until_next | total_runs \n--------+------------+------------\n   1000 | @ 1 min    |          3\n\n--change next run to be after 30s instead\nSELECT (next_start - '30s'::interval) AS \"NEW_NEXT_START\"\nFROM _timescaledb_internal.bgw_job_stat\nWHERE job_id=:job_id \\gset\nSELECT alter_job(:job_id, next_start => :'NEW_NEXT_START');\n                                                                                                                           alter_job                                                                                                                            \n----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1000,\"@ 1 min\",\"@ 0\",-1,\"@ 1 min\",t,\"{\"\"end_offset\"\": 4, \"\"start_offset\"\": null, \"\"mat_hypertable_id\"\": 2}\",\"Sat Jan 01 04:02:30 2000 PST\",_timescaledb_functions.policy_refresh_continuous_aggregate_check,f,,,\"Refresh Continuous Aggregate Policy [1000]\")\n\nSELECT ts_bgw_params_reset_time((extract(epoch from interval '12 hour')::bigint * 1000000)+(extract(epoch from interval '2 minute 30 seconds')::bigint * 1000000), true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT test.wait_for_job_to_run(:job_id, 4);\n wait_for_job_to_run \n---------------------\n t\n\n--advance clock to quit scheduler\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '25 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nselect ts_bgw_db_scheduler_test_wait_for_scheduler_finish();\n ts_bgw_db_scheduler_test_wait_for_scheduler_finish \n----------------------------------------------------\n \n\nSELECT ts_bgw_params_mock_wait_returns_immediately(:WAIT_ON_JOB);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\nTRUNCATE public.bgw_log;\n-- data before 8\nSELECT * FROM test_continuous_agg_view ORDER BY 1;\n time_bucket | value \n-------------+-------\n           0 |     1\n           2 |     5\n           4 |     9\n\n-- invalidations test by running job multiple times\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nDROP MATERIALIZED VIEW test_continuous_agg_view;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_2_3_chunk\nCREATE MATERIALIZED VIEW test_continuous_agg_view\n    WITH (timescaledb.continuous,\n        timescaledb.materialized_only=true)\n    AS SELECT time_bucket('2', time), SUM(data) as value\n        FROM test_continuous_agg_table\n        GROUP BY 1 WITH NO DATA;\nSELECT add_continuous_aggregate_policy('test_continuous_agg_view', 100::integer, -2::integer, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1001\n\nSELECT mat_hypertable_id FROM _timescaledb_catalog.continuous_agg \\gset\nSELECT id AS job_id FROM _timescaledb_catalog.bgw_job WHERE hypertable_id=:mat_hypertable_id \\gset\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | mock_time |              application_name              |                                                            msg                                                            \n--------+-----------+--------------------------------------------+---------------------------------------------------------------------------------------------------------------------------\n      0 |         0 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 |         0 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 |         0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ 10, 12 ] (batch 1 of 2)\n      1 |         0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      2 |         0 | Refresh Continuous Aggregate Policy [1001] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      3 |         0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ -10, 10 ] (batch 2 of 2)\n      4 |         0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      5 |         0 | Refresh Continuous Aggregate Policy [1001] | inserted 5 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n\n-- job ran once, successfully\nSELECT job_id, last_finish - next_start as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:job_id;\n job_id |   until_next   | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+----------------+------------------+------------+-----------------+----------------+---------------\n   1001 | @ 12 hours ago | t                |          1 |               1 |              0 |             0\n\n-- should have refreshed everything we have so far\nSELECT * FROM test_continuous_agg_view ORDER BY 1;\n time_bucket | value \n-------------+-------\n           0 |     1\n           2 |     5\n           4 |     9\n           6 |    13\n           8 |    17\n          10 |    10\n\n-- invalidate some data\nUPDATE test_continuous_agg_table\nSET data = 11 WHERE time = 6;\n--advance time by 12h so that job runs one more time\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '12 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25, 25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no |  mock_time  |              application_name              |                                                            msg                                                            \n--------+-------------+--------------------------------------------+---------------------------------------------------------------------------------------------------------------------------\n      0 |           0 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 |           0 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 |           0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ 10, 12 ] (batch 1 of 2)\n      1 |           0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      2 |           0 | Refresh Continuous Aggregate Policy [1001] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      3 |           0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ -10, 10 ] (batch 2 of 2)\n      4 |           0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      5 |           0 | Refresh Continuous Aggregate Policy [1001] | inserted 5 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      0 | 43200000000 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 | 43200000000 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ -90, -10 ]\n      1 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      2 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | inserted 0 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      3 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ 6, 8 ]\n      4 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | deleted 1 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      5 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n\nSELECT job_id, next_start - last_finish as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:job_id;\n job_id | until_next | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------+------------------+------------+-----------------+----------------+---------------\n   1001 | @ 12 hours | t                |          2 |               2 |              0 |             0\n\n-- should have updated data for time=6\nSELECT * FROM test_continuous_agg_view ORDER BY 1;\n time_bucket | value \n-------------+-------\n           0 |     1\n           2 |     5\n           4 |     9\n           6 |    18\n           8 |    17\n          10 |    10\n\n\\x on\n--check the information views --\nselect view_name, view_owner, materialization_hypertable_schema, materialization_hypertable_name\nfrom timescaledb_information.continuous_aggregates\nwhere view_name::text like '%test_continuous_agg_view';\n-[ RECORD 1 ]---------------------+---------------------------\nview_name                         | test_continuous_agg_view\nview_owner                        | default_perm_user\nmaterialization_hypertable_schema | _timescaledb_internal\nmaterialization_hypertable_name   | _materialized_hypertable_3\n\nselect view_name, view_definition from timescaledb_information.continuous_aggregates\nwhere view_name::text like '%test_continuous_agg_view';\n-[ RECORD 1 ]---+-------------------------------------------------------------------------\nview_name       | test_continuous_agg_view\nview_definition |  SELECT time_bucket(2, test_continuous_agg_table.\"time\") AS time_bucket,+\n                |     sum(test_continuous_agg_table.data) AS value                        +\n                |    FROM test_continuous_agg_table                                       +\n                |   GROUP BY (time_bucket(2, test_continuous_agg_table.\"time\"));\n\nselect job_status, last_run_duration\nfrom timescaledb_information.job_stats ps, timescaledb_information.continuous_aggregates cagg\nwhere cagg.view_name::text like '%test_continuous_agg_view'\nand cagg.materialization_hypertable_name = ps.hypertable_name;\n-[ RECORD 1 ]-----+----------\njob_status        | Scheduled\nlast_run_duration | \n\n\\x off\n-- test merged refresh (change data in two chunks)\nUPDATE test_continuous_agg_table SET data = 11;\n--advance time by 1day so that job runs one more time\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '1day')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(50, 50);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no |  mock_time  |              application_name              |                                                            msg                                                            \n--------+-------------+--------------------------------------------+---------------------------------------------------------------------------------------------------------------------------\n      0 |           0 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 |           0 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 |           0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ 10, 12 ] (batch 1 of 2)\n      1 |           0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      2 |           0 | Refresh Continuous Aggregate Policy [1001] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      3 |           0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ -10, 10 ] (batch 2 of 2)\n      4 |           0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      5 |           0 | Refresh Continuous Aggregate Policy [1001] | inserted 5 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      0 | 43200000000 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 | 43200000000 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ -90, -10 ]\n      1 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      2 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | inserted 0 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      3 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ 6, 8 ]\n      4 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | deleted 1 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      5 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      0 | 86400000000 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 | 86400000000 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | 86400000000 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ 0, 12 ]\n      1 | 86400000000 | Refresh Continuous Aggregate Policy [1001] | deleted 6 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      2 | 86400000000 | Refresh Continuous Aggregate Policy [1001] | inserted 6 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n\nDROP MATERIALIZED VIEW test_continuous_agg_view;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_3_4_chunk\n--create a view with a function that it has no permission to execute\nCREATE MATERIALIZED VIEW test_continuous_agg_view\n    WITH (timescaledb.continuous,\n        timescaledb.materialized_only=true)\n    AS SELECT time_bucket('2', time), SUM(data) as value, get_constant_no_perms()\n        FROM test_continuous_agg_table\n        GROUP BY 1 WITH NO DATA;\nSELECT add_continuous_aggregate_policy('test_continuous_agg_view', 100::integer, -2::integer, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1002\n\nSELECT id AS job_id FROM _timescaledb_catalog.bgw_job ORDER BY id desc limit 1 \\gset\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\n-- job fails\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:job_id;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1002 | f                |          1 |               0 |              1 |             0\n\nDROP MATERIALIZED VIEW test_continuous_agg_view;\n--advance clock to quit scheduler\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '25 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nselect ts_bgw_db_scheduler_test_wait_for_scheduler_finish();\n ts_bgw_db_scheduler_test_wait_for_scheduler_finish \n----------------------------------------------------\n \n\nSELECT ts_bgw_params_mock_wait_returns_immediately(:WAIT_ON_JOB);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\n--clear log for next run of the scheduler\nTRUNCATE public.bgw_log;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\n--\n-- Test creating continuous aggregate with a user that is the non-owner of the raw table\n--\nCREATE TABLE test_continuous_agg_table_w_grant(time int, data int);\nSELECT create_hypertable('test_continuous_agg_table_w_grant', 'time', chunk_time_interval => 10);\n               create_hypertable                \n------------------------------------------------\n (5,public,test_continuous_agg_table_w_grant,t)\n\nCREATE OR REPLACE FUNCTION integer_now_test1() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), 0) FROM test_continuous_agg_table_w_grant $$;\nSELECT set_integer_now_func('test_continuous_agg_table_w_grant', 'integer_now_test1');\n set_integer_now_func \n----------------------\n \n\nGRANT SELECT, TRIGGER ON test_continuous_agg_table_w_grant TO public;\nINSERT INTO test_continuous_agg_table_w_grant\n    SELECT 1 , 1;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n-- make sure view can be created\nCREATE MATERIALIZED VIEW test_continuous_agg_view_user_2\n    WITH ( timescaledb.continuous,\n        timescaledb.materialized_only=true)\n    AS SELECT time_bucket('2', time), SUM(data) as value\n        FROM test_continuous_agg_table_w_grant\n        GROUP BY 1 WITH NO DATA;\nSELECT add_continuous_aggregate_policy('test_continuous_agg_view_user_2', NULL, -2::integer, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1003\n\nSELECT id AS job_id FROM _timescaledb_catalog.bgw_job ORDER BY id desc limit 1 \\gset\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT id, owner FROM _timescaledb_catalog.bgw_job WHERE id = :job_id ;\n  id  |        owner        \n------+---------------------\n 1003 | default_perm_user_2\n\nSELECT job_id, next_start - last_finish as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:job_id;\n job_id | until_next | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------+------------------+------------+-----------------+----------------+---------------\n   1003 | @ 12 hours | t                |          1 |               1 |              0 |             0\n\n--view is populated\nSELECT * FROM test_continuous_agg_view_user_2 ORDER BY 1;\n time_bucket | value \n-------------+-------\n           0 |     1\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n--revoke permissions from the continuous agg view owner to select from raw table\n--no further updates to cont agg should happen\nREVOKE SELECT ON test_continuous_agg_table_w_grant FROM public;\n--add new data to table\nINSERT INTO test_continuous_agg_table_w_grant VALUES(5,1);\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n--advance time by 12h so that job tries to run one more time\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '12 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25, 25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\n--should show a failing execution because no longer has permissions (due to lack of permission on partial view owner's part)\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:job_id;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1003 | f                |          2 |               1 |              1 |             0\n\n--view was NOT updated; but the old stuff is still there\nSELECT * FROM test_continuous_agg_view_user_2;\n time_bucket | value \n-------------+-------\n           0 |     1\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT * from sorted_bgw_log;\n msg_no |  mock_time  |              application_name              |                                                           msg                                                            \n--------+-------------+--------------------------------------------+--------------------------------------------------------------------------------------------------------------------------\n      0 |           0 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 |           0 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 |           0 | Refresh Continuous Aggregate Policy [1003] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view_user_2\" in window [ -2147483648, 2 ]\n      1 |           0 | Refresh Continuous Aggregate Policy [1003] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n      2 |           0 | Refresh Continuous Aggregate Policy [1003] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n      0 | 43200000000 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 | 43200000000 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | 43200000000 | Refresh Continuous Aggregate Policy [1003] | job 1003 threw an error\n      1 | 43200000000 | Refresh Continuous Aggregate Policy [1003] | permission denied for table test_continuous_agg_table_w_grant\n\n-- Count the number of continuous aggregate policies\nSELECT count(*) FROM _timescaledb_catalog.bgw_job\n WHERE proc_schema = '_timescaledb_functions'\n   AND proc_name = 'policy_refresh_continuous_aggregate';\n count \n-------\n     1\n\n"
  },
  {
    "path": "tsl/test/expected/cagg_bgw-16.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n--\n-- Setup\n--\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(timeout INT = -1, mock_start_time INT = 0) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_db_scheduler_test_run(timeout INT = -1, mock_start_time INT = 0) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_db_scheduler_test_wait_for_scheduler_finish() RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_params_create() RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_params_destroy() RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_params_reset_time(set_time BIGINT = 0, wait BOOLEAN = false) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\n--test that this all works under the community license\nALTER DATABASE :TEST_DBNAME SET timescaledb.license_key='Community';\n--create a function with no permissions to execute\nCREATE FUNCTION get_constant_no_perms() RETURNS INTEGER LANGUAGE SQL IMMUTABLE AS\n$BODY$\n    SELECT 10;\n$BODY$;\nREVOKE EXECUTE ON FUNCTION get_constant_no_perms() FROM PUBLIC;\n\\set WAIT_ON_JOB 0\n\\set IMMEDIATELY_SET_UNTIL 1\n\\set WAIT_FOR_OTHER_TO_ADVANCE 2\nCREATE OR REPLACE FUNCTION ts_bgw_params_mock_wait_returns_immediately(new_val INTEGER) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\n-- Remove any default jobs, e.g., telemetry\nDELETE FROM _timescaledb_catalog.bgw_job WHERE TRUE;\nTRUNCATE _timescaledb_internal.bgw_job_stat;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nCREATE TABLE public.bgw_log(\n    msg_no INT,\n    mock_time BIGINT,\n    application_name TEXT,\n    msg TEXT\n);\nCREATE VIEW sorted_bgw_log AS\n    SELECT msg_no,\n    \t   mock_time,\n    \t   application_name,\n    \t   regexp_replace(regexp_replace(msg, '(Wait until|started at|execution time) [0-9]+(\\.[0-9]+)?', '\\1 (RANDOM)', 'g'), 'background worker \"[^\"]+\"','connection') AS msg\n\t   FROM bgw_log ORDER BY mock_time, application_name COLLATE \"C\", msg_no;\nCREATE TABLE public.bgw_dsm_handle_store(\n    handle BIGINT\n);\nINSERT INTO public.bgw_dsm_handle_store VALUES (0);\nSELECT ts_bgw_params_create();\n ts_bgw_params_create \n----------------------\n \n\nSELECT * FROM _timescaledb_catalog.bgw_job;\n id | application_name | schedule_interval | max_runtime | max_retries | retry_period | proc_schema | proc_name | owner | scheduled | fixed_schedule | initial_start | hypertable_id | config | check_schema | check_name | timezone \n----+------------------+-------------------+-------------+-------------+--------------+-------------+-----------+-------+-----------+----------------+---------------+---------------+--------+--------------+------------+----------\n\nSELECT * FROM timescaledb_information.job_stats;\n hypertable_schema | hypertable_name | job_id | last_run_started_at | last_successful_finish | last_run_status | job_status | last_run_duration | next_start | total_runs | total_successes | total_failures \n-------------------+-----------------+--------+---------------------+------------------------+-----------------+------------+-------------------+------------+------------+-----------------+----------------\n\nSELECT * FROM _timescaledb_catalog.continuous_agg;\n mat_hypertable_id | raw_hypertable_id | parent_mat_hypertable_id | user_view_schema | user_view_name | partial_view_schema | partial_view_name | direct_view_schema | direct_view_name | materialized_only \n-------------------+-------------------+--------------------------+------------------+----------------+---------------------+-------------------+--------------------+------------------+-------------------\n\n-- though user on access node has required GRANTS, this will propagate GRANTS to the connected data nodes\nGRANT CREATE ON SCHEMA public TO :ROLE_DEFAULT_PERM_USER;\nWARNING:  no privileges were granted for \"public\"\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nCREATE TABLE test_continuous_agg_table(time int, data int);\nSELECT create_hypertable('test_continuous_agg_table', 'time', chunk_time_interval => 10);\n           create_hypertable            \n----------------------------------------\n (1,public,test_continuous_agg_table,t)\n\nCREATE OR REPLACE FUNCTION integer_now_test() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), 0) FROM test_continuous_agg_table $$;\nSELECT set_integer_now_func('test_continuous_agg_table', 'integer_now_test');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW test_continuous_agg_view\n    WITH (timescaledb.continuous, timescaledb.materialized_only=true)\n    AS SELECT time_bucket('2', time), SUM(data) as value\n        FROM test_continuous_agg_table\n        GROUP BY 1 WITH NO DATA;\nSELECT add_continuous_aggregate_policy('test_continuous_agg_view', NULL, 4::integer, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1000\n\nSELECT id as raw_table_id FROM _timescaledb_catalog.hypertable WHERE table_name='test_continuous_agg_table' \\gset\n-- min distance from end should be 1\nSELECT mat_hypertable_id, user_view_schema, user_view_name FROM _timescaledb_catalog.continuous_agg;\n mat_hypertable_id | user_view_schema |      user_view_name      \n-------------------+------------------+--------------------------\n                 2 | public           | test_continuous_agg_view\n\nSELECT mat_hypertable_id, bucket_width FROM _timescaledb_catalog.continuous_aggs_bucket_function;\n mat_hypertable_id | bucket_width \n-------------------+--------------\n                 2 | 2\n\nSELECT mat_hypertable_id FROM _timescaledb_catalog.continuous_agg \\gset\nSELECT id AS job_id FROM _timescaledb_catalog.bgw_job where hypertable_id=:mat_hypertable_id \\gset\n-- job was created\nSELECT * FROM _timescaledb_catalog.bgw_job where hypertable_id=:mat_hypertable_id;\n  id  |              application_name              | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema       |              proc_name              |       owner       | scheduled | fixed_schedule | initial_start | hypertable_id |                             config                              |      check_schema      |                check_name                 | timezone \n------+--------------------------------------------+-------------------+-------------+-------------+--------------+------------------------+-------------------------------------+-------------------+-----------+----------------+---------------+---------------+-----------------------------------------------------------------+------------------------+-------------------------------------------+----------\n 1000 | Refresh Continuous Aggregate Policy [1000] | @ 12 hours        | @ 0         |          -1 | @ 12 hours   | _timescaledb_functions | policy_refresh_continuous_aggregate | default_perm_user | t         | f              |               |             2 | {\"end_offset\": 4, \"start_offset\": null, \"mat_hypertable_id\": 2} | _timescaledb_functions | policy_refresh_continuous_aggregate_check | \n\n-- create 10 time buckets\nINSERT INTO test_continuous_agg_table\n    SELECT i, i FROM\n        (SELECT generate_series(0, 10) as i) AS j;\n-- no stats\nSELECT job_id, next_start, last_finish as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    ORDER BY job_id;\n job_id | next_start | until_next | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------+------------+------------------+------------+-----------------+----------------+---------------\n\n-- no data in view\nSELECT * FROM test_continuous_agg_view ORDER BY 1;\n time_bucket | value \n-------------+-------\n\n-- run first time\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | mock_time |              application_name              |                                                        msg                                                        \n--------+-----------+--------------------------------------------+-------------------------------------------------------------------------------------------------------------------\n      0 |         0 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 |         0 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 |         0 | Refresh Continuous Aggregate Policy [1000] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ -2147483648, 6 ]\n      1 |         0 | Refresh Continuous Aggregate Policy [1000] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      2 |         0 | Refresh Continuous Aggregate Policy [1000] | inserted 3 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n\nSELECT * FROM _timescaledb_catalog.bgw_job where id=:job_id;\n  id  |              application_name              | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema       |              proc_name              |       owner       | scheduled | fixed_schedule | initial_start | hypertable_id |                             config                              |      check_schema      |                check_name                 | timezone \n------+--------------------------------------------+-------------------+-------------+-------------+--------------+------------------------+-------------------------------------+-------------------+-----------+----------------+---------------+---------------+-----------------------------------------------------------------+------------------------+-------------------------------------------+----------\n 1000 | Refresh Continuous Aggregate Policy [1000] | @ 12 hours        | @ 0         |          -1 | @ 12 hours   | _timescaledb_functions | policy_refresh_continuous_aggregate | default_perm_user | t         | f              |               |             2 | {\"end_offset\": 4, \"start_offset\": null, \"mat_hypertable_id\": 2} | _timescaledb_functions | policy_refresh_continuous_aggregate_check | \n\n-- job ran once, successfully\nSELECT job_id, next_start-last_finish as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:job_id;\n job_id | until_next | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------+------------------+------------+-----------------+----------------+---------------\n   1000 | @ 12 hours | t                |          1 |               1 |              0 |             0\n\n--clear log for next run of scheduler.\nTRUNCATE public.bgw_log;\nCREATE FUNCTION wait_for_timer_to_run(started_at INTEGER, spins INTEGER=:TEST_SPINWAIT_ITERS) RETURNS BOOLEAN LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n    num_runs INTEGER;\n    message TEXT;\nBEGIN\n    select format('[TESTING] Wait until %%, started at %s', started_at) into message;\n    FOR i in 1..spins\n    LOOP\n    SELECT COUNT(*) from bgw_log where msg LIKE message INTO num_runs;\n    if (num_runs > 0) THEN\n        RETURN true;\n    ELSE\n        PERFORM pg_sleep(0.1);\n    END IF;\n    END LOOP;\n    RETURN false;\nEND\n$BODY$;\n--make sure there is 1 job to start with\nSELECT test.wait_for_job_to_run(:job_id, 1);\n wait_for_job_to_run \n---------------------\n t\n\nSELECT ts_bgw_params_mock_wait_returns_immediately(:WAIT_FOR_OTHER_TO_ADVANCE);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\n--start the scheduler on 0 time\nSELECT ts_bgw_params_reset_time(0, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run(extract(epoch from interval '24 hour')::int * 1000, 0);\n ts_bgw_db_scheduler_test_run \n------------------------------\n \n\nSELECT wait_for_timer_to_run(0);\n wait_for_timer_to_run \n-----------------------\n t\n\n--advance to 12:00 so that it runs one more time; now we know the\n--scheduler has loaded up the job with the old schedule_interval\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '12 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT test.wait_for_job_to_run(:job_id, 2);\n wait_for_job_to_run \n---------------------\n t\n\n--advance clock 1us to make the scheduler realize the job is done\nSELECT ts_bgw_params_reset_time((extract(epoch from interval '12 hour')::bigint * 1000000)+1, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\n--alter the refresh interval and check if next_start is altered\nSELECT alter_job(:job_id, schedule_interval => '1m', retry_period => '1m');\n                                                                                                                           alter_job                                                                                                                            \n----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1000,\"@ 1 min\",\"@ 0\",-1,\"@ 1 min\",t,\"{\"\"end_offset\"\": 4, \"\"start_offset\"\": null, \"\"mat_hypertable_id\"\": 2}\",\"Sat Jan 01 04:01:00 2000 PST\",_timescaledb_functions.policy_refresh_continuous_aggregate_check,f,,,\"Refresh Continuous Aggregate Policy [1000]\")\n\nSELECT job_id, next_start - last_finish as until_next, total_runs\nFROM _timescaledb_internal.bgw_job_stat\nWHERE job_id=:job_id;;\n job_id | until_next | total_runs \n--------+------------+------------\n   1000 | @ 1 min    |          2\n\n--advance to 12:02, job should have run at 12:01\nSELECT ts_bgw_params_reset_time((extract(epoch from interval '12 hour')::bigint * 1000000)+(extract(epoch from interval '2 minute')::bigint * 1000000), true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT test.wait_for_job_to_run(:job_id, 3);\n wait_for_job_to_run \n---------------------\n t\n\n--next run in 1 minute\nSELECT job_id, next_start-last_finish as until_next, total_runs\nFROM _timescaledb_internal.bgw_job_stat\nWHERE job_id=:job_id;\n job_id | until_next | total_runs \n--------+------------+------------\n   1000 | @ 1 min    |          3\n\n--change next run to be after 30s instead\nSELECT (next_start - '30s'::interval) AS \"NEW_NEXT_START\"\nFROM _timescaledb_internal.bgw_job_stat\nWHERE job_id=:job_id \\gset\nSELECT alter_job(:job_id, next_start => :'NEW_NEXT_START');\n                                                                                                                           alter_job                                                                                                                            \n----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1000,\"@ 1 min\",\"@ 0\",-1,\"@ 1 min\",t,\"{\"\"end_offset\"\": 4, \"\"start_offset\"\": null, \"\"mat_hypertable_id\"\": 2}\",\"Sat Jan 01 04:02:30 2000 PST\",_timescaledb_functions.policy_refresh_continuous_aggregate_check,f,,,\"Refresh Continuous Aggregate Policy [1000]\")\n\nSELECT ts_bgw_params_reset_time((extract(epoch from interval '12 hour')::bigint * 1000000)+(extract(epoch from interval '2 minute 30 seconds')::bigint * 1000000), true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT test.wait_for_job_to_run(:job_id, 4);\n wait_for_job_to_run \n---------------------\n t\n\n--advance clock to quit scheduler\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '25 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nselect ts_bgw_db_scheduler_test_wait_for_scheduler_finish();\n ts_bgw_db_scheduler_test_wait_for_scheduler_finish \n----------------------------------------------------\n \n\nSELECT ts_bgw_params_mock_wait_returns_immediately(:WAIT_ON_JOB);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\nTRUNCATE public.bgw_log;\n-- data before 8\nSELECT * FROM test_continuous_agg_view ORDER BY 1;\n time_bucket | value \n-------------+-------\n           0 |     1\n           2 |     5\n           4 |     9\n\n-- invalidations test by running job multiple times\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nDROP MATERIALIZED VIEW test_continuous_agg_view;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_2_3_chunk\nCREATE MATERIALIZED VIEW test_continuous_agg_view\n    WITH (timescaledb.continuous,\n        timescaledb.materialized_only=true)\n    AS SELECT time_bucket('2', time), SUM(data) as value\n        FROM test_continuous_agg_table\n        GROUP BY 1 WITH NO DATA;\nSELECT add_continuous_aggregate_policy('test_continuous_agg_view', 100::integer, -2::integer, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1001\n\nSELECT mat_hypertable_id FROM _timescaledb_catalog.continuous_agg \\gset\nSELECT id AS job_id FROM _timescaledb_catalog.bgw_job WHERE hypertable_id=:mat_hypertable_id \\gset\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | mock_time |              application_name              |                                                            msg                                                            \n--------+-----------+--------------------------------------------+---------------------------------------------------------------------------------------------------------------------------\n      0 |         0 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 |         0 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 |         0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ 10, 12 ] (batch 1 of 2)\n      1 |         0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      2 |         0 | Refresh Continuous Aggregate Policy [1001] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      3 |         0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ -10, 10 ] (batch 2 of 2)\n      4 |         0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      5 |         0 | Refresh Continuous Aggregate Policy [1001] | inserted 5 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n\n-- job ran once, successfully\nSELECT job_id, last_finish - next_start as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:job_id;\n job_id |   until_next   | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+----------------+------------------+------------+-----------------+----------------+---------------\n   1001 | @ 12 hours ago | t                |          1 |               1 |              0 |             0\n\n-- should have refreshed everything we have so far\nSELECT * FROM test_continuous_agg_view ORDER BY 1;\n time_bucket | value \n-------------+-------\n           0 |     1\n           2 |     5\n           4 |     9\n           6 |    13\n           8 |    17\n          10 |    10\n\n-- invalidate some data\nUPDATE test_continuous_agg_table\nSET data = 11 WHERE time = 6;\n--advance time by 12h so that job runs one more time\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '12 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25, 25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no |  mock_time  |              application_name              |                                                            msg                                                            \n--------+-------------+--------------------------------------------+---------------------------------------------------------------------------------------------------------------------------\n      0 |           0 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 |           0 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 |           0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ 10, 12 ] (batch 1 of 2)\n      1 |           0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      2 |           0 | Refresh Continuous Aggregate Policy [1001] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      3 |           0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ -10, 10 ] (batch 2 of 2)\n      4 |           0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      5 |           0 | Refresh Continuous Aggregate Policy [1001] | inserted 5 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      0 | 43200000000 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 | 43200000000 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ -90, -10 ]\n      1 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      2 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | inserted 0 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      3 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ 6, 8 ]\n      4 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | deleted 1 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      5 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n\nSELECT job_id, next_start - last_finish as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:job_id;\n job_id | until_next | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------+------------------+------------+-----------------+----------------+---------------\n   1001 | @ 12 hours | t                |          2 |               2 |              0 |             0\n\n-- should have updated data for time=6\nSELECT * FROM test_continuous_agg_view ORDER BY 1;\n time_bucket | value \n-------------+-------\n           0 |     1\n           2 |     5\n           4 |     9\n           6 |    18\n           8 |    17\n          10 |    10\n\n\\x on\n--check the information views --\nselect view_name, view_owner, materialization_hypertable_schema, materialization_hypertable_name\nfrom timescaledb_information.continuous_aggregates\nwhere view_name::text like '%test_continuous_agg_view';\n-[ RECORD 1 ]---------------------+---------------------------\nview_name                         | test_continuous_agg_view\nview_owner                        | default_perm_user\nmaterialization_hypertable_schema | _timescaledb_internal\nmaterialization_hypertable_name   | _materialized_hypertable_3\n\nselect view_name, view_definition from timescaledb_information.continuous_aggregates\nwhere view_name::text like '%test_continuous_agg_view';\n-[ RECORD 1 ]---+-----------------------------------------------\nview_name       | test_continuous_agg_view\nview_definition |  SELECT time_bucket(2, \"time\") AS time_bucket,+\n                |     sum(data) AS value                        +\n                |    FROM test_continuous_agg_table             +\n                |   GROUP BY (time_bucket(2, \"time\"));\n\nselect job_status, last_run_duration\nfrom timescaledb_information.job_stats ps, timescaledb_information.continuous_aggregates cagg\nwhere cagg.view_name::text like '%test_continuous_agg_view'\nand cagg.materialization_hypertable_name = ps.hypertable_name;\n-[ RECORD 1 ]-----+----------\njob_status        | Scheduled\nlast_run_duration | \n\n\\x off\n-- test merged refresh (change data in two chunks)\nUPDATE test_continuous_agg_table SET data = 11;\n--advance time by 1day so that job runs one more time\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '1day')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(50, 50);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no |  mock_time  |              application_name              |                                                            msg                                                            \n--------+-------------+--------------------------------------------+---------------------------------------------------------------------------------------------------------------------------\n      0 |           0 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 |           0 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 |           0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ 10, 12 ] (batch 1 of 2)\n      1 |           0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      2 |           0 | Refresh Continuous Aggregate Policy [1001] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      3 |           0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ -10, 10 ] (batch 2 of 2)\n      4 |           0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      5 |           0 | Refresh Continuous Aggregate Policy [1001] | inserted 5 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      0 | 43200000000 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 | 43200000000 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ -90, -10 ]\n      1 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      2 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | inserted 0 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      3 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ 6, 8 ]\n      4 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | deleted 1 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      5 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      0 | 86400000000 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 | 86400000000 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | 86400000000 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ 0, 12 ]\n      1 | 86400000000 | Refresh Continuous Aggregate Policy [1001] | deleted 6 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      2 | 86400000000 | Refresh Continuous Aggregate Policy [1001] | inserted 6 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n\nDROP MATERIALIZED VIEW test_continuous_agg_view;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_3_4_chunk\n--create a view with a function that it has no permission to execute\nCREATE MATERIALIZED VIEW test_continuous_agg_view\n    WITH (timescaledb.continuous,\n        timescaledb.materialized_only=true)\n    AS SELECT time_bucket('2', time), SUM(data) as value, get_constant_no_perms()\n        FROM test_continuous_agg_table\n        GROUP BY 1 WITH NO DATA;\nSELECT add_continuous_aggregate_policy('test_continuous_agg_view', 100::integer, -2::integer, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1002\n\nSELECT id AS job_id FROM _timescaledb_catalog.bgw_job ORDER BY id desc limit 1 \\gset\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\n-- job fails\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:job_id;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1002 | f                |          1 |               0 |              1 |             0\n\nDROP MATERIALIZED VIEW test_continuous_agg_view;\n--advance clock to quit scheduler\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '25 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nselect ts_bgw_db_scheduler_test_wait_for_scheduler_finish();\n ts_bgw_db_scheduler_test_wait_for_scheduler_finish \n----------------------------------------------------\n \n\nSELECT ts_bgw_params_mock_wait_returns_immediately(:WAIT_ON_JOB);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\n--clear log for next run of the scheduler\nTRUNCATE public.bgw_log;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\n--\n-- Test creating continuous aggregate with a user that is the non-owner of the raw table\n--\nCREATE TABLE test_continuous_agg_table_w_grant(time int, data int);\nSELECT create_hypertable('test_continuous_agg_table_w_grant', 'time', chunk_time_interval => 10);\n               create_hypertable                \n------------------------------------------------\n (5,public,test_continuous_agg_table_w_grant,t)\n\nCREATE OR REPLACE FUNCTION integer_now_test1() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), 0) FROM test_continuous_agg_table_w_grant $$;\nSELECT set_integer_now_func('test_continuous_agg_table_w_grant', 'integer_now_test1');\n set_integer_now_func \n----------------------\n \n\nGRANT SELECT, TRIGGER ON test_continuous_agg_table_w_grant TO public;\nINSERT INTO test_continuous_agg_table_w_grant\n    SELECT 1 , 1;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n-- make sure view can be created\nCREATE MATERIALIZED VIEW test_continuous_agg_view_user_2\n    WITH ( timescaledb.continuous,\n        timescaledb.materialized_only=true)\n    AS SELECT time_bucket('2', time), SUM(data) as value\n        FROM test_continuous_agg_table_w_grant\n        GROUP BY 1 WITH NO DATA;\nSELECT add_continuous_aggregate_policy('test_continuous_agg_view_user_2', NULL, -2::integer, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1003\n\nSELECT id AS job_id FROM _timescaledb_catalog.bgw_job ORDER BY id desc limit 1 \\gset\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT id, owner FROM _timescaledb_catalog.bgw_job WHERE id = :job_id ;\n  id  |        owner        \n------+---------------------\n 1003 | default_perm_user_2\n\nSELECT job_id, next_start - last_finish as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:job_id;\n job_id | until_next | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------+------------------+------------+-----------------+----------------+---------------\n   1003 | @ 12 hours | t                |          1 |               1 |              0 |             0\n\n--view is populated\nSELECT * FROM test_continuous_agg_view_user_2 ORDER BY 1;\n time_bucket | value \n-------------+-------\n           0 |     1\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n--revoke permissions from the continuous agg view owner to select from raw table\n--no further updates to cont agg should happen\nREVOKE SELECT ON test_continuous_agg_table_w_grant FROM public;\n--add new data to table\nINSERT INTO test_continuous_agg_table_w_grant VALUES(5,1);\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n--advance time by 12h so that job tries to run one more time\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '12 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25, 25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\n--should show a failing execution because no longer has permissions (due to lack of permission on partial view owner's part)\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:job_id;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1003 | f                |          2 |               1 |              1 |             0\n\n--view was NOT updated; but the old stuff is still there\nSELECT * FROM test_continuous_agg_view_user_2;\n time_bucket | value \n-------------+-------\n           0 |     1\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT * from sorted_bgw_log;\n msg_no |  mock_time  |              application_name              |                                                           msg                                                            \n--------+-------------+--------------------------------------------+--------------------------------------------------------------------------------------------------------------------------\n      0 |           0 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 |           0 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 |           0 | Refresh Continuous Aggregate Policy [1003] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view_user_2\" in window [ -2147483648, 2 ]\n      1 |           0 | Refresh Continuous Aggregate Policy [1003] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n      2 |           0 | Refresh Continuous Aggregate Policy [1003] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n      0 | 43200000000 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 | 43200000000 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | 43200000000 | Refresh Continuous Aggregate Policy [1003] | job 1003 threw an error\n      1 | 43200000000 | Refresh Continuous Aggregate Policy [1003] | permission denied for table test_continuous_agg_table_w_grant\n\n-- Count the number of continuous aggregate policies\nSELECT count(*) FROM _timescaledb_catalog.bgw_job\n WHERE proc_schema = '_timescaledb_functions'\n   AND proc_name = 'policy_refresh_continuous_aggregate';\n count \n-------\n     1\n\n"
  },
  {
    "path": "tsl/test/expected/cagg_bgw-17.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n--\n-- Setup\n--\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(timeout INT = -1, mock_start_time INT = 0) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_db_scheduler_test_run(timeout INT = -1, mock_start_time INT = 0) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_db_scheduler_test_wait_for_scheduler_finish() RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_params_create() RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_params_destroy() RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_params_reset_time(set_time BIGINT = 0, wait BOOLEAN = false) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\n--test that this all works under the community license\nALTER DATABASE :TEST_DBNAME SET timescaledb.license_key='Community';\n--create a function with no permissions to execute\nCREATE FUNCTION get_constant_no_perms() RETURNS INTEGER LANGUAGE SQL IMMUTABLE AS\n$BODY$\n    SELECT 10;\n$BODY$;\nREVOKE EXECUTE ON FUNCTION get_constant_no_perms() FROM PUBLIC;\n\\set WAIT_ON_JOB 0\n\\set IMMEDIATELY_SET_UNTIL 1\n\\set WAIT_FOR_OTHER_TO_ADVANCE 2\nCREATE OR REPLACE FUNCTION ts_bgw_params_mock_wait_returns_immediately(new_val INTEGER) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\n-- Remove any default jobs, e.g., telemetry\nDELETE FROM _timescaledb_catalog.bgw_job WHERE TRUE;\nTRUNCATE _timescaledb_internal.bgw_job_stat;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nCREATE TABLE public.bgw_log(\n    msg_no INT,\n    mock_time BIGINT,\n    application_name TEXT,\n    msg TEXT\n);\nCREATE VIEW sorted_bgw_log AS\n    SELECT msg_no,\n    \t   mock_time,\n    \t   application_name,\n    \t   regexp_replace(regexp_replace(msg, '(Wait until|started at|execution time) [0-9]+(\\.[0-9]+)?', '\\1 (RANDOM)', 'g'), 'background worker \"[^\"]+\"','connection') AS msg\n\t   FROM bgw_log ORDER BY mock_time, application_name COLLATE \"C\", msg_no;\nCREATE TABLE public.bgw_dsm_handle_store(\n    handle BIGINT\n);\nINSERT INTO public.bgw_dsm_handle_store VALUES (0);\nSELECT ts_bgw_params_create();\n ts_bgw_params_create \n----------------------\n \n\nSELECT * FROM _timescaledb_catalog.bgw_job;\n id | application_name | schedule_interval | max_runtime | max_retries | retry_period | proc_schema | proc_name | owner | scheduled | fixed_schedule | initial_start | hypertable_id | config | check_schema | check_name | timezone \n----+------------------+-------------------+-------------+-------------+--------------+-------------+-----------+-------+-----------+----------------+---------------+---------------+--------+--------------+------------+----------\n\nSELECT * FROM timescaledb_information.job_stats;\n hypertable_schema | hypertable_name | job_id | last_run_started_at | last_successful_finish | last_run_status | job_status | last_run_duration | next_start | total_runs | total_successes | total_failures \n-------------------+-----------------+--------+---------------------+------------------------+-----------------+------------+-------------------+------------+------------+-----------------+----------------\n\nSELECT * FROM _timescaledb_catalog.continuous_agg;\n mat_hypertable_id | raw_hypertable_id | parent_mat_hypertable_id | user_view_schema | user_view_name | partial_view_schema | partial_view_name | direct_view_schema | direct_view_name | materialized_only \n-------------------+-------------------+--------------------------+------------------+----------------+---------------------+-------------------+--------------------+------------------+-------------------\n\n-- though user on access node has required GRANTS, this will propagate GRANTS to the connected data nodes\nGRANT CREATE ON SCHEMA public TO :ROLE_DEFAULT_PERM_USER;\nWARNING:  no privileges were granted for \"public\"\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nCREATE TABLE test_continuous_agg_table(time int, data int);\nSELECT create_hypertable('test_continuous_agg_table', 'time', chunk_time_interval => 10);\n           create_hypertable            \n----------------------------------------\n (1,public,test_continuous_agg_table,t)\n\nCREATE OR REPLACE FUNCTION integer_now_test() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), 0) FROM test_continuous_agg_table $$;\nSELECT set_integer_now_func('test_continuous_agg_table', 'integer_now_test');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW test_continuous_agg_view\n    WITH (timescaledb.continuous, timescaledb.materialized_only=true)\n    AS SELECT time_bucket('2', time), SUM(data) as value\n        FROM test_continuous_agg_table\n        GROUP BY 1 WITH NO DATA;\nSELECT add_continuous_aggregate_policy('test_continuous_agg_view', NULL, 4::integer, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1000\n\nSELECT id as raw_table_id FROM _timescaledb_catalog.hypertable WHERE table_name='test_continuous_agg_table' \\gset\n-- min distance from end should be 1\nSELECT mat_hypertable_id, user_view_schema, user_view_name FROM _timescaledb_catalog.continuous_agg;\n mat_hypertable_id | user_view_schema |      user_view_name      \n-------------------+------------------+--------------------------\n                 2 | public           | test_continuous_agg_view\n\nSELECT mat_hypertable_id, bucket_width FROM _timescaledb_catalog.continuous_aggs_bucket_function;\n mat_hypertable_id | bucket_width \n-------------------+--------------\n                 2 | 2\n\nSELECT mat_hypertable_id FROM _timescaledb_catalog.continuous_agg \\gset\nSELECT id AS job_id FROM _timescaledb_catalog.bgw_job where hypertable_id=:mat_hypertable_id \\gset\n-- job was created\nSELECT * FROM _timescaledb_catalog.bgw_job where hypertable_id=:mat_hypertable_id;\n  id  |              application_name              | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema       |              proc_name              |       owner       | scheduled | fixed_schedule | initial_start | hypertable_id |                             config                              |      check_schema      |                check_name                 | timezone \n------+--------------------------------------------+-------------------+-------------+-------------+--------------+------------------------+-------------------------------------+-------------------+-----------+----------------+---------------+---------------+-----------------------------------------------------------------+------------------------+-------------------------------------------+----------\n 1000 | Refresh Continuous Aggregate Policy [1000] | @ 12 hours        | @ 0         |          -1 | @ 12 hours   | _timescaledb_functions | policy_refresh_continuous_aggregate | default_perm_user | t         | f              |               |             2 | {\"end_offset\": 4, \"start_offset\": null, \"mat_hypertable_id\": 2} | _timescaledb_functions | policy_refresh_continuous_aggregate_check | \n\n-- create 10 time buckets\nINSERT INTO test_continuous_agg_table\n    SELECT i, i FROM\n        (SELECT generate_series(0, 10) as i) AS j;\n-- no stats\nSELECT job_id, next_start, last_finish as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    ORDER BY job_id;\n job_id | next_start | until_next | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------+------------+------------------+------------+-----------------+----------------+---------------\n\n-- no data in view\nSELECT * FROM test_continuous_agg_view ORDER BY 1;\n time_bucket | value \n-------------+-------\n\n-- run first time\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | mock_time |              application_name              |                                                        msg                                                        \n--------+-----------+--------------------------------------------+-------------------------------------------------------------------------------------------------------------------\n      0 |         0 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 |         0 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 |         0 | Refresh Continuous Aggregate Policy [1000] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ -2147483648, 6 ]\n      1 |         0 | Refresh Continuous Aggregate Policy [1000] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      2 |         0 | Refresh Continuous Aggregate Policy [1000] | inserted 3 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n\nSELECT * FROM _timescaledb_catalog.bgw_job where id=:job_id;\n  id  |              application_name              | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema       |              proc_name              |       owner       | scheduled | fixed_schedule | initial_start | hypertable_id |                             config                              |      check_schema      |                check_name                 | timezone \n------+--------------------------------------------+-------------------+-------------+-------------+--------------+------------------------+-------------------------------------+-------------------+-----------+----------------+---------------+---------------+-----------------------------------------------------------------+------------------------+-------------------------------------------+----------\n 1000 | Refresh Continuous Aggregate Policy [1000] | @ 12 hours        | @ 0         |          -1 | @ 12 hours   | _timescaledb_functions | policy_refresh_continuous_aggregate | default_perm_user | t         | f              |               |             2 | {\"end_offset\": 4, \"start_offset\": null, \"mat_hypertable_id\": 2} | _timescaledb_functions | policy_refresh_continuous_aggregate_check | \n\n-- job ran once, successfully\nSELECT job_id, next_start-last_finish as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:job_id;\n job_id | until_next | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------+------------------+------------+-----------------+----------------+---------------\n   1000 | @ 12 hours | t                |          1 |               1 |              0 |             0\n\n--clear log for next run of scheduler.\nTRUNCATE public.bgw_log;\nCREATE FUNCTION wait_for_timer_to_run(started_at INTEGER, spins INTEGER=:TEST_SPINWAIT_ITERS) RETURNS BOOLEAN LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n    num_runs INTEGER;\n    message TEXT;\nBEGIN\n    select format('[TESTING] Wait until %%, started at %s', started_at) into message;\n    FOR i in 1..spins\n    LOOP\n    SELECT COUNT(*) from bgw_log where msg LIKE message INTO num_runs;\n    if (num_runs > 0) THEN\n        RETURN true;\n    ELSE\n        PERFORM pg_sleep(0.1);\n    END IF;\n    END LOOP;\n    RETURN false;\nEND\n$BODY$;\n--make sure there is 1 job to start with\nSELECT test.wait_for_job_to_run(:job_id, 1);\n wait_for_job_to_run \n---------------------\n t\n\nSELECT ts_bgw_params_mock_wait_returns_immediately(:WAIT_FOR_OTHER_TO_ADVANCE);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\n--start the scheduler on 0 time\nSELECT ts_bgw_params_reset_time(0, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run(extract(epoch from interval '24 hour')::int * 1000, 0);\n ts_bgw_db_scheduler_test_run \n------------------------------\n \n\nSELECT wait_for_timer_to_run(0);\n wait_for_timer_to_run \n-----------------------\n t\n\n--advance to 12:00 so that it runs one more time; now we know the\n--scheduler has loaded up the job with the old schedule_interval\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '12 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT test.wait_for_job_to_run(:job_id, 2);\n wait_for_job_to_run \n---------------------\n t\n\n--advance clock 1us to make the scheduler realize the job is done\nSELECT ts_bgw_params_reset_time((extract(epoch from interval '12 hour')::bigint * 1000000)+1, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\n--alter the refresh interval and check if next_start is altered\nSELECT alter_job(:job_id, schedule_interval => '1m', retry_period => '1m');\n                                                                                                                           alter_job                                                                                                                            \n----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1000,\"@ 1 min\",\"@ 0\",-1,\"@ 1 min\",t,\"{\"\"end_offset\"\": 4, \"\"start_offset\"\": null, \"\"mat_hypertable_id\"\": 2}\",\"Sat Jan 01 04:01:00 2000 PST\",_timescaledb_functions.policy_refresh_continuous_aggregate_check,f,,,\"Refresh Continuous Aggregate Policy [1000]\")\n\nSELECT job_id, next_start - last_finish as until_next, total_runs\nFROM _timescaledb_internal.bgw_job_stat\nWHERE job_id=:job_id;;\n job_id | until_next | total_runs \n--------+------------+------------\n   1000 | @ 1 min    |          2\n\n--advance to 12:02, job should have run at 12:01\nSELECT ts_bgw_params_reset_time((extract(epoch from interval '12 hour')::bigint * 1000000)+(extract(epoch from interval '2 minute')::bigint * 1000000), true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT test.wait_for_job_to_run(:job_id, 3);\n wait_for_job_to_run \n---------------------\n t\n\n--next run in 1 minute\nSELECT job_id, next_start-last_finish as until_next, total_runs\nFROM _timescaledb_internal.bgw_job_stat\nWHERE job_id=:job_id;\n job_id | until_next | total_runs \n--------+------------+------------\n   1000 | @ 1 min    |          3\n\n--change next run to be after 30s instead\nSELECT (next_start - '30s'::interval) AS \"NEW_NEXT_START\"\nFROM _timescaledb_internal.bgw_job_stat\nWHERE job_id=:job_id \\gset\nSELECT alter_job(:job_id, next_start => :'NEW_NEXT_START');\n                                                                                                                           alter_job                                                                                                                            \n----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1000,\"@ 1 min\",\"@ 0\",-1,\"@ 1 min\",t,\"{\"\"end_offset\"\": 4, \"\"start_offset\"\": null, \"\"mat_hypertable_id\"\": 2}\",\"Sat Jan 01 04:02:30 2000 PST\",_timescaledb_functions.policy_refresh_continuous_aggregate_check,f,,,\"Refresh Continuous Aggregate Policy [1000]\")\n\nSELECT ts_bgw_params_reset_time((extract(epoch from interval '12 hour')::bigint * 1000000)+(extract(epoch from interval '2 minute 30 seconds')::bigint * 1000000), true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT test.wait_for_job_to_run(:job_id, 4);\n wait_for_job_to_run \n---------------------\n t\n\n--advance clock to quit scheduler\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '25 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nselect ts_bgw_db_scheduler_test_wait_for_scheduler_finish();\n ts_bgw_db_scheduler_test_wait_for_scheduler_finish \n----------------------------------------------------\n \n\nSELECT ts_bgw_params_mock_wait_returns_immediately(:WAIT_ON_JOB);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\nTRUNCATE public.bgw_log;\n-- data before 8\nSELECT * FROM test_continuous_agg_view ORDER BY 1;\n time_bucket | value \n-------------+-------\n           0 |     1\n           2 |     5\n           4 |     9\n\n-- invalidations test by running job multiple times\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nDROP MATERIALIZED VIEW test_continuous_agg_view;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_2_3_chunk\nCREATE MATERIALIZED VIEW test_continuous_agg_view\n    WITH (timescaledb.continuous,\n        timescaledb.materialized_only=true)\n    AS SELECT time_bucket('2', time), SUM(data) as value\n        FROM test_continuous_agg_table\n        GROUP BY 1 WITH NO DATA;\nSELECT add_continuous_aggregate_policy('test_continuous_agg_view', 100::integer, -2::integer, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1001\n\nSELECT mat_hypertable_id FROM _timescaledb_catalog.continuous_agg \\gset\nSELECT id AS job_id FROM _timescaledb_catalog.bgw_job WHERE hypertable_id=:mat_hypertable_id \\gset\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | mock_time |              application_name              |                                                            msg                                                            \n--------+-----------+--------------------------------------------+---------------------------------------------------------------------------------------------------------------------------\n      0 |         0 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 |         0 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 |         0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ 10, 12 ] (batch 1 of 2)\n      1 |         0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      2 |         0 | Refresh Continuous Aggregate Policy [1001] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      3 |         0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ -10, 10 ] (batch 2 of 2)\n      4 |         0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      5 |         0 | Refresh Continuous Aggregate Policy [1001] | inserted 5 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n\n-- job ran once, successfully\nSELECT job_id, last_finish - next_start as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:job_id;\n job_id |   until_next   | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+----------------+------------------+------------+-----------------+----------------+---------------\n   1001 | @ 12 hours ago | t                |          1 |               1 |              0 |             0\n\n-- should have refreshed everything we have so far\nSELECT * FROM test_continuous_agg_view ORDER BY 1;\n time_bucket | value \n-------------+-------\n           0 |     1\n           2 |     5\n           4 |     9\n           6 |    13\n           8 |    17\n          10 |    10\n\n-- invalidate some data\nUPDATE test_continuous_agg_table\nSET data = 11 WHERE time = 6;\n--advance time by 12h so that job runs one more time\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '12 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25, 25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no |  mock_time  |              application_name              |                                                            msg                                                            \n--------+-------------+--------------------------------------------+---------------------------------------------------------------------------------------------------------------------------\n      0 |           0 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 |           0 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 |           0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ 10, 12 ] (batch 1 of 2)\n      1 |           0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      2 |           0 | Refresh Continuous Aggregate Policy [1001] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      3 |           0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ -10, 10 ] (batch 2 of 2)\n      4 |           0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      5 |           0 | Refresh Continuous Aggregate Policy [1001] | inserted 5 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      0 | 43200000000 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 | 43200000000 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ -90, -10 ]\n      1 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      2 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | inserted 0 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      3 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ 6, 8 ]\n      4 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | deleted 1 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      5 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n\nSELECT job_id, next_start - last_finish as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:job_id;\n job_id | until_next | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------+------------------+------------+-----------------+----------------+---------------\n   1001 | @ 12 hours | t                |          2 |               2 |              0 |             0\n\n-- should have updated data for time=6\nSELECT * FROM test_continuous_agg_view ORDER BY 1;\n time_bucket | value \n-------------+-------\n           0 |     1\n           2 |     5\n           4 |     9\n           6 |    18\n           8 |    17\n          10 |    10\n\n\\x on\n--check the information views --\nselect view_name, view_owner, materialization_hypertable_schema, materialization_hypertable_name\nfrom timescaledb_information.continuous_aggregates\nwhere view_name::text like '%test_continuous_agg_view';\n-[ RECORD 1 ]---------------------+---------------------------\nview_name                         | test_continuous_agg_view\nview_owner                        | default_perm_user\nmaterialization_hypertable_schema | _timescaledb_internal\nmaterialization_hypertable_name   | _materialized_hypertable_3\n\nselect view_name, view_definition from timescaledb_information.continuous_aggregates\nwhere view_name::text like '%test_continuous_agg_view';\n-[ RECORD 1 ]---+-----------------------------------------------\nview_name       | test_continuous_agg_view\nview_definition |  SELECT time_bucket(2, \"time\") AS time_bucket,+\n                |     sum(data) AS value                        +\n                |    FROM test_continuous_agg_table             +\n                |   GROUP BY (time_bucket(2, \"time\"));\n\nselect job_status, last_run_duration\nfrom timescaledb_information.job_stats ps, timescaledb_information.continuous_aggregates cagg\nwhere cagg.view_name::text like '%test_continuous_agg_view'\nand cagg.materialization_hypertable_name = ps.hypertable_name;\n-[ RECORD 1 ]-----+----------\njob_status        | Scheduled\nlast_run_duration | \n\n\\x off\n-- test merged refresh (change data in two chunks)\nUPDATE test_continuous_agg_table SET data = 11;\n--advance time by 1day so that job runs one more time\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '1day')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(50, 50);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no |  mock_time  |              application_name              |                                                            msg                                                            \n--------+-------------+--------------------------------------------+---------------------------------------------------------------------------------------------------------------------------\n      0 |           0 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 |           0 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 |           0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ 10, 12 ] (batch 1 of 2)\n      1 |           0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      2 |           0 | Refresh Continuous Aggregate Policy [1001] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      3 |           0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ -10, 10 ] (batch 2 of 2)\n      4 |           0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      5 |           0 | Refresh Continuous Aggregate Policy [1001] | inserted 5 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      0 | 43200000000 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 | 43200000000 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ -90, -10 ]\n      1 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      2 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | inserted 0 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      3 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ 6, 8 ]\n      4 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | deleted 1 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      5 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      0 | 86400000000 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 | 86400000000 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | 86400000000 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ 0, 12 ]\n      1 | 86400000000 | Refresh Continuous Aggregate Policy [1001] | deleted 6 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      2 | 86400000000 | Refresh Continuous Aggregate Policy [1001] | inserted 6 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n\nDROP MATERIALIZED VIEW test_continuous_agg_view;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_3_4_chunk\n--create a view with a function that it has no permission to execute\nCREATE MATERIALIZED VIEW test_continuous_agg_view\n    WITH (timescaledb.continuous,\n        timescaledb.materialized_only=true)\n    AS SELECT time_bucket('2', time), SUM(data) as value, get_constant_no_perms()\n        FROM test_continuous_agg_table\n        GROUP BY 1 WITH NO DATA;\nSELECT add_continuous_aggregate_policy('test_continuous_agg_view', 100::integer, -2::integer, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1002\n\nSELECT id AS job_id FROM _timescaledb_catalog.bgw_job ORDER BY id desc limit 1 \\gset\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\n-- job fails\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:job_id;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1002 | f                |          1 |               0 |              1 |             0\n\nDROP MATERIALIZED VIEW test_continuous_agg_view;\n--advance clock to quit scheduler\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '25 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nselect ts_bgw_db_scheduler_test_wait_for_scheduler_finish();\n ts_bgw_db_scheduler_test_wait_for_scheduler_finish \n----------------------------------------------------\n \n\nSELECT ts_bgw_params_mock_wait_returns_immediately(:WAIT_ON_JOB);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\n--clear log for next run of the scheduler\nTRUNCATE public.bgw_log;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\n--\n-- Test creating continuous aggregate with a user that is the non-owner of the raw table\n--\nCREATE TABLE test_continuous_agg_table_w_grant(time int, data int);\nSELECT create_hypertable('test_continuous_agg_table_w_grant', 'time', chunk_time_interval => 10);\n               create_hypertable                \n------------------------------------------------\n (5,public,test_continuous_agg_table_w_grant,t)\n\nCREATE OR REPLACE FUNCTION integer_now_test1() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), 0) FROM test_continuous_agg_table_w_grant $$;\nSELECT set_integer_now_func('test_continuous_agg_table_w_grant', 'integer_now_test1');\n set_integer_now_func \n----------------------\n \n\nGRANT SELECT, TRIGGER ON test_continuous_agg_table_w_grant TO public;\nINSERT INTO test_continuous_agg_table_w_grant\n    SELECT 1 , 1;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n-- make sure view can be created\nCREATE MATERIALIZED VIEW test_continuous_agg_view_user_2\n    WITH ( timescaledb.continuous,\n        timescaledb.materialized_only=true)\n    AS SELECT time_bucket('2', time), SUM(data) as value\n        FROM test_continuous_agg_table_w_grant\n        GROUP BY 1 WITH NO DATA;\nSELECT add_continuous_aggregate_policy('test_continuous_agg_view_user_2', NULL, -2::integer, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1003\n\nSELECT id AS job_id FROM _timescaledb_catalog.bgw_job ORDER BY id desc limit 1 \\gset\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT id, owner FROM _timescaledb_catalog.bgw_job WHERE id = :job_id ;\n  id  |        owner        \n------+---------------------\n 1003 | default_perm_user_2\n\nSELECT job_id, next_start - last_finish as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:job_id;\n job_id | until_next | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------+------------------+------------+-----------------+----------------+---------------\n   1003 | @ 12 hours | t                |          1 |               1 |              0 |             0\n\n--view is populated\nSELECT * FROM test_continuous_agg_view_user_2 ORDER BY 1;\n time_bucket | value \n-------------+-------\n           0 |     1\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n--revoke permissions from the continuous agg view owner to select from raw table\n--no further updates to cont agg should happen\nREVOKE SELECT ON test_continuous_agg_table_w_grant FROM public;\n--add new data to table\nINSERT INTO test_continuous_agg_table_w_grant VALUES(5,1);\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n--advance time by 12h so that job tries to run one more time\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '12 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25, 25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\n--should show a failing execution because no longer has permissions (due to lack of permission on partial view owner's part)\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:job_id;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1003 | f                |          2 |               1 |              1 |             0\n\n--view was NOT updated; but the old stuff is still there\nSELECT * FROM test_continuous_agg_view_user_2;\n time_bucket | value \n-------------+-------\n           0 |     1\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT * from sorted_bgw_log;\n msg_no |  mock_time  |              application_name              |                                                           msg                                                            \n--------+-------------+--------------------------------------------+--------------------------------------------------------------------------------------------------------------------------\n      0 |           0 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 |           0 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 |           0 | Refresh Continuous Aggregate Policy [1003] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view_user_2\" in window [ -2147483648, 2 ]\n      1 |           0 | Refresh Continuous Aggregate Policy [1003] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n      2 |           0 | Refresh Continuous Aggregate Policy [1003] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n      0 | 43200000000 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 | 43200000000 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | 43200000000 | Refresh Continuous Aggregate Policy [1003] | job 1003 threw an error\n      1 | 43200000000 | Refresh Continuous Aggregate Policy [1003] | permission denied for table test_continuous_agg_table_w_grant\n\n-- Count the number of continuous aggregate policies\nSELECT count(*) FROM _timescaledb_catalog.bgw_job\n WHERE proc_schema = '_timescaledb_functions'\n   AND proc_name = 'policy_refresh_continuous_aggregate';\n count \n-------\n     1\n\n"
  },
  {
    "path": "tsl/test/expected/cagg_bgw-18.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n--\n-- Setup\n--\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(timeout INT = -1, mock_start_time INT = 0) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_db_scheduler_test_run(timeout INT = -1, mock_start_time INT = 0) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_db_scheduler_test_wait_for_scheduler_finish() RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_params_create() RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_params_destroy() RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_params_reset_time(set_time BIGINT = 0, wait BOOLEAN = false) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\n--test that this all works under the community license\nALTER DATABASE :TEST_DBNAME SET timescaledb.license_key='Community';\n--create a function with no permissions to execute\nCREATE FUNCTION get_constant_no_perms() RETURNS INTEGER LANGUAGE SQL IMMUTABLE AS\n$BODY$\n    SELECT 10;\n$BODY$;\nREVOKE EXECUTE ON FUNCTION get_constant_no_perms() FROM PUBLIC;\n\\set WAIT_ON_JOB 0\n\\set IMMEDIATELY_SET_UNTIL 1\n\\set WAIT_FOR_OTHER_TO_ADVANCE 2\nCREATE OR REPLACE FUNCTION ts_bgw_params_mock_wait_returns_immediately(new_val INTEGER) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\n-- Remove any default jobs, e.g., telemetry\nDELETE FROM _timescaledb_catalog.bgw_job WHERE TRUE;\nTRUNCATE _timescaledb_internal.bgw_job_stat;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nCREATE TABLE public.bgw_log(\n    msg_no INT,\n    mock_time BIGINT,\n    application_name TEXT,\n    msg TEXT\n);\nCREATE VIEW sorted_bgw_log AS\n    SELECT msg_no,\n    \t   mock_time,\n    \t   application_name,\n    \t   regexp_replace(regexp_replace(msg, '(Wait until|started at|execution time) [0-9]+(\\.[0-9]+)?', '\\1 (RANDOM)', 'g'), 'background worker \"[^\"]+\"','connection') AS msg\n\t   FROM bgw_log ORDER BY mock_time, application_name COLLATE \"C\", msg_no;\nCREATE TABLE public.bgw_dsm_handle_store(\n    handle BIGINT\n);\nINSERT INTO public.bgw_dsm_handle_store VALUES (0);\nSELECT ts_bgw_params_create();\n ts_bgw_params_create \n----------------------\n \n\nSELECT * FROM _timescaledb_catalog.bgw_job;\n id | application_name | schedule_interval | max_runtime | max_retries | retry_period | proc_schema | proc_name | owner | scheduled | fixed_schedule | initial_start | hypertable_id | config | check_schema | check_name | timezone \n----+------------------+-------------------+-------------+-------------+--------------+-------------+-----------+-------+-----------+----------------+---------------+---------------+--------+--------------+------------+----------\n\nSELECT * FROM timescaledb_information.job_stats;\n hypertable_schema | hypertable_name | job_id | last_run_started_at | last_successful_finish | last_run_status | job_status | last_run_duration | next_start | total_runs | total_successes | total_failures \n-------------------+-----------------+--------+---------------------+------------------------+-----------------+------------+-------------------+------------+------------+-----------------+----------------\n\nSELECT * FROM _timescaledb_catalog.continuous_agg;\n mat_hypertable_id | raw_hypertable_id | parent_mat_hypertable_id | user_view_schema | user_view_name | partial_view_schema | partial_view_name | direct_view_schema | direct_view_name | materialized_only \n-------------------+-------------------+--------------------------+------------------+----------------+---------------------+-------------------+--------------------+------------------+-------------------\n\n-- though user on access node has required GRANTS, this will propagate GRANTS to the connected data nodes\nGRANT CREATE ON SCHEMA public TO :ROLE_DEFAULT_PERM_USER;\nWARNING:  no privileges were granted for \"public\"\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nCREATE TABLE test_continuous_agg_table(time int, data int);\nSELECT create_hypertable('test_continuous_agg_table', 'time', chunk_time_interval => 10);\n           create_hypertable            \n----------------------------------------\n (1,public,test_continuous_agg_table,t)\n\nCREATE OR REPLACE FUNCTION integer_now_test() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), 0) FROM test_continuous_agg_table $$;\nSELECT set_integer_now_func('test_continuous_agg_table', 'integer_now_test');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW test_continuous_agg_view\n    WITH (timescaledb.continuous, timescaledb.materialized_only=true)\n    AS SELECT time_bucket('2', time), SUM(data) as value\n        FROM test_continuous_agg_table\n        GROUP BY 1 WITH NO DATA;\nSELECT add_continuous_aggregate_policy('test_continuous_agg_view', NULL, 4::integer, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1000\n\nSELECT id as raw_table_id FROM _timescaledb_catalog.hypertable WHERE table_name='test_continuous_agg_table' \\gset\n-- min distance from end should be 1\nSELECT mat_hypertable_id, user_view_schema, user_view_name FROM _timescaledb_catalog.continuous_agg;\n mat_hypertable_id | user_view_schema |      user_view_name      \n-------------------+------------------+--------------------------\n                 2 | public           | test_continuous_agg_view\n\nSELECT mat_hypertable_id, bucket_width FROM _timescaledb_catalog.continuous_aggs_bucket_function;\n mat_hypertable_id | bucket_width \n-------------------+--------------\n                 2 | 2\n\nSELECT mat_hypertable_id FROM _timescaledb_catalog.continuous_agg \\gset\nSELECT id AS job_id FROM _timescaledb_catalog.bgw_job where hypertable_id=:mat_hypertable_id \\gset\n-- job was created\nSELECT * FROM _timescaledb_catalog.bgw_job where hypertable_id=:mat_hypertable_id;\n  id  |              application_name              | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema       |              proc_name              |       owner       | scheduled | fixed_schedule | initial_start | hypertable_id |                             config                              |      check_schema      |                check_name                 | timezone \n------+--------------------------------------------+-------------------+-------------+-------------+--------------+------------------------+-------------------------------------+-------------------+-----------+----------------+---------------+---------------+-----------------------------------------------------------------+------------------------+-------------------------------------------+----------\n 1000 | Refresh Continuous Aggregate Policy [1000] | @ 12 hours        | @ 0         |          -1 | @ 12 hours   | _timescaledb_functions | policy_refresh_continuous_aggregate | default_perm_user | t         | f              |               |             2 | {\"end_offset\": 4, \"start_offset\": null, \"mat_hypertable_id\": 2} | _timescaledb_functions | policy_refresh_continuous_aggregate_check | \n\n-- create 10 time buckets\nINSERT INTO test_continuous_agg_table\n    SELECT i, i FROM\n        (SELECT generate_series(0, 10) as i) AS j;\n-- no stats\nSELECT job_id, next_start, last_finish as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    ORDER BY job_id;\n job_id | next_start | until_next | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------+------------+------------------+------------+-----------------+----------------+---------------\n\n-- no data in view\nSELECT * FROM test_continuous_agg_view ORDER BY 1;\n time_bucket | value \n-------------+-------\n\n-- run first time\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | mock_time |              application_name              |                                                        msg                                                        \n--------+-----------+--------------------------------------------+-------------------------------------------------------------------------------------------------------------------\n      0 |         0 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 |         0 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 |         0 | Refresh Continuous Aggregate Policy [1000] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ -2147483648, 6 ]\n      1 |         0 | Refresh Continuous Aggregate Policy [1000] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      2 |         0 | Refresh Continuous Aggregate Policy [1000] | inserted 3 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n\nSELECT * FROM _timescaledb_catalog.bgw_job where id=:job_id;\n  id  |              application_name              | schedule_interval | max_runtime | max_retries | retry_period |      proc_schema       |              proc_name              |       owner       | scheduled | fixed_schedule | initial_start | hypertable_id |                             config                              |      check_schema      |                check_name                 | timezone \n------+--------------------------------------------+-------------------+-------------+-------------+--------------+------------------------+-------------------------------------+-------------------+-----------+----------------+---------------+---------------+-----------------------------------------------------------------+------------------------+-------------------------------------------+----------\n 1000 | Refresh Continuous Aggregate Policy [1000] | @ 12 hours        | @ 0         |          -1 | @ 12 hours   | _timescaledb_functions | policy_refresh_continuous_aggregate | default_perm_user | t         | f              |               |             2 | {\"end_offset\": 4, \"start_offset\": null, \"mat_hypertable_id\": 2} | _timescaledb_functions | policy_refresh_continuous_aggregate_check | \n\n-- job ran once, successfully\nSELECT job_id, next_start-last_finish as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:job_id;\n job_id | until_next | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------+------------------+------------+-----------------+----------------+---------------\n   1000 | @ 12 hours | t                |          1 |               1 |              0 |             0\n\n--clear log for next run of scheduler.\nTRUNCATE public.bgw_log;\nCREATE FUNCTION wait_for_timer_to_run(started_at INTEGER, spins INTEGER=:TEST_SPINWAIT_ITERS) RETURNS BOOLEAN LANGUAGE PLPGSQL AS\n$BODY$\nDECLARE\n    num_runs INTEGER;\n    message TEXT;\nBEGIN\n    select format('[TESTING] Wait until %%, started at %s', started_at) into message;\n    FOR i in 1..spins\n    LOOP\n    SELECT COUNT(*) from bgw_log where msg LIKE message INTO num_runs;\n    if (num_runs > 0) THEN\n        RETURN true;\n    ELSE\n        PERFORM pg_sleep(0.1);\n    END IF;\n    END LOOP;\n    RETURN false;\nEND\n$BODY$;\n--make sure there is 1 job to start with\nSELECT test.wait_for_job_to_run(:job_id, 1);\n wait_for_job_to_run \n---------------------\n t\n\nSELECT ts_bgw_params_mock_wait_returns_immediately(:WAIT_FOR_OTHER_TO_ADVANCE);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\n--start the scheduler on 0 time\nSELECT ts_bgw_params_reset_time(0, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run(extract(epoch from interval '24 hour')::int * 1000, 0);\n ts_bgw_db_scheduler_test_run \n------------------------------\n \n\nSELECT wait_for_timer_to_run(0);\n wait_for_timer_to_run \n-----------------------\n t\n\n--advance to 12:00 so that it runs one more time; now we know the\n--scheduler has loaded up the job with the old schedule_interval\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '12 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT test.wait_for_job_to_run(:job_id, 2);\n wait_for_job_to_run \n---------------------\n t\n\n--advance clock 1us to make the scheduler realize the job is done\nSELECT ts_bgw_params_reset_time((extract(epoch from interval '12 hour')::bigint * 1000000)+1, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\n--alter the refresh interval and check if next_start is altered\nSELECT alter_job(:job_id, schedule_interval => '1m', retry_period => '1m');\n                                                                                                                           alter_job                                                                                                                            \n----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1000,\"@ 1 min\",\"@ 0\",-1,\"@ 1 min\",t,\"{\"\"end_offset\"\": 4, \"\"start_offset\"\": null, \"\"mat_hypertable_id\"\": 2}\",\"Sat Jan 01 04:01:00 2000 PST\",_timescaledb_functions.policy_refresh_continuous_aggregate_check,f,,,\"Refresh Continuous Aggregate Policy [1000]\")\n\nSELECT job_id, next_start - last_finish as until_next, total_runs\nFROM _timescaledb_internal.bgw_job_stat\nWHERE job_id=:job_id;;\n job_id | until_next | total_runs \n--------+------------+------------\n   1000 | @ 1 min    |          2\n\n--advance to 12:02, job should have run at 12:01\nSELECT ts_bgw_params_reset_time((extract(epoch from interval '12 hour')::bigint * 1000000)+(extract(epoch from interval '2 minute')::bigint * 1000000), true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT test.wait_for_job_to_run(:job_id, 3);\n wait_for_job_to_run \n---------------------\n t\n\n--next run in 1 minute\nSELECT job_id, next_start-last_finish as until_next, total_runs\nFROM _timescaledb_internal.bgw_job_stat\nWHERE job_id=:job_id;\n job_id | until_next | total_runs \n--------+------------+------------\n   1000 | @ 1 min    |          3\n\n--change next run to be after 30s instead\nSELECT (next_start - '30s'::interval) AS \"NEW_NEXT_START\"\nFROM _timescaledb_internal.bgw_job_stat\nWHERE job_id=:job_id \\gset\nSELECT alter_job(:job_id, next_start => :'NEW_NEXT_START');\n                                                                                                                           alter_job                                                                                                                            \n----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1000,\"@ 1 min\",\"@ 0\",-1,\"@ 1 min\",t,\"{\"\"end_offset\"\": 4, \"\"start_offset\"\": null, \"\"mat_hypertable_id\"\": 2}\",\"Sat Jan 01 04:02:30 2000 PST\",_timescaledb_functions.policy_refresh_continuous_aggregate_check,f,,,\"Refresh Continuous Aggregate Policy [1000]\")\n\nSELECT ts_bgw_params_reset_time((extract(epoch from interval '12 hour')::bigint * 1000000)+(extract(epoch from interval '2 minute 30 seconds')::bigint * 1000000), true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT test.wait_for_job_to_run(:job_id, 4);\n wait_for_job_to_run \n---------------------\n t\n\n--advance clock to quit scheduler\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '25 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nselect ts_bgw_db_scheduler_test_wait_for_scheduler_finish();\n ts_bgw_db_scheduler_test_wait_for_scheduler_finish \n----------------------------------------------------\n \n\nSELECT ts_bgw_params_mock_wait_returns_immediately(:WAIT_ON_JOB);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\nTRUNCATE public.bgw_log;\n-- data before 8\nSELECT * FROM test_continuous_agg_view ORDER BY 1;\n time_bucket | value \n-------------+-------\n           0 |     1\n           2 |     5\n           4 |     9\n\n-- invalidations test by running job multiple times\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\nDROP MATERIALIZED VIEW test_continuous_agg_view;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_2_3_chunk\nCREATE MATERIALIZED VIEW test_continuous_agg_view\n    WITH (timescaledb.continuous,\n        timescaledb.materialized_only=true)\n    AS SELECT time_bucket('2', time), SUM(data) as value\n        FROM test_continuous_agg_table\n        GROUP BY 1 WITH NO DATA;\nSELECT add_continuous_aggregate_policy('test_continuous_agg_view', 100::integer, -2::integer, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1001\n\nSELECT mat_hypertable_id FROM _timescaledb_catalog.continuous_agg \\gset\nSELECT id AS job_id FROM _timescaledb_catalog.bgw_job WHERE hypertable_id=:mat_hypertable_id \\gset\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | mock_time |              application_name              |                                                            msg                                                            \n--------+-----------+--------------------------------------------+---------------------------------------------------------------------------------------------------------------------------\n      0 |         0 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 |         0 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 |         0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ 10, 12 ] (batch 1 of 2)\n      1 |         0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      2 |         0 | Refresh Continuous Aggregate Policy [1001] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      3 |         0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ -10, 10 ] (batch 2 of 2)\n      4 |         0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      5 |         0 | Refresh Continuous Aggregate Policy [1001] | inserted 5 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n\n-- job ran once, successfully\nSELECT job_id, last_finish - next_start as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:job_id;\n job_id |   until_next   | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+----------------+------------------+------------+-----------------+----------------+---------------\n   1001 | @ 12 hours ago | t                |          1 |               1 |              0 |             0\n\n-- should have refreshed everything we have so far\nSELECT * FROM test_continuous_agg_view ORDER BY 1;\n time_bucket | value \n-------------+-------\n           0 |     1\n           2 |     5\n           4 |     9\n           6 |    13\n           8 |    17\n          10 |    10\n\n-- invalidate some data\nUPDATE test_continuous_agg_table\nSET data = 11 WHERE time = 6;\n--advance time by 12h so that job runs one more time\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '12 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25, 25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no |  mock_time  |              application_name              |                                                            msg                                                            \n--------+-------------+--------------------------------------------+---------------------------------------------------------------------------------------------------------------------------\n      0 |           0 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 |           0 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 |           0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ 10, 12 ] (batch 1 of 2)\n      1 |           0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      2 |           0 | Refresh Continuous Aggregate Policy [1001] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      3 |           0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ -10, 10 ] (batch 2 of 2)\n      4 |           0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      5 |           0 | Refresh Continuous Aggregate Policy [1001] | inserted 5 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      0 | 43200000000 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 | 43200000000 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ -90, -10 ]\n      1 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      2 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | inserted 0 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      3 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ 6, 8 ]\n      4 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | deleted 1 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      5 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n\nSELECT job_id, next_start - last_finish as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:job_id;\n job_id | until_next | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------+------------------+------------+-----------------+----------------+---------------\n   1001 | @ 12 hours | t                |          2 |               2 |              0 |             0\n\n-- should have updated data for time=6\nSELECT * FROM test_continuous_agg_view ORDER BY 1;\n time_bucket | value \n-------------+-------\n           0 |     1\n           2 |     5\n           4 |     9\n           6 |    18\n           8 |    17\n          10 |    10\n\n\\x on\n--check the information views --\nselect view_name, view_owner, materialization_hypertable_schema, materialization_hypertable_name\nfrom timescaledb_information.continuous_aggregates\nwhere view_name::text like '%test_continuous_agg_view';\n-[ RECORD 1 ]---------------------+---------------------------\nview_name                         | test_continuous_agg_view\nview_owner                        | default_perm_user\nmaterialization_hypertable_schema | _timescaledb_internal\nmaterialization_hypertable_name   | _materialized_hypertable_3\n\nselect view_name, view_definition from timescaledb_information.continuous_aggregates\nwhere view_name::text like '%test_continuous_agg_view';\n-[ RECORD 1 ]---+-----------------------------------------------\nview_name       | test_continuous_agg_view\nview_definition |  SELECT time_bucket(2, \"time\") AS time_bucket,+\n                |     sum(data) AS value                        +\n                |    FROM test_continuous_agg_table             +\n                |   GROUP BY (time_bucket(2, \"time\"));\n\nselect job_status, last_run_duration\nfrom timescaledb_information.job_stats ps, timescaledb_information.continuous_aggregates cagg\nwhere cagg.view_name::text like '%test_continuous_agg_view'\nand cagg.materialization_hypertable_name = ps.hypertable_name;\n-[ RECORD 1 ]-----+----------\njob_status        | Scheduled\nlast_run_duration | \n\n\\x off\n-- test merged refresh (change data in two chunks)\nUPDATE test_continuous_agg_table SET data = 11;\n--advance time by 1day so that job runs one more time\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '1day')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(50, 50);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no |  mock_time  |              application_name              |                                                            msg                                                            \n--------+-------------+--------------------------------------------+---------------------------------------------------------------------------------------------------------------------------\n      0 |           0 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 |           0 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 |           0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ 10, 12 ] (batch 1 of 2)\n      1 |           0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      2 |           0 | Refresh Continuous Aggregate Policy [1001] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      3 |           0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ -10, 10 ] (batch 2 of 2)\n      4 |           0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      5 |           0 | Refresh Continuous Aggregate Policy [1001] | inserted 5 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      0 | 43200000000 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 | 43200000000 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ -90, -10 ]\n      1 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      2 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | inserted 0 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      3 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ 6, 8 ]\n      4 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | deleted 1 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      5 | 43200000000 | Refresh Continuous Aggregate Policy [1001] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      0 | 86400000000 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 | 86400000000 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | 86400000000 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view\" in window [ 0, 12 ]\n      1 | 86400000000 | Refresh Continuous Aggregate Policy [1001] | deleted 6 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      2 | 86400000000 | Refresh Continuous Aggregate Policy [1001] | inserted 6 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n\nDROP MATERIALIZED VIEW test_continuous_agg_view;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_3_4_chunk\n--create a view with a function that it has no permission to execute\nCREATE MATERIALIZED VIEW test_continuous_agg_view\n    WITH (timescaledb.continuous,\n        timescaledb.materialized_only=true)\n    AS SELECT time_bucket('2', time), SUM(data) as value, get_constant_no_perms()\n        FROM test_continuous_agg_table\n        GROUP BY 1 WITH NO DATA;\nSELECT add_continuous_aggregate_policy('test_continuous_agg_view', 100::integer, -2::integer, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1002\n\nSELECT id AS job_id FROM _timescaledb_catalog.bgw_job ORDER BY id desc limit 1 \\gset\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\n-- job fails\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:job_id;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1002 | f                |          1 |               0 |              1 |             0\n\nDROP MATERIALIZED VIEW test_continuous_agg_view;\n--advance clock to quit scheduler\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '25 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nselect ts_bgw_db_scheduler_test_wait_for_scheduler_finish();\n ts_bgw_db_scheduler_test_wait_for_scheduler_finish \n----------------------------------------------------\n \n\nSELECT ts_bgw_params_mock_wait_returns_immediately(:WAIT_ON_JOB);\n ts_bgw_params_mock_wait_returns_immediately \n---------------------------------------------\n \n\n--clear log for next run of the scheduler\nTRUNCATE public.bgw_log;\nSELECT ts_bgw_params_reset_time();\n ts_bgw_params_reset_time \n--------------------------\n \n\n--\n-- Test creating continuous aggregate with a user that is the non-owner of the raw table\n--\nCREATE TABLE test_continuous_agg_table_w_grant(time int, data int);\nSELECT create_hypertable('test_continuous_agg_table_w_grant', 'time', chunk_time_interval => 10);\n               create_hypertable                \n------------------------------------------------\n (5,public,test_continuous_agg_table_w_grant,t)\n\nCREATE OR REPLACE FUNCTION integer_now_test1() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), 0) FROM test_continuous_agg_table_w_grant $$;\nSELECT set_integer_now_func('test_continuous_agg_table_w_grant', 'integer_now_test1');\n set_integer_now_func \n----------------------\n \n\nGRANT SELECT, TRIGGER ON test_continuous_agg_table_w_grant TO public;\nINSERT INTO test_continuous_agg_table_w_grant\n    SELECT 1 , 1;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n-- make sure view can be created\nCREATE MATERIALIZED VIEW test_continuous_agg_view_user_2\n    WITH ( timescaledb.continuous,\n        timescaledb.materialized_only=true)\n    AS SELECT time_bucket('2', time), SUM(data) as value\n        FROM test_continuous_agg_table_w_grant\n        GROUP BY 1 WITH NO DATA;\nSELECT add_continuous_aggregate_policy('test_continuous_agg_view_user_2', NULL, -2::integer, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1003\n\nSELECT id AS job_id FROM _timescaledb_catalog.bgw_job ORDER BY id desc limit 1 \\gset\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT id, owner FROM _timescaledb_catalog.bgw_job WHERE id = :job_id ;\n  id  |        owner        \n------+---------------------\n 1003 | default_perm_user_2\n\nSELECT job_id, next_start - last_finish as until_next, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:job_id;\n job_id | until_next | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------+------------------+------------+-----------------+----------------+---------------\n   1003 | @ 12 hours | t                |          1 |               1 |              0 |             0\n\n--view is populated\nSELECT * FROM test_continuous_agg_view_user_2 ORDER BY 1;\n time_bucket | value \n-------------+-------\n           0 |     1\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n--revoke permissions from the continuous agg view owner to select from raw table\n--no further updates to cont agg should happen\nREVOKE SELECT ON test_continuous_agg_table_w_grant FROM public;\n--add new data to table\nINSERT INTO test_continuous_agg_table_w_grant VALUES(5,1);\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n--advance time by 12h so that job tries to run one more time\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '12 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25, 25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\n--should show a failing execution because no longer has permissions (due to lack of permission on partial view owner's part)\nSELECT job_id, last_run_success, total_runs, total_successes, total_failures, total_crashes\n    FROM _timescaledb_internal.bgw_job_stat\n    where job_id=:job_id;\n job_id | last_run_success | total_runs | total_successes | total_failures | total_crashes \n--------+------------------+------------+-----------------+----------------+---------------\n   1003 | f                |          2 |               1 |              1 |             0\n\n--view was NOT updated; but the old stuff is still there\nSELECT * FROM test_continuous_agg_view_user_2;\n time_bucket | value \n-------------+-------\n           0 |     1\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT * from sorted_bgw_log;\n msg_no |  mock_time  |              application_name              |                                                           msg                                                            \n--------+-------------+--------------------------------------------+--------------------------------------------------------------------------------------------------------------------------\n      0 |           0 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 |           0 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 |           0 | Refresh Continuous Aggregate Policy [1003] | continuous aggregate refresh (individual invalidation) on \"test_continuous_agg_view_user_2\" in window [ -2147483648, 2 ]\n      1 |           0 | Refresh Continuous Aggregate Policy [1003] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n      2 |           0 | Refresh Continuous Aggregate Policy [1003] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n      0 | 43200000000 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 | 43200000000 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | 43200000000 | Refresh Continuous Aggregate Policy [1003] | job 1003 threw an error\n      1 | 43200000000 | Refresh Continuous Aggregate Policy [1003] | permission denied for table test_continuous_agg_table_w_grant\n\n-- Count the number of continuous aggregate policies\nSELECT count(*) FROM _timescaledb_catalog.bgw_job\n WHERE proc_schema = '_timescaledb_functions'\n   AND proc_name = 'policy_refresh_continuous_aggregate';\n count \n-------\n     1\n\n"
  },
  {
    "path": "tsl/test/expected/cagg_bgw_drop_chunks.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n--\n-- Setup for testing bgw jobs ---\n--\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(timeout INT = -1) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_params_create() RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\n\\set WAIT_ON_JOB 0\n\\set IMMEDIATELY_SET_UNTIL 1\n\\set WAIT_FOR_OTHER_TO_ADVANCE 2\n-- Remove any default jobs, e.g., telemetry\nDELETE FROM _timescaledb_catalog.bgw_job WHERE TRUE;\nTRUNCATE _timescaledb_internal.bgw_job_stat;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nCREATE TABLE public.bgw_log(\n    msg_no INT,\n    mock_time BIGINT,\n    application_name TEXT,\n    msg TEXT\n);\nCREATE VIEW sorted_bgw_log AS\n    SELECT * FROM bgw_log ORDER BY mock_time, application_name COLLATE \"C\", msg_no;\nCREATE TABLE public.bgw_dsm_handle_store(\n    handle BIGINT\n);\nINSERT INTO public.bgw_dsm_handle_store VALUES (0);\nSELECT ts_bgw_params_create();\n ts_bgw_params_create \n----------------------\n \n\n-----------------------------------\n-- test drop chunks policy runs for materialized hypertables created for\n-- cont. aggregates\n-----------------------------------\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nCREATE TABLE drop_chunks_table(time BIGINT, data INTEGER);\nSELECT hypertable_id AS drop_chunks_table_nid\n    FROM create_hypertable('drop_chunks_table', 'time', chunk_time_interval => 1) \\gset\nCREATE OR REPLACE FUNCTION integer_now_test2() returns bigint LANGUAGE SQL STABLE as $$ SELECT 40::bigint  $$;\nSELECT set_integer_now_func('drop_chunks_table', 'integer_now_test2');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW drop_chunks_view1 WITH (timescaledb.continuous)\nAS SELECT time_bucket('5', time), max(data)\n    FROM drop_chunks_table\n    GROUP BY 1 ORDER BY 1 WITH NO DATA;\n--raw hypertable will have 40 chunks and the mat. hypertable will have 2 and 4\n-- chunks respectively\nSELECT set_chunk_time_interval('_timescaledb_internal._materialized_hypertable_2', 10);\n set_chunk_time_interval \n-------------------------\n \n\n\\set ON_ERROR_STOP 0\nINSERT INTO drop_chunks_table SELECT i, i FROM generate_series(1, 39) AS i;\n\\set ON_ERROR_STOP 1\nCALL refresh_continuous_aggregate('drop_chunks_view1', NULL, NULL);\n--TEST1  specify drop chunks policy on mat. hypertable by\n-- directly does not work\n\\set ON_ERROR_STOP 0\nSELECT add_retention_policy( '_timescaledb_internal._materialized_hypertable_2', drop_after => -50) as drop_chunks_job_id1 \\gset\nERROR:  cannot add retention policy to materialized hypertable \"_materialized_hypertable_2\" \n\\set ON_ERROR_STOP 1\n--TEST2  specify drop chunks policy on cont. aggregate\n-- integer_now func on raw hypertable is used by the drop\n-- chunks policy\nSELECT hypertable_id, table_name, integer_now_func\nFROM _timescaledb_catalog.dimension d,  _timescaledb_catalog.hypertable ht\nWHERE ht.id = d.hypertable_id;\n hypertable_id |         table_name         | integer_now_func  \n---------------+----------------------------+-------------------\n             1 | drop_chunks_table          | integer_now_test2\n             2 | _materialized_hypertable_2 | \n\nSELECT chunk_name, range_start_integer, range_end_integer\nFROM timescaledb_information.chunks\nWHERE hypertable_name = '_materialized_hypertable_2' ORDER BY range_start_integer;\n    chunk_name     | range_start_integer | range_end_integer \n-------------------+---------------------+-------------------\n _hyper_2_40_chunk |                   0 |                10\n _hyper_2_41_chunk |                  10 |                20\n _hyper_2_42_chunk |                  20 |                30\n _hyper_2_43_chunk |                  30 |                40\n\nSELECT add_retention_policy( 'drop_chunks_view1', drop_after => 10) as drop_chunks_job_id1 \\gset\nSELECT alter_job(:drop_chunks_job_id1, schedule_interval => INTERVAL '1 second');\n                                                                                   alter_job                                                                                    \n--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1000,\"@ 1 sec\",\"@ 5 mins\",-1,\"@ 5 mins\",t,\"{\"\"drop_after\"\": 10, \"\"hypertable_id\"\": 2}\",-infinity,_timescaledb_functions.policy_retention_check,f,,,\"Retention Policy [1000]\")\n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(2000000);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT count(c) from show_chunks('drop_chunks_view1') as c ;\n count \n-------\n     1\n\nSELECT remove_retention_policy('drop_chunks_view1');\n remove_retention_policy \n-------------------------\n \n\n\\set ON_ERROR_STOP 0\nSELECT remove_retention_policy('unknown');\nERROR:  relation \"unknown\" does not exist at character 32\nSELECT remove_retention_policy('1');\nERROR:  relation is not a hypertable or continuous aggregate\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "tsl/test/expected/cagg_ddl-15.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- Set this variable to avoid using a hard-coded path each time query\n-- results are compared\n\\set QUERY_RESULT_TEST_EQUAL_RELPATH '../../../../test/sql/include/query_result_test_equal.sql'\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--DDL commands on continuous aggregates\nCREATE TABLE conditions (\n      timec        TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature integer  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      timemeasure TIMESTAMPTZ,\n      timeinterval INTERVAL\n);\nSELECT table_name FROM create_hypertable('conditions', 'timec');\n table_name \n------------\n conditions\n\n-- schema tests\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET timezone TO 'UTC+8';\n-- drop if the tablespace1 and/or tablespace2 exists\nSET client_min_messages TO error;\nDROP TABLESPACE IF EXISTS tablespace1;\nDROP TABLESPACE IF EXISTS tablespace2;\nRESET client_min_messages;\nCREATE TABLESPACE tablespace1 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE1_PATH;\nCREATE TABLESPACE tablespace2 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE2_PATH;\nCREATE SCHEMA rename_schema;\nGRANT ALL ON SCHEMA rename_schema TO :ROLE_DEFAULT_PERM_USER;\nCREATE SCHEMA test_schema AUTHORIZATION :ROLE_DEFAULT_PERM_USER;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nCREATE TABLE foo(time TIMESTAMPTZ NOT NULL, data INTEGER);\nSELECT create_hypertable('foo', 'time');\n create_hypertable \n-------------------\n (2,public,foo,t)\n\nCREATE MATERIALIZED VIEW rename_test_old\n  WITH ( timescaledb.continuous, timescaledb.materialized_only=true)\nAS SELECT time_bucket('1week', time), COUNT(data)\n    FROM foo\n    GROUP BY 1 WITH NO DATA;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name  |  partial_view_schema  | partial_view_name \n------------------+-----------------+-----------------------+-------------------\n public           | rename_test_old | _timescaledb_internal | _partial_view_3\n\nALTER TABLE rename_test_old RENAME TO rename_test;\nALTER TABLE rename_test SET SCHEMA test_schema;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name |  partial_view_schema  | partial_view_name \n------------------+----------------+-----------------------+-------------------\n test_schema      | rename_test    | _timescaledb_internal | _partial_view_3\n\nALTER MATERIALIZED VIEW test_schema.rename_test SET SCHEMA rename_schema;\nDROP SCHEMA test_schema;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name |  partial_view_schema  | partial_view_name \n------------------+----------------+-----------------------+-------------------\n rename_schema    | rename_test    | _timescaledb_internal | _partial_view_3\n\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\",\n       direct_view_name as \"DIR_VIEW_NAME\",\n       direct_view_schema as \"DIR_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'rename_test'\n\\gset\nRESET ROLE;\nSELECT current_user;\n current_user \n--------------\n super_user\n\nALTER VIEW :\"PART_VIEW_SCHEMA\".:\"PART_VIEW_NAME\" SET SCHEMA public;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name \n------------------+----------------+---------------------+-------------------\n rename_schema    | rename_test    | public              | _partial_view_3\n\n--alter direct view schema\nSELECT user_view_schema, user_view_name, direct_view_schema, direct_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name |  direct_view_schema   | direct_view_name \n------------------+----------------+-----------------------+------------------\n rename_schema    | rename_test    | _timescaledb_internal | _direct_view_3\n\nRESET ROLE;\nSELECT current_user;\n current_user \n--------------\n super_user\n\nALTER VIEW :\"DIR_VIEW_SCHEMA\".:\"DIR_VIEW_NAME\" SET SCHEMA public;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name,\n      direct_view_schema, direct_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name | direct_view_schema | direct_view_name \n------------------+----------------+---------------------+-------------------+--------------------+------------------\n rename_schema    | rename_test    | public              | _partial_view_3   | public             | _direct_view_3\n\nRESET ROLE;\nSELECT current_user;\n current_user \n--------------\n super_user\n\nALTER SCHEMA rename_schema RENAME TO new_name_schema;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name,\n       direct_view_schema, direct_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name | direct_view_schema | direct_view_name \n------------------+----------------+---------------------+-------------------+--------------------+------------------\n new_name_schema  | rename_test    | public              | _partial_view_3   | public             | _direct_view_3\n\nALTER VIEW :\"PART_VIEW_NAME\" SET SCHEMA new_name_schema;\nALTER VIEW :\"DIR_VIEW_NAME\" SET SCHEMA new_name_schema;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name,\n       direct_view_schema, direct_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name | direct_view_schema | direct_view_name \n------------------+----------------+---------------------+-------------------+--------------------+------------------\n new_name_schema  | rename_test    | new_name_schema     | _partial_view_3   | new_name_schema    | _direct_view_3\n\nRESET ROLE;\nSELECT current_user;\n current_user \n--------------\n super_user\n\nALTER SCHEMA new_name_schema RENAME TO foo_name_schema;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name \n------------------+----------------+---------------------+-------------------\n foo_name_schema  | rename_test    | foo_name_schema     | _partial_view_3\n\nALTER MATERIALIZED VIEW foo_name_schema.rename_test SET SCHEMA public;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name \n------------------+----------------+---------------------+-------------------\n public           | rename_test    | foo_name_schema     | _partial_view_3\n\nRESET ROLE;\nSELECT current_user;\n current_user \n--------------\n super_user\n\nALTER SCHEMA foo_name_schema RENAME TO rename_schema;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSET client_min_messages TO NOTICE;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name \n------------------+----------------+---------------------+-------------------\n public           | rename_test    | rename_schema       | _partial_view_3\n\nALTER MATERIALIZED VIEW rename_test RENAME TO rename_c_aggregate;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema |   user_view_name   | partial_view_schema | partial_view_name \n------------------+--------------------+---------------------+-------------------\n public           | rename_c_aggregate | rename_schema       | _partial_view_3\n\nSELECT * FROM rename_c_aggregate;\n time_bucket | count \n-------------+-------\n\nALTER VIEW rename_schema.:\"PART_VIEW_NAME\" RENAME TO partial_view;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name,\n      direct_view_schema, direct_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema |   user_view_name   | partial_view_schema | partial_view_name | direct_view_schema | direct_view_name \n------------------+--------------------+---------------------+-------------------+--------------------+------------------\n public           | rename_c_aggregate | rename_schema       | partial_view      | rename_schema      | _direct_view_3\n\n--rename direct view\nALTER VIEW rename_schema.:\"DIR_VIEW_NAME\" RENAME TO direct_view;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name,\n      direct_view_schema, direct_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema |   user_view_name   | partial_view_schema | partial_view_name | direct_view_schema | direct_view_name \n------------------+--------------------+---------------------+-------------------+--------------------+------------------\n public           | rename_c_aggregate | rename_schema       | partial_view      | rename_schema      | direct_view\n\n-- drop_chunks tests\nDROP TABLE conditions CASCADE;\nDROP TABLE foo CASCADE;\nNOTICE:  drop cascades to 2 other objects\nCREATE TABLE drop_chunks_table(time BIGINT NOT NULL, data INTEGER);\nSELECT hypertable_id AS drop_chunks_table_id\n    FROM create_hypertable('drop_chunks_table', 'time', chunk_time_interval => 10) \\gset\nCREATE OR REPLACE FUNCTION integer_now_test() returns bigint LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), bigint '0') FROM drop_chunks_table $$;\nSELECT set_integer_now_func('drop_chunks_table', 'integer_now_test');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW drop_chunks_view\n  WITH (\n    timescaledb.continuous,\n    timescaledb.materialized_only=true\n  )\nAS SELECT time_bucket('5', time), COUNT(data)\n    FROM drop_chunks_table\n    GROUP BY 1 WITH NO DATA;\nSELECT format('%I.%I', schema_name, table_name) AS drop_chunks_mat_table,\n        schema_name AS drop_chunks_mat_schema,\n        table_name AS drop_chunks_mat_table_name\n    FROM _timescaledb_catalog.hypertable, _timescaledb_catalog.continuous_agg\n    WHERE _timescaledb_catalog.continuous_agg.raw_hypertable_id = :drop_chunks_table_id\n        AND _timescaledb_catalog.hypertable.id = _timescaledb_catalog.continuous_agg.mat_hypertable_id \\gset\n-- create 3 chunks, with 3 time bucket\nINSERT INTO drop_chunks_table SELECT i, i FROM generate_series(0, 29) AS i;\n-- Only refresh up to bucket 15 initially. Matches the old refresh\n-- behavior that didn't materialize everything\nCALL refresh_continuous_aggregate('drop_chunks_view', 0, 15);\nSELECT count(c) FROM show_chunks('drop_chunks_table') AS c;\n count \n-------\n     3\n\nSELECT count(c) FROM show_chunks('drop_chunks_view') AS c;\n count \n-------\n     1\n\nSELECT * FROM drop_chunks_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     5\n           5 |     5\n          10 |     5\n\n-- cannot drop directly from the materialization table without specifying\n-- cont. aggregate view name explicitly\n\\set ON_ERROR_STOP 0\nSELECT drop_chunks(:'drop_chunks_mat_table',\n    newer_than => -20,\n    verbose => true);\nERROR:  operation not supported on materialized hypertable\n\\set ON_ERROR_STOP 1\nSELECT count(c) FROM show_chunks('drop_chunks_table') AS c;\n count \n-------\n     3\n\nSELECT count(c) FROM show_chunks('drop_chunks_view') AS c;\n count \n-------\n     1\n\nSELECT * FROM drop_chunks_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     5\n           5 |     5\n          10 |     5\n\n-- drop chunks when the chunksize and time_bucket aren't aligned\nDROP TABLE drop_chunks_table CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_5_4_chunk\nCREATE TABLE drop_chunks_table_u(time BIGINT NOT NULL, data INTEGER);\nSELECT hypertable_id AS drop_chunks_table_u_id\n    FROM create_hypertable('drop_chunks_table_u', 'time', chunk_time_interval => 7) \\gset\nCREATE OR REPLACE FUNCTION integer_now_test1() returns bigint LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), bigint '0') FROM drop_chunks_table_u $$;\nSELECT set_integer_now_func('drop_chunks_table_u', 'integer_now_test1');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW drop_chunks_view\n  WITH (\n    timescaledb.continuous,\n    timescaledb.materialized_only=true\n  )\nAS SELECT time_bucket('3', time), COUNT(data)\n    FROM drop_chunks_table_u\n    GROUP BY 1 WITH NO DATA;\nSELECT format('%I.%I', schema_name, table_name) AS drop_chunks_mat_table_u,\n        schema_name AS drop_chunks_mat_schema,\n        table_name AS drop_chunks_mat_table_u_name\n    FROM _timescaledb_catalog.hypertable, _timescaledb_catalog.continuous_agg\n    WHERE _timescaledb_catalog.continuous_agg.raw_hypertable_id = :drop_chunks_table_u_id\n        AND _timescaledb_catalog.hypertable.id = _timescaledb_catalog.continuous_agg.mat_hypertable_id \\gset\n-- create 3 chunks, with 3 time bucket\nINSERT INTO drop_chunks_table_u SELECT i, i FROM generate_series(0, 21) AS i;\n-- Refresh up to bucket 15 to match old materializer behavior\nCALL refresh_continuous_aggregate('drop_chunks_view', 0, 15);\nSELECT count(c) FROM show_chunks('drop_chunks_table_u') AS c;\n count \n-------\n     4\n\nSELECT count(c) FROM show_chunks('drop_chunks_view') AS c;\n count \n-------\n     1\n\nSELECT * FROM drop_chunks_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     3\n           3 |     3\n           6 |     3\n           9 |     3\n          12 |     3\n\n-- TRUNCATE test\n-- Can truncate regular hypertables that have caggs\nTRUNCATE drop_chunks_table_u;\n\\set ON_ERROR_STOP 0\n-- Can't truncate materialized hypertables directly\nTRUNCATE :drop_chunks_mat_table_u;\nERROR:  cannot TRUNCATE a hypertable underlying a continuous aggregate\n\\set ON_ERROR_STOP 1\n-- Check that we don't interfere with TRUNCATE of normal table and\n-- partitioned table\nCREATE TABLE truncate (value int);\nINSERT INTO truncate VALUES (1), (2);\nTRUNCATE truncate;\nSELECT * FROM truncate;\n value \n-------\n\nCREATE TABLE truncate_partitioned (value int)\n  PARTITION BY RANGE(value);\nCREATE TABLE truncate_p1 PARTITION OF truncate_partitioned\n  FOR VALUES FROM (1) TO (3);\nINSERT INTO truncate_partitioned VALUES (1), (2);\nTRUNCATE truncate_partitioned;\nSELECT * FROM truncate_partitioned;\n value \n-------\n\n-- ALTER TABLE tests\n\\set ON_ERROR_STOP 0\n-- test a variety of ALTER TABLE statements\nALTER TABLE :drop_chunks_mat_table_u RENAME time_bucket TO bad_name;\nERROR:  renaming columns on materialization tables is not supported\nALTER TABLE :drop_chunks_mat_table_u ADD UNIQUE(time_bucket);\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u SET UNLOGGED;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u ENABLE ROW LEVEL SECURITY;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u ADD COLUMN fizzle INTEGER;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u DROP COLUMN time_bucket;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u ALTER COLUMN time_bucket DROP NOT NULL;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u ALTER COLUMN time_bucket SET DEFAULT 1;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u ALTER COLUMN time_bucket SET STORAGE EXTERNAL;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u DISABLE TRIGGER ALL;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u SET TABLESPACE foo;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u NOT OF;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u OWNER TO CURRENT_USER;\nERROR:  operation not supported on materialization tables\n\\set ON_ERROR_STOP 1\nALTER TABLE :drop_chunks_mat_table_u SET SCHEMA public;\nALTER TABLE :drop_chunks_mat_table_u_name RENAME TO new_name;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSET client_min_messages TO NOTICE;\nSELECT * FROM new_name ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     3\n           3 |     3\n           6 |     3\n           9 |     3\n          12 |     3\n\nSELECT * FROM drop_chunks_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     3\n           3 |     3\n           6 |     3\n           9 |     3\n          12 |     3\n\n\\set ON_ERROR_STOP 0\n-- no continuous aggregates on a continuous aggregate materialization table\nCREATE MATERIALIZED VIEW new_name_view\n  WITH (\n    timescaledb.continuous,\n    timescaledb.materialized_only=true\n  )\nAS SELECT time_bucket('6', time_bucket), COUNT(\"count\")\n    FROM new_name\n    GROUP BY 1 WITH NO DATA;\nERROR:  hypertable is a continuous aggregate materialization table\n\\set ON_ERROR_STOP 1\nCREATE TABLE metrics(time timestamptz NOT NULL, device_id int, v1 float, v2 float);\nSELECT create_hypertable('metrics','time');\n  create_hypertable   \n----------------------\n (8,public,metrics,t)\n\nINSERT INTO metrics SELECT generate_series('2000-01-01'::timestamptz,'2000-01-10','1m'),1,0.25,0.75;\n-- check expressions in view definition\nCREATE MATERIALIZED VIEW cagg_expr\n  WITH (timescaledb.continuous, timescaledb.materialized_only=true)\nAS\nSELECT\n  time_bucket('1d', time) AS time,\n  'Const'::text AS Const,\n  4.3::numeric AS \"numeric\",\n  first(metrics,time),\n  CASE WHEN true THEN 'foo' ELSE 'bar' END,\n  COALESCE(NULL,'coalesce'),\n  avg(v1) + avg(v2) AS avg1,\n  avg(v1+v2) AS avg2\nFROM metrics\nGROUP BY 1 WITH NO DATA;\nCALL refresh_continuous_aggregate('cagg_expr', NULL, NULL);\nSELECT * FROM cagg_expr ORDER BY time LIMIT 5;\n             time             | const | numeric |                    first                     | case | coalesce | avg1 | avg2 \n------------------------------+-------+---------+----------------------------------------------+------+----------+------+------\n Fri Dec 31 16:00:00 1999 UTC | Const |     4.3 | (\"Sat Jan 01 00:00:00 2000 UTC\",1,0.25,0.75) | foo  | coalesce |    1 |    1\n Sat Jan 01 16:00:00 2000 UTC | Const |     4.3 | (\"Sat Jan 01 16:00:00 2000 UTC\",1,0.25,0.75) | foo  | coalesce |    1 |    1\n Sun Jan 02 16:00:00 2000 UTC | Const |     4.3 | (\"Sun Jan 02 16:00:00 2000 UTC\",1,0.25,0.75) | foo  | coalesce |    1 |    1\n Mon Jan 03 16:00:00 2000 UTC | Const |     4.3 | (\"Mon Jan 03 16:00:00 2000 UTC\",1,0.25,0.75) | foo  | coalesce |    1 |    1\n Tue Jan 04 16:00:00 2000 UTC | Const |     4.3 | (\"Tue Jan 04 16:00:00 2000 UTC\",1,0.25,0.75) | foo  | coalesce |    1 |    1\n\n--test materialization of invalidation before drop\nDROP TABLE IF EXISTS drop_chunks_table CASCADE;\nNOTICE:  table \"drop_chunks_table\" does not exist, skipping\nDROP TABLE IF EXISTS drop_chunks_table_u CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_7_9_chunk\nCREATE TABLE drop_chunks_table(time BIGINT NOT NULL, data INTEGER);\nSELECT hypertable_id AS drop_chunks_table_nid\n    FROM create_hypertable('drop_chunks_table', 'time', chunk_time_interval => 10) \\gset\nCREATE OR REPLACE FUNCTION integer_now_test2() returns bigint LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), bigint '0') FROM drop_chunks_table $$;\nSELECT set_integer_now_func('drop_chunks_table', 'integer_now_test2');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW drop_chunks_view\n  WITH (\n    timescaledb.continuous,\n    timescaledb.materialized_only=true\n  )\nAS SELECT time_bucket('5', time), max(data)\n    FROM drop_chunks_table\n    GROUP BY 1 WITH NO DATA;\nINSERT INTO drop_chunks_table SELECT i, i FROM generate_series(0, 20) AS i;\n--dropping chunks will process the invalidations\nSELECT drop_chunks('drop_chunks_table', older_than => (integer_now_test2()-9));\n               drop_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_10_13_chunk\n\nSELECT * FROM drop_chunks_table ORDER BY time ASC limit 1;\n time | data \n------+------\n   10 |   10\n\nINSERT INTO drop_chunks_table SELECT i, i FROM generate_series(20, 35) AS i;\nCALL refresh_continuous_aggregate('drop_chunks_view', 10, 40);\n--this will be seen after the drop its within the invalidation window and will be dropped\nINSERT INTO drop_chunks_table VALUES (26, 100);\n--this will not be processed by the drop since chunk 30-39 is not dropped but will be seen after refresh\n--shows that the drop doesn't do more work than necessary\nINSERT INTO drop_chunks_table VALUES (31, 200);\n--move the time up to 39\nINSERT INTO drop_chunks_table SELECT i, i FROM generate_series(35, 39) AS i;\n--the chunks and ranges we have thus far\nSELECT chunk_name, range_start_integer, range_end_integer\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'drop_chunks_table';\n     chunk_name     | range_start_integer | range_end_integer \n--------------------+---------------------+-------------------\n _hyper_10_14_chunk |                  10 |                20\n _hyper_10_15_chunk |                  20 |                30\n _hyper_10_16_chunk |                  30 |                40\n\n--the invalidation on 25 not yet seen\nSELECT * FROM drop_chunks_view ORDER BY time_bucket DESC;\n time_bucket | max \n-------------+-----\n          35 |  35\n          30 |  34\n          25 |  29\n          20 |  24\n          15 |  19\n          10 |  14\n\n--refresh to process the invalidations and then drop\nCALL refresh_continuous_aggregate('drop_chunks_view', NULL, (integer_now_test2()-9));\nSELECT drop_chunks('drop_chunks_table', older_than => (integer_now_test2()-9));\n               drop_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_10_14_chunk\n _timescaledb_internal._hyper_10_15_chunk\n\n--new values on 25 now seen in view\nSELECT * FROM drop_chunks_view ORDER BY time_bucket DESC;\n time_bucket | max \n-------------+-----\n          35 |  35\n          30 |  34\n          25 | 100\n          20 |  24\n          15 |  19\n          10 |  14\n\n--earliest datapoint now in table\nSELECT * FROM drop_chunks_table ORDER BY time ASC limit 1;\n time | data \n------+------\n   30 |   30\n\n--still see data in the view\nSELECT * FROM drop_chunks_view WHERE time_bucket < (integer_now_test2()-9) ORDER BY time_bucket DESC;\n time_bucket | max \n-------------+-----\n          25 | 100\n          20 |  24\n          15 |  19\n          10 |  14\n\n--no data but covers dropped chunks\nSELECT * FROM drop_chunks_table WHERE time < (integer_now_test2()-9) ORDER BY time DESC;\n time | data \n------+------\n\n--recreate the dropped chunk\nINSERT INTO drop_chunks_table SELECT i, i FROM generate_series(0, 20) AS i;\n--see data from recreated region\nSELECT * FROM drop_chunks_table WHERE time < (integer_now_test2()-9) ORDER BY time DESC;\n time | data \n------+------\n   20 |   20\n   19 |   19\n   18 |   18\n   17 |   17\n   16 |   16\n   15 |   15\n   14 |   14\n   13 |   13\n   12 |   12\n   11 |   11\n   10 |   10\n    9 |    9\n    8 |    8\n    7 |    7\n    6 |    6\n    5 |    5\n    4 |    4\n    3 |    3\n    2 |    2\n    1 |    1\n    0 |    0\n\n--should show chunk with old name and old ranges\nSELECT chunk_name, range_start_integer, range_end_integer\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'drop_chunks_table'\nORDER BY range_start_integer;\n     chunk_name     | range_start_integer | range_end_integer \n--------------------+---------------------+-------------------\n _hyper_10_18_chunk |                   0 |                10\n _hyper_10_19_chunk |                  10 |                20\n _hyper_10_20_chunk |                  20 |                30\n _hyper_10_16_chunk |                  30 |                40\n\n--We dropped everything up to the bucket starting at 30 and then\n--inserted new data up to and including time 20. Therefore, the\n--dropped data should stay the same as long as we only refresh\n--buckets that have non-dropped data.\nCALL refresh_continuous_aggregate('drop_chunks_view', 30, 40);\nSELECT * FROM drop_chunks_view ORDER BY time_bucket DESC;\n time_bucket | max \n-------------+-----\n          35 |  39\n          30 | 200\n          25 | 100\n          20 |  24\n          15 |  19\n          10 |  14\n\nSELECT format('%I.%I', schema_name, table_name) AS drop_chunks_mat_tablen,\n        schema_name AS drop_chunks_mat_schema,\n        table_name AS drop_chunks_mat_table_name\n    FROM _timescaledb_catalog.hypertable, _timescaledb_catalog.continuous_agg\n    WHERE _timescaledb_catalog.continuous_agg.raw_hypertable_id = :drop_chunks_table_nid\n        AND _timescaledb_catalog.hypertable.id = _timescaledb_catalog.continuous_agg.mat_hypertable_id \\gset\n-- TEST drop chunks from continuous aggregates by specifying view name\nSELECT drop_chunks('drop_chunks_view',\n    newer_than => -20,\n    verbose => true);\nINFO:  dropping chunk _timescaledb_internal._hyper_11_17_chunk\n               drop_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_11_17_chunk\n\n-- Test that we cannot drop chunks when specifying materialized\n-- hypertable\nINSERT INTO drop_chunks_table SELECT generate_series(45, 55), 500;\nCALL refresh_continuous_aggregate('drop_chunks_view', 45, 55);\nSELECT chunk_name, range_start_integer, range_end_integer\nFROM timescaledb_information.chunks\nWHERE hypertable_name = :'drop_chunks_mat_table_name' ORDER BY range_start_integer;\n     chunk_name     | range_start_integer | range_end_integer \n--------------------+---------------------+-------------------\n _hyper_11_23_chunk |                   0 |               100\n\n\\set ON_ERROR_STOP 0\n\\set VERBOSITY default\nSELECT drop_chunks(:'drop_chunks_mat_tablen', older_than => 60);\nERROR:  operation not supported on materialized hypertable\nDETAIL:  Hypertable \"_materialized_hypertable_11\" is a materialized hypertable.\nHINT:  Try the operation on the continuous aggregate instead.\n\\set VERBOSITY terse\n\\set ON_ERROR_STOP 1\n-----------------------------------------------------------------\n-- Test that refresh_continuous_aggregate on chunk will refresh,\n-- but only in the regions covered by the show chunks.\n-----------------------------------------------------------------\nSELECT chunk_name, range_start_integer, range_end_integer\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'drop_chunks_table'\nORDER BY 2,3;\n     chunk_name     | range_start_integer | range_end_integer \n--------------------+---------------------+-------------------\n _hyper_10_18_chunk |                   0 |                10\n _hyper_10_19_chunk |                  10 |                20\n _hyper_10_20_chunk |                  20 |                30\n _hyper_10_16_chunk |                  30 |                40\n _hyper_10_21_chunk |                  40 |                50\n _hyper_10_22_chunk |                  50 |                60\n\n-- Pick the second chunk as the one to drop\nWITH numbered_chunks AS (\n     SELECT row_number() OVER (ORDER BY range_start_integer), chunk_schema, chunk_name, range_start_integer, range_end_integer\n     FROM timescaledb_information.chunks\n     WHERE hypertable_name = 'drop_chunks_table'\n     ORDER BY 1\n)\nSELECT format('%I.%I', chunk_schema, chunk_name) AS chunk_to_drop, range_start_integer, range_end_integer\nFROM numbered_chunks\nWHERE row_number = 2 \\gset\n-- There's data in the table for the chunk/range we will drop\nSELECT * FROM drop_chunks_table\nWHERE time >= :range_start_integer\nAND time < :range_end_integer\nORDER BY 1;\n time | data \n------+------\n   10 |   10\n   11 |   11\n   12 |   12\n   13 |   13\n   14 |   14\n   15 |   15\n   16 |   16\n   17 |   17\n   18 |   18\n   19 |   19\n\n-- Make sure there is also data in the continuous aggregate\n-- CARE:\n-- Note that this behaviour of dropping the materialization table chunks and expecting a refresh\n-- that overlaps that time range to NOT update those chunks is undefined.\nCALL refresh_continuous_aggregate('drop_chunks_view', 0, 50);\nSELECT * FROM drop_chunks_view\nORDER BY 1;\n time_bucket | max \n-------------+-----\n           0 |   4\n           5 |   9\n          10 |  14\n          15 |  19\n          20 |  20\n          45 | 500\n          50 | 500\n\n-- Drop the second chunk, to leave a gap in the data\nDROP TABLE :chunk_to_drop;\n-- Verify that the second chunk is dropped\nSELECT chunk_name, range_start_integer, range_end_integer\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'drop_chunks_table'\nORDER BY 2,3;\n     chunk_name     | range_start_integer | range_end_integer \n--------------------+---------------------+-------------------\n _hyper_10_18_chunk |                   0 |                10\n _hyper_10_20_chunk |                  20 |                30\n _hyper_10_16_chunk |                  30 |                40\n _hyper_10_21_chunk |                  40 |                50\n _hyper_10_22_chunk |                  50 |                60\n\n-- Data is no longer in the table but still in the view\nSELECT * FROM drop_chunks_table\nWHERE time >= :range_start_integer\nAND time < :range_end_integer\nORDER BY 1;\n time | data \n------+------\n\nSELECT * FROM drop_chunks_view\nWHERE time_bucket >= :range_start_integer\nAND time_bucket < :range_end_integer\nORDER BY 1;\n time_bucket | max \n-------------+-----\n          10 |  14\n          15 |  19\n\n-- Insert a large value in one of the chunks that will be dropped\nINSERT INTO drop_chunks_table VALUES (:range_start_integer-1, 100);\n-- Now refresh and drop the two adjecent chunks\nCALL refresh_continuous_aggregate('drop_chunks_view', NULL, 30);\nSELECT drop_chunks('drop_chunks_table', older_than=>30);\n               drop_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_10_18_chunk\n _timescaledb_internal._hyper_10_20_chunk\n\n-- Verify that the chunks are dropped\nSELECT chunk_name, range_start_integer, range_end_integer\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'drop_chunks_table'\nORDER BY 2,3;\n     chunk_name     | range_start_integer | range_end_integer \n--------------------+---------------------+-------------------\n _hyper_10_16_chunk |                  30 |                40\n _hyper_10_21_chunk |                  40 |                50\n _hyper_10_22_chunk |                  50 |                60\n\n-- The continuous aggregate should be refreshed in the regions covered\n-- by the dropped chunks, but not in the \"gap\" region, i.e., the\n-- region of the chunk that was dropped via DROP TABLE.\nSELECT * FROM drop_chunks_view\nORDER BY 1;\n time_bucket | max \n-------------+-----\n           0 |   4\n           5 | 100\n          20 |  20\n          45 | 500\n          50 | 500\n\n-- Now refresh in the region of the first two dropped chunks\nCALL refresh_continuous_aggregate('drop_chunks_view', 0, :range_end_integer);\n-- Aggregate data in the refreshed range should no longer exist since\n-- the underlying data was dropped.\nSELECT * FROM drop_chunks_view\nORDER BY 1;\n time_bucket | max \n-------------+-----\n          20 |  20\n          45 | 500\n          50 | 500\n\n--------------------------------------------------------------------\n-- Check that we can create a materialized table in a tablespace. We\n-- create one with tablespace and one without and compare them.\nCREATE VIEW cagg_info AS\nWITH\n  caggs AS (\n    SELECT format('%I.%I', user_view_schema, user_view_name)::regclass AS user_view,\n           format('%I.%I', direct_view_schema, direct_view_name)::regclass AS direct_view,\n           format('%I.%I', partial_view_schema, partial_view_name)::regclass AS partial_view,\n           format('%I.%I', ht.schema_name, ht.table_name)::regclass AS mat_relid\n      FROM _timescaledb_catalog.hypertable ht,\n           _timescaledb_catalog.continuous_agg cagg\n     WHERE ht.id = cagg.mat_hypertable_id\n  )\nSELECT user_view,\n       pg_get_userbyid(relowner) AS user_view_owner,\n       relname AS mat_table,\n       (SELECT pg_get_userbyid(relowner) FROM pg_class WHERE oid = mat_relid) AS mat_table_owner,\n       direct_view,\n       (SELECT pg_get_userbyid(relowner) FROM pg_class WHERE oid = direct_view) AS direct_view_owner,\n       partial_view,\n       (SELECT pg_get_userbyid(relowner) FROM pg_class WHERE oid = partial_view) AS partial_view_owner,\n       (SELECT spcname FROM pg_tablespace WHERE oid = reltablespace) AS tablespace\n  FROM pg_class JOIN caggs ON pg_class.oid = caggs.mat_relid;\nGRANT SELECT ON cagg_info TO PUBLIC;\nCREATE VIEW chunk_info AS\nSELECT ht.schema_name, ht.table_name, relname AS chunk_name,\n       (SELECT spcname FROM pg_tablespace WHERE oid = reltablespace) AS tablespace\n  FROM pg_class c,\n       _timescaledb_catalog.hypertable ht,\n       _timescaledb_catalog.chunk ch\n WHERE ch.table_name = c.relname AND ht.id = ch.hypertable_id;\nCREATE TABLE whatever(time BIGINT NOT NULL, data INTEGER);\nSELECT hypertable_id AS whatever_nid\n  FROM create_hypertable('whatever', 'time', chunk_time_interval => 10)\n\\gset\nSELECT set_integer_now_func('whatever', 'integer_now_test');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW whatever_view_1\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT time_bucket('5', time), COUNT(data)\n  FROM whatever GROUP BY 1 WITH NO DATA;\nCREATE MATERIALIZED VIEW whatever_view_2\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nTABLESPACE tablespace1 AS\nSELECT time_bucket('5', time), COUNT(data)\n  FROM whatever GROUP BY 1 WITH NO DATA;\nINSERT INTO whatever SELECT i, i FROM generate_series(0, 29) AS i;\nCALL refresh_continuous_aggregate('whatever_view_1', NULL, NULL);\nCALL refresh_continuous_aggregate('whatever_view_2', NULL, NULL);\nSELECT user_view,\n       mat_table,\n       cagg_info.tablespace AS mat_tablespace,\n       chunk_name,\n       chunk_info.tablespace AS chunk_tablespace\n  FROM cagg_info, chunk_info\n WHERE mat_table::text = table_name\n   AND user_view::text LIKE 'whatever_view%';\n    user_view    |          mat_table          | mat_tablespace |     chunk_name     | chunk_tablespace \n-----------------+-----------------------------+----------------+--------------------+------------------\n whatever_view_1 | _materialized_hypertable_13 |                | _hyper_13_27_chunk | \n whatever_view_2 | _materialized_hypertable_14 | tablespace1    | _hyper_14_28_chunk | tablespace1\n\nALTER MATERIALIZED VIEW whatever_view_1 SET TABLESPACE tablespace2;\nSELECT user_view,\n       mat_table,\n       cagg_info.tablespace AS mat_tablespace,\n       chunk_name,\n       chunk_info.tablespace AS chunk_tablespace\n  FROM cagg_info, chunk_info\n WHERE mat_table::text = table_name\n   AND user_view::text LIKE 'whatever_view%';\n    user_view    |          mat_table          | mat_tablespace |     chunk_name     | chunk_tablespace \n-----------------+-----------------------------+----------------+--------------------+------------------\n whatever_view_1 | _materialized_hypertable_13 | tablespace2    | _hyper_13_27_chunk | tablespace2\n whatever_view_2 | _materialized_hypertable_14 | tablespace1    | _hyper_14_28_chunk | tablespace1\n\nDROP MATERIALIZED VIEW whatever_view_1;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_13_27_chunk\nDROP MATERIALIZED VIEW whatever_view_2;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_14_28_chunk\n-- test bucket width expressions on integer hypertables\nCREATE TABLE metrics_int2 (\n  time int2 NOT NULL,\n  device_id int,\n  v1 float,\n  v2 float\n);\nCREATE TABLE metrics_int4 (\n  time int4 NOT NULL,\n  device_id int,\n  v1 float,\n  v2 float\n);\nCREATE TABLE metrics_int8 (\n  time int8 NOT NULL,\n  device_id int,\n  v1 float,\n  v2 float\n);\nSELECT create_hypertable (('metrics_' || dt)::regclass, 'time', chunk_time_interval => 10)\nFROM (\n  VALUES ('int2'),\n    ('int4'),\n    ('int8')) v (dt);\n     create_hypertable      \n----------------------------\n (15,public,metrics_int2,t)\n (16,public,metrics_int4,t)\n (17,public,metrics_int8,t)\n\nCREATE OR REPLACE FUNCTION int2_now ()\n  RETURNS int2\n  LANGUAGE SQL\n  STABLE\n  AS $$\n  SELECT 10::int2\n$$;\nCREATE OR REPLACE FUNCTION int4_now ()\n  RETURNS int4\n  LANGUAGE SQL\n  STABLE\n  AS $$\n  SELECT 10::int4\n$$;\nCREATE OR REPLACE FUNCTION int8_now ()\n  RETURNS int8\n  LANGUAGE SQL\n  STABLE\n  AS $$\n  SELECT 10::int8\n$$;\nSELECT set_integer_now_func (('metrics_' || dt)::regclass, (dt || '_now')::regproc)\nFROM (\n  VALUES ('int2'),\n    ('int4'),\n    ('int8')) v (dt);\n set_integer_now_func \n----------------------\n \n \n \n\n-- width expression for int2 hypertables\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1::smallint, time)\nFROM metrics_int2\nGROUP BY 1;\nNOTICE:  continuous aggregate \"width_expr\" is already up-to-date\nDROP MATERIALIZED VIEW width_expr;\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1::smallint + 2::smallint, time)\nFROM metrics_int2\nGROUP BY 1;\nNOTICE:  continuous aggregate \"width_expr\" is already up-to-date\nDROP MATERIALIZED VIEW width_expr;\n-- width expression for int4 hypertables\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1, time)\nFROM metrics_int4\nGROUP BY 1;\nNOTICE:  continuous aggregate \"width_expr\" is already up-to-date\nDROP MATERIALIZED VIEW width_expr;\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1 + 2, time)\nFROM metrics_int4\nGROUP BY 1;\nNOTICE:  continuous aggregate \"width_expr\" is already up-to-date\nDROP MATERIALIZED VIEW width_expr;\n-- width expression for int8 hypertables\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1, time)\nFROM metrics_int8\nGROUP BY 1;\nNOTICE:  continuous aggregate \"width_expr\" is already up-to-date\nDROP MATERIALIZED VIEW width_expr;\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1 + 2, time)\nFROM metrics_int8\nGROUP BY 1;\nNOTICE:  continuous aggregate \"width_expr\" is already up-to-date\nDROP MATERIALIZED VIEW width_expr;\n\\set ON_ERROR_STOP 0\n-- non-immutable expresions should be rejected\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(extract(year FROM now())::smallint, time)\nFROM metrics_int2\nGROUP BY 1;\nERROR:  only immutable expressions allowed in time bucket function\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(extract(year FROM now())::int, time)\nFROM metrics_int4\nGROUP BY 1;\nERROR:  only immutable expressions allowed in time bucket function\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(extract(year FROM now())::int, time)\nFROM metrics_int8\nGROUP BY 1;\nERROR:  only immutable expressions allowed in time bucket function\n\\set ON_ERROR_STOP 1\n-- Test various ALTER MATERIALIZED VIEW statements.\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nCREATE MATERIALIZED VIEW owner_check WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1 + 2, time)\nFROM metrics_int8\nGROUP BY 1\nWITH NO DATA;\n\\x on\nSELECT * FROM cagg_info WHERE user_view::text = 'owner_check';\n-[ RECORD 1 ]------+---------------------------------------\nuser_view          | owner_check\nuser_view_owner    | default_perm_user\nmat_table          | _materialized_hypertable_24\nmat_table_owner    | default_perm_user\ndirect_view        | _timescaledb_internal._direct_view_24\ndirect_view_owner  | default_perm_user\npartial_view       | _timescaledb_internal._partial_view_24\npartial_view_owner | default_perm_user\ntablespace         | \n\n\\x off\n-- This should not work since the target user has the wrong role, but\n-- we test that the normal checks are done when changing the owner.\n\\set ON_ERROR_STOP 0\nALTER MATERIALIZED VIEW owner_check OWNER TO :ROLE_1;\nERROR:  must be member of role \"test_role_1\"\n\\set ON_ERROR_STOP 1\n-- Superuser can always change owner\nSET ROLE :ROLE_SUPERUSER;\n-- Add a refresh policy before changing owner to verify job owner is propagated\nSELECT add_continuous_aggregate_policy('owner_check', NULL, 1::int8, '1 day'::interval) AS cagg_job_id \\gset\nSELECT owner FROM _timescaledb_config.bgw_job WHERE id = :cagg_job_id;\n       owner       \n-------------------\n default_perm_user\n\nALTER MATERIALIZED VIEW owner_check OWNER TO :ROLE_1;\n\\x on\nSELECT * FROM cagg_info WHERE user_view::text = 'owner_check';\n-[ RECORD 1 ]------+---------------------------------------\nuser_view          | owner_check\nuser_view_owner    | test_role_1\nmat_table          | _materialized_hypertable_24\nmat_table_owner    | test_role_1\ndirect_view        | _timescaledb_internal._direct_view_24\ndirect_view_owner  | test_role_1\npartial_view       | _timescaledb_internal._partial_view_24\npartial_view_owner | test_role_1\ntablespace         | \n\n\\x off\n-- make sure policy job owner is propagated\nSELECT owner FROM _timescaledb_config.bgw_job WHERE id = :cagg_job_id;\n    owner    \n-------------\n test_role_1\n\nSELECT remove_continuous_aggregate_policy('owner_check');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n--\n-- Test drop continuous aggregate cases\n--\n-- Issue: #2608\n--\nCREATE OR REPLACE FUNCTION test_int_now()\n  RETURNS INT LANGUAGE SQL STABLE AS\n$BODY$\n  SELECT 50;\n$BODY$;\nCREATE TABLE conditionsnm(time_int INT NOT NULL, device INT, value FLOAT);\nSELECT create_hypertable('conditionsnm', 'time_int', chunk_time_interval => 10);\n     create_hypertable      \n----------------------------\n (25,public,conditionsnm,t)\n\nSELECT set_integer_now_func('conditionsnm', 'test_int_now');\n set_integer_now_func \n----------------------\n \n\nINSERT INTO conditionsnm\nSELECT time_val, time_val % 4, 3.14 FROM generate_series(0,100,1) AS time_val;\n-- Case 1: DROP\nCREATE MATERIALIZED VIEW conditionsnm_4\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE)\nAS\nSELECT time_bucket(7, time_int) as bucket,\nSUM(value), COUNT(value)\nFROM conditionsnm GROUP BY bucket WITH DATA;\nNOTICE:  refreshing continuous aggregate \"conditionsnm_4\"\nDROP materialized view conditionsnm_4;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_26_40_chunk\n-- Case 2: DROP CASCADE should have similar behaviour as DROP\nCREATE MATERIALIZED VIEW conditionsnm_4\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE)\nAS\nSELECT time_bucket(7, time_int) as bucket,\nSUM(value), COUNT(value)\nFROM conditionsnm GROUP BY bucket WITH DATA;\nNOTICE:  refreshing continuous aggregate \"conditionsnm_4\"\nDROP materialized view conditionsnm_4 CASCADE;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_27_41_chunk\n-- Case 3: require CASCADE in case of dependent object\nCREATE MATERIALIZED VIEW conditionsnm_4\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE)\nAS\nSELECT time_bucket(7, time_int) as bucket,\nSUM(value), COUNT(value)\nFROM conditionsnm GROUP BY bucket WITH DATA;\nNOTICE:  refreshing continuous aggregate \"conditionsnm_4\"\nCREATE VIEW see_cagg as select * from conditionsnm_4;\n\\set ON_ERROR_STOP 0\nDROP MATERIALIZED VIEW conditionsnm_4;\nERROR:  cannot drop view conditionsnm_4 because other objects depend on it\n\\set ON_ERROR_STOP 1\n-- Case 4: DROP CASCADE with dependency\nDROP MATERIALIZED VIEW conditionsnm_4 CASCADE;\nNOTICE:  drop cascades to view see_cagg\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_28_42_chunk\n-- Test DROP SCHEMA CASCADE with continuous aggregates\n--\n-- Issue: #2350\n--\n-- Case 1: DROP SCHEMA CASCADE\nCREATE SCHEMA test_schema;\nCREATE TABLE test_schema.telemetry_raw (\n  ts        TIMESTAMP WITH TIME ZONE NOT NULL,\n  value     DOUBLE PRECISION\n);\nSELECT create_hypertable('test_schema.telemetry_raw', 'ts');\n        create_hypertable         \n----------------------------------\n (29,test_schema,telemetry_raw,t)\n\nCREATE MATERIALIZED VIEW test_schema.telemetry_1s\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false)\n    AS\nSELECT time_bucket(INTERVAL '1s', ts) AS ts_1s,\n       avg(value)\n  FROM test_schema.telemetry_raw\n GROUP BY ts_1s WITH NO DATA;\nSELECT ca.raw_hypertable_id,\n       h.schema_name,\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON (h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'telemetry_1s';\n raw_hypertable_id |      schema_name      |       MAT_TABLE_NAME        |  PART_VIEW_NAME  |  partial_view_schema  \n-------------------+-----------------------+-----------------------------+------------------+-----------------------\n                29 | _timescaledb_internal | _materialized_hypertable_30 | _partial_view_30 | _timescaledb_internal\n\n\\gset\nDROP SCHEMA test_schema CASCADE;\nNOTICE:  drop cascades to 4 other objects\nSELECT count(*) FROM pg_class WHERE relname = :'MAT_TABLE_NAME';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = :'PART_VIEW_NAME';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = 'telemetry_1s';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_namespace WHERE nspname = 'test_schema';\n count \n-------\n     0\n\n-- Case 2: DROP SCHEMA CASCADE with multiple caggs\nCREATE SCHEMA test_schema;\nCREATE TABLE test_schema.telemetry_raw (\n  ts        TIMESTAMP WITH TIME ZONE NOT NULL,\n  value     DOUBLE PRECISION\n);\nSELECT create_hypertable('test_schema.telemetry_raw', 'ts');\n        create_hypertable         \n----------------------------------\n (31,test_schema,telemetry_raw,t)\n\nCREATE MATERIALIZED VIEW test_schema.cagg1\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false)\n    AS\nSELECT time_bucket(INTERVAL '1s', ts) AS ts_1s,\n       avg(value)\n  FROM test_schema.telemetry_raw\n GROUP BY ts_1s WITH NO DATA;\nCREATE MATERIALIZED VIEW test_schema.cagg2\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false)\n    AS\nSELECT time_bucket(INTERVAL '1s', ts) AS ts_1s,\n       avg(value)\n  FROM test_schema.telemetry_raw\n GROUP BY ts_1s WITH NO DATA;\nSELECT ca.raw_hypertable_id,\n       h.schema_name,\n       h.table_name AS \"MAT_TABLE_NAME1\",\n       partial_view_name as \"PART_VIEW_NAME1\",\n       partial_view_schema\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON (h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'cagg1';\n raw_hypertable_id |      schema_name      |       MAT_TABLE_NAME1       | PART_VIEW_NAME1  |  partial_view_schema  \n-------------------+-----------------------+-----------------------------+------------------+-----------------------\n                31 | _timescaledb_internal | _materialized_hypertable_32 | _partial_view_32 | _timescaledb_internal\n\n\\gset\nSELECT ca.raw_hypertable_id,\n       h.schema_name,\n       h.table_name AS \"MAT_TABLE_NAME2\",\n       partial_view_name as \"PART_VIEW_NAME2\",\n       partial_view_schema\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON (h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'cagg2';\n raw_hypertable_id |      schema_name      |       MAT_TABLE_NAME2       | PART_VIEW_NAME2  |  partial_view_schema  \n-------------------+-----------------------+-----------------------------+------------------+-----------------------\n                31 | _timescaledb_internal | _materialized_hypertable_33 | _partial_view_33 | _timescaledb_internal\n\n\\gset\nDROP SCHEMA test_schema CASCADE;\nNOTICE:  drop cascades to 7 other objects\nSELECT count(*) FROM pg_class WHERE relname = :'MAT_TABLE_NAME1';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = :'PART_VIEW_NAME1';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = 'cagg1';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = :'MAT_TABLE_NAME2';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = :'PART_VIEW_NAME2';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = 'cagg2';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_namespace WHERE nspname = 'test_schema';\n count \n-------\n     0\n\nDROP TABLESPACE tablespace1;\nDROP TABLESPACE tablespace2;\n-- Check that we can rename a column of a materialized view and still\n-- rebuild it after (#3051, #3405)\nCREATE TABLE conditions (\n       time TIMESTAMPTZ NOT NULL,\n       location TEXT NOT NULL,\n       temperature DOUBLE PRECISION NULL\n);\nSELECT create_hypertable('conditions', 'time');\n    create_hypertable     \n--------------------------\n (34,public,conditions,t)\n\nINSERT INTO conditions VALUES ( '2018-01-01 09:20:00-08', 'SFO', 55);\nINSERT INTO conditions VALUES ( '2018-01-02 09:30:00-08', 'por', 100);\nINSERT INTO conditions VALUES ( '2018-01-02 09:20:00-08', 'SFO', 65);\nINSERT INTO conditions VALUES ( '2018-01-02 09:10:00-08', 'NYC', 65);\nINSERT INTO conditions VALUES ( '2018-11-01 09:20:00-08', 'NYC', 45);\nINSERT INTO conditions VALUES ( '2018-11-01 10:40:00-08', 'NYC', 55);\nINSERT INTO conditions VALUES ( '2018-11-01 11:50:00-08', 'NYC', 65);\nINSERT INTO conditions VALUES ( '2018-11-01 12:10:00-08', 'NYC', 75);\nINSERT INTO conditions VALUES ( '2018-11-01 13:10:00-08', 'NYC', 85);\nINSERT INTO conditions VALUES ( '2018-11-02 09:20:00-08', 'NYC', 10);\nINSERT INTO conditions VALUES ( '2018-11-02 10:30:00-08', 'NYC', 20);\nCREATE MATERIALIZED VIEW conditions_daily\nWITH (timescaledb.continuous, timescaledb.materialized_only = true) AS\nSELECT location,\n       time_bucket(INTERVAL '1 day', time) AS bucket,\n       AVG(temperature)\n  FROM conditions\nGROUP BY location, bucket\nWITH NO DATA;\nCREATE MATERIALIZED VIEW conditions_weekly\nWITH (timescaledb.continuous, timescaledb.materialized_only = true) AS\nSELECT location,\n       time_bucket(INTERVAL '7 day', bucket) AS bucket,\n       AVG(avg)\n  FROM conditions_daily\nGROUP BY 1, 2\nWITH NO DATA;\nSELECT format('%I.%I', '_timescaledb_internal', h.table_name) AS \"MAT_TABLE_NAME\",\n       format('%I.%I', '_timescaledb_internal', partial_view_name) AS \"PART_VIEW_NAME\",\n       format('%I.%I', '_timescaledb_internal', direct_view_name) AS \"DIRECT_VIEW_NAME\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON (h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'conditions_daily'\n\\gset\n-- Show both the columns and the view definitions to see that\n-- references are correct in the view as well.\nSELECT * FROM test.show_columns('conditions_daily');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n bucket   | timestamp with time zone | f\n avg      | double precision         | f\n\nSELECT * FROM test.show_columns(:'DIRECT_VIEW_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n bucket   | timestamp with time zone | f\n avg      | double precision         | f\n\nSELECT * FROM test.show_columns(:'PART_VIEW_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n bucket   | timestamp with time zone | f\n avg      | double precision         | f\n\nSELECT * FROM test.show_columns(:'MAT_TABLE_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n bucket   | timestamp with time zone | t\n avg      | double precision         | f\n\nALTER MATERIALIZED VIEW conditions_daily RENAME COLUMN bucket to \"time\";\n-- Show both the columns and the view definitions to see that\n-- references are correct in the view as well.\nSELECT * FROM test.show_columns(' conditions_daily');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | f\n avg      | double precision         | f\n\nSELECT * FROM test.show_columns(:'DIRECT_VIEW_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | f\n avg      | double precision         | f\n\nSELECT * FROM test.show_columns(:'PART_VIEW_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | f\n avg      | double precision         | f\n\nSELECT * FROM test.show_columns(:'MAT_TABLE_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | t\n avg      | double precision         | f\n\n-- This will rebuild the materialized view and should succeed.\nALTER MATERIALIZED VIEW conditions_daily SET (timescaledb.materialized_only = false);\n-- Refresh the continuous aggregate to check that it works after the\n-- rename.\n\\set VERBOSITY verbose\nCALL refresh_continuous_aggregate('conditions_daily', NULL, NULL);\n\\set VERBOSITY terse\n-- Rename another column after the flip and verify toggling back and\n-- forth still works. This exercises the rename when the user view\n-- already has a UNION ALL query (materialized_only = false).\nALTER MATERIALIZED VIEW conditions_daily RENAME COLUMN avg TO average;\nSELECT * FROM test.show_columns('conditions_daily');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | f\n average  | double precision         | f\n\nSELECT * FROM test.show_columns(:'DIRECT_VIEW_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | f\n average  | double precision         | f\n\nSELECT * FROM test.show_columns(:'MAT_TABLE_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | t\n average  | double precision         | f\n\nALTER MATERIALIZED VIEW conditions_daily SET (timescaledb.materialized_only = true);\nALTER MATERIALIZED VIEW conditions_daily SET (timescaledb.materialized_only = false);\n-- Verify data is still accessible after multiple renames and toggles.\nSELECT * FROM conditions_daily ORDER BY location COLLATE \"C\", time;\n location |             time             | average \n----------+------------------------------+---------\n NYC      | Mon Jan 01 16:00:00 2018 UTC |      65\n NYC      | Wed Oct 31 16:00:00 2018 UTC |      65\n NYC      | Thu Nov 01 16:00:00 2018 UTC |      15\n SFO      | Sun Dec 31 16:00:00 2017 UTC |      55\n SFO      | Mon Jan 01 16:00:00 2018 UTC |      65\n por      | Mon Jan 01 16:00:00 2018 UTC |     100\n\n-- check hierarchical continuous aggregate still works after renames and toggles on the underlying cagg\nALTER MATERIALIZED VIEW conditions_weekly SET (timescaledb.materialized_only = false);\nALTER MATERIALIZED VIEW conditions_weekly SET (timescaledb.materialized_only = true);\nSELECT * FROM conditions_weekly ORDER BY location COLLATE \"C\", bucket;\n location | bucket | avg \n----------+--------+-----\n\n-- Verify that direct rename on the materialization hypertable is blocked.\n\\set ON_ERROR_STOP 0\nALTER TABLE :MAT_TABLE_NAME RENAME COLUMN average TO avg;\nERROR:  renaming columns on materialization tables is not supported\n\\set ON_ERROR_STOP 1\n-- Rename back so subsequent tests that reference \"avg\" still work.\nALTER MATERIALIZED VIEW conditions_daily RENAME COLUMN average TO avg;\n--\n-- Indexes on continuous aggregate\n--\n\\set ON_ERROR_STOP 0\n-- unique indexes are not supported\nCREATE UNIQUE INDEX index_unique_error ON conditions_daily (\"time\", location);\nERROR:  continuous aggregates do not support UNIQUE indexes\n-- concurrently index creation not supported\nCREATE INDEX CONCURRENTLY index_concurrently_avg ON conditions_daily (avg);\nERROR:  hypertables do not support concurrent index creation\n\\set ON_ERROR_STOP 1\nCREATE INDEX index_avg ON conditions_daily (avg);\nCREATE INDEX index_avg_only ON ONLY conditions_daily (avg);\nCREATE INDEX index_avg_include ON conditions_daily (avg) INCLUDE (location);\nCREATE INDEX index_avg_expr ON conditions_daily ((avg + 1));\nCREATE INDEX index_avg_location_sfo ON conditions_daily (avg) WHERE location = 'SFO';\nCREATE INDEX index_avg_expr_location_sfo ON conditions_daily ((avg + 2)) WHERE location = 'SFO';\nSELECT * FROM test.show_indexespred(:'MAT_TABLE_NAME');\n                                 Index                                 |      Columns      |           Expr            |          Pred          | Unique | Primary | Exclusion | Tablespace \n-----------------------------------------------------------------------+-------------------+---------------------------+------------------------+--------+---------+-----------+------------\n _timescaledb_internal._materialized_hypertable_35_bucket_idx          | {bucket}          |                           |                        | f      | f       | f         | \n _timescaledb_internal._materialized_hypertable_35_location_bucket_idx | {location,bucket} |                           |                        | f      | f       | f         | \n _timescaledb_internal.index_avg                                       | {avg}             |                           |                        | f      | f       | f         | \n _timescaledb_internal.index_avg_expr                                  | {expr}            | avg + 1::double precision |                        | f      | f       | f         | \n _timescaledb_internal.index_avg_expr_location_sfo                     | {expr}            | avg + 2::double precision | location = 'SFO'::text | f      | f       | f         | \n _timescaledb_internal.index_avg_include                               | {avg,location}    |                           |                        | f      | f       | f         | \n _timescaledb_internal.index_avg_location_sfo                          | {avg}             |                           | location = 'SFO'::text | f      | f       | f         | \n _timescaledb_internal.index_avg_only                                  | {avg}             |                           |                        | f      | f       | f         | \n\n-- #3696 assertion failure when referencing columns not present in result\nCREATE TABLE i3696(time timestamptz NOT NULL, search_query text, cnt integer, cnt2 integer);\nSELECT table_name FROM create_hypertable('i3696','time');\n table_name \n------------\n i3696\n\nCREATE MATERIALIZED VIEW i3696_cagg1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\n SELECT  search_query,count(search_query) as count, sum(cnt), time_bucket(INTERVAL '1 minute', time) AS bucket\n FROM i3696 GROUP BY cnt +cnt2 , bucket, search_query;\nNOTICE:  continuous aggregate \"i3696_cagg1\" is already up-to-date\nALTER MATERIALIZED VIEW i3696_cagg1 SET (timescaledb.materialized_only = 'true');\nCREATE MATERIALIZED VIEW i3696_cagg2 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\n SELECT  search_query,count(search_query) as count, sum(cnt), time_bucket(INTERVAL '1 minute', time) AS bucket\n FROM i3696 GROUP BY cnt + cnt2, bucket, search_query\n HAVING cnt + cnt2 + sum(cnt) > 2 or count(cnt2) > 10;\nNOTICE:  continuous aggregate \"i3696_cagg2\" is already up-to-date\nALTER MATERIALIZED VIEW i3696_cagg2 SET (timescaledb.materialized_only = 'true');\n--TEST test with multiple settings on continuous aggregates --\n-- test for materialized_only + compress combinations (real time aggs enabled initially)\nCREATE TABLE test_setting(time timestamptz not null, val numeric);\nSELECT create_hypertable('test_setting', 'time');\n     create_hypertable      \n----------------------------\n (40,public,test_setting,t)\n\nCREATE MATERIALIZED VIEW test_setting_cagg with (timescaledb.continuous, timescaledb.materialized_only=false)\nAS SELECT time_bucket('1h',time), avg(val), count(*) FROM test_setting GROUP BY 1;\nNOTICE:  continuous aggregate \"test_setting_cagg\" is already up-to-date\nINSERT INTO test_setting\nSELECT generate_series( '2020-01-10 8:00'::timestamp, '2020-01-30 10:00+00'::timestamptz, '1 day'::interval), 10.0;\nCALL refresh_continuous_aggregate('test_setting_cagg', NULL, '2020-05-30 10:00+00'::timestamptz);\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    20\n\n--this row is not in the materialized result ---\nINSERT INTO test_setting VALUES( '2020-11-01', 20);\n--try out 2 settings here --\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'true', timescaledb.compress='true');\nNOTICE:  defaulting compress_orderby to time_bucket\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | t                   | t\n\n--real time aggs is off now , should return 20 --\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    20\n\n--now set it back to false --\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'false', timescaledb.compress='true');\nNOTICE:  defaulting compress_orderby to time_bucket\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | t                   | f\n\n--count should return additional data since we have real time aggs on\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    21\n\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'true', timescaledb.compress='false');\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | f                   | t\n\n--real time aggs is off now , should return 20 --\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    20\n\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'false', timescaledb.compress='false');\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | f                   | f\n\n--count should return additional data since we have real time aggs on\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    21\n\nDELETE FROM test_setting WHERE val = 20;\n--TEST test with multiple settings on continuous aggregates with real time aggregates turned off initially --\n-- test for materialized_only + compress combinations (real time aggs enabled initially)\nDROP MATERIALIZED VIEW test_setting_cagg;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_41_50_chunk\nCREATE MATERIALIZED VIEW test_setting_cagg with (timescaledb.continuous, timescaledb.materialized_only = true)\nAS SELECT time_bucket('1h',time), avg(val), count(*) FROM test_setting GROUP BY 1;\nNOTICE:  refreshing continuous aggregate \"test_setting_cagg\"\nCALL refresh_continuous_aggregate('test_setting_cagg', NULL, '2020-05-30 10:00+00'::timestamptz);\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    20\n\n--this row is not in the materialized result ---\nINSERT INTO test_setting VALUES( '2020-11-01', 20);\n--try out 2 settings here --\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'false', timescaledb.compress='true');\nNOTICE:  defaulting compress_orderby to time_bucket\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | t                   | f\n\n--count should return additional data since we have real time aggs on\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    21\n\n--now set it back to false --\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'true', timescaledb.compress='true');\nNOTICE:  defaulting compress_orderby to time_bucket\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | t                   | t\n\n--real time aggs is off now , should return 20 --\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    20\n\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'false', timescaledb.compress='false');\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | f                   | f\n\n--count should return additional data since we have real time aggs on\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    21\n\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'true', timescaledb.compress='false');\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | f                   | t\n\n--real time aggs is off now , should return 20 --\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    20\n\n-- END TEST with multiple settings\n-- Test View Target Entries that contain both aggrefs and Vars in the same expression\nCREATE TABLE transactions\n(\n    \"time\" timestamp with time zone NOT NULL,\n    dummy1 integer,\n    dummy2 integer,\n    dummy3 integer,\n    dummy4 integer,\n    dummy5 integer,\n    amount integer,\n    fiat_value integer\n);\nSELECT create_hypertable('transactions', 'time');\n     create_hypertable      \n----------------------------\n (45,public,transactions,t)\n\nINSERT INTO transactions VALUES ( '2018-01-01 09:20:00-08', 0, 0, 0, 0, 0, 1, 10);\nINSERT INTO transactions VALUES ( '2018-01-02 09:30:00-08', 0, 0, 0, 0, 0, -1, 10);\nINSERT INTO transactions VALUES ( '2018-01-02 09:20:00-08', 0, 0, 0, 0, 0, -1, 10);\nINSERT INTO transactions VALUES ( '2018-01-02 09:10:00-08', 0, 0, 0, 0, 0, -1, 10);\nINSERT INTO transactions VALUES ( '2018-11-01 09:20:00-08', 0, 0, 0, 0, 0, 1, 10);\nINSERT INTO transactions VALUES ( '2018-11-01 10:40:00-08', 0, 0, 0, 0, 0, 1, 10);\nINSERT INTO transactions VALUES ( '2018-11-01 11:50:00-08', 0, 0, 0, 0, 0, 1, 10);\nINSERT INTO transactions VALUES ( '2018-11-01 12:10:00-08', 0, 0, 0, 0, 0, -1, 10);\nINSERT INTO transactions VALUES ( '2018-11-01 13:10:00-08', 0, 0, 0, 0, 0, -1, 10);\nINSERT INTO transactions VALUES ( '2018-11-02 09:20:00-08', 0, 0, 0, 0, 0, 1, 10);\nINSERT INTO transactions VALUES ( '2018-11-02 10:30:00-08', 0, 0, 0, 0, 0, -1, 10);\nCREATE materialized view cashflows(\n    bucket,\n  \tamount,\n    cashflow,\n    cashflow2\n) WITH (\n    timescaledb.continuous,\n    timescaledb.materialized_only = true\n) AS\nSELECT time_bucket ('1 day', time) AS bucket,\n\tamount,\n  CASE\n      WHEN amount < 0 THEN (0 - sum(fiat_value))\n      ELSE sum(fiat_value)\n  END AS cashflow,\n  amount + sum(fiat_value)\nFROM transactions\nGROUP BY bucket, amount;\nNOTICE:  refreshing continuous aggregate \"cashflows\"\nSELECT h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name AS \"PART_VIEW_NAME\",\n       direct_view_name AS \"DIRECT_VIEW_NAME\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON (h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'cashflows'\n\\gset\n-- Show both the columns and the view definitions to see that\n-- references are correct in the view as well.\n\\d+ \"_timescaledb_internal\".:\"DIRECT_VIEW_NAME\"\n                         View \"_timescaledb_internal._direct_view_46\"\n  Column   |           Type           | Collation | Nullable | Default | Storage | Description \n-----------+--------------------------+-----------+----------+---------+---------+-------------\n bucket    | timestamp with time zone |           |          |         | plain   | \n amount    | integer                  |           |          |         | plain   | \n cashflow  | bigint                   |           |          |         | plain   | \n cashflow2 | bigint                   |           |          |         | plain   | \nView definition:\n SELECT time_bucket('@ 1 day'::interval, transactions.\"time\") AS bucket,\n    transactions.amount,\n        CASE\n            WHEN transactions.amount < 0 THEN 0 - sum(transactions.fiat_value)\n            ELSE sum(transactions.fiat_value)\n        END AS cashflow,\n    transactions.amount + sum(transactions.fiat_value) AS cashflow2\n   FROM transactions\n  GROUP BY (time_bucket('@ 1 day'::interval, transactions.\"time\")), transactions.amount;\n\n\\d+ \"_timescaledb_internal\".:\"PART_VIEW_NAME\"\n                         View \"_timescaledb_internal._partial_view_46\"\n  Column   |           Type           | Collation | Nullable | Default | Storage | Description \n-----------+--------------------------+-----------+----------+---------+---------+-------------\n bucket    | timestamp with time zone |           |          |         | plain   | \n amount    | integer                  |           |          |         | plain   | \n cashflow  | bigint                   |           |          |         | plain   | \n cashflow2 | bigint                   |           |          |         | plain   | \nView definition:\n SELECT time_bucket('@ 1 day'::interval, transactions.\"time\") AS bucket,\n    transactions.amount,\n        CASE\n            WHEN transactions.amount < 0 THEN 0 - sum(transactions.fiat_value)\n            ELSE sum(transactions.fiat_value)\n        END AS cashflow,\n    transactions.amount + sum(transactions.fiat_value) AS cashflow2\n   FROM transactions\n  GROUP BY (time_bucket('@ 1 day'::interval, transactions.\"time\")), transactions.amount;\n\n\\d+ \"_timescaledb_internal\".:\"MAT_TABLE_NAME\"\n                          Table \"_timescaledb_internal._materialized_hypertable_46\"\n  Column   |           Type           | Collation | Nullable | Default | Storage | Stats target | Description \n-----------+--------------------------+-----------+----------+---------+---------+--------------+-------------\n bucket    | timestamp with time zone |           | not null |         | plain   |              | \n amount    | integer                  |           |          |         | plain   |              | \n cashflow  | bigint                   |           |          |         | plain   |              | \n cashflow2 | bigint                   |           |          |         | plain   |              | \nIndexes:\n    \"_materialized_hypertable_46_amount_bucket_idx\" btree (amount, bucket DESC)\n    \"_materialized_hypertable_46_bucket_idx\" btree (bucket DESC)\nChild tables: _timescaledb_internal._hyper_46_55_chunk,\n              _timescaledb_internal._hyper_46_56_chunk\n\n\\d+ 'cashflows'\n                                    View \"public.cashflows\"\n  Column   |           Type           | Collation | Nullable | Default | Storage | Description \n-----------+--------------------------+-----------+----------+---------+---------+-------------\n bucket    | timestamp with time zone |           |          |         | plain   | \n amount    | integer                  |           |          |         | plain   | \n cashflow  | bigint                   |           |          |         | plain   | \n cashflow2 | bigint                   |           |          |         | plain   | \nView definition:\n SELECT _materialized_hypertable_46.bucket,\n    _materialized_hypertable_46.amount,\n    _materialized_hypertable_46.cashflow,\n    _materialized_hypertable_46.cashflow2\n   FROM _timescaledb_internal._materialized_hypertable_46;\n\nSELECT * FROM cashflows ORDER BY cashflows;\n            bucket            | amount | cashflow | cashflow2 \n------------------------------+--------+----------+-----------\n Sun Dec 31 16:00:00 2017 UTC |      1 |       10 |        11\n Mon Jan 01 16:00:00 2018 UTC |     -1 |      -30 |        29\n Wed Oct 31 16:00:00 2018 UTC |     -1 |      -20 |        19\n Wed Oct 31 16:00:00 2018 UTC |      1 |       30 |        31\n Thu Nov 01 16:00:00 2018 UTC |     -1 |      -10 |         9\n Thu Nov 01 16:00:00 2018 UTC |      1 |       10 |        11\n\n-- test cagg creation with named arguments in time_bucket\n-- note that positional arguments cannot follow named arguments\n-- 1. test named origin\n-- 2. test named timezone\n-- 3. test named ts\n-- 4. test named bucket width\n-- named origin\nCREATE MATERIALIZED VIEW cagg_named_origin WITH\n(timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket('1h', time, 'UTC', origin => '2001-01-03 01:23:45') AS bucket,\navg(amount) as avg_amount\nFROM transactions GROUP BY 1 WITH NO DATA;\n-- named timezone\nCREATE MATERIALIZED VIEW cagg_named_tz_origin WITH\n(timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket('1h', time, timezone => 'UTC', origin => '2001-01-03 01:23:45') AS bucket,\navg(amount) as avg_amount\nFROM transactions GROUP BY 1 WITH NO DATA;\n-- named ts\nCREATE MATERIALIZED VIEW cagg_named_ts_tz_origin WITH\n(timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket('1h', ts => time, timezone => 'UTC', origin => '2001-01-03 01:23:45') AS bucket,\navg(amount) as avg_amount\nFROM transactions GROUP BY 1 WITH NO DATA;\n-- named bucket width\nCREATE MATERIALIZED VIEW cagg_named_all WITH\n(timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(bucket_width => '1h', ts => time, timezone => 'UTC', origin => '2001-01-03 01:23:45') AS bucket,\navg(amount) as avg_amount\nFROM transactions GROUP BY 1 WITH NO DATA;\n-- Refreshing from the beginning (NULL) of a CAGG with variable time bucket and\n-- using an INTERVAL for the end timestamp (issue #5534)\nCREATE MATERIALIZED VIEW transactions_montly\nWITH (timescaledb.continuous, timescaledb.materialized_only = true) AS\nSELECT time_bucket(INTERVAL '1 month', time) AS bucket,\n       SUM(fiat_value),\n       MAX(fiat_value),\n       MIN(fiat_value)\n  FROM transactions\nGROUP BY 1\nWITH NO DATA;\n-- No rows\nSELECT * FROM transactions_montly ORDER BY bucket;\n bucket | sum | max | min \n--------+-----+-----+-----\n\n-- Refresh from beginning of the CAGG for 1 month\nCALL refresh_continuous_aggregate('transactions_montly', NULL, INTERVAL '1 month');\nSELECT * FROM transactions_montly ORDER BY bucket;\n            bucket            | sum | max | min \n------------------------------+-----+-----+-----\n Sun Dec 31 16:00:00 2017 UTC |  40 |  10 |  10\n Wed Oct 31 16:00:00 2018 UTC |  70 |  10 |  10\n\nTRUNCATE transactions_montly;\n-- Partial refresh the CAGG from beginning to an specific timestamp\nCALL refresh_continuous_aggregate('transactions_montly', NULL, '2018-11-01 11:50:00-08'::timestamptz);\nSELECT * FROM transactions_montly ORDER BY bucket;\n            bucket            | sum | max | min \n------------------------------+-----+-----+-----\n Sun Dec 31 16:00:00 2017 UTC |  40 |  10 |  10\n\n-- Full refresh the CAGG\nCALL refresh_continuous_aggregate('transactions_montly', NULL, NULL);\nSELECT * FROM transactions_montly ORDER BY bucket;\n            bucket            | sum | max | min \n------------------------------+-----+-----+-----\n Sun Dec 31 16:00:00 2017 UTC |  40 |  10 |  10\n Wed Oct 31 16:00:00 2018 UTC |  70 |  10 |  10\n\n-- Check set_chunk_time_interval on continuous aggregate\nCREATE MATERIALIZED VIEW cagg_set_chunk_time_interval\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(INTERVAL '1 month', time) AS bucket,\n       SUM(fiat_value),\n       MAX(fiat_value),\n       MIN(fiat_value)\nFROM transactions\nGROUP BY 1\nWITH NO DATA;\nSELECT set_chunk_time_interval('cagg_set_chunk_time_interval', chunk_time_interval => interval '1 month');\n set_chunk_time_interval \n-------------------------\n \n\nCALL refresh_continuous_aggregate('cagg_set_chunk_time_interval', NULL, NULL);\nSELECT _timescaledb_functions.to_interval(d.interval_length) = interval '1 month'\nFROM _timescaledb_catalog.dimension d\n         RIGHT JOIN _timescaledb_catalog.continuous_agg ca ON ca.user_view_name = 'cagg_set_chunk_time_interval'\nWHERE d.hypertable_id = ca.mat_hypertable_id;\n ?column? \n----------\n t\n\n-- Since #6077 CAggs are materialized only by default\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 5 other objects\nNOTICE:  drop cascades to 2 other objects\nCREATE TABLE conditions (\n       time TIMESTAMPTZ NOT NULL,\n       location TEXT NOT NULL,\n       temperature DOUBLE PRECISION NULL\n);\nSELECT create_hypertable('conditions', 'time');\n    create_hypertable     \n--------------------------\n (53,public,conditions,t)\n\nINSERT INTO conditions VALUES ( '2018-01-01 09:20:00-08', 'SFO', 55);\nINSERT INTO conditions VALUES ( '2018-01-02 09:30:00-08', 'POR', 100);\nINSERT INTO conditions VALUES ( '2018-01-02 09:20:00-08', 'SFO', 65);\nINSERT INTO conditions VALUES ( '2018-01-02 09:10:00-08', 'NYC', 65);\nINSERT INTO conditions VALUES ( '2018-11-01 09:20:00-08', 'NYC', 45);\nINSERT INTO conditions VALUES ( '2018-11-01 10:40:00-08', 'NYC', 55);\nINSERT INTO conditions VALUES ( '2018-11-01 11:50:00-08', 'NYC', 65);\nINSERT INTO conditions VALUES ( '2018-11-01 12:10:00-08', 'NYC', 75);\nINSERT INTO conditions VALUES ( '2018-11-01 13:10:00-08', 'NYC', 85);\nINSERT INTO conditions VALUES ( '2018-11-02 09:20:00-08', 'NYC', 10);\nINSERT INTO conditions VALUES ( '2018-11-02 10:30:00-08', 'NYC', 20);\nCREATE MATERIALIZED VIEW conditions_daily\nWITH (timescaledb.continuous) AS\nSELECT location,\n       time_bucket(INTERVAL '1 day', time) AS bucket,\n       AVG(temperature)\n  FROM conditions\nGROUP BY location, bucket\nWITH NO DATA;\n\\d+ conditions_daily\n                                View \"public.conditions_daily\"\n  Column  |           Type           | Collation | Nullable | Default | Storage  | Description \n----------+--------------------------+-----------+----------+---------+----------+-------------\n location | text                     |           |          |         | extended | \n bucket   | timestamp with time zone |           |          |         | plain    | \n avg      | double precision         |           |          |         | plain    | \nView definition:\n SELECT _materialized_hypertable_54.location,\n    _materialized_hypertable_54.bucket,\n    _materialized_hypertable_54.avg\n   FROM _timescaledb_internal._materialized_hypertable_54;\n\n-- Should return NO ROWS\nSELECT * FROM conditions_daily ORDER BY bucket, location;\n location | bucket | avg \n----------+--------+-----\n\nALTER MATERIALIZED VIEW conditions_daily SET (timescaledb.materialized_only=false);\n\\d+ conditions_daily\n                                View \"public.conditions_daily\"\n  Column  |           Type           | Collation | Nullable | Default | Storage  | Description \n----------+--------------------------+-----------+----------+---------+----------+-------------\n location | text                     |           |          |         | extended | \n bucket   | timestamp with time zone |           |          |         | plain    | \n avg      | double precision         |           |          |         | plain    | \nView definition:\n SELECT _materialized_hypertable_54.location,\n    _materialized_hypertable_54.bucket,\n    _materialized_hypertable_54.avg\n   FROM _timescaledb_internal._materialized_hypertable_54\n  WHERE _materialized_hypertable_54.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(54)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT conditions.location,\n    time_bucket('@ 1 day'::interval, conditions.\"time\") AS bucket,\n    avg(conditions.temperature) AS avg\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(54)), '-infinity'::timestamp with time zone)\n  GROUP BY conditions.location, (time_bucket('@ 1 day'::interval, conditions.\"time\"));\n\n-- Should return ROWS because now it is realtime\nSELECT * FROM conditions_daily ORDER BY bucket, location;\n location |            bucket            | avg \n----------+------------------------------+-----\n SFO      | Sun Dec 31 16:00:00 2017 UTC |  55\n NYC      | Mon Jan 01 16:00:00 2018 UTC |  65\n POR      | Mon Jan 01 16:00:00 2018 UTC | 100\n SFO      | Mon Jan 01 16:00:00 2018 UTC |  65\n NYC      | Wed Oct 31 16:00:00 2018 UTC |  65\n NYC      | Thu Nov 01 16:00:00 2018 UTC |  15\n\n-- Should return ROWS because we refreshed it\nALTER MATERIALIZED VIEW conditions_daily SET (timescaledb.materialized_only=true);\n\\d+ conditions_daily\n                                View \"public.conditions_daily\"\n  Column  |           Type           | Collation | Nullable | Default | Storage  | Description \n----------+--------------------------+-----------+----------+---------+----------+-------------\n location | text                     |           |          |         | extended | \n bucket   | timestamp with time zone |           |          |         | plain    | \n avg      | double precision         |           |          |         | plain    | \nView definition:\n SELECT _materialized_hypertable_54.location,\n    _materialized_hypertable_54.bucket,\n    _materialized_hypertable_54.avg\n   FROM _timescaledb_internal._materialized_hypertable_54;\n\nCALL refresh_continuous_aggregate('conditions_daily', NULL, NULL);\nSELECT * FROM conditions_daily ORDER BY bucket, location;\n location |            bucket            | avg \n----------+------------------------------+-----\n SFO      | Sun Dec 31 16:00:00 2017 UTC |  55\n NYC      | Mon Jan 01 16:00:00 2018 UTC |  65\n POR      | Mon Jan 01 16:00:00 2018 UTC | 100\n SFO      | Mon Jan 01 16:00:00 2018 UTC |  65\n NYC      | Wed Oct 31 16:00:00 2018 UTC |  65\n NYC      | Thu Nov 01 16:00:00 2018 UTC |  15\n\n-- Test TRUNCATE over a Realtime CAgg\nDROP MATERIALIZED VIEW conditions_daily;\nNOTICE:  drop cascades to 2 other objects\nCREATE MATERIALIZED VIEW conditions_daily\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT location,\n       time_bucket(INTERVAL '1 day', time) AS bucket,\n       AVG(temperature)\n  FROM conditions\nGROUP BY location, bucket\nWITH NO DATA;\nSELECT mat_hypertable_id FROM _timescaledb_catalog.continuous_agg WHERE user_view_name = 'conditions_daily' \\gset\n-- Check the current watermark for an empty CAgg\nSELECT _timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(:mat_hypertable_id)) AS watermak_empty_cagg;\n       watermak_empty_cagg       \n---------------------------------\n Sun Nov 23 16:00:00 4714 UTC BC\n\n-- Refresh the CAGG\nCALL refresh_continuous_aggregate('conditions_daily', NULL, NULL);\n-- Check the watermark after the refresh and before truncate the CAgg\nSELECT _timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(:mat_hypertable_id)) AS watermak_before;\n       watermak_before        \n------------------------------\n Fri Nov 02 16:00:00 2018 UTC\n\n-- Exists chunks before truncate the cagg (> 0)\nSELECT count(*) FROM show_chunks('conditions_daily');\n count \n-------\n     2\n\n-- Truncate the given CAgg, it should reset the watermark to the empty state\nTRUNCATE conditions_daily;\n-- No chunks remains after truncate the cagg (= 0)\nSELECT count(*) FROM show_chunks('conditions_daily');\n count \n-------\n     0\n\n-- Watermark should be reseted\nSELECT _timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(:mat_hypertable_id)) AS watermak_after;\n         watermak_after          \n---------------------------------\n Sun Nov 23 16:00:00 4714 UTC BC\n\n-- Should return ROWS because the watermark was reseted by the TRUNCATE\nSELECT * FROM conditions_daily ORDER BY bucket, location;\n location |            bucket            | avg \n----------+------------------------------+-----\n SFO      | Sun Dec 31 16:00:00 2017 UTC |  55\n NYC      | Mon Jan 01 16:00:00 2018 UTC |  65\n POR      | Mon Jan 01 16:00:00 2018 UTC | 100\n SFO      | Mon Jan 01 16:00:00 2018 UTC |  65\n NYC      | Wed Oct 31 16:00:00 2018 UTC |  65\n NYC      | Thu Nov 01 16:00:00 2018 UTC |  15\n\n-- check compression settings are cleaned up when deleting a cagg with compression\nCREATE TABLE cagg_cleanup(time timestamptz not null);\nSELECT table_name FROM create_hypertable('cagg_cleanup','time');\n  table_name  \n--------------\n cagg_cleanup\n\nINSERT INTO cagg_cleanup SELECT '2020-01-01';\nCREATE MATERIALIZED VIEW cagg1 WITH (timescaledb.continuous) AS SELECT time_bucket('1h',time) FROM cagg_cleanup GROUP BY 1;\nNOTICE:  refreshing continuous aggregate \"cagg1\"\nALTER MATERIALIZED VIEW cagg1 SET (timescaledb.compress);\nNOTICE:  defaulting compress_orderby to time_bucket\nSELECT count(compress_chunk(ch)) FROM show_chunks('cagg1') ch;\n count \n-------\n     1\n\nDROP MATERIALIZED VIEW cagg1;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_57_70_chunk\nSELECT * FROM _timescaledb_catalog.compression_settings;\n relid | compress_relid | segmentby | orderby | orderby_desc | orderby_nullsfirst | index \n-------+----------------+-----------+---------+--------------+--------------------+-------\n\n-- test WITH namespace alias\nCREATE TABLE with_alias(time timestamptz not null);\nCREATE MATERIALIZED VIEW cagg_alias\nWITH (tsdb.continuous, tsdb.materialized_only=false) AS\nSELECT time_bucket(INTERVAL '1 day', time) FROM conditions GROUP BY 1 WITH NO DATA;\nALTER MATERIALIZED VIEW cagg_alias SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW cagg_alias SET (tsdb.materialized_only=false);\nDROP MATERIALIZED VIEW cagg_alias;\n-- test SET chunk_time_interval\nCREATE MATERIALIZED VIEW cagg_set\nWITH (tsdb.continuous, tsdb.chunk_interval='1day') AS\nSELECT time_bucket(INTERVAL '1 day', time) AS cagg_interval_setter FROM conditions GROUP BY 1 WITH NO DATA;\nSELECT column_name, time_interval FROM timescaledb_information.dimensions WHERE column_name='cagg_interval_setter';\n     column_name      | time_interval \n----------------------+---------------\n cagg_interval_setter | @ 1 day\n\nALTER MATERIALIZED VIEW cagg_set SET (tsdb.chunk_interval='23 day');\nSELECT column_name, time_interval FROM timescaledb_information.dimensions WHERE column_name='cagg_interval_setter';\n     column_name      | time_interval \n----------------------+---------------\n cagg_interval_setter | @ 23 days\n\nALTER MATERIALIZED VIEW cagg_set SET (tsdb.chunk_interval='6 month');\nSELECT column_name, time_interval FROM timescaledb_information.dimensions WHERE column_name='cagg_interval_setter';\n     column_name      | time_interval \n----------------------+---------------\n cagg_interval_setter | @ 180 days\n\nALTER MATERIALIZED VIEW cagg_set SET (tsdb.chunk_interval='1 year');\nSELECT column_name, time_interval FROM timescaledb_information.dimensions WHERE column_name='cagg_interval_setter';\n     column_name      | time_interval \n----------------------+---------------\n cagg_interval_setter | @ 360 days\n\n-- test cagg with stable functions\nCREATE MATERIALIZED VIEW cagg_stable WITH (tsdb.continuous) AS\nSELECT sum(temperature), max(time + INTERVAL '1h')\nFROM conditions\nGROUP BY time_bucket('1week', time), location;\nWARNING:  using non-immutable functions in continuous aggregate view may lead to inconsistent results on rematerialization\nNOTICE:  refreshing continuous aggregate \"cagg_stable\"\nSELECT * FROM cagg_stable t ORDER BY t;\n sum |             max              \n-----+------------------------------\n  65 | Tue Jan 02 10:10:00 2018 UTC\n 100 | Tue Jan 02 10:30:00 2018 UTC\n 120 | Tue Jan 02 10:20:00 2018 UTC\n 355 | Fri Nov 02 11:30:00 2018 UTC\n\n--aggregate without combine function but stable function\nCREATE MATERIALIZED VIEW cagg_json_agg WITH (tsdb.continuous, tsdb.materialized_only=false)\nAS SELECT json_agg(location) from conditions group by time_bucket('1week', time), location WITH NO DATA;\nWARNING:  using non-immutable functions in continuous aggregate view may lead to inconsistent results on rematerialization\nCREATE FUNCTION test_stablefunc(int) RETURNS int LANGUAGE 'sql' STABLE AS 'SELECT $1 + 10';\nCREATE MATERIALIZED VIEW cagg_stable2 WITH (tsdb.continuous) AS\nSELECT sum(test_stablefunc(temperature::int)), min(location)\nFROM conditions\nGROUP BY time_bucket('1week', time) WITH NO DATA;\nWARNING:  using non-immutable functions in continuous aggregate view may lead to inconsistent results on rematerialization\nCREATE MATERIALIZED VIEW cagg_stable3 WITH (tsdb.continuous) AS\nSELECT sum(temperature), min(location)\nFROM conditions\nGROUP BY time_bucket('1week', time), test_stablefunc(temperature::int) WITH NO DATA;\nWARNING:  using non-immutable functions in continuous aggregate view may lead to inconsistent results on rematerialization\n-- test window functions in caggs\n-- first do sanity check that we error without the GUC\n\\set ON_ERROR_STOP 0\nCREATE MATERIALIZED VIEW cagg_window WITH (tsdb.continuous)\nAS SELECT time_bucket('1week', time), rank() OVER (PARTITION BY time_bucket('1 week',time)) FROM conditions GROUP BY 1;\nERROR:  invalid continuous aggregate query\n\\set ON_ERROR_STOP 1\nSET timescaledb.enable_cagg_window_functions TO on;\nCREATE MATERIALIZED VIEW cagg_window_1 WITH (tsdb.continuous)\nAS SELECT time_bucket('1week', time), rank() OVER (PARTITION BY time_bucket('1 week',time)) FROM conditions GROUP BY 1;\nWARNING:  window function support is experimental and may result in unexpected results depending on the functions used.\nNOTICE:  refreshing continuous aggregate \"cagg_window_1\"\nCREATE MATERIALIZED VIEW cagg_window_2 WITH (tsdb.continuous)\nAS SELECT time_bucket('1week', time), rank() OVER (PARTITION BY time_bucket('1 week',time), location) FROM conditions GROUP BY 1, location;\nWARNING:  window function support is experimental and may result in unexpected results depending on the functions used.\nNOTICE:  refreshing continuous aggregate \"cagg_window_2\"\nCREATE MATERIALIZED VIEW cagg_window_3 WITH (tsdb.continuous)\nAS SELECT time_bucket('1week', time), rank() OVER (PARTITION BY time_bucket('1 week',time)) FROM conditions GROUP BY 1, location;\nWARNING:  window function support is experimental and may result in unexpected results depending on the functions used.\nNOTICE:  refreshing continuous aggregate \"cagg_window_3\"\nCREATE MATERIALIZED VIEW cagg_window_4 WITH (tsdb.continuous)\nAS SELECT time_bucket('1week', time), rank() OVER w FROM conditions GROUP BY 1, location WINDOW w AS (PARTITION BY time_bucket('1 week',time));\nWARNING:  window function support is experimental and may result in unexpected results depending on the functions used.\nNOTICE:  refreshing continuous aggregate \"cagg_window_4\"\n-- test setting chunk_interval on a cagg\nCREATE MATERIALIZED VIEW cagg_chunk_interval WITH (tsdb.continuous, tsdb.chunk_interval='1000 day') AS SELECT time_bucket('1 week', time) FROM conditions GROUP BY 1 WITH NO DATA;\nSELECT time_interval from timescaledb_information.continuous_aggregates cagg INNER JOIN timescaledb_information.dimensions dim ON cagg.materialization_hypertable_name = dim.hypertable_name  WHERE view_name='cagg_chunk_interval';\n time_interval \n---------------\n @ 1000 days\n\nALTER MATERIALIZED VIEW cagg_chunk_interval SET (tsdb.chunk_interval='110 day');\nSELECT time_interval from timescaledb_information.continuous_aggregates cagg INNER JOIN timescaledb_information.dimensions dim ON cagg.materialization_hypertable_name = dim.hypertable_name  WHERE view_name='cagg_chunk_interval';\n time_interval \n---------------\n @ 110 days\n\n-- test columnstore options\nCREATE MATERIALIZED VIEW columnstore_options WITH (tsdb.continuous, tsdb.chunk_interval='1 day') AS SELECT time_bucket('1 day', time) FROM conditions GROUP BY 1 WITH NO DATA;\nSELECT column_name, compress_interval_length from _timescaledb_catalog.dimension where column_name='time_bucket' ORDER BY id DESC LIMIT 1;\n column_name | compress_interval_length \n-------------+--------------------------\n time_bucket |                         \n\nALTER MATERIALIZED VIEW columnstore_options SET (tsdb.columnstore, tsdb.chunk_interval='1 day', tsdb.orderby='time_bucket DESC', tsdb.compress_chunk_interval='2 day');\nSELECT column_name, compress_interval_length from _timescaledb_catalog.dimension where column_name='time_bucket' ORDER BY id DESC LIMIT 1;\n column_name | compress_interval_length \n-------------+--------------------------\n time_bucket |             172800000000\n\nALTER MATERIALIZED VIEW columnstore_options SET (tsdb.compress_chunk_interval='3 day');\nSELECT column_name, compress_interval_length from _timescaledb_catalog.dimension where column_name='time_bucket' ORDER BY id DESC LIMIT 1;\n column_name | compress_interval_length \n-------------+--------------------------\n time_bucket |             259200000000\n\nALTER MATERIALIZED VIEW columnstore_options SET (tsdb.compress_chunk_time_interval='4 day');\nSELECT column_name, compress_interval_length from _timescaledb_catalog.dimension where column_name='time_bucket' ORDER BY id DESC LIMIT 1;\n column_name | compress_interval_length \n-------------+--------------------------\n time_bucket |             345600000000\n\n-- test set returning functions in caggs\nCREATE TABLE kpis_raw (time TIMESTAMP NOT NULL, value INTEGER, groups TEXT[]) WITH (tsdb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nINSERT INTO kpis_raw (time, value, groups) VALUES\n  ('2025-01-01', 10, '{group1,group2,group3}'),\n  ('2025-01-02', 20, '{group1,group4}'),\n  ('2025-02-01', 10, '{group1,group3}'),\n  ('2025-02-01', 20, '{group1,group4}');\nCREATE MATERIALIZED VIEW kpis_cagg WITH (tsdb.continuous) AS\nSELECT time_bucket('7 day', time) AS bucket, count(*) AS number_of_records, avg(value) AS average, unnest(groups) AS kpi_group\nFROM kpis_raw GROUP BY bucket, kpi_group;\nNOTICE:  refreshing continuous aggregate \"kpis_cagg\"\nSELECT * FROM kpis_cagg ORDER BY bucket, kpi_group;\n          bucket          | number_of_records |       average       | kpi_group \n--------------------------+-------------------+---------------------+-----------\n Mon Dec 30 00:00:00 2024 |                 2 | 15.0000000000000000 | group1\n Mon Dec 30 00:00:00 2024 |                 1 | 10.0000000000000000 | group2\n Mon Dec 30 00:00:00 2024 |                 1 | 10.0000000000000000 | group3\n Mon Dec 30 00:00:00 2024 |                 1 | 20.0000000000000000 | group4\n Mon Jan 27 00:00:00 2025 |                 2 | 15.0000000000000000 | group1\n Mon Jan 27 00:00:00 2025 |                 1 | 10.0000000000000000 | group3\n Mon Jan 27 00:00:00 2025 |                 1 | 20.0000000000000000 | group4\n\n--TEST for caggs with non timescaledb namespace options\n--non timescaledb namespace options can be set via ALTER\n\\set ON_ERROR_STOP 0\n-- will error out\nCREATE MATERIALIZED VIEW ht_try_weekly\nWITH (timescaledb.continuous, tigerlake.newoption = true) AS\nSELECT time_bucket(interval '1 week', time) AS ts_bucket, avg(value)\nFROM kpis_raw\nGROUP BY 1;\nERROR:  non \"timescaledb\" namespace options can be set only via ALTER\n--caught by Postgres now\nALTER MATERIALIZED VIEW kpis_cagg SET (tigerlake.newoption = true);\nERROR:  \"kpis_cagg\" is not a materialized view\n\\set ON_ERROR_STOP 1\n-- TEST that cached alter stmt still works (see PR 8739)\n-- test DDL inside function\nCREATE TABLE hypertab_ddl( ts timestamp, a integer)\nWITH (timescaledb.hypertable);\nNOTICE:  using column \"ts\" as partitioning column\nCREATE OR REPLACE FUNCTION ddl_function() RETURNS VOID LANGUAGE PLPGSQL AS $$\nBEGIN\n  DROP MATERIALIZED VIEW IF EXISTS cagg_hypertab_ddl;\n  CREATE MATERIALIZED VIEW cagg_hypertab_ddl WITH (timescaledb.continuous)\n  AS SELECT time_bucket( '1 day'::interval, ts), COUNT(*)\n  FROM hypertab_ddl GROUP BY 1 WITH NO DATA;\nEND\n$$;\nSELECT ddl_function();\nNOTICE:  materialized view \"cagg_hypertab_ddl\" does not exist, skipping\n ddl_function \n--------------\n \n\nSELECT view_name from timescaledb_information.continuous_aggregates WHERE hypertable_name='hypertab_ddl';\n     view_name     \n-------------------\n cagg_hypertab_ddl\n\nSELECT ddl_function();\n ddl_function \n--------------\n \n\nSELECT view_name from timescaledb_information.continuous_aggregates WHERE hypertable_name='hypertab_ddl';\n     view_name     \n-------------------\n cagg_hypertab_ddl\n\n-- TEST continuous aggregate with functionally dependent columns\n-- name column depends on id which is in GROUP BY so name doesnt have to be\nCREATE table sensor(id int PRIMARY KEY, name text);\nCREATE TABLE sensordata(time timestamptz,id int, value float) WITH\t(timescaledb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nCREATE MATERIALIZED VIEW cagg_sensordata WITH (tsdb.continuous) AS\nSELECT s.id, s.name,time_bucket('15 minutes', sd.time) as bucket, avg(sd.value) FROM sensordata sd JOIN sensor s USING(id) GROUP BY s.id, bucket WITH NO DATA;\nSELECT * FROM cagg_sensordata;\n id | name | bucket | avg \n----+------+--------+-----\n\n"
  },
  {
    "path": "tsl/test/expected/cagg_ddl-16.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- Set this variable to avoid using a hard-coded path each time query\n-- results are compared\n\\set QUERY_RESULT_TEST_EQUAL_RELPATH '../../../../test/sql/include/query_result_test_equal.sql'\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--DDL commands on continuous aggregates\nCREATE TABLE conditions (\n      timec        TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature integer  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      timemeasure TIMESTAMPTZ,\n      timeinterval INTERVAL\n);\nSELECT table_name FROM create_hypertable('conditions', 'timec');\n table_name \n------------\n conditions\n\n-- schema tests\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET timezone TO 'UTC+8';\n-- drop if the tablespace1 and/or tablespace2 exists\nSET client_min_messages TO error;\nDROP TABLESPACE IF EXISTS tablespace1;\nDROP TABLESPACE IF EXISTS tablespace2;\nRESET client_min_messages;\nCREATE TABLESPACE tablespace1 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE1_PATH;\nCREATE TABLESPACE tablespace2 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE2_PATH;\nCREATE SCHEMA rename_schema;\nGRANT ALL ON SCHEMA rename_schema TO :ROLE_DEFAULT_PERM_USER;\nCREATE SCHEMA test_schema AUTHORIZATION :ROLE_DEFAULT_PERM_USER;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nCREATE TABLE foo(time TIMESTAMPTZ NOT NULL, data INTEGER);\nSELECT create_hypertable('foo', 'time');\n create_hypertable \n-------------------\n (2,public,foo,t)\n\nCREATE MATERIALIZED VIEW rename_test_old\n  WITH ( timescaledb.continuous, timescaledb.materialized_only=true)\nAS SELECT time_bucket('1week', time), COUNT(data)\n    FROM foo\n    GROUP BY 1 WITH NO DATA;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name  |  partial_view_schema  | partial_view_name \n------------------+-----------------+-----------------------+-------------------\n public           | rename_test_old | _timescaledb_internal | _partial_view_3\n\nALTER TABLE rename_test_old RENAME TO rename_test;\nALTER TABLE rename_test SET SCHEMA test_schema;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name |  partial_view_schema  | partial_view_name \n------------------+----------------+-----------------------+-------------------\n test_schema      | rename_test    | _timescaledb_internal | _partial_view_3\n\nALTER MATERIALIZED VIEW test_schema.rename_test SET SCHEMA rename_schema;\nDROP SCHEMA test_schema;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name |  partial_view_schema  | partial_view_name \n------------------+----------------+-----------------------+-------------------\n rename_schema    | rename_test    | _timescaledb_internal | _partial_view_3\n\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\",\n       direct_view_name as \"DIR_VIEW_NAME\",\n       direct_view_schema as \"DIR_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'rename_test'\n\\gset\nRESET ROLE;\nSELECT current_user;\n current_user \n--------------\n super_user\n\nALTER VIEW :\"PART_VIEW_SCHEMA\".:\"PART_VIEW_NAME\" SET SCHEMA public;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name \n------------------+----------------+---------------------+-------------------\n rename_schema    | rename_test    | public              | _partial_view_3\n\n--alter direct view schema\nSELECT user_view_schema, user_view_name, direct_view_schema, direct_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name |  direct_view_schema   | direct_view_name \n------------------+----------------+-----------------------+------------------\n rename_schema    | rename_test    | _timescaledb_internal | _direct_view_3\n\nRESET ROLE;\nSELECT current_user;\n current_user \n--------------\n super_user\n\nALTER VIEW :\"DIR_VIEW_SCHEMA\".:\"DIR_VIEW_NAME\" SET SCHEMA public;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name,\n      direct_view_schema, direct_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name | direct_view_schema | direct_view_name \n------------------+----------------+---------------------+-------------------+--------------------+------------------\n rename_schema    | rename_test    | public              | _partial_view_3   | public             | _direct_view_3\n\nRESET ROLE;\nSELECT current_user;\n current_user \n--------------\n super_user\n\nALTER SCHEMA rename_schema RENAME TO new_name_schema;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name,\n       direct_view_schema, direct_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name | direct_view_schema | direct_view_name \n------------------+----------------+---------------------+-------------------+--------------------+------------------\n new_name_schema  | rename_test    | public              | _partial_view_3   | public             | _direct_view_3\n\nALTER VIEW :\"PART_VIEW_NAME\" SET SCHEMA new_name_schema;\nALTER VIEW :\"DIR_VIEW_NAME\" SET SCHEMA new_name_schema;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name,\n       direct_view_schema, direct_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name | direct_view_schema | direct_view_name \n------------------+----------------+---------------------+-------------------+--------------------+------------------\n new_name_schema  | rename_test    | new_name_schema     | _partial_view_3   | new_name_schema    | _direct_view_3\n\nRESET ROLE;\nSELECT current_user;\n current_user \n--------------\n super_user\n\nALTER SCHEMA new_name_schema RENAME TO foo_name_schema;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name \n------------------+----------------+---------------------+-------------------\n foo_name_schema  | rename_test    | foo_name_schema     | _partial_view_3\n\nALTER MATERIALIZED VIEW foo_name_schema.rename_test SET SCHEMA public;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name \n------------------+----------------+---------------------+-------------------\n public           | rename_test    | foo_name_schema     | _partial_view_3\n\nRESET ROLE;\nSELECT current_user;\n current_user \n--------------\n super_user\n\nALTER SCHEMA foo_name_schema RENAME TO rename_schema;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSET client_min_messages TO NOTICE;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name \n------------------+----------------+---------------------+-------------------\n public           | rename_test    | rename_schema       | _partial_view_3\n\nALTER MATERIALIZED VIEW rename_test RENAME TO rename_c_aggregate;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema |   user_view_name   | partial_view_schema | partial_view_name \n------------------+--------------------+---------------------+-------------------\n public           | rename_c_aggregate | rename_schema       | _partial_view_3\n\nSELECT * FROM rename_c_aggregate;\n time_bucket | count \n-------------+-------\n\nALTER VIEW rename_schema.:\"PART_VIEW_NAME\" RENAME TO partial_view;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name,\n      direct_view_schema, direct_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema |   user_view_name   | partial_view_schema | partial_view_name | direct_view_schema | direct_view_name \n------------------+--------------------+---------------------+-------------------+--------------------+------------------\n public           | rename_c_aggregate | rename_schema       | partial_view      | rename_schema      | _direct_view_3\n\n--rename direct view\nALTER VIEW rename_schema.:\"DIR_VIEW_NAME\" RENAME TO direct_view;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name,\n      direct_view_schema, direct_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema |   user_view_name   | partial_view_schema | partial_view_name | direct_view_schema | direct_view_name \n------------------+--------------------+---------------------+-------------------+--------------------+------------------\n public           | rename_c_aggregate | rename_schema       | partial_view      | rename_schema      | direct_view\n\n-- drop_chunks tests\nDROP TABLE conditions CASCADE;\nDROP TABLE foo CASCADE;\nNOTICE:  drop cascades to 2 other objects\nCREATE TABLE drop_chunks_table(time BIGINT NOT NULL, data INTEGER);\nSELECT hypertable_id AS drop_chunks_table_id\n    FROM create_hypertable('drop_chunks_table', 'time', chunk_time_interval => 10) \\gset\nCREATE OR REPLACE FUNCTION integer_now_test() returns bigint LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), bigint '0') FROM drop_chunks_table $$;\nSELECT set_integer_now_func('drop_chunks_table', 'integer_now_test');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW drop_chunks_view\n  WITH (\n    timescaledb.continuous,\n    timescaledb.materialized_only=true\n  )\nAS SELECT time_bucket('5', time), COUNT(data)\n    FROM drop_chunks_table\n    GROUP BY 1 WITH NO DATA;\nSELECT format('%I.%I', schema_name, table_name) AS drop_chunks_mat_table,\n        schema_name AS drop_chunks_mat_schema,\n        table_name AS drop_chunks_mat_table_name\n    FROM _timescaledb_catalog.hypertable, _timescaledb_catalog.continuous_agg\n    WHERE _timescaledb_catalog.continuous_agg.raw_hypertable_id = :drop_chunks_table_id\n        AND _timescaledb_catalog.hypertable.id = _timescaledb_catalog.continuous_agg.mat_hypertable_id \\gset\n-- create 3 chunks, with 3 time bucket\nINSERT INTO drop_chunks_table SELECT i, i FROM generate_series(0, 29) AS i;\n-- Only refresh up to bucket 15 initially. Matches the old refresh\n-- behavior that didn't materialize everything\nCALL refresh_continuous_aggregate('drop_chunks_view', 0, 15);\nSELECT count(c) FROM show_chunks('drop_chunks_table') AS c;\n count \n-------\n     3\n\nSELECT count(c) FROM show_chunks('drop_chunks_view') AS c;\n count \n-------\n     1\n\nSELECT * FROM drop_chunks_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     5\n           5 |     5\n          10 |     5\n\n-- cannot drop directly from the materialization table without specifying\n-- cont. aggregate view name explicitly\n\\set ON_ERROR_STOP 0\nSELECT drop_chunks(:'drop_chunks_mat_table',\n    newer_than => -20,\n    verbose => true);\nERROR:  operation not supported on materialized hypertable\n\\set ON_ERROR_STOP 1\nSELECT count(c) FROM show_chunks('drop_chunks_table') AS c;\n count \n-------\n     3\n\nSELECT count(c) FROM show_chunks('drop_chunks_view') AS c;\n count \n-------\n     1\n\nSELECT * FROM drop_chunks_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     5\n           5 |     5\n          10 |     5\n\n-- drop chunks when the chunksize and time_bucket aren't aligned\nDROP TABLE drop_chunks_table CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_5_4_chunk\nCREATE TABLE drop_chunks_table_u(time BIGINT NOT NULL, data INTEGER);\nSELECT hypertable_id AS drop_chunks_table_u_id\n    FROM create_hypertable('drop_chunks_table_u', 'time', chunk_time_interval => 7) \\gset\nCREATE OR REPLACE FUNCTION integer_now_test1() returns bigint LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), bigint '0') FROM drop_chunks_table_u $$;\nSELECT set_integer_now_func('drop_chunks_table_u', 'integer_now_test1');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW drop_chunks_view\n  WITH (\n    timescaledb.continuous,\n    timescaledb.materialized_only=true\n  )\nAS SELECT time_bucket('3', time), COUNT(data)\n    FROM drop_chunks_table_u\n    GROUP BY 1 WITH NO DATA;\nSELECT format('%I.%I', schema_name, table_name) AS drop_chunks_mat_table_u,\n        schema_name AS drop_chunks_mat_schema,\n        table_name AS drop_chunks_mat_table_u_name\n    FROM _timescaledb_catalog.hypertable, _timescaledb_catalog.continuous_agg\n    WHERE _timescaledb_catalog.continuous_agg.raw_hypertable_id = :drop_chunks_table_u_id\n        AND _timescaledb_catalog.hypertable.id = _timescaledb_catalog.continuous_agg.mat_hypertable_id \\gset\n-- create 3 chunks, with 3 time bucket\nINSERT INTO drop_chunks_table_u SELECT i, i FROM generate_series(0, 21) AS i;\n-- Refresh up to bucket 15 to match old materializer behavior\nCALL refresh_continuous_aggregate('drop_chunks_view', 0, 15);\nSELECT count(c) FROM show_chunks('drop_chunks_table_u') AS c;\n count \n-------\n     4\n\nSELECT count(c) FROM show_chunks('drop_chunks_view') AS c;\n count \n-------\n     1\n\nSELECT * FROM drop_chunks_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     3\n           3 |     3\n           6 |     3\n           9 |     3\n          12 |     3\n\n-- TRUNCATE test\n-- Can truncate regular hypertables that have caggs\nTRUNCATE drop_chunks_table_u;\n\\set ON_ERROR_STOP 0\n-- Can't truncate materialized hypertables directly\nTRUNCATE :drop_chunks_mat_table_u;\nERROR:  cannot TRUNCATE a hypertable underlying a continuous aggregate\n\\set ON_ERROR_STOP 1\n-- Check that we don't interfere with TRUNCATE of normal table and\n-- partitioned table\nCREATE TABLE truncate (value int);\nINSERT INTO truncate VALUES (1), (2);\nTRUNCATE truncate;\nSELECT * FROM truncate;\n value \n-------\n\nCREATE TABLE truncate_partitioned (value int)\n  PARTITION BY RANGE(value);\nCREATE TABLE truncate_p1 PARTITION OF truncate_partitioned\n  FOR VALUES FROM (1) TO (3);\nINSERT INTO truncate_partitioned VALUES (1), (2);\nTRUNCATE truncate_partitioned;\nSELECT * FROM truncate_partitioned;\n value \n-------\n\n-- ALTER TABLE tests\n\\set ON_ERROR_STOP 0\n-- test a variety of ALTER TABLE statements\nALTER TABLE :drop_chunks_mat_table_u RENAME time_bucket TO bad_name;\nERROR:  renaming columns on materialization tables is not supported\nALTER TABLE :drop_chunks_mat_table_u ADD UNIQUE(time_bucket);\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u SET UNLOGGED;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u ENABLE ROW LEVEL SECURITY;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u ADD COLUMN fizzle INTEGER;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u DROP COLUMN time_bucket;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u ALTER COLUMN time_bucket DROP NOT NULL;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u ALTER COLUMN time_bucket SET DEFAULT 1;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u ALTER COLUMN time_bucket SET STORAGE EXTERNAL;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u DISABLE TRIGGER ALL;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u SET TABLESPACE foo;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u NOT OF;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u OWNER TO CURRENT_USER;\nERROR:  operation not supported on materialization tables\n\\set ON_ERROR_STOP 1\nALTER TABLE :drop_chunks_mat_table_u SET SCHEMA public;\nALTER TABLE :drop_chunks_mat_table_u_name RENAME TO new_name;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSET client_min_messages TO NOTICE;\nSELECT * FROM new_name ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     3\n           3 |     3\n           6 |     3\n           9 |     3\n          12 |     3\n\nSELECT * FROM drop_chunks_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     3\n           3 |     3\n           6 |     3\n           9 |     3\n          12 |     3\n\n\\set ON_ERROR_STOP 0\n-- no continuous aggregates on a continuous aggregate materialization table\nCREATE MATERIALIZED VIEW new_name_view\n  WITH (\n    timescaledb.continuous,\n    timescaledb.materialized_only=true\n  )\nAS SELECT time_bucket('6', time_bucket), COUNT(\"count\")\n    FROM new_name\n    GROUP BY 1 WITH NO DATA;\nERROR:  hypertable is a continuous aggregate materialization table\n\\set ON_ERROR_STOP 1\nCREATE TABLE metrics(time timestamptz NOT NULL, device_id int, v1 float, v2 float);\nSELECT create_hypertable('metrics','time');\n  create_hypertable   \n----------------------\n (8,public,metrics,t)\n\nINSERT INTO metrics SELECT generate_series('2000-01-01'::timestamptz,'2000-01-10','1m'),1,0.25,0.75;\n-- check expressions in view definition\nCREATE MATERIALIZED VIEW cagg_expr\n  WITH (timescaledb.continuous, timescaledb.materialized_only=true)\nAS\nSELECT\n  time_bucket('1d', time) AS time,\n  'Const'::text AS Const,\n  4.3::numeric AS \"numeric\",\n  first(metrics,time),\n  CASE WHEN true THEN 'foo' ELSE 'bar' END,\n  COALESCE(NULL,'coalesce'),\n  avg(v1) + avg(v2) AS avg1,\n  avg(v1+v2) AS avg2\nFROM metrics\nGROUP BY 1 WITH NO DATA;\nCALL refresh_continuous_aggregate('cagg_expr', NULL, NULL);\nSELECT * FROM cagg_expr ORDER BY time LIMIT 5;\n             time             | const | numeric |                    first                     | case | coalesce | avg1 | avg2 \n------------------------------+-------+---------+----------------------------------------------+------+----------+------+------\n Fri Dec 31 16:00:00 1999 UTC | Const |     4.3 | (\"Sat Jan 01 00:00:00 2000 UTC\",1,0.25,0.75) | foo  | coalesce |    1 |    1\n Sat Jan 01 16:00:00 2000 UTC | Const |     4.3 | (\"Sat Jan 01 16:00:00 2000 UTC\",1,0.25,0.75) | foo  | coalesce |    1 |    1\n Sun Jan 02 16:00:00 2000 UTC | Const |     4.3 | (\"Sun Jan 02 16:00:00 2000 UTC\",1,0.25,0.75) | foo  | coalesce |    1 |    1\n Mon Jan 03 16:00:00 2000 UTC | Const |     4.3 | (\"Mon Jan 03 16:00:00 2000 UTC\",1,0.25,0.75) | foo  | coalesce |    1 |    1\n Tue Jan 04 16:00:00 2000 UTC | Const |     4.3 | (\"Tue Jan 04 16:00:00 2000 UTC\",1,0.25,0.75) | foo  | coalesce |    1 |    1\n\n--test materialization of invalidation before drop\nDROP TABLE IF EXISTS drop_chunks_table CASCADE;\nNOTICE:  table \"drop_chunks_table\" does not exist, skipping\nDROP TABLE IF EXISTS drop_chunks_table_u CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_7_9_chunk\nCREATE TABLE drop_chunks_table(time BIGINT NOT NULL, data INTEGER);\nSELECT hypertable_id AS drop_chunks_table_nid\n    FROM create_hypertable('drop_chunks_table', 'time', chunk_time_interval => 10) \\gset\nCREATE OR REPLACE FUNCTION integer_now_test2() returns bigint LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), bigint '0') FROM drop_chunks_table $$;\nSELECT set_integer_now_func('drop_chunks_table', 'integer_now_test2');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW drop_chunks_view\n  WITH (\n    timescaledb.continuous,\n    timescaledb.materialized_only=true\n  )\nAS SELECT time_bucket('5', time), max(data)\n    FROM drop_chunks_table\n    GROUP BY 1 WITH NO DATA;\nINSERT INTO drop_chunks_table SELECT i, i FROM generate_series(0, 20) AS i;\n--dropping chunks will process the invalidations\nSELECT drop_chunks('drop_chunks_table', older_than => (integer_now_test2()-9));\n               drop_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_10_13_chunk\n\nSELECT * FROM drop_chunks_table ORDER BY time ASC limit 1;\n time | data \n------+------\n   10 |   10\n\nINSERT INTO drop_chunks_table SELECT i, i FROM generate_series(20, 35) AS i;\nCALL refresh_continuous_aggregate('drop_chunks_view', 10, 40);\n--this will be seen after the drop its within the invalidation window and will be dropped\nINSERT INTO drop_chunks_table VALUES (26, 100);\n--this will not be processed by the drop since chunk 30-39 is not dropped but will be seen after refresh\n--shows that the drop doesn't do more work than necessary\nINSERT INTO drop_chunks_table VALUES (31, 200);\n--move the time up to 39\nINSERT INTO drop_chunks_table SELECT i, i FROM generate_series(35, 39) AS i;\n--the chunks and ranges we have thus far\nSELECT chunk_name, range_start_integer, range_end_integer\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'drop_chunks_table';\n     chunk_name     | range_start_integer | range_end_integer \n--------------------+---------------------+-------------------\n _hyper_10_14_chunk |                  10 |                20\n _hyper_10_15_chunk |                  20 |                30\n _hyper_10_16_chunk |                  30 |                40\n\n--the invalidation on 25 not yet seen\nSELECT * FROM drop_chunks_view ORDER BY time_bucket DESC;\n time_bucket | max \n-------------+-----\n          35 |  35\n          30 |  34\n          25 |  29\n          20 |  24\n          15 |  19\n          10 |  14\n\n--refresh to process the invalidations and then drop\nCALL refresh_continuous_aggregate('drop_chunks_view', NULL, (integer_now_test2()-9));\nSELECT drop_chunks('drop_chunks_table', older_than => (integer_now_test2()-9));\n               drop_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_10_14_chunk\n _timescaledb_internal._hyper_10_15_chunk\n\n--new values on 25 now seen in view\nSELECT * FROM drop_chunks_view ORDER BY time_bucket DESC;\n time_bucket | max \n-------------+-----\n          35 |  35\n          30 |  34\n          25 | 100\n          20 |  24\n          15 |  19\n          10 |  14\n\n--earliest datapoint now in table\nSELECT * FROM drop_chunks_table ORDER BY time ASC limit 1;\n time | data \n------+------\n   30 |   30\n\n--still see data in the view\nSELECT * FROM drop_chunks_view WHERE time_bucket < (integer_now_test2()-9) ORDER BY time_bucket DESC;\n time_bucket | max \n-------------+-----\n          25 | 100\n          20 |  24\n          15 |  19\n          10 |  14\n\n--no data but covers dropped chunks\nSELECT * FROM drop_chunks_table WHERE time < (integer_now_test2()-9) ORDER BY time DESC;\n time | data \n------+------\n\n--recreate the dropped chunk\nINSERT INTO drop_chunks_table SELECT i, i FROM generate_series(0, 20) AS i;\n--see data from recreated region\nSELECT * FROM drop_chunks_table WHERE time < (integer_now_test2()-9) ORDER BY time DESC;\n time | data \n------+------\n   20 |   20\n   19 |   19\n   18 |   18\n   17 |   17\n   16 |   16\n   15 |   15\n   14 |   14\n   13 |   13\n   12 |   12\n   11 |   11\n   10 |   10\n    9 |    9\n    8 |    8\n    7 |    7\n    6 |    6\n    5 |    5\n    4 |    4\n    3 |    3\n    2 |    2\n    1 |    1\n    0 |    0\n\n--should show chunk with old name and old ranges\nSELECT chunk_name, range_start_integer, range_end_integer\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'drop_chunks_table'\nORDER BY range_start_integer;\n     chunk_name     | range_start_integer | range_end_integer \n--------------------+---------------------+-------------------\n _hyper_10_18_chunk |                   0 |                10\n _hyper_10_19_chunk |                  10 |                20\n _hyper_10_20_chunk |                  20 |                30\n _hyper_10_16_chunk |                  30 |                40\n\n--We dropped everything up to the bucket starting at 30 and then\n--inserted new data up to and including time 20. Therefore, the\n--dropped data should stay the same as long as we only refresh\n--buckets that have non-dropped data.\nCALL refresh_continuous_aggregate('drop_chunks_view', 30, 40);\nSELECT * FROM drop_chunks_view ORDER BY time_bucket DESC;\n time_bucket | max \n-------------+-----\n          35 |  39\n          30 | 200\n          25 | 100\n          20 |  24\n          15 |  19\n          10 |  14\n\nSELECT format('%I.%I', schema_name, table_name) AS drop_chunks_mat_tablen,\n        schema_name AS drop_chunks_mat_schema,\n        table_name AS drop_chunks_mat_table_name\n    FROM _timescaledb_catalog.hypertable, _timescaledb_catalog.continuous_agg\n    WHERE _timescaledb_catalog.continuous_agg.raw_hypertable_id = :drop_chunks_table_nid\n        AND _timescaledb_catalog.hypertable.id = _timescaledb_catalog.continuous_agg.mat_hypertable_id \\gset\n-- TEST drop chunks from continuous aggregates by specifying view name\nSELECT drop_chunks('drop_chunks_view',\n    newer_than => -20,\n    verbose => true);\nINFO:  dropping chunk _timescaledb_internal._hyper_11_17_chunk\n               drop_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_11_17_chunk\n\n-- Test that we cannot drop chunks when specifying materialized\n-- hypertable\nINSERT INTO drop_chunks_table SELECT generate_series(45, 55), 500;\nCALL refresh_continuous_aggregate('drop_chunks_view', 45, 55);\nSELECT chunk_name, range_start_integer, range_end_integer\nFROM timescaledb_information.chunks\nWHERE hypertable_name = :'drop_chunks_mat_table_name' ORDER BY range_start_integer;\n     chunk_name     | range_start_integer | range_end_integer \n--------------------+---------------------+-------------------\n _hyper_11_23_chunk |                   0 |               100\n\n\\set ON_ERROR_STOP 0\n\\set VERBOSITY default\nSELECT drop_chunks(:'drop_chunks_mat_tablen', older_than => 60);\nERROR:  operation not supported on materialized hypertable\nDETAIL:  Hypertable \"_materialized_hypertable_11\" is a materialized hypertable.\nHINT:  Try the operation on the continuous aggregate instead.\n\\set VERBOSITY terse\n\\set ON_ERROR_STOP 1\n-----------------------------------------------------------------\n-- Test that refresh_continuous_aggregate on chunk will refresh,\n-- but only in the regions covered by the show chunks.\n-----------------------------------------------------------------\nSELECT chunk_name, range_start_integer, range_end_integer\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'drop_chunks_table'\nORDER BY 2,3;\n     chunk_name     | range_start_integer | range_end_integer \n--------------------+---------------------+-------------------\n _hyper_10_18_chunk |                   0 |                10\n _hyper_10_19_chunk |                  10 |                20\n _hyper_10_20_chunk |                  20 |                30\n _hyper_10_16_chunk |                  30 |                40\n _hyper_10_21_chunk |                  40 |                50\n _hyper_10_22_chunk |                  50 |                60\n\n-- Pick the second chunk as the one to drop\nWITH numbered_chunks AS (\n     SELECT row_number() OVER (ORDER BY range_start_integer), chunk_schema, chunk_name, range_start_integer, range_end_integer\n     FROM timescaledb_information.chunks\n     WHERE hypertable_name = 'drop_chunks_table'\n     ORDER BY 1\n)\nSELECT format('%I.%I', chunk_schema, chunk_name) AS chunk_to_drop, range_start_integer, range_end_integer\nFROM numbered_chunks\nWHERE row_number = 2 \\gset\n-- There's data in the table for the chunk/range we will drop\nSELECT * FROM drop_chunks_table\nWHERE time >= :range_start_integer\nAND time < :range_end_integer\nORDER BY 1;\n time | data \n------+------\n   10 |   10\n   11 |   11\n   12 |   12\n   13 |   13\n   14 |   14\n   15 |   15\n   16 |   16\n   17 |   17\n   18 |   18\n   19 |   19\n\n-- Make sure there is also data in the continuous aggregate\n-- CARE:\n-- Note that this behaviour of dropping the materialization table chunks and expecting a refresh\n-- that overlaps that time range to NOT update those chunks is undefined.\nCALL refresh_continuous_aggregate('drop_chunks_view', 0, 50);\nSELECT * FROM drop_chunks_view\nORDER BY 1;\n time_bucket | max \n-------------+-----\n           0 |   4\n           5 |   9\n          10 |  14\n          15 |  19\n          20 |  20\n          45 | 500\n          50 | 500\n\n-- Drop the second chunk, to leave a gap in the data\nDROP TABLE :chunk_to_drop;\n-- Verify that the second chunk is dropped\nSELECT chunk_name, range_start_integer, range_end_integer\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'drop_chunks_table'\nORDER BY 2,3;\n     chunk_name     | range_start_integer | range_end_integer \n--------------------+---------------------+-------------------\n _hyper_10_18_chunk |                   0 |                10\n _hyper_10_20_chunk |                  20 |                30\n _hyper_10_16_chunk |                  30 |                40\n _hyper_10_21_chunk |                  40 |                50\n _hyper_10_22_chunk |                  50 |                60\n\n-- Data is no longer in the table but still in the view\nSELECT * FROM drop_chunks_table\nWHERE time >= :range_start_integer\nAND time < :range_end_integer\nORDER BY 1;\n time | data \n------+------\n\nSELECT * FROM drop_chunks_view\nWHERE time_bucket >= :range_start_integer\nAND time_bucket < :range_end_integer\nORDER BY 1;\n time_bucket | max \n-------------+-----\n          10 |  14\n          15 |  19\n\n-- Insert a large value in one of the chunks that will be dropped\nINSERT INTO drop_chunks_table VALUES (:range_start_integer-1, 100);\n-- Now refresh and drop the two adjecent chunks\nCALL refresh_continuous_aggregate('drop_chunks_view', NULL, 30);\nSELECT drop_chunks('drop_chunks_table', older_than=>30);\n               drop_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_10_18_chunk\n _timescaledb_internal._hyper_10_20_chunk\n\n-- Verify that the chunks are dropped\nSELECT chunk_name, range_start_integer, range_end_integer\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'drop_chunks_table'\nORDER BY 2,3;\n     chunk_name     | range_start_integer | range_end_integer \n--------------------+---------------------+-------------------\n _hyper_10_16_chunk |                  30 |                40\n _hyper_10_21_chunk |                  40 |                50\n _hyper_10_22_chunk |                  50 |                60\n\n-- The continuous aggregate should be refreshed in the regions covered\n-- by the dropped chunks, but not in the \"gap\" region, i.e., the\n-- region of the chunk that was dropped via DROP TABLE.\nSELECT * FROM drop_chunks_view\nORDER BY 1;\n time_bucket | max \n-------------+-----\n           0 |   4\n           5 | 100\n          20 |  20\n          45 | 500\n          50 | 500\n\n-- Now refresh in the region of the first two dropped chunks\nCALL refresh_continuous_aggregate('drop_chunks_view', 0, :range_end_integer);\n-- Aggregate data in the refreshed range should no longer exist since\n-- the underlying data was dropped.\nSELECT * FROM drop_chunks_view\nORDER BY 1;\n time_bucket | max \n-------------+-----\n          20 |  20\n          45 | 500\n          50 | 500\n\n--------------------------------------------------------------------\n-- Check that we can create a materialized table in a tablespace. We\n-- create one with tablespace and one without and compare them.\nCREATE VIEW cagg_info AS\nWITH\n  caggs AS (\n    SELECT format('%I.%I', user_view_schema, user_view_name)::regclass AS user_view,\n           format('%I.%I', direct_view_schema, direct_view_name)::regclass AS direct_view,\n           format('%I.%I', partial_view_schema, partial_view_name)::regclass AS partial_view,\n           format('%I.%I', ht.schema_name, ht.table_name)::regclass AS mat_relid\n      FROM _timescaledb_catalog.hypertable ht,\n           _timescaledb_catalog.continuous_agg cagg\n     WHERE ht.id = cagg.mat_hypertable_id\n  )\nSELECT user_view,\n       pg_get_userbyid(relowner) AS user_view_owner,\n       relname AS mat_table,\n       (SELECT pg_get_userbyid(relowner) FROM pg_class WHERE oid = mat_relid) AS mat_table_owner,\n       direct_view,\n       (SELECT pg_get_userbyid(relowner) FROM pg_class WHERE oid = direct_view) AS direct_view_owner,\n       partial_view,\n       (SELECT pg_get_userbyid(relowner) FROM pg_class WHERE oid = partial_view) AS partial_view_owner,\n       (SELECT spcname FROM pg_tablespace WHERE oid = reltablespace) AS tablespace\n  FROM pg_class JOIN caggs ON pg_class.oid = caggs.mat_relid;\nGRANT SELECT ON cagg_info TO PUBLIC;\nCREATE VIEW chunk_info AS\nSELECT ht.schema_name, ht.table_name, relname AS chunk_name,\n       (SELECT spcname FROM pg_tablespace WHERE oid = reltablespace) AS tablespace\n  FROM pg_class c,\n       _timescaledb_catalog.hypertable ht,\n       _timescaledb_catalog.chunk ch\n WHERE ch.table_name = c.relname AND ht.id = ch.hypertable_id;\nCREATE TABLE whatever(time BIGINT NOT NULL, data INTEGER);\nSELECT hypertable_id AS whatever_nid\n  FROM create_hypertable('whatever', 'time', chunk_time_interval => 10)\n\\gset\nSELECT set_integer_now_func('whatever', 'integer_now_test');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW whatever_view_1\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT time_bucket('5', time), COUNT(data)\n  FROM whatever GROUP BY 1 WITH NO DATA;\nCREATE MATERIALIZED VIEW whatever_view_2\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nTABLESPACE tablespace1 AS\nSELECT time_bucket('5', time), COUNT(data)\n  FROM whatever GROUP BY 1 WITH NO DATA;\nINSERT INTO whatever SELECT i, i FROM generate_series(0, 29) AS i;\nCALL refresh_continuous_aggregate('whatever_view_1', NULL, NULL);\nCALL refresh_continuous_aggregate('whatever_view_2', NULL, NULL);\nSELECT user_view,\n       mat_table,\n       cagg_info.tablespace AS mat_tablespace,\n       chunk_name,\n       chunk_info.tablespace AS chunk_tablespace\n  FROM cagg_info, chunk_info\n WHERE mat_table::text = table_name\n   AND user_view::text LIKE 'whatever_view%';\n    user_view    |          mat_table          | mat_tablespace |     chunk_name     | chunk_tablespace \n-----------------+-----------------------------+----------------+--------------------+------------------\n whatever_view_1 | _materialized_hypertable_13 |                | _hyper_13_27_chunk | \n whatever_view_2 | _materialized_hypertable_14 | tablespace1    | _hyper_14_28_chunk | tablespace1\n\nALTER MATERIALIZED VIEW whatever_view_1 SET TABLESPACE tablespace2;\nSELECT user_view,\n       mat_table,\n       cagg_info.tablespace AS mat_tablespace,\n       chunk_name,\n       chunk_info.tablespace AS chunk_tablespace\n  FROM cagg_info, chunk_info\n WHERE mat_table::text = table_name\n   AND user_view::text LIKE 'whatever_view%';\n    user_view    |          mat_table          | mat_tablespace |     chunk_name     | chunk_tablespace \n-----------------+-----------------------------+----------------+--------------------+------------------\n whatever_view_1 | _materialized_hypertable_13 | tablespace2    | _hyper_13_27_chunk | tablespace2\n whatever_view_2 | _materialized_hypertable_14 | tablespace1    | _hyper_14_28_chunk | tablespace1\n\nDROP MATERIALIZED VIEW whatever_view_1;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_13_27_chunk\nDROP MATERIALIZED VIEW whatever_view_2;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_14_28_chunk\n-- test bucket width expressions on integer hypertables\nCREATE TABLE metrics_int2 (\n  time int2 NOT NULL,\n  device_id int,\n  v1 float,\n  v2 float\n);\nCREATE TABLE metrics_int4 (\n  time int4 NOT NULL,\n  device_id int,\n  v1 float,\n  v2 float\n);\nCREATE TABLE metrics_int8 (\n  time int8 NOT NULL,\n  device_id int,\n  v1 float,\n  v2 float\n);\nSELECT create_hypertable (('metrics_' || dt)::regclass, 'time', chunk_time_interval => 10)\nFROM (\n  VALUES ('int2'),\n    ('int4'),\n    ('int8')) v (dt);\n     create_hypertable      \n----------------------------\n (15,public,metrics_int2,t)\n (16,public,metrics_int4,t)\n (17,public,metrics_int8,t)\n\nCREATE OR REPLACE FUNCTION int2_now ()\n  RETURNS int2\n  LANGUAGE SQL\n  STABLE\n  AS $$\n  SELECT 10::int2\n$$;\nCREATE OR REPLACE FUNCTION int4_now ()\n  RETURNS int4\n  LANGUAGE SQL\n  STABLE\n  AS $$\n  SELECT 10::int4\n$$;\nCREATE OR REPLACE FUNCTION int8_now ()\n  RETURNS int8\n  LANGUAGE SQL\n  STABLE\n  AS $$\n  SELECT 10::int8\n$$;\nSELECT set_integer_now_func (('metrics_' || dt)::regclass, (dt || '_now')::regproc)\nFROM (\n  VALUES ('int2'),\n    ('int4'),\n    ('int8')) v (dt);\n set_integer_now_func \n----------------------\n \n \n \n\n-- width expression for int2 hypertables\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1::smallint, time)\nFROM metrics_int2\nGROUP BY 1;\nNOTICE:  continuous aggregate \"width_expr\" is already up-to-date\nDROP MATERIALIZED VIEW width_expr;\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1::smallint + 2::smallint, time)\nFROM metrics_int2\nGROUP BY 1;\nNOTICE:  continuous aggregate \"width_expr\" is already up-to-date\nDROP MATERIALIZED VIEW width_expr;\n-- width expression for int4 hypertables\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1, time)\nFROM metrics_int4\nGROUP BY 1;\nNOTICE:  continuous aggregate \"width_expr\" is already up-to-date\nDROP MATERIALIZED VIEW width_expr;\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1 + 2, time)\nFROM metrics_int4\nGROUP BY 1;\nNOTICE:  continuous aggregate \"width_expr\" is already up-to-date\nDROP MATERIALIZED VIEW width_expr;\n-- width expression for int8 hypertables\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1, time)\nFROM metrics_int8\nGROUP BY 1;\nNOTICE:  continuous aggregate \"width_expr\" is already up-to-date\nDROP MATERIALIZED VIEW width_expr;\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1 + 2, time)\nFROM metrics_int8\nGROUP BY 1;\nNOTICE:  continuous aggregate \"width_expr\" is already up-to-date\nDROP MATERIALIZED VIEW width_expr;\n\\set ON_ERROR_STOP 0\n-- non-immutable expresions should be rejected\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(extract(year FROM now())::smallint, time)\nFROM metrics_int2\nGROUP BY 1;\nERROR:  only immutable expressions allowed in time bucket function\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(extract(year FROM now())::int, time)\nFROM metrics_int4\nGROUP BY 1;\nERROR:  only immutable expressions allowed in time bucket function\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(extract(year FROM now())::int, time)\nFROM metrics_int8\nGROUP BY 1;\nERROR:  only immutable expressions allowed in time bucket function\n\\set ON_ERROR_STOP 1\n-- Test various ALTER MATERIALIZED VIEW statements.\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nCREATE MATERIALIZED VIEW owner_check WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1 + 2, time)\nFROM metrics_int8\nGROUP BY 1\nWITH NO DATA;\n\\x on\nSELECT * FROM cagg_info WHERE user_view::text = 'owner_check';\n-[ RECORD 1 ]------+---------------------------------------\nuser_view          | owner_check\nuser_view_owner    | default_perm_user\nmat_table          | _materialized_hypertable_24\nmat_table_owner    | default_perm_user\ndirect_view        | _timescaledb_internal._direct_view_24\ndirect_view_owner  | default_perm_user\npartial_view       | _timescaledb_internal._partial_view_24\npartial_view_owner | default_perm_user\ntablespace         | \n\n\\x off\n-- This should not work since the target user has the wrong role, but\n-- we test that the normal checks are done when changing the owner.\n\\set ON_ERROR_STOP 0\nALTER MATERIALIZED VIEW owner_check OWNER TO :ROLE_1;\nERROR:  must be able to SET ROLE \"test_role_1\"\n\\set ON_ERROR_STOP 1\n-- Superuser can always change owner\nSET ROLE :ROLE_SUPERUSER;\n-- Add a refresh policy before changing owner to verify job owner is propagated\nSELECT add_continuous_aggregate_policy('owner_check', NULL, 1::int8, '1 day'::interval) AS cagg_job_id \\gset\nSELECT owner FROM _timescaledb_config.bgw_job WHERE id = :cagg_job_id;\n       owner       \n-------------------\n default_perm_user\n\nALTER MATERIALIZED VIEW owner_check OWNER TO :ROLE_1;\n\\x on\nSELECT * FROM cagg_info WHERE user_view::text = 'owner_check';\n-[ RECORD 1 ]------+---------------------------------------\nuser_view          | owner_check\nuser_view_owner    | test_role_1\nmat_table          | _materialized_hypertable_24\nmat_table_owner    | test_role_1\ndirect_view        | _timescaledb_internal._direct_view_24\ndirect_view_owner  | test_role_1\npartial_view       | _timescaledb_internal._partial_view_24\npartial_view_owner | test_role_1\ntablespace         | \n\n\\x off\n-- make sure policy job owner is propagated\nSELECT owner FROM _timescaledb_config.bgw_job WHERE id = :cagg_job_id;\n    owner    \n-------------\n test_role_1\n\nSELECT remove_continuous_aggregate_policy('owner_check');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n--\n-- Test drop continuous aggregate cases\n--\n-- Issue: #2608\n--\nCREATE OR REPLACE FUNCTION test_int_now()\n  RETURNS INT LANGUAGE SQL STABLE AS\n$BODY$\n  SELECT 50;\n$BODY$;\nCREATE TABLE conditionsnm(time_int INT NOT NULL, device INT, value FLOAT);\nSELECT create_hypertable('conditionsnm', 'time_int', chunk_time_interval => 10);\n     create_hypertable      \n----------------------------\n (25,public,conditionsnm,t)\n\nSELECT set_integer_now_func('conditionsnm', 'test_int_now');\n set_integer_now_func \n----------------------\n \n\nINSERT INTO conditionsnm\nSELECT time_val, time_val % 4, 3.14 FROM generate_series(0,100,1) AS time_val;\n-- Case 1: DROP\nCREATE MATERIALIZED VIEW conditionsnm_4\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE)\nAS\nSELECT time_bucket(7, time_int) as bucket,\nSUM(value), COUNT(value)\nFROM conditionsnm GROUP BY bucket WITH DATA;\nNOTICE:  refreshing continuous aggregate \"conditionsnm_4\"\nDROP materialized view conditionsnm_4;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_26_40_chunk\n-- Case 2: DROP CASCADE should have similar behaviour as DROP\nCREATE MATERIALIZED VIEW conditionsnm_4\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE)\nAS\nSELECT time_bucket(7, time_int) as bucket,\nSUM(value), COUNT(value)\nFROM conditionsnm GROUP BY bucket WITH DATA;\nNOTICE:  refreshing continuous aggregate \"conditionsnm_4\"\nDROP materialized view conditionsnm_4 CASCADE;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_27_41_chunk\n-- Case 3: require CASCADE in case of dependent object\nCREATE MATERIALIZED VIEW conditionsnm_4\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE)\nAS\nSELECT time_bucket(7, time_int) as bucket,\nSUM(value), COUNT(value)\nFROM conditionsnm GROUP BY bucket WITH DATA;\nNOTICE:  refreshing continuous aggregate \"conditionsnm_4\"\nCREATE VIEW see_cagg as select * from conditionsnm_4;\n\\set ON_ERROR_STOP 0\nDROP MATERIALIZED VIEW conditionsnm_4;\nERROR:  cannot drop view conditionsnm_4 because other objects depend on it\n\\set ON_ERROR_STOP 1\n-- Case 4: DROP CASCADE with dependency\nDROP MATERIALIZED VIEW conditionsnm_4 CASCADE;\nNOTICE:  drop cascades to view see_cagg\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_28_42_chunk\n-- Test DROP SCHEMA CASCADE with continuous aggregates\n--\n-- Issue: #2350\n--\n-- Case 1: DROP SCHEMA CASCADE\nCREATE SCHEMA test_schema;\nCREATE TABLE test_schema.telemetry_raw (\n  ts        TIMESTAMP WITH TIME ZONE NOT NULL,\n  value     DOUBLE PRECISION\n);\nSELECT create_hypertable('test_schema.telemetry_raw', 'ts');\n        create_hypertable         \n----------------------------------\n (29,test_schema,telemetry_raw,t)\n\nCREATE MATERIALIZED VIEW test_schema.telemetry_1s\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false)\n    AS\nSELECT time_bucket(INTERVAL '1s', ts) AS ts_1s,\n       avg(value)\n  FROM test_schema.telemetry_raw\n GROUP BY ts_1s WITH NO DATA;\nSELECT ca.raw_hypertable_id,\n       h.schema_name,\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON (h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'telemetry_1s';\n raw_hypertable_id |      schema_name      |       MAT_TABLE_NAME        |  PART_VIEW_NAME  |  partial_view_schema  \n-------------------+-----------------------+-----------------------------+------------------+-----------------------\n                29 | _timescaledb_internal | _materialized_hypertable_30 | _partial_view_30 | _timescaledb_internal\n\n\\gset\nDROP SCHEMA test_schema CASCADE;\nNOTICE:  drop cascades to 4 other objects\nSELECT count(*) FROM pg_class WHERE relname = :'MAT_TABLE_NAME';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = :'PART_VIEW_NAME';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = 'telemetry_1s';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_namespace WHERE nspname = 'test_schema';\n count \n-------\n     0\n\n-- Case 2: DROP SCHEMA CASCADE with multiple caggs\nCREATE SCHEMA test_schema;\nCREATE TABLE test_schema.telemetry_raw (\n  ts        TIMESTAMP WITH TIME ZONE NOT NULL,\n  value     DOUBLE PRECISION\n);\nSELECT create_hypertable('test_schema.telemetry_raw', 'ts');\n        create_hypertable         \n----------------------------------\n (31,test_schema,telemetry_raw,t)\n\nCREATE MATERIALIZED VIEW test_schema.cagg1\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false)\n    AS\nSELECT time_bucket(INTERVAL '1s', ts) AS ts_1s,\n       avg(value)\n  FROM test_schema.telemetry_raw\n GROUP BY ts_1s WITH NO DATA;\nCREATE MATERIALIZED VIEW test_schema.cagg2\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false)\n    AS\nSELECT time_bucket(INTERVAL '1s', ts) AS ts_1s,\n       avg(value)\n  FROM test_schema.telemetry_raw\n GROUP BY ts_1s WITH NO DATA;\nSELECT ca.raw_hypertable_id,\n       h.schema_name,\n       h.table_name AS \"MAT_TABLE_NAME1\",\n       partial_view_name as \"PART_VIEW_NAME1\",\n       partial_view_schema\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON (h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'cagg1';\n raw_hypertable_id |      schema_name      |       MAT_TABLE_NAME1       | PART_VIEW_NAME1  |  partial_view_schema  \n-------------------+-----------------------+-----------------------------+------------------+-----------------------\n                31 | _timescaledb_internal | _materialized_hypertable_32 | _partial_view_32 | _timescaledb_internal\n\n\\gset\nSELECT ca.raw_hypertable_id,\n       h.schema_name,\n       h.table_name AS \"MAT_TABLE_NAME2\",\n       partial_view_name as \"PART_VIEW_NAME2\",\n       partial_view_schema\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON (h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'cagg2';\n raw_hypertable_id |      schema_name      |       MAT_TABLE_NAME2       | PART_VIEW_NAME2  |  partial_view_schema  \n-------------------+-----------------------+-----------------------------+------------------+-----------------------\n                31 | _timescaledb_internal | _materialized_hypertable_33 | _partial_view_33 | _timescaledb_internal\n\n\\gset\nDROP SCHEMA test_schema CASCADE;\nNOTICE:  drop cascades to 7 other objects\nSELECT count(*) FROM pg_class WHERE relname = :'MAT_TABLE_NAME1';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = :'PART_VIEW_NAME1';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = 'cagg1';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = :'MAT_TABLE_NAME2';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = :'PART_VIEW_NAME2';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = 'cagg2';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_namespace WHERE nspname = 'test_schema';\n count \n-------\n     0\n\nDROP TABLESPACE tablespace1;\nDROP TABLESPACE tablespace2;\n-- Check that we can rename a column of a materialized view and still\n-- rebuild it after (#3051, #3405)\nCREATE TABLE conditions (\n       time TIMESTAMPTZ NOT NULL,\n       location TEXT NOT NULL,\n       temperature DOUBLE PRECISION NULL\n);\nSELECT create_hypertable('conditions', 'time');\n    create_hypertable     \n--------------------------\n (34,public,conditions,t)\n\nINSERT INTO conditions VALUES ( '2018-01-01 09:20:00-08', 'SFO', 55);\nINSERT INTO conditions VALUES ( '2018-01-02 09:30:00-08', 'por', 100);\nINSERT INTO conditions VALUES ( '2018-01-02 09:20:00-08', 'SFO', 65);\nINSERT INTO conditions VALUES ( '2018-01-02 09:10:00-08', 'NYC', 65);\nINSERT INTO conditions VALUES ( '2018-11-01 09:20:00-08', 'NYC', 45);\nINSERT INTO conditions VALUES ( '2018-11-01 10:40:00-08', 'NYC', 55);\nINSERT INTO conditions VALUES ( '2018-11-01 11:50:00-08', 'NYC', 65);\nINSERT INTO conditions VALUES ( '2018-11-01 12:10:00-08', 'NYC', 75);\nINSERT INTO conditions VALUES ( '2018-11-01 13:10:00-08', 'NYC', 85);\nINSERT INTO conditions VALUES ( '2018-11-02 09:20:00-08', 'NYC', 10);\nINSERT INTO conditions VALUES ( '2018-11-02 10:30:00-08', 'NYC', 20);\nCREATE MATERIALIZED VIEW conditions_daily\nWITH (timescaledb.continuous, timescaledb.materialized_only = true) AS\nSELECT location,\n       time_bucket(INTERVAL '1 day', time) AS bucket,\n       AVG(temperature)\n  FROM conditions\nGROUP BY location, bucket\nWITH NO DATA;\nCREATE MATERIALIZED VIEW conditions_weekly\nWITH (timescaledb.continuous, timescaledb.materialized_only = true) AS\nSELECT location,\n       time_bucket(INTERVAL '7 day', bucket) AS bucket,\n       AVG(avg)\n  FROM conditions_daily\nGROUP BY 1, 2\nWITH NO DATA;\nSELECT format('%I.%I', '_timescaledb_internal', h.table_name) AS \"MAT_TABLE_NAME\",\n       format('%I.%I', '_timescaledb_internal', partial_view_name) AS \"PART_VIEW_NAME\",\n       format('%I.%I', '_timescaledb_internal', direct_view_name) AS \"DIRECT_VIEW_NAME\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON (h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'conditions_daily'\n\\gset\n-- Show both the columns and the view definitions to see that\n-- references are correct in the view as well.\nSELECT * FROM test.show_columns('conditions_daily');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n bucket   | timestamp with time zone | f\n avg      | double precision         | f\n\nSELECT * FROM test.show_columns(:'DIRECT_VIEW_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n bucket   | timestamp with time zone | f\n avg      | double precision         | f\n\nSELECT * FROM test.show_columns(:'PART_VIEW_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n bucket   | timestamp with time zone | f\n avg      | double precision         | f\n\nSELECT * FROM test.show_columns(:'MAT_TABLE_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n bucket   | timestamp with time zone | t\n avg      | double precision         | f\n\nALTER MATERIALIZED VIEW conditions_daily RENAME COLUMN bucket to \"time\";\n-- Show both the columns and the view definitions to see that\n-- references are correct in the view as well.\nSELECT * FROM test.show_columns(' conditions_daily');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | f\n avg      | double precision         | f\n\nSELECT * FROM test.show_columns(:'DIRECT_VIEW_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | f\n avg      | double precision         | f\n\nSELECT * FROM test.show_columns(:'PART_VIEW_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | f\n avg      | double precision         | f\n\nSELECT * FROM test.show_columns(:'MAT_TABLE_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | t\n avg      | double precision         | f\n\n-- This will rebuild the materialized view and should succeed.\nALTER MATERIALIZED VIEW conditions_daily SET (timescaledb.materialized_only = false);\n-- Refresh the continuous aggregate to check that it works after the\n-- rename.\n\\set VERBOSITY verbose\nCALL refresh_continuous_aggregate('conditions_daily', NULL, NULL);\n\\set VERBOSITY terse\n-- Rename another column after the flip and verify toggling back and\n-- forth still works. This exercises the rename when the user view\n-- already has a UNION ALL query (materialized_only = false).\nALTER MATERIALIZED VIEW conditions_daily RENAME COLUMN avg TO average;\nSELECT * FROM test.show_columns('conditions_daily');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | f\n average  | double precision         | f\n\nSELECT * FROM test.show_columns(:'DIRECT_VIEW_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | f\n average  | double precision         | f\n\nSELECT * FROM test.show_columns(:'MAT_TABLE_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | t\n average  | double precision         | f\n\nALTER MATERIALIZED VIEW conditions_daily SET (timescaledb.materialized_only = true);\nALTER MATERIALIZED VIEW conditions_daily SET (timescaledb.materialized_only = false);\n-- Verify data is still accessible after multiple renames and toggles.\nSELECT * FROM conditions_daily ORDER BY location COLLATE \"C\", time;\n location |             time             | average \n----------+------------------------------+---------\n NYC      | Mon Jan 01 16:00:00 2018 UTC |      65\n NYC      | Wed Oct 31 16:00:00 2018 UTC |      65\n NYC      | Thu Nov 01 16:00:00 2018 UTC |      15\n SFO      | Sun Dec 31 16:00:00 2017 UTC |      55\n SFO      | Mon Jan 01 16:00:00 2018 UTC |      65\n por      | Mon Jan 01 16:00:00 2018 UTC |     100\n\n-- check hierarchical continuous aggregate still works after renames and toggles on the underlying cagg\nALTER MATERIALIZED VIEW conditions_weekly SET (timescaledb.materialized_only = false);\nALTER MATERIALIZED VIEW conditions_weekly SET (timescaledb.materialized_only = true);\nSELECT * FROM conditions_weekly ORDER BY location COLLATE \"C\", bucket;\n location | bucket | avg \n----------+--------+-----\n\n-- Verify that direct rename on the materialization hypertable is blocked.\n\\set ON_ERROR_STOP 0\nALTER TABLE :MAT_TABLE_NAME RENAME COLUMN average TO avg;\nERROR:  renaming columns on materialization tables is not supported\n\\set ON_ERROR_STOP 1\n-- Rename back so subsequent tests that reference \"avg\" still work.\nALTER MATERIALIZED VIEW conditions_daily RENAME COLUMN average TO avg;\n--\n-- Indexes on continuous aggregate\n--\n\\set ON_ERROR_STOP 0\n-- unique indexes are not supported\nCREATE UNIQUE INDEX index_unique_error ON conditions_daily (\"time\", location);\nERROR:  continuous aggregates do not support UNIQUE indexes\n-- concurrently index creation not supported\nCREATE INDEX CONCURRENTLY index_concurrently_avg ON conditions_daily (avg);\nERROR:  hypertables do not support concurrent index creation\n\\set ON_ERROR_STOP 1\nCREATE INDEX index_avg ON conditions_daily (avg);\nCREATE INDEX index_avg_only ON ONLY conditions_daily (avg);\nCREATE INDEX index_avg_include ON conditions_daily (avg) INCLUDE (location);\nCREATE INDEX index_avg_expr ON conditions_daily ((avg + 1));\nCREATE INDEX index_avg_location_sfo ON conditions_daily (avg) WHERE location = 'SFO';\nCREATE INDEX index_avg_expr_location_sfo ON conditions_daily ((avg + 2)) WHERE location = 'SFO';\nSELECT * FROM test.show_indexespred(:'MAT_TABLE_NAME');\n                                 Index                                 |      Columns      |           Expr            |          Pred          | Unique | Primary | Exclusion | Tablespace \n-----------------------------------------------------------------------+-------------------+---------------------------+------------------------+--------+---------+-----------+------------\n _timescaledb_internal._materialized_hypertable_35_bucket_idx          | {bucket}          |                           |                        | f      | f       | f         | \n _timescaledb_internal._materialized_hypertable_35_location_bucket_idx | {location,bucket} |                           |                        | f      | f       | f         | \n _timescaledb_internal.index_avg                                       | {avg}             |                           |                        | f      | f       | f         | \n _timescaledb_internal.index_avg_expr                                  | {expr}            | avg + 1::double precision |                        | f      | f       | f         | \n _timescaledb_internal.index_avg_expr_location_sfo                     | {expr}            | avg + 2::double precision | location = 'SFO'::text | f      | f       | f         | \n _timescaledb_internal.index_avg_include                               | {avg,location}    |                           |                        | f      | f       | f         | \n _timescaledb_internal.index_avg_location_sfo                          | {avg}             |                           | location = 'SFO'::text | f      | f       | f         | \n _timescaledb_internal.index_avg_only                                  | {avg}             |                           |                        | f      | f       | f         | \n\n-- #3696 assertion failure when referencing columns not present in result\nCREATE TABLE i3696(time timestamptz NOT NULL, search_query text, cnt integer, cnt2 integer);\nSELECT table_name FROM create_hypertable('i3696','time');\n table_name \n------------\n i3696\n\nCREATE MATERIALIZED VIEW i3696_cagg1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\n SELECT  search_query,count(search_query) as count, sum(cnt), time_bucket(INTERVAL '1 minute', time) AS bucket\n FROM i3696 GROUP BY cnt +cnt2 , bucket, search_query;\nNOTICE:  continuous aggregate \"i3696_cagg1\" is already up-to-date\nALTER MATERIALIZED VIEW i3696_cagg1 SET (timescaledb.materialized_only = 'true');\nCREATE MATERIALIZED VIEW i3696_cagg2 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\n SELECT  search_query,count(search_query) as count, sum(cnt), time_bucket(INTERVAL '1 minute', time) AS bucket\n FROM i3696 GROUP BY cnt + cnt2, bucket, search_query\n HAVING cnt + cnt2 + sum(cnt) > 2 or count(cnt2) > 10;\nNOTICE:  continuous aggregate \"i3696_cagg2\" is already up-to-date\nALTER MATERIALIZED VIEW i3696_cagg2 SET (timescaledb.materialized_only = 'true');\n--TEST test with multiple settings on continuous aggregates --\n-- test for materialized_only + compress combinations (real time aggs enabled initially)\nCREATE TABLE test_setting(time timestamptz not null, val numeric);\nSELECT create_hypertable('test_setting', 'time');\n     create_hypertable      \n----------------------------\n (40,public,test_setting,t)\n\nCREATE MATERIALIZED VIEW test_setting_cagg with (timescaledb.continuous, timescaledb.materialized_only=false)\nAS SELECT time_bucket('1h',time), avg(val), count(*) FROM test_setting GROUP BY 1;\nNOTICE:  continuous aggregate \"test_setting_cagg\" is already up-to-date\nINSERT INTO test_setting\nSELECT generate_series( '2020-01-10 8:00'::timestamp, '2020-01-30 10:00+00'::timestamptz, '1 day'::interval), 10.0;\nCALL refresh_continuous_aggregate('test_setting_cagg', NULL, '2020-05-30 10:00+00'::timestamptz);\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    20\n\n--this row is not in the materialized result ---\nINSERT INTO test_setting VALUES( '2020-11-01', 20);\n--try out 2 settings here --\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'true', timescaledb.compress='true');\nNOTICE:  defaulting compress_orderby to time_bucket\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | t                   | t\n\n--real time aggs is off now , should return 20 --\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    20\n\n--now set it back to false --\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'false', timescaledb.compress='true');\nNOTICE:  defaulting compress_orderby to time_bucket\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | t                   | f\n\n--count should return additional data since we have real time aggs on\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    21\n\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'true', timescaledb.compress='false');\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | f                   | t\n\n--real time aggs is off now , should return 20 --\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    20\n\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'false', timescaledb.compress='false');\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | f                   | f\n\n--count should return additional data since we have real time aggs on\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    21\n\nDELETE FROM test_setting WHERE val = 20;\n--TEST test with multiple settings on continuous aggregates with real time aggregates turned off initially --\n-- test for materialized_only + compress combinations (real time aggs enabled initially)\nDROP MATERIALIZED VIEW test_setting_cagg;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_41_50_chunk\nCREATE MATERIALIZED VIEW test_setting_cagg with (timescaledb.continuous, timescaledb.materialized_only = true)\nAS SELECT time_bucket('1h',time), avg(val), count(*) FROM test_setting GROUP BY 1;\nNOTICE:  refreshing continuous aggregate \"test_setting_cagg\"\nCALL refresh_continuous_aggregate('test_setting_cagg', NULL, '2020-05-30 10:00+00'::timestamptz);\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    20\n\n--this row is not in the materialized result ---\nINSERT INTO test_setting VALUES( '2020-11-01', 20);\n--try out 2 settings here --\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'false', timescaledb.compress='true');\nNOTICE:  defaulting compress_orderby to time_bucket\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | t                   | f\n\n--count should return additional data since we have real time aggs on\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    21\n\n--now set it back to false --\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'true', timescaledb.compress='true');\nNOTICE:  defaulting compress_orderby to time_bucket\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | t                   | t\n\n--real time aggs is off now , should return 20 --\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    20\n\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'false', timescaledb.compress='false');\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | f                   | f\n\n--count should return additional data since we have real time aggs on\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    21\n\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'true', timescaledb.compress='false');\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | f                   | t\n\n--real time aggs is off now , should return 20 --\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    20\n\n-- END TEST with multiple settings\n-- Test View Target Entries that contain both aggrefs and Vars in the same expression\nCREATE TABLE transactions\n(\n    \"time\" timestamp with time zone NOT NULL,\n    dummy1 integer,\n    dummy2 integer,\n    dummy3 integer,\n    dummy4 integer,\n    dummy5 integer,\n    amount integer,\n    fiat_value integer\n);\nSELECT create_hypertable('transactions', 'time');\n     create_hypertable      \n----------------------------\n (45,public,transactions,t)\n\nINSERT INTO transactions VALUES ( '2018-01-01 09:20:00-08', 0, 0, 0, 0, 0, 1, 10);\nINSERT INTO transactions VALUES ( '2018-01-02 09:30:00-08', 0, 0, 0, 0, 0, -1, 10);\nINSERT INTO transactions VALUES ( '2018-01-02 09:20:00-08', 0, 0, 0, 0, 0, -1, 10);\nINSERT INTO transactions VALUES ( '2018-01-02 09:10:00-08', 0, 0, 0, 0, 0, -1, 10);\nINSERT INTO transactions VALUES ( '2018-11-01 09:20:00-08', 0, 0, 0, 0, 0, 1, 10);\nINSERT INTO transactions VALUES ( '2018-11-01 10:40:00-08', 0, 0, 0, 0, 0, 1, 10);\nINSERT INTO transactions VALUES ( '2018-11-01 11:50:00-08', 0, 0, 0, 0, 0, 1, 10);\nINSERT INTO transactions VALUES ( '2018-11-01 12:10:00-08', 0, 0, 0, 0, 0, -1, 10);\nINSERT INTO transactions VALUES ( '2018-11-01 13:10:00-08', 0, 0, 0, 0, 0, -1, 10);\nINSERT INTO transactions VALUES ( '2018-11-02 09:20:00-08', 0, 0, 0, 0, 0, 1, 10);\nINSERT INTO transactions VALUES ( '2018-11-02 10:30:00-08', 0, 0, 0, 0, 0, -1, 10);\nCREATE materialized view cashflows(\n    bucket,\n  \tamount,\n    cashflow,\n    cashflow2\n) WITH (\n    timescaledb.continuous,\n    timescaledb.materialized_only = true\n) AS\nSELECT time_bucket ('1 day', time) AS bucket,\n\tamount,\n  CASE\n      WHEN amount < 0 THEN (0 - sum(fiat_value))\n      ELSE sum(fiat_value)\n  END AS cashflow,\n  amount + sum(fiat_value)\nFROM transactions\nGROUP BY bucket, amount;\nNOTICE:  refreshing continuous aggregate \"cashflows\"\nSELECT h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name AS \"PART_VIEW_NAME\",\n       direct_view_name AS \"DIRECT_VIEW_NAME\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON (h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'cashflows'\n\\gset\n-- Show both the columns and the view definitions to see that\n-- references are correct in the view as well.\n\\d+ \"_timescaledb_internal\".:\"DIRECT_VIEW_NAME\"\n                         View \"_timescaledb_internal._direct_view_46\"\n  Column   |           Type           | Collation | Nullable | Default | Storage | Description \n-----------+--------------------------+-----------+----------+---------+---------+-------------\n bucket    | timestamp with time zone |           |          |         | plain   | \n amount    | integer                  |           |          |         | plain   | \n cashflow  | bigint                   |           |          |         | plain   | \n cashflow2 | bigint                   |           |          |         | plain   | \nView definition:\n SELECT time_bucket('@ 1 day'::interval, \"time\") AS bucket,\n    amount,\n        CASE\n            WHEN amount < 0 THEN 0 - sum(fiat_value)\n            ELSE sum(fiat_value)\n        END AS cashflow,\n    amount + sum(fiat_value) AS cashflow2\n   FROM transactions\n  GROUP BY (time_bucket('@ 1 day'::interval, \"time\")), amount;\n\n\\d+ \"_timescaledb_internal\".:\"PART_VIEW_NAME\"\n                         View \"_timescaledb_internal._partial_view_46\"\n  Column   |           Type           | Collation | Nullable | Default | Storage | Description \n-----------+--------------------------+-----------+----------+---------+---------+-------------\n bucket    | timestamp with time zone |           |          |         | plain   | \n amount    | integer                  |           |          |         | plain   | \n cashflow  | bigint                   |           |          |         | plain   | \n cashflow2 | bigint                   |           |          |         | plain   | \nView definition:\n SELECT time_bucket('@ 1 day'::interval, \"time\") AS bucket,\n    amount,\n        CASE\n            WHEN amount < 0 THEN 0 - sum(fiat_value)\n            ELSE sum(fiat_value)\n        END AS cashflow,\n    amount + sum(fiat_value) AS cashflow2\n   FROM transactions\n  GROUP BY (time_bucket('@ 1 day'::interval, \"time\")), amount;\n\n\\d+ \"_timescaledb_internal\".:\"MAT_TABLE_NAME\"\n                          Table \"_timescaledb_internal._materialized_hypertable_46\"\n  Column   |           Type           | Collation | Nullable | Default | Storage | Stats target | Description \n-----------+--------------------------+-----------+----------+---------+---------+--------------+-------------\n bucket    | timestamp with time zone |           | not null |         | plain   |              | \n amount    | integer                  |           |          |         | plain   |              | \n cashflow  | bigint                   |           |          |         | plain   |              | \n cashflow2 | bigint                   |           |          |         | plain   |              | \nIndexes:\n    \"_materialized_hypertable_46_amount_bucket_idx\" btree (amount, bucket DESC)\n    \"_materialized_hypertable_46_bucket_idx\" btree (bucket DESC)\nChild tables: _timescaledb_internal._hyper_46_55_chunk,\n              _timescaledb_internal._hyper_46_56_chunk\n\n\\d+ 'cashflows'\n                                    View \"public.cashflows\"\n  Column   |           Type           | Collation | Nullable | Default | Storage | Description \n-----------+--------------------------+-----------+----------+---------+---------+-------------\n bucket    | timestamp with time zone |           |          |         | plain   | \n amount    | integer                  |           |          |         | plain   | \n cashflow  | bigint                   |           |          |         | plain   | \n cashflow2 | bigint                   |           |          |         | plain   | \nView definition:\n SELECT bucket,\n    amount,\n    cashflow,\n    cashflow2\n   FROM _timescaledb_internal._materialized_hypertable_46;\n\nSELECT * FROM cashflows ORDER BY cashflows;\n            bucket            | amount | cashflow | cashflow2 \n------------------------------+--------+----------+-----------\n Sun Dec 31 16:00:00 2017 UTC |      1 |       10 |        11\n Mon Jan 01 16:00:00 2018 UTC |     -1 |      -30 |        29\n Wed Oct 31 16:00:00 2018 UTC |     -1 |      -20 |        19\n Wed Oct 31 16:00:00 2018 UTC |      1 |       30 |        31\n Thu Nov 01 16:00:00 2018 UTC |     -1 |      -10 |         9\n Thu Nov 01 16:00:00 2018 UTC |      1 |       10 |        11\n\n-- test cagg creation with named arguments in time_bucket\n-- note that positional arguments cannot follow named arguments\n-- 1. test named origin\n-- 2. test named timezone\n-- 3. test named ts\n-- 4. test named bucket width\n-- named origin\nCREATE MATERIALIZED VIEW cagg_named_origin WITH\n(timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket('1h', time, 'UTC', origin => '2001-01-03 01:23:45') AS bucket,\navg(amount) as avg_amount\nFROM transactions GROUP BY 1 WITH NO DATA;\n-- named timezone\nCREATE MATERIALIZED VIEW cagg_named_tz_origin WITH\n(timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket('1h', time, timezone => 'UTC', origin => '2001-01-03 01:23:45') AS bucket,\navg(amount) as avg_amount\nFROM transactions GROUP BY 1 WITH NO DATA;\n-- named ts\nCREATE MATERIALIZED VIEW cagg_named_ts_tz_origin WITH\n(timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket('1h', ts => time, timezone => 'UTC', origin => '2001-01-03 01:23:45') AS bucket,\navg(amount) as avg_amount\nFROM transactions GROUP BY 1 WITH NO DATA;\n-- named bucket width\nCREATE MATERIALIZED VIEW cagg_named_all WITH\n(timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(bucket_width => '1h', ts => time, timezone => 'UTC', origin => '2001-01-03 01:23:45') AS bucket,\navg(amount) as avg_amount\nFROM transactions GROUP BY 1 WITH NO DATA;\n-- Refreshing from the beginning (NULL) of a CAGG with variable time bucket and\n-- using an INTERVAL for the end timestamp (issue #5534)\nCREATE MATERIALIZED VIEW transactions_montly\nWITH (timescaledb.continuous, timescaledb.materialized_only = true) AS\nSELECT time_bucket(INTERVAL '1 month', time) AS bucket,\n       SUM(fiat_value),\n       MAX(fiat_value),\n       MIN(fiat_value)\n  FROM transactions\nGROUP BY 1\nWITH NO DATA;\n-- No rows\nSELECT * FROM transactions_montly ORDER BY bucket;\n bucket | sum | max | min \n--------+-----+-----+-----\n\n-- Refresh from beginning of the CAGG for 1 month\nCALL refresh_continuous_aggregate('transactions_montly', NULL, INTERVAL '1 month');\nSELECT * FROM transactions_montly ORDER BY bucket;\n            bucket            | sum | max | min \n------------------------------+-----+-----+-----\n Sun Dec 31 16:00:00 2017 UTC |  40 |  10 |  10\n Wed Oct 31 16:00:00 2018 UTC |  70 |  10 |  10\n\nTRUNCATE transactions_montly;\n-- Partial refresh the CAGG from beginning to an specific timestamp\nCALL refresh_continuous_aggregate('transactions_montly', NULL, '2018-11-01 11:50:00-08'::timestamptz);\nSELECT * FROM transactions_montly ORDER BY bucket;\n            bucket            | sum | max | min \n------------------------------+-----+-----+-----\n Sun Dec 31 16:00:00 2017 UTC |  40 |  10 |  10\n\n-- Full refresh the CAGG\nCALL refresh_continuous_aggregate('transactions_montly', NULL, NULL);\nSELECT * FROM transactions_montly ORDER BY bucket;\n            bucket            | sum | max | min \n------------------------------+-----+-----+-----\n Sun Dec 31 16:00:00 2017 UTC |  40 |  10 |  10\n Wed Oct 31 16:00:00 2018 UTC |  70 |  10 |  10\n\n-- Check set_chunk_time_interval on continuous aggregate\nCREATE MATERIALIZED VIEW cagg_set_chunk_time_interval\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(INTERVAL '1 month', time) AS bucket,\n       SUM(fiat_value),\n       MAX(fiat_value),\n       MIN(fiat_value)\nFROM transactions\nGROUP BY 1\nWITH NO DATA;\nSELECT set_chunk_time_interval('cagg_set_chunk_time_interval', chunk_time_interval => interval '1 month');\n set_chunk_time_interval \n-------------------------\n \n\nCALL refresh_continuous_aggregate('cagg_set_chunk_time_interval', NULL, NULL);\nSELECT _timescaledb_functions.to_interval(d.interval_length) = interval '1 month'\nFROM _timescaledb_catalog.dimension d\n         RIGHT JOIN _timescaledb_catalog.continuous_agg ca ON ca.user_view_name = 'cagg_set_chunk_time_interval'\nWHERE d.hypertable_id = ca.mat_hypertable_id;\n ?column? \n----------\n t\n\n-- Since #6077 CAggs are materialized only by default\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 5 other objects\nNOTICE:  drop cascades to 2 other objects\nCREATE TABLE conditions (\n       time TIMESTAMPTZ NOT NULL,\n       location TEXT NOT NULL,\n       temperature DOUBLE PRECISION NULL\n);\nSELECT create_hypertable('conditions', 'time');\n    create_hypertable     \n--------------------------\n (53,public,conditions,t)\n\nINSERT INTO conditions VALUES ( '2018-01-01 09:20:00-08', 'SFO', 55);\nINSERT INTO conditions VALUES ( '2018-01-02 09:30:00-08', 'POR', 100);\nINSERT INTO conditions VALUES ( '2018-01-02 09:20:00-08', 'SFO', 65);\nINSERT INTO conditions VALUES ( '2018-01-02 09:10:00-08', 'NYC', 65);\nINSERT INTO conditions VALUES ( '2018-11-01 09:20:00-08', 'NYC', 45);\nINSERT INTO conditions VALUES ( '2018-11-01 10:40:00-08', 'NYC', 55);\nINSERT INTO conditions VALUES ( '2018-11-01 11:50:00-08', 'NYC', 65);\nINSERT INTO conditions VALUES ( '2018-11-01 12:10:00-08', 'NYC', 75);\nINSERT INTO conditions VALUES ( '2018-11-01 13:10:00-08', 'NYC', 85);\nINSERT INTO conditions VALUES ( '2018-11-02 09:20:00-08', 'NYC', 10);\nINSERT INTO conditions VALUES ( '2018-11-02 10:30:00-08', 'NYC', 20);\nCREATE MATERIALIZED VIEW conditions_daily\nWITH (timescaledb.continuous) AS\nSELECT location,\n       time_bucket(INTERVAL '1 day', time) AS bucket,\n       AVG(temperature)\n  FROM conditions\nGROUP BY location, bucket\nWITH NO DATA;\n\\d+ conditions_daily\n                                View \"public.conditions_daily\"\n  Column  |           Type           | Collation | Nullable | Default | Storage  | Description \n----------+--------------------------+-----------+----------+---------+----------+-------------\n location | text                     |           |          |         | extended | \n bucket   | timestamp with time zone |           |          |         | plain    | \n avg      | double precision         |           |          |         | plain    | \nView definition:\n SELECT location,\n    bucket,\n    avg\n   FROM _timescaledb_internal._materialized_hypertable_54;\n\n-- Should return NO ROWS\nSELECT * FROM conditions_daily ORDER BY bucket, location;\n location | bucket | avg \n----------+--------+-----\n\nALTER MATERIALIZED VIEW conditions_daily SET (timescaledb.materialized_only=false);\n\\d+ conditions_daily\n                                View \"public.conditions_daily\"\n  Column  |           Type           | Collation | Nullable | Default | Storage  | Description \n----------+--------------------------+-----------+----------+---------+----------+-------------\n location | text                     |           |          |         | extended | \n bucket   | timestamp with time zone |           |          |         | plain    | \n avg      | double precision         |           |          |         | plain    | \nView definition:\n SELECT _materialized_hypertable_54.location,\n    _materialized_hypertable_54.bucket,\n    _materialized_hypertable_54.avg\n   FROM _timescaledb_internal._materialized_hypertable_54\n  WHERE _materialized_hypertable_54.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(54)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT conditions.location,\n    time_bucket('@ 1 day'::interval, conditions.\"time\") AS bucket,\n    avg(conditions.temperature) AS avg\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(54)), '-infinity'::timestamp with time zone)\n  GROUP BY conditions.location, (time_bucket('@ 1 day'::interval, conditions.\"time\"));\n\n-- Should return ROWS because now it is realtime\nSELECT * FROM conditions_daily ORDER BY bucket, location;\n location |            bucket            | avg \n----------+------------------------------+-----\n SFO      | Sun Dec 31 16:00:00 2017 UTC |  55\n NYC      | Mon Jan 01 16:00:00 2018 UTC |  65\n POR      | Mon Jan 01 16:00:00 2018 UTC | 100\n SFO      | Mon Jan 01 16:00:00 2018 UTC |  65\n NYC      | Wed Oct 31 16:00:00 2018 UTC |  65\n NYC      | Thu Nov 01 16:00:00 2018 UTC |  15\n\n-- Should return ROWS because we refreshed it\nALTER MATERIALIZED VIEW conditions_daily SET (timescaledb.materialized_only=true);\n\\d+ conditions_daily\n                                View \"public.conditions_daily\"\n  Column  |           Type           | Collation | Nullable | Default | Storage  | Description \n----------+--------------------------+-----------+----------+---------+----------+-------------\n location | text                     |           |          |         | extended | \n bucket   | timestamp with time zone |           |          |         | plain    | \n avg      | double precision         |           |          |         | plain    | \nView definition:\n SELECT location,\n    bucket,\n    avg\n   FROM _timescaledb_internal._materialized_hypertable_54;\n\nCALL refresh_continuous_aggregate('conditions_daily', NULL, NULL);\nSELECT * FROM conditions_daily ORDER BY bucket, location;\n location |            bucket            | avg \n----------+------------------------------+-----\n SFO      | Sun Dec 31 16:00:00 2017 UTC |  55\n NYC      | Mon Jan 01 16:00:00 2018 UTC |  65\n POR      | Mon Jan 01 16:00:00 2018 UTC | 100\n SFO      | Mon Jan 01 16:00:00 2018 UTC |  65\n NYC      | Wed Oct 31 16:00:00 2018 UTC |  65\n NYC      | Thu Nov 01 16:00:00 2018 UTC |  15\n\n-- Test TRUNCATE over a Realtime CAgg\nDROP MATERIALIZED VIEW conditions_daily;\nNOTICE:  drop cascades to 2 other objects\nCREATE MATERIALIZED VIEW conditions_daily\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT location,\n       time_bucket(INTERVAL '1 day', time) AS bucket,\n       AVG(temperature)\n  FROM conditions\nGROUP BY location, bucket\nWITH NO DATA;\nSELECT mat_hypertable_id FROM _timescaledb_catalog.continuous_agg WHERE user_view_name = 'conditions_daily' \\gset\n-- Check the current watermark for an empty CAgg\nSELECT _timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(:mat_hypertable_id)) AS watermak_empty_cagg;\n       watermak_empty_cagg       \n---------------------------------\n Sun Nov 23 16:00:00 4714 UTC BC\n\n-- Refresh the CAGG\nCALL refresh_continuous_aggregate('conditions_daily', NULL, NULL);\n-- Check the watermark after the refresh and before truncate the CAgg\nSELECT _timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(:mat_hypertable_id)) AS watermak_before;\n       watermak_before        \n------------------------------\n Fri Nov 02 16:00:00 2018 UTC\n\n-- Exists chunks before truncate the cagg (> 0)\nSELECT count(*) FROM show_chunks('conditions_daily');\n count \n-------\n     2\n\n-- Truncate the given CAgg, it should reset the watermark to the empty state\nTRUNCATE conditions_daily;\n-- No chunks remains after truncate the cagg (= 0)\nSELECT count(*) FROM show_chunks('conditions_daily');\n count \n-------\n     0\n\n-- Watermark should be reseted\nSELECT _timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(:mat_hypertable_id)) AS watermak_after;\n         watermak_after          \n---------------------------------\n Sun Nov 23 16:00:00 4714 UTC BC\n\n-- Should return ROWS because the watermark was reseted by the TRUNCATE\nSELECT * FROM conditions_daily ORDER BY bucket, location;\n location |            bucket            | avg \n----------+------------------------------+-----\n SFO      | Sun Dec 31 16:00:00 2017 UTC |  55\n NYC      | Mon Jan 01 16:00:00 2018 UTC |  65\n POR      | Mon Jan 01 16:00:00 2018 UTC | 100\n SFO      | Mon Jan 01 16:00:00 2018 UTC |  65\n NYC      | Wed Oct 31 16:00:00 2018 UTC |  65\n NYC      | Thu Nov 01 16:00:00 2018 UTC |  15\n\n-- check compression settings are cleaned up when deleting a cagg with compression\nCREATE TABLE cagg_cleanup(time timestamptz not null);\nSELECT table_name FROM create_hypertable('cagg_cleanup','time');\n  table_name  \n--------------\n cagg_cleanup\n\nINSERT INTO cagg_cleanup SELECT '2020-01-01';\nCREATE MATERIALIZED VIEW cagg1 WITH (timescaledb.continuous) AS SELECT time_bucket('1h',time) FROM cagg_cleanup GROUP BY 1;\nNOTICE:  refreshing continuous aggregate \"cagg1\"\nALTER MATERIALIZED VIEW cagg1 SET (timescaledb.compress);\nNOTICE:  defaulting compress_orderby to time_bucket\nSELECT count(compress_chunk(ch)) FROM show_chunks('cagg1') ch;\n count \n-------\n     1\n\nDROP MATERIALIZED VIEW cagg1;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_57_70_chunk\nSELECT * FROM _timescaledb_catalog.compression_settings;\n relid | compress_relid | segmentby | orderby | orderby_desc | orderby_nullsfirst | index \n-------+----------------+-----------+---------+--------------+--------------------+-------\n\n-- test WITH namespace alias\nCREATE TABLE with_alias(time timestamptz not null);\nCREATE MATERIALIZED VIEW cagg_alias\nWITH (tsdb.continuous, tsdb.materialized_only=false) AS\nSELECT time_bucket(INTERVAL '1 day', time) FROM conditions GROUP BY 1 WITH NO DATA;\nALTER MATERIALIZED VIEW cagg_alias SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW cagg_alias SET (tsdb.materialized_only=false);\nDROP MATERIALIZED VIEW cagg_alias;\n-- test SET chunk_time_interval\nCREATE MATERIALIZED VIEW cagg_set\nWITH (tsdb.continuous, tsdb.chunk_interval='1day') AS\nSELECT time_bucket(INTERVAL '1 day', time) AS cagg_interval_setter FROM conditions GROUP BY 1 WITH NO DATA;\nSELECT column_name, time_interval FROM timescaledb_information.dimensions WHERE column_name='cagg_interval_setter';\n     column_name      | time_interval \n----------------------+---------------\n cagg_interval_setter | @ 1 day\n\nALTER MATERIALIZED VIEW cagg_set SET (tsdb.chunk_interval='23 day');\nSELECT column_name, time_interval FROM timescaledb_information.dimensions WHERE column_name='cagg_interval_setter';\n     column_name      | time_interval \n----------------------+---------------\n cagg_interval_setter | @ 23 days\n\nALTER MATERIALIZED VIEW cagg_set SET (tsdb.chunk_interval='6 month');\nSELECT column_name, time_interval FROM timescaledb_information.dimensions WHERE column_name='cagg_interval_setter';\n     column_name      | time_interval \n----------------------+---------------\n cagg_interval_setter | @ 180 days\n\nALTER MATERIALIZED VIEW cagg_set SET (tsdb.chunk_interval='1 year');\nSELECT column_name, time_interval FROM timescaledb_information.dimensions WHERE column_name='cagg_interval_setter';\n     column_name      | time_interval \n----------------------+---------------\n cagg_interval_setter | @ 360 days\n\n-- test cagg with stable functions\nCREATE MATERIALIZED VIEW cagg_stable WITH (tsdb.continuous) AS\nSELECT sum(temperature), max(time + INTERVAL '1h')\nFROM conditions\nGROUP BY time_bucket('1week', time), location;\nWARNING:  using non-immutable functions in continuous aggregate view may lead to inconsistent results on rematerialization\nNOTICE:  refreshing continuous aggregate \"cagg_stable\"\nSELECT * FROM cagg_stable t ORDER BY t;\n sum |             max              \n-----+------------------------------\n  65 | Tue Jan 02 10:10:00 2018 UTC\n 100 | Tue Jan 02 10:30:00 2018 UTC\n 120 | Tue Jan 02 10:20:00 2018 UTC\n 355 | Fri Nov 02 11:30:00 2018 UTC\n\n--aggregate without combine function but stable function\nCREATE MATERIALIZED VIEW cagg_json_agg WITH (tsdb.continuous, tsdb.materialized_only=false)\nAS SELECT json_agg(location) from conditions group by time_bucket('1week', time), location WITH NO DATA;\nWARNING:  using non-immutable functions in continuous aggregate view may lead to inconsistent results on rematerialization\nCREATE FUNCTION test_stablefunc(int) RETURNS int LANGUAGE 'sql' STABLE AS 'SELECT $1 + 10';\nCREATE MATERIALIZED VIEW cagg_stable2 WITH (tsdb.continuous) AS\nSELECT sum(test_stablefunc(temperature::int)), min(location)\nFROM conditions\nGROUP BY time_bucket('1week', time) WITH NO DATA;\nWARNING:  using non-immutable functions in continuous aggregate view may lead to inconsistent results on rematerialization\nCREATE MATERIALIZED VIEW cagg_stable3 WITH (tsdb.continuous) AS\nSELECT sum(temperature), min(location)\nFROM conditions\nGROUP BY time_bucket('1week', time), test_stablefunc(temperature::int) WITH NO DATA;\nWARNING:  using non-immutable functions in continuous aggregate view may lead to inconsistent results on rematerialization\n-- test window functions in caggs\n-- first do sanity check that we error without the GUC\n\\set ON_ERROR_STOP 0\nCREATE MATERIALIZED VIEW cagg_window WITH (tsdb.continuous)\nAS SELECT time_bucket('1week', time), rank() OVER (PARTITION BY time_bucket('1 week',time)) FROM conditions GROUP BY 1;\nERROR:  invalid continuous aggregate query\n\\set ON_ERROR_STOP 1\nSET timescaledb.enable_cagg_window_functions TO on;\nCREATE MATERIALIZED VIEW cagg_window_1 WITH (tsdb.continuous)\nAS SELECT time_bucket('1week', time), rank() OVER (PARTITION BY time_bucket('1 week',time)) FROM conditions GROUP BY 1;\nWARNING:  window function support is experimental and may result in unexpected results depending on the functions used.\nNOTICE:  refreshing continuous aggregate \"cagg_window_1\"\nCREATE MATERIALIZED VIEW cagg_window_2 WITH (tsdb.continuous)\nAS SELECT time_bucket('1week', time), rank() OVER (PARTITION BY time_bucket('1 week',time), location) FROM conditions GROUP BY 1, location;\nWARNING:  window function support is experimental and may result in unexpected results depending on the functions used.\nNOTICE:  refreshing continuous aggregate \"cagg_window_2\"\nCREATE MATERIALIZED VIEW cagg_window_3 WITH (tsdb.continuous)\nAS SELECT time_bucket('1week', time), rank() OVER (PARTITION BY time_bucket('1 week',time)) FROM conditions GROUP BY 1, location;\nWARNING:  window function support is experimental and may result in unexpected results depending on the functions used.\nNOTICE:  refreshing continuous aggregate \"cagg_window_3\"\nCREATE MATERIALIZED VIEW cagg_window_4 WITH (tsdb.continuous)\nAS SELECT time_bucket('1week', time), rank() OVER w FROM conditions GROUP BY 1, location WINDOW w AS (PARTITION BY time_bucket('1 week',time));\nWARNING:  window function support is experimental and may result in unexpected results depending on the functions used.\nNOTICE:  refreshing continuous aggregate \"cagg_window_4\"\n-- test setting chunk_interval on a cagg\nCREATE MATERIALIZED VIEW cagg_chunk_interval WITH (tsdb.continuous, tsdb.chunk_interval='1000 day') AS SELECT time_bucket('1 week', time) FROM conditions GROUP BY 1 WITH NO DATA;\nSELECT time_interval from timescaledb_information.continuous_aggregates cagg INNER JOIN timescaledb_information.dimensions dim ON cagg.materialization_hypertable_name = dim.hypertable_name  WHERE view_name='cagg_chunk_interval';\n time_interval \n---------------\n @ 1000 days\n\nALTER MATERIALIZED VIEW cagg_chunk_interval SET (tsdb.chunk_interval='110 day');\nSELECT time_interval from timescaledb_information.continuous_aggregates cagg INNER JOIN timescaledb_information.dimensions dim ON cagg.materialization_hypertable_name = dim.hypertable_name  WHERE view_name='cagg_chunk_interval';\n time_interval \n---------------\n @ 110 days\n\n-- test columnstore options\nCREATE MATERIALIZED VIEW columnstore_options WITH (tsdb.continuous, tsdb.chunk_interval='1 day') AS SELECT time_bucket('1 day', time) FROM conditions GROUP BY 1 WITH NO DATA;\nSELECT column_name, compress_interval_length from _timescaledb_catalog.dimension where column_name='time_bucket' ORDER BY id DESC LIMIT 1;\n column_name | compress_interval_length \n-------------+--------------------------\n time_bucket |                         \n\nALTER MATERIALIZED VIEW columnstore_options SET (tsdb.columnstore, tsdb.chunk_interval='1 day', tsdb.orderby='time_bucket DESC', tsdb.compress_chunk_interval='2 day');\nSELECT column_name, compress_interval_length from _timescaledb_catalog.dimension where column_name='time_bucket' ORDER BY id DESC LIMIT 1;\n column_name | compress_interval_length \n-------------+--------------------------\n time_bucket |             172800000000\n\nALTER MATERIALIZED VIEW columnstore_options SET (tsdb.compress_chunk_interval='3 day');\nSELECT column_name, compress_interval_length from _timescaledb_catalog.dimension where column_name='time_bucket' ORDER BY id DESC LIMIT 1;\n column_name | compress_interval_length \n-------------+--------------------------\n time_bucket |             259200000000\n\nALTER MATERIALIZED VIEW columnstore_options SET (tsdb.compress_chunk_time_interval='4 day');\nSELECT column_name, compress_interval_length from _timescaledb_catalog.dimension where column_name='time_bucket' ORDER BY id DESC LIMIT 1;\n column_name | compress_interval_length \n-------------+--------------------------\n time_bucket |             345600000000\n\n-- test set returning functions in caggs\nCREATE TABLE kpis_raw (time TIMESTAMP NOT NULL, value INTEGER, groups TEXT[]) WITH (tsdb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nINSERT INTO kpis_raw (time, value, groups) VALUES\n  ('2025-01-01', 10, '{group1,group2,group3}'),\n  ('2025-01-02', 20, '{group1,group4}'),\n  ('2025-02-01', 10, '{group1,group3}'),\n  ('2025-02-01', 20, '{group1,group4}');\nCREATE MATERIALIZED VIEW kpis_cagg WITH (tsdb.continuous) AS\nSELECT time_bucket('7 day', time) AS bucket, count(*) AS number_of_records, avg(value) AS average, unnest(groups) AS kpi_group\nFROM kpis_raw GROUP BY bucket, kpi_group;\nNOTICE:  refreshing continuous aggregate \"kpis_cagg\"\nSELECT * FROM kpis_cagg ORDER BY bucket, kpi_group;\n          bucket          | number_of_records |       average       | kpi_group \n--------------------------+-------------------+---------------------+-----------\n Mon Dec 30 00:00:00 2024 |                 2 | 15.0000000000000000 | group1\n Mon Dec 30 00:00:00 2024 |                 1 | 10.0000000000000000 | group2\n Mon Dec 30 00:00:00 2024 |                 1 | 10.0000000000000000 | group3\n Mon Dec 30 00:00:00 2024 |                 1 | 20.0000000000000000 | group4\n Mon Jan 27 00:00:00 2025 |                 2 | 15.0000000000000000 | group1\n Mon Jan 27 00:00:00 2025 |                 1 | 10.0000000000000000 | group3\n Mon Jan 27 00:00:00 2025 |                 1 | 20.0000000000000000 | group4\n\n--TEST for caggs with non timescaledb namespace options\n--non timescaledb namespace options can be set via ALTER\n\\set ON_ERROR_STOP 0\n-- will error out\nCREATE MATERIALIZED VIEW ht_try_weekly\nWITH (timescaledb.continuous, tigerlake.newoption = true) AS\nSELECT time_bucket(interval '1 week', time) AS ts_bucket, avg(value)\nFROM kpis_raw\nGROUP BY 1;\nERROR:  non \"timescaledb\" namespace options can be set only via ALTER\n--caught by Postgres now\nALTER MATERIALIZED VIEW kpis_cagg SET (tigerlake.newoption = true);\nERROR:  \"kpis_cagg\" is not a materialized view\n\\set ON_ERROR_STOP 1\n-- TEST that cached alter stmt still works (see PR 8739)\n-- test DDL inside function\nCREATE TABLE hypertab_ddl( ts timestamp, a integer)\nWITH (timescaledb.hypertable);\nNOTICE:  using column \"ts\" as partitioning column\nCREATE OR REPLACE FUNCTION ddl_function() RETURNS VOID LANGUAGE PLPGSQL AS $$\nBEGIN\n  DROP MATERIALIZED VIEW IF EXISTS cagg_hypertab_ddl;\n  CREATE MATERIALIZED VIEW cagg_hypertab_ddl WITH (timescaledb.continuous)\n  AS SELECT time_bucket( '1 day'::interval, ts), COUNT(*)\n  FROM hypertab_ddl GROUP BY 1 WITH NO DATA;\nEND\n$$;\nSELECT ddl_function();\nNOTICE:  materialized view \"cagg_hypertab_ddl\" does not exist, skipping\n ddl_function \n--------------\n \n\nSELECT view_name from timescaledb_information.continuous_aggregates WHERE hypertable_name='hypertab_ddl';\n     view_name     \n-------------------\n cagg_hypertab_ddl\n\nSELECT ddl_function();\n ddl_function \n--------------\n \n\nSELECT view_name from timescaledb_information.continuous_aggregates WHERE hypertable_name='hypertab_ddl';\n     view_name     \n-------------------\n cagg_hypertab_ddl\n\n-- TEST continuous aggregate with functionally dependent columns\n-- name column depends on id which is in GROUP BY so name doesnt have to be\nCREATE table sensor(id int PRIMARY KEY, name text);\nCREATE TABLE sensordata(time timestamptz,id int, value float) WITH\t(timescaledb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nCREATE MATERIALIZED VIEW cagg_sensordata WITH (tsdb.continuous) AS\nSELECT s.id, s.name,time_bucket('15 minutes', sd.time) as bucket, avg(sd.value) FROM sensordata sd JOIN sensor s USING(id) GROUP BY s.id, bucket WITH NO DATA;\nSELECT * FROM cagg_sensordata;\n id | name | bucket | avg \n----+------+--------+-----\n\n"
  },
  {
    "path": "tsl/test/expected/cagg_ddl-17.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- Set this variable to avoid using a hard-coded path each time query\n-- results are compared\n\\set QUERY_RESULT_TEST_EQUAL_RELPATH '../../../../test/sql/include/query_result_test_equal.sql'\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--DDL commands on continuous aggregates\nCREATE TABLE conditions (\n      timec        TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature integer  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      timemeasure TIMESTAMPTZ,\n      timeinterval INTERVAL\n);\nSELECT table_name FROM create_hypertable('conditions', 'timec');\n table_name \n------------\n conditions\n\n-- schema tests\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET timezone TO 'UTC+8';\n-- drop if the tablespace1 and/or tablespace2 exists\nSET client_min_messages TO error;\nDROP TABLESPACE IF EXISTS tablespace1;\nDROP TABLESPACE IF EXISTS tablespace2;\nRESET client_min_messages;\nCREATE TABLESPACE tablespace1 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE1_PATH;\nCREATE TABLESPACE tablespace2 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE2_PATH;\nCREATE SCHEMA rename_schema;\nGRANT ALL ON SCHEMA rename_schema TO :ROLE_DEFAULT_PERM_USER;\nCREATE SCHEMA test_schema AUTHORIZATION :ROLE_DEFAULT_PERM_USER;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nCREATE TABLE foo(time TIMESTAMPTZ NOT NULL, data INTEGER);\nSELECT create_hypertable('foo', 'time');\n create_hypertable \n-------------------\n (2,public,foo,t)\n\nCREATE MATERIALIZED VIEW rename_test_old\n  WITH ( timescaledb.continuous, timescaledb.materialized_only=true)\nAS SELECT time_bucket('1week', time), COUNT(data)\n    FROM foo\n    GROUP BY 1 WITH NO DATA;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name  |  partial_view_schema  | partial_view_name \n------------------+-----------------+-----------------------+-------------------\n public           | rename_test_old | _timescaledb_internal | _partial_view_3\n\nALTER TABLE rename_test_old RENAME TO rename_test;\nALTER TABLE rename_test SET SCHEMA test_schema;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name |  partial_view_schema  | partial_view_name \n------------------+----------------+-----------------------+-------------------\n test_schema      | rename_test    | _timescaledb_internal | _partial_view_3\n\nALTER MATERIALIZED VIEW test_schema.rename_test SET SCHEMA rename_schema;\nDROP SCHEMA test_schema;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name |  partial_view_schema  | partial_view_name \n------------------+----------------+-----------------------+-------------------\n rename_schema    | rename_test    | _timescaledb_internal | _partial_view_3\n\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\",\n       direct_view_name as \"DIR_VIEW_NAME\",\n       direct_view_schema as \"DIR_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'rename_test'\n\\gset\nRESET ROLE;\nSELECT current_user;\n current_user \n--------------\n super_user\n\nALTER VIEW :\"PART_VIEW_SCHEMA\".:\"PART_VIEW_NAME\" SET SCHEMA public;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name \n------------------+----------------+---------------------+-------------------\n rename_schema    | rename_test    | public              | _partial_view_3\n\n--alter direct view schema\nSELECT user_view_schema, user_view_name, direct_view_schema, direct_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name |  direct_view_schema   | direct_view_name \n------------------+----------------+-----------------------+------------------\n rename_schema    | rename_test    | _timescaledb_internal | _direct_view_3\n\nRESET ROLE;\nSELECT current_user;\n current_user \n--------------\n super_user\n\nALTER VIEW :\"DIR_VIEW_SCHEMA\".:\"DIR_VIEW_NAME\" SET SCHEMA public;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name,\n      direct_view_schema, direct_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name | direct_view_schema | direct_view_name \n------------------+----------------+---------------------+-------------------+--------------------+------------------\n rename_schema    | rename_test    | public              | _partial_view_3   | public             | _direct_view_3\n\nRESET ROLE;\nSELECT current_user;\n current_user \n--------------\n super_user\n\nALTER SCHEMA rename_schema RENAME TO new_name_schema;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name,\n       direct_view_schema, direct_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name | direct_view_schema | direct_view_name \n------------------+----------------+---------------------+-------------------+--------------------+------------------\n new_name_schema  | rename_test    | public              | _partial_view_3   | public             | _direct_view_3\n\nALTER VIEW :\"PART_VIEW_NAME\" SET SCHEMA new_name_schema;\nALTER VIEW :\"DIR_VIEW_NAME\" SET SCHEMA new_name_schema;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name,\n       direct_view_schema, direct_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name | direct_view_schema | direct_view_name \n------------------+----------------+---------------------+-------------------+--------------------+------------------\n new_name_schema  | rename_test    | new_name_schema     | _partial_view_3   | new_name_schema    | _direct_view_3\n\nRESET ROLE;\nSELECT current_user;\n current_user \n--------------\n super_user\n\nALTER SCHEMA new_name_schema RENAME TO foo_name_schema;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name \n------------------+----------------+---------------------+-------------------\n foo_name_schema  | rename_test    | foo_name_schema     | _partial_view_3\n\nALTER MATERIALIZED VIEW foo_name_schema.rename_test SET SCHEMA public;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name \n------------------+----------------+---------------------+-------------------\n public           | rename_test    | foo_name_schema     | _partial_view_3\n\nRESET ROLE;\nSELECT current_user;\n current_user \n--------------\n super_user\n\nALTER SCHEMA foo_name_schema RENAME TO rename_schema;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSET client_min_messages TO NOTICE;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name \n------------------+----------------+---------------------+-------------------\n public           | rename_test    | rename_schema       | _partial_view_3\n\nALTER MATERIALIZED VIEW rename_test RENAME TO rename_c_aggregate;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema |   user_view_name   | partial_view_schema | partial_view_name \n------------------+--------------------+---------------------+-------------------\n public           | rename_c_aggregate | rename_schema       | _partial_view_3\n\nSELECT * FROM rename_c_aggregate;\n time_bucket | count \n-------------+-------\n\nALTER VIEW rename_schema.:\"PART_VIEW_NAME\" RENAME TO partial_view;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name,\n      direct_view_schema, direct_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema |   user_view_name   | partial_view_schema | partial_view_name | direct_view_schema | direct_view_name \n------------------+--------------------+---------------------+-------------------+--------------------+------------------\n public           | rename_c_aggregate | rename_schema       | partial_view      | rename_schema      | _direct_view_3\n\n--rename direct view\nALTER VIEW rename_schema.:\"DIR_VIEW_NAME\" RENAME TO direct_view;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name,\n      direct_view_schema, direct_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema |   user_view_name   | partial_view_schema | partial_view_name | direct_view_schema | direct_view_name \n------------------+--------------------+---------------------+-------------------+--------------------+------------------\n public           | rename_c_aggregate | rename_schema       | partial_view      | rename_schema      | direct_view\n\n-- drop_chunks tests\nDROP TABLE conditions CASCADE;\nDROP TABLE foo CASCADE;\nNOTICE:  drop cascades to 2 other objects\nCREATE TABLE drop_chunks_table(time BIGINT NOT NULL, data INTEGER);\nSELECT hypertable_id AS drop_chunks_table_id\n    FROM create_hypertable('drop_chunks_table', 'time', chunk_time_interval => 10) \\gset\nCREATE OR REPLACE FUNCTION integer_now_test() returns bigint LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), bigint '0') FROM drop_chunks_table $$;\nSELECT set_integer_now_func('drop_chunks_table', 'integer_now_test');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW drop_chunks_view\n  WITH (\n    timescaledb.continuous,\n    timescaledb.materialized_only=true\n  )\nAS SELECT time_bucket('5', time), COUNT(data)\n    FROM drop_chunks_table\n    GROUP BY 1 WITH NO DATA;\nSELECT format('%I.%I', schema_name, table_name) AS drop_chunks_mat_table,\n        schema_name AS drop_chunks_mat_schema,\n        table_name AS drop_chunks_mat_table_name\n    FROM _timescaledb_catalog.hypertable, _timescaledb_catalog.continuous_agg\n    WHERE _timescaledb_catalog.continuous_agg.raw_hypertable_id = :drop_chunks_table_id\n        AND _timescaledb_catalog.hypertable.id = _timescaledb_catalog.continuous_agg.mat_hypertable_id \\gset\n-- create 3 chunks, with 3 time bucket\nINSERT INTO drop_chunks_table SELECT i, i FROM generate_series(0, 29) AS i;\n-- Only refresh up to bucket 15 initially. Matches the old refresh\n-- behavior that didn't materialize everything\nCALL refresh_continuous_aggregate('drop_chunks_view', 0, 15);\nSELECT count(c) FROM show_chunks('drop_chunks_table') AS c;\n count \n-------\n     3\n\nSELECT count(c) FROM show_chunks('drop_chunks_view') AS c;\n count \n-------\n     1\n\nSELECT * FROM drop_chunks_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     5\n           5 |     5\n          10 |     5\n\n-- cannot drop directly from the materialization table without specifying\n-- cont. aggregate view name explicitly\n\\set ON_ERROR_STOP 0\nSELECT drop_chunks(:'drop_chunks_mat_table',\n    newer_than => -20,\n    verbose => true);\nERROR:  operation not supported on materialized hypertable\n\\set ON_ERROR_STOP 1\nSELECT count(c) FROM show_chunks('drop_chunks_table') AS c;\n count \n-------\n     3\n\nSELECT count(c) FROM show_chunks('drop_chunks_view') AS c;\n count \n-------\n     1\n\nSELECT * FROM drop_chunks_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     5\n           5 |     5\n          10 |     5\n\n-- drop chunks when the chunksize and time_bucket aren't aligned\nDROP TABLE drop_chunks_table CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_5_4_chunk\nCREATE TABLE drop_chunks_table_u(time BIGINT NOT NULL, data INTEGER);\nSELECT hypertable_id AS drop_chunks_table_u_id\n    FROM create_hypertable('drop_chunks_table_u', 'time', chunk_time_interval => 7) \\gset\nCREATE OR REPLACE FUNCTION integer_now_test1() returns bigint LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), bigint '0') FROM drop_chunks_table_u $$;\nSELECT set_integer_now_func('drop_chunks_table_u', 'integer_now_test1');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW drop_chunks_view\n  WITH (\n    timescaledb.continuous,\n    timescaledb.materialized_only=true\n  )\nAS SELECT time_bucket('3', time), COUNT(data)\n    FROM drop_chunks_table_u\n    GROUP BY 1 WITH NO DATA;\nSELECT format('%I.%I', schema_name, table_name) AS drop_chunks_mat_table_u,\n        schema_name AS drop_chunks_mat_schema,\n        table_name AS drop_chunks_mat_table_u_name\n    FROM _timescaledb_catalog.hypertable, _timescaledb_catalog.continuous_agg\n    WHERE _timescaledb_catalog.continuous_agg.raw_hypertable_id = :drop_chunks_table_u_id\n        AND _timescaledb_catalog.hypertable.id = _timescaledb_catalog.continuous_agg.mat_hypertable_id \\gset\n-- create 3 chunks, with 3 time bucket\nINSERT INTO drop_chunks_table_u SELECT i, i FROM generate_series(0, 21) AS i;\n-- Refresh up to bucket 15 to match old materializer behavior\nCALL refresh_continuous_aggregate('drop_chunks_view', 0, 15);\nSELECT count(c) FROM show_chunks('drop_chunks_table_u') AS c;\n count \n-------\n     4\n\nSELECT count(c) FROM show_chunks('drop_chunks_view') AS c;\n count \n-------\n     1\n\nSELECT * FROM drop_chunks_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     3\n           3 |     3\n           6 |     3\n           9 |     3\n          12 |     3\n\n-- TRUNCATE test\n-- Can truncate regular hypertables that have caggs\nTRUNCATE drop_chunks_table_u;\n\\set ON_ERROR_STOP 0\n-- Can't truncate materialized hypertables directly\nTRUNCATE :drop_chunks_mat_table_u;\nERROR:  cannot TRUNCATE a hypertable underlying a continuous aggregate\n\\set ON_ERROR_STOP 1\n-- Check that we don't interfere with TRUNCATE of normal table and\n-- partitioned table\nCREATE TABLE truncate (value int);\nINSERT INTO truncate VALUES (1), (2);\nTRUNCATE truncate;\nSELECT * FROM truncate;\n value \n-------\n\nCREATE TABLE truncate_partitioned (value int)\n  PARTITION BY RANGE(value);\nCREATE TABLE truncate_p1 PARTITION OF truncate_partitioned\n  FOR VALUES FROM (1) TO (3);\nINSERT INTO truncate_partitioned VALUES (1), (2);\nTRUNCATE truncate_partitioned;\nSELECT * FROM truncate_partitioned;\n value \n-------\n\n-- ALTER TABLE tests\n\\set ON_ERROR_STOP 0\n-- test a variety of ALTER TABLE statements\nALTER TABLE :drop_chunks_mat_table_u RENAME time_bucket TO bad_name;\nERROR:  renaming columns on materialization tables is not supported\nALTER TABLE :drop_chunks_mat_table_u ADD UNIQUE(time_bucket);\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u SET UNLOGGED;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u ENABLE ROW LEVEL SECURITY;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u ADD COLUMN fizzle INTEGER;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u DROP COLUMN time_bucket;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u ALTER COLUMN time_bucket DROP NOT NULL;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u ALTER COLUMN time_bucket SET DEFAULT 1;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u ALTER COLUMN time_bucket SET STORAGE EXTERNAL;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u DISABLE TRIGGER ALL;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u SET TABLESPACE foo;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u NOT OF;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u OWNER TO CURRENT_USER;\nERROR:  operation not supported on materialization tables\n\\set ON_ERROR_STOP 1\nALTER TABLE :drop_chunks_mat_table_u SET SCHEMA public;\nALTER TABLE :drop_chunks_mat_table_u_name RENAME TO new_name;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSET client_min_messages TO NOTICE;\nSELECT * FROM new_name ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     3\n           3 |     3\n           6 |     3\n           9 |     3\n          12 |     3\n\nSELECT * FROM drop_chunks_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     3\n           3 |     3\n           6 |     3\n           9 |     3\n          12 |     3\n\n\\set ON_ERROR_STOP 0\n-- no continuous aggregates on a continuous aggregate materialization table\nCREATE MATERIALIZED VIEW new_name_view\n  WITH (\n    timescaledb.continuous,\n    timescaledb.materialized_only=true\n  )\nAS SELECT time_bucket('6', time_bucket), COUNT(\"count\")\n    FROM new_name\n    GROUP BY 1 WITH NO DATA;\nERROR:  hypertable is a continuous aggregate materialization table\n\\set ON_ERROR_STOP 1\nCREATE TABLE metrics(time timestamptz NOT NULL, device_id int, v1 float, v2 float);\nSELECT create_hypertable('metrics','time');\n  create_hypertable   \n----------------------\n (8,public,metrics,t)\n\nINSERT INTO metrics SELECT generate_series('2000-01-01'::timestamptz,'2000-01-10','1m'),1,0.25,0.75;\n-- check expressions in view definition\nCREATE MATERIALIZED VIEW cagg_expr\n  WITH (timescaledb.continuous, timescaledb.materialized_only=true)\nAS\nSELECT\n  time_bucket('1d', time) AS time,\n  'Const'::text AS Const,\n  4.3::numeric AS \"numeric\",\n  first(metrics,time),\n  CASE WHEN true THEN 'foo' ELSE 'bar' END,\n  COALESCE(NULL,'coalesce'),\n  avg(v1) + avg(v2) AS avg1,\n  avg(v1+v2) AS avg2\nFROM metrics\nGROUP BY 1 WITH NO DATA;\nCALL refresh_continuous_aggregate('cagg_expr', NULL, NULL);\nSELECT * FROM cagg_expr ORDER BY time LIMIT 5;\n             time             | const | numeric |                    first                     | case | coalesce | avg1 | avg2 \n------------------------------+-------+---------+----------------------------------------------+------+----------+------+------\n Fri Dec 31 16:00:00 1999 UTC | Const |     4.3 | (\"Sat Jan 01 00:00:00 2000 UTC\",1,0.25,0.75) | foo  | coalesce |    1 |    1\n Sat Jan 01 16:00:00 2000 UTC | Const |     4.3 | (\"Sat Jan 01 16:00:00 2000 UTC\",1,0.25,0.75) | foo  | coalesce |    1 |    1\n Sun Jan 02 16:00:00 2000 UTC | Const |     4.3 | (\"Sun Jan 02 16:00:00 2000 UTC\",1,0.25,0.75) | foo  | coalesce |    1 |    1\n Mon Jan 03 16:00:00 2000 UTC | Const |     4.3 | (\"Mon Jan 03 16:00:00 2000 UTC\",1,0.25,0.75) | foo  | coalesce |    1 |    1\n Tue Jan 04 16:00:00 2000 UTC | Const |     4.3 | (\"Tue Jan 04 16:00:00 2000 UTC\",1,0.25,0.75) | foo  | coalesce |    1 |    1\n\n--test materialization of invalidation before drop\nDROP TABLE IF EXISTS drop_chunks_table CASCADE;\nNOTICE:  table \"drop_chunks_table\" does not exist, skipping\nDROP TABLE IF EXISTS drop_chunks_table_u CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_7_9_chunk\nCREATE TABLE drop_chunks_table(time BIGINT NOT NULL, data INTEGER);\nSELECT hypertable_id AS drop_chunks_table_nid\n    FROM create_hypertable('drop_chunks_table', 'time', chunk_time_interval => 10) \\gset\nCREATE OR REPLACE FUNCTION integer_now_test2() returns bigint LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), bigint '0') FROM drop_chunks_table $$;\nSELECT set_integer_now_func('drop_chunks_table', 'integer_now_test2');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW drop_chunks_view\n  WITH (\n    timescaledb.continuous,\n    timescaledb.materialized_only=true\n  )\nAS SELECT time_bucket('5', time), max(data)\n    FROM drop_chunks_table\n    GROUP BY 1 WITH NO DATA;\nINSERT INTO drop_chunks_table SELECT i, i FROM generate_series(0, 20) AS i;\n--dropping chunks will process the invalidations\nSELECT drop_chunks('drop_chunks_table', older_than => (integer_now_test2()-9));\n               drop_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_10_13_chunk\n\nSELECT * FROM drop_chunks_table ORDER BY time ASC limit 1;\n time | data \n------+------\n   10 |   10\n\nINSERT INTO drop_chunks_table SELECT i, i FROM generate_series(20, 35) AS i;\nCALL refresh_continuous_aggregate('drop_chunks_view', 10, 40);\n--this will be seen after the drop its within the invalidation window and will be dropped\nINSERT INTO drop_chunks_table VALUES (26, 100);\n--this will not be processed by the drop since chunk 30-39 is not dropped but will be seen after refresh\n--shows that the drop doesn't do more work than necessary\nINSERT INTO drop_chunks_table VALUES (31, 200);\n--move the time up to 39\nINSERT INTO drop_chunks_table SELECT i, i FROM generate_series(35, 39) AS i;\n--the chunks and ranges we have thus far\nSELECT chunk_name, range_start_integer, range_end_integer\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'drop_chunks_table';\n     chunk_name     | range_start_integer | range_end_integer \n--------------------+---------------------+-------------------\n _hyper_10_14_chunk |                  10 |                20\n _hyper_10_15_chunk |                  20 |                30\n _hyper_10_16_chunk |                  30 |                40\n\n--the invalidation on 25 not yet seen\nSELECT * FROM drop_chunks_view ORDER BY time_bucket DESC;\n time_bucket | max \n-------------+-----\n          35 |  35\n          30 |  34\n          25 |  29\n          20 |  24\n          15 |  19\n          10 |  14\n\n--refresh to process the invalidations and then drop\nCALL refresh_continuous_aggregate('drop_chunks_view', NULL, (integer_now_test2()-9));\nSELECT drop_chunks('drop_chunks_table', older_than => (integer_now_test2()-9));\n               drop_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_10_14_chunk\n _timescaledb_internal._hyper_10_15_chunk\n\n--new values on 25 now seen in view\nSELECT * FROM drop_chunks_view ORDER BY time_bucket DESC;\n time_bucket | max \n-------------+-----\n          35 |  35\n          30 |  34\n          25 | 100\n          20 |  24\n          15 |  19\n          10 |  14\n\n--earliest datapoint now in table\nSELECT * FROM drop_chunks_table ORDER BY time ASC limit 1;\n time | data \n------+------\n   30 |   30\n\n--still see data in the view\nSELECT * FROM drop_chunks_view WHERE time_bucket < (integer_now_test2()-9) ORDER BY time_bucket DESC;\n time_bucket | max \n-------------+-----\n          25 | 100\n          20 |  24\n          15 |  19\n          10 |  14\n\n--no data but covers dropped chunks\nSELECT * FROM drop_chunks_table WHERE time < (integer_now_test2()-9) ORDER BY time DESC;\n time | data \n------+------\n\n--recreate the dropped chunk\nINSERT INTO drop_chunks_table SELECT i, i FROM generate_series(0, 20) AS i;\n--see data from recreated region\nSELECT * FROM drop_chunks_table WHERE time < (integer_now_test2()-9) ORDER BY time DESC;\n time | data \n------+------\n   20 |   20\n   19 |   19\n   18 |   18\n   17 |   17\n   16 |   16\n   15 |   15\n   14 |   14\n   13 |   13\n   12 |   12\n   11 |   11\n   10 |   10\n    9 |    9\n    8 |    8\n    7 |    7\n    6 |    6\n    5 |    5\n    4 |    4\n    3 |    3\n    2 |    2\n    1 |    1\n    0 |    0\n\n--should show chunk with old name and old ranges\nSELECT chunk_name, range_start_integer, range_end_integer\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'drop_chunks_table'\nORDER BY range_start_integer;\n     chunk_name     | range_start_integer | range_end_integer \n--------------------+---------------------+-------------------\n _hyper_10_18_chunk |                   0 |                10\n _hyper_10_19_chunk |                  10 |                20\n _hyper_10_20_chunk |                  20 |                30\n _hyper_10_16_chunk |                  30 |                40\n\n--We dropped everything up to the bucket starting at 30 and then\n--inserted new data up to and including time 20. Therefore, the\n--dropped data should stay the same as long as we only refresh\n--buckets that have non-dropped data.\nCALL refresh_continuous_aggregate('drop_chunks_view', 30, 40);\nSELECT * FROM drop_chunks_view ORDER BY time_bucket DESC;\n time_bucket | max \n-------------+-----\n          35 |  39\n          30 | 200\n          25 | 100\n          20 |  24\n          15 |  19\n          10 |  14\n\nSELECT format('%I.%I', schema_name, table_name) AS drop_chunks_mat_tablen,\n        schema_name AS drop_chunks_mat_schema,\n        table_name AS drop_chunks_mat_table_name\n    FROM _timescaledb_catalog.hypertable, _timescaledb_catalog.continuous_agg\n    WHERE _timescaledb_catalog.continuous_agg.raw_hypertable_id = :drop_chunks_table_nid\n        AND _timescaledb_catalog.hypertable.id = _timescaledb_catalog.continuous_agg.mat_hypertable_id \\gset\n-- TEST drop chunks from continuous aggregates by specifying view name\nSELECT drop_chunks('drop_chunks_view',\n    newer_than => -20,\n    verbose => true);\nINFO:  dropping chunk _timescaledb_internal._hyper_11_17_chunk\n               drop_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_11_17_chunk\n\n-- Test that we cannot drop chunks when specifying materialized\n-- hypertable\nINSERT INTO drop_chunks_table SELECT generate_series(45, 55), 500;\nCALL refresh_continuous_aggregate('drop_chunks_view', 45, 55);\nSELECT chunk_name, range_start_integer, range_end_integer\nFROM timescaledb_information.chunks\nWHERE hypertable_name = :'drop_chunks_mat_table_name' ORDER BY range_start_integer;\n     chunk_name     | range_start_integer | range_end_integer \n--------------------+---------------------+-------------------\n _hyper_11_23_chunk |                   0 |               100\n\n\\set ON_ERROR_STOP 0\n\\set VERBOSITY default\nSELECT drop_chunks(:'drop_chunks_mat_tablen', older_than => 60);\nERROR:  operation not supported on materialized hypertable\nDETAIL:  Hypertable \"_materialized_hypertable_11\" is a materialized hypertable.\nHINT:  Try the operation on the continuous aggregate instead.\n\\set VERBOSITY terse\n\\set ON_ERROR_STOP 1\n-----------------------------------------------------------------\n-- Test that refresh_continuous_aggregate on chunk will refresh,\n-- but only in the regions covered by the show chunks.\n-----------------------------------------------------------------\nSELECT chunk_name, range_start_integer, range_end_integer\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'drop_chunks_table'\nORDER BY 2,3;\n     chunk_name     | range_start_integer | range_end_integer \n--------------------+---------------------+-------------------\n _hyper_10_18_chunk |                   0 |                10\n _hyper_10_19_chunk |                  10 |                20\n _hyper_10_20_chunk |                  20 |                30\n _hyper_10_16_chunk |                  30 |                40\n _hyper_10_21_chunk |                  40 |                50\n _hyper_10_22_chunk |                  50 |                60\n\n-- Pick the second chunk as the one to drop\nWITH numbered_chunks AS (\n     SELECT row_number() OVER (ORDER BY range_start_integer), chunk_schema, chunk_name, range_start_integer, range_end_integer\n     FROM timescaledb_information.chunks\n     WHERE hypertable_name = 'drop_chunks_table'\n     ORDER BY 1\n)\nSELECT format('%I.%I', chunk_schema, chunk_name) AS chunk_to_drop, range_start_integer, range_end_integer\nFROM numbered_chunks\nWHERE row_number = 2 \\gset\n-- There's data in the table for the chunk/range we will drop\nSELECT * FROM drop_chunks_table\nWHERE time >= :range_start_integer\nAND time < :range_end_integer\nORDER BY 1;\n time | data \n------+------\n   10 |   10\n   11 |   11\n   12 |   12\n   13 |   13\n   14 |   14\n   15 |   15\n   16 |   16\n   17 |   17\n   18 |   18\n   19 |   19\n\n-- Make sure there is also data in the continuous aggregate\n-- CARE:\n-- Note that this behaviour of dropping the materialization table chunks and expecting a refresh\n-- that overlaps that time range to NOT update those chunks is undefined.\nCALL refresh_continuous_aggregate('drop_chunks_view', 0, 50);\nSELECT * FROM drop_chunks_view\nORDER BY 1;\n time_bucket | max \n-------------+-----\n           0 |   4\n           5 |   9\n          10 |  14\n          15 |  19\n          20 |  20\n          45 | 500\n          50 | 500\n\n-- Drop the second chunk, to leave a gap in the data\nDROP TABLE :chunk_to_drop;\n-- Verify that the second chunk is dropped\nSELECT chunk_name, range_start_integer, range_end_integer\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'drop_chunks_table'\nORDER BY 2,3;\n     chunk_name     | range_start_integer | range_end_integer \n--------------------+---------------------+-------------------\n _hyper_10_18_chunk |                   0 |                10\n _hyper_10_20_chunk |                  20 |                30\n _hyper_10_16_chunk |                  30 |                40\n _hyper_10_21_chunk |                  40 |                50\n _hyper_10_22_chunk |                  50 |                60\n\n-- Data is no longer in the table but still in the view\nSELECT * FROM drop_chunks_table\nWHERE time >= :range_start_integer\nAND time < :range_end_integer\nORDER BY 1;\n time | data \n------+------\n\nSELECT * FROM drop_chunks_view\nWHERE time_bucket >= :range_start_integer\nAND time_bucket < :range_end_integer\nORDER BY 1;\n time_bucket | max \n-------------+-----\n          10 |  14\n          15 |  19\n\n-- Insert a large value in one of the chunks that will be dropped\nINSERT INTO drop_chunks_table VALUES (:range_start_integer-1, 100);\n-- Now refresh and drop the two adjecent chunks\nCALL refresh_continuous_aggregate('drop_chunks_view', NULL, 30);\nSELECT drop_chunks('drop_chunks_table', older_than=>30);\n               drop_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_10_18_chunk\n _timescaledb_internal._hyper_10_20_chunk\n\n-- Verify that the chunks are dropped\nSELECT chunk_name, range_start_integer, range_end_integer\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'drop_chunks_table'\nORDER BY 2,3;\n     chunk_name     | range_start_integer | range_end_integer \n--------------------+---------------------+-------------------\n _hyper_10_16_chunk |                  30 |                40\n _hyper_10_21_chunk |                  40 |                50\n _hyper_10_22_chunk |                  50 |                60\n\n-- The continuous aggregate should be refreshed in the regions covered\n-- by the dropped chunks, but not in the \"gap\" region, i.e., the\n-- region of the chunk that was dropped via DROP TABLE.\nSELECT * FROM drop_chunks_view\nORDER BY 1;\n time_bucket | max \n-------------+-----\n           0 |   4\n           5 | 100\n          20 |  20\n          45 | 500\n          50 | 500\n\n-- Now refresh in the region of the first two dropped chunks\nCALL refresh_continuous_aggregate('drop_chunks_view', 0, :range_end_integer);\n-- Aggregate data in the refreshed range should no longer exist since\n-- the underlying data was dropped.\nSELECT * FROM drop_chunks_view\nORDER BY 1;\n time_bucket | max \n-------------+-----\n          20 |  20\n          45 | 500\n          50 | 500\n\n--------------------------------------------------------------------\n-- Check that we can create a materialized table in a tablespace. We\n-- create one with tablespace and one without and compare them.\nCREATE VIEW cagg_info AS\nWITH\n  caggs AS (\n    SELECT format('%I.%I', user_view_schema, user_view_name)::regclass AS user_view,\n           format('%I.%I', direct_view_schema, direct_view_name)::regclass AS direct_view,\n           format('%I.%I', partial_view_schema, partial_view_name)::regclass AS partial_view,\n           format('%I.%I', ht.schema_name, ht.table_name)::regclass AS mat_relid\n      FROM _timescaledb_catalog.hypertable ht,\n           _timescaledb_catalog.continuous_agg cagg\n     WHERE ht.id = cagg.mat_hypertable_id\n  )\nSELECT user_view,\n       pg_get_userbyid(relowner) AS user_view_owner,\n       relname AS mat_table,\n       (SELECT pg_get_userbyid(relowner) FROM pg_class WHERE oid = mat_relid) AS mat_table_owner,\n       direct_view,\n       (SELECT pg_get_userbyid(relowner) FROM pg_class WHERE oid = direct_view) AS direct_view_owner,\n       partial_view,\n       (SELECT pg_get_userbyid(relowner) FROM pg_class WHERE oid = partial_view) AS partial_view_owner,\n       (SELECT spcname FROM pg_tablespace WHERE oid = reltablespace) AS tablespace\n  FROM pg_class JOIN caggs ON pg_class.oid = caggs.mat_relid;\nGRANT SELECT ON cagg_info TO PUBLIC;\nCREATE VIEW chunk_info AS\nSELECT ht.schema_name, ht.table_name, relname AS chunk_name,\n       (SELECT spcname FROM pg_tablespace WHERE oid = reltablespace) AS tablespace\n  FROM pg_class c,\n       _timescaledb_catalog.hypertable ht,\n       _timescaledb_catalog.chunk ch\n WHERE ch.table_name = c.relname AND ht.id = ch.hypertable_id;\nCREATE TABLE whatever(time BIGINT NOT NULL, data INTEGER);\nSELECT hypertable_id AS whatever_nid\n  FROM create_hypertable('whatever', 'time', chunk_time_interval => 10)\n\\gset\nSELECT set_integer_now_func('whatever', 'integer_now_test');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW whatever_view_1\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT time_bucket('5', time), COUNT(data)\n  FROM whatever GROUP BY 1 WITH NO DATA;\nCREATE MATERIALIZED VIEW whatever_view_2\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nTABLESPACE tablespace1 AS\nSELECT time_bucket('5', time), COUNT(data)\n  FROM whatever GROUP BY 1 WITH NO DATA;\nINSERT INTO whatever SELECT i, i FROM generate_series(0, 29) AS i;\nCALL refresh_continuous_aggregate('whatever_view_1', NULL, NULL);\nCALL refresh_continuous_aggregate('whatever_view_2', NULL, NULL);\nSELECT user_view,\n       mat_table,\n       cagg_info.tablespace AS mat_tablespace,\n       chunk_name,\n       chunk_info.tablespace AS chunk_tablespace\n  FROM cagg_info, chunk_info\n WHERE mat_table::text = table_name\n   AND user_view::text LIKE 'whatever_view%';\n    user_view    |          mat_table          | mat_tablespace |     chunk_name     | chunk_tablespace \n-----------------+-----------------------------+----------------+--------------------+------------------\n whatever_view_1 | _materialized_hypertable_13 |                | _hyper_13_27_chunk | \n whatever_view_2 | _materialized_hypertable_14 | tablespace1    | _hyper_14_28_chunk | tablespace1\n\nALTER MATERIALIZED VIEW whatever_view_1 SET TABLESPACE tablespace2;\nSELECT user_view,\n       mat_table,\n       cagg_info.tablespace AS mat_tablespace,\n       chunk_name,\n       chunk_info.tablespace AS chunk_tablespace\n  FROM cagg_info, chunk_info\n WHERE mat_table::text = table_name\n   AND user_view::text LIKE 'whatever_view%';\n    user_view    |          mat_table          | mat_tablespace |     chunk_name     | chunk_tablespace \n-----------------+-----------------------------+----------------+--------------------+------------------\n whatever_view_1 | _materialized_hypertable_13 | tablespace2    | _hyper_13_27_chunk | tablespace2\n whatever_view_2 | _materialized_hypertable_14 | tablespace1    | _hyper_14_28_chunk | tablespace1\n\nDROP MATERIALIZED VIEW whatever_view_1;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_13_27_chunk\nDROP MATERIALIZED VIEW whatever_view_2;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_14_28_chunk\n-- test bucket width expressions on integer hypertables\nCREATE TABLE metrics_int2 (\n  time int2 NOT NULL,\n  device_id int,\n  v1 float,\n  v2 float\n);\nCREATE TABLE metrics_int4 (\n  time int4 NOT NULL,\n  device_id int,\n  v1 float,\n  v2 float\n);\nCREATE TABLE metrics_int8 (\n  time int8 NOT NULL,\n  device_id int,\n  v1 float,\n  v2 float\n);\nSELECT create_hypertable (('metrics_' || dt)::regclass, 'time', chunk_time_interval => 10)\nFROM (\n  VALUES ('int2'),\n    ('int4'),\n    ('int8')) v (dt);\n     create_hypertable      \n----------------------------\n (15,public,metrics_int2,t)\n (16,public,metrics_int4,t)\n (17,public,metrics_int8,t)\n\nCREATE OR REPLACE FUNCTION int2_now ()\n  RETURNS int2\n  LANGUAGE SQL\n  STABLE\n  AS $$\n  SELECT 10::int2\n$$;\nCREATE OR REPLACE FUNCTION int4_now ()\n  RETURNS int4\n  LANGUAGE SQL\n  STABLE\n  AS $$\n  SELECT 10::int4\n$$;\nCREATE OR REPLACE FUNCTION int8_now ()\n  RETURNS int8\n  LANGUAGE SQL\n  STABLE\n  AS $$\n  SELECT 10::int8\n$$;\nSELECT set_integer_now_func (('metrics_' || dt)::regclass, (dt || '_now')::regproc)\nFROM (\n  VALUES ('int2'),\n    ('int4'),\n    ('int8')) v (dt);\n set_integer_now_func \n----------------------\n \n \n \n\n-- width expression for int2 hypertables\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1::smallint, time)\nFROM metrics_int2\nGROUP BY 1;\nNOTICE:  continuous aggregate \"width_expr\" is already up-to-date\nDROP MATERIALIZED VIEW width_expr;\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1::smallint + 2::smallint, time)\nFROM metrics_int2\nGROUP BY 1;\nNOTICE:  continuous aggregate \"width_expr\" is already up-to-date\nDROP MATERIALIZED VIEW width_expr;\n-- width expression for int4 hypertables\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1, time)\nFROM metrics_int4\nGROUP BY 1;\nNOTICE:  continuous aggregate \"width_expr\" is already up-to-date\nDROP MATERIALIZED VIEW width_expr;\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1 + 2, time)\nFROM metrics_int4\nGROUP BY 1;\nNOTICE:  continuous aggregate \"width_expr\" is already up-to-date\nDROP MATERIALIZED VIEW width_expr;\n-- width expression for int8 hypertables\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1, time)\nFROM metrics_int8\nGROUP BY 1;\nNOTICE:  continuous aggregate \"width_expr\" is already up-to-date\nDROP MATERIALIZED VIEW width_expr;\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1 + 2, time)\nFROM metrics_int8\nGROUP BY 1;\nNOTICE:  continuous aggregate \"width_expr\" is already up-to-date\nDROP MATERIALIZED VIEW width_expr;\n\\set ON_ERROR_STOP 0\n-- non-immutable expresions should be rejected\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(extract(year FROM now())::smallint, time)\nFROM metrics_int2\nGROUP BY 1;\nERROR:  only immutable expressions allowed in time bucket function\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(extract(year FROM now())::int, time)\nFROM metrics_int4\nGROUP BY 1;\nERROR:  only immutable expressions allowed in time bucket function\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(extract(year FROM now())::int, time)\nFROM metrics_int8\nGROUP BY 1;\nERROR:  only immutable expressions allowed in time bucket function\n\\set ON_ERROR_STOP 1\n-- Test various ALTER MATERIALIZED VIEW statements.\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nCREATE MATERIALIZED VIEW owner_check WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1 + 2, time)\nFROM metrics_int8\nGROUP BY 1\nWITH NO DATA;\n\\x on\nSELECT * FROM cagg_info WHERE user_view::text = 'owner_check';\n-[ RECORD 1 ]------+---------------------------------------\nuser_view          | owner_check\nuser_view_owner    | default_perm_user\nmat_table          | _materialized_hypertable_24\nmat_table_owner    | default_perm_user\ndirect_view        | _timescaledb_internal._direct_view_24\ndirect_view_owner  | default_perm_user\npartial_view       | _timescaledb_internal._partial_view_24\npartial_view_owner | default_perm_user\ntablespace         | \n\n\\x off\n-- This should not work since the target user has the wrong role, but\n-- we test that the normal checks are done when changing the owner.\n\\set ON_ERROR_STOP 0\nALTER MATERIALIZED VIEW owner_check OWNER TO :ROLE_1;\nERROR:  must be able to SET ROLE \"test_role_1\"\n\\set ON_ERROR_STOP 1\n-- Superuser can always change owner\nSET ROLE :ROLE_SUPERUSER;\n-- Add a refresh policy before changing owner to verify job owner is propagated\nSELECT add_continuous_aggregate_policy('owner_check', NULL, 1::int8, '1 day'::interval) AS cagg_job_id \\gset\nSELECT owner FROM _timescaledb_config.bgw_job WHERE id = :cagg_job_id;\n       owner       \n-------------------\n default_perm_user\n\nALTER MATERIALIZED VIEW owner_check OWNER TO :ROLE_1;\n\\x on\nSELECT * FROM cagg_info WHERE user_view::text = 'owner_check';\n-[ RECORD 1 ]------+---------------------------------------\nuser_view          | owner_check\nuser_view_owner    | test_role_1\nmat_table          | _materialized_hypertable_24\nmat_table_owner    | test_role_1\ndirect_view        | _timescaledb_internal._direct_view_24\ndirect_view_owner  | test_role_1\npartial_view       | _timescaledb_internal._partial_view_24\npartial_view_owner | test_role_1\ntablespace         | \n\n\\x off\n-- make sure policy job owner is propagated\nSELECT owner FROM _timescaledb_config.bgw_job WHERE id = :cagg_job_id;\n    owner    \n-------------\n test_role_1\n\nSELECT remove_continuous_aggregate_policy('owner_check');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n--\n-- Test drop continuous aggregate cases\n--\n-- Issue: #2608\n--\nCREATE OR REPLACE FUNCTION test_int_now()\n  RETURNS INT LANGUAGE SQL STABLE AS\n$BODY$\n  SELECT 50;\n$BODY$;\nCREATE TABLE conditionsnm(time_int INT NOT NULL, device INT, value FLOAT);\nSELECT create_hypertable('conditionsnm', 'time_int', chunk_time_interval => 10);\n     create_hypertable      \n----------------------------\n (25,public,conditionsnm,t)\n\nSELECT set_integer_now_func('conditionsnm', 'test_int_now');\n set_integer_now_func \n----------------------\n \n\nINSERT INTO conditionsnm\nSELECT time_val, time_val % 4, 3.14 FROM generate_series(0,100,1) AS time_val;\n-- Case 1: DROP\nCREATE MATERIALIZED VIEW conditionsnm_4\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE)\nAS\nSELECT time_bucket(7, time_int) as bucket,\nSUM(value), COUNT(value)\nFROM conditionsnm GROUP BY bucket WITH DATA;\nNOTICE:  refreshing continuous aggregate \"conditionsnm_4\"\nDROP materialized view conditionsnm_4;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_26_40_chunk\n-- Case 2: DROP CASCADE should have similar behaviour as DROP\nCREATE MATERIALIZED VIEW conditionsnm_4\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE)\nAS\nSELECT time_bucket(7, time_int) as bucket,\nSUM(value), COUNT(value)\nFROM conditionsnm GROUP BY bucket WITH DATA;\nNOTICE:  refreshing continuous aggregate \"conditionsnm_4\"\nDROP materialized view conditionsnm_4 CASCADE;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_27_41_chunk\n-- Case 3: require CASCADE in case of dependent object\nCREATE MATERIALIZED VIEW conditionsnm_4\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE)\nAS\nSELECT time_bucket(7, time_int) as bucket,\nSUM(value), COUNT(value)\nFROM conditionsnm GROUP BY bucket WITH DATA;\nNOTICE:  refreshing continuous aggregate \"conditionsnm_4\"\nCREATE VIEW see_cagg as select * from conditionsnm_4;\n\\set ON_ERROR_STOP 0\nDROP MATERIALIZED VIEW conditionsnm_4;\nERROR:  cannot drop view conditionsnm_4 because other objects depend on it\n\\set ON_ERROR_STOP 1\n-- Case 4: DROP CASCADE with dependency\nDROP MATERIALIZED VIEW conditionsnm_4 CASCADE;\nNOTICE:  drop cascades to view see_cagg\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_28_42_chunk\n-- Test DROP SCHEMA CASCADE with continuous aggregates\n--\n-- Issue: #2350\n--\n-- Case 1: DROP SCHEMA CASCADE\nCREATE SCHEMA test_schema;\nCREATE TABLE test_schema.telemetry_raw (\n  ts        TIMESTAMP WITH TIME ZONE NOT NULL,\n  value     DOUBLE PRECISION\n);\nSELECT create_hypertable('test_schema.telemetry_raw', 'ts');\n        create_hypertable         \n----------------------------------\n (29,test_schema,telemetry_raw,t)\n\nCREATE MATERIALIZED VIEW test_schema.telemetry_1s\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false)\n    AS\nSELECT time_bucket(INTERVAL '1s', ts) AS ts_1s,\n       avg(value)\n  FROM test_schema.telemetry_raw\n GROUP BY ts_1s WITH NO DATA;\nSELECT ca.raw_hypertable_id,\n       h.schema_name,\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON (h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'telemetry_1s';\n raw_hypertable_id |      schema_name      |       MAT_TABLE_NAME        |  PART_VIEW_NAME  |  partial_view_schema  \n-------------------+-----------------------+-----------------------------+------------------+-----------------------\n                29 | _timescaledb_internal | _materialized_hypertable_30 | _partial_view_30 | _timescaledb_internal\n\n\\gset\nDROP SCHEMA test_schema CASCADE;\nNOTICE:  drop cascades to 4 other objects\nSELECT count(*) FROM pg_class WHERE relname = :'MAT_TABLE_NAME';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = :'PART_VIEW_NAME';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = 'telemetry_1s';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_namespace WHERE nspname = 'test_schema';\n count \n-------\n     0\n\n-- Case 2: DROP SCHEMA CASCADE with multiple caggs\nCREATE SCHEMA test_schema;\nCREATE TABLE test_schema.telemetry_raw (\n  ts        TIMESTAMP WITH TIME ZONE NOT NULL,\n  value     DOUBLE PRECISION\n);\nSELECT create_hypertable('test_schema.telemetry_raw', 'ts');\n        create_hypertable         \n----------------------------------\n (31,test_schema,telemetry_raw,t)\n\nCREATE MATERIALIZED VIEW test_schema.cagg1\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false)\n    AS\nSELECT time_bucket(INTERVAL '1s', ts) AS ts_1s,\n       avg(value)\n  FROM test_schema.telemetry_raw\n GROUP BY ts_1s WITH NO DATA;\nCREATE MATERIALIZED VIEW test_schema.cagg2\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false)\n    AS\nSELECT time_bucket(INTERVAL '1s', ts) AS ts_1s,\n       avg(value)\n  FROM test_schema.telemetry_raw\n GROUP BY ts_1s WITH NO DATA;\nSELECT ca.raw_hypertable_id,\n       h.schema_name,\n       h.table_name AS \"MAT_TABLE_NAME1\",\n       partial_view_name as \"PART_VIEW_NAME1\",\n       partial_view_schema\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON (h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'cagg1';\n raw_hypertable_id |      schema_name      |       MAT_TABLE_NAME1       | PART_VIEW_NAME1  |  partial_view_schema  \n-------------------+-----------------------+-----------------------------+------------------+-----------------------\n                31 | _timescaledb_internal | _materialized_hypertable_32 | _partial_view_32 | _timescaledb_internal\n\n\\gset\nSELECT ca.raw_hypertable_id,\n       h.schema_name,\n       h.table_name AS \"MAT_TABLE_NAME2\",\n       partial_view_name as \"PART_VIEW_NAME2\",\n       partial_view_schema\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON (h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'cagg2';\n raw_hypertable_id |      schema_name      |       MAT_TABLE_NAME2       | PART_VIEW_NAME2  |  partial_view_schema  \n-------------------+-----------------------+-----------------------------+------------------+-----------------------\n                31 | _timescaledb_internal | _materialized_hypertable_33 | _partial_view_33 | _timescaledb_internal\n\n\\gset\nDROP SCHEMA test_schema CASCADE;\nNOTICE:  drop cascades to 7 other objects\nSELECT count(*) FROM pg_class WHERE relname = :'MAT_TABLE_NAME1';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = :'PART_VIEW_NAME1';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = 'cagg1';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = :'MAT_TABLE_NAME2';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = :'PART_VIEW_NAME2';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = 'cagg2';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_namespace WHERE nspname = 'test_schema';\n count \n-------\n     0\n\nDROP TABLESPACE tablespace1;\nDROP TABLESPACE tablespace2;\n-- Check that we can rename a column of a materialized view and still\n-- rebuild it after (#3051, #3405)\nCREATE TABLE conditions (\n       time TIMESTAMPTZ NOT NULL,\n       location TEXT NOT NULL,\n       temperature DOUBLE PRECISION NULL\n);\nSELECT create_hypertable('conditions', 'time');\n    create_hypertable     \n--------------------------\n (34,public,conditions,t)\n\nINSERT INTO conditions VALUES ( '2018-01-01 09:20:00-08', 'SFO', 55);\nINSERT INTO conditions VALUES ( '2018-01-02 09:30:00-08', 'por', 100);\nINSERT INTO conditions VALUES ( '2018-01-02 09:20:00-08', 'SFO', 65);\nINSERT INTO conditions VALUES ( '2018-01-02 09:10:00-08', 'NYC', 65);\nINSERT INTO conditions VALUES ( '2018-11-01 09:20:00-08', 'NYC', 45);\nINSERT INTO conditions VALUES ( '2018-11-01 10:40:00-08', 'NYC', 55);\nINSERT INTO conditions VALUES ( '2018-11-01 11:50:00-08', 'NYC', 65);\nINSERT INTO conditions VALUES ( '2018-11-01 12:10:00-08', 'NYC', 75);\nINSERT INTO conditions VALUES ( '2018-11-01 13:10:00-08', 'NYC', 85);\nINSERT INTO conditions VALUES ( '2018-11-02 09:20:00-08', 'NYC', 10);\nINSERT INTO conditions VALUES ( '2018-11-02 10:30:00-08', 'NYC', 20);\nCREATE MATERIALIZED VIEW conditions_daily\nWITH (timescaledb.continuous, timescaledb.materialized_only = true) AS\nSELECT location,\n       time_bucket(INTERVAL '1 day', time) AS bucket,\n       AVG(temperature)\n  FROM conditions\nGROUP BY location, bucket\nWITH NO DATA;\nCREATE MATERIALIZED VIEW conditions_weekly\nWITH (timescaledb.continuous, timescaledb.materialized_only = true) AS\nSELECT location,\n       time_bucket(INTERVAL '7 day', bucket) AS bucket,\n       AVG(avg)\n  FROM conditions_daily\nGROUP BY 1, 2\nWITH NO DATA;\nSELECT format('%I.%I', '_timescaledb_internal', h.table_name) AS \"MAT_TABLE_NAME\",\n       format('%I.%I', '_timescaledb_internal', partial_view_name) AS \"PART_VIEW_NAME\",\n       format('%I.%I', '_timescaledb_internal', direct_view_name) AS \"DIRECT_VIEW_NAME\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON (h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'conditions_daily'\n\\gset\n-- Show both the columns and the view definitions to see that\n-- references are correct in the view as well.\nSELECT * FROM test.show_columns('conditions_daily');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n bucket   | timestamp with time zone | f\n avg      | double precision         | f\n\nSELECT * FROM test.show_columns(:'DIRECT_VIEW_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n bucket   | timestamp with time zone | f\n avg      | double precision         | f\n\nSELECT * FROM test.show_columns(:'PART_VIEW_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n bucket   | timestamp with time zone | f\n avg      | double precision         | f\n\nSELECT * FROM test.show_columns(:'MAT_TABLE_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n bucket   | timestamp with time zone | t\n avg      | double precision         | f\n\nALTER MATERIALIZED VIEW conditions_daily RENAME COLUMN bucket to \"time\";\n-- Show both the columns and the view definitions to see that\n-- references are correct in the view as well.\nSELECT * FROM test.show_columns(' conditions_daily');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | f\n avg      | double precision         | f\n\nSELECT * FROM test.show_columns(:'DIRECT_VIEW_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | f\n avg      | double precision         | f\n\nSELECT * FROM test.show_columns(:'PART_VIEW_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | f\n avg      | double precision         | f\n\nSELECT * FROM test.show_columns(:'MAT_TABLE_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | t\n avg      | double precision         | f\n\n-- This will rebuild the materialized view and should succeed.\nALTER MATERIALIZED VIEW conditions_daily SET (timescaledb.materialized_only = false);\n-- Refresh the continuous aggregate to check that it works after the\n-- rename.\n\\set VERBOSITY verbose\nCALL refresh_continuous_aggregate('conditions_daily', NULL, NULL);\n\\set VERBOSITY terse\n-- Rename another column after the flip and verify toggling back and\n-- forth still works. This exercises the rename when the user view\n-- already has a UNION ALL query (materialized_only = false).\nALTER MATERIALIZED VIEW conditions_daily RENAME COLUMN avg TO average;\nSELECT * FROM test.show_columns('conditions_daily');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | f\n average  | double precision         | f\n\nSELECT * FROM test.show_columns(:'DIRECT_VIEW_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | f\n average  | double precision         | f\n\nSELECT * FROM test.show_columns(:'MAT_TABLE_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | t\n average  | double precision         | f\n\nALTER MATERIALIZED VIEW conditions_daily SET (timescaledb.materialized_only = true);\nALTER MATERIALIZED VIEW conditions_daily SET (timescaledb.materialized_only = false);\n-- Verify data is still accessible after multiple renames and toggles.\nSELECT * FROM conditions_daily ORDER BY location COLLATE \"C\", time;\n location |             time             | average \n----------+------------------------------+---------\n NYC      | Mon Jan 01 16:00:00 2018 UTC |      65\n NYC      | Wed Oct 31 16:00:00 2018 UTC |      65\n NYC      | Thu Nov 01 16:00:00 2018 UTC |      15\n SFO      | Sun Dec 31 16:00:00 2017 UTC |      55\n SFO      | Mon Jan 01 16:00:00 2018 UTC |      65\n por      | Mon Jan 01 16:00:00 2018 UTC |     100\n\n-- check hierarchical continuous aggregate still works after renames and toggles on the underlying cagg\nALTER MATERIALIZED VIEW conditions_weekly SET (timescaledb.materialized_only = false);\nALTER MATERIALIZED VIEW conditions_weekly SET (timescaledb.materialized_only = true);\nSELECT * FROM conditions_weekly ORDER BY location COLLATE \"C\", bucket;\n location | bucket | avg \n----------+--------+-----\n\n-- Verify that direct rename on the materialization hypertable is blocked.\n\\set ON_ERROR_STOP 0\nALTER TABLE :MAT_TABLE_NAME RENAME COLUMN average TO avg;\nERROR:  renaming columns on materialization tables is not supported\n\\set ON_ERROR_STOP 1\n-- Rename back so subsequent tests that reference \"avg\" still work.\nALTER MATERIALIZED VIEW conditions_daily RENAME COLUMN average TO avg;\n--\n-- Indexes on continuous aggregate\n--\n\\set ON_ERROR_STOP 0\n-- unique indexes are not supported\nCREATE UNIQUE INDEX index_unique_error ON conditions_daily (\"time\", location);\nERROR:  continuous aggregates do not support UNIQUE indexes\n-- concurrently index creation not supported\nCREATE INDEX CONCURRENTLY index_concurrently_avg ON conditions_daily (avg);\nERROR:  hypertables do not support concurrent index creation\n\\set ON_ERROR_STOP 1\nCREATE INDEX index_avg ON conditions_daily (avg);\nCREATE INDEX index_avg_only ON ONLY conditions_daily (avg);\nCREATE INDEX index_avg_include ON conditions_daily (avg) INCLUDE (location);\nCREATE INDEX index_avg_expr ON conditions_daily ((avg + 1));\nCREATE INDEX index_avg_location_sfo ON conditions_daily (avg) WHERE location = 'SFO';\nCREATE INDEX index_avg_expr_location_sfo ON conditions_daily ((avg + 2)) WHERE location = 'SFO';\nSELECT * FROM test.show_indexespred(:'MAT_TABLE_NAME');\n                                 Index                                 |      Columns      |           Expr            |          Pred          | Unique | Primary | Exclusion | Tablespace \n-----------------------------------------------------------------------+-------------------+---------------------------+------------------------+--------+---------+-----------+------------\n _timescaledb_internal._materialized_hypertable_35_bucket_idx          | {bucket}          |                           |                        | f      | f       | f         | \n _timescaledb_internal._materialized_hypertable_35_location_bucket_idx | {location,bucket} |                           |                        | f      | f       | f         | \n _timescaledb_internal.index_avg                                       | {avg}             |                           |                        | f      | f       | f         | \n _timescaledb_internal.index_avg_expr                                  | {expr}            | avg + 1::double precision |                        | f      | f       | f         | \n _timescaledb_internal.index_avg_expr_location_sfo                     | {expr}            | avg + 2::double precision | location = 'SFO'::text | f      | f       | f         | \n _timescaledb_internal.index_avg_include                               | {avg,location}    |                           |                        | f      | f       | f         | \n _timescaledb_internal.index_avg_location_sfo                          | {avg}             |                           | location = 'SFO'::text | f      | f       | f         | \n _timescaledb_internal.index_avg_only                                  | {avg}             |                           |                        | f      | f       | f         | \n\n-- #3696 assertion failure when referencing columns not present in result\nCREATE TABLE i3696(time timestamptz NOT NULL, search_query text, cnt integer, cnt2 integer);\nSELECT table_name FROM create_hypertable('i3696','time');\n table_name \n------------\n i3696\n\nCREATE MATERIALIZED VIEW i3696_cagg1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\n SELECT  search_query,count(search_query) as count, sum(cnt), time_bucket(INTERVAL '1 minute', time) AS bucket\n FROM i3696 GROUP BY cnt +cnt2 , bucket, search_query;\nNOTICE:  continuous aggregate \"i3696_cagg1\" is already up-to-date\nALTER MATERIALIZED VIEW i3696_cagg1 SET (timescaledb.materialized_only = 'true');\nCREATE MATERIALIZED VIEW i3696_cagg2 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\n SELECT  search_query,count(search_query) as count, sum(cnt), time_bucket(INTERVAL '1 minute', time) AS bucket\n FROM i3696 GROUP BY cnt + cnt2, bucket, search_query\n HAVING cnt + cnt2 + sum(cnt) > 2 or count(cnt2) > 10;\nNOTICE:  continuous aggregate \"i3696_cagg2\" is already up-to-date\nALTER MATERIALIZED VIEW i3696_cagg2 SET (timescaledb.materialized_only = 'true');\n--TEST test with multiple settings on continuous aggregates --\n-- test for materialized_only + compress combinations (real time aggs enabled initially)\nCREATE TABLE test_setting(time timestamptz not null, val numeric);\nSELECT create_hypertable('test_setting', 'time');\n     create_hypertable      \n----------------------------\n (40,public,test_setting,t)\n\nCREATE MATERIALIZED VIEW test_setting_cagg with (timescaledb.continuous, timescaledb.materialized_only=false)\nAS SELECT time_bucket('1h',time), avg(val), count(*) FROM test_setting GROUP BY 1;\nNOTICE:  continuous aggregate \"test_setting_cagg\" is already up-to-date\nINSERT INTO test_setting\nSELECT generate_series( '2020-01-10 8:00'::timestamp, '2020-01-30 10:00+00'::timestamptz, '1 day'::interval), 10.0;\nCALL refresh_continuous_aggregate('test_setting_cagg', NULL, '2020-05-30 10:00+00'::timestamptz);\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    20\n\n--this row is not in the materialized result ---\nINSERT INTO test_setting VALUES( '2020-11-01', 20);\n--try out 2 settings here --\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'true', timescaledb.compress='true');\nNOTICE:  defaulting compress_orderby to time_bucket\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | t                   | t\n\n--real time aggs is off now , should return 20 --\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    20\n\n--now set it back to false --\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'false', timescaledb.compress='true');\nNOTICE:  defaulting compress_orderby to time_bucket\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | t                   | f\n\n--count should return additional data since we have real time aggs on\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    21\n\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'true', timescaledb.compress='false');\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | f                   | t\n\n--real time aggs is off now , should return 20 --\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    20\n\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'false', timescaledb.compress='false');\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | f                   | f\n\n--count should return additional data since we have real time aggs on\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    21\n\nDELETE FROM test_setting WHERE val = 20;\n--TEST test with multiple settings on continuous aggregates with real time aggregates turned off initially --\n-- test for materialized_only + compress combinations (real time aggs enabled initially)\nDROP MATERIALIZED VIEW test_setting_cagg;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_41_50_chunk\nCREATE MATERIALIZED VIEW test_setting_cagg with (timescaledb.continuous, timescaledb.materialized_only = true)\nAS SELECT time_bucket('1h',time), avg(val), count(*) FROM test_setting GROUP BY 1;\nNOTICE:  refreshing continuous aggregate \"test_setting_cagg\"\nCALL refresh_continuous_aggregate('test_setting_cagg', NULL, '2020-05-30 10:00+00'::timestamptz);\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    20\n\n--this row is not in the materialized result ---\nINSERT INTO test_setting VALUES( '2020-11-01', 20);\n--try out 2 settings here --\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'false', timescaledb.compress='true');\nNOTICE:  defaulting compress_orderby to time_bucket\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | t                   | f\n\n--count should return additional data since we have real time aggs on\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    21\n\n--now set it back to false --\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'true', timescaledb.compress='true');\nNOTICE:  defaulting compress_orderby to time_bucket\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | t                   | t\n\n--real time aggs is off now , should return 20 --\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    20\n\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'false', timescaledb.compress='false');\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | f                   | f\n\n--count should return additional data since we have real time aggs on\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    21\n\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'true', timescaledb.compress='false');\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | f                   | t\n\n--real time aggs is off now , should return 20 --\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    20\n\n-- END TEST with multiple settings\n-- Test View Target Entries that contain both aggrefs and Vars in the same expression\nCREATE TABLE transactions\n(\n    \"time\" timestamp with time zone NOT NULL,\n    dummy1 integer,\n    dummy2 integer,\n    dummy3 integer,\n    dummy4 integer,\n    dummy5 integer,\n    amount integer,\n    fiat_value integer\n);\nSELECT create_hypertable('transactions', 'time');\n     create_hypertable      \n----------------------------\n (45,public,transactions,t)\n\nINSERT INTO transactions VALUES ( '2018-01-01 09:20:00-08', 0, 0, 0, 0, 0, 1, 10);\nINSERT INTO transactions VALUES ( '2018-01-02 09:30:00-08', 0, 0, 0, 0, 0, -1, 10);\nINSERT INTO transactions VALUES ( '2018-01-02 09:20:00-08', 0, 0, 0, 0, 0, -1, 10);\nINSERT INTO transactions VALUES ( '2018-01-02 09:10:00-08', 0, 0, 0, 0, 0, -1, 10);\nINSERT INTO transactions VALUES ( '2018-11-01 09:20:00-08', 0, 0, 0, 0, 0, 1, 10);\nINSERT INTO transactions VALUES ( '2018-11-01 10:40:00-08', 0, 0, 0, 0, 0, 1, 10);\nINSERT INTO transactions VALUES ( '2018-11-01 11:50:00-08', 0, 0, 0, 0, 0, 1, 10);\nINSERT INTO transactions VALUES ( '2018-11-01 12:10:00-08', 0, 0, 0, 0, 0, -1, 10);\nINSERT INTO transactions VALUES ( '2018-11-01 13:10:00-08', 0, 0, 0, 0, 0, -1, 10);\nINSERT INTO transactions VALUES ( '2018-11-02 09:20:00-08', 0, 0, 0, 0, 0, 1, 10);\nINSERT INTO transactions VALUES ( '2018-11-02 10:30:00-08', 0, 0, 0, 0, 0, -1, 10);\nCREATE materialized view cashflows(\n    bucket,\n  \tamount,\n    cashflow,\n    cashflow2\n) WITH (\n    timescaledb.continuous,\n    timescaledb.materialized_only = true\n) AS\nSELECT time_bucket ('1 day', time) AS bucket,\n\tamount,\n  CASE\n      WHEN amount < 0 THEN (0 - sum(fiat_value))\n      ELSE sum(fiat_value)\n  END AS cashflow,\n  amount + sum(fiat_value)\nFROM transactions\nGROUP BY bucket, amount;\nNOTICE:  refreshing continuous aggregate \"cashflows\"\nSELECT h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name AS \"PART_VIEW_NAME\",\n       direct_view_name AS \"DIRECT_VIEW_NAME\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON (h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'cashflows'\n\\gset\n-- Show both the columns and the view definitions to see that\n-- references are correct in the view as well.\n\\d+ \"_timescaledb_internal\".:\"DIRECT_VIEW_NAME\"\n                         View \"_timescaledb_internal._direct_view_46\"\n  Column   |           Type           | Collation | Nullable | Default | Storage | Description \n-----------+--------------------------+-----------+----------+---------+---------+-------------\n bucket    | timestamp with time zone |           |          |         | plain   | \n amount    | integer                  |           |          |         | plain   | \n cashflow  | bigint                   |           |          |         | plain   | \n cashflow2 | bigint                   |           |          |         | plain   | \nView definition:\n SELECT time_bucket('@ 1 day'::interval, \"time\") AS bucket,\n    amount,\n        CASE\n            WHEN amount < 0 THEN 0 - sum(fiat_value)\n            ELSE sum(fiat_value)\n        END AS cashflow,\n    amount + sum(fiat_value) AS cashflow2\n   FROM transactions\n  GROUP BY (time_bucket('@ 1 day'::interval, \"time\")), amount;\n\n\\d+ \"_timescaledb_internal\".:\"PART_VIEW_NAME\"\n                         View \"_timescaledb_internal._partial_view_46\"\n  Column   |           Type           | Collation | Nullable | Default | Storage | Description \n-----------+--------------------------+-----------+----------+---------+---------+-------------\n bucket    | timestamp with time zone |           |          |         | plain   | \n amount    | integer                  |           |          |         | plain   | \n cashflow  | bigint                   |           |          |         | plain   | \n cashflow2 | bigint                   |           |          |         | plain   | \nView definition:\n SELECT time_bucket('@ 1 day'::interval, \"time\") AS bucket,\n    amount,\n        CASE\n            WHEN amount < 0 THEN 0 - sum(fiat_value)\n            ELSE sum(fiat_value)\n        END AS cashflow,\n    amount + sum(fiat_value) AS cashflow2\n   FROM transactions\n  GROUP BY (time_bucket('@ 1 day'::interval, \"time\")), amount;\n\n\\d+ \"_timescaledb_internal\".:\"MAT_TABLE_NAME\"\n                          Table \"_timescaledb_internal._materialized_hypertable_46\"\n  Column   |           Type           | Collation | Nullable | Default | Storage | Stats target | Description \n-----------+--------------------------+-----------+----------+---------+---------+--------------+-------------\n bucket    | timestamp with time zone |           | not null |         | plain   |              | \n amount    | integer                  |           |          |         | plain   |              | \n cashflow  | bigint                   |           |          |         | plain   |              | \n cashflow2 | bigint                   |           |          |         | plain   |              | \nIndexes:\n    \"_materialized_hypertable_46_amount_bucket_idx\" btree (amount, bucket DESC)\n    \"_materialized_hypertable_46_bucket_idx\" btree (bucket DESC)\nChild tables: _timescaledb_internal._hyper_46_55_chunk,\n              _timescaledb_internal._hyper_46_56_chunk\n\n\\d+ 'cashflows'\n                                    View \"public.cashflows\"\n  Column   |           Type           | Collation | Nullable | Default | Storage | Description \n-----------+--------------------------+-----------+----------+---------+---------+-------------\n bucket    | timestamp with time zone |           |          |         | plain   | \n amount    | integer                  |           |          |         | plain   | \n cashflow  | bigint                   |           |          |         | plain   | \n cashflow2 | bigint                   |           |          |         | plain   | \nView definition:\n SELECT bucket,\n    amount,\n    cashflow,\n    cashflow2\n   FROM _timescaledb_internal._materialized_hypertable_46;\n\nSELECT * FROM cashflows ORDER BY cashflows;\n            bucket            | amount | cashflow | cashflow2 \n------------------------------+--------+----------+-----------\n Sun Dec 31 16:00:00 2017 UTC |      1 |       10 |        11\n Mon Jan 01 16:00:00 2018 UTC |     -1 |      -30 |        29\n Wed Oct 31 16:00:00 2018 UTC |     -1 |      -20 |        19\n Wed Oct 31 16:00:00 2018 UTC |      1 |       30 |        31\n Thu Nov 01 16:00:00 2018 UTC |     -1 |      -10 |         9\n Thu Nov 01 16:00:00 2018 UTC |      1 |       10 |        11\n\n-- test cagg creation with named arguments in time_bucket\n-- note that positional arguments cannot follow named arguments\n-- 1. test named origin\n-- 2. test named timezone\n-- 3. test named ts\n-- 4. test named bucket width\n-- named origin\nCREATE MATERIALIZED VIEW cagg_named_origin WITH\n(timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket('1h', time, 'UTC', origin => '2001-01-03 01:23:45') AS bucket,\navg(amount) as avg_amount\nFROM transactions GROUP BY 1 WITH NO DATA;\n-- named timezone\nCREATE MATERIALIZED VIEW cagg_named_tz_origin WITH\n(timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket('1h', time, timezone => 'UTC', origin => '2001-01-03 01:23:45') AS bucket,\navg(amount) as avg_amount\nFROM transactions GROUP BY 1 WITH NO DATA;\n-- named ts\nCREATE MATERIALIZED VIEW cagg_named_ts_tz_origin WITH\n(timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket('1h', ts => time, timezone => 'UTC', origin => '2001-01-03 01:23:45') AS bucket,\navg(amount) as avg_amount\nFROM transactions GROUP BY 1 WITH NO DATA;\n-- named bucket width\nCREATE MATERIALIZED VIEW cagg_named_all WITH\n(timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(bucket_width => '1h', ts => time, timezone => 'UTC', origin => '2001-01-03 01:23:45') AS bucket,\navg(amount) as avg_amount\nFROM transactions GROUP BY 1 WITH NO DATA;\n-- Refreshing from the beginning (NULL) of a CAGG with variable time bucket and\n-- using an INTERVAL for the end timestamp (issue #5534)\nCREATE MATERIALIZED VIEW transactions_montly\nWITH (timescaledb.continuous, timescaledb.materialized_only = true) AS\nSELECT time_bucket(INTERVAL '1 month', time) AS bucket,\n       SUM(fiat_value),\n       MAX(fiat_value),\n       MIN(fiat_value)\n  FROM transactions\nGROUP BY 1\nWITH NO DATA;\n-- No rows\nSELECT * FROM transactions_montly ORDER BY bucket;\n bucket | sum | max | min \n--------+-----+-----+-----\n\n-- Refresh from beginning of the CAGG for 1 month\nCALL refresh_continuous_aggregate('transactions_montly', NULL, INTERVAL '1 month');\nSELECT * FROM transactions_montly ORDER BY bucket;\n            bucket            | sum | max | min \n------------------------------+-----+-----+-----\n Sun Dec 31 16:00:00 2017 UTC |  40 |  10 |  10\n Wed Oct 31 16:00:00 2018 UTC |  70 |  10 |  10\n\nTRUNCATE transactions_montly;\n-- Partial refresh the CAGG from beginning to an specific timestamp\nCALL refresh_continuous_aggregate('transactions_montly', NULL, '2018-11-01 11:50:00-08'::timestamptz);\nSELECT * FROM transactions_montly ORDER BY bucket;\n            bucket            | sum | max | min \n------------------------------+-----+-----+-----\n Sun Dec 31 16:00:00 2017 UTC |  40 |  10 |  10\n\n-- Full refresh the CAGG\nCALL refresh_continuous_aggregate('transactions_montly', NULL, NULL);\nSELECT * FROM transactions_montly ORDER BY bucket;\n            bucket            | sum | max | min \n------------------------------+-----+-----+-----\n Sun Dec 31 16:00:00 2017 UTC |  40 |  10 |  10\n Wed Oct 31 16:00:00 2018 UTC |  70 |  10 |  10\n\n-- Check set_chunk_time_interval on continuous aggregate\nCREATE MATERIALIZED VIEW cagg_set_chunk_time_interval\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(INTERVAL '1 month', time) AS bucket,\n       SUM(fiat_value),\n       MAX(fiat_value),\n       MIN(fiat_value)\nFROM transactions\nGROUP BY 1\nWITH NO DATA;\nSELECT set_chunk_time_interval('cagg_set_chunk_time_interval', chunk_time_interval => interval '1 month');\n set_chunk_time_interval \n-------------------------\n \n\nCALL refresh_continuous_aggregate('cagg_set_chunk_time_interval', NULL, NULL);\nSELECT _timescaledb_functions.to_interval(d.interval_length) = interval '1 month'\nFROM _timescaledb_catalog.dimension d\n         RIGHT JOIN _timescaledb_catalog.continuous_agg ca ON ca.user_view_name = 'cagg_set_chunk_time_interval'\nWHERE d.hypertable_id = ca.mat_hypertable_id;\n ?column? \n----------\n t\n\n-- Since #6077 CAggs are materialized only by default\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 5 other objects\nNOTICE:  drop cascades to 2 other objects\nCREATE TABLE conditions (\n       time TIMESTAMPTZ NOT NULL,\n       location TEXT NOT NULL,\n       temperature DOUBLE PRECISION NULL\n);\nSELECT create_hypertable('conditions', 'time');\n    create_hypertable     \n--------------------------\n (53,public,conditions,t)\n\nINSERT INTO conditions VALUES ( '2018-01-01 09:20:00-08', 'SFO', 55);\nINSERT INTO conditions VALUES ( '2018-01-02 09:30:00-08', 'POR', 100);\nINSERT INTO conditions VALUES ( '2018-01-02 09:20:00-08', 'SFO', 65);\nINSERT INTO conditions VALUES ( '2018-01-02 09:10:00-08', 'NYC', 65);\nINSERT INTO conditions VALUES ( '2018-11-01 09:20:00-08', 'NYC', 45);\nINSERT INTO conditions VALUES ( '2018-11-01 10:40:00-08', 'NYC', 55);\nINSERT INTO conditions VALUES ( '2018-11-01 11:50:00-08', 'NYC', 65);\nINSERT INTO conditions VALUES ( '2018-11-01 12:10:00-08', 'NYC', 75);\nINSERT INTO conditions VALUES ( '2018-11-01 13:10:00-08', 'NYC', 85);\nINSERT INTO conditions VALUES ( '2018-11-02 09:20:00-08', 'NYC', 10);\nINSERT INTO conditions VALUES ( '2018-11-02 10:30:00-08', 'NYC', 20);\nCREATE MATERIALIZED VIEW conditions_daily\nWITH (timescaledb.continuous) AS\nSELECT location,\n       time_bucket(INTERVAL '1 day', time) AS bucket,\n       AVG(temperature)\n  FROM conditions\nGROUP BY location, bucket\nWITH NO DATA;\n\\d+ conditions_daily\n                                View \"public.conditions_daily\"\n  Column  |           Type           | Collation | Nullable | Default | Storage  | Description \n----------+--------------------------+-----------+----------+---------+----------+-------------\n location | text                     |           |          |         | extended | \n bucket   | timestamp with time zone |           |          |         | plain    | \n avg      | double precision         |           |          |         | plain    | \nView definition:\n SELECT location,\n    bucket,\n    avg\n   FROM _timescaledb_internal._materialized_hypertable_54;\n\n-- Should return NO ROWS\nSELECT * FROM conditions_daily ORDER BY bucket, location;\n location | bucket | avg \n----------+--------+-----\n\nALTER MATERIALIZED VIEW conditions_daily SET (timescaledb.materialized_only=false);\n\\d+ conditions_daily\n                                View \"public.conditions_daily\"\n  Column  |           Type           | Collation | Nullable | Default | Storage  | Description \n----------+--------------------------+-----------+----------+---------+----------+-------------\n location | text                     |           |          |         | extended | \n bucket   | timestamp with time zone |           |          |         | plain    | \n avg      | double precision         |           |          |         | plain    | \nView definition:\n SELECT _materialized_hypertable_54.location,\n    _materialized_hypertable_54.bucket,\n    _materialized_hypertable_54.avg\n   FROM _timescaledb_internal._materialized_hypertable_54\n  WHERE _materialized_hypertable_54.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(54)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT conditions.location,\n    time_bucket('@ 1 day'::interval, conditions.\"time\") AS bucket,\n    avg(conditions.temperature) AS avg\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(54)), '-infinity'::timestamp with time zone)\n  GROUP BY conditions.location, (time_bucket('@ 1 day'::interval, conditions.\"time\"));\n\n-- Should return ROWS because now it is realtime\nSELECT * FROM conditions_daily ORDER BY bucket, location;\n location |            bucket            | avg \n----------+------------------------------+-----\n SFO      | Sun Dec 31 16:00:00 2017 UTC |  55\n NYC      | Mon Jan 01 16:00:00 2018 UTC |  65\n POR      | Mon Jan 01 16:00:00 2018 UTC | 100\n SFO      | Mon Jan 01 16:00:00 2018 UTC |  65\n NYC      | Wed Oct 31 16:00:00 2018 UTC |  65\n NYC      | Thu Nov 01 16:00:00 2018 UTC |  15\n\n-- Should return ROWS because we refreshed it\nALTER MATERIALIZED VIEW conditions_daily SET (timescaledb.materialized_only=true);\n\\d+ conditions_daily\n                                View \"public.conditions_daily\"\n  Column  |           Type           | Collation | Nullable | Default | Storage  | Description \n----------+--------------------------+-----------+----------+---------+----------+-------------\n location | text                     |           |          |         | extended | \n bucket   | timestamp with time zone |           |          |         | plain    | \n avg      | double precision         |           |          |         | plain    | \nView definition:\n SELECT location,\n    bucket,\n    avg\n   FROM _timescaledb_internal._materialized_hypertable_54;\n\nCALL refresh_continuous_aggregate('conditions_daily', NULL, NULL);\nSELECT * FROM conditions_daily ORDER BY bucket, location;\n location |            bucket            | avg \n----------+------------------------------+-----\n SFO      | Sun Dec 31 16:00:00 2017 UTC |  55\n NYC      | Mon Jan 01 16:00:00 2018 UTC |  65\n POR      | Mon Jan 01 16:00:00 2018 UTC | 100\n SFO      | Mon Jan 01 16:00:00 2018 UTC |  65\n NYC      | Wed Oct 31 16:00:00 2018 UTC |  65\n NYC      | Thu Nov 01 16:00:00 2018 UTC |  15\n\n-- Test TRUNCATE over a Realtime CAgg\nDROP MATERIALIZED VIEW conditions_daily;\nNOTICE:  drop cascades to 2 other objects\nCREATE MATERIALIZED VIEW conditions_daily\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT location,\n       time_bucket(INTERVAL '1 day', time) AS bucket,\n       AVG(temperature)\n  FROM conditions\nGROUP BY location, bucket\nWITH NO DATA;\nSELECT mat_hypertable_id FROM _timescaledb_catalog.continuous_agg WHERE user_view_name = 'conditions_daily' \\gset\n-- Check the current watermark for an empty CAgg\nSELECT _timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(:mat_hypertable_id)) AS watermak_empty_cagg;\n       watermak_empty_cagg       \n---------------------------------\n Sun Nov 23 16:00:00 4714 UTC BC\n\n-- Refresh the CAGG\nCALL refresh_continuous_aggregate('conditions_daily', NULL, NULL);\n-- Check the watermark after the refresh and before truncate the CAgg\nSELECT _timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(:mat_hypertable_id)) AS watermak_before;\n       watermak_before        \n------------------------------\n Fri Nov 02 16:00:00 2018 UTC\n\n-- Exists chunks before truncate the cagg (> 0)\nSELECT count(*) FROM show_chunks('conditions_daily');\n count \n-------\n     2\n\n-- Truncate the given CAgg, it should reset the watermark to the empty state\nTRUNCATE conditions_daily;\n-- No chunks remains after truncate the cagg (= 0)\nSELECT count(*) FROM show_chunks('conditions_daily');\n count \n-------\n     0\n\n-- Watermark should be reseted\nSELECT _timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(:mat_hypertable_id)) AS watermak_after;\n         watermak_after          \n---------------------------------\n Sun Nov 23 16:00:00 4714 UTC BC\n\n-- Should return ROWS because the watermark was reseted by the TRUNCATE\nSELECT * FROM conditions_daily ORDER BY bucket, location;\n location |            bucket            | avg \n----------+------------------------------+-----\n SFO      | Sun Dec 31 16:00:00 2017 UTC |  55\n NYC      | Mon Jan 01 16:00:00 2018 UTC |  65\n POR      | Mon Jan 01 16:00:00 2018 UTC | 100\n SFO      | Mon Jan 01 16:00:00 2018 UTC |  65\n NYC      | Wed Oct 31 16:00:00 2018 UTC |  65\n NYC      | Thu Nov 01 16:00:00 2018 UTC |  15\n\n-- check compression settings are cleaned up when deleting a cagg with compression\nCREATE TABLE cagg_cleanup(time timestamptz not null);\nSELECT table_name FROM create_hypertable('cagg_cleanup','time');\n  table_name  \n--------------\n cagg_cleanup\n\nINSERT INTO cagg_cleanup SELECT '2020-01-01';\nCREATE MATERIALIZED VIEW cagg1 WITH (timescaledb.continuous) AS SELECT time_bucket('1h',time) FROM cagg_cleanup GROUP BY 1;\nNOTICE:  refreshing continuous aggregate \"cagg1\"\nALTER MATERIALIZED VIEW cagg1 SET (timescaledb.compress);\nNOTICE:  defaulting compress_orderby to time_bucket\nSELECT count(compress_chunk(ch)) FROM show_chunks('cagg1') ch;\n count \n-------\n     1\n\nDROP MATERIALIZED VIEW cagg1;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_57_70_chunk\nSELECT * FROM _timescaledb_catalog.compression_settings;\n relid | compress_relid | segmentby | orderby | orderby_desc | orderby_nullsfirst | index \n-------+----------------+-----------+---------+--------------+--------------------+-------\n\n-- test WITH namespace alias\nCREATE TABLE with_alias(time timestamptz not null);\nCREATE MATERIALIZED VIEW cagg_alias\nWITH (tsdb.continuous, tsdb.materialized_only=false) AS\nSELECT time_bucket(INTERVAL '1 day', time) FROM conditions GROUP BY 1 WITH NO DATA;\nALTER MATERIALIZED VIEW cagg_alias SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW cagg_alias SET (tsdb.materialized_only=false);\nDROP MATERIALIZED VIEW cagg_alias;\n-- test SET chunk_time_interval\nCREATE MATERIALIZED VIEW cagg_set\nWITH (tsdb.continuous, tsdb.chunk_interval='1day') AS\nSELECT time_bucket(INTERVAL '1 day', time) AS cagg_interval_setter FROM conditions GROUP BY 1 WITH NO DATA;\nSELECT column_name, time_interval FROM timescaledb_information.dimensions WHERE column_name='cagg_interval_setter';\n     column_name      | time_interval \n----------------------+---------------\n cagg_interval_setter | @ 1 day\n\nALTER MATERIALIZED VIEW cagg_set SET (tsdb.chunk_interval='23 day');\nSELECT column_name, time_interval FROM timescaledb_information.dimensions WHERE column_name='cagg_interval_setter';\n     column_name      | time_interval \n----------------------+---------------\n cagg_interval_setter | @ 23 days\n\nALTER MATERIALIZED VIEW cagg_set SET (tsdb.chunk_interval='6 month');\nSELECT column_name, time_interval FROM timescaledb_information.dimensions WHERE column_name='cagg_interval_setter';\n     column_name      | time_interval \n----------------------+---------------\n cagg_interval_setter | @ 180 days\n\nALTER MATERIALIZED VIEW cagg_set SET (tsdb.chunk_interval='1 year');\nSELECT column_name, time_interval FROM timescaledb_information.dimensions WHERE column_name='cagg_interval_setter';\n     column_name      | time_interval \n----------------------+---------------\n cagg_interval_setter | @ 360 days\n\n-- test cagg with stable functions\nCREATE MATERIALIZED VIEW cagg_stable WITH (tsdb.continuous) AS\nSELECT sum(temperature), max(time + INTERVAL '1h')\nFROM conditions\nGROUP BY time_bucket('1week', time), location;\nWARNING:  using non-immutable functions in continuous aggregate view may lead to inconsistent results on rematerialization\nNOTICE:  refreshing continuous aggregate \"cagg_stable\"\nSELECT * FROM cagg_stable t ORDER BY t;\n sum |             max              \n-----+------------------------------\n  65 | Tue Jan 02 10:10:00 2018 UTC\n 100 | Tue Jan 02 10:30:00 2018 UTC\n 120 | Tue Jan 02 10:20:00 2018 UTC\n 355 | Fri Nov 02 11:30:00 2018 UTC\n\n--aggregate without combine function but stable function\nCREATE MATERIALIZED VIEW cagg_json_agg WITH (tsdb.continuous, tsdb.materialized_only=false)\nAS SELECT json_agg(location) from conditions group by time_bucket('1week', time), location WITH NO DATA;\nWARNING:  using non-immutable functions in continuous aggregate view may lead to inconsistent results on rematerialization\nCREATE FUNCTION test_stablefunc(int) RETURNS int LANGUAGE 'sql' STABLE AS 'SELECT $1 + 10';\nCREATE MATERIALIZED VIEW cagg_stable2 WITH (tsdb.continuous) AS\nSELECT sum(test_stablefunc(temperature::int)), min(location)\nFROM conditions\nGROUP BY time_bucket('1week', time) WITH NO DATA;\nWARNING:  using non-immutable functions in continuous aggregate view may lead to inconsistent results on rematerialization\nCREATE MATERIALIZED VIEW cagg_stable3 WITH (tsdb.continuous) AS\nSELECT sum(temperature), min(location)\nFROM conditions\nGROUP BY time_bucket('1week', time), test_stablefunc(temperature::int) WITH NO DATA;\nWARNING:  using non-immutable functions in continuous aggregate view may lead to inconsistent results on rematerialization\n-- test window functions in caggs\n-- first do sanity check that we error without the GUC\n\\set ON_ERROR_STOP 0\nCREATE MATERIALIZED VIEW cagg_window WITH (tsdb.continuous)\nAS SELECT time_bucket('1week', time), rank() OVER (PARTITION BY time_bucket('1 week',time)) FROM conditions GROUP BY 1;\nERROR:  invalid continuous aggregate query\n\\set ON_ERROR_STOP 1\nSET timescaledb.enable_cagg_window_functions TO on;\nCREATE MATERIALIZED VIEW cagg_window_1 WITH (tsdb.continuous)\nAS SELECT time_bucket('1week', time), rank() OVER (PARTITION BY time_bucket('1 week',time)) FROM conditions GROUP BY 1;\nWARNING:  window function support is experimental and may result in unexpected results depending on the functions used.\nNOTICE:  refreshing continuous aggregate \"cagg_window_1\"\nCREATE MATERIALIZED VIEW cagg_window_2 WITH (tsdb.continuous)\nAS SELECT time_bucket('1week', time), rank() OVER (PARTITION BY time_bucket('1 week',time), location) FROM conditions GROUP BY 1, location;\nWARNING:  window function support is experimental and may result in unexpected results depending on the functions used.\nNOTICE:  refreshing continuous aggregate \"cagg_window_2\"\nCREATE MATERIALIZED VIEW cagg_window_3 WITH (tsdb.continuous)\nAS SELECT time_bucket('1week', time), rank() OVER (PARTITION BY time_bucket('1 week',time)) FROM conditions GROUP BY 1, location;\nWARNING:  window function support is experimental and may result in unexpected results depending on the functions used.\nNOTICE:  refreshing continuous aggregate \"cagg_window_3\"\nCREATE MATERIALIZED VIEW cagg_window_4 WITH (tsdb.continuous)\nAS SELECT time_bucket('1week', time), rank() OVER w FROM conditions GROUP BY 1, location WINDOW w AS (PARTITION BY time_bucket('1 week',time));\nWARNING:  window function support is experimental and may result in unexpected results depending on the functions used.\nNOTICE:  refreshing continuous aggregate \"cagg_window_4\"\n-- test setting chunk_interval on a cagg\nCREATE MATERIALIZED VIEW cagg_chunk_interval WITH (tsdb.continuous, tsdb.chunk_interval='1000 day') AS SELECT time_bucket('1 week', time) FROM conditions GROUP BY 1 WITH NO DATA;\nSELECT time_interval from timescaledb_information.continuous_aggregates cagg INNER JOIN timescaledb_information.dimensions dim ON cagg.materialization_hypertable_name = dim.hypertable_name  WHERE view_name='cagg_chunk_interval';\n time_interval \n---------------\n @ 1000 days\n\nALTER MATERIALIZED VIEW cagg_chunk_interval SET (tsdb.chunk_interval='110 day');\nSELECT time_interval from timescaledb_information.continuous_aggregates cagg INNER JOIN timescaledb_information.dimensions dim ON cagg.materialization_hypertable_name = dim.hypertable_name  WHERE view_name='cagg_chunk_interval';\n time_interval \n---------------\n @ 110 days\n\n-- test columnstore options\nCREATE MATERIALIZED VIEW columnstore_options WITH (tsdb.continuous, tsdb.chunk_interval='1 day') AS SELECT time_bucket('1 day', time) FROM conditions GROUP BY 1 WITH NO DATA;\nSELECT column_name, compress_interval_length from _timescaledb_catalog.dimension where column_name='time_bucket' ORDER BY id DESC LIMIT 1;\n column_name | compress_interval_length \n-------------+--------------------------\n time_bucket |                         \n\nALTER MATERIALIZED VIEW columnstore_options SET (tsdb.columnstore, tsdb.chunk_interval='1 day', tsdb.orderby='time_bucket DESC', tsdb.compress_chunk_interval='2 day');\nSELECT column_name, compress_interval_length from _timescaledb_catalog.dimension where column_name='time_bucket' ORDER BY id DESC LIMIT 1;\n column_name | compress_interval_length \n-------------+--------------------------\n time_bucket |             172800000000\n\nALTER MATERIALIZED VIEW columnstore_options SET (tsdb.compress_chunk_interval='3 day');\nSELECT column_name, compress_interval_length from _timescaledb_catalog.dimension where column_name='time_bucket' ORDER BY id DESC LIMIT 1;\n column_name | compress_interval_length \n-------------+--------------------------\n time_bucket |             259200000000\n\nALTER MATERIALIZED VIEW columnstore_options SET (tsdb.compress_chunk_time_interval='4 day');\nSELECT column_name, compress_interval_length from _timescaledb_catalog.dimension where column_name='time_bucket' ORDER BY id DESC LIMIT 1;\n column_name | compress_interval_length \n-------------+--------------------------\n time_bucket |             345600000000\n\n-- test set returning functions in caggs\nCREATE TABLE kpis_raw (time TIMESTAMP NOT NULL, value INTEGER, groups TEXT[]) WITH (tsdb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nINSERT INTO kpis_raw (time, value, groups) VALUES\n  ('2025-01-01', 10, '{group1,group2,group3}'),\n  ('2025-01-02', 20, '{group1,group4}'),\n  ('2025-02-01', 10, '{group1,group3}'),\n  ('2025-02-01', 20, '{group1,group4}');\nCREATE MATERIALIZED VIEW kpis_cagg WITH (tsdb.continuous) AS\nSELECT time_bucket('7 day', time) AS bucket, count(*) AS number_of_records, avg(value) AS average, unnest(groups) AS kpi_group\nFROM kpis_raw GROUP BY bucket, kpi_group;\nNOTICE:  refreshing continuous aggregate \"kpis_cagg\"\nSELECT * FROM kpis_cagg ORDER BY bucket, kpi_group;\n          bucket          | number_of_records |       average       | kpi_group \n--------------------------+-------------------+---------------------+-----------\n Mon Dec 30 00:00:00 2024 |                 2 | 15.0000000000000000 | group1\n Mon Dec 30 00:00:00 2024 |                 1 | 10.0000000000000000 | group2\n Mon Dec 30 00:00:00 2024 |                 1 | 10.0000000000000000 | group3\n Mon Dec 30 00:00:00 2024 |                 1 | 20.0000000000000000 | group4\n Mon Jan 27 00:00:00 2025 |                 2 | 15.0000000000000000 | group1\n Mon Jan 27 00:00:00 2025 |                 1 | 10.0000000000000000 | group3\n Mon Jan 27 00:00:00 2025 |                 1 | 20.0000000000000000 | group4\n\n--TEST for caggs with non timescaledb namespace options\n--non timescaledb namespace options can be set via ALTER\n\\set ON_ERROR_STOP 0\n-- will error out\nCREATE MATERIALIZED VIEW ht_try_weekly\nWITH (timescaledb.continuous, tigerlake.newoption = true) AS\nSELECT time_bucket(interval '1 week', time) AS ts_bucket, avg(value)\nFROM kpis_raw\nGROUP BY 1;\nERROR:  non \"timescaledb\" namespace options can be set only via ALTER\n--caught by Postgres now\nALTER MATERIALIZED VIEW kpis_cagg SET (tigerlake.newoption = true);\nERROR:  \"kpis_cagg\" is not a materialized view\n\\set ON_ERROR_STOP 1\n-- TEST that cached alter stmt still works (see PR 8739)\n-- test DDL inside function\nCREATE TABLE hypertab_ddl( ts timestamp, a integer)\nWITH (timescaledb.hypertable);\nNOTICE:  using column \"ts\" as partitioning column\nCREATE OR REPLACE FUNCTION ddl_function() RETURNS VOID LANGUAGE PLPGSQL AS $$\nBEGIN\n  DROP MATERIALIZED VIEW IF EXISTS cagg_hypertab_ddl;\n  CREATE MATERIALIZED VIEW cagg_hypertab_ddl WITH (timescaledb.continuous)\n  AS SELECT time_bucket( '1 day'::interval, ts), COUNT(*)\n  FROM hypertab_ddl GROUP BY 1 WITH NO DATA;\nEND\n$$;\nSELECT ddl_function();\nNOTICE:  materialized view \"cagg_hypertab_ddl\" does not exist, skipping\n ddl_function \n--------------\n \n\nSELECT view_name from timescaledb_information.continuous_aggregates WHERE hypertable_name='hypertab_ddl';\n     view_name     \n-------------------\n cagg_hypertab_ddl\n\nSELECT ddl_function();\n ddl_function \n--------------\n \n\nSELECT view_name from timescaledb_information.continuous_aggregates WHERE hypertable_name='hypertab_ddl';\n     view_name     \n-------------------\n cagg_hypertab_ddl\n\n-- TEST continuous aggregate with functionally dependent columns\n-- name column depends on id which is in GROUP BY so name doesnt have to be\nCREATE table sensor(id int PRIMARY KEY, name text);\nCREATE TABLE sensordata(time timestamptz,id int, value float) WITH\t(timescaledb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nCREATE MATERIALIZED VIEW cagg_sensordata WITH (tsdb.continuous) AS\nSELECT s.id, s.name,time_bucket('15 minutes', sd.time) as bucket, avg(sd.value) FROM sensordata sd JOIN sensor s USING(id) GROUP BY s.id, bucket WITH NO DATA;\nSELECT * FROM cagg_sensordata;\n id | name | bucket | avg \n----+------+--------+-----\n\n"
  },
  {
    "path": "tsl/test/expected/cagg_ddl-18.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- Set this variable to avoid using a hard-coded path each time query\n-- results are compared\n\\set QUERY_RESULT_TEST_EQUAL_RELPATH '../../../../test/sql/include/query_result_test_equal.sql'\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--DDL commands on continuous aggregates\nCREATE TABLE conditions (\n      timec        TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature integer  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      timemeasure TIMESTAMPTZ,\n      timeinterval INTERVAL\n);\nSELECT table_name FROM create_hypertable('conditions', 'timec');\n table_name \n------------\n conditions\n\n-- schema tests\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET timezone TO 'UTC+8';\n-- drop if the tablespace1 and/or tablespace2 exists\nSET client_min_messages TO error;\nDROP TABLESPACE IF EXISTS tablespace1;\nDROP TABLESPACE IF EXISTS tablespace2;\nRESET client_min_messages;\nCREATE TABLESPACE tablespace1 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE1_PATH;\nCREATE TABLESPACE tablespace2 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE2_PATH;\nCREATE SCHEMA rename_schema;\nGRANT ALL ON SCHEMA rename_schema TO :ROLE_DEFAULT_PERM_USER;\nCREATE SCHEMA test_schema AUTHORIZATION :ROLE_DEFAULT_PERM_USER;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nCREATE TABLE foo(time TIMESTAMPTZ NOT NULL, data INTEGER);\nSELECT create_hypertable('foo', 'time');\n create_hypertable \n-------------------\n (2,public,foo,t)\n\nCREATE MATERIALIZED VIEW rename_test_old\n  WITH ( timescaledb.continuous, timescaledb.materialized_only=true)\nAS SELECT time_bucket('1week', time), COUNT(data)\n    FROM foo\n    GROUP BY 1 WITH NO DATA;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name  |  partial_view_schema  | partial_view_name \n------------------+-----------------+-----------------------+-------------------\n public           | rename_test_old | _timescaledb_internal | _partial_view_3\n\nALTER TABLE rename_test_old RENAME TO rename_test;\nALTER TABLE rename_test SET SCHEMA test_schema;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name |  partial_view_schema  | partial_view_name \n------------------+----------------+-----------------------+-------------------\n test_schema      | rename_test    | _timescaledb_internal | _partial_view_3\n\nALTER MATERIALIZED VIEW test_schema.rename_test SET SCHEMA rename_schema;\nDROP SCHEMA test_schema;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name |  partial_view_schema  | partial_view_name \n------------------+----------------+-----------------------+-------------------\n rename_schema    | rename_test    | _timescaledb_internal | _partial_view_3\n\nSELECT ca.raw_hypertable_id as \"RAW_HYPERTABLE_ID\",\n       h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\",\n       direct_view_name as \"DIR_VIEW_NAME\",\n       direct_view_schema as \"DIR_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'rename_test'\n\\gset\nRESET ROLE;\nSELECT current_user;\n current_user \n--------------\n super_user\n\nALTER VIEW :\"PART_VIEW_SCHEMA\".:\"PART_VIEW_NAME\" SET SCHEMA public;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name \n------------------+----------------+---------------------+-------------------\n rename_schema    | rename_test    | public              | _partial_view_3\n\n--alter direct view schema\nSELECT user_view_schema, user_view_name, direct_view_schema, direct_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name |  direct_view_schema   | direct_view_name \n------------------+----------------+-----------------------+------------------\n rename_schema    | rename_test    | _timescaledb_internal | _direct_view_3\n\nRESET ROLE;\nSELECT current_user;\n current_user \n--------------\n super_user\n\nALTER VIEW :\"DIR_VIEW_SCHEMA\".:\"DIR_VIEW_NAME\" SET SCHEMA public;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name,\n      direct_view_schema, direct_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name | direct_view_schema | direct_view_name \n------------------+----------------+---------------------+-------------------+--------------------+------------------\n rename_schema    | rename_test    | public              | _partial_view_3   | public             | _direct_view_3\n\nRESET ROLE;\nSELECT current_user;\n current_user \n--------------\n super_user\n\nALTER SCHEMA rename_schema RENAME TO new_name_schema;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name,\n       direct_view_schema, direct_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name | direct_view_schema | direct_view_name \n------------------+----------------+---------------------+-------------------+--------------------+------------------\n new_name_schema  | rename_test    | public              | _partial_view_3   | public             | _direct_view_3\n\nALTER VIEW :\"PART_VIEW_NAME\" SET SCHEMA new_name_schema;\nALTER VIEW :\"DIR_VIEW_NAME\" SET SCHEMA new_name_schema;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name,\n       direct_view_schema, direct_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name | direct_view_schema | direct_view_name \n------------------+----------------+---------------------+-------------------+--------------------+------------------\n new_name_schema  | rename_test    | new_name_schema     | _partial_view_3   | new_name_schema    | _direct_view_3\n\nRESET ROLE;\nSELECT current_user;\n current_user \n--------------\n super_user\n\nALTER SCHEMA new_name_schema RENAME TO foo_name_schema;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name \n------------------+----------------+---------------------+-------------------\n foo_name_schema  | rename_test    | foo_name_schema     | _partial_view_3\n\nALTER MATERIALIZED VIEW foo_name_schema.rename_test SET SCHEMA public;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name \n------------------+----------------+---------------------+-------------------\n public           | rename_test    | foo_name_schema     | _partial_view_3\n\nRESET ROLE;\nSELECT current_user;\n current_user \n--------------\n super_user\n\nALTER SCHEMA foo_name_schema RENAME TO rename_schema;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSET client_min_messages TO NOTICE;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema | user_view_name | partial_view_schema | partial_view_name \n------------------+----------------+---------------------+-------------------\n public           | rename_test    | rename_schema       | _partial_view_3\n\nALTER MATERIALIZED VIEW rename_test RENAME TO rename_c_aggregate;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema |   user_view_name   | partial_view_schema | partial_view_name \n------------------+--------------------+---------------------+-------------------\n public           | rename_c_aggregate | rename_schema       | _partial_view_3\n\nSELECT * FROM rename_c_aggregate;\n time_bucket | count \n-------------+-------\n\nALTER VIEW rename_schema.:\"PART_VIEW_NAME\" RENAME TO partial_view;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name,\n      direct_view_schema, direct_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema |   user_view_name   | partial_view_schema | partial_view_name | direct_view_schema | direct_view_name \n------------------+--------------------+---------------------+-------------------+--------------------+------------------\n public           | rename_c_aggregate | rename_schema       | partial_view      | rename_schema      | _direct_view_3\n\n--rename direct view\nALTER VIEW rename_schema.:\"DIR_VIEW_NAME\" RENAME TO direct_view;\nSELECT user_view_schema, user_view_name, partial_view_schema, partial_view_name,\n      direct_view_schema, direct_view_name\n      FROM _timescaledb_catalog.continuous_agg;\n user_view_schema |   user_view_name   | partial_view_schema | partial_view_name | direct_view_schema | direct_view_name \n------------------+--------------------+---------------------+-------------------+--------------------+------------------\n public           | rename_c_aggregate | rename_schema       | partial_view      | rename_schema      | direct_view\n\n-- drop_chunks tests\nDROP TABLE conditions CASCADE;\nDROP TABLE foo CASCADE;\nNOTICE:  drop cascades to 2 other objects\nCREATE TABLE drop_chunks_table(time BIGINT NOT NULL, data INTEGER);\nSELECT hypertable_id AS drop_chunks_table_id\n    FROM create_hypertable('drop_chunks_table', 'time', chunk_time_interval => 10) \\gset\nCREATE OR REPLACE FUNCTION integer_now_test() returns bigint LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), bigint '0') FROM drop_chunks_table $$;\nSELECT set_integer_now_func('drop_chunks_table', 'integer_now_test');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW drop_chunks_view\n  WITH (\n    timescaledb.continuous,\n    timescaledb.materialized_only=true\n  )\nAS SELECT time_bucket('5', time), COUNT(data)\n    FROM drop_chunks_table\n    GROUP BY 1 WITH NO DATA;\nSELECT format('%I.%I', schema_name, table_name) AS drop_chunks_mat_table,\n        schema_name AS drop_chunks_mat_schema,\n        table_name AS drop_chunks_mat_table_name\n    FROM _timescaledb_catalog.hypertable, _timescaledb_catalog.continuous_agg\n    WHERE _timescaledb_catalog.continuous_agg.raw_hypertable_id = :drop_chunks_table_id\n        AND _timescaledb_catalog.hypertable.id = _timescaledb_catalog.continuous_agg.mat_hypertable_id \\gset\n-- create 3 chunks, with 3 time bucket\nINSERT INTO drop_chunks_table SELECT i, i FROM generate_series(0, 29) AS i;\n-- Only refresh up to bucket 15 initially. Matches the old refresh\n-- behavior that didn't materialize everything\nCALL refresh_continuous_aggregate('drop_chunks_view', 0, 15);\nSELECT count(c) FROM show_chunks('drop_chunks_table') AS c;\n count \n-------\n     3\n\nSELECT count(c) FROM show_chunks('drop_chunks_view') AS c;\n count \n-------\n     1\n\nSELECT * FROM drop_chunks_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     5\n           5 |     5\n          10 |     5\n\n-- cannot drop directly from the materialization table without specifying\n-- cont. aggregate view name explicitly\n\\set ON_ERROR_STOP 0\nSELECT drop_chunks(:'drop_chunks_mat_table',\n    newer_than => -20,\n    verbose => true);\nERROR:  operation not supported on materialized hypertable\n\\set ON_ERROR_STOP 1\nSELECT count(c) FROM show_chunks('drop_chunks_table') AS c;\n count \n-------\n     3\n\nSELECT count(c) FROM show_chunks('drop_chunks_view') AS c;\n count \n-------\n     1\n\nSELECT * FROM drop_chunks_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     5\n           5 |     5\n          10 |     5\n\n-- drop chunks when the chunksize and time_bucket aren't aligned\nDROP TABLE drop_chunks_table CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_5_4_chunk\nCREATE TABLE drop_chunks_table_u(time BIGINT NOT NULL, data INTEGER);\nSELECT hypertable_id AS drop_chunks_table_u_id\n    FROM create_hypertable('drop_chunks_table_u', 'time', chunk_time_interval => 7) \\gset\nCREATE OR REPLACE FUNCTION integer_now_test1() returns bigint LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), bigint '0') FROM drop_chunks_table_u $$;\nSELECT set_integer_now_func('drop_chunks_table_u', 'integer_now_test1');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW drop_chunks_view\n  WITH (\n    timescaledb.continuous,\n    timescaledb.materialized_only=true\n  )\nAS SELECT time_bucket('3', time), COUNT(data)\n    FROM drop_chunks_table_u\n    GROUP BY 1 WITH NO DATA;\nSELECT format('%I.%I', schema_name, table_name) AS drop_chunks_mat_table_u,\n        schema_name AS drop_chunks_mat_schema,\n        table_name AS drop_chunks_mat_table_u_name\n    FROM _timescaledb_catalog.hypertable, _timescaledb_catalog.continuous_agg\n    WHERE _timescaledb_catalog.continuous_agg.raw_hypertable_id = :drop_chunks_table_u_id\n        AND _timescaledb_catalog.hypertable.id = _timescaledb_catalog.continuous_agg.mat_hypertable_id \\gset\n-- create 3 chunks, with 3 time bucket\nINSERT INTO drop_chunks_table_u SELECT i, i FROM generate_series(0, 21) AS i;\n-- Refresh up to bucket 15 to match old materializer behavior\nCALL refresh_continuous_aggregate('drop_chunks_view', 0, 15);\nSELECT count(c) FROM show_chunks('drop_chunks_table_u') AS c;\n count \n-------\n     4\n\nSELECT count(c) FROM show_chunks('drop_chunks_view') AS c;\n count \n-------\n     1\n\nSELECT * FROM drop_chunks_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     3\n           3 |     3\n           6 |     3\n           9 |     3\n          12 |     3\n\n-- TRUNCATE test\n-- Can truncate regular hypertables that have caggs\nTRUNCATE drop_chunks_table_u;\n\\set ON_ERROR_STOP 0\n-- Can't truncate materialized hypertables directly\nTRUNCATE :drop_chunks_mat_table_u;\nERROR:  cannot TRUNCATE a hypertable underlying a continuous aggregate\n\\set ON_ERROR_STOP 1\n-- Check that we don't interfere with TRUNCATE of normal table and\n-- partitioned table\nCREATE TABLE truncate (value int);\nINSERT INTO truncate VALUES (1), (2);\nTRUNCATE truncate;\nSELECT * FROM truncate;\n value \n-------\n\nCREATE TABLE truncate_partitioned (value int)\n  PARTITION BY RANGE(value);\nCREATE TABLE truncate_p1 PARTITION OF truncate_partitioned\n  FOR VALUES FROM (1) TO (3);\nINSERT INTO truncate_partitioned VALUES (1), (2);\nTRUNCATE truncate_partitioned;\nSELECT * FROM truncate_partitioned;\n value \n-------\n\n-- ALTER TABLE tests\n\\set ON_ERROR_STOP 0\n-- test a variety of ALTER TABLE statements\nALTER TABLE :drop_chunks_mat_table_u RENAME time_bucket TO bad_name;\nERROR:  renaming columns on materialization tables is not supported\nALTER TABLE :drop_chunks_mat_table_u ADD UNIQUE(time_bucket);\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u SET UNLOGGED;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u ENABLE ROW LEVEL SECURITY;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u ADD COLUMN fizzle INTEGER;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u DROP COLUMN time_bucket;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u ALTER COLUMN time_bucket DROP NOT NULL;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u ALTER COLUMN time_bucket SET DEFAULT 1;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u ALTER COLUMN time_bucket SET STORAGE EXTERNAL;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u DISABLE TRIGGER ALL;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u SET TABLESPACE foo;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u NOT OF;\nERROR:  operation not supported on materialization tables\nALTER TABLE :drop_chunks_mat_table_u OWNER TO CURRENT_USER;\nERROR:  operation not supported on materialization tables\n\\set ON_ERROR_STOP 1\nALTER TABLE :drop_chunks_mat_table_u SET SCHEMA public;\nALTER TABLE :drop_chunks_mat_table_u_name RENAME TO new_name;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSET client_min_messages TO NOTICE;\nSELECT * FROM new_name ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     3\n           3 |     3\n           6 |     3\n           9 |     3\n          12 |     3\n\nSELECT * FROM drop_chunks_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     3\n           3 |     3\n           6 |     3\n           9 |     3\n          12 |     3\n\n\\set ON_ERROR_STOP 0\n-- no continuous aggregates on a continuous aggregate materialization table\nCREATE MATERIALIZED VIEW new_name_view\n  WITH (\n    timescaledb.continuous,\n    timescaledb.materialized_only=true\n  )\nAS SELECT time_bucket('6', time_bucket), COUNT(\"count\")\n    FROM new_name\n    GROUP BY 1 WITH NO DATA;\nERROR:  hypertable is a continuous aggregate materialization table\n\\set ON_ERROR_STOP 1\nCREATE TABLE metrics(time timestamptz NOT NULL, device_id int, v1 float, v2 float);\nSELECT create_hypertable('metrics','time');\n  create_hypertable   \n----------------------\n (8,public,metrics,t)\n\nINSERT INTO metrics SELECT generate_series('2000-01-01'::timestamptz,'2000-01-10','1m'),1,0.25,0.75;\n-- check expressions in view definition\nCREATE MATERIALIZED VIEW cagg_expr\n  WITH (timescaledb.continuous, timescaledb.materialized_only=true)\nAS\nSELECT\n  time_bucket('1d', time) AS time,\n  'Const'::text AS Const,\n  4.3::numeric AS \"numeric\",\n  first(metrics,time),\n  CASE WHEN true THEN 'foo' ELSE 'bar' END,\n  COALESCE(NULL,'coalesce'),\n  avg(v1) + avg(v2) AS avg1,\n  avg(v1+v2) AS avg2\nFROM metrics\nGROUP BY 1 WITH NO DATA;\nCALL refresh_continuous_aggregate('cagg_expr', NULL, NULL);\nSELECT * FROM cagg_expr ORDER BY time LIMIT 5;\n             time             | const | numeric |                    first                     | case | coalesce | avg1 | avg2 \n------------------------------+-------+---------+----------------------------------------------+------+----------+------+------\n Fri Dec 31 16:00:00 1999 UTC | Const |     4.3 | (\"Sat Jan 01 00:00:00 2000 UTC\",1,0.25,0.75) | foo  | coalesce |    1 |    1\n Sat Jan 01 16:00:00 2000 UTC | Const |     4.3 | (\"Sat Jan 01 16:00:00 2000 UTC\",1,0.25,0.75) | foo  | coalesce |    1 |    1\n Sun Jan 02 16:00:00 2000 UTC | Const |     4.3 | (\"Sun Jan 02 16:00:00 2000 UTC\",1,0.25,0.75) | foo  | coalesce |    1 |    1\n Mon Jan 03 16:00:00 2000 UTC | Const |     4.3 | (\"Mon Jan 03 16:00:00 2000 UTC\",1,0.25,0.75) | foo  | coalesce |    1 |    1\n Tue Jan 04 16:00:00 2000 UTC | Const |     4.3 | (\"Tue Jan 04 16:00:00 2000 UTC\",1,0.25,0.75) | foo  | coalesce |    1 |    1\n\n--test materialization of invalidation before drop\nDROP TABLE IF EXISTS drop_chunks_table CASCADE;\nNOTICE:  table \"drop_chunks_table\" does not exist, skipping\nDROP TABLE IF EXISTS drop_chunks_table_u CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_7_9_chunk\nCREATE TABLE drop_chunks_table(time BIGINT NOT NULL, data INTEGER);\nSELECT hypertable_id AS drop_chunks_table_nid\n    FROM create_hypertable('drop_chunks_table', 'time', chunk_time_interval => 10) \\gset\nCREATE OR REPLACE FUNCTION integer_now_test2() returns bigint LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), bigint '0') FROM drop_chunks_table $$;\nSELECT set_integer_now_func('drop_chunks_table', 'integer_now_test2');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW drop_chunks_view\n  WITH (\n    timescaledb.continuous,\n    timescaledb.materialized_only=true\n  )\nAS SELECT time_bucket('5', time), max(data)\n    FROM drop_chunks_table\n    GROUP BY 1 WITH NO DATA;\nINSERT INTO drop_chunks_table SELECT i, i FROM generate_series(0, 20) AS i;\n--dropping chunks will process the invalidations\nSELECT drop_chunks('drop_chunks_table', older_than => (integer_now_test2()-9));\n               drop_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_10_13_chunk\n\nSELECT * FROM drop_chunks_table ORDER BY time ASC limit 1;\n time | data \n------+------\n   10 |   10\n\nINSERT INTO drop_chunks_table SELECT i, i FROM generate_series(20, 35) AS i;\nCALL refresh_continuous_aggregate('drop_chunks_view', 10, 40);\n--this will be seen after the drop its within the invalidation window and will be dropped\nINSERT INTO drop_chunks_table VALUES (26, 100);\n--this will not be processed by the drop since chunk 30-39 is not dropped but will be seen after refresh\n--shows that the drop doesn't do more work than necessary\nINSERT INTO drop_chunks_table VALUES (31, 200);\n--move the time up to 39\nINSERT INTO drop_chunks_table SELECT i, i FROM generate_series(35, 39) AS i;\n--the chunks and ranges we have thus far\nSELECT chunk_name, range_start_integer, range_end_integer\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'drop_chunks_table';\n     chunk_name     | range_start_integer | range_end_integer \n--------------------+---------------------+-------------------\n _hyper_10_14_chunk |                  10 |                20\n _hyper_10_15_chunk |                  20 |                30\n _hyper_10_16_chunk |                  30 |                40\n\n--the invalidation on 25 not yet seen\nSELECT * FROM drop_chunks_view ORDER BY time_bucket DESC;\n time_bucket | max \n-------------+-----\n          35 |  35\n          30 |  34\n          25 |  29\n          20 |  24\n          15 |  19\n          10 |  14\n\n--refresh to process the invalidations and then drop\nCALL refresh_continuous_aggregate('drop_chunks_view', NULL, (integer_now_test2()-9));\nSELECT drop_chunks('drop_chunks_table', older_than => (integer_now_test2()-9));\n               drop_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_10_14_chunk\n _timescaledb_internal._hyper_10_15_chunk\n\n--new values on 25 now seen in view\nSELECT * FROM drop_chunks_view ORDER BY time_bucket DESC;\n time_bucket | max \n-------------+-----\n          35 |  35\n          30 |  34\n          25 | 100\n          20 |  24\n          15 |  19\n          10 |  14\n\n--earliest datapoint now in table\nSELECT * FROM drop_chunks_table ORDER BY time ASC limit 1;\n time | data \n------+------\n   30 |   30\n\n--still see data in the view\nSELECT * FROM drop_chunks_view WHERE time_bucket < (integer_now_test2()-9) ORDER BY time_bucket DESC;\n time_bucket | max \n-------------+-----\n          25 | 100\n          20 |  24\n          15 |  19\n          10 |  14\n\n--no data but covers dropped chunks\nSELECT * FROM drop_chunks_table WHERE time < (integer_now_test2()-9) ORDER BY time DESC;\n time | data \n------+------\n\n--recreate the dropped chunk\nINSERT INTO drop_chunks_table SELECT i, i FROM generate_series(0, 20) AS i;\n--see data from recreated region\nSELECT * FROM drop_chunks_table WHERE time < (integer_now_test2()-9) ORDER BY time DESC;\n time | data \n------+------\n   20 |   20\n   19 |   19\n   18 |   18\n   17 |   17\n   16 |   16\n   15 |   15\n   14 |   14\n   13 |   13\n   12 |   12\n   11 |   11\n   10 |   10\n    9 |    9\n    8 |    8\n    7 |    7\n    6 |    6\n    5 |    5\n    4 |    4\n    3 |    3\n    2 |    2\n    1 |    1\n    0 |    0\n\n--should show chunk with old name and old ranges\nSELECT chunk_name, range_start_integer, range_end_integer\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'drop_chunks_table'\nORDER BY range_start_integer;\n     chunk_name     | range_start_integer | range_end_integer \n--------------------+---------------------+-------------------\n _hyper_10_18_chunk |                   0 |                10\n _hyper_10_19_chunk |                  10 |                20\n _hyper_10_20_chunk |                  20 |                30\n _hyper_10_16_chunk |                  30 |                40\n\n--We dropped everything up to the bucket starting at 30 and then\n--inserted new data up to and including time 20. Therefore, the\n--dropped data should stay the same as long as we only refresh\n--buckets that have non-dropped data.\nCALL refresh_continuous_aggregate('drop_chunks_view', 30, 40);\nSELECT * FROM drop_chunks_view ORDER BY time_bucket DESC;\n time_bucket | max \n-------------+-----\n          35 |  39\n          30 | 200\n          25 | 100\n          20 |  24\n          15 |  19\n          10 |  14\n\nSELECT format('%I.%I', schema_name, table_name) AS drop_chunks_mat_tablen,\n        schema_name AS drop_chunks_mat_schema,\n        table_name AS drop_chunks_mat_table_name\n    FROM _timescaledb_catalog.hypertable, _timescaledb_catalog.continuous_agg\n    WHERE _timescaledb_catalog.continuous_agg.raw_hypertable_id = :drop_chunks_table_nid\n        AND _timescaledb_catalog.hypertable.id = _timescaledb_catalog.continuous_agg.mat_hypertable_id \\gset\n-- TEST drop chunks from continuous aggregates by specifying view name\nSELECT drop_chunks('drop_chunks_view',\n    newer_than => -20,\n    verbose => true);\nINFO:  dropping chunk _timescaledb_internal._hyper_11_17_chunk\n               drop_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_11_17_chunk\n\n-- Test that we cannot drop chunks when specifying materialized\n-- hypertable\nINSERT INTO drop_chunks_table SELECT generate_series(45, 55), 500;\nCALL refresh_continuous_aggregate('drop_chunks_view', 45, 55);\nSELECT chunk_name, range_start_integer, range_end_integer\nFROM timescaledb_information.chunks\nWHERE hypertable_name = :'drop_chunks_mat_table_name' ORDER BY range_start_integer;\n     chunk_name     | range_start_integer | range_end_integer \n--------------------+---------------------+-------------------\n _hyper_11_23_chunk |                   0 |               100\n\n\\set ON_ERROR_STOP 0\n\\set VERBOSITY default\nSELECT drop_chunks(:'drop_chunks_mat_tablen', older_than => 60);\nERROR:  operation not supported on materialized hypertable\nDETAIL:  Hypertable \"_materialized_hypertable_11\" is a materialized hypertable.\nHINT:  Try the operation on the continuous aggregate instead.\n\\set VERBOSITY terse\n\\set ON_ERROR_STOP 1\n-----------------------------------------------------------------\n-- Test that refresh_continuous_aggregate on chunk will refresh,\n-- but only in the regions covered by the show chunks.\n-----------------------------------------------------------------\nSELECT chunk_name, range_start_integer, range_end_integer\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'drop_chunks_table'\nORDER BY 2,3;\n     chunk_name     | range_start_integer | range_end_integer \n--------------------+---------------------+-------------------\n _hyper_10_18_chunk |                   0 |                10\n _hyper_10_19_chunk |                  10 |                20\n _hyper_10_20_chunk |                  20 |                30\n _hyper_10_16_chunk |                  30 |                40\n _hyper_10_21_chunk |                  40 |                50\n _hyper_10_22_chunk |                  50 |                60\n\n-- Pick the second chunk as the one to drop\nWITH numbered_chunks AS (\n     SELECT row_number() OVER (ORDER BY range_start_integer), chunk_schema, chunk_name, range_start_integer, range_end_integer\n     FROM timescaledb_information.chunks\n     WHERE hypertable_name = 'drop_chunks_table'\n     ORDER BY 1\n)\nSELECT format('%I.%I', chunk_schema, chunk_name) AS chunk_to_drop, range_start_integer, range_end_integer\nFROM numbered_chunks\nWHERE row_number = 2 \\gset\n-- There's data in the table for the chunk/range we will drop\nSELECT * FROM drop_chunks_table\nWHERE time >= :range_start_integer\nAND time < :range_end_integer\nORDER BY 1;\n time | data \n------+------\n   10 |   10\n   11 |   11\n   12 |   12\n   13 |   13\n   14 |   14\n   15 |   15\n   16 |   16\n   17 |   17\n   18 |   18\n   19 |   19\n\n-- Make sure there is also data in the continuous aggregate\n-- CARE:\n-- Note that this behaviour of dropping the materialization table chunks and expecting a refresh\n-- that overlaps that time range to NOT update those chunks is undefined.\nCALL refresh_continuous_aggregate('drop_chunks_view', 0, 50);\nSELECT * FROM drop_chunks_view\nORDER BY 1;\n time_bucket | max \n-------------+-----\n           0 |   4\n           5 |   9\n          10 |  14\n          15 |  19\n          20 |  20\n          45 | 500\n          50 | 500\n\n-- Drop the second chunk, to leave a gap in the data\nDROP TABLE :chunk_to_drop;\n-- Verify that the second chunk is dropped\nSELECT chunk_name, range_start_integer, range_end_integer\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'drop_chunks_table'\nORDER BY 2,3;\n     chunk_name     | range_start_integer | range_end_integer \n--------------------+---------------------+-------------------\n _hyper_10_18_chunk |                   0 |                10\n _hyper_10_20_chunk |                  20 |                30\n _hyper_10_16_chunk |                  30 |                40\n _hyper_10_21_chunk |                  40 |                50\n _hyper_10_22_chunk |                  50 |                60\n\n-- Data is no longer in the table but still in the view\nSELECT * FROM drop_chunks_table\nWHERE time >= :range_start_integer\nAND time < :range_end_integer\nORDER BY 1;\n time | data \n------+------\n\nSELECT * FROM drop_chunks_view\nWHERE time_bucket >= :range_start_integer\nAND time_bucket < :range_end_integer\nORDER BY 1;\n time_bucket | max \n-------------+-----\n          10 |  14\n          15 |  19\n\n-- Insert a large value in one of the chunks that will be dropped\nINSERT INTO drop_chunks_table VALUES (:range_start_integer-1, 100);\n-- Now refresh and drop the two adjecent chunks\nCALL refresh_continuous_aggregate('drop_chunks_view', NULL, 30);\nSELECT drop_chunks('drop_chunks_table', older_than=>30);\n               drop_chunks                \n------------------------------------------\n _timescaledb_internal._hyper_10_18_chunk\n _timescaledb_internal._hyper_10_20_chunk\n\n-- Verify that the chunks are dropped\nSELECT chunk_name, range_start_integer, range_end_integer\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'drop_chunks_table'\nORDER BY 2,3;\n     chunk_name     | range_start_integer | range_end_integer \n--------------------+---------------------+-------------------\n _hyper_10_16_chunk |                  30 |                40\n _hyper_10_21_chunk |                  40 |                50\n _hyper_10_22_chunk |                  50 |                60\n\n-- The continuous aggregate should be refreshed in the regions covered\n-- by the dropped chunks, but not in the \"gap\" region, i.e., the\n-- region of the chunk that was dropped via DROP TABLE.\nSELECT * FROM drop_chunks_view\nORDER BY 1;\n time_bucket | max \n-------------+-----\n           0 |   4\n           5 | 100\n          20 |  20\n          45 | 500\n          50 | 500\n\n-- Now refresh in the region of the first two dropped chunks\nCALL refresh_continuous_aggregate('drop_chunks_view', 0, :range_end_integer);\n-- Aggregate data in the refreshed range should no longer exist since\n-- the underlying data was dropped.\nSELECT * FROM drop_chunks_view\nORDER BY 1;\n time_bucket | max \n-------------+-----\n          20 |  20\n          45 | 500\n          50 | 500\n\n--------------------------------------------------------------------\n-- Check that we can create a materialized table in a tablespace. We\n-- create one with tablespace and one without and compare them.\nCREATE VIEW cagg_info AS\nWITH\n  caggs AS (\n    SELECT format('%I.%I', user_view_schema, user_view_name)::regclass AS user_view,\n           format('%I.%I', direct_view_schema, direct_view_name)::regclass AS direct_view,\n           format('%I.%I', partial_view_schema, partial_view_name)::regclass AS partial_view,\n           format('%I.%I', ht.schema_name, ht.table_name)::regclass AS mat_relid\n      FROM _timescaledb_catalog.hypertable ht,\n           _timescaledb_catalog.continuous_agg cagg\n     WHERE ht.id = cagg.mat_hypertable_id\n  )\nSELECT user_view,\n       pg_get_userbyid(relowner) AS user_view_owner,\n       relname AS mat_table,\n       (SELECT pg_get_userbyid(relowner) FROM pg_class WHERE oid = mat_relid) AS mat_table_owner,\n       direct_view,\n       (SELECT pg_get_userbyid(relowner) FROM pg_class WHERE oid = direct_view) AS direct_view_owner,\n       partial_view,\n       (SELECT pg_get_userbyid(relowner) FROM pg_class WHERE oid = partial_view) AS partial_view_owner,\n       (SELECT spcname FROM pg_tablespace WHERE oid = reltablespace) AS tablespace\n  FROM pg_class JOIN caggs ON pg_class.oid = caggs.mat_relid;\nGRANT SELECT ON cagg_info TO PUBLIC;\nCREATE VIEW chunk_info AS\nSELECT ht.schema_name, ht.table_name, relname AS chunk_name,\n       (SELECT spcname FROM pg_tablespace WHERE oid = reltablespace) AS tablespace\n  FROM pg_class c,\n       _timescaledb_catalog.hypertable ht,\n       _timescaledb_catalog.chunk ch\n WHERE ch.table_name = c.relname AND ht.id = ch.hypertable_id;\nCREATE TABLE whatever(time BIGINT NOT NULL, data INTEGER);\nSELECT hypertable_id AS whatever_nid\n  FROM create_hypertable('whatever', 'time', chunk_time_interval => 10)\n\\gset\nSELECT set_integer_now_func('whatever', 'integer_now_test');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW whatever_view_1\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT time_bucket('5', time), COUNT(data)\n  FROM whatever GROUP BY 1 WITH NO DATA;\nCREATE MATERIALIZED VIEW whatever_view_2\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nTABLESPACE tablespace1 AS\nSELECT time_bucket('5', time), COUNT(data)\n  FROM whatever GROUP BY 1 WITH NO DATA;\nINSERT INTO whatever SELECT i, i FROM generate_series(0, 29) AS i;\nCALL refresh_continuous_aggregate('whatever_view_1', NULL, NULL);\nCALL refresh_continuous_aggregate('whatever_view_2', NULL, NULL);\nSELECT user_view,\n       mat_table,\n       cagg_info.tablespace AS mat_tablespace,\n       chunk_name,\n       chunk_info.tablespace AS chunk_tablespace\n  FROM cagg_info, chunk_info\n WHERE mat_table::text = table_name\n   AND user_view::text LIKE 'whatever_view%';\n    user_view    |          mat_table          | mat_tablespace |     chunk_name     | chunk_tablespace \n-----------------+-----------------------------+----------------+--------------------+------------------\n whatever_view_1 | _materialized_hypertable_13 |                | _hyper_13_27_chunk | \n whatever_view_2 | _materialized_hypertable_14 | tablespace1    | _hyper_14_28_chunk | tablespace1\n\nALTER MATERIALIZED VIEW whatever_view_1 SET TABLESPACE tablespace2;\nSELECT user_view,\n       mat_table,\n       cagg_info.tablespace AS mat_tablespace,\n       chunk_name,\n       chunk_info.tablespace AS chunk_tablespace\n  FROM cagg_info, chunk_info\n WHERE mat_table::text = table_name\n   AND user_view::text LIKE 'whatever_view%';\n    user_view    |          mat_table          | mat_tablespace |     chunk_name     | chunk_tablespace \n-----------------+-----------------------------+----------------+--------------------+------------------\n whatever_view_1 | _materialized_hypertable_13 | tablespace2    | _hyper_13_27_chunk | tablespace2\n whatever_view_2 | _materialized_hypertable_14 | tablespace1    | _hyper_14_28_chunk | tablespace1\n\nDROP MATERIALIZED VIEW whatever_view_1;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_13_27_chunk\nDROP MATERIALIZED VIEW whatever_view_2;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_14_28_chunk\n-- test bucket width expressions on integer hypertables\nCREATE TABLE metrics_int2 (\n  time int2 NOT NULL,\n  device_id int,\n  v1 float,\n  v2 float\n);\nCREATE TABLE metrics_int4 (\n  time int4 NOT NULL,\n  device_id int,\n  v1 float,\n  v2 float\n);\nCREATE TABLE metrics_int8 (\n  time int8 NOT NULL,\n  device_id int,\n  v1 float,\n  v2 float\n);\nSELECT create_hypertable (('metrics_' || dt)::regclass, 'time', chunk_time_interval => 10)\nFROM (\n  VALUES ('int2'),\n    ('int4'),\n    ('int8')) v (dt);\n     create_hypertable      \n----------------------------\n (15,public,metrics_int2,t)\n (16,public,metrics_int4,t)\n (17,public,metrics_int8,t)\n\nCREATE OR REPLACE FUNCTION int2_now ()\n  RETURNS int2\n  LANGUAGE SQL\n  STABLE\n  AS $$\n  SELECT 10::int2\n$$;\nCREATE OR REPLACE FUNCTION int4_now ()\n  RETURNS int4\n  LANGUAGE SQL\n  STABLE\n  AS $$\n  SELECT 10::int4\n$$;\nCREATE OR REPLACE FUNCTION int8_now ()\n  RETURNS int8\n  LANGUAGE SQL\n  STABLE\n  AS $$\n  SELECT 10::int8\n$$;\nSELECT set_integer_now_func (('metrics_' || dt)::regclass, (dt || '_now')::regproc)\nFROM (\n  VALUES ('int2'),\n    ('int4'),\n    ('int8')) v (dt);\n set_integer_now_func \n----------------------\n \n \n \n\n-- width expression for int2 hypertables\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1::smallint, time)\nFROM metrics_int2\nGROUP BY 1;\nNOTICE:  continuous aggregate \"width_expr\" is already up-to-date\nDROP MATERIALIZED VIEW width_expr;\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1::smallint + 2::smallint, time)\nFROM metrics_int2\nGROUP BY 1;\nNOTICE:  continuous aggregate \"width_expr\" is already up-to-date\nDROP MATERIALIZED VIEW width_expr;\n-- width expression for int4 hypertables\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1, time)\nFROM metrics_int4\nGROUP BY 1;\nNOTICE:  continuous aggregate \"width_expr\" is already up-to-date\nDROP MATERIALIZED VIEW width_expr;\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1 + 2, time)\nFROM metrics_int4\nGROUP BY 1;\nNOTICE:  continuous aggregate \"width_expr\" is already up-to-date\nDROP MATERIALIZED VIEW width_expr;\n-- width expression for int8 hypertables\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1, time)\nFROM metrics_int8\nGROUP BY 1;\nNOTICE:  continuous aggregate \"width_expr\" is already up-to-date\nDROP MATERIALIZED VIEW width_expr;\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1 + 2, time)\nFROM metrics_int8\nGROUP BY 1;\nNOTICE:  continuous aggregate \"width_expr\" is already up-to-date\nDROP MATERIALIZED VIEW width_expr;\n\\set ON_ERROR_STOP 0\n-- non-immutable expresions should be rejected\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(extract(year FROM now())::smallint, time)\nFROM metrics_int2\nGROUP BY 1;\nERROR:  only immutable expressions allowed in time bucket function\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(extract(year FROM now())::int, time)\nFROM metrics_int4\nGROUP BY 1;\nERROR:  only immutable expressions allowed in time bucket function\nCREATE MATERIALIZED VIEW width_expr WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(extract(year FROM now())::int, time)\nFROM metrics_int8\nGROUP BY 1;\nERROR:  only immutable expressions allowed in time bucket function\n\\set ON_ERROR_STOP 1\n-- Test various ALTER MATERIALIZED VIEW statements.\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nCREATE MATERIALIZED VIEW owner_check WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(1 + 2, time)\nFROM metrics_int8\nGROUP BY 1\nWITH NO DATA;\n\\x on\nSELECT * FROM cagg_info WHERE user_view::text = 'owner_check';\n-[ RECORD 1 ]------+---------------------------------------\nuser_view          | owner_check\nuser_view_owner    | default_perm_user\nmat_table          | _materialized_hypertable_24\nmat_table_owner    | default_perm_user\ndirect_view        | _timescaledb_internal._direct_view_24\ndirect_view_owner  | default_perm_user\npartial_view       | _timescaledb_internal._partial_view_24\npartial_view_owner | default_perm_user\ntablespace         | \n\n\\x off\n-- This should not work since the target user has the wrong role, but\n-- we test that the normal checks are done when changing the owner.\n\\set ON_ERROR_STOP 0\nALTER MATERIALIZED VIEW owner_check OWNER TO :ROLE_1;\nERROR:  must be able to SET ROLE \"test_role_1\"\n\\set ON_ERROR_STOP 1\n-- Superuser can always change owner\nSET ROLE :ROLE_SUPERUSER;\n-- Add a refresh policy before changing owner to verify job owner is propagated\nSELECT add_continuous_aggregate_policy('owner_check', NULL, 1::int8, '1 day'::interval) AS cagg_job_id \\gset\nSELECT owner FROM _timescaledb_config.bgw_job WHERE id = :cagg_job_id;\n       owner       \n-------------------\n default_perm_user\n\nALTER MATERIALIZED VIEW owner_check OWNER TO :ROLE_1;\n\\x on\nSELECT * FROM cagg_info WHERE user_view::text = 'owner_check';\n-[ RECORD 1 ]------+---------------------------------------\nuser_view          | owner_check\nuser_view_owner    | test_role_1\nmat_table          | _materialized_hypertable_24\nmat_table_owner    | test_role_1\ndirect_view        | _timescaledb_internal._direct_view_24\ndirect_view_owner  | test_role_1\npartial_view       | _timescaledb_internal._partial_view_24\npartial_view_owner | test_role_1\ntablespace         | \n\n\\x off\n-- make sure policy job owner is propagated\nSELECT owner FROM _timescaledb_config.bgw_job WHERE id = :cagg_job_id;\n    owner    \n-------------\n test_role_1\n\nSELECT remove_continuous_aggregate_policy('owner_check');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n--\n-- Test drop continuous aggregate cases\n--\n-- Issue: #2608\n--\nCREATE OR REPLACE FUNCTION test_int_now()\n  RETURNS INT LANGUAGE SQL STABLE AS\n$BODY$\n  SELECT 50;\n$BODY$;\nCREATE TABLE conditionsnm(time_int INT NOT NULL, device INT, value FLOAT);\nSELECT create_hypertable('conditionsnm', 'time_int', chunk_time_interval => 10);\n     create_hypertable      \n----------------------------\n (25,public,conditionsnm,t)\n\nSELECT set_integer_now_func('conditionsnm', 'test_int_now');\n set_integer_now_func \n----------------------\n \n\nINSERT INTO conditionsnm\nSELECT time_val, time_val % 4, 3.14 FROM generate_series(0,100,1) AS time_val;\n-- Case 1: DROP\nCREATE MATERIALIZED VIEW conditionsnm_4\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE)\nAS\nSELECT time_bucket(7, time_int) as bucket,\nSUM(value), COUNT(value)\nFROM conditionsnm GROUP BY bucket WITH DATA;\nNOTICE:  refreshing continuous aggregate \"conditionsnm_4\"\nDROP materialized view conditionsnm_4;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_26_40_chunk\n-- Case 2: DROP CASCADE should have similar behaviour as DROP\nCREATE MATERIALIZED VIEW conditionsnm_4\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE)\nAS\nSELECT time_bucket(7, time_int) as bucket,\nSUM(value), COUNT(value)\nFROM conditionsnm GROUP BY bucket WITH DATA;\nNOTICE:  refreshing continuous aggregate \"conditionsnm_4\"\nDROP materialized view conditionsnm_4 CASCADE;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_27_41_chunk\n-- Case 3: require CASCADE in case of dependent object\nCREATE MATERIALIZED VIEW conditionsnm_4\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE)\nAS\nSELECT time_bucket(7, time_int) as bucket,\nSUM(value), COUNT(value)\nFROM conditionsnm GROUP BY bucket WITH DATA;\nNOTICE:  refreshing continuous aggregate \"conditionsnm_4\"\nCREATE VIEW see_cagg as select * from conditionsnm_4;\n\\set ON_ERROR_STOP 0\nDROP MATERIALIZED VIEW conditionsnm_4;\nERROR:  cannot drop view conditionsnm_4 because other objects depend on it\n\\set ON_ERROR_STOP 1\n-- Case 4: DROP CASCADE with dependency\nDROP MATERIALIZED VIEW conditionsnm_4 CASCADE;\nNOTICE:  drop cascades to view see_cagg\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_28_42_chunk\n-- Test DROP SCHEMA CASCADE with continuous aggregates\n--\n-- Issue: #2350\n--\n-- Case 1: DROP SCHEMA CASCADE\nCREATE SCHEMA test_schema;\nCREATE TABLE test_schema.telemetry_raw (\n  ts        TIMESTAMP WITH TIME ZONE NOT NULL,\n  value     DOUBLE PRECISION\n);\nSELECT create_hypertable('test_schema.telemetry_raw', 'ts');\n        create_hypertable         \n----------------------------------\n (29,test_schema,telemetry_raw,t)\n\nCREATE MATERIALIZED VIEW test_schema.telemetry_1s\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false)\n    AS\nSELECT time_bucket(INTERVAL '1s', ts) AS ts_1s,\n       avg(value)\n  FROM test_schema.telemetry_raw\n GROUP BY ts_1s WITH NO DATA;\nSELECT ca.raw_hypertable_id,\n       h.schema_name,\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON (h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'telemetry_1s';\n raw_hypertable_id |      schema_name      |       MAT_TABLE_NAME        |  PART_VIEW_NAME  |  partial_view_schema  \n-------------------+-----------------------+-----------------------------+------------------+-----------------------\n                29 | _timescaledb_internal | _materialized_hypertable_30 | _partial_view_30 | _timescaledb_internal\n\n\\gset\nDROP SCHEMA test_schema CASCADE;\nNOTICE:  drop cascades to 4 other objects\nSELECT count(*) FROM pg_class WHERE relname = :'MAT_TABLE_NAME';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = :'PART_VIEW_NAME';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = 'telemetry_1s';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_namespace WHERE nspname = 'test_schema';\n count \n-------\n     0\n\n-- Case 2: DROP SCHEMA CASCADE with multiple caggs\nCREATE SCHEMA test_schema;\nCREATE TABLE test_schema.telemetry_raw (\n  ts        TIMESTAMP WITH TIME ZONE NOT NULL,\n  value     DOUBLE PRECISION\n);\nSELECT create_hypertable('test_schema.telemetry_raw', 'ts');\n        create_hypertable         \n----------------------------------\n (31,test_schema,telemetry_raw,t)\n\nCREATE MATERIALIZED VIEW test_schema.cagg1\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false)\n    AS\nSELECT time_bucket(INTERVAL '1s', ts) AS ts_1s,\n       avg(value)\n  FROM test_schema.telemetry_raw\n GROUP BY ts_1s WITH NO DATA;\nCREATE MATERIALIZED VIEW test_schema.cagg2\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false)\n    AS\nSELECT time_bucket(INTERVAL '1s', ts) AS ts_1s,\n       avg(value)\n  FROM test_schema.telemetry_raw\n GROUP BY ts_1s WITH NO DATA;\nSELECT ca.raw_hypertable_id,\n       h.schema_name,\n       h.table_name AS \"MAT_TABLE_NAME1\",\n       partial_view_name as \"PART_VIEW_NAME1\",\n       partial_view_schema\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON (h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'cagg1';\n raw_hypertable_id |      schema_name      |       MAT_TABLE_NAME1       | PART_VIEW_NAME1  |  partial_view_schema  \n-------------------+-----------------------+-----------------------------+------------------+-----------------------\n                31 | _timescaledb_internal | _materialized_hypertable_32 | _partial_view_32 | _timescaledb_internal\n\n\\gset\nSELECT ca.raw_hypertable_id,\n       h.schema_name,\n       h.table_name AS \"MAT_TABLE_NAME2\",\n       partial_view_name as \"PART_VIEW_NAME2\",\n       partial_view_schema\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON (h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'cagg2';\n raw_hypertable_id |      schema_name      |       MAT_TABLE_NAME2       | PART_VIEW_NAME2  |  partial_view_schema  \n-------------------+-----------------------+-----------------------------+------------------+-----------------------\n                31 | _timescaledb_internal | _materialized_hypertable_33 | _partial_view_33 | _timescaledb_internal\n\n\\gset\nDROP SCHEMA test_schema CASCADE;\nNOTICE:  drop cascades to 7 other objects\nSELECT count(*) FROM pg_class WHERE relname = :'MAT_TABLE_NAME1';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = :'PART_VIEW_NAME1';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = 'cagg1';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = :'MAT_TABLE_NAME2';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = :'PART_VIEW_NAME2';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_class WHERE relname = 'cagg2';\n count \n-------\n     0\n\nSELECT count(*) FROM pg_namespace WHERE nspname = 'test_schema';\n count \n-------\n     0\n\nDROP TABLESPACE tablespace1;\nDROP TABLESPACE tablespace2;\n-- Check that we can rename a column of a materialized view and still\n-- rebuild it after (#3051, #3405)\nCREATE TABLE conditions (\n       time TIMESTAMPTZ NOT NULL,\n       location TEXT NOT NULL,\n       temperature DOUBLE PRECISION NULL\n);\nSELECT create_hypertable('conditions', 'time');\n    create_hypertable     \n--------------------------\n (34,public,conditions,t)\n\nINSERT INTO conditions VALUES ( '2018-01-01 09:20:00-08', 'SFO', 55);\nINSERT INTO conditions VALUES ( '2018-01-02 09:30:00-08', 'por', 100);\nINSERT INTO conditions VALUES ( '2018-01-02 09:20:00-08', 'SFO', 65);\nINSERT INTO conditions VALUES ( '2018-01-02 09:10:00-08', 'NYC', 65);\nINSERT INTO conditions VALUES ( '2018-11-01 09:20:00-08', 'NYC', 45);\nINSERT INTO conditions VALUES ( '2018-11-01 10:40:00-08', 'NYC', 55);\nINSERT INTO conditions VALUES ( '2018-11-01 11:50:00-08', 'NYC', 65);\nINSERT INTO conditions VALUES ( '2018-11-01 12:10:00-08', 'NYC', 75);\nINSERT INTO conditions VALUES ( '2018-11-01 13:10:00-08', 'NYC', 85);\nINSERT INTO conditions VALUES ( '2018-11-02 09:20:00-08', 'NYC', 10);\nINSERT INTO conditions VALUES ( '2018-11-02 10:30:00-08', 'NYC', 20);\nCREATE MATERIALIZED VIEW conditions_daily\nWITH (timescaledb.continuous, timescaledb.materialized_only = true) AS\nSELECT location,\n       time_bucket(INTERVAL '1 day', time) AS bucket,\n       AVG(temperature)\n  FROM conditions\nGROUP BY location, bucket\nWITH NO DATA;\nCREATE MATERIALIZED VIEW conditions_weekly\nWITH (timescaledb.continuous, timescaledb.materialized_only = true) AS\nSELECT location,\n       time_bucket(INTERVAL '7 day', bucket) AS bucket,\n       AVG(avg)\n  FROM conditions_daily\nGROUP BY 1, 2\nWITH NO DATA;\nSELECT format('%I.%I', '_timescaledb_internal', h.table_name) AS \"MAT_TABLE_NAME\",\n       format('%I.%I', '_timescaledb_internal', partial_view_name) AS \"PART_VIEW_NAME\",\n       format('%I.%I', '_timescaledb_internal', direct_view_name) AS \"DIRECT_VIEW_NAME\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON (h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'conditions_daily'\n\\gset\n-- Show both the columns and the view definitions to see that\n-- references are correct in the view as well.\nSELECT * FROM test.show_columns('conditions_daily');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n bucket   | timestamp with time zone | f\n avg      | double precision         | f\n\nSELECT * FROM test.show_columns(:'DIRECT_VIEW_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n bucket   | timestamp with time zone | f\n avg      | double precision         | f\n\nSELECT * FROM test.show_columns(:'PART_VIEW_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n bucket   | timestamp with time zone | f\n avg      | double precision         | f\n\nSELECT * FROM test.show_columns(:'MAT_TABLE_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n bucket   | timestamp with time zone | t\n avg      | double precision         | f\n\nALTER MATERIALIZED VIEW conditions_daily RENAME COLUMN bucket to \"time\";\n-- Show both the columns and the view definitions to see that\n-- references are correct in the view as well.\nSELECT * FROM test.show_columns(' conditions_daily');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | f\n avg      | double precision         | f\n\nSELECT * FROM test.show_columns(:'DIRECT_VIEW_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | f\n avg      | double precision         | f\n\nSELECT * FROM test.show_columns(:'PART_VIEW_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | f\n avg      | double precision         | f\n\nSELECT * FROM test.show_columns(:'MAT_TABLE_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | t\n avg      | double precision         | f\n\n-- This will rebuild the materialized view and should succeed.\nALTER MATERIALIZED VIEW conditions_daily SET (timescaledb.materialized_only = false);\n-- Refresh the continuous aggregate to check that it works after the\n-- rename.\n\\set VERBOSITY verbose\nCALL refresh_continuous_aggregate('conditions_daily', NULL, NULL);\n\\set VERBOSITY terse\n-- Rename another column after the flip and verify toggling back and\n-- forth still works. This exercises the rename when the user view\n-- already has a UNION ALL query (materialized_only = false).\nALTER MATERIALIZED VIEW conditions_daily RENAME COLUMN avg TO average;\nSELECT * FROM test.show_columns('conditions_daily');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | f\n average  | double precision         | f\n\nSELECT * FROM test.show_columns(:'DIRECT_VIEW_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | f\n average  | double precision         | f\n\nSELECT * FROM test.show_columns(:'MAT_TABLE_NAME');\n  Column  |           Type           | NotNull \n----------+--------------------------+---------\n location | text                     | f\n time     | timestamp with time zone | t\n average  | double precision         | f\n\nALTER MATERIALIZED VIEW conditions_daily SET (timescaledb.materialized_only = true);\nALTER MATERIALIZED VIEW conditions_daily SET (timescaledb.materialized_only = false);\n-- Verify data is still accessible after multiple renames and toggles.\nSELECT * FROM conditions_daily ORDER BY location COLLATE \"C\", time;\n location |             time             | average \n----------+------------------------------+---------\n NYC      | Mon Jan 01 16:00:00 2018 UTC |      65\n NYC      | Wed Oct 31 16:00:00 2018 UTC |      65\n NYC      | Thu Nov 01 16:00:00 2018 UTC |      15\n SFO      | Sun Dec 31 16:00:00 2017 UTC |      55\n SFO      | Mon Jan 01 16:00:00 2018 UTC |      65\n por      | Mon Jan 01 16:00:00 2018 UTC |     100\n\n-- check hierarchical continuous aggregate still works after renames and toggles on the underlying cagg\nALTER MATERIALIZED VIEW conditions_weekly SET (timescaledb.materialized_only = false);\nALTER MATERIALIZED VIEW conditions_weekly SET (timescaledb.materialized_only = true);\nSELECT * FROM conditions_weekly ORDER BY location COLLATE \"C\", bucket;\n location | bucket | avg \n----------+--------+-----\n\n-- Verify that direct rename on the materialization hypertable is blocked.\n\\set ON_ERROR_STOP 0\nALTER TABLE :MAT_TABLE_NAME RENAME COLUMN average TO avg;\nERROR:  renaming columns on materialization tables is not supported\n\\set ON_ERROR_STOP 1\n-- Rename back so subsequent tests that reference \"avg\" still work.\nALTER MATERIALIZED VIEW conditions_daily RENAME COLUMN average TO avg;\n--\n-- Indexes on continuous aggregate\n--\n\\set ON_ERROR_STOP 0\n-- unique indexes are not supported\nCREATE UNIQUE INDEX index_unique_error ON conditions_daily (\"time\", location);\nERROR:  continuous aggregates do not support UNIQUE indexes\n-- concurrently index creation not supported\nCREATE INDEX CONCURRENTLY index_concurrently_avg ON conditions_daily (avg);\nERROR:  hypertables do not support concurrent index creation\n\\set ON_ERROR_STOP 1\nCREATE INDEX index_avg ON conditions_daily (avg);\nCREATE INDEX index_avg_only ON ONLY conditions_daily (avg);\nCREATE INDEX index_avg_include ON conditions_daily (avg) INCLUDE (location);\nCREATE INDEX index_avg_expr ON conditions_daily ((avg + 1));\nCREATE INDEX index_avg_location_sfo ON conditions_daily (avg) WHERE location = 'SFO';\nCREATE INDEX index_avg_expr_location_sfo ON conditions_daily ((avg + 2)) WHERE location = 'SFO';\nSELECT * FROM test.show_indexespred(:'MAT_TABLE_NAME');\n                                 Index                                 |      Columns      |           Expr            |          Pred          | Unique | Primary | Exclusion | Tablespace \n-----------------------------------------------------------------------+-------------------+---------------------------+------------------------+--------+---------+-----------+------------\n _timescaledb_internal._materialized_hypertable_35_bucket_idx          | {bucket}          |                           |                        | f      | f       | f         | \n _timescaledb_internal._materialized_hypertable_35_location_bucket_idx | {location,bucket} |                           |                        | f      | f       | f         | \n _timescaledb_internal.index_avg                                       | {avg}             |                           |                        | f      | f       | f         | \n _timescaledb_internal.index_avg_expr                                  | {expr}            | avg + 1::double precision |                        | f      | f       | f         | \n _timescaledb_internal.index_avg_expr_location_sfo                     | {expr}            | avg + 2::double precision | location = 'SFO'::text | f      | f       | f         | \n _timescaledb_internal.index_avg_include                               | {avg,location}    |                           |                        | f      | f       | f         | \n _timescaledb_internal.index_avg_location_sfo                          | {avg}             |                           | location = 'SFO'::text | f      | f       | f         | \n _timescaledb_internal.index_avg_only                                  | {avg}             |                           |                        | f      | f       | f         | \n\n-- #3696 assertion failure when referencing columns not present in result\nCREATE TABLE i3696(time timestamptz NOT NULL, search_query text, cnt integer, cnt2 integer);\nSELECT table_name FROM create_hypertable('i3696','time');\n table_name \n------------\n i3696\n\nCREATE MATERIALIZED VIEW i3696_cagg1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\n SELECT  search_query,count(search_query) as count, sum(cnt), time_bucket(INTERVAL '1 minute', time) AS bucket\n FROM i3696 GROUP BY cnt +cnt2 , bucket, search_query;\nNOTICE:  continuous aggregate \"i3696_cagg1\" is already up-to-date\nALTER MATERIALIZED VIEW i3696_cagg1 SET (timescaledb.materialized_only = 'true');\nCREATE MATERIALIZED VIEW i3696_cagg2 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\n SELECT  search_query,count(search_query) as count, sum(cnt), time_bucket(INTERVAL '1 minute', time) AS bucket\n FROM i3696 GROUP BY cnt + cnt2, bucket, search_query\n HAVING cnt + cnt2 + sum(cnt) > 2 or count(cnt2) > 10;\nNOTICE:  continuous aggregate \"i3696_cagg2\" is already up-to-date\nALTER MATERIALIZED VIEW i3696_cagg2 SET (timescaledb.materialized_only = 'true');\n--TEST test with multiple settings on continuous aggregates --\n-- test for materialized_only + compress combinations (real time aggs enabled initially)\nCREATE TABLE test_setting(time timestamptz not null, val numeric);\nSELECT create_hypertable('test_setting', 'time');\n     create_hypertable      \n----------------------------\n (40,public,test_setting,t)\n\nCREATE MATERIALIZED VIEW test_setting_cagg with (timescaledb.continuous, timescaledb.materialized_only=false)\nAS SELECT time_bucket('1h',time), avg(val), count(*) FROM test_setting GROUP BY 1;\nNOTICE:  continuous aggregate \"test_setting_cagg\" is already up-to-date\nINSERT INTO test_setting\nSELECT generate_series( '2020-01-10 8:00'::timestamp, '2020-01-30 10:00+00'::timestamptz, '1 day'::interval), 10.0;\nCALL refresh_continuous_aggregate('test_setting_cagg', NULL, '2020-05-30 10:00+00'::timestamptz);\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    20\n\n--this row is not in the materialized result ---\nINSERT INTO test_setting VALUES( '2020-11-01', 20);\n--try out 2 settings here --\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'true', timescaledb.compress='true');\nNOTICE:  defaulting compress_orderby to time_bucket\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | t                   | t\n\n--real time aggs is off now , should return 20 --\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    20\n\n--now set it back to false --\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'false', timescaledb.compress='true');\nNOTICE:  defaulting compress_orderby to time_bucket\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | t                   | f\n\n--count should return additional data since we have real time aggs on\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    21\n\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'true', timescaledb.compress='false');\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | f                   | t\n\n--real time aggs is off now , should return 20 --\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    20\n\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'false', timescaledb.compress='false');\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | f                   | f\n\n--count should return additional data since we have real time aggs on\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    21\n\nDELETE FROM test_setting WHERE val = 20;\n--TEST test with multiple settings on continuous aggregates with real time aggregates turned off initially --\n-- test for materialized_only + compress combinations (real time aggs enabled initially)\nDROP MATERIALIZED VIEW test_setting_cagg;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_41_50_chunk\nCREATE MATERIALIZED VIEW test_setting_cagg with (timescaledb.continuous, timescaledb.materialized_only = true)\nAS SELECT time_bucket('1h',time), avg(val), count(*) FROM test_setting GROUP BY 1;\nNOTICE:  refreshing continuous aggregate \"test_setting_cagg\"\nCALL refresh_continuous_aggregate('test_setting_cagg', NULL, '2020-05-30 10:00+00'::timestamptz);\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    20\n\n--this row is not in the materialized result ---\nINSERT INTO test_setting VALUES( '2020-11-01', 20);\n--try out 2 settings here --\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'false', timescaledb.compress='true');\nNOTICE:  defaulting compress_orderby to time_bucket\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | t                   | f\n\n--count should return additional data since we have real time aggs on\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    21\n\n--now set it back to false --\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'true', timescaledb.compress='true');\nNOTICE:  defaulting compress_orderby to time_bucket\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | t                   | t\n\n--real time aggs is off now , should return 20 --\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    20\n\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'false', timescaledb.compress='false');\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | f                   | f\n\n--count should return additional data since we have real time aggs on\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    21\n\nALTER MATERIALIZED VIEW test_setting_cagg SET (timescaledb.materialized_only = 'true', timescaledb.compress='false');\nSELECT view_name, compression_enabled, materialized_only\nFROM timescaledb_information.continuous_aggregates\nwhere view_name = 'test_setting_cagg';\n     view_name     | compression_enabled | materialized_only \n-------------------+---------------------+-------------------\n test_setting_cagg | f                   | t\n\n--real time aggs is off now , should return 20 --\nSELECT count(*) from test_setting_cagg ORDER BY 1;\n count \n-------\n    20\n\n-- END TEST with multiple settings\n-- Test View Target Entries that contain both aggrefs and Vars in the same expression\nCREATE TABLE transactions\n(\n    \"time\" timestamp with time zone NOT NULL,\n    dummy1 integer,\n    dummy2 integer,\n    dummy3 integer,\n    dummy4 integer,\n    dummy5 integer,\n    amount integer,\n    fiat_value integer\n);\nSELECT create_hypertable('transactions', 'time');\n     create_hypertable      \n----------------------------\n (45,public,transactions,t)\n\nINSERT INTO transactions VALUES ( '2018-01-01 09:20:00-08', 0, 0, 0, 0, 0, 1, 10);\nINSERT INTO transactions VALUES ( '2018-01-02 09:30:00-08', 0, 0, 0, 0, 0, -1, 10);\nINSERT INTO transactions VALUES ( '2018-01-02 09:20:00-08', 0, 0, 0, 0, 0, -1, 10);\nINSERT INTO transactions VALUES ( '2018-01-02 09:10:00-08', 0, 0, 0, 0, 0, -1, 10);\nINSERT INTO transactions VALUES ( '2018-11-01 09:20:00-08', 0, 0, 0, 0, 0, 1, 10);\nINSERT INTO transactions VALUES ( '2018-11-01 10:40:00-08', 0, 0, 0, 0, 0, 1, 10);\nINSERT INTO transactions VALUES ( '2018-11-01 11:50:00-08', 0, 0, 0, 0, 0, 1, 10);\nINSERT INTO transactions VALUES ( '2018-11-01 12:10:00-08', 0, 0, 0, 0, 0, -1, 10);\nINSERT INTO transactions VALUES ( '2018-11-01 13:10:00-08', 0, 0, 0, 0, 0, -1, 10);\nINSERT INTO transactions VALUES ( '2018-11-02 09:20:00-08', 0, 0, 0, 0, 0, 1, 10);\nINSERT INTO transactions VALUES ( '2018-11-02 10:30:00-08', 0, 0, 0, 0, 0, -1, 10);\nCREATE materialized view cashflows(\n    bucket,\n  \tamount,\n    cashflow,\n    cashflow2\n) WITH (\n    timescaledb.continuous,\n    timescaledb.materialized_only = true\n) AS\nSELECT time_bucket ('1 day', time) AS bucket,\n\tamount,\n  CASE\n      WHEN amount < 0 THEN (0 - sum(fiat_value))\n      ELSE sum(fiat_value)\n  END AS cashflow,\n  amount + sum(fiat_value)\nFROM transactions\nGROUP BY bucket, amount;\nNOTICE:  refreshing continuous aggregate \"cashflows\"\nSELECT h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name AS \"PART_VIEW_NAME\",\n       direct_view_name AS \"DIRECT_VIEW_NAME\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON (h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'cashflows'\n\\gset\n-- Show both the columns and the view definitions to see that\n-- references are correct in the view as well.\n\\d+ \"_timescaledb_internal\".:\"DIRECT_VIEW_NAME\"\n                         View \"_timescaledb_internal._direct_view_46\"\n  Column   |           Type           | Collation | Nullable | Default | Storage | Description \n-----------+--------------------------+-----------+----------+---------+---------+-------------\n bucket    | timestamp with time zone |           |          |         | plain   | \n amount    | integer                  |           |          |         | plain   | \n cashflow  | bigint                   |           |          |         | plain   | \n cashflow2 | bigint                   |           |          |         | plain   | \nView definition:\n SELECT time_bucket('@ 1 day'::interval, \"time\") AS bucket,\n    amount,\n        CASE\n            WHEN amount < 0 THEN 0 - sum(fiat_value)\n            ELSE sum(fiat_value)\n        END AS cashflow,\n    amount + sum(fiat_value) AS cashflow2\n   FROM transactions\n  GROUP BY (time_bucket('@ 1 day'::interval, \"time\")), amount;\n\n\\d+ \"_timescaledb_internal\".:\"PART_VIEW_NAME\"\n                         View \"_timescaledb_internal._partial_view_46\"\n  Column   |           Type           | Collation | Nullable | Default | Storage | Description \n-----------+--------------------------+-----------+----------+---------+---------+-------------\n bucket    | timestamp with time zone |           |          |         | plain   | \n amount    | integer                  |           |          |         | plain   | \n cashflow  | bigint                   |           |          |         | plain   | \n cashflow2 | bigint                   |           |          |         | plain   | \nView definition:\n SELECT time_bucket('@ 1 day'::interval, \"time\") AS bucket,\n    amount,\n        CASE\n            WHEN amount < 0 THEN 0 - sum(fiat_value)\n            ELSE sum(fiat_value)\n        END AS cashflow,\n    amount + sum(fiat_value) AS cashflow2\n   FROM transactions\n  GROUP BY (time_bucket('@ 1 day'::interval, \"time\")), amount;\n\n\\d+ \"_timescaledb_internal\".:\"MAT_TABLE_NAME\"\n                          Table \"_timescaledb_internal._materialized_hypertable_46\"\n  Column   |           Type           | Collation | Nullable | Default | Storage | Stats target | Description \n-----------+--------------------------+-----------+----------+---------+---------+--------------+-------------\n bucket    | timestamp with time zone |           | not null |         | plain   |              | \n amount    | integer                  |           |          |         | plain   |              | \n cashflow  | bigint                   |           |          |         | plain   |              | \n cashflow2 | bigint                   |           |          |         | plain   |              | \nIndexes:\n    \"_materialized_hypertable_46_amount_bucket_idx\" btree (amount, bucket DESC)\n    \"_materialized_hypertable_46_bucket_idx\" btree (bucket DESC)\nChild tables: _timescaledb_internal._hyper_46_55_chunk,\n              _timescaledb_internal._hyper_46_56_chunk\n\n\\d+ 'cashflows'\n                                    View \"public.cashflows\"\n  Column   |           Type           | Collation | Nullable | Default | Storage | Description \n-----------+--------------------------+-----------+----------+---------+---------+-------------\n bucket    | timestamp with time zone |           |          |         | plain   | \n amount    | integer                  |           |          |         | plain   | \n cashflow  | bigint                   |           |          |         | plain   | \n cashflow2 | bigint                   |           |          |         | plain   | \nView definition:\n SELECT bucket,\n    amount,\n    cashflow,\n    cashflow2\n   FROM _timescaledb_internal._materialized_hypertable_46;\n\nSELECT * FROM cashflows ORDER BY cashflows;\n            bucket            | amount | cashflow | cashflow2 \n------------------------------+--------+----------+-----------\n Sun Dec 31 16:00:00 2017 UTC |      1 |       10 |        11\n Mon Jan 01 16:00:00 2018 UTC |     -1 |      -30 |        29\n Wed Oct 31 16:00:00 2018 UTC |     -1 |      -20 |        19\n Wed Oct 31 16:00:00 2018 UTC |      1 |       30 |        31\n Thu Nov 01 16:00:00 2018 UTC |     -1 |      -10 |         9\n Thu Nov 01 16:00:00 2018 UTC |      1 |       10 |        11\n\n-- test cagg creation with named arguments in time_bucket\n-- note that positional arguments cannot follow named arguments\n-- 1. test named origin\n-- 2. test named timezone\n-- 3. test named ts\n-- 4. test named bucket width\n-- named origin\nCREATE MATERIALIZED VIEW cagg_named_origin WITH\n(timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket('1h', time, 'UTC', origin => '2001-01-03 01:23:45') AS bucket,\navg(amount) as avg_amount\nFROM transactions GROUP BY 1 WITH NO DATA;\n-- named timezone\nCREATE MATERIALIZED VIEW cagg_named_tz_origin WITH\n(timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket('1h', time, timezone => 'UTC', origin => '2001-01-03 01:23:45') AS bucket,\navg(amount) as avg_amount\nFROM transactions GROUP BY 1 WITH NO DATA;\n-- named ts\nCREATE MATERIALIZED VIEW cagg_named_ts_tz_origin WITH\n(timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket('1h', ts => time, timezone => 'UTC', origin => '2001-01-03 01:23:45') AS bucket,\navg(amount) as avg_amount\nFROM transactions GROUP BY 1 WITH NO DATA;\n-- named bucket width\nCREATE MATERIALIZED VIEW cagg_named_all WITH\n(timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(bucket_width => '1h', ts => time, timezone => 'UTC', origin => '2001-01-03 01:23:45') AS bucket,\navg(amount) as avg_amount\nFROM transactions GROUP BY 1 WITH NO DATA;\n-- Refreshing from the beginning (NULL) of a CAGG with variable time bucket and\n-- using an INTERVAL for the end timestamp (issue #5534)\nCREATE MATERIALIZED VIEW transactions_montly\nWITH (timescaledb.continuous, timescaledb.materialized_only = true) AS\nSELECT time_bucket(INTERVAL '1 month', time) AS bucket,\n       SUM(fiat_value),\n       MAX(fiat_value),\n       MIN(fiat_value)\n  FROM transactions\nGROUP BY 1\nWITH NO DATA;\n-- No rows\nSELECT * FROM transactions_montly ORDER BY bucket;\n bucket | sum | max | min \n--------+-----+-----+-----\n\n-- Refresh from beginning of the CAGG for 1 month\nCALL refresh_continuous_aggregate('transactions_montly', NULL, INTERVAL '1 month');\nSELECT * FROM transactions_montly ORDER BY bucket;\n            bucket            | sum | max | min \n------------------------------+-----+-----+-----\n Sun Dec 31 16:00:00 2017 UTC |  40 |  10 |  10\n Wed Oct 31 16:00:00 2018 UTC |  70 |  10 |  10\n\nTRUNCATE transactions_montly;\n-- Partial refresh the CAGG from beginning to an specific timestamp\nCALL refresh_continuous_aggregate('transactions_montly', NULL, '2018-11-01 11:50:00-08'::timestamptz);\nSELECT * FROM transactions_montly ORDER BY bucket;\n            bucket            | sum | max | min \n------------------------------+-----+-----+-----\n Sun Dec 31 16:00:00 2017 UTC |  40 |  10 |  10\n\n-- Full refresh the CAGG\nCALL refresh_continuous_aggregate('transactions_montly', NULL, NULL);\nSELECT * FROM transactions_montly ORDER BY bucket;\n            bucket            | sum | max | min \n------------------------------+-----+-----+-----\n Sun Dec 31 16:00:00 2017 UTC |  40 |  10 |  10\n Wed Oct 31 16:00:00 2018 UTC |  70 |  10 |  10\n\n-- Check set_chunk_time_interval on continuous aggregate\nCREATE MATERIALIZED VIEW cagg_set_chunk_time_interval\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket(INTERVAL '1 month', time) AS bucket,\n       SUM(fiat_value),\n       MAX(fiat_value),\n       MIN(fiat_value)\nFROM transactions\nGROUP BY 1\nWITH NO DATA;\nSELECT set_chunk_time_interval('cagg_set_chunk_time_interval', chunk_time_interval => interval '1 month');\n set_chunk_time_interval \n-------------------------\n \n\nCALL refresh_continuous_aggregate('cagg_set_chunk_time_interval', NULL, NULL);\nSELECT _timescaledb_functions.to_interval(d.interval_length) = interval '1 month'\nFROM _timescaledb_catalog.dimension d\n         RIGHT JOIN _timescaledb_catalog.continuous_agg ca ON ca.user_view_name = 'cagg_set_chunk_time_interval'\nWHERE d.hypertable_id = ca.mat_hypertable_id;\n ?column? \n----------\n t\n\n-- Since #6077 CAggs are materialized only by default\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 5 other objects\nNOTICE:  drop cascades to 2 other objects\nCREATE TABLE conditions (\n       time TIMESTAMPTZ NOT NULL,\n       location TEXT NOT NULL,\n       temperature DOUBLE PRECISION NULL\n);\nSELECT create_hypertable('conditions', 'time');\n    create_hypertable     \n--------------------------\n (53,public,conditions,t)\n\nINSERT INTO conditions VALUES ( '2018-01-01 09:20:00-08', 'SFO', 55);\nINSERT INTO conditions VALUES ( '2018-01-02 09:30:00-08', 'POR', 100);\nINSERT INTO conditions VALUES ( '2018-01-02 09:20:00-08', 'SFO', 65);\nINSERT INTO conditions VALUES ( '2018-01-02 09:10:00-08', 'NYC', 65);\nINSERT INTO conditions VALUES ( '2018-11-01 09:20:00-08', 'NYC', 45);\nINSERT INTO conditions VALUES ( '2018-11-01 10:40:00-08', 'NYC', 55);\nINSERT INTO conditions VALUES ( '2018-11-01 11:50:00-08', 'NYC', 65);\nINSERT INTO conditions VALUES ( '2018-11-01 12:10:00-08', 'NYC', 75);\nINSERT INTO conditions VALUES ( '2018-11-01 13:10:00-08', 'NYC', 85);\nINSERT INTO conditions VALUES ( '2018-11-02 09:20:00-08', 'NYC', 10);\nINSERT INTO conditions VALUES ( '2018-11-02 10:30:00-08', 'NYC', 20);\nCREATE MATERIALIZED VIEW conditions_daily\nWITH (timescaledb.continuous) AS\nSELECT location,\n       time_bucket(INTERVAL '1 day', time) AS bucket,\n       AVG(temperature)\n  FROM conditions\nGROUP BY location, bucket\nWITH NO DATA;\n\\d+ conditions_daily\n                                View \"public.conditions_daily\"\n  Column  |           Type           | Collation | Nullable | Default | Storage  | Description \n----------+--------------------------+-----------+----------+---------+----------+-------------\n location | text                     |           |          |         | extended | \n bucket   | timestamp with time zone |           |          |         | plain    | \n avg      | double precision         |           |          |         | plain    | \nView definition:\n SELECT location,\n    bucket,\n    avg\n   FROM _timescaledb_internal._materialized_hypertable_54;\n\n-- Should return NO ROWS\nSELECT * FROM conditions_daily ORDER BY bucket, location;\n location | bucket | avg \n----------+--------+-----\n\nALTER MATERIALIZED VIEW conditions_daily SET (timescaledb.materialized_only=false);\n\\d+ conditions_daily\n                                View \"public.conditions_daily\"\n  Column  |           Type           | Collation | Nullable | Default | Storage  | Description \n----------+--------------------------+-----------+----------+---------+----------+-------------\n location | text                     |           |          |         | extended | \n bucket   | timestamp with time zone |           |          |         | plain    | \n avg      | double precision         |           |          |         | plain    | \nView definition:\n SELECT _materialized_hypertable_54.location,\n    _materialized_hypertable_54.bucket,\n    _materialized_hypertable_54.avg\n   FROM _timescaledb_internal._materialized_hypertable_54\n  WHERE _materialized_hypertable_54.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(54)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT conditions.location,\n    time_bucket('@ 1 day'::interval, conditions.\"time\") AS bucket,\n    avg(conditions.temperature) AS avg\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(54)), '-infinity'::timestamp with time zone)\n  GROUP BY conditions.location, (time_bucket('@ 1 day'::interval, conditions.\"time\"));\n\n-- Should return ROWS because now it is realtime\nSELECT * FROM conditions_daily ORDER BY bucket, location;\n location |            bucket            | avg \n----------+------------------------------+-----\n SFO      | Sun Dec 31 16:00:00 2017 UTC |  55\n NYC      | Mon Jan 01 16:00:00 2018 UTC |  65\n POR      | Mon Jan 01 16:00:00 2018 UTC | 100\n SFO      | Mon Jan 01 16:00:00 2018 UTC |  65\n NYC      | Wed Oct 31 16:00:00 2018 UTC |  65\n NYC      | Thu Nov 01 16:00:00 2018 UTC |  15\n\n-- Should return ROWS because we refreshed it\nALTER MATERIALIZED VIEW conditions_daily SET (timescaledb.materialized_only=true);\n\\d+ conditions_daily\n                                View \"public.conditions_daily\"\n  Column  |           Type           | Collation | Nullable | Default | Storage  | Description \n----------+--------------------------+-----------+----------+---------+----------+-------------\n location | text                     |           |          |         | extended | \n bucket   | timestamp with time zone |           |          |         | plain    | \n avg      | double precision         |           |          |         | plain    | \nView definition:\n SELECT location,\n    bucket,\n    avg\n   FROM _timescaledb_internal._materialized_hypertable_54;\n\nCALL refresh_continuous_aggregate('conditions_daily', NULL, NULL);\nSELECT * FROM conditions_daily ORDER BY bucket, location;\n location |            bucket            | avg \n----------+------------------------------+-----\n SFO      | Sun Dec 31 16:00:00 2017 UTC |  55\n NYC      | Mon Jan 01 16:00:00 2018 UTC |  65\n POR      | Mon Jan 01 16:00:00 2018 UTC | 100\n SFO      | Mon Jan 01 16:00:00 2018 UTC |  65\n NYC      | Wed Oct 31 16:00:00 2018 UTC |  65\n NYC      | Thu Nov 01 16:00:00 2018 UTC |  15\n\n-- Test TRUNCATE over a Realtime CAgg\nDROP MATERIALIZED VIEW conditions_daily;\nNOTICE:  drop cascades to 2 other objects\nCREATE MATERIALIZED VIEW conditions_daily\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT location,\n       time_bucket(INTERVAL '1 day', time) AS bucket,\n       AVG(temperature)\n  FROM conditions\nGROUP BY location, bucket\nWITH NO DATA;\nSELECT mat_hypertable_id FROM _timescaledb_catalog.continuous_agg WHERE user_view_name = 'conditions_daily' \\gset\n-- Check the current watermark for an empty CAgg\nSELECT _timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(:mat_hypertable_id)) AS watermak_empty_cagg;\n       watermak_empty_cagg       \n---------------------------------\n Sun Nov 23 16:00:00 4714 UTC BC\n\n-- Refresh the CAGG\nCALL refresh_continuous_aggregate('conditions_daily', NULL, NULL);\n-- Check the watermark after the refresh and before truncate the CAgg\nSELECT _timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(:mat_hypertable_id)) AS watermak_before;\n       watermak_before        \n------------------------------\n Fri Nov 02 16:00:00 2018 UTC\n\n-- Exists chunks before truncate the cagg (> 0)\nSELECT count(*) FROM show_chunks('conditions_daily');\n count \n-------\n     2\n\n-- Truncate the given CAgg, it should reset the watermark to the empty state\nTRUNCATE conditions_daily;\n-- No chunks remains after truncate the cagg (= 0)\nSELECT count(*) FROM show_chunks('conditions_daily');\n count \n-------\n     0\n\n-- Watermark should be reseted\nSELECT _timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(:mat_hypertable_id)) AS watermak_after;\n         watermak_after          \n---------------------------------\n Sun Nov 23 16:00:00 4714 UTC BC\n\n-- Should return ROWS because the watermark was reseted by the TRUNCATE\nSELECT * FROM conditions_daily ORDER BY bucket, location;\n location |            bucket            | avg \n----------+------------------------------+-----\n SFO      | Sun Dec 31 16:00:00 2017 UTC |  55\n NYC      | Mon Jan 01 16:00:00 2018 UTC |  65\n POR      | Mon Jan 01 16:00:00 2018 UTC | 100\n SFO      | Mon Jan 01 16:00:00 2018 UTC |  65\n NYC      | Wed Oct 31 16:00:00 2018 UTC |  65\n NYC      | Thu Nov 01 16:00:00 2018 UTC |  15\n\n-- check compression settings are cleaned up when deleting a cagg with compression\nCREATE TABLE cagg_cleanup(time timestamptz not null);\nSELECT table_name FROM create_hypertable('cagg_cleanup','time');\n  table_name  \n--------------\n cagg_cleanup\n\nINSERT INTO cagg_cleanup SELECT '2020-01-01';\nCREATE MATERIALIZED VIEW cagg1 WITH (timescaledb.continuous) AS SELECT time_bucket('1h',time) FROM cagg_cleanup GROUP BY 1;\nNOTICE:  refreshing continuous aggregate \"cagg1\"\nALTER MATERIALIZED VIEW cagg1 SET (timescaledb.compress);\nNOTICE:  defaulting compress_orderby to time_bucket\nSELECT count(compress_chunk(ch)) FROM show_chunks('cagg1') ch;\n count \n-------\n     1\n\nDROP MATERIALIZED VIEW cagg1;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_57_70_chunk\nSELECT * FROM _timescaledb_catalog.compression_settings;\n relid | compress_relid | segmentby | orderby | orderby_desc | orderby_nullsfirst | index \n-------+----------------+-----------+---------+--------------+--------------------+-------\n\n-- test WITH namespace alias\nCREATE TABLE with_alias(time timestamptz not null);\nCREATE MATERIALIZED VIEW cagg_alias\nWITH (tsdb.continuous, tsdb.materialized_only=false) AS\nSELECT time_bucket(INTERVAL '1 day', time) FROM conditions GROUP BY 1 WITH NO DATA;\nALTER MATERIALIZED VIEW cagg_alias SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW cagg_alias SET (tsdb.materialized_only=false);\nDROP MATERIALIZED VIEW cagg_alias;\n-- test SET chunk_time_interval\nCREATE MATERIALIZED VIEW cagg_set\nWITH (tsdb.continuous, tsdb.chunk_interval='1day') AS\nSELECT time_bucket(INTERVAL '1 day', time) AS cagg_interval_setter FROM conditions GROUP BY 1 WITH NO DATA;\nSELECT column_name, time_interval FROM timescaledb_information.dimensions WHERE column_name='cagg_interval_setter';\n     column_name      | time_interval \n----------------------+---------------\n cagg_interval_setter | @ 1 day\n\nALTER MATERIALIZED VIEW cagg_set SET (tsdb.chunk_interval='23 day');\nSELECT column_name, time_interval FROM timescaledb_information.dimensions WHERE column_name='cagg_interval_setter';\n     column_name      | time_interval \n----------------------+---------------\n cagg_interval_setter | @ 23 days\n\nALTER MATERIALIZED VIEW cagg_set SET (tsdb.chunk_interval='6 month');\nSELECT column_name, time_interval FROM timescaledb_information.dimensions WHERE column_name='cagg_interval_setter';\n     column_name      | time_interval \n----------------------+---------------\n cagg_interval_setter | @ 180 days\n\nALTER MATERIALIZED VIEW cagg_set SET (tsdb.chunk_interval='1 year');\nSELECT column_name, time_interval FROM timescaledb_information.dimensions WHERE column_name='cagg_interval_setter';\n     column_name      | time_interval \n----------------------+---------------\n cagg_interval_setter | @ 360 days\n\n-- test cagg with stable functions\nCREATE MATERIALIZED VIEW cagg_stable WITH (tsdb.continuous) AS\nSELECT sum(temperature), max(time + INTERVAL '1h')\nFROM conditions\nGROUP BY time_bucket('1week', time), location;\nWARNING:  using non-immutable functions in continuous aggregate view may lead to inconsistent results on rematerialization\nNOTICE:  refreshing continuous aggregate \"cagg_stable\"\nSELECT * FROM cagg_stable t ORDER BY t;\n sum |             max              \n-----+------------------------------\n  65 | Tue Jan 02 10:10:00 2018 UTC\n 100 | Tue Jan 02 10:30:00 2018 UTC\n 120 | Tue Jan 02 10:20:00 2018 UTC\n 355 | Fri Nov 02 11:30:00 2018 UTC\n\n--aggregate without combine function but stable function\nCREATE MATERIALIZED VIEW cagg_json_agg WITH (tsdb.continuous, tsdb.materialized_only=false)\nAS SELECT json_agg(location) from conditions group by time_bucket('1week', time), location WITH NO DATA;\nWARNING:  using non-immutable functions in continuous aggregate view may lead to inconsistent results on rematerialization\nCREATE FUNCTION test_stablefunc(int) RETURNS int LANGUAGE 'sql' STABLE AS 'SELECT $1 + 10';\nCREATE MATERIALIZED VIEW cagg_stable2 WITH (tsdb.continuous) AS\nSELECT sum(test_stablefunc(temperature::int)), min(location)\nFROM conditions\nGROUP BY time_bucket('1week', time) WITH NO DATA;\nWARNING:  using non-immutable functions in continuous aggregate view may lead to inconsistent results on rematerialization\nCREATE MATERIALIZED VIEW cagg_stable3 WITH (tsdb.continuous) AS\nSELECT sum(temperature), min(location)\nFROM conditions\nGROUP BY time_bucket('1week', time), test_stablefunc(temperature::int) WITH NO DATA;\nWARNING:  using non-immutable functions in continuous aggregate view may lead to inconsistent results on rematerialization\n-- test window functions in caggs\n-- first do sanity check that we error without the GUC\n\\set ON_ERROR_STOP 0\nCREATE MATERIALIZED VIEW cagg_window WITH (tsdb.continuous)\nAS SELECT time_bucket('1week', time), rank() OVER (PARTITION BY time_bucket('1 week',time)) FROM conditions GROUP BY 1;\nERROR:  invalid continuous aggregate query\n\\set ON_ERROR_STOP 1\nSET timescaledb.enable_cagg_window_functions TO on;\nCREATE MATERIALIZED VIEW cagg_window_1 WITH (tsdb.continuous)\nAS SELECT time_bucket('1week', time), rank() OVER (PARTITION BY time_bucket('1 week',time)) FROM conditions GROUP BY 1;\nWARNING:  window function support is experimental and may result in unexpected results depending on the functions used.\nNOTICE:  refreshing continuous aggregate \"cagg_window_1\"\nCREATE MATERIALIZED VIEW cagg_window_2 WITH (tsdb.continuous)\nAS SELECT time_bucket('1week', time), rank() OVER (PARTITION BY time_bucket('1 week',time), location) FROM conditions GROUP BY 1, location;\nWARNING:  window function support is experimental and may result in unexpected results depending on the functions used.\nNOTICE:  refreshing continuous aggregate \"cagg_window_2\"\nCREATE MATERIALIZED VIEW cagg_window_3 WITH (tsdb.continuous)\nAS SELECT time_bucket('1week', time), rank() OVER (PARTITION BY time_bucket('1 week',time)) FROM conditions GROUP BY 1, location;\nWARNING:  window function support is experimental and may result in unexpected results depending on the functions used.\nNOTICE:  refreshing continuous aggregate \"cagg_window_3\"\nCREATE MATERIALIZED VIEW cagg_window_4 WITH (tsdb.continuous)\nAS SELECT time_bucket('1week', time), rank() OVER w FROM conditions GROUP BY 1, location WINDOW w AS (PARTITION BY time_bucket('1 week',time));\nWARNING:  window function support is experimental and may result in unexpected results depending on the functions used.\nNOTICE:  refreshing continuous aggregate \"cagg_window_4\"\n-- test setting chunk_interval on a cagg\nCREATE MATERIALIZED VIEW cagg_chunk_interval WITH (tsdb.continuous, tsdb.chunk_interval='1000 day') AS SELECT time_bucket('1 week', time) FROM conditions GROUP BY 1 WITH NO DATA;\nSELECT time_interval from timescaledb_information.continuous_aggregates cagg INNER JOIN timescaledb_information.dimensions dim ON cagg.materialization_hypertable_name = dim.hypertable_name  WHERE view_name='cagg_chunk_interval';\n time_interval \n---------------\n @ 1000 days\n\nALTER MATERIALIZED VIEW cagg_chunk_interval SET (tsdb.chunk_interval='110 day');\nSELECT time_interval from timescaledb_information.continuous_aggregates cagg INNER JOIN timescaledb_information.dimensions dim ON cagg.materialization_hypertable_name = dim.hypertable_name  WHERE view_name='cagg_chunk_interval';\n time_interval \n---------------\n @ 110 days\n\n-- test columnstore options\nCREATE MATERIALIZED VIEW columnstore_options WITH (tsdb.continuous, tsdb.chunk_interval='1 day') AS SELECT time_bucket('1 day', time) FROM conditions GROUP BY 1 WITH NO DATA;\nSELECT column_name, compress_interval_length from _timescaledb_catalog.dimension where column_name='time_bucket' ORDER BY id DESC LIMIT 1;\n column_name | compress_interval_length \n-------------+--------------------------\n time_bucket |                         \n\nALTER MATERIALIZED VIEW columnstore_options SET (tsdb.columnstore, tsdb.chunk_interval='1 day', tsdb.orderby='time_bucket DESC', tsdb.compress_chunk_interval='2 day');\nSELECT column_name, compress_interval_length from _timescaledb_catalog.dimension where column_name='time_bucket' ORDER BY id DESC LIMIT 1;\n column_name | compress_interval_length \n-------------+--------------------------\n time_bucket |             172800000000\n\nALTER MATERIALIZED VIEW columnstore_options SET (tsdb.compress_chunk_interval='3 day');\nSELECT column_name, compress_interval_length from _timescaledb_catalog.dimension where column_name='time_bucket' ORDER BY id DESC LIMIT 1;\n column_name | compress_interval_length \n-------------+--------------------------\n time_bucket |             259200000000\n\nALTER MATERIALIZED VIEW columnstore_options SET (tsdb.compress_chunk_time_interval='4 day');\nSELECT column_name, compress_interval_length from _timescaledb_catalog.dimension where column_name='time_bucket' ORDER BY id DESC LIMIT 1;\n column_name | compress_interval_length \n-------------+--------------------------\n time_bucket |             345600000000\n\n-- test set returning functions in caggs\nCREATE TABLE kpis_raw (time TIMESTAMP NOT NULL, value INTEGER, groups TEXT[]) WITH (tsdb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nINSERT INTO kpis_raw (time, value, groups) VALUES\n  ('2025-01-01', 10, '{group1,group2,group3}'),\n  ('2025-01-02', 20, '{group1,group4}'),\n  ('2025-02-01', 10, '{group1,group3}'),\n  ('2025-02-01', 20, '{group1,group4}');\nCREATE MATERIALIZED VIEW kpis_cagg WITH (tsdb.continuous) AS\nSELECT time_bucket('7 day', time) AS bucket, count(*) AS number_of_records, avg(value) AS average, unnest(groups) AS kpi_group\nFROM kpis_raw GROUP BY bucket, kpi_group;\nNOTICE:  refreshing continuous aggregate \"kpis_cagg\"\nSELECT * FROM kpis_cagg ORDER BY bucket, kpi_group;\n          bucket          | number_of_records |       average       | kpi_group \n--------------------------+-------------------+---------------------+-----------\n Mon Dec 30 00:00:00 2024 |                 2 | 15.0000000000000000 | group1\n Mon Dec 30 00:00:00 2024 |                 1 | 10.0000000000000000 | group2\n Mon Dec 30 00:00:00 2024 |                 1 | 10.0000000000000000 | group3\n Mon Dec 30 00:00:00 2024 |                 1 | 20.0000000000000000 | group4\n Mon Jan 27 00:00:00 2025 |                 2 | 15.0000000000000000 | group1\n Mon Jan 27 00:00:00 2025 |                 1 | 10.0000000000000000 | group3\n Mon Jan 27 00:00:00 2025 |                 1 | 20.0000000000000000 | group4\n\n--TEST for caggs with non timescaledb namespace options\n--non timescaledb namespace options can be set via ALTER\n\\set ON_ERROR_STOP 0\n-- will error out\nCREATE MATERIALIZED VIEW ht_try_weekly\nWITH (timescaledb.continuous, tigerlake.newoption = true) AS\nSELECT time_bucket(interval '1 week', time) AS ts_bucket, avg(value)\nFROM kpis_raw\nGROUP BY 1;\nERROR:  non \"timescaledb\" namespace options can be set only via ALTER\n--caught by Postgres now\nALTER MATERIALIZED VIEW kpis_cagg SET (tigerlake.newoption = true);\nERROR:  \"kpis_cagg\" is not a materialized view\n\\set ON_ERROR_STOP 1\n-- TEST that cached alter stmt still works (see PR 8739)\n-- test DDL inside function\nCREATE TABLE hypertab_ddl( ts timestamp, a integer)\nWITH (timescaledb.hypertable);\nNOTICE:  using column \"ts\" as partitioning column\nCREATE OR REPLACE FUNCTION ddl_function() RETURNS VOID LANGUAGE PLPGSQL AS $$\nBEGIN\n  DROP MATERIALIZED VIEW IF EXISTS cagg_hypertab_ddl;\n  CREATE MATERIALIZED VIEW cagg_hypertab_ddl WITH (timescaledb.continuous)\n  AS SELECT time_bucket( '1 day'::interval, ts), COUNT(*)\n  FROM hypertab_ddl GROUP BY 1 WITH NO DATA;\nEND\n$$;\nSELECT ddl_function();\nNOTICE:  materialized view \"cagg_hypertab_ddl\" does not exist, skipping\n ddl_function \n--------------\n \n\nSELECT view_name from timescaledb_information.continuous_aggregates WHERE hypertable_name='hypertab_ddl';\n     view_name     \n-------------------\n cagg_hypertab_ddl\n\nSELECT ddl_function();\n ddl_function \n--------------\n \n\nSELECT view_name from timescaledb_information.continuous_aggregates WHERE hypertable_name='hypertab_ddl';\n     view_name     \n-------------------\n cagg_hypertab_ddl\n\n-- TEST continuous aggregate with functionally dependent columns\n-- name column depends on id which is in GROUP BY so name doesnt have to be\nCREATE table sensor(id int PRIMARY KEY, name text);\nCREATE TABLE sensordata(time timestamptz,id int, value float) WITH\t(timescaledb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nCREATE MATERIALIZED VIEW cagg_sensordata WITH (tsdb.continuous) AS\nSELECT s.id, s.name,time_bucket('15 minutes', sd.time) as bucket, avg(sd.value) FROM sensordata sd JOIN sensor s USING(id) GROUP BY s.id, bucket WITH NO DATA;\nSELECT * FROM cagg_sensordata;\n id | name | bucket | avg \n----+------+--------+-----\n\n"
  },
  {
    "path": "tsl/test/expected/cagg_deprecated_bucket_ng.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\nCREATE TABLE conditions(\n  time timestamptz NOT NULL,\n  city text NOT NULL,\n  temperature INT NOT NULL);\nSELECT create_hypertable(\n  'conditions', 'time',\n  chunk_time_interval => INTERVAL '1 day'\n);\n    create_hypertable    \n-------------------------\n (1,public,conditions,t)\n\n-- Ensure no CAgg using time_bucket_ng can be created\n\\set ON_ERROR_STOP 0\n-- Regular CAgg\nCREATE MATERIALIZED VIEW conditions_summary_weekly\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT city,\n       timescaledb_experimental.time_bucket_ng('7 days', time, 'UTC') AS bucket,\n       MIN(temperature),\n       MAX(temperature)\nFROM conditions\nGROUP BY city, bucket WITH NO DATA;\nERROR:  experimental bucket functions are not supported inside a CAgg definition\n-- CAgg with origin\nCREATE MATERIALIZED VIEW conditions_summary_weekly\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT city,\n       timescaledb_experimental.time_bucket_ng('7 days', time, '2024-01-16 18:00:00+00') AS bucket,\n       MIN(temperature),\n       MAX(temperature)\nFROM conditions\nGROUP BY city, bucket WITH NO DATA;\nERROR:  experimental bucket functions are not supported inside a CAgg definition\n-- CAgg with origin and timezone\nCREATE MATERIALIZED VIEW conditions_summary_weekly\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT city,\n       timescaledb_experimental.time_bucket_ng('7 days', time, '2024-01-16 18:00:00+00', 'UTC') AS bucket,\n       MIN(temperature),\n       MAX(temperature)\nFROM conditions\nGROUP BY city, bucket WITH NO DATA;\nERROR:  experimental bucket functions are not supported inside a CAgg definition\n\\set ON_ERROR_STOP 1\n"
  },
  {
    "path": "tsl/test/expected/cagg_direct_compress.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\nSET timezone TO PST8PDT;\nCREATE TABLE conditions (\n  time         TIMESTAMP WITH TIME ZONE NOT NULL,\n  device_id    TEXT,\n  location_id  INTEGER,\n  temperature  NUMERIC,\n  humidity     NUMERIC\n) WITH (\n  timescaledb.hypertable,\n  timescaledb.chunk_interval = '1 month'\n);\nNOTICE:  using column \"time\" as partitioning column\nINSERT INTO conditions\nSELECT t, d::text, d, 1, 1 FROM generate_series('2025-12-15 00:00:00+00'::timestamptz - interval '1 year', '2025-12-15 00:00:00+00'::timestamptz, interval '1 hour') AS t, generate_series(1, 10) AS d;\nCREATE MATERIALIZED VIEW conditions_hourly\nWITH (timescaledb.continuous) AS\nSELECT\n  time_bucket(INTERVAL '1 hour', time) AS bucket,\n  device_id,\n  MAX(temperature),\n  MIN(temperature),\n  COUNT(*)\nFROM conditions\nGROUP BY 1, 2\nWITH NO DATA;\nCALL refresh_continuous_aggregate('conditions_hourly', NULL, NULL);\nSELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('conditions_hourly') chunk;\n chunk_status_text \n-------------------\n {}\n\n-- Enable columnstore\nTRUNCATE conditions_hourly;\nALTER MATERIALIZED VIEW conditions_hourly SET (timescaledb.compress);\nNOTICE:  defaulting compress_orderby to bucket,device_id\n-- Enable direct compress on cagg refresh\nSET timescaledb.enable_direct_compress_on_cagg_refresh TO on;\nCALL refresh_continuous_aggregate('conditions_hourly', NULL, NULL);\nSELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('conditions_hourly') chunk;\n chunk_status_text \n-------------------\n {COMPRESSED}\n\n-- Backfill data and refresh again WITHOUT direct compress\nINSERT INTO conditions\nSELECT t, d::text, 1, 1 FROM generate_series('2025-12-15 00:00:00+00'::timestamptz - interval '1 year', '2025-12-15 00:00:00+00'::timestamptz, interval '1 hour') AS t, generate_series(1, 10) AS d;\nSET timescaledb.enable_direct_compress_on_cagg_refresh TO off;\nCALL refresh_continuous_aggregate('conditions_hourly', NULL, NULL);\nSELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('conditions_hourly') chunk;\n  chunk_status_text   \n----------------------\n {COMPRESSED,PARTIAL}\n\n-- Recompress all uncompressed chunks\nSELECT compress_chunk(show_chunks('conditions_hourly'), recompress => true) IS NOT NULL AS compress;\n compress \n----------\n t\n t\n t\n\nSELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('conditions_hourly') chunk;\n chunk_status_text \n-------------------\n {COMPRESSED}\n\n-- Backfill data and refresh again WITH direct compress\nINSERT INTO conditions\nSELECT t, d::text, 1, 1 FROM generate_series('2025-12-15 00:00:00+00'::timestamptz - interval '1 year', '2025-12-15 00:00:00+00'::timestamptz, interval '1 hour') AS t, generate_series(1, 10) AS d;\nSET timescaledb.enable_direct_compress_on_cagg_refresh TO on;\nCALL refresh_continuous_aggregate('conditions_hourly', NULL, NULL);\nSELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('conditions_hourly') chunk;\n chunk_status_text \n-------------------\n {COMPRESSED}\n\n-- Cleanup\nTRUNCATE conditions;\nTRUNCATE conditions_hourly;\n-- Hierarchical CAgg tests\nCREATE MATERIALIZED VIEW conditions_daily\nWITH (timescaledb.continuous) AS\nSELECT\n  time_bucket(INTERVAL '1 day', bucket) AS bucket,\n  device_id,\n  MAX(max),\n  MIN(min),\n  SUM(count) AS count\nFROM conditions_hourly\nGROUP BY 1, 2\nWITH NO DATA;\nALTER MATERIALIZED VIEW conditions_daily SET (timescaledb.compress);\nNOTICE:  defaulting compress_orderby to bucket,device_id\nINSERT INTO conditions\nSELECT t, d::text, 1, 1 FROM generate_series('2025-12-15 00:00:00+00'::timestamptz - interval '1 year', '2025-12-15 00:00:00+00'::timestamptz, interval '1 hour') AS t, generate_series(1, 10) AS d;\nSET timescaledb.enable_direct_compress_on_cagg_refresh TO on;\n-- Refresh the base CAgg\nCALL refresh_continuous_aggregate('conditions_hourly', NULL, NULL);\nSELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('conditions_hourly') chunk;\n chunk_status_text \n-------------------\n {COMPRESSED}\n\n-- Refresh the hierarchical CAgg\nCALL refresh_continuous_aggregate('conditions_daily', NULL, NULL);\nSELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('conditions_daily') chunk;\n chunk_status_text \n-------------------\n {COMPRESSED}\n\n-- Produce some invalidations for the base CAgg\nINSERT INTO conditions\nSELECT t, d::text, 1, 1 FROM generate_series('2025-12-15 00:00:00+00'::timestamptz - interval '1 year', '2025-12-15 00:00:00+00'::timestamptz, interval '1 hour') AS t, generate_series(1, 10) AS d;\n-- Refresh the base CAgg\nCALL refresh_continuous_aggregate('conditions_hourly', NULL, NULL);\nSELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('conditions_hourly') chunk;\n chunk_status_text \n-------------------\n {COMPRESSED}\n\n-- Refreshing again the base CAgg is a no-op since everything is up to date\nCALL refresh_continuous_aggregate('conditions_hourly', NULL, NULL);\nNOTICE:  continuous aggregate \"conditions_hourly\" is already up-to-date\n-- Refresh the hierarchical CAgg with invalidations procuded by the base CAgg\nCALL refresh_continuous_aggregate('conditions_daily', NULL, NULL);\nSELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('conditions_daily') chunk;\n chunk_status_text \n-------------------\n {COMPRESSED}\n\n-- Refreshing again the hierarchical CAgg is a no-op since everything is up to date\nCALL refresh_continuous_aggregate('conditions_daily', NULL, NULL);\nNOTICE:  continuous aggregate \"conditions_daily\" is already up-to-date\n-- Tests with custom segmentby and orderby\nCREATE MATERIALIZED VIEW conditions_weekly\nWITH (timescaledb.continuous) AS\nSELECT\n  time_bucket(INTERVAL '1 hour', time) AS bucket,\n  location_id,\n  device_id,\n  MAX(temperature),\n  MIN(temperature),\n  COUNT(*)\nFROM conditions\nGROUP BY 1, 2, 3\nWITH NO DATA;\nALTER MATERIALIZED VIEW conditions_weekly SET (timescaledb.compress_segmentby = 'device_id, location_id', timescaledb.compress_orderby = 'max, min, bucket DESC');\nCALL refresh_continuous_aggregate('conditions_weekly', NULL, NULL);\nSELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('conditions_weekly') chunk;\n chunk_status_text \n-------------------\n {COMPRESSED}\n\nRESET timescaledb.enable_direct_compress_on_cagg_refresh;\n"
  },
  {
    "path": "tsl/test/expected/cagg_drop_chunks.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET timezone TO PST8PDT;\n--\n-- Check that drop chunks with a unique constraint works as expected.\n--\nCREATE TABLE clients (\n       id SERIAL PRIMARY KEY,\n       name TEXT NOT NULL,\n       UNIQUE(name)\n);\nCREATE TABLE records (\n    time TIMESTAMPTZ NOT NULL,\n    clientId INT NOT NULL REFERENCES clients(id),\n    value DOUBLE PRECISION,\n    UNIQUE(time, clientId)\n);\nSELECT * FROM create_hypertable('records', 'time',\n       chunk_time_interval => INTERVAL '1h');\n hypertable_id | schema_name | table_name | created \n---------------+-------------+------------+---------\n             1 | public      | records    | t\n\nCREATE MATERIALIZED VIEW records_monthly\n    WITH (timescaledb.continuous, timescaledb.materialized_only=false)\n    AS\n        SELECT time_bucket('1d', time) as bucket,\n            clientId,\n            avg(value) as value_avg,\n            max(value)-min(value) as value_spread\n        FROM records GROUP BY bucket, clientId WITH NO DATA;\nINSERT INTO clients(name) VALUES ('test-client');\nINSERT INTO records\nSELECT generate_series('2000-03-01'::timestamptz,'2000-04-01','1 day'),1,3.14;\nSELECT * FROM records_monthly ORDER BY bucket, clientId;\n            bucket            | clientid | value_avg | value_spread \n------------------------------+----------+-----------+--------------\n Tue Feb 29 16:00:00 2000 PST |        1 |      3.14 |            0\n Wed Mar 01 16:00:00 2000 PST |        1 |      3.14 |            0\n Thu Mar 02 16:00:00 2000 PST |        1 |      3.14 |            0\n Fri Mar 03 16:00:00 2000 PST |        1 |      3.14 |            0\n Sat Mar 04 16:00:00 2000 PST |        1 |      3.14 |            0\n Sun Mar 05 16:00:00 2000 PST |        1 |      3.14 |            0\n Mon Mar 06 16:00:00 2000 PST |        1 |      3.14 |            0\n Tue Mar 07 16:00:00 2000 PST |        1 |      3.14 |            0\n Wed Mar 08 16:00:00 2000 PST |        1 |      3.14 |            0\n Thu Mar 09 16:00:00 2000 PST |        1 |      3.14 |            0\n Fri Mar 10 16:00:00 2000 PST |        1 |      3.14 |            0\n Sat Mar 11 16:00:00 2000 PST |        1 |      3.14 |            0\n Sun Mar 12 16:00:00 2000 PST |        1 |      3.14 |            0\n Mon Mar 13 16:00:00 2000 PST |        1 |      3.14 |            0\n Tue Mar 14 16:00:00 2000 PST |        1 |      3.14 |            0\n Wed Mar 15 16:00:00 2000 PST |        1 |      3.14 |            0\n Thu Mar 16 16:00:00 2000 PST |        1 |      3.14 |            0\n Fri Mar 17 16:00:00 2000 PST |        1 |      3.14 |            0\n Sat Mar 18 16:00:00 2000 PST |        1 |      3.14 |            0\n Sun Mar 19 16:00:00 2000 PST |        1 |      3.14 |            0\n Mon Mar 20 16:00:00 2000 PST |        1 |      3.14 |            0\n Tue Mar 21 16:00:00 2000 PST |        1 |      3.14 |            0\n Wed Mar 22 16:00:00 2000 PST |        1 |      3.14 |            0\n Thu Mar 23 16:00:00 2000 PST |        1 |      3.14 |            0\n Fri Mar 24 16:00:00 2000 PST |        1 |      3.14 |            0\n Sat Mar 25 16:00:00 2000 PST |        1 |      3.14 |            0\n Sun Mar 26 16:00:00 2000 PST |        1 |      3.14 |            0\n Mon Mar 27 16:00:00 2000 PST |        1 |      3.14 |            0\n Tue Mar 28 16:00:00 2000 PST |        1 |      3.14 |            0\n Wed Mar 29 16:00:00 2000 PST |        1 |      3.14 |            0\n Thu Mar 30 16:00:00 2000 PST |        1 |      3.14 |            0\n Fri Mar 31 16:00:00 2000 PST |        1 |      3.14 |            0\n\nSELECT chunk_name, range_start, range_end\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'records_monthly' ORDER BY range_start;\n chunk_name | range_start | range_end \n------------+-------------+-----------\n\nSELECT chunk_name, range_start, range_end\nFROM timescaledb_information.chunks\nWHERE hypertable_name = 'records' ORDER BY range_start;\n    chunk_name     |         range_start          |          range_end           \n-------------------+------------------------------+------------------------------\n _hyper_1_1_chunk  | Wed Mar 01 00:00:00 2000 PST | Wed Mar 01 01:00:00 2000 PST\n _hyper_1_2_chunk  | Thu Mar 02 00:00:00 2000 PST | Thu Mar 02 01:00:00 2000 PST\n _hyper_1_3_chunk  | Fri Mar 03 00:00:00 2000 PST | Fri Mar 03 01:00:00 2000 PST\n _hyper_1_4_chunk  | Sat Mar 04 00:00:00 2000 PST | Sat Mar 04 01:00:00 2000 PST\n _hyper_1_5_chunk  | Sun Mar 05 00:00:00 2000 PST | Sun Mar 05 01:00:00 2000 PST\n _hyper_1_6_chunk  | Mon Mar 06 00:00:00 2000 PST | Mon Mar 06 01:00:00 2000 PST\n _hyper_1_7_chunk  | Tue Mar 07 00:00:00 2000 PST | Tue Mar 07 01:00:00 2000 PST\n _hyper_1_8_chunk  | Wed Mar 08 00:00:00 2000 PST | Wed Mar 08 01:00:00 2000 PST\n _hyper_1_9_chunk  | Thu Mar 09 00:00:00 2000 PST | Thu Mar 09 01:00:00 2000 PST\n _hyper_1_10_chunk | Fri Mar 10 00:00:00 2000 PST | Fri Mar 10 01:00:00 2000 PST\n _hyper_1_11_chunk | Sat Mar 11 00:00:00 2000 PST | Sat Mar 11 01:00:00 2000 PST\n _hyper_1_12_chunk | Sun Mar 12 00:00:00 2000 PST | Sun Mar 12 01:00:00 2000 PST\n _hyper_1_13_chunk | Mon Mar 13 00:00:00 2000 PST | Mon Mar 13 01:00:00 2000 PST\n _hyper_1_14_chunk | Tue Mar 14 00:00:00 2000 PST | Tue Mar 14 01:00:00 2000 PST\n _hyper_1_15_chunk | Wed Mar 15 00:00:00 2000 PST | Wed Mar 15 01:00:00 2000 PST\n _hyper_1_16_chunk | Thu Mar 16 00:00:00 2000 PST | Thu Mar 16 01:00:00 2000 PST\n _hyper_1_17_chunk | Fri Mar 17 00:00:00 2000 PST | Fri Mar 17 01:00:00 2000 PST\n _hyper_1_18_chunk | Sat Mar 18 00:00:00 2000 PST | Sat Mar 18 01:00:00 2000 PST\n _hyper_1_19_chunk | Sun Mar 19 00:00:00 2000 PST | Sun Mar 19 01:00:00 2000 PST\n _hyper_1_20_chunk | Mon Mar 20 00:00:00 2000 PST | Mon Mar 20 01:00:00 2000 PST\n _hyper_1_21_chunk | Tue Mar 21 00:00:00 2000 PST | Tue Mar 21 01:00:00 2000 PST\n _hyper_1_22_chunk | Wed Mar 22 00:00:00 2000 PST | Wed Mar 22 01:00:00 2000 PST\n _hyper_1_23_chunk | Thu Mar 23 00:00:00 2000 PST | Thu Mar 23 01:00:00 2000 PST\n _hyper_1_24_chunk | Fri Mar 24 00:00:00 2000 PST | Fri Mar 24 01:00:00 2000 PST\n _hyper_1_25_chunk | Sat Mar 25 00:00:00 2000 PST | Sat Mar 25 01:00:00 2000 PST\n _hyper_1_26_chunk | Sun Mar 26 00:00:00 2000 PST | Sun Mar 26 01:00:00 2000 PST\n _hyper_1_27_chunk | Mon Mar 27 00:00:00 2000 PST | Mon Mar 27 01:00:00 2000 PST\n _hyper_1_28_chunk | Tue Mar 28 00:00:00 2000 PST | Tue Mar 28 01:00:00 2000 PST\n _hyper_1_29_chunk | Wed Mar 29 00:00:00 2000 PST | Wed Mar 29 01:00:00 2000 PST\n _hyper_1_30_chunk | Thu Mar 30 00:00:00 2000 PST | Thu Mar 30 01:00:00 2000 PST\n _hyper_1_31_chunk | Fri Mar 31 00:00:00 2000 PST | Fri Mar 31 01:00:00 2000 PST\n _hyper_1_32_chunk | Sat Apr 01 00:00:00 2000 PST | Sat Apr 01 01:00:00 2000 PST\n\nCALL refresh_continuous_aggregate('records_monthly', NULL, NULL);\n\\set VERBOSITY default\nSELECT drop_chunks('records', '2000-03-16'::timestamptz);\n               drop_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_1_1_chunk\n _timescaledb_internal._hyper_1_2_chunk\n _timescaledb_internal._hyper_1_3_chunk\n _timescaledb_internal._hyper_1_4_chunk\n _timescaledb_internal._hyper_1_5_chunk\n _timescaledb_internal._hyper_1_6_chunk\n _timescaledb_internal._hyper_1_7_chunk\n _timescaledb_internal._hyper_1_8_chunk\n _timescaledb_internal._hyper_1_9_chunk\n _timescaledb_internal._hyper_1_10_chunk\n _timescaledb_internal._hyper_1_11_chunk\n _timescaledb_internal._hyper_1_12_chunk\n _timescaledb_internal._hyper_1_13_chunk\n _timescaledb_internal._hyper_1_14_chunk\n _timescaledb_internal._hyper_1_15_chunk\n\n\\set VERBOSITY terse\nDROP MATERIALIZED VIEW records_monthly;\nNOTICE:  drop cascades to 32 other objects\nDROP TABLE records;\nDROP TABLE clients;\n\\set VERBOSITY default\nCREATE PROCEDURE refresh_cagg_by_chunk_range(_cagg REGCLASS, _hypertable REGCLASS, _older_than INTEGER)\nAS\n$$\nDECLARE\n    _r RECORD;\nBEGIN\n    WITH _chunks AS (\n        SELECT relname, nspname\n        FROM show_chunks(_hypertable, _older_than) AS relid\n        JOIN pg_catalog.pg_class ON pg_class.oid = relid AND pg_class.relkind = 'r'\n        JOIN pg_catalog.pg_namespace ON pg_namespace.oid = pg_class.relnamespace\n    )\n    SELECT MIN(range_start) AS range_start, MAX(range_end) AS range_end\n    INTO _r\n    FROM\n        _chunks\n        JOIN _timescaledb_catalog.chunk ON chunk.schema_name = _chunks.nspname AND chunk.table_name = _chunks.relname\n        JOIN _timescaledb_catalog.chunk_constraint ON chunk_id = chunk.id\n        JOIN _timescaledb_catalog.dimension_slice ON dimension_slice.id = dimension_slice_id;\n\n    RAISE INFO 'range_start=% range_end=%', _r.range_start::int, _r.range_end::int;\n    CALL refresh_continuous_aggregate(_cagg, _r.range_start::int, _r.range_end::int + 1);\nEND;\n$$\nLANGUAGE plpgsql;\nCREATE OR REPLACE FUNCTION test_int_now() returns INT LANGUAGE SQL STABLE as\n    $$ SELECT 125 $$;\nCREATE TABLE conditions(time_int INT NOT NULL, value FLOAT);\nSELECT create_hypertable('conditions', 'time_int', chunk_time_interval => 4);\n    create_hypertable    \n-------------------------\n (3,public,conditions,t)\n\nINSERT INTO conditions\nSELECT time_val, 1 FROM generate_series(0, 19, 1) AS time_val;\nSELECT set_integer_now_func('conditions', 'test_int_now');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW conditions_2\n    WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE)\n    AS\n        SELECT time_bucket(2, time_int) as bucket,\n            SUM(value), COUNT(value)\n        FROM conditions GROUP BY bucket WITH DATA;\nNOTICE:  refreshing continuous aggregate \"conditions_2\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\nSELECT * FROM conditions_2 ORDER BY bucket;\n bucket | sum | count \n--------+-----+-------\n      0 |   2 |     2\n      2 |   2 |     2\n      4 |   2 |     2\n      6 |   2 |     2\n      8 |   2 |     2\n     10 |   2 |     2\n     12 |   2 |     2\n     14 |   2 |     2\n     16 |   2 |     2\n     18 |   2 |     2\n\nUPDATE conditions SET value = 4.00 WHERE time_int = 0;\nUPDATE conditions SET value = 4.00 WHERE time_int = 6;\nCALL refresh_cagg_by_chunk_range('conditions_2', 'conditions', 4);\nINFO:  range_start=0 range_end=4\nSELECT drop_chunks('conditions', 4);\n               drop_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_3_65_chunk\n\nSELECT * FROM conditions_2 ORDER BY bucket;\n bucket | sum | count \n--------+-----+-------\n      0 |   5 |     2\n      2 |   2 |     2\n      4 |   2 |     2\n      6 |   2 |     2\n      8 |   2 |     2\n     10 |   2 |     2\n     12 |   2 |     2\n     14 |   2 |     2\n     16 |   2 |     2\n     18 |   2 |     2\n\nCALL refresh_cagg_by_chunk_range('conditions_2', 'conditions', 8);\nINFO:  range_start=4 range_end=8\nSELECT * FROM conditions_2 ORDER BY bucket;\n bucket | sum | count \n--------+-----+-------\n      0 |   5 |     2\n      2 |   2 |     2\n      4 |   2 |     2\n      6 |   5 |     2\n      8 |   2 |     2\n     10 |   2 |     2\n     12 |   2 |     2\n     14 |   2 |     2\n     16 |   2 |     2\n     18 |   2 |     2\n\nUPDATE conditions SET value = 4.00 WHERE time_int = 19;\nSELECT drop_chunks('conditions', 8);\n               drop_chunks               \n-----------------------------------------\n _timescaledb_internal._hyper_3_66_chunk\n\nCALL refresh_cagg_by_chunk_range('conditions_2', 'conditions', 12);\nINFO:  range_start=8 range_end=12\nSELECT * FROM conditions_2 ORDER BY bucket;\n bucket | sum | count \n--------+-----+-------\n      0 |   5 |     2\n      2 |   2 |     2\n      4 |   2 |     2\n      6 |   5 |     2\n      8 |   2 |     2\n     10 |   2 |     2\n     12 |   2 |     2\n     14 |   2 |     2\n     16 |   2 |     2\n     18 |   2 |     2\n\nCALL refresh_cagg_by_chunk_range('conditions_2', 'conditions', NULL);\nINFO:  range_start=8 range_end=20\nSELECT * FROM conditions_2 ORDER BY bucket;\n bucket | sum | count \n--------+-----+-------\n      0 |   5 |     2\n      2 |   2 |     2\n      4 |   2 |     2\n      6 |   5 |     2\n      8 |   2 |     2\n     10 |   2 |     2\n     12 |   2 |     2\n     14 |   2 |     2\n     16 |   2 |     2\n     18 |   5 |     2\n\nDROP PROCEDURE refresh_cagg_by_chunk_range(REGCLASS, REGCLASS, INTEGER);\n"
  },
  {
    "path": "tsl/test/expected/cagg_dump.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\nSET timezone TO PST8PDT;\nCREATE TYPE custom_type AS (high int, low int);\nCREATE TABLE conditions_before (\n      timec       TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null,\n      highlow     custom_type null,\n      bit_int     smallint,\n      good_life   boolean\n    );\nSELECT table_name FROM create_hypertable( 'conditions_before', 'timec');\n    table_name     \n-------------------\n conditions_before\n\nINSERT INTO conditions_before\nSELECT generate_series('2018-12-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'POR', 55, 75, 40, 70, NULL, (1,2)::custom_type, 2, true;\nINSERT INTO conditions_before\nSELECT generate_series('2018-11-01 00:00'::timestamp, '2018-12-31 00:00'::timestamp, '1 day'), 'NYC', 35, 45, 50, 40, NULL, (3,4)::custom_type, 4, false;\nINSERT INTO conditions_before\nSELECT generate_series('2018-11-01 00:00'::timestamp, '2018-12-15 00:00'::timestamp, '1 day'), 'LA', 73, 55, NULL, 28, NULL, NULL, 8, true;\nCREATE TABLE conditions_after (like conditions_before including all);\nSELECT table_name FROM create_hypertable( 'conditions_after', 'timec');\n    table_name    \n------------------\n conditions_after\n\nINSERT INTO conditions_after SELECT * FROM conditions_before;\nSELECT\n  $$\n  SELECT time_bucket('1week', timec) as bucket,\n    location,\n    min(allnull) as min_allnull,\n    max(temperature) as max_temp,\n    sum(temperature)+sum(humidity) as agg_sum_expr,\n    avg(humidity),\n    stddev(humidity),\n    bit_and(bit_int),\n    bit_or(bit_int),\n    bool_and(good_life),\n    every(temperature > 0),\n    bool_or(good_life),\n    count(*) as count_rows,\n    count(temperature) as count_temp,\n    count(allnull) as count_zero,\n    corr(temperature, humidity),\n    covar_pop(temperature, humidity),\n    covar_samp(temperature, humidity),\n    regr_avgx(temperature, humidity),\n    regr_avgy(temperature, humidity),\n    regr_count(temperature, humidity),\n    regr_intercept(temperature, humidity),\n    regr_r2(temperature, humidity),\n    regr_slope(temperature, humidity),\n    regr_sxx(temperature, humidity),\n    regr_sxy(temperature, humidity),\n    regr_syy(temperature, humidity),\n    stddev(temperature) as stddev_temp,\n    stddev_pop(temperature),\n    stddev_samp(temperature),\n    variance(temperature),\n    var_pop(temperature),\n    var_samp(temperature),\n    last(temperature, timec) as last_temp,\n    last(highlow, timec) as last_hl,\n    first(highlow, timec) as first_hl,\n    histogram(temperature, 0, 100, 5)\n  FROM TABLE\n  GROUP BY bucket, location\n  HAVING min(location) >= 'NYC' and avg(temperature) > 20\n  $$ AS \"QUERY_TEMPLATE\"\n\\gset\nSELECT\n  replace(:'QUERY_TEMPLATE', 'TABLE', 'conditions_before') AS \"QUERY_BEFORE\",\n  replace(:'QUERY_TEMPLATE', 'TABLE', 'conditions_after') AS \"QUERY_AFTER\"\n\\gset\nDROP MATERIALIZED VIEW IF EXISTS mat_test;\nNOTICE:  materialized view \"mat_test\" does not exist, skipping\n--materialize this VIEW before dump this tests\n--that the partial state survives the dump intact\nCREATE MATERIALIZED VIEW mat_before\nWITH (timescaledb.continuous)\nAS :QUERY_BEFORE WITH NO DATA;\n--materialize this VIEW after dump this tests\n--that the partialize VIEW and refresh mechanics\n--survives the dump intact\nCREATE MATERIALIZED VIEW mat_after\nWITH (timescaledb.continuous)\nAS :QUERY_AFTER WITH NO DATA;\n--materialize mat_before\nSET client_min_messages TO NOTICE;\nCALL refresh_continuous_aggregate('mat_before', NULL, NULL);\nSELECT count(*) FROM conditions_before;\n count \n-------\n   137\n\nSELECT count(*) FROM conditions_after;\n count \n-------\n   137\n\nSELECT  h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_VIEW_name as \"PART_VIEW_NAME\",\n       partial_VIEW_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_VIEW_name = 'mat_before'\n\\gset\nSELECT count(*) FROM conditions_before;\n count \n-------\n   137\n\nSELECT count(*) FROM conditions_after;\n count \n-------\n   137\n\n--dump & restore\n\\c postgres :ROLE_SUPERUSER\n\\! utils/pg_dump_aux_dump.sh dump/pg_dump.sql\n\\c :TEST_DBNAME\nSET client_min_messages = ERROR;\nCREATE EXTENSION timescaledb CASCADE;\nRESET client_min_messages;\n--\\! cp dump/pg_dump.sql /tmp/dump.sql\nSELECT timescaledb_pre_restore();\n timescaledb_pre_restore \n-------------------------\n t\n\n\\! utils/pg_dump_aux_restore.sh dump/pg_dump.sql\nSELECT timescaledb_post_restore();\n timescaledb_post_restore \n--------------------------\n t\n\nSELECT _timescaledb_functions.stop_background_workers();\n stop_background_workers \n-------------------------\n t\n\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSET timezone TO PST8PDT;\n--make sure the appropriate DROP are still blocked.\n\\set ON_ERROR_STOP 0\nDROP table :\"MAT_SCHEMA_NAME\".:\"MAT_TABLE_NAME\";\nERROR:  cannot drop table _timescaledb_internal._materialized_hypertable_3 because other objects depend on it\nDROP VIEW :\"PART_VIEW_SCHEMA\".:\"PART_VIEW_NAME\";\nERROR:  cannot drop the partial/direct view because it is required by a continuous aggregate\n\\set ON_ERROR_STOP 1\n--materialize mat_after\nCALL refresh_continuous_aggregate('mat_after', NULL, NULL);\nSELECT count(*) FROM mat_after;\n count \n-------\n    16\n\n--compare results\nSELECT count(*) FROM conditions_before;\n count \n-------\n   137\n\nSELECT count(*) FROM conditions_after;\n count \n-------\n   137\n\n\\set VIEW_NAME mat_before\n\\set QUERY :QUERY_BEFORE\n\\set ECHO errors\n                          description                          | view_name  | count \n---------------------------------------------------------------+------------+-------\n Number of rows different between view and original (expect 0) | mat_before |     0\n\n\\set VIEW_NAME mat_after\n\\set QUERY :QUERY_AFTER\n\\set ECHO errors\n                          description                          | view_name | count \n---------------------------------------------------------------+-----------+-------\n Number of rows different between view and original (expect 0) | mat_after |     0\n\nDROP MATERIALIZED VIEW mat_after;\nNOTICE:  drop cascades to 2 other objects\nDROP MATERIALIZED VIEW mat_before;\nNOTICE:  drop cascades to 2 other objects\n"
  },
  {
    "path": "tsl/test/expected/cagg_errors.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set ON_ERROR_STOP 0\n\\set VERBOSITY default\nSET timezone TO PST8PDT;\n--negative tests for query validation\ncreate table mat_t1( a integer, b integer,c TEXT);\nCREATE TABLE conditions (\n      timec        TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature integer  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      timemeasure TIMESTAMPTZ,\n      timeinterval INTERVAL\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false, timescaledb.myfill = 1)\nas\nselect location , min(temperature)\nfrom conditions\ngroup by time_bucket('1d', timec), location WITH NO DATA;\nERROR:  unrecognized parameter \"timescaledb.myfill\"\nHINT:  Valid timescaledb parameters are: continuous, create_group_indexes, materialized_only, columnstore, chunk_interval, segmentby, orderby, compress_chunk_interval\n--valid PG option\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false, check_option = LOCAL )\nas\nselect * from conditions , mat_t1 WITH NO DATA;\nERROR:  unsupported combination of storage parameters\nDETAIL:  A continuous aggregate does not support standard storage parameters.\nHINT:  Use only parameters with the \"timescaledb.\" prefix when creating a continuous aggregate.\n--non-hypertable\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nas\nselect a, count(*) from mat_t1\ngroup by a WITH NO DATA;\nERROR:  invalid continuous aggregate view\nDETAIL:  At least one hypertable should be used in the view definition.\n-- no group by\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nas\nselect count(*) from conditions  WITH NO DATA;\nERROR:  invalid continuous aggregate query\nHINT:  Include at least one aggregate function and a GROUP BY clause with time bucket.\n-- no time_bucket in group by\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nas\nselect count(*) from conditions group by location WITH NO DATA;\nERROR:  continuous aggregate view must include a valid time bucket function\n-- with valid query in a CTE\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nwith m1 as (\nSelect location, count(*) from conditions\n group by time_bucket('1week', timec) , location)\nselect * from m1 WITH NO DATA;\nERROR:  invalid continuous aggregate query\nDETAIL:  CTEs and subqueries are not supported by continuous aggregates.\n--with DISTINCT ON\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nas\n select distinct on ( location ) count(*)  from conditions group by location, time_bucket('1week', timec)  WITH NO DATA;\nERROR:  invalid continuous aggregate query\nDETAIL:  DISTINCT / DISTINCT ON queries are not supported by continuous aggregates.\n-- time_bucket on non partitioning column of hypertable\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSelect max(temperature)\nfrom conditions\n group by time_bucket('1week', timemeasure) , location WITH NO DATA;\nERROR:  time bucket function must reference the primary hypertable dimension column\n--time_bucket on expression\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSelect max(temperature)\nfrom conditions\n group by time_bucket('1week', timec+ '10 minutes'::interval) , location WITH NO DATA;\nERROR:  time bucket function must reference the primary hypertable dimension column\n--multiple time_bucket functions\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSelect max(temperature)\nfrom conditions\n group by time_bucket('1week', timec) , time_bucket('1month', timec), location WITH NO DATA;\nERROR:  continuous aggregate view cannot contain multiple time bucket functions\n--time_bucket using non-const for first argument\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSelect max(temperature)\nfrom conditions\n group by time_bucket( timeinterval, timec) , location WITH NO DATA;\nERROR:  only immutable expressions allowed in time bucket function\nHINT:  Use an immutable expression as first argument to the time bucket function.\n--window function\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSelect avg(temperature) over( order by humidity)\nfrom conditions\n WITH NO DATA;\nERROR:  invalid continuous aggregate query\nDETAIL:  Window function support not enabled.\nHINT:  Enable experimental window function support by setting timescaledb.enable_cagg_window_functions.\n-- using subqueries\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSelect sum(humidity), avg(temperature::int4)\nfrom\n( select humidity, temperature, location, timec\nfrom conditions ) q\n group by time_bucket('1week', timec) , location  WITH NO DATA;\nERROR:  invalid continuous aggregate view\nDETAIL:  Sub-queries are not supported in FROM clause.\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nselect * from\n( Select sum(humidity), avg(temperature::int4)\nfrom conditions\n group by time_bucket('1week', timec) , location )  q WITH NO DATA;\nERROR:  invalid continuous aggregate query\nHINT:  Include at least one aggregate function and a GROUP BY clause with time bucket.\n--using limit /limit offset\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSelect sum(humidity), avg(temperature::int4)\nfrom conditions\n group by time_bucket('1week', timec) , location\nlimit 10  WITH NO DATA;\nERROR:  invalid continuous aggregate query\nDETAIL:  LIMIT and LIMIT OFFSET are not supported in queries defining continuous aggregates.\nHINT:  Use LIMIT and LIMIT OFFSET in SELECTS from the continuous aggregate view instead.\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSelect sum(humidity), avg(temperature::int4)\nfrom conditions\n group by time_bucket('1week', timec) , location\noffset 10 WITH NO DATA;\nERROR:  invalid continuous aggregate query\nDETAIL:  LIMIT and LIMIT OFFSET are not supported in queries defining continuous aggregates.\nHINT:  Use LIMIT and LIMIT OFFSET in SELECTS from the continuous aggregate view instead.\n--using FETCH\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSelect sum(humidity), avg(temperature::int4)\nfrom conditions\n group by time_bucket('1week', timec) , location\nfetch first 10 rows only WITH NO DATA;\nERROR:  invalid continuous aggregate query\nDETAIL:  LIMIT and LIMIT OFFSET are not supported in queries defining continuous aggregates.\nHINT:  Use LIMIT and LIMIT OFFSET in SELECTS from the continuous aggregate view instead.\n--using locking clauses FOR clause\n--all should be disabled. we cannot guarntee locks on the hypertable\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSelect sum(humidity), avg(temperature::int4)\nfrom conditions\n group by time_bucket('1week', timec) , location\nFOR KEY SHARE WITH NO DATA;\nERROR:  FOR KEY SHARE is not allowed with GROUP BY clause\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSelect sum(humidity), avg(temperature::int4)\nfrom conditions\n group by time_bucket('1week', timec) , location\nFOR SHARE WITH NO DATA;\nERROR:  FOR SHARE is not allowed with GROUP BY clause\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSelect sum(humidity), avg(temperature::int4)\nfrom conditions\n group by time_bucket('1week', timec) , location\nFOR UPDATE WITH NO DATA;\nERROR:  FOR UPDATE is not allowed with GROUP BY clause\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSelect sum(humidity), avg(temperature::int4)\nfrom conditions\n group by time_bucket('1week', timec) , location\nFOR NO KEY UPDATE WITH NO DATA;\nERROR:  FOR NO KEY UPDATE is not allowed with GROUP BY clause\n--tablesample clause\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSelect sum(humidity), avg(temperature::int4)\nfrom conditions tablesample bernoulli(0.2)\n group by time_bucket('1week', timec) , location\n WITH NO DATA;\nERROR:  invalid continuous aggregate view\nDETAIL:  TABLESAMPLE is not supported in continuous aggregate.\n-- ONLY in from clause\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSelect sum(humidity), avg(temperature::int4)\nfrom ONLY conditions\n group by time_bucket('1week', timec) , location  WITH NO DATA;\nERROR:  invalid continuous aggregate view\nDETAIL:  FROM ONLY on hypertables is not allowed in continuous aggregate.\n--grouping sets and variants\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSelect sum(humidity), avg(temperature::int4)\nfrom conditions\n group by grouping sets(time_bucket('1week', timec) , location )  WITH NO DATA;\nERROR:  invalid continuous aggregate query\nDETAIL:  GROUP BY GROUPING SETS, ROLLUP and CUBE are not supported by continuous aggregates\nHINT:  Define multiple continuous aggregates with different grouping levels.\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSelect sum(humidity), avg(temperature::int4)\nfrom conditions\ngroup by rollup(time_bucket('1week', timec) , location )  WITH NO DATA;\nERROR:  invalid continuous aggregate query\nDETAIL:  GROUP BY GROUPING SETS, ROLLUP and CUBE are not supported by continuous aggregates\nHINT:  Define multiple continuous aggregates with different grouping levels.\n-- Should use CREATE MATERIALIZED VIEW to create continuous aggregates\nCREATE VIEW continuous_aggs_errors_tbl1 WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket('1 week', timec)\n  FROM conditions\nGROUP BY time_bucket('1 week', timec);\nERROR:  cannot create continuous aggregate with CREATE VIEW\nHINT:  Use CREATE MATERIALIZED VIEW to create a continuous aggregate.\n-- invalid `bucket_width` for `time_bucket` function\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT time_bucket(NULL, timec), sum(temperature), min(location)\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\nERROR:  invalid bucket width for time bucket function\n-- row security on table\ncreate table rowsec_tab( a bigint, b integer, c integer);\nselect table_name from create_hypertable( 'rowsec_tab', 'a', chunk_time_interval=>10);\n table_name \n------------\n rowsec_tab\n\nCREATE OR REPLACE FUNCTION integer_now_test() returns bigint LANGUAGE SQL STABLE as $$ SELECT coalesce(max(a), 0)::bigint FROM rowsec_tab $$;\nSELECT set_integer_now_func('rowsec_tab', 'integer_now_test');\n set_integer_now_func \n----------------------\n \n\nalter table rowsec_tab ENABLE ROW LEVEL SECURITY;\ncreate policy rowsec_tab_allview ON rowsec_tab FOR SELECT USING(true);\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSelect sum( b), min(c)\nfrom rowsec_tab\ngroup by time_bucket('1', a) WITH NO DATA;\nERROR:  cannot create continuous aggregate on hypertable with row security\ndrop table conditions cascade;\n--negative tests for WITH options\nCREATE TABLE conditions (\n      timec       TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\ncreate materialized view mat_with_test( timec, minl, sumt , sumh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=false)\nas\nselect time_bucket('1day', timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by time_bucket('1day', timec) WITH NO DATA;\nSELECT  h.schema_name AS \"MAT_SCHEMA_NAME\",\n       h.table_name AS \"MAT_TABLE_NAME\",\n       partial_view_name as \"PART_VIEW_NAME\",\n       partial_view_schema as \"PART_VIEW_SCHEMA\"\nFROM _timescaledb_catalog.continuous_agg ca\nINNER JOIN _timescaledb_catalog.hypertable h ON(h.id = ca.mat_hypertable_id)\nWHERE user_view_name = 'mat_with_test'\n\\gset\n\\set ON_ERROR_STOP 0\n-- triggers not allowed on  continuous aggregate\nCREATE OR REPLACE FUNCTION not_allowed() RETURNS trigger AS $$\nBEGIN\n  RETURN NEW;\nEND; $$ LANGUAGE plpgsql;\nCREATE TRIGGER not_allowed_trigger INSTEAD OF INSERT ON mat_with_test\nFOR EACH ROW EXECUTE FUNCTION not_allowed();\nERROR:  triggers are not supported on continuous aggregate\nALTER MATERIALIZED VIEW mat_with_test SET(timescaledb.create_group_indexes = 'false');\nERROR:  cannot alter create_group_indexes option for continuous aggregates\nALTER MATERIALIZED VIEW mat_with_test SET(timescaledb.create_group_indexes = 'true');\nERROR:  cannot alter create_group_indexes option for continuous aggregates\nALTER MATERIALIZED VIEW mat_with_test ALTER timec DROP default;\nERROR:  cannot alter only SET options of a continuous aggregate\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 3 other objects\n--test WITH using a hypertable with an integer time dimension\nCREATE TABLE conditions (\n      timec       SMALLINT       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec', chunk_time_interval=> 100);\n table_name \n------------\n conditions\n\nCREATE OR REPLACE FUNCTION integer_now_test_s() returns smallint LANGUAGE SQL STABLE as $$ SELECT coalesce(max(timec), 0)::smallint FROM conditions $$;\nSELECT set_integer_now_func('conditions', 'integer_now_test_s');\n set_integer_now_func \n----------------------\n \n\n\\set ON_ERROR_STOP 0\ncreate materialized view mat_with_test( timec, minl, sumt , sumh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=false)\nas\nselect time_bucket(100, timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by time_bucket(100, timec) WITH NO DATA;\nERROR:  time bucket function must reference the primary hypertable dimension column\ncreate materialized view mat_with_test( timec, minl, sumt , sumh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=false)\nas\nselect time_bucket(100, timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by time_bucket(100, timec) WITH NO DATA;\nERROR:  time bucket function must reference the primary hypertable dimension column\nALTER TABLE conditions ALTER timec type int;\ncreate materialized view mat_with_test( timec, minl, sumt , sumh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=false)\nas\nselect time_bucket(100, timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by time_bucket(100, timec) WITH NO DATA;\n\\set ON_ERROR_STOP 1\nDROP TABLE conditions cascade;\nNOTICE:  drop cascades to 3 other objects\nCREATE TABLE conditions (\n      timec       BIGINT       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec', chunk_time_interval=> 100);\n table_name \n------------\n conditions\n\nCREATE OR REPLACE FUNCTION integer_now_test_b() returns bigint LANGUAGE SQL STABLE as $$ SELECT coalesce(max(timec), 0)::bigint FROM conditions $$;\nSELECT set_integer_now_func('conditions', 'integer_now_test_b');\n set_integer_now_func \n----------------------\n \n\ncreate materialized view mat_with_test( timec, minl, sumt , sumh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=false)\nas\nselect time_bucket(BIGINT '100', timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by 1 WITH NO DATA;\n-- custom time partition functions are not supported with invalidations\nCREATE FUNCTION text_part_func(TEXT) RETURNS BIGINT\n    AS $$ SELECT length($1)::BIGINT $$\n    LANGUAGE SQL IMMUTABLE;\nCREATE TABLE text_time(time TEXT);\n    SELECT create_hypertable('text_time', 'time', chunk_time_interval => 10, time_partitioning_func => 'text_part_func');\n   create_hypertable    \n------------------------\n (9,public,text_time,t)\n\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\nCREATE MATERIALIZED VIEW text_view\n    WITH (timescaledb.continuous, timescaledb.materialized_only=false)\n    AS SELECT time_bucket('5', text_part_func(time)), COUNT(time)\n        FROM text_time\n        GROUP BY 1 WITH NO DATA;\nERROR:  custom partitioning functions not supported with continuous aggregates\n\\set ON_ERROR_STOP 1\n-- Check that we get an error when mixing normal materialized views\n-- and continuous aggregates.\nCREATE MATERIALIZED VIEW normal_mat_view AS\nSELECT time_bucket('5', text_part_func(time)), COUNT(time)\n  FROM text_time\nGROUP BY 1 WITH NO DATA;\n\\set VERBOSITY terse\n\\set ON_ERROR_STOP 0\nDROP MATERIALIZED VIEW normal_mat_view, mat_with_test;\nERROR:  mixing continuous aggregates and other objects not allowed\n\\set ON_ERROR_STOP 1\nDROP TABLE text_time CASCADE;\nNOTICE:  drop cascades to materialized view normal_mat_view\nCREATE TABLE measurements (time TIMESTAMPTZ NOT NULL, device INT, value FLOAT);\nSELECT create_hypertable('measurements', 'time');\n     create_hypertable      \n----------------------------\n (10,public,measurements,t)\n\nINSERT INTO measurements VALUES ('2019-03-04 13:30', 1, 1.3);\n-- Add a continuous aggregate on the measurements table and a policy\n-- to be able to test error cases for the add_job function.\nCREATE MATERIALIZED VIEW measurements_summary WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT time_bucket('1 day', time), COUNT(time)\n  FROM measurements\nGROUP BY 1 WITH NO DATA;\n-- First test that add_job checks the config. It is currently possible\n-- to add non-custom jobs using the add_job function so we need to\n-- test that the function actually checks the config parameters. These\n-- should all generate errors, for different reasons...\n\\set ON_ERROR_STOP 0\n-- ... this one because it is missing a field.\nSELECT add_job(\n       '_timescaledb_functions.policy_refresh_continuous_aggregate'::regproc,\n       '1 hour'::interval,\n       check_config => '_timescaledb_functions.policy_refresh_continuous_aggregate_check'::regproc,\n       config => '{\"end_offset\": null, \"start_offset\": null}');\nERROR:  could not find \"mat_hypertable_id\" in config for job\n-- ... this one because it has a bad value for start_offset\nSELECT add_job(\n       '_timescaledb_functions.policy_refresh_continuous_aggregate'::regproc,\n       '1 hour'::interval,\n       check_config => '_timescaledb_functions.policy_refresh_continuous_aggregate_check'::regproc,\n       config => '{\"end_offset\": null, \"start_offset\": \"1 fortnight\", \"mat_hypertable_id\": 11}');\nERROR:  invalid input syntax for type interval: \"1 fortnight\"\n-- ... this one because it has a bad value for end_offset\nSELECT add_job(\n       '_timescaledb_functions.policy_refresh_continuous_aggregate'::regproc,\n       '1 hour'::interval,\n       check_config => '_timescaledb_functions.policy_refresh_continuous_aggregate_check'::regproc,\n       config => '{\"end_offset\": \"chicken\", \"start_offset\": null, \"mat_hypertable_id\": 11}');\nERROR:  invalid input syntax for type interval: \"chicken\"\n\\set ON_ERROR_STOP 1\nSELECT add_continuous_aggregate_policy('measurements_summary', NULL, NULL, '1 h'::interval) AS job_id\n\\gset\n\\x on\nSELECT * FROM _timescaledb_catalog.bgw_job WHERE id = :job_id;\n-[ RECORD 1 ]-----+--------------------------------------------------------------------\nid                | 1000\napplication_name  | Refresh Continuous Aggregate Policy [1000]\nschedule_interval | @ 1 hour\nmax_runtime       | @ 0\nmax_retries       | -1\nretry_period      | @ 1 hour\nproc_schema       | _timescaledb_functions\nproc_name         | policy_refresh_continuous_aggregate\nowner             | default_perm_user\nscheduled         | t\nfixed_schedule    | f\ninitial_start     | \nhypertable_id     | 11\nconfig            | {\"end_offset\": null, \"start_offset\": null, \"mat_hypertable_id\": 11}\ncheck_schema      | _timescaledb_functions\ncheck_name        | policy_refresh_continuous_aggregate_check\ntimezone          | \n\n\\x off\n-- These are all weird values for the parameters for the continuous\n-- aggregate jobs and should generate an error. Since the config will\n-- be replaced, we will also generate error for missing arguments.\n\\set ON_ERROR_STOP 0\nSELECT alter_job(:job_id, config => '{\"end_offset\": \"1 week\", \"start_offset\": \"2 fortnights\"}');\nERROR:  could not find \"mat_hypertable_id\" in config for job\nSELECT alter_job(:job_id,\n       config => '{\"mat_hypertable_id\": 11, \"end_offset\": \"chicken\", \"start_offset\": \"1 fortnights\"}');\nERROR:  invalid input syntax for type interval: \"1 fortnights\"\nSELECT alter_job(:job_id,\n       config => '{\"mat_hypertable_id\": 11, \"end_offset\": \"chicken\", \"start_offset\": \"1 week\"}');\nERROR:  invalid input syntax for type interval: \"chicken\"\n\\set ON_ERROR_STOP 1\nDROP TABLE measurements CASCADE;\nNOTICE:  drop cascades to 3 other objects\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 3 other objects\n-- test handling of invalid mat_hypertable_id\ncreate table i2980(time timestamptz not null);\nselect create_hypertable('i2980','time');\n  create_hypertable  \n---------------------\n (12,public,i2980,t)\n\ncreate materialized view i2980_cagg with (timescaledb.continuous, timescaledb.materialized_only=false) AS SELECT time_bucket('1h',time), avg(7) FROM i2980 GROUP BY 1;\nNOTICE:  continuous aggregate \"i2980_cagg\" is already up-to-date\nselect add_continuous_aggregate_policy('i2980_cagg',NULL,NULL,'4h') AS job_id \\gset\n\\set ON_ERROR_STOP 0\nselect alter_job(:job_id,config:='{\"end_offset\": null, \"start_offset\": null, \"mat_hypertable_id\": 1000}');\nERROR:  invalid materialized hypertable ID: 1000\n--test creating continuous aggregate with compression enabled --\nCREATE MATERIALIZED VIEW  i2980_cagg2 with (timescaledb.continuous, timescaledb.materialized_only=false, timescaledb.compress)\nAS SELECT time_bucket('1h',time), avg(7) FROM i2980 GROUP BY 1;\nERROR:  cannot enable compression while creating a continuous aggregate\n--this one succeeds\nCREATE MATERIALIZED VIEW  i2980_cagg2 with (timescaledb.continuous, timescaledb.materialized_only=false)\nAS SELECT time_bucket('1h',time) as bucket, avg(7) FROM i2980 GROUP BY 1;\nNOTICE:  continuous aggregate \"i2980_cagg2\" is already up-to-date\n--now enable compression with invalid parameters\nALTER MATERIALIZED VIEW i2980_cagg2 SET ( timescaledb.compress,\ntimescaledb.compress_segmentby = 'bucket');\nNOTICE:  defaulting compress_orderby to bucket\nERROR:  cannot use column \"bucket\" for both ordering and segmenting\nALTER MATERIALIZED VIEW i2980_cagg2 SET ( timescaledb.compress,\ntimescaledb.compress_orderby = 'bucket');\n--enable compression and test re-enabling compression\nALTER MATERIALIZED VIEW i2980_cagg2 SET ( timescaledb.compress);\nNOTICE:  defaulting compress_orderby to bucket\ninsert into i2980 select now();\ncall refresh_continuous_aggregate('i2980_cagg2', NULL, NULL);\nSELECT compress_chunk(ch) FROM show_chunks('i2980_cagg2') ch;\n             compress_chunk              \n-----------------------------------------\n _timescaledb_internal._hyper_14_3_chunk\n\nALTER MATERIALIZED VIEW i2980_cagg2 SET ( timescaledb.compress = 'false');\nERROR:  cannot disable columnstore on hypertable with columnstore chunks\nALTER MATERIALIZED VIEW i2980_cagg2 SET ( timescaledb.compress = 'true');\nNOTICE:  defaulting compress_orderby to bucket\nALTER MATERIALIZED VIEW i2980_cagg2 SET ( timescaledb.compress, timescaledb.compress_segmentby = 'bucket');\nNOTICE:  defaulting compress_orderby to bucket\nERROR:  cannot use column \"bucket\" for both ordering and segmenting\n--Errors with compression policy on caggs--\nselect add_continuous_aggregate_policy('i2980_cagg2', interval '10 day', interval '2 day' ,'4h') AS job_id ;\n job_id \n--------\n   1002\n\nSELECT add_compression_policy('i2980_cagg', '8 day'::interval);\nERROR:  columnstore not enabled on continuous aggregate \"i2980_cagg\"\nALTER MATERIALIZED VIEW i2980_cagg SET ( timescaledb.compress );\nNOTICE:  defaulting compress_orderby to time_bucket\nSELECT add_continuous_aggregate_policy('i2980_cagg2', '10 day'::interval, '6 day'::interval);\nERROR:  function add_continuous_aggregate_policy(unknown, interval, interval) does not exist at character 8\nSELECT add_compression_policy('i2980_cagg2', '3'::integer);\nERROR:  unsupported compress_after argument type, expected type : interval\nSELECT add_compression_policy('i2980_cagg2', 13::integer);\nERROR:  unsupported compress_after argument type, expected type : interval\nSELECT materialization_hypertable_schema || '.' || materialization_hypertable_name AS \"MAT_TABLE_NAME\"\nFROM timescaledb_information.continuous_aggregates\nWHERE view_name = 'i2980_cagg2'\n\\gset\nSELECT add_compression_policy( :'MAT_TABLE_NAME', 13::integer);\nERROR:  cannot add compression policy to materialized hypertable \"_materialized_hypertable_14\" \n--TEST compressing cagg chunks without enabling compression\nSELECT count(*) FROM (select decompress_chunk(ch) FROM show_chunks('i2980_cagg2') ch ) q;\n count \n-------\n     1\n\nALTER MATERIALIZED VIEW i2980_cagg2 SET (timescaledb.compress = 'false');\nSELECT compress_chunk(ch) FROM show_chunks('i2980_cagg2') ch;\nERROR:  columnstore not enabled on \"i2980_cagg2\"\n-- test error handling when trying to create cagg on internal hypertable\nCREATE TABLE comp_ht_test(time timestamptz NOT NULL);\nSELECT table_name FROM create_hypertable('comp_ht_test','time');\n  table_name  \n--------------\n comp_ht_test\n\nALTER TABLE comp_ht_test SET (timescaledb.compress);\nSELECT\n  format('%I.%I', ht.schema_name, ht.table_name) AS \"INTERNALTABLE\"\nFROM\n  _timescaledb_catalog.hypertable ht\n  INNER JOIN _timescaledb_catalog.hypertable uncompress ON (ht.id = uncompress.compressed_hypertable_id\n      AND uncompress.table_name = 'comp_ht_test') \\gset\nCREATE MATERIALIZED VIEW cagg1 WITH(timescaledb.continuous, timescaledb.materialized_only=false) AS SELECT time_bucket('1h',now()) FROM :INTERNALTABLE GROUP BY 1;\nERROR:  hypertable is an internal compressed hypertable\n--TEST ht + cagg, do not enable compression on ht and try to compress chunk on ht.\n--Check error handling for this case\nSELECT compress_chunk(ch) FROM show_chunks('i2980') ch;\nERROR:  columnstore not enabled on \"i2980\"\n-- cagg on normal view should error out\nCREATE VIEW v1 AS SELECT now() AS time;\nCREATE MATERIALIZED VIEW cagg1 WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS SELECT time_bucket('1h',time) FROM v1 GROUP BY 1;\nERROR:  invalid continuous aggregate view\n-- cagg on normal view should error out\nCREATE MATERIALIZED VIEW matv1 AS SELECT now() AS time;\nCREATE MATERIALIZED VIEW cagg1 WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS SELECT time_bucket('1h',time) FROM matv1 GROUP BY 1;\nERROR:  invalid continuous aggregate view\n-- No FROM clause in CAGG definition\nCREATE MATERIALIZED VIEW cagg1 with (timescaledb.continuous, timescaledb.materialized_only=false) AS SELECT 1 GROUP BY 1 WITH NO DATA;\nERROR:  invalid continuous aggregate query\n"
  },
  {
    "path": "tsl/test/expected/cagg_exp_monthly.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\nCREATE TABLE conditions(\n  day DATE NOT NULL,\n  city text NOT NULL,\n  temperature INT NOT NULL);\nSELECT create_hypertable(\n  'conditions', 'day',\n  chunk_time_interval => INTERVAL '1 day'\n);\n    create_hypertable    \n-------------------------\n (1,public,conditions,t)\n\nINSERT INTO conditions (day, city, temperature) VALUES\n  ('2021-06-14', 'Moscow', 26),\n  ('2021-06-15', 'Moscow', 22),\n  ('2021-06-16', 'Moscow', 24),\n  ('2021-06-17', 'Moscow', 24),\n  ('2021-06-18', 'Moscow', 27),\n  ('2021-06-19', 'Moscow', 28),\n  ('2021-06-20', 'Moscow', 30),\n  ('2021-06-21', 'Moscow', 31),\n  ('2021-06-22', 'Moscow', 34),\n  ('2021-06-23', 'Moscow', 34),\n  ('2021-06-24', 'Moscow', 34),\n  ('2021-06-25', 'Moscow', 32),\n  ('2021-06-26', 'Moscow', 32),\n  ('2021-06-27', 'Moscow', 31);\n-- Check that buckets like '1 month 15 days' (fixed-sized + variable-sized) are not allowed\n\\set ON_ERROR_STOP 0\n-- timebucket_ng is deprecated and can not be used in new CAggs anymore.\n-- However, using this GUC the restriction can be lifted in debug builds\n-- to ensure the functionality can be tested.\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions_summary\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT city,\n   timescaledb_experimental.time_bucket_ng('1 month 15 days', day) AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions\nGROUP BY city, bucket\nWITH NO DATA;\nERROR:  invalid interval specified\n\\set ON_ERROR_STOP 1\n-- Make sure it's possible to create an empty cagg (WITH NO DATA) and\n-- that all the information about the bucketing function will be saved\n-- to the TS catalog.\nCREATE MATERIALIZED VIEW conditions_summary\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT city,\n   timescaledb_experimental.time_bucket_ng('1 month', day) AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions\nGROUP BY city, bucket\nWITH NO DATA;\n-- Reset GUC to check if the CAgg would also work in release builds\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nSELECT mat_hypertable_id AS cagg_id\nFROM _timescaledb_catalog.continuous_agg\nWHERE user_view_name = 'conditions_summary'\n\\gset\nSELECT raw_hypertable_id AS ht_id\nFROM _timescaledb_catalog.continuous_agg\nWHERE user_view_name = 'conditions_summary'\n\\gset\n\\pset null <NULL>\nSELECT *\nFROM _timescaledb_catalog.continuous_aggs_bucket_function\nWHERE mat_hypertable_id = :cagg_id;\n mat_hypertable_id |                            bucket_func                            | bucket_width | bucket_origin | bucket_offset | bucket_timezone | bucket_fixed_width \n-------------------+-------------------------------------------------------------------+--------------+---------------+---------------+-----------------+--------------------\n                 2 | timescaledb_experimental.time_bucket_ng(interval,pg_catalog.date) | @ 1 mon      | <NULL>        | <NULL>        | <NULL>          | f\n\n\\pset null \"\"\n-- Check that the saved invalidation threshold is -infinity\nSELECT _timescaledb_functions.to_timestamp(watermark)\nFROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nWHERE hypertable_id = :ht_id;\n to_timestamp \n--------------\n -infinity\n\n-- Make sure truncating of the refresh window works\n\\set ON_ERROR_STOP 0\nCALL refresh_continuous_aggregate('conditions_summary', '2021-07-02', '2021-07-12');\nERROR:  refresh window too small\n\\set ON_ERROR_STOP 1\n-- Make sure refreshing works\nCALL refresh_continuous_aggregate('conditions_summary', '2021-06-01', '2021-07-01');\nSELECT city, to_char(bucket, 'YYYY-MM-DD') AS month, min, max\nFROM conditions_summary\nORDER by month, city;\n  city  |   month    | min | max \n--------+------------+-----+-----\n Moscow | 2021-06-01 |  22 |  34\n\n-- Make sure larger refresh window is fine too\nCALL refresh_continuous_aggregate('conditions_summary', '2021-03-01', '2021-07-01');\nSELECT city, to_char(bucket, 'YYYY-MM-DD') AS month, min, max\nFROM conditions_summary\nORDER by month, city;\n  city  |   month    | min | max \n--------+------------+-----+-----\n Moscow | 2021-06-01 |  22 |  34\n\n-- Special check for \"invalid or missing information about the bucketing\n-- function\" code path\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE TEMPORARY TABLE restore_table ( LIKE _timescaledb_catalog.continuous_aggs_bucket_function );\nINSERT INTO restore_table SELECT * FROM  _timescaledb_catalog.continuous_aggs_bucket_function;\nDELETE FROM _timescaledb_catalog.continuous_aggs_bucket_function;\n\\set ON_ERROR_STOP 0\n-- should fail with \"invalid or missing information...\"\nCALL refresh_continuous_aggregate('conditions_summary', '2021-06-01', '2021-07-01');\nERROR:  invalid or missing information about the bucketing function for cagg\n\\set ON_ERROR_STOP 1\nINSERT INTO _timescaledb_catalog.continuous_aggs_bucket_function SELECT * FROM restore_table;\nDROP TABLE restore_table;\n-- should execute successfully\nCALL refresh_continuous_aggregate('conditions_summary', '2021-06-01', '2021-07-01');\nNOTICE:  continuous aggregate \"conditions_summary\" is already up-to-date\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n-- Check the invalidation threshold\nSELECT _timescaledb_functions.to_timestamp(watermark) at time zone 'UTC'\nFROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nWHERE hypertable_id = :ht_id;\n         timezone         \n--------------------------\n Thu Jul 01 00:00:00 2021\n\n-- Add some dummy data for two more months and call refresh (no invalidations test case)\nINSERT INTO conditions (day, city, temperature)\nSELECT ts :: date, city, row_number() OVER ()\nFROM generate_series('2021-07-01' :: date, '2021-08-31', '1 day') as ts,\n     unnest(array['Moscow', 'Berlin']) as city;\n-- Double check generated data\nSELECT to_char(day, 'YYYY-MM-DD'), city, temperature\nFROM conditions\nWHERE day >= '2021-07-01'\nORDER BY city DESC, day;\n  to_char   |  city  | temperature \n------------+--------+-------------\n 2021-07-01 | Moscow |           1\n 2021-07-02 | Moscow |           2\n 2021-07-03 | Moscow |           3\n 2021-07-04 | Moscow |           4\n 2021-07-05 | Moscow |           5\n 2021-07-06 | Moscow |           6\n 2021-07-07 | Moscow |           7\n 2021-07-08 | Moscow |           8\n 2021-07-09 | Moscow |           9\n 2021-07-10 | Moscow |          10\n 2021-07-11 | Moscow |          11\n 2021-07-12 | Moscow |          12\n 2021-07-13 | Moscow |          13\n 2021-07-14 | Moscow |          14\n 2021-07-15 | Moscow |          15\n 2021-07-16 | Moscow |          16\n 2021-07-17 | Moscow |          17\n 2021-07-18 | Moscow |          18\n 2021-07-19 | Moscow |          19\n 2021-07-20 | Moscow |          20\n 2021-07-21 | Moscow |          21\n 2021-07-22 | Moscow |          22\n 2021-07-23 | Moscow |          23\n 2021-07-24 | Moscow |          24\n 2021-07-25 | Moscow |          25\n 2021-07-26 | Moscow |          26\n 2021-07-27 | Moscow |          27\n 2021-07-28 | Moscow |          28\n 2021-07-29 | Moscow |          29\n 2021-07-30 | Moscow |          30\n 2021-07-31 | Moscow |          31\n 2021-08-01 | Moscow |          32\n 2021-08-02 | Moscow |          33\n 2021-08-03 | Moscow |          34\n 2021-08-04 | Moscow |          35\n 2021-08-05 | Moscow |          36\n 2021-08-06 | Moscow |          37\n 2021-08-07 | Moscow |          38\n 2021-08-08 | Moscow |          39\n 2021-08-09 | Moscow |          40\n 2021-08-10 | Moscow |          41\n 2021-08-11 | Moscow |          42\n 2021-08-12 | Moscow |          43\n 2021-08-13 | Moscow |          44\n 2021-08-14 | Moscow |          45\n 2021-08-15 | Moscow |          46\n 2021-08-16 | Moscow |          47\n 2021-08-17 | Moscow |          48\n 2021-08-18 | Moscow |          49\n 2021-08-19 | Moscow |          50\n 2021-08-20 | Moscow |          51\n 2021-08-21 | Moscow |          52\n 2021-08-22 | Moscow |          53\n 2021-08-23 | Moscow |          54\n 2021-08-24 | Moscow |          55\n 2021-08-25 | Moscow |          56\n 2021-08-26 | Moscow |          57\n 2021-08-27 | Moscow |          58\n 2021-08-28 | Moscow |          59\n 2021-08-29 | Moscow |          60\n 2021-08-30 | Moscow |          61\n 2021-08-31 | Moscow |          62\n 2021-07-01 | Berlin |          63\n 2021-07-02 | Berlin |          64\n 2021-07-03 | Berlin |          65\n 2021-07-04 | Berlin |          66\n 2021-07-05 | Berlin |          67\n 2021-07-06 | Berlin |          68\n 2021-07-07 | Berlin |          69\n 2021-07-08 | Berlin |          70\n 2021-07-09 | Berlin |          71\n 2021-07-10 | Berlin |          72\n 2021-07-11 | Berlin |          73\n 2021-07-12 | Berlin |          74\n 2021-07-13 | Berlin |          75\n 2021-07-14 | Berlin |          76\n 2021-07-15 | Berlin |          77\n 2021-07-16 | Berlin |          78\n 2021-07-17 | Berlin |          79\n 2021-07-18 | Berlin |          80\n 2021-07-19 | Berlin |          81\n 2021-07-20 | Berlin |          82\n 2021-07-21 | Berlin |          83\n 2021-07-22 | Berlin |          84\n 2021-07-23 | Berlin |          85\n 2021-07-24 | Berlin |          86\n 2021-07-25 | Berlin |          87\n 2021-07-26 | Berlin |          88\n 2021-07-27 | Berlin |          89\n 2021-07-28 | Berlin |          90\n 2021-07-29 | Berlin |          91\n 2021-07-30 | Berlin |          92\n 2021-07-31 | Berlin |          93\n 2021-08-01 | Berlin |          94\n 2021-08-02 | Berlin |          95\n 2021-08-03 | Berlin |          96\n 2021-08-04 | Berlin |          97\n 2021-08-05 | Berlin |          98\n 2021-08-06 | Berlin |          99\n 2021-08-07 | Berlin |         100\n 2021-08-08 | Berlin |         101\n 2021-08-09 | Berlin |         102\n 2021-08-10 | Berlin |         103\n 2021-08-11 | Berlin |         104\n 2021-08-12 | Berlin |         105\n 2021-08-13 | Berlin |         106\n 2021-08-14 | Berlin |         107\n 2021-08-15 | Berlin |         108\n 2021-08-16 | Berlin |         109\n 2021-08-17 | Berlin |         110\n 2021-08-18 | Berlin |         111\n 2021-08-19 | Berlin |         112\n 2021-08-20 | Berlin |         113\n 2021-08-21 | Berlin |         114\n 2021-08-22 | Berlin |         115\n 2021-08-23 | Berlin |         116\n 2021-08-24 | Berlin |         117\n 2021-08-25 | Berlin |         118\n 2021-08-26 | Berlin |         119\n 2021-08-27 | Berlin |         120\n 2021-08-28 | Berlin |         121\n 2021-08-29 | Berlin |         122\n 2021-08-30 | Berlin |         123\n 2021-08-31 | Berlin |         124\n\n-- Make sure the invalidation threshold was unaffected\nSELECT _timescaledb_functions.to_timestamp(watermark) at time zone 'UTC'\nFROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nWHERE hypertable_id = :ht_id;\n         timezone         \n--------------------------\n Thu Jul 01 00:00:00 2021\n\n-- Make sure the invalidation log is empty\nSELECT\n    _timescaledb_functions.to_timestamp(lowest_modified_value) AS lowest,\n    _timescaledb_functions.to_timestamp(greatest_modified_value) AS greatest\nFROM _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log\nWHERE hypertable_id = :ht_id;\n lowest | greatest \n--------+----------\n\n-- Call refresh\nCALL refresh_continuous_aggregate('conditions_summary', '2021-06-15', '2021-09-15');\nSELECT city, to_char(bucket, 'YYYY-MM-DD') AS month, min, max\nFROM conditions_summary\nORDER by month, city;\n  city  |   month    | min | max \n--------+------------+-----+-----\n Moscow | 2021-06-01 |  22 |  34\n Berlin | 2021-07-01 |  63 |  93\n Moscow | 2021-07-01 |   1 |  31\n Berlin | 2021-08-01 |  94 | 124\n Moscow | 2021-08-01 |  32 |  62\n\n-- Make sure the invalidation threshold has changed\nSELECT _timescaledb_functions.to_timestamp(watermark) at time zone 'UTC'\nFROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nWHERE hypertable_id = :ht_id;\n         timezone         \n--------------------------\n Wed Sep 01 00:00:00 2021\n\n-- Make sure the catalog is cleaned up when the cagg is dropped\nDROP MATERIALIZED VIEW conditions_summary;\nNOTICE:  drop cascades to 3 other objects\nSELECT count(*) FROM _timescaledb_catalog.continuous_agg\nWHERE mat_hypertable_id = :cagg_id;\n count \n-------\n     0\n\nSELECT count(*) FROM _timescaledb_catalog.continuous_aggs_bucket_function\nWHERE mat_hypertable_id = :cagg_id;\n count \n-------\n     0\n\n-- Re-create cagg, this time WITH DATA\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions_summary\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT city,\n   timescaledb_experimental.time_bucket_ng('1 month', day) AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions\nGROUP BY city, bucket;\nNOTICE:  refreshing continuous aggregate \"conditions_summary\"\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\n-- Make sure cagg was filled\nSELECT city, to_char(bucket, 'YYYY-MM-DD') AS month, min, max\nFROM conditions_summary\nORDER by month, city;\n  city  |   month    | min | max \n--------+------------+-----+-----\n Moscow | 2021-06-01 |  22 |  34\n Berlin | 2021-07-01 |  63 |  93\n Moscow | 2021-07-01 |   1 |  31\n Berlin | 2021-08-01 |  94 | 124\n Moscow | 2021-08-01 |  32 |  62\n\n-- Check the invalidation.\n-- Step 1/2. Insert some more data , do a refresh and make sure that the\n--           invalidation log is empty.\nINSERT INTO conditions (day, city, temperature)\nSELECT ts :: date, city, row_number() OVER ()\nFROM generate_series('2021-09-01' :: date, '2021-09-15', '1 day') as ts,\n     unnest(array['Moscow', 'Berlin']) as city;\nCALL refresh_continuous_aggregate('conditions_summary', '2021-09-01', '2021-10-01');\nSELECT\n    _timescaledb_functions.to_timestamp(lowest_modified_value) AS lowest,\n    _timescaledb_functions.to_timestamp(greatest_modified_value) AS greatest\nFROM _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log\nWHERE hypertable_id = :ht_id;\n lowest | greatest \n--------+----------\n\nSELECT city, to_char(bucket, 'YYYY-MM-DD') AS month, min, max\nFROM conditions_summary\nORDER by month, city;\n  city  |   month    | min | max \n--------+------------+-----+-----\n Moscow | 2021-06-01 |  22 |  34\n Berlin | 2021-07-01 |  63 |  93\n Moscow | 2021-07-01 |   1 |  31\n Berlin | 2021-08-01 |  94 | 124\n Moscow | 2021-08-01 |  32 |  62\n Berlin | 2021-09-01 |  16 |  30\n Moscow | 2021-09-01 |   1 |  15\n\n-- Step 2/2. Add more data below the invalidation threshold, make sure that the\n--           invalidation log is not empty, that do a refresh.\nINSERT INTO conditions (day, city, temperature)\nSELECT ts :: date, city, (CASE WHEN city = 'Moscow' THEN -40 ELSE 40 END)\nFROM generate_series('2021-09-16' :: date, '2021-09-30', '1 day') as ts,\n     unnest(array['Moscow', 'Berlin']) as city;\nSELECT\n    _timescaledb_functions.to_timestamp(lowest_modified_value) at time zone 'UTC' AS lowest,\n    _timescaledb_functions.to_timestamp(greatest_modified_value) at time zone 'UTC' AS greatest\nFROM _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log\nWHERE hypertable_id = :ht_id;\n          lowest          |         greatest         \n--------------------------+--------------------------\n Thu Sep 16 00:00:00 2021 | Thu Sep 16 00:00:00 2021\n Fri Sep 17 00:00:00 2021 | Fri Sep 17 00:00:00 2021\n Sat Sep 18 00:00:00 2021 | Sat Sep 18 00:00:00 2021\n Sun Sep 19 00:00:00 2021 | Sun Sep 19 00:00:00 2021\n Mon Sep 20 00:00:00 2021 | Mon Sep 20 00:00:00 2021\n Tue Sep 21 00:00:00 2021 | Tue Sep 21 00:00:00 2021\n Wed Sep 22 00:00:00 2021 | Wed Sep 22 00:00:00 2021\n Thu Sep 23 00:00:00 2021 | Thu Sep 23 00:00:00 2021\n Fri Sep 24 00:00:00 2021 | Fri Sep 24 00:00:00 2021\n Sat Sep 25 00:00:00 2021 | Sat Sep 25 00:00:00 2021\n Sun Sep 26 00:00:00 2021 | Sun Sep 26 00:00:00 2021\n Mon Sep 27 00:00:00 2021 | Mon Sep 27 00:00:00 2021\n Tue Sep 28 00:00:00 2021 | Tue Sep 28 00:00:00 2021\n Wed Sep 29 00:00:00 2021 | Wed Sep 29 00:00:00 2021\n Thu Sep 30 00:00:00 2021 | Thu Sep 30 00:00:00 2021\n\nCALL refresh_continuous_aggregate('conditions_summary', '2021-09-01', '2021-10-01');\nSELECT city, to_char(bucket, 'YYYY-MM-DD') AS month, min, max\nFROM conditions_summary\nORDER by month, city;\n  city  |   month    | min | max \n--------+------------+-----+-----\n Moscow | 2021-06-01 |  22 |  34\n Berlin | 2021-07-01 |  63 |  93\n Moscow | 2021-07-01 |   1 |  31\n Berlin | 2021-08-01 |  94 | 124\n Moscow | 2021-08-01 |  32 |  62\n Berlin | 2021-09-01 |  16 |  40\n Moscow | 2021-09-01 | -40 |  15\n\nSELECT\n    _timescaledb_functions.to_timestamp(lowest_modified_value) AS lowest,\n    _timescaledb_functions.to_timestamp(greatest_modified_value) AS greatest\nFROM _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log\nWHERE hypertable_id = :ht_id;\n lowest | greatest \n--------+----------\n\n-- Create a real-time aggregate\nDROP MATERIALIZED VIEW conditions_summary;\nNOTICE:  drop cascades to 4 other objects\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions_summary\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT city,\n   timescaledb_experimental.time_bucket_ng('1 month', day) AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions\nGROUP BY city, bucket;\nNOTICE:  refreshing continuous aggregate \"conditions_summary\"\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nSELECT city, to_char(bucket, 'YYYY-MM-DD') AS month, min, max\nFROM conditions_summary\nORDER by month, city;\n  city  |   month    | min | max \n--------+------------+-----+-----\n Moscow | 2021-06-01 |  22 |  34\n Berlin | 2021-07-01 |  63 |  93\n Moscow | 2021-07-01 |   1 |  31\n Berlin | 2021-08-01 |  94 | 124\n Moscow | 2021-08-01 |  32 |  62\n Berlin | 2021-09-01 |  16 |  40\n Moscow | 2021-09-01 | -40 |  15\n\n-- Add some data to the hypertable and make sure they are visible in the cagg\nINSERT INTO conditions (day, city, temperature) VALUES\n  ('2021-10-01', 'Moscow', 1),\n  ('2021-10-02', 'Moscow', 2),\n  ('2021-10-03', 'Moscow', 3),\n  ('2021-10-04', 'Moscow', 4),\n  ('2021-10-01', 'Berlin', 5),\n  ('2021-10-02', 'Berlin', 6),\n  ('2021-10-03', 'Berlin', 7),\n  ('2021-10-04', 'Berlin', 8);\nSELECT city, to_char(bucket, 'YYYY-MM-DD') AS month, min, max\nFROM conditions_summary\nORDER by month, city;\n  city  |   month    | min | max \n--------+------------+-----+-----\n Moscow | 2021-06-01 |  22 |  34\n Berlin | 2021-07-01 |  63 |  93\n Moscow | 2021-07-01 |   1 |  31\n Berlin | 2021-08-01 |  94 | 124\n Moscow | 2021-08-01 |  32 |  62\n Berlin | 2021-09-01 |  16 |  40\n Moscow | 2021-09-01 | -40 |  15\n Berlin | 2021-10-01 |   5 |   8\n Moscow | 2021-10-01 |   1 |   4\n\n-- Refresh the cagg and make sure that the result of SELECT query didn't change\nCALL refresh_continuous_aggregate('conditions_summary', '2021-10-01', '2021-11-01');\nSELECT city, to_char(bucket, 'YYYY-MM-DD') AS month, min, max\nFROM conditions_summary\nORDER by month, city;\n  city  |   month    | min | max \n--------+------------+-----+-----\n Moscow | 2021-06-01 |  22 |  34\n Berlin | 2021-07-01 |  63 |  93\n Moscow | 2021-07-01 |   1 |  31\n Berlin | 2021-08-01 |  94 | 124\n Moscow | 2021-08-01 |  32 |  62\n Berlin | 2021-09-01 |  16 |  40\n Moscow | 2021-09-01 | -40 |  15\n Berlin | 2021-10-01 |   5 |   8\n Moscow | 2021-10-01 |   1 |   4\n\n-- Add some more data, enable compression, compress the chunks and repeat the test\nINSERT INTO conditions (day, city, temperature) VALUES\n  ('2021-11-01', 'Moscow', 11),\n  ('2021-11-02', 'Moscow', 12),\n  ('2021-11-03', 'Moscow', 13),\n  ('2021-11-04', 'Moscow', 14),\n  ('2021-11-01', 'Berlin', 15),\n  ('2021-11-02', 'Berlin', 16),\n  ('2021-11-03', 'Berlin', 17),\n  ('2021-11-04', 'Berlin', 18);\nALTER TABLE conditions SET (\n    timescaledb.compress,\n    timescaledb.compress_segmentby = 'city'\n);\nSELECT compress_chunk(ch) FROM show_chunks('conditions') AS ch;\n              compress_chunk              \n------------------------------------------\n _timescaledb_internal._hyper_1_1_chunk\n _timescaledb_internal._hyper_1_2_chunk\n _timescaledb_internal._hyper_1_3_chunk\n _timescaledb_internal._hyper_1_4_chunk\n _timescaledb_internal._hyper_1_5_chunk\n _timescaledb_internal._hyper_1_6_chunk\n _timescaledb_internal._hyper_1_7_chunk\n _timescaledb_internal._hyper_1_8_chunk\n _timescaledb_internal._hyper_1_9_chunk\n _timescaledb_internal._hyper_1_10_chunk\n _timescaledb_internal._hyper_1_11_chunk\n _timescaledb_internal._hyper_1_12_chunk\n _timescaledb_internal._hyper_1_13_chunk\n _timescaledb_internal._hyper_1_14_chunk\n _timescaledb_internal._hyper_1_16_chunk\n _timescaledb_internal._hyper_1_17_chunk\n _timescaledb_internal._hyper_1_18_chunk\n _timescaledb_internal._hyper_1_19_chunk\n _timescaledb_internal._hyper_1_20_chunk\n _timescaledb_internal._hyper_1_21_chunk\n _timescaledb_internal._hyper_1_22_chunk\n _timescaledb_internal._hyper_1_23_chunk\n _timescaledb_internal._hyper_1_24_chunk\n _timescaledb_internal._hyper_1_25_chunk\n _timescaledb_internal._hyper_1_26_chunk\n _timescaledb_internal._hyper_1_27_chunk\n _timescaledb_internal._hyper_1_28_chunk\n _timescaledb_internal._hyper_1_29_chunk\n _timescaledb_internal._hyper_1_30_chunk\n _timescaledb_internal._hyper_1_31_chunk\n _timescaledb_internal._hyper_1_32_chunk\n _timescaledb_internal._hyper_1_33_chunk\n _timescaledb_internal._hyper_1_34_chunk\n _timescaledb_internal._hyper_1_35_chunk\n _timescaledb_internal._hyper_1_36_chunk\n _timescaledb_internal._hyper_1_37_chunk\n _timescaledb_internal._hyper_1_38_chunk\n _timescaledb_internal._hyper_1_39_chunk\n _timescaledb_internal._hyper_1_40_chunk\n _timescaledb_internal._hyper_1_41_chunk\n _timescaledb_internal._hyper_1_42_chunk\n _timescaledb_internal._hyper_1_43_chunk\n _timescaledb_internal._hyper_1_44_chunk\n _timescaledb_internal._hyper_1_45_chunk\n _timescaledb_internal._hyper_1_46_chunk\n _timescaledb_internal._hyper_1_47_chunk\n _timescaledb_internal._hyper_1_48_chunk\n _timescaledb_internal._hyper_1_49_chunk\n _timescaledb_internal._hyper_1_50_chunk\n _timescaledb_internal._hyper_1_51_chunk\n _timescaledb_internal._hyper_1_52_chunk\n _timescaledb_internal._hyper_1_53_chunk\n _timescaledb_internal._hyper_1_54_chunk\n _timescaledb_internal._hyper_1_55_chunk\n _timescaledb_internal._hyper_1_56_chunk\n _timescaledb_internal._hyper_1_57_chunk\n _timescaledb_internal._hyper_1_58_chunk\n _timescaledb_internal._hyper_1_59_chunk\n _timescaledb_internal._hyper_1_60_chunk\n _timescaledb_internal._hyper_1_61_chunk\n _timescaledb_internal._hyper_1_62_chunk\n _timescaledb_internal._hyper_1_63_chunk\n _timescaledb_internal._hyper_1_64_chunk\n _timescaledb_internal._hyper_1_65_chunk\n _timescaledb_internal._hyper_1_66_chunk\n _timescaledb_internal._hyper_1_67_chunk\n _timescaledb_internal._hyper_1_68_chunk\n _timescaledb_internal._hyper_1_69_chunk\n _timescaledb_internal._hyper_1_70_chunk\n _timescaledb_internal._hyper_1_71_chunk\n _timescaledb_internal._hyper_1_72_chunk\n _timescaledb_internal._hyper_1_73_chunk\n _timescaledb_internal._hyper_1_74_chunk\n _timescaledb_internal._hyper_1_75_chunk\n _timescaledb_internal._hyper_1_76_chunk\n _timescaledb_internal._hyper_1_77_chunk\n _timescaledb_internal._hyper_1_83_chunk\n _timescaledb_internal._hyper_1_84_chunk\n _timescaledb_internal._hyper_1_85_chunk\n _timescaledb_internal._hyper_1_86_chunk\n _timescaledb_internal._hyper_1_87_chunk\n _timescaledb_internal._hyper_1_88_chunk\n _timescaledb_internal._hyper_1_89_chunk\n _timescaledb_internal._hyper_1_90_chunk\n _timescaledb_internal._hyper_1_91_chunk\n _timescaledb_internal._hyper_1_92_chunk\n _timescaledb_internal._hyper_1_93_chunk\n _timescaledb_internal._hyper_1_94_chunk\n _timescaledb_internal._hyper_1_95_chunk\n _timescaledb_internal._hyper_1_96_chunk\n _timescaledb_internal._hyper_1_97_chunk\n _timescaledb_internal._hyper_1_99_chunk\n _timescaledb_internal._hyper_1_100_chunk\n _timescaledb_internal._hyper_1_101_chunk\n _timescaledb_internal._hyper_1_102_chunk\n _timescaledb_internal._hyper_1_103_chunk\n _timescaledb_internal._hyper_1_104_chunk\n _timescaledb_internal._hyper_1_105_chunk\n _timescaledb_internal._hyper_1_106_chunk\n _timescaledb_internal._hyper_1_107_chunk\n _timescaledb_internal._hyper_1_108_chunk\n _timescaledb_internal._hyper_1_109_chunk\n _timescaledb_internal._hyper_1_110_chunk\n _timescaledb_internal._hyper_1_111_chunk\n _timescaledb_internal._hyper_1_112_chunk\n _timescaledb_internal._hyper_1_113_chunk\n _timescaledb_internal._hyper_1_118_chunk\n _timescaledb_internal._hyper_1_119_chunk\n _timescaledb_internal._hyper_1_120_chunk\n _timescaledb_internal._hyper_1_121_chunk\n _timescaledb_internal._hyper_1_123_chunk\n _timescaledb_internal._hyper_1_124_chunk\n _timescaledb_internal._hyper_1_125_chunk\n _timescaledb_internal._hyper_1_126_chunk\n\n-- Data for 2021-11 is seen because the cagg is real-time\nSELECT city, to_char(bucket, 'YYYY-MM-DD') AS month, min, max\nFROM conditions_summary\nORDER by month, city;\n  city  |   month    | min | max \n--------+------------+-----+-----\n Moscow | 2021-06-01 |  22 |  34\n Berlin | 2021-07-01 |  63 |  93\n Moscow | 2021-07-01 |   1 |  31\n Berlin | 2021-08-01 |  94 | 124\n Moscow | 2021-08-01 |  32 |  62\n Berlin | 2021-09-01 |  16 |  40\n Moscow | 2021-09-01 | -40 |  15\n Berlin | 2021-10-01 |   5 |   8\n Moscow | 2021-10-01 |   1 |   4\n Berlin | 2021-11-01 |  15 |  18\n Moscow | 2021-11-01 |  11 |  14\n\nCALL refresh_continuous_aggregate('conditions_summary', '2021-11-01', '2021-12-01');\n-- Data for 2021-11 is seen because the cagg was refreshed\nSELECT city, to_char(bucket, 'YYYY-MM-DD') AS month, min, max\nFROM conditions_summary\nORDER by month, city;\n  city  |   month    | min | max \n--------+------------+-----+-----\n Moscow | 2021-06-01 |  22 |  34\n Berlin | 2021-07-01 |  63 |  93\n Moscow | 2021-07-01 |   1 |  31\n Berlin | 2021-08-01 |  94 | 124\n Moscow | 2021-08-01 |  32 |  62\n Berlin | 2021-09-01 |  16 |  40\n Moscow | 2021-09-01 | -40 |  15\n Berlin | 2021-10-01 |   5 |   8\n Moscow | 2021-10-01 |   1 |   4\n Berlin | 2021-11-01 |  15 |  18\n Moscow | 2021-11-01 |  11 |  14\n\n-- Test N-months buckets where N in 2,3,4,5,6,12,13 on a relatively large table\n-- This also tests the case when a single hypertable has multiple caggs.\nCREATE TABLE conditions_large(\n  day DATE NOT NULL,\n  temperature INT NOT NULL);\nSELECT create_hypertable(\n  'conditions_large', 'day',\n  chunk_time_interval => INTERVAL '1 month'\n);\n       create_hypertable       \n-------------------------------\n (6,public,conditions_large,t)\n\nINSERT INTO conditions_large(day, temperature)\nSELECT ts, date_part('month', ts)*100 + date_part('day', ts)\nFROM generate_series('2010-01-01' :: date, '2020-01-01' :: date - interval '1 day', '1 day') as ts;\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions_large_2m\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n   timescaledb_experimental.time_bucket_ng('2 months', day) AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions_large\nGROUP BY bucket;\nNOTICE:  refreshing continuous aggregate \"conditions_large_2m\"\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nSELECT * FROM conditions_large_2m ORDER BY bucket;\n   bucket   | min  | max  \n------------+------+------\n 01-01-2010 |  101 |  228\n 03-01-2010 |  301 |  430\n 05-01-2010 |  501 |  630\n 07-01-2010 |  701 |  831\n 09-01-2010 |  901 | 1031\n 11-01-2010 | 1101 | 1231\n 01-01-2011 |  101 |  228\n 03-01-2011 |  301 |  430\n 05-01-2011 |  501 |  630\n 07-01-2011 |  701 |  831\n 09-01-2011 |  901 | 1031\n 11-01-2011 | 1101 | 1231\n 01-01-2012 |  101 |  229\n 03-01-2012 |  301 |  430\n 05-01-2012 |  501 |  630\n 07-01-2012 |  701 |  831\n 09-01-2012 |  901 | 1031\n 11-01-2012 | 1101 | 1231\n 01-01-2013 |  101 |  228\n 03-01-2013 |  301 |  430\n 05-01-2013 |  501 |  630\n 07-01-2013 |  701 |  831\n 09-01-2013 |  901 | 1031\n 11-01-2013 | 1101 | 1231\n 01-01-2014 |  101 |  228\n 03-01-2014 |  301 |  430\n 05-01-2014 |  501 |  630\n 07-01-2014 |  701 |  831\n 09-01-2014 |  901 | 1031\n 11-01-2014 | 1101 | 1231\n 01-01-2015 |  101 |  228\n 03-01-2015 |  301 |  430\n 05-01-2015 |  501 |  630\n 07-01-2015 |  701 |  831\n 09-01-2015 |  901 | 1031\n 11-01-2015 | 1101 | 1231\n 01-01-2016 |  101 |  229\n 03-01-2016 |  301 |  430\n 05-01-2016 |  501 |  630\n 07-01-2016 |  701 |  831\n 09-01-2016 |  901 | 1031\n 11-01-2016 | 1101 | 1231\n 01-01-2017 |  101 |  228\n 03-01-2017 |  301 |  430\n 05-01-2017 |  501 |  630\n 07-01-2017 |  701 |  831\n 09-01-2017 |  901 | 1031\n 11-01-2017 | 1101 | 1231\n 01-01-2018 |  101 |  228\n 03-01-2018 |  301 |  430\n 05-01-2018 |  501 |  630\n 07-01-2018 |  701 |  831\n 09-01-2018 |  901 | 1031\n 11-01-2018 | 1101 | 1231\n 01-01-2019 |  101 |  228\n 03-01-2019 |  301 |  430\n 05-01-2019 |  501 |  630\n 07-01-2019 |  701 |  831\n 09-01-2019 |  901 | 1031\n 11-01-2019 | 1101 | 1231\n\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions_large_3m\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n   timescaledb_experimental.time_bucket_ng('3 months', day) AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions_large\nGROUP BY bucket;\nNOTICE:  refreshing continuous aggregate \"conditions_large_3m\"\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nSELECT * FROM conditions_large_3m ORDER BY bucket;\n   bucket   | min  | max  \n------------+------+------\n 01-01-2010 |  101 |  331\n 04-01-2010 |  401 |  630\n 07-01-2010 |  701 |  930\n 10-01-2010 | 1001 | 1231\n 01-01-2011 |  101 |  331\n 04-01-2011 |  401 |  630\n 07-01-2011 |  701 |  930\n 10-01-2011 | 1001 | 1231\n 01-01-2012 |  101 |  331\n 04-01-2012 |  401 |  630\n 07-01-2012 |  701 |  930\n 10-01-2012 | 1001 | 1231\n 01-01-2013 |  101 |  331\n 04-01-2013 |  401 |  630\n 07-01-2013 |  701 |  930\n 10-01-2013 | 1001 | 1231\n 01-01-2014 |  101 |  331\n 04-01-2014 |  401 |  630\n 07-01-2014 |  701 |  930\n 10-01-2014 | 1001 | 1231\n 01-01-2015 |  101 |  331\n 04-01-2015 |  401 |  630\n 07-01-2015 |  701 |  930\n 10-01-2015 | 1001 | 1231\n 01-01-2016 |  101 |  331\n 04-01-2016 |  401 |  630\n 07-01-2016 |  701 |  930\n 10-01-2016 | 1001 | 1231\n 01-01-2017 |  101 |  331\n 04-01-2017 |  401 |  630\n 07-01-2017 |  701 |  930\n 10-01-2017 | 1001 | 1231\n 01-01-2018 |  101 |  331\n 04-01-2018 |  401 |  630\n 07-01-2018 |  701 |  930\n 10-01-2018 | 1001 | 1231\n 01-01-2019 |  101 |  331\n 04-01-2019 |  401 |  630\n 07-01-2019 |  701 |  930\n 10-01-2019 | 1001 | 1231\n\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions_large_4m\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n   timescaledb_experimental.time_bucket_ng('4 months', day) AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions_large\nGROUP BY bucket;\nNOTICE:  refreshing continuous aggregate \"conditions_large_4m\"\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nSELECT * FROM conditions_large_4m ORDER BY bucket;\n   bucket   | min | max  \n------------+-----+------\n 01-01-2010 | 101 |  430\n 05-01-2010 | 501 |  831\n 09-01-2010 | 901 | 1231\n 01-01-2011 | 101 |  430\n 05-01-2011 | 501 |  831\n 09-01-2011 | 901 | 1231\n 01-01-2012 | 101 |  430\n 05-01-2012 | 501 |  831\n 09-01-2012 | 901 | 1231\n 01-01-2013 | 101 |  430\n 05-01-2013 | 501 |  831\n 09-01-2013 | 901 | 1231\n 01-01-2014 | 101 |  430\n 05-01-2014 | 501 |  831\n 09-01-2014 | 901 | 1231\n 01-01-2015 | 101 |  430\n 05-01-2015 | 501 |  831\n 09-01-2015 | 901 | 1231\n 01-01-2016 | 101 |  430\n 05-01-2016 | 501 |  831\n 09-01-2016 | 901 | 1231\n 01-01-2017 | 101 |  430\n 05-01-2017 | 501 |  831\n 09-01-2017 | 901 | 1231\n 01-01-2018 | 101 |  430\n 05-01-2018 | 501 |  831\n 09-01-2018 | 901 | 1231\n 01-01-2019 | 101 |  430\n 05-01-2019 | 501 |  831\n 09-01-2019 | 901 | 1231\n\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions_large_5m\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n   timescaledb_experimental.time_bucket_ng('5 months', day) AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions_large\nGROUP BY bucket;\nNOTICE:  refreshing continuous aggregate \"conditions_large_5m\"\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nSELECT * FROM conditions_large_5m ORDER BY bucket;\n   bucket   | min | max  \n------------+-----+------\n 01-01-2010 | 101 |  531\n 06-01-2010 | 601 | 1031\n 11-01-2010 | 101 | 1231\n 04-01-2011 | 401 |  831\n 09-01-2011 | 101 | 1231\n 02-01-2012 | 201 |  630\n 07-01-2012 | 701 | 1130\n 12-01-2012 | 101 | 1231\n 05-01-2013 | 501 |  930\n 10-01-2013 | 101 | 1231\n 03-01-2014 | 301 |  731\n 08-01-2014 | 801 | 1231\n 01-01-2015 | 101 |  531\n 06-01-2015 | 601 | 1031\n 11-01-2015 | 101 | 1231\n 04-01-2016 | 401 |  831\n 09-01-2016 | 101 | 1231\n 02-01-2017 | 201 |  630\n 07-01-2017 | 701 | 1130\n 12-01-2017 | 101 | 1231\n 05-01-2018 | 501 |  930\n 10-01-2018 | 101 | 1231\n 03-01-2019 | 301 |  731\n 08-01-2019 | 801 | 1231\n\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions_large_6m\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n   timescaledb_experimental.time_bucket_ng('6 months', day) AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions_large\nGROUP BY bucket;\nNOTICE:  refreshing continuous aggregate \"conditions_large_6m\"\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nSELECT * FROM conditions_large_6m ORDER BY bucket;\n   bucket   | min | max  \n------------+-----+------\n 01-01-2010 | 101 |  630\n 07-01-2010 | 701 | 1231\n 01-01-2011 | 101 |  630\n 07-01-2011 | 701 | 1231\n 01-01-2012 | 101 |  630\n 07-01-2012 | 701 | 1231\n 01-01-2013 | 101 |  630\n 07-01-2013 | 701 | 1231\n 01-01-2014 | 101 |  630\n 07-01-2014 | 701 | 1231\n 01-01-2015 | 101 |  630\n 07-01-2015 | 701 | 1231\n 01-01-2016 | 101 |  630\n 07-01-2016 | 701 | 1231\n 01-01-2017 | 101 |  630\n 07-01-2017 | 701 | 1231\n 01-01-2018 | 101 |  630\n 07-01-2018 | 701 | 1231\n 01-01-2019 | 101 |  630\n 07-01-2019 | 701 | 1231\n\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions_large_1y\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n   timescaledb_experimental.time_bucket_ng('1 year', day) AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions_large\nGROUP BY bucket;\nNOTICE:  refreshing continuous aggregate \"conditions_large_1y\"\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nSELECT * FROM conditions_large_1y ORDER BY bucket;\n   bucket   | min | max  \n------------+-----+------\n 01-01-2010 | 101 | 1231\n 01-01-2011 | 101 | 1231\n 01-01-2012 | 101 | 1231\n 01-01-2013 | 101 | 1231\n 01-01-2014 | 101 | 1231\n 01-01-2015 | 101 | 1231\n 01-01-2016 | 101 | 1231\n 01-01-2017 | 101 | 1231\n 01-01-2018 | 101 | 1231\n 01-01-2019 | 101 | 1231\n\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions_large_1y1m\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n   timescaledb_experimental.time_bucket_ng('1 year 1 month', day) AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions_large\nGROUP BY bucket;\nNOTICE:  refreshing continuous aggregate \"conditions_large_1y1m\"\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nSELECT * FROM conditions_large_1y1m ORDER BY bucket;\n   bucket   | min | max  \n------------+-----+------\n 10-01-2009 | 101 | 1031\n 11-01-2010 | 101 | 1231\n 12-01-2011 | 101 | 1231\n 01-01-2013 | 101 | 1231\n 02-01-2014 | 101 | 1231\n 03-01-2015 | 101 | 1231\n 04-01-2016 | 101 | 1231\n 05-01-2017 | 101 | 1231\n 06-01-2018 | 101 | 1231\n 07-01-2019 | 701 | 1231\n\n-- Trigger merged refresh to check corresponding code path as well\nDROP MATERIALIZED VIEW conditions_large_1y;\nNOTICE:  drop cascades to 10 other objects\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions_large_1y\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n   timescaledb_experimental.time_bucket_ng('1 year', day) AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions_large\nGROUP BY bucket;\nNOTICE:  refreshing continuous aggregate \"conditions_large_1y\"\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nSELECT * FROM conditions_large_1y ORDER BY bucket;\n   bucket   | min | max  \n------------+-----+------\n 01-01-2010 | 101 | 1231\n 01-01-2011 | 101 | 1231\n 01-01-2012 | 101 | 1231\n 01-01-2013 | 101 | 1231\n 01-01-2014 | 101 | 1231\n 01-01-2015 | 101 | 1231\n 01-01-2016 | 101 | 1231\n 01-01-2017 | 101 | 1231\n 01-01-2018 | 101 | 1231\n 01-01-2019 | 101 | 1231\n\nINSERT INTO conditions_large(day, temperature)\nSELECT ts, date_part('month', ts)*100 + date_part('day', ts)\nFROM generate_series('2020-01-01' :: date, '2021-01-01' :: date - interval '1 day', '1 day') as ts;\nCALL refresh_continuous_aggregate('conditions_large_1y', '2020-01-01', '2021-01-01');\nSELECT * FROM conditions_large_1y ORDER BY bucket;\n   bucket   | min | max  \n------------+-----+------\n 01-01-2010 | 101 | 1231\n 01-01-2011 | 101 | 1231\n 01-01-2012 | 101 | 1231\n 01-01-2013 | 101 | 1231\n 01-01-2014 | 101 | 1231\n 01-01-2015 | 101 | 1231\n 01-01-2016 | 101 | 1231\n 01-01-2017 | 101 | 1231\n 01-01-2018 | 101 | 1231\n 01-01-2019 | 101 | 1231\n 01-01-2020 | 101 | 1231\n\n-- Test the specific code path of creating a CAGG on top of empty hypertable.\nCREATE TABLE conditions_empty(\n  day DATE NOT NULL,\n  city text NOT NULL,\n  temperature INT NOT NULL);\nSELECT create_hypertable(\n  'conditions_empty', 'day',\n  chunk_time_interval => INTERVAL '1 day'\n);\n       create_hypertable        \n--------------------------------\n (15,public,conditions_empty,t)\n\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions_summary_empty\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT city,\n   timescaledb_experimental.time_bucket_ng('1 month', day) AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions_empty\nGROUP BY city, bucket;\nNOTICE:  continuous aggregate \"conditions_summary_empty\" is already up-to-date\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nSELECT city, to_char(bucket, 'YYYY-MM-DD') AS month, min, max\nFROM conditions_summary_empty\nORDER by month, city;\n city | month | min | max \n------+-------+-----+-----\n\n-- The test above changes the record that gets added to the invalidation log\n-- for an empty table. Make sure it doesn't have any unintended side-effects\n-- and the refreshing works as expected.\nINSERT INTO conditions_empty (day, city, temperature) VALUES\n  ('2021-06-14', 'Moscow', 26),\n  ('2021-06-15', 'Moscow', 22),\n  ('2021-06-16', 'Moscow', 24),\n  ('2021-06-17', 'Moscow', 24),\n  ('2021-06-18', 'Moscow', 27),\n  ('2021-06-19', 'Moscow', 28),\n  ('2021-06-20', 'Moscow', 30),\n  ('2021-06-21', 'Moscow', 31),\n  ('2021-06-22', 'Moscow', 34),\n  ('2021-06-23', 'Moscow', 34),\n  ('2021-06-24', 'Moscow', 34),\n  ('2021-06-25', 'Moscow', 32),\n  ('2021-06-26', 'Moscow', 32),\n  ('2021-06-27', 'Moscow', 31);\nCALL refresh_continuous_aggregate('conditions_summary_empty', '2021-06-01', '2021-07-01');\nSELECT city, to_char(bucket, 'YYYY-MM-DD') AS month, min, max\nFROM conditions_summary_empty\nORDER by month, city;\n  city  |   month    | min | max \n--------+------------+-----+-----\n Moscow | 2021-06-01 |  22 |  34\n\n-- Make sure add_continuous_aggregate_policy() works\nCREATE TABLE conditions_policy(\n  day DATE NOT NULL,\n  city text NOT NULL,\n  temperature INT NOT NULL);\nSELECT create_hypertable(\n  'conditions_policy', 'day',\n  chunk_time_interval => INTERVAL '1 day'\n);\n        create_hypertable        \n---------------------------------\n (17,public,conditions_policy,t)\n\nINSERT INTO conditions_policy (day, city, temperature) VALUES\n  ('2021-06-14', 'Moscow', 26),\n  ('2021-06-15', 'Moscow', 22),\n  ('2021-06-16', 'Moscow', 24),\n  ('2021-06-17', 'Moscow', 24),\n  ('2021-06-18', 'Moscow', 27),\n  ('2021-06-19', 'Moscow', 28),\n  ('2021-06-20', 'Moscow', 30),\n  ('2021-06-21', 'Moscow', 31),\n  ('2021-06-22', 'Moscow', 34),\n  ('2021-06-23', 'Moscow', 34),\n  ('2021-06-24', 'Moscow', 34),\n  ('2021-06-25', 'Moscow', 32),\n  ('2021-06-26', 'Moscow', 32),\n  ('2021-06-27', 'Moscow', 31);\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions_summary_policy\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT city,\n   timescaledb_experimental.time_bucket_ng('1 month', day) AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions_policy\nGROUP BY city, bucket;\nNOTICE:  refreshing continuous aggregate \"conditions_summary_policy\"\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nSELECT * FROM conditions_summary_policy;\n  city  |   bucket   | min | max \n--------+------------+-----+-----\n Moscow | 06-01-2021 |  22 |  34\n\n\\set ON_ERROR_STOP 0\n-- Check for \"policy refresh window too small\" error\nSELECT add_continuous_aggregate_policy('conditions_summary_policy',\n    -- Historically, 1 month is just a synonym to 30 days here.\n    -- See interval_to_int64() and interval_to_int128().\n    start_offset => INTERVAL '2 months',\n    end_offset => INTERVAL '1 day',\n    schedule_interval => INTERVAL '1 hour');\nERROR:  policy refresh window too small\n\\set ON_ERROR_STOP 1\nSELECT add_continuous_aggregate_policy('conditions_summary_policy',\n    start_offset => INTERVAL '65 days',\n    end_offset => INTERVAL '1 day',\n    schedule_interval => INTERVAL '1 hour') AS job_id \\gset\nSELECT delete_job(:job_id);\n delete_job \n------------\n \n\nSELECT add_continuous_aggregate_policy('conditions_summary_policy',\n    start_offset => INTERVAL '2 months',\n    end_offset => INTERVAL '0 months',\n    schedule_interval => INTERVAL '1 hour') AS job_id \\gset\nSELECT delete_job(:job_id);\n delete_job \n------------\n \n\n\\set ON_ERROR_STOP 0\nSELECT add_continuous_aggregate_policy('conditions_summary_policy',\n    start_offset => INTERVAL '2 months',\n    end_offset => INTERVAL '1 months',\n    schedule_interval => INTERVAL '1 hour');\nERROR:  policy refresh window too small\n\\set ON_ERROR_STOP 1\nSELECT add_continuous_aggregate_policy('conditions_summary_policy',\n    start_offset => INTERVAL '3 months',\n    end_offset => INTERVAL '1 months',\n    schedule_interval => INTERVAL '1 hour');\n add_continuous_aggregate_policy \n---------------------------------\n                            1002\n\n"
  },
  {
    "path": "tsl/test/expected/cagg_exp_next_gen.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- Make sure experimental immutable function with 2 arguments can be used in caggs.\n-- Functions with 3 arguments and/or stable functions are currently not supported in caggs.\nCREATE TABLE conditions(\n  day DATE NOT NULL,\n  city text NOT NULL,\n  temperature INT NOT NULL);\nSELECT create_hypertable(\n  'conditions', 'day',\n  chunk_time_interval => INTERVAL '1 day'\n);\n    create_hypertable    \n-------------------------\n (1,public,conditions,t)\n\nINSERT INTO conditions (day, city, temperature) VALUES\n  ('2021-06-14', 'Moscow', 26),\n  ('2021-06-15', 'Moscow', 22),\n  ('2021-06-16', 'Moscow', 24),\n  ('2021-06-17', 'Moscow', 24),\n  ('2021-06-18', 'Moscow', 27),\n  ('2021-06-19', 'Moscow', 28),\n  ('2021-06-20', 'Moscow', 30),\n  ('2021-06-21', 'Moscow', 31),\n  ('2021-06-22', 'Moscow', 34),\n  ('2021-06-23', 'Moscow', 34),\n  ('2021-06-24', 'Moscow', 34),\n  ('2021-06-25', 'Moscow', 32),\n  ('2021-06-26', 'Moscow', 32),\n  ('2021-06-27', 'Moscow', 31);\n-- timebucket_ng is deprecated and can not be used in new CAggs anymore.\n-- However, using this GUC the restriction can be lifted in debug builds\n-- to ensure the functionality can be tested.\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions_summary_weekly\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT city,\n       timescaledb_experimental.time_bucket_ng('7 days', day) AS bucket,\n       MIN(temperature),\n       MAX(temperature)\nFROM conditions\nGROUP BY city, bucket;\nNOTICE:  refreshing continuous aggregate \"conditions_summary_weekly\"\n-- Reset GUC to check if the CAgg would also work in release builds\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nSELECT to_char(bucket, 'YYYY-MM-DD'), city, min, max\nFROM conditions_summary_weekly\nORDER BY bucket;\n  to_char   |  city  | min | max \n------------+--------+-----+-----\n 2021-06-12 | Moscow |  22 |  27\n 2021-06-19 | Moscow |  28 |  34\n 2021-06-26 | Moscow |  31 |  32\n\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 3 other objects\nNOTICE:  drop cascades to 2 other objects\n-- Make sure seconds, minutes, and hours can be used with caggs ('origin' is not\n-- currently supported in caggs).\nCREATE TABLE conditions(\n  tstamp TIMESTAMP NOT NULL,\n  city text NOT NULL,\n  temperature INT NOT NULL);\nSELECT create_hypertable(\n  'conditions', 'tstamp',\n  chunk_time_interval => INTERVAL '1 day'\n);\nWARNING:  column type \"timestamp without time zone\" used for \"tstamp\" does not follow best practices\n    create_hypertable    \n-------------------------\n (3,public,conditions,t)\n\nINSERT INTO conditions (tstamp, city, temperature) VALUES\n  ('2021-06-14 12:30:00', 'Moscow', 26),\n  ('2021-06-14 12:30:10', 'Moscow', 22),\n  ('2021-06-14 12:30:20', 'Moscow', 24),\n  ('2021-06-14 12:30:30', 'Moscow', 24),\n  ('2021-06-14 12:30:40', 'Moscow', 27),\n  ('2021-06-14 12:30:50', 'Moscow', 28),\n  ('2021-06-14 12:31:10', 'Moscow', 30),\n  ('2021-06-14 12:31:20', 'Moscow', 31),\n  ('2021-06-14 12:31:30', 'Moscow', 34),\n  ('2021-06-14 12:31:40', 'Moscow', 34),\n  ('2021-06-14 12:31:50', 'Moscow', 34),\n  ('2021-06-14 12:32:00', 'Moscow', 32),\n  ('2021-06-14 12:32:10', 'Moscow', 32),\n  ('2021-06-14 12:32:20', 'Moscow', 31);\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions_summary_30sec\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT city,\n       timescaledb_experimental.time_bucket_ng('30 seconds', tstamp) AS bucket,\n       MIN(temperature),\n       MAX(temperature)\nFROM conditions\nGROUP BY city, bucket;\nNOTICE:  refreshing continuous aggregate \"conditions_summary_30sec\"\nCREATE MATERIALIZED VIEW conditions_summary_1min\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT city,\n       timescaledb_experimental.time_bucket_ng('1 minute', tstamp) AS bucket,\n       MIN(temperature),\n       MAX(temperature)\nFROM conditions\nGROUP BY city, bucket;\nNOTICE:  refreshing continuous aggregate \"conditions_summary_1min\"\nCREATE MATERIALIZED VIEW conditions_summary_1hour\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT city,\n       timescaledb_experimental.time_bucket_ng('1 hour', tstamp) AS bucket,\n       MIN(temperature),\n       MAX(temperature)\nFROM conditions\nGROUP BY city, bucket;\nNOTICE:  refreshing continuous aggregate \"conditions_summary_1hour\"\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nSELECT city, to_char(bucket, 'YYYY-MM-DD HH:mi:ss'), min, max FROM conditions_summary_30sec ORDER BY bucket;\n  city  |       to_char       | min | max \n--------+---------------------+-----+-----\n Moscow | 2021-06-14 12:30:00 |  22 |  26\n Moscow | 2021-06-14 12:30:30 |  24 |  28\n Moscow | 2021-06-14 12:31:00 |  30 |  31\n Moscow | 2021-06-14 12:31:30 |  34 |  34\n Moscow | 2021-06-14 12:32:00 |  31 |  32\n\nSELECT city, to_char(bucket, 'YYYY-MM-DD HH:mi:ss'), min, max FROM conditions_summary_1min ORDER BY bucket;\n  city  |       to_char       | min | max \n--------+---------------------+-----+-----\n Moscow | 2021-06-14 12:30:00 |  22 |  28\n Moscow | 2021-06-14 12:31:00 |  30 |  34\n Moscow | 2021-06-14 12:32:00 |  31 |  32\n\nSELECT city, to_char(bucket, 'YYYY-MM-DD HH:mi:ss'), min, max FROM conditions_summary_1hour ORDER BY bucket;\n  city  |       to_char       | min | max \n--------+---------------------+-----+-----\n Moscow | 2021-06-14 12:00:00 |  22 |  34\n\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 9 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_4_18_chunk\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_5_19_chunk\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_6_20_chunk\n-- Experimental functions using different schema for installation than PUBLIC\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n\\set TEST_DBNAME_2 :TEST_DBNAME _2\nCREATE DATABASE :TEST_DBNAME_2;\n\\c :TEST_DBNAME_2 :ROLE_SUPERUSER\nCREATE SCHEMA test1;\nSET client_min_messages TO ERROR;\nCREATE EXTENSION timescaledb SCHEMA test1;\nCREATE TABLE conditions(\n  tstamp TIMESTAMP NOT NULL,\n  city text NOT NULL,\n  temperature INT NOT NULL);\nSELECT test1.create_hypertable(\n  'conditions', 'tstamp',\n  chunk_time_interval => INTERVAL '1 day'\n);\n    create_hypertable    \n-------------------------\n (1,public,conditions,t)\n\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions_summary_monthly\nWITH (timescaledb.continuous) AS\nSELECT city,\n       timescaledb_experimental.time_bucket_ng('1 month', tstamp) AS bucket,\n       MIN(temperature),\n       MAX(temperature)\nFROM conditions\nGROUP BY city, bucket\nWITH NO DATA;\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nCREATE MATERIALIZED VIEW conditions_summary_yearly\nWITH (timescaledb.continuous) AS\nSELECT city,\n       test1.time_bucket('1 year', tstamp) AS bucket,\n       MIN(temperature),\n       MAX(temperature)\nFROM conditions\nGROUP BY city, bucket\nWITH NO DATA;\nSELECT bucket_func, bucket_width, bucket_origin, bucket_timezone, bucket_fixed_width\nFROM _timescaledb_catalog.continuous_aggs_bucket_function ORDER BY 1;\n                                  bucket_func                                  | bucket_width | bucket_origin | bucket_timezone | bucket_fixed_width \n-------------------------------------------------------------------------------+--------------+---------------+-----------------+--------------------\n test1.time_bucket(interval,timestamp without time zone)                       | @ 1 year     |               |                 | f\n timescaledb_experimental.time_bucket_ng(interval,timestamp without time zone) | @ 1 mon      |               |                 | f\n\n-- Try to toggle realtime feature on existing CAgg using timescaledb_experimental.time_bucket_ng\nALTER MATERIALIZED VIEW conditions_summary_monthly SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW conditions_summary_monthly SET (timescaledb.materialized_only=true);\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nDROP DATABASE :TEST_DBNAME_2 WITH (FORCE);\n"
  },
  {
    "path": "tsl/test/expected/cagg_exp_origin.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\nCREATE TABLE conditions(\n  day DATE NOT NULL,\n  city text NOT NULL,\n  temperature INT NOT NULL);\nSELECT create_hypertable(\n  'conditions', 'day',\n  chunk_time_interval => INTERVAL '1 day'\n);\n    create_hypertable    \n-------------------------\n (1,public,conditions,t)\n\nINSERT INTO conditions (day, city, temperature) VALUES\n  ('2021-06-14', 'Moscow', 26),\n  ('2021-06-15', 'Moscow', 22),\n  ('2021-06-16', 'Moscow', 24),\n  ('2021-06-17', 'Moscow', 24),\n  ('2021-06-18', 'Moscow', 27),\n  ('2021-06-19', 'Moscow', 28),\n  ('2021-06-20', 'Moscow', 30),\n  ('2021-06-21', 'Moscow', 31),\n  ('2021-06-22', 'Moscow', 34),\n  ('2021-06-23', 'Moscow', 34),\n  ('2021-06-24', 'Moscow', 34),\n  ('2021-06-25', 'Moscow', 32),\n  ('2021-06-26', 'Moscow', 32),\n  ('2021-06-27', 'Moscow', 31);\n\\set ON_ERROR_STOP 0\n-- timebucket_ng is deprecated and can not be used in new CAggs anymore.\n-- However, using this GUC the restriction can be lifted in debug builds\n-- to ensure the functionality can be tested.\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\n-- Make sure 'infinity' can't be specified as an origin\nCREATE MATERIALIZED VIEW conditions_summary_weekly\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT city,\n       timescaledb_experimental.time_bucket_ng('7 days', day, 'infinity' :: date) AS bucket,\n       MIN(temperature),\n       MAX(temperature)\nFROM conditions\nGROUP BY city, bucket\nWITH NO DATA;\nERROR:  invalid origin value: infinity\n-- Make sure buckets like '1 months 15 days\" (fixed+variable-sized) are not allowed\nCREATE MATERIALIZED VIEW conditions_summary_weekly\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT city,\n       timescaledb_experimental.time_bucket_ng('1 month 15 days', day, '2021-06-01') AS bucket,\n       MIN(temperature),\n       MAX(temperature)\nFROM conditions\nGROUP BY city, bucket\nWITH NO DATA;\nERROR:  invalid interval specified\n\\set ON_ERROR_STOP 1\nCREATE MATERIALIZED VIEW conditions_summary_weekly\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT city,\n       timescaledb_experimental.time_bucket_ng('7 days', day, '2000-01-03' :: date) AS bucket,\n       MIN(temperature),\n       MAX(temperature)\nFROM conditions\nGROUP BY city, bucket\nWITH NO DATA;\n-- Reset GUC to check if the CAgg would also work in release builds\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nSELECT to_char(bucket, 'YYYY-MM-DD'), city, min, max\nFROM conditions_summary_weekly\nORDER BY bucket;\n to_char | city | min | max \n---------+------+-----+-----\n\nSELECT mat_hypertable_id AS cagg_id, raw_hypertable_id AS ht_id\nFROM _timescaledb_catalog.continuous_agg\nWHERE user_view_name = 'conditions_summary_weekly'\n\\gset\n-- Make sure truncating of the refresh window works\n\\set ON_ERROR_STOP 0\nCALL refresh_continuous_aggregate('conditions_summary_weekly', '2021-06-14', '2021-06-20');\nERROR:  refresh window too small\n\\set ON_ERROR_STOP 1\n-- Make sure refreshing works\nCALL refresh_continuous_aggregate('conditions_summary_weekly', '2021-06-14', '2021-06-21');\nSELECT city, to_char(bucket, 'YYYY-MM-DD') AS week, min, max\nFROM conditions_summary_weekly\nORDER BY week, city;\n  city  |    week    | min | max \n--------+------------+-----+-----\n Moscow | 2021-06-14 |  22 |  30\n\n-- Check the invalidation threshold\nSELECT _timescaledb_functions.to_timestamp(watermark) at time zone 'UTC'\nFROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nWHERE hypertable_id = :ht_id;\n         timezone         \n--------------------------\n Mon Jun 21 00:00:00 2021\n\n-- Add some dummy data for two more weeks and call refresh (no invalidations test case)\nINSERT INTO conditions (day, city, temperature)\nSELECT ts :: date, city, row_number() OVER ()\nFROM generate_series('2021-06-28' :: date, '2021-07-11', '1 day') as ts,\n     unnest(array['Moscow', 'Berlin']) as city;\n-- Double check generated data\nSELECT to_char(day, 'YYYY-MM-DD'), city, temperature\nFROM conditions\nWHERE day >= '2021-06-28'\nORDER BY city DESC, day;\n  to_char   |  city  | temperature \n------------+--------+-------------\n 2021-06-28 | Moscow |           1\n 2021-06-29 | Moscow |           2\n 2021-06-30 | Moscow |           3\n 2021-07-01 | Moscow |           4\n 2021-07-02 | Moscow |           5\n 2021-07-03 | Moscow |           6\n 2021-07-04 | Moscow |           7\n 2021-07-05 | Moscow |           8\n 2021-07-06 | Moscow |           9\n 2021-07-07 | Moscow |          10\n 2021-07-08 | Moscow |          11\n 2021-07-09 | Moscow |          12\n 2021-07-10 | Moscow |          13\n 2021-07-11 | Moscow |          14\n 2021-06-28 | Berlin |          15\n 2021-06-29 | Berlin |          16\n 2021-06-30 | Berlin |          17\n 2021-07-01 | Berlin |          18\n 2021-07-02 | Berlin |          19\n 2021-07-03 | Berlin |          20\n 2021-07-04 | Berlin |          21\n 2021-07-05 | Berlin |          22\n 2021-07-06 | Berlin |          23\n 2021-07-07 | Berlin |          24\n 2021-07-08 | Berlin |          25\n 2021-07-09 | Berlin |          26\n 2021-07-10 | Berlin |          27\n 2021-07-11 | Berlin |          28\n\n-- Make sure the invalidation threshold was unaffected\nSELECT _timescaledb_functions.to_timestamp(watermark) at time zone 'UTC'\nFROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nWHERE hypertable_id = :ht_id;\n         timezone         \n--------------------------\n Mon Jun 21 00:00:00 2021\n\n-- Make sure the invalidation log is empty\nSELECT\n    _timescaledb_functions.to_timestamp(lowest_modified_value) AS lowest,\n    _timescaledb_functions.to_timestamp(greatest_modified_value) AS greatest\nFROM _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log\nWHERE hypertable_id = :ht_id;\n lowest | greatest \n--------+----------\n\n-- Call refresh\nCALL refresh_continuous_aggregate('conditions_summary_weekly', '2021-06-28', '2021-07-12');\nSELECT city, to_char(bucket, 'YYYY-MM-DD') AS week, min, max\nFROM conditions_summary_weekly\nORDER BY week, city;\n  city  |    week    | min | max \n--------+------------+-----+-----\n Moscow | 2021-06-14 |  22 |  30\n Berlin | 2021-06-28 |  15 |  21\n Moscow | 2021-06-28 |   1 |   7\n Berlin | 2021-07-05 |  22 |  28\n Moscow | 2021-07-05 |   8 |  14\n\n-- Make sure the invalidation threshold has changed\nSELECT _timescaledb_functions.to_timestamp(watermark) at time zone 'UTC'\nFROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nWHERE hypertable_id = :ht_id;\n         timezone         \n--------------------------\n Mon Jul 12 00:00:00 2021\n\n-- Check if CREATE MATERIALIZED VIEW ... WITH DATA works.\n-- Use monthly buckets this time and specify June 2000 as an origin.\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions_summary_monthly\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT city,\n       timescaledb_experimental.time_bucket_ng('1 month', day, '2000-06-01' :: date) AS bucket,\n       MIN(temperature),\n       MAX(temperature)\nFROM conditions\nGROUP BY city, bucket;\nNOTICE:  refreshing continuous aggregate \"conditions_summary_monthly\"\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nSELECT city, to_char(bucket, 'YYYY-MM-DD') AS month, min, max\nFROM conditions_summary_monthly\nORDER BY month, city;\n  city  |   month    | min | max \n--------+------------+-----+-----\n Berlin | 2021-06-01 |  15 |  17\n Moscow | 2021-06-01 |   1 |  34\n Berlin | 2021-07-01 |  18 |  28\n Moscow | 2021-07-01 |   4 |  14\n\n-- Check the invalidation.\n-- Step 1/2. Insert some more data , do a refresh and make sure that the\n--           invalidation log is empty.\nINSERT INTO conditions (day, city, temperature)\nSELECT ts :: date, city, row_number() OVER ()\nFROM generate_series('2021-09-01' :: date, '2021-09-15', '1 day') as ts,\n     unnest(array['Moscow', 'Berlin']) as city;\nCALL refresh_continuous_aggregate('conditions_summary_monthly', '2021-09-01', '2021-10-01');\nSELECT\n    _timescaledb_functions.to_timestamp(lowest_modified_value) AS lowest,\n    _timescaledb_functions.to_timestamp(greatest_modified_value) AS greatest\nFROM _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log\nWHERE hypertable_id = :ht_id;\n lowest | greatest \n--------+----------\n\nSELECT city, to_char(bucket, 'YYYY-MM-DD') AS month, min, max\nFROM conditions_summary_monthly\nORDER BY month, city;\n  city  |   month    | min | max \n--------+------------+-----+-----\n Berlin | 2021-06-01 |  15 |  17\n Moscow | 2021-06-01 |   1 |  34\n Berlin | 2021-07-01 |  18 |  28\n Moscow | 2021-07-01 |   4 |  14\n Berlin | 2021-09-01 |  16 |  30\n Moscow | 2021-09-01 |   1 |  15\n\n-- Step 2/2. Add more data below the invalidation threshold, make sure that the\n--           invalidation log is not empty, then do a refresh.\nINSERT INTO conditions (day, city, temperature)\nSELECT ts :: date, city, (CASE WHEN city = 'Moscow' THEN -40 ELSE 40 END)\nFROM generate_series('2021-09-16' :: date, '2021-09-30', '1 day') as ts,\n     unnest(array['Moscow', 'Berlin']) as city;\nSELECT\n    _timescaledb_functions.to_timestamp(lowest_modified_value) at time zone 'UTC' AS lowest,\n    _timescaledb_functions.to_timestamp(greatest_modified_value) at time zone 'UTC' AS greatest\nFROM _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log\nWHERE hypertable_id = :ht_id;\n          lowest          |         greatest         \n--------------------------+--------------------------\n Thu Sep 16 00:00:00 2021 | Thu Sep 16 00:00:00 2021\n Fri Sep 17 00:00:00 2021 | Fri Sep 17 00:00:00 2021\n Sat Sep 18 00:00:00 2021 | Sat Sep 18 00:00:00 2021\n Sun Sep 19 00:00:00 2021 | Sun Sep 19 00:00:00 2021\n Mon Sep 20 00:00:00 2021 | Mon Sep 20 00:00:00 2021\n Tue Sep 21 00:00:00 2021 | Tue Sep 21 00:00:00 2021\n Wed Sep 22 00:00:00 2021 | Wed Sep 22 00:00:00 2021\n Thu Sep 23 00:00:00 2021 | Thu Sep 23 00:00:00 2021\n Fri Sep 24 00:00:00 2021 | Fri Sep 24 00:00:00 2021\n Sat Sep 25 00:00:00 2021 | Sat Sep 25 00:00:00 2021\n Sun Sep 26 00:00:00 2021 | Sun Sep 26 00:00:00 2021\n Mon Sep 27 00:00:00 2021 | Mon Sep 27 00:00:00 2021\n Tue Sep 28 00:00:00 2021 | Tue Sep 28 00:00:00 2021\n Wed Sep 29 00:00:00 2021 | Wed Sep 29 00:00:00 2021\n Thu Sep 30 00:00:00 2021 | Thu Sep 30 00:00:00 2021\n\nCALL refresh_continuous_aggregate('conditions_summary_monthly', '2021-09-01', '2021-10-01');\nSELECT city, to_char(bucket, 'YYYY-MM-DD') AS month, min, max\nFROM conditions_summary_monthly\nORDER BY month, city;\n  city  |   month    | min | max \n--------+------------+-----+-----\n Berlin | 2021-06-01 |  15 |  17\n Moscow | 2021-06-01 |   1 |  34\n Berlin | 2021-07-01 |  18 |  28\n Moscow | 2021-07-01 |   4 |  14\n Berlin | 2021-09-01 |  16 |  40\n Moscow | 2021-09-01 | -40 |  15\n\nSELECT\n    _timescaledb_functions.to_timestamp(lowest_modified_value) AS lowest,\n    _timescaledb_functions.to_timestamp(greatest_modified_value) AS greatest\nFROM _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log\nWHERE hypertable_id = :ht_id;\n lowest | greatest \n--------+----------\n\n-- Create a real-time aggregate with custom origin - June 2000\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions_summary_rt\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT city,\n   timescaledb_experimental.time_bucket_ng('1 month', day, '2000-06-01' :: date) AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions\nGROUP BY city, bucket;\nNOTICE:  refreshing continuous aggregate \"conditions_summary_rt\"\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nSELECT city, to_char(bucket, 'YYYY-MM-DD') AS month, min, max\nFROM conditions_summary_rt\nORDER BY month, city;\n  city  |   month    | min | max \n--------+------------+-----+-----\n Berlin | 2021-06-01 |  15 |  17\n Moscow | 2021-06-01 |   1 |  34\n Berlin | 2021-07-01 |  18 |  28\n Moscow | 2021-07-01 |   4 |  14\n Berlin | 2021-09-01 |  16 |  40\n Moscow | 2021-09-01 | -40 |  15\n\n-- Add some data to the hypertable and make sure it is visible in the cagg\nINSERT INTO conditions (day, city, temperature) VALUES\n  ('2021-10-01', 'Moscow', 1),\n  ('2021-10-02', 'Moscow', 2),\n  ('2021-10-03', 'Moscow', 3),\n  ('2021-10-04', 'Moscow', 4),\n  ('2021-10-01', 'Berlin', 5),\n  ('2021-10-02', 'Berlin', 6),\n  ('2021-10-03', 'Berlin', 7),\n  ('2021-10-04', 'Berlin', 8);\nSELECT city, to_char(bucket, 'YYYY-MM-DD') AS month, min, max\nFROM conditions_summary_rt\nORDER BY month, city;\n  city  |   month    | min | max \n--------+------------+-----+-----\n Berlin | 2021-06-01 |  15 |  17\n Moscow | 2021-06-01 |   1 |  34\n Berlin | 2021-07-01 |  18 |  28\n Moscow | 2021-07-01 |   4 |  14\n Berlin | 2021-09-01 |  16 |  40\n Moscow | 2021-09-01 | -40 |  15\n Berlin | 2021-10-01 |   5 |   8\n Moscow | 2021-10-01 |   1 |   4\n\n-- Refresh the cagg and make sure that the result of SELECT query didn't change\nCALL refresh_continuous_aggregate('conditions_summary_rt', '2021-10-01', '2021-11-01');\nSELECT city, to_char(bucket, 'YYYY-MM-DD') AS month, min, max\nFROM conditions_summary_rt\nORDER BY month, city;\n  city  |   month    | min | max \n--------+------------+-----+-----\n Berlin | 2021-06-01 |  15 |  17\n Moscow | 2021-06-01 |   1 |  34\n Berlin | 2021-07-01 |  18 |  28\n Moscow | 2021-07-01 |   4 |  14\n Berlin | 2021-09-01 |  16 |  40\n Moscow | 2021-09-01 | -40 |  15\n Berlin | 2021-10-01 |   5 |   8\n Moscow | 2021-10-01 |   1 |   4\n\n-- Add some more data, enable compression, compress the chunks and repeat the test\nINSERT INTO conditions (day, city, temperature) VALUES\n  ('2021-11-01', 'Moscow', 11),\n  ('2021-11-02', 'Moscow', 12),\n  ('2021-11-03', 'Moscow', 13),\n  ('2021-11-04', 'Moscow', 14),\n  ('2021-11-01', 'Berlin', 15),\n  ('2021-11-02', 'Berlin', 16),\n  ('2021-11-03', 'Berlin', 17),\n  ('2021-11-04', 'Berlin', 18);\nALTER TABLE conditions SET (\n    timescaledb.compress,\n    timescaledb.compress_segmentby = 'city'\n);\nSELECT compress_chunk(ch) FROM show_chunks('conditions') AS ch;\n             compress_chunk              \n-----------------------------------------\n _timescaledb_internal._hyper_1_1_chunk\n _timescaledb_internal._hyper_1_2_chunk\n _timescaledb_internal._hyper_1_3_chunk\n _timescaledb_internal._hyper_1_4_chunk\n _timescaledb_internal._hyper_1_5_chunk\n _timescaledb_internal._hyper_1_6_chunk\n _timescaledb_internal._hyper_1_7_chunk\n _timescaledb_internal._hyper_1_8_chunk\n _timescaledb_internal._hyper_1_9_chunk\n _timescaledb_internal._hyper_1_10_chunk\n _timescaledb_internal._hyper_1_11_chunk\n _timescaledb_internal._hyper_1_12_chunk\n _timescaledb_internal._hyper_1_13_chunk\n _timescaledb_internal._hyper_1_14_chunk\n _timescaledb_internal._hyper_1_16_chunk\n _timescaledb_internal._hyper_1_17_chunk\n _timescaledb_internal._hyper_1_18_chunk\n _timescaledb_internal._hyper_1_19_chunk\n _timescaledb_internal._hyper_1_20_chunk\n _timescaledb_internal._hyper_1_21_chunk\n _timescaledb_internal._hyper_1_22_chunk\n _timescaledb_internal._hyper_1_23_chunk\n _timescaledb_internal._hyper_1_24_chunk\n _timescaledb_internal._hyper_1_25_chunk\n _timescaledb_internal._hyper_1_26_chunk\n _timescaledb_internal._hyper_1_27_chunk\n _timescaledb_internal._hyper_1_28_chunk\n _timescaledb_internal._hyper_1_29_chunk\n _timescaledb_internal._hyper_1_34_chunk\n _timescaledb_internal._hyper_1_35_chunk\n _timescaledb_internal._hyper_1_36_chunk\n _timescaledb_internal._hyper_1_37_chunk\n _timescaledb_internal._hyper_1_38_chunk\n _timescaledb_internal._hyper_1_39_chunk\n _timescaledb_internal._hyper_1_40_chunk\n _timescaledb_internal._hyper_1_41_chunk\n _timescaledb_internal._hyper_1_42_chunk\n _timescaledb_internal._hyper_1_43_chunk\n _timescaledb_internal._hyper_1_44_chunk\n _timescaledb_internal._hyper_1_45_chunk\n _timescaledb_internal._hyper_1_46_chunk\n _timescaledb_internal._hyper_1_47_chunk\n _timescaledb_internal._hyper_1_48_chunk\n _timescaledb_internal._hyper_1_50_chunk\n _timescaledb_internal._hyper_1_51_chunk\n _timescaledb_internal._hyper_1_52_chunk\n _timescaledb_internal._hyper_1_53_chunk\n _timescaledb_internal._hyper_1_54_chunk\n _timescaledb_internal._hyper_1_55_chunk\n _timescaledb_internal._hyper_1_56_chunk\n _timescaledb_internal._hyper_1_57_chunk\n _timescaledb_internal._hyper_1_58_chunk\n _timescaledb_internal._hyper_1_59_chunk\n _timescaledb_internal._hyper_1_60_chunk\n _timescaledb_internal._hyper_1_61_chunk\n _timescaledb_internal._hyper_1_62_chunk\n _timescaledb_internal._hyper_1_63_chunk\n _timescaledb_internal._hyper_1_64_chunk\n _timescaledb_internal._hyper_1_68_chunk\n _timescaledb_internal._hyper_1_69_chunk\n _timescaledb_internal._hyper_1_70_chunk\n _timescaledb_internal._hyper_1_71_chunk\n _timescaledb_internal._hyper_1_73_chunk\n _timescaledb_internal._hyper_1_74_chunk\n _timescaledb_internal._hyper_1_75_chunk\n _timescaledb_internal._hyper_1_76_chunk\n\n-- Data for 2021-11 is seen because the cagg is real-time\nSELECT city, to_char(bucket, 'YYYY-MM-DD') AS month, min, max\nFROM conditions_summary_rt\nORDER BY month, city;\n  city  |   month    | min | max \n--------+------------+-----+-----\n Berlin | 2021-06-01 |  15 |  17\n Moscow | 2021-06-01 |   1 |  34\n Berlin | 2021-07-01 |  18 |  28\n Moscow | 2021-07-01 |   4 |  14\n Berlin | 2021-09-01 |  16 |  40\n Moscow | 2021-09-01 | -40 |  15\n Berlin | 2021-10-01 |   5 |   8\n Moscow | 2021-10-01 |   1 |   4\n Berlin | 2021-11-01 |  15 |  18\n Moscow | 2021-11-01 |  11 |  14\n\nCALL refresh_continuous_aggregate('conditions_summary_rt', '2021-11-01', '2021-12-01');\n-- Data for 2021-11 is seen because the cagg was refreshed\nSELECT city, to_char(bucket, 'YYYY-MM-DD') AS month, min, max\nFROM conditions_summary_rt\nORDER BY month, city;\n  city  |   month    | min | max \n--------+------------+-----+-----\n Berlin | 2021-06-01 |  15 |  17\n Moscow | 2021-06-01 |   1 |  34\n Berlin | 2021-07-01 |  18 |  28\n Moscow | 2021-07-01 |   4 |  14\n Berlin | 2021-09-01 |  16 |  40\n Moscow | 2021-09-01 | -40 |  15\n Berlin | 2021-10-01 |   5 |   8\n Moscow | 2021-10-01 |   1 |   4\n Berlin | 2021-11-01 |  15 |  18\n Moscow | 2021-11-01 |  11 |  14\n\n-- Clean up\nDROP TABLE conditions CASCADE;\nNOTICE:  drop cascades to 7 other objects\nNOTICE:  drop cascades to 3 other objects\nNOTICE:  drop cascades to 3 other objects\nNOTICE:  drop cascades to 5 other objects\n-- Test the specific code path of creating a CAGG on top of empty hypertable.\nCREATE TABLE conditions_empty(\n  day DATE NOT NULL,\n  city text NOT NULL,\n  temperature INT NOT NULL);\nSELECT create_hypertable(\n  'conditions_empty', 'day',\n  chunk_time_interval => INTERVAL '1 day'\n);\n       create_hypertable       \n-------------------------------\n (6,public,conditions_empty,t)\n\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions_summary_empty\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT city,\n   timescaledb_experimental.time_bucket_ng('1 month', day, '2005-02-01') AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions_empty\nGROUP BY city, bucket;\nNOTICE:  continuous aggregate \"conditions_summary_empty\" is already up-to-date\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nSELECT city, to_char(bucket, 'YYYY-MM-DD') AS month, min, max\nFROM conditions_summary_empty\nORDER BY month, city;\n city | month | min | max \n------+-------+-----+-----\n\n-- The test above changes the record that gets added to the invalidation log\n-- for an empty table. Make sure it doesn't have any unintended side-effects\n-- and the refreshing works as expected.\nINSERT INTO conditions_empty (day, city, temperature) VALUES\n  ('2021-06-14', 'Moscow', 26),\n  ('2021-06-15', 'Moscow', 22),\n  ('2021-06-16', 'Moscow', 24),\n  ('2021-06-17', 'Moscow', 24),\n  ('2021-06-18', 'Moscow', 27),\n  ('2021-06-19', 'Moscow', 28),\n  ('2021-06-20', 'Moscow', 30),\n  ('2021-06-21', 'Moscow', 31),\n  ('2021-06-22', 'Moscow', 34),\n  ('2021-06-23', 'Moscow', 34),\n  ('2021-06-24', 'Moscow', 34),\n  ('2021-06-25', 'Moscow', 32),\n  ('2021-06-26', 'Moscow', 32),\n  ('2021-06-27', 'Moscow', 31);\nCALL refresh_continuous_aggregate('conditions_summary_empty', '2021-06-01', '2021-07-01');\nSELECT city, to_char(bucket, 'YYYY-MM-DD') AS month, min, max\nFROM conditions_summary_empty\nORDER BY month, city;\n  city  |   month    | min | max \n--------+------------+-----+-----\n Moscow | 2021-06-01 |  22 |  34\n\n-- Clean up\nDROP TABLE conditions_empty CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_7_158_chunk\n-- Make sure add_continuous_aggregate_policy() works\nCREATE TABLE conditions_policy(\n  day DATE NOT NULL,\n  city text NOT NULL,\n  temperature INT NOT NULL);\nSELECT create_hypertable(\n  'conditions_policy', 'day',\n  chunk_time_interval => INTERVAL '1 day'\n);\n       create_hypertable        \n--------------------------------\n (8,public,conditions_policy,t)\n\nINSERT INTO conditions_policy (day, city, temperature) VALUES\n  ('2021-06-14', 'Moscow', 26),\n  ('2021-06-15', 'Moscow', 22),\n  ('2021-06-16', 'Moscow', 24),\n  ('2021-06-17', 'Moscow', 24),\n  ('2021-06-18', 'Moscow', 27),\n  ('2021-06-19', 'Moscow', 28),\n  ('2021-06-20', 'Moscow', 30),\n  ('2021-06-21', 'Moscow', 31),\n  ('2021-06-22', 'Moscow', 34),\n  ('2021-06-23', 'Moscow', 34),\n  ('2021-06-24', 'Moscow', 34),\n  ('2021-06-25', 'Moscow', 32),\n  ('2021-06-26', 'Moscow', 32),\n  ('2021-06-27', 'Moscow', 31);\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions_summary_policy\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT city,\n   timescaledb_experimental.time_bucket_ng('1 month', day, '2005-03-01') AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions_policy\nGROUP BY city, bucket;\nNOTICE:  refreshing continuous aggregate \"conditions_summary_policy\"\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nSELECT * FROM conditions_summary_policy;\n  city  |   bucket   | min | max \n--------+------------+-----+-----\n Moscow | 06-01-2021 |  22 |  34\n\n\\set ON_ERROR_STOP 0\n-- Check for \"policy refresh window too small\" error\nSELECT add_continuous_aggregate_policy('conditions_summary_policy',\n    -- Historically, 1 month is just a synonym to 30 days here.\n    -- See interval_to_int64() and interval_to_int128().\n    start_offset => INTERVAL '2 months',\n    end_offset => INTERVAL '1 day',\n    schedule_interval => INTERVAL '1 hour');\nERROR:  policy refresh window too small\n\\set ON_ERROR_STOP 1\nSELECT add_continuous_aggregate_policy('conditions_summary_policy',\n    start_offset => INTERVAL '65 days',\n    end_offset => INTERVAL '1 day',\n    schedule_interval => INTERVAL '1 hour');\n add_continuous_aggregate_policy \n---------------------------------\n                            1000\n\n-- Clean up\nDROP TABLE conditions_policy CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_9_173_chunk\n-- Make sure CAGGs with custom origin work for timestamp type\nCREATE TABLE conditions_timestamp(\n  tstamp TIMESTAMP NOT NULL,\n  city TEXT NOT NULL,\n  temperature INT NOT NULL);\nSELECT create_hypertable(\n  'conditions_timestamp', 'tstamp',\n  chunk_time_interval => INTERVAL '1 day'\n);\nWARNING:  column type \"timestamp without time zone\" used for \"tstamp\" does not follow best practices\n         create_hypertable          \n------------------------------------\n (10,public,conditions_timestamp,t)\n\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions_summary_timestamp\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT city,\n   timescaledb_experimental.time_bucket_ng('12 hours', tstamp, '2000-06-01 12:00:00') AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions_timestamp\nGROUP BY city, bucket;\nNOTICE:  continuous aggregate \"conditions_summary_timestamp\" is already up-to-date\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nSELECT city, to_char(bucket, 'YYYY-MM-DD HH24:MI:SS') AS b, min, max\nFROM conditions_summary_timestamp\nORDER BY b, city;\n city | b | min | max \n------+---+-----+-----\n\n-- Add some data to the hypertable and make sure it is visible in the cagg\nINSERT INTO conditions_timestamp(tstamp, city, temperature)\nSELECT ts, city, (CASE WHEN city = 'Moscow' THEN 20000 ELSE 10000 END) + date_part('day', ts)*100 + date_part('hour', ts)\nFROM\n  generate_series('2010-01-01 00:00:00' :: timestamp, '2010-01-02 00:00:00' :: timestamp - interval '1 hour', '1 hour') as ts,\n  unnest(array['Moscow', 'Berlin']) as city;\nSELECT city, to_char(bucket, 'YYYY-MM-DD HH24:MI:SS') AS b, min, max\nFROM conditions_summary_timestamp\nORDER BY b, city;\n  city  |          b          |  min  |  max  \n--------+---------------------+-------+-------\n Berlin | 2010-01-01 00:00:00 | 10100 | 10111\n Moscow | 2010-01-01 00:00:00 | 20100 | 20111\n Berlin | 2010-01-01 12:00:00 | 10112 | 10123\n Moscow | 2010-01-01 12:00:00 | 20112 | 20123\n\n-- Refresh the cagg and make sure that the result of SELECT query didn't change\nCALL refresh_continuous_aggregate('conditions_summary_timestamp', '2010-01-01 00:00:00', '2010-01-02 00:00:00');\nSELECT city, to_char(bucket, 'YYYY-MM-DD HH24:MI:SS') AS b, min, max\nFROM conditions_summary_timestamp\nORDER BY b, city;\n  city  |          b          |  min  |  max  \n--------+---------------------+-------+-------\n Berlin | 2010-01-01 00:00:00 | 10100 | 10111\n Moscow | 2010-01-01 00:00:00 | 20100 | 20111\n Berlin | 2010-01-01 12:00:00 | 10112 | 10123\n Moscow | 2010-01-01 12:00:00 | 20112 | 20123\n\n-- Add some more data, enable compression, compress the chunks and repeat the test\nINSERT INTO conditions_timestamp(tstamp, city, temperature)\nSELECT ts, city, (CASE WHEN city = 'Moscow' THEN 20000 ELSE 10000 END) + date_part('day', ts)*100 + date_part('hour', ts)\nFROM\n  generate_series('2010-01-02 00:00:00' :: timestamp, '2010-01-03 00:00:00' :: timestamp - interval '1 hour', '1 hour') as ts,\n  unnest(array['Moscow', 'Berlin']) as city;\nALTER TABLE conditions_timestamp SET (\n    timescaledb.compress,\n    timescaledb.compress_segmentby = 'city'\n);\nSELECT compress_chunk(ch) FROM show_chunks('conditions_timestamp') AS ch;\n              compress_chunk               \n-------------------------------------------\n _timescaledb_internal._hyper_10_174_chunk\n _timescaledb_internal._hyper_10_176_chunk\n\n-- New data is seen because the cagg is real-time\nSELECT city, to_char(bucket, 'YYYY-MM-DD HH24:MI:SS') AS b, min, max\nFROM conditions_summary_timestamp\nORDER BY b, city;\n  city  |          b          |  min  |  max  \n--------+---------------------+-------+-------\n Berlin | 2010-01-01 00:00:00 | 10100 | 10111\n Moscow | 2010-01-01 00:00:00 | 20100 | 20111\n Berlin | 2010-01-01 12:00:00 | 10112 | 10123\n Moscow | 2010-01-01 12:00:00 | 20112 | 20123\n Berlin | 2010-01-02 00:00:00 | 10200 | 10211\n Moscow | 2010-01-02 00:00:00 | 20200 | 20211\n Berlin | 2010-01-02 12:00:00 | 10212 | 10223\n Moscow | 2010-01-02 12:00:00 | 20212 | 20223\n\nCALL refresh_continuous_aggregate('conditions_summary_timestamp', '2010-01-02 00:00:00', '2010-01-03 00:00:00');\n-- New data is seen because the cagg was refreshed\nSELECT city, to_char(bucket, 'YYYY-MM-DD HH24:MI:SS') AS b, min, max\nFROM conditions_summary_timestamp\nORDER BY b, city;\n  city  |          b          |  min  |  max  \n--------+---------------------+-------+-------\n Berlin | 2010-01-01 00:00:00 | 10100 | 10111\n Moscow | 2010-01-01 00:00:00 | 20100 | 20111\n Berlin | 2010-01-01 12:00:00 | 10112 | 10123\n Moscow | 2010-01-01 12:00:00 | 20112 | 20123\n Berlin | 2010-01-02 00:00:00 | 10200 | 10211\n Moscow | 2010-01-02 00:00:00 | 20200 | 20211\n Berlin | 2010-01-02 12:00:00 | 10212 | 10223\n Moscow | 2010-01-02 12:00:00 | 20212 | 20223\n\n-- Add a refresh policy\nSELECT add_continuous_aggregate_policy('conditions_summary_timestamp',\n    start_offset => INTERVAL '25 hours',\n    end_offset => INTERVAL '1 hour',\n    schedule_interval => INTERVAL '30 minutes');\n add_continuous_aggregate_policy \n---------------------------------\n                            1001\n\n-- Clean up\nDROP TABLE conditions_timestamp CASCADE;\nNOTICE:  drop cascades to 3 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_11_175_chunk\n-- Make sure CAGGs with custom origin work for timestamptz type\nCREATE TABLE conditions_timestamptz(\n  tstamp TIMESTAMPTZ NOT NULL,\n  city TEXT NOT NULL,\n  temperature INT NOT NULL);\nSELECT create_hypertable(\n  'conditions_timestamptz', 'tstamp',\n  chunk_time_interval => INTERVAL '1 day'\n);\n          create_hypertable           \n--------------------------------------\n (13,public,conditions_timestamptz,t)\n\n-- Add some data to the hypertable and make sure it is visible in the cagg\nINSERT INTO conditions_timestamptz(tstamp, city, temperature)\nSELECT ts, city,\n  (CASE WHEN city = 'Moscow' THEN 20000 ELSE 10000 END) +\n  date_part('day', ts at time zone 'MSK')*100 +\n  date_part('hour', ts at time zone 'MSK')\nFROM\n  generate_series('2022-01-01 00:00:00 MSK' :: timestamptz, '2022-01-02 00:00:00 MSK' :: timestamptz - interval '1 hour', '1 hour') as ts,\n  unnest(array['Moscow', 'Berlin']) as city;\n\\set ON_ERROR_STOP 0\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\n-- For monthly buckets origin should be the first day of the month in given timezone\n-- 2020-06-02 00:00:00 MSK == 2020-06-01 21:00:00 UTC\nCREATE MATERIALIZED VIEW conditions_summary_timestamptz\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT city,\n       timescaledb_experimental.time_bucket_ng('1 month', tstamp, '2020-06-02 00:00:00 MSK', 'Europe/Moscow') AS bucket,\n       MIN(temperature),\n       MAX(temperature)\nFROM conditions_timestamptz\nGROUP BY city, bucket;\nERROR:  origin must be the first day of the month\n-- Make sure buckets like '1 months 15 days\" (fixed+variable-sized) are not allowed\nCREATE MATERIALIZED VIEW conditions_summary_timestamptz\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT city,\n       timescaledb_experimental.time_bucket_ng('1 month 15 days', tstamp, '2020-06-01 00:00:00 MSK', 'Europe/Moscow') AS bucket,\n       MIN(temperature),\n       MAX(temperature)\nFROM conditions_timestamptz\nGROUP BY city, bucket;\nERROR:  invalid interval specified\n\\set ON_ERROR_STOP 1\nCREATE MATERIALIZED VIEW conditions_summary_timestamptz\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT city,\n   timescaledb_experimental.time_bucket_ng('12 hours', tstamp, '2020-06-01 12:00:00 MSK', 'Europe/Moscow') AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions_timestamptz\nGROUP BY city, bucket;\nNOTICE:  refreshing continuous aggregate \"conditions_summary_timestamptz\"\nRESET timescaledb.debug_allow_cagg_with_deprecated_func;\n-- Make sure the origin is saved in the catalog table\nSELECT mat_hypertable_id AS cagg_id\n  FROM _timescaledb_catalog.continuous_agg\n  WHERE user_view_name = 'conditions_summary_timestamptz'\n\\gset\nSELECT bucket_func, bucket_width, bucket_origin, bucket_timezone, bucket_fixed_width\n  FROM _timescaledb_catalog.continuous_aggs_bucket_function\n  WHERE mat_hypertable_id = :cagg_id;\n                                                     bucket_func                                                     | bucket_width |        bucket_origin         | bucket_timezone | bucket_fixed_width \n---------------------------------------------------------------------------------------------------------------------+--------------+------------------------------+-----------------+--------------------\n timescaledb_experimental.time_bucket_ng(interval,timestamp with time zone,timestamp with time zone,pg_catalog.text) | @ 12 hours   | Mon Jun 01 02:00:00 2020 PDT | Europe/Moscow   | f\n\nSELECT city, to_char(bucket at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') AS b, min, max\nFROM conditions_summary_timestamptz\nORDER BY b, city;\n  city  |          b          |  min  |  max  \n--------+---------------------+-------+-------\n Berlin | 2022-01-01 00:00:00 | 10100 | 10111\n Moscow | 2022-01-01 00:00:00 | 20100 | 20111\n Berlin | 2022-01-01 12:00:00 | 10112 | 10123\n Moscow | 2022-01-01 12:00:00 | 20112 | 20123\n\n-- Check the data\nSELECT to_char(tstamp at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') AS ts, city, temperature FROM conditions_timestamptz\nORDER BY ts, city;\n         ts          |  city  | temperature \n---------------------+--------+-------------\n 2022-01-01 00:00:00 | Berlin |       10100\n 2022-01-01 00:00:00 | Moscow |       20100\n 2022-01-01 01:00:00 | Berlin |       10101\n 2022-01-01 01:00:00 | Moscow |       20101\n 2022-01-01 02:00:00 | Berlin |       10102\n 2022-01-01 02:00:00 | Moscow |       20102\n 2022-01-01 03:00:00 | Berlin |       10103\n 2022-01-01 03:00:00 | Moscow |       20103\n 2022-01-01 04:00:00 | Berlin |       10104\n 2022-01-01 04:00:00 | Moscow |       20104\n 2022-01-01 05:00:00 | Berlin |       10105\n 2022-01-01 05:00:00 | Moscow |       20105\n 2022-01-01 06:00:00 | Berlin |       10106\n 2022-01-01 06:00:00 | Moscow |       20106\n 2022-01-01 07:00:00 | Berlin |       10107\n 2022-01-01 07:00:00 | Moscow |       20107\n 2022-01-01 08:00:00 | Berlin |       10108\n 2022-01-01 08:00:00 | Moscow |       20108\n 2022-01-01 09:00:00 | Berlin |       10109\n 2022-01-01 09:00:00 | Moscow |       20109\n 2022-01-01 10:00:00 | Berlin |       10110\n 2022-01-01 10:00:00 | Moscow |       20110\n 2022-01-01 11:00:00 | Berlin |       10111\n 2022-01-01 11:00:00 | Moscow |       20111\n 2022-01-01 12:00:00 | Berlin |       10112\n 2022-01-01 12:00:00 | Moscow |       20112\n 2022-01-01 13:00:00 | Berlin |       10113\n 2022-01-01 13:00:00 | Moscow |       20113\n 2022-01-01 14:00:00 | Berlin |       10114\n 2022-01-01 14:00:00 | Moscow |       20114\n 2022-01-01 15:00:00 | Berlin |       10115\n 2022-01-01 15:00:00 | Moscow |       20115\n 2022-01-01 16:00:00 | Berlin |       10116\n 2022-01-01 16:00:00 | Moscow |       20116\n 2022-01-01 17:00:00 | Berlin |       10117\n 2022-01-01 17:00:00 | Moscow |       20117\n 2022-01-01 18:00:00 | Berlin |       10118\n 2022-01-01 18:00:00 | Moscow |       20118\n 2022-01-01 19:00:00 | Berlin |       10119\n 2022-01-01 19:00:00 | Moscow |       20119\n 2022-01-01 20:00:00 | Berlin |       10120\n 2022-01-01 20:00:00 | Moscow |       20120\n 2022-01-01 21:00:00 | Berlin |       10121\n 2022-01-01 21:00:00 | Moscow |       20121\n 2022-01-01 22:00:00 | Berlin |       10122\n 2022-01-01 22:00:00 | Moscow |       20122\n 2022-01-01 23:00:00 | Berlin |       10123\n 2022-01-01 23:00:00 | Moscow |       20123\n\nSELECT city, to_char(bucket at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') AS b, min, max\nFROM conditions_summary_timestamptz\nORDER BY b, city;\n  city  |          b          |  min  |  max  \n--------+---------------------+-------+-------\n Berlin | 2022-01-01 00:00:00 | 10100 | 10111\n Moscow | 2022-01-01 00:00:00 | 20100 | 20111\n Berlin | 2022-01-01 12:00:00 | 10112 | 10123\n Moscow | 2022-01-01 12:00:00 | 20112 | 20123\n\n-- Refresh the cagg and make sure that the result of SELECT query didn't change\nCALL refresh_continuous_aggregate('conditions_summary_timestamptz', '2022-01-01 00:00:00 MSK', '2022-01-02 00:00:00 MSK');\nNOTICE:  continuous aggregate \"conditions_summary_timestamptz\" is already up-to-date\nSELECT city, to_char(bucket at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') AS b, min, max\nFROM conditions_summary_timestamptz\nORDER BY b, city;\n  city  |          b          |  min  |  max  \n--------+---------------------+-------+-------\n Berlin | 2022-01-01 00:00:00 | 10100 | 10111\n Moscow | 2022-01-01 00:00:00 | 20100 | 20111\n Berlin | 2022-01-01 12:00:00 | 10112 | 10123\n Moscow | 2022-01-01 12:00:00 | 20112 | 20123\n\n-- Add some more data, enable compression, compress the chunks and repeat the test\nINSERT INTO conditions_timestamptz(tstamp, city, temperature)\nSELECT ts, city,\n  (CASE WHEN city = 'Moscow' THEN 20000 ELSE 10000 END) +\n  date_part('day', ts at time zone 'MSK')*100 +\n  date_part('hour', ts at time zone 'MSK')\nFROM\n  generate_series('2022-01-02 00:00:00 MSK' :: timestamptz, '2022-01-03 00:00:00 MSK' :: timestamptz - interval '1 hour', '1 hour') as ts,\n  unnest(array['Moscow', 'Berlin']) as city;\nALTER TABLE conditions_timestamptz SET (\n    timescaledb.compress,\n    timescaledb.compress_segmentby = 'city'\n);\nSELECT compress_chunk(ch) FROM show_chunks('conditions_timestamptz') AS ch;\n              compress_chunk               \n-------------------------------------------\n _timescaledb_internal._hyper_13_179_chunk\n _timescaledb_internal._hyper_13_180_chunk\n _timescaledb_internal._hyper_13_182_chunk\n\n-- New data is seen because the cagg is real-time\nSELECT city, to_char(bucket at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') AS b, min, max\nFROM conditions_summary_timestamptz\nORDER BY b, city;\n  city  |          b          |  min  |  max  \n--------+---------------------+-------+-------\n Berlin | 2022-01-01 00:00:00 | 10100 | 10111\n Moscow | 2022-01-01 00:00:00 | 20100 | 20111\n Berlin | 2022-01-01 12:00:00 | 10112 | 10123\n Moscow | 2022-01-01 12:00:00 | 20112 | 20123\n Berlin | 2022-01-02 00:00:00 | 10200 | 10211\n Moscow | 2022-01-02 00:00:00 | 20200 | 20211\n Berlin | 2022-01-02 12:00:00 | 10212 | 10223\n Moscow | 2022-01-02 12:00:00 | 20212 | 20223\n\nCALL refresh_continuous_aggregate('conditions_summary_timestamptz', '2022-01-02 00:00:00 MSK', '2022-01-03 00:00:00 MSK');\n-- New data is seen because the cagg was refreshed\nSELECT city, to_char(bucket at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') AS b, min, max\nFROM conditions_summary_timestamptz\nORDER BY b, city;\n  city  |          b          |  min  |  max  \n--------+---------------------+-------+-------\n Berlin | 2022-01-01 00:00:00 | 10100 | 10111\n Moscow | 2022-01-01 00:00:00 | 20100 | 20111\n Berlin | 2022-01-01 12:00:00 | 10112 | 10123\n Moscow | 2022-01-01 12:00:00 | 20112 | 20123\n Berlin | 2022-01-02 00:00:00 | 10200 | 10211\n Moscow | 2022-01-02 00:00:00 | 20200 | 20211\n Berlin | 2022-01-02 12:00:00 | 10212 | 10223\n Moscow | 2022-01-02 12:00:00 | 20212 | 20223\n\n-- Add a refresh policy\nSELECT add_continuous_aggregate_policy('conditions_summary_timestamptz',\n    start_offset => INTERVAL '25 hours',\n    end_offset => INTERVAL '1 hour',\n    schedule_interval => INTERVAL '30 minutes');\n add_continuous_aggregate_policy \n---------------------------------\n                            1002\n\n-- Clean up\nDROP TABLE conditions_timestamptz CASCADE;\nNOTICE:  drop cascades to 3 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_15_181_chunk\n"
  },
  {
    "path": "tsl/test/expected/cagg_exp_timezone.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- Make sure that timezone can't be used for types other that timestamptz\nCREATE TABLE conditions(\n  day timestamp NOT NULL, -- not timestamptz!\n  city text NOT NULL,\n  temperature INT NOT NULL);\nSELECT create_hypertable(\n  'conditions', 'day',\n  chunk_time_interval => INTERVAL '1 day'\n);\nWARNING:  column type \"timestamp without time zone\" used for \"day\" does not follow best practices\n    create_hypertable    \n-------------------------\n (1,public,conditions,t)\n\n\\set ON_ERROR_STOP 0\n-- timebucket_ng is deprecated and can not be used in new CAggs anymore.\n-- However, using this GUC the restriction can be lifted in debug builds\n-- to ensure the functionality can be tested.\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT city,\n   timescaledb_experimental.time_bucket_ng('1 month', day, 'Europe/Moscow') AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions\nGROUP BY city, bucket\nWITH NO DATA;\nERROR:  invalid input syntax for type timestamp: \"Europe/Moscow\" at character 178\n-- Reset GUC to check if the CAgg would also work in release builds\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\n\\set ON_ERROR_STOP 1\nDROP TABLE conditions CASCADE;\nCREATE TABLE conditions_tz(\n  day timestamptz NOT NULL,\n  city text NOT NULL,\n  temperature INT NOT NULL);\nSELECT create_hypertable(\n  'conditions_tz', 'day',\n  chunk_time_interval => INTERVAL '1 day'\n);\n     create_hypertable      \n----------------------------\n (2,public,conditions_tz,t)\n\nINSERT INTO conditions_tz (day, city, temperature) VALUES\n  ('2021-06-14 00:00:00 MSK', 'Moscow', 26),\n  ('2021-06-15 00:00:00 MSK', 'Moscow', 22),\n  ('2021-06-16 00:00:00 MSK', 'Moscow', 24),\n  ('2021-06-17 00:00:00 MSK', 'Moscow', 24),\n  ('2021-06-18 00:00:00 MSK', 'Moscow', 27),\n  ('2021-06-19 00:00:00 MSK', 'Moscow', 28),\n  ('2021-06-20 00:00:00 MSK', 'Moscow', 30),\n  ('2021-06-21 00:00:00 MSK', 'Moscow', 31),\n  ('2021-06-22 00:00:00 MSK', 'Moscow', 34),\n  ('2021-06-23 00:00:00 MSK', 'Moscow', 34),\n  ('2021-06-24 00:00:00 MSK', 'Moscow', 34),\n  ('2021-06-25 00:00:00 MSK', 'Moscow', 32),\n  ('2021-06-26 00:00:00 MSK', 'Moscow', 32),\n  ('2021-06-27 00:00:00 MSK', 'Moscow', 31);\n\\set ON_ERROR_STOP 0\n-- Check that the name of the timezone is validated\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions_summary_tz\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT city,\n   timescaledb_experimental.time_bucket_ng('1 month', day, 'Europe/Ololondon') AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions_tz\nGROUP BY city, bucket\nWITH NO DATA;\nERROR:  invalid timezone name \"Europe/Ololondon\"\n-- Check that buckets like '1 month 15 days' (fixed-sized + variable-sized) are not allowed\nCREATE MATERIALIZED VIEW conditions_summary_tz\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT city,\n   timescaledb_experimental.time_bucket_ng('1 month 15 days', day, 'MSK') AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions_tz\nGROUP BY city, bucket\nWITH NO DATA;\nERROR:  invalid interval specified\n-- Check that only immutable expressions can be used as a timezone\nCREATE MATERIALIZED VIEW conditions_summary_tz\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT city,\n   timescaledb_experimental.time_bucket_ng('1 day', day, city) AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions_tz\nGROUP BY city, bucket\nWITH NO DATA;\nERROR:  only immutable expressions allowed in time bucket function\n\\set ON_ERROR_STOP 1\n-- Make sure it's possible to create an empty cagg (WITH NO DATA) and\n-- that all the information about the bucketing function will be saved\n-- to the TS catalog.\nCREATE MATERIALIZED VIEW conditions_summary_tz\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT city,\n   timescaledb_experimental.time_bucket_ng('1 month', day, 'MSK') AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions_tz\nGROUP BY city, bucket\nWITH NO DATA;\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nSELECT mat_hypertable_id AS cagg_id_tz, raw_hypertable_id AS ht_id\nFROM _timescaledb_catalog.continuous_agg\nWHERE user_view_name = 'conditions_summary_tz'\n\\gset\n-- Make sure the timezone is saved in the catalog table\nSELECT bucket_func, bucket_width, bucket_origin, bucket_timezone, bucket_fixed_width\nFROM _timescaledb_catalog.continuous_aggs_bucket_function\nWHERE mat_hypertable_id = :cagg_id_tz;\n                                        bucket_func                                         | bucket_width | bucket_origin | bucket_timezone | bucket_fixed_width \n--------------------------------------------------------------------------------------------+--------------+---------------+-----------------+--------------------\n timescaledb_experimental.time_bucket_ng(interval,timestamp with time zone,pg_catalog.text) | @ 1 mon      |               | MSK             | f\n\n-- Make sure that buckets with specified timezone are always treated as\n-- variable-sized, even if the interval is fixed (i.e. days and/or hours)\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions_summary_1w\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT city,\n   timescaledb_experimental.time_bucket_ng('7 days', day, 'MSK') AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions_tz\nGROUP BY city, bucket\nWITH NO DATA;\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nSELECT mat_hypertable_id AS cagg_id_1w\nFROM _timescaledb_catalog.continuous_agg\nWHERE user_view_name = 'conditions_summary_1w'\n\\gset\n-- Make sure the timezone is saved in the catalog table\nSELECT bucket_func, bucket_width, bucket_origin, bucket_timezone, bucket_fixed_width\nFROM _timescaledb_catalog.continuous_aggs_bucket_function\nWHERE mat_hypertable_id = :cagg_id_1w;\n                                        bucket_func                                         | bucket_width | bucket_origin | bucket_timezone | bucket_fixed_width \n--------------------------------------------------------------------------------------------+--------------+---------------+-----------------+--------------------\n timescaledb_experimental.time_bucket_ng(interval,timestamp with time zone,pg_catalog.text) | @ 7 days     |               | MSK             | f\n\n-- Check the invalidation threshold is -infinity\nSELECT _timescaledb_functions.to_timestamp(watermark) at time zone 'MSK'\nFROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nWHERE hypertable_id = :ht_id;\n timezone  \n-----------\n -infinity\n\n-- Make sure the invalidation log is empty\nSELECT\n    to_char(_timescaledb_functions.to_timestamp(lowest_modified_value) at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') AS lowest,\n    to_char(_timescaledb_functions.to_timestamp(greatest_modified_value) at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') AS greatest\nFROM _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log\nWHERE hypertable_id = :ht_id;\n lowest | greatest \n--------+----------\n\n-- Make sure truncating of the refresh window works\n\\set ON_ERROR_STOP 0\nCALL refresh_continuous_aggregate('conditions_summary_tz', '2021-07-02 MSK', '2021-07-12 MSK');\nERROR:  refresh window too small\nCALL refresh_continuous_aggregate('conditions_summary_1w', '2021-07-02 MSK', '2021-07-05 MSK');\nERROR:  refresh window too small\n\\set ON_ERROR_STOP 1\n-- Make sure refreshing works\nCALL refresh_continuous_aggregate('conditions_summary_tz', '2021-06-01 MSK', '2021-07-01 MSK');\nSELECT city, to_char(bucket at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') as month, min, max\nFROM conditions_summary_tz\nORDER by month, city;\n  city  |        month        | min | max \n--------+---------------------+-----+-----\n Moscow | 2021-06-01 00:00:00 |  22 |  34\n\n-- Check the invalidation threshold\nSELECT to_char(_timescaledb_functions.to_timestamp(watermark) at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS')\nFROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nWHERE hypertable_id = :ht_id;\n       to_char       \n---------------------\n 2021-07-01 00:00:00\n\n-- The default origin is Saturday. Here we do refresh only for two full weeks\n-- in June in order to keep the invalidation threshold as it is.\nCALL refresh_continuous_aggregate('conditions_summary_1w', '2021-06-12 MSK', '2021-06-26 MSK');\nSELECT city, to_char(bucket at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') as week, min, max\nFROM conditions_summary_1w\nORDER by week, city;\n  city  |        week         | min | max \n--------+---------------------+-----+-----\n Moscow | 2021-06-12 00:00:00 |  22 |  27\n Moscow | 2021-06-19 00:00:00 |  28 |  34\n\n-- Check the invalidation threshold\nSELECT to_char(_timescaledb_functions.to_timestamp(watermark) at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS')\nFROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nWHERE hypertable_id = :ht_id;\n       to_char       \n---------------------\n 2021-07-01 00:00:00\n\n-- Make sure creating CAGGs without NO DATA works the same way\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions_summary_tz2\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT city,\n   timescaledb_experimental.time_bucket_ng('1 month', day, 'MSK') AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions_tz\nGROUP BY city, bucket;\nNOTICE:  refreshing continuous aggregate \"conditions_summary_tz2\"\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nSELECT city, to_char(bucket at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') as month, min, max\nFROM conditions_summary_tz2\nORDER by month, city;\n  city  |        month        | min | max \n--------+---------------------+-----+-----\n Moscow | 2021-06-01 00:00:00 |  22 |  34\n\n-- Add some dummy data for two more months (no invalidations test case)\nINSERT INTO conditions_tz (day, city, temperature)\nSELECT ts, city, row_number() OVER ()\nFROM generate_series('2021-07-01 MSK' :: timestamptz, '2021-08-31 MSK', '3 day') as ts,\n     unnest(array['Moscow', 'Berlin']) as city;\n-- Double check the generated data\nSELECT to_char(day at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS'), city, temperature\nFROM conditions_tz\nWHERE day >= '2021-07-01 MSK'\nORDER BY city DESC, day;\n       to_char       |  city  | temperature \n---------------------+--------+-------------\n 2021-07-01 00:00:00 | Moscow |           1\n 2021-07-04 00:00:00 | Moscow |           2\n 2021-07-07 00:00:00 | Moscow |           3\n 2021-07-10 00:00:00 | Moscow |           4\n 2021-07-13 00:00:00 | Moscow |           5\n 2021-07-16 00:00:00 | Moscow |           6\n 2021-07-19 00:00:00 | Moscow |           7\n 2021-07-22 00:00:00 | Moscow |           8\n 2021-07-25 00:00:00 | Moscow |           9\n 2021-07-28 00:00:00 | Moscow |          10\n 2021-07-31 00:00:00 | Moscow |          11\n 2021-08-03 00:00:00 | Moscow |          12\n 2021-08-06 00:00:00 | Moscow |          13\n 2021-08-09 00:00:00 | Moscow |          14\n 2021-08-12 00:00:00 | Moscow |          15\n 2021-08-15 00:00:00 | Moscow |          16\n 2021-08-18 00:00:00 | Moscow |          17\n 2021-08-21 00:00:00 | Moscow |          18\n 2021-08-24 00:00:00 | Moscow |          19\n 2021-08-27 00:00:00 | Moscow |          20\n 2021-08-30 00:00:00 | Moscow |          21\n 2021-07-01 00:00:00 | Berlin |          22\n 2021-07-04 00:00:00 | Berlin |          23\n 2021-07-07 00:00:00 | Berlin |          24\n 2021-07-10 00:00:00 | Berlin |          25\n 2021-07-13 00:00:00 | Berlin |          26\n 2021-07-16 00:00:00 | Berlin |          27\n 2021-07-19 00:00:00 | Berlin |          28\n 2021-07-22 00:00:00 | Berlin |          29\n 2021-07-25 00:00:00 | Berlin |          30\n 2021-07-28 00:00:00 | Berlin |          31\n 2021-07-31 00:00:00 | Berlin |          32\n 2021-08-03 00:00:00 | Berlin |          33\n 2021-08-06 00:00:00 | Berlin |          34\n 2021-08-09 00:00:00 | Berlin |          35\n 2021-08-12 00:00:00 | Berlin |          36\n 2021-08-15 00:00:00 | Berlin |          37\n 2021-08-18 00:00:00 | Berlin |          38\n 2021-08-21 00:00:00 | Berlin |          39\n 2021-08-24 00:00:00 | Berlin |          40\n 2021-08-27 00:00:00 | Berlin |          41\n 2021-08-30 00:00:00 | Berlin |          42\n\n-- Make sure the invalidation threshold was unaffected\nSELECT to_char(_timescaledb_functions.to_timestamp(watermark) at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS')\nFROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nWHERE hypertable_id = :ht_id;\n       to_char       \n---------------------\n 2021-07-01 00:00:00\n\n-- Make sure the invalidation log is still empty\nSELECT\n    to_char(_timescaledb_functions.to_timestamp(lowest_modified_value) at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') AS lowest,\n    to_char(_timescaledb_functions.to_timestamp(greatest_modified_value) at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') AS greatest\nFROM _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log\nWHERE hypertable_id = :ht_id;\n lowest | greatest \n--------+----------\n\n-- Call refresh for two full buckets: 2021-07-01 and 2021-08-01\nCALL refresh_continuous_aggregate('conditions_summary_tz', '2021-06-15 MSK', '2021-09-15 MSK');\nSELECT city, to_char(bucket at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') as month, min, max\nFROM conditions_summary_tz\nORDER by month, city;\n  city  |        month        | min | max \n--------+---------------------+-----+-----\n Moscow | 2021-06-01 00:00:00 |  22 |  34\n Berlin | 2021-07-01 00:00:00 |  22 |  32\n Moscow | 2021-07-01 00:00:00 |   1 |  11\n Berlin | 2021-08-01 00:00:00 |  33 |  42\n Moscow | 2021-08-01 00:00:00 |  12 |  21\n\n-- Make sure the invalidation threshold has changed\nSELECT to_char(_timescaledb_functions.to_timestamp(watermark) at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS')\nFROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nWHERE hypertable_id = :ht_id;\n       to_char       \n---------------------\n 2021-09-01 00:00:00\n\n-- Make sure the invalidation log is still empty\nSELECT\n    to_char(_timescaledb_functions.to_timestamp(lowest_modified_value) at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') AS lowest,\n    to_char(_timescaledb_functions.to_timestamp(greatest_modified_value) at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') AS greatest\nFROM _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log\nWHERE hypertable_id = :ht_id;\n lowest | greatest \n--------+----------\n\n-- Add more data below the invalidation threshold, make sure that the\n-- invalidation log is not empty, then do a refresh.\nINSERT INTO conditions_tz (day, city, temperature)\nSELECT ts :: timestamptz, city, (CASE WHEN city = 'Moscow' THEN -100 ELSE 100 END)\nFROM generate_series('2021-08-16 MSK' :: timestamptz, '2021-08-30 MSK', '1 day') as ts,\n     unnest(array['Moscow', 'Berlin']) as city;\nSELECT\n    to_char(_timescaledb_functions.to_timestamp(lowest_modified_value) at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') AS lowest,\n    to_char(_timescaledb_functions.to_timestamp(greatest_modified_value) at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') AS greatest\nFROM _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log\nWHERE hypertable_id = :ht_id;\n       lowest        |      greatest       \n---------------------+---------------------\n 2021-08-16 00:00:00 | 2021-08-16 00:00:00\n 2021-08-17 00:00:00 | 2021-08-17 00:00:00\n 2021-08-18 00:00:00 | 2021-08-18 00:00:00\n 2021-08-19 00:00:00 | 2021-08-19 00:00:00\n 2021-08-20 00:00:00 | 2021-08-20 00:00:00\n 2021-08-21 00:00:00 | 2021-08-21 00:00:00\n 2021-08-22 00:00:00 | 2021-08-22 00:00:00\n 2021-08-23 00:00:00 | 2021-08-23 00:00:00\n 2021-08-24 00:00:00 | 2021-08-24 00:00:00\n 2021-08-25 00:00:00 | 2021-08-25 00:00:00\n 2021-08-26 00:00:00 | 2021-08-26 00:00:00\n 2021-08-27 00:00:00 | 2021-08-27 00:00:00\n 2021-08-28 00:00:00 | 2021-08-28 00:00:00\n 2021-08-29 00:00:00 | 2021-08-29 00:00:00\n 2021-08-30 00:00:00 | 2021-08-30 00:00:00\n\nCALL refresh_continuous_aggregate('conditions_summary_tz', '2021-08-01 MSK', '2021-09-01 MSK');\nSELECT city, to_char(bucket at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') as month, min, max\nFROM conditions_summary_tz\nORDER by month, city;\n  city  |        month        | min  | max \n--------+---------------------+------+-----\n Moscow | 2021-06-01 00:00:00 |   22 |  34\n Berlin | 2021-07-01 00:00:00 |   22 |  32\n Moscow | 2021-07-01 00:00:00 |    1 |  11\n Berlin | 2021-08-01 00:00:00 |   33 | 100\n Moscow | 2021-08-01 00:00:00 | -100 |  21\n\nSELECT\n    to_char(_timescaledb_functions.to_timestamp(lowest_modified_value) at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') AS lowest,\n    to_char(_timescaledb_functions.to_timestamp(greatest_modified_value) at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') AS greatest\nFROM _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log\nWHERE hypertable_id = :ht_id;\n lowest | greatest \n--------+----------\n\n-- Clean up\nDROP MATERIALIZED VIEW conditions_summary_tz;\nNOTICE:  drop cascades to 3 other objects\nDROP MATERIALIZED VIEW conditions_summary_1w;\nNOTICE:  drop cascades to 2 other objects\n-- Create a real-time aggregate\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions_summary_tz\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT city,\n   timescaledb_experimental.time_bucket_ng('1 month', day, 'MSK') AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions_tz\nGROUP BY city, bucket;\nNOTICE:  refreshing continuous aggregate \"conditions_summary_tz\"\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nSELECT city, to_char(bucket at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') as month, min, max\nFROM conditions_summary_tz\nORDER by month, city;\n  city  |        month        | min  | max \n--------+---------------------+------+-----\n Moscow | 2021-06-01 00:00:00 |   22 |  34\n Berlin | 2021-07-01 00:00:00 |   22 |  32\n Moscow | 2021-07-01 00:00:00 |    1 |  11\n Berlin | 2021-08-01 00:00:00 |   33 | 100\n Moscow | 2021-08-01 00:00:00 | -100 |  21\n\n-- Add some data to the hypertable and make sure they are visible in the cagg\nINSERT INTO conditions_tz (day, city, temperature) VALUES\n  ('2021-10-01 00:00:00 MSK', 'Moscow', 1),\n  ('2021-10-02 00:00:00 MSK', 'Moscow', 2),\n  ('2021-10-03 00:00:00 MSK', 'Moscow', 3),\n  ('2021-10-04 00:00:00 MSK', 'Moscow', 4),\n  ('2021-10-01 00:00:00 MSK', 'Berlin', 5),\n  ('2021-10-02 00:00:00 MSK', 'Berlin', 6),\n  ('2021-10-03 00:00:00 MSK', 'Berlin', 7),\n  ('2021-10-04 00:00:00 MSK', 'Berlin', 8);\nSELECT city, to_char(bucket at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') as month, min, max\nFROM conditions_summary_tz\nORDER by month, city;\n  city  |        month        | min  | max \n--------+---------------------+------+-----\n Moscow | 2021-06-01 00:00:00 |   22 |  34\n Berlin | 2021-07-01 00:00:00 |   22 |  32\n Moscow | 2021-07-01 00:00:00 |    1 |  11\n Berlin | 2021-08-01 00:00:00 |   33 | 100\n Moscow | 2021-08-01 00:00:00 | -100 |  21\n Berlin | 2021-10-01 00:00:00 |    5 |   8\n Moscow | 2021-10-01 00:00:00 |    1 |   4\n\n-- Refresh the cagg and make sure that the result of SELECT query didn't change\nCALL refresh_continuous_aggregate('conditions_summary_tz', '2021-10-01 00:00:00 MSK', '2021-11-01 00:00:00 MSK');\nSELECT city, to_char(bucket at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') as month, min, max\nFROM conditions_summary_tz\nORDER by month, city;\n  city  |        month        | min  | max \n--------+---------------------+------+-----\n Moscow | 2021-06-01 00:00:00 |   22 |  34\n Berlin | 2021-07-01 00:00:00 |   22 |  32\n Moscow | 2021-07-01 00:00:00 |    1 |  11\n Berlin | 2021-08-01 00:00:00 |   33 | 100\n Moscow | 2021-08-01 00:00:00 | -100 |  21\n Berlin | 2021-10-01 00:00:00 |    5 |   8\n Moscow | 2021-10-01 00:00:00 |    1 |   4\n\n-- Add some more data, enable compression, compress the chunks and repeat the test\nINSERT INTO conditions_tz (day, city, temperature) VALUES\n  ('2021-11-01 00:00:00 MSK', 'Moscow', 11),\n  ('2021-11-02 00:00:00 MSK', 'Moscow', 12),\n  ('2021-11-03 00:00:00 MSK', 'Moscow', 13),\n  ('2021-11-04 00:00:00 MSK', 'Moscow', 14),\n  ('2021-11-01 00:00:00 MSK', 'Berlin', 15),\n  ('2021-11-02 00:00:00 MSK', 'Berlin', 16),\n  ('2021-11-03 00:00:00 MSK', 'Berlin', 17),\n  ('2021-11-04 00:00:00 MSK', 'Berlin', 18);\nALTER TABLE conditions_tz SET (\n    timescaledb.compress,\n    timescaledb.compress_segmentby = 'city'\n);\nSELECT compress_chunk(ch) FROM show_chunks('conditions_tz') AS ch;\n             compress_chunk              \n-----------------------------------------\n _timescaledb_internal._hyper_2_1_chunk\n _timescaledb_internal._hyper_2_2_chunk\n _timescaledb_internal._hyper_2_3_chunk\n _timescaledb_internal._hyper_2_4_chunk\n _timescaledb_internal._hyper_2_5_chunk\n _timescaledb_internal._hyper_2_6_chunk\n _timescaledb_internal._hyper_2_7_chunk\n _timescaledb_internal._hyper_2_8_chunk\n _timescaledb_internal._hyper_2_9_chunk\n _timescaledb_internal._hyper_2_10_chunk\n _timescaledb_internal._hyper_2_11_chunk\n _timescaledb_internal._hyper_2_12_chunk\n _timescaledb_internal._hyper_2_13_chunk\n _timescaledb_internal._hyper_2_14_chunk\n _timescaledb_internal._hyper_2_19_chunk\n _timescaledb_internal._hyper_2_20_chunk\n _timescaledb_internal._hyper_2_21_chunk\n _timescaledb_internal._hyper_2_22_chunk\n _timescaledb_internal._hyper_2_23_chunk\n _timescaledb_internal._hyper_2_24_chunk\n _timescaledb_internal._hyper_2_25_chunk\n _timescaledb_internal._hyper_2_26_chunk\n _timescaledb_internal._hyper_2_27_chunk\n _timescaledb_internal._hyper_2_28_chunk\n _timescaledb_internal._hyper_2_29_chunk\n _timescaledb_internal._hyper_2_30_chunk\n _timescaledb_internal._hyper_2_31_chunk\n _timescaledb_internal._hyper_2_32_chunk\n _timescaledb_internal._hyper_2_33_chunk\n _timescaledb_internal._hyper_2_34_chunk\n _timescaledb_internal._hyper_2_35_chunk\n _timescaledb_internal._hyper_2_36_chunk\n _timescaledb_internal._hyper_2_37_chunk\n _timescaledb_internal._hyper_2_38_chunk\n _timescaledb_internal._hyper_2_39_chunk\n _timescaledb_internal._hyper_2_42_chunk\n _timescaledb_internal._hyper_2_43_chunk\n _timescaledb_internal._hyper_2_44_chunk\n _timescaledb_internal._hyper_2_45_chunk\n _timescaledb_internal._hyper_2_46_chunk\n _timescaledb_internal._hyper_2_47_chunk\n _timescaledb_internal._hyper_2_48_chunk\n _timescaledb_internal._hyper_2_49_chunk\n _timescaledb_internal._hyper_2_50_chunk\n _timescaledb_internal._hyper_2_51_chunk\n _timescaledb_internal._hyper_2_55_chunk\n _timescaledb_internal._hyper_2_56_chunk\n _timescaledb_internal._hyper_2_57_chunk\n _timescaledb_internal._hyper_2_58_chunk\n _timescaledb_internal._hyper_2_60_chunk\n _timescaledb_internal._hyper_2_61_chunk\n _timescaledb_internal._hyper_2_62_chunk\n _timescaledb_internal._hyper_2_63_chunk\n\n-- Data for 2021-11 is seen because the cagg is real-time\nSELECT city, to_char(bucket at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') as month, min, max\nFROM conditions_summary_tz\nORDER by month, city;\n  city  |        month        | min  | max \n--------+---------------------+------+-----\n Moscow | 2021-06-01 00:00:00 |   22 |  34\n Berlin | 2021-07-01 00:00:00 |   22 |  32\n Moscow | 2021-07-01 00:00:00 |    1 |  11\n Berlin | 2021-08-01 00:00:00 |   33 | 100\n Moscow | 2021-08-01 00:00:00 | -100 |  21\n Berlin | 2021-10-01 00:00:00 |    5 |   8\n Moscow | 2021-10-01 00:00:00 |    1 |   4\n Berlin | 2021-11-01 00:00:00 |   15 |  18\n Moscow | 2021-11-01 00:00:00 |   11 |  14\n\nCALL refresh_continuous_aggregate('conditions_summary_tz', '2021-11-01 00:00:00 MSK', '2021-12-01 00:00:00 MSK');\n-- Data for 2021-11 is seen because the cagg was refreshed\nSELECT city, to_char(bucket at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') as month, min, max\nFROM conditions_summary_tz\nORDER by month, city;\n  city  |        month        | min  | max \n--------+---------------------+------+-----\n Moscow | 2021-06-01 00:00:00 |   22 |  34\n Berlin | 2021-07-01 00:00:00 |   22 |  32\n Moscow | 2021-07-01 00:00:00 |    1 |  11\n Berlin | 2021-08-01 00:00:00 |   33 | 100\n Moscow | 2021-08-01 00:00:00 | -100 |  21\n Berlin | 2021-10-01 00:00:00 |    5 |   8\n Moscow | 2021-10-01 00:00:00 |    1 |   4\n Berlin | 2021-11-01 00:00:00 |   15 |  18\n Moscow | 2021-11-01 00:00:00 |   11 |  14\n\n-- Test for some more cases: single CAGG per HT, creating CAGG on top of an\n-- empty HT, buckets other than 1 month.\nCREATE TABLE conditions2(\n  day timestamptz NOT NULL,\n  city text NOT NULL,\n  temperature INT NOT NULL);\nSELECT create_hypertable(\n  'conditions2', 'day',\n  chunk_time_interval => INTERVAL '1 day'\n);\n    create_hypertable     \n--------------------------\n (8,public,conditions2,t)\n\n-- Create a real-time aggregate on top of empty HT\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions2_summary\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT city,\n   timescaledb_experimental.time_bucket_ng('7 days', day, 'MSK') AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions2\nGROUP BY city, bucket;\nNOTICE:  continuous aggregate \"conditions2_summary\" is already up-to-date\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nINSERT INTO conditions2 (day, city, temperature) VALUES\n  ('2021-06-14 00:00:00 MSK', 'Moscow', 26),\n  ('2021-06-15 00:00:00 MSK', 'Moscow', 22),\n  ('2021-06-16 00:00:00 MSK', 'Moscow', 24),\n  ('2021-06-17 00:00:00 MSK', 'Moscow', 24),\n  ('2021-06-18 00:00:00 MSK', 'Moscow', 27),\n  ('2021-06-19 00:00:00 MSK', 'Moscow', 28),\n  ('2021-06-20 00:00:00 MSK', 'Moscow', 30),\n  ('2021-06-21 00:00:00 MSK', 'Moscow', 31),\n  ('2021-06-22 00:00:00 MSK', 'Moscow', 34),\n  ('2021-06-23 00:00:00 MSK', 'Moscow', 34),\n  ('2021-06-24 00:00:00 MSK', 'Moscow', 34),\n  ('2021-06-25 00:00:00 MSK', 'Moscow', 32),\n  ('2021-06-26 00:00:00 MSK', 'Moscow', 32),\n  ('2021-06-27 00:00:00 MSK', 'Moscow', 31);\n-- All data should be seen for a real-time aggregate\nSELECT city, to_char(bucket at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') as month, min, max\nFROM conditions2_summary\nORDER by month, city;\n  city  |        month        | min | max \n--------+---------------------+-----+-----\n Moscow | 2021-06-12 00:00:00 |  22 |  27\n Moscow | 2021-06-19 00:00:00 |  28 |  34\n Moscow | 2021-06-26 00:00:00 |  31 |  32\n\n-- Refresh should work\nCALL refresh_continuous_aggregate('conditions2_summary', '2021-06-12 MSK', '2021-07-03 MSK');\nSELECT city, to_char(bucket at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') as month, min, max\nFROM conditions2_summary\nORDER by month, city;\n  city  |        month        | min | max \n--------+---------------------+-----+-----\n Moscow | 2021-06-12 00:00:00 |  22 |  27\n Moscow | 2021-06-19 00:00:00 |  28 |  34\n Moscow | 2021-06-26 00:00:00 |  31 |  32\n\n-- New data should be seen\nINSERT INTO conditions2 (day, city, temperature) VALUES\n  ('2021-09-30 00:00:00 MSK', 'Moscow', 0),\n  ('2021-10-01 00:00:00 MSK', 'Moscow', 1),\n  ('2021-10-02 00:00:00 MSK', 'Moscow', 2),\n  ('2021-10-03 00:00:00 MSK', 'Moscow', 3),\n  ('2021-10-04 00:00:00 MSK', 'Moscow', 4),\n  ('2021-09-30 00:00:00 MSK', 'Berlin', 5),\n  ('2021-10-01 00:00:00 MSK', 'Berlin', 6),\n  ('2021-10-02 00:00:00 MSK', 'Berlin', 7),\n  ('2021-10-03 00:00:00 MSK', 'Berlin', 8),\n  ('2021-10-04 00:00:00 MSK', 'Berlin', 9);\nSELECT city, to_char(bucket at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') as month, min, max\nFROM conditions2_summary\nORDER by month, city;\n  city  |        month        | min | max \n--------+---------------------+-----+-----\n Moscow | 2021-06-12 00:00:00 |  22 |  27\n Moscow | 2021-06-19 00:00:00 |  28 |  34\n Moscow | 2021-06-26 00:00:00 |  31 |  32\n Berlin | 2021-09-25 00:00:00 |   5 |   6\n Moscow | 2021-09-25 00:00:00 |   0 |   1\n Berlin | 2021-10-02 00:00:00 |   7 |   9\n Moscow | 2021-10-02 00:00:00 |   2 |   4\n\n-- Make sure add_continuous_aggregate_policy() works\nCREATE TABLE conditions_policy(\n  day TIMESTAMPTZ NOT NULL,\n  city text NOT NULL,\n  temperature INT NOT NULL);\nSELECT create_hypertable(\n  'conditions_policy', 'day',\n  chunk_time_interval => INTERVAL '1 day'\n);\n        create_hypertable        \n---------------------------------\n (10,public,conditions_policy,t)\n\nINSERT INTO conditions_policy (day, city, temperature) VALUES\n  ('2021-06-14 00:00:00 MSK', 'Moscow', 26),\n  ('2021-06-14 10:00:00 MSK', 'Moscow', 22),\n  ('2021-06-14 20:00:00 MSK', 'Moscow', 24),\n  ('2021-06-15 00:00:00 MSK', 'Moscow', 24),\n  ('2021-06-15 10:00:00 MSK', 'Moscow', 27),\n  ('2021-06-15 20:00:00 MSK', 'Moscow', 28),\n  ('2021-06-16 00:00:00 MSK', 'Moscow', 30),\n  ('2021-06-16 10:00:00 MSK', 'Moscow', 31),\n  ('2021-06-16 20:00:00 MSK', 'Moscow', 34),\n  ('2021-06-17 00:00:00 MSK', 'Moscow', 34),\n  ('2021-06-17 10:00:00 MSK', 'Moscow', 34),\n  ('2021-06-17 20:00:00 MSK', 'Moscow', 32),\n  ('2021-06-18 00:00:00 MSK', 'Moscow', 32),\n  ('2021-06-18 10:00:00 MSK', 'Moscow', 31),\n  ('2021-06-18 20:00:00 MSK', 'Moscow', 26);\nSET timescaledb.debug_allow_cagg_with_deprecated_funcs = true;\nCREATE MATERIALIZED VIEW conditions_summary_policy\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT city,\n   timescaledb_experimental.time_bucket_ng('1 day', day, 'Europe/Moscow') AS bucket,\n   MIN(temperature),\n   MAX(temperature)\nFROM conditions_policy\nGROUP BY city, bucket;\nNOTICE:  refreshing continuous aggregate \"conditions_summary_policy\"\nRESET timescaledb.debug_allow_cagg_with_deprecated_funcs;\nSELECT city, to_char(bucket at time zone 'MSK', 'YYYY-MM-DD HH24:MI:SS') as month, min, max\nFROM conditions_summary_policy\nORDER by month, city;\n  city  |        month        | min | max \n--------+---------------------+-----+-----\n Moscow | 2021-06-14 00:00:00 |  22 |  26\n Moscow | 2021-06-15 00:00:00 |  24 |  28\n Moscow | 2021-06-16 00:00:00 |  30 |  34\n Moscow | 2021-06-17 00:00:00 |  32 |  34\n Moscow | 2021-06-18 00:00:00 |  26 |  32\n\n\\set ON_ERROR_STOP 0\n-- Check for \"policy refresh window too small\" error\nSELECT add_continuous_aggregate_policy('conditions_summary_policy',\n    start_offset => INTERVAL '2 days 23 hours',\n    end_offset => INTERVAL '1 day',\n    schedule_interval => INTERVAL '1 hour');\nERROR:  policy refresh window too small\n\\set ON_ERROR_STOP 1\nSELECT add_continuous_aggregate_policy('conditions_summary_policy',\n    start_offset => INTERVAL '3 days',\n    end_offset => INTERVAL '1 day',\n    schedule_interval => INTERVAL '1 hour');\n add_continuous_aggregate_policy \n---------------------------------\n                            1000\n\n"
  },
  {
    "path": "tsl/test/expected/cagg_invalidation.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- Disable background workers since we are testing manual refresh\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT _timescaledb_functions.stop_background_workers();\n stop_background_workers \n-------------------------\n t\n\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSET datestyle TO 'ISO, YMD';\nSET timezone TO 'UTC';\nCREATE VIEW hypertable_invalidation_thresholds AS\nSELECT format('%I.%I', ht.schema_name, ht.table_name)::regclass AS hypertable,\n       watermark AS threshold\n  FROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\n  JOIN _timescaledb_catalog.hypertable ht\n    ON hypertable_id = ht.id\nORDER BY 1;\nCREATE TABLE conditions (time bigint NOT NULL, device int, temp float);\nSELECT create_hypertable('conditions', 'time', chunk_time_interval => 10);\n    create_hypertable    \n-------------------------\n (1,public,conditions,t)\n\nCREATE TABLE measurements (time int NOT NULL, device int, temp float);\nSELECT create_hypertable('measurements', 'time', chunk_time_interval => 10);\n     create_hypertable     \n---------------------------\n (2,public,measurements,t)\n\nCREATE OR REPLACE FUNCTION bigint_now()\nRETURNS bigint LANGUAGE SQL STABLE AS\n$$\n    SELECT coalesce(max(time), 0)\n    FROM conditions\n$$;\nCREATE OR REPLACE FUNCTION int_now()\nRETURNS int LANGUAGE SQL STABLE AS\n$$\n    SELECT coalesce(max(time), 0)\n    FROM measurements\n$$;\nSELECT set_integer_now_func('conditions', 'bigint_now');\n set_integer_now_func \n----------------------\n \n\nSELECT set_integer_now_func('measurements', 'int_now');\n set_integer_now_func \n----------------------\n \n\nINSERT INTO conditions\nSELECT t, ceil(abs(timestamp_hash(to_timestamp(t)::timestamp))%4)::int,\n       abs(timestamp_hash(to_timestamp(t)::timestamp))%40\nFROM generate_series(1, 100, 1) t;\nCREATE TABLE temp AS\nSELECT * FROM conditions;\nINSERT INTO measurements\nSELECT * FROM temp;\n-- Show the most recent data\nSELECT * FROM conditions\nORDER BY time DESC, device\nLIMIT 10;\n time | device | temp \n------+--------+------\n  100 |      0 |    8\n   99 |      1 |    5\n   98 |      2 |   26\n   97 |      2 |   10\n   96 |      2 |   34\n   95 |      2 |   30\n   94 |      3 |   31\n   93 |      0 |    4\n   92 |      0 |   32\n   91 |      3 |   15\n\n-- Create two continuous aggregates on the same hypertable to test\n-- that invalidations are handled correctly across both of them.\nCREATE MATERIALIZED VIEW cond_10\nWITH (timescaledb.continuous,\n      timescaledb.materialized_only=true)\nAS\nSELECT time_bucket(BIGINT '10', time) AS bucket, device, avg(temp) AS avg_temp\nFROM conditions\nGROUP BY 1,2 WITH NO DATA;\nCREATE MATERIALIZED VIEW cond_20\nWITH (timescaledb.continuous,\n      timescaledb.materialized_only=true)\nAS\nSELECT time_bucket(BIGINT '20', time) AS bucket, device, avg(temp) AS avg_temp\nFROM conditions\nGROUP BY 1,2 WITH NO DATA;\nCREATE MATERIALIZED VIEW measure_10\nWITH (timescaledb.continuous,\n      timescaledb.materialized_only=true)\nAS\nSELECT time_bucket(10, time) AS bucket, device, avg(temp) AS avg_temp\nFROM measurements\nGROUP BY 1,2 WITH NO DATA;\n-- There should be three continuous aggregates, two on one hypertable\n-- and one on the other:\nSELECT mat_hypertable_id, raw_hypertable_id, user_view_name\nFROM _timescaledb_catalog.continuous_agg;\n mat_hypertable_id | raw_hypertable_id | user_view_name \n-------------------+-------------------+----------------\n                 3 |                 1 | cond_10\n                 4 |                 1 | cond_20\n                 5 |                 2 | measure_10\n\n-- The continuous aggregates should be empty\nSELECT * FROM cond_10\nORDER BY 1 DESC, 2;\n bucket | device | avg_temp \n--------+--------+----------\n\nSELECT * FROM cond_20\nORDER BY 1 DESC, 2;\n bucket | device | avg_temp \n--------+--------+----------\n\nSELECT * FROM measure_10\nORDER BY 1 DESC, 2;\n bucket | device | avg_temp \n--------+--------+----------\n\nCREATE OR REPLACE FUNCTION get_hyper_invals() RETURNS  TABLE (\n      \"hyper_id\" INT,\n      \"start\" BIGINT,\n      \"end\" BIGINT\n)\nLANGUAGE SQL VOLATILE AS\n$$\nSELECT hypertable_id,\n       lowest_modified_value,\n       greatest_modified_value\n       FROM _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log\n       ORDER BY 1,2,3\n$$;\nCREATE OR REPLACE FUNCTION get_cagg_invals() RETURNS TABLE (\n      \"cagg_id\" INT,\n      \"start\" BIGINT,\n      \"end\" BIGINT\n)\nLANGUAGE SQL VOLATILE AS\n$$\nSELECT materialization_id,\n       lowest_modified_value,\n       greatest_modified_value\n       FROM _timescaledb_catalog.continuous_aggs_materialization_invalidation_log\n       ORDER BY 1,2,3\n$$;\nCREATE VIEW hyper_invals AS SELECT * FROM get_hyper_invals();\nCREATE VIEW cagg_invals AS SELECT * FROM get_cagg_invals();\n-- Must refresh to move the invalidation threshold, or no\n-- invalidations will be generated. Initially, threshold is the\n-- MIN of the time dimension data type:\nSELECT * FROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nORDER BY 1,2;\n hypertable_id |      watermark       \n---------------+----------------------\n             1 | -9223372036854775808\n             2 |          -2147483648\n\n-- There should be only \"infinite\" invalidations in the cagg\n-- invalidation log:\nSELECT * FROM cagg_invals;\n cagg_id |        start         |         end         \n---------+----------------------+---------------------\n       3 | -9223372036854775808 | 9223372036854775807\n       4 | -9223372036854775808 | 9223372036854775807\n       5 | -9223372036854775808 | 9223372036854775807\n\n-- Now refresh up to 50 without the first bucket, and the threshold should be updated accordingly:\nCALL refresh_continuous_aggregate('cond_10', 1, 50);\nSELECT * FROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nORDER BY 1,2;\n hypertable_id |  watermark  \n---------------+-------------\n             1 |          50\n             2 | -2147483648\n\n-- Invalidations should be cleared inside the refresh window:\nSELECT * FROM cagg_invals;\n cagg_id |        start         |         end         \n---------+----------------------+---------------------\n       3 | -9223372036854775808 |                   9\n       3 |                   50 | 9223372036854775807\n       4 | -9223372036854775808 | 9223372036854775807\n       5 | -9223372036854775808 | 9223372036854775807\n\n-- Refresh up to 50 from the beginning\nCALL refresh_continuous_aggregate('cond_10', 0, 50);\nSELECT * FROM cagg_invals;\n cagg_id |        start         |         end         \n---------+----------------------+---------------------\n       3 | -9223372036854775808 |                  -1\n       3 |                   50 | 9223372036854775807\n       4 | -9223372036854775808 | 9223372036854775807\n       5 | -9223372036854775808 | 9223372036854775807\n\n-- Refreshing below the threshold does not move it:\nCALL refresh_continuous_aggregate('cond_10', 20, 49);\nNOTICE:  continuous aggregate \"cond_10\" is already up-to-date\nSELECT * FROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nORDER BY 1,2;\n hypertable_id |  watermark  \n---------------+-------------\n             1 |          50\n             2 | -2147483648\n\n-- Nothing changes with invalidations either since the region was\n-- already refreshed and no new invalidations have been generated:\nSELECT * FROM cagg_invals;\n cagg_id |        start         |         end         \n---------+----------------------+---------------------\n       3 | -9223372036854775808 |                  -1\n       3 |                   50 | 9223372036854775807\n       4 | -9223372036854775808 | 9223372036854775807\n       5 | -9223372036854775808 | 9223372036854775807\n\n-- Refreshing measure_10 moves the threshold only for the other hypertable:\nCALL refresh_continuous_aggregate('measure_10', 0, 30);\nSELECT * FROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nORDER BY 1,2;\n hypertable_id | watermark \n---------------+-----------\n             1 |        50\n             2 |        30\n\nSELECT * FROM cagg_invals;\n cagg_id |        start         |         end         \n---------+----------------------+---------------------\n       3 | -9223372036854775808 |                  -1\n       3 |                   50 | 9223372036854775807\n       4 | -9223372036854775808 | 9223372036854775807\n       5 | -9223372036854775808 |                  -1\n       5 |                   30 | 9223372036854775807\n\n-- Refresh on the second continuous aggregate, cond_20, on the first\n-- hypertable moves the same threshold as when refreshing cond_10:\nCALL refresh_continuous_aggregate('cond_20', 60, 100);\nSELECT * FROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nORDER BY 1,2;\n hypertable_id | watermark \n---------------+-----------\n             1 |       100\n             2 |        30\n\nSELECT * FROM cagg_invals;\n cagg_id |        start         |         end         \n---------+----------------------+---------------------\n       3 | -9223372036854775808 |                  -1\n       3 |                   50 | 9223372036854775807\n       4 | -9223372036854775808 |                  59\n       4 |                  100 | 9223372036854775807\n       5 | -9223372036854775808 |                  -1\n       5 |                   30 | 9223372036854775807\n\n-- There should be no hypertable invalidations initially:\nSELECT * FROM hyper_invals;\n hyper_id | start | end \n----------+-------+-----\n\nSELECT * FROM cagg_invals;\n cagg_id |        start         |         end         \n---------+----------------------+---------------------\n       3 | -9223372036854775808 |                  -1\n       3 |                   50 | 9223372036854775807\n       4 | -9223372036854775808 |                  59\n       4 |                  100 | 9223372036854775807\n       5 | -9223372036854775808 |                  -1\n       5 |                   30 | 9223372036854775807\n\n-- Create invalidations across different ranges. Some of these should\n-- be deleted and others cut in different ways when a refresh is\n-- run. Note that the refresh window is inclusive in the start of the\n-- window but exclusive at the end.\n-- Entries that should be left unmodified:\nINSERT INTO conditions VALUES (10, 4, 23.7);\nINSERT INTO conditions VALUES (10, 5, 23.8), (19, 3, 23.6);\nINSERT INTO conditions VALUES (60, 3, 23.7), (70, 4, 23.7);\n-- Should see some invaliations in the hypertable invalidation log:\nSELECT * FROM hyper_invals;\n hyper_id | start | end \n----------+-------+-----\n        1 |    10 |  10\n        1 |    10 |  19\n        1 |    60 |  60\n        1 |    70 |  70\n\n-- Generate some invalidations for the other hypertable\nINSERT INTO measurements VALUES (20, 4, 23.7);\nINSERT INTO measurements VALUES (30, 5, 23.8), (80, 3, 23.6);\n-- Should now see invalidations for both hypertables\nSELECT * FROM hyper_invals;\n hyper_id | start | end \n----------+-------+-----\n        1 |    10 |  10\n        1 |    10 |  19\n        1 |    60 |  60\n        1 |    70 |  70\n        2 |    20 |  20\n\n-- First refresh a window where we don't have any invalidations. This\n-- allows us to see only the copying of the invalidations to the per\n-- cagg log without additional processing.\nCALL refresh_continuous_aggregate('cond_10', 20, 60);\n-- Invalidation threshold remains at 100:\nSELECT * FROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nORDER BY 1,2;\n hypertable_id | watermark \n---------------+-----------\n             1 |       100\n             2 |        30\n\n-- Invalidations should be moved from the hypertable invalidation log\n-- to the continuous aggregate log, but only for the hypertable that\n-- the refreshed aggregate belongs to:\nSELECT * FROM hyper_invals;\n hyper_id | start | end \n----------+-------+-----\n        2 |    20 |  20\n\nSELECT * FROM cagg_invals;\n cagg_id |        start         |         end         \n---------+----------------------+---------------------\n       3 | -9223372036854775808 |                  -1\n       3 |                   10 |                  19\n       3 |                   60 | 9223372036854775807\n       4 | -9223372036854775808 |                  59\n       4 |                    0 |                  19\n       4 |                   60 |                  79\n       4 |                  100 | 9223372036854775807\n       5 | -9223372036854775808 |                  -1\n       5 |                   30 | 9223372036854775807\n\n-- Now add more invalidations to test a refresh that overlaps with them.\n-- Entries that should be deleted:\nINSERT INTO conditions VALUES (30, 1, 23.4), (59, 1, 23.4);\nINSERT INTO conditions VALUES (20, 1, 23.4), (30, 1, 23.4);\n-- Entries that should be cut to the right, leaving an invalidation to\n-- the left of the refresh window:\nINSERT INTO conditions VALUES (1, 4, 23.7), (25, 1, 23.4);\nINSERT INTO conditions VALUES (19, 4, 23.7), (59, 1, 23.4);\n-- Entries that should be cut to the left and right, leaving two\n-- invalidation entries on each side of the refresh window:\nINSERT INTO conditions VALUES (2, 2, 23.5), (60, 1, 23.4);\nINSERT INTO conditions VALUES (3, 2, 23.5), (80, 1, 23.4);\n-- Entries that should be cut to the left, leaving an invalidation to\n-- the right of the refresh window:\nINSERT INTO conditions VALUES (60, 3, 23.6), (90, 3, 23.6);\nINSERT INTO conditions VALUES (20, 5, 23.8), (100, 3, 23.6);\n-- New invalidations in the hypertable invalidation log:\nSELECT * FROM hyper_invals;\n hyper_id | start | end \n----------+-------+-----\n        1 |     1 |   1\n        1 |     2 |   2\n        1 |     3 |   3\n        1 |    19 |  19\n        1 |    20 |  20\n        1 |    20 |  20\n        1 |    25 |  25\n        1 |    30 |  30\n        1 |    30 |  30\n        1 |    59 |  59\n        1 |    59 |  59\n        1 |    60 |  60\n        1 |    60 |  60\n        1 |    80 |  80\n        1 |    90 |  90\n        2 |    20 |  20\n\n-- But nothing has yet changed in the cagg invalidation log:\nSELECT * FROM cagg_invals;\n cagg_id |        start         |         end         \n---------+----------------------+---------------------\n       3 | -9223372036854775808 |                  -1\n       3 |                   10 |                  19\n       3 |                   60 | 9223372036854775807\n       4 | -9223372036854775808 |                  59\n       4 |                    0 |                  19\n       4 |                   60 |                  79\n       4 |                  100 | 9223372036854775807\n       5 | -9223372036854775808 |                  -1\n       5 |                   30 | 9223372036854775807\n\n-- Refresh to process invalidations for daily temperature:\nCALL refresh_continuous_aggregate('cond_10', 20, 60);\n-- Invalidations should be moved from the hypertable invalidation log\n-- to the continuous aggregate log.\nSELECT * FROM hyper_invals;\n hyper_id | start | end \n----------+-------+-----\n        2 |    20 |  20\n\n-- Only the cond_10 cagg should have its entries cut:\nSELECT * FROM cagg_invals;\n cagg_id |        start         |         end         \n---------+----------------------+---------------------\n       3 | -9223372036854775808 |                  19\n       3 |                   60 | 9223372036854775807\n       4 | -9223372036854775808 |                  59\n       4 |                    0 |                  19\n       4 |                    0 |                  99\n       4 |                   60 |                  79\n       4 |                  100 | 9223372036854775807\n       5 | -9223372036854775808 |                  -1\n       5 |                   30 | 9223372036854775807\n\n-- Refresh also cond_20:\nCALL refresh_continuous_aggregate('cond_20', 20, 60);\n-- The cond_20 cagg should also have its entries cut:\nSELECT * FROM cagg_invals;\n cagg_id |        start         |         end         \n---------+----------------------+---------------------\n       3 | -9223372036854775808 |                  19\n       3 |                   60 | 9223372036854775807\n       4 | -9223372036854775808 |                  19\n       4 |                   60 | 9223372036854775807\n       5 | -9223372036854775808 |                  -1\n       5 |                   30 | 9223372036854775807\n\n-- Refresh cond_10 to completely remove an invalidation:\nCALL refresh_continuous_aggregate('cond_10', 0, 20);\n-- The 1-19 invalidation should be deleted:\nSELECT * FROM cagg_invals;\n cagg_id |        start         |         end         \n---------+----------------------+---------------------\n       3 | -9223372036854775808 |                  -1\n       3 |                   60 | 9223372036854775807\n       4 | -9223372036854775808 |                  19\n       4 |                   60 | 9223372036854775807\n       5 | -9223372036854775808 |                  -1\n       5 |                   30 | 9223372036854775807\n\n-- Clear everything between 0 and 100 to make way for new\n-- invalidations\nCALL refresh_continuous_aggregate('cond_10', 0, 100);\n-- Test refreshing with non-overlapping invalidations\nINSERT INTO conditions VALUES (20, 1, 23.4), (25, 1, 23.4);\nINSERT INTO conditions VALUES (30, 1, 23.4), (46, 1, 23.4);\nCALL refresh_continuous_aggregate('cond_10', 1, 40);\nSELECT * FROM cagg_invals;\n cagg_id |        start         |         end         \n---------+----------------------+---------------------\n       3 | -9223372036854775808 |                  -1\n       3 |                   40 |                  49\n       3 |                  100 | 9223372036854775807\n       4 | -9223372036854775808 |                  19\n       4 |                   20 |                  59\n       4 |                   60 | 9223372036854775807\n       5 | -9223372036854775808 |                  -1\n       5 |                   30 | 9223372036854775807\n\n-- Refresh whithout cutting (in area where there are no\n-- invalidations). Merging of overlapping entries should still happen:\nINSERT INTO conditions VALUES (15, 1, 23.4), (42, 1, 23.4);\nCALL refresh_continuous_aggregate('cond_10', 90, 100);\nNOTICE:  continuous aggregate \"cond_10\" is already up-to-date\nSELECT * FROM cagg_invals;\n cagg_id |        start         |         end         \n---------+----------------------+---------------------\n       3 | -9223372036854775808 |                  -1\n       3 |                   10 |                  19\n       3 |                   40 |                  49\n       3 |                  100 | 9223372036854775807\n       4 | -9223372036854775808 |                  19\n       4 |                    0 |                  19\n       4 |                   20 |                  59\n       4 |                   40 |                  59\n       4 |                   60 | 9223372036854775807\n       5 | -9223372036854775808 |                  -1\n       5 |                   30 | 9223372036854775807\n\n-- Test max refresh window\nCALL refresh_continuous_aggregate('cond_10', NULL, NULL);\nSELECT * FROM cagg_invals;\n cagg_id |        start         |         end         \n---------+----------------------+---------------------\n       3 |                  110 | 9223372036854775807\n       4 | -9223372036854775808 |                  19\n       4 |                    0 |                  19\n       4 |                   20 |                  59\n       4 |                   40 |                  59\n       4 |                   60 | 9223372036854775807\n       5 | -9223372036854775808 |                  -1\n       5 |                   30 | 9223372036854775807\n\nSELECT * FROM hyper_invals;\n hyper_id | start | end \n----------+-------+-----\n        2 |    20 |  20\n\n-- Pick the first chunk of conditions to TRUNCATE\nSELECT show_chunks AS chunk_to_truncate\nFROM show_chunks('conditions')\nORDER BY 1\nLIMIT 1 \\gset\n-- Show the data before truncating one of the chunks\nSELECT * FROM :chunk_to_truncate\nORDER BY 1;\n time | device | temp \n------+--------+------\n    1 |      4 | 23.7\n    1 |      0 |   16\n    2 |      2 | 23.5\n    2 |      1 |   25\n    3 |      2 | 23.5\n    3 |      0 |   20\n    4 |      2 |   10\n    5 |      2 |   26\n    6 |      1 |   13\n    7 |      3 |   35\n    8 |      1 |   37\n    9 |      3 |    7\n\n-- Truncate one chunk\nTRUNCATE TABLE :chunk_to_truncate;\n-- Should see new invalidation entries for conditions\nSELECT * FROM hyper_invals;\n hyper_id | start | end \n----------+-------+-----\n        1 |     0 |  10\n        2 |    20 |  20\n\n-- TRUNCATE the hypertable to invalidate all its continuous aggregates\nTRUNCATE conditions;\n-- Now empty\nSELECT * FROM conditions;\n time | device | temp \n------+--------+------\n\n-- Should see an infinite invalidation entry for conditions\nSELECT * FROM hyper_invals;\n hyper_id |        start         |         end         \n----------+----------------------+---------------------\n        1 | -9223372036854775808 | 9223372036854775807\n        1 |                    0 |                  10\n        2 |                   20 |                  20\n\n-- Aggregates still hold data\nSELECT * FROM cond_10\nORDER BY 1,2\nLIMIT 5;\n bucket | device | avg_temp \n--------+--------+----------\n      0 |      0 |       18\n      0 |      1 |       25\n      0 |      2 |    20.75\n      0 |      3 |       21\n      0 |      4 |     23.7\n\nSELECT * FROM cond_20\nORDER BY 1,2\nLIMIT 5;\n bucket | device |     avg_temp     \n--------+--------+------------------\n     20 |      0 | 18.2857142857143\n     20 |      1 | 23.5142857142857\n     20 |      2 |               26\n     20 |      3 |               23\n     20 |      5 |             23.8\n\nCALL refresh_continuous_aggregate('cond_10', NULL, NULL);\nCALL refresh_continuous_aggregate('cond_20', NULL, NULL);\n-- Both should now be empty after refresh\nSELECT * FROM cond_10\nORDER BY 1,2;\n bucket | device | avg_temp \n--------+--------+----------\n\nSELECT * FROM cond_20\nORDER BY 1,2;\n bucket | device | avg_temp \n--------+--------+----------\n\n-- Insert new data again and refresh\nINSERT INTO conditions VALUES\n       (1, 1, 23.4), (4, 3, 14.3), (5, 1, 13.6),\n       (6, 2, 17.9), (12, 1, 18.3), (19, 3, 28.2),\n       (10, 3, 22.3), (11, 2, 34.9), (15, 2, 45.6),\n       (21, 1, 15.3), (22, 2, 12.3), (29, 3, 16.3);\nCALL refresh_continuous_aggregate('cond_10', NULL, NULL);\nCALL refresh_continuous_aggregate('cond_20', NULL, NULL);\n-- Should now hold data again\nSELECT * FROM cond_10\nORDER BY 1,2;\n bucket | device | avg_temp \n--------+--------+----------\n      0 |      1 |     18.5\n      0 |      2 |     17.9\n      0 |      3 |     14.3\n     10 |      1 |     18.3\n     10 |      2 |    40.25\n     10 |      3 |    25.25\n     20 |      1 |     15.3\n     20 |      2 |     12.3\n     20 |      3 |     16.3\n\nSELECT * FROM cond_20\nORDER BY 1,2;\n bucket | device |     avg_temp     \n--------+--------+------------------\n      0 |      1 | 18.4333333333333\n      0 |      2 |             32.8\n      0 |      3 |             21.6\n     20 |      1 |             15.3\n     20 |      2 |             12.3\n     20 |      3 |             16.3\n\n-- Truncate one of the aggregates, but first test that we block\n-- TRUNCATE ONLY\n\\set ON_ERROR_STOP 0\nTRUNCATE ONLY cond_20;\nERROR:  cannot truncate only a continuous aggregate\n\\set ON_ERROR_STOP 1\nTRUNCATE cond_20;\n-- Should now be empty\nSELECT * FROM cond_20\nORDER BY 1,2;\n bucket | device | avg_temp \n--------+--------+----------\n\n-- Other aggregate is not affected\nSELECT * FROM cond_10\nORDER BY 1,2;\n bucket | device | avg_temp \n--------+--------+----------\n      0 |      1 |     18.5\n      0 |      2 |     17.9\n      0 |      3 |     14.3\n     10 |      1 |     18.3\n     10 |      2 |    40.25\n     10 |      3 |    25.25\n     20 |      1 |     15.3\n     20 |      2 |     12.3\n     20 |      3 |     16.3\n\n-- Refresh again to bring data back\nCALL refresh_continuous_aggregate('cond_20', NULL, NULL);\n-- The aggregate should be populated again\nSELECT * FROM cond_20\nORDER BY 1,2;\n bucket | device |     avg_temp     \n--------+--------+------------------\n      0 |      1 | 18.4333333333333\n      0 |      2 |             32.8\n      0 |      3 |             21.6\n     20 |      1 |             15.3\n     20 |      2 |             12.3\n     20 |      3 |             16.3\n\n-------------------------------------------------------\n-- Test corner cases against a minimal bucket aggregate\n-------------------------------------------------------\n-- First, clear the table and aggregate\nTRUNCATE conditions;\nSELECT * FROM conditions;\n time | device | temp \n------+--------+------\n\nCALL refresh_continuous_aggregate('cond_10', NULL, NULL);\nSELECT * FROM cond_10\nORDER BY 1,2;\n bucket | device | avg_temp \n--------+--------+----------\n\nCREATE MATERIALIZED VIEW cond_1\nWITH (timescaledb.continuous,\n      timescaledb.materialized_only=true)\nAS\nSELECT time_bucket(BIGINT '1', time) AS bucket, device, avg(temp) AS avg_temp\nFROM conditions\nGROUP BY 1,2 WITH NO DATA;\nSELECT mat_hypertable_id AS cond_1_id\nFROM _timescaledb_catalog.continuous_agg\nWHERE user_view_name = 'cond_1' \\gset\n-- Test invalidations with bucket size 1\nINSERT INTO conditions VALUES (0, 1, 1.0);\nSELECT * FROM hyper_invals;\n hyper_id | start | end \n----------+-------+-----\n        1 |     0 |   0\n        2 |    20 |  20\n\n-- Refreshing around the bucket should not update the aggregate\nCALL refresh_continuous_aggregate('cond_1', -1, 0);\nSELECT * FROM cond_1\nORDER BY 1,2;\n bucket | device | avg_temp \n--------+--------+----------\n\nCALL refresh_continuous_aggregate('cond_1', 1, 2);\nSELECT * FROM cond_1\nORDER BY 1,2;\n bucket | device | avg_temp \n--------+--------+----------\n\n-- Refresh only the invalidated bucket\nCALL refresh_continuous_aggregate('cond_1', 0, 1);\nSELECT * FROM cagg_invals\nWHERE cagg_id = :cond_1_id;\n cagg_id |        start         |         end         \n---------+----------------------+---------------------\n       6 | -9223372036854775808 |                  -2\n       6 |                    2 | 9223372036854775807\n\nSELECT * FROM cond_1\nORDER BY 1,2;\n bucket | device | avg_temp \n--------+--------+----------\n      0 |      1 |        1\n\n-- Refresh 1 extra bucket on the left\nINSERT INTO conditions VALUES (0, 1, 2.0);\nCALL refresh_continuous_aggregate('cond_1', -1, 1);\nSELECT * FROM cond_1\nORDER BY 1,2;\n bucket | device | avg_temp \n--------+--------+----------\n      0 |      1 |      1.5\n\n-- Refresh 1 extra bucket on the right\nINSERT INTO conditions VALUES (0, 1, 3.0);\nCALL refresh_continuous_aggregate('cond_1', 0, 2);\nSELECT * FROM cond_1\nORDER BY 1,2;\n bucket | device | avg_temp \n--------+--------+----------\n      0 |      1 |        2\n\n-- Refresh 1 extra bucket on each side\nINSERT INTO conditions VALUES (0, 1, 4.0);\nCALL refresh_continuous_aggregate('cond_1', -1, 2);\nSELECT * FROM cond_1\nORDER BY 1,2;\n bucket | device | avg_temp \n--------+--------+----------\n      0 |      1 |      2.5\n\n-- Clear to reset aggregate\nTRUNCATE conditions;\nCALL refresh_continuous_aggregate('cond_1', NULL, NULL);\n-- Test invalidation of size 2\nINSERT INTO conditions VALUES (0, 1, 1.0), (1, 1, 2.0);\n-- Refresh one bucket at a time\nCALL refresh_continuous_aggregate('cond_1', 0, 1);\nSELECT * FROM cond_1\nORDER BY 1,2;\n bucket | device | avg_temp \n--------+--------+----------\n      0 |      1 |        1\n\nCALL refresh_continuous_aggregate('cond_1', 1, 2);\nSELECT * FROM cond_1\nORDER BY 1,2;\n bucket | device | avg_temp \n--------+--------+----------\n      0 |      1 |        1\n      1 |      1 |        2\n\n-- Repeat the same thing but refresh the whole invalidation at once\nTRUNCATE conditions;\nCALL refresh_continuous_aggregate('cond_1', NULL, NULL);\nINSERT INTO conditions VALUES (0, 1, 1.0), (1, 1, 2.0);\nCALL refresh_continuous_aggregate('cond_1', 0, 2);\nSELECT * FROM cond_1\nORDER BY 1,2;\n bucket | device | avg_temp \n--------+--------+----------\n      0 |      1 |        1\n      1 |      1 |        2\n\n-- Test invalidation of size 3\nTRUNCATE conditions;\nCALL refresh_continuous_aggregate('cond_1', NULL, NULL);\nINSERT INTO conditions VALUES (0, 1, 1.0), (1, 1, 2.0), (2, 1, 3.0);\n-- Invalidation extends beyond the refresh window on both ends\nCALL refresh_continuous_aggregate('cond_1', 1, 2);\nSELECT * FROM cond_1\nORDER BY 1,2;\n bucket | device | avg_temp \n--------+--------+----------\n      1 |      1 |        2\n\n-- Should leave one invalidation on each side of the refresh window\nSELECT * FROM cagg_invals\nWHERE cagg_id = :cond_1_id;\n cagg_id | start |         end         \n---------+-------+---------------------\n       6 |     0 |                   0\n       6 |     2 |                   2\n       6 |   110 | 9223372036854775807\n\n-- Refresh the two remaining invalidations\nCALL refresh_continuous_aggregate('cond_1', 0, 1);\nSELECT * FROM cond_1\nORDER BY 1,2;\n bucket | device | avg_temp \n--------+--------+----------\n      0 |      1 |        1\n      1 |      1 |        2\n\nCALL refresh_continuous_aggregate('cond_1', 2, 3);\nSELECT * FROM cond_1\nORDER BY 1,2;\n bucket | device | avg_temp \n--------+--------+----------\n      0 |      1 |        1\n      1 |      1 |        2\n      2 |      1 |        3\n\n-- Clear and repeat but instead refresh the whole range in one go. The\n-- result should be the same as the three partial refreshes. Use\n-- DELETE instead of TRUNCATE to clear this time.\nDELETE FROM conditions;\nCALL refresh_continuous_aggregate('cond_1', NULL, NULL);\nINSERT INTO conditions VALUES (0, 1, 1.0), (1, 1, 2.0), (2, 1, 3.0);\nCALL refresh_continuous_aggregate('cond_1', 0, 3);\nSELECT * FROM cond_1\nORDER BY 1,2;\n bucket | device | avg_temp \n--------+--------+----------\n      0 |      1 |        1\n      1 |      1 |        2\n      2 |      1 |        3\n\n----------------------------------------------\n-- Test that invalidation threshold is capped\n----------------------------------------------\nCREATE table threshold_test (time int, value int);\nSELECT create_hypertable('threshold_test', 'time', chunk_time_interval => 4);\n      create_hypertable      \n-----------------------------\n (7,public,threshold_test,t)\n\nSELECT set_integer_now_func('threshold_test', 'int_now');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW thresh_2\nWITH (timescaledb.continuous,\n      timescaledb.materialized_only=true)\nAS\nSELECT time_bucket(2, time) AS bucket, max(value) AS max\nFROM threshold_test\nGROUP BY 1 WITH NO DATA;\nSELECT raw_hypertable_id AS thresh_hyper_id, mat_hypertable_id AS thresh_cagg_id\nFROM _timescaledb_catalog.continuous_agg\nWHERE user_view_name = 'thresh_2' \\gset\n-- There's no invalidation threshold initially\nSELECT * FROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nWHERE hypertable_id = :thresh_hyper_id\nORDER BY 1,2;\n hypertable_id |  watermark  \n---------------+-------------\n             7 | -2147483648\n\n-- Test that threshold is initilized to min value when there's no data\n-- and we specify an infinite end. Note that the min value may differ\n-- depending on time type.\nCALL refresh_continuous_aggregate('thresh_2', 0, NULL);\nNOTICE:  continuous aggregate \"thresh_2\" is already up-to-date\nSELECT * FROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nWHERE hypertable_id = :thresh_hyper_id\nORDER BY 1,2;\n hypertable_id |  watermark  \n---------------+-------------\n             7 | -2147483648\n\nINSERT INTO threshold_test\nSELECT v, v FROM generate_series(1, 10) v;\nCALL refresh_continuous_aggregate('thresh_2', 0, 5);\n-- Threshold should move to end of the last refreshed bucket, which is\n-- the last bucket fully included in the window, i.e., the window\n-- shrinks to end of previous bucket.\nSELECT * FROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nWHERE hypertable_id = :thresh_hyper_id\nORDER BY 1,2;\n hypertable_id | watermark \n---------------+-----------\n             7 |         4\n\n-- Refresh where both the start and end of the window is above the\n-- max data value\nCALL refresh_continuous_aggregate('thresh_2', 14, NULL);\nNOTICE:  continuous aggregate \"thresh_2\" is already up-to-date\nSELECT watermark AS thresh_hyper_id_watermark\nFROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nWHERE hypertable_id = :thresh_hyper_id \\gset\n-- Refresh where we start from the current watermark to infinity\nCALL refresh_continuous_aggregate('thresh_2', :thresh_hyper_id_watermark, NULL);\nNOTICE:  continuous aggregate \"thresh_2\" is already up-to-date\n-- Now refresh with max end of the window to test that the\n-- invalidation threshold is capped at the last bucket of data\nCALL refresh_continuous_aggregate('thresh_2', 0, NULL);\nSELECT * FROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nWHERE hypertable_id = :thresh_hyper_id\nORDER BY 1,2;\n hypertable_id | watermark \n---------------+-----------\n             7 |        12\n\n-- Should not have processed invalidations beyond the invalidation\n-- threshold.\nSELECT * FROM cagg_invals\nWHERE cagg_id = :thresh_cagg_id;\n cagg_id |        start         |         end         \n---------+----------------------+---------------------\n       8 | -9223372036854775808 |                  -1\n       8 |                   12 | 9223372036854775807\n\n-- Check that things are properly materialized\nSELECT * FROM thresh_2\nORDER BY 1;\n bucket | max \n--------+-----\n      0 |   1\n      2 |   3\n      4 |   5\n      6 |   7\n      8 |   9\n     10 |  10\n\n-- Delete the last data\nSELECT show_chunks AS chunk_to_drop\nFROM show_chunks('threshold_test')\nORDER BY 1 DESC\nLIMIT 1 \\gset\nDELETE FROM threshold_test\nWHERE time > 6;\n-- The last data in the hypertable is gone\nSELECT time_bucket(2, time) AS bucket, max(value) AS max\nFROM threshold_test\nGROUP BY 1\nORDER BY 1;\n bucket | max \n--------+-----\n      0 |   1\n      2 |   3\n      4 |   5\n      6 |   6\n\n-- The aggregate still holds data\nSELECT * FROM thresh_2\nORDER BY 1;\n bucket | max \n--------+-----\n      0 |   1\n      2 |   3\n      4 |   5\n      6 |   7\n      8 |   9\n     10 |  10\n\n-- Refresh the aggregate to bring it up-to-date\nCALL refresh_continuous_aggregate('thresh_2', 0, NULL);\n-- Data also gone from the aggregate\nSELECT * FROM thresh_2\nORDER BY 1;\n bucket | max \n--------+-----\n      0 |   1\n      2 |   3\n      4 |   5\n      6 |   6\n\n-- The invalidation threshold remains the same\nSELECT * FROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nWHERE hypertable_id = :thresh_hyper_id\nORDER BY 1,2;\n hypertable_id | watermark \n---------------+-----------\n             7 |        12\n\n-- Insert new data beyond the invalidation threshold to move it\n-- forward\nINSERT INTO threshold_test\nSELECT v, v FROM generate_series(7, 15) v;\nCALL refresh_continuous_aggregate('thresh_2', 0, NULL);\n-- Aggregate now updated to reflect newly aggregated data\nSELECT * FROM thresh_2\nORDER BY 1;\n bucket | max \n--------+-----\n      0 |   1\n      2 |   3\n      4 |   5\n      6 |   7\n      8 |   9\n     10 |  11\n     12 |  13\n     14 |  15\n\n-- The invalidation threshold should have moved forward to the end of\n-- the new data\nSELECT * FROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nWHERE hypertable_id = :thresh_hyper_id\nORDER BY 1,2;\n hypertable_id | watermark \n---------------+-----------\n             7 |        16\n\n-- The aggregate remains invalid beyond the invalidation threshold\nSELECT * FROM cagg_invals\nWHERE cagg_id = :thresh_cagg_id;\n cagg_id |        start         |         end         \n---------+----------------------+---------------------\n       8 | -9223372036854775808 |                  -1\n       8 |                   16 | 9223372036854775807\n\n----------------------------------------------------------------------\n-- Test that dropping a chunk invalidates the dropped region. First\n-- create another chunk so that we have two chunks. One of the chunks\n-- will be dropped.\n---------------------------------------------------------------------\nINSERT INTO conditions VALUES (10, 1, 10.0);\n-- Chunks currently associated with the hypertable\nSELECT show_chunks AS chunk_to_drop\nFROM show_chunks('conditions');\n              chunk_to_drop              \n-----------------------------------------\n _timescaledb_internal._hyper_1_35_chunk\n _timescaledb_internal._hyper_1_41_chunk\n\n-- Pick the first one to drop\nSELECT show_chunks AS chunk_to_drop\nFROM show_chunks('conditions')\nORDER BY 1\nLIMIT 1 \\gset\n-- Show the data before dropping one of the chunks\nSELECT * FROM conditions\nORDER BY 1,2;\n time | device | temp \n------+--------+------\n    0 |      1 |    1\n    1 |      1 |    2\n    2 |      1 |    3\n   10 |      1 |   10\n\n-- Drop one chunk\nDROP TABLE :chunk_to_drop;\n-- The chunk's data no longer exists in the hypertable\nSELECT * FROM conditions\nORDER BY 1,2;\n time | device | temp \n------+--------+------\n   10 |      1 |   10\n\n-- Aggregate still remains in continuous aggregate, however\nSELECT * FROM cond_1\nORDER BY 1,2;\n bucket | device | avg_temp \n--------+--------+----------\n      0 |      1 |        1\n      1 |      1 |        2\n      2 |      1 |        3\n\n-- Refresh the continuous aggregate to make the dropped data be\n-- reflected in the aggregate\nCALL refresh_continuous_aggregate('cond_1', NULL, NULL);\n-- Aggregate now up-to-date with the source hypertable\nSELECT * FROM cond_1\nORDER BY 1,2;\n bucket | device | avg_temp \n--------+--------+----------\n     10 |      1 |       10\n\n-- Test that adjacent invalidations are merged\nINSERT INTO conditions VALUES(1, 1, 1.0), (2, 1, 2.0);\nINSERT INTO conditions VALUES(3, 1, 1.0);\nINSERT INTO conditions VALUES(4, 1, 1.0);\nINSERT INTO conditions VALUES(6, 1, 1.0);\nCALL refresh_continuous_aggregate('cond_1', 10, NULL);\nNOTICE:  continuous aggregate \"cond_1\" is already up-to-date\nSELECT * FROM cagg_invals\nWHERE cagg_id = :cond_1_id;\n cagg_id | start |         end         \n---------+-------+---------------------\n       6 |     1 |                   4\n       6 |     6 |                   6\n       6 |   110 | 9223372036854775807\n\n---------------------------------------------------------------------\n-- Test that single timestamp invalidations are expanded to buckets,\n-- and adjacent buckets merged.\n---------------------------------------------------------------------\n-- First clear invalidations in a range:\nCALL refresh_continuous_aggregate('cond_10', -20, 60);\n-- The following three should be merged to one range 0-29\nINSERT INTO conditions VALUES (5, 1, 1.0);\nINSERT INTO conditions VALUES (15, 1, 1.0);\nINSERT INTO conditions VALUES (25, 1, 1.0);\n-- The last one should not merge with the others\nINSERT INTO conditions VALUES (40, 1, 1.0);\n-- Refresh to process invalidations, but outside the range of\n-- invalidations we inserted so that we don't clear them.\nCALL refresh_continuous_aggregate('cond_10', 50, 60);\nNOTICE:  continuous aggregate \"cond_10\" is already up-to-date\nSELECT mat_hypertable_id AS cond_10_id\nFROM _timescaledb_catalog.continuous_agg\nWHERE user_view_name = 'cond_10' \\gset\nSELECT * FROM cagg_invals\nWHERE cagg_id = :cond_10_id;\n cagg_id |        start         |         end         \n---------+----------------------+---------------------\n       3 | -9223372036854775808 |                 -21\n       3 |                    0 |                  29\n       3 |                   40 |                  49\n       3 |                   60 | 9223372036854775807\n\n-- should trigger two individual refreshes\nCALL refresh_continuous_aggregate('cond_10', 0, 200);\n-- Insert into every second bucket\nINSERT INTO conditions VALUES (20, 1, 1.0);\nINSERT INTO conditions VALUES (40, 1, 1.0);\nINSERT INTO conditions VALUES (60, 1, 1.0);\nINSERT INTO conditions VALUES (80, 1, 1.0);\nINSERT INTO conditions VALUES (100, 1, 1.0);\nINSERT INTO conditions VALUES (120, 1, 1.0);\nINSERT INTO conditions VALUES (140, 1, 1.0);\nCALL refresh_continuous_aggregate('cond_10', 0, 200);\n-- Test refresh with undefined invalidation threshold and variable sized buckets\nCREATE TABLE timestamp_ht (\n  time timestamptz NOT NULL,\n  value float\n);\nSELECT create_hypertable('timestamp_ht', 'time');\n     create_hypertable     \n---------------------------\n (9,public,timestamp_ht,t)\n\nCREATE MATERIALIZED VIEW temperature_4h\n  WITH  (timescaledb.continuous) AS\n  SELECT time_bucket('4 hour', time), avg(value)\n    FROM timestamp_ht\n    GROUP BY 1 ORDER BY 1;\nNOTICE:  continuous aggregate \"temperature_4h\" is already up-to-date\n-- We also treat time_buckets with an hourly interval that uses a time-zone\n-- as a variable see caggtimebucket_validate().\nCREATE MATERIALIZED VIEW temperature_4h_2\n  WITH  (timescaledb.continuous) AS\n  SELECT time_bucket('4 hour', time, 'Europe/Berlin') AS bucket_4h, avg(value) AS average\n    FROM timestamp_ht\n    GROUP BY 1 ORDER BY 1;\nNOTICE:  continuous aggregate \"temperature_4h_2\" is already up-to-date\nCREATE MATERIALIZED VIEW temperature_1month\n  WITH  (timescaledb.continuous) AS\n  SELECT time_bucket('1 month', time), avg(value)\n    FROM timestamp_ht\n    GROUP BY 1 ORDER BY 1;\nNOTICE:  continuous aggregate \"temperature_1month\" is already up-to-date\nCREATE MATERIALIZED VIEW temperature_1month_ts\n  WITH  (timescaledb.continuous) AS\n  SELECT time_bucket('1 month', time, 'Europe/Berlin'), avg(value)\n    FROM timestamp_ht\n    GROUP BY 1 ORDER BY 1;\nNOTICE:  continuous aggregate \"temperature_1month_ts\" is already up-to-date\nCREATE MATERIALIZED VIEW temperature_1month_hierarchical\n  WITH  (timescaledb.continuous) AS\n  SELECT time_bucket('1 month', bucket_4h), avg(average)\n    FROM temperature_4h_2\n    GROUP BY 1 ORDER BY 1;\nNOTICE:  continuous aggregate \"temperature_1month_hierarchical\" is already up-to-date\nCREATE MATERIALIZED VIEW temperature_1month_hierarchical_ts\n  WITH  (timescaledb.continuous) AS\n  SELECT time_bucket('1 month', bucket_4h, 'Europe/Berlin'), avg(average)\n    FROM temperature_4h_2\n    GROUP BY 1 ORDER BY 1;\nNOTICE:  continuous aggregate \"temperature_1month_hierarchical_ts\" is already up-to-date\n---------------------------------------------------------------------\n--- Issue 5474\n---------------------------------------------------------------------\nCREATE TABLE i5474 (\ntime timestamptz NOT NULL,\nsensor_id integer NOT NULL,\ncpu double precision NOT NULL,\ntemperature double precision NOT NULL);\nSELECT create_hypertable('i5474','time');\n  create_hypertable  \n---------------------\n (16,public,i5474,t)\n\nCREATE MATERIALIZED VIEW i5474_summary_daily\n    WITH (timescaledb.continuous) AS\n        SELECT\n            time_bucket('1 hour', time, 'AWST') AS bucket,\n            sensor_id,\n            avg(cpu) AS avg_cpu\n        FROM i5474\n        GROUP BY bucket, sensor_id;\nNOTICE:  continuous aggregate \"i5474_summary_daily\" is already up-to-date\nSELECT add_continuous_aggregate_policy('i5474_summary_daily',\n    start_offset      => NULL,\n    end_offset        => INTERVAL '10 MINUTES',\n    schedule_interval => INTERVAL '1 MINUTE'\n) new_job_id \\gset\n-- Check that start_offset = NULL is handled properly by the refresh job...\nCALL run_job(:new_job_id);\n-- ...and the CAgg can be refreshed afterward\nCALL refresh_continuous_aggregate('i5474_summary_daily', NULL, '2023-03-21 05:00:00+00');\nNOTICE:  continuous aggregate \"i5474_summary_daily\" is already up-to-date\nINSERT INTO i5474 (time, sensor_id, cpu, temperature) VALUES ('2000-01-01 05:00:00+00', 1, 1.0, 1.0);\nCALL refresh_continuous_aggregate('i5474_summary_daily', NULL, '2023-01-01 01:00:00+00');\n-- CAgg should be up-to-date now\nCALL refresh_continuous_aggregate('i5474_summary_daily', NULL, '2023-01-01 01:00:00+00');\nNOTICE:  continuous aggregate \"i5474_summary_daily\" is already up-to-date\n--\n-- Test the invalidation move function\n--\n-- Make sure to move the threshold for the insertions we are going to\n-- do.\nCALL refresh_continuous_aggregate('measure_10', 0, 200);\nSELECT * FROM hypertable_invalidation_thresholds\n WHERE hypertable IN ('conditions'::regclass, 'measurements'::regclass);\n  hypertable  | threshold \n--------------+-----------\n conditions   |       200\n measurements |       200\n\nSELECT * FROM hyper_invals;\n hyper_id | start | end \n----------+-------+-----\n\n-- Save away the contents of some materialized views so that we can\n-- check that they are not updated when we move invalidations.\nSELECT * INTO saved_measure_10 FROM measure_10;\nSELECT * INTO saved_cond_10 FROM cond_10;\n-- Generate some invalidations for the hypertables\nINSERT INTO conditions VALUES (110, 14, 23.7);\nINSERT INTO conditions VALUES (110, 15, 23.8), (119, 3, 23.6);\nINSERT INTO conditions VALUES (160, 13, 23.7), (170, 4, 23.7);\nINSERT INTO measurements VALUES (120, 14, 23.7);\nINSERT INTO measurements VALUES (130, 15, 23.8), (180, 3, 23.6);\n-- test direct compress insert invalidation\nCREATE TABLE direct_compress_insert(time timestamptz) WITH (tsdb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nINSERT INTO direct_compress_insert SELECT '2025-01-01';\nCREATE MATERIALIZED VIEW cagg_insert WITH (tsdb.continuous) AS SELECT time_bucket('1day', time) FROM direct_compress_insert GROUP BY 1;\nNOTICE:  refreshing continuous aggregate \"cagg_insert\"\nSET timescaledb.enable_direct_compress_insert = true;\nEXPLAIN (analyze,buffers off,costs off,timing off,summary off) INSERT INTO direct_compress_insert SELECT '2024-01-01'::timestamptz + format('%sm',i)::interval FROM generate_series(1,1000) g(i);\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)\n   Direct Compress: true\n   ->  Insert on direct_compress_insert (actual rows=0.00 loops=1)\n         ->  Function Scan on generate_series g (actual rows=1000.00 loops=1)\n\nEXPLAIN (analyze,buffers off,costs off,timing off,summary off) INSERT INTO direct_compress_insert SELECT '2024-01-01'::timestamptz - format('%sm',i)::interval FROM generate_series(1,1000) g(i);\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)\n   Direct Compress: true\n   ->  Insert on direct_compress_insert (actual rows=0.00 loops=1)\n         ->  Function Scan on generate_series g (actual rows=1000.00 loops=1)\n\n-- should have 2 entries\nSELECT _timescaledb_functions.to_timestamp(lowest_modified_value) start, _timescaledb_functions.to_timestamp(greatest_modified_value) end from _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log WHERE hypertable_id = 18 ORDER BY 1,2;\n         start          |          end           \n------------------------+------------------------\n 2023-12-31 07:20:00+00 | 2023-12-31 23:59:00+00\n 2024-01-01 00:01:00+00 | 2024-01-01 16:40:00+00\n\nEXPLAIN (analyze,buffers off,costs off,timing off,summary off) INSERT INTO direct_compress_insert SELECT '2023-12-31'::timestamptz + format('%sm',i)::interval FROM generate_series(1,2000) g(i);\n--- QUERY PLAN ---\n Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)\n   Direct Compress: true\n   ->  Insert on direct_compress_insert (actual rows=0.00 loops=1)\n         ->  Function Scan on generate_series g (actual rows=2000.00 loops=1)\n\n-- should have 3 entries\nSELECT _timescaledb_functions.to_timestamp(lowest_modified_value) start, _timescaledb_functions.to_timestamp(greatest_modified_value) end from _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log WHERE hypertable_id = 18 ORDER BY 1,2;\n         start          |          end           \n------------------------+------------------------\n 2023-12-31 00:01:00+00 | 2024-01-01 09:20:00+00\n 2023-12-31 07:20:00+00 | 2023-12-31 23:59:00+00\n 2024-01-01 00:01:00+00 | 2024-01-01 16:40:00+00\n\n-- should have 1 uncompressed and 1 compressed chunk\nEXPLAIN (costs off,timing off,summary off) SELECT FROM direct_compress_insert;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on _hyper_18_59_chunk\n   ->  Custom Scan (ColumnarScan) on _hyper_18_61_chunk\n         ->  Seq Scan on compress_hyper_19_62_chunk\n\nRESET timescaledb.enable_direct_compress_insert;\n-- test direct compress copy invalidation\nCREATE TABLE direct_compress_copy(time timestamptz) WITH (tsdb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nINSERT INTO direct_compress_copy SELECT '2025-01-01';\nCREATE MATERIALIZED VIEW cagg_copy WITH (tsdb.continuous) AS SELECT time_bucket('1day', time) FROM direct_compress_copy GROUP BY 1;\nNOTICE:  refreshing continuous aggregate \"cagg_copy\"\nSET timescaledb.enable_direct_compress_copy = true;\nCOPY direct_compress_copy FROM STDIN;\n-- should have 1 entries now\nSELECT _timescaledb_functions.to_timestamp(lowest_modified_value) start, _timescaledb_functions.to_timestamp(greatest_modified_value) end from _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log WHERE hypertable_id = 21 ORDER BY 1,2;\n         start          |          end           \n------------------------+------------------------\n 2023-01-01 00:00:00+00 | 2023-01-03 00:00:00+00\n\nCOPY direct_compress_copy FROM STDIN;\n-- should have 2 entries now\nSELECT _timescaledb_functions.to_timestamp(lowest_modified_value) start, _timescaledb_functions.to_timestamp(greatest_modified_value) end from _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log WHERE hypertable_id = 21 ORDER BY 1,2;\n         start          |          end           \n------------------------+------------------------\n 2022-12-31 00:00:00+00 | 2023-01-03 00:00:00+00\n 2023-01-01 00:00:00+00 | 2023-01-03 00:00:00+00\n\n-- range spanning multiple chunks\nCOPY direct_compress_copy FROM STDIN;\n-- should have 3 entries now\nSELECT _timescaledb_functions.to_timestamp(lowest_modified_value) start, _timescaledb_functions.to_timestamp(greatest_modified_value) end from _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log WHERE hypertable_id = 21 ORDER BY 1,2;\n         start          |          end           \n------------------------+------------------------\n 2022-01-01 00:00:00+00 | 2022-01-01 00:00:00+00\n 2022-02-28 00:00:00+00 | 2022-02-28 00:00:00+00\n 2022-12-31 00:00:00+00 | 2023-01-03 00:00:00+00\n 2023-01-01 00:00:00+00 | 2023-01-03 00:00:00+00\n\n-- should have 1 uncompressed and 3 compressed chunk\nEXPLAIN (costs off,timing off,summary off) SELECT FROM direct_compress_copy;\n--- QUERY PLAN ---\n Append\n   ->  Seq Scan on _hyper_21_63_chunk\n   ->  Custom Scan (ColumnarScan) on _hyper_21_65_chunk\n         ->  Seq Scan on compress_hyper_22_66_chunk\n   ->  Custom Scan (ColumnarScan) on _hyper_21_67_chunk\n         ->  Seq Scan on compress_hyper_22_68_chunk\n   ->  Custom Scan (ColumnarScan) on _hyper_21_69_chunk\n         ->  Seq Scan on compress_hyper_22_70_chunk\n\nRESET timescaledb.enable_direct_compress_copy;\n-- test direct compress invalidation with custom partitioning function (not supported atm)\nCREATE OR REPLACE FUNCTION f_month(timestamptz) returns int language sql AS $$ SELECT 12 * extract(year from $1) + extract(month from $1);$$ immutable;\nCREATE TABLE part_cagg (time timestamptz);\nSELECT create_hypertable('part_cagg', 'time', time_partitioning_func => 'f_month', chunk_time_interval => 1);\n    create_hypertable    \n-------------------------\n (24,public,part_cagg,t)\n\n\\set ON_ERROR_STOP 0\nCREATE MATERIALIZED VIEW part_cagg1 WITH (tsdb.continuous) AS SELECT time_bucket('1day', time) FROM part_cagg GROUP BY 1;\nERROR:  custom partitioning functions not supported with continuous aggregates\n\\set ON_ERROR_STOP 1\n-- test UPDATE invalidation\nCREATE TABLE inval_update(time timestamptz) WITH (tsdb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nINSERT INTO inval_update SELECT '2025-01-01';\nCREATE MATERIALIZED VIEW cagg_inval_update WITH (tsdb.continuous) AS SELECT time_bucket('1day', time) FROM inval_update GROUP BY 1;\nNOTICE:  refreshing continuous aggregate \"cagg_inval_update\"\n-- check setting to NULL is handled gracefully\n\\set ON_ERROR_STOP 0\nUPDATE inval_update SET time = NULL WHERE time = '2025-01-01';\nERROR:  null value in column \"time\" of relation \"_hyper_25_71_chunk\" violates not-null constraint\n\\set ON_ERROR_STOP 1\nUPDATE inval_update SET time = '2025-01-01 00:00:23' WHERE time = '2025-01-01';\n-- should have 1 entries now\nSELECT _timescaledb_functions.to_timestamp(lowest_modified_value) start, _timescaledb_functions.to_timestamp(greatest_modified_value) end from _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log WHERE hypertable_id = 25 ORDER BY 1,2;\n         start          |          end           \n------------------------+------------------------\n 2025-01-01 00:00:00+00 | 2025-01-01 00:00:23+00\n\n------------------------------------------------------------------------------------------\n--Test that invalidation's greatest_modified value are handle correctly for variable bucket\n-------------------------------------------------------------------------------------------\nCREATE TABLE test_data (\n    time TIMESTAMPTZ NOT NULL,\n    value INT\n);\nSELECT public.create_hypertable(\n        relation => 'test_data',\n        time_column_name => 'time',\n        chunk_time_interval => interval '1 months'\n);\n    create_hypertable    \n-------------------------\n (28,public,test_data,t)\n\n-- Insert initial data\nINSERT INTO test_data\nSELECT time, 1\nFROM generate_series('2024-01-01'::timestamptz, '2024-12-31'::timestamptz, '1 day'::interval) time;\n-- Create continuous aggregate with variable bucket\nCREATE MATERIALIZED VIEW test_cagg\nWITH (timescaledb.continuous) AS\nSELECT\n    time_bucket('1 month'::interval, time) AS bucket,\n    count(*) as count\nFROM test_data\nGROUP BY bucket\nWITH NO DATA;\n--create continuous aggregate with fixed bucket with offset\nCREATE MATERIALIZED VIEW test_cagg_1d_offset\nWITH (timescaledb.continuous) AS\nSELECT\n    time_bucket('1 day'::interval, time, \"offset\" => INTERVAL '18 hours') AS bucket,\n    count(*) as count\nFROM test_data\nGROUP BY bucket\nWITH NO DATA;\nSET timezone = 'UTC';\n--Do the first refresh and check materialization invalidation log\ncall refresh_continuous_aggregate ('test_cagg','2023-12-29 15:00:00', '2026-01-28 15:00:00');\ncall refresh_continuous_aggregate ('test_cagg_1d_offset','2023-12-29 18:00:00', '2024-01-01 18:00:00');\nSELECT materialization_id,\n       _timescaledb_functions.to_timestamp(lowest_modified_value) as low,\n       _timescaledb_functions.to_timestamp(greatest_modified_value) as high\nFROM _timescaledb_catalog.continuous_aggs_materialization_invalidation_log\nWHERE materialization_id IN\n      (SELECT mat_hypertable_id FROM _timescaledb_catalog.continuous_agg\n       WHERE user_view_name = 'test_cagg')\nORDER BY low;\n materialization_id |          low           |             high              \n--------------------+------------------------+-------------------------------\n                 29 | -infinity              | 2023-12-31 23:59:59.999999+00\n                 29 | 2026-01-01 00:00:00+00 | infinity\n\nSELECT\n    CASE WHEN lowest_modified_value <= _timescaledb_functions.get_internal_time_min('timestamptz'::regtype)\n        THEN '-infinity'::timestamptz\n        ELSE _timescaledb_functions.to_timestamp(lowest_modified_value)\n    END AS low,\n    CASE WHEN greatest_modified_value >= _timescaledb_functions.get_internal_time_max('timestamptz'::regtype)\n        THEN 'infinity'::timestamptz\n        ELSE _timescaledb_functions.to_timestamp(greatest_modified_value)\n    END AS high\nFROM _timescaledb_catalog.continuous_aggs_materialization_invalidation_log\nWHERE materialization_id = (\n    SELECT mat_hypertable_id FROM _timescaledb_catalog.continuous_agg\n    WHERE user_view_name = 'test_cagg_1d_offset'\n)\nORDER BY lowest_modified_value, greatest_modified_value;\n          low           |             high              \n------------------------+-------------------------------\n -infinity              | 2023-12-29 17:59:59.999999+00\n 2024-01-01 18:00:00+00 | infinity\n\n--now do the same refresh again, it should say the cagg is already up to date\nCALL refresh_continuous_aggregate ('test_cagg','2023-12-29 15:00:00', '2026-01-28 15:00:00');\nNOTICE:  continuous aggregate \"test_cagg\" is already up-to-date\nCALL refresh_continuous_aggregate ('test_cagg','2023-12-29 15:00:00', '2026-01-28 15:00:00');\nNOTICE:  continuous aggregate \"test_cagg\" is already up-to-date\n--Insert data to test that invalidation is moved correctly from hypertable invalidation log\n-- to materialization invalidation log\nINSERT INTO test_data\nSELECT time, 2\nFROM generate_series('2024-01-01'::timestamptz, '2024-12-31'::timestamptz, '10 day'::interval) time;\n--Refresh some first part of the updated range. The range show up in the materialization log\n--should end with the last timestamp of the bucket (i.e., has the .999999 at the end),\n--rather than the start of the next bucket\nCALL refresh_continuous_aggregate ('test_cagg','2023-12-29 15:00:00', '2024-03-15 15:00:00');\nSELECT materialization_id,\n       _timescaledb_functions.to_timestamp(lowest_modified_value) as low,\n       _timescaledb_functions.to_timestamp(greatest_modified_value) as high\nFROM _timescaledb_catalog.continuous_aggs_materialization_invalidation_log\nWHERE materialization_id IN\n      (SELECT mat_hypertable_id FROM _timescaledb_catalog.continuous_agg\n       WHERE user_view_name = 'test_cagg')\nORDER BY low;\n materialization_id |          low           |             high              \n--------------------+------------------------+-------------------------------\n                 29 | -infinity              | 2023-12-31 23:59:59.999999+00\n                 29 | 2024-03-01 00:00:00+00 | 2024-12-31 23:59:59.999999+00\n                 29 | 2026-01-01 00:00:00+00 | infinity\n\n--should see the invalidation ranges with offset (i.e,boundary at the 18hour)\nSELECT\n    CASE WHEN lowest_modified_value <= _timescaledb_functions.get_internal_time_min('timestamptz'::regtype)\n        THEN '-infinity'::timestamptz\n        ELSE _timescaledb_functions.to_timestamp(lowest_modified_value)\n    END AS low,\n    CASE WHEN greatest_modified_value >= _timescaledb_functions.get_internal_time_max('timestamptz'::regtype)\n        THEN 'infinity'::timestamptz\n        ELSE _timescaledb_functions.to_timestamp(greatest_modified_value)\n    END AS high\nFROM _timescaledb_catalog.continuous_aggs_materialization_invalidation_log\nWHERE materialization_id = (\n    SELECT mat_hypertable_id FROM _timescaledb_catalog.continuous_agg\n    WHERE user_view_name = 'test_cagg_1d_offset'\n)\nORDER BY lowest_modified_value, greatest_modified_value;\n          low           |             high              \n------------------------+-------------------------------\n -infinity              | 2023-12-29 17:59:59.999999+00\n 2023-12-31 18:00:00+00 | 2024-01-11 17:59:59.999999+00\n 2024-01-01 18:00:00+00 | infinity\n 2024-01-20 18:00:00+00 | 2024-02-10 17:59:59.999999+00\n 2024-02-19 18:00:00+00 | 2024-03-11 17:59:59.999999+00\n 2024-03-20 18:00:00+00 | 2024-04-10 17:59:59.999999+00\n 2024-04-19 18:00:00+00 | 2024-05-10 17:59:59.999999+00\n 2024-05-19 18:00:00+00 | 2024-06-09 17:59:59.999999+00\n 2024-06-18 18:00:00+00 | 2024-07-09 17:59:59.999999+00\n 2024-07-18 18:00:00+00 | 2024-08-08 17:59:59.999999+00\n 2024-08-17 18:00:00+00 | 2024-09-07 17:59:59.999999+00\n 2024-09-16 18:00:00+00 | 2024-10-07 17:59:59.999999+00\n 2024-10-16 18:00:00+00 | 2024-11-06 17:59:59.999999+00\n 2024-11-15 18:00:00+00 | 2024-12-06 17:59:59.999999+00\n 2024-12-15 18:00:00+00 | 2024-12-26 17:59:59.999999+00\n\n--test that offset was accounted for in invalidation threshold when refresh the offset cagg to NULL\nSELECT  _timescaledb_functions.to_timestamp(watermark) as invalidation_threshold\nFROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nWHERE hypertable_id IN (\n  SELECT raw_hypertable_id FROM _timescaledb_catalog.continuous_agg WHERE user_view_name = 'test_cagg_1d_offset');\n invalidation_threshold \n------------------------\n 2026-01-01 00:00:00+00\n\nINSERT INTO test_data values ('2026-01-05 00:00:00', 1);\nCALL refresh_continuous_aggregate ('test_cagg_1d_offset','2023-12-29 15:00:00', NULL);\n--should be at the 18th hour\nSELECT  _timescaledb_functions.to_timestamp(watermark) as invalidation_threshold\nFROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nWHERE hypertable_id IN (\n  SELECT raw_hypertable_id FROM _timescaledb_catalog.continuous_agg WHERE user_view_name = 'test_cagg_1d_offset');\n invalidation_threshold \n------------------------\n 2026-01-05 18:00:00+00\n\n--clean up\nDROP TABLE test_data CASCADE;\nNOTICE:  drop cascades to 4 other objects\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to 3 other objects\nRESET timezone;\n"
  },
  {
    "path": "tsl/test/expected/cagg_invalidation_variable_bucket.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- Tests for continuous aggregate invalidation with variable-sized buckets\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSET datestyle TO 'ISO, YMD';\nSET timezone TO 'UTC';\nCREATE VIEW hyper_inval_log AS\nSELECT ht.schema_name || '.' || ht.table_name AS hypertable,\n       _timescaledb_functions.to_timestamp(lowest_modified_value) AS inval_start,\n       _timescaledb_functions.to_timestamp(greatest_modified_value) AS inval_end\nFROM _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log hil\nJOIN _timescaledb_catalog.hypertable ht ON ht.id = hil.hypertable_id\nORDER BY 1, 2, 3;\nCREATE VIEW cagg_inval_log AS\nSELECT ca.user_view_name AS cagg_name,\n       _timescaledb_functions.to_timestamp(mil.lowest_modified_value) AS inval_start,\n       _timescaledb_functions.to_timestamp(mil.greatest_modified_value) AS inval_end\nFROM _timescaledb_catalog.continuous_aggs_materialization_invalidation_log mil\nJOIN _timescaledb_catalog.continuous_agg ca ON ca.mat_hypertable_id = mil.materialization_id\nORDER BY 1, 2, 3;\n-----------------------------------------------------------------------\n-- SECTION 1: Monthly buckets with varying month lengths\n-- Tests that invalidations are correctly processed for variable-width\n-- buckets.\n-----------------------------------------------------------------------\nCREATE TABLE monthly_data (\n    time TIMESTAMPTZ NOT NULL,\n    device INT,\n    value FLOAT\n);\nSELECT create_hypertable('monthly_data', 'time', chunk_time_interval => INTERVAL '1 month');\n     create_hypertable     \n---------------------------\n (1,public,monthly_data,t)\n\n-- Create a 1-month bucket cagg\nCREATE MATERIALIZED VIEW cagg_monthly\nWITH (timescaledb.continuous, timescaledb.materialized_only = true) AS\nSELECT time_bucket('1 month'::interval, time) AS bucket,\n       device,\n       count(*) AS cnt\nFROM monthly_data\nGROUP BY 1, 2\nWITH NO DATA;\n-- Insert data spanning 12 months of 2024 (leap year)\nINSERT INTO monthly_data\nSELECT ts, 1, extract(epoch FROM ts)::int % 100\nFROM generate_series('2024-01-01 00:00:00'::timestamptz,\n                     '2024-12-31 23:59:59'::timestamptz,\n                     '1 day'::interval) ts;\nCALL refresh_continuous_aggregate('cagg_monthly', '2024-01-01 00:00:00', '2025-01-01 00:00:00');\n-- Verify data is materialized\nSELECT bucket, cnt FROM cagg_monthly ORDER BY bucket;\n         bucket         | cnt \n------------------------+-----\n 2024-01-01 00:00:00+00 |  31\n 2024-02-01 00:00:00+00 |  29\n 2024-03-01 00:00:00+00 |  31\n 2024-04-01 00:00:00+00 |  30\n 2024-05-01 00:00:00+00 |  31\n 2024-06-01 00:00:00+00 |  30\n 2024-07-01 00:00:00+00 |  31\n 2024-08-01 00:00:00+00 |  31\n 2024-09-01 00:00:00+00 |  30\n 2024-10-01 00:00:00+00 |  31\n 2024-11-01 00:00:00+00 |  30\n 2024-12-01 00:00:00+00 |  31\n\n-- No invalidations should remain after full refresh\nSELECT * FROM cagg_inval_log WHERE cagg_name = 'cagg_monthly';\n  cagg_name   |      inval_start       |           inval_end           \n--------------+------------------------+-------------------------------\n cagg_monthly | -infinity              | 2023-12-31 23:59:59.999999+00\n cagg_monthly | 2025-01-01 00:00:00+00 | infinity\n\n-----------------------------------------------------------------------\n-- Test 1a: Invalidation in February (28/29 day month) of a leap year\n-- February 2024 has 29 days.\n-----------------------------------------------------------------------\nINSERT INTO monthly_data VALUES ('2024-02-15 12:00:00', 1, 999.0);\nSELECT * FROM hyper_inval_log;\n     hypertable      |      inval_start       |       inval_end        \n---------------------+------------------------+------------------------\n public.monthly_data | 2024-02-15 12:00:00+00 | 2024-02-15 12:00:00+00\n\n-- Refresh only February\nCALL refresh_continuous_aggregate('cagg_monthly', '2024-02-01 00:00:00', '2024-03-01 00:00:00');\nSELECT bucket, cnt FROM cagg_monthly WHERE bucket = '2024-02-01';\n         bucket         | cnt \n------------------------+-----\n 2024-02-01 00:00:00+00 |  30\n\n-- No invalidation should remain for February\nSELECT * FROM cagg_inval_log WHERE cagg_name = 'cagg_monthly';\n  cagg_name   |      inval_start       |           inval_end           \n--------------+------------------------+-------------------------------\n cagg_monthly | -infinity              | 2023-12-31 23:59:59.999999+00\n cagg_monthly | 2025-01-01 00:00:00+00 | infinity\n\n-----------------------------------------------------------------------\n-- Test 1b: Invalidation at the exact boundary between Feb 29 and Mar 1\n-----------------------------------------------------------------------\n-- Insert at the very last moment of Feb 29\nINSERT INTO monthly_data VALUES ('2024-02-29 23:59:59.999999', 1, 888.0);\n-- Insert at the very first moment of Mar 1\nINSERT INTO monthly_data VALUES ('2024-03-01 00:00:00', 1, 777.0);\nSELECT * FROM hyper_inval_log;\n     hypertable      |          inval_start          |           inval_end           \n---------------------+-------------------------------+-------------------------------\n public.monthly_data | 2024-02-29 23:59:59.999999+00 | 2024-02-29 23:59:59.999999+00\n public.monthly_data | 2024-03-01 00:00:00+00        | 2024-03-01 00:00:00+00\n\n-- Refresh February only\nCALL refresh_continuous_aggregate('cagg_monthly', '2024-02-01 00:00:00', '2024-03-01 00:00:00');\n-- The remaining invalidation should only cover March\nSELECT * FROM cagg_inval_log WHERE cagg_name = 'cagg_monthly';\n  cagg_name   |      inval_start       |           inval_end           \n--------------+------------------------+-------------------------------\n cagg_monthly | -infinity              | 2023-12-31 23:59:59.999999+00\n cagg_monthly | 2024-03-01 00:00:00+00 | 2024-03-31 23:59:59.999999+00\n cagg_monthly | 2025-01-01 00:00:00+00 | infinity\n\n-- Now refresh March\nCALL refresh_continuous_aggregate('cagg_monthly', '2024-03-01 00:00:00', '2024-04-01 00:00:00');\n-- No invalidations should remain\nSELECT * FROM cagg_inval_log WHERE cagg_name = 'cagg_monthly';\n  cagg_name   |      inval_start       |           inval_end           \n--------------+------------------------+-------------------------------\n cagg_monthly | -infinity              | 2023-12-31 23:59:59.999999+00\n cagg_monthly | 2025-01-01 00:00:00+00 | infinity\n\n-----------------------------------------------------------------------\n-- Test 1c: Invalidation spanning multiple months of different lengths\n-----------------------------------------------------------------------\n-- Insert one value in each month\nINSERT INTO monthly_data VALUES ('2024-02-29 23:59:59', 1, 100.0);  -- 29-day\nINSERT INTO monthly_data VALUES ('2024-03-31 12:00:00', 1, 200.0);  -- 31-day\nINSERT INTO monthly_data VALUES ('2024-04-30 23:59:59', 1, 300.0);  -- 30-day\n-- Refresh with a window that partially covers all three months.\nCALL refresh_continuous_aggregate('cagg_monthly', '2024-02-15 00:00:00', '2024-04-15 00:00:00');\nSELECT * FROM cagg_inval_log WHERE cagg_name = 'cagg_monthly';\n  cagg_name   |      inval_start       |           inval_end           \n--------------+------------------------+-------------------------------\n cagg_monthly | -infinity              | 2023-12-31 23:59:59.999999+00\n cagg_monthly | 2024-02-01 00:00:00+00 | 2024-02-29 23:59:59.999999+00\n cagg_monthly | 2024-04-01 00:00:00+00 | 2024-04-30 23:59:59.999999+00\n cagg_monthly | 2025-01-01 00:00:00+00 | infinity\n\n-- Refresh the whole window to clear all invalidations\nCALL refresh_continuous_aggregate('cagg_monthly', '2024-02-01 00:00:00', '2024-05-01 00:00:00');\nSELECT * FROM cagg_inval_log WHERE cagg_name = 'cagg_monthly';\n  cagg_name   |      inval_start       |           inval_end           \n--------------+------------------------+-------------------------------\n cagg_monthly | -infinity              | 2023-12-31 23:59:59.999999+00\n cagg_monthly | 2025-01-01 00:00:00+00 | infinity\n\n-----------------------------------------------------------------------\n-- Test 1d: Non-leap year February (28 days)\n-----------------------------------------------------------------------\nINSERT INTO monthly_data\nSELECT ts, 2, 50.0\nFROM generate_series('2025-02-01 00:00:00'::timestamptz,\n                     '2025-02-28 23:59:59'::timestamptz,\n                     '1 day'::interval) ts;\nCALL refresh_continuous_aggregate('cagg_monthly', '2025-02-01 00:00:00', '2025-03-01 00:00:00');\n-- Verify Feb 2025 bucket has correct number of days\nSELECT bucket, cnt FROM cagg_monthly\nWHERE device = 2 AND bucket = '2025-02-01 00:00:00';\n         bucket         | cnt \n------------------------+-----\n 2025-02-01 00:00:00+00 |  28\n\n-- Insert at Feb 28 boundary\nINSERT INTO monthly_data VALUES ('2025-02-28 23:59:59.999999', 2, 999.0);\nCALL refresh_continuous_aggregate('cagg_monthly', '2025-02-01 00:00:00', '2025-03-01 00:00:00');\nSELECT * FROM cagg_inval_log WHERE cagg_name = 'cagg_monthly';\n  cagg_name   |      inval_start       |           inval_end           \n--------------+------------------------+-------------------------------\n cagg_monthly | -infinity              | 2023-12-31 23:59:59.999999+00\n cagg_monthly | 2025-01-01 00:00:00+00 | 2025-01-31 23:59:59.999999+00\n cagg_monthly | 2025-03-01 00:00:00+00 | infinity\n\n-----------------------------------------------------------------------\n-- SECTION 2: Yearly buckets with leap year crossing\n-- Tests year-length variability (365 vs 366 days) and the\n-- 30-day x 12 = 360-day approximation in bucket_width.\n-----------------------------------------------------------------------\nCREATE TABLE yearly_data (\n    time TIMESTAMPTZ NOT NULL,\n    value FLOAT\n);\nSELECT create_hypertable('yearly_data', 'time', chunk_time_interval => INTERVAL '1 year');\n    create_hypertable     \n--------------------------\n (3,public,yearly_data,t)\n\nCREATE MATERIALIZED VIEW cagg_yearly\nWITH (timescaledb.continuous, timescaledb.materialized_only = true) AS\nSELECT time_bucket('1 year'::interval, time) AS bucket,\n       count(*) AS cnt\nFROM yearly_data\nGROUP BY 1\nWITH NO DATA;\nINSERT INTO yearly_data\nSELECT ts, extract(epoch FROM ts)::int % 1000\nFROM generate_series('2024-01-01 00:00:00'::timestamptz,\n                     '2025-12-31 00:00:00'::timestamptz,\n                     '1 day'::interval) ts;\n-- Verify each year bucket has the right number of rows\nCALL refresh_continuous_aggregate('cagg_yearly', '2024-01-01 00:00:00', '2026-01-01 00:00:00');\nSELECT bucket, cnt FROM cagg_yearly ORDER BY bucket;\n         bucket         | cnt \n------------------------+-----\n 2024-01-01 00:00:00+00 | 366\n 2025-01-01 00:00:00+00 | 365\n\n-----------------------------------------------------------------------\n-- Test 2a: Invalidation crossing year boundary\n-----------------------------------------------------------------------\nINSERT INTO yearly_data VALUES ('2023-12-31 23:59:59.999999', 1111.0);\nINSERT INTO yearly_data VALUES ('2024-01-01 00:00:00', 2222.0);\n-- Check that both years are invalidated\nSELECT * FROM hyper_inval_log;\n     hypertable     |          inval_start          |           inval_end           \n--------------------+-------------------------------+-------------------------------\n public.yearly_data | 2023-12-31 23:59:59.999999+00 | 2023-12-31 23:59:59.999999+00\n public.yearly_data | 2024-01-01 00:00:00+00        | 2024-01-01 00:00:00+00\n\n-- Refresh only 2023 - should leave 2024 invalidation in the log\nCALL refresh_continuous_aggregate('cagg_yearly', '2023-01-01 00:00:00', '2024-01-01 00:00:00');\nSELECT * FROM cagg_inval_log WHERE cagg_name = 'cagg_yearly';\n  cagg_name  |      inval_start       |           inval_end           \n-------------+------------------------+-------------------------------\n cagg_yearly | -infinity              | 2022-12-31 23:59:59.999999+00\n cagg_yearly | 2024-01-01 00:00:00+00 | 2024-12-31 23:59:59.999999+00\n cagg_yearly | 2026-01-01 00:00:00+00 | infinity\n\nCALL refresh_continuous_aggregate('cagg_yearly', '2024-01-01 00:00:00', '2025-01-01 00:00:00');\nSELECT * FROM cagg_inval_log WHERE cagg_name = 'cagg_yearly';\n  cagg_name  |      inval_start       |           inval_end           \n-------------+------------------------+-------------------------------\n cagg_yearly | -infinity              | 2022-12-31 23:59:59.999999+00\n cagg_yearly | 2026-01-01 00:00:00+00 | infinity\n\n-----------------------------------------------------------------------\n-- SECTION 3: DST transitions with timezone-aware monthly buckets\n-- Tests that bucket boundaries are correct during spring-forward\n-- and fall-back DST changes.\n-----------------------------------------------------------------------\nSET timezone TO 'Europe/Berlin';\nCREATE TABLE dst_data (\n    time TIMESTAMPTZ NOT NULL,\n    value FLOAT\n);\nSELECT create_hypertable('dst_data', 'time', chunk_time_interval => INTERVAL '1 month');\n   create_hypertable   \n-----------------------\n (5,public,dst_data,t)\n\n-- Daily bucket with Europe/Berlin timezone (DST transitions in March and October)\nCREATE MATERIALIZED VIEW cagg_dst_daily\nWITH (timescaledb.continuous, timescaledb.materialized_only = true) AS\nSELECT time_bucket('1 day'::interval, time, 'Europe/Berlin') AS bucket,\n       count(*) AS cnt\nFROM dst_data\nGROUP BY 1\nWITH NO DATA;\n-- Insert data around March 2025 DST spring-forward (Mar 30, 2025 at 2:00 AM Europe/Berlin)\nINSERT INTO dst_data\nSELECT ts, 1.0\nFROM generate_series('2025-03-30 00:00:00'::timestamptz,\n                     '2025-03-31 23:59:59.999999'::timestamptz,\n                     '1 hour'::interval) ts;\nCALL refresh_continuous_aggregate('cagg_dst_daily', '2025-03-01 00:00:00', '2025-05-01 00:00:00');\n-- March 30 should have 23 hours\nSELECT bucket, cnt FROM cagg_dst_daily\nORDER BY bucket;\n         bucket         | cnt \n------------------------+-----\n 2025-03-30 00:00:00+01 |  23\n 2025-03-31 00:00:00+02 |  24\n\n-----------------------------------------------------------------------\n-- Test 3a: Fall-back DST transition (October 2025)\n-- Oct 26, 2025 at 3:00 AM Europe/Berlin becomes 2:00 AM (repeated hour)\n-----------------------------------------------------------------------\nINSERT INTO dst_data\nSELECT ts, 2.0\nFROM generate_series('2025-10-26 00:00:00'::timestamptz,\n                     '2025-10-27 23:59:59.999999'::timestamptz,\n                     '1 hour'::interval) ts;\n-- Wide window to cover all DST-shifted buckets\nCALL refresh_continuous_aggregate('cagg_dst_daily', '2025-10-01 00:00:00', '2026-12-01 00:00:00');\n-- October bucket should have extra hour (25-hour day on Oct 26)\nSELECT bucket, cnt FROM cagg_dst_daily\nWHERE bucket >= '2025-10-01 00:00:00' AND bucket < '2026-01-01 00:00:00'\nORDER BY bucket;\n         bucket         | cnt \n------------------------+-----\n 2025-10-26 00:00:00+02 |  25\n 2025-10-27 00:00:00+01 |  24\n\n-- Insert near the fall-back boundary\nINSERT INTO dst_data VALUES ('2025-10-26 01:00:00', 888.0);  -- 2:00 AM Europe/Berlin (after fall-back)\nINSERT INTO dst_data VALUES ('2025-10-26 00:30:00', 777.0);  -- 2:30 AM Europe/Berlin (before fall-back)\nCALL refresh_continuous_aggregate('cagg_dst_daily', '2025-09-01 00:00:00', '2026-02-01 00:00:00');\nSELECT * FROM cagg_inval_log WHERE cagg_name = 'cagg_dst_daily';\n   cagg_name    |      inval_start       |           inval_end           \n----------------+------------------------+-------------------------------\n cagg_dst_daily | -infinity              | 2025-02-28 23:59:59.999999+01\n cagg_dst_daily | 2025-05-01 00:00:00+02 | 2025-08-31 23:59:59.999999+02\n cagg_dst_daily | 2026-12-01 00:00:00+01 | infinity\n\nSET timezone TO 'UTC';\n-----------------------------------------------------------------------\n-- SECTION 4: Two-month buckets\n-- Tests 2-month intervals where pairs of months have different totals:\n-- Jan+Feb: 59-60 days, Mar+Apr: 61, May+Jun: 61, Jul+Aug: 62,\n-- Sep+Oct: 61, Nov+Dec: 61\n-----------------------------------------------------------------------\nCREATE TABLE bimonthly_data (\n    time TIMESTAMPTZ NOT NULL,\n    value INT\n);\nSELECT create_hypertable('bimonthly_data', 'time', chunk_time_interval => INTERVAL '1 month');\n      create_hypertable      \n-----------------------------\n (7,public,bimonthly_data,t)\n\nCREATE MATERIALIZED VIEW cagg_bimonthly\nWITH (timescaledb.continuous, timescaledb.materialized_only = true) AS\nSELECT time_bucket('2 months'::interval, time) AS bucket,\n       count(*) AS cnt\nFROM bimonthly_data\nGROUP BY 1\nWITH NO DATA;\nINSERT INTO bimonthly_data\nSELECT ts, 1\nFROM generate_series('2025-01-01 00:00:00'::timestamptz,\n                     '2025-12-31 00:00:00'::timestamptz,\n                     '1 day'::interval) ts;\nCALL refresh_continuous_aggregate('cagg_bimonthly', '2025-01-01 00:00:00', '2025-12-31 00:00:00');\nSELECT bucket, cnt FROM cagg_bimonthly ORDER BY bucket;\n         bucket         | cnt \n------------------------+-----\n 2025-01-01 00:00:00+00 |  59\n 2025-03-01 00:00:00+00 |  61\n 2025-05-01 00:00:00+00 |  61\n 2025-07-01 00:00:00+00 |  62\n 2025-09-01 00:00:00+00 |  61\n\n-----------------------------------------------------------------------\n-- Test 4a: Invalidation at the boundary between 2-month buckets\n-- (Feb 29 / Mar 1 boundary in leap year, also the JanFeb/MarApr bucket boundary)\n-----------------------------------------------------------------------\nINSERT INTO bimonthly_data VALUES ('2024-02-29 23:59:59.999999', 999);\nINSERT INTO bimonthly_data VALUES ('2024-03-01 00:00:00', 888);\n-- Refresh only the Jan-Feb bucket\nCALL refresh_continuous_aggregate('cagg_bimonthly', '2024-01-01 00:00:00', '2024-03-01 00:00:00');\n-- Mar-Apr invalidation should remain\nSELECT * FROM cagg_inval_log WHERE cagg_name = 'cagg_bimonthly';\n   cagg_name    |      inval_start       |           inval_end           \n----------------+------------------------+-------------------------------\n cagg_bimonthly | -infinity              | 2023-12-31 23:59:59.999999+00\n cagg_bimonthly | 2024-03-01 00:00:00+00 | 2024-12-31 23:59:59.999999+00\n cagg_bimonthly | 2025-11-01 00:00:00+00 | infinity\n\n-- Refresh Mar-Apr\nCALL refresh_continuous_aggregate('cagg_bimonthly', '2024-03-01 00:00:00', '2024-05-01 00:00:00');\nSELECT * FROM cagg_inval_log WHERE cagg_name = 'cagg_bimonthly';\n   cagg_name    |      inval_start       |           inval_end           \n----------------+------------------------+-------------------------------\n cagg_bimonthly | -infinity              | 2023-12-31 23:59:59.999999+00\n cagg_bimonthly | 2024-05-01 00:00:00+00 | 2024-12-31 23:59:59.999999+00\n cagg_bimonthly | 2025-11-01 00:00:00+00 | infinity\n\n-----------------------------------------------------------------------\n-- SECTION 5: Variable-width buckets with offset\n-- Tests that invalidations are correctly processed when variable-width\n-- buckets are shifted by an offset.\n-----------------------------------------------------------------------\nCREATE TABLE offset_data (\n    time TIMESTAMPTZ NOT NULL,\n    device INT,\n    value FLOAT\n);\nSELECT create_hypertable('offset_data', 'time', chunk_time_interval => INTERVAL '1 month');\n    create_hypertable     \n--------------------------\n (9,public,offset_data,t)\n\n-- Create a 1-month bucket cagg with a 15-day offset\nCREATE MATERIALIZED VIEW cagg_month_offset\nWITH (timescaledb.continuous, timescaledb.materialized_only = true) AS\nSELECT time_bucket('1 month'::interval, time, \"offset\" := INTERVAL '15 days') AS bucket,\n       device,\n       count(*) AS cnt\nFROM offset_data\nGROUP BY 1, 2\nWITH NO DATA;\n-- Insert data spanning 12 months of 2024 (leap year)\nINSERT INTO offset_data\nSELECT ts, 1, extract(epoch FROM ts)::int % 100\nFROM generate_series('2024-01-01 00:00:00'::timestamptz,\n                     '2024-12-31 23:59:59'::timestamptz,\n                     '1 day'::interval) ts;\nCALL refresh_continuous_aggregate('cagg_month_offset', '2024-01-01 00:00:00', '2025-01-01 00:00:00');\n-- Buckets should be shifted to the 16th of each month:\nSELECT bucket, cnt FROM cagg_month_offset ORDER BY bucket;\n         bucket         | cnt \n------------------------+-----\n 2024-01-16 00:00:00+00 |  31\n 2024-02-16 00:00:00+00 |  29\n 2024-03-16 00:00:00+00 |  31\n 2024-04-16 00:00:00+00 |  30\n 2024-05-16 00:00:00+00 |  31\n 2024-06-16 00:00:00+00 |  30\n 2024-07-16 00:00:00+00 |  31\n 2024-08-16 00:00:00+00 |  31\n 2024-09-16 00:00:00+00 |  30\n 2024-10-16 00:00:00+00 |  31\n 2024-11-16 00:00:00+00 |  30\n\n-- No invalidations should remain for the refreshed range\nSELECT * FROM cagg_inval_log WHERE cagg_name = 'cagg_month_offset';\n     cagg_name     |      inval_start       |           inval_end           \n-------------------+------------------------+-------------------------------\n cagg_month_offset | -infinity              | 2024-01-15 23:59:59.999999+00\n cagg_month_offset | 2024-12-16 00:00:00+00 | infinity\n\n-----------------------------------------------------------------------\n-- Test 5a: Invalidation at an offset bucket boundary (Feb 16)\n-----------------------------------------------------------------------\n-- Insert just before the bucket boundary\nINSERT INTO offset_data VALUES ('2024-02-15 23:59:59.999999', 1, 888.0);\n-- Insert at the bucket boundary\nINSERT INTO offset_data VALUES ('2024-02-16 00:00:00', 1, 777.0);\nSELECT * FROM hyper_inval_log;\n     hypertable     |          inval_start          |           inval_end           \n--------------------+-------------------------------+-------------------------------\n public.offset_data | 2024-02-15 23:59:59.999999+00 | 2024-02-15 23:59:59.999999+00\n public.offset_data | 2024-02-16 00:00:00+00        | 2024-02-16 00:00:00+00\n\nCALL refresh_continuous_aggregate('cagg_month_offset', '2024-01-01 00:00:00', '2024-04-01 00:00:00');\nSELECT bucket, cnt FROM cagg_month_offset\nWHERE bucket >= '2024-01-16 00:00:00' AND bucket <= '2024-02-16 00:00:00'\nORDER BY bucket;\n         bucket         | cnt \n------------------------+-----\n 2024-01-16 00:00:00+00 |  32\n 2024-02-16 00:00:00+00 |  30\n\nSELECT * FROM cagg_inval_log WHERE cagg_name = 'cagg_month_offset';\n     cagg_name     |      inval_start       |           inval_end           \n-------------------+------------------------+-------------------------------\n cagg_month_offset | -infinity              | 2024-01-15 23:59:59.999999+00\n cagg_month_offset | 2024-12-16 00:00:00+00 | infinity\n\n-----------------------------------------------------------------------\n-- Test 5b: Partial refresh leaves correct invalidation with offset\n-----------------------------------------------------------------------\n-- Insert data across two offset bucket boundaries\nINSERT INTO offset_data VALUES ('2024-05-15 12:00:00', 1, 100.0);  -- in Apr 16 - May 16 bucket\nINSERT INTO offset_data VALUES ('2024-06-20 12:00:00', 1, 200.0);  -- in Jun 16 - Jul 16 bucket\n-- Refresh only May\nCALL refresh_continuous_aggregate('cagg_month_offset', '2024-04-01 00:00:00', '2024-06-01 00:00:00');\n-- Jun 16 - Jul 16 invalidation should remain\nSELECT * FROM cagg_inval_log WHERE cagg_name = 'cagg_month_offset';\n     cagg_name     |      inval_start       |           inval_end           \n-------------------+------------------------+-------------------------------\n cagg_month_offset | -infinity              | 2024-01-15 23:59:59.999999+00\n cagg_month_offset | 2024-06-16 00:00:00+00 | 2024-07-15 23:59:59.999999+00\n cagg_month_offset | 2024-12-16 00:00:00+00 | infinity\n\nCALL refresh_continuous_aggregate('cagg_month_offset', '2024-06-01 00:00:00', '2024-08-01 00:00:00');\nSELECT * FROM cagg_inval_log WHERE cagg_name = 'cagg_month_offset';\n     cagg_name     |      inval_start       |           inval_end           \n-------------------+------------------------+-------------------------------\n cagg_month_offset | -infinity              | 2024-01-15 23:59:59.999999+00\n cagg_month_offset | 2024-12-16 00:00:00+00 | infinity\n\n-----------------------------------------------------------------------\n-- Test 5d: Offset with timezone and variable-width bucket\n-----------------------------------------------------------------------\nSET timezone TO 'Europe/Berlin';\nCREATE TABLE offset_tz_data (\n    time TIMESTAMPTZ NOT NULL,\n    value FLOAT\n);\nSELECT create_hypertable('offset_tz_data', 'time', chunk_time_interval => INTERVAL '1 month');\n      create_hypertable       \n------------------------------\n (11,public,offset_tz_data,t)\n\nCREATE MATERIALIZED VIEW cagg_offset_tz\nWITH (timescaledb.continuous, timescaledb.materialized_only = true) AS\nSELECT time_bucket('1 day'::interval, time, 'Europe/Berlin', \"offset\" := INTERVAL '2 hour') AS bucket,\n       count(*) AS cnt\nFROM offset_tz_data\nGROUP BY 1\nWITH NO DATA;\n-- Insert data around the DST spring-forward (Mar 30, 2025 at 2:00 AM Europe/Berlin)\nINSERT INTO offset_tz_data\nSELECT ts, 1.0\nFROM generate_series('2025-03-28 00:00:00'::timestamptz,\n                     '2025-04-01 23:59:59'::timestamptz,\n                     '1 hour'::interval) ts;\nCALL refresh_continuous_aggregate('cagg_offset_tz', '2025-03-01 00:00:00', '2025-05-01 00:00:00');\nSELECT bucket, cnt FROM cagg_offset_tz ORDER BY bucket;\n         bucket         | cnt \n------------------------+-----\n 2025-03-27 02:00:00+01 |   2\n 2025-03-28 02:00:00+01 |  24\n 2025-03-29 02:00:00+01 |  24\n 2025-03-30 03:00:00+02 |  23\n 2025-03-31 02:00:00+02 |  24\n 2025-04-01 02:00:00+02 |  22\n\nINSERT INTO offset_tz_data VALUES ('2025-03-30 01:59:59.999999', 888.0);\nINSERT INTO offset_tz_data VALUES ('2025-03-30 02:00:00', 777.0);\nCALL refresh_continuous_aggregate('cagg_offset_tz', '2025-03-01 00:00:00', '2025-06-01 00:00:00');\nSELECT bucket, cnt FROM cagg_offset_tz ORDER BY bucket;\n         bucket         | cnt \n------------------------+-----\n 2025-03-27 02:00:00+01 |   2\n 2025-03-28 02:00:00+01 |  24\n 2025-03-29 02:00:00+01 |  25\n 2025-03-30 03:00:00+02 |  24\n 2025-03-31 02:00:00+02 |  24\n 2025-04-01 02:00:00+02 |  22\n\nSELECT * FROM cagg_inval_log WHERE cagg_name = 'cagg_offset_tz';\n   cagg_name    |      inval_start       |           inval_end           \n----------------+------------------------+-------------------------------\n cagg_offset_tz | -infinity              | 2025-03-01 01:59:59.999999+01\n cagg_offset_tz | 2025-05-31 02:00:00+02 | infinity\n\nSET timezone TO 'UTC';\n-----------------------------------------------------------------------\n-- SECTION 6: Variable-width buckets with custom origin\n-- Tests that invalidations are correctly processed when variable-width\n-- buckets use a custom origin.\n-----------------------------------------------------------------------\nCREATE TABLE origin_data (\n    time TIMESTAMPTZ NOT NULL,\n    device INT,\n    value FLOAT\n);\nSELECT create_hypertable('origin_data', 'time', chunk_time_interval => INTERVAL '1 month');\n     create_hypertable     \n---------------------------\n (13,public,origin_data,t)\n\n-- 2-month buckets with origin in Feb: shifts pairs from Jan+Feb/Mar+Apr to Feb+Mar/Apr+May\nCREATE MATERIALIZED VIEW cagg_bimonth_origin\nWITH (timescaledb.continuous, timescaledb.materialized_only = true) AS\nSELECT time_bucket('2 months'::interval, time, '2025-02-01 00:00:00+00'::timestamptz) AS bucket,\n       device,\n       count(*) AS cnt\nFROM origin_data\nGROUP BY 1, 2\nWITH NO DATA;\n-- Insert data spanning 12 months of 2024 (leap year)\nINSERT INTO origin_data\nSELECT ts, 1, extract(epoch FROM ts)::int % 100\nFROM generate_series('2025-01-01 00:00:00'::timestamptz,\n                     '2025-12-31 23:59:59'::timestamptz,\n                     '1 day'::interval) ts;\nCALL refresh_continuous_aggregate('cagg_bimonth_origin', '2025-01-01 00:00:00', '2026-01-01 00:00:00');\nSELECT bucket, cnt FROM cagg_bimonth_origin ORDER BY bucket;\n         bucket         | cnt \n------------------------+-----\n 2025-02-01 00:00:00+00 |  59\n 2025-04-01 00:00:00+00 |  61\n 2025-06-01 00:00:00+00 |  61\n 2025-08-01 00:00:00+00 |  61\n 2025-10-01 00:00:00+00 |  61\n\nSELECT * FROM cagg_inval_log WHERE cagg_name = 'cagg_bimonth_origin';\n      cagg_name      |      inval_start       |           inval_end           \n---------------------+------------------------+-------------------------------\n cagg_bimonth_origin | -infinity              | 2025-01-31 23:59:59.999999+00\n cagg_bimonth_origin | 2025-12-01 00:00:00+00 | infinity\n\n-----------------------------------------------------------------------\n-- Test 6a: Invalidation at the origin-shifted bucket boundary (Apr 1)\n-----------------------------------------------------------------------\n-- Insert at the shifted boundary\nINSERT INTO origin_data VALUES ('2025-03-31 23:59:59.999999', 1, 888.0);\nINSERT INTO origin_data VALUES ('2025-04-01 00:00:00', 1, 777.0);\nSELECT * FROM hyper_inval_log;\n     hypertable     |          inval_start          |           inval_end           \n--------------------+-------------------------------+-------------------------------\n public.origin_data | 2025-03-31 23:59:59.999999+00 | 2025-03-31 23:59:59.999999+00\n public.origin_data | 2025-04-01 00:00:00+00        | 2025-04-01 00:00:00+00\n\nCALL refresh_continuous_aggregate('cagg_bimonth_origin', '2025-02-01 00:00:00', '2025-06-01 00:00:00');\n-- Verify Feb+Mar and Apr+May buckets are updated\nSELECT bucket, cnt FROM cagg_bimonth_origin\nWHERE bucket IN ('2025-02-01 00:00:00', '2025-04-01 00:00:00')\nORDER BY bucket;\n         bucket         | cnt \n------------------------+-----\n 2025-02-01 00:00:00+00 |  60\n 2025-04-01 00:00:00+00 |  62\n\nSELECT * FROM cagg_inval_log WHERE cagg_name = 'cagg_bimonth_origin';\n      cagg_name      |      inval_start       |           inval_end           \n---------------------+------------------------+-------------------------------\n cagg_bimonth_origin | -infinity              | 2025-01-31 23:59:59.999999+00\n cagg_bimonth_origin | 2025-12-01 00:00:00+00 | infinity\n\n-----------------------------------------------------------------------\n-- Test 6b: Partial refresh leaves correct invalidation with origin\n-----------------------------------------------------------------------\n-- Insert across two origin-shifted bucket boundaries\nINSERT INTO origin_data VALUES ('2025-05-20 12:00:00', 1, 100.0);  -- in Apr+May bucket\nINSERT INTO origin_data VALUES ('2025-07-10 12:00:00', 1, 200.0);  -- in Jun+Jul bucket\n-- Refresh only the Apr-May window\nCALL refresh_continuous_aggregate('cagg_bimonth_origin', '2025-04-01 00:00:00', '2025-06-01 00:00:00');\n-- Jun+Jul invalidation should remain\nSELECT * FROM cagg_inval_log WHERE cagg_name = 'cagg_bimonth_origin';\n      cagg_name      |      inval_start       |           inval_end           \n---------------------+------------------------+-------------------------------\n cagg_bimonth_origin | -infinity              | 2025-01-31 23:59:59.999999+00\n cagg_bimonth_origin | 2025-06-01 00:00:00+00 | 2025-07-31 23:59:59.999999+00\n cagg_bimonth_origin | 2025-12-01 00:00:00+00 | infinity\n\nCALL refresh_continuous_aggregate('cagg_bimonth_origin', '2025-06-01 00:00:00', '2025-09-01 00:00:00');\nSELECT * FROM cagg_inval_log WHERE cagg_name = 'cagg_bimonth_origin';\n      cagg_name      |      inval_start       |           inval_end           \n---------------------+------------------------+-------------------------------\n cagg_bimonth_origin | -infinity              | 2025-01-31 23:59:59.999999+00\n cagg_bimonth_origin | 2025-12-01 00:00:00+00 | infinity\n\n-----------------------------------------------------------------------\n-- Test 6d: Origin with timezone and variable-width bucket\n-- 2-month bucket with origin in Feb and Europe/Berlin timezone,\n-- covering the DST spring-forward transition.\n-----------------------------------------------------------------------\nSET timezone TO 'Europe/Berlin';\nCREATE TABLE origin_tz_data (\n    time TIMESTAMPTZ NOT NULL,\n    value FLOAT\n);\nSELECT create_hypertable('origin_tz_data', 'time', chunk_time_interval => INTERVAL '1 month');\n      create_hypertable       \n------------------------------\n (15,public,origin_tz_data,t)\n\nCREATE MATERIALIZED VIEW cagg_origin_tz\nWITH (timescaledb.continuous, timescaledb.materialized_only = true) AS\nSELECT time_bucket('1 day'::interval, time, 'Europe/Berlin',\n                   origin := '2025-02-01 05:00:00 Europe/Berlin'::timestamptz) AS bucket,\n       count(*) AS cnt\nFROM origin_tz_data\nGROUP BY 1\nWITH NO DATA;\n-- DST transition on Mar 30 in Europe/Berlin\nINSERT INTO origin_tz_data\nSELECT ts, 1.0\nFROM generate_series('2025-03-29 00:00:00'::timestamptz,\n                     '2025-03-31 23:59:59'::timestamptz,\n                     '1 hour'::interval) ts;\nCALL refresh_continuous_aggregate('cagg_origin_tz', '2025-02-01 00:00:00', '2025-04-01 00:00:00');\nSELECT bucket, cnt FROM cagg_origin_tz ORDER BY bucket;\n         bucket         | cnt \n------------------------+-----\n 2025-03-28 05:00:00+01 |   5\n 2025-03-29 05:00:00+01 |  23\n 2025-03-30 05:00:00+02 |  24\n\nSELECT * FROM cagg_inval_log WHERE cagg_name = 'cagg_origin_tz';\n   cagg_name    |      inval_start       |           inval_end           \n----------------+------------------------+-------------------------------\n cagg_origin_tz | -infinity              | 2025-02-01 04:59:59.999999+01\n cagg_origin_tz | 2025-03-31 05:00:00+02 | infinity\n\nSET timezone TO 'UTC';\n-----------------------------------------------------------------------\n-- SECTION 8: Hierarchical continuous aggregates with variable-width buckets\n-- Tests invalidation propagation through a two-level hierarchy:\n-- Level 1: 1-month buckets on raw hypertable\n-- Level 2: 3-month (quarterly) buckets on Level 1\n-----------------------------------------------------------------------\nCREATE TABLE hier_data (\n    time TIMESTAMPTZ NOT NULL,\n    value FLOAT\n);\nSELECT create_hypertable('hier_data', 'time', chunk_time_interval => INTERVAL '1 month');\n    create_hypertable    \n-------------------------\n (17,public,hier_data,t)\n\nCREATE MATERIALIZED VIEW cagg_hier_monthly\nWITH (timescaledb.continuous, timescaledb.materialized_only = true) AS\nSELECT time_bucket('1 month'::interval, time) AS bucket,\n       count(*) AS cnt\nFROM hier_data\nGROUP BY 1\nWITH NO DATA;\nCREATE MATERIALIZED VIEW cagg_hier_quarterly\nWITH (timescaledb.continuous, timescaledb.materialized_only = true) AS\nSELECT time_bucket('3 months'::interval, bucket) AS bucket,\n       sum(cnt) AS cnt\nFROM cagg_hier_monthly\nGROUP BY 1\nWITH NO DATA;\nINSERT INTO hier_data\nSELECT ts, 1.0\nFROM generate_series('2025-01-01 00:00:00'::timestamptz,\n                     '2025-12-31 23:59:59'::timestamptz,\n                     '1 day'::interval) ts;\nCALL refresh_continuous_aggregate('cagg_hier_monthly', '2025-01-01 00:00:00', '2026-01-01 00:00:00');\nCALL refresh_continuous_aggregate('cagg_hier_quarterly', '2025-01-01 00:00:00', '2026-01-01 00:00:00');\nSELECT bucket, cnt FROM cagg_hier_monthly ORDER BY bucket;\n         bucket         | cnt \n------------------------+-----\n 2025-01-01 00:00:00+00 |  31\n 2025-02-01 00:00:00+00 |  28\n 2025-03-01 00:00:00+00 |  31\n 2025-04-01 00:00:00+00 |  30\n 2025-05-01 00:00:00+00 |  31\n 2025-06-01 00:00:00+00 |  30\n 2025-07-01 00:00:00+00 |  31\n 2025-08-01 00:00:00+00 |  31\n 2025-09-01 00:00:00+00 |  30\n 2025-10-01 00:00:00+00 |  31\n 2025-11-01 00:00:00+00 |  30\n 2025-12-01 00:00:00+00 |  31\n\nSELECT bucket, cnt FROM cagg_hier_quarterly ORDER BY bucket;\n         bucket         | cnt \n------------------------+-----\n 2025-01-01 00:00:00+00 |  90\n 2025-04-01 00:00:00+00 |  91\n 2025-07-01 00:00:00+00 |  92\n 2025-10-01 00:00:00+00 |  92\n\nSELECT * FROM cagg_inval_log WHERE cagg_name IN ('cagg_hier_monthly', 'cagg_hier_quarterly') ORDER BY 1, 2;\n      cagg_name      |      inval_start       |           inval_end           \n---------------------+------------------------+-------------------------------\n cagg_hier_monthly   | -infinity              | 2024-12-31 23:59:59.999999+00\n cagg_hier_monthly   | 2026-01-01 00:00:00+00 | infinity\n cagg_hier_quarterly | -infinity              | 2024-12-31 23:59:59.999999+00\n cagg_hier_quarterly | 2026-01-01 00:00:00+00 | infinity\n\n-----------------------------------------------------------------------\n-- Test 8a: Insert into base, refresh Level 1 only, then Level 2\n-- Verifies that refreshing Level 1 creates invalidation in Level 2\n-- and that Level 2 refresh picks up the change.\n-----------------------------------------------------------------------\nINSERT INTO hier_data VALUES ('2025-02-15 12:00:00', 999.0);\nCALL refresh_continuous_aggregate('cagg_hier_monthly', '2025-02-01 00:00:00', '2025-03-01 00:00:00');\nSELECT bucket, cnt FROM cagg_hier_monthly WHERE bucket = '2025-02-01 00:00:00';\n         bucket         | cnt \n------------------------+-----\n 2025-02-01 00:00:00+00 |  29\n\nSELECT * FROM hyper_inval_log;\n                    hypertable                     |      inval_start       |       inval_end        \n---------------------------------------------------+------------------------+------------------------\n _timescaledb_internal._materialized_hypertable_18 | 2025-02-01 00:00:00+00 | 2025-02-01 00:00:00+00\n\nSELECT * FROM cagg_inval_log WHERE cagg_name = 'cagg_hier_quarterly';\n      cagg_name      |      inval_start       |           inval_end           \n---------------------+------------------------+-------------------------------\n cagg_hier_quarterly | -infinity              | 2024-12-31 23:59:59.999999+00\n cagg_hier_quarterly | 2026-01-01 00:00:00+00 | infinity\n\n-- Refresh Level 2 for Q1\nCALL refresh_continuous_aggregate('cagg_hier_quarterly', '2025-01-01 00:00:00', '2025-04-01 00:00:00');\nSELECT bucket, cnt FROM cagg_hier_quarterly WHERE bucket = '2025-01-01 00:00:00';\n         bucket         | cnt \n------------------------+-----\n 2025-01-01 00:00:00+00 |  91\n\nSELECT * FROM cagg_inval_log WHERE cagg_name = 'cagg_hier_quarterly';\n      cagg_name      |      inval_start       |           inval_end           \n---------------------+------------------------+-------------------------------\n cagg_hier_quarterly | -infinity              | 2024-12-31 23:59:59.999999+00\n cagg_hier_quarterly | 2026-01-01 00:00:00+00 | infinity\n\n-----------------------------------------------------------------------\n-- Test 8b: Invalidation at the quarter boundary (Mar 31 / Apr 1)\n-- Mar 31 is in Q1, Apr 1 is in Q2. Refreshing Level 1 modifies both\n-- March and April in the mat table, so Level 2 should update both\n-- Q1 and Q2.\n-----------------------------------------------------------------------\nINSERT INTO hier_data VALUES ('2025-03-31 23:59:59.999999', 888.0);\nINSERT INTO hier_data VALUES ('2025-04-01 00:00:00', 777.0);\n-- Refresh Level 1 for March and April\nCALL refresh_continuous_aggregate('cagg_hier_monthly', '2025-03-01 00:00:00', '2025-05-01 00:00:00');\nSELECT bucket, cnt FROM cagg_hier_monthly\nWHERE bucket IN ('2025-03-01 00:00:00', '2025-04-01 00:00:00')\nORDER BY bucket;\n         bucket         | cnt \n------------------------+-----\n 2025-03-01 00:00:00+00 |  32\n 2025-04-01 00:00:00+00 |  31\n\n-- Refresh Level 2 with a wide window covering both Q1 and Q2\nCALL refresh_continuous_aggregate('cagg_hier_quarterly', '2025-01-01 00:00:00', '2025-07-01 00:00:00');\nSELECT bucket, cnt FROM cagg_hier_quarterly\nWHERE bucket IN ('2025-01-01 00:00:00', '2025-04-01 00:00:00')\nORDER BY bucket;\n         bucket         | cnt \n------------------------+-----\n 2025-01-01 00:00:00+00 |  92\n 2025-04-01 00:00:00+00 |  91\n\nSELECT * FROM cagg_inval_log WHERE cagg_name = 'cagg_hier_quarterly';\n      cagg_name      |      inval_start       |           inval_end           \n---------------------+------------------------+-------------------------------\n cagg_hier_quarterly | -infinity              | 2024-12-31 23:59:59.999999+00\n cagg_hier_quarterly | 2026-01-01 00:00:00+00 | infinity\n\nDROP MATERIALIZED VIEW cagg_hier_quarterly;\nNOTICE:  drop cascades to 2 other objects\nDROP TABLE monthly_data CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to 3 other objects\nDROP TABLE yearly_data CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_4_22_chunk\nDROP TABLE dst_data CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_6_24_chunk\nDROP TABLE bimonthly_data CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to 3 other objects\nDROP TABLE offset_data CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to 2 other objects\nDROP TABLE offset_tz_data CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_12_59_chunk\nDROP TABLE origin_data CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_14_73_chunk\nDROP TABLE origin_tz_data CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_16_75_chunk\nDROP TABLE hier_data CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to 3 other objects\nDROP VIEW hyper_inval_log;\nDROP VIEW cagg_inval_log;\n"
  },
  {
    "path": "tsl/test/expected/cagg_joins.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set VERBOSITY default\nSET timezone TO PST8PDT;\nCREATE TABLE conditions(\n  day TIMESTAMPTZ NOT NULL,\n  city text NOT NULL,\n  temperature INT NOT NULL,\n  device_id int NOT NULL\n);\nSELECT table_name FROM create_hypertable('conditions', 'day', chunk_time_interval => INTERVAL '1 day');\n table_name \n------------\n conditions\n\nINSERT INTO conditions (day, city, temperature, device_id) VALUES\n  ('2021-06-14', 'Moscow', 26,1),\n  ('2021-06-15', 'Berlin', 22,2),\n  ('2021-06-16', 'Stockholm', 24,3),\n  ('2021-06-17', 'London', 24,4),\n  ('2021-06-18', 'London', 27,4),\n  ('2021-06-19', 'Moscow', 28,4),\n  ('2021-06-20', 'Moscow', 30,1),\n  ('2021-06-21', 'Berlin', 31,1),\n  ('2021-06-22', 'Stockholm', 34,1),\n  ('2021-06-23', 'Stockholm', 34,2),\n  ('2021-06-24', 'Moscow', 34,2),\n  ('2021-06-25', 'London', 32,3),\n  ('2021-06-26', 'Moscow', 32,3),\n  ('2021-06-27', 'Moscow', 31,3);\nCREATE TABLE conditions_dup AS SELECT * FROM conditions;\nSELECT table_name FROM create_hypertable('conditions_dup', 'day', chunk_time_interval => INTERVAL '1 day', migrate_data => true);\nNOTICE:  migrating data to chunks\nDETAIL:  Migration might take a while depending on the amount of data.\n   table_name   \n----------------\n conditions_dup\n\nCREATE TABLE devices ( device_id int not null, name text, location text);\nINSERT INTO devices values (1, 'thermo_1', 'Moscow'), (2, 'thermo_2', 'Berlin'),(3, 'thermo_3', 'London'),(4, 'thermo_4', 'Stockholm');\nCREATE TABLE location (location_id INTEGER, name TEXT);\nINSERT INTO location VALUES (1, 'Moscow'), (2, 'Berlin'), (3, 'London'), (4, 'Stockholm');\nCREATE TABLE devices_dup AS SELECT * FROM devices;\nCREATE VIEW devices_view AS SELECT * FROM devices;\n-- Cagg with inner join + realtime  aggregate\nCREATE MATERIALIZED VIEW cagg_realtime\nWITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM conditions, devices\nWHERE conditions.device_id = devices.device_id\nGROUP BY name, bucket\nORDER BY bucket;\nNOTICE:  refreshing continuous aggregate \"cagg_realtime\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\n\\d+ cagg_realtime\n                                 View \"public.cagg_realtime\"\n Column |           Type           | Collation | Nullable | Default | Storage  | Description \n--------+--------------------------+-----------+----------+---------+----------+-------------\n bucket | timestamp with time zone |           |          |         | plain    | \n avg    | numeric                  |           |          |         | main     | \n name   | text                     |           |          |         | extended | \nView definition:\n( SELECT _materialized_hypertable_3.bucket,\n    _materialized_hypertable_3.avg,\n    _materialized_hypertable_3.name\n   FROM _timescaledb_internal._materialized_hypertable_3\n  WHERE _materialized_hypertable_3.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(3)), '-infinity'::timestamp with time zone)\n  ORDER BY _materialized_hypertable_3.bucket)\nUNION ALL\n( SELECT time_bucket('@ 1 day'::interval, conditions.day) AS bucket,\n    avg(conditions.temperature) AS avg,\n    devices.name\n   FROM conditions,\n    devices\n  WHERE conditions.device_id = devices.device_id AND conditions.day >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(3)), '-infinity'::timestamp with time zone)\n  GROUP BY devices.name, (time_bucket('@ 1 day'::interval, conditions.day))\n  ORDER BY (time_bucket('@ 1 day'::interval, conditions.day)))\n  ORDER BY 1;\n\nSELECT * FROM cagg_realtime ORDER BY bucket, name;\n            bucket            |         avg         |   name   \n------------------------------+---------------------+----------\n Sun Jun 13 17:00:00 2021 PDT | 26.0000000000000000 | thermo_1\n Mon Jun 14 17:00:00 2021 PDT | 22.0000000000000000 | thermo_2\n Tue Jun 15 17:00:00 2021 PDT | 24.0000000000000000 | thermo_3\n Wed Jun 16 17:00:00 2021 PDT | 24.0000000000000000 | thermo_4\n Thu Jun 17 17:00:00 2021 PDT | 27.0000000000000000 | thermo_4\n Fri Jun 18 17:00:00 2021 PDT | 28.0000000000000000 | thermo_4\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1\n Sun Jun 20 17:00:00 2021 PDT | 31.0000000000000000 | thermo_1\n Mon Jun 21 17:00:00 2021 PDT | 34.0000000000000000 | thermo_1\n Tue Jun 22 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Wed Jun 23 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Fri Jun 25 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Sat Jun 26 17:00:00 2021 PDT | 31.0000000000000000 | thermo_3\n\nINSERT INTO conditions (day, city, temperature, device_id) VALUES\n  ('2021-06-30', 'Moscow', 28, 3);\nSELECT * FROM cagg_realtime ORDER BY bucket, name;\n            bucket            |         avg         |   name   \n------------------------------+---------------------+----------\n Sun Jun 13 17:00:00 2021 PDT | 26.0000000000000000 | thermo_1\n Mon Jun 14 17:00:00 2021 PDT | 22.0000000000000000 | thermo_2\n Tue Jun 15 17:00:00 2021 PDT | 24.0000000000000000 | thermo_3\n Wed Jun 16 17:00:00 2021 PDT | 24.0000000000000000 | thermo_4\n Thu Jun 17 17:00:00 2021 PDT | 27.0000000000000000 | thermo_4\n Fri Jun 18 17:00:00 2021 PDT | 28.0000000000000000 | thermo_4\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1\n Sun Jun 20 17:00:00 2021 PDT | 31.0000000000000000 | thermo_1\n Mon Jun 21 17:00:00 2021 PDT | 34.0000000000000000 | thermo_1\n Tue Jun 22 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Wed Jun 23 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Fri Jun 25 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Sat Jun 26 17:00:00 2021 PDT | 31.0000000000000000 | thermo_3\n Tue Jun 29 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n\n-- Cagg with inner join + realtime  aggregate + JOIN clause\nCREATE MATERIALIZED VIEW cagg_realtime_join\nWITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM conditions JOIN devices\nON conditions.device_id = devices.device_id\nGROUP BY name, bucket\nORDER BY bucket;\nNOTICE:  refreshing continuous aggregate \"cagg_realtime_join\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\n\\d+ cagg_realtime_join\n                              View \"public.cagg_realtime_join\"\n Column |           Type           | Collation | Nullable | Default | Storage  | Description \n--------+--------------------------+-----------+----------+---------+----------+-------------\n bucket | timestamp with time zone |           |          |         | plain    | \n avg    | numeric                  |           |          |         | main     | \n name   | text                     |           |          |         | extended | \nView definition:\n( SELECT _materialized_hypertable_4.bucket,\n    _materialized_hypertable_4.avg,\n    _materialized_hypertable_4.name\n   FROM _timescaledb_internal._materialized_hypertable_4\n  WHERE _materialized_hypertable_4.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(4)), '-infinity'::timestamp with time zone)\n  ORDER BY _materialized_hypertable_4.bucket)\nUNION ALL\n( SELECT time_bucket('@ 1 day'::interval, conditions.day) AS bucket,\n    avg(conditions.temperature) AS avg,\n    devices.name\n   FROM conditions\n     JOIN devices ON conditions.device_id = devices.device_id\n  WHERE conditions.day >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(4)), '-infinity'::timestamp with time zone)\n  GROUP BY devices.name, (time_bucket('@ 1 day'::interval, conditions.day))\n  ORDER BY (time_bucket('@ 1 day'::interval, conditions.day)))\n  ORDER BY 1;\n\nSELECT * FROM cagg_realtime_join ORDER BY bucket, name;\n            bucket            |         avg         |   name   \n------------------------------+---------------------+----------\n Sun Jun 13 17:00:00 2021 PDT | 26.0000000000000000 | thermo_1\n Mon Jun 14 17:00:00 2021 PDT | 22.0000000000000000 | thermo_2\n Tue Jun 15 17:00:00 2021 PDT | 24.0000000000000000 | thermo_3\n Wed Jun 16 17:00:00 2021 PDT | 24.0000000000000000 | thermo_4\n Thu Jun 17 17:00:00 2021 PDT | 27.0000000000000000 | thermo_4\n Fri Jun 18 17:00:00 2021 PDT | 28.0000000000000000 | thermo_4\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1\n Sun Jun 20 17:00:00 2021 PDT | 31.0000000000000000 | thermo_1\n Mon Jun 21 17:00:00 2021 PDT | 34.0000000000000000 | thermo_1\n Tue Jun 22 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Wed Jun 23 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Fri Jun 25 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Sat Jun 26 17:00:00 2021 PDT | 31.0000000000000000 | thermo_3\n Tue Jun 29 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n\nINSERT INTO conditions (day, city, temperature, device_id) VALUES\n  ('2021-06-30', 'Moscow', 28, 3);\nSELECT * FROM cagg_realtime_join ORDER BY bucket, name;\n            bucket            |         avg         |   name   \n------------------------------+---------------------+----------\n Sun Jun 13 17:00:00 2021 PDT | 26.0000000000000000 | thermo_1\n Mon Jun 14 17:00:00 2021 PDT | 22.0000000000000000 | thermo_2\n Tue Jun 15 17:00:00 2021 PDT | 24.0000000000000000 | thermo_3\n Wed Jun 16 17:00:00 2021 PDT | 24.0000000000000000 | thermo_4\n Thu Jun 17 17:00:00 2021 PDT | 27.0000000000000000 | thermo_4\n Fri Jun 18 17:00:00 2021 PDT | 28.0000000000000000 | thermo_4\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1\n Sun Jun 20 17:00:00 2021 PDT | 31.0000000000000000 | thermo_1\n Mon Jun 21 17:00:00 2021 PDT | 34.0000000000000000 | thermo_1\n Tue Jun 22 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Wed Jun 23 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Fri Jun 25 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Sat Jun 26 17:00:00 2021 PDT | 31.0000000000000000 | thermo_3\n Tue Jun 29 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n\n-- Cagg with inner join + realtime  aggregate + USING clause\nCREATE MATERIALIZED VIEW cagg_realtime_using\nWITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM conditions JOIN devices\nUSING (device_id)\nGROUP BY name, bucket\nORDER BY bucket;\nNOTICE:  refreshing continuous aggregate \"cagg_realtime_using\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\n\\d+ cagg_realtime_using\n                              View \"public.cagg_realtime_using\"\n Column |           Type           | Collation | Nullable | Default | Storage  | Description \n--------+--------------------------+-----------+----------+---------+----------+-------------\n bucket | timestamp with time zone |           |          |         | plain    | \n avg    | numeric                  |           |          |         | main     | \n name   | text                     |           |          |         | extended | \nView definition:\n( SELECT _materialized_hypertable_5.bucket,\n    _materialized_hypertable_5.avg,\n    _materialized_hypertable_5.name\n   FROM _timescaledb_internal._materialized_hypertable_5\n  WHERE _materialized_hypertable_5.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(5)), '-infinity'::timestamp with time zone)\n  ORDER BY _materialized_hypertable_5.bucket)\nUNION ALL\n( SELECT time_bucket('@ 1 day'::interval, conditions.day) AS bucket,\n    avg(conditions.temperature) AS avg,\n    devices.name\n   FROM conditions\n     JOIN devices USING (device_id)\n  WHERE conditions.day >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(5)), '-infinity'::timestamp with time zone)\n  GROUP BY devices.name, (time_bucket('@ 1 day'::interval, conditions.day))\n  ORDER BY (time_bucket('@ 1 day'::interval, conditions.day)))\n  ORDER BY 1;\n\nSELECT * FROM cagg_realtime_using ORDER BY bucket;\n            bucket            |         avg         |   name   \n------------------------------+---------------------+----------\n Sun Jun 13 17:00:00 2021 PDT | 26.0000000000000000 | thermo_1\n Mon Jun 14 17:00:00 2021 PDT | 22.0000000000000000 | thermo_2\n Tue Jun 15 17:00:00 2021 PDT | 24.0000000000000000 | thermo_3\n Wed Jun 16 17:00:00 2021 PDT | 24.0000000000000000 | thermo_4\n Thu Jun 17 17:00:00 2021 PDT | 27.0000000000000000 | thermo_4\n Fri Jun 18 17:00:00 2021 PDT | 28.0000000000000000 | thermo_4\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1\n Sun Jun 20 17:00:00 2021 PDT | 31.0000000000000000 | thermo_1\n Mon Jun 21 17:00:00 2021 PDT | 34.0000000000000000 | thermo_1\n Tue Jun 22 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Wed Jun 23 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Fri Jun 25 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Sat Jun 26 17:00:00 2021 PDT | 31.0000000000000000 | thermo_3\n Tue Jun 29 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n\nINSERT INTO conditions (day, city, temperature, device_id) VALUES\n  ('2021-06-30', 'Moscow', 28, 3);\nSELECT * FROM cagg_realtime_using ORDER BY bucket, name;\n            bucket            |         avg         |   name   \n------------------------------+---------------------+----------\n Sun Jun 13 17:00:00 2021 PDT | 26.0000000000000000 | thermo_1\n Mon Jun 14 17:00:00 2021 PDT | 22.0000000000000000 | thermo_2\n Tue Jun 15 17:00:00 2021 PDT | 24.0000000000000000 | thermo_3\n Wed Jun 16 17:00:00 2021 PDT | 24.0000000000000000 | thermo_4\n Thu Jun 17 17:00:00 2021 PDT | 27.0000000000000000 | thermo_4\n Fri Jun 18 17:00:00 2021 PDT | 28.0000000000000000 | thermo_4\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1\n Sun Jun 20 17:00:00 2021 PDT | 31.0000000000000000 | thermo_1\n Mon Jun 21 17:00:00 2021 PDT | 34.0000000000000000 | thermo_1\n Tue Jun 22 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Wed Jun 23 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Fri Jun 25 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Sat Jun 26 17:00:00 2021 PDT | 31.0000000000000000 | thermo_3\n Tue Jun 29 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n\n-- Reorder tables in FROM clause\n-- Cagg with inner join + realtime aggregate\nCREATE MATERIALIZED VIEW cagg_realtime_reorder\nWITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM devices, conditions\nWHERE conditions.device_id = devices.device_id\nGROUP BY name, bucket\nORDER BY bucket;\nNOTICE:  refreshing continuous aggregate \"cagg_realtime_reorder\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\n\\d+ cagg_realtime_reorder\n                             View \"public.cagg_realtime_reorder\"\n Column |           Type           | Collation | Nullable | Default | Storage  | Description \n--------+--------------------------+-----------+----------+---------+----------+-------------\n bucket | timestamp with time zone |           |          |         | plain    | \n avg    | numeric                  |           |          |         | main     | \n name   | text                     |           |          |         | extended | \nView definition:\n( SELECT _materialized_hypertable_6.bucket,\n    _materialized_hypertable_6.avg,\n    _materialized_hypertable_6.name\n   FROM _timescaledb_internal._materialized_hypertable_6\n  WHERE _materialized_hypertable_6.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(6)), '-infinity'::timestamp with time zone)\n  ORDER BY _materialized_hypertable_6.bucket)\nUNION ALL\n( SELECT time_bucket('@ 1 day'::interval, conditions.day) AS bucket,\n    avg(conditions.temperature) AS avg,\n    devices.name\n   FROM devices,\n    conditions\n  WHERE conditions.device_id = devices.device_id AND conditions.day >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(6)), '-infinity'::timestamp with time zone)\n  GROUP BY devices.name, (time_bucket('@ 1 day'::interval, conditions.day))\n  ORDER BY (time_bucket('@ 1 day'::interval, conditions.day)))\n  ORDER BY 1;\n\nSELECT * FROM cagg_realtime_reorder ORDER BY bucket, name;\n            bucket            |         avg         |   name   \n------------------------------+---------------------+----------\n Sun Jun 13 17:00:00 2021 PDT | 26.0000000000000000 | thermo_1\n Mon Jun 14 17:00:00 2021 PDT | 22.0000000000000000 | thermo_2\n Tue Jun 15 17:00:00 2021 PDT | 24.0000000000000000 | thermo_3\n Wed Jun 16 17:00:00 2021 PDT | 24.0000000000000000 | thermo_4\n Thu Jun 17 17:00:00 2021 PDT | 27.0000000000000000 | thermo_4\n Fri Jun 18 17:00:00 2021 PDT | 28.0000000000000000 | thermo_4\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1\n Sun Jun 20 17:00:00 2021 PDT | 31.0000000000000000 | thermo_1\n Mon Jun 21 17:00:00 2021 PDT | 34.0000000000000000 | thermo_1\n Tue Jun 22 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Wed Jun 23 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Fri Jun 25 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Sat Jun 26 17:00:00 2021 PDT | 31.0000000000000000 | thermo_3\n Tue Jun 29 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n\nINSERT INTO conditions (day, city, temperature, device_id) VALUES\n  ('2021-07-01', 'Moscow', 28, 3);\nSELECT * FROM cagg_realtime_reorder ORDER BY bucket, name;\n            bucket            |         avg         |   name   \n------------------------------+---------------------+----------\n Sun Jun 13 17:00:00 2021 PDT | 26.0000000000000000 | thermo_1\n Mon Jun 14 17:00:00 2021 PDT | 22.0000000000000000 | thermo_2\n Tue Jun 15 17:00:00 2021 PDT | 24.0000000000000000 | thermo_3\n Wed Jun 16 17:00:00 2021 PDT | 24.0000000000000000 | thermo_4\n Thu Jun 17 17:00:00 2021 PDT | 27.0000000000000000 | thermo_4\n Fri Jun 18 17:00:00 2021 PDT | 28.0000000000000000 | thermo_4\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1\n Sun Jun 20 17:00:00 2021 PDT | 31.0000000000000000 | thermo_1\n Mon Jun 21 17:00:00 2021 PDT | 34.0000000000000000 | thermo_1\n Tue Jun 22 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Wed Jun 23 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Fri Jun 25 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Sat Jun 26 17:00:00 2021 PDT | 31.0000000000000000 | thermo_3\n Tue Jun 29 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n Wed Jun 30 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n\n-- Reorder tables in FROM clause\n-- Cagg with inner join + realtime  aggregate + JOIN clause\nCREATE MATERIALIZED VIEW cagg_realtime_reorder_join\nWITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM devices JOIN conditions\nON conditions.device_id = devices.device_id\nGROUP BY name, bucket\nORDER BY bucket;\nNOTICE:  refreshing continuous aggregate \"cagg_realtime_reorder_join\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\n\\d+ cagg_realtime_reorder_join\n                          View \"public.cagg_realtime_reorder_join\"\n Column |           Type           | Collation | Nullable | Default | Storage  | Description \n--------+--------------------------+-----------+----------+---------+----------+-------------\n bucket | timestamp with time zone |           |          |         | plain    | \n avg    | numeric                  |           |          |         | main     | \n name   | text                     |           |          |         | extended | \nView definition:\n( SELECT _materialized_hypertable_7.bucket,\n    _materialized_hypertable_7.avg,\n    _materialized_hypertable_7.name\n   FROM _timescaledb_internal._materialized_hypertable_7\n  WHERE _materialized_hypertable_7.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(7)), '-infinity'::timestamp with time zone)\n  ORDER BY _materialized_hypertable_7.bucket)\nUNION ALL\n( SELECT time_bucket('@ 1 day'::interval, conditions.day) AS bucket,\n    avg(conditions.temperature) AS avg,\n    devices.name\n   FROM devices\n     JOIN conditions ON conditions.device_id = devices.device_id\n  WHERE conditions.day >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(7)), '-infinity'::timestamp with time zone)\n  GROUP BY devices.name, (time_bucket('@ 1 day'::interval, conditions.day))\n  ORDER BY (time_bucket('@ 1 day'::interval, conditions.day)))\n  ORDER BY 1;\n\nSELECT * FROM cagg_realtime_reorder_join ORDER BY bucket, name;\n            bucket            |         avg         |   name   \n------------------------------+---------------------+----------\n Sun Jun 13 17:00:00 2021 PDT | 26.0000000000000000 | thermo_1\n Mon Jun 14 17:00:00 2021 PDT | 22.0000000000000000 | thermo_2\n Tue Jun 15 17:00:00 2021 PDT | 24.0000000000000000 | thermo_3\n Wed Jun 16 17:00:00 2021 PDT | 24.0000000000000000 | thermo_4\n Thu Jun 17 17:00:00 2021 PDT | 27.0000000000000000 | thermo_4\n Fri Jun 18 17:00:00 2021 PDT | 28.0000000000000000 | thermo_4\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1\n Sun Jun 20 17:00:00 2021 PDT | 31.0000000000000000 | thermo_1\n Mon Jun 21 17:00:00 2021 PDT | 34.0000000000000000 | thermo_1\n Tue Jun 22 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Wed Jun 23 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Fri Jun 25 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Sat Jun 26 17:00:00 2021 PDT | 31.0000000000000000 | thermo_3\n Tue Jun 29 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n Wed Jun 30 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n\nINSERT INTO conditions (day, city, temperature, device_id) VALUES\n  ('2021-07-01', 'Moscow', 28, 3);\nSELECT *\nFROM cagg_realtime_reorder_join;\n            bucket            |         avg         |   name   \n------------------------------+---------------------+----------\n Sun Jun 13 17:00:00 2021 PDT | 26.0000000000000000 | thermo_1\n Mon Jun 14 17:00:00 2021 PDT | 22.0000000000000000 | thermo_2\n Tue Jun 15 17:00:00 2021 PDT | 24.0000000000000000 | thermo_3\n Wed Jun 16 17:00:00 2021 PDT | 24.0000000000000000 | thermo_4\n Thu Jun 17 17:00:00 2021 PDT | 27.0000000000000000 | thermo_4\n Fri Jun 18 17:00:00 2021 PDT | 28.0000000000000000 | thermo_4\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1\n Sun Jun 20 17:00:00 2021 PDT | 31.0000000000000000 | thermo_1\n Mon Jun 21 17:00:00 2021 PDT | 34.0000000000000000 | thermo_1\n Tue Jun 22 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Wed Jun 23 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Fri Jun 25 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Sat Jun 26 17:00:00 2021 PDT | 31.0000000000000000 | thermo_3\n Tue Jun 29 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n Wed Jun 30 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n\n-- Reorder tables in FROM clause\n-- Cagg with inner join + realtime  aggregate + USING clause\nCREATE MATERIALIZED VIEW cagg_realtime_reorder_using\nWITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM devices JOIN conditions\nUSING (device_id)\nGROUP BY name, bucket\nORDER BY bucket;\nNOTICE:  refreshing continuous aggregate \"cagg_realtime_reorder_using\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\n\\d+ cagg_realtime_reorder_using\n                          View \"public.cagg_realtime_reorder_using\"\n Column |           Type           | Collation | Nullable | Default | Storage  | Description \n--------+--------------------------+-----------+----------+---------+----------+-------------\n bucket | timestamp with time zone |           |          |         | plain    | \n avg    | numeric                  |           |          |         | main     | \n name   | text                     |           |          |         | extended | \nView definition:\n( SELECT _materialized_hypertable_8.bucket,\n    _materialized_hypertable_8.avg,\n    _materialized_hypertable_8.name\n   FROM _timescaledb_internal._materialized_hypertable_8\n  WHERE _materialized_hypertable_8.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(8)), '-infinity'::timestamp with time zone)\n  ORDER BY _materialized_hypertable_8.bucket)\nUNION ALL\n( SELECT time_bucket('@ 1 day'::interval, conditions.day) AS bucket,\n    avg(conditions.temperature) AS avg,\n    devices.name\n   FROM devices\n     JOIN conditions USING (device_id)\n  WHERE conditions.day >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(8)), '-infinity'::timestamp with time zone)\n  GROUP BY devices.name, (time_bucket('@ 1 day'::interval, conditions.day))\n  ORDER BY (time_bucket('@ 1 day'::interval, conditions.day)))\n  ORDER BY 1;\n\nSELECT * FROM cagg_realtime_reorder_using ORDER BY bucket, name;\n            bucket            |         avg         |   name   \n------------------------------+---------------------+----------\n Sun Jun 13 17:00:00 2021 PDT | 26.0000000000000000 | thermo_1\n Mon Jun 14 17:00:00 2021 PDT | 22.0000000000000000 | thermo_2\n Tue Jun 15 17:00:00 2021 PDT | 24.0000000000000000 | thermo_3\n Wed Jun 16 17:00:00 2021 PDT | 24.0000000000000000 | thermo_4\n Thu Jun 17 17:00:00 2021 PDT | 27.0000000000000000 | thermo_4\n Fri Jun 18 17:00:00 2021 PDT | 28.0000000000000000 | thermo_4\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1\n Sun Jun 20 17:00:00 2021 PDT | 31.0000000000000000 | thermo_1\n Mon Jun 21 17:00:00 2021 PDT | 34.0000000000000000 | thermo_1\n Tue Jun 22 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Wed Jun 23 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Fri Jun 25 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Sat Jun 26 17:00:00 2021 PDT | 31.0000000000000000 | thermo_3\n Tue Jun 29 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n Wed Jun 30 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n\nINSERT INTO conditions (day, city, temperature, device_id) VALUES\n  ('2021-07-01', 'Moscow', 28, 3);\nSELECT * FROM cagg_realtime_reorder_using ORDER BY bucket, name;\n            bucket            |         avg         |   name   \n------------------------------+---------------------+----------\n Sun Jun 13 17:00:00 2021 PDT | 26.0000000000000000 | thermo_1\n Mon Jun 14 17:00:00 2021 PDT | 22.0000000000000000 | thermo_2\n Tue Jun 15 17:00:00 2021 PDT | 24.0000000000000000 | thermo_3\n Wed Jun 16 17:00:00 2021 PDT | 24.0000000000000000 | thermo_4\n Thu Jun 17 17:00:00 2021 PDT | 27.0000000000000000 | thermo_4\n Fri Jun 18 17:00:00 2021 PDT | 28.0000000000000000 | thermo_4\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1\n Sun Jun 20 17:00:00 2021 PDT | 31.0000000000000000 | thermo_1\n Mon Jun 21 17:00:00 2021 PDT | 34.0000000000000000 | thermo_1\n Tue Jun 22 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Wed Jun 23 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Fri Jun 25 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Sat Jun 26 17:00:00 2021 PDT | 31.0000000000000000 | thermo_3\n Tue Jun 29 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n Wed Jun 30 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n\n-- Cagg with inner joins - realtime aggregate\nCREATE MATERIALIZED VIEW cagg\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name,\n   devices.device_id AS thermo_id\nFROM conditions, devices\nWHERE conditions.device_id = devices.device_id\nGROUP BY bucket, name, thermo_id\nORDER BY bucket;\nNOTICE:  refreshing continuous aggregate \"cagg\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\nSELECT * FROM cagg ORDER BY bucket, name, thermo_id;\n            bucket            |         avg         |   name   | thermo_id \n------------------------------+---------------------+----------+-----------\n Sun Jun 13 17:00:00 2021 PDT | 26.0000000000000000 | thermo_1 |         1\n Mon Jun 14 17:00:00 2021 PDT | 22.0000000000000000 | thermo_2 |         2\n Tue Jun 15 17:00:00 2021 PDT | 24.0000000000000000 | thermo_3 |         3\n Wed Jun 16 17:00:00 2021 PDT | 24.0000000000000000 | thermo_4 |         4\n Thu Jun 17 17:00:00 2021 PDT | 27.0000000000000000 | thermo_4 |         4\n Fri Jun 18 17:00:00 2021 PDT | 28.0000000000000000 | thermo_4 |         4\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1 |         1\n Sun Jun 20 17:00:00 2021 PDT | 31.0000000000000000 | thermo_1 |         1\n Mon Jun 21 17:00:00 2021 PDT | 34.0000000000000000 | thermo_1 |         1\n Tue Jun 22 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2 |         2\n Wed Jun 23 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2 |         2\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3 |         3\n Fri Jun 25 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3 |         3\n Sat Jun 26 17:00:00 2021 PDT | 31.0000000000000000 | thermo_3 |         3\n Tue Jun 29 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3 |         3\n Wed Jun 30 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3 |         3\n\n-- Cagg with inner joins - realtime aggregate + JOIN clause\nCREATE MATERIALIZED VIEW cagg_join\nWITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM conditions JOIN devices ON conditions.device_id = devices.device_id\nGROUP BY bucket,name\nORDER BY bucket;\nNOTICE:  refreshing continuous aggregate \"cagg_join\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\nSELECT * FROM cagg_join ORDER BY bucket, name;\n            bucket            |         avg         |   name   \n------------------------------+---------------------+----------\n Sun Jun 13 17:00:00 2021 PDT | 26.0000000000000000 | thermo_1\n Mon Jun 14 17:00:00 2021 PDT | 22.0000000000000000 | thermo_2\n Tue Jun 15 17:00:00 2021 PDT | 24.0000000000000000 | thermo_3\n Wed Jun 16 17:00:00 2021 PDT | 24.0000000000000000 | thermo_4\n Thu Jun 17 17:00:00 2021 PDT | 27.0000000000000000 | thermo_4\n Fri Jun 18 17:00:00 2021 PDT | 28.0000000000000000 | thermo_4\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1\n Sun Jun 20 17:00:00 2021 PDT | 31.0000000000000000 | thermo_1\n Mon Jun 21 17:00:00 2021 PDT | 34.0000000000000000 | thermo_1\n Tue Jun 22 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Wed Jun 23 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Fri Jun 25 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Sat Jun 26 17:00:00 2021 PDT | 31.0000000000000000 | thermo_3\n Tue Jun 29 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n Wed Jun 30 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n\n-- Cagg with inner joins - realtime aggregate + USING clause\nCREATE MATERIALIZED VIEW cagg_using\nWITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM conditions JOIN devices USING (device_id)\nGROUP BY name, bucket\nORDER BY bucket;\nNOTICE:  refreshing continuous aggregate \"cagg_using\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\nSELECT * FROM cagg_using;\n            bucket            |         avg         |   name   \n------------------------------+---------------------+----------\n Sun Jun 13 17:00:00 2021 PDT | 26.0000000000000000 | thermo_1\n Mon Jun 14 17:00:00 2021 PDT | 22.0000000000000000 | thermo_2\n Tue Jun 15 17:00:00 2021 PDT | 24.0000000000000000 | thermo_3\n Wed Jun 16 17:00:00 2021 PDT | 24.0000000000000000 | thermo_4\n Thu Jun 17 17:00:00 2021 PDT | 27.0000000000000000 | thermo_4\n Fri Jun 18 17:00:00 2021 PDT | 28.0000000000000000 | thermo_4\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1\n Sun Jun 20 17:00:00 2021 PDT | 31.0000000000000000 | thermo_1\n Mon Jun 21 17:00:00 2021 PDT | 34.0000000000000000 | thermo_1\n Tue Jun 22 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Wed Jun 23 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Fri Jun 25 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Sat Jun 26 17:00:00 2021 PDT | 31.0000000000000000 | thermo_3\n Tue Jun 29 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n Wed Jun 30 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n\n-- Reorder tables in FROM clause\n-- Cagg with inner joins - realtime aggregate\nCREATE MATERIALIZED VIEW cagg_reorder\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM devices, conditions\nWHERE conditions.device_id = devices.device_id\nGROUP BY bucket, name\nORDER BY bucket;\nNOTICE:  refreshing continuous aggregate \"cagg_reorder\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\nSELECT * FROM cagg_reorder ORDER BY bucket, name;\n            bucket            |         avg         |   name   \n------------------------------+---------------------+----------\n Sun Jun 13 17:00:00 2021 PDT | 26.0000000000000000 | thermo_1\n Mon Jun 14 17:00:00 2021 PDT | 22.0000000000000000 | thermo_2\n Tue Jun 15 17:00:00 2021 PDT | 24.0000000000000000 | thermo_3\n Wed Jun 16 17:00:00 2021 PDT | 24.0000000000000000 | thermo_4\n Thu Jun 17 17:00:00 2021 PDT | 27.0000000000000000 | thermo_4\n Fri Jun 18 17:00:00 2021 PDT | 28.0000000000000000 | thermo_4\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1\n Sun Jun 20 17:00:00 2021 PDT | 31.0000000000000000 | thermo_1\n Mon Jun 21 17:00:00 2021 PDT | 34.0000000000000000 | thermo_1\n Tue Jun 22 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Wed Jun 23 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Fri Jun 25 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Sat Jun 26 17:00:00 2021 PDT | 31.0000000000000000 | thermo_3\n Tue Jun 29 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n Wed Jun 30 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n\n-- Cagg with inner joins - realtime aggregate + JOIN clause\nCREATE MATERIALIZED VIEW cagg_reorder_join\nWITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM devices JOIN conditions ON conditions.device_id = devices.device_id\nGROUP BY name, bucket\nORDER BY bucket;\nNOTICE:  refreshing continuous aggregate \"cagg_reorder_join\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\nSELECT * FROM cagg_reorder_join;\n            bucket            |         avg         |   name   \n------------------------------+---------------------+----------\n Sun Jun 13 17:00:00 2021 PDT | 26.0000000000000000 | thermo_1\n Mon Jun 14 17:00:00 2021 PDT | 22.0000000000000000 | thermo_2\n Tue Jun 15 17:00:00 2021 PDT | 24.0000000000000000 | thermo_3\n Wed Jun 16 17:00:00 2021 PDT | 24.0000000000000000 | thermo_4\n Thu Jun 17 17:00:00 2021 PDT | 27.0000000000000000 | thermo_4\n Fri Jun 18 17:00:00 2021 PDT | 28.0000000000000000 | thermo_4\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1\n Sun Jun 20 17:00:00 2021 PDT | 31.0000000000000000 | thermo_1\n Mon Jun 21 17:00:00 2021 PDT | 34.0000000000000000 | thermo_1\n Tue Jun 22 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Wed Jun 23 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Fri Jun 25 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Sat Jun 26 17:00:00 2021 PDT | 31.0000000000000000 | thermo_3\n Tue Jun 29 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n Wed Jun 30 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n\n-- Cagg with inner joins - realtime aggregate + USING clause\nCREATE MATERIALIZED VIEW cagg_reorder_using\nWITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM devices JOIN conditions USING (device_id)\nGROUP BY name, bucket\nORDER BY bucket;\nNOTICE:  refreshing continuous aggregate \"cagg_reorder_using\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\nSELECT * FROM cagg_reorder_using;\n            bucket            |         avg         |   name   \n------------------------------+---------------------+----------\n Sun Jun 13 17:00:00 2021 PDT | 26.0000000000000000 | thermo_1\n Mon Jun 14 17:00:00 2021 PDT | 22.0000000000000000 | thermo_2\n Tue Jun 15 17:00:00 2021 PDT | 24.0000000000000000 | thermo_3\n Wed Jun 16 17:00:00 2021 PDT | 24.0000000000000000 | thermo_4\n Thu Jun 17 17:00:00 2021 PDT | 27.0000000000000000 | thermo_4\n Fri Jun 18 17:00:00 2021 PDT | 28.0000000000000000 | thermo_4\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1\n Sun Jun 20 17:00:00 2021 PDT | 31.0000000000000000 | thermo_1\n Mon Jun 21 17:00:00 2021 PDT | 34.0000000000000000 | thermo_1\n Tue Jun 22 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Wed Jun 23 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Fri Jun 25 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Sat Jun 26 17:00:00 2021 PDT | 31.0000000000000000 | thermo_3\n Tue Jun 29 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n Wed Jun 30 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n\n-- Cagg join with another table using FROM ONLY\nCREATE MATERIALIZED VIEW cagg_from_only\nWITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM ONLY devices JOIN conditions USING (device_id)\nGROUP BY name, bucket\nORDER BY bucket;\nNOTICE:  refreshing continuous aggregate \"cagg_from_only\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\nSELECT * FROM cagg_from_only;\n            bucket            |         avg         |   name   \n------------------------------+---------------------+----------\n Sun Jun 13 17:00:00 2021 PDT | 26.0000000000000000 | thermo_1\n Mon Jun 14 17:00:00 2021 PDT | 22.0000000000000000 | thermo_2\n Tue Jun 15 17:00:00 2021 PDT | 24.0000000000000000 | thermo_3\n Wed Jun 16 17:00:00 2021 PDT | 24.0000000000000000 | thermo_4\n Thu Jun 17 17:00:00 2021 PDT | 27.0000000000000000 | thermo_4\n Fri Jun 18 17:00:00 2021 PDT | 28.0000000000000000 | thermo_4\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1\n Sun Jun 20 17:00:00 2021 PDT | 31.0000000000000000 | thermo_1\n Mon Jun 21 17:00:00 2021 PDT | 34.0000000000000000 | thermo_1\n Tue Jun 22 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Wed Jun 23 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Fri Jun 25 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n Sat Jun 26 17:00:00 2021 PDT | 31.0000000000000000 | thermo_3\n Tue Jun 29 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n Wed Jun 30 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3\n\n-- Create CAgg with join and additional WHERE conditions\nCREATE MATERIALIZED VIEW cagg_more_conds\nWITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM conditions JOIN devices ON conditions.device_id = devices.device_id\nWHERE conditions.city = devices.location AND\n      conditions.temperature > 28\nGROUP BY name, bucket;\nNOTICE:  refreshing continuous aggregate \"cagg_more_conds\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\nSELECT * FROM cagg_more_conds ORDER BY bucket;\n            bucket            |         avg         |   name   \n------------------------------+---------------------+----------\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n\n-- Cagg with more conditions and USING clause\nCREATE MATERIALIZED VIEW cagg_more_conds_using\nWITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM conditions JOIN devices USING (device_id)\nWHERE conditions.city = devices.location AND\n      conditions.temperature > 28\nGROUP BY name, bucket;\nNOTICE:  refreshing continuous aggregate \"cagg_more_conds_using\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\nSELECT * FROM cagg_more_conds_using ORDER BY bucket;\n            bucket            |         avg         |   name   \n------------------------------+---------------------+----------\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n\n-- Hierarchical CAgg with join\nCREATE MATERIALIZED VIEW cagg_on_cagg\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT time_bucket(INTERVAL '1 day', bucket) AS bucket,\n       SUM(avg) AS temperature\nFROM cagg, devices\nWHERE devices.device_id = cagg.thermo_id\nGROUP BY 1;\nNOTICE:  refreshing continuous aggregate \"cagg_on_cagg\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\nSELECT * FROM cagg_on_cagg ORDER BY bucket;\n            bucket            |     temperature     \n------------------------------+---------------------\n Sun Jun 13 17:00:00 2021 PDT | 26.0000000000000000\n Mon Jun 14 17:00:00 2021 PDT | 22.0000000000000000\n Tue Jun 15 17:00:00 2021 PDT | 24.0000000000000000\n Wed Jun 16 17:00:00 2021 PDT | 24.0000000000000000\n Thu Jun 17 17:00:00 2021 PDT | 27.0000000000000000\n Fri Jun 18 17:00:00 2021 PDT | 28.0000000000000000\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000\n Sun Jun 20 17:00:00 2021 PDT | 31.0000000000000000\n Mon Jun 21 17:00:00 2021 PDT | 34.0000000000000000\n Tue Jun 22 17:00:00 2021 PDT | 34.0000000000000000\n Wed Jun 23 17:00:00 2021 PDT | 34.0000000000000000\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000\n Fri Jun 25 17:00:00 2021 PDT | 32.0000000000000000\n Sat Jun 26 17:00:00 2021 PDT | 31.0000000000000000\n Tue Jun 29 17:00:00 2021 PDT | 28.0000000000000000\n Wed Jun 30 17:00:00 2021 PDT | 28.0000000000000000\n\nDROP MATERIALIZED VIEW cagg_on_cagg CASCADE;\nNOTICE:  drop cascades to 2 other objects\nDETAIL:  drop cascades to table _timescaledb_internal._hyper_18_61_chunk\ndrop cascades to table _timescaledb_internal._hyper_18_62_chunk\n-- Nested CAgg over a CAgg with JOIN clause\nCREATE MATERIALIZED VIEW cagg_on_cagg_join\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT time_bucket(INTERVAL '1 day', bucket) AS bucket,\n       SUM(avg) AS temperature\nFROM cagg JOIN devices\nON devices.device_id = cagg.thermo_id\nGROUP BY 1;\nNOTICE:  refreshing continuous aggregate \"cagg_on_cagg_join\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\nSELECT * FROM cagg_on_cagg_join ORDER BY bucket;\n            bucket            |     temperature     \n------------------------------+---------------------\n Sun Jun 13 17:00:00 2021 PDT | 26.0000000000000000\n Mon Jun 14 17:00:00 2021 PDT | 22.0000000000000000\n Tue Jun 15 17:00:00 2021 PDT | 24.0000000000000000\n Wed Jun 16 17:00:00 2021 PDT | 24.0000000000000000\n Thu Jun 17 17:00:00 2021 PDT | 27.0000000000000000\n Fri Jun 18 17:00:00 2021 PDT | 28.0000000000000000\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000\n Sun Jun 20 17:00:00 2021 PDT | 31.0000000000000000\n Mon Jun 21 17:00:00 2021 PDT | 34.0000000000000000\n Tue Jun 22 17:00:00 2021 PDT | 34.0000000000000000\n Wed Jun 23 17:00:00 2021 PDT | 34.0000000000000000\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000\n Fri Jun 25 17:00:00 2021 PDT | 32.0000000000000000\n Sat Jun 26 17:00:00 2021 PDT | 31.0000000000000000\n Tue Jun 29 17:00:00 2021 PDT | 28.0000000000000000\n Wed Jun 30 17:00:00 2021 PDT | 28.0000000000000000\n\nDROP MATERIALIZED VIEW cagg_on_cagg_join CASCADE;\nNOTICE:  drop cascades to 2 other objects\nDETAIL:  drop cascades to table _timescaledb_internal._hyper_19_63_chunk\ndrop cascades to table _timescaledb_internal._hyper_19_64_chunk\n-- Create CAgg with join and ORDER BY\nCREATE MATERIALIZED VIEW cagg_ordered\nWITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM conditions JOIN devices ON conditions.device_id = devices.device_id\nWHERE conditions.city = devices.location AND\n      conditions.temperature > 28\nGROUP BY name, bucket\nORDER BY name;\nNOTICE:  refreshing continuous aggregate \"cagg_ordered\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\nSELECT * FROM cagg_ordered ORDER BY bucket;\n            bucket            |         avg         |   name   \n------------------------------+---------------------+----------\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n\nCREATE MATERIALIZED VIEW cagg_ordered_2\nWITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM conditions JOIN devices ON conditions.device_id = devices.device_id\nWHERE conditions.city = devices.location AND\n      conditions.temperature > 28\nGROUP BY name, bucket\nORDER BY name DESC;\nNOTICE:  refreshing continuous aggregate \"cagg_ordered_2\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\nSELECT * FROM cagg_ordered_2 ORDER BY bucket;\n            bucket            |         avg         |   name   \n------------------------------+---------------------+----------\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n\nCREATE MATERIALIZED VIEW cagg_ordered_3\nWITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM conditions JOIN devices ON conditions.device_id = devices.device_id\nWHERE conditions.city = devices.location AND\n      conditions.temperature > 28\nGROUP BY name, bucket\nORDER BY name, bucket;\nNOTICE:  refreshing continuous aggregate \"cagg_ordered_3\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\nSELECT * FROM cagg_ordered_3 ORDER BY bucket;\n            bucket            |         avg         |   name   \n------------------------------+---------------------+----------\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3\n\nCREATE MATERIALIZED VIEW cagg_cagg\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   devices.device_id device_id,\n   name\nFROM conditions, devices\nWHERE conditions.device_id = devices.device_id\nGROUP BY name, bucket, devices.device_id;\nNOTICE:  refreshing continuous aggregate \"cagg_cagg\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\n--Join between cagg and normal table\nCREATE MATERIALIZED VIEW cagg_nested\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS\nSELECT time_bucket(INTERVAL '1 day', cagg.bucket) AS bucket,\n   devices.name\nFROM cagg_cagg cagg, devices\nWHERE cagg.device_id = devices.device_id\nGROUP BY 1,2;\nNOTICE:  refreshing continuous aggregate \"cagg_nested\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\nDROP MATERIALIZED VIEW cagg_nested CASCADE;\nNOTICE:  drop cascades to 2 other objects\nDETAIL:  drop cascades to table _timescaledb_internal._hyper_24_73_chunk\ndrop cascades to table _timescaledb_internal._hyper_24_74_chunk\n-- CAgg with multiple join conditions without JOIN clause\nCREATE MATERIALIZED VIEW cagg_more_joins_conds\nWITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   MAX(temperature),\n   MIN(temperature),\n   name\nFROM conditions JOIN devices ON conditions.device_id = devices.device_id\nAND conditions.city = devices.location AND\n      conditions.temperature > 28\nGROUP BY name, bucket;\nNOTICE:  refreshing continuous aggregate \"cagg_more_joins_conds\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\n-- With LATERAL multiple tables in new format\nCREATE TABLE mat_t1(a integer, b integer, c TEXT);\nCREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only = FALSE)\nAS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket, temperature, count(*) from conditions,\nLATERAL (SELECT * FROM mat_t1 WHERE mat_t1.a = conditions.temperature) q\nGROUP BY bucket, temperature;\nNOTICE:  refreshing continuous aggregate \"mat_m1\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\n-- Joining a hypertable and view\nCREATE MATERIALIZED VIEW cagg_view\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   devices_view.device_id,\n   name\nFROM conditions, devices_view\nWHERE conditions.device_id = devices_view.device_id\nGROUP BY name, bucket, devices_view.device_id;\nNOTICE:  refreshing continuous aggregate \"cagg_view\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\nCREATE TABLE cities(name text, currency text);\nINSERT INTO cities VALUES ('Berlin', 'EUR'), ('London', 'PND');\n--Error out when FROM clause has sub selects\n\\set ON_ERROR_STOP 0\nCREATE MATERIALIZED VIEW conditions_summary_subselect\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM conditions JOIN\n(SELECT *\nFROM devices\nWHERE location in (\n   SELECT name\n   FROM cities\n   WHERE currency = 'EUR')) dev ON conditions.device_id = dev.device_id\nGROUP BY name, bucket;\nERROR:  invalid continuous aggregate view\nDETAIL:  Sub-queries are not supported in FROM clause.\n--Error out when WHERE clause has sub selects\nCREATE MATERIALIZED VIEW conditions_summary_subselect\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM conditions, devices\nWHERE conditions.city IN (\n   SELECT location\n   FROM devices\n   WHERE location in (\n      SELECT name\n      FROM cities\n      WHERE currency = 'EUR'))\nAND conditions.device_id = devices.device_id\nGROUP BY name, bucket;\nERROR:  invalid continuous aggregate query\nDETAIL:  CTEs and subqueries are not supported by continuous aggregates.\nCREATE MATERIALIZED VIEW conditions_summary_subselect\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM conditions JOIN devices USING(device_id)\nWHERE conditions.city IN (\n   SELECT location\n   FROM devices\n   WHERE location in (\n      SELECT name\n      FROM cities\n      WHERE currency = 'EUR'))\nGROUP BY name, bucket;\nERROR:  invalid continuous aggregate query\nDETAIL:  CTEs and subqueries are not supported by continuous aggregates.\nCREATE MATERIALIZED VIEW conditions_summary_subselect\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM conditions JOIN devices ON conditions.device_id = devices.device_id\nWHERE conditions.city IN (\n   SELECT location\n   FROM devices\n   WHERE location in (\n      SELECT name\n      FROM cities\n      WHERE currency = 'EUR'))\nGROUP BY name, bucket;\nERROR:  invalid continuous aggregate query\nDETAIL:  CTEs and subqueries are not supported by continuous aggregates.\nDROP TABLE cities CASCADE;\n--Error out when join is between two hypertables\nCREATE MATERIALIZED VIEW cagg_ht\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS\nSELECT time_bucket(INTERVAL '1 day', conditions.day) AS bucket,\n   AVG(conditions.temperature)\nFROM conditions, conditions_dup\nWHERE conditions.device_id = conditions_dup.device_id\nGROUP BY bucket;\nERROR:  invalid continuous aggregate view\nDETAIL:  Only one hypertable is allowed in continuous aggregate view.\n--Error out when join is between two normal tables\nCREATE MATERIALIZED VIEW cagg_nt\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS\nSELECT AVG(devices.device_id),\n   devices.name,\n   devices.location\nFROM devices, devices_dup\nWHERE devices.device_id = devices_dup.device_id\nGROUP BY devices.name, devices.location;\nERROR:  invalid continuous aggregate view\nDETAIL:  At least one hypertable should be used in the view definition.\n\\set ON_ERROR_STOP 1\n-- JOIN with non-equality condition\nCREATE MATERIALIZED VIEW cagg_unequal1\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM conditions, devices\nWHERE conditions.device_id <> devices.device_id\nGROUP BY name, bucket;\nNOTICE:  refreshing continuous aggregate \"cagg_unequal1\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\n-- JOIN with non-equality condition\nCREATE MATERIALIZED VIEW cagg_unequal2\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM conditions, devices\nWHERE conditions.device_id = devices.device_id AND\n      conditions.city like '%cow*'\nGROUP BY name, bucket;\nNOTICE:  refreshing continuous aggregate \"cagg_unequal2\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\n-- JOIN with non-equality condition\nCREATE MATERIALIZED VIEW cagg_unequal3\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM conditions\nJOIN devices ON conditions.device_id = devices.device_id OR conditions.city LIKE '%cow*'\nGROUP BY name, bucket;\nNOTICE:  refreshing continuous aggregate \"cagg_unequal3\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\n-- Error out when join type is not INNER or LEFT\n\\set ON_ERROR_STOP 0\nCREATE MATERIALIZED VIEW cagg_outer\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM conditions FULL JOIN devices\nON conditions.device_id = devices.device_id\nGROUP BY name, bucket;\nERROR:  only INNER or LEFT joins are supported in continuous aggregates\nCREATE MATERIALIZED VIEW cagg_right\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM conditions RIGHT JOIN devices\nON conditions.device_id = devices.device_id\nGROUP BY name, bucket;\nERROR:  only INNER or LEFT joins are supported in continuous aggregates\n\\set ON_ERROR_STOP 1\n-- LEFT JOIN is allowed\nCREATE MATERIALIZED VIEW cagg_left_join\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS\nSELECT time_bucket(INTERVAL '1 day', day) AS bucket,\n   AVG(temperature),\n   name\nFROM conditions LEFT JOIN devices\nON conditions.device_id = devices.device_id\nGROUP BY name, bucket;\nNOTICE:  refreshing continuous aggregate \"cagg_left_join\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\n-- Error out for join between cagg and hypertable\n\\set ON_ERROR_STOP 0\nCREATE MATERIALIZED VIEW cagg_nested_ht\nWITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS\nSELECT time_bucket(INTERVAL '1 day', cagg.bucket) AS bucket,\n   cagg.name,\n   conditions.temperature\nFROM cagg_cagg cagg, conditions\nWHERE cagg.device_id = conditions.device_id\nGROUP BY 1,2,3;\nERROR:  invalid continuous aggregate view\nDETAIL:  Only one hypertable is allowed in continuous aggregate view.\n\\set ON_ERROR_STOP 1\n-- Multiple JOINS are supported\nCREATE MATERIALIZED VIEW conditions_by_day WITH (timescaledb.continuous) AS\nSELECT time_bucket(INTERVAL '1 day', conditions.day) AS bucket,\n   AVG(conditions.temperature),\n   devices.name AS device,\n   location.name AS location\nFROM conditions\nJOIN devices ON conditions.device_id = devices.device_id\nJOIN location ON location.name = devices.location\nGROUP BY bucket, devices.name, location.name;\nNOTICE:  refreshing continuous aggregate \"conditions_by_day\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\nSELECT * FROM conditions_by_day ORDER BY bucket, device, location;\n            bucket            |         avg         |  device  | location  \n------------------------------+---------------------+----------+-----------\n Sun Jun 13 17:00:00 2021 PDT | 26.0000000000000000 | thermo_1 | Moscow\n Mon Jun 14 17:00:00 2021 PDT | 22.0000000000000000 | thermo_2 | Berlin\n Tue Jun 15 17:00:00 2021 PDT | 24.0000000000000000 | thermo_3 | London\n Wed Jun 16 17:00:00 2021 PDT | 24.0000000000000000 | thermo_4 | Stockholm\n Thu Jun 17 17:00:00 2021 PDT | 27.0000000000000000 | thermo_4 | Stockholm\n Fri Jun 18 17:00:00 2021 PDT | 28.0000000000000000 | thermo_4 | Stockholm\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1 | Moscow\n Sun Jun 20 17:00:00 2021 PDT | 31.0000000000000000 | thermo_1 | Moscow\n Mon Jun 21 17:00:00 2021 PDT | 34.0000000000000000 | thermo_1 | Moscow\n Tue Jun 22 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2 | Berlin\n Wed Jun 23 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2 | Berlin\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3 | London\n Fri Jun 25 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3 | London\n Sat Jun 26 17:00:00 2021 PDT | 31.0000000000000000 | thermo_3 | London\n Tue Jun 29 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3 | London\n Wed Jun 30 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3 | London\n\nALTER MATERIALIZED VIEW conditions_by_day SET (timescaledb.materialized_only = FALSE);\n-- Insert one more row on conditions and check the result (should have one more row)\nINSERT INTO conditions (day, city, temperature, device_id) VALUES\n  ('2024-07-01', 'Moscow', 28, 3);\nSELECT * FROM conditions_by_day ORDER BY bucket, device, location;\n            bucket            |         avg         |  device  | location  \n------------------------------+---------------------+----------+-----------\n Sun Jun 13 17:00:00 2021 PDT | 26.0000000000000000 | thermo_1 | Moscow\n Mon Jun 14 17:00:00 2021 PDT | 22.0000000000000000 | thermo_2 | Berlin\n Tue Jun 15 17:00:00 2021 PDT | 24.0000000000000000 | thermo_3 | London\n Wed Jun 16 17:00:00 2021 PDT | 24.0000000000000000 | thermo_4 | Stockholm\n Thu Jun 17 17:00:00 2021 PDT | 27.0000000000000000 | thermo_4 | Stockholm\n Fri Jun 18 17:00:00 2021 PDT | 28.0000000000000000 | thermo_4 | Stockholm\n Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1 | Moscow\n Sun Jun 20 17:00:00 2021 PDT | 31.0000000000000000 | thermo_1 | Moscow\n Mon Jun 21 17:00:00 2021 PDT | 34.0000000000000000 | thermo_1 | Moscow\n Tue Jun 22 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2 | Berlin\n Wed Jun 23 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2 | Berlin\n Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3 | London\n Fri Jun 25 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3 | London\n Sat Jun 26 17:00:00 2021 PDT | 31.0000000000000000 | thermo_3 | London\n Tue Jun 29 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3 | London\n Wed Jun 30 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3 | London\n Sun Jun 30 17:00:00 2024 PDT | 28.0000000000000000 | thermo_3 | London\n\n-- JOIN with a foreign table\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSELECT current_setting('port') AS \"PGPORT\", current_database() AS \"PGDATABASE\" \\gset\nCREATE EXTENSION postgres_fdw;\nCREATE SERVER loopback\n   FOREIGN DATA WRAPPER postgres_fdw\n   OPTIONS (host 'localhost', dbname :'PGDATABASE', port :'PGPORT');\nCREATE USER MAPPING FOR :ROLE_DEFAULT_PERM_USER\n   SERVER loopback\n   OPTIONS (user :'ROLE_DEFAULT_PERM_USER', password 'nopassword');\nALTER USER MAPPING FOR :ROLE_DEFAULT_PERM_USER\n   SERVER loopback\n   OPTIONS (ADD password_required 'false');\nGRANT USAGE ON FOREIGN SERVER loopback TO :ROLE_DEFAULT_PERM_USER;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER;\nSET timezone TO PST8PDT;\nCREATE FOREIGN TABLE devices_fdw (\n   device_id int not null,\n   name text,\n   location text\n) SERVER loopback OPTIONS (table_name 'devices');\nCREATE MATERIALIZED VIEW conditions_fdw WITH (timescaledb.continuous) AS\nSELECT time_bucket(INTERVAL '1 day', conditions.day) AS bucket,\n   AVG(conditions.temperature),\n   devices.name AS device\nFROM conditions\nJOIN devices_fdw AS devices ON conditions.device_id = devices.device_id\nGROUP BY bucket, devices.name;\nNOTICE:  refreshing continuous aggregate \"conditions_fdw\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\n\\set VERBOSITY terse\nSET client_min_messages TO WARNING;\nDROP TABLE conditions CASCADE;\nDROP TABLE devices CASCADE;\nDROP TABLE conditions_dup CASCADE;\nDROP TABLE devices_dup CASCADE;\nRESET client_min_messages;\n\\set VERBOSITY default\n-- SDC #1859\nCREATE TABLE conditions(\n  time TIMESTAMPTZ NOT NULL,\n  value FLOAT8 NOT NULL,\n  device_id int NOT NULL\n);\nSELECT table_name FROM create_hypertable('conditions', 'time', chunk_time_interval => INTERVAL '1 day');\n table_name \n------------\n conditions\n\nINSERT INTO conditions (time, value, device_id)\nSELECT t, 1, 1 FROM generate_series('2024-01-01 00:00:00-00'::timestamptz, '2024-12-31 00:00:00-00'::timestamptz, '1 hour'::interval) AS t;\nCREATE TABLE devices (device_id int not null, name text, location text);\nINSERT INTO devices values (1, 'thermo_1', 'Moscow'), (2, 'thermo_2', 'Berlin'),(3, 'thermo_3', 'London'),(4, 'thermo_4', 'Stockholm');\nCREATE MATERIALIZED VIEW cagg_realtime\nWITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS\nSELECT time_bucket(INTERVAL '1 day', time) AS bucket,\n   MAX(value),\n   MIN(value),\n   AVG(value),\n   devices.name,\n   devices.location\nFROM conditions\nJOIN devices ON conditions.device_id = devices.device_id\nGROUP BY name, location, bucket\nWITH NO DATA;\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nVACUUM ANALYZE;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT a.* FROM cagg_realtime a WHERE a.location = 'Moscow' ORDER BY bucket LIMIT 2;\n            bucket            | max | min | avg |   name   | location \n------------------------------+-----+-----+-----+----------+----------\n Sun Dec 31 16:00:00 2023 PST |   1 |   1 |   1 | thermo_1 | Moscow\n Mon Jan 01 16:00:00 2024 PST |   1 |   1 |   1 | thermo_1 | Moscow\n\n\\set VERBOSITY terse\nSET client_min_messages TO WARNING;\nDROP TABLE conditions CASCADE;\nDROP TABLE devices CASCADE;\nRESET client_min_messages;\n"
  },
  {
    "path": "tsl/test/expected/cagg_multi.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSET client_min_messages TO NOTICE;\nCREATE TABLE continuous_agg_test(timeval integer, col1 integer, col2 integer);\nselect create_hypertable('continuous_agg_test', 'timeval', chunk_time_interval=> 2);\n        create_hypertable         \n----------------------------------\n (1,public,continuous_agg_test,t)\n\nCREATE OR REPLACE FUNCTION integer_now_test1() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(timeval), 0) FROM continuous_agg_test $$;\nSELECT set_integer_now_func('continuous_agg_test', 'integer_now_test1');\n set_integer_now_func \n----------------------\n \n\nINSERT INTO continuous_agg_test VALUES\n    (10, - 4, 1), (11, - 3, 5), (12, - 3, 7), (13, - 3, 9), (14,-4, 11),\n    (15, -4, 22), (16, -4, 23);\n-- TEST for multiple continuous aggs\n--- invalidations are picked up by both caggs\nCREATE MATERIALIZED VIEW cagg_1( timed, cnt )\nWITH (timescaledb.continuous,\n      timescaledb.materialized_only=true)\nAS\n    SELECT time_bucket( 2, timeval), COUNT(col1)\n    FROM continuous_agg_test\n    GROUP BY 1 WITH NO DATA;\nCREATE MATERIALIZED VIEW cagg_2( timed, grp, maxval)\nWITH (timescaledb.continuous,\n      timescaledb.materialized_only=true)\nAS\n    SELECT time_bucket(2, timeval), col1, max(col2)\n    FROM continuous_agg_test\n    GROUP BY 1, 2 WITH NO DATA;\nselect view_name, view_owner, materialization_hypertable_name\nfrom timescaledb_information.continuous_aggregates ORDER BY 1;\n view_name |    view_owner     | materialization_hypertable_name \n-----------+-------------------+---------------------------------\n cagg_1    | default_perm_user | _materialized_hypertable_2\n cagg_2    | default_perm_user | _materialized_hypertable_3\n\n--TEST1: cagg_1 is materialized, not cagg_2.\nCALL refresh_continuous_aggregate('cagg_1', NULL, NULL);\nselect * from cagg_1 order by 1;\n timed | cnt \n-------+-----\n    10 |   2\n    12 |   2\n    14 |   2\n    16 |   1\n\nSELECT time_bucket(2, timeval), COUNT(col1) as value\nFROM continuous_agg_test\nGROUP BY 1 order by 1;\n time_bucket | value \n-------------+-------\n          10 |     2\n          12 |     2\n          14 |     2\n          16 |     1\n\n-- check that cagg_2 not materialized\nselect * from cagg_2 order by 1,2;\n timed | grp | maxval \n-------+-----+--------\n\nCALL refresh_continuous_aggregate('cagg_2', NULL, NULL);\nselect * from cagg_2 order by 1,2;\n timed | grp | maxval \n-------+-----+--------\n    10 |  -4 |      1\n    10 |  -3 |      5\n    12 |  -3 |      9\n    14 |  -4 |     22\n    16 |  -4 |     23\n\nSELECT * FROM _timescaledb_catalog.continuous_aggs_invalidation_threshold;\n hypertable_id | watermark \n---------------+-----------\n             1 |        18\n\n--TEST2: cagg_2 gets invalidations after cagg_1's refresh\n--will trigger invalidations\nINSERT INTO continuous_agg_test VALUES\n    (10, -4, 10), (11, - 3, 50), (11, - 3, 70), (10, - 4, 10);\nSELECT * FROM _timescaledb_catalog.continuous_aggs_invalidation_threshold;\n hypertable_id | watermark \n---------------+-----------\n             1 |        18\n\nCALL refresh_continuous_aggregate('cagg_1', NULL, NULL);\nselect * from cagg_1 order by 1;\n timed | cnt \n-------+-----\n    10 |   6\n    12 |   2\n    14 |   2\n    16 |   1\n\nSELECT time_bucket(2, timeval), COUNT(col1) as value\nFROM continuous_agg_test\nGROUP BY 1 order by 1;\n time_bucket | value \n-------------+-------\n          10 |     6\n          12 |     2\n          14 |     2\n          16 |     1\n\n-- are the invalidations picked up here?\nselect * from cagg_2 order by 1, 2;\n timed | grp | maxval \n-------+-----+--------\n    10 |  -4 |      1\n    10 |  -3 |      5\n    12 |  -3 |      9\n    14 |  -4 |     22\n    16 |  -4 |     23\n\nSELECT time_bucket(2, timeval), col1, max(col2)\nFROM continuous_agg_test\nGROUP BY 1, 2\norder by 1,2 ;\n time_bucket | col1 | max \n-------------+------+-----\n          10 |   -4 |  10\n          10 |   -3 |  70\n          12 |   -3 |   9\n          14 |   -4 |  22\n          16 |   -4 |  23\n\nCALL refresh_continuous_aggregate('cagg_2', NULL, NULL);\nselect * from cagg_2 order by 1, 2;\n timed | grp | maxval \n-------+-----+--------\n    10 |  -4 |     10\n    10 |  -3 |     70\n    12 |  -3 |      9\n    14 |  -4 |     22\n    16 |  -4 |     23\n\n--TEST3: invalidations left over by cagg_1 are dropped\n--trigger another invalidation\nINSERT INTO continuous_agg_test VALUES\n    (10, -4, 1000);\nselect count(*) from _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log;\n count \n-------\n     1\n\nselect count(*) from _timescaledb_catalog.continuous_aggs_materialization_invalidation_log;\n count \n-------\n     4\n\nCALL refresh_continuous_aggregate('cagg_1', NULL, NULL);\nselect count(*) from _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log;\n count \n-------\n     0\n\nselect count(*) from _timescaledb_catalog.continuous_aggs_materialization_invalidation_log;\n count \n-------\n     5\n\n--now drop cagg_1, should still have materialization_invalidation_log\nDROP MATERIALIZED VIEW cagg_1;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_2_5_chunk\nselect count(*) from _timescaledb_catalog.continuous_aggs_materialization_invalidation_log;\n count \n-------\n     3\n\n--cagg_2 still exists\nselect view_name from timescaledb_information.continuous_aggregates;\n view_name \n-----------\n cagg_2\n\ndrop table continuous_agg_test cascade;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_3_6_chunk\nselect count(*) from _timescaledb_catalog.continuous_aggs_materialization_invalidation_log;\n count \n-------\n     0\n\nselect view_name from timescaledb_information.continuous_aggregates;\n view_name \n-----------\n\n--TEST4: invalidations that are copied over by cagg1 are not deleted by cagg2 refresh if\n-- they do not meet materialization invalidation threshold for cagg2.\nCREATE TABLE continuous_agg_test(timeval integer, col1 integer, col2 integer);\nselect create_hypertable('continuous_agg_test', 'timeval', chunk_time_interval=> 2);\n        create_hypertable         \n----------------------------------\n (4,public,continuous_agg_test,t)\n\nCREATE OR REPLACE FUNCTION integer_now_test1() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(timeval), 0) FROM continuous_agg_test $$;\nSELECT set_integer_now_func('continuous_agg_test', 'integer_now_test1');\n set_integer_now_func \n----------------------\n \n\nINSERT INTO continuous_agg_test VALUES\n    (10, -4, 1), (11, -3, 5), (12, -3, 7), (13, -3, 9), (14, -4, 11),\n    (15, -4, 22), (16, -4, 23);\nCREATE MATERIALIZED VIEW cagg_1( timed, cnt )\nWITH (timescaledb.continuous,\n      timescaledb.materialized_only = true)\nAS\n    SELECT time_bucket( 2, timeval), COUNT(col1)\n    FROM continuous_agg_test\n    GROUP BY 1 WITH DATA;\nNOTICE:  refreshing continuous aggregate \"cagg_1\"\nCREATE MATERIALIZED VIEW cagg_2( timed, maxval)\nWITH (timescaledb.continuous,\n      timescaledb.materialized_only = true)\nAS\n    SELECT time_bucket(2, timeval), max(col2)\n    FROM continuous_agg_test\n    GROUP BY 1 WITH NO DATA;\n--cagg_1 already refreshed on creation\nselect * from cagg_1 order by 1;\n timed | cnt \n-------+-----\n    10 |   2\n    12 |   2\n    14 |   2\n    16 |   1\n\n--partially refresh cagg_2 to 14 to test inserts that won't be seen in\n--one of the caggs\nCALL refresh_continuous_aggregate('cagg_2', NULL, 14);\nselect * from cagg_2 order by 1;\n timed | maxval \n-------+--------\n    10 |      5\n    12 |      9\n\n--this insert will be processed only by cagg_1 and copied over to cagg_2\ninsert into continuous_agg_test values( 14, -2, 100);\nCALL refresh_continuous_aggregate('cagg_1', NULL, NULL);\nselect * from cagg_1 order by 1;\n timed | cnt \n-------+-----\n    10 |   2\n    12 |   2\n    14 |   3\n    16 |   1\n\nCALL refresh_continuous_aggregate('cagg_2', NULL, 14);\nNOTICE:  continuous aggregate \"cagg_2\" is already up-to-date\nselect * from cagg_2 order by 1;\n timed | maxval \n-------+--------\n    10 |      5\n    12 |      9\n\nSET ROLE :ROLE_SUPERUSER;\nselect * from _timescaledb_catalog.continuous_aggs_invalidation_threshold order by 1;\n hypertable_id | watermark \n---------------+-----------\n             4 |        18\n\nselect * from _timescaledb_catalog.continuous_aggs_materialization_invalidation_log order by 1;\n materialization_id | lowest_modified_value | greatest_modified_value \n--------------------+-----------------------+-------------------------\n                  5 |  -9223372036854775808 |             -2147483649\n                  5 |                    18 |     9223372036854775807\n                  6 |  -9223372036854775808 |             -2147483649\n                  6 |                    14 |     9223372036854775807\n\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--this insert will be processed only by cagg_1 and cagg_2 will process the previous\n--one\ninsert into continuous_agg_test values( 18, -2, 200);\nCALL refresh_continuous_aggregate('cagg_1', NULL, NULL);\nselect * from cagg_1 order by 1;\n timed | cnt \n-------+-----\n    10 |   2\n    12 |   2\n    14 |   3\n    16 |   1\n    18 |   1\n\n-- Now make changes visible up to 16\nCALL refresh_continuous_aggregate('cagg_2', NULL, 16);\nselect * from cagg_2 order by 1;\n timed | maxval \n-------+--------\n    10 |      5\n    12 |      9\n    14 |    100\n\n--TEST5 2 inserts with the same value can be copied over to materialization invalidation log\ninsert into continuous_agg_test values( 18, -2, 100);\ninsert into continuous_agg_test values( 18, -2, 100);\nselect * from _timescaledb_catalog.continuous_aggs_hypertable_invalidation_log order by 1;\n hypertable_id | lowest_modified_value | greatest_modified_value \n---------------+-----------------------+-------------------------\n             4 |                    18 |                      18\n             4 |                    18 |                      18\n\nCALL refresh_continuous_aggregate('cagg_1', NULL, NULL);\nselect * from cagg_1 where timed = 18 ;\n timed | cnt \n-------+-----\n    18 |   3\n\n--copied over for cagg_2 to process later?\nselect * from _timescaledb_catalog.continuous_aggs_materialization_invalidation_log order by 1;\n materialization_id | lowest_modified_value | greatest_modified_value \n--------------------+-----------------------+-------------------------\n                  5 |  -9223372036854775808 |             -2147483649\n                  5 |                    20 |     9223372036854775807\n                  6 |  -9223372036854775808 |             -2147483649\n                  6 |                    16 |     9223372036854775807\n                  6 |                    18 |                      19\n\nDROP MATERIALIZED VIEW cagg_1;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_5_11_chunk\nDROP MATERIALIZED VIEW cagg_2;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_6_12_chunk\n----TEST7 multiple continuous aggregates with real time aggregates test----\ncreate table foo (a integer, b integer, c integer);\nselect table_name FROM create_hypertable('foo', 'a', chunk_time_interval=> 10);\n table_name \n------------\n foo\n\nINSERT into foo values( 1 , 10 , 20);\nINSERT into foo values( 1 , 11 , 20);\nINSERT into foo values( 1 , 12 , 20);\nINSERT into foo values( 1 , 13 , 20);\nINSERT into foo values( 1 , 14 , 20);\nINSERT into foo values( 5 , 14 , 20);\nINSERT into foo values( 5 , 15 , 20);\nINSERT into foo values( 5 , 16 , 20);\nINSERT into foo values( 20 , 16 , 20);\nINSERT into foo values( 20 , 26 , 20);\nINSERT into foo values( 20 , 16 , 20);\nINSERT into foo values( 21 , 15 , 30);\nINSERT into foo values( 21 , 15 , 30);\nINSERT into foo values( 21 , 15 , 30);\nINSERT into foo values( 45 , 14 , 70);\nCREATE OR REPLACE FUNCTION integer_now_foo() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(a), 0) FROM foo $$;\nSELECT set_integer_now_func('foo', 'integer_now_foo');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW mat_m1(a, countb)\nWITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT time_bucket(10, a), count(*)\nFROM foo\nGROUP BY time_bucket(10, a) WITH NO DATA;\nCREATE MATERIALIZED VIEW mat_m2(a, countb)\nWITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT time_bucket(5, a), count(*)\nFROM foo\nGROUP BY time_bucket(5, a) WITH NO DATA;\nselect view_name, materialized_only from timescaledb_information.continuous_aggregates\nWHERE view_name::text like 'mat_m%'\norder by view_name;\n view_name | materialized_only \n-----------+-------------------\n mat_m1    | f\n mat_m2    | f\n\nCALL refresh_continuous_aggregate('mat_m1', NULL, 35);\nCALL refresh_continuous_aggregate('mat_m2', NULL, NULL);\n-- the results from the view should match the direct query\nSELECT * from mat_m1 order by 1;\n a  | countb \n----+--------\n  0 |      8\n 20 |      6\n 40 |      1\n\nSELECT time_bucket(5, a), count(*)\nFROM foo\nGROUP BY time_bucket(5, a)\nORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     5\n           5 |     3\n          20 |     6\n          45 |     1\n\nSELECT * from mat_m2 order by 1;\n a  | countb \n----+--------\n  0 |      5\n  5 |      3\n 20 |      6\n 45 |      1\n\nSELECT time_bucket(10, a), count(*)\nFROM foo\nGROUP BY time_bucket(10, a)\nORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     8\n          20 |     6\n          40 |     1\n\n"
  },
  {
    "path": "tsl/test/expected/cagg_on_cagg.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- Global test variables\n\\set IS_TIME_DIMENSION_WITH_TIMEZONE_1ST FALSE\n\\set IS_TIME_DIMENSION_WITH_TIMEZONE_2TH FALSE\n\\set IS_JOIN FALSE\n\\set INTERVAL_TEST FALSE\n-- ########################################################\n-- ## INTEGER data type tests\n-- ########################################################\n-- Current test variables\n\\set IS_TIME_DIMENSION FALSE\n\\set TIME_DIMENSION_DATATYPE INTEGER\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2_5\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3_10\n--\n-- Run common tests for INTEGER\n--\n\\set BUCKET_WIDTH_1ST 'INTEGER \\'1\\''\n\\set BUCKET_WIDTH_2TH 'INTEGER \\'5\\''\n\\set BUCKET_WIDTH_3TH 'INTEGER \\'10\\''\n-- Different order of time dimension in raw ht\n\\set IS_DEFAULT_COLUMN_ORDER FALSE\n\\ir include/cagg_on_cagg_setup.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- CAGGs on CAGGs tests\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nDROP TABLE IF EXISTS conditions CASCADE;\npsql:include/cagg_on_cagg_setup.sql:8: NOTICE:  table \"conditions\" does not exist, skipping\n\\if :IS_DEFAULT_COLUMN_ORDER\n    CREATE TABLE conditions (\n    time :TIME_DIMENSION_DATATYPE NOT NULL,\n    temperature NUMERIC,\n    device_id INT\n  );\n\\else\n    CREATE TABLE conditions (\n    temperature NUMERIC,\n    time :TIME_DIMENSION_DATATYPE NOT NULL,\n    device_id INT\n  );\n\\endif\n\\if :IS_JOIN\n  DROP TABLE IF EXISTS devices CASCADE;\n  CREATE TABLE devices ( device_id int not null, name text, location text);\n  INSERT INTO devices values (1, 'thermo_1', 'Moscow'), (2, 'thermo_2', 'Berlin'),(3, 'thermo_3', 'London'),(4, 'thermo_4', 'Stockholm');\n\\endif\n\\if :IS_TIME_DIMENSION\n  SELECT table_name FROM create_hypertable('conditions', 'time');\n\\else\n  SELECT table_name FROM create_hypertable('conditions', 'time', chunk_time_interval => 10);\n table_name \n------------\n conditions\n\n\\endif\n\\if :IS_TIME_DIMENSION\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 1);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 01:00:00-00',  5, 2);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-02 01:00:00-00', 20, 3);\n\\else\n  CREATE OR REPLACE FUNCTION integer_now()\n  RETURNS :TIME_DIMENSION_DATATYPE LANGUAGE SQL STABLE AS\n  $$\n    SELECT coalesce(max(time), 0)\n    FROM conditions\n  $$;\n  SELECT set_integer_now_func('conditions', 'integer_now');\n set_integer_now_func \n----------------------\n \n\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (1, 10, 1);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (2,  5, 2);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (5, 20, 3);\n\\endif\n\\ir include/cagg_on_cagg_common.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- CAGG on hypertable (1st level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  SUM(temperature) AS temperature,\n  device_id\nFROM conditions\nGROUP BY 1, 3\nORDER BY 1, 2, 3\nWITH NO DATA;\n-- CAGG on CAGG (2th level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  SUM(temperature) AS temperature\n\\if :IS_JOIN\n  , devices.device_id\n  , devices.name\n  FROM :CAGG_NAME_1ST_LEVEL\n  JOIN devices ON devices.device_id = :CAGG_NAME_1ST_LEVEL.device_id\n  GROUP BY 1, 3, 4\n  ORDER BY 1, 2, 3, 4\n\\else\n  FROM :CAGG_NAME_1ST_LEVEL\n  GROUP BY 1\n  ORDER BY 1, 2\n\\endif\nWITH NO DATA;\n-- CAGG on CAGG (3th level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket,\n  SUM(temperature) AS temperature\n\\if :IS_JOIN\n  , :CAGG_NAME_2TH_LEVEL.device_id\n  , :CAGG_NAME_2TH_LEVEL.name\n  , devices.location\n  FROM :CAGG_NAME_2TH_LEVEL\n  JOIN devices ON devices.device_id = :CAGG_NAME_2TH_LEVEL.device_id\n  GROUP BY 1, 3, 4, 5\n  ORDER BY 1, 2, 3, 4, 5\n\\else\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  ORDER BY 1, 2\n\\endif\nWITH NO DATA;\n-- Check chunk_interval\n\\if :IS_TIME_DIMENSION\n  SELECT h.table_name AS name, _timescaledb_functions.to_interval(d.interval_length) AS chunk_interval\n  FROM _timescaledb_catalog.hypertable h\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = h.id\n  WHERE h.table_name = 'conditions'\n  UNION ALL\n  SELECT c.user_view_name AS name, _timescaledb_functions.to_interval(d.interval_length) AS chunk_interval\n  FROM _timescaledb_catalog.continuous_agg c\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = c.mat_hypertable_id\n  WHERE c.user_view_name IN (:'CAGG_NAME_1ST_LEVEL', :'CAGG_NAME_2TH_LEVEL', :'CAGG_NAME_3TH_LEVEL')\n  ORDER BY 1, 2;\n\\else\n  SELECT h.table_name AS name, d.interval_length AS chunk_interval\n  FROM _timescaledb_catalog.hypertable h\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = h.id\n  WHERE h.table_name = 'conditions'\n  UNION ALL\n  SELECT c.user_view_name AS name, d.interval_length AS chunk_interval\n  FROM _timescaledb_catalog.continuous_agg c\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = c.mat_hypertable_id\n  WHERE c.user_view_name IN (:'CAGG_NAME_1ST_LEVEL', :'CAGG_NAME_2TH_LEVEL', :'CAGG_NAME_3TH_LEVEL')\n  ORDER BY 1, 2;\n          name           | chunk_interval \n-------------------------+----------------\n conditions              |             10\n conditions_summary_1_1  |            100\n conditions_summary_2_5  |            100\n conditions_summary_3_10 |            100\n\n\\endif\n-- No data because the CAGGs are just for materialized data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature \n--------+-------------\n\n-- Turn CAGGs into Realtime\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=false);\n-- Realtime data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           5 |         2\n      5 |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n      0 |          15\n      5 |          20\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature \n--------+-------------\n      0 |          35\n\n-- Turn CAGGs into materialized only again\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=true);\n-- Refresh all data\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Materialized data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           5 |         2\n      5 |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n      0 |          15\n      5 |          20\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature \n--------+-------------\n      0 |          35\n\n\\if :IS_TIME_DIMENSION\n-- Invalidate an old region\nINSERT INTO conditions (\"time\", temperature) VALUES ('2022-01-01 01:00:00-00'::timestamptz, 2);\n-- New region\nINSERT INTO conditions (\"time\", temperature) VALUES ('2022-01-03 01:00:00-00'::timestamptz, 2);\n\\else\n-- Invalidate an old region\nINSERT INTO conditions (\"time\", temperature) VALUES (2,  2);\n-- New region\nINSERT INTO conditions (\"time\", temperature) VALUES (10, 2);\n\\endif\n-- No changes\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           5 |         2\n      5 |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n      0 |          15\n      5 |          20\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature \n--------+-------------\n      0 |          35\n\n-- Turn CAGGs into Realtime\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=false);\n-- Realtime changes, just new region\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           5 |         2\n      5 |          20 |         3\n     10 |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n      0 |          15\n      5 |          20\n     10 |           2\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature \n--------+-------------\n      0 |          35\n     10 |           2\n\n-- Turn CAGGs into materialized only again\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=true);\n-- Refresh all data\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- All changes are materialized\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           2 |          \n      2 |           5 |         2\n      5 |          20 |         3\n     10 |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n      0 |          17\n      5 |          20\n     10 |           2\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature \n--------+-------------\n      0 |          37\n     10 |           2\n\n-- TRUNCATE tests\nTRUNCATE :CAGG_NAME_2TH_LEVEL;\n-- This full refresh will remove all the data from the 3TH level cagg\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Should return no rows\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature \n--------+-------------\n\n-- If we have all the data in the bottom levels caggs we can rebuild\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Now we have all the data\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n      0 |          17\n      5 |          20\n     10 |           2\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature \n--------+-------------\n      0 |          37\n     10 |           2\n\n-- DROP tests\n\\set ON_ERROR_STOP 0\n-- should error because it depends of other CAGGs\nDROP MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:172: ERROR:  cannot drop view conditions_summary_1_1 because other objects depend on it\nDROP MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:173: ERROR:  cannot drop view conditions_summary_2_5 because other objects depend on it\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\npsql:include/cagg_on_cagg_common.sql:174: NOTICE:  continuous aggregate \"conditions_summary_1_1\" is already up-to-date\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\npsql:include/cagg_on_cagg_common.sql:175: NOTICE:  continuous aggregate \"conditions_summary_2_5\" is already up-to-date\n\\set ON_ERROR_STOP 1\n-- DROP the 3TH level CAGG don't affect others\nDROP MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:179: NOTICE:  drop cascades to table _timescaledb_internal._hyper_4_4_chunk\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:182: ERROR:  relation \"conditions_summary_3_10\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- should work because dropping the top level CAGG\n-- don't affect the down level CAGGs\nTRUNCATE :CAGG_NAME_2TH_LEVEL,:CAGG_NAME_1ST_LEVEL;\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           2 |          \n      2 |           5 |         2\n      5 |          20 |         3\n     10 |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n\n-- DROP the 2TH level CAGG don't affect others\nDROP MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL;\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:196: ERROR:  relation \"conditions_summary_2_5\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- should work because dropping the top level CAGG\n-- don't affect the down level CAGGs\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           2 |          \n      2 |           5 |         2\n      5 |          20 |         3\n     10 |           2 |          \n\n-- DROP the first CAGG should work\nDROP MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:203: NOTICE:  drop cascades to table _timescaledb_internal._hyper_2_7_chunk\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:206: ERROR:  relation \"conditions_summary_1_1\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- Default tests\n\\set IS_DEFAULT_COLUMN_ORDER TRUE\n\\ir include/cagg_on_cagg_setup.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- CAGGs on CAGGs tests\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nDROP TABLE IF EXISTS conditions CASCADE;\n\\if :IS_DEFAULT_COLUMN_ORDER\n    CREATE TABLE conditions (\n    time :TIME_DIMENSION_DATATYPE NOT NULL,\n    temperature NUMERIC,\n    device_id INT\n  );\n\\else\n    CREATE TABLE conditions (\n    temperature NUMERIC,\n    time :TIME_DIMENSION_DATATYPE NOT NULL,\n    device_id INT\n  );\n\\endif\n\\if :IS_JOIN\n  DROP TABLE IF EXISTS devices CASCADE;\n  CREATE TABLE devices ( device_id int not null, name text, location text);\n  INSERT INTO devices values (1, 'thermo_1', 'Moscow'), (2, 'thermo_2', 'Berlin'),(3, 'thermo_3', 'London'),(4, 'thermo_4', 'Stockholm');\n\\endif\n\\if :IS_TIME_DIMENSION\n  SELECT table_name FROM create_hypertable('conditions', 'time');\n\\else\n  SELECT table_name FROM create_hypertable('conditions', 'time', chunk_time_interval => 10);\n table_name \n------------\n conditions\n\n\\endif\n\\if :IS_TIME_DIMENSION\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 1);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 01:00:00-00',  5, 2);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-02 01:00:00-00', 20, 3);\n\\else\n  CREATE OR REPLACE FUNCTION integer_now()\n  RETURNS :TIME_DIMENSION_DATATYPE LANGUAGE SQL STABLE AS\n  $$\n    SELECT coalesce(max(time), 0)\n    FROM conditions\n  $$;\n  SELECT set_integer_now_func('conditions', 'integer_now');\n set_integer_now_func \n----------------------\n \n\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (1, 10, 1);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (2,  5, 2);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (5, 20, 3);\n\\endif\n\\ir include/cagg_on_cagg_common.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- CAGG on hypertable (1st level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  SUM(temperature) AS temperature,\n  device_id\nFROM conditions\nGROUP BY 1, 3\nORDER BY 1, 2, 3\nWITH NO DATA;\n-- CAGG on CAGG (2th level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  SUM(temperature) AS temperature\n\\if :IS_JOIN\n  , devices.device_id\n  , devices.name\n  FROM :CAGG_NAME_1ST_LEVEL\n  JOIN devices ON devices.device_id = :CAGG_NAME_1ST_LEVEL.device_id\n  GROUP BY 1, 3, 4\n  ORDER BY 1, 2, 3, 4\n\\else\n  FROM :CAGG_NAME_1ST_LEVEL\n  GROUP BY 1\n  ORDER BY 1, 2\n\\endif\nWITH NO DATA;\n-- CAGG on CAGG (3th level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket,\n  SUM(temperature) AS temperature\n\\if :IS_JOIN\n  , :CAGG_NAME_2TH_LEVEL.device_id\n  , :CAGG_NAME_2TH_LEVEL.name\n  , devices.location\n  FROM :CAGG_NAME_2TH_LEVEL\n  JOIN devices ON devices.device_id = :CAGG_NAME_2TH_LEVEL.device_id\n  GROUP BY 1, 3, 4, 5\n  ORDER BY 1, 2, 3, 4, 5\n\\else\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  ORDER BY 1, 2\n\\endif\nWITH NO DATA;\n-- Check chunk_interval\n\\if :IS_TIME_DIMENSION\n  SELECT h.table_name AS name, _timescaledb_functions.to_interval(d.interval_length) AS chunk_interval\n  FROM _timescaledb_catalog.hypertable h\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = h.id\n  WHERE h.table_name = 'conditions'\n  UNION ALL\n  SELECT c.user_view_name AS name, _timescaledb_functions.to_interval(d.interval_length) AS chunk_interval\n  FROM _timescaledb_catalog.continuous_agg c\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = c.mat_hypertable_id\n  WHERE c.user_view_name IN (:'CAGG_NAME_1ST_LEVEL', :'CAGG_NAME_2TH_LEVEL', :'CAGG_NAME_3TH_LEVEL')\n  ORDER BY 1, 2;\n\\else\n  SELECT h.table_name AS name, d.interval_length AS chunk_interval\n  FROM _timescaledb_catalog.hypertable h\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = h.id\n  WHERE h.table_name = 'conditions'\n  UNION ALL\n  SELECT c.user_view_name AS name, d.interval_length AS chunk_interval\n  FROM _timescaledb_catalog.continuous_agg c\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = c.mat_hypertable_id\n  WHERE c.user_view_name IN (:'CAGG_NAME_1ST_LEVEL', :'CAGG_NAME_2TH_LEVEL', :'CAGG_NAME_3TH_LEVEL')\n  ORDER BY 1, 2;\n          name           | chunk_interval \n-------------------------+----------------\n conditions              |             10\n conditions_summary_1_1  |            100\n conditions_summary_2_5  |            100\n conditions_summary_3_10 |            100\n\n\\endif\n-- No data because the CAGGs are just for materialized data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature \n--------+-------------\n\n-- Turn CAGGs into Realtime\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=false);\n-- Realtime data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           5 |         2\n      5 |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n      0 |          15\n      5 |          20\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature \n--------+-------------\n      0 |          35\n\n-- Turn CAGGs into materialized only again\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=true);\n-- Refresh all data\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Materialized data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           5 |         2\n      5 |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n      0 |          15\n      5 |          20\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature \n--------+-------------\n      0 |          35\n\n\\if :IS_TIME_DIMENSION\n-- Invalidate an old region\nINSERT INTO conditions (\"time\", temperature) VALUES ('2022-01-01 01:00:00-00'::timestamptz, 2);\n-- New region\nINSERT INTO conditions (\"time\", temperature) VALUES ('2022-01-03 01:00:00-00'::timestamptz, 2);\n\\else\n-- Invalidate an old region\nINSERT INTO conditions (\"time\", temperature) VALUES (2,  2);\n-- New region\nINSERT INTO conditions (\"time\", temperature) VALUES (10, 2);\n\\endif\n-- No changes\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           5 |         2\n      5 |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n      0 |          15\n      5 |          20\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature \n--------+-------------\n      0 |          35\n\n-- Turn CAGGs into Realtime\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=false);\n-- Realtime changes, just new region\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           5 |         2\n      5 |          20 |         3\n     10 |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n      0 |          15\n      5 |          20\n     10 |           2\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature \n--------+-------------\n      0 |          35\n     10 |           2\n\n-- Turn CAGGs into materialized only again\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=true);\n-- Refresh all data\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- All changes are materialized\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           2 |          \n      2 |           5 |         2\n      5 |          20 |         3\n     10 |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n      0 |          17\n      5 |          20\n     10 |           2\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature \n--------+-------------\n      0 |          37\n     10 |           2\n\n-- TRUNCATE tests\nTRUNCATE :CAGG_NAME_2TH_LEVEL;\n-- This full refresh will remove all the data from the 3TH level cagg\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Should return no rows\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature \n--------+-------------\n\n-- If we have all the data in the bottom levels caggs we can rebuild\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Now we have all the data\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n      0 |          17\n      5 |          20\n     10 |           2\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature \n--------+-------------\n      0 |          37\n     10 |           2\n\n-- DROP tests\n\\set ON_ERROR_STOP 0\n-- should error because it depends of other CAGGs\nDROP MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:172: ERROR:  cannot drop view conditions_summary_1_1 because other objects depend on it\nDROP MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:173: ERROR:  cannot drop view conditions_summary_2_5 because other objects depend on it\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\npsql:include/cagg_on_cagg_common.sql:174: NOTICE:  continuous aggregate \"conditions_summary_1_1\" is already up-to-date\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\npsql:include/cagg_on_cagg_common.sql:175: NOTICE:  continuous aggregate \"conditions_summary_2_5\" is already up-to-date\n\\set ON_ERROR_STOP 1\n-- DROP the 3TH level CAGG don't affect others\nDROP MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:179: NOTICE:  drop cascades to table _timescaledb_internal._hyper_8_11_chunk\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:182: ERROR:  relation \"conditions_summary_3_10\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- should work because dropping the top level CAGG\n-- don't affect the down level CAGGs\nTRUNCATE :CAGG_NAME_2TH_LEVEL,:CAGG_NAME_1ST_LEVEL;\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           2 |          \n      2 |           5 |         2\n      5 |          20 |         3\n     10 |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n\n-- DROP the 2TH level CAGG don't affect others\nDROP MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL;\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:196: ERROR:  relation \"conditions_summary_2_5\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- should work because dropping the top level CAGG\n-- don't affect the down level CAGGs\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           2 |          \n      2 |           5 |         2\n      5 |          20 |         3\n     10 |           2 |          \n\n-- DROP the first CAGG should work\nDROP MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:203: NOTICE:  drop cascades to table _timescaledb_internal._hyper_6_14_chunk\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:206: ERROR:  relation \"conditions_summary_1_1\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n--\n-- Validation test for non-multiple bucket sizes\n--\n\\set BUCKET_WIDTH_1ST 'INTEGER \\'2\\''\n\\set BUCKET_WIDTH_2TH 'INTEGER \\'5\\''\n\\set WARNING_MESSAGE '-- SHOULD ERROR because non-multiple bucket sizes'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                       View \"public.conditions_summary_1\"\n   Column    |  Type   | Collation | Nullable | Default | Storage | Description \n-------------+---------+-----------+----------+---------+---------+-------------\n bucket      | integer |           |          |         | plain   | \n temperature | numeric |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_9.bucket,\n    _materialized_hypertable_9.temperature\n   FROM _timescaledb_internal._materialized_hypertable_9\n  WHERE _materialized_hypertable_9.bucket < COALESCE(_timescaledb_functions.cagg_watermark(9)::integer, '-2147483648'::integer)\nUNION ALL\n SELECT time_bucket(2, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.cagg_watermark(9)::integer, '-2147483648'::integer)\n  GROUP BY (time_bucket(2, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD ERROR because non-multiple bucket sizes\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\npsql:include/cagg_on_cagg_validations.sql:44: ERROR:  cannot create continuous aggregate with incompatible bucket width\nDETAIL:  Time bucket width of \"public.conditions_summary_2\" [5] should be multiple of the time bucket width of \"public.conditions_summary_1\" [2].\n\\d+ :CAGG_NAME_2TH_LEVEL\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:86: NOTICE:  materialized view \"conditions_summary_2\" does not exist, skipping\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n--\n-- Validation test for equal bucket sizes\n--\n\\set BUCKET_WIDTH_1ST 'INTEGER \\'2\\''\n\\set BUCKET_WIDTH_2TH 'INTEGER \\'2\\''\n\\set WARNING_MESSAGE 'SHOULD WORK because new bucket should be greater than previous'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                       View \"public.conditions_summary_1\"\n   Column    |  Type   | Collation | Nullable | Default | Storage | Description \n-------------+---------+-----------+----------+---------+---------+-------------\n bucket      | integer |           |          |         | plain   | \n temperature | numeric |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_10.bucket,\n    _materialized_hypertable_10.temperature\n   FROM _timescaledb_internal._materialized_hypertable_10\n  WHERE _materialized_hypertable_10.bucket < COALESCE(_timescaledb_functions.cagg_watermark(10)::integer, '-2147483648'::integer)\nUNION ALL\n SELECT time_bucket(2, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.cagg_watermark(10)::integer, '-2147483648'::integer)\n  GROUP BY (time_bucket(2, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\nSHOULD WORK because new bucket should be greater than previous\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                       View \"public.conditions_summary_2\"\n   Column    |  Type   | Collation | Nullable | Default | Storage | Description \n-------------+---------+-----------+----------+---------+---------+-------------\n bucket      | integer |           |          |         | plain   | \n temperature | numeric |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_11.bucket,\n    _materialized_hypertable_11.temperature\n   FROM _timescaledb_internal._materialized_hypertable_11\n  WHERE _materialized_hypertable_11.bucket < COALESCE(_timescaledb_functions.cagg_watermark(11)::integer, '-2147483648'::integer)\nUNION ALL\n SELECT time_bucket(2, conditions_summary_1.bucket) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.cagg_watermark(11)::integer, '-2147483648'::integer)\n  GROUP BY (time_bucket(2, conditions_summary_1.bucket));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n--\n-- Validation test for bucket size less than source\n--\n\\set BUCKET_WIDTH_1ST 'INTEGER \\'4\\''\n\\set BUCKET_WIDTH_2TH 'INTEGER \\'2\\''\n\\set WARNING_MESSAGE '-- SHOULD ERROR because new bucket should be greater than previous'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                       View \"public.conditions_summary_1\"\n   Column    |  Type   | Collation | Nullable | Default | Storage | Description \n-------------+---------+-----------+----------+---------+---------+-------------\n bucket      | integer |           |          |         | plain   | \n temperature | numeric |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_12.bucket,\n    _materialized_hypertable_12.temperature\n   FROM _timescaledb_internal._materialized_hypertable_12\n  WHERE _materialized_hypertable_12.bucket < COALESCE(_timescaledb_functions.cagg_watermark(12)::integer, '-2147483648'::integer)\nUNION ALL\n SELECT time_bucket(4, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.cagg_watermark(12)::integer, '-2147483648'::integer)\n  GROUP BY (time_bucket(4, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD ERROR because new bucket should be greater than previous\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\npsql:include/cagg_on_cagg_validations.sql:44: ERROR:  cannot create continuous aggregate with incompatible bucket width\nDETAIL:  Time bucket width of \"public.conditions_summary_2\" [2] should be greater or equal than the time bucket width of \"public.conditions_summary_1\" [4].\n\\d+ :CAGG_NAME_2TH_LEVEL\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:86: NOTICE:  materialized view \"conditions_summary_2\" does not exist, skipping\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n-- ########################################################\n-- ## TIMESTAMP data type tests\n-- ########################################################\n-- Current test variables\n\\set IS_TIME_DIMENSION TRUE\n\\set TIME_DIMENSION_DATATYPE TIMESTAMP\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1_hourly\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2_daily\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3_weekly\nSET timezone TO 'UTC';\n--\n-- Run common tests for TIMESTAMP\n--\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 hour\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 day\\''\n\\set BUCKET_WIDTH_3TH 'INTERVAL \\'1 week\\''\n-- Different order of time dimension in raw ht\n\\set IS_DEFAULT_COLUMN_ORDER FALSE\n\\ir include/cagg_on_cagg_setup.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- CAGGs on CAGGs tests\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nDROP TABLE IF EXISTS conditions CASCADE;\n\\if :IS_DEFAULT_COLUMN_ORDER\n    CREATE TABLE conditions (\n    time :TIME_DIMENSION_DATATYPE NOT NULL,\n    temperature NUMERIC,\n    device_id INT\n  );\n\\else\n    CREATE TABLE conditions (\n    temperature NUMERIC,\n    time :TIME_DIMENSION_DATATYPE NOT NULL,\n    device_id INT\n  );\n\\endif\n\\if :IS_JOIN\n  DROP TABLE IF EXISTS devices CASCADE;\n  CREATE TABLE devices ( device_id int not null, name text, location text);\n  INSERT INTO devices values (1, 'thermo_1', 'Moscow'), (2, 'thermo_2', 'Berlin'),(3, 'thermo_3', 'London'),(4, 'thermo_4', 'Stockholm');\n\\endif\n\\if :IS_TIME_DIMENSION\n  SELECT table_name FROM create_hypertable('conditions', 'time');\npsql:include/cagg_on_cagg_setup.sql:30: WARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n table_name \n------------\n conditions\n\n\\else\n  SELECT table_name FROM create_hypertable('conditions', 'time', chunk_time_interval => 10);\n\\endif\n\\if :IS_TIME_DIMENSION\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 1);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 01:00:00-00',  5, 2);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-02 01:00:00-00', 20, 3);\n\\else\n  CREATE OR REPLACE FUNCTION integer_now()\n  RETURNS :TIME_DIMENSION_DATATYPE LANGUAGE SQL STABLE AS\n  $$\n    SELECT coalesce(max(time), 0)\n    FROM conditions\n  $$;\n  SELECT set_integer_now_func('conditions', 'integer_now');\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (1, 10, 1);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (2,  5, 2);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (5, 20, 3);\n\\endif\n\\ir include/cagg_on_cagg_common.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- CAGG on hypertable (1st level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  SUM(temperature) AS temperature,\n  device_id\nFROM conditions\nGROUP BY 1, 3\nORDER BY 1, 2, 3\nWITH NO DATA;\n-- CAGG on CAGG (2th level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  SUM(temperature) AS temperature\n\\if :IS_JOIN\n  , devices.device_id\n  , devices.name\n  FROM :CAGG_NAME_1ST_LEVEL\n  JOIN devices ON devices.device_id = :CAGG_NAME_1ST_LEVEL.device_id\n  GROUP BY 1, 3, 4\n  ORDER BY 1, 2, 3, 4\n\\else\n  FROM :CAGG_NAME_1ST_LEVEL\n  GROUP BY 1\n  ORDER BY 1, 2\n\\endif\nWITH NO DATA;\n-- CAGG on CAGG (3th level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket,\n  SUM(temperature) AS temperature\n\\if :IS_JOIN\n  , :CAGG_NAME_2TH_LEVEL.device_id\n  , :CAGG_NAME_2TH_LEVEL.name\n  , devices.location\n  FROM :CAGG_NAME_2TH_LEVEL\n  JOIN devices ON devices.device_id = :CAGG_NAME_2TH_LEVEL.device_id\n  GROUP BY 1, 3, 4, 5\n  ORDER BY 1, 2, 3, 4, 5\n\\else\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  ORDER BY 1, 2\n\\endif\nWITH NO DATA;\n-- Check chunk_interval\n\\if :IS_TIME_DIMENSION\n  SELECT h.table_name AS name, _timescaledb_functions.to_interval(d.interval_length) AS chunk_interval\n  FROM _timescaledb_catalog.hypertable h\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = h.id\n  WHERE h.table_name = 'conditions'\n  UNION ALL\n  SELECT c.user_view_name AS name, _timescaledb_functions.to_interval(d.interval_length) AS chunk_interval\n  FROM _timescaledb_catalog.continuous_agg c\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = c.mat_hypertable_id\n  WHERE c.user_view_name IN (:'CAGG_NAME_1ST_LEVEL', :'CAGG_NAME_2TH_LEVEL', :'CAGG_NAME_3TH_LEVEL')\n  ORDER BY 1, 2;\n            name             | chunk_interval \n-----------------------------+----------------\n conditions                  | @ 7 days\n conditions_summary_1_hourly | @ 70 days\n conditions_summary_2_daily  | @ 70 days\n conditions_summary_3_weekly | @ 70 days\n\n\\else\n  SELECT h.table_name AS name, d.interval_length AS chunk_interval\n  FROM _timescaledb_catalog.hypertable h\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = h.id\n  WHERE h.table_name = 'conditions'\n  UNION ALL\n  SELECT c.user_view_name AS name, d.interval_length AS chunk_interval\n  FROM _timescaledb_catalog.continuous_agg c\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = c.mat_hypertable_id\n  WHERE c.user_view_name IN (:'CAGG_NAME_1ST_LEVEL', :'CAGG_NAME_2TH_LEVEL', :'CAGG_NAME_3TH_LEVEL')\n  ORDER BY 1, 2;\n\\endif\n-- No data because the CAGGs are just for materialized data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature \n--------+-------------\n\n-- Turn CAGGs into Realtime\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=false);\n-- Realtime data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n          bucket          | temperature \n--------------------------+-------------\n Sat Jan 01 00:00:00 2022 |          15\n Sun Jan 02 00:00:00 2022 |          20\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n          bucket          | temperature \n--------------------------+-------------\n Mon Dec 27 00:00:00 2021 |          35\n\n-- Turn CAGGs into materialized only again\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=true);\n-- Refresh all data\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Materialized data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n          bucket          | temperature \n--------------------------+-------------\n Sat Jan 01 00:00:00 2022 |          15\n Sun Jan 02 00:00:00 2022 |          20\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n          bucket          | temperature \n--------------------------+-------------\n Mon Dec 27 00:00:00 2021 |          35\n\n\\if :IS_TIME_DIMENSION\n-- Invalidate an old region\nINSERT INTO conditions (\"time\", temperature) VALUES ('2022-01-01 01:00:00-00'::timestamptz, 2);\n-- New region\nINSERT INTO conditions (\"time\", temperature) VALUES ('2022-01-03 01:00:00-00'::timestamptz, 2);\n\\else\n-- Invalidate an old region\nINSERT INTO conditions (\"time\", temperature) VALUES (2,  2);\n-- New region\nINSERT INTO conditions (\"time\", temperature) VALUES (10, 2);\n\\endif\n-- No changes\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n          bucket          | temperature \n--------------------------+-------------\n Sat Jan 01 00:00:00 2022 |          15\n Sun Jan 02 00:00:00 2022 |          20\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n          bucket          | temperature \n--------------------------+-------------\n Mon Dec 27 00:00:00 2021 |          35\n\n-- Turn CAGGs into Realtime\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=false);\n-- Realtime changes, just new region\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n Mon Jan 03 01:00:00 2022 |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n          bucket          | temperature \n--------------------------+-------------\n Sat Jan 01 00:00:00 2022 |          15\n Sun Jan 02 00:00:00 2022 |          20\n Mon Jan 03 00:00:00 2022 |           2\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n          bucket          | temperature \n--------------------------+-------------\n Mon Dec 27 00:00:00 2021 |          35\n Mon Jan 03 00:00:00 2022 |           2\n\n-- Turn CAGGs into materialized only again\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=true);\n-- Refresh all data\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- All changes are materialized\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           2 |          \n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n Mon Jan 03 01:00:00 2022 |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n          bucket          | temperature \n--------------------------+-------------\n Sat Jan 01 00:00:00 2022 |          17\n Sun Jan 02 00:00:00 2022 |          20\n Mon Jan 03 00:00:00 2022 |           2\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n          bucket          | temperature \n--------------------------+-------------\n Mon Dec 27 00:00:00 2021 |          37\n Mon Jan 03 00:00:00 2022 |           2\n\n-- TRUNCATE tests\nTRUNCATE :CAGG_NAME_2TH_LEVEL;\n-- This full refresh will remove all the data from the 3TH level cagg\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Should return no rows\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature \n--------+-------------\n\n-- If we have all the data in the bottom levels caggs we can rebuild\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Now we have all the data\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n          bucket          | temperature \n--------------------------+-------------\n Sat Jan 01 00:00:00 2022 |          17\n Sun Jan 02 00:00:00 2022 |          20\n Mon Jan 03 00:00:00 2022 |           2\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n          bucket          | temperature \n--------------------------+-------------\n Mon Dec 27 00:00:00 2021 |          37\n Mon Jan 03 00:00:00 2022 |           2\n\n-- DROP tests\n\\set ON_ERROR_STOP 0\n-- should error because it depends of other CAGGs\nDROP MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:172: ERROR:  cannot drop view conditions_summary_1_hourly because other objects depend on it\nDROP MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:173: ERROR:  cannot drop view conditions_summary_2_daily because other objects depend on it\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\npsql:include/cagg_on_cagg_common.sql:174: NOTICE:  continuous aggregate \"conditions_summary_1_hourly\" is already up-to-date\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\npsql:include/cagg_on_cagg_common.sql:175: NOTICE:  continuous aggregate \"conditions_summary_2_daily\" is already up-to-date\n\\set ON_ERROR_STOP 1\n-- DROP the 3TH level CAGG don't affect others\nDROP MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:179: NOTICE:  drop cascades to table _timescaledb_internal._hyper_16_18_chunk\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:182: ERROR:  relation \"conditions_summary_3_weekly\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- should work because dropping the top level CAGG\n-- don't affect the down level CAGGs\nTRUNCATE :CAGG_NAME_2TH_LEVEL,:CAGG_NAME_1ST_LEVEL;\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           2 |          \n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n Mon Jan 03 01:00:00 2022 |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n\n-- DROP the 2TH level CAGG don't affect others\nDROP MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL;\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:196: ERROR:  relation \"conditions_summary_2_daily\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- should work because dropping the top level CAGG\n-- don't affect the down level CAGGs\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           2 |          \n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n Mon Jan 03 01:00:00 2022 |           2 |          \n\n-- DROP the first CAGG should work\nDROP MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:203: NOTICE:  drop cascades to table _timescaledb_internal._hyper_14_20_chunk\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:206: ERROR:  relation \"conditions_summary_1_hourly\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- Default tests\n\\set IS_DEFAULT_COLUMN_ORDER TRUE\n\\ir include/cagg_on_cagg_setup.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- CAGGs on CAGGs tests\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nDROP TABLE IF EXISTS conditions CASCADE;\n\\if :IS_DEFAULT_COLUMN_ORDER\n    CREATE TABLE conditions (\n    time :TIME_DIMENSION_DATATYPE NOT NULL,\n    temperature NUMERIC,\n    device_id INT\n  );\n\\else\n    CREATE TABLE conditions (\n    temperature NUMERIC,\n    time :TIME_DIMENSION_DATATYPE NOT NULL,\n    device_id INT\n  );\n\\endif\n\\if :IS_JOIN\n  DROP TABLE IF EXISTS devices CASCADE;\n  CREATE TABLE devices ( device_id int not null, name text, location text);\n  INSERT INTO devices values (1, 'thermo_1', 'Moscow'), (2, 'thermo_2', 'Berlin'),(3, 'thermo_3', 'London'),(4, 'thermo_4', 'Stockholm');\n\\endif\n\\if :IS_TIME_DIMENSION\n  SELECT table_name FROM create_hypertable('conditions', 'time');\npsql:include/cagg_on_cagg_setup.sql:30: WARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n table_name \n------------\n conditions\n\n\\else\n  SELECT table_name FROM create_hypertable('conditions', 'time', chunk_time_interval => 10);\n\\endif\n\\if :IS_TIME_DIMENSION\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 1);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 01:00:00-00',  5, 2);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-02 01:00:00-00', 20, 3);\n\\else\n  CREATE OR REPLACE FUNCTION integer_now()\n  RETURNS :TIME_DIMENSION_DATATYPE LANGUAGE SQL STABLE AS\n  $$\n    SELECT coalesce(max(time), 0)\n    FROM conditions\n  $$;\n  SELECT set_integer_now_func('conditions', 'integer_now');\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (1, 10, 1);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (2,  5, 2);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (5, 20, 3);\n\\endif\n\\ir include/cagg_on_cagg_common.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- CAGG on hypertable (1st level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  SUM(temperature) AS temperature,\n  device_id\nFROM conditions\nGROUP BY 1, 3\nORDER BY 1, 2, 3\nWITH NO DATA;\n-- CAGG on CAGG (2th level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  SUM(temperature) AS temperature\n\\if :IS_JOIN\n  , devices.device_id\n  , devices.name\n  FROM :CAGG_NAME_1ST_LEVEL\n  JOIN devices ON devices.device_id = :CAGG_NAME_1ST_LEVEL.device_id\n  GROUP BY 1, 3, 4\n  ORDER BY 1, 2, 3, 4\n\\else\n  FROM :CAGG_NAME_1ST_LEVEL\n  GROUP BY 1\n  ORDER BY 1, 2\n\\endif\nWITH NO DATA;\n-- CAGG on CAGG (3th level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket,\n  SUM(temperature) AS temperature\n\\if :IS_JOIN\n  , :CAGG_NAME_2TH_LEVEL.device_id\n  , :CAGG_NAME_2TH_LEVEL.name\n  , devices.location\n  FROM :CAGG_NAME_2TH_LEVEL\n  JOIN devices ON devices.device_id = :CAGG_NAME_2TH_LEVEL.device_id\n  GROUP BY 1, 3, 4, 5\n  ORDER BY 1, 2, 3, 4, 5\n\\else\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  ORDER BY 1, 2\n\\endif\nWITH NO DATA;\n-- Check chunk_interval\n\\if :IS_TIME_DIMENSION\n  SELECT h.table_name AS name, _timescaledb_functions.to_interval(d.interval_length) AS chunk_interval\n  FROM _timescaledb_catalog.hypertable h\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = h.id\n  WHERE h.table_name = 'conditions'\n  UNION ALL\n  SELECT c.user_view_name AS name, _timescaledb_functions.to_interval(d.interval_length) AS chunk_interval\n  FROM _timescaledb_catalog.continuous_agg c\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = c.mat_hypertable_id\n  WHERE c.user_view_name IN (:'CAGG_NAME_1ST_LEVEL', :'CAGG_NAME_2TH_LEVEL', :'CAGG_NAME_3TH_LEVEL')\n  ORDER BY 1, 2;\n            name             | chunk_interval \n-----------------------------+----------------\n conditions                  | @ 7 days\n conditions_summary_1_hourly | @ 70 days\n conditions_summary_2_daily  | @ 70 days\n conditions_summary_3_weekly | @ 70 days\n\n\\else\n  SELECT h.table_name AS name, d.interval_length AS chunk_interval\n  FROM _timescaledb_catalog.hypertable h\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = h.id\n  WHERE h.table_name = 'conditions'\n  UNION ALL\n  SELECT c.user_view_name AS name, d.interval_length AS chunk_interval\n  FROM _timescaledb_catalog.continuous_agg c\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = c.mat_hypertable_id\n  WHERE c.user_view_name IN (:'CAGG_NAME_1ST_LEVEL', :'CAGG_NAME_2TH_LEVEL', :'CAGG_NAME_3TH_LEVEL')\n  ORDER BY 1, 2;\n\\endif\n-- No data because the CAGGs are just for materialized data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature \n--------+-------------\n\n-- Turn CAGGs into Realtime\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=false);\n-- Realtime data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n          bucket          | temperature \n--------------------------+-------------\n Sat Jan 01 00:00:00 2022 |          15\n Sun Jan 02 00:00:00 2022 |          20\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n          bucket          | temperature \n--------------------------+-------------\n Mon Dec 27 00:00:00 2021 |          35\n\n-- Turn CAGGs into materialized only again\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=true);\n-- Refresh all data\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Materialized data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n          bucket          | temperature \n--------------------------+-------------\n Sat Jan 01 00:00:00 2022 |          15\n Sun Jan 02 00:00:00 2022 |          20\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n          bucket          | temperature \n--------------------------+-------------\n Mon Dec 27 00:00:00 2021 |          35\n\n\\if :IS_TIME_DIMENSION\n-- Invalidate an old region\nINSERT INTO conditions (\"time\", temperature) VALUES ('2022-01-01 01:00:00-00'::timestamptz, 2);\n-- New region\nINSERT INTO conditions (\"time\", temperature) VALUES ('2022-01-03 01:00:00-00'::timestamptz, 2);\n\\else\n-- Invalidate an old region\nINSERT INTO conditions (\"time\", temperature) VALUES (2,  2);\n-- New region\nINSERT INTO conditions (\"time\", temperature) VALUES (10, 2);\n\\endif\n-- No changes\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n          bucket          | temperature \n--------------------------+-------------\n Sat Jan 01 00:00:00 2022 |          15\n Sun Jan 02 00:00:00 2022 |          20\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n          bucket          | temperature \n--------------------------+-------------\n Mon Dec 27 00:00:00 2021 |          35\n\n-- Turn CAGGs into Realtime\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=false);\n-- Realtime changes, just new region\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n Mon Jan 03 01:00:00 2022 |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n          bucket          | temperature \n--------------------------+-------------\n Sat Jan 01 00:00:00 2022 |          15\n Sun Jan 02 00:00:00 2022 |          20\n Mon Jan 03 00:00:00 2022 |           2\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n          bucket          | temperature \n--------------------------+-------------\n Mon Dec 27 00:00:00 2021 |          35\n Mon Jan 03 00:00:00 2022 |           2\n\n-- Turn CAGGs into materialized only again\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=true);\n-- Refresh all data\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- All changes are materialized\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           2 |          \n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n Mon Jan 03 01:00:00 2022 |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n          bucket          | temperature \n--------------------------+-------------\n Sat Jan 01 00:00:00 2022 |          17\n Sun Jan 02 00:00:00 2022 |          20\n Mon Jan 03 00:00:00 2022 |           2\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n          bucket          | temperature \n--------------------------+-------------\n Mon Dec 27 00:00:00 2021 |          37\n Mon Jan 03 00:00:00 2022 |           2\n\n-- TRUNCATE tests\nTRUNCATE :CAGG_NAME_2TH_LEVEL;\n-- This full refresh will remove all the data from the 3TH level cagg\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Should return no rows\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature \n--------+-------------\n\n-- If we have all the data in the bottom levels caggs we can rebuild\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Now we have all the data\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n          bucket          | temperature \n--------------------------+-------------\n Sat Jan 01 00:00:00 2022 |          17\n Sun Jan 02 00:00:00 2022 |          20\n Mon Jan 03 00:00:00 2022 |           2\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n          bucket          | temperature \n--------------------------+-------------\n Mon Dec 27 00:00:00 2021 |          37\n Mon Jan 03 00:00:00 2022 |           2\n\n-- DROP tests\n\\set ON_ERROR_STOP 0\n-- should error because it depends of other CAGGs\nDROP MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:172: ERROR:  cannot drop view conditions_summary_1_hourly because other objects depend on it\nDROP MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:173: ERROR:  cannot drop view conditions_summary_2_daily because other objects depend on it\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\npsql:include/cagg_on_cagg_common.sql:174: NOTICE:  continuous aggregate \"conditions_summary_1_hourly\" is already up-to-date\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\npsql:include/cagg_on_cagg_common.sql:175: NOTICE:  continuous aggregate \"conditions_summary_2_daily\" is already up-to-date\n\\set ON_ERROR_STOP 1\n-- DROP the 3TH level CAGG don't affect others\nDROP MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:179: NOTICE:  drop cascades to table _timescaledb_internal._hyper_20_24_chunk\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:182: ERROR:  relation \"conditions_summary_3_weekly\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- should work because dropping the top level CAGG\n-- don't affect the down level CAGGs\nTRUNCATE :CAGG_NAME_2TH_LEVEL,:CAGG_NAME_1ST_LEVEL;\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           2 |          \n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n Mon Jan 03 01:00:00 2022 |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n\n-- DROP the 2TH level CAGG don't affect others\nDROP MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL;\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:196: ERROR:  relation \"conditions_summary_2_daily\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- should work because dropping the top level CAGG\n-- don't affect the down level CAGGs\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           2 |          \n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n Mon Jan 03 01:00:00 2022 |           2 |          \n\n-- DROP the first CAGG should work\nDROP MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:203: NOTICE:  drop cascades to table _timescaledb_internal._hyper_18_26_chunk\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:206: ERROR:  relation \"conditions_summary_1_hourly\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n--\n-- Validation test for variable bucket on top of fixed bucket\n--\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 month\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'60 days\\''\n\\set WARNING_MESSAGE '-- SHOULD ERROR because is not allowed variable-size bucket on top of fixed-size bucket'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                                 View \"public.conditions_summary_1\"\n   Column    |            Type             | Collation | Nullable | Default | Storage | Description \n-------------+-----------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp without time zone |           |          |         | plain   | \n temperature | numeric                     |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_21.bucket,\n    _materialized_hypertable_21.temperature\n   FROM _timescaledb_internal._materialized_hypertable_21\n  WHERE _materialized_hypertable_21.bucket < COALESCE(_timescaledb_functions.to_timestamp_without_timezone(_timescaledb_functions.cagg_watermark(21)), '-infinity'::timestamp without time zone)\nUNION ALL\n SELECT time_bucket('@ 1 mon'::interval, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp_without_timezone(_timescaledb_functions.cagg_watermark(21)), '-infinity'::timestamp without time zone)\n  GROUP BY (time_bucket('@ 1 mon'::interval, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD ERROR because is not allowed variable-size bucket on top of fixed-size bucket\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\npsql:include/cagg_on_cagg_validations.sql:44: ERROR:  cannot create continuous aggregate with fixed-width bucket on top of one using variable-width bucket\nDETAIL:  Continuous aggregate with a fixed time bucket width (e.g. 61 days) cannot be created on top of one using variable time bucket width (e.g. 1 month).\nThe variance can lead to the fixed width one not being a multiple of the variable width one.\n\\d+ :CAGG_NAME_2TH_LEVEL\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:86: NOTICE:  materialized view \"conditions_summary_2\" does not exist, skipping\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n--\n-- Validation test for non-multiple bucket sizes\n--\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'2 hours\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'3 hours\\''\n\\set WARNING_MESSAGE '-- SHOULD ERROR because non-multiple bucket sizes'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                                 View \"public.conditions_summary_1\"\n   Column    |            Type             | Collation | Nullable | Default | Storage | Description \n-------------+-----------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp without time zone |           |          |         | plain   | \n temperature | numeric                     |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_22.bucket,\n    _materialized_hypertable_22.temperature\n   FROM _timescaledb_internal._materialized_hypertable_22\n  WHERE _materialized_hypertable_22.bucket < COALESCE(_timescaledb_functions.to_timestamp_without_timezone(_timescaledb_functions.cagg_watermark(22)), '-infinity'::timestamp without time zone)\nUNION ALL\n SELECT time_bucket('@ 2 hours'::interval, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp_without_timezone(_timescaledb_functions.cagg_watermark(22)), '-infinity'::timestamp without time zone)\n  GROUP BY (time_bucket('@ 2 hours'::interval, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD ERROR because non-multiple bucket sizes\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\npsql:include/cagg_on_cagg_validations.sql:44: ERROR:  cannot create continuous aggregate with incompatible bucket width\nDETAIL:  Time bucket width of \"public.conditions_summary_2\" [@ 3 hours] should be multiple of the time bucket width of \"public.conditions_summary_1\" [@ 2 hours].\n\\d+ :CAGG_NAME_2TH_LEVEL\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:86: NOTICE:  materialized view \"conditions_summary_2\" does not exist, skipping\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n--\n-- Validation test for equal bucket sizes\n--\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 hour\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 hour\\''\n\\set WARNING_MESSAGE 'SHOULD WORK because new bucket should be greater than previous'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                                 View \"public.conditions_summary_1\"\n   Column    |            Type             | Collation | Nullable | Default | Storage | Description \n-------------+-----------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp without time zone |           |          |         | plain   | \n temperature | numeric                     |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_23.bucket,\n    _materialized_hypertable_23.temperature\n   FROM _timescaledb_internal._materialized_hypertable_23\n  WHERE _materialized_hypertable_23.bucket < COALESCE(_timescaledb_functions.to_timestamp_without_timezone(_timescaledb_functions.cagg_watermark(23)), '-infinity'::timestamp without time zone)\nUNION ALL\n SELECT time_bucket('@ 1 hour'::interval, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp_without_timezone(_timescaledb_functions.cagg_watermark(23)), '-infinity'::timestamp without time zone)\n  GROUP BY (time_bucket('@ 1 hour'::interval, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\nSHOULD WORK because new bucket should be greater than previous\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                                 View \"public.conditions_summary_2\"\n   Column    |            Type             | Collation | Nullable | Default | Storage | Description \n-------------+-----------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp without time zone |           |          |         | plain   | \n temperature | numeric                     |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_24.bucket,\n    _materialized_hypertable_24.temperature\n   FROM _timescaledb_internal._materialized_hypertable_24\n  WHERE _materialized_hypertable_24.bucket < COALESCE(_timescaledb_functions.to_timestamp_without_timezone(_timescaledb_functions.cagg_watermark(24)), '-infinity'::timestamp without time zone)\nUNION ALL\n SELECT time_bucket('@ 1 hour'::interval, conditions_summary_1.bucket) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp_without_timezone(_timescaledb_functions.cagg_watermark(24)), '-infinity'::timestamp without time zone)\n  GROUP BY (time_bucket('@ 1 hour'::interval, conditions_summary_1.bucket));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n--\n-- Validation test for bucket size less than source\n--\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'2 hours\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 hour\\''\n\\set WARNING_MESSAGE '-- SHOULD ERROR because new bucket should be greater than previous'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                                 View \"public.conditions_summary_1\"\n   Column    |            Type             | Collation | Nullable | Default | Storage | Description \n-------------+-----------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp without time zone |           |          |         | plain   | \n temperature | numeric                     |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_25.bucket,\n    _materialized_hypertable_25.temperature\n   FROM _timescaledb_internal._materialized_hypertable_25\n  WHERE _materialized_hypertable_25.bucket < COALESCE(_timescaledb_functions.to_timestamp_without_timezone(_timescaledb_functions.cagg_watermark(25)), '-infinity'::timestamp without time zone)\nUNION ALL\n SELECT time_bucket('@ 2 hours'::interval, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp_without_timezone(_timescaledb_functions.cagg_watermark(25)), '-infinity'::timestamp without time zone)\n  GROUP BY (time_bucket('@ 2 hours'::interval, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD ERROR because new bucket should be greater than previous\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\npsql:include/cagg_on_cagg_validations.sql:44: ERROR:  cannot create continuous aggregate with incompatible bucket width\nDETAIL:  Time bucket width of \"public.conditions_summary_2\" [@ 1 hour] should be greater or equal than the time bucket width of \"public.conditions_summary_1\" [@ 2 hours].\n\\d+ :CAGG_NAME_2TH_LEVEL\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:86: NOTICE:  materialized view \"conditions_summary_2\" does not exist, skipping\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n-- ########################################################\n-- ## TIMESTAMPTZ data type tests\n-- ########################################################\n-- Current test variables\n\\set IS_TIME_DIMENSION TRUE\n\\set TIME_DIMENSION_DATATYPE TIMESTAMPTZ\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1_hourly\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2_daily\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3_weekly\nSET timezone TO 'UTC';\n--\n-- Run common tests for TIMESTAMPTZ\n--\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 hour\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 day\\''\n\\set BUCKET_WIDTH_3TH 'INTERVAL \\'1 week\\''\n-- Different order of time dimension in raw ht\n\\set IS_DEFAULT_COLUMN_ORDER FALSE\n\\ir include/cagg_on_cagg_setup.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- CAGGs on CAGGs tests\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nDROP TABLE IF EXISTS conditions CASCADE;\n\\if :IS_DEFAULT_COLUMN_ORDER\n    CREATE TABLE conditions (\n    time :TIME_DIMENSION_DATATYPE NOT NULL,\n    temperature NUMERIC,\n    device_id INT\n  );\n\\else\n    CREATE TABLE conditions (\n    temperature NUMERIC,\n    time :TIME_DIMENSION_DATATYPE NOT NULL,\n    device_id INT\n  );\n\\endif\n\\if :IS_JOIN\n  DROP TABLE IF EXISTS devices CASCADE;\n  CREATE TABLE devices ( device_id int not null, name text, location text);\n  INSERT INTO devices values (1, 'thermo_1', 'Moscow'), (2, 'thermo_2', 'Berlin'),(3, 'thermo_3', 'London'),(4, 'thermo_4', 'Stockholm');\n\\endif\n\\if :IS_TIME_DIMENSION\n  SELECT table_name FROM create_hypertable('conditions', 'time');\n table_name \n------------\n conditions\n\n\\else\n  SELECT table_name FROM create_hypertable('conditions', 'time', chunk_time_interval => 10);\n\\endif\n\\if :IS_TIME_DIMENSION\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 1);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 01:00:00-00',  5, 2);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-02 01:00:00-00', 20, 3);\n\\else\n  CREATE OR REPLACE FUNCTION integer_now()\n  RETURNS :TIME_DIMENSION_DATATYPE LANGUAGE SQL STABLE AS\n  $$\n    SELECT coalesce(max(time), 0)\n    FROM conditions\n  $$;\n  SELECT set_integer_now_func('conditions', 'integer_now');\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (1, 10, 1);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (2,  5, 2);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (5, 20, 3);\n\\endif\n\\ir include/cagg_on_cagg_common.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- CAGG on hypertable (1st level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  SUM(temperature) AS temperature,\n  device_id\nFROM conditions\nGROUP BY 1, 3\nORDER BY 1, 2, 3\nWITH NO DATA;\n-- CAGG on CAGG (2th level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  SUM(temperature) AS temperature\n\\if :IS_JOIN\n  , devices.device_id\n  , devices.name\n  FROM :CAGG_NAME_1ST_LEVEL\n  JOIN devices ON devices.device_id = :CAGG_NAME_1ST_LEVEL.device_id\n  GROUP BY 1, 3, 4\n  ORDER BY 1, 2, 3, 4\n\\else\n  FROM :CAGG_NAME_1ST_LEVEL\n  GROUP BY 1\n  ORDER BY 1, 2\n\\endif\nWITH NO DATA;\n-- CAGG on CAGG (3th level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket,\n  SUM(temperature) AS temperature\n\\if :IS_JOIN\n  , :CAGG_NAME_2TH_LEVEL.device_id\n  , :CAGG_NAME_2TH_LEVEL.name\n  , devices.location\n  FROM :CAGG_NAME_2TH_LEVEL\n  JOIN devices ON devices.device_id = :CAGG_NAME_2TH_LEVEL.device_id\n  GROUP BY 1, 3, 4, 5\n  ORDER BY 1, 2, 3, 4, 5\n\\else\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  ORDER BY 1, 2\n\\endif\nWITH NO DATA;\n-- Check chunk_interval\n\\if :IS_TIME_DIMENSION\n  SELECT h.table_name AS name, _timescaledb_functions.to_interval(d.interval_length) AS chunk_interval\n  FROM _timescaledb_catalog.hypertable h\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = h.id\n  WHERE h.table_name = 'conditions'\n  UNION ALL\n  SELECT c.user_view_name AS name, _timescaledb_functions.to_interval(d.interval_length) AS chunk_interval\n  FROM _timescaledb_catalog.continuous_agg c\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = c.mat_hypertable_id\n  WHERE c.user_view_name IN (:'CAGG_NAME_1ST_LEVEL', :'CAGG_NAME_2TH_LEVEL', :'CAGG_NAME_3TH_LEVEL')\n  ORDER BY 1, 2;\n            name             | chunk_interval \n-----------------------------+----------------\n conditions                  | @ 7 days\n conditions_summary_1_hourly | @ 70 days\n conditions_summary_2_daily  | @ 70 days\n conditions_summary_3_weekly | @ 70 days\n\n\\else\n  SELECT h.table_name AS name, d.interval_length AS chunk_interval\n  FROM _timescaledb_catalog.hypertable h\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = h.id\n  WHERE h.table_name = 'conditions'\n  UNION ALL\n  SELECT c.user_view_name AS name, d.interval_length AS chunk_interval\n  FROM _timescaledb_catalog.continuous_agg c\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = c.mat_hypertable_id\n  WHERE c.user_view_name IN (:'CAGG_NAME_1ST_LEVEL', :'CAGG_NAME_2TH_LEVEL', :'CAGG_NAME_3TH_LEVEL')\n  ORDER BY 1, 2;\n\\endif\n-- No data because the CAGGs are just for materialized data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature \n--------+-------------\n\n-- Turn CAGGs into Realtime\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=false);\n-- Realtime data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n            bucket            | temperature \n------------------------------+-------------\n Sat Jan 01 00:00:00 2022 UTC |          15\n Sun Jan 02 00:00:00 2022 UTC |          20\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n            bucket            | temperature \n------------------------------+-------------\n Mon Dec 27 00:00:00 2021 UTC |          35\n\n-- Turn CAGGs into materialized only again\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=true);\n-- Refresh all data\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Materialized data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n            bucket            | temperature \n------------------------------+-------------\n Sat Jan 01 00:00:00 2022 UTC |          15\n Sun Jan 02 00:00:00 2022 UTC |          20\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n            bucket            | temperature \n------------------------------+-------------\n Mon Dec 27 00:00:00 2021 UTC |          35\n\n\\if :IS_TIME_DIMENSION\n-- Invalidate an old region\nINSERT INTO conditions (\"time\", temperature) VALUES ('2022-01-01 01:00:00-00'::timestamptz, 2);\n-- New region\nINSERT INTO conditions (\"time\", temperature) VALUES ('2022-01-03 01:00:00-00'::timestamptz, 2);\n\\else\n-- Invalidate an old region\nINSERT INTO conditions (\"time\", temperature) VALUES (2,  2);\n-- New region\nINSERT INTO conditions (\"time\", temperature) VALUES (10, 2);\n\\endif\n-- No changes\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n            bucket            | temperature \n------------------------------+-------------\n Sat Jan 01 00:00:00 2022 UTC |          15\n Sun Jan 02 00:00:00 2022 UTC |          20\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n            bucket            | temperature \n------------------------------+-------------\n Mon Dec 27 00:00:00 2021 UTC |          35\n\n-- Turn CAGGs into Realtime\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=false);\n-- Realtime changes, just new region\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n Mon Jan 03 01:00:00 2022 UTC |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n            bucket            | temperature \n------------------------------+-------------\n Sat Jan 01 00:00:00 2022 UTC |          15\n Sun Jan 02 00:00:00 2022 UTC |          20\n Mon Jan 03 00:00:00 2022 UTC |           2\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n            bucket            | temperature \n------------------------------+-------------\n Mon Dec 27 00:00:00 2021 UTC |          35\n Mon Jan 03 00:00:00 2022 UTC |           2\n\n-- Turn CAGGs into materialized only again\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=true);\n-- Refresh all data\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- All changes are materialized\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           2 |          \n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n Mon Jan 03 01:00:00 2022 UTC |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n            bucket            | temperature \n------------------------------+-------------\n Sat Jan 01 00:00:00 2022 UTC |          17\n Sun Jan 02 00:00:00 2022 UTC |          20\n Mon Jan 03 00:00:00 2022 UTC |           2\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n            bucket            | temperature \n------------------------------+-------------\n Mon Dec 27 00:00:00 2021 UTC |          37\n Mon Jan 03 00:00:00 2022 UTC |           2\n\n-- TRUNCATE tests\nTRUNCATE :CAGG_NAME_2TH_LEVEL;\n-- This full refresh will remove all the data from the 3TH level cagg\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Should return no rows\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature \n--------+-------------\n\n-- If we have all the data in the bottom levels caggs we can rebuild\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Now we have all the data\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n            bucket            | temperature \n------------------------------+-------------\n Sat Jan 01 00:00:00 2022 UTC |          17\n Sun Jan 02 00:00:00 2022 UTC |          20\n Mon Jan 03 00:00:00 2022 UTC |           2\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n            bucket            | temperature \n------------------------------+-------------\n Mon Dec 27 00:00:00 2021 UTC |          37\n Mon Jan 03 00:00:00 2022 UTC |           2\n\n-- DROP tests\n\\set ON_ERROR_STOP 0\n-- should error because it depends of other CAGGs\nDROP MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:172: ERROR:  cannot drop view conditions_summary_1_hourly because other objects depend on it\nDROP MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:173: ERROR:  cannot drop view conditions_summary_2_daily because other objects depend on it\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\npsql:include/cagg_on_cagg_common.sql:174: NOTICE:  continuous aggregate \"conditions_summary_1_hourly\" is already up-to-date\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\npsql:include/cagg_on_cagg_common.sql:175: NOTICE:  continuous aggregate \"conditions_summary_2_daily\" is already up-to-date\n\\set ON_ERROR_STOP 1\n-- DROP the 3TH level CAGG don't affect others\nDROP MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:179: NOTICE:  drop cascades to table _timescaledb_internal._hyper_29_30_chunk\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:182: ERROR:  relation \"conditions_summary_3_weekly\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- should work because dropping the top level CAGG\n-- don't affect the down level CAGGs\nTRUNCATE :CAGG_NAME_2TH_LEVEL,:CAGG_NAME_1ST_LEVEL;\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           2 |          \n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n Mon Jan 03 01:00:00 2022 UTC |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n\n-- DROP the 2TH level CAGG don't affect others\nDROP MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL;\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:196: ERROR:  relation \"conditions_summary_2_daily\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- should work because dropping the top level CAGG\n-- don't affect the down level CAGGs\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           2 |          \n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n Mon Jan 03 01:00:00 2022 UTC |           2 |          \n\n-- DROP the first CAGG should work\nDROP MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:203: NOTICE:  drop cascades to table _timescaledb_internal._hyper_27_32_chunk\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:206: ERROR:  relation \"conditions_summary_1_hourly\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- Default tests\n\\set IS_DEFAULT_COLUMN_ORDER TRUE\n\\ir include/cagg_on_cagg_setup.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- CAGGs on CAGGs tests\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nDROP TABLE IF EXISTS conditions CASCADE;\n\\if :IS_DEFAULT_COLUMN_ORDER\n    CREATE TABLE conditions (\n    time :TIME_DIMENSION_DATATYPE NOT NULL,\n    temperature NUMERIC,\n    device_id INT\n  );\n\\else\n    CREATE TABLE conditions (\n    temperature NUMERIC,\n    time :TIME_DIMENSION_DATATYPE NOT NULL,\n    device_id INT\n  );\n\\endif\n\\if :IS_JOIN\n  DROP TABLE IF EXISTS devices CASCADE;\n  CREATE TABLE devices ( device_id int not null, name text, location text);\n  INSERT INTO devices values (1, 'thermo_1', 'Moscow'), (2, 'thermo_2', 'Berlin'),(3, 'thermo_3', 'London'),(4, 'thermo_4', 'Stockholm');\n\\endif\n\\if :IS_TIME_DIMENSION\n  SELECT table_name FROM create_hypertable('conditions', 'time');\n table_name \n------------\n conditions\n\n\\else\n  SELECT table_name FROM create_hypertable('conditions', 'time', chunk_time_interval => 10);\n\\endif\n\\if :IS_TIME_DIMENSION\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 1);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 01:00:00-00',  5, 2);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-02 01:00:00-00', 20, 3);\n\\else\n  CREATE OR REPLACE FUNCTION integer_now()\n  RETURNS :TIME_DIMENSION_DATATYPE LANGUAGE SQL STABLE AS\n  $$\n    SELECT coalesce(max(time), 0)\n    FROM conditions\n  $$;\n  SELECT set_integer_now_func('conditions', 'integer_now');\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (1, 10, 1);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (2,  5, 2);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (5, 20, 3);\n\\endif\n\\ir include/cagg_on_cagg_common.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- CAGG on hypertable (1st level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  SUM(temperature) AS temperature,\n  device_id\nFROM conditions\nGROUP BY 1, 3\nORDER BY 1, 2, 3\nWITH NO DATA;\n-- CAGG on CAGG (2th level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  SUM(temperature) AS temperature\n\\if :IS_JOIN\n  , devices.device_id\n  , devices.name\n  FROM :CAGG_NAME_1ST_LEVEL\n  JOIN devices ON devices.device_id = :CAGG_NAME_1ST_LEVEL.device_id\n  GROUP BY 1, 3, 4\n  ORDER BY 1, 2, 3, 4\n\\else\n  FROM :CAGG_NAME_1ST_LEVEL\n  GROUP BY 1\n  ORDER BY 1, 2\n\\endif\nWITH NO DATA;\n-- CAGG on CAGG (3th level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket,\n  SUM(temperature) AS temperature\n\\if :IS_JOIN\n  , :CAGG_NAME_2TH_LEVEL.device_id\n  , :CAGG_NAME_2TH_LEVEL.name\n  , devices.location\n  FROM :CAGG_NAME_2TH_LEVEL\n  JOIN devices ON devices.device_id = :CAGG_NAME_2TH_LEVEL.device_id\n  GROUP BY 1, 3, 4, 5\n  ORDER BY 1, 2, 3, 4, 5\n\\else\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  ORDER BY 1, 2\n\\endif\nWITH NO DATA;\n-- Check chunk_interval\n\\if :IS_TIME_DIMENSION\n  SELECT h.table_name AS name, _timescaledb_functions.to_interval(d.interval_length) AS chunk_interval\n  FROM _timescaledb_catalog.hypertable h\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = h.id\n  WHERE h.table_name = 'conditions'\n  UNION ALL\n  SELECT c.user_view_name AS name, _timescaledb_functions.to_interval(d.interval_length) AS chunk_interval\n  FROM _timescaledb_catalog.continuous_agg c\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = c.mat_hypertable_id\n  WHERE c.user_view_name IN (:'CAGG_NAME_1ST_LEVEL', :'CAGG_NAME_2TH_LEVEL', :'CAGG_NAME_3TH_LEVEL')\n  ORDER BY 1, 2;\n            name             | chunk_interval \n-----------------------------+----------------\n conditions                  | @ 7 days\n conditions_summary_1_hourly | @ 70 days\n conditions_summary_2_daily  | @ 70 days\n conditions_summary_3_weekly | @ 70 days\n\n\\else\n  SELECT h.table_name AS name, d.interval_length AS chunk_interval\n  FROM _timescaledb_catalog.hypertable h\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = h.id\n  WHERE h.table_name = 'conditions'\n  UNION ALL\n  SELECT c.user_view_name AS name, d.interval_length AS chunk_interval\n  FROM _timescaledb_catalog.continuous_agg c\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = c.mat_hypertable_id\n  WHERE c.user_view_name IN (:'CAGG_NAME_1ST_LEVEL', :'CAGG_NAME_2TH_LEVEL', :'CAGG_NAME_3TH_LEVEL')\n  ORDER BY 1, 2;\n\\endif\n-- No data because the CAGGs are just for materialized data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature \n--------+-------------\n\n-- Turn CAGGs into Realtime\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=false);\n-- Realtime data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n            bucket            | temperature \n------------------------------+-------------\n Sat Jan 01 00:00:00 2022 UTC |          15\n Sun Jan 02 00:00:00 2022 UTC |          20\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n            bucket            | temperature \n------------------------------+-------------\n Mon Dec 27 00:00:00 2021 UTC |          35\n\n-- Turn CAGGs into materialized only again\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=true);\n-- Refresh all data\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Materialized data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n            bucket            | temperature \n------------------------------+-------------\n Sat Jan 01 00:00:00 2022 UTC |          15\n Sun Jan 02 00:00:00 2022 UTC |          20\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n            bucket            | temperature \n------------------------------+-------------\n Mon Dec 27 00:00:00 2021 UTC |          35\n\n\\if :IS_TIME_DIMENSION\n-- Invalidate an old region\nINSERT INTO conditions (\"time\", temperature) VALUES ('2022-01-01 01:00:00-00'::timestamptz, 2);\n-- New region\nINSERT INTO conditions (\"time\", temperature) VALUES ('2022-01-03 01:00:00-00'::timestamptz, 2);\n\\else\n-- Invalidate an old region\nINSERT INTO conditions (\"time\", temperature) VALUES (2,  2);\n-- New region\nINSERT INTO conditions (\"time\", temperature) VALUES (10, 2);\n\\endif\n-- No changes\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n            bucket            | temperature \n------------------------------+-------------\n Sat Jan 01 00:00:00 2022 UTC |          15\n Sun Jan 02 00:00:00 2022 UTC |          20\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n            bucket            | temperature \n------------------------------+-------------\n Mon Dec 27 00:00:00 2021 UTC |          35\n\n-- Turn CAGGs into Realtime\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=false);\n-- Realtime changes, just new region\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n Mon Jan 03 01:00:00 2022 UTC |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n            bucket            | temperature \n------------------------------+-------------\n Sat Jan 01 00:00:00 2022 UTC |          15\n Sun Jan 02 00:00:00 2022 UTC |          20\n Mon Jan 03 00:00:00 2022 UTC |           2\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n            bucket            | temperature \n------------------------------+-------------\n Mon Dec 27 00:00:00 2021 UTC |          35\n Mon Jan 03 00:00:00 2022 UTC |           2\n\n-- Turn CAGGs into materialized only again\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=true);\n-- Refresh all data\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- All changes are materialized\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           2 |          \n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n Mon Jan 03 01:00:00 2022 UTC |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n            bucket            | temperature \n------------------------------+-------------\n Sat Jan 01 00:00:00 2022 UTC |          17\n Sun Jan 02 00:00:00 2022 UTC |          20\n Mon Jan 03 00:00:00 2022 UTC |           2\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n            bucket            | temperature \n------------------------------+-------------\n Mon Dec 27 00:00:00 2021 UTC |          37\n Mon Jan 03 00:00:00 2022 UTC |           2\n\n-- TRUNCATE tests\nTRUNCATE :CAGG_NAME_2TH_LEVEL;\n-- This full refresh will remove all the data from the 3TH level cagg\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Should return no rows\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature \n--------+-------------\n\n-- If we have all the data in the bottom levels caggs we can rebuild\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Now we have all the data\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n            bucket            | temperature \n------------------------------+-------------\n Sat Jan 01 00:00:00 2022 UTC |          17\n Sun Jan 02 00:00:00 2022 UTC |          20\n Mon Jan 03 00:00:00 2022 UTC |           2\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n            bucket            | temperature \n------------------------------+-------------\n Mon Dec 27 00:00:00 2021 UTC |          37\n Mon Jan 03 00:00:00 2022 UTC |           2\n\n-- DROP tests\n\\set ON_ERROR_STOP 0\n-- should error because it depends of other CAGGs\nDROP MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:172: ERROR:  cannot drop view conditions_summary_1_hourly because other objects depend on it\nDROP MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:173: ERROR:  cannot drop view conditions_summary_2_daily because other objects depend on it\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\npsql:include/cagg_on_cagg_common.sql:174: NOTICE:  continuous aggregate \"conditions_summary_1_hourly\" is already up-to-date\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\npsql:include/cagg_on_cagg_common.sql:175: NOTICE:  continuous aggregate \"conditions_summary_2_daily\" is already up-to-date\n\\set ON_ERROR_STOP 1\n-- DROP the 3TH level CAGG don't affect others\nDROP MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:179: NOTICE:  drop cascades to table _timescaledb_internal._hyper_33_36_chunk\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:182: ERROR:  relation \"conditions_summary_3_weekly\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- should work because dropping the top level CAGG\n-- don't affect the down level CAGGs\nTRUNCATE :CAGG_NAME_2TH_LEVEL,:CAGG_NAME_1ST_LEVEL;\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           2 |          \n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n Mon Jan 03 01:00:00 2022 UTC |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature \n--------+-------------\n\n-- DROP the 2TH level CAGG don't affect others\nDROP MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL;\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:196: ERROR:  relation \"conditions_summary_2_daily\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- should work because dropping the top level CAGG\n-- don't affect the down level CAGGs\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           2 |          \n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n Mon Jan 03 01:00:00 2022 UTC |           2 |          \n\n-- DROP the first CAGG should work\nDROP MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:203: NOTICE:  drop cascades to table _timescaledb_internal._hyper_31_38_chunk\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:206: ERROR:  relation \"conditions_summary_1_hourly\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n--\n-- Validation test for variable bucket on top of fixed bucket\n--\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 month\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'60 days\\''\n\\set WARNING_MESSAGE '-- SHOULD ERROR because is not allowed variable-size bucket on top of fixed-size bucket'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_34.bucket,\n    _materialized_hypertable_34.temperature\n   FROM _timescaledb_internal._materialized_hypertable_34\n  WHERE _materialized_hypertable_34.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(34)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 mon'::interval, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(34)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 mon'::interval, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD ERROR because is not allowed variable-size bucket on top of fixed-size bucket\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\npsql:include/cagg_on_cagg_validations.sql:44: ERROR:  cannot create continuous aggregate with fixed-width bucket on top of one using variable-width bucket\nDETAIL:  Continuous aggregate with a fixed time bucket width (e.g. 61 days) cannot be created on top of one using variable time bucket width (e.g. 1 month).\nThe variance can lead to the fixed width one not being a multiple of the variable width one.\n\\d+ :CAGG_NAME_2TH_LEVEL\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:86: NOTICE:  materialized view \"conditions_summary_2\" does not exist, skipping\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n--\n-- Validation test for non-multiple bucket sizes\n--\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'2 hours\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'3 hours\\''\n\\set WARNING_MESSAGE '-- SHOULD ERROR because non-multiple bucket sizes'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_35.bucket,\n    _materialized_hypertable_35.temperature\n   FROM _timescaledb_internal._materialized_hypertable_35\n  WHERE _materialized_hypertable_35.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(35)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 2 hours'::interval, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(35)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 2 hours'::interval, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD ERROR because non-multiple bucket sizes\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\npsql:include/cagg_on_cagg_validations.sql:44: ERROR:  cannot create continuous aggregate with incompatible bucket width\nDETAIL:  Time bucket width of \"public.conditions_summary_2\" [@ 3 hours] should be multiple of the time bucket width of \"public.conditions_summary_1\" [@ 2 hours].\n\\d+ :CAGG_NAME_2TH_LEVEL\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:86: NOTICE:  materialized view \"conditions_summary_2\" does not exist, skipping\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n--\n-- Validation test for equal bucket sizes\n--\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 hour\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 hour\\''\n\\set WARNING_MESSAGE 'SHOULD WORK because new bucket should be greater than previous'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_36.bucket,\n    _materialized_hypertable_36.temperature\n   FROM _timescaledb_internal._materialized_hypertable_36\n  WHERE _materialized_hypertable_36.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(36)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 hour'::interval, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(36)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 hour'::interval, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\nSHOULD WORK because new bucket should be greater than previous\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                               View \"public.conditions_summary_2\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_37.bucket,\n    _materialized_hypertable_37.temperature\n   FROM _timescaledb_internal._materialized_hypertable_37\n  WHERE _materialized_hypertable_37.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(37)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 hour'::interval, conditions_summary_1.bucket) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(37)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 hour'::interval, conditions_summary_1.bucket));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n--\n-- Validation test for bucket size less than source\n--\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'2 hours\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 hour\\''\n\\set WARNING_MESSAGE '-- SHOULD ERROR because new bucket should be greater than previous'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_38.bucket,\n    _materialized_hypertable_38.temperature\n   FROM _timescaledb_internal._materialized_hypertable_38\n  WHERE _materialized_hypertable_38.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(38)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 2 hours'::interval, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(38)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 2 hours'::interval, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD ERROR because new bucket should be greater than previous\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\npsql:include/cagg_on_cagg_validations.sql:44: ERROR:  cannot create continuous aggregate with incompatible bucket width\nDETAIL:  Time bucket width of \"public.conditions_summary_2\" [@ 1 hour] should be greater or equal than the time bucket width of \"public.conditions_summary_1\" [@ 2 hours].\n\\d+ :CAGG_NAME_2TH_LEVEL\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:86: NOTICE:  materialized view \"conditions_summary_2\" does not exist, skipping\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n--\n-- Validations using time bucket with timezone (ref issue #5126)\n--\n\\set TIME_DIMENSION_DATATYPE TIMESTAMPTZ\n\\set IS_TIME_DIMENSION_WITH_TIMEZONE_1ST TRUE\n\\set IS_TIME_DIMENSION_WITH_TIMEZONE_2TH TRUE\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1_5m\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2_1h\n\\set BUCKET_TZNAME_1ST 'US/Pacific'\n\\set BUCKET_TZNAME_2TH 'US/Pacific'\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'5 minutes\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 hour\\''\n\\set WARNING_MESSAGE '-- SHOULD WORK'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_39.bucket,\n    _materialized_hypertable_39.temperature\n   FROM _timescaledb_internal._materialized_hypertable_39\n  WHERE _materialized_hypertable_39.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(39)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 5 mins'::interval, conditions.\"time\", 'US/Pacific'::text) AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(39)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 5 mins'::interval, conditions.\"time\", 'US/Pacific'::text));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                               View \"public.conditions_summary_2\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_40.bucket,\n    _materialized_hypertable_40.temperature\n   FROM _timescaledb_internal._materialized_hypertable_40\n  WHERE _materialized_hypertable_40.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(40)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 hour'::interval, conditions_summary_1.bucket, 'US/Pacific'::text) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(40)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 hour'::interval, conditions_summary_1.bucket, 'US/Pacific'::text));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'5 minutes\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'16 minutes\\''\n\\set WARNING_MESSAGE '-- SHOULD ERROR because non-multiple bucket sizes'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_41.bucket,\n    _materialized_hypertable_41.temperature\n   FROM _timescaledb_internal._materialized_hypertable_41\n  WHERE _materialized_hypertable_41.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(41)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 5 mins'::interval, conditions.\"time\", 'US/Pacific'::text) AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(41)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 5 mins'::interval, conditions.\"time\", 'US/Pacific'::text));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD ERROR because non-multiple bucket sizes\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\npsql:include/cagg_on_cagg_validations.sql:44: ERROR:  cannot create continuous aggregate with incompatible bucket width\nDETAIL:  Time bucket width of \"public.conditions_summary_2\" [@ 16 mins] should be multiple of the time bucket width of \"public.conditions_summary_1\" [@ 5 mins].\n\\d+ :CAGG_NAME_2TH_LEVEL\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:86: NOTICE:  materialized view \"conditions_summary_2\" does not exist, skipping\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n--\n-- Variable bucket size with the same timezones\n--\n\\set BUCKET_TZNAME_1ST 'UTC'\n\\set BUCKET_TZNAME_2TH 'UTC'\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 day\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 month\\''\n\\set WARNING_MESSAGE '-- SHOULD WORK'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_42.bucket,\n    _materialized_hypertable_42.temperature\n   FROM _timescaledb_internal._materialized_hypertable_42\n  WHERE _materialized_hypertable_42.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(42)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 day'::interval, conditions.\"time\", 'UTC'::text) AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(42)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 day'::interval, conditions.\"time\", 'UTC'::text));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                               View \"public.conditions_summary_2\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_43.bucket,\n    _materialized_hypertable_43.temperature\n   FROM _timescaledb_internal._materialized_hypertable_43\n  WHERE _materialized_hypertable_43.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(43)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 mon'::interval, conditions_summary_1.bucket, 'UTC'::text) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(43)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 mon'::interval, conditions_summary_1.bucket, 'UTC'::text));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n--#Bugfix 5734 #1\n\\set INTERVAL_TEST TRUE\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 hour\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 day\\''\n\\set BUCKET_WIDTH_3TH 'INTERVAL \\'1 month\\''\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_44.bucket,\n    _materialized_hypertable_44.temperature\n   FROM _timescaledb_internal._materialized_hypertable_44\n  WHERE _materialized_hypertable_44.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(44)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 hour'::interval, conditions.\"time\", 'UTC'::text) AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(44)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 hour'::interval, conditions.\"time\", 'UTC'::text));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                               View \"public.conditions_summary_2\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_45.bucket,\n    _materialized_hypertable_45.temperature\n   FROM _timescaledb_internal._materialized_hypertable_45\n  WHERE _materialized_hypertable_45.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(45)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 day'::interval, conditions_summary_1.bucket, 'UTC'::text) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(45)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 day'::interval, conditions_summary_1.bucket, 'UTC'::text));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\npsql:include/cagg_on_cagg_validations.sql:71: NOTICE:  refreshing continuous aggregate \"conditions_summary_3\"\n  \\d+ :CAGG_NAME_3TH_LEVEL\n                             View \"public.conditions_summary_3\"\n Column |           Type           | Collation | Nullable | Default | Storage | Description \n--------+--------------------------+-----------+----------+---------+---------+-------------\n bucket | timestamp with time zone |           |          |         | plain   | \nView definition:\n SELECT _materialized_hypertable_46.bucket\n   FROM _timescaledb_internal._materialized_hypertable_46\n  WHERE _materialized_hypertable_46.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(46)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 mon'::interval, conditions_summary_2.bucket, 'UTC'::text) AS bucket\n   FROM conditions_summary_2\n  WHERE conditions_summary_2.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(46)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 mon'::interval, conditions_summary_2.bucket, 'UTC'::text));\n\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n            bucket            \n------------------------------\n Sat Jan 01 00:00:00 2022 UTC\n\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:79: NOTICE:  drop cascades to table _timescaledb_internal._hyper_46_43_chunk\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:86: NOTICE:  drop cascades to table _timescaledb_internal._hyper_45_42_chunk\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:87: NOTICE:  drop cascades to table _timescaledb_internal._hyper_44_41_chunk\n\\set INTERVAL_TEST FALSE\n--\n-- Variable bucket size with different timezones\n--\n\\set BUCKET_TZNAME_1ST 'US/Pacific'\n\\set BUCKET_TZNAME_2TH 'UTC'\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 day\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 month\\''\n\\set WARNING_MESSAGE '-- SHOULD WORK'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_47.bucket,\n    _materialized_hypertable_47.temperature\n   FROM _timescaledb_internal._materialized_hypertable_47\n  WHERE _materialized_hypertable_47.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(47)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 day'::interval, conditions.\"time\", 'US/Pacific'::text) AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(47)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 day'::interval, conditions.\"time\", 'US/Pacific'::text));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                               View \"public.conditions_summary_2\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_48.bucket,\n    _materialized_hypertable_48.temperature\n   FROM _timescaledb_internal._materialized_hypertable_48\n  WHERE _materialized_hypertable_48.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(48)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 mon'::interval, conditions_summary_1.bucket, 'UTC'::text) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(48)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 mon'::interval, conditions_summary_1.bucket, 'UTC'::text));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n--#Bugfix 5734 #2\n\\set INTERVAL_TEST TRUE\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 hour\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 day\\''\n\\set BUCKET_WIDTH_3TH 'INTERVAL \\'1 month\\''\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_49.bucket,\n    _materialized_hypertable_49.temperature\n   FROM _timescaledb_internal._materialized_hypertable_49\n  WHERE _materialized_hypertable_49.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(49)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 hour'::interval, conditions.\"time\", 'US/Pacific'::text) AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(49)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 hour'::interval, conditions.\"time\", 'US/Pacific'::text));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                               View \"public.conditions_summary_2\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_50.bucket,\n    _materialized_hypertable_50.temperature\n   FROM _timescaledb_internal._materialized_hypertable_50\n  WHERE _materialized_hypertable_50.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(50)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 day'::interval, conditions_summary_1.bucket, 'UTC'::text) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(50)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 day'::interval, conditions_summary_1.bucket, 'UTC'::text));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\npsql:include/cagg_on_cagg_validations.sql:71: NOTICE:  refreshing continuous aggregate \"conditions_summary_3\"\n  \\d+ :CAGG_NAME_3TH_LEVEL\n                             View \"public.conditions_summary_3\"\n Column |           Type           | Collation | Nullable | Default | Storage | Description \n--------+--------------------------+-----------+----------+---------+---------+-------------\n bucket | timestamp with time zone |           |          |         | plain   | \nView definition:\n SELECT _materialized_hypertable_51.bucket\n   FROM _timescaledb_internal._materialized_hypertable_51\n  WHERE _materialized_hypertable_51.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(51)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 mon'::interval, conditions_summary_2.bucket, 'UTC'::text) AS bucket\n   FROM conditions_summary_2\n  WHERE conditions_summary_2.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(51)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 mon'::interval, conditions_summary_2.bucket, 'UTC'::text));\n\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n            bucket            \n------------------------------\n Sat Jan 01 00:00:00 2022 UTC\n\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:79: NOTICE:  drop cascades to table _timescaledb_internal._hyper_51_46_chunk\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:86: NOTICE:  drop cascades to table _timescaledb_internal._hyper_50_45_chunk\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:87: NOTICE:  drop cascades to table _timescaledb_internal._hyper_49_44_chunk\n\\set INTERVAL_TEST FALSE\n--\n-- TZ bucket on top of non-TZ bucket\n--\n\\set IS_TIME_DIMENSION_WITH_TIMEZONE_1ST FALSE\n\\set IS_TIME_DIMENSION_WITH_TIMEZONE_2TH TRUE\n\\set BUCKET_TZNAME_2TH 'UTC'\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 day\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 month\\''\n\\set WARNING_MESSAGE '-- SHOULD WORK'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_52.bucket,\n    _materialized_hypertable_52.temperature\n   FROM _timescaledb_internal._materialized_hypertable_52\n  WHERE _materialized_hypertable_52.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(52)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 day'::interval, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(52)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 day'::interval, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                               View \"public.conditions_summary_2\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_53.bucket,\n    _materialized_hypertable_53.temperature\n   FROM _timescaledb_internal._materialized_hypertable_53\n  WHERE _materialized_hypertable_53.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(53)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 mon'::interval, conditions_summary_1.bucket, 'UTC'::text) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(53)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 mon'::interval, conditions_summary_1.bucket, 'UTC'::text));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n--#Bugfix 5734 #3\n\\set INTERVAL_TEST TRUE\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 hour\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 day\\''\n\\set BUCKET_WIDTH_3TH 'INTERVAL \\'1 month\\''\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_54.bucket,\n    _materialized_hypertable_54.temperature\n   FROM _timescaledb_internal._materialized_hypertable_54\n  WHERE _materialized_hypertable_54.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(54)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 hour'::interval, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(54)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 hour'::interval, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                               View \"public.conditions_summary_2\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_55.bucket,\n    _materialized_hypertable_55.temperature\n   FROM _timescaledb_internal._materialized_hypertable_55\n  WHERE _materialized_hypertable_55.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(55)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 day'::interval, conditions_summary_1.bucket, 'UTC'::text) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(55)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 day'::interval, conditions_summary_1.bucket, 'UTC'::text));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\npsql:include/cagg_on_cagg_validations.sql:71: NOTICE:  refreshing continuous aggregate \"conditions_summary_3\"\n  \\d+ :CAGG_NAME_3TH_LEVEL\n                             View \"public.conditions_summary_3\"\n Column |           Type           | Collation | Nullable | Default | Storage | Description \n--------+--------------------------+-----------+----------+---------+---------+-------------\n bucket | timestamp with time zone |           |          |         | plain   | \nView definition:\n SELECT _materialized_hypertable_56.bucket\n   FROM _timescaledb_internal._materialized_hypertable_56\n  WHERE _materialized_hypertable_56.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(56)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 mon'::interval, conditions_summary_2.bucket, 'UTC'::text) AS bucket\n   FROM conditions_summary_2\n  WHERE conditions_summary_2.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(56)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 mon'::interval, conditions_summary_2.bucket, 'UTC'::text));\n\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n            bucket            \n------------------------------\n Sat Jan 01 00:00:00 2022 UTC\n\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:79: NOTICE:  drop cascades to table _timescaledb_internal._hyper_56_49_chunk\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:86: NOTICE:  drop cascades to table _timescaledb_internal._hyper_55_48_chunk\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:87: NOTICE:  drop cascades to table _timescaledb_internal._hyper_54_47_chunk\n\\set INTERVAL_TEST FALSE\n--\n-- non-TZ bucket on top of TZ bucket\n--\n\\set IS_TIME_DIMENSION_WITH_TIMEZONE_1ST TRUE\n\\set IS_TIME_DIMENSION_WITH_TIMEZONE_2TH FALSE\n\\set BUCKET_TZNAME_1ST 'UTC'\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 day\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 month\\''\n\\set WARNING_MESSAGE '-- SHOULD WORK'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_57.bucket,\n    _materialized_hypertable_57.temperature\n   FROM _timescaledb_internal._materialized_hypertable_57\n  WHERE _materialized_hypertable_57.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(57)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 day'::interval, conditions.\"time\", 'UTC'::text) AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(57)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 day'::interval, conditions.\"time\", 'UTC'::text));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                               View \"public.conditions_summary_2\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_58.bucket,\n    _materialized_hypertable_58.temperature\n   FROM _timescaledb_internal._materialized_hypertable_58\n  WHERE _materialized_hypertable_58.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(58)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 mon'::interval, conditions_summary_1.bucket) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(58)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 mon'::interval, conditions_summary_1.bucket));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n-- test some intuitive intervals that should work but\n-- were not working due to unix epochs\n-- validation test for 1 year on top of one day\n-- validation test for 1 year on top of 1 month\n-- validation test for 1 year on top of 1 week\n-- bug report 5231\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 day\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 year\\''\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_59.bucket,\n    _materialized_hypertable_59.temperature\n   FROM _timescaledb_internal._materialized_hypertable_59\n  WHERE _materialized_hypertable_59.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(59)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 day'::interval, conditions.\"time\", 'UTC'::text) AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(59)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 day'::interval, conditions.\"time\", 'UTC'::text));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                               View \"public.conditions_summary_2\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_60.bucket,\n    _materialized_hypertable_60.temperature\n   FROM _timescaledb_internal._materialized_hypertable_60\n  WHERE _materialized_hypertable_60.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(60)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 year'::interval, conditions_summary_1.bucket) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(60)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 year'::interval, conditions_summary_1.bucket));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 day\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'3 month\\''\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_61.bucket,\n    _materialized_hypertable_61.temperature\n   FROM _timescaledb_internal._materialized_hypertable_61\n  WHERE _materialized_hypertable_61.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(61)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 day'::interval, conditions.\"time\", 'UTC'::text) AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(61)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 day'::interval, conditions.\"time\", 'UTC'::text));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                               View \"public.conditions_summary_2\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_62.bucket,\n    _materialized_hypertable_62.temperature\n   FROM _timescaledb_internal._materialized_hypertable_62\n  WHERE _materialized_hypertable_62.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(62)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 3 mons'::interval, conditions_summary_1.bucket) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(62)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 3 mons'::interval, conditions_summary_1.bucket));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 month\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 year\\''\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_63.bucket,\n    _materialized_hypertable_63.temperature\n   FROM _timescaledb_internal._materialized_hypertable_63\n  WHERE _materialized_hypertable_63.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(63)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 mon'::interval, conditions.\"time\", 'UTC'::text) AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(63)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 mon'::interval, conditions.\"time\", 'UTC'::text));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                               View \"public.conditions_summary_2\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_64.bucket,\n    _materialized_hypertable_64.temperature\n   FROM _timescaledb_internal._materialized_hypertable_64\n  WHERE _materialized_hypertable_64.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(64)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 year'::interval, conditions_summary_1.bucket) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(64)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 year'::interval, conditions_summary_1.bucket));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 week\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 year\\''\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_65.bucket,\n    _materialized_hypertable_65.temperature\n   FROM _timescaledb_internal._materialized_hypertable_65\n  WHERE _materialized_hypertable_65.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(65)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 7 days'::interval, conditions.\"time\", 'UTC'::text) AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(65)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 7 days'::interval, conditions.\"time\", 'UTC'::text));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\npsql:include/cagg_on_cagg_validations.sql:44: ERROR:  cannot create continuous aggregate with incompatible bucket width\nDETAIL:  Time bucket width of \"public.conditions_summary_2\" [@ 1 year] should be multiple of the time bucket width of \"public.conditions_summary_1\" [@ 7 days].\n\\d+ :CAGG_NAME_2TH_LEVEL\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:86: NOTICE:  materialized view \"conditions_summary_2\" does not exist, skipping\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 week\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 month\\''\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_66.bucket,\n    _materialized_hypertable_66.temperature\n   FROM _timescaledb_internal._materialized_hypertable_66\n  WHERE _materialized_hypertable_66.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(66)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 7 days'::interval, conditions.\"time\", 'UTC'::text) AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(66)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 7 days'::interval, conditions.\"time\", 'UTC'::text));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\npsql:include/cagg_on_cagg_validations.sql:44: ERROR:  cannot create continuous aggregate with incompatible bucket width\nDETAIL:  Time bucket width of \"public.conditions_summary_2\" [@ 1 mon] should be multiple of the time bucket width of \"public.conditions_summary_1\" [@ 7 days].\n\\d+ :CAGG_NAME_2TH_LEVEL\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:86: NOTICE:  materialized view \"conditions_summary_2\" does not exist, skipping\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n-- bug report 5277\n\\set IS_TIME_DIMENSION_WITH_TIMEZONE_1ST FALSE\n\\set IS_TIME_DIMENSION_WITH_TIMEZONE_2TH FALSE\n-- epoch plus cast to int would compute a bucket width of 0 for parent\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'146 ms\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1168 ms\\''\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_67.bucket,\n    _materialized_hypertable_67.temperature\n   FROM _timescaledb_internal._materialized_hypertable_67\n  WHERE _materialized_hypertable_67.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(67)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 0.146 secs'::interval, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(67)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 0.146 secs'::interval, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                               View \"public.conditions_summary_2\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_68.bucket,\n    _materialized_hypertable_68.temperature\n   FROM _timescaledb_internal._materialized_hypertable_68\n  WHERE _materialized_hypertable_68.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(68)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1.168 secs'::interval, conditions_summary_1.bucket) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(68)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1.168 secs'::interval, conditions_summary_1.bucket));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'9344 ms\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'74752 ms\\''\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_69.bucket,\n    _materialized_hypertable_69.temperature\n   FROM _timescaledb_internal._materialized_hypertable_69\n  WHERE _materialized_hypertable_69.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(69)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 9.344 secs'::interval, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(69)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 9.344 secs'::interval, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                               View \"public.conditions_summary_2\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_70.bucket,\n    _materialized_hypertable_70.temperature\n   FROM _timescaledb_internal._materialized_hypertable_70\n  WHERE _materialized_hypertable_70.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(70)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 min 14.752 secs'::interval, conditions_summary_1.bucket) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(70)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 min 14.752 secs'::interval, conditions_summary_1.bucket));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'74752 ms\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'598016 ms\\''\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_71.bucket,\n    _materialized_hypertable_71.temperature\n   FROM _timescaledb_internal._materialized_hypertable_71\n  WHERE _materialized_hypertable_71.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(71)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 min 14.752 secs'::interval, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(71)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 min 14.752 secs'::interval, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                               View \"public.conditions_summary_2\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_72.bucket,\n    _materialized_hypertable_72.temperature\n   FROM _timescaledb_internal._materialized_hypertable_72\n  WHERE _materialized_hypertable_72.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(72)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 9 mins 58.016 secs'::interval, conditions_summary_1.bucket) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(72)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 9 mins 58.016 secs'::interval, conditions_summary_1.bucket));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n-- test microseconds - should pass\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'146 usec\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1168 usec\\''\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_73.bucket,\n    _materialized_hypertable_73.temperature\n   FROM _timescaledb_internal._materialized_hypertable_73\n  WHERE _materialized_hypertable_73.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(73)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 0.000146 secs'::interval, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(73)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 0.000146 secs'::interval, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                               View \"public.conditions_summary_2\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_74.bucket,\n    _materialized_hypertable_74.temperature\n   FROM _timescaledb_internal._materialized_hypertable_74\n  WHERE _materialized_hypertable_74.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(74)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 0.001168 secs'::interval, conditions_summary_1.bucket) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(74)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 0.001168 secs'::interval, conditions_summary_1.bucket));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n-- test microseconds - SHOULD FAIL\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'146 usec\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1160 usec\\''\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_75.bucket,\n    _materialized_hypertable_75.temperature\n   FROM _timescaledb_internal._materialized_hypertable_75\n  WHERE _materialized_hypertable_75.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(75)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 0.000146 secs'::interval, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(75)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 0.000146 secs'::interval, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\npsql:include/cagg_on_cagg_validations.sql:44: ERROR:  cannot create continuous aggregate with incompatible bucket width\nDETAIL:  Time bucket width of \"public.conditions_summary_2\" [@ 0.00116 secs] should be multiple of the time bucket width of \"public.conditions_summary_1\" [@ 0.000146 secs].\n\\d+ :CAGG_NAME_2TH_LEVEL\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:86: NOTICE:  materialized view \"conditions_summary_2\" does not exist, skipping\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n"
  },
  {
    "path": "tsl/test/expected/cagg_on_cagg_joins.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- Global test variables\n\\set IS_TIME_DIMENSION_WITH_TIMEZONE_1ST FALSE\n\\set IS_TIME_DIMENSION_WITH_TIMEZONE_2TH FALSE\n\\set IS_JOIN TRUE\n\\set INTERVAL_TEST FALSE\n-- ########################################################\n-- ## INTEGER data type tests\n-- ########################################################\n-- Current test variables\n\\set IS_TIME_DIMENSION FALSE\n\\set TIME_DIMENSION_DATATYPE INTEGER\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2_5\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3_10\n--\n-- Run common tests for INTEGER\n--\n\\set BUCKET_WIDTH_1ST 'INTEGER \\'1\\''\n\\set BUCKET_WIDTH_2TH 'INTEGER \\'5\\''\n\\set BUCKET_WIDTH_3TH 'INTEGER \\'10\\''\n-- Different order of time dimension in raw ht\n\\set IS_DEFAULT_COLUMN_ORDER FALSE\n\\ir include/cagg_on_cagg_setup.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- CAGGs on CAGGs tests\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nDROP TABLE IF EXISTS conditions CASCADE;\npsql:include/cagg_on_cagg_setup.sql:8: NOTICE:  table \"conditions\" does not exist, skipping\n\\if :IS_DEFAULT_COLUMN_ORDER\n    CREATE TABLE conditions (\n    time :TIME_DIMENSION_DATATYPE NOT NULL,\n    temperature NUMERIC,\n    device_id INT\n  );\n\\else\n    CREATE TABLE conditions (\n    temperature NUMERIC,\n    time :TIME_DIMENSION_DATATYPE NOT NULL,\n    device_id INT\n  );\n\\endif\n\\if :IS_JOIN\n  DROP TABLE IF EXISTS devices CASCADE;\npsql:include/cagg_on_cagg_setup.sql:24: NOTICE:  table \"devices\" does not exist, skipping\n  CREATE TABLE devices ( device_id int not null, name text, location text);\n  INSERT INTO devices values (1, 'thermo_1', 'Moscow'), (2, 'thermo_2', 'Berlin'),(3, 'thermo_3', 'London'),(4, 'thermo_4', 'Stockholm');\n\\endif\n\\if :IS_TIME_DIMENSION\n  SELECT table_name FROM create_hypertable('conditions', 'time');\n\\else\n  SELECT table_name FROM create_hypertable('conditions', 'time', chunk_time_interval => 10);\n table_name \n------------\n conditions\n\n\\endif\n\\if :IS_TIME_DIMENSION\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 1);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 01:00:00-00',  5, 2);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-02 01:00:00-00', 20, 3);\n\\else\n  CREATE OR REPLACE FUNCTION integer_now()\n  RETURNS :TIME_DIMENSION_DATATYPE LANGUAGE SQL STABLE AS\n  $$\n    SELECT coalesce(max(time), 0)\n    FROM conditions\n  $$;\n  SELECT set_integer_now_func('conditions', 'integer_now');\n set_integer_now_func \n----------------------\n \n\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (1, 10, 1);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (2,  5, 2);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (5, 20, 3);\n\\endif\n\\ir include/cagg_on_cagg_common.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- CAGG on hypertable (1st level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  SUM(temperature) AS temperature,\n  device_id\nFROM conditions\nGROUP BY 1, 3\nORDER BY 1, 2, 3\nWITH NO DATA;\n-- CAGG on CAGG (2th level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  SUM(temperature) AS temperature\n\\if :IS_JOIN\n  , devices.device_id\n  , devices.name\n  FROM :CAGG_NAME_1ST_LEVEL\n  JOIN devices ON devices.device_id = :CAGG_NAME_1ST_LEVEL.device_id\n  GROUP BY 1, 3, 4\n  ORDER BY 1, 2, 3, 4\n\\else\n  FROM :CAGG_NAME_1ST_LEVEL\n  GROUP BY 1\n  ORDER BY 1, 2\n\\endif\nWITH NO DATA;\n-- CAGG on CAGG (3th level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket,\n  SUM(temperature) AS temperature\n\\if :IS_JOIN\n  , :CAGG_NAME_2TH_LEVEL.device_id\n  , :CAGG_NAME_2TH_LEVEL.name\n  , devices.location\n  FROM :CAGG_NAME_2TH_LEVEL\n  JOIN devices ON devices.device_id = :CAGG_NAME_2TH_LEVEL.device_id\n  GROUP BY 1, 3, 4, 5\n  ORDER BY 1, 2, 3, 4, 5\n\\else\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  ORDER BY 1, 2\n\\endif\nWITH NO DATA;\n-- Check chunk_interval\n\\if :IS_TIME_DIMENSION\n  SELECT h.table_name AS name, _timescaledb_functions.to_interval(d.interval_length) AS chunk_interval\n  FROM _timescaledb_catalog.hypertable h\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = h.id\n  WHERE h.table_name = 'conditions'\n  UNION ALL\n  SELECT c.user_view_name AS name, _timescaledb_functions.to_interval(d.interval_length) AS chunk_interval\n  FROM _timescaledb_catalog.continuous_agg c\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = c.mat_hypertable_id\n  WHERE c.user_view_name IN (:'CAGG_NAME_1ST_LEVEL', :'CAGG_NAME_2TH_LEVEL', :'CAGG_NAME_3TH_LEVEL')\n  ORDER BY 1, 2;\n\\else\n  SELECT h.table_name AS name, d.interval_length AS chunk_interval\n  FROM _timescaledb_catalog.hypertable h\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = h.id\n  WHERE h.table_name = 'conditions'\n  UNION ALL\n  SELECT c.user_view_name AS name, d.interval_length AS chunk_interval\n  FROM _timescaledb_catalog.continuous_agg c\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = c.mat_hypertable_id\n  WHERE c.user_view_name IN (:'CAGG_NAME_1ST_LEVEL', :'CAGG_NAME_2TH_LEVEL', :'CAGG_NAME_3TH_LEVEL')\n  ORDER BY 1, 2;\n          name           | chunk_interval \n-------------------------+----------------\n conditions              |             10\n conditions_summary_1_1  |            100\n conditions_summary_2_5  |            100\n conditions_summary_3_10 |            100\n\n\\endif\n-- No data because the CAGGs are just for materialized data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id | name \n--------+-------------+-----------+------\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature | device_id | name | location \n--------+-------------+-----------+------+----------\n\n-- Turn CAGGs into Realtime\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=false);\n-- Realtime data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           5 |         2\n      5 |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id |   name   \n--------+-------------+-----------+----------\n      0 |           5 |         2 | thermo_2\n      0 |          10 |         1 | thermo_1\n      5 |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature | device_id |   name   | location \n--------+-------------+-----------+----------+----------\n      0 |           5 |         2 | thermo_2 | Berlin\n      0 |          10 |         1 | thermo_1 | Moscow\n      0 |          20 |         3 | thermo_3 | London\n\n-- Turn CAGGs into materialized only again\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=true);\n-- Refresh all data\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Materialized data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           5 |         2\n      5 |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id |   name   \n--------+-------------+-----------+----------\n      0 |           5 |         2 | thermo_2\n      0 |          10 |         1 | thermo_1\n      5 |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature | device_id |   name   | location \n--------+-------------+-----------+----------+----------\n      0 |           5 |         2 | thermo_2 | Berlin\n      0 |          10 |         1 | thermo_1 | Moscow\n      0 |          20 |         3 | thermo_3 | London\n\n\\if :IS_TIME_DIMENSION\n-- Invalidate an old region\nINSERT INTO conditions (\"time\", temperature) VALUES ('2022-01-01 01:00:00-00'::timestamptz, 2);\n-- New region\nINSERT INTO conditions (\"time\", temperature) VALUES ('2022-01-03 01:00:00-00'::timestamptz, 2);\n\\else\n-- Invalidate an old region\nINSERT INTO conditions (\"time\", temperature) VALUES (2,  2);\n-- New region\nINSERT INTO conditions (\"time\", temperature) VALUES (10, 2);\n\\endif\n-- No changes\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           5 |         2\n      5 |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id |   name   \n--------+-------------+-----------+----------\n      0 |           5 |         2 | thermo_2\n      0 |          10 |         1 | thermo_1\n      5 |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature | device_id |   name   | location \n--------+-------------+-----------+----------+----------\n      0 |           5 |         2 | thermo_2 | Berlin\n      0 |          10 |         1 | thermo_1 | Moscow\n      0 |          20 |         3 | thermo_3 | London\n\n-- Turn CAGGs into Realtime\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=false);\n-- Realtime changes, just new region\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           5 |         2\n      5 |          20 |         3\n     10 |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id |   name   \n--------+-------------+-----------+----------\n      0 |           5 |         2 | thermo_2\n      0 |          10 |         1 | thermo_1\n      5 |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature | device_id |   name   | location \n--------+-------------+-----------+----------+----------\n      0 |           5 |         2 | thermo_2 | Berlin\n      0 |          10 |         1 | thermo_1 | Moscow\n      0 |          20 |         3 | thermo_3 | London\n\n-- Turn CAGGs into materialized only again\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=true);\n-- Refresh all data\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- All changes are materialized\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           2 |          \n      2 |           5 |         2\n      5 |          20 |         3\n     10 |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id |   name   \n--------+-------------+-----------+----------\n      0 |           5 |         2 | thermo_2\n      0 |          10 |         1 | thermo_1\n      5 |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature | device_id |   name   | location \n--------+-------------+-----------+----------+----------\n      0 |           5 |         2 | thermo_2 | Berlin\n      0 |          10 |         1 | thermo_1 | Moscow\n      0 |          20 |         3 | thermo_3 | London\n\n-- TRUNCATE tests\nTRUNCATE :CAGG_NAME_2TH_LEVEL;\n-- This full refresh will remove all the data from the 3TH level cagg\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Should return no rows\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id | name \n--------+-------------+-----------+------\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature | device_id | name | location \n--------+-------------+-----------+------+----------\n\n-- If we have all the data in the bottom levels caggs we can rebuild\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Now we have all the data\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id |   name   \n--------+-------------+-----------+----------\n      0 |           5 |         2 | thermo_2\n      0 |          10 |         1 | thermo_1\n      5 |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature | device_id |   name   | location \n--------+-------------+-----------+----------+----------\n      0 |           5 |         2 | thermo_2 | Berlin\n      0 |          10 |         1 | thermo_1 | Moscow\n      0 |          20 |         3 | thermo_3 | London\n\n-- DROP tests\n\\set ON_ERROR_STOP 0\n-- should error because it depends of other CAGGs\nDROP MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:172: ERROR:  cannot drop view conditions_summary_1_1 because other objects depend on it\nDROP MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:173: ERROR:  cannot drop view conditions_summary_2_5 because other objects depend on it\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\npsql:include/cagg_on_cagg_common.sql:174: NOTICE:  continuous aggregate \"conditions_summary_1_1\" is already up-to-date\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\npsql:include/cagg_on_cagg_common.sql:175: NOTICE:  continuous aggregate \"conditions_summary_2_5\" is already up-to-date\n\\set ON_ERROR_STOP 1\n-- DROP the 3TH level CAGG don't affect others\nDROP MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:179: NOTICE:  drop cascades to table _timescaledb_internal._hyper_4_4_chunk\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:182: ERROR:  relation \"conditions_summary_3_10\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- should work because dropping the top level CAGG\n-- don't affect the down level CAGGs\nTRUNCATE :CAGG_NAME_2TH_LEVEL,:CAGG_NAME_1ST_LEVEL;\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           2 |          \n      2 |           5 |         2\n      5 |          20 |         3\n     10 |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id | name \n--------+-------------+-----------+------\n\n-- DROP the 2TH level CAGG don't affect others\nDROP MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL;\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:196: ERROR:  relation \"conditions_summary_2_5\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- should work because dropping the top level CAGG\n-- don't affect the down level CAGGs\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           2 |          \n      2 |           5 |         2\n      5 |          20 |         3\n     10 |           2 |          \n\n-- DROP the first CAGG should work\nDROP MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:203: NOTICE:  drop cascades to table _timescaledb_internal._hyper_2_7_chunk\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:206: ERROR:  relation \"conditions_summary_1_1\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- Default tests\n\\set ON_ERROR_STOP 0\n\\set IS_DEFAULT_COLUMN_ORDER TRUE\n\\ir include/cagg_on_cagg_setup.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- CAGGs on CAGGs tests\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nDROP TABLE IF EXISTS conditions CASCADE;\n\\if :IS_DEFAULT_COLUMN_ORDER\n    CREATE TABLE conditions (\n    time :TIME_DIMENSION_DATATYPE NOT NULL,\n    temperature NUMERIC,\n    device_id INT\n  );\n\\else\n    CREATE TABLE conditions (\n    temperature NUMERIC,\n    time :TIME_DIMENSION_DATATYPE NOT NULL,\n    device_id INT\n  );\n\\endif\n\\if :IS_JOIN\n  DROP TABLE IF EXISTS devices CASCADE;\n  CREATE TABLE devices ( device_id int not null, name text, location text);\n  INSERT INTO devices values (1, 'thermo_1', 'Moscow'), (2, 'thermo_2', 'Berlin'),(3, 'thermo_3', 'London'),(4, 'thermo_4', 'Stockholm');\n\\endif\n\\if :IS_TIME_DIMENSION\n  SELECT table_name FROM create_hypertable('conditions', 'time');\n\\else\n  SELECT table_name FROM create_hypertable('conditions', 'time', chunk_time_interval => 10);\n table_name \n------------\n conditions\n\n\\endif\n\\if :IS_TIME_DIMENSION\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 1);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 01:00:00-00',  5, 2);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-02 01:00:00-00', 20, 3);\n\\else\n  CREATE OR REPLACE FUNCTION integer_now()\n  RETURNS :TIME_DIMENSION_DATATYPE LANGUAGE SQL STABLE AS\n  $$\n    SELECT coalesce(max(time), 0)\n    FROM conditions\n  $$;\n  SELECT set_integer_now_func('conditions', 'integer_now');\n set_integer_now_func \n----------------------\n \n\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (1, 10, 1);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (2,  5, 2);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (5, 20, 3);\n\\endif\n\\ir include/cagg_on_cagg_common.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- CAGG on hypertable (1st level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  SUM(temperature) AS temperature,\n  device_id\nFROM conditions\nGROUP BY 1, 3\nORDER BY 1, 2, 3\nWITH NO DATA;\n-- CAGG on CAGG (2th level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  SUM(temperature) AS temperature\n\\if :IS_JOIN\n  , devices.device_id\n  , devices.name\n  FROM :CAGG_NAME_1ST_LEVEL\n  JOIN devices ON devices.device_id = :CAGG_NAME_1ST_LEVEL.device_id\n  GROUP BY 1, 3, 4\n  ORDER BY 1, 2, 3, 4\n\\else\n  FROM :CAGG_NAME_1ST_LEVEL\n  GROUP BY 1\n  ORDER BY 1, 2\n\\endif\nWITH NO DATA;\n-- CAGG on CAGG (3th level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket,\n  SUM(temperature) AS temperature\n\\if :IS_JOIN\n  , :CAGG_NAME_2TH_LEVEL.device_id\n  , :CAGG_NAME_2TH_LEVEL.name\n  , devices.location\n  FROM :CAGG_NAME_2TH_LEVEL\n  JOIN devices ON devices.device_id = :CAGG_NAME_2TH_LEVEL.device_id\n  GROUP BY 1, 3, 4, 5\n  ORDER BY 1, 2, 3, 4, 5\n\\else\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  ORDER BY 1, 2\n\\endif\nWITH NO DATA;\n-- Check chunk_interval\n\\if :IS_TIME_DIMENSION\n  SELECT h.table_name AS name, _timescaledb_functions.to_interval(d.interval_length) AS chunk_interval\n  FROM _timescaledb_catalog.hypertable h\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = h.id\n  WHERE h.table_name = 'conditions'\n  UNION ALL\n  SELECT c.user_view_name AS name, _timescaledb_functions.to_interval(d.interval_length) AS chunk_interval\n  FROM _timescaledb_catalog.continuous_agg c\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = c.mat_hypertable_id\n  WHERE c.user_view_name IN (:'CAGG_NAME_1ST_LEVEL', :'CAGG_NAME_2TH_LEVEL', :'CAGG_NAME_3TH_LEVEL')\n  ORDER BY 1, 2;\n\\else\n  SELECT h.table_name AS name, d.interval_length AS chunk_interval\n  FROM _timescaledb_catalog.hypertable h\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = h.id\n  WHERE h.table_name = 'conditions'\n  UNION ALL\n  SELECT c.user_view_name AS name, d.interval_length AS chunk_interval\n  FROM _timescaledb_catalog.continuous_agg c\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = c.mat_hypertable_id\n  WHERE c.user_view_name IN (:'CAGG_NAME_1ST_LEVEL', :'CAGG_NAME_2TH_LEVEL', :'CAGG_NAME_3TH_LEVEL')\n  ORDER BY 1, 2;\n          name           | chunk_interval \n-------------------------+----------------\n conditions              |             10\n conditions_summary_1_1  |            100\n conditions_summary_2_5  |            100\n conditions_summary_3_10 |            100\n\n\\endif\n-- No data because the CAGGs are just for materialized data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id | name \n--------+-------------+-----------+------\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature | device_id | name | location \n--------+-------------+-----------+------+----------\n\n-- Turn CAGGs into Realtime\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=false);\n-- Realtime data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           5 |         2\n      5 |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id |   name   \n--------+-------------+-----------+----------\n      0 |           5 |         2 | thermo_2\n      0 |          10 |         1 | thermo_1\n      5 |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature | device_id |   name   | location \n--------+-------------+-----------+----------+----------\n      0 |           5 |         2 | thermo_2 | Berlin\n      0 |          10 |         1 | thermo_1 | Moscow\n      0 |          20 |         3 | thermo_3 | London\n\n-- Turn CAGGs into materialized only again\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=true);\n-- Refresh all data\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Materialized data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           5 |         2\n      5 |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id |   name   \n--------+-------------+-----------+----------\n      0 |           5 |         2 | thermo_2\n      0 |          10 |         1 | thermo_1\n      5 |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature | device_id |   name   | location \n--------+-------------+-----------+----------+----------\n      0 |           5 |         2 | thermo_2 | Berlin\n      0 |          10 |         1 | thermo_1 | Moscow\n      0 |          20 |         3 | thermo_3 | London\n\n\\if :IS_TIME_DIMENSION\n-- Invalidate an old region\nINSERT INTO conditions (\"time\", temperature) VALUES ('2022-01-01 01:00:00-00'::timestamptz, 2);\n-- New region\nINSERT INTO conditions (\"time\", temperature) VALUES ('2022-01-03 01:00:00-00'::timestamptz, 2);\n\\else\n-- Invalidate an old region\nINSERT INTO conditions (\"time\", temperature) VALUES (2,  2);\n-- New region\nINSERT INTO conditions (\"time\", temperature) VALUES (10, 2);\n\\endif\n-- No changes\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           5 |         2\n      5 |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id |   name   \n--------+-------------+-----------+----------\n      0 |           5 |         2 | thermo_2\n      0 |          10 |         1 | thermo_1\n      5 |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature | device_id |   name   | location \n--------+-------------+-----------+----------+----------\n      0 |           5 |         2 | thermo_2 | Berlin\n      0 |          10 |         1 | thermo_1 | Moscow\n      0 |          20 |         3 | thermo_3 | London\n\n-- Turn CAGGs into Realtime\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=false);\n-- Realtime changes, just new region\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           5 |         2\n      5 |          20 |         3\n     10 |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id |   name   \n--------+-------------+-----------+----------\n      0 |           5 |         2 | thermo_2\n      0 |          10 |         1 | thermo_1\n      5 |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature | device_id |   name   | location \n--------+-------------+-----------+----------+----------\n      0 |           5 |         2 | thermo_2 | Berlin\n      0 |          10 |         1 | thermo_1 | Moscow\n      0 |          20 |         3 | thermo_3 | London\n\n-- Turn CAGGs into materialized only again\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=true);\n-- Refresh all data\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- All changes are materialized\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           2 |          \n      2 |           5 |         2\n      5 |          20 |         3\n     10 |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id |   name   \n--------+-------------+-----------+----------\n      0 |           5 |         2 | thermo_2\n      0 |          10 |         1 | thermo_1\n      5 |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature | device_id |   name   | location \n--------+-------------+-----------+----------+----------\n      0 |           5 |         2 | thermo_2 | Berlin\n      0 |          10 |         1 | thermo_1 | Moscow\n      0 |          20 |         3 | thermo_3 | London\n\n-- TRUNCATE tests\nTRUNCATE :CAGG_NAME_2TH_LEVEL;\n-- This full refresh will remove all the data from the 3TH level cagg\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Should return no rows\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id | name \n--------+-------------+-----------+------\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature | device_id | name | location \n--------+-------------+-----------+------+----------\n\n-- If we have all the data in the bottom levels caggs we can rebuild\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Now we have all the data\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id |   name   \n--------+-------------+-----------+----------\n      0 |           5 |         2 | thermo_2\n      0 |          10 |         1 | thermo_1\n      5 |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature | device_id |   name   | location \n--------+-------------+-----------+----------+----------\n      0 |           5 |         2 | thermo_2 | Berlin\n      0 |          10 |         1 | thermo_1 | Moscow\n      0 |          20 |         3 | thermo_3 | London\n\n-- DROP tests\n\\set ON_ERROR_STOP 0\n-- should error because it depends of other CAGGs\nDROP MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:172: ERROR:  cannot drop view conditions_summary_1_1 because other objects depend on it\nDROP MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:173: ERROR:  cannot drop view conditions_summary_2_5 because other objects depend on it\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\npsql:include/cagg_on_cagg_common.sql:174: NOTICE:  continuous aggregate \"conditions_summary_1_1\" is already up-to-date\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\npsql:include/cagg_on_cagg_common.sql:175: NOTICE:  continuous aggregate \"conditions_summary_2_5\" is already up-to-date\n\\set ON_ERROR_STOP 1\n-- DROP the 3TH level CAGG don't affect others\nDROP MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:179: NOTICE:  drop cascades to table _timescaledb_internal._hyper_8_11_chunk\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:182: ERROR:  relation \"conditions_summary_3_10\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- should work because dropping the top level CAGG\n-- don't affect the down level CAGGs\nTRUNCATE :CAGG_NAME_2TH_LEVEL,:CAGG_NAME_1ST_LEVEL;\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           2 |          \n      2 |           5 |         2\n      5 |          20 |         3\n     10 |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id | name \n--------+-------------+-----------+------\n\n-- DROP the 2TH level CAGG don't affect others\nDROP MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL;\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:196: ERROR:  relation \"conditions_summary_2_5\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- should work because dropping the top level CAGG\n-- don't affect the down level CAGGs\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n      1 |          10 |         1\n      2 |           2 |          \n      2 |           5 |         2\n      5 |          20 |         3\n     10 |           2 |          \n\n-- DROP the first CAGG should work\nDROP MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:203: NOTICE:  drop cascades to table _timescaledb_internal._hyper_6_14_chunk\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:206: ERROR:  relation \"conditions_summary_1_1\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n--\n-- Validation test for non-multiple bucket sizes\n--\n\\set ON_ERROR_STOP 0\n\\set BUCKET_WIDTH_1ST 'INTEGER \\'2\\''\n\\set BUCKET_WIDTH_2TH 'INTEGER \\'5\\''\n\\set WARNING_MESSAGE '-- SHOULD ERROR because non-multiple bucket sizes'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                       View \"public.conditions_summary_1\"\n   Column    |  Type   | Collation | Nullable | Default | Storage | Description \n-------------+---------+-----------+----------+---------+---------+-------------\n bucket      | integer |           |          |         | plain   | \n temperature | numeric |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_9.bucket,\n    _materialized_hypertable_9.temperature\n   FROM _timescaledb_internal._materialized_hypertable_9\n  WHERE _materialized_hypertable_9.bucket < COALESCE(_timescaledb_functions.cagg_watermark(9)::integer, '-2147483648'::integer)\nUNION ALL\n SELECT time_bucket(2, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.cagg_watermark(9)::integer, '-2147483648'::integer)\n  GROUP BY (time_bucket(2, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD ERROR because non-multiple bucket sizes\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\npsql:include/cagg_on_cagg_validations.sql:44: ERROR:  cannot create continuous aggregate with incompatible bucket width\nDETAIL:  Time bucket width of \"public.conditions_summary_2\" [5] should be multiple of the time bucket width of \"public.conditions_summary_1\" [2].\n\\d+ :CAGG_NAME_2TH_LEVEL\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:86: NOTICE:  materialized view \"conditions_summary_2\" does not exist, skipping\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n--\n-- Validation test for equal bucket sizes\n--\n\\set ON_ERROR_STOP 0\n\\set BUCKET_WIDTH_1ST 'INTEGER \\'2\\''\n\\set BUCKET_WIDTH_2TH 'INTEGER \\'2\\''\n\\set WARNING_MESSAGE 'SHOULD WORK because new bucket should be greater than previous'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                       View \"public.conditions_summary_1\"\n   Column    |  Type   | Collation | Nullable | Default | Storage | Description \n-------------+---------+-----------+----------+---------+---------+-------------\n bucket      | integer |           |          |         | plain   | \n temperature | numeric |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_10.bucket,\n    _materialized_hypertable_10.temperature\n   FROM _timescaledb_internal._materialized_hypertable_10\n  WHERE _materialized_hypertable_10.bucket < COALESCE(_timescaledb_functions.cagg_watermark(10)::integer, '-2147483648'::integer)\nUNION ALL\n SELECT time_bucket(2, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.cagg_watermark(10)::integer, '-2147483648'::integer)\n  GROUP BY (time_bucket(2, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\nSHOULD WORK because new bucket should be greater than previous\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                       View \"public.conditions_summary_2\"\n   Column    |  Type   | Collation | Nullable | Default | Storage | Description \n-------------+---------+-----------+----------+---------+---------+-------------\n bucket      | integer |           |          |         | plain   | \n temperature | numeric |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_11.bucket,\n    _materialized_hypertable_11.temperature\n   FROM _timescaledb_internal._materialized_hypertable_11\n  WHERE _materialized_hypertable_11.bucket < COALESCE(_timescaledb_functions.cagg_watermark(11)::integer, '-2147483648'::integer)\nUNION ALL\n SELECT time_bucket(2, conditions_summary_1.bucket) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.cagg_watermark(11)::integer, '-2147483648'::integer)\n  GROUP BY (time_bucket(2, conditions_summary_1.bucket));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n--\n-- Validation test for bucket size less than source\n--\n\\set ON_ERROR_STOP 0\n\\set BUCKET_WIDTH_1ST 'INTEGER \\'4\\''\n\\set BUCKET_WIDTH_2TH 'INTEGER \\'2\\''\n\\set WARNING_MESSAGE '-- SHOULD ERROR because new bucket should be greater than previous'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                       View \"public.conditions_summary_1\"\n   Column    |  Type   | Collation | Nullable | Default | Storage | Description \n-------------+---------+-----------+----------+---------+---------+-------------\n bucket      | integer |           |          |         | plain   | \n temperature | numeric |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_12.bucket,\n    _materialized_hypertable_12.temperature\n   FROM _timescaledb_internal._materialized_hypertable_12\n  WHERE _materialized_hypertable_12.bucket < COALESCE(_timescaledb_functions.cagg_watermark(12)::integer, '-2147483648'::integer)\nUNION ALL\n SELECT time_bucket(4, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.cagg_watermark(12)::integer, '-2147483648'::integer)\n  GROUP BY (time_bucket(4, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD ERROR because new bucket should be greater than previous\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\npsql:include/cagg_on_cagg_validations.sql:44: ERROR:  cannot create continuous aggregate with incompatible bucket width\nDETAIL:  Time bucket width of \"public.conditions_summary_2\" [2] should be greater or equal than the time bucket width of \"public.conditions_summary_1\" [4].\n\\d+ :CAGG_NAME_2TH_LEVEL\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:86: NOTICE:  materialized view \"conditions_summary_2\" does not exist, skipping\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n-- ########################################################\n-- ## TIMESTAMP data type tests\n-- ########################################################\n-- Current test variables\n\\set IS_TIME_DIMENSION TRUE\n\\set TIME_DIMENSION_DATATYPE TIMESTAMP\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1_hourly\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2_daily\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3_weekly\n\\set IS_JOIN TRUE\nSET timezone TO 'UTC';\n--\n-- Run common tests for TIMESTAMP\n--\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 hour\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 day\\''\n\\set BUCKET_WIDTH_3TH 'INTERVAL \\'1 week\\''\n-- Different order of time dimension in raw ht\n\\set IS_DEFAULT_COLUMN_ORDER FALSE\n\\ir include/cagg_on_cagg_setup.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- CAGGs on CAGGs tests\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nDROP TABLE IF EXISTS conditions CASCADE;\n\\if :IS_DEFAULT_COLUMN_ORDER\n    CREATE TABLE conditions (\n    time :TIME_DIMENSION_DATATYPE NOT NULL,\n    temperature NUMERIC,\n    device_id INT\n  );\n\\else\n    CREATE TABLE conditions (\n    temperature NUMERIC,\n    time :TIME_DIMENSION_DATATYPE NOT NULL,\n    device_id INT\n  );\n\\endif\n\\if :IS_JOIN\n  DROP TABLE IF EXISTS devices CASCADE;\n  CREATE TABLE devices ( device_id int not null, name text, location text);\n  INSERT INTO devices values (1, 'thermo_1', 'Moscow'), (2, 'thermo_2', 'Berlin'),(3, 'thermo_3', 'London'),(4, 'thermo_4', 'Stockholm');\n\\endif\n\\if :IS_TIME_DIMENSION\n  SELECT table_name FROM create_hypertable('conditions', 'time');\npsql:include/cagg_on_cagg_setup.sql:30: WARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n table_name \n------------\n conditions\n\n\\else\n  SELECT table_name FROM create_hypertable('conditions', 'time', chunk_time_interval => 10);\n\\endif\n\\if :IS_TIME_DIMENSION\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 1);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 01:00:00-00',  5, 2);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-02 01:00:00-00', 20, 3);\n\\else\n  CREATE OR REPLACE FUNCTION integer_now()\n  RETURNS :TIME_DIMENSION_DATATYPE LANGUAGE SQL STABLE AS\n  $$\n    SELECT coalesce(max(time), 0)\n    FROM conditions\n  $$;\n  SELECT set_integer_now_func('conditions', 'integer_now');\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (1, 10, 1);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (2,  5, 2);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (5, 20, 3);\n\\endif\n\\ir include/cagg_on_cagg_common.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- CAGG on hypertable (1st level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  SUM(temperature) AS temperature,\n  device_id\nFROM conditions\nGROUP BY 1, 3\nORDER BY 1, 2, 3\nWITH NO DATA;\n-- CAGG on CAGG (2th level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  SUM(temperature) AS temperature\n\\if :IS_JOIN\n  , devices.device_id\n  , devices.name\n  FROM :CAGG_NAME_1ST_LEVEL\n  JOIN devices ON devices.device_id = :CAGG_NAME_1ST_LEVEL.device_id\n  GROUP BY 1, 3, 4\n  ORDER BY 1, 2, 3, 4\n\\else\n  FROM :CAGG_NAME_1ST_LEVEL\n  GROUP BY 1\n  ORDER BY 1, 2\n\\endif\nWITH NO DATA;\n-- CAGG on CAGG (3th level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket,\n  SUM(temperature) AS temperature\n\\if :IS_JOIN\n  , :CAGG_NAME_2TH_LEVEL.device_id\n  , :CAGG_NAME_2TH_LEVEL.name\n  , devices.location\n  FROM :CAGG_NAME_2TH_LEVEL\n  JOIN devices ON devices.device_id = :CAGG_NAME_2TH_LEVEL.device_id\n  GROUP BY 1, 3, 4, 5\n  ORDER BY 1, 2, 3, 4, 5\n\\else\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  ORDER BY 1, 2\n\\endif\nWITH NO DATA;\n-- Check chunk_interval\n\\if :IS_TIME_DIMENSION\n  SELECT h.table_name AS name, _timescaledb_functions.to_interval(d.interval_length) AS chunk_interval\n  FROM _timescaledb_catalog.hypertable h\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = h.id\n  WHERE h.table_name = 'conditions'\n  UNION ALL\n  SELECT c.user_view_name AS name, _timescaledb_functions.to_interval(d.interval_length) AS chunk_interval\n  FROM _timescaledb_catalog.continuous_agg c\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = c.mat_hypertable_id\n  WHERE c.user_view_name IN (:'CAGG_NAME_1ST_LEVEL', :'CAGG_NAME_2TH_LEVEL', :'CAGG_NAME_3TH_LEVEL')\n  ORDER BY 1, 2;\n            name             | chunk_interval \n-----------------------------+----------------\n conditions                  | @ 7 days\n conditions_summary_1_hourly | @ 70 days\n conditions_summary_2_daily  | @ 70 days\n conditions_summary_3_weekly | @ 70 days\n\n\\else\n  SELECT h.table_name AS name, d.interval_length AS chunk_interval\n  FROM _timescaledb_catalog.hypertable h\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = h.id\n  WHERE h.table_name = 'conditions'\n  UNION ALL\n  SELECT c.user_view_name AS name, d.interval_length AS chunk_interval\n  FROM _timescaledb_catalog.continuous_agg c\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = c.mat_hypertable_id\n  WHERE c.user_view_name IN (:'CAGG_NAME_1ST_LEVEL', :'CAGG_NAME_2TH_LEVEL', :'CAGG_NAME_3TH_LEVEL')\n  ORDER BY 1, 2;\n\\endif\n-- No data because the CAGGs are just for materialized data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id | name \n--------+-------------+-----------+------\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature | device_id | name | location \n--------+-------------+-----------+------+----------\n\n-- Turn CAGGs into Realtime\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=false);\n-- Realtime data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n          bucket          | temperature | device_id |   name   \n--------------------------+-------------+-----------+----------\n Sat Jan 01 00:00:00 2022 |           5 |         2 | thermo_2\n Sat Jan 01 00:00:00 2022 |          10 |         1 | thermo_1\n Sun Jan 02 00:00:00 2022 |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n          bucket          | temperature | device_id |   name   | location \n--------------------------+-------------+-----------+----------+----------\n Mon Dec 27 00:00:00 2021 |           5 |         2 | thermo_2 | Berlin\n Mon Dec 27 00:00:00 2021 |          10 |         1 | thermo_1 | Moscow\n Mon Dec 27 00:00:00 2021 |          20 |         3 | thermo_3 | London\n\n-- Turn CAGGs into materialized only again\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=true);\n-- Refresh all data\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Materialized data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n          bucket          | temperature | device_id |   name   \n--------------------------+-------------+-----------+----------\n Sat Jan 01 00:00:00 2022 |           5 |         2 | thermo_2\n Sat Jan 01 00:00:00 2022 |          10 |         1 | thermo_1\n Sun Jan 02 00:00:00 2022 |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n          bucket          | temperature | device_id |   name   | location \n--------------------------+-------------+-----------+----------+----------\n Mon Dec 27 00:00:00 2021 |           5 |         2 | thermo_2 | Berlin\n Mon Dec 27 00:00:00 2021 |          10 |         1 | thermo_1 | Moscow\n Mon Dec 27 00:00:00 2021 |          20 |         3 | thermo_3 | London\n\n\\if :IS_TIME_DIMENSION\n-- Invalidate an old region\nINSERT INTO conditions (\"time\", temperature) VALUES ('2022-01-01 01:00:00-00'::timestamptz, 2);\n-- New region\nINSERT INTO conditions (\"time\", temperature) VALUES ('2022-01-03 01:00:00-00'::timestamptz, 2);\n\\else\n-- Invalidate an old region\nINSERT INTO conditions (\"time\", temperature) VALUES (2,  2);\n-- New region\nINSERT INTO conditions (\"time\", temperature) VALUES (10, 2);\n\\endif\n-- No changes\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n          bucket          | temperature | device_id |   name   \n--------------------------+-------------+-----------+----------\n Sat Jan 01 00:00:00 2022 |           5 |         2 | thermo_2\n Sat Jan 01 00:00:00 2022 |          10 |         1 | thermo_1\n Sun Jan 02 00:00:00 2022 |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n          bucket          | temperature | device_id |   name   | location \n--------------------------+-------------+-----------+----------+----------\n Mon Dec 27 00:00:00 2021 |           5 |         2 | thermo_2 | Berlin\n Mon Dec 27 00:00:00 2021 |          10 |         1 | thermo_1 | Moscow\n Mon Dec 27 00:00:00 2021 |          20 |         3 | thermo_3 | London\n\n-- Turn CAGGs into Realtime\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=false);\n-- Realtime changes, just new region\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n Mon Jan 03 01:00:00 2022 |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n          bucket          | temperature | device_id |   name   \n--------------------------+-------------+-----------+----------\n Sat Jan 01 00:00:00 2022 |           5 |         2 | thermo_2\n Sat Jan 01 00:00:00 2022 |          10 |         1 | thermo_1\n Sun Jan 02 00:00:00 2022 |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n          bucket          | temperature | device_id |   name   | location \n--------------------------+-------------+-----------+----------+----------\n Mon Dec 27 00:00:00 2021 |           5 |         2 | thermo_2 | Berlin\n Mon Dec 27 00:00:00 2021 |          10 |         1 | thermo_1 | Moscow\n Mon Dec 27 00:00:00 2021 |          20 |         3 | thermo_3 | London\n\n-- Turn CAGGs into materialized only again\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=true);\n-- Refresh all data\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- All changes are materialized\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           2 |          \n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n Mon Jan 03 01:00:00 2022 |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n          bucket          | temperature | device_id |   name   \n--------------------------+-------------+-----------+----------\n Sat Jan 01 00:00:00 2022 |           5 |         2 | thermo_2\n Sat Jan 01 00:00:00 2022 |          10 |         1 | thermo_1\n Sun Jan 02 00:00:00 2022 |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n          bucket          | temperature | device_id |   name   | location \n--------------------------+-------------+-----------+----------+----------\n Mon Dec 27 00:00:00 2021 |           5 |         2 | thermo_2 | Berlin\n Mon Dec 27 00:00:00 2021 |          10 |         1 | thermo_1 | Moscow\n Mon Dec 27 00:00:00 2021 |          20 |         3 | thermo_3 | London\n\n-- TRUNCATE tests\nTRUNCATE :CAGG_NAME_2TH_LEVEL;\n-- This full refresh will remove all the data from the 3TH level cagg\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Should return no rows\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id | name \n--------+-------------+-----------+------\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature | device_id | name | location \n--------+-------------+-----------+------+----------\n\n-- If we have all the data in the bottom levels caggs we can rebuild\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Now we have all the data\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n          bucket          | temperature | device_id |   name   \n--------------------------+-------------+-----------+----------\n Sat Jan 01 00:00:00 2022 |           5 |         2 | thermo_2\n Sat Jan 01 00:00:00 2022 |          10 |         1 | thermo_1\n Sun Jan 02 00:00:00 2022 |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n          bucket          | temperature | device_id |   name   | location \n--------------------------+-------------+-----------+----------+----------\n Mon Dec 27 00:00:00 2021 |           5 |         2 | thermo_2 | Berlin\n Mon Dec 27 00:00:00 2021 |          10 |         1 | thermo_1 | Moscow\n Mon Dec 27 00:00:00 2021 |          20 |         3 | thermo_3 | London\n\n-- DROP tests\n\\set ON_ERROR_STOP 0\n-- should error because it depends of other CAGGs\nDROP MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:172: ERROR:  cannot drop view conditions_summary_1_hourly because other objects depend on it\nDROP MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:173: ERROR:  cannot drop view conditions_summary_2_daily because other objects depend on it\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\npsql:include/cagg_on_cagg_common.sql:174: NOTICE:  continuous aggregate \"conditions_summary_1_hourly\" is already up-to-date\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\npsql:include/cagg_on_cagg_common.sql:175: NOTICE:  continuous aggregate \"conditions_summary_2_daily\" is already up-to-date\n\\set ON_ERROR_STOP 1\n-- DROP the 3TH level CAGG don't affect others\nDROP MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:179: NOTICE:  drop cascades to table _timescaledb_internal._hyper_16_18_chunk\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:182: ERROR:  relation \"conditions_summary_3_weekly\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- should work because dropping the top level CAGG\n-- don't affect the down level CAGGs\nTRUNCATE :CAGG_NAME_2TH_LEVEL,:CAGG_NAME_1ST_LEVEL;\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           2 |          \n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n Mon Jan 03 01:00:00 2022 |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id | name \n--------+-------------+-----------+------\n\n-- DROP the 2TH level CAGG don't affect others\nDROP MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL;\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:196: ERROR:  relation \"conditions_summary_2_daily\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- should work because dropping the top level CAGG\n-- don't affect the down level CAGGs\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           2 |          \n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n Mon Jan 03 01:00:00 2022 |           2 |          \n\n-- DROP the first CAGG should work\nDROP MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:203: NOTICE:  drop cascades to table _timescaledb_internal._hyper_14_20_chunk\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:206: ERROR:  relation \"conditions_summary_1_hourly\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- Default tests\n\\set IS_DEFAULT_COLUMN_ORDER TRUE\n\\ir include/cagg_on_cagg_setup.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- CAGGs on CAGGs tests\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nDROP TABLE IF EXISTS conditions CASCADE;\n\\if :IS_DEFAULT_COLUMN_ORDER\n    CREATE TABLE conditions (\n    time :TIME_DIMENSION_DATATYPE NOT NULL,\n    temperature NUMERIC,\n    device_id INT\n  );\n\\else\n    CREATE TABLE conditions (\n    temperature NUMERIC,\n    time :TIME_DIMENSION_DATATYPE NOT NULL,\n    device_id INT\n  );\n\\endif\n\\if :IS_JOIN\n  DROP TABLE IF EXISTS devices CASCADE;\n  CREATE TABLE devices ( device_id int not null, name text, location text);\n  INSERT INTO devices values (1, 'thermo_1', 'Moscow'), (2, 'thermo_2', 'Berlin'),(3, 'thermo_3', 'London'),(4, 'thermo_4', 'Stockholm');\n\\endif\n\\if :IS_TIME_DIMENSION\n  SELECT table_name FROM create_hypertable('conditions', 'time');\npsql:include/cagg_on_cagg_setup.sql:30: WARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n table_name \n------------\n conditions\n\n\\else\n  SELECT table_name FROM create_hypertable('conditions', 'time', chunk_time_interval => 10);\n\\endif\n\\if :IS_TIME_DIMENSION\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 1);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 01:00:00-00',  5, 2);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-02 01:00:00-00', 20, 3);\n\\else\n  CREATE OR REPLACE FUNCTION integer_now()\n  RETURNS :TIME_DIMENSION_DATATYPE LANGUAGE SQL STABLE AS\n  $$\n    SELECT coalesce(max(time), 0)\n    FROM conditions\n  $$;\n  SELECT set_integer_now_func('conditions', 'integer_now');\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (1, 10, 1);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (2,  5, 2);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (5, 20, 3);\n\\endif\n\\ir include/cagg_on_cagg_common.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- CAGG on hypertable (1st level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  SUM(temperature) AS temperature,\n  device_id\nFROM conditions\nGROUP BY 1, 3\nORDER BY 1, 2, 3\nWITH NO DATA;\n-- CAGG on CAGG (2th level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  SUM(temperature) AS temperature\n\\if :IS_JOIN\n  , devices.device_id\n  , devices.name\n  FROM :CAGG_NAME_1ST_LEVEL\n  JOIN devices ON devices.device_id = :CAGG_NAME_1ST_LEVEL.device_id\n  GROUP BY 1, 3, 4\n  ORDER BY 1, 2, 3, 4\n\\else\n  FROM :CAGG_NAME_1ST_LEVEL\n  GROUP BY 1\n  ORDER BY 1, 2\n\\endif\nWITH NO DATA;\n-- CAGG on CAGG (3th level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket,\n  SUM(temperature) AS temperature\n\\if :IS_JOIN\n  , :CAGG_NAME_2TH_LEVEL.device_id\n  , :CAGG_NAME_2TH_LEVEL.name\n  , devices.location\n  FROM :CAGG_NAME_2TH_LEVEL\n  JOIN devices ON devices.device_id = :CAGG_NAME_2TH_LEVEL.device_id\n  GROUP BY 1, 3, 4, 5\n  ORDER BY 1, 2, 3, 4, 5\n\\else\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  ORDER BY 1, 2\n\\endif\nWITH NO DATA;\n-- Check chunk_interval\n\\if :IS_TIME_DIMENSION\n  SELECT h.table_name AS name, _timescaledb_functions.to_interval(d.interval_length) AS chunk_interval\n  FROM _timescaledb_catalog.hypertable h\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = h.id\n  WHERE h.table_name = 'conditions'\n  UNION ALL\n  SELECT c.user_view_name AS name, _timescaledb_functions.to_interval(d.interval_length) AS chunk_interval\n  FROM _timescaledb_catalog.continuous_agg c\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = c.mat_hypertable_id\n  WHERE c.user_view_name IN (:'CAGG_NAME_1ST_LEVEL', :'CAGG_NAME_2TH_LEVEL', :'CAGG_NAME_3TH_LEVEL')\n  ORDER BY 1, 2;\n            name             | chunk_interval \n-----------------------------+----------------\n conditions                  | @ 7 days\n conditions_summary_1_hourly | @ 70 days\n conditions_summary_2_daily  | @ 70 days\n conditions_summary_3_weekly | @ 70 days\n\n\\else\n  SELECT h.table_name AS name, d.interval_length AS chunk_interval\n  FROM _timescaledb_catalog.hypertable h\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = h.id\n  WHERE h.table_name = 'conditions'\n  UNION ALL\n  SELECT c.user_view_name AS name, d.interval_length AS chunk_interval\n  FROM _timescaledb_catalog.continuous_agg c\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = c.mat_hypertable_id\n  WHERE c.user_view_name IN (:'CAGG_NAME_1ST_LEVEL', :'CAGG_NAME_2TH_LEVEL', :'CAGG_NAME_3TH_LEVEL')\n  ORDER BY 1, 2;\n\\endif\n-- No data because the CAGGs are just for materialized data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id | name \n--------+-------------+-----------+------\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature | device_id | name | location \n--------+-------------+-----------+------+----------\n\n-- Turn CAGGs into Realtime\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=false);\n-- Realtime data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n          bucket          | temperature | device_id |   name   \n--------------------------+-------------+-----------+----------\n Sat Jan 01 00:00:00 2022 |           5 |         2 | thermo_2\n Sat Jan 01 00:00:00 2022 |          10 |         1 | thermo_1\n Sun Jan 02 00:00:00 2022 |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n          bucket          | temperature | device_id |   name   | location \n--------------------------+-------------+-----------+----------+----------\n Mon Dec 27 00:00:00 2021 |           5 |         2 | thermo_2 | Berlin\n Mon Dec 27 00:00:00 2021 |          10 |         1 | thermo_1 | Moscow\n Mon Dec 27 00:00:00 2021 |          20 |         3 | thermo_3 | London\n\n-- Turn CAGGs into materialized only again\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=true);\n-- Refresh all data\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Materialized data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n          bucket          | temperature | device_id |   name   \n--------------------------+-------------+-----------+----------\n Sat Jan 01 00:00:00 2022 |           5 |         2 | thermo_2\n Sat Jan 01 00:00:00 2022 |          10 |         1 | thermo_1\n Sun Jan 02 00:00:00 2022 |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n          bucket          | temperature | device_id |   name   | location \n--------------------------+-------------+-----------+----------+----------\n Mon Dec 27 00:00:00 2021 |           5 |         2 | thermo_2 | Berlin\n Mon Dec 27 00:00:00 2021 |          10 |         1 | thermo_1 | Moscow\n Mon Dec 27 00:00:00 2021 |          20 |         3 | thermo_3 | London\n\n\\if :IS_TIME_DIMENSION\n-- Invalidate an old region\nINSERT INTO conditions (\"time\", temperature) VALUES ('2022-01-01 01:00:00-00'::timestamptz, 2);\n-- New region\nINSERT INTO conditions (\"time\", temperature) VALUES ('2022-01-03 01:00:00-00'::timestamptz, 2);\n\\else\n-- Invalidate an old region\nINSERT INTO conditions (\"time\", temperature) VALUES (2,  2);\n-- New region\nINSERT INTO conditions (\"time\", temperature) VALUES (10, 2);\n\\endif\n-- No changes\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n          bucket          | temperature | device_id |   name   \n--------------------------+-------------+-----------+----------\n Sat Jan 01 00:00:00 2022 |           5 |         2 | thermo_2\n Sat Jan 01 00:00:00 2022 |          10 |         1 | thermo_1\n Sun Jan 02 00:00:00 2022 |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n          bucket          | temperature | device_id |   name   | location \n--------------------------+-------------+-----------+----------+----------\n Mon Dec 27 00:00:00 2021 |           5 |         2 | thermo_2 | Berlin\n Mon Dec 27 00:00:00 2021 |          10 |         1 | thermo_1 | Moscow\n Mon Dec 27 00:00:00 2021 |          20 |         3 | thermo_3 | London\n\n-- Turn CAGGs into Realtime\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=false);\n-- Realtime changes, just new region\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n Mon Jan 03 01:00:00 2022 |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n          bucket          | temperature | device_id |   name   \n--------------------------+-------------+-----------+----------\n Sat Jan 01 00:00:00 2022 |           5 |         2 | thermo_2\n Sat Jan 01 00:00:00 2022 |          10 |         1 | thermo_1\n Sun Jan 02 00:00:00 2022 |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n          bucket          | temperature | device_id |   name   | location \n--------------------------+-------------+-----------+----------+----------\n Mon Dec 27 00:00:00 2021 |           5 |         2 | thermo_2 | Berlin\n Mon Dec 27 00:00:00 2021 |          10 |         1 | thermo_1 | Moscow\n Mon Dec 27 00:00:00 2021 |          20 |         3 | thermo_3 | London\n\n-- Turn CAGGs into materialized only again\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=true);\n-- Refresh all data\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- All changes are materialized\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           2 |          \n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n Mon Jan 03 01:00:00 2022 |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n          bucket          | temperature | device_id |   name   \n--------------------------+-------------+-----------+----------\n Sat Jan 01 00:00:00 2022 |           5 |         2 | thermo_2\n Sat Jan 01 00:00:00 2022 |          10 |         1 | thermo_1\n Sun Jan 02 00:00:00 2022 |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n          bucket          | temperature | device_id |   name   | location \n--------------------------+-------------+-----------+----------+----------\n Mon Dec 27 00:00:00 2021 |           5 |         2 | thermo_2 | Berlin\n Mon Dec 27 00:00:00 2021 |          10 |         1 | thermo_1 | Moscow\n Mon Dec 27 00:00:00 2021 |          20 |         3 | thermo_3 | London\n\n-- TRUNCATE tests\nTRUNCATE :CAGG_NAME_2TH_LEVEL;\n-- This full refresh will remove all the data from the 3TH level cagg\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Should return no rows\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id | name \n--------+-------------+-----------+------\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature | device_id | name | location \n--------+-------------+-----------+------+----------\n\n-- If we have all the data in the bottom levels caggs we can rebuild\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Now we have all the data\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n          bucket          | temperature | device_id |   name   \n--------------------------+-------------+-----------+----------\n Sat Jan 01 00:00:00 2022 |           5 |         2 | thermo_2\n Sat Jan 01 00:00:00 2022 |          10 |         1 | thermo_1\n Sun Jan 02 00:00:00 2022 |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n          bucket          | temperature | device_id |   name   | location \n--------------------------+-------------+-----------+----------+----------\n Mon Dec 27 00:00:00 2021 |           5 |         2 | thermo_2 | Berlin\n Mon Dec 27 00:00:00 2021 |          10 |         1 | thermo_1 | Moscow\n Mon Dec 27 00:00:00 2021 |          20 |         3 | thermo_3 | London\n\n-- DROP tests\n\\set ON_ERROR_STOP 0\n-- should error because it depends of other CAGGs\nDROP MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:172: ERROR:  cannot drop view conditions_summary_1_hourly because other objects depend on it\nDROP MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:173: ERROR:  cannot drop view conditions_summary_2_daily because other objects depend on it\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\npsql:include/cagg_on_cagg_common.sql:174: NOTICE:  continuous aggregate \"conditions_summary_1_hourly\" is already up-to-date\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\npsql:include/cagg_on_cagg_common.sql:175: NOTICE:  continuous aggregate \"conditions_summary_2_daily\" is already up-to-date\n\\set ON_ERROR_STOP 1\n-- DROP the 3TH level CAGG don't affect others\nDROP MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:179: NOTICE:  drop cascades to table _timescaledb_internal._hyper_20_24_chunk\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:182: ERROR:  relation \"conditions_summary_3_weekly\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- should work because dropping the top level CAGG\n-- don't affect the down level CAGGs\nTRUNCATE :CAGG_NAME_2TH_LEVEL,:CAGG_NAME_1ST_LEVEL;\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           2 |          \n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n Mon Jan 03 01:00:00 2022 |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id | name \n--------+-------------+-----------+------\n\n-- DROP the 2TH level CAGG don't affect others\nDROP MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL;\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:196: ERROR:  relation \"conditions_summary_2_daily\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- should work because dropping the top level CAGG\n-- don't affect the down level CAGGs\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n          bucket          | temperature | device_id \n--------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 |          10 |         1\n Sat Jan 01 01:00:00 2022 |           2 |          \n Sat Jan 01 01:00:00 2022 |           5 |         2\n Sun Jan 02 01:00:00 2022 |          20 |         3\n Mon Jan 03 01:00:00 2022 |           2 |          \n\n-- DROP the first CAGG should work\nDROP MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:203: NOTICE:  drop cascades to table _timescaledb_internal._hyper_18_26_chunk\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:206: ERROR:  relation \"conditions_summary_1_hourly\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n--\n-- Validation test for variable bucket on top of fixed bucket\n--\n\\set ON_ERROR_STOP 0\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 month\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'60 days\\''\n\\set WARNING_MESSAGE '-- SHOULD ERROR because is not allowed variable-size bucket on top of fixed-size bucket'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                                 View \"public.conditions_summary_1\"\n   Column    |            Type             | Collation | Nullable | Default | Storage | Description \n-------------+-----------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp without time zone |           |          |         | plain   | \n temperature | numeric                     |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_21.bucket,\n    _materialized_hypertable_21.temperature\n   FROM _timescaledb_internal._materialized_hypertable_21\n  WHERE _materialized_hypertable_21.bucket < COALESCE(_timescaledb_functions.to_timestamp_without_timezone(_timescaledb_functions.cagg_watermark(21)), '-infinity'::timestamp without time zone)\nUNION ALL\n SELECT time_bucket('@ 1 mon'::interval, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp_without_timezone(_timescaledb_functions.cagg_watermark(21)), '-infinity'::timestamp without time zone)\n  GROUP BY (time_bucket('@ 1 mon'::interval, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD ERROR because is not allowed variable-size bucket on top of fixed-size bucket\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\npsql:include/cagg_on_cagg_validations.sql:44: ERROR:  cannot create continuous aggregate with fixed-width bucket on top of one using variable-width bucket\nDETAIL:  Continuous aggregate with a fixed time bucket width (e.g. 61 days) cannot be created on top of one using variable time bucket width (e.g. 1 month).\nThe variance can lead to the fixed width one not being a multiple of the variable width one.\n\\d+ :CAGG_NAME_2TH_LEVEL\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:86: NOTICE:  materialized view \"conditions_summary_2\" does not exist, skipping\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n--\n-- Validation test for non-multiple bucket sizes\n--\n\\set ON_ERROR_STOP 0\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'2 hours\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'3 hours\\''\n\\set WARNING_MESSAGE '-- SHOULD ERROR because non-multiple bucket sizes'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                                 View \"public.conditions_summary_1\"\n   Column    |            Type             | Collation | Nullable | Default | Storage | Description \n-------------+-----------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp without time zone |           |          |         | plain   | \n temperature | numeric                     |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_22.bucket,\n    _materialized_hypertable_22.temperature\n   FROM _timescaledb_internal._materialized_hypertable_22\n  WHERE _materialized_hypertable_22.bucket < COALESCE(_timescaledb_functions.to_timestamp_without_timezone(_timescaledb_functions.cagg_watermark(22)), '-infinity'::timestamp without time zone)\nUNION ALL\n SELECT time_bucket('@ 2 hours'::interval, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp_without_timezone(_timescaledb_functions.cagg_watermark(22)), '-infinity'::timestamp without time zone)\n  GROUP BY (time_bucket('@ 2 hours'::interval, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD ERROR because non-multiple bucket sizes\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\npsql:include/cagg_on_cagg_validations.sql:44: ERROR:  cannot create continuous aggregate with incompatible bucket width\nDETAIL:  Time bucket width of \"public.conditions_summary_2\" [@ 3 hours] should be multiple of the time bucket width of \"public.conditions_summary_1\" [@ 2 hours].\n\\d+ :CAGG_NAME_2TH_LEVEL\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:86: NOTICE:  materialized view \"conditions_summary_2\" does not exist, skipping\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n--\n-- Validation test for equal bucket sizes\n--\n\\set ON_ERROR_STOP 0\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 hour\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 hour\\''\n\\set WARNING_MESSAGE 'SHOULD WORK because new bucket should be greater than previous'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                                 View \"public.conditions_summary_1\"\n   Column    |            Type             | Collation | Nullable | Default | Storage | Description \n-------------+-----------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp without time zone |           |          |         | plain   | \n temperature | numeric                     |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_23.bucket,\n    _materialized_hypertable_23.temperature\n   FROM _timescaledb_internal._materialized_hypertable_23\n  WHERE _materialized_hypertable_23.bucket < COALESCE(_timescaledb_functions.to_timestamp_without_timezone(_timescaledb_functions.cagg_watermark(23)), '-infinity'::timestamp without time zone)\nUNION ALL\n SELECT time_bucket('@ 1 hour'::interval, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp_without_timezone(_timescaledb_functions.cagg_watermark(23)), '-infinity'::timestamp without time zone)\n  GROUP BY (time_bucket('@ 1 hour'::interval, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\nSHOULD WORK because new bucket should be greater than previous\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                                 View \"public.conditions_summary_2\"\n   Column    |            Type             | Collation | Nullable | Default | Storage | Description \n-------------+-----------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp without time zone |           |          |         | plain   | \n temperature | numeric                     |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_24.bucket,\n    _materialized_hypertable_24.temperature\n   FROM _timescaledb_internal._materialized_hypertable_24\n  WHERE _materialized_hypertable_24.bucket < COALESCE(_timescaledb_functions.to_timestamp_without_timezone(_timescaledb_functions.cagg_watermark(24)), '-infinity'::timestamp without time zone)\nUNION ALL\n SELECT time_bucket('@ 1 hour'::interval, conditions_summary_1.bucket) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp_without_timezone(_timescaledb_functions.cagg_watermark(24)), '-infinity'::timestamp without time zone)\n  GROUP BY (time_bucket('@ 1 hour'::interval, conditions_summary_1.bucket));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n--\n-- Validation test for bucket size less than source\n--\n\\set ON_ERROR_STOP 0\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'2 hours\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 hour\\''\n\\set WARNING_MESSAGE '-- SHOULD ERROR because new bucket should be greater than previous'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                                 View \"public.conditions_summary_1\"\n   Column    |            Type             | Collation | Nullable | Default | Storage | Description \n-------------+-----------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp without time zone |           |          |         | plain   | \n temperature | numeric                     |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_25.bucket,\n    _materialized_hypertable_25.temperature\n   FROM _timescaledb_internal._materialized_hypertable_25\n  WHERE _materialized_hypertable_25.bucket < COALESCE(_timescaledb_functions.to_timestamp_without_timezone(_timescaledb_functions.cagg_watermark(25)), '-infinity'::timestamp without time zone)\nUNION ALL\n SELECT time_bucket('@ 2 hours'::interval, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp_without_timezone(_timescaledb_functions.cagg_watermark(25)), '-infinity'::timestamp without time zone)\n  GROUP BY (time_bucket('@ 2 hours'::interval, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD ERROR because new bucket should be greater than previous\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\npsql:include/cagg_on_cagg_validations.sql:44: ERROR:  cannot create continuous aggregate with incompatible bucket width\nDETAIL:  Time bucket width of \"public.conditions_summary_2\" [@ 1 hour] should be greater or equal than the time bucket width of \"public.conditions_summary_1\" [@ 2 hours].\n\\d+ :CAGG_NAME_2TH_LEVEL\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:86: NOTICE:  materialized view \"conditions_summary_2\" does not exist, skipping\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n-- ########################################################\n-- ## TIMESTAMPTZ data type tests\n-- ########################################################\n-- Current test variables\n\\set IS_TIME_DIMENSION TRUE\n\\set TIME_DIMENSION_DATATYPE TIMESTAMPTZ\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1_hourly\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2_daily\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3_weekly\nSET timezone TO 'UTC';\n--\n-- Run common tests for TIMESTAMPTZ\n--\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 hour\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 day\\''\n\\set BUCKET_WIDTH_3TH 'INTERVAL \\'1 week\\''\n-- Different order of time dimension in raw ht\n\\set ON_ERROR_STOP 0\n\\set IS_DEFAULT_COLUMN_ORDER FALSE\n\\ir include/cagg_on_cagg_setup.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- CAGGs on CAGGs tests\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nDROP TABLE IF EXISTS conditions CASCADE;\n\\if :IS_DEFAULT_COLUMN_ORDER\n    CREATE TABLE conditions (\n    time :TIME_DIMENSION_DATATYPE NOT NULL,\n    temperature NUMERIC,\n    device_id INT\n  );\n\\else\n    CREATE TABLE conditions (\n    temperature NUMERIC,\n    time :TIME_DIMENSION_DATATYPE NOT NULL,\n    device_id INT\n  );\n\\endif\n\\if :IS_JOIN\n  DROP TABLE IF EXISTS devices CASCADE;\n  CREATE TABLE devices ( device_id int not null, name text, location text);\n  INSERT INTO devices values (1, 'thermo_1', 'Moscow'), (2, 'thermo_2', 'Berlin'),(3, 'thermo_3', 'London'),(4, 'thermo_4', 'Stockholm');\n\\endif\n\\if :IS_TIME_DIMENSION\n  SELECT table_name FROM create_hypertable('conditions', 'time');\n table_name \n------------\n conditions\n\n\\else\n  SELECT table_name FROM create_hypertable('conditions', 'time', chunk_time_interval => 10);\n\\endif\n\\if :IS_TIME_DIMENSION\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 1);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 01:00:00-00',  5, 2);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-02 01:00:00-00', 20, 3);\n\\else\n  CREATE OR REPLACE FUNCTION integer_now()\n  RETURNS :TIME_DIMENSION_DATATYPE LANGUAGE SQL STABLE AS\n  $$\n    SELECT coalesce(max(time), 0)\n    FROM conditions\n  $$;\n  SELECT set_integer_now_func('conditions', 'integer_now');\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (1, 10, 1);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (2,  5, 2);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (5, 20, 3);\n\\endif\n\\ir include/cagg_on_cagg_common.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- CAGG on hypertable (1st level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  SUM(temperature) AS temperature,\n  device_id\nFROM conditions\nGROUP BY 1, 3\nORDER BY 1, 2, 3\nWITH NO DATA;\n-- CAGG on CAGG (2th level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  SUM(temperature) AS temperature\n\\if :IS_JOIN\n  , devices.device_id\n  , devices.name\n  FROM :CAGG_NAME_1ST_LEVEL\n  JOIN devices ON devices.device_id = :CAGG_NAME_1ST_LEVEL.device_id\n  GROUP BY 1, 3, 4\n  ORDER BY 1, 2, 3, 4\n\\else\n  FROM :CAGG_NAME_1ST_LEVEL\n  GROUP BY 1\n  ORDER BY 1, 2\n\\endif\nWITH NO DATA;\n-- CAGG on CAGG (3th level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket,\n  SUM(temperature) AS temperature\n\\if :IS_JOIN\n  , :CAGG_NAME_2TH_LEVEL.device_id\n  , :CAGG_NAME_2TH_LEVEL.name\n  , devices.location\n  FROM :CAGG_NAME_2TH_LEVEL\n  JOIN devices ON devices.device_id = :CAGG_NAME_2TH_LEVEL.device_id\n  GROUP BY 1, 3, 4, 5\n  ORDER BY 1, 2, 3, 4, 5\n\\else\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  ORDER BY 1, 2\n\\endif\nWITH NO DATA;\n-- Check chunk_interval\n\\if :IS_TIME_DIMENSION\n  SELECT h.table_name AS name, _timescaledb_functions.to_interval(d.interval_length) AS chunk_interval\n  FROM _timescaledb_catalog.hypertable h\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = h.id\n  WHERE h.table_name = 'conditions'\n  UNION ALL\n  SELECT c.user_view_name AS name, _timescaledb_functions.to_interval(d.interval_length) AS chunk_interval\n  FROM _timescaledb_catalog.continuous_agg c\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = c.mat_hypertable_id\n  WHERE c.user_view_name IN (:'CAGG_NAME_1ST_LEVEL', :'CAGG_NAME_2TH_LEVEL', :'CAGG_NAME_3TH_LEVEL')\n  ORDER BY 1, 2;\n            name             | chunk_interval \n-----------------------------+----------------\n conditions                  | @ 7 days\n conditions_summary_1_hourly | @ 70 days\n conditions_summary_2_daily  | @ 70 days\n conditions_summary_3_weekly | @ 70 days\n\n\\else\n  SELECT h.table_name AS name, d.interval_length AS chunk_interval\n  FROM _timescaledb_catalog.hypertable h\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = h.id\n  WHERE h.table_name = 'conditions'\n  UNION ALL\n  SELECT c.user_view_name AS name, d.interval_length AS chunk_interval\n  FROM _timescaledb_catalog.continuous_agg c\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = c.mat_hypertable_id\n  WHERE c.user_view_name IN (:'CAGG_NAME_1ST_LEVEL', :'CAGG_NAME_2TH_LEVEL', :'CAGG_NAME_3TH_LEVEL')\n  ORDER BY 1, 2;\n\\endif\n-- No data because the CAGGs are just for materialized data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id | name \n--------+-------------+-----------+------\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature | device_id | name | location \n--------+-------------+-----------+------+----------\n\n-- Turn CAGGs into Realtime\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=false);\n-- Realtime data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n            bucket            | temperature | device_id |   name   \n------------------------------+-------------+-----------+----------\n Sat Jan 01 00:00:00 2022 UTC |           5 |         2 | thermo_2\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1 | thermo_1\n Sun Jan 02 00:00:00 2022 UTC |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n            bucket            | temperature | device_id |   name   | location \n------------------------------+-------------+-----------+----------+----------\n Mon Dec 27 00:00:00 2021 UTC |           5 |         2 | thermo_2 | Berlin\n Mon Dec 27 00:00:00 2021 UTC |          10 |         1 | thermo_1 | Moscow\n Mon Dec 27 00:00:00 2021 UTC |          20 |         3 | thermo_3 | London\n\n-- Turn CAGGs into materialized only again\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=true);\n-- Refresh all data\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Materialized data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n            bucket            | temperature | device_id |   name   \n------------------------------+-------------+-----------+----------\n Sat Jan 01 00:00:00 2022 UTC |           5 |         2 | thermo_2\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1 | thermo_1\n Sun Jan 02 00:00:00 2022 UTC |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n            bucket            | temperature | device_id |   name   | location \n------------------------------+-------------+-----------+----------+----------\n Mon Dec 27 00:00:00 2021 UTC |           5 |         2 | thermo_2 | Berlin\n Mon Dec 27 00:00:00 2021 UTC |          10 |         1 | thermo_1 | Moscow\n Mon Dec 27 00:00:00 2021 UTC |          20 |         3 | thermo_3 | London\n\n\\if :IS_TIME_DIMENSION\n-- Invalidate an old region\nINSERT INTO conditions (\"time\", temperature) VALUES ('2022-01-01 01:00:00-00'::timestamptz, 2);\n-- New region\nINSERT INTO conditions (\"time\", temperature) VALUES ('2022-01-03 01:00:00-00'::timestamptz, 2);\n\\else\n-- Invalidate an old region\nINSERT INTO conditions (\"time\", temperature) VALUES (2,  2);\n-- New region\nINSERT INTO conditions (\"time\", temperature) VALUES (10, 2);\n\\endif\n-- No changes\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n            bucket            | temperature | device_id |   name   \n------------------------------+-------------+-----------+----------\n Sat Jan 01 00:00:00 2022 UTC |           5 |         2 | thermo_2\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1 | thermo_1\n Sun Jan 02 00:00:00 2022 UTC |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n            bucket            | temperature | device_id |   name   | location \n------------------------------+-------------+-----------+----------+----------\n Mon Dec 27 00:00:00 2021 UTC |           5 |         2 | thermo_2 | Berlin\n Mon Dec 27 00:00:00 2021 UTC |          10 |         1 | thermo_1 | Moscow\n Mon Dec 27 00:00:00 2021 UTC |          20 |         3 | thermo_3 | London\n\n-- Turn CAGGs into Realtime\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=false);\n-- Realtime changes, just new region\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n Mon Jan 03 01:00:00 2022 UTC |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n            bucket            | temperature | device_id |   name   \n------------------------------+-------------+-----------+----------\n Sat Jan 01 00:00:00 2022 UTC |           5 |         2 | thermo_2\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1 | thermo_1\n Sun Jan 02 00:00:00 2022 UTC |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n            bucket            | temperature | device_id |   name   | location \n------------------------------+-------------+-----------+----------+----------\n Mon Dec 27 00:00:00 2021 UTC |           5 |         2 | thermo_2 | Berlin\n Mon Dec 27 00:00:00 2021 UTC |          10 |         1 | thermo_1 | Moscow\n Mon Dec 27 00:00:00 2021 UTC |          20 |         3 | thermo_3 | London\n\n-- Turn CAGGs into materialized only again\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=true);\n-- Refresh all data\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- All changes are materialized\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           2 |          \n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n Mon Jan 03 01:00:00 2022 UTC |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n            bucket            | temperature | device_id |   name   \n------------------------------+-------------+-----------+----------\n Sat Jan 01 00:00:00 2022 UTC |           5 |         2 | thermo_2\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1 | thermo_1\n Sun Jan 02 00:00:00 2022 UTC |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n            bucket            | temperature | device_id |   name   | location \n------------------------------+-------------+-----------+----------+----------\n Mon Dec 27 00:00:00 2021 UTC |           5 |         2 | thermo_2 | Berlin\n Mon Dec 27 00:00:00 2021 UTC |          10 |         1 | thermo_1 | Moscow\n Mon Dec 27 00:00:00 2021 UTC |          20 |         3 | thermo_3 | London\n\n-- TRUNCATE tests\nTRUNCATE :CAGG_NAME_2TH_LEVEL;\n-- This full refresh will remove all the data from the 3TH level cagg\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Should return no rows\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id | name \n--------+-------------+-----------+------\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature | device_id | name | location \n--------+-------------+-----------+------+----------\n\n-- If we have all the data in the bottom levels caggs we can rebuild\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Now we have all the data\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n            bucket            | temperature | device_id |   name   \n------------------------------+-------------+-----------+----------\n Sat Jan 01 00:00:00 2022 UTC |           5 |         2 | thermo_2\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1 | thermo_1\n Sun Jan 02 00:00:00 2022 UTC |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n            bucket            | temperature | device_id |   name   | location \n------------------------------+-------------+-----------+----------+----------\n Mon Dec 27 00:00:00 2021 UTC |           5 |         2 | thermo_2 | Berlin\n Mon Dec 27 00:00:00 2021 UTC |          10 |         1 | thermo_1 | Moscow\n Mon Dec 27 00:00:00 2021 UTC |          20 |         3 | thermo_3 | London\n\n-- DROP tests\n\\set ON_ERROR_STOP 0\n-- should error because it depends of other CAGGs\nDROP MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:172: ERROR:  cannot drop view conditions_summary_1_hourly because other objects depend on it\nDROP MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:173: ERROR:  cannot drop view conditions_summary_2_daily because other objects depend on it\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\npsql:include/cagg_on_cagg_common.sql:174: NOTICE:  continuous aggregate \"conditions_summary_1_hourly\" is already up-to-date\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\npsql:include/cagg_on_cagg_common.sql:175: NOTICE:  continuous aggregate \"conditions_summary_2_daily\" is already up-to-date\n\\set ON_ERROR_STOP 1\n-- DROP the 3TH level CAGG don't affect others\nDROP MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:179: NOTICE:  drop cascades to table _timescaledb_internal._hyper_29_30_chunk\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:182: ERROR:  relation \"conditions_summary_3_weekly\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- should work because dropping the top level CAGG\n-- don't affect the down level CAGGs\nTRUNCATE :CAGG_NAME_2TH_LEVEL,:CAGG_NAME_1ST_LEVEL;\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           2 |          \n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n Mon Jan 03 01:00:00 2022 UTC |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id | name \n--------+-------------+-----------+------\n\n-- DROP the 2TH level CAGG don't affect others\nDROP MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL;\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:196: ERROR:  relation \"conditions_summary_2_daily\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- should work because dropping the top level CAGG\n-- don't affect the down level CAGGs\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           2 |          \n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n Mon Jan 03 01:00:00 2022 UTC |           2 |          \n\n-- DROP the first CAGG should work\nDROP MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:203: NOTICE:  drop cascades to table _timescaledb_internal._hyper_27_32_chunk\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:206: ERROR:  relation \"conditions_summary_1_hourly\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- Default tests\n\\set ON_ERROR_STOP 0\n\\set IS_DEFAULT_COLUMN_ORDER TRUE\n\\ir include/cagg_on_cagg_setup.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- CAGGs on CAGGs tests\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nDROP TABLE IF EXISTS conditions CASCADE;\n\\if :IS_DEFAULT_COLUMN_ORDER\n    CREATE TABLE conditions (\n    time :TIME_DIMENSION_DATATYPE NOT NULL,\n    temperature NUMERIC,\n    device_id INT\n  );\n\\else\n    CREATE TABLE conditions (\n    temperature NUMERIC,\n    time :TIME_DIMENSION_DATATYPE NOT NULL,\n    device_id INT\n  );\n\\endif\n\\if :IS_JOIN\n  DROP TABLE IF EXISTS devices CASCADE;\n  CREATE TABLE devices ( device_id int not null, name text, location text);\n  INSERT INTO devices values (1, 'thermo_1', 'Moscow'), (2, 'thermo_2', 'Berlin'),(3, 'thermo_3', 'London'),(4, 'thermo_4', 'Stockholm');\n\\endif\n\\if :IS_TIME_DIMENSION\n  SELECT table_name FROM create_hypertable('conditions', 'time');\n table_name \n------------\n conditions\n\n\\else\n  SELECT table_name FROM create_hypertable('conditions', 'time', chunk_time_interval => 10);\n\\endif\n\\if :IS_TIME_DIMENSION\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 1);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 01:00:00-00',  5, 2);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-02 01:00:00-00', 20, 3);\n\\else\n  CREATE OR REPLACE FUNCTION integer_now()\n  RETURNS :TIME_DIMENSION_DATATYPE LANGUAGE SQL STABLE AS\n  $$\n    SELECT coalesce(max(time), 0)\n    FROM conditions\n  $$;\n  SELECT set_integer_now_func('conditions', 'integer_now');\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (1, 10, 1);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (2,  5, 2);\n    INSERT INTO conditions (\"time\", temperature, device_id) VALUES (5, 20, 3);\n\\endif\n\\ir include/cagg_on_cagg_common.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- CAGG on hypertable (1st level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  SUM(temperature) AS temperature,\n  device_id\nFROM conditions\nGROUP BY 1, 3\nORDER BY 1, 2, 3\nWITH NO DATA;\n-- CAGG on CAGG (2th level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  SUM(temperature) AS temperature\n\\if :IS_JOIN\n  , devices.device_id\n  , devices.name\n  FROM :CAGG_NAME_1ST_LEVEL\n  JOIN devices ON devices.device_id = :CAGG_NAME_1ST_LEVEL.device_id\n  GROUP BY 1, 3, 4\n  ORDER BY 1, 2, 3, 4\n\\else\n  FROM :CAGG_NAME_1ST_LEVEL\n  GROUP BY 1\n  ORDER BY 1, 2\n\\endif\nWITH NO DATA;\n-- CAGG on CAGG (3th level)\nCREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n  time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket,\n  SUM(temperature) AS temperature\n\\if :IS_JOIN\n  , :CAGG_NAME_2TH_LEVEL.device_id\n  , :CAGG_NAME_2TH_LEVEL.name\n  , devices.location\n  FROM :CAGG_NAME_2TH_LEVEL\n  JOIN devices ON devices.device_id = :CAGG_NAME_2TH_LEVEL.device_id\n  GROUP BY 1, 3, 4, 5\n  ORDER BY 1, 2, 3, 4, 5\n\\else\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  ORDER BY 1, 2\n\\endif\nWITH NO DATA;\n-- Check chunk_interval\n\\if :IS_TIME_DIMENSION\n  SELECT h.table_name AS name, _timescaledb_functions.to_interval(d.interval_length) AS chunk_interval\n  FROM _timescaledb_catalog.hypertable h\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = h.id\n  WHERE h.table_name = 'conditions'\n  UNION ALL\n  SELECT c.user_view_name AS name, _timescaledb_functions.to_interval(d.interval_length) AS chunk_interval\n  FROM _timescaledb_catalog.continuous_agg c\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = c.mat_hypertable_id\n  WHERE c.user_view_name IN (:'CAGG_NAME_1ST_LEVEL', :'CAGG_NAME_2TH_LEVEL', :'CAGG_NAME_3TH_LEVEL')\n  ORDER BY 1, 2;\n            name             | chunk_interval \n-----------------------------+----------------\n conditions                  | @ 7 days\n conditions_summary_1_hourly | @ 70 days\n conditions_summary_2_daily  | @ 70 days\n conditions_summary_3_weekly | @ 70 days\n\n\\else\n  SELECT h.table_name AS name, d.interval_length AS chunk_interval\n  FROM _timescaledb_catalog.hypertable h\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = h.id\n  WHERE h.table_name = 'conditions'\n  UNION ALL\n  SELECT c.user_view_name AS name, d.interval_length AS chunk_interval\n  FROM _timescaledb_catalog.continuous_agg c\n  LEFT JOIN _timescaledb_catalog.dimension d on d.hypertable_id = c.mat_hypertable_id\n  WHERE c.user_view_name IN (:'CAGG_NAME_1ST_LEVEL', :'CAGG_NAME_2TH_LEVEL', :'CAGG_NAME_3TH_LEVEL')\n  ORDER BY 1, 2;\n\\endif\n-- No data because the CAGGs are just for materialized data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n bucket | temperature | device_id \n--------+-------------+-----------\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id | name \n--------+-------------+-----------+------\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature | device_id | name | location \n--------+-------------+-----------+------+----------\n\n-- Turn CAGGs into Realtime\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=false);\n-- Realtime data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n            bucket            | temperature | device_id |   name   \n------------------------------+-------------+-----------+----------\n Sat Jan 01 00:00:00 2022 UTC |           5 |         2 | thermo_2\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1 | thermo_1\n Sun Jan 02 00:00:00 2022 UTC |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n            bucket            | temperature | device_id |   name   | location \n------------------------------+-------------+-----------+----------+----------\n Mon Dec 27 00:00:00 2021 UTC |           5 |         2 | thermo_2 | Berlin\n Mon Dec 27 00:00:00 2021 UTC |          10 |         1 | thermo_1 | Moscow\n Mon Dec 27 00:00:00 2021 UTC |          20 |         3 | thermo_3 | London\n\n-- Turn CAGGs into materialized only again\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=true);\n-- Refresh all data\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Materialized data\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n            bucket            | temperature | device_id |   name   \n------------------------------+-------------+-----------+----------\n Sat Jan 01 00:00:00 2022 UTC |           5 |         2 | thermo_2\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1 | thermo_1\n Sun Jan 02 00:00:00 2022 UTC |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n            bucket            | temperature | device_id |   name   | location \n------------------------------+-------------+-----------+----------+----------\n Mon Dec 27 00:00:00 2021 UTC |           5 |         2 | thermo_2 | Berlin\n Mon Dec 27 00:00:00 2021 UTC |          10 |         1 | thermo_1 | Moscow\n Mon Dec 27 00:00:00 2021 UTC |          20 |         3 | thermo_3 | London\n\n\\if :IS_TIME_DIMENSION\n-- Invalidate an old region\nINSERT INTO conditions (\"time\", temperature) VALUES ('2022-01-01 01:00:00-00'::timestamptz, 2);\n-- New region\nINSERT INTO conditions (\"time\", temperature) VALUES ('2022-01-03 01:00:00-00'::timestamptz, 2);\n\\else\n-- Invalidate an old region\nINSERT INTO conditions (\"time\", temperature) VALUES (2,  2);\n-- New region\nINSERT INTO conditions (\"time\", temperature) VALUES (10, 2);\n\\endif\n-- No changes\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n            bucket            | temperature | device_id |   name   \n------------------------------+-------------+-----------+----------\n Sat Jan 01 00:00:00 2022 UTC |           5 |         2 | thermo_2\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1 | thermo_1\n Sun Jan 02 00:00:00 2022 UTC |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n            bucket            | temperature | device_id |   name   | location \n------------------------------+-------------+-----------+----------+----------\n Mon Dec 27 00:00:00 2021 UTC |           5 |         2 | thermo_2 | Berlin\n Mon Dec 27 00:00:00 2021 UTC |          10 |         1 | thermo_1 | Moscow\n Mon Dec 27 00:00:00 2021 UTC |          20 |         3 | thermo_3 | London\n\n-- Turn CAGGs into Realtime\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=false);\n-- Realtime changes, just new region\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n Mon Jan 03 01:00:00 2022 UTC |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n            bucket            | temperature | device_id |   name   \n------------------------------+-------------+-----------+----------\n Sat Jan 01 00:00:00 2022 UTC |           5 |         2 | thermo_2\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1 | thermo_1\n Sun Jan 02 00:00:00 2022 UTC |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n            bucket            | temperature | device_id |   name   | location \n------------------------------+-------------+-----------+----------+----------\n Mon Dec 27 00:00:00 2021 UTC |           5 |         2 | thermo_2 | Berlin\n Mon Dec 27 00:00:00 2021 UTC |          10 |         1 | thermo_1 | Moscow\n Mon Dec 27 00:00:00 2021 UTC |          20 |         3 | thermo_3 | London\n\n-- Turn CAGGs into materialized only again\nALTER MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL SET (timescaledb.materialized_only=true);\n-- Refresh all data\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- All changes are materialized\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           2 |          \n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n Mon Jan 03 01:00:00 2022 UTC |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n            bucket            | temperature | device_id |   name   \n------------------------------+-------------+-----------+----------\n Sat Jan 01 00:00:00 2022 UTC |           5 |         2 | thermo_2\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1 | thermo_1\n Sun Jan 02 00:00:00 2022 UTC |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n            bucket            | temperature | device_id |   name   | location \n------------------------------+-------------+-----------+----------+----------\n Mon Dec 27 00:00:00 2021 UTC |           5 |         2 | thermo_2 | Berlin\n Mon Dec 27 00:00:00 2021 UTC |          10 |         1 | thermo_1 | Moscow\n Mon Dec 27 00:00:00 2021 UTC |          20 |         3 | thermo_3 | London\n\n-- TRUNCATE tests\nTRUNCATE :CAGG_NAME_2TH_LEVEL;\n-- This full refresh will remove all the data from the 3TH level cagg\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Should return no rows\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id | name \n--------+-------------+-----------+------\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n bucket | temperature | device_id | name | location \n--------+-------------+-----------+------+----------\n\n-- If we have all the data in the bottom levels caggs we can rebuild\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_3TH_LEVEL', NULL, NULL);\n-- Now we have all the data\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n            bucket            | temperature | device_id |   name   \n------------------------------+-------------+-----------+----------\n Sat Jan 01 00:00:00 2022 UTC |           5 |         2 | thermo_2\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1 | thermo_1\n Sun Jan 02 00:00:00 2022 UTC |          20 |         3 | thermo_3\n\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\n            bucket            | temperature | device_id |   name   | location \n------------------------------+-------------+-----------+----------+----------\n Mon Dec 27 00:00:00 2021 UTC |           5 |         2 | thermo_2 | Berlin\n Mon Dec 27 00:00:00 2021 UTC |          10 |         1 | thermo_1 | Moscow\n Mon Dec 27 00:00:00 2021 UTC |          20 |         3 | thermo_3 | London\n\n-- DROP tests\n\\set ON_ERROR_STOP 0\n-- should error because it depends of other CAGGs\nDROP MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:172: ERROR:  cannot drop view conditions_summary_1_hourly because other objects depend on it\nDROP MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:173: ERROR:  cannot drop view conditions_summary_2_daily because other objects depend on it\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\npsql:include/cagg_on_cagg_common.sql:174: NOTICE:  continuous aggregate \"conditions_summary_1_hourly\" is already up-to-date\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\npsql:include/cagg_on_cagg_common.sql:175: NOTICE:  continuous aggregate \"conditions_summary_2_daily\" is already up-to-date\n\\set ON_ERROR_STOP 1\n-- DROP the 3TH level CAGG don't affect others\nDROP MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:179: NOTICE:  drop cascades to table _timescaledb_internal._hyper_33_36_chunk\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_3TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:182: ERROR:  relation \"conditions_summary_3_weekly\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- should work because dropping the top level CAGG\n-- don't affect the down level CAGGs\nTRUNCATE :CAGG_NAME_2TH_LEVEL,:CAGG_NAME_1ST_LEVEL;\nCALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\nCALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           2 |          \n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n Mon Jan 03 01:00:00 2022 UTC |           2 |          \n\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\n bucket | temperature | device_id | name \n--------+-------------+-----------+------\n\n-- DROP the 2TH level CAGG don't affect others\nDROP MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL;\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_common.sql:196: ERROR:  relation \"conditions_summary_2_daily\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n-- should work because dropping the top level CAGG\n-- don't affect the down level CAGGs\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\n            bucket            | temperature | device_id \n------------------------------+-------------+-----------\n Sat Jan 01 00:00:00 2022 UTC |          10 |         1\n Sat Jan 01 01:00:00 2022 UTC |           2 |          \n Sat Jan 01 01:00:00 2022 UTC |           5 |         2\n Sun Jan 02 01:00:00 2022 UTC |          20 |         3\n Mon Jan 03 01:00:00 2022 UTC |           2 |          \n\n-- DROP the first CAGG should work\nDROP MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:203: NOTICE:  drop cascades to table _timescaledb_internal._hyper_31_38_chunk\n\\set ON_ERROR_STOP 0\n-- should error because it was dropped\nSELECT * FROM :CAGG_NAME_1ST_LEVEL;\npsql:include/cagg_on_cagg_common.sql:206: ERROR:  relation \"conditions_summary_1_hourly\" does not exist at character 15\n\\set ON_ERROR_STOP 1\n--\n-- Validation test for variable bucket on top of fixed bucket\n--\n\\set ON_ERROR_STOP 0\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 month\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'60 days\\''\n\\set WARNING_MESSAGE '-- SHOULD ERROR because is not allowed variable-size bucket on top of fixed-size bucket'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_34.bucket,\n    _materialized_hypertable_34.temperature\n   FROM _timescaledb_internal._materialized_hypertable_34\n  WHERE _materialized_hypertable_34.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(34)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 mon'::interval, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(34)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 mon'::interval, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD ERROR because is not allowed variable-size bucket on top of fixed-size bucket\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\npsql:include/cagg_on_cagg_validations.sql:44: ERROR:  cannot create continuous aggregate with fixed-width bucket on top of one using variable-width bucket\nDETAIL:  Continuous aggregate with a fixed time bucket width (e.g. 61 days) cannot be created on top of one using variable time bucket width (e.g. 1 month).\nThe variance can lead to the fixed width one not being a multiple of the variable width one.\n\\d+ :CAGG_NAME_2TH_LEVEL\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:86: NOTICE:  materialized view \"conditions_summary_2\" does not exist, skipping\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n--\n-- Validation test for non-multiple bucket sizes\n--\n\\set ON_ERROR_STOP 0\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'2 hours\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'3 hours\\''\n\\set WARNING_MESSAGE '-- SHOULD ERROR because non-multiple bucket sizes'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_35.bucket,\n    _materialized_hypertable_35.temperature\n   FROM _timescaledb_internal._materialized_hypertable_35\n  WHERE _materialized_hypertable_35.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(35)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 2 hours'::interval, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(35)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 2 hours'::interval, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD ERROR because non-multiple bucket sizes\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\npsql:include/cagg_on_cagg_validations.sql:44: ERROR:  cannot create continuous aggregate with incompatible bucket width\nDETAIL:  Time bucket width of \"public.conditions_summary_2\" [@ 3 hours] should be multiple of the time bucket width of \"public.conditions_summary_1\" [@ 2 hours].\n\\d+ :CAGG_NAME_2TH_LEVEL\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:86: NOTICE:  materialized view \"conditions_summary_2\" does not exist, skipping\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n--\n-- Validation test for equal bucket sizes\n--\n\\set ON_ERROR_STOP 0\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 hour\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 hour\\''\n\\set WARNING_MESSAGE 'SHOULD WORK because new bucket should be greater than previous'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_36.bucket,\n    _materialized_hypertable_36.temperature\n   FROM _timescaledb_internal._materialized_hypertable_36\n  WHERE _materialized_hypertable_36.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(36)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 hour'::interval, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(36)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 hour'::interval, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\nSHOULD WORK because new bucket should be greater than previous\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                               View \"public.conditions_summary_2\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_37.bucket,\n    _materialized_hypertable_37.temperature\n   FROM _timescaledb_internal._materialized_hypertable_37\n  WHERE _materialized_hypertable_37.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(37)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 hour'::interval, conditions_summary_1.bucket) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(37)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 hour'::interval, conditions_summary_1.bucket));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n--\n-- Validation test for bucket size less than source\n--\n\\set ON_ERROR_STOP 0\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'2 hours\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 hour\\''\n\\set WARNING_MESSAGE '-- SHOULD ERROR because new bucket should be greater than previous'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_38.bucket,\n    _materialized_hypertable_38.temperature\n   FROM _timescaledb_internal._materialized_hypertable_38\n  WHERE _materialized_hypertable_38.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(38)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 2 hours'::interval, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(38)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 2 hours'::interval, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD ERROR because new bucket should be greater than previous\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\npsql:include/cagg_on_cagg_validations.sql:44: ERROR:  cannot create continuous aggregate with incompatible bucket width\nDETAIL:  Time bucket width of \"public.conditions_summary_2\" [@ 1 hour] should be greater or equal than the time bucket width of \"public.conditions_summary_1\" [@ 2 hours].\n\\d+ :CAGG_NAME_2TH_LEVEL\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:86: NOTICE:  materialized view \"conditions_summary_2\" does not exist, skipping\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n--\n-- Validations using time bucket with timezone (ref issue #5126)\n--\n\\set ON_ERROR_STOP 0\n\\set TIME_DIMENSION_DATATYPE TIMESTAMPTZ\n\\set IS_TIME_DIMENSION_WITH_TIMEZONE_1ST TRUE\n\\set IS_TIME_DIMENSION_WITH_TIMEZONE_2TH TRUE\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1_5m\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2_1h\n\\set BUCKET_TZNAME_1ST 'US/Pacific'\n\\set BUCKET_TZNAME_2TH 'US/Pacific'\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'5 minutes\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 hour\\''\n\\set WARNING_MESSAGE '-- SHOULD WORK'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_39.bucket,\n    _materialized_hypertable_39.temperature\n   FROM _timescaledb_internal._materialized_hypertable_39\n  WHERE _materialized_hypertable_39.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(39)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 5 mins'::interval, conditions.\"time\", 'US/Pacific'::text) AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(39)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 5 mins'::interval, conditions.\"time\", 'US/Pacific'::text));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                               View \"public.conditions_summary_2\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_40.bucket,\n    _materialized_hypertable_40.temperature\n   FROM _timescaledb_internal._materialized_hypertable_40\n  WHERE _materialized_hypertable_40.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(40)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 hour'::interval, conditions_summary_1.bucket, 'US/Pacific'::text) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(40)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 hour'::interval, conditions_summary_1.bucket, 'US/Pacific'::text));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'5 minutes\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'16 minutes\\''\n\\set WARNING_MESSAGE '-- SHOULD ERROR because non-multiple bucket sizes'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_41.bucket,\n    _materialized_hypertable_41.temperature\n   FROM _timescaledb_internal._materialized_hypertable_41\n  WHERE _materialized_hypertable_41.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(41)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 5 mins'::interval, conditions.\"time\", 'US/Pacific'::text) AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(41)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 5 mins'::interval, conditions.\"time\", 'US/Pacific'::text));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD ERROR because non-multiple bucket sizes\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\npsql:include/cagg_on_cagg_validations.sql:44: ERROR:  cannot create continuous aggregate with incompatible bucket width\nDETAIL:  Time bucket width of \"public.conditions_summary_2\" [@ 16 mins] should be multiple of the time bucket width of \"public.conditions_summary_1\" [@ 5 mins].\n\\d+ :CAGG_NAME_2TH_LEVEL\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:86: NOTICE:  materialized view \"conditions_summary_2\" does not exist, skipping\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n--\n-- Variable bucket size with the same timezones\n--\n\\set BUCKET_TZNAME_1ST 'UTC'\n\\set BUCKET_TZNAME_2TH 'UTC'\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 day\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 month\\''\n\\set WARNING_MESSAGE '-- SHOULD WORK'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_42.bucket,\n    _materialized_hypertable_42.temperature\n   FROM _timescaledb_internal._materialized_hypertable_42\n  WHERE _materialized_hypertable_42.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(42)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 day'::interval, conditions.\"time\", 'UTC'::text) AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(42)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 day'::interval, conditions.\"time\", 'UTC'::text));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                               View \"public.conditions_summary_2\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_43.bucket,\n    _materialized_hypertable_43.temperature\n   FROM _timescaledb_internal._materialized_hypertable_43\n  WHERE _materialized_hypertable_43.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(43)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 mon'::interval, conditions_summary_1.bucket, 'UTC'::text) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(43)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 mon'::interval, conditions_summary_1.bucket, 'UTC'::text));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n--\n-- Variable bucket size with different timezones\n--\n\\set BUCKET_TZNAME_1ST 'US/Pacific'\n\\set BUCKET_TZNAME_2TH 'UTC'\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 day\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 month\\''\n\\set WARNING_MESSAGE '-- SHOULD WORK'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_44.bucket,\n    _materialized_hypertable_44.temperature\n   FROM _timescaledb_internal._materialized_hypertable_44\n  WHERE _materialized_hypertable_44.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(44)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 day'::interval, conditions.\"time\", 'US/Pacific'::text) AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(44)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 day'::interval, conditions.\"time\", 'US/Pacific'::text));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                               View \"public.conditions_summary_2\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_45.bucket,\n    _materialized_hypertable_45.temperature\n   FROM _timescaledb_internal._materialized_hypertable_45\n  WHERE _materialized_hypertable_45.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(45)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 mon'::interval, conditions_summary_1.bucket, 'UTC'::text) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(45)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 mon'::interval, conditions_summary_1.bucket, 'UTC'::text));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n--\n-- TZ bucket on top of non-TZ bucket\n--\n\\set IS_TIME_DIMENSION_WITH_TIMEZONE_1ST FALSE\n\\set IS_TIME_DIMENSION_WITH_TIMEZONE_2TH TRUE\n\\set BUCKET_TZNAME_2TH 'UTC'\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 day\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 month\\''\n\\set WARNING_MESSAGE '-- SHOULD WORK'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_46.bucket,\n    _materialized_hypertable_46.temperature\n   FROM _timescaledb_internal._materialized_hypertable_46\n  WHERE _materialized_hypertable_46.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(46)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 day'::interval, conditions.\"time\") AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(46)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 day'::interval, conditions.\"time\"));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                               View \"public.conditions_summary_2\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_47.bucket,\n    _materialized_hypertable_47.temperature\n   FROM _timescaledb_internal._materialized_hypertable_47\n  WHERE _materialized_hypertable_47.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(47)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 mon'::interval, conditions_summary_1.bucket, 'UTC'::text) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(47)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 mon'::interval, conditions_summary_1.bucket, 'UTC'::text));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n--\n-- non-TZ bucket on top of TZ bucket\n--\n\\set IS_TIME_DIMENSION_WITH_TIMEZONE_1ST TRUE\n\\set IS_TIME_DIMENSION_WITH_TIMEZONE_2TH FALSE\n\\set BUCKET_TZNAME_1ST 'UTC'\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 day\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 month\\''\n\\set WARNING_MESSAGE '-- SHOULD WORK'\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_48.bucket,\n    _materialized_hypertable_48.temperature\n   FROM _timescaledb_internal._materialized_hypertable_48\n  WHERE _materialized_hypertable_48.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(48)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 day'::interval, conditions.\"time\", 'UTC'::text) AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(48)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 day'::interval, conditions.\"time\", 'UTC'::text));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                               View \"public.conditions_summary_2\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_49.bucket,\n    _materialized_hypertable_49.temperature\n   FROM _timescaledb_internal._materialized_hypertable_49\n  WHERE _materialized_hypertable_49.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(49)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 mon'::interval, conditions_summary_1.bucket) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(49)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 mon'::interval, conditions_summary_1.bucket));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n-- test some intuitive intervals that should work but\n-- were not working due to unix epochs\n-- validation test for 1 year on top of one day\n-- validation test for 1 year on top of 1 month\n-- validation test for 1 year on top of 1 week\n-- bug report 5231\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 day\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 year\\''\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_50.bucket,\n    _materialized_hypertable_50.temperature\n   FROM _timescaledb_internal._materialized_hypertable_50\n  WHERE _materialized_hypertable_50.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(50)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 day'::interval, conditions.\"time\", 'UTC'::text) AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(50)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 day'::interval, conditions.\"time\", 'UTC'::text));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                               View \"public.conditions_summary_2\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_51.bucket,\n    _materialized_hypertable_51.temperature\n   FROM _timescaledb_internal._materialized_hypertable_51\n  WHERE _materialized_hypertable_51.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(51)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 year'::interval, conditions_summary_1.bucket) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(51)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 year'::interval, conditions_summary_1.bucket));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 day\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'3 month\\''\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_52.bucket,\n    _materialized_hypertable_52.temperature\n   FROM _timescaledb_internal._materialized_hypertable_52\n  WHERE _materialized_hypertable_52.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(52)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 day'::interval, conditions.\"time\", 'UTC'::text) AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(52)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 day'::interval, conditions.\"time\", 'UTC'::text));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                               View \"public.conditions_summary_2\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_53.bucket,\n    _materialized_hypertable_53.temperature\n   FROM _timescaledb_internal._materialized_hypertable_53\n  WHERE _materialized_hypertable_53.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(53)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 3 mons'::interval, conditions_summary_1.bucket) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(53)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 3 mons'::interval, conditions_summary_1.bucket));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 month\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 year\\''\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_54.bucket,\n    _materialized_hypertable_54.temperature\n   FROM _timescaledb_internal._materialized_hypertable_54\n  WHERE _materialized_hypertable_54.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(54)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 mon'::interval, conditions.\"time\", 'UTC'::text) AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(54)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 mon'::interval, conditions.\"time\", 'UTC'::text));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_2TH_LEVEL\n                               View \"public.conditions_summary_2\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_55.bucket,\n    _materialized_hypertable_55.temperature\n   FROM _timescaledb_internal._materialized_hypertable_55\n  WHERE _materialized_hypertable_55.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(55)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 1 year'::interval, conditions_summary_1.bucket) AS bucket,\n    sum(conditions_summary_1.temperature) AS temperature\n   FROM conditions_summary_1\n  WHERE conditions_summary_1.bucket >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(55)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 1 year'::interval, conditions_summary_1.bucket));\n\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 week\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 year\\''\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_56.bucket,\n    _materialized_hypertable_56.temperature\n   FROM _timescaledb_internal._materialized_hypertable_56\n  WHERE _materialized_hypertable_56.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(56)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 7 days'::interval, conditions.\"time\", 'UTC'::text) AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(56)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 7 days'::interval, conditions.\"time\", 'UTC'::text));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\npsql:include/cagg_on_cagg_validations.sql:44: ERROR:  cannot create continuous aggregate with incompatible bucket width\nDETAIL:  Time bucket width of \"public.conditions_summary_2\" [@ 1 year] should be multiple of the time bucket width of \"public.conditions_summary_1\" [@ 7 days].\n\\d+ :CAGG_NAME_2TH_LEVEL\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:86: NOTICE:  materialized view \"conditions_summary_2\" does not exist, skipping\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n\\set BUCKET_WIDTH_1ST 'INTERVAL \\'1 week\\''\n\\set BUCKET_WIDTH_2TH 'INTERVAL \\'1 month\\''\n\\ir include/cagg_on_cagg_validations.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\set CAGG_NAME_1ST_LEVEL conditions_summary_1\n\\set CAGG_NAME_2TH_LEVEL conditions_summary_2\n\\set CAGG_NAME_3TH_LEVEL conditions_summary_3\n--\n-- CAGG on hypertable (1st level)\n--\nCREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\", :'BUCKET_TZNAME_1ST') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_1ST, \"time\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM conditions\nGROUP BY 1\nWITH NO DATA;\n\\d+ :CAGG_NAME_1ST_LEVEL\n                               View \"public.conditions_summary_1\"\n   Column    |           Type           | Collation | Nullable | Default | Storage | Description \n-------------+--------------------------+-----------+----------+---------+---------+-------------\n bucket      | timestamp with time zone |           |          |         | plain   | \n temperature | numeric                  |           |          |         | main    | \nView definition:\n SELECT _materialized_hypertable_57.bucket,\n    _materialized_hypertable_57.temperature\n   FROM _timescaledb_internal._materialized_hypertable_57\n  WHERE _materialized_hypertable_57.bucket < COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(57)), '-infinity'::timestamp with time zone)\nUNION ALL\n SELECT time_bucket('@ 7 days'::interval, conditions.\"time\", 'UTC'::text) AS bucket,\n    sum(conditions.temperature) AS temperature\n   FROM conditions\n  WHERE conditions.\"time\" >= COALESCE(_timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(57)), '-infinity'::timestamp with time zone)\n  GROUP BY (time_bucket('@ 7 days'::interval, conditions.\"time\", 'UTC'::text));\n\n--\n-- CAGG on CAGG (2th level)\n--\n\\set VERBOSITY default\n\\set ON_ERROR_STOP 0\n\\echo :WARNING_MESSAGE\n-- SHOULD WORK\nCREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL\nWITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\nSELECT\n  \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket,\n  \\else\n    time_bucket(:BUCKET_WIDTH_2TH, \"bucket\") AS bucket,\n  \\endif\n  SUM(temperature) AS temperature\nFROM :CAGG_NAME_1ST_LEVEL\nGROUP BY 1\nWITH NO DATA;\npsql:include/cagg_on_cagg_validations.sql:44: ERROR:  cannot create continuous aggregate with incompatible bucket width\nDETAIL:  Time bucket width of \"public.conditions_summary_2\" [@ 1 mon] should be multiple of the time bucket width of \"public.conditions_summary_1\" [@ 7 days].\n\\d+ :CAGG_NAME_2TH_LEVEL\n\\set ON_ERROR_STOP 1\n\\set VERBOSITY terse\n-- Check for incorrect CAGGs\n\\if :INTERVAL_TEST\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-01 00:00:00-00', 10, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-15 01:00:00-00', 20, 4);\n  INSERT INTO conditions (\"time\", temperature, device_id) VALUES ('2022-01-31 01:00:00-00', 30, 4);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_1ST_LEVEL', NULL, NULL);\n  CALL refresh_continuous_aggregate(:'CAGG_NAME_2TH_LEVEL', NULL, NULL);\n  CREATE MATERIALIZED VIEW :CAGG_NAME_3TH_LEVEL\n  WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS\n  SELECT\n    \\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\", :'BUCKET_TZNAME_2TH') AS bucket\n    \\else\n      time_bucket(:BUCKET_WIDTH_3TH, \"bucket\") AS bucket\n    \\endif\n  FROM :CAGG_NAME_2TH_LEVEL\n  GROUP BY 1\n  WITH DATA;\n  \\d+ :CAGG_NAME_3TH_LEVEL\n  --There should never be dulpicates in the output of the following query\n  SELECT * from :CAGG_NAME_3TH_LEVEL;\n  DROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_3TH_LEVEL;\n  DELETE FROM conditions WHERE device_id = 4;\n\\endif\n--\n-- Cleanup\n--\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_2TH_LEVEL;\npsql:include/cagg_on_cagg_validations.sql:86: NOTICE:  materialized view \"conditions_summary_2\" does not exist, skipping\nDROP MATERIALIZED VIEW IF EXISTS :CAGG_NAME_1ST_LEVEL;\n"
  },
  {
    "path": "tsl/test/expected/cagg_permissions-15.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- initialize the bgw mock state to prevent the materialization workers from running\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- remove any default jobs, e.g., telemetry so bgw_job isn't polluted\nDELETE FROM _timescaledb_catalog.bgw_job WHERE TRUE;\nCREATE VIEW cagg_info AS\nWITH\n  caggs AS (\n    SELECT format('%I.%I', user_view_schema, user_view_name)::regclass AS user_view,\n           format('%I.%I', direct_view_schema, direct_view_name)::regclass AS direct_view,\n           format('%I.%I', partial_view_schema, partial_view_name)::regclass AS partial_view,\n           format('%I.%I', ht.schema_name, ht.table_name)::regclass AS mat_relid\n      FROM _timescaledb_catalog.hypertable ht,\n           _timescaledb_catalog.continuous_agg cagg\n     WHERE ht.id = cagg.mat_hypertable_id\n  )\nSELECT user_view,\n       (SELECT relacl FROM pg_class WHERE oid = user_view) AS user_view_perm,\n       relname AS mat_table,\n       (relacl) AS mat_table_perm,\n       direct_view,\n       (SELECT relacl FROM pg_class WHERE oid = direct_view) AS direct_view_perm,\n       partial_view,\n       (SELECT relacl FROM pg_class WHERE oid = partial_view) AS partial_view_perm\n  FROM pg_class JOIN caggs ON pg_class.oid = caggs.mat_relid;\nGRANT SELECT ON cagg_info TO PUBLIC;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nCREATE TABLE conditions (\n      timec       INT       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec', chunk_time_interval=> 100);\n table_name \n------------\n conditions\n\nCREATE OR REPLACE FUNCTION integer_now_test1() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(timec), 0) FROM conditions $$;\nSELECT set_integer_now_func('conditions', 'integer_now_test1');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW mat_refresh_test\nWITH (timescaledb.continuous, timescaledb.materialized_only=false)\nas\nselect location, max(humidity)\nfrom conditions\ngroup by time_bucket(100, timec), location WITH NO DATA;\n-- Manually create index on CAgg\nCREATE INDEX cagg_idx on mat_refresh_test(location);\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE USER not_priv;\n\\c :TEST_DBNAME not_priv\n-- A user with no ownership on the Cagg cannot create index on it. -- This should fail\n\\set ON_ERROR_STOP 0\nCREATE INDEX cagg_idx on mat_refresh_test(humidity);\nERROR:  must be owner of hypertable \"_materialized_hypertable_2\"\n\\set ON_ERROR_STOP 1\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nDROP USER not_priv;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT add_continuous_aggregate_policy('mat_refresh_test', NULL, -200::integer, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1000\n\ninsert into conditions\nselect generate_series(0, 50, 10), 'NYC', 55, 75, 40, 70, NULL;\nCALL refresh_continuous_aggregate(' mat_refresh_test', NULL, NULL);\nSELECT id as cagg_job_id FROM _timescaledb_catalog.bgw_job order by id desc limit 1 \\gset\nSELECT format('%I.%I', materialization_hypertable_schema, materialization_hypertable_name ) as materialization_hypertable\nFROM timescaledb_information.continuous_aggregates\nWHERE view_name = 'mat_refresh_test' \\gset\nSELECT mat_hypertable_id FROM _timescaledb_catalog.continuous_agg WHERE user_view_name = 'mat_refresh_test' \\gset\nSELECT schema_name as mat_chunk_schema, table_name as mat_chunk_table\nFROM _timescaledb_catalog.chunk\nWHERE hypertable_id = :mat_hypertable_id\nORDER BY id desc\nLIMIT 1 \\gset\nCREATE TABLE conditions_for_perm_check (\n      timec       INT       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable('conditions_for_perm_check', 'timec', chunk_time_interval=> 100);\n        table_name         \n---------------------------\n conditions_for_perm_check\n\nCREATE OR REPLACE FUNCTION integer_now_test2() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(timec), 0) FROM conditions_for_perm_check $$;\nSELECT set_integer_now_func('conditions_for_perm_check', 'integer_now_test2');\n set_integer_now_func \n----------------------\n \n\nCREATE TABLE conditions_for_perm_check_w_grant (\n      timec       INT       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable('conditions_for_perm_check_w_grant', 'timec', chunk_time_interval=> 100);\n            table_name             \n-----------------------------------\n conditions_for_perm_check_w_grant\n\nCREATE OR REPLACE FUNCTION integer_now_test3() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(timec), 0) FROM conditions_for_perm_check_w_grant $$;\nSELECT set_integer_now_func('conditions_for_perm_check_w_grant', 'integer_now_test3');\n set_integer_now_func \n----------------------\n \n\nGRANT SELECT, TRIGGER ON conditions_for_perm_check_w_grant TO public;\ninsert into conditions_for_perm_check_w_grant\nselect generate_series(0, 30, 10), 'POR', 55, 75, 40, 70, NULL;\n--need both select and trigger permissions to create a materialized view on top of it.\nGRANT SELECT, TRIGGER ON conditions_for_perm_check_w_grant TO public;\n\\c  :TEST_DBNAME :ROLE_SUPERUSER\ncreate schema custom_schema;\nCREATE FUNCTION get_constant() RETURNS INTEGER LANGUAGE SQL IMMUTABLE AS\n$BODY$\n    SELECT 10;\n$BODY$;\nREVOKE EXECUTE ON FUNCTION get_constant() FROM PUBLIC;\n\\c  :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n\\set ON_ERROR_STOP 0\nselect from alter_job(:cagg_job_id, max_runtime => NULL);\nERROR:  insufficient permissions to alter job 1000\n--make sure that commands fail\nALTER MATERIALIZED VIEW mat_refresh_test SET(timescaledb.materialized_only = true);\nERROR:  must be owner of continuous aggregate \"mat_refresh_test\"\nDROP MATERIALIZED VIEW mat_refresh_test;\nERROR:  must be owner of view mat_refresh_test\nCALL refresh_continuous_aggregate('mat_refresh_test', NULL, NULL);\nERROR:  must be owner of view mat_refresh_test\nSELECT * FROM mat_refresh_test;\nERROR:  permission denied for view mat_refresh_test\n-- Test permissions also when the watermark is not constified and the ACL checks\n-- in ts_continuous_agg_watermark are executed\nSET timescaledb.enable_cagg_watermark_constify = OFF;\nSELECT * FROM mat_refresh_test;\nERROR:  permission denied for view mat_refresh_test\nRESET timescaledb.enable_cagg_watermark_constify;\nSELECT * FROM :materialization_hypertable;\nERROR:  permission denied for table _materialized_hypertable_2\nSELECT * FROM :\"mat_chunk_schema\".:\"mat_chunk_table\";\nERROR:  permission denied for table _hyper_2_2_chunk\n--cannot create a mat view without select and trigger grants\nCREATE MATERIALIZED VIEW mat_perm_view_test\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect location, max(humidity)\nfrom conditions_for_perm_check\ngroup by time_bucket(100, timec), location WITH NO DATA;\nERROR:  permission denied for table conditions_for_perm_check\n--cannot create mat view in a schema without create privileges\nCREATE MATERIALIZED VIEW custom_schema.mat_perm_view_test\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect location, max(humidity)\nfrom conditions_for_perm_check_w_grant\ngroup by time_bucket(100, timec), location WITH NO DATA;\nERROR:  permission denied for schema custom_schema\n--cannot use a function without EXECUTE privileges\n--you can create a VIEW but cannot refresh it\nCREATE MATERIALIZED VIEW mat_perm_view_test\nWITH ( timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect location, max(humidity), get_constant()\nfrom conditions_for_perm_check_w_grant\ngroup by time_bucket(100, timec), location WITH NO DATA;\n--this should fail\nCALL refresh_continuous_aggregate('mat_perm_view_test', NULL, NULL);\nERROR:  permission denied for function get_constant\nDROP MATERIALIZED VIEW mat_perm_view_test;\n\\set ON_ERROR_STOP 1\n--can create a mat view on something with select and trigger grants\nCREATE MATERIALIZED VIEW mat_perm_view_test\nWITH ( timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect location, max(humidity)\nfrom conditions_for_perm_check_w_grant\ngroup by time_bucket(100, timec), location WITH NO DATA;\nCALL refresh_continuous_aggregate('mat_perm_view_test', NULL, NULL);\nSELECT * FROM mat_perm_view_test;\n location | max \n----------+-----\n POR      |  75\n\n\\c  :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n--revoke select permissions from role with mat view\nREVOKE SELECT ON conditions_for_perm_check_w_grant FROM public;\ninsert into conditions_for_perm_check_w_grant\nselect generate_series(100, 130, 10), 'POR', 65, 85, 30, 90, NULL;\n\\c  :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n\\set ON_ERROR_STOP 0\n--refresh mat view should now fail due to lack of permissions\nCALL refresh_continuous_aggregate('mat_perm_view_test', NULL, NULL);\nERROR:  permission denied for table conditions_for_perm_check_w_grant\n\\set ON_ERROR_STOP 1\n--but the old data will still be there\nSELECT * FROM mat_perm_view_test;\n location | max \n----------+-----\n POR      |  75\n\n\\set VERBOSITY default\n-- Test that grants and revokes are propagated to the implementation\n-- objects, that is, the user view, the partial view, the direct view,\n-- and the materialization table.\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nCREATE TABLE devices (\n   time TIMESTAMPTZ NOT NULL,\n   device INT,\n   temp DOUBLE PRECISION NULL,\n   PRIMARY KEY(time, device)\n);\nSELECT create_hypertable('devices', 'time');\n  create_hypertable   \n----------------------\n (8,public,devices,t)\n\nGRANT SELECT, TRIGGER ON devices TO :ROLE_DEFAULT_PERM_USER_2;\nINSERT INTO devices\nSELECT time, (random() * 30)::int, random() * 80\nFROM generate_series('2020-02-01 00:00:00'::timestamptz, '2020-03-01 00:00:00', '1 hour') AS time;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\nCREATE MATERIALIZED VIEW devices_summary\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nAS SELECT time_bucket('1 day', time) AS bucket, device, MAX(temp)\nFROM devices GROUP BY bucket, device WITH NO DATA;\n\\x on\nSELECT * FROM cagg_info WHERE user_view::text = 'devices_summary';\n-[ RECORD 1 ]-----+--------------------------------------\nuser_view         | devices_summary\nuser_view_perm    | \nmat_table         | _materialized_hypertable_9\nmat_table_perm    | \ndirect_view       | _timescaledb_internal._direct_view_9\ndirect_view_perm  | \npartial_view      | _timescaledb_internal._partial_view_9\npartial_view_perm | \n\nGRANT ALL ON devices_summary TO :ROLE_DEFAULT_PERM_USER;\nSELECT * FROM cagg_info WHERE user_view::text = 'devices_summary';\n-[ RECORD 1 ]-----+------------------------------------------------------------------------------------------------\nuser_view         | devices_summary\nuser_view_perm    | {default_perm_user_2=arwdDxt/default_perm_user_2,default_perm_user=arwdDxt/default_perm_user_2}\nmat_table         | _materialized_hypertable_9\nmat_table_perm    | {default_perm_user_2=arwdDxt/default_perm_user_2,default_perm_user=arwdDxt/default_perm_user_2}\ndirect_view       | _timescaledb_internal._direct_view_9\ndirect_view_perm  | {default_perm_user_2=arwdDxt/default_perm_user_2,default_perm_user=arwdDxt/default_perm_user_2}\npartial_view      | _timescaledb_internal._partial_view_9\npartial_view_perm | {default_perm_user_2=arwdDxt/default_perm_user_2,default_perm_user=arwdDxt/default_perm_user_2}\n\nREVOKE SELECT, UPDATE ON devices_summary FROM :ROLE_DEFAULT_PERM_USER;\nSELECT * FROM cagg_info WHERE user_view::text = 'devices_summary';\n-[ RECORD 1 ]-----+----------------------------------------------------------------------------------------------\nuser_view         | devices_summary\nuser_view_perm    | {default_perm_user_2=arwdDxt/default_perm_user_2,default_perm_user=adDxt/default_perm_user_2}\nmat_table         | _materialized_hypertable_9\nmat_table_perm    | {default_perm_user_2=arwdDxt/default_perm_user_2,default_perm_user=adDxt/default_perm_user_2}\ndirect_view       | _timescaledb_internal._direct_view_9\ndirect_view_perm  | {default_perm_user_2=arwdDxt/default_perm_user_2,default_perm_user=adDxt/default_perm_user_2}\npartial_view      | _timescaledb_internal._partial_view_9\npartial_view_perm | {default_perm_user_2=arwdDxt/default_perm_user_2,default_perm_user=adDxt/default_perm_user_2}\n\n\\x off\n-- Check for default privilege permissions get propagated to the materialization hypertable\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA test_default_privileges;\nGRANT USAGE ON SCHEMA \"test_default_privileges\" TO :ROLE_DEFAULT_PERM_USER;\nALTER DEFAULT PRIVILEGES IN SCHEMA \"test_default_privileges\" GRANT SELECT ON TABLES TO :ROLE_DEFAULT_PERM_USER;\nCREATE TABLE test_default_privileges.devices (\n   time TIMESTAMPTZ NOT NULL,\n   device INT,\n   temp DOUBLE PRECISION NULL,\n   PRIMARY KEY(time, device)\n);\nSELECT create_hypertable('test_default_privileges.devices', 'time');\n           create_hypertable            \n----------------------------------------\n (10,test_default_privileges,devices,t)\n\nCREATE MATERIALIZED VIEW test_default_privileges.devices_summary\nWITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT time_bucket('1 day', time) AS bucket, device, MAX(temp)\nFROM test_default_privileges.devices\nGROUP BY bucket, device\nWITH NO DATA;\n-- check if user view perms have been propagated to the mat-ht\nSELECT user_view_perm IS NOT DISTINCT FROM mat_table_perm\nFROM cagg_info\nWHERE user_view = 'test_default_privileges.devices_summary'::regclass;\n ?column? \n----------\n t\n\n"
  },
  {
    "path": "tsl/test/expected/cagg_permissions-16.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- initialize the bgw mock state to prevent the materialization workers from running\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- remove any default jobs, e.g., telemetry so bgw_job isn't polluted\nDELETE FROM _timescaledb_catalog.bgw_job WHERE TRUE;\nCREATE VIEW cagg_info AS\nWITH\n  caggs AS (\n    SELECT format('%I.%I', user_view_schema, user_view_name)::regclass AS user_view,\n           format('%I.%I', direct_view_schema, direct_view_name)::regclass AS direct_view,\n           format('%I.%I', partial_view_schema, partial_view_name)::regclass AS partial_view,\n           format('%I.%I', ht.schema_name, ht.table_name)::regclass AS mat_relid\n      FROM _timescaledb_catalog.hypertable ht,\n           _timescaledb_catalog.continuous_agg cagg\n     WHERE ht.id = cagg.mat_hypertable_id\n  )\nSELECT user_view,\n       (SELECT relacl FROM pg_class WHERE oid = user_view) AS user_view_perm,\n       relname AS mat_table,\n       (relacl) AS mat_table_perm,\n       direct_view,\n       (SELECT relacl FROM pg_class WHERE oid = direct_view) AS direct_view_perm,\n       partial_view,\n       (SELECT relacl FROM pg_class WHERE oid = partial_view) AS partial_view_perm\n  FROM pg_class JOIN caggs ON pg_class.oid = caggs.mat_relid;\nGRANT SELECT ON cagg_info TO PUBLIC;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nCREATE TABLE conditions (\n      timec       INT       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec', chunk_time_interval=> 100);\n table_name \n------------\n conditions\n\nCREATE OR REPLACE FUNCTION integer_now_test1() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(timec), 0) FROM conditions $$;\nSELECT set_integer_now_func('conditions', 'integer_now_test1');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW mat_refresh_test\nWITH (timescaledb.continuous, timescaledb.materialized_only=false)\nas\nselect location, max(humidity)\nfrom conditions\ngroup by time_bucket(100, timec), location WITH NO DATA;\n-- Manually create index on CAgg\nCREATE INDEX cagg_idx on mat_refresh_test(location);\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE USER not_priv;\n\\c :TEST_DBNAME not_priv\n-- A user with no ownership on the Cagg cannot create index on it. -- This should fail\n\\set ON_ERROR_STOP 0\nCREATE INDEX cagg_idx on mat_refresh_test(humidity);\nERROR:  must be owner of hypertable \"_materialized_hypertable_2\"\n\\set ON_ERROR_STOP 1\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nDROP USER not_priv;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT add_continuous_aggregate_policy('mat_refresh_test', NULL, -200::integer, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1000\n\ninsert into conditions\nselect generate_series(0, 50, 10), 'NYC', 55, 75, 40, 70, NULL;\nCALL refresh_continuous_aggregate(' mat_refresh_test', NULL, NULL);\nSELECT id as cagg_job_id FROM _timescaledb_catalog.bgw_job order by id desc limit 1 \\gset\nSELECT format('%I.%I', materialization_hypertable_schema, materialization_hypertable_name ) as materialization_hypertable\nFROM timescaledb_information.continuous_aggregates\nWHERE view_name = 'mat_refresh_test' \\gset\nSELECT mat_hypertable_id FROM _timescaledb_catalog.continuous_agg WHERE user_view_name = 'mat_refresh_test' \\gset\nSELECT schema_name as mat_chunk_schema, table_name as mat_chunk_table\nFROM _timescaledb_catalog.chunk\nWHERE hypertable_id = :mat_hypertable_id\nORDER BY id desc\nLIMIT 1 \\gset\nCREATE TABLE conditions_for_perm_check (\n      timec       INT       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable('conditions_for_perm_check', 'timec', chunk_time_interval=> 100);\n        table_name         \n---------------------------\n conditions_for_perm_check\n\nCREATE OR REPLACE FUNCTION integer_now_test2() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(timec), 0) FROM conditions_for_perm_check $$;\nSELECT set_integer_now_func('conditions_for_perm_check', 'integer_now_test2');\n set_integer_now_func \n----------------------\n \n\nCREATE TABLE conditions_for_perm_check_w_grant (\n      timec       INT       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable('conditions_for_perm_check_w_grant', 'timec', chunk_time_interval=> 100);\n            table_name             \n-----------------------------------\n conditions_for_perm_check_w_grant\n\nCREATE OR REPLACE FUNCTION integer_now_test3() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(timec), 0) FROM conditions_for_perm_check_w_grant $$;\nSELECT set_integer_now_func('conditions_for_perm_check_w_grant', 'integer_now_test3');\n set_integer_now_func \n----------------------\n \n\nGRANT SELECT, TRIGGER ON conditions_for_perm_check_w_grant TO public;\ninsert into conditions_for_perm_check_w_grant\nselect generate_series(0, 30, 10), 'POR', 55, 75, 40, 70, NULL;\n--need both select and trigger permissions to create a materialized view on top of it.\nGRANT SELECT, TRIGGER ON conditions_for_perm_check_w_grant TO public;\n\\c  :TEST_DBNAME :ROLE_SUPERUSER\ncreate schema custom_schema;\nCREATE FUNCTION get_constant() RETURNS INTEGER LANGUAGE SQL IMMUTABLE AS\n$BODY$\n    SELECT 10;\n$BODY$;\nREVOKE EXECUTE ON FUNCTION get_constant() FROM PUBLIC;\n\\c  :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n\\set ON_ERROR_STOP 0\nselect from alter_job(:cagg_job_id, max_runtime => NULL);\nERROR:  insufficient permissions to alter job 1000\n--make sure that commands fail\nALTER MATERIALIZED VIEW mat_refresh_test SET(timescaledb.materialized_only = true);\nERROR:  must be owner of continuous aggregate \"mat_refresh_test\"\nDROP MATERIALIZED VIEW mat_refresh_test;\nERROR:  must be owner of view mat_refresh_test\nCALL refresh_continuous_aggregate('mat_refresh_test', NULL, NULL);\nERROR:  must be owner of view mat_refresh_test\nSELECT * FROM mat_refresh_test;\nERROR:  permission denied for view mat_refresh_test\n-- Test permissions also when the watermark is not constified and the ACL checks\n-- in ts_continuous_agg_watermark are executed\nSET timescaledb.enable_cagg_watermark_constify = OFF;\nSELECT * FROM mat_refresh_test;\nERROR:  permission denied for view mat_refresh_test\nRESET timescaledb.enable_cagg_watermark_constify;\nSELECT * FROM :materialization_hypertable;\nERROR:  permission denied for table _materialized_hypertable_2\nSELECT * FROM :\"mat_chunk_schema\".:\"mat_chunk_table\";\nERROR:  permission denied for table _hyper_2_2_chunk\n--cannot create a mat view without select and trigger grants\nCREATE MATERIALIZED VIEW mat_perm_view_test\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect location, max(humidity)\nfrom conditions_for_perm_check\ngroup by time_bucket(100, timec), location WITH NO DATA;\nERROR:  permission denied for table conditions_for_perm_check\n--cannot create mat view in a schema without create privileges\nCREATE MATERIALIZED VIEW custom_schema.mat_perm_view_test\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect location, max(humidity)\nfrom conditions_for_perm_check_w_grant\ngroup by time_bucket(100, timec), location WITH NO DATA;\nERROR:  permission denied for schema custom_schema\n--cannot use a function without EXECUTE privileges\n--you can create a VIEW but cannot refresh it\nCREATE MATERIALIZED VIEW mat_perm_view_test\nWITH ( timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect location, max(humidity), get_constant()\nfrom conditions_for_perm_check_w_grant\ngroup by time_bucket(100, timec), location WITH NO DATA;\n--this should fail\nCALL refresh_continuous_aggregate('mat_perm_view_test', NULL, NULL);\nERROR:  permission denied for function get_constant\nDROP MATERIALIZED VIEW mat_perm_view_test;\n\\set ON_ERROR_STOP 1\n--can create a mat view on something with select and trigger grants\nCREATE MATERIALIZED VIEW mat_perm_view_test\nWITH ( timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect location, max(humidity)\nfrom conditions_for_perm_check_w_grant\ngroup by time_bucket(100, timec), location WITH NO DATA;\nCALL refresh_continuous_aggregate('mat_perm_view_test', NULL, NULL);\nSELECT * FROM mat_perm_view_test;\n location | max \n----------+-----\n POR      |  75\n\n\\c  :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n--revoke select permissions from role with mat view\nREVOKE SELECT ON conditions_for_perm_check_w_grant FROM public;\ninsert into conditions_for_perm_check_w_grant\nselect generate_series(100, 130, 10), 'POR', 65, 85, 30, 90, NULL;\n\\c  :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n\\set ON_ERROR_STOP 0\n--refresh mat view should now fail due to lack of permissions\nCALL refresh_continuous_aggregate('mat_perm_view_test', NULL, NULL);\nERROR:  permission denied for table conditions_for_perm_check_w_grant\n\\set ON_ERROR_STOP 1\n--but the old data will still be there\nSELECT * FROM mat_perm_view_test;\n location | max \n----------+-----\n POR      |  75\n\n\\set VERBOSITY default\n-- Test that grants and revokes are propagated to the implementation\n-- objects, that is, the user view, the partial view, the direct view,\n-- and the materialization table.\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nCREATE TABLE devices (\n   time TIMESTAMPTZ NOT NULL,\n   device INT,\n   temp DOUBLE PRECISION NULL,\n   PRIMARY KEY(time, device)\n);\nSELECT create_hypertable('devices', 'time');\n  create_hypertable   \n----------------------\n (8,public,devices,t)\n\nGRANT SELECT, TRIGGER ON devices TO :ROLE_DEFAULT_PERM_USER_2;\nINSERT INTO devices\nSELECT time, (random() * 30)::int, random() * 80\nFROM generate_series('2020-02-01 00:00:00'::timestamptz, '2020-03-01 00:00:00', '1 hour') AS time;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\nCREATE MATERIALIZED VIEW devices_summary\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nAS SELECT time_bucket('1 day', time) AS bucket, device, MAX(temp)\nFROM devices GROUP BY bucket, device WITH NO DATA;\n\\x on\nSELECT * FROM cagg_info WHERE user_view::text = 'devices_summary';\n-[ RECORD 1 ]-----+--------------------------------------\nuser_view         | devices_summary\nuser_view_perm    | \nmat_table         | _materialized_hypertable_9\nmat_table_perm    | \ndirect_view       | _timescaledb_internal._direct_view_9\ndirect_view_perm  | \npartial_view      | _timescaledb_internal._partial_view_9\npartial_view_perm | \n\nGRANT ALL ON devices_summary TO :ROLE_DEFAULT_PERM_USER;\nSELECT * FROM cagg_info WHERE user_view::text = 'devices_summary';\n-[ RECORD 1 ]-----+------------------------------------------------------------------------------------------------\nuser_view         | devices_summary\nuser_view_perm    | {default_perm_user_2=arwdDxt/default_perm_user_2,default_perm_user=arwdDxt/default_perm_user_2}\nmat_table         | _materialized_hypertable_9\nmat_table_perm    | {default_perm_user_2=arwdDxt/default_perm_user_2,default_perm_user=arwdDxt/default_perm_user_2}\ndirect_view       | _timescaledb_internal._direct_view_9\ndirect_view_perm  | {default_perm_user_2=arwdDxt/default_perm_user_2,default_perm_user=arwdDxt/default_perm_user_2}\npartial_view      | _timescaledb_internal._partial_view_9\npartial_view_perm | {default_perm_user_2=arwdDxt/default_perm_user_2,default_perm_user=arwdDxt/default_perm_user_2}\n\nREVOKE SELECT, UPDATE ON devices_summary FROM :ROLE_DEFAULT_PERM_USER;\nSELECT * FROM cagg_info WHERE user_view::text = 'devices_summary';\n-[ RECORD 1 ]-----+----------------------------------------------------------------------------------------------\nuser_view         | devices_summary\nuser_view_perm    | {default_perm_user_2=arwdDxt/default_perm_user_2,default_perm_user=adDxt/default_perm_user_2}\nmat_table         | _materialized_hypertable_9\nmat_table_perm    | {default_perm_user_2=arwdDxt/default_perm_user_2,default_perm_user=adDxt/default_perm_user_2}\ndirect_view       | _timescaledb_internal._direct_view_9\ndirect_view_perm  | {default_perm_user_2=arwdDxt/default_perm_user_2,default_perm_user=adDxt/default_perm_user_2}\npartial_view      | _timescaledb_internal._partial_view_9\npartial_view_perm | {default_perm_user_2=arwdDxt/default_perm_user_2,default_perm_user=adDxt/default_perm_user_2}\n\n\\x off\n-- Check for default privilege permissions get propagated to the materialization hypertable\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA test_default_privileges;\nGRANT USAGE ON SCHEMA \"test_default_privileges\" TO :ROLE_DEFAULT_PERM_USER;\nALTER DEFAULT PRIVILEGES IN SCHEMA \"test_default_privileges\" GRANT SELECT ON TABLES TO :ROLE_DEFAULT_PERM_USER;\nCREATE TABLE test_default_privileges.devices (\n   time TIMESTAMPTZ NOT NULL,\n   device INT,\n   temp DOUBLE PRECISION NULL,\n   PRIMARY KEY(time, device)\n);\nSELECT create_hypertable('test_default_privileges.devices', 'time');\n           create_hypertable            \n----------------------------------------\n (10,test_default_privileges,devices,t)\n\nCREATE MATERIALIZED VIEW test_default_privileges.devices_summary\nWITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT time_bucket('1 day', time) AS bucket, device, MAX(temp)\nFROM test_default_privileges.devices\nGROUP BY bucket, device\nWITH NO DATA;\n-- check if user view perms have been propagated to the mat-ht\nSELECT user_view_perm IS NOT DISTINCT FROM mat_table_perm\nFROM cagg_info\nWHERE user_view = 'test_default_privileges.devices_summary'::regclass;\n ?column? \n----------\n t\n\n"
  },
  {
    "path": "tsl/test/expected/cagg_permissions-17.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- initialize the bgw mock state to prevent the materialization workers from running\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- remove any default jobs, e.g., telemetry so bgw_job isn't polluted\nDELETE FROM _timescaledb_catalog.bgw_job WHERE TRUE;\nCREATE VIEW cagg_info AS\nWITH\n  caggs AS (\n    SELECT format('%I.%I', user_view_schema, user_view_name)::regclass AS user_view,\n           format('%I.%I', direct_view_schema, direct_view_name)::regclass AS direct_view,\n           format('%I.%I', partial_view_schema, partial_view_name)::regclass AS partial_view,\n           format('%I.%I', ht.schema_name, ht.table_name)::regclass AS mat_relid\n      FROM _timescaledb_catalog.hypertable ht,\n           _timescaledb_catalog.continuous_agg cagg\n     WHERE ht.id = cagg.mat_hypertable_id\n  )\nSELECT user_view,\n       (SELECT relacl FROM pg_class WHERE oid = user_view) AS user_view_perm,\n       relname AS mat_table,\n       (relacl) AS mat_table_perm,\n       direct_view,\n       (SELECT relacl FROM pg_class WHERE oid = direct_view) AS direct_view_perm,\n       partial_view,\n       (SELECT relacl FROM pg_class WHERE oid = partial_view) AS partial_view_perm\n  FROM pg_class JOIN caggs ON pg_class.oid = caggs.mat_relid;\nGRANT SELECT ON cagg_info TO PUBLIC;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nCREATE TABLE conditions (\n      timec       INT       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec', chunk_time_interval=> 100);\n table_name \n------------\n conditions\n\nCREATE OR REPLACE FUNCTION integer_now_test1() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(timec), 0) FROM conditions $$;\nSELECT set_integer_now_func('conditions', 'integer_now_test1');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW mat_refresh_test\nWITH (timescaledb.continuous, timescaledb.materialized_only=false)\nas\nselect location, max(humidity)\nfrom conditions\ngroup by time_bucket(100, timec), location WITH NO DATA;\n-- Manually create index on CAgg\nCREATE INDEX cagg_idx on mat_refresh_test(location);\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE USER not_priv;\n\\c :TEST_DBNAME not_priv\n-- A user with no ownership on the Cagg cannot create index on it. -- This should fail\n\\set ON_ERROR_STOP 0\nCREATE INDEX cagg_idx on mat_refresh_test(humidity);\nERROR:  must be owner of hypertable \"_materialized_hypertable_2\"\n\\set ON_ERROR_STOP 1\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nDROP USER not_priv;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT add_continuous_aggregate_policy('mat_refresh_test', NULL, -200::integer, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1000\n\ninsert into conditions\nselect generate_series(0, 50, 10), 'NYC', 55, 75, 40, 70, NULL;\nCALL refresh_continuous_aggregate(' mat_refresh_test', NULL, NULL);\nSELECT id as cagg_job_id FROM _timescaledb_catalog.bgw_job order by id desc limit 1 \\gset\nSELECT format('%I.%I', materialization_hypertable_schema, materialization_hypertable_name ) as materialization_hypertable\nFROM timescaledb_information.continuous_aggregates\nWHERE view_name = 'mat_refresh_test' \\gset\nSELECT mat_hypertable_id FROM _timescaledb_catalog.continuous_agg WHERE user_view_name = 'mat_refresh_test' \\gset\nSELECT schema_name as mat_chunk_schema, table_name as mat_chunk_table\nFROM _timescaledb_catalog.chunk\nWHERE hypertable_id = :mat_hypertable_id\nORDER BY id desc\nLIMIT 1 \\gset\nCREATE TABLE conditions_for_perm_check (\n      timec       INT       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable('conditions_for_perm_check', 'timec', chunk_time_interval=> 100);\n        table_name         \n---------------------------\n conditions_for_perm_check\n\nCREATE OR REPLACE FUNCTION integer_now_test2() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(timec), 0) FROM conditions_for_perm_check $$;\nSELECT set_integer_now_func('conditions_for_perm_check', 'integer_now_test2');\n set_integer_now_func \n----------------------\n \n\nCREATE TABLE conditions_for_perm_check_w_grant (\n      timec       INT       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable('conditions_for_perm_check_w_grant', 'timec', chunk_time_interval=> 100);\n            table_name             \n-----------------------------------\n conditions_for_perm_check_w_grant\n\nCREATE OR REPLACE FUNCTION integer_now_test3() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(timec), 0) FROM conditions_for_perm_check_w_grant $$;\nSELECT set_integer_now_func('conditions_for_perm_check_w_grant', 'integer_now_test3');\n set_integer_now_func \n----------------------\n \n\nGRANT SELECT, TRIGGER ON conditions_for_perm_check_w_grant TO public;\ninsert into conditions_for_perm_check_w_grant\nselect generate_series(0, 30, 10), 'POR', 55, 75, 40, 70, NULL;\n--need both select and trigger permissions to create a materialized view on top of it.\nGRANT SELECT, TRIGGER ON conditions_for_perm_check_w_grant TO public;\n\\c  :TEST_DBNAME :ROLE_SUPERUSER\ncreate schema custom_schema;\nCREATE FUNCTION get_constant() RETURNS INTEGER LANGUAGE SQL IMMUTABLE AS\n$BODY$\n    SELECT 10;\n$BODY$;\nREVOKE EXECUTE ON FUNCTION get_constant() FROM PUBLIC;\n\\c  :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n\\set ON_ERROR_STOP 0\nselect from alter_job(:cagg_job_id, max_runtime => NULL);\nERROR:  insufficient permissions to alter job 1000\n--make sure that commands fail\nALTER MATERIALIZED VIEW mat_refresh_test SET(timescaledb.materialized_only = true);\nERROR:  must be owner of continuous aggregate \"mat_refresh_test\"\nDROP MATERIALIZED VIEW mat_refresh_test;\nERROR:  must be owner of view mat_refresh_test\nCALL refresh_continuous_aggregate('mat_refresh_test', NULL, NULL);\nERROR:  must be owner of view mat_refresh_test\nSELECT * FROM mat_refresh_test;\nERROR:  permission denied for view mat_refresh_test\n-- Test permissions also when the watermark is not constified and the ACL checks\n-- in ts_continuous_agg_watermark are executed\nSET timescaledb.enable_cagg_watermark_constify = OFF;\nSELECT * FROM mat_refresh_test;\nERROR:  permission denied for view mat_refresh_test\nRESET timescaledb.enable_cagg_watermark_constify;\nSELECT * FROM :materialization_hypertable;\nERROR:  permission denied for table _materialized_hypertable_2\nSELECT * FROM :\"mat_chunk_schema\".:\"mat_chunk_table\";\nERROR:  permission denied for table _hyper_2_2_chunk\n--cannot create a mat view without select and trigger grants\nCREATE MATERIALIZED VIEW mat_perm_view_test\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect location, max(humidity)\nfrom conditions_for_perm_check\ngroup by time_bucket(100, timec), location WITH NO DATA;\nERROR:  permission denied for table conditions_for_perm_check\n--cannot create mat view in a schema without create privileges\nCREATE MATERIALIZED VIEW custom_schema.mat_perm_view_test\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect location, max(humidity)\nfrom conditions_for_perm_check_w_grant\ngroup by time_bucket(100, timec), location WITH NO DATA;\nERROR:  permission denied for schema custom_schema\n--cannot use a function without EXECUTE privileges\n--you can create a VIEW but cannot refresh it\nCREATE MATERIALIZED VIEW mat_perm_view_test\nWITH ( timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect location, max(humidity), get_constant()\nfrom conditions_for_perm_check_w_grant\ngroup by time_bucket(100, timec), location WITH NO DATA;\n--this should fail\nCALL refresh_continuous_aggregate('mat_perm_view_test', NULL, NULL);\nERROR:  permission denied for function get_constant\nDROP MATERIALIZED VIEW mat_perm_view_test;\n\\set ON_ERROR_STOP 1\n--can create a mat view on something with select and trigger grants\nCREATE MATERIALIZED VIEW mat_perm_view_test\nWITH ( timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect location, max(humidity)\nfrom conditions_for_perm_check_w_grant\ngroup by time_bucket(100, timec), location WITH NO DATA;\nCALL refresh_continuous_aggregate('mat_perm_view_test', NULL, NULL);\nSELECT * FROM mat_perm_view_test;\n location | max \n----------+-----\n POR      |  75\n\n\\c  :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n--revoke select permissions from role with mat view\nREVOKE SELECT ON conditions_for_perm_check_w_grant FROM public;\ninsert into conditions_for_perm_check_w_grant\nselect generate_series(100, 130, 10), 'POR', 65, 85, 30, 90, NULL;\n\\c  :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n\\set ON_ERROR_STOP 0\n--refresh mat view should now fail due to lack of permissions\nCALL refresh_continuous_aggregate('mat_perm_view_test', NULL, NULL);\nERROR:  permission denied for table conditions_for_perm_check_w_grant\n\\set ON_ERROR_STOP 1\n--but the old data will still be there\nSELECT * FROM mat_perm_view_test;\n location | max \n----------+-----\n POR      |  75\n\n\\set VERBOSITY default\n-- Test that grants and revokes are propagated to the implementation\n-- objects, that is, the user view, the partial view, the direct view,\n-- and the materialization table.\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nCREATE TABLE devices (\n   time TIMESTAMPTZ NOT NULL,\n   device INT,\n   temp DOUBLE PRECISION NULL,\n   PRIMARY KEY(time, device)\n);\nSELECT create_hypertable('devices', 'time');\n  create_hypertable   \n----------------------\n (8,public,devices,t)\n\nGRANT SELECT, TRIGGER ON devices TO :ROLE_DEFAULT_PERM_USER_2;\nINSERT INTO devices\nSELECT time, (random() * 30)::int, random() * 80\nFROM generate_series('2020-02-01 00:00:00'::timestamptz, '2020-03-01 00:00:00', '1 hour') AS time;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\nCREATE MATERIALIZED VIEW devices_summary\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nAS SELECT time_bucket('1 day', time) AS bucket, device, MAX(temp)\nFROM devices GROUP BY bucket, device WITH NO DATA;\n\\x on\nSELECT * FROM cagg_info WHERE user_view::text = 'devices_summary';\n-[ RECORD 1 ]-----+--------------------------------------\nuser_view         | devices_summary\nuser_view_perm    | \nmat_table         | _materialized_hypertable_9\nmat_table_perm    | \ndirect_view       | _timescaledb_internal._direct_view_9\ndirect_view_perm  | \npartial_view      | _timescaledb_internal._partial_view_9\npartial_view_perm | \n\nGRANT ALL ON devices_summary TO :ROLE_DEFAULT_PERM_USER;\nSELECT * FROM cagg_info WHERE user_view::text = 'devices_summary';\n-[ RECORD 1 ]-----+--------------------------------------------------------------------------------------------------\nuser_view         | devices_summary\nuser_view_perm    | {default_perm_user_2=arwdDxtm/default_perm_user_2,default_perm_user=arwdDxtm/default_perm_user_2}\nmat_table         | _materialized_hypertable_9\nmat_table_perm    | {default_perm_user_2=arwdDxtm/default_perm_user_2,default_perm_user=arwdDxtm/default_perm_user_2}\ndirect_view       | _timescaledb_internal._direct_view_9\ndirect_view_perm  | {default_perm_user_2=arwdDxtm/default_perm_user_2,default_perm_user=arwdDxtm/default_perm_user_2}\npartial_view      | _timescaledb_internal._partial_view_9\npartial_view_perm | {default_perm_user_2=arwdDxtm/default_perm_user_2,default_perm_user=arwdDxtm/default_perm_user_2}\n\nREVOKE SELECT, UPDATE ON devices_summary FROM :ROLE_DEFAULT_PERM_USER;\nSELECT * FROM cagg_info WHERE user_view::text = 'devices_summary';\n-[ RECORD 1 ]-----+------------------------------------------------------------------------------------------------\nuser_view         | devices_summary\nuser_view_perm    | {default_perm_user_2=arwdDxtm/default_perm_user_2,default_perm_user=adDxtm/default_perm_user_2}\nmat_table         | _materialized_hypertable_9\nmat_table_perm    | {default_perm_user_2=arwdDxtm/default_perm_user_2,default_perm_user=adDxtm/default_perm_user_2}\ndirect_view       | _timescaledb_internal._direct_view_9\ndirect_view_perm  | {default_perm_user_2=arwdDxtm/default_perm_user_2,default_perm_user=adDxtm/default_perm_user_2}\npartial_view      | _timescaledb_internal._partial_view_9\npartial_view_perm | {default_perm_user_2=arwdDxtm/default_perm_user_2,default_perm_user=adDxtm/default_perm_user_2}\n\n\\x off\n-- Check for default privilege permissions get propagated to the materialization hypertable\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA test_default_privileges;\nGRANT USAGE ON SCHEMA \"test_default_privileges\" TO :ROLE_DEFAULT_PERM_USER;\nALTER DEFAULT PRIVILEGES IN SCHEMA \"test_default_privileges\" GRANT SELECT ON TABLES TO :ROLE_DEFAULT_PERM_USER;\nCREATE TABLE test_default_privileges.devices (\n   time TIMESTAMPTZ NOT NULL,\n   device INT,\n   temp DOUBLE PRECISION NULL,\n   PRIMARY KEY(time, device)\n);\nSELECT create_hypertable('test_default_privileges.devices', 'time');\n           create_hypertable            \n----------------------------------------\n (10,test_default_privileges,devices,t)\n\nCREATE MATERIALIZED VIEW test_default_privileges.devices_summary\nWITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT time_bucket('1 day', time) AS bucket, device, MAX(temp)\nFROM test_default_privileges.devices\nGROUP BY bucket, device\nWITH NO DATA;\n-- check if user view perms have been propagated to the mat-ht\nSELECT user_view_perm IS NOT DISTINCT FROM mat_table_perm\nFROM cagg_info\nWHERE user_view = 'test_default_privileges.devices_summary'::regclass;\n ?column? \n----------\n t\n\n"
  },
  {
    "path": "tsl/test/expected/cagg_permissions-18.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- initialize the bgw mock state to prevent the materialization workers from running\n\\c :TEST_DBNAME :ROLE_SUPERUSER\n-- remove any default jobs, e.g., telemetry so bgw_job isn't polluted\nDELETE FROM _timescaledb_catalog.bgw_job WHERE TRUE;\nCREATE VIEW cagg_info AS\nWITH\n  caggs AS (\n    SELECT format('%I.%I', user_view_schema, user_view_name)::regclass AS user_view,\n           format('%I.%I', direct_view_schema, direct_view_name)::regclass AS direct_view,\n           format('%I.%I', partial_view_schema, partial_view_name)::regclass AS partial_view,\n           format('%I.%I', ht.schema_name, ht.table_name)::regclass AS mat_relid\n      FROM _timescaledb_catalog.hypertable ht,\n           _timescaledb_catalog.continuous_agg cagg\n     WHERE ht.id = cagg.mat_hypertable_id\n  )\nSELECT user_view,\n       (SELECT relacl FROM pg_class WHERE oid = user_view) AS user_view_perm,\n       relname AS mat_table,\n       (relacl) AS mat_table_perm,\n       direct_view,\n       (SELECT relacl FROM pg_class WHERE oid = direct_view) AS direct_view_perm,\n       partial_view,\n       (SELECT relacl FROM pg_class WHERE oid = partial_view) AS partial_view_perm\n  FROM pg_class JOIN caggs ON pg_class.oid = caggs.mat_relid;\nGRANT SELECT ON cagg_info TO PUBLIC;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nCREATE TABLE conditions (\n      timec       INT       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable( 'conditions', 'timec', chunk_time_interval=> 100);\n table_name \n------------\n conditions\n\nCREATE OR REPLACE FUNCTION integer_now_test1() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(timec), 0) FROM conditions $$;\nSELECT set_integer_now_func('conditions', 'integer_now_test1');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW mat_refresh_test\nWITH (timescaledb.continuous, timescaledb.materialized_only=false)\nas\nselect location, max(humidity)\nfrom conditions\ngroup by time_bucket(100, timec), location WITH NO DATA;\n-- Manually create index on CAgg\nCREATE INDEX cagg_idx on mat_refresh_test(location);\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE USER not_priv;\n\\c :TEST_DBNAME not_priv\n-- A user with no ownership on the Cagg cannot create index on it. -- This should fail\n\\set ON_ERROR_STOP 0\nCREATE INDEX cagg_idx on mat_refresh_test(humidity);\nERROR:  must be owner of hypertable \"_materialized_hypertable_2\"\n\\set ON_ERROR_STOP 1\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nDROP USER not_priv;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nSELECT add_continuous_aggregate_policy('mat_refresh_test', NULL, -200::integer, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1000\n\ninsert into conditions\nselect generate_series(0, 50, 10), 'NYC', 55, 75, 40, 70, NULL;\nCALL refresh_continuous_aggregate(' mat_refresh_test', NULL, NULL);\nSELECT id as cagg_job_id FROM _timescaledb_catalog.bgw_job order by id desc limit 1 \\gset\nSELECT format('%I.%I', materialization_hypertable_schema, materialization_hypertable_name ) as materialization_hypertable\nFROM timescaledb_information.continuous_aggregates\nWHERE view_name = 'mat_refresh_test' \\gset\nSELECT mat_hypertable_id FROM _timescaledb_catalog.continuous_agg WHERE user_view_name = 'mat_refresh_test' \\gset\nSELECT schema_name as mat_chunk_schema, table_name as mat_chunk_table\nFROM _timescaledb_catalog.chunk\nWHERE hypertable_id = :mat_hypertable_id\nORDER BY id desc\nLIMIT 1 \\gset\nCREATE TABLE conditions_for_perm_check (\n      timec       INT       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable('conditions_for_perm_check', 'timec', chunk_time_interval=> 100);\n        table_name         \n---------------------------\n conditions_for_perm_check\n\nCREATE OR REPLACE FUNCTION integer_now_test2() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(timec), 0) FROM conditions_for_perm_check $$;\nSELECT set_integer_now_func('conditions_for_perm_check', 'integer_now_test2');\n set_integer_now_func \n----------------------\n \n\nCREATE TABLE conditions_for_perm_check_w_grant (\n      timec       INT       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL,\n      lowp        double precision NULL,\n      highp       double precision null,\n      allnull     double precision null\n    );\nselect table_name from create_hypertable('conditions_for_perm_check_w_grant', 'timec', chunk_time_interval=> 100);\n            table_name             \n-----------------------------------\n conditions_for_perm_check_w_grant\n\nCREATE OR REPLACE FUNCTION integer_now_test3() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(timec), 0) FROM conditions_for_perm_check_w_grant $$;\nSELECT set_integer_now_func('conditions_for_perm_check_w_grant', 'integer_now_test3');\n set_integer_now_func \n----------------------\n \n\nGRANT SELECT, TRIGGER ON conditions_for_perm_check_w_grant TO public;\ninsert into conditions_for_perm_check_w_grant\nselect generate_series(0, 30, 10), 'POR', 55, 75, 40, 70, NULL;\n--need both select and trigger permissions to create a materialized view on top of it.\nGRANT SELECT, TRIGGER ON conditions_for_perm_check_w_grant TO public;\n\\c  :TEST_DBNAME :ROLE_SUPERUSER\ncreate schema custom_schema;\nCREATE FUNCTION get_constant() RETURNS INTEGER LANGUAGE SQL IMMUTABLE AS\n$BODY$\n    SELECT 10;\n$BODY$;\nREVOKE EXECUTE ON FUNCTION get_constant() FROM PUBLIC;\n\\c  :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n\\set ON_ERROR_STOP 0\nselect from alter_job(:cagg_job_id, max_runtime => NULL);\nERROR:  insufficient permissions to alter job 1000\n--make sure that commands fail\nALTER MATERIALIZED VIEW mat_refresh_test SET(timescaledb.materialized_only = true);\nERROR:  must be owner of continuous aggregate \"mat_refresh_test\"\nDROP MATERIALIZED VIEW mat_refresh_test;\nERROR:  must be owner of view mat_refresh_test\nCALL refresh_continuous_aggregate('mat_refresh_test', NULL, NULL);\nERROR:  must be owner of view mat_refresh_test\nSELECT * FROM mat_refresh_test;\nERROR:  permission denied for view mat_refresh_test\n-- Test permissions also when the watermark is not constified and the ACL checks\n-- in ts_continuous_agg_watermark are executed\nSET timescaledb.enable_cagg_watermark_constify = OFF;\nSELECT * FROM mat_refresh_test;\nERROR:  permission denied for view mat_refresh_test\nRESET timescaledb.enable_cagg_watermark_constify;\nSELECT * FROM :materialization_hypertable;\nERROR:  permission denied for table _materialized_hypertable_2\nSELECT * FROM :\"mat_chunk_schema\".:\"mat_chunk_table\";\nERROR:  permission denied for table _hyper_2_2_chunk\n--cannot create a mat view without select and trigger grants\nCREATE MATERIALIZED VIEW mat_perm_view_test\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect location, max(humidity)\nfrom conditions_for_perm_check\ngroup by time_bucket(100, timec), location WITH NO DATA;\nERROR:  permission denied for table conditions_for_perm_check\n--cannot create mat view in a schema without create privileges\nCREATE MATERIALIZED VIEW custom_schema.mat_perm_view_test\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect location, max(humidity)\nfrom conditions_for_perm_check_w_grant\ngroup by time_bucket(100, timec), location WITH NO DATA;\nERROR:  permission denied for schema custom_schema\n--cannot use a function without EXECUTE privileges\n--you can create a VIEW but cannot refresh it\nCREATE MATERIALIZED VIEW mat_perm_view_test\nWITH ( timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect location, max(humidity), get_constant()\nfrom conditions_for_perm_check_w_grant\ngroup by time_bucket(100, timec), location WITH NO DATA;\n--this should fail\nCALL refresh_continuous_aggregate('mat_perm_view_test', NULL, NULL);\nERROR:  permission denied for function get_constant\nDROP MATERIALIZED VIEW mat_perm_view_test;\n\\set ON_ERROR_STOP 1\n--can create a mat view on something with select and trigger grants\nCREATE MATERIALIZED VIEW mat_perm_view_test\nWITH ( timescaledb.continuous, timescaledb.materialized_only=true)\nas\nselect location, max(humidity)\nfrom conditions_for_perm_check_w_grant\ngroup by time_bucket(100, timec), location WITH NO DATA;\nCALL refresh_continuous_aggregate('mat_perm_view_test', NULL, NULL);\nSELECT * FROM mat_perm_view_test;\n location | max \n----------+-----\n POR      |  75\n\n\\c  :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\n--revoke select permissions from role with mat view\nREVOKE SELECT ON conditions_for_perm_check_w_grant FROM public;\ninsert into conditions_for_perm_check_w_grant\nselect generate_series(100, 130, 10), 'POR', 65, 85, 30, 90, NULL;\n\\c  :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\n\\set ON_ERROR_STOP 0\n--refresh mat view should now fail due to lack of permissions\nCALL refresh_continuous_aggregate('mat_perm_view_test', NULL, NULL);\nERROR:  permission denied for table conditions_for_perm_check_w_grant\n\\set ON_ERROR_STOP 1\n--but the old data will still be there\nSELECT * FROM mat_perm_view_test;\n location | max \n----------+-----\n POR      |  75\n\n\\set VERBOSITY default\n-- Test that grants and revokes are propagated to the implementation\n-- objects, that is, the user view, the partial view, the direct view,\n-- and the materialization table.\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER\nCREATE TABLE devices (\n   time TIMESTAMPTZ NOT NULL,\n   device INT,\n   temp DOUBLE PRECISION NULL,\n   PRIMARY KEY(time, device)\n);\nSELECT create_hypertable('devices', 'time');\n  create_hypertable   \n----------------------\n (8,public,devices,t)\n\nGRANT SELECT, TRIGGER ON devices TO :ROLE_DEFAULT_PERM_USER_2;\nINSERT INTO devices\nSELECT time, (random() * 30)::int, random() * 80\nFROM generate_series('2020-02-01 00:00:00'::timestamptz, '2020-03-01 00:00:00', '1 hour') AS time;\n\\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2\nCREATE MATERIALIZED VIEW devices_summary\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nAS SELECT time_bucket('1 day', time) AS bucket, device, MAX(temp)\nFROM devices GROUP BY bucket, device WITH NO DATA;\n\\x on\nSELECT * FROM cagg_info WHERE user_view::text = 'devices_summary';\n-[ RECORD 1 ]-----+--------------------------------------\nuser_view         | devices_summary\nuser_view_perm    | \nmat_table         | _materialized_hypertable_9\nmat_table_perm    | \ndirect_view       | _timescaledb_internal._direct_view_9\ndirect_view_perm  | \npartial_view      | _timescaledb_internal._partial_view_9\npartial_view_perm | \n\nGRANT ALL ON devices_summary TO :ROLE_DEFAULT_PERM_USER;\nSELECT * FROM cagg_info WHERE user_view::text = 'devices_summary';\n-[ RECORD 1 ]-----+--------------------------------------------------------------------------------------------------\nuser_view         | devices_summary\nuser_view_perm    | {default_perm_user_2=arwdDxtm/default_perm_user_2,default_perm_user=arwdDxtm/default_perm_user_2}\nmat_table         | _materialized_hypertable_9\nmat_table_perm    | {default_perm_user_2=arwdDxtm/default_perm_user_2,default_perm_user=arwdDxtm/default_perm_user_2}\ndirect_view       | _timescaledb_internal._direct_view_9\ndirect_view_perm  | {default_perm_user_2=arwdDxtm/default_perm_user_2,default_perm_user=arwdDxtm/default_perm_user_2}\npartial_view      | _timescaledb_internal._partial_view_9\npartial_view_perm | {default_perm_user_2=arwdDxtm/default_perm_user_2,default_perm_user=arwdDxtm/default_perm_user_2}\n\nREVOKE SELECT, UPDATE ON devices_summary FROM :ROLE_DEFAULT_PERM_USER;\nSELECT * FROM cagg_info WHERE user_view::text = 'devices_summary';\n-[ RECORD 1 ]-----+------------------------------------------------------------------------------------------------\nuser_view         | devices_summary\nuser_view_perm    | {default_perm_user_2=arwdDxtm/default_perm_user_2,default_perm_user=adDxtm/default_perm_user_2}\nmat_table         | _materialized_hypertable_9\nmat_table_perm    | {default_perm_user_2=arwdDxtm/default_perm_user_2,default_perm_user=adDxtm/default_perm_user_2}\ndirect_view       | _timescaledb_internal._direct_view_9\ndirect_view_perm  | {default_perm_user_2=arwdDxtm/default_perm_user_2,default_perm_user=adDxtm/default_perm_user_2}\npartial_view      | _timescaledb_internal._partial_view_9\npartial_view_perm | {default_perm_user_2=arwdDxtm/default_perm_user_2,default_perm_user=adDxtm/default_perm_user_2}\n\n\\x off\n-- Check for default privilege permissions get propagated to the materialization hypertable\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE SCHEMA test_default_privileges;\nGRANT USAGE ON SCHEMA \"test_default_privileges\" TO :ROLE_DEFAULT_PERM_USER;\nALTER DEFAULT PRIVILEGES IN SCHEMA \"test_default_privileges\" GRANT SELECT ON TABLES TO :ROLE_DEFAULT_PERM_USER;\nCREATE TABLE test_default_privileges.devices (\n   time TIMESTAMPTZ NOT NULL,\n   device INT,\n   temp DOUBLE PRECISION NULL,\n   PRIMARY KEY(time, device)\n);\nSELECT create_hypertable('test_default_privileges.devices', 'time');\n           create_hypertable            \n----------------------------------------\n (10,test_default_privileges,devices,t)\n\nCREATE MATERIALIZED VIEW test_default_privileges.devices_summary\nWITH (timescaledb.continuous, timescaledb.materialized_only=false)\nAS\nSELECT time_bucket('1 day', time) AS bucket, device, MAX(temp)\nFROM test_default_privileges.devices\nGROUP BY bucket, device\nWITH NO DATA;\n-- check if user view perms have been propagated to the mat-ht\nSELECT user_view_perm IS NOT DISTINCT FROM mat_table_perm\nFROM cagg_info\nWHERE user_view = 'test_default_privileges.devices_summary'::regclass;\n ?column? \n----------\n t\n\n"
  },
  {
    "path": "tsl/test/expected/cagg_policy.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- test add and remove refresh policy apis\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n--TEST1 ---\n--basic test with count\nCREATE TABLE int_tab (a integer, b integer, c integer);\nSELECT table_name FROM create_hypertable('int_tab', 'a', chunk_time_interval=> 10);\n table_name \n------------\n int_tab\n\nINSERT INTO int_tab VALUES( 3 , 16 , 20);\nINSERT INTO int_tab VALUES( 1 , 10 , 20);\nINSERT INTO int_tab VALUES( 1 , 11 , 20);\nINSERT INTO int_tab VALUES( 1 , 12 , 20);\nINSERT INTO int_tab VALUES( 1 , 13 , 20);\nINSERT INTO int_tab VALUES( 1 , 14 , 20);\nINSERT INTO int_tab VALUES( 2 , 14 , 20);\nINSERT INTO int_tab VALUES( 2 , 15 , 20);\nINSERT INTO int_tab VALUES( 2 , 16 , 20);\nCREATE OR REPLACE FUNCTION integer_now_int_tab() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(a), 0) FROM int_tab $$;\nSELECT set_integer_now_func('int_tab', 'integer_now_int_tab');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW mat_m1( a, countb )\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nSELECT a, count(b)\nFROM int_tab\nGROUP BY time_bucket(1, a), a WITH NO DATA;\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET timezone TO PST8PDT;\nDELETE FROM _timescaledb_catalog.bgw_job WHERE TRUE;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT count(*) FROM _timescaledb_catalog.bgw_job;\n count \n-------\n     0\n\n\\set ON_ERROR_STOP 0\n\\set VERBOSITY default\n-- Test 1 step policy for integer type buckets\nALTER materialized view mat_m1 set (timescaledb.compress = true);\nNOTICE:  defaulting compress_orderby to time_partition_col,a\n-- No policy is added if one errors out\nSELECT timescaledb_experimental.add_policies('mat_m1', refresh_start_offset => 1, refresh_end_offset => 10, compress_after => 11, drop_after => 20);\nERROR:  policy refresh window too small\nDETAIL:  The start and end offsets must cover at least two buckets in the valid time range of type \"integer\".\nSELECT timescaledb_experimental.show_policies('mat_m1');\n show_policies \n---------------\n\n-- All policies are added in one step\nSELECT timescaledb_experimental.add_policies('mat_m1', refresh_start_offset => 10, refresh_end_offset => 1, compress_after => 11, drop_after => 20);\n add_policies \n--------------\n t\n\nSELECT timescaledb_experimental.show_policies('mat_m1');\n                                                                show_policies                                                                \n---------------------------------------------------------------------------------------------------------------------------------------------\n {\"policy_name\": \"policy_compression\", \"compress_after\": 11, \"compress_interval\": \"@ 1 day\"}\n {\"policy_name\": \"policy_refresh_continuous_aggregate\", \"refresh_interval\": \"@ 1 hour\", \"refresh_end_offset\": 1, \"refresh_start_offset\": 10}\n {\"drop_after\": 20, \"policy_name\": \"policy_retention\", \"retention_interval\": \"@ 1 day\"}\n\n--Test coverage: new view for policies on CAggs\nSELECT * FROM timescaledb_experimental.policies ORDER BY relation_name, proc_name;\n relation_name | relation_schema | schedule_interval |      proc_schema       |              proc_name              |                            config                             |   hypertable_schema   |      hypertable_name       \n---------------+-----------------+-------------------+------------------------+-------------------------------------+---------------------------------------------------------------+-----------------------+----------------------------\n mat_m1        | public          | @ 1 day           | _timescaledb_functions | policy_compression                  | {\"hypertable_id\": 2, \"compress_after\": 11}                    | _timescaledb_internal | _materialized_hypertable_2\n mat_m1        | public          | @ 1 hour          | _timescaledb_functions | policy_refresh_continuous_aggregate | {\"end_offset\": 1, \"start_offset\": 10, \"mat_hypertable_id\": 2} | _timescaledb_internal | _materialized_hypertable_2\n mat_m1        | public          | @ 1 day           | _timescaledb_functions | policy_retention                    | {\"drop_after\": 20, \"hypertable_id\": 2}                        | _timescaledb_internal | _materialized_hypertable_2\n\n--Test coverage: new view for policies only shows the policies for  CAggs\nSELECT add_retention_policy('int_tab', 20);\n add_retention_policy \n----------------------\n                 1003\n\nSELECT * FROM timescaledb_experimental.policies ORDER BY relation_name, proc_name;\n relation_name | relation_schema | schedule_interval |      proc_schema       |              proc_name              |                            config                             |   hypertable_schema   |      hypertable_name       \n---------------+-----------------+-------------------+------------------------+-------------------------------------+---------------------------------------------------------------+-----------------------+----------------------------\n mat_m1        | public          | @ 1 day           | _timescaledb_functions | policy_compression                  | {\"hypertable_id\": 2, \"compress_after\": 11}                    | _timescaledb_internal | _materialized_hypertable_2\n mat_m1        | public          | @ 1 hour          | _timescaledb_functions | policy_refresh_continuous_aggregate | {\"end_offset\": 1, \"start_offset\": 10, \"mat_hypertable_id\": 2} | _timescaledb_internal | _materialized_hypertable_2\n mat_m1        | public          | @ 1 day           | _timescaledb_functions | policy_retention                    | {\"drop_after\": 20, \"hypertable_id\": 2}                        | _timescaledb_internal | _materialized_hypertable_2\n\nSELECT remove_retention_policy('int_tab');\n remove_retention_policy \n-------------------------\n \n\n-- Test for duplicated policies (issue #5492)\nCREATE MATERIALIZED VIEW mat_m2( a, sumb )\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nSELECT a, sum(b)\nFROM int_tab\nGROUP BY time_bucket(1, a), a WITH NO DATA;\n-- add refresh policy\nSELECT timescaledb_experimental.add_policies('mat_m2', refresh_start_offset => 10, refresh_end_offset => 1);\n add_policies \n--------------\n t\n\nSELECT timescaledb_experimental.show_policies('mat_m2');\n                                                                show_policies                                                                \n---------------------------------------------------------------------------------------------------------------------------------------------\n {\"policy_name\": \"policy_refresh_continuous_aggregate\", \"refresh_interval\": \"@ 1 hour\", \"refresh_end_offset\": 1, \"refresh_start_offset\": 10}\n\n-- check for only one refresh policy for each cagg\nSELECT * FROM timescaledb_experimental.policies WHERE proc_name ~ 'refresh' ORDER BY relation_name, proc_name;\n relation_name | relation_schema | schedule_interval |      proc_schema       |              proc_name              |                            config                             |   hypertable_schema   |      hypertable_name       \n---------------+-----------------+-------------------+------------------------+-------------------------------------+---------------------------------------------------------------+-----------------------+----------------------------\n mat_m1        | public          | @ 1 hour          | _timescaledb_functions | policy_refresh_continuous_aggregate | {\"end_offset\": 1, \"start_offset\": 10, \"mat_hypertable_id\": 2} | _timescaledb_internal | _materialized_hypertable_2\n mat_m2        | public          | @ 1 hour          | _timescaledb_functions | policy_refresh_continuous_aggregate | {\"end_offset\": 1, \"start_offset\": 10, \"mat_hypertable_id\": 4} | _timescaledb_internal | _materialized_hypertable_4\n\nSELECT timescaledb_experimental.remove_all_policies('mat_m2');\n remove_all_policies \n---------------------\n t\n\nDROP MATERIALIZED VIEW mat_m2;\n-- Alter policies\nSELECT timescaledb_experimental.alter_policies('mat_m1',  refresh_start_offset => 11, compress_after=>13, drop_after => 25);\n alter_policies \n----------------\n t\n\nSELECT timescaledb_experimental.show_policies('mat_m1');\n                                                                show_policies                                                                \n---------------------------------------------------------------------------------------------------------------------------------------------\n {\"policy_name\": \"policy_compression\", \"compress_after\": 13, \"compress_interval\": \"@ 1 day\"}\n {\"policy_name\": \"policy_refresh_continuous_aggregate\", \"refresh_interval\": \"@ 1 hour\", \"refresh_end_offset\": 1, \"refresh_start_offset\": 11}\n {\"drop_after\": 25, \"policy_name\": \"policy_retention\", \"retention_interval\": \"@ 1 day\"}\n\n-- Remove one or more policy\nSELECT timescaledb_experimental.remove_policies('mat_m1', false, 'policy_refresh_continuous_aggregate', 'policy_compression');\n remove_policies \n-----------------\n t\n\nSELECT timescaledb_experimental.show_policies('mat_m1');\n                                     show_policies                                      \n----------------------------------------------------------------------------------------\n {\"drop_after\": 25, \"policy_name\": \"policy_retention\", \"retention_interval\": \"@ 1 day\"}\n\n-- Add one policy\nSELECT timescaledb_experimental.add_policies('mat_m1', refresh_start_offset => 10, refresh_end_offset => 1);\n add_policies \n--------------\n t\n\nSELECT timescaledb_experimental.show_policies('mat_m1');\n                                                                show_policies                                                                \n---------------------------------------------------------------------------------------------------------------------------------------------\n {\"policy_name\": \"policy_refresh_continuous_aggregate\", \"refresh_interval\": \"@ 1 hour\", \"refresh_end_offset\": 1, \"refresh_start_offset\": 10}\n {\"drop_after\": 25, \"policy_name\": \"policy_retention\", \"retention_interval\": \"@ 1 day\"}\n\n-- Remove all policies\nSELECT timescaledb_experimental.remove_policies('mat_m1', false, 'policy_refresh_continuous_aggregate', 'policy_retention');\n remove_policies \n-----------------\n t\n\nSELECT timescaledb_experimental.show_policies('mat_m1');\n show_policies \n---------------\n\n--Cross policy checks\n--refresh and compression policy overlap\nSELECT timescaledb_experimental.add_policies('mat_m1', refresh_start_offset => 12, refresh_end_offset => 1, compress_after=>11);\nERROR:  refresh and columnstore policies overlap\n--refresh and retention policy overlap\nSELECT timescaledb_experimental.add_policies('mat_m1', refresh_start_offset => 12, refresh_end_offset => 1, drop_after=>11);\nERROR:  refresh and retention policies overlap\n--compression and retention policy overlap\nSELECT timescaledb_experimental.add_policies('mat_m1', compress_after => 10, drop_after => 10);\nERROR:  columnstore and retention policies overlap\n-- Alter non existent policies\nSELECT timescaledb_experimental.alter_policies('mat_m1', refresh_start_offset => 12, compress_after=>11, drop_after => 15);\nERROR:  no jobs found\nALTER materialized view mat_m1 set (timescaledb.compress = false);\nSELECT add_continuous_aggregate_policy('int_tab', '1 day'::interval, 10 , '1 h'::interval);\nERROR:  \"int_tab\" is not a continuous aggregate\nSELECT add_continuous_aggregate_policy('mat_m1', '1 day'::interval, 10 , '1 h'::interval);\nERROR:  invalid parameter value for start_offset\nHINT:  Use time interval of type integer with the continuous aggregate.\nSELECT add_continuous_aggregate_policy('mat_m1', '1 day'::interval, 10 );\nERROR:  function add_continuous_aggregate_policy(unknown, interval, integer) does not exist\nLINE 1: SELECT add_continuous_aggregate_policy('mat_m1', '1 day'::in...\n               ^\nHINT:  No function matches the given name and argument types. You might need to add explicit type casts.\nSELECT add_continuous_aggregate_policy('mat_m1', 10, '1 day'::interval, '1 h'::interval);\nERROR:  invalid parameter value for end_offset\nHINT:  Use time interval of type integer with the continuous aggregate.\n--start_interval < end_interval\nSELECT add_continuous_aggregate_policy('mat_m1', 5, 10, '1h'::interval);\nERROR:  policy refresh window too small\nDETAIL:  The start and end offsets must cover at least two buckets in the valid time range of type \"integer\".\n--refresh window less than two buckets\nSELECT add_continuous_aggregate_policy('mat_m1', 11, 10, '1h'::interval);\nERROR:  policy refresh window too small\nDETAIL:  The start and end offsets must cover at least two buckets in the valid time range of type \"integer\".\nSELECT add_continuous_aggregate_policy('mat_m1', 20, 10, '1h'::interval) as job_id \\gset\n--adding again should warn/error\nSELECT add_continuous_aggregate_policy('mat_m1', 20, 10, '1h'::interval, if_not_exists=>false);\nERROR:  continuous aggregate refresh policy already exists for \"mat_m1\"\nDETAIL:  A refresh policy with the same start and end offset already exists for continuous aggregate \"mat_m1\".\nSELECT add_continuous_aggregate_policy('mat_m1', 20, 15, '1h'::interval, if_not_exists=>true);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT add_continuous_aggregate_policy('mat_m1', 20, 10, '1h'::interval, if_not_exists=>true);\nNOTICE:  continuous aggregate refresh policy already exists for \"mat_m1\", skipping\nDETAIL:  A refresh policy with the same start and end offset already exists for continuous aggregate \"mat_m1\".\n add_continuous_aggregate_policy \n---------------------------------\n                              -1\n\n-- modify config and try to add, should error out\nSELECT config FROM _timescaledb_catalog.bgw_job where id = :job_id;\n                             config                             \n----------------------------------------------------------------\n {\"end_offset\": 10, \"start_offset\": 20, \"mat_hypertable_id\": 2}\n\nSELECT hypertable_id as mat_id FROM _timescaledb_catalog.bgw_job where id = :job_id \\gset\n\\set VERBOSITY terse\n\\set ON_ERROR_STOP 1\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET timezone TO PST8PDT;\nUPDATE _timescaledb_catalog.bgw_job\nSET config = jsonb_build_object('mat_hypertable_id', :mat_id)\nWHERE id = :job_id;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT config FROM _timescaledb_catalog.bgw_job where id = :job_id;\n          config          \n--------------------------\n {\"mat_hypertable_id\": 2}\n\n\\set ON_ERROR_STOP 0\n\\set VERBOSITY default\nSELECT add_continuous_aggregate_policy('mat_m1', 20, 10, '1h'::interval, if_not_exists=>true);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('int_tab');\nERROR:  \"int_tab\" is not a continuous aggregate\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n-- add with NULL offset, readd with NULL offset\nSELECT add_continuous_aggregate_policy('mat_m1', 20, NULL, '1h'::interval, if_not_exists=>true);\n add_continuous_aggregate_policy \n---------------------------------\n                            1010\n\nSELECT add_continuous_aggregate_policy('mat_m1', 20, NULL, '1h'::interval, if_not_exists=>true); -- same param values, so we get a NOTICE\nNOTICE:  continuous aggregate refresh policy already exists for \"mat_m1\", skipping\nDETAIL:  A refresh policy with the same start and end offset already exists for continuous aggregate \"mat_m1\".\n add_continuous_aggregate_policy \n---------------------------------\n                              -1\n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, NULL, '1h'::interval, if_not_exists=>true); -- different values, so we get a WARNING\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, 20, '1h'::interval, if_not_exists=>true);\n add_continuous_aggregate_policy \n---------------------------------\n                            1011\n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, 20, '1h'::interval, if_not_exists=>true);\nNOTICE:  continuous aggregate refresh policy already exists for \"mat_m1\", skipping\nDETAIL:  A refresh policy with the same start and end offset already exists for continuous aggregate \"mat_m1\".\n add_continuous_aggregate_policy \n---------------------------------\n                              -1\n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, NULL, '1h'::interval, if_not_exists=>true);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n--this one will fail\nSELECT remove_continuous_aggregate_policy('mat_m1');\nERROR:  continuous aggregate policy not found for \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1', if_not_exists=>true);\nNOTICE:  continuous aggregate policy not found for \"mat_m1\", skipping\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n--now try to add a policy as a different user than the one that created the cagg\n--should fail\nSET ROLE :ROLE_DEFAULT_PERM_USER_2;\nSELECT add_continuous_aggregate_policy('mat_m1', 20, 10, '1h'::interval) as job_id ;\nERROR:  must be owner of continuous aggregate \"mat_m1\"\n\\set VERBOSITY terse\n\\set ON_ERROR_STOP 1\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nDROP MATERIALIZED VIEW mat_m1;\n--- code coverage tests : add policy for timestamp and date based table ---\nCREATE TABLE continuous_agg_max_mat_date(time DATE);\nSELECT create_hypertable('continuous_agg_max_mat_date', 'time');\n            create_hypertable             \n------------------------------------------\n (5,public,continuous_agg_max_mat_date,t)\n\nCREATE MATERIALIZED VIEW max_mat_view_date\n\tWITH (timescaledb.continuous, timescaledb.materialized_only=true)\n\tAS SELECT time_bucket('7 days', time)\n\t\tFROM continuous_agg_max_mat_date\n\t\tGROUP BY 1 WITH NO DATA;\n\\set ON_ERROR_STOP 0\n\\set VERBOSITY default\n-- Test 1 step policy for timestamp type buckets\nALTER materialized view max_mat_view_date set (timescaledb.compress = true);\nNOTICE:  defaulting compress_orderby to time_bucket\n-- Only works for cagg\nSELECT timescaledb_experimental.add_policies('continuous_agg_max_mat_date', refresh_start_offset => '1 day'::interval, refresh_end_offset => '2 day'::interval, compress_after => '20 days'::interval, drop_after => '25 days'::interval);\nERROR:  \"continuous_agg_max_mat_date\" is not a continuous aggregate\nSELECT timescaledb_experimental.show_policies('continuous_agg_max_mat_date');\nERROR:  \"continuous_agg_max_mat_date\" is not a continuous aggregate\nSELECT timescaledb_experimental.alter_policies('continuous_agg_max_mat_date', compress_after=>'16 days'::interval);\nERROR:  \"continuous_agg_max_mat_date\" is not a continuous aggregate\nSELECT timescaledb_experimental.remove_policies('continuous_agg_max_mat_date', false, 'policy_refresh_continuous_aggregate');\nERROR:  \"continuous_agg_max_mat_date\" is not a continuous aggregate\n-- No policy is added if one errors out\nSELECT timescaledb_experimental.add_policies('max_mat_view_date', refresh_start_offset => '1 day'::interval, refresh_end_offset => '2 day'::interval, compress_after => '20 days'::interval, drop_after => '25 days'::interval);\nERROR:  policy refresh window too small\nDETAIL:  The start and end offsets must cover at least two buckets in the valid time range of type \"date\".\nSELECT timescaledb_experimental.show_policies('max_mat_view_date');\n show_policies \n---------------\n\n-- Create open ended refresh_policy\nSELECT timescaledb_experimental.add_policies('max_mat_view_date', refresh_end_offset => '2 day'::interval);\n add_policies \n--------------\n t\n\nSELECT timescaledb_experimental.show_policies('max_mat_view_date');\n                                                                     show_policies                                                                      \n--------------------------------------------------------------------------------------------------------------------------------------------------------\n {\"policy_name\": \"policy_refresh_continuous_aggregate\", \"refresh_interval\": \"@ 1 hour\", \"refresh_end_offset\": \"@ 2 days\", \"refresh_start_offset\": null}\n\nSELECT timescaledb_experimental.remove_policies('max_mat_view_date', false, 'policy_refresh_continuous_aggregate');\n remove_policies \n-----------------\n t\n\nSELECT timescaledb_experimental.add_policies('max_mat_view_date', refresh_end_offset => '2 day'::interval, refresh_start_offset=>'-infinity');\n add_policies \n--------------\n t\n\nSELECT timescaledb_experimental.show_policies('max_mat_view_date');\n                                                                     show_policies                                                                      \n--------------------------------------------------------------------------------------------------------------------------------------------------------\n {\"policy_name\": \"policy_refresh_continuous_aggregate\", \"refresh_interval\": \"@ 1 hour\", \"refresh_end_offset\": \"@ 2 days\", \"refresh_start_offset\": null}\n\nSELECT timescaledb_experimental.remove_policies('max_mat_view_date', false, 'policy_refresh_continuous_aggregate');\n remove_policies \n-----------------\n t\n\nSELECT timescaledb_experimental.add_policies('max_mat_view_date', refresh_start_offset => '2 day'::interval);\n add_policies \n--------------\n t\n\nSELECT timescaledb_experimental.show_policies('max_mat_view_date');\n                                                                     show_policies                                                                      \n--------------------------------------------------------------------------------------------------------------------------------------------------------\n {\"policy_name\": \"policy_refresh_continuous_aggregate\", \"refresh_interval\": \"@ 1 hour\", \"refresh_end_offset\": null, \"refresh_start_offset\": \"@ 2 days\"}\n\nSELECT timescaledb_experimental.remove_policies('max_mat_view_date', false, 'policy_refresh_continuous_aggregate');\n remove_policies \n-----------------\n t\n\nSELECT timescaledb_experimental.add_policies('max_mat_view_date', refresh_start_offset => '2 day'::interval, refresh_end_offset=>'infinity');\n add_policies \n--------------\n t\n\nSELECT timescaledb_experimental.show_policies('max_mat_view_date');\n                                                                     show_policies                                                                      \n--------------------------------------------------------------------------------------------------------------------------------------------------------\n {\"policy_name\": \"policy_refresh_continuous_aggregate\", \"refresh_interval\": \"@ 1 hour\", \"refresh_end_offset\": null, \"refresh_start_offset\": \"@ 2 days\"}\n\nSELECT timescaledb_experimental.remove_policies('max_mat_view_date', false, 'policy_refresh_continuous_aggregate');\n remove_policies \n-----------------\n t\n\n-- Open ended at both sides, for code coverage\nSELECT timescaledb_experimental.add_policies('max_mat_view_date', refresh_end_offset => 'infinity', refresh_start_offset => '-infinity');\n add_policies \n--------------\n t\n\nSELECT timescaledb_experimental.show_policies('max_mat_view_date');\n                                                                  show_policies                                                                   \n--------------------------------------------------------------------------------------------------------------------------------------------------\n {\"policy_name\": \"policy_refresh_continuous_aggregate\", \"refresh_interval\": \"@ 1 hour\", \"refresh_end_offset\": null, \"refresh_start_offset\": null}\n\nSELECT timescaledb_experimental.remove_policies('max_mat_view_date', false, 'policy_refresh_continuous_aggregate');\n remove_policies \n-----------------\n t\n\n-- All policies are added in one step\nSELECT timescaledb_experimental.add_policies('max_mat_view_date', refresh_start_offset => '15 days'::interval, refresh_end_offset => '1 day'::interval, compress_after => '20 days'::interval, drop_after => '25 days'::interval);\n add_policies \n--------------\n t\n\nSELECT timescaledb_experimental.show_policies('max_mat_view_date');\n                                                                        show_policies                                                                         \n--------------------------------------------------------------------------------------------------------------------------------------------------------------\n {\"policy_name\": \"policy_compression\", \"compress_after\": \"@ 20 days\", \"compress_interval\": \"@ 12 hours\"}\n {\"policy_name\": \"policy_refresh_continuous_aggregate\", \"refresh_interval\": \"@ 1 hour\", \"refresh_end_offset\": \"@ 1 day\", \"refresh_start_offset\": \"@ 15 days\"}\n {\"drop_after\": \"@ 25 days\", \"policy_name\": \"policy_retention\", \"retention_interval\": \"@ 1 day\"}\n\n-- Alter policies\nSELECT timescaledb_experimental.alter_policies('max_mat_view_date', refresh_start_offset => '16 days'::interval, compress_after=>'26 days'::interval, drop_after => '40 days'::interval);\n alter_policies \n----------------\n t\n\nSELECT timescaledb_experimental.show_policies('max_mat_view_date');\n                                                                        show_policies                                                                         \n--------------------------------------------------------------------------------------------------------------------------------------------------------------\n {\"policy_name\": \"policy_compression\", \"compress_after\": \"@ 26 days\", \"compress_interval\": \"@ 12 hours\"}\n {\"policy_name\": \"policy_refresh_continuous_aggregate\", \"refresh_interval\": \"@ 1 hour\", \"refresh_end_offset\": \"@ 1 day\", \"refresh_start_offset\": \"@ 16 days\"}\n {\"drop_after\": \"@ 40 days\", \"policy_name\": \"policy_retention\", \"retention_interval\": \"@ 1 day\"}\n\n--Alter refresh_policy to make it open ended\nSELECT timescaledb_experimental.remove_policies('max_mat_view_date', false, 'policy_retention', 'policy_compression');\n remove_policies \n-----------------\n t\n\nSELECT timescaledb_experimental.alter_policies('max_mat_view_date', refresh_start_offset =>'-infinity');\n alter_policies \n----------------\n t\n\nSELECT timescaledb_experimental.show_policies('max_mat_view_date');\n                                                                     show_policies                                                                     \n-------------------------------------------------------------------------------------------------------------------------------------------------------\n {\"policy_name\": \"policy_refresh_continuous_aggregate\", \"refresh_interval\": \"@ 1 hour\", \"refresh_end_offset\": \"@ 1 day\", \"refresh_start_offset\": null}\n\nSELECT timescaledb_experimental.alter_policies('max_mat_view_date', refresh_end_offset =>'infinity', refresh_start_offset =>'5 days'::interval);\n alter_policies \n----------------\n t\n\nSELECT timescaledb_experimental.show_policies('max_mat_view_date');\n                                                                     show_policies                                                                      \n--------------------------------------------------------------------------------------------------------------------------------------------------------\n {\"policy_name\": \"policy_refresh_continuous_aggregate\", \"refresh_interval\": \"@ 1 hour\", \"refresh_end_offset\": null, \"refresh_start_offset\": \"@ 5 days\"}\n\n--Cross policy checks\n-- Refresh and compression policies overlap\nSELECT timescaledb_experimental.add_policies('max_mat_view_date', compress_after => '20 days'::interval, drop_after => '25 days'::interval);\n add_policies \n--------------\n t\n\nSELECT timescaledb_experimental.alter_policies('max_mat_view_date', compress_after=> '4 days'::interval);\nERROR:  refresh and columnstore policies overlap\nSELECT timescaledb_experimental.show_policies('max_mat_view_date');\n                                                                     show_policies                                                                      \n--------------------------------------------------------------------------------------------------------------------------------------------------------\n {\"policy_name\": \"policy_compression\", \"compress_after\": \"@ 20 days\", \"compress_interval\": \"@ 12 hours\"}\n {\"policy_name\": \"policy_refresh_continuous_aggregate\", \"refresh_interval\": \"@ 1 hour\", \"refresh_end_offset\": null, \"refresh_start_offset\": \"@ 5 days\"}\n {\"drop_after\": \"@ 25 days\", \"policy_name\": \"policy_retention\", \"retention_interval\": \"@ 1 day\"}\n\n-- Refresh and retention policies overlap\nSELECT timescaledb_experimental.alter_policies('max_mat_view_date', refresh_start_offset =>'5 days'::interval, drop_after=> '4 days'::interval);\nERROR:  refresh and retention policies overlap\nSELECT timescaledb_experimental.show_policies('max_mat_view_date');\n                                                                     show_policies                                                                      \n--------------------------------------------------------------------------------------------------------------------------------------------------------\n {\"policy_name\": \"policy_compression\", \"compress_after\": \"@ 20 days\", \"compress_interval\": \"@ 12 hours\"}\n {\"policy_name\": \"policy_refresh_continuous_aggregate\", \"refresh_interval\": \"@ 1 hour\", \"refresh_end_offset\": null, \"refresh_start_offset\": \"@ 5 days\"}\n {\"drop_after\": \"@ 25 days\", \"policy_name\": \"policy_retention\", \"retention_interval\": \"@ 1 day\"}\n\n--Do not allow refreshed data to be deleted\nSELECT add_retention_policy('continuous_agg_max_mat_date', '25 days'::interval);\n add_retention_policy \n----------------------\n                 1027\n\nSELECT timescaledb_experimental.alter_policies('max_mat_view_date', refresh_start_offset =>'25 days'::interval);\nERROR:  refresh policy of continuous aggregate and retention policy of underlying hypertable overlap\nSELECT remove_retention_policy('continuous_agg_max_mat_date');\n remove_retention_policy \n-------------------------\n \n\n-- Remove one or more policy\n-- Code coverage: no policy names provided\nSELECT timescaledb_experimental.remove_policies('max_mat_view_date', false);\n remove_policies \n-----------------\n f\n\n-- Code coverage: incorrect name of policy\nSELECT timescaledb_experimental.remove_policies('max_mat_view_date', false, 'refresh_policy');\nNOTICE:  No relevant policy found\n remove_policies \n-----------------\n f\n\nSELECT timescaledb_experimental.remove_policies('max_mat_view_date', false, 'policy_refresh_continuous_aggregate', 'policy_compression');\n remove_policies \n-----------------\n t\n\nSELECT timescaledb_experimental.show_policies('max_mat_view_date');\n                                          show_policies                                          \n-------------------------------------------------------------------------------------------------\n {\"drop_after\": \"@ 25 days\", \"policy_name\": \"policy_retention\", \"retention_interval\": \"@ 1 day\"}\n\n-- Add one policy\nSELECT timescaledb_experimental.add_policies('max_mat_view_date', refresh_start_offset => '15 day'::interval, refresh_end_offset => '1 day'::interval);\n add_policies \n--------------\n t\n\nSELECT timescaledb_experimental.show_policies('max_mat_view_date');\n                                                                        show_policies                                                                         \n--------------------------------------------------------------------------------------------------------------------------------------------------------------\n {\"policy_name\": \"policy_refresh_continuous_aggregate\", \"refresh_interval\": \"@ 1 hour\", \"refresh_end_offset\": \"@ 1 day\", \"refresh_start_offset\": \"@ 15 days\"}\n {\"drop_after\": \"@ 25 days\", \"policy_name\": \"policy_retention\", \"retention_interval\": \"@ 1 day\"}\n\n-- Remove all policies\nSELECT * FROM timescaledb_experimental.policies ORDER BY relation_name, proc_name;\n   relation_name   | relation_schema | schedule_interval |      proc_schema       |              proc_name              |                                     config                                     |   hypertable_schema   |      hypertable_name       \n-------------------+-----------------+-------------------+------------------------+-------------------------------------+--------------------------------------------------------------------------------+-----------------------+----------------------------\n max_mat_view_date | public          | @ 1 hour          | _timescaledb_functions | policy_refresh_continuous_aggregate | {\"end_offset\": \"@ 1 day\", \"start_offset\": \"@ 15 days\", \"mat_hypertable_id\": 6} | _timescaledb_internal | _materialized_hypertable_6\n max_mat_view_date | public          | @ 1 day           | _timescaledb_functions | policy_retention                    | {\"drop_after\": \"@ 25 days\", \"hypertable_id\": 6}                                | _timescaledb_internal | _materialized_hypertable_6\n\nSELECT timescaledb_experimental.remove_all_policies(NULL); -- should fail\n remove_all_policies \n---------------------\n f\n\nSELECT timescaledb_experimental.remove_all_policies('continuous_agg_max_mat_date'); -- should fail\nERROR:  \"continuous_agg_max_mat_date\" is not a continuous aggregate\nSELECT timescaledb_experimental.remove_all_policies('max_mat_view_date', false);\n remove_all_policies \n---------------------\n t\n\nSELECT timescaledb_experimental.remove_all_policies('max_mat_view_date', false); -- should fail\n remove_all_policies \n---------------------\n f\n\nCREATE OR REPLACE FUNCTION custom_func(jobid int, args jsonb) RETURNS RECORD LANGUAGE SQL AS\n$$\n  VALUES($1, $2, 'custom_func');\n$$;\n -- inject custom job\nSELECT add_job('custom_func','1h', config:='{\"type\":\"function\"}'::jsonb, initial_start => '2000-01-01 00:00:00+00'::timestamptz) AS job_id \\gset\nSELECT _timescaledb_functions.alter_job_set_hypertable_id( :job_id, 'max_mat_view_date'::regclass);\n alter_job_set_hypertable_id \n-----------------------------\n                        1029\n\nSELECT * FROM timescaledb_information.jobs WHERE job_id != 1 ORDER BY 1;\n job_id |      application_name      | schedule_interval | max_runtime | max_retries | retry_period | proc_schema |  proc_name  |       owner       | scheduled | fixed_schedule |        config        |          next_start          |        initial_start         | hypertable_schema |  hypertable_name  | check_schema | check_name \n--------+----------------------------+-------------------+-------------+-------------+--------------+-------------+-------------+-------------------+-----------+----------------+----------------------+------------------------------+------------------------------+-------------------+-------------------+--------------+------------\n   1029 | User-Defined Action [1029] | @ 1 hour          | @ 0         |          -1 | @ 5 mins     | public      | custom_func | default_perm_user | t         | t              | {\"type\": \"function\"} | Fri Dec 31 16:00:00 1999 PST | Fri Dec 31 16:00:00 1999 PST | public            | max_mat_view_date |              | \n\nSELECT timescaledb_experimental.remove_all_policies('max_mat_view_date', true); -- ignore custom job\nNOTICE:  Ignoring custom job\n remove_all_policies \n---------------------\n t\n\nSELECT delete_job(:job_id);\n delete_job \n------------\n \n\nDROP FUNCTION custom_func;\nSELECT timescaledb_experimental.show_policies('max_mat_view_date');\n show_policies \n---------------\n\nALTER materialized view max_mat_view_date set (timescaledb.compress = false);\nSELECT add_continuous_aggregate_policy('max_mat_view_date', '2 days', 10, '1 day'::interval);\nERROR:  invalid parameter value for end_offset\nHINT:  Use time interval with a continuous aggregate using timestamp-based time bucket.\n--start_interval < end_interval\nSELECT add_continuous_aggregate_policy('max_mat_view_date', '1 day'::interval, '2 days'::interval , '1 day'::interval) ;\nERROR:  policy refresh window too small\nDETAIL:  The start and end offsets must cover at least two buckets in the valid time range of type \"date\".\n--interval less than two buckets\nSELECT add_continuous_aggregate_policy('max_mat_view_date', '7 days', '1 day', '1 day'::interval);\nERROR:  policy refresh window too small\nDETAIL:  The start and end offsets must cover at least two buckets in the valid time range of type \"date\".\nSELECT add_continuous_aggregate_policy('max_mat_view_date', '14 days', '1 day', '1 day'::interval);\nERROR:  policy refresh window too small\nDETAIL:  The start and end offsets must cover at least two buckets in the valid time range of type \"date\".\nSELECT add_continuous_aggregate_policy('max_mat_view_date', '13 days', '-10 hours', '1 day'::interval);\nERROR:  policy refresh window too small\nDETAIL:  The start and end offsets must cover at least two buckets in the valid time range of type \"date\".\n\\set VERBOSITY terse\n\\set ON_ERROR_STOP 1\n-- Negative start offset gives two bucket window:\nSELECT add_continuous_aggregate_policy('max_mat_view_date', '13 days', '-1 day', '1 day'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1030\n\nSELECT remove_continuous_aggregate_policy('max_mat_view_date');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n-- Both offsets NULL:\nSELECT add_continuous_aggregate_policy('max_mat_view_date', NULL, NULL, '1 day'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1031\n\nSELECT remove_continuous_aggregate_policy('max_mat_view_date');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('max_mat_view_date', '15 days', '1 day', '1 day'::interval) as job_id \\gset\nSELECT config FROM _timescaledb_catalog.bgw_job\nWHERE id = :job_id;\n                                     config                                     \n--------------------------------------------------------------------------------\n {\"end_offset\": \"@ 1 day\", \"start_offset\": \"@ 15 days\", \"mat_hypertable_id\": 6}\n\nINSERT INTO continuous_agg_max_mat_date\n\tSELECT generate_series('2019-09-01'::date, '2019-09-10'::date, '1 day');\n--- to prevent NOTICES set message level to warning\nSET client_min_messages TO warning;\nCALL run_job(:job_id);\nRESET client_min_messages;\nDROP MATERIALIZED VIEW max_mat_view_date;\nCREATE TABLE continuous_agg_timestamp(time TIMESTAMP);\nSELECT create_hypertable('continuous_agg_timestamp', 'time');\nWARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n           create_hypertable           \n---------------------------------------\n (8,public,continuous_agg_timestamp,t)\n\nCREATE MATERIALIZED VIEW max_mat_view_timestamp\n\tWITH (timescaledb.continuous, timescaledb.materialized_only=true)\n\tAS SELECT time_bucket('7 days', time)\n\t\tFROM continuous_agg_timestamp\n\t\tGROUP BY 1 WITH NO DATA;\n--the start offset overflows the smallest time value, but is capped at\n--the min value\nSELECT add_continuous_aggregate_policy('max_mat_view_timestamp', '1000000 years', '1 day' , '1 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1033\n\nSELECT remove_continuous_aggregate_policy('max_mat_view_timestamp');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n\\set ON_ERROR_STOP 0\n\\set VERBOSITY default\n--start and end offset capped at the lowest time value, which means\n--zero size window\nSELECT add_continuous_aggregate_policy('max_mat_view_timestamp', '1000000 years', '900000 years' , '1 h'::interval);\nERROR:  policy refresh window too small\nDETAIL:  The start and end offsets must cover at least two buckets in the valid time range of type \"timestamp without time zone\".\nSELECT add_continuous_aggregate_policy('max_mat_view_timestamp', '301 days', '10 months' , '1 h'::interval);\nERROR:  policy refresh window too small\nDETAIL:  The start and end offsets must cover at least two buckets in the valid time range of type \"timestamp without time zone\".\n\\set VERBOSITY terse\n\\set ON_ERROR_STOP 1\nSELECT add_continuous_aggregate_policy('max_mat_view_timestamp', '15 days', '1 h'::interval , '1 h'::interval) as job_id \\gset\n--- to prevent NOTICES set message level to warning\nSET client_min_messages TO warning;\nCALL run_job(:job_id);\nRESET client_min_messages ;\nSELECT config FROM _timescaledb_catalog.bgw_job\nWHERE id = :job_id;\n                                     config                                      \n---------------------------------------------------------------------------------\n {\"end_offset\": \"@ 1 hour\", \"start_offset\": \"@ 15 days\", \"mat_hypertable_id\": 9}\n\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET timezone TO PST8PDT;\nUPDATE _timescaledb_catalog.bgw_job\nSET config = jsonb_build_object('mat_hypertable_id', :mat_id)\nWHERE id = :job_id;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nSELECT config FROM _timescaledb_catalog.bgw_job where id = :job_id;\n          config          \n--------------------------\n {\"mat_hypertable_id\": 2}\n\n\\set ON_ERROR_STOP 0\nSELECT add_continuous_aggregate_policy('max_mat_view_timestamp', '15 day', '1 day', '1h'::interval, if_not_exists=>true);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"max_mat_view_timestamp\"\nSELECT add_continuous_aggregate_policy('max_mat_view_timestamp', 'xyz', '1 day', '1h'::interval, if_not_exists=>true);\nERROR:  invalid input syntax for type interval: \"xyz\"\n\\set ON_ERROR_STOP 1\nDROP MATERIALIZED VIEW max_mat_view_timestamp;\n--smallint table\nCREATE TABLE smallint_tab (a smallint);\nSELECT table_name FROM create_hypertable('smallint_tab', 'a', chunk_time_interval=> 10);\n  table_name  \n--------------\n smallint_tab\n\nCREATE OR REPLACE FUNCTION integer_now_smallint_tab() returns smallint LANGUAGE SQL STABLE as $$ SELECT coalesce(max(a)::smallint, 0::smallint) FROM smallint_tab ; $$;\nSELECT set_integer_now_func('smallint_tab', 'integer_now_smallint_tab');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW mat_smallint( a, countb )\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nSELECT time_bucket( SMALLINT '1', a) , count(*)\nFROM smallint_tab\nGROUP BY 1 WITH NO DATA;\n\\set ON_ERROR_STOP 0\n\\set VERBOSITY default\n-- Test 1 step policy for smallint type buckets\nALTER materialized view mat_smallint set (timescaledb.compress = true);\nNOTICE:  defaulting compress_orderby to a\n-- All policies are added in one step\nSELECT timescaledb_experimental.add_policies('mat_smallint', refresh_start_offset => 10::smallint, refresh_end_offset => 1::smallint, compress_after => 11::smallint, drop_after => 20::smallint);\n add_policies \n--------------\n t\n\nSELECT timescaledb_experimental.show_policies('mat_smallint');\n                                                                show_policies                                                                \n---------------------------------------------------------------------------------------------------------------------------------------------\n {\"policy_name\": \"policy_compression\", \"compress_after\": 11, \"compress_interval\": \"@ 1 day\"}\n {\"policy_name\": \"policy_refresh_continuous_aggregate\", \"refresh_interval\": \"@ 1 hour\", \"refresh_end_offset\": 1, \"refresh_start_offset\": 10}\n {\"drop_after\": 20, \"policy_name\": \"policy_retention\", \"retention_interval\": \"@ 1 day\"}\n\n-- Alter policies\nSELECT timescaledb_experimental.alter_policies('mat_smallint',  refresh_start_offset => 11::smallint, compress_after=>13::smallint, drop_after => 25::smallint);\n alter_policies \n----------------\n t\n\nSELECT timescaledb_experimental.show_policies('mat_smallint');\n                                                                show_policies                                                                \n---------------------------------------------------------------------------------------------------------------------------------------------\n {\"policy_name\": \"policy_compression\", \"compress_after\": 13, \"compress_interval\": \"@ 1 day\"}\n {\"policy_name\": \"policy_refresh_continuous_aggregate\", \"refresh_interval\": \"@ 1 hour\", \"refresh_end_offset\": 1, \"refresh_start_offset\": 11}\n {\"drop_after\": 25, \"policy_name\": \"policy_retention\", \"retention_interval\": \"@ 1 day\"}\n\nSELECT timescaledb_experimental.remove_all_policies('mat_smallint', false);\n remove_all_policies \n---------------------\n t\n\nALTER materialized view mat_smallint set (timescaledb.compress = false);\nSELECT add_continuous_aggregate_policy('mat_smallint', 15, 0 , '1 h'::interval);\nERROR:  invalid parameter value for start_offset\nHINT:  Use time interval of type smallint with the continuous aggregate.\nSELECT add_continuous_aggregate_policy('mat_smallint', 98898::smallint , 0::smallint, '1 h'::interval);\nERROR:  smallint out of range\nSELECT add_continuous_aggregate_policy('mat_smallint', 5::smallint, 10::smallint , '1 h'::interval) as job_id \\gset\nERROR:  policy refresh window too small\nDETAIL:  The start and end offsets must cover at least two buckets in the valid time range of type \"smallint\".\n\\set VERBOSITY terse\n\\set ON_ERROR_STOP 1\nSELECT add_continuous_aggregate_policy('mat_smallint', 15::smallint, 0::smallint , '1 h'::interval) as job_id \\gset\nINSERT INTO smallint_tab VALUES(5);\nINSERT INTO smallint_tab VALUES(10);\nINSERT INTO smallint_tab VALUES(20);\nCALL run_job(:job_id);\nSELECT * FROM mat_smallint ORDER BY 1;\n a  | countb \n----+--------\n  5 |      1\n 10 |      1\n\n--remove all the data--\nTRUNCATE table smallint_tab;\nCALL refresh_continuous_aggregate('mat_smallint', NULL, NULL);\nSELECT * FROM mat_smallint ORDER BY 1;\n a | countb \n---+--------\n\n-- Case 1: overflow by subtracting from PG_INT16_MIN\n--overflow start_interval, end_interval [-32768, -32768)\nSELECT remove_continuous_aggregate_policy('mat_smallint');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nINSERT INTO smallint_tab VALUES( -32768 );\nSELECT integer_now_smallint_tab();\n integer_now_smallint_tab \n--------------------------\n                   -32768\n\nSELECT add_continuous_aggregate_policy('mat_smallint', 10::smallint, 5::smallint , '1 h'::interval) as job_id \\gset\n\\set ON_ERROR_STOP 0\nCALL run_job(:job_id);\nERROR:  invalid refresh window\n\\set ON_ERROR_STOP 1\nSELECT * FROM mat_smallint ORDER BY 1;\n a | countb \n---+--------\n\n-- overflow start_interval. now this runs as range is capped [-32768, -32765)\nINSERT INTO smallint_tab VALUES( -32760 );\nSELECT maxval, maxval - 10, maxval -5 FROM integer_now_smallint_tab() as maxval;\n maxval | ?column? | ?column? \n--------+----------+----------\n -32760 |   -32770 |   -32765\n\nCALL run_job(:job_id);\nSELECT * FROM mat_smallint ORDER BY 1;\n   a    | countb \n--------+--------\n -32768 |      1\n\n--remove all the data--\nTRUNCATE table smallint_tab;\nCALL refresh_continuous_aggregate('mat_smallint', NULL, NULL);\nSELECT * FROM mat_smallint ORDER BY 1;\n a | countb \n---+--------\n\n-- Case 2: overflow by subtracting from PG_INT16_MAX\n--overflow start and end . will fail as range is [32767, 32767]\nSELECT remove_continuous_aggregate_policy('mat_smallint');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nINSERT INTO smallint_tab VALUES( 32766 );\nINSERT INTO smallint_tab VALUES( 32767 );\nSELECT maxval, maxval - (-1), maxval - (-2) FROM integer_now_smallint_tab() as maxval;\n maxval | ?column? | ?column? \n--------+----------+----------\n  32767 |    32768 |    32769\n\nSELECT add_continuous_aggregate_policy('mat_smallint', -1::smallint, -3::smallint , '1 h'::interval) as job_id \\gset\n\\set ON_ERROR_STOP 0\nCALL run_job(:job_id);\nERROR:  invalid refresh window\n\\set ON_ERROR_STOP 1\nSELECT * FROM mat_smallint ORDER BY 1;\n a | countb \n---+--------\n\nSELECT remove_continuous_aggregate_policy('mat_smallint');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n--overflow end . will work range is [32765, 32767)\nSELECT maxval, maxval - (1), maxval - (-2) FROM integer_now_smallint_tab() as maxval;\n maxval | ?column? | ?column? \n--------+----------+----------\n  32767 |    32766 |    32769\n\nSELECT add_continuous_aggregate_policy('mat_smallint', 1::smallint, -3::smallint , '1 h'::interval) as job_id \\gset\n\\set ON_ERROR_STOP 0\nCALL run_job(:job_id);\nSELECT * FROM mat_smallint ORDER BY 1;\n   a   | countb \n-------+--------\n 32766 |      1\n\n-- tests for interval argument conversions\n--\n\\set ON_ERROR_STOP 0\nSELECT add_continuous_aggregate_policy('mat_smallint', 15, 10, '1h'::interval, if_not_exists=>true);\nERROR:  invalid parameter value for start_offset\nSELECT add_continuous_aggregate_policy('mat_smallint', '15', 10, '1h'::interval, if_not_exists=>true);\nERROR:  invalid parameter value for end_offset\nSELECT add_continuous_aggregate_policy('mat_smallint', '15', '10', '1h'::interval, if_not_exists=>true);\n add_continuous_aggregate_policy \n---------------------------------\n                            1045\n\n\\set ON_ERROR_STOP 1\n--bigint table\nCREATE TABLE bigint_tab (a bigint);\nSELECT table_name FROM create_hypertable('bigint_tab', 'a', chunk_time_interval=> 10);\n table_name \n------------\n bigint_tab\n\nCREATE OR REPLACE FUNCTION integer_now_bigint_tab() returns bigint LANGUAGE SQL STABLE as $$ SELECT 20::bigint $$;\nSELECT set_integer_now_func('bigint_tab', 'integer_now_bigint_tab');\n set_integer_now_func \n----------------------\n \n\nCREATE MATERIALIZED VIEW mat_bigint( a, countb )\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nas\nSELECT time_bucket( BIGINT '1', a) , count(*)\nFROM bigint_tab\nGROUP BY 1 WITH NO DATA;\n-- Test 1 step policy for bigint type buckets\nALTER materialized view mat_bigint set (timescaledb.compress = true);\nNOTICE:  defaulting compress_orderby to a\n-- All policies are added in one step\nSELECT timescaledb_experimental.add_policies('mat_bigint', refresh_start_offset => 10::bigint, refresh_end_offset => 1::bigint, compress_after => 11::bigint, drop_after => 20::bigint);\n add_policies \n--------------\n t\n\nSELECT timescaledb_experimental.show_policies('mat_bigint');\n                                                                show_policies                                                                \n---------------------------------------------------------------------------------------------------------------------------------------------\n {\"policy_name\": \"policy_compression\", \"compress_after\": 11, \"compress_interval\": \"@ 1 day\"}\n {\"policy_name\": \"policy_refresh_continuous_aggregate\", \"refresh_interval\": \"@ 1 hour\", \"refresh_end_offset\": 1, \"refresh_start_offset\": 10}\n {\"drop_after\": 20, \"policy_name\": \"policy_retention\", \"retention_interval\": \"@ 1 day\"}\n\n-- Alter policies\nSELECT timescaledb_experimental.alter_policies('mat_bigint',  refresh_start_offset => 11::bigint, compress_after=>13::bigint, drop_after => 25::bigint);\n alter_policies \n----------------\n t\n\nSELECT timescaledb_experimental.show_policies('mat_bigint');\n                                                                show_policies                                                                \n---------------------------------------------------------------------------------------------------------------------------------------------\n {\"policy_name\": \"policy_compression\", \"compress_after\": 13, \"compress_interval\": \"@ 1 day\"}\n {\"policy_name\": \"policy_refresh_continuous_aggregate\", \"refresh_interval\": \"@ 1 hour\", \"refresh_end_offset\": 1, \"refresh_start_offset\": 11}\n {\"drop_after\": 25, \"policy_name\": \"policy_retention\", \"retention_interval\": \"@ 1 day\"}\n\nSELECT timescaledb_experimental.remove_all_policies('mat_bigint', false);\n remove_all_policies \n---------------------\n t\n\nALTER materialized view mat_bigint set (timescaledb.compress = false);\n\\set ON_ERROR_STOP 0\nSELECT add_continuous_aggregate_policy('mat_bigint', 5::bigint, 10::bigint , '1 h'::interval) ;\nERROR:  policy refresh window too small\n\\set ON_ERROR_STOP 1\nSELECT add_continuous_aggregate_policy('mat_bigint', 15::bigint, 0::bigint , '1 h'::interval) as job_mid \\gset\nINSERT INTO bigint_tab VALUES(5);\nINSERT INTO bigint_tab VALUES(10);\nINSERT INTO bigint_tab VALUES(20);\nCALL run_job(:job_mid);\nSELECT * FROM mat_bigint ORDER BY 1;\n a  | countb \n----+--------\n  5 |      1\n 10 |      1\n\n-- test NULL for end\nSELECT remove_continuous_aggregate_policy('mat_bigint');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_bigint', 1::smallint, NULL , '1 h'::interval) as job_id \\gset\nINSERT INTO bigint_tab VALUES(500);\nCALL run_job(:job_id);\nSELECT * FROM mat_bigint WHERE a>100 ORDER BY 1;\n  a  | countb \n-----+--------\n 500 |      1\n\nALTER MATERIALIZED VIEW mat_bigint SET (timescaledb.compress);\nNOTICE:  defaulting compress_orderby to a\nALTER MATERIALIZED VIEW mat_smallint SET (timescaledb.compress);\nNOTICE:  defaulting compress_orderby to a\n-- With immutable compressed chunks, these policies would fail by overlapping the refresh window\nSELECT add_compression_policy('mat_smallint', -4::smallint);\n add_compression_policy \n------------------------\n                   1054\n\nSELECT remove_compression_policy('mat_smallint');\n remove_compression_policy \n---------------------------\n t\n\nSELECT add_compression_policy('mat_bigint', 0::bigint);\n add_compression_policy \n------------------------\n                   1055\n\nSELECT remove_compression_policy('mat_bigint');\n remove_compression_policy \n---------------------------\n t\n\n-- End previous limitation tests\nSELECT add_compression_policy('mat_smallint', 5::smallint);\n add_compression_policy \n------------------------\n                   1056\n\nSELECT add_compression_policy('mat_bigint', 20::bigint);\n add_compression_policy \n------------------------\n                   1057\n\n-- end of coverage tests\n--TEST continuous aggregate + compression policy on caggs\nCREATE TABLE metrics (\n    time timestamptz NOT NULL,\n    device_id int,\n    device_id_peer int,\n    v0 int,\n    v1 int,\n    v2 float,\n    v3 float\n);\nSELECT create_hypertable('metrics', 'time');\n   create_hypertable   \n-----------------------\n (18,public,metrics,t)\n\nINSERT INTO metrics (time, device_id, device_id_peer, v0, v1, v2, v3)\nSELECT time,\n    device_id,\n    0,\n    device_id + 1,\n    device_id + 2,\n    0.5,\n    NULL\nFROM generate_series('2000-01-01 0:00:00+0'::timestamptz, '2000-01-02 23:55:00+0', '20m') gtime (time),\n    generate_series(1, 2, 1) gdevice (device_id);\nALTER TABLE metrics SET ( timescaledb.compress );\nSELECT compress_chunk(ch) FROM show_chunks('metrics') ch;\n              compress_chunk              \n------------------------------------------\n _timescaledb_internal._hyper_18_19_chunk\n\nCREATE MATERIALIZED VIEW metrics_cagg WITH (timescaledb.continuous,\n  timescaledb.materialized_only = true)\nAS\nSELECT time_bucket('1 day', time) as dayb, device_id,\n       sum(v0), avg(v3)\nFROM metrics\nGROUP BY 1, 2\nWITH NO DATA;\n-- this was previously crashing\nSELECT add_continuous_aggregate_policy('metrics_cagg', '7 day'::interval, NULL, '1 h'::interval, if_not_exists => true);\n add_continuous_aggregate_policy \n---------------------------------\n                            1058\n\n\\set ON_ERROR_STOP 0\nSELECT add_continuous_aggregate_policy('metrics_cagg', '7 day'::interval, '1 day'::interval, '1 h'::interval, if_not_exists => true);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"metrics_cagg\"\nSELECT remove_continuous_aggregate_policy('metrics_cagg');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('metrics_cagg', NULL, '1 day'::interval, '1h'::interval, if_not_exists=>true);\n add_continuous_aggregate_policy \n---------------------------------\n                            1059\n\nSELECT add_continuous_aggregate_policy('metrics_cagg', NULL, '1 day'::interval, '1h'::interval, if_not_exists=>true); -- same param values, so we get a NOTICE\nNOTICE:  continuous aggregate refresh policy already exists for \"metrics_cagg\", skipping\n add_continuous_aggregate_policy \n---------------------------------\n                              -1\n\nSELECT add_continuous_aggregate_policy('metrics_cagg', NULL, NULL, '1h'::interval, if_not_exists=>true); -- different values, so we get a WARNING\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"metrics_cagg\"\nSELECT remove_continuous_aggregate_policy('metrics_cagg');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n--can set compression policy only after setting up refresh policy --\nSELECT add_compression_policy('metrics_cagg', '1 day'::interval);\nERROR:  setup a refresh policy for \"metrics_cagg\" before setting up a columnstore policy\n--can set compression policy only after enabling compression --\nSELECT add_continuous_aggregate_policy('metrics_cagg', '7 day'::interval, '1 day'::interval, '1 h'::interval) as \"REFRESH_JOB\" \\gset\nSELECT add_compression_policy('metrics_cagg', '8 day'::interval) AS \"COMP_JOB\" ;\nERROR:  columnstore not enabled on continuous aggregate \"metrics_cagg\"\nALTER MATERIALIZED VIEW metrics_cagg SET (timescaledb.compress);\nNOTICE:  defaulting compress_orderby to dayb,device_id\n--cannot use compress_created_before with cagg\nSELECT add_compression_policy('metrics_cagg', compress_created_before => '8 day'::interval) AS \"COMP_JOB\" ;\nERROR:  cannot use \"compress_created_before\" with continuous aggregate \"metrics_cagg\" \n\\set ON_ERROR_STOP 1\nSELECT add_compression_policy('metrics_cagg', '8 day'::interval) AS \"COMP_JOB\" ;\n COMP_JOB \n----------\n     1061\n\nSELECT remove_compression_policy('metrics_cagg');\n remove_compression_policy \n---------------------------\n t\n\nSELECT add_compression_policy('metrics_cagg', '8 day'::interval) AS \"COMP_JOB\" \\gset\n--verify that jobs were added for the policies ---\nSELECT materialization_hypertable_name AS \"MAT_TABLE_NAME\",\n       view_name AS \"VIEW_NAME\"\nFROM timescaledb_information.continuous_aggregates\nWHERE view_name = 'metrics_cagg' \\gset\nSELECT count(*) FROM timescaledb_information.jobs\nWHERE hypertable_name = :'VIEW_NAME';\n count \n-------\n     2\n\n--exec the cagg compression job --\nCALL refresh_continuous_aggregate('metrics_cagg', NULL, '2001-02-01 00:00:00+0');\nCALL run_job(:COMP_JOB);\nSELECT count(*), count(*) FILTER ( WHERE is_compressed is TRUE  )\nFROM timescaledb_information.chunks\nWHERE hypertable_name = :'MAT_TABLE_NAME' ORDER BY 1;\n count | count \n-------+-------\n     1 |     1\n\n--add some new data into metrics_cagg so that cagg policy job has something to do\nINSERT INTO metrics (time, device_id, device_id_peer, v0, v1, v2, v3)\nSELECT now() - '5 day'::interval, 102, 0, 10, 10, 10, 10;\nCALL run_job(:REFRESH_JOB);\n--now we have a new chunk and it is not compressed\nSELECT count(*), count(*) FILTER ( WHERE is_compressed is TRUE  )\nFROM timescaledb_information.chunks\nWHERE hypertable_name = :'MAT_TABLE_NAME' ORDER BY 1;\n count | count \n-------+-------\n     2 |     1\n\n--verify that both jobs are dropped when view is dropped\nDROP MATERIALIZED VIEW metrics_cagg;\nNOTICE:  drop cascades to 2 other objects\nSELECT count(*) FROM timescaledb_information.jobs\nWHERE hypertable_name = :'VIEW_NAME';\n count \n-------\n     0\n\n-- add test case for issue 4252\nCREATE TABLE IF NOT EXISTS sensor_data(\ntime TIMESTAMPTZ NOT NULL,\nsensor_id INTEGER,\ntemperature DOUBLE PRECISION,\ncpu DOUBLE PRECISION);\nSELECT create_hypertable('sensor_data','time');\n     create_hypertable     \n---------------------------\n (22,public,sensor_data,t)\n\nINSERT INTO sensor_data(time, sensor_id, cpu, temperature)\nSELECT\ntime,\nsensor_id,\nextract(dow from time) AS cpu,\nextract(doy from time) AS temperature\nFROM\ngenerate_series('2022-05-05'::timestamp at time zone 'UTC' - interval '6 weeks', '2022-05-05'::timestamp at time zone 'UTC', interval '5 hours') as g1(time),\ngenerate_series(1,1000,1) as g2(sensor_id);\nCREATE materialized view deals_best_weekly\nWITH (timescaledb.continuous) AS\nSELECT\ntime_bucket('7 days', \"time\") AS bucket,\navg(temperature) AS avg_temp,\nmax(cpu) AS max_rating\nFROM sensor_data\nGROUP BY bucket\nWITH NO DATA;\nCREATE materialized view deals_best_daily\nWITH (timescaledb.continuous) AS\nSELECT\ntime_bucket('1 day', \"time\") AS bucket,\navg(temperature) AS avg_temp,\nmax(cpu) AS max_rating\nFROM sensor_data\nGROUP BY bucket\nWITH NO DATA;\nALTER materialized view deals_best_weekly set (timescaledb.materialized_only=true);\nALTER materialized view deals_best_daily set (timescaledb.materialized_only=true);\n-- we have data from 6 weeks before to May 5 2022 (Thu)\nCALL refresh_continuous_aggregate('deals_best_weekly', '2022-04-24', '2022-05-03');\nSELECT * FROM deals_best_weekly ORDER BY bucket;\n            bucket            |     avg_temp     | max_rating \n------------------------------+------------------+------------\n Sun Apr 24 17:00:00 2022 PDT | 117.764705882353 |          6\n\nCALL refresh_continuous_aggregate('deals_best_daily', '2022-04-20', '2022-05-04');\nSELECT * FROM deals_best_daily ORDER BY bucket LIMIT 2;\n            bucket            | avg_temp | max_rating \n------------------------------+----------+------------\n Wed Apr 20 17:00:00 2022 PDT |    110.8 |          4\n Thu Apr 21 17:00:00 2022 PDT |   111.75 |          5\n\n-- expect to get an up-to-date notice\nCALL refresh_continuous_aggregate('deals_best_weekly', '2022-04-24', '2022-05-05');\nNOTICE:  continuous aggregate \"deals_best_weekly\" is already up-to-date\nSELECT * FROM deals_best_weekly ORDER BY bucket;\n            bucket            |     avg_temp     | max_rating \n------------------------------+------------------+------------\n Sun Apr 24 17:00:00 2022 PDT | 117.764705882353 |          6\n\n-- github issue 5907: segfault when creating 1-step policies on cagg\n-- whose underlying hypertable has a retention policy setup\nCREATE TABLE t(a integer NOT NULL, b integer);\nSELECT create_hypertable('t', 'a', chunk_time_interval=> 10);\n create_hypertable \n-------------------\n (25,public,t,t)\n\nCREATE OR REPLACE FUNCTION unix_now() returns int LANGUAGE SQL IMMUTABLE as $$ SELECT extract(epoch from now())::INT $$;\nSELECT set_integer_now_func('t', 'unix_now');\n set_integer_now_func \n----------------------\n \n\nSELECT add_retention_policy('t', 20);\n add_retention_policy \n----------------------\n                 1063\n\nCREATE MATERIALIZED VIEW cagg(a, sumb) WITH (timescaledb.continuous)\nAS SELECT time_bucket(1, a), sum(b)\n   FROM t GROUP BY time_bucket(1, a);\nNOTICE:  continuous aggregate \"cagg\" is already up-to-date\nSELECT timescaledb_experimental.add_policies('cagg');\n add_policies \n--------------\n f\n\n-- Issue #6902\n-- Fix timestamp out of range in a refresh policy when setting `end_offset=>NULL`\n-- for a CAgg with variable sized bucket (i.e: using `time_bucket` with timezone)\nCREATE TABLE issue_6902 (\n  ts  TIMESTAMPTZ NOT NULL,\n  temperature NUMERIC\n) WITH (\n  timescaledb.hypertable,\n  timescaledb.partition_column='ts',\n  timescaledb.chunk_interval='1 day',\n  timescaledb.compress='off'\n);\nINSERT INTO issue_6902\nSELECT t, 1 FROM generate_series(now() - interval '3 hours', now(), interval '1 minute') AS t;\nCREATE MATERIALIZED VIEW issue_6902_by_hour\nWITH (timescaledb.continuous) AS\nSELECT\n  time_bucket(INTERVAL '1 hour', ts, 'America/Sao_Paulo') AS bucket, -- using timezone\n  MAX(temperature),\n  MIN(temperature),\n  COUNT(*)\nFROM issue_6902\nGROUP BY 1\nWITH NO DATA;\nSELECT add_continuous_aggregate_policy (\n  'issue_6902_by_hour',\n  start_offset => INTERVAL '3 hours',\n  end_offset => NULL,\n  schedule_interval => INTERVAL '12 hour',\n  initial_start => now() + INTERVAL '12 hour'\n) AS job_id \\gset\n-- 181 rows\nCALL run_job(:job_id);\nSELECT count(*) FROM issue_6902;\n count \n-------\n   181\n\n-- run again without any change, remain 181 rows\nCALL run_job(:job_id);\nSELECT count(*) FROM issue_6902;\n count \n-------\n   181\n\n-- change existing data\nUPDATE issue_6902\nSET temperature = temperature + 1;\n-- run again without any change, remain 181 rows\nCALL run_job(:job_id);\nSELECT count(*) FROM issue_6902;\n count \n-------\n   181\n\n-- insert more data\nINSERT INTO issue_6902\nSELECT t, 1 FROM generate_series(now() - interval '3 hours', now(), interval '1 minute') AS t;\n-- run again without and should have 362 rows\nCALL run_job(:job_id);\nSELECT count(*) FROM issue_6902;\n count \n-------\n   362\n\n-- test untyped interval error handling\nCREATE TABLE m(time timestamptz) WITH (tsdb.hypertable);\nNOTICE:  using column \"time\" as partitioning column\nCREATE MATERIALIZED VIEW cagg_error WITH (tsdb.continuous)\nAS SELECT time_bucket('1 day', time) FROM m GROUP BY 1;\nNOTICE:  continuous aggregate \"cagg_error\" is already up-to-date\nSELECT timescaledb_experimental.add_policies('cagg_error', drop_after => '20 days');\nERROR:  unsupported interval argument type: unknown\n"
  },
  {
    "path": "tsl/test/expected/cagg_policy_concurrent.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- Test creation of multiple refresh policies\nSET timezone TO PST8PDT;\nSET timescaledb.current_timestamp_mock TO '2025-06-01 0:30:00+00';\nSELECT setseed(1);\n setseed \n---------\n \n\n-- test interval checking with bigint\nCREATE TABLE overlap_test_bigint (\n    time BIGINT NOT NULL,\n    a INTEGER,\n    b INTEGER\n);\nSELECT create_hypertable('overlap_test_bigint', 'time', chunk_time_interval => 100);\n        create_hypertable         \n----------------------------------\n (1,public,overlap_test_bigint,t)\n\nCREATE OR REPLACE FUNCTION integer_now_overlap_test_bigint()\nRETURNS BIGINT LANGUAGE SQL STABLE AS\n$$ SELECT COALESCE(MAX(time), 0) FROM overlap_test_bigint $$;\nSELECT set_integer_now_func('overlap_test_bigint', 'integer_now_overlap_test_bigint');\n set_integer_now_func \n----------------------\n \n\nINSERT INTO overlap_test_bigint\nSELECT i, (i % 5), random() * 100\nFROM generate_series(1, 2000) i;\nCREATE MATERIALIZED VIEW mat_m1(time, counta)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nAS\nSELECT\n    time_bucket(10, time) AS bucket,\n    count(a),\n    sum(b)\nFROM overlap_test_bigint\nGROUP BY 1\nWITH NO DATA;\n/* Test interval checking when multiple policies are created on the same cagg */\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, 1000::bigint, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1000\n\nSELECT add_continuous_aggregate_policy('mat_m1', 1000::bigint, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1001\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n/* Creating policies in either order should work */\nSELECT add_continuous_aggregate_policy('mat_m1', 1000::bigint, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1002\n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, 1000::bigint, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1003\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, 3000::bigint, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1004\n\nSELECT add_continuous_aggregate_policy('mat_m1', 2000::bigint, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1005\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n/* Test non-null offsets on both sides too */\nSELECT add_continuous_aggregate_policy('mat_m1', 2000::bigint, 1000::bigint, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1006\n\nSELECT add_continuous_aggregate_policy('mat_m1', 4000::bigint, 3000::bigint,'12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1007\n\nSELECT add_continuous_aggregate_policy('mat_m1', 3000::bigint, 2000::bigint, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1008\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n/* Check overlap is detected correctly */\n\\set ON_ERROR_STOP 0\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, 1000::bigint, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1009\n\nSELECT add_continuous_aggregate_policy('mat_m1', 2000::bigint, NULL, '12 h'::interval);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', 2000::bigint, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1010\n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, 1000::bigint, '12 h'::interval);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', 5000::bigint, 1000::bigint, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1011\n\nSELECT add_continuous_aggregate_policy('mat_m1', 4000::bigint, 2000::bigint, '12 h'::interval);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', 4000::bigint, 2000::bigint, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1012\n\nSELECT add_continuous_aggregate_policy('mat_m1', 5000::bigint, 1000::bigint, '12 h'::interval);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, 2000::bigint, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1013\n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, 1000::bigint, '12 h'::interval);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', 2000::bigint, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1014\n\nSELECT add_continuous_aggregate_policy('mat_m1', 1000::bigint, NULL, '12 h'::interval);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n\\set ON_ERROR_STOP 1\n/* Check behaviour when exact policy is already defined */\n\\set ON_ERROR_STOP 0\n/*if_not_exists=false*/\nSELECT add_continuous_aggregate_policy('mat_m1', 4000::bigint, 2000::bigint, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1015\n\nSELECT add_continuous_aggregate_policy('mat_m1', 2000::bigint, 1000::bigint, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1016\n\nSELECT add_continuous_aggregate_policy('mat_m1', 2000::bigint, 1000::bigint, '12 h'::interval);\nERROR:  continuous aggregate refresh policy already exists for \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1017\n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, NULL, '12 h'::interval);\nERROR:  continuous aggregate refresh policy already exists for \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n\\set ON_ERROR_STOP 1\n/*if_not_exists => true*/\nSELECT add_continuous_aggregate_policy('mat_m1', 4000::bigint, 2000::bigint, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1018\n\nSELECT add_continuous_aggregate_policy('mat_m1', 2000::bigint, 1000::bigint, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1019\n\nSELECT add_continuous_aggregate_policy('mat_m1', 2000::bigint, 1000::bigint, '12 h'::interval, if_not_exists => true);\nNOTICE:  continuous aggregate refresh policy already exists for \"mat_m1\", skipping\n add_continuous_aggregate_policy \n---------------------------------\n                              -1\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1020\n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, NULL, '12 h'::interval, if_not_exists => true);\nNOTICE:  continuous aggregate refresh policy already exists for \"mat_m1\", skipping\n add_continuous_aggregate_policy \n---------------------------------\n                              -1\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n/* Throw an error if there is an overlap even if if_not_exists => true */\nSELECT add_continuous_aggregate_policy('mat_m1', 4000::bigint, 2000::bigint, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1021\n\n\\set ON_ERROR_STOP 0\nSELECT add_continuous_aggregate_policy('mat_m1', 3000::bigint, 1000::bigint, '12 h'::interval, if_not_exists => true);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\n\\set ON_ERROR_STOP 1\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n/* Test `alter_job` changing the config */\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, 3000::bigint, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1022\n\nSELECT id AS job_id, config AS config FROM _timescaledb_catalog.bgw_job WHERE proc_name = 'policy_refresh_continuous_aggregate' \\gset\nSELECT add_continuous_aggregate_policy('mat_m1', 2000::bigint, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1023\n\n/* Alter end offset but don't overlap */\nSELECT jsonb_set(:'config', '{end_offset}', '2000') AS config \\gset\nSELECT * FROM alter_job(:job_id, config := :'config');\n job_id | schedule_interval | max_runtime | max_retries | retry_period | scheduled |                               config                               | next_start |                           check_config                           | fixed_schedule | initial_start | timezone |              application_name              \n--------+-------------------+-------------+-------------+--------------+-----------+--------------------------------------------------------------------+------------+------------------------------------------------------------------+----------------+---------------+----------+--------------------------------------------\n   1022 | @ 12 hours        | @ 0         |          -1 | @ 12 hours   | t         | {\"end_offset\": 2000, \"start_offset\": null, \"mat_hypertable_id\": 2} | -infinity  | _timescaledb_functions.policy_refresh_continuous_aggregate_check | f              |               |          | Refresh Continuous Aggregate Policy [1022]\n\n\\set ON_ERROR_STOP 0\n/* Alter end offset to overlap with another job*/\nSELECT jsonb_set(:'config', '{end_offset}', '1000') AS config \\gset\nSELECT * FROM alter_job(:job_id, config := :'config');\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\n/* Alter end offset to be null */\nSELECT jsonb_set(:'config', '{end_offset}', 'null') AS config \\gset\nSELECT * FROM alter_job(:job_id, config := :'config');\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\n/* Alter job to be identical to existing job */\nSELECT jsonb_set(:'config', '{start_offset}', '2000') AS config \\gset\nSELECT * FROM alter_job(:job_id, config := :'config');\nERROR:  continuous aggregate refresh policy already exists for \"mat_m1\"\n\\set ON_ERROR_STOP 1\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', 2000::bigint, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1024\n\nSELECT id AS job_id, config AS config FROM _timescaledb_catalog.bgw_job WHERE proc_name = 'policy_refresh_continuous_aggregate' \\gset\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, 3000::bigint, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1025\n\n/* Alter end offset to null but no overlap */\nSELECT jsonb_set(:'config', '{end_offset}', 'null') AS config \\gset\nSELECT * FROM alter_job(:job_id, config := :'config');\n job_id | schedule_interval | max_runtime | max_retries | retry_period | scheduled |                               config                               | next_start |                           check_config                           | fixed_schedule | initial_start | timezone |              application_name              \n--------+-------------------+-------------+-------------+--------------+-----------+--------------------------------------------------------------------+------------+------------------------------------------------------------------+----------------+---------------+----------+--------------------------------------------\n   1024 | @ 12 hours        | @ 0         |          -1 | @ 12 hours   | t         | {\"end_offset\": null, \"start_offset\": 2000, \"mat_hypertable_id\": 2} | -infinity  | _timescaledb_functions.policy_refresh_continuous_aggregate_check | f              |               |          | Refresh Continuous Aggregate Policy [1024]\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n/* Test that refresh is done correctly even though multiple policies exist */\n/* We do this by creating two CAggs on the same hypertable */\n/* One will have a single policy while the other will have two policies with adjacent offsets */\nCREATE MATERIALIZED VIEW mat_m2(time, counta)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nAS\nSELECT\n    time_bucket(10, time) AS bucket,\n    count(a),\n    sum(b)\nFROM overlap_test_bigint\nGROUP BY 1\nWITH NO DATA;\n/* Create two policies on mat_m1 */\nSELECT add_continuous_aggregate_policy('mat_m1', 5000::bigint, 3000::bigint, '12 h'::interval) AS agg_m1_job_1 \\gset\nSELECT add_continuous_aggregate_policy('mat_m1', 3000::bigint, 1000::bigint, '12 h'::interval) AS agg_m1_job_2 \\gset\n/* Create single policy on mat_m2 */\nSELECT add_continuous_aggregate_policy('mat_m2', 5000::bigint, 1000::bigint, '12 h'::interval) AS agg_m2_job \\gset\n/* Cleanup any existing data */\nTRUNCATE mat_m1;\nTRUNCATE mat_m2;\n/* Refresh both continuous aggs immediately */\nCALL run_job(:agg_m1_job_1);\nCALL run_job(:agg_m1_job_2);\nCALL run_job(:agg_m2_job);\n/* Compare both outputs */\nSELECT count(*) AS exp_row_count from mat_m1 \\gset\nSELECT count(*) AS actual_row_count from (\nSELECT * from mat_m1 UNION SELECT * from mat_m2) union_q \\gset\n/* Row counts should be the same */\nSELECT :exp_row_count = :actual_row_count, :exp_row_count, :actual_row_count;\n ?column? | ?column? | ?column? \n----------+----------+----------\n t        |      100 |      100\n\nSELECT * from mat_m2 EXCEPT SELECT * from mat_m1;\n time | counta | sum \n------+--------+-----\n\nSELECT * from mat_m1 EXCEPT SELECT * from mat_m2;\n time | counta | sum \n------+--------+-----\n\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_2_22_chunk\nDROP MATERIALIZED VIEW mat_m2;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_3_23_chunk\nCREATE TABLE overlap_test_timestamptz (\n    time timestamptz NOT NULL,\n    a INTEGER,\n    b INTEGER\n);\nSELECT create_hypertable('overlap_test_timestamptz', 'time', chunk_time_interval => '1 day'::interval);\n           create_hypertable           \n---------------------------------------\n (4,public,overlap_test_timestamptz,t)\n\nINSERT INTO overlap_test_timestamptz\nSELECT t, (i % 5), random() * 100\nFROM\ngenerate_series('2025-01-01T01:01:01+00', '2025-06-01T01:01:01+00', INTERVAL '1 days') t,\ngenerate_series(1, 10) i;\nCREATE MATERIALIZED VIEW mat_m1(time, counta)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nAS\nSELECT\n    time_bucket('1 day', time) AS bucket,\n    count(a),\n    sum(b)\nFROM overlap_test_timestamptz\nGROUP BY 1\nWITH NO DATA;\n/* Test interval checking when multiple policies are created on the same cagg */\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '30 days'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1029\n\nSELECT add_continuous_aggregate_policy('mat_m1', '29 days'::interval, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1030\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n/* Creating policies in either order should work */\nSELECT add_continuous_aggregate_policy('mat_m1', '30 days'::interval, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1031\n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '30 days'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1032\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '30 days'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1033\n\nSELECT add_continuous_aggregate_policy('mat_m1', '15 days'::interval, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1034\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n/* Test non-null offsets on both sides too */\nSELECT add_continuous_aggregate_policy('mat_m1', '30 days'::interval, '20 days'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1035\n\nSELECT add_continuous_aggregate_policy('mat_m1', '10 days'::interval, '5 days'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1036\n\nSELECT add_continuous_aggregate_policy('mat_m1', '19 days'::interval, '11 days'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1037\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n/* Check overlap is detected correctly */\n\\set ON_ERROR_STOP 0\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '30 days'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1038\n\nSELECT add_continuous_aggregate_policy('mat_m1', '45 days'::interval, NULL, '12 h'::interval);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', '45 days'::interval, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1039\n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '30 days'::interval, '12 h'::interval);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', '30 days'::interval, '10 days'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1040\n\nSELECT add_continuous_aggregate_policy('mat_m1', '20 days'::interval, '15 days'::interval, '12 h'::interval);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', '20 days'::interval, '15 days'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1041\n\nSELECT add_continuous_aggregate_policy('mat_m1', '30 days'::interval, '10 days'::interval, '12 h'::interval);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '30 days'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1042\n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '20 days'::interval, '12 h'::interval);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', '30 days'::interval, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1043\n\nSELECT add_continuous_aggregate_policy('mat_m1', '20 days'::interval, NULL, '12 h'::interval);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n\\set ON_ERROR_STOP 1\n/* Check behaviour when exact policy is already defined */\n\\set ON_ERROR_STOP 0\n/*if_not_exists=false*/\nSELECT add_continuous_aggregate_policy('mat_m1', '45 days'::interval, '30 days', '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1044\n\nSELECT add_continuous_aggregate_policy('mat_m1', '31 days'::interval, '15 days', '12 h'::interval);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT add_continuous_aggregate_policy('mat_m1', '31 days'::interval, '15 days', '12 h'::interval);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL::interval, NULL::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1045\n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL::interval, NULL::interval, '12 h'::interval);\nERROR:  continuous aggregate refresh policy already exists for \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n\\set ON_ERROR_STOP 1\n/*if_not_exists => true*/\n\\set ON_ERROR_STOP 0\nSELECT add_continuous_aggregate_policy('mat_m1', '45 days', '30 days', '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1046\n\nSELECT add_continuous_aggregate_policy('mat_m1', '30 days'::interval, '15 days'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1047\n\nSELECT add_continuous_aggregate_policy('mat_m1', '30 days'::interval, '15 days'::interval, '12 h'::interval, if_not_exists => true);\nNOTICE:  continuous aggregate refresh policy already exists for \"mat_m1\", skipping\n add_continuous_aggregate_policy \n---------------------------------\n                              -1\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL::interval, NULL::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1048\n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL::interval, NULL::interval, '12 h'::interval, if_not_exists => true);\nNOTICE:  continuous aggregate refresh policy already exists for \"mat_m1\", skipping\n add_continuous_aggregate_policy \n---------------------------------\n                              -1\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n\\set ON_ERROR_STOP 1\n/* Throw an error if there is an overlap even if if_not_exists => true */\nSELECT add_continuous_aggregate_policy('mat_m1', '30 days'::interval, '10 days'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1049\n\n\\set ON_ERROR_STOP 0\nSELECT add_continuous_aggregate_policy('mat_m1', '15 days'::interval, NULL, '12 h'::interval);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\n\\set ON_ERROR_STOP 1\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n/* Mixing different interval units should also work correctly*/\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '1 month'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1050\n\nSELECT add_continuous_aggregate_policy('mat_m1', '2 weeks'::interval, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1051\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', '1 year'::interval, '2 months'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1052\n\nSELECT add_continuous_aggregate_policy('mat_m1', '5 weeks'::interval, '-7 days'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1053\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', '2 weeks'::interval, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1054\n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '1 month'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1055\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n\\set ON_ERROR_STOP 0\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '2 weeks'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1056\n\nSELECT add_continuous_aggregate_policy('mat_m1', '1 month'::interval, NULL, '12 h'::interval);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n\\set ON_ERROR_STOP 1\n/* Check overlap with negative offsets */\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '2 weeks'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1057\n\nSELECT add_continuous_aggregate_policy('mat_m1', '-1 month'::interval, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1058\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n\\set ON_ERROR_STOP 0\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '-2 weeks'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1059\n\nSELECT add_continuous_aggregate_policy('mat_m1', '1 month'::interval, NULL, '12 h'::interval);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n\\set ON_ERROR_STOP 1\n/* Test `alter_job` changing the config */\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '2 months'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1060\n\nSELECT id AS job_id, config AS config FROM _timescaledb_catalog.bgw_job WHERE proc_name = 'policy_refresh_continuous_aggregate' \\gset\nSELECT add_continuous_aggregate_policy('mat_m1', '2 weeks'::interval, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1061\n\n/* Alter end offset but don't overlap */\nSELECT jsonb_set(:'config', '{end_offset}', '\"30 days\"') AS config \\gset\nSELECT * FROM alter_job(:job_id, config := :'config');\n job_id | schedule_interval | max_runtime | max_retries | retry_period | scheduled |                                 config                                  | next_start |                           check_config                           | fixed_schedule | initial_start | timezone |              application_name              \n--------+-------------------+-------------+-------------+--------------+-----------+-------------------------------------------------------------------------+------------+------------------------------------------------------------------+----------------+---------------+----------+--------------------------------------------\n   1060 | @ 12 hours        | @ 0         |          -1 | @ 12 hours   | t         | {\"end_offset\": \"30 days\", \"start_offset\": null, \"mat_hypertable_id\": 5} | -infinity  | _timescaledb_functions.policy_refresh_continuous_aggregate_check | f              |               |          | Refresh Continuous Aggregate Policy [1060]\n\n\\set ON_ERROR_STOP 0\n/* Alter end offset to overlap with another job*/\nSELECT jsonb_set(:'config', '{end_offset}', '\"1 week\"') AS config \\gset\nSELECT * FROM alter_job(:job_id, config := :'config');\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\n/* Alter end offset to be null */\nSELECT jsonb_set(:'config', '{end_offset}', 'null') AS config \\gset\nSELECT * FROM alter_job(:job_id, config := :'config');\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\n/* Alter job to be identical to existing job */\nSELECT jsonb_set(:'config', '{start_offset}', '\"2 weeks\"') AS config \\gset\nSELECT * FROM alter_job(:job_id, config := :'config');\nERROR:  continuous aggregate refresh policy already exists for \"mat_m1\"\n\\set ON_ERROR_STOP 1\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', '2 weeks'::interval, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1062\n\nSELECT id AS job_id, config AS config FROM _timescaledb_catalog.bgw_job WHERE proc_name = 'policy_refresh_continuous_aggregate' \\gset\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '2 months'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1063\n\n/* Alter end offset to null but no overlap */\nSELECT jsonb_set(:'config', '{end_offset}', 'null') AS config \\gset\nSELECT * FROM alter_job(:job_id, config := :'config');\n job_id | schedule_interval | max_runtime | max_retries | retry_period | scheduled |                                  config                                   | next_start |                           check_config                           | fixed_schedule | initial_start | timezone |              application_name              \n--------+-------------------+-------------+-------------+--------------+-----------+---------------------------------------------------------------------------+------------+------------------------------------------------------------------+----------------+---------------+----------+--------------------------------------------\n   1062 | @ 12 hours        | @ 0         |          -1 | @ 12 hours   | t         | {\"end_offset\": null, \"start_offset\": \"@ 14 days\", \"mat_hypertable_id\": 5} | -infinity  | _timescaledb_functions.policy_refresh_continuous_aggregate_check | f              |               |          | Refresh Continuous Aggregate Policy [1062]\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nCREATE MATERIALIZED VIEW mat_m2(time, counta)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nAS\nSELECT\n    time_bucket('1 day', time) AS bucket,\n    count(a),\n    sum(b)\nFROM overlap_test_timestamptz\nGROUP BY 1\nWITH NO DATA;\n/* Create two policies on mat_m1 */\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '30 days'::interval, '12 h'::interval) AS agg_m1_job_1 \\gset\nSELECT add_continuous_aggregate_policy('mat_m1', '30 days'::interval,  NULL, '12 h'::interval) AS agg_m1_job_2 \\gset\n/* Create single policy on mat_m2 */\nSELECT add_continuous_aggregate_policy('mat_m2', NULL, NULL, '12 h'::interval) AS agg_m2_job \\gset\n/* Cleanup any existing data */\nTRUNCATE mat_m1;\nTRUNCATE mat_m2;\n/* Refresh both continuous aggs immediately */\nCALL run_job(:agg_m1_job_1);\nCALL run_job(:agg_m1_job_2);\nCALL run_job(:agg_m2_job);\n/* Compare both outputs */\nSELECT count(*) AS exp_row_count from mat_m1 \\gset\nSELECT count(*) AS actual_row_count from (\nSELECT * from mat_m1 UNION SELECT * from mat_m2) AS union_q \\gset\n/* Row counts should be the same */\nSELECT :exp_row_count = :actual_row_count, :exp_row_count, :actual_row_count;\n ?column? | ?column? | ?column? \n----------+----------+----------\n t        |      152 |      152\n\nSELECT * from mat_m2 EXCEPT SELECT * from mat_m1;\n time | counta | sum \n------+--------+-----\n\nSELECT * from mat_m1 EXCEPT SELECT * from mat_m2;\n time | counta | sum \n------+--------+-----\n\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 17 other objects\nDROP MATERIALIZED VIEW mat_m2;\nNOTICE:  drop cascades to 17 other objects\n/* Test with variable sized buckets */\nCREATE TABLE overlap_test_timestamptz_var (\n    time timestamptz NOT NULL,\n    a INTEGER,\n    b INTEGER\n);\nSELECT create_hypertable('overlap_test_timestamptz_var', 'time', chunk_time_interval => '1 month'::interval);\n             create_hypertable             \n-------------------------------------------\n (7,public,overlap_test_timestamptz_var,t)\n\nINSERT INTO overlap_test_timestamptz_var\nSELECT t, (i % 5), random() * 100\nFROM\ngenerate_series('2024-01-01T01:01:01+00', '2025-06-01T01:01:01+00', INTERVAL '1 day') t,\ngenerate_series(1, 10) i;\nCREATE MATERIALIZED VIEW mat_m1(time, counta)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nAS\nSELECT\n    time_bucket('1 month', time) AS bucket,\n    count(a),\n    sum(b)\nFROM overlap_test_timestamptz_var\nGROUP BY 1\nWITH NO DATA;\n/* Test interval checking when multiple policies are created on the same cagg */\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '3 months'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1067\n\nSELECT add_continuous_aggregate_policy('mat_m1', '3 months'::interval, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1068\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n/* Creating policies in either order should work */\nSELECT add_continuous_aggregate_policy('mat_m1', '3 months'::interval, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1069\n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '3 months'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1070\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '3 months'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1071\n\nSELECT add_continuous_aggregate_policy('mat_m1', '2 months'::interval, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1072\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n/* Test non-null offsets on both sides too */\nSELECT add_continuous_aggregate_policy('mat_m1', '8 months'::interval, '6 months'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1073\n\nSELECT add_continuous_aggregate_policy('mat_m1', '6 months'::interval, '12 weeks'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1074\n\nSELECT add_continuous_aggregate_policy('mat_m1', '12 weeks'::interval, '1 days'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1075\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n/* Check overlap is detected correctly */\n\\set ON_ERROR_STOP 0\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '2 months'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1076\n\nSELECT add_continuous_aggregate_policy('mat_m1', '3 months'::interval, NULL, '12 h'::interval);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', '3 months'::interval, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1077\n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '2 months'::interval, '12 h'::interval);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', '6 months'::interval, '1 week'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1078\n\nSELECT add_continuous_aggregate_policy('mat_m1', '4 months'::interval, '2 weeks'::interval, '12 h'::interval);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', '4 months'::interval, '2 weeks'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1079\n\nSELECT add_continuous_aggregate_policy('mat_m1', '6 months'::interval, '1 week'::interval, '12 h'::interval);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '30 days'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1080\n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '20 days'::interval, '12 h'::interval);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', '30 days'::interval, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1081\n\nSELECT add_continuous_aggregate_policy('mat_m1', '20 days'::interval, NULL, '12 h'::interval);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n\\set ON_ERROR_STOP 1\n/* Check behaviour when exact policy is already defined */\n\\set ON_ERROR_STOP 0\n/*if_not_exists=false*/\nSELECT add_continuous_aggregate_policy('mat_m1', '1 year'::interval, '8 months', '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1082\n\nSELECT add_continuous_aggregate_policy('mat_m1', '8 months'::interval, '2 weeks', '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1083\n\nSELECT add_continuous_aggregate_policy('mat_m1', '8 months'::interval, '2 weeks', '12 h'::interval);\nERROR:  continuous aggregate refresh policy already exists for \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL::interval, NULL::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1084\n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL::interval, NULL::interval, '12 h'::interval);\nERROR:  continuous aggregate refresh policy already exists for \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n\\set ON_ERROR_STOP 1\n/*if_not_exists => true*/\n\\set ON_ERROR_STOP 0\nSELECT add_continuous_aggregate_policy('mat_m1', '1 year'::interval, '8 months', '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1085\n\nSELECT add_continuous_aggregate_policy('mat_m1', '8 months'::interval, '2 weeks', '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1086\n\nSELECT add_continuous_aggregate_policy('mat_m1', '8 months'::interval, '2 weeks', '12 h'::interval, if_not_exists => true);\nNOTICE:  continuous aggregate refresh policy already exists for \"mat_m1\", skipping\n add_continuous_aggregate_policy \n---------------------------------\n                              -1\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL::interval, NULL::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1087\n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL::interval, NULL::interval, '12 h'::interval, if_not_exists => true);\nNOTICE:  continuous aggregate refresh policy already exists for \"mat_m1\", skipping\n add_continuous_aggregate_policy \n---------------------------------\n                              -1\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n\\set ON_ERROR_STOP 1\n/* Mixing different interval units should also work correctly*/\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '1 month'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1088\n\nSELECT add_continuous_aggregate_policy('mat_m1', '2 weeks'::interval, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1089\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', '1 year'::interval, '2 months'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1090\n\nSELECT add_continuous_aggregate_policy('mat_m1', '8 weeks'::interval, '-7 days'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1091\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', '2 weeks'::interval, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1092\n\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '1 month'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1093\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n\\set ON_ERROR_STOP 0\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '2 weeks'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1094\n\nSELECT add_continuous_aggregate_policy('mat_m1', '1 month'::interval, NULL, '12 h'::interval);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n\\set ON_ERROR_STOP 1\n/* Check overlap with negative offsets */\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '2 weeks'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1095\n\nSELECT add_continuous_aggregate_policy('mat_m1', '-1 month'::interval, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1096\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n\\set ON_ERROR_STOP 0\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '-2 weeks'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1097\n\nSELECT add_continuous_aggregate_policy('mat_m1', '1 month'::interval, NULL, '12 h'::interval);\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\n\\set ON_ERROR_STOP 1\n/* Test `alter_job` changing the config */\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '2 months'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1098\n\nSELECT id AS job_id, config AS config FROM _timescaledb_catalog.bgw_job WHERE proc_name = 'policy_refresh_continuous_aggregate' \\gset\nSELECT add_continuous_aggregate_policy('mat_m1', '2 weeks'::interval, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1099\n\n/* Alter end offset but don't overlap */\nSELECT jsonb_set(:'config', '{end_offset}', '\"30 days\"') AS config \\gset\nSELECT * FROM alter_job(:job_id, config := :'config');\n job_id | schedule_interval | max_runtime | max_retries | retry_period | scheduled |                                 config                                  | next_start |                           check_config                           | fixed_schedule | initial_start | timezone |              application_name              \n--------+-------------------+-------------+-------------+--------------+-----------+-------------------------------------------------------------------------+------------+------------------------------------------------------------------+----------------+---------------+----------+--------------------------------------------\n   1098 | @ 12 hours        | @ 0         |          -1 | @ 12 hours   | t         | {\"end_offset\": \"30 days\", \"start_offset\": null, \"mat_hypertable_id\": 8} | -infinity  | _timescaledb_functions.policy_refresh_continuous_aggregate_check | f              |               |          | Refresh Continuous Aggregate Policy [1098]\n\n\\set ON_ERROR_STOP 0\n/* Alter end offset to overlap with another job*/\nSELECT jsonb_set(:'config', '{end_offset}', '\"1 week\"') AS config \\gset\nSELECT * FROM alter_job(:job_id, config := :'config');\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\n/* Alter end offset to be null */\nSELECT jsonb_set(:'config', '{end_offset}', 'null') AS config \\gset\nSELECT * FROM alter_job(:job_id, config := :'config');\nERROR:  refresh interval overlaps with an existing continuous aggregate policy on \"mat_m1\"\n/* Alter job to be identical to existing job */\nSELECT jsonb_set(:'config', '{start_offset}', '\"2 weeks\"') AS config \\gset\nSELECT * FROM alter_job(:job_id, config := :'config');\nERROR:  continuous aggregate refresh policy already exists for \"mat_m1\"\n\\set ON_ERROR_STOP 1\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nSELECT add_continuous_aggregate_policy('mat_m1', '2 weeks'::interval, NULL, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1100\n\nSELECT id AS job_id, config AS config FROM _timescaledb_catalog.bgw_job WHERE proc_name = 'policy_refresh_continuous_aggregate' \\gset\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '2 months'::interval, '12 h'::interval);\n add_continuous_aggregate_policy \n---------------------------------\n                            1101\n\n/* Alter end offset to null but no overlap */\nSELECT jsonb_set(:'config', '{end_offset}', 'null') AS config \\gset\nSELECT * FROM alter_job(:job_id, config := :'config');\n job_id | schedule_interval | max_runtime | max_retries | retry_period | scheduled |                                  config                                   | next_start |                           check_config                           | fixed_schedule | initial_start | timezone |              application_name              \n--------+-------------------+-------------+-------------+--------------+-----------+---------------------------------------------------------------------------+------------+------------------------------------------------------------------+----------------+---------------+----------+--------------------------------------------\n   1100 | @ 12 hours        | @ 0         |          -1 | @ 12 hours   | t         | {\"end_offset\": null, \"start_offset\": \"@ 14 days\", \"mat_hypertable_id\": 8} | -infinity  | _timescaledb_functions.policy_refresh_continuous_aggregate_check | f              |               |          | Refresh Continuous Aggregate Policy [1100]\n\nSELECT remove_continuous_aggregate_policy('mat_m1');\n remove_continuous_aggregate_policy \n------------------------------------\n \n\nCREATE MATERIALIZED VIEW mat_m2(time, counta)\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nAS\nSELECT\n    time_bucket('1 month', time) AS bucket,\n    count(a),\n    sum(b)\nFROM overlap_test_timestamptz_var\nGROUP BY 1\nWITH NO DATA;\n/* Create two policies on mat_m1 */\nSELECT add_continuous_aggregate_policy('mat_m1', NULL, '30 days'::interval, '12 h'::interval, buckets_per_batch => 0) AS agg_m1_job_1 \\gset\nSELECT add_continuous_aggregate_policy('mat_m1', '30 days'::interval,  NULL, '12 h'::interval, buckets_per_batch => 0) AS agg_m1_job_2 \\gset\n/* Create single policy on mat_m2 */\nSELECT add_continuous_aggregate_policy('mat_m2', NULL, NULL, '12 h'::interval, buckets_per_batch => 0) AS agg_m2_job \\gset\n/* Cleanup any existing data */\nTRUNCATE mat_m1;\nTRUNCATE mat_m2;\n/* Refresh both continuous aggs immediately */\nCALL run_job(:agg_m1_job_1);\nCALL run_job(:agg_m1_job_2);\nCALL run_job(:agg_m2_job);\n/* Compare both outputs */\nSELECT count(*) AS exp_row_count from mat_m1 \\gset\nSELECT count(*) AS actual_row_count from (\nSELECT * from mat_m1 UNION SELECT * from mat_m2) AS union_q \\gset\n/* Row counts should be the same */\nSELECT :exp_row_count = :actual_row_count, :exp_row_count, :actual_row_count;\n ?column? | ?column? | ?column? \n----------+----------+----------\n t        |       18 |       18\n\nSELECT * from mat_m2 EXCEPT SELECT * from mat_m1;\n time | counta | sum \n------+--------+-----\n\nSELECT * from mat_m1 EXCEPT SELECT * from mat_m2;\n time | counta | sum \n------+--------+-----\n\nDROP MATERIALIZED VIEW mat_m1;\nNOTICE:  drop cascades to 3 other objects\nDROP MATERIALIZED VIEW mat_m2;\nNOTICE:  drop cascades to 3 other objects\n/* Concurrent policies aren't allowed on hierarchical continuous aggs */\nCREATE MATERIALIZED VIEW mat_m1\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nAS\nSELECT\n    time_bucket('1 day', time) AS bucket,\n    count(a) AS counta,\n    sum(b) AS sumb\nFROM overlap_test_timestamptz\nGROUP BY 1\nWITH NO DATA;\nCREATE MATERIALIZED VIEW mat_m1_rollup\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nAS\nSELECT\n    time_bucket('1 month', bucket) AS bucket,\n    sum(counta) AS counta,\n    sum(sumb) AS sumb\nFROM mat_m1\nGROUP BY 1\nWITH NO DATA;\nCREATE MATERIALIZED VIEW mat_m1_rollup2\nWITH (timescaledb.continuous, timescaledb.materialized_only=true)\nAS\nSELECT\n    time_bucket('1 month', bucket) AS bucket,\n    sum(counta) AS counta,\n    sum(sumb) AS sumb\nFROM mat_m1\nGROUP BY 1\nWITH NO DATA;\nSELECT add_continuous_aggregate_policy('mat_m1_rollup', NULL, '30 days'::interval, '12 h'::interval) AS \"JOB_ID\" \\gset\n-- alter_job should not be blocked\nSELECT alter_job(:JOB_ID, next_start => '2000-01-01'::timestamptz);\n                                                                                                                                     alter_job                                                                                                                                     \n-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1105,\"@ 12 hours\",\"@ 0\",-1,\"@ 12 hours\",t,\"{\"\"end_offset\"\": \"\"@ 30 days\"\", \"\"start_offset\"\": null, \"\"mat_hypertable_id\"\": 11}\",\"Sat Jan 01 00:00:00 2000 PST\",_timescaledb_functions.policy_refresh_continuous_aggregate_check,f,,,\"Refresh Continuous Aggregate Policy [1105]\")\n\n\\set ON_ERROR_STOP 0\n-- Multiple policies on hierarchical cagg should not be allowed\nSELECT add_continuous_aggregate_policy('mat_m1_rollup', '29 days'::interval, NULL, '12 h'::interval);\nERROR:  multiple refresh policies are not supported for hierarchical continuous aggregates\n\\set ON_ERROR_STOP 1\n-- Adding the exact same policy with if_not_exists should succeed (not error)\nSELECT add_continuous_aggregate_policy('mat_m1_rollup', NULL, '30 days'::interval, '12 h'::interval, if_not_exists => true);\nNOTICE:  continuous aggregate refresh policy already exists for \"mat_m1_rollup\", skipping\n add_continuous_aggregate_policy \n---------------------------------\n                              -1\n\n-- different hierarchical caggs should be allowed to have their own policies\nSELECT add_continuous_aggregate_policy('mat_m1_rollup2', NULL, '30 days'::interval, '12 h'::interval) AS \"JOB_ID2\" \\gset\nSELECT alter_job(:JOB_ID2, next_start => '2000-01-01'::timestamptz);\n                                                                                                                                     alter_job                                                                                                                                     \n-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n (1106,\"@ 12 hours\",\"@ 0\",-1,\"@ 12 hours\",t,\"{\"\"end_offset\"\": \"\"@ 30 days\"\", \"\"start_offset\"\": null, \"\"mat_hypertable_id\"\": 12}\",\"Sat Jan 01 00:00:00 2000 PST\",_timescaledb_functions.policy_refresh_continuous_aggregate_check,f,,,\"Refresh Continuous Aggregate Policy [1106]\")\n\n"
  },
  {
    "path": "tsl/test/expected/cagg_policy_incremental.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE OR REPLACE FUNCTION ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(timeout INT = -1, mock_start_time INT = 0) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_params_create() RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_params_destroy() RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\nCREATE OR REPLACE FUNCTION ts_bgw_params_reset_time(set_time BIGINT = 0, wait BOOLEAN = false) RETURNS VOID\nAS :MODULE_PATHNAME LANGUAGE C VOLATILE;\n-- Create a user with specific timezone and mock time\nCREATE ROLE test_cagg_refresh_policy_user WITH LOGIN;\nALTER ROLE test_cagg_refresh_policy_user SET timezone TO 'UTC';\nALTER ROLE test_cagg_refresh_policy_user SET timescaledb.current_timestamp_mock TO '2025-03-11 00:00:00+00';\nGRANT ALL ON SCHEMA public TO test_cagg_refresh_policy_user;\n\\c :TEST_DBNAME test_cagg_refresh_policy_user\nCREATE TABLE public.bgw_log(\n    msg_no INT,\n    mock_time BIGINT,\n    application_name TEXT,\n    msg TEXT\n);\nCREATE VIEW sorted_bgw_log AS\nSELECT\n    msg_no,\n    mock_time,\n    application_name,\n    regexp_replace(regexp_replace(msg, '(Wait until|started at|execution time) [0-9]+(\\.[0-9]+)?', '\\1 (RANDOM)', 'g'), 'background worker \"[^\"]+\"','connection') AS msg\nFROM\n    bgw_log\nORDER BY\n    mock_time,\n    application_name COLLATE \"C\",\n    msg_no;\nCREATE TABLE public.bgw_dsm_handle_store(\n    handle BIGINT\n);\nINSERT INTO public.bgw_dsm_handle_store VALUES (0);\nSELECT ts_bgw_params_create();\n ts_bgw_params_create \n----------------------\n \n\nCREATE TABLE conditions (\n    time         TIMESTAMP WITH TIME ZONE NOT NULL,\n    device_id    INTEGER,\n    temperature  NUMERIC\n);\nSELECT FROM create_hypertable('conditions', by_range('time'));\n--\n\nINSERT INTO conditions\nSELECT\n    t, d, 10\nFROM\n    generate_series(\n        '2025-02-05 00:00:00+00',\n        '2025-03-05 00:00:00+00',\n        '1 hour'::interval) AS t,\n    generate_series(1,5) AS d;\nCREATE MATERIALIZED VIEW conditions_by_day\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n    time_bucket('1 day', time),\n    device_id,\n    count(*),\n    min(temperature),\n    max(temperature),\n    avg(temperature),\n    sum(temperature)\nFROM\n    conditions\nGROUP BY\n    1, 2\nWITH NO DATA;\nSELECT\n    add_continuous_aggregate_policy(\n        'conditions_by_day',\n        start_offset => NULL,\n        end_offset => NULL,\n        schedule_interval => INTERVAL '1 h',\n        buckets_per_batch => 10\n    ) AS job_id \\gset\nSELECT\n    config\nFROM\n    timescaledb_information.jobs\nWHERE\n    job_id = :'job_id' \\gset\nSELECT ts_bgw_params_reset_time(0, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | mock_time |              application_name              |                                                                                   msg                                                                                    \n--------+-----------+--------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n      0 |         0 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 |         0 | DB Scheduler                               | [TESTING] Registered new background worker\n      2 |         0 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 |         0 | Refresh Continuous Aggregate Policy [1000] | continuous aggregate refresh (individual invalidation) on \"conditions_by_day\" in window [ Sat Mar 01 00:00:00 2025 UTC, Thu Mar 06 00:00:00 2025 UTC ] (batch 1 of 4)\n      1 |         0 | Refresh Continuous Aggregate Policy [1000] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      2 |         0 | Refresh Continuous Aggregate Policy [1000] | inserted 25 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      3 |         0 | Refresh Continuous Aggregate Policy [1000] | continuous aggregate refresh (individual invalidation) on \"conditions_by_day\" in window [ Wed Feb 19 00:00:00 2025 UTC, Sat Mar 01 00:00:00 2025 UTC ] (batch 2 of 4)\n      4 |         0 | Refresh Continuous Aggregate Policy [1000] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      5 |         0 | Refresh Continuous Aggregate Policy [1000] | inserted 50 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      6 |         0 | Refresh Continuous Aggregate Policy [1000] | continuous aggregate refresh (individual invalidation) on \"conditions_by_day\" in window [ Sun Feb 09 00:00:00 2025 UTC, Wed Feb 19 00:00:00 2025 UTC ] (batch 3 of 4)\n      7 |         0 | Refresh Continuous Aggregate Policy [1000] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      8 |         0 | Refresh Continuous Aggregate Policy [1000] | inserted 50 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      9 |         0 | Refresh Continuous Aggregate Policy [1000] | continuous aggregate refresh (individual invalidation) on \"conditions_by_day\" in window [ Mon Nov 24 00:00:00 4714 UTC BC, Sun Feb 09 00:00:00 2025 UTC ] (batch 4 of 4)\n     10 |         0 | Refresh Continuous Aggregate Policy [1000] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n     11 |         0 | Refresh Continuous Aggregate Policy [1000] | inserted 20 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n\nSELECT * FROM _timescaledb_catalog.continuous_aggs_materialization_ranges;\n materialization_id | lowest_modified_value | greatest_modified_value \n--------------------+-----------------------+-------------------------\n\nCREATE MATERIALIZED VIEW conditions_by_day_manual_refresh\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n    time_bucket('1 day', time),\n    device_id,\n    count(*),\n    min(temperature),\n    max(temperature),\n    avg(temperature),\n    sum(temperature)\nFROM\n    conditions\nGROUP BY\n    1, 2\nWITH NO DATA;\nCALL refresh_continuous_aggregate('conditions_by_day_manual_refresh', NULL, NULL);\nSELECT count(*) FROM conditions_by_day;\n count \n-------\n   145\n\nSELECT count(*) FROM conditions_by_day_manual_refresh;\n count \n-------\n   145\n\n-- Should have no differences\nSELECT\n    count(*) > 0 AS has_diff\nFROM\n    ((SELECT * FROM conditions_by_day_manual_refresh ORDER BY 1, 2)\n    EXCEPT\n    (SELECT * FROM conditions_by_day ORDER BY 1, 2)) AS diff;\n has_diff \n----------\n f\n\nTRUNCATE bgw_log, conditions_by_day;\nSELECT\n    config\nFROM\n    alter_job(\n        :'job_id',\n        config => jsonb_set(:'config', '{max_batches_per_execution}', '2')\n    );\n                                                           config                                                            \n-----------------------------------------------------------------------------------------------------------------------------\n {\"end_offset\": null, \"start_offset\": null, \"buckets_per_batch\": 10, \"mat_hypertable_id\": 2, \"max_batches_per_execution\": 2}\n\n-- advance time by 1h so that job runs one more time\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '1 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | mock_time  |              application_name              |                                                                                  msg                                                                                  \n--------+------------+--------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------\n      0 | 3600000000 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 | 3600000000 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | 3600000000 | Refresh Continuous Aggregate Policy [1000] | continuous aggregate refresh (individual invalidation) on \"conditions_by_day\" in window [ Sat Mar 01 00:00:00 2025 UTC, Thu Mar 06 00:00:00 2025 UTC ] (batch 1 of 4)\n      1 | 3600000000 | Refresh Continuous Aggregate Policy [1000] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      2 | 3600000000 | Refresh Continuous Aggregate Policy [1000] | inserted 25 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      3 | 3600000000 | Refresh Continuous Aggregate Policy [1000] | continuous aggregate refresh (individual invalidation) on \"conditions_by_day\" in window [ Wed Feb 19 00:00:00 2025 UTC, Sat Mar 01 00:00:00 2025 UTC ] (batch 2 of 4)\n      4 | 3600000000 | Refresh Continuous Aggregate Policy [1000] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      5 | 3600000000 | Refresh Continuous Aggregate Policy [1000] | inserted 50 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      6 | 3600000000 | Refresh Continuous Aggregate Policy [1000] | reached maximum number of batches per execution (2), batches not processed (2)\n\nSELECT * FROM _timescaledb_catalog.continuous_aggs_materialization_ranges;\n materialization_id | lowest_modified_value | greatest_modified_value \n--------------------+-----------------------+-------------------------\n\nSELECT count(*) FROM conditions_by_day;\n count \n-------\n    75\n\nSELECT count(*) FROM conditions_by_day_manual_refresh;\n count \n-------\n   145\n\n-- Should have differences\nSELECT\n    count(*) > 0 AS has_diff\nFROM\n    ((SELECT * FROM conditions_by_day_manual_refresh ORDER BY 1, 2)\n    EXCEPT\n    (SELECT * FROM conditions_by_day ORDER BY 1, 2)) AS diff;\n has_diff \n----------\n t\n\n-- advance time by 2h so that job runs one more time\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '2 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | mock_time  |              application_name              |                                                                                   msg                                                                                    \n--------+------------+--------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n      0 | 3600000000 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 | 3600000000 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | 3600000000 | Refresh Continuous Aggregate Policy [1000] | continuous aggregate refresh (individual invalidation) on \"conditions_by_day\" in window [ Sat Mar 01 00:00:00 2025 UTC, Thu Mar 06 00:00:00 2025 UTC ] (batch 1 of 4)\n      1 | 3600000000 | Refresh Continuous Aggregate Policy [1000] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      2 | 3600000000 | Refresh Continuous Aggregate Policy [1000] | inserted 25 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      3 | 3600000000 | Refresh Continuous Aggregate Policy [1000] | continuous aggregate refresh (individual invalidation) on \"conditions_by_day\" in window [ Wed Feb 19 00:00:00 2025 UTC, Sat Mar 01 00:00:00 2025 UTC ] (batch 2 of 4)\n      4 | 3600000000 | Refresh Continuous Aggregate Policy [1000] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      5 | 3600000000 | Refresh Continuous Aggregate Policy [1000] | inserted 50 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      6 | 3600000000 | Refresh Continuous Aggregate Policy [1000] | reached maximum number of batches per execution (2), batches not processed (2)\n      0 | 7200000000 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 | 7200000000 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | 7200000000 | Refresh Continuous Aggregate Policy [1000] | continuous aggregate refresh (individual invalidation) on \"conditions_by_day\" in window [ Sun Feb 09 00:00:00 2025 UTC, Wed Feb 19 00:00:00 2025 UTC ] (batch 1 of 2)\n      1 | 7200000000 | Refresh Continuous Aggregate Policy [1000] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      2 | 7200000000 | Refresh Continuous Aggregate Policy [1000] | inserted 50 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      3 | 7200000000 | Refresh Continuous Aggregate Policy [1000] | continuous aggregate refresh (individual invalidation) on \"conditions_by_day\" in window [ Mon Nov 24 00:00:00 4714 UTC BC, Sun Feb 09 00:00:00 2025 UTC ] (batch 2 of 2)\n      4 | 7200000000 | Refresh Continuous Aggregate Policy [1000] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      5 | 7200000000 | Refresh Continuous Aggregate Policy [1000] | inserted 20 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n\nSELECT * FROM _timescaledb_catalog.continuous_aggs_materialization_ranges;\n materialization_id | lowest_modified_value | greatest_modified_value \n--------------------+-----------------------+-------------------------\n\n-- Should have no differences\nSELECT\n    count(*) > 0 AS has_diff\nFROM\n    ((SELECT * FROM conditions_by_day_manual_refresh ORDER BY 1, 2)\n    EXCEPT\n    (SELECT * FROM conditions_by_day ORDER BY 1, 2)) AS diff;\n has_diff \n----------\n f\n\n-- Set max_batches_per_execution to 10\nSELECT\n    config\nFROM\n    alter_job(\n        :'job_id',\n        config => jsonb_set(:'config', '{max_batches_per_execution}', '10')\n    );\n                                                            config                                                            \n------------------------------------------------------------------------------------------------------------------------------\n {\"end_offset\": null, \"start_offset\": null, \"buckets_per_batch\": 10, \"mat_hypertable_id\": 2, \"max_batches_per_execution\": 10}\n\nTRUNCATE bgw_log;\n-- Insert data into the past\nINSERT INTO conditions\nSELECT\n    t, d, 10\nFROM\n    generate_series(\n        '2020-02-05 00:00:00+00',\n        '2020-03-05 00:00:00+00',\n        '1 hour'::interval) AS t,\n    generate_series(1,5) AS d;\n-- advance time by 3h so that job runs one more time\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '3 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\n-- Should process all four batches in the past\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no |  mock_time  |              application_name              |                                                                                  msg                                                                                  \n--------+-------------+--------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------\n      0 | 10800000000 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 | 10800000000 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | 10800000000 | Refresh Continuous Aggregate Policy [1000] | continuous aggregate refresh (individual invalidation) on \"conditions_by_day\" in window [ Sat Feb 29 00:00:00 2020 UTC, Fri Mar 06 00:00:00 2020 UTC ] (batch 1 of 4)\n      1 | 10800000000 | Refresh Continuous Aggregate Policy [1000] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      2 | 10800000000 | Refresh Continuous Aggregate Policy [1000] | inserted 30 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      3 | 10800000000 | Refresh Continuous Aggregate Policy [1000] | continuous aggregate refresh (individual invalidation) on \"conditions_by_day\" in window [ Wed Feb 19 00:00:00 2020 UTC, Sat Feb 29 00:00:00 2020 UTC ] (batch 2 of 4)\n      4 | 10800000000 | Refresh Continuous Aggregate Policy [1000] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      5 | 10800000000 | Refresh Continuous Aggregate Policy [1000] | inserted 50 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      6 | 10800000000 | Refresh Continuous Aggregate Policy [1000] | continuous aggregate refresh (individual invalidation) on \"conditions_by_day\" in window [ Sun Feb 09 00:00:00 2020 UTC, Wed Feb 19 00:00:00 2020 UTC ] (batch 3 of 4)\n      7 | 10800000000 | Refresh Continuous Aggregate Policy [1000] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      8 | 10800000000 | Refresh Continuous Aggregate Policy [1000] | inserted 50 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      9 | 10800000000 | Refresh Continuous Aggregate Policy [1000] | continuous aggregate refresh (individual invalidation) on \"conditions_by_day\" in window [ Wed Feb 05 00:00:00 2020 UTC, Sun Feb 09 00:00:00 2020 UTC ] (batch 4 of 4)\n     10 | 10800000000 | Refresh Continuous Aggregate Policy [1000] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n     11 | 10800000000 | Refresh Continuous Aggregate Policy [1000] | inserted 20 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n\nSELECT * FROM _timescaledb_catalog.continuous_aggs_materialization_ranges;\n materialization_id | lowest_modified_value | greatest_modified_value \n--------------------+-----------------------+-------------------------\n\nSELECT count(*) FROM conditions_by_day;\n count \n-------\n   295\n\nSELECT count(*) FROM conditions_by_day_manual_refresh;\n count \n-------\n   145\n\nCALL refresh_continuous_aggregate('conditions_by_day_manual_refresh', NULL, NULL);\nSELECT count(*) FROM conditions_by_day;\n count \n-------\n   295\n\nSELECT count(*) FROM conditions_by_day_manual_refresh;\n count \n-------\n   295\n\n-- Should have no differences\nSELECT\n    count(*) > 0 AS has_diff\nFROM\n    ((SELECT * FROM conditions_by_day_manual_refresh ORDER BY 1, 2)\n    EXCEPT\n    (SELECT * FROM conditions_by_day ORDER BY 1, 2)) AS diff;\n has_diff \n----------\n f\n\n-- Check invalid configurations\n\\set ON_ERROR_STOP 0\n\\set VERBOSITY default\nSELECT\n    config\nFROM\n    alter_job(\n        :'job_id',\n        config => jsonb_set(:'config', '{max_batches_per_execution}', '-1')\n    );\nERROR:  invalid max batches per execution\nDETAIL:  max_batches_per_execution: -1\nHINT:  The max batches per execution should be greater than or equal to zero.\nSELECT\n    config\nFROM\n    alter_job(\n        :'job_id',\n        config => jsonb_set(:'config', '{buckets_per_batch}', '-1')\n    );\nERROR:  invalid buckets per batch\nDETAIL:  buckets_per_batch: -1\nHINT:  The buckets per batch should be greater than or equal to zero.\n\\set VERBOSITY terse\n\\set ON_ERROR_STOP 1\n-- Truncate all data from the original hypertable\nTRUNCATE bgw_log, conditions;\n-- advance time by 4h so that job runs one more time\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '4 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\n-- Should fallback to single batch processing because there's no data to be refreshed on the original hypertable\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no |  mock_time  |              application_name              |                                                                            msg                                                                            \n--------+-------------+--------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------\n      0 | 14400000000 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 | 14400000000 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | 14400000000 | Refresh Continuous Aggregate Policy [1000] | continuous aggregate refresh (individual invalidation) on \"conditions_by_day\" in window [ Mon Nov 24 00:00:00 4714 UTC BC, Thu Mar 06 00:00:00 2025 UTC ]\n      1 | 14400000000 | Refresh Continuous Aggregate Policy [1000] | deleted 295 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      2 | 14400000000 | Refresh Continuous Aggregate Policy [1000] | inserted 0 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n\nSELECT * FROM _timescaledb_catalog.continuous_aggs_materialization_ranges;\n materialization_id | lowest_modified_value | greatest_modified_value \n--------------------+-----------------------+-------------------------\n\n-- Should return zero rows\nSELECT count(*) FROM conditions_by_day;\n count \n-------\n     0\n\n-- 1 day of data\nINSERT INTO conditions\nSELECT\n    t, d, 10\nFROM\n    generate_series(\n        '2020-02-05 00:00:00+00',\n        '2020-02-06 00:00:00+00',\n        '1 hour'::interval) AS t,\n    generate_series(1,5) AS d;\nTRUNCATE bgw_log;\n-- advance time by 5h so that job runs one more time\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '5 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\n-- Should fallback to single batch processing because the refresh size is too small\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no |  mock_time  |              application_name              |                                                                          msg                                                                           \n--------+-------------+--------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------\n      0 | 18000000000 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 | 18000000000 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | 18000000000 | Refresh Continuous Aggregate Policy [1000] | continuous aggregate refresh (individual invalidation) on \"conditions_by_day\" in window [ Wed Feb 05 00:00:00 2020 UTC, Fri Feb 07 00:00:00 2020 UTC ]\n      1 | 18000000000 | Refresh Continuous Aggregate Policy [1000] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      2 | 18000000000 | Refresh Continuous Aggregate Policy [1000] | inserted 10 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n\nSELECT * FROM _timescaledb_catalog.continuous_aggs_materialization_ranges;\n materialization_id | lowest_modified_value | greatest_modified_value \n--------------------+-----------------------+-------------------------\n\n-- Should return 10 rows because the bucket width is `1 day` and buckets per batch is `10`\nSELECT count(*) FROM conditions_by_day;\n count \n-------\n    10\n\nTRUNCATE conditions_by_day, conditions, bgw_log;\n-- Less than 1 day of data (smaller than the bucket width)\nINSERT INTO conditions\nVALUES ('2020-02-05 00:00:00+00', 1, 10);\n-- advance time by 6h so that job runs one more time\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '6 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\n-- Should fallback to single batch processing because the refresh size is too small\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no |  mock_time  |              application_name              |                                                                            msg                                                                            \n--------+-------------+--------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------\n      0 | 21600000000 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 | 21600000000 | DB Scheduler                               | [TESTING] Registered new background worker\n      2 | 21600000000 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 | 21600000000 | Refresh Continuous Aggregate Policy [1000] | continuous aggregate refresh (individual invalidation) on \"conditions_by_day\" in window [ Mon Nov 24 00:00:00 4714 UTC BC, Thu Mar 06 00:00:00 2025 UTC ]\n      1 | 21600000000 | Refresh Continuous Aggregate Policy [1000] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      2 | 21600000000 | Refresh Continuous Aggregate Policy [1000] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n\nSELECT * FROM _timescaledb_catalog.continuous_aggs_materialization_ranges;\n materialization_id | lowest_modified_value | greatest_modified_value \n--------------------+-----------------------+-------------------------\n\n-- Should return 1 row\nSELECT count(*) FROM conditions_by_day;\n count \n-------\n     1\n\nSELECT delete_job(:job_id);\n delete_job \n------------\n \n\nSELECT\n    add_continuous_aggregate_policy(\n        'conditions_by_day',\n        start_offset => INTERVAL '15 days',\n        end_offset => NULL,\n        schedule_interval => INTERVAL '1 h',\n        buckets_per_batch => 5,\n        refresh_newest_first => true -- explicitly set to true to test the default behavior\n    ) AS job_id \\gset\nSELECT\n    add_continuous_aggregate_policy(\n        'conditions_by_day_manual_refresh',\n        start_offset => INTERVAL '15 days',\n        end_offset => NULL,\n        schedule_interval => INTERVAL '1 h',\n        buckets_per_batch => 0 -- 0 means no batching, so it will refresh all buckets in one go\n    ) AS job_id_manual \\gset\nTRUNCATE bgw_log, conditions_by_day, conditions_by_day_manual_refresh, conditions;\nINSERT INTO conditions\nSELECT\n    t, d, 10\nFROM\n    generate_series(\n        '2025-03-11 00:00:00+00'::timestamptz - INTERVAL '30 days',\n        '2025-03-11 00:00:00+00'::timestamptz,\n        '1 hour'::interval) AS t,\n    generate_series(1,5) AS d;\nSELECT ts_bgw_params_reset_time(0, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | mock_time |              application_name              |                                                                                  msg                                                                                  \n--------+-----------+--------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------\n      0 |         0 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 |         0 | DB Scheduler                               | [TESTING] Registered new background worker\n      2 |         0 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 |         0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"conditions_by_day\" in window [ Tue Mar 11 00:00:00 2025 UTC, Wed Mar 12 00:00:00 2025 UTC ] (batch 1 of 4)\n      1 |         0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      2 |         0 | Refresh Continuous Aggregate Policy [1001] | inserted 5 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      3 |         0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"conditions_by_day\" in window [ Thu Mar 06 00:00:00 2025 UTC, Tue Mar 11 00:00:00 2025 UTC ] (batch 2 of 4)\n      4 |         0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      5 |         0 | Refresh Continuous Aggregate Policy [1001] | inserted 25 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      6 |         0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"conditions_by_day\" in window [ Sat Mar 01 00:00:00 2025 UTC, Thu Mar 06 00:00:00 2025 UTC ] (batch 3 of 4)\n      7 |         0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      8 |         0 | Refresh Continuous Aggregate Policy [1001] | inserted 25 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      9 |         0 | Refresh Continuous Aggregate Policy [1001] | continuous aggregate refresh (individual invalidation) on \"conditions_by_day\" in window [ Mon Feb 24 00:00:00 2025 UTC, Sat Mar 01 00:00:00 2025 UTC ] (batch 4 of 4)\n     10 |         0 | Refresh Continuous Aggregate Policy [1001] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n     11 |         0 | Refresh Continuous Aggregate Policy [1001] | inserted 25 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      0 |         0 | Refresh Continuous Aggregate Policy [1002] | continuous aggregate refresh (individual invalidation) on \"conditions_by_day_manual_refresh\" in window [ Mon Feb 24 00:00:00 2025 UTC, Wed Mar 12 00:00:00 2025 UTC ]\n      1 |         0 | Refresh Continuous Aggregate Policy [1002] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n      2 |         0 | Refresh Continuous Aggregate Policy [1002] | inserted 80 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_3\"\n\nSELECT * FROM _timescaledb_catalog.continuous_aggs_materialization_ranges;\n materialization_id | lowest_modified_value | greatest_modified_value \n--------------------+-----------------------+-------------------------\n\n-- Both continuous aggregates should have the same data\nSELECT count(*) FROM conditions_by_day;\n count \n-------\n    80\n\nSELECT count(*) FROM conditions_by_day_manual_refresh;\n count \n-------\n    80\n\n-- Should have no differences\nSELECT\n    count(*) > 0 AS has_diff\nFROM\n    ((SELECT * FROM conditions_by_day_manual_refresh ORDER BY 1, 2)\n    EXCEPT\n    (SELECT * FROM conditions_by_day ORDER BY 1, 2)) AS diff;\n has_diff \n----------\n f\n\n-- Testing with explicit refresh_newest_first = false (from oldest to newest)\nSELECT delete_job(:job_id);\n delete_job \n------------\n \n\nSELECT delete_job(:job_id_manual);\n delete_job \n------------\n \n\nSELECT\n    add_continuous_aggregate_policy(\n        'conditions_by_day',\n        start_offset => INTERVAL '15 days',\n        end_offset => NULL,\n        schedule_interval => INTERVAL '1 h',\n        buckets_per_batch => 5,\n        refresh_newest_first => false\n    ) AS job_id \\gset\nSELECT\n    config\nFROM\n    timescaledb_information.jobs\nWHERE\n    job_id = :'job_id';\n                                                              config                                                              \n----------------------------------------------------------------------------------------------------------------------------------\n {\"end_offset\": null, \"start_offset\": \"@ 15 days\", \"buckets_per_batch\": 5, \"mat_hypertable_id\": 2, \"refresh_newest_first\": false}\n\nTRUNCATE bgw_log, conditions_by_day;\nSELECT ts_bgw_params_reset_time(0, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | mock_time |              application_name              |                                                                                  msg                                                                                  \n--------+-----------+--------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------\n      0 |         0 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 |         0 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 |         0 | Refresh Continuous Aggregate Policy [1003] | continuous aggregate refresh (individual invalidation) on \"conditions_by_day\" in window [ Mon Feb 24 00:00:00 2025 UTC, Sat Mar 01 00:00:00 2025 UTC ] (batch 1 of 4)\n      1 |         0 | Refresh Continuous Aggregate Policy [1003] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      2 |         0 | Refresh Continuous Aggregate Policy [1003] | inserted 25 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      3 |         0 | Refresh Continuous Aggregate Policy [1003] | continuous aggregate refresh (individual invalidation) on \"conditions_by_day\" in window [ Sat Mar 01 00:00:00 2025 UTC, Thu Mar 06 00:00:00 2025 UTC ] (batch 2 of 4)\n      4 |         0 | Refresh Continuous Aggregate Policy [1003] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      5 |         0 | Refresh Continuous Aggregate Policy [1003] | inserted 25 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      6 |         0 | Refresh Continuous Aggregate Policy [1003] | continuous aggregate refresh (individual invalidation) on \"conditions_by_day\" in window [ Thu Mar 06 00:00:00 2025 UTC, Tue Mar 11 00:00:00 2025 UTC ] (batch 3 of 4)\n      7 |         0 | Refresh Continuous Aggregate Policy [1003] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      8 |         0 | Refresh Continuous Aggregate Policy [1003] | inserted 25 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n      9 |         0 | Refresh Continuous Aggregate Policy [1003] | continuous aggregate refresh (individual invalidation) on \"conditions_by_day\" in window [ Tue Mar 11 00:00:00 2025 UTC, Wed Mar 12 00:00:00 2025 UTC ] (batch 4 of 4)\n     10 |         0 | Refresh Continuous Aggregate Policy [1003] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n     11 |         0 | Refresh Continuous Aggregate Policy [1003] | inserted 5 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_2\"\n\nSELECT * FROM _timescaledb_catalog.continuous_aggs_materialization_ranges;\n materialization_id | lowest_modified_value | greatest_modified_value \n--------------------+-----------------------+-------------------------\n\n-- Both continuous aggregates should have the same data\nSELECT count(*) FROM conditions_by_day;\n count \n-------\n    80\n\nSELECT count(*) FROM conditions_by_day_manual_refresh;\n count \n-------\n    80\n\n-- Should have no differences\nSELECT\n    count(*) > 0 AS has_diff\nFROM\n    ((SELECT * FROM conditions_by_day_manual_refresh ORDER BY 1, 2)\n    EXCEPT\n    (SELECT * FROM conditions_by_day ORDER BY 1, 2)) AS diff;\n has_diff \n----------\n f\n\n-- Tests with Variable sized bucket\nSELECT delete_job(:job_id);\n delete_job \n------------\n \n\nTRUNCATE conditions;\nINSERT INTO conditions\nSELECT\n    t, d, 10\nFROM\n    generate_series(\n        '2025-01-01 00:00:00+00',\n        '2025-10-08 00:00:00+00',\n        '1 hour'::interval) AS t,\n    generate_series(1,5) AS d;\nCREATE MATERIALIZED VIEW conditions_by_month\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT\n    time_bucket('1 month', time),\n    device_id,\n    count(*),\n    min(temperature),\n    max(temperature),\n    avg(temperature),\n    sum(temperature)\nFROM\n    conditions\nGROUP BY\n    1, 2\nWITH NO DATA;\nSELECT\n    add_continuous_aggregate_policy(\n        'conditions_by_month',\n        start_offset => INTERVAL '600 days',\n        end_offset => INTERVAL '7 days',\n        schedule_interval => INTERVAL '1 day',\n        refresh_newest_first => false\n    ) AS job_id \\gset\nSELECT\n    config\nFROM\n    timescaledb_information.jobs\nWHERE\n    job_id = :'job_id';\n                                                     config                                                      \n-----------------------------------------------------------------------------------------------------------------\n {\"end_offset\": \"@ 7 days\", \"start_offset\": \"@ 600 days\", \"mat_hypertable_id\": 4, \"refresh_newest_first\": false}\n\nTRUNCATE bgw_log, conditions_by_day;\nSELECT ts_bgw_params_reset_time(0, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | mock_time |              application_name              |                                                                           msg                                                                            \n--------+-----------+--------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------\n      0 |         0 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 |         0 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 |         0 | Refresh Continuous Aggregate Policy [1004] | continuous aggregate refresh (individual invalidation) on \"conditions_by_month\" in window [ Tue Aug 01 00:00:00 2023 UTC, Sat Mar 01 00:00:00 2025 UTC ]\n      1 |         0 | Refresh Continuous Aggregate Policy [1004] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_4\"\n      2 |         0 | Refresh Continuous Aggregate Policy [1004] | inserted 10 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_4\"\n\nSELECT * FROM _timescaledb_catalog.continuous_aggs_materialization_ranges;\n materialization_id | lowest_modified_value | greatest_modified_value \n--------------------+-----------------------+-------------------------\n\nSELECT delete_job(:job_id);\n delete_job \n------------\n \n\n------------------------------------------------------------------------------------------\n--Test that batched refresh with variable-length buckets doesn't leave remainders\n-------------------------------------------------------------------------------------------\nCREATE TABLE test_data (\n    time TIMESTAMPTZ NOT NULL,\n    value INT\n);\nSELECT public.create_hypertable(\n        relation => 'test_data',\n        time_column_name => 'time',\n        chunk_time_interval => interval '1 months'\n);\n   create_hypertable    \n------------------------\n (5,public,test_data,t)\n\n-- Insert initial data\nINSERT INTO test_data\nSELECT time, 1\nFROM generate_series('2024-01-01'::timestamptz, '2024-12-31'::timestamptz, '1 day'::interval) time;\n-- Create continuous aggregate with monthly buckets and timezone (variable-length buckets)\nCREATE MATERIALIZED VIEW batch_test_cagg\nWITH (timescaledb.continuous) AS\nSELECT\n    time_bucket('1 month'::interval, time) AS bucket,\n    count(*) as count\nFROM test_data\nGROUP BY bucket\nWITH NO DATA;\n-- Add a policy to enable batched refresh (batch size is 30 days by default for monthly buckets)\nSELECT add_continuous_aggregate_policy('batch_test_cagg',\n    start_offset =>null,\n    end_offset => INTERVAL '1 month',\n    schedule_interval => INTERVAL '1 hour',\n    buckets_per_batch => 1\n) AS job_id \\gset\n-- Run the policy job - this uses batched processing, 1 bucket per batch\nTRUNCATE bgw_log;\nSELECT ts_bgw_params_reset_time(0, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\n-- Verify that invalidation log has no entries other than the left and right ends with -/+ infinity\nSELECT materialization_id,\n       _timescaledb_functions.to_timestamp(lowest_modified_value) as low,\n       _timescaledb_functions.to_timestamp(greatest_modified_value) as high\nFROM _timescaledb_catalog.continuous_aggs_materialization_invalidation_log\nWHERE materialization_id IN\n      (SELECT mat_hypertable_id FROM _timescaledb_catalog.continuous_agg\n       WHERE user_view_name = 'batch_test_cagg')\n  AND lowest_modified_value != -9223372036854775808 --  -infinity\n  AND greatest_modified_value != 9223372036854775807 -- +infinity\nORDER BY low;\n materialization_id | low | high \n--------------------+-----+------\n\n--verify that there is no duplicate/overlapping refreshes.\n--Note that batch 1 and batch 12 contains 2 buckets instead of 1 bucket as set in the policy.\n--This is due to the fact that we currently cut a batch of 30 days for monthly cagg,\n--so first batch and batch containing February can have 2 buckets. After we have a cleaner solution to\n--cut an exact batch size for variable-length buckets, this should be fixed.\nSELECT * FROM sorted_bgw_log;\n msg_no | mock_time |              application_name              |                                                                                  msg                                                                                  \n--------+-----------+--------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------\n      0 |         0 | DB Scheduler                               | [TESTING] Registered new background worker\n      1 |         0 | DB Scheduler                               | [TESTING] Wait until (RANDOM), started at (RANDOM)\n      0 |         0 | Refresh Continuous Aggregate Policy [1005] | continuous aggregate refresh (individual invalidation) on \"batch_test_cagg\" in window [ Sun Dec 01 00:00:00 2024 UTC, Sat Feb 01 00:00:00 2025 UTC ] (batch 1 of 13)\n      1 |         0 | Refresh Continuous Aggregate Policy [1005] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n      2 |         0 | Refresh Continuous Aggregate Policy [1005] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n      3 |         0 | Refresh Continuous Aggregate Policy [1005] | continuous aggregate refresh (individual invalidation) on \"batch_test_cagg\" in window [ Fri Nov 01 00:00:00 2024 UTC, Sun Dec 01 00:00:00 2024 UTC ] (batch 2 of 13)\n      4 |         0 | Refresh Continuous Aggregate Policy [1005] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n      5 |         0 | Refresh Continuous Aggregate Policy [1005] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n      6 |         0 | Refresh Continuous Aggregate Policy [1005] | continuous aggregate refresh (individual invalidation) on \"batch_test_cagg\" in window [ Tue Oct 01 00:00:00 2024 UTC, Fri Nov 01 00:00:00 2024 UTC ] (batch 3 of 13)\n      7 |         0 | Refresh Continuous Aggregate Policy [1005] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n      8 |         0 | Refresh Continuous Aggregate Policy [1005] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n      9 |         0 | Refresh Continuous Aggregate Policy [1005] | continuous aggregate refresh (individual invalidation) on \"batch_test_cagg\" in window [ Sun Sep 01 00:00:00 2024 UTC, Tue Oct 01 00:00:00 2024 UTC ] (batch 4 of 13)\n     10 |         0 | Refresh Continuous Aggregate Policy [1005] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n     11 |         0 | Refresh Continuous Aggregate Policy [1005] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n     12 |         0 | Refresh Continuous Aggregate Policy [1005] | continuous aggregate refresh (individual invalidation) on \"batch_test_cagg\" in window [ Thu Aug 01 00:00:00 2024 UTC, Sun Sep 01 00:00:00 2024 UTC ] (batch 5 of 13)\n     13 |         0 | Refresh Continuous Aggregate Policy [1005] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n     14 |         0 | Refresh Continuous Aggregate Policy [1005] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n     15 |         0 | Refresh Continuous Aggregate Policy [1005] | continuous aggregate refresh (individual invalidation) on \"batch_test_cagg\" in window [ Mon Jul 01 00:00:00 2024 UTC, Thu Aug 01 00:00:00 2024 UTC ] (batch 6 of 13)\n     16 |         0 | Refresh Continuous Aggregate Policy [1005] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n     17 |         0 | Refresh Continuous Aggregate Policy [1005] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n     18 |         0 | Refresh Continuous Aggregate Policy [1005] | continuous aggregate refresh (individual invalidation) on \"batch_test_cagg\" in window [ Sat Jun 01 00:00:00 2024 UTC, Mon Jul 01 00:00:00 2024 UTC ] (batch 7 of 13)\n     19 |         0 | Refresh Continuous Aggregate Policy [1005] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n     20 |         0 | Refresh Continuous Aggregate Policy [1005] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n     21 |         0 | Refresh Continuous Aggregate Policy [1005] | continuous aggregate refresh (individual invalidation) on \"batch_test_cagg\" in window [ Wed May 01 00:00:00 2024 UTC, Sat Jun 01 00:00:00 2024 UTC ] (batch 8 of 13)\n     22 |         0 | Refresh Continuous Aggregate Policy [1005] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n     23 |         0 | Refresh Continuous Aggregate Policy [1005] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n     24 |         0 | Refresh Continuous Aggregate Policy [1005] | continuous aggregate refresh (individual invalidation) on \"batch_test_cagg\" in window [ Mon Apr 01 00:00:00 2024 UTC, Wed May 01 00:00:00 2024 UTC ] (batch 9 of 13)\n     25 |         0 | Refresh Continuous Aggregate Policy [1005] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n     26 |         0 | Refresh Continuous Aggregate Policy [1005] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n     27 |         0 | Refresh Continuous Aggregate Policy [1005] | continuous aggregate refresh (individual invalidation) on \"batch_test_cagg\" in window [ Fri Mar 01 00:00:00 2024 UTC, Mon Apr 01 00:00:00 2024 UTC ] (batch 10 of 13)\n     28 |         0 | Refresh Continuous Aggregate Policy [1005] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n     29 |         0 | Refresh Continuous Aggregate Policy [1005] | inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n     30 |         0 | Refresh Continuous Aggregate Policy [1005] | continuous aggregate refresh (individual invalidation) on \"batch_test_cagg\" in window [ Mon Jan 01 00:00:00 2024 UTC, Fri Mar 01 00:00:00 2024 UTC ] (batch 12 of 13)\n     31 |         0 | Refresh Continuous Aggregate Policy [1005] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n     32 |         0 | Refresh Continuous Aggregate Policy [1005] | inserted 2 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n     33 |         0 | Refresh Continuous Aggregate Policy [1005] | continuous aggregate refresh (individual invalidation) on \"batch_test_cagg\" in window [ -infinity, Mon Jan 01 00:00:00 2024 UTC ] (batch 13 of 13)\n     34 |         0 | Refresh Continuous Aggregate Policy [1005] | deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n     35 |         0 | Refresh Continuous Aggregate Policy [1005] | inserted 0 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_6\"\n\n--now run the refresh again, should not do anything\nTRUNCATE bgw_log;\nSELECT ts_bgw_params_reset_time(extract(epoch from interval '1 hour')::bigint * 1000000, true);\n ts_bgw_params_reset_time \n--------------------------\n \n\nSELECT ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish(25);\n ts_bgw_db_scheduler_test_run_and_wait_for_scheduler_finish \n------------------------------------------------------------\n \n\nSELECT * FROM sorted_bgw_log;\n msg_no | mock_time  | application_name |                        msg                         \n--------+------------+------------------+----------------------------------------------------\n      0 | 3600000000 | DB Scheduler     | [TESTING] Registered new background worker\n      1 | 3600000000 | DB Scheduler     | [TESTING] Wait until (RANDOM), started at (RANDOM)\n\n--clean up\nDROP TABLE test_data CASCADE;\nNOTICE:  drop cascades to 2 other objects\nNOTICE:  drop cascades to 2 other objects\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nREASSIGN OWNED BY test_cagg_refresh_policy_user TO :ROLE_SUPERUSER;\nREVOKE ALL ON SCHEMA public FROM test_cagg_refresh_policy_user;\nDROP ROLE test_cagg_refresh_policy_user;\n"
  },
  {
    "path": "tsl/test/expected/cagg_query-16.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n-- Connect as superuser to use SET ROLE later\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nSET timezone TO PST8PDT;\n-- Run tests with default role\nSET ROLE :ROLE_DEFAULT_PERM_USER;\n\\set TEST_BASE_NAME cagg_query\n\\ir include/cagg_query_common.sql\n-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\nSELECT\n       format('%s/results/%s_results_view.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_VIEW\",\n       format('%s/results/%s_results_view_hashagg.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_VIEW_HASHAGG\",\n       format('%s/results/%s_results_table.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as \"TEST_RESULTS_TABLE\"\n\\gset\nSELECT format('\\! diff %s %s', :'TEST_RESULTS_VIEW', :'TEST_RESULTS_TABLE') as \"DIFF_CMD\",\n      format('\\! diff %s %s', :'TEST_RESULTS_VIEW_HASHAGG', :'TEST_RESULTS_TABLE') as \"DIFF_CMD2\"\n\\gset\n\\set EXPLAIN 'EXPLAIN (VERBOSE, BUFFERS OFF, COSTS OFF)'\nSET client_min_messages TO NOTICE;\nCREATE TABLE conditions (\n      timec        TIMESTAMPTZ       NOT NULL,\n      location    TEXT              NOT NULL,\n      temperature DOUBLE PRECISION  NULL,\n      humidity    DOUBLE PRECISION  NULL\n    );\nselect table_name from create_hypertable( 'conditions', 'timec');\n table_name \n------------\n conditions\n\ninsert into conditions values ( '2018-01-01 09:20:00-08', 'SFO', 55, 45);\ninsert into conditions values ( '2018-01-02 09:30:00-08', 'por', 100, 100);\ninsert into conditions values ( '2018-01-02 09:20:00-08', 'SFO', 65, 45);\ninsert into conditions values ( '2018-01-02 09:10:00-08', 'NYC', 65, 45);\ninsert into conditions values ( '2018-11-01 09:20:00-08', 'NYC', 45, 30);\ninsert into conditions values ( '2018-11-01 10:40:00-08', 'NYC', 55, 35);\ninsert into conditions values ( '2018-11-01 11:50:00-08', 'NYC', 65, 40);\ninsert into conditions values ( '2018-11-01 12:10:00-08', 'NYC', 75, 45);\ninsert into conditions values ( '2018-11-01 13:10:00-08', 'NYC', 85, 50);\ninsert into conditions values ( '2018-11-02 09:20:00-08', 'NYC', 10, 10);\ninsert into conditions values ( '2018-11-02 10:30:00-08', 'NYC', 20, 15);\ninsert into conditions values ( '2018-11-02 11:40:00-08', 'NYC', null, null);\ninsert into conditions values ( '2018-11-03 09:50:00-08', 'NYC', null, null);\ncreate table location_tab( locid integer, locname text );\ninsert into location_tab values( 1, 'SFO');\ninsert into location_tab values( 2, 'NYC');\ninsert into location_tab values( 3, 'por');\ncreate materialized view mat_m1( location, timec, minl, sumt , sumh)\nWITH (timescaledb.continuous, timescaledb.materialized_only=false)\nas\nselect location, time_bucket('1day', timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by time_bucket('1day', timec), location WITH NO DATA;\n--compute time_bucketted max+bucket_width for the materialized view\nSELECT time_bucket('1day' , q.timeval+ '1day'::interval)\nFROM ( select max(timec)as timeval from conditions ) as q;\n         time_bucket          \n------------------------------\n Sat Nov 03 17:00:00 2018 PDT\n\nCALL refresh_continuous_aggregate('mat_m1', NULL, NULL);\n--test first/last\ncreate materialized view mat_m2(location, timec, firsth, lasth, maxtemp, mintemp)\nWITH (timescaledb.continuous, timescaledb.materialized_only=false)\nas\nselect location, time_bucket('1day', timec), first(humidity, timec), last(humidity, timec), max(temperature), min(temperature)\nfrom conditions\ngroup by time_bucket('1day', timec), location WITH NO DATA;\n--time that refresh assumes as now() for repeatability\nSELECT time_bucket('1day' , q.timeval+ '1day'::interval)\nFROM ( select max(timec)as timeval from conditions ) as q;\n         time_bucket          \n------------------------------\n Sat Nov 03 17:00:00 2018 PDT\n\nCALL refresh_continuous_aggregate('mat_m2', NULL, NULL);\n--normal view --\ncreate or replace view regview( location, timec, minl, sumt , sumh)\nas\nselect location, time_bucket('1day', timec), min(location), sum(temperature),sum(humidity)\nfrom conditions\ngroup by location, time_bucket('1day', timec);\nset enable_hashagg = false;\n-- NO pushdown cases ---\n--when we have addl. attrs in order by that are not in the\n-- group by, we will still need a sort\n:EXPLAIN\nselect * from mat_m1 order by sumh, sumt, minl, timec ;\n--- QUERY PLAN ---\n Sort\n   Output: _materialized_hypertable_2.location, _materialized_hypertable_2.timec, _materialized_hypertable_2.minl, _materialized_hypertable_2.sumt, _materialized_hypertable_2.sumh\n   Sort Key: _materialized_hypertable_2.sumh, _materialized_hypertable_2.sumt, _materialized_hypertable_2.minl, _materialized_hypertable_2.timec\n   ->  Append\n         ->  Append\n               ->  Seq Scan on _timescaledb_internal._hyper_2_3_chunk\n                     Output: _hyper_2_3_chunk.location, _hyper_2_3_chunk.timec, _hyper_2_3_chunk.minl, _hyper_2_3_chunk.sumt, _hyper_2_3_chunk.sumh\n               ->  Index Scan using _hyper_2_4_chunk__materialized_hypertable_2_timec_idx on _timescaledb_internal._hyper_2_4_chunk\n                     Output: _hyper_2_4_chunk.location, _hyper_2_4_chunk.timec, _hyper_2_4_chunk.minl, _hyper_2_4_chunk.sumt, _hyper_2_4_chunk.sumh\n                     Index Cond: (_hyper_2_4_chunk.timec < 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone)\n         ->  GroupAggregate\n               Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), min(_hyper_1_2_chunk.location), sum(_hyper_1_2_chunk.temperature), sum(_hyper_1_2_chunk.humidity)\n               Group Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.location\n               ->  Sort\n                     Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.temperature, _hyper_1_2_chunk.humidity\n                     Sort Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.location\n                     ->  Result\n                           Output: _hyper_1_2_chunk.location, time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec), _hyper_1_2_chunk.temperature, _hyper_1_2_chunk.humidity\n                           ->  Index Scan using _hyper_1_2_chunk_conditions_timec_idx on _timescaledb_internal._hyper_1_2_chunk\n                                 Output: _hyper_1_2_chunk.location, _hyper_1_2_chunk.timec, _hyper_1_2_chunk.temperature, _hyper_1_2_chunk.humidity\n                                 Index Cond: (_hyper_1_2_chunk.timec >= 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone)\n\n:EXPLAIN\nselect * from regview order by timec desc;\n--- QUERY PLAN ---\n Sort\n   Output: conditions.location, (time_bucket('@ 1 day'::interval, conditions.timec)), (min(conditions.location)), (sum(conditions.temperature)), (sum(conditions.humidity))\n   Sort Key: (time_bucket('@ 1 day'::interval, conditions.timec)) DESC\n   ->  Finalize GroupAggregate\n         Output: conditions.location, (time_bucket('@ 1 day'::interval, conditions.timec)), min(conditions.location), sum(conditions.temperature), sum(conditions.humidity)\n         Group Key: conditions.location, (time_bucket('@ 1 day'::interval, conditions.timec))\n         ->  Sort\n               Output: conditions.location, (time_bucket('@ 1 day'::interval, conditions.timec)), (PARTIAL min(conditions.location)), (PARTIAL sum(conditions.temperature)), (PARTIAL sum(conditions.humidity))\n               Sort Key: conditions.location, (time_bucket('@ 1 day'::interval, conditions.timec))\n               ->  Append\n                     ->  Partial GroupAggregate\n                           Output: _hyper_1_1_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_1_chunk.timec)), PARTIAL min(_hyper_1_1_chunk.location), PARTIAL sum(_hyper_1_1_chunk.temperature), PARTIAL sum(_hyper_1_1_chunk.humidity)\n                           Group Key: _hyper_1_1_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_1_chunk.timec))\n                           ->  Sort\n                                 Output: _hyper_1_1_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_1_chunk.timec)), _hyper_1_1_chunk.temperature, _hyper_1_1_chunk.humidity\n                                 Sort Key: _hyper_1_1_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_1_chunk.timec))\n                                 ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                                       Output: _hyper_1_1_chunk.location, time_bucket('@ 1 day'::interval, _hyper_1_1_chunk.timec), _hyper_1_1_chunk.temperature, _hyper_1_1_chunk.humidity\n                     ->  Partial GroupAggregate\n                           Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), PARTIAL min(_hyper_1_2_chunk.location), PARTIAL sum(_hyper_1_2_chunk.temperature), PARTIAL sum(_hyper_1_2_chunk.humidity)\n                           Group Key: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec))\n                           ->  Sort\n                                 Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.temperature, _hyper_1_2_chunk.humidity\n                                 Sort Key: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec))\n                                 ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                                       Output: _hyper_1_2_chunk.location, time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec), _hyper_1_2_chunk.temperature, _hyper_1_2_chunk.humidity\n\n-- PUSHDOWN cases --\n-- all group by elts in order by , reorder group by elts to match\n-- group by order\n-- This should prevent an additional sort after GroupAggregate\n:EXPLAIN\nselect * from mat_m1 order by timec desc, location;\n--- QUERY PLAN ---\n Sort\n   Output: _materialized_hypertable_2.location, _materialized_hypertable_2.timec, _materialized_hypertable_2.minl, _materialized_hypertable_2.sumt, _materialized_hypertable_2.sumh\n   Sort Key: _materialized_hypertable_2.timec DESC, _materialized_hypertable_2.location\n   ->  Append\n         ->  Append\n               ->  Seq Scan on _timescaledb_internal._hyper_2_3_chunk\n                     Output: _hyper_2_3_chunk.location, _hyper_2_3_chunk.timec, _hyper_2_3_chunk.minl, _hyper_2_3_chunk.sumt, _hyper_2_3_chunk.sumh\n               ->  Index Scan using _hyper_2_4_chunk__materialized_hypertable_2_timec_idx on _timescaledb_internal._hyper_2_4_chunk\n                     Output: _hyper_2_4_chunk.location, _hyper_2_4_chunk.timec, _hyper_2_4_chunk.minl, _hyper_2_4_chunk.sumt, _hyper_2_4_chunk.sumh\n                     Index Cond: (_hyper_2_4_chunk.timec < 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone)\n         ->  GroupAggregate\n               Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), min(_hyper_1_2_chunk.location), sum(_hyper_1_2_chunk.temperature), sum(_hyper_1_2_chunk.humidity)\n               Group Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.location\n               ->  Sort\n                     Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.temperature, _hyper_1_2_chunk.humidity\n                     Sort Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.location\n                     ->  Result\n                           Output: _hyper_1_2_chunk.location, time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec), _hyper_1_2_chunk.temperature, _hyper_1_2_chunk.humidity\n                           ->  Index Scan using _hyper_1_2_chunk_conditions_timec_idx on _timescaledb_internal._hyper_1_2_chunk\n                                 Output: _hyper_1_2_chunk.location, _hyper_1_2_chunk.timec, _hyper_1_2_chunk.temperature, _hyper_1_2_chunk.humidity\n                                 Index Cond: (_hyper_1_2_chunk.timec >= 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone)\n\n:EXPLAIN\nselect * from mat_m1 order by location, timec desc;\n--- QUERY PLAN ---\n Sort\n   Output: _materialized_hypertable_2.location, _materialized_hypertable_2.timec, _materialized_hypertable_2.minl, _materialized_hypertable_2.sumt, _materialized_hypertable_2.sumh\n   Sort Key: _materialized_hypertable_2.location, _materialized_hypertable_2.timec DESC\n   ->  Append\n         ->  Append\n               ->  Seq Scan on _timescaledb_internal._hyper_2_3_chunk\n                     Output: _hyper_2_3_chunk.location, _hyper_2_3_chunk.timec, _hyper_2_3_chunk.minl, _hyper_2_3_chunk.sumt, _hyper_2_3_chunk.sumh\n               ->  Index Scan using _hyper_2_4_chunk__materialized_hypertable_2_timec_idx on _timescaledb_internal._hyper_2_4_chunk\n                     Output: _hyper_2_4_chunk.location, _hyper_2_4_chunk.timec, _hyper_2_4_chunk.minl, _hyper_2_4_chunk.sumt, _hyper_2_4_chunk.sumh\n                     Index Cond: (_hyper_2_4_chunk.timec < 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone)\n         ->  GroupAggregate\n               Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), min(_hyper_1_2_chunk.location), sum(_hyper_1_2_chunk.temperature), sum(_hyper_1_2_chunk.humidity)\n               Group Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.location\n               ->  Sort\n                     Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.temperature, _hyper_1_2_chunk.humidity\n                     Sort Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.location\n                     ->  Result\n                           Output: _hyper_1_2_chunk.location, time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec), _hyper_1_2_chunk.temperature, _hyper_1_2_chunk.humidity\n                           ->  Index Scan using _hyper_1_2_chunk_conditions_timec_idx on _timescaledb_internal._hyper_1_2_chunk\n                                 Output: _hyper_1_2_chunk.location, _hyper_1_2_chunk.timec, _hyper_1_2_chunk.temperature, _hyper_1_2_chunk.humidity\n                                 Index Cond: (_hyper_1_2_chunk.timec >= 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone)\n\n:EXPLAIN\nselect * from mat_m1 order by location, timec asc;\n--- QUERY PLAN ---\n Sort\n   Output: _materialized_hypertable_2.location, _materialized_hypertable_2.timec, _materialized_hypertable_2.minl, _materialized_hypertable_2.sumt, _materialized_hypertable_2.sumh\n   Sort Key: _materialized_hypertable_2.location, _materialized_hypertable_2.timec\n   ->  Append\n         ->  Append\n               ->  Seq Scan on _timescaledb_internal._hyper_2_3_chunk\n                     Output: _hyper_2_3_chunk.location, _hyper_2_3_chunk.timec, _hyper_2_3_chunk.minl, _hyper_2_3_chunk.sumt, _hyper_2_3_chunk.sumh\n               ->  Index Scan using _hyper_2_4_chunk__materialized_hypertable_2_timec_idx on _timescaledb_internal._hyper_2_4_chunk\n                     Output: _hyper_2_4_chunk.location, _hyper_2_4_chunk.timec, _hyper_2_4_chunk.minl, _hyper_2_4_chunk.sumt, _hyper_2_4_chunk.sumh\n                     Index Cond: (_hyper_2_4_chunk.timec < 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone)\n         ->  GroupAggregate\n               Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), min(_hyper_1_2_chunk.location), sum(_hyper_1_2_chunk.temperature), sum(_hyper_1_2_chunk.humidity)\n               Group Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.location\n               ->  Sort\n                     Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.temperature, _hyper_1_2_chunk.humidity\n                     Sort Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.location\n                     ->  Result\n                           Output: _hyper_1_2_chunk.location, time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec), _hyper_1_2_chunk.temperature, _hyper_1_2_chunk.humidity\n                           ->  Index Scan using _hyper_1_2_chunk_conditions_timec_idx on _timescaledb_internal._hyper_1_2_chunk\n                                 Output: _hyper_1_2_chunk.location, _hyper_1_2_chunk.timec, _hyper_1_2_chunk.temperature, _hyper_1_2_chunk.humidity\n                                 Index Cond: (_hyper_1_2_chunk.timec >= 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone)\n\n:EXPLAIN\nselect * from mat_m1 where timec > '2018-10-01' order by timec desc;\n--- QUERY PLAN ---\n Append\n   ->  GroupAggregate\n         Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), min(_hyper_1_2_chunk.location), sum(_hyper_1_2_chunk.temperature), sum(_hyper_1_2_chunk.humidity)\n         Group Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.location\n         ->  Sort\n               Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.temperature, _hyper_1_2_chunk.humidity\n               Sort Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)) DESC, _hyper_1_2_chunk.location\n               ->  Result\n                     Output: _hyper_1_2_chunk.location, time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec), _hyper_1_2_chunk.temperature, _hyper_1_2_chunk.humidity\n                     ->  Index Scan using _hyper_1_2_chunk_conditions_timec_idx on _timescaledb_internal._hyper_1_2_chunk\n                           Output: _hyper_1_2_chunk.location, _hyper_1_2_chunk.timec, _hyper_1_2_chunk.temperature, _hyper_1_2_chunk.humidity\n                           Index Cond: ((_hyper_1_2_chunk.timec >= 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone) AND (_hyper_1_2_chunk.timec > 'Mon Oct 01 00:00:00 2018 PDT'::timestamp with time zone))\n                           Filter: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec) > 'Mon Oct 01 00:00:00 2018 PDT'::timestamp with time zone)\n   ->  Index Scan using _hyper_2_4_chunk__materialized_hypertable_2_timec_idx on _timescaledb_internal._hyper_2_4_chunk\n         Output: _hyper_2_4_chunk.location, _hyper_2_4_chunk.timec, _hyper_2_4_chunk.minl, _hyper_2_4_chunk.sumt, _hyper_2_4_chunk.sumh\n         Index Cond: ((_hyper_2_4_chunk.timec < 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone) AND (_hyper_2_4_chunk.timec > 'Mon Oct 01 00:00:00 2018 PDT'::timestamp with time zone))\n\n-- outer sort is used by mat_m1 for grouping. But doesn't avoid a sort after the join ---\n:EXPLAIN\nselect l.locid, mat_m1.* from mat_m1 , location_tab l where timec > '2018-10-01' and l.locname = mat_m1.location order by timec desc;\n--- QUERY PLAN ---\n Sort\n   Output: l.locid, _hyper_2_4_chunk.location, _hyper_2_4_chunk.timec, _hyper_2_4_chunk.minl, _hyper_2_4_chunk.sumt, _hyper_2_4_chunk.sumh\n   Sort Key: _hyper_2_4_chunk.timec DESC\n   ->  Hash Join\n         Output: l.locid, _hyper_2_4_chunk.location, _hyper_2_4_chunk.timec, _hyper_2_4_chunk.minl, _hyper_2_4_chunk.sumt, _hyper_2_4_chunk.sumh\n         Hash Cond: (l.locname = _hyper_2_4_chunk.location)\n         ->  Seq Scan on public.location_tab l\n               Output: l.locid, l.locname\n         ->  Hash\n               Output: _hyper_2_4_chunk.location, _hyper_2_4_chunk.timec, _hyper_2_4_chunk.minl, _hyper_2_4_chunk.sumt, _hyper_2_4_chunk.sumh\n               ->  Append\n                     ->  Index Scan using _hyper_2_4_chunk__materialized_hypertable_2_timec_idx on _timescaledb_internal._hyper_2_4_chunk\n                           Output: _hyper_2_4_chunk.location, _hyper_2_4_chunk.timec, _hyper_2_4_chunk.minl, _hyper_2_4_chunk.sumt, _hyper_2_4_chunk.sumh\n                           Index Cond: ((_hyper_2_4_chunk.timec < 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone) AND (_hyper_2_4_chunk.timec > 'Mon Oct 01 00:00:00 2018 PDT'::timestamp with time zone))\n                     ->  GroupAggregate\n                           Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), min(_hyper_1_2_chunk.location), sum(_hyper_1_2_chunk.temperature), sum(_hyper_1_2_chunk.humidity)\n                           Group Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.location\n                           ->  Sort\n                                 Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.temperature, _hyper_1_2_chunk.humidity\n                                 Sort Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.location\n                                 ->  Result\n                                       Output: _hyper_1_2_chunk.location, time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec), _hyper_1_2_chunk.temperature, _hyper_1_2_chunk.humidity\n                                       ->  Index Scan using _hyper_1_2_chunk_conditions_timec_idx on _timescaledb_internal._hyper_1_2_chunk\n                                             Output: _hyper_1_2_chunk.location, _hyper_1_2_chunk.timec, _hyper_1_2_chunk.temperature, _hyper_1_2_chunk.humidity\n                                             Index Cond: ((_hyper_1_2_chunk.timec >= 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone) AND (_hyper_1_2_chunk.timec > 'Mon Oct 01 00:00:00 2018 PDT'::timestamp with time zone))\n                                             Filter: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec) > 'Mon Oct 01 00:00:00 2018 PDT'::timestamp with time zone)\n\n:EXPLAIN\nselect * from mat_m2 where timec > '2018-10-01' order by timec desc;\n--- QUERY PLAN ---\n Append\n   ->  GroupAggregate\n         Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), first(_hyper_1_2_chunk.humidity, _hyper_1_2_chunk.timec), last(_hyper_1_2_chunk.humidity, _hyper_1_2_chunk.timec), max(_hyper_1_2_chunk.temperature), min(_hyper_1_2_chunk.temperature)\n         Group Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.location\n         ->  Sort\n               Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.humidity, _hyper_1_2_chunk.timec, _hyper_1_2_chunk.temperature\n               Sort Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)) DESC, _hyper_1_2_chunk.location\n               ->  Result\n                     Output: _hyper_1_2_chunk.location, time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec), _hyper_1_2_chunk.humidity, _hyper_1_2_chunk.timec, _hyper_1_2_chunk.temperature\n                     ->  Index Scan using _hyper_1_2_chunk_conditions_timec_idx on _timescaledb_internal._hyper_1_2_chunk\n                           Output: _hyper_1_2_chunk.location, _hyper_1_2_chunk.timec, _hyper_1_2_chunk.humidity, _hyper_1_2_chunk.temperature\n                           Index Cond: ((_hyper_1_2_chunk.timec >= 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone) AND (_hyper_1_2_chunk.timec > 'Mon Oct 01 00:00:00 2018 PDT'::timestamp with time zone))\n                           Filter: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec) > 'Mon Oct 01 00:00:00 2018 PDT'::timestamp with time zone)\n   ->  Index Scan using _hyper_3_6_chunk__materialized_hypertable_3_timec_idx on _timescaledb_internal._hyper_3_6_chunk\n         Output: _hyper_3_6_chunk.location, _hyper_3_6_chunk.timec, _hyper_3_6_chunk.firsth, _hyper_3_6_chunk.lasth, _hyper_3_6_chunk.maxtemp, _hyper_3_6_chunk.mintemp\n         Index Cond: ((_hyper_3_6_chunk.timec < 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone) AND (_hyper_3_6_chunk.timec > 'Mon Oct 01 00:00:00 2018 PDT'::timestamp with time zone))\n\n:EXPLAIN\nselect * from (select * from mat_m2 where timec > '2018-10-01' order by timec desc ) as q limit 1;\n--- QUERY PLAN ---\n Limit\n   Output: _hyper_3_6_chunk.location, _hyper_3_6_chunk.timec, _hyper_3_6_chunk.firsth, _hyper_3_6_chunk.lasth, _hyper_3_6_chunk.maxtemp, _hyper_3_6_chunk.mintemp\n   ->  Sort\n         Output: _hyper_3_6_chunk.location, _hyper_3_6_chunk.timec, _hyper_3_6_chunk.firsth, _hyper_3_6_chunk.lasth, _hyper_3_6_chunk.maxtemp, _hyper_3_6_chunk.mintemp\n         Sort Key: _hyper_3_6_chunk.timec DESC\n         ->  Append\n               ->  Index Scan using _hyper_3_6_chunk__materialized_hypertable_3_timec_idx on _timescaledb_internal._hyper_3_6_chunk\n                     Output: _hyper_3_6_chunk.location, _hyper_3_6_chunk.timec, _hyper_3_6_chunk.firsth, _hyper_3_6_chunk.lasth, _hyper_3_6_chunk.maxtemp, _hyper_3_6_chunk.mintemp\n                     Index Cond: ((_hyper_3_6_chunk.timec < 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone) AND (_hyper_3_6_chunk.timec > 'Mon Oct 01 00:00:00 2018 PDT'::timestamp with time zone))\n               ->  GroupAggregate\n                     Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), first(_hyper_1_2_chunk.humidity, _hyper_1_2_chunk.timec), last(_hyper_1_2_chunk.humidity, _hyper_1_2_chunk.timec), max(_hyper_1_2_chunk.temperature), min(_hyper_1_2_chunk.temperature)\n                     Group Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.location\n                     ->  Sort\n                           Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.humidity, _hyper_1_2_chunk.timec, _hyper_1_2_chunk.temperature\n                           Sort Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.location\n                           ->  Result\n                                 Output: _hyper_1_2_chunk.location, time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec), _hyper_1_2_chunk.humidity, _hyper_1_2_chunk.timec, _hyper_1_2_chunk.temperature\n                                 ->  Index Scan using _hyper_1_2_chunk_conditions_timec_idx on _timescaledb_internal._hyper_1_2_chunk\n                                       Output: _hyper_1_2_chunk.location, _hyper_1_2_chunk.timec, _hyper_1_2_chunk.humidity, _hyper_1_2_chunk.temperature\n                                       Index Cond: ((_hyper_1_2_chunk.timec >= 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone) AND (_hyper_1_2_chunk.timec > 'Mon Oct 01 00:00:00 2018 PDT'::timestamp with time zone))\n                                       Filter: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec) > 'Mon Oct 01 00:00:00 2018 PDT'::timestamp with time zone)\n\n:EXPLAIN\nselect * from (select * from mat_m2 where timec > '2018-10-01' order by timec desc , location asc nulls first) as q limit 1;\n--- QUERY PLAN ---\n Limit\n   Output: _hyper_3_6_chunk.location, _hyper_3_6_chunk.timec, _hyper_3_6_chunk.firsth, _hyper_3_6_chunk.lasth, _hyper_3_6_chunk.maxtemp, _hyper_3_6_chunk.mintemp\n   ->  Sort\n         Output: _hyper_3_6_chunk.location, _hyper_3_6_chunk.timec, _hyper_3_6_chunk.firsth, _hyper_3_6_chunk.lasth, _hyper_3_6_chunk.maxtemp, _hyper_3_6_chunk.mintemp\n         Sort Key: _hyper_3_6_chunk.timec DESC, _hyper_3_6_chunk.location NULLS FIRST\n         ->  Append\n               ->  Index Scan using _hyper_3_6_chunk__materialized_hypertable_3_timec_idx on _timescaledb_internal._hyper_3_6_chunk\n                     Output: _hyper_3_6_chunk.location, _hyper_3_6_chunk.timec, _hyper_3_6_chunk.firsth, _hyper_3_6_chunk.lasth, _hyper_3_6_chunk.maxtemp, _hyper_3_6_chunk.mintemp\n                     Index Cond: ((_hyper_3_6_chunk.timec < 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone) AND (_hyper_3_6_chunk.timec > 'Mon Oct 01 00:00:00 2018 PDT'::timestamp with time zone))\n               ->  GroupAggregate\n                     Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), first(_hyper_1_2_chunk.humidity, _hyper_1_2_chunk.timec), last(_hyper_1_2_chunk.humidity, _hyper_1_2_chunk.timec), max(_hyper_1_2_chunk.temperature), min(_hyper_1_2_chunk.temperature)\n                     Group Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.location\n                     ->  Sort\n                           Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.humidity, _hyper_1_2_chunk.timec, _hyper_1_2_chunk.temperature\n                           Sort Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.location\n                           ->  Result\n                                 Output: _hyper_1_2_chunk.location, time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec), _hyper_1_2_chunk.humidity, _hyper_1_2_chunk.timec, _hyper_1_2_chunk.temperature\n                                 ->  Index Scan using _hyper_1_2_chunk_conditions_timec_idx on _timescaledb_internal._hyper_1_2_chunk\n                                       Output: _hyper_1_2_chunk.location, _hyper_1_2_chunk.timec, _hyper_1_2_chunk.humidity, _hyper_1_2_chunk.temperature\n                                       Index Cond: ((_hyper_1_2_chunk.timec >= 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone) AND (_hyper_1_2_chunk.timec > 'Mon Oct 01 00:00:00 2018 PDT'::timestamp with time zone))\n                                       Filter: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec) > 'Mon Oct 01 00:00:00 2018 PDT'::timestamp with time zone)\n\n--plans with CTE\n:EXPLAIN\nwith m1 as (\nSelect * from mat_m2 where timec > '2018-10-01' order by timec desc )\nselect * from m1;\n--- QUERY PLAN ---\n Sort\n   Output: _hyper_3_6_chunk.location, _hyper_3_6_chunk.timec, _hyper_3_6_chunk.firsth, _hyper_3_6_chunk.lasth, _hyper_3_6_chunk.maxtemp, _hyper_3_6_chunk.mintemp\n   Sort Key: _hyper_3_6_chunk.timec DESC\n   ->  Append\n         ->  Index Scan using _hyper_3_6_chunk__materialized_hypertable_3_timec_idx on _timescaledb_internal._hyper_3_6_chunk\n               Output: _hyper_3_6_chunk.location, _hyper_3_6_chunk.timec, _hyper_3_6_chunk.firsth, _hyper_3_6_chunk.lasth, _hyper_3_6_chunk.maxtemp, _hyper_3_6_chunk.mintemp\n               Index Cond: ((_hyper_3_6_chunk.timec < 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone) AND (_hyper_3_6_chunk.timec > 'Mon Oct 01 00:00:00 2018 PDT'::timestamp with time zone))\n         ->  GroupAggregate\n               Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), first(_hyper_1_2_chunk.humidity, _hyper_1_2_chunk.timec), last(_hyper_1_2_chunk.humidity, _hyper_1_2_chunk.timec), max(_hyper_1_2_chunk.temperature), min(_hyper_1_2_chunk.temperature)\n               Group Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.location\n               ->  Sort\n                     Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.humidity, _hyper_1_2_chunk.timec, _hyper_1_2_chunk.temperature\n                     Sort Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.location\n                     ->  Result\n                           Output: _hyper_1_2_chunk.location, time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec), _hyper_1_2_chunk.humidity, _hyper_1_2_chunk.timec, _hyper_1_2_chunk.temperature\n                           ->  Index Scan using _hyper_1_2_chunk_conditions_timec_idx on _timescaledb_internal._hyper_1_2_chunk\n                                 Output: _hyper_1_2_chunk.location, _hyper_1_2_chunk.timec, _hyper_1_2_chunk.humidity, _hyper_1_2_chunk.temperature\n                                 Index Cond: ((_hyper_1_2_chunk.timec >= 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone) AND (_hyper_1_2_chunk.timec > 'Mon Oct 01 00:00:00 2018 PDT'::timestamp with time zone))\n                                 Filter: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec) > 'Mon Oct 01 00:00:00 2018 PDT'::timestamp with time zone)\n\n-- should reorder mat_m1 group by only based on mat_m1 order-by\n:EXPLAIN\nselect * from mat_m1, mat_m2 where mat_m1.timec > '2018-10-01' and mat_m1.timec = mat_m2.timec order by mat_m1.timec desc;\n--- QUERY PLAN ---\n Sort\n   Output: _hyper_2_4_chunk.location, _hyper_2_4_chunk.timec, _hyper_2_4_chunk.minl, _hyper_2_4_chunk.sumt, _hyper_2_4_chunk.sumh, _materialized_hypertable_3.location, _materialized_hypertable_3.timec, _materialized_hypertable_3.firsth, _materialized_hypertable_3.lasth, _materialized_hypertable_3.maxtemp, _materialized_hypertable_3.mintemp\n   Sort Key: _hyper_2_4_chunk.timec DESC\n   ->  Hash Join\n         Output: _hyper_2_4_chunk.location, _hyper_2_4_chunk.timec, _hyper_2_4_chunk.minl, _hyper_2_4_chunk.sumt, _hyper_2_4_chunk.sumh, _materialized_hypertable_3.location, _materialized_hypertable_3.timec, _materialized_hypertable_3.firsth, _materialized_hypertable_3.lasth, _materialized_hypertable_3.maxtemp, _materialized_hypertable_3.mintemp\n         Hash Cond: (_materialized_hypertable_3.timec = _hyper_2_4_chunk.timec)\n         ->  Append\n               ->  Append\n                     ->  Seq Scan on _timescaledb_internal._hyper_3_5_chunk\n                           Output: _hyper_3_5_chunk.location, _hyper_3_5_chunk.timec, _hyper_3_5_chunk.firsth, _hyper_3_5_chunk.lasth, _hyper_3_5_chunk.maxtemp, _hyper_3_5_chunk.mintemp\n                     ->  Index Scan using _hyper_3_6_chunk__materialized_hypertable_3_timec_idx on _timescaledb_internal._hyper_3_6_chunk\n                           Output: _hyper_3_6_chunk.location, _hyper_3_6_chunk.timec, _hyper_3_6_chunk.firsth, _hyper_3_6_chunk.lasth, _hyper_3_6_chunk.maxtemp, _hyper_3_6_chunk.mintemp\n                           Index Cond: (_hyper_3_6_chunk.timec < 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone)\n               ->  GroupAggregate\n                     Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), first(_hyper_1_2_chunk.humidity, _hyper_1_2_chunk.timec), last(_hyper_1_2_chunk.humidity, _hyper_1_2_chunk.timec), max(_hyper_1_2_chunk.temperature), min(_hyper_1_2_chunk.temperature)\n                     Group Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.location\n                     ->  Sort\n                           Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.humidity, _hyper_1_2_chunk.timec, _hyper_1_2_chunk.temperature\n                           Sort Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.location\n                           ->  Result\n                                 Output: _hyper_1_2_chunk.location, time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec), _hyper_1_2_chunk.humidity, _hyper_1_2_chunk.timec, _hyper_1_2_chunk.temperature\n                                 ->  Index Scan using _hyper_1_2_chunk_conditions_timec_idx on _timescaledb_internal._hyper_1_2_chunk\n                                       Output: _hyper_1_2_chunk.location, _hyper_1_2_chunk.timec, _hyper_1_2_chunk.humidity, _hyper_1_2_chunk.temperature\n                                       Index Cond: (_hyper_1_2_chunk.timec >= 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone)\n         ->  Hash\n               Output: _hyper_2_4_chunk.location, _hyper_2_4_chunk.timec, _hyper_2_4_chunk.minl, _hyper_2_4_chunk.sumt, _hyper_2_4_chunk.sumh\n               ->  Append\n                     ->  Index Scan using _hyper_2_4_chunk__materialized_hypertable_2_timec_idx on _timescaledb_internal._hyper_2_4_chunk\n                           Output: _hyper_2_4_chunk.location, _hyper_2_4_chunk.timec, _hyper_2_4_chunk.minl, _hyper_2_4_chunk.sumt, _hyper_2_4_chunk.sumh\n                           Index Cond: ((_hyper_2_4_chunk.timec < 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone) AND (_hyper_2_4_chunk.timec > 'Mon Oct 01 00:00:00 2018 PDT'::timestamp with time zone))\n                     ->  GroupAggregate\n                           Output: _hyper_1_2_chunk_1.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk_1.timec)), min(_hyper_1_2_chunk_1.location), sum(_hyper_1_2_chunk_1.temperature), sum(_hyper_1_2_chunk_1.humidity)\n                           Group Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk_1.timec)), _hyper_1_2_chunk_1.location\n                           ->  Sort\n                                 Output: _hyper_1_2_chunk_1.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk_1.timec)), _hyper_1_2_chunk_1.temperature, _hyper_1_2_chunk_1.humidity\n                                 Sort Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk_1.timec)), _hyper_1_2_chunk_1.location\n                                 ->  Result\n                                       Output: _hyper_1_2_chunk_1.location, time_bucket('@ 1 day'::interval, _hyper_1_2_chunk_1.timec), _hyper_1_2_chunk_1.temperature, _hyper_1_2_chunk_1.humidity\n                                       ->  Index Scan using _hyper_1_2_chunk_conditions_timec_idx on _timescaledb_internal._hyper_1_2_chunk _hyper_1_2_chunk_1\n                                             Output: _hyper_1_2_chunk_1.location, _hyper_1_2_chunk_1.timec, _hyper_1_2_chunk_1.temperature, _hyper_1_2_chunk_1.humidity\n                                             Index Cond: ((_hyper_1_2_chunk_1.timec >= 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone) AND (_hyper_1_2_chunk_1.timec > 'Mon Oct 01 00:00:00 2018 PDT'::timestamp with time zone))\n                                             Filter: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk_1.timec) > 'Mon Oct 01 00:00:00 2018 PDT'::timestamp with time zone)\n\n--should reorder only for mat_m1.\n:EXPLAIN\nselect * from mat_m1, regview where mat_m1.timec > '2018-10-01' and mat_m1.timec = regview.timec order by mat_m1.timec desc;\n--- QUERY PLAN ---\n Sort\n   Output: _hyper_2_4_chunk.location, _hyper_2_4_chunk.timec, _hyper_2_4_chunk.minl, _hyper_2_4_chunk.sumt, _hyper_2_4_chunk.sumh, conditions.location, (time_bucket('@ 1 day'::interval, conditions.timec)), (min(conditions.location)), (sum(conditions.temperature)), (sum(conditions.humidity))\n   Sort Key: _hyper_2_4_chunk.timec DESC\n   ->  Hash Join\n         Output: _hyper_2_4_chunk.location, _hyper_2_4_chunk.timec, _hyper_2_4_chunk.minl, _hyper_2_4_chunk.sumt, _hyper_2_4_chunk.sumh, conditions.location, (time_bucket('@ 1 day'::interval, conditions.timec)), (min(conditions.location)), (sum(conditions.temperature)), (sum(conditions.humidity))\n         Hash Cond: ((time_bucket('@ 1 day'::interval, conditions.timec)) = _hyper_2_4_chunk.timec)\n         ->  Finalize GroupAggregate\n               Output: conditions.location, (time_bucket('@ 1 day'::interval, conditions.timec)), min(conditions.location), sum(conditions.temperature), sum(conditions.humidity)\n               Group Key: conditions.location, (time_bucket('@ 1 day'::interval, conditions.timec))\n               ->  Sort\n                     Output: conditions.location, (time_bucket('@ 1 day'::interval, conditions.timec)), (PARTIAL min(conditions.location)), (PARTIAL sum(conditions.temperature)), (PARTIAL sum(conditions.humidity))\n                     Sort Key: conditions.location, (time_bucket('@ 1 day'::interval, conditions.timec))\n                     ->  Append\n                           ->  Partial GroupAggregate\n                                 Output: _hyper_1_1_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_1_chunk.timec)), PARTIAL min(_hyper_1_1_chunk.location), PARTIAL sum(_hyper_1_1_chunk.temperature), PARTIAL sum(_hyper_1_1_chunk.humidity)\n                                 Group Key: _hyper_1_1_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_1_chunk.timec))\n                                 ->  Sort\n                                       Output: _hyper_1_1_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_1_chunk.timec)), _hyper_1_1_chunk.temperature, _hyper_1_1_chunk.humidity\n                                       Sort Key: _hyper_1_1_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_1_chunk.timec))\n                                       ->  Seq Scan on _timescaledb_internal._hyper_1_1_chunk\n                                             Output: _hyper_1_1_chunk.location, time_bucket('@ 1 day'::interval, _hyper_1_1_chunk.timec), _hyper_1_1_chunk.temperature, _hyper_1_1_chunk.humidity\n                           ->  Partial GroupAggregate\n                                 Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), PARTIAL min(_hyper_1_2_chunk.location), PARTIAL sum(_hyper_1_2_chunk.temperature), PARTIAL sum(_hyper_1_2_chunk.humidity)\n                                 Group Key: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec))\n                                 ->  Sort\n                                       Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.temperature, _hyper_1_2_chunk.humidity\n                                       Sort Key: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec))\n                                       ->  Seq Scan on _timescaledb_internal._hyper_1_2_chunk\n                                             Output: _hyper_1_2_chunk.location, time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec), _hyper_1_2_chunk.temperature, _hyper_1_2_chunk.humidity\n         ->  Hash\n               Output: _hyper_2_4_chunk.location, _hyper_2_4_chunk.timec, _hyper_2_4_chunk.minl, _hyper_2_4_chunk.sumt, _hyper_2_4_chunk.sumh\n               ->  Append\n                     ->  Index Scan using _hyper_2_4_chunk__materialized_hypertable_2_timec_idx on _timescaledb_internal._hyper_2_4_chunk\n                           Output: _hyper_2_4_chunk.location, _hyper_2_4_chunk.timec, _hyper_2_4_chunk.minl, _hyper_2_4_chunk.sumt, _hyper_2_4_chunk.sumh\n                           Index Cond: ((_hyper_2_4_chunk.timec < 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone) AND (_hyper_2_4_chunk.timec > 'Mon Oct 01 00:00:00 2018 PDT'::timestamp with time zone))\n                     ->  GroupAggregate\n                           Output: _hyper_1_2_chunk_1.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk_1.timec)), min(_hyper_1_2_chunk_1.location), sum(_hyper_1_2_chunk_1.temperature), sum(_hyper_1_2_chunk_1.humidity)\n                           Group Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk_1.timec)), _hyper_1_2_chunk_1.location\n                           ->  Sort\n                                 Output: _hyper_1_2_chunk_1.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk_1.timec)), _hyper_1_2_chunk_1.temperature, _hyper_1_2_chunk_1.humidity\n                                 Sort Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk_1.timec)), _hyper_1_2_chunk_1.location\n                                 ->  Result\n                                       Output: _hyper_1_2_chunk_1.location, time_bucket('@ 1 day'::interval, _hyper_1_2_chunk_1.timec), _hyper_1_2_chunk_1.temperature, _hyper_1_2_chunk_1.humidity\n                                       ->  Index Scan using _hyper_1_2_chunk_conditions_timec_idx on _timescaledb_internal._hyper_1_2_chunk _hyper_1_2_chunk_1\n                                             Output: _hyper_1_2_chunk_1.location, _hyper_1_2_chunk_1.timec, _hyper_1_2_chunk_1.temperature, _hyper_1_2_chunk_1.humidity\n                                             Index Cond: ((_hyper_1_2_chunk_1.timec >= 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone) AND (_hyper_1_2_chunk_1.timec > 'Mon Oct 01 00:00:00 2018 PDT'::timestamp with time zone))\n                                             Filter: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk_1.timec) > 'Mon Oct 01 00:00:00 2018 PDT'::timestamp with time zone)\n\nselect l.locid, mat_m1.* from mat_m1 , location_tab l where timec > '2018-10-01' and l.locname = mat_m1.location order by timec desc;\n locid | location |            timec             | minl | sumt | sumh \n-------+----------+------------------------------+------+------+------\n     2 | NYC      | Fri Nov 02 17:00:00 2018 PDT | NYC  |      |     \n     2 | NYC      | Thu Nov 01 17:00:00 2018 PDT | NYC  |   30 |   25\n     2 | NYC      | Wed Oct 31 17:00:00 2018 PDT | NYC  |  325 |  200\n\n\\set ECHO none\n---- Run the same queries with hash agg enabled now\nset enable_hashagg = true;\n\\set ECHO none\n--- Run the queries directly on the table now\nset enable_hashagg = true;\n\\set ECHO none\n-- diff results view select and table select\n:DIFF_CMD\n:DIFF_CMD2\n--check if the guc works , reordering will not work\nset timescaledb.enable_cagg_reorder_groupby = false;\nset enable_hashagg = false;\n:EXPLAIN\nselect * from mat_m1 order by timec desc, location;\n--- QUERY PLAN ---\n Sort\n   Output: _materialized_hypertable_2.location, _materialized_hypertable_2.timec, _materialized_hypertable_2.minl, _materialized_hypertable_2.sumt, _materialized_hypertable_2.sumh\n   Sort Key: _materialized_hypertable_2.timec DESC, _materialized_hypertable_2.location\n   ->  Append\n         ->  Append\n               ->  Seq Scan on _timescaledb_internal._hyper_2_3_chunk\n                     Output: _hyper_2_3_chunk.location, _hyper_2_3_chunk.timec, _hyper_2_3_chunk.minl, _hyper_2_3_chunk.sumt, _hyper_2_3_chunk.sumh\n               ->  Index Scan using _hyper_2_4_chunk__materialized_hypertable_2_timec_idx on _timescaledb_internal._hyper_2_4_chunk\n                     Output: _hyper_2_4_chunk.location, _hyper_2_4_chunk.timec, _hyper_2_4_chunk.minl, _hyper_2_4_chunk.sumt, _hyper_2_4_chunk.sumh\n                     Index Cond: (_hyper_2_4_chunk.timec < 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone)\n         ->  GroupAggregate\n               Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), min(_hyper_1_2_chunk.location), sum(_hyper_1_2_chunk.temperature), sum(_hyper_1_2_chunk.humidity)\n               Group Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.location\n               ->  Sort\n                     Output: _hyper_1_2_chunk.location, (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.temperature, _hyper_1_2_chunk.humidity\n                     Sort Key: (time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec)), _hyper_1_2_chunk.location\n                     ->  Result\n                           Output: _hyper_1_2_chunk.location, time_bucket('@ 1 day'::interval, _hyper_1_2_chunk.timec), _hyper_1_2_chunk.temperature, _hyper_1_2_chunk.humidity\n                           ->  Index Scan using _hyper_1_2_chunk_conditions_timec_idx on _timescaledb_internal._hyper_1_2_chunk\n                                 Output: _hyper_1_2_chunk.location, _hyper_1_2_chunk.timec, _hyper_1_2_chunk.temperature, _hyper_1_2_chunk.humidity\n                                 Index Cond: (_hyper_1_2_chunk.timec >= 'Sat Nov 03 17:00:00 2018 PDT'::timestamp with time zone)\n\n-----------------------------------------------------------------------\n-- Test the cagg_watermark function. The watermark gives the point\n-- where to UNION raw and materialized data in real-time\n-- aggregation. Specifically, test that the watermark caching works as\n-- expected.\n-----------------------------------------------------------------------\n-- Insert some more data so that there is something to UNION in\n-- real-time aggregation.\ninsert into conditions values ( '2018-12-02 20:10:00-08', 'SFO', 55, 45);\ninsert into conditions values ( '2018-12-02 21:20:00-08', 'SFO', 65, 45);\ninsert into conditions values ( '2018-12-02 20:30:00-08', 'NYC', 65, 45);\ninsert into conditions values ( '2018-12-02 21:50:00-08', 'NYC', 45, 30);\n-- Test join of two caggs. Joining two caggs will force the cache to\n-- reset every time the watermark function is invoked on a different\n-- cagg in the same query.\nSELECT mat_hypertable_id AS mat_id,\n\t   raw_hypertable_id AS raw_id,\n\t   schema_name AS mat_schema,\n\t   table_name AS mat_name,\n\t   format('%I.%I', schema_name, table_name) AS mat_table\nFROM _timescaledb_catalog.continuous_agg ca, _timescaledb_catalog.hypertable h\nWHERE user_view_name='mat_m1'\nAND h.id = ca.mat_hypertable_id \\gset\nBEGIN;\n-- Query without join\nSELECT m1.location, m1.timec, sumt, sumh\nFROM mat_m1 m1\nORDER BY m1.location COLLATE \"C\", m1.timec DESC\nLIMIT 10;\n location |            timec             | sumt | sumh \n----------+------------------------------+------+------\n NYC      | Sun Dec 02 16:00:00 2018 PST |  110 |   75\n NYC      | Fri Nov 02 17:00:00 2018 PDT |      |     \n NYC      | Thu Nov 01 17:00:00 2018 PDT |   30 |   25\n NYC      | Wed Oct 31 17:00:00 2018 PDT |  325 |  200\n NYC      | Mon Jan 01 16:00:00 2018 PST |   65 |   45\n SFO      | Sun Dec 02 16:00:00 2018 PST |  120 |   90\n SFO      | Mon Jan 01 16:00:00 2018 PST |   65 |   45\n SFO      | Sun Dec 31 16:00:00 2017 PST |   55 |   45\n por      | Mon Jan 01 16:00:00 2018 PST |  100 |  100\n\n-- Query that joins two caggs. This should force the watermark cache\n-- to reset when the materialized hypertable ID changes. A hash join\n-- could potentially read all values from mat_m1 then all values from\n-- mat_m2. This would be the optimal situation for cagg_watermark\n-- caching. We want to avoid it in tests to see that caching doesn't\n-- do anything wrong in worse situations (e.g., a nested loop join).\nSET enable_hashjoin=false;\nSELECT m1.location, m1.timec, sumt, sumh, firsth, lasth, maxtemp, mintemp\nFROM mat_m1 m1 RIGHT JOIN mat_m2 m2\nON (m1.location = m2.location\nAND m1.timec = m2.timec)\nORDER BY m1.location COLLATE \"C\", m1.timec DESC\nLIMIT 10;\n location |            timec             | sumt | sumh | firsth | lasth | maxtemp | mintemp \n----------+------------------------------+------+------+--------+-------+---------+---------\n NYC      | Sun Dec 02 16:00:00 2018 PST |  110 |   75 |     45 |    30 |      65 |      45\n NYC      | Fri Nov 02 17:00:00 2018 PDT |      |      |        |       |         |        \n NYC      | Thu Nov 01 17:00:00 2018 PDT |   30 |   25 |     10 |       |      20 |      10\n NYC      | Wed Oct 31 17:00:00 2018 PDT |  325 |  200 |     30 |    50 |      85 |      45\n NYC      | Mon Jan 01 16:00:00 2018 PST |   65 |   45 |     45 |    45 |      65 |      65\n SFO      | Sun Dec 02 16:00:00 2018 PST |  120 |   90 |     45 |    45 |      65 |      55\n SFO      | Mon Jan 01 16:00:00 2018 PST |   65 |   45 |     45 |    45 |      65 |      65\n SFO      | Sun Dec 31 16:00:00 2017 PST |   55 |   45 |     45 |    45 |      55 |      55\n por      | Mon Jan 01 16:00:00 2018 PST |  100 |  100 |    100 |   100 |     100 |     100\n\n-- Show the current watermark\nSELECT _timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(:mat_id));\n         to_timestamp         \n------------------------------\n Sat Nov 03 17:00:00 2018 PDT\n\n-- The watermark should, in this case, be the same as the invalidation\n-- threshold\nSELECT _timescaledb_functions.to_timestamp(watermark)\nFROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nWHERE hypertable_id = :raw_id;\n         to_timestamp         \n------------------------------\n Sat Nov 03 17:00:00 2018 PDT\n\n-- The watermark is the end of materialization (end of last bucket)\n-- while the MAX is the start of the last bucket\nSELECT max(timec) FROM :mat_table;\n             max              \n------------------------------\n Fri Nov 02 17:00:00 2018 PDT\n\n-- Drop the most recent chunk\nSELECT chunk_name, range_start, range_end\nFROM timescaledb_information.chunks\nWHERE hypertable_name = :'mat_name';\n    chunk_name    |         range_start          |          range_end           \n------------------+------------------------------+------------------------------\n _hyper_2_3_chunk | Wed Nov 29 16:00:00 2017 PST | Wed Feb 07 16:00:00 2018 PST\n _hyper_2_4_chunk | Wed Sep 05 17:00:00 2018 PDT | Wed Nov 14 16:00:00 2018 PST\n\nSELECT drop_chunks('mat_m1', newer_than=>'2018-01-01'::timestamptz);\n              drop_chunks               \n----------------------------------------\n _timescaledb_internal._hyper_2_4_chunk\n\nSELECT chunk_name, range_start, range_end\nFROM timescaledb_information.chunks\nWHERE hypertable_name = :'mat_name';\n    chunk_name    |         range_start          |          range_end           \n------------------+------------------------------+------------------------------\n _hyper_2_3_chunk | Wed Nov 29 16:00:00 2017 PST | Wed Feb 07 16:00:00 2018 PST\n\n-- The watermark should be updated to reflect the dropped data (i.e.,\n-- the cache should be reset)\nSELECT _timescaledb_functions.to_timestamp(_timescaledb_functions.cagg_watermark(:mat_id));\n         to_timestamp         \n------------------------------\n Tue Jan 02 16:00:00 2018 PST\n\n-- Since we removed the last chunk, the invalidation threshold doesn't\n-- move back, while the watermark does.\nSELECT _timescaledb_functions.to_timestamp(watermark)\nFROM _timescaledb_catalog.continuous_aggs_invalidation_threshold\nWHERE hypertable_id = :raw_id;\n         to_timestamp         \n------------------------------\n Sat Nov 03 17:00:00 2018 PDT\n\n-- Compare the new watermark to the MAX time in the table\nSELECT max(timec) FROM :mat_table;\n             max              \n------------------------------\n Mon Jan 01 16:00:00 2018 PST\n\n-- Try a subtransaction\nSAVEPOINT clear_cagg;\nSELECT m1.location, m1.timec, sumt, sumh, firsth, lasth, maxtemp, mintemp\nFROM mat_m1 m1 RIGHT JOIN mat_m2 m2\nON (m1.location = m2.location\nAND m1.timec = m2.timec)\nORDER BY m1.location COLLATE \"C\", m1.timec DESC\nLIMIT 10;\n location |            timec             | sumt | sumh | firsth | lasth | maxtemp | mintemp \n----------+------------------------------+------+------+--------+-------+---------+---------\n NYC      | Sun Dec 02 16:00:00 2018 PST |  110 |   75 |     45 |    30 |      65 |      45\n NYC      | Fri Nov 02 17:00:00 2018 PDT |      |      |        |       |         |        \n NYC      | Thu Nov 01 17:00:00 2018 PDT |   30 |   25 |     10 |       |      20 |      10\n NYC      | Wed Oct 31 17:00:00 2018 PDT |  325 |  200 |     30 |    50 |      85 |      45\n NYC      | Mon Jan 01 16:00:00 2018 PST |   65 |   45 |     45 |    45 |      65 |      65\n SFO      | Sun Dec 02 16:00:00 2018 PST |  120 |   90 |     45 |    45 |      65 |      55\n SFO      | Mon Jan 01 16:00:00 2018 PST |   65 |   45 |     45 |    45 |      65 |      65\n SFO      | Sun Dec 31 16:00:00 2017 PST |   55 |   45 |     45 |    45 |      55 |      55\n por      | Mon Jan 01 16:00:00 2018 PST |  100 |  100 |    100 |   100 |     100 |     100\n\nALTER MATERIALIZED VIEW mat_m1 SET (timescaledb.materialized_only=true);\nSELECT m1.location, m1.timec, sumt, sumh, firsth, lasth, maxtemp, mintemp\nFROM mat_m1 m1 RIGHT JOIN mat_m2 m2\nON (m1.location = m2.location\nAND m1.timec = m2.timec)\nORDER BY m1.location COLLATE \"C\" NULLS LAST, m1.timec DESC NULLS LAST, firsth NULLS LAST,\n         lasth NULLS LAST, mintemp NULLS LAST, maxtemp NULLS LAST\nLIMIT 10;\n location |            timec             | sumt | sumh | firsth | lasth | maxtemp | mintemp \n----------+------------------------------+------+------+--------+-------+---------+---------\n NYC      | Mon Jan 01 16:00:00 2018 PST |   65 |   45 |     45 |    45 |      65 |      65\n SFO      | Mon Jan 01 16:00:00 2018 PST |   65 |   45 |     45 |    45 |      65 |      65\n SFO      | Sun Dec 31 16:00:00 2017 PST |   55 |   45 |     45 |    45 |      55 |      55\n por      | Mon Jan 01 16:00:00 2018 PST |  100 |  100 |    100 |   100 |     100 |     100\n          |                              |      |      |     10 |       |      20 |      10\n          |                              |      |      |     30 |    50 |      85 |      45\n          |                              |      |      |     45 |    30 |      65 |      45\n          |                              |      |      |     45 |    45 |      65 |      55\n          |                              |      |      |        |       |         |        \n\nROLLBACK;\n-----\n-- Tests with time_bucket and offset/origin\n-----\nCREATE TABLE temperature (\n  time timestamptz NOT NULL,\n  value float\n);\nSELECT create_hypertable('temperature', 'time');\n    create_hypertable     \n--------------------------\n (4,public,temperature,t)\n\nINSERT INTO temperature VALUES ('2000-01-01 01:00:00'::timestamptz, 5);\nCREATE TABLE temperature_wo_tz (\n  time timestamp NOT NULL,\n  value float\n);\nSELECT create_hypertable('temperature_wo_tz', 'time');\npsql:include/cagg_query_common.sql:316: WARNING:  column type \"timestamp without time zone\" used for \"time\" does not follow best practices\n       create_hypertable        \n--------------------------------\n (5,public,temperature_wo_tz,t)\n\nINSERT INTO temperature_wo_tz VALUES ('2000-01-01 01:00:00'::timestamp, 5);\nCREATE TABLE temperature_date (\n  time date NOT NULL,\n  value float\n);\nSELECT create_hypertable('temperature_date', 'time');\n       create_hypertable       \n-------------------------------\n (6,public,temperature_date,t)\n\nINSERT INTO temperature_date VALUES ('2000-01-01 01:00:00'::timestamp, 5);\n-- Integer based tables\nCREATE TABLE table_smallint (\n  time smallint,\n  data smallint\n);\nCREATE TABLE table_int (\n  time int,\n  data int\n);\nCREATE TABLE table_bigint (\n  time bigint,\n  data bigint\n);\nSELECT create_hypertable('table_smallint', 'time', chunk_time_interval => 10);\n      create_hypertable      \n-----------------------------\n (7,public,table_smallint,t)\n\nSELECT create_hypertable('table_int', 'time', chunk_time_interval => 10);\n   create_hypertable    \n------------------------\n (8,public,table_int,t)\n\nSELECT create_hypertable('table_bigint', 'time', chunk_time_interval => 10);\n     create_hypertable     \n---------------------------\n (9,public,table_bigint,t)\n\nCREATE OR REPLACE FUNCTION integer_now_smallint() returns smallint LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), 0) FROM table_smallint $$;\nCREATE OR REPLACE FUNCTION integer_now_int() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), 0) FROM table_int $$;\nCREATE OR REPLACE FUNCTION integer_now_bigint() returns bigint LANGUAGE SQL STABLE as $$ SELECT coalesce(max(time), 0) FROM table_bigint $$;\nSELECT set_integer_now_func('table_smallint', 'integer_now_smallint');\n set_integer_now_func \n----------------------\n \n\nSELECT set_integer_now_func('table_int', 'integer_now_int');\n set_integer_now_func \n----------------------\n \n\nSELECT set_integer_now_func('table_bigint', 'integer_now_bigint');\n set_integer_now_func \n----------------------\n \n\nINSERT INTO table_smallint VALUES(1,2);\nINSERT INTO table_int VALUES(1,2);\nINSERT INTO table_bigint VALUES(1,2);\nCREATE VIEW caggs_info AS\nSELECT user_view_schema, user_view_name, bucket_func, bucket_width, bucket_origin, bucket_offset, bucket_timezone, bucket_fixed_width\nFROM _timescaledb_catalog.continuous_aggs_bucket_function NATURAL JOIN _timescaledb_catalog.continuous_agg;\n---\n-- Tests with CAgg creation\n---\nCREATE MATERIALIZED VIEW cagg_4_hours\n  WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS\n  SELECT time_bucket('4 hour', time), max(value)\n    FROM temperature\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:372: NOTICE:  refreshing continuous aggregate \"cagg_4_hours\"\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_4_hours';\n user_view_schema | user_view_name |                      bucket_func                      | bucket_width | bucket_origin | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+----------------+-------------------------------------------------------+--------------+---------------+---------------+-----------------+--------------------\n public           | cagg_4_hours   | public.time_bucket(interval,timestamp with time zone) | @ 4 hours    |               |               |                 | t\n\nDROP MATERIALIZED VIEW cagg_4_hours;\npsql:include/cagg_query_common.sql:374: NOTICE:  drop cascades to table _timescaledb_internal._hyper_10_14_chunk\nCREATE MATERIALIZED VIEW cagg_4_hours_offset\n  WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS\n  SELECT time_bucket('4 hour', time, '30m'::interval), max(value)\n    FROM temperature\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:380: NOTICE:  refreshing continuous aggregate \"cagg_4_hours_offset\"\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_4_hours_offset';\n user_view_schema |   user_view_name    |                          bucket_func                           | bucket_width | bucket_origin | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+---------------------+----------------------------------------------------------------+--------------+---------------+---------------+-----------------+--------------------\n public           | cagg_4_hours_offset | public.time_bucket(interval,timestamp with time zone,interval) | @ 4 hours    |               | @ 30 mins     |                 | t\n\nDROP MATERIALIZED VIEW cagg_4_hours_offset;\npsql:include/cagg_query_common.sql:382: NOTICE:  drop cascades to table _timescaledb_internal._hyper_11_15_chunk\nCREATE MATERIALIZED VIEW cagg_4_hours_offset2\n  WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS\n  SELECT time_bucket('4 hour', time, \"offset\"=>'30m'::interval), max(value)\n    FROM temperature\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:388: NOTICE:  refreshing continuous aggregate \"cagg_4_hours_offset2\"\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_4_hours_offset2';\n user_view_schema |    user_view_name    |                          bucket_func                           | bucket_width | bucket_origin | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+----------------------+----------------------------------------------------------------+--------------+---------------+---------------+-----------------+--------------------\n public           | cagg_4_hours_offset2 | public.time_bucket(interval,timestamp with time zone,interval) | @ 4 hours    |               | @ 30 mins     |                 | t\n\nDROP MATERIALIZED VIEW cagg_4_hours_offset2;\npsql:include/cagg_query_common.sql:390: NOTICE:  drop cascades to table _timescaledb_internal._hyper_12_16_chunk\n-- Variable buckets (timezone is provided) with offset\nCREATE MATERIALIZED VIEW cagg_4_hours_offset_ts\n  WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS\n  SELECT time_bucket('4 hour', time, \"offset\"=>'30m'::interval, timezone=>'UTC'), max(value)\n    FROM temperature\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:397: NOTICE:  refreshing continuous aggregate \"cagg_4_hours_offset_ts\"\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_4_hours_offset_ts';\n user_view_schema |     user_view_name     |                                               bucket_func                                               | bucket_width | bucket_origin | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+------------------------+---------------------------------------------------------------------------------------------------------+--------------+---------------+---------------+-----------------+--------------------\n public           | cagg_4_hours_offset_ts | public.time_bucket(interval,timestamp with time zone,pg_catalog.text,timestamp with time zone,interval) | @ 4 hours    |               | @ 30 mins     | UTC             | f\n\nDROP MATERIALIZED VIEW cagg_4_hours_offset_ts;\npsql:include/cagg_query_common.sql:399: NOTICE:  drop cascades to table _timescaledb_internal._hyper_13_17_chunk\nCREATE MATERIALIZED VIEW cagg_4_hours_origin\n  WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS\n  SELECT time_bucket('4 hour', time, '2000-01-01 01:00:00 PST'::timestamptz), max(value)\n    FROM temperature\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:405: NOTICE:  refreshing continuous aggregate \"cagg_4_hours_origin\"\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_4_hours_origin';\n user_view_schema |   user_view_name    |                                  bucket_func                                   | bucket_width |        bucket_origin         | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+---------------------+--------------------------------------------------------------------------------+--------------+------------------------------+---------------+-----------------+--------------------\n public           | cagg_4_hours_origin | public.time_bucket(interval,timestamp with time zone,timestamp with time zone) | @ 4 hours    | Sat Jan 01 01:00:00 2000 PST |               |                 | t\n\nDROP MATERIALIZED VIEW cagg_4_hours_origin;\npsql:include/cagg_query_common.sql:407: NOTICE:  drop cascades to table _timescaledb_internal._hyper_14_18_chunk\n-- Using named parameter\nCREATE MATERIALIZED VIEW cagg_4_hours_origin2\n  WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS\n  SELECT time_bucket('4 hour', time, origin=>'2000-01-01 01:00:00 PST'::timestamptz), max(value)\n    FROM temperature\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:414: NOTICE:  refreshing continuous aggregate \"cagg_4_hours_origin2\"\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_4_hours_origin2';\n user_view_schema |    user_view_name    |                                  bucket_func                                   | bucket_width |        bucket_origin         | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+----------------------+--------------------------------------------------------------------------------+--------------+------------------------------+---------------+-----------------+--------------------\n public           | cagg_4_hours_origin2 | public.time_bucket(interval,timestamp with time zone,timestamp with time zone) | @ 4 hours    | Sat Jan 01 01:00:00 2000 PST |               |                 | t\n\nDROP MATERIALIZED VIEW cagg_4_hours_origin2;\npsql:include/cagg_query_common.sql:416: NOTICE:  drop cascades to table _timescaledb_internal._hyper_15_19_chunk\n-- Variable buckets (timezone is provided) with origin\nCREATE MATERIALIZED VIEW cagg_4_hours_origin_ts\n  WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS\n  SELECT time_bucket('4 hour', time, origin=>'2000-01-01 01:00:00 PST'::timestamptz, timezone=>'UTC'), max(value)\n    FROM temperature\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:423: NOTICE:  refreshing continuous aggregate \"cagg_4_hours_origin_ts\"\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_4_hours_origin_ts';\n user_view_schema |     user_view_name     |                                               bucket_func                                               | bucket_width |        bucket_origin         | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+------------------------+---------------------------------------------------------------------------------------------------------+--------------+------------------------------+---------------+-----------------+--------------------\n public           | cagg_4_hours_origin_ts | public.time_bucket(interval,timestamp with time zone,pg_catalog.text,timestamp with time zone,interval) | @ 4 hours    | Sat Jan 01 01:00:00 2000 PST |               | UTC             | f\n\nDROP MATERIALIZED VIEW cagg_4_hours_origin_ts;\npsql:include/cagg_query_common.sql:425: NOTICE:  drop cascades to table _timescaledb_internal._hyper_16_20_chunk\n-- Without named parameter\nCREATE MATERIALIZED VIEW cagg_4_hours_origin_ts2\n  WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS\n  SELECT time_bucket('4 hour', time, 'UTC', '2000-01-01 01:00:00 PST'::timestamptz), max(value)\n    FROM temperature\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:432: NOTICE:  refreshing continuous aggregate \"cagg_4_hours_origin_ts2\"\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_4_hours_origin_ts2';\n user_view_schema |     user_view_name      |                                               bucket_func                                               | bucket_width |        bucket_origin         | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+-------------------------+---------------------------------------------------------------------------------------------------------+--------------+------------------------------+---------------+-----------------+--------------------\n public           | cagg_4_hours_origin_ts2 | public.time_bucket(interval,timestamp with time zone,pg_catalog.text,timestamp with time zone,interval) | @ 4 hours    | Sat Jan 01 01:00:00 2000 PST |               | UTC             | f\n\nDROP MATERIALIZED VIEW cagg_4_hours_origin_ts2;\npsql:include/cagg_query_common.sql:434: NOTICE:  drop cascades to table _timescaledb_internal._hyper_17_21_chunk\n-- Timestamp based CAggs\nCREATE MATERIALIZED VIEW cagg_4_hours_wo_tz\n  WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS\n  SELECT time_bucket('4 hour', time), max(value)\n    FROM temperature_wo_tz\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:441: NOTICE:  refreshing continuous aggregate \"cagg_4_hours_wo_tz\"\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_4_hours_wo_tz';\n user_view_schema |   user_view_name   |                       bucket_func                        | bucket_width | bucket_origin | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+--------------------+----------------------------------------------------------+--------------+---------------+---------------+-----------------+--------------------\n public           | cagg_4_hours_wo_tz | public.time_bucket(interval,timestamp without time zone) | @ 4 hours    |               |               |                 | t\n\nCREATE MATERIALIZED VIEW cagg_4_hours_origin_ts_wo_tz\n  WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS\n  SELECT time_bucket('4 hour', time, '2000-01-01 01:00:00'::timestamp), max(value)\n    FROM temperature_wo_tz\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:448: NOTICE:  refreshing continuous aggregate \"cagg_4_hours_origin_ts_wo_tz\"\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_4_hours_origin_ts_wo_tz';\n user_view_schema |        user_view_name        |                                     bucket_func                                      | bucket_width |        bucket_origin         | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+------------------------------+--------------------------------------------------------------------------------------+--------------+------------------------------+---------------+-----------------+--------------------\n public           | cagg_4_hours_origin_ts_wo_tz | public.time_bucket(interval,timestamp without time zone,timestamp without time zone) | @ 4 hours    | Fri Dec 31 17:00:00 1999 PST |               |                 | t\n\nDROP MATERIALIZED VIEW cagg_4_hours_origin_ts_wo_tz;\npsql:include/cagg_query_common.sql:450: NOTICE:  drop cascades to table _timescaledb_internal._hyper_19_23_chunk\n-- Variable buckets (timezone is provided) with origin\nCREATE MATERIALIZED VIEW cagg_4_hours_origin_ts_wo_tz2\n  WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS\n  SELECT time_bucket('4 hour', time, origin=>'2000-01-01 01:00:00'::timestamp), max(value)\n    FROM temperature_wo_tz\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:457: NOTICE:  refreshing continuous aggregate \"cagg_4_hours_origin_ts_wo_tz2\"\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_4_hours_origin_ts_wo_tz2';\n user_view_schema |        user_view_name         |                                     bucket_func                                      | bucket_width |        bucket_origin         | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+-------------------------------+--------------------------------------------------------------------------------------+--------------+------------------------------+---------------+-----------------+--------------------\n public           | cagg_4_hours_origin_ts_wo_tz2 | public.time_bucket(interval,timestamp without time zone,timestamp without time zone) | @ 4 hours    | Fri Dec 31 17:00:00 1999 PST |               |                 | t\n\nDROP MATERIALIZED VIEW cagg_4_hours_origin_ts_wo_tz2;\npsql:include/cagg_query_common.sql:459: NOTICE:  drop cascades to table _timescaledb_internal._hyper_20_24_chunk\nCREATE MATERIALIZED VIEW cagg_4_hours_offset_wo_tz\n  WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS\n  SELECT time_bucket('4 hour', time, \"offset\"=>'30m'::interval), max(value)\n    FROM temperature_wo_tz\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:465: NOTICE:  refreshing continuous aggregate \"cagg_4_hours_offset_wo_tz\"\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_4_hours_offset_wo_tz';\n user_view_schema |      user_view_name       |                            bucket_func                            | bucket_width | bucket_origin | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+---------------------------+-------------------------------------------------------------------+--------------+---------------+---------------+-----------------+--------------------\n public           | cagg_4_hours_offset_wo_tz | public.time_bucket(interval,timestamp without time zone,interval) | @ 4 hours    |               | @ 30 mins     |                 | t\n\nDROP MATERIALIZED VIEW cagg_4_hours_offset_wo_tz;\npsql:include/cagg_query_common.sql:467: NOTICE:  drop cascades to table _timescaledb_internal._hyper_21_25_chunk\nDROP MATERIALIZED VIEW cagg_4_hours_wo_tz;\npsql:include/cagg_query_common.sql:468: NOTICE:  drop cascades to table _timescaledb_internal._hyper_18_22_chunk\n-- Date based CAggs\nCREATE MATERIALIZED VIEW cagg_4_hours_date\n  WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS\n  SELECT time_bucket('4 days', time), max(value)\n    FROM temperature_date\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:475: NOTICE:  refreshing continuous aggregate \"cagg_4_hours_date\"\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_4_hours_date';\n user_view_schema |  user_view_name   |                 bucket_func                  | bucket_width | bucket_origin | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+-------------------+----------------------------------------------+--------------+---------------+---------------+-----------------+--------------------\n public           | cagg_4_hours_date | public.time_bucket(interval,pg_catalog.date) | @ 4 days     |               |               |                 | t\n\nDROP MATERIALIZED VIEW cagg_4_hours_date;\npsql:include/cagg_query_common.sql:477: NOTICE:  drop cascades to table _timescaledb_internal._hyper_22_26_chunk\nCREATE MATERIALIZED VIEW cagg_4_hours_date_origin\n  WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS\n  SELECT time_bucket('4 days', time, '2000-01-01'::date), max(value)\n    FROM temperature_date\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:483: NOTICE:  refreshing continuous aggregate \"cagg_4_hours_date_origin\"\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_4_hours_date_origin';\n user_view_schema |      user_view_name      |                         bucket_func                          | bucket_width |        bucket_origin         | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+--------------------------+--------------------------------------------------------------+--------------+------------------------------+---------------+-----------------+--------------------\n public           | cagg_4_hours_date_origin | public.time_bucket(interval,pg_catalog.date,pg_catalog.date) | @ 4 days     | Sat Jan 01 00:00:00 2000 PST |               |                 | t\n\nDROP MATERIALIZED VIEW cagg_4_hours_date_origin;\npsql:include/cagg_query_common.sql:485: NOTICE:  drop cascades to table _timescaledb_internal._hyper_23_27_chunk\nCREATE MATERIALIZED VIEW cagg_4_hours_date_origin2\n  WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS\n  SELECT time_bucket('4 days', time, origin=>'2000-01-01'::date), max(value)\n    FROM temperature_date\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:491: NOTICE:  refreshing continuous aggregate \"cagg_4_hours_date_origin2\"\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_4_hours_date_origin2';\n user_view_schema |      user_view_name       |                         bucket_func                          | bucket_width |        bucket_origin         | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+---------------------------+--------------------------------------------------------------+--------------+------------------------------+---------------+-----------------+--------------------\n public           | cagg_4_hours_date_origin2 | public.time_bucket(interval,pg_catalog.date,pg_catalog.date) | @ 4 days     | Sat Jan 01 00:00:00 2000 PST |               |                 | t\n\nDROP MATERIALIZED VIEW cagg_4_hours_date_origin2;\npsql:include/cagg_query_common.sql:493: NOTICE:  drop cascades to table _timescaledb_internal._hyper_24_28_chunk\nCREATE MATERIALIZED VIEW cagg_4_hours_date_offset\n  WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS\n  SELECT time_bucket('4 days', time, \"offset\"=>'30m'::interval), max(value)\n    FROM temperature_date\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:499: NOTICE:  refreshing continuous aggregate \"cagg_4_hours_date_offset\"\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_4_hours_date_offset';\n user_view_schema |      user_view_name      |                      bucket_func                      | bucket_width | bucket_origin | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+--------------------------+-------------------------------------------------------+--------------+---------------+---------------+-----------------+--------------------\n public           | cagg_4_hours_date_offset | public.time_bucket(interval,pg_catalog.date,interval) | @ 4 days     |               | @ 30 mins     |                 | t\n\nDROP MATERIALIZED VIEW cagg_4_hours_date_offset;\npsql:include/cagg_query_common.sql:501: NOTICE:  drop cascades to table _timescaledb_internal._hyper_25_29_chunk\n-- Integer based CAggs\nCREATE MATERIALIZED VIEW cagg_smallint\n    WITH (timescaledb.continuous, timescaledb.materialized_only=true)\n    AS SELECT time_bucket('2', time), SUM(data) as value\n        FROM table_smallint\n        GROUP BY 1;\npsql:include/cagg_query_common.sql:508: NOTICE:  refreshing continuous aggregate \"cagg_smallint\"\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_smallint';\n user_view_schema | user_view_name |              bucket_func              | bucket_width | bucket_origin | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+----------------+---------------------------------------+--------------+---------------+---------------+-----------------+--------------------\n public           | cagg_smallint  | public.time_bucket(smallint,smallint) | 2            |               |               |                 | t\n\nDROP MATERIALIZED VIEW cagg_smallint;\npsql:include/cagg_query_common.sql:510: NOTICE:  drop cascades to table _timescaledb_internal._hyper_26_30_chunk\nCREATE MATERIALIZED VIEW cagg_smallint_offset\n    WITH (timescaledb.continuous, timescaledb.materialized_only=true)\n    AS SELECT time_bucket('2', time, \"offset\"=>1::smallint), SUM(data) as value\n        FROM table_smallint\n        GROUP BY 1;\npsql:include/cagg_query_common.sql:516: NOTICE:  refreshing continuous aggregate \"cagg_smallint_offset\"\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_smallint_offset';\n user_view_schema |    user_view_name    |                  bucket_func                   | bucket_width | bucket_origin | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+----------------------+------------------------------------------------+--------------+---------------+---------------+-----------------+--------------------\n public           | cagg_smallint_offset | public.time_bucket(smallint,smallint,smallint) | 2            |               | 1             |                 | t\n\nDROP MATERIALIZED VIEW cagg_smallint_offset;\npsql:include/cagg_query_common.sql:518: NOTICE:  drop cascades to table _timescaledb_internal._hyper_27_31_chunk\nCREATE MATERIALIZED VIEW cagg_int\n    WITH (timescaledb.continuous, timescaledb.materialized_only=true)\n    AS SELECT time_bucket('2', time), SUM(data) as value\n        FROM table_int\n        GROUP BY 1;\npsql:include/cagg_query_common.sql:524: NOTICE:  refreshing continuous aggregate \"cagg_int\"\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_int';\n user_view_schema | user_view_name |             bucket_func             | bucket_width | bucket_origin | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+----------------+-------------------------------------+--------------+---------------+---------------+-----------------+--------------------\n public           | cagg_int       | public.time_bucket(integer,integer) | 2            |               |               |                 | t\n\nDROP MATERIALIZED VIEW cagg_int;\npsql:include/cagg_query_common.sql:526: NOTICE:  drop cascades to table _timescaledb_internal._hyper_28_32_chunk\nCREATE MATERIALIZED VIEW cagg_int_offset\n    WITH (timescaledb.continuous, timescaledb.materialized_only=true)\n    AS SELECT time_bucket('2', time, \"offset\"=>1::int), SUM(data) as value\n        FROM table_int\n        GROUP BY 1;\npsql:include/cagg_query_common.sql:532: NOTICE:  refreshing continuous aggregate \"cagg_int_offset\"\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_int_offset';\n user_view_schema | user_view_name  |                 bucket_func                 | bucket_width | bucket_origin | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+-----------------+---------------------------------------------+--------------+---------------+---------------+-----------------+--------------------\n public           | cagg_int_offset | public.time_bucket(integer,integer,integer) | 2            |               | 1             |                 | t\n\nDROP MATERIALIZED VIEW cagg_int_offset;\npsql:include/cagg_query_common.sql:534: NOTICE:  drop cascades to table _timescaledb_internal._hyper_29_33_chunk\nCREATE MATERIALIZED VIEW cagg_bigint\n    WITH (timescaledb.continuous, timescaledb.materialized_only=true)\n    AS SELECT time_bucket('2', time), SUM(data) as value\n        FROM table_bigint\n        GROUP BY 1 WITH NO DATA;\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_bigint';\n user_view_schema | user_view_name |            bucket_func            | bucket_width | bucket_origin | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+----------------+-----------------------------------+--------------+---------------+---------------+-----------------+--------------------\n public           | cagg_bigint    | public.time_bucket(bigint,bigint) | 2            |               |               |                 | t\n\nDROP MATERIALIZED VIEW cagg_bigint;\nCREATE MATERIALIZED VIEW cagg_bigint_offset\n    WITH (timescaledb.continuous, timescaledb.materialized_only=true)\n    AS SELECT time_bucket('2', time, \"offset\"=>1::bigint), SUM(data) as value\n        FROM table_bigint\n        GROUP BY 1 WITH NO DATA;\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_bigint_offset';\n user_view_schema |   user_view_name   |               bucket_func                | bucket_width | bucket_origin | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+--------------------+------------------------------------------+--------------+---------------+---------------+-----------------+--------------------\n public           | cagg_bigint_offset | public.time_bucket(bigint,bigint,bigint) | 2            |               | 1             |                 | t\n\nDROP MATERIALIZED VIEW cagg_bigint_offset;\n-- Without named parameter\nCREATE MATERIALIZED VIEW cagg_bigint_offset2\n    WITH (timescaledb.continuous, timescaledb.materialized_only=true)\n    AS SELECT time_bucket('2', time, 1::bigint), SUM(data) as value\n        FROM table_bigint\n        GROUP BY 1 WITH NO DATA;\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_bigint_offset2';\n user_view_schema |   user_view_name    |               bucket_func                | bucket_width | bucket_origin | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+---------------------+------------------------------------------+--------------+---------------+---------------+-----------------+--------------------\n public           | cagg_bigint_offset2 | public.time_bucket(bigint,bigint,bigint) | 2            |               | 1             |                 | t\n\n-- mess with the bucket_func signature to make sure it will raise an exception\nSET ROLE :ROLE_SUPERUSER;\n\\set ON_ERROR_STOP 0\nBEGIN;\nUPDATE _timescaledb_catalog.continuous_aggs_bucket_function SET bucket_func = 'func_does_not_exist()';\n-- should error because function does not exist\nCALL refresh_continuous_aggregate('cagg_bigint_offset2', NULL, NULL);\npsql:include/cagg_query_common.sql:566: ERROR:  function \"func_does_not_exist()\" does not exist\nROLLBACK;\n\\set ON_ERROR_STOP 1\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nDROP MATERIALIZED VIEW cagg_bigint_offset2;\n-- Test invalid bucket definitions\n\\set ON_ERROR_STOP 0\n-- Offset and origin at the same time is not allowed (function does not exists)\nCREATE MATERIALIZED VIEW cagg_4_hours_offset_and_origin\n  WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS\n  SELECT time_bucket('4 hour', time, \"offset\"=>'30m'::interval, origin=>'2000-01-01 01:00:00 PST'::timestamptz), max(value)\n    FROM temperature\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:580: ERROR:  function time_bucket(unknown, timestamp with time zone, offset => interval, origin => timestamp with time zone) does not exist at character 140\n-- Offset and origin at the same time is not allowed (function does exists but invalid parameter combination)\nCREATE MATERIALIZED VIEW cagg_4_hours_offset_and_origin\n  WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS\n  SELECT time_bucket('4 hour', time, \"offset\"=>'30m'::interval, origin=>'2000-01-01 01:00:00 PST'::timestamptz, timezone=>'UTC'), max(value)\n    FROM temperature\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:587: ERROR:  using offset and origin in a time_bucket function at the same time is not supported\n\\set ON_ERROR_STOP 1\n---\n-- Tests with CAgg processing\n---\n-- Check used timezone\nSHOW timezone;\n TimeZone \n----------\n PST8PDT\n\n-- Populate it\nINSERT INTO temperature\n  SELECT time, 5\n    FROM generate_series('2000-01-01 01:00:00 PST'::timestamptz,\n                         '2000-01-01 23:59:59 PST','1m') time;\nINSERT INTO temperature\n  SELECT time, 6\n    FROM generate_series('2020-01-01 00:00:00 PST'::timestamptz,\n                         '2020-01-01 23:59:59 PST','1m') time;\n-- Create CAggs\nCREATE MATERIALIZED VIEW cagg_4_hours\n  WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS\n  SELECT time_bucket('4 hour', time), max(value)\n    FROM temperature\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:613: NOTICE:  refreshing continuous aggregate \"cagg_4_hours\"\nCREATE MATERIALIZED VIEW cagg_4_hours_offset\n  WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS\n  SELECT time_bucket('4 hour', time, '30m'::interval), max(value)\n    FROM temperature\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:619: NOTICE:  refreshing continuous aggregate \"cagg_4_hours_offset\"\n-- Align origin with first value\nCREATE MATERIALIZED VIEW cagg_4_hours_origin\n  WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS\n  SELECT time_bucket('4 hour', time, '2000-01-01 01:00:00 PST'::timestamptz), max(value)\n    FROM temperature\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:626: NOTICE:  refreshing continuous aggregate \"cagg_4_hours_origin\"\n-- Query the CAggs and check that all buckets are materialized\nSELECT time_bucket('4 hour', time), max(value) FROM temperature GROUP BY 1 ORDER BY 1;\n         time_bucket          | max \n------------------------------+-----\n Sat Jan 01 00:00:00 2000 PST |   5\n Sat Jan 01 04:00:00 2000 PST |   5\n Sat Jan 01 08:00:00 2000 PST |   5\n Sat Jan 01 12:00:00 2000 PST |   5\n Sat Jan 01 16:00:00 2000 PST |   5\n Sat Jan 01 20:00:00 2000 PST |   5\n Wed Jan 01 00:00:00 2020 PST |   6\n Wed Jan 01 04:00:00 2020 PST |   6\n Wed Jan 01 08:00:00 2020 PST |   6\n Wed Jan 01 12:00:00 2020 PST |   6\n Wed Jan 01 16:00:00 2020 PST |   6\n Wed Jan 01 20:00:00 2020 PST |   6\n\nSELECT * FROM cagg_4_hours;\n         time_bucket          | max \n------------------------------+-----\n Sat Jan 01 00:00:00 2000 PST |   5\n Sat Jan 01 04:00:00 2000 PST |   5\n Sat Jan 01 08:00:00 2000 PST |   5\n Sat Jan 01 12:00:00 2000 PST |   5\n Sat Jan 01 16:00:00 2000 PST |   5\n Sat Jan 01 20:00:00 2000 PST |   5\n Wed Jan 01 00:00:00 2020 PST |   6\n Wed Jan 01 04:00:00 2020 PST |   6\n Wed Jan 01 08:00:00 2020 PST |   6\n Wed Jan 01 12:00:00 2020 PST |   6\n Wed Jan 01 16:00:00 2020 PST |   6\n Wed Jan 01 20:00:00 2020 PST |   6\n\nALTER MATERIALIZED VIEW cagg_4_hours SET (timescaledb.materialized_only=true);\nSELECT * FROM cagg_4_hours;\n         time_bucket          | max \n------------------------------+-----\n Sat Jan 01 00:00:00 2000 PST |   5\n Sat Jan 01 04:00:00 2000 PST |   5\n Sat Jan 01 08:00:00 2000 PST |   5\n Sat Jan 01 12:00:00 2000 PST |   5\n Sat Jan 01 16:00:00 2000 PST |   5\n Sat Jan 01 20:00:00 2000 PST |   5\n Wed Jan 01 00:00:00 2020 PST |   6\n Wed Jan 01 04:00:00 2020 PST |   6\n Wed Jan 01 08:00:00 2020 PST |   6\n Wed Jan 01 12:00:00 2020 PST |   6\n Wed Jan 01 16:00:00 2020 PST |   6\n Wed Jan 01 20:00:00 2020 PST |   6\n\nSELECT time_bucket('4 hour', time, '30m'::interval), max(value) FROM temperature GROUP BY 1 ORDER BY 1;\n         time_bucket          | max \n------------------------------+-----\n Sat Jan 01 00:30:00 2000 PST |   5\n Sat Jan 01 04:30:00 2000 PST |   5\n Sat Jan 01 08:30:00 2000 PST |   5\n Sat Jan 01 12:30:00 2000 PST |   5\n Sat Jan 01 16:30:00 2000 PST |   5\n Sat Jan 01 20:30:00 2000 PST |   5\n Tue Dec 31 20:30:00 2019 PST |   6\n Wed Jan 01 00:30:00 2020 PST |   6\n Wed Jan 01 04:30:00 2020 PST |   6\n Wed Jan 01 08:30:00 2020 PST |   6\n Wed Jan 01 12:30:00 2020 PST |   6\n Wed Jan 01 16:30:00 2020 PST |   6\n Wed Jan 01 20:30:00 2020 PST |   6\n\nSELECT * FROM cagg_4_hours_offset;\n         time_bucket          | max \n------------------------------+-----\n Sat Jan 01 00:30:00 2000 PST |   5\n Sat Jan 01 04:30:00 2000 PST |   5\n Sat Jan 01 08:30:00 2000 PST |   5\n Sat Jan 01 12:30:00 2000 PST |   5\n Sat Jan 01 16:30:00 2000 PST |   5\n Sat Jan 01 20:30:00 2000 PST |   5\n Tue Dec 31 20:30:00 2019 PST |   6\n Wed Jan 01 00:30:00 2020 PST |   6\n Wed Jan 01 04:30:00 2020 PST |   6\n Wed Jan 01 08:30:00 2020 PST |   6\n Wed Jan 01 12:30:00 2020 PST |   6\n Wed Jan 01 16:30:00 2020 PST |   6\n Wed Jan 01 20:30:00 2020 PST |   6\n\nALTER MATERIALIZED VIEW cagg_4_hours_offset SET (timescaledb.materialized_only=true);\nSELECT * FROM cagg_4_hours_offset;\n         time_bucket          | max \n------------------------------+-----\n Sat Jan 01 00:30:00 2000 PST |   5\n Sat Jan 01 04:30:00 2000 PST |   5\n Sat Jan 01 08:30:00 2000 PST |   5\n Sat Jan 01 12:30:00 2000 PST |   5\n Sat Jan 01 16:30:00 2000 PST |   5\n Sat Jan 01 20:30:00 2000 PST |   5\n Tue Dec 31 20:30:00 2019 PST |   6\n Wed Jan 01 00:30:00 2020 PST |   6\n Wed Jan 01 04:30:00 2020 PST |   6\n Wed Jan 01 08:30:00 2020 PST |   6\n Wed Jan 01 12:30:00 2020 PST |   6\n Wed Jan 01 16:30:00 2020 PST |   6\n Wed Jan 01 20:30:00 2020 PST |   6\n\nSELECT time_bucket('4 hour', time, '2000-01-01 01:00:00 PST'::timestamptz), max(value) FROM temperature GROUP BY 1 ORDER BY 1;\n         time_bucket          | max \n------------------------------+-----\n Sat Jan 01 01:00:00 2000 PST |   5\n Sat Jan 01 05:00:00 2000 PST |   5\n Sat Jan 01 09:00:00 2000 PST |   5\n Sat Jan 01 13:00:00 2000 PST |   5\n Sat Jan 01 17:00:00 2000 PST |   5\n Sat Jan 01 21:00:00 2000 PST |   5\n Tue Dec 31 21:00:00 2019 PST |   6\n Wed Jan 01 01:00:00 2020 PST |   6\n Wed Jan 01 05:00:00 2020 PST |   6\n Wed Jan 01 09:00:00 2020 PST |   6\n Wed Jan 01 13:00:00 2020 PST |   6\n Wed Jan 01 17:00:00 2020 PST |   6\n Wed Jan 01 21:00:00 2020 PST |   6\n\nSELECT * FROM cagg_4_hours_origin;\n         time_bucket          | max \n------------------------------+-----\n Sat Jan 01 01:00:00 2000 PST |   5\n Sat Jan 01 05:00:00 2000 PST |   5\n Sat Jan 01 09:00:00 2000 PST |   5\n Sat Jan 01 13:00:00 2000 PST |   5\n Sat Jan 01 17:00:00 2000 PST |   5\n Sat Jan 01 21:00:00 2000 PST |   5\n Tue Dec 31 21:00:00 2019 PST |   6\n Wed Jan 01 01:00:00 2020 PST |   6\n Wed Jan 01 05:00:00 2020 PST |   6\n Wed Jan 01 09:00:00 2020 PST |   6\n Wed Jan 01 13:00:00 2020 PST |   6\n Wed Jan 01 17:00:00 2020 PST |   6\n Wed Jan 01 21:00:00 2020 PST |   6\n\nALTER MATERIALIZED VIEW cagg_4_hours_origin SET (timescaledb.materialized_only=true);\nSELECT * FROM cagg_4_hours_origin;\n         time_bucket          | max \n------------------------------+-----\n Sat Jan 01 01:00:00 2000 PST |   5\n Sat Jan 01 05:00:00 2000 PST |   5\n Sat Jan 01 09:00:00 2000 PST |   5\n Sat Jan 01 13:00:00 2000 PST |   5\n Sat Jan 01 17:00:00 2000 PST |   5\n Sat Jan 01 21:00:00 2000 PST |   5\n Tue Dec 31 21:00:00 2019 PST |   6\n Wed Jan 01 01:00:00 2020 PST |   6\n Wed Jan 01 05:00:00 2020 PST |   6\n Wed Jan 01 09:00:00 2020 PST |   6\n Wed Jan 01 13:00:00 2020 PST |   6\n Wed Jan 01 17:00:00 2020 PST |   6\n Wed Jan 01 21:00:00 2020 PST |   6\n\n-- Update the last bucket and re-materialize\nINSERT INTO temperature values('2020-01-01 23:55:00 PST', 10);\nCALL refresh_continuous_aggregate('cagg_4_hours', NULL, NULL);\nCALL refresh_continuous_aggregate('cagg_4_hours_offset', NULL, NULL);\nCALL refresh_continuous_aggregate('cagg_4_hours_origin', NULL, NULL);\nSELECT * FROM cagg_4_hours;\n         time_bucket          | max \n------------------------------+-----\n Sat Jan 01 00:00:00 2000 PST |   5\n Sat Jan 01 04:00:00 2000 PST |   5\n Sat Jan 01 08:00:00 2000 PST |   5\n Sat Jan 01 12:00:00 2000 PST |   5\n Sat Jan 01 16:00:00 2000 PST |   5\n Sat Jan 01 20:00:00 2000 PST |   5\n Wed Jan 01 00:00:00 2020 PST |   6\n Wed Jan 01 04:00:00 2020 PST |   6\n Wed Jan 01 08:00:00 2020 PST |   6\n Wed Jan 01 12:00:00 2020 PST |   6\n Wed Jan 01 16:00:00 2020 PST |   6\n Wed Jan 01 20:00:00 2020 PST |  10\n\nSELECT * FROM cagg_4_hours_offset;\n         time_bucket          | max \n------------------------------+-----\n Sat Jan 01 00:30:00 2000 PST |   5\n Sat Jan 01 04:30:00 2000 PST |   5\n Sat Jan 01 08:30:00 2000 PST |   5\n Sat Jan 01 12:30:00 2000 PST |   5\n Sat Jan 01 16:30:00 2000 PST |   5\n Sat Jan 01 20:30:00 2000 PST |   5\n Tue Dec 31 20:30:00 2019 PST |   6\n Wed Jan 01 00:30:00 2020 PST |   6\n Wed Jan 01 04:30:00 2020 PST |   6\n Wed Jan 01 08:30:00 2020 PST |   6\n Wed Jan 01 12:30:00 2020 PST |   6\n Wed Jan 01 16:30:00 2020 PST |   6\n Wed Jan 01 20:30:00 2020 PST |  10\n\nSELECT * FROM cagg_4_hours_origin;\n         time_bucket          | max \n------------------------------+-----\n Sat Jan 01 01:00:00 2000 PST |   5\n Sat Jan 01 05:00:00 2000 PST |   5\n Sat Jan 01 09:00:00 2000 PST |   5\n Sat Jan 01 13:00:00 2000 PST |   5\n Sat Jan 01 17:00:00 2000 PST |   5\n Sat Jan 01 21:00:00 2000 PST |   5\n Tue Dec 31 21:00:00 2019 PST |   6\n Wed Jan 01 01:00:00 2020 PST |   6\n Wed Jan 01 05:00:00 2020 PST |   6\n Wed Jan 01 09:00:00 2020 PST |   6\n Wed Jan 01 13:00:00 2020 PST |   6\n Wed Jan 01 17:00:00 2020 PST |   6\n Wed Jan 01 21:00:00 2020 PST |  10\n\n-- Check the real-time functionality\nALTER MATERIALIZED VIEW cagg_4_hours SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW cagg_4_hours_offset SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW cagg_4_hours_origin SET (timescaledb.materialized_only=false);\n-- Check watermarks\nSELECT continuous_agg.user_view_name, continuous_aggs_watermark.watermark, _timescaledb_functions.to_timestamp(watermark)\n  FROM _timescaledb_catalog.continuous_aggs_watermark\n  JOIN _timescaledb_catalog.continuous_agg USING (mat_hypertable_id)\nWHERE user_view_name LIKE 'cagg_4_hours%'\nORDER BY mat_hypertable_id, watermark;\n   user_view_name    |    watermark     |         to_timestamp         \n---------------------+------------------+------------------------------\n cagg_4_hours        | 1577952000000000 | Thu Jan 02 00:00:00 2020 PST\n cagg_4_hours_offset | 1577953800000000 | Thu Jan 02 00:30:00 2020 PST\n cagg_4_hours_origin | 1577955600000000 | Thu Jan 02 01:00:00 2020 PST\n\n-- Insert new data\nINSERT INTO temperature values('2020-01-02 00:10:00 PST', 2222);\nINSERT INTO temperature values('2020-01-02 05:35:00 PST', 5555);\nINSERT INTO temperature values('2020-01-02 09:05:00 PST', 8888);\n-- Watermark is at Thu Jan 02 00:00:00 2020 PST - all inserted tuples should be seen\nSELECT * FROM cagg_4_hours;\n         time_bucket          | max  \n------------------------------+------\n Sat Jan 01 00:00:00 2000 PST |    5\n Sat Jan 01 04:00:00 2000 PST |    5\n Sat Jan 01 08:00:00 2000 PST |    5\n Sat Jan 01 12:00:00 2000 PST |    5\n Sat Jan 01 16:00:00 2000 PST |    5\n Sat Jan 01 20:00:00 2000 PST |    5\n Wed Jan 01 00:00:00 2020 PST |    6\n Wed Jan 01 04:00:00 2020 PST |    6\n Wed Jan 01 08:00:00 2020 PST |    6\n Wed Jan 01 12:00:00 2020 PST |    6\n Wed Jan 01 16:00:00 2020 PST |    6\n Wed Jan 01 20:00:00 2020 PST |   10\n Thu Jan 02 00:00:00 2020 PST | 2222\n Thu Jan 02 04:00:00 2020 PST | 5555\n Thu Jan 02 08:00:00 2020 PST | 8888\n\n-- Watermark is at Thu Jan 02 00:30:00 2020 PST - only two inserted tuples should be seen\nSELECT * FROM cagg_4_hours_offset;\n         time_bucket          | max  \n------------------------------+------\n Sat Jan 01 00:30:00 2000 PST |    5\n Sat Jan 01 04:30:00 2000 PST |    5\n Sat Jan 01 08:30:00 2000 PST |    5\n Sat Jan 01 12:30:00 2000 PST |    5\n Sat Jan 01 16:30:00 2000 PST |    5\n Sat Jan 01 20:30:00 2000 PST |    5\n Tue Dec 31 20:30:00 2019 PST |    6\n Wed Jan 01 00:30:00 2020 PST |    6\n Wed Jan 01 04:30:00 2020 PST |    6\n Wed Jan 01 08:30:00 2020 PST |    6\n Wed Jan 01 12:30:00 2020 PST |    6\n Wed Jan 01 16:30:00 2020 PST |    6\n Wed Jan 01 20:30:00 2020 PST |   10\n Thu Jan 02 04:30:00 2020 PST | 5555\n Thu Jan 02 08:30:00 2020 PST | 8888\n\n-- Watermark is at Thu Jan 02 01:00:00 2020 PST - only two inserted tuples should be seen\nSELECT * FROM cagg_4_hours_origin;\n         time_bucket          | max  \n------------------------------+------\n Sat Jan 01 01:00:00 2000 PST |    5\n Sat Jan 01 05:00:00 2000 PST |    5\n Sat Jan 01 09:00:00 2000 PST |    5\n Sat Jan 01 13:00:00 2000 PST |    5\n Sat Jan 01 17:00:00 2000 PST |    5\n Sat Jan 01 21:00:00 2000 PST |    5\n Tue Dec 31 21:00:00 2019 PST |    6\n Wed Jan 01 01:00:00 2020 PST |    6\n Wed Jan 01 05:00:00 2020 PST |    6\n Wed Jan 01 09:00:00 2020 PST |    6\n Wed Jan 01 13:00:00 2020 PST |    6\n Wed Jan 01 17:00:00 2020 PST |    6\n Wed Jan 01 21:00:00 2020 PST |   10\n Thu Jan 02 05:00:00 2020 PST | 5555\n Thu Jan 02 09:00:00 2020 PST | 8888\n\n-- Update materialized data\nSET client_min_messages TO DEBUG1;\nCALL refresh_continuous_aggregate('cagg_4_hours', NULL, NULL);\npsql:include/cagg_query_common.sql:683: LOG:  statement: CALL refresh_continuous_aggregate('cagg_4_hours', NULL, NULL);\npsql:include/cagg_query_common.sql:683: DEBUG:  continuous aggregate refresh (individual invalidation) on \"cagg_4_hours\" in window [ Thu Jan 02 00:00:00 2020 PST, Thu Jan 02 12:00:00 2020 PST ]\npsql:include/cagg_query_common.sql:683: LOG:  deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_33\"\npsql:include/cagg_query_common.sql:683: LOG:  inserted 3 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_33\"\nCALL refresh_continuous_aggregate('cagg_4_hours_offset', NULL, NULL);\npsql:include/cagg_query_common.sql:684: LOG:  statement: CALL refresh_continuous_aggregate('cagg_4_hours_offset', NULL, NULL);\npsql:include/cagg_query_common.sql:684: DEBUG:  continuous aggregate refresh (individual invalidation) on \"cagg_4_hours_offset\" in window [ Wed Jan 01 20:30:00 2020 PST, Thu Jan 02 12:30:00 2020 PST ]\npsql:include/cagg_query_common.sql:684: LOG:  deleted 1 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_34\"\npsql:include/cagg_query_common.sql:684: LOG:  inserted 3 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_34\"\nCALL refresh_continuous_aggregate('cagg_4_hours_origin', NULL, NULL);\npsql:include/cagg_query_common.sql:685: LOG:  statement: CALL refresh_continuous_aggregate('cagg_4_hours_origin', NULL, NULL);\npsql:include/cagg_query_common.sql:685: DEBUG:  continuous aggregate refresh (individual invalidation) on \"cagg_4_hours_origin\" in window [ Wed Jan 01 21:00:00 2020 PST, Thu Jan 02 13:00:00 2020 PST ]\npsql:include/cagg_query_common.sql:685: LOG:  deleted 1 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_35\"\npsql:include/cagg_query_common.sql:685: LOG:  inserted 3 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_35\"\nRESET client_min_messages;\npsql:include/cagg_query_common.sql:686: LOG:  statement: RESET client_min_messages;\n-- Query the CAggs and check that all buckets are materialized\nSELECT * FROM cagg_4_hours;\n         time_bucket          | max  \n------------------------------+------\n Sat Jan 01 00:00:00 2000 PST |    5\n Sat Jan 01 04:00:00 2000 PST |    5\n Sat Jan 01 08:00:00 2000 PST |    5\n Sat Jan 01 12:00:00 2000 PST |    5\n Sat Jan 01 16:00:00 2000 PST |    5\n Sat Jan 01 20:00:00 2000 PST |    5\n Wed Jan 01 00:00:00 2020 PST |    6\n Wed Jan 01 04:00:00 2020 PST |    6\n Wed Jan 01 08:00:00 2020 PST |    6\n Wed Jan 01 12:00:00 2020 PST |    6\n Wed Jan 01 16:00:00 2020 PST |    6\n Wed Jan 01 20:00:00 2020 PST |   10\n Thu Jan 02 00:00:00 2020 PST | 2222\n Thu Jan 02 04:00:00 2020 PST | 5555\n Thu Jan 02 08:00:00 2020 PST | 8888\n\nALTER MATERIALIZED VIEW cagg_4_hours SET (timescaledb.materialized_only=true);\nSELECT * FROM cagg_4_hours;\n         time_bucket          | max  \n------------------------------+------\n Sat Jan 01 00:00:00 2000 PST |    5\n Sat Jan 01 04:00:00 2000 PST |    5\n Sat Jan 01 08:00:00 2000 PST |    5\n Sat Jan 01 12:00:00 2000 PST |    5\n Sat Jan 01 16:00:00 2000 PST |    5\n Sat Jan 01 20:00:00 2000 PST |    5\n Wed Jan 01 00:00:00 2020 PST |    6\n Wed Jan 01 04:00:00 2020 PST |    6\n Wed Jan 01 08:00:00 2020 PST |    6\n Wed Jan 01 12:00:00 2020 PST |    6\n Wed Jan 01 16:00:00 2020 PST |    6\n Wed Jan 01 20:00:00 2020 PST |   10\n Thu Jan 02 00:00:00 2020 PST | 2222\n Thu Jan 02 04:00:00 2020 PST | 5555\n Thu Jan 02 08:00:00 2020 PST | 8888\n\nSELECT time_bucket('4 hour', time), max(value) FROM temperature GROUP BY 1 ORDER BY 1;\n         time_bucket          | max  \n------------------------------+------\n Sat Jan 01 00:00:00 2000 PST |    5\n Sat Jan 01 04:00:00 2000 PST |    5\n Sat Jan 01 08:00:00 2000 PST |    5\n Sat Jan 01 12:00:00 2000 PST |    5\n Sat Jan 01 16:00:00 2000 PST |    5\n Sat Jan 01 20:00:00 2000 PST |    5\n Wed Jan 01 00:00:00 2020 PST |    6\n Wed Jan 01 04:00:00 2020 PST |    6\n Wed Jan 01 08:00:00 2020 PST |    6\n Wed Jan 01 12:00:00 2020 PST |    6\n Wed Jan 01 16:00:00 2020 PST |    6\n Wed Jan 01 20:00:00 2020 PST |   10\n Thu Jan 02 00:00:00 2020 PST | 2222\n Thu Jan 02 04:00:00 2020 PST | 5555\n Thu Jan 02 08:00:00 2020 PST | 8888\n\nSELECT * FROM cagg_4_hours_offset;\n         time_bucket          | max  \n------------------------------+------\n Sat Jan 01 00:30:00 2000 PST |    5\n Sat Jan 01 04:30:00 2000 PST |    5\n Sat Jan 01 08:30:00 2000 PST |    5\n Sat Jan 01 12:30:00 2000 PST |    5\n Sat Jan 01 16:30:00 2000 PST |    5\n Sat Jan 01 20:30:00 2000 PST |    5\n Tue Dec 31 20:30:00 2019 PST |    6\n Wed Jan 01 00:30:00 2020 PST |    6\n Wed Jan 01 04:30:00 2020 PST |    6\n Wed Jan 01 08:30:00 2020 PST |    6\n Wed Jan 01 12:30:00 2020 PST |    6\n Wed Jan 01 16:30:00 2020 PST |    6\n Wed Jan 01 20:30:00 2020 PST | 2222\n Thu Jan 02 04:30:00 2020 PST | 5555\n Thu Jan 02 08:30:00 2020 PST | 8888\n\nALTER MATERIALIZED VIEW cagg_4_hours_offset SET (timescaledb.materialized_only=true);\nSELECT * FROM cagg_4_hours_offset;\n         time_bucket          | max  \n------------------------------+------\n Sat Jan 01 00:30:00 2000 PST |    5\n Sat Jan 01 04:30:00 2000 PST |    5\n Sat Jan 01 08:30:00 2000 PST |    5\n Sat Jan 01 12:30:00 2000 PST |    5\n Sat Jan 01 16:30:00 2000 PST |    5\n Sat Jan 01 20:30:00 2000 PST |    5\n Tue Dec 31 20:30:00 2019 PST |    6\n Wed Jan 01 00:30:00 2020 PST |    6\n Wed Jan 01 04:30:00 2020 PST |    6\n Wed Jan 01 08:30:00 2020 PST |    6\n Wed Jan 01 12:30:00 2020 PST |    6\n Wed Jan 01 16:30:00 2020 PST |    6\n Wed Jan 01 20:30:00 2020 PST | 2222\n Thu Jan 02 04:30:00 2020 PST | 5555\n Thu Jan 02 08:30:00 2020 PST | 8888\n\nSELECT time_bucket('4 hour', time, '30m'::interval), max(value) FROM temperature GROUP BY 1 ORDER BY 1;\n         time_bucket          | max  \n------------------------------+------\n Sat Jan 01 00:30:00 2000 PST |    5\n Sat Jan 01 04:30:00 2000 PST |    5\n Sat Jan 01 08:30:00 2000 PST |    5\n Sat Jan 01 12:30:00 2000 PST |    5\n Sat Jan 01 16:30:00 2000 PST |    5\n Sat Jan 01 20:30:00 2000 PST |    5\n Tue Dec 31 20:30:00 2019 PST |    6\n Wed Jan 01 00:30:00 2020 PST |    6\n Wed Jan 01 04:30:00 2020 PST |    6\n Wed Jan 01 08:30:00 2020 PST |    6\n Wed Jan 01 12:30:00 2020 PST |    6\n Wed Jan 01 16:30:00 2020 PST |    6\n Wed Jan 01 20:30:00 2020 PST | 2222\n Thu Jan 02 04:30:00 2020 PST | 5555\n Thu Jan 02 08:30:00 2020 PST | 8888\n\nSELECT * FROM cagg_4_hours_origin;\n         time_bucket          | max  \n------------------------------+------\n Sat Jan 01 01:00:00 2000 PST |    5\n Sat Jan 01 05:00:00 2000 PST |    5\n Sat Jan 01 09:00:00 2000 PST |    5\n Sat Jan 01 13:00:00 2000 PST |    5\n Sat Jan 01 17:00:00 2000 PST |    5\n Sat Jan 01 21:00:00 2000 PST |    5\n Tue Dec 31 21:00:00 2019 PST |    6\n Wed Jan 01 01:00:00 2020 PST |    6\n Wed Jan 01 05:00:00 2020 PST |    6\n Wed Jan 01 09:00:00 2020 PST |    6\n Wed Jan 01 13:00:00 2020 PST |    6\n Wed Jan 01 17:00:00 2020 PST |    6\n Wed Jan 01 21:00:00 2020 PST | 2222\n Thu Jan 02 05:00:00 2020 PST | 5555\n Thu Jan 02 09:00:00 2020 PST | 8888\n\nALTER MATERIALIZED VIEW cagg_4_hours_origin SET (timescaledb.materialized_only=true);\nSELECT * FROM cagg_4_hours_origin;\n         time_bucket          | max  \n------------------------------+------\n Sat Jan 01 01:00:00 2000 PST |    5\n Sat Jan 01 05:00:00 2000 PST |    5\n Sat Jan 01 09:00:00 2000 PST |    5\n Sat Jan 01 13:00:00 2000 PST |    5\n Sat Jan 01 17:00:00 2000 PST |    5\n Sat Jan 01 21:00:00 2000 PST |    5\n Tue Dec 31 21:00:00 2019 PST |    6\n Wed Jan 01 01:00:00 2020 PST |    6\n Wed Jan 01 05:00:00 2020 PST |    6\n Wed Jan 01 09:00:00 2020 PST |    6\n Wed Jan 01 13:00:00 2020 PST |    6\n Wed Jan 01 17:00:00 2020 PST |    6\n Wed Jan 01 21:00:00 2020 PST | 2222\n Thu Jan 02 05:00:00 2020 PST | 5555\n Thu Jan 02 09:00:00 2020 PST | 8888\n\nSELECT time_bucket('4 hour', time, '2000-01-01 01:00:00 PST'::timestamptz), max(value) FROM temperature GROUP BY 1 ORDER BY 1;\n         time_bucket          | max  \n------------------------------+------\n Sat Jan 01 01:00:00 2000 PST |    5\n Sat Jan 01 05:00:00 2000 PST |    5\n Sat Jan 01 09:00:00 2000 PST |    5\n Sat Jan 01 13:00:00 2000 PST |    5\n Sat Jan 01 17:00:00 2000 PST |    5\n Sat Jan 01 21:00:00 2000 PST |    5\n Tue Dec 31 21:00:00 2019 PST |    6\n Wed Jan 01 01:00:00 2020 PST |    6\n Wed Jan 01 05:00:00 2020 PST |    6\n Wed Jan 01 09:00:00 2020 PST |    6\n Wed Jan 01 13:00:00 2020 PST |    6\n Wed Jan 01 17:00:00 2020 PST |    6\n Wed Jan 01 21:00:00 2020 PST | 2222\n Thu Jan 02 05:00:00 2020 PST | 5555\n Thu Jan 02 09:00:00 2020 PST | 8888\n\n-- Test invalidations\nTRUNCATE temperature;\nCALL refresh_continuous_aggregate('cagg_4_hours', NULL, NULL);\nCALL refresh_continuous_aggregate('cagg_4_hours_offset', NULL, NULL);\nCALL refresh_continuous_aggregate('cagg_4_hours_origin', NULL, NULL);\nINSERT INTO temperature\n  SELECT time, 5\n    FROM generate_series('2000-01-01 01:00:00 PST'::timestamptz,\n                         '2000-01-01 23:59:59 PST','1m') time;\nINSERT INTO temperature\n  SELECT time, 6\n    FROM generate_series('2020-01-01 00:00:00 PST'::timestamptz,\n                         '2020-01-01 23:59:59 PST','1m') time;\nINSERT INTO temperature values('2020-01-02 01:05:00+01', 2222);\nINSERT INTO temperature values('2020-01-02 01:35:00+01', 5555);\nINSERT INTO temperature values('2020-01-02 05:05:00+01', 8888);\nSET client_min_messages TO DEBUG1;\nCALL refresh_continuous_aggregate('cagg_4_hours', NULL, NULL);\npsql:include/cagg_query_common.sql:725: LOG:  statement: CALL refresh_continuous_aggregate('cagg_4_hours', NULL, NULL);\npsql:include/cagg_query_common.sql:725: DEBUG:  hypertable 4 existing watermark >= new invalidation threshold 1577998800000000 1577952000000000\npsql:include/cagg_query_common.sql:725: DEBUG:  continuous aggregate refresh (individual invalidation) on \"cagg_4_hours\" in window [ Sat Jan 01 00:00:00 2000 PST, Sun Jan 02 00:00:00 2000 PST ]\npsql:include/cagg_query_common.sql:725: LOG:  deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_33\"\npsql:include/cagg_query_common.sql:725: LOG:  inserted 6 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_33\"\npsql:include/cagg_query_common.sql:725: DEBUG:  hypertable 33 existing watermark >= new watermark 1577995200000000 946800000000000\npsql:include/cagg_query_common.sql:725: DEBUG:  continuous aggregate refresh (individual invalidation) on \"cagg_4_hours\" in window [ Wed Jan 01 00:00:00 2020 PST, Thu Jan 02 00:00:00 2020 PST ]\npsql:include/cagg_query_common.sql:725: LOG:  deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_33\"\npsql:include/cagg_query_common.sql:725: LOG:  inserted 6 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_33\"\npsql:include/cagg_query_common.sql:725: DEBUG:  hypertable 33 existing watermark >= new watermark 1577995200000000 1577952000000000\npsql:include/cagg_query_common.sql:725: DEBUG:  continuous aggregate refresh (individual invalidation) on \"cagg_4_hours\" in window [ Thu Jan 02 12:00:00 2020 PST, Thu Jan 02 16:00:00 2020 PST ]\npsql:include/cagg_query_common.sql:725: LOG:  deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_33\"\npsql:include/cagg_query_common.sql:725: LOG:  inserted 0 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_33\"\nCALL refresh_continuous_aggregate('cagg_4_hours_offset', NULL, NULL);\npsql:include/cagg_query_common.sql:726: LOG:  statement: CALL refresh_continuous_aggregate('cagg_4_hours_offset', NULL, NULL);\npsql:include/cagg_query_common.sql:726: DEBUG:  hypertable 4 existing watermark >= new invalidation threshold 1577998800000000 1577953800000000\npsql:include/cagg_query_common.sql:726: DEBUG:  continuous aggregate refresh (individual invalidation) on \"cagg_4_hours_offset\" in window [ Sat Jan 01 00:30:00 2000 PST, Sun Jan 02 00:30:00 2000 PST ]\npsql:include/cagg_query_common.sql:726: LOG:  deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_34\"\npsql:include/cagg_query_common.sql:726: LOG:  inserted 6 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_34\"\npsql:include/cagg_query_common.sql:726: DEBUG:  hypertable 34 existing watermark >= new watermark 1577997000000000 946801800000000\npsql:include/cagg_query_common.sql:726: DEBUG:  continuous aggregate refresh (individual invalidation) on \"cagg_4_hours_offset\" in window [ Tue Dec 31 20:30:00 2019 PST, Thu Jan 02 00:30:00 2020 PST ]\npsql:include/cagg_query_common.sql:726: LOG:  deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_34\"\npsql:include/cagg_query_common.sql:726: LOG:  inserted 7 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_34\"\npsql:include/cagg_query_common.sql:726: DEBUG:  hypertable 34 existing watermark >= new watermark 1577997000000000 1577953800000000\npsql:include/cagg_query_common.sql:726: DEBUG:  continuous aggregate refresh (individual invalidation) on \"cagg_4_hours_offset\" in window [ Thu Jan 02 12:30:00 2020 PST, Thu Jan 02 16:30:00 2020 PST ]\npsql:include/cagg_query_common.sql:726: LOG:  deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_34\"\npsql:include/cagg_query_common.sql:726: LOG:  inserted 0 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_34\"\nCALL refresh_continuous_aggregate('cagg_4_hours_origin', NULL, NULL);\npsql:include/cagg_query_common.sql:727: LOG:  statement: CALL refresh_continuous_aggregate('cagg_4_hours_origin', NULL, NULL);\npsql:include/cagg_query_common.sql:727: DEBUG:  hypertable 4 existing watermark >= new invalidation threshold 1577998800000000 1577955600000000\npsql:include/cagg_query_common.sql:727: DEBUG:  continuous aggregate refresh (individual invalidation) on \"cagg_4_hours_origin\" in window [ Sat Jan 01 01:00:00 2000 PST, Sun Jan 02 01:00:00 2000 PST ]\npsql:include/cagg_query_common.sql:727: LOG:  deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_35\"\npsql:include/cagg_query_common.sql:727: LOG:  inserted 6 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_35\"\npsql:include/cagg_query_common.sql:727: DEBUG:  hypertable 35 existing watermark >= new watermark 1577998800000000 946803600000000\npsql:include/cagg_query_common.sql:727: DEBUG:  continuous aggregate refresh (individual invalidation) on \"cagg_4_hours_origin\" in window [ Tue Dec 31 21:00:00 2019 PST, Thu Jan 02 01:00:00 2020 PST ]\npsql:include/cagg_query_common.sql:727: LOG:  deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_35\"\npsql:include/cagg_query_common.sql:727: LOG:  inserted 7 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_35\"\npsql:include/cagg_query_common.sql:727: DEBUG:  hypertable 35 existing watermark >= new watermark 1577998800000000 1577955600000000\nRESET client_min_messages;\npsql:include/cagg_query_common.sql:728: LOG:  statement: RESET client_min_messages;\nALTER MATERIALIZED VIEW cagg_4_hours SET (timescaledb.materialized_only=true);\nSELECT * FROM cagg_4_hours;\n         time_bucket          | max  \n------------------------------+------\n Sat Jan 01 00:00:00 2000 PST |    5\n Sat Jan 01 04:00:00 2000 PST |    5\n Sat Jan 01 08:00:00 2000 PST |    5\n Sat Jan 01 12:00:00 2000 PST |    5\n Sat Jan 01 16:00:00 2000 PST |    5\n Sat Jan 01 20:00:00 2000 PST |    5\n Wed Jan 01 00:00:00 2020 PST |    6\n Wed Jan 01 04:00:00 2020 PST |    6\n Wed Jan 01 08:00:00 2020 PST |    6\n Wed Jan 01 12:00:00 2020 PST |    6\n Wed Jan 01 16:00:00 2020 PST | 5555\n Wed Jan 01 20:00:00 2020 PST | 8888\n\nALTER MATERIALIZED VIEW cagg_4_hours SET (timescaledb.materialized_only=false);\nSELECT * FROM cagg_4_hours;\n         time_bucket          | max  \n------------------------------+------\n Sat Jan 01 00:00:00 2000 PST |    5\n Sat Jan 01 04:00:00 2000 PST |    5\n Sat Jan 01 08:00:00 2000 PST |    5\n Sat Jan 01 12:00:00 2000 PST |    5\n Sat Jan 01 16:00:00 2000 PST |    5\n Sat Jan 01 20:00:00 2000 PST |    5\n Wed Jan 01 00:00:00 2020 PST |    6\n Wed Jan 01 04:00:00 2020 PST |    6\n Wed Jan 01 08:00:00 2020 PST |    6\n Wed Jan 01 12:00:00 2020 PST |    6\n Wed Jan 01 16:00:00 2020 PST | 5555\n Wed Jan 01 20:00:00 2020 PST | 8888\n\nSELECT time_bucket('4 hour', time), max(value) FROM temperature GROUP BY 1 ORDER BY 1;\n         time_bucket          | max  \n------------------------------+------\n Sat Jan 01 00:00:00 2000 PST |    5\n Sat Jan 01 04:00:00 2000 PST |    5\n Sat Jan 01 08:00:00 2000 PST |    5\n Sat Jan 01 12:00:00 2000 PST |    5\n Sat Jan 01 16:00:00 2000 PST |    5\n Sat Jan 01 20:00:00 2000 PST |    5\n Wed Jan 01 00:00:00 2020 PST |    6\n Wed Jan 01 04:00:00 2020 PST |    6\n Wed Jan 01 08:00:00 2020 PST |    6\n Wed Jan 01 12:00:00 2020 PST |    6\n Wed Jan 01 16:00:00 2020 PST | 5555\n Wed Jan 01 20:00:00 2020 PST | 8888\n\nALTER MATERIALIZED VIEW cagg_4_hours_offset SET (timescaledb.materialized_only=true);\nSELECT * FROM cagg_4_hours_offset;\n         time_bucket          | max  \n------------------------------+------\n Sat Jan 01 00:30:00 2000 PST |    5\n Sat Jan 01 04:30:00 2000 PST |    5\n Sat Jan 01 08:30:00 2000 PST |    5\n Sat Jan 01 12:30:00 2000 PST |    5\n Sat Jan 01 16:30:00 2000 PST |    5\n Sat Jan 01 20:30:00 2000 PST |    5\n Tue Dec 31 20:30:00 2019 PST |    6\n Wed Jan 01 00:30:00 2020 PST |    6\n Wed Jan 01 04:30:00 2020 PST |    6\n Wed Jan 01 08:30:00 2020 PST |    6\n Wed Jan 01 12:30:00 2020 PST | 2222\n Wed Jan 01 16:30:00 2020 PST | 8888\n Wed Jan 01 20:30:00 2020 PST |    6\n\nALTER MATERIALIZED VIEW cagg_4_hours_offset SET (timescaledb.materialized_only=false);\nSELECT * FROM cagg_4_hours_offset;\n         time_bucket          | max  \n------------------------------+------\n Sat Jan 01 00:30:00 2000 PST |    5\n Sat Jan 01 04:30:00 2000 PST |    5\n Sat Jan 01 08:30:00 2000 PST |    5\n Sat Jan 01 12:30:00 2000 PST |    5\n Sat Jan 01 16:30:00 2000 PST |    5\n Sat Jan 01 20:30:00 2000 PST |    5\n Tue Dec 31 20:30:00 2019 PST |    6\n Wed Jan 01 00:30:00 2020 PST |    6\n Wed Jan 01 04:30:00 2020 PST |    6\n Wed Jan 01 08:30:00 2020 PST |    6\n Wed Jan 01 12:30:00 2020 PST | 2222\n Wed Jan 01 16:30:00 2020 PST | 8888\n Wed Jan 01 20:30:00 2020 PST |    6\n\nSELECT time_bucket('4 hour', time, '30m'::interval), max(value) FROM temperature GROUP BY 1 ORDER BY 1;\n         time_bucket          | max  \n------------------------------+------\n Sat Jan 01 00:30:00 2000 PST |    5\n Sat Jan 01 04:30:00 2000 PST |    5\n Sat Jan 01 08:30:00 2000 PST |    5\n Sat Jan 01 12:30:00 2000 PST |    5\n Sat Jan 01 16:30:00 2000 PST |    5\n Sat Jan 01 20:30:00 2000 PST |    5\n Tue Dec 31 20:30:00 2019 PST |    6\n Wed Jan 01 00:30:00 2020 PST |    6\n Wed Jan 01 04:30:00 2020 PST |    6\n Wed Jan 01 08:30:00 2020 PST |    6\n Wed Jan 01 12:30:00 2020 PST | 2222\n Wed Jan 01 16:30:00 2020 PST | 8888\n Wed Jan 01 20:30:00 2020 PST |    6\n\nALTER MATERIALIZED VIEW cagg_4_hours_origin SET (timescaledb.materialized_only=true);\nSELECT * FROM cagg_4_hours_origin;\n         time_bucket          | max  \n------------------------------+------\n Sat Jan 01 01:00:00 2000 PST |    5\n Sat Jan 01 05:00:00 2000 PST |    5\n Sat Jan 01 09:00:00 2000 PST |    5\n Sat Jan 01 13:00:00 2000 PST |    5\n Sat Jan 01 17:00:00 2000 PST |    5\n Sat Jan 01 21:00:00 2000 PST |    5\n Tue Dec 31 21:00:00 2019 PST |    6\n Wed Jan 01 01:00:00 2020 PST |    6\n Wed Jan 01 05:00:00 2020 PST |    6\n Wed Jan 01 09:00:00 2020 PST |    6\n Wed Jan 01 13:00:00 2020 PST | 5555\n Wed Jan 01 17:00:00 2020 PST | 8888\n Wed Jan 01 21:00:00 2020 PST |    6\n\nALTER MATERIALIZED VIEW cagg_4_hours_origin SET (timescaledb.materialized_only=false);\nSELECT * FROM cagg_4_hours_origin;\n         time_bucket          | max  \n------------------------------+------\n Sat Jan 01 01:00:00 2000 PST |    5\n Sat Jan 01 05:00:00 2000 PST |    5\n Sat Jan 01 09:00:00 2000 PST |    5\n Sat Jan 01 13:00:00 2000 PST |    5\n Sat Jan 01 17:00:00 2000 PST |    5\n Sat Jan 01 21:00:00 2000 PST |    5\n Tue Dec 31 21:00:00 2019 PST |    6\n Wed Jan 01 01:00:00 2020 PST |    6\n Wed Jan 01 05:00:00 2020 PST |    6\n Wed Jan 01 09:00:00 2020 PST |    6\n Wed Jan 01 13:00:00 2020 PST | 5555\n Wed Jan 01 17:00:00 2020 PST | 8888\n Wed Jan 01 21:00:00 2020 PST |    6\n\nSELECT time_bucket('4 hour', time, '2000-01-01 01:00:00 PST'::timestamptz), max(value) FROM temperature GROUP BY 1 ORDER BY 1;\n         time_bucket          | max  \n------------------------------+------\n Sat Jan 01 01:00:00 2000 PST |    5\n Sat Jan 01 05:00:00 2000 PST |    5\n Sat Jan 01 09:00:00 2000 PST |    5\n Sat Jan 01 13:00:00 2000 PST |    5\n Sat Jan 01 17:00:00 2000 PST |    5\n Sat Jan 01 21:00:00 2000 PST |    5\n Tue Dec 31 21:00:00 2019 PST |    6\n Wed Jan 01 01:00:00 2020 PST |    6\n Wed Jan 01 05:00:00 2020 PST |    6\n Wed Jan 01 09:00:00 2020 PST |    6\n Wed Jan 01 13:00:00 2020 PST | 5555\n Wed Jan 01 17:00:00 2020 PST | 8888\n Wed Jan 01 21:00:00 2020 PST |    6\n\n--- Test with variable width buckets (use February, since hourly origins are not supported with variable sized buckets)\nTRUNCATE temperature;\nINSERT INTO temperature\n  SELECT time, 5\n    FROM generate_series('2000-02-01 01:00:00 PST'::timestamptz,\n                         '2000-02-01 23:59:59 PST','1m') time;\nINSERT INTO temperature\n  SELECT time, 6\n    FROM generate_series('2020-02-01 01:00:00 PST'::timestamptz,\n                         '2020-02-01 23:59:59 PST','1m') time;\nSELECT * FROM _timescaledb_catalog.continuous_aggs_materialization_invalidation_log ORDER BY 1, 2, 3;\n materialization_id | lowest_modified_value | greatest_modified_value \n--------------------+-----------------------+-------------------------\n                  2 |  -9223372036854775808 |     -210866803200000001\n                  2 |      1541289600000000 |     9223372036854775807\n                  3 |  -9223372036854775808 |     -210866803200000001\n                  3 |      1541289600000000 |     9223372036854775807\n                 33 |  -9223372036854775808 |     -210866803200000001\n                 33 |      1577998800000000 |     9223372036854775807\n                 34 |  -9223372036854775808 |     -210866803200000001\n                 34 |      1577998800000000 |     9223372036854775807\n                 35 |  -9223372036854775808 |     -210866803200000001\n                 35 |      1577998800000000 |     9223372036854775807\n\nCREATE MATERIALIZED VIEW cagg_1_year\n  WITH (timescaledb.continuous, timescaledb.materialized_only = false) AS\n  SELECT time_bucket('1 year', time), max(value)\n    FROM temperature\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:766: NOTICE:  refreshing continuous aggregate \"cagg_1_year\"\nSELECT * FROM _timescaledb_catalog.continuous_aggs_materialization_invalidation_log ORDER BY 1, 2, 3;\n materialization_id | lowest_modified_value | greatest_modified_value \n--------------------+-----------------------+-------------------------\n                  2 |  -9223372036854775808 |     -210866803200000001\n                  2 |      1541289600000000 |     9223372036854775807\n                  3 |  -9223372036854775808 |     -210866803200000001\n                  3 |      1541289600000000 |     9223372036854775807\n                 33 |  -9223372036854775808 |     -210866803200000001\n                 33 |  -9223372036854775808 |     9223372036854775807\n                 33 |      1577998800000000 |     9223372036854775807\n                 34 |  -9223372036854775808 |     -210866803200000001\n                 34 |  -9223372036854775808 |     9223372036854775807\n                 34 |      1577998800000000 |     9223372036854775807\n                 35 |  -9223372036854775808 |     -210866803200000001\n                 35 |  -9223372036854775808 |     9223372036854775807\n                 35 |      1577998800000000 |     9223372036854775807\n                 36 |      1609459200000000 |     9223372036854775807\n\n---\n-- Tests with integer based hypertables\n---\nTRUNCATE table_int;\nINSERT INTO table_int\n  SELECT time, 5\n    FROM generate_series(-50, 50) time;\nCREATE MATERIALIZED VIEW cagg_int\n    WITH (timescaledb.continuous, timescaledb.materialized_only=false)\n    AS SELECT time_bucket('10', time), SUM(data) as value\n        FROM table_int\n        GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:783: NOTICE:  refreshing continuous aggregate \"cagg_int\"\nCREATE MATERIALIZED VIEW cagg_int_offset\n    WITH (timescaledb.continuous, timescaledb.materialized_only=false)\n    AS SELECT time_bucket('10', time, \"offset\"=>5), SUM(data) as value\n        FROM table_int\n        GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:789: NOTICE:  refreshing continuous aggregate \"cagg_int_offset\"\n-- Compare bucketing results\nSELECT time_bucket('10', time), SUM(data) FROM table_int GROUP BY 1 ORDER BY 1;\n time_bucket | sum \n-------------+-----\n         -50 |  50\n         -40 |  50\n         -30 |  50\n         -20 |  50\n         -10 |  50\n           0 |  50\n          10 |  50\n          20 |  50\n          30 |  50\n          40 |  50\n          50 |   5\n\nSELECT * FROM cagg_int;\n time_bucket | value \n-------------+-------\n         -50 |    50\n         -40 |    50\n         -30 |    50\n         -20 |    50\n         -10 |    50\n           0 |    50\n          10 |    50\n          20 |    50\n          30 |    50\n          40 |    50\n          50 |     5\n\nSELECT time_bucket('10', time, \"offset\"=>5), SUM(data) FROM table_int GROUP BY 1 ORDER BY 1;\n time_bucket | sum \n-------------+-----\n         -55 |  25\n         -45 |  50\n         -35 |  50\n         -25 |  50\n         -15 |  50\n          -5 |  50\n           5 |  50\n          15 |  50\n          25 |  50\n          35 |  50\n          45 |  30\n\nSELECT * FROM cagg_int_offset;\n time_bucket | value \n-------------+-------\n         -55 |    25\n         -45 |    50\n         -35 |    50\n         -25 |    50\n         -15 |    50\n          -5 |    50\n           5 |    50\n          15 |    50\n          25 |    50\n          35 |    50\n          45 |    30\n\n-- Update table\nINSERT INTO table_int VALUES(51, 100);\nINSERT INTO table_int VALUES(100, 555);\n-- Compare bucketing results\nSELECT time_bucket('10', time), SUM(data) FROM table_int GROUP BY 1 ORDER BY 1;\n time_bucket | sum \n-------------+-----\n         -50 |  50\n         -40 |  50\n         -30 |  50\n         -20 |  50\n         -10 |  50\n           0 |  50\n          10 |  50\n          20 |  50\n          30 |  50\n          40 |  50\n          50 | 105\n         100 | 555\n\nSELECT * FROM cagg_int;\n time_bucket | value \n-------------+-------\n         -50 |    50\n         -40 |    50\n         -30 |    50\n         -20 |    50\n         -10 |    50\n           0 |    50\n          10 |    50\n          20 |    50\n          30 |    50\n          40 |    50\n          50 |     5\n         100 |   555\n\nCALL refresh_continuous_aggregate('cagg_int', NULL, NULL);\nSELECT * FROM cagg_int;\n time_bucket | value \n-------------+-------\n         -50 |    50\n         -40 |    50\n         -30 |    50\n         -20 |    50\n         -10 |    50\n           0 |    50\n          10 |    50\n          20 |    50\n          30 |    50\n          40 |    50\n          50 |   105\n         100 |   555\n\nSELECT time_bucket('10', time, \"offset\"=>5), SUM(data) FROM table_int GROUP BY 1 ORDER BY 1;\n time_bucket | sum \n-------------+-----\n         -55 |  25\n         -45 |  50\n         -35 |  50\n         -25 |  50\n         -15 |  50\n          -5 |  50\n           5 |  50\n          15 |  50\n          25 |  50\n          35 |  50\n          45 | 130\n          95 | 555\n\nSELECT * FROM cagg_int_offset;  -- the value 100 is part of the already serialized bucket, so it should not be visible\n time_bucket | value \n-------------+-------\n         -55 |    25\n         -45 |    50\n         -35 |    50\n         -25 |    50\n         -15 |    50\n          -5 |    50\n           5 |    50\n          15 |    50\n          25 |    50\n          35 |    50\n          45 |    30\n          95 |   555\n\nCALL refresh_continuous_aggregate('cagg_int_offset', NULL, NULL);\nSELECT * FROM cagg_int_offset;\n time_bucket | value \n-------------+-------\n         -55 |    25\n         -45 |    50\n         -35 |    50\n         -25 |    50\n         -15 |    50\n          -5 |    50\n           5 |    50\n          15 |    50\n          25 |    50\n          35 |    50\n          45 |   130\n          95 |   555\n\n-- Ensure everything was materialized\nALTER MATERIALIZED VIEW cagg_int SET (timescaledb.materialized_only=true);\nALTER MATERIALIZED VIEW cagg_int_offset SET (timescaledb.materialized_only=true);\nSELECT * FROM cagg_int;\n time_bucket | value \n-------------+-------\n         -50 |    50\n         -40 |    50\n         -30 |    50\n         -20 |    50\n         -10 |    50\n           0 |    50\n          10 |    50\n          20 |    50\n          30 |    50\n          40 |    50\n          50 |   105\n         100 |   555\n\nSELECT * FROM cagg_int_offset;\n time_bucket | value \n-------------+-------\n         -55 |    25\n         -45 |    50\n         -35 |    50\n         -25 |    50\n         -15 |    50\n          -5 |    50\n           5 |    50\n          15 |    50\n          25 |    50\n          35 |    50\n          45 |   130\n          95 |   555\n\n-- Check that the refresh is properly aligned\nINSERT INTO table_int VALUES(114, 0);\nSET client_min_messages TO DEBUG1;\nCALL refresh_continuous_aggregate('cagg_int_offset', 100, 130);\npsql:include/cagg_query_common.sql:824: LOG:  statement: CALL refresh_continuous_aggregate('cagg_int_offset', 100, 130);\npsql:include/cagg_query_common.sql:824: DEBUG:  continuous aggregate refresh (individual invalidation) on \"cagg_int_offset\" in window [ 105, 125 ]\npsql:include/cagg_query_common.sql:824: LOG:  deleted 0 row(s) from materialization table \"_timescaledb_internal._materialized_hypertable_38\"\npsql:include/cagg_query_common.sql:824: DEBUG:  building index \"_hyper_38_67_chunk__materialized_hypertable_38_time_bucket_idx\" on table \"_hyper_38_67_chunk\" serially\npsql:include/cagg_query_common.sql:824: DEBUG:  index \"_hyper_38_67_chunk__materialized_hypertable_38_time_bucket_idx\" can safely use deduplication\npsql:include/cagg_query_common.sql:824: LOG:  inserted 1 row(s) into materialization table \"_timescaledb_internal._materialized_hypertable_38\"\nRESET client_min_messages;\npsql:include/cagg_query_common.sql:825: LOG:  statement: RESET client_min_messages;\nSELECT * FROM cagg_int_offset;\n time_bucket | value \n-------------+-------\n         -55 |    25\n         -45 |    50\n         -35 |    50\n         -25 |    50\n         -15 |    50\n          -5 |    50\n           5 |    50\n          15 |    50\n          25 |    50\n          35 |    50\n          45 |   130\n          95 |   555\n         105 |     0\n\nSELECT time_bucket('10', time, \"offset\"=>5), SUM(data) FROM table_int GROUP BY 1 ORDER BY 1;\n time_bucket | sum \n-------------+-----\n         -55 |  25\n         -45 |  50\n         -35 |  50\n         -25 |  50\n         -15 |  50\n          -5 |  50\n           5 |  50\n          15 |  50\n          25 |  50\n          35 |  50\n          45 | 130\n          95 | 555\n         105 |   0\n\n-- Variable sized buckets with origin\nCREATE MATERIALIZED VIEW cagg_1_hour_variable_bucket_fixed_origin\n  WITH (timescaledb.continuous) AS\n  SELECT time_bucket('1 year', time, origin=>'2000-01-01 01:05:00 UTC'::timestamptz, timezone=>'UTC') AS hour_bucket, max(value) AS max_value\n    FROM temperature\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:835: NOTICE:  refreshing continuous aggregate \"cagg_1_hour_variable_bucket_fixed_origin\"\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_1_hour_variable_bucket_fixed_origin';\n user_view_schema |              user_view_name              |                                               bucket_func                                               | bucket_width |        bucket_origin         | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+------------------------------------------+---------------------------------------------------------------------------------------------------------+--------------+------------------------------+---------------+-----------------+--------------------\n public           | cagg_1_hour_variable_bucket_fixed_origin | public.time_bucket(interval,timestamp with time zone,pg_catalog.text,timestamp with time zone,interval) | @ 1 year     | Fri Dec 31 17:05:00 1999 PST |               | UTC             | f\n\nDROP MATERIALIZED VIEW cagg_1_hour_variable_bucket_fixed_origin;\npsql:include/cagg_query_common.sql:837: NOTICE:  drop cascades to 2 other objects\n-- Variable due to the used timezone\nCREATE MATERIALIZED VIEW cagg_1_hour_variable_bucket_fixed_origin2\n  WITH (timescaledb.continuous) AS\n  SELECT time_bucket('1 hour', time, origin=>'2000-01-01 01:05:00 UTC'::timestamptz, timezone=>'UTC') AS hour_bucket, max(value) AS max_value\n    FROM temperature\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:844: NOTICE:  refreshing continuous aggregate \"cagg_1_hour_variable_bucket_fixed_origin2\"\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_1_hour_variable_bucket_fixed_origin2';\n user_view_schema |              user_view_name               |                                               bucket_func                                               | bucket_width |        bucket_origin         | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+-------------------------------------------+---------------------------------------------------------------------------------------------------------+--------------+------------------------------+---------------+-----------------+--------------------\n public           | cagg_1_hour_variable_bucket_fixed_origin2 | public.time_bucket(interval,timestamp with time zone,pg_catalog.text,timestamp with time zone,interval) | @ 1 hour     | Fri Dec 31 17:05:00 1999 PST |               | UTC             | f\n\nDROP MATERIALIZED VIEW cagg_1_hour_variable_bucket_fixed_origin2;\npsql:include/cagg_query_common.sql:846: NOTICE:  drop cascades to 2 other objects\n-- Variable with offset\nCREATE MATERIALIZED VIEW cagg_1_hour_variable_bucket_fixed_origin3\n  WITH (timescaledb.continuous) AS\n  SELECT time_bucket('1 year', time, \"offset\"=>'5 minutes'::interval) AS hour_bucket, max(value) AS max_value\n    FROM temperature\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:853: NOTICE:  refreshing continuous aggregate \"cagg_1_hour_variable_bucket_fixed_origin3\"\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_1_hour_variable_bucket_fixed_origin3';\n user_view_schema |              user_view_name               |                          bucket_func                           | bucket_width | bucket_origin | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+-------------------------------------------+----------------------------------------------------------------+--------------+---------------+---------------+-----------------+--------------------\n public           | cagg_1_hour_variable_bucket_fixed_origin3 | public.time_bucket(interval,timestamp with time zone,interval) | @ 1 year     |               | @ 5 mins      |                 | f\n\nDROP MATERIALIZED VIEW cagg_1_hour_variable_bucket_fixed_origin3;\npsql:include/cagg_query_common.sql:855: NOTICE:  drop cascades to 2 other objects\n---\n-- Test with blocking a few broken configurations\n---\n\\set ON_ERROR_STOP 0\n-- Unfortunately '\\set VERBOSITY verbose' cannot be used here to check the error details\n-- since it also prints the line number of the location, which is depended on the build\n-- Different time origin\nCREATE MATERIALIZED VIEW cagg_1_hour_origin\n  WITH (timescaledb.continuous) AS\n  SELECT time_bucket('1 hour', time, origin=>'2000-01-02 01:00:00 PST'::timestamptz) AS hour_bucket, max(value) AS max_value\n    FROM temperature\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:870: NOTICE:  refreshing continuous aggregate \"cagg_1_hour_origin\"\nCREATE MATERIALIZED VIEW cagg_1_week_origin\n  WITH (timescaledb.continuous) AS\n  SELECT time_bucket('1 week', hour_bucket, origin=>'2022-01-02 01:00:00 PST'::timestamptz) AS week_bucket, max(max_value) AS max_value\n    FROM cagg_1_hour_origin\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:876: ERROR:  cannot create continuous aggregate with different bucket origin values\n-- Different time offset\nCREATE MATERIALIZED VIEW cagg_1_hour_offset\n  WITH (timescaledb.continuous) AS\n  SELECT time_bucket('1 hour', time, \"offset\"=>'30m'::interval) AS hour_bucket, max(value) AS max_value\n    FROM temperature\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:883: NOTICE:  refreshing continuous aggregate \"cagg_1_hour_offset\"\nCREATE MATERIALIZED VIEW cagg_1_week_offset\n  WITH (timescaledb.continuous) AS\n  SELECT time_bucket('1 week', hour_bucket, \"offset\"=>'35m'::interval) AS week_bucket, max(max_value) AS max_value\n    FROM cagg_1_hour_offset\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:889: ERROR:  cannot create continuous aggregate with different bucket offset values\n-- Cagg with NULL offset on top of cagg with non-NULL offset\n\\set VERBOSITY default\nCREATE MATERIALIZED VIEW cagg_1_week_null_offset\n  WITH (timescaledb.continuous) AS\n  SELECT time_bucket('1 week', hour_bucket, \"offset\"=>NULL::interval) AS week_bucket, max(max_value) AS max_value\n    FROM cagg_1_hour_offset\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:897: ERROR:  cannot create continuous aggregate with different bucket offset values\nDETAIL:  Time origin of \"public.cagg_1_week_null_offset\" [NULL] and \"public.cagg_1_hour_offset\" [@ 30 mins] should be the same.\n-- Cagg with non-NULL offset on top of cagg with NULL offset\nCREATE MATERIALIZED VIEW cagg_1_hour_null_offset\n  WITH (timescaledb.continuous) AS\n  SELECT time_bucket('1 hour', time, \"offset\"=>NULL::interval) AS hour_bucket, max(value) AS max_value\n    FROM temperature\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:904: NOTICE:  refreshing continuous aggregate \"cagg_1_hour_null_offset\"\nHINT:  Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation.\nCREATE MATERIALIZED VIEW cagg_1_week_non_null_offset\n  WITH (timescaledb.continuous) AS\n  SELECT time_bucket('1 week', hour_bucket, \"offset\"=>'35m'::interval) AS week_bucket, max(max_value) AS max_value\n    FROM cagg_1_hour_null_offset\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:910: ERROR:  cannot create continuous aggregate with different bucket offset values\nDETAIL:  Time origin of \"public.cagg_1_week_non_null_offset\" [@ 35 mins] and \"public.cagg_1_hour_null_offset\" [NULL] should be the same.\n\\set VERBOSITY terse\n-- Different integer offset\nCREATE MATERIALIZED VIEW cagg_int_offset_5\n    WITH (timescaledb.continuous, timescaledb.materialized_only=false)\n    AS SELECT time_bucket('10', time, \"offset\"=>5) AS time, SUM(data) AS value\n        FROM table_int\n        GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:918: NOTICE:  refreshing continuous aggregate \"cagg_int_offset_5\"\nCREATE MATERIALIZED VIEW cagg_int_offset_10\n    WITH (timescaledb.continuous, timescaledb.materialized_only=false)\n    AS SELECT time_bucket('10', time, \"offset\"=>10) AS time, SUM(value) AS value\n        FROM cagg_int_offset_5\n        GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:924: ERROR:  cannot create continuous aggregate with different bucket offset values\n\\set ON_ERROR_STOP 1\nDROP MATERIALIZED VIEW cagg_1_hour_origin;\npsql:include/cagg_query_common.sql:928: NOTICE:  drop cascades to 2 other objects\nDROP MATERIALIZED VIEW cagg_1_hour_offset;\npsql:include/cagg_query_common.sql:929: NOTICE:  drop cascades to 2 other objects\nDROP MATERIALIZED VIEW cagg_int_offset_5;\npsql:include/cagg_query_common.sql:930: NOTICE:  drop cascades to 3 other objects\n---\n-- CAGGs on CAGGs tests\n---\nCREATE MATERIALIZED VIEW cagg_1_hour_offset\n  WITH (timescaledb.continuous) AS\n  SELECT time_bucket('1 hour', time, origin=>'2000-01-02 01:00:00 PST'::timestamptz) AS hour_bucket, max(value) AS max_value\n    FROM temperature\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:939: NOTICE:  refreshing continuous aggregate \"cagg_1_hour_offset\"\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_1_hour_offset';\n user_view_schema |   user_view_name   |                                  bucket_func                                   | bucket_width |        bucket_origin         | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+--------------------+--------------------------------------------------------------------------------+--------------+------------------------------+---------------+-----------------+--------------------\n public           | cagg_1_hour_offset | public.time_bucket(interval,timestamp with time zone,timestamp with time zone) | @ 1 hour     | Sun Jan 02 01:00:00 2000 PST |               |                 | t\n\nCREATE MATERIALIZED VIEW cagg_1_week_offset\n  WITH (timescaledb.continuous) AS\n  SELECT time_bucket('1 week', hour_bucket, origin=>'2000-01-02 01:00:00 PST'::timestamptz) AS week_bucket, max(max_value) AS max_value\n    FROM cagg_1_hour_offset\n    GROUP BY 1 ORDER BY 1;\npsql:include/cagg_query_common.sql:946: NOTICE:  refreshing continuous aggregate \"cagg_1_week_offset\"\nSELECT * FROM caggs_info WHERE user_view_name = 'cagg_1_week_offset';\n user_view_schema |   user_view_name   |                                  bucket_func                                   | bucket_width |        bucket_origin         | bucket_offset | bucket_timezone | bucket_fixed_width \n------------------+--------------------+--------------------------------------------------------------------------------+--------------+------------------------------+---------------+-----------------+--------------------\n public           | cagg_1_week_offset | public.time_bucket(interval,timestamp with time zone,timestamp with time zone) | @ 7 days     | Sun Jan 02 01:00:00 2000 PST |               |                 | t\n\n-- Compare output\nSELECT * FROM cagg_1_week_offset;\n         week_bucket          | max_value \n------------------------------+-----------\n Sun Jan 30 01:00:00 2000 PST |         5\n Sun Jan 26 01:00:00 2020 PST |         6\n\nSELECT time_bucket('1 week', time, origin=>'2000-01-02 01:00:00 PST'::timestamptz), max(value) FROM temperature GROUP BY 1 ORDER BY 1;\n         time_bucket          | max \n------------------------------+-----\n Sun Jan 30 01:00:00 2000 PST |   5\n Sun Jan 26 01:00:00 2020 PST |   6\n\nINSERT INTO temperature values('2030-01-01 05:05:00 PST', 22222);\nINSERT INTO temperature values('2030-01-03 05:05:00 PST', 55555);\n-- Compare real-time functionality\nALTER MATERIALIZED VIEW cagg_1_hour_offset SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW cagg_1_week_offset SET (timescaledb.materialized_only=false);\nSELECT * FROM cagg_1_week_offset;\n         week_bucket          | max_value \n------------------------------+-----------\n Sun Jan 30 01:00:00 2000 PST |         5\n Sun Jan 26 01:00:00 2020 PST |         6\n Sun Dec 30 01:00:00 2029 PST |     55555\n\nSELECT time_bucket('1 week', time, origin=>'2000-01-02 01:00:00 PST'::timestamptz), max(value) FROM temperature GROUP BY 1 ORDER BY 1;\n         time_bucket          |  max  \n------------------------------+-------\n Sun Jan 30 01:00:00 2000 PST |     5\n Sun Jan 26 01:00:00 2020 PST |     6\n Sun Dec 30 01:00:00 2029 PST | 55555\n\n-- Test refresh\nCALL refresh_continuous_aggregate('cagg_1_hour_offset', NULL, NULL);\nCALL refresh_continuous_aggregate('cagg_1_week_offset', NULL, NULL);\n-- Everything should be now materailized\nALTER MATERIALIZED VIEW cagg_1_hour_offset SET (timescaledb.materialized_only=false);\nALTER MATERIALIZED VIEW cagg_1_week_offset SET (timescaledb.materialized_only=false);\nSELECT * FROM cagg_1_week_offset;\n         week_bucket          | max_value \n------------------------------+-----------\n Sun Jan 30 01:00:00 2000 PST |         5\n Sun Jan 26 01:00:00 2020 PST |         6\n Sun Dec 30 01:00:00 2029 PST |     55555\n\nSELECT time_bucket('1 week', time, origin=>'2000-01-02 01:00:00 PST'::timestamptz), max(value) FROM temperature GROUP BY 1 ORDER BY 1;\n         time_bucket          |  max  \n------------------------------+-------\n Sun Jan 30 01:00:00 2000 PST |     5\n Sun Jan 26 01:00:00 2020 PST |     6\n Sun Dec 30 01:00:00 2029 PST | 55555\n\nTRUNCATE temperature;\nSELECT * FROM cagg_1_week_offset;\n         week_bucket          | max_value \n------------------------------+-----------\n Sun Jan 30 01:00:00 2000 PST |         5\n Sun Jan 26 01:00:00 2020 PST |         6\n Sun Dec 30 01:00:00 2029 PST |     55555\n\nSELECT time_bucket('1 week', time, origin=>'2000-01-02 01:00:00 PST'::timestamptz), max(value) FROM temperature GROUP BY 1 ORDER BY 1;\n time_bucket | max \n-------------+-----\n\nDROP VIEW caggs_info;\n"
  },
  {
    "path": "tsl/test/expected/cagg_tableam.out",
    "content": "-- This file and its contents are licensed under the Timescale License.\n-- Please see the included NOTICE for copyright information and\n-- LICENSE-TIMESCALE for a copy of the license.\n\\c :TEST_DBNAME :ROLE_SUPERUSER\nCREATE ACCESS METHOD heap2 TYPE TABLE HANDLER heap_tableam_handler;\nSET ROLE :ROLE_DEFAULT_PERM_USER;\nCREATE VIEW cagg_info AS\nWITH\n  caggs AS (\n    SELECT format('%I.%I', user_view_schema, user_view_name)::regclass AS user_view,\n           format('%I.%I', ht.schema_name, ht.table_name)::regclass AS mat_relid\n      FROM _timescaledb_catalog.hypertable ht,\n           _timescaledb_catalog.continuous_agg cagg\n     WHERE ht.id = cagg.mat_hypertable_id\n  )\nSELECT user_view,\n       relname AS mat_table,\n       (SELECT spcname FROM pg_tablespace WHERE oid = reltablespace) AS tablespace\n  FROM pg_class JOIN caggs ON pg_class.oid = caggs.mat_relid;\nCREATE TABLE whatever(time BIGINT NOT NULL, data INTEGER);\nSELECT hypertable_id AS whatever_nid\n  FROM create_hypertable('whatever', 'time', chunk_time_interval => 10)\n\\gset\nCREATE OR REPLACE FUNCTION integer_now_test() RETURNS bigint\nLANGUAGE SQL STABLE AS $$\n\tSELECT coalesce(max(time), bigint '0') FROM whatever\n$$;\nSELECT set_integer_now_func('whatever', 'integer_now_test');\n set_integer_now_func \n----------------------\n \n\nINSERT INTO whatever SELECT i, i FROM generate_series(0, 29) AS i;\n-- Checking that the access method for the materialized hypertable is\n-- set to the correct access method.\nCREATE MATERIALIZED VIEW tableam_view USING heap2\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT time_bucket('5', time), COUNT(data)\n  FROM whatever GROUP BY 1 WITH NO DATA;\nCREATE MATERIALIZED VIEW notableam_view\nWITH (timescaledb.continuous, timescaledb.materialized_only=true) AS\nSELECT time_bucket('5', time), COUNT(data)\n  FROM whatever GROUP BY 1 WITH NO DATA;\nSELECT user_view, mat_table, amname\nFROM cagg_info,\n     LATERAL (SELECT relam FROM pg_class WHERE relname = mat_table) AS cls,\n     LATERAL (SELECT amname FROM pg_am WHERE oid = relam) AS am\nWHERE user_view::text IN ('tableam_view', 'notableam_view');\n   user_view    |         mat_table          | amname \n----------------+----------------------------+--------\n tableam_view   | _materialized_hypertable_2 | heap2\n notableam_view | _materialized_hypertable_3 | heap\n\n-- Check that the view with the other access method actually works.\nSELECT * FROM notableam_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n\nSELECT * FROM tableam_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n\nCALL refresh_continuous_aggregate('notableam_view', NULL, NULL);\nCALL refresh_continuous_aggregate('tableam_view', NULL, NULL);\nSELECT * FROM notableam_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     5\n           5 |     5\n          10 |     5\n          15 |     5\n          20 |     5\n          25 |     5\n\nSELECT * FROM tableam_view ORDER BY 1;\n time_bucket | count \n-------------+-------\n           0 |     5\n           5 |     5\n          10 |     5\n          15 |     5\n          20 |     5\n          25 |     5\n\nDROP MATERIALIZED VIEW notableam_view;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_3_4_chunk\nDROP MATERIALIZED VIEW tableam_view;\nNOTICE:  drop cascades to table _timescaledb_internal._hyper_2_5_chunk\n"
  },
  {
    "path": "tsl/test/fuzzing/compression/array-bool/empty",
    "content": ""
  },
  {
    "path": "tsl/test/fuzzing/compression/array-uuid/empty",
    "content": ""
  },
  {
    "path": "tsl/test/fuzzing/compression/bool-bool/empty",
    "content": ""
  },
  {
    "path": "tsl/test/fuzzing/compression/dictionary-bool/empty",
    "content": ""
  },
  {
    "path": "tsl/test/fuzzing/compression/dictionary-uuid/empty",
    "content": ""
  },
  {
    "path": "tsl/test/fuzzing/compression/uuid-uuid/empty",
    "content": ""
  }
]